Conversation
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>
* 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>
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>
* 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>
* 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>
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>
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description of the change
Temporary PR to trigger the test validations.
Type of change