@twin3-ai/agent-id 0.1.0 → 0.3.0

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/bin/agent-id.js CHANGED
@@ -3,16 +3,29 @@ 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, 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
+ const { createRepositoryConnector } = require("../repository-connector.js");
8
9
  const { generateSiteAgentIdentity, createEnterpriseSession } = require("../enterprise-identity.js");
9
- const { buildWellKnownProofDocument, resolveProofTarget, writeWellKnownProofDocument } = require("../domain-proof.js");
10
+ const { buildWellKnownProofDocument, buildEnterpriseIdentityDocument, verifyEnterpriseIdentityDocument, resolveProofTarget, writeWellKnownProofDocument, writeEnterpriseIdentityDocument } = require("../domain-proof.js");
10
11
  const { initializeLocalPolicy, loadLocalPolicy, approvePolicyRule } = require("../local-policy.js");
12
+ const { createTaskExecutor } = require("../task-executor.js");
13
+ const { createOptimizationLoop } = require("../optimization-loop.js");
14
+ const crypto = require("node:crypto");
11
15
  const { verifyPortableBundle } = require("../trust-verifier.js");
12
16
  const { verifyReleaseManifest } = require("../release-verifier.js");
13
17
  const { runProductionPreflight } = require("../production-preflight.js");
14
18
  const packageMetadata = require("../package.json");
15
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
+
16
29
  async function post(path, body, headers = {}) {
17
30
  const res = await fetch(endpoint + path, {
18
31
  method: "POST",
@@ -49,6 +62,11 @@ function readFlagValue(argv, flag) {
49
62
  return idx >= 0 ? argv[idx + 1] : undefined;
50
63
  }
51
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
+
52
70
  async function enterpriseSessionForProject(projectRoot) {
53
71
  const root = path.resolve(projectRoot || process.cwd());
54
72
  const state = JSON.parse(fs.readFileSync(path.join(root, ".agent-id", "enterprise-state.json"), "utf8"));
@@ -62,7 +80,17 @@ async function enterpriseSessionForProject(projectRoot) {
62
80
  credential: existing.credential,
63
81
  identity
64
82
  });
65
- return { root, state, session };
83
+ return { root, state, session, identity, credential: existing.credential };
84
+ }
85
+
86
+ async function trustedIssuerPublicKey() {
87
+ const response = await fetch(endpoint + "/.well-known/agentx-issuer-key.json");
88
+ if (!response.ok) throw new Error("enterprise issuer key unavailable");
89
+ const descriptor = await response.json();
90
+ const publicKey = String(descriptor.public_key_pem || "");
91
+ const actual = `sha256:${crypto.createHash("sha256").update(publicKey).digest("hex")}`;
92
+ if (!publicKey || actual !== TRUSTED_ISSUER_KEY_SHA256) throw new Error("enterprise issuer key pin mismatch");
93
+ return publicKey;
66
94
  }
67
95
 
68
96
  function printManagedResult(value) {
@@ -106,7 +134,8 @@ function printProductionPreflight(value) {
106
134
  `API domain: ${checks.stable_api_origin ? "verified" : "not ready"}`,
107
135
  `Release signature: ${checks.release_signature ? "verified" : "not verified"}`,
108
136
  `npm package: ${checks.registry_integrity ? "verified" : "not ready"}`,
109
- `Agent surfaces: ${checks.health_endpoint && checks.agent_card ? "verified" : "not ready"}`,
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"}`,
110
139
  value.ok ? `Install: ${value.install_template}` : `Blockers: ${(value.blockers || []).join(", ") || "unknown"}`,
111
140
  "Use --json for the complete machine-readable result.",
112
141
  ];
@@ -149,86 +178,170 @@ async function releaseCheck({ printResult = true, setExitCode = true } = {}) {
149
178
  return result;
150
179
  }
151
180
 
152
- async function createEnterpriseEnrollment({ siteUrl, projectRoot, publicRoot, environment = "production", method = "well_known", applyProof = false }) {
153
- const secretPaths = await prepareEnterpriseSecretDirectory(projectRoot);
154
- const identity = generateSiteAgentIdentity({ keyPath: secretPaths.key_path });
155
- const response = await post("/api/site_agents/v1/challenges", {
156
- url: siteUrl,
157
- public_key: identity.public_key_pem,
158
- environment,
159
- method,
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 };
160
194
  });
161
- const challenge = response && response.challenge;
162
- if (!challenge || !challenge.challenge_id) {
163
- return { ok: false, error: response && response.error || "enterprise_enrollment_failed" };
164
- }
165
- const domainProof = method === "well_known" ? buildWellKnownProofDocument(challenge) : null;
166
- await writeEnterpriseEnrollmentState(projectRoot, {
167
- status: "challenge_pending",
168
- site_url: siteUrl,
169
- environment,
170
- agent_id: challenge.agent_id,
171
- challenge_id: challenge.challenge_id,
172
- public_key_thumbprint: identity.public_key_thumbprint,
173
- 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 };
174
205
  });
175
- let proofReceipt = null;
176
- if (applyProof) {
177
- if (method !== "well_known") throw new Error("one-line apply currently requires --method well_known");
178
- proofReceipt = await writeWellKnownProofDocument({
179
- challenge: domainProof,
180
- projectRoot,
181
- publicRoot,
182
- approved: true,
183
- });
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
+ }
184
237
  }
185
- return {
186
- ok: true,
187
- schema: "agentx-enterprise-enrollment-v1",
188
- status: "challenge_pending",
189
- challenge,
190
- domain_proof: domainProof,
191
- proof_receipt: proofReceipt,
192
- identity: {
193
- algorithm: identity.algorithm,
194
- public_key_pem: identity.public_key_pem,
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,
195
271
  public_key_thumbprint: identity.public_key_thumbprint,
196
- private_key_persisted_locally: true,
197
- },
198
- next_commands: method === "well_known" ? (applyProof ? [
199
- `deploy ${proofReceipt.relative_path}, then run: agent-id enterprise-domain-verify --project-root ${projectRoot}`,
200
- ] : [
201
- `agent-id enterprise-domain-proof --project-root ${projectRoot} --public-root <public-root>`,
202
- `agent-id enterprise-domain-proof --project-root ${projectRoot} --public-root <public-root> --apply`,
203
- `agent-id enterprise-domain-verify --project-root ${projectRoot}`,
204
- ]) : ["publish_domain_proof_then_verify_with_agent_id"],
205
- non_claims: [
206
- "does_not_issue_shared_site_agent_key",
207
- "does_not_persist_access_token",
208
- "does_not_grant_mutation_authority",
209
- "domain_verification_still_required",
210
- ],
211
- };
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
+ }
212
316
  }
