@kylecheng3146/agent-ops 0.0.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 +83 -0
- package/SECURITY.md +22 -0
- package/dist/packages/cli/src/args.js +322 -0
- package/dist/packages/cli/src/bin.js +290 -0
- package/dist/packages/cli/src/cli.js +80 -0
- package/dist/packages/cli/src/commands/config.js +8 -0
- package/dist/packages/cli/src/commands/doctor.js +32 -0
- package/dist/packages/cli/src/commands/index.js +16 -0
- package/dist/packages/cli/src/commands/init.js +65 -0
- package/dist/packages/cli/src/commands/review.js +71 -0
- package/dist/packages/cli/src/commands/task.js +141 -0
- package/dist/packages/cli/src/commands/trust.js +48 -0
- package/dist/packages/cli/src/commands/uninstall.js +58 -0
- package/dist/packages/cli/src/commands/update.js +58 -0
- package/dist/packages/cli/src/commands/verify.js +108 -0
- package/dist/packages/cli/src/output.js +53 -0
- package/dist/packages/cli/src/plan-output.js +28 -0
- package/dist/packages/cli/src/wizard.js +75 -0
- package/dist/runtime/src/adapters/claude/config.js +109 -0
- package/dist/runtime/src/adapters/claude/events.js +8 -0
- package/dist/runtime/src/adapters/claude/input.js +38 -0
- package/dist/runtime/src/adapters/claude/output.js +30 -0
- package/dist/runtime/src/adapters/codex/config.js +93 -0
- package/dist/runtime/src/adapters/codex/events.js +8 -0
- package/dist/runtime/src/adapters/codex/input.js +41 -0
- package/dist/runtime/src/adapters/codex/output.js +24 -0
- package/dist/runtime/src/config/explain.js +34 -0
- package/dist/runtime/src/config/load.js +25 -0
- package/dist/runtime/src/config/merge.js +170 -0
- package/dist/runtime/src/config/migrate.js +61 -0
- package/dist/runtime/src/contracts.js +1 -0
- package/dist/runtime/src/discovery/go.js +145 -0
- package/dist/runtime/src/discovery/index.js +40 -0
- package/dist/runtime/src/discovery/make.js +162 -0
- package/dist/runtime/src/discovery/node.js +175 -0
- package/dist/runtime/src/discovery/python.js +163 -0
- package/dist/runtime/src/discovery/rust.js +159 -0
- package/dist/runtime/src/discovery/types.js +1 -0
- package/dist/runtime/src/fs/hash.js +19 -0
- package/dist/runtime/src/fs/managed-block.js +90 -0
- package/dist/runtime/src/fs/manifest.js +24 -0
- package/dist/runtime/src/fs/mutation-worker.js +185 -0
- package/dist/runtime/src/fs/paths.js +96 -0
- package/dist/runtime/src/fs/transaction.js +498 -0
- package/dist/runtime/src/guardrails/destructive.js +207 -0
- package/dist/runtime/src/guardrails/evaluate.js +9 -0
- package/dist/runtime/src/guardrails/exceptions.js +49 -0
- package/dist/runtime/src/guardrails/secrets.js +97 -0
- package/dist/runtime/src/guardrails/types.js +9 -0
- package/dist/runtime/src/hooks/dispatch.js +78 -0
- package/dist/runtime/src/hooks/events.js +1 -0
- package/dist/runtime/src/hooks/hook-entry.js +19 -0
- package/dist/runtime/src/hooks/normalize.js +59 -0
- package/dist/runtime/src/hooks/output.js +12 -0
- package/dist/runtime/src/hooks/shell.js +138 -0
- package/dist/runtime/src/hooks/stop-verify.js +70 -0
- package/dist/runtime/src/install/apply.js +70 -0
- package/dist/runtime/src/install/doctor.js +196 -0
- package/dist/runtime/src/install/harness.js +87 -0
- package/dist/runtime/src/install/ownership.js +84 -0
- package/dist/runtime/src/install/plan.js +257 -0
- package/dist/runtime/src/install/profiles.js +28 -0
- package/dist/runtime/src/install/types.js +1 -0
- package/dist/runtime/src/install/uninstall.js +206 -0
- package/dist/runtime/src/install/update.js +123 -0
- package/dist/runtime/src/logging/local-log.js +158 -0
- package/dist/runtime/src/registry/npm.js +141 -0
- package/dist/runtime/src/review/claude-runner.js +4 -0
- package/dist/runtime/src/review/codex-runner.js +4 -0
- package/dist/runtime/src/review/packet.js +10 -0
- package/dist/runtime/src/review/result.js +24 -0
- package/dist/runtime/src/review/roles.js +3 -0
- package/dist/runtime/src/review/runner.js +45 -0
- package/dist/runtime/src/schema/validate.js +584 -0
- package/dist/runtime/src/security/permissions.js +654 -0
- package/dist/runtime/src/security/redact.js +41 -0
- package/dist/runtime/src/security/trust.js +209 -0
- package/dist/runtime/src/task/render.js +43 -0
- package/dist/runtime/src/task/service.js +235 -0
- package/dist/runtime/src/task/store.js +265 -0
- package/dist/runtime/src/verify/change-surface.js +86 -0
- package/dist/runtime/src/verify/evidence.js +89 -0
- package/dist/runtime/src/verify/fingerprint.js +67 -0
- package/dist/runtime/src/verify/scope.js +69 -0
- package/dist/runtime/src/verify/service.js +217 -0
- package/dist/runtime/src/verify/spawn.js +326 -0
- package/dist/runtime/src/verify/test-count.js +148 -0
- package/docs/en/spec/README.md +13 -0
- package/docs/en/spec/acceptance-and-evidence.md +21 -0
- package/docs/en/spec/delegation.md +21 -0
- package/docs/en/spec/guardrails.md +21 -0
- package/docs/en/spec/harness-adapters.md +21 -0
- package/docs/en/spec/judgment.md +21 -0
- package/docs/en/spec/loop-engineering.md +23 -0
- package/docs/en/spec/maintenance.md +21 -0
- package/docs/en/spec/review.md +21 -0
- package/docs/en/spec/troubleshooting.md +21 -0
- package/docs/zh-TW/spec/README.md +13 -0
- package/docs/zh-TW/spec/acceptance-and-evidence.md +23 -0
- package/docs/zh-TW/spec/delegation.md +23 -0
- package/docs/zh-TW/spec/guardrails.md +23 -0
- package/docs/zh-TW/spec/harness-adapters.md +23 -0
- package/docs/zh-TW/spec/judgment.md +23 -0
- package/docs/zh-TW/spec/loop-engineering.md +23 -0
- package/docs/zh-TW/spec/maintenance.md +23 -0
- package/docs/zh-TW/spec/review.md +23 -0
- package/docs/zh-TW/spec/troubleshooting.md +23 -0
- package/package.json +41 -0
- package/schemas/config.schema.json +231 -0
- package/schemas/evidence.schema.json +115 -0
- package/schemas/manifest.schema.json +116 -0
- package/schemas/task.schema.json +59 -0
- package/templates/common/AGENTS.block.md +3 -0
- package/templates/common/CLAUDE.block.md +3 -0
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { createInterface } from "node:readline/promises";
|
|
5
|
+
import { execFileSync } from "node:child_process";
|
|
6
|
+
import { commonHarnessAdapters } from "../../../runtime/src/install/harness.js";
|
|
7
|
+
import { NpmRegistryClient } from "../../../runtime/src/registry/npm.js";
|
|
8
|
+
import { TaskService } from "../../../runtime/src/task/service.js";
|
|
9
|
+
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
|
+
import { FileTrustStore, calculateTrustBinding } from "../../../runtime/src/security/trust.js";
|
|
14
|
+
import { localStatePaths } from "../../../runtime/src/security/permissions.js";
|
|
15
|
+
import { sha256 } from "../../../runtime/src/fs/hash.js";
|
|
16
|
+
import { FileEvidenceStore } from "../../../runtime/src/verify/evidence.js";
|
|
17
|
+
import { VerificationService } from "../../../runtime/src/verify/service.js";
|
|
18
|
+
import { NodeVerificationProcessRunner } from "../../../runtime/src/verify/spawn.js";
|
|
19
|
+
import { runCli } from "./cli.js";
|
|
20
|
+
import { createCommandRegistry } from "./commands/index.js";
|
|
21
|
+
import { explainConfigCommand } from "./commands/config.js";
|
|
22
|
+
import { runDoctorCommand } from "./commands/doctor.js";
|
|
23
|
+
import { formatInstallPlan, runInitCommand } from "./commands/init.js";
|
|
24
|
+
import { formatUninstallPlan, runUninstallCommand } from "./commands/uninstall.js";
|
|
25
|
+
import { runTaskCommand } from "./commands/task.js";
|
|
26
|
+
import { runReviewCommand } from "./commands/review.js";
|
|
27
|
+
import { runTrustCommand } from "./commands/trust.js";
|
|
28
|
+
import { runVerifyCommand } from "./commands/verify.js";
|
|
29
|
+
import { formatUpdatePlan, runUpdateCommand } from "./commands/update.js";
|
|
30
|
+
import { errorEnvelope } from "./output.js";
|
|
31
|
+
const CLI_VERSION = "0.1.0";
|
|
32
|
+
const DEFAULT_CONFIG = {
|
|
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) {
|
|
47
|
+
try {
|
|
48
|
+
return await loadConfigFile(path);
|
|
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
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const project = await loadOptionalConfig(projectPath);
|
|
89
|
+
if (project !== null) {
|
|
90
|
+
layers.push({
|
|
91
|
+
source: "project",
|
|
92
|
+
sourcePath: project.sourcePath,
|
|
93
|
+
config: project.config
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return mergeConfigLayers(layers);
|
|
97
|
+
}
|
|
98
|
+
async function repositoryTrust(root, config) {
|
|
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
|
+
})();
|
|
112
|
+
try {
|
|
113
|
+
const binding = await calculateTrustBinding({
|
|
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;
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return "UNTRUSTED";
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
async function confirmInit(plan) {
|
|
126
|
+
return await confirmPlan(formatInstallPlan(plan));
|
|
127
|
+
}
|
|
128
|
+
async function confirmPlan(text) {
|
|
129
|
+
process.stdout.write(text);
|
|
130
|
+
const prompt = createInterface({
|
|
131
|
+
input: process.stdin,
|
|
132
|
+
output: process.stdout
|
|
133
|
+
});
|
|
134
|
+
try {
|
|
135
|
+
const answer = await prompt.question("Apply this installation plan? [y/N]: ");
|
|
136
|
+
return ["y", "yes"].includes(answer.trim().toLowerCase());
|
|
137
|
+
}
|
|
138
|
+
finally {
|
|
139
|
+
prompt.close();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
process.exitCode = await runCli(process.argv.slice(2), {
|
|
143
|
+
isTTY: process.stdin.isTTY === true && process.stdout.isTTY === true,
|
|
144
|
+
input: process.stdin,
|
|
145
|
+
output: process.stdout,
|
|
146
|
+
writeStdout: (value) => process.stdout.write(value),
|
|
147
|
+
writeStderr: (value) => process.stderr.write(value)
|
|
148
|
+
}, {
|
|
149
|
+
version: CLI_VERSION,
|
|
150
|
+
registry: createCommandRegistry(Object.fromEntries([
|
|
151
|
+
"init",
|
|
152
|
+
"config",
|
|
153
|
+
"trust",
|
|
154
|
+
"doctor",
|
|
155
|
+
"update",
|
|
156
|
+
"uninstall",
|
|
157
|
+
"task",
|
|
158
|
+
"verify",
|
|
159
|
+
"review"
|
|
160
|
+
].map((command) => [command, async (args) => {
|
|
161
|
+
const root = args.scope === "user"
|
|
162
|
+
? process.env.AGENT_OPS_HOME ?? homedir()
|
|
163
|
+
: process.cwd();
|
|
164
|
+
const isTTY = !args.json &&
|
|
165
|
+
process.stdin.isTTY === true &&
|
|
166
|
+
process.stdout.isTTY === true;
|
|
167
|
+
if (args.command === "init") {
|
|
168
|
+
return await runInitCommand({
|
|
169
|
+
args,
|
|
170
|
+
root,
|
|
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({
|
|
227
|
+
root,
|
|
228
|
+
scope: args.scope === "user" ? "user" : "project",
|
|
229
|
+
config,
|
|
230
|
+
gitRunner: {
|
|
231
|
+
run: async (gitArgs) => {
|
|
232
|
+
try {
|
|
233
|
+
return {
|
|
234
|
+
exitCode: 0,
|
|
235
|
+
stdout: execFileSync("git", [...gitArgs], {
|
|
236
|
+
cwd: root,
|
|
237
|
+
encoding: "buffer",
|
|
238
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
239
|
+
})
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
const failure = error;
|
|
244
|
+
return {
|
|
245
|
+
exitCode: failure.status ?? 1,
|
|
246
|
+
stdout: failure.stdout ?? new Uint8Array()
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
processRunner: new NodeVerificationProcessRunner(),
|
|
252
|
+
taskService,
|
|
253
|
+
evidenceStore: new FileEvidenceStore(root, root),
|
|
254
|
+
trusted: trustStatus === "TRUSTED"
|
|
255
|
+
})
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
if (args.command === "trust") {
|
|
259
|
+
const config = (await loadEffectiveConfig(root, args.scope === "user" ? "user" : "project")).config;
|
|
260
|
+
const state = localStatePaths(process.env.AGENT_OPS_HOME ?? homedir());
|
|
261
|
+
const remote = (() => {
|
|
262
|
+
try {
|
|
263
|
+
return execFileSync("git", ["config", "--get", "remote.origin.url"], {
|
|
264
|
+
cwd: root,
|
|
265
|
+
encoding: "utf8"
|
|
266
|
+
}).trim();
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
return `local:${root}`;
|
|
270
|
+
}
|
|
271
|
+
})();
|
|
272
|
+
const binding = await calculateTrustBinding({
|
|
273
|
+
repositoryPath: root,
|
|
274
|
+
remoteUrl: remote,
|
|
275
|
+
configHash: sha256(JSON.stringify(config)),
|
|
276
|
+
runtimeHash: sha256(CLI_VERSION)
|
|
277
|
+
});
|
|
278
|
+
return await runTrustCommand({
|
|
279
|
+
action: args.action,
|
|
280
|
+
yes: args.yes,
|
|
281
|
+
isTTY,
|
|
282
|
+
calculateBinding: async () => binding,
|
|
283
|
+
presentBinding: async () => undefined,
|
|
284
|
+
confirmGrant: async () => await confirmPlan(JSON.stringify(binding, null, 2)),
|
|
285
|
+
store: new FileTrustStore(state.trustStore, state.anchorDirectory)
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
return errorEnvelope("CLI_COMMAND_UNAVAILABLE", `Command is not implemented yet: ${args.command}`);
|
|
289
|
+
}])))
|
|
290
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { CliArgumentError, parseArgs } from "./args.js";
|
|
2
|
+
import { errorEnvelope, okEnvelope, writeEnvelope } from "./output.js";
|
|
3
|
+
import { completeInitChoices } from "./wizard.js";
|
|
4
|
+
export const HELP_TEXT = `Usage: agent-ops <command> [options]
|
|
5
|
+
|
|
6
|
+
Commands:
|
|
7
|
+
init Plan or install agent-ops
|
|
8
|
+
config explain
|
|
9
|
+
Inspect effective configuration provenance
|
|
10
|
+
trust <status|grant|revoke>
|
|
11
|
+
Manage explicit repository trust
|
|
12
|
+
doctor Diagnose an installation
|
|
13
|
+
update Update managed artifacts
|
|
14
|
+
uninstall Remove managed artifacts
|
|
15
|
+
task <create|status|attach|complete|archive|export>
|
|
16
|
+
Manage independent task acceptance state
|
|
17
|
+
verify Run configured verification
|
|
18
|
+
review Run an independent review
|
|
19
|
+
|
|
20
|
+
Options:
|
|
21
|
+
--scope <project|user>
|
|
22
|
+
--harness <both|claude|codex>
|
|
23
|
+
--profile <core|advisory|guardrails> Repeatable
|
|
24
|
+
--task <id>
|
|
25
|
+
--target-version <version> Update target version (offline-capable)
|
|
26
|
+
--title <text>
|
|
27
|
+
--criterion <json> Repeatable
|
|
28
|
+
--evidence <criterion-id=reference> Repeatable
|
|
29
|
+
--session <id>
|
|
30
|
+
--dry-run
|
|
31
|
+
--json
|
|
32
|
+
--yes
|
|
33
|
+
--help
|
|
34
|
+
--version
|
|
35
|
+
`;
|
|
36
|
+
function wantsJson(argv) {
|
|
37
|
+
return argv.includes("--json");
|
|
38
|
+
}
|
|
39
|
+
function writeAndReturn(io, envelope, json, exitCode) {
|
|
40
|
+
writeEnvelope(io, envelope, json);
|
|
41
|
+
return exitCode;
|
|
42
|
+
}
|
|
43
|
+
export async function runCli(argv, io, services) {
|
|
44
|
+
const json = wantsJson(argv);
|
|
45
|
+
let args;
|
|
46
|
+
try {
|
|
47
|
+
args = parseArgs(argv);
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
if (error instanceof CliArgumentError) {
|
|
51
|
+
return writeAndReturn(io, errorEnvelope(error.code, error.message), json, 2);
|
|
52
|
+
}
|
|
53
|
+
return writeAndReturn(io, errorEnvelope("CLI_INTERNAL_ERROR", "Unable to parse command arguments."), json, 1);
|
|
54
|
+
}
|
|
55
|
+
if (args.command === "help") {
|
|
56
|
+
return writeAndReturn(io, okEnvelope("CLI_HELP", { text: HELP_TEXT }), args.json, 0);
|
|
57
|
+
}
|
|
58
|
+
if (args.command === "version") {
|
|
59
|
+
return writeAndReturn(io, okEnvelope("CLI_VERSION", { version: services.version }), args.json, 0);
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
if (args.command === "init") {
|
|
63
|
+
args = await completeInitChoices(args, args.json ? { ...io, isTTY: false } : io);
|
|
64
|
+
}
|
|
65
|
+
const execute = args.command === "help" || args.command === "version"
|
|
66
|
+
? services.execute
|
|
67
|
+
: services.registry?.get(args.command) ?? services.execute;
|
|
68
|
+
if (execute === undefined) {
|
|
69
|
+
return writeAndReturn(io, errorEnvelope("CLI_COMMAND_UNAVAILABLE", `Command is not implemented yet: ${args.command}`), args.json, 1);
|
|
70
|
+
}
|
|
71
|
+
const result = await execute(args);
|
|
72
|
+
return writeAndReturn(io, result, args.json, result.status === "ok" ? 0 : 1);
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
if (error instanceof CliArgumentError) {
|
|
76
|
+
return writeAndReturn(io, errorEnvelope(error.code, error.message), args.json, 2);
|
|
77
|
+
}
|
|
78
|
+
return writeAndReturn(io, errorEnvelope("CLI_INTERNAL_ERROR", "Command execution failed."), args.json, 1);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { explainConfig } from "../../../../runtime/src/config/explain.js";
|
|
2
|
+
import { okEnvelope } from "../output.js";
|
|
3
|
+
export function explainConfigCommand(merged) {
|
|
4
|
+
return okEnvelope("CONFIG_EXPLAIN", {
|
|
5
|
+
...explainConfig(merged),
|
|
6
|
+
message: "Effective configuration provenance calculated."
|
|
7
|
+
});
|
|
8
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { doctorInstallation } from "../../../../runtime/src/install/doctor.js";
|
|
2
|
+
function formatDoctorReport(report) {
|
|
3
|
+
return `${[
|
|
4
|
+
"Installation doctor",
|
|
5
|
+
...report.checks.map(({ id, status, message }) => `- ${status} ${id}: ${message}`)
|
|
6
|
+
].join("\n")}\n`;
|
|
7
|
+
}
|
|
8
|
+
export async function runDoctorCommand(options) {
|
|
9
|
+
const report = await doctorInstallation(options);
|
|
10
|
+
const hasFailure = report.checks.some(({ status }) => status === "FAIL");
|
|
11
|
+
const hasUnknown = report.checks.some(({ status }) => status === "UNKNOWN");
|
|
12
|
+
const code = hasFailure
|
|
13
|
+
? "DOCTOR_FAILED"
|
|
14
|
+
: hasUnknown
|
|
15
|
+
? "DOCTOR_UNKNOWN"
|
|
16
|
+
: "DOCTOR_OK";
|
|
17
|
+
const message = hasFailure
|
|
18
|
+
? "Installation diagnostics found failures."
|
|
19
|
+
: hasUnknown
|
|
20
|
+
? "Installation diagnostics contain unknown checks."
|
|
21
|
+
: "Installation diagnostics passed.";
|
|
22
|
+
return {
|
|
23
|
+
code,
|
|
24
|
+
status: hasFailure || hasUnknown ? "error" : "ok",
|
|
25
|
+
data: {
|
|
26
|
+
report,
|
|
27
|
+
message,
|
|
28
|
+
text: formatDoctorReport(report)
|
|
29
|
+
},
|
|
30
|
+
errors: hasFailure || hasUnknown ? [{ code, message }] : []
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function createCommandRegistry(handlers) {
|
|
2
|
+
const entries = new Map();
|
|
3
|
+
for (const [command, handler] of Object.entries(handlers)) {
|
|
4
|
+
if (handler !== undefined) {
|
|
5
|
+
entries.set(command, handler);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
return {
|
|
9
|
+
get(command) {
|
|
10
|
+
return entries.get(command);
|
|
11
|
+
},
|
|
12
|
+
commands() {
|
|
13
|
+
return [...entries.keys()];
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { AgentOpsError } from "../../../../runtime/src/fs/paths.js";
|
|
2
|
+
import { createInstallPlan } from "../../../../runtime/src/install/plan.js";
|
|
3
|
+
import { applyInstallPlan } from "../../../../runtime/src/install/apply.js";
|
|
4
|
+
import { okEnvelope } from "../output.js";
|
|
5
|
+
import { formatOperationPlan } from "../plan-output.js";
|
|
6
|
+
export function formatInstallPlan(plan) {
|
|
7
|
+
return formatOperationPlan({
|
|
8
|
+
title: "Installation plan",
|
|
9
|
+
metadata: [
|
|
10
|
+
`Scope: ${plan.scope}`,
|
|
11
|
+
`Harness: ${plan.harness}`,
|
|
12
|
+
`Profiles: ${plan.profiles.join(", ")}`
|
|
13
|
+
],
|
|
14
|
+
operations: plan.operations
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
function initError(code, message, plan) {
|
|
18
|
+
return {
|
|
19
|
+
code,
|
|
20
|
+
status: "error",
|
|
21
|
+
data: { applied: false, plan, message },
|
|
22
|
+
errors: [{ code, message }]
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export async function runInitCommand(options) {
|
|
26
|
+
const { args } = options;
|
|
27
|
+
if (args.command !== "init" ||
|
|
28
|
+
args.scope === undefined ||
|
|
29
|
+
args.harness === undefined ||
|
|
30
|
+
args.profiles.length === 0) {
|
|
31
|
+
throw new AgentOpsError("INIT_CHOICES_REQUIRED", "Init requires complete scope, harness, and profile choices.");
|
|
32
|
+
}
|
|
33
|
+
const plan = await createInstallPlan({
|
|
34
|
+
root: options.root,
|
|
35
|
+
scope: args.scope,
|
|
36
|
+
harness: args.harness,
|
|
37
|
+
profiles: args.profiles,
|
|
38
|
+
adapters: options.adapters,
|
|
39
|
+
...(options.toolkitVersion === undefined
|
|
40
|
+
? {}
|
|
41
|
+
: { toolkitVersion: options.toolkitVersion })
|
|
42
|
+
});
|
|
43
|
+
if (args.dryRun) {
|
|
44
|
+
return okEnvelope("INIT_PLAN_READY", {
|
|
45
|
+
applied: false,
|
|
46
|
+
plan,
|
|
47
|
+
message: "Installation plan calculated; no files were written.",
|
|
48
|
+
text: formatInstallPlan(plan)
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
if (!args.yes && !options.isTTY) {
|
|
52
|
+
return initError("INIT_CONFIRMATION_REQUIRED", "Non-interactive init requires --yes after all choices are explicit.", plan);
|
|
53
|
+
}
|
|
54
|
+
if (!args.yes &&
|
|
55
|
+
options.isTTY &&
|
|
56
|
+
!(await options.confirm(plan))) {
|
|
57
|
+
return initError("INIT_CANCELLED", "Installation was cancelled; no files were written.", plan);
|
|
58
|
+
}
|
|
59
|
+
await applyInstallPlan(options.root, plan);
|
|
60
|
+
return okEnvelope("INIT_APPLIED", {
|
|
61
|
+
applied: true,
|
|
62
|
+
plan,
|
|
63
|
+
message: "Loop Engineering Toolkit installation applied."
|
|
64
|
+
});
|
|
65
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { buildReviewPacket } from "../../../../runtime/src/review/packet.js";
|
|
2
|
+
import { runIndependentReview } from "../../../../runtime/src/review/runner.js";
|
|
3
|
+
import { resolveReviewRole } from "../../../../runtime/src/review/roles.js";
|
|
4
|
+
import { okEnvelope } from "../output.js";
|
|
5
|
+
function harness(value) {
|
|
6
|
+
return value === "claude" ? "claude" : "codex";
|
|
7
|
+
}
|
|
8
|
+
export async function runReviewCommand(options) {
|
|
9
|
+
const ids = options.args.criteria ?? [];
|
|
10
|
+
const criteria = ids.map((id) => ({
|
|
11
|
+
id,
|
|
12
|
+
description: id
|
|
13
|
+
}));
|
|
14
|
+
const evidenceRequirements = (options.args.evidence ?? []).map((value) => {
|
|
15
|
+
const separator = value.indexOf("=");
|
|
16
|
+
return {
|
|
17
|
+
criterionId: separator < 0 ? value : value.slice(0, separator),
|
|
18
|
+
requirement: separator < 0 ? value : value.slice(separator + 1)
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
const selectedHarness = harness(options.args.harness);
|
|
22
|
+
const role = resolveReviewRole(options.role ?? "independent-review", options.roles ?? []);
|
|
23
|
+
const result = await runIndependentReview({
|
|
24
|
+
invocation: {
|
|
25
|
+
harness: role?.harness ?? selectedHarness,
|
|
26
|
+
model: role?.model ?? options.model ?? "configured",
|
|
27
|
+
effort: role?.effort ?? options.effort ?? "configured",
|
|
28
|
+
packet: buildReviewPacket({
|
|
29
|
+
request: "Review the requested implementation.",
|
|
30
|
+
criteria,
|
|
31
|
+
artifactRefs: [],
|
|
32
|
+
evidenceRequirements
|
|
33
|
+
})
|
|
34
|
+
},
|
|
35
|
+
authorized: options.authorized,
|
|
36
|
+
execute: options.execute ?? (async () => ({
|
|
37
|
+
status: "NOT_RUN",
|
|
38
|
+
reason: "missing-cli"
|
|
39
|
+
}))
|
|
40
|
+
});
|
|
41
|
+
const message = result.status === "PASS"
|
|
42
|
+
? "Independent review passed."
|
|
43
|
+
: result.status === "FAIL"
|
|
44
|
+
? "Independent review failed."
|
|
45
|
+
: "Independent review was not run.";
|
|
46
|
+
const data = {
|
|
47
|
+
message,
|
|
48
|
+
result,
|
|
49
|
+
text: [
|
|
50
|
+
message,
|
|
51
|
+
`Status: ${result.status}`,
|
|
52
|
+
`Harness: ${result.harness}; model: ${result.model}; effort: ${result.effort}.`,
|
|
53
|
+
...(result.reason === undefined ? [] : [`Reason: ${result.reason}.`]),
|
|
54
|
+
...(result.results === undefined
|
|
55
|
+
? []
|
|
56
|
+
: result.results.map((item) => `${item.criterionId}: ${item.status} [${item.evidence.join(", ")}]`)),
|
|
57
|
+
result.prompt,
|
|
58
|
+
""
|
|
59
|
+
].join("\n")
|
|
60
|
+
};
|
|
61
|
+
if (result.status === "PASS") {
|
|
62
|
+
return okEnvelope("REVIEW_RESULT", data);
|
|
63
|
+
}
|
|
64
|
+
const code = result.status === "FAIL" ? "REVIEW_FAILED" : "REVIEW_NOT_RUN";
|
|
65
|
+
return {
|
|
66
|
+
code,
|
|
67
|
+
status: "error",
|
|
68
|
+
data,
|
|
69
|
+
errors: [{ code, message }]
|
|
70
|
+
};
|
|
71
|
+
}
|