@twin3-ai/agent-id 0.2.0 → 0.3.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/b1-sync.js +85 -0
- package/bin/agent-id-b1-sync.js +44 -0
- package/bin/agent-id-cloudflare-a1.js +53 -0
- package/bin/agent-id.js +328 -75
- package/cloudflare-a1-sync.js +105 -0
- package/domain-proof.js +193 -0
- package/edge-html-injection.js +258 -0
- package/enterprise-identity.js +21 -0
- package/installer.js +565 -43
- package/package.json +12 -3
- package/production-preflight.js +35 -4
- package/release-verifier.js +46 -1
- package/repository-connector.js +123 -5
- package/site-agent.js +55 -3
- package/sync-service.js +1 -1
package/bin/agent-id.js
CHANGED
|
@@ -3,11 +3,11 @@ const { configuredEndpoint, TRUSTED_ISSUER_KEY_SHA256 } = require("../runtime-co
|
|
|
3
3
|
const endpoint = configuredEndpoint();
|
|
4
4
|
const fs = require("fs");
|
|
5
5
|
const path = require("path");
|
|
6
|
-
const { buildInstallPlan, applyInstallPlan, resumeInstall, uninstallInstall, enterpriseInstallStatus, prepareEnterpriseSecretDirectory, writeEnterpriseBootstrapCredential, readEnterpriseBootstrapCredential, replaceEnterpriseBootstrapCredential, removeEnterpriseBootstrapCredential, writeEnterpriseEnrollmentState } = require("../installer.js");
|
|
6
|
+
const { buildInstallPlan, preflightInstallPlan, applyInstallPlan, resumeInstall, uninstallInstall, enterpriseInstallStatus, prepareEnterpriseSecretDirectory, writeEnterpriseBootstrapCredential, readEnterpriseBootstrapCredential, replaceEnterpriseBootstrapCredential, removeEnterpriseBootstrapCredential, writeEnterpriseEnrollmentState } = require("../installer.js");
|
|
7
7
|
const { buildRepositoryPatch, applyRepositoryPatch, rollbackRepositoryPatch } = require("../repository-connector.js");
|
|
8
8
|
const { createRepositoryConnector } = require("../repository-connector.js");
|
|
9
9
|
const { generateSiteAgentIdentity, createEnterpriseSession } = require("../enterprise-identity.js");
|
|
10
|
-
const { buildWellKnownProofDocument, resolveProofTarget, writeWellKnownProofDocument } = require("../domain-proof.js");
|
|
10
|
+
const { buildWellKnownProofDocument, buildEnterpriseIdentityDocument, verifyEnterpriseIdentityDocument, resolveProofTarget, writeWellKnownProofDocument, writeEnterpriseIdentityDocument } = require("../domain-proof.js");
|
|
11
11
|
const { initializeLocalPolicy, loadLocalPolicy, approvePolicyRule } = require("../local-policy.js");
|
|
12
12
|
const { createTaskExecutor } = require("../task-executor.js");
|
|
13
13
|
const { createOptimizationLoop } = require("../optimization-loop.js");
|
|
@@ -17,6 +17,15 @@ const { verifyReleaseManifest } = require("../release-verifier.js");
|
|
|
17
17
|
const { runProductionPreflight } = require("../production-preflight.js");
|
|
18
18
|
const packageMetadata = require("../package.json");
|
|
19
19
|
|
|
20
|
+
function shellQuote(value) {
|
|
21
|
+
return `'${String(value).replace(/'/g, `'"'"'`)}'`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function pinnedCliCommand(command, args = []) {
|
|
25
|
+
const executable = `npx --yes --package=@twin3-ai/agent-id@${packageMetadata.version} agent-id`;
|
|
26
|
+
return [executable, command, ...args.map(shellQuote)].join(" ");
|
|
27
|
+
}
|
|
28
|
+
|
|
20
29
|
async function post(path, body, headers = {}) {
|
|
21
30
|
const res = await fetch(endpoint + path, {
|
|
22
31
|
method: "POST",
|
|
@@ -53,6 +62,11 @@ function readFlagValue(argv, flag) {
|
|
|
53
62
|
return idx >= 0 ? argv[idx + 1] : undefined;
|
|
54
63
|
}
|
|
55
64
|
|
|
65
|
+
function readCsvFlag(argv, flag) {
|
|
66
|
+
const value = readFlagValue(argv, flag);
|
|
67
|
+
return value ? value.split(",").map((item) => item.trim()).filter(Boolean) : undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
56
70
|
async function enterpriseSessionForProject(projectRoot) {
|
|
57
71
|
const root = path.resolve(projectRoot || process.cwd());
|
|
58
72
|
const state = JSON.parse(fs.readFileSync(path.join(root, ".agent-id", "enterprise-state.json"), "utf8"));
|
|
@@ -120,7 +134,8 @@ function printProductionPreflight(value) {
|
|
|
120
134
|
`API domain: ${checks.stable_api_origin ? "verified" : "not ready"}`,
|
|
121
135
|
`Release signature: ${checks.release_signature ? "verified" : "not verified"}`,
|
|
122
136
|
`npm package: ${checks.registry_integrity ? "verified" : "not ready"}`,
|
|
123
|
-
`
|
|
137
|
+
`Production binding: ${checks.production_release_binding ? "verified" : "not ready"}`,
|
|
138
|
+
`Agent surfaces: ${checks.health_endpoint && checks.agent_card && checks.install_manifest && checks.openapi ? "verified" : "not ready"}`,
|
|
124
139
|
value.ok ? `Install: ${value.install_template}` : `Blockers: ${(value.blockers || []).join(", ") || "unknown"}`,
|
|
125
140
|
"Use --json for the complete machine-readable result.",
|
|
126
141
|
];
|
|
@@ -163,86 +178,170 @@ async function releaseCheck({ printResult = true, setExitCode = true } = {}) {
|
|
|
163
178
|
return result;
|
|
164
179
|
}
|
|
165
180
|
|
|
166
|
-
|
|
167
|
-
const
|
|
168
|
-
const
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
181
|
+
function enrollmentArtifactSnapshot(projectRoot, publicRoot) {
|
|
182
|
+
const project = path.resolve(projectRoot);
|
|
183
|
+
const proof = resolveProofTarget({ projectRoot: project, publicRoot }).target;
|
|
184
|
+
const files = [
|
|
185
|
+
path.join(project, ".agent-id", "secrets", "enterprise-ed25519.pem"),
|
|
186
|
+
path.join(project, ".agent-id", "secrets", ".gitignore"),
|
|
187
|
+
path.join(project, ".agent-id", "enterprise-state.json"),
|
|
188
|
+
proof,
|
|
189
|
+
].map((target) => {
|
|
190
|
+
if (!fs.existsSync(target)) return { target, existed: false };
|
|
191
|
+
const stat = fs.lstatSync(target);
|
|
192
|
+
if (stat.isSymbolicLink() || !stat.isFile()) throw new Error(`UNSAFE_ENROLLMENT_ARTIFACT: ${target}`);
|
|
193
|
+
return { target, existed: true, content: fs.readFileSync(target), mode: stat.mode & 0o777 };
|
|
174
194
|
});
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
challenge_id: challenge.challenge_id,
|
|
186
|
-
public_key_thumbprint: identity.public_key_thumbprint,
|
|
187
|
-
domain_proof: domainProof,
|
|
195
|
+
const directoryPaths = [
|
|
196
|
+
path.join(project, ".agent-id"),
|
|
197
|
+
path.join(project, ".agent-id", "secrets"),
|
|
198
|
+
path.dirname(proof),
|
|
199
|
+
];
|
|
200
|
+
const directories = [...new Set(directoryPaths)].map((target) => {
|
|
201
|
+
if (!fs.existsSync(target)) return { target, existed: false };
|
|
202
|
+
const stat = fs.lstatSync(target);
|
|
203
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) throw new Error(`UNSAFE_ENROLLMENT_DIRECTORY: ${target}`);
|
|
204
|
+
return { target, existed: true, mode: stat.mode & 0o777 };
|
|
188
205
|
});
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
206
|
+
return { project, files, directories };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function restoreEnrollmentArtifacts(snapshot) {
|
|
210
|
+
const receipt = {
|
|
211
|
+
schema: "agentx-enrollment-local-rollback-receipt-v1",
|
|
212
|
+
status: "rolled_back",
|
|
213
|
+
restored_paths: [],
|
|
214
|
+
removed_paths: [],
|
|
215
|
+
errors: [],
|
|
216
|
+
};
|
|
217
|
+
for (const item of [...snapshot.files].reverse()) {
|
|
218
|
+
try {
|
|
219
|
+
if (!item.existed) {
|
|
220
|
+
fs.rmSync(item.target, { force: true });
|
|
221
|
+
receipt.removed_paths.push(path.relative(snapshot.project, item.target));
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
fs.mkdirSync(path.dirname(item.target), { recursive: true });
|
|
225
|
+
const temporary = `${item.target}.${process.pid}.${crypto.randomUUID()}.restore`;
|
|
226
|
+
try {
|
|
227
|
+
fs.writeFileSync(temporary, item.content, { mode: item.mode, flag: "wx" });
|
|
228
|
+
fs.renameSync(temporary, item.target);
|
|
229
|
+
fs.chmodSync(item.target, item.mode);
|
|
230
|
+
} finally {
|
|
231
|
+
fs.rmSync(temporary, { force: true });
|
|
232
|
+
}
|
|
233
|
+
receipt.restored_paths.push(path.relative(snapshot.project, item.target));
|
|
234
|
+
} catch (error) {
|
|
235
|
+
receipt.errors.push({ path: path.relative(snapshot.project, item.target), code: error.code || "RESTORE_FAILED" });
|
|
236
|
+
}
|
|
198
237
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
238
|
+
for (const item of [...snapshot.directories].reverse()) {
|
|
239
|
+
try {
|
|
240
|
+
if (item.existed) fs.chmodSync(item.target, item.mode);
|
|
241
|
+
else fs.rmdirSync(item.target);
|
|
242
|
+
} catch (error) {
|
|
243
|
+
if (!item.existed && ["ENOENT", "ENOTEMPTY"].includes(error.code)) continue;
|
|
244
|
+
receipt.errors.push({ path: path.relative(snapshot.project, item.target), code: error.code || "DIRECTORY_RESTORE_FAILED" });
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (receipt.errors.length) receipt.status = "rollback_incomplete";
|
|
248
|
+
return receipt;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async function createEnterpriseEnrollment({ siteUrl, projectRoot, publicRoot, environment = "production", method = "well_known", applyProof = false }) {
|
|
252
|
+
const snapshot = enrollmentArtifactSnapshot(projectRoot, publicRoot);
|
|
253
|
+
try {
|
|
254
|
+
const secretPaths = await prepareEnterpriseSecretDirectory(projectRoot);
|
|
255
|
+
const identity = generateSiteAgentIdentity({ keyPath: secretPaths.key_path });
|
|
256
|
+
const response = await post("/api/site_agents/v1/challenges", {
|
|
257
|
+
url: siteUrl,
|
|
258
|
+
public_key: identity.public_key_pem,
|
|
259
|
+
environment,
|
|
260
|
+
method,
|
|
261
|
+
});
|
|
262
|
+
const challenge = response && response.challenge;
|
|
263
|
+
if (!challenge || !challenge.challenge_id) throw new Error(response && response.error || "enterprise_enrollment_failed");
|
|
264
|
+
const domainProof = method === "well_known" ? buildWellKnownProofDocument(challenge) : null;
|
|
265
|
+
await writeEnterpriseEnrollmentState(projectRoot, {
|
|
266
|
+
status: "challenge_pending",
|
|
267
|
+
site_url: siteUrl,
|
|
268
|
+
environment,
|
|
269
|
+
agent_id: challenge.agent_id,
|
|
270
|
+
challenge_id: challenge.challenge_id,
|
|
209
271
|
public_key_thumbprint: identity.public_key_thumbprint,
|
|
210
|
-
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
272
|
+
domain_proof: domainProof,
|
|
273
|
+
});
|
|
274
|
+
let proofReceipt = null;
|
|
275
|
+
if (applyProof) {
|
|
276
|
+
if (method !== "well_known") throw new Error("one-line apply currently requires --method well_known");
|
|
277
|
+
proofReceipt = await writeWellKnownProofDocument({
|
|
278
|
+
challenge: domainProof,
|
|
279
|
+
projectRoot,
|
|
280
|
+
publicRoot,
|
|
281
|
+
approved: true,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
return {
|
|
285
|
+
ok: true,
|
|
286
|
+
schema: "agentx-enterprise-enrollment-v1",
|
|
287
|
+
status: "challenge_pending",
|
|
288
|
+
challenge,
|
|
289
|
+
domain_proof: domainProof,
|
|
290
|
+
proof_receipt: proofReceipt,
|
|
291
|
+
identity: {
|
|
292
|
+
algorithm: identity.algorithm,
|
|
293
|
+
public_key_pem: identity.public_key_pem,
|
|
294
|
+
public_key_thumbprint: identity.public_key_thumbprint,
|
|
295
|
+
private_key_persisted_locally: true,
|
|
296
|
+
},
|
|
297
|
+
next_commands: method === "well_known" ? (applyProof ? [
|
|
298
|
+
pinnedCliCommand("enterprise-domain-verify", ["--project-root", projectRoot]),
|
|
299
|
+
] : [
|
|
300
|
+
pinnedCliCommand("enterprise-domain-proof", ["--project-root", projectRoot, "--public-root", publicRoot]),
|
|
301
|
+
pinnedCliCommand("enterprise-domain-proof", ["--project-root", projectRoot, "--public-root", publicRoot, "--apply"]),
|
|
302
|
+
pinnedCliCommand("enterprise-domain-verify", ["--project-root", projectRoot]),
|
|
303
|
+
]) : [],
|
|
304
|
+
next_actions: method === "well_known" ? ["deploy_domain_proof", "verify_domain"] : ["publish_domain_proof", "verify_domain"],
|
|
305
|
+
non_claims: [
|
|
306
|
+
"does_not_issue_shared_site_agent_key",
|
|
307
|
+
"does_not_persist_access_token",
|
|
308
|
+
"does_not_grant_mutation_authority",
|
|
309
|
+
"domain_verification_still_required",
|
|
310
|
+
],
|
|
311
|
+
};
|
|
312
|
+
} catch (error) {
|
|
313
|
+
error.enrollment_rollback_receipt = restoreEnrollmentArtifacts(snapshot);
|
|
314
|
+
throw error;
|
|
315
|
+
}
|
|
226
316
|
}
|
|
227
317
|
|
|
228
318
|
function printSignedInstallResult(value) {
|
|
229
319
|
if (process.argv.includes("--json")) return print(value);
|
|
230
320
|
const plan = value.plan || {};
|
|
231
321
|
const enrollment = value.enrollment || {};
|
|
322
|
+
const executableCommand = (enrollment.next_commands || [])[0];
|
|
232
323
|
const lines = value.dry_run ? [
|
|
233
324
|
"Agent ID signed installation plan",
|
|
234
325
|
`Site: ${plan.site_url || "unknown"}`,
|
|
235
326
|
`Framework: ${plan.framework || "unknown"}`,
|
|
236
327
|
"Identity: Ed25519, domain-bound",
|
|
237
328
|
"No files changed",
|
|
238
|
-
|
|
329
|
+
"Copy and run:",
|
|
330
|
+
value.next_command,
|
|
239
331
|
] : [
|
|
240
332
|
"Agent ID signed installation started",
|
|
241
333
|
`Site: ${plan.site_url || "unknown"}`,
|
|
242
334
|
"Signed identity: created",
|
|
243
335
|
`Domain proof: ${enrollment.proof_receipt?.relative_path || "pending"}`,
|
|
244
|
-
`Next: ${(enrollment.next_commands || ["deploy the domain proof"])[0]}`,
|
|
245
336
|
];
|
|
337
|
+
if (!value.dry_run) {
|
|
338
|
+
if (executableCommand) {
|
|
339
|
+
lines.push(`Deploy ${enrollment.proof_receipt?.relative_path || "the domain proof"}, then run:`);
|
|
340
|
+
lines.push(executableCommand);
|
|
341
|
+
} else {
|
|
342
|
+
lines.push("Next action: publish the domain proof, then verify the domain with Agent ID.");
|
|
343
|
+
}
|
|
344
|
+
}
|
|
246
345
|
lines.push("Use --json for the complete machine-readable result.");
|
|
247
346
|
console.log(lines.join("\n"));
|
|
248
347
|
}
|
|
@@ -1362,22 +1461,26 @@ async function runCli(argv) {
|
|
|
1362
1461
|
" agent-id job-evaluate <job-id>",
|
|
1363
1462
|
" agent-id register <url>",
|
|
1364
1463
|
" agent-id install-guide <url>",
|
|
1365
|
-
" agent-id install <url> [--project-root <dir>] [--public-root <dir>] [--apply]",
|
|
1464
|
+
" agent-id install <url> [--modules identity,audit,measurement,optimization,automation] [--grant identity_publish,public_read,telemetry_submit,insights_read,ai_files_write] [--project-root <dir>] [--public-root <dir>] [--apply]",
|
|
1366
1465
|
" agent-id install-resume [--project-root <dir>]",
|
|
1367
1466
|
" agent-id install-uninstall [--project-root <dir>] --approve",
|
|
1368
1467
|
" agent-id enterprise-enroll <url> [--environment production|staging] [--method well_known|dns_txt|html_meta] [--project-root <dir>]",
|
|
1369
1468
|
" agent-id enterprise-domain-proof [--project-root <dir>] [--public-root <dir>] [--apply]",
|
|
1370
1469
|
" agent-id enterprise-domain-verify [--project-root <dir>]",
|
|
1470
|
+
" agent-id enterprise-identity-publish [--project-root <dir>] [--public-root <dir>] [--apply]",
|
|
1471
|
+
" agent-id enterprise-identity-verify <agent-id-json-file> [--offline]",
|
|
1371
1472
|
" agent-id enterprise-credential-rotate [--project-root <dir>] --approve",
|
|
1372
1473
|
" agent-id enterprise-credential-revoke [--project-root <dir>] --reason <reason> --approve",
|
|
1373
1474
|
" agent-id enterprise-policy-init [--project-root <dir>]",
|
|
1374
1475
|
" agent-id enterprise-policy-approve <rule-json-file> [--project-root <dir>] --approve --approved-by <identity>",
|
|
1375
1476
|
" agent-id enterprise-status [--project-root <dir>]",
|
|
1477
|
+
" agent-id dashboard-login [--project-root <dir>] [--json]",
|
|
1376
1478
|
" agent-id enterprise-managed-enable [--project-root <dir>] [--json]",
|
|
1377
1479
|
" agent-id enterprise-managed-status [--project-root <dir>] [--json]",
|
|
1378
1480
|
" agent-id enterprise-managed-pause [--project-root <dir>] [--json]",
|
|
1379
1481
|
" agent-id enterprise-managed-resume [--project-root <dir>] [--json]",
|
|
1380
1482
|
" agent-id enterprise-managed-runs [--project-root <dir>] [--limit <1-100>] [--json]",
|
|
1483
|
+
" agent-id enterprise-task-propose <allowlisted-path> <content-file> [--project-root <dir>] [--idempotency-key <key>] [--json]",
|
|
1381
1484
|
" agent-id enterprise-optimize-once [--project-root <dir>] [--approve-task <task-id>] [--json]",
|
|
1382
1485
|
" agent-id enterprise-rollback-once --task-id <task-id> --project-root <dir> --approve [--json]",
|
|
1383
1486
|
" agent-id enterprise-contract",
|
|
@@ -2515,7 +2618,26 @@ async function runCli(argv) {
|
|
|
2515
2618
|
if (cmd === "install") {
|
|
2516
2619
|
if (!url) throw new Error("install requires a site URL");
|
|
2517
2620
|
const projectRoot = readFlagValue(argv, "--project-root") || process.cwd();
|
|
2518
|
-
const
|
|
2621
|
+
const modules = readCsvFlag(argv, "--modules");
|
|
2622
|
+
const grantedPermissions = readCsvFlag(argv, "--grant");
|
|
2623
|
+
const detectedPlan = await buildInstallPlan({ url, projectRoot, endpoint, modules, grantedPermissions });
|
|
2624
|
+
const publicRoot = path.resolve(readFlagValue(argv, "--public-root") || projectRoot);
|
|
2625
|
+
let realProjectRoot;
|
|
2626
|
+
let realPublicRoot;
|
|
2627
|
+
try {
|
|
2628
|
+
realProjectRoot = fs.realpathSync(path.resolve(projectRoot));
|
|
2629
|
+
realPublicRoot = fs.realpathSync(publicRoot);
|
|
2630
|
+
} catch (_error) {
|
|
2631
|
+
throw new Error("INVALID_PUBLIC_ROOT: Project root and public root must already exist.");
|
|
2632
|
+
}
|
|
2633
|
+
const relativePublicRoot = path.relative(realProjectRoot, realPublicRoot);
|
|
2634
|
+
if (relativePublicRoot.startsWith("..") || path.isAbsolute(relativePublicRoot)) {
|
|
2635
|
+
throw new Error("UNSAFE_PUBLIC_ROOT: Public root must remain inside the website project.");
|
|
2636
|
+
}
|
|
2637
|
+
const publicProofPath = path.posix.join(...relativePublicRoot.split(path.sep).filter(Boolean), ".well-known", "agent-id.json");
|
|
2638
|
+
const privateKeyPath = ".agent-id/secrets/enterprise-ed25519.pem";
|
|
2639
|
+
const managedFiles = detectedPlan.files.map(({ path: filePath, mode, sha256 }) => ({ path: filePath, mode, sha256 }));
|
|
2640
|
+
const plannedWrites = [...new Set([privateKeyPath, ".agent-id/enterprise-state.json", publicProofPath, ...managedFiles.map((item) => item.path)])];
|
|
2519
2641
|
const plan = {
|
|
2520
2642
|
schema: "agentx-signed-install-plan-v1",
|
|
2521
2643
|
site_url: detectedPlan.site_url,
|
|
@@ -2523,8 +2645,15 @@ async function runCli(argv) {
|
|
|
2523
2645
|
project_root: detectedPlan.project_root,
|
|
2524
2646
|
framework: detectedPlan.framework,
|
|
2525
2647
|
package_manager: detectedPlan.package_manager,
|
|
2648
|
+
modules: detectedPlan.modules,
|
|
2649
|
+
granted_permissions: detectedPlan.granted_permissions,
|
|
2526
2650
|
identity_mode: "ed25519_domain_bound",
|
|
2527
2651
|
approval_required: true,
|
|
2652
|
+
private_key_path: privateKeyPath,
|
|
2653
|
+
private_key_behavior: "created_locally_on_apply_never_uploaded",
|
|
2654
|
+
public_proof_path: publicProofPath,
|
|
2655
|
+
managed_files: managedFiles,
|
|
2656
|
+
planned_writes: plannedWrites,
|
|
2528
2657
|
side_effects: ["create_customer_private_key", "write_agent_id_state", "write_public_domain_proof"],
|
|
2529
2658
|
excluded_side_effects: ["package_install", "shared_site_agent_key", "customer_content_change", "deployment", "database_change", "default_branch_push"],
|
|
2530
2659
|
};
|
|
@@ -2533,13 +2662,16 @@ async function runCli(argv) {
|
|
|
2533
2662
|
ok: true,
|
|
2534
2663
|
dry_run: true,
|
|
2535
2664
|
plan,
|
|
2536
|
-
next_command:
|
|
2537
|
-
non_claims: ["
|
|
2665
|
+
next_command: pinnedCliCommand("install", [url, "--modules", detectedPlan.modules.join(","), "--grant", detectedPlan.granted_permissions.join(","), "--project-root", projectRoot, "--public-root", publicRoot, "--apply"]),
|
|
2666
|
+
non_claims: ["dry_run_does_not_register_domain", "dry_run_does_not_create_private_key", "dry_run_does_not_write_files"]
|
|
2538
2667
|
});
|
|
2539
2668
|
}
|
|
2540
|
-
|
|
2669
|
+
// All local runtime conflicts must be discovered before an identity key,
|
|
2670
|
+
// server-side challenge, or public proof is created.
|
|
2671
|
+
await preflightInstallPlan(detectedPlan);
|
|
2541
2672
|
const gate = await releaseCheck({ printResult: false, setExitCode: false });
|
|
2542
2673
|
if (!gate.ok) throw new Error(`release gate blocked: ${gate.blockers.join(", ")}`);
|
|
2674
|
+
const enrollmentSnapshot = enrollmentArtifactSnapshot(path.resolve(projectRoot), publicRoot);
|
|
2543
2675
|
const enrollment = await createEnterpriseEnrollment({
|
|
2544
2676
|
siteUrl: url,
|
|
2545
2677
|
projectRoot: path.resolve(projectRoot),
|
|
@@ -2554,7 +2686,13 @@ async function runCli(argv) {
|
|
|
2554
2686
|
// installer that writes them was never called from the CLI, so nothing was
|
|
2555
2687
|
// installed, install-resume always reported not_installed, and
|
|
2556
2688
|
// install-uninstall removed nothing because there was no state to find.
|
|
2557
|
-
|
|
2689
|
+
let receipt;
|
|
2690
|
+
try {
|
|
2691
|
+
receipt = await applyInstallPlan(detectedPlan, { approved: true });
|
|
2692
|
+
} catch (error) {
|
|
2693
|
+
error.enrollment_rollback_receipt = restoreEnrollmentArtifacts(enrollmentSnapshot);
|
|
2694
|
+
throw error;
|
|
2695
|
+
}
|
|
2558
2696
|
return printSignedInstallResult({
|
|
2559
2697
|
ok: true,
|
|
2560
2698
|
dry_run: false,
|
|
@@ -2595,12 +2733,12 @@ async function runCli(argv) {
|
|
|
2595
2733
|
schema: "agentx-domain-proof-plan-v1",
|
|
2596
2734
|
target,
|
|
2597
2735
|
document,
|
|
2598
|
-
next_command:
|
|
2736
|
+
next_command: pinnedCliCommand("enterprise-domain-proof", ["--project-root", projectRoot, "--public-root", publicRoot, "--apply"]),
|
|
2599
2737
|
non_claims: ["does_not_verify_domain", "does_not_issue_credentials", "does_not_overwrite_customer_managed_files"]
|
|
2600
2738
|
});
|
|
2601
2739
|
}
|
|
2602
2740
|
const receipt = await writeWellKnownProofDocument({ challenge: state.domain_proof, projectRoot, publicRoot, approved: true });
|
|
2603
|
-
return print({ ok: true, dry_run: false, receipt, next_command:
|
|
2741
|
+
return print({ ok: true, dry_run: false, receipt, next_command: pinnedCliCommand("enterprise-domain-verify", ["--project-root", projectRoot]) });
|
|
2604
2742
|
}
|
|
2605
2743
|
if (cmd === "enterprise-domain-verify") {
|
|
2606
2744
|
const projectRoot = path.resolve(readFlagValue(argv, "--project-root") || process.cwd());
|
|
@@ -2650,7 +2788,7 @@ async function runCli(argv) {
|
|
|
2650
2788
|
credential_path: existing.credential_path,
|
|
2651
2789
|
private_key_path: existing.key_path,
|
|
2652
2790
|
secret_storage_required: "move both customer secret files into the customer secret manager before production runtime",
|
|
2653
|
-
next_command:
|
|
2791
|
+
next_command: pinnedCliCommand("enterprise-identity-publish", ["--project-root", projectRoot, "--public-root", projectRoot])
|
|
2654
2792
|
});
|
|
2655
2793
|
}
|
|
2656
2794
|
const response = await post("/api/site_agents/v1/challenges/verify", { challenge_id: state.challenge_id });
|
|
@@ -2663,7 +2801,7 @@ async function runCli(argv) {
|
|
|
2663
2801
|
response.bootstrap_secret
|
|
2664
2802
|
);
|
|
2665
2803
|
} catch (_error) {
|
|
2666
|
-
throw new Error(`enterprise_credential_delivery_failed; request a new single-use challenge with:
|
|
2804
|
+
throw new Error(`enterprise_credential_delivery_failed; request a new single-use challenge with: ${pinnedCliCommand("enterprise-enroll", [state.site_url, "--environment", state.environment, "--method", "well_known", "--project-root", projectRoot])}`);
|
|
2667
2805
|
}
|
|
2668
2806
|
await writeEnterpriseEnrollmentState(projectRoot, {
|
|
2669
2807
|
...state,
|
|
@@ -2681,7 +2819,82 @@ async function runCli(argv) {
|
|
|
2681
2819
|
credential_path: secretReceipt.credential_path,
|
|
2682
2820
|
private_key_path: path.join(projectRoot, ".agent-id", "secrets", "enterprise-ed25519.pem"),
|
|
2683
2821
|
secret_storage_required: "move both customer secret files into the customer secret manager before production runtime",
|
|
2684
|
-
next_command:
|
|
2822
|
+
next_command: pinnedCliCommand("enterprise-identity-publish", ["--project-root", projectRoot, "--public-root", projectRoot])
|
|
2823
|
+
});
|
|
2824
|
+
}
|
|
2825
|
+
if (cmd === "enterprise-identity-publish") {
|
|
2826
|
+
const projectRoot = path.resolve(readFlagValue(argv, "--project-root") || process.cwd());
|
|
2827
|
+
const publicRoot = path.resolve(readFlagValue(argv, "--public-root") || projectRoot);
|
|
2828
|
+
const { state, identity, credential } = await enterpriseSessionForProject(projectRoot);
|
|
2829
|
+
const document = buildEnterpriseIdentityDocument({
|
|
2830
|
+
state,
|
|
2831
|
+
credential,
|
|
2832
|
+
identity,
|
|
2833
|
+
serviceOrigin: endpoint,
|
|
2834
|
+
});
|
|
2835
|
+
const verification = verifyEnterpriseIdentityDocument(document);
|
|
2836
|
+
if (!argv.includes("--apply")) {
|
|
2837
|
+
return print({
|
|
2838
|
+
ok: verification.ok,
|
|
2839
|
+
dry_run: true,
|
|
2840
|
+
schema: "agentx-site-identity-publish-plan-v1",
|
|
2841
|
+
document,
|
|
2842
|
+
verification,
|
|
2843
|
+
next_command: pinnedCliCommand("enterprise-identity-publish", ["--project-root", projectRoot, "--public-root", publicRoot, "--apply"]),
|
|
2844
|
+
non_claims: ["does_not_write_without_apply", "does_not_register_erc_8004", "does_not_grant_mutation_authority"],
|
|
2845
|
+
});
|
|
2846
|
+
}
|
|
2847
|
+
const receipt = await writeEnterpriseIdentityDocument({ document, projectRoot, publicRoot, approved: true });
|
|
2848
|
+
return print({
|
|
2849
|
+
ok: true,
|
|
2850
|
+
dry_run: false,
|
|
2851
|
+
schema: "agentx-site-identity-publish-result-v1",
|
|
2852
|
+
receipt,
|
|
2853
|
+
verification,
|
|
2854
|
+
next_actions: [
|
|
2855
|
+
`deploy ${receipt.relative_path}`,
|
|
2856
|
+
`verify ${document.credential_status.status_url}`,
|
|
2857
|
+
`verify ${document.services.passport}`,
|
|
2858
|
+
pinnedCliCommand("enterprise-policy-init", ["--project-root", projectRoot]),
|
|
2859
|
+
],
|
|
2860
|
+
});
|
|
2861
|
+
}
|
|
2862
|
+
if (cmd === "enterprise-identity-verify") {
|
|
2863
|
+
if (!url || url.startsWith("--")) throw new Error("enterprise-identity-verify requires an Agent identity JSON file");
|
|
2864
|
+
const document = JSON.parse(fs.readFileSync(path.resolve(url), "utf8"));
|
|
2865
|
+
const offline = verifyEnterpriseIdentityDocument(document);
|
|
2866
|
+
if (argv.includes("--offline") || !offline.ok) {
|
|
2867
|
+
const failures = offline.ok ? [...offline.failure_codes, "online_status_not_checked"] : offline.failure_codes;
|
|
2868
|
+
return print({
|
|
2869
|
+
...offline,
|
|
2870
|
+
ok: false,
|
|
2871
|
+
document_valid: offline.ok,
|
|
2872
|
+
trust_established: false,
|
|
2873
|
+
online_checked: false,
|
|
2874
|
+
failure_codes: [...new Set(failures)],
|
|
2875
|
+
});
|
|
2876
|
+
}
|
|
2877
|
+
const expectedStatus = `${endpoint}/agentx/id/${encodeURIComponent(document.agent_id)}/status.json`;
|
|
2878
|
+
if ((document.credential_status || {}).status_url !== expectedStatus) {
|
|
2879
|
+
return print({ ...offline, ok: false, online_checked: false, failure_codes: [...offline.failure_codes, "status_url_not_trusted_issuer"] });
|
|
2880
|
+
}
|
|
2881
|
+
const current = await get(`/agentx/id/${encodeURIComponent(document.agent_id)}/status.json`);
|
|
2882
|
+
const onlineFailures = [];
|
|
2883
|
+
if (!current || current.error) onlineFailures.push("current_status_unavailable");
|
|
2884
|
+
if (current.agent_id !== document.agent_id) onlineFailures.push("current_agent_id_mismatch");
|
|
2885
|
+
if (current.host !== document.host) onlineFailures.push("current_host_mismatch");
|
|
2886
|
+
if (!current.active || current.revoked) onlineFailures.push("current_identity_not_active");
|
|
2887
|
+
if (current.credential_status && current.credential_status !== "active") onlineFailures.push("current_credential_not_active");
|
|
2888
|
+
if (current.credential_id && current.credential_id !== document.credential_status.credential_id) onlineFailures.push("current_credential_mismatch");
|
|
2889
|
+
if (current.public_key_thumbprint !== document.verification_method.public_key_thumbprint) onlineFailures.push("current_public_key_mismatch");
|
|
2890
|
+
return print({
|
|
2891
|
+
...offline,
|
|
2892
|
+
ok: offline.ok && onlineFailures.length === 0,
|
|
2893
|
+
document_valid: offline.ok,
|
|
2894
|
+
trust_established: offline.ok && onlineFailures.length === 0,
|
|
2895
|
+
online_checked: true,
|
|
2896
|
+
current_status: current,
|
|
2897
|
+
failure_codes: [...new Set([...offline.failure_codes, ...onlineFailures])],
|
|
2685
2898
|
});
|
|
2686
2899
|
}
|
|
2687
2900
|
if (cmd === "enterprise-credential-rotate") {
|
|
@@ -2697,7 +2910,16 @@ async function runCli(argv) {
|
|
|
2697
2910
|
try { response = await session.rotateCredential(); } finally { session.clear(); }
|
|
2698
2911
|
const receipt = await replaceEnterpriseBootstrapCredential(projectRoot, response.credential, response.bootstrap_secret, existing.credential.credential_id);
|
|
2699
2912
|
await writeEnterpriseEnrollmentState(projectRoot, { ...state, status: "verified_domain", credential_id: response.credential.credential_id });
|
|
2700
|
-
return print({
|
|
2913
|
+
return print({
|
|
2914
|
+
ok: true,
|
|
2915
|
+
schema: "agentx-enterprise-credential-rotation-v1",
|
|
2916
|
+
receipt,
|
|
2917
|
+
next_actions: [
|
|
2918
|
+
"update_the_customer_secret_manager_copy",
|
|
2919
|
+
`agent-id enterprise-identity-publish --project-root ${projectRoot} --public-root <public-root> --apply`,
|
|
2920
|
+
"deploy_and_verify_the_updated_well_known_identity",
|
|
2921
|
+
],
|
|
2922
|
+
});
|
|
2701
2923
|
}
|
|
2702
2924
|
if (cmd === "enterprise-credential-revoke") {
|
|
2703
2925
|
if (!argv.includes("--approve")) throw new Error("enterprise credential revocation requires --approve");
|
|
@@ -2735,6 +2957,19 @@ async function runCli(argv) {
|
|
|
2735
2957
|
if (cmd === "enterprise-status") {
|
|
2736
2958
|
return print(await enterpriseInstallStatus(readFlagValue(argv, "--project-root") || process.cwd()));
|
|
2737
2959
|
}
|
|
2960
|
+
if (cmd === "dashboard-login") {
|
|
2961
|
+
const projectRoot = readFlagValue(argv, "--project-root") || process.cwd();
|
|
2962
|
+
const { session } = await enterpriseSessionForProject(projectRoot);
|
|
2963
|
+
try {
|
|
2964
|
+
const result = await session.createDashboardPairing();
|
|
2965
|
+
if (argv.includes("--json")) return print(result);
|
|
2966
|
+
console.log(`Dashboard: ${result.login_url}`);
|
|
2967
|
+
console.log(`Valid for ${result.expires_in} seconds and usable once.`);
|
|
2968
|
+
return;
|
|
2969
|
+
} finally {
|
|
2970
|
+
session.clear();
|
|
2971
|
+
}
|
|
2972
|
+
}
|
|
2738
2973
|
if ([
|
|
2739
2974
|
"enterprise-managed-enable",
|
|
2740
2975
|
"enterprise-managed-status",
|
|
@@ -2779,6 +3014,24 @@ async function runCli(argv) {
|
|
|
2779
3014
|
session.clear();
|
|
2780
3015
|
}
|
|
2781
3016
|
}
|
|
3017
|
+
if (cmd === "enterprise-task-propose") {
|
|
3018
|
+
const projectRoot = readFlagValue(argv, "--project-root") || process.cwd();
|
|
3019
|
+
const contentFile = argv[2];
|
|
3020
|
+
if (!url || !contentFile) throw new Error("enterprise-task-propose requires <allowlisted-path> <content-file>");
|
|
3021
|
+
const policy = loadLocalPolicy(path.join(path.resolve(projectRoot), ".agent-id", "enterprise-policy.json"));
|
|
3022
|
+
const { session } = await enterpriseSessionForProject(projectRoot);
|
|
3023
|
+
try {
|
|
3024
|
+
const idempotencyKey = readFlagValue(argv, "--idempotency-key") || `cli-${crypto.randomUUID()}`;
|
|
3025
|
+
return print(await session.proposeTask({
|
|
3026
|
+
path: url,
|
|
3027
|
+
content: fs.readFileSync(path.resolve(contentFile), "utf8"),
|
|
3028
|
+
policyDigest: policy.digest,
|
|
3029
|
+
idempotencyKey,
|
|
3030
|
+
}));
|
|
3031
|
+
} finally {
|
|
3032
|
+
session.clear();
|
|
3033
|
+
}
|
|
3034
|
+
}
|
|
2782
3035
|
if (cmd === "enterprise-rollback-once") {
|
|
2783
3036
|
const projectRoot = readFlagValue(argv, "--project-root") || process.cwd();
|
|
2784
3037
|
const taskId = readFlagValue(argv, "--task-id");
|
|
@@ -2862,7 +3115,7 @@ function runMcp() {
|
|
|
2862
3115
|
|
|
2863
3116
|
async function handleMcpLine(line) {
|
|
2864
3117
|
const msg = JSON.parse(line);
|
|
2865
|
-
if (msg.method === "initialize") return send(msg.id, { protocolVersion: "2024-11-05", serverInfo: { name: "agent-id", version: "0.
|
|
3118
|
+
if (msg.method === "initialize") return send(msg.id, { protocolVersion: "2024-11-05", serverInfo: { name: "agent-id", version: "0.3.0" } });
|
|
2866
3119
|
if (msg.method === "tools/list") {
|
|
2867
3120
|
const tools = [
|
|
2868
3121
|
{ name: "audit_url", description: "Audit a website for SEO/AEO/GEO/Agent readiness.", inputSchema: { type: "object", properties: { url: { type: "string" } }, required: ["url"] } },
|
|
@@ -3464,6 +3717,6 @@ async function handleMcpLine(line) {
|
|
|
3464
3717
|
}
|
|
3465
3718
|
|
|
3466
3719
|
runCli(process.argv.slice(2)).catch(err => {
|
|
3467
|
-
console.error(err.message);
|
|
3720
|
+
console.error(err.code && !String(err.message).includes(err.code) ? `${err.code}: ${err.message}` : err.message);
|
|
3468
3721
|
process.exit(1);
|
|
3469
3722
|
});
|