213
317
 
214
318
  function printSignedInstallResult(value) {
215
319
  if (process.argv.includes("--json")) return print(value);
216
320
  const plan = value.plan || {};
217
321
  const enrollment = value.enrollment || {};
322
+ const executableCommand = (enrollment.next_commands || [])[0];
218
323
  const lines = value.dry_run ? [
219
324
  "Agent ID signed installation plan",
220
325
  `Site: ${plan.site_url || "unknown"}`,
221
326
  `Framework: ${plan.framework || "unknown"}`,
222
327
  "Identity: Ed25519, domain-bound",
223
328
  "No files changed",
224
- `Next: ${value.next_command}`,
329
+ "Copy and run:",
330
+ value.next_command,
225
331
  ] : [
226
332
  "Agent ID signed installation started",
227
333
  `Site: ${plan.site_url || "unknown"}`,
228
334
  "Signed identity: created",
229
335
  `Domain proof: ${enrollment.proof_receipt?.relative_path || "pending"}`,
230
- `Next: ${(enrollment.next_commands || ["deploy the domain proof"])[0]}`,
231
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
+ }
232
345
  lines.push("Use --json for the complete machine-readable result.");
233
346
  console.log(lines.join("\n"));
234
347
  }
@@ -1348,22 +1461,28 @@ async function runCli(argv) {
1348
1461
  " agent-id job-evaluate <job-id>",
1349
1462
  " agent-id register <url>",
1350
1463
  " agent-id install-guide <url>",
1351
- " 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]",
1352
1465
  " agent-id install-resume [--project-root <dir>]",
1353
1466
  " agent-id install-uninstall [--project-root <dir>] --approve",
1354
1467
  " agent-id enterprise-enroll <url> [--environment production|staging] [--method well_known|dns_txt|html_meta] [--project-root <dir>]",
1355
1468
  " agent-id enterprise-domain-proof [--project-root <dir>] [--public-root <dir>] [--apply]",
1356
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]",
1357
1472
  " agent-id enterprise-credential-rotate [--project-root <dir>] --approve",
1358
1473
  " agent-id enterprise-credential-revoke [--project-root <dir>] --reason <reason> --approve",
1359
1474
  " agent-id enterprise-policy-init [--project-root <dir>]",
1360
1475
  " agent-id enterprise-policy-approve <rule-json-file> [--project-root <dir>] --approve --approved-by <identity>",
