@agentkv/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +111 -0
- package/dist/chunk-34AG6FLC.js +720 -0
- package/dist/cli.js +679 -0
- package/dist/mcp-MINXA6WX.js +416 -0
- package/package.json +59 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,679 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
VERSION,
|
|
4
|
+
accountPath,
|
|
5
|
+
agentkvDir,
|
|
6
|
+
clientFromConfig,
|
|
7
|
+
createStoredAccount,
|
|
8
|
+
getOnrampProvider,
|
|
9
|
+
getOrCreateStoredWallet,
|
|
10
|
+
peekStoredAccount,
|
|
11
|
+
peekStoredWallet,
|
|
12
|
+
readConfigFile,
|
|
13
|
+
readEnvSecret,
|
|
14
|
+
readFileSecret,
|
|
15
|
+
resolveConfig,
|
|
16
|
+
writeSecretFile
|
|
17
|
+
} from "./chunk-34AG6FLC.js";
|
|
18
|
+
|
|
19
|
+
// src/cli.ts
|
|
20
|
+
import { mkdirSync, realpathSync, writeFileSync } from "fs";
|
|
21
|
+
import { join } from "path";
|
|
22
|
+
import { fileURLToPath } from "url";
|
|
23
|
+
import { AgentKVError, SpendCapError } from "@agentkv/client";
|
|
24
|
+
|
|
25
|
+
// src/args.ts
|
|
26
|
+
var UsageError = class extends Error {
|
|
27
|
+
constructor(message) {
|
|
28
|
+
super(message);
|
|
29
|
+
this.name = "UsageError";
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
var KNOWN_FLAGS = /* @__PURE__ */ new Set([
|
|
33
|
+
"endpoint",
|
|
34
|
+
"network",
|
|
35
|
+
"max-spend-usd",
|
|
36
|
+
"ttl-days",
|
|
37
|
+
"strict-ttl",
|
|
38
|
+
"pretty",
|
|
39
|
+
"json",
|
|
40
|
+
"reveal",
|
|
41
|
+
"out",
|
|
42
|
+
"file",
|
|
43
|
+
"from-key",
|
|
44
|
+
"from-env",
|
|
45
|
+
"cursor",
|
|
46
|
+
"limit",
|
|
47
|
+
"idempotency-key",
|
|
48
|
+
"onramp-app-id",
|
|
49
|
+
"onramp-provider"
|
|
50
|
+
]);
|
|
51
|
+
function parseFlags(args) {
|
|
52
|
+
const flags = {};
|
|
53
|
+
const positionals = [];
|
|
54
|
+
for (let i = 0; i < args.length; i++) {
|
|
55
|
+
const a = args[i];
|
|
56
|
+
if (a.startsWith("--")) {
|
|
57
|
+
const key = a.slice(2);
|
|
58
|
+
if (!KNOWN_FLAGS.has(key)) {
|
|
59
|
+
throw new UsageError(`unknown flag --${key}`);
|
|
60
|
+
}
|
|
61
|
+
const boolish = ["strict-ttl", "pretty", "json", "reveal"].includes(key);
|
|
62
|
+
const val = boolish ? true : args[++i];
|
|
63
|
+
if (!boolish && (val === void 0 || val === "" || val.startsWith("--"))) {
|
|
64
|
+
throw new UsageError(`flag --${key} requires a value`);
|
|
65
|
+
}
|
|
66
|
+
if (key.endsWith("usd") || key === "ttl-days") {
|
|
67
|
+
const n = Number(val);
|
|
68
|
+
if (!Number.isFinite(n) || n < 0) {
|
|
69
|
+
throw new UsageError(
|
|
70
|
+
`flag --${key} must be a non-negative number (got ${JSON.stringify(val)})`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
flags[camel(key)] = n;
|
|
74
|
+
} else {
|
|
75
|
+
flags[camel(key)] = val;
|
|
76
|
+
}
|
|
77
|
+
} else positionals.push(a);
|
|
78
|
+
}
|
|
79
|
+
return { flags, positionals };
|
|
80
|
+
}
|
|
81
|
+
var camel = (s) => s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
82
|
+
|
|
83
|
+
// src/commands/account.ts
|
|
84
|
+
import { AgentKV, isAccountKeyFormat } from "@agentkv/client";
|
|
85
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
86
|
+
|
|
87
|
+
// src/output.ts
|
|
88
|
+
var EXIT = { OK: 0, GENERIC: 1, USAGE: 2, PAYMENT: 3, NOT_FOUND: 4 };
|
|
89
|
+
function printJson(w, value) {
|
|
90
|
+
w(`${JSON.stringify(value, null, 2)}
|
|
91
|
+
`);
|
|
92
|
+
}
|
|
93
|
+
function printError(w, code, message, hint) {
|
|
94
|
+
w(`${JSON.stringify({ error: message, code, ...hint ? { hint } : {} })}
|
|
95
|
+
`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// src/commands/account.ts
|
|
99
|
+
var PAYER_KEY_RE = /^0x[0-9a-fA-F]{64}$/;
|
|
100
|
+
function resolvePayerKey(flags, env) {
|
|
101
|
+
const explicit = (typeof flags.fromKey === "string" ? flags.fromKey.trim() : "") || env.AGENTKV_PAYER_KEY?.trim() || env.AGENTKV_PRIVATE_KEY?.trim() || "";
|
|
102
|
+
if (explicit) {
|
|
103
|
+
if (!PAYER_KEY_RE.test(explicit)) {
|
|
104
|
+
throw new Error(
|
|
105
|
+
"payer key must be a 0x-prefixed 32-byte hex private key (from --from-key / AGENTKV_PAYER_KEY / AGENTKV_PRIVATE_KEY)"
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
return explicit;
|
|
109
|
+
}
|
|
110
|
+
if (peekStoredWallet(env)) return getOrCreateStoredWallet(env).privateKey;
|
|
111
|
+
return void 0;
|
|
112
|
+
}
|
|
113
|
+
async function runAccount(args, io) {
|
|
114
|
+
const sub = args[0];
|
|
115
|
+
const env = io.env ?? process.env;
|
|
116
|
+
const { flags, positionals } = parseFlags(args);
|
|
117
|
+
const cfg = resolveConfig(flags, env, () => readConfigFile(env));
|
|
118
|
+
if (sub === "new") {
|
|
119
|
+
let acct;
|
|
120
|
+
try {
|
|
121
|
+
acct = createStoredAccount(env);
|
|
122
|
+
} catch (e) {
|
|
123
|
+
if (e?.code === "EEXIST") {
|
|
124
|
+
printError(
|
|
125
|
+
io.stderr,
|
|
126
|
+
"account_exists",
|
|
127
|
+
// accountPath (not peekStoredAccount) so a present-but-CORRUPT file — which now
|
|
128
|
+
// throws from peek — still yields a clean "already exists" message, not a crash.
|
|
129
|
+
`an account already exists at ${accountPath(env)}; delete it first to replace it`
|
|
130
|
+
);
|
|
131
|
+
return EXIT.GENERIC;
|
|
132
|
+
}
|
|
133
|
+
throw e;
|
|
134
|
+
}
|
|
135
|
+
printJson(io.stdout, {
|
|
136
|
+
accountKey: acct.accountKey,
|
|
137
|
+
encryptionKey: acct.encryptionKey,
|
|
138
|
+
path: acct.path,
|
|
139
|
+
note: `Back up these \u2014 they are UNRECOVERABLE (the server stores only a hash of the account key). Fund this account by depositing >=$1 to ${cfg.endpoint}/account/deposit from a wallet that can sign (e.g. awal). To buy USDC with a card for such a signing wallet, run \`agentkv fund\`.`
|
|
140
|
+
});
|
|
141
|
+
return EXIT.OK;
|
|
142
|
+
}
|
|
143
|
+
if (sub === "show") {
|
|
144
|
+
const reveal = args.includes("--reveal");
|
|
145
|
+
const envAccountKey = cfg.accountKey;
|
|
146
|
+
if (envAccountKey !== void 0 && !isAccountKeyFormat(envAccountKey)) {
|
|
147
|
+
printError(
|
|
148
|
+
io.stderr,
|
|
149
|
+
"invalid_config",
|
|
150
|
+
"AGENTKV_ACCOUNT_KEY must be of the form ak_<64 lowercase hex> (run 'agentkv account new' to mint one)"
|
|
151
|
+
);
|
|
152
|
+
return EXIT.GENERIC;
|
|
153
|
+
}
|
|
154
|
+
const envAccount = envAccountKey ? { accountKey: envAccountKey, encryptionKey: cfg.encryptionKey } : null;
|
|
155
|
+
const stored = envAccount ? null : peekStoredAccount(env);
|
|
156
|
+
if (!envAccount && !stored) {
|
|
157
|
+
printJson(io.stdout, {
|
|
158
|
+
configured: false,
|
|
159
|
+
note: "No account yet \u2014 run 'agentkv account new' to mint one (account-key mode is opt-in)."
|
|
160
|
+
});
|
|
161
|
+
return EXIT.OK;
|
|
162
|
+
}
|
|
163
|
+
if (reveal) {
|
|
164
|
+
if (envAccount) {
|
|
165
|
+
printJson(io.stdout, {
|
|
166
|
+
source: "AGENTKV_ACCOUNT_KEY env",
|
|
167
|
+
accountKey: envAccount.accountKey,
|
|
168
|
+
encryptionKey: envAccount.encryptionKey ?? null,
|
|
169
|
+
note: "These come from the environment (AGENTKV_ACCOUNT_KEY / AGENTKV_ENCRYPTION_KEY)."
|
|
170
|
+
});
|
|
171
|
+
return EXIT.OK;
|
|
172
|
+
}
|
|
173
|
+
printJson(io.stdout, {
|
|
174
|
+
source: "local account file",
|
|
175
|
+
path: stored.path,
|
|
176
|
+
accountKey: stored.accountKey,
|
|
177
|
+
encryptionKey: stored.encryptionKey,
|
|
178
|
+
note: "These are unrecoverable secrets \u2014 store them somewhere safe."
|
|
179
|
+
});
|
|
180
|
+
return EXIT.OK;
|
|
181
|
+
}
|
|
182
|
+
const acctKey = envAccount?.accountKey ?? stored?.accountKey;
|
|
183
|
+
const encKey = envAccount?.encryptionKey ?? stored?.encryptionKey;
|
|
184
|
+
let balance;
|
|
185
|
+
let balanceError;
|
|
186
|
+
try {
|
|
187
|
+
const balanceClient = io.client ?? (acctKey && encKey ? new AgentKV({
|
|
188
|
+
accountKey: acctKey,
|
|
189
|
+
encryptionKey: encKey,
|
|
190
|
+
endpoint: cfg.endpoint,
|
|
191
|
+
network: cfg.network
|
|
192
|
+
}) : void 0);
|
|
193
|
+
if (balanceClient) {
|
|
194
|
+
balance = await balanceClient.balance();
|
|
195
|
+
}
|
|
196
|
+
} catch (e) {
|
|
197
|
+
balanceError = e instanceof Error ? e.message : String(e);
|
|
198
|
+
}
|
|
199
|
+
if (envAccount) {
|
|
200
|
+
printJson(io.stdout, {
|
|
201
|
+
configured: true,
|
|
202
|
+
source: "AGENTKV_ACCOUNT_KEY env",
|
|
203
|
+
note: "Account configured from the environment (AGENTKV_ACCOUNT_KEY). Its account key + encryption key are unrecoverable \u2014 back them up. Use --reveal to print them.",
|
|
204
|
+
...balance !== void 0 ? { balance } : {},
|
|
205
|
+
...balanceError ? { balanceError } : {}
|
|
206
|
+
});
|
|
207
|
+
return EXIT.OK;
|
|
208
|
+
}
|
|
209
|
+
printJson(io.stdout, {
|
|
210
|
+
source: "local account file",
|
|
211
|
+
path: stored.path,
|
|
212
|
+
note: "Back up this file \u2014 its account key + encryption key are unrecoverable. Use --reveal to print them.",
|
|
213
|
+
...balance !== void 0 ? { balance } : {},
|
|
214
|
+
...balanceError ? { balanceError } : {}
|
|
215
|
+
});
|
|
216
|
+
return EXIT.OK;
|
|
217
|
+
}
|
|
218
|
+
if (sub === "fund") {
|
|
219
|
+
const usd = Number(positionals[1]);
|
|
220
|
+
if (!Number.isInteger(usd) || usd < 1) {
|
|
221
|
+
printError(
|
|
222
|
+
io.stderr,
|
|
223
|
+
"usage",
|
|
224
|
+
"account fund requires <usd> as a whole number of US dollars >= 1"
|
|
225
|
+
);
|
|
226
|
+
return EXIT.USAGE;
|
|
227
|
+
}
|
|
228
|
+
const envAccountKey = cfg.accountKey;
|
|
229
|
+
if (envAccountKey !== void 0 && !isAccountKeyFormat(envAccountKey)) {
|
|
230
|
+
printError(
|
|
231
|
+
io.stderr,
|
|
232
|
+
"invalid_config",
|
|
233
|
+
"AGENTKV_ACCOUNT_KEY must be of the form ak_<64 lowercase hex> (run 'agentkv account new' to mint one)"
|
|
234
|
+
);
|
|
235
|
+
return EXIT.GENERIC;
|
|
236
|
+
}
|
|
237
|
+
const envAccount = envAccountKey ? { accountKey: envAccountKey, encryptionKey: cfg.encryptionKey } : null;
|
|
238
|
+
const stored = envAccount ? null : peekStoredAccount(env);
|
|
239
|
+
const acctKey = envAccount?.accountKey ?? stored?.accountKey;
|
|
240
|
+
const encKey = envAccount?.encryptionKey ?? stored?.encryptionKey;
|
|
241
|
+
if (!acctKey) {
|
|
242
|
+
printError(
|
|
243
|
+
io.stderr,
|
|
244
|
+
"no_account",
|
|
245
|
+
"no account configured; run 'agentkv account new' or set AGENTKV_ACCOUNT_KEY (+ AGENTKV_ENCRYPTION_KEY)"
|
|
246
|
+
);
|
|
247
|
+
return EXIT.GENERIC;
|
|
248
|
+
}
|
|
249
|
+
let payerKey;
|
|
250
|
+
try {
|
|
251
|
+
payerKey = resolvePayerKey(flags, env);
|
|
252
|
+
} catch (e) {
|
|
253
|
+
printError(io.stderr, "invalid_payer", e instanceof Error ? e.message : String(e));
|
|
254
|
+
return EXIT.USAGE;
|
|
255
|
+
}
|
|
256
|
+
if (!payerKey) {
|
|
257
|
+
printError(
|
|
258
|
+
io.stderr,
|
|
259
|
+
"no_payer",
|
|
260
|
+
"no payer wallet; pass --from-key <0xhex> or set AGENTKV_PAYER_KEY (or AGENTKV_PRIVATE_KEY)"
|
|
261
|
+
);
|
|
262
|
+
return EXIT.GENERIC;
|
|
263
|
+
}
|
|
264
|
+
const payer = privateKeyToAccount(payerKey);
|
|
265
|
+
let client = io.client;
|
|
266
|
+
if (!io.client) {
|
|
267
|
+
if (!encKey) {
|
|
268
|
+
printError(
|
|
269
|
+
io.stderr,
|
|
270
|
+
"invalid_config",
|
|
271
|
+
envAccount ? "account-key mode (AGENTKV_ACCOUNT_KEY) requires AGENTKV_ENCRYPTION_KEY (the local 32-byte AES key)" : "account.json is missing or has a malformed encryptionKey (the local 32-byte AES key)"
|
|
272
|
+
);
|
|
273
|
+
return EXIT.GENERIC;
|
|
274
|
+
}
|
|
275
|
+
client = new AgentKV({
|
|
276
|
+
accountKey: acctKey,
|
|
277
|
+
encryptionKey: encKey,
|
|
278
|
+
endpoint: cfg.endpoint,
|
|
279
|
+
network: cfg.network
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
const result = await client.fundAccount(payer, usd);
|
|
283
|
+
printJson(io.stdout, result);
|
|
284
|
+
return EXIT.OK;
|
|
285
|
+
}
|
|
286
|
+
printError(
|
|
287
|
+
io.stderr,
|
|
288
|
+
"usage",
|
|
289
|
+
"account new | account show [--reveal] | account fund <usd> [--from-key <0xhex>]"
|
|
290
|
+
);
|
|
291
|
+
return EXIT.USAGE;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// src/commands/credits.ts
|
|
295
|
+
async function runCredits(cmd, args, io) {
|
|
296
|
+
if (cmd === "balance") {
|
|
297
|
+
printJson(io.stdout, { balance: await io.client.balance() });
|
|
298
|
+
return EXIT.OK;
|
|
299
|
+
}
|
|
300
|
+
const { positionals } = parseFlags(args);
|
|
301
|
+
const usd = Number(positionals[0]);
|
|
302
|
+
if (!Number.isFinite(usd) || usd < 1 || usd * 1e6 !== Math.round(usd * 1e6)) {
|
|
303
|
+
printError(
|
|
304
|
+
io.stderr,
|
|
305
|
+
"usage",
|
|
306
|
+
"deposit requires <usd> >= 1 (a whole number of atomic USDC units)"
|
|
307
|
+
);
|
|
308
|
+
return EXIT.USAGE;
|
|
309
|
+
}
|
|
310
|
+
printJson(io.stdout, await io.client.deposit(usd));
|
|
311
|
+
return EXIT.OK;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// src/commands/fund.ts
|
|
315
|
+
import { privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
|
|
316
|
+
var KEY_RE = /^0x[0-9a-fA-F]{64}$/;
|
|
317
|
+
function runFund(args, io) {
|
|
318
|
+
const env = io.env ?? process.env;
|
|
319
|
+
const { flags, positionals } = parseFlags(args);
|
|
320
|
+
let amountUsd;
|
|
321
|
+
if (positionals.length > 0) {
|
|
322
|
+
const n = Number(positionals[0]);
|
|
323
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
324
|
+
printError(
|
|
325
|
+
io.stderr,
|
|
326
|
+
"usage",
|
|
327
|
+
`amount must be a positive number (got ${JSON.stringify(positionals[0])})`
|
|
328
|
+
);
|
|
329
|
+
return EXIT.USAGE;
|
|
330
|
+
}
|
|
331
|
+
amountUsd = n;
|
|
332
|
+
}
|
|
333
|
+
const cfg = resolveConfig(flags, env, () => readConfigFile(env));
|
|
334
|
+
const providerId = cfg.onrampProvider ?? "coinbase";
|
|
335
|
+
const onrampConfig = cfg.onrampConfig ?? {};
|
|
336
|
+
let address;
|
|
337
|
+
const envKey = env.AGENTKV_PRIVATE_KEY?.trim();
|
|
338
|
+
if (envKey) {
|
|
339
|
+
if (!KEY_RE.test(envKey)) {
|
|
340
|
+
printError(
|
|
341
|
+
io.stderr,
|
|
342
|
+
"invalid_config",
|
|
343
|
+
"AGENTKV_PRIVATE_KEY is set but malformed (expected 0x followed by 64 hex chars)"
|
|
344
|
+
);
|
|
345
|
+
return EXIT.GENERIC;
|
|
346
|
+
}
|
|
347
|
+
address = privateKeyToAccount2(envKey).address;
|
|
348
|
+
} else {
|
|
349
|
+
address = peekStoredWallet(env)?.address;
|
|
350
|
+
}
|
|
351
|
+
if (!address) {
|
|
352
|
+
const account = peekStoredAccount(env);
|
|
353
|
+
const inAccountMode = !!(env.AGENTKV_ACCOUNT_KEY?.trim() || account);
|
|
354
|
+
if (inAccountMode) {
|
|
355
|
+
printJson(io.stdout, {
|
|
356
|
+
provider: providerId,
|
|
357
|
+
url: null,
|
|
358
|
+
address: null,
|
|
359
|
+
note: `Account-key mode has no single wallet to onramp into. Account credits are funded by depositing USDC to ${cfg.endpoint}/account/deposit from a wallet that can sign (e.g. awal). An onramp can fund such a signing wallet: fund a wallet first (e.g. send USDC to it, or set AGENTKV_PRIVATE_KEY and re-run \`agentkv fund\`), then deposit to the account.`
|
|
360
|
+
});
|
|
361
|
+
return EXIT.OK;
|
|
362
|
+
}
|
|
363
|
+
printJson(io.stdout, {
|
|
364
|
+
provider: providerId,
|
|
365
|
+
url: null,
|
|
366
|
+
address: null,
|
|
367
|
+
note: "No wallet yet \u2014 one is auto-provisioned on the first paid op (e.g. `agentkv balance`), or set AGENTKV_PRIVATE_KEY to use your own. Provision/configure a wallet, then re-run `agentkv fund` for a funding URL."
|
|
368
|
+
});
|
|
369
|
+
return EXIT.OK;
|
|
370
|
+
}
|
|
371
|
+
let provider;
|
|
372
|
+
try {
|
|
373
|
+
provider = getOnrampProvider(providerId);
|
|
374
|
+
} catch (e) {
|
|
375
|
+
printError(io.stderr, "unknown_provider", e instanceof Error ? e.message : String(e));
|
|
376
|
+
return EXIT.USAGE;
|
|
377
|
+
}
|
|
378
|
+
let url;
|
|
379
|
+
try {
|
|
380
|
+
url = provider.buildUrl({ address, network: cfg.network, amountUsd, config: onrampConfig });
|
|
381
|
+
} catch (e) {
|
|
382
|
+
printError(io.stderr, "onramp_config", e instanceof Error ? e.message : String(e));
|
|
383
|
+
return EXIT.GENERIC;
|
|
384
|
+
}
|
|
385
|
+
printJson(io.stdout, {
|
|
386
|
+
provider: provider.id,
|
|
387
|
+
url,
|
|
388
|
+
address,
|
|
389
|
+
...amountUsd !== void 0 ? { amountUsd } : {},
|
|
390
|
+
note: `Open this URL to buy USDC with a card and deliver it to ${address} on Base via ${provider.name}.`
|
|
391
|
+
});
|
|
392
|
+
return EXIT.OK;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// src/commands/kv.ts
|
|
396
|
+
import { readFileSync } from "fs";
|
|
397
|
+
async function runKv(cmd, args, io) {
|
|
398
|
+
const { flags, positionals } = parseFlags(args);
|
|
399
|
+
const key = positionals[0];
|
|
400
|
+
if (!key) {
|
|
401
|
+
printError(io.stderr, "usage", `${cmd} requires <key>`);
|
|
402
|
+
return EXIT.USAGE;
|
|
403
|
+
}
|
|
404
|
+
if (cmd === "delete") {
|
|
405
|
+
const bad = ["out", "fromEnv", "file"].find((f) => f in flags);
|
|
406
|
+
if (bad) {
|
|
407
|
+
const flag = bad === "fromEnv" ? "from-env" : bad;
|
|
408
|
+
printError(io.stderr, "usage", `--${flag} is not valid for delete`);
|
|
409
|
+
return EXIT.USAGE;
|
|
410
|
+
}
|
|
411
|
+
printJson(io.stdout, await io.client.delete(key));
|
|
412
|
+
return EXIT.OK;
|
|
413
|
+
}
|
|
414
|
+
if (cmd === "get") {
|
|
415
|
+
const v = await io.client.get(
|
|
416
|
+
key,
|
|
417
|
+
flags.idempotencyKey ? { idempotencyKey: flags.idempotencyKey } : {}
|
|
418
|
+
);
|
|
419
|
+
if (v === null) {
|
|
420
|
+
printJson(io.stdout, flags.out ? { found: false } : null);
|
|
421
|
+
return EXIT.NOT_FOUND;
|
|
422
|
+
}
|
|
423
|
+
if (flags.out) {
|
|
424
|
+
const text = typeof v === "string" ? v : JSON.stringify(v);
|
|
425
|
+
let written;
|
|
426
|
+
try {
|
|
427
|
+
written = writeSecretFile(text, flags.out);
|
|
428
|
+
} catch {
|
|
429
|
+
printError(
|
|
430
|
+
io.stderr,
|
|
431
|
+
"write_failed",
|
|
432
|
+
"could not write --out file (choose a fresh path that does not already exist)"
|
|
433
|
+
);
|
|
434
|
+
return EXIT.USAGE;
|
|
435
|
+
}
|
|
436
|
+
printJson(io.stdout, { found: true, path: written.path, bytes: written.bytes });
|
|
437
|
+
return EXIT.OK;
|
|
438
|
+
}
|
|
439
|
+
printJson(io.stdout, v);
|
|
440
|
+
return EXIT.OK;
|
|
441
|
+
}
|
|
442
|
+
let value;
|
|
443
|
+
if (flags.fromEnv) {
|
|
444
|
+
const r = readEnvSecret(flags.fromEnv);
|
|
445
|
+
if (!r.ok) {
|
|
446
|
+
printError(io.stderr, r.code, r.error);
|
|
447
|
+
return EXIT.USAGE;
|
|
448
|
+
}
|
|
449
|
+
value = r.value;
|
|
450
|
+
} else {
|
|
451
|
+
let raw;
|
|
452
|
+
if (positionals[1] !== void 0) {
|
|
453
|
+
raw = positionals[1];
|
|
454
|
+
} else if (flags.file) {
|
|
455
|
+
const r = readFileSecret(flags.file, { trim: false });
|
|
456
|
+
if (!r.ok) {
|
|
457
|
+
printError(io.stderr, r.code, r.error);
|
|
458
|
+
return EXIT.USAGE;
|
|
459
|
+
}
|
|
460
|
+
raw = r.value;
|
|
461
|
+
} else {
|
|
462
|
+
raw = readFileSync(0, "utf8");
|
|
463
|
+
}
|
|
464
|
+
try {
|
|
465
|
+
value = JSON.parse(raw);
|
|
466
|
+
} catch {
|
|
467
|
+
printError(
|
|
468
|
+
io.stderr,
|
|
469
|
+
"invalid_value",
|
|
470
|
+
"value must be valid JSON",
|
|
471
|
+
`examples: '"a string"' 42 '{"k":"v"}'`
|
|
472
|
+
);
|
|
473
|
+
return EXIT.USAGE;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
const opts = {};
|
|
477
|
+
if (flags.ttlDays !== void 0) opts.ttlDays = flags.ttlDays;
|
|
478
|
+
if (flags.strictTtl) opts.strictTtl = true;
|
|
479
|
+
if (flags.idempotencyKey) opts.idempotencyKey = flags.idempotencyKey;
|
|
480
|
+
printJson(io.stdout, await io.client.set(key, value, opts));
|
|
481
|
+
return EXIT.OK;
|
|
482
|
+
}
|
|
483
|
+
async function runListKeys(args, io) {
|
|
484
|
+
const { flags } = parseFlags(args);
|
|
485
|
+
const limit = flags.limit !== void 0 ? Number(flags.limit) : void 0;
|
|
486
|
+
const onePage = flags.cursor !== void 0;
|
|
487
|
+
const keys = [];
|
|
488
|
+
let cursor = flags.cursor ?? null;
|
|
489
|
+
do {
|
|
490
|
+
const res = await io.client.listKeys(cursor ? { cursor, limit } : { limit });
|
|
491
|
+
keys.push(...res.keys);
|
|
492
|
+
cursor = res.cursor;
|
|
493
|
+
} while (cursor && !onePage);
|
|
494
|
+
printJson(io.stdout, { keys: keys.sort(), count: keys.length, cursor: onePage ? cursor : null });
|
|
495
|
+
return EXIT.OK;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// src/commands/wallet.ts
|
|
499
|
+
import { generatePrivateKey, privateKeyToAccount as privateKeyToAccount3 } from "viem/accounts";
|
|
500
|
+
var KEY_RE2 = /^0x[0-9a-fA-F]{64}$/;
|
|
501
|
+
function runWallet(args, io) {
|
|
502
|
+
const sub = args[0];
|
|
503
|
+
if (sub === "new") {
|
|
504
|
+
const privateKey = generatePrivateKey();
|
|
505
|
+
printJson(io.stdout, {
|
|
506
|
+
address: privateKeyToAccount3(privateKey).address,
|
|
507
|
+
privateKey,
|
|
508
|
+
note: "Optional: set AGENTKV_PRIVATE_KEY to this to manage your own key. Otherwise AgentKV mints + manages a local wallet on first use."
|
|
509
|
+
});
|
|
510
|
+
return EXIT.OK;
|
|
511
|
+
}
|
|
512
|
+
if (sub === "show") {
|
|
513
|
+
const env = io.env ?? process.env;
|
|
514
|
+
const envKey = env.AGENTKV_PRIVATE_KEY?.trim();
|
|
515
|
+
if (envKey) {
|
|
516
|
+
if (!KEY_RE2.test(envKey)) {
|
|
517
|
+
printError(
|
|
518
|
+
io.stderr,
|
|
519
|
+
"invalid_config",
|
|
520
|
+
"AGENTKV_PRIVATE_KEY is set but malformed (expected 0x followed by 64 hex chars)"
|
|
521
|
+
);
|
|
522
|
+
return EXIT.GENERIC;
|
|
523
|
+
}
|
|
524
|
+
printJson(io.stdout, {
|
|
525
|
+
address: privateKeyToAccount3(envKey).address,
|
|
526
|
+
source: "env (AGENTKV_PRIVATE_KEY)"
|
|
527
|
+
});
|
|
528
|
+
return EXIT.OK;
|
|
529
|
+
}
|
|
530
|
+
const stored = peekStoredWallet(env);
|
|
531
|
+
if (stored) {
|
|
532
|
+
printJson(io.stdout, {
|
|
533
|
+
address: stored.address,
|
|
534
|
+
source: "local keystore",
|
|
535
|
+
path: stored.path,
|
|
536
|
+
note: "Back up this file \u2014 it is your namespace and holds your funds."
|
|
537
|
+
});
|
|
538
|
+
return EXIT.OK;
|
|
539
|
+
}
|
|
540
|
+
printJson(io.stdout, {
|
|
541
|
+
address: null,
|
|
542
|
+
note: "No wallet yet \u2014 one is created automatically on first use (set / get / deposit / \u2026)."
|
|
543
|
+
});
|
|
544
|
+
return EXIT.OK;
|
|
545
|
+
}
|
|
546
|
+
printError(io.stderr, "usage", "wallet new | wallet show");
|
|
547
|
+
return EXIT.USAGE;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// src/cli.ts
|
|
551
|
+
var HELP = `agentkv \u2014 encrypted, x402-paid key-value store (v${VERSION})
|
|
552
|
+
|
|
553
|
+
Usage: agentkv <command> [options]
|
|
554
|
+
|
|
555
|
+
Commands:
|
|
556
|
+
set <key> <json> Encrypt and store a value
|
|
557
|
+
get <key> Fetch and decrypt a value
|
|
558
|
+
delete <key> Delete a value
|
|
559
|
+
list-keys List stored key names
|
|
560
|
+
balance Show prepaid credit balance
|
|
561
|
+
deposit <usd> Buy credits (>= $1) with USDC via x402
|
|
562
|
+
wallet new|show Manage the local signing wallet
|
|
563
|
+
account new|show|fund Manage an account-key (ak_) namespace
|
|
564
|
+
fund Print a card->USDC onramp URL
|
|
565
|
+
config Read/write local defaults
|
|
566
|
+
mcp Run the MCP server (stdio)
|
|
567
|
+
|
|
568
|
+
Global options:
|
|
569
|
+
--endpoint <url> API endpoint (default https://api.agentx402.ai)
|
|
570
|
+
--network <caip2> eip155:8453 (default) or eip155:84532
|
|
571
|
+
--max-spend-usd <n> Per-operation spend cap (USD)
|
|
572
|
+
--json | --pretty Output format
|
|
573
|
+
-h, --help Show this help
|
|
574
|
+
-V, --version Show the CLI version
|
|
575
|
+
|
|
576
|
+
Docs: https://github.com/agentx402-ai/agentkv
|
|
577
|
+
`;
|
|
578
|
+
async function runCli(argv, deps = {}) {
|
|
579
|
+
const stdout = deps.stdout ?? ((s) => process.stdout.write(s));
|
|
580
|
+
const stderr = deps.stderr ?? ((s) => process.stderr.write(s));
|
|
581
|
+
const [cmd, ...rest] = argv;
|
|
582
|
+
if (cmd === void 0 || cmd === "-h" || cmd === "--help" || cmd === "help") {
|
|
583
|
+
stdout(HELP);
|
|
584
|
+
return EXIT.OK;
|
|
585
|
+
}
|
|
586
|
+
if (cmd === "-V" || cmd === "--version" || cmd === "version") {
|
|
587
|
+
stdout(`${VERSION}
|
|
588
|
+
`);
|
|
589
|
+
return EXIT.OK;
|
|
590
|
+
}
|
|
591
|
+
try {
|
|
592
|
+
const env = deps.env ?? process.env;
|
|
593
|
+
if (cmd === "wallet") return runWallet(rest, { stdout, stderr, env });
|
|
594
|
+
if (cmd === "fund") return runFund(rest, { stdout, stderr, env });
|
|
595
|
+
if (cmd === "config") return runConfig(rest, { stdout, stderr, env });
|
|
596
|
+
if (cmd === "account") {
|
|
597
|
+
return await runAccount(rest, { client: deps.client, stdout, stderr, env });
|
|
598
|
+
}
|
|
599
|
+
if (cmd === "mcp") {
|
|
600
|
+
const { startMcp } = await import("./mcp-MINXA6WX.js");
|
|
601
|
+
await startMcp({ env: deps.env, client: deps.client });
|
|
602
|
+
return EXIT.OK;
|
|
603
|
+
}
|
|
604
|
+
const KV_COMMANDS = /* @__PURE__ */ new Set(["set", "get", "delete", "list-keys", "balance", "deposit"]);
|
|
605
|
+
if (!KV_COMMANDS.has(cmd)) {
|
|
606
|
+
printError(
|
|
607
|
+
stderr,
|
|
608
|
+
"usage",
|
|
609
|
+
`unknown command: ${cmd ?? "(none)"}`,
|
|
610
|
+
"commands: set get delete list-keys deposit balance wallet account fund config mcp (run `agentkv --help`)"
|
|
611
|
+
);
|
|
612
|
+
return EXIT.USAGE;
|
|
613
|
+
}
|
|
614
|
+
const client = deps.client ?? clientFromConfig(
|
|
615
|
+
resolveConfig(parseFlags(rest).flags, env, () => readConfigFile(env)),
|
|
616
|
+
{
|
|
617
|
+
env,
|
|
618
|
+
// Auto-provision notice -> stderr, so it never pollutes JSON stdout.
|
|
619
|
+
notify: (m) => stderr(`agentkv: ${m}
|
|
620
|
+
`)
|
|
621
|
+
}
|
|
622
|
+
);
|
|
623
|
+
if (cmd === "set" || cmd === "get" || cmd === "delete")
|
|
624
|
+
return await runKv(cmd, rest, { client, stdout, stderr });
|
|
625
|
+
if (cmd === "list-keys") return await runListKeys(rest, { client, stdout, stderr });
|
|
626
|
+
return await runCredits(cmd, rest, { client, stdout, stderr });
|
|
627
|
+
} catch (e) {
|
|
628
|
+
return mapError(e, stderr);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
function mapError(e, stderr) {
|
|
632
|
+
if (e instanceof SpendCapError) {
|
|
633
|
+
printError(stderr, e.code, e.message);
|
|
634
|
+
return EXIT.PAYMENT;
|
|
635
|
+
}
|
|
636
|
+
if (e instanceof AgentKVError) {
|
|
637
|
+
printError(stderr, e.code, e.message);
|
|
638
|
+
if (e.status === 404) return EXIT.NOT_FOUND;
|
|
639
|
+
if (e.status === 402) return EXIT.PAYMENT;
|
|
640
|
+
return EXIT.GENERIC;
|
|
641
|
+
}
|
|
642
|
+
if (e instanceof UsageError) {
|
|
643
|
+
printError(stderr, "usage", e.message);
|
|
644
|
+
return EXIT.USAGE;
|
|
645
|
+
}
|
|
646
|
+
printError(stderr, "error", e instanceof Error ? e.message : String(e));
|
|
647
|
+
return EXIT.GENERIC;
|
|
648
|
+
}
|
|
649
|
+
function runConfig(args, io) {
|
|
650
|
+
const env = io.env ?? process.env;
|
|
651
|
+
const { flags } = parseFlags(args);
|
|
652
|
+
const dir = agentkvDir(env);
|
|
653
|
+
mkdirSync(dir, { recursive: true });
|
|
654
|
+
const existing = readConfigFile(env) ?? {};
|
|
655
|
+
const merged = { ...existing };
|
|
656
|
+
if (flags.endpoint) merged.endpoint = flags.endpoint;
|
|
657
|
+
if (flags.network) merged.network = flags.network;
|
|
658
|
+
if (flags.maxSpendUsd !== void 0) merged.maxSpendUsd = flags.maxSpendUsd;
|
|
659
|
+
const path = join(dir, "config.json");
|
|
660
|
+
writeFileSync(path, JSON.stringify(merged, null, 2));
|
|
661
|
+
printJson(io.stdout, { ok: true, path, ...merged });
|
|
662
|
+
return EXIT.OK;
|
|
663
|
+
}
|
|
664
|
+
function isMainModule() {
|
|
665
|
+
const argv1 = process.argv[1];
|
|
666
|
+
if (!argv1) return false;
|
|
667
|
+
try {
|
|
668
|
+
return realpathSync(argv1) === fileURLToPath(import.meta.url);
|
|
669
|
+
} catch {
|
|
670
|
+
return false;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
if (isMainModule()) {
|
|
674
|
+
runCli(process.argv.slice(2)).then((code) => process.exit(code));
|
|
675
|
+
}
|
|
676
|
+
export {
|
|
677
|
+
parseFlags,
|
|
678
|
+
runCli
|
|
679
|
+
};
|