arc402-cli 0.3.4 → 0.4.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.
Files changed (145) hide show
  1. package/dist/commands/accept.d.ts.map +1 -1
  2. package/dist/commands/accept.js +17 -7
  3. package/dist/commands/accept.js.map +1 -1
  4. package/dist/commands/agent-handshake.d.ts.map +1 -1
  5. package/dist/commands/agent-handshake.js +9 -4
  6. package/dist/commands/agent-handshake.js.map +1 -1
  7. package/dist/commands/agent.d.ts.map +1 -1
  8. package/dist/commands/agent.js +14 -8
  9. package/dist/commands/agent.js.map +1 -1
  10. package/dist/commands/agreements.d.ts.map +1 -1
  11. package/dist/commands/agreements.js +51 -26
  12. package/dist/commands/agreements.js.map +1 -1
  13. package/dist/commands/arbitrator.d.ts.map +1 -1
  14. package/dist/commands/arbitrator.js +77 -20
  15. package/dist/commands/arbitrator.js.map +1 -1
  16. package/dist/commands/arena-handshake.d.ts.map +1 -1
  17. package/dist/commands/arena-handshake.js +14 -11
  18. package/dist/commands/arena-handshake.js.map +1 -1
  19. package/dist/commands/arena.d.ts.map +1 -1
  20. package/dist/commands/arena.js +4 -3
  21. package/dist/commands/arena.js.map +1 -1
  22. package/dist/commands/cancel.d.ts.map +1 -1
  23. package/dist/commands/cancel.js +20 -10
  24. package/dist/commands/cancel.js.map +1 -1
  25. package/dist/commands/channel.d.ts.map +1 -1
  26. package/dist/commands/channel.js +27 -17
  27. package/dist/commands/channel.js.map +1 -1
  28. package/dist/commands/coldstart.d.ts.map +1 -1
  29. package/dist/commands/coldstart.js +33 -22
  30. package/dist/commands/coldstart.js.map +1 -1
  31. package/dist/commands/config.d.ts.map +1 -1
  32. package/dist/commands/config.js +34 -17
  33. package/dist/commands/config.js.map +1 -1
  34. package/dist/commands/daemon.d.ts.map +1 -1
  35. package/dist/commands/daemon.js +44 -37
  36. package/dist/commands/daemon.js.map +1 -1
  37. package/dist/commands/deliver.d.ts.map +1 -1
  38. package/dist/commands/deliver.js +16 -3
  39. package/dist/commands/deliver.js.map +1 -1
  40. package/dist/commands/discover.d.ts.map +1 -1
  41. package/dist/commands/discover.js +2 -0
  42. package/dist/commands/discover.js.map +1 -1
  43. package/dist/commands/dispute.d.ts.map +1 -1
  44. package/dist/commands/dispute.js +11 -10
  45. package/dist/commands/dispute.js.map +1 -1
  46. package/dist/commands/endpoint.d.ts.map +1 -1
  47. package/dist/commands/endpoint.js +4 -3
  48. package/dist/commands/endpoint.js.map +1 -1
  49. package/dist/commands/feed.d.ts.map +1 -1
  50. package/dist/commands/feed.js.map +1 -1
  51. package/dist/commands/hire.d.ts.map +1 -1
  52. package/dist/commands/hire.js +15 -2
  53. package/dist/commands/hire.js.map +1 -1
  54. package/dist/commands/migrate.d.ts.map +1 -1
  55. package/dist/commands/migrate.js +29 -29
  56. package/dist/commands/migrate.js.map +1 -1
  57. package/dist/commands/negotiate.d.ts.map +1 -1
  58. package/dist/commands/negotiate.js +8 -7
  59. package/dist/commands/negotiate.js.map +1 -1
  60. package/dist/commands/openshell.d.ts.map +1 -1
  61. package/dist/commands/openshell.js +6 -5
  62. package/dist/commands/openshell.js.map +1 -1
  63. package/dist/commands/owner.d.ts.map +1 -1
  64. package/dist/commands/owner.js +7 -2
  65. package/dist/commands/owner.js.map +1 -1
  66. package/dist/commands/policy.d.ts.map +1 -1
  67. package/dist/commands/policy.js +22 -10
  68. package/dist/commands/policy.js.map +1 -1
  69. package/dist/commands/relay.d.ts.map +1 -1
  70. package/dist/commands/relay.js +6 -5
  71. package/dist/commands/relay.js.map +1 -1
  72. package/dist/commands/remediate.d.ts.map +1 -1
  73. package/dist/commands/remediate.js +3 -2
  74. package/dist/commands/remediate.js.map +1 -1
  75. package/dist/commands/reputation.d.ts.map +1 -1
  76. package/dist/commands/reputation.js +12 -5
  77. package/dist/commands/reputation.js.map +1 -1
  78. package/dist/commands/trust.d.ts.map +1 -1
  79. package/dist/commands/trust.js +16 -1
  80. package/dist/commands/trust.js.map +1 -1
  81. package/dist/commands/verify.d.ts.map +1 -1
  82. package/dist/commands/verify.js +6 -4
  83. package/dist/commands/verify.js.map +1 -1
  84. package/dist/commands/wallet.d.ts.map +1 -1
  85. package/dist/commands/wallet.js +217 -170
  86. package/dist/commands/wallet.js.map +1 -1
  87. package/dist/commands/watch.d.ts +3 -0
  88. package/dist/commands/watch.d.ts.map +1 -0
  89. package/dist/commands/watch.js +23 -0
  90. package/dist/commands/watch.js.map +1 -0
  91. package/dist/commands/watchtower.d.ts.map +1 -1
  92. package/dist/commands/watchtower.js +30 -13
  93. package/dist/commands/watchtower.js.map +1 -1
  94. package/dist/commands/workroom.d.ts.map +1 -1
  95. package/dist/commands/workroom.js +123 -95
  96. package/dist/commands/workroom.js.map +1 -1
  97. package/dist/config.d.ts.map +1 -1
  98. package/dist/config.js +16 -2
  99. package/dist/config.js.map +1 -1
  100. package/dist/index.js +73 -38
  101. package/dist/index.js.map +1 -1
  102. package/dist/ui/banner.d.ts.map +1 -1
  103. package/dist/ui/banner.js +4 -2
  104. package/dist/ui/banner.js.map +1 -1
  105. package/dist/ui/tree.d.ts +7 -0
  106. package/dist/ui/tree.d.ts.map +1 -0
  107. package/dist/ui/tree.js +13 -0
  108. package/dist/ui/tree.js.map +1 -0
  109. package/package.json +1 -1
  110. package/src/commands/accept.ts +19 -10
  111. package/src/commands/agent-handshake.ts +9 -4
  112. package/src/commands/agent.ts +15 -6
  113. package/src/commands/agreements.ts +51 -25
  114. package/src/commands/arbitrator.ts +71 -20
  115. package/src/commands/arena-handshake.ts +15 -8
  116. package/src/commands/arena.ts +4 -3
  117. package/src/commands/cancel.ts +19 -10
  118. package/src/commands/channel.ts +27 -17
  119. package/src/commands/coldstart.ts +29 -20
  120. package/src/commands/config.ts +34 -17
  121. package/src/commands/daemon.ts +45 -38
  122. package/src/commands/deliver.ts +16 -3
  123. package/src/commands/discover.ts +2 -0
  124. package/src/commands/dispute.ts +12 -10
  125. package/src/commands/endpoint.ts +4 -3
  126. package/src/commands/feed.ts +1 -0
  127. package/src/commands/hire.ts +17 -2
  128. package/src/commands/migrate.ts +28 -26
  129. package/src/commands/negotiate.ts +8 -7
  130. package/src/commands/openshell.ts +7 -5
  131. package/src/commands/owner.ts +7 -2
  132. package/src/commands/policy.ts +21 -10
  133. package/src/commands/relay.ts +6 -5
  134. package/src/commands/remediate.ts +4 -2
  135. package/src/commands/reputation.ts +13 -5
  136. package/src/commands/trust.ts +13 -1
  137. package/src/commands/verify.ts +7 -4
  138. package/src/commands/wallet.ts +218 -170
  139. package/src/commands/watch.ts +23 -0
  140. package/src/commands/watchtower.ts +29 -13
  141. package/src/commands/workroom.ts +121 -95
  142. package/src/config.ts +16 -2
  143. package/src/index.ts +43 -3
  144. package/src/ui/banner.ts +5 -2
  145. package/src/ui/tree.ts +16 -0
