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
12 changes: 12 additions & 0 deletions doc/cli/hive-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,18 @@ limacharlie external-adapter list
limacharlie cloud-adapter list
```

### app

User-authored, AI-generated mini web apps (a self-contained HTML document
rendered in a sandboxed iframe by the web UI).

```bash
limacharlie app list
limacharlie app get --key my-app
limacharlie app set --key my-app --input-file app.yaml
limacharlie app delete --key my-app --confirm
```

## extension

```bash
Expand Down
1 change: 1 addition & 0 deletions limacharlie/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ def _config_no_warnings() -> bool:
"ai-skill": ("ai_skill", "group"),
"api": ("api_cmd", "cmd"),
"api-key": ("api_key", "group"),
"app": ("app", "group"),
"arl": ("arl", "group"),
"artifact": ("artifact", "group"),
"audit": ("audit", "group"),
Expand Down
62 changes: 62 additions & 0 deletions limacharlie/commands/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""App 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("app", "app", "app")

# Override the generic hive explains with app-specific documentation.

register_explain("app.list", """\
List all apps stored in the 'app' hive. An app is a user-authored,
AI-generated mini web application: a single self-contained HTML
document that the LimaCharlie web UI renders inside a sandboxed
<iframe>.
""")

register_explain("app.get", """\
Get a specific app by key. Returns the full record including the
HTML document, required permissions, and metadata.
""")

register_explain("app.set", """\
Create or update an app. An app record holds a single self-contained
HTML document (HTML + inline JS + inline CSS) rendered in a sandboxed
iframe.

The data payload looks like:

data:
display_name: "My App" # required, human label
html: "<html>...</html>" # required, the self-contained document
description: "What it does" # optional blurb
icon: "🛠" # optional emoji / icon id / data-URI
required_permissions: # perms minted into the iframe JWT,
- sensor.get # intersected with the viewing user's
- sensor.task # own permissions at view time
allowed_origins: # optional third-party https origins
- https://example.com # the iframe JS may contact (CSP)
required_services: # optional first-party LC services to
- search # broker: search, replay, cases, ai
locations: # optional UI surfaces: standalone,
- standalone # within_a_sensor, within_a_case, etc.
expected_context: # optional context keys passed in when
- sid # embedded (sid, atom, detection_id…)

Records can be tagged and organized with usr_mtd:

usr_mtd:
tags: [internal, dashboard]
comment: "Sensor triage dashboard"

Provide data via --input-file (YAML/JSON) or pipe through stdin.

Examples:
limacharlie app set --key triage-dashboard --input-file app.yaml
""")

register_explain("app.delete", """\
Delete an app from the 'app' hive. Requires --confirm.
""")
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", "arl", "artifact",
"ai", "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 @@ -64,6 +64,7 @@
"ai_skill": ("group", "ai-skill"),
"api_cmd": ("cmd", "api"),
"api_key": ("group", "api-key"),
"app": ("group", "app"),
"arl": ("group", "arl"),
"artifact": ("group", "artifact"),
"audit": ("group", "audit"),
Expand Down Expand Up @@ -127,6 +128,7 @@
}),
"ai-skill": frozenset({"delete", "disable", "enable", "get", "list", "set", "tag"}),
"api-key": frozenset({"create", "delete", "list"}),
"app": frozenset({"delete", "disable", "enable", "get", "list", "set", "tag"}),
"arl": frozenset({"get"}),
"artifact": frozenset({"download", "list", "upload"}),
"audit": frozenset({"list"}),
Expand Down Expand Up @@ -913,7 +915,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"):
for name in ("secret", "fp", "lookup", "playbook", "sop", "note", "app"):
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