@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 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 from ${truncateAddress(job.buyer)}${deadline}`
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 can never keep funds (anyone may refund after the
36513
- deadline). v1 caps jobs at ${MAX_JOB_USDC} USDC.
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 jobs)
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, with --mine, the provider inbox (every job selling to you)").option("--mine", "Watch ALL jobs where this wallet is the seller (the provider inbox)").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) => {
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 jobs = await fetchSellerJobs(base, me);
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 fetchSellerJobs(base, me);
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 use --mine for the provider inbox.");
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
- printKeyValue(
37089
- "Seller",
37090
- `${o.agentName ?? "unnamed"} ${import_picocolors14.default.dim(truncateAddress(o.agent))}`
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-text search (empty = everything)").option("--api <url>", `API base URL (default ${DEFAULT_API_BASE8})`).action(async (query, cmdOpts) => {
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 params = query ? `?q=${encodeURIComponent(query)}` : "";
37246
- const json = await fetchJson4(`${base}/services${params}`);
37247
- const rows = json.services ?? [];
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({ query: query ?? null, total: json.total ?? rows.length, services: rows });
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(query ? `No services match "${query}".` : "No services listed yet.");
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
  }