@yansirplus/cli 0.5.17 → 0.5.18
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 +12 -6
- package/agent-catalog/agentOS/SKILL.md +22 -0
- package/agent-catalog/agentOS/references/agent/decision-graph.json +530 -0
- package/agent-catalog/agentOS/references/agent/errors.json +497 -0
- package/agent-catalog/agentOS/references/agent/invariant-matrix.json +337 -0
- package/agent-catalog/agentOS/references/agent/primitives.json +989 -0
- package/agent-catalog/agentOS/references/agent/recipes.json +109 -0
- package/agent-catalog/agentOS/references/agent/start-here.md +25 -0
- package/agent-catalog/agentOS/references/package-map.md +72 -0
- package/agent-catalog/agentOS/references/provenance.json +251 -0
- package/agent-catalog/agentOS/references/public-api/cli.md +20 -0
- package/agent-catalog/agentOS/references/public-api/client.md +88 -0
- package/agent-catalog/agentOS/references/public-api/core.md +1817 -0
- package/agent-catalog/agentOS/references/public-api/runtime.md +794 -0
- package/dist/build/agent-authoring/config.d.ts +20 -5
- package/dist/build/agent-authoring/config.js +132 -32
- package/dist/build/agent-authoring/manifest-compiler.d.ts +131 -2
- package/dist/build/agent-authoring/manifest-compiler.js +630 -8
- package/dist/build/agent-authoring/shared.d.ts +2 -0
- package/dist/build/agent-authoring/shared.js +2 -0
- package/dist/build/agent-authoring/static-target.d.ts +6 -3
- package/dist/build/agent-authoring/static-target.js +1807 -286
- package/dist/build/agent-authoring.d.ts +3 -3
- package/dist/build/agent-authoring.js +1 -1
- package/dist/build/build-cli.d.ts +1 -1
- package/dist/build/build-cli.js +1614 -26
- package/dist/check/algorithmic/client-boundary-checks.mjs +3 -34
- package/dist/check/algorithmic/convergence-smoke-checks.mjs +652 -6
- package/dist/check/algorithmic/distribution-checks.mjs +8 -7
- package/dist/check/algorithmic/package-boundary-checks.mjs +3 -2
- package/dist/check/algorithmic/repo-surface-checks.mjs +55 -1
- package/dist/check/algorithmic/static-target-checks.mjs +83 -5
- package/dist/check/algorithmic-checks.mjs +10 -17
- package/dist/check/default-gate.mjs +3 -3
- package/dist/check/effect-scan-gate.mjs +121 -0
- package/dist/check/package-graph.mjs +2 -32
- package/dist/consumer-overlay.mjs +802 -0
- package/dist/lib/public-api-model.mjs +19 -0
- package/dist/lib/repo-source-files.mjs +26 -0
- package/dist/lib/ts-module-loader.mjs +44 -0
- package/dist/lib/workspace-manifest.mjs +77 -0
- package/dist/main.mjs +151 -21
- package/package.json +8 -4
- package/dist/check/check-coverage.mjs +0 -231
- package/dist/generate/generate-agent-docs.mjs +0 -435
- package/dist/generate/generate-carrier-reference.mjs +0 -514
- package/dist/generate/generate-docs.mjs +0 -345
- package/dist/generate/generate-effect-skill-manifests.mjs +0 -193
- package/dist/generate/project-docs-site.mjs +0 -190
- package/dist/lib/boundary-rules.mjs +0 -63
- package/dist/lib/capability-routes.mjs +0 -354
- package/dist/lib/projection-sink.mjs +0 -113
|
@@ -0,0 +1,802 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import crypto from "node:crypto";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
export const installManifestProtocol = "agentos-install-manifest@1";
|
|
8
|
+
export const localConsumerMarkerName = ".agentos-local.json";
|
|
9
|
+
|
|
10
|
+
const packageRootFromModule = () => path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
11
|
+
|
|
12
|
+
const fail = (message) => {
|
|
13
|
+
throw new Error(message);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const readJson = (file) => JSON.parse(fs.readFileSync(file, "utf8"));
|
|
17
|
+
|
|
18
|
+
const writeJson = (file, value) => {
|
|
19
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
20
|
+
fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const sha256File = (file) =>
|
|
24
|
+
crypto.createHash("sha256").update(fs.readFileSync(file)).digest("hex");
|
|
25
|
+
|
|
26
|
+
const run = (cmd, args, options = {}) => {
|
|
27
|
+
const result = spawnSync(cmd, args, {
|
|
28
|
+
cwd: options.cwd ?? process.cwd(),
|
|
29
|
+
env: options.env ?? process.env,
|
|
30
|
+
encoding: "utf8",
|
|
31
|
+
stdio: options.capture === true ? ["ignore", "pipe", "pipe"] : "inherit",
|
|
32
|
+
});
|
|
33
|
+
if (result.status !== 0) {
|
|
34
|
+
const detail = options.capture === true ? `\n${result.stdout ?? ""}${result.stderr ?? ""}` : "";
|
|
35
|
+
fail(`${cmd} ${args.join(" ")} failed with exit ${result.status}${detail}`);
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const parseArgs = (args) => {
|
|
41
|
+
const parsed = { _: [] };
|
|
42
|
+
const booleanKeys = new Set(["skip-pack", "no-install", "json", "check-npm"]);
|
|
43
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
44
|
+
const arg = args[index];
|
|
45
|
+
if (!arg.startsWith("--")) {
|
|
46
|
+
parsed._.push(arg);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const eq = arg.indexOf("=");
|
|
50
|
+
if (eq >= 0) {
|
|
51
|
+
parsed[arg.slice(2, eq)] = arg.slice(eq + 1);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const key = arg.slice(2);
|
|
55
|
+
if (booleanKeys.has(key)) {
|
|
56
|
+
parsed[key] = true;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const next = args[index + 1];
|
|
60
|
+
if (next !== undefined && !next.startsWith("--")) {
|
|
61
|
+
parsed[key] = next;
|
|
62
|
+
index += 1;
|
|
63
|
+
} else {
|
|
64
|
+
parsed[key] = true;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return parsed;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const positionalArgs = (args) => args._ ?? [];
|
|
71
|
+
|
|
72
|
+
const boolArg = (args, name) => args[name] === true || args[name] === "true";
|
|
73
|
+
|
|
74
|
+
const packageMetadata = (packageRoot = packageRootFromModule()) => {
|
|
75
|
+
const manifest = readJson(path.join(packageRoot, "package.json"));
|
|
76
|
+
const version =
|
|
77
|
+
typeof manifest.agentOsRelease?.version === "string"
|
|
78
|
+
? manifest.agentOsRelease.version
|
|
79
|
+
: manifest.version;
|
|
80
|
+
if (typeof version !== "string" || version.length === 0) {
|
|
81
|
+
fail(`${path.join(packageRoot, "package.json")}: package version must be a non-empty string`);
|
|
82
|
+
}
|
|
83
|
+
const scope = typeof manifest.name === "string" ? manifest.name.split("/")[0] : undefined;
|
|
84
|
+
return {
|
|
85
|
+
packageRoot,
|
|
86
|
+
packageName: manifest.name,
|
|
87
|
+
packageVersion: version,
|
|
88
|
+
packageScope: typeof scope === "string" && scope.startsWith("@") ? scope : undefined,
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const gitValue = (cwd, args, fallback) => {
|
|
93
|
+
const result = spawnSync("git", args, {
|
|
94
|
+
cwd,
|
|
95
|
+
encoding: "utf8",
|
|
96
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
97
|
+
});
|
|
98
|
+
if (result.status !== 0) return fallback;
|
|
99
|
+
const value = result.stdout.trim();
|
|
100
|
+
return value.length === 0 ? fallback : value;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export const sourceIdentityFor = (sourceRoot) => ({
|
|
104
|
+
repoRoot: sourceRoot,
|
|
105
|
+
branch: gitValue(sourceRoot, ["branch", "--show-current"], "unknown"),
|
|
106
|
+
head: gitValue(sourceRoot, ["rev-parse", "HEAD"], "unknown"),
|
|
107
|
+
dirty: gitValue(sourceRoot, ["status", "--short"], "").length > 0,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
export const consumerManifestFiles = (consumerRoot) =>
|
|
111
|
+
["package.json", "package-lock.json", "npm-shrinkwrap.json", "pnpm-lock.yaml", "yarn.lock"].map(
|
|
112
|
+
(name) => path.join(consumerRoot, name),
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
export const snapshotFiles = (files) =>
|
|
116
|
+
new Map(files.map((file) => [file, fs.existsSync(file) ? fs.readFileSync(file) : undefined]));
|
|
117
|
+
|
|
118
|
+
export const assertSnapshotUnchanged = (snapshot, context) => {
|
|
119
|
+
const changed = [];
|
|
120
|
+
for (const [file, before] of snapshot.entries()) {
|
|
121
|
+
const after = fs.existsSync(file) ? fs.readFileSync(file) : undefined;
|
|
122
|
+
if (
|
|
123
|
+
before === undefined
|
|
124
|
+
? after !== undefined
|
|
125
|
+
: after === undefined || !Buffer.from(before).equals(after)
|
|
126
|
+
) {
|
|
127
|
+
changed.push(path.basename(file));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (changed.length > 0) {
|
|
131
|
+
fail(`${context} changed consumer manifest/lock files:\n${changed.join("\n")}`);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export const resolveConsumerRoot = (value) => {
|
|
136
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
137
|
+
fail("consumer path is required");
|
|
138
|
+
}
|
|
139
|
+
const consumerRoot = path.resolve(process.cwd(), value);
|
|
140
|
+
if (!fs.existsSync(path.join(consumerRoot, "package.json"))) {
|
|
141
|
+
fail(`${consumerRoot}: missing package.json`);
|
|
142
|
+
}
|
|
143
|
+
return consumerRoot;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export const localConsumerMarkerPath = (consumerRoot) =>
|
|
147
|
+
path.join(consumerRoot, "node_modules", localConsumerMarkerName);
|
|
148
|
+
|
|
149
|
+
const consumerPackageManagerName = (consumerRoot) => {
|
|
150
|
+
const packageJson = readJson(path.join(consumerRoot, "package.json"));
|
|
151
|
+
const packageManager =
|
|
152
|
+
typeof packageJson.packageManager === "string" ? packageJson.packageManager : "";
|
|
153
|
+
if (packageManager.startsWith("pnpm@")) return "pnpm";
|
|
154
|
+
if (packageManager.startsWith("npm@")) return "npm";
|
|
155
|
+
if (packageManager.startsWith("bun@")) return "bun";
|
|
156
|
+
if (packageManager.startsWith("yarn@")) return "yarn";
|
|
157
|
+
if (fs.existsSync(path.join(consumerRoot, "pnpm-lock.yaml"))) return "pnpm";
|
|
158
|
+
if (fs.existsSync(path.join(consumerRoot, "package-lock.json"))) return "npm";
|
|
159
|
+
if (fs.existsSync(path.join(consumerRoot, "bun.lock"))) return "bun";
|
|
160
|
+
if (fs.existsSync(path.join(consumerRoot, "bun.lockb"))) return "bun";
|
|
161
|
+
if (fs.existsSync(path.join(consumerRoot, "yarn.lock"))) return "yarn";
|
|
162
|
+
return null;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
export const consumerInstallCommand = (consumerRoot) => {
|
|
166
|
+
const manager = consumerPackageManagerName(consumerRoot);
|
|
167
|
+
switch (manager) {
|
|
168
|
+
case "pnpm":
|
|
169
|
+
return {
|
|
170
|
+
manager,
|
|
171
|
+
cmd: "pnpm",
|
|
172
|
+
args: ["install", "--frozen-lockfile", "--ignore-scripts"],
|
|
173
|
+
env: { ...process.env, CI: "true", COREPACK_ENABLE_DOWNLOAD_PROMPT: "0" },
|
|
174
|
+
};
|
|
175
|
+
case "npm":
|
|
176
|
+
return fs.existsSync(path.join(consumerRoot, "package-lock.json"))
|
|
177
|
+
? {
|
|
178
|
+
manager,
|
|
179
|
+
cmd: "npm",
|
|
180
|
+
args: ["ci", "--ignore-scripts", "--no-audit", "--no-fund"],
|
|
181
|
+
env: process.env,
|
|
182
|
+
}
|
|
183
|
+
: {
|
|
184
|
+
manager,
|
|
185
|
+
cmd: "npm",
|
|
186
|
+
args: [
|
|
187
|
+
"install",
|
|
188
|
+
"--package-lock=false",
|
|
189
|
+
"--ignore-scripts",
|
|
190
|
+
"--no-audit",
|
|
191
|
+
"--no-fund",
|
|
192
|
+
],
|
|
193
|
+
env: process.env,
|
|
194
|
+
};
|
|
195
|
+
case "bun":
|
|
196
|
+
return {
|
|
197
|
+
manager,
|
|
198
|
+
cmd: "bun",
|
|
199
|
+
args: ["install", "--frozen-lockfile", "--ignore-scripts"],
|
|
200
|
+
env: { ...process.env, CI: "true" },
|
|
201
|
+
};
|
|
202
|
+
case "yarn":
|
|
203
|
+
return {
|
|
204
|
+
manager,
|
|
205
|
+
cmd: "yarn",
|
|
206
|
+
args: ["install", "--immutable", "--ignore-scripts"],
|
|
207
|
+
env: { ...process.env, CI: "true" },
|
|
208
|
+
};
|
|
209
|
+
default:
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
export const nodeModulesRoot = (consumerRoot, options = {}) => {
|
|
215
|
+
const root = path.join(consumerRoot, "node_modules");
|
|
216
|
+
if (fs.existsSync(root)) return root;
|
|
217
|
+
if (options.install !== true) {
|
|
218
|
+
fail(
|
|
219
|
+
`${consumerRoot}: missing node_modules; run the consumer package manager install first, or rerun install without --no-install to let agentOS run a frozen install`,
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
const installCommand = consumerInstallCommand(consumerRoot);
|
|
223
|
+
if (installCommand === null) {
|
|
224
|
+
fail(
|
|
225
|
+
`${consumerRoot}: missing node_modules and no package manager/lockfile was detected; run the consumer install first`,
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
console.log(
|
|
229
|
+
`node_modules missing; running ${installCommand.cmd} ${installCommand.args.join(" ")} in ${consumerRoot}`,
|
|
230
|
+
);
|
|
231
|
+
run(installCommand.cmd, installCommand.args, {
|
|
232
|
+
cwd: consumerRoot,
|
|
233
|
+
capture: true,
|
|
234
|
+
env: installCommand.env,
|
|
235
|
+
});
|
|
236
|
+
if (!fs.existsSync(root)) {
|
|
237
|
+
fail(`${consumerRoot}: package manager install completed but node_modules is still missing`);
|
|
238
|
+
}
|
|
239
|
+
return root;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
export const packageTargetDir = (nodeModules, packageName) =>
|
|
243
|
+
path.join(nodeModules, ...packageName.split("/"));
|
|
244
|
+
|
|
245
|
+
export const unpackTarballInto = (tarball, target) => {
|
|
246
|
+
const tmp = fs.mkdtempSync(path.join(fs.realpathSync("/tmp"), "agentos-consumer-package-"));
|
|
247
|
+
try {
|
|
248
|
+
run("tar", ["-xzf", tarball, "-C", tmp], { capture: true });
|
|
249
|
+
const packageDir = path.join(tmp, "package");
|
|
250
|
+
if (!fs.existsSync(packageDir)) {
|
|
251
|
+
fail(`${tarball}: tarball did not contain package/`);
|
|
252
|
+
}
|
|
253
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
254
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
255
|
+
fs.renameSync(packageDir, target);
|
|
256
|
+
} finally {
|
|
257
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
export const readInstallManifest = (manifestPath) => {
|
|
262
|
+
if (typeof manifestPath !== "string" || manifestPath.length === 0) {
|
|
263
|
+
fail("install manifest path is required");
|
|
264
|
+
}
|
|
265
|
+
const absolutePath = path.resolve(process.cwd(), manifestPath);
|
|
266
|
+
if (!fs.existsSync(absolutePath)) {
|
|
267
|
+
fail(`${absolutePath}: install manifest is missing`);
|
|
268
|
+
}
|
|
269
|
+
const manifest = readJson(absolutePath);
|
|
270
|
+
if (manifest === null || typeof manifest !== "object" || Array.isArray(manifest)) {
|
|
271
|
+
fail(`${absolutePath}: install manifest must be an object`);
|
|
272
|
+
}
|
|
273
|
+
if (manifest.protocol !== installManifestProtocol) {
|
|
274
|
+
fail(`${absolutePath}: install manifest protocol must be ${installManifestProtocol}`);
|
|
275
|
+
}
|
|
276
|
+
if (typeof manifest.version !== "string" || manifest.version.length === 0) {
|
|
277
|
+
fail(`${absolutePath}: install manifest version must be a non-empty string`);
|
|
278
|
+
}
|
|
279
|
+
if (manifest.tarballs === null || typeof manifest.tarballs !== "object") {
|
|
280
|
+
fail(`${absolutePath}: install manifest tarballs must be an object`);
|
|
281
|
+
}
|
|
282
|
+
return { path: absolutePath, manifest };
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const fileSpecPath = (spec) => {
|
|
286
|
+
if (typeof spec !== "string" || !spec.startsWith("file:")) {
|
|
287
|
+
fail(`expected file: tarball spec; actual ${String(spec)}`);
|
|
288
|
+
}
|
|
289
|
+
return spec.slice("file:".length);
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
export const tarballPackageEntries = (manifest) =>
|
|
293
|
+
Object.entries(manifest.tarballs)
|
|
294
|
+
.map(([packageName, entry]) => {
|
|
295
|
+
if (entry === null || typeof entry !== "object") {
|
|
296
|
+
fail(`${packageName}: invalid tarball manifest entry`);
|
|
297
|
+
}
|
|
298
|
+
const tarball = fileSpecPath(entry.spec);
|
|
299
|
+
if (!fs.existsSync(tarball)) {
|
|
300
|
+
fail(`${packageName}: tarball does not exist: ${tarball}`);
|
|
301
|
+
}
|
|
302
|
+
if (typeof entry.sha256 !== "string" || entry.sha256.length !== 64) {
|
|
303
|
+
fail(`${packageName}: tarball manifest entry sha256 must be hex64`);
|
|
304
|
+
}
|
|
305
|
+
return {
|
|
306
|
+
packageName,
|
|
307
|
+
tarball,
|
|
308
|
+
sha256: entry.sha256,
|
|
309
|
+
};
|
|
310
|
+
})
|
|
311
|
+
.sort((left, right) => left.packageName.localeCompare(right.packageName));
|
|
312
|
+
|
|
313
|
+
const markerArtifact = (manifestPath, manifest) => ({
|
|
314
|
+
kind: "install-manifest-overlay",
|
|
315
|
+
installManifest: {
|
|
316
|
+
path: manifestPath,
|
|
317
|
+
sha256: sha256File(manifestPath),
|
|
318
|
+
protocol: manifest.protocol,
|
|
319
|
+
version: manifest.version,
|
|
320
|
+
generatedBy: manifest.generatedBy,
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
const packageOverlayRows = (consumerRoot, marker) => {
|
|
325
|
+
const nodeModules = path.join(consumerRoot, "node_modules");
|
|
326
|
+
return Object.entries(marker.packages ?? {})
|
|
327
|
+
.map(([packageName, record]) => {
|
|
328
|
+
const target = packageTargetDir(nodeModules, packageName);
|
|
329
|
+
const targetExists = fs.existsSync(target);
|
|
330
|
+
const targetStatus = !targetExists
|
|
331
|
+
? "missing"
|
|
332
|
+
: fs.lstatSync(target).isSymbolicLink()
|
|
333
|
+
? "symlink"
|
|
334
|
+
: "installed";
|
|
335
|
+
const tarball = typeof record.tarball === "string" ? record.tarball : "";
|
|
336
|
+
const tarballExists = tarball.length > 0 && fs.existsSync(tarball);
|
|
337
|
+
const expectedSha = typeof record.sha256 === "string" ? record.sha256 : undefined;
|
|
338
|
+
const actualSha = tarballExists ? sha256File(tarball) : undefined;
|
|
339
|
+
return {
|
|
340
|
+
packageName,
|
|
341
|
+
target: record.target,
|
|
342
|
+
installed: targetStatus === "installed",
|
|
343
|
+
targetStatus,
|
|
344
|
+
tarball,
|
|
345
|
+
tarballStatus: tarballExists
|
|
346
|
+
? expectedSha === undefined || expectedSha === actualSha
|
|
347
|
+
? "verified"
|
|
348
|
+
: "sha_mismatch"
|
|
349
|
+
: "missing",
|
|
350
|
+
sha256: expectedSha,
|
|
351
|
+
};
|
|
352
|
+
})
|
|
353
|
+
.sort((left, right) => left.packageName.localeCompare(right.packageName));
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
const overlaySourceStatus = (marker, currentSource) => {
|
|
357
|
+
if (marker.source === undefined) return "not_recorded";
|
|
358
|
+
if (currentSource === undefined) return "not_checked";
|
|
359
|
+
if (marker.source?.repoRoot !== currentSource.repoRoot) return "foreign_source";
|
|
360
|
+
if (marker.source?.head !== currentSource.head) return "stale_source";
|
|
361
|
+
if (marker.source?.dirty !== currentSource.dirty) return "dirty_state_changed";
|
|
362
|
+
return "current_source";
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
const truthModeFor = (marker) => {
|
|
366
|
+
if (marker === undefined) return "npm_release";
|
|
367
|
+
if (marker.artifact?.kind === "install-manifest-overlay") return "local_overlay";
|
|
368
|
+
return "legacy_local_overlay";
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
const packageIntegrityFor = (marker, packages) => {
|
|
372
|
+
if (marker === undefined) {
|
|
373
|
+
return {
|
|
374
|
+
status: "not_checked",
|
|
375
|
+
reason: "local overlay marker is missing; consumer is using package-manager truth",
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
const failures = [];
|
|
379
|
+
if (packages.length === 0) {
|
|
380
|
+
failures.push({ code: "local_overlay_packages_missing", message: "marker lists no packages" });
|
|
381
|
+
}
|
|
382
|
+
for (const pkg of packages) {
|
|
383
|
+
if (pkg.targetStatus === "missing") {
|
|
384
|
+
failures.push({
|
|
385
|
+
code: "local_overlay_package_missing",
|
|
386
|
+
packageName: pkg.packageName,
|
|
387
|
+
targetStatus: pkg.targetStatus,
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
if (pkg.targetStatus === "symlink") {
|
|
391
|
+
failures.push({
|
|
392
|
+
code: "local_overlay_package_symlink",
|
|
393
|
+
packageName: pkg.packageName,
|
|
394
|
+
targetStatus: pkg.targetStatus,
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
if (pkg.tarballStatus !== "verified") {
|
|
398
|
+
failures.push({
|
|
399
|
+
code: "local_overlay_tarball_not_verified",
|
|
400
|
+
packageName: pkg.packageName,
|
|
401
|
+
tarballStatus: pkg.tarballStatus,
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return {
|
|
406
|
+
status: failures.length === 0 ? "verified" : "failed",
|
|
407
|
+
packagesChecked: packages.length,
|
|
408
|
+
failures,
|
|
409
|
+
};
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
const sourceFreshnessFor = (marker, currentSource) => {
|
|
413
|
+
const status = overlaySourceStatus(marker ?? {}, currentSource);
|
|
414
|
+
return {
|
|
415
|
+
status,
|
|
416
|
+
checked: currentSource !== undefined,
|
|
417
|
+
gate: ["current_source", "not_checked"].includes(status) ? "pass" : "fail",
|
|
418
|
+
...(status === "not_checked"
|
|
419
|
+
? { reason: "source checkout identity is unavailable in this invocation" }
|
|
420
|
+
: {}),
|
|
421
|
+
};
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
const packageVersionStatus = (marker, packageVersion) =>
|
|
425
|
+
marker.packageVersion === packageVersion ? "release_version_match" : "release_version_mismatch";
|
|
426
|
+
|
|
427
|
+
const npmLatestNotChecked = () => ({
|
|
428
|
+
status: "not_checked",
|
|
429
|
+
reason: "pass --check-npm to compare against the registry",
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
const npmLatestFor = (packageNames, registry) => {
|
|
433
|
+
const packages = {};
|
|
434
|
+
for (const packageName of packageNames) {
|
|
435
|
+
const args = ["view", packageName, "version", "--json"];
|
|
436
|
+
if (typeof registry === "string" && registry.length > 0) args.push("--registry", registry);
|
|
437
|
+
const result = spawnSync("npm", args, {
|
|
438
|
+
encoding: "utf8",
|
|
439
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
440
|
+
});
|
|
441
|
+
packages[packageName] =
|
|
442
|
+
result.status === 0
|
|
443
|
+
? { status: "resolved", version: JSON.parse(result.stdout.trim()) }
|
|
444
|
+
: { status: "unresolved", detail: result.stderr.trim() || result.stdout.trim() };
|
|
445
|
+
}
|
|
446
|
+
return { status: "checked", packages };
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
const consumerGateIssue = (code, severity, dimension, message, detail = {}) => ({
|
|
450
|
+
code,
|
|
451
|
+
severity,
|
|
452
|
+
dimension,
|
|
453
|
+
message,
|
|
454
|
+
...detail,
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
const consumerOverlayGate = (status) => {
|
|
458
|
+
const hardFailures = [];
|
|
459
|
+
const signals = [];
|
|
460
|
+
if (status.localOverlay.status === "missing") {
|
|
461
|
+
hardFailures.push(
|
|
462
|
+
consumerGateIssue(
|
|
463
|
+
"local_overlay_missing",
|
|
464
|
+
"hard",
|
|
465
|
+
"truth_mode",
|
|
466
|
+
"local consumer overlay marker is missing",
|
|
467
|
+
{ markerPath: status.markerPath },
|
|
468
|
+
),
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
if (status.localOverlay.status === "partial") {
|
|
472
|
+
hardFailures.push(
|
|
473
|
+
consumerGateIssue(
|
|
474
|
+
"local_overlay_partial",
|
|
475
|
+
"hard",
|
|
476
|
+
"package_integrity",
|
|
477
|
+
"local consumer overlay package installation is partial",
|
|
478
|
+
),
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
if (status.sourceFreshness?.gate === "fail") {
|
|
482
|
+
hardFailures.push(
|
|
483
|
+
consumerGateIssue(
|
|
484
|
+
"local_overlay_source_not_current",
|
|
485
|
+
"hard",
|
|
486
|
+
"source_freshness",
|
|
487
|
+
`local consumer overlay source freshness is ${status.sourceFreshness.status}`,
|
|
488
|
+
{ sourceStatus: status.sourceFreshness.status },
|
|
489
|
+
),
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
if (status.sourceFreshness?.status === "not_checked") {
|
|
493
|
+
signals.push(
|
|
494
|
+
consumerGateIssue(
|
|
495
|
+
"local_overlay_source_not_checked",
|
|
496
|
+
"signal",
|
|
497
|
+
"source_freshness",
|
|
498
|
+
"local overlay source was not checked by this packaged CLI invocation",
|
|
499
|
+
),
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
if (
|
|
503
|
+
status.packageVersion.status !== undefined &&
|
|
504
|
+
status.packageVersion.status !== "release_version_match"
|
|
505
|
+
) {
|
|
506
|
+
hardFailures.push(
|
|
507
|
+
consumerGateIssue(
|
|
508
|
+
"local_overlay_release_version_mismatch",
|
|
509
|
+
"hard",
|
|
510
|
+
"release_identity",
|
|
511
|
+
`local consumer overlay version is ${status.packageVersion.status}`,
|
|
512
|
+
{ packageVersionStatus: status.packageVersion.status },
|
|
513
|
+
),
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
for (const pkg of status.localOverlay.packages ?? []) {
|
|
517
|
+
if (pkg.targetStatus === "missing") {
|
|
518
|
+
hardFailures.push(
|
|
519
|
+
consumerGateIssue(
|
|
520
|
+
"local_overlay_package_missing",
|
|
521
|
+
"hard",
|
|
522
|
+
"package_integrity",
|
|
523
|
+
`${pkg.packageName} is missing from the consumer overlay`,
|
|
524
|
+
{ packageName: pkg.packageName },
|
|
525
|
+
),
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
if (pkg.targetStatus === "symlink") {
|
|
529
|
+
hardFailures.push(
|
|
530
|
+
consumerGateIssue(
|
|
531
|
+
"local_overlay_package_symlink",
|
|
532
|
+
"hard",
|
|
533
|
+
"package_integrity",
|
|
534
|
+
`${pkg.packageName} is a symlink, not packed package content`,
|
|
535
|
+
{ packageName: pkg.packageName },
|
|
536
|
+
),
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
if (pkg.tarballStatus !== "verified") {
|
|
540
|
+
hardFailures.push(
|
|
541
|
+
consumerGateIssue(
|
|
542
|
+
"local_overlay_tarball_not_verified",
|
|
543
|
+
"hard",
|
|
544
|
+
"package_integrity",
|
|
545
|
+
`${pkg.packageName} tarball status is ${pkg.tarballStatus}`,
|
|
546
|
+
{ packageName: pkg.packageName, tarballStatus: pkg.tarballStatus },
|
|
547
|
+
),
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
if (status.npmLatest.status === "not_checked") {
|
|
552
|
+
signals.push(
|
|
553
|
+
consumerGateIssue(
|
|
554
|
+
"npm_latest_not_checked",
|
|
555
|
+
"signal",
|
|
556
|
+
"registry_observation",
|
|
557
|
+
"npm latest was not checked; pass --check-npm to include registry observation",
|
|
558
|
+
),
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
if (status.npmLatest.status === "checked") {
|
|
562
|
+
for (const [packageName, row] of Object.entries(status.npmLatest.packages ?? {})) {
|
|
563
|
+
if (row.status !== "resolved") {
|
|
564
|
+
signals.push(
|
|
565
|
+
consumerGateIssue(
|
|
566
|
+
"npm_latest_unresolved",
|
|
567
|
+
"signal",
|
|
568
|
+
"registry_observation",
|
|
569
|
+
`${packageName} npm latest could not be resolved`,
|
|
570
|
+
{ packageName, status: row.status },
|
|
571
|
+
),
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
return {
|
|
577
|
+
status: hardFailures.length === 0 ? "pass" : "fail",
|
|
578
|
+
hardFailures,
|
|
579
|
+
signals,
|
|
580
|
+
};
|
|
581
|
+
};
|
|
582
|
+
|
|
583
|
+
const withConsumerGate = (status) => ({
|
|
584
|
+
...status,
|
|
585
|
+
gate: consumerOverlayGate(status),
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
export const consumerStatusData = (consumerRoot, options = {}) => {
|
|
589
|
+
const metadata = packageMetadata(options.packageRoot);
|
|
590
|
+
const markerPath = localConsumerMarkerPath(consumerRoot);
|
|
591
|
+
const currentSource =
|
|
592
|
+
typeof options.sourceRoot === "string" ? sourceIdentityFor(options.sourceRoot) : undefined;
|
|
593
|
+
if (!fs.existsSync(markerPath)) {
|
|
594
|
+
return withConsumerGate({
|
|
595
|
+
schemaVersion: 1,
|
|
596
|
+
consumerRoot,
|
|
597
|
+
markerPath: path.relative(consumerRoot, markerPath).split(path.sep).join("/"),
|
|
598
|
+
truthMode: truthModeFor(undefined),
|
|
599
|
+
localOverlay: { status: "missing" },
|
|
600
|
+
packageIntegrity: packageIntegrityFor(undefined, []),
|
|
601
|
+
sourceFreshness: { status: "not_applicable", checked: false, gate: "pass" },
|
|
602
|
+
packageVersion: { release: metadata.packageVersion },
|
|
603
|
+
npmLatest:
|
|
604
|
+
options.checkNpm === true ? npmLatestFor([], options.registry) : npmLatestNotChecked(),
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
const marker = readJson(markerPath);
|
|
608
|
+
const packages = packageOverlayRows(consumerRoot, marker);
|
|
609
|
+
const sourceStatus = overlaySourceStatus(marker, currentSource);
|
|
610
|
+
const packageIntegrity = packageIntegrityFor(marker, packages);
|
|
611
|
+
const sourceFreshness = sourceFreshnessFor(marker, currentSource);
|
|
612
|
+
return withConsumerGate({
|
|
613
|
+
schemaVersion: 1,
|
|
614
|
+
consumerRoot,
|
|
615
|
+
markerPath: path.relative(consumerRoot, markerPath).split(path.sep).join("/"),
|
|
616
|
+
truthMode: truthModeFor(marker),
|
|
617
|
+
localOverlay: {
|
|
618
|
+
status: packages.every((pkg) => pkg.installed) ? "installed" : "partial",
|
|
619
|
+
sourceStatus,
|
|
620
|
+
generatedBy: marker.generatedBy,
|
|
621
|
+
installedAt: marker.installedAt,
|
|
622
|
+
artifact: marker.artifact ?? { kind: "legacy-local-overlay" },
|
|
623
|
+
packages,
|
|
624
|
+
},
|
|
625
|
+
packageIntegrity,
|
|
626
|
+
sourceFreshness,
|
|
627
|
+
source: {
|
|
628
|
+
...(currentSource === undefined ? {} : { current: currentSource }),
|
|
629
|
+
overlay: marker.source,
|
|
630
|
+
},
|
|
631
|
+
packageVersion: {
|
|
632
|
+
release: metadata.packageVersion,
|
|
633
|
+
overlay: marker.packageVersion,
|
|
634
|
+
status: packageVersionStatus(marker, metadata.packageVersion),
|
|
635
|
+
},
|
|
636
|
+
npmLatest:
|
|
637
|
+
options.checkNpm === true
|
|
638
|
+
? npmLatestFor(
|
|
639
|
+
packages.map((pkg) => pkg.packageName),
|
|
640
|
+
options.registry,
|
|
641
|
+
)
|
|
642
|
+
: npmLatestNotChecked(),
|
|
643
|
+
});
|
|
644
|
+
};
|
|
645
|
+
|
|
646
|
+
const printConsumerStatus = (status) => {
|
|
647
|
+
console.log(`consumer: ${status.consumerRoot}`);
|
|
648
|
+
console.log(`marker: ${status.markerPath}`);
|
|
649
|
+
console.log(`truth mode: ${status.truthMode}`);
|
|
650
|
+
console.log(`local overlay: ${status.localOverlay.status}`);
|
|
651
|
+
console.log(`package integrity: ${status.packageIntegrity.status}`);
|
|
652
|
+
if (status.sourceFreshness !== undefined) {
|
|
653
|
+
console.log(`source freshness: ${status.sourceFreshness.status}`);
|
|
654
|
+
}
|
|
655
|
+
if (status.localOverlay.sourceStatus !== undefined) {
|
|
656
|
+
console.log(`source status: ${status.localOverlay.sourceStatus}`);
|
|
657
|
+
}
|
|
658
|
+
console.log(
|
|
659
|
+
`package version: overlay=${status.packageVersion.overlay ?? "none"} release=${status.packageVersion.release} status=${status.packageVersion.status ?? "none"}`,
|
|
660
|
+
);
|
|
661
|
+
console.log(`npm latest: ${status.npmLatest.status}`);
|
|
662
|
+
console.log(`gate: ${status.gate.status}`);
|
|
663
|
+
for (const pkg of status.localOverlay.packages ?? []) {
|
|
664
|
+
console.log(
|
|
665
|
+
`package ${pkg.packageName}: target=${pkg.targetStatus} tarball=${pkg.tarballStatus} sha256=${pkg.sha256}`,
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
for (const failure of status.gate.hardFailures) {
|
|
669
|
+
console.log(`failure ${failure.code}: ${failure.message}`);
|
|
670
|
+
}
|
|
671
|
+
for (const signal of status.gate.signals) {
|
|
672
|
+
console.log(`signal ${signal.code}: ${signal.message}`);
|
|
673
|
+
}
|
|
674
|
+
};
|
|
675
|
+
|
|
676
|
+
const installManifestPathForArgs = async (args, context) => {
|
|
677
|
+
if (typeof args["from-manifest"] === "string") {
|
|
678
|
+
return path.resolve(process.cwd(), args["from-manifest"]);
|
|
679
|
+
}
|
|
680
|
+
if (boolArg(args, "skip-pack")) {
|
|
681
|
+
if (typeof context.defaultInstallManifestPath !== "string") {
|
|
682
|
+
fail(
|
|
683
|
+
"agentos consumer install --skip-pack requires --from-manifest outside a source checkout",
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
return context.defaultInstallManifestPath;
|
|
687
|
+
}
|
|
688
|
+
if (typeof context.produceInstallManifest !== "function") {
|
|
689
|
+
fail("agentos consumer install requires --from-manifest outside an agentOS source checkout");
|
|
690
|
+
}
|
|
691
|
+
return await context.produceInstallManifest();
|
|
692
|
+
};
|
|
693
|
+
|
|
694
|
+
export const installConsumer = async (rawArgs, context = {}) => {
|
|
695
|
+
const args = parseArgs(rawArgs);
|
|
696
|
+
const consumerRoot = resolveConsumerRoot(positionalArgs(args)[0]);
|
|
697
|
+
const manifestPath = await installManifestPathForArgs(args, context);
|
|
698
|
+
const snapshot = snapshotFiles(consumerManifestFiles(consumerRoot));
|
|
699
|
+
const { manifest } = readInstallManifest(manifestPath);
|
|
700
|
+
const entries = tarballPackageEntries(manifest);
|
|
701
|
+
const nodeModules = nodeModulesRoot(consumerRoot, { install: !boolArg(args, "no-install") });
|
|
702
|
+
const packages = {};
|
|
703
|
+
for (const entry of entries) {
|
|
704
|
+
const target = packageTargetDir(nodeModules, entry.packageName);
|
|
705
|
+
unpackTarballInto(entry.tarball, target);
|
|
706
|
+
packages[entry.packageName] = {
|
|
707
|
+
target: path.relative(consumerRoot, target).split(path.sep).join("/"),
|
|
708
|
+
tarball: entry.tarball,
|
|
709
|
+
sha256: entry.sha256,
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
const source =
|
|
713
|
+
typeof context.sourceRoot === "string"
|
|
714
|
+
? sourceIdentityFor(context.sourceRoot)
|
|
715
|
+
: (manifest.source ?? undefined);
|
|
716
|
+
writeJson(localConsumerMarkerPath(consumerRoot), {
|
|
717
|
+
schemaVersion: 1,
|
|
718
|
+
generatedBy: "agentos consumer install",
|
|
719
|
+
installedAt: new Date().toISOString(),
|
|
720
|
+
consumerRoot,
|
|
721
|
+
...(source === undefined ? {} : { source }),
|
|
722
|
+
packageVersion: manifest.version,
|
|
723
|
+
artifact: markerArtifact(manifestPath, manifest),
|
|
724
|
+
packages,
|
|
725
|
+
});
|
|
726
|
+
assertSnapshotUnchanged(snapshot, "agentos consumer install");
|
|
727
|
+
const status = consumerStatusData(consumerRoot, { sourceRoot: context.sourceRoot });
|
|
728
|
+
if (boolArg(args, "json")) {
|
|
729
|
+
console.log(JSON.stringify(status, null, 2));
|
|
730
|
+
} else {
|
|
731
|
+
console.log(`installed ${entries.length} local agentOS packages into ${consumerRoot}`);
|
|
732
|
+
console.log(
|
|
733
|
+
`wrote ${path.relative(consumerRoot, localConsumerMarkerPath(consumerRoot)).split(path.sep).join("/")}`,
|
|
734
|
+
);
|
|
735
|
+
printConsumerStatus(status);
|
|
736
|
+
}
|
|
737
|
+
};
|
|
738
|
+
|
|
739
|
+
export const consumerStatus = (rawArgs, context = {}) => {
|
|
740
|
+
const args = parseArgs(rawArgs);
|
|
741
|
+
const consumerRoot = resolveConsumerRoot(positionalArgs(args)[0]);
|
|
742
|
+
const status = consumerStatusData(consumerRoot, {
|
|
743
|
+
packageRoot: context.packageRoot,
|
|
744
|
+
sourceRoot: context.sourceRoot,
|
|
745
|
+
checkNpm: boolArg(args, "check-npm"),
|
|
746
|
+
registry: args.registry,
|
|
747
|
+
});
|
|
748
|
+
if (boolArg(args, "json")) {
|
|
749
|
+
console.log(JSON.stringify(status, null, 2));
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
printConsumerStatus(status);
|
|
753
|
+
};
|
|
754
|
+
|
|
755
|
+
export const consumerCheck = (rawArgs, context = {}) => {
|
|
756
|
+
const args = parseArgs(rawArgs);
|
|
757
|
+
const consumerRoot = resolveConsumerRoot(positionalArgs(args)[0]);
|
|
758
|
+
const status = consumerStatusData(consumerRoot, {
|
|
759
|
+
packageRoot: context.packageRoot,
|
|
760
|
+
sourceRoot: context.sourceRoot,
|
|
761
|
+
checkNpm: boolArg(args, "check-npm"),
|
|
762
|
+
registry: args.registry,
|
|
763
|
+
});
|
|
764
|
+
if (boolArg(args, "json")) {
|
|
765
|
+
console.log(JSON.stringify(status, null, 2));
|
|
766
|
+
} else {
|
|
767
|
+
printConsumerStatus(status);
|
|
768
|
+
}
|
|
769
|
+
if (status.gate.status !== "pass") {
|
|
770
|
+
process.exitCode = 1;
|
|
771
|
+
}
|
|
772
|
+
};
|
|
773
|
+
|
|
774
|
+
export const restoreConsumer = (rawArgs) => {
|
|
775
|
+
const args = parseArgs(rawArgs);
|
|
776
|
+
const consumerRoot = resolveConsumerRoot(positionalArgs(args)[0]);
|
|
777
|
+
const nodeModules = nodeModulesRoot(consumerRoot);
|
|
778
|
+
const markerPath = localConsumerMarkerPath(consumerRoot);
|
|
779
|
+
if (!fs.existsSync(markerPath)) {
|
|
780
|
+
fail(`${markerPath}: no local agentOS overlay marker`);
|
|
781
|
+
}
|
|
782
|
+
const marker = readJson(markerPath);
|
|
783
|
+
const packageNames = Object.keys(marker.packages ?? {}).sort((left, right) =>
|
|
784
|
+
left.localeCompare(right),
|
|
785
|
+
);
|
|
786
|
+
if (packageNames.length === 0) fail(`${markerPath}: marker does not list packages`);
|
|
787
|
+
const snapshot = snapshotFiles(consumerManifestFiles(consumerRoot));
|
|
788
|
+
for (const packageName of packageNames) {
|
|
789
|
+
fs.rmSync(packageTargetDir(nodeModules, packageName), { recursive: true, force: true });
|
|
790
|
+
}
|
|
791
|
+
fs.rmSync(markerPath, { force: true });
|
|
792
|
+
if (!boolArg(args, "no-install")) {
|
|
793
|
+
run("npm", ["install"], { cwd: consumerRoot });
|
|
794
|
+
}
|
|
795
|
+
assertSnapshotUnchanged(snapshot, "agentos consumer restore");
|
|
796
|
+
const result = { schemaVersion: 1, restoredPackages: packageNames };
|
|
797
|
+
if (boolArg(args, "json")) {
|
|
798
|
+
console.log(JSON.stringify(result, null, 2));
|
|
799
|
+
} else {
|
|
800
|
+
console.log(`restored ${packageNames.length} local agentOS package overlays`);
|
|
801
|
+
}
|
|
802
|
+
};
|