@kylecheng3146/agent-ops 0.1.2 → 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 +184 -269
- 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/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/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
|
|
@@ -2,17 +2,14 @@
|
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
-
import { createInterface } from "node:readline/promises";
|
|
6
5
|
import { execFileSync } from "node:child_process";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { commonHarnessAdapters } from "../../../runtime/src/install/harness.js";
|
|
8
8
|
import { hookRegistrationSatisfied, repositoryTrustStatus, smokeAvailabilityStatus } from "../../../runtime/src/install/probes.js";
|
|
9
9
|
import { parseInstallManifest } from "../../../runtime/src/fs/manifest.js";
|
|
10
10
|
import { NpmRegistryClient } from "../../../runtime/src/registry/npm.js";
|
|
11
11
|
import { TaskService } from "../../../runtime/src/task/service.js";
|
|
12
12
|
import { FileTaskStore } from "../../../runtime/src/task/store.js";
|
|
13
|
-
import { mergeConfigLayers } from "../../../runtime/src/config/merge.js";
|
|
14
|
-
import { loadConfigFile } from "../../../runtime/src/config/load.js";
|
|
15
|
-
import { AgentOpsError } from "../../../runtime/src/fs/paths.js";
|
|
16
13
|
import { FileTrustStore, calculateTrustBinding } from "../../../runtime/src/security/trust.js";
|
|
17
14
|
import { localStatePaths } from "../../../runtime/src/security/permissions.js";
|
|
18
15
|
import { sha256 } from "../../../runtime/src/fs/hash.js";
|
|
@@ -20,6 +17,10 @@ import { FileEvidenceStore } from "../../../runtime/src/verify/evidence.js";
|
|
|
20
17
|
import { VerificationService } from "../../../runtime/src/verify/service.js";
|
|
21
18
|
import { NodeVerificationProcessRunner } from "../../../runtime/src/verify/spawn.js";
|
|
22
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";
|
|
23
24
|
import { createCommandRegistry } from "./commands/index.js";
|
|
24
25
|
import { explainConfigCommand } from "./commands/config.js";
|
|
25
26
|
import { runDoctorCommand } from "./commands/doctor.js";
|
|
@@ -31,73 +32,7 @@ import { runTrustCommand } from "./commands/trust.js";
|
|
|
31
32
|
import { runVerifyCommand } from "./commands/verify.js";
|
|
32
33
|
import { formatUpdatePlan, runUpdateCommand } from "./commands/update.js";
|
|
33
34
|
import { errorEnvelope } from "./output.js";
|
|
34
|
-
const
|
|
35
|
-
const DEFAULT_CONFIG = {
|
|
36
|
-
schemaVersion: 1,
|
|
37
|
-
profiles: [],
|
|
38
|
-
verification: { commands: [] },
|
|
39
|
-
pathMappings: [],
|
|
40
|
-
securityExceptions: []
|
|
41
|
-
};
|
|
42
|
-
function defaultConfigLayer() {
|
|
43
|
-
return {
|
|
44
|
-
source: "default",
|
|
45
|
-
sourcePath: "built-in defaults",
|
|
46
|
-
config: DEFAULT_CONFIG
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
async function loadOptionalConfig(path) {
|
|
50
|
-
try {
|
|
51
|
-
return await loadConfigFile(path);
|
|
52
|
-
}
|
|
53
|
-
catch (error) {
|
|
54
|
-
if (error instanceof AgentOpsError &&
|
|
55
|
-
error.code === "CONFIG_READ_FAILED" &&
|
|
56
|
-
typeof error.cause === "object" &&
|
|
57
|
-
error.cause !== null &&
|
|
58
|
-
"code" in error.cause &&
|
|
59
|
-
error.cause.code === "ENOENT") {
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
62
|
-
throw error;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
async function loadEffectiveConfig(root, scope) {
|
|
66
|
-
const home = process.env.AGENT_OPS_HOME ?? homedir();
|
|
67
|
-
const userPath = join(home, ".agent-ops", "config.json");
|
|
68
|
-
const projectPath = join(root, ".agent-ops", "config.json");
|
|
69
|
-
const layers = [defaultConfigLayer()];
|
|
70
|
-
if (scope === "user") {
|
|
71
|
-
const user = await loadOptionalConfig(userPath);
|
|
72
|
-
if (user !== null) {
|
|
73
|
-
layers.push({
|
|
74
|
-
source: "user",
|
|
75
|
-
sourcePath: user.sourcePath,
|
|
76
|
-
config: user.config
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
return mergeConfigLayers(layers);
|
|
80
|
-
}
|
|
81
|
-
if (projectPath !== userPath) {
|
|
82
|
-
const user = await loadOptionalConfig(userPath);
|
|
83
|
-
if (user !== null) {
|
|
84
|
-
layers.push({
|
|
85
|
-
source: "user",
|
|
86
|
-
sourcePath: user.sourcePath,
|
|
87
|
-
config: user.config
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
const project = await loadOptionalConfig(projectPath);
|
|
92
|
-
if (project !== null) {
|
|
93
|
-
layers.push({
|
|
94
|
-
source: "project",
|
|
95
|
-
sourcePath: project.sourcePath,
|
|
96
|
-
config: project.config
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
return mergeConfigLayers(layers);
|
|
100
|
-
}
|
|
35
|
+
const HOOK_RUNTIME_PATH = fileURLToPath(new URL("./hook-entry.js", import.meta.url));
|
|
101
36
|
async function readOptionalJson(path) {
|
|
102
37
|
try {
|
|
103
38
|
return JSON.parse(await readFile(path, "utf8"));
|
|
@@ -115,209 +50,189 @@ async function installedHarness(root) {
|
|
|
115
50
|
return "both";
|
|
116
51
|
}
|
|
117
52
|
}
|
|
118
|
-
async function repositoryTrust(root, config) {
|
|
119
|
-
const home = process.env.AGENT_OPS_HOME ?? homedir();
|
|
120
|
-
const state = localStatePaths(home);
|
|
121
|
-
const remote = (() => {
|
|
122
|
-
try {
|
|
123
|
-
return execFileSync("git", ["config", "--get", "remote.origin.url"], {
|
|
124
|
-
cwd: root,
|
|
125
|
-
encoding: "utf8"
|
|
126
|
-
}).trim();
|
|
127
|
-
}
|
|
128
|
-
catch {
|
|
129
|
-
return `local:${root}`;
|
|
130
|
-
}
|
|
131
|
-
})();
|
|
132
|
-
try {
|
|
133
|
-
const binding = await calculateTrustBinding({
|
|
134
|
-
repositoryPath: root,
|
|
135
|
-
remoteUrl: remote,
|
|
136
|
-
configHash: sha256(JSON.stringify(config)),
|
|
137
|
-
runtimeHash: sha256(CLI_VERSION)
|
|
138
|
-
});
|
|
139
|
-
return (await new FileTrustStore(state.trustStore, state.anchorDirectory).status(binding)).status;
|
|
140
|
-
}
|
|
141
|
-
catch {
|
|
142
|
-
return "UNTRUSTED";
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
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
|
+
});
|
|
146
59
|
return await confirmPlan(formatInstallPlan(plan));
|
|
147
60
|
}
|
|
148
61
|
async function confirmPlan(text) {
|
|
149
62
|
process.stdout.write(text);
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
prompt.close();
|
|
160
|
-
}
|
|
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);
|
|
161
72
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
adapters: commonHarnessAdapters(),
|
|
192
|
-
isTTY,
|
|
193
|
-
toolkitVersion: CLI_VERSION,
|
|
194
|
-
confirm: async (plan) => await confirmInit(plan)
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
if (args.command === "doctor") {
|
|
198
|
-
const config = (await loadEffectiveConfig(root, args.scope === "user" ? "user" : "project")).config;
|
|
199
|
-
return await runDoctorCommand({
|
|
200
|
-
root,
|
|
201
|
-
probes: {
|
|
202
|
-
hookRegistration: async () => hookRegistrationSatisfied({
|
|
203
|
-
harness: await installedHarness(root),
|
|
204
|
-
profiles: config.profiles,
|
|
205
|
-
claudeSettings: await readOptionalJson(join(root, ".claude", "settings.json")),
|
|
206
|
-
codexHooks: await readOptionalJson(join(root, ".codex", "hooks.json"))
|
|
207
|
-
}),
|
|
208
|
-
repositoryTrust: async () => repositoryTrustStatus(await repositoryTrust(root, config)),
|
|
209
|
-
smokeAvailability: () => smokeAvailabilityStatus(config)
|
|
210
|
-
}
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
if (args.command === "uninstall") {
|
|
214
|
-
return await runUninstallCommand({
|
|
215
|
-
args,
|
|
216
|
-
root,
|
|
217
|
-
isTTY,
|
|
218
|
-
confirm: async (plan) => await confirmPlan(formatUninstallPlan(plan))
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
if (args.command === "update") {
|
|
222
|
-
return await runUpdateCommand({
|
|
223
|
-
args,
|
|
224
|
-
root,
|
|
225
|
-
adapters: commonHarnessAdapters(),
|
|
226
|
-
registry: new NpmRegistryClient(),
|
|
227
|
-
isTTY,
|
|
228
|
-
confirm: async (plan) => await confirmPlan(formatUpdatePlan(plan)),
|
|
229
|
-
...(args.targetVersion === undefined
|
|
230
|
-
? {}
|
|
231
|
-
: { targetVersion: args.targetVersion })
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
const taskService = new TaskService(new FileTaskStore(join(root, ".agent-ops", "tasks", "state.json"), root));
|
|
235
|
-
if (args.command === "task") {
|
|
236
|
-
const sessionId = process.env.AGENT_OPS_SESSION_ID;
|
|
237
|
-
return await runTaskCommand({
|
|
238
|
-
args,
|
|
239
|
-
service: taskService,
|
|
240
|
-
...(sessionId === undefined ? {} : { sessionId })
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
if (args.command === "review") {
|
|
244
|
-
return await runReviewCommand({
|
|
245
|
-
args,
|
|
246
|
-
authorized: args.yes
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
if (args.command === "config") {
|
|
250
|
-
return explainConfigCommand(await loadEffectiveConfig(root, args.scope === "user" ? "user" : "project"));
|
|
251
|
-
}
|
|
252
|
-
if (args.command === "verify") {
|
|
253
|
-
const merged = await loadEffectiveConfig(root, args.scope === "user" ? "user" : "project");
|
|
254
|
-
const config = merged.config;
|
|
255
|
-
const trustStatus = await repositoryTrust(root, config);
|
|
256
|
-
return await runVerifyCommand({
|
|
257
|
-
args,
|
|
258
|
-
taskService,
|
|
259
|
-
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,
|
|
260
102
|
root,
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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,
|
|
285
172
|
taskService,
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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", {
|