@@ -2,31 +2,48 @@ import { Command } from "commander";
2
2
  import prompts from "prompts";
3
3
  import chalk from "chalk";
4
4
  import { Arc402Config, NETWORK_DEFAULTS, configExists, loadConfig, saveConfig, getSubdomainApi } from "../config";
5
+ import { c } from '../ui/colors';
5
6
 
6
7
  export function registerConfigCommands(program: Command): void {
7
8
  const config = program.command("config").description("Manage ARC-402 CLI configuration");
8
9
  config.command("init").description("Interactive setup for ~/.arc402/config.json").action(async () => {
9
10
  const existing: Partial<Arc402Config> = configExists() ? loadConfig() : {};
10
11
  const answers = await prompts([
11
- { type: "select", name: "network", message: "Network", choices: [{ title: "Base Sepolia", value: "base-sepolia" }, { title: "Base Mainnet", value: "base-mainnet" }], initial: existing.network === "base-mainnet" ? 1 : 0 },
12
- { type: "text", name: "rpcUrl", message: "RPC URL", initial: (_: unknown, values: Record<string, string>) => existing.rpcUrl ?? NETWORK_DEFAULTS[values.network]?.rpcUrl ?? "" },
13
- { type: "text", name: "agentRegistryAddress", message: "AgentRegistry address (optional)", initial: existing.agentRegistryAddress ?? "" },
14
- { type: "text", name: "serviceAgreementAddress", message: "ServiceAgreement address (optional)", initial: existing.serviceAgreementAddress ?? "" },
15
- { type: "text", name: "trustRegistryAddress", message: "TrustRegistry / TrustRegistryV3 address", initial: (_: unknown, values: Record<string, string>) => existing.trustRegistryAddress ?? NETWORK_DEFAULTS[values.network]?.trustRegistryAddress ?? "" },
16
- { type: "text", name: "reputationOracleAddress", message: "ReputationOracle address (optional)", initial: existing.reputationOracleAddress ?? "" },
17
- { type: "text", name: "sponsorshipAttestationAddress", message: "SponsorshipAttestation address (optional)", initial: existing.sponsorshipAttestationAddress ?? "" },
18
- { type: "text", name: "capabilityRegistryAddress", message: "CapabilityRegistry address (optional)", initial: existing.capabilityRegistryAddress ?? "" },
19
- { type: "text", name: "governanceAddress", message: "ARC402Governance address (optional)", initial: existing.governanceAddress ?? "" },
20
- { type: "confirm", name: "storeKey", message: "Store private key in config?", initial: false },
21
- { type: (prev: boolean) => prev ? "password" : null, name: "privateKey", message: "Private key (0x...)" },
22
- { type: "text", name: "subdomainApi", message: "Subdomain API endpoint (optional, default: https://api.arc402.xyz):", initial: existing.subdomainApi ?? "" },
23
- { type: "text", name: "telegramBotToken", message: "Telegram bot token (optional, for approval notifications):", initial: existing.telegramBotToken ?? "" },
24
- { type: "text", name: "telegramChatId", message: "Telegram chat ID (optional):", initial: existing.telegramChatId ?? "" },
25
- { type: "text", name: "telegramThreadId", message: "Telegram thread ID (optional, for forum topics):", initial: existing.telegramThreadId?.toString() ?? "" },
12
+ { type: "select", name: "network", message: "Network", choices: [{ title: "Base Mainnet", value: "base-mainnet" }, { title: "Base Sepolia (testnet)", value: "base-sepolia" }], initial: existing.network === "base-sepolia" ? 1 : 0 },
26
13
  ]);
27
- const cfg: Arc402Config = { network: answers.network, rpcUrl: answers.rpcUrl, trustRegistryAddress: answers.trustRegistryAddress, agentRegistryAddress: answers.agentRegistryAddress || undefined, serviceAgreementAddress: answers.serviceAgreementAddress || undefined, reputationOracleAddress: answers.reputationOracleAddress || undefined, sponsorshipAttestationAddress: answers.sponsorshipAttestationAddress || undefined, capabilityRegistryAddress: answers.capabilityRegistryAddress || undefined, governanceAddress: answers.governanceAddress || undefined, ...(answers.storeKey && answers.privateKey ? { privateKey: answers.privateKey } : {}), ...(answers.subdomainApi ? { subdomainApi: answers.subdomainApi } : {}), ...(answers.telegramBotToken ? { telegramBotToken: answers.telegramBotToken } : {}), ...(answers.telegramChatId ? { telegramChatId: answers.telegramChatId } : {}), ...(answers.telegramThreadId ? { telegramThreadId: Number(answers.telegramThreadId) } : {}) };
14
+
15
+ if (!answers.network) { console.log(chalk.red("✗ Setup cancelled")); return; }
16
+
17
+ const defaults = NETWORK_DEFAULTS[answers.network] ?? {};
18
+ const cfg: Arc402Config = {
19
+ network: answers.network,
20
+ walletConnectProjectId: "2bc39e3b38de7ef7deeed1e3dbbe6ad6",
21
+ rpcUrl: defaults.rpcUrl ?? "https://mainnet.base.org",
22
+ trustRegistryAddress: defaults.trustRegistryAddress ?? "",
23
+ agentRegistryAddress: defaults.agentRegistryV2Address ?? defaults.agentRegistryAddress,
24
+ serviceAgreementAddress: defaults.serviceAgreementAddress,
25
+ reputationOracleAddress: defaults.reputationOracleAddress,
26
+ sponsorshipAttestationAddress: defaults.sponsorshipAttestationAddress,
27
+ capabilityRegistryAddress: defaults.capabilityRegistryAddress,
28
+ governanceAddress: defaults.governanceAddress,
29
+ ...(existing.privateKey ? { privateKey: existing.privateKey } : {}),
30
+ ...(existing.subdomainApi ? { subdomainApi: existing.subdomainApi } : {}),
31
+ ...(existing.telegramBotToken ? { telegramBotToken: existing.telegramBotToken } : {}),
32
+ ...(existing.telegramChatId ? { telegramChatId: existing.telegramChatId } : {}),
33
+ ...(existing.telegramThreadId ? { telegramThreadId: existing.telegramThreadId } : {}),
34
+ ...(existing.walletContractAddress ? { walletContractAddress: existing.walletContractAddress } : {}),
35
+ };
28
36
  saveConfig(cfg);
29
- console.log(chalk.green("✓ Config saved"));
37
+
38
+ console.log();
39
+ console.log(' ' + c.success + c.white(' Config saved'));
40
+ console.log();
41
+ console.log(chalk.dim(" Network ") + chalk.white(answers.network === "base-mainnet" ? "Base Mainnet" : "Base Sepolia"));
42
+ console.log(chalk.dim(" RPC ") + chalk.white(cfg.rpcUrl!));
43
+ console.log(chalk.dim(" Contracts ") + chalk.white("All protocol addresses loaded"));
44
+ console.log();
45
+ console.log(chalk.dim(" Next: ") + chalk.white("arc402 wallet deploy"));
46
+ console.log();
30
47
  });
31
48
  config.command("show").description("Print current config").action(() => {
32
49
  const cfg = loadConfig();
@@ -8,6 +8,9 @@ import { ethers } from "ethers";
8
8
  import prompts from "prompts";
9
9
  import { loadConfig } from "../config";
10
10
  import { requireSigner } from "../client";
11
+ import { startSpinner } from "../ui/spinner";
12
+ import { renderTree } from "../ui/tree";
13
+ import { c } from "../ui/colors";
11
14
  import { SERVICE_AGREEMENT_ABI } from "../abis";
12
15
  import {
13
16
  DAEMON_DIR,
@@ -344,15 +347,21 @@ async function startDaemonBackground(sandboxName?: string, runtimeRemoteRoot?: s
344
347
  if (sandboxName) {
345
348
  const remotePid = await readRemotePid(sandboxName);
346
349
  if (remotePid) {
347
- console.log(`ARC-402 daemon started inside OpenShell. PID: ${remotePid}`);
348
- console.log(`Remote log: ${REMOTE_DAEMON_LOG}`);
350
+ console.log(` ${c.success} ARC-402 daemon started (OpenShell)`);
351
+ renderTree([
352
+ { label: "PID", value: String(remotePid) },
353
+ { label: "Log", value: REMOTE_DAEMON_LOG, last: true },
354
+ ]);
349
355
  return;
350
356
  }
351
357
  } else {
352
358
  const pid = readPid();
353
359
  if (pid && isProcessAlive(pid)) {
354
- console.log(`ARC-402 daemon started. PID: ${pid}`);
355
- console.log(`Log: ${DAEMON_LOG}`);
360
+ console.log(` ${c.success} ARC-402 daemon started`);
361
+ renderTree([
362
+ { label: "PID", value: String(pid) },
363
+ { label: "Log", value: DAEMON_LOG, last: true },
364
+ ]);
356
365
  return;
357
366
  }
358
367
  }
@@ -408,34 +417,31 @@ async function stopDaemon(opts: { wait?: boolean } = {}): Promise<boolean> {
408
417
  // ─── Output formatters ────────────────────────────────────────────────────────
409
418
 
410
419
  function formatStatus(data: Record<string, unknown>): void {
411
- const line = (label: string, value: string) =>
412
- console.log(`${label.padEnd(20)}${value}`);
413
-
414
- console.log("ARC-402 Daemon Status");
415
- console.log("─────────────────────");
416
- line("State:", String(data.state ?? "unknown"));
417
- line("PID:", String(data.pid ?? "unknown"));
418
- line("Uptime:", String(data.uptime ?? "unknown"));
419
- line("Wallet:", String(data.wallet ?? "unknown"));
420
- line("Machine Key:", String(data.machine_key_address ?? "unknown"));
421
- console.log();
422
- console.log("Subsystems:");
423
420
  const relayStatus = data.relay_enabled
424
421
  ? `active — polling ${data.relay_url || "relay"} every ${data.relay_poll_seconds}s`
425
422
  : "disabled";
426
423
  const watchtowerStatus = data.watchtower_enabled ? "active" : "disabled";
427
424
  const bundlerStatus = `${data.bundler_mode} — ${data.bundler_endpoint || "default"}`;
428
- console.log(` Relay: ${relayStatus}`);
429
- console.log(` Watchtower: ${watchtowerStatus}`);
430
- console.log(` Bundler: ${bundlerStatus}`);
431
- console.log();
432
425
  const pending = Number(data.pending_approval ?? 0);
433
- console.log(`Active Agreements: ${data.active_agreements ?? 0}`);
434
- if (pending > 0) {
435
- console.log(`Pending Approval: ${pending} ← (review with: arc402 daemon pending)`);
436
- } else {
437
- console.log(`Pending Approval: 0`);
438
- }
426
+
427
+ console.log(`${c.mark} ARC-402 Daemon Status`);
428
+ console.log();
429
+ renderTree([
430
+ { label: "State", value: String(data.state ?? "unknown") },
431
+ { label: "PID", value: String(data.pid ?? "unknown") },
432
+ { label: "Uptime", value: String(data.uptime ?? "unknown") },
433
+ { label: "Wallet", value: String(data.wallet ?? "unknown") },
434
+ { label: "Key", value: String(data.machine_key_address ?? "unknown") },
435
+ { label: "Relay", value: relayStatus },
436
+ { label: "Watchtower", value: watchtowerStatus },
437
+ { label: "Bundler", value: bundlerStatus },
438
+ { label: "Agreements", value: String(data.active_agreements ?? 0) },
439
+ {
440
+ label: "Pending",
441
+ value: pending > 0 ? `${pending} ← arc402 daemon pending` : "0",
442
+ last: true,
443
+ },
444
+ ]);
439
445
  }
440
446
 
441
447
  interface HireRow {
@@ -585,9 +591,9 @@ export function registerDaemonCommands(program: Command): void {
585
591
  process.exit(0);
586
592
  }
587
593
  const { configPath, host } = buildOpenShellSshConfig(openShellCfg.sandbox.name);
588
- console.log(`Stopping daemon (OpenShell PID ${remotePid})...`);
594
+ const stopSpinnerRemote = startSpinner(`Stopping daemon (OpenShell PID ${remotePid})...`);
589
595
  runCmd("ssh", ["-F", configPath, host, `kill ${remotePid}`], { timeout: 20000 });
590
- console.log("Stop signal sent.");
596
+ stopSpinnerRemote.succeed("Stop signal sent");
591
597
  return;
592
598
  }
593
599
 
@@ -597,11 +603,12 @@ export function registerDaemonCommands(program: Command): void {
597
603
  process.exit(0);
598
604
  }
599
605
 
600
- console.log(`Stopping daemon (PID ${pid})...`);
606
+ const stopSpinner = startSpinner(`Stopping daemon (PID ${pid})...`);
601
607
  const stopped = await stopDaemon({ wait: true });
602
608
  if (stopped) {
603
- console.log("Daemon stopped.");
609
+ stopSpinner.succeed("Daemon stopped");
604
610
  } else {
611
+ stopSpinner.fail("Failed to stop daemon cleanly");
605
612
  console.error("Failed to stop daemon cleanly.");
606
613
  process.exit(1);
607
614
  }
@@ -656,15 +663,15 @@ export function registerDaemonCommands(program: Command): void {
656
663
  console.log("Launch path: arc402 openshell init, then arc402 daemon start");
657
664
  process.exit(1);
658
665
  }
659
- console.log("ARC-402 Daemon Status");
660
- console.log("─────────────────────");
661
- console.log(`State: running (OpenShell sandbox)`);
662
- console.log(`PID: ${remotePid}`);
663
- console.log(`Sandbox: ${openShellCfg.sandbox.name}`);
664
- console.log(`Runtime root: ${openShellCfg.runtime?.remote_root ?? DEFAULT_RUNTIME_REMOTE_ROOT}`);
665
- console.log(`Log path: ${REMOTE_DAEMON_LOG}`);
666
+ console.log(`${c.mark} ARC-402 Daemon Status`);
666
667
  console.log();
667
- console.log("Note: remote daemon control plane is active; deep status still flows through the in-sandbox IPC socket.");
668
+ renderTree([
669
+ { label: "State", value: "running (OpenShell sandbox)" },
670
+ { label: "PID", value: String(remotePid) },
671
+ { label: "Sandbox", value: openShellCfg.sandbox.name },
672
+ { label: "Runtime", value: openShellCfg.runtime?.remote_root ?? DEFAULT_RUNTIME_REMOTE_ROOT },
673
+ { label: "Log", value: REMOTE_DAEMON_LOG, last: true },
674
+ ]);
668
675
  return;
669
676
  }
670
677
 
@@ -9,6 +9,9 @@ import { SERVICE_AGREEMENT_ABI } from "../abis";
9
9
  import { readFile } from "fs/promises";
10
10
  import prompts from "prompts";
11
11
  import { resolveAgentEndpoint, notifyAgent, DEFAULT_REGISTRY_ADDRESS } from "../endpoint-notify";
12
+ import { c } from '../ui/colors';
13
+ import { startSpinner } from '../ui/spinner';
14
+ import { renderTree } from '../ui/tree';
12
15
 
13
16
  export function registerDeliverCommand(program: Command): void {
14
17
  program
@@ -88,8 +91,9 @@ export function registerDeliverCommand(program: Command): void {
88
91
  const { ethers } = await import("ethers");
89
92
  const hash = ethers.keccak256(buffer);
90
93
 
91
- console.log(`encrypted upload: ${uri} (CID: ${cid})`);
94
+ console.log(' ' + c.dim('Encrypted upload:') + ' ' + c.white(uri));
92
95
 
96
+ const encSpinner = startSpinner('Committing deliverable…');
93
97
  if (config.walletContractAddress) {
94
98
  await executeContractWriteViaWallet(
95
99
  config.walletContractAddress, signer, config.serviceAgreementAddress,
@@ -99,11 +103,17 @@ export function registerDeliverCommand(program: Command): void {
99
103
  const client = new ServiceAgreementClient(config.serviceAgreementAddress, signer);
100
104
  await client.commitDeliverable(BigInt(id), hash);
101
105
  }
102
- console.log(`committed ${id} hash=${hash} (plaintext hash; encrypted at ${uri})`);
106
+ encSpinner.succeed(` Delivered agreement #${id}`);
107
+ renderTree([
108
+ { label: 'Hash', value: hash },
109
+ { label: 'CID', value: cid },
110
+ { label: 'URI', value: uri, last: true },
111
+ ]);
103
112
  return;
104
113
  }
105
114
 
106
115
  const hash = opts.output ? hashFile(opts.output) : hashString(opts.message ?? `agreement:${id}`);
116
+ const deliverSpinner = startSpinner(`${opts.fulfill ? 'Fulfilling' : 'Committing deliverable for'} agreement #${id}…`);
107
117
  if (config.walletContractAddress) {
108
118
  const fn = opts.fulfill ? "fulfill" : "commitDeliverable";
109
119
  await executeContractWriteViaWallet(
@@ -114,7 +124,10 @@ export function registerDeliverCommand(program: Command): void {
114
124
  const client = new ServiceAgreementClient(config.serviceAgreementAddress, signer);
115
125
  if (opts.fulfill) await client.fulfill(BigInt(id), hash); else await client.commitDeliverable(BigInt(id), hash);
116
126
  }
117
- console.log(`${opts.fulfill ? 'fulfilled' : 'committed'} ${id} hash=${hash}`);
127
+ deliverSpinner.succeed(` ${opts.fulfill ? 'Fulfilled' : 'Delivered'} — agreement #${id}`);
128
+ renderTree([
129
+ { label: 'Hash', value: hash, last: true },
130
+ ]);
118
131
 
119
132
  // Notify client's HTTP endpoint (non-blocking)
120
133
  if (clientAddress) {
@@ -4,6 +4,7 @@ import { AgentRegistryClient, CapabilityRegistryClient, ReputationOracleClient,
4
4
  import { loadConfig } from "../config";
5
5
  import { getClient } from "../client";
6
6
  import { getTrustTier, printTable, truncateAddress } from "../utils/format";
7
+ import { c } from '../ui/colors';
7
8
 
8
9
  // Minimal ABI for the new getAgentsWithCapability function (Spec 18)
9
10
  const CAPABILITY_REGISTRY_EXTRA_ABI = [
@@ -273,6 +274,7 @@ export function registerDiscoverCommand(program: Command): void {
273
274
  ));
274
275
  }
275
276
 
277
+ console.log('\n ' + c.mark + c.white(' Discover Results') + c.dim(` — ${scored.length} agent${scored.length !== 1 ? 's' : ''} found`));
276
278
  printTable(
277
279
  ["RANK", "ADDRESS", "NAME", "SERVICE", "TRUST", "SCORE", "CAPABILITIES"],
278
280
  scored.map((agent) => {
@@ -7,6 +7,8 @@ import { getClient, requireSigner } from "../client";
7
7
  import { hashFile, hashString } from "../utils/hash";
8
8
  import { printSenderInfo, executeContractWriteViaWallet } from "../wallet-router";
9
9
  import { SERVICE_AGREEMENT_ABI } from "../abis";
10
+ import { c } from '../ui/colors';
11
+ import { startSpinner } from '../ui/spinner';
10
12
 
11
13
  export function registerDisputeCommand(program: Command): void {
12
14
  const dispute = program.command("dispute").description("Formal dispute workflow; remediation-first by default, with narrow hard-fail direct-dispute exceptions");
@@ -33,7 +35,7 @@ export function registerDisputeCommand(program: Command): void {
33
35
  const disputeClass = classMap[String(opts.class).toLowerCase()];
34
36
  if (!mode || !disputeClass) throw new Error("Invalid --mode or --class");
35
37
  const feeInTokens = await client.getFeeQuote(BigInt(opts.price), opts.token, mode, disputeClass);
36
- console.log(`Fee quote for agreement ${agreementId}: ${feeInTokens.toString()} tokens`);
38
+ console.log(' ' + c.mark + c.white(` Fee quote agreement #${agreementId}: ${feeInTokens.toString()} tokens`));
37
39
  });
38
40
 
39
41
  // Open with explicit mode/class
@@ -68,7 +70,7 @@ export function registerDisputeCommand(program: Command): void {
68
70
  const client = new ServiceAgreementClient(config.serviceAgreementAddress, signer);
69
71
  await client.openDisputeWithMode(BigInt(agreementId), mode, disputeClass, opts.reason, BigInt(opts.fee));
70
72
  }
71
- console.log(`dispute opened for ${agreementId} (${opts.mode} / ${opts.class})`);
73
+ console.log(' ' + c.success + c.white(` Dispute opened — agreement #${agreementId}`));
72
74
  });
73
75
 
74
76
  // Join mutual dispute (respondent pays their half) — DisputeArbitration contract, no wallet routing
@@ -81,7 +83,7 @@ export function registerDisputeCommand(program: Command): void {
81
83
  const { signer } = await requireSigner(config);
82
84
  const client = new DisputeArbitrationClient(config.disputeArbitrationAddress, signer);
83
85
  await client.joinMutualDispute(BigInt(agreementId), BigInt(opts.fee));
84
- console.log(`joined mutual dispute ${agreementId}`);
86
+ console.log(' ' + c.success + c.white(` Joined mutual dispute — agreement #${agreementId}`));
85
87
  });
86
88
 
87
89
  dispute.command("open <id>")
@@ -177,7 +179,7 @@ export function registerDisputeCommand(program: Command): void {
177
179
  await client.dispute(BigInt(id), opts.reason);
178
180
  }
179
181
  }
180
- console.log(`dispute opened for ${id}`);
182
+ console.log(' ' + c.success + c.white(` Dispute opened agreement #${id}`));
181
183
 
182
184
  // J4-04: Display arbitration selection window deadline
183
185
  try {
@@ -210,7 +212,7 @@ export function registerDisputeCommand(program: Command): void {
210
212
  const client = new ServiceAgreementClient(config.serviceAgreementAddress, signer);
211
213
  await client.submitDisputeEvidence(BigInt(id), evidenceType, hash, opts.uri);
212
214
  }
213
- console.log(`evidence submitted for ${id} hash=${hash}`);
215
+ console.log(' ' + c.success + c.white(` Evidence submitted agreement #${id}`));
214
216
  });
215
217
 
216
218
  // status — read-only, no wallet routing needed
@@ -263,7 +265,7 @@ export function registerDisputeCommand(program: Command): void {
263
265
  const client = new ServiceAgreementClient(config.serviceAgreementAddress, signer);
264
266
  await client.nominateArbitrator(BigInt(id), opts.arbitrator);
265
267
  }
266
- console.log(`arbitrator nominated for ${id}: ${opts.arbitrator}`);
268
+ console.log(' ' + c.success + c.white(` Arbitrator nominated agreement #${id}`));
267
269
  });
268
270
 
269
271
  dispute.command("vote <id>")
@@ -309,7 +311,7 @@ export function registerDisputeCommand(program: Command): void {
309
311
  const client = new ServiceAgreementClient(config.serviceAgreementAddress, signer);
310
312
  await client.castArbitrationVote(BigInt(id), vote, BigInt(opts.providerAward), BigInt(opts.clientAward));
311
313
  }
312
- console.log(`arbitration vote recorded for ${id}`);
314
+ console.log(' ' + c.success + c.white(` Vote recorded agreement #${id}`));
313
315
  });
314
316
 
315
317
  dispute.command("human <id>")
@@ -329,7 +331,7 @@ export function registerDisputeCommand(program: Command): void {
329
331
  const client = new ServiceAgreementClient(config.serviceAgreementAddress, signer);
330
332
  await client.requestHumanEscalation(BigInt(id), opts.reason);
331
333
  }
332
- console.log(`human escalation requested for ${id}`);
334
+ console.log(' ' + c.success + c.white(` Escalated to human agreement #${id}`));
333
335
  });
334
336
 
335
337
  dispute.command("resolve <id>").description("Owner-only admin path if you are operating the dispute contract").requiredOption("--outcome <outcome>", "provider|refund|partial-provider|partial-client|mutual-cancel|human-review").option("--provider-award <amount>", "Wei/token units", "0").option("--client-award <amount>", "Wei/token units", "0").action(async (id, opts) => {
@@ -348,7 +350,7 @@ export function registerDisputeCommand(program: Command): void {
348
350
  const client = new ServiceAgreementClient(config.serviceAgreementAddress, signer);
349
351
  await client.resolveDisputeDetailed(BigInt(id), mapping[String(opts.outcome)], BigInt(opts.providerAward), BigInt(opts.clientAward));
350
352
  }
351
- console.log(`resolved ${id}`);
353
+ console.log(' ' + c.success + c.white(` Resolved — agreement #${id}`));
352
354
  });
353
355
 
354
356
  dispute.command("owner-resolve <agreementId>")
@@ -368,6 +370,6 @@ export function registerDisputeCommand(program: Command): void {
368
370
  const client = new ServiceAgreementClient(config.serviceAgreementAddress, signer);
369
371
  await client.ownerResolveDispute(BigInt(agreementId), !!opts.favorProvider);
370
372
  }
371
- console.log(`owner resolved agreement ${agreementId} — favor provider: ${!!opts.favorProvider}`);
373
+ console.log(' ' + c.success + c.white(` Owner resolved agreement #${agreementId}`));
372
374
  });
373
375
  }
