mirror of
https://github.com/coralogix/cx-cli.git
synced 2026-09-14 16:15:45 +08:00
78cbd9e25c
## Summary - After a successful command on any entity in the coverage table below, `cx` prints a single `View in Coralogix: <url>` line to stderr per profile. The link is **stderr-only** - `-o json`/`-o agents` output never carries a `consoleUrl` field or any other trace of it, so scripting against structured output never needs to account for it. - Console URL resolution order: (1) explicit `console_url` in the profile TOML - pure config, no API call; (2) a fresh cached `team_url` in the profile TOML, written back the first time step 3 resolves it and reused for 7 days so repeated invocations skip the round-trip; (3) `GET /identity/whoami`'s `team_url` field, resolved live and then cached per step 2 - the default on a cold cache, no config needed; (4) no link. A failed/unusable `/identity/whoami` response, or an expired-and-unrefreshable cache, fails quietly - no stderr hint, just no link - and the command still succeeds normally. See `docs/configuration.md` § "Console links". - Console links can be suppressed entirely with `--no-console-link` (single invocation), `CX_NO_CONSOLE_LINK` (env var / CI job), or `no_console_link = true` in `~/.cx/config.toml` (all invocations) - narrowest to broadest scope. Suppressing also skips the `/identity/whoami` lookup on a cold cache. - What the single stderr line points at differs by command shape, but it is always exactly one link per profile - never a per-row link: entity commands (`get`/`create`/`update`/...) point at that entity's own page; list/search commands that return many rows (`alerts list`, `iam api-keys list`, `tco list`, `dashboards check`, etc.) point at the shared list/settings page instead, printed once per profile regardless of row count. - Empty results skip link printing entirely, so an empty list never advertises a page link. - 132 of 252 subcommands across 30 command groups print a link; the Command coverage table below documents every subcommand's ✅/✖ status and why. ## Command coverage Every `cx` command group and **every individual subcommand** in it (enumerated from `cx schema`, not hand-curated), each explicitly marked whether it prints a "View in Coralogix" console link (✅) or not (✖) - including read-only subcommands. 252 subcommands total across 30 command groups; **132 print a link**. **Every single ✅/✖ has its own stated reason below** - not just one blanket note per command group. Three distinct kinds of "✖" show up, and each row says which kind applies: - **(a) no page exists at all** - genuinely nothing to link to, confirmed against `cx-web-workspace` routing source or the command's own read-only/local nature. - **(b) a route exists but reads weren't wired for that group** - the group is a *per-entity* group (an `:id` path segment or `?id=`-style query param) where only some subcommand(s) are linked; the remaining backlog after this round is listed below the table. - **(c) nothing left to point at** - `delete`/`bulk-delete` subcommands, where the entity no longer exists afterward. | Command group | Every subcommand (✅ = prints a console link, ✖ = does not) | Notes | |---|---|---| | `profiles` | `✖ list`, `✖ add`, `✖ delete`, `✖ set-default` | (a) Local command: reads/writes `~/.cx/profiles/*.toml` on disk only - there is no Coralogix console concept of a "profile", so none of the four has a page | | `completions` | `✖ generate`, `✖ install`, `✖ refresh` | (a) Local command: generates/installs/refreshes shell completion scripts on disk; no console counterpart for any of the three | | `cleanup` | `✖ cleanup` | (a) Local command: deletes stale temp files under `~/.cx`; purely local filesystem operation | | `logs` | `✖ logs` | (a) Read-only DataPrime query; Explore's ad-hoc query screen isn't a bookmarkable per-query URL (only *saved views* are, already covered by the `views` link) | | `metrics` | `✖ query`, `✖ query-range`, `✖ search`, `✖ get-labels` | (a) Read-only PromQL queries/label lookups; same reasoning as `logs` - no addressable per-query page | | `spans` | `✖ spans` | (a) Read-only DataPrime query over spans; same reasoning as `logs` | | `dashboards` | `✖ catalog`, `✅ get`, `✅ create`, `✅ replace`, `✅ check`, `✖ delete`, `✖ search`, `✖ query-search`, `✖ folders/list`, `✖ folders/create`, `✖ folders/delete` | `create`/`replace`/`get` ✅ → `{base}/dashboards/{id}`, a real per-id route confirmed in `routing-utils.ts`. `check` ✅ when checking a *stored* dashboard by id (`cx dashboards check <id>`) - same route as `get`; prints no link when checking `--from-file` since there's no persisted entity. `catalog`/`search`/`query-search` ✖ (a) - list/search operations return many dashboards, no single one to point at. `delete` ✖ (c) - dashboard no longer exists afterward. `folders/*` ✖ (a) - `folderId` lives only in the data model, confirmed never written to the URL | | `alerts` | `✅ list`, `✅ get`, `✅ create`, `✖ delete`, `✅ enable`, `✅ disable`, `✖ events`, `✖ event-stats`, `✖ suppression-rules/create`, `✖ suppression-rules/update`, `✖ suppression-rules/list`, `✖ suppression-rules/get`, `✖ suppression-rules/delete` | `create`/`get` ✅ → `{base}/alerts/{id}`. `list` ✅ - same as the static-page groups: the alerts-list page (`{base}/alerts`) link is printed once per profile on stderr, not a per-row link. `delete` ✖ (c, alert gone afterward). `enable`/`disable` ✅ - status toggle on an existing alert with a known id, same route as `create`/`get`. `events`/`event-stats` ✖ (a) - aggregate event history/stats across time, not a single alert's own page. The whole `suppression-rules` group is ✖ here. The route `{base}/suppression-rules?edit={id}` (top-level, not nested under `/alerts`) is confirmed, but the console resolves `?edit=` against the rule's `uniqueIdentifier`, and the CLI's response model only parses the API's `id` field - which is the rule *version* id - so the link silently fell back to the bare list page. Fixing it needs a response-model change, not a link change, so `create`/`update` were pulled back out of this PR (same treatment as `parsing-rules` below); the fix plus links for `create`/`update`/`get`/`list` land together in FORGE-710. `delete` ✖ (c) regardless | | `cases` | `✅ get`, `✅ update`, `✅ comment`, `✅ assign`, `✅ unassign`, `✅ acknowledge`, `✅ unacknowledge`, `✅ resolve`, `✅ close`, `✅ set-priority`, `✅ clear-priority`, `✖ events/list`, `✖ events/get`, `✖ notifications` | `get` and all ten lifecycle-mutation subcommands ✅ → `{base}/cases?id={id}` (confirmed via `SELECTED_CASE_QUERY_PARAM`) - each acts on one already-known case id. No `create` (cases are pipeline-generated, never created via this CLI). `events/list`/`events/get` ✖ (a) - a case's audit/event history is a distinct sub-resource with no page of its own. `notifications` ✖ (a) - reads a case's notification-routing settings, same distinct-sub-resource reasoning | | `notifications` | `✅ connectors/create`, `✅ connectors/update`, `✖ connectors/list`, `✅ connectors/get`, `✖ connectors/delete`, `✖ connectors/types`, `✖ connectors/entity-types`, `✖ connectors/entity-subtypes`, `✅ routers/create`, `✅ routers/update`, `✖ routers/list`, `✖ routers/get`, `✖ routers/delete`, `✖ routers/validate-matcher`, `✖ presets/list`, `✖ presets/get`, `✖ presets/create`, `✖ presets/update`, `✖ presets/delete`, `✖ presets/set-default`, `✖ test/connector`, `✖ test/destination`, `✖ test/preset`, `✖ test/routing-condition`, `✖ test/template-render` | `connectors create`/`update`/`get` ✅ → `?id=` auto-open route. `connectors/list` ✖ (a, enumerates many) and `connectors/delete` ✖ (c, gone afterward). `routers create`/`update` ✅ → same `?id=` pattern; its `list` ✖ (a), `get` ✖ (b, not wired yet), and `delete` ✖ (c) follow the same reasoning. `connectors/types`/`entity-types`/`entity-subtypes` ✖ (a) - list the *taxonomy* of connector kinds (string enum values), not any actual connector instance. `routers/validate-matcher` ✖ (a) - a stateless dry-run check, nothing persisted. `presets/*` ✖ (a) - a real route exists (`/presets/:entityType?id=`) but requires resolving `entityType` from the payload first, a materially bigger change than the query-param groups above; tracked as backlog. `test/*` ✖ (a) - fires a one-off test notification; nothing is created or persisted | | `usage` | `✅ summary`, `✅ daily`, `✅ logs-count`, `✅ spans-count`, `✅ capabilities`, `✅ query`, `✅ export-status` | All seven ✅ → `{base}/settings/datausage`. `usage` is 100% read-only with no mutation to gate on at all, so every subcommand links to the one page that exists regardless of which of the seven you ran | | `tco` | `✅ list`, `✅ get`, `✅ create`, `✅ update`, `✅ delete`, `✅ reorder`, `✅ test`, `✅ settings`, `✅ settings-update` | All nine ✅ → `{base}/tco-policies`. TCO is a single settings page per team regardless of subcommand - list/get/test/settings (read-only) and create/update/delete/reorder/settings-update (mutations) all point at the same page, and even `delete` still links here since the *page* (not a per-policy route) is what's being pointed at | | `retentions` | `✖ list`, `✖ update`, `✖ activate`, `✖ status` | (a) A global per-team setting; no per-entity or per-feature route for it could be confirmed in `cx-web-workspace` frontend source at all - genuinely no page found | | `e2m` | `✅ create`, `✅ update`, `✖ list`, `✖ get`, `✖ delete`, `✖ labels-cardinality`, `✖ limits` | `create`/`update` ✅ → `{base}/tco/metrics/{id}` (`:metricId` path-segment route). `list` ✖ (a, enumerates many). `get` ✖ (b, known-id read not wired). `delete` ✖ (c, gone afterward). `labels-cardinality`/`limits` ✖ (a) - team-wide diagnostic/quota reporting across *all* E2M definitions, not any single one | | `recording-rules` | `✅ list`, `✅ get`, `✅ create`, `✅ update`, `✅ delete` | All five ✅ → `{base}/recording-rules`. Single settings page per team regardless of which rule group is being listed/read/edited | | `parsing-rules` | `✖ create`, `✖ update`, `✖ list`, `✖ get`, `✖ delete`, `✖ bulk-delete`, `✖ usage-limits` | Not wired in this PR - `create`/`update` had a confirmed `{base}/rules/group/{id}` route (`:themeId` path-segment) but were pulled out of this PR's scope; tracked as backlog alongside `get` ✖ (b). `list` ✖ (a, enumerates many). `delete`/`bulk-delete` ✖ (c, gone afterward). `usage-limits` ✖ (a) - team-wide quota reporting, not a single rule group | | `enrichments` | `✅ list`, `✅ add`, `✅ remove`, `✅ overwrite`, `✅ limit`, `✅ settings`, `✅ custom/list`, `✅ custom/get`, `✅ custom/create`, `✅ custom/update`, `✅ custom/delete`, `✅ custom/search` | All twelve ✅ → `{base}/enrichments` (shared by enrichment rules and custom enrichment tables - tabs on the same page). Single settings page per team regardless of subcommand, so even `custom/delete`/`custom/search` still point at the page rather than a per-entity route that doesn't exist | | `integrations` | `✅ list`, `✅ get`, `✅ definition`, `✅ deployed`, `✅ create`, `✅ update`, `✅ delete`, `✅ test`, `✅ template`, `✅ extensions/list`, `✅ extensions/get`, `✅ extensions/deployed`, `✅ extensions/deploy`, `✅ extensions/update`, `✅ extensions/undeploy`, `✅ contextual-data/list`, `✅ contextual-data/get`, `✅ contextual-data/create`, `✅ contextual-data/update`, `✅ contextual-data/delete`, `✅ contextual-data/definition`, `✅ contextual-data/test` | All 22 ✅ → `{base}/extensions/integrations`, shared across integrations/extensions/contextual-data (facets of the same catalog, itself a static per-type page, not per-instance) | | `webhooks` | `✅ list`, `✅ get`, `✅ create`, `✅ update`, `✅ delete`, `✅ test`, `✅ types`, `✖ actions/list`, `✖ actions/get`, `✖ actions/create`, `✖ actions/update`, `✖ actions/delete`, `✖ actions/batch`, `✖ actions/reorder` | `webhooks` subcommands: all seven ✅ → `{base}/extensions/outbound-webhooks`, one settings page regardless of subcommand. `actions/*` all seven ✖ (a) - `actions` renders as an in-app slide-in panel, confirmed to have no route at all in frontend source; this applies uniformly to every `actions` subcommand | | `views` | `✖ list`, `✖ get`, `✅ create`, `✅ update`, `✖ delete`, `✖ folders/list`, `✖ folders/get`, `✖ folders/create`, `✖ folders/update`, `✖ folders/delete` | `create`/`update` ✅ → `{base}/explore?viewId={id}` (confirmed via `viewIdParam()`). `list` ✖ (a, enumerates many). `get` ✖ (b, known-id read not wired). `delete` ✖ (c, gone afterward). `folders/*` ✖ (a) - `folderId` confirmed to exist only in the data model, never written to the URL | | `iam` | `✅ roles/create`, `✅ roles/update`, `✖ roles/list`, `✖ roles/get`, `✖ roles/delete`, `✖ roles/system`, `✅ scopes/create`, `✅ scopes/update`, `✖ scopes/list`, `✖ scopes/get`, `✖ scopes/delete`, `✅ groups/create`, `✅ groups/update`, `✖ groups/list`, `✖ groups/get`, `✖ groups/get-by-name`, `✖ groups/users`, `✖ groups/delete`, `✅ api-keys/list`, `✅ api-keys/get`, `✅ api-keys/create`, `✅ api-keys/update`, `✅ api-keys/delete`, `✅ api-keys/send-data-keys`, `✅ api-keys/admin/delete`, `✅ api-keys/admin/set-status`, `✅ users/search`, `✅ users/get`, `✅ users/create`, `✅ users/update`, `✅ users/set-status`, `✅ ip-access/get`, `✅ ip-access/create`, `✅ ip-access/update`, `✅ ip-access/delete` | `roles`/`scopes`/`groups create`/`update` ✅ → `?selected*Id=` auto-open routes (per-entity). Their `list` ✖ (a, enumerates many), `get`/`get-by-name` ✖ (b, known-id read not wired), `delete` ✖ (c, gone afterward). `roles/system` ✖ (a) - lists built-in system roles which have no editable console entry at all. `groups/users` ✖ (a) - reads a group's *membership list*, a distinct sub-resource from the group's own settings row. **`api-keys`, `users`, `ip-access`**: all 17 ✅ → their respective static settings pages (`{base}/settings/api-keys`, `{base}/settings/team/members`, `{base}/settings/login-access-policies`) - flat pages where create/edit is a dialog, so list/get/read subcommands point at the same page as mutations | | `archive` | `✖ metrics/get`, `✖ metrics/create`, `✖ metrics/update`, `✖ metrics/enable`, `✖ metrics/disable`, `✖ metrics/validate`, `✖ logs/get`, `✖ logs/set` | Not wired in this PR - all eight had a confirmed `{base}/physical-locations` route (shared by both the metrics- and logs-archive subtrees, since they configure the same underlying storage locations) but were pulled out of this PR's scope; tracked as backlog | | `ai-center` | `✅ applications/list`, `✅ applications/get`, `✅ evaluations/list`, `✅ evaluations/get`, `✅ evaluations/create`, `✅ evaluations/update`, `✅ evaluations/delete`, `✅ custom-evaluations/list`, `✅ custom-evaluations/list-for-application`, `✅ custom-evaluations/create`, `✅ custom-evaluations/update`, `✅ custom-evaluations/add`, `✅ custom-evaluations/remove`, `✖ coverage`, `✖ model-pricing/get`, `✖ model-pricing/set` | `applications/list`+`get` ✅ → `{base}/ai-center/overview/application-catalog` - this CLI has no `create`/`update`/`delete` for applications at all, so `list`/`get` are the *only* entry points and were linked from the start. `evaluations/*`+`custom-evaluations/*` (13 total) ✅ → `{base}/ai-center/overview/eval-catalog`, one settings page regardless of subcommand. `coverage` ✖ (a) - a team-wide aggregate report; no dedicated route could be confirmed in frontend source at all. `model-pricing/get`+`set` ✖ (a) - confirmed dialog-only, no route | | `slos` | `✅ create`, `✅ update`, `✖ list`, `✖ get`, `✖ delete` | `create`/`update` ✅ → `{base}/slo/{id}/overview` (`:sloId` path-segment route). `list` ✖ (a, enumerates many). `get` ✖ (b, known-id read not wired). `delete` ✖ (c, gone afterward) | | `infra` | `✖ resources/types`, `✖ resources/list`, `✖ resources/health-history`, `✖ resources/raw-data` | (a) Read-only infrastructure inventory/health queries; no per-resource console route could be confirmed in `cx-web-workspace` frontend source for any of the four | | `search-fields` | `✖ search-fields` | (a) Read-only field-metadata search; no console page indexes/searches fields this way at all | | `dataprime` | `✖ list`, `✖ show`, `✖ query` | (a) `list`/`show` are local reference lookups (DataPrime language docs bundled in the binary); `query` is a raw read-only query, same reasoning as `logs` | | `docs` | `✖ search`, `✖ fetch` | (a) Read-only documentation search/fetch against Coralogix's public docs site, which isn't part of the web console at all | | `schema` | `✖ schema` | (a) Local introspection - dumps this CLI's own command tree as JSON for agent consumption; no console counterpart | | `olly` | `✅ ask`, `✖ artifacts/list`, `✖ artifacts/get` | `ask` ✅ → `{base}/olly/chat/{chatId}`, the chat the command just created/continued. **Caveat:** only the parent `{base}/olly` route is confirmed in `cx-web-workspace` frontend source; the `/chat/{chatId}` child segment is not, and no generated link has been opened yet — it needs one manual click before merge, or the link should fall back to `{base}/olly`. `artifacts/list`+`get` ✖ (a) - these retrieve a previously-generated Olly *output* (a stored answer object), a distinct resource with no route of its own confirmed in frontend source | Category (b) rows (`notifications routers/get`, `e2m get`, `parsing-rules get`, `views get`, `iam roles/scopes/groups get`/`get-by-name`, `slos get`) remain a coherent backlog: each has a confirmed route (the same one its `create`/`update` sibling uses), it's just that wiring that particular read wasn't part of this round. Category (a) rows have no route to wire at all, confirmed against `cx-web-workspace` frontend source. Category (c) rows (`delete`/`bulk-delete`) are permanent - there will never be a page to link to once the entity is gone. `parsing-rules create`/`update` are a special case of (b): they were wired earlier in this PR's development but pulled back out to keep this round scoped to a smaller diff, so the whole group is backlog for now, not just `get`. `archive` is the same case: all eight of its subcommands were wired earlier in this PR's development against the confirmed `{base}/physical-locations` route, but the whole group was pulled back out to keep this round scoped to a smaller diff. `alerts suppression-rules` was pulled back out for a different reason - its link was wired and *wrong* (version id instead of `uniqueIdentifier`, see the `alerts` row) - and is tracked in FORGE-710, which stacks on this branch. ## Live verification The scripts added in `verification/forge-586-console-links/` were run against a real Coralogix team and their output captured into an interactive terminal-replay report: **[cx CLI — console-link commands, terminal output (PR #176)](https://claude.ai/code/artifact/6fbae5df-d0b6-41d0-a79e-4e2ef72ab2e8)**. The report replays the recorded runs across the 15 command groups that have at least one linked subcommand - both the ✅ and the ✖ subcommands, so the "no link is printed" cases are evidenced too - as they appeared in a real terminal (stderr progress lines then stdout result). Each card shows the exact command that was run, exit code, duration, and scrollable terminal output; commands with multiple output formats (`-o text`, `-o json`, `-o agents`) have tabs to switch between them. Totals: **244 pass / 39 fail / 33 skip across 322 invocations (76%)** — the fails are concentrated in groups whose live team data hit empty-list or backend-permission edge cases, not link-wiring regressions. `archive` was pulled out of this PR's scope (see Command coverage above), so its recorded runs were dropped from this report. ## Test plan - [x] `cargo test --lib` (557 tests, 9 ignored - filesystem/keyring-dependent) - [x] `cargo test --test console_urls` (58/58, full-binary integration suite - includes regression tests for the empty-list console-link fix and the `/identity/whoami`-based `team_url` resolution) - [x] `cargo test --test <name>` for every touched command module's integration suite (`alerts`, `api_keys`, `cases`, `connectors`, `contextual_data`, `custom_enrichments`, `data_archive`, `data_usage`, `e2m`, `enrichments`, `extensions`, `integrations`, `ip_access`, `olly`, `parsing_rules`, `recording_rules`, `roles`, `routers`, `scopes`, `slos`, `tco_policies`, `team_groups`, `users`, `views`, `webhooks`, `dashboards`, `ai_center`) - all green - [x] `cargo fmt` clean - [x] `cargo clippy --locked -- -D warnings` clean - [x] `docs/configuration.md` "Console links" section kept in sync with current resolution order and behavior 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: cx-saga[bot] <294118815+cx-saga[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: Binyamin Levine <binyamin.levine@coralogix.com> Co-authored-by: Cursor <cursoragent@cursor.com>
36 lines
1.5 KiB
TOML
36 lines
1.5 KiB
TOML
# Example global cx config.
|
|
# Save as ~/.cx/config.toml
|
|
|
|
# Profile name used when --profile is not provided.
|
|
default_profile = "default"
|
|
|
|
# Default output mode when --output is not provided.
|
|
# Valid values: "text", "json", "agents"
|
|
default_output_format = "text"
|
|
|
|
# Maximum serialized byte size of a non-aggregated Dataprime response that can
|
|
# be printed directly to stdout in "agents" output mode. If the payload exceeds
|
|
# this limit the results are written to a temp file instead, and a short summary
|
|
# (result count + file path) is printed to stdout. Default: 102400 (100 KiB).
|
|
# Set to -1 to disable the limit and always print results directly.
|
|
# max_dataprime_direct_output_size = 102400
|
|
|
|
# Directory where temp result files are stored when the payload exceeds
|
|
# max_dataprime_direct_output_size. Files are prefixed with "cx_results" so
|
|
# they can be identified and removed with `cx cleanup`. Default: "/tmp/".
|
|
# temp_dir = "/tmp/"
|
|
|
|
# Credentials live in separate profile files under ~/.cx/profiles/.
|
|
# Example: ~/.cx/profiles/default.toml
|
|
#
|
|
# api_key = "cxp_your_api_key_here"
|
|
# region = "eu1"
|
|
# label = "production"
|
|
#
|
|
# Optional: override the web console base URL used to build "View in
|
|
# Coralogix" links printed after dashboard/alert/case mutations. By default
|
|
# this is resolved automatically via GET /identity/whoami; only needed when
|
|
# that lookup isn't reachable (e.g. a custom, self-hosted environment).
|
|
# See docs/configuration.md#console-links.
|
|
# console_url = "https://<your-team>.<console-domain>"
|