harnery 0.0.1 → 0.2.0
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 +494 -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 +32 -0
- package/dist/lib/readability/client.d.ts.map +1 -0
- package/dist/lib/readability/client.js +119 -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 +564 -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 +156 -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,147 @@
|
|
|
1
|
+
/** ANSI color helpers, minimal, no dependencies */
|
|
2
|
+
const isColor = process.stdout.isTTY !== false;
|
|
3
|
+
|
|
4
|
+
const code = (n: number) => (isColor ? `\x1b[${n}m` : "");
|
|
5
|
+
const reset = code(0);
|
|
6
|
+
|
|
7
|
+
export const c = {
|
|
8
|
+
bold: (s: string) => `${code(1)}${s}${reset}`,
|
|
9
|
+
dim: (s: string) => `${code(2)}${s}${reset}`,
|
|
10
|
+
green: (s: string) => `${code(32)}${s}${reset}`,
|
|
11
|
+
red: (s: string) => `${code(31)}${s}${reset}`,
|
|
12
|
+
yellow: (s: string) => `${code(33)}${s}${reset}`,
|
|
13
|
+
cyan: (s: string) => `${code(36)}${s}${reset}`,
|
|
14
|
+
gray: (s: string) => `${code(90)}${s}${reset}`,
|
|
15
|
+
magenta: (s: string) => `${code(35)}${s}${reset}`,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/** Placeholder shown when a value is missing (null/empty), e.g. an unknown branch or age. */
|
|
19
|
+
export const NO_DATA = "—";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Pretty-print + ANSI-colorize a JSON value. 2-space indent. TTY-aware: when
|
|
23
|
+
* stdout is not a TTY, the underlying `c.*` helpers emit no ANSI codes, so
|
|
24
|
+
* output is plain text. Use this for `--pretty` flags on JSON-emitting commands.
|
|
25
|
+
*
|
|
26
|
+
* Colors: keys=cyan, strings=green, numbers=magenta, booleans=yellow, null=dim.
|
|
27
|
+
*/
|
|
28
|
+
export function colorJson(value: unknown, indent = 0): string {
|
|
29
|
+
const seen = new WeakSet<object>();
|
|
30
|
+
const step = " ";
|
|
31
|
+
|
|
32
|
+
function fmt(v: unknown, depth: number): string {
|
|
33
|
+
if (v === null) return c.dim("null");
|
|
34
|
+
if (typeof v === "boolean") return c.yellow(String(v));
|
|
35
|
+
if (typeof v === "number") return c.magenta(String(v));
|
|
36
|
+
if (typeof v === "string") return c.green(JSON.stringify(v));
|
|
37
|
+
if (typeof v === "bigint") return c.magenta(`${v}n`);
|
|
38
|
+
if (Array.isArray(v)) {
|
|
39
|
+
if (v.length === 0) return "[]";
|
|
40
|
+
if (seen.has(v)) return c.dim('"[Circular]"');
|
|
41
|
+
seen.add(v);
|
|
42
|
+
const pad = step.repeat(depth + 1);
|
|
43
|
+
const close = step.repeat(depth);
|
|
44
|
+
const items = v.map((x) => pad + fmt(x, depth + 1)).join(",\n");
|
|
45
|
+
return `[\n${items}\n${close}]`;
|
|
46
|
+
}
|
|
47
|
+
if (typeof v === "object") {
|
|
48
|
+
// Class instances that define toJSON() (Big.js, Decimal.js, Date, etc.)
|
|
49
|
+
// would render as their raw internal shape if we walked Object.keys
|
|
50
|
+
// directly. Unwrap once so callers see the intended representation
|
|
51
|
+
// (Big.js → "1.97" instead of {s,e,c} from BigQuery NUMERIC fields).
|
|
52
|
+
const maybeJsonable = v as { toJSON?: () => unknown };
|
|
53
|
+
if (typeof maybeJsonable.toJSON === "function") {
|
|
54
|
+
return fmt(maybeJsonable.toJSON(), depth);
|
|
55
|
+
}
|
|
56
|
+
const obj = v as Record<string, unknown>;
|
|
57
|
+
const keys = Object.keys(obj);
|
|
58
|
+
if (keys.length === 0) return "{}";
|
|
59
|
+
if (seen.has(obj)) return c.dim('"[Circular]"');
|
|
60
|
+
seen.add(obj);
|
|
61
|
+
const pad = step.repeat(depth + 1);
|
|
62
|
+
const close = step.repeat(depth);
|
|
63
|
+
const items = keys
|
|
64
|
+
.map((k) => `${pad}${c.cyan(JSON.stringify(k))}: ${fmt(obj[k], depth + 1)}`)
|
|
65
|
+
.join(",\n");
|
|
66
|
+
return `{\n${items}\n${close}}`;
|
|
67
|
+
}
|
|
68
|
+
return JSON.stringify(v) ?? "null";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return fmt(value, indent);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Render a table from rows of objects */
|
|
75
|
+
export function table(
|
|
76
|
+
rows: Record<string, unknown>[],
|
|
77
|
+
opts: { maxColWidth?: number } = {},
|
|
78
|
+
): string {
|
|
79
|
+
if (rows.length === 0) return c.dim("(no rows)");
|
|
80
|
+
|
|
81
|
+
const maxCol = opts.maxColWidth ?? 60;
|
|
82
|
+
const keys = Object.keys(rows[0]!);
|
|
83
|
+
|
|
84
|
+
// Compute column widths
|
|
85
|
+
const widths = new Map<string, number>();
|
|
86
|
+
for (const key of keys) {
|
|
87
|
+
let max = key.length;
|
|
88
|
+
for (const row of rows) {
|
|
89
|
+
const val = stringify(row[key]);
|
|
90
|
+
max = Math.max(max, Math.min(val.length, maxCol));
|
|
91
|
+
}
|
|
92
|
+
widths.set(key, max);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Header
|
|
96
|
+
const header = keys.map((k) => c.bold(k.padEnd(widths.get(k)!))).join(" ");
|
|
97
|
+
const separator = keys.map((k) => "─".repeat(widths.get(k)!)).join("──");
|
|
98
|
+
|
|
99
|
+
// Rows
|
|
100
|
+
const lines = rows.map((row) =>
|
|
101
|
+
keys
|
|
102
|
+
.map((k) => {
|
|
103
|
+
const val = stringify(row[k]);
|
|
104
|
+
const display = val.length > maxCol ? `${val.slice(0, maxCol - 1)}…` : val;
|
|
105
|
+
return display.padEnd(widths.get(k)!);
|
|
106
|
+
})
|
|
107
|
+
.join(" "),
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
return [header, separator, ...lines].join("\n");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Convert a value to a display string */
|
|
114
|
+
function stringify(val: unknown): string {
|
|
115
|
+
if (val === null || val === undefined) return "NULL";
|
|
116
|
+
if (typeof val === "object") {
|
|
117
|
+
if (val instanceof Date) return val.toISOString();
|
|
118
|
+
// BigQuery returns { value: "..." } for some types
|
|
119
|
+
if ("value" in val && Object.keys(val).length === 1) {
|
|
120
|
+
return String((val as { value: unknown }).value);
|
|
121
|
+
}
|
|
122
|
+
return JSON.stringify(val);
|
|
123
|
+
}
|
|
124
|
+
return String(val);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Format rows as CSV */
|
|
128
|
+
export function csv(rows: Record<string, unknown>[]): string {
|
|
129
|
+
if (rows.length === 0) return "";
|
|
130
|
+
const keys = Object.keys(rows[0]!);
|
|
131
|
+
const header = keys.map(csvEscape).join(",");
|
|
132
|
+
const lines = rows.map((row) => keys.map((k) => csvEscape(stringify(row[k]))).join(","));
|
|
133
|
+
return [header, ...lines].join("\n");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function csvEscape(val: string): string {
|
|
137
|
+
if (val.includes(",") || val.includes('"') || val.includes("\n")) {
|
|
138
|
+
return `"${val.replace(/"/g, '""')}"`;
|
|
139
|
+
}
|
|
140
|
+
return val;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Print a labeled key-value section */
|
|
144
|
+
export function kvLine(label: string, value: string, color?: (s: string) => string): string {
|
|
145
|
+
const colorFn = color ?? ((s: string) => s);
|
|
146
|
+
return ` ${c.dim(label.padEnd(14))} ${colorFn(value)}`;
|
|
147
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import type { Cookie, CookieJar } from "../cookies/index.ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Simple cookie-aware HTTP client.
|
|
5
|
+
*
|
|
6
|
+
* Wraps the global `fetch` (Bun's native one) with two extras:
|
|
7
|
+
* - Attaches a `Cookie:` header from a CookieJar before the request.
|
|
8
|
+
* - Persists `Set-Cookie` responses back into the same jar.
|
|
9
|
+
*
|
|
10
|
+
* Used by the `fetch` command and other tooling that wants to share session
|
|
11
|
+
* state with `browse`.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export interface FetchOptions {
|
|
15
|
+
/** HTTP method. Default GET. */
|
|
16
|
+
method?: string;
|
|
17
|
+
/** Request body. Strings/Buffers/Streams pass through to fetch directly. */
|
|
18
|
+
body?: BodyInit | null;
|
|
19
|
+
/** Extra headers (merged with auto-added Cookie). */
|
|
20
|
+
headers?: Record<string, string>;
|
|
21
|
+
/**
|
|
22
|
+
* Optional CookieJar. When provided:
|
|
23
|
+
* - Matching cookies become a Cookie header.
|
|
24
|
+
* - Set-Cookie response headers are parsed and merged into the jar.
|
|
25
|
+
* Pass `null` (or omit) to disable.
|
|
26
|
+
*/
|
|
27
|
+
jar?: CookieJar | null;
|
|
28
|
+
/** Optional override for redirect handling. Default `'follow'`. */
|
|
29
|
+
redirect?: RequestRedirect;
|
|
30
|
+
/** AbortSignal for timeout/cancel control. */
|
|
31
|
+
signal?: AbortSignal;
|
|
32
|
+
/**
|
|
33
|
+
* Optional callback that returns extra headers to attach based on the
|
|
34
|
+
* target URL. Consumers can inject extra HTTP headers per-URL via this
|
|
35
|
+
* callback (e.g., a Cloudflare-bypass header for specific zones).
|
|
36
|
+
* Caller-provided explicit headers always win; auto-attached values
|
|
37
|
+
* only land when the key isn't already set.
|
|
38
|
+
*/
|
|
39
|
+
extraHeaders?: (url: string) => Record<string, string>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface FetchResult {
|
|
43
|
+
status: number;
|
|
44
|
+
statusText: string;
|
|
45
|
+
url: string;
|
|
46
|
+
headers: Record<string, string>;
|
|
47
|
+
body: string;
|
|
48
|
+
/** Number of cookies persisted back into the jar (0 if no jar passed). */
|
|
49
|
+
cookiesSaved: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Fetch a URL with optional cookie-jar attach + persist.
|
|
54
|
+
*
|
|
55
|
+
* Returns the response body as a string (callers handle JSON parsing).
|
|
56
|
+
* Streams aren't supported; this is a CLI helper, not a streaming HTTP
|
|
57
|
+
* client. Use Bun's `fetch` directly for streaming workloads.
|
|
58
|
+
*/
|
|
59
|
+
export async function fetchWithJar(url: string, opts: FetchOptions = {}): Promise<FetchResult> {
|
|
60
|
+
const headers: Record<string, string> = { ...(opts.headers ?? {}) };
|
|
61
|
+
|
|
62
|
+
if (opts.jar) {
|
|
63
|
+
const cookieHeader = opts.jar.header(url);
|
|
64
|
+
if (cookieHeader && !headers.Cookie && !headers.cookie) {
|
|
65
|
+
headers.Cookie = cookieHeader;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Caller-provided extraHeaders callback (e.g., for Cloudflare-bypass or
|
|
70
|
+
// custom auth headers). Caller-supplied explicit headers always win.
|
|
71
|
+
if (opts.extraHeaders) {
|
|
72
|
+
for (const [k, v] of Object.entries(opts.extraHeaders(url))) {
|
|
73
|
+
if (!(k in headers) && !(k.toLowerCase() in headers)) headers[k] = v;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const response = await fetch(url, {
|
|
78
|
+
method: opts.method ?? "GET",
|
|
79
|
+
body: opts.body,
|
|
80
|
+
headers,
|
|
81
|
+
redirect: opts.redirect ?? "follow",
|
|
82
|
+
signal: opts.signal,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
let cookiesSaved = 0;
|
|
86
|
+
if (opts.jar) {
|
|
87
|
+
const setCookieHeaders = collectSetCookie(response.headers);
|
|
88
|
+
if (setCookieHeaders.length > 0) {
|
|
89
|
+
const parsedHost = new URL(response.url).hostname;
|
|
90
|
+
const cookies: Cookie[] = setCookieHeaders
|
|
91
|
+
.map((sc) => parseSetCookie(sc, parsedHost))
|
|
92
|
+
.filter((c): c is Cookie => c !== null);
|
|
93
|
+
for (const cookie of cookies) {
|
|
94
|
+
opts.jar.set(cookie);
|
|
95
|
+
}
|
|
96
|
+
cookiesSaved = cookies.length;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const headerObj: Record<string, string> = {};
|
|
101
|
+
response.headers.forEach((value, key) => {
|
|
102
|
+
headerObj[key] = value;
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
status: response.status,
|
|
107
|
+
statusText: response.statusText,
|
|
108
|
+
url: response.url,
|
|
109
|
+
headers: headerObj,
|
|
110
|
+
body: await response.text(),
|
|
111
|
+
cookiesSaved,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
// Set-Cookie parsing
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
//
|
|
119
|
+
// `fetch` joins multiple Set-Cookie headers with `, ` per the spec, but that
|
|
120
|
+
// collides with the comma-separated date format in `Expires=`. We use the
|
|
121
|
+
// dual-mode approach: prefer the `getSetCookie()` method when available
|
|
122
|
+
// (Node 20+, Bun 1.0+) and fall back to manual splitting otherwise.
|
|
123
|
+
|
|
124
|
+
function collectSetCookie(headers: Headers): string[] {
|
|
125
|
+
const h = headers as Headers & { getSetCookie?: () => string[] };
|
|
126
|
+
if (typeof h.getSetCookie === "function") {
|
|
127
|
+
return h.getSetCookie();
|
|
128
|
+
}
|
|
129
|
+
const raw = headers.get("set-cookie");
|
|
130
|
+
return raw ? splitSetCookieHeader(raw) : [];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function splitSetCookieHeader(raw: string): string[] {
|
|
134
|
+
// Splits on ", " that precedes a new cookie (heuristic: a token followed
|
|
135
|
+
// by `=`), avoiding date-internal commas like `Expires=Wed, 21 Oct …`.
|
|
136
|
+
const parts: string[] = [];
|
|
137
|
+
let depth = 0;
|
|
138
|
+
let buf = "";
|
|
139
|
+
for (let i = 0; i < raw.length; i++) {
|
|
140
|
+
const ch = raw[i];
|
|
141
|
+
if (ch === "," && depth === 0) {
|
|
142
|
+
const ahead = raw.slice(i + 1).trimStart();
|
|
143
|
+
if (/^[A-Za-z0-9_!#$%&'*+\-.^`|~]+=/.test(ahead)) {
|
|
144
|
+
parts.push(buf.trim());
|
|
145
|
+
buf = "";
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (ch === "=") depth++;
|
|
150
|
+
if (ch === ";") depth = 0;
|
|
151
|
+
buf += ch;
|
|
152
|
+
}
|
|
153
|
+
if (buf.trim()) parts.push(buf.trim());
|
|
154
|
+
return parts;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function parseSetCookie(raw: string, defaultDomain: string): Cookie | null {
|
|
158
|
+
const parts = raw.split(";").map((p) => p.trim());
|
|
159
|
+
if (parts.length === 0) return null;
|
|
160
|
+
|
|
161
|
+
const first = parts[0];
|
|
162
|
+
if (!first) return null;
|
|
163
|
+
const eq = first.indexOf("=");
|
|
164
|
+
if (eq < 0) return null;
|
|
165
|
+
const name = first.slice(0, eq).trim();
|
|
166
|
+
const value = first.slice(eq + 1).trim();
|
|
167
|
+
if (!name) return null;
|
|
168
|
+
|
|
169
|
+
const cookie: Cookie = {
|
|
170
|
+
name,
|
|
171
|
+
value,
|
|
172
|
+
domain: defaultDomain,
|
|
173
|
+
path: "/",
|
|
174
|
+
expires: -1,
|
|
175
|
+
httpOnly: false,
|
|
176
|
+
secure: false,
|
|
177
|
+
session: true,
|
|
178
|
+
size: name.length + value.length,
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
for (const attr of parts.slice(1)) {
|
|
182
|
+
const lower = attr.toLowerCase();
|
|
183
|
+
if (lower.startsWith("domain=")) {
|
|
184
|
+
const d = attr.slice(7).trim();
|
|
185
|
+
cookie.domain = d.startsWith(".") ? d : `.${d}`;
|
|
186
|
+
} else if (lower.startsWith("path=")) {
|
|
187
|
+
cookie.path = attr.slice(5).trim() || "/";
|
|
188
|
+
} else if (lower.startsWith("expires=")) {
|
|
189
|
+
const ts = Date.parse(attr.slice(8).trim());
|
|
190
|
+
if (!Number.isNaN(ts)) {
|
|
191
|
+
cookie.expires = Math.floor(ts / 1000);
|
|
192
|
+
cookie.session = false;
|
|
193
|
+
}
|
|
194
|
+
} else if (lower.startsWith("max-age=")) {
|
|
195
|
+
const seconds = Number.parseInt(attr.slice(8).trim(), 10);
|
|
196
|
+
if (!Number.isNaN(seconds)) {
|
|
197
|
+
cookie.expires = Math.floor(Date.now() / 1000) + seconds;
|
|
198
|
+
cookie.session = false;
|
|
199
|
+
}
|
|
200
|
+
} else if (lower === "secure") {
|
|
201
|
+
cookie.secure = true;
|
|
202
|
+
} else if (lower === "httponly") {
|
|
203
|
+
cookie.httpOnly = true;
|
|
204
|
+
} else if (lower.startsWith("samesite=")) {
|
|
205
|
+
const v = attr.slice(9).trim();
|
|
206
|
+
cookie.sameSite = v.charAt(0).toUpperCase() + v.slice(1).toLowerCase();
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return cookie;
|
|
211
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { type FetchOptions, type FetchResult, fetchWithJar } from "./client.js";
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent persona identity registry: per-agent durable UUIDs.
|
|
3
|
+
*
|
|
4
|
+
* The coord layer has two id concepts that get confused:
|
|
5
|
+
* - `instance_id`: Claude Code session UUID (fresh per session). Lives at
|
|
6
|
+
* `.harnery/active/<instance_id>.json` as the heartbeat filename.
|
|
7
|
+
* - `agent_id` (this module): durable per-AGENT-PERSONA UUID. Stable
|
|
8
|
+
* across sessions; same UUID for every Maya session, regardless of
|
|
9
|
+
* how many times she restarts Claude Code.
|
|
10
|
+
*
|
|
11
|
+
* Storage: `.harnery/identities/<agent_id>.json`, one file per persona.
|
|
12
|
+
* The filename IS the id so reverse lookup is O(1) by id; forward lookup
|
|
13
|
+
* by name is a scan (small directory, ~100 personas tops in practice).
|
|
14
|
+
*
|
|
15
|
+
* Used as the canonical identifier in:
|
|
16
|
+
* - Council manifests (`created_by_id`, `steward_id`, `member_ids[]`)
|
|
17
|
+
* - Council body filenames (`<agent_id>.md` not `<name>.md`)
|
|
18
|
+
* - Session events ndjson (new `agent_id` field alongside `agent_name`)
|
|
19
|
+
* - Heartbeats (new `agent_id` field; existing CC subagent-call id
|
|
20
|
+
* renamed to `subagent_call_id`)
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { randomUUID } from "node:crypto";
|
|
24
|
+
import {
|
|
25
|
+
existsSync,
|
|
26
|
+
mkdirSync,
|
|
27
|
+
readdirSync,
|
|
28
|
+
readFileSync,
|
|
29
|
+
renameSync,
|
|
30
|
+
writeFileSync,
|
|
31
|
+
} from "node:fs";
|
|
32
|
+
import { resolve } from "node:path";
|
|
33
|
+
|
|
34
|
+
import { monorepoRoot } from "../../core/agents/index.ts";
|
|
35
|
+
|
|
36
|
+
export const IDENTITY_SCHEMA_VERSION = 1 as const;
|
|
37
|
+
|
|
38
|
+
export interface AgentIdentity {
|
|
39
|
+
schema_version: 1;
|
|
40
|
+
/** Persistent persona UUID. v4. */
|
|
41
|
+
agent_id: string;
|
|
42
|
+
/** Current display name (e.g. "Maya", without the "agent-" prefix). */
|
|
43
|
+
name: string;
|
|
44
|
+
/** Prior names this identity has been known by. Updated by renameIdentity(). */
|
|
45
|
+
aliases: Array<{ name: string; retired_at: string }>;
|
|
46
|
+
/** UTC ISO-8601 timestamp of first mint. */
|
|
47
|
+
created_at: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function identitiesDir(): string | null {
|
|
51
|
+
const root = monorepoRoot();
|
|
52
|
+
if (!root) return null;
|
|
53
|
+
return resolve(root, ".harnery", "identities");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function ensureDir(): string {
|
|
57
|
+
const dir = identitiesDir();
|
|
58
|
+
if (!dir) throw new Error("not in an agent session; no monorepo root");
|
|
59
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
60
|
+
return dir;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function identityPath(agentId: string): string {
|
|
64
|
+
return resolve(ensureDir(), `${agentId}.json`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function nowIso(): string {
|
|
68
|
+
return new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function readIdentityFile(path: string): AgentIdentity | null {
|
|
72
|
+
try {
|
|
73
|
+
const parsed = JSON.parse(readFileSync(path, "utf8")) as AgentIdentity;
|
|
74
|
+
if (parsed.schema_version !== IDENTITY_SCHEMA_VERSION) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
`identity ${path}: unsupported schema_version=${parsed.schema_version} (expected ${IDENTITY_SCHEMA_VERSION})`,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
return parsed;
|
|
80
|
+
} catch {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Strip an "agent-" prefix if present. The registry stores bare names. */
|
|
86
|
+
export function bareName(raw: string): string {
|
|
87
|
+
const trimmed = raw.trim();
|
|
88
|
+
return trimmed.startsWith("agent-") ? trimmed.slice("agent-".length) : trimmed;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Re-add the "agent-" prefix for display contexts. */
|
|
92
|
+
export function displayName(name: string): string {
|
|
93
|
+
return name.startsWith("agent-") ? name : `agent-${name}`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Read one identity by id. Null if missing. */
|
|
97
|
+
export function lookupById(agentId: string): AgentIdentity | null {
|
|
98
|
+
const dir = identitiesDir();
|
|
99
|
+
if (!dir) return null;
|
|
100
|
+
const fp = resolve(dir, `${agentId}.json`);
|
|
101
|
+
if (!existsSync(fp)) return null;
|
|
102
|
+
return readIdentityFile(fp);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Read one identity by display name (case-insensitive on bare name).
|
|
106
|
+
* Scans the directory; O(N) on identity count. */
|
|
107
|
+
export function lookupByName(name: string): AgentIdentity | null {
|
|
108
|
+
const dir = identitiesDir();
|
|
109
|
+
if (!dir || !existsSync(dir)) return null;
|
|
110
|
+
const wanted = bareName(name).toLowerCase();
|
|
111
|
+
for (const f of readdirSync(dir)) {
|
|
112
|
+
if (!f.endsWith(".json")) continue;
|
|
113
|
+
const id = readIdentityFile(resolve(dir, f));
|
|
114
|
+
if (!id) continue;
|
|
115
|
+
if (id.name.toLowerCase() === wanted) return id;
|
|
116
|
+
for (const alias of id.aliases) {
|
|
117
|
+
if (alias.name.toLowerCase() === wanted) return id;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** All known identities, sorted by created_at ascending. */
|
|
124
|
+
export function listIdentities(): AgentIdentity[] {
|
|
125
|
+
const dir = identitiesDir();
|
|
126
|
+
if (!dir || !existsSync(dir)) return [];
|
|
127
|
+
const out: AgentIdentity[] = [];
|
|
128
|
+
for (const f of readdirSync(dir)) {
|
|
129
|
+
if (!f.endsWith(".json")) continue;
|
|
130
|
+
const id = readIdentityFile(resolve(dir, f));
|
|
131
|
+
if (id) out.push(id);
|
|
132
|
+
}
|
|
133
|
+
out.sort((a, b) => a.created_at.localeCompare(b.created_at));
|
|
134
|
+
return out;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Find an identity by name, or mint one. Returns the resolved identity.
|
|
139
|
+
* Idempotent: calling twice with the same name returns the same record.
|
|
140
|
+
*
|
|
141
|
+
* Mint path: generates a fresh uuid v4, writes the file atomically (tmp +
|
|
142
|
+
* rename), returns the new identity. Race-safe: if two processes mint
|
|
143
|
+
* simultaneously, both files land (different ids), but lookupByName will
|
|
144
|
+
* surface whichever happened to be read first; the second is orphaned.
|
|
145
|
+
* For agent personas (added at human cadence) this race is theoretical.
|
|
146
|
+
*/
|
|
147
|
+
export function ensureIdentity(name: string): AgentIdentity {
|
|
148
|
+
const existing = lookupByName(name);
|
|
149
|
+
if (existing) return existing;
|
|
150
|
+
const id: AgentIdentity = {
|
|
151
|
+
schema_version: IDENTITY_SCHEMA_VERSION,
|
|
152
|
+
agent_id: randomUUID(),
|
|
153
|
+
name: bareName(name),
|
|
154
|
+
aliases: [],
|
|
155
|
+
created_at: nowIso(),
|
|
156
|
+
};
|
|
157
|
+
writeIdentity(id);
|
|
158
|
+
return id;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Persist an identity (tmp + rename). Creates the dir if missing. */
|
|
162
|
+
export function writeIdentity(id: AgentIdentity): void {
|
|
163
|
+
ensureDir();
|
|
164
|
+
const fp = identityPath(id.agent_id);
|
|
165
|
+
const tmp = `${fp}.tmp.${process.pid}`;
|
|
166
|
+
writeFileSync(tmp, `${JSON.stringify(id, null, 2)}\n`, "utf8");
|
|
167
|
+
renameSync(tmp, fp);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Rename an existing identity. The prior name lands in aliases[] so name
|
|
172
|
+
* lookup still resolves correctly. The agent_id is stable across renames,
|
|
173
|
+
* which is the whole point of the registry.
|
|
174
|
+
*/
|
|
175
|
+
export function renameIdentity(agentId: string, newName: string): AgentIdentity {
|
|
176
|
+
const existing = lookupById(agentId);
|
|
177
|
+
if (!existing) {
|
|
178
|
+
throw new Error(`renameIdentity: no identity matching '${agentId}'`);
|
|
179
|
+
}
|
|
180
|
+
const bare = bareName(newName);
|
|
181
|
+
if (existing.name === bare) return existing;
|
|
182
|
+
const next: AgentIdentity = {
|
|
183
|
+
...existing,
|
|
184
|
+
name: bare,
|
|
185
|
+
aliases: [...existing.aliases, { name: existing.name, retired_at: nowIso() }],
|
|
186
|
+
};
|
|
187
|
+
writeIdentity(next);
|
|
188
|
+
return next;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Resolve a string to an agent_id. Accepts:
|
|
193
|
+
* - A UUID (already an id), verified to exist in registry
|
|
194
|
+
* - A display name (e.g. "agent-Maya" or "Maya"), resolved via lookupByName
|
|
195
|
+
* Returns null when the input is neither a known id nor a known name.
|
|
196
|
+
*
|
|
197
|
+
* Does NOT mint. Callers that want mint-on-miss should call ensureIdentity()
|
|
198
|
+
* with a name, then use the returned agent_id.
|
|
199
|
+
*/
|
|
200
|
+
export function resolveAgentId(input: string): string | null {
|
|
201
|
+
const trimmed = input.trim();
|
|
202
|
+
if (!trimmed) return null;
|
|
203
|
+
// UUID shape check, relaxed; we only need the lookupById to confirm.
|
|
204
|
+
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(trimmed)) {
|
|
205
|
+
const byId = lookupById(trimmed);
|
|
206
|
+
if (byId) return byId.agent_id;
|
|
207
|
+
}
|
|
208
|
+
const byName = lookupByName(trimmed);
|
|
209
|
+
return byName ? byName.agent_id : null;
|
|
210
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { hostname } from "node:os";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { coordEnv } from "./env.ts";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Resolve a human-friendly label for the current machine. Used to tag
|
|
8
|
+
* coordination artifacts (scratch-doc headers, session telemetry) by the host
|
|
9
|
+
* they originated on.
|
|
10
|
+
*
|
|
11
|
+
* Precedence (first non-empty wins):
|
|
12
|
+
* 1. HARNERY_MACHINE env var: transient / per-launch / CI override
|
|
13
|
+
* 2. ~/.config/harnery/machine file: persistent, harness-neutral identity
|
|
14
|
+
* 3. os.hostname() (normalized): automatic floor; never empty
|
|
15
|
+
*
|
|
16
|
+
* The env var sits on top for ad-hoc overrides; the file is the durable home for
|
|
17
|
+
* a machine's name (it survives across Claude Code / Cursor / Codex and GUI
|
|
18
|
+
* launches that don't inherit a shell rc); hostname guarantees a real answer
|
|
19
|
+
* with zero configuration.
|
|
20
|
+
*/
|
|
21
|
+
export function resolveMachineLabel(): string {
|
|
22
|
+
const fromEnv = coordEnv("MACHINE")?.trim();
|
|
23
|
+
if (fromEnv) return fromEnv;
|
|
24
|
+
|
|
25
|
+
const fromFile = readMachineFile();
|
|
26
|
+
if (fromFile) return fromFile;
|
|
27
|
+
|
|
28
|
+
return normalizeHost(hostname());
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Absolute path of the machine-label file, honoring XDG_CONFIG_HOME. */
|
|
32
|
+
export function machineFilePath(): string | null {
|
|
33
|
+
const home = process.env.HOME ?? process.env.USERPROFILE;
|
|
34
|
+
if (!home) return null;
|
|
35
|
+
const configHome = process.env.XDG_CONFIG_HOME?.trim() || resolve(home, ".config");
|
|
36
|
+
return resolve(configHome, "harnery", "machine");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** First non-empty, non-comment line of the machine-label file, or null. */
|
|
40
|
+
function readMachineFile(): string | null {
|
|
41
|
+
const path = machineFilePath();
|
|
42
|
+
if (!path || !existsSync(path)) return null;
|
|
43
|
+
try {
|
|
44
|
+
for (const line of readFileSync(path, "utf8").split("\n")) {
|
|
45
|
+
const trimmed = line.trim();
|
|
46
|
+
if (trimmed && !trimmed.startsWith("#")) return trimmed;
|
|
47
|
+
}
|
|
48
|
+
} catch {
|
|
49
|
+
/* unreadable; fall through to hostname */
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Lowercase, strip a trailing `.local` (macOS mDNS suffix). Never empty. */
|
|
55
|
+
function normalizeHost(raw: string): string {
|
|
56
|
+
const cleaned = raw
|
|
57
|
+
.trim()
|
|
58
|
+
.replace(/\.local$/i, "")
|
|
59
|
+
.toLowerCase();
|
|
60
|
+
return cleaned || "unknown";
|
|
61
|
+
}
|