harnery 0.0.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +84 -2
- package/bin/agent-coord +42 -0
- package/bin/agent-hook +44 -0
- package/bin/harn +40 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +18 -0
- package/dist/commander.d.ts +128 -0
- package/dist/commander.d.ts.map +1 -0
- package/dist/commander.js +126 -0
- package/dist/commands/agents.d.ts +18 -0
- package/dist/commands/agents.d.ts.map +1 -0
- package/dist/commands/agents.js +3946 -0
- package/dist/commands/backup.d.ts +22 -0
- package/dist/commands/backup.d.ts.map +1 -0
- package/dist/commands/backup.js +262 -0
- package/dist/commands/browse-ai.d.ts +4 -0
- package/dist/commands/browse-ai.d.ts.map +1 -0
- package/dist/commands/browse-ai.js +156 -0
- package/dist/commands/browse.d.ts +4 -0
- package/dist/commands/browse.d.ts.map +1 -0
- package/dist/commands/browse.js +590 -0
- package/dist/commands/callers.d.ts +4 -0
- package/dist/commands/callers.d.ts.map +1 -0
- package/dist/commands/callers.js +276 -0
- package/dist/commands/completion.d.ts +17 -0
- package/dist/commands/completion.d.ts.map +1 -0
- package/dist/commands/completion.js +158 -0
- package/dist/commands/config-get.d.ts +4 -0
- package/dist/commands/config-get.d.ts.map +1 -0
- package/dist/commands/config-get.js +131 -0
- package/dist/commands/context.d.ts +11 -0
- package/dist/commands/context.d.ts.map +1 -0
- package/dist/commands/context.js +185 -0
- package/dist/commands/cookies.d.ts +4 -0
- package/dist/commands/cookies.d.ts.map +1 -0
- package/dist/commands/cookies.js +140 -0
- package/dist/commands/docs.d.ts +4 -0
- package/dist/commands/docs.d.ts.map +1 -0
- package/dist/commands/docs.js +137 -0
- package/dist/commands/doctor.d.ts +25 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +200 -0
- package/dist/commands/edit-batch.d.ts +18 -0
- package/dist/commands/edit-batch.d.ts.map +1 -0
- package/dist/commands/edit-batch.js +172 -0
- package/dist/commands/eml.d.ts +4 -0
- package/dist/commands/eml.d.ts.map +1 -0
- package/dist/commands/eml.js +428 -0
- package/dist/commands/env.d.ts +4 -0
- package/dist/commands/env.d.ts.map +1 -0
- package/dist/commands/env.js +201 -0
- package/dist/commands/fetch.d.ts +4 -0
- package/dist/commands/fetch.d.ts.map +1 -0
- package/dist/commands/fetch.js +99 -0
- package/dist/commands/file-history.d.ts +4 -0
- package/dist/commands/file-history.d.ts.map +1 -0
- package/dist/commands/file-history.js +152 -0
- package/dist/commands/grep.d.ts +4 -0
- package/dist/commands/grep.d.ts.map +1 -0
- package/dist/commands/grep.js +317 -0
- package/dist/commands/init.d.ts +82 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +288 -0
- package/dist/commands/outline.d.ts +4 -0
- package/dist/commands/outline.d.ts.map +1 -0
- package/dist/commands/outline.js +509 -0
- package/dist/commands/presence.d.ts +12 -0
- package/dist/commands/presence.d.ts.map +1 -0
- package/dist/commands/presence.js +123 -0
- package/dist/commands/read.d.ts +7 -0
- package/dist/commands/read.d.ts.map +1 -0
- package/dist/commands/read.js +46 -0
- package/dist/commands/scratch.d.ts +4 -0
- package/dist/commands/scratch.d.ts.map +1 -0
- package/dist/commands/scratch.js +426 -0
- package/dist/commands/session.d.ts +4 -0
- package/dist/commands/session.d.ts.map +1 -0
- package/dist/commands/session.js +162 -0
- package/dist/commands/sync.d.ts +24 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +275 -0
- package/dist/commands/toc.d.ts +5 -0
- package/dist/commands/toc.d.ts.map +1 -0
- package/dist/commands/toc.js +153 -0
- package/dist/commands/tokens.d.ts +4 -0
- package/dist/commands/tokens.d.ts.map +1 -0
- package/dist/commands/tokens.js +48 -0
- package/dist/commands/tunnel.d.ts +4 -0
- package/dist/commands/tunnel.d.ts.map +1 -0
- package/dist/commands/tunnel.js +513 -0
- package/dist/commands/uninstall.d.ts +22 -0
- package/dist/commands/uninstall.d.ts.map +1 -0
- package/dist/commands/uninstall.js +126 -0
- package/dist/commands/web.d.ts +4 -0
- package/dist/commands/web.d.ts.map +1 -0
- package/dist/commands/web.js +165 -0
- package/dist/core/agents/canonical-emit.d.ts +27 -0
- package/dist/core/agents/canonical-emit.d.ts.map +1 -0
- package/dist/core/agents/canonical-emit.js +72 -0
- package/dist/core/agents/cli-emit.d.ts +27 -0
- package/dist/core/agents/cli-emit.d.ts.map +1 -0
- package/dist/core/agents/cli-emit.js +57 -0
- package/dist/core/agents/cli.d.ts +10 -0
- package/dist/core/agents/cli.d.ts.map +1 -0
- package/dist/core/agents/cli.js +757 -0
- package/dist/core/agents/codex-replay.d.ts +29 -0
- package/dist/core/agents/codex-replay.d.ts.map +1 -0
- package/dist/core/agents/codex-replay.js +138 -0
- package/dist/core/agents/coord-client.d.ts +98 -0
- package/dist/core/agents/coord-client.d.ts.map +1 -0
- package/dist/core/agents/coord-client.js +212 -0
- package/dist/core/agents/events/consume.d.ts +59 -0
- package/dist/core/agents/events/consume.d.ts.map +1 -0
- package/dist/core/agents/events/consume.js +147 -0
- package/dist/core/agents/events/emit.d.ts +42 -0
- package/dist/core/agents/events/emit.d.ts.map +1 -0
- package/dist/core/agents/events/emit.js +70 -0
- package/dist/core/agents/events/ulid.d.ts +11 -0
- package/dist/core/agents/events/ulid.d.ts.map +1 -0
- package/dist/core/agents/events/ulid.js +47 -0
- package/dist/core/agents/index.d.ts +14 -0
- package/dist/core/agents/index.d.ts.map +1 -0
- package/dist/core/agents/index.js +13 -0
- package/dist/core/agents/paths.d.ts +6 -0
- package/dist/core/agents/paths.d.ts.map +1 -0
- package/dist/core/agents/paths.js +17 -0
- package/dist/core/agents/render/prompt-context.d.ts +43 -0
- package/dist/core/agents/render/prompt-context.d.ts.map +1 -0
- package/dist/core/agents/render/prompt-context.js +335 -0
- package/dist/core/agents/render/session-context.d.ts +39 -0
- package/dist/core/agents/render/session-context.d.ts.map +1 -0
- package/dist/core/agents/render/session-context.js +283 -0
- package/dist/core/agents/rules/claim-conflict.d.ts +35 -0
- package/dist/core/agents/rules/claim-conflict.d.ts.map +1 -0
- package/dist/core/agents/rules/claim-conflict.js +244 -0
- package/dist/core/agents/rules/commit-conflict.d.ts +59 -0
- package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -0
- package/dist/core/agents/rules/commit-conflict.js +244 -0
- package/dist/core/agents/rules/stop-hook.d.ts +44 -0
- package/dist/core/agents/rules/stop-hook.d.ts.map +1 -0
- package/dist/core/agents/rules/stop-hook.js +161 -0
- package/dist/core/agents/session-events.d.ts +41 -0
- package/dist/core/agents/session-events.d.ts.map +1 -0
- package/dist/core/agents/session-events.js +205 -0
- package/dist/core/agents/state/activity-log.d.ts +18 -0
- package/dist/core/agents/state/activity-log.d.ts.map +1 -0
- package/dist/core/agents/state/activity-log.js +34 -0
- package/dist/core/agents/state/council.d.ts +39 -0
- package/dist/core/agents/state/council.d.ts.map +1 -0
- package/dist/core/agents/state/council.js +216 -0
- package/dist/core/agents/state/heartbeat-projector.d.ts +59 -0
- package/dist/core/agents/state/heartbeat-projector.d.ts.map +1 -0
- package/dist/core/agents/state/heartbeat-projector.js +436 -0
- package/dist/core/agents/state/heartbeat-writer.d.ts +64 -0
- package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -0
- package/dist/core/agents/state/heartbeat-writer.js +271 -0
- package/dist/core/agents/state/names.d.ts +35 -0
- package/dist/core/agents/state/names.d.ts.map +1 -0
- package/dist/core/agents/state/names.js +376 -0
- package/dist/core/agents/state/pidmap.d.ts +11 -0
- package/dist/core/agents/state/pidmap.d.ts.map +1 -0
- package/dist/core/agents/state/pidmap.js +32 -0
- package/dist/core/agents/state/scratch.d.ts +27 -0
- package/dist/core/agents/state/scratch.d.ts.map +1 -0
- package/dist/core/agents/state/scratch.js +90 -0
- package/dist/core/agents/state/shell-mutation.d.ts +17 -0
- package/dist/core/agents/state/shell-mutation.d.ts.map +1 -0
- package/dist/core/agents/state/shell-mutation.js +41 -0
- package/dist/core/agents/state/stale-sweep.d.ts +16 -0
- package/dist/core/agents/state/stale-sweep.d.ts.map +1 -0
- package/dist/core/agents/state/stale-sweep.js +166 -0
- package/dist/core/config.d.ts +29 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +108 -0
- package/dist/core/hooks/cli.d.ts +21 -0
- package/dist/core/hooks/cli.d.ts.map +1 -0
- package/dist/core/hooks/cli.js +1123 -0
- package/dist/core/hooks/effects/image-capture.d.ts +43 -0
- package/dist/core/hooks/effects/image-capture.d.ts.map +1 -0
- package/dist/core/hooks/effects/image-capture.js +288 -0
- package/dist/core/hooks/effects/index.d.ts +64 -0
- package/dist/core/hooks/effects/index.d.ts.map +1 -0
- package/dist/core/hooks/effects/index.js +197 -0
- package/dist/core/hooks/events/emit.d.ts +31 -0
- package/dist/core/hooks/events/emit.d.ts.map +1 -0
- package/dist/core/hooks/events/emit.js +89 -0
- package/dist/core/hooks/events/schema.d.ts +235 -0
- package/dist/core/hooks/events/schema.d.ts.map +1 -0
- package/dist/core/hooks/events/schema.js +12 -0
- package/dist/core/hooks/events/ulid.d.ts +10 -0
- package/dist/core/hooks/events/ulid.d.ts.map +1 -0
- package/dist/core/hooks/events/ulid.js +47 -0
- package/dist/core/hooks/harness/detect.d.ts +9 -0
- package/dist/core/hooks/harness/detect.d.ts.map +1 -0
- package/dist/core/hooks/harness/detect.js +29 -0
- package/dist/core/hooks/harness/events.d.ts +45 -0
- package/dist/core/hooks/harness/events.d.ts.map +1 -0
- package/dist/core/hooks/harness/events.js +71 -0
- package/dist/core/hooks/harness/output.d.ts +46 -0
- package/dist/core/hooks/harness/output.d.ts.map +1 -0
- package/dist/core/hooks/harness/output.js +87 -0
- package/dist/core/hooks/harness/parse.d.ts +67 -0
- package/dist/core/hooks/harness/parse.d.ts.map +1 -0
- package/dist/core/hooks/harness/parse.js +132 -0
- package/dist/core/hooks/index.d.ts +8 -0
- package/dist/core/hooks/index.d.ts.map +1 -0
- package/dist/core/hooks/index.js +7 -0
- package/dist/core/hooks/resolve/anchor.d.ts +37 -0
- package/dist/core/hooks/resolve/anchor.d.ts.map +1 -0
- package/dist/core/hooks/resolve/anchor.js +48 -0
- package/dist/core/hooks/resolve/coord-root.d.ts +6 -0
- package/dist/core/hooks/resolve/coord-root.d.ts.map +1 -0
- package/dist/core/hooks/resolve/coord-root.js +27 -0
- package/dist/core/hooks/resolve/intent.d.ts +33 -0
- package/dist/core/hooks/resolve/intent.d.ts.map +1 -0
- package/dist/core/hooks/resolve/intent.js +79 -0
- package/dist/core/hooks/resolve/owner.d.ts +42 -0
- package/dist/core/hooks/resolve/owner.d.ts.map +1 -0
- package/dist/core/hooks/resolve/owner.js +140 -0
- package/dist/core/hooks/resolve/transcript.d.ts +26 -0
- package/dist/core/hooks/resolve/transcript.d.ts.map +1 -0
- package/dist/core/hooks/resolve/transcript.js +73 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/lib/agent-browser/client.d.ts +99 -0
- package/dist/lib/agent-browser/client.d.ts.map +1 -0
- package/dist/lib/agent-browser/client.js +177 -0
- package/dist/lib/agent-browser/index.d.ts +2 -0
- package/dist/lib/agent-browser/index.d.ts.map +1 -0
- package/dist/lib/agent-browser/index.js +1 -0
- package/dist/lib/browser/client.d.ts +193 -0
- package/dist/lib/browser/client.d.ts.map +1 -0
- package/dist/lib/browser/client.js +325 -0
- package/dist/lib/browser/dev-overlay.d.ts +23 -0
- package/dist/lib/browser/dev-overlay.d.ts.map +1 -0
- package/dist/lib/browser/dev-overlay.js +153 -0
- package/dist/lib/browser/index.d.ts +5 -0
- package/dist/lib/browser/index.d.ts.map +1 -0
- package/dist/lib/browser/index.js +2 -0
- package/dist/lib/browser/layout.d.ts +79 -0
- package/dist/lib/browser/layout.d.ts.map +1 -0
- package/dist/lib/browser/layout.js +220 -0
- package/dist/lib/browser/visibility.d.ts +86 -0
- package/dist/lib/browser/visibility.d.ts.map +1 -0
- package/dist/lib/browser/visibility.js +333 -0
- package/dist/lib/browser/visual-diff.d.ts +38 -0
- package/dist/lib/browser/visual-diff.d.ts.map +1 -0
- package/dist/lib/browser/visual-diff.js +107 -0
- package/dist/lib/completion/bash.d.ts +25 -0
- package/dist/lib/completion/bash.d.ts.map +1 -0
- package/dist/lib/completion/bash.js +284 -0
- package/dist/lib/completion/fish.d.ts +16 -0
- package/dist/lib/completion/fish.d.ts.map +1 -0
- package/dist/lib/completion/fish.js +118 -0
- package/dist/lib/completion/index.d.ts +5 -0
- package/dist/lib/completion/index.d.ts.map +1 -0
- package/dist/lib/completion/index.js +4 -0
- package/dist/lib/completion/walk.d.ts +68 -0
- package/dist/lib/completion/walk.d.ts.map +1 -0
- package/dist/lib/completion/walk.js +102 -0
- package/dist/lib/completion/zsh.d.ts +13 -0
- package/dist/lib/completion/zsh.d.ts.map +1 -0
- package/dist/lib/completion/zsh.js +249 -0
- package/dist/lib/context/index.d.ts +107 -0
- package/dist/lib/context/index.d.ts.map +1 -0
- package/dist/lib/context/index.js +275 -0
- package/dist/lib/cookies/client.d.ts +131 -0
- package/dist/lib/cookies/client.d.ts.map +1 -0
- package/dist/lib/cookies/client.js +239 -0
- package/dist/lib/cookies/index.d.ts +2 -0
- package/dist/lib/cookies/index.d.ts.map +1 -0
- package/dist/lib/cookies/index.js +1 -0
- package/dist/lib/council/index.d.ts +266 -0
- package/dist/lib/council/index.d.ts.map +1 -0
- package/dist/lib/council/index.js +674 -0
- package/dist/lib/docs-index.d.ts +28 -0
- package/dist/lib/docs-index.d.ts.map +1 -0
- package/dist/lib/docs-index.js +169 -0
- package/dist/lib/docs-lint.d.ts +26 -0
- package/dist/lib/docs-lint.d.ts.map +1 -0
- package/dist/lib/docs-lint.js +378 -0
- package/dist/lib/docs-sweep.d.ts +34 -0
- package/dist/lib/docs-sweep.d.ts.map +1 -0
- package/dist/lib/docs-sweep.js +304 -0
- package/dist/lib/docs.d.ts +27 -0
- package/dist/lib/docs.d.ts.map +1 -0
- package/dist/lib/docs.js +142 -0
- package/dist/lib/env.d.ts +11 -0
- package/dist/lib/env.d.ts.map +1 -0
- package/dist/lib/env.js +12 -0
- package/dist/lib/exec.d.ts +32 -0
- package/dist/lib/exec.d.ts.map +1 -0
- package/dist/lib/exec.js +54 -0
- package/dist/lib/format.d.ts +29 -0
- package/dist/lib/format.d.ts.map +1 -0
- package/dist/lib/format.js +139 -0
- package/dist/lib/http/client.d.ts +56 -0
- package/dist/lib/http/client.d.ts.map +1 -0
- package/dist/lib/http/client.js +160 -0
- package/dist/lib/http/index.d.ts +2 -0
- package/dist/lib/http/index.d.ts.map +1 -0
- package/dist/lib/http/index.js +1 -0
- package/dist/lib/identities/index.d.ts +77 -0
- package/dist/lib/identities/index.d.ts.map +1 -0
- package/dist/lib/identities/index.js +190 -0
- package/dist/lib/machine.d.ts +19 -0
- package/dist/lib/machine.d.ts.map +1 -0
- package/dist/lib/machine.js +61 -0
- package/dist/lib/presence.d.ts +48 -0
- package/dist/lib/presence.d.ts.map +1 -0
- package/dist/lib/presence.js +123 -0
- package/dist/lib/readability/client.d.ts +39 -0
- package/dist/lib/readability/client.d.ts.map +1 -0
- package/dist/lib/readability/client.js +121 -0
- package/dist/lib/readability/index.d.ts +2 -0
- package/dist/lib/readability/index.d.ts.map +1 -0
- package/dist/lib/readability/index.js +1 -0
- package/dist/lib/scratch/index.d.ts +74 -0
- package/dist/lib/scratch/index.d.ts.map +1 -0
- package/dist/lib/scratch/index.js +393 -0
- package/dist/lib/tunnel/gate.d.ts +12 -0
- package/dist/lib/tunnel/gate.d.ts.map +1 -0
- package/dist/lib/tunnel/gate.js +101 -0
- package/dist/lib/tunnel/state.d.ts +34 -0
- package/dist/lib/tunnel/state.d.ts.map +1 -0
- package/dist/lib/tunnel/state.js +132 -0
- package/package.json +160 -8
- package/schemas/.gitkeep +0 -0
- package/schemas/config.schema.json +109 -0
- package/src/cli.ts +22 -0
- package/src/commander.ts +242 -0
- package/src/commands/.gitkeep +0 -0
- package/src/commands/agents.ts +4567 -0
- package/src/commands/backup.ts +305 -0
- package/src/commands/browse-ai.ts +198 -0
- package/src/commands/browse.ts +849 -0
- package/src/commands/callers.ts +363 -0
- package/src/commands/completion.ts +193 -0
- package/src/commands/config-get.ts +161 -0
- package/src/commands/context.ts +209 -0
- package/src/commands/cookies.ts +198 -0
- package/src/commands/docs.ts +174 -0
- package/src/commands/doctor.ts +231 -0
- package/src/commands/edit-batch.ts +233 -0
- package/src/commands/eml.ts +519 -0
- package/src/commands/env.ts +254 -0
- package/src/commands/fetch.ts +136 -0
- package/src/commands/file-history.ts +202 -0
- package/src/commands/grep.ts +371 -0
- package/src/commands/init.ts +335 -0
- package/src/commands/outline.ts +583 -0
- package/src/commands/presence.ts +152 -0
- package/src/commands/read.ts +64 -0
- package/src/commands/scratch.ts +445 -0
- package/src/commands/session.ts +187 -0
- package/src/commands/sync.ts +306 -0
- package/src/commands/toc.ts +218 -0
- package/src/commands/tokens.ts +79 -0
- package/src/commands/tunnel.ts +633 -0
- package/src/commands/uninstall.ts +144 -0
- package/src/commands/web.ts +193 -0
- package/src/core/agents/canonical-emit.ts +77 -0
- package/src/core/agents/cli-emit.ts +64 -0
- package/src/core/agents/cli.ts +838 -0
- package/src/core/agents/codex-replay.ts +163 -0
- package/src/core/agents/coord-client.ts +249 -0
- package/src/core/agents/events/consume.ts +196 -0
- package/src/core/agents/events/emit.ts +108 -0
- package/src/core/agents/events/ulid.ts +51 -0
- package/src/core/agents/index.ts +14 -0
- package/src/core/agents/paths.ts +16 -0
- package/src/core/agents/render/prompt-context.ts +401 -0
- package/src/core/agents/render/session-context.ts +341 -0
- package/src/core/agents/rules/claim-conflict.ts +282 -0
- package/src/core/agents/rules/commit-conflict.ts +303 -0
- package/src/core/agents/rules/stop-hook.ts +229 -0
- package/src/core/agents/session-events.ts +228 -0
- package/src/core/agents/state/activity-log.ts +33 -0
- package/src/core/agents/state/council.ts +265 -0
- package/src/core/agents/state/heartbeat-projector.ts +488 -0
- package/src/core/agents/state/heartbeat-writer.ts +333 -0
- package/src/core/agents/state/names.ts +399 -0
- package/src/core/agents/state/pidmap.ts +38 -0
- package/src/core/agents/state/scratch.ts +121 -0
- package/src/core/agents/state/shell-mutation.ts +44 -0
- package/src/core/agents/state/stale-sweep.ts +190 -0
- package/src/core/config.ts +111 -0
- package/src/core/hooks/cli.ts +1247 -0
- package/src/core/hooks/effects/image-capture.ts +330 -0
- package/src/core/hooks/effects/index.ts +210 -0
- package/src/core/hooks/events/emit.ts +120 -0
- package/src/core/hooks/events/schema.ts +430 -0
- package/src/core/hooks/events/ulid.ts +51 -0
- package/src/core/hooks/harness/detect.ts +30 -0
- package/src/core/hooks/harness/events.ts +102 -0
- package/src/core/hooks/harness/output.ts +100 -0
- package/src/core/hooks/harness/parse.ts +180 -0
- package/src/core/hooks/index.ts +16 -0
- package/src/core/hooks/resolve/anchor.ts +51 -0
- package/src/core/hooks/resolve/coord-root.ts +25 -0
- package/src/core/hooks/resolve/intent.ts +89 -0
- package/src/core/hooks/resolve/owner.ts +140 -0
- package/src/core/hooks/resolve/transcript.ts +72 -0
- package/src/hooks/.gitkeep +0 -0
- package/src/index.ts +15 -0
- package/src/lib/agent-browser/client.ts +239 -0
- package/src/lib/agent-browser/index.ts +1 -0
- package/src/lib/browser/client.ts +449 -0
- package/src/lib/browser/dev-overlay.ts +207 -0
- package/src/lib/browser/index.ts +24 -0
- package/src/lib/browser/layout.ts +288 -0
- package/src/lib/browser/visibility.ts +419 -0
- package/src/lib/browser/visual-diff.ts +150 -0
- package/src/lib/completion/bash.ts +291 -0
- package/src/lib/completion/fish.ts +134 -0
- package/src/lib/completion/index.ts +10 -0
- package/src/lib/completion/walk.ts +184 -0
- package/src/lib/completion/zsh.ts +262 -0
- package/src/lib/context/index.ts +386 -0
- package/src/lib/cookies/client.ts +301 -0
- package/src/lib/cookies/index.ts +13 -0
- package/src/lib/council/index.ts +803 -0
- package/src/lib/docs-index.ts +216 -0
- package/src/lib/docs-lint.ts +413 -0
- package/src/lib/docs-sweep.ts +348 -0
- package/src/lib/docs.ts +199 -0
- package/src/lib/env.ts +12 -0
- package/src/lib/exec.ts +74 -0
- package/src/lib/format.ts +147 -0
- package/src/lib/http/client.ts +211 -0
- package/src/lib/http/index.ts +1 -0
- package/src/lib/identities/index.ts +210 -0
- package/src/lib/machine.ts +61 -0
- package/src/lib/presence.ts +154 -0
- package/src/lib/readability/client.ts +169 -0
- package/src/lib/readability/index.ts +5 -0
- package/src/lib/readability/turndown-plugin-gfm.d.ts +10 -0
- package/src/lib/scratch/index.ts +470 -0
- package/src/lib/tunnel/gate.ts +113 -0
- package/src/lib/tunnel/state.ts +167 -0
- package/src/web/.gitkeep +0 -0
- package/index.js +0 -1
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import type { EmitContext } from "../commander.ts";
|
|
3
|
+
import {
|
|
4
|
+
emitCanonical,
|
|
5
|
+
normalizeHarness,
|
|
6
|
+
readHeartbeat,
|
|
7
|
+
resolveOwner,
|
|
8
|
+
} from "../core/agents/index.ts";
|
|
9
|
+
import {
|
|
10
|
+
ageSeconds,
|
|
11
|
+
applyDetection,
|
|
12
|
+
clearPresence,
|
|
13
|
+
formatAge,
|
|
14
|
+
type PresenceState,
|
|
15
|
+
presenceFilePath,
|
|
16
|
+
readPresence,
|
|
17
|
+
writePresence,
|
|
18
|
+
} from "../lib/presence.ts";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* `presence`: mobile/office state for agents (~/.claude/presence).
|
|
22
|
+
*
|
|
23
|
+
* Uses the injected EmitContext so composed and standalone consumers share
|
|
24
|
+
* one code path. The state file path is Claude-Code-specific (lives under
|
|
25
|
+
* ~/.claude/) but the detect/set/clear logic is generic: any agent harness
|
|
26
|
+
* can adopt the same convention.
|
|
27
|
+
*/
|
|
28
|
+
export function registerPresenceCommand(program: Command, emit: EmitContext): void {
|
|
29
|
+
const cmd = program
|
|
30
|
+
.command("presence")
|
|
31
|
+
.description("Mobile/office state for agents (~/.claude/presence)")
|
|
32
|
+
.action(() => {
|
|
33
|
+
printStatus(emit);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
cmd
|
|
37
|
+
.command("get")
|
|
38
|
+
.description('Print just the state ("mobile" / "office"), or full record with --json')
|
|
39
|
+
.option("--json", "Print the full record as JSON")
|
|
40
|
+
.action((opts: { json?: boolean }) => {
|
|
41
|
+
const r = readPresence();
|
|
42
|
+
if (opts.json) {
|
|
43
|
+
emit.config({ format: "json" });
|
|
44
|
+
emit.data({
|
|
45
|
+
state: r.state,
|
|
46
|
+
updated_at: r.updated_at,
|
|
47
|
+
source: r.source,
|
|
48
|
+
is_default: r.is_default,
|
|
49
|
+
path: presenceFilePath(),
|
|
50
|
+
});
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
emit.text(`${r.state}\n`);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
cmd
|
|
57
|
+
.command("set")
|
|
58
|
+
.argument("<state>", "mobile | office")
|
|
59
|
+
.description("Set state explicitly (source=cli). Hook auto-detection can still overwrite.")
|
|
60
|
+
.action((state: string) => {
|
|
61
|
+
if (state !== "mobile" && state !== "office") {
|
|
62
|
+
emit.error({
|
|
63
|
+
code: "invalid_state",
|
|
64
|
+
message: `state must be "mobile" or "office" (got: ${JSON.stringify(state)})`,
|
|
65
|
+
});
|
|
66
|
+
process.exit(2);
|
|
67
|
+
}
|
|
68
|
+
const before = readPresence();
|
|
69
|
+
writePresence(state as PresenceState, "cli");
|
|
70
|
+
emitPresenceChange(before.state, state as PresenceState, "cli");
|
|
71
|
+
printStatus(emit);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
cmd
|
|
75
|
+
.command("clear")
|
|
76
|
+
.description('Delete the state file (next read returns the default, "office")')
|
|
77
|
+
.action(() => {
|
|
78
|
+
const removed = clearPresence();
|
|
79
|
+
emit.data({
|
|
80
|
+
ok: true,
|
|
81
|
+
removed,
|
|
82
|
+
path: presenceFilePath(),
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
cmd
|
|
87
|
+
.command("detect")
|
|
88
|
+
.description(
|
|
89
|
+
"Internal: read prompt text from stdin, apply detection rules, update state if signal is clear",
|
|
90
|
+
)
|
|
91
|
+
.option("--from-stdin", "Read prompt from stdin (the only supported input mode today)")
|
|
92
|
+
.option("--verbose", "Print the detection result to stderr")
|
|
93
|
+
.action(async (opts: { fromStdin?: boolean; verbose?: boolean }) => {
|
|
94
|
+
const prompt = await readStdin();
|
|
95
|
+
if (!prompt) {
|
|
96
|
+
if (opts.verbose) emit.log("(no input on stdin; skipping)", "info");
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const result = applyDetection(prompt);
|
|
100
|
+
if (opts.verbose) {
|
|
101
|
+
emit.log(
|
|
102
|
+
JSON.stringify({
|
|
103
|
+
detected: result.detected,
|
|
104
|
+
before: result.before,
|
|
105
|
+
after: result.after,
|
|
106
|
+
changed: result.changed,
|
|
107
|
+
}),
|
|
108
|
+
"info",
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function printStatus(emit: EmitContext): void {
|
|
115
|
+
const r = readPresence();
|
|
116
|
+
emit.data({
|
|
117
|
+
state: r.state,
|
|
118
|
+
updated_at: r.updated_at,
|
|
119
|
+
source: r.source,
|
|
120
|
+
is_default: r.is_default,
|
|
121
|
+
age_seconds: r.is_default ? null : ageSeconds(r.updated_at),
|
|
122
|
+
age_human: r.is_default ? "(default: file missing)" : formatAge(ageSeconds(r.updated_at)),
|
|
123
|
+
path: presenceFilePath(),
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function readStdin(): Promise<string> {
|
|
128
|
+
if (process.stdin.isTTY) return "";
|
|
129
|
+
const chunks: Buffer[] = [];
|
|
130
|
+
for await (const chunk of process.stdin) {
|
|
131
|
+
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
132
|
+
}
|
|
133
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function emitPresenceChange(
|
|
137
|
+
from: PresenceState,
|
|
138
|
+
to: PresenceState,
|
|
139
|
+
source: "cli" | "hook" | "user",
|
|
140
|
+
): void {
|
|
141
|
+
if (from === to) return;
|
|
142
|
+
const owner = resolveOwner();
|
|
143
|
+
if (!owner) return;
|
|
144
|
+
const hb = readHeartbeat(owner);
|
|
145
|
+
emitCanonical({
|
|
146
|
+
type: "state.presence_change",
|
|
147
|
+
owner,
|
|
148
|
+
session: hb?.session_id ?? owner,
|
|
149
|
+
harness: normalizeHarness(hb?.platform),
|
|
150
|
+
data: { from, to, source },
|
|
151
|
+
});
|
|
152
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import type { Command } from "commander";
|
|
3
|
+
import type { EmitContext } from "../commander.ts";
|
|
4
|
+
import { resolveBinName } from "../core/config.ts";
|
|
5
|
+
import { htmlToMarkdown } from "../lib/readability/index.ts";
|
|
6
|
+
|
|
7
|
+
// Module-scoped emit assigned by registerReadCommand. Same pattern as cookies.ts:
|
|
8
|
+
// the runRead helper closes over this so the .action callback stays concise.
|
|
9
|
+
let emit: EmitContext;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* `harn read`: extract clean readable markdown from HTML.
|
|
13
|
+
*/
|
|
14
|
+
export function registerReadCommand(program: Command, emitParam: EmitContext): void {
|
|
15
|
+
emit = emitParam;
|
|
16
|
+
program
|
|
17
|
+
.command("read [html-file]")
|
|
18
|
+
.description(
|
|
19
|
+
`Extract clean readable markdown from HTML. Reads from file or stdin (use '-'). Pair with \`${resolveBinName()} fetch\` or \`${resolveBinName()} browse\` for scrape-to-markdown.`,
|
|
20
|
+
)
|
|
21
|
+
.option("-o, --output <file>", "Write markdown to file instead of stdout")
|
|
22
|
+
.option("--url <url>", "Base URL: used to resolve relative links")
|
|
23
|
+
.option(
|
|
24
|
+
"--selector <css>",
|
|
25
|
+
"Use this CSS selector instead of Readability (fallback when extraction misses content)",
|
|
26
|
+
)
|
|
27
|
+
.option("--raw", "Output cleaned HTML instead of markdown (debugging)")
|
|
28
|
+
.option("--max-chars <n>", "Truncate output to N characters (0 = disable)", "100000")
|
|
29
|
+
.action(async (htmlFile: string | undefined, opts: ReadOpts) => {
|
|
30
|
+
try {
|
|
31
|
+
await runRead(htmlFile, opts);
|
|
32
|
+
} catch (err: unknown) {
|
|
33
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
34
|
+
emit.error({ code: "read_error", message: msg });
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface ReadOpts {
|
|
41
|
+
output?: string;
|
|
42
|
+
url?: string;
|
|
43
|
+
selector?: string;
|
|
44
|
+
raw?: boolean;
|
|
45
|
+
maxChars: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function runRead(htmlFile: string | undefined, opts: ReadOpts): Promise<void> {
|
|
49
|
+
const input =
|
|
50
|
+
htmlFile && htmlFile !== "-" ? readFileSync(htmlFile, "utf-8") : readFileSync(0, "utf-8");
|
|
51
|
+
const result = await htmlToMarkdown(input, {
|
|
52
|
+
url: opts.url,
|
|
53
|
+
selector: opts.selector,
|
|
54
|
+
raw: opts.raw,
|
|
55
|
+
maxChars: Number.parseInt(opts.maxChars, 10),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
if (opts.output) {
|
|
59
|
+
writeFileSync(opts.output, result.output);
|
|
60
|
+
emit.file(opts.output, { chars: result.output.length });
|
|
61
|
+
} else {
|
|
62
|
+
emit.text(result.output);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, statSync, unlinkSync, watch } from "node:fs";
|
|
2
|
+
import { resolve as resolvePath } from "node:path";
|
|
3
|
+
import type { Command } from "commander";
|
|
4
|
+
import type { EmitContext } from "../commander.ts";
|
|
5
|
+
import { emitCanonical, normalizeHarness, readHeartbeat } from "../core/agents/index.ts";
|
|
6
|
+
import { resolveBinName } from "../core/config.ts";
|
|
7
|
+
import {
|
|
8
|
+
appendEntry,
|
|
9
|
+
archiveScratch,
|
|
10
|
+
currentOwnerOrThrow,
|
|
11
|
+
lintScratch,
|
|
12
|
+
listArchives,
|
|
13
|
+
loadScratch,
|
|
14
|
+
parseScratch,
|
|
15
|
+
pruneArchives,
|
|
16
|
+
resolveOwnerByName,
|
|
17
|
+
SCRATCH_CATEGORIES,
|
|
18
|
+
type ScratchCategory,
|
|
19
|
+
type ScratchDoc,
|
|
20
|
+
scratchDir,
|
|
21
|
+
scratchPath,
|
|
22
|
+
sweepOrphanScratchpads,
|
|
23
|
+
} from "../lib/scratch/index.ts";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* `harn scratch`: per-agent markdown journal at `.harnery/scratch/<instance_id>.md`.
|
|
27
|
+
* Used both for self-notes (surviving compaction) and peer coordination (other
|
|
28
|
+
* agents pull-read it on demand). SessionEnd hook archives; SessionStart
|
|
29
|
+
* janitor surfaces the most-recent archive as a recovery cue.
|
|
30
|
+
*/
|
|
31
|
+
let emit: EmitContext;
|
|
32
|
+
|
|
33
|
+
export function registerScratchCommand(program: Command, emitParam: EmitContext): void {
|
|
34
|
+
emit = emitParam;
|
|
35
|
+
const root = program
|
|
36
|
+
.command("scratch")
|
|
37
|
+
.description(
|
|
38
|
+
"Per-agent markdown journal: append-only timestamped entries. " +
|
|
39
|
+
"Survives in-session compaction, archived at session end, pruned after 7 days.",
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
// ── add ───────────────────────────────────────────────────────────────
|
|
43
|
+
root
|
|
44
|
+
.command("add <category> <text...>")
|
|
45
|
+
.description(`Append an entry to my scratchpad. Category: ${SCRATCH_CATEGORIES.join(" | ")}`)
|
|
46
|
+
.action((category: string, text: string[]) => {
|
|
47
|
+
const cat = validateCategory(category);
|
|
48
|
+
const body = text.join(" ");
|
|
49
|
+
if (!body.trim()) {
|
|
50
|
+
emit.error({ code: "empty_body", message: "scratch add: body is empty" });
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const owner = currentOwnerOrThrow();
|
|
55
|
+
const doc = appendEntry(owner, cat, body);
|
|
56
|
+
const hb = readHeartbeat(owner);
|
|
57
|
+
emitCanonical({
|
|
58
|
+
type: "state.scratch_append",
|
|
59
|
+
owner,
|
|
60
|
+
session: hb?.session_id ?? owner,
|
|
61
|
+
harness: normalizeHarness(hb?.platform),
|
|
62
|
+
data: {
|
|
63
|
+
category: cat,
|
|
64
|
+
body_summary: body.length > 1000 ? `${body.slice(0, 997)}...` : body,
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
emit.data({
|
|
68
|
+
instance_id: owner,
|
|
69
|
+
name: doc.header.name,
|
|
70
|
+
category: cat,
|
|
71
|
+
entries: doc.entries.length,
|
|
72
|
+
bytes: doc.bytes,
|
|
73
|
+
path: doc.path,
|
|
74
|
+
});
|
|
75
|
+
} catch (err) {
|
|
76
|
+
emit.error({ code: "add_failed", message: (err as Error).message });
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// ── read ──────────────────────────────────────────────────────────────
|
|
82
|
+
root
|
|
83
|
+
.command("read")
|
|
84
|
+
.description("Render a scratchpad to stdout. No --name: my own. With --name: a peer's.")
|
|
85
|
+
.option("--name <name>", "Read the named peer's scratchpad (case-insensitive)")
|
|
86
|
+
.option("--owner <id>", "Read by instance_id directly")
|
|
87
|
+
.option("--archive <basename>", "Read an archive file (e.g. <owner>-<ts>.md)")
|
|
88
|
+
.option("--limit <n>", "Cap entries rendered (newest first)", "50")
|
|
89
|
+
.action((opts: { name?: string; owner?: string; archive?: string; limit: string }) => {
|
|
90
|
+
try {
|
|
91
|
+
runRead(opts);
|
|
92
|
+
} catch (err) {
|
|
93
|
+
emit.error({ code: "read_failed", message: (err as Error).message });
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// ── list ──────────────────────────────────────────────────────────────
|
|
99
|
+
root
|
|
100
|
+
.command("list")
|
|
101
|
+
.description("Summarize all active scratchpads + archive count")
|
|
102
|
+
.option("--archives", "List archive files instead of active scratchpads")
|
|
103
|
+
.action((opts: { archives?: boolean }) => {
|
|
104
|
+
try {
|
|
105
|
+
if (opts.archives) {
|
|
106
|
+
const items = listArchives().map((a) => {
|
|
107
|
+
const doc = parseSafe(a.path);
|
|
108
|
+
const lastEntry = doc?.entries[0];
|
|
109
|
+
const ageMin = Math.floor((Date.now() - a.mtimeMs) / 60000);
|
|
110
|
+
return {
|
|
111
|
+
basename: a.basename,
|
|
112
|
+
name: doc?.header.name ?? "unknown",
|
|
113
|
+
entries: doc?.entries.length ?? 0,
|
|
114
|
+
bytes: a.bytes,
|
|
115
|
+
archived_min_ago: ageMin,
|
|
116
|
+
last_category: lastEntry?.category ?? null,
|
|
117
|
+
last_ts: lastEntry?.ts_display ?? null,
|
|
118
|
+
};
|
|
119
|
+
});
|
|
120
|
+
emit.data({ rows: items });
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const dir = scratchDir();
|
|
124
|
+
const rows: Array<{
|
|
125
|
+
instance_id: string;
|
|
126
|
+
name: string;
|
|
127
|
+
entries: number;
|
|
128
|
+
bytes: number;
|
|
129
|
+
last_category: string | null;
|
|
130
|
+
last_ts: string | null;
|
|
131
|
+
}> = [];
|
|
132
|
+
for (const f of readdirSync(dir)) {
|
|
133
|
+
if (!f.endsWith(".md")) continue;
|
|
134
|
+
const instanceId = f.replace(/\.md$/, "");
|
|
135
|
+
const doc = loadScratch(instanceId);
|
|
136
|
+
if (!doc) continue;
|
|
137
|
+
rows.push({
|
|
138
|
+
instance_id: instanceId,
|
|
139
|
+
name: doc.header.name,
|
|
140
|
+
entries: doc.entries.length,
|
|
141
|
+
bytes: doc.bytes,
|
|
142
|
+
last_category: doc.entries[0]?.category ?? null,
|
|
143
|
+
last_ts: doc.entries[0]?.ts_display ?? null,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
rows.sort((a, b) => (b.last_ts ?? "").localeCompare(a.last_ts ?? ""));
|
|
147
|
+
emit.data({ rows });
|
|
148
|
+
} catch (err) {
|
|
149
|
+
emit.error({ code: "list_failed", message: (err as Error).message });
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// ── tail ──────────────────────────────────────────────────────────────
|
|
155
|
+
root
|
|
156
|
+
.command("tail")
|
|
157
|
+
.description("Follow my scratchpad (or a peer's) for new entries.")
|
|
158
|
+
.option("--name <name>", "Tail the named peer's scratchpad")
|
|
159
|
+
.option("--owner <id>", "Tail by instance_id directly")
|
|
160
|
+
.action(async (opts: { name?: string; owner?: string }) => {
|
|
161
|
+
try {
|
|
162
|
+
const owner = resolveTargetOwner(opts);
|
|
163
|
+
const path = scratchPath(owner);
|
|
164
|
+
if (!existsSync(path)) {
|
|
165
|
+
emit.error({ code: "no_scratchpad", message: `no scratchpad at ${path} yet` });
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
process.stderr.write(`tailing ${path}\n`); // lint-ok-emission: tail banner, immediate stderr flush before streaming
|
|
169
|
+
let lastSize = statSync(path).size;
|
|
170
|
+
// Print initial render
|
|
171
|
+
process.stdout.write(`${renderScratch(loadScratch(owner)!, 10)}\n`); // lint-ok-emission: streaming tail body
|
|
172
|
+
const w = watch(path, { persistent: true }, () => {
|
|
173
|
+
try {
|
|
174
|
+
const curr = statSync(path).size;
|
|
175
|
+
if (curr === lastSize) return;
|
|
176
|
+
lastSize = curr;
|
|
177
|
+
const doc = loadScratch(owner);
|
|
178
|
+
if (!doc) return;
|
|
179
|
+
const newest = doc.entries[0];
|
|
180
|
+
if (!newest) return;
|
|
181
|
+
const line = `\n## ${newest.ts_display} · ${newest.category}\n${newest.body}\n`;
|
|
182
|
+
process.stdout.write(line); // lint-ok-emission: streaming tail, per-line stdout flush, no envelope wrap
|
|
183
|
+
} catch {
|
|
184
|
+
// ignore transient
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
await new Promise<void>((resolveP) => {
|
|
188
|
+
const stop = () => {
|
|
189
|
+
w.close();
|
|
190
|
+
resolveP();
|
|
191
|
+
};
|
|
192
|
+
process.on("SIGINT", stop);
|
|
193
|
+
process.on("SIGTERM", stop);
|
|
194
|
+
});
|
|
195
|
+
} catch (err) {
|
|
196
|
+
emit.error({ code: "tail_failed", message: (err as Error).message });
|
|
197
|
+
process.exit(1);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// ── clear ─────────────────────────────────────────────────────────────
|
|
202
|
+
root
|
|
203
|
+
.command("clear")
|
|
204
|
+
.description("Delete my scratchpad (rare; mainly for testing)")
|
|
205
|
+
.option("--yes", "Confirm deletion")
|
|
206
|
+
.action((opts: { yes?: boolean }) => {
|
|
207
|
+
if (!opts.yes) {
|
|
208
|
+
emit.error({ code: "needs_yes", message: "pass --yes to confirm" });
|
|
209
|
+
process.exit(1);
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
const owner = currentOwnerOrThrow();
|
|
213
|
+
const path = scratchPath(owner);
|
|
214
|
+
if (existsSync(path)) {
|
|
215
|
+
unlinkSync(path);
|
|
216
|
+
emit.data({ cleared: true, path });
|
|
217
|
+
} else {
|
|
218
|
+
emit.data({ cleared: false, path, reason: "did not exist" });
|
|
219
|
+
}
|
|
220
|
+
} catch (err) {
|
|
221
|
+
emit.error({ code: "clear_failed", message: (err as Error).message });
|
|
222
|
+
process.exit(1);
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// ── lint ──────────────────────────────────────────────────────────────
|
|
227
|
+
root
|
|
228
|
+
.command("lint")
|
|
229
|
+
.description("Validate scratchpad format + size")
|
|
230
|
+
.option("--all", "Lint every scratchpad in .harnery/scratch/")
|
|
231
|
+
.option("--owner <id>", "Lint a specific owner's scratchpad")
|
|
232
|
+
.action((opts: { all?: boolean; owner?: string }) => {
|
|
233
|
+
try {
|
|
234
|
+
runLint(opts);
|
|
235
|
+
} catch (err) {
|
|
236
|
+
emit.error({ code: "lint_failed", message: (err as Error).message });
|
|
237
|
+
process.exit(1);
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// ── archive (manual / hook helper) ────────────────────────────────────
|
|
242
|
+
root
|
|
243
|
+
.command("archive")
|
|
244
|
+
.description("Archive my scratchpad now (idempotent; fired by SessionEnd hook)")
|
|
245
|
+
.option("--owner <id>", "Archive a specific owner's scratchpad")
|
|
246
|
+
.action((opts: { owner?: string }) => {
|
|
247
|
+
try {
|
|
248
|
+
const owner = opts.owner ?? currentOwnerOrThrow();
|
|
249
|
+
const dest = archiveScratch(owner);
|
|
250
|
+
emit.data({ instance_id: owner, archived: !!dest, path: dest });
|
|
251
|
+
} catch (err) {
|
|
252
|
+
emit.error({ code: "archive_failed", message: (err as Error).message });
|
|
253
|
+
process.exit(1);
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// ── recovery-cue (SessionStart hook helper) ───────────────────────────
|
|
258
|
+
root
|
|
259
|
+
.command("recovery-cue")
|
|
260
|
+
.description(
|
|
261
|
+
"Emit a one-line recovery hint to stdout when a recent archive exists. " +
|
|
262
|
+
"Used by SessionStart hook to surface 'previous session was doing X'. " +
|
|
263
|
+
"Stays silent when no relevant archive (no noise on fresh sessions).",
|
|
264
|
+
)
|
|
265
|
+
.option("--max-age-hours <n>", "Only surface archives newer than this", "24")
|
|
266
|
+
.action((opts: { maxAgeHours: string }) => {
|
|
267
|
+
try {
|
|
268
|
+
const maxHours = Number.parseInt(opts.maxAgeHours, 10);
|
|
269
|
+
const archives = listArchives();
|
|
270
|
+
if (archives.length === 0) return;
|
|
271
|
+
const newest = archives[0];
|
|
272
|
+
const ageHours = (Date.now() - newest.mtimeMs) / 3600_000;
|
|
273
|
+
if (ageHours > maxHours) return;
|
|
274
|
+
const doc = parseSafe(newest.path);
|
|
275
|
+
if (!doc || doc.entries.length === 0) return;
|
|
276
|
+
const last = doc.entries[0];
|
|
277
|
+
const ageMin = Math.floor((Date.now() - newest.mtimeMs) / 60_000);
|
|
278
|
+
const ageStr = ageMin < 60 ? `${ageMin}m` : `${Math.floor(ageMin / 60)}h ${ageMin % 60}m`;
|
|
279
|
+
const bodyOneLine = last.body.replace(/\s+/g, " ").trim();
|
|
280
|
+
const bodyTrunc = bodyOneLine.length > 100 ? `${bodyOneLine.slice(0, 99)}…` : bodyOneLine;
|
|
281
|
+
const cue =
|
|
282
|
+
`Recent scratchpad archive (${ageStr} ago): agent-${doc.header.name}: ` +
|
|
283
|
+
`last entry [${last.category}] "${bodyTrunc}". ` +
|
|
284
|
+
`Read full: \`${resolveBinName()} scratch read --archive ${newest.basename}\`.`;
|
|
285
|
+
process.stdout.write(`${cue}\n`); // lint-ok-emission: bash hook reads stdout to compose SessionStart additionalContext
|
|
286
|
+
} catch (_err) {
|
|
287
|
+
// Recovery cue is best-effort; never fail the SessionStart hook.
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// ── janitor (SessionStart hook) ───────────────────────────────────────
|
|
292
|
+
root
|
|
293
|
+
.command("janitor")
|
|
294
|
+
.description(
|
|
295
|
+
"Prune old archives + sweep orphan scratchpads (heartbeat gone). Fired by SessionStart hook.",
|
|
296
|
+
)
|
|
297
|
+
.option("--days <n>", "Archive retention in days", "7")
|
|
298
|
+
.option("--quiet", "Suppress stdout output")
|
|
299
|
+
.action((opts: { days: string; quiet?: boolean }) => {
|
|
300
|
+
try {
|
|
301
|
+
const days = Number.parseInt(opts.days, 10);
|
|
302
|
+
const swept = sweepOrphanScratchpads();
|
|
303
|
+
const pruned = pruneArchives(days);
|
|
304
|
+
if (!opts.quiet) {
|
|
305
|
+
emit.data({
|
|
306
|
+
archives_pruned: pruned,
|
|
307
|
+
orphans_swept: swept.length,
|
|
308
|
+
orphans: swept,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
} catch (err) {
|
|
312
|
+
emit.error({ code: "janitor_failed", message: (err as Error).message });
|
|
313
|
+
process.exit(1);
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// ─── runRead ──────────────────────────────────────────────────────────────
|
|
319
|
+
|
|
320
|
+
function runRead(opts: { name?: string; owner?: string; archive?: string; limit: string }): void {
|
|
321
|
+
const limit = Number.parseInt(opts.limit, 10);
|
|
322
|
+
if (opts.archive) {
|
|
323
|
+
const dir = scratchDir();
|
|
324
|
+
const path = resolvePath(dir, "archived", opts.archive);
|
|
325
|
+
if (!existsSync(path)) {
|
|
326
|
+
emit.error({ code: "no_archive", message: `archive not found: ${opts.archive}` });
|
|
327
|
+
process.exit(1);
|
|
328
|
+
}
|
|
329
|
+
const content = readFileSync(path, "utf8");
|
|
330
|
+
process.stdout.write(content); // lint-ok-emission: archive render, file content is already the rendered form
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const owner = resolveTargetOwner(opts);
|
|
335
|
+
const doc = loadScratch(owner);
|
|
336
|
+
if (!doc) {
|
|
337
|
+
emit.error({
|
|
338
|
+
code: "no_scratchpad",
|
|
339
|
+
message: `no scratchpad for owner=${owner.slice(0, 8)}… (file not created yet)`,
|
|
340
|
+
});
|
|
341
|
+
process.exit(1);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const hb = readHeartbeat(owner);
|
|
345
|
+
const staleBanner = renderStaleBannerIfNeeded(hb?.last_heartbeat);
|
|
346
|
+
process.stdout.write(`${staleBanner + renderScratch(doc, limit)}\n`); // lint-ok-emission: pretty render, multi-line markdown for direct TTY/pipe consumption
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function runLint(opts: { all?: boolean; owner?: string }): void {
|
|
350
|
+
const dir = scratchDir();
|
|
351
|
+
const files: string[] = [];
|
|
352
|
+
if (opts.owner) {
|
|
353
|
+
files.push(scratchPath(opts.owner));
|
|
354
|
+
} else if (opts.all) {
|
|
355
|
+
for (const f of readdirSync(dir)) {
|
|
356
|
+
if (f.endsWith(".md")) files.push(resolvePath(dir, f));
|
|
357
|
+
}
|
|
358
|
+
} else {
|
|
359
|
+
const owner = currentOwnerOrThrow();
|
|
360
|
+
files.push(scratchPath(owner));
|
|
361
|
+
}
|
|
362
|
+
let totalIssues = 0;
|
|
363
|
+
const reports: Array<{ path: string; issues: { line: number; message: string }[] }> = [];
|
|
364
|
+
for (const path of files) {
|
|
365
|
+
if (!existsSync(path)) {
|
|
366
|
+
reports.push({ path, issues: [{ line: 0, message: "(file does not exist)" }] });
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
const content = readFileSync(path, "utf8");
|
|
370
|
+
const issues = lintScratch(content);
|
|
371
|
+
if (issues.length > 0) totalIssues += issues.length;
|
|
372
|
+
reports.push({ path, issues });
|
|
373
|
+
}
|
|
374
|
+
emit.data({ files: reports.length, total_issues: totalIssues, reports });
|
|
375
|
+
if (totalIssues > 0) process.exit(1);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────
|
|
379
|
+
|
|
380
|
+
function validateCategory(c: string): ScratchCategory {
|
|
381
|
+
if (!(SCRATCH_CATEGORIES as readonly string[]).includes(c)) {
|
|
382
|
+
emit.error({
|
|
383
|
+
code: "bad_category",
|
|
384
|
+
message: `category must be one of: ${SCRATCH_CATEGORIES.join(", ")}`,
|
|
385
|
+
});
|
|
386
|
+
process.exit(1);
|
|
387
|
+
}
|
|
388
|
+
return c as ScratchCategory;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function resolveTargetOwner(opts: { name?: string; owner?: string }): string {
|
|
392
|
+
if (opts.owner) return opts.owner;
|
|
393
|
+
if (opts.name) {
|
|
394
|
+
const id = resolveOwnerByName(opts.name);
|
|
395
|
+
if (!id) {
|
|
396
|
+
emit.error({
|
|
397
|
+
code: "no_match",
|
|
398
|
+
message: `no live agent named "${opts.name}". Try \`${resolveBinName()} agents list\`.`,
|
|
399
|
+
});
|
|
400
|
+
process.exit(1);
|
|
401
|
+
}
|
|
402
|
+
return id;
|
|
403
|
+
}
|
|
404
|
+
return currentOwnerOrThrow();
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function renderScratch(doc: ScratchDoc, limit: number): string {
|
|
408
|
+
const lines: string[] = [];
|
|
409
|
+
lines.push(`# Scratchpad: agent-${doc.header.name}`);
|
|
410
|
+
if (doc.header.session_id) lines.push(`session_id: ${doc.header.session_id}`);
|
|
411
|
+
if (doc.header.machine) lines.push(`machine: ${doc.header.machine}`);
|
|
412
|
+
if (doc.header.started) lines.push(`started: ${doc.header.started}`);
|
|
413
|
+
if (doc.header.last_updated) lines.push(`last_updated: ${doc.header.last_updated}`);
|
|
414
|
+
lines.push("\n---\n");
|
|
415
|
+
for (const entry of doc.entries.slice(0, limit)) {
|
|
416
|
+
lines.push(`## ${entry.ts_display} · ${entry.category}`);
|
|
417
|
+
if (entry.body) lines.push(entry.body);
|
|
418
|
+
lines.push("");
|
|
419
|
+
}
|
|
420
|
+
if (doc.entries.length > limit) {
|
|
421
|
+
lines.push(`(+${doc.entries.length - limit} older entries; raise --limit to see them)`);
|
|
422
|
+
}
|
|
423
|
+
return lines.join("\n").trimEnd();
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const FRESHNESS_SECS = 600;
|
|
427
|
+
function renderStaleBannerIfNeeded(lastHeartbeat: string | undefined): string {
|
|
428
|
+
if (!lastHeartbeat) return "";
|
|
429
|
+
const ts = Date.parse(lastHeartbeat);
|
|
430
|
+
if (!Number.isFinite(ts)) return "";
|
|
431
|
+
const ageSec = Math.floor((Date.now() - ts) / 1000);
|
|
432
|
+
if (ageSec < FRESHNESS_SECS) return "";
|
|
433
|
+
const m = Math.floor(ageSec / 60);
|
|
434
|
+
return `[STALE: heartbeat ${m}m old, agent may be dead]\n\n`;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function parseSafe(path: string): ScratchDoc | null {
|
|
438
|
+
try {
|
|
439
|
+
if (!existsSync(path)) return null;
|
|
440
|
+
const content = readFileSync(path, "utf8");
|
|
441
|
+
return parseScratch(path, content);
|
|
442
|
+
} catch {
|
|
443
|
+
return null;
|
|
444
|
+
}
|
|
445
|
+
}
|