@t2000/cli 9.9.1 → 9.10.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.
@@ -145782,7 +145782,7 @@ Through this wallet you can reach essentially any major external API, billed to
145782
145782
  CRITICAL: When the user asks to use any external or paid API, names a provider (e.g. "via fal.ai", "with ElevenLabs"), or requests a capability one of the services above provides, DO NOT say you cannot reach that service, that it isn't on an allowlist, or that there's no connector \u2014 and do NOT fall back to writing a script for the user to run. You CAN do it directly through this wallet. Use t2000_services to discover the endpoint and request shape, then t2000_pay to execute, then show the user the result (display image/audio URLs returned in the response).
145783
145783
 
145784
145784
  Spending is the user's own USDC and every t2000_pay call is bounded by maxPrice. For larger or multi-step spends, state the estimated cost first and proceed once the user is happy. Use t2000_balance to check funds. The v4 wallet is payments-only \u2014 there is no savings / lending surface.`;
145785
- var PKG_VERSION = "9.9.1";
145785
+ var PKG_VERSION = "9.10.0";
145786
145786
  console.log = (...args) => console.error("[log]", ...args);
145787
145787
  console.warn = (...args) => console.error("[warn]", ...args);
145788
145788
  async function startMcpServer(opts) {
@@ -145867,4 +145867,4 @@ mime-types/index.js:
145867
145867
  @scure/bip39/index.js:
145868
145868
  (*! scure-bip39 - MIT License (c) 2022 Patricio Palladino, Paul Miller (paulmillr.com) *)
145869
145869
  */
145870
- //# sourceMappingURL=dist-436FYT54.js.map
145870
+ //# sourceMappingURL=dist-FMVRVPKV.js.map
package/dist/index.js CHANGED
@@ -23370,7 +23370,7 @@ function registerMcpStart(parent) {
23370
23370
  parent.command("start", { isDefault: true }).description("Start MCP server (stdio transport \u2014 for AI client integration)").option("--key <path>", "Custom wallet path (default ~/.t2000/wallet.key)").action(async (opts) => {
23371
23371
  let mod;
23372
23372
  try {
23373
- mod = await import("./dist-436FYT54.js");
23373
+ mod = await import("./dist-FMVRVPKV.js");
23374
23374
  } catch {
23375
23375
  console.error("MCP server not installed. Run:\n npm install -g @t2000/mcp");
23376
23376
  process.exit(1);
@@ -24512,8 +24512,72 @@ function registerCheck(program3) {
24512
24512
 
24513
24513
  // src/commands/job.ts
24514
24514
  var import_picocolors15 = __toESM(require_picocolors(), 1);
24515
- import { createHash } from "crypto";
24515
+ import { createHash as createHash2 } from "crypto";
24516
24516
  import { readFile as readFile3 } from "fs/promises";
24517
+
24518
+ // src/lib/offerings.ts
24519
+ import { createHash } from "crypto";
24520
+ async function fetchJson3(url, init) {
24521
+ const res = await fetch(url, {
24522
+ method: init?.method ?? "GET",
24523
+ headers: init?.body ? { "Content-Type": "application/json" } : void 0,
24524
+ body: init?.body ? JSON.stringify(init.body) : void 0
24525
+ });
24526
+ const json = await res.json().catch(() => ({}));
24527
+ if (!res.ok) {
24528
+ const err = json.error;
24529
+ const msg = typeof err === "string" ? err : err?.message ?? `HTTP ${res.status}`;
24530
+ throw new Error(msg);
24531
+ }
24532
+ return json;
24533
+ }
24534
+ async function fetchOffering(base, agent, slug) {
24535
+ const json = await fetchJson3(
24536
+ `${base}/offerings?agent=${encodeURIComponent(agent)}`
24537
+ );
24538
+ const rows = json.offerings ?? [];
24539
+ const match = rows.find((o) => o.slug === slug.trim().toLowerCase());
24540
+ if (!match) {
24541
+ const live = rows.filter((o) => !o.retired).map((o) => o.slug);
24542
+ throw new Error(
24543
+ `Agent ${truncateAddress(agent)} has no offering "${slug}".` + (live.length > 0 ? ` Live offerings: ${live.join(", ")}` : "")
24544
+ );
24545
+ }
24546
+ if (match.retired) {
24547
+ throw new Error(
24548
+ `Offering "${slug}" is retired \u2014 the seller no longer sells it.`
24549
+ );
24550
+ }
24551
+ return match;
24552
+ }
24553
+ async function putJobSpec(base, content) {
24554
+ const json = await fetchJson3(`${base}/job/spec`, {
24555
+ method: "POST",
24556
+ body: { content }
24557
+ });
24558
+ const hash = json.hash;
24559
+ if (!hash) {
24560
+ throw new Error("Failed to store the job spec.");
24561
+ }
24562
+ return hash;
24563
+ }
24564
+ async function getJobSpec(base, hash) {
24565
+ const clean = hash.trim().toLowerCase().replace(/^0x/, "");
24566
+ const json = await fetchJson3(`${base}/job/spec/${clean}`);
24567
+ const content = json.content;
24568
+ if (content === void 0) {
24569
+ throw new Error("No spec stored for this hash.");
24570
+ }
24571
+ const actual = createHash("sha256").update(content, "utf8").digest("hex");
24572
+ if (actual !== clean) {
24573
+ throw new Error(
24574
+ "Spec content does NOT match its hash \u2014 the store returned tampered data. Do not trust it."
24575
+ );
24576
+ }
24577
+ return content;
24578
+ }
24579
+
24580
+ // src/commands/job.ts
24517
24581
  var DEFAULT_API_BASE5 = process.env.T2000_API_URL ?? "https://api.t2000.ai/v1";
24518
24582
  var DEFAULT_REVIEW_WINDOW_MS = 24 * 60 * 60 * 1e3;
24519
24583
  var DEFAULT_REJECT_SPLIT_BPS = 8e3;
@@ -24536,7 +24600,7 @@ async function resolveCommitment(input) {
24536
24600
  } catch {
24537
24601
  bytes = Buffer.from(input, "utf8");
24538
24602
  }
24539
- return `0x${createHash("sha256").update(bytes).digest("hex")}`;
24603
+ return `0x${createHash2("sha256").update(bytes).digest("hex")}`;
24540
24604
  }
24541
24605
  function stateColor(state) {
24542
24606
  if (state === "released") return import_picocolors15.default.green(state);
@@ -24590,21 +24654,100 @@ Typical flow:
24590
24654
  seller $ t2 job deliver 0xJOB report.pdf
24591
24655
  buyer $ t2 job release 0xJOB (or: t2 job reject 0xJOB)
24592
24656
  either $ t2 job watch 0xJOB
24657
+
24658
+ Buying an OFFERING (t2 ACP) \u2014 price + terms come from the listing:
24659
+ buyer $ t2 browse "market report"
24660
+ buyer $ t2 job create --agent 0xSELLER --offering sui-market-report \\
24661
+ --requirements '{"token":"DEEP"}'
24662
+ seller $ t2 job spec 0xJOB (read the buyer's requirements)
24593
24663
  `
24594
24664
  );
24595
- group.command("create").argument("<amount>", `USDC to escrow (max ${MAX_JOB_USDC})`).argument("<seller>", "The seller's Sui address (their listing's payTo wallet)").description("Create + fund an escrow job in one transaction (buyer)").requiredOption("--spec <file-or-text>", "Job spec \u2014 a file path or inline text (hashed on-chain), or a 0x\u2026 hash").option("--deadline <duration>", "Time the seller has to deliver (e.g. 30m, 24h, 7d)", "24h").option("--review <duration>", "Your accept/reject window after delivery", "24h").option("--split <bps>", "Your share in bps if you reject (0\u201310000)", String(DEFAULT_REJECT_SPLIT_BPS)).option("--key <path>", "Custom wallet path (default ~/.t2000/wallet.key)").option("--api <url>", `API base URL (default ${DEFAULT_API_BASE5})`).action(
24665
+ group.command("create").argument("[amount]", `USDC to escrow (max ${MAX_JOB_USDC}; omit when buying an --offering)`).argument("[seller]", "The seller's Sui address (omit when buying an --offering)").description("Create + fund an escrow job in one transaction (buyer)").option("--spec <file-or-text>", "Job spec \u2014 a file path or inline text (hashed on-chain), or a 0x\u2026 hash").option("--agent <address>", "Buy from an offering: the seller's agent address").option("--offering <slug>", "The offering slug (see t2 browse / t2 offering list <agent>)").option("--requirements <file-or-json-or-text>", "Your requirements for the offering (what the seller asked for)").option("--deadline <duration>", "Time the seller has to deliver (e.g. 30m, 24h, 7d)", "24h").option("--review <duration>", "Your accept/reject window after delivery", "24h").option("--split <bps>", "Your share in bps if you reject (0\u201310000)", String(DEFAULT_REJECT_SPLIT_BPS)).option("--key <path>", "Custom wallet path (default ~/.t2000/wallet.key)").option("--api <url>", `API base URL (default ${DEFAULT_API_BASE5})`).action(
24596
24666
  async (amountArg, sellerArg, opts) => {
24597
24667
  try {
24598
- const amountUsdc = Number.parseFloat(amountArg);
24599
- if (!Number.isFinite(amountUsdc) || amountUsdc <= 0) {
24600
- throw new Error(`Amount must be a positive number (got "${amountArg}").`);
24601
- }
24602
- const seller = validateAddress(sellerArg);
24603
- const specHash = await resolveCommitment(opts.spec);
24604
- const deliverByMs = Date.now() + parseDuration(opts.deadline);
24605
- const reviewWindowMs = opts.review ? parseDuration(opts.review) : DEFAULT_REVIEW_WINDOW_MS;
24606
- const rejectSplitBps = Number.parseInt(opts.split, 10);
24607
24668
  const base = opts.api ?? DEFAULT_API_BASE5;
24669
+ let amountUsdc;
24670
+ let seller;
24671
+ let specHash;
24672
+ let deliverByMs;
24673
+ let reviewWindowMs;
24674
+ let rejectSplitBps;
24675
+ let offeringSlug;
24676
+ if (opts.offering || opts.agent) {
24677
+ if (!(opts.offering && opts.agent)) {
24678
+ throw new Error("--agent and --offering go together.");
24679
+ }
24680
+ if (amountArg || sellerArg) {
24681
+ throw new Error(
24682
+ "Omit the amount/seller arguments when buying an offering \u2014 the listing sets the price and the seller."
24683
+ );
24684
+ }
24685
+ const sellerAgent = validateAddress(opts.agent);
24686
+ const offering = await fetchOffering(base, sellerAgent, opts.offering);
24687
+ offeringSlug = offering.slug;
24688
+ let requirements = null;
24689
+ if (opts.requirements) {
24690
+ let text = opts.requirements;
24691
+ try {
24692
+ text = await readFile3(opts.requirements, "utf8");
24693
+ } catch {
24694
+ }
24695
+ try {
24696
+ requirements = JSON.parse(text);
24697
+ } catch {
24698
+ requirements = text.trim();
24699
+ }
24700
+ }
24701
+ if (offering.requirements != null && requirements == null) {
24702
+ const want = typeof offering.requirements === "string" ? offering.requirements : `JSON matching: ${JSON.stringify(offering.requirements)}`;
24703
+ throw new Error(
24704
+ `This offering needs --requirements. The seller asks for: ${want}`
24705
+ );
24706
+ }
24707
+ if (offering.requirements != null && typeof offering.requirements === "object" && (typeof requirements !== "object" || requirements === null)) {
24708
+ throw new Error(
24709
+ `This offering expects JSON requirements matching: ${JSON.stringify(offering.requirements)}`
24710
+ );
24711
+ }
24712
+ const buyer = (await withAgent({ keyPath: opts.key })).address();
24713
+ const spec = JSON.stringify({
24714
+ type: "t2-acp-job-spec@1",
24715
+ offering: {
24716
+ agent: offering.agent,
24717
+ slug: offering.slug,
24718
+ name: offering.name,
24719
+ priceUsdc: offering.priceUsdc,
24720
+ deliverable: offering.deliverable
24721
+ },
24722
+ requirements,
24723
+ buyer,
24724
+ createdAtMs: Date.now()
24725
+ });
24726
+ specHash = `0x${await putJobSpec(base, spec)}`;
24727
+ amountUsdc = offering.priceUsdc;
24728
+ seller = offering.agent;
24729
+ deliverByMs = Date.now() + offering.slaMinutes * 6e4;
24730
+ reviewWindowMs = offering.reviewWindowMinutes * 6e4;
24731
+ rejectSplitBps = offering.rejectSplitBps;
24732
+ } else {
24733
+ if (!(amountArg && sellerArg)) {
24734
+ throw new Error(
24735
+ "Provide <amount> <seller> (direct job) or --agent + --offering (buy a listing)."
24736
+ );
24737
+ }
24738
+ if (!opts.spec) {
24739
+ throw new Error("--spec is required for a direct job.");
24740
+ }
24741
+ amountUsdc = Number.parseFloat(amountArg);
24742
+ if (!Number.isFinite(amountUsdc) || amountUsdc <= 0) {
24743
+ throw new Error(`Amount must be a positive number (got "${amountArg}").`);
24744
+ }
24745
+ seller = validateAddress(sellerArg);
24746
+ specHash = await resolveCommitment(opts.spec);
24747
+ deliverByMs = Date.now() + parseDuration(opts.deadline);
24748
+ reviewWindowMs = opts.review ? parseDuration(opts.review) : DEFAULT_REVIEW_WINDOW_MS;
24749
+ rejectSplitBps = Number.parseInt(opts.split, 10);
24750
+ }
24608
24751
  const { address, digest } = await sponsoredJobVerb({
24609
24752
  base,
24610
24753
  keyPath: opts.key,
@@ -24627,11 +24770,11 @@ Typical flow:
24627
24770
  }
24628
24771
  }
24629
24772
  if (isJsonMode()) {
24630
- printJson({ jobId, digest, buyer: address, seller, amountUsdc, specHash, deliverByMs, reviewWindowMs, rejectSplitBps });
24773
+ printJson({ jobId, digest, buyer: address, seller, amountUsdc, specHash, deliverByMs, reviewWindowMs, rejectSplitBps, ...offeringSlug ? { offering: offeringSlug } : {} });
24631
24774
  return;
24632
24775
  }
24633
24776
  printBlank();
24634
- printSuccess(`Escrowed $${amountUsdc.toFixed(2)} USDC \u2192 job for ${truncateAddress(seller)}`);
24777
+ printSuccess(`Escrowed $${amountUsdc.toFixed(2)} USDC \u2192 job for ${truncateAddress(seller)}${offeringSlug ? ` (offering: ${offeringSlug})` : ""}`);
24635
24778
  if (jobId) printKeyValue("Job", jobId);
24636
24779
  printKeyValue("Spec hash", specHash);
24637
24780
  printKeyValue("Deliver by", new Date(deliverByMs).toISOString());
@@ -24737,6 +24880,30 @@ Typical flow:
24737
24880
  }
24738
24881
  });
24739
24882
  }
24883
+ group.command("spec").argument("<jobId>", "The Job object id (0x\u2026)").description("Fetch the buyer's job spec / requirements by the on-chain hash (seller)").option("--api <url>", `API base URL (default ${DEFAULT_API_BASE5})`).action(async (jobId, opts) => {
24884
+ try {
24885
+ const client = getSuiClient();
24886
+ const job = await getJob(client, jobId);
24887
+ const content = await getJobSpec(opts.api ?? DEFAULT_API_BASE5, job.specHash);
24888
+ if (isJsonMode()) {
24889
+ let parsed = content;
24890
+ try {
24891
+ parsed = JSON.parse(content);
24892
+ } catch {
24893
+ }
24894
+ printJson({ jobId, specHash: job.specHash, spec: parsed });
24895
+ return;
24896
+ }
24897
+ printBlank();
24898
+ printKeyValue("Job", jobId);
24899
+ printKeyValue("Spec hash", `${job.specHash} ${import_picocolors15.default.green("(content verified)")}`);
24900
+ printBlank();
24901
+ printLine(content);
24902
+ printBlank();
24903
+ } catch (error) {
24904
+ handleError(error);
24905
+ }
24906
+ });
24740
24907
  group.command("watch").argument("<jobId>", "The Job object id (0x\u2026)").description("Poll the job \u2014 state, timers, and what YOU can do right now").option("--interval <seconds>", "Poll interval", "15").option("--once", "Print the current state and exit").option("--key <path>", "Custom wallet path (default ~/.t2000/wallet.key)").action(async (jobId, opts) => {
24741
24908
  try {
24742
24909
  const agent = await withAgent({ keyPath: opts.key });
@@ -24767,6 +24934,226 @@ Typical flow:
24767
24934
  });
24768
24935
  }
24769
24936
 
24937
+ // src/commands/offering.ts
24938
+ var import_picocolors16 = __toESM(require_picocolors(), 1);
24939
+ import { createHash as createHash3 } from "crypto";
24940
+ import { readFile as readFile4 } from "fs/promises";
24941
+ var DEFAULT_API_BASE6 = process.env.T2000_API_URL ?? "https://api.t2000.ai/v1";
24942
+ async function signedOfferingAction(opts) {
24943
+ const agent = await withAgent({ keyPath: opts.keyPath });
24944
+ const address = agent.address();
24945
+ const challenge = await fetchJson3(`${opts.base}/agent/challenge`, {
24946
+ method: "POST",
24947
+ body: { address }
24948
+ });
24949
+ const nonce = challenge.nonce;
24950
+ if (!nonce) {
24951
+ throw new Error("Failed to get a challenge nonce.");
24952
+ }
24953
+ const payloadHash = createHash3("sha256").update(JSON.stringify(opts.payload), "utf8").digest("hex");
24954
+ const message = new TextEncoder().encode(
24955
+ `t2000-agent-offering:${nonce}:${payloadHash}`
24956
+ );
24957
+ const { signature } = await agent.keypair.signPersonalMessage(message);
24958
+ const response = await fetchJson3(`${opts.base}/agent/offering`, {
24959
+ method: "POST",
24960
+ body: {
24961
+ address,
24962
+ nonce,
24963
+ signature,
24964
+ action: opts.action,
24965
+ payload: opts.payload
24966
+ }
24967
+ });
24968
+ return { address, response };
24969
+ }
24970
+ async function resolveRequirements(input) {
24971
+ let text = input;
24972
+ try {
24973
+ text = await readFile4(input, "utf8");
24974
+ } catch {
24975
+ }
24976
+ try {
24977
+ const parsed = JSON.parse(text);
24978
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
24979
+ return parsed;
24980
+ }
24981
+ } catch {
24982
+ }
24983
+ return text.trim();
24984
+ }
24985
+ function slugify(name) {
24986
+ return name.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replaceAll(/^-+|-+$/g, "").slice(0, 48);
24987
+ }
24988
+ function formatSla(minutes) {
24989
+ if (minutes % 1440 === 0) return `${minutes / 1440}d`;
24990
+ if (minutes % 60 === 0) return `${minutes / 60}h`;
24991
+ return `${minutes}m`;
24992
+ }
24993
+ function printOffering(o) {
24994
+ const flag = o.retired ? import_picocolors16.default.dim(" (retired)") : "";
24995
+ printLine(`${import_picocolors16.default.bold(o.name)} ${import_picocolors16.default.dim(`\xB7 ${o.slug}`)}${flag}`);
24996
+ printKeyValue("Price", `$${o.priceUsdc.toFixed(2)} USDC`);
24997
+ printKeyValue("Delivery", `within ${formatSla(o.slaMinutes)}`);
24998
+ printKeyValue(
24999
+ "Seller",
25000
+ `${o.agentName ?? "unnamed"} ${import_picocolors16.default.dim(truncateAddress(o.agent))}`
25001
+ );
25002
+ printKeyValue("You get", o.deliverable);
25003
+ if (o.requirements != null) {
25004
+ printKeyValue(
25005
+ "You provide",
25006
+ typeof o.requirements === "string" ? o.requirements : JSON.stringify(o.requirements)
25007
+ );
25008
+ }
25009
+ printKeyValue(
25010
+ "Buy",
25011
+ `t2 job create --agent ${o.agent} --offering ${o.slug}`
25012
+ );
25013
+ }
25014
+ function registerOffering(program3) {
25015
+ const group = program3.command("offering").description(
25016
+ "Sell deliverable work \u2014 list what you do, at what price, on what SLA (t2 ACP)"
25017
+ ).addHelpText(
25018
+ "after",
25019
+ `
25020
+ An offering needs NO server and NO endpoint: buyers fund an on-chain escrow
25021
+ Job against it, you deliver with \`t2 job deliver\`, the escrow settles. Your
25022
+ catalog lives on your Agent ID and shows on agents.t2000.ai.
25023
+
25024
+ Examples:
25025
+ $ t2 offering create --name "Sui market report" --price 5 --sla 24h \\
25026
+ --description "Daily research report on any Sui token" \\
25027
+ --deliverable "PDF report, 2+ pages, sources cited" \\
25028
+ --requirements "Token symbol or coin type to analyze"
25029
+ $ t2 offering list
25030
+ $ t2 offering retire sui-market-report
25031
+ `
25032
+ );
25033
+ group.command("create").description("List an offering under your Agent ID (re-run to update it)").requiredOption("--name <name>", "Offering name (max 80 chars)").requiredOption("--price <usdc>", "Fixed price in USDC (0.01\u201350)").requiredOption("--sla <duration>", "Delivery SLA \u2014 e.g. 30m, 24h, 7d").requiredOption("--description <text>", "What this offering is (max 2000 chars)").requiredOption("--deliverable <text>", "What the buyer receives (max 1000 chars)").option("--slug <slug>", "Machine name (default: derived from --name)").option(
25034
+ "--requirements <file-or-json-or-text>",
25035
+ "What the buyer must provide \u2014 free text or a JSON schema (file path ok)"
25036
+ ).option("--review <duration>", "Buyer's accept/reject window after delivery", "24h").option("--split <bps>", "Buyer's share in bps if they reject (0\u201310000)", "8000").option("--key <path>", "Custom wallet path (default ~/.t2000/wallet.key)").option("--api <url>", `API base URL (default ${DEFAULT_API_BASE6})`).action(
25037
+ async (opts) => {
25038
+ try {
25039
+ const priceUsdc = Number.parseFloat(opts.price);
25040
+ if (!Number.isFinite(priceUsdc) || priceUsdc <= 0) {
25041
+ throw new Error(`--price must be a positive number (got "${opts.price}").`);
25042
+ }
25043
+ const slaMinutes = Math.round(parseDuration(opts.sla) / 6e4);
25044
+ const reviewWindowMinutes = Math.round(parseDuration(opts.review) / 6e4);
25045
+ const rejectSplitBps = Number.parseInt(opts.split, 10);
25046
+ const slug = (opts.slug ?? slugify(opts.name)).trim().toLowerCase();
25047
+ const requirements = opts.requirements ? await resolveRequirements(opts.requirements) : null;
25048
+ const payload = {
25049
+ slug,
25050
+ name: opts.name.trim(),
25051
+ description: opts.description.trim(),
25052
+ priceUsdc,
25053
+ slaMinutes,
25054
+ reviewWindowMinutes,
25055
+ rejectSplitBps,
25056
+ requirements,
25057
+ deliverable: opts.deliverable.trim()
25058
+ };
25059
+ const base = opts.api ?? DEFAULT_API_BASE6;
25060
+ const { address } = await signedOfferingAction({
25061
+ base,
25062
+ keyPath: opts.key,
25063
+ action: "upsert",
25064
+ payload
25065
+ });
25066
+ if (isJsonMode()) {
25067
+ printJson({ address, ...payload });
25068
+ return;
25069
+ }
25070
+ printBlank();
25071
+ printSuccess(`"${payload.name}" is listed \u2014 $${priceUsdc.toFixed(2)} USDC, delivery within ${formatSla(slaMinutes)}`);
25072
+ printKeyValue("Slug", slug);
25073
+ printKeyValue("Storefront", `https://agents.t2000.ai/${address}`);
25074
+ printKeyValue("Buyers run", `t2 job create --agent ${address} --offering ${slug}`);
25075
+ printBlank();
25076
+ printInfo("Watch for incoming jobs with: t2 job watch <jobId> (buyers hand you the job id)");
25077
+ printBlank();
25078
+ } catch (error) {
25079
+ handleError(error);
25080
+ }
25081
+ }
25082
+ );
25083
+ group.command("list").argument("[agent]", "Agent address (default: this wallet's)").description("An agent's offerings \u2014 yours by default, retired included").option("--key <path>", "Custom wallet path (default ~/.t2000/wallet.key)").option("--api <url>", `API base URL (default ${DEFAULT_API_BASE6})`).action(async (agentArg, opts) => {
25084
+ try {
25085
+ const base = opts.api ?? DEFAULT_API_BASE6;
25086
+ const agent = agentArg ? validateAddress(agentArg) : (await withAgent({ keyPath: opts.key })).address();
25087
+ const json = await fetchJson3(
25088
+ `${base}/offerings?agent=${encodeURIComponent(agent)}`
25089
+ );
25090
+ const rows = json.offerings ?? [];
25091
+ if (isJsonMode()) {
25092
+ printJson({ agent, offerings: rows });
25093
+ return;
25094
+ }
25095
+ printBlank();
25096
+ if (rows.length === 0) {
25097
+ printInfo(`No offerings for ${truncateAddress(agent)} \u2014 list one with: t2 offering create`);
25098
+ printBlank();
25099
+ return;
25100
+ }
25101
+ for (const o of rows) {
25102
+ printOffering(o);
25103
+ printBlank();
25104
+ }
25105
+ } catch (error) {
25106
+ handleError(error);
25107
+ }
25108
+ });
25109
+ group.command("retire").argument("<slug>", "The offering slug to retire").description("Take an offering off the board (funded jobs still settle on-chain)").option("--key <path>", "Custom wallet path (default ~/.t2000/wallet.key)").option("--api <url>", `API base URL (default ${DEFAULT_API_BASE6})`).action(async (slug, opts) => {
25110
+ try {
25111
+ const base = opts.api ?? DEFAULT_API_BASE6;
25112
+ const { address } = await signedOfferingAction({
25113
+ base,
25114
+ keyPath: opts.key,
25115
+ action: "retire",
25116
+ payload: { slug: slug.trim().toLowerCase() }
25117
+ });
25118
+ if (isJsonMode()) {
25119
+ printJson({ address, retired: slug.trim().toLowerCase() });
25120
+ return;
25121
+ }
25122
+ printBlank();
25123
+ printSuccess(`Offering "${slug}" retired. Re-run t2 offering create with the same slug to relist.`);
25124
+ printBlank();
25125
+ } catch (error) {
25126
+ handleError(error);
25127
+ }
25128
+ });
25129
+ }
25130
+ function registerBrowse(program3) {
25131
+ program3.command("browse").argument("[query]", "What you need \u2014 free-text search (empty = everything)").description("Browse offerings across every agent \u2014 find work to buy (t2 ACP)").option("--api <url>", `API base URL (default ${DEFAULT_API_BASE6})`).action(async (query, opts) => {
25132
+ try {
25133
+ const base = opts.api ?? DEFAULT_API_BASE6;
25134
+ const params = query ? `?q=${encodeURIComponent(query)}` : "";
25135
+ const json = await fetchJson3(`${base}/offerings${params}`);
25136
+ const rows = json.offerings ?? [];
25137
+ if (isJsonMode()) {
25138
+ printJson({ query: query ?? null, total: json.total ?? rows.length, offerings: rows });
25139
+ return;
25140
+ }
25141
+ printBlank();
25142
+ if (rows.length === 0) {
25143
+ printInfo(query ? `No offerings match "${query}".` : "No offerings listed yet.");
25144
+ printBlank();
25145
+ return;
25146
+ }
25147
+ for (const o of rows) {
25148
+ printOffering(o);
25149
+ printBlank();
25150
+ }
25151
+ } catch (error) {
25152
+ handleError(error);
25153
+ }
25154
+ });
25155
+ }
25156
+
24770
25157
  // src/program.ts
24771
25158
  var require3 = createRequire2(import.meta.url);
24772
25159
  var { version: CLI_VERSION2 } = require3("../package.json");
@@ -24790,6 +25177,8 @@ Examples:
24790
25177
  $ t2 services search "image" Discover x402 services in the gateway catalog
24791
25178
  $ t2 check <url> Validate your paid API against the listing gates (add --list to sell it)
24792
25179
  $ t2 job create 5 0xSELLER --spec brief.md --deadline 24h Escrow USDC for deliverable work (A2A)
25180
+ $ t2 offering create --name "Report" --price 5 --sla 24h ... Sell deliverable work (no server needed)
25181
+ $ t2 browse "market report" Find offerings to buy across every agent
24793
25182
  $ t2 agents Look up the agent directory (agents.t2000.ai)
24794
25183
  $ t2 limit set --daily 100 Change the daily spend cap (default $100/day)
24795
25184
  $ t2 mcp install Connect Claude / Cursor / Windsurf
@@ -24814,6 +25203,8 @@ Examples:
24814
25203
  registerAgents(program3);
24815
25204
  registerCheck(program3);
24816
25205
  registerJob(program3);
25206
+ registerOffering(program3);
25207
+ registerBrowse(program3);
24817
25208
  return program3;
24818
25209
  }
24819
25210