@t2000/cli 9.9.1 → 9.11.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.
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-LRBJUCHL.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);
@@ -24560,6 +24624,29 @@ function printJob(job, me) {
24560
24624
  if (job.deliveryHash) printKeyValue("Delivery hash", job.deliveryHash);
24561
24625
  printKeyValue("Reject split", `${job.rejectSplitBps / 100}% buyer / ${(1e4 - job.rejectSplitBps) / 100}% seller`);
24562
24626
  }
24627
+ async function fetchSellerJobs(base, seller) {
24628
+ const json = await fetchJson3(`${base}/jobs?seller=${encodeURIComponent(seller)}&limit=100`);
24629
+ return json.jobs ?? [];
24630
+ }
24631
+ var TERMINAL_STATES = /* @__PURE__ */ new Set(["released", "rejected", "refunded"]);
24632
+ function inboxHint(job) {
24633
+ if (job.state === "funded") {
24634
+ return `t2 job spec ${truncateAddress(job.jobId)} \u2192 do the work \u2192 t2 job deliver ${truncateAddress(job.jobId)} <file>`;
24635
+ }
24636
+ if (job.state === "delivered") {
24637
+ return `waiting on the buyer's review \u2014 anyone can \`t2 job release\` once it lapses`;
24638
+ }
24639
+ return "";
24640
+ }
24641
+ function printInboxRow(job) {
24642
+ const deadline = job.state === "funded" ? ` \xB7 deliver by ${new Date(job.deliverByMs).toISOString()}` : "";
24643
+ printLine(
24644
+ ` ${stateColor(job.state)} $${job.amountUsdc.toFixed(2)} USDC \xB7 from ${truncateAddress(job.buyer)}${deadline}`
24645
+ );
24646
+ printLine(` ${import_picocolors15.default.dim(job.jobId)}`);
24647
+ const hint = inboxHint(job);
24648
+ if (hint) printLine(` ${import_picocolors15.default.dim("\u2192")} ${hint}`);
24649
+ }
24563
24650
  async function sponsoredJobVerb(opts) {
24564
24651
  const agent = await withAgent({ keyPath: opts.keyPath });
24565
24652
  const address = agent.address();
@@ -24590,21 +24677,101 @@ Typical flow:
24590
24677
  seller $ t2 job deliver 0xJOB report.pdf
24591
24678
  buyer $ t2 job release 0xJOB (or: t2 job reject 0xJOB)
24592
24679
  either $ t2 job watch 0xJOB
24680
+ seller $ t2 job watch --mine (the provider inbox \u2014 all your jobs)
24681
+
24682
+ Buying an OFFERING (t2 ACP) \u2014 price + terms come from the listing:
24683
+ buyer $ t2 browse "market report"
24684
+ buyer $ t2 job create --agent 0xSELLER --offering sui-market-report \\
24685
+ --requirements '{"token":"DEEP"}'
24686
+ seller $ t2 job spec 0xJOB (read the buyer's requirements)
24593
24687
  `
24594
24688
  );
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(
24689
+ 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
24690
  async (amountArg, sellerArg, opts) => {
24597
24691
  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
24692
  const base = opts.api ?? DEFAULT_API_BASE5;
24693
+ let amountUsdc;
24694
+ let seller;
24695
+ let specHash;
24696
+ let deliverByMs;
24697
+ let reviewWindowMs;
24698
+ let rejectSplitBps;
24699
+ let offeringSlug;
24700
+ if (opts.offering || opts.agent) {
24701
+ if (!(opts.offering && opts.agent)) {
24702
+ throw new Error("--agent and --offering go together.");
24703
+ }
24704
+ if (amountArg || sellerArg) {
24705
+ throw new Error(
24706
+ "Omit the amount/seller arguments when buying an offering \u2014 the listing sets the price and the seller."
24707
+ );
24708
+ }
24709
+ const sellerAgent = validateAddress(opts.agent);
24710
+ const offering = await fetchOffering(base, sellerAgent, opts.offering);
24711
+ offeringSlug = offering.slug;
24712
+ let requirements = null;
24713
+ if (opts.requirements) {
24714
+ let text = opts.requirements;
24715
+ try {
24716
+ text = await readFile3(opts.requirements, "utf8");
24717
+ } catch {
24718
+ }
24719
+ try {
24720
+ requirements = JSON.parse(text);
24721
+ } catch {
24722
+ requirements = text.trim();
24723
+ }
24724
+ }
24725
+ if (offering.requirements != null && requirements == null) {
24726
+ const want = typeof offering.requirements === "string" ? offering.requirements : `JSON matching: ${JSON.stringify(offering.requirements)}`;
24727
+ throw new Error(
24728
+ `This offering needs --requirements. The seller asks for: ${want}`
24729
+ );
24730
+ }
24731
+ if (offering.requirements != null && typeof offering.requirements === "object" && (typeof requirements !== "object" || requirements === null)) {
24732
+ throw new Error(
24733
+ `This offering expects JSON requirements matching: ${JSON.stringify(offering.requirements)}`
24734
+ );
24735
+ }
24736
+ const buyer = (await withAgent({ keyPath: opts.key })).address();
24737
+ const spec = JSON.stringify({
24738
+ type: "t2-acp-job-spec@1",
24739
+ offering: {
24740
+ agent: offering.agent,
24741
+ slug: offering.slug,
24742
+ name: offering.name,
24743
+ priceUsdc: offering.priceUsdc,
24744
+ deliverable: offering.deliverable
24745
+ },
24746
+ requirements,
24747
+ buyer,
24748
+ createdAtMs: Date.now()
24749
+ });
24750
+ specHash = `0x${await putJobSpec(base, spec)}`;
24751
+ amountUsdc = offering.priceUsdc;
24752
+ seller = offering.agent;
24753
+ deliverByMs = Date.now() + offering.slaMinutes * 6e4;
24754
+ reviewWindowMs = offering.reviewWindowMinutes * 6e4;
24755
+ rejectSplitBps = offering.rejectSplitBps;
24756
+ } else {
24757
+ if (!(amountArg && sellerArg)) {
24758
+ throw new Error(
24759
+ "Provide <amount> <seller> (direct job) or --agent + --offering (buy a listing)."
24760
+ );
24761
+ }
24762
+ if (!opts.spec) {
24763
+ throw new Error("--spec is required for a direct job.");
24764
+ }
24765
+ amountUsdc = Number.parseFloat(amountArg);
24766
+ if (!Number.isFinite(amountUsdc) || amountUsdc <= 0) {
24767
+ throw new Error(`Amount must be a positive number (got "${amountArg}").`);
24768
+ }
24769
+ seller = validateAddress(sellerArg);
24770
+ specHash = await resolveCommitment(opts.spec);
24771
+ deliverByMs = Date.now() + parseDuration(opts.deadline);
24772
+ reviewWindowMs = opts.review ? parseDuration(opts.review) : DEFAULT_REVIEW_WINDOW_MS;
24773
+ rejectSplitBps = Number.parseInt(opts.split, 10);
24774
+ }
24608
24775
  const { address, digest } = await sponsoredJobVerb({
24609
24776
  base,
24610
24777
  keyPath: opts.key,
@@ -24627,11 +24794,11 @@ Typical flow:
24627
24794
  }
24628
24795
  }
24629
24796
  if (isJsonMode()) {
24630
- printJson({ jobId, digest, buyer: address, seller, amountUsdc, specHash, deliverByMs, reviewWindowMs, rejectSplitBps });
24797
+ printJson({ jobId, digest, buyer: address, seller, amountUsdc, specHash, deliverByMs, reviewWindowMs, rejectSplitBps, ...offeringSlug ? { offering: offeringSlug } : {} });
24631
24798
  return;
24632
24799
  }
24633
24800
  printBlank();
24634
- printSuccess(`Escrowed $${amountUsdc.toFixed(2)} USDC \u2192 job for ${truncateAddress(seller)}`);
24801
+ printSuccess(`Escrowed $${amountUsdc.toFixed(2)} USDC \u2192 job for ${truncateAddress(seller)}${offeringSlug ? ` (offering: ${offeringSlug})` : ""}`);
24635
24802
  if (jobId) printKeyValue("Job", jobId);
24636
24803
  printKeyValue("Spec hash", specHash);
24637
24804
  printKeyValue("Deliver by", new Date(deliverByMs).toISOString());
@@ -24737,12 +24904,86 @@ Typical flow:
24737
24904
  }
24738
24905
  });
24739
24906
  }
24740
- 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) => {
24907
+ 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) => {
24908
+ try {
24909
+ const client = getSuiClient();
24910
+ const job = await getJob(client, jobId);
24911
+ const content = await getJobSpec(opts.api ?? DEFAULT_API_BASE5, job.specHash);
24912
+ if (isJsonMode()) {
24913
+ let parsed = content;
24914
+ try {
24915
+ parsed = JSON.parse(content);
24916
+ } catch {
24917
+ }
24918
+ printJson({ jobId, specHash: job.specHash, spec: parsed });
24919
+ return;
24920
+ }
24921
+ printBlank();
24922
+ printKeyValue("Job", jobId);
24923
+ printKeyValue("Spec hash", `${job.specHash} ${import_picocolors15.default.green("(content verified)")}`);
24924
+ printBlank();
24925
+ printLine(content);
24926
+ printBlank();
24927
+ } catch (error) {
24928
+ handleError(error);
24929
+ }
24930
+ });
24931
+ 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_BASE5})`).action(async (jobId, opts) => {
24741
24932
  try {
24742
24933
  const agent = await withAgent({ keyPath: opts.key });
24743
24934
  const me = agent.address();
24744
- const client = getSuiClient();
24745
24935
  const intervalMs = Math.max(5, Number.parseInt(opts.interval, 10) || 15) * 1e3;
24936
+ if (opts.mine) {
24937
+ const base = opts.api ?? DEFAULT_API_BASE5;
24938
+ const seen = /* @__PURE__ */ new Map();
24939
+ const jobs = await fetchSellerJobs(base, me);
24940
+ if (isJsonMode()) {
24941
+ printJson({ seller: me, jobs });
24942
+ return;
24943
+ }
24944
+ const open = jobs.filter((j) => !TERMINAL_STATES.has(j.state));
24945
+ printBlank();
24946
+ printInfo(`Provider inbox for ${truncateAddress(me)} \u2014 ${jobs.length} job(s), ${open.length} open.`);
24947
+ printBlank();
24948
+ for (const job of open) {
24949
+ printInboxRow(job);
24950
+ printBlank();
24951
+ }
24952
+ if (open.length === 0) {
24953
+ printInfo("No open jobs. New hires appear here the moment the escrow funds.");
24954
+ printBlank();
24955
+ }
24956
+ for (const job of jobs) seen.set(job.jobId, job.state);
24957
+ if (opts.once) return;
24958
+ for (; ; ) {
24959
+ await new Promise((r) => setTimeout(r, intervalMs));
24960
+ let latest;
24961
+ try {
24962
+ latest = await fetchSellerJobs(base, me);
24963
+ } catch {
24964
+ continue;
24965
+ }
24966
+ for (const job of latest) {
24967
+ const prev = seen.get(job.jobId);
24968
+ if (prev === job.state) continue;
24969
+ seen.set(job.jobId, job.state);
24970
+ printBlank();
24971
+ if (prev === void 0 && job.state === "funded") {
24972
+ printSuccess(`New job \u2014 $${job.amountUsdc.toFixed(2)} USDC escrowed for you.`);
24973
+ } else {
24974
+ printInfo(`Job ${truncateAddress(job.jobId)}: ${prev ?? "new"} \u2192 ${job.state}`);
24975
+ }
24976
+ printInboxRow(job);
24977
+ printBlank();
24978
+ }
24979
+ }
24980
+ }
24981
+ if (!jobId) {
24982
+ printError("Provide a job id \u2014 or use --mine for the provider inbox.");
24983
+ process.exitCode = 1;
24984
+ return;
24985
+ }
24986
+ const client = getSuiClient();
24746
24987
  for (; ; ) {
24747
24988
  const job = await getJob(client, jobId);
24748
24989
  const actions = jobActionsFor(job, me);
@@ -24767,6 +25008,226 @@ Typical flow:
24767
25008
  });
24768
25009
  }
24769
25010
 
25011
+ // src/commands/offering.ts
25012
+ var import_picocolors16 = __toESM(require_picocolors(), 1);
25013
+ import { createHash as createHash3 } from "crypto";
25014
+ import { readFile as readFile4 } from "fs/promises";
25015
+ var DEFAULT_API_BASE6 = process.env.T2000_API_URL ?? "https://api.t2000.ai/v1";
25016
+ async function signedOfferingAction(opts) {
25017
+ const agent = await withAgent({ keyPath: opts.keyPath });
25018
+ const address = agent.address();
25019
+ const challenge = await fetchJson3(`${opts.base}/agent/challenge`, {
25020
+ method: "POST",
25021
+ body: { address }
25022
+ });
25023
+ const nonce = challenge.nonce;
25024
+ if (!nonce) {
25025
+ throw new Error("Failed to get a challenge nonce.");
25026
+ }
25027
+ const payloadHash = createHash3("sha256").update(JSON.stringify(opts.payload), "utf8").digest("hex");
25028
+ const message = new TextEncoder().encode(
25029
+ `t2000-agent-offering:${nonce}:${payloadHash}`
25030
+ );
25031
+ const { signature } = await agent.keypair.signPersonalMessage(message);
25032
+ const response = await fetchJson3(`${opts.base}/agent/offering`, {
25033
+ method: "POST",
25034
+ body: {
25035
+ address,
25036
+ nonce,
25037
+ signature,
25038
+ action: opts.action,
25039
+ payload: opts.payload
25040
+ }
25041
+ });
25042
+ return { address, response };
25043
+ }
25044
+ async function resolveRequirements(input) {
25045
+ let text = input;
25046
+ try {
25047
+ text = await readFile4(input, "utf8");
25048
+ } catch {
25049
+ }
25050
+ try {
25051
+ const parsed = JSON.parse(text);
25052
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
25053
+ return parsed;
25054
+ }
25055
+ } catch {
25056
+ }
25057
+ return text.trim();
25058
+ }
25059
+ function slugify(name) {
25060
+ return name.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replaceAll(/^-+|-+$/g, "").slice(0, 48);
25061
+ }
25062
+ function formatSla(minutes) {
25063
+ if (minutes % 1440 === 0) return `${minutes / 1440}d`;
25064
+ if (minutes % 60 === 0) return `${minutes / 60}h`;
25065
+ return `${minutes}m`;
25066
+ }
25067
+ function printOffering(o) {
25068
+ const flag = o.retired ? import_picocolors16.default.dim(" (retired)") : "";
25069
+ printLine(`${import_picocolors16.default.bold(o.name)} ${import_picocolors16.default.dim(`\xB7 ${o.slug}`)}${flag}`);
25070
+ printKeyValue("Price", `$${o.priceUsdc.toFixed(2)} USDC`);
25071
+ printKeyValue("Delivery", `within ${formatSla(o.slaMinutes)}`);
25072
+ printKeyValue(
25073
+ "Seller",
25074
+ `${o.agentName ?? "unnamed"} ${import_picocolors16.default.dim(truncateAddress(o.agent))}`
25075
+ );
25076
+ printKeyValue("You get", o.deliverable);
25077
+ if (o.requirements != null) {
25078
+ printKeyValue(
25079
+ "You provide",
25080
+ typeof o.requirements === "string" ? o.requirements : JSON.stringify(o.requirements)
25081
+ );
25082
+ }
25083
+ printKeyValue(
25084
+ "Buy",
25085
+ `t2 job create --agent ${o.agent} --offering ${o.slug}`
25086
+ );
25087
+ }
25088
+ function registerOffering(program3) {
25089
+ const group = program3.command("offering").description(
25090
+ "Sell deliverable work \u2014 list what you do, at what price, on what SLA (t2 ACP)"
25091
+ ).addHelpText(
25092
+ "after",
25093
+ `
25094
+ An offering needs NO server and NO endpoint: buyers fund an on-chain escrow
25095
+ Job against it, you deliver with \`t2 job deliver\`, the escrow settles. Your
25096
+ catalog lives on your Agent ID and shows on agents.t2000.ai.
25097
+
25098
+ Examples:
25099
+ $ t2 offering create --name "Sui market report" --price 5 --sla 24h \\
25100
+ --description "Daily research report on any Sui token" \\
25101
+ --deliverable "PDF report, 2+ pages, sources cited" \\
25102
+ --requirements "Token symbol or coin type to analyze"
25103
+ $ t2 offering list
25104
+ $ t2 offering retire sui-market-report
25105
+ `
25106
+ );
25107
+ 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(
25108
+ "--requirements <file-or-json-or-text>",
25109
+ "What the buyer must provide \u2014 free text or a JSON schema (file path ok)"
25110
+ ).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(
25111
+ async (opts) => {
25112
+ try {
25113
+ const priceUsdc = Number.parseFloat(opts.price);
25114
+ if (!Number.isFinite(priceUsdc) || priceUsdc <= 0) {
25115
+ throw new Error(`--price must be a positive number (got "${opts.price}").`);
25116
+ }
25117
+ const slaMinutes = Math.round(parseDuration(opts.sla) / 6e4);
25118
+ const reviewWindowMinutes = Math.round(parseDuration(opts.review) / 6e4);
25119
+ const rejectSplitBps = Number.parseInt(opts.split, 10);
25120
+ const slug = (opts.slug ?? slugify(opts.name)).trim().toLowerCase();
25121
+ const requirements = opts.requirements ? await resolveRequirements(opts.requirements) : null;
25122
+ const payload = {
25123
+ slug,
25124
+ name: opts.name.trim(),
25125
+ description: opts.description.trim(),
25126
+ priceUsdc,
25127
+ slaMinutes,
25128
+ reviewWindowMinutes,
25129
+ rejectSplitBps,
25130
+ requirements,
25131
+ deliverable: opts.deliverable.trim()
25132
+ };
25133
+ const base = opts.api ?? DEFAULT_API_BASE6;
25134
+ const { address } = await signedOfferingAction({
25135
+ base,
25136
+ keyPath: opts.key,
25137
+ action: "upsert",
25138
+ payload
25139
+ });
25140
+ if (isJsonMode()) {
25141
+ printJson({ address, ...payload });
25142
+ return;
25143
+ }
25144
+ printBlank();
25145
+ printSuccess(`"${payload.name}" is listed \u2014 $${priceUsdc.toFixed(2)} USDC, delivery within ${formatSla(slaMinutes)}`);
25146
+ printKeyValue("Slug", slug);
25147
+ printKeyValue("Storefront", `https://agents.t2000.ai/${address}`);
25148
+ printKeyValue("Buyers run", `t2 job create --agent ${address} --offering ${slug}`);
25149
+ printBlank();
25150
+ printInfo("Watch for incoming jobs with: t2 job watch <jobId> (buyers hand you the job id)");
25151
+ printBlank();
25152
+ } catch (error) {
25153
+ handleError(error);
25154
+ }
25155
+ }
25156
+ );
25157
+ 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) => {
25158
+ try {
25159
+ const base = opts.api ?? DEFAULT_API_BASE6;
25160
+ const agent = agentArg ? validateAddress(agentArg) : (await withAgent({ keyPath: opts.key })).address();
25161
+ const json = await fetchJson3(
25162
+ `${base}/offerings?agent=${encodeURIComponent(agent)}`
25163
+ );
25164
+ const rows = json.offerings ?? [];
25165
+ if (isJsonMode()) {
25166
+ printJson({ agent, offerings: rows });
25167
+ return;
25168
+ }
25169
+ printBlank();
25170
+ if (rows.length === 0) {
25171
+ printInfo(`No offerings for ${truncateAddress(agent)} \u2014 list one with: t2 offering create`);
25172
+ printBlank();
25173
+ return;
25174
+ }
25175
+ for (const o of rows) {
25176
+ printOffering(o);
25177
+ printBlank();
25178
+ }
25179
+ } catch (error) {
25180
+ handleError(error);
25181
+ }
25182
+ });
25183
+ 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) => {
25184
+ try {
25185
+ const base = opts.api ?? DEFAULT_API_BASE6;
25186
+ const { address } = await signedOfferingAction({
25187
+ base,
25188
+ keyPath: opts.key,
25189
+ action: "retire",
25190
+ payload: { slug: slug.trim().toLowerCase() }
25191
+ });
25192
+ if (isJsonMode()) {
25193
+ printJson({ address, retired: slug.trim().toLowerCase() });
25194
+ return;
25195
+ }
25196
+ printBlank();
25197
+ printSuccess(`Offering "${slug}" retired. Re-run t2 offering create with the same slug to relist.`);
25198
+ printBlank();
25199
+ } catch (error) {
25200
+ handleError(error);
25201
+ }
25202
+ });
25203
+ }
25204
+ function registerBrowse(program3) {
25205
+ 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) => {
25206
+ try {
25207
+ const base = opts.api ?? DEFAULT_API_BASE6;
25208
+ const params = query ? `?q=${encodeURIComponent(query)}` : "";
25209
+ const json = await fetchJson3(`${base}/offerings${params}`);
25210
+ const rows = json.offerings ?? [];
25211
+ if (isJsonMode()) {
25212
+ printJson({ query: query ?? null, total: json.total ?? rows.length, offerings: rows });
25213
+ return;
25214
+ }
25215
+ printBlank();
25216
+ if (rows.length === 0) {
25217
+ printInfo(query ? `No offerings match "${query}".` : "No offerings listed yet.");
25218
+ printBlank();
25219
+ return;
25220
+ }
25221
+ for (const o of rows) {
25222
+ printOffering(o);
25223
+ printBlank();
25224
+ }
25225
+ } catch (error) {
25226
+ handleError(error);
25227
+ }
25228
+ });
25229
+ }
25230
+
24770
25231
  // src/program.ts
