@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/dist/bin.js +3 -1
- package/dist/bin.js.map +1 -1
- package/dist/chat.d.ts +1 -1
- package/dist/chat.d.ts.map +1 -1
- package/dist/chat.js +10 -6
- package/dist/chat.js.map +1 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +81 -72
- package/dist/init.js.map +1 -1
- package/dist/secrets.d.ts.map +1 -1
- package/dist/secrets.js.map +1 -1
- package/dist/tui.d.ts +15 -22
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +178 -116
- package/dist/tui.js.map +1 -1
- package/package.json +3 -2
- package/src/bin.ts +8 -2
- package/src/chat.ts +9 -6
- package/src/init.ts +93 -96
- package/src/secrets.ts +6 -2
- package/src/tui.ts +187 -129
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(`
|
|
58
|
-
console.log(`\nRun \`sofer\` to chat. To start fresh, delete
|
|
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
|
-
|
|
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
|
|
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
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
97
|
+
sFund.stop("funded");
|
|
104
98
|
} catch (e) {
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
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 =
|
|
110
|
+
let agentId = existing.agentObjectId;
|
|
117
111
|
if (!agentId) {
|
|
118
|
-
|
|
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
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
123
|
+
sMint.stop(`agent: ${agentId}`);
|
|
131
124
|
} catch (e) {
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
130
|
+
note(agentId, "reusing agent");
|
|
138
131
|
}
|
|
139
132
|
|
|
140
133
|
// ── 6. Provision MemWal memory ────────────────────────────────────────
|
|
141
|
-
let accountId =
|
|
142
|
-
let delegateKey =
|
|
134
|
+
let accountId = existing.memwal.accountId;
|
|
135
|
+
let delegateKey = existing.memwal.delegateKey;
|
|
143
136
|
if (!accountId || !delegateKey) {
|
|
144
|
-
|
|
137
|
+
const sMem = spinner();
|
|
138
|
+
sMem.start("Provisioning Walrus memory (MemWal)");
|
|
145
139
|
try {
|
|
146
|
-
const prov = await
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
|
|
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
|
-
|
|
162
|
-
"This wallet already owns a MemWal account
|
|
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
|
-
|
|
158
|
+
return;
|
|
167
159
|
}
|
|
168
160
|
} else {
|
|
169
|
-
|
|
161
|
+
note(accountId, "reusing memory account");
|
|
170
162
|
}
|
|
171
163
|
|
|
172
164
|
// ── 7. Link memory to agent on-chain ───────────────────────────────────
|
|
173
|
-
|
|
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
|
|
177
|
-
|
|
173
|
+
await agents.setMemoryAccount(keypair, agentId, accountId);
|
|
174
|
+
sLink.stop("linked");
|
|
178
175
|
} catch (e) {
|
|
179
|
-
|
|
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
|
-
|
|
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
|
|
195
|
-
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
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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(
|
|
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(
|
|
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
|
}
|