harnery 0.8.0 → 0.10.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/README.md +2 -0
- package/dist/commander.d.ts.map +1 -1
- package/dist/commander.js +8 -0
- package/dist/commands/agents.d.ts +24 -0
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +244 -13
- package/dist/commands/backup.d.ts +5 -4
- package/dist/commands/backup.d.ts.map +1 -1
- package/dist/commands/backup.js +15 -14
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +27 -1
- package/dist/commands/claude-desktop.d.ts +19 -0
- package/dist/commands/claude-desktop.d.ts.map +1 -0
- package/dist/commands/claude-desktop.js +168 -0
- package/dist/commands/context.d.ts.map +1 -1
- package/dist/commands/context.js +149 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +37 -0
- package/dist/commands/eml.d.ts +32 -0
- package/dist/commands/eml.d.ts.map +1 -1
- package/dist/commands/eml.js +16 -3
- package/dist/commands/grep.d.ts +35 -2
- package/dist/commands/grep.d.ts.map +1 -1
- package/dist/commands/grep.js +427 -139
- package/dist/commands/harness.d.ts +11 -0
- package/dist/commands/harness.d.ts.map +1 -0
- package/dist/commands/harness.js +118 -0
- package/dist/commands/init.d.ts +15 -5
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +125 -14
- package/dist/commands/presence.d.ts +9 -4
- package/dist/commands/presence.d.ts.map +1 -1
- package/dist/commands/presence.js +88 -5
- package/dist/commands/relay.d.ts +9 -0
- package/dist/commands/relay.d.ts.map +1 -0
- package/dist/commands/relay.js +143 -0
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +5 -0
- package/dist/commands/workflow.d.ts +4 -0
- package/dist/commands/workflow.d.ts.map +1 -0
- package/dist/commands/workflow.js +82 -0
- package/dist/core/agents/canonical-emit.d.ts +15 -0
- package/dist/core/agents/canonical-emit.d.ts.map +1 -1
- package/dist/core/agents/canonical-emit.js +41 -3
- package/dist/core/agents/cli.js +14 -5
- package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
- package/dist/core/agents/render/prompt-context.js +44 -3
- package/dist/core/agents/render/session-context.d.ts.map +1 -1
- package/dist/core/agents/render/session-context.js +26 -1
- package/dist/core/agents/rules/claim-conflict.d.ts.map +1 -1
- package/dist/core/agents/rules/claim-conflict.js +26 -1
- package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -1
- package/dist/core/agents/rules/commit-conflict.js +4 -37
- package/dist/core/agents/rules/stop-hook.d.ts +8 -0
- package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
- package/dist/core/agents/rules/stop-hook.js +8 -0
- package/dist/core/agents/session-events.d.ts.map +1 -1
- package/dist/core/agents/session-events.js +16 -43
- package/dist/core/agents/state/heartbeat-projector.d.ts +2 -0
- package/dist/core/agents/state/heartbeat-projector.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-projector.js +24 -4
- package/dist/core/agents/state/heartbeat-writer.d.ts +16 -2
- package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-writer.js +24 -7
- package/dist/core/agents/state/names.d.ts +38 -3
- package/dist/core/agents/state/names.d.ts.map +1 -1
- package/dist/core/agents/state/names.js +46 -7
- package/dist/core/agents/state/pidmap.d.ts +5 -1
- package/dist/core/agents/state/pidmap.d.ts.map +1 -1
- package/dist/core/agents/state/pidmap.js +58 -2
- package/dist/core/agents/state/stale-sweep.d.ts +3 -2
- package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
- package/dist/core/agents/state/stale-sweep.js +6 -6
- package/dist/core/config.d.ts +77 -11
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +210 -25
- package/dist/core/context/index.d.ts +144 -0
- package/dist/core/context/index.d.ts.map +1 -0
- package/dist/core/context/index.js +380 -0
- package/dist/core/harnesses/bench.d.ts +29 -0
- package/dist/core/harnesses/bench.d.ts.map +1 -0
- package/dist/core/harnesses/bench.js +151 -0
- package/dist/core/harnesses/index.d.ts +9 -0
- package/dist/core/harnesses/index.d.ts.map +1 -0
- package/dist/core/harnesses/index.js +4 -0
- package/dist/core/harnesses/profiles.d.ts +62 -0
- package/dist/core/harnesses/profiles.d.ts.map +1 -0
- package/dist/core/harnesses/profiles.js +115 -0
- package/dist/core/harnesses/registry.d.ts +15 -0
- package/dist/core/harnesses/registry.d.ts.map +1 -0
- package/dist/core/harnesses/registry.js +113 -0
- package/dist/core/harnesses/types.d.ts +64 -0
- package/dist/core/harnesses/types.d.ts.map +1 -0
- package/dist/core/harnesses/types.js +20 -0
- package/dist/core/hooks/cli.js +309 -66
- package/dist/core/hooks/events/schema.d.ts +42 -1
- package/dist/core/hooks/events/schema.d.ts.map +1 -1
- package/dist/core/hooks/harness/events.d.ts +7 -0
- package/dist/core/hooks/harness/events.d.ts.map +1 -1
- package/dist/core/hooks/harness/events.js +4 -0
- package/dist/core/hooks/harness/parse.d.ts +1 -1
- package/dist/core/hooks/harness/parse.d.ts.map +1 -1
- package/dist/core/hooks/harness/parse.js +4 -0
- package/dist/core/hooks/resolve/coord-root.d.ts +11 -0
- package/dist/core/hooks/resolve/coord-root.d.ts.map +1 -1
- package/dist/core/hooks/resolve/coord-root.js +28 -6
- package/dist/core/presence/blob.d.ts +40 -0
- package/dist/core/presence/blob.d.ts.map +1 -0
- package/dist/core/presence/blob.js +91 -0
- package/dist/core/presence/git.d.ts +68 -0
- package/dist/core/presence/git.d.ts.map +1 -0
- package/dist/core/presence/git.js +173 -0
- package/dist/core/presence/index.d.ts +74 -0
- package/dist/core/presence/index.d.ts.map +1 -0
- package/dist/core/presence/index.js +224 -0
- package/dist/core/presence/relay-client.d.ts +39 -0
- package/dist/core/presence/relay-client.d.ts.map +1 -0
- package/dist/core/presence/relay-client.js +295 -0
- package/dist/core/presence/relay-protocol.d.ts +94 -0
- package/dist/core/presence/relay-protocol.d.ts.map +1 -0
- package/dist/core/presence/relay-protocol.js +169 -0
- package/dist/core/workflow/billing.d.ts +48 -0
- package/dist/core/workflow/billing.d.ts.map +1 -0
- package/dist/core/workflow/billing.js +108 -0
- package/dist/core/workflow/child-env.d.ts +30 -0
- package/dist/core/workflow/child-env.d.ts.map +1 -0
- package/dist/core/workflow/child-env.js +43 -0
- package/dist/core/workflow/engine.d.ts +21 -0
- package/dist/core/workflow/engine.d.ts.map +1 -0
- package/dist/core/workflow/engine.js +349 -0
- package/dist/core/workflow/harnesses.d.ts +17 -0
- package/dist/core/workflow/harnesses.d.ts.map +1 -0
- package/dist/core/workflow/harnesses.js +21 -0
- package/dist/core/workflow/spawn-claude.d.ts +25 -0
- package/dist/core/workflow/spawn-claude.d.ts.map +1 -0
- package/dist/core/workflow/spawn-claude.js +103 -0
- package/dist/core/workflow/spawn-codex.d.ts +22 -0
- package/dist/core/workflow/spawn-codex.d.ts.map +1 -0
- package/dist/core/workflow/spawn-codex.js +88 -0
- package/dist/core/workflow/spawn-cursor.d.ts +29 -0
- package/dist/core/workflow/spawn-cursor.d.ts.map +1 -0
- package/dist/core/workflow/spawn-cursor.js +91 -0
- package/dist/core/workflow/types.d.ts +152 -0
- package/dist/core/workflow/types.d.ts.map +1 -0
- package/dist/core/workflow/types.js +9 -0
- package/dist/core/workflow/validate.d.ts +15 -0
- package/dist/core/workflow/validate.d.ts.map +1 -0
- package/dist/core/workflow/validate.js +70 -0
- package/dist/lib/browser/client.d.ts +16 -0
- package/dist/lib/browser/client.d.ts.map +1 -1
- package/dist/lib/browser/client.js +22 -0
- package/dist/lib/browser/index.d.ts +1 -0
- package/dist/lib/browser/index.d.ts.map +1 -1
- package/dist/lib/browser/index.js +1 -0
- package/dist/lib/browser/launch-args.d.ts +21 -0
- package/dist/lib/browser/launch-args.d.ts.map +1 -0
- package/dist/lib/browser/launch-args.js +32 -0
- package/dist/lib/claude-desktop.d.ts +105 -0
- package/dist/lib/claude-desktop.d.ts.map +1 -0
- package/dist/lib/claude-desktop.js +217 -0
- package/dist/lib/docs-lint.d.ts.map +1 -1
- package/dist/lib/docs-lint.js +5 -2
- package/dist/lib/identities/assume.d.ts +51 -0
- package/dist/lib/identities/assume.d.ts.map +1 -0
- package/dist/lib/identities/assume.js +274 -0
- package/dist/lib/identities/index.d.ts +7 -7
- package/dist/lib/identities/index.d.ts.map +1 -1
- package/dist/lib/identities/index.js +24 -24
- package/dist/lib/instructions/templates.d.ts.map +1 -1
- package/dist/lib/instructions/templates.js +15 -2
- package/package.json +12 -1
- package/schemas/config.schema.json +88 -19
- package/src/commander.ts +8 -0
- package/src/commands/agents.ts +280 -14
- package/src/commands/backup.ts +17 -16
- package/src/commands/browse.ts +32 -0
- package/src/commands/claude-desktop.ts +215 -0
- package/src/commands/context.ts +174 -1
- package/src/commands/doctor.ts +44 -0
- package/src/commands/eml.ts +22 -4
- package/src/commands/grep.ts +535 -142
- package/src/commands/harness.ts +147 -0
- package/src/commands/init.ts +138 -17
- package/src/commands/presence.ts +111 -5
- package/src/commands/relay.ts +160 -0
- package/src/commands/sync.ts +4 -0
- package/src/commands/workflow.ts +123 -0
- package/src/core/agents/canonical-emit.ts +41 -3
- package/src/core/agents/cli.ts +15 -6
- package/src/core/agents/render/prompt-context.ts +48 -3
- package/src/core/agents/render/session-context.ts +27 -1
- package/src/core/agents/rules/claim-conflict.ts +26 -1
- package/src/core/agents/rules/commit-conflict.ts +4 -33
- package/src/core/agents/rules/stop-hook.ts +17 -0
- package/src/core/agents/session-events.ts +15 -39
- package/src/core/agents/state/heartbeat-projector.ts +23 -2
- package/src/core/agents/state/heartbeat-writer.ts +33 -7
- package/src/core/agents/state/names.ts +68 -9
- package/src/core/agents/state/pidmap.ts +59 -2
- package/src/core/agents/state/stale-sweep.ts +6 -11
- package/src/core/config.ts +271 -24
- package/src/core/context/index.ts +575 -0
- package/src/core/harnesses/bench.ts +214 -0
- package/src/core/harnesses/index.ts +31 -0
- package/src/core/harnesses/profiles.ts +141 -0
- package/src/core/harnesses/registry.ts +140 -0
- package/src/core/harnesses/types.ts +90 -0
- package/src/core/hooks/cli.ts +361 -65
- package/src/core/hooks/events/schema.ts +72 -0
- package/src/core/hooks/harness/events.ts +11 -0
- package/src/core/hooks/harness/parse.ts +7 -1
- package/src/core/hooks/resolve/coord-root.ts +28 -6
- package/src/core/presence/blob.ts +125 -0
- package/src/core/presence/git.ts +191 -0
- package/src/core/presence/index.ts +274 -0
- package/src/core/presence/relay-client.ts +322 -0
- package/src/core/presence/relay-protocol.ts +246 -0
- package/src/core/workflow/billing.ts +152 -0
- package/src/core/workflow/child-env.ts +47 -0
- package/src/core/workflow/engine.ts +403 -0
- package/src/core/workflow/harnesses.ts +33 -0
- package/src/core/workflow/spawn-claude.ts +118 -0
- package/src/core/workflow/spawn-codex.ts +93 -0
- package/src/core/workflow/spawn-cursor.ts +108 -0
- package/src/core/workflow/types.ts +160 -0
- package/src/core/workflow/validate.ts +75 -0
- package/src/lib/browser/client.ts +37 -0
- package/src/lib/browser/index.ts +1 -0
- package/src/lib/browser/launch-args.ts +33 -0
- package/src/lib/claude-desktop.ts +293 -0
- package/src/lib/docs-lint.ts +5 -2
- package/src/lib/identities/assume.ts +363 -0
- package/src/lib/identities/index.ts +28 -24
- package/src/lib/instructions/templates.ts +16 -2
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import type { EmitContext } from "../commander.js";
|
|
3
|
+
import { type BenchResult, type HarnessBenchReport } from "../core/harnesses/index.js";
|
|
4
|
+
import type { HarnessProfile } from "../core/harnesses/types.js";
|
|
5
|
+
export declare function registerHarnessCommand(program: Command, emit: EmitContext): void;
|
|
6
|
+
export declare function renderProfileTable(profiles: readonly HarnessProfile[]): string;
|
|
7
|
+
export declare function renderProfile(profile: HarnessProfile): string;
|
|
8
|
+
export declare function renderBenchReport(report: HarnessBenchReport): string;
|
|
9
|
+
/** Kept exported so command tests can assert row semantics without model calls. */
|
|
10
|
+
export type { BenchResult };
|
|
11
|
+
//# sourceMappingURL=harness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"harness.d.ts","sourceRoot":"","sources":["../../src/commands/harness.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EACL,KAAK,WAAW,EAEhB,KAAK,kBAAkB,EAExB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAYjE,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CA6DhF;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,GAAG,MAAM,CAU9E;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM,CAkB7D;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAcpE;AAgBD,mFAAmF;AACnF,YAAY,EAAE,WAAW,EAAE,CAAC"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { createBuiltinHarnessRegistry, runHarnessBench, } from "../core/harnesses/index.js";
|
|
2
|
+
const registry = createBuiltinHarnessRegistry();
|
|
3
|
+
export function registerHarnessCommand(program, emit) {
|
|
4
|
+
const command = program
|
|
5
|
+
.command("harness")
|
|
6
|
+
.description("Inspect registered harness capabilities and run their conformance bench.");
|
|
7
|
+
command
|
|
8
|
+
.command("list")
|
|
9
|
+
.description("List registered harness adapters and their high-signal capability claims.")
|
|
10
|
+
.option("--json", "Machine-readable profile catalog")
|
|
11
|
+
.action((opts) => {
|
|
12
|
+
const profiles = registry.list().map((adapter) => adapter.profile);
|
|
13
|
+
if (opts.json) {
|
|
14
|
+
emit.config({ format: "json" });
|
|
15
|
+
emit.data({ harnesses: profiles });
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
emit.text(renderProfileTable(profiles));
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
command
|
|
22
|
+
.command("show <id>")
|
|
23
|
+
.description("Show one harness's complete capability declaration.")
|
|
24
|
+
.option("--json", "Machine-readable profile")
|
|
25
|
+
.action((id, opts) => {
|
|
26
|
+
const adapter = registry.get(id);
|
|
27
|
+
if (!adapter) {
|
|
28
|
+
emit.error({
|
|
29
|
+
code: "unknown_harness",
|
|
30
|
+
message: `unknown harness ${JSON.stringify(id)} (registered: ${registry.ids().join(", ")})`,
|
|
31
|
+
});
|
|
32
|
+
emit.setExitCode(1);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (opts.json) {
|
|
36
|
+
emit.config({ format: "json" });
|
|
37
|
+
emit.data(adapter.profile);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
emit.text(renderProfile(adapter.profile));
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
command
|
|
44
|
+
.command("bench [harnesses...]")
|
|
45
|
+
.description("Run the offline adapter-contract bench (no model calls); drift exits non-zero.")
|
|
46
|
+
.option("--require-installed", "Also fail when a registered vendor CLI is missing")
|
|
47
|
+
.option("--json", "Machine-readable conformance report")
|
|
48
|
+
.action((harnesses, opts) => {
|
|
49
|
+
try {
|
|
50
|
+
const report = runHarnessBench(registry, { harnesses });
|
|
51
|
+
if (opts.json) {
|
|
52
|
+
emit.config({ format: "json" });
|
|
53
|
+
emit.data(report);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
emit.text(renderBenchReport(report));
|
|
57
|
+
}
|
|
58
|
+
emit.setExitCode(report.drift || (opts.requireInstalled && report.skipped) ? 1 : 0);
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
emit.error({ code: "harness_bench_failed", message: error.message });
|
|
62
|
+
emit.setExitCode(1);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
export function renderProfileTable(profiles) {
|
|
67
|
+
const rows = profiles.map((profile) => [
|
|
68
|
+
profile.id,
|
|
69
|
+
profile.binary,
|
|
70
|
+
profile.capabilities.modelSelection.support,
|
|
71
|
+
profile.capabilities.effortSelection.support,
|
|
72
|
+
profile.capabilities.sessionId.support,
|
|
73
|
+
profile.capabilities.cost.support,
|
|
74
|
+
]);
|
|
75
|
+
return renderTable(["HARNESS", "BINARY", "MODEL", "EFFORT", "SESSION", "COST"], rows);
|
|
76
|
+
}
|
|
77
|
+
export function renderProfile(profile) {
|
|
78
|
+
const lines = [
|
|
79
|
+
`${profile.displayName} (${profile.id})`,
|
|
80
|
+
`binary: ${profile.binary}`,
|
|
81
|
+
`integration: ${profile.integrationMode}`,
|
|
82
|
+
`auth: ${profile.authModel}`,
|
|
83
|
+
`model family: ${profile.modelFamily}`,
|
|
84
|
+
`effort values: ${profile.effortValues.join(", ") || "none"}`,
|
|
85
|
+
profile.verified
|
|
86
|
+
? `verified: ${profile.verified.date} (${profile.verified.version})`
|
|
87
|
+
: "verified: not recorded",
|
|
88
|
+
"",
|
|
89
|
+
"capabilities:",
|
|
90
|
+
];
|
|
91
|
+
for (const [dimension, claim] of Object.entries(profile.capabilities)) {
|
|
92
|
+
lines.push(` ${dimension.padEnd(18)} ${claim.support}${claim.note ? ` — ${claim.note}` : ""}`);
|
|
93
|
+
}
|
|
94
|
+
return lines.join("\n");
|
|
95
|
+
}
|
|
96
|
+
export function renderBenchReport(report) {
|
|
97
|
+
const rows = report.results.map((result) => [
|
|
98
|
+
result.harness,
|
|
99
|
+
result.dimension,
|
|
100
|
+
result.declared,
|
|
101
|
+
result.observed,
|
|
102
|
+
result.verdict,
|
|
103
|
+
]);
|
|
104
|
+
const table = renderTable(["HARNESS", "DIMENSION", "DECLARED", "OBSERVED", "VERDICT"], rows);
|
|
105
|
+
const summary = Object.entries(report.summary)
|
|
106
|
+
.filter(([, count]) => count > 0)
|
|
107
|
+
.map(([verdict, count]) => `${verdict}=${count}`)
|
|
108
|
+
.join(", ");
|
|
109
|
+
return `${table}\n\nmode: ${report.mode} (no model calls)\n${summary}`;
|
|
110
|
+
}
|
|
111
|
+
function renderTable(headers, rows) {
|
|
112
|
+
const widths = headers.map((header, index) => Math.max(header.length, ...rows.map((row) => row[index]?.length ?? 0)));
|
|
113
|
+
const line = (cells) => cells
|
|
114
|
+
.map((cell, index) => cell.padEnd(widths[index]))
|
|
115
|
+
.join(" ")
|
|
116
|
+
.trimEnd();
|
|
117
|
+
return [line(headers), line(widths.map((width) => "-".repeat(width))), ...rows.map(line)].join("\n");
|
|
118
|
+
}
|
package/dist/commands/init.d.ts
CHANGED
|
@@ -23,11 +23,12 @@ export type { HookGroup, SettingsFile };
|
|
|
23
23
|
export declare function registerInitCommand(program: Command, emit: EmitContext, binName?: string): void;
|
|
24
24
|
/**
|
|
25
25
|
* Merge agent-hook entries into a harness settings object in place, idempotently.
|
|
26
|
-
* Preserves every existing hook;
|
|
27
|
-
* `agent-hook <subcommand
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
26
|
+
* Preserves every existing hook; rewrites events already wired to
|
|
27
|
+
* `agent-hook <subcommand>` when their command string is stale (e.g. an older
|
|
28
|
+
* harnery wired a bare relative path). Honors the harness's entry shape
|
|
29
|
+
* (Claude/Codex nest under an inner `hooks` array; Cursor uses a flat
|
|
30
|
+
* `{ command }`) and ensures the root `version` key when the harness requires
|
|
31
|
+
* one. Pure (no fs/git) so it's unit-testable.
|
|
31
32
|
*
|
|
32
33
|
* The trailing space in the match (`agent-hook ${subcommand} `) is load-bearing:
|
|
33
34
|
* it keeps `stop` from matching `stop-failure`.
|
|
@@ -36,6 +37,7 @@ export declare function wireHooks(settings: SettingsFile, spec: HarnessSpec, age
|
|
|
36
37
|
wired: number;
|
|
37
38
|
already: number;
|
|
38
39
|
removed: number;
|
|
40
|
+
upgraded: number;
|
|
39
41
|
};
|
|
40
42
|
/**
|
|
41
43
|
* Inverse of {@link wireHooks}: strip every harnery-owned hook entry from a
|
|
@@ -61,4 +63,12 @@ export declare function unwireHooks(settings: SettingsFile): {
|
|
|
61
63
|
* - key absent → splice it in as the first key (comment-safe).
|
|
62
64
|
*/
|
|
63
65
|
export declare function stampBinName(configPath: string, binName: string, dryRun: boolean): string | null;
|
|
66
|
+
/**
|
|
67
|
+
* Idempotently pin `workflow.subscriptionOnly: true` in `.harnery/config.jsonc`
|
|
68
|
+
* (see `workflowSubscriptionOnly()` in core/config.ts and the `workflow run`
|
|
69
|
+
* billing safeguards). Same comment-preserving discipline as `stampBinName`.
|
|
70
|
+
* A `workflow` key of ANY shape (including `subscriptionOnly: false`) is a
|
|
71
|
+
* deliberate project choice and is left alone.
|
|
72
|
+
*/
|
|
73
|
+
export declare function stampWorkflowDefaults(configPath: string, dryRun: boolean): string | null;
|
|
64
74
|
//# sourceMappingURL=init.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAEnD,OAAO,EAAiB,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAClG,OAAO,EAGL,KAAK,SAAS,EAEd,KAAK,YAAY,EAClB,MAAM,iCAAiC,CAAC;AAKzC,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC;AAYxC,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAEnD,OAAO,EAAiB,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAClG,OAAO,EAGL,KAAK,SAAS,EAEd,KAAK,YAAY,EAClB,MAAM,iCAAiC,CAAC;AAKzC,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC;AAYxC,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAiI/F;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CACvB,QAAQ,EAAE,YAAY,EACtB,IAAI,EAAE,WAAW,EACjB,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,SAAS,GACjB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAqCvE;AAyDD;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,YAAY,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAmB1F;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAmDhG;AASD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAkCxF"}
|
package/dist/commands/init.js
CHANGED
|
@@ -92,6 +92,17 @@ export function registerInitCommand(program, emit, binName) {
|
|
|
92
92
|
if (stamp)
|
|
93
93
|
actions.push(stamp);
|
|
94
94
|
}
|
|
95
|
+
// ── 1c. workflow billing default ───────────────────────────────────────
|
|
96
|
+
// Every init'd project gets `workflow.subscriptionOnly: true`: workflow
|
|
97
|
+
// children ride the logged-in (subscription) harness auth, and the pin
|
|
98
|
+
// makes per-token API billing structurally impossible unless the project
|
|
99
|
+
// deliberately flips it. A committed `workflow` key of any shape is a
|
|
100
|
+
// deliberate choice and is never touched.
|
|
101
|
+
{
|
|
102
|
+
const stamp = stampWorkflowDefaults(resolve(coordDir, "config.jsonc"), dryRun);
|
|
103
|
+
if (stamp)
|
|
104
|
+
actions.push(stamp);
|
|
105
|
+
}
|
|
95
106
|
// ── 2. harness hooks ───────────────────────────────────────────────────
|
|
96
107
|
const settingsPath = resolve(projectRoot, spec.settingsFile);
|
|
97
108
|
const agentHook = relative(projectRoot, resolve(HARNERY_ROOT, "bin", "agent-hook"));
|
|
@@ -110,19 +121,19 @@ export function registerInitCommand(program, emit, binName) {
|
|
|
110
121
|
else {
|
|
111
122
|
settings = {};
|
|
112
123
|
}
|
|
113
|
-
const { wired, already, removed } = wireHooks(settings, spec, agentHook, harness);
|
|
114
|
-
if (wired === 0 && removed === 0) {
|
|
124
|
+
const { wired, already, removed, upgraded } = wireHooks(settings, spec, agentHook, harness);
|
|
125
|
+
if (wired === 0 && removed === 0 && upgraded === 0) {
|
|
115
126
|
actions.push(`· all ${spec.events.length} ${harness} hooks already wired in ${rel(projectRoot, settingsPath)}`);
|
|
116
127
|
}
|
|
117
128
|
else if (dryRun) {
|
|
118
|
-
actions.push(`+ would wire ${wired} hook(s)
|
|
119
|
-
`${rel(projectRoot, settingsPath)} (${already} already present)`);
|
|
129
|
+
actions.push(`+ would wire ${wired} hook(s), upgrade ${upgraded} stale command(s), and remove ` +
|
|
130
|
+
`${removed} legacy hook(s) in ${rel(projectRoot, settingsPath)} (${already} already present)`);
|
|
120
131
|
}
|
|
121
132
|
else {
|
|
122
133
|
mkdirSync(dirname(settingsPath), { recursive: true });
|
|
123
134
|
writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`);
|
|
124
|
-
actions.push(`+ wired ${wired} hook(s)
|
|
125
|
-
`${rel(projectRoot, settingsPath)} (${already} already present)`);
|
|
135
|
+
actions.push(`+ wired ${wired} hook(s), upgraded ${upgraded} stale command(s), and removed ` +
|
|
136
|
+
`${removed} legacy hook(s) in ${rel(projectRoot, settingsPath)} (${already} already present)`);
|
|
126
137
|
}
|
|
127
138
|
// ── 3. agent-facing instructions block + skills ────────────────────────
|
|
128
139
|
const applied = applyInstructions(projectRoot, { binName: bin, harness, dryRun });
|
|
@@ -132,11 +143,12 @@ export function registerInitCommand(program, emit, binName) {
|
|
|
132
143
|
}
|
|
133
144
|
/**
|
|
134
145
|
* Merge agent-hook entries into a harness settings object in place, idempotently.
|
|
135
|
-
* Preserves every existing hook;
|
|
136
|
-
* `agent-hook <subcommand
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
146
|
+
* Preserves every existing hook; rewrites events already wired to
|
|
147
|
+
* `agent-hook <subcommand>` when their command string is stale (e.g. an older
|
|
148
|
+
* harnery wired a bare relative path). Honors the harness's entry shape
|
|
149
|
+
* (Claude/Codex nest under an inner `hooks` array; Cursor uses a flat
|
|
150
|
+
* `{ command }`) and ensures the root `version` key when the harness requires
|
|
151
|
+
* one. Pure (no fs/git) so it's unit-testable.
|
|
140
152
|
*
|
|
141
153
|
* The trailing space in the match (`agent-hook ${subcommand} `) is load-bearing:
|
|
142
154
|
* it keeps `stop` from matching `stop-failure`.
|
|
@@ -150,6 +162,7 @@ export function wireHooks(settings, spec, agentHookPath, harness) {
|
|
|
150
162
|
let wired = 0;
|
|
151
163
|
let already = 0;
|
|
152
164
|
let removed = 0;
|
|
165
|
+
let upgraded = 0;
|
|
153
166
|
for (const { settingsKey, subcommand } of spec.legacyEvents ?? []) {
|
|
154
167
|
const groups = settings.hooks[settingsKey] ?? [];
|
|
155
168
|
const kept = groups.filter((group) => !groupCommands(group).some((command) => commandWiresSubcommand(command, subcommand)));
|
|
@@ -160,9 +173,14 @@ export function wireHooks(settings, spec, agentHookPath, harness) {
|
|
|
160
173
|
settings.hooks[settingsKey] = kept;
|
|
161
174
|
}
|
|
162
175
|
for (const { settingsKey, subcommand } of spec.events) {
|
|
163
|
-
const command =
|
|
176
|
+
const command = hookCommand(spec, agentHookPath, subcommand, harness);
|
|
164
177
|
const groups = settings.hooks[settingsKey] ?? [];
|
|
165
|
-
|
|
178
|
+
let present = false;
|
|
179
|
+
for (const group of groups) {
|
|
180
|
+
upgraded += rewriteStaleCommands(group, subcommand, command, () => {
|
|
181
|
+
present = true;
|
|
182
|
+
});
|
|
183
|
+
}
|
|
166
184
|
if (present) {
|
|
167
185
|
already++;
|
|
168
186
|
continue;
|
|
@@ -171,7 +189,52 @@ export function wireHooks(settings, spec, agentHookPath, harness) {
|
|
|
171
189
|
settings.hooks[settingsKey] = groups;
|
|
172
190
|
wired++;
|
|
173
191
|
}
|
|
174
|
-
return { wired, already, removed };
|
|
192
|
+
return { wired, already, removed, upgraded };
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* The canonical hook command for one event. When the harness exports a
|
|
196
|
+
* project-dir env var to hook processes (Claude Code's CLAUDE_PROJECT_DIR),
|
|
197
|
+
* anchor the agent-hook path on it: hook processes inherit the session
|
|
198
|
+
* shell's cwd, which follows `cd` away from the project root — a bare
|
|
199
|
+
* relative path silently fails to spawn from there (no events, no image
|
|
200
|
+
* capture, no guards). `:-.` keeps the command working on harness versions
|
|
201
|
+
* that don't set the var. Only the env expansion is quoted so the
|
|
202
|
+
* `agent-hook <subcommand> ` wiring match stays byte-identical.
|
|
203
|
+
*/
|
|
204
|
+
function hookCommand(spec, agentHookPath, subcommand, harness) {
|
|
205
|
+
const anchor = spec.projectDirEnv ? `"\${${spec.projectDirEnv}:-.}"/` : "";
|
|
206
|
+
return `bash ${anchor}${agentHookPath} ${subcommand} --harness ${harness}`;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Rewrite any harnery-owned command for `subcommand` inside one hook group to
|
|
210
|
+
* the canonical form, in place. Returns the number of commands rewritten and
|
|
211
|
+
* calls `onPresent` when the subcommand is wired in this group at all.
|
|
212
|
+
*/
|
|
213
|
+
function rewriteStaleCommands(group, subcommand, canonical, onPresent) {
|
|
214
|
+
let rewritten = 0;
|
|
215
|
+
if ("command" in group && typeof group.command === "string") {
|
|
216
|
+
if (commandWiresSubcommand(group.command, subcommand)) {
|
|
217
|
+
onPresent();
|
|
218
|
+
if (group.command !== canonical) {
|
|
219
|
+
group.command = canonical;
|
|
220
|
+
rewritten++;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if ("hooks" in group && Array.isArray(group.hooks)) {
|
|
225
|
+
for (const hook of group.hooks) {
|
|
226
|
+
if (typeof hook.command !== "string")
|
|
227
|
+
continue;
|
|
228
|
+
if (!commandWiresSubcommand(hook.command, subcommand))
|
|
229
|
+
continue;
|
|
230
|
+
onPresent();
|
|
231
|
+
if (hook.command !== canonical) {
|
|
232
|
+
hook.command = canonical;
|
|
233
|
+
rewritten++;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return rewritten;
|
|
175
238
|
}
|
|
176
239
|
/**
|
|
177
240
|
* Inverse of {@link wireHooks}: strip every harnery-owned hook entry from a
|
|
@@ -270,6 +333,54 @@ export function stampBinName(configPath, binName, dryRun) {
|
|
|
270
333
|
writeFileSync(configPath, next);
|
|
271
334
|
return `+ added binName "${binName}" to ${rel(configPath)}`;
|
|
272
335
|
}
|
|
336
|
+
/** The commented workflow-billing default block init stamps into config.jsonc. */
|
|
337
|
+
const WORKFLOW_DEFAULT_BLOCK = ` // Workflow children ride the logged-in (subscription) harness auth; this pin\n` +
|
|
338
|
+
` // scrubs API-key vars from child envs so runs can never bill per-token rates.\n` +
|
|
339
|
+
` // Key-only hosts (CI): set false, or HARNERY_WORKFLOW_SUBSCRIPTION_ONLY=0.\n` +
|
|
340
|
+
` "workflow": { "subscriptionOnly": true }`;
|
|
341
|
+
/**
|
|
342
|
+
* Idempotently pin `workflow.subscriptionOnly: true` in `.harnery/config.jsonc`
|
|
343
|
+
* (see `workflowSubscriptionOnly()` in core/config.ts and the `workflow run`
|
|
344
|
+
* billing safeguards). Same comment-preserving discipline as `stampBinName`.
|
|
345
|
+
* A `workflow` key of ANY shape (including `subscriptionOnly: false`) is a
|
|
346
|
+
* deliberate project choice and is left alone.
|
|
347
|
+
*/
|
|
348
|
+
export function stampWorkflowDefaults(configPath, dryRun) {
|
|
349
|
+
const rel = (p) => relative(dirname(dirname(configPath)), p) || p;
|
|
350
|
+
if (!existsSync(configPath)) {
|
|
351
|
+
if (dryRun)
|
|
352
|
+
return `+ would pin workflow.subscriptionOnly in ${rel(configPath)}`;
|
|
353
|
+
writeFileSync(configPath, `{\n${WORKFLOW_DEFAULT_BLOCK}\n}\n`);
|
|
354
|
+
return `+ pinned workflow.subscriptionOnly: true in ${rel(configPath)}`;
|
|
355
|
+
}
|
|
356
|
+
let raw;
|
|
357
|
+
try {
|
|
358
|
+
raw = readFileSync(configPath, "utf8");
|
|
359
|
+
}
|
|
360
|
+
catch {
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
let parsed;
|
|
364
|
+
try {
|
|
365
|
+
parsed = JSON.parse(stripJsonComments(raw)) ?? {};
|
|
366
|
+
}
|
|
367
|
+
catch {
|
|
368
|
+
return `· ${rel(configPath)} isn't valid JSONC; skipped workflow.subscriptionOnly pin`;
|
|
369
|
+
}
|
|
370
|
+
if ("workflow" in parsed)
|
|
371
|
+
return null; // deliberate config; never touch
|
|
372
|
+
const at = firstBraceIndex(raw);
|
|
373
|
+
if (at < 0)
|
|
374
|
+
return `· ${rel(configPath)} has no object literal; skipped workflow pin`;
|
|
375
|
+
const keys = Object.keys(parsed);
|
|
376
|
+
const next = keys.length === 0
|
|
377
|
+
? `{\n${WORKFLOW_DEFAULT_BLOCK}\n}\n`
|
|
378
|
+
: `${raw.slice(0, at + 1)}\n${WORKFLOW_DEFAULT_BLOCK},${raw.slice(at + 1)}`;
|
|
379
|
+
if (dryRun)
|
|
380
|
+
return `+ would pin workflow.subscriptionOnly in ${rel(configPath)}`;
|
|
381
|
+
writeFileSync(configPath, next);
|
|
382
|
+
return `+ pinned workflow.subscriptionOnly: true in ${rel(configPath)}`;
|
|
383
|
+
}
|
|
273
384
|
/** Index of the first structural `{`, skipping leading whitespace + comments. */
|
|
274
385
|
function firstBraceIndex(raw) {
|
|
275
386
|
let i = 0;
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import type { Command } from "commander";
|
|
2
2
|
import type { EmitContext } from "../commander.js";
|
|
3
3
|
/**
|
|
4
|
-
* `presence`:
|
|
4
|
+
* `presence`: two related surfaces under one name.
|
|
5
|
+
*
|
|
6
|
+
* 1. Mobile/office state for agents (~/.claude/presence): get / set /
|
|
7
|
+
* clear / detect. The state file path is Claude-Code-specific but the
|
|
8
|
+
* logic is generic.
|
|
9
|
+
* 2. Cross-machine session presence (ADR 0016): publish / fetch / peers —
|
|
10
|
+
* the git-refs transport that lets sessions on other machines show up
|
|
11
|
+
* in the peer table via `refs/harnery/presence/<machine>` on origin.
|
|
5
12
|
*
|
|
6
13
|
* Uses the injected EmitContext so composed and standalone consumers share
|
|
7
|
-
* one code path.
|
|
8
|
-
* ~/.claude/) but the detect/set/clear logic is generic: any agent harness
|
|
9
|
-
* can adopt the same convention.
|
|
14
|
+
* one code path.
|
|
10
15
|
*/
|
|
11
16
|
export declare function registerPresenceCommand(program: Command, emit: EmitContext): void;
|
|
12
17
|
//# sourceMappingURL=presence.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"presence.d.ts","sourceRoot":"","sources":["../../src/commands/presence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"presence.d.ts","sourceRoot":"","sources":["../../src/commands/presence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AA0BnD;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAwKjF"}
|
|
@@ -1,17 +1,24 @@
|
|
|
1
|
+
import { monorepoRoot } from "../core/agents/coord-client.js";
|
|
1
2
|
import { emitCanonical, normalizeHarness, readHeartbeat, resolveOwner, } from "../core/agents/index.js";
|
|
3
|
+
import { fetchPresence, publishPresence, readRemoteMachines, relayDaemonStatus, runRelayDaemon, } from "../core/presence/index.js";
|
|
2
4
|
import { ageSeconds, applyDetection, clearPresence, formatAge, presenceFilePath, readPresence, writePresence, } from "../lib/presence.js";
|
|
3
5
|
/**
|
|
4
|
-
* `presence`:
|
|
6
|
+
* `presence`: two related surfaces under one name.
|
|
7
|
+
*
|
|
8
|
+
* 1. Mobile/office state for agents (~/.claude/presence): get / set /
|
|
9
|
+
* clear / detect. The state file path is Claude-Code-specific but the
|
|
10
|
+
* logic is generic.
|
|
11
|
+
* 2. Cross-machine session presence (ADR 0016): publish / fetch / peers —
|
|
12
|
+
* the git-refs transport that lets sessions on other machines show up
|
|
13
|
+
* in the peer table via `refs/harnery/presence/<machine>` on origin.
|
|
5
14
|
*
|
|
6
15
|
* Uses the injected EmitContext so composed and standalone consumers share
|
|
7
|
-
* one code path.
|
|
8
|
-
* ~/.claude/) but the detect/set/clear logic is generic: any agent harness
|
|
9
|
-
* can adopt the same convention.
|
|
16
|
+
* one code path.
|
|
10
17
|
*/
|
|
11
18
|
export function registerPresenceCommand(program, emit) {
|
|
12
19
|
const cmd = program
|
|
13
20
|
.command("presence")
|
|
14
|
-
.description("
|
|
21
|
+
.description("Presence: mobile/office state (get/set/clear/detect) + cross-machine session presence (publish/fetch/peers)")
|
|
15
22
|
.action(() => {
|
|
16
23
|
printStatus(emit);
|
|
17
24
|
});
|
|
@@ -62,6 +69,74 @@ export function registerPresenceCommand(program, emit) {
|
|
|
62
69
|
path: presenceFilePath(),
|
|
63
70
|
});
|
|
64
71
|
});
|
|
72
|
+
// ---- cross-machine session presence (ADR 0016) ----
|
|
73
|
+
cmd
|
|
74
|
+
.command("publish")
|
|
75
|
+
.description("Publish this machine's live sessions to refs/harnery/presence/<machine> on origin. " +
|
|
76
|
+
"Change-batched with a keepalive; --force pushes regardless. Synchronous (errors report).")
|
|
77
|
+
.option("--force", "Push even if unchanged / within throttle windows")
|
|
78
|
+
.action((opts) => {
|
|
79
|
+
const root = requireRoot(emit);
|
|
80
|
+
const r = publishPresence(root, { force: opts.force, sync: true });
|
|
81
|
+
if (r.status === "error") {
|
|
82
|
+
emit.error({ code: "publish_failed", message: r.error });
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
emit.data(r);
|
|
86
|
+
});
|
|
87
|
+
cmd
|
|
88
|
+
.command("fetch")
|
|
89
|
+
.description("Fetch peer machines' presence refs from origin (throttled; --force bypasses). Synchronous.")
|
|
90
|
+
.option("--force", "Fetch even within the throttle interval")
|
|
91
|
+
.action((opts) => {
|
|
92
|
+
const root = requireRoot(emit);
|
|
93
|
+
const r = fetchPresence(root, { force: opts.force, sync: true });
|
|
94
|
+
if (r.status === "error") {
|
|
95
|
+
emit.error({ code: "fetch_failed", message: r.error });
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
emit.data(r);
|
|
99
|
+
});
|
|
100
|
+
cmd
|
|
101
|
+
.command("relay-daemon")
|
|
102
|
+
.description("Run the relay client daemon in the foreground (normally spawned detached by the hooks " +
|
|
103
|
+
"when presence.relay is configured; holds the WebSocket, publishes on heartbeat change, " +
|
|
104
|
+
"caches peers' blobs for the renderers, exits when the machine goes idle).")
|
|
105
|
+
.action(async () => {
|
|
106
|
+
const root = requireRoot(emit);
|
|
107
|
+
process.exit(await runRelayDaemon(root));
|
|
108
|
+
});
|
|
109
|
+
cmd
|
|
110
|
+
.command("peers")
|
|
111
|
+
.description("Show sessions on other machines (merged from presence refs + the relay daemon's cache)")
|
|
112
|
+
.option("--json", "JSON output")
|
|
113
|
+
.option("--stale", "Include machines past the stale window")
|
|
114
|
+
.action((opts) => {
|
|
115
|
+
const root = requireRoot(emit);
|
|
116
|
+
const machines = readRemoteMachines(root, { includeStale: opts.stale });
|
|
117
|
+
const daemon = relayDaemonStatus(root);
|
|
118
|
+
if (opts.json)
|
|
119
|
+
emit.config({ format: "json" });
|
|
120
|
+
if (opts.json || !process.stdout.isTTY) {
|
|
121
|
+
emit.data({ machines, relay_daemon: daemon });
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
emit.text(daemon.running
|
|
125
|
+
? `relay daemon: running (pid ${daemon.pid}, ${daemon.url})\n`
|
|
126
|
+
: "relay daemon: not running (git-refs transport only)\n");
|
|
127
|
+
if (machines.length === 0) {
|
|
128
|
+
emit.text("no remote sessions known (try `presence fetch --force`)\n");
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
for (const m of machines) {
|
|
132
|
+
emit.text(`${m.machine} (published ${formatAge(m.age_secs)} ago)\n`);
|
|
133
|
+
for (const a of m.agents) {
|
|
134
|
+
const task = a.task ? ` "${a.task.slice(0, 70)}"` : "";
|
|
135
|
+
const files = a.files_touched?.length ? ` [${a.files_touched.length} files]` : "";
|
|
136
|
+
emit.text(` agent-${a.name ?? a.instance_id.slice(0, 8)}${task}${files}\n`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
});
|
|
65
140
|
cmd
|
|
66
141
|
.command("detect")
|
|
67
142
|
.description("Internal: read prompt text from stdin, apply detection rules, update state if signal is clear")
|
|
@@ -85,6 +160,14 @@ export function registerPresenceCommand(program, emit) {
|
|
|
85
160
|
}
|
|
86
161
|
});
|
|
87
162
|
}
|
|
163
|
+
function requireRoot(emit) {
|
|
164
|
+
const root = monorepoRoot();
|
|
165
|
+
if (!root) {
|
|
166
|
+
emit.error({ code: "not_in_repo", message: "coord root not found (no .harnery/ upward)" });
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
return root;
|
|
170
|
+
}
|
|
88
171
|
function printStatus(emit) {
|
|
89
172
|
const r = readPresence();
|
|
90
173
|
emit.data({
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
import type { EmitContext } from "../commander.js";
|
|
3
|
+
/** Start the relay server. Exported for in-process integration tests. */
|
|
4
|
+
export declare function startRelayServer(port: number): {
|
|
5
|
+
port: number;
|
|
6
|
+
stop: () => void;
|
|
7
|
+
};
|
|
8
|
+
export declare function registerRelayCommand(program: Command, emit: EmitContext): void;
|
|
9
|
+
//# sourceMappingURL=relay.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relay.d.ts","sourceRoot":"","sources":["../../src/commands/relay.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAgCnD,yEAAyE;AACzE,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,IAAI,CAAA;CAAE,CAkGjF;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAyB9E"}
|