@twin3-ai/agent-id 0.2.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,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
- `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"}`,
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
- async function createEnterpriseEnrollment({ siteUrl, projectRoot, publicRoot, environment = "production", method = "well_known", applyProof = false }) {
167
- const secretPaths = await prepareEnterpriseSecretDirectory(projectRoot);
168
- const identity = generateSiteAgentIdentity({ keyPath: secretPaths.key_path });
169
- const response = await post("/api/site_agents/v1/challenges", {
170
- url: siteUrl,
171
- public_key: identity.public_key_pem,
172
- environment,
173
- 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 };
174
194
  });
175
- const challenge = response && response.challenge;
176
- if (!challenge || !challenge.challenge_id) {
177
- return { ok: false, error: response && response.error || "enterprise_enrollment_failed" };
178
- }
179
- const domainProof = method === "well_known" ? buildWellKnownProofDocument(challenge) : null;
180
- await writeEnterpriseEnrollmentState(projectRoot, {
181
- status: "challenge_pending",
182
- site_url: siteUrl,
183
- environment,
184
- agent_id: challenge.agent_id,
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
- let proofReceipt = null;
190
- if (applyProof) {
191
- if (method !== "well_known") throw new Error("one-line apply currently requires --method well_known");
192
- proofReceipt = await writeWellKnownProofDocument({
193
- challenge: domainProof,
194
- projectRoot,
195
- publicRoot,
196
- approved: true,
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
- return {
200
- ok: true,
201
- schema: "agentx-enterprise-enrollment-v1",
202
- status: "challenge_pending",
203
- challenge,
204
- domain_proof: domainProof,
205
- proof_receipt: proofReceipt,
206
- identity: {
207
- algorithm: identity.algorithm,
208
- 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,
209
271
  public_key_thumbprint: identity.public_key_thumbprint,
210
- private_key_persisted_locally: true,
211
- },
212
- next_commands: method === "well_known" ? (applyProof ? [
213
- `deploy ${proofReceipt.relative_path}, then run: agent-id enterprise-domain-verify --project-root ${projectRoot}`,
214
- ] : [
215
- `agent-id enterprise-domain-proof --project-root ${projectRoot} --public-root <public-root>`,
216
- `agent-id enterprise-domain-proof --project-root ${projectRoot} --public-root <public-root> --apply`,
217
- `agent-id enterprise-domain-verify --project-root ${projectRoot}`,
218
- ]) : ["publish_domain_proof_then_verify_with_agent_id"],
219
- non_claims: [
220
- "does_not_issue_shared_site_agent_key",
221
- "does_not_persist_access_token",
222
- "does_not_grant_mutation_authority",
223
- "domain_verification_still_required",
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
- `Next: ${value.next_command}`,
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 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)])];
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: `agent-id install ${url} --project-root ${projectRoot} --apply`,
2537
- 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"]
2538
2667
  });
2539
2668
  }
2540
- 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);
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
- const receipt = await applyInstallPlan(detectedPlan, { approved: true });
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: `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"]),
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: `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]) });
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: `agent-id enterprise-policy-init --project-root ${projectRoot}`
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: 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])}`);
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: `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])],
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({ 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
+ });
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.2.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
  });