1361
1476
  " agent-id enterprise-status [--project-root <dir>]",
1477
+ " agent-id dashboard-login [--project-root <dir>] [--json]",
1362
1478
  " agent-id enterprise-managed-enable [--project-root <dir>] [--json]",
1363
1479
  " agent-id enterprise-managed-status [--project-root <dir>] [--json]",
1364
1480
  " agent-id enterprise-managed-pause [--project-root <dir>] [--json]",
1365
1481
  " agent-id enterprise-managed-resume [--project-root <dir>] [--json]",
1366
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]",
1484
+ " agent-id enterprise-optimize-once [--project-root <dir>] [--approve-task <task-id>] [--json]",
1485
+ " agent-id enterprise-rollback-once --task-id <task-id> --project-root <dir> --approve [--json]",
1367
1486
  " agent-id enterprise-contract",
1368
1487
  " agent-id repository-patch-plan <root> <changes-json-file>",
1369
1488
  " agent-id repository-patch-apply <plan-json-file> --approve",
@@ -2499,7 +2618,26 @@ async function runCli(argv) {
2499
2618
  if (cmd === "install") {
2500
2619
  if (!url) throw new Error("install requires a site URL");
2501
2620
  const projectRoot = readFlagValue(argv, "--project-root") || process.cwd();
2502
- const detectedPlan = await buildInstallPlan({ url, projectRoot, endpoint });
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)])];
2503
2641
  const plan = {
2504
2642
  schema: "agentx-signed-install-plan-v1",
2505
2643
  site_url: detectedPlan.site_url,
@@ -2507,8 +2645,15 @@ async function runCli(argv) {
2507
2645
  project_root: detectedPlan.project_root,
2508
2646
  framework: detectedPlan.framework,
2509
2647
  package_manager: detectedPlan.package_manager,
2648
+ modules: detectedPlan.modules,
2649
+ granted_permissions: detectedPlan.granted_permissions,
2510
2650
  identity_mode: "ed25519_domain_bound",
2511
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,
2512
2657
  side_effects: ["create_customer_private_key", "write_agent_id_state", "write_public_domain_proof"],
2513
2658
  excluded_side_effects: ["package_install", "shared_site_agent_key", "customer_content_change", "deployment", "database_change", "default_branch_push"],
2514
2659
  };
@@ -2517,13 +2662,16 @@ async function runCli(argv) {
2517
2662
  ok: true,
2518
2663
  dry_run: true,
2519
2664
  plan,
2520
- next_command: `agent-id install ${url} --project-root ${projectRoot} --apply`,
2521
- non_claims: ["does_not_register_domain", "does_not_create_private_key", "does_not_modify_customer_site"]
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"]
2522
2667
  });
2523
2668
  }
2524
- const publicRoot = path.resolve(readFlagValue(argv, "--public-root") || projectRoot);
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);
2525
2672
  const gate = await releaseCheck({ printResult: false, setExitCode: false });
2526
2673
  if (!gate.ok) throw new Error(`release gate blocked: ${gate.blockers.join(", ")}`);
2674
+ const enrollmentSnapshot = enrollmentArtifactSnapshot(path.resolve(projectRoot), publicRoot);
2527
2675
  const enrollment = await createEnterpriseEnrollment({
2528
2676
  siteUrl: url,
2529
2677
  projectRoot: path.resolve(projectRoot),
@@ -2533,12 +2681,25 @@ async function runCli(argv) {
2533
2681
  applyProof: true,
2534
2682
  });
2535
2683
  if (!enrollment.ok) throw new Error(enrollment.error || "enterprise_enrollment_failed");
2684
+ // Enrollment creates the customer's key and publishes the proof, but the
2685
+ // runtime that polls for work is a separate set of files. Without this the
2686
+ // installer that writes them was never called from the CLI, so nothing was
2687
+ // installed, install-resume always reported not_installed, and
2688
+ // install-uninstall removed nothing because there was no state to find.
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
+ }
2536
2696
  return printSignedInstallResult({
2537
2697
  ok: true,
2538
2698
  dry_run: false,
2539
2699
  plan,
2540
2700
  release: gate,
2541
2701
  enrollment,
2702
+ runtime_install: receipt,
2542
2703
  non_claims: ["domain_verification_still_required", "shared_site_agent_key_not_used", "does_not_modify_customer_content"]
2543
2704
  });
2544
2705
  }