@@ -17,6 +17,7 @@ import * as dns from "dns/promises";
17
17
  import * as fs from "fs";
18
18
  import * as net from "net";
19
19
  import chalk from "chalk";
20
+ import { c } from '../ui/colors';
20
21
 
21
22
  interface DoctorCheck {
22
23
  layer: string;
@@ -477,7 +478,7 @@ export function registerEndpointCommands(program: Command): void {
477
478
  });
478
479
  saveEndpointConfig(cfg);
479
480
 
480
- console.log(chalk.green(`✓ Endpoint scaffold written: ${ENDPOINT_CONFIG_PATH}`));
481
+ console.log(c.success + c.white(' Endpoint scaffold written: ' + ENDPOINT_CONFIG_PATH));
481
482
  console.log(` Agent name: ${cfg.agentName}`);
482
483
  console.log(` Hostname: ${cfg.hostname}`);
483
484
  console.log(` Public URL: ${cfg.publicUrl}`);
@@ -510,7 +511,7 @@ export function registerEndpointCommands(program: Command): void {
510
511
  const allGood = checks.every((check) => check.ok);
511
512
  const brokenLayers = Array.from(new Set(checks.filter((check) => !check.ok).map((check) => check.layer)));
512
513
 
513
- console.log("ARC-402 Endpoint Status");
514
+ console.log('\n ' + c.mark + c.white(' ARC-402 Endpoint Status'));
514
515
  console.log("─────────────────────");
515
516
  console.log(`Agent name: ${cfg.agentName}`);
516
517
  console.log(`Hostname: ${cfg.hostname}`);
@@ -571,7 +572,7 @@ export function registerEndpointCommands(program: Command): void {
571
572
  });
