arc402-cli 0.3.3 → 0.4.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.
Files changed (102) 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 +15 -7
  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/channel.d.ts.map +1 -1
  20. package/dist/commands/channel.js +27 -17
  21. package/dist/commands/channel.js.map +1 -1
  22. package/dist/commands/coldstart.d.ts.map +1 -1
  23. package/dist/commands/coldstart.js +33 -22
  24. package/dist/commands/coldstart.js.map +1 -1
  25. package/dist/commands/config.d.ts.map +1 -1
  26. package/dist/commands/config.js +33 -17
  27. package/dist/commands/config.js.map +1 -1
  28. package/dist/commands/daemon.d.ts.map +1 -1
  29. package/dist/commands/daemon.js +44 -37
  30. package/dist/commands/daemon.js.map +1 -1
  31. package/dist/commands/endpoint.d.ts.map +1 -1
  32. package/dist/commands/endpoint.js +4 -3
  33. package/dist/commands/endpoint.js.map +1 -1
  34. package/dist/commands/feed.d.ts.map +1 -1
  35. package/dist/commands/feed.js.map +1 -1
  36. package/dist/commands/hire.d.ts.map +1 -1
  37. package/dist/commands/hire.js +3 -0
  38. package/dist/commands/hire.js.map +1 -1
  39. package/dist/commands/owner.d.ts.map +1 -1
  40. package/dist/commands/owner.js +5 -1
  41. package/dist/commands/owner.js.map +1 -1
  42. package/dist/commands/policy.d.ts.map +1 -1
  43. package/dist/commands/policy.js.map +1 -1
  44. package/dist/commands/relay.d.ts.map +1 -1
  45. package/dist/commands/relay.js.map +1 -1
  46. package/dist/commands/remediate.d.ts.map +1 -1
  47. package/dist/commands/remediate.js.map +1 -1
  48. package/dist/commands/reputation.d.ts.map +1 -1
  49. package/dist/commands/reputation.js.map +1 -1
  50. package/dist/commands/trust.d.ts.map +1 -1
  51. package/dist/commands/trust.js.map +1 -1
  52. package/dist/commands/verify.d.ts.map +1 -1
  53. package/dist/commands/verify.js.map +1 -1
  54. package/dist/commands/wallet.d.ts.map +1 -1
  55. package/dist/commands/wallet.js +15 -5
  56. package/dist/commands/wallet.js.map +1 -1
  57. package/dist/commands/watch.d.ts +3 -0
  58. package/dist/commands/watch.d.ts.map +1 -0
  59. package/dist/commands/watch.js +23 -0
  60. package/dist/commands/watch.js.map +1 -0
  61. package/dist/commands/watchtower.d.ts.map +1 -1
  62. package/dist/commands/watchtower.js.map +1 -1
  63. package/dist/config.d.ts.map +1 -1
  64. package/dist/config.js +15 -2
  65. package/dist/config.js.map +1 -1
  66. package/dist/index.js +73 -38
  67. package/dist/index.js.map +1 -1
  68. package/dist/ui/banner.d.ts.map +1 -1
  69. package/dist/ui/banner.js +4 -2
  70. package/dist/ui/banner.js.map +1 -1
  71. package/dist/ui/tree.d.ts +7 -0
  72. package/dist/ui/tree.d.ts.map +1 -0
  73. package/dist/ui/tree.js +13 -0
  74. package/dist/ui/tree.js.map +1 -0
  75. package/package.json +1 -1
  76. package/src/commands/accept.ts +19 -10
  77. package/src/commands/agent-handshake.ts +9 -4
  78. package/src/commands/agent.ts +15 -6
  79. package/src/commands/agreements.ts +51 -25
  80. package/src/commands/arbitrator.ts +26 -10
  81. package/src/commands/arena-handshake.ts +15 -8
  82. package/src/commands/channel.ts +27 -17
  83. package/src/commands/coldstart.ts +29 -20
  84. package/src/commands/config.ts +33 -17
  85. package/src/commands/daemon.ts +45 -38
  86. package/src/commands/endpoint.ts +4 -3
  87. package/src/commands/feed.ts +1 -0
  88. package/src/commands/hire.ts +8 -0
  89. package/src/commands/owner.ts +5 -1
  90. package/src/commands/policy.ts +3 -0
  91. package/src/commands/relay.ts +1 -0
  92. package/src/commands/remediate.ts +2 -0
  93. package/src/commands/reputation.ts +4 -0
  94. package/src/commands/trust.ts +3 -0
  95. package/src/commands/verify.ts +2 -0
  96. package/src/commands/wallet.ts +15 -5
  97. package/src/commands/watch.ts +23 -0
  98. package/src/commands/watchtower.ts +4 -0
  99. package/src/config.ts +15 -2
  100. package/src/index.ts +44 -4
  101. package/src/ui/banner.ts +5 -2
  102. package/src/ui/tree.ts +16 -0
@@ -3,6 +3,9 @@ import { ethers } from "ethers";
3
3
  import { loadConfig, getUsdcAddress } from "../config";
4
4
  import { requireSigner } from "../client";
5
5
  import { AGENT_REGISTRY_ABI } from "../abis";
6
+ import { startSpinner } from "../ui/spinner";
7
+ import { renderTree } from "../ui/tree";
8
+ import { c } from "../ui/colors";
6
9
 
7
10
  const DEFAULT_REGISTRY_ADDRESS = "0xD5c2851B00090c92Ba7F4723FB548bb30C9B6865";
8
11
 
@@ -122,6 +125,7 @@ export function registerArenaHandshakeCommands(program: Command): void {
122
125
 
123
126
  const handshake = new ethers.Contract(config.handshakeAddress, HANDSHAKE_ABI, signer);
124
127
 
128
+ const hsSpinner = startSpinner(`Sending ${opts.type} handshake...`);
125
129
  let tx;
126
130
  if (opts.usdc) {
127
131
  // USDC handshake
@@ -133,6 +137,7 @@ export function registerArenaHandshakeCommands(program: Command): void {
133
137
  const value = opts.tip ? ethers.parseEther(opts.tip) : 0n;
134
138
  tx = await handshake.sendHandshake(agentAddress, hsType, opts.note, { value });
135
139
  }
140
+ hsSpinner.succeed("Handshake sent");
136
141
 
137
142
  // Notify recipient's HTTP endpoint (non-blocking)
138
143
  const registryAddress = config.agentRegistryV2Address ?? config.agentRegistryAddress ?? DEFAULT_REGISTRY_ADDRESS;
@@ -150,14 +155,16 @@ export function registerArenaHandshakeCommands(program: Command): void {
150
155
  if (opts.json) {
151
156
  console.log(JSON.stringify({ tx: tx.hash, from: myAddress, to: agentAddress, type: opts.type, note: opts.note }));
152
157
  } else {
153
- console.log(`✓ Handshake sent`);
154
- console.log(` From: ${myAddress}`);
155
- console.log(` To: ${agentAddress}`);
156
- console.log(` Type: ${opts.type}`);
157
- if (opts.note) console.log(` Note: ${opts.note}`);
158
- if (opts.tip) console.log(` Tip: ${opts.tip} ETH`);
159
- if (opts.usdc) console.log(` Tip: ${opts.usdc} USDC`);
160
- console.log(` tx: ${tx.hash}`);
158
+ const treeItems = [
159
+ { label: "From", value: myAddress },
160
+ { label: "To", value: agentAddress },
161
+ { label: "Type", value: opts.type },
162
+ ...(opts.note ? [{ label: "Note", value: opts.note as string }] : []),
163
+ ...(opts.tip ? [{ label: "Tip", value: `${opts.tip as string} ETH` }] : []),
164
+ ...(opts.usdc ? [{ label: "Tip", value: `${opts.usdc as string} USDC` }] : []),
165
+ { label: "Tx", value: tx.hash as string, last: true },
166
+ ];
167
+ renderTree(treeItems);
161
168
  }
162
169
  });
163
170
 
@@ -6,6 +6,10 @@ import type { ChannelState } from "@arc402/sdk";
6
6
  import * as fs from "fs";
7
7
  import * as path from "path";
8
8
  import * as os from "os";
9
+ import { c } from '../ui/colors';
10
+ import { startSpinner } from '../ui/spinner';
11
+ import { renderTree } from '../ui/tree';
12
+ import { formatAddress } from '../ui/format';
9
13
 
10
14
  const CHANNEL_STATES_DIR = path.join(os.homedir(), ".arc402", "channel-states");
11
15
 
@@ -33,6 +37,7 @@ export function registerChannelCommands(program: Command): void {
33
37
  if (!config.serviceAgreementAddress) throw new Error("serviceAgreementAddress missing in config");
34
38
  const { signer } = await requireSigner(config);
35
39
  const client = new ChannelClient(config.serviceAgreementAddress, signer);
40
+ const spinner = startSpinner('Opening session channel…');
36
41
  const result = await client.openSessionChannel(
37
42
  provider,
38
43
  opts.token,
@@ -41,10 +46,14 @@ export function registerChannelCommands(program: Command): void {
41
46
  Number(opts.deadline)
42
47
  );
43
48
  if (opts.json || program.opts().json) {
49
+ spinner.stop();
44
50
  console.log(JSON.stringify(result, null, 2));
45
51
  } else {
46
- console.log(`channel opened: ${result.channelId}`);
47
- console.log(`tx: ${result.txHash}`);
52
+ spinner.succeed(' Opened — channel ' + result.channelId);
53
+ renderTree([
54
+ { label: 'Channel', value: result.channelId },
55
+ { label: 'Tx', value: result.txHash, last: true },
56
+ ]);
48
57
  }
49
58
  });
50
59
 
@@ -60,14 +69,15 @@ export function registerChannelCommands(program: Command): void {
60
69
  if (opts.json || program.opts().json) {
61
70
  console.log(JSON.stringify(ch, (_k, v) => typeof v === "bigint" ? v.toString() : v, 2));
62
71
  } else {
63
- console.log(`channel: ${channelId}`);
64
- console.log(` status: ${ch.status}`);
65
- console.log(` client: ${ch.client}`);
66
- console.log(` provider: ${ch.provider}`);
67
- console.log(` deposit: ${ch.depositAmount}`);
68
- console.log(` settled: ${ch.settledAmount}`);
69
- console.log(` seq: ${ch.lastSequenceNumber}`);
70
- console.log(` deadline: ${ch.deadline}`);
72
+ console.log('\n ' + c.mark + c.white(` Channel ${channelId}`));
73
+ renderTree([
74
+ { label: 'Status', value: ch.status },
75
+ { label: 'Client', value: formatAddress(ch.client) },
76
+ { label: 'Provider', value: formatAddress(ch.provider) },
77
+ { label: 'Deposit', value: ch.depositAmount.toString() },
78
+ { label: 'Settled', value: ch.settledAmount.toString() },
79
+ { label: 'Seq', value: ch.lastSequenceNumber.toString(), last: true },
80
+ ]);
71
81
  }
72
82
  });
73
83
 
@@ -107,8 +117,8 @@ export function registerChannelCommands(program: Command): void {
107
117
  if (opts.json || program.opts().json) {
108
118
  console.log(JSON.stringify(result, null, 2));
109
119
  } else {
110
- console.log(`close submitted: ${result.txHash}`);
111
- console.log("challenge window open (24h)");
120
+ console.log(' ' + c.success + c.white(` Close submitted ${result.txHash}`));
121
+ console.log(' ' + c.dim('Challenge window open (24h)'));
112
122
  }
113
123
  });
114
124
 
@@ -126,7 +136,7 @@ export function registerChannelCommands(program: Command): void {
126
136
  if (opts.json || program.opts().json) {
127
137
  console.log(JSON.stringify(result, null, 2));
128
138
  } else {
129
- console.log(`challenge submitted: ${result.txHash}`);
139
+ console.log(' ' + c.success + c.white(` Challenge submitted ${result.txHash}`));
130
140
  }
131
141
  });
132
142
 
@@ -142,7 +152,7 @@ export function registerChannelCommands(program: Command): void {
142
152
  if (opts.json || program.opts().json) {
143
153
  console.log(JSON.stringify(result, null, 2));
144
154
  } else {
145
- console.log(`finalised: ${result.txHash}`);
155
+ console.log(' ' + c.success + c.white(` Finalised — ${result.txHash}`));
146
156
  }
147
157
  });
148
158
 
@@ -158,7 +168,7 @@ export function registerChannelCommands(program: Command): void {
158
168
  if (opts.json || program.opts().json) {
159
169
  console.log(JSON.stringify(result, null, 2));
160
170
  } else {
161
- console.log(`reclaimed: ${result.txHash}`);
171
+ console.log(' ' + c.success + c.white(` Reclaimed — ${result.txHash}`));
162
172
  }
163
173
  });
164
174
 
@@ -201,8 +211,8 @@ export function registerChannelCommands(program: Command): void {
201
211
  if (opts.json || program.opts().json) {
202
212
  console.log(JSON.stringify({ stored: true, channelId, path: dest }));
203
213
  } else {
204
- console.log(`State stored: ${dest}`);
205
- console.log(` seq: ${state.sequenceNumber}`);
214
+ console.log(' ' + c.success + c.white(` State stored ${dest}`));
215
+ console.log(' ' + c.dim(`seq: ${state.sequenceNumber}`));
206
216
  }
207
217
  });
208
218
  }
@@ -2,6 +2,9 @@ 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';
5
8
 
6
9
  const VOUCHING_REGISTRY_ABI = [
7
10
  "function vouch(address newAgent, uint256 stakeAmount) external payable",
@@ -42,6 +45,7 @@ export function registerColdStartCommands(program: Command): void {
42
45
  const stakeAmount = opts.stake ? BigInt(opts.stake) : 0n;
43
46
  const contract = new ethers.Contract(config.vouchingRegistryAddress, VOUCHING_REGISTRY_ABI, signer);
44
47
 
48
+ const spinner = startSpinner(`Vouching for ${address}…`);
45
49
  const tx = await contract.vouch(address, stakeAmount);
46
50
  const receipt = await tx.wait();
47
51
 
@@ -53,11 +57,14 @@ export function registerColdStartCommands(program: Command): void {
53
57
  boostGranted: boost.toString(),
54
58
  txHash: receipt.hash,
55
59
  };
56
- if (opts.json) return console.log(JSON.stringify(payload, null, 2));
57
- console.log(`vouched for ${address}`);
58
- if (stakeAmount > 0n) console.log(` stake: ${ethers.formatEther(stakeAmount)} ETH`);
59
- console.log(` boost granted: +${boost} trust points`);
60
- console.log(` tx: ${receipt.hash}`);
60
+ if (opts.json) { spinner.stop(); return console.log(JSON.stringify(payload, null, 2)); }
61
+ spinner.succeed(` Vouched ${address}`);
62
+ renderTree([
63
+ { label: 'New Agent', value: address },
64
+ { label: 'Stake', value: stakeAmount > 0n ? `${ethers.formatEther(stakeAmount)} ETH` : '0' },
65
+ { label: 'Boost', value: `+${boost} trust points` },
66
+ { label: 'Tx', value: receipt.hash, last: true },
67
+ ]);
61
68
  });
62
69
 
63
70
  // ─── bond ──────────────────────────────────────────────────────────────────
@@ -88,14 +95,18 @@ export function registerColdStartCommands(program: Command): void {
88
95
  }
89
96
 
90
97
  const amount = opts.amount ? BigInt(opts.amount) : BOND_DEFAULT_AMOUNT;
98
+ const bondSpinner = startSpinner('Posting bond…');
91
99
  const tx = await contract.postBond({ value: amount });
92
100
  const receipt = await tx.wait();
93
101
 
94
102
  const payload = { bonded: true, amount: amount.toString(), txHash: receipt.hash };
95
- if (opts.json) return console.log(JSON.stringify(payload, null, 2));
96
- console.log(`bond posted: ${ethers.formatEther(amount)} ETH`);
97
- console.log(` claimable after 90 days of clean operation`);
98
- console.log(` tx: ${receipt.hash}`);
103
+ if (opts.json) { bondSpinner.stop(); return console.log(JSON.stringify(payload, null, 2)); }
104
+ bondSpinner.succeed(` Bond posted ${ethers.formatEther(amount)} ETH`);
105
+ renderTree([
106
+ { label: 'Amount', value: `${ethers.formatEther(amount)} ETH` },
107
+ { label: 'Tx', value: receipt.hash },
108
+ { label: 'Note', value: 'Claimable after 90 days of clean operation', last: true },
109
+ ]);
99
110
  });
100
111
 
101
112
  // arc402 bond status <address>
@@ -121,8 +132,8 @@ export function registerColdStartCommands(program: Command): void {
121
132
  } catch {
122
133
  const payload = { address, bonded: false };
123
134
  if (opts.json) return console.log(JSON.stringify(payload, null, 2));
124
- console.log(`address=${address}`);
125
- console.log(` no active bond`);
135
+ console.log('\n ' + c.mark + c.white(` Bond Status — ${address}`));
136
+ renderTree([{ label: 'Status', value: 'No active bond', last: true }]);
126
137
  return;
127
138
  }
128
139
 
@@ -140,17 +151,15 @@ export function registerColdStartCommands(program: Command): void {
140
151
  daysRemaining,
141
152
  };
142
153
  if (opts.json) return console.log(JSON.stringify(payload, null, 2));
143
- console.log(`address=${address}`);
154
+ console.log('\n ' + c.mark + c.white(` Bond Status — ${address}`));
144
155
  if (!active) {
145
- console.log(` no active bond`);
156
+ renderTree([{ label: 'Status', value: 'No active bond', last: true }]);
146
157
  return;
147
158
  }
148
- console.log(` amount: ${ethers.formatEther(amount)} ETH`);
149
- console.log(` posted: ${new Date(Number(postedAt) * 1000).toISOString()}`);
150
- if (daysRemaining > 0) {
151
- console.log(` claimable in: ${daysRemaining} days`);
152
- } else {
153
- console.log(` claimable: now`);
154
- }
159
+ renderTree([
160
+ { label: 'Amount', value: `${ethers.formatEther(amount)} ETH` },
161
+ { label: 'Posted', value: new Date(Number(postedAt) * 1000).toISOString() },
162
+ { label: 'Claimable', value: daysRemaining > 0 ? `in ${daysRemaining} days` : 'now', last: true },
163
+ ]);
155
164
  });
156
165
  }
@@ -2,31 +2,47 @@ 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
+ rpcUrl: defaults.rpcUrl ?? "https://mainnet.base.org",
21
+ trustRegistryAddress: defaults.trustRegistryAddress ?? "",
22
+ agentRegistryAddress: defaults.agentRegistryV2Address ?? defaults.agentRegistryAddress,
23
+ serviceAgreementAddress: defaults.serviceAgreementAddress,
24
+ reputationOracleAddress: defaults.reputationOracleAddress,
25
+ sponsorshipAttestationAddress: defaults.sponsorshipAttestationAddress,
26
+ capabilityRegistryAddress: defaults.capabilityRegistryAddress,
27
+ governanceAddress: defaults.governanceAddress,
28
+ ...(existing.privateKey ? { privateKey: existing.privateKey } : {}),
29
+ ...(existing.subdomainApi ? { subdomainApi: existing.subdomainApi } : {}),
30
+ ...(existing.telegramBotToken ? { telegramBotToken: existing.telegramBotToken } : {}),
31
+ ...(existing.telegramChatId ? { telegramChatId: existing.telegramChatId } : {}),
32
+ ...(existing.telegramThreadId ? { telegramThreadId: existing.telegramThreadId } : {}),
33
+ ...(existing.walletContractAddress ? { walletContractAddress: existing.walletContractAddress } : {}),
34
+ };
28
35
  saveConfig(cfg);
29
- console.log(chalk.green("✓ Config saved"));
36
+
37
+ console.log();
38
+ console.log(' ' + c.success + c.white(' Config saved'));
39
+ console.log();
40
+ console.log(chalk.dim(" Network ") + chalk.white(answers.network === "base-mainnet" ? "Base Mainnet" : "Base Sepolia"));
41
+ console.log(chalk.dim(" RPC ") + chalk.white(cfg.rpcUrl!));
42
+ console.log(chalk.dim(" Contracts ") + chalk.white("All protocol addresses loaded"));
43
+ console.log();
44
+ console.log(chalk.dim(" Next: ") + chalk.white("arc402 wallet deploy"));
45
+ console.log();
30
46
  });
31
47
  config.command("show").description("Print current config").action(() => {
32
48
  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
 
@@ -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 {
@@ -2,6 +2,9 @@ import { Command } from "commander";
2
2
  import { DisputeArbitrationClient } from "@arc402/sdk";
3
3
  import { loadConfig } from "../config";
4
4
  import { requireSigner } from "../client";
5
+ import { c } from '../ui/colors';
6
+ import { startSpinner } from '../ui/spinner';
7
+ import { formatAddress } from '../ui/format';
5
8
 
6
9
  export function registerOwnerCommands(program: Command): void {
7
10
  const owner = program.command("owner").description("Protocol ownership management (DisputeArbitration two-step transfer)");
@@ -13,8 +16,9 @@ export function registerOwnerCommands(program: Command): void {
13
16
  if (!config.disputeArbitrationAddress) throw new Error("disputeArbitrationAddress missing in config");
14
17
  const { signer } = await requireSigner(config);
15
18
  const client = new DisputeArbitrationClient(config.disputeArbitrationAddress, signer);
19
+ const spinner = startSpinner('Proposing ownership transfer…');
16
20
  await client.proposeOwner(newOwner);
17
- console.log(`ownership transfer proposed to ${newOwner} new owner must call: arc402 owner accept-transfer`);
21
+ spinner.succeed(c.success + c.white(' Ownership transfer proposed to ' + formatAddress(newOwner)));
18
22
  });
19
23
 
20
24
  owner.command("accept-transfer")
@@ -2,6 +2,9 @@ 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 { formatAddress } from '../ui/format';
5
8
 
6
9
  const POLICY_ENGINE_EXTENDED_ABI = [
7
10
  "function addToBlocklist(address wallet, address provider) external",
@@ -5,6 +5,7 @@ import * as os from "os";
5
5
  import * as http from "http";
6
6
  import * as https from "https";
7
7
  import { spawn } from "child_process";
8
+ import { c } from '../ui/colors';
8
9
 
9
10
  const PID_FILE = path.join(os.homedir(), ".arc402", "relay.pid");
10
11
 
@@ -3,6 +3,8 @@ import { ProviderResponseType, ServiceAgreementClient } from "@arc402/sdk";
3
3
  import { loadConfig } from "../config";
4
4
  import { getClient, requireSigner } from "../client";
5
5
  import { hashFile, hashString } from "../utils/hash";
6
+ import { c } from '../ui/colors';
7
+ import { startSpinner } from '../ui/spinner';
6
8
 
7
9
  export function registerRemediateCommands(program: Command): void {
8
10
  const remediate = program.command("remediate").description("Negotiated remediation before formal dispute");
@@ -3,6 +3,10 @@ import { ReputationOracleClient, ReputationSignalType } from "@arc402/sdk";
3
3
  import { ethers } from "ethers";
4
4
  import { loadConfig } from "../config";
5
5
  import { getClient, requireSigner } from "../client";
6
+ import { c } from '../ui/colors';
7
+ import { startSpinner } from '../ui/spinner';
8
+ import { renderTree } from '../ui/tree';
9
+ import { formatAddress } from '../ui/format';
6
10
 
7
11
  const reputation = new Command("reputation").description("Network-wide reputation signals");
8
12
 
@@ -3,6 +3,9 @@ import { ReputationOracleClient, SponsorshipAttestationClient, TrustClient } fro
3
3
  import { loadConfig } from "../config";
4
4
  import { getClient } from "../client";
5
5
  import { getTrustTier, identityTierLabel } from "../utils/format";
6
+ import { c } from '../ui/colors';
7
+ import { renderTree } from '../ui/tree';
8
+ import { formatAddress } from '../ui/format';
6
9
 
7
10
  export function registerTrustCommand(program: Command): void {
8
11
  program.command("trust <address>").description("Look up trust plus secondary sponsorship/reputation signals for an address").option("--json").action(async (address, opts) => {
@@ -5,6 +5,8 @@ import { loadConfig } from "../config";
5
5
  import { getClient, requireSigner } from "../client";
6
6
  import { printSenderInfo, executeContractWriteViaWallet } from "../wallet-router";
7
7
  import { SERVICE_AGREEMENT_ABI } from "../abis";
8
+ import { c } from '../ui/colors';
9
+ import { startSpinner } from '../ui/spinner';
8
10
 
9
11
  // Agreement status values from the contract
10
12
  const AGREEMENT_STATUS_NAMES: Record<number, string> = {