@t2000/cli 9.10.0 → 9.12.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-FMVRVPKV.js");
23373
+ mod = await import("./dist-PAYZWJCN.js");
23374
23374
  } catch {
23375
23375
  console.error("MCP server not installed. Run:\n npm install -g @t2000/mcp");
23376
23376
  process.exit(1);
@@ -24624,6 +24624,29 @@ function printJob(job, me) {
24624
24624
  if (job.deliveryHash) printKeyValue("Delivery hash", job.deliveryHash);
24625
24625
  printKeyValue("Reject split", `${job.rejectSplitBps / 100}% buyer / ${(1e4 - job.rejectSplitBps) / 100}% seller`);
24626
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
+ }
24627
24650
  async function sponsoredJobVerb(opts) {
24628
24651
  const agent = await withAgent({ keyPath: opts.keyPath });
24629
24652
  const address = agent.address();
@@ -24654,6 +24677,7 @@ Typical flow:
24654
24677
  seller $ t2 job deliver 0xJOB report.pdf
24655
24678
  buyer $ t2 job release 0xJOB (or: t2 job reject 0xJOB)
24656
24679
  either $ t2 job watch 0xJOB
24680
+ seller $ t2 job watch --mine (the provider inbox \u2014 all your jobs)
24657
24681
 
24658
24682
  Buying an OFFERING (t2 ACP) \u2014 price + terms come from the listing:
24659
24683
  buyer $ t2 browse "market report"
@@ -24874,12 +24898,60 @@ Buying an OFFERING (t2 ACP) \u2014 price + terms come from the listing:
24874
24898
  printBlank();
24875
24899
  printSuccess(note);
24876
24900
  if (digest) printKeyValue("Tx", digest);
24901
+ if (verb === "release") {
24902
+ printInfo(`Rate the work (builds the seller's on-chain-backed reputation): t2 job review ${truncateAddress(jobId)} --stars 5`);
24903
+ }
24877
24904
  printBlank();
24878
24905
  } catch (error) {
24879
24906
  handleError(error);
24880
24907
  }
24881
24908
  });
24882
24909
  }
24910
+ group.command("review").argument("<jobId>", "The Job object id (0x\u2026) of a RELEASED job you paid for").description("Rate a released job 1\u20135 stars \u2014 receipt-bound to the Job object (buyer)").requiredOption("--stars <1-5>", "Star rating, 1 (poor) to 5 (excellent)").option("--text <text>", "Optional short review (max 400 chars)").option("--key <path>", "Custom wallet path (default ~/.t2000/wallet.key)").option("--api <url>", `API base URL (default ${DEFAULT_API_BASE5})`).action(async (jobId, opts) => {
24911
+ try {
24912
+ const stars = Number.parseInt(opts.stars, 10);
24913
+ if (!Number.isInteger(stars) || stars < 1 || stars > 5) {
24914
+ throw new Error(`--stars must be an integer 1\u20135 (got "${opts.stars}").`);
24915
+ }
24916
+ const base = opts.api ?? DEFAULT_API_BASE5;
24917
+ const agent = await withAgent({ keyPath: opts.key });
24918
+ const address = agent.address();
24919
+ const challenge = await fetchJson3(`${base}/agent/challenge`, {
24920
+ method: "POST",
24921
+ body: { address }
24922
+ });
24923
+ const nonce = challenge.nonce;
24924
+ if (!nonce) throw new Error("Failed to get a challenge nonce.");
24925
+ const payload = {
24926
+ jobId: validateAddress(jobId),
24927
+ stars,
24928
+ text: opts.text?.trim() || null
24929
+ };
24930
+ const payloadHash = createHash2("sha256").update(JSON.stringify(payload), "utf8").digest("hex");
24931
+ const message = new TextEncoder().encode(
24932
+ `t2000-job-review:${nonce}:${payloadHash}`
24933
+ );
24934
+ const { signature } = await agent.keypair.signPersonalMessage(message);
24935
+ const response = await fetchJson3(`${base}/job/review`, {
24936
+ method: "POST",
24937
+ body: { address, nonce, signature, payload }
24938
+ });
24939
+ if (isJsonMode()) {
24940
+ printJson(response);
24941
+ return;
24942
+ }
24943
+ printBlank();
24944
+ printSuccess(`Review saved \u2014 ${"\u2605".repeat(stars)}${"\u2606".repeat(5 - stars)} on job ${truncateAddress(payload.jobId)}.`);
24945
+ const review = response.review;
24946
+ if (review?.seller) {
24947
+ printKeyValue("Seller page", `https://agents.t2000.ai/${review.seller}`);
24948
+ }
24949
+ printInfo("Re-run with different --stars/--text to edit your review.");
24950
+ printBlank();
24951
+ } catch (error) {
24952
+ handleError(error);
24953
+ }
24954
+ });
24883
24955
  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