572
573
  saveEndpointConfig(cfg);
573
574
 
574
- console.log(chalk.green(`✓ Endpoint config locked to ${cfg.publicUrl}`));
575
+ console.log(c.success + c.white(' Endpoint config locked to ' + cfg.publicUrl));
575
576
  console.log(` Hostname: ${cfg.hostname}`);
576
577
  console.log(` Tunnel target: ${cfg.tunnelTarget}`);
577
578
  console.log(` Wallet: ${cfg.walletAddress}`);
@@ -1,5 +1,6 @@
1
1
  import { Command } from "commander";
2
2
  import chalk from "chalk";
3
+ import { c } from '../ui/colors';
3
4
 
4
5
  const SUBGRAPH_URL = "https://api.studio.thegraph.com/query/1744310/arc-402/v0.2.0";
5
6
 
@@ -7,6 +7,10 @@ import { hashFile, hashString } from "../utils/hash";
7
7
  import { parseDuration } from "../utils/time";
8
8
  import { printSenderInfo, executeContractWriteViaWallet } from "../wallet-router";
9
9
  import { AGENT_REGISTRY_ABI, SERVICE_AGREEMENT_ABI } from "../abis";
10
+ import { c } from '../ui/colors';
11
+ import { startSpinner } from '../ui/spinner';
12
+ import { renderTree, TreeItem } from '../ui/tree';
13
+ import { formatAddress } from '../ui/format';
10
14
 
