@grubbylee/aiba 0.1.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/GENERATED_OUTPUT_EXCEPTION.md +30 -0
- package/LICENSE +661 -0
- package/README.md +20 -0
- package/capabilities/audit/README.md +7 -0
- package/capabilities/audit/capability.yaml +54 -0
- package/capabilities/audit/recipes/typescript-reference.yaml +64 -0
- package/capabilities/authorization/README.md +7 -0
- package/capabilities/authorization/capability.yaml +66 -0
- package/capabilities/authorization/recipes/typescript-reference.yaml +73 -0
- package/capabilities/identity/README.md +8 -0
- package/capabilities/identity/capability.yaml +63 -0
- package/capabilities/identity/recipes/typescript-reference.yaml +75 -0
- package/capabilities/notification/README.md +7 -0
- package/capabilities/notification/capability.yaml +71 -0
- package/capabilities/notification/recipes/typescript-reference.yaml +73 -0
- package/capabilities/review-access/README.md +14 -0
- package/capabilities/review-access/capability.yaml +65 -0
- package/capabilities/review-access/recipes/typescript-reference.yaml +78 -0
- package/capabilities/review-access/recipes/wechat-native.yaml +73 -0
- package/capabilities/users/README.md +8 -0
- package/capabilities/users/capability.yaml +76 -0
- package/capabilities/users/recipes/typescript-reference.yaml +75 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +609 -0
- package/dist/render.d.ts +4 -0
- package/dist/render.js +54 -0
- package/package.json +41 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { Command } from "commander";
|
|
7
|
+
import { finalizeCapability, finalizeUpgrade, createCapabilityBundle, createCapabilityApproval, createRegistryIndex, diffProject, generatePublisherKeyPair, initializeProject, initializeGovernancePolicy, importRegistryBundle, inspectProject, prepareCapability, prepareUpgrade, evaluateGovernance, fetchRegistryCapability, resolveRegistryCapability, verifyCapabilityBundle, verifyProject, } from "aiba-core";
|
|
8
|
+
import { createRegistryServer } from "aiba-registry-server";
|
|
9
|
+
import { renderDiff, renderInspection, renderVerification } from "./render.js";
|
|
10
|
+
const program = new Command();
|
|
11
|
+
const installedPacksDirectory = fileURLToPath(new URL("../capabilities", import.meta.url));
|
|
12
|
+
function packageVersion() {
|
|
13
|
+
const value = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
14
|
+
if (typeof value !== "object"
|
|
15
|
+
|| value === null
|
|
16
|
+
|| !("version" in value)
|
|
17
|
+
|| typeof value.version !== "string") {
|
|
18
|
+
throw new Error("AIBA package metadata has no valid version");
|
|
19
|
+
}
|
|
20
|
+
return value.version;
|
|
21
|
+
}
|
|
22
|
+
function defaultPacksDirectory() {
|
|
23
|
+
return existsSync(installedPacksDirectory)
|
|
24
|
+
? installedPacksDirectory
|
|
25
|
+
: resolve("capabilities");
|
|
26
|
+
}
|
|
27
|
+
program
|
|
28
|
+
.name("aiba")
|
|
29
|
+
.description("Install, verify, trace, and upgrade application capabilities")
|
|
30
|
+
.version(packageVersion());
|
|
31
|
+
program
|
|
32
|
+
.command("policy-init")
|
|
33
|
+
.description("Initialize project-owned capability governance")
|
|
34
|
+
.option("--root <path>", "project root", ".")
|
|
35
|
+
.requiredOption("--id <id>", "policy identifier")
|
|
36
|
+
.requiredOption("--approver <id>", "initial approver identifier")
|
|
37
|
+
.requiredOption("--key-id <id>", "approver public key identifier")
|
|
38
|
+
.requiredOption("--public-key <path>", "Ed25519 SPKI public key")
|
|
39
|
+
.requiredOption("--capability <ids...>", "allowed capability identifiers")
|
|
40
|
+
.option("--install-approvals <number>", "required install approvals", "1")
|
|
41
|
+
.option("--upgrade-approvals <number>", "required upgrade approvals", "1")
|
|
42
|
+
.option("--conflict-approvals <number>", "required conflict upgrade approvals", "1")
|
|
43
|
+
.option("--ttl-hours <number>", "maximum approval lifetime in hours", "72")
|
|
44
|
+
.option("--allow-self-approval", "allow the implementing Agent to approve")
|
|
45
|
+
.option("--json", "print machine-readable JSON")
|
|
46
|
+
.action(async (options) => {
|
|
47
|
+
const installApprovals = Number(options.installApprovals);
|
|
48
|
+
const upgradeApprovals = Number(options.upgradeApprovals);
|
|
49
|
+
const conflictApprovals = Number(options.conflictApprovals);
|
|
50
|
+
const ttlHours = Number(options.ttlHours);
|
|
51
|
+
if (!Number.isSafeInteger(installApprovals)
|
|
52
|
+
|| !Number.isSafeInteger(upgradeApprovals)
|
|
53
|
+
|| !Number.isSafeInteger(conflictApprovals)
|
|
54
|
+
|| installApprovals < 1
|
|
55
|
+
|| upgradeApprovals < 1
|
|
56
|
+
|| conflictApprovals < 1) {
|
|
57
|
+
throw new Error("approval thresholds must be positive integers");
|
|
58
|
+
}
|
|
59
|
+
if (!Number.isFinite(ttlHours) || ttlHours < 1 / 60 || ttlHours > 720) {
|
|
60
|
+
throw new Error("--ttl-hours must be between 1/60 and 720");
|
|
61
|
+
}
|
|
62
|
+
const result = await initializeGovernancePolicy({
|
|
63
|
+
projectRoot: resolve(options.root),
|
|
64
|
+
policyId: options.id,
|
|
65
|
+
approverId: options.approver,
|
|
66
|
+
keyId: options.keyId,
|
|
67
|
+
publicKeyPath: resolve(options.publicKey),
|
|
68
|
+
capabilities: options.capability,
|
|
69
|
+
installApprovals,
|
|
70
|
+
upgradeApprovals,
|
|
71
|
+
conflictUpgradeApprovals: conflictApprovals,
|
|
72
|
+
approvalTtlSeconds: Math.round(ttlHours * 60 * 60),
|
|
73
|
+
prohibitSelfApproval: !options.allowSelfApproval,
|
|
74
|
+
});
|
|
75
|
+
if (options.json) {
|
|
76
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
process.stdout.write([
|
|
80
|
+
`Initialized governance policy ${result.policy.metadata.id}@${result.policy.metadata.version}.`,
|
|
81
|
+
`Policy: ${result.policyPath}`,
|
|
82
|
+
`Initial approver: ${options.approver}/${options.keyId}`,
|
|
83
|
+
].join("\n") + "\n");
|
|
84
|
+
});
|
|
85
|
+
program
|
|
86
|
+
.command("approve")
|
|
87
|
+
.description("Sign the current capability plan as a trusted approver")
|
|
88
|
+
.argument("<capability>", "capability plan to approve")
|
|
89
|
+
.option("--root <path>", "project root", ".")
|
|
90
|
+
.requiredOption("--approver <id>", "approver identifier")
|
|
91
|
+
.requiredOption("--key-id <id>", "approver key identifier")
|
|
92
|
+
.requiredOption("--private-key <path>", "Ed25519 PKCS#8 private key")
|
|
93
|
+
.option("--upgrade", "approve the current upgrade plan")
|
|
94
|
+
.option("--json", "print machine-readable JSON")
|
|
95
|
+
.action(async (capability, options) => {
|
|
96
|
+
const result = await createCapabilityApproval({
|
|
97
|
+
projectRoot: resolve(options.root),
|
|
98
|
+
capabilityId: capability,
|
|
99
|
+
operation: options.upgrade ? "upgrade" : "install",
|
|
100
|
+
approverId: options.approver,
|
|
101
|
+
keyId: options.keyId,
|
|
102
|
+
privateKeyPath: resolve(options.privateKey),
|
|
103
|
+
});
|
|
104
|
+
if (options.json) {
|
|
105
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
process.stdout.write([
|
|
109
|
+
`Approved ${result.approval.statement.operation.type} of ${capability}.`,
|
|
110
|
+
`Approver: ${result.approval.statement.approver.id}/${result.approval.statement.approver.keyId}`,
|
|
111
|
+
`Expires: ${result.approval.statement.expiresAt}`,
|
|
112
|
+
`Approval: ${result.approvalPath}`,
|
|
113
|
+
].join("\n") + "\n");
|
|
114
|
+
});
|
|
115
|
+
program
|
|
116
|
+
.command("policy-check")
|
|
117
|
+
.description("Evaluate signed approvals for the current capability plan")
|
|
118
|
+
.argument("<capability>", "capability plan to evaluate")
|
|
119
|
+
.option("--root <path>", "project root", ".")
|
|
120
|
+
.option("--upgrade", "evaluate the current upgrade plan")
|
|
121
|
+
.option("--agent <name>", "implementing Agent identity")
|
|
122
|
+
.option("--json", "print machine-readable JSON")
|
|
123
|
+
.action(async (capability, options) => {
|
|
124
|
+
const result = await evaluateGovernance({
|
|
125
|
+
projectRoot: resolve(options.root),
|
|
126
|
+
capabilityId: capability,
|
|
127
|
+
operation: options.upgrade ? "upgrade" : "install",
|
|
128
|
+
...(options.agent ? { agent: options.agent } : {}),
|
|
129
|
+
});
|
|
130
|
+
if (options.json) {
|
|
131
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
132
|
+
}
|
|
133
|
+
else if (!result.enabled) {
|
|
134
|
+
process.stdout.write("Governance is not enabled for this project.\n");
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
process.stdout.write([
|
|
138
|
+
`Governance: ${result.ok ? "approved" : "denied"}`,
|
|
139
|
+
`Approvals: ${result.validApprovals}/${result.requiredApprovals}`,
|
|
140
|
+
...result.issues.map((issue) => `${issue.code}: ${issue.message}`),
|
|
141
|
+
].join("\n") + "\n");
|
|
142
|
+
}
|
|
143
|
+
if (!result.ok)
|
|
144
|
+
process.exitCode = 1;
|
|
145
|
+
});
|
|
146
|
+
program
|
|
147
|
+
.command("registry-add")
|
|
148
|
+
.description("Import a verified signed bundle into a local registry")
|
|
149
|
+
.argument("<bundle-directory>", "signed capability bundle directory")
|
|
150
|
+
.requiredOption("--registry <path>", "local registry directory")
|
|
151
|
+
.requiredOption("--publisher-trust <path>", "capability publisher trust policy JSON")
|
|
152
|
+
.option("--json", "print machine-readable JSON")
|
|
153
|
+
.action(async (bundleDirectory, options) => {
|
|
154
|
+
const result = await importRegistryBundle({
|
|
155
|
+
registryDirectory: resolve(options.registry),
|
|
156
|
+
bundleDirectory: resolve(bundleDirectory),
|
|
157
|
+
publisherTrustPolicyPath: resolve(options.publisherTrust),
|
|
158
|
+
});
|
|
159
|
+
if (options.json) {
|
|
160
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
process.stdout.write([
|
|
164
|
+
`${result.imported ? "Imported" : "Already present"}: ${result.capability}@${result.version}.`,
|
|
165
|
+
`Publisher: ${result.publisher}/${result.keyId}`,
|
|
166
|
+
`Bundle: ${result.bundleDirectory}`,
|
|
167
|
+
`Manifest SHA-256: ${result.manifestSha256}`,
|
|
168
|
+
].join("\n") + "\n");
|
|
169
|
+
});
|
|
170
|
+
program
|
|
171
|
+
.command("registry-index")
|
|
172
|
+
.description("Create an immutable signed registry index snapshot")
|
|
173
|
+
.argument("<registry-directory>", "local registry directory")
|
|
174
|
+
.requiredOption("--id <id>", "registry identifier")
|
|
175
|
+
.requiredOption("--publisher <id>", "registry operator publisher identifier")
|
|
176
|
+
.requiredOption("--key-id <id>", "registry signing key identifier")
|
|
177
|
+
.requiredOption("--private-key <path>", "Ed25519 PKCS#8 private key")
|
|
178
|
+
.requiredOption("--publisher-trust <path>", "capability publisher trust policy JSON")
|
|
179
|
+
.requiredOption("--sequence <number>", "new monotonically increasing sequence")
|
|
180
|
+
.requiredOption("--expires-at <date-time>", "index expiry in ISO 8601 format")
|
|
181
|
+
.option("--json", "print machine-readable JSON")
|
|
182
|
+
.action(async (registryDirectory, options) => {
|
|
183
|
+
const sequence = Number(options.sequence);
|
|
184
|
+
if (!Number.isSafeInteger(sequence) || sequence < 1) {
|
|
185
|
+
throw new Error("--sequence must be a positive safe integer");
|
|
186
|
+
}
|
|
187
|
+
const expiresAt = new Date(options.expiresAt);
|
|
188
|
+
if (Number.isNaN(expiresAt.getTime())) {
|
|
189
|
+
throw new Error("--expires-at must be a valid ISO 8601 date-time");
|
|
190
|
+
}
|
|
191
|
+
const result = await createRegistryIndex({
|
|
192
|
+
registryDirectory: resolve(registryDirectory),
|
|
193
|
+
registryId: options.id,
|
|
194
|
+
publisherId: options.publisher,
|
|
195
|
+
keyId: options.keyId,
|
|
196
|
+
privateKeyPath: resolve(options.privateKey),
|
|
197
|
+
publisherTrustPolicyPath: resolve(options.publisherTrust),
|
|
198
|
+
sequence,
|
|
199
|
+
expiresAt,
|
|
200
|
+
});
|
|
201
|
+
if (options.json) {
|
|
202
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
process.stdout.write([
|
|
206
|
+
`Indexed ${result.entries} bundles for ${result.registry}.`,
|
|
207
|
+
`Sequence: ${result.sequence}`,
|
|
208
|
+
`Snapshot: ${result.snapshotDirectory}`,
|
|
209
|
+
`Index SHA-256: ${result.indexSha256}`,
|
|
210
|
+
].join("\n") + "\n");
|
|
211
|
+
});
|
|
212
|
+
program
|
|
213
|
+
.command("registry-serve")
|
|
214
|
+
.description("Serve a verified registry through the authenticated v0 read API")
|
|
215
|
+
.argument("<registry-directory>", "local registry directory")
|
|
216
|
+
.requiredOption("--registry-trust <path>", "registry signer trust policy JSON")
|
|
217
|
+
.requiredOption("--publisher-trust <path>", "capability publisher trust policy JSON")
|
|
218
|
+
.option("--token-env <name>", "environment variable containing the read token", "AIBA_REGISTRY_TOKEN")
|
|
219
|
+
.option("--host <host>", "listen host", "127.0.0.1")
|
|
220
|
+
.option("--port <number>", "listen port", "7331")
|
|
221
|
+
.option("--tls-cert <path>", "TLS certificate PEM")
|
|
222
|
+
.option("--tls-key <path>", "TLS private key PEM")
|
|
223
|
+
.option("--allow-insecure-localhost", "allow HTTP on a loopback host")
|
|
224
|
+
.option("--json", "print machine-readable startup state")
|
|
225
|
+
.action(async (registryDirectory, options) => {
|
|
226
|
+
if (!/^[A-Z_][A-Z0-9_]*$/.test(options.tokenEnv)) {
|
|
227
|
+
throw new Error("--token-env must be an uppercase environment variable name");
|
|
228
|
+
}
|
|
229
|
+
const token = process.env[options.tokenEnv];
|
|
230
|
+
if (!token)
|
|
231
|
+
throw new Error(`registry token is missing in ${options.tokenEnv}`);
|
|
232
|
+
const port = Number(options.port);
|
|
233
|
+
if (!Number.isSafeInteger(port) || port < 0 || port > 65535) {
|
|
234
|
+
throw new Error("--port must be an integer between 0 and 65535");
|
|
235
|
+
}
|
|
236
|
+
const hasTls = options.tlsCert !== undefined || options.tlsKey !== undefined;
|
|
237
|
+
const loopback = ["127.0.0.1", "::1", "localhost"].includes(options.host);
|
|
238
|
+
if (!hasTls && !(options.allowInsecureLocalhost && loopback)) {
|
|
239
|
+
throw new Error("HTTP requires --allow-insecure-localhost and a loopback --host");
|
|
240
|
+
}
|
|
241
|
+
const created = await createRegistryServer({
|
|
242
|
+
registryDirectory: resolve(registryDirectory),
|
|
243
|
+
registryTrustPolicyPath: resolve(options.registryTrust),
|
|
244
|
+
publisherTrustPolicyPath: resolve(options.publisherTrust),
|
|
245
|
+
token,
|
|
246
|
+
...(options.tlsCert ? { tlsCertificatePath: resolve(options.tlsCert) } : {}),
|
|
247
|
+
...(options.tlsKey ? { tlsPrivateKeyPath: resolve(options.tlsKey) } : {}),
|
|
248
|
+
});
|
|
249
|
+
await new Promise((resolvePromise, reject) => {
|
|
250
|
+
created.server.once("error", reject);
|
|
251
|
+
created.server.listen(port, options.host, resolvePromise);
|
|
252
|
+
});
|
|
253
|
+
const address = created.server.address();
|
|
254
|
+
const actualPort = address && typeof address !== "string" ? address.port : port;
|
|
255
|
+
const started = {
|
|
256
|
+
...created.snapshot,
|
|
257
|
+
secure: created.secure,
|
|
258
|
+
host: options.host,
|
|
259
|
+
port: actualPort,
|
|
260
|
+
};
|
|
261
|
+
process.stdout.write(`${options.json
|
|
262
|
+
? JSON.stringify(started)
|
|
263
|
+
: `Serving ${started.registry} sequence ${started.sequence} on ${created.secure ? "https" : "http"}://${options.host}:${actualPort}`}\n`);
|
|
264
|
+
await new Promise((resolvePromise) => {
|
|
265
|
+
let closing = false;
|
|
266
|
+
const close = () => {
|
|
267
|
+
if (closing)
|
|
268
|
+
return;
|
|
269
|
+
closing = true;
|
|
270
|
+
created.server.close(() => resolvePromise());
|
|
271
|
+
};
|
|
272
|
+
process.once("SIGINT", close);
|
|
273
|
+
process.once("SIGTERM", close);
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
program
|
|
277
|
+
.command("fetch")
|
|
278
|
+
.description("Fetch a verified capability from an authenticated private registry")
|
|
279
|
+
.argument("<capability>", "capability to fetch")
|
|
280
|
+
.requiredOption("--registry-url <url>", "private registry base URL")
|
|
281
|
+
.requiredOption("--registry-trust <path>", "registry signer trust policy JSON")
|
|
282
|
+
.requiredOption("--publisher-trust <path>", "capability publisher trust policy JSON")
|
|
283
|
+
.option("--cache <path>", "verified registry cache", ".aiba/registry-cache")
|
|
284
|
+
.option("--state <path>", "trusted anti-rollback state", ".aiba/registry-state.json")
|
|
285
|
+
.option("--token-env <name>", "environment variable containing the bearer token", "AIBA_REGISTRY_TOKEN")
|
|
286
|
+
.option("--timeout-ms <number>", "request timeout in milliseconds", "15000")
|
|
287
|
+
.option("--version <version>", "fetch an exact semantic version")
|
|
288
|
+
.option("--allow-insecure-localhost", "allow HTTP only for a localhost registry")
|
|
289
|
+
.option("--json", "print machine-readable JSON")
|
|
290
|
+
.action(async (capability, options) => {
|
|
291
|
+
const timeoutMs = Number(options.timeoutMs);
|
|
292
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 120_000) {
|
|
293
|
+
throw new Error("--timeout-ms must be an integer between 1 and 120000");
|
|
294
|
+
}
|
|
295
|
+
const result = await fetchRegistryCapability({
|
|
296
|
+
registryUrl: options.registryUrl,
|
|
297
|
+
cacheDirectory: resolve(options.cache),
|
|
298
|
+
registryTrustPolicyPath: resolve(options.registryTrust),
|
|
299
|
+
publisherTrustPolicyPath: resolve(options.publisherTrust),
|
|
300
|
+
statePath: resolve(options.state),
|
|
301
|
+
capabilityId: capability,
|
|
302
|
+
tokenEnvironmentVariable: options.tokenEnv,
|
|
303
|
+
timeoutMs,
|
|
304
|
+
allowInsecureLocalhost: options.allowInsecureLocalhost ?? false,
|
|
305
|
+
...(options.version ? { version: options.version } : {}),
|
|
306
|
+
});
|
|
307
|
+
if (options.json) {
|
|
308
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
process.stdout.write([
|
|
312
|
+
`Fetched ${result.capability}@${result.version}.`,
|
|
313
|
+
`Registry: ${result.registry} sequence ${result.sequence}`,
|
|
314
|
+
`Publisher: ${result.publisher}/${result.keyId}`,
|
|
315
|
+
`Cache: ${result.cacheDirectory}`,
|
|
316
|
+
`Pack: ${result.packDirectory}`,
|
|
317
|
+
`Anti-rollback state: ${result.statePath}`,
|
|
318
|
+
].join("\n") + "\n");
|
|
319
|
+
});
|
|
320
|
+
program
|
|
321
|
+
.command("resolve")
|
|
322
|
+
.description("Resolve a verified capability from a signed local registry")
|
|
323
|
+
.argument("<capability>", "capability to resolve")
|
|
324
|
+
.requiredOption("--registry <path>", "local registry directory")
|
|
325
|
+
.requiredOption("--registry-trust <path>", "registry signer trust policy JSON")
|
|
326
|
+
.requiredOption("--publisher-trust <path>", "capability publisher trust policy JSON")
|
|
327
|
+
.option("--state <path>", "trusted anti-rollback state", ".aiba/registry-state.json")
|
|
328
|
+
.option("--version <version>", "resolve an exact semantic version")
|
|
329
|
+
.option("--json", "print machine-readable JSON")
|
|
330
|
+
.action(async (capability, options) => {
|
|
331
|
+
const result = await resolveRegistryCapability({
|
|
332
|
+
registryDirectory: resolve(options.registry),
|
|
333
|
+
registryTrustPolicyPath: resolve(options.registryTrust),
|
|
334
|
+
publisherTrustPolicyPath: resolve(options.publisherTrust),
|
|
335
|
+
statePath: resolve(options.state),
|
|
336
|
+
capabilityId: capability,
|
|
337
|
+
...(options.version ? { version: options.version } : {}),
|
|
338
|
+
});
|
|
339
|
+
if (options.json) {
|
|
340
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
process.stdout.write([
|
|
344
|
+
`Resolved ${result.capability}@${result.version}.`,
|
|
345
|
+
`Registry: ${result.registry} sequence ${result.sequence}`,
|
|
346
|
+
`Publisher: ${result.publisher}/${result.keyId}`,
|
|
347
|
+
`Bundle: ${result.bundleDirectory}`,
|
|
348
|
+
`Pack: ${result.packDirectory}`,
|
|
349
|
+
`Anti-rollback state: ${result.statePath}`,
|
|
350
|
+
].join("\n") + "\n");
|
|
351
|
+
});
|
|
352
|
+
program
|
|
353
|
+
.command("keygen")
|
|
354
|
+
.description("Generate an Ed25519 capability publisher key pair")
|
|
355
|
+
.argument("<publisher>", "publisher identifier")
|
|
356
|
+
.requiredOption("--out <path>", "new key output directory")
|
|
357
|
+
.option("--key-id <id>", "publisher key identifier", "root-1")
|
|
358
|
+
.option("--json", "print machine-readable JSON")
|
|
359
|
+
.action(async (publisher, options) => {
|
|
360
|
+
const result = await generatePublisherKeyPair({
|
|
361
|
+
publisherId: publisher,
|
|
362
|
+
keyId: options.keyId,
|
|
363
|
+
outputDirectory: resolve(options.out),
|
|
364
|
+
});
|
|
365
|
+
if (options.json) {
|
|
366
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
process.stdout.write([
|
|
370
|
+
`Generated Ed25519 key ${result.publisherId}/${result.keyId}.`,
|
|
371
|
+
`Private key: ${result.privateKeyPath}`,
|
|
372
|
+
`Public key: ${result.publicKeyPath}`,
|
|
373
|
+
].join("\n") + "\n");
|
|
374
|
+
});
|
|
375
|
+
program
|
|
376
|
+
.command("pack")
|
|
377
|
+
.description("Create and sign a capability bundle")
|
|
378
|
+
.argument("<capability>", "capability to package")
|
|
379
|
+
.requiredOption("--publisher <id>", "publisher identifier")
|
|
380
|
+
.requiredOption("--key-id <id>", "publisher key identifier")
|
|
381
|
+
.requiredOption("--private-key <path>", "Ed25519 PKCS#8 private key")
|
|
382
|
+
.requiredOption("--out <path>", "new bundle output directory")
|
|
383
|
+
.option("--packs-dir <path>", "capability pack directory", defaultPacksDirectory())
|
|
384
|
+
.option("--json", "print machine-readable JSON")
|
|
385
|
+
.action(async (capability, options) => {
|
|
386
|
+
const result = await createCapabilityBundle({
|
|
387
|
+
packsDirectory: resolve(options.packsDir),
|
|
388
|
+
capabilityId: capability,
|
|
389
|
+
outputDirectory: resolve(options.out),
|
|
390
|
+
publisherId: options.publisher,
|
|
391
|
+
keyId: options.keyId,
|
|
392
|
+
privateKeyPath: resolve(options.privateKey),
|
|
393
|
+
});
|
|
394
|
+
if (options.json) {
|
|
395
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
process.stdout.write([
|
|
399
|
+
`Packed ${result.capability}@${result.version}.`,
|
|
400
|
+
`Publisher: ${result.publisher}/${result.keyId}`,
|
|
401
|
+
`Files: ${result.files}`,
|
|
402
|
+
`Bundle: ${result.bundleDirectory}`,
|
|
403
|
+
`Manifest SHA-256: ${result.manifestSha256}`,
|
|
404
|
+
].join("\n") + "\n");
|
|
405
|
+
});
|
|
406
|
+
program
|
|
407
|
+
.command("verify-bundle")
|
|
408
|
+
.description("Verify a signed capability bundle against a local trust policy")
|
|
409
|
+
.argument("<bundle>", "bundle directory")
|
|
410
|
+
.requiredOption("--trust <path>", "publisher trust policy JSON")
|
|
411
|
+
.option("--json", "print machine-readable JSON")
|
|
412
|
+
.action(async (bundle, options) => {
|
|
413
|
+
const result = await verifyCapabilityBundle({
|
|
414
|
+
bundleDirectory: resolve(bundle),
|
|
415
|
+
trustPolicyPath: resolve(options.trust),
|
|
416
|
+
});
|
|
417
|
+
if (options.json) {
|
|
418
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
process.stdout.write([
|
|
422
|
+
`Verified ${result.capability}@${result.version}.`,
|
|
423
|
+
`Publisher: ${result.publisher}/${result.keyId}`,
|
|
424
|
+
`Files: ${result.files}`,
|
|
425
|
+
`Manifest SHA-256: ${result.manifestSha256}`,
|
|
426
|
+
].join("\n") + "\n");
|
|
427
|
+
});
|
|
428
|
+
program
|
|
429
|
+
.command("init")
|
|
430
|
+
.description("Initialize project-owned AIBA state")
|
|
431
|
+
.argument("[root]", "project root", ".")
|
|
432
|
+
.option("--json", "print machine-readable JSON")
|
|
433
|
+
.action(async (root, options) => {
|
|
434
|
+
const result = await initializeProject(root);
|
|
435
|
+
if (options.json) {
|
|
436
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
process.stdout.write([
|
|
440
|
+
"AIBA project initialized.",
|
|
441
|
+
`Manifest: ${result.manifestPath}`,
|
|
442
|
+
`Lock: ${result.lockPath}`,
|
|
443
|
+
`Receipts: ${result.receiptsPath}`,
|
|
444
|
+
].join("\n") + "\n");
|
|
445
|
+
});
|
|
446
|
+
program
|
|
447
|
+
.command("inspect")
|
|
448
|
+
.description("Inspect an existing project without changing it")
|
|
449
|
+
.argument("[root]", "project root", ".")
|
|
450
|
+
.option("--json", "print machine-readable JSON")
|
|
451
|
+
.option("--max-files <number>", "maximum files to inspect", "5000")
|
|
452
|
+
.action(async (root, options) => {
|
|
453
|
+
const maximumFiles = Number.parseInt(options.maxFiles, 10);
|
|
454
|
+
if (!Number.isSafeInteger(maximumFiles) || maximumFiles < 1) {
|
|
455
|
+
throw new Error("--max-files must be a positive integer");
|
|
456
|
+
}
|
|
457
|
+
const report = await inspectProject(root, maximumFiles);
|
|
458
|
+
process.stdout.write(`${options.json ? JSON.stringify(report, null, 2) : renderInspection(report)}\n`);
|
|
459
|
+
});
|
|
460
|
+
program
|
|
461
|
+
.command("add")
|
|
462
|
+
.description("Prepare or finalize a capability installation")
|
|
463
|
+
.argument("<capability>", "capability to install")
|
|
464
|
+
.option("--root <path>", "project root", ".")
|
|
465
|
+
.option("--packs-dir <path>", "capability pack directory", defaultPacksDirectory())
|
|
466
|
+
.option("--recipe <id>", "select a capability recipe")
|
|
467
|
+
.option("--agent <name>", "record the installing Agent")
|
|
468
|
+
.option("--prepare", "prepare an operation plan (default)")
|
|
469
|
+
.option("--finalize", "verify evidence and record the installation")
|
|
470
|
+
.option("--json", "print machine-readable JSON")
|
|
471
|
+
.action(async (capability, options) => {
|
|
472
|
+
if (options.prepare && options.finalize) {
|
|
473
|
+
throw new Error("--prepare and --finalize cannot be used together");
|
|
474
|
+
}
|
|
475
|
+
if (options.finalize && options.recipe) {
|
|
476
|
+
throw new Error("--recipe is only valid while preparing an installation");
|
|
477
|
+
}
|
|
478
|
+
const projectRoot = resolve(options.root);
|
|
479
|
+
const packsDirectory = resolve(options.packsDir);
|
|
480
|
+
if (options.finalize) {
|
|
481
|
+
const result = await finalizeCapability({
|
|
482
|
+
projectRoot,
|
|
483
|
+
packsDirectory,
|
|
484
|
+
capabilityId: capability,
|
|
485
|
+
...(options.agent ? { agent: options.agent } : {}),
|
|
486
|
+
});
|
|
487
|
+
if (options.json) {
|
|
488
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
process.stdout.write([
|
|
492
|
+
`Installed ${result.capability}@${result.version}.`,
|
|
493
|
+
`Receipt: ${result.receiptPath}`,
|
|
494
|
+
`Hashed evidence entries: ${result.evidenceFiles}`,
|
|
495
|
+
].join("\n") + "\n");
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
const result = await prepareCapability({
|
|
499
|
+
projectRoot,
|
|
500
|
+
packsDirectory,
|
|
501
|
+
capabilityId: capability,
|
|
502
|
+
...(options.recipe ? { recipeId: options.recipe } : {}),
|
|
503
|
+
});
|
|
504
|
+
if (options.json) {
|
|
505
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
process.stdout.write([
|
|
509
|
+
`Prepared ${result.plan.capability.id}@${result.plan.capability.version}.`,
|
|
510
|
+
`Recipe: ${result.plan.recipe.id}@${result.plan.recipe.version}`,
|
|
511
|
+
`Plan: ${result.planPath}`,
|
|
512
|
+
].join("\n") + "\n");
|
|
513
|
+
});
|
|
514
|
+
program
|
|
515
|
+
.command("upgrade")
|
|
516
|
+
.description("Prepare or finalize a customization-aware capability upgrade")
|
|
517
|
+
.argument("<capability>", "capability to upgrade")
|
|
518
|
+
.option("--root <path>", "project root", ".")
|
|
519
|
+
.option("--packs-dir <path>", "target capability pack directory", defaultPacksDirectory())
|
|
520
|
+
.option("--recipe <id>", "select a target capability recipe")
|
|
521
|
+
.option("--agent <name>", "record the upgrading Agent")
|
|
522
|
+
.option("--prepare", "prepare an upgrade plan (default)")
|
|
523
|
+
.option("--finalize", "verify resolutions and record the upgrade")
|
|
524
|
+
.option("--json", "print machine-readable JSON")
|
|
525
|
+
.action(async (capability, options) => {
|
|
526
|
+
if (options.prepare && options.finalize) {
|
|
527
|
+
throw new Error("--prepare and --finalize cannot be used together");
|
|
528
|
+
}
|
|
529
|
+
if (options.finalize && options.recipe) {
|
|
530
|
+
throw new Error("--recipe is only valid while preparing an upgrade");
|
|
531
|
+
}
|
|
532
|
+
const projectRoot = resolve(options.root);
|
|
533
|
+
const targetPacksDirectory = resolve(options.packsDir);
|
|
534
|
+
if (options.finalize) {
|
|
535
|
+
const result = await finalizeUpgrade({
|
|
536
|
+
projectRoot,
|
|
537
|
+
targetPacksDirectory,
|
|
538
|
+
capabilityId: capability,
|
|
539
|
+
...(options.agent ? { agent: options.agent } : {}),
|
|
540
|
+
});
|
|
541
|
+
if (options.json) {
|
|
542
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
process.stdout.write([
|
|
546
|
+
`Upgraded ${result.capability} from ${result.fromVersion} to ${result.toVersion}.`,
|
|
547
|
+
`Receipt: ${result.receiptPath}`,
|
|
548
|
+
`Resolved conflicts: ${result.resolvedConflicts}`,
|
|
549
|
+
`Hashed evidence entries: ${result.evidenceFiles}`,
|
|
550
|
+
].join("\n") + "\n");
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
const result = await prepareUpgrade({
|
|
554
|
+
projectRoot,
|
|
555
|
+
targetPacksDirectory,
|
|
556
|
+
capabilityId: capability,
|
|
557
|
+
...(options.recipe ? { recipeId: options.recipe } : {}),
|
|
558
|
+
});
|
|
559
|
+
if (options.json) {
|
|
560
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
const conflicts = result.plan.drift.filter((file) => file.conflict !== "none").length;
|
|
564
|
+
process.stdout.write([
|
|
565
|
+
`Prepared ${result.plan.capability.id} upgrade from ${result.plan.capability.fromVersion} to ${result.plan.capability.toVersion}.`,
|
|
566
|
+
`Migration: ${result.plan.migration.id}@${result.plan.migration.version}`,
|
|
567
|
+
`Conflicts requiring resolution: ${conflicts}`,
|
|
568
|
+
`Plan: ${result.planPath}`,
|
|
569
|
+
].join("\n") + "\n");
|
|
570
|
+
});
|
|
571
|
+
program
|
|
572
|
+
.command("diff")
|
|
573
|
+
.description("Classify capability customization and source drift")
|
|
574
|
+
.argument("[capability]", "inspect only one capability")
|
|
575
|
+
.option("--root <path>", "project root", ".")
|
|
576
|
+
.option("--packs-dir <path>", "capability pack directory", defaultPacksDirectory())
|
|
577
|
+
.option("--json", "print machine-readable JSON")
|
|
578
|
+
.action(async (capability, options) => {
|
|
579
|
+
const report = await diffProject({
|
|
580
|
+
projectRoot: resolve(options.root),
|
|
581
|
+
packsDirectory: resolve(options.packsDir),
|
|
582
|
+
...(capability ? { capabilityId: capability } : {}),
|
|
583
|
+
});
|
|
584
|
+
process.stdout.write(`${options.json ? JSON.stringify(report, null, 2) : renderDiff(report)}\n`);
|
|
585
|
+
if (!report.ok)
|
|
586
|
+
process.exitCode = 1;
|
|
587
|
+
});
|
|
588
|
+
program
|
|
589
|
+
.command("verify")
|
|
590
|
+
.description("Verify declared capabilities and provenance evidence")
|
|
591
|
+
.argument("[capability]", "verify only one capability")
|
|
592
|
+
.option("--root <path>", "project root", ".")
|
|
593
|
+
.option("--packs-dir <path>", "capability pack directory", defaultPacksDirectory())
|
|
594
|
+
.option("--json", "print machine-readable JSON")
|
|
595
|
+
.action(async (capability, options) => {
|
|
596
|
+
const report = await verifyProject({
|
|
597
|
+
projectRoot: resolve(options.root),
|
|
598
|
+
packsDirectory: resolve(options.packsDir),
|
|
599
|
+
...(capability ? { capabilityId: capability } : {}),
|
|
600
|
+
});
|
|
601
|
+
process.stdout.write(`${options.json ? JSON.stringify(report, null, 2) : renderVerification(report)}\n`);
|
|
602
|
+
if (!report.ok)
|
|
603
|
+
process.exitCode = 1;
|
|
604
|
+
});
|
|
605
|
+
program.parseAsync().catch((error) => {
|
|
606
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
607
|
+
process.stderr.write(`aiba: ${message}\n`);
|
|
608
|
+
process.exitCode = 1;
|
|
609
|
+
});
|
package/dist/render.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ProjectDiffReport, ProjectInspection, VerificationReport } from "aiba-core";
|
|
2
|
+
export declare function renderInspection(report: ProjectInspection): string;
|
|
3
|
+
export declare function renderVerification(report: VerificationReport): string;
|
|
4
|
+
export declare function renderDiff(report: ProjectDiffReport): string;
|
package/dist/render.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export function renderInspection(report) {
|
|
2
|
+
const lines = [
|
|
3
|
+
`Project: ${report.name}`,
|
|
4
|
+
`Root: ${report.root}`,
|
|
5
|
+
`Package manager: ${report.packageManager}`,
|
|
6
|
+
`Languages: ${report.languages.length > 0
|
|
7
|
+
? report.languages.map((language) => `${language.name} (${language.files})`).join(", ")
|
|
8
|
+
: "none detected"}`,
|
|
9
|
+
`Frameworks: ${report.frameworks.length > 0 ? report.frameworks.join(", ") : "none detected"}`,
|
|
10
|
+
`AIBA state: ${report.aiba.initialized ? "initialized" : "not initialized"}`,
|
|
11
|
+
`Files scanned: ${report.filesScanned}${report.truncated ? " (limit reached)" : ""}`,
|
|
12
|
+
];
|
|
13
|
+
return lines.join("\n");
|
|
14
|
+
}
|
|
15
|
+
export function renderVerification(report) {
|
|
16
|
+
const lines = [
|
|
17
|
+
report.ok ? "Verification passed." : "Verification failed.",
|
|
18
|
+
`Project: ${report.projectRoot}`,
|
|
19
|
+
];
|
|
20
|
+
if (report.verifiedCapabilities.length > 0) {
|
|
21
|
+
lines.push(`Verified capabilities: ${report.verifiedCapabilities.join(", ")}`);
|
|
22
|
+
}
|
|
23
|
+
if (report.issues.length === 0) {
|
|
24
|
+
lines.push("Issues: none");
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
lines.push("Issues:");
|
|
28
|
+
for (const issue of report.issues) {
|
|
29
|
+
const context = [issue.capability, issue.invariant, issue.path]
|
|
30
|
+
.filter(Boolean)
|
|
31
|
+
.join(" / ");
|
|
32
|
+
lines.push(` ${issue.level === "error" ? "ERROR" : "WARN"} ${issue.code}`
|
|
33
|
+
+ `${context ? ` [${context}]` : ""}: ${issue.message}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return lines.join("\n");
|
|
37
|
+
}
|
|
38
|
+
export function renderDiff(report) {
|
|
39
|
+
const lines = [
|
|
40
|
+
report.hasDrift ? "Capability drift detected." : "No capability drift detected.",
|
|
41
|
+
`Project: ${report.projectRoot}`,
|
|
42
|
+
];
|
|
43
|
+
for (const capability of report.capabilities) {
|
|
44
|
+
lines.push(`${capability.id}@${capability.version} (${capability.ancestry} ancestry)`, ` sources: capability=${capability.sources.capability}`
|
|
45
|
+
+ `${capability.sources.recipe ? ` recipe=${capability.sources.recipe}` : ""}`);
|
|
46
|
+
for (const file of capability.files) {
|
|
47
|
+
lines.push(` ${file.status.toUpperCase()} [${file.ownership}] ${file.path}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
for (const issue of report.issues) {
|
|
51
|
+
lines.push(`ERROR ${issue.code}${issue.capability ? ` [${issue.capability}]` : ""}: ${issue.message}`);
|
|
52
|
+
}
|
|
53
|
+
return lines.join("\n");
|
|
54
|
+
}
|