## Context
`cx incidents list` was producing frequent 400s because the v5 list
endpoint expects repeated filter fields, fully qualified incident enum
values, and paginated request/response shapes. The CLI was sending
several filters as scalar strings and did not bound or paginate list
responses, which made both API errors and oversized responses likely.
## Linked Issues
https://linear.app/coralogix/issue/AIAPP-1882/fix-incidents-list-endpoint
## Design
The list command now builds a typed `ListIncidentsOptions` object from
clap arguments, converts that into the v5 JSON request body in one
helper, and loops through `pagination.nextPageToken` until the requested
limit is reached. The API layer accepts the v5 `pagination` response
while retaining compatibility with older top-level `nextPageToken`.
Rendering stays unchanged after the command has accumulated incidents
across selected profiles.
## Key Decisions
The CLI accepts short user-facing values such as `CRITICAL`,
`TRIGGERED`, and `created_at`, then normalizes them to v5 enum values
like `INCIDENT_SEVERITY_CRITICAL`, `INCIDENT_STATUS_TRIGGERED`, and
`INCIDENTS_FIELDS_CREATED_TIME`. Results are bounded to `--limit 100` by
default to avoid unexpectedly large payloads; callers can opt into
`--all` when they deliberately need every page. When every selected
profile fails, list now exits nonzero instead of silently returning an
empty result, since the previous behavior hid live API failures.
## Changes
- Changes `cx incidents list` filters from scalar request fields to v5
repeated fields for status, severity, state, assignee, application, and
subsystem filters.
- Adds list flags for state, application/subsystem, contextual labels,
query targeting, muted/unmuted, created and duration ranges, ordering,
page size/token, bounded limits, and explicit `--all` pagination.
- Parses v5 pagination responses and fetches additional pages up to the
requested limit.
- Validates contextual labels and `--query` target selection before
sending malformed requests.
- Updates incident-management skill guidance to recommend bounded
queries and the supported filter set.
- Extends ignored live e2e coverage and adds a wiremock integration test
for request shape and pagination.
## Testing
- `cargo test --lib incidents --locked`
- `cargo test --test incidents --locked`
- `cargo test incidents --locked`
- `cargo test --test e2e incidents --locked`
- `cargo test --test e2e incidents --locked -- --ignored --nocapture`
- `cargo fmt --check`
- `cargo clippy --locked -- -D warnings`
- Manual live checks with `target/debug/cx incidents list` covering:
`--limit`, status/severity filters, state filter, created range plus
ordering, muted/unmuted, app/subsystem, contextual labels, repeated
status/severity, duration range, and pagination across pages.
- Manual live search checks: contextual-label search and some
`searchQuery.incidentField` values succeeded without 400s; `ID` and
`SEVERITY` incident-field search returned v5 server 500s.
## Risks & Rollout
This changes the public `cx incidents list` interface by making existing
`--status`, `--severity`, and `--assignee` repeatable while preserving
single-use invocations. The default result set is now bounded to 100
incidents, which is safer for agents but may return fewer rows than
before for users who expected an unbounded first response; `--all`
restores full pagination. Rollback is the single commit if the v5
request shape causes unexpected compatibility issues.
## Out of Scope / Follow-ups
Grouping global help options is intentionally left for a separate task.
v5 incident search has server-side inconsistencies: the OpenAPI schema
says `searchQuery.field` is required, the live endpoint rejects that
field, and `searchQuery.incidentField` with `ID` or `SEVERITY` returns
500. This PR avoids known malformed no-target search requests but does
not attempt to work around those server-side search failures.
## Summary
- Adds `range_samples_to_toon_rows()` to flatten each `(metric,
timestamp, value)` triple from a range query into its own TOON row,
matching the pattern established by `instant_samples_to_toon_rows()`
- Splits the `OutputFormat::Agents` arm in `run_query_range()` away from
`OutputFormat::Json` so `-o agents` produces TOON output instead of raw
JSON
- Epoch timestamps from the PromQL API are converted to ISO-8601 via
`chrono` (e.g. `2024-06-22T00:00:00Z`) to prevent LLM
timestamp-conversion errors
- Column order: sorted metric label keys → optional `profile` →
`timestamp` → `value`
## Tests
- 10 new unit tests for `range_samples_to_toon_rows` covering: empty
input, single/multiple series, differing labels (padding), alphabetical
key sort, ISO-8601 timestamp formatting, profile column
inclusion/exclusion, and column order
- 1 new wiremock integration test (`range_query_agents_output`)
verifying the full API → parse → TOON encode pipeline
## Validation
```
cargo fmt --check ✓
cargo clippy --locked -- -D warnings ✓
cargo test --locked 315 passed, 0 failed
```
🤖 Generated with [Claude Code](https://claude.ai/claude-code)
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
## Summary
- Replaces the full interactive session transcript in the Quick Start
with a single copy-paste command and a numbered list of what to fill in
at each prompt
- The old block mixed CLI output with user inputs (`eu2`, `production`,
`file`) making it hard to scan and impossible to copy-paste meaningfully
- Credential storage explanation is now inline in the prompt list
instead of buried in a paragraph after the block
## Before / After
**Before:** a 15-line terminal session transcript showing inputs and
outputs mixed together
**After:**
```sh
cx profiles add
```
Then a numbered list: Authentication method → Region → Label →
Credential storage
## Context
Feedback from Yael Vogel / Niv Hertz via the Coralogix docs team (Slack,
2026-05-06). Tracked in CX-41355.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CXAI-2089
## Summary
- Document `--read-only` flag, `CX_READ_ONLY` env var, and `read_only`
config option in `docs/configuration.md`
- Add previously undocumented `allow_risky_commands` and `olly_enabled`
fields to the global config table and example
- Rename `allow_costly_commands` to `olly_enabled` throughout code,
tests, and docs - the old name was overly generic for what it actually
gates (`olly ask`)
## Test plan
- [x] `cargo fmt` - clean
- [x] `cargo clippy` - no warnings
- [x] `cargo test` - 590 passed
- [x] `cargo test --test risky_costly_gating` - 10 passed (includes
renamed olly gating tests)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
## Summary
- OAuth profiles can now persist tokens either inline in the profile
TOML (`credential_storage = "file"`, the new default) or in the OS
keyring (`os_store`), mirroring the existing API-key flow. `cx profiles
add` prompts for the choice.
- Adds `StoredOAuthTokens` to the profile schema (`access_token`,
`refresh_token`, `id_token`, `expiry`), serialized inline only when
`credential_storage = "file"` and skipped otherwise. File-mode profile
TOMLs are still written with `0600` perms.
- `oauth::resolve_token` now takes the chosen storage backend and any
cached file tokens, and returns refreshed tokens to the caller so
`resolve_single` can persist them back to disk after a refresh. Keyring
path is unchanged in behavior.
- Refactors: extracted `unix_now_secs()` helper (replaces 4 inline
`SystemTime` calls); replaced the `&[&str]` storage-options array +
`starts_with` lookup with a paired `&[(&str, CredentialStorage)]` table
and a `select_credential_storage()` helper used by both configure paths.
- Tests: 7 new unit tests in `oauth.rs` covering `tokens_to_stored`
(with/without `expires_in`), `cached_token_is_valid` (expiry path +
JWT-fallback path), and three `resolve_token` File-mode paths
(cached-valid no-network, missing-tokens error, expired-no-refresh
error). New TOML round-trip test for OAuth profiles with file-stored
tokens.
- Docs: `docs/configuration.md` updated (quick-start example output,
OAuth section, profile-fields table, new file-storage example with
sample `[oauth_tokens]`); `README.md` credential-storage paragraph
rewritten.
## Test plan
- [x] `cargo test --lib` (286 + 7 new = 293 passing)
- [x] `cargo fmt`
- [x] `cargo clippy --lib` clean
- [ ] Manual: `cx profiles add` -> OAuth -> pick `file` -> verify TOML
contains `[oauth_tokens]`, run a query, force token expiry, verify
refresh updates the file in place
- [ ] Manual: `cx profiles add` -> OAuth -> pick `os-store` -> verify
TOML has no `[oauth_tokens]`, refresh still works against keyring
- [ ] Manual: re-add an existing profile and switch storage backend in
both directions; verify no stale state in keyring or TOML
## Known follow-up
- Concurrent `cx` invocations on the same file-mode OAuth profile can
race when both find the access token expired and both refresh; the
loser's rotated refresh token gets clobbered by the non-atomic
`fs::write` in `save_profile`. Low likelihood in normal interactive use,
more plausible under fan-out / scripts. Worth a follow-up to use atomic
rename or a file lock.
- If the IdP omits `refresh_token` from a refresh response (some don't
rotate), both backends currently lose the cached refresh token.
Pre-existing bug for the keyring path; file path inherits it.
## Summary
Three doc fixes based on customer report and portal QA.
### 1. DataPrime string quoting in shell examples
Shell examples use double quotes for DataPrime string literals, but
DataPrime only accepts single quotes. Running the examples as written
produces:
```
error from profile 'default': API request failed (400): Compilation errors:
- malformed expression at [0:22:22-0:23:23]: `"`
- excessive input at [0:28:28-0:29:29]: `"`
```
- Severity comparisons: remove quotes — `ERROR`, `INFO`, etc. are enum
values in DataPrime and don't need quoting
- String comparisons (e.g. `serviceName`): switch to double-quoted outer
shell arg with escaped `$` and single-quoted DataPrime string literal
### 2. `<details open>` → `<details open markdown="1">`
MkDocs Material requires `markdown="1"` on `<details>` elements to
render Markdown content inside them correctly.
### 3. Fix 404 on `skills/README.md` link
The relative `skills/README.md` link in the AI agent skills section
resolves correctly on GitHub but 404s on the Coralogix docs portal (the
`skills/` directory is not synced). Changed to an absolute GitHub URL.
**Files changed:** `README.md`, `docs/multi-profile.md`,
`docs/time-syntax.md`
Reported via Coralogix support (DOCS-23).
AIAG-696
## Summary
### Full API coverage
- Implement remaining cx CLI commands covering all Coralogix APIs
(dashboards, views, SLOs, incidents, notifications, webhooks,
enrichments, parsing-rules, TCO, quotas, usage, archive, integrations,
IAM, recording-rules, E2M, search-fields) - 27 command groups total
- Add user-facing skills for all command groups (cx-observability-setup,
cx-cost-optimization, cx-incident-management, cx-data-pipeline,
cx-platform-admin, cx-create-dashboard)
- Rename all skills to cx- prefix for consistency, update
cross-references and docs
- Add integration tests (wiremock) and E2E test scaffolding for all new
commands
- Update CLAUDE.md, README, CODEOWNERS, architecture docs, and
contributor guides
- Bug fixes: data_usage timestamp parsing, users error propagation,
team_groups unused params, dashboard skill API wire values, stale skill
paths
### CLI safety features
- Add `--read-only` global flag that blocks all write operations before
credential resolution - safe for exploration and agent use
- Add agent mode detection (`CX_AGENT_MODE`, `CLAUDECODE`, `CLAUDE_CODE`
env vars) - blocks write operations unless `--yes` is passed
- Wire `confirm_destructive()` to all write subcommands across all 27
command groups (104 call sites) - interactive confirmation for
create/update/delete/enable/disable operations
- Mark `iam` and `archive` as `(risky)` in help output
- Add `[requires --yes]` tags to all write subcommand help text (102
annotations)
- Update skills with read-only and agent mode guidance
### Testing
- 441 tests passing (88 ignored E2E)
- Add `tests/write_command_gating/` - systematic verification that every
write command is gated
- Add `tests/read_only/` - read-only mode enforcement tests
- Add `tests/agent_mode/` - agent mode detection and blocking tests
- Add integration tests (wiremock) for all new command groups
- Add E2E test scaffolding for all commands
## Test plan
- [x] `cargo fmt --check` passes
- [x] `cargo clippy` passes with no warnings
- [x] `cargo test` passes (441 unit + integration tests)
- [ ] `cargo test --test e2e -- --ignored --test-threads=1` passes
against test team (requires CX_API_KEY)
- [ ] `cx schema` outputs updated command tree with all new commands
- [ ] Skills trigger correctly in Claude Code sessions
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CXAI-2055
## Summary
- Restructure source so each CLI command owns a directory:
`src/commands/<cmd>/{mod.rs,api.rs}` (was `src/commands/<cmd>.rs` +
`src/api/<cmd>.rs`). Rename `src/api/client.rs` → `src/api_client.rs`.
- Restructure tests to mirror the new layout: integration suites become
`tests/<cmd>/main.rs`, e2e suites become `tests/e2e/<cmd>/mod.rs`.
- Replace the single-line `* @coralogix/team-cxai` CODEOWNERS with
per-domain review gates (traces, metrics, dataprime, config-surface,
docs). All domains currently resolve to `@coralogix/team-cxai` as
placeholders — swap in real team handles when domain teams form.
- Update contributor docs and dev skills (`CLAUDE.md`,
`docs/architecture.md`, `docs/adding-a-command.md`,
`.claude/skills/add-command`, `.claude/skills/run-tests`) to reflect the
new per-command layout.
## Ticket
[AIKN-348](https://coralogix.atlassian.net/browse/AIKN-348)
## Summary
Refines the `create-dashboard` skill and extends the `cx` CLI so the
skill can verify its own queries and deploy the finished dashboard
end-to-end, instead of emitting JSON for the user to paste into the
Coralogix UI.
### Skill (`skills/create-dashboard/`)
- **Workflow expanded to 7 phases** — adds a live-verify phase (every
PromQL / DataPrime query runs through the `cx` CLI before deploy) and a
deploy phase (`cx dashboards create` into a user-chosen folder),
replacing the old "paste into Dashboards → Import" ending.
- **Stops duplicating query-language knowledge.** Phase 1 discovery and
the references now delegate to the sibling `metrics-query`,
`query-logs`, `query-spans`, `dataprime`, and `telemetry-querying`
skills. `references/query-syntax.md` is slimmed to
Coralogix-dashboard-specific gotchas only (``${__range}``,
`promqlQueryType`, widget filter syntax, common PromQL idioms) and
points readers at the sibling skill references for full language docs.
- **Live verification uses the dashboard's own time range.** Phase 5
substitutes ``${__range}`` with a concrete token derived from
`relativeTimeFrame` (e.g. `[48h]` for the default `172800s`) for the CLI
check, then restores ``${__range}`` in the emitted JSON so the dashboard
keeps the injected variable.
- **Prevents invented metric names early.** Phase 1 requires each
proposed metric to appear in `cx metrics search` output before being
used, catching the #1 failure mode before verification has to.
- Deleted `references/workflow.md` (100% redundant with the rewritten
`SKILL.md`).
- Refreshed `references/widget-templates.md` to point at the sibling
skills and Phase 5 live verification.
### CLI (`src/`)
- `src/api/dashboards.rs` — adds `DashboardsApi::create` (`POST
/mgmt/openapi/latest/dashboards/dashboards/v1`) and
`DashboardsApi::folders` (`GET
/mgmt/openapi/latest/dashboards/folders/v1`), plus `DashboardFolderItem`
/ `DashboardFoldersResponse` / `FolderIdValue` types.
- `src/commands/dashboards.rs` — new `run_create` handler (accepts a
bare dashboard JSON or a `{"dashboard": ...}` wrapper from file or
stdin, auto-generates the `requestId` envelope, optionally injects
`folderId` from `--folder`) and `run_folders_list` (table / JSON /
agents output), mirroring the `cx alerts create` pattern.
- `src/main.rs` — extends `DashboardsCmd` with `Create { from_file,
folder }` and `Folders { List }` and wires them through.
New CLI surface:
```
cx dashboards create --from-file <path|-> [--folder <id>]
cx dashboards folders list
```
## Test plan
- [x] `cargo fmt --check`
- [x] `cargo clippy` (no new warnings in changed files; there's a
pre-existing warning in `src/api/dataprime.rs` unrelated to this change)
- [x] `cargo build`
- [x] `cargo test --lib` (89/89 pass locally)
- [x] `cx dashboards create --help` / `cx dashboards folders list
--help` show the new subcommands
- [x] End-to-end: `cx dashboards folders list` against a live profile,
then `cx dashboards create --from-file <dashboard>.json --folder <id>`
returns the new dashboard ID
[AIKN-348]:
https://coralogix.atlassian.net/browse/AIKN-348?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
## Summary
- Add `CONTRIBUTING.md` with ownership model, PR review process, and
contribution lifecycle
- Add `docs/architecture.md` covering execution flow, key modules,
config, and output modes
- Add `docs/adding-a-command.md` step-by-step guide for implementing new
CLI commands
- Add `docs/adding-a-skill.md` guide for creating Claude Code skill
plugins
These docs are aimed at Coralogix domain teams onboarding to contribute
commands and skills to the cx CLI.
---------