@t2000/cli 10.27.2 → 10.29.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
@@ -28274,28 +28274,6 @@ var SUPPORTED_ASSETS = {
28274
28274
  }
28275
28275
  };
28276
28276
  var STABLE_ASSETS = ["USDC", "USDsui"];
28277
- var OPERATION_ASSETS = {
28278
- send: ["USDC", "USDsui", "SUI"],
28279
- swap: "*"
28280
- };
28281
- function isAllowedAsset(op, asset) {
28282
- const allowed = OPERATION_ASSETS[op];
28283
- if (allowed === "*") return true;
28284
- const target = asset.toLowerCase();
28285
- return allowed.some((a) => a.toLowerCase() === target);
28286
- }
28287
- function assertAllowedAsset(op, asset) {
28288
- if (!asset) return;
28289
- if (!isAllowedAsset(op, asset)) {
28290
- const allowed = OPERATION_ASSETS[op];
28291
- const list = Array.isArray(allowed) ? allowed.join(", ") : "any";
28292
- const swapHint = op === "send" ? " Swap to USDC or USDsui first, or send SUI." : "";
28293
- throw new T2000Error(
28294
- "INVALID_ASSET",
28295
- `${op} only supports ${list}. Cannot use ${asset}.${swapHint}`
28296
- );
28297
- }
28298
- }
28299
28277
  var GASLESS_STABLE_TYPES = {
28300
28278
  USDC: SUPPORTED_ASSETS.USDC.type,
28301
28279
  USDsui: SUPPORTED_ASSETS.USDsui.type
@@ -28899,19 +28877,42 @@ for (const [key, info] of Object.entries(SUPPORTED_ASSETS)) {
28899
28877
  ASSET_LOOKUP.set(info.displayName.toUpperCase(), key);
28900
28878
  }
28901
28879
  }
28880
+ init_token_registry();
28902
28881
  init_preflight();
28903
- function preflightSend(input) {
28882
+ function classifySendAsset(asset) {
28883
+ const coinType = resolveTokenType(asset.trim());
28884
+ if (!coinType) return null;
28885
+ let normalized;
28904
28886
  try {
28905
- assertAllowedAsset("send", input.asset);
28906
- } catch (e) {
28907
- return preflightFail("INVALID_ASSET", e.message);
28887
+ normalized = normalizeStructTag(coinType);
28888
+ } catch {
28889
+ return null;
28890
+ }
28891
+ if (normalized === normalizeStructTag(GASLESS_STABLE_TYPES.USDC)) {
28892
+ return { kind: "gasless-stable", symbol: "USDC", coinType: GASLESS_STABLE_TYPES.USDC };
28893
+ }
28894
+ if (normalized === normalizeStructTag(GASLESS_STABLE_TYPES.USDsui)) {
28895
+ return { kind: "gasless-stable", symbol: "USDsui", coinType: GASLESS_STABLE_TYPES.USDsui };
28896
+ }
28897
+ if (normalized === normalizeStructTag(SUI_TYPE)) {
28898
+ return { kind: "sui", symbol: "SUI", coinType: SUI_TYPE };
28899
+ }
28900
+ return { kind: "coin", symbol: resolveSymbol(coinType), coinType };
28901
+ }
28902
+ function invalidSendAssetMessage(asset) {
28903
+ return `Unknown asset "${asset}". Use a registry symbol (USDC, USDsui, SUI, MANIFEST, \u2026) or a full coin type (0x\u2026::module::TYPE).`;
28904
+ }
28905
+ function preflightSend(input) {
28906
+ const cls = classifySendAsset(input.asset);
28907
+ if (!cls) {
28908
+ return preflightFail("INVALID_ASSET", invalidSendAssetMessage(input.asset));
28908
28909
  }
28909
28910
  const amountCheck = checkPositiveAmount(input.amount);
28910
28911
  if (!amountCheck.valid) return amountCheck;
28911
- if ((input.asset === "USDC" || input.asset === "USDsui") && input.amount < GASLESS_MIN_STABLE_AMOUNT) {
28912
+ if (cls.kind === "gasless-stable" && input.amount < GASLESS_MIN_STABLE_AMOUNT) {
28912
28913
  return preflightFail(
28913
28914
  "INVALID_AMOUNT",
28914
- `Minimum gasless transfer is ${GASLESS_MIN_STABLE_AMOUNT} ${input.asset}. Got ${input.amount}.`
28915
+ `Minimum gasless transfer is ${GASLESS_MIN_STABLE_AMOUNT} ${cls.symbol}. Got ${input.amount}.`
28915
28916
  );
28916
28917
  }
28917
28918
  const addressCheck = checkSuiAddress(input.to);
@@ -28928,42 +28929,44 @@ async function buildSendTx({
28928
28929
  const pf = preflightSend({ to, amount, asset });
28929
28930
  if (!pf.valid) throw new T2000Error(pf.code, pf.error);
28930
28931
  const recipient = validateAddress(to);
28931
- const assetInfo = SUPPORTED_ASSETS[asset];
28932
- if (!assetInfo) throw new T2000Error("ASSET_NOT_SUPPORTED", `Asset ${asset} is not supported`);
28933
- const rawAmount = displayToRaw(amount, assetInfo.decimals);
28932
+ const cls = classifySendAsset(asset);
28933
+ const decimals = cls.kind === "gasless-stable" || cls.kind === "sui" ? SUPPORTED_ASSETS[cls.symbol].decimals : await resolveCoinDecimals(client, cls.coinType);
28934
+ const rawAmount = displayToRaw(amount, decimals);
28934
28935
  const tx = new Transaction();
28935
28936
  tx.setSender(address);
28936
- const balanceResp = await client.core.getBalance({ owner: address, coinType: assetInfo.type });
28937
+ const balanceResp = await client.core.getBalance({ owner: address, coinType: cls.coinType });
28937
28938
  const totalBalance = BigInt(balanceResp.balance.balance);
28938
28939
  if (totalBalance < rawAmount) {
28939
- throw new T2000Error("INSUFFICIENT_BALANCE", `Insufficient ${asset} balance`, {
28940
- available: Number(totalBalance) / 10 ** assetInfo.decimals,
28940
+ throw new T2000Error("INSUFFICIENT_BALANCE", `Insufficient ${cls.symbol} balance`, {
28941
+ available: Number(totalBalance) / 10 ** decimals,
28941
28942
  required: amount
28942
28943
  });
28943
28944
  }
28944
- if (asset === "USDC" || asset === "USDsui") {
28945
- const rawFloor = displayToRaw(GASLESS_MIN_STABLE_AMOUNT, assetInfo.decimals);
28946
- const remainder = totalBalance - rawAmount;
28947
- if (remainder > 0n && remainder < rawFloor) {
28948
- const total = Number(totalBalance) / 10 ** assetInfo.decimals;
28949
- throw new T2000Error(
28950
- "INVALID_AMOUNT",
28951
- `Gasless ${asset} transfers must send the entire balance or leave at least ${GASLESS_MIN_STABLE_AMOUNT} ${asset}. Sending ${amount} of ${total} leaves ${(total - amount).toFixed(assetInfo.decimals)}. Send ${total} (everything) or at most ${(total - GASLESS_MIN_STABLE_AMOUNT).toFixed(assetInfo.decimals)}.`,
28952
- { available: total, required: amount }
28953
- );
28954
- }
28955
- }
28956
- if (asset === "SUI") {
28945
+ if (cls.kind === "sui") {
28957
28946
  const [sendCoin] = tx.splitCoins(tx.gas, [rawAmount]);
28958
28947
  tx.transferObjects([sendCoin], recipient);
28959
28948
  return tx;
28960
28949
  }
28961
- const coinType = GASLESS_STABLE_TYPES[asset];
28950
+ if (cls.kind === "coin") {
28951
+ const sendCoin = coinWithBalance({ type: cls.coinType, balance: rawAmount })(tx);
28952
+ tx.transferObjects([sendCoin], recipient);
28953
+ return tx;
28954
+ }
28955
+ const rawFloor = displayToRaw(GASLESS_MIN_STABLE_AMOUNT, decimals);
28956
+ const remainder = totalBalance - rawAmount;
28957
+ if (remainder > 0n && remainder < rawFloor) {
28958
+ const total = Number(totalBalance) / 10 ** decimals;
28959
+ throw new T2000Error(
28960
+ "INVALID_AMOUNT",
28961
+ `Gasless ${cls.symbol} transfers must send the entire balance or leave at least ${GASLESS_MIN_STABLE_AMOUNT} ${cls.symbol}. Sending ${amount} of ${total} leaves ${(total - amount).toFixed(decimals)}. Send ${total} (everything) or at most ${(total - GASLESS_MIN_STABLE_AMOUNT).toFixed(decimals)}.`,
28962
+ { available: total, required: amount }
28963
+ );
28964
+ }
28962
28965
  tx.moveCall({
28963
28966
  target: "0x2::balance::send_funds",
28964
- typeArguments: [coinType],
28967
+ typeArguments: [cls.coinType],
28965
28968
  arguments: [
28966
- tx.balance({ type: coinType, balance: rawAmount }),
28969
+ tx.balance({ type: cls.coinType, balance: rawAmount }),
28967
28970
  tx.pure.address(recipient)
28968
28971
  ]
28969
28972
  });
@@ -30349,18 +30352,17 @@ var T2000 = class _T2000 extends import_index2.default {
30349
30352
  /**
30350
30353
  * Send `amount` of `asset` to `to` (hex address or SuiNS name).
30351
30354
  *
30352
- * [v4.0 Phase A Day 2SPEC_AGENT_WALLET_GREENFIELD §A]
30355
+ * [S.957 2026-08-08] `asset` accepts **any held coin type** a
30356
+ * registry symbol (`USDC`, `MANIFEST`) or a full `0x…::module::TYPE`.
30357
+ * Unresolvable strings throw `INVALID_ASSET`; empty balances throw
30358
+ * `INSUFFICIENT_BALANCE`.
30353
30359
  *
30354
- * **Breaking changes from v3.x:**
30355
- * - `asset` is now REQUIRED (no implicit `?? 'USDC'` default). Callers
30356
- * must specify `'USDC' | 'USDsui' | 'SUI'`. Sending `'USDT'` /
30357
- * `'USDe'` / `'WAL'` / `'ETH'` / `'NAVX'` / `'GOLD'` now errors
30358
- * with `INVALID_ASSET` — swap to a stable first.
30359
30360
  * - USDC + USDsui builds go through `SuiGrpcClient` so the gRPC build
30360
30361
  * resolver auto-detects `0x2::balance::send_funds` eligibility and
30361
- * zeros gas at simulate time. Result: **gasless USDC / USDsui sends
30362
- * from a zero-SUI wallet.** SUI sends stay on the standard gas-paid
30363
- * path.
30362
+ * zeros gas at simulate time **gasless sends from a zero-SUI
30363
+ * wallet.** They remain the ONLY gasless assets.
30364
+ * - SUI and every other coin type are gas-paid (the sender needs SUI).
30365
+ * - `asset` stays REQUIRED (no implicit `?? 'USDC'` default — v4 rule).
30364
30366
  *
30365
30367
  * Submission stays on the JSON-RPC client (the rest of the SDK
30366
30368
  * expects JSON-RPC for read paths, and Sui's docs explicitly support
@@ -30371,30 +30373,31 @@ var T2000 = class _T2000 extends import_index2.default {
30371
30373
  if (!asset) {
30372
30374
  throw new T2000Error(
30373
30375
  "INVALID_ASSET",
30374
- "send() requires an explicit asset. Use one of: USDC, USDsui, SUI."
30376
+ "send() requires an explicit asset. Use a registry symbol (USDC, USDsui, SUI, MANIFEST, \u2026) or a full coin type (0x\u2026::module::TYPE)."
30375
30377
  );
30376
30378
  }
30377
- assertAllowedAsset("send", asset);
30378
- const sendableAsset = asset;
30379
+ const cls = classifySendAsset(asset);
30380
+ if (!cls) {
30381
+ throw new T2000Error("INVALID_ASSET", invalidSendAssetMessage(asset));
30382
+ }
30379
30383
  this.limits.assert({
30380
30384
  operation: "send",
30381
- amountUsd: approxUsdValue(sendableAsset, params.amount) ?? 0,
30385
+ amountUsd: approxUsdValue(cls.symbol, params.amount) ?? 0,
30382
30386
  force: params.force
30383
30387
  });
30384
30388
  const resolved = await this.resolveRecipient(params.to);
30385
30389
  const sendAmount = params.amount;
30386
30390
  const sendTo = resolved.address;
30387
- const useGrpc = sendableAsset === "USDC" || sendableAsset === "USDsui";
30388
- const buildClient = useGrpc ? getSuiGrpcClient() : void 0;
30391
+ const buildClient = cls.kind === "gasless-stable" ? getSuiGrpcClient() : void 0;
30389
30392
  const gasResult = await executeTx(
30390
30393
  this.client,
30391
30394
  this._signer,
30392
- () => buildSendTx({ client: this.client, address: this._address, to: sendTo, amount: sendAmount, asset: sendableAsset }),
30395
+ () => buildSendTx({ client: this.client, address: this._address, to: sendTo, amount: sendAmount, asset }),
30393
30396
  { buildClient }
30394
30397
  );
30395
- this.limits.record(approxUsdValue(sendableAsset, sendAmount) ?? 0);
30398
+ this.limits.record(approxUsdValue(cls.symbol, sendAmount) ?? 0);
30396
30399
  const balance = await this.balance();
30397
- this.emitBalanceChange(sendableAsset, sendAmount, "send", gasResult.digest);
30400
+ this.emitBalanceChange(cls.symbol, sendAmount, "send", gasResult.digest);
30398
30401
  return {
30399
30402
  success: true,
30400
30403
  tx: gasResult.digest,
@@ -30820,6 +30823,34 @@ async function getJobSpec(base, hash) {
30820
30823
  }
30821
30824
  return content;
30822
30825
  }
30826
+ var TITLE_PREFIX_RE = /^title\s*:\s*(.+)$/i;
30827
+ var ENVELOPE_TITLE_MAX = 80;
30828
+ function customHireEnvelope(brief, title, now) {
30829
+ const body2 = brief.trim();
30830
+ let t = title?.trim() ?? "";
30831
+ if (!t) {
30832
+ const first = body2.split("\n").find((l) => l.trim())?.trim() ?? "";
30833
+ const prefixed = TITLE_PREFIX_RE.exec(first);
30834
+ t = (prefixed ? prefixed[1] : first).trim();
30835
+ }
30836
+ if (t.length > ENVELOPE_TITLE_MAX) {
30837
+ t = `${t.slice(0, ENVELOPE_TITLE_MAX - 1).trimEnd()}\u2026`;
30838
+ }
30839
+ return JSON.stringify({
30840
+ type: "t2-acp-custom@1",
30841
+ title: t || "Custom job",
30842
+ brief: body2,
30843
+ createdAtMs: now
30844
+ });
30845
+ }
30846
+ function isCustomHireEnvelope(text) {
30847
+ try {
30848
+ const parsed = JSON.parse(text);
30849
+ return (parsed.type === "t2-acp-custom@1" || parsed.type === "t2-acp-invite@1") && typeof parsed.brief === "string" && parsed.brief.trim().length > 0;
30850
+ } catch {
30851
+ return false;
30852
+ }
30853
+ }
30823
30854
  async function fetchJson(url, init) {
30824
30855
  const res = await fetch(url, {
30825
30856
  method: init?.method ?? "GET",
@@ -34104,20 +34135,19 @@ function registerStatus(program3) {
34104
34135
 
34105
34136
  // src/commands/send.ts
34106
34137
  var import_picocolors6 = __toESM(require_picocolors(), 1);
34107
- var ACCEPTED_ASSETS = ["USDC", "USDsui", "SUI"];
34108
- var ACCEPTED_ASSETS_LIST = ACCEPTED_ASSETS.join(", ");
34138
+ var ASSET_HINT = "a registry symbol (USDC, USDsui, SUI, MANIFEST, \u2026) or a full coin type (0x\u2026::module::TYPE)";
34109
34139
  function parseSendArgs(args) {
34110
34140
  const filtered = args.filter((a) => a.toLowerCase() !== "to");
34111
34141
  if (filtered.length < 2) {
34112
34142
  throw new Error(
34113
34143
  `Usage: t2 send <amount> <asset> <recipient>
34114
- asset must be one of: ${ACCEPTED_ASSETS_LIST}
34144
+ asset is ${ASSET_HINT}
34115
34145
  recipient can be a 0x address or SuiNS name (alice.sui, alice.audric.sui)`
34116
34146
  );
34117
34147
  }
34118
34148
  if (filtered.length === 2) {
34119
34149
  throw new Error(
34120
- `Missing required <asset> argument. Use one of: ${ACCEPTED_ASSETS_LIST}. Example: t2 send ${filtered[0]} USDC ${filtered[1]}`
34150
+ `Missing required <asset> argument. Use ${ASSET_HINT}. Example: t2 send ${filtered[0]} USDC ${filtered[1]}`
34121
34151
  );
34122
34152
  }
34123
34153
  const amount = parseFloat(filtered[0]);
@@ -34125,36 +34155,34 @@ function parseSendArgs(args) {
34125
34155
  throw new Error(`Amount must be a positive number (got "${filtered[0]}").`);
34126
34156
  }
34127
34157
  const candidate = filtered[1];
34128
- const normalized = normalizeAssetSymbol(candidate);
34129
- if (!normalized) {
34158
+ const cls = classifySendAsset(candidate);
34159
+ if (!cls) {
34130
34160
  throw new Error(
34131
- `Unsupported asset "${candidate}". Use one of: ${ACCEPTED_ASSETS_LIST}. Swap to USDC or USDsui first with \`t2 swap\`, or send SUI.`
34161
+ `Unknown asset "${candidate}". Use ${ASSET_HINT}. Check your holdings with \`t2 balance\`.`
34132
34162
  );
34133
34163
  }
34134
34164
  const recipient = filtered[2];
34135
34165
  if (!recipient) {
34136
34166
  throw new Error(`Missing recipient. Usage: t2 send <amount> <asset> <recipient>.`);
34137
34167
  }
34138
- return { amount, asset: normalized, recipient };
34139
- }
34140
- function normalizeAssetSymbol(input) {
34141
- const lower = input.toLowerCase();
34142
- if (lower === "usdc") return "USDC";
34143
- if (lower === "usdsui") return "USDsui";
34144
- if (lower === "sui") return "SUI";
34145
- return void 0;
34168
+ const asset = cls.kind === "gasless-stable" || cls.kind === "sui" ? cls.symbol : candidate;
34169
+ return { amount, asset, recipient };
34146
34170
  }
34147
34171
  function registerSend(program3) {
34148
34172
  program3.command("send").argument("<amount>", "Amount of <asset> to send (denominated in asset units, NOT USD)").argument(
34149
34173
  "[args...]",
34150
- 'Asset (USDC | USDsui | SUI), optional "to" keyword, and recipient (0x address or SuiNS name like alice.sui)'
34151
- ).description("Send USDC, USDsui, or SUI. USDC + USDsui are gasless (no SUI required).").option("--key <path>", "Custom wallet path (default ~/.t2000/wallet.key)").option("--force", "Override spending limits for this call (see `t2 limit`)").addHelpText(
34174
+ 'Asset (registry symbol like USDC / MANIFEST, or a full 0x\u2026::module::TYPE coin type), optional "to" keyword, and recipient (0x address or SuiNS name like alice.sui)'
34175
+ ).description(
34176
+ "Send any held token. USDC + USDsui are gasless; everything else needs SUI for gas."
34177
+ ).option("--key <path>", "Custom wallet path (default ~/.t2000/wallet.key)").option("--force", "Override spending limits for this call (see `t2 limit`)").addHelpText(
34152
34178
  "after",
34153
34179
  `
34154
34180
  Examples:
34155
34181
  $ t2 send 5 USDC 0xabc\u2026 Send 5 USDC (gasless) to a hex address
34156
34182
  $ t2 send 5 USDsui alice.sui Send 5 USDsui (gasless) to a SuiNS name
34157
34183
  $ t2 send 0.1 SUI alice.audric.sui Send 0.1 SUI (gas required) to a SuiNS subname
34184
+ $ t2 send 10 MANIFEST 0xabc\u2026 Send any held token (gas required)
34185
+ $ t2 send 10 0xc466\u2026::manifest::MANIFEST 0xabc\u2026 Full coin type works too
34158
34186
  `
34159
34187
  ).action(async (amount, args, opts) => {
34160
34188
  try {
@@ -34163,9 +34191,8 @@ Examples:
34163
34191
  const result = await agent.send({
34164
34192
  to: recipient,
34165
34193
  amount: parsedAmount,
34166
- // The CLI parser already narrowed asset to USDC / USDsui / SUI;
34167
- // the SDK accepts `SupportedAsset` and re-validates via
34168
- // `assertAllowedAsset('send', …)` at runtime.
34194
+ // [S.957] Any resolvable asset string the SDK re-resolves via
34195
+ // `classifySendAsset` (same rule the parser used).
34169
34196
  asset,
34170
34197
  force: opts.force
34171
34198
  });
@@ -34178,7 +34205,7 @@ Examples:
34178
34205
  return;
34179
34206
  }
34180
34207
  const displayTo = result.suinsName ? `${result.suinsName} ${import_picocolors6.default.dim(`(${truncateAddress(result.to)})`)}` : truncateAddress(result.to);
34181
- const amountDisplay = asset === "SUI" ? `${result.amount.toFixed(4)} SUI` : `${formatUsd(result.amount)} ${asset}`;
34208
+ const amountDisplay = asset === "USDC" || asset === "USDsui" ? `${formatUsd(result.amount)} ${asset}` : asset === "SUI" ? `${result.amount.toFixed(4)} SUI` : `${result.amount} ${asset}`;
34182
34209
  printBlank();
34183
34210
  printSuccess(`Sent ${amountDisplay} \u2192 ${displayTo}`);
34184
34211
  if (result.gasCost === 0) {
@@ -36224,10 +36251,10 @@ function parseDuration(input) {
36224
36251
  }
36225
36252
  var SHA256_HEX_RE = /^0x[0-9a-fA-F]{64}$/;
36226
36253
  var SPEC_STORE_MAX_BYTES = 16 * 1024;
36227
- async function resolveSpecUpload(base, input) {
36254
+ async function loadSpecText(input) {
36228
36255
  const trimmed = input.trim();
36229
36256
  if (SHA256_HEX_RE.test(trimmed)) {
36230
- return { hash: trimmed.toLowerCase(), uploaded: false };
36257
+ return { kind: "hash", hash: trimmed.toLowerCase() };
36231
36258
  }
36232
36259
  let bytes;
36233
36260
  try {
@@ -36248,7 +36275,27 @@ async function resolveSpecUpload(base, input) {
36248
36275
  "Content is not UTF-8 text \u2014 the job-spec store holds text only. Upload a short note that LINKS the artifact, or pin a precomputed commitment with --hash-only 0x<sha256>."
36249
36276
  );
36250
36277
  }
36251
- return { hash: `0x${await putJobSpec(base, text)}`, uploaded: true };
36278
+ return { kind: "text", text };
36279
+ }
36280
+ async function resolveSpecUpload(base, input) {
36281
+ const loaded = await loadSpecText(input);
36282
+ if (loaded.kind === "hash") {
36283
+ return { hash: loaded.hash, uploaded: false };
36284
+ }
36285
+ return { hash: `0x${await putJobSpec(base, loaded.text)}`, uploaded: true };
36286
+ }
36287
+ async function resolveHireSpecUpload(base, input, title) {
36288
+ const loaded = await loadSpecText(input);
36289
+ if (loaded.kind === "hash") {
36290
+ return { hash: loaded.hash, uploaded: false };
36291
+ }
36292
+ const body2 = isCustomHireEnvelope(loaded.text) ? loaded.text : customHireEnvelope(loaded.text, title, Date.now());
36293
+ if (Buffer.byteLength(body2, "utf8") > SPEC_STORE_MAX_BYTES) {
36294
+ throw new Error(
36295
+ "The brief plus its envelope exceeds the 16 KiB job-spec store cap \u2014 shorten the brief, or link the long artifact (URL / IPFS)."
36296
+ );
36297
+ }
36298
+ return { hash: `0x${await putJobSpec(base, body2)}`, uploaded: true };
36252
36299
  }
36253
36300
  function stateColor(state) {
36254
36301
  if (state === "released") return import_picocolors13.default.green(state);
@@ -36349,7 +36396,7 @@ no fund step; unclaimed openings refund fee-free):
36349
36396
  ASP $ t2 job board \xB7 t2 job claim <openingId>
36350
36397
  `
36351
36398
  );
36352
- group.command("hire").alias("create").argument("[amount]", `USDC to escrow (max ${MAX_JOB_USDC}; omit when hiring a --service listing)`).argument("[seller]", "The ASP's Sui address (omit when hiring a --service listing)").description("Hire \u2014 fund an escrow job in one transaction (buyer): a listing (--agent + --service) or your own terms (amount + seller + --spec)").option("--spec <file-or-text>", "Job spec \u2014 a file path or inline text (UPLOADED so the seller can read it; sha256 pinned on-chain), or a bare 0x\u2026 sha256 (confidential: pins without uploading)").option(
36399
+ group.command("hire").alias("create").argument("[amount]", `USDC to escrow (max ${MAX_JOB_USDC}; omit when hiring a --service listing)`).argument("[seller]", "The ASP's Sui address (omit when hiring a --service listing)").description("Hire \u2014 fund an escrow job in one transaction (buyer): a listing (--agent + --service) or your own terms (amount + seller + --spec)").option("--spec <file-or-text>", "Job spec \u2014 a file path or inline text (UPLOADED as the public t2-acp-custom@1 title+brief envelope so the seller and the store can read it; sha256 pinned on-chain), or a bare 0x\u2026 sha256 (confidential: pins without uploading, no envelope)").option("--title <text>", "Public job title (\u226480 chars). Custom/direct hire only; derived from the brief's first line if omitted").option(
36353
36400
  "--agent <address|#id|@handle>",
36354
36401
  "Hire a listing: the ASP's agent address, #id, or @handle"
36355
36402
  ).option("--service <slug>", "The service slug (see t2 services / t2 service list <agent>)").option("--requirements <file-or-json-or-text>", "What the seller asked buyers to provide \u2014 if the listing lists JSON keys, fill EVERY key (JSON object; extra keys OK)").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_BASE7})`).action(
@@ -36429,7 +36476,11 @@ no fund step; unclaimed openings refund fee-free):
36429
36476
  seller = validateAddress(
36430
36477
  (await resolveAgentRef(base, sellerArg)).address
36431
36478
  );
36432
- ({ hash: specHash } = await resolveSpecUpload(base, opts.spec));
36479
+ ({ hash: specHash } = await resolveHireSpecUpload(
36480
+ base,
36481
+ opts.spec,
36482
+ opts.title
36483
+ ));
36433
36484
  deliverByMs = Date.now() + parseDuration(opts.deadline);
36434
36485
  reviewWindowMs = opts.review ? parseDuration(opts.review) : DEFAULT_REVIEW_WINDOW_MS;
36435
36486
  rejectSplitBps = Number.parseInt(opts.split, 10);
@@ -36895,9 +36946,9 @@ Examples:
36895
36946
  $ t2 service retire sui-market-report
36896
36947
  `
36897
36948
  );
36898
- group.command("create").description("List a service under your Agent ID (re-run to update it)").requiredOption("--name <name>", "Service 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 service is (max 2000 chars)").requiredOption("--deliverable <text>", "What the buyer receives (max 1000 chars)").option("--slug <slug>", "Machine name (default: derived from --name)").option(
36949
+ group.command("create").description("List a service under your Agent ID (re-run to update it)").requiredOption("--name <name>", "Service 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 service is (max 2000 chars)").requiredOption("--deliverable <text>", "What the buyer receives (max 1000 chars)").option("--slug <slug>", "Machine name (default: derived from --name)").requiredOption(
36899
36950
  "--requirements <file-or-json-or-text>",
36900
- "What the buyer must provide \u2014 prefer a JSON object of required fields (keys enforced non-empty at hire); free text ok (file path ok)"
36951
+ "The questions buyers answer at hire (REQUIRED \u2014 the API rejects listings that ask nothing): free text, or a JSON object of field names \u2192 hints (keys enforced non-empty at hire; file path ok)"
36901
36952
  ).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(
36902
36953
  "--category <category>",
36903
36954
  `Directory category for your listing: ${AGENT_CATEGORIES.join(" | ")} (required unless already set on your profile)`