Skip to content

feat(cli): harness add/remove/list/serve + node ports - #72

Merged
devin-ai-integration[bot] merged 6 commits into
v2from
feat/cli
Oct 8, 2026
Merged

devin-ai-integration[bot] merged 6 commits into
v2from
feat/cli

Conversation

@espetro

@espetro espetro commented Oct 8, 2026

Copy link
Copy Markdown
Collaborator

Summary

Workstream W9 of the v2 build-out (.agents/plans/2026-10-07-v2-foundation.md, Wave 2): implements packages/cli — the harness binary — against the pinned @any-harness/sdk surface that W8 is building in parallel.

Flag-driven only, per the scriptc-island constraints (AGENTS.md §7): every mutating op takes -y, all output supports --json, git ops shell out to the git binary, symlinks go through ln with a copy fallback, logging is a console shim on stderr. No TUI/prompt/arg-parser/TOML/log deps.

Changes

  • Commands: add <source> [--skill-target shared|store] [-y], remove <name> [-y], list [--all] [--kinds], enable/disable <name>, verify <name> (trust-violation on integrity mismatch), doctor (exit 1 on error findings), audit [--event|--actor|--extension|--limit] (reads audit.log JSONL via the fs port, tolerant of partial lines), serve [--transport stdio].
  • src/ports/: node-side StorePorts — FsPort over node:fs (atomic sibling-temp+rename writes per store-layout §7.1, ln-based symlink with copy fallback) and ExecPort over node:child_process (one-token+argv exec for git, shell run; non-zero exits are results, not throws).
  • src/serve/: stdio NDJSON loop → handleBridgeRequest (parse-error -32700, invalid-request -32600, notifications unanswered, handler throws as -32603); per-request caller authz from [[serve.caller]] in config.toml (deny wins, * wildcard, auto-granted capabilities.negotiate/events.notify, -32012 forbidden); exec-policy gate resolving ask → exec.nonInteractive (deny default) with -32007 policy-denied — no interactive asks through a bridge session.
  • Core plumbing: api.ts carries the pinned surface (Store/ports/SourceRef/JSON-RPC); sdk-bind.ts is the only file importing @any-harness/sdk — it casts through unknown and resolves each export lazily, so tsc is green against the current stub and commands fail cleanly with sdk-unavailable until W8 merges. Hand-rolled flag parser, minimal TOML-subset reader for [policy]/[[serve.caller]], actor detection per trust.md §4.2.
  • Wiring: @any-harness/sdk workspace dep, tsdown build (dist/cli.mjs + shebang bin), types: ["node"] in tsconfig.
  • Tests: 55 vitest cases on a hand-rolled in-memory Store + memory FsPort (src/testing/) — no sdk import on the test path. Covers the flag parser, TOML/policy/caller loading, all commands' flag surfaces + confirmation discipline, authz/policy-gate/NDJSON framing, and the real node ports on a tmpdir.

Test plan

  • mise exec -- pnpm -F @any-harness/cli test — 55/55 pass
  • mise exec -- pnpm -r typecheck — clean across all 5 packages (structural cast keeps the sdk-stub boundary compiling)
  • mise exec -- pnpm -F @any-harness/cli build — dist/cli.mjs; --version/--help work, commands emit a clean sdk-unavailable JSON error until W8 lands
  • Capability matrix updated (if adapter behaviour changed) — n/a, no emitters yet
  • Linked to a refined issue in Project 14

Notes

  • Sdk-integration gaps: FsPort/ExecPort member-level shapes, Store return types (VerifyResult, DoctorReport), and InstallOptions are my reading of the pin — W8's canonical definitions may need reconciling at merge; api.ts + sdk-bind.ts are the only drift points by construction.
  • exec.nonInteractive is NOT consulted for store mutations — -y is the only non-interactive attestation for add/remove; the policy key governs exec-class ask resolution only (trust.md §4.1 scope).
  • harness serve --http is out of scope (v0.1 ships stdio only); --transport rejects non-stdio values with a usage error. [[serve.caller]] authz is honored for the stdio owner caller when an explicit owner entry exists — an opt-in narrowing; default is the spec's full op set.

Link to Devin session: https://app.devin.ai/sessions/be2e2d373dbf4e6a851935c31b794ba8
Open in Devin Desktop: https://app.devin.ai/desktop/session/be2e2d373dbf4e6a851935c31b794ba8?variant=devin
Requested by: @espetro

api.ts carries the Wave-2 pin (Store/StorePorts/FsPort/ExecPort/
SourceRef/JsonRpc*/BridgeMethod); sdk-bind.ts is the single file
importing @any-harness/sdk — it casts through unknown so typecheck
stays green while the W8 stub stands, and resolves each export
lazily with a clear sdk-unavailable CliError.

Core modules: hand-rolled flag parser (no arg lib), console-shim
output with --json everywhere, minimal TOML-subset reader for
[policy] + [[serve.caller]] (no parser dep), CliError exit codes,
actor detection per trust.md 4.2, -y/interactive confirmation.
packages/cli owns the concrete StorePorts the sdk store runs on:
FsPort over node:fs (atomic sibling-temp+rename writes per
store-layout 7.1, recursive ops, symlink via the ln binary with
copy as the caller's fallback — no symlinkSync on the island) and
ExecPort over node:child_process (exec = one token + argv for git,
run = shell form; non-zero exits are results, not throws).
Flag-driven command surface, no TUI: every mutating op takes -y
(hand-rolled y/N on interactive TTY, confirmation-required
otherwise); --json on every command; --skill-target shared|store
on add (shared skills root default per store-layout 5.1); actor
classification forwarded to store.install for the trust layer;
audit reads audit.log JSONL through the fs port, tolerating
partial lines (trust.md 6.1); verify raises trust-violation on
integrity mismatch; doctor exits 1 on error findings.
NDJSON loop per transports 1: parse-error -32700, invalid-request
-32600, notifications dispatched without reply, handler throws as
-32603 without killing the loop. Per-request authz from
[[serve.caller]] (deny wins, absent allow = full set, * wildcard,
auto-granted negotiate/notify) with -32012 forbidden; stdio
identity is the implicit owner caller. Trust-policy gate resolves
exec.hooks/exec.mcp ask to exec.nonInteractive (deny default) with
-32007 policy-denied — no interactive asks through a bridge
session. Diagnostics on stderr only; stdout stays protocol-clean.
cli.ts is the harness bin (shebang, tsdown build to dist/cli.mjs,
workspace dep on @any-harness/sdk). Global flags: --root,
--json, --help, --version; store root resolves ANYHARNESS_STORE >
ANYHARNESS_HOME/harness > ~/.agents/harness.

Tests run entirely on a hand-rolled in-memory Store + memory
FsPort under src/testing/ — no sdk import on the test path, so
vitest stays green while packages/sdk is a stub. 55 tests cover
the flag parser, TOML/policy/caller loading, all eight commands,
caller authz + policy gate + NDJSON framing, and the real node
ports on a tmpdir.
@devin-ai-integration

Copy link
Copy Markdown

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Approved. Cursor Bugbot was not present on this PR after the initial check poll, so that automated-review signal was skipped; no applicable approval policy required human review. This is a non-blocking approval from the Cursor Approval Agent.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Deploying agentplugins with  Cloudflare Pages  Cloudflare Pages

Latest commit: 38ce90c
Status:🚫  Build failed.

View logs

@devin-ai-integration
devin-ai-integration Bot merged commit 09a55d7 into v2 Oct 8, 2026
1 of 2 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