@t2000/cli 10.30.0 → 10.30.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +152 -41
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -34368,7 +34368,7 @@ var import_picocolors8 = __toESM(require_picocolors(), 1);
|
|
|
34368
34368
|
function registerPay(program3) {
|
|
34369
34369
|
program3.command("pay <url>").description("Pay an x402 Service (USDC on Sui) \u2014 the seller's own endpoint").option("--key <path>", "Custom wallet path (default ~/.t2000/wallet.key)").option("--method <method>", "HTTP method (GET, POST, PUT)", "GET").option("--data <json>", "Request body for POST/PUT (auto-promotes --method to POST)").option("--header <key=value>", "Additional HTTP header (repeatable)", collectHeaders, {}).option("--max-price <amount>", "Max USDC price to auto-approve", "1.00").option(
|
|
34370
34370
|
"--estimate",
|
|
34371
|
-
|
|
34371
|
+
'Preview the price + service info (no signing, no payment). Exits 0 ONLY if the service answers a payable 402 x402 challenge; any other response (including 2xx "no payment required") exits 1.'
|
|
34372
34372
|
).option("--force", "Override spending limits for this call (see `t2 limit`)").addHelpText(
|
|
34373
34373
|
"after",
|
|
34374
34374
|
`
|
|
@@ -34439,6 +34439,23 @@ Examples:
|
|
|
34439
34439
|
}
|
|
34440
34440
|
});
|
|
34441
34441
|
}
|
|
34442
|
+
var ESTIMATE_PREVIEW_MAX = 512;
|
|
34443
|
+
function truncatePreview(body2, max = ESTIMATE_PREVIEW_MAX) {
|
|
34444
|
+
if (body2.length <= max) {
|
|
34445
|
+
return body2;
|
|
34446
|
+
}
|
|
34447
|
+
return `${body2.slice(0, max)}\u2026 (truncated, ${body2.length} total bytes)`;
|
|
34448
|
+
}
|
|
34449
|
+
var EstimateFailure = class extends Error {
|
|
34450
|
+
constructor(message, payload) {
|
|
34451
|
+
super(message);
|
|
34452
|
+
this.payload = payload;
|
|
34453
|
+
this.name = "EstimateFailure";
|
|
34454
|
+
}
|
|
34455
|
+
toJSON() {
|
|
34456
|
+
return this.payload;
|
|
34457
|
+
}
|
|
34458
|
+
};
|
|
34442
34459
|
async function runEstimate(url, opts) {
|
|
34443
34460
|
const method = opts.data && opts.method === "GET" ? "POST" : opts.method;
|
|
34444
34461
|
const canHaveBody = method !== "GET" && method !== "HEAD";
|
|
@@ -34461,28 +34478,33 @@ async function runEstimate(url, opts) {
|
|
|
34461
34478
|
});
|
|
34462
34479
|
if (response.status !== 402) {
|
|
34463
34480
|
const body2 = await response.text().catch(() => "");
|
|
34464
|
-
|
|
34465
|
-
|
|
34481
|
+
const preview = truncatePreview(body2);
|
|
34482
|
+
const open = response.status >= 200 && response.status < 300;
|
|
34483
|
+
const note = open ? `Endpoint responded ${response.status} without a 402 challenge \u2014 no payment required.` : `Endpoint responded ${response.status} (not a 402 payment challenge).`;
|
|
34484
|
+
if (!isJsonMode()) {
|
|
34485
|
+
if (open) {
|
|
34486
|
+
printInfo(`No payment required (status ${response.status}).`);
|
|
34487
|
+
} else {
|
|
34488
|
+
printInfo(`Status ${response.status} \u2014 not a 402 payment challenge.`);
|
|
34489
|
+
}
|
|
34490
|
+
if (preview) {
|
|
34491
|
+
printBlank();
|
|
34492
|
+
console.log(preview);
|
|
34493
|
+
printBlank();
|
|
34494
|
+
}
|
|
34495
|
+
}
|
|
34496
|
+
throw new EstimateFailure(
|
|
34497
|
+
`${note} --estimate exits 0 only for a payable 402 x402 challenge.`,
|
|
34498
|
+
{
|
|
34466
34499
|
url,
|
|
34467
34500
|
method,
|
|
34468
34501
|
status: response.status,
|
|
34502
|
+
ok: false,
|
|
34469
34503
|
estimate: null,
|
|
34470
|
-
note
|
|
34471
|
-
|
|
34472
|
-
}
|
|
34473
|
-
|
|
34474
|
-
}
|
|
34475
|
-
if (response.status >= 200 && response.status < 300) {
|
|
34476
|
-
printSuccess(`No payment required (status ${response.status}).`);
|
|
34477
|
-
} else {
|
|
34478
|
-
printInfo(`Status ${response.status} \u2014 not a 402 payment challenge.`);
|
|
34479
|
-
}
|
|
34480
|
-
if (body2) {
|
|
34481
|
-
printBlank();
|
|
34482
|
-
console.log(body2);
|
|
34483
|
-
printBlank();
|
|
34484
|
-
}
|
|
34485
|
-
return;
|
|
34504
|
+
note,
|
|
34505
|
+
...preview ? { bodyPreview: preview } : {}
|
|
34506
|
+
}
|
|
34507
|
+
);
|
|
34486
34508
|
}
|
|
34487
34509
|
let accepts = [];
|
|
34488
34510
|
try {
|
|
@@ -36387,24 +36409,76 @@ async function fetchSellerJobs(base, seller) {
|
|
|
36387
36409
|
return json.jobs ?? [];
|
|
36388
36410
|
}
|
|
36389
36411
|
var TERMINAL_STATES = /* @__PURE__ */ new Set(["released", "rejected", "refunded"]);
|
|
36390
|
-
function
|
|
36391
|
-
|
|
36392
|
-
|
|
36412
|
+
function reviewClosesMs(job) {
|
|
36413
|
+
const anchor = job.deliveredAtMs ?? job.updatedAtMs;
|
|
36414
|
+
if (!(typeof anchor === "number" && anchor > 0 && typeof job.reviewWindowMs === "number" && job.reviewWindowMs > 0)) {
|
|
36415
|
+
return null;
|
|
36416
|
+
}
|
|
36417
|
+
return anchor + job.reviewWindowMs;
|
|
36418
|
+
}
|
|
36419
|
+
function bucketSellerJob(job, nowMs) {
|
|
36420
|
+
if (TERMINAL_STATES.has(job.state)) {
|
|
36421
|
+
return "terminal";
|
|
36393
36422
|
}
|
|
36394
|
-
if (job.state === "
|
|
36395
|
-
return
|
|
36423
|
+
if (job.state === "funded") {
|
|
36424
|
+
return nowMs <= job.deliverByMs ? "needsYou" : "fundedLate";
|
|
36425
|
+
}
|
|
36426
|
+
const closes = reviewClosesMs(job);
|
|
36427
|
+
return closes !== null && nowMs > closes ? "releasable" : "awaitingBuyer";
|
|
36428
|
+
}
|
|
36429
|
+
function summarizeSellerInbox(jobs, nowMs) {
|
|
36430
|
+
const buckets = {
|
|
36431
|
+
counts: { total: jobs.length, needsYou: 0, fundedLate: 0, awaitingBuyer: 0, releasable: 0, terminal: 0 },
|
|
36432
|
+
needsYou: [],
|
|
36433
|
+
fundedLate: [],
|
|
36434
|
+
awaitingBuyer: [],
|
|
36435
|
+
releasable: [],
|
|
36436
|
+
terminal: []
|
|
36437
|
+
};
|
|
36438
|
+
for (const job of jobs) {
|
|
36439
|
+
const bucket = bucketSellerJob(job, nowMs);
|
|
36440
|
+
buckets[bucket].push(job);
|
|
36441
|
+
buckets.counts[bucket] += 1;
|
|
36442
|
+
}
|
|
36443
|
+
return buckets;
|
|
36444
|
+
}
|
|
36445
|
+
function inboxHint(job, bucket) {
|
|
36446
|
+
switch (bucket) {
|
|
36447
|
+
case "needsYou":
|
|
36448
|
+
return `t2 job spec ${job.jobId} \u2192 do the work \u2192 t2 job deliver ${job.jobId} <file>`;
|
|
36449
|
+
case "releasable":
|
|
36450
|
+
return `releasable now \u2014 t2 job release ${job.jobId}`;
|
|
36451
|
+
case "awaitingBuyer": {
|
|
36452
|
+
const closes = reviewClosesMs(job);
|
|
36453
|
+
return closes !== null ? `waiting on the buyer's review \u2014 release becomes permissionless after ${new Date(closes).toISOString()}` : `waiting on the buyer's review \u2014 anyone can \`t2 job release\` once it lapses`;
|
|
36454
|
+
}
|
|
36455
|
+
case "fundedLate":
|
|
36456
|
+
return `deliver deadline passed \u2014 the chain rejects late delivers; the buyer (or anyone) may t2 job refund ${job.jobId}`;
|
|
36457
|
+
default:
|
|
36458
|
+
return "";
|
|
36396
36459
|
}
|
|
36397
|
-
return "";
|
|
36398
36460
|
}
|
|
36399
|
-
function printInboxRow(job) {
|
|
36461
|
+
function printInboxRow(job, bucket) {
|
|
36400
36462
|
const deadline = job.state === "funded" ? ` \xB7 deliver by ${new Date(job.deliverByMs).toISOString()}` : "";
|
|
36401
36463
|
printLine(
|
|
36402
36464
|
` ${stateColor(job.state)} $${job.amountUsdc.toFixed(2)} USDC \xB7 from ${truncateAddress(job.buyer)}${deadline}`
|
|
36403
36465
|
);
|
|
36404
36466
|
printLine(` ${import_picocolors13.default.dim(job.jobId)}`);
|
|
36405
|
-
const hint = inboxHint(job);
|
|
36467
|
+
const hint = inboxHint(job, bucket);
|
|
36406
36468
|
if (hint) printLine(` ${import_picocolors13.default.dim("\u2192")} ${hint}`);
|
|
36407
36469
|
}
|
|
36470
|
+
function deliverPreflightError(state, deliverByMs, nowMs) {
|
|
36471
|
+
if (state === "delivered") {
|
|
36472
|
+
return "This job is already delivered. The delivery hash is permanent and cannot be replaced \u2014 wait for the buyer's review, or once the window closes anyone may run `t2 job release <jobId>`. Fixes travel via buyer reject (inside the window) or out-of-band, never a second deliver.";
|
|
36473
|
+
}
|
|
36474
|
+
if (state === "released" || state === "rejected" || state === "refunded") {
|
|
36475
|
+
return `This job is ${state} \u2014 nothing can be delivered.`;
|
|
36476
|
+
}
|
|
36477
|
+
if (state === "funded" && nowMs > deliverByMs) {
|
|
36478
|
+
return `The deliver deadline (${new Date(deliverByMs).toISOString()}) has passed \u2014 the chain rejects late delivers, so nothing was uploaded or signed. The refund path is open: anyone may run \`t2 job refund <jobId>\`.`;
|
|
36479
|
+
}
|
|
36480
|
+
return null;
|
|
36481
|
+
}
|
|
36408
36482
|
async function sponsoredJobVerb(opts) {
|
|
36409
36483
|
const agent = await withAgent({ keyPath: opts.keyPath });
|
|
36410
36484
|
const address = agent.address();
|
|
@@ -36653,9 +36727,16 @@ no fund step; unclaimed openings refund fee-free):
|
|
|
36653
36727
|
handleError(error);
|
|
36654
36728
|
}
|
|
36655
36729
|
});
|
|
36656
|
-
group.command("deliver").argument("<jobId>", "The Job object id (0x\u2026)").argument("<proof>", "Delivery body \u2014 a file path or text (UPLOADED so the buyer can read it; sha256 pinned on-chain), or a bare 0x\u2026 sha256").description("Post your delivery before the deadline (seller) \u2014 the
|
|
36730
|
+
group.command("deliver").argument("<jobId>", "The Job object id (0x\u2026)").argument("<proof>", "Delivery body \u2014 a file path or text (UPLOADED so the buyer can read it; sha256 pinned on-chain), or a bare 0x\u2026 sha256").description("Post your delivery before the deadline (seller) \u2014 ONE SHOT: the sha256 pins on-chain permanently and cannot be replaced. Fix mistakes via buyer reject (inside the review window) or out-of-band \u2014 never a second deliver. Decline is only possible BEFORE delivery.").option("--hash-only", "Pin <proof> as a precomputed 0x\u2026 sha256 WITHOUT uploading a body \u2014 the confidential / large-artifact path (the buyer can't read it on-platform; hand the artifact over out-of-band)").option("--key <path>", "Custom wallet path (default ~/.t2000/wallet.key)").option("--api <url>", `API base URL (default ${DEFAULT_API_BASE7})`).action(async (jobId, proof, opts) => {
|
|
36657
36731
|
try {
|
|
36658
36732
|
const base = opts.api ?? DEFAULT_API_BASE7;
|
|
36733
|
+
const existing = await getJob(getSuiClient(), jobId).catch(() => null);
|
|
36734
|
+
if (existing) {
|
|
36735
|
+
const preflightError = deliverPreflightError(existing.state, existing.deliverByMs, Date.now());
|
|
36736
|
+
if (preflightError) {
|
|
36737
|
+
throw new Error(preflightError.replaceAll("<jobId>", jobId));
|
|
36738
|
+
}
|
|
36739
|
+
}
|
|
36659
36740
|
let deliveryHash;
|
|
36660
36741
|
let uploaded = false;
|
|
36661
36742
|
if (opts.hashOnly) {
|
|
@@ -36667,6 +36748,9 @@ no fund step; unclaimed openings refund fee-free):
|
|
|
36667
36748
|
} else {
|
|
36668
36749
|
({ hash: deliveryHash, uploaded } = await resolveSpecUpload(base, proof));
|
|
36669
36750
|
}
|
|
36751
|
+
if (!isJsonMode()) {
|
|
36752
|
+
printInfo("Delivery pins once. The buyer review window starts after this succeeds; you cannot replace the hash.");
|
|
36753
|
+
}
|
|
36670
36754
|
const { digest } = await sponsoredJobVerb({
|
|
36671
36755
|
base,
|
|
36672
36756
|
keyPath: opts.key,
|
|
@@ -36674,11 +36758,12 @@ no fund step; unclaimed openings refund fee-free):
|
|
|
36674
36758
|
params: { jobId, deliveryHash }
|
|
36675
36759
|
});
|
|
36676
36760
|
if (isJsonMode()) {
|
|
36677
|
-
printJson({ jobId, deliveryHash, uploaded, digest });
|
|
36761
|
+
printJson({ jobId, deliveryHash, uploaded, digest, onceOnly: true });
|
|
36678
36762
|
return;
|
|
36679
36763
|
}
|
|
36680
36764
|
printBlank();
|
|
36681
36765
|
printSuccess("Delivery posted \u2014 the buyer's review window is now open.");
|
|
36766
|
+
printInfo("This delivery cannot be amended \u2014 further `t2 job deliver` calls on this job will fail.");
|
|
36682
36767
|
printKeyValue("Delivery hash", deliveryHash);
|
|
36683
36768
|
if (digest) printKeyValue("Tx", digest);
|
|
36684
36769
|
if (uploaded) {
|
|
@@ -36822,23 +36907,45 @@ no fund step; unclaimed openings refund fee-free):
|
|
|
36822
36907
|
const base = opts.api ?? DEFAULT_API_BASE7;
|
|
36823
36908
|
const seen = /* @__PURE__ */ new Map();
|
|
36824
36909
|
const jobs = await fetchSellerJobs(base, me);
|
|
36910
|
+
const inbox = summarizeSellerInbox(jobs, Date.now());
|
|
36825
36911
|
if (isJsonMode()) {
|
|
36826
|
-
printJson({
|
|
36912
|
+
printJson({
|
|
36913
|
+
seller: me,
|
|
36914
|
+
counts: inbox.counts,
|
|
36915
|
+
needsYou: inbox.needsYou,
|
|
36916
|
+
fundedLate: inbox.fundedLate,
|
|
36917
|
+
awaitingBuyer: inbox.awaitingBuyer,
|
|
36918
|
+
releasable: inbox.releasable,
|
|
36919
|
+
terminal: inbox.terminal,
|
|
36920
|
+
jobs
|
|
36921
|
+
});
|
|
36827
36922
|
return;
|
|
36828
36923
|
}
|
|
36829
|
-
const open = jobs.filter((j) => !TERMINAL_STATES.has(j.state));
|
|
36830
36924
|
printBlank();
|
|
36831
|
-
printInfo(
|
|
36925
|
+
printInfo(
|
|
36926
|
+
`Provider inbox for ${truncateAddress(me)} \u2014 ${jobs.length} job(s) \xB7 ${inbox.counts.needsYou} need you \xB7 ${inbox.counts.awaitingBuyer} awaiting buyer \xB7 ${inbox.counts.releasable} releasable now`
|
|
36927
|
+
);
|
|
36928
|
+
if (inbox.counts.fundedLate > 0) {
|
|
36929
|
+
printLine(import_picocolors13.default.dim(` ${inbox.counts.fundedLate} past deliver deadline (not deliverable \u2014 refund path is open to others)`));
|
|
36930
|
+
}
|
|
36832
36931
|
printBlank();
|
|
36833
|
-
for (const
|
|
36834
|
-
|
|
36835
|
-
|
|
36932
|
+
for (const [bucket, rows] of [
|
|
36933
|
+
["needsYou", inbox.needsYou],
|
|
36934
|
+
["releasable", inbox.releasable],
|
|
36935
|
+
["awaitingBuyer", inbox.awaitingBuyer],
|
|
36936
|
+
["fundedLate", inbox.fundedLate]
|
|
36937
|
+
]) {
|
|
36938
|
+
for (const job of rows) {
|
|
36939
|
+
printInboxRow(job, bucket);
|
|
36940
|
+
printBlank();
|
|
36941
|
+
}
|
|
36836
36942
|
}
|
|
36837
|
-
|
|
36943
|
+
const openCount = jobs.length - inbox.counts.terminal;
|
|
36944
|
+
if (openCount === 0) {
|
|
36838
36945
|
printInfo("No open jobs. New hires appear here the moment the escrow funds.");
|
|
36839
36946
|
printBlank();
|
|
36840
36947
|
}
|
|
36841
|
-
for (const job of jobs) seen.set(job.jobId, job.
|
|
36948
|
+
for (const job of jobs) seen.set(job.jobId, bucketSellerJob(job, Date.now()));
|
|
36842
36949
|
if (opts.once) return;
|
|
36843
36950
|
for (; ; ) {
|
|
36844
36951
|
await new Promise((r) => setTimeout(r, intervalMs));
|
|
@@ -36848,17 +36955,21 @@ no fund step; unclaimed openings refund fee-free):
|
|
|
36848
36955
|
} catch {
|
|
36849
36956
|
continue;
|
|
36850
36957
|
}
|
|
36958
|
+
const nowMs = Date.now();
|
|
36851
36959
|
for (const job of latest) {
|
|
36960
|
+
const bucket = bucketSellerJob(job, nowMs);
|
|
36852
36961
|
const prev = seen.get(job.jobId);
|
|
36853
|
-
if (prev ===
|
|
36854
|
-
seen.set(job.jobId,
|
|
36962
|
+
if (prev === bucket) continue;
|
|
36963
|
+
seen.set(job.jobId, bucket);
|
|
36855
36964
|
printBlank();
|
|
36856
|
-
if (prev === void 0 &&
|
|
36965
|
+
if (prev === void 0 && bucket === "needsYou") {
|
|
36857
36966
|
printSuccess(`New job \u2014 $${job.amountUsdc.toFixed(2)} USDC escrowed for you.`);
|
|
36967
|
+
} else if (bucket === "releasable") {
|
|
36968
|
+
printSuccess(`Releasable now \u2014 t2 job release ${job.jobId}`);
|
|
36858
36969
|
} else {
|
|
36859
36970
|
printInfo(`Job ${truncateAddress(job.jobId)}: ${prev ?? "new"} \u2192 ${job.state}`);
|
|
36860
36971
|
}
|
|
36861
|
-
printInboxRow(job);
|
|
36972
|
+
printInboxRow(job, bucket);
|
|
36862
36973
|
printBlank();
|
|
36863
36974
|
}
|
|
36864
36975
|
}
|