11
15
  const DEFAULT_REGISTRY_ADDRESS = "0xD5c2851B00090c92Ba7F4723FB548bb30C9B6865";
12
16
 
@@ -132,6 +136,8 @@ export function registerHireCommand(program: Command): void {
132
136
 
133
137
  let agreementId: bigint;
134
138
 
139
+ const hireSpinner = startSpinner('Submitting agreement...');
140
+
135
141
  if (config.walletContractAddress) {
136
142
  // Smart wallet path — wallet handles per-tx USDC approval via maxApprovalAmount
137
143
  const tx = await executeContractWriteViaWallet(
@@ -185,6 +191,8 @@ export function registerHireCommand(program: Command): void {
185
191
  agreementId = result.agreementId;
186
192
  }
187
193
 
194
+ hireSpinner.succeed('Agreement proposed');
195
+
188
196
  // Notify provider's HTTP endpoint (non-blocking)
189
197
  const hireRegistryAddress = config.agentRegistryV2Address ?? config.agentRegistryAddress ?? DEFAULT_REGISTRY_ADDRESS;
190
198
  try {
@@ -224,7 +232,14 @@ export function registerHireCommand(program: Command): void {
224
232
  return console.log(JSON.stringify(output, null, 2));
225
233
  }
226
234
 
227
- console.log(`agreementId=${agreementId!} deliverablesHash=${deliverablesHash}`);
228
- if (transcriptHash) console.log(`transcriptHash=${transcriptHash}`);
235
+ console.log(' ' + c.success + c.white(` Agreement #${agreementId!} proposed`));
236
+ const hireTreeItems: TreeItem[] = [
237
+ { label: 'Agent', value: formatAddress(opts.agent) },
238
+ { label: 'Task', value: opts.task.slice(0, 60) + (opts.task.length > 60 ? '...' : '') },
239
+ { label: 'Service', value: opts.serviceType },
240
+ { label: 'Hash', value: String(deliverablesHash), last: !transcriptHash },
241
+ ];
242
+ if (transcriptHash) hireTreeItems.push({ label: 'Transcript', value: transcriptHash, last: true });
243
+ renderTree(hireTreeItems);
229
244
  });
230
245
  }
@@ -2,6 +2,10 @@ import { Command } from "commander";
2
2
  import { ethers } from "ethers";
3
3
  import { loadConfig } from "../config";
4
4
  import { getClient, requireSigner } from "../client";
5
+ import { c } from '../ui/colors';
6
+ import { startSpinner } from '../ui/spinner';
7
+ import { renderTree } from '../ui/tree';
8
+ import { formatAddress } from '../ui/format';
5
9
 
6
10
  const MIGRATION_REGISTRY_ABI = [
7
11
  "function registerMigration(address oldWallet, address newWallet) external",
@@ -35,8 +39,10 @@ export function registerMigrateCommands(program: Command): void {
35
39
  const { signer } = await requireSigner(config);
36
40
  const contract = new ethers.Contract(config.migrationRegistryAddress, MIGRATION_REGISTRY_ABI, signer);
37
41
 
42
+ const migrateSpinner = startSpinner('Registering migration...');
38
43
  const tx = await contract.registerMigration(oldWallet, newWallet);
39
44
  const receipt = await tx.wait();
45
+ migrateSpinner.succeed('Migration registered');
40
46
 
41
47
  const payload = {
42
48
  oldWallet,
@@ -44,11 +50,11 @@ export function registerMigrateCommands(program: Command): void {
44
50
  txHash: receipt.hash,
45
51
  };
46
52
  if (opts.json) return console.log(JSON.stringify(payload, null, 2));
47
- console.log(`migration registered`);
48
- console.log(` old: ${oldWallet}`);
49
- console.log(` new: ${newWallet}`);
50
- console.log(` note: 10% trust score decay applied on migration`);
51
- console.log(` tx: ${receipt.hash}`);
53
+ renderTree([
54
+ { label: 'Old', value: formatAddress(oldWallet) },
55
+ { label: 'New', value: formatAddress(newWallet) },
56
+ { label: 'Note', value: '10% trust score decay applied', last: true },
57
+ ]);
52
58
  });
53
59
 
54
60
  // ─── migrate status <address> ──────────────────────────────────────────────
@@ -85,15 +91,15 @@ export function registerMigrateCommands(program: Command): void {
85
91
  migratedFrom: wasSource ? migratedFrom : null,
86
92
  };
87
93
  if (opts.json) return console.log(JSON.stringify(payload, null, 2));
88
- console.log(`address=${address}`);
89
- console.log(` active wallet: ${activeWallet}`);
90
- if (isCurrent) {
91
- console.log(` status: current (no further migration)`);
92
- } else {
93
- console.log(` status: migrated score queries resolve to ${activeWallet}`);
94
- }
95
- if (hasMigrated) console.log(` migrated to: ${migratedTo}`);
96
- if (wasSource) console.log(` migrated from: ${migratedFrom}`);
94
+ const statusItems: import('../ui/tree').TreeItem[] = [
95
+ { label: 'Address', value: formatAddress(address) },
96
+ { label: 'Active', value: formatAddress(activeWallet) },
97
+ { label: 'Status', value: isCurrent ? 'current (no further migration)' : `migrated — resolves to ${formatAddress(activeWallet)}` },
98
+ ];
99
+ if (hasMigrated) statusItems.push({ label: 'Migrated to', value: formatAddress(migratedTo) });
100
+ if (wasSource) statusItems.push({ label: 'Migrated from', value: formatAddress(migratedFrom) });
101
+ statusItems[statusItems.length - 1].last = true;
102
+ renderTree(statusItems);
97
103
  });
