feat(terminal): add TUI app and Claude Code plugin#142
Conversation
Adds the terminal application with interactive TUI dashboard, agent dispatcher, hunt/query subsystem, workcell orchestration, and a full-featured Claude Code plugin with policy enforcement hooks. Plugin highlights: - 6 hooks (PreToolUse, PostToolUse, SessionStart, SessionEnd, UserPromptSubmit, Stop) with fail-closed enforcement - 15 MCP tools, 2 resources, 2 prompts via stdio server - 3 skills (security-review, threat-hunt, policy-guide) - 6 commands including /clawdstrike:selftest - Security-reviewer agent with batching and remediation guidance - 30 passing tests (hooks + MCP tools) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1fedf9fee8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const issues = await Beads.query({ | ||
| limit: options.timeout, // reusing timeout for limit | ||
| }) |
There was a problem hiding this comment.
Stop using timeout as the beads list limit
The beads list path maps options.timeout to Beads.query({ limit }), so an unrelated execution-timeout flag unexpectedly truncates issue output while documented list options (--limit, --status, etc.) are not actually wired. This produces incorrect listing behavior and makes paging/filtering controls unusable from the CLI.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 73543b6 — added proper --limit and --offset CLI flags and wired them to Beads.query(). No longer reuses --timeout as the limit.
- grid.ts: remove redundant `value > 0` check (always true after guard) - cli/index.ts: exit(1) on dispatch/speculate/gate failure instead of 0 - cli/index.ts: add --limit/--offset flags for beads list (was reusing --timeout as limit) - orchestrator.ts: pass task cwd to Workcell.acquire for speculation - session-start.sh: propagate SESSION_ID to subsequent hooks via hookSpecificOutput env field - session-start.sh: use --json flag for policy show so jq can parse it - mcp-server.ts: pipe events via stdin in policy_simulate (same fix as policy_eval, avoids CLI arg length limits) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix fail-open flag mismatch: scripts now accept `true`, `1`, `yes` (case-insensitive) instead of only `"1"`, matching README docs - Extract shared `callAnthropicApi`/`callOpenAiApi` into `adapters/llm-api.ts` to deduplicate opencode.ts and crush.ts - Move `clawdstrike-plugin/` from `apps/terminal/` to repo root for easier discovery and `--plugin-dir` usage - Add Claude Code Plugin section to README under OpenClaw Plugin - Update tui.md path after plugin relocation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| if (options.help && command === "help") { | ||
| await cmdHelp() | ||
| return | ||
| } |
There was a problem hiding this comment.
--help flag ignored, launches TUI instead
Medium Severity
Running clawdstrike --help or clawdstrike -h launches the interactive TUI instead of showing help. The condition at line 601 requires both options.help to be true AND command === "help", but when the user passes just --help without a subcommand, command is "". The --version flag is handled correctly (no command check), but --help effectively requires clawdstrike help -h — a combination nobody would type.
| config: RouterConfig = DEFAULT_CONFIG | ||
| ): Promise<RoutingDecision> { | ||
| // Combine default rules with config rules (config rules take precedence via priority) | ||
| const allRules = [...rules.DEFAULT_RULES, ...config.rules] |
There was a problem hiding this comment.
Router duplicates default rules on every route call
Medium Severity
Router.route() always doubles the default rules. DEFAULT_CONFIG.rules is assigned rules.DEFAULT_RULES, and line 73 concatenates rules.DEFAULT_RULES with config.rules. When called with the default config (the common path), every built-in rule appears twice in allRules. While set-based gate merging masks most symptoms, duplicated hint-override rules cause the hint action to be pushed twice into matchingActions, and the logic of "combine defaults with custom rules" is fundamentally broken.
Additional Locations (1)
| error: "Speculation aborted", | ||
| })), | ||
| timing: { startedAt, completedAt }, | ||
| } |
There was a problem hiding this comment.
Aborted speculation result has wrong field names
Medium Severity
createAbortedResult populates allResults items with success and gatesPassed fields, but the SpeculationResult type expects result (an ExecutionResult) and gateResults (a GateResults). Consumers accessing r.result?.success from the aborted entries get undefined instead of false, and the success/gatesPassed fields are dead data that nothing reads.
| } | ||
|
|
||
| const response = await fetch( | ||
| `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-pro:generateContent?key=${apiKey}`, |
There was a problem hiding this comment.
Google API key leaked in URL query parameter
Medium Severity
executeGoogleApi passes GOOGLE_API_KEY as a URL query parameter (?key=...). API keys in URLs can leak through server access logs, proxy logs, browser history, and HTTP Referer headers. The Anthropic and OpenAI adapters correctly place their keys in request headers.
- Create `.claude-plugin/marketplace.json` at repo root so the repo
serves as its own Claude Code plugin marketplace
- Update hook commands and MCP server args to use `${CLAUDE_PLUGIN_ROOT}`
so the plugin works when installed from marketplace cache
- Flesh out `plugin.json` with author, homepage, license, and keywords
- Update README install instructions with marketplace commands:
`/plugin marketplace add backbay-labs/clawdstrike`
Users can now install without cloning:
/plugin marketplace add backbay-labs/clawdstrike
/plugin install clawdstrike@clawdstrike
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 3 potential issues.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
| process.stdout.on("resize", () => { | ||
| this.updateTerminalSize() | ||
| this.render() | ||
| }) |
There was a problem hiding this comment.
Event listeners accumulate on each TUI re-entry
Medium Severity
setupInput adds data and resize listeners each time start() is called, but cleanup() never removes them. After using showBeads, showRuns, or showHelp (which call cleanup() then start()), duplicate listeners accumulate, causing every keypress to trigger handleInput multiple times — leading to increasingly broken input handling.
Additional Locations (1)
| started_at, | ||
| completed_at: new Date().toISOString(), | ||
| success, | ||
| } |
There was a problem hiding this comment.
Playbook result returns all steps as "pending"
Medium Severity
executePlaybook creates a shallow copy of each step ({ ...steps[i], status: "running" }) and mutates the copy, but the returned PlaybookResult.steps references the original unmodified array. All steps in the returned result retain their initial "pending" status, with no output, error, or duration_ms values, even though the onStepUpdate callbacks received the correct updated data.
Additional Locations (1)
| const lines = buffer.split("\n") | ||
| buffer = lines.pop() ?? "" | ||
|
|
||
| let eventData = "" |
There was a problem hiding this comment.
SSE parser loses events split across read chunks
Medium Severity
The eventData accumulator is declared inside the while (true) read loop, resetting to "" on every reader.read() iteration. If a TCP frame boundary falls between a data: line and its blank-line terminator, the accumulated event data from the previous read is discarded and the event is silently dropped. For a security monitoring SSE stream, silently losing events means missed security alerts.