24956
  try {
24885
24957
  const client = getSuiClient();
@@ -24904,12 +24976,62 @@ Buying an OFFERING (t2 ACP) \u2014 price + terms come from the listing:
24904
24976
  handleError(error);
24905
24977
  }
24906
24978
  });
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) => {
24979
+ 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) => {
24908
24980
  try {
24909
24981
  const agent = await withAgent({ keyPath: opts.key });
24910
24982
  const me = agent.address();
24911
- const client = getSuiClient();
24912
24983
  const intervalMs = Math.max(5, Number.parseInt(opts.interval, 10) || 15) * 1e3;
24984
+ if (opts.mine) {
24985
+ const base = opts.api ?? DEFAULT_API_BASE5;
24986
+ const seen = /* @__PURE__ */ new Map();
24987
+ const jobs = await fetchSellerJobs(base, me);
24988
+ if (isJsonMode()) {
24989
+ printJson({ seller: me, jobs });
24990
+ return;
24991
+ }
24992
+ const open = jobs.filter((j) => !TERMINAL_STATES.has(j.state));
24993
+ printBlank();
24994
+ printInfo(`Provider inbox for ${truncateAddress(me)} \u2014 ${jobs.length} job(s), ${open.length} open.`);
24995
+ printBlank();
24996
+ for (const job of open) {
24997
+ printInboxRow(job);
24998
+ printBlank();
24999
+ }
25000
+ if (open.length === 0) {
25001
+ printInfo("No open jobs. New hires appear here the moment the escrow funds.");
25002
+ printBlank();
25003
+ }
25004
+ for (const job of jobs) seen.set(job.jobId, job.state);
25005
+ if (opts.once) return;
25006
+ for (; ; ) {
25007
+ await new Promise((r) => setTimeout(r, intervalMs));
25008
+ let latest;
25009
+ try {
25010
+ latest = await fetchSellerJobs(base, me);
25011
+ } catch {
25012
+ continue;
25013
+ }
25014
+ for (const job of latest) {
25015
+ const prev = seen.get(job.jobId);
25016
+ if (prev === job.state) continue;
25017
+ seen.set(job.jobId, job.state);
25018
+ printBlank();
25019
+ if (prev === void 0 && job.state === "funded") {
25020
+ printSuccess(`New job \u2014 $${job.amountUsdc.toFixed(2)} USDC escrowed for you.`);
25021
+ } else {
25022
+ printInfo(`Job ${truncateAddress(job.jobId)}: ${prev ?? "new"} \u2192 ${job.state}`);
25023
+ }
25024
+ printInboxRow(job);
25025
+ printBlank();
25026
+ }
25027
+ }
25028
+ }
25029
+ if (!jobId) {
25030
+ printError("Provide a job id \u2014 or use --mine for the provider inbox.");
25031
+ process.exitCode = 1;
25032
+ return;
25033
+ }
25034
+ const client = getSuiClient();
24913
25035
  for (; ; ) {
24914
25036
  const job = await getJob(client, jobId);
24915
25037
  const actions = jobActionsFor(job, me);