Skip to content

CLI v5#222

Open
maximelb wants to merge 123 commits intomasterfrom
cli-v2
Open

CLI v5#222
maximelb wants to merge 123 commits intomasterfrom
cli-v2

Conversation

@maximelb
Copy link
Copy Markdown
Contributor

Description of the change

Temporary PR to trigger the test validations.

Type of change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)

maximelb and others added 30 commits February 11, 2026 14:23
Full rewrite of the LimaCharlie Python SDK and CLI (v2.0.0) with
AI/LLM-first discoverability. All commands follow a consistent
noun-verb pattern with rich help, --explain flags, and multiple
output formats (json/yaml/csv/table/jsonl).

Core infrastructure:
- Click-based CLI with auto-discovered command modules
- HTTP client with JWT auth, retry with backoff, rate limiting
- Config system supporting ~/.limacharlie, env vars, named environments
- Structured error hierarchy with suggestion messages
- Output formatting with jmespath filtering

SDK (31 modules in limacharlie/sdk/):
- Organization, Sensor, D&R Rules, FP Rules, Hive, Outputs
- Artifacts, Payloads, Search, Extensions, Installation/Ingestion Keys
- Users, Groups, API Keys, Billing, Spout, Replay
- Integrity, Exfil, Logging Rules, Configs (sync), AI generation
- Investigations, USP, Jobs, YARA, ARL

CLI (49 command modules in limacharlie/commands/):
- Full CRUD for all resource types
- Hive shortcuts: secret, lookup, playbook, adapter, cloud-sensor, sop, note
- Help system: discover, help topics, cheatsheets, schema
- Streaming, sync, search with LCQL support

Tests:
- 481 unit tests (v2) covering all SDK and core modules
- 25 integration test files (63 tests) for API validation
- Removed 3 obsolete v1 unit tests replaced by v2 equivalents

Packaging:
- Entry point updated to limacharlie.cli:main
- Added click>=8.0 and jmespath dependencies

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- organization.py: service_request now base64-encodes request_data,
  get_urls unwraps data["url"], get_detections/get_audit_logs use
  correct response keys (detects/events) with next_cursor and
  gzip decompression, get_available_services unwraps replicants key,
  get_jobs decompresses compressed response
- sensor.py: get_info extracts data["info"], is_online checks
  data["online"] dict, get_tags handles v1 {sid: {tag: null}} format,
  is_isolated/is_sealed use should_isolate/should_seal fields,
  get_events uses next_cursor with decompression, get_children_events
  decompresses, get_overview extracts overview key
- search.py: adds https:// prefix and /v1 suffix to search URL,
  includes oid in all request bodies, sends times as strings
- extensions.py: rewritten to use correct extension/request/{name}
  endpoint with gzip+base64 encoded gzdata (was incorrectly going
  through service_request)
- configs.py: uses Extensions class directly instead of nonexistent
  org.extension_request method
- artifacts.py: rewritten to use ingestion URL with Basic auth
  headers matching v1 Logs.py upload pattern
- insight.py: search_ioc info param now configurable (was hardcoded),
  batch_search uses form-encoded params matching v1
- replay.py: passes is_async=True matching v1 behavior
- client.py: added unwrap() for gzip+base64 decompression
- cli.py: import errors logged when LC_DEBUG is set
- artifact.py CLI: --type/--start/--end/--limit no longer silently ignored
- sensor.py CLI: fixed --offset+--limit pagination
- _hive_shortcut.py: YAML-first parsing, supports usr_mtd/etag,
  better --confirm error message

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Bug fixes:
- Fix HiveRecord crash from invalid kwargs in _hive_shortcut.py
- Fix org rename() sending wrong query param (newName -> name)
- Fix subscribe/unsubscribe extension not splitting res_cat/res_name
- Fix replay double-serialization of detect/respond via service_request
- Fix on_refresh_auth callback inconsistency (now always passes client)
- Fix sensor set-version --version incorrectly required
- Fix org delete --confirm-token incorrectly required
- Remove sensor upgrade --selector that was silently ignored
- Fix ioc hosts using invalid IOC type instead of find_sensors_by_hostname
- Add defensive .get() for bare resp["key"] in org and sensor SDK
- Fix register_explain using spaces instead of dots in hive shortcuts

New features:
- Add OAuth browser-based login: limacharlie auth login --oauth
- Support Google and Microsoft providers with --provider flag
- Support headless OAuth with --no-browser flag
- MFA/2FA handled automatically via existing infrastructure

Documentation:
- Rewrite README with comprehensive CLI reference for all command groups
- Add accurate Python SDK examples matching actual v2 API patterns
- Document both API key and OAuth authentication flows
- Add SDK class reference table
- Move legacy v1 docs to bottom, v2 content first