@@ -2572,12 +2733,12 @@ async function runCli(argv) {
2572
2733
  schema: "agentx-domain-proof-plan-v1",
2573
2734
  target,
2574
2735
  document,
2575
- next_command: `agent-id enterprise-domain-proof --project-root ${projectRoot} --public-root ${publicRoot} --apply`,
2736
+ next_command: pinnedCliCommand("enterprise-domain-proof", ["--project-root", projectRoot, "--public-root", publicRoot, "--apply"]),
2576
2737
  non_claims: ["does_not_verify_domain", "does_not_issue_credentials", "does_not_overwrite_customer_managed_files"]
2577
2738
  });
2578
2739
  }
2579
2740
  const receipt = await writeWellKnownProofDocument({ challenge: state.domain_proof, projectRoot, publicRoot, approved: true });
2580
- return print({ ok: true, dry_run: false, receipt, next_command: `agent-id enterprise-domain-verify --project-root ${projectRoot}` });
2741
+ return print({ ok: true, dry_run: false, receipt, next_command: pinnedCliCommand("enterprise-domain-verify", ["--project-root", projectRoot]) });
2581
2742
  }
2582
2743
  if (cmd === "enterprise-domain-verify") {
2583
2744
  const projectRoot = path.resolve(readFlagValue(argv, "--project-root") || process.cwd());
@@ -2627,7 +2788,7 @@ async function runCli(argv) {
2627
2788
  credential_path: existing.credential_path,
2628
2789
  private_key_path: existing.key_path,
2629
2790
  secret_storage_required: "move both customer secret files into the customer secret manager before production runtime",
2630
- next_command: `agent-id enterprise-policy-init --project-root ${projectRoot}`
2791
+ next_command: pinnedCliCommand("enterprise-identity-publish", ["--project-root", projectRoot, "--public-root", projectRoot])
2631
2792
  });
2632
2793
  }
2633
2794
  const response = await post("/api/site_agents/v1/challenges/verify", { challenge_id: state.challenge_id });
@@ -2640,7 +2801,7 @@ async function runCli(argv) {
2640
2801
  response.bootstrap_secret
2641
2802
  );
