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,187 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import type { Command } from "commander";
|
|
3
|
+
import type { EmitContext } from "../commander.ts";
|
|
4
|
+
import { resolveOwner, selfDisplayName } from "../core/agents/index.ts";
|
|
5
|
+
import {
|
|
6
|
+
clampField,
|
|
7
|
+
newCmdId,
|
|
8
|
+
readLastIntent,
|
|
9
|
+
writeSessionEvent,
|
|
10
|
+
} from "../core/agents/session-events.ts";
|
|
11
|
+
import { resolveBinName } from "../core/config.ts";
|
|
12
|
+
|
|
13
|
+
/** Strip the `agent-` prefix so structured event `agent` field matches heartbeat.name. */
|
|
14
|
+
function bareAgentName(): string {
|
|
15
|
+
const full = selfDisplayName();
|
|
16
|
+
return full.startsWith("agent-") ? full.slice(6) : full;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function selfOwnerId(): string | undefined {
|
|
20
|
+
return resolveOwner() ?? undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* `harn session`: run a command (or narrate) and emit canonical coordination
|
|
25
|
+
* events for follow-along.
|
|
26
|
+
*
|
|
27
|
+
* Follow-along happens through the canonical `.harnery/events.ndjson` stream +
|
|
28
|
+
* the `/live` web viewer (or `harn agents watch`): `harn session -- <cmd>` runs
|
|
29
|
+
* the command, forwards its output to the terminal verbatim, and emits canonical
|
|
30
|
+
* `command.start/output/end`; `harn session log` emits a canonical `narration`
|
|
31
|
+
* event. The file-management subcommands (`tail`/`clear`/`trim`/`path`) are
|
|
32
|
+
* retired no-op stubs; `trim` is a silent no-op so a SessionStart hook that
|
|
33
|
+
* calls it keeps working.
|
|
34
|
+
*
|
|
35
|
+
* Usage:
|
|
36
|
+
* harn session "<intent>" -- <cmd> [args...] run command, emit command.* events
|
|
37
|
+
* harn session log "<message>" emit a narration event
|
|
38
|
+
*/
|
|
39
|
+
let emit: EmitContext;
|
|
40
|
+
|
|
41
|
+
export function registerSessionCommand(program: Command, emitParam: EmitContext): void {
|
|
42
|
+
emit = emitParam;
|
|
43
|
+
const session = program
|
|
44
|
+
.command("session")
|
|
45
|
+
.description("Run a command (or narrate) and emit canonical coordination events");
|
|
46
|
+
|
|
47
|
+
// Narration-only entry → canonical narration event.
|
|
48
|
+
session
|
|
49
|
+
.command("log <message...>")
|
|
50
|
+
.description("Emit a narration event (no command run)")
|
|
51
|
+
.action((messageParts: string[]) => {
|
|
52
|
+
const message = messageParts.join(" ");
|
|
53
|
+
writeSessionEvent("narration", bareAgentName(), {
|
|
54
|
+
instance_id: selfOwnerId(),
|
|
55
|
+
message: clampField(message),
|
|
56
|
+
});
|
|
57
|
+
emit.text(`⋯ ${message}\n`);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Retired file-management subcommands, kept as graceful no-op stubs so
|
|
61
|
+
// existing callers (hooks, muscle memory) don't error. `trim` is the one a
|
|
62
|
+
// hook invokes, so it must exit 0 silently.
|
|
63
|
+
session
|
|
64
|
+
.command("trim")
|
|
65
|
+
.description("(retired): no-op kept for hook compatibility")
|
|
66
|
+
.option("-y, --yes", "(ignored)")
|
|
67
|
+
.option("--max-entries <n>", "(ignored)")
|
|
68
|
+
.option("--max-bytes <size>", "(ignored)")
|
|
69
|
+
.allowUnknownOption()
|
|
70
|
+
.allowExcessArguments()
|
|
71
|
+
.action(() => {
|
|
72
|
+
/* silent no-op, exit 0 */
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
for (const name of ["tail", "clear", "path"] as const) {
|
|
76
|
+
session
|
|
77
|
+
.command(name)
|
|
78
|
+
.description(`(retired): watch /live or '${resolveBinName()} agents watch'`)
|
|
79
|
+
.allowUnknownOption()
|
|
80
|
+
.action(() => {
|
|
81
|
+
emit.text(
|
|
82
|
+
`Retired. Live activity flows to .harnery/events.ndjson; watch the /live web viewer or run '${resolveBinName()} agents watch'.\n`,
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Default form: harn session "<intent>" -- <cmd> [args]
|
|
88
|
+
session
|
|
89
|
+
.argument("<intent>", "One-line description of what this action is for")
|
|
90
|
+
.argument("<cmd...>", "The command to run (prefix with -- if it has its own flags)")
|
|
91
|
+
.allowUnknownOption()
|
|
92
|
+
.action((intent: string, cmd: string[]) => {
|
|
93
|
+
runLogged(intent, cmd);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
// Core
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Strip ONLY carriage-return redraw noise (progress bars like `docker pull`'s
|
|
103
|
+
* sticky line) from a chunk before splitting into event lines. Terminal
|
|
104
|
+
* forwarding keeps the raw bytes; this only cleans the canonical event copy.
|
|
105
|
+
*/
|
|
106
|
+
function stripCRNoise(text: string): string {
|
|
107
|
+
return text.replace(/[^\n]*\r(?!\n)/g, "");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function runLogged(intent: string, cmdArg: string[]): void {
|
|
111
|
+
// Strip a leading `--` separator if commander left one in.
|
|
112
|
+
const cmd = cmdArg[0] === "--" ? cmdArg.slice(1) : cmdArg;
|
|
113
|
+
if (cmd.length === 0) {
|
|
114
|
+
emit.error({ code: "usage", message: "Usage: harn session <intent> -- <cmd> [args...]" });
|
|
115
|
+
process.exit(2);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const cmdLine = cmd.map(quoteArg).join(" ");
|
|
119
|
+
const agent = bareAgentName();
|
|
120
|
+
const instanceId = selfOwnerId();
|
|
121
|
+
const cmdId = newCmdId();
|
|
122
|
+
|
|
123
|
+
// Intent precedence: explicit arg wins; else the PreToolUse-stamped intent
|
|
124
|
+
// file; else the command line itself.
|
|
125
|
+
const resolvedIntent =
|
|
126
|
+
intent && intent.trim().length > 0 ? intent : (readLastIntent(instanceId) ?? cmdLine);
|
|
127
|
+
writeSessionEvent("command_start", agent, {
|
|
128
|
+
instance_id: instanceId,
|
|
129
|
+
cmd_id: cmdId,
|
|
130
|
+
intent: clampField(resolvedIntent),
|
|
131
|
+
cmd: clampField(cmdLine),
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const start = Date.now();
|
|
135
|
+
const child = spawn(cmd[0]!, cmd.slice(1), {
|
|
136
|
+
stdio: ["inherit", "pipe", "pipe"],
|
|
137
|
+
env: process.env,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
child.stdout?.on("data", (chunk: Buffer) => {
|
|
141
|
+
process.stdout.write(chunk); // lint-ok-emission: harn session forwards child stdout verbatim; this IS the command's primary purpose
|
|
142
|
+
for (const line of stripCRNoise(chunk.toString("utf8")).split("\n")) {
|
|
143
|
+
if (line.length === 0) continue;
|
|
144
|
+
writeSessionEvent("output", agent, {
|
|
145
|
+
instance_id: instanceId,
|
|
146
|
+
cmd_id: cmdId,
|
|
147
|
+
stream: "stdout",
|
|
148
|
+
line: clampField(line),
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
child.stderr?.on("data", (chunk: Buffer) => {
|
|
153
|
+
process.stderr.write(chunk); // lint-ok-emission: harn session forwards child stderr verbatim; this IS the command's primary purpose
|
|
154
|
+
for (const line of stripCRNoise(chunk.toString("utf8")).split("\n")) {
|
|
155
|
+
if (line.length === 0) continue;
|
|
156
|
+
writeSessionEvent("output", agent, {
|
|
157
|
+
instance_id: instanceId,
|
|
158
|
+
cmd_id: cmdId,
|
|
159
|
+
stream: "stderr",
|
|
160
|
+
line: clampField(line),
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
child.on("error", (err) => {
|
|
166
|
+
process.stderr.write(`✗ spawn failed: ${err.message}\n`);
|
|
167
|
+
process.exit(127);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
child.on("close", (code, signal) => {
|
|
171
|
+
const durationMs = Date.now() - start;
|
|
172
|
+
writeSessionEvent("command_end", agent, {
|
|
173
|
+
instance_id: instanceId,
|
|
174
|
+
cmd_id: cmdId,
|
|
175
|
+
exit: code ?? null,
|
|
176
|
+
signal: signal ?? null,
|
|
177
|
+
duration_ms: durationMs,
|
|
178
|
+
});
|
|
179
|
+
process.exit(code ?? 1);
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Shell-safe quote an argument for human-readable logging (not for eval). */
|
|
184
|
+
function quoteArg(arg: string): string {
|
|
185
|
+
if (/^[A-Za-z0-9_@%+=:,./-]+$/.test(arg)) return arg;
|
|
186
|
+
return `'${arg.replace(/'/g, `'\\''`)}'`;
|
|
187
|
+
}
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `harn sync`: keep a curated subset of `.harnery/` live across machines
|
|
3
|
+
* via rclone (typically a Google Drive remote, but any rclone remote works).
|
|
4
|
+
*
|
|
5
|
+
* Scope: a deliberately small set of file types: durable identities,
|
|
6
|
+
* archived scratchpads, council manifests. The high-churn machine-local
|
|
7
|
+
* stuff (events.ndjson, .pid-map/, active/, .last-intent.*) stays put.
|
|
8
|
+
*
|
|
9
|
+
* Subcommands:
|
|
10
|
+
* init: interactive: `rclone config` walks through Google OAuth, then
|
|
11
|
+
* we record the chosen remote name in ~/.config/harnery/sync.json
|
|
12
|
+
* status: diff local vs remote (rclone check --one-way)
|
|
13
|
+
* push: local to remote (rclone copy)
|
|
14
|
+
* pull: remote to local (rclone copy)
|
|
15
|
+
* list: show what's in the remote
|
|
16
|
+
*
|
|
17
|
+
* Power users override via env (HARNERY_SYNC_REMOTE, HARNERY_SYNC_PREFIX) or by
|
|
18
|
+
* editing the config file. Like `harn backup`, this surfaces rclone rather
|
|
19
|
+
* than abstracting it.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { spawnSync } from "node:child_process";
|
|
23
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
24
|
+
import os from "node:os";
|
|
25
|
+
import path from "node:path";
|
|
26
|
+
import type { Command } from "commander";
|
|
27
|
+
import type { EmitContext } from "../commander.ts";
|
|
28
|
+
|
|
29
|
+
// What we sync. Anything matching one of these (relative to .harnery/) is
|
|
30
|
+
// considered part of the sync set. Everything else stays local-only.
|
|
31
|
+
const SYNC_PATHS = ["identities/", "scratch/archived/", "councils/"];
|
|
32
|
+
|
|
33
|
+
interface SyncConfig {
|
|
34
|
+
remote: string;
|
|
35
|
+
prefix: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function configPath(): string {
|
|
39
|
+
return path.join(os.homedir(), ".config", "harnery", "sync.json");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function loadConfig(): SyncConfig | null {
|
|
43
|
+
const envRemote = process.env.HARNERY_SYNC_REMOTE;
|
|
44
|
+
const envPrefix = process.env.HARNERY_SYNC_PREFIX ?? "harnery";
|
|
45
|
+
if (envRemote) return { remote: envRemote, prefix: envPrefix };
|
|
46
|
+
const p = configPath();
|
|
47
|
+
if (!existsSync(p)) return null;
|
|
48
|
+
try {
|
|
49
|
+
const parsed = JSON.parse(readFileSync(p, "utf-8")) as SyncConfig;
|
|
50
|
+
if (typeof parsed.remote === "string" && typeof parsed.prefix === "string") {
|
|
51
|
+
return parsed;
|
|
52
|
+
}
|
|
53
|
+
} catch {
|
|
54
|
+
/* fall through */
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function saveConfig(cfg: SyncConfig): void {
|
|
60
|
+
const p = configPath();
|
|
61
|
+
mkdirSync(path.dirname(p), { recursive: true });
|
|
62
|
+
writeFileSync(p, `${JSON.stringify(cfg, null, 2)}\n`, "utf-8");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function findHarneryDir(): string | null {
|
|
66
|
+
let dir = process.cwd();
|
|
67
|
+
for (let i = 0; i < 8; i++) {
|
|
68
|
+
if (existsSync(path.join(dir, ".harnery"))) {
|
|
69
|
+
return path.join(dir, ".harnery");
|
|
70
|
+
}
|
|
71
|
+
const parent = path.dirname(dir);
|
|
72
|
+
if (parent === dir) break;
|
|
73
|
+
dir = parent;
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function checkRclone(emit: EmitContext): boolean {
|
|
79
|
+
const r = spawnSync("rclone", ["version"], { encoding: "utf-8" });
|
|
80
|
+
if (r.status !== 0) {
|
|
81
|
+
emit.error({
|
|
82
|
+
code: "rclone_missing",
|
|
83
|
+
message: "rclone is not on PATH",
|
|
84
|
+
hint:
|
|
85
|
+
"Install via `curl https://rclone.org/install.sh | sudo bash` or " +
|
|
86
|
+
"https://rclone.org/downloads/",
|
|
87
|
+
});
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function listRemotes(): string[] {
|
|
94
|
+
const r = spawnSync("rclone", ["listremotes"], { encoding: "utf-8" });
|
|
95
|
+
if (r.status !== 0) return [];
|
|
96
|
+
return r.stdout
|
|
97
|
+
.split("\n")
|
|
98
|
+
.map((s) => s.trim().replace(/:$/, ""))
|
|
99
|
+
.filter(Boolean);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function ensureConfig(emit: EmitContext): SyncConfig | null {
|
|
103
|
+
const cfg = loadConfig();
|
|
104
|
+
if (cfg) return cfg;
|
|
105
|
+
emit.error({
|
|
106
|
+
code: "not_initialized",
|
|
107
|
+
message: "no harn sync config",
|
|
108
|
+
hint: "Run `harn sync init` to pick an rclone remote and record it.",
|
|
109
|
+
});
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function buildRcloneArgs(
|
|
114
|
+
cfg: SyncConfig,
|
|
115
|
+
harneryDir: string,
|
|
116
|
+
direction: "push" | "pull",
|
|
117
|
+
): {
|
|
118
|
+
local: string;
|
|
119
|
+
remote: string;
|
|
120
|
+
} {
|
|
121
|
+
const local = harneryDir;
|
|
122
|
+
const remote = `${cfg.remote}:${cfg.prefix}`;
|
|
123
|
+
return direction === "push" ? { local, remote } : { local, remote };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
interface FilterOpts {
|
|
127
|
+
dryRun?: boolean;
|
|
128
|
+
verbose?: boolean;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function syncIncludeFlags(): string[] {
|
|
132
|
+
const out: string[] = [];
|
|
133
|
+
for (const p of SYNC_PATHS) {
|
|
134
|
+
out.push("--include", `/${p}**`);
|
|
135
|
+
}
|
|
136
|
+
return out;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function registerSyncCommand(program: Command, emit: EmitContext): void {
|
|
140
|
+
const sync = program
|
|
141
|
+
.command("sync")
|
|
142
|
+
.description(
|
|
143
|
+
"Cross-machine sync of curated .harnery/ subset (identities, archived " +
|
|
144
|
+
"scratchpads, council manifests) via rclone. Google Drive is the " +
|
|
145
|
+
"expected remote; any rclone backend works.",
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
sync
|
|
149
|
+
.command("init")
|
|
150
|
+
.description(
|
|
151
|
+
"Pick the rclone remote to sync against. Run `rclone config` first " +
|
|
152
|
+
"if you haven't set up a remote yet (harnery doesn't wrap the OAuth " +
|
|
153
|
+
"flow; rclone's own config wizard handles that).",
|
|
154
|
+
)
|
|
155
|
+
.option("--remote <name>", "Use this rclone remote (skips the picker)")
|
|
156
|
+
.option("--prefix <path>", "Subpath under the remote root (default: harnery)", "harnery")
|
|
157
|
+
.action((opts: { remote?: string; prefix: string }) => {
|
|
158
|
+
if (!checkRclone(emit)) {
|
|
159
|
+
emit.setExitCode(1);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const remotes = listRemotes();
|
|
163
|
+
if (remotes.length === 0) {
|
|
164
|
+
emit.error({
|
|
165
|
+
code: "no_remotes",
|
|
166
|
+
message: "rclone has no remotes configured",
|
|
167
|
+
hint: "Run `rclone config` to add one (Google Drive: choose `drive`).",
|
|
168
|
+
});
|
|
169
|
+
emit.setExitCode(1);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
let remote = opts.remote;
|
|
174
|
+
if (!remote) {
|
|
175
|
+
if (remotes.length === 1) {
|
|
176
|
+
remote = remotes[0];
|
|
177
|
+
} else {
|
|
178
|
+
emit.error({
|
|
179
|
+
code: "remote_required",
|
|
180
|
+
message: `multiple rclone remotes; pass --remote <name>; available: ${remotes.join(", ")}`,
|
|
181
|
+
});
|
|
182
|
+
emit.setExitCode(1);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (!remotes.includes(remote)) {
|
|
187
|
+
emit.error({
|
|
188
|
+
code: "unknown_remote",
|
|
189
|
+
message: `remote "${remote}" not in rclone config (have: ${remotes.join(", ")})`,
|
|
190
|
+
});
|
|
191
|
+
emit.setExitCode(1);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
saveConfig({ remote, prefix: opts.prefix });
|
|
196
|
+
emit.text(`recorded: remote=${remote} prefix=${opts.prefix}`);
|
|
197
|
+
emit.text(`config: ${configPath()}`);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
sync
|
|
201
|
+
.command("status")
|
|
202
|
+
.description("Diff local .harnery/ subset vs the remote (rclone check --one-way).")
|
|
203
|
+
.option("--dry-run", "Same as no-op for status; kept for symmetry with push/pull")
|
|
204
|
+
.option("--verbose", "Pass -v to rclone")
|
|
205
|
+
.action((opts: FilterOpts) => {
|
|
206
|
+
if (!checkRclone(emit)) {
|
|
207
|
+
emit.setExitCode(1);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const cfg = ensureConfig(emit);
|
|
211
|
+
if (!cfg) {
|
|
212
|
+
emit.setExitCode(1);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const hDir = findHarneryDir();
|
|
216
|
+
if (!hDir) {
|
|
217
|
+
emit.error({ code: "no_harnery", message: "no .harnery/ found above cwd" });
|
|
218
|
+
emit.setExitCode(1);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
const { local, remote } = buildRcloneArgs(cfg, hDir, "push");
|
|
222
|
+
const args = ["check", local, remote, "--one-way", ...syncIncludeFlags()];
|
|
223
|
+
if (opts.verbose) args.push("-v");
|
|
224
|
+
const r = spawnSync("rclone", args, { stdio: "inherit" });
|
|
225
|
+
emit.setExitCode(r.status ?? 1);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
sync
|
|
229
|
+
.command("push")
|
|
230
|
+
.description("Push local .harnery/ subset to the remote (rclone copy).")
|
|
231
|
+
.option("--dry-run", "Pass --dry-run to rclone")
|
|
232
|
+
.option("--verbose", "Pass -v to rclone")
|
|
233
|
+
.action((opts: FilterOpts) => {
|
|
234
|
+
if (!checkRclone(emit)) {
|
|
235
|
+
emit.setExitCode(1);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const cfg = ensureConfig(emit);
|
|
239
|
+
if (!cfg) {
|
|
240
|
+
emit.setExitCode(1);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const hDir = findHarneryDir();
|
|
244
|
+
if (!hDir) {
|
|
245
|
+
emit.error({ code: "no_harnery", message: "no .harnery/ found above cwd" });
|
|
246
|
+
emit.setExitCode(1);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
const { local, remote } = buildRcloneArgs(cfg, hDir, "push");
|
|
250
|
+
const args = ["copy", local, remote, ...syncIncludeFlags()];
|
|
251
|
+
if (opts.dryRun) args.push("--dry-run");
|
|
252
|
+
if (opts.verbose) args.push("-v");
|
|
253
|
+
const r = spawnSync("rclone", args, { stdio: "inherit" });
|
|
254
|
+
emit.setExitCode(r.status ?? 1);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
sync
|
|
258
|
+
.command("pull")
|
|
259
|
+
.description("Pull the remote subset down into local .harnery/ (rclone copy).")
|
|
260
|
+
.option("--dry-run", "Pass --dry-run to rclone")
|
|
261
|
+
.option("--verbose", "Pass -v to rclone")
|
|
262
|
+
.action((opts: FilterOpts) => {
|
|
263
|
+
if (!checkRclone(emit)) {
|
|
264
|
+
emit.setExitCode(1);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
const cfg = ensureConfig(emit);
|
|
268
|
+
if (!cfg) {
|
|
269
|
+
emit.setExitCode(1);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
const hDir = findHarneryDir();
|
|
273
|
+
if (!hDir) {
|
|
274
|
+
emit.error({ code: "no_harnery", message: "no .harnery/ found above cwd" });
|
|
275
|
+
emit.setExitCode(1);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
const { local, remote } = buildRcloneArgs(cfg, hDir, "pull");
|
|
279
|
+
const args = ["copy", remote, local, ...syncIncludeFlags()];
|
|
280
|
+
if (opts.dryRun) args.push("--dry-run");
|
|
281
|
+
if (opts.verbose) args.push("-v");
|
|
282
|
+
const r = spawnSync("rclone", args, { stdio: "inherit" });
|
|
283
|
+
emit.setExitCode(r.status ?? 1);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
sync
|
|
287
|
+
.command("list")
|
|
288
|
+
.description("List what's currently in the remote (rclone lsf).")
|
|
289
|
+
.option("--depth <n>", "Max recursion depth", "4")
|
|
290
|
+
.action((opts: { depth: string }) => {
|
|
291
|
+
if (!checkRclone(emit)) {
|
|
292
|
+
emit.setExitCode(1);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
const cfg = ensureConfig(emit);
|
|
296
|
+
if (!cfg) {
|
|
297
|
+
emit.setExitCode(1);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
const remote = `${cfg.remote}:${cfg.prefix}`;
|
|
301
|
+
const r = spawnSync("rclone", ["lsf", remote, "-R", "--max-depth", opts.depth], {
|
|
302
|
+
stdio: "inherit",
|
|
303
|
+
});
|
|
304
|
+
emit.setExitCode(r.status ?? 1);
|
|
305
|
+
});
|
|
306
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import type { Command } from "commander";
|
|
4
|
+
import type { EmitContext } from "../commander.ts";
|
|
5
|
+
import { resolveBinName } from "../core/config.ts";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* `harn toc <file.md>`: markdown header outline (level + text + line number).
|
|
9
|
+
* `harn section <file.md> <header>`: extract one section by header substring.
|
|
10
|
+
*
|
|
11
|
+
* Companion to `harn outline` for code files. Both let you navigate long docs
|
|
12
|
+
* without reading the whole thing.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
interface TocOpts {
|
|
16
|
+
json?: boolean;
|
|
17
|
+
depth?: string;
|
|
18
|
+
withSizes?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface SectionOpts {
|
|
22
|
+
json?: boolean;
|
|
23
|
+
caseSensitive?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface Header {
|
|
27
|
+
level: number;
|
|
28
|
+
text: string;
|
|
29
|
+
line: number;
|
|
30
|
+
size_lines?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface TocResult {
|
|
34
|
+
file: string;
|
|
35
|
+
total_lines: number;
|
|
36
|
+
headers: Header[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface SectionResult {
|
|
40
|
+
file: string;
|
|
41
|
+
matched_header: string;
|
|
42
|
+
matched_level: number;
|
|
43
|
+
line_start: number;
|
|
44
|
+
line_end: number;
|
|
45
|
+
content: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function registerTocCommand(program: Command, emit: EmitContext): void {
|
|
49
|
+
program
|
|
50
|
+
.command("toc <file>")
|
|
51
|
+
.description("Print the markdown header outline (level + text + line number).")
|
|
52
|
+
.option("--json", "Structured JSON envelope")
|
|
53
|
+
.option("-d, --depth <n>", "Maximum header depth (1-6)")
|
|
54
|
+
.option("--with-sizes", "Include line count per section")
|
|
55
|
+
.action(async (file: string, opts: TocOpts) => {
|
|
56
|
+
try {
|
|
57
|
+
const result = await runToc(file, opts);
|
|
58
|
+
if (opts.json) {
|
|
59
|
+
emit.config({ format: "json" });
|
|
60
|
+
emit.data(result);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
emit.text(`${renderToc(result)}\n`);
|
|
64
|
+
} catch (err) {
|
|
65
|
+
emit.error({ code: "toc_failed", message: (err as Error).message });
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function registerSectionCommand(program: Command, emit: EmitContext): void {
|
|
72
|
+
program
|
|
73
|
+
.command("section <file> <header...>")
|
|
74
|
+
.description(
|
|
75
|
+
"Extract one section from a markdown file by header substring (case-insensitive by default). " +
|
|
76
|
+
"Joins multi-word args into one query string.",
|
|
77
|
+
)
|
|
78
|
+
.option(
|
|
79
|
+
"--json",
|
|
80
|
+
"Structured JSON envelope: {file, matched_header, line_start, line_end, content}",
|
|
81
|
+
)
|
|
82
|
+
.option("--case-sensitive", "Case-sensitive header match")
|
|
83
|
+
.action(async (file: string, header: string[], opts: SectionOpts) => {
|
|
84
|
+
try {
|
|
85
|
+
const result = await runSection(file, header.join(" "), opts);
|
|
86
|
+
if (opts.json) {
|
|
87
|
+
emit.config({ format: "json" });
|
|
88
|
+
emit.data(result);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
emit.text(`${result.content}\n`);
|
|
92
|
+
} catch (err) {
|
|
93
|
+
emit.error({ code: "section_failed", message: (err as Error).message });
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function runToc(file: string, opts: TocOpts): Promise<TocResult> {
|
|
100
|
+
const absPath = resolve(file);
|
|
101
|
+
if (!existsSync(absPath)) throw new Error(`no such file: ${file}`);
|
|
102
|
+
const content = readFileSync(absPath, "utf8");
|
|
103
|
+
const lines = content.split("\n");
|
|
104
|
+
const maxDepth = opts.depth ? Number.parseInt(opts.depth, 10) : 6;
|
|
105
|
+
if (!Number.isFinite(maxDepth) || maxDepth < 1 || maxDepth > 6) {
|
|
106
|
+
throw new Error(`--depth must be 1-6 (got ${opts.depth})`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const headers: Header[] = [];
|
|
110
|
+
let inFence = false;
|
|
111
|
+
let fenceMarker = "";
|
|
112
|
+
|
|
113
|
+
for (let i = 0; i < lines.length; i++) {
|
|
114
|
+
const line = lines[i];
|
|
115
|
+
const fenceMatch = line.match(/^(\s*)(`{3,}|~{3,})/);
|
|
116
|
+
if (fenceMatch) {
|
|
117
|
+
const marker = fenceMatch[2];
|
|
118
|
+
if (!inFence) {
|
|
119
|
+
inFence = true;
|
|
120
|
+
fenceMarker = marker[0];
|
|
121
|
+
} else if (marker[0] === fenceMarker) {
|
|
122
|
+
inFence = false;
|
|
123
|
+
}
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (inFence) continue;
|
|
127
|
+
|
|
128
|
+
const headerMatch = line.match(/^(#{1,6})\s+(.+?)\s*#*\s*$/);
|
|
129
|
+
if (headerMatch) {
|
|
130
|
+
const level = headerMatch[1].length;
|
|
131
|
+
if (level > maxDepth) continue;
|
|
132
|
+
headers.push({
|
|
133
|
+
level,
|
|
134
|
+
text: headerMatch[2],
|
|
135
|
+
line: i + 1,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (opts.withSizes) {
|
|
141
|
+
for (let i = 0; i < headers.length; i++) {
|
|
142
|
+
const nextLine = i + 1 < headers.length ? headers[i + 1].line : lines.length + 1;
|
|
143
|
+
headers[i].size_lines = nextLine - headers[i].line;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
file,
|
|
149
|
+
total_lines: lines.length,
|
|
150
|
+
headers,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function renderToc(r: TocResult): string {
|
|
155
|
+
const lines: string[] = [];
|
|
156
|
+
lines.push(
|
|
157
|
+
`toc · ${r.file} (${r.headers.length} header${r.headers.length === 1 ? "" : "s"}, ${r.total_lines} lines)`,
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
if (r.headers.length === 0) {
|
|
161
|
+
lines.push("(no markdown headers found)");
|
|
162
|
+
return lines.join("\n");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
lines.push("");
|
|
166
|
+
for (const h of r.headers) {
|
|
167
|
+
const indent = " ".repeat(h.level - 1);
|
|
168
|
+
const lineMark = `L${h.line}`.padEnd(6);
|
|
169
|
+
const size = h.size_lines !== undefined ? ` (${h.size_lines}L)` : "";
|
|
170
|
+
lines.push(` ${lineMark}${indent}${"#".repeat(h.level)} ${h.text}${size}`);
|
|
171
|
+
}
|
|
172
|
+
return lines.join("\n");
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async function runSection(file: string, query: string, opts: SectionOpts): Promise<SectionResult> {
|
|
176
|
+
if (!query.trim()) throw new Error("header query required");
|
|
177
|
+
const tocResult = await runToc(file, {});
|
|
178
|
+
const absPath = resolve(file);
|
|
179
|
+
const lines = readFileSync(absPath, "utf8").split("\n");
|
|
180
|
+
|
|
181
|
+
const matches = tocResult.headers.filter((h) =>
|
|
182
|
+
opts.caseSensitive
|
|
183
|
+
? h.text.includes(query)
|
|
184
|
+
: h.text.toLowerCase().includes(query.toLowerCase()),
|
|
185
|
+
);
|
|
186
|
+
if (matches.length === 0) {
|
|
187
|
+
throw new Error(
|
|
188
|
+
`no header matching "${query}". Run \`${resolveBinName()} toc ${file}\` to list available headers.`,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
if (matches.length > 1) {
|
|
192
|
+
const list = matches.map((h) => ` L${h.line} ${"#".repeat(h.level)} ${h.text}`).join("\n");
|
|
193
|
+
throw new Error(
|
|
194
|
+
`multiple headers match "${query}":\n${list}\n\nRefine the query or use exact text.`,
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const matched = matches[0];
|
|
199
|
+
const idx = tocResult.headers.findIndex((h) => h.line === matched.line);
|
|
200
|
+
let endLine = lines.length;
|
|
201
|
+
for (let i = idx + 1; i < tocResult.headers.length; i++) {
|
|
202
|
+
if (tocResult.headers[i].level <= matched.level) {
|
|
203
|
+
endLine = tocResult.headers[i].line - 1;
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const content = lines.slice(matched.line - 1, endLine).join("\n");
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
file,
|
|
212
|
+
matched_header: matched.text,
|
|
213
|
+
matched_level: matched.level,
|
|
214
|
+
line_start: matched.line,
|
|
215
|
+
line_end: endLine,
|
|
216
|
+
content,
|
|
217
|
+
};
|
|
218
|
+
}
|