Tests:
- Add tests for rename, subscribe/unsubscribe param splitting
- Add tests for get_all_tags defensive .get() and find_sensors_by_hostname
- Add tests for on_refresh_auth callback consistency
- Add tests for HiveRecord raw dict construction
- Add test for replay non-double-serialization

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
SDK fixes discovered by running integration tests against the live API:
- organization.py: fix add_api_key param (permissions→perms, json→join)
- organization.py: add default start/end time for get_jobs()
- artifacts.py: add required start/end params to list() with defaults
- search.py: add required startTime/endTime defaults to validate()
- integrity.py: replace non-existent get_rule action with list+filter
- logging_rules.py: same get_rule fix as integrity
- exfil.py: fix all action names, split delete into delete_event/delete_watch

CLI fixes:
- commands/exfil.py: add --type option to delete for event vs watch rules
- commands/integrity.py: handle None return from get() with error message
- commands/logging_cmd.py: same not-found handling as integrity
- commands/artifact.py: pass start/end to SDK, remove redundant filtering

Tests: 65 CLI integration tests covering all command groups (63 pass,
2 skip due to missing hive permissions on test API key).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove deprecated REST endpoints (GET/POST/DELETE /rules/{oid} and
/fp/{oid}) from the SDK and rewrite CLI commands to use the Hive API.
Rename `limacharlie rule` to `limacharlie dr` with namespace-to-hive
mapping (general→dr-general, managed→dr-managed, service→dr-service).
Replace FP commands with a 3-line hive shortcut backed by the fp hive.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix Click command naming: add explicit @group.command("set") in dr.py
  and hive.py to prevent Click deriving "set-cmd"/"set-record" names
- Fix HiveRecord construction crash in extension.py config-set (use
  raw= pattern instead of invalid kwargs)
- Fix payloads SDK: implement two-step signed URL pattern for upload
  and download matching v1 behavior
- Fix OAuth token persistence: save refreshed tokens to config file
- Fix exception swallowing in dr_rules.py/fp_rules.py: only catch
  ApiError with 404 status, re-raise other errors
- Fix insight.py: decompress response when is_compressed=true is set
- Fix hive.py rename: remove double URL encoding of new_name
- Update payload CLI download command to handle raw bytes from SDK
- Add test for non-404 errors propagating in DR rules get

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New `limacharlie auth signup` command that lets users create a brand
new LimaCharlie account and organization directly from the CLI.

The flow: OAuth authentication -> user profile creation via the signUp
Cloud Function -> new organization creation -> credentials saved.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New `limacharlie download` command group with sensor, adapter, and list
subcommands. Downloads pre-built binaries from downloads.limacharlie.io
for all supported platforms (Windows, Linux, macOS, Chrome, AIX,
FreeBSD, OpenBSD, NetBSD, Solaris) with sensible default filenames,
auto-executable permissions, and stdout piping support.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Modernize the CLI v2 codebase with three major improvements:

- Add `from __future__ import annotations` and full type annotations to
  all 80+ Python files (core, SDK, and command modules)
- Convert LimaCharlieContext and HiveRecord to @DataClass, add
  Credentials TypedDict to config.py
- Migrate packaging from setup.py/setup.cfg/requirements.txt to
  pyproject.toml with setuptools backend, add PEP 561 py.typed marker
- Update Dockerfile and CI (cloudbuild) for new packaging
- Add tests for dataclass conversions, type infrastructure, and packaging

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add consistent type annotations to _make_explain_callback inner
  functions across all 15 command files that were missing them
- Remove unused imports (signal, sys, os) from stream.py, artifact.py,
  output_cmd.py
- Fix spotcheck.py register_explain key from space to dot separator
- Fix test_packaging.py tomllib import for Python 3.9/3.10 compat
- Add tomli backport to pyproject.toml dev dependencies

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Current PyPI latest is 4.11.3, so 2.0.0 would be a downgrade.
Version 5.0.0 signals the major rewrite and sorts above 4.x.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix AI methods: remove spurious {oid} from paths, use "query" not "description"
- Fix org param names: "permission"->"perm", "email"->"member_email"
- Fix groups set_permissions: "permissions" dict -> "perm" list
- Rewrite investigations SDK to use Hive for CRUD operations
- Remove non-existent endpoints: artifacts.get, billing.get_sku_definitions,
  ingestion_keys.configure_usp, insight.get_object_timeline,
  insight.get_host_count_per_platform, org.find_sensors_by_ip,
  org.configure_usp_key, org.convert_extension_rules
- Remove CLI commands for removed endpoints (artifact get, billing skus,
  extension convert-rules)
- Update investigation CLI commands to use --name with Hive-based SDK
- Update tests for new investigation Hive interface

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update README.md investigation examples and all _EXPLAIN_* texts in
investigation.py to use --name instead of --id, matching the Hive-based
CLI commands.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously only ~8% of SDK methods had tests verifying actual HTTP
contracts (URL paths, methods, param names, body fields). This led to
bugs shipping undetected (wrong param names, wrong paths, wrong body
fields). Now every SDK method has a unit test verifying its full HTTP
contract.

- 16 new test files for ai, artifacts, billing, exfil, extensions,
  firehose, fp_rules, insight, integrity, investigations, jobs,
  logging_rules, payloads, replay, wrappers, yara
- Expanded organization (17→107 tests), sensor (13→27), spout, dr_rules,
  hive tests with full contract verification