98
104
 
99
105
  // ─── migrate lineage <address> ────────────────────────────────────────────
@@ -158,18 +164,14 @@ export function registerMigrateCommands(program: Command): void {
158
164
  };
159
165
  if (opts.json) return console.log(JSON.stringify(payload, null, 2));
160
166
 
161
- console.log(`address=${address}`);
162
- console.log(` lineage depth: ${lineage.length} wallet${lineage.length !== 1 ? "s" : ""}`);
163
- console.log(` migrations: ${entries.length}`);
164
- console.log();
165
- lineage.forEach((addr, i) => {
166
- const label = i === 0 ? " (origin)" : i === lineage.length - 1 ? " (current)" : "";
167
- console.log(` [${i}] ${addr}${label}`);
168
- if (i < entries.length) {
169
- const e = entries[i];
170
- if (e.timestamp) console.log(` migrated: ${e.timestamp}`);
171
- if (e.scoreAtMigration) console.log(` score at migration: ${e.scoreAtMigration} (decay: ${Number(e.decayBps) / 100}%)`);
172
- }
167
+ const lineageItems = lineage.map((addr, i) => {
168
+ const roleLabel = i === 0 ? ' (origin)' : i === lineage.length - 1 ? ' (current)' : '';
169
+ const e = i < entries.length ? entries[i] : null;
170
+ let value = formatAddress(addr) + roleLabel;
171
+ if (e?.timestamp) value += ` · ${e.timestamp}`;
172
+ if (e?.scoreAtMigration) value += ` · score: ${e.scoreAtMigration} (decay: ${Number(e.decayBps) / 100}%)`;
173
+ return { label: `[${i}]`, value, last: i === lineage.length - 1 };
173
174
  });
175
+ renderTree(lineageItems);
174
176
  });
