diff --git a/limacharlie/cli.py b/limacharlie/cli.py index 56df3d72..96d48d61 100644 --- a/limacharlie/cli.py +++ b/limacharlie/cli.py @@ -98,6 +98,7 @@ def _config_no_warnings() -> bool: # The regression test TestModuleMapping verifies this stays in sync. _COMMAND_MODULE_MAP: dict[str, tuple[str, str]] = { "ai": ("ai", "group"), + "ai-cost-model": ("ai_cost_model", "group"), "ai-memory": ("ai_memory", "group"), "ai-skill": ("ai_skill", "group"), "api": ("api_cmd", "cmd"), diff --git a/limacharlie/commands/ai_cost_model.py b/limacharlie/commands/ai_cost_model.py new file mode 100644 index 00000000..ba6c2cd6 --- /dev/null +++ b/limacharlie/commands/ai_cost_model.py @@ -0,0 +1,57 @@ +"""AI cost model commands for LimaCharlie CLI v2.""" + +from __future__ import annotations + +from ._hive_shortcut import make_hive_group +from ..discovery import register_explain + +group = make_hive_group("ai-cost-model", "ai_cost_model", "AI cost model") + +# Override the generic hive explains with cost-model-specific documentation. + +register_explain("ai-cost-model.list", """\ +List all AI cost-model profiles stored in the organization. Each +profile turns AI-agent activity into an auditable cost/savings figure +by pairing a fully-burdened analyst rate with the standard manual +handling time for one investigation of that kind of work. Because +this hive is per-org (OID-partitioned), the profiles are per-tenant. +""") + +register_explain("ai-cost-model.get", """\ +Get a specific AI cost-model profile by key. Returns the full record +including the economic inputs and metadata. +""") + +register_explain("ai-cost-model.set", """\ +Create or update an AI cost-model profile. + +The data payload describes one costing profile: + + data: + label: "SOC L1 Triage" # optional display name + loaded_hourly_rate: 95.0 # required: fully-burdened USD/hour + minutes_per_investigation: 30.0 # required: standard manual minutes + rate_source_note: "FY26 loaded SOC cost / 1,600 hrs" # optional provenance + +loaded_hourly_rate and minutes_per_investigation are required and must +be non-negative; the backend rejects either value above 100000. + +Profiles can be tagged and organized with usr_mtd: + + data: + label: "SOC L1 Triage" + loaded_hourly_rate: 95.0 + minutes_per_investigation: 30.0 + usr_mtd: + tags: [soc, triage] + comment: "FY26 baseline" + +Provide data via --input-file (YAML/JSON) or pipe through stdin. + +Examples: + limacharlie ai-cost-model set --key soc-l1 --input-file model.yaml +""") + +register_explain("ai-cost-model.delete", """\ +Delete an AI cost-model profile. Requires --confirm. +""") diff --git a/limacharlie/commands/hive.py b/limacharlie/commands/hive.py index 03594d48..f8f2b378 100644 --- a/limacharlie/commands/hive.py +++ b/limacharlie/commands/hive.py @@ -100,6 +100,8 @@ def _record_from_input(key: str, data: Any) -> HiveRecord: "external_adapter", "sop", "org_notes", + "app", + "ai_cost_model", ] @@ -141,6 +143,8 @@ def group() -> None: ai_agent - AI agent configurations ai_skill - Claude Code skill definitions ai_memory - AI agent memories (partial-merge updates) + ai_cost_model - Per-org AI cost/savings economic model + app - AI-generated iframe web apps Each record returned contains: data - The record payload (structure varies by hive type) diff --git a/limacharlie/help_topics.py b/limacharlie/help_topics.py index 43cd5989..5ef3df09 100644 --- a/limacharlie/help_topics.py +++ b/limacharlie/help_topics.py @@ -85,8 +85,10 @@ limacharlie cloud-sensor list/get/set/delete limacharlie sop list/get/set/delete limacharlie note list/get/set/delete + limacharlie app list/get/set/delete + limacharlie ai-cost-model list/get/set/delete -Related commands: hive, secret, lookup, playbook, adapter, cloud-sensor, sop, note +Related commands: hive, secret, lookup, playbook, adapter, cloud-sensor, sop, note, app, ai-cost-model """ HELP_TOPICS["lcql"] = """\ diff --git a/tests/unit/test_cli_lazy_loading_regression.py b/tests/unit/test_cli_lazy_loading_regression.py index 1c993db3..8115210b 100644 --- a/tests/unit/test_cli_lazy_loading_regression.py +++ b/tests/unit/test_cli_lazy_loading_regression.py @@ -45,7 +45,7 @@ # Every top-level command/group that must be registered on cli. EXPECTED_TOP_LEVEL_COMMANDS = frozenset({ - "ai", "ai-memory", "ai-skill", "api", "api-key", "app", "arl", "artifact", + "ai", "ai-cost-model", "ai-memory", "ai-skill", "api", "api-key", "app", "arl", "artifact", "audit", "auth", "billing", "case", "cloud-adapter", "completion", "config", "detection", "download", "dr", "endpoint-policy", "event", "exfil", "extension", "external-adapter", "feedback", "fp", "group", "help", "hive", @@ -60,6 +60,7 @@ EXPECTED_MODULE_MAP = { "adapter": ("group", "external-adapter"), "ai": ("group", "ai"), + "ai_cost_model": ("group", "ai-cost-model"), "ai_memory": ("group", "ai-memory"), "ai_skill": ("group", "ai-skill"), "api_cmd": ("cmd", "api"), @@ -123,6 +124,7 @@ "generate-response", "generate-rule", "generate-selector", "session", "start-session", "summarize-detection", "usage", }), + "ai-cost-model": frozenset({"delete", "disable", "enable", "get", "list", "set", "tag"}), "ai-memory": frozenset({ "delete", "delete-record", "get", "list", "list-records", "set", }), @@ -915,7 +917,7 @@ def test_hive_shortcut_commands_load_correctly(self): """Hive shortcut commands (secret, fp, lookup, etc.) must load correctly via lazy loading since they use a factory pattern.""" ctx = click.Context(cli) - for name in ("secret", "fp", "lookup", "playbook", "sop", "note", "app"): + for name in ("secret", "fp", "lookup", "playbook", "sop", "note", "app", "ai-cost-model"): cmd = cli.get_command(ctx, name) assert cmd is not None, f"Hive shortcut {name!r} not loaded" assert isinstance(cmd, click.Group), (