diff --git a/README.md b/README.md index 875867b..1c1b27d 100644 --- a/README.md +++ b/README.md @@ -81,10 +81,13 @@ dotagents setup [--memory off|basic|memsearch] [--yes] [--dry-run] [--json] dotagents status [--agents ...] dotagents sync [--pull] [--agents ...] dotagents doctor [--e2e] [--agents ...] +dotagents view [--port N] [--host ADDR] # launch HarnessKit (inspection UI) dotagents skill new|update|promote dotagents mcp list|add|import|remove ``` +`dotagents view` shells out to [HarnessKit](https://github.com/RealZST/HarnessKit) (`hk serve`) for an inspection UI over every detected harness — skills, MCP servers, hooks, and configs in one place. HarnessKit does its own harness discovery and can also enable/disable/deploy; those writes bypass dotagents, so use `view` to inspect and reconcile any changes with `dotagents sync`. Install HarnessKit separately. + ## Configuration `~/.agents/dotagents.yaml` is the single source of truth; `setup` fills in detected harnesses. Resolution order: `--config ` → `$DOTAGENTS_HOME/dotagents.yaml` → `~/.agents/dotagents.yaml`; never walks the current project. Machine-local entries overlay via `dotagents.local.yaml`. Managed entries are marked in native configs; anything else is left untouched. diff --git a/cmd/dotagents/main.go b/cmd/dotagents/main.go index 49bf74a..53cfdef 100644 --- a/cmd/dotagents/main.go +++ b/cmd/dotagents/main.go @@ -139,6 +139,8 @@ func run(args []string) error { return runSyncCommand(args[1:]) case "doctor": return runDoctorCommand(args[1:]) + case "view": + return runView(args[1:]) case "skill": return runSkillCommand(args[1:]) case "mcp": @@ -490,6 +492,7 @@ func printAllUsage() { fmt.Println(" dotagents status [--agents ...]") fmt.Println(" dotagents sync [--pull] [--agents ...]") fmt.Println(" dotagents doctor [--e2e] [--agents ...]") + fmt.Println(" dotagents view [hk serve flags: --port N, --host ADDR, --no-token]") fmt.Println(" dotagents skill new [--description ...]") fmt.Println(" dotagents skill update [name ...]") fmt.Println(" dotagents skill promote [--dry-run]") diff --git a/cmd/dotagents/view.go b/cmd/dotagents/view.go new file mode 100644 index 0000000..652db7c --- /dev/null +++ b/cmd/dotagents/view.go @@ -0,0 +1,50 @@ +package main + +import ( + "errors" + "fmt" + "os" + "os/exec" +) + +// hkBinary is the HarnessKit CLI that `dotagents view` launches as a +// cross-harness inspection surface over the materialized native harness dirs. +// The `view` command itself never writes. dotagents does NOT enforce read-only: +// the launched HarnessKit UI can enable/disable/deploy, and those writes go +// straight to native dirs, bypassing dotagents. The launch banner warns against +// using them on managed surfaces; reconcile any drift with `dotagents sync`. +const hkBinary = "hk" + +// hkLookPath is indirected so tests can exercise the missing-binary path +// without depending on the host PATH. +var hkLookPath = exec.LookPath + +const hkInstallHint = `HarnessKit (hk) not found on PATH. + +dotagents view launches HarnessKit as a cross-harness inspection surface for +skills, MCP servers, hooks, and configs across every detected agent. + +Install it from https://github.com/RealZST/HarnessKit, then re-run: dotagents view` + +// hkServeArgs builds the argv for the underlying `hk serve` invocation. Extra +// args are forwarded verbatim to hk serve (e.g. --port, --host, --no-token). +func hkServeArgs(passthrough []string) []string { + return append([]string{"serve"}, passthrough...) +} + +// runView starts `hk serve` (forwarding its stdio, which prints the tokenized +// URL) so the user can inspect every harness in HarnessKit's web UI. The +// launcher itself writes nothing; the banner cautions that HarnessKit's own +// write actions bypass dotagents. +func runView(args []string) error { + path, err := hkLookPath(hkBinary) + if err != nil { + return errors.New(hkInstallHint) + } + fmt.Fprintln(os.Stdout, "Launching HarnessKit for inspection. Note: HarnessKit can also enable/disable/deploy, and those writes bypass dotagents — avoid them on dotagents-managed skills, MCP, and hooks (reconcile drift with: dotagents sync).") + cmd := exec.Command(path, hkServeArgs(args)...) // nosemgrep: go.lang.security.audit.dangerous-exec-command + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +} diff --git a/cmd/dotagents/view_test.go b/cmd/dotagents/view_test.go new file mode 100644 index 0000000..e719065 --- /dev/null +++ b/cmd/dotagents/view_test.go @@ -0,0 +1,33 @@ +package main + +import ( + "errors" + "reflect" + "strings" + "testing" +) + +func TestHKServeArgs(t *testing.T) { + if got := hkServeArgs(nil); !reflect.DeepEqual(got, []string{"serve"}) { + t.Fatalf("hkServeArgs(nil) = %v, want [serve]", got) + } + got := hkServeArgs([]string{"--port", "8080"}) + want := []string{"serve", "--port", "8080"} + if !reflect.DeepEqual(got, want) { + t.Fatalf("hkServeArgs passthrough = %v, want %v", got, want) + } +} + +func TestRunViewMissingBinary(t *testing.T) { + orig := hkLookPath + t.Cleanup(func() { hkLookPath = orig }) + hkLookPath = func(string) (string, error) { return "", errors.New("not found") } + + err := runView(nil) + if err == nil { + t.Fatal("expected error when hk binary is missing") + } + if !strings.Contains(err.Error(), "HarnessKit") || !strings.Contains(err.Error(), "github.com/RealZST/HarnessKit") { + t.Fatalf("error should guide install, got: %v", err) + } +} diff --git a/docs/harnesskit-integration.md b/docs/harnesskit-integration.md new file mode 100644 index 0000000..3cee303 --- /dev/null +++ b/docs/harnesskit-integration.md @@ -0,0 +1,84 @@ +# HarnessKit integration — design notes + +Status: draft / thinking. Branch: `feat/harnesskit-integration`. Date: 2026-09-06. + +## Finding + +[HarnessKit](https://github.com/RealZST/HarnessKit) (RealZST/HarnessKit, Rust, Apache-2.0, ~420★, active) is a web UI (also desktop/CLI) that inspects and manages agent extensions, configs, memory, and rules across 13 harnesses. Verified live against a full stack install on 2026-09-06: it detects and reads the **full dotagents stack** — Claude Code, Codex, **Oh My Pi** (`~/.omp/agent/`), **Hermes** (`~/.hermes/`), plus Gemini CLI, Copilot, OpenCode, Grok Build. This is exactly the coverage (Pi/OMP + Hermes) that CCO and ai-config-sync-manager lack. + +Consequence: dotagents does **not** need to build its own config viewer. HarnessKit already does the read/inspect/audit surface better than we would from scratch, and on every harness we care about. + +## Why integrate, and the one boundary rule + +HarnessKit and dotagents are complementary, not competing: + +- **HarnessKit** = read/inspect/audit dashboard + marketplace. Reads *materialized native dirs*. Its write model is **convergence** ("deploy this extension to every agent"). +- **dotagents** = sync engine + source of truth. Manages the 5 surfaces (skills, MCP, hooks, roles, plugins) via symlinks + `dotagents.lock` + intentional per-harness divergence. + +**Boundary invariant for this integration:** dotagents stays the only writer. HarnessKit is consumed read-mostly. We never route dotagents' managed surfaces *through* HarnessKit's convergence writer, and we never let HK's "deploy to all" become the mechanism that mutates a dotagents-owned symlink. Divergence is a feature here, not drift — HK's model treats it as drift, so its write path is off-limits for managed surfaces. + +## Integration levels + +### L0 — Recommend (docs only, zero coupling) + +Name HarnessKit in `README.md`, `docs/comparison.md`, and the `dotagents` skill as the inspection dashboard: "dotagents owns sync; use HarnessKit to see/audit the result across harnesses." No code. Ships today. + +### L1 — Optional dependency (`deps` / `setup`) + +Register HarnessKit as an **optional, opt-in** external tool: + +- `dotagents setup` offers (never forces) HK install after the first sync, gated behind a prompt. +- `dotagents deps check` reports whether HK is present + version; `deps update` bumps it. +- Honor the existing publish-age gate (`checkExternalPackageAge`, `package_age.go`) — HK is a fast-moving Rust binary; do not auto-pull a release younger than the configured window. + +Install method is an **open question** (see below) — do not hardcode one until verified. + +### L2 — Launch command (`dotagents view`) + +New subcommand that starts `hk serve` and forwards its stdio, which prints the tokenized URL for the user to open: + +- HarnessKit does its own harness discovery over the native homes (`~/.claude`, `~/.omp`, `~/.hermes`, …), so `view` does not load or pass the dotagents config root; a nonstandard `--config`/`$DOTAGENTS_HOME` only relocates dotagents' YAML, not the harness homes HK reads. +- Spawns the HK local server (127.0.0.1, token in URL — as observed at `:7070`) and prints the URL; the launcher does not open a browser itself. Mirrors the existing external-CLI launch path (`external_cli.go`, `cli_launch_test.go`). +- Inspection intent, not enforced: `hk serve` has no read-only mode, so HarnessKit's own enable/disable/deploy actions can still write native dirs and bypass dotagents. The launch banner warns against using them on managed surfaces; reconcile drift with `dotagents sync`. + +L0–L2 are the concrete near-term scope. All three keep the boundary invariant trivially (no managed-surface writes). + +### L3 — Write-through (research spike, do not build yet) + +The "can we drive changes from HK's UI via dotagents commands" question. HK writes to native dirs directly; dotagents owns those via symlinks/lock. Reconciling them needs one of: + +- **(a) HK-as-frontend:** HK calls `dotagents` as its backend for mutations. Requires HK to expose a pluggable write backend — **not known to exist**; would need an upstream change or fork. Verify before assuming. +- **(b) Watch-and-reconcile:** dotagents watches HK's writes and folds them back into the canonical store + re-syncs. Fragile (race with HK's own convergence writes), and it inverts the source-of-truth direction. + +Both risk exactly the drift the boundary invariant forbids. Treat L3 as a spike with a written go/no-go, not a committed feature. Likely outcome: keep writes in dotagents; if HK-authored edits are wanted, add a `dotagents import` path that pulls a specific HK change into the canonical store deliberately, rather than a live bridge. + +## Codebase seams (verified in-tree) + +- `cmd/dotagents/deps.go` — `deps check`/`update`, already wraps external-package-age. Home for L1. +- `cmd/dotagents/setup.go`, `setup_scaffold.go` — first-run; add the opt-in HK prompt here. +- `cmd/dotagents/external.go`, `external_cli.go`, `cli_launch_test.go` — external tool materialization + launch. Model for L2. +- `cmd/dotagents/detect.go`, `harness.go` — harness detection; useful if HK install is only offered when ≥1 supported harness is present. +- `cmd/dotagents/report.go`, `inspect.go` — the `status`/`inspect` output; where a "view in HarnessKit" pointer could surface. + +## Open questions + +Resolved 2026-09-06 by inspecting `hk` 1.10.0 (`hk --help`, `hk serve --help`, `hk list --help`): + +1. **HK headless/CLI mode — RESOLVED (yes).** `hk serve` is fully scriptable: `--port`, `--host`, `--token`/`--no-token`, `--name`. The 3-step onboarding is client-side UI state, not a server gate. HK also ships a pure CLI — `hk status`, `hk list --json`, `hk audit`, `hk info`, `hk enable/disable` — so a future text/status integration can consume `hk list --json` without the web server. +2. **Config-root targeting — RESOLVED (no flag, not needed).** `hk serve`/`hk list` have no `--config`/`--root`; only `HK_SCOPE_LAST_USED` (scope memory). HK reads the native harness homes (`~/.claude`, `~/.codex`, `~/.omp`, `~/.hermes`), which is exactly what dotagents materializes — so `view` targets the right thing for the default `~/.agents` root. Custom `$DOTAGENTS_HOME`/`--config` only relocates dotagents' YAML, not the harness homes HK reads, so no retargeting is required. +3. **HK binary/version — RESOLVED.** `hk` 1.10.0, Mach-O arm64, installed at `~/.local/bin/hk`. + +Still open (gate L1, not L2): + +4. **HK install method (L1)** — release binary vs `cargo install` vs `brew` tap. Verify from HK's releases/install docs before wiring an opt-in installer. +5. **Publish-age policy fit (L1)** — HK's release cadence vs the 3-day external-package rule (`package_age.go`); pick a window. +6. **Pluggable write backend (L3)** — does HK expose any hook/API to delegate mutations? Assume no until shown. + +## Recommended first slice + +**L0 + L2, now unblocked** (open questions #1–#3 resolved in HK's favor): + +1. L0 docs pointer — README + `dotagents` SKILL + CLI help. Pointer only, no duplicated harness-compat table. +2. `dotagents view` — thin launcher: `exec.LookPath("hk")`, forward args to `hk serve`, inspection framing (writes not enforced — banner cautions), install hint when absent. Implemented on this branch (`cmd/dotagents/view.go`, `view_test.go`). +3. L1 opt-in install — deferred until #4 and #5 are settled. +4. L3 — separate spike, no code; decision recorded here. diff --git a/skills/dotagents/SKILL.md b/skills/dotagents/SKILL.md index cefedc9..788bb8d 100644 --- a/skills/dotagents/SKILL.md +++ b/skills/dotagents/SKILL.md @@ -22,6 +22,7 @@ dotagents setup [--memory off|basic|memsearch] [--agents ...] [--yes] [--dry-run dotagents status [--agents ...] dotagents sync [--pull] [--agents ...] dotagents doctor [--e2e] [--agents ...] +dotagents view [--port N] [--host ADDR] dotagents skill new [--description ...] dotagents skill update [name ...] dotagents skill promote [--dry-run] @@ -119,6 +120,14 @@ Runs sync, status, and doctor as one health check. It fails on drift, conflicts, dotagents doctor --e2e ``` +## view + +Launches [HarnessKit](https://github.com/RealZST/HarnessKit) (`hk serve`) as an inspection web UI over every detected harness — skills, MCP, hooks, and configs in one place, with a security audit. The `view` command writes nothing, but HarnessKit's own enable/disable/deploy actions bypass dotagents; treat `view` as inspect/audit and reconcile any HarnessKit changes with `dotagents sync`. Requires `hk` on `PATH` (install HarnessKit separately); flags are forwarded to `hk serve`. + +```bash +dotagents view --port 7070 +``` + ## Capability matrix | Harness | Skills | Roles | MCP | Hooks |