2642
2803
  } catch (_error) {
2643
- throw new Error(`enterprise_credential_delivery_failed; request a new single-use challenge with: agent-id enterprise-enroll ${state.site_url} --environment ${state.environment} --method well_known --project-root ${projectRoot}`);
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])}`);
2644
2805
  }
2645
2806
  await writeEnterpriseEnrollmentState(projectRoot, {
2646
2807
  ...state,
@@ -2658,7 +2819,82 @@ async function runCli(argv) {
2658
2819
  credential_path: secretReceipt.credential_path,
2659
2820
  private_key_path: path.join(projectRoot, ".agent-id", "secrets", "enterprise-ed25519.pem"),
2660
2821
  secret_storage_required: "move both customer secret files into the customer secret manager before production runtime",
2661
- next_command: `agent-id enterprise-policy-init --project-root ${projectRoot}`
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])],
2662
2898
  });
2663
2899
  }
2664
2900
  if (cmd === "enterprise-credential-rotate") {
@@ -2674,7 +2910,16 @@ async function runCli(argv) {
2674
2910
  try { response = await session.rotateCredential(); } finally { session.clear(); }
2675
2911
  const receipt = await replaceEnterpriseBootstrapCredential(projectRoot, response.credential, response.bootstrap_secret, existing.credential.credential_id);
2676
2912
  await writeEnterpriseEnrollmentState(projectRoot, { ...state, status: "verified_domain", credential_id: response.credential.credential_id });
2677
- return print({ ok: true, schema: "agentx-enterprise-credential-rotation-v1", receipt, next_action: "update_the_customer_secret_manager_copy" });
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
+ });
2678
2923
  }
2679
2924
  if (cmd === "enterprise-credential-revoke") {
2680
2925
  if (!argv.includes("--approve")) throw new Error("enterprise credential revocation requires --approve");
@@ -2712,6 +2957,19 @@ async function runCli(argv) {
2712
2957
  if (cmd === "enterprise-status") {
2713
2958
  return print(await enterpriseInstallStatus(readFlagValue(argv, "--project-root") || process.cwd()));
2714
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
+ }
2715
2973
  if ([
2716
2974
  "enterprise-managed-enable",
2717
2975
  "enterprise-managed-status",
@@ -2737,6 +2995,73 @@ async function runCli(argv) {
2737
2995
  session.clear();
2738
2996
  }
2739
2997
  }
2998
+ if (cmd === "enterprise-optimize-once") {
2999
+ const projectRoot = readFlagValue(argv, "--project-root") || process.cwd();
3000
+ const { root, state, session, identity, credential } = await enterpriseSessionForProject(projectRoot);
3001
+ const policy = loadLocalPolicy(path.join(root, ".agent-id", "enterprise-policy.json"));
3002
+ identity.subject = { tenant_id: credential.tenant_id, host: credential.host, environment: state.environment, agent_id: credential.agent_id };
3003
+ const executor = createTaskExecutor({
3004
+ issuerPublicKey: await trustedIssuerPublicKey(),
3005
+ identity,
3006
+ policy,
3007
+ connectors: { repository_patch: createRepositoryConnector({ repositoryRoot: root }) },
3008
+ statePath: path.join(root, ".agent-id", "executor-state.json")
3009
+ });
3010
+ const approvedTask = readFlagValue(argv, "--approve-task");
3011
+ try {
3012
+ return print(await createOptimizationLoop({ session, executor }).runOnce({ approvedTaskIds: approvedTask ? [approvedTask] : [] }));
3013
+ } finally {
3014
+ session.clear();
3015
+ }
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
+ }
3035
+ if (cmd === "enterprise-rollback-once") {
3036
+ const projectRoot = readFlagValue(argv, "--project-root") || process.cwd();
3037
+ const taskId = readFlagValue(argv, "--task-id");
3038
+ if (!taskId) throw new Error("enterprise-rollback-once requires --task-id");
3039
+ if (!argv.includes("--approve")) throw new Error("enterprise rollback requires --approve");
3040
+ const { root, state, session, identity, credential } = await enterpriseSessionForProject(projectRoot);
3041
+ const policy = loadLocalPolicy(path.join(root, ".agent-id", "enterprise-policy.json"));
3042
+ identity.subject = { tenant_id: credential.tenant_id, host: credential.host, environment: state.environment, agent_id: credential.agent_id };
3043
+ const executor = createTaskExecutor({
3044
+ issuerPublicKey: await trustedIssuerPublicKey(),
3045
+ identity,
3046
+ policy,
3047
+ connectors: { repository_patch: createRepositoryConnector({ repositoryRoot: root }) },
3048
+ statePath: path.join(root, ".agent-id", "executor-state.json")
3049
+ });
3050
+ try {
3051
+ const listed = await session.listTasks();
3052
+ let task = (listed.tasks || []).find((item) => item.task_id === taskId);
3053
+ if (!task) throw new Error("enterprise_task_not_found");
3054
+ if (["deployed", "failed"].includes(task.state)) {
3055
+ task = (await session.transitionTask(taskId, "rollback", task.version)).task;
3056
+ }
3057
+ if (task.state !== "rollback_pending") throw new Error(`enterprise_task_not_rollbackable:${task.state}`);
3058
+ const rollbackReceipt = await executor.rollback(taskId, { approved: true, task_id: taskId });
3059
+ const completed = await session.transitionTask(taskId, "rollback-receipt", task.version, { rollback_receipt: rollbackReceipt });
3060
+ return print({ ok: true, task: completed.task, rollback_receipt: rollbackReceipt });
3061
+ } finally {
3062
+ session.clear();
3063
+ }
3064
+ }
2740
3065
  if (cmd === "repository-patch-plan") {
2741
3066
  const root = url;
2742
3067
  const changesFile = argv[2];
@@ -2790,7 +3115,7 @@ function runMcp() {
2790
3115
 
2791
3116
  async function handleMcpLine(line) {
2792
3117
  const msg = JSON.parse(line);
2793
- if (msg.method === "initialize") return send(msg.id, { protocolVersion: "2024-11-05", serverInfo: { name: "agent-id", version: "0.1.0" } });
3118
+ if (msg.method === "initialize") return send(msg.id, { protocolVersion: "2024-11-05", serverInfo: { name: "agent-id", version: "0.3.0" } });
2794
3119
  if (msg.method === "tools/list") {
2795
3120
  const tools = [
2796
3121
  { name: "audit_url", description: "Audit a website for SEO/AEO/GEO/Agent readiness.", inputSchema: { type: "object", properties: { url: { type: "string" } }, required: ["url"] } },
@@ -3392,6 +3717,6 @@ async function handleMcpLine(line) {
3392
3717
  }
3393
3718
 
3394
3719
  runCli(process.argv.slice(2)).catch(err => {
3395
- console.error(err.message);
3720
+ console.error(err.code && !String(err.message).includes(err.code) ? `${err.code}: ${err.message}` : err.message);
3396
3721
  process.exit(1);
3397
3722
  });