@lucid-agents/taskmarket 1.7.3 → 1.9.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 +1104 -445
  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 Command68 } 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(),
@@ -4488,11 +4499,66 @@ var TaskInboxResponseSchema = external_exports.object({
4488
4499
  // than a separate notification-center feature.
4489
4500
  invitedPrivateTasks: external_exports.array(TaskResponseSchema).optional().default([])
4490
4501
  });
4502
+ var TaskActionIntent = external_exports.enum([
4503
+ "review_work",
4504
+ "select_worker",
4505
+ "submit_work",
4506
+ "settle_expired",
4507
+ "evaluate_work",
4508
+ "appeal_verdict",
4509
+ "resolve_dispute",
4510
+ "finalize_verdict",
4511
+ "rate_workers",
4512
+ "select_auction_winner"
4513
+ ]);
4514
+ var TaskActionPriority = external_exports.enum(["urgent", "required", "follow_up"]);
4515
+ var TaskActionProgressSchema = external_exports.object({
4516
+ completed: external_exports.number().int().nonnegative(),
4517
+ total: external_exports.number().int().positive()
4518
+ }).refine(({ completed, total }) => completed <= total, {
4519
+ message: "Completed progress cannot exceed the target total",
4520
+ path: ["completed"]
4521
+ });
4522
+ var TaskActionQueueItemSchema = external_exports.object({
4523
+ id: external_exports.string(),
4524
+ task: TaskResponseSchema,
4525
+ role: PendingActionSchema.shape.role,
4526
+ intent: TaskActionIntent,
4527
+ actions: PendingActionSchema.array().min(1),
4528
+ priority: TaskActionPriority,
4529
+ dueAt: external_exports.string().nullable(),
4530
+ progress: TaskActionProgressSchema.nullable().optional()
4531
+ });
4532
+ var TaskActionWaitingReason = external_exports.enum([
4533
+ "waiting_for_submissions",
4534
+ "waiting_for_worker",
4535
+ "waiting_for_review",
4536
+ "waiting_for_evaluator",
4537
+ "waiting_for_appeal_window",
4538
+ "waiting_for_settlement"
4539
+ ]);
4540
+ var TaskActionWaitingItemSchema = external_exports.object({
4541
+ id: external_exports.string(),
4542
+ task: TaskResponseSchema,
4543
+ role: PendingActionSchema.shape.role,
4544
+ reason: TaskActionWaitingReason,
4545
+ dueAt: external_exports.string().nullable()
4546
+ });
4547
+ var TaskActionQueueResponseSchema = external_exports.object({
4548
+ items: TaskActionQueueItemSchema.array(),
4549
+ total: external_exports.number().int().nonnegative(),
4550
+ urgentTotal: external_exports.number().int().nonnegative(),
4551
+ waiting: TaskActionWaitingItemSchema.array()
4552
+ });
4491
4553
  var TaskDetailResponseSchema = TaskResponseSchema.extend({
4492
4554
  pendingActions: PendingActionSchema.array(),
4493
4555
  awards: TaskAwardSchema.array().optional(),
4494
4556
  dreamsPerUsdc: external_exports.string().optional(),
4495
- bonusBps: external_exports.number().optional(),
4557
+ // Optional as a set, not individually: the router emits `dreamsPerUsdc`, `bonusBps` and every
4558
+ // `estimated*` field together or not at all, depending on whether the task carries the DREAMS
4559
+ // hook. No client multiplies this by a split of its own -- the worker/requester estimates are
4560
+ // computed server-side -- so the absent case needs no fallback multiplier anywhere.
4561
+ bonusBps: external_exports.number().int().min(0).max(1e4).optional(),
4496
4562
  estimatedUsdBonusValue: external_exports.string().optional(),
4497
4563
  estimatedWorkerUsdBonusValue: external_exports.string().optional(),
4498
4564
  estimatedRequesterUsdBonusValue: external_exports.string().optional(),
@@ -4506,6 +4572,16 @@ var RejectSubmissionInputSchema = external_exports.object({
4506
4572
  taskId: external_exports.string(),
4507
4573
  worker: external_exports.string()
4508
4574
  });
4575
+ var AssignEvaluatorInputSchema = external_exports.object({
4576
+ taskId: external_exports.string(),
4577
+ evaluator: external_exports.string(),
4578
+ // Integral: same field, same `uint16`, and the two request-side schemas must not disagree
4579
+ // about what a caller may send.
4580
+ evaluatorFeeBps: external_exports.number().int().min(0).max(1e4).optional(),
4581
+ evaluationWindowHours: external_exports.number().positive().optional(),
4582
+ appealWindowHours: external_exports.number().positive().optional(),
4583
+ disputeResolver: external_exports.string().optional()
4584
+ });
4509
4585
  var RefundExpiredInputSchema = external_exports.object({
4510
4586
  taskId: external_exports.string()
4511
4587
  });
@@ -4714,7 +4790,47 @@ var FeedbackSchema = external_exports.object({
4714
4790
  var HealthResponseSchema = external_exports.object({
4715
4791
  status: external_exports.literal("ok"),
4716
4792
  timestamp: external_exports.string(),
4717
- environment: external_exports.string()
4793
+ environment: external_exports.string(),
4794
+ /**
4795
+ * The submission limits this process is actually enforcing, after any environment
4796
+ * override. Reported because a limit a caller cannot observe is a limit a test can only
4797
+ * guess at: smoke-rate-limit.ts asserted against its own copy of the ceiling and had no
4798
+ * way to notice the backend was started with a different one, so it failed with a
4799
+ * confusing boundary error instead of saying the two disagreed.
4800
+ */
4801
+ limits: external_exports.object({
4802
+ freeSubmissionAllowance: external_exports.number().int().positive(),
4803
+ hardSubmissionCeiling: external_exports.number().int().positive()
4804
+ }),
4805
+ /**
4806
+ * Relayed-intent facts a caller can poll for, in the same spirit as `limits`: a queryable
4807
+ * number, not an alert (ADR-0053).
4808
+ *
4809
+ * Optional because health is what tells you the service is up. If no count is available the
4810
+ * object is omitted rather than reported as a sentinel -- absence under an optional field is
4811
+ * unambiguous, whereas a `-1` or an `"unknown"` string is a value every consumer has to learn
4812
+ * not to plot, and it is also exactly how this endpoint already reads to a caller running
4813
+ * against a build from before the field existed.
4814
+ *
4815
+ * Absent means "no answer here", and it covers a process whose sweep has not run yet as well
4816
+ * as one that does not sweep at all. It never means zero.
4817
+ */
4818
+ intents: external_exports.object({
4819
+ /**
4820
+ * When the count below was computed, not when it was served.
4821
+ *
4822
+ * These numbers come from a periodic sweep rather than from the request, so the response
4823
+ * is a cache and says so. Without this a consumer cannot tell a live zero from a zero
4824
+ * left behind by a worker that stopped running -- and the second is the more urgent of
4825
+ * the two, because a sweep that is not running is a queue nothing is draining.
4826
+ */
4827
+ measuredAt: external_exports.string(),
4828
+ /**
4829
+ * Intents that are neither completed nor failed and have not been touched for well past
4830
+ * normal settlement. A count only -- the endpoint is public.
4831
+ */
4832
+ staleNonTerminal: external_exports.number().int().nonnegative()
4833
+ }).optional()
4718
4834
  });
4719
4835
 
4720
4836
  // ../../packages/shared/dist/schemas/pitch.schemas.js
@@ -4873,8 +4989,12 @@ var WithdrawDreamsOutputSchema = external_exports.object({
4873
4989
  });
4874
4990
  var ExchangeRateOutputSchema = external_exports.object({
4875
4991
  dreamsPerUsdc: external_exports.string(),
4876
- workerSplitBps: external_exports.number(),
4877
- bonusBps: external_exports.number()
4992
+ // Both are read straight off `TaskTokenRewardHook`, which rejects either above 10000
4993
+ // (`InvalidBps`), and both are required here rather than optional: a client that had to
4994
+ // default the split would default it to the whole reward, and one that had to default the
4995
+ // bonus would report no bonus at all. Neither is a fact this endpoint may leave to the reader.
4996
+ workerSplitBps: external_exports.number().int().min(0).max(1e4),
4997
+ bonusBps: external_exports.number().int().min(0).max(1e4)
4878
4998
  });
4879
4999
 
4880
5000
  // ../../packages/shared/dist/schemas/xmtp.schemas.js
@@ -5204,6 +5324,225 @@ var AllowedViewerResponseSchema = external_exports.object({
5204
5324
  });
5205
5325
  var ListAllowedViewersResponseSchema = external_exports.array(AllowedViewerResponseSchema);
5206
5326
 
5327
+ // ../../packages/shared/dist/schemas/api-error.schemas.js
5328
+ var API_ERROR_REASONS = [
5329
+ /**
5330
+ * No terminal outcome has been established. The write is owned by the settlement path and
5331
+ * may still succeed. **This is not a failure and not a success**, and it makes no claim about
5332
+ * whether the payment moved (ADR-0049 point 3). Never resubmit; poll `intents.get`.
5333
+ */
5334
+ "intent_in_flight",
5335
+ /**
5336
+ * A write carrying this idempotency key already exists, and this request neither charged the
5337
+ * caller nor submitted anything a second time. Idempotency working, reported as a conflict --
5338
+ * see ADR-0058 for why this is not a 200 carrying the original result.
5339
+ */
5340
+ "idempotency_key_reused",
5341
+ /**
5342
+ * The request arrived with no `X-Taskmarket-Idempotency-Key` header (the value of
5343
+ * `IDEMPOTENCY_KEY_HEADER`), or one that is not a UUID. Rejected before the 402 challenge,
5344
+ * so nothing was charged (ADR-0052).
5345
+ */
5346
+ "idempotency_key_required",
5347
+ /**
5348
+ * The key is already bound to a *different* operation, or to a different caller's write. Not
5349
+ * the benign repeat: nothing here can be waited on, and the caller must generate a fresh key.
5350
+ * Distinguished from `idempotency_key_reused` because the correct client response is opposite
5351
+ * -- poll there, resubmit under a new key here.
5352
+ */
5353
+ "idempotency_key_conflict",
5354
+ /**
5355
+ * The key names the *same* operation by the *same* caller, but with different arguments. One
5356
+ * level finer than `idempotency_key_conflict`, and a distinct reason because the caller's
5357
+ * mistake is distinct: they reused a key while changing what they were asking for, so the
5358
+ * write they just described never happened and the one the key already names is not it
5359
+ * (ADR-0061). Re-send the original arguments to retry the original write, or generate a fresh
5360
+ * key for the new one.
5361
+ */
5362
+ "idempotency_key_payload_mismatch",
5363
+ /**
5364
+ * The settled payment behind this request has already funded another intent. A retry that
5365
+ * failed to reuse its key, or a replayed payment. The money is spent and this write did not
5366
+ * happen; the caller reads the intent that did consume it from `intents.get`.
5367
+ */
5368
+ "payment_already_spent",
5369
+ /**
5370
+ * The idempotency precondition could not be read, so the request was refused rather than
5371
+ * risking a double charge on an unverified assumption. Safe to retry with the same key.
5372
+ */
5373
+ "idempotency_check_unavailable",
5374
+ /**
5375
+ * The chain call is confirmed and the work happened, but recording it did not finish. There
5376
+ * is nothing to refund and nothing to resubmit; the intent is retried automatically.
5377
+ */
5378
+ "intent_completion_deferred",
5379
+ /**
5380
+ * No intent answers to that id or key *for this caller*. Deliberately one reason for both "no
5381
+ * such intent" and "not yours": telling them apart would make this surface an oracle for which
5382
+ * intent ids and idempotency keys exist (ADR-0049 point 6).
5383
+ */
5384
+ "intent_not_found",
5385
+ /** The x402 exchange did not produce a settled payment. Nothing was charged. */
5386
+ "payment_rejected",
5387
+ /** A pre-settlement check on the request's own inputs or on task state rejected it. */
5388
+ "payment_preflight_rejected",
5389
+ /**
5390
+ * The settled payment came from an address other than the one the request is authenticated as
5391
+ * acting for. The write was refused; the payment had already settled when the mismatch was
5392
+ * seen, so the fee is not returned by this path.
5393
+ */
5394
+ "payment_payer_mismatch",
5395
+ /**
5396
+ * The error carries no more specific classification. Present so that every error has a
5397
+ * reason field and a client's switch is total; not a licence to leave one here.
5398
+ */
5399
+ "unclassified"
5400
+ ];
5401
+ var ApiErrorReasonSchema = external_exports.enum(API_ERROR_REASONS);
5402
+ var ApiErrorIntentStatusSchema = external_exports.enum([
5403
+ "reserved",
5404
+ "recorded",
5405
+ "broadcast",
5406
+ "completed",
5407
+ "failed"
5408
+ ]);
5409
+ var REASONS_WITHOUT_REQUIRED_INTENT_STATUS = API_ERROR_REASONS.filter((reason) => reason !== "idempotency_key_reused");
5410
+ var ENVELOPE_FIELDS = {
5411
+ /** The durable handle to the write, where one was recorded. Key on this, not on `txHash`. */
5412
+ intentId: external_exports.string().optional(),
5413
+ /** The operation the intent stands for, e.g. `tasks.create`. */
5414
+ operation: external_exports.string().optional(),
5415
+ /** The caller's own key for the operation, echoed so a dropped response is still traceable. */
5416
+ idempotencyKey: external_exports.string().optional(),
5417
+ /**
5418
+ * Known once something has been broadcast, and explicitly not the handle: the reconciler may
5419
+ * land a replacement at the same nonce, which is a different hash for the same intent.
5420
+ */
5421
+ txHash: external_exports.string().optional()
5422
+ };
5423
+ var ApiErrorEnvelopeSchema = external_exports.discriminatedUnion("reason", [
5424
+ external_exports.object({
5425
+ reason: external_exports.literal("idempotency_key_reused"),
5426
+ /** Required here: the reason means an intent under this key exists, so it has a status. */
5427
+ intentStatus: ApiErrorIntentStatusSchema,
5428
+ ...ENVELOPE_FIELDS
5429
+ }),
5430
+ external_exports.object({
5431
+ reason: external_exports.enum(REASONS_WITHOUT_REQUIRED_INTENT_STATUS),
5432
+ /** The intent's status at the moment this answer was produced, where there is an intent. */
5433
+ intentStatus: ApiErrorIntentStatusSchema.optional(),
5434
+ ...ENVELOPE_FIELDS
5435
+ })
5436
+ ]);
5437
+ var IN_FLIGHT_REASONS = /* @__PURE__ */ new Set([
5438
+ "intent_in_flight",
5439
+ "idempotency_key_reused",
5440
+ // Not "may still succeed" but "already succeeded, still being written down". Both are
5441
+ // answered the same way -- poll, never resubmit -- and reporting this one as settled told a
5442
+ // script following the documented rule that re-running it was an ordinary retry. It is not:
5443
+ // the chain call is confirmed, so a re-run is a second paid action for work already done.
5444
+ "intent_completion_deferred"
5445
+ ]);
5446
+ function isInFlightApiError(envelope) {
5447
+ if (!envelope || !IN_FLIGHT_REASONS.has(envelope.reason))
5448
+ return false;
5449
+ if (envelope.reason !== "idempotency_key_reused")
5450
+ return true;
5451
+ return envelope.intentStatus !== "completed" && envelope.intentStatus !== "failed";
5452
+ }
5453
+ function apiErrorEnvelopeOf(value) {
5454
+ if (!value || typeof value !== "object")
5455
+ return null;
5456
+ const at = (source, key) => source && typeof source === "object" ? source[key] : void 0;
5457
+ const candidate = at(value, "taskmarket") ?? at(at(value, "data"), "taskmarket") ?? at(at(at(value, "error"), "data"), "taskmarket") ?? value;
5458
+ const parsed = ApiErrorEnvelopeSchema.safeParse(candidate);
5459
+ return parsed.success ? parsed.data : null;
5460
+ }
5461
+
5462
+ // ../../packages/shared/dist/schemas/intent.schemas.js
5463
+ var IntentStatusInputSchema = external_exports.object({
5464
+ intentId: external_exports.string().min(1).optional(),
5465
+ idempotencyKey: external_exports.string().min(1).optional()
5466
+ }).refine((value) => Boolean(value.intentId) !== Boolean(value.idempotencyKey), {
5467
+ message: "Provide exactly one of intentId or idempotencyKey"
5468
+ });
5469
+ var IntentRefundStateSchema = external_exports.object({
5470
+ status: external_exports.enum(["pending", "refunding", "refunded", "failed"]),
5471
+ txHash: external_exports.string().nullable()
5472
+ });
5473
+ var IntentStatusResponseSchema = external_exports.object({
5474
+ intentId: external_exports.string(),
5475
+ idempotencyKey: external_exports.string(),
5476
+ /** The operation kind, e.g. `tasks.create`. */
5477
+ operation: external_exports.string(),
5478
+ /**
5479
+ * `reserved` is the pre-payment state (ADR-0067): the key is claimed and the caller has been
5480
+ * challenged, but no payment has landed and nothing has been sent to the chain. It is
5481
+ * non-terminal, it is never broadcastable, and it expires if it is never filled.
5482
+ */
5483
+ status: external_exports.enum(["reserved", "recorded", "broadcast", "completed", "failed"]),
5484
+ /**
5485
+ * Known once something has been broadcast, and explicitly not the handle: the reconciler
5486
+ * may land a replacement at the same nonce, which is a different hash for the same intent.
5487
+ */
5488
+ txHash: external_exports.string().nullable(),
5489
+ /** Why it failed, where it did. Null on every non-terminal status. */
5490
+ terminalReason: external_exports.string().nullable(),
5491
+ /**
5492
+ * The task a completed `tasks.create` produced.
5493
+ *
5494
+ * This is where a caller whose creation went in flight learns the id. The id does not
5495
+ * exist until the transaction runs -- the contract derives it from a requester nonce it
5496
+ * increments as it goes -- so an in-flight creation has no id to report, and reporting one
5497
+ * would be a guess of the kind that made concurrent creates overwrite each other. Null
5498
+ * until the intent completes, and null for every other operation.
5499
+ */
5500
+ taskId: external_exports.string().nullable(),
5501
+ /**
5502
+ * Only present where the intent carried a payment and that payment has been written off.
5503
+ * Null means no refund has been decided -- which is not the same as "no refund is coming"
5504
+ * while the intent is still in flight.
5505
+ */
5506
+ refund: IntentRefundStateSchema.nullable()
5507
+ }).superRefine((value, ctx) => {
5508
+ if (value.status === "reserved") {
5509
+ for (const field of ["txHash", "terminalReason", "taskId"]) {
5510
+ if (value[field] !== null) {
5511
+ ctx.addIssue({
5512
+ code: external_exports.ZodIssueCode.custom,
5513
+ path: [field],
5514
+ message: `A reserved intent has not been sent to the chain, so ${field} must be null`
5515
+ });
5516
+ }
5517
+ }
5518
+ return;
5519
+ }
5520
+ if (value.terminalReason !== null && value.status !== "failed") {
5521
+ ctx.addIssue({
5522
+ code: external_exports.ZodIssueCode.custom,
5523
+ path: ["terminalReason"],
5524
+ message: "Only a failed intent has a terminal reason"
5525
+ });
5526
+ }
5527
+ if (value.taskId === null)
5528
+ return;
5529
+ if (value.status !== "completed") {
5530
+ ctx.addIssue({
5531
+ code: external_exports.ZodIssueCode.custom,
5532
+ path: ["taskId"],
5533
+ message: "taskId is only known once the intent has completed"
5534
+ });
5535
+ return;
5536
+ }
5537
+ if (value.operation !== "tasks.create") {
5538
+ ctx.addIssue({
5539
+ code: external_exports.ZodIssueCode.custom,
5540
+ path: ["taskId"],
5541
+ message: "Only a tasks.create intent produces a taskId"
5542
+ });
5543
+ }
5544
+ });
5545
+
5207
5546
  // ../../packages/shared/dist/platform.constants.js
5208
5547
  var USDC_DECIMALS = 6;
5209
5548
 
@@ -5508,6 +5847,7 @@ function buildReadAuthMessage(address) {
5508
5847
  var READ_AUTH_ADDRESS_HEADER = "X-Taskmarket-Caller-Address";
5509
5848
  var READ_AUTH_SIGNATURE_HEADER = "X-Taskmarket-Caller-Signature";
5510
5849
  var TASK_ACCESS_GRANT_HEADER = "X-Taskmarket-Task-Access-Grant";
5850
+ var IDEMPOTENCY_KEY_HEADER = "X-Taskmarket-Idempotency-Key";
5511
5851
  function buildSubmitMessage(taskId, contentBindings) {
5512
5852
  return contentBindings ? `taskmarket:submit:${taskId}:${contentBindings.join(",")}` : `taskmarket:submit:${taskId}`;
5513
5853
  }
@@ -6035,17 +6375,103 @@ async function keystoreExists(keystorePath) {
6035
6375
  }
6036
6376
  }
6037
6377
 
6378
+ // src/lib/idempotency.ts
6379
+ import { randomUUID } from "crypto";
6380
+ var pendingEnvKey = process.env.TASKMARKET_IDEMPOTENCY_KEY?.trim() || void 0;
6381
+ var keyByError = /* @__PURE__ */ new WeakMap();
6382
+ function rememberIdempotencyKeyFor(error, key) {
6383
+ if (error === null || typeof error !== "object" && typeof error !== "function") return;
6384
+ if (keyByError.has(error)) return;
6385
+ keyByError.set(error, key);
6386
+ }
6387
+ function idempotencyKeyForError(error) {
6388
+ if (error === null || typeof error !== "object" && typeof error !== "function")
6389
+ return void 0;
6390
+ return keyByError.get(error);
6391
+ }
6392
+ function newIdempotencyKey() {
6393
+ return randomUUID();
6394
+ }
6395
+ function resolveIdempotencyKey(explicit) {
6396
+ if (explicit !== void 0) {
6397
+ return explicit;
6398
+ }
6399
+ if (pendingEnvKey !== void 0) {
6400
+ const fromEnv = pendingEnvKey;
6401
+ pendingEnvKey = void 0;
6402
+ return fromEnv;
6403
+ }
6404
+ return newIdempotencyKey();
6405
+ }
6406
+ async function withIdempotentWrite(key, fn) {
6407
+ try {
6408
+ return await fn();
6409
+ } catch (error) {
6410
+ rememberIdempotencyKeyFor(error, key);
6411
+ throw error;
6412
+ }
6413
+ }
6414
+ function idempotencyHeaders(key) {
6415
+ return { [IDEMPOTENCY_KEY_HEADER]: key };
6416
+ }
6417
+
6038
6418
  // src/lib/api.ts
6039
6419
  var API_URL = process.env.TASKMARKET_API_URL ?? "https://api.taskmarket.dev";
6040
6420
  var API_ORIGIN = new URL(API_URL).origin;
6041
6421
  var ApiError = class extends Error {
6042
6422
  status;
6043
- constructor(status, message) {
6423
+ /**
6424
+ * The idempotency key the failed write was sent under, when the request was a write. This is
6425
+ * the only identifier the caller can still hold after a failure -- the intent id is minted by
6426
+ * the backend and reaches the caller only in the response the failure destroyed -- so it
6427
+ * travels on the error rather than being recoverable from the response. See
6428
+ * apps/cli/src/index.ts and lib/output.ts, which surface it on the CLI's JSON envelope.
6429
+ */
6430
+ idempotencyKey;
6431
+ /**
6432
+ * The backend's machine-readable classification of the failure (ADR-0058).
6433
+ *
6434
+ * Before it existed the CLI rendered every failure identically, which is why
6435
+ * `docs/CLI_GUIDE.md` had to tell script authors never to auto-retry a paid command: there was
6436
+ * no way to tell a write that is still landing from one that was definitively rejected, and
6437
+ * retrying the first is a second payment. `reason` is that way.
6438
+ *
6439
+ * Absent when the backend answered with no envelope -- an older deployment, or a failure that
6440
+ * never reached the API at all -- in which case a caller must fall back to the old rule and
6441
+ * treat the outcome as unknown rather than as safe to retry.
6442
+ */
6443
+ envelope;
6444
+ constructor(status, message, idempotencyKey, envelope) {
6044
6445
  super(message);
6045
6446
  this.name = "ApiError";
6046
6447
  this.status = status;
6448
+ this.idempotencyKey = idempotencyKey;
6449
+ this.envelope = envelope;
6047
6450
  }
6048
6451
  };
6452
+ async function readFailureBody(res) {
6453
+ const text = await res.text().catch(() => "");
6454
+ try {
6455
+ return { body: JSON.parse(text), text };
6456
+ } catch {
6457
+ return { body: void 0, text };
6458
+ }
6459
+ }
6460
+ function failureMessage(prefix, status, read) {
6461
+ return `${prefix} (${status}): ${read.body !== void 0 ? JSON.stringify(read.body) : read.text}`;
6462
+ }
6463
+ function withErrorContext(error, context) {
6464
+ const message = error instanceof Error ? error.message : String(error);
6465
+ const wrapped = error instanceof ApiError ? new ApiError(error.status, `${context}: ${message}`, error.idempotencyKey, error.envelope) : new Error(`${context}: ${message}`);
6466
+ const key = idempotencyKeyForError(error);
6467
+ if (key !== void 0) {
6468
+ rememberIdempotencyKeyFor(wrapped, key);
6469
+ }
6470
+ return wrapped;
6471
+ }
6472
+ function isPendingApiError(error) {
6473
+ return error instanceof ApiError && isInFlightApiError(error.envelope);
6474
+ }
6049
6475
  function legalReceiptHeadersForKeystore(keystore, path4, method) {
6050
6476
  const canSend = keystore.legalAcceptanceApiOrigin === API_ORIGIN && (method === "POST" || path4 === "/api/legal/status");
6051
6477
  return canSend ? buildLegalReceiptHeaders(keystore.legalAcceptanceReceipt) : {};
@@ -6069,61 +6495,100 @@ async function apiGet(path4, options) {
6069
6495
  ...options?.headers ?? {}
6070
6496
  }
6071
6497
  });
6072
- const body = await res.json();
6073
6498
  if (!res.ok) {
6074
- throw new ApiError(res.status, `GET ${path4} failed (${res.status}): ${JSON.stringify(body)}`);
6499
+ const read = await readFailureBody(res);
6500
+ throw new ApiError(
6501
+ res.status,
6502
+ failureMessage(`GET ${path4} failed`, res.status, read),
6503
+ void 0,
6504
+ apiErrorEnvelopeOf(read.body) ?? void 0
6505
+ );
6075
6506
  }
6076
- return body;
6507
+ return res.json();
6077
6508
  }
6078
6509
  async function apiPost(path4, body, options) {
6079
6510
  const legalHeaders = await legalReceiptHeaders(path4, "POST");
6080
- const res = await fetch(`${API_URL}${path4}`, {
6081
- method: "POST",
6082
- redirect: "error",
6083
- headers: {
6084
- "Content-Type": "application/json",
6085
- ...legalHeaders,
6086
- ...options?.headers ?? {}
6087
- },
6088
- body: JSON.stringify(body)
6511
+ const idempotencyKey = resolveIdempotencyKey(options?.idempotencyKey);
6512
+ return withIdempotentWrite(idempotencyKey, async () => {
6513
+ const res = await fetch(`${API_URL}${path4}`, {
6514
+ method: "POST",
6515
+ redirect: "error",
6516
+ headers: {
6517
+ "Content-Type": "application/json",
6518
+ ...legalHeaders,
6519
+ ...idempotencyHeaders(idempotencyKey),
6520
+ ...options?.headers ?? {}
6521
+ },
6522
+ body: JSON.stringify(body)
6523
+ });
6524
+ if (!res.ok) {
6525
+ const read = await readFailureBody(res);
6526
+ throw new ApiError(
6527
+ res.status,
6528
+ failureMessage(`POST ${path4} failed`, res.status, read),
6529
+ idempotencyKey,
6530
+ apiErrorEnvelopeOf(read.body) ?? void 0
6531
+ );
6532
+ }
6533
+ return { data: await res.json(), idempotencyKey };
6089
6534
  });
6090
- const result = await res.json();
6091
- if (!res.ok) {
6092
- throw new ApiError(
6093
- res.status,
6094
- `POST ${path4} failed (${res.status}): ${JSON.stringify(result)}`
6095
- );
6096
- }
6097
- return result;
6098
6535
  }
6099
6536
  async function apiDelete(path4, options) {
6100
6537
  const legalHeaders = await legalReceiptHeaders(path4, "POST");
6101
- const res = await fetch(`${API_URL}${path4}`, {
6102
- method: "DELETE",
6103
- redirect: "error",
6104
- headers: {
6105
- "Content-Type": "application/json",
6106
- ...legalHeaders,
6107
- ...options?.headers ?? {}
6538
+ const idempotencyKey = resolveIdempotencyKey(options?.idempotencyKey);
6539
+ return withIdempotentWrite(idempotencyKey, async () => {
6540
+ const res = await fetch(`${API_URL}${path4}`, {
6541
+ method: "DELETE",
6542
+ redirect: "error",
6543
+ headers: {
6544
+ "Content-Type": "application/json",
6545
+ ...legalHeaders,
6546
+ ...idempotencyHeaders(idempotencyKey),
6547
+ ...options?.headers ?? {}
6548
+ }
6549
+ });
6550
+ if (!res.ok) {
6551
+ const read = await readFailureBody(res);
6552
+ throw new ApiError(
6553
+ res.status,
6554
+ failureMessage(`DELETE ${path4} failed`, res.status, read),
6555
+ idempotencyKey,
6556
+ apiErrorEnvelopeOf(read.body) ?? void 0
6557
+ );
6108
6558
  }
6559
+ return { data: await res.json(), idempotencyKey };
6109
6560
  });
6110
- const result = await res.json();
6111
- if (!res.ok) {
6112
- throw new ApiError(
6113
- res.status,
6114
- `DELETE ${path4} failed (${res.status}): ${JSON.stringify(result)}`
6115
- );
6116
- }
6117
- return result;
6118
6561
  }
6119
6562
 
6120
6563
  // src/lib/output.ts
6121
- function printResult(data) {
6122
- console.log(JSON.stringify({ ok: true, data }));
6564
+ function printResult(data, options) {
6565
+ const idempotencyKey = options?.idempotencyKey;
6566
+ console.log(
6567
+ JSON.stringify({ ok: true, data, ...idempotencyKey !== void 0 ? { idempotencyKey } : {} })
6568
+ );
6569
+ }
6570
+ function renderFailure(error, options) {
6571
+ const message = error instanceof Error ? error.message : options?.fallback ?? String(error);
6572
+ const apiError = error instanceof ApiError ? error : void 0;
6573
+ const idempotencyKey = options?.idempotencyKey ?? apiError?.idempotencyKey ?? idempotencyKeyForError(error);
6574
+ const envelope = apiError?.envelope;
6575
+ process.stderr.write(
6576
+ JSON.stringify({
6577
+ ok: false,
6578
+ error: message,
6579
+ ...apiError !== void 0 ? { status: apiError.status } : {},
6580
+ ...idempotencyKey !== void 0 ? { idempotencyKey } : {},
6581
+ ...envelope !== void 0 ? { ...envelope, pending: isInFlightApiError(envelope) } : {},
6582
+ ...options?.details ?? {}
6583
+ }) + "\n"
6584
+ );
6585
+ process.exitCode = 1;
6123
6586
  }
6124
- function printError(message) {
6587
+ function printError(message, options) {
6588
+ return renderFailure(new Error(message), options);
6589
+ }
6590
+ function printWarning(message) {
6125
6591
  process.stderr.write(JSON.stringify({ ok: false, error: message }) + "\n");
6126
- process.exit(1);
6127
6592
  }
6128
6593
 
6129
6594
  // src/lib/agent.ts
@@ -6372,21 +6837,21 @@ async function acceptLegalBundle(options) {
6372
6837
  }
6373
6838
  await confirmAcceptance(bundle, options.assumeYes);
6374
6839
  const challenge = LegalChallengeResponseSchema.parse(
6375
- await apiPost("/api/legal/challenge", {
6840
+ (await apiPost("/api/legal/challenge", {
6376
6841
  walletAddress: options.walletAddress
6377
- })
6842
+ })).data
6378
6843
  );
6379
6844
  validateLegalChallenge(bundle, challenge, options.walletAddress);
6380
6845
  const signature = await options.signMessage(challenge.message);
6381
6846
  const result = LegalAcceptanceResponseSchema.parse(
6382
- await apiPost("/api/legal/accept/wallet", {
6847
+ (await apiPost("/api/legal/accept/wallet", {
6383
6848
  ...affirmations,
6384
6849
  bundleDigest: bundle.bundleDigest,
6385
6850
  bundleVersion: bundle.version,
6386
6851
  nonce: challenge.nonce,
6387
6852
  signature,
6388
6853
  walletAddress: options.walletAddress
6389
- })
6854
+ })).data
6390
6855
  );
6391
6856
  if (result.bundleDigest !== bundle.bundleDigest || result.bundleVersion !== bundle.version) {
6392
6857
  throw new Error("The server returned a receipt for a different legal bundle.");
@@ -6458,7 +6923,7 @@ function sanitizeEmailUsername(agentId, suffix) {
6458
6923
  }
6459
6924
  async function tryRegisterEmail(deviceId, apiToken, username, explicit) {
6460
6925
  try {
6461
- const reg = await apiPost("/api/emails/register", {
6926
+ const { data: reg } = await apiPost("/api/emails/register", {
6462
6927
  deviceId,
6463
6928
  apiToken,
6464
6929
  username
@@ -6467,11 +6932,8 @@ async function tryRegisterEmail(deviceId, apiToken, username, explicit) {
6467
6932
  } catch (err) {
6468
6933
  if (explicit) {
6469
6934
  const msg = err instanceof Error ? err.message : String(err);
6470
- process.stderr.write(
6471
- JSON.stringify({
6472
- ok: false,
6473
- error: `Email registration failed: ${msg}. Run: taskmarket email register --username ${username}`
6474
- }) + "\n"
6935
+ printWarning(
6936
+ `Email registration failed: ${msg}. Run: taskmarket email register --username ${username}`
6475
6937
  );
6476
6938
  }
6477
6939
  return null;
@@ -6490,6 +6952,7 @@ var initCommand = new Command2("init").description("Create and register a new ag
6490
6952
  );
6491
6953
  if (!check.available) {
6492
6954
  printError(`Email username "${opts.email}" is not available.`);
6955
+ return;
6493
6956
  }
6494
6957
  }
6495
6958
  if (await keystoreExists()) {
@@ -6606,68 +7069,80 @@ var addressCommand = new Command3("address").description("Print the wallet addre
6606
7069
  import { Command as Command4 } from "commander";
6607
7070
 
6608
7071
  // src/lib/x402.ts
6609
- async function x402Post(path4, body) {
7072
+ async function x402Post(path4, body, options) {
6610
7073
  const url = `${API_URL}${path4}`;
6611
7074
  const keystore = await loadKeystore();
6612
7075
  const legalHeaders = legalReceiptHeadersForKeystore(keystore, path4, "POST");
6613
- const r1 = await fetch(url, {
6614
- method: "POST",
6615
- redirect: "error",
6616
- headers: { "Content-Type": "application/json", ...legalHeaders },
6617
- body: JSON.stringify(body)
6618
- });
6619
- if (r1.status !== 402) {
6620
- if (r1.ok) {
6621
- return r1.json();
6622
- }
6623
- const text = await r1.text().catch(() => "");
6624
- throw new ApiError(r1.status, `POST ${path4} failed (${r1.status}): ${text}`);
6625
- }
6626
- const requirements = await r1.json();
6627
- if (!requirements.accepts || requirements.accepts.length === 0) {
6628
- throw new Error("No payment methods accepted by server");
6629
- }
6630
- const accept = requirements.accepts[0];
6631
- const { authorization, signature } = await createTransferAuthorization(accept, keystore);
6632
- const paymentPayload = {
6633
- x402Version: 2,
6634
- resource: requirements.resource,
6635
- accepted: {
6636
- scheme: accept.scheme,
6637
- network: accept.network,
6638
- amount: accept.amount,
6639
- asset: accept.asset,
6640
- payTo: accept.payTo,
6641
- maxTimeoutSeconds: accept.maxTimeoutSeconds,
6642
- extra: accept.extra
6643
- },
6644
- payload: { authorization, signature }
6645
- };
6646
- const r2 = await fetch(url, {
6647
- method: "POST",
6648
- redirect: "error",
6649
- headers: {
6650
- "Content-Type": "application/json",
6651
- ...legalHeaders,
6652
- "PAYMENT-SIGNATURE": Buffer.from(JSON.stringify(paymentPayload)).toString("base64")
6653
- },
6654
- body: JSON.stringify(body)
7076
+ const idempotencyKey = resolveIdempotencyKey(options?.idempotencyKey);
7077
+ const idempotency = idempotencyHeaders(idempotencyKey);
7078
+ return withIdempotentWrite(idempotencyKey, async () => {
7079
+ const r1 = await fetch(url, {
7080
+ method: "POST",
7081
+ redirect: "error",
7082
+ headers: { "Content-Type": "application/json", ...legalHeaders, ...idempotency },
7083
+ body: JSON.stringify(body)
7084
+ });
7085
+ if (r1.status !== 402) {
7086
+ if (r1.ok) {
7087
+ return { data: await r1.json(), idempotencyKey };
7088
+ }
7089
+ const read = await readFailureBody(r1);
7090
+ throw new ApiError(
7091
+ r1.status,
7092
+ `POST ${path4} failed (${r1.status}): ${read.text}`,
7093
+ idempotencyKey,
7094
+ apiErrorEnvelopeOf(read.body) ?? void 0
7095
+ );
7096
+ }
7097
+ const requirements = await r1.json();
7098
+ if (!requirements.accepts || requirements.accepts.length === 0) {
7099
+ throw new Error("No payment methods accepted by server");
7100
+ }
7101
+ const accept = requirements.accepts[0];
7102
+ const { authorization, signature } = await createTransferAuthorization(accept, keystore);
7103
+ const paymentPayload = {
7104
+ x402Version: 2,
7105
+ resource: requirements.resource,
7106
+ accepted: {
7107
+ scheme: accept.scheme,
7108
+ network: accept.network,
7109
+ amount: accept.amount,
7110
+ asset: accept.asset,
7111
+ payTo: accept.payTo,
7112
+ maxTimeoutSeconds: accept.maxTimeoutSeconds,
7113
+ extra: accept.extra
7114
+ },
7115
+ payload: { authorization, signature }
7116
+ };
7117
+ const r2 = await fetch(url, {
7118
+ method: "POST",
7119
+ redirect: "error",
7120
+ headers: {
7121
+ "Content-Type": "application/json",
7122
+ ...legalHeaders,
7123
+ ...idempotency,
7124
+ "PAYMENT-SIGNATURE": Buffer.from(JSON.stringify(paymentPayload)).toString("base64")
7125
+ },
7126
+ body: JSON.stringify(body)
7127
+ });
7128
+ if (!r2.ok) {
7129
+ const read = await readFailureBody(r2);
7130
+ throw new ApiError(
7131
+ r2.status,
7132
+ failureMessage(`POST ${path4} failed after payment`, r2.status, read),
7133
+ idempotencyKey,
7134
+ apiErrorEnvelopeOf(read.body) ?? void 0
7135
+ );
7136
+ }
7137
+ return { data: await r2.json(), idempotencyKey };
6655
7138
  });
6656
- const result = await r2.json();
6657
- if (!r2.ok) {
6658
- throw new ApiError(
6659
- r2.status,
6660
- `POST ${path4} failed after payment (${r2.status}): ${JSON.stringify(result)}`
6661
- );
6662
- }
6663
- return result;
6664
7139
  }
6665
7140
 
6666
7141
  // src/commands/identity.ts
6667
7142
  var identityCommand = new Command4("identity").description("Manage agent identity");
6668
7143
  identityCommand.command("register").description("Register ERC-8004 agent identity (costs 0.001 USDC)").action(async () => {
6669
- const result = await x402Post("/api/identity/register", {});
6670
- printResult({ agentId: result.agentId });
7144
+ const { data: result, idempotencyKey } = await x402Post("/api/identity/register", {});
7145
+ printResult({ agentId: result.agentId }, { idempotencyKey });
6671
7146
  });
6672
7147
  identityCommand.command("status").description("Check identity registration status").action(async () => {
6673
7148
  const keystore = await loadKeystore();
@@ -6723,7 +7198,7 @@ var statsCommand = new Command5("stats").description("View agent statistics").op
6723
7198
  });
6724
7199
 
6725
7200
  // src/commands/task/index.ts
6726
- import { Command as Command40 } from "commander";
7201
+ import { Command as Command41 } from "commander";
6727
7202
 
6728
7203
  // src/commands/task/create.ts
6729
7204
  import { Command as Command6 } from "commander";
@@ -6759,9 +7234,8 @@ var createCmd = new Command6("create").description("Create a new task (costs rew
6759
7234
  try {
6760
7235
  rewardBaseUnits = usdcToBaseUnits(opts.reward);
6761
7236
  } catch (err) {
6762
- return void printError(
6763
- `Invalid --reward: ${err instanceof Error ? err.message : String(err)}`
6764
- );
7237
+ renderFailure(withErrorContext(err, "Invalid --reward"));
7238
+ return;
6765
7239
  }
6766
7240
  const duration = Number(opts.duration);
6767
7241
  if (!Number.isFinite(duration) || duration <= 0) {
@@ -6821,9 +7295,8 @@ var createCmd = new Command6("create").description("Create a new task (costs rew
6821
7295
  try {
6822
7296
  maxPriceBaseUnits = usdcToBaseUnits(opts.maxPrice);
6823
7297
  } catch (err) {
6824
- return void printError(
6825
- `Invalid --max-price: ${err instanceof Error ? err.message : String(err)}`
6826
- );
7298
+ renderFailure(withErrorContext(err, "Invalid --max-price"));
7299
+ return;
6827
7300
  }
6828
7301
  if (maxPriceBaseUnits !== rewardBaseUnits) {
6829
7302
  return void printError(
@@ -6865,18 +7338,16 @@ var createCmd = new Command6("create").description("Create a new task (costs rew
6865
7338
  try {
6866
7339
  body.auctionStartPrice = usdcToBaseUnits(opts.auctionStartPrice, { allowZero: true });
6867
7340
  } catch (err) {
6868
- return void printError(
6869
- `Invalid --auction-start-price: ${err instanceof Error ? err.message : String(err)}`
6870
- );
7341
+ renderFailure(withErrorContext(err, "Invalid --auction-start-price"));
7342
+ return;
6871
7343
  }
6872
7344
  }
6873
7345
  if (opts.auctionFloorPrice) {
6874
7346
  try {
6875
7347
  body.auctionFloorPrice = usdcToBaseUnits(opts.auctionFloorPrice, { allowZero: true });
6876
7348
  } catch (err) {
6877
- return void printError(
6878
- `Invalid --auction-floor-price: ${err instanceof Error ? err.message : String(err)}`
6879
- );
7349
+ renderFailure(withErrorContext(err, "Invalid --auction-floor-price"));
7350
+ return;
6880
7351
  }
6881
7352
  }
6882
7353
  for (const [flag, value] of [
@@ -6924,8 +7395,11 @@ var createCmd = new Command6("create").description("Create a new task (costs rew
6924
7395
  if (opts.appealWindow) body.appealWindowHours = parseFloat(opts.appealWindow);
6925
7396
  if (opts.disputeResolver) body.disputeResolver = opts.disputeResolver;
6926
7397
  }
6927
- const result = await x402Post("/api/tasks", body);
6928
- printResult({ taskId: result.taskId });
7398
+ const { data: result, idempotencyKey } = await x402Post(
7399
+ "/api/tasks",
7400
+ body
7401
+ );
7402
+ printResult({ taskId: result.taskId }, { idempotencyKey });
6929
7403
  }
6930
7404
  );
6931
7405
 
@@ -7036,6 +7510,7 @@ var getCmd = new Command8("get").description("Get task details").argument("<task
7036
7510
  printError(
7037
7511
  `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>)`
7038
7512
  );
7513
+ return;
7039
7514
  }
7040
7515
  printResult(task);
7041
7516
  });
@@ -7136,6 +7611,7 @@ var submitCmd = new Command9("submit").description("Submit work for a task").arg
7136
7611
  try {
7137
7612
  if (!opts.file || opts.file.length === 0) {
7138
7613
  printError("--file is required");
7614
+ return;
7139
7615
  }
7140
7616
  const keystore = await loadKeystore();
7141
7617
  const signature = await signMessage(buildSubmitMessage(taskId), keystore);
@@ -7144,9 +7620,11 @@ var submitCmd = new Command9("submit").description("Submit work for a task").arg
7144
7620
  const mimeType = mimeTypeForPath(filePath);
7145
7621
  const data = await fsPromises.readFile(filePath);
7146
7622
  if (data.length === 0) {
7147
- printError(`File is empty: ${basename(filePath)}`);
7623
+ throw new Error(`File is empty: ${basename(filePath)}`);
7148
7624
  }
7149
- const { uploadUrl, artifactKey } = await apiPost(
7625
+ const {
7626
+ data: { uploadUrl, artifactKey }
7627
+ } = await apiPost(
7150
7628
  `/api/tasks/${taskId}/submissions/request-upload-url`,
7151
7629
  {
7152
7630
  taskId,
@@ -7181,13 +7659,16 @@ var submitCmd = new Command9("submit").description("Submit work for a task").arg
7181
7659
  ),
7182
7660
  keystore
7183
7661
  );
7184
- const result = await x402Post(`/api/tasks/${taskId}/submissions/from-keys`, {
7185
- taskId,
7186
- workerAddress: keystore.walletAddress,
7187
- artifacts: artifactInputs,
7188
- signature: submitSignature
7189
- });
7190
- printResult({ submissionId: result.submissionId });
7662
+ const { data: result, idempotencyKey } = await x402Post(
7663
+ `/api/tasks/${taskId}/submissions/from-keys`,
7664
+ {
7665
+ taskId,
7666
+ workerAddress: keystore.walletAddress,
7667
+ artifacts: artifactInputs,
7668
+ signature: submitSignature
7669
+ }
7670
+ );
7671
+ printResult({ submissionId: result.submissionId }, { idempotencyKey });
7191
7672
  } finally {
7192
7673
  submitCmd.setOptionValue("file", []);
7193
7674
  }
@@ -7199,7 +7680,6 @@ var acceptCmd = new Command10("accept").description("Accept a submission (costs
7199
7680
  const task = await apiGet(`/api/tasks/${taskId}`);
7200
7681
  if (!task) {
7201
7682
  printError("Task not found");
7202
- process.exit(1);
7203
7683
  return;
7204
7684
  }
7205
7685
  const canAccept = task.pendingActions?.some(
@@ -7215,14 +7695,13 @@ var acceptCmd = new Command10("accept").description("Accept a submission (costs
7215
7695
  printError(
7216
7696
  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."
7217
7697
  );
7218
- process.exit(1);
7219
7698
  return;
7220
7699
  }
7221
- await x402Post(`/api/tasks/${taskId}/accept`, {
7700
+ const { idempotencyKey } = await x402Post(`/api/tasks/${taskId}/accept`, {
7222
7701
  taskId,
7223
7702
  worker: opts.worker
7224
7703
  });
7225
- printResult({ accepted: true });
7704
+ printResult({ accepted: true }, { idempotencyKey });
7226
7705
  });
7227
7706
 
7228
7707
  // src/commands/task/accept-submissions.ts
@@ -7257,13 +7736,16 @@ var acceptSubmissionsCmd = new Command11("accept-submissions").description(
7257
7736
  if (sum !== 1e4) {
7258
7737
  throw new Error(`Winner shares must sum to 10000 basis points (got ${sum})`);
7259
7738
  }
7260
- const result = await x402Post(`/api/tasks/${taskId}/accept-submissions`, {
7261
- taskId,
7262
- winners
7263
- });
7264
- printResult({ accepted: result.success, winners: winners.length });
7739
+ const { data: result, idempotencyKey } = await x402Post(
7740
+ `/api/tasks/${taskId}/accept-submissions`,
7741
+ {
7742
+ taskId,
7743
+ winners
7744
+ }
7745
+ );
7746
+ printResult({ accepted: result.success, winners: winners.length }, { idempotencyKey });
7265
7747
  } catch (err) {
7266
- printError(err instanceof Error ? err.message : String(err));
7748
+ renderFailure(err);
7267
7749
  }
7268
7750
  });
7269
7751
 
@@ -7273,14 +7755,15 @@ var rateCmd = new Command12("rate").description("Rate a worker (costs 0.001 USDC
7273
7755
  const rating = parseInt(opts.rating, 10);
7274
7756
  if (rating < 0 || rating > 100) {
7275
7757
  printError("Rating must be between 0 and 100");
7758
+ return;
7276
7759
  }
7277
- const result = await x402Post(`/api/tasks/${taskId}/rate`, {
7760
+ const { data: result, idempotencyKey } = await x402Post(`/api/tasks/${taskId}/rate`, {
7278
7761
  taskId,
7279
7762
  worker: opts.worker,
7280
7763
  rating,
7281
7764
  ...opts.feedback ? { feedbackText: opts.feedback } : {}
7282
7765
  });
7283
- printResult({ feedbackId: result.feedbackId });
7766
+ printResult({ feedbackId: result.feedbackId }, { idempotencyKey });
7284
7767
  });
7285
7768
 
7286
7769
  // src/commands/task/claim.ts
@@ -7289,11 +7772,14 @@ var claimCmd = new Command13("claim").description("Claim a task as a worker").ar
7289
7772
  const keystore = await loadKeystore();
7290
7773
  const message = buildClaimMessage(taskId);
7291
7774
  const signature = await signMessage(message, keystore);
7292
- const result = await apiPost(`/api/tasks/${taskId}/claim`, {
7293
- workerAddress: keystore.walletAddress,
7294
- signature
7295
- });
7296
- printResult({ claimId: result.claimId });
7775
+ const { data: result, idempotencyKey } = await apiPost(
7776
+ `/api/tasks/${taskId}/claim`,
7777
+ {
7778
+ workerAddress: keystore.walletAddress,
7779
+ signature
7780
+ }
7781
+ );
7782
+ printResult({ claimId: result.claimId }, { idempotencyKey });
7297
7783
  });
7298
7784
 
7299
7785
  // src/commands/task/pitch.ts
@@ -7301,14 +7787,17 @@ import { Command as Command14 } from "commander";
7301
7787
  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) => {
7302
7788
  const keystore = await loadKeystore();
7303
7789
  const signature = await signMessage(`taskmarket:pitch:${taskId}`, keystore);
7304
- const result = await x402Post(`/api/tasks/${taskId}/pitches`, {
7305
- taskId,
7306
- workerAddress: keystore.walletAddress,
7307
- pitchText: opts.text,
7308
- ...opts.duration ? { estimatedDuration: parseInt(opts.duration, 10) } : {},
7309
- signature
7310
- });
7311
- printResult({ pitchId: result.pitchId });
7790
+ const { data: result, idempotencyKey } = await x402Post(
7791
+ `/api/tasks/${taskId}/pitches`,
7792
+ {
7793
+ taskId,
7794
+ workerAddress: keystore.walletAddress,
7795
+ pitchText: opts.text,
7796
+ ...opts.duration ? { estimatedDuration: parseInt(opts.duration, 10) } : {},
7797
+ signature
7798
+ }
7799
+ );
7800
+ printResult({ pitchId: result.pitchId }, { idempotencyKey });
7312
7801
  });
7313
7802
 
7314
7803
  // src/commands/task/pitches.ts
@@ -7319,7 +7808,7 @@ var pitchesCmd = new Command15("pitches").description("List pitches for a task")
7319
7808
  const headers = { ...auth?.headers ?? {}, ...await taskAccessGrantHeaders(taskId) };
7320
7809
  printResult(await apiGet(`/api/tasks/${taskId}/pitches`, { headers }));
7321
7810
  } catch (err) {
7322
- printError(err instanceof Error ? err.message : String(err));
7811
+ renderFailure(err);
7323
7812
  }
7324
7813
  });
7325
7814
 
@@ -7329,13 +7818,13 @@ var selectWorkerCmd = new Command16("select-worker").description("Select a worke
7329
7818
  const keystore = await loadKeystore();
7330
7819
  const message = buildSelectWorkerMessage(taskId, opts.pitch, opts.worker);
7331
7820
  const signature = await signMessage(message, keystore);
7332
- await apiPost(`/api/tasks/${taskId}/pitches/select`, {
7821
+ const { idempotencyKey } = await apiPost(`/api/tasks/${taskId}/pitches/select`, {
7333
7822
  taskId,
7334
7823
  pitchId: opts.pitch,
7335
7824
  workerAddress: opts.worker,
7336
7825
  signature
7337
7826
  });
7338
- printResult({ selected: true });
7827
+ printResult({ selected: true }, { idempotencyKey });
7339
7828
  });
7340
7829
 
7341
7830
  // src/commands/task/proof.ts
@@ -7343,7 +7832,7 @@ import { Command as Command17 } from "commander";
7343
7832
  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) => {
7344
7833
  const keystore = await loadKeystore();
7345
7834
  const signature = await signMessage(`taskmarket:proof:${taskId}`, keystore);
7346
- const result = await x402Post(`/api/tasks/${taskId}/proofs`, {
7835
+ const { data: result, idempotencyKey } = await x402Post(`/api/tasks/${taskId}/proofs`, {
7347
7836
  taskId,
7348
7837
  workerAddress: keystore.walletAddress,
7349
7838
  proofData: opts.data,
@@ -7351,7 +7840,7 @@ var proofCmd = new Command17("proof").description("Submit a benchmark proof (cos
7351
7840
  ...opts.metric ? { metricValue: opts.metric } : {},
7352
7841
  signature
7353
7842
  });
7354
- printResult({ proofId: result.proofId, submissionId: result.submissionId });
7843
+ printResult({ proofId: result.proofId, submissionId: result.submissionId }, { idempotencyKey });
7355
7844
  });
7356
7845
 
7357
7846
  // src/commands/task/proofs.ts
@@ -7362,7 +7851,7 @@ var proofsCmd = new Command18("proofs").description("List proofs for a benchmark
7362
7851
  const headers = { ...auth?.headers ?? {}, ...await taskAccessGrantHeaders(taskId) };
7363
7852
  printResult(await apiGet(`/api/tasks/${taskId}/proofs`, { headers }));
7364
7853
  } catch (err) {
7365
- printError(err instanceof Error ? err.message : String(err));
7854
+ renderFailure(err);
7366
7855
  }
7367
7856
  });
7368
7857
 
@@ -7373,15 +7862,17 @@ var bidCmd = new Command19("bid").description("Submit a bid on an auction task")
7373
7862
  try {
7374
7863
  priceBaseUnits = usdcToBaseUnits(opts.price);
7375
7864
  } catch (err) {
7376
- return void printError(
7377
- `Invalid --price: ${err instanceof Error ? err.message : String(err)}`
7378
- );
7865
+ renderFailure(withErrorContext(err, "Invalid --price"));
7866
+ return;
7379
7867
  }
7380
- const result = await x402Post(`/api/tasks/${taskId}/bids`, {
7381
- taskId,
7382
- price: priceBaseUnits
7383
- });
7384
- printResult({ bidId: result.bidId });
7868
+ const { data: result, idempotencyKey } = await x402Post(
7869
+ `/api/tasks/${taskId}/bids`,
7870
+ {
7871
+ taskId,
7872
+ price: priceBaseUnits
7873
+ }
7874
+ );
7875
+ printResult({ bidId: result.bidId }, { idempotencyKey });
7385
7876
  });
7386
7877
 
7387
7878
  // src/commands/task/auction-accept.ts
@@ -7395,17 +7886,19 @@ var auctionAcceptCmd = new Command20("auction-accept").description("Accept curre
7395
7886
  try {
7396
7887
  body.minPrice = usdcToBaseUnits(opts.minPrice, { allowZero: true });
7397
7888
  } catch (error) {
7398
- return void printError(
7399
- `Invalid --min-price: ${error instanceof Error ? error.message : String(error)}`
7400
- );
7889
+ renderFailure(withErrorContext(error, "Invalid --min-price"));
7890
+ return;
7401
7891
  }
7402
7892
  }
7403
- const result = await x402Post(`/api/tasks/${taskId}/bids/accept`, body);
7404
- printResult({
7405
- acceptedPrice: result.acceptedPrice,
7406
- acceptedPriceUsdc: formatUsdcBaseUnits(result.acceptedPrice),
7407
- workerAddress: result.workerAddress
7408
- });
7893
+ const { data: result, idempotencyKey } = await x402Post(`/api/tasks/${taskId}/bids/accept`, body);
7894
+ printResult(
7895
+ {
7896
+ acceptedPrice: result.acceptedPrice,
7897
+ acceptedPriceUsdc: formatUsdcBaseUnits(result.acceptedPrice),
7898
+ workerAddress: result.workerAddress
7899
+ },
7900
+ { idempotencyKey }
7901
+ );
7409
7902
  });
7410
7903
 
7411
7904
  // src/commands/task/submissions.ts
@@ -7416,8 +7909,8 @@ var submissionsCmd = new Command21("submissions").description("List submissions
7416
7909
  try {
7417
7910
  subs = await apiGet(`/api/tasks/${taskId}/submissions`, { headers: auth?.headers ?? {} });
7418
7911
  } catch (err) {
7419
- const msg = err instanceof Error ? err.message : "Failed to fetch submissions.";
7420
- printError(msg);
7912
+ renderFailure(err, { fallback: "Failed to fetch submissions." });
7913
+ return;
7421
7914
  }
7422
7915
  printResult(subs);
7423
7916
  });
@@ -7426,15 +7919,17 @@ var submissionsCmd = new Command21("submissions").description("List submissions
7426
7919
  import { Command as Command22 } from "commander";
7427
7920
  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) => {
7428
7921
  let result;
7922
+ let idempotencyKey;
7429
7923
  try {
7430
- result = await apiPost(`/api/tasks/${taskId}/bids/select-winner`, {
7431
- taskId
7432
- });
7924
+ ({ data: result, idempotencyKey } = await apiPost(
7925
+ `/api/tasks/${taskId}/bids/select-winner`,
7926
+ { taskId }
7927
+ ));
7433
7928
  } catch (err) {
7434
- const msg = err instanceof Error ? err.message : "Failed to select winner.";
7435
- printError(msg);
7929
+ renderFailure(err, { fallback: "Failed to select winner." });
7930
+ return;
7436
7931
  }
7437
- printResult(result);
7932
+ printResult(result, { idempotencyKey });
7438
7933
  });
7439
7934
 
7440
7935
  // src/commands/task/download.ts
@@ -7457,18 +7952,19 @@ var downloadCmd = new Command23("download").description("Download a submission f
7457
7952
  if (opts.artifact) {
7458
7953
  body.artifactId = opts.artifact;
7459
7954
  }
7460
- const result = await apiPost(
7955
+ const { data: result } = await apiPost(
7461
7956
  `/api/tasks/${taskId}/submissions/${opts.submission}/preview`,
7462
7957
  body
7463
7958
  );
7464
7959
  presignedUrl = result.presignedUrl;
7465
7960
  } catch (err) {
7466
- const msg = err instanceof Error ? err.message : "Failed to get download URL.";
7467
- printError(msg);
7961
+ renderFailure(err, { fallback: "Failed to get download URL." });
7962
+ return;
7468
7963
  }
7469
7964
  const res = await fetch(presignedUrl);
7470
7965
  if (!res.ok) {
7471
7966
  printError(`Failed to download file: ${res.status}`);
7967
+ return;
7472
7968
  }
7473
7969
  const content = Buffer.from(await res.arrayBuffer());
7474
7970
  if (opts.output) {
@@ -7482,6 +7978,7 @@ var downloadCmd = new Command23("download").description("Download a submission f
7482
7978
  printError(
7483
7979
  "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."
7484
7980
  );
7981
+ return;
7485
7982
  }
7486
7983
  process.stdout.write(sanitizeForTerminal(decoded));
7487
7984
  }
@@ -7491,8 +7988,11 @@ var downloadCmd = new Command23("download").description("Download a submission f
7491
7988
  // src/commands/task/cancel.ts
7492
7989
  import { Command as Command24 } from "commander";
7493
7990
  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) => {
7494
- const result = await x402Post(`/api/tasks/${taskId}/cancel`, { taskId });
7495
- printResult(result);
7991
+ const { data: result, idempotencyKey } = await x402Post(
7992
+ `/api/tasks/${taskId}/cancel`,
7993
+ { taskId }
7994
+ );
7995
+ printResult(result, { idempotencyKey });
7496
7996
  });
7497
7997
 
7498
7998
  // src/commands/task/refund-expired.ts
@@ -7501,11 +8001,13 @@ var refundExpiredCmd = new Command25("refund-expired").description(
7501
8001
  "Refund an expired task with no submissions back to the requester (costs 0.001 USDC)"
7502
8002
  ).argument("<taskId>", "Task ID (0x-prefixed hex)").action(async (taskId) => {
7503
8003
  try {
7504
- const result = await x402Post(`/api/tasks/${taskId}/refund-expired`, { taskId });
7505
- printResult(result);
8004
+ const { data: result, idempotencyKey } = await x402Post(
8005
+ `/api/tasks/${taskId}/refund-expired`,
8006
+ { taskId }
8007
+ );
8008
+ printResult(result, { idempotencyKey });
7506
8009
  } catch (err) {
7507
- printError(err instanceof Error ? err.message : String(err));
7508
- process.exit(1);
8010
+ renderFailure(err);
7509
8011
  }
7510
8012
  });
7511
8013
 
@@ -7515,14 +8017,16 @@ var rejectSubmissionCmd = new Command26("reject-submission").description(
7515
8017
  "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."
7516
8018
  ).argument("<taskId>", "Task ID (0x-prefixed hex)").requiredOption("--worker <address>", "Worker address whose submission to reject").action(async (taskId, opts) => {
7517
8019
  try {
7518
- const result = await x402Post(`/api/tasks/${taskId}/reject-submission`, {
7519
- taskId,
7520
- worker: opts.worker
7521
- });
7522
- printResult(result);
8020
+ const { data: result, idempotencyKey } = await x402Post(
8021
+ `/api/tasks/${taskId}/reject-submission`,
8022
+ {
8023
+ taskId,
8024
+ worker: opts.worker
8025
+ }
8026
+ );
8027
+ printResult(result, { idempotencyKey });
7523
8028
  } catch (err) {
7524
- printError(err instanceof Error ? err.message : String(err));
7525
- process.exit(1);
8029
+ renderFailure(err);
7526
8030
  }
7527
8031
  });
7528
8032
 
@@ -7544,7 +8048,8 @@ var rejectAllSubmissionsCmd = new Command27("reject-all-submissions").descriptio
7544
8048
  try {
7545
8049
  submissions = await apiGet(`/api/tasks/${taskId}/submissions`);
7546
8050
  } catch (err) {
7547
- return printError(err instanceof Error ? err.message : String(err));
8051
+ renderFailure(err);
8052
+ return;
7548
8053
  }
7549
8054
  const workers = activeSubmissionWorkers(submissions);
7550
8055
  if (workers.length === 0) {
@@ -7554,20 +8059,40 @@ var rejectAllSubmissionsCmd = new Command27("reject-all-submissions").descriptio
7554
8059
  const results = [];
7555
8060
  for (const worker of workers) {
7556
8061
  try {
7557
- const res = await x402Post(`/api/tasks/${taskId}/reject-submission`, {
7558
- taskId,
7559
- worker
7560
- });
7561
- results.push({ worker, txHash: res.txHash });
8062
+ const { data: res, idempotencyKey } = await x402Post(
8063
+ `/api/tasks/${taskId}/reject-submission`,
8064
+ { taskId, worker }
8065
+ );
8066
+ results.push({ worker, txHash: res.txHash, idempotencyKey });
7562
8067
  } catch (err) {
7563
- results.push({ worker, error: err instanceof Error ? err.message : String(err) });
8068
+ results.push({
8069
+ worker,
8070
+ error: err instanceof Error ? err.message : String(err),
8071
+ idempotencyKey: idempotencyKeyForError(err),
8072
+ cause: err
8073
+ });
7564
8074
  }
7565
8075
  }
7566
- const failed = results.filter((r) => r.error);
8076
+ const failed = results.filter((r) => r.error !== void 0);
7567
8077
  if (failed.length > 0) {
7568
- return printError(
7569
- `${failed.length} of ${results.length} rejection(s) failed: ` + failed.map((r) => `${r.worker}: ${r.error}`).join("; ")
8078
+ const representative = failed.find((r) => isPendingApiError(r.cause)) ?? failed[0];
8079
+ renderFailure(
8080
+ withErrorContext(
8081
+ representative.cause,
8082
+ `${failed.length} of ${results.length} rejection(s) failed`
8083
+ ),
8084
+ {
8085
+ details: {
8086
+ results: results.map(({ worker, txHash, idempotencyKey, error }) => ({
8087
+ worker,
8088
+ txHash,
8089
+ idempotencyKey,
8090
+ error
8091
+ }))
8092
+ }
8093
+ }
7570
8094
  );
8095
+ return;
7571
8096
  }
7572
8097
  if (!opts.cancel) {
7573
8098
  printResult({ rejected: results.length, results });
@@ -7575,11 +8100,15 @@ var rejectAllSubmissionsCmd = new Command27("reject-all-submissions").descriptio
7575
8100
  }
7576
8101
  let cancelRes;
7577
8102
  try {
7578
- cancelRes = await x402Post(`/api/tasks/${taskId}/cancel`, { taskId });
8103
+ ({ data: cancelRes } = await x402Post(
8104
+ `/api/tasks/${taskId}/cancel`,
8105
+ { taskId }
8106
+ ));
7579
8107
  } catch (err) {
7580
- return printError(
7581
- `All ${results.length} submission(s) rejected but cancel failed: ${err instanceof Error ? err.message : String(err)}`
8108
+ renderFailure(
8109
+ withErrorContext(err, `All ${results.length} submission(s) rejected but cancel failed`)
7582
8110
  );
8111
+ return;
7583
8112
  }
7584
8113
  printResult({ rejected: results.length, results, cancelTxHash: cancelRes.txHash });
7585
8114
  });
@@ -7599,8 +8128,7 @@ var mySubmissionsCmd = new Command28("my-submissions").description("List all sub
7599
8128
  );
7600
8129
  printResult(result);
7601
8130
  } catch (err) {
7602
- printError(err instanceof Error ? err.message : String(err));
7603
- process.exit(1);
8131
+ renderFailure(err);
7604
8132
  }
7605
8133
  });
7606
8134
 
@@ -7613,9 +8141,8 @@ var updateCmd = new Command29("update").description("Update an open task (costs
7613
8141
  try {
7614
8142
  body.reward = usdcToBaseUnits(opts.reward);
7615
8143
  } catch (err) {
7616
- return void printError(
7617
- `Invalid --reward: ${err instanceof Error ? err.message : String(err)}`
7618
- );
8144
+ renderFailure(withErrorContext(err, "Invalid --reward"));
8145
+ return;
7619
8146
  }
7620
8147
  }
7621
8148
  if (opts.extendExpiry !== void 0) {
@@ -7624,10 +8151,12 @@ var updateCmd = new Command29("update").description("Update an open task (costs
7624
8151
  printError(
7625
8152
  `--extend-expiry must be a positive integer (seconds), got: ${opts.extendExpiry}`
7626
8153
  );
8154
+ return;
7627
8155
  }
7628
8156
  const task = await apiGet(`/api/tasks/${taskId}`);
7629
8157
  if (!task) {
7630
8158
  printError(`Task not found: ${taskId}`);
8159
+ return;
7631
8160
  }
7632
8161
  const currentExpiry = Math.floor(new Date(task.expiryTime).getTime() / 1e3);
7633
8162
  const newExpiry = currentExpiry + delta;
@@ -7637,6 +8166,7 @@ var updateCmd = new Command29("update").description("Update an open task (costs
7637
8166
  printError(
7638
8167
  `--extend-expiry would set expiry in the past. Task expired ${expiredAgo}s ago; pass at least ${expiredAgo + 1} seconds to extend beyond now.`
7639
8168
  );
8169
+ return;
7640
8170
  }
7641
8171
  body.expiryTime = newExpiry;
7642
8172
  }
@@ -7644,6 +8174,7 @@ var updateCmd = new Command29("update").description("Update an open task (costs
7644
8174
  const ts = Math.floor(new Date(opts.bidDeadline).getTime() / 1e3);
7645
8175
  if (ts <= Math.floor(Date.now() / 1e3)) {
7646
8176
  printError("--bid-deadline must be in the future");
8177
+ return;
7647
8178
  } else {
7648
8179
  body.bidDeadline = ts;
7649
8180
  }
@@ -7652,6 +8183,7 @@ var updateCmd = new Command29("update").description("Update an open task (costs
7652
8183
  const ts = Math.floor(new Date(opts.pitchDeadline).getTime() / 1e3);
7653
8184
  if (ts <= Math.floor(Date.now() / 1e3)) {
7654
8185
  printError("--pitch-deadline must be in the future");
8186
+ return;
7655
8187
  } else {
7656
8188
  body.pitchDeadline = ts;
7657
8189
  }
@@ -7660,18 +8192,16 @@ var updateCmd = new Command29("update").description("Update an open task (costs
7660
8192
  try {
7661
8193
  body.auctionFloorPrice = usdcToBaseUnits(opts.auctionFloorPrice, { allowZero: true });
7662
8194
  } catch (err) {
7663
- return void printError(
7664
- `Invalid --auction-floor-price: ${err instanceof Error ? err.message : String(err)}`
7665
- );
8195
+ renderFailure(withErrorContext(err, "Invalid --auction-floor-price"));
8196
+ return;
7666
8197
  }
7667
8198
  }
7668
8199
  if (opts.auctionStartPrice !== void 0) {
7669
8200
  try {
7670
8201
  body.auctionStartPrice = usdcToBaseUnits(opts.auctionStartPrice, { allowZero: true });
7671
8202
  } catch (err) {
7672
- return void printError(
7673
- `Invalid --auction-start-price: ${err instanceof Error ? err.message : String(err)}`
7674
- );
8203
+ renderFailure(withErrorContext(err, "Invalid --auction-start-price"));
8204
+ return;
7675
8205
  }
7676
8206
  }
7677
8207
  if (opts.description !== void 0) {
@@ -7683,8 +8213,11 @@ var updateCmd = new Command29("update").description("Update an open task (costs
7683
8213
  if (opts.metricDescription !== void 0) {
7684
8214
  body.metricDescription = opts.metricDescription;
7685
8215
  }
7686
- const result = await x402Post(`/api/tasks/${taskId}/update`, body);
7687
- printResult(result);
8216
+ const { data: result, idempotencyKey } = await x402Post(
8217
+ `/api/tasks/${taskId}/update`,
8218
+ body
8219
+ );
8220
+ printResult(result, { idempotencyKey });
7688
8221
  }
7689
8222
  );
7690
8223
 
@@ -7695,20 +8228,77 @@ var forfeitCmd = new Command30("forfeit").description("Reclaim a claim-mode task
7695
8228
  const keystore = await loadKeystore();
7696
8229
  const message = buildForfeitMessage(taskId);
7697
8230
  const signature = await signMessage(message, keystore);
7698
- const result = await apiPost(`/api/tasks/${taskId}/forfeit`, {
7699
- taskId,
7700
- requesterAddress: keystore.walletAddress,
7701
- signature
7702
- });
7703
- printResult(result);
8231
+ const { data: result, idempotencyKey } = await apiPost(
8232
+ `/api/tasks/${taskId}/forfeit`,
8233
+ {
8234
+ taskId,
8235
+ requesterAddress: keystore.walletAddress,
8236
+ signature
8237
+ }
8238
+ );
8239
+ printResult(result, { idempotencyKey });
7704
8240
  } catch (err) {
7705
- printError(err instanceof Error ? err.message : String(err));
8241
+ renderFailure(err);
7706
8242
  }
7707
8243
  });
7708
8244
 
7709
- // src/commands/task/evaluate.ts
8245
+ // src/commands/task/assign-evaluator.ts
7710
8246
  import { Command as Command31 } from "commander";
7711
- 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(
8247
+ var ADDRESS_PATTERN = /^0x[0-9a-fA-F]{40}$/;
8248
+ var assignEvaluatorCmd = new Command31("assign-evaluator").description(
8249
+ "Assign an evaluator to an open, unclaimed task you requested (costs 0.001 USDC). Only possible before a worker claims the task."
8250
+ ).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(
8251
+ async (taskId, opts) => {
8252
+ if (!ADDRESS_PATTERN.test(opts.evaluator)) {
8253
+ return void printError(`--evaluator must be a wallet address, got: ${opts.evaluator}`);
8254
+ }
8255
+ if (opts.disputeResolver !== void 0 && !ADDRESS_PATTERN.test(opts.disputeResolver)) {
8256
+ return void printError(
8257
+ `--dispute-resolver must be a wallet address, got: ${opts.disputeResolver}`
8258
+ );
8259
+ }
8260
+ const body = { taskId, evaluator: opts.evaluator };
8261
+ if (opts.evaluatorFeeBps !== void 0) {
8262
+ const bps = Number(opts.evaluatorFeeBps);
8263
+ if (!Number.isInteger(bps) || bps < 0 || bps > 1e4) {
8264
+ return void printError(
8265
+ `--evaluator-fee-bps must be an integer between 0 and 10000, got: ${opts.evaluatorFeeBps}`
8266
+ );
8267
+ }
8268
+ body.evaluatorFeeBps = bps;
8269
+ }
8270
+ if (opts.evaluationWindow !== void 0) {
8271
+ const hours = Number(opts.evaluationWindow);
8272
+ if (!Number.isFinite(hours) || hours <= 0) {
8273
+ return void printError(
8274
+ `--evaluation-window must be a positive number of hours, got: ${opts.evaluationWindow}`
8275
+ );
8276
+ }
8277
+ body.evaluationWindowHours = hours;
8278
+ }
8279
+ if (opts.appealWindow !== void 0) {
8280
+ const hours = Number(opts.appealWindow);
8281
+ if (!Number.isFinite(hours) || hours <= 0) {
8282
+ return void printError(
8283
+ `--appeal-window must be a positive number of hours, got: ${opts.appealWindow}`
8284
+ );
8285
+ }
8286
+ body.appealWindowHours = hours;
8287
+ }
8288
+ if (opts.disputeResolver !== void 0) {
8289
+ body.disputeResolver = opts.disputeResolver;
8290
+ }
8291
+ const { data: result, idempotencyKey } = await x402Post(
8292
+ `/api/tasks/${taskId}/evaluator`,
8293
+ body
8294
+ );
8295
+ printResult(result, { idempotencyKey });
8296
+ }
8297
+ );
8298
+
8299
+ // src/commands/task/evaluate.ts
8300
+ import { Command as Command32 } from "commander";
8301
+ 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(
7712
8302
  "--award <worker:amount:rank>",
7713
8303
  "Award entry (repeatable): worker:amountUSDC:rank",
7714
8304
  collect,
@@ -7737,9 +8327,8 @@ var evaluateCmd = new Command31("evaluate").description("Submit an evaluation ve
7737
8327
  try {
7738
8328
  microUnits = usdcToBaseUnits(amountUSDC);
7739
8329
  } catch (err) {
7740
- return void printError(
7741
- `Invalid amount in '${entry}': ${err instanceof Error ? err.message : String(err)}`
7742
- );
8330
+ renderFailure(withErrorContext(err, `Invalid amount in '${entry}'`));
8331
+ return;
7743
8332
  }
7744
8333
  const parsedRank = Number(rankStr);
7745
8334
  if (!Number.isInteger(parsedRank) || parsedRank < 1) {
@@ -7762,8 +8351,11 @@ var evaluateCmd = new Command31("evaluate").description("Submit an evaluation ve
7762
8351
  }
7763
8352
  body.evidenceHash = opts.evidenceHash;
7764
8353
  }
7765
- const result = await x402Post(`/api/tasks/${taskId}/evaluate`, body);
7766
- printResult({ txHash: result.txHash });
8354
+ const { data: result, idempotencyKey } = await x402Post(
8355
+ `/api/tasks/${taskId}/evaluate`,
8356
+ body
8357
+ );
8358
+ printResult({ txHash: result.txHash }, { idempotencyKey });
7767
8359
  }
7768
8360
  );
7769
8361
  function collect(val, prev) {
@@ -7771,28 +8363,28 @@ function collect(val, prev) {
7771
8363
  }
7772
8364
 
7773
8365
  // src/commands/task/appeal.ts
7774
- import { Command as Command32 } from "commander";
7775
- var appealCmd = new Command32("appeal").description("Appeal an evaluator verdict while in Appealing state").argument("<taskId>", "Task ID (0x-prefixed hex)").action(async (taskId) => {
7776
- const result = await x402Post(`/api/tasks/${taskId}/appeal`, { taskId });
7777
- printResult({ txHash: result.txHash });
8366
+ import { Command as Command33 } from "commander";
8367
+ var appealCmd = new Command33("appeal").description("Appeal an evaluator verdict while in Appealing state").argument("<taskId>", "Task ID (0x-prefixed hex)").action(async (taskId) => {
8368
+ const { data: result, idempotencyKey } = await x402Post(`/api/tasks/${taskId}/appeal`, { taskId });
8369
+ printResult({ txHash: result.txHash }, { idempotencyKey });
7778
8370
  });
7779
8371
 
7780
8372
  // src/commands/task/evaluator-timeout.ts
7781
- import { Command as Command33 } from "commander";
7782
- 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) => {
7783
- const result = await x402Post(`/api/tasks/${taskId}/evaluator-timeout`, { taskId });
7784
- printResult({ txHash: result.txHash });
8373
+ import { Command as Command34 } from "commander";
8374
+ 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) => {
8375
+ const { data: result, idempotencyKey } = await x402Post(`/api/tasks/${taskId}/evaluator-timeout`, { taskId });
8376
+ printResult({ txHash: result.txHash }, { idempotencyKey });
7785
8377
  });
7786
8378
 
7787
8379
  // src/commands/task/finalize-verdict.ts
7788
- import { Command as Command34 } from "commander";
7789
- 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) => {
7790
- const result = await apiPost(`/api/tasks/${taskId}/finalize-verdict`, { taskId });
7791
- printResult({ txHash: result.txHash });
8380
+ import { Command as Command35 } from "commander";
8381
+ 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) => {
8382
+ const { data: result, idempotencyKey } = await apiPost(`/api/tasks/${taskId}/finalize-verdict`, { taskId });
8383
+ printResult({ txHash: result.txHash }, { idempotencyKey });
7792
8384
  });
7793
8385
 
7794
8386
  // src/commands/task/resolve-dispute.ts
7795
- import { Command as Command35 } from "commander";
8387
+ import { Command as Command36 } from "commander";
7796
8388
  function parseAward(spec) {
7797
8389
  const parts = spec.split(":");
7798
8390
  if (parts.length !== 3) {
@@ -7813,7 +8405,7 @@ function parseAward(spec) {
7813
8405
  rank
7814
8406
  };
7815
8407
  }
7816
- var resolveDisputeCmd = new Command35("resolve-dispute").description(
8408
+ var resolveDisputeCmd = new Command36("resolve-dispute").description(
7817
8409
  "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"
7818
8410
  ).argument("<taskId>", "Task ID (0x-prefixed hex)").requiredOption("--verdict <type>", "Verdict type: approve or partial").requiredOption(
7819
8411
  "--award <spec...>",
@@ -7827,76 +8419,80 @@ var resolveDisputeCmd = new Command35("resolve-dispute").description(
7827
8419
  try {
7828
8420
  awards = opts.award.map(parseAward);
7829
8421
  } catch (err) {
7830
- printError(err instanceof Error ? err.message : String(err));
8422
+ renderFailure(err);
7831
8423
  return;
7832
8424
  }
7833
- const result = await x402Post(`/api/tasks/${taskId}/resolve-dispute`, {
7834
- taskId,
7835
- verdict: opts.verdict,
7836
- awards
7837
- });
7838
- printResult({ txHash: result.txHash });
8425
+ const { data: result, idempotencyKey } = await x402Post(
8426
+ `/api/tasks/${taskId}/resolve-dispute`,
8427
+ {
8428
+ taskId,
8429
+ verdict: opts.verdict,
8430
+ awards
8431
+ }
8432
+ );
8433
+ printResult({ txHash: result.txHash }, { idempotencyKey });
7839
8434
  });
7840
8435
 
7841
8436
  // src/commands/task/unlock.ts
7842
- import { Command as Command36 } from "commander";
7843
- 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) => {
8437
+ import { Command as Command37 } from "commander";
8438
+ 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) => {
7844
8439
  let result;
8440
+ let idempotencyKey;
7845
8441
  try {
7846
- result = await apiPost(`/api/tasks/${taskId}/private-access/verify`, {
7847
- taskId,
7848
- password: opts.password
7849
- });
8442
+ ({ data: result, idempotencyKey } = await apiPost(
8443
+ `/api/tasks/${taskId}/private-access/verify`,
8444
+ { taskId, password: opts.password }
8445
+ ));
7850
8446
  } catch (err) {
7851
- printError(err instanceof Error ? err.message : "Failed to unlock task.");
8447
+ renderFailure(err, { fallback: "Failed to unlock task." });
7852
8448
  return;
7853
8449
  }
7854
8450
  await saveTaskAccessGrant(taskId, result.grant, result.expiresAt);
7855
- printResult({ taskId, expiresAt: result.expiresAt });
8451
+ printResult({ taskId, expiresAt: result.expiresAt }, { idempotencyKey });
7856
8452
  });
7857
8453
 
7858
8454
  // src/commands/task/invite.ts
7859
- import { Command as Command37 } from "commander";
7860
- 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) => {
8455
+ import { Command as Command38 } from "commander";
8456
+ 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) => {
7861
8457
  const auth = await signReadAuth();
7862
8458
  if (!auth || Object.keys(auth.headers).length === 0) {
7863
8459
  printError("No keystore found. Run `taskmarket init` first.");
7864
8460
  return;
7865
8461
  }
7866
8462
  try {
7867
- const result = await apiPost(
8463
+ const { data: result, idempotencyKey } = await apiPost(
7868
8464
  `/api/tasks/${taskId}/private-access/viewers`,
7869
8465
  { taskId, viewerAddress: address },
7870
8466
  { headers: auth.headers }
7871
8467
  );
7872
- printResult(result);
8468
+ printResult(result, { idempotencyKey });
7873
8469
  } catch (err) {
7874
- printError(err instanceof Error ? err.message : "Failed to invite viewer.");
8470
+ renderFailure(err, { fallback: "Failed to invite viewer." });
7875
8471
  }
7876
8472
  });
7877
8473
 
7878
8474
  // src/commands/task/uninvite.ts
7879
- import { Command as Command38 } from "commander";
7880
- 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) => {
8475
+ import { Command as Command39 } from "commander";
8476
+ 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) => {
7881
8477
  const auth = await signReadAuth();
7882
8478
  if (!auth || Object.keys(auth.headers).length === 0) {
7883
8479
  printError("No keystore found. Run `taskmarket init` first.");
7884
8480
  return;
7885
8481
  }
7886
8482
  try {
7887
- const result = await apiDelete(
8483
+ const { data: result, idempotencyKey } = await apiDelete(
7888
8484
  `/api/tasks/${taskId}/private-access/viewers/${encodeURIComponent(address)}`,
7889
8485
  { headers: auth.headers }
7890
8486
  );
7891
- printResult(result);
8487
+ printResult(result, { idempotencyKey });
7892
8488
  } catch (err) {
7893
- printError(err instanceof Error ? err.message : "Failed to remove viewer.");
8489
+ renderFailure(err, { fallback: "Failed to remove viewer." });
7894
8490
  }
7895
8491
  });
7896
8492
 
7897
8493
  // src/commands/task/viewers.ts
7898
- import { Command as Command39 } from "commander";
7899
- 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) => {
8494
+ import { Command as Command40 } from "commander";
8495
+ 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) => {
7900
8496
  const auth = await signReadAuth();
7901
8497
  if (!auth || Object.keys(auth.headers).length === 0) {
7902
8498
  printError("No keystore found. Run `taskmarket init` first.");
@@ -7908,12 +8504,12 @@ var viewersCmd = new Command39("viewers").description("List a private task's wal
7908
8504
  });
7909
8505
  printResult(result);
7910
8506
  } catch (err) {
7911
- printError(err instanceof Error ? err.message : "Failed to list viewers.");
8507
+ renderFailure(err, { fallback: "Failed to list viewers." });
7912
8508
  }
7913
8509
  });
7914
8510
 
7915
8511
  // src/commands/task/index.ts
7916
- var taskCommand = new Command40("task").description("Manage tasks");
8512
+ var taskCommand = new Command41("task").description("Manage tasks");
7917
8513
  taskCommand.addCommand(createCmd);
7918
8514
  taskCommand.addCommand(listCmd);
7919
8515
  taskCommand.addCommand(getCmd);
@@ -7939,6 +8535,7 @@ taskCommand.addCommand(rejectAllSubmissionsCmd);
7939
8535
  taskCommand.addCommand(mySubmissionsCmd);
7940
8536
  taskCommand.addCommand(updateCmd);
7941
8537
  taskCommand.addCommand(forfeitCmd);
8538
+ taskCommand.addCommand(assignEvaluatorCmd);
7942
8539
  taskCommand.addCommand(evaluateCmd);
7943
8540
  taskCommand.addCommand(appealCmd);
7944
8541
  taskCommand.addCommand(evaluatorTimeoutCmd);
@@ -7950,8 +8547,8 @@ taskCommand.addCommand(uninviteCmd);
7950
8547
  taskCommand.addCommand(viewersCmd);
7951
8548
 
7952
8549
  // src/commands/agents.ts
7953
- import { Command as Command41 } from "commander";
7954
- 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) => {
8550
+ import { Command as Command42 } from "commander";
8551
+ 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) => {
7955
8552
  const params = new URLSearchParams();
7956
8553
  if (opts.sort) params.set("sort", opts.sort);
7957
8554
  if (opts.skill) params.set("skill", opts.skill);
@@ -7962,11 +8559,12 @@ var agentsCommand = new Command41("agents").description("Browse the agent direct
7962
8559
  });
7963
8560
 
7964
8561
  // src/commands/inbox.ts
7965
- import { Command as Command42 } from "commander";
7966
- var inboxCommand = new Command42("inbox").description("Show tasks you created and tasks you are working on").action(async () => {
8562
+ import { Command as Command43 } from "commander";
8563
+ var inboxCommand = new Command43("inbox").description("Show tasks you created and tasks you are working on").action(async () => {
7967
8564
  const auth = await signReadAuth();
7968
8565
  if (!auth) {
7969
8566
  printError("No keystore found. Run `taskmarket init` first.");
8567
+ return;
7970
8568
  }
7971
8569
  const address = auth.walletAddress;
7972
8570
  const hasReadAuth = Object.keys(auth.headers).length > 0;
@@ -7982,23 +8580,38 @@ var inboxCommand = new Command42("inbox").description("Show tasks you created an
7982
8580
  printResult({ ...taskResult, pendingBids });
7983
8581
  });
7984
8582
 
8583
+ // src/commands/actions.ts
8584
+ import { Command as Command44 } from "commander";
8585
+ var actionsCommand = new Command44("actions").description("Show lifecycle actions awaiting you, grouped and prioritized by the server").action(async () => {
8586
+ const auth = await signReadAuth();
8587
+ if (!auth) {
8588
+ printError("No keystore found. Run `taskmarket init` first.");
8589
+ return;
8590
+ }
8591
+ const params = new URLSearchParams({ address: auth.walletAddress });
8592
+ const queue = await apiGet(`/api/agents/action-queue?${params.toString()}`, {
8593
+ headers: auth.headers
8594
+ });
8595
+ printResult(queue);
8596
+ });
8597
+
7985
8598
  // src/commands/deposit.ts
7986
- import { Command as Command43 } from "commander";
7987
- var depositCommand = new Command43("deposit").description("Show wallet address and network info for funding").action(async () => {
8599
+ import { Command as Command45 } from "commander";
8600
+ var depositCommand = new Command45("deposit").description("Show wallet address and network info for funding").action(async () => {
7988
8601
  let address;
7989
8602
  try {
7990
8603
  address = await getWalletAddress();
7991
8604
  } catch (err) {
7992
- const msg = err instanceof Error ? err.message : "No keystore found. Run `taskmarket init` first.";
7993
- printError(msg);
8605
+ renderFailure(err, { fallback: "No keystore found. Run `taskmarket init` first." });
8606
+ return;
7994
8607
  }
7995
8608
  let networkInfo;
7996
8609
  try {
7997
8610
  const response = await apiGet("/trpc/network.info");
7998
8611
  networkInfo = response.result.data;
7999
8612
  } catch (err) {
8000
- const msg = err instanceof Error ? err.message : "Failed to fetch network info from backend.";
8001
- printError(msg);
8613
+ renderFailure(err, { fallback: "Failed to fetch network info from backend." });
8614
+ return;
8002
8615
  }
8003
8616
  const data = {
8004
8617
  address,
@@ -8011,11 +8624,11 @@ var depositCommand = new Command43("deposit").description("Show wallet address a
8011
8624
  });
8012
8625
 
8013
8626
  // src/commands/wallet/index.ts
8014
- import { Command as Command49 } from "commander";
8627
+ import { Command as Command51 } from "commander";
8015
8628
 
8016
8629
  // src/commands/wallet/balance.ts
8017
- import { Command as Command44 } from "commander";
8018
- var walletBalanceCommand = new Command44("balance").description("Show USDC balance").option("--address <addr>", "Address to check (defaults to own wallet)").action(async (opts) => {
8630
+ import { Command as Command46 } from "commander";
8631
+ var walletBalanceCommand = new Command46("balance").description("Show USDC balance").option("--address <addr>", "Address to check (defaults to own wallet)").action(async (opts) => {
8019
8632
  let address = opts.address;
8020
8633
  if (!address) {
8021
8634
  const keystore = await loadKeystore();
@@ -8026,7 +8639,7 @@ var walletBalanceCommand = new Command44("balance").description("Show USDC balan
8026
8639
  });
8027
8640
 
8028
8641
  // src/commands/wallet/import.ts
8029
- import { Command as Command45 } from "commander";
8642
+ import { Command as Command47 } from "commander";
8030
8643
  import { createInterface as createInterface2 } from "readline";
8031
8644
  import { privateKeyToAccount as privateKeyToAccount4 } from "viem/accounts";
8032
8645
  function normalizePrivateKey(raw) {
@@ -8061,7 +8674,7 @@ function promptHiddenInput(prompt) {
8061
8674
  });
8062
8675
  });
8063
8676
  }
8064
- 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(
8677
+ var walletImportCommand = new Command47("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(
8065
8678
  "--yes",
8066
8679
  "Confirm legal acceptance non-interactively after reviewing every current policy"
8067
8680
  ).action(async (opts) => {
@@ -8140,69 +8753,77 @@ var walletImportCommand = new Command45("import").description("Import an existin
8140
8753
  });
8141
8754
 
8142
8755
  // src/commands/wallet/set-withdrawal-address.ts
8143
- import { Command as Command46 } from "commander";
8756
+ import { Command as Command48 } from "commander";
8144
8757
  function isValidAddress(addr) {
8145
8758
  return /^0x[a-fA-F0-9]{40}$/.test(addr);
8146
8759
  }
8147
- 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) => {
8760
+ var setWithdrawalAddressCommand = new Command48("set-withdrawal-address").description("Set a withdrawal address for USDC withdrawals (one-time, free)").argument("<address>", "Ethereum address to receive withdrawals").action(async (address) => {
8148
8761
  if (!isValidAddress(address)) {
8149
8762
  printError("Invalid Ethereum address: must be 0x followed by 40 hex characters");
8763
+ return;
8150
8764
  }
8151
8765
  let keystore;
8152
8766
  try {
8153
8767
  keystore = await loadKeystore();
8154
8768
  } catch {
8155
8769
  printError("No keystore found. Run `taskmarket init` first.");
8770
+ return;
8156
8771
  }
8157
8772
  const walletAddress = keystore.walletAddress;
8158
8773
  const message = buildSetWithdrawalAddressMessage(address);
8159
8774
  const signature = await signMessage(message, keystore);
8160
- const result = await apiPost("/api/wallet/set-withdrawal-address", {
8161
- walletAddress,
8162
- withdrawalAddress: address,
8163
- signature
8164
- });
8165
- printResult({ withdrawalAddress: result.withdrawalAddress });
8775
+ const { data: result, idempotencyKey } = await apiPost(
8776
+ "/api/wallet/set-withdrawal-address",
8777
+ {
8778
+ walletAddress,
8779
+ withdrawalAddress: address,
8780
+ signature
8781
+ }
8782
+ );
8783
+ printResult({ withdrawalAddress: result.withdrawalAddress }, { idempotencyKey });
8166
8784
  });
8167
8785
 
8168
8786
  // src/commands/wallet/publish-key.ts
8169
- import { Command as Command47 } from "commander";
8170
- var publishKeyCommand = new Command47("publish-key").description("Publish your secp256k1 public key so others can encrypt files for you").action(async () => {
8787
+ import { Command as Command49 } from "commander";
8788
+ var publishKeyCommand = new Command49("publish-key").description("Publish your secp256k1 public key so others can encrypt files for you").action(async () => {
8171
8789
  let keystore;
8172
8790
  try {
8173
8791
  keystore = await loadKeystore();
8174
8792
  } catch {
8175
8793
  printError("No keystore found. Run `taskmarket init` first.");
8794
+ return;
8176
8795
  }
8177
8796
  const dek = await fetchDeviceKey(keystore.deviceId, keystore.apiToken);
8178
8797
  const privateKey = decryptPrivateKey(dek, keystore.encryptedKey);
8179
8798
  const publicKey = deriveCompressedPublicKey(privateKey);
8180
- const result = await apiPost("/trpc/agents.setPublicKey", {
8799
+ const { data: result, idempotencyKey } = await apiPost("/trpc/agents.setPublicKey", {
8181
8800
  deviceId: keystore.deviceId,
8182
8801
  apiToken: keystore.apiToken,
8183
8802
  publicKey
8184
8803
  });
8185
- printResult({ publicKey: result.result.data.publicKey });
8804
+ printResult({ publicKey: result.result.data.publicKey }, { idempotencyKey });
8186
8805
  });
8187
8806
 
8188
8807
  // src/commands/wallet/withdraw-dreams.ts
8189
- import { Command as Command48 } from "commander";
8808
+ import { Command as Command50 } from "commander";
8190
8809
  import { toHex as toHex2 } from "viem";
8191
8810
  var AUTHORIZATION_VALIDITY_SECONDS = 300;
8192
8811
  function isValidAddress2(addr) {
8193
8812
  return /^0x[a-fA-F0-9]{40}$/.test(addr);
8194
8813
  }
8195
- 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) => {
8814
+ var withdrawDreamsCommand = new Command50("withdraw-dreams").description("Withdraw accumulated DREAMS rewards to the withdrawal address").option("--destination <addr>", "Destination address (defaults to registered withdrawal address)").action(async (opts) => {
8196
8815
  let keystore;
8197
8816
  try {
8198
8817
  keystore = await loadKeystore();
8199
8818
  } catch {
8200
8819
  printError("No keystore found. Run `taskmarket init` first.");
8820
+ return;
8201
8821
  }
8202
8822
  const workerAddress = keystore.walletAddress;
8203
8823
  let destination = opts.destination;
8204
8824
  if (destination !== void 0 && !isValidAddress2(destination)) {
8205
8825
  printError("Invalid Ethereum address: must be 0x followed by 40 hex characters");
8826
+ return;
8206
8827
  }
8207
8828
  if (!destination) {
8208
8829
  const addressResponse = await apiGet(
@@ -8212,6 +8833,7 @@ var withdrawDreamsCommand = new Command48("withdraw-dreams").description("Withdr
8212
8833
  printError(
8213
8834
  "No withdrawal address set. Run: taskmarket wallet set-withdrawal-address <address>"
8214
8835
  );
8836
+ return;
8215
8837
  }
8216
8838
  destination = addressResponse.withdrawalAddress;
8217
8839
  }
@@ -8219,25 +8841,31 @@ var withdrawDreamsCommand = new Command48("withdraw-dreams").description("Withdr
8219
8841
  const validBefore = String(Math.floor(Date.now() / 1e3) + AUTHORIZATION_VALIDITY_SECONDS);
8220
8842
  const message = buildWithdrawDreamsMessage(destination, nonce, validBefore);
8221
8843
  const signature = await signMessage(message, keystore);
8222
- const result = await apiPost("/api/wallet/withdraw-dreams", {
8223
- workerAddress,
8224
- destination,
8225
- nonce,
8226
- validBefore,
8227
- signature
8228
- });
8229
- printResult({
8230
- txHash: result.txHash,
8231
- destination: result.destination,
8232
- claimedBaseUnits: result.claimedBaseUnits,
8233
- claimedDreams: formatDreams(result.claimedBaseUnits),
8234
- dreamsPerUsdc: result.dreamsPerUsdc,
8235
- usdEquivalent: result.usdEquivalent
8236
- });
8844
+ const { data: result, idempotencyKey } = await apiPost(
8845
+ "/api/wallet/withdraw-dreams",
8846
+ {
8847
+ workerAddress,
8848
+ destination,
8849
+ nonce,
8850
+ validBefore,
8851
+ signature
8852
+ }
8853
+ );
8854
+ printResult(
8855
+ {
8856
+ txHash: result.txHash,
8857
+ destination: result.destination,
8858
+ claimedBaseUnits: result.claimedBaseUnits,
8859
+ claimedDreams: formatDreams(result.claimedBaseUnits),
8860
+ dreamsPerUsdc: result.dreamsPerUsdc,
8861
+ usdEquivalent: result.usdEquivalent
8862
+ },
8863
+ { idempotencyKey }
8864
+ );
8237
8865
  });
8238
8866
 
8239
8867
  // src/commands/wallet/index.ts
8240
- var walletCommand = new Command49("wallet").description("Wallet management commands");
8868
+ var walletCommand = new Command51("wallet").description("Wallet management commands");
8241
8869
  walletCommand.addCommand(walletBalanceCommand);
8242
8870
  walletCommand.addCommand(walletImportCommand);
8243
8871
  walletCommand.addCommand(setWithdrawalAddressCommand);
@@ -8245,7 +8873,7 @@ walletCommand.addCommand(publishKeyCommand);
8245
8873
  walletCommand.addCommand(withdrawDreamsCommand);
8246
8874
 
8247
8875
  // src/commands/withdraw.ts
8248
- import { Command as Command50 } from "commander";
8876
+ import { Command as Command52 } from "commander";
8249
8877
  import { toHex as toHex3 } from "viem";
8250
8878
  var USDC_TYPES = {
8251
8879
  TransferWithAuthorization: [
@@ -8257,10 +8885,11 @@ var USDC_TYPES = {
8257
8885
  { name: "nonce", type: "bytes32" }
8258
8886
  ]
8259
8887
  };
8260
- 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) => {
8888
+ var withdrawCommand = new Command52("withdraw").description("Withdraw USDC to the registered withdrawal address").argument("<amount>", "Amount in USDC (e.g. 5 for 5 USDC)").action(async (amount) => {
8261
8889
  const parsed = parseFloat(amount);
8262
8890
  if (isNaN(parsed) || parsed <= 0) {
8263
8891
  printError("Invalid amount: must be a positive number (e.g. 5 for 5 USDC)");
8892
+ return;
8264
8893
  }
8265
8894
  const amountBaseUnits = String(Math.round(parsed * 1e6));
8266
8895
  let keystore;
@@ -8268,6 +8897,7 @@ var withdrawCommand = new Command50("withdraw").description("Withdraw USDC to th
8268
8897
  keystore = await loadKeystore();
8269
8898
  } catch {
8270
8899
  printError("No keystore found. Run `taskmarket init` first.");
8900
+ return;
8271
8901
  }
8272
8902
  const from = keystore.walletAddress;
8273
8903
  const addressResponse = await apiGet(
@@ -8277,6 +8907,7 @@ var withdrawCommand = new Command50("withdraw").description("Withdraw USDC to th
8277
8907
  printError(
8278
8908
  "No withdrawal address set. Run: taskmarket wallet set-withdrawal-address <address>"
8279
8909
  );
8910
+ return;
8280
8911
  }
8281
8912
  const { withdrawalAddress, usdcDomain } = addressResponse;
8282
8913
  const now = Math.floor(Date.now() / 1e3);
@@ -8297,30 +8928,38 @@ var withdrawCommand = new Command50("withdraw").description("Withdraw USDC to th
8297
8928
  },
8298
8929
  keystore
8299
8930
  );
8300
- const result = await apiPost("/api/wallet/withdraw", {
8301
- from,
8302
- amountBaseUnits,
8303
- authorization,
8304
- signature
8305
- });
8306
- printResult({ txHash: result.txHash, amountBaseUnits: result.amountBaseUnits, to: result.to });
8931
+ const { data: result, idempotencyKey } = await apiPost(
8932
+ "/api/wallet/withdraw",
8933
+ {
8934
+ from,
8935
+ amountBaseUnits,
8936
+ authorization,
8937
+ signature
8938
+ }
8939
+ );
8940
+ printResult(
8941
+ { txHash: result.txHash, amountBaseUnits: result.amountBaseUnits, to: result.to },
8942
+ { idempotencyKey }
8943
+ );
8307
8944
  });
8308
8945
 
8309
8946
  // src/commands/encrypt.ts
8310
- import { Command as Command51 } from "commander";
8947
+ import { Command as Command53 } from "commander";
8311
8948
  import { promises as fs3 } from "fs";
8312
- 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) => {
8949
+ var encryptCommand = new Command53("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) => {
8313
8950
  let plaintext;
8314
8951
  try {
8315
8952
  plaintext = await fs3.readFile(file);
8316
8953
  } catch {
8317
8954
  printError(`Cannot read file: ${file}`);
8955
+ return;
8318
8956
  }
8319
8957
  let keystore;
8320
8958
  try {
8321
8959
  keystore = await loadKeystore();
8322
8960
  } catch {
8323
8961
  printError("No keystore found. Run `taskmarket init` first.");
8962
+ return;
8324
8963
  }
8325
8964
  let recipientPubKey;
8326
8965
  let recipientAddress;
@@ -8333,12 +8972,9 @@ var encryptCommand = new Command51("encrypt").description("Encrypt a file with E
8333
8972
  );
8334
8973
  } catch (err) {
8335
8974
  const msg = err instanceof Error ? err.message : String(err);
8336
- if (msg.includes("publish their public key") || msg.includes("NOT_FOUND")) {
8337
- printError(
8338
- `Recipient has not published their public key. Ask them to run: taskmarket wallet publish-key`
8339
- );
8340
- }
8341
- printError(`Failed to fetch recipient public key: ${msg}`);
8975
+ 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";
8976
+ renderFailure(withErrorContext(err, context));
8977
+ return;
8342
8978
  }
8343
8979
  recipientPubKey = result.result.data.publicKey;
8344
8980
  } else {
@@ -8358,20 +8994,22 @@ var encryptCommand = new Command51("encrypt").description("Encrypt a file with E
8358
8994
  });
8359
8995
 
8360
8996
  // src/commands/decrypt.ts
8361
- import { Command as Command52 } from "commander";
8997
+ import { Command as Command54 } from "commander";
8362
8998
  import { promises as fs4 } from "fs";
8363
- 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) => {
8999
+ var decryptCommand = new Command54("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) => {
8364
9000
  let ciphertext;
8365
9001
  try {
8366
9002
  ciphertext = await fs4.readFile(file);
8367
9003
  } catch {
8368
9004
  printError(`Cannot read file: ${file}`);
9005
+ return;
8369
9006
  }
8370
9007
  let keystore;
8371
9008
  try {
8372
9009
  keystore = await loadKeystore();
8373
9010
  } catch {
8374
9011
  printError("No keystore found. Run `taskmarket init` first.");
9012
+ return;
8375
9013
  }
8376
9014
  const dek = await fetchDeviceKey(keystore.deviceId, keystore.apiToken);
8377
9015
  const privateKey = decryptPrivateKey(dek, keystore.encryptedKey);
@@ -8379,8 +9017,8 @@ var decryptCommand = new Command52("decrypt").description("Decrypt a file using
8379
9017
  try {
8380
9018
  plaintext = decryptWithPrivateKey(ciphertext, privateKey);
8381
9019
  } catch (err) {
8382
- const msg = err instanceof Error ? err.message : String(err);
8383
- printError(msg);
9020
+ renderFailure(err);
9021
+ return;
8384
9022
  }
8385
9023
  let outputPath;
8386
9024
  if (opts.output) {
@@ -8398,21 +9036,21 @@ var decryptCommand = new Command52("decrypt").description("Decrypt a file using
8398
9036
  });
8399
9037
 
8400
9038
  // src/commands/xmtp.ts
8401
- import { Command as Command53 } from "commander";
9039
+ import { Command as Command55 } from "commander";
8402
9040
 
8403
9041
  // src/lib/xmtp-client.ts
8404
9042
  import os3 from "os";
8405
9043
  import path3 from "path";
8406
- import { randomUUID as randomUUID2, hkdfSync as hkdfSync2 } from "crypto";
9044
+ import { randomUUID as randomUUID3, hkdfSync as hkdfSync2 } from "crypto";
8407
9045
  import { privateKeyToAccount as privateKeyToAccount5 } from "viem/accounts";
8408
9046
 
8409
9047
  // src/lib/xmtp-envelope.ts
8410
- import { randomUUID } from "crypto";
9048
+ import { randomUUID as randomUUID2 } from "crypto";
8411
9049
  function buildEnvelope(input) {
8412
9050
  return AgentMessageEnvelopeSchema.parse({
8413
9051
  schemaVersion: 1,
8414
9052
  type: input.type,
8415
- requestId: input.requestId ?? randomUUID(),
9053
+ requestId: input.requestId ?? randomUUID2(),
8416
9054
  replyToRequestId: input.replyToRequestId,
8417
9055
  senderInboxId: input.senderInboxId,
8418
9056
  senderAddress: input.senderAddress,
@@ -8565,7 +9203,7 @@ function defaultDbPath(walletAddress) {
8565
9203
  }
8566
9204
  async function createDevClient(input) {
8567
9205
  const inboxId = input.existingInboxId ?? `dev-inbox-${input.walletAddress.slice(2, 10).toLowerCase()}`;
8568
- const installationId = input.existingInstallationId ?? randomUUID2();
9206
+ const installationId = input.existingInstallationId ?? randomUUID3();
8569
9207
  const dbPath = input.existingDbPath ?? defaultDbPath(input.walletAddress);
8570
9208
  return {
8571
9209
  inboxId,
@@ -8994,7 +9632,7 @@ async function createProductionClient(input) {
8994
9632
  "non-retryable"
8995
9633
  );
8996
9634
  }
8997
- const installationId = extractString(client, ["installationId", "installationID"]) ?? extractString(asObject(client)?.installation, ["id", "installationId"]) ?? input.existingInstallationId ?? randomUUID2();
9635
+ const installationId = extractString(client, ["installationId", "installationID"]) ?? extractString(asObject(client)?.installation, ["id", "installationId"]) ?? input.existingInstallationId ?? randomUUID3();
8998
9636
  return {
8999
9637
  inboxId,
9000
9638
  installationId,
@@ -9162,7 +9800,7 @@ function getDefaultQueryTimeoutMs() {
9162
9800
  }
9163
9801
  return Math.floor(parsed);
9164
9802
  }
9165
- var xmtpCommand = new Command53("xmtp").description("Manage XMTP messaging for this agent");
9803
+ var xmtpCommand = new Command55("xmtp").description("Manage XMTP messaging for this agent");
9166
9804
  xmtpCommand.command("init").description("Initialize XMTP client and register installation metadata with backend").action(async () => {
9167
9805
  const keystore = await loadKeystore();
9168
9806
  const client = await createXmtpClient({
@@ -9172,7 +9810,7 @@ xmtpCommand.command("init").description("Initialize XMTP client and register ins
9172
9810
  existingDbPath: keystore.xmtpDbPath,
9173
9811
  keystore
9174
9812
  });
9175
- const status = await apiPost("/api/xmtp/bootstrap", {
9813
+ const { data: status, idempotencyKey } = await apiPost("/api/xmtp/bootstrap", {
9176
9814
  deviceId: keystore.deviceId,
9177
9815
  apiToken: keystore.apiToken,
9178
9816
  inboxId: client.inboxId,
@@ -9186,7 +9824,7 @@ xmtpCommand.command("init").description("Initialize XMTP client and register ins
9186
9824
  xmtpInstallationId: status.installationId,
9187
9825
  xmtpDbPath: client.dbPath
9188
9826
  });
9189
- printResult({ policyMode: status.policyMode });
9827
+ printResult({ policyMode: status.policyMode }, { idempotencyKey });
9190
9828
  });
9191
9829
  xmtpCommand.command("status").description("Check XMTP status and active installation state").action(async () => {
9192
9830
  const keystore = await loadKeystore();
@@ -9299,12 +9937,15 @@ xmtpCommand.command("heartbeat").description("Send a one-shot heartbeat to keep
9299
9937
  if (!keystore.xmtpInstallationId) {
9300
9938
  throw new Error("XMTP not initialized. Run `taskmarket xmtp init` first.");
9301
9939
  }
9302
- const result = await apiPost("/api/xmtp/heartbeat", {
9303
- deviceId: keystore.deviceId,
9304
- apiToken: keystore.apiToken,
9305
- installationId: keystore.xmtpInstallationId
9306
- });
9307
- printResult(result);
9940
+ const { data: result, idempotencyKey } = await apiPost(
9941
+ "/api/xmtp/heartbeat",
9942
+ {
9943
+ deviceId: keystore.deviceId,
9944
+ apiToken: keystore.apiToken,
9945
+ installationId: keystore.xmtpInstallationId
9946
+ }
9947
+ );
9948
+ printResult(result, { idempotencyKey });
9308
9949
  });
9309
9950
  var peersCommand = xmtpCommand.command("peers").description("Manage taskmarket peer messaging policies");
9310
9951
  peersCommand.command("list").description("List per-peer messaging policies stored on the backend").action(async () => {
@@ -9322,14 +9963,17 @@ peersCommand.command("set").description("Set messaging policy for a specific pee
9322
9963
  }
9323
9964
  const keystore = await loadKeystore();
9324
9965
  const peerInboxId = await resolveInboxId(options.to);
9325
- const result = await apiPost("/api/xmtp/peers", {
9326
- deviceId: keystore.deviceId,
9327
- apiToken: keystore.apiToken,
9328
- peerInboxId,
9329
- policy: options.policy,
9330
- reason: options.reason
9331
- });
9332
- printResult(result);
9966
+ const { data: result, idempotencyKey } = await apiPost(
9967
+ "/api/xmtp/peers",
9968
+ {
9969
+ deviceId: keystore.deviceId,
9970
+ apiToken: keystore.apiToken,
9971
+ peerInboxId,
9972
+ policy: options.policy,
9973
+ reason: options.reason
9974
+ }
9975
+ );
9976
+ printResult(result, { idempotencyKey });
9333
9977
  });
9334
9978
  var allowlistCommand = xmtpCommand.command("allowlist").description("Manage XMTP SDK consent allowlist (protocol-level)");
9335
9979
  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) => {
@@ -9382,15 +10026,18 @@ allowlistCommand.command("check").description("Check the XMTP SDK consent state
9382
10026
  });
9383
10027
  xmtpCommand.command("purge").description("Revoke stale XMTP installations that have missed heartbeats").action(async () => {
9384
10028
  const keystore = await loadKeystore();
9385
- const result = await apiPost("/api/xmtp/purge", {
9386
- deviceId: keystore.deviceId,
9387
- apiToken: keystore.apiToken
9388
- });
9389
- printResult(result);
10029
+ const { data: result, idempotencyKey } = await apiPost(
10030
+ "/api/xmtp/purge",
10031
+ {
10032
+ deviceId: keystore.deviceId,
10033
+ apiToken: keystore.apiToken
10034
+ }
10035
+ );
10036
+ printResult(result, { idempotencyKey });
9390
10037
  });
9391
10038
 
9392
10039
  // src/commands/daemon.ts
9393
- import { Command as Command54 } from "commander";
10040
+ import { Command as Command56 } from "commander";
9394
10041
  function diffTaskStatuses(prev, next) {
9395
10042
  const changes = [];
9396
10043
  for (const [taskId, newStatus] of next) {
@@ -9418,7 +10065,7 @@ function sleepOrAbort(ms, signal) {
9418
10065
  });
9419
10066
  }
9420
10067
  var MAX_SEEN_TASK_IDS = 2e3;
9421
- 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(
10068
+ var daemonCommand = new Command56("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(
9422
10069
  "--auction-poll-interval <ms>",
9423
10070
  "Poll interval for clock-based auction tasks (dutch/reverse_dutch) in milliseconds",
9424
10071
  "15000"
@@ -9502,12 +10149,15 @@ var daemonCommand = new Command54("daemon").description("Long-running agent daem
9502
10149
  await sleepOrAbort(heartbeatIntervalMs, abortController.signal);
9503
10150
  if (stopped) break;
9504
10151
  try {
9505
- await apiPost("/trpc/xmtp.heartbeat", {
10152
+ const { idempotencyKey } = await apiPost("/trpc/xmtp.heartbeat", {
9506
10153
  deviceId: keystore.deviceId,
9507
10154
  apiToken: keystore.apiToken,
9508
10155
  installationId: client.installationId
9509
10156
  });
9510
- printResult({ event: "xmtp.heartbeat", installationId: client.installationId });
10157
+ printResult(
10158
+ { event: "xmtp.heartbeat", installationId: client.installationId },
10159
+ { idempotencyKey }
10160
+ );
9511
10161
  } catch (err) {
9512
10162
  process.stderr.write(
9513
10163
  `Heartbeat failed: ${err instanceof Error ? err.message : String(err)}
@@ -9739,29 +10389,33 @@ var daemonCommand = new Command54("daemon").description("Long-running agent daem
9739
10389
  );
9740
10390
 
9741
10391
  // src/commands/email/index.ts
9742
- import { Command as Command63 } from "commander";
10392
+ import { Command as Command65 } from "commander";
9743
10393
 
9744
10394
  // src/commands/email/register.ts
9745
- import { Command as Command55 } from "commander";
9746
- var registerCommand = new Command55("register").description("Register an email address for this agent").requiredOption("--username <name>", "Desired username (e.g. myagent)").action(async (opts) => {
10395
+ import { Command as Command57 } from "commander";
10396
+ var registerCommand = new Command57("register").description("Register an email address for this agent").requiredOption("--username <name>", "Desired username (e.g. myagent)").action(async (opts) => {
9747
10397
  const check = await apiGet(
9748
10398
  `/api/emails/check-username?username=${encodeURIComponent(opts.username)}`
9749
10399
  );
9750
10400
  if (!check.available) {
9751
10401
  printError(`Username "${opts.username}" is not available.`);
10402
+ return;
9752
10403
  }
9753
10404
  const keystore = await loadKeystore();
9754
- const result = await apiPost("/api/emails/register", {
9755
- deviceId: keystore.deviceId,
9756
- apiToken: keystore.apiToken,
9757
- username: opts.username
9758
- });
9759
- printResult({ emailAddress: result.emailAddress });
10405
+ const { data: result, idempotencyKey } = await apiPost(
10406
+ "/api/emails/register",
10407
+ {
10408
+ deviceId: keystore.deviceId,
10409
+ apiToken: keystore.apiToken,
10410
+ username: opts.username
10411
+ }
10412
+ );
10413
+ printResult({ emailAddress: result.emailAddress }, { idempotencyKey });
9760
10414
  });
9761
10415
 
9762
10416
  // src/commands/email/inbox.ts
9763
- import { Command as Command56 } from "commander";
9764
- 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) => {
10417
+ import { Command as Command58 } from "commander";
10418
+ var inboxCommand2 = new Command58("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) => {
9765
10419
  const keystore = await loadKeystore();
9766
10420
  const params = new URLSearchParams({
9767
10421
  deviceId: keystore.deviceId,
@@ -9776,8 +10430,8 @@ var inboxCommand2 = new Command56("inbox").description("List emails in your inbo
9776
10430
  });
9777
10431
 
9778
10432
  // src/commands/email/read.ts
9779
- import { Command as Command57 } from "commander";
9780
- var readCommand = new Command57("read").description("Read an email by ID (marks it as read)").argument("<id>", "Email ID").action(async (id) => {
10433
+ import { Command as Command59 } from "commander";
10434
+ var readCommand = new Command59("read").description("Read an email by ID (marks it as read)").argument("<id>", "Email ID").action(async (id) => {
9781
10435
  const keystore = await loadKeystore();
9782
10436
  const params = new URLSearchParams({
9783
10437
  deviceId: keystore.deviceId,
@@ -9789,22 +10443,22 @@ var readCommand = new Command57("read").description("Read an email by ID (marks
9789
10443
  });
9790
10444
 
9791
10445
  // src/commands/email/send.ts
9792
- import { Command as Command58 } from "commander";
9793
- 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) => {
10446
+ import { Command as Command60 } from "commander";
10447
+ var sendCommand = new Command60("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) => {
9794
10448
  const keystore = await loadKeystore();
9795
- const result = await apiPost("/api/emails/send", {
10449
+ const { data: result, idempotencyKey } = await apiPost("/api/emails/send", {
9796
10450
  deviceId: keystore.deviceId,
9797
10451
  apiToken: keystore.apiToken,
9798
10452
  to: opts.to,
9799
10453
  subject: opts.subject,
9800
10454
  bodyText: opts.body
9801
10455
  });
9802
- printResult({ sent: result.sent });
10456
+ printResult({ sent: result.sent }, { idempotencyKey });
9803
10457
  });
9804
10458
 
9805
10459
  // src/commands/email/reply.ts
9806
- import { Command as Command59 } from "commander";
9807
- 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) => {
10460
+ import { Command as Command61 } from "commander";
10461
+ var replyCommand = new Command61("reply").description("Reply to an email").argument("<id>", "Email ID to reply to").requiredOption("--body <text>", "Reply body text").action(async (id, opts) => {
9808
10462
  const keystore = await loadKeystore();
9809
10463
  const params = new URLSearchParams({
9810
10464
  deviceId: keystore.deviceId,
@@ -9815,54 +10469,60 @@ var replyCommand = new Command59("reply").description("Reply to an email").argum
9815
10469
  const to = original.fromAddress;
9816
10470
  const rawSubject = original.subject ?? "";
9817
10471
  const subject = rawSubject.startsWith("Re: ") ? rawSubject : `Re: ${rawSubject}`;
9818
- const result = await apiPost("/api/emails/send", {
10472
+ const { data: result, idempotencyKey } = await apiPost("/api/emails/send", {
9819
10473
  deviceId: keystore.deviceId,
9820
10474
  apiToken: keystore.apiToken,
9821
10475
  to,
9822
10476
  subject,
9823
10477
  bodyText: opts.body
9824
10478
  });
9825
- printResult({ sent: result.sent });
10479
+ printResult({ sent: result.sent }, { idempotencyKey });
9826
10480
  });
9827
10481
 
9828
10482
  // src/commands/email/delete.ts
9829
- import { Command as Command60 } from "commander";
9830
- var deleteCommand = new Command60("delete").description("Delete an email by ID").argument("<id>", "Email ID").action(async (id) => {
10483
+ import { Command as Command62 } from "commander";
10484
+ var deleteCommand = new Command62("delete").description("Delete an email by ID").argument("<id>", "Email ID").action(async (id) => {
9831
10485
  const keystore = await loadKeystore();
9832
- const result = await apiPost("/api/emails/delete", {
9833
- deviceId: keystore.deviceId,
9834
- apiToken: keystore.apiToken,
9835
- id
9836
- });
9837
- printResult({ deleted: result.deleted });
10486
+ const { data: result, idempotencyKey } = await apiPost(
10487
+ "/api/emails/delete",
10488
+ {
10489
+ deviceId: keystore.deviceId,
10490
+ apiToken: keystore.apiToken,
10491
+ id
10492
+ }
10493
+ );
10494
+ printResult({ deleted: result.deleted }, { idempotencyKey });
9838
10495
  });
9839
10496
 
9840
10497
  // src/commands/email/address.ts
9841
- import { Command as Command61 } from "commander";
9842
- var addressCommand2 = new Command61("address").description("Show your registered email address").action(async () => {
10498
+ import { Command as Command63 } from "commander";
10499
+ var addressCommand2 = new Command63("address").description("Show your registered email address").action(async () => {
9843
10500
  const keystore = await loadKeystore();
9844
10501
  const result = await apiGet(`/api/agents/stats?address=${keystore.walletAddress}`);
9845
10502
  printResult({ emailAddress: result.emailAddress ?? null });
9846
10503
  });
9847
10504
 
9848
10505
  // src/commands/email/mark-read.ts
9849
- import { Command as Command62 } from "commander";
10506
+ import { Command as Command64 } from "commander";
9850
10507
  async function setReadStatus(id, read) {
9851
10508
  const keystore = await loadKeystore();
9852
- const result = await apiPost("/api/emails/mark-read", {
9853
- deviceId: keystore.deviceId,
9854
- apiToken: keystore.apiToken,
9855
- id,
9856
- read
9857
- });
9858
- printResult({ id: result.id, isRead: result.isRead });
10509
+ const { data: result, idempotencyKey } = await apiPost(
10510
+ "/api/emails/mark-read",
10511
+ {
10512
+ deviceId: keystore.deviceId,
10513
+ apiToken: keystore.apiToken,
10514
+ id,
10515
+ read
10516
+ }
10517
+ );
10518
+ printResult({ id: result.id, isRead: result.isRead }, { idempotencyKey });
9859
10519
  }
9860
- 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) => {
10520
+ var markReadCommand = new Command64("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) => {
9861
10521
  await setReadStatus(id, !opts.unread);
9862
10522
  });
9863
10523
 
9864
10524
  // src/commands/email/index.ts
9865
- var emailCommand = new Command63("email").description("Email management commands");
10525
+ var emailCommand = new Command65("email").description("Email management commands");
9866
10526
  emailCommand.addCommand(registerCommand);
9867
10527
  emailCommand.addCommand(inboxCommand2);
9868
10528
  emailCommand.addCommand(readCommand);
@@ -9873,32 +10533,30 @@ emailCommand.addCommand(addressCommand2);
9873
10533
  emailCommand.addCommand(markReadCommand);
9874
10534
 
9875
10535
  // src/commands/requester/index.ts
9876
- import { Command as Command65 } from "commander";
10536
+ import { Command as Command67 } from "commander";
9877
10537
 
9878
10538
  // src/commands/requester/stats.ts
9879
- import { Command as Command64 } from "commander";
9880
- var requesterStatsCmd = new Command64("stats").description("Get requester reputation stats for an address").argument("<address>", "Wallet address of the requester").action(async (address) => {
10539
+ import { Command as Command66 } from "commander";
10540
+ var requesterStatsCmd = new Command66("stats").description("Get requester reputation stats for an address").argument("<address>", "Wallet address of the requester").action(async (address) => {
9881
10541
  if (!/^0x[0-9a-fA-F]{40}$/.test(address)) {
9882
10542
  printError("Address must be a 0x-prefixed 20-byte hex string");
9883
- process.exit(1);
9884
10543
  return;
9885
10544
  }
9886
10545
  const data = await apiGet(`/api/requester/${address}/stats`);
9887
10546
  if (!data) {
9888
10547
  printError("No stats found for address");
9889
- process.exit(1);
9890
10548
  return;
9891
10549
  }
9892
10550
  printResult(data);
9893
10551
  });
9894
10552
 
9895
10553
  // src/commands/requester/index.ts
9896
- var requesterCmd = new Command65("requester").description("Requester reputation commands").addCommand(requesterStatsCmd);
10554
+ var requesterCmd = new Command67("requester").description("Requester reputation commands").addCommand(requesterStatsCmd);
9897
10555
 
9898
10556
  // src/index.ts
9899
10557
  var require2 = createRequire(import.meta.url);
9900
10558
  var { version } = require2("../package.json");
9901
- var program = new Command66();
10559
+ var program = new Command68();
9902
10560
  program.name("taskmarket").description("Taskmarket CLI for AI agents").version(version);
9903
10561
  program.addCommand(initCommand);
9904
10562
  program.addCommand(walletCommand);
@@ -9908,6 +10566,7 @@ program.addCommand(statsCommand);
9908
10566
  program.addCommand(taskCommand);
9909
10567
  program.addCommand(agentsCommand);
9910
10568
  program.addCommand(inboxCommand);
10569
+ program.addCommand(actionsCommand);
9911
10570
  program.addCommand(depositCommand);
9912
10571
  program.addCommand(withdrawCommand);
9913
10572
  program.addCommand(encryptCommand);
@@ -9917,10 +10576,10 @@ program.addCommand(daemonCommand);
9917
10576
  program.addCommand(emailCommand);
9918
10577
  program.addCommand(requesterCmd);
9919
10578
  program.addCommand(legalCommand);
9920
- program.parseAsync(process.argv).catch((err) => {
9921
- const status = err instanceof ApiError ? err.status : void 0;
9922
- process.stderr.write(
9923
- JSON.stringify({ ok: false, error: err.message, ...status !== void 0 ? { status } : {} }) + "\n"
9924
- );
9925
- process.exit(1);
9926
- });
10579
+ void (async () => {
10580
+ try {
10581
+ await program.parseAsync(process.argv);
10582
+ } catch (err) {
10583
+ renderFailure(err);
10584
+ }
10585
+ })();