Summary
apps/terminal/— interactive TUI dashboard for ClawdStrike with hunt, policy, audit, and workcell screensapps/terminal/clawdstrike-plugin/— full-featured Claude Code plugin with policy enforcement at the tool boundaryPlugin capabilities
Bug fixes
policy show(positional arg, not--rulesetflag)policy evalto pipe event JSON via stdin instead of CLI arguuidgen)Test plan
bash -n scripts/*.sh— all 6 shell scripts parsebun tsc --noEmit— TypeScript compiles cleanlybun test— 30/30 tests pass (hooks + MCP tools)claude --plugin-dir ./apps/terminal/clawdstrike-plugin— verify session start, tool enforcement, denial receipts, selftest, MCP tools🤖 Generated with Claude Code
Note
Medium Risk
Large, mostly additive terminal app that spawns external AI CLIs, runs verification gates, starts a local MCP server, and performs filesystem/network I/O, which increases the chance of runtime/interop issues. Core existing code is minimally touched beyond documentation and marketplace metadata.
Overview
Adds a new Bun/TypeScript terminal application (
apps/terminal) that provides theclawdstrikeCLI and interactive TUI for dispatching tasks to multiple AI toolchains, including rule-based routing, Speculate+Vote parallel execution, workcell sandboxing, and quality gates.The terminal app also introduces local persistence and integrations:
BeadsJSONL issue tracking, rollout telemetry storage, a lightweight healthcheck system, a local TCP JSON-RPC MCP server (plus external MCP client connections), and ahushdHTTP/SSE client used for policy/audit workflows and hunt bridging.Separately, adds
.claude-plugin/marketplace.jsonplus README updates documenting Claude Code plugin installation and usage.Written by Cursor Bugbot for commit 9e97acc. This will update automatically on new commits. Configure here.