@t2000/cli 10.30.0 → 10.30.2
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 +195 -42
- 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,116 @@ 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
|
|
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";
|
|
36422
|
+
}
|
|
36391
36423
|
if (job.state === "funded") {
|
|
36392
|
-
return
|
|
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;
|
|
36393
36442
|
}
|
|
36394
|
-
|
|
36395
|
-
|
|
36443
|
+
return buckets;
|
|
36444
|
+
}
|
|
36445
|
+
var HYDRATE_MAX = 25;
|
|
36446
|
+
var HYDRATE_CONCURRENCY = 8;
|
|
36447
|
+
function mergeIndexedJobFromChain(row, chain) {
|
|
36448
|
+
return {
|
|
36449
|
+
...row,
|
|
36450
|
+
jobId: chain.id ?? row.jobId,
|
|
36451
|
+
state: chain.state,
|
|
36452
|
+
buyer: chain.buyer ?? row.buyer,
|
|
36453
|
+
seller: chain.seller ?? row.seller,
|
|
36454
|
+
amountUsdc: chain.amountUsdc ?? row.amountUsdc,
|
|
36455
|
+
deliverByMs: chain.deliverByMs ?? row.deliverByMs,
|
|
36456
|
+
reviewWindowMs: chain.reviewWindowMs ?? row.reviewWindowMs,
|
|
36457
|
+
deliveryHash: chain.deliveryHash ?? row.deliveryHash,
|
|
36458
|
+
deliveredAtMs: chain.deliveredAtMs ?? row.deliveredAtMs ?? null,
|
|
36459
|
+
createdAtMs: chain.createdAtMs ?? row.createdAtMs,
|
|
36460
|
+
// Review clock prefers deliveredAtMs (reviewClosesMs anchors there when
|
|
36461
|
+
// set); for delivered rows keep updatedAtMs honest with the chain clock,
|
|
36462
|
+
// otherwise the index value stands — never invent a clock.
|
|
36463
|
+
updatedAtMs: chain.state === "delivered" && chain.deliveredAtMs != null ? chain.deliveredAtMs : row.updatedAtMs
|
|
36464
|
+
};
|
|
36465
|
+
}
|
|
36466
|
+
async function hydrateSellerJobsFromChain(rows, getJobById, opts) {
|
|
36467
|
+
const max = opts?.maxHydrate ?? HYDRATE_MAX;
|
|
36468
|
+
const out = [...rows];
|
|
36469
|
+
const targets = [];
|
|
36470
|
+
for (let i = 0; i < out.length && targets.length < max; i++) {
|
|
36471
|
+
if (!TERMINAL_STATES.has(out[i].state)) targets.push(i);
|
|
36472
|
+
}
|
|
36473
|
+
for (let at = 0; at < targets.length; at += HYDRATE_CONCURRENCY) {
|
|
36474
|
+
await Promise.all(
|
|
36475
|
+
targets.slice(at, at + HYDRATE_CONCURRENCY).map(async (i) => {
|
|
36476
|
+
try {
|
|
36477
|
+
out[i] = mergeIndexedJobFromChain(out[i], await getJobById(out[i].jobId));
|
|
36478
|
+
} catch {
|
|
36479
|
+
}
|
|
36480
|
+
})
|
|
36481
|
+
);
|
|
36396
36482
|
}
|
|
36397
|
-
return
|
|
36483
|
+
return out;
|
|
36398
36484
|
}
|
|
36399
|
-
function
|
|
36485
|
+
function inboxHint(job, bucket) {
|
|
36486
|
+
switch (bucket) {
|
|
36487
|
+
case "needsYou":
|
|
36488
|
+
return `t2 job spec ${job.jobId} \u2192 do the work \u2192 t2 job deliver ${job.jobId} <file>`;
|
|
36489
|
+
case "releasable":
|
|
36490
|
+
return `releasable now \u2014 t2 job release ${job.jobId}`;
|
|
36491
|
+
case "awaitingBuyer": {
|
|
36492
|
+
const closes = reviewClosesMs(job);
|
|
36493
|
+
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`;
|
|
36494
|
+
}
|
|
36495
|
+
case "fundedLate":
|
|
36496
|
+
return `deliver deadline passed \u2014 the chain rejects late delivers; the buyer (or anyone) may t2 job refund ${job.jobId}`;
|
|
36497
|
+
default:
|
|
36498
|
+
return "";
|
|
36499
|
+
}
|
|
36500
|
+
}
|
|
36501
|
+
function printInboxRow(job, bucket) {
|
|
36400
36502
|
const deadline = job.state === "funded" ? ` \xB7 deliver by ${new Date(job.deliverByMs).toISOString()}` : "";
|
|
36401
36503
|
printLine(
|
|
36402
36504
|
` ${stateColor(job.state)} $${job.amountUsdc.toFixed(2)} USDC \xB7 from ${truncateAddress(job.buyer)}${deadline}`
|
|
36403
36505
|
);
|
|
36404
36506
|
printLine(` ${import_picocolors13.default.dim(job.jobId)}`);
|
|
36405
|
-
const hint = inboxHint(job);
|
|
36507
|
+
const hint = inboxHint(job, bucket);
|
|
36406
36508
|
if (hint) printLine(` ${import_picocolors13.default.dim("\u2192")} ${hint}`);
|
|
36407
36509
|
}
|
|
36510
|
+
function deliverPreflightError(state, deliverByMs, nowMs) {
|
|
36511
|
+
if (state === "delivered") {
|
|
36512
|
+
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.";
|
|
36513
|
+
}
|
|
36514
|
+
if (state === "released" || state === "rejected" || state === "refunded") {
|
|
36515
|
+
return `This job is ${state} \u2014 nothing can be delivered.`;
|
|
36516
|
+
}
|
|
36517
|
+
if (state === "funded" && nowMs > deliverByMs) {
|
|
36518
|
+
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>\`.`;
|
|
36519
|
+
}
|
|
36520
|
+
return null;
|
|
36521
|
+
}
|
|
36408
36522
|
async function sponsoredJobVerb(opts) {
|
|
36409
36523
|
const agent = await withAgent({ keyPath: opts.keyPath });
|
|
36410
36524
|
const address = agent.address();
|
|
@@ -36653,9 +36767,16 @@ no fund step; unclaimed openings refund fee-free):
|
|
|
36653
36767
|
handleError(error);
|
|
36654
36768
|
}
|
|
36655
36769
|
});
|
|
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
|
|
36770
|
+
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
36771
|
try {
|
|
36658
36772
|
const base = opts.api ?? DEFAULT_API_BASE7;
|
|
36773
|
+
const existing = await getJob(getSuiClient(), jobId).catch(() => null);
|
|
36774
|
+
if (existing) {
|
|
36775
|
+
const preflightError = deliverPreflightError(existing.state, existing.deliverByMs, Date.now());
|
|
36776
|
+
if (preflightError) {
|
|
36777
|
+
throw new Error(preflightError.replaceAll("<jobId>", jobId));
|
|
36778
|
+
}
|
|
36779
|
+
}
|
|
36659
36780
|
let deliveryHash;
|
|
36660
36781
|
let uploaded = false;
|
|
36661
36782
|
if (opts.hashOnly) {
|
|
@@ -36667,6 +36788,9 @@ no fund step; unclaimed openings refund fee-free):
|
|
|
36667
36788
|
} else {
|
|
36668
36789
|
({ hash: deliveryHash, uploaded } = await resolveSpecUpload(base, proof));
|
|
36669
36790
|
}
|
|
36791
|
+
if (!isJsonMode()) {
|
|
36792
|
+
printInfo("Delivery pins once. The buyer review window starts after this succeeds; you cannot replace the hash.");
|
|
36793
|
+
}
|
|
36670
36794
|
const { digest } = await sponsoredJobVerb({
|
|
36671
36795
|
base,
|
|
36672
36796
|
keyPath: opts.key,
|
|
@@ -36674,11 +36798,12 @@ no fund step; unclaimed openings refund fee-free):
|
|
|
36674
36798
|
params: { jobId, deliveryHash }
|
|
36675
36799
|
});
|
|
36676
36800
|
if (isJsonMode()) {
|
|
36677
|
-
printJson({ jobId, deliveryHash, uploaded, digest });
|
|
36801
|
+
printJson({ jobId, deliveryHash, uploaded, digest, onceOnly: true });
|
|
36678
36802
|
return;
|
|
36679
36803
|
}
|
|
36680
36804
|
printBlank();
|
|
36681
36805
|
printSuccess("Delivery posted \u2014 the buyer's review window is now open.");
|
|
36806
|
+
printInfo("This delivery cannot be amended \u2014 further `t2 job deliver` calls on this job will fail.");
|
|
36682
36807
|
printKeyValue("Delivery hash", deliveryHash);
|
|
36683
36808
|
if (digest) printKeyValue("Tx", digest);
|
|
36684
36809
|
if (uploaded) {
|
|
@@ -36821,44 +36946,72 @@ no fund step; unclaimed openings refund fee-free):
|
|
|
36821
36946
|
if (opts.mine) {
|
|
36822
36947
|
const base = opts.api ?? DEFAULT_API_BASE7;
|
|
36823
36948
|
const seen = /* @__PURE__ */ new Map();
|
|
36824
|
-
const
|
|
36949
|
+
const mineClient = getSuiClient();
|
|
36950
|
+
const loadInbox = async () => hydrateSellerJobsFromChain(await fetchSellerJobs(base, me), (id) => getJob(mineClient, id));
|
|
36951
|
+
const jobs = await loadInbox();
|
|
36952
|
+
const inbox = summarizeSellerInbox(jobs, Date.now());
|
|
36825
36953
|
if (isJsonMode()) {
|
|
36826
|
-
printJson({
|
|
36954
|
+
printJson({
|
|
36955
|
+
seller: me,
|
|
36956
|
+
counts: inbox.counts,
|
|
36957
|
+
needsYou: inbox.needsYou,
|
|
36958
|
+
fundedLate: inbox.fundedLate,
|
|
36959
|
+
awaitingBuyer: inbox.awaitingBuyer,
|
|
36960
|
+
releasable: inbox.releasable,
|
|
36961
|
+
terminal: inbox.terminal,
|
|
36962
|
+
jobs
|
|
36963
|
+
});
|
|
36827
36964
|
return;
|
|
36828
36965
|
}
|
|
36829
|
-
const open = jobs.filter((j) => !TERMINAL_STATES.has(j.state));
|
|
36830
36966
|
printBlank();
|
|
36831
|
-
printInfo(
|
|
36967
|
+
printInfo(
|
|
36968
|
+
`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`
|
|
36969
|
+
);
|
|
36970
|
+
if (inbox.counts.fundedLate > 0) {
|
|
36971
|
+
printLine(import_picocolors13.default.dim(` ${inbox.counts.fundedLate} past deliver deadline (not deliverable \u2014 refund path is open to others)`));
|
|
36972
|
+
}
|
|
36832
36973
|
printBlank();
|
|
36833
|
-
for (const
|
|
36834
|
-
|
|
36835
|
-
|
|
36974
|
+
for (const [bucket, rows] of [
|
|
36975
|
+
["needsYou", inbox.needsYou],
|
|
36976
|
+
["releasable", inbox.releasable],
|
|
36977
|
+
["awaitingBuyer", inbox.awaitingBuyer],
|
|
36978
|
+
["fundedLate", inbox.fundedLate]
|
|
36979
|
+
]) {
|
|
36980
|
+
for (const job of rows) {
|
|
36981
|
+
printInboxRow(job, bucket);
|
|
36982
|
+
printBlank();
|
|
36983
|
+
}
|
|
36836
36984
|
}
|
|
36837
|
-
|
|
36985
|
+
const openCount = jobs.length - inbox.counts.terminal;
|
|
36986
|
+
if (openCount === 0) {
|
|
36838
36987
|
printInfo("No open jobs. New hires appear here the moment the escrow funds.");
|
|
36839
36988
|
printBlank();
|
|
36840
36989
|
}
|
|
36841
|
-
for (const job of jobs) seen.set(job.jobId, job.
|
|
36990
|
+
for (const job of jobs) seen.set(job.jobId, bucketSellerJob(job, Date.now()));
|
|
36842
36991
|
if (opts.once) return;
|
|
36843
36992
|
for (; ; ) {
|
|
36844
36993
|
await new Promise((r) => setTimeout(r, intervalMs));
|
|
36845
36994
|
let latest;
|
|
36846
36995
|
try {
|
|
36847
|
-
latest = await
|
|
36996
|
+
latest = await loadInbox();
|
|
36848
36997
|
} catch {
|
|
36849
36998
|
continue;
|
|
36850
36999
|
}
|
|
37000
|
+
const nowMs = Date.now();
|
|
36851
37001
|
for (const job of latest) {
|
|
37002
|
+
const bucket = bucketSellerJob(job, nowMs);
|
|
36852
37003
|
const prev = seen.get(job.jobId);
|
|
36853
|
-
if (prev ===
|
|
36854
|
-
seen.set(job.jobId,
|
|
37004
|
+
if (prev === bucket) continue;
|
|
37005
|
+
seen.set(job.jobId, bucket);
|
|
36855
37006
|
printBlank();
|
|
36856
|
-
if (prev === void 0 &&
|
|
37007
|
+
if (prev === void 0 && bucket === "needsYou") {
|
|
36857
37008
|
printSuccess(`New job \u2014 $${job.amountUsdc.toFixed(2)} USDC escrowed for you.`);
|
|
37009
|
+
} else if (bucket === "releasable") {
|
|
37010
|
+
printSuccess(`Releasable now \u2014 t2 job release ${job.jobId}`);
|
|
36858
37011
|
} else {
|
|
36859
37012
|
printInfo(`Job ${truncateAddress(job.jobId)}: ${prev ?? "new"} \u2192 ${job.state}`);
|
|
36860
37013
|
}
|
|
36861
|
-
printInboxRow(job);
|
|
37014
|
+
printInboxRow(job, bucket);
|
|
36862
37015
|
printBlank();
|
|
36863
37016
|
}
|
|
36864
37017
|
}
|