175
177
  }
@@ -11,6 +11,7 @@ import {
11
11
  import { loadConfig } from "../config";
12
12
  import { requireSigner, getClient } from "../client";
13
13
  import { hashFile, hashString } from "../utils/hash";
14
+ import { c } from '../ui/colors';
14
15
 
15
16
  const sessionManager = new SessionManager();
16
17
 
@@ -102,7 +103,7 @@ export function registerNegotiateCommands(program: Command): void {
102
103
  if (opts.json) {
103
104
  console.log(JSON.stringify({ sessionId: session.sessionId, message: proposal }));
104
105
  } else {
105
- console.log(`Session started: ${session.sessionId}`);
106
+ console.log(' ' + c.success + c.white(' Session started: ' + session.sessionId.slice(0, 12) + '...'));
106
107
  console.log(`Signed PROPOSE:`);
107
108
  console.log(JSON.stringify(proposal, null, 2));
108
109
  }
@@ -138,7 +139,7 @@ export function registerNegotiateCommands(program: Command): void {
138
139
  if (opts.json) {
139
140
  console.log(JSON.stringify(counter));
140
141
  } else {
141
- console.log(`Signed COUNTER added to session ${sessionId.slice(0, 10)}...`);
142
+ console.log(' ' + c.success + c.white(' Counter added to session'));
142
143
  console.log(JSON.stringify(counter, null, 2));
143
144
  }
144
145
  });
@@ -177,8 +178,8 @@ export function registerNegotiateCommands(program: Command): void {
177
178
  message: accept,
178
179
  }));