- Slimmed test_sdk_misc.py to only ARL/USP/downloads (moved rest to
  dedicated files)
- Total: 814 passing tests (up from ~564)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
SDK fixes:
- config.py: Clear stale uid from config when explicit api_key provided
- Manager.py: Only inherit GLOBAL_UID when api_key also from globals
- organization.py: get_all_tags handles null, unwrap api_keys/installation_keys
- exfil.py: Split string path into list for service request
- usp.py: Wrap single json_input dict in list

Integration test fixes:
- Fix exfil/hive/artifacts/outputs/search/AI/groups/replay tests
- Handle user-only auth endpoints gracefully
- Use valid FP rule data for hive CRUD test
- Fix CLI command names for search tests

Unit test updates:
- Update exfil path and USP json_input assertions to match SDK fixes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Table output now auto-truncates large dicts ({N keys}) and long lists
([N items]) to fit the terminal. --wide/-W disables truncation, and
--filter applies a JMESPath expression to transform output data.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds ~40 new tests across two files:
- test_output.py: truncation, max_value_width, _table_value, wide mode,
  module-level filter expr, and detect_output_format tests
- test_cli_commands.py: CliRunner tests for global --wide/--filter flags,
  auth (whoami, test, list-envs), org (info, urls, errors), sensor (list,
  get, delete without confirm), DR (list), and --explain flag

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove the infrastructure-service code path and use_extension toggle from
Configs — all sync now goes through ext-infrastructure.  Drop the legacy
--rules / --fps CLI flags (and sync_rules / sync_fps SDK params) in favor
of hive flags (--hive-dr-general, --hive-fp, etc.) matching the old v1 CLI.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…unknown api key"

When users login with OAuth but don't set a default org, org-scoped commands
would fail with the cryptic JWT endpoint error "unknown api key". Now we catch
the missing OID before hitting the network and suggest use-org, --oid flag
placement, or LC_OID env var.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two issues caused OAuth login to fail with "unknown api key":

1. All _get_org helpers double-resolved credentials — they called
   resolve_credentials() then re-passed api_key explicitly to Client(),
   which cleared OAuth creds inside Client's own resolve_credentials().
   Fix: let Client resolve credentials once from oid + environment.

2. OAuth login preserved any stale api_key already in the config file.
   Fix: clear api_key from config on successful OAuth login.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ut help text

- adapter.py now uses "external adapter" noun to distinguish from cloud adapters
- Hive shortcut factory uses proper a/an article for nouns starting with vowels
- Subcommand help text uses the noun instead of generic "records"
- Group help drops the internal hive name for cleaner output

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Mock resolve_credentials so the test doesn't pick up api_key from
the developer's ~/.limacharlie config file.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ud adapters

The bare 'adapter' name was ambiguous — cloud sensors are often called
"cloud adapters". Now 'ext-adapter' and 'cloud-sensor' are clearly distinct.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rename 'ext-adapter' to 'external-adapter' for clarity, and rename
'cloud-sensor' to 'cloud-adapter' so both adapter types use consistent
naming.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reflect the cloud-sensor → cloud-adapter and adapter → external-adapter
renames in the hive shortcuts section.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Strip top-level wrapper keys (payloads, users, user_permissions, groups,
errors) so CLI output shows useful data directly instead of a single
wrapper object. Follows the existing resp.get("key", resp) pattern
already used by get_api_keys, get_installation_keys, get_outputs, etc.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement _is_list_of_dicts() to detect dict-of-dicts data (e.g.
payloads keyed by name) and flatten it into a list-of-dicts for proper
columnar table display. Adds a "name" column from the dict key when
values don't already contain one.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The seal/unseal subcommands have nothing to do with network policy;
'endpoint-policy' better describes the full scope of the command group
(network isolation + configuration sealing).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a unified --ai-help flag that generates compact markdown help at
three levels (top-level overview, command group, individual command).
Remove the per-command --explain boilerplate from all 40 command files
(-1,407 lines) while keeping the explain text registry that feeds
--ai-help context.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
tomaz-lc and others added 3 commits March 20, 2026 18:16
* feat: lazy command loading for faster CLI startup

Replace eager auto-discovery with lazy command loading using a static
command-to-module map. Command modules are only imported when the
specific command is invoked, not at CLI import time.

Key changes:
- _GlobalOptionsGroup replaced with _LazyCommandGroup that combines
  lazy loading with global option hoisting
- Static _COMMAND_MODULE_MAP provides O(1) command name to module
  resolution without importing any command modules
- list_commands() returns names from the static map (no imports)
- get_command() imports only the requested module on first access
- --ai-help injection deferred to per-command first access
- Import __version__ from _version directly instead of client.py
  to avoid pulling in ssl, yaml, urllib at import time

Performance (end-to-end subprocess, cold start):
- limacharlie --version: ~55ms (was ~280ms)
- limacharlie sensor list --help: ~67ms (was ~265ms)
- limacharlie completion bash: ~58ms (was ~280ms)
- limacharlie --help (all commands): ~134ms (loads all modules)

