Skip to content

feat(terminal): add TUI app and Claude Code plugin#142

Merged
bb-connor merged 4 commits intomainfrom
feat/tui-poc
Mar 3, 2026
Merged

feat(terminal): add TUI app and Claude Code plugin#142
bb-connor merged 4 commits intomainfrom
feat/tui-poc

Conversation

@bb-connor
Copy link
Copy Markdown
Collaborator

@bb-connor bb-connor commented Mar 2, 2026

Summary

  • Adds apps/terminal/ — interactive TUI dashboard for ClawdStrike with hunt, policy, audit, and workcell screens
  • Adds apps/terminal/clawdstrike-plugin/ — full-featured Claude Code plugin with policy enforcement at the tool boundary

Plugin capabilities

  • 6 hooks: PreToolUse (fail-closed policy check), PostToolUse (receipt logging), SessionStart/End (audit trail), UserPromptSubmit (prompt injection detection), Stop (graceful session close)
  • 15 MCP tools: check, scan, query, timeline, correlate, ioc, policy_show, policy_eval, hunt_diff, report, policy_lint, policy_simulate, verify_receipt, merkle_verify, guard_inspect
  • 2 MCP resources: session receipts (JSONL), policy by ruleset (YAML)
  • 2 MCP prompts: investigate-incident (6-step threat hunt), assess-posture (posture assessment workflow)
  • 3 skills: security-review, threat-hunt, policy-guide — with improved triggers, tool mappings, and remediation guidance
  • 6 commands: audit, scan, posture, policy, tui, selftest
  • 1 agent: security-reviewer with batching guidance and concrete before/after code fix examples

Bug fixes

  • Fixed CLI arg format for policy show (positional arg, not --ruleset flag)
  • Fixed policy eval to pipe event JSON via stdin instead of CLI arg
  • Fixed session ID collision (uses uuidgen)
  • Fixed empty target bypassing policy (fail-closed for Read/Write/Edit/Bash)
  • Fixed hardcoded "success" outcome in post-tool receipts

Test plan

  • bash -n scripts/*.sh — all 6 shell scripts parse
  • bun tsc --noEmit — TypeScript compiles cleanly
  • bun test — 30/30 tests pass (hooks + MCP tools)
  • Manual: 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 the clawdstrike CLI 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: Beads JSONL issue tracking, rollout telemetry storage, a lightweight healthcheck system, a local TCP JSON-RPC MCP server (plus external MCP client connections), and a hushd HTTP/SSE client used for policy/audit workflows and hunt bridging.

Separately, adds .claude-plugin/marketplace.json plus 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.

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>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +355 to +357
const issues = await Beads.query({
limit: options.timeout, // reusing timeout for limit
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--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.

Fix in Cursor Fix in Web

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]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Fix in Cursor Fix in Web

error: "Speculation aborted",
})),
timing: { startedAt, completedAt },
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

}

const response = await fetch(
`https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-pro:generateContent?key=${apiKey}`,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

- 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>
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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()
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Fix in Cursor Fix in Web

started_at,
completed_at: new Date().toISOString(),
success,
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Fix in Cursor Fix in Web

const lines = buffer.split("\n")
buffer = lines.pop() ?? ""

let eventData = ""
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

@bb-connor bb-connor merged commit 5879aa7 into main Mar 3, 2026
40 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant