@sofer_agent/cli 0.3.0 → 0.3.2

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/src/init.ts CHANGED
@@ -1,7 +1,7 @@
1
+ import { cancel, intro, isCancel, note, outro, password, select, spinner } from "@clack/prompts";
1
2
  import {
2
- type PersistedConfig,
3
- type SoferConfig,
4
3
  AgentClient,
4
+ type PersistedConfig,
5
5
  agentPaths,
6
6
  generateKeypair,
7
7
  keypairFromSecret,
@@ -16,29 +16,6 @@ import {
16
16
  writeSecret,
17
17
  } from "@sofer_agent/core";
18
18
 
19
- /** Simple spinner that logs to stdout. */
20
- function spin<T>(label: string, fn: () => Promise<T>): Promise<T> {
21
- process.stdout.write(`${label}… `);
22
- return fn().then(
23
- (result) => {
24
- console.log("done");
25
- return result;
26
- },
27
- (err) => {
28
- console.log("failed");
29
- throw err;
30
- },
31
- );
32
- }
33
-
34
- async function ask(question: string): Promise<string> {
35
- const rl = await import("node:readline/promises");
36
- const iface = rl.createInterface({ input: process.stdin, output: process.stdout });
37
- const answer = await iface.question(question);
38
- iface.close();
39
- return answer.trim();
40
- }
41
-
42
19
  /**
43
20
  * Interactive init wizard — mint the agent + provision Walrus memory.
44
21
  * Stores config to ~/.sofer/config.json and secrets to ~/.sofer/{sui,brain}.key
@@ -54,39 +31,55 @@ export async function initCommand(): Promise<void> {
54
31
  console.log(` sui key: ${agentPaths.suiKey}`);
55
32
  console.log(` brain key: ${agentPaths.brainKey}`);
56
33
  console.log(` agent: ${existing.agentObjectId}`);
57
- console.log(` address: ${existing.memwal.accountId}`);
58
- console.log(`\nRun \`sofer\` to chat. To start fresh, delete ~/.sofer/.`);
34
+ console.log(` memory: ${existing.memwal.accountId}`);
35
+ console.log(`\nRun \`sofer\` to chat. To start fresh, delete ${agentPaths.root}/.`);
59
36
  return;
60
37
  }
61
38
 
62
- console.log("╔══════════════════════════════════════════╗");
63
- console.log("║ sofer init — agent setup ║");
64
- console.log("╚══════════════════════════════════════════╝\n");
39
+ intro("sofer init");
65
40
 
66
41
  // ── 1. Network ─────────────────────────────────────────────────────────
67
- const network = (await ask("Network? [testnet/mainnet] (testnet): ")) || "testnet";
42
+ const network = (await select({
43
+ message: "Which network?",
44
+ options: [
45
+ { value: "testnet", label: "Sui Testnet" },
46
+ { value: "mainnet", label: "Sui Mainnet" },
47
+ ],
48
+ initialValue: "testnet",
49
+ })) as string | symbol;
50
+ if (isCancel(network)) {
51
+ cancel("Aborted.");
52
+ return;
53
+ }
68
54
  const net = network === "mainnet" ? "mainnet" : "testnet";
69
- console.log(` → ${net}\n`);
70
55
 
71
56
  // ── 2. Anthropic API key ───────────────────────────────────────────────
72
- const apiKey = await ask("Anthropic API key: ");
73
- if (!apiKey) throw new Error("ANTHROPIC_API_KEY is required");
74
- console.log(" → saved (encrypted to disk)\n");
57
+ const apiKey = (await password({
58
+ message: "Anthropic API key (stored in ~/.sofer/brain.key, never in .env)",
59
+ mask: "*",
60
+ })) as string | symbol;
61
+ if (isCancel(apiKey) || !apiKey) {
62
+ cancel("API key is required.");
63
+ return;
64
+ }
75
65
 
76
66
  // ── 3. Sui wallet ─────────────────────────────────────────────────────
77
67
  const existingSecret = process.env.SOFER_SUI_SECRET_KEY;
68
+ const sGen = spinner();
78
69
  let secret: string;
79
70
  let address: string;
80
71
  if (existingSecret) {
72
+ sGen.start("Loading wallet from SOFER_SUI_SECRET_KEY");
81
73
  const kp = keypairFromSecret(existingSecret);
82
74
  secret = existingSecret;
83
75
  address = kp.toSuiAddress();
84
- console.log(` wallet: ${address} (from env)\n`);
76
+ sGen.stop(`wallet: ${address} (from env)`);
85
77
  } else {
78
+ sGen.start("Generating new Sui wallet");
86
79
  const kp = generateKeypair();
87
80
  secret = kp.getSecretKey();
88
81
  address = kp.toSuiAddress();
89
- console.log(` wallet: ${address} (new)\n`);
82
+ sGen.stop(`wallet: ${address} (new)`);
90
83
  }
91
84
 
92
85
  const config = loadConfig();
@@ -96,119 +89,123 @@ export async function initCommand(): Promise<void> {
96
89
  if (net !== "mainnet") {
97
90
  const bal = await suiBalance(sui, address);
98
91
  if (bal === 0n) {
99
- console.log("Requesting testnet faucet…");
92
+ const sFund = spinner();
93
+ sFund.start("Requesting testnet faucet");
100
94
  try {
101
95
  await requestFaucet(config.network, address);
102
96
  await waitForBalance(sui, address, 1n);
103
- console.log("funded\n");
97
+ sFund.stop("funded");
104
98
  } catch (e) {
105
- console.warn(` faucet failed: ${String(e).slice(0, 80)}`);
106
- console.warn(` fund ${address} at https://faucet.sui.io/ and re-run\n`);
99
+ sFund.stop(`faucet failed: ${String(e).slice(0, 80)}`);
100
+ note(`Fund ${address} at https://faucet.sui.io/ and re-run.`, "manual funding needed");
107
101
  }
108
102
  } else {
109
- console.log(` balance: ${(Number(bal) / 1e9).toFixed(4)} SUI\n`);
103
+ note(`${(Number(bal) / 1e9).toFixed(4)} SUI on ${address}`, "wallet balance");
110
104
  }
111
105
  }
112
106
 
113
107
  const agents = new AgentClient(sui, config.packageId);
114
108
 
115
109
  // ── 5. Mint agent ─────────────────────────────────────────────────────
116
- let agentId = config.agentObjectId;
110
+ let agentId = existing.agentObjectId;
117
111
  if (!agentId) {
118
- console.log("Minting agent identity object on-chain…");
112
+ const sMint = spinner();
113
+ sMint.start("Minting agent identity object on-chain");
119
114
  try {
120
115
  const keypair = keypairFromSecret(secret);
121
- const minted = await spin(" minting", () =>
122
- agents.mint(
123
- keypair,
124
- "Sofer",
125
- "a sovereign AI agent on Sui with encrypted memory on Walrus",
126
- new TextEncoder().encode("sofer"),
127
- ),
116
+ const minted = await agents.mint(
117
+ keypair,
118
+ "Sofer",
119
+ "a sovereign AI agent on Sui with encrypted memory on Walrus",
120
+ new TextEncoder().encode("sofer"),
128
121
  );
129
122
  agentId = minted.agentId;
130
- console.log(`agent: ${agentId}\n`);
123
+ sMint.stop(`agent: ${agentId}`);
131
124
  } catch (e) {
132
- console.error(` mint failed: ${String(e).slice(0, 120)}`);
133
- console.error(" (maybe already minted? set SOFER_AGENT_OBJECT_ID and re-run)");
125
+ sMint.stop(`mint failed: ${String(e).slice(0, 120)}`);
126
+ cancel("Mint failed. Is the wallet funded?");
134
127
  return;
135
128
  }
136
129
  } else {
137
- console.log(` reusing agent: ${agentId}\n`);
130
+ note(agentId, "reusing agent");
138
131
  }
139
132
 
140
133
  // ── 6. Provision MemWal memory ────────────────────────────────────────
141
- let accountId = config.memwal.accountId;
142
- let delegateKey = config.memwal.delegateKey;
134
+ let accountId = existing.memwal.accountId;
135
+ let delegateKey = existing.memwal.delegateKey;
143
136
  if (!accountId || !delegateKey) {
144
- console.log("Provisioning Walrus memory (MemWal)…");
137
+ const sMem = spinner();
138
+ sMem.start("Provisioning Walrus memory (MemWal)");
145
139
  try {
146
- const prov = await spin(" provisioning", () =>
147
- provisionMemwal({
148
- ownerSecret: secret,
149
- packageId: config.memwal.packageId,
150
- registryId: config.memwal.registryId,
151
- network: net,
152
- label: "sofer-cli",
153
- }),
154
- );
140
+ const prov = await provisionMemwal({
141
+ ownerSecret: secret,
142
+ packageId: config.memwal.packageId,
143
+ registryId: config.memwal.registryId,
144
+ network: net,
145
+ label: "sofer-cli",
146
+ });
155
147
  accountId = prov.accountId;
156
148
  delegateKey = prov.delegateKey;
157
- console.log(`account: ${accountId}\n`);
149
+ sMem.stop(`account: ${accountId}`);
158
150
  } catch (e) {
151
+ sMem.stop(`provision failed: ${String(e).slice(0, 120)}`);
159
152
  const msg = String(e);
160
153
  if (msg.includes("abort code: 3") || msg.includes("create_account")) {
161
- throw new Error(
162
- "This wallet already owns a MemWal account (one per address).\n" +
163
- "Delete ~/.sofer/ and re-run `sofer init`, or set the env vars manually.",
154
+ cancel(
155
+ "This wallet already owns a MemWal account.\nDelete ~/.sofer/ and re-run, or set env vars manually.",
164
156
  );
165
157
  }
166
- throw e;
158
+ return;
167
159
  }
168
160
  } else {
169
- console.log(` reusing memory account: ${accountId}\n`);
161
+ note(accountId, "reusing memory account");
170
162
  }
171
163
 
172
164
  // ── 7. Link memory to agent on-chain ───────────────────────────────────
173
- console.log("Linking memory account to agent on-chain…");
165
+ if (!agentId || !accountId) {
166
+ cancel("Agent or memory not provisioned. Re-run `sofer init`.");
167
+ return;
168
+ }
169
+ const sLink = spinner();
170
+ sLink.start("Linking memory account to agent on-chain");
174
171
  try {
175
172
  const keypair = keypairFromSecret(secret);
176
- await spin(" linking", () => agents.setMemoryAccount(keypair, agentId!, accountId!));
177
- console.log("linked\n");
173
+ await agents.setMemoryAccount(keypair, agentId, accountId);
174
+ sLink.stop("linked");
178
175
  } catch (e) {
179
- console.warn(` link failed (may already be linked): ${String(e).slice(0, 80)}\n`);
176
+ sLink.stop(`link failed (may already be linked): ${String(e).slice(0, 80)}`);
180
177
  }
181
178
 
182
179
  // ── 8. Persist config + secrets to ~/.sofer/ ───────────────────────────
183
- console.log("Saving config to ~/.sofer/…");
184
-
180
+ if (!delegateKey) {
181
+ cancel("Memory delegate key missing. Re-run `sofer init`.");
182
+ return;
183
+ }
184
+ const sSave = spinner();
185
+ sSave.start("Saving to ~/.sofer/");
185
186
  writeSecret(agentPaths.suiKey, secret);
186
187
  writeSecret(agentPaths.brainKey, apiKey);
187
-
188
188
  const persisted: PersistedConfig = {
189
189
  network: net,
190
- agentObjectId: agentId!,
190
+ agentObjectId: agentId,
191
191
  agentAddress: address,
192
192
  brain: { model: config.brain.model },
193
193
  memwal: {
194
- accountId: accountId!,
195
- delegateKey: delegateKey!,
194
+ accountId,
195
+ delegateKey,
196
196
  },
197
197
  };
198
198
  writePersistedConfig(persisted);
199
-
200
- const agentDir = agentPaths.agent(shortAgentId(address));
201
- console.log(` config: ${agentPaths.config}`);
202
- console.log(` keys: ${agentPaths.suiKey} / ${agentPaths.brainKey}`);
203
- console.log(` agent: ${agentDir.dir}\n`);
199
+ sSave.stop(`config: ${agentPaths.config}`);
204
200
 
205
201
  // ── 9. Summary ────────────────────────────────────────────────────────
206
- console.log("┌─────────────────────────────────────────┐");
207
- console.log(`│ agent id ${agentId!.slice(0, 30)}… │`);
208
- console.log(`│ address ${address.slice(0, 10)}… │`);
209
- console.log(`│ network ${net.padEnd(23)} │`);
210
- console.log(`│ memory ${accountId!.slice(0, 10)}… │`);
211
- console.log("├─────────────────────────────────────────┤");
212
- console.log("Run `sofer` to chat with your agent.");
213
- console.log("└─────────────────────────────────────────┘");
202
+ const lines = [
203
+ `agent ${agentId.slice(0, 30)}…`,
204
+ `address ${address.slice(0, 10)}…`,
205
+ `network ${net}`,
206
+ `memory ${accountId.slice(0, 10)}…`,
207
+ "",
208
+ "Run `sofer` to chat with your agent.",
209
+ ];
210
+ outro(lines.join("\n"));
214
211
  }
package/src/secrets.ts CHANGED
@@ -5,10 +5,14 @@ import { loadSecrets } from "./env.js";
5
5
  export function requireSecrets(): AgentSecrets {
6
6
  const { suiSecretKey, anthropicApiKey } = loadSecrets();
7
7
  if (!suiSecretKey) {
8
- throw new Error("SOFER_SUI_SECRET_KEY not found — run `sofer init` first (creates ~/.sofer/sui.key)");
8
+ throw new Error(
9
+ "SOFER_SUI_SECRET_KEY not found — run `sofer init` first (creates ~/.sofer/sui.key)",
10
+ );
9
11
  }
10
12
  if (!anthropicApiKey) {
11
- throw new Error("ANTHROPIC_API_KEY not found — run `sofer init` first (creates ~/.sofer/brain.key)");
13
+ throw new Error(
14
+ "ANTHROPIC_API_KEY not found — run `sofer init` first (creates ~/.sofer/brain.key)",
15
+ );
12
16
  }
13
17
  return { suiSecretKey, anthropicApiKey };
14
18
  }