Tests:
- 816 regression tests covering full CLI surface: command registration,
  subcommand structure, module mapping, global options, --help for every
  command, --ai-help injection and output, explain registry, discovery
  profiles, completion, and context propagation
- End-to-end subprocess benchmarks and regression tests
- In-process microbenchmarks for import, help, completion, resolution

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: --ai-help shows all command groups with lazy loading

_top_level_help() iterated cli.commands directly which is empty with
lazy loading. Use list_commands() + get_command() instead, matching
how Click itself resolves commands for --help.

Also: remove dead pkgutil/field imports from cli.py, always emit
stderr warning for broken command modules (not just with LC_DEBUG),
fix weak test assertion, and add regression tests + microbenchmarks
for --ai-help.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* perf: defer limacharlie.output import to cli callback

limacharlie.output pulls in jmespath, tabulate, yaml, and csv which
adds significant import overhead. Deferring the import from module
level into the cli() callback avoids this cost on fast paths like
--help, --version, and --ai-help that never render command output.

Benchmark results (e2e subprocess, cold process):
- cli import: 2.2ms -> 1.2ms (47% faster)
- --version: 114ms -> 55ms (52% faster)
- --help: 537ms -> 176ms (67% faster)
- --ai-help: 481ms -> 205ms (57% faster)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…265)

