Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions limacharlie/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
57 changes: 57 additions & 0 deletions limacharlie/commands/ai_cost_model.py
Original file line number Diff line number Diff line change
@@ -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")
Comment thread
maximelb marked this conversation as resolved.

# 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.
""")
4 changes: 4 additions & 0 deletions limacharlie/commands/hive.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ def _record_from_input(key: str, data: Any) -> HiveRecord:
"external_adapter",
"sop",
"org_notes",
"app",
"ai_cost_model",
Comment thread
maximelb marked this conversation as resolved.
]


Expand Down Expand Up @@ -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)
Expand Down
4 changes: 3 additions & 1 deletion limacharlie/help_topics.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"] = """\
Expand Down
6 changes: 4 additions & 2 deletions tests/unit/test_cli_lazy_loading_regression.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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"),
Expand Down Expand Up @@ -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",
}),
Expand Down Expand Up @@ -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), (
Expand Down