179
180
  } else {
180
- console.log(`✓ Session ${sessionId.slice(0, 10)}... ACCEPTED`);
181
- console.log(`✓ Transcript hash: ${updatedSession.transcriptHash}`);
181
+ console.log(' ' + c.success + c.white(' Session ACCEPTED — transcript locked'));
182
+ console.log(' ' + c.dim(' Transcript:') + ' ' + c.white(updatedSession.transcriptHash ?? ''));
182
183
  if (opts.record) {
183
184
  console.log(`\nTranscript hash is ready to commit on-chain.`);
184
185
  console.log(`Run: arc402 hire --session ${sessionId} to propose() and record the transcript hash.`);
@@ -212,7 +213,7 @@ export function registerNegotiateCommands(program: Command): void {
212
213
  if (opts.json) {
213
214
  console.log(JSON.stringify(reject));
214
215
  } else {
215
- console.log(`✗ Session ${sessionId.slice(0, 10)}... REJECTED`);
216
+ console.log(' ' + c.failure + c.white(' Session REJECTED'));
216
217
  console.log(`Reason: ${opts.reason}`);
217
218
  }
218
219
  });
@@ -239,9 +240,9 @@ export function registerNegotiateCommands(program: Command): void {
239
240
  if (opts.json) {
240
241
  console.log(JSON.stringify(result));
241
242
  } else if (result.valid) {
242
- console.log(`✓ Valid — signer: ${result.recoveredSigner}`);
243
+ console.log(' ' + c.success + c.white(' Valid — signer: ' + result.recoveredSigner));
243
244
  } else {
244
- console.error(`✗ Invalid — ${result.error}`);
245
+ console.error(' ' + c.failure + c.white(' Invalid — ' + result.error));
245
246
  process.exit(1);
246
247
  }
247
248
  });