@lucid-agents/taskmarket 1.25.0 → 1.26.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 (28) hide show
  1. package/dist/THIRD_PARTY_NOTICES.md +224 -122
  2. package/dist/agent-compatibility.json +1 -1
  3. package/dist/api-H7VPA6C6.js +35 -0
  4. package/dist/{channels-CNVUFGAL.js → channels-4IFPTRDY.js} +5 -4
  5. package/dist/{chunk-GBQBHJ77.js → chunk-2AJZDGEL.js} +1 -45
  6. package/dist/{chunk-OZUGI2FO.js → chunk-CNWA5PU3.js} +1 -1
  7. package/dist/{chunk-SBCQA7NQ.js → chunk-FMK32RJF.js} +11 -127
  8. package/dist/{chunk-VSAHZTWA.js → chunk-GT5TOHOY.js} +5 -5
  9. package/dist/chunk-NTLOY7SP.js +46 -0
  10. package/dist/chunk-PJAWRHHZ.js +39 -0
  11. package/dist/chunk-Q2FZSDFG.js +933 -0
  12. package/dist/{chunk-KW2FNQFI.js → chunk-RPGVVVJD.js} +6 -6
  13. package/dist/chunk-VTMYCZLT.js +17003 -0
  14. package/dist/{chunk-DOI4ACQ4.js → chunk-XV6DBK4C.js} +42 -18
  15. package/dist/{chunk-ZS4DVWW2.js → chunk-YAKNCBUK.js} +90 -84
  16. package/dist/{chunk-HTJ7WSAL.js → chunk-YIOZ7W3M.js} +9 -7
  17. package/dist/{doctor-FDTAT6OD.js → doctor-HENBO5YV.js} +11 -19
  18. package/dist/index.js +16586 -5259
  19. package/dist/{launch-B7DBYHZD.js → launch-DMKAPDO4.js} +73 -46
  20. package/dist/pi-extensions/remote-tools.cjs +4 -4
  21. package/dist/pi-extensions/xgate.cjs +4283 -325
  22. package/dist/read-auth-26D4BRUP.js +10 -0
  23. package/dist/{reconcile-RSDLDHIA.js → reconcile-6A5U2Q7Q.js} +7 -6
  24. package/dist/{status-YFWORML2.js → status-GRLQL23D.js} +11 -16
  25. package/dist/{sync-pull-JMAKIXL6.js → sync-pull-MSYWP5WU.js} +4 -3
  26. package/dist/task-access-grants-54GQX63L.js +13 -0
  27. package/package.json +8 -4
  28. package/dist/chunk-4WVIB4DD.js +0 -11695
@@ -0,0 +1,933 @@
1
+ import { createRequire as __tmCreateRequire } from 'node:module'; import { fileURLToPath as __tmFileURLToPath } from 'node:url'; import { dirname as __tmDirname } from 'node:path'; const require = __tmCreateRequire(import.meta.url); const __tmFilename = __tmFileURLToPath(import.meta.url); const __tmDirnameValue = __tmDirname(__tmFilename);
2
+ import {
3
+ IDEMPOTENCY_KEY_HEADER,
4
+ NetworkInfoSchema,
5
+ apiErrorEnvelopeOf,
6
+ buildLegalReceiptHeaders,
7
+ getTaskmarketHome,
8
+ isInFlightApiError,
9
+ paymentProtocolFor,
10
+ taskmarketChain,
11
+ verifierProviderId
12
+ } from "./chunk-VTMYCZLT.js";
13
+
14
+ // src/lib/idempotency.ts
15
+ import { randomUUID } from "crypto";
16
+ var pendingEnvKey = process.env.TASKMARKET_IDEMPOTENCY_KEY?.trim() || void 0;
17
+ var keyByError = /* @__PURE__ */ new WeakMap();
18
+ function rememberIdempotencyKeyFor(error, key) {
19
+ if (error === null || typeof error !== "object" && typeof error !== "function") return;
20
+ if (keyByError.has(error)) return;
21
+ keyByError.set(error, key);
22
+ }
23
+ function idempotencyKeyForError(error) {
24
+ if (error === null || typeof error !== "object" && typeof error !== "function")
25
+ return void 0;
26
+ return keyByError.get(error);
27
+ }
28
+ function newIdempotencyKey() {
29
+ return randomUUID();
30
+ }
31
+ function resolveIdempotencyKey(explicit) {
32
+ if (explicit !== void 0) {
33
+ return explicit;
34
+ }
35
+ if (pendingEnvKey !== void 0) {
36
+ const fromEnv = pendingEnvKey;
37
+ pendingEnvKey = void 0;
38
+ return fromEnv;
39
+ }
40
+ return newIdempotencyKey();
41
+ }
42
+ async function withIdempotentWrite(key, fn) {
43
+ try {
44
+ return await fn();
45
+ } catch (error) {
46
+ rememberIdempotencyKeyFor(error, key);
47
+ throw error;
48
+ }
49
+ }
50
+ function idempotencyHeaders(key) {
51
+ return { [IDEMPOTENCY_KEY_HEADER]: key };
52
+ }
53
+
54
+ // src/lib/keystore.ts
55
+ import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
56
+ import { promises as fs } from "fs";
57
+ import path from "path";
58
+ import { privateKeyToAccount } from "viem/accounts";
59
+ function getKeystorePath() {
60
+ return path.join(getTaskmarketHome(), "keystore.json");
61
+ }
62
+ function generateKeypair() {
63
+ const keyBytes = randomBytes(32);
64
+ const privateKey = "0x" + keyBytes.toString("hex");
65
+ const account = privateKeyToAccount(privateKey);
66
+ return { privateKey, address: account.address };
67
+ }
68
+ function encryptPrivateKey(deviceEncryptionKeyHex, privateKey) {
69
+ const key = Buffer.from(deviceEncryptionKeyHex, "hex");
70
+ const iv = randomBytes(12);
71
+ const cipher = createCipheriv("aes-256-gcm", key, iv);
72
+ const plaintext = Buffer.from(privateKey, "utf8");
73
+ const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
74
+ const tag = cipher.getAuthTag();
75
+ return Buffer.concat([iv, tag, encrypted]).toString("hex");
76
+ }
77
+ function decryptPrivateKey(deviceEncryptionKeyHex, encryptedHex) {
78
+ const key = Buffer.from(deviceEncryptionKeyHex, "hex");
79
+ const data = Buffer.from(encryptedHex, "hex");
80
+ const iv = data.subarray(0, 12);
81
+ const tag = data.subarray(12, 28);
82
+ const ciphertext = data.subarray(28);
83
+ const decipher = createDecipheriv("aes-256-gcm", key, iv);
84
+ decipher.setAuthTag(tag);
85
+ const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
86
+ return decrypted.toString("utf8");
87
+ }
88
+ async function saveKeystore(keystore, keystorePath) {
89
+ const p = keystorePath ?? getKeystorePath();
90
+ await fs.mkdir(path.dirname(p), { recursive: true });
91
+ await fs.writeFile(p, JSON.stringify(keystore, null, 2), { encoding: "utf8", mode: 384 });
92
+ await fs.chmod(p, 384);
93
+ }
94
+ var KeystoreExistsError = class extends Error {
95
+ constructor(keystorePath) {
96
+ super(`A keystore already exists at ${keystorePath}.`);
97
+ this.name = "KeystoreExistsError";
98
+ }
99
+ };
100
+ async function createKeystore(keystore, keystorePath) {
101
+ const p = keystorePath ?? getKeystorePath();
102
+ await fs.mkdir(path.dirname(p), { recursive: true });
103
+ try {
104
+ await fs.writeFile(p, JSON.stringify(keystore, null, 2), {
105
+ encoding: "utf8",
106
+ mode: 384,
107
+ flag: "wx"
108
+ });
109
+ } catch (error) {
110
+ if (error.code === "EEXIST") throw new KeystoreExistsError(p);
111
+ throw error;
112
+ }
113
+ }
114
+ async function loadKeystore(keystorePath) {
115
+ const p = keystorePath ?? getKeystorePath();
116
+ let data;
117
+ try {
118
+ data = await fs.readFile(p, "utf8");
119
+ } catch {
120
+ throw new Error(`Keystore not found at ${p}. Run 'taskmarket init' first.`);
121
+ }
122
+ return JSON.parse(data);
123
+ }
124
+ async function keystoreExists(keystorePath) {
125
+ const p = keystorePath ?? getKeystorePath();
126
+ try {
127
+ await fs.access(p);
128
+ return true;
129
+ } catch {
130
+ return false;
131
+ }
132
+ }
133
+ async function readKeystoreNetwork(keystorePath) {
134
+ try {
135
+ const keystore = JSON.parse(
136
+ await fs.readFile(keystorePath ?? getKeystorePath(), "utf8")
137
+ );
138
+ return typeof keystore.network === "string" ? keystore.network : void 0;
139
+ } catch {
140
+ return void 0;
141
+ }
142
+ }
143
+
144
+ // src/lib/network.ts
145
+ var NETWORK_NAMES = [
146
+ "base",
147
+ "base-sepolia",
148
+ "tempo",
149
+ "tempo-testnet",
150
+ "robinhood",
151
+ "robinhood-testnet",
152
+ "solana",
153
+ "solana-devnet"
154
+ ];
155
+ var PRODUCTION_API_URL = "https://api.taskmarket.dev";
156
+ var TESTNET_API_URL = "https://testnet-api-market.daydreams.systems";
157
+ var NETWORKS = {
158
+ base: { name: "base", apiUrl: PRODUCTION_API_URL, chainId: 8453, available: true },
159
+ "base-sepolia": {
160
+ name: "base-sepolia",
161
+ apiUrl: TESTNET_API_URL,
162
+ chainId: 84532,
163
+ available: true
164
+ },
165
+ tempo: {
166
+ name: "tempo",
167
+ apiUrl: PRODUCTION_API_URL,
168
+ chainId: 4217,
169
+ available: true
170
+ },
171
+ "tempo-testnet": {
172
+ name: "tempo-testnet",
173
+ apiUrl: TESTNET_API_URL,
174
+ chainId: 42431,
175
+ available: true
176
+ },
177
+ // Robinhood Chain serves Sponsored Tasks only. Whether a chain is served is the API's own
178
+ // per-chain setting (ADR-0327): `chainInfoFor` refuses one its network.info does not list.
179
+ robinhood: {
180
+ name: "robinhood",
181
+ apiUrl: PRODUCTION_API_URL,
182
+ chainId: 4663,
183
+ available: true
184
+ },
185
+ "robinhood-testnet": {
186
+ name: "robinhood-testnet",
187
+ apiUrl: TESTNET_API_URL,
188
+ chainId: 46630,
189
+ available: true
190
+ },
191
+ // Solana runs Sponsored Tasks only; its API requests name the Base chain of the same network.
192
+ solana: {
193
+ name: "solana",
194
+ apiUrl: PRODUCTION_API_URL,
195
+ chainId: 8453,
196
+ available: true,
197
+ solana: { chainId: 101, cluster: "mainnet-beta" }
198
+ },
199
+ "solana-devnet": {
200
+ name: "solana-devnet",
201
+ apiUrl: TESTNET_API_URL,
202
+ chainId: 84532,
203
+ available: true,
204
+ solana: { chainId: 103, cluster: "devnet" }
205
+ }
206
+ };
207
+ var SPONSORED_ONLY_COMMANDS = ["sponsored-task", "wallet", "address"];
208
+ function coreNetworkNames() {
209
+ return liveNetworkNames().filter(
210
+ (name) => !NETWORKS[name].solana && taskmarketChain(NETWORKS[name].chainId).isCore
211
+ );
212
+ }
213
+ function assertNetworkServesCommand(network, command) {
214
+ if (!network || !network.solana && taskmarketChain(network.chainId).isCore) return;
215
+ if (SPONSORED_ONLY_COMMANDS.includes(command)) return;
216
+ const name = network.solana ? `Solana (${network.name})` : taskmarketChain(network.chainId).name;
217
+ throw new Error(
218
+ `${name} serves Sponsored Tasks only: \`taskmarket ${command}\` needs a core network (${coreNetworkNames().join(", ")}). On --network ${network.name}, use: ${SPONSORED_ONLY_COMMANDS.join(", ")}.`
219
+ );
220
+ }
221
+ function networkNameForChain(chainId) {
222
+ return NETWORK_NAMES.find(
223
+ (name) => NETWORKS[name].solana ? NETWORKS[name].solana.chainId === chainId : NETWORKS[name].chainId === chainId
224
+ );
225
+ }
226
+ function taskmarketCommandFor(chainId) {
227
+ const name = networkNameForChain(chainId);
228
+ return name ? `taskmarket --network ${name}` : "taskmarket";
229
+ }
230
+ function isNetworkName(value) {
231
+ return NETWORK_NAMES.includes(value);
232
+ }
233
+ function getNetwork(nameOrChainId) {
234
+ const raw = nameOrChainId.trim().toLowerCase();
235
+ const name = /^\d+$/.test(raw) ? networkNameForChain(Number(raw)) ?? raw : raw;
236
+ if (!isNetworkName(name)) {
237
+ throw new Error(
238
+ `Unknown network '${name}'. Expected one of: ${NETWORK_NAMES.join(", ")}, or its chain id.`
239
+ );
240
+ }
241
+ const network = NETWORKS[name];
242
+ if (!network.available) {
243
+ const live = liveNetworkNames().join(", ");
244
+ throw new Error(
245
+ `${network.unavailableMessage ?? `Network '${name}' is not available yet.`} Choose one of: ${live}.`
246
+ );
247
+ }
248
+ return network;
249
+ }
250
+ function liveNetworkNames() {
251
+ return NETWORK_NAMES.filter((n) => NETWORKS[n].available);
252
+ }
253
+ var NetworkRequiredError = class extends Error {
254
+ code = "network_required";
255
+ constructor() {
256
+ super(
257
+ `No network selected. The CLI has no default network: pass --network <name> or set TASKMARKET_NETWORK (one of: ${liveNetworkNames().join(", ")}).`
258
+ );
259
+ this.name = "NetworkRequiredError";
260
+ }
261
+ };
262
+ function resolveNetwork(sources) {
263
+ const name = sources.flag || sources.env || sources.keystore;
264
+ return name ? getNetwork(name) : void 0;
265
+ }
266
+ var selected;
267
+ function setSelectedNetwork(network) {
268
+ selected = network;
269
+ }
270
+ function selectedNetworkOrUndefined() {
271
+ return selected ?? resolveNetwork({ env: process.env.TASKMARKET_NETWORK });
272
+ }
273
+ function selectedNetwork() {
274
+ const network = selectedNetworkOrUndefined();
275
+ if (!network) throw new NetworkRequiredError();
276
+ return network;
277
+ }
278
+ function withChainId(path2, chainId = selectedNetwork().chainId) {
279
+ return `${path2}${path2.includes("?") ? "&" : "?"}chainId=${chainId}`;
280
+ }
281
+ function apiUrl() {
282
+ return process.env.TASKMARKET_API_URL || selectedNetwork().apiUrl;
283
+ }
284
+ function paymentProtocolOf(chainId) {
285
+ return paymentProtocolFor(taskmarketChain(chainId).payment.method);
286
+ }
287
+ function apiOrigin() {
288
+ return new URL(apiUrl()).origin;
289
+ }
290
+ function networkToStamp() {
291
+ return process.env.TASKMARKET_API_URL ? void 0 : selectedNetwork().name;
292
+ }
293
+ function chainInfoFor(raw, chainId) {
294
+ const full = NetworkInfoSchema.safeParse(raw);
295
+ if (!full.success) return void 0;
296
+ const chain = full.data.chains.find((entry) => entry.chainId === chainId);
297
+ if (chain) return chain;
298
+ const selected2 = selectedNetworkOrUndefined();
299
+ const subject = selected2?.chainId === chainId ? `network "${selected2.name}" (chain ${chainId})` : `chain ${chainId}`;
300
+ const served = full.data.chains.map((entry) => entry.chainId).join(", ");
301
+ throw new Error(
302
+ `The selected ${subject} is not available: the API at ${process.env.TASKMARKET_API_URL || selected2?.apiUrl || "the selected URL"} does not serve it (it serves chain ${served}). Choose a network it serves with --network, or point TASKMARKET_API_URL at an API that serves this one.`
303
+ );
304
+ }
305
+
306
+ // src/lib/tap.ts
307
+ import {
308
+ constants,
309
+ createPrivateKey,
310
+ createHash,
311
+ createPublicKey,
312
+ generateKeyPairSync,
313
+ randomBytes as randomBytes2,
314
+ sign as cryptoSign
315
+ } from "crypto";
316
+
317
+ // src/lib/signer.ts
318
+ import { privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
319
+ async function fetchDeviceKey(deviceId, apiToken, keyServerUrl) {
320
+ const base = keyServerUrl ?? apiUrl();
321
+ const res = await fetch(`${base}/api/devices/${deviceId}/key`, {
322
+ method: "POST",
323
+ headers: { "Content-Type": "application/json" },
324
+ body: JSON.stringify({ deviceId, apiToken })
325
+ });
326
+ if (!res.ok) {
327
+ const text = await res.text().catch(() => "");
328
+ throw new Error(`Failed to fetch device key (${res.status}): ${text}`);
329
+ }
330
+ const data = await res.json();
331
+ return data.deviceEncryptionKey;
332
+ }
333
+ async function signTypedData(typedData, keystore) {
334
+ const account = await createWalletAccountFromKeystore(keystore);
335
+ const sig = await account.signTypedData(typedData);
336
+ return sig;
337
+ }
338
+ async function signMessage(message, keystore) {
339
+ const account = await createWalletAccountFromKeystore(keystore);
340
+ const sig = await account.signMessage({ message });
341
+ return sig;
342
+ }
343
+ async function createWalletAccountFromKeystore(keystore) {
344
+ const dek = await fetchDeviceKey(keystore.deviceId, keystore.apiToken, keystore.keyServerUrl);
345
+ const privateKey = decryptPrivateKey(dek, keystore.encryptedKey);
346
+ return privateKeyToAccount2(privateKey);
347
+ }
348
+ async function getWalletAddress() {
349
+ const keystore = await loadKeystore();
350
+ return keystore.walletAddress;
351
+ }
352
+
353
+ // src/lib/tap.ts
354
+ var TAP_ALGORITHMS = ["ed25519", "rsa-pss-sha256", "rsa-pss-sha512"];
355
+ var TAP_MAX_LIFETIME_SECONDS = 8 * 60;
356
+ var TAP_DEFAULT_LIFETIME_SECONDS = 5 * 60;
357
+ var ED25519_PKCS8_PREFIX = Buffer.from("302e020100300506032b657004220420", "hex");
358
+ function isTapAlgorithm(value) {
359
+ return TAP_ALGORITHMS.includes(value);
360
+ }
361
+ function algorithmsFor(key) {
362
+ if (key.asymmetricKeyType === "ed25519") return ["ed25519"];
363
+ if (key.asymmetricKeyType === "rsa" || key.asymmetricKeyType === "rsa-pss") {
364
+ return ["rsa-pss-sha256", "rsa-pss-sha512"];
365
+ }
366
+ return [];
367
+ }
368
+ function parseTapPrivateKey(text, alg) {
369
+ const trimmed = text.trim();
370
+ let privateKey;
371
+ if (/^(0x)?[0-9a-fA-F]{64}$/.test(trimmed)) {
372
+ privateKey = createPrivateKey({
373
+ key: Buffer.concat([ED25519_PKCS8_PREFIX, Buffer.from(trimmed.replace(/^0x/, ""), "hex")]),
374
+ format: "der",
375
+ type: "pkcs8"
376
+ });
377
+ } else if (trimmed.includes("-----BEGIN")) {
378
+ try {
379
+ privateKey = createPrivateKey(trimmed);
380
+ } catch {
381
+ throw new Error("The TAP key is not a readable PEM private key");
382
+ }
383
+ } else {
384
+ throw new Error(
385
+ "The TAP key must be a PEM private key or a 32-byte ed25519 seed as 64 hex characters"
386
+ );
387
+ }
388
+ const supported = algorithmsFor(privateKey);
389
+ if (supported.length === 0) {
390
+ throw new Error(
391
+ `TAP keys must be ed25519 or RSA, not ${privateKey.asymmetricKeyType ?? "unknown"}`
392
+ );
393
+ }
394
+ if (alg !== void 0) {
395
+ if (!isTapAlgorithm(alg)) {
396
+ throw new Error(`--alg must be one of: ${TAP_ALGORITHMS.join(", ")}`);
397
+ }
398
+ if (!supported.includes(alg)) {
399
+ throw new Error(`A ${privateKey.asymmetricKeyType} key cannot sign ${alg}`);
400
+ }
401
+ return { privateKey, alg };
402
+ }
403
+ if (supported.length > 1) {
404
+ throw new Error("An RSA TAP key needs --alg rsa-pss-sha256 or --alg rsa-pss-sha512");
405
+ }
406
+ return { privateKey, alg: supported[0] };
407
+ }
408
+ function validateTapKeyId(keyId) {
409
+ const trimmed = keyId.trim();
410
+ if (!/^[A-Za-z0-9._:~\-/+=]{1,256}$/.test(trimmed)) {
411
+ throw new Error("--key-id must be 1 to 256 characters of letters, digits and . _ : ~ - / + =");
412
+ }
413
+ return trimmed;
414
+ }
415
+ function validateTapSignatureAgent(value) {
416
+ const trimmed = value.trim();
417
+ let url;
418
+ try {
419
+ url = new URL(trimmed);
420
+ } catch {
421
+ throw new Error("--signature-agent must be an https URL");
422
+ }
423
+ if (url.protocol !== "https:") throw new Error("--signature-agent must be an https URL");
424
+ if (url.username !== "" || url.password !== "") {
425
+ throw new Error("--signature-agent must not carry credentials");
426
+ }
427
+ if (!/^[\x20-\x7e]+$/.test(trimmed) || /["\\]/.test(trimmed)) {
428
+ throw new Error("--signature-agent must be printable ASCII with no quotes or backslashes");
429
+ }
430
+ return trimmed;
431
+ }
432
+ function simulatedTapSignatureAgent(apiBase, chainId) {
433
+ return `${new URL(apiBase).origin}/api/tap/simulated/${chainId}`;
434
+ }
435
+ function tapSignatureAgentFor(stored, apiBase = apiUrl) {
436
+ if (stored.simulated === true && stored.chainId !== void 0) {
437
+ return simulatedTapSignatureAgent(apiBase(), stored.chainId);
438
+ }
439
+ return stored.signatureAgent;
440
+ }
441
+ function generateTapKey() {
442
+ return { privateKey: generateKeyPairSync("ed25519").privateKey, alg: "ed25519" };
443
+ }
444
+ function storedTapKey(deviceEncryptionKeyHex, keyId, key, now = /* @__PURE__ */ new Date(), source = "imported") {
445
+ const pem = key.privateKey.export({ type: "pkcs8", format: "pem" }).toString();
446
+ return {
447
+ keyId,
448
+ alg: key.alg,
449
+ encryptedKey: encryptPrivateKey(deviceEncryptionKeyHex, pem),
450
+ publicKey: createPublicKey(key.privateKey).export({ type: "spki", format: "pem" }).toString(),
451
+ importedAt: now.toISOString(),
452
+ source
453
+ };
454
+ }
455
+ function tapKeySummary(stored) {
456
+ return {
457
+ keyId: stored.keyId,
458
+ enrolled: stored.keyId !== null,
459
+ alg: stored.alg,
460
+ publicKey: stored.publicKey,
461
+ publicKeyJwk: createPublicKey(stored.publicKey).export({ format: "jwk" }),
462
+ source: stored.source ?? "imported",
463
+ importedAt: stored.importedAt,
464
+ /** The chain the CLI enrolled the key on, or null when it did not enrol it. */
465
+ chainId: stored.chainId ?? null,
466
+ /** True for a key id from the simulated directory, null when the CLI did not enrol it. */
467
+ simulated: stored.simulated ?? null,
468
+ /** The directory URL given at import for `Signature-Agent`, or null when none was. */
469
+ signatureAgent: stored.signatureAgent ?? null
470
+ };
471
+ }
472
+ async function loadTapSigningKey(keystore, deviceKey, taskId, apiBase = apiUrl) {
473
+ const stored = keystore.tapKey;
474
+ if (!stored) return void 0;
475
+ if (stored.keyId === null) throw new TapKeyNotEnrolledError(taskId);
476
+ const dek = deviceKey ? await deviceKey() : await fetchDeviceKey(keystore.deviceId, keystore.apiToken, keystore.keyServerUrl);
477
+ const signatureAgent = tapSignatureAgentFor(stored, apiBase);
478
+ return {
479
+ keyId: stored.keyId,
480
+ alg: stored.alg,
481
+ privateKey: createPrivateKey(decryptPrivateKey(dek, stored.encryptedKey)),
482
+ ...signatureAgent !== void 0 ? { signatureAgent } : {}
483
+ };
484
+ }
485
+ var TAP_OPTIONAL_HEADERS = ["x-taskmarket-idempotency-key", "authorization"];
486
+ var TAP_OWN_HEADERS = /* @__PURE__ */ new Set([
487
+ "signature",
488
+ "signature-input",
489
+ "content-digest",
490
+ "signature-agent"
491
+ ]);
492
+ function tapSignatureParams(p) {
493
+ return `(${p.components.map((c) => `"${c}"`).join(" ")});created=${p.created};expires=${p.expires};keyid="${p.keyId}";alg="${p.alg}";nonce="${p.nonce}";tag="${p.tag}"`;
494
+ }
495
+ function bodyBytes(body) {
496
+ return typeof body === "string" ? Buffer.from(body, "utf8") : Buffer.from(body);
497
+ }
498
+ function tapContentDigest(body) {
499
+ return `sha-256=:${createHash("sha256").update(bodyBytes(body)).digest("base64")}:`;
500
+ }
501
+ function tapSignatureAgentHeader(url) {
502
+ return `"${url}"`;
503
+ }
504
+ function tapCoveredComponents(request) {
505
+ const u = new URL(request.url);
506
+ const headers = new Headers(request.headers);
507
+ const out = [
508
+ { name: "@method", value: request.method.toUpperCase() },
509
+ { name: "@scheme", value: u.protocol.replace(/:$/, "").toLowerCase() },
510
+ { name: "@authority", value: u.host.toLowerCase() },
511
+ { name: "@path", value: u.pathname || "/" },
512
+ // `?` and the raw query, or a bare `?` when there is none (RFC 9421 section 2.2.7).
513
+ { name: "@query", value: u.search === "" ? "?" : u.search }
514
+ ];
515
+ const header = (name) => {
516
+ const value = headers.get(name);
517
+ if (value !== null) out.push({ name, value: value.trim() });
518
+ };
519
+ if (request.body !== void 0 && request.body !== null) {
520
+ header("content-digest");
521
+ header("content-type");
522
+ }
523
+ header("signature-agent");
524
+ for (const name of TAP_OPTIONAL_HEADERS) header(name);
525
+ return out;
526
+ }
527
+ function tapSignatureBase(components, signatureParams) {
528
+ return [
529
+ ...components.map((c) => `"${c.name}": ${c.value}`),
530
+ `"@signature-params": ${signatureParams}`
531
+ ].join("\n");
532
+ }
533
+ function rawSign(alg, privateKey, data) {
534
+ if (alg === "ed25519") return cryptoSign(null, data, privateKey);
535
+ return cryptoSign(alg === "rsa-pss-sha256" ? "sha256" : "sha512", data, {
536
+ key: privateKey,
537
+ padding: constants.RSA_PKCS1_PSS_PADDING,
538
+ saltLength: alg === "rsa-pss-sha256" ? 32 : 64
539
+ });
540
+ }
541
+ function withoutTapHeaders(headers) {
542
+ const out = {};
543
+ for (const [name, value] of Object.entries(headers ?? {})) {
544
+ if (!TAP_OWN_HEADERS.has(name.toLowerCase())) out[name] = value;
545
+ }
546
+ return out;
547
+ }
548
+ function signTapRequest(opts) {
549
+ const created = opts.created ?? Math.floor(Date.now() / 1e3);
550
+ const lifetime = Math.min(
551
+ opts.lifetimeSeconds ?? TAP_DEFAULT_LIFETIME_SECONDS,
552
+ TAP_MAX_LIFETIME_SECONDS
553
+ );
554
+ const added = {};
555
+ if (opts.body !== void 0 && opts.body !== null) {
556
+ added["Content-Digest"] = tapContentDigest(opts.body);
557
+ }
558
+ if (opts.key.signatureAgent !== void 0) {
559
+ added["Signature-Agent"] = tapSignatureAgentHeader(opts.key.signatureAgent);
560
+ }
561
+ const components = tapCoveredComponents({
562
+ ...opts,
563
+ headers: { ...withoutTapHeaders(opts.headers), ...added }
564
+ });
565
+ const params = tapSignatureParams({
566
+ components: components.map((c) => c.name),
567
+ keyId: opts.key.keyId,
568
+ alg: opts.key.alg,
569
+ tag: opts.tag ?? "agent-browser-auth",
570
+ created,
571
+ expires: created + lifetime,
572
+ nonce: opts.nonce ?? randomBytes2(16).toString("base64url")
573
+ });
574
+ const base = tapSignatureBase(components, params);
575
+ const sig = rawSign(opts.key.alg, opts.key.privateKey, Buffer.from(base, "utf8"));
576
+ return {
577
+ ...added,
578
+ "Signature-Input": `sig1=${params}`,
579
+ Signature: `sig1=:${sig.toString("base64")}:`
580
+ };
581
+ }
582
+ function withTapSignature(request, tap) {
583
+ if (!tap) return { ...request.headers ?? {} };
584
+ const headers = withoutTapHeaders(request.headers);
585
+ return { ...headers, ...signTapRequest({ ...request, headers, key: tap.key, tag: tap.tag }) };
586
+ }
587
+ function headerRecord(init) {
588
+ const out = {};
589
+ new Headers(init ?? {}).forEach((value, name) => {
590
+ out[name] = value;
591
+ });
592
+ return out;
593
+ }
594
+ async function fetchBody(input, init) {
595
+ const body = init?.body ?? null;
596
+ if (body === null) {
597
+ if (input instanceof Request && input.body !== null) {
598
+ return new Uint8Array(await input.clone().arrayBuffer());
599
+ }
600
+ return null;
601
+ }
602
+ if (typeof body === "string" || body instanceof Uint8Array) return body;
603
+ if (body instanceof ArrayBuffer) return new Uint8Array(body);
604
+ if (ArrayBuffer.isView(body)) {
605
+ return new Uint8Array(body.buffer, body.byteOffset, body.byteLength);
606
+ }
607
+ throw new Error("A TAP-signed request body must be a string or bytes");
608
+ }
609
+ function tapSigningFetch(base, tap) {
610
+ return (async (input, init) => {
611
+ const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
612
+ const method = (init?.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase();
613
+ if (method !== "POST") return base(input, init);
614
+ const headers = {
615
+ ...headerRecord(input instanceof Request ? input.headers : void 0),
616
+ ...headerRecord(init?.headers)
617
+ };
618
+ const body = await fetchBody(input, init);
619
+ return base(input, { ...init, headers: withTapSignature({ method, url, headers, body }, tap) });
620
+ });
621
+ }
622
+ function taskRequiresTap(task) {
623
+ return (task?.verifiedBy ?? []).some((id) => verifierProviderId(id) === "tap");
624
+ }
625
+ var TapKeyMissingError = class extends Error {
626
+ code = "tap_key_missing";
627
+ constructor(taskId) {
628
+ super(
629
+ `Task ${taskId} requires TAP verification and no TAP key is configured. Import one with 'taskmarket tap import --key-id <id> --key-file <path>', or create one with 'taskmarket tap create' and enrol it with 'taskmarket tap enrol'.`
630
+ );
631
+ this.name = "TapKeyMissingError";
632
+ }
633
+ };
634
+ var TapKeyNotEnrolledError = class extends Error {
635
+ code = "tap_key_not_enrolled";
636
+ constructor(taskId) {
637
+ super(
638
+ `${taskId ? `Task ${taskId} requires TAP verification and the` : "The"} TAP key is not enrolled: it has no key id. Enrol it with 'taskmarket tap enrol' on a chain that uses the simulated directory; enrolment with the verifier is not available from the CLI yet. Nothing was sent.`
639
+ );
640
+ this.name = "TapKeyNotEnrolledError";
641
+ }
642
+ };
643
+ async function runWorkerAction(input) {
644
+ if (!taskRequiresTap(input.task)) return input.run(void 0);
645
+ const key = await loadTapSigningKey(input.keystore, input.deviceKey, input.taskId);
646
+ if (!key) throw new TapKeyMissingError(input.taskId);
647
+ return input.run({ key, tag: input.tag ?? "agent-browser-auth" });
648
+ }
649
+ var TASK_REQUIREMENT_HINTS = {
650
+ identity_missing: "The worker wallet has no identity under this protocol. Register one with 'taskmarket identity register'.",
651
+ verification_failed: "The verifier did not accept the request. For TAP, import a key with 'taskmarket tap import' or, if none is configured, create one with 'taskmarket tap create' and enrol it with 'taskmarket tap enrol', then check 'taskmarket tap show': the key id must be in the verifier's key directory, the algorithm must match it, the key must not be bound to another wallet, and the local clock must be correct.",
652
+ verifier_unavailable: "The verifier is not configured on this server, could not be reached, or does not run on this task's chain (TAP is Tempo only). Nothing was charged; try again later, or pick a task on Tempo.",
653
+ wallet_unidentified: "The request did not say which wallet is acting. Update the CLI; it identifies the worker on gated actions.",
654
+ batched_request: "A gated action was sent in a batched request. Send it on its own."
655
+ };
656
+ function taskRequirementHints(requirements) {
657
+ return requirements.map((r) => ({
658
+ ...r,
659
+ hint: requirementHint(r)
660
+ }));
661
+ }
662
+ function requirementHint(r) {
663
+ if (r.id === "erc8004:reputation:reputable" && r.reason === "verification_failed") {
664
+ return `${r.message ? `This wallet ${r.message}. ` : ""}Accepted jobs are counted from Taskmarket's own records; 'taskmarket stats' shows your count. Nothing was charged.`;
665
+ }
666
+ return TASK_REQUIREMENT_HINTS[r.reason] ?? "This requirement is not met.";
667
+ }
668
+
669
+ // src/lib/api.ts
670
+ var ApiError = class extends Error {
671
+ status;
672
+ /**
673
+ * The idempotency key the failed write was sent under, when the request was a write. This is
674
+ * the only identifier the caller can still hold after a failure -- the intent id is minted by
675
+ * the backend and reaches the caller only in the response the failure destroyed -- so it
676
+ * travels on the error rather than being recoverable from the response. See
677
+ * apps/cli/src/index.ts and lib/output.ts, which surface it on the CLI's JSON envelope.
678
+ */
679
+ idempotencyKey;
680
+ /**
681
+ * The backend's machine-readable classification of the failure (ADR-0058).
682
+ *
683
+ * Before it existed the CLI rendered every failure identically, which is why
684
+ * `docs/guides/CLI_GUIDE.md` had to tell script authors never to auto-retry a paid command: there was
685
+ * no way to tell a write that is still landing from one that was definitively rejected, and
686
+ * retrying the first is a second payment. `reason` is that way.
687
+ *
688
+ * Absent when the backend answered with no envelope -- an older deployment, or a failure that
689
+ * never reached the API at all -- in which case a caller must fall back to the old rule and
690
+ * treat the outcome as unknown rather than as safe to retry.
691
+ */
692
+ envelope;
693
+ constructor(status, message, idempotencyKey, envelope) {
694
+ super(message);
695
+ this.name = "ApiError";
696
+ this.status = status;
697
+ this.idempotencyKey = idempotencyKey;
698
+ this.envelope = envelope;
699
+ }
700
+ };
701
+ async function readFailureBody(res) {
702
+ return parseFailureBody(await res.text().catch(() => ""));
703
+ }
704
+ function parseFailureBody(text) {
705
+ try {
706
+ return { body: JSON.parse(text), text };
707
+ } catch {
708
+ return { body: void 0, text };
709
+ }
710
+ }
711
+ function failureMessage(prefix, status, read) {
712
+ return `${prefix} (${status}): ${read.body !== void 0 ? JSON.stringify(read.body) : read.text}`;
713
+ }
714
+ function withErrorContext(error, context) {
715
+ const message = error instanceof Error ? error.message : String(error);
716
+ const wrapped = error instanceof ApiError ? new ApiError(error.status, `${context}: ${message}`, error.idempotencyKey, error.envelope) : new Error(`${context}: ${message}`);
717
+ const key = idempotencyKeyForError(error);
718
+ if (key !== void 0) {
719
+ rememberIdempotencyKeyFor(wrapped, key);
720
+ }
721
+ return wrapped;
722
+ }
723
+ function isPendingApiError(error) {
724
+ return error instanceof ApiError && isInFlightApiError(error.envelope);
725
+ }
726
+ function legalReceiptHeadersForKeystore(keystore, path2, method) {
727
+ const canSend = keystore.legalAcceptanceApiOrigin === apiOrigin() && (method === "POST" || path2 === "/api/legal/status");
728
+ return canSend ? buildLegalReceiptHeaders(keystore.legalAcceptanceReceipt) : {};
729
+ }
730
+ async function legalReceiptHeaders(path2, method) {
731
+ try {
732
+ const keystore = await loadKeystore();
733
+ return legalReceiptHeadersForKeystore(keystore, path2, method);
734
+ } catch {
735
+ return {};
736
+ }
737
+ }
738
+ async function apiGet(path2, options) {
739
+ const legalHeaders = await legalReceiptHeaders(path2, "GET");
740
+ const res = await fetch(`${apiUrl()}${path2}`, {
741
+ method: "GET",
742
+ redirect: "error",
743
+ signal: options?.signal,
744
+ headers: {
745
+ "Content-Type": "application/json",
746
+ ...legalHeaders,
747
+ ...options?.headers ?? {}
748
+ }
749
+ });
750
+ if (!res.ok) {
751
+ const read = await readFailureBody(res);
752
+ throw new ApiError(
753
+ res.status,
754
+ failureMessage(`GET ${path2} failed`, res.status, read),
755
+ void 0,
756
+ apiErrorEnvelopeOf(read.body) ?? void 0
757
+ );
758
+ }
759
+ return res.json();
760
+ }
761
+ async function apiReadPost(path2, body, options) {
762
+ const legalHeaders = await legalReceiptHeaders(path2, "POST");
763
+ const res = await fetch(`${apiUrl()}${path2}`, {
764
+ method: "POST",
765
+ redirect: "error",
766
+ headers: { "Content-Type": "application/json", ...legalHeaders, ...options?.headers ?? {} },
767
+ body: JSON.stringify(body)
768
+ });
769
+ if (!res.ok) {
770
+ const read = await readFailureBody(res);
771
+ throw new ApiError(
772
+ res.status,
773
+ failureMessage(`POST ${path2} failed`, res.status, read),
774
+ void 0,
775
+ apiErrorEnvelopeOf(read.body) ?? void 0
776
+ );
777
+ }
778
+ return res.json();
779
+ }
780
+ async function apiPost(path2, body, options) {
781
+ const legalHeaders = await legalReceiptHeaders(path2, "POST");
782
+ const idempotencyKey = resolveIdempotencyKey(options?.idempotencyKey);
783
+ return withIdempotentWrite(idempotencyKey, async () => {
784
+ const url = `${apiUrl()}${path2}`;
785
+ const payload = JSON.stringify(body);
786
+ const res = await fetch(url, {
787
+ method: "POST",
788
+ redirect: "error",
789
+ // Signed over this exact request with a fresh nonce when the write carries an attestation.
790
+ headers: withTapSignature(
791
+ {
792
+ method: "POST",
793
+ url,
794
+ headers: {
795
+ "Content-Type": "application/json",
796
+ ...legalHeaders,
797
+ ...idempotencyHeaders(idempotencyKey),
798
+ ...options?.headers ?? {}
799
+ },
800
+ body: payload
801
+ },
802
+ options?.tap
803
+ ),
804
+ body: payload
805
+ });
806
+ if (!res.ok) {
807
+ const read = await readFailureBody(res);
808
+ throw new ApiError(
809
+ res.status,
810
+ failureMessage(`POST ${path2} failed`, res.status, read),
811
+ idempotencyKey,
812
+ apiErrorEnvelopeOf(read.body) ?? void 0
813
+ );
814
+ }
815
+ return { data: await res.json(), idempotencyKey };
816
+ });
817
+ }
818
+ async function apiDelete(path2, options) {
819
+ const legalHeaders = await legalReceiptHeaders(path2, "POST");
820
+ const idempotencyKey = resolveIdempotencyKey(options?.idempotencyKey);
821
+ return withIdempotentWrite(idempotencyKey, async () => {
822
+ const res = await fetch(`${apiUrl()}${path2}`, {
823
+ method: "DELETE",
824
+ redirect: "error",
825
+ headers: {
826
+ "Content-Type": "application/json",
827
+ ...legalHeaders,
828
+ ...idempotencyHeaders(idempotencyKey),
829
+ ...options?.headers ?? {}
830
+ }
831
+ });
832
+ if (!res.ok) {
833
+ const read = await readFailureBody(res);
834
+ throw new ApiError(
835
+ res.status,
836
+ failureMessage(`DELETE ${path2} failed`, res.status, read),
837
+ idempotencyKey,
838
+ apiErrorEnvelopeOf(read.body) ?? void 0
839
+ );
840
+ }
841
+ return { data: await res.json(), idempotencyKey };
842
+ });
843
+ }
844
+ async function apiPut(path2, body, options) {
845
+ const legalHeaders = await legalReceiptHeaders(path2, "POST");
846
+ const idempotencyKey = resolveIdempotencyKey(options?.idempotencyKey);
847
+ return withIdempotentWrite(idempotencyKey, async () => {
848
+ const res = await fetch(`${apiUrl()}${path2}`, {
849
+ method: "PUT",
850
+ redirect: "error",
851
+ headers: {
852
+ "Content-Type": "application/json",
853
+ ...legalHeaders,
854
+ ...idempotencyHeaders(idempotencyKey),
855
+ ...options?.headers ?? {}
856
+ },
857
+ body: JSON.stringify(body)
858
+ });
859
+ if (!res.ok) {
860
+ const read = await readFailureBody(res);
861
+ throw new ApiError(
862
+ res.status,
863
+ failureMessage(`PUT ${path2} failed`, res.status, read),
864
+ idempotencyKey,
865
+ apiErrorEnvelopeOf(read.body) ?? void 0
866
+ );
867
+ }
868
+ return { data: await res.json(), idempotencyKey };
869
+ });
870
+ }
871
+
872
+ export {
873
+ generateKeypair,
874
+ encryptPrivateKey,
875
+ decryptPrivateKey,
876
+ saveKeystore,
877
+ KeystoreExistsError,
878
+ createKeystore,
879
+ loadKeystore,
880
+ keystoreExists,
881
+ readKeystoreNetwork,
882
+ idempotencyKeyForError,
883
+ resolveIdempotencyKey,
884
+ withIdempotentWrite,
885
+ idempotencyHeaders,
886
+ NETWORK_NAMES,
887
+ NETWORKS,
888
+ coreNetworkNames,
889
+ assertNetworkServesCommand,
890
+ taskmarketCommandFor,
891
+ isNetworkName,
892
+ getNetwork,
893
+ NetworkRequiredError,
894
+ resolveNetwork,
895
+ setSelectedNetwork,
896
+ selectedNetworkOrUndefined,
897
+ selectedNetwork,
898
+ withChainId,
899
+ apiUrl,
900
+ paymentProtocolOf,
901
+ apiOrigin,
902
+ networkToStamp,
903
+ chainInfoFor,
904
+ fetchDeviceKey,
905
+ signTypedData,
906
+ signMessage,
907
+ createWalletAccountFromKeystore,
908
+ getWalletAddress,
909
+ TAP_ALGORITHMS,
910
+ parseTapPrivateKey,
911
+ validateTapKeyId,
912
+ validateTapSignatureAgent,
913
+ generateTapKey,
914
+ storedTapKey,
915
+ tapKeySummary,
916
+ withTapSignature,
917
+ tapSigningFetch,
918
+ taskRequiresTap,
919
+ runWorkerAction,
920
+ taskRequirementHints,
921
+ ApiError,
922
+ readFailureBody,
923
+ parseFailureBody,
924
+ failureMessage,
925
+ withErrorContext,
926
+ isPendingApiError,
927
+ legalReceiptHeadersForKeystore,
928
+ apiGet,
929
+ apiReadPost,
930
+ apiPost,
931
+ apiDelete,
932
+ apiPut
933
+ };