// tunder CLI reference — mirrors agents/src/tunder/commands/
function CLI() {
  const shell = useShell();
  const tocItems = [
    { id: 'overview', label: 'Overview' },
    { id: 'invocation', label: 'How to invoke' },
    { id: 'skill', label: 'skill' },
    { id: 'memory', label: 'memory' },
    { id: 'cron', label: 'cron' },
    { id: 'sessions', label: 'sessions' },
    { id: 'message', label: 'message' },
    { id: 'channels', label: 'channels' },
    { id: 'agents', label: 'agents' },
    { id: 'agent-config', label: 'agent-config' },
    { id: 'api-keys', label: 'api-keys' },
    { id: 'config', label: 'config' },
    { id: 'pairing', label: 'pairing' },
    { id: 'approvals', label: 'approvals' },
    { id: 'plugins', label: 'plugins' },
    { id: 'secrets', label: 'secrets' },
    { id: 'security', label: 'security' },
    { id: 'backup', label: 'backup' },
    { id: 'doctor', label: 'doctor' },
    { id: 'status', label: 'status' },
    { id: 'logs', label: 'logs' },
  ];

  const CMDS = [
    { id: 'skill', desc: 'Manage AI skills (Markdown files in /data/tenants/{id}/skills/)', verbs: [
      ['list',    'List all enabled skills with their descriptions'],
      ['get',     'Get full SKILL.md content by name',                ['--name (required)']],
      ['search',  'Fuzzy search skills by name/description',          ['--query (required)']],
      ['install', "Install a skill from a local .md path or a URL",   ['--from (required)', '--name']],
      ['update',  "Replace an already-installed skill's SKILL.md",    ['--name (required)', '--content (required)']],
    ]},
    { id: 'memory', desc: 'Persistent AI memory (hybrid vec + FTS5 search)', verbs: [
      ['list',    'List all memories (key + value + category)'],
      ['recall',  'Get a memory by exact key',                         ['--key (required)']],
      ['search',  'Hybrid vector + keyword search over memories',      ['--query (required)', '--limit']],
      ['compact', 'Summarize the older half of MEMORY.md via LLM to bound its growth'],
    ]},
    { id: 'cron', desc: 'Scheduled tasks (OpenClaw-style cron)', verbs: [
      ['list',   'List all cron jobs with status'],
      ['create', 'Create a scheduled task',                            ['--name (required)', '--schedule (required)', '--message (required)']],
      ['run',    'Force-run a task now',                               ['--name (required)']],
      ['runs',   'View run history for a specific task',               ['--name (required)']],
      ['edit',   "Edit a task's name, message, or enabled status"],
      ['remove', 'Delete a scheduled task',                            ['--name (required)']],
      ['status', 'Scheduler health snapshot'],
    ]},
    { id: 'sessions', desc: 'Inspect + manage session history', verbs: [
      ['list',    'List sessions newest-first',                        ['--agent', '--limit']],
      ['get',     'Get session metadata (DB row) by id',               ['--id (required)']],
      ['history', 'Get full transcript (messages[]) of a session',     ['--id (required)']],
      ['cleanup', 'Delete sessions older than N days',                 ['--days (required)']],
      ['send',    "Append a user message to another session's transcript (no reply)", ['--id (required)', '--text (required)']],
      ['spawn',   'Create a new session and run one message through it synchronously', ['--agent (required)', '--message (required)']],
    ]},
    { id: 'message', desc: 'Higher-level messaging alias over sessions (send / poll / thread)', verbs: [
      ['send',   'Append a user message to a session (alias of sessions.send)', ['--id (required)', '--text (required)']],
      ['poll',   'Return messages added to a session after a given timestamp',  ['--id (required)', '--since']],
      ['thread', 'Read the full transcript of a session (alias of sessions.history)', ['--id (required)']],
    ]},
    { id: 'channels', desc: 'Introspect configured channels (config/channels/*.yaml)', verbs: [
      ['list',   'List all configured channels + policy summary'],
      ['status', 'Probe a channel for reachability + auth (e.g. Telegram getMe)', ['--channel (required)']],
      ['logout', "Revoke a channel: clear botToken, set dmPolicy=disabled, deleteWebhook", ['--channel (required)']],
    ]},
    { id: 'agents', desc: 'Introspect agent + binding config (config/agents.yaml)', verbs: [
      ['list',     'List agents defined in agents.yaml (or built-in defaults)'],
      ['bindings', 'List routing bindings (channel + accountId → agentId)'],
    ]},
    { id: 'agent-config', desc: 'View / reload per-tenant agents.yaml overrides', verbs: [
      ['list',   "Show the tenant's active agent overrides"],
      ['reload', 'Invalidate the cached config — next agent turn re-reads the file'],
    ]},
    { id: 'api-keys', desc: "Manage this tenant's API keys (issue / list / revoke)", verbs: [
      ['issue',  'Create a new API key for this tenant. Returns the raw key ONCE.', ['--name (required)']],
      ['list',   'List keys for this tenant (masked). Add --all to include revoked.', ['--all']],
      ['revoke', 'Revoke an active key by name (idempotent)',         ['--name (required)']],
    ]},
    { id: 'config', desc: 'Get/set simple tenant config values (config/tenant.yaml)', verbs: [
      ['get',    'Get a value by key',                                 ['--key (required)']],
      ['set',    'Set a value by key (string). Creates the file on first write.', ['--key (required)', '--value (required)']],
      ['list',   'List all keys with values'],
      ['delete', 'Delete a key',                                       ['--key (required)']],
      ['schema', 'Return the expected shape of tenant.yaml + current validation status'],
    ]},
    { id: 'pairing', desc: 'Manage unknown-sender pairing codes (dmPolicy: pairing)', verbs: [
      ['list',    'List pending pairing requests',                    ['--channel']],
      ['approve', 'Approve a pairing code, update allowFrom, notify sender', ['--code (required)']],
      ['reject',  'Reject a pairing code (sender stays blocked)',     ['--code (required)']],
    ]},
    { id: 'approvals', desc: 'Exec approval queue for tools.exec.ask=always / denied commands', verbs: [
      ['list',    'List pending approvals newest-first'],
      ['get',     'Get approval by id',                               ['--id (required)']],
      ['approve', 'Approve a pending approval',                       ['--id (required)']],
      ['reject',  'Reject a pending approval',                        ['--id (required)']],
      ['request', 'Seed a pending approval (for testing)',            ['--command (required)']],
    ]},
    { id: 'plugins', desc: 'Tenant plugin tools (list / reload)', verbs: [
      ['list',   'List plugin tools currently registered for this tenant'],
      ['reload', 'Re-walk /plugins and re-import every plugin file'],
    ]},
    { id: 'secrets', desc: 'Audit env keys + provider connectivity', verbs: [
      ['audit', 'Run all key/secret checks and report'],
    ]},
    { id: 'security', desc: 'Filesystem posture audit (perms, channel configs)', verbs: [
      ['audit', 'Audit; add --fix to repair perms in place',          ['--fix']],
    ]},
    { id: 'backup', desc: "Snapshot a tenant's /data/tenants/{id}/ dir (+ sha256 sidecar)", verbs: [
      ['create', 'Create a tar.gz snapshot + .sha256 sidecar'],
      ['list',   'List backups for this tenant'],
      ['verify', 'Recompute sha256 and compare to the stored sidecar hash', ['--file (required)']],
    ]},
    { id: 'doctor', desc: 'Diagnose tenant health', verbs: [
      ['check', 'Run all health checks'],
    ]},
    { id: 'status', desc: 'Dashboard snapshot for this tenant', verbs: [
      ['snapshot', 'Summary of tenant state'],
    ]},
    { id: 'logs', desc: 'Tenant-facing logs', verbs: [
      ['tail',  'Show recent log entries',                             ['--lines', '--level']],
      ['clear', 'Delete all log rows older than N days',               ['--days (required)']],
    ]},
  ];

  const VerbTable = ({ verbs }) => (
    <div style={{overflow:'auto', margin:'8px 0 28px'}}>
      <table className="doc-table">
        <thead><tr>
          <th style={{width:'18%'}}>Verb</th>
          <th style={{width:'52%'}}>Description</th>
          <th style={{width:'30%'}}>Arguments</th>
        </tr></thead>
        <tbody>
          {verbs.map(([v, d, args]) => (
            <tr key={v}>
              <td><code style={{fontSize:'0.9em'}}>{v}</code></td>
              <td>{d}</td>
              <td>{args ? args.map((a, i) => <code key={i} style={{fontSize:'0.85em', marginRight:6}}>{a}</code>) : <span style={{color:'var(--ink-4)'}}>—</span>}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );

  return (
    <div className="app">
      <Topbar section="docs" theme={shell.theme} setTheme={shell.setTheme}
              onSearch={() => shell.setSearchOpen(true)}
              onMenuToggle={() => shell.setMobileMenuOpen(true)} />
      <div className="main">
        <Sidebar activeId="cli"
                 mobileOpen={shell.mobileMenuOpen}
                 onMobileClose={() => shell.setMobileMenuOpen(false)} />
        <article className="content">
          <div className="crumbs">
            <a href="index.html">Docs</a>
            <span className="sep">/</span>
            <a href="#">Reference</a>
            <span className="sep">/</span>
            <span>tunder CLI</span>
          </div>

          <div className="eyebrow">Reference · 19 commands</div>
          <h1 className="h1">tunder <em>CLI.</em></h1>
          <p className="lede">
            Every noun and verb the agent runtime (or an operator with shell access) can dispatch
            against a tenant. Mirrors OpenClaw's command layout. The LLM reaches these via{' '}
            <code>bash("tunder &lt;noun&gt; &lt;verb&gt; --arg=value")</code> when a shell call is
            needed; operators run them inside the agents container.
          </p>

          <h2 id="overview" className="h2">Overview</h2>
          <p>
            Commands are defined under <code>agents/src/tunder/commands/</code> — one file per
            noun. Each verb declares its args (name, required?, description) and a handler that
            receives the parsed args + a{' '}
            <code>CommandContext</code> (accountId, db, tenant dir). The dispatcher lives in{' '}
            <code>agents/src/tunder/index.ts</code>.
          </p>
          <Callout type="note" title="Why not expose these as LLM tools?">
            They're mostly admin/ops surface — routine operator work like backups, key rotation,
            approvals, plugin reloads — not things you want every agent turn reaching for. Exposing
            them as direct tools would bloat the schema surface and expand the attack surface for
            prompt injection. Putting them behind <code>bash</code> means only the admin agent
            sees them, and they remain scriptable for humans.
          </Callout>

          <h2 id="invocation" className="h2">How to invoke</h2>
          <CodeBlock tabs={[
            { label: 'inside the agents container', raw: `# Operator, dropping into the container\ndocker compose exec agents bun run src/tunder/cli.ts skill list --account 5\n\n# Help\ndocker compose exec agents bun run src/tunder/cli.ts --help\ndocker compose exec agents bun run src/tunder/cli.ts cron --help`,
              code: `<span class="tok-com"># Operator, dropping into the container</span>
<span class="tok-com">$</span> docker compose exec agents bun run src/tunder/cli.ts skill list --account <span class="tok-num">5</span>

<span class="tok-com"># Help</span>
<span class="tok-com">$</span> docker compose exec agents bun run src/tunder/cli.ts --help
<span class="tok-com">$</span> docker compose exec agents bun run src/tunder/cli.ts cron --help` },
            { label: 'via bash tool (agent)', raw: `// The admin agent decides to run a tunder command:\ntool_call: bash({ command: "tunder memory search --query='latest sales number'" })\n// -> dispatcher runs it in the tenant sandbox, returns stdout`,
              code: `<span class="tok-com">// The admin agent decides to run a tunder command:</span>
<span class="tok-kw">tool_call</span>: bash({ command: <span class="tok-str">"tunder memory search --query='latest sales number'"</span> })
<span class="tok-com">// -&gt; dispatcher runs it in the tenant sandbox, returns stdout</span>` },
          ]} />

          {CMDS.map((c) => (
            <React.Fragment key={c.id}>
              <h2 id={c.id} className="h2"><code>{c.id}</code></h2>
              <p>{c.desc}</p>
              <VerbTable verbs={c.verbs} />
            </React.Fragment>
          ))}

          <Feedback />
          <PageFoot
            prev={{ label: 'Tool registry', href: 'tools.html' }}
            next={{ label: 'API reference', href: 'api-reference.html' }}
          />
        </article>
        <TOC items={tocItems} />
      </div>
      <SearchOverlay open={shell.searchOpen} onClose={() => shell.setSearchOpen(false)} />
      <TweaksPanel visible={shell.tweaksVisible} theme={shell.theme} setTheme={shell.setTheme} />
    </div>
  );
}
ReactDOM.createRoot(document.getElementById('root')).render(<CLI />);