- Add config_cmd to _COMMAND_MODULE_MAP in cli.py (missing after #257
  and #261 were merged independently)
- Add config to EXPECTED_TOP_LEVEL_COMMANDS, EXPECTED_MODULE_MAP, and
  EXPECTED_SUBCOMMANDS in regression tests
- Fix test_cli_import_does_not_load_output to handle third-party deps
  already loaded by other tests in the same pytest process
- Add ci.yml GHA workflow that runs unit tests and dist checks on every
  push and PR

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
maximelb and others added 9 commits March 21, 2026 16:47
The note shortcut command was passing 'note' as the hive name to the
SDK's Hive class, which hits the REST API at /hive/note/... — but the
backend hive is named 'org_notes'. This caused UNKNOWN_HIVE errors
when using the SDK directly (not through the CLI gateway which may
translate names).

Also fixes the _KNOWN_HIVE_TYPES list and explain text.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add ui_actions field to HiveRecord for UI action buttons

Support the new usr_mtd.ui_actions component that allows hive records
(ai_agent, playbook) to declare action buttons surfaced in the web UI.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: update HiveRecord field count test for ui_actions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: update cases SDK/CLI to match ext-cases data model changes

Align the Python SDK and CLI with the latest ext-cases backend:

- Remove `acknowledged` and `escalated` case statuses (now: new,
  in_progress, resolved, closed)
- Simplify status transitions to match new state machine
- Replace `assignee` (singular) with `assignees` (array) on update
- Remove `escalation_group` and `investigation_id` fields
- Standardize evidence fields across entities, telemetry, artifacts:
  use `note` + `verdict` instead of name/context/first_seen/last_seen
  (entities), event_summary/relevance (telemetry), description (artifacts)
- Artifact add now requires `path` + `source` (artifact_type is optional)
- Add `to_stakeholder` and `from_stakeholder` note types
- Update all explain texts, docstrings, and tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: send 'sid' query param instead of 'sensor_id' for case list filter

The ext-cases backend reads q.Get("sid") not q.Get("sensor_id").
The old param name was silently ignored, making --sid filtering a no-op.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Checking a specific permission without an OID is ambiguous and can
return misleading results. Fail early with a clear error message
telling the user to specify --oid or set LC_OID.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cases require summaries, so the CLI now enforces this at invocation time
rather than allowing empty summaries.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: stop double-encoding detection in case create and update mock returns

The detection field was being JSON-serialized with json.dumps() before
being passed to the extension request. Since the gzdata encoding already
JSON-serializes the full data dict, this produced a JSON string instead
of an object. The LC backend silently dropped the field because schema
type "json" expects an object, not a string.

Also update test mock return values from case_id to case_number to match
the current ext-cases backend response format.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: rename --id to --case-number for case commands

Replace --id with --case-number across all case CLI commands (get,
export, update, add-note, tag set/add/remove) to prevent confusion
with case_id (UUID). No backward compat alias needed since case
management is a new feature.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: cases CLI/SDK alignment with ext-cases backend

- Fix export --with-data using wrong field name (detection_id -> detect_id),
  which caused all detection fetches to silently fail
- Add is_public parameter to add_note SDK method and --is-public flag to
  CLI add-note command
- Add update_note_visibility SDK method and CLI update-note command for
  toggling note public/private visibility
- Add list_orgs SDK method and CLI orgs command for discovering
  ext-cases-subscribed organizations

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add orgs and update-note to case subcommand regression test

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
maximelb and others added 3 commits April 11, 2026 08:33
* feat: add feedback CLI commands and SDK for ext-feedback

Add dedicated CLI commands for the ext-feedback extension:
- feedback request-approval: send Approve/Deny prompts
- feedback request-ack: send acknowledgement requests
- feedback request-question: send free-form text questions
- feedback channel list/add/remove: manage channel config

Channels support web, slack, email, telegram, and ms_teams types.
Responses are dispatched to a case (as a note) or a playbook.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: add timeout support to feedback CLI commands

Add --timeout, --timeout-choice, and --timeout-content options to all
three feedback request commands, matching the ext-feedback backend's
new auto-respond-on-timeout feature.

- request-approval: --timeout N --timeout-choice approved|denied
- request-ack: --timeout N (choice is always "acknowledged")
- request-question: --timeout N --timeout-content '...' (content required)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: JSON-stringify content fields for LC platform schema validation

The LC platform expects SchemaDataTypes.JSON fields as JSON-encoded
strings, not raw dicts. The extension's flexJSON unmarshaler handles
both forms, but the platform validates before forwarding.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: improve feedback CLI help text for LLM readability

- Add explicit Dependencies section showing conditional requirements
- Use \b blocks so Click preserves formatting for examples and lists
- Single-line examples that don't get mangled by Click's text wrapper
- Option help strings explicitly state JSON format with examples
- Channel group help lists all types with their output requirements

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…dback (#276)

The ext-feedback extension now expects actual JSON objects in the
_content fields rather than double-encoded JSON strings.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…#278)

* feat: add `ai session attach` for live WebSocket streaming + interactive chat

Adds a new CLI subcommand that attaches to a running AI session over
WebSocket and streams messages in real time, with an optional
interactive mode that turns the terminal into a chat with the agent.

- new `limacharlie.sdk.ai_session.SessionAttachment` async helper
  wrapping the ai-sessions WebSocket protocol (prompt, interrupt,
  tool_approval_response, ask_user_question_response, heartbeat)
- `AI.attach_session()` factory on the SDK, selecting the
  owner-interactive or org-scoped read-only endpoint
- `limacharlie ai session attach --id <sid> [--interactive]
  [--read-only] [--raw] [--no-history]` command with colour-coded
  pretty printing and transparent 403 fallback to the read-only
  org endpoint when the caller is not the session owner
- interactive loop surfaces tool approval requests and
  `ask_user_question` messages as live prompts
- `websockets>=13.0` added as a runtime dependency
- 39 unit tests covering URL derivation, send/receive plumbing,
  read-only enforcement, message rendering, and CLI registration

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: treat ai_agent hive record as template, allow per-field overrides

Reworks `limacharlie ai start-session` so the ai_agent hive record is a
starting template and every profile field can be overridden from the
CLI for a single run.

Adds override flags matching the full server ProfileContent schema:

  * --model, --max-turns, --max-budget-usd, --task-budget-tokens,
    --ttl-seconds, --one-shot/--no-one-shot, --permission-mode,
    --allowed-tools, --denied-tools, --plugin (repeatable)
  * --env KEY=VALUE (repeatable) - merged with the template's
    environment; override wins on key collisions
  * --anthropic-key, --lc-api-key, --lc-uid - credential overrides
    that accept literal values or "hive://secret/<name>" refs

Scalars and lists REPLACE the template value when passed.  Environment
MERGES.  Omitted flags leave the template untouched.  Override values
that start with hive://secret/ are resolved before sending, so secrets
never appear in argv.

SDK: AI.start_session grows keyword-only override parameters.

Adds 30 new unit tests covering every override path: scalar replace,
list replace, env merge with collision, env secret resolution on
overrides, credential literal vs secret-ref overrides, CSV parsing
edge cases (empty list vs None), --env KEY=VALUE parsing validation,
and a help-text assertion that every flag is documented.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: use /v1/ws/... path for ai session attach WebSocket

The GCP load balancer fronting ai.limacharlie.io routes /v1/sessions/*
to session-manager (REST API) and only /v1/ws/* to interaction-proxy
(WebSocket). The legacy /v1/sessions/{id}/ws path registered on the
proxy service isn't reachable through the LB, so attach was 404-ing
in prod and staging.

Switch _derive_ws_url to the GCP-routable form:
  owner:     /v1/ws/sessions/{id}
  read-only: /v1/ws/org/sessions/{id}

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: add `ai chat` and `ai auth claude` for user-owned sessions

Splits the interactive chat flow out from `ai start-session` so the
two kinds of ai-sessions the backend supports each have their own
clear CLI surface:

* `ai start-session` continues to create org-owned sessions from an
  ai_agent hive template, runs against the org's anthropic_secret,
  and is designed for automation (DR-rule triggers, cron jobs).
  These sessions are read-only over the WebSocket by design on the
  backend, so `ai session attach --interactive` against them would
  always fall back to read-only.

* `ai chat` creates a user-owned session via POST /v1/sessions,
  billed against the caller's registered Claude credential, and
  drops straight into an interactive WebSocket chat.  Supports the
  same profile overrides as start-session minus the org-only credential
  fields (--anthropic-key, --lc-api-key, --lc-uid).

* `ai auth claude {status,login,set-key,logout}` manages the per-user
  Claude credential that `ai chat` consumes.  `login` walks the
  browser OAuth flow; `set-key` accepts a raw Anthropic API key
  (with hive://secret/<name> support and --key-from-stdin for piping).

Also extends the existing `_ai_attach.run_attach` with an optional
initial_prompt arg so `ai chat "question"` seeds the first turn
without duplicating the attach runtime.

New SDK methods on `limacharlie.sdk.ai.AI`:
  register_user, claude_auth_status, claude_login_start,
  claude_login_get_url, claude_login_submit_code, claude_set_apikey,
  claude_logout, create_user_session.
All routed through a new `_user_request` helper that omits X-LC-OID
and uses JWT auth (matching the ai-sessions user-scoped route auth
middleware).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: unwrap session object from `ai chat` server response

POST /v1/sessions returns the session nested under a "session" key
(same shape as GET /v1/sessions/{id}), not flat.  Caught during
live white-sands testing: the CLI raised "server response missing
session id" even though creation succeeded.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: add `ai chats` group + drop stdin-as-prompt in `ai chat`

Two follow-ups discovered during the white-sands round.

`ai chats {list,get,terminate,history}` is the user-owned counterpart
to the existing `ai session` group.  Both groups share the same
shape, but each calls a distinct backend route family — `ai session`
hits /v1/org/sessions/* (org-owned, OwnerOID-keyed) and `ai chats`
hits /v1/sessions/* (user-owned, OwnerUID-keyed).  Without this
split, sessions created by `ai chat` were unreachable from the CLI:
they'd accumulate, eventually hit the concurrent-session cap, and
required raw SDK calls or TTL expiry to clean up.

`ai chat` no longer consumes stdin as the initial prompt.  The old
behaviour silently glued multi-line stdin into one prompt and left
nothing for the interactive input loop to read; users got a
one-shot exchange when they expected a back-and-forth.  Now the
opening prompt is supplied via the PROMPT argument only, and stdin
is reserved for the interactive loop after attach (which is what a
TTY user would expect).

New SDK helpers on AI: list_user_sessions, get_user_session,
terminate_user_session, get_user_session_history.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: auto-drain pagination in `ai session/chats list` by default

Both `ai session list` and `ai chats list` were exposing raw
`--cursor` / `next_cursor` plumbing, pushing the pagination loop onto
the user. Switch to the convention used by `detection list` /
`audit list`: the CLI drains every matching session by default.

SDK changes
-----------
Split each list method into two layers:

- `list_sessions_page(...)` / `list_user_sessions_page(...)`: the
  original single-page call, kept for callers that want explicit
  pagination control (scripts, resumable iteration, streaming).
- `list_sessions(...)` / `list_user_sessions(...)`: generators that
  drain `next_cursor` internally. `limit` caps total yielded rows
  client-side; `page_size` maps to the server's per-request limit.
  Mirrors `Organization.get_detections` / `get_audit_logs`.

CLI changes
-----------
`ai session list` / `ai chats list`:
- Default (no `--cursor`): drain the generator and output a plain
  list of sessions — same shape as `detection list`.
- `--cursor <CURSOR>`: fall through to the single-page API and output
  the raw `{sessions, next_cursor}` dict so callers can resume
  pagination themselves. `--limit` under `--cursor` acts as the
  server-side per-page cap; without `--cursor` it is a total cap.

Tests
-----
- Moved the existing single-page tests onto the `_page` methods.
- Added generator-side tests: multi-page drain, `limit` early-stops,
  empty-string `next_cursor` is treated the same as None,
  `page_size` shows up in the request but client-side `limit` does not.
- CLI: verified the default invocation routes through the generator
  and never touches `_page`, and that `--cursor` routes through
  `_page` and preserves the raw response.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: clean up ai chat/attach live output

Default pretty output was a firehose: empty assistant/user/result
frames, plumbing system subtypes (init_received, hook_started,
autoinit_loaded, ...), raw session_status JSON, full ISO timestamps,
and a stray ">" caret that landed on the next streamed line.

- Filter noisy message types (session_status, usage_delta) and system
  subtypes (bridge + Claude SDK plumbing) by default.
- Skip assistant frames with only tool_use blocks, user frames that
  wrap a tool_result, and result pings without a summary.
- Shorten timestamps to HH:MM:SS; add --verbose/-v to both
  `ai session attach` and `ai chat` to restore the firehose.
- Fix user-content extraction: server sends {"content": [blocks]},
  not {"text": ...}, so the "user:" line was always blank.
- Strip leading/trailing whitespace and whitespace-only blocks in
  assistant text so Claude's "\n\n..." openers stop rendering as
  empty indented lines.
- Drop the eager "> " input caret — under concurrent streaming it
  interleaved with output; the terminal echoes keystrokes anyway.
- Share the noise set with `ai session history` / `ai chats history`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
lc-kirill and others added 5 commits April 23, 2026 17:29
* Migrate billing endpoints from billing-service to lc_api-go

Point the v2 Billing SDK at the lc_api-go paths under
api.limacharlie.io/v1/ instead of the legacy billing.limacharlie.io
service, which is being retired. Mirrors #280 against master.

Paths updated:

  orgs/{oid}/status          -> orgs/{oid}/billing/status
  orgs/{oid}/details         -> orgs/{oid}/billing/details
  orgs/{oid}/invoice_url/... -> orgs/{oid}/billing/invoice/...
  user/self/plans            -> plans

All four methods drop the alt_root argument and now use the default
api.limacharlie.io/v1/ base. Also updates the help topic to stop
directing users to billing.limacharlie.io.

Semantic parity was verified against billing-services/billing-service:
- /billing/invoice returns only the first invoice per month; the
  legacy "urls"/"invoices" multi-invoice keys are gone.
- /plans response is narrower ({id, name, region}) than the legacy
  shape that returned MetaProduct and a nested Datacenter block.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Reroute --target billing to lc_api as a compat shim

The billing.limacharlie.io host is deprecated; all its endpoints have
been folded into lc_api under api.limacharlie.io/v1/. Rather than
break existing callers of 'limacharlie api --target billing', rewrite
the legacy relative paths they pass to their new /v1/ counterparts
and route through the default api host.

Rewrites applied:
  orgs/{oid}/status              -> orgs/{oid}/billing/status
  orgs/{oid}/details             -> orgs/{oid}/billing/details
  orgs/{oid}/invoice_url/{y}/{m} -> orgs/{oid}/billing/invoice/{y}/{m}
  user/self/plans                -> plans

Paths with no path change (user/self/auth, domain/{d}/auth,
orgs/{oid}/quota) pass through unchanged but still route to api.
Unrecognized paths also pass through — if a caller hits something
the legacy host served that never migrated, they get a 404 from
api.limacharlie.io instead of a DNS/connection error.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Update billing unit tests for lc_api paths and api_cmd shim

- test_sdk_billing.py: drop the BILLING_URL import/assertion and
  expect the new /v1/ relative paths with no alt_root.
- test_api_cmd.py: replace test_billing_target (which asserted the
  old alt_root) with coverage of the compat shim — four rewrites
  (status, details, invoice_url, user/self/plans) plus a passthrough
  case for paths that kept their shape (user/self/auth).

Full unit suite: 3105 passed, 5 skipped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: add TOON output format alongside json/yaml

Wires up Token-Oriented Object Notation (TOON) as a new --output
choice so CLI output can be piped into LLM prompts with ~30-60%
fewer tokens than equivalent JSON.

Uses the toon_format package from the official toon-format org
(MIT, community-driven, currently beta).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test: broaden TOON coverage + align search help strings

- Unit tests for nested structures, unicode, empty list, ImportError
  guard, and that list-of-dicts hit the compact tabular form.
- Integration tests confirming --filter / --fields / --sort-by still
  work with fmt=toon.
- Search output test parallel to the yaml/csv/jsonl ones.
- Refresh remaining "CSV and YAML" help strings in search.py.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: add hive schema CLI command

Maps to the new GET /v1/hive/{hive_name}/schema endpoint that returns
the JSON Schema (draft 2020-12) describing a hive's record type. Lets
CLI users discover record structure programmatically and validate
records before calling 'hive set' with any standard JSON Schema
validator.

Hives without a typed record format (dr-*, fp, extension_config)
return NO_SCHEMA_AVAILABLE from the underlying RPC.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test: accept 'user not found' as valid auth-failure phrasing

JWT service now returns 'user not found: ' when an API key tries to
obtain a user-scoped JWT (oid='-'). Broaden the assertion in
test_v2_org_list_accessible_orgs to also accept this phrasing so the
test stays green across backend versions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: correct audit list field documentation in --ai-help

The --ai-help text for `audit list` listed fields (who, action, target,
details) that don't exist in the API response, leading callers to write
filters like `[].action` that silently return empty results. Replace with
the actual response fields (ts, time, etype, ident, msg, oid, origin,
entity, mtd).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* audit: distinguish V1/V2 fields per CloudAudit struct

Align the --ai-help field listing with the canonical CloudAudit struct
in go-essentials (lc/message.go), which explicitly groups fields into
"Audit Message" (V1) and "Audit V2 Fields".  V2 added time/ident/entity/
mtd; origin is the legacy V1 actor field that ident supersedes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Surfaces the new info=locations / limit params on POST
/insight/{oid}/objects (lc_api-go) which routes to insight-go's
get_obj_batch_locations RPC. Adds the params to Insight.batch_search
and exposes them as --info / --limit on the batch-search CLI.

The whole batch still consumes a single ioc-search rate-limit
charge regardless of --info -- that's the entire reason callers
should prefer batch-search over fanning out per-IOC search calls.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
maximelb and others added 3 commits May 3, 2026 08:38
The SDK's get_audit_logs() and the API endpoint both accept event_type
and sid filters, but the CLI didn't expose them.  Without server-side
filtering, callers have to pull the full audit stream and grep client-
side, which is expensive on busy orgs (e.g. one weekly window contained
~5,000 entries dominated by a single extension's hive_set traffic).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: add ai-skill and ai-memory hive CLI commands

Mirrors the two new typed hives added in legion_config_hive: ai_skill
(Claude Code skill definitions) and ai_memory (per-agent memory store
with a server-side partial-merge hook).

ai-skill is a straight hive shortcut on top of make_hive_group: list,
get, set, delete, enable, disable.

ai-memory is custom because its hook lets Set on a single memory name
update only that one entry while every other memory on the record is
preserved by the server. The new AiMemory SDK class bakes that
mechanism in: set/delete take a required --memory-name and send
{memories: {name: content|null}} only — no GET-then-PUT round trip,
no etag race window. delete-record removes the whole agent record
through the regular DELETE verb.

Both hive names are also added to Configs.ALL_HIVES, the sync flag
map (--hive-ai-skill, --hive-ai-memory), the hive list-types output,
and the lazy-loading regression snapshot.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(hive): clear data envelope from get_metadata responses

The /mtd endpoint serializes "data": {} into the response even though
the call is metadata-only. HiveRecord.from_raw kept the empty dict, so
a follow-up set() saw record.data != None and routed to /data with an
empty payload. Hives whose validator requires fields (ai_skill needs
content, ai_agent needs provider config, ...) then rejected the
disable/enable flow with "<field> is required".

Drop the envelope in get_metadata so set() routes back to /mtd, where
the validator-skip-on-metadata-only path applies and the round-trip
preserves the record's data on the server.

Surfaced testing the new ai-skill disable/enable shortcut against
white-sands; secret/lookup escaped the bug only because their
validators tolerate empty data.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(test): give ai-memory CLI tests a serializable mock response

The 5 failing tests mocked Client/Organization but never set a return
value for client.request(), so the SDK call returned a MagicMock that
the CLI's _output() then crashed on with
"Type is not JSON serializable: MagicMock".

Set request.return_value = {} on each so the CLI's serialization step
gets a real dict.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…288)

* feat: add vulnerability CLI commands for ext-vulnerability-reporting

Adds a dedicated `limacharlie vulnerability` command set that wraps
the ext-vulnerability-reporting extension's RPC actions (query_cves,
query_cve, query_cve_vuln_hosts, query_cve_vuln_packages,
query_endpoints, query_host_vuln_packages, query_dashboard,
scan_packages) behind typed SDK and Click surfaces.

Subscription management stays under `limacharlie extension`; this
group is purely the user-facing query / scan-trigger surface.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: add unwrap=False flag to Extensions.request

Adds an opt-in unwrap parameter to limacharlie.sdk.extensions.Extensions.request
that returns the inner data field of the standard {data, error, retry}
extension envelope instead of the whole response. Default stays False to keep
existing callers (feedback, cases, configs, the extension request CLI)
byte-compatible.

The new vulnerability SDK opts in everywhere so its CLI surfaces clean
payloads — JMESPath filters can now address fields directly (e.g.
'results[*].cve' instead of 'data.results[*].cve') and --output table
renders rows instead of '{N keys}'.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: drop --simulate from vulnerability scan

The simulate flag was a debug knob from the extension's testing
pipeline; it doesn't belong in the user-facing CLI.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
lc-cbot and others added 2 commits May 7, 2026 07:21
The validator currently rejects `auth login --uid X --api-key Y`
with "Error: --oid and --api-key are required for API key login.",
even though `--uid` is documented as the flag for user-scoped API
keys. This makes brand-new accounts un-bootstrappable from the CLI
because they have no organization yet — chicken-and-egg, since the
user can't list/create their first org without working CLI auth,
but can't auth without an OID, but doesn't have an OID until they
create an org.

The User API Key itself is a fully valid credential — env-var auth
(LC_UID + LC_API_KEY) works for `auth list-orgs` already, which is
the only call needed to bootstrap from "I have a User API Key" to
"I know my OIDs". The bug was purely in the `auth login` validator
forcing an --oid constraint that the rest of the CLI doesn't need.

Fix: rework the validator to accept either:
  --oid + --api-key  (org-scoped key — existing behavior)
  --uid + --api-key  (user-scoped key — new path, --oid optional)

When neither --oid nor --uid is provided, emit a clear error
distinguishing the two key types. The `write_credentials` call
already handles oid=None correctly (skips writing the field), so
no downstream changes needed.

Tested:
- `auth login --uid X --api-key Y` (no --oid) → succeeds
- `auth list-orgs` against those credentials → returns the user's orgs
- `auth login --api-key Y` (neither --oid nor --uid) → clear error
- `auth login --oid X --api-key Y` (existing org-scoped path) → unchanged
- `auth login --oauth --provider google` → unchanged
Follow-up to #289. When a user re-runs `auth login --uid X --api-key Y`
in an environment that previously held an `--oid + --api-key` login, the
old `oid` survived in config because `write_credentials` only writes
fields it is given. Subsequent commands would silently pair the new
user-scoped credentials with the stale org context.

Fix it by dropping the `oid` field from the affected env block right
after the write whenever the login was user-scoped (`uid` set, `oid`
unset). Mirrors the post-write cleanup pattern already used by the
OAuth flow for `api_key`.

Also refresh the `--ai-help` text so it advertises the user-scoped
shape (`--uid + --api-key`, no `--oid`) that #289 enabled, and add
unit tests for the validator branches and the stale-oid cleanup --
neither was covered before.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants