@t2000/cli 10.30.1 → 10.30.3
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 +284 -28
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -30670,7 +30670,6 @@ function jobActionsFor(job, caller, nowMs = Date.now()) {
|
|
|
30670
30670
|
const actions = [];
|
|
30671
30671
|
if (job.state === "funded") {
|
|
30672
30672
|
if (isSeller && nowMs <= job.deliverByMs) actions.push("deliver");
|
|
30673
|
-
if (isBuyer) actions.push("release");
|
|
30674
30673
|
if (nowMs > job.deliverByMs) actions.push("refund");
|
|
30675
30674
|
} else if (job.state === "delivered") {
|
|
30676
30675
|
const windowClosesMs = (job.deliveredAtMs ?? 0) + job.reviewWindowMs;
|
|
@@ -36404,6 +36403,10 @@ function printJob(job, me) {
|
|
|
36404
36403
|
if (job.deliveryHash) printKeyValue("Delivery hash", job.deliveryHash);
|
|
36405
36404
|
printKeyValue("Reject split", `${job.rejectSplitBps / 100}% buyer / ${(1e4 - job.rejectSplitBps) / 100}% seller`);
|
|
36406
36405
|
}
|
|
36406
|
+
async function fetchBuyerJobs(base, buyer) {
|
|
36407
|
+
const json = await fetchJson4(`${base}/jobs?buyer=${encodeURIComponent(buyer)}&limit=100`);
|
|
36408
|
+
return json.jobs ?? [];
|
|
36409
|
+
}
|
|
36407
36410
|
async function fetchSellerJobs(base, seller) {
|
|
36408
36411
|
const json = await fetchJson4(`${base}/jobs?seller=${encodeURIComponent(seller)}&limit=100`);
|
|
36409
36412
|
return json.jobs ?? [];
|
|
@@ -36442,6 +36445,83 @@ function summarizeSellerInbox(jobs, nowMs) {
|
|
|
36442
36445
|
}
|
|
36443
36446
|
return buckets;
|
|
36444
36447
|
}
|
|
36448
|
+
var HYDRATE_MAX = 25;
|
|
36449
|
+
var HYDRATE_CONCURRENCY = 8;
|
|
36450
|
+
function mergeIndexedJobFromChain(row, chain) {
|
|
36451
|
+
return {
|
|
36452
|
+
...row,
|
|
36453
|
+
jobId: chain.id ?? row.jobId,
|
|
36454
|
+
state: chain.state,
|
|
36455
|
+
buyer: chain.buyer ?? row.buyer,
|
|
36456
|
+
seller: chain.seller ?? row.seller,
|
|
36457
|
+
amountUsdc: chain.amountUsdc ?? row.amountUsdc,
|
|
36458
|
+
deliverByMs: chain.deliverByMs ?? row.deliverByMs,
|
|
36459
|
+
reviewWindowMs: chain.reviewWindowMs ?? row.reviewWindowMs,
|
|
36460
|
+
deliveryHash: chain.deliveryHash ?? row.deliveryHash,
|
|
36461
|
+
deliveredAtMs: chain.deliveredAtMs ?? row.deliveredAtMs ?? null,
|
|
36462
|
+
createdAtMs: chain.createdAtMs ?? row.createdAtMs,
|
|
36463
|
+
// Review clock prefers deliveredAtMs (reviewClosesMs anchors there when
|
|
36464
|
+
// set); for delivered rows keep updatedAtMs honest with the chain clock,
|
|
36465
|
+
// otherwise the index value stands — never invent a clock.
|
|
36466
|
+
updatedAtMs: chain.state === "delivered" && chain.deliveredAtMs != null ? chain.deliveredAtMs : row.updatedAtMs
|
|
36467
|
+
};
|
|
36468
|
+
}
|
|
36469
|
+
async function hydrateJobsFromChain(rows, getJobById, opts) {
|
|
36470
|
+
const max = opts?.maxHydrate ?? HYDRATE_MAX;
|
|
36471
|
+
const out = [...rows];
|
|
36472
|
+
const targets = [];
|
|
36473
|
+
for (let i = 0; i < out.length && targets.length < max; i++) {
|
|
36474
|
+
if (!TERMINAL_STATES.has(out[i].state)) targets.push(i);
|
|
36475
|
+
}
|
|
36476
|
+
for (let at = 0; at < targets.length; at += HYDRATE_CONCURRENCY) {
|
|
36477
|
+
await Promise.all(
|
|
36478
|
+
targets.slice(at, at + HYDRATE_CONCURRENCY).map(async (i) => {
|
|
36479
|
+
try {
|
|
36480
|
+
out[i] = mergeIndexedJobFromChain(out[i], await getJobById(out[i].jobId));
|
|
36481
|
+
} catch {
|
|
36482
|
+
}
|
|
36483
|
+
})
|
|
36484
|
+
);
|
|
36485
|
+
}
|
|
36486
|
+
return out;
|
|
36487
|
+
}
|
|
36488
|
+
var hydrateSellerJobsFromChain = hydrateJobsFromChain;
|
|
36489
|
+
function bucketBuyerJob(job, nowMs) {
|
|
36490
|
+
if (TERMINAL_STATES.has(job.state)) {
|
|
36491
|
+
return "terminal";
|
|
36492
|
+
}
|
|
36493
|
+
if (job.state === "delivered") {
|
|
36494
|
+
return "needsYou";
|
|
36495
|
+
}
|
|
36496
|
+
return nowMs > job.deliverByMs ? "refundable" : "waiting";
|
|
36497
|
+
}
|
|
36498
|
+
function summarizeBuyerInbox(jobs, nowMs) {
|
|
36499
|
+
const buckets = {
|
|
36500
|
+
counts: { total: jobs.length, needsYou: 0, refundable: 0, waiting: 0, terminal: 0 },
|
|
36501
|
+
needsYou: [],
|
|
36502
|
+
refundable: [],
|
|
36503
|
+
waiting: [],
|
|
36504
|
+
terminal: []
|
|
36505
|
+
};
|
|
36506
|
+
for (const job of jobs) {
|
|
36507
|
+
const bucket = bucketBuyerJob(job, nowMs);
|
|
36508
|
+
buckets[bucket].push(job);
|
|
36509
|
+
buckets.counts[bucket] += 1;
|
|
36510
|
+
}
|
|
36511
|
+
return buckets;
|
|
36512
|
+
}
|
|
36513
|
+
function buyerInboxHint(job, bucket) {
|
|
36514
|
+
switch (bucket) {
|
|
36515
|
+
case "needsYou":
|
|
36516
|
+
return `delivered \u2014 grade it: t2 job release ${job.jobId} \xB7 t2 job reject ${job.jobId}`;
|
|
36517
|
+
case "refundable":
|
|
36518
|
+
return `deadline passed, no delivery \u2014 t2 job refund ${job.jobId} (fee-free, anyone may crank it)`;
|
|
36519
|
+
case "waiting":
|
|
36520
|
+
return `waiting on the seller \u2014 t2 job watch ${job.jobId}`;
|
|
36521
|
+
default:
|
|
36522
|
+
return "";
|
|
36523
|
+
}
|
|
36524
|
+
}
|
|
36445
36525
|
function inboxHint(job, bucket) {
|
|
36446
36526
|
switch (bucket) {
|
|
36447
36527
|
case "needsYou":
|
|
@@ -36459,12 +36539,17 @@ function inboxHint(job, bucket) {
|
|
|
36459
36539
|
}
|
|
36460
36540
|
}
|
|
36461
36541
|
function printInboxRow(job, bucket) {
|
|
36542
|
+
printInboxRowWithHint(job, inboxHint(job, bucket), "from", job.buyer);
|
|
36543
|
+
}
|
|
36544
|
+
function printBuyerInboxRow(job, bucket) {
|
|
36545
|
+
printInboxRowWithHint(job, buyerInboxHint(job, bucket), "seller", job.seller);
|
|
36546
|
+
}
|
|
36547
|
+
function printInboxRowWithHint(job, hint, partyLabel, party) {
|
|
36462
36548
|
const deadline = job.state === "funded" ? ` \xB7 deliver by ${new Date(job.deliverByMs).toISOString()}` : "";
|
|
36463
36549
|
printLine(
|
|
36464
|
-
` ${stateColor(job.state)} $${job.amountUsdc.toFixed(2)} USDC \xB7
|
|
36550
|
+
` ${stateColor(job.state)} $${job.amountUsdc.toFixed(2)} USDC \xB7 ${partyLabel} ${truncateAddress(party)}${deadline}`
|
|
36465
36551
|
);
|
|
36466
36552
|
printLine(` ${import_picocolors13.default.dim(job.jobId)}`);
|
|
36467
|
-
const hint = inboxHint(job, bucket);
|
|
36468
36553
|
if (hint) printLine(` ${import_picocolors13.default.dim("\u2192")} ${hint}`);
|
|
36469
36554
|
}
|
|
36470
36555
|
function deliverPreflightError(state, deliverByMs, nowMs) {
|
|
@@ -36509,8 +36594,11 @@ function registerJob(program3) {
|
|
|
36509
36594
|
The escrow is a Sui object, not a company: funds lock inside the Job object at
|
|
36510
36595
|
create; release/refund are pure functions of (state, clock, caller). A ghosting
|
|
36511
36596
|
buyer can't strand a delivering seller (anyone may release after the review
|
|
36512
|
-
window) and a no-show seller
|
|
36513
|
-
deadline
|
|
36597
|
+
window), and a no-show seller cannot keep funds AS LONG AS the buyer uses the
|
|
36598
|
+
escrow's protections \u2014 wait out the deadline and refund (anyone may crank it),
|
|
36599
|
+
or reject a bad delivery in-window. Releasing early without a delivery
|
|
36600
|
+
(--pay-without-delivery) voluntarily waives that protection. v1 caps jobs at
|
|
36601
|
+
${MAX_JOB_USDC} USDC.
|
|
36514
36602
|
|
|
36515
36603
|
Typical flow:
|
|
36516
36604
|
buyer $ t2 job hire 5 0xSELLER --spec brief.md --deadline 24h
|
|
@@ -36518,7 +36606,8 @@ Typical flow:
|
|
|
36518
36606
|
seller $ t2 job deliver 0xJOB report.md
|
|
36519
36607
|
buyer $ t2 job release 0xJOB (or: t2 job reject 0xJOB)
|
|
36520
36608
|
either $ t2 job watch 0xJOB
|
|
36521
|
-
seller $ t2 job watch --mine (the provider inbox \u2014 all your
|
|
36609
|
+
seller $ t2 job watch --mine (the provider inbox \u2014 all your sells)
|
|
36610
|
+
buyer $ t2 job watch --buying (the buyer inbox \u2014 every job you funded)
|
|
36522
36611
|
|
|
36523
36612
|
Hiring a LISTING (t2 ACP) \u2014 price + terms come from the listing:
|
|
36524
36613
|
buyer $ t2 services "market report"
|
|
@@ -36777,12 +36866,45 @@ no fund step; unclaimed openings refund fee-free):
|
|
|
36777
36866
|
handleError(error);
|
|
36778
36867
|
}
|
|
36779
36868
|
});
|
|
36869
|
+
group.command("release").argument("<jobId>", "The Job object id (0x\u2026)").description("Accept delivery \u2014 funds go to the seller (buyer; or anyone once the review window lapses). On a FUNDED job with no delivery this pays the full escrow to the seller, terminally \u2014 refused unless --pay-without-delivery.").option("--pay-without-delivery", "DELIBERATE goodwill: release the full escrow on a funded job with NO delivery (off-band delivery only \u2014 the seller keeps everything, no refund path)").option("--key <path>", "Custom wallet path (default ~/.t2000/wallet.key)").option("--api <url>", `API base URL (default ${DEFAULT_API_BASE7})`).action(async (jobId, opts) => {
|
|
36870
|
+
try {
|
|
36871
|
+
const existing = await getJob(getSuiClient(), jobId).catch(() => null);
|
|
36872
|
+
const undelivered = existing?.state === "funded" && !existing.deliveryHash;
|
|
36873
|
+
if (undelivered && !opts.payWithoutDelivery) {
|
|
36874
|
+
throw new Error(
|
|
36875
|
+
"This job is FUNDED with no delivery \u2014 releasing now pays the full escrow to the seller with no recovery path. Wait for the delivery, refund after the deadline (t2 job refund), or \u2014 only if the work arrived off-band \u2014 re-run with --pay-without-delivery."
|
|
36876
|
+
);
|
|
36877
|
+
}
|
|
36878
|
+
if (undelivered && opts.payWithoutDelivery) {
|
|
36879
|
+
printWarning(
|
|
36880
|
+
"Paying WITHOUT an on-chain delivery: the full escrow goes to the seller, terminally. No refund path exists after this."
|
|
36881
|
+
);
|
|
36882
|
+
}
|
|
36883
|
+
const { digest } = await sponsoredJobVerb({
|
|
36884
|
+
base: opts.api ?? DEFAULT_API_BASE7,
|
|
36885
|
+
keyPath: opts.key,
|
|
36886
|
+
action: "release",
|
|
36887
|
+
params: {
|
|
36888
|
+
jobId,
|
|
36889
|
+
...opts.payWithoutDelivery ? { payWithoutDelivery: true } : {}
|
|
36890
|
+
}
|
|
36891
|
+
});
|
|
36892
|
+
if (isJsonMode()) {
|
|
36893
|
+
printJson({ jobId, action: "release", digest, ...undelivered ? { paidWithoutDelivery: true } : {} });
|
|
36894
|
+
return;
|
|
36895
|
+
}
|
|
36896
|
+
printBlank();
|
|
36897
|
+
printSuccess("Funds released to the seller.");
|
|
36898
|
+
if (digest) printKeyValue("Tx", digest);
|
|
36899
|
+
if (existing?.deliveryHash) {
|
|
36900
|
+
printInfo(`Rate the work: t2 job review ${jobId} --stars <1-5>`);
|
|
36901
|
+
}
|
|
36902
|
+
printBlank();
|
|
36903
|
+
} catch (error) {
|
|
36904
|
+
handleError(error);
|
|
36905
|
+
}
|
|
36906
|
+
});
|
|
36780
36907
|
for (const [verb, description, note] of [
|
|
36781
|
-
[
|
|
36782
|
-
"release",
|
|
36783
|
-
"Accept delivery \u2014 funds go to the seller (buyer; or anyone once the review window lapses)",
|
|
36784
|
-
"Funds released to the seller."
|
|
36785
|
-
],
|
|
36786
36908
|
[
|
|
36787
36909
|
"reject",
|
|
36788
36910
|
"Reject a delivery within the review window \u2014 funds split per the create terms (buyer)",
|
|
@@ -36814,9 +36936,6 @@ no fund step; unclaimed openings refund fee-free):
|
|
|
36814
36936
|
printBlank();
|
|
36815
36937
|
printSuccess(note);
|
|
36816
36938
|
if (digest) printKeyValue("Tx", digest);
|
|
36817
|
-
if (verb === "release") {
|
|
36818
|
-
printInfo(`Rate the work (builds the seller's on-chain-backed reputation): t2 job review ${jobId} --stars 5`);
|
|
36819
|
-
}
|
|
36820
36939
|
printBlank();
|
|
36821
36940
|
} catch (error) {
|
|
36822
36941
|
handleError(error);
|
|
@@ -36829,6 +36948,12 @@ no fund step; unclaimed openings refund fee-free):
|
|
|
36829
36948
|
if (!Number.isInteger(stars) || stars < 1 || stars > 5) {
|
|
36830
36949
|
throw new Error(`--stars must be an integer 1\u20135 (got "${opts.stars}").`);
|
|
36831
36950
|
}
|
|
36951
|
+
const reviewedJob = await getJob(getSuiClient(), jobId).catch(() => null);
|
|
36952
|
+
if (reviewedJob && !reviewedJob.deliveryHash) {
|
|
36953
|
+
throw new Error(
|
|
36954
|
+
"This job has no on-chain delivery \u2014 there is no work to rate. Reviews attach only to jobs the seller actually delivered."
|
|
36955
|
+
);
|
|
36956
|
+
}
|
|
36832
36957
|
const base = opts.api ?? DEFAULT_API_BASE7;
|
|
36833
36958
|
const agent = await withAgent({ keyPath: opts.key });
|
|
36834
36959
|
const address = agent.address();
|
|
@@ -36898,15 +37023,90 @@ no fund step; unclaimed openings refund fee-free):
|
|
|
36898
37023
|
handleError(error);
|
|
36899
37024
|
}
|
|
36900
37025
|
});
|
|
36901
|
-
group.command("watch").argument("[jobId]", "The Job object id (0x\u2026) \u2014 omit with --mine").description("Poll a job \u2014 or
|
|
37026
|
+
group.command("watch").argument("[jobId]", "The Job object id (0x\u2026) \u2014 omit with --mine or --buying").description("Poll a job \u2014 or an inbox: --mine (every job selling to you) / --buying (every job you funded)").option("--mine", "Watch ALL jobs where this wallet is the seller (the provider inbox)").option("--buying", "Watch ALL jobs where this wallet is the BUYER \u2014 recovers job ids the hire line printed once (S.1016)").option("--interval <seconds>", "Poll interval", "15").option("--once", "Print the current state and exit").option("--key <path>", "Custom wallet path (default ~/.t2000/wallet.key)").option("--api <url>", `API base URL (default ${DEFAULT_API_BASE7})`).action(async (jobId, opts) => {
|
|
36902
37027
|
try {
|
|
37028
|
+
if (opts.mine && opts.buying) {
|
|
37029
|
+
throw new Error("Pick one seat: --mine (selling) or --buying (funding) \u2014 not both.");
|
|
37030
|
+
}
|
|
37031
|
+
if (jobId && (opts.mine || opts.buying)) {
|
|
37032
|
+
throw new Error("Pick one mode: a job id OR an inbox flag (--mine / --buying).");
|
|
37033
|
+
}
|
|
36903
37034
|
const agent = await withAgent({ keyPath: opts.key });
|
|
36904
37035
|
const me = agent.address();
|
|
36905
37036
|
const intervalMs = Math.max(5, Number.parseInt(opts.interval, 10) || 15) * 1e3;
|
|
37037
|
+
if (opts.buying) {
|
|
37038
|
+
const base = opts.api ?? DEFAULT_API_BASE7;
|
|
37039
|
+
const seen = /* @__PURE__ */ new Map();
|
|
37040
|
+
const buyClient = getSuiClient();
|
|
37041
|
+
const loadInbox = async () => hydrateJobsFromChain(await fetchBuyerJobs(base, me), (id) => getJob(buyClient, id));
|
|
37042
|
+
const jobs = await loadInbox();
|
|
37043
|
+
const inbox = summarizeBuyerInbox(jobs, Date.now());
|
|
37044
|
+
if (isJsonMode()) {
|
|
37045
|
+
printJson({
|
|
37046
|
+
buyer: me,
|
|
37047
|
+
counts: inbox.counts,
|
|
37048
|
+
needsYou: inbox.needsYou,
|
|
37049
|
+
refundable: inbox.refundable,
|
|
37050
|
+
waiting: inbox.waiting,
|
|
37051
|
+
terminal: inbox.terminal,
|
|
37052
|
+
jobs
|
|
37053
|
+
});
|
|
37054
|
+
return;
|
|
37055
|
+
}
|
|
37056
|
+
printBlank();
|
|
37057
|
+
printInfo(
|
|
37058
|
+
`Buyer inbox for ${truncateAddress(me)} \u2014 ${jobs.length} job(s) \xB7 ${inbox.counts.needsYou} need you \xB7 ${inbox.counts.refundable} refundable \xB7 ${inbox.counts.waiting} waiting`
|
|
37059
|
+
);
|
|
37060
|
+
printBlank();
|
|
37061
|
+
for (const [bucket, rows] of [
|
|
37062
|
+
["needsYou", inbox.needsYou],
|
|
37063
|
+
["refundable", inbox.refundable],
|
|
37064
|
+
["waiting", inbox.waiting]
|
|
37065
|
+
]) {
|
|
37066
|
+
for (const job of rows) {
|
|
37067
|
+
printBuyerInboxRow(job, bucket);
|
|
37068
|
+
printBlank();
|
|
37069
|
+
}
|
|
37070
|
+
}
|
|
37071
|
+
if (jobs.length - inbox.counts.terminal === 0) {
|
|
37072
|
+
printInfo("No open buys. Hire a listing or post an Open job and it lands here.");
|
|
37073
|
+
printBlank();
|
|
37074
|
+
}
|
|
37075
|
+
for (const job of jobs) seen.set(job.jobId, bucketBuyerJob(job, Date.now()));
|
|
37076
|
+
if (opts.once) return;
|
|
37077
|
+
for (; ; ) {
|
|
37078
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
37079
|
+
let latest;
|
|
37080
|
+
try {
|
|
37081
|
+
latest = await loadInbox();
|
|
37082
|
+
} catch {
|
|
37083
|
+
continue;
|
|
37084
|
+
}
|
|
37085
|
+
const nowMs = Date.now();
|
|
37086
|
+
for (const job of latest) {
|
|
37087
|
+
const bucket = bucketBuyerJob(job, nowMs);
|
|
37088
|
+
const prev = seen.get(job.jobId);
|
|
37089
|
+
if (prev === bucket) continue;
|
|
37090
|
+
seen.set(job.jobId, bucket);
|
|
37091
|
+
printBlank();
|
|
37092
|
+
if (bucket === "needsYou") {
|
|
37093
|
+
printSuccess(`Delivered \u2014 grade it: t2 job release ${job.jobId} \xB7 t2 job reject ${job.jobId}`);
|
|
37094
|
+
} else if (bucket === "refundable") {
|
|
37095
|
+
printSuccess(`Refundable \u2014 t2 job refund ${job.jobId} (deadline passed, no delivery)`);
|
|
37096
|
+
} else {
|
|
37097
|
+
printInfo(`Job ${truncateAddress(job.jobId)}: ${prev ?? "new"} \u2192 ${job.state}`);
|
|
37098
|
+
}
|
|
37099
|
+
printBuyerInboxRow(job, bucket);
|
|
37100
|
+
printBlank();
|
|
37101
|
+
}
|
|
37102
|
+
}
|
|
37103
|
+
}
|
|
36906
37104
|
if (opts.mine) {
|
|
36907
37105
|
const base = opts.api ?? DEFAULT_API_BASE7;
|
|
36908
37106
|
const seen = /* @__PURE__ */ new Map();
|
|
36909
|
-
const
|
|
37107
|
+
const mineClient = getSuiClient();
|
|
37108
|
+
const loadInbox = async () => hydrateSellerJobsFromChain(await fetchSellerJobs(base, me), (id) => getJob(mineClient, id));
|
|
37109
|
+
const jobs = await loadInbox();
|
|
36910
37110
|
const inbox = summarizeSellerInbox(jobs, Date.now());
|
|
36911
37111
|
if (isJsonMode()) {
|
|
36912
37112
|
printJson({
|
|
@@ -36951,7 +37151,7 @@ no fund step; unclaimed openings refund fee-free):
|
|
|
36951
37151
|
await new Promise((r) => setTimeout(r, intervalMs));
|
|
36952
37152
|
let latest;
|
|
36953
37153
|
try {
|
|
36954
|
-
latest = await
|
|
37154
|
+
latest = await loadInbox();
|
|
36955
37155
|
} catch {
|
|
36956
37156
|
continue;
|
|
36957
37157
|
}
|
|
@@ -36975,7 +37175,7 @@ no fund step; unclaimed openings refund fee-free):
|
|
|
36975
37175
|
}
|
|
36976
37176
|
}
|
|
36977
37177
|
if (!jobId) {
|
|
36978
|
-
printError("Provide a job id \u2014 or
|
|
37178
|
+
printError("Provide a job id \u2014 or an inbox: --mine (selling) / --buying (funding).");
|
|
36979
37179
|
process.exitCode = 1;
|
|
36980
37180
|
return;
|
|
36981
37181
|
}
|
|
@@ -37085,10 +37285,13 @@ function printService(o) {
|
|
|
37085
37285
|
`$${o.priceUsdc.toFixed(2)} USDC ${import_picocolors14.default.dim(`\xB7 seller receives ${settlementSplit(o.priceUsdc).payout}`)}`
|
|
37086
37286
|
);
|
|
37087
37287
|
printKeyValue("Delivery", `within ${formatSla(o.slaMinutes)}`);
|
|
37088
|
-
|
|
37089
|
-
"
|
|
37090
|
-
|
|
37091
|
-
|
|
37288
|
+
{
|
|
37289
|
+
const id = o.agentNumericId != null ? ` #${o.agentNumericId}` : "";
|
|
37290
|
+
printKeyValue(
|
|
37291
|
+
"Seller",
|
|
37292
|
+
`${o.agentName ?? "unnamed"}${import_picocolors14.default.bold(id)} ${import_picocolors14.default.dim(truncateAddress(o.agent))}`
|
|
37293
|
+
);
|
|
37294
|
+
}
|
|
37092
37295
|
printKeyValue("You get", o.deliverable);
|
|
37093
37296
|
if (o.requirements != null) {
|
|
37094
37297
|
printKeyValue(
|
|
@@ -37238,15 +37441,55 @@ Examples:
|
|
|
37238
37441
|
}
|
|
37239
37442
|
});
|
|
37240
37443
|
}
|
|
37444
|
+
async function resolveServicesQuery(base, query) {
|
|
37445
|
+
const q = query?.trim();
|
|
37446
|
+
if (!q) {
|
|
37447
|
+
return { url: `${base}/services`, scope: { kind: "all" } };
|
|
37448
|
+
}
|
|
37449
|
+
if (q.startsWith("0x")) {
|
|
37450
|
+
try {
|
|
37451
|
+
const agent = validateAddress(q);
|
|
37452
|
+
return {
|
|
37453
|
+
url: `${base}/services?agent=${encodeURIComponent(agent)}`,
|
|
37454
|
+
scope: { kind: "agent", agent }
|
|
37455
|
+
};
|
|
37456
|
+
} catch {
|
|
37457
|
+
}
|
|
37458
|
+
} else if (looksLikeAgentRefValue(q)) {
|
|
37459
|
+
const ref = await resolveAgentRef(base, q);
|
|
37460
|
+
return {
|
|
37461
|
+
url: `${base}/services?agent=${encodeURIComponent(ref.address)}`,
|
|
37462
|
+
scope: { kind: "agent", agent: ref.address, numericId: ref.numericId }
|
|
37463
|
+
};
|
|
37464
|
+
}
|
|
37465
|
+
return {
|
|
37466
|
+
url: `${base}/services?q=${encodeURIComponent(q)}`,
|
|
37467
|
+
scope: { kind: "search", q }
|
|
37468
|
+
};
|
|
37469
|
+
}
|
|
37470
|
+
function scopeLabel(scope) {
|
|
37471
|
+
if (scope.kind !== "agent") {
|
|
37472
|
+
return "";
|
|
37473
|
+
}
|
|
37474
|
+
return scope.numericId != null ? `#${scope.numericId} (${truncateAddress(scope.agent)})` : truncateAddress(scope.agent);
|
|
37475
|
+
}
|
|
37241
37476
|
function registerDiscovery(command, opts) {
|
|
37242
|
-
command.argument("[query]", "What you need \u2014 free
|
|
37477
|
+
command.argument("[query]", "What you need \u2014 free text, or a SELLER scope: 0x\u2026 address, #id, or @handle (empty = everything)").option("--api <url>", `API base URL (default ${DEFAULT_API_BASE8})`).action(async (query, cmdOpts) => {
|
|
37243
37478
|
try {
|
|
37244
37479
|
const base = cmdOpts.api ?? DEFAULT_API_BASE8;
|
|
37245
|
-
const
|
|
37246
|
-
const json = await fetchJson4(
|
|
37247
|
-
const
|
|
37480
|
+
const { url, scope } = await resolveServicesQuery(base, query);
|
|
37481
|
+
const json = await fetchJson4(url);
|
|
37482
|
+
const all = json.services ?? [];
|
|
37483
|
+
const rows = scope.kind === "agent" ? all.filter((o) => !o.retired) : all;
|
|
37484
|
+
const retiredHidden = all.length - rows.length;
|
|
37248
37485
|
if (isJsonMode()) {
|
|
37249
|
-
printJson({
|
|
37486
|
+
printJson({
|
|
37487
|
+
query: query ?? null,
|
|
37488
|
+
scope,
|
|
37489
|
+
total: scope.kind === "agent" ? rows.length : json.total ?? rows.length,
|
|
37490
|
+
...retiredHidden > 0 ? { retiredHidden } : {},
|
|
37491
|
+
services: rows
|
|
37492
|
+
});
|
|
37250
37493
|
return;
|
|
37251
37494
|
}
|
|
37252
37495
|
printBlank();
|
|
@@ -37255,7 +37498,9 @@ function registerDiscovery(command, opts) {
|
|
|
37255
37498
|
printBlank();
|
|
37256
37499
|
}
|
|
37257
37500
|
if (rows.length === 0) {
|
|
37258
|
-
printInfo(
|
|
37501
|
+
printInfo(
|
|
37502
|
+
scope.kind === "agent" ? `No active services for ${scopeLabel(scope)}.` : scope.kind === "search" ? `No services match "${scope.q}".` : "No services listed yet."
|
|
37503
|
+
);
|
|
37259
37504
|
printBlank();
|
|
37260
37505
|
return;
|
|
37261
37506
|
}
|
|
@@ -37263,6 +37508,17 @@ function registerDiscovery(command, opts) {
|
|
|
37263
37508
|
printService(o);
|
|
37264
37509
|
printBlank();
|
|
37265
37510
|
}
|
|
37511
|
+
if (retiredHidden > 0) {
|
|
37512
|
+
printInfo(import_picocolors14.default.dim(`${retiredHidden} retired omitted \u2014 t2 service list ${scope.kind === "agent" ? scope.agent : ""}`));
|
|
37513
|
+
printBlank();
|
|
37514
|
+
}
|
|
37515
|
+
if (scope.kind === "search") {
|
|
37516
|
+
const sellers = new Set(rows.map((o) => o.agent)).size;
|
|
37517
|
+
if (sellers >= 2) {
|
|
37518
|
+
printInfo(import_picocolors14.default.dim(`Results span ${sellers} sellers \u2014 scope to one with t2 services 0x\u2026 or #id.`));
|
|
37519
|
+
printBlank();
|
|
37520
|
+
}
|
|
37521
|
+
}
|
|
37266
37522
|
} catch (error) {
|
|
37267
37523
|
handleError(error);
|
|
37268
37524
|
}
|