@lucid-agents/taskmarket 1.7.2 → 1.8.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 (2) hide show
  1. package/dist/index.js +1044 -432
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ var __export = (target, all) => {
6
6
  };
7
7
 
8
8
  // src/index.ts
9
- import { Command as Command66 } from "commander";
9
+ import { Command as Command67 } from "commander";
10
10
  import { createRequire } from "module";
11
11
 
12
12
  // src/commands/init.ts
@@ -4271,7 +4271,10 @@ var TaskCreateSchema = external_exports.object({
4271
4271
  taskVisibility: TaskVisibility.optional().default("public"),
4272
4272
  submissionVisibility: SubmissionVisibility.optional().default("public"),
4273
4273
  stakeRequired: external_exports.boolean().optional().default(false),
4274
- stakeBps: external_exports.number().min(0).max(1e4).optional().default(0),
4274
+ // Integral: this is client-supplied and lands in a `uint16` on chain, where a fraction is
4275
+ // not representable at all. Without `.int()` a 12.5 was accepted here and silently became
4276
+ // something else by the time it reached the contract (ADR-0069).
4277
+ stakeBps: external_exports.number().int().min(0).max(1e4).optional().default(0),
4275
4278
  pitchDeadline: external_exports.number().positive().optional(),
4276
4279
  bidDeadline: external_exports.number().positive().optional(),
4277
4280
  maxPrice: PositiveUsdcBaseUnitsSchema.optional(),
@@ -4283,7 +4286,8 @@ var TaskCreateSchema = external_exports.object({
4283
4286
  hookContract: external_exports.string().optional(),
4284
4287
  hookData: external_exports.string().regex(/^0x(?:[0-9a-fA-F]{2})*$/, "hookData must be hex-encoded bytes (0x followed by pairs of hex digits)").optional(),
4285
4288
  evaluator: external_exports.string().optional(),
4286
- evaluatorFeeBps: external_exports.number().min(0).max(1e4).optional(),
4289
+ // Integral for the same reason as `stakeBps` above: client-supplied, `uint16` on chain.
4290
+ evaluatorFeeBps: external_exports.number().int().min(0).max(1e4).optional(),
4287
4291
  evaluationWindowHours: external_exports.number().positive().optional(),
4288
4292
  appealWindowHours: external_exports.number().positive().optional(),
4289
4293
  disputeResolver: external_exports.string().optional(),
@@ -4399,7 +4403,9 @@ var TaskResponseSchema = external_exports.object({
4399
4403
  // never the hash itself, and never returned for a non-private task.
4400
4404
  hasAccessPassword: external_exports.boolean().optional(),
4401
4405
  stakeRequired: external_exports.boolean(),
4402
- stakeBps: external_exports.number(),
4406
+ // Same shape the request side already enforces (ADR-0069): a `uint16` fraction of the escrow,
4407
+ // so the response cannot describe a stake the contract could not have held.
4408
+ stakeBps: external_exports.number().int().min(0).max(1e4),
4403
4409
  pitchDeadline: external_exports.string().nullable(),
4404
4410
  bidDeadline: external_exports.string().nullable(),
4405
4411
  maxPrice: external_exports.string().nullable(),
@@ -4407,7 +4413,10 @@ var TaskResponseSchema = external_exports.object({
4407
4413
  metricTarget: external_exports.string().nullable(),
4408
4414
  claimedBy: external_exports.string().nullable(),
4409
4415
  claimedAt: external_exports.string().nullable(),
4410
- platformFeeBps: external_exports.number(),
4416
+ // Required, never optional: this is the multiplier every net-reward figure a client shows is
4417
+ // derived from, and the column behind it has been `NOT NULL` since the first migration. A
4418
+ // reader that had to supply its own value on absence would be guessing at money.
4419
+ platformFeeBps: external_exports.number().int().min(0).max(1e4),
4411
4420
  submissionCount: external_exports.number().optional().default(0),
4412
4421
  awardCount: external_exports.number().int().nonnegative().optional(),
4413
4422
  // Read-time projection of the rank-1 task_awards row -- not a separately
@@ -4433,7 +4442,9 @@ var TaskResponseSchema = external_exports.object({
4433
4442
  hookContract: external_exports.string().nullable().optional(),
4434
4443
  evaluator: external_exports.string().nullable().optional(),
4435
4444
  evaluatorStake: external_exports.string().nullable().optional(),
4436
- evaluatorFeeBps: external_exports.number().nullable().optional(),
4445
+ // Genuinely absent when no evaluator is configured -- unlike `platformFeeBps`, null here means
4446
+ // "no evaluator fee", which is the same thing as zero, so a reader coalescing to 0 is right.
4447
+ evaluatorFeeBps: external_exports.number().int().min(0).max(1e4).nullable().optional(),
4437
4448
  evaluationWindow: external_exports.number().nullable().optional(),
4438
4449
  appealWindow: external_exports.number().nullable().optional(),
4439
4450
  disputeResolver: external_exports.string().nullable().optional(),
@@ -4492,7 +4503,11 @@ var TaskDetailResponseSchema = TaskResponseSchema.extend({
4492
4503
  pendingActions: PendingActionSchema.array(),
4493
4504
  awards: TaskAwardSchema.array().optional(),
4494
4505
  dreamsPerUsdc: external_exports.string().optional(),
4495
- bonusBps: external_exports.number().optional(),
4506
+ // Optional as a set, not individually: the router emits `dreamsPerUsdc`, `bonusBps` and every
4507
+ // `estimated*` field together or not at all, depending on whether the task carries the DREAMS
4508
+ // hook. No client multiplies this by a split of its own -- the worker/requester estimates are
4509
+ // computed server-side -- so the absent case needs no fallback multiplier anywhere.
4510
+ bonusBps: external_exports.number().int().min(0).max(1e4).optional(),
4496
4511
  estimatedUsdBonusValue: external_exports.string().optional(),
4497
4512
  estimatedWorkerUsdBonusValue: external_exports.string().optional(),
4498
4513
  estimatedRequesterUsdBonusValue: external_exports.string().optional(),
@@ -4506,6 +4521,16 @@ var RejectSubmissionInputSchema = external_exports.object({
4506
4521
  taskId: external_exports.string(),
4507
4522
  worker: external_exports.string()
4508
4523
  });
4524
+ var AssignEvaluatorInputSchema = external_exports.object({
4525
+ taskId: external_exports.string(),
4526
+ evaluator: external_exports.string(),
4527
+ // Integral: same field, same `uint16`, and the two request-side schemas must not disagree
4528
+ // about what a caller may send.
4529
+ evaluatorFeeBps: external_exports.number().int().min(0).max(1e4).optional(),
4530
+ evaluationWindowHours: external_exports.number().positive().optional(),
4531
+ appealWindowHours: external_exports.number().positive().optional(),
4532
+ disputeResolver: external_exports.string().optional()
4533
+ });
4509
4534
  var RefundExpiredInputSchema = external_exports.object({
4510
4535
  taskId: external_exports.string()
4511
4536
  });
@@ -4714,7 +4739,47 @@ var FeedbackSchema = external_exports.object({
4714
4739
  var HealthResponseSchema = external_exports.object({
4715
4740
  status: external_exports.literal("ok"),
4716
4741
  timestamp: external_exports.string(),
4717
- environment: external_exports.string()
4742
+ environment: external_exports.string(),
4743
+ /**
4744
+ * The submission limits this process is actually enforcing, after any environment
4745
+ * override. Reported because a limit a caller cannot observe is a limit a test can only
4746
+ * guess at: smoke-rate-limit.ts asserted against its own copy of the ceiling and had no
4747
+ * way to notice the backend was started with a different one, so it failed with a
4748
+ * confusing boundary error instead of saying the two disagreed.
4749
+ */
4750
+ limits: external_exports.object({
4751
+ freeSubmissionAllowance: external_exports.number().int().positive(),
4752
+ hardSubmissionCeiling: external_exports.number().int().positive()
4753
+ }),
4754
+ /**
4755
+ * Relayed-intent facts a caller can poll for, in the same spirit as `limits`: a queryable
4756
+ * number, not an alert (ADR-0053).
4757
+ *
4758
+ * Optional because health is what tells you the service is up. If no count is available the
4759
+ * object is omitted rather than reported as a sentinel -- absence under an optional field is
4760
+ * unambiguous, whereas a `-1` or an `"unknown"` string is a value every consumer has to learn
4761
+ * not to plot, and it is also exactly how this endpoint already reads to a caller running
4762
+ * against a build from before the field existed.
4763
+ *
4764
+ * Absent means "no answer here", and it covers a process whose sweep has not run yet as well
4765
+ * as one that does not sweep at all. It never means zero.
4766
+ */
4767
+ intents: external_exports.object({
4768
+ /**
4769
+ * When the count below was computed, not when it was served.
4770
+ *
4771
+ * These numbers come from a periodic sweep rather than from the request, so the response
4772
+ * is a cache and says so. Without this a consumer cannot tell a live zero from a zero
4773
+ * left behind by a worker that stopped running -- and the second is the more urgent of
4774
+ * the two, because a sweep that is not running is a queue nothing is draining.
4775
+ */
4776
+ measuredAt: external_exports.string(),
4777
+ /**
4778
+ * Intents that are neither completed nor failed and have not been touched for well past
4779
+ * normal settlement. A count only -- the endpoint is public.
4780
+ */
4781
+ staleNonTerminal: external_exports.number().int().nonnegative()
4782
+ }).optional()
4718
4783
  });
4719
4784
 
4720
4785
  // ../../packages/shared/dist/schemas/pitch.schemas.js
@@ -4873,8 +4938,12 @@ var WithdrawDreamsOutputSchema = external_exports.object({
4873
4938
  });
4874
4939
  var ExchangeRateOutputSchema = external_exports.object({
4875
4940
  dreamsPerUsdc: external_exports.string(),
4876
- workerSplitBps: external_exports.number(),
4877
- bonusBps: external_exports.number()
4941
+ // Both are read straight off `TaskTokenRewardHook`, which rejects either above 10000
4942
+ // (`InvalidBps`), and both are required here rather than optional: a client that had to
4943
+ // default the split would default it to the whole reward, and one that had to default the
4944
+ // bonus would report no bonus at all. Neither is a fact this endpoint may leave to the reader.
4945
+ workerSplitBps: external_exports.number().int().min(0).max(1e4),
4946
+ bonusBps: external_exports.number().int().min(0).max(1e4)
4878
4947
  });
4879
4948
 
4880
4949
  // ../../packages/shared/dist/schemas/xmtp.schemas.js
@@ -5204,6 +5273,225 @@ var AllowedViewerResponseSchema = external_exports.object({
5204
5273
  });
5205
5274
  var ListAllowedViewersResponseSchema = external_exports.array(AllowedViewerResponseSchema);
5206
5275
 
5276
+ // ../../packages/shared/dist/schemas/api-error.schemas.js
5277
+ var API_ERROR_REASONS = [
5278
+ /**
5279
+ * No terminal outcome has been established. The write is owned by the settlement path and
5280
+ * may still succeed. **This is not a failure and not a success**, and it makes no claim about
5281
+ * whether the payment moved (ADR-0049 point 3). Never resubmit; poll `intents.get`.
5282
+ */
5283
+ "intent_in_flight",
5284
+ /**
5285
+ * A write carrying this idempotency key already exists, and this request neither charged the
5286
+ * caller nor submitted anything a second time. Idempotency working, reported as a conflict --
5287
+ * see ADR-0058 for why this is not a 200 carrying the original result.
5288
+ */
5289
+ "idempotency_key_reused",
5290
+ /**
5291
+ * The request arrived with no `X-Taskmarket-Idempotency-Key` header (the value of
5292
+ * `IDEMPOTENCY_KEY_HEADER`), or one that is not a UUID. Rejected before the 402 challenge,
5293
+ * so nothing was charged (ADR-0052).
5294
+ */
5295
+ "idempotency_key_required",
5296
+ /**
5297
+ * The key is already bound to a *different* operation, or to a different caller's write. Not
5298
+ * the benign repeat: nothing here can be waited on, and the caller must generate a fresh key.
5299
+ * Distinguished from `idempotency_key_reused` because the correct client response is opposite
5300
+ * -- poll there, resubmit under a new key here.
5301
+ */
5302
+ "idempotency_key_conflict",
5303
+ /**
5304
+ * The key names the *same* operation by the *same* caller, but with different arguments. One
5305
+ * level finer than `idempotency_key_conflict`, and a distinct reason because the caller's
5306
+ * mistake is distinct: they reused a key while changing what they were asking for, so the
5307
+ * write they just described never happened and the one the key already names is not it
5308
+ * (ADR-0061). Re-send the original arguments to retry the original write, or generate a fresh
5309
+ * key for the new one.
5310
+ */
5311
+ "idempotency_key_payload_mismatch",
5312
+ /**
5313
+ * The settled payment behind this request has already funded another intent. A retry that
5314
+ * failed to reuse its key, or a replayed payment. The money is spent and this write did not
5315
+ * happen; the caller reads the intent that did consume it from `intents.get`.
5316
+ */
5317
+ "payment_already_spent",
5318
+ /**
5319
+ * The idempotency precondition could not be read, so the request was refused rather than
5320
+ * risking a double charge on an unverified assumption. Safe to retry with the same key.
5321
+ */
5322
+ "idempotency_check_unavailable",
5323
+ /**
5324
+ * The chain call is confirmed and the work happened, but recording it did not finish. There
5325
+ * is nothing to refund and nothing to resubmit; the intent is retried automatically.
5326
+ */
5327
+ "intent_completion_deferred",
5328
+ /**
5329
+ * No intent answers to that id or key *for this caller*. Deliberately one reason for both "no
5330
+ * such intent" and "not yours": telling them apart would make this surface an oracle for which
5331
+ * intent ids and idempotency keys exist (ADR-0049 point 6).
5332
+ */
5333
+ "intent_not_found",
5334
+ /** The x402 exchange did not produce a settled payment. Nothing was charged. */
5335
+ "payment_rejected",
5336
+ /** A pre-settlement check on the request's own inputs or on task state rejected it. */
5337
+ "payment_preflight_rejected",
5338
+ /**
5339
+ * The settled payment came from an address other than the one the request is authenticated as
5340
+ * acting for. The write was refused; the payment had already settled when the mismatch was
5341
+ * seen, so the fee is not returned by this path.
5342
+ */
5343
+ "payment_payer_mismatch",
5344
+ /**
5345
+ * The error carries no more specific classification. Present so that every error has a
5346
+ * reason field and a client's switch is total; not a licence to leave one here.
5347
+ */
5348
+ "unclassified"
5349
+ ];
5350
+ var ApiErrorReasonSchema = external_exports.enum(API_ERROR_REASONS);
5351
+ var ApiErrorIntentStatusSchema = external_exports.enum([
5352
+ "reserved",
5353
+ "recorded",
5354
+ "broadcast",
5355
+ "completed",
5356
+ "failed"
5357
+ ]);
5358
+ var REASONS_WITHOUT_REQUIRED_INTENT_STATUS = API_ERROR_REASONS.filter((reason) => reason !== "idempotency_key_reused");
5359
+ var ENVELOPE_FIELDS = {
5360
+ /** The durable handle to the write, where one was recorded. Key on this, not on `txHash`. */
5361
+ intentId: external_exports.string().optional(),
5362
+ /** The operation the intent stands for, e.g. `tasks.create`. */
5363
+ operation: external_exports.string().optional(),
5364
+ /** The caller's own key for the operation, echoed so a dropped response is still traceable. */
5365
+ idempotencyKey: external_exports.string().optional(),
5366
+ /**
5367
+ * Known once something has been broadcast, and explicitly not the handle: the reconciler may
5368
+ * land a replacement at the same nonce, which is a different hash for the same intent.
5369
+ */
5370
+ txHash: external_exports.string().optional()
5371
+ };
5372
+ var ApiErrorEnvelopeSchema = external_exports.discriminatedUnion("reason", [
5373
+ external_exports.object({
5374
+ reason: external_exports.literal("idempotency_key_reused"),
5375
+ /** Required here: the reason means an intent under this key exists, so it has a status. */
5376
+ intentStatus: ApiErrorIntentStatusSchema,
5377
+ ...ENVELOPE_FIELDS
5378
+ }),
5379
+ external_exports.object({
5380
+ reason: external_exports.enum(REASONS_WITHOUT_REQUIRED_INTENT_STATUS),
5381
+ /** The intent's status at the moment this answer was produced, where there is an intent. */
5382
+ intentStatus: ApiErrorIntentStatusSchema.optional(),
5383
+ ...ENVELOPE_FIELDS
5384
+ })
5385
+ ]);
5386
+ var IN_FLIGHT_REASONS = /* @__PURE__ */ new Set([
5387
+ "intent_in_flight",
5388
+ "idempotency_key_reused",
5389
+ // Not "may still succeed" but "already succeeded, still being written down". Both are
5390
+ // answered the same way -- poll, never resubmit -- and reporting this one as settled told a
5391
+ // script following the documented rule that re-running it was an ordinary retry. It is not:
5392
+ // the chain call is confirmed, so a re-run is a second paid action for work already done.
5393
+ "intent_completion_deferred"
5394
+ ]);
5395
+ function isInFlightApiError(envelope) {
5396
+ if (!envelope || !IN_FLIGHT_REASONS.has(envelope.reason))
5397
+ return false;
5398
+ if (envelope.reason !== "idempotency_key_reused")
5399
+ return true;
5400
+ return envelope.intentStatus !== "completed" && envelope.intentStatus !== "failed";
5401
+ }
5402
+ function apiErrorEnvelopeOf(value) {
5403
+ if (!value || typeof value !== "object")
5404
+ return null;
5405
+ const at = (source, key) => source && typeof source === "object" ? source[key] : void 0;
5406
+ const candidate = at(value, "taskmarket") ?? at(at(value, "data"), "taskmarket") ?? at(at(at(value, "error"), "data"), "taskmarket") ?? value;
5407
+ const parsed = ApiErrorEnvelopeSchema.safeParse(candidate);
5408
+ return parsed.success ? parsed.data : null;
5409
+ }
5410
+
5411
+ // ../../packages/shared/dist/schemas/intent.schemas.js
5412
+ var IntentStatusInputSchema = external_exports.object({
5413
+ intentId: external_exports.string().min(1).optional(),
5414
+ idempotencyKey: external_exports.string().min(1).optional()
5415
+ }).refine((value) => Boolean(value.intentId) !== Boolean(value.idempotencyKey), {
5416
+ message: "Provide exactly one of intentId or idempotencyKey"
5417
+ });
5418
+ var IntentRefundStateSchema = external_exports.object({
5419
+ status: external_exports.enum(["pending", "refunding", "refunded", "failed"]),
5420
+ txHash: external_exports.string().nullable()
5421
+ });
5422
+ var IntentStatusResponseSchema = external_exports.object({
5423
+ intentId: external_exports.string(),
5424
+ idempotencyKey: external_exports.string(),
5425
+ /** The operation kind, e.g. `tasks.create`. */
5426
+ operation: external_exports.string(),
5427
+ /**
5428
+ * `reserved` is the pre-payment state (ADR-0067): the key is claimed and the caller has been
5429
+ * challenged, but no payment has landed and nothing has been sent to the chain. It is
5430
+ * non-terminal, it is never broadcastable, and it expires if it is never filled.
5431
+ */
5432
+ status: external_exports.enum(["reserved", "recorded", "broadcast", "completed", "failed"]),
5433
+ /**
5434
+ * Known once something has been broadcast, and explicitly not the handle: the reconciler
5435
+ * may land a replacement at the same nonce, which is a different hash for the same intent.
5436
+ */
5437
+ txHash: external_exports.string().nullable(),
5438
+ /** Why it failed, where it did. Null on every non-terminal status. */
5439
+ terminalReason: external_exports.string().nullable(),
5440
+ /**
5441
+ * The task a completed `tasks.create` produced.
5442
+ *
5443
+ * This is where a caller whose creation went in flight learns the id. The id does not
5444
+ * exist until the transaction runs -- the contract derives it from a requester nonce it
5445
+ * increments as it goes -- so an in-flight creation has no id to report, and reporting one
5446
+ * would be a guess of the kind that made concurrent creates overwrite each other. Null
5447
+ * until the intent completes, and null for every other operation.
5448
+ */
5449
+ taskId: external_exports.string().nullable(),
5450
+ /**
5451
+ * Only present where the intent carried a payment and that payment has been written off.
5452
+ * Null means no refund has been decided -- which is not the same as "no refund is coming"
5453
+ * while the intent is still in flight.
5454
+ */
5455
+ refund: IntentRefundStateSchema.nullable()
5456
+ }).superRefine((value, ctx) => {
5457
+ if (value.status === "reserved") {
5458
+ for (const field of ["txHash", "terminalReason", "taskId"]) {
5459
+ if (value[field] !== null) {
5460
+ ctx.addIssue({
5461
+ code: external_exports.ZodIssueCode.custom,
5462
+ path: [field],
5463
+ message: `A reserved intent has not been sent to the chain, so ${field} must be null`
5464
+ });
5465
+ }
5466
+ }
5467
+ return;
5468
+ }
5469
+ if (value.terminalReason !== null && value.status !== "failed") {
5470
+ ctx.addIssue({
5471
+ code: external_exports.ZodIssueCode.custom,
5472
+ path: ["terminalReason"],
5473
+ message: "Only a failed intent has a terminal reason"
5474
+ });
5475
+ }
5476
+ if (value.taskId === null)
5477
+ return;
5478
+ if (value.status !== "completed") {
5479
+ ctx.addIssue({
5480
+ code: external_exports.ZodIssueCode.custom,
5481
+ path: ["taskId"],
5482
+ message: "taskId is only known once the intent has completed"
5483
+ });
5484
+ return;
5485
+ }
5486
+ if (value.operation !== "tasks.create") {
5487
+ ctx.addIssue({
5488
+ code: external_exports.ZodIssueCode.custom,
5489
+ path: ["taskId"],
5490
+ message: "Only a tasks.create intent produces a taskId"
5491
+ });
5492
+ }
5493
+ });
5494
+
5207
5495
  // ../../packages/shared/dist/platform.constants.js
5208
5496
  var USDC_DECIMALS = 6;
5209
5497
 
@@ -5508,6 +5796,7 @@ function buildReadAuthMessage(address) {
5508
5796
  var READ_AUTH_ADDRESS_HEADER = "X-Taskmarket-Caller-Address";
5509
5797
  var READ_AUTH_SIGNATURE_HEADER = "X-Taskmarket-Caller-Signature";
5510
5798
  var TASK_ACCESS_GRANT_HEADER = "X-Taskmarket-Task-Access-Grant";
5799
+ var IDEMPOTENCY_KEY_HEADER = "X-Taskmarket-Idempotency-Key";
5511
5800
  function buildSubmitMessage(taskId, contentBindings) {
5512
5801
  return contentBindings ? `taskmarket:submit:${taskId}:${contentBindings.join(",")}` : `taskmarket:submit:${taskId}`;
5513
5802
  }
@@ -6035,9 +6324,103 @@ async function keystoreExists(keystorePath) {
6035
6324
  }
6036
6325
  }
6037
6326
 
6327
+ // src/lib/idempotency.ts
6328
+ import { randomUUID } from "crypto";
6329
+ var pendingEnvKey = process.env.TASKMARKET_IDEMPOTENCY_KEY?.trim() || void 0;
6330
+ var keyByError = /* @__PURE__ */ new WeakMap();
6331
+ function rememberIdempotencyKeyFor(error, key) {
6332
+ if (error === null || typeof error !== "object" && typeof error !== "function") return;
6333
+ if (keyByError.has(error)) return;
6334
+ keyByError.set(error, key);
6335
+ }
6336
+ function idempotencyKeyForError(error) {
6337
+ if (error === null || typeof error !== "object" && typeof error !== "function")
6338
+ return void 0;
6339
+ return keyByError.get(error);
6340
+ }
6341
+ function newIdempotencyKey() {
6342
+ return randomUUID();
6343
+ }
6344
+ function resolveIdempotencyKey(explicit) {
6345
+ if (explicit !== void 0) {
6346
+ return explicit;
6347
+ }
6348
+ if (pendingEnvKey !== void 0) {
6349
+ const fromEnv = pendingEnvKey;
6350
+ pendingEnvKey = void 0;
6351
+ return fromEnv;
6352
+ }
6353
+ return newIdempotencyKey();
6354
+ }
6355
+ async function withIdempotentWrite(key, fn) {
6356
+ try {
6357
+ return await fn();
6358
+ } catch (error) {
6359
+ rememberIdempotencyKeyFor(error, key);
6360
+ throw error;
6361
+ }
6362
+ }
6363
+ function idempotencyHeaders(key) {
6364
+ return { [IDEMPOTENCY_KEY_HEADER]: key };
6365
+ }
6366
+
6038
6367
  // src/lib/api.ts
6039
6368
  var API_URL = process.env.TASKMARKET_API_URL ?? "https://api.taskmarket.dev";
6040
6369
  var API_ORIGIN = new URL(API_URL).origin;
6370
+ var ApiError = class extends Error {
6371
+ status;
6372
+ /**
6373
+ * The idempotency key the failed write was sent under, when the request was a write. This is
6374
+ * the only identifier the caller can still hold after a failure -- the intent id is minted by
6375
+ * the backend and reaches the caller only in the response the failure destroyed -- so it
6376
+ * travels on the error rather than being recoverable from the response. See
6377
+ * apps/cli/src/index.ts and lib/output.ts, which surface it on the CLI's JSON envelope.
6378
+ */
6379
+ idempotencyKey;
6380
+ /**
6381
+ * The backend's machine-readable classification of the failure (ADR-0058).
6382
+ *
6383
+ * Before it existed the CLI rendered every failure identically, which is why
6384
+ * `docs/CLI_GUIDE.md` had to tell script authors never to auto-retry a paid command: there was
6385
+ * no way to tell a write that is still landing from one that was definitively rejected, and
6386
+ * retrying the first is a second payment. `reason` is that way.
6387
+ *
6388
+ * Absent when the backend answered with no envelope -- an older deployment, or a failure that
6389
+ * never reached the API at all -- in which case a caller must fall back to the old rule and
6390
+ * treat the outcome as unknown rather than as safe to retry.
6391
+ */
6392
+ envelope;
6393
+ constructor(status, message, idempotencyKey, envelope) {
6394
+ super(message);
6395
+ this.name = "ApiError";
6396
+ this.status = status;
6397
+ this.idempotencyKey = idempotencyKey;
6398
+ this.envelope = envelope;
6399
+ }
6400
+ };
6401
+ async function readFailureBody(res) {
6402
+ const text = await res.text().catch(() => "");
6403
+ try {
6404
+ return { body: JSON.parse(text), text };
6405
+ } catch {
6406
+ return { body: void 0, text };
6407
+ }
6408
+ }
6409
+ function failureMessage(prefix, status, read) {
6410
+ return `${prefix} (${status}): ${read.body !== void 0 ? JSON.stringify(read.body) : read.text}`;
6411
+ }
6412
+ function withErrorContext(error, context) {
6413
+ const message = error instanceof Error ? error.message : String(error);
6414
+ const wrapped = error instanceof ApiError ? new ApiError(error.status, `${context}: ${message}`, error.idempotencyKey, error.envelope) : new Error(`${context}: ${message}`);
6415
+ const key = idempotencyKeyForError(error);
6416
+ if (key !== void 0) {
6417
+ rememberIdempotencyKeyFor(wrapped, key);
6418
+ }
6419
+ return wrapped;
6420
+ }
6421
+ function isPendingApiError(error) {
6422
+ return error instanceof ApiError && isInFlightApiError(error.envelope);
6423
+ }
6041
6424
  function legalReceiptHeadersForKeystore(keystore, path4, method) {
6042
6425
  const canSend = keystore.legalAcceptanceApiOrigin === API_ORIGIN && (method === "POST" || path4 === "/api/legal/status");
6043
6426
  return canSend ? buildLegalReceiptHeaders(keystore.legalAcceptanceReceipt) : {};
@@ -6061,55 +6444,100 @@ async function apiGet(path4, options) {
6061
6444
  ...options?.headers ?? {}
6062
6445
  }
6063
6446
  });
6064
- const body = await res.json();
6065
6447
  if (!res.ok) {
6066
- throw new Error(`GET ${path4} failed (${res.status}): ${JSON.stringify(body)}`);
6448
+ const read = await readFailureBody(res);
6449
+ throw new ApiError(
6450
+ res.status,
6451
+ failureMessage(`GET ${path4} failed`, res.status, read),
6452
+ void 0,
6453
+ apiErrorEnvelopeOf(read.body) ?? void 0
6454
+ );
6067
6455
  }
6068
- return body;
6456
+ return res.json();
6069
6457
  }
6070
6458
  async function apiPost(path4, body, options) {
6071
6459
  const legalHeaders = await legalReceiptHeaders(path4, "POST");
6072
- const res = await fetch(`${API_URL}${path4}`, {
6073
- method: "POST",
6074
- redirect: "error",
6075
- headers: {
6076
- "Content-Type": "application/json",
6077
- ...legalHeaders,
6078
- ...options?.headers ?? {}
6079
- },
6080
- body: JSON.stringify(body)
6460
+ const idempotencyKey = resolveIdempotencyKey(options?.idempotencyKey);
6461
+ return withIdempotentWrite(idempotencyKey, async () => {
6462
+ const res = await fetch(`${API_URL}${path4}`, {
6463
+ method: "POST",
6464
+ redirect: "error",
6465
+ headers: {
6466
+ "Content-Type": "application/json",
6467
+ ...legalHeaders,
6468
+ ...idempotencyHeaders(idempotencyKey),
6469
+ ...options?.headers ?? {}
6470
+ },
6471
+ body: JSON.stringify(body)
6472
+ });
6473
+ if (!res.ok) {
6474
+ const read = await readFailureBody(res);
6475
+ throw new ApiError(
6476
+ res.status,
6477
+ failureMessage(`POST ${path4} failed`, res.status, read),
6478
+ idempotencyKey,
6479
+ apiErrorEnvelopeOf(read.body) ?? void 0
6480
+ );
6481
+ }
6482
+ return { data: await res.json(), idempotencyKey };
6081
6483
  });
6082
- const result = await res.json();
6083
- if (!res.ok) {
6084
- throw new Error(`POST ${path4} failed (${res.status}): ${JSON.stringify(result)}`);
6085
- }
6086
- return result;
6087
6484
  }
6088
6485
  async function apiDelete(path4, options) {
6089
6486
  const legalHeaders = await legalReceiptHeaders(path4, "POST");
6090
- const res = await fetch(`${API_URL}${path4}`, {
6091
- method: "DELETE",
6092
- redirect: "error",
6093
- headers: {
6094
- "Content-Type": "application/json",
6095
- ...legalHeaders,
6096
- ...options?.headers ?? {}
6487
+ const idempotencyKey = resolveIdempotencyKey(options?.idempotencyKey);
6488
+ return withIdempotentWrite(idempotencyKey, async () => {
6489
+ const res = await fetch(`${API_URL}${path4}`, {
6490
+ method: "DELETE",
6491
+ redirect: "error",
6492
+ headers: {
6493
+ "Content-Type": "application/json",
6494
+ ...legalHeaders,
6495
+ ...idempotencyHeaders(idempotencyKey),
6496
+ ...options?.headers ?? {}
6497
+ }
6498
+ });
6499
+ if (!res.ok) {
6500
+ const read = await readFailureBody(res);
6501
+ throw new ApiError(
6502
+ res.status,
6503
+ failureMessage(`DELETE ${path4} failed`, res.status, read),
6504
+ idempotencyKey,
6505
+ apiErrorEnvelopeOf(read.body) ?? void 0
6506
+ );
6097
6507
  }
6508
+ return { data: await res.json(), idempotencyKey };
6098
6509
  });
6099
- const result = await res.json();
6100
- if (!res.ok) {
6101
- throw new Error(`DELETE ${path4} failed (${res.status}): ${JSON.stringify(result)}`);
6102
- }
6103
- return result;
6104
6510
  }
6105
6511
 
6106
6512
  // src/lib/output.ts
6107
- function printResult(data) {
6108
- console.log(JSON.stringify({ ok: true, data }));
6513
+ function printResult(data, options) {
6514
+ const idempotencyKey = options?.idempotencyKey;
6515
+ console.log(
6516
+ JSON.stringify({ ok: true, data, ...idempotencyKey !== void 0 ? { idempotencyKey } : {} })
6517
+ );
6518
+ }
6519
+ function renderFailure(error, options) {
6520
+ const message = error instanceof Error ? error.message : options?.fallback ?? String(error);
6521
+ const apiError = error instanceof ApiError ? error : void 0;
6522
+ const idempotencyKey = options?.idempotencyKey ?? apiError?.idempotencyKey ?? idempotencyKeyForError(error);
6523
+ const envelope = apiError?.envelope;
6524
+ process.stderr.write(
6525
+ JSON.stringify({
6526
+ ok: false,
6527
+ error: message,
6528
+ ...apiError !== void 0 ? { status: apiError.status } : {},
6529
+ ...idempotencyKey !== void 0 ? { idempotencyKey } : {},
6530
+ ...envelope !== void 0 ? { ...envelope, pending: isInFlightApiError(envelope) } : {},
6531
+ ...options?.details ?? {}
6532
+ }) + "\n"
6533
+ );
6534
+ process.exitCode = 1;
6109
6535
  }
6110
- function printError(message) {
6536
+ function printError(message, options) {
6537
+ return renderFailure(new Error(message), options);
6538
+ }
6539
+ function printWarning(message) {
6111
6540
  process.stderr.write(JSON.stringify({ ok: false, error: message }) + "\n");
6112
- process.exit(1);
6113
6541
  }
6114
6542
 
6115
6543
  // src/lib/agent.ts
@@ -6358,21 +6786,21 @@ async function acceptLegalBundle(options) {
6358
6786
  }
6359
6787
  await confirmAcceptance(bundle, options.assumeYes);
6360
6788
  const challenge = LegalChallengeResponseSchema.parse(
6361
- await apiPost("/api/legal/challenge", {
6789
+ (await apiPost("/api/legal/challenge", {
6362
6790
  walletAddress: options.walletAddress
6363
- })
6791
+ })).data
6364
6792
  );
6365
6793
  validateLegalChallenge(bundle, challenge, options.walletAddress);
6366
6794
  const signature = await options.signMessage(challenge.message);
6367
6795
  const result = LegalAcceptanceResponseSchema.parse(
6368
- await apiPost("/api/legal/accept/wallet", {
6796
+ (await apiPost("/api/legal/accept/wallet", {
6369
6797
  ...affirmations,
6370
6798
  bundleDigest: bundle.bundleDigest,
6371
6799
  bundleVersion: bundle.version,
6372
6800
  nonce: challenge.nonce,
6373
6801
  signature,
6374
6802
  walletAddress: options.walletAddress
6375
- })
6803
+ })).data
6376
6804
  );
6377
6805
  if (result.bundleDigest !== bundle.bundleDigest || result.bundleVersion !== bundle.version) {
6378
6806
  throw new Error("The server returned a receipt for a different legal bundle.");
@@ -6444,7 +6872,7 @@ function sanitizeEmailUsername(agentId, suffix) {
6444
6872
  }
6445
6873
  async function tryRegisterEmail(deviceId, apiToken, username, explicit) {
6446
6874
  try {
6447
- const reg = await apiPost("/api/emails/register", {
6875
+ const { data: reg } = await apiPost("/api/emails/register", {
6448
6876
  deviceId,
6449
6877
  apiToken,
6450
6878
  username
@@ -6453,11 +6881,8 @@ async function tryRegisterEmail(deviceId, apiToken, username, explicit) {
6453
6881
  } catch (err) {
6454
6882
  if (explicit) {
6455
6883
  const msg = err instanceof Error ? err.message : String(err);
6456
- process.stderr.write(
6457
- JSON.stringify({
6458
- ok: false,
6459
- error: `Email registration failed: ${msg}. Run: taskmarket email register --username ${username}`
6460
- }) + "\n"
6884
+ printWarning(
6885
+ `Email registration failed: ${msg}. Run: taskmarket email register --username ${username}`
6461
6886
  );
6462
6887
  }
6463
6888
  return null;
@@ -6476,6 +6901,7 @@ var initCommand = new Command2("init").description("Create and register a new ag
6476
6901
  );
6477
6902
  if (!check.available) {
6478
6903
  printError(`Email username "${opts.email}" is not available.`);
6904
+ return;
6479
6905
  }
6480
6906
  }
6481
6907
  if (await keystoreExists()) {
@@ -6592,65 +7018,80 @@ var addressCommand = new Command3("address").description("Print the wallet addre
6592
7018
  import { Command as Command4 } from "commander";
6593
7019
 
6594
7020
  // src/lib/x402.ts
6595
- async function x402Post(path4, body) {
7021
+ async function x402Post(path4, body, options) {
6596
7022
  const url = `${API_URL}${path4}`;
6597
7023
  const keystore = await loadKeystore();
6598
7024
  const legalHeaders = legalReceiptHeadersForKeystore(keystore, path4, "POST");
6599
- const r1 = await fetch(url, {
6600
- method: "POST",
6601
- redirect: "error",
6602
- headers: { "Content-Type": "application/json", ...legalHeaders },
6603
- body: JSON.stringify(body)
6604
- });
6605
- if (r1.status !== 402) {
6606
- if (r1.ok) {
6607
- return r1.json();
6608
- }
6609
- const text = await r1.text().catch(() => "");
6610
- throw new Error(`POST ${path4} failed (${r1.status}): ${text}`);
6611
- }
6612
- const requirements = await r1.json();
6613
- if (!requirements.accepts || requirements.accepts.length === 0) {
6614
- throw new Error("No payment methods accepted by server");
6615
- }
6616
- const accept = requirements.accepts[0];
6617
- const { authorization, signature } = await createTransferAuthorization(accept, keystore);
6618
- const paymentPayload = {
6619
- x402Version: 2,
6620
- resource: requirements.resource,
6621
- accepted: {
6622
- scheme: accept.scheme,
6623
- network: accept.network,
6624
- amount: accept.amount,
6625
- asset: accept.asset,
6626
- payTo: accept.payTo,
6627
- maxTimeoutSeconds: accept.maxTimeoutSeconds,
6628
- extra: accept.extra
6629
- },
6630
- payload: { authorization, signature }
6631
- };
6632
- const r2 = await fetch(url, {
6633
- method: "POST",
6634
- redirect: "error",
6635
- headers: {
6636
- "Content-Type": "application/json",
6637
- ...legalHeaders,
6638
- "PAYMENT-SIGNATURE": Buffer.from(JSON.stringify(paymentPayload)).toString("base64")
6639
- },
6640
- body: JSON.stringify(body)
7025
+ const idempotencyKey = resolveIdempotencyKey(options?.idempotencyKey);
7026
+ const idempotency = idempotencyHeaders(idempotencyKey);
7027
+ return withIdempotentWrite(idempotencyKey, async () => {
7028
+ const r1 = await fetch(url, {
7029
+ method: "POST",
7030
+ redirect: "error",
7031
+ headers: { "Content-Type": "application/json", ...legalHeaders, ...idempotency },
7032
+ body: JSON.stringify(body)
7033
+ });
7034
+ if (r1.status !== 402) {
7035
+ if (r1.ok) {
7036
+ return { data: await r1.json(), idempotencyKey };
7037
+ }
7038
+ const read = await readFailureBody(r1);
7039
+ throw new ApiError(
7040
+ r1.status,
7041
+ `POST ${path4} failed (${r1.status}): ${read.text}`,
7042
+ idempotencyKey,
7043
+ apiErrorEnvelopeOf(read.body) ?? void 0
7044
+ );
7045
+ }
7046
+ const requirements = await r1.json();
7047
+ if (!requirements.accepts || requirements.accepts.length === 0) {
7048
+ throw new Error("No payment methods accepted by server");
7049
+ }
7050
+ const accept = requirements.accepts[0];
7051
+ const { authorization, signature } = await createTransferAuthorization(accept, keystore);
7052
+ const paymentPayload = {
7053
+ x402Version: 2,
7054
+ resource: requirements.resource,
7055
+ accepted: {
7056
+ scheme: accept.scheme,
7057
+ network: accept.network,
7058
+ amount: accept.amount,
7059
+ asset: accept.asset,
7060
+ payTo: accept.payTo,
7061
+ maxTimeoutSeconds: accept.maxTimeoutSeconds,
7062
+ extra: accept.extra
7063
+ },
7064
+ payload: { authorization, signature }
7065
+ };
7066
+ const r2 = await fetch(url, {
7067
+ method: "POST",
7068
+ redirect: "error",
7069
+ headers: {
7070
+ "Content-Type": "application/json",
7071
+ ...legalHeaders,
7072
+ ...idempotency,
7073
+ "PAYMENT-SIGNATURE": Buffer.from(JSON.stringify(paymentPayload)).toString("base64")
7074
+ },
7075
+ body: JSON.stringify(body)
7076
+ });
7077
+ if (!r2.ok) {
7078
+ const read = await readFailureBody(r2);
7079
+ throw new ApiError(
7080
+ r2.status,
7081
+ failureMessage(`POST ${path4} failed after payment`, r2.status, read),
7082
+ idempotencyKey,
7083
+ apiErrorEnvelopeOf(read.body) ?? void 0
7084
+ );
7085
+ }
7086
+ return { data: await r2.json(), idempotencyKey };
6641
7087
  });
6642
- const result = await r2.json();
6643
- if (!r2.ok) {
6644
- throw new Error(`POST ${path4} failed after payment (${r2.status}): ${JSON.stringify(result)}`);
6645
- }
6646
- return result;
6647
7088
  }
6648
7089
 
6649
7090
  // src/commands/identity.ts
6650
7091
  var identityCommand = new Command4("identity").description("Manage agent identity");
6651
7092
  identityCommand.command("register").description("Register ERC-8004 agent identity (costs 0.001 USDC)").action(async () => {
6652
- const result = await x402Post("/api/identity/register", {});
6653
- printResult({ agentId: result.agentId });
7093
+ const { data: result, idempotencyKey } = await x402Post("/api/identity/register", {});
7094
+ printResult({ agentId: result.agentId }, { idempotencyKey });
6654
7095
  });
6655
7096
  identityCommand.command("status").description("Check identity registration status").action(async () => {
6656
7097
  const keystore = await loadKeystore();
@@ -6706,7 +7147,7 @@ var statsCommand = new Command5("stats").description("View agent statistics").op
6706
7147
  });
6707
7148
 
6708
7149
  // src/commands/task/index.ts
6709
- import { Command as Command40 } from "commander";
7150
+ import { Command as Command41 } from "commander";
6710
7151
 
6711
7152
  // src/commands/task/create.ts
6712
7153
  import { Command as Command6 } from "commander";
@@ -6742,9 +7183,8 @@ var createCmd = new Command6("create").description("Create a new task (costs rew
6742
7183
  try {
6743
7184
  rewardBaseUnits = usdcToBaseUnits(opts.reward);
6744
7185
  } catch (err) {
6745
- return void printError(
6746
- `Invalid --reward: ${err instanceof Error ? err.message : String(err)}`
6747
- );
7186
+ renderFailure(withErrorContext(err, "Invalid --reward"));
7187
+ return;
6748
7188
  }
6749
7189
  const duration = Number(opts.duration);
6750
7190
  if (!Number.isFinite(duration) || duration <= 0) {
@@ -6804,9 +7244,8 @@ var createCmd = new Command6("create").description("Create a new task (costs rew
6804
7244
  try {
6805
7245
  maxPriceBaseUnits = usdcToBaseUnits(opts.maxPrice);
6806
7246
  } catch (err) {
6807
- return void printError(
6808
- `Invalid --max-price: ${err instanceof Error ? err.message : String(err)}`
6809
- );
7247
+ renderFailure(withErrorContext(err, "Invalid --max-price"));
7248
+ return;
6810
7249
  }
6811
7250
  if (maxPriceBaseUnits !== rewardBaseUnits) {
6812
7251
  return void printError(
@@ -6848,18 +7287,16 @@ var createCmd = new Command6("create").description("Create a new task (costs rew
6848
7287
  try {
6849
7288
  body.auctionStartPrice = usdcToBaseUnits(opts.auctionStartPrice, { allowZero: true });
6850
7289
  } catch (err) {
6851
- return void printError(
6852
- `Invalid --auction-start-price: ${err instanceof Error ? err.message : String(err)}`
6853
- );
7290
+ renderFailure(withErrorContext(err, "Invalid --auction-start-price"));
7291
+ return;
6854
7292
  }
6855
7293
  }
6856
7294
  if (opts.auctionFloorPrice) {
6857
7295
  try {
6858
7296
  body.auctionFloorPrice = usdcToBaseUnits(opts.auctionFloorPrice, { allowZero: true });
6859
7297
  } catch (err) {
6860
- return void printError(
6861
- `Invalid --auction-floor-price: ${err instanceof Error ? err.message : String(err)}`
6862
- );
7298
+ renderFailure(withErrorContext(err, "Invalid --auction-floor-price"));
7299
+ return;
6863
7300
  }
6864
7301
  }
6865
7302
  for (const [flag, value] of [
@@ -6907,8 +7344,11 @@ var createCmd = new Command6("create").description("Create a new task (costs rew
6907
7344
  if (opts.appealWindow) body.appealWindowHours = parseFloat(opts.appealWindow);
6908
7345
  if (opts.disputeResolver) body.disputeResolver = opts.disputeResolver;
6909
7346
  }
6910
- const result = await x402Post("/api/tasks", body);
6911
- printResult({ taskId: result.taskId });
7347
+ const { data: result, idempotencyKey } = await x402Post(
7348
+ "/api/tasks",
7349
+ body
7350
+ );
7351
+ printResult({ taskId: result.taskId }, { idempotencyKey });
6912
7352
  }
6913
7353
  );
6914
7354
 
@@ -7019,6 +7459,7 @@ var getCmd = new Command8("get").description("Get task details").argument("<task
7019
7459
  printError(
7020
7460
  `Task not found: ${taskId} (or this is a private task you don't have access to -- if you have a password for it, run: taskmarket task unlock ${taskId} --password <password>)`
7021
7461
  );
7462
+ return;
7022
7463
  }
7023
7464
  printResult(task);
7024
7465
  });
@@ -7119,6 +7560,7 @@ var submitCmd = new Command9("submit").description("Submit work for a task").arg
7119
7560
  try {
7120
7561
  if (!opts.file || opts.file.length === 0) {
7121
7562
  printError("--file is required");
7563
+ return;
7122
7564
  }
7123
7565
  const keystore = await loadKeystore();
7124
7566
  const signature = await signMessage(buildSubmitMessage(taskId), keystore);
@@ -7127,9 +7569,11 @@ var submitCmd = new Command9("submit").description("Submit work for a task").arg
7127
7569
  const mimeType = mimeTypeForPath(filePath);
7128
7570
  const data = await fsPromises.readFile(filePath);
7129
7571
  if (data.length === 0) {
7130
- printError(`File is empty: ${basename(filePath)}`);
7572
+ throw new Error(`File is empty: ${basename(filePath)}`);
7131
7573
  }
7132
- const { uploadUrl, artifactKey } = await apiPost(
7574
+ const {
7575
+ data: { uploadUrl, artifactKey }
7576
+ } = await apiPost(
7133
7577
  `/api/tasks/${taskId}/submissions/request-upload-url`,
7134
7578
  {
7135
7579
  taskId,
@@ -7164,13 +7608,16 @@ var submitCmd = new Command9("submit").description("Submit work for a task").arg
7164
7608
  ),
7165
7609
  keystore
7166
7610
  );
7167
- const result = await apiPost(`/api/tasks/${taskId}/submissions/from-keys`, {
7168
- taskId,
7169
- workerAddress: keystore.walletAddress,
7170
- artifacts: artifactInputs,
7171
- signature: submitSignature
7172
- });
7173
- printResult({ submissionId: result.submissionId });
7611
+ const { data: result, idempotencyKey } = await x402Post(
7612
+ `/api/tasks/${taskId}/submissions/from-keys`,
7613
+ {
7614
+ taskId,
7615
+ workerAddress: keystore.walletAddress,
7616
+ artifacts: artifactInputs,
7617
+ signature: submitSignature
7618
+ }
7619
+ );
7620
+ printResult({ submissionId: result.submissionId }, { idempotencyKey });
7174
7621
  } finally {
7175
7622
  submitCmd.setOptionValue("file", []);
7176
7623
  }
@@ -7182,7 +7629,6 @@ var acceptCmd = new Command10("accept").description("Accept a submission (costs
7182
7629
  const task = await apiGet(`/api/tasks/${taskId}`);
7183
7630
  if (!task) {
7184
7631
  printError("Task not found");
7185
- process.exit(1);
7186
7632
  return;
7187
7633
  }
7188
7634
  const canAccept = task.pendingActions?.some(
@@ -7198,14 +7644,13 @@ var acceptCmd = new Command10("accept").description("Accept a submission (costs
7198
7644
  printError(
7199
7645
  hasRefund ? "Task has expired with no submissions. Use taskmarket task refund-expired to recover escrow." : hasSelectWorker ? "Pitch window has closed. Use taskmarket task select-worker to pick from received pitches, or cancel the task." : "Accept is not available for this task in its current state."
7200
7646
  );
7201
- process.exit(1);
7202
7647
  return;
7203
7648
  }
7204
- await x402Post(`/api/tasks/${taskId}/accept`, {
7649
+ const { idempotencyKey } = await x402Post(`/api/tasks/${taskId}/accept`, {
7205
7650
  taskId,
7206
7651
  worker: opts.worker
7207
7652
  });
7208
- printResult({ accepted: true });
7653
+ printResult({ accepted: true }, { idempotencyKey });
7209
7654
  });
7210
7655
 
7211
7656
  // src/commands/task/accept-submissions.ts
@@ -7240,13 +7685,16 @@ var acceptSubmissionsCmd = new Command11("accept-submissions").description(
7240
7685
  if (sum !== 1e4) {
7241
7686
  throw new Error(`Winner shares must sum to 10000 basis points (got ${sum})`);
7242
7687
  }
7243
- const result = await x402Post(`/api/tasks/${taskId}/accept-submissions`, {
7244
- taskId,
7245
- winners
7246
- });
7247
- printResult({ accepted: result.success, winners: winners.length });
7688
+ const { data: result, idempotencyKey } = await x402Post(
7689
+ `/api/tasks/${taskId}/accept-submissions`,
7690
+ {
7691
+ taskId,
7692
+ winners
7693
+ }
7694
+ );
7695
+ printResult({ accepted: result.success, winners: winners.length }, { idempotencyKey });
7248
7696
  } catch (err) {
7249
- printError(err instanceof Error ? err.message : String(err));
7697
+ renderFailure(err);
7250
7698
  }
7251
7699
  });
7252
7700
 
@@ -7256,14 +7704,15 @@ var rateCmd = new Command12("rate").description("Rate a worker (costs 0.001 USDC
7256
7704
  const rating = parseInt(opts.rating, 10);
7257
7705
  if (rating < 0 || rating > 100) {
7258
7706
  printError("Rating must be between 0 and 100");
7707
+ return;
7259
7708
  }
7260
- const result = await x402Post(`/api/tasks/${taskId}/rate`, {
7709
+ const { data: result, idempotencyKey } = await x402Post(`/api/tasks/${taskId}/rate`, {
7261
7710
  taskId,
7262
7711
  worker: opts.worker,
7263
7712
  rating,
7264
7713
  ...opts.feedback ? { feedbackText: opts.feedback } : {}
7265
7714
  });
7266
- printResult({ feedbackId: result.feedbackId });
7715
+ printResult({ feedbackId: result.feedbackId }, { idempotencyKey });
7267
7716
  });
7268
7717
 
7269
7718
  // src/commands/task/claim.ts
@@ -7272,11 +7721,14 @@ var claimCmd = new Command13("claim").description("Claim a task as a worker").ar
7272
7721
  const keystore = await loadKeystore();
7273
7722
  const message = buildClaimMessage(taskId);
7274
7723
  const signature = await signMessage(message, keystore);
7275
- const result = await apiPost(`/api/tasks/${taskId}/claim`, {
7276
- workerAddress: keystore.walletAddress,
7277
- signature
7278
- });
7279
- printResult({ claimId: result.claimId });
7724
+ const { data: result, idempotencyKey } = await apiPost(
7725
+ `/api/tasks/${taskId}/claim`,
7726
+ {
7727
+ workerAddress: keystore.walletAddress,
7728
+ signature
7729
+ }
7730
+ );
7731
+ printResult({ claimId: result.claimId }, { idempotencyKey });
7280
7732
  });
7281
7733
 
7282
7734
  // src/commands/task/pitch.ts
@@ -7284,14 +7736,17 @@ import { Command as Command14 } from "commander";
7284
7736
  var pitchCmd = new Command14("pitch").description("Submit a pitch for a task (costs 0.001 USDC, anchors hash on-chain)").argument("<taskId>", "Task ID (0x-prefixed hex)").requiredOption("--text <text>", "Pitch text").option("--duration <hours>", "Estimated duration in hours").action(async (taskId, opts) => {
7285
7737
  const keystore = await loadKeystore();
7286
7738
  const signature = await signMessage(`taskmarket:pitch:${taskId}`, keystore);
7287
- const result = await x402Post(`/api/tasks/${taskId}/pitches`, {
7288
- taskId,
7289
- workerAddress: keystore.walletAddress,
7290
- pitchText: opts.text,
7291
- ...opts.duration ? { estimatedDuration: parseInt(opts.duration, 10) } : {},
7292
- signature
7293
- });
7294
- printResult({ pitchId: result.pitchId });
7739
+ const { data: result, idempotencyKey } = await x402Post(
7740
+ `/api/tasks/${taskId}/pitches`,
7741
+ {
7742
+ taskId,
7743
+ workerAddress: keystore.walletAddress,
7744
+ pitchText: opts.text,
7745
+ ...opts.duration ? { estimatedDuration: parseInt(opts.duration, 10) } : {},
7746
+ signature
7747
+ }
7748
+ );
7749
+ printResult({ pitchId: result.pitchId }, { idempotencyKey });
7295
7750
  });
7296
7751
 
7297
7752
  // src/commands/task/pitches.ts
@@ -7302,7 +7757,7 @@ var pitchesCmd = new Command15("pitches").description("List pitches for a task")
7302
7757
  const headers = { ...auth?.headers ?? {}, ...await taskAccessGrantHeaders(taskId) };
7303
7758
  printResult(await apiGet(`/api/tasks/${taskId}/pitches`, { headers }));
7304
7759
  } catch (err) {
7305
- printError(err instanceof Error ? err.message : String(err));
7760
+ renderFailure(err);
7306
7761
  }
7307
7762
  });
7308
7763
 
@@ -7312,13 +7767,13 @@ var selectWorkerCmd = new Command16("select-worker").description("Select a worke
7312
7767
  const keystore = await loadKeystore();
7313
7768
  const message = buildSelectWorkerMessage(taskId, opts.pitch, opts.worker);
7314
7769
  const signature = await signMessage(message, keystore);
7315
- await apiPost(`/api/tasks/${taskId}/pitches/select`, {
7770
+ const { idempotencyKey } = await apiPost(`/api/tasks/${taskId}/pitches/select`, {
7316
7771
  taskId,
7317
7772
  pitchId: opts.pitch,
7318
7773
  workerAddress: opts.worker,
7319
7774
  signature
7320
7775
  });
7321
- printResult({ selected: true });
7776
+ printResult({ selected: true }, { idempotencyKey });
7322
7777
  });
7323
7778
 
7324
7779
  // src/commands/task/proof.ts
@@ -7326,7 +7781,7 @@ import { Command as Command17 } from "commander";
7326
7781
  var proofCmd = new Command17("proof").description("Submit a benchmark proof (costs 0.001 USDC, anchors hash on-chain)").argument("<taskId>", "Task ID (0x-prefixed hex)").requiredOption("--data <data>", "Proof data").requiredOption("--type <type>", "Proof type").option("--metric <val>", "Metric value").action(async (taskId, opts) => {
7327
7782
  const keystore = await loadKeystore();
7328
7783
  const signature = await signMessage(`taskmarket:proof:${taskId}`, keystore);
7329
- const result = await x402Post(`/api/tasks/${taskId}/proofs`, {
7784
+ const { data: result, idempotencyKey } = await x402Post(`/api/tasks/${taskId}/proofs`, {
7330
7785
  taskId,
7331
7786
  workerAddress: keystore.walletAddress,
7332
7787
  proofData: opts.data,
@@ -7334,7 +7789,7 @@ var proofCmd = new Command17("proof").description("Submit a benchmark proof (cos
7334
7789
  ...opts.metric ? { metricValue: opts.metric } : {},
7335
7790
  signature
7336
7791
  });
7337
- printResult({ proofId: result.proofId, submissionId: result.submissionId });
7792
+ printResult({ proofId: result.proofId, submissionId: result.submissionId }, { idempotencyKey });
7338
7793
  });
7339
7794
 
7340
7795
  // src/commands/task/proofs.ts
@@ -7345,7 +7800,7 @@ var proofsCmd = new Command18("proofs").description("List proofs for a benchmark
7345
7800
  const headers = { ...auth?.headers ?? {}, ...await taskAccessGrantHeaders(taskId) };
7346
7801
  printResult(await apiGet(`/api/tasks/${taskId}/proofs`, { headers }));
7347
7802
  } catch (err) {
7348
- printError(err instanceof Error ? err.message : String(err));
7803
+ renderFailure(err);
7349
7804
  }
7350
7805
  });
7351
7806
 
@@ -7356,15 +7811,17 @@ var bidCmd = new Command19("bid").description("Submit a bid on an auction task")
7356
7811
  try {
7357
7812
  priceBaseUnits = usdcToBaseUnits(opts.price);
7358
7813
  } catch (err) {
7359
- return void printError(
7360
- `Invalid --price: ${err instanceof Error ? err.message : String(err)}`
7361
- );
7814
+ renderFailure(withErrorContext(err, "Invalid --price"));
7815
+ return;
7362
7816
  }
7363
- const result = await x402Post(`/api/tasks/${taskId}/bids`, {
7364
- taskId,
7365
- price: priceBaseUnits
7366
- });
7367
- printResult({ bidId: result.bidId });
7817
+ const { data: result, idempotencyKey } = await x402Post(
7818
+ `/api/tasks/${taskId}/bids`,
7819
+ {
7820
+ taskId,
7821
+ price: priceBaseUnits
7822
+ }
7823
+ );
7824
+ printResult({ bidId: result.bidId }, { idempotencyKey });
7368
7825
  });
7369
7826
 
7370
7827
  // src/commands/task/auction-accept.ts
@@ -7378,17 +7835,19 @@ var auctionAcceptCmd = new Command20("auction-accept").description("Accept curre
7378
7835
  try {
7379
7836
  body.minPrice = usdcToBaseUnits(opts.minPrice, { allowZero: true });
7380
7837
  } catch (error) {
7381
- return void printError(
7382
- `Invalid --min-price: ${error instanceof Error ? error.message : String(error)}`
7383
- );
7838
+ renderFailure(withErrorContext(error, "Invalid --min-price"));
7839
+ return;
7384
7840
  }
7385
7841
  }
7386
- const result = await x402Post(`/api/tasks/${taskId}/bids/accept`, body);
7387
- printResult({
7388
- acceptedPrice: result.acceptedPrice,
7389
- acceptedPriceUsdc: formatUsdcBaseUnits(result.acceptedPrice),
7390
- workerAddress: result.workerAddress
7391
- });
7842
+ const { data: result, idempotencyKey } = await x402Post(`/api/tasks/${taskId}/bids/accept`, body);
7843
+ printResult(
7844
+ {
7845
+ acceptedPrice: result.acceptedPrice,
7846
+ acceptedPriceUsdc: formatUsdcBaseUnits(result.acceptedPrice),
7847
+ workerAddress: result.workerAddress
7848
+ },
7849
+ { idempotencyKey }
7850
+ );
7392
7851
  });
7393
7852
 
7394
7853
  // src/commands/task/submissions.ts
@@ -7399,8 +7858,8 @@ var submissionsCmd = new Command21("submissions").description("List submissions
7399
7858
  try {
7400
7859
  subs = await apiGet(`/api/tasks/${taskId}/submissions`, { headers: auth?.headers ?? {} });
7401
7860
  } catch (err) {
7402
- const msg = err instanceof Error ? err.message : "Failed to fetch submissions.";
7403
- printError(msg);
7861
+ renderFailure(err, { fallback: "Failed to fetch submissions." });
7862
+ return;
7404
7863
  }
7405
7864
  printResult(subs);
7406
7865
  });
@@ -7409,15 +7868,17 @@ var submissionsCmd = new Command21("submissions").description("List submissions
7409
7868
  import { Command as Command22 } from "commander";
7410
7869
  var selectWinnerCmd = new Command22("select-winner").description("Permissionlessly finalize the lowest bidder after the auction deadline").argument("<taskId>", "Task ID (0x-prefixed hex)").action(async (taskId) => {
7411
7870
  let result;
7871
+ let idempotencyKey;
7412
7872
  try {
7413
- result = await apiPost(`/api/tasks/${taskId}/bids/select-winner`, {
7414
- taskId
7415
- });
7873
+ ({ data: result, idempotencyKey } = await apiPost(
7874
+ `/api/tasks/${taskId}/bids/select-winner`,
7875
+ { taskId }
7876
+ ));
7416
7877
  } catch (err) {
7417
- const msg = err instanceof Error ? err.message : "Failed to select winner.";
7418
- printError(msg);
7878
+ renderFailure(err, { fallback: "Failed to select winner." });
7879
+ return;
7419
7880
  }
7420
- printResult(result);
7881
+ printResult(result, { idempotencyKey });
7421
7882
  });
7422
7883
 
7423
7884
  // src/commands/task/download.ts
@@ -7440,18 +7901,19 @@ var downloadCmd = new Command23("download").description("Download a submission f
7440
7901
  if (opts.artifact) {
7441
7902
  body.artifactId = opts.artifact;
7442
7903
  }
7443
- const result = await apiPost(
7904
+ const { data: result } = await apiPost(
7444
7905
  `/api/tasks/${taskId}/submissions/${opts.submission}/preview`,
7445
7906
  body
7446
7907
  );
7447
7908
  presignedUrl = result.presignedUrl;
7448
7909
  } catch (err) {
7449
- const msg = err instanceof Error ? err.message : "Failed to get download URL.";
7450
- printError(msg);
7910
+ renderFailure(err, { fallback: "Failed to get download URL." });
7911
+ return;
7451
7912
  }
7452
7913
  const res = await fetch(presignedUrl);
7453
7914
  if (!res.ok) {
7454
7915
  printError(`Failed to download file: ${res.status}`);
7916
+ return;
7455
7917
  }
7456
7918
  const content = Buffer.from(await res.arrayBuffer());
7457
7919
  if (opts.output) {
@@ -7465,6 +7927,7 @@ var downloadCmd = new Command23("download").description("Download a submission f
7465
7927
  printError(
7466
7928
  "Downloaded content is not valid UTF-8 text and cannot be safely printed to the terminal. Use --output <path> to save it to a file instead."
7467
7929
  );
7930
+ return;
7468
7931
  }
7469
7932
  process.stdout.write(sanitizeForTerminal(decoded));
7470
7933
  }
@@ -7474,8 +7937,11 @@ var downloadCmd = new Command23("download").description("Download a submission f
7474
7937
  // src/commands/task/cancel.ts
7475
7938
  import { Command as Command24 } from "commander";
7476
7939
  var cancelCmd = new Command24("cancel").description("Cancel an open task and refund the escrowed reward (costs 0.001 USDC)").argument("<taskId>", "Task ID (0x-prefixed hex)").action(async (taskId) => {
7477
- const result = await x402Post(`/api/tasks/${taskId}/cancel`, { taskId });
7478
- printResult(result);
7940
+ const { data: result, idempotencyKey } = await x402Post(
7941
+ `/api/tasks/${taskId}/cancel`,
7942
+ { taskId }
7943
+ );
7944
+ printResult(result, { idempotencyKey });
7479
7945
  });
7480
7946
 
7481
7947
  // src/commands/task/refund-expired.ts
@@ -7484,11 +7950,13 @@ var refundExpiredCmd = new Command25("refund-expired").description(
7484
7950
  "Refund an expired task with no submissions back to the requester (costs 0.001 USDC)"
7485
7951
  ).argument("<taskId>", "Task ID (0x-prefixed hex)").action(async (taskId) => {
7486
7952
  try {
7487
- const result = await x402Post(`/api/tasks/${taskId}/refund-expired`, { taskId });
7488
- printResult(result);
7953
+ const { data: result, idempotencyKey } = await x402Post(
7954
+ `/api/tasks/${taskId}/refund-expired`,
7955
+ { taskId }
7956
+ );
7957
+ printResult(result, { idempotencyKey });
7489
7958
  } catch (err) {
7490
- printError(err instanceof Error ? err.message : String(err));
7491
- process.exit(1);
7959
+ renderFailure(err);
7492
7960
  }
7493
7961
  });
7494
7962
 
@@ -7498,14 +7966,16 @@ var rejectSubmissionCmd = new Command26("reject-submission").description(
7498
7966
  "Reject a spam or low-quality submission (costs 0.001 USDC relay fee). Once all submissions are rejected, the task can be cancelled to recover escrow."
7499
7967
  ).argument("<taskId>", "Task ID (0x-prefixed hex)").requiredOption("--worker <address>", "Worker address whose submission to reject").action(async (taskId, opts) => {
7500
7968
  try {
7501
- const result = await x402Post(`/api/tasks/${taskId}/reject-submission`, {
7502
- taskId,
7503
- worker: opts.worker
7504
- });
7505
- printResult(result);
7969
+ const { data: result, idempotencyKey } = await x402Post(
7970
+ `/api/tasks/${taskId}/reject-submission`,
7971
+ {
7972
+ taskId,
7973
+ worker: opts.worker
7974
+ }
7975
+ );
7976
+ printResult(result, { idempotencyKey });
7506
7977
  } catch (err) {
7507
- printError(err instanceof Error ? err.message : String(err));
7508
- process.exit(1);
7978
+ renderFailure(err);
7509
7979
  }
7510
7980
  });
7511
7981
 
@@ -7527,7 +7997,8 @@ var rejectAllSubmissionsCmd = new Command27("reject-all-submissions").descriptio
7527
7997
  try {
7528
7998
  submissions = await apiGet(`/api/tasks/${taskId}/submissions`);
7529
7999
  } catch (err) {
7530
- return printError(err instanceof Error ? err.message : String(err));
8000
+ renderFailure(err);
8001
+ return;
7531
8002
  }
7532
8003
  const workers = activeSubmissionWorkers(submissions);
7533
8004
  if (workers.length === 0) {
@@ -7537,20 +8008,40 @@ var rejectAllSubmissionsCmd = new Command27("reject-all-submissions").descriptio
7537
8008
  const results = [];
7538
8009
  for (const worker of workers) {
7539
8010
  try {
7540
- const res = await x402Post(`/api/tasks/${taskId}/reject-submission`, {
7541
- taskId,
7542
- worker
7543
- });
7544
- results.push({ worker, txHash: res.txHash });
8011
+ const { data: res, idempotencyKey } = await x402Post(
8012
+ `/api/tasks/${taskId}/reject-submission`,
8013
+ { taskId, worker }
8014
+ );
8015
+ results.push({ worker, txHash: res.txHash, idempotencyKey });
7545
8016
  } catch (err) {
7546
- results.push({ worker, error: err instanceof Error ? err.message : String(err) });
8017
+ results.push({
8018
+ worker,
8019
+ error: err instanceof Error ? err.message : String(err),
8020
+ idempotencyKey: idempotencyKeyForError(err),
8021
+ cause: err
8022
+ });
7547
8023
  }
7548
8024
  }
7549
- const failed = results.filter((r) => r.error);
8025
+ const failed = results.filter((r) => r.error !== void 0);
7550
8026
  if (failed.length > 0) {
7551
- return printError(
7552
- `${failed.length} of ${results.length} rejection(s) failed: ` + failed.map((r) => `${r.worker}: ${r.error}`).join("; ")
8027
+ const representative = failed.find((r) => isPendingApiError(r.cause)) ?? failed[0];
8028
+ renderFailure(
8029
+ withErrorContext(
8030
+ representative.cause,
8031
+ `${failed.length} of ${results.length} rejection(s) failed`
8032
+ ),
8033
+ {
8034
+ details: {
8035
+ results: results.map(({ worker, txHash, idempotencyKey, error }) => ({
8036
+ worker,
8037
+ txHash,
8038
+ idempotencyKey,
8039
+ error
8040
+ }))
8041
+ }
8042
+ }
7553
8043
  );
8044
+ return;
7554
8045
  }
7555
8046
  if (!opts.cancel) {
7556
8047
  printResult({ rejected: results.length, results });
@@ -7558,11 +8049,15 @@ var rejectAllSubmissionsCmd = new Command27("reject-all-submissions").descriptio
7558
8049
  }
7559
8050
  let cancelRes;
7560
8051
  try {
7561
- cancelRes = await x402Post(`/api/tasks/${taskId}/cancel`, { taskId });
8052
+ ({ data: cancelRes } = await x402Post(
8053
+ `/api/tasks/${taskId}/cancel`,
8054
+ { taskId }
8055
+ ));
7562
8056
  } catch (err) {
7563
- return printError(
7564
- `All ${results.length} submission(s) rejected but cancel failed: ${err instanceof Error ? err.message : String(err)}`
8057
+ renderFailure(
8058
+ withErrorContext(err, `All ${results.length} submission(s) rejected but cancel failed`)
7565
8059
  );
8060
+ return;
7566
8061
  }
7567
8062
  printResult({ rejected: results.length, results, cancelTxHash: cancelRes.txHash });
7568
8063
  });
@@ -7582,8 +8077,7 @@ var mySubmissionsCmd = new Command28("my-submissions").description("List all sub
7582
8077
  );
7583
8078
  printResult(result);
7584
8079
  } catch (err) {
7585
- printError(err instanceof Error ? err.message : String(err));
7586
- process.exit(1);
8080
+ renderFailure(err);
7587
8081
  }
7588
8082
  });
7589
8083
 
@@ -7596,9 +8090,8 @@ var updateCmd = new Command29("update").description("Update an open task (costs
7596
8090
  try {
7597
8091
  body.reward = usdcToBaseUnits(opts.reward);
7598
8092
  } catch (err) {
7599
- return void printError(
7600
- `Invalid --reward: ${err instanceof Error ? err.message : String(err)}`
7601
- );
8093
+ renderFailure(withErrorContext(err, "Invalid --reward"));
8094
+ return;
7602
8095
  }
7603
8096
  }
7604
8097
  if (opts.extendExpiry !== void 0) {
@@ -7607,10 +8100,12 @@ var updateCmd = new Command29("update").description("Update an open task (costs
7607
8100
  printError(
7608
8101
  `--extend-expiry must be a positive integer (seconds), got: ${opts.extendExpiry}`
7609
8102
  );
8103
+ return;
7610
8104
  }
7611
8105
  const task = await apiGet(`/api/tasks/${taskId}`);
7612
8106
  if (!task) {
7613
8107
  printError(`Task not found: ${taskId}`);
8108
+ return;
7614
8109
  }
7615
8110
  const currentExpiry = Math.floor(new Date(task.expiryTime).getTime() / 1e3);
7616
8111
  const newExpiry = currentExpiry + delta;
@@ -7620,6 +8115,7 @@ var updateCmd = new Command29("update").description("Update an open task (costs
7620
8115
  printError(
7621
8116
  `--extend-expiry would set expiry in the past. Task expired ${expiredAgo}s ago; pass at least ${expiredAgo + 1} seconds to extend beyond now.`
7622
8117
  );
8118
+ return;
7623
8119
  }
7624
8120
  body.expiryTime = newExpiry;
7625
8121
  }
@@ -7627,6 +8123,7 @@ var updateCmd = new Command29("update").description("Update an open task (costs
7627
8123
  const ts = Math.floor(new Date(opts.bidDeadline).getTime() / 1e3);
7628
8124
  if (ts <= Math.floor(Date.now() / 1e3)) {
7629
8125
  printError("--bid-deadline must be in the future");
8126
+ return;
7630
8127
  } else {
7631
8128
  body.bidDeadline = ts;
7632
8129
  }
@@ -7635,6 +8132,7 @@ var updateCmd = new Command29("update").description("Update an open task (costs
7635
8132
  const ts = Math.floor(new Date(opts.pitchDeadline).getTime() / 1e3);
7636
8133
  if (ts <= Math.floor(Date.now() / 1e3)) {
7637
8134
  printError("--pitch-deadline must be in the future");
8135
+ return;
7638
8136
  } else {
7639
8137
  body.pitchDeadline = ts;
7640
8138
  }
@@ -7643,18 +8141,16 @@ var updateCmd = new Command29("update").description("Update an open task (costs
7643
8141
  try {
7644
8142
  body.auctionFloorPrice = usdcToBaseUnits(opts.auctionFloorPrice, { allowZero: true });
7645
8143
  } catch (err) {
7646
- return void printError(
7647
- `Invalid --auction-floor-price: ${err instanceof Error ? err.message : String(err)}`
7648
- );
8144
+ renderFailure(withErrorContext(err, "Invalid --auction-floor-price"));
8145
+ return;
7649
8146
  }
7650
8147
  }
7651
8148
  if (opts.auctionStartPrice !== void 0) {
7652
8149
  try {
7653
8150
  body.auctionStartPrice = usdcToBaseUnits(opts.auctionStartPrice, { allowZero: true });
7654
8151
  } catch (err) {
7655
- return void printError(
7656
- `Invalid --auction-start-price: ${err instanceof Error ? err.message : String(err)}`
7657
- );
8152
+ renderFailure(withErrorContext(err, "Invalid --auction-start-price"));
8153
+ return;
7658
8154
  }
7659
8155
  }
7660
8156
  if (opts.description !== void 0) {
@@ -7666,8 +8162,11 @@ var updateCmd = new Command29("update").description("Update an open task (costs
7666
8162
  if (opts.metricDescription !== void 0) {
7667
8163
  body.metricDescription = opts.metricDescription;
7668
8164
  }
7669
- const result = await x402Post(`/api/tasks/${taskId}/update`, body);
7670
- printResult(result);
8165
+ const { data: result, idempotencyKey } = await x402Post(
8166
+ `/api/tasks/${taskId}/update`,
8167
+ body
8168
+ );
8169
+ printResult(result, { idempotencyKey });
7671
8170
  }
7672
8171
  );
7673
8172
 
@@ -7678,20 +8177,77 @@ var forfeitCmd = new Command30("forfeit").description("Reclaim a claim-mode task
7678
8177
  const keystore = await loadKeystore();
7679
8178
  const message = buildForfeitMessage(taskId);
7680
8179
  const signature = await signMessage(message, keystore);
7681
- const result = await apiPost(`/api/tasks/${taskId}/forfeit`, {
7682
- taskId,
7683
- requesterAddress: keystore.walletAddress,
7684
- signature
7685
- });
7686
- printResult(result);
8180
+ const { data: result, idempotencyKey } = await apiPost(
8181
+ `/api/tasks/${taskId}/forfeit`,
8182
+ {
8183
+ taskId,
8184
+ requesterAddress: keystore.walletAddress,
8185
+ signature
8186
+ }
8187
+ );
8188
+ printResult(result, { idempotencyKey });
7687
8189
  } catch (err) {
7688
- printError(err instanceof Error ? err.message : String(err));
8190
+ renderFailure(err);
7689
8191
  }
7690
8192
  });
7691
8193
 
7692
- // src/commands/task/evaluate.ts
8194
+ // src/commands/task/assign-evaluator.ts
7693
8195
  import { Command as Command31 } from "commander";
7694
- var evaluateCmd = new Command31("evaluate").description("Submit an evaluation verdict for a task in Review state").argument("<taskId>", "Task ID (0x-prefixed hex)").requiredOption("--verdict <type>", "Verdict: approve, reject, or partial").option("--score <n>", "Score 0-1000 (default: 1000)", "1000").option("--confidence <n>", "Confidence 0-1000 (default: 1000)", "1000").option("--evidence-hash <hash>", "Evidence hash (32-byte hex)").option(
8196
+ var ADDRESS_PATTERN = /^0x[0-9a-fA-F]{40}$/;
8197
+ var assignEvaluatorCmd = new Command31("assign-evaluator").description(
8198
+ "Assign an evaluator to an open, unclaimed task you requested (costs 0.001 USDC). Only possible before a worker claims the task."
8199
+ ).argument("<taskId>", "Task ID (0x-prefixed hex)").requiredOption("--evaluator <address>", "Evaluator wallet address").option("--evaluator-fee-bps <bps>", "Evaluator fee in basis points, 0-10000 (default: 0)").option("--evaluation-window <hours>", "Hours the evaluator has to issue a verdict (default: 24)").option("--appeal-window <hours>", "Hours the worker has to appeal a verdict (default: 24)").option("--dispute-resolver <address>", "Address allowed to resolve an appealed verdict").action(
8200
+ async (taskId, opts) => {
8201
+ if (!ADDRESS_PATTERN.test(opts.evaluator)) {
8202
+ return void printError(`--evaluator must be a wallet address, got: ${opts.evaluator}`);
8203
+ }
8204
+ if (opts.disputeResolver !== void 0 && !ADDRESS_PATTERN.test(opts.disputeResolver)) {
8205
+ return void printError(
8206
+ `--dispute-resolver must be a wallet address, got: ${opts.disputeResolver}`
8207
+ );
8208
+ }
8209
+ const body = { taskId, evaluator: opts.evaluator };
8210
+ if (opts.evaluatorFeeBps !== void 0) {
8211
+ const bps = Number(opts.evaluatorFeeBps);
8212
+ if (!Number.isInteger(bps) || bps < 0 || bps > 1e4) {
8213
+ return void printError(
8214
+ `--evaluator-fee-bps must be an integer between 0 and 10000, got: ${opts.evaluatorFeeBps}`
8215
+ );
8216
+ }
8217
+ body.evaluatorFeeBps = bps;
8218
+ }
8219
+ if (opts.evaluationWindow !== void 0) {
8220
+ const hours = Number(opts.evaluationWindow);
8221
+ if (!Number.isFinite(hours) || hours <= 0) {
8222
+ return void printError(
8223
+ `--evaluation-window must be a positive number of hours, got: ${opts.evaluationWindow}`
8224
+ );
8225
+ }
8226
+ body.evaluationWindowHours = hours;
8227
+ }
8228
+ if (opts.appealWindow !== void 0) {
8229
+ const hours = Number(opts.appealWindow);
8230
+ if (!Number.isFinite(hours) || hours <= 0) {
8231
+ return void printError(
8232
+ `--appeal-window must be a positive number of hours, got: ${opts.appealWindow}`
8233
+ );
8234
+ }
8235
+ body.appealWindowHours = hours;
8236
+ }
8237
+ if (opts.disputeResolver !== void 0) {
8238
+ body.disputeResolver = opts.disputeResolver;
8239
+ }
8240
+ const { data: result, idempotencyKey } = await x402Post(
8241
+ `/api/tasks/${taskId}/evaluator`,
8242
+ body
8243
+ );
8244
+ printResult(result, { idempotencyKey });
8245
+ }
8246
+ );
8247
+
8248
+ // src/commands/task/evaluate.ts
8249
+ import { Command as Command32 } from "commander";
8250
+ var evaluateCmd = new Command32("evaluate").description("Submit an evaluation verdict for a task in Review state").argument("<taskId>", "Task ID (0x-prefixed hex)").requiredOption("--verdict <type>", "Verdict: approve, reject, or partial").option("--score <n>", "Score 0-1000 (default: 1000)", "1000").option("--confidence <n>", "Confidence 0-1000 (default: 1000)", "1000").option("--evidence-hash <hash>", "Evidence hash (32-byte hex)").option(
7695
8251
  "--award <worker:amount:rank>",
7696
8252
  "Award entry (repeatable): worker:amountUSDC:rank",
7697
8253
  collect,
@@ -7720,9 +8276,8 @@ var evaluateCmd = new Command31("evaluate").description("Submit an evaluation ve
7720
8276
  try {
7721
8277
  microUnits = usdcToBaseUnits(amountUSDC);
7722
8278
  } catch (err) {
7723
- return void printError(
7724
- `Invalid amount in '${entry}': ${err instanceof Error ? err.message : String(err)}`
7725
- );
8279
+ renderFailure(withErrorContext(err, `Invalid amount in '${entry}'`));
8280
+ return;
7726
8281
  }
7727
8282
  const parsedRank = Number(rankStr);
7728
8283
  if (!Number.isInteger(parsedRank) || parsedRank < 1) {
@@ -7745,8 +8300,11 @@ var evaluateCmd = new Command31("evaluate").description("Submit an evaluation ve
7745
8300
  }
7746
8301
  body.evidenceHash = opts.evidenceHash;
7747
8302
  }
7748
- const result = await x402Post(`/api/tasks/${taskId}/evaluate`, body);
7749
- printResult({ txHash: result.txHash });
8303
+ const { data: result, idempotencyKey } = await x402Post(
8304
+ `/api/tasks/${taskId}/evaluate`,
8305
+ body
8306
+ );
8307
+ printResult({ txHash: result.txHash }, { idempotencyKey });
7750
8308
  }
7751
8309
  );
7752
8310
  function collect(val, prev) {
@@ -7754,28 +8312,28 @@ function collect(val, prev) {
7754
8312
  }
7755
8313
 
7756
8314
  // src/commands/task/appeal.ts
7757
- import { Command as Command32 } from "commander";
7758
- var appealCmd = new Command32("appeal").description("Appeal an evaluator verdict while in Appealing state").argument("<taskId>", "Task ID (0x-prefixed hex)").action(async (taskId) => {
7759
- const result = await x402Post(`/api/tasks/${taskId}/appeal`, { taskId });
7760
- printResult({ txHash: result.txHash });
8315
+ import { Command as Command33 } from "commander";
8316
+ var appealCmd = new Command33("appeal").description("Appeal an evaluator verdict while in Appealing state").argument("<taskId>", "Task ID (0x-prefixed hex)").action(async (taskId) => {
8317
+ const { data: result, idempotencyKey } = await x402Post(`/api/tasks/${taskId}/appeal`, { taskId });
8318
+ printResult({ txHash: result.txHash }, { idempotencyKey });
7761
8319
  });
7762
8320
 
7763
8321
  // src/commands/task/evaluator-timeout.ts
7764
- import { Command as Command33 } from "commander";
7765
- var evaluatorTimeoutCmd = new Command33("evaluator-timeout").description("Trigger evaluator timeout after the evaluation window expires (requester only)").argument("<taskId>", "Task ID (0x-prefixed hex)").action(async (taskId) => {
7766
- const result = await x402Post(`/api/tasks/${taskId}/evaluator-timeout`, { taskId });
7767
- printResult({ txHash: result.txHash });
8322
+ import { Command as Command34 } from "commander";
8323
+ var evaluatorTimeoutCmd = new Command34("evaluator-timeout").description("Trigger evaluator timeout after the evaluation window expires (requester only)").argument("<taskId>", "Task ID (0x-prefixed hex)").action(async (taskId) => {
8324
+ const { data: result, idempotencyKey } = await x402Post(`/api/tasks/${taskId}/evaluator-timeout`, { taskId });
8325
+ printResult({ txHash: result.txHash }, { idempotencyKey });
7768
8326
  });
7769
8327
 
7770
8328
  // src/commands/task/finalize-verdict.ts
7771
- import { Command as Command34 } from "commander";
7772
- var finalizeVerdictCmd = new Command34("finalize-verdict").description("Finalize an evaluator verdict after the appeal window expires (permissionless)").argument("<taskId>", "Task ID (0x-prefixed hex)").action(async (taskId) => {
7773
- const result = await apiPost(`/api/tasks/${taskId}/finalize-verdict`, { taskId });
7774
- printResult({ txHash: result.txHash });
8329
+ import { Command as Command35 } from "commander";
8330
+ var finalizeVerdictCmd = new Command35("finalize-verdict").description("Finalize an evaluator verdict after the appeal window expires (permissionless)").argument("<taskId>", "Task ID (0x-prefixed hex)").action(async (taskId) => {
8331
+ const { data: result, idempotencyKey } = await apiPost(`/api/tasks/${taskId}/finalize-verdict`, { taskId });
8332
+ printResult({ txHash: result.txHash }, { idempotencyKey });
7775
8333
  });
7776
8334
 
7777
8335
  // src/commands/task/resolve-dispute.ts
7778
- import { Command as Command35 } from "commander";
8336
+ import { Command as Command36 } from "commander";
7779
8337
  function parseAward(spec) {
7780
8338
  const parts = spec.split(":");
7781
8339
  if (parts.length !== 3) {
@@ -7796,7 +8354,7 @@ function parseAward(spec) {
7796
8354
  rank
7797
8355
  };
7798
8356
  }
7799
- var resolveDisputeCmd = new Command35("resolve-dispute").description(
8357
+ var resolveDisputeCmd = new Command36("resolve-dispute").description(
7800
8358
  "Resolve a disputed task as the designated dispute resolver (X402 required).\n\nExamples:\n task resolve-dispute 0x\u2026 --verdict approve --award 0xAlice:5:1\n task resolve-dispute 0x\u2026 --verdict partial --award 0xAlice:3:1 --award 0xBob:2:2"
7801
8359
  ).argument("<taskId>", "Task ID (0x-prefixed hex)").requiredOption("--verdict <type>", "Verdict type: approve or partial").requiredOption(
7802
8360
  "--award <spec...>",
@@ -7810,76 +8368,80 @@ var resolveDisputeCmd = new Command35("resolve-dispute").description(
7810
8368
  try {
7811
8369
  awards = opts.award.map(parseAward);
7812
8370
  } catch (err) {
7813
- printError(err instanceof Error ? err.message : String(err));
8371
+ renderFailure(err);
7814
8372
  return;
7815
8373
  }
7816
- const result = await x402Post(`/api/tasks/${taskId}/resolve-dispute`, {
7817
- taskId,
7818
- verdict: opts.verdict,
7819
- awards
7820
- });
7821
- printResult({ txHash: result.txHash });
8374
+ const { data: result, idempotencyKey } = await x402Post(
8375
+ `/api/tasks/${taskId}/resolve-dispute`,
8376
+ {
8377
+ taskId,
8378
+ verdict: opts.verdict,
8379
+ awards
8380
+ }
8381
+ );
8382
+ printResult({ txHash: result.txHash }, { idempotencyKey });
7822
8383
  });
7823
8384
 
7824
8385
  // src/commands/task/unlock.ts
7825
- import { Command as Command36 } from "commander";
7826
- var unlockCmd = new Command36("unlock").description("Unlock a private task with its password, caching a task-scoped access grant").argument("<taskId>", "Task ID (0x-prefixed hex)").requiredOption("--password <password>", "The private task's password").action(async (taskId, opts) => {
8386
+ import { Command as Command37 } from "commander";
8387
+ var unlockCmd = new Command37("unlock").description("Unlock a private task with its password, caching a task-scoped access grant").argument("<taskId>", "Task ID (0x-prefixed hex)").requiredOption("--password <password>", "The private task's password").action(async (taskId, opts) => {
7827
8388
  let result;
8389
+ let idempotencyKey;
7828
8390
  try {
7829
- result = await apiPost(`/api/tasks/${taskId}/private-access/verify`, {
7830
- taskId,
7831
- password: opts.password
7832
- });
8391
+ ({ data: result, idempotencyKey } = await apiPost(
8392
+ `/api/tasks/${taskId}/private-access/verify`,
8393
+ { taskId, password: opts.password }
8394
+ ));
7833
8395
  } catch (err) {
7834
- printError(err instanceof Error ? err.message : "Failed to unlock task.");
8396
+ renderFailure(err, { fallback: "Failed to unlock task." });
7835
8397
  return;
7836
8398
  }
7837
8399
  await saveTaskAccessGrant(taskId, result.grant, result.expiresAt);
7838
- printResult({ taskId, expiresAt: result.expiresAt });
8400
+ printResult({ taskId, expiresAt: result.expiresAt }, { idempotencyKey });
7839
8401
  });
7840
8402
 
7841
8403
  // src/commands/task/invite.ts
7842
- import { Command as Command37 } from "commander";
7843
- var inviteCmd = new Command37("invite").description("Invite a wallet to view a private task (requester only)").argument("<taskId>", "Task ID (0x-prefixed hex)").argument("<address>", "Wallet address to invite").action(async (taskId, address) => {
8404
+ import { Command as Command38 } from "commander";
8405
+ var inviteCmd = new Command38("invite").description("Invite a wallet to view a private task (requester only)").argument("<taskId>", "Task ID (0x-prefixed hex)").argument("<address>", "Wallet address to invite").action(async (taskId, address) => {
7844
8406
  const auth = await signReadAuth();
7845
8407
  if (!auth || Object.keys(auth.headers).length === 0) {
7846
8408
  printError("No keystore found. Run `taskmarket init` first.");
7847
8409
  return;
7848
8410
  }
7849
8411
  try {
7850
- const result = await apiPost(
8412
+ const { data: result, idempotencyKey } = await apiPost(
7851
8413
  `/api/tasks/${taskId}/private-access/viewers`,
7852
8414
  { taskId, viewerAddress: address },
7853
8415
  { headers: auth.headers }
7854
8416
  );
7855
- printResult(result);
8417
+ printResult(result, { idempotencyKey });
7856
8418
  } catch (err) {
7857
- printError(err instanceof Error ? err.message : "Failed to invite viewer.");
8419
+ renderFailure(err, { fallback: "Failed to invite viewer." });
7858
8420
  }
7859
8421
  });
7860
8422
 
7861
8423
  // src/commands/task/uninvite.ts
7862
- import { Command as Command38 } from "commander";
7863
- var uninviteCmd = new Command38("uninvite").description("Remove a wallet from a private task's allowlist (requester only)").argument("<taskId>", "Task ID (0x-prefixed hex)").argument("<address>", "Wallet address to remove").action(async (taskId, address) => {
8424
+ import { Command as Command39 } from "commander";
8425
+ var uninviteCmd = new Command39("uninvite").description("Remove a wallet from a private task's allowlist (requester only)").argument("<taskId>", "Task ID (0x-prefixed hex)").argument("<address>", "Wallet address to remove").action(async (taskId, address) => {
7864
8426
  const auth = await signReadAuth();
7865
8427
  if (!auth || Object.keys(auth.headers).length === 0) {
7866
8428
  printError("No keystore found. Run `taskmarket init` first.");
7867
8429
  return;
7868
8430
  }
7869
8431
  try {
7870
- const result = await apiDelete(
8432
+ const { data: result, idempotencyKey } = await apiDelete(
7871
8433
  `/api/tasks/${taskId}/private-access/viewers/${encodeURIComponent(address)}`,
7872
8434
  { headers: auth.headers }
7873
8435
  );
7874
- printResult(result);
8436
+ printResult(result, { idempotencyKey });
7875
8437
  } catch (err) {
7876
- printError(err instanceof Error ? err.message : "Failed to remove viewer.");
8438
+ renderFailure(err, { fallback: "Failed to remove viewer." });
7877
8439
  }
7878
8440
  });
7879
8441
 
7880
8442
  // src/commands/task/viewers.ts
7881
- import { Command as Command39 } from "commander";
7882
- var viewersCmd = new Command39("viewers").description("List a private task's wallet allowlist (requester only)").argument("<taskId>", "Task ID (0x-prefixed hex)").action(async (taskId) => {
8443
+ import { Command as Command40 } from "commander";
8444
+ var viewersCmd = new Command40("viewers").description("List a private task's wallet allowlist (requester only)").argument("<taskId>", "Task ID (0x-prefixed hex)").action(async (taskId) => {
7883
8445
  const auth = await signReadAuth();
7884
8446
  if (!auth || Object.keys(auth.headers).length === 0) {
7885
8447
  printError("No keystore found. Run `taskmarket init` first.");
@@ -7891,12 +8453,12 @@ var viewersCmd = new Command39("viewers").description("List a private task's wal
7891
8453
  });
7892
8454
  printResult(result);
7893
8455
  } catch (err) {
7894
- printError(err instanceof Error ? err.message : "Failed to list viewers.");
8456
+ renderFailure(err, { fallback: "Failed to list viewers." });
7895
8457
  }
7896
8458
  });
7897
8459
 
7898
8460
  // src/commands/task/index.ts
7899
- var taskCommand = new Command40("task").description("Manage tasks");
8461
+ var taskCommand = new Command41("task").description("Manage tasks");
7900
8462
  taskCommand.addCommand(createCmd);
7901
8463
  taskCommand.addCommand(listCmd);
7902
8464
  taskCommand.addCommand(getCmd);
@@ -7922,6 +8484,7 @@ taskCommand.addCommand(rejectAllSubmissionsCmd);
7922
8484
  taskCommand.addCommand(mySubmissionsCmd);
7923
8485
  taskCommand.addCommand(updateCmd);
7924
8486
  taskCommand.addCommand(forfeitCmd);
8487
+ taskCommand.addCommand(assignEvaluatorCmd);
7925
8488
  taskCommand.addCommand(evaluateCmd);
7926
8489
  taskCommand.addCommand(appealCmd);
7927
8490
  taskCommand.addCommand(evaluatorTimeoutCmd);
@@ -7933,8 +8496,8 @@ taskCommand.addCommand(uninviteCmd);
7933
8496
  taskCommand.addCommand(viewersCmd);
7934
8497
 
7935
8498
  // src/commands/agents.ts
7936
- import { Command as Command41 } from "commander";
7937
- var agentsCommand = new Command41("agents").description("Browse the agent directory").option("--sort <order>", "Sort order: reputation or tasks", "reputation").option("--skill <tag>", "Filter by skill tag").option("--search <query>", "Search by agentId or wallet address").option("--limit <n>", "Maximum results to return", "20").action(async (opts) => {
8499
+ import { Command as Command42 } from "commander";
8500
+ var agentsCommand = new Command42("agents").description("Browse the agent directory").option("--sort <order>", "Sort order: reputation or tasks", "reputation").option("--skill <tag>", "Filter by skill tag").option("--search <query>", "Search by agentId or wallet address").option("--limit <n>", "Maximum results to return", "20").action(async (opts) => {
7938
8501
  const params = new URLSearchParams();
7939
8502
  if (opts.sort) params.set("sort", opts.sort);
7940
8503
  if (opts.skill) params.set("skill", opts.skill);
@@ -7945,11 +8508,12 @@ var agentsCommand = new Command41("agents").description("Browse the agent direct
7945
8508
  });
7946
8509
 
7947
8510
  // src/commands/inbox.ts
7948
- import { Command as Command42 } from "commander";
7949
- var inboxCommand = new Command42("inbox").description("Show tasks you created and tasks you are working on").action(async () => {
8511
+ import { Command as Command43 } from "commander";
8512
+ var inboxCommand = new Command43("inbox").description("Show tasks you created and tasks you are working on").action(async () => {
7950
8513
  const auth = await signReadAuth();
7951
8514
  if (!auth) {
7952
8515
  printError("No keystore found. Run `taskmarket init` first.");
8516
+ return;
7953
8517
  }
7954
8518
  const address = auth.walletAddress;
7955
8519
  const hasReadAuth = Object.keys(auth.headers).length > 0;
@@ -7966,22 +8530,22 @@ var inboxCommand = new Command42("inbox").description("Show tasks you created an
7966
8530
  });
7967
8531
 
7968
8532
  // src/commands/deposit.ts
7969
- import { Command as Command43 } from "commander";
7970
- var depositCommand = new Command43("deposit").description("Show wallet address and network info for funding").action(async () => {
8533
+ import { Command as Command44 } from "commander";
8534
+ var depositCommand = new Command44("deposit").description("Show wallet address and network info for funding").action(async () => {
7971
8535
  let address;
7972
8536
  try {
7973
8537
  address = await getWalletAddress();
7974
8538
  } catch (err) {
7975
- const msg = err instanceof Error ? err.message : "No keystore found. Run `taskmarket init` first.";
7976
- printError(msg);
8539
+ renderFailure(err, { fallback: "No keystore found. Run `taskmarket init` first." });
8540
+ return;
7977
8541
  }
7978
8542
  let networkInfo;
7979
8543
  try {
7980
8544
  const response = await apiGet("/trpc/network.info");
7981
8545
  networkInfo = response.result.data;
7982
8546
  } catch (err) {
7983
- const msg = err instanceof Error ? err.message : "Failed to fetch network info from backend.";
7984
- printError(msg);
8547
+ renderFailure(err, { fallback: "Failed to fetch network info from backend." });
8548
+ return;
7985
8549
  }
7986
8550
  const data = {
7987
8551
  address,
@@ -7994,11 +8558,11 @@ var depositCommand = new Command43("deposit").description("Show wallet address a
7994
8558
  });
7995
8559
 
7996
8560
  // src/commands/wallet/index.ts
7997
- import { Command as Command49 } from "commander";
8561
+ import { Command as Command50 } from "commander";
7998
8562
 
7999
8563
  // src/commands/wallet/balance.ts
8000
- import { Command as Command44 } from "commander";
8001
- var walletBalanceCommand = new Command44("balance").description("Show USDC balance").option("--address <addr>", "Address to check (defaults to own wallet)").action(async (opts) => {
8564
+ import { Command as Command45 } from "commander";
8565
+ var walletBalanceCommand = new Command45("balance").description("Show USDC balance").option("--address <addr>", "Address to check (defaults to own wallet)").action(async (opts) => {
8002
8566
  let address = opts.address;
8003
8567
  if (!address) {
8004
8568
  const keystore = await loadKeystore();
@@ -8009,7 +8573,7 @@ var walletBalanceCommand = new Command44("balance").description("Show USDC balan
8009
8573
  });
8010
8574
 
8011
8575
  // src/commands/wallet/import.ts
8012
- import { Command as Command45 } from "commander";
8576
+ import { Command as Command46 } from "commander";
8013
8577
  import { createInterface as createInterface2 } from "readline";
8014
8578
  import { privateKeyToAccount as privateKeyToAccount4 } from "viem/accounts";
8015
8579
  function normalizePrivateKey(raw) {
@@ -8044,7 +8608,7 @@ function promptHiddenInput(prompt) {
8044
8608
  });
8045
8609
  });
8046
8610
  }
8047
- var walletImportCommand = new Command45("import").description("Import an existing private key as the agent wallet").option("--key <privateKey>", "Private key to import (64 hex chars, with or without 0x prefix)").option(
8611
+ var walletImportCommand = new Command46("import").description("Import an existing private key as the agent wallet").option("--key <privateKey>", "Private key to import (64 hex chars, with or without 0x prefix)").option(
8048
8612
  "--yes",
8049
8613
  "Confirm legal acceptance non-interactively after reviewing every current policy"
8050
8614
  ).action(async (opts) => {
@@ -8123,69 +8687,77 @@ var walletImportCommand = new Command45("import").description("Import an existin
8123
8687
  });
8124
8688
 
8125
8689
  // src/commands/wallet/set-withdrawal-address.ts
8126
- import { Command as Command46 } from "commander";
8690
+ import { Command as Command47 } from "commander";
8127
8691
  function isValidAddress(addr) {
8128
8692
  return /^0x[a-fA-F0-9]{40}$/.test(addr);
8129
8693
  }
8130
- var setWithdrawalAddressCommand = new Command46("set-withdrawal-address").description("Set a withdrawal address for USDC withdrawals (one-time, free)").argument("<address>", "Ethereum address to receive withdrawals").action(async (address) => {
8694
+ var setWithdrawalAddressCommand = new Command47("set-withdrawal-address").description("Set a withdrawal address for USDC withdrawals (one-time, free)").argument("<address>", "Ethereum address to receive withdrawals").action(async (address) => {
8131
8695
  if (!isValidAddress(address)) {
8132
8696
  printError("Invalid Ethereum address: must be 0x followed by 40 hex characters");
8697
+ return;
8133
8698
  }
8134
8699
  let keystore;
8135
8700
  try {
8136
8701
  keystore = await loadKeystore();
8137
8702
  } catch {
8138
8703
  printError("No keystore found. Run `taskmarket init` first.");
8704
+ return;
8139
8705
  }
8140
8706
  const walletAddress = keystore.walletAddress;
8141
8707
  const message = buildSetWithdrawalAddressMessage(address);
8142
8708
  const signature = await signMessage(message, keystore);
8143
- const result = await apiPost("/api/wallet/set-withdrawal-address", {
8144
- walletAddress,
8145
- withdrawalAddress: address,
8146
- signature
8147
- });
8148
- printResult({ withdrawalAddress: result.withdrawalAddress });
8709
+ const { data: result, idempotencyKey } = await apiPost(
8710
+ "/api/wallet/set-withdrawal-address",
8711
+ {
8712
+ walletAddress,
8713
+ withdrawalAddress: address,
8714
+ signature
8715
+ }
8716
+ );
8717
+ printResult({ withdrawalAddress: result.withdrawalAddress }, { idempotencyKey });
8149
8718
  });
8150
8719
 
8151
8720
  // src/commands/wallet/publish-key.ts
8152
- import { Command as Command47 } from "commander";
8153
- var publishKeyCommand = new Command47("publish-key").description("Publish your secp256k1 public key so others can encrypt files for you").action(async () => {
8721
+ import { Command as Command48 } from "commander";
8722
+ var publishKeyCommand = new Command48("publish-key").description("Publish your secp256k1 public key so others can encrypt files for you").action(async () => {
8154
8723
  let keystore;
8155
8724
  try {
8156
8725
  keystore = await loadKeystore();
8157
8726
  } catch {
8158
8727
  printError("No keystore found. Run `taskmarket init` first.");
8728
+ return;
8159
8729
  }
8160
8730
  const dek = await fetchDeviceKey(keystore.deviceId, keystore.apiToken);
8161
8731
  const privateKey = decryptPrivateKey(dek, keystore.encryptedKey);
8162
8732
  const publicKey = deriveCompressedPublicKey(privateKey);
8163
- const result = await apiPost("/trpc/agents.setPublicKey", {
8733
+ const { data: result, idempotencyKey } = await apiPost("/trpc/agents.setPublicKey", {
8164
8734
  deviceId: keystore.deviceId,
8165
8735
  apiToken: keystore.apiToken,
8166
8736
  publicKey
8167
8737
  });
8168
- printResult({ publicKey: result.result.data.publicKey });
8738
+ printResult({ publicKey: result.result.data.publicKey }, { idempotencyKey });
8169
8739
  });
8170
8740
 
8171
8741
  // src/commands/wallet/withdraw-dreams.ts
8172
- import { Command as Command48 } from "commander";
8742
+ import { Command as Command49 } from "commander";
8173
8743
  import { toHex as toHex2 } from "viem";
8174
8744
  var AUTHORIZATION_VALIDITY_SECONDS = 300;
8175
8745
  function isValidAddress2(addr) {
8176
8746
  return /^0x[a-fA-F0-9]{40}$/.test(addr);
8177
8747
  }
8178
- var withdrawDreamsCommand = new Command48("withdraw-dreams").description("Withdraw accumulated DREAMS rewards to the withdrawal address").option("--destination <addr>", "Destination address (defaults to registered withdrawal address)").action(async (opts) => {
8748
+ var withdrawDreamsCommand = new Command49("withdraw-dreams").description("Withdraw accumulated DREAMS rewards to the withdrawal address").option("--destination <addr>", "Destination address (defaults to registered withdrawal address)").action(async (opts) => {
8179
8749
  let keystore;
8180
8750
  try {
8181
8751
  keystore = await loadKeystore();
8182
8752
  } catch {
8183
8753
  printError("No keystore found. Run `taskmarket init` first.");
8754
+ return;
8184
8755
  }
8185
8756
  const workerAddress = keystore.walletAddress;
8186
8757
  let destination = opts.destination;
8187
8758
  if (destination !== void 0 && !isValidAddress2(destination)) {
8188
8759
  printError("Invalid Ethereum address: must be 0x followed by 40 hex characters");
8760
+ return;
8189
8761
  }
8190
8762
  if (!destination) {
8191
8763
  const addressResponse = await apiGet(
@@ -8195,6 +8767,7 @@ var withdrawDreamsCommand = new Command48("withdraw-dreams").description("Withdr
8195
8767
  printError(
8196
8768
  "No withdrawal address set. Run: taskmarket wallet set-withdrawal-address <address>"
8197
8769
  );
8770
+ return;
8198
8771
  }
8199
8772
  destination = addressResponse.withdrawalAddress;
8200
8773
  }
@@ -8202,25 +8775,31 @@ var withdrawDreamsCommand = new Command48("withdraw-dreams").description("Withdr
8202
8775
  const validBefore = String(Math.floor(Date.now() / 1e3) + AUTHORIZATION_VALIDITY_SECONDS);
8203
8776
  const message = buildWithdrawDreamsMessage(destination, nonce, validBefore);
8204
8777
  const signature = await signMessage(message, keystore);
8205
- const result = await apiPost("/api/wallet/withdraw-dreams", {
8206
- workerAddress,
8207
- destination,
8208
- nonce,
8209
- validBefore,
8210
- signature
8211
- });
8212
- printResult({
8213
- txHash: result.txHash,
8214
- destination: result.destination,
8215
- claimedBaseUnits: result.claimedBaseUnits,
8216
- claimedDreams: formatDreams(result.claimedBaseUnits),
8217
- dreamsPerUsdc: result.dreamsPerUsdc,
8218
- usdEquivalent: result.usdEquivalent
8219
- });
8778
+ const { data: result, idempotencyKey } = await apiPost(
8779
+ "/api/wallet/withdraw-dreams",
8780
+ {
8781
+ workerAddress,
8782
+ destination,
8783
+ nonce,
8784
+ validBefore,
8785
+ signature
8786
+ }
8787
+ );
8788
+ printResult(
8789
+ {
8790
+ txHash: result.txHash,
8791
+ destination: result.destination,
8792
+ claimedBaseUnits: result.claimedBaseUnits,
8793
+ claimedDreams: formatDreams(result.claimedBaseUnits),
8794
+ dreamsPerUsdc: result.dreamsPerUsdc,
8795
+ usdEquivalent: result.usdEquivalent
8796
+ },
8797
+ { idempotencyKey }
8798
+ );
8220
8799
  });
8221
8800
 
8222
8801
  // src/commands/wallet/index.ts
8223
- var walletCommand = new Command49("wallet").description("Wallet management commands");
8802
+ var walletCommand = new Command50("wallet").description("Wallet management commands");
8224
8803
  walletCommand.addCommand(walletBalanceCommand);
8225
8804
  walletCommand.addCommand(walletImportCommand);
8226
8805
  walletCommand.addCommand(setWithdrawalAddressCommand);
@@ -8228,7 +8807,7 @@ walletCommand.addCommand(publishKeyCommand);
8228
8807
  walletCommand.addCommand(withdrawDreamsCommand);
8229
8808
 
8230
8809
  // src/commands/withdraw.ts
8231
- import { Command as Command50 } from "commander";
8810
+ import { Command as Command51 } from "commander";
8232
8811
  import { toHex as toHex3 } from "viem";
8233
8812
  var USDC_TYPES = {
8234
8813
  TransferWithAuthorization: [
@@ -8240,10 +8819,11 @@ var USDC_TYPES = {
8240
8819
  { name: "nonce", type: "bytes32" }
8241
8820
  ]
8242
8821
  };
8243
- var withdrawCommand = new Command50("withdraw").description("Withdraw USDC to the registered withdrawal address").argument("<amount>", "Amount in USDC (e.g. 5 for 5 USDC)").action(async (amount) => {
8822
+ var withdrawCommand = new Command51("withdraw").description("Withdraw USDC to the registered withdrawal address").argument("<amount>", "Amount in USDC (e.g. 5 for 5 USDC)").action(async (amount) => {
8244
8823
  const parsed = parseFloat(amount);
8245
8824
  if (isNaN(parsed) || parsed <= 0) {
8246
8825
  printError("Invalid amount: must be a positive number (e.g. 5 for 5 USDC)");
8826
+ return;
8247
8827
  }
8248
8828
  const amountBaseUnits = String(Math.round(parsed * 1e6));
8249
8829
  let keystore;
@@ -8251,6 +8831,7 @@ var withdrawCommand = new Command50("withdraw").description("Withdraw USDC to th
8251
8831
  keystore = await loadKeystore();
8252
8832
  } catch {
8253
8833
  printError("No keystore found. Run `taskmarket init` first.");
8834
+ return;
8254
8835
  }
8255
8836
  const from = keystore.walletAddress;
8256
8837
  const addressResponse = await apiGet(
@@ -8260,6 +8841,7 @@ var withdrawCommand = new Command50("withdraw").description("Withdraw USDC to th
8260
8841
  printError(
8261
8842
  "No withdrawal address set. Run: taskmarket wallet set-withdrawal-address <address>"
8262
8843
  );
8844
+ return;
8263
8845
  }
8264
8846
  const { withdrawalAddress, usdcDomain } = addressResponse;
8265
8847
  const now = Math.floor(Date.now() / 1e3);
@@ -8280,30 +8862,38 @@ var withdrawCommand = new Command50("withdraw").description("Withdraw USDC to th
8280
8862
  },
8281
8863
  keystore
8282
8864
  );
8283
- const result = await apiPost("/api/wallet/withdraw", {
8284
- from,
8285
- amountBaseUnits,
8286
- authorization,
8287
- signature
8288
- });
8289
- printResult({ txHash: result.txHash, amountBaseUnits: result.amountBaseUnits, to: result.to });
8865
+ const { data: result, idempotencyKey } = await apiPost(
8866
+ "/api/wallet/withdraw",
8867
+ {
8868
+ from,
8869
+ amountBaseUnits,
8870
+ authorization,
8871
+ signature
8872
+ }
8873
+ );
8874
+ printResult(
8875
+ { txHash: result.txHash, amountBaseUnits: result.amountBaseUnits, to: result.to },
8876
+ { idempotencyKey }
8877
+ );
8290
8878
  });
8291
8879
 
8292
8880
  // src/commands/encrypt.ts
8293
- import { Command as Command51 } from "commander";
8881
+ import { Command as Command52 } from "commander";
8294
8882
  import { promises as fs3 } from "fs";
8295
- var encryptCommand = new Command51("encrypt").description("Encrypt a file with ECIES using wallet keys").argument("<file>", "Path to the file to encrypt").option("--recipient <address>", "Recipient wallet address (default: self)").option("--output <path>", "Output path (default: <file>.enc)").action(async (file, opts) => {
8883
+ var encryptCommand = new Command52("encrypt").description("Encrypt a file with ECIES using wallet keys").argument("<file>", "Path to the file to encrypt").option("--recipient <address>", "Recipient wallet address (default: self)").option("--output <path>", "Output path (default: <file>.enc)").action(async (file, opts) => {
8296
8884
  let plaintext;
8297
8885
  try {
8298
8886
  plaintext = await fs3.readFile(file);
8299
8887
  } catch {
8300
8888
  printError(`Cannot read file: ${file}`);
8889
+ return;
8301
8890
  }
8302
8891
  let keystore;
8303
8892
  try {
8304
8893
  keystore = await loadKeystore();
8305
8894
  } catch {
8306
8895
  printError("No keystore found. Run `taskmarket init` first.");
8896
+ return;
8307
8897
  }
8308
8898
  let recipientPubKey;
8309
8899
  let recipientAddress;
@@ -8316,12 +8906,9 @@ var encryptCommand = new Command51("encrypt").description("Encrypt a file with E
8316
8906
  );
8317
8907
  } catch (err) {
8318
8908
  const msg = err instanceof Error ? err.message : String(err);
8319
- if (msg.includes("publish their public key") || msg.includes("NOT_FOUND")) {
8320
- printError(
8321
- `Recipient has not published their public key. Ask them to run: taskmarket wallet publish-key`
8322
- );
8323
- }
8324
- printError(`Failed to fetch recipient public key: ${msg}`);
8909
+ const context = msg.includes("publish their public key") || msg.includes("NOT_FOUND") ? "Recipient has not published their public key. Ask them to run: taskmarket wallet publish-key" : "Failed to fetch recipient public key";
8910
+ renderFailure(withErrorContext(err, context));
8911
+ return;
8325
8912
  }
8326
8913
  recipientPubKey = result.result.data.publicKey;
8327
8914
  } else {
@@ -8341,20 +8928,22 @@ var encryptCommand = new Command51("encrypt").description("Encrypt a file with E
8341
8928
  });
8342
8929
 
8343
8930
  // src/commands/decrypt.ts
8344
- import { Command as Command52 } from "commander";
8931
+ import { Command as Command53 } from "commander";
8345
8932
  import { promises as fs4 } from "fs";
8346
- var decryptCommand = new Command52("decrypt").description("Decrypt a file using your wallet key").argument("<file>", "Path to the encrypted file").option("--output <path>", "Output path (default: strips .enc, otherwise appends .dec)").action(async (file, opts) => {
8933
+ var decryptCommand = new Command53("decrypt").description("Decrypt a file using your wallet key").argument("<file>", "Path to the encrypted file").option("--output <path>", "Output path (default: strips .enc, otherwise appends .dec)").action(async (file, opts) => {
8347
8934
  let ciphertext;
8348
8935
  try {
8349
8936
  ciphertext = await fs4.readFile(file);
8350
8937
  } catch {
8351
8938
  printError(`Cannot read file: ${file}`);
8939
+ return;
8352
8940
  }
8353
8941
  let keystore;
8354
8942
  try {
8355
8943
  keystore = await loadKeystore();
8356
8944
  } catch {
8357
8945
  printError("No keystore found. Run `taskmarket init` first.");
8946
+ return;
8358
8947
  }
8359
8948
  const dek = await fetchDeviceKey(keystore.deviceId, keystore.apiToken);
8360
8949
  const privateKey = decryptPrivateKey(dek, keystore.encryptedKey);
@@ -8362,8 +8951,8 @@ var decryptCommand = new Command52("decrypt").description("Decrypt a file using
8362
8951
  try {
8363
8952
  plaintext = decryptWithPrivateKey(ciphertext, privateKey);
8364
8953
  } catch (err) {
8365
- const msg = err instanceof Error ? err.message : String(err);
8366
- printError(msg);
8954
+ renderFailure(err);
8955
+ return;
8367
8956
  }
8368
8957
  let outputPath;
8369
8958
  if (opts.output) {
@@ -8381,21 +8970,21 @@ var decryptCommand = new Command52("decrypt").description("Decrypt a file using
8381
8970
  });
8382
8971
 
8383
8972
  // src/commands/xmtp.ts
8384
- import { Command as Command53 } from "commander";
8973
+ import { Command as Command54 } from "commander";
8385
8974
 
8386
8975
  // src/lib/xmtp-client.ts
8387
8976
  import os3 from "os";
8388
8977
  import path3 from "path";
8389
- import { randomUUID as randomUUID2, hkdfSync as hkdfSync2 } from "crypto";
8978
+ import { randomUUID as randomUUID3, hkdfSync as hkdfSync2 } from "crypto";
8390
8979
  import { privateKeyToAccount as privateKeyToAccount5 } from "viem/accounts";
8391
8980
 
8392
8981
  // src/lib/xmtp-envelope.ts
8393
- import { randomUUID } from "crypto";
8982
+ import { randomUUID as randomUUID2 } from "crypto";
8394
8983
  function buildEnvelope(input) {
8395
8984
  return AgentMessageEnvelopeSchema.parse({
8396
8985
  schemaVersion: 1,
8397
8986
  type: input.type,
8398
- requestId: input.requestId ?? randomUUID(),
8987
+ requestId: input.requestId ?? randomUUID2(),
8399
8988
  replyToRequestId: input.replyToRequestId,
8400
8989
  senderInboxId: input.senderInboxId,
8401
8990
  senderAddress: input.senderAddress,
@@ -8548,7 +9137,7 @@ function defaultDbPath(walletAddress) {
8548
9137
  }
8549
9138
  async function createDevClient(input) {
8550
9139
  const inboxId = input.existingInboxId ?? `dev-inbox-${input.walletAddress.slice(2, 10).toLowerCase()}`;
8551
- const installationId = input.existingInstallationId ?? randomUUID2();
9140
+ const installationId = input.existingInstallationId ?? randomUUID3();
8552
9141
  const dbPath = input.existingDbPath ?? defaultDbPath(input.walletAddress);
8553
9142
  return {
8554
9143
  inboxId,
@@ -8977,7 +9566,7 @@ async function createProductionClient(input) {
8977
9566
  "non-retryable"
8978
9567
  );
8979
9568
  }
8980
- const installationId = extractString(client, ["installationId", "installationID"]) ?? extractString(asObject(client)?.installation, ["id", "installationId"]) ?? input.existingInstallationId ?? randomUUID2();
9569
+ const installationId = extractString(client, ["installationId", "installationID"]) ?? extractString(asObject(client)?.installation, ["id", "installationId"]) ?? input.existingInstallationId ?? randomUUID3();
8981
9570
  return {
8982
9571
  inboxId,
8983
9572
  installationId,
@@ -9145,7 +9734,7 @@ function getDefaultQueryTimeoutMs() {
9145
9734
  }
9146
9735
  return Math.floor(parsed);
9147
9736
  }
9148
- var xmtpCommand = new Command53("xmtp").description("Manage XMTP messaging for this agent");
9737
+ var xmtpCommand = new Command54("xmtp").description("Manage XMTP messaging for this agent");
9149
9738
  xmtpCommand.command("init").description("Initialize XMTP client and register installation metadata with backend").action(async () => {
9150
9739
  const keystore = await loadKeystore();
9151
9740
  const client = await createXmtpClient({
@@ -9155,7 +9744,7 @@ xmtpCommand.command("init").description("Initialize XMTP client and register ins
9155
9744
  existingDbPath: keystore.xmtpDbPath,
9156
9745
  keystore
9157
9746
  });
9158
- const status = await apiPost("/api/xmtp/bootstrap", {
9747
+ const { data: status, idempotencyKey } = await apiPost("/api/xmtp/bootstrap", {
9159
9748
  deviceId: keystore.deviceId,
9160
9749
  apiToken: keystore.apiToken,
9161
9750
  inboxId: client.inboxId,
@@ -9169,7 +9758,7 @@ xmtpCommand.command("init").description("Initialize XMTP client and register ins
9169
9758
  xmtpInstallationId: status.installationId,
9170
9759
  xmtpDbPath: client.dbPath
9171
9760
  });
9172
- printResult({ policyMode: status.policyMode });
9761
+ printResult({ policyMode: status.policyMode }, { idempotencyKey });
9173
9762
  });
9174
9763
  xmtpCommand.command("status").description("Check XMTP status and active installation state").action(async () => {
9175
9764
  const keystore = await loadKeystore();
@@ -9282,12 +9871,15 @@ xmtpCommand.command("heartbeat").description("Send a one-shot heartbeat to keep
9282
9871
  if (!keystore.xmtpInstallationId) {
9283
9872
  throw new Error("XMTP not initialized. Run `taskmarket xmtp init` first.");
9284
9873
  }
9285
- const result = await apiPost("/api/xmtp/heartbeat", {
9286
- deviceId: keystore.deviceId,
9287
- apiToken: keystore.apiToken,
9288
- installationId: keystore.xmtpInstallationId
9289
- });
9290
- printResult(result);
9874
+ const { data: result, idempotencyKey } = await apiPost(
9875
+ "/api/xmtp/heartbeat",
9876
+ {
9877
+ deviceId: keystore.deviceId,
9878
+ apiToken: keystore.apiToken,
9879
+ installationId: keystore.xmtpInstallationId
9880
+ }
9881
+ );
9882
+ printResult(result, { idempotencyKey });
9291
9883
  });
9292
9884
  var peersCommand = xmtpCommand.command("peers").description("Manage taskmarket peer messaging policies");
9293
9885
  peersCommand.command("list").description("List per-peer messaging policies stored on the backend").action(async () => {
@@ -9305,14 +9897,17 @@ peersCommand.command("set").description("Set messaging policy for a specific pee
9305
9897
  }
9306
9898
  const keystore = await loadKeystore();
9307
9899
  const peerInboxId = await resolveInboxId(options.to);
9308
- const result = await apiPost("/api/xmtp/peers", {
9309
- deviceId: keystore.deviceId,
9310
- apiToken: keystore.apiToken,
9311
- peerInboxId,
9312
- policy: options.policy,
9313
- reason: options.reason
9314
- });
9315
- printResult(result);
9900
+ const { data: result, idempotencyKey } = await apiPost(
9901
+ "/api/xmtp/peers",
9902
+ {
9903
+ deviceId: keystore.deviceId,
9904
+ apiToken: keystore.apiToken,
9905
+ peerInboxId,
9906
+ policy: options.policy,
9907
+ reason: options.reason
9908
+ }
9909
+ );
9910
+ printResult(result, { idempotencyKey });
9316
9911
  });
9317
9912
  var allowlistCommand = xmtpCommand.command("allowlist").description("Manage XMTP SDK consent allowlist (protocol-level)");
9318
9913
  allowlistCommand.command("add").description("Allow messages from a peer inbox (XMTP SDK consent)").requiredOption("--to <agentIdOrAddrOrInboxId>", "Target agent ID, wallet address, or inboxId").action(async (options) => {
@@ -9365,15 +9960,18 @@ allowlistCommand.command("check").description("Check the XMTP SDK consent state
9365
9960
  });
9366
9961
  xmtpCommand.command("purge").description("Revoke stale XMTP installations that have missed heartbeats").action(async () => {
9367
9962
  const keystore = await loadKeystore();
9368
- const result = await apiPost("/api/xmtp/purge", {
9369
- deviceId: keystore.deviceId,
9370
- apiToken: keystore.apiToken
9371
- });
9372
- printResult(result);
9963
+ const { data: result, idempotencyKey } = await apiPost(
9964
+ "/api/xmtp/purge",
9965
+ {
9966
+ deviceId: keystore.deviceId,
9967
+ apiToken: keystore.apiToken
9968
+ }
9969
+ );
9970
+ printResult(result, { idempotencyKey });
9373
9971
  });
9374
9972
 
9375
9973
  // src/commands/daemon.ts
9376
- import { Command as Command54 } from "commander";
9974
+ import { Command as Command55 } from "commander";
9377
9975
  function diffTaskStatuses(prev, next) {
9378
9976
  const changes = [];
9379
9977
  for (const [taskId, newStatus] of next) {
@@ -9401,7 +9999,7 @@ function sleepOrAbort(ms, signal) {
9401
9999
  });
9402
10000
  }
9403
10001
  var MAX_SEEN_TASK_IDS = 2e3;
9404
- var daemonCommand = new Command54("daemon").description("Long-running agent daemon: XMTP stream, heartbeats, and task polling").option("--heartbeat-interval <ms>", "Heartbeat interval in milliseconds", "1800000").option("--inbox-interval <ms>", "Inbox poll interval in milliseconds", "15000").option("--task-interval <ms>", "New task poll interval in milliseconds", "15000").option(
10002
+ var daemonCommand = new Command55("daemon").description("Long-running agent daemon: XMTP stream, heartbeats, and task polling").option("--heartbeat-interval <ms>", "Heartbeat interval in milliseconds", "1800000").option("--inbox-interval <ms>", "Inbox poll interval in milliseconds", "15000").option("--task-interval <ms>", "New task poll interval in milliseconds", "15000").option(
9405
10003
  "--auction-poll-interval <ms>",
9406
10004
  "Poll interval for clock-based auction tasks (dutch/reverse_dutch) in milliseconds",
9407
10005
  "15000"
@@ -9485,12 +10083,15 @@ var daemonCommand = new Command54("daemon").description("Long-running agent daem
9485
10083
  await sleepOrAbort(heartbeatIntervalMs, abortController.signal);
9486
10084
  if (stopped) break;
9487
10085
  try {
9488
- await apiPost("/trpc/xmtp.heartbeat", {
10086
+ const { idempotencyKey } = await apiPost("/trpc/xmtp.heartbeat", {
9489
10087
  deviceId: keystore.deviceId,
9490
10088
  apiToken: keystore.apiToken,
9491
10089
  installationId: client.installationId
9492
10090
  });
9493
- printResult({ event: "xmtp.heartbeat", installationId: client.installationId });
10091
+ printResult(
10092
+ { event: "xmtp.heartbeat", installationId: client.installationId },
10093
+ { idempotencyKey }
10094
+ );
9494
10095
  } catch (err) {
9495
10096
  process.stderr.write(
9496
10097
  `Heartbeat failed: ${err instanceof Error ? err.message : String(err)}
@@ -9722,29 +10323,33 @@ var daemonCommand = new Command54("daemon").description("Long-running agent daem
9722
10323
  );
9723
10324
 
9724
10325
  // src/commands/email/index.ts
9725
- import { Command as Command63 } from "commander";
10326
+ import { Command as Command64 } from "commander";
9726
10327
 
9727
10328
  // src/commands/email/register.ts
9728
- import { Command as Command55 } from "commander";
9729
- var registerCommand = new Command55("register").description("Register an email address for this agent").requiredOption("--username <name>", "Desired username (e.g. myagent)").action(async (opts) => {
10329
+ import { Command as Command56 } from "commander";
10330
+ var registerCommand = new Command56("register").description("Register an email address for this agent").requiredOption("--username <name>", "Desired username (e.g. myagent)").action(async (opts) => {
9730
10331
  const check = await apiGet(
9731
10332
  `/api/emails/check-username?username=${encodeURIComponent(opts.username)}`
9732
10333
  );
9733
10334
  if (!check.available) {
9734
10335
  printError(`Username "${opts.username}" is not available.`);
10336
+ return;
9735
10337
  }
9736
10338
  const keystore = await loadKeystore();
9737
- const result = await apiPost("/api/emails/register", {
9738
- deviceId: keystore.deviceId,
9739
- apiToken: keystore.apiToken,
9740
- username: opts.username
9741
- });
9742
- printResult({ emailAddress: result.emailAddress });
10339
+ const { data: result, idempotencyKey } = await apiPost(
10340
+ "/api/emails/register",
10341
+ {
10342
+ deviceId: keystore.deviceId,
10343
+ apiToken: keystore.apiToken,
10344
+ username: opts.username
10345
+ }
10346
+ );
10347
+ printResult({ emailAddress: result.emailAddress }, { idempotencyKey });
9743
10348
  });
9744
10349
 
9745
10350
  // src/commands/email/inbox.ts
9746
- import { Command as Command56 } from "commander";
9747
- var inboxCommand2 = new Command56("inbox").description("List emails in your inbox").option("--limit <n>", "Number of emails to return", "20").option("--unread", "Show only unread emails").action(async (opts) => {
10351
+ import { Command as Command57 } from "commander";
10352
+ var inboxCommand2 = new Command57("inbox").description("List emails in your inbox").option("--limit <n>", "Number of emails to return", "20").option("--unread", "Show only unread emails").action(async (opts) => {
9748
10353
  const keystore = await loadKeystore();
9749
10354
  const params = new URLSearchParams({
9750
10355
  deviceId: keystore.deviceId,
@@ -9759,8 +10364,8 @@ var inboxCommand2 = new Command56("inbox").description("List emails in your inbo
9759
10364
  });
9760
10365
 
9761
10366
  // src/commands/email/read.ts
9762
- import { Command as Command57 } from "commander";
9763
- var readCommand = new Command57("read").description("Read an email by ID (marks it as read)").argument("<id>", "Email ID").action(async (id) => {
10367
+ import { Command as Command58 } from "commander";
10368
+ var readCommand = new Command58("read").description("Read an email by ID (marks it as read)").argument("<id>", "Email ID").action(async (id) => {
9764
10369
  const keystore = await loadKeystore();
9765
10370
  const params = new URLSearchParams({
9766
10371
  deviceId: keystore.deviceId,
@@ -9772,22 +10377,22 @@ var readCommand = new Command57("read").description("Read an email by ID (marks
9772
10377
  });
9773
10378
 
9774
10379
  // src/commands/email/send.ts
9775
- import { Command as Command58 } from "commander";
9776
- var sendCommand = new Command58("send").description("Send an email").requiredOption("--to <address>", "Recipient email address").requiredOption("--subject <subject>", "Email subject").requiredOption("--body <text>", "Email body text").action(async (opts) => {
10380
+ import { Command as Command59 } from "commander";
10381
+ var sendCommand = new Command59("send").description("Send an email").requiredOption("--to <address>", "Recipient email address").requiredOption("--subject <subject>", "Email subject").requiredOption("--body <text>", "Email body text").action(async (opts) => {
9777
10382
  const keystore = await loadKeystore();
9778
- const result = await apiPost("/api/emails/send", {
10383
+ const { data: result, idempotencyKey } = await apiPost("/api/emails/send", {
9779
10384
  deviceId: keystore.deviceId,
9780
10385
  apiToken: keystore.apiToken,
9781
10386
  to: opts.to,
9782
10387
  subject: opts.subject,
9783
10388
  bodyText: opts.body
9784
10389
  });
9785
- printResult({ sent: result.sent });
10390
+ printResult({ sent: result.sent }, { idempotencyKey });
9786
10391
  });
9787
10392
 
9788
10393
  // src/commands/email/reply.ts
9789
- import { Command as Command59 } from "commander";
9790
- var replyCommand = new Command59("reply").description("Reply to an email").argument("<id>", "Email ID to reply to").requiredOption("--body <text>", "Reply body text").action(async (id, opts) => {
10394
+ import { Command as Command60 } from "commander";
10395
+ var replyCommand = new Command60("reply").description("Reply to an email").argument("<id>", "Email ID to reply to").requiredOption("--body <text>", "Reply body text").action(async (id, opts) => {
9791
10396
  const keystore = await loadKeystore();
9792
10397
  const params = new URLSearchParams({
9793
10398
  deviceId: keystore.deviceId,
@@ -9798,54 +10403,60 @@ var replyCommand = new Command59("reply").description("Reply to an email").argum
9798
10403
  const to = original.fromAddress;
9799
10404
  const rawSubject = original.subject ?? "";
9800
10405
  const subject = rawSubject.startsWith("Re: ") ? rawSubject : `Re: ${rawSubject}`;
9801
- const result = await apiPost("/api/emails/send", {
10406
+ const { data: result, idempotencyKey } = await apiPost("/api/emails/send", {
9802
10407
  deviceId: keystore.deviceId,
9803
10408
  apiToken: keystore.apiToken,
9804
10409
  to,
9805
10410
  subject,
9806
10411
  bodyText: opts.body
9807
10412
  });
9808
- printResult({ sent: result.sent });
10413
+ printResult({ sent: result.sent }, { idempotencyKey });
9809
10414
  });
9810
10415
 
9811
10416
  // src/commands/email/delete.ts
9812
- import { Command as Command60 } from "commander";
9813
- var deleteCommand = new Command60("delete").description("Delete an email by ID").argument("<id>", "Email ID").action(async (id) => {
10417
+ import { Command as Command61 } from "commander";
10418
+ var deleteCommand = new Command61("delete").description("Delete an email by ID").argument("<id>", "Email ID").action(async (id) => {
9814
10419
  const keystore = await loadKeystore();
9815
- const result = await apiPost("/api/emails/delete", {
9816
- deviceId: keystore.deviceId,
9817
- apiToken: keystore.apiToken,
9818
- id
9819
- });
9820
- printResult({ deleted: result.deleted });
10420
+ const { data: result, idempotencyKey } = await apiPost(
10421
+ "/api/emails/delete",
10422
+ {
10423
+ deviceId: keystore.deviceId,
10424
+ apiToken: keystore.apiToken,
10425
+ id
10426
+ }
10427
+ );
10428
+ printResult({ deleted: result.deleted }, { idempotencyKey });
9821
10429
  });
9822
10430
 
9823
10431
  // src/commands/email/address.ts
9824
- import { Command as Command61 } from "commander";
9825
- var addressCommand2 = new Command61("address").description("Show your registered email address").action(async () => {
10432
+ import { Command as Command62 } from "commander";
10433
+ var addressCommand2 = new Command62("address").description("Show your registered email address").action(async () => {
9826
10434
  const keystore = await loadKeystore();
9827
10435
  const result = await apiGet(`/api/agents/stats?address=${keystore.walletAddress}`);
9828
10436
  printResult({ emailAddress: result.emailAddress ?? null });
9829
10437
  });
9830
10438
 
9831
10439
  // src/commands/email/mark-read.ts
9832
- import { Command as Command62 } from "commander";
10440
+ import { Command as Command63 } from "commander";
9833
10441
  async function setReadStatus(id, read) {
9834
10442
  const keystore = await loadKeystore();
9835
- const result = await apiPost("/api/emails/mark-read", {
9836
- deviceId: keystore.deviceId,
9837
- apiToken: keystore.apiToken,
9838
- id,
9839
- read
9840
- });
9841
- printResult({ id: result.id, isRead: result.isRead });
10443
+ const { data: result, idempotencyKey } = await apiPost(
10444
+ "/api/emails/mark-read",
10445
+ {
10446
+ deviceId: keystore.deviceId,
10447
+ apiToken: keystore.apiToken,
10448
+ id,
10449
+ read
10450
+ }
10451
+ );
10452
+ printResult({ id: result.id, isRead: result.isRead }, { idempotencyKey });
9842
10453
  }
9843
- var markReadCommand = new Command62("mark-read").description("Mark an email as read or unread").argument("<id>", "Email ID").option("--unread", "Mark as unread instead of read").action(async (id, opts) => {
10454
+ var markReadCommand = new Command63("mark-read").description("Mark an email as read or unread").argument("<id>", "Email ID").option("--unread", "Mark as unread instead of read").action(async (id, opts) => {
9844
10455
  await setReadStatus(id, !opts.unread);
9845
10456
  });
9846
10457
 
9847
10458
  // src/commands/email/index.ts
9848
- var emailCommand = new Command63("email").description("Email management commands");
10459
+ var emailCommand = new Command64("email").description("Email management commands");
9849
10460
  emailCommand.addCommand(registerCommand);
9850
10461
  emailCommand.addCommand(inboxCommand2);
9851
10462
  emailCommand.addCommand(readCommand);
@@ -9856,32 +10467,30 @@ emailCommand.addCommand(addressCommand2);
9856
10467
  emailCommand.addCommand(markReadCommand);
9857
10468
 
9858
10469
  // src/commands/requester/index.ts
9859
- import { Command as Command65 } from "commander";
10470
+ import { Command as Command66 } from "commander";
9860
10471
 
9861
10472
  // src/commands/requester/stats.ts
9862
- import { Command as Command64 } from "commander";
9863
- var requesterStatsCmd = new Command64("stats").description("Get requester reputation stats for an address").argument("<address>", "Wallet address of the requester").action(async (address) => {
10473
+ import { Command as Command65 } from "commander";
10474
+ var requesterStatsCmd = new Command65("stats").description("Get requester reputation stats for an address").argument("<address>", "Wallet address of the requester").action(async (address) => {
9864
10475
  if (!/^0x[0-9a-fA-F]{40}$/.test(address)) {
9865
10476
  printError("Address must be a 0x-prefixed 20-byte hex string");
9866
- process.exit(1);
9867
10477
  return;
9868
10478
  }
9869
10479
  const data = await apiGet(`/api/requester/${address}/stats`);
9870
10480
  if (!data) {
9871
10481
  printError("No stats found for address");
9872
- process.exit(1);
9873
10482
  return;
9874
10483
  }
9875
10484
  printResult(data);
9876
10485
  });
9877
10486
 
9878
10487
  // src/commands/requester/index.ts
9879
- var requesterCmd = new Command65("requester").description("Requester reputation commands").addCommand(requesterStatsCmd);
10488
+ var requesterCmd = new Command66("requester").description("Requester reputation commands").addCommand(requesterStatsCmd);
9880
10489
 
9881
10490
  // src/index.ts
9882
10491
  var require2 = createRequire(import.meta.url);
9883
10492
  var { version } = require2("../package.json");
9884
- var program = new Command66();
10493
+ var program = new Command67();
9885
10494
  program.name("taskmarket").description("Taskmarket CLI for AI agents").version(version);
9886
10495
  program.addCommand(initCommand);
9887
10496
  program.addCommand(walletCommand);
@@ -9900,7 +10509,10 @@ program.addCommand(daemonCommand);
9900
10509
  program.addCommand(emailCommand);
9901
10510
  program.addCommand(requesterCmd);
9902
10511
  program.addCommand(legalCommand);
9903
- program.parseAsync(process.argv).catch((err) => {
9904
- process.stderr.write(JSON.stringify({ ok: false, error: err.message }) + "\n");
9905
- process.exit(1);
9906
- });
10512
+ void (async () => {
10513
+ try {
10514
+ await program.parseAsync(process.argv);
10515
+ } catch (err) {
10516
+ renderFailure(err);
10517
+ }
10518
+ })();