@kylecheng3146/agent-ops 0.1.1 → 0.1.3
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 +11 -2
- package/dist/packages/cli/src/bin.js +195 -247
- package/dist/packages/cli/src/commands/hook.js +42 -0
- package/dist/packages/cli/src/commands/init.js +4 -1
- package/dist/packages/cli/src/commands/update.js +4 -1
- package/dist/packages/cli/src/context.js +102 -0
- package/dist/packages/cli/src/hook-entry.js +10 -0
- package/dist/packages/cli/src/hook-process.js +54 -0
- package/dist/packages/cli/src/ui.js +132 -0
- package/dist/packages/cli/src/version.js +3 -0
- package/dist/runtime/src/adapters/claude/config.js +19 -0
- package/dist/runtime/src/adapters/codex/config.js +16 -0
- package/dist/runtime/src/fs/transaction.js +20 -10
- package/dist/runtime/src/install/doctor.js +7 -3
- package/dist/runtime/src/install/harness.js +1 -1
- package/dist/runtime/src/install/hooks.js +77 -0
- package/dist/runtime/src/install/ownership.js +20 -0
- package/dist/runtime/src/install/plan.js +27 -2
- package/dist/runtime/src/install/probes.js +68 -0
- package/dist/runtime/src/install/uninstall.js +28 -0
- package/dist/runtime/src/install/update.js +3 -0
- package/dist/runtime/src/schema/validate.js +57 -0
- package/dist/runtime/src/security/permissions.js +22 -3
- package/package.json +1 -1
- package/schemas/manifest.schema.json +33 -0
package/README.md
CHANGED
|
@@ -94,8 +94,17 @@ node dist/packages/cli/src/bin.js update --dry-run --json
|
|
|
94
94
|
node dist/packages/cli/src/bin.js uninstall --dry-run --json
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
-
The commands after `init --yes` are post-apply operations
|
|
98
|
-
|
|
97
|
+
The commands after `init --yes` are post-apply operations. `doctor` reports
|
|
98
|
+
`UNKNOWN` for a probe that has nothing to verify yet: `repository-trust` until
|
|
99
|
+
`trust grant` runs, and `smoke-availability` until the configuration declares a
|
|
100
|
+
verification command.
|
|
101
|
+
|
|
102
|
+
Installing the `advisory` or `guardrails` profile also registers lifecycle
|
|
103
|
+
hooks in `.claude/settings.json` and `.codex/hooks.json`. Only agent-ops owned
|
|
104
|
+
handlers are added, foreign settings in those files are preserved, and
|
|
105
|
+
`uninstall` removes exactly the handlers it registered. The hooks call
|
|
106
|
+
`agent-ops hook <harness> <event>`, which always exits 0 so a toolkit failure
|
|
107
|
+
can never block the harness.
|
|
99
108
|
|
|
100
109
|
For a full command reference, run `agent-ops --help`. The `task`, `verify`, and
|
|
101
110
|
`review` commands support acceptance tracking and independent verification when
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
2
3
|
import { homedir } from "node:os";
|
|
3
4
|
import { join } from "node:path";
|
|
4
|
-
import { createInterface } from "node:readline/promises";
|
|
5
5
|
import { execFileSync } from "node:child_process";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
6
7
|
import { commonHarnessAdapters } from "../../../runtime/src/install/harness.js";
|
|
8
|
+
import { hookRegistrationSatisfied, repositoryTrustStatus, smokeAvailabilityStatus } from "../../../runtime/src/install/probes.js";
|
|
9
|
+
import { parseInstallManifest } from "../../../runtime/src/fs/manifest.js";
|
|
7
10
|
import { NpmRegistryClient } from "../../../runtime/src/registry/npm.js";
|
|
8
11
|
import { TaskService } from "../../../runtime/src/task/service.js";
|
|
9
12
|
import { FileTaskStore } from "../../../runtime/src/task/store.js";
|
|
10
|
-
import { mergeConfigLayers } from "../../../runtime/src/config/merge.js";
|
|
11
|
-
import { loadConfigFile } from "../../../runtime/src/config/load.js";
|
|
12
|
-
import { AgentOpsError } from "../../../runtime/src/fs/paths.js";
|
|
13
13
|
import { FileTrustStore, calculateTrustBinding } from "../../../runtime/src/security/trust.js";
|
|
14
14
|
import { localStatePaths } from "../../../runtime/src/security/permissions.js";
|
|
15
15
|
import { sha256 } from "../../../runtime/src/fs/hash.js";
|
|
@@ -17,6 +17,10 @@ import { FileEvidenceStore } from "../../../runtime/src/verify/evidence.js";
|
|
|
17
17
|
import { VerificationService } from "../../../runtime/src/verify/service.js";
|
|
18
18
|
import { NodeVerificationProcessRunner } from "../../../runtime/src/verify/spawn.js";
|
|
19
19
|
import { runCli } from "./cli.js";
|
|
20
|
+
import { loadEffectiveConfig, repositoryTrust } from "./context.js";
|
|
21
|
+
import { runHookProcess } from "./hook-process.js";
|
|
22
|
+
import { selectYesNo, writeBanner } from "./ui.js";
|
|
23
|
+
import { CLI_VERSION } from "./version.js";
|
|
20
24
|
import { createCommandRegistry } from "./commands/index.js";
|
|
21
25
|
import { explainConfigCommand } from "./commands/config.js";
|
|
22
26
|
import { runDoctorCommand } from "./commands/doctor.js";
|
|
@@ -28,263 +32,207 @@ import { runTrustCommand } from "./commands/trust.js";
|
|
|
28
32
|
import { runVerifyCommand } from "./commands/verify.js";
|
|
29
33
|
import { formatUpdatePlan, runUpdateCommand } from "./commands/update.js";
|
|
30
34
|
import { errorEnvelope } from "./output.js";
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
schemaVersion: 1,
|
|
34
|
-
profiles: [],
|
|
35
|
-
verification: { commands: [] },
|
|
36
|
-
pathMappings: [],
|
|
37
|
-
securityExceptions: []
|
|
38
|
-
};
|
|
39
|
-
function defaultConfigLayer() {
|
|
40
|
-
return {
|
|
41
|
-
source: "default",
|
|
42
|
-
sourcePath: "built-in defaults",
|
|
43
|
-
config: DEFAULT_CONFIG
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
async function loadOptionalConfig(path) {
|
|
35
|
+
const HOOK_RUNTIME_PATH = fileURLToPath(new URL("./hook-entry.js", import.meta.url));
|
|
36
|
+
async function readOptionalJson(path) {
|
|
47
37
|
try {
|
|
48
|
-
return await
|
|
49
|
-
}
|
|
50
|
-
catch (error) {
|
|
51
|
-
if (error instanceof AgentOpsError &&
|
|
52
|
-
error.code === "CONFIG_READ_FAILED" &&
|
|
53
|
-
typeof error.cause === "object" &&
|
|
54
|
-
error.cause !== null &&
|
|
55
|
-
"code" in error.cause &&
|
|
56
|
-
error.cause.code === "ENOENT") {
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
throw error;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
async function loadEffectiveConfig(root, scope) {
|
|
63
|
-
const home = process.env.AGENT_OPS_HOME ?? homedir();
|
|
64
|
-
const userPath = join(home, ".agent-ops", "config.json");
|
|
65
|
-
const projectPath = join(root, ".agent-ops", "config.json");
|
|
66
|
-
const layers = [defaultConfigLayer()];
|
|
67
|
-
if (scope === "user") {
|
|
68
|
-
const user = await loadOptionalConfig(userPath);
|
|
69
|
-
if (user !== null) {
|
|
70
|
-
layers.push({
|
|
71
|
-
source: "user",
|
|
72
|
-
sourcePath: user.sourcePath,
|
|
73
|
-
config: user.config
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
return mergeConfigLayers(layers);
|
|
77
|
-
}
|
|
78
|
-
if (projectPath !== userPath) {
|
|
79
|
-
const user = await loadOptionalConfig(userPath);
|
|
80
|
-
if (user !== null) {
|
|
81
|
-
layers.push({
|
|
82
|
-
source: "user",
|
|
83
|
-
sourcePath: user.sourcePath,
|
|
84
|
-
config: user.config
|
|
85
|
-
});
|
|
86
|
-
}
|
|
38
|
+
return JSON.parse(await readFile(path, "utf8"));
|
|
87
39
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
layers.push({
|
|
91
|
-
source: "project",
|
|
92
|
-
sourcePath: project.sourcePath,
|
|
93
|
-
config: project.config
|
|
94
|
-
});
|
|
40
|
+
catch {
|
|
41
|
+
return null;
|
|
95
42
|
}
|
|
96
|
-
return mergeConfigLayers(layers);
|
|
97
43
|
}
|
|
98
|
-
async function
|
|
99
|
-
const home = process.env.AGENT_OPS_HOME ?? homedir();
|
|
100
|
-
const state = localStatePaths(home);
|
|
101
|
-
const remote = (() => {
|
|
102
|
-
try {
|
|
103
|
-
return execFileSync("git", ["config", "--get", "remote.origin.url"], {
|
|
104
|
-
cwd: root,
|
|
105
|
-
encoding: "utf8"
|
|
106
|
-
}).trim();
|
|
107
|
-
}
|
|
108
|
-
catch {
|
|
109
|
-
return `local:${root}`;
|
|
110
|
-
}
|
|
111
|
-
})();
|
|
44
|
+
async function installedHarness(root) {
|
|
112
45
|
try {
|
|
113
|
-
|
|
114
|
-
repositoryPath: root,
|
|
115
|
-
remoteUrl: remote,
|
|
116
|
-
configHash: sha256(JSON.stringify(config)),
|
|
117
|
-
runtimeHash: sha256(CLI_VERSION)
|
|
118
|
-
});
|
|
119
|
-
return (await new FileTrustStore(state.trustStore, state.anchorDirectory).status(binding)).status;
|
|
46
|
+
return parseInstallManifest(await readFile(join(root, ".agent-ops", "manifest.json"), "utf8")).harness;
|
|
120
47
|
}
|
|
121
48
|
catch {
|
|
122
|
-
|
|
49
|
+
// ponytail: no readable manifest means demand hooks for both harnesses.
|
|
50
|
+
return "both";
|
|
123
51
|
}
|
|
124
52
|
}
|
|
125
53
|
async function confirmInit(plan) {
|
|
54
|
+
writeBanner({
|
|
55
|
+
isTTY: process.stdout.isTTY === true,
|
|
56
|
+
columns: process.stdout.columns,
|
|
57
|
+
write: (value) => process.stdout.write(value)
|
|
58
|
+
});
|
|
126
59
|
return await confirmPlan(formatInstallPlan(plan));
|
|
127
60
|
}
|
|
128
61
|
async function confirmPlan(text) {
|
|
129
62
|
process.stdout.write(text);
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
prompt.close();
|
|
140
|
-
}
|
|
63
|
+
return await selectYesNo("Apply this installation plan?", { input: process.stdin, output: process.stdout }, false);
|
|
64
|
+
}
|
|
65
|
+
const argv = process.argv.slice(2);
|
|
66
|
+
if (argv[0] === "hook") {
|
|
67
|
+
process.exitCode = await runHookProcess(argv.slice(1), {
|
|
68
|
+
stdin: process.stdin,
|
|
69
|
+
writeStdout: (value) => process.stdout.write(value),
|
|
70
|
+
writeStderr: (value) => process.stderr.write(value)
|
|
71
|
+
}, CLI_VERSION);
|
|
141
72
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
adapters: commonHarnessAdapters(),
|
|
172
|
-
isTTY,
|
|
173
|
-
toolkitVersion: CLI_VERSION,
|
|
174
|
-
confirm: async (plan) => await confirmInit(plan)
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
if (args.command === "doctor") {
|
|
178
|
-
return await runDoctorCommand({ root });
|
|
179
|
-
}
|
|
180
|
-
if (args.command === "uninstall") {
|
|
181
|
-
return await runUninstallCommand({
|
|
182
|
-
args,
|
|
183
|
-
root,
|
|
184
|
-
isTTY,
|
|
185
|
-
confirm: async (plan) => await confirmPlan(formatUninstallPlan(plan))
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
if (args.command === "update") {
|
|
189
|
-
return await runUpdateCommand({
|
|
190
|
-
args,
|
|
191
|
-
root,
|
|
192
|
-
adapters: commonHarnessAdapters(),
|
|
193
|
-
registry: new NpmRegistryClient(),
|
|
194
|
-
isTTY,
|
|
195
|
-
confirm: async (plan) => await confirmPlan(formatUpdatePlan(plan)),
|
|
196
|
-
...(args.targetVersion === undefined
|
|
197
|
-
? {}
|
|
198
|
-
: { targetVersion: args.targetVersion })
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
const taskService = new TaskService(new FileTaskStore(join(root, ".agent-ops", "tasks", "state.json"), root));
|
|
202
|
-
if (args.command === "task") {
|
|
203
|
-
const sessionId = process.env.AGENT_OPS_SESSION_ID;
|
|
204
|
-
return await runTaskCommand({
|
|
205
|
-
args,
|
|
206
|
-
service: taskService,
|
|
207
|
-
...(sessionId === undefined ? {} : { sessionId })
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
if (args.command === "review") {
|
|
211
|
-
return await runReviewCommand({
|
|
212
|
-
args,
|
|
213
|
-
authorized: args.yes
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
if (args.command === "config") {
|
|
217
|
-
return explainConfigCommand(await loadEffectiveConfig(root, args.scope === "user" ? "user" : "project"));
|
|
218
|
-
}
|
|
219
|
-
if (args.command === "verify") {
|
|
220
|
-
const merged = await loadEffectiveConfig(root, args.scope === "user" ? "user" : "project");
|
|
221
|
-
const config = merged.config;
|
|
222
|
-
const trustStatus = await repositoryTrust(root, config);
|
|
223
|
-
return await runVerifyCommand({
|
|
224
|
-
args,
|
|
225
|
-
taskService,
|
|
226
|
-
service: new VerificationService({
|
|
73
|
+
else {
|
|
74
|
+
process.exitCode = await runCli(argv, {
|
|
75
|
+
isTTY: process.stdin.isTTY === true && process.stdout.isTTY === true,
|
|
76
|
+
input: process.stdin,
|
|
77
|
+
output: process.stdout,
|
|
78
|
+
writeStdout: (value) => process.stdout.write(value),
|
|
79
|
+
writeStderr: (value) => process.stderr.write(value)
|
|
80
|
+
}, {
|
|
81
|
+
version: CLI_VERSION,
|
|
82
|
+
registry: createCommandRegistry(Object.fromEntries([
|
|
83
|
+
"init",
|
|
84
|
+
"config",
|
|
85
|
+
"trust",
|
|
86
|
+
"doctor",
|
|
87
|
+
"update",
|
|
88
|
+
"uninstall",
|
|
89
|
+
"task",
|
|
90
|
+
"verify",
|
|
91
|
+
"review"
|
|
92
|
+
].map((command) => [command, async (args) => {
|
|
93
|
+
const root = args.scope === "user"
|
|
94
|
+
? process.env.AGENT_OPS_HOME ?? homedir()
|
|
95
|
+
: process.cwd();
|
|
96
|
+
const isTTY = !args.json &&
|
|
97
|
+
process.stdin.isTTY === true &&
|
|
98
|
+
process.stdout.isTTY === true;
|
|
99
|
+
if (args.command === "init") {
|
|
100
|
+
return await runInitCommand({
|
|
101
|
+
args,
|
|
227
102
|
root,
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
103
|
+
adapters: commonHarnessAdapters(),
|
|
104
|
+
isTTY,
|
|
105
|
+
toolkitVersion: CLI_VERSION,
|
|
106
|
+
hookRuntimePath: HOOK_RUNTIME_PATH,
|
|
107
|
+
confirm: async (plan) => await confirmInit(plan)
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
if (args.command === "doctor") {
|
|
111
|
+
const config = (await loadEffectiveConfig(root, args.scope === "user" ? "user" : "project")).config;
|
|
112
|
+
return await runDoctorCommand({
|
|
113
|
+
root,
|
|
114
|
+
probes: {
|
|
115
|
+
hookRegistration: async () => hookRegistrationSatisfied({
|
|
116
|
+
harness: await installedHarness(root),
|
|
117
|
+
profiles: config.profiles,
|
|
118
|
+
claudeSettings: await readOptionalJson(join(root, ".claude", "settings.json")),
|
|
119
|
+
codexHooks: await readOptionalJson(join(root, ".codex", "hooks.json"))
|
|
120
|
+
}),
|
|
121
|
+
repositoryTrust: async () => repositoryTrustStatus(await repositoryTrust(root, config, CLI_VERSION)),
|
|
122
|
+
smokeAvailability: () => smokeAvailabilityStatus(config)
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
if (args.command === "uninstall") {
|
|
127
|
+
return await runUninstallCommand({
|
|
128
|
+
args,
|
|
129
|
+
root,
|
|
130
|
+
isTTY,
|
|
131
|
+
confirm: async (plan) => await confirmPlan(formatUninstallPlan(plan))
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
if (args.command === "update") {
|
|
135
|
+
return await runUpdateCommand({
|
|
136
|
+
args,
|
|
137
|
+
root,
|
|
138
|
+
adapters: commonHarnessAdapters(),
|
|
139
|
+
registry: new NpmRegistryClient(),
|
|
140
|
+
isTTY,
|
|
141
|
+
hookRuntimePath: HOOK_RUNTIME_PATH,
|
|
142
|
+
confirm: async (plan) => await confirmPlan(formatUpdatePlan(plan)),
|
|
143
|
+
...(args.targetVersion === undefined
|
|
144
|
+
? {}
|
|
145
|
+
: { targetVersion: args.targetVersion })
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
const taskService = new TaskService(new FileTaskStore(join(root, ".agent-ops", "tasks", "state.json"), root));
|
|
149
|
+
if (args.command === "task") {
|
|
150
|
+
const sessionId = process.env.AGENT_OPS_SESSION_ID;
|
|
151
|
+
return await runTaskCommand({
|
|
152
|
+
args,
|
|
153
|
+
service: taskService,
|
|
154
|
+
...(sessionId === undefined ? {} : { sessionId })
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
if (args.command === "review") {
|
|
158
|
+
return await runReviewCommand({
|
|
159
|
+
args,
|
|
160
|
+
authorized: args.yes
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
if (args.command === "config") {
|
|
164
|
+
return explainConfigCommand(await loadEffectiveConfig(root, args.scope === "user" ? "user" : "project"));
|
|
165
|
+
}
|
|
166
|
+
if (args.command === "verify") {
|
|
167
|
+
const merged = await loadEffectiveConfig(root, args.scope === "user" ? "user" : "project");
|
|
168
|
+
const config = merged.config;
|
|
169
|
+
const trustStatus = await repositoryTrust(root, config, CLI_VERSION);
|
|
170
|
+
return await runVerifyCommand({
|
|
171
|
+
args,
|
|
252
172
|
taskService,
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
173
|
+
service: new VerificationService({
|
|
174
|
+
root,
|
|
175
|
+
scope: args.scope === "user" ? "user" : "project",
|
|
176
|
+
config,
|
|
177
|
+
gitRunner: {
|
|
178
|
+
run: async (gitArgs) => {
|
|
179
|
+
try {
|
|
180
|
+
return {
|
|
181
|
+
exitCode: 0,
|
|
182
|
+
stdout: execFileSync("git", [...gitArgs], {
|
|
183
|
+
cwd: root,
|
|
184
|
+
encoding: "buffer",
|
|
185
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
186
|
+
})
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
const failure = error;
|
|
191
|
+
return {
|
|
192
|
+
exitCode: failure.status ?? 1,
|
|
193
|
+
stdout: failure.stdout ?? new Uint8Array()
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
processRunner: new NodeVerificationProcessRunner(),
|
|
199
|
+
taskService,
|
|
200
|
+
evidenceStore: new FileEvidenceStore(root, root),
|
|
201
|
+
trusted: trustStatus === "TRUSTED"
|
|
202
|
+
})
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
if (args.command === "trust") {
|
|
206
|
+
const config = (await loadEffectiveConfig(root, args.scope === "user" ? "user" : "project")).config;
|
|
207
|
+
const state = localStatePaths(process.env.AGENT_OPS_HOME ?? homedir());
|
|
208
|
+
const remote = (() => {
|
|
209
|
+
try {
|
|
210
|
+
return execFileSync("git", ["config", "--get", "remote.origin.url"], {
|
|
211
|
+
cwd: root,
|
|
212
|
+
encoding: "utf8"
|
|
213
|
+
}).trim();
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
return `local:${root}`;
|
|
217
|
+
}
|
|
218
|
+
})();
|
|
219
|
+
const binding = await calculateTrustBinding({
|
|
220
|
+
repositoryPath: root,
|
|
221
|
+
remoteUrl: remote,
|
|
222
|
+
configHash: sha256(JSON.stringify(config)),
|
|
223
|
+
runtimeHash: sha256(CLI_VERSION)
|
|
224
|
+
});
|
|
225
|
+
return await runTrustCommand({
|
|
226
|
+
action: args.action,
|
|
227
|
+
yes: args.yes,
|
|
228
|
+
isTTY,
|
|
229
|
+
calculateBinding: async () => binding,
|
|
230
|
+
presentBinding: async () => undefined,
|
|
231
|
+
confirmGrant: async () => await confirmPlan(JSON.stringify(binding, null, 2)),
|
|
232
|
+
store: new FileTrustStore(state.trustStore, state.anchorDirectory)
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
return errorEnvelope("CLI_COMMAND_UNAVAILABLE", `Command is not implemented yet: ${args.command}`);
|
|
236
|
+
}])))
|
|
237
|
+
});
|
|
238
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { normalizeClaudeHookInput } from "../../../../runtime/src/adapters/claude/input.js";
|
|
2
|
+
import { claudeHookOutput } from "../../../../runtime/src/adapters/claude/output.js";
|
|
3
|
+
import { normalizeCodexHookInput } from "../../../../runtime/src/adapters/codex/input.js";
|
|
4
|
+
import { codexHookOutput } from "../../../../runtime/src/adapters/codex/output.js";
|
|
5
|
+
import { dispatchHookEvent } from "../../../../runtime/src/hooks/dispatch.js";
|
|
6
|
+
import { resolveProfiles } from "../../../../runtime/src/install/profiles.js";
|
|
7
|
+
export const HOOK_EVENTS = [
|
|
8
|
+
"SessionStart",
|
|
9
|
+
"PreToolUse",
|
|
10
|
+
"Stop"
|
|
11
|
+
];
|
|
12
|
+
/**
|
|
13
|
+
* Hooks are advisory infrastructure: every failure path stays fail-open with
|
|
14
|
+
* exit code 0 so a broken toolkit can never wedge the harness.
|
|
15
|
+
*/
|
|
16
|
+
export async function runHookCommand(options) {
|
|
17
|
+
try {
|
|
18
|
+
const { capabilities } = resolveProfiles(options.config.profiles);
|
|
19
|
+
let input;
|
|
20
|
+
try {
|
|
21
|
+
input = JSON.parse(options.stdin);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return { exitCode: 0, stdout: "", stderr: "" };
|
|
25
|
+
}
|
|
26
|
+
const event = options.harness === "claude"
|
|
27
|
+
? normalizeClaudeHookInput(input)
|
|
28
|
+
: normalizeCodexHookInput(input);
|
|
29
|
+
const result = await dispatchHookEvent(event, {
|
|
30
|
+
capabilities,
|
|
31
|
+
trusted: options.trusted
|
|
32
|
+
});
|
|
33
|
+
if (options.harness === "claude") {
|
|
34
|
+
return claudeHookOutput(options.event, result);
|
|
35
|
+
}
|
|
36
|
+
const codex = codexHookOutput(options.event, result);
|
|
37
|
+
return { exitCode: 0, stdout: codex.stdout, stderr: "" };
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return { exitCode: 0, stdout: "", stderr: "" };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -38,7 +38,10 @@ export async function runInitCommand(options) {
|
|
|
38
38
|
adapters: options.adapters,
|
|
39
39
|
...(options.toolkitVersion === undefined
|
|
40
40
|
? {}
|
|
41
|
-
: { toolkitVersion: options.toolkitVersion })
|
|
41
|
+
: { toolkitVersion: options.toolkitVersion }),
|
|
42
|
+
...(options.hookRuntimePath === undefined
|
|
43
|
+
? {}
|
|
44
|
+
: { hookRuntimePath: options.hookRuntimePath })
|
|
42
45
|
});
|
|
43
46
|
if (args.dryRun) {
|
|
44
47
|
return okEnvelope("INIT_PLAN_READY", {
|
|
@@ -32,7 +32,10 @@ export async function runUpdateCommand(options) {
|
|
|
32
32
|
: { registry: options.registry }),
|
|
33
33
|
...(options.targetVersion === undefined
|
|
34
34
|
? {}
|
|
35
|
-
: { targetVersion: options.targetVersion })
|
|
35
|
+
: { targetVersion: options.targetVersion }),
|
|
36
|
+
...(options.hookRuntimePath === undefined
|
|
37
|
+
? {}
|
|
38
|
+
: { hookRuntimePath: options.hookRuntimePath })
|
|
36
39
|
});
|
|
37
40
|
if (options.args.dryRun) {
|
|
38
41
|
return okEnvelope("UPDATE_PLAN_READY", {
|