24771
25232
  var require3 = createRequire2(import.meta.url);
24772
25233
  var { version: CLI_VERSION2 } = require3("../package.json");
@@ -24790,6 +25251,8 @@ Examples:
24790
25251
  $ t2 services search "image" Discover x402 services in the gateway catalog
24791
25252
  $ t2 check <url> Validate your paid API against the listing gates (add --list to sell it)
24792
25253
  $ t2 job create 5 0xSELLER --spec brief.md --deadline 24h Escrow USDC for deliverable work (A2A)
25254
+ $ t2 offering create --name "Report" --price 5 --sla 24h ... Sell deliverable work (no server needed)
25255
+ $ t2 browse "market report" Find offerings to buy across every agent
24793
25256
  $ t2 agents Look up the agent directory (agents.t2000.ai)
24794
25257
  $ t2 limit set --daily 100 Change the daily spend cap (default $100/day)
24795
25258
  $ t2 mcp install Connect Claude / Cursor / Windsurf
@@ -24814,6 +25277,8 @@ Examples:
24814
25277
  registerAgents(program3);
24815
25278
  registerCheck(program3);
24816
25279
  registerJob(program3);
25280
+ registerOffering(program3);
25281
+ registerBrowse(program3);
24817
25282
  return program3;
24818
25283
  }
24819
25284