shinjuku-shield 0.3.99 → 0.3.100

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/README.md CHANGED
@@ -6,13 +6,13 @@ answers privately, in a terminal chat or for your agent app. Its `mcp`
6
6
  command runs a local MCP server (stdio) for your agent. Your keys never
7
7
  leave your machine.
8
8
 
9
- This is version 0.3.99. The npm packages `shinjuku-shield` and
9
+ This is version 0.3.100. The npm packages `shinjuku-shield` and
10
10
  `shinjuku-shielded` hold the same files. The command is `shinjuku`.
11
11
 
12
12
  ## Set up
13
13
 
14
14
  ```sh
15
- npx -y --no-audit --no-fund shinjuku-shield@0.3.99 setup
15
+ npx -y --no-audit --no-fund shinjuku-shield@0.3.100 setup
16
16
  ```
17
17
 
18
18
  Setup makes the wallet on this machine, a private passphrase file, and the
@@ -24,7 +24,7 @@ server to that host's config.
24
24
  ## The `shinjuku` command
25
25
 
26
26
  ```sh
27
- npm install -g --no-audit --no-fund shinjuku@0.3.99
27
+ npm install -g --no-audit --no-fund shinjuku@0.3.100
28
28
  shinjuku help
29
29
  ```
30
30
 
@@ -36,13 +36,13 @@ Every wallet command works after `shinjuku`, for example `shinjuku balance`,
36
36
  Claude Code:
37
37
 
38
38
  ```sh
39
- claude mcp add --scope user shinjuku -- npx -y --no-audit --no-fund shinjuku-shield@0.3.99 mcp
39
+ claude mcp add --scope user shinjuku -- npx -y --no-audit --no-fund shinjuku-shield@0.3.100 mcp
40
40
  ```
41
41
 
42
42
  Claude Desktop (`claude_desktop_config.json`) or Cursor (`~/.cursor/mcp.json`):
43
43
 
44
44
  ```json
45
- { "mcpServers": { "shinjuku": { "command": "npx", "args": ["-y", "--no-audit", "--no-fund", "shinjuku-shield@0.3.99", "mcp"] } } }
45
+ { "mcpServers": { "shinjuku": { "command": "npx", "args": ["-y", "--no-audit", "--no-fund", "shinjuku-shield@0.3.100", "mcp"] } } }
46
46
  ```
47
47
 
48
48
  On native Windows, if the host cannot find `npx`, use `"command": "cmd"` and
package/manifest.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "schema": "carbon-shielded-wallet/bundle-manifest/v1",
3
3
  "file": "shinjuku-wallet.mjs",
4
- "sha256": "5023a2917ba9f9f802a955c0782826c061664754b9b463c59aca590154c1539e",
5
- "bytes": 12786716,
6
- "sourceCommit": "03d2c6ac920ba3cdec72d0223ec26516d467edc2",
4
+ "sha256": "fe6cb068e76316bd9c4fab329dbd3586d980d69f6fe42a0f01fa1a254abb4410",
5
+ "bytes": 12791175,
6
+ "sourceCommit": "779d2665c82980faf7fbc61f8660c8f390b3884c",
7
7
  "sourceDirty": false,
8
8
  "installRecordMatchesLockfile": true,
9
9
  "releaseRule": "valid only when two fresh npm ci --ignore-scripts builds on different OSes give the same sha256; installRecordMatchesLockfile true means every entry npm installed equals its package-lock.json entry and every entry not marked optional, devOptional included, is installed: an optional entry (a package for another platform) may be absent",
@@ -1297,5 +1297,5 @@
1297
1297
  "version": "1.8.0"
1298
1298
  }
1299
1299
  ],
1300
- "publishedAt": "2026-10-24T11:39:09.768Z"
1300
+ "publishedAt": "2026-10-24T12:55:23.206Z"
1301
1301
  }
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "shinjuku-shield",
3
- "version": "0.3.99",
3
+ "version": "0.3.100",
4
4
  "mcpName": "io.github.ShinjukuStaition/shinjuku-mcp",
5
5
  "description": "Shinjuku Shielded, the private x402 facilitator on Solana: the self-custody wallet CLI and its MCP server. Shield, pay, and unshield USDC from any MCP agent, under caps you set.",
6
- "shinjukuRelease": "5023a291",
7
- "shinjukuSha256": "5023a2917ba9f9f802a955c0782826c061664754b9b463c59aca590154c1539e",
6
+ "shinjukuRelease": "fe6cb068",
7
+ "shinjukuSha256": "fe6cb068e76316bd9c4fab329dbd3586d980d69f6fe42a0f01fa1a254abb4410",
8
8
  "license": "SEE LICENSE IN LICENSE.txt",
9
9
  "author": "Shinjuku Shielded",
10
10
  "homepage": "https://github.com/ShinjukuStaition/shinjuku-shield#readme",
@@ -50934,7 +50934,7 @@ function historyRpcUrl(value) {
50934
50934
  }
50935
50935
  function historyRpcHint(reason, { signsNothing: signsNothing2 = false } = {}) {
50936
50936
  if (/\bhistory_rpc_failed\b/.test(reason)) {
50937
- return `the pool's public history from the history source (--history-rpc, or the history proxy this wallet pins for this pool: its onion in a --tor run, else its https origin) could not be used: the source did not answer, answered an error (for example JSON-RPC -32016: it stayed behind the chain for ${HISTORY_RPC_BEHIND_WAIT_MS / 1e3} s), or served history that does not match the pool account your own RPC reads, a transaction whose Ed25519 signatures do not verify, or a transaction that differs from the copy your own RPC holds (a code after history_rpc_failed names the check). The wallet never falls back to your own RPC by itself. ` + (signsNothing2 ? "This run signs nothing, so nothing changed on chain. " : "A rerun of the same command continues where it stopped and never pays twice. ") + "Rerun later, or use --history-rpc own (your RPC provider then sees that you read this pool)";
50937
+ return `the pool's public history from the history source (--history-rpc, or the history proxy this wallet pins for this pool: its onion in a --tor run, else its https origin) could not be used: the source did not answer, answered an error (for example JSON-RPC -32016: it stayed behind the chain for ${HISTORY_RPC_BEHIND_WAIT_MS / 1e3} s), or served history that does not match the pool state your own RPC reads, a transaction whose Ed25519 signatures do not verify, or a transaction that differs from the copy your own RPC holds (a code after history_rpc_failed names the check). The wallet never falls back to your own RPC by itself. ` + (signsNothing2 ? "This run signs nothing, so nothing changed on chain. " : "A rerun of the same command continues where it stopped and never pays twice. ") + "Rerun later, or use --history-rpc own (your RPC provider then sees that you read this pool)";
50938
50938
  }
50939
50939
  if (/\bhistory_rpc_invalid\b/.test(reason)) {
50940
50940
  return "--history-rpc takes own, an https URL, or an http://<v3 onion>/ origin with --tor (outside a Tor run an onion is refused before any lookup); nothing was read or signed";
@@ -51157,8 +51157,8 @@ function connectSplit(own, historyUrl, commitment = "finalized", { pause: pause3
51157
51157
  const now = Date.now();
51158
51158
  if (behindSince === void 0) {
51159
51159
  behindSince = now;
51160
- notice(strictAfterSlot === void 0 ? `history: the history source is behind the chain (JSON-RPC -32016: it has not reached the slot your own RPC read the pool account at); asking again every 0.15 s for about 2 s, then after 0.5 s, 1 s, 2 s, then every 5 s, for at most ${HISTORY_RPC_BEHIND_WAIT_MS / 1e3} s
51161
- ` : `history: the pool account changed after slot ${strictAfterSlot}, the newest slot the history source had reached (its history did not rebuild the pool account your own RPC reads); asking it again every 0.15 s for about 2 s, then after 0.5 s, 1 s, 2 s, then every 5 s, for at most ${HISTORY_RPC_BEHIND_WAIT_MS / 1e3} s
51160
+ notice(strictAfterSlot === void 0 ? `history: the history source is behind the chain (JSON-RPC -32016: it has not reached the slot your own RPC read the pool state at); asking again every 0.15 s for about 2 s, then after 0.5 s, 1 s, 2 s, then every 5 s, for at most ${HISTORY_RPC_BEHIND_WAIT_MS / 1e3} s
51161
+ ` : `history: the pool state changed after slot ${strictAfterSlot}, the newest slot the history source had reached (its history did not rebuild the pool state your own RPC reads); asking it again every 0.15 s for about 2 s, then after 0.5 s, 1 s, 2 s, then every 5 s, for at most ${HISTORY_RPC_BEHIND_WAIT_MS / 1e3} s
51162
51162
  `);
51163
51163
  }
51164
51164
  const delay3 = behindWaits < HISTORY_RPC_BEHIND_FAST_TRIES ? HISTORY_RPC_BEHIND_FAST_MS : HISTORY_RPC_BEHIND_RETRY_STEPS_MS[Math.min(behindWaits - HISTORY_RPC_BEHIND_FAST_TRIES, HISTORY_RPC_BEHIND_RETRY_STEPS_MS.length - 1)];
@@ -107668,12 +107668,19 @@ var init_family = __esm({
107668
107668
  });
107669
107669
 
107670
107670
  // wallet/src/inference/models.mjs
107671
- var DEFAULT_MODEL, DEFAULT_MAX_TOKENS, MIN_MAX_TOKENS, MODELS;
107671
+ function defaultMaxTokens(model) {
107672
+ const entry = MODELS.find((item) => item.id === model);
107673
+ return entry && entry.priceOutPerMAtomic * BigInt(DEFAULT_MAX_TOKENS) > FLOOR_ATOMIC * 1000000n ? SHORT_DEFAULT_MAX_TOKENS : DEFAULT_MAX_TOKENS;
107674
+ }
107675
+ var DEFAULT_MODEL, DEFAULT_MODEL_PAID_WORDS, DEFAULT_MAX_TOKENS, SHORT_DEFAULT_MAX_TOKENS, FLOOR_ATOMIC, MIN_MAX_TOKENS, MODELS;
107672
107676
  var init_models = __esm({
107673
107677
  "wallet/src/inference/models.mjs"() {
107674
107678
  "use strict";
107675
- DEFAULT_MODEL = "deepseek-v4-1-flash";
107676
- DEFAULT_MAX_TOKENS = 512;
107679
+ DEFAULT_MODEL = "qwen3-8-flash";
107680
+ DEFAULT_MODEL_PAID_WORDS = "0.002175 USDC";
107681
+ DEFAULT_MAX_TOKENS = 2048;
107682
+ SHORT_DEFAULT_MAX_TOKENS = 512;
107683
+ FLOOR_ATOMIC = 10000n;
107677
107684
  MIN_MAX_TOKENS = 256;
107678
107685
  MODELS = Object.freeze([
107679
107686
  Object.freeze({ id: "claude-opus-5", priceInPerMAtomic: 5000000n, priceOutPerMAtomic: 25000000n, note: "strongest; pays at most $5 in / $25 out per 1M tokens" }),
@@ -109359,7 +109366,7 @@ async function reshieldPocket({ store, connection, options }) {
109359
109366
  held = 0n;
109360
109367
  }
109361
109368
  if (!open4 && held === 0n) {
109362
- return { success: true, pocket: chosen.index, reshieldedAtomic: "0", note: "this pocket's USDC account holds nothing; nothing was sent" };
109369
+ return { success: true, pocket: chosen.index, reshieldedAtomic: "0", note: "this pocket holds no USDC; nothing was sent" };
109363
109370
  }
109364
109371
  const depositId = open4?.depositId ?? `${prefix}${records.length + 1}`;
109365
109372
  const proofTools = loadP03PublicPaymentProofTools(resolve20(options["proof-tools"]));
@@ -112174,7 +112181,7 @@ var init_job = __esm({
112174
112181
  "a pocket of the old pool is returning its remainder into your private balance (a relayed deposit); it is then moved with your notes",
112175
112182
  `run ${terminalCommand("move")} again in a minute; it resumes the same deposit and never sends twice`
112176
112183
  ],
112177
- wallet_move_stuck_notes: ["some notes of the old pool sit before every account of the wallet, so no payment can take them", "tell the operator; an unshield of those notes takes them out"],
112184
+ wallet_move_stuck_notes: ["some notes of the old pool are where no payment of this wallet can take them", "tell the operator; an unshield of those notes takes them out"],
112178
112185
  // Pool succession (succession/scheduler.mjs, WALLET-MCP-SPEC §3.3): the old pool was upgraded to a new pool.
112179
112186
  wallet_move_pool_succeeded: [
112180
112187
  "the facilitator upgraded this old pool to a new pool, so it takes no payment and this kind of move cannot run there; your money there is safe",
@@ -114071,16 +114078,16 @@ function pocketCarryReason(carry) {
114071
114078
  if (carry && CARRY_DONE.has(carry.state)) return { done: true, waits: false, code: null, reason: "" };
114072
114079
  if (carry?.state !== "refused") return { done: false, waits: true, code: null, reason: "the wallet has not finished its check of the pool switch" };
114073
114080
  const code2 = String(carry.code ?? "");
114074
- const reason = code2 === "p03_pocket_carry_source_fill_pending" || code2 === "p03_pocket_carry_source_payment_pending" ? "a pocket top-up or a pocket payment is not final yet" : code2 === "wallet_locked" ? "another wallet command uses the old pool's wallet now" : CARRY_WAIT_CODES.has(code2) ? "the wallet has not finished its check of the pool switch" : code2 === "p03_pocket_carry_target_not_empty" ? "the new pool's wallet already has pocket accounts of its own" : "the pocket records of the two wallets do not match";
114081
+ const reason = code2 === "p03_pocket_carry_source_fill_pending" || code2 === "p03_pocket_carry_source_payment_pending" ? "a pocket top-up or a pocket payment is not final yet" : code2 === "wallet_locked" ? "another wallet command uses the old pool's wallet now" : CARRY_WAIT_CODES.has(code2) ? "the wallet has not finished its check of the pool switch" : code2 === "p03_pocket_carry_target_not_empty" ? "the new pool's wallet already has pockets of its own" : "the pocket records of the two wallets do not match";
114075
114082
  return { done: false, waits: CARRY_WAIT_CODES.has(code2), code: code2, reason };
114076
114083
  }
114077
114084
  function pocketCarryText(carry) {
114078
114085
  if (carry?.state !== "refused") return "";
114079
114086
  const { code: code2, reason } = pocketCarryReason(carry);
114080
114087
  if (CARRY_WAIT_CODES.has(code2)) {
114081
- return ` Your pocket accounts stay in the old pool's records for now (${reason}). The money in them is safe. The wallet tries again by itself in a minute.`;
114088
+ return ` Your pockets stay in the old pool's records for now (${reason}). The money in them is safe. The wallet tries again by itself in a minute.`;
114082
114089
  }
114083
- return ` Your pocket accounts stay in the old pool's records (${reason}). The money in them is safe. Tell the operator this code: ${code2}. The wallet tries again by itself every 6 hours.`;
114090
+ return ` Your pockets stay in the old pool's records (${reason}). The money in them is safe. Tell the operator this code: ${code2}. The wallet tries again by itself every 6 hours.`;
114084
114091
  }
114085
114092
  function isPermanentSuccessionCode(code2) {
114086
114093
  const text3 = String(code2 ?? "");
@@ -120461,7 +120468,7 @@ async function readJson3(req) {
120461
120468
  if (value && typeof value === "object" && !Array.isArray(value)) return value;
120462
120469
  } catch {
120463
120470
  }
120464
- throw refusal2("inference_messages_invalid", 'the request body must be one JSON object, like {"model":"deepseek-v4-1-flash","max_tokens":512,"messages":[{"role":"user","content":"hi"}]}');
120471
+ throw refusal2("inference_messages_invalid", `the request body must be one JSON object, like {"model":"${DEFAULT_MODEL}","max_tokens":${DEFAULT_MAX_TOKENS},"messages":[{"role":"user","content":"hi"}]}`);
120465
120472
  }
120466
120473
  function usageOf(usage2) {
120467
120474
  if (!usage2 || typeof usage2 !== "object") return void 0;
@@ -120472,8 +120479,9 @@ function usageOf(usage2) {
120472
120479
  function defaultMaxTokensLine({ inference, maxPaymentAtomic }) {
120473
120480
  const floor = BigInt(inference.USEPOD_MIN_PAYMENT_ATOMIC ?? 0n);
120474
120481
  const prices = inference.listModels().map((model) => {
120475
- const output2 = (BigInt(model.priceOutPerMAtomic) * BigInt(DEFAULT_MAX_TOKENS) + 999999n) / 1000000n;
120476
- return `${usdcWords(output2 > floor ? output2 : floor)} on ${model.id}`;
120482
+ const tokens = defaultMaxTokens(model.id);
120483
+ const output2 = (BigInt(model.priceOutPerMAtomic) * BigInt(tokens) + 999999n) / 1000000n;
120484
+ return `${usdcWords(output2 > floor ? output2 : floor)} on ${model.id}${tokens === DEFAULT_MAX_TOKENS ? "" : ` (max_tokens ${tokens})`}`;
120477
120485
  });
120478
120486
  return `a request without max_tokens gets max_tokens ${DEFAULT_MAX_TOKENS}: the AI provider's price for it is at most ${prices.join(", ")}, plus the prompt at the model's input price and the Shinjuku fee, and never more than this server's limit per request (${usdcWords(maxPaymentAtomic)})`;
120479
120487
  }
@@ -120555,10 +120563,10 @@ function createInferenceServer({
120555
120563
  const given = body.max_tokens ?? body.max_completion_tokens;
120556
120564
  const maxTokensDefault = given === void 0 || given === null;
120557
120565
  if (!maxTokensDefault && (!Number.isSafeInteger(given) || given < 1)) {
120558
- throw refusal2("inference_max_tokens_required", `max_tokens must be a whole number of at least 1, or left out for the default ${DEFAULT_MAX_TOKENS}: it sets the ceiling price this request may pay; nothing was paid. Example: "max_tokens": 512`, "max_tokens");
120566
+ throw refusal2("inference_max_tokens_required", `max_tokens must be a whole number of at least 1, or left out for the default ${defaultMaxTokens(body.model)}: it sets the ceiling price this request may pay; nothing was paid. Example: "max_tokens": 512`, "max_tokens");
120559
120567
  }
120560
120568
  const raisedFrom = !maxTokensDefault && given < MIN_MAX_TOKENS ? given : void 0;
120561
- const maxTokens = maxTokensDefault ? DEFAULT_MAX_TOKENS : raisedFrom === void 0 ? given : MIN_MAX_TOKENS;
120569
+ const maxTokens = maxTokensDefault ? defaultMaxTokens(body.model) : raisedFrom === void 0 ? given : MIN_MAX_TOKENS;
120562
120570
  if (typeof body.model !== "string" || !body.model) {
120563
120571
  const ids = inference.listModels().map((model) => model.id).join(", ");
120564
120572
  throw refusal2("inference_model_unknown", `model is required; pick one of: ${ids} (GET /v1/models lists them with prices)`, "model");
@@ -121409,7 +121417,7 @@ function capsLine(caps) {
121409
121417
  }
121410
121418
  function costLine(caps) {
121411
121419
  const payment = usdc2(caps?.maxPaymentAtomic);
121412
- return `Each answer the app asks for is paid from the private balance: the AI provider's price for the request's max_tokens, up front, plus the 0.001 USDC Shinjuku fee${payment === void 0 ? "" : `, never more than ${payment} USDC a call`}. What the answer does not use is not refunded when it is shorter. A request without max_tokens gets ${DEFAULT_MAX_TOKENS}; one under ${MIN_MAX_TOKENS} is raised to ${MIN_MAX_TOKENS}, so no answer is paid and empty. A request with stream: true works: the answer is paid whole, then arrives at once, at the end.`;
121420
+ return `Each answer the app asks for is paid from the private balance: the AI provider's price for the request's max_tokens, up front, plus the 0.001 USDC Shinjuku fee${payment === void 0 ? "" : `, never more than ${payment} USDC a call`}. What the answer does not use is not refunded when it is shorter. A request without max_tokens gets ${DEFAULT_MAX_TOKENS} (512 for claude-opus-5); one under ${MIN_MAX_TOKENS} is raised to ${MIN_MAX_TOKENS}, so no answer is paid and empty. A request with stream: true works: the answer is paid whole, then arrives at once, at the end.`;
121413
121421
  }
121414
121422
  function planCaps(planned, launch, given) {
121415
121423
  const caps = { source: {} };
@@ -121694,7 +121702,7 @@ function createLocalAiTool({
121694
121702
  return {
121695
121703
  name: LOCAL_AI_TOOL_NAME,
121696
121704
  title: "Private local AI address for agent apps",
121697
- description: `Use this when the user wants a private AI address for another agent app: an OpenAI-compatible base_url and api key on this machine (${localAiBaseUrl()}). action "start" (the default) runs it in the background and answers the lines to paste into the app: OPENAI_BASE_URL, OPENAI_API_KEY and the model. It keeps running after this app or terminal closes, until action "stop" (or ${terminalCommand3(STOP_WORDS)} in a terminal) or a computer restart. action "status" says whether it runs, its caps, and what it paid. COST: each answer the app asks for is paid from the private balance: the AI provider's price for the request's max_tokens, up front (${DEFAULT_MAX_TOKENS} when the request names none), plus the 0.001 USDC Shinjuku fee; the unused rest is NOT refunded. Caps: the lowest of setup's caps (else 0.05 USDC a call and 1 USDC per server run) and this app's own spending limit; max_payment and max_session (USDC, like "0.02") can only lower them. PRIVACY: it answers only on 127.0.0.1 with its api key, keeps no prompt or answer on disk, and reaches the AI provider through Tor: the AI provider and the company that runs the model see the words, not this machine's IP. This call pays nothing itself. For one answer inside this chat, use inference_chat instead.`,
121705
+ description: `Use this when the user wants a private AI address for another agent app: an OpenAI-compatible base_url and api key on this machine (${localAiBaseUrl()}). action "start" (the default) runs it in the background and answers the lines to paste into the app: OPENAI_BASE_URL, OPENAI_API_KEY and the model. It keeps running after this app or terminal closes, until action "stop" (or ${terminalCommand3(STOP_WORDS)} in a terminal) or a computer restart. action "status" says whether it runs, its caps, and what it paid. COST: each answer the app asks for is paid from the private balance: the AI provider's price for the request's max_tokens, up front (${DEFAULT_MAX_TOKENS} when the request names none, 512 for claude-opus-5), plus the 0.001 USDC Shinjuku fee; the unused rest is NOT refunded. Caps: the lowest of setup's caps (else 0.05 USDC a call and 1 USDC per server run) and this app's own spending limit; max_payment and max_session (USDC, like "0.02") can only lower them. PRIVACY: it answers only on 127.0.0.1 with its api key, keeps no prompt or answer on disk, and reaches the AI provider through Tor: the AI provider and the company that runs the model see the words, not this machine's IP. This call pays nothing itself. For one answer inside this chat, use inference_chat instead.`,
121698
121706
  inputSchema: {
121699
121707
  type: "object",
121700
121708
  properties: {
@@ -124763,13 +124771,13 @@ function checkMessages(messages) {
124763
124771
  return { role: message.role, content: message.content };
124764
124772
  });
124765
124773
  }
124766
- function checkMaxTokens(maxTokens) {
124767
- if (maxTokens === void 0 || maxTokens === null) return BigInt(DEFAULT_MAX_TOKENS);
124774
+ function checkMaxTokens(maxTokens, model) {
124775
+ if (maxTokens === void 0 || maxTokens === null) return BigInt(defaultMaxTokens(model));
124768
124776
  if (!Number.isSafeInteger(maxTokens) || maxTokens < 1) {
124769
124777
  fail39(
124770
124778
  "inference_max_tokens_required",
124771
- `max_tokens must be a whole number of at least 1, or left out for the default ${DEFAULT_MAX_TOKENS}: it sets the most the call can cost; nothing was paid`,
124772
- { next: `pass max_tokens 512 for a short answer or 1024 for a long one (256 or more: a reasoning model thinks first), or leave it out for ${DEFAULT_MAX_TOKENS}` }
124779
+ `max_tokens must be a whole number of at least 1, or left out for the default ${defaultMaxTokens(model)}: it sets the most the call can cost; nothing was paid`,
124780
+ { next: `pass max_tokens 512 for a short answer or ${DEFAULT_MAX_TOKENS} for a long one (256 or more: a reasoning model thinks first), or leave it out for ${defaultMaxTokens(model)}` }
124773
124781
  );
124774
124782
  }
124775
124783
  return BigInt(maxTokens);
@@ -124787,7 +124795,7 @@ function ceilingOf(entry, messages, maxTokens) {
124787
124795
  }
124788
124796
  function ceilingAtomic({ model, messages, maxTokens }) {
124789
124797
  const entry = findModel(model);
124790
- return ceilingOf(entry, checkMessages(messages), checkMaxTokens(maxTokens));
124798
+ return ceilingOf(entry, checkMessages(messages), checkMaxTokens(maxTokens, entry.id));
124791
124799
  }
124792
124800
  function buildChatRequest({ model, messages, maxTokens, temperature, stream }) {
124793
124801
  if (stream === true) {
@@ -124799,7 +124807,7 @@ function buildChatRequest({ model, messages, maxTokens, temperature, stream }) {
124799
124807
  }
124800
124808
  const entry = findModel(model);
124801
124809
  const clean5 = checkMessages(messages);
124802
- const tokens = checkMaxTokens(maxTokens);
124810
+ const tokens = checkMaxTokens(maxTokens, entry.id);
124803
124811
  if (temperature !== void 0 && (typeof temperature !== "number" || !Number.isFinite(temperature) || temperature < 0 || temperature > 2)) {
124804
124812
  fail39("inference_temperature_invalid", "temperature must be a number from 0 to 2; nothing was paid", { next: "pass temperature 0.7, or leave it out" });
124805
124813
  }
@@ -124902,7 +124910,7 @@ async function inferenceChat({ payFetch, model, messages, maxTokens, temperature
124902
124910
  const { url, init } = buildChatRequest({ model, messages, maxTokens, temperature });
124903
124911
  const entry = findModel(model);
124904
124912
  const clean5 = checkMessages(messages);
124905
- const tokens = checkMaxTokens(maxTokens);
124913
+ const tokens = checkMaxTokens(maxTokens, entry.id);
124906
124914
  const ceiling = ceilingOf(entry, clean5, tokens);
124907
124915
  const cap = parseCap(maxPaymentAtomic);
124908
124916
  if (ceiling > cap) {
@@ -130907,7 +130915,7 @@ ${canonical5}`;
130907
130915
  store: { readWal: (name) => store.readWal(name), writeWal: (name, value) => store.writeWal(name, value) },
130908
130916
  plan,
130909
130917
  // No time in the stage: it reaches the host's log, and a part's time would point at its exit on chain. The answer has it.
130910
- onWait: ({ part }) => onStage(`part ${part.index}/${plan.parts.length}: waiting`),
130918
+ onWait: ({ part }) => onStage(`payment ${part.index}/${plan.parts.length}: waiting`),
130911
130919
  // A late part's new time is a plan write: under the lease, so a cancel written meanwhile is kept.
130912
130920
  reschedule: (part, notBefore) => underLease((opened) => {
130913
130921
  store = opened;
@@ -130920,7 +130928,7 @@ ${canonical5}`;
130920
130928
  store = opened;
130921
130929
  if (partCancelled(opened, sendId, part)) return { cancelled: true };
130922
130930
  if (partState(opened, part).state === "exited") return {};
130923
- const result = await unshieldPart(opened, job, part.unshieldId, part.amountAtomic, (stage) => onStage(`part ${index}/${total}: ${stage}`));
130931
+ const result = await unshieldPart(opened, job, part.unshieldId, part.amountAtomic, (stage) => onStage(`payment ${index}/${total}: ${stage}`));
130924
130932
  if (result.backupPath !== void 0) backup = { backupPath: result.backupPath };
130925
130933
  if (result.backupError !== void 0) backup = { ...backup, backupError: result.backupError };
130926
130934
  store = session.openStore();
@@ -132906,7 +132914,7 @@ ${canonical5}`;
132906
132914
  const inferenceModule = async () => ops.inference ?? await Promise.resolve().then(() => (init_inference(), inference_exports));
132907
132915
  const proxied = () => typeof ops.proxied === "function" ? ops.proxied() : proxyProvenAt() !== void 0;
132908
132916
  const usd = (atomic4) => `$${formatAtomic(atomic4, 6)}`;
132909
- const INFERENCE_COST_WORDS = "A short deepseek-v4-1-flash answer (the default) paid 0.001047 USDC live: the AI provider's price (often 0.00001-0.0001 USDC) plus the Shinjuku fee (0.001 USDC). The call's cap (max_payment, else the per-payment limit) must still cover the call's ceiling: max_tokens at the model's output price plus the prompt, and at least 0.01 USDC";
132917
+ const INFERENCE_COST_WORDS = "A qwen3-8-flash answer (the default, 2048 tokens) paid 0.002175 USDC live: the AI provider's price plus the Shinjuku fee (0.001 USDC). The call's cap (max_payment, else the per-payment limit) must still cover the call's ceiling: max_tokens at the model's output price plus the prompt, and at least 0.01 USDC";
132910
132918
  const paysUpToAtomic = (inference, asked) => {
132911
132919
  try {
132912
132920
  const extra = inference.listModels().find((model) => model.id === asked.model).priceOutPerMAtomic * 100n;
@@ -132960,7 +132968,7 @@ ${canonical5}`;
132960
132968
  ],
132961
132969
  inference_model_unknown: [
132962
132970
  "that model id is not one the AI provider serves through this wallet",
132963
- "call inference_chat with no prompt or messages (it lists the model ids, free), then pass one of its ids, for example deepseek-v4-1-flash or claude-opus-5"
132971
+ "call inference_chat with no prompt or messages (it lists the model ids, free), then pass one of its ids, for example qwen3-8-flash or claude-opus-5"
132964
132972
  ],
132965
132973
  inference_messages_invalid: [
132966
132974
  "messages must be a non-empty list of { role, content } with role system, user, or assistant and content text",
@@ -133005,11 +133013,11 @@ ${canonical5}`;
133005
133013
  const inferenceChat2 = {
133006
133014
  name: "inference_chat",
133007
133015
  title: "Ask an AI model, paid privately",
133008
- description: `Use this when the user wants an AI model's answer paid from the PRIVATE balance: it calls the AI provider's x402 chat endpoint (the AI provider picks the model's provider) and pays it privately (the chain does not show which wallet paid); use this tool, not x402_pay, for AI answers. Only prompt (or messages) is needed: model defaults to deepseek-v4-1-flash and max_tokens to 512. Called with neither prompt nor messages, it lists the models and their prices and pays nothing (for example deepseek-v4-1-flash, qwen3-8-flash, claude-opus-5). COST: a short deepseek-v4-1-flash answer paid 0.001047 USDC live (the AI provider's price, often 0.00001-0.0001 USDC, plus the Shinjuku fee 0.001 USDC). The call's cap (max_payment, in USDC like "0.05", or max_payment_atomic; else the per-payment limit) must still cover the call's ceiling: max_tokens of output plus the prompt at the model's prices, and at least 0.01 USDC (the AI provider's floor for its standard offer). The total paid is never above the cap, and the cap is only a limit: a larger cap never pays more. What max_tokens does not use is NOT refunded when the answer is shorter. max_tokens must be at least ${INFERENCE_MIN_MAX_TOKENS} (a reasoning model spends its first tokens thinking). Example: { "prompt": "Name three prime numbers" }. PRIVACY: the AI provider and the company that runs the model see the prompt and the answer; never put keys, passphrases, or secrets in it. It runs only when this wallet runs over Tor or its own proxy (else inference_tor_required, nothing paid), so they never see this machine's IP. The same caps as x402_pay apply (max_payment can only lower the per-payment limit). When the call can cost more than that limit and the MCP client can ask the user, the wallet asks the user once to allow this one call; else the answer says the cost and the limit. On an error code that ends in _retry_same_request, call inference_chat again with the SAME request_id and arguments: it never pays twice. No streaming.`,
133016
+ description: `Use this when the user wants an AI model's answer paid from the PRIVATE balance: it calls the AI provider's x402 chat endpoint (the AI provider picks the model's provider) and pays it privately (the chain does not show which wallet paid); use this tool, not x402_pay, for AI answers. Only prompt (or messages) is needed: model defaults to qwen3-8-flash and max_tokens to 2048 (512 for claude-opus-5). Called with neither prompt nor messages, it lists the models and their prices and pays nothing (for example qwen3-8-flash, deepseek-v4-1-flash, claude-opus-5). COST: a qwen3-8-flash answer at 2048 tokens paid 0.002175 USDC live (the AI provider's price plus the Shinjuku fee 0.001 USDC). The call's cap (max_payment, in USDC like "0.05", or max_payment_atomic; else the per-payment limit) must still cover the call's ceiling: max_tokens of output plus the prompt at the model's prices, and at least 0.01 USDC (the AI provider's floor for its standard offer). The total paid is never above the cap, and the cap is only a limit: a larger cap never pays more. What max_tokens does not use is NOT refunded when the answer is shorter. max_tokens must be at least ${INFERENCE_MIN_MAX_TOKENS} (a reasoning model spends its first tokens thinking). Example: { "prompt": "Name three prime numbers" }. PRIVACY: the AI provider and the company that runs the model see the prompt and the answer; never put keys, passphrases, or secrets in it. It runs only when this wallet runs over Tor or its own proxy (else inference_tor_required, nothing paid), so they never see this machine's IP. The same caps as x402_pay apply (max_payment can only lower the per-payment limit). When the call can cost more than that limit and the MCP client can ask the user, the wallet asks the user once to allow this one call; else the answer says the cost and the limit. On an error code that ends in _retry_same_request, call inference_chat again with the SAME request_id and arguments: it never pays twice. No streaming.`,
133009
133017
  inputSchema: {
133010
133018
  type: "object",
133011
133019
  properties: {
133012
- model: { type: "string", description: "Optional, default deepseek-v4-1-flash. A model id (inference_chat with no prompt lists them)." },
133020
+ model: { type: "string", description: "Optional, default qwen3-8-flash. A model id (inference_chat with no prompt lists them)." },
133013
133021
  messages: { type: "array", items: {
133014
133022
  type: "object",
133015
133023
  properties: { role: { type: "string", enum: ["system", "user", "assistant"] }, content: { type: "string" } },
@@ -133017,7 +133025,7 @@ ${canonical5}`;
133017
133025
  additionalProperties: false
133018
133026
  }, description: "The conversation, oldest first. Or pass prompt instead." },
133019
133027
  prompt: { type: "string", description: "One user message (instead of messages)." },
133020
- max_tokens: { type: "integer", minimum: 1, description: `Optional, default 512, at least ${INFERENCE_MIN_MAX_TOKENS}. The most output tokens; the call pays for all of them up front. A reasoning model spends its first tokens thinking, so fewer can end with no answer.` },
133028
+ max_tokens: { type: "integer", minimum: 1, description: `Optional, default 2048 (512 for claude-opus-5), at least ${INFERENCE_MIN_MAX_TOKENS}. The most output tokens; the call pays for all of them up front. A reasoning model spends its first tokens thinking, so fewer can end with no answer.` },
133021
133029
  temperature: { type: "number", minimum: 0, maximum: 2, description: "Sampling temperature; leave it out for the model's default." },
133022
133030
  max_payment: { type: "string", description: 'Optional: the most this call may pay, in USDC, like "0.05"; at most the spending limit.' },
133023
133031
  max_payment_atomic: { type: "string", pattern: "^[1-9][0-9]*$", description: "The most this call may pay, in atomic units; at most the spending limit." },
@@ -133040,10 +133048,11 @@ ${canonical5}`;
133040
133048
  ...body,
133041
133049
  paid: false,
133042
133050
  detail: "no prompt or messages were given, so nothing was sent or paid; these are the models and prices",
133043
- next: body.ready ? "call inference_chat with prompt (model defaults to deepseek-v4-1-flash, max_tokens to 512)" : body.next
133051
+ next: body.ready ? "call inference_chat with prompt (model defaults to qwen3-8-flash, max_tokens to 2048)" : body.next
133044
133052
  });
133045
133053
  }
133046
- const args = { ...rawArgs, model: rawArgs.model ?? DEFAULT_MODEL, max_tokens: rawArgs.max_tokens ?? 512 };
133054
+ const model = rawArgs.model ?? DEFAULT_MODEL;
133055
+ const args = { ...rawArgs, model, max_tokens: rawArgs.max_tokens ?? defaultMaxTokens(model) };
133047
133056
  if (!proxied()) return torRefusal();
133048
133057
  const inference = await inferenceModule();
133049
133058
  if (args.prompt !== void 0 && args.messages !== void 0) {
@@ -133053,7 +133062,7 @@ ${canonical5}`;
133053
133062
  return refuse12(
133054
133063
  "inference_max_tokens_too_low",
133055
133064
  `max_tokens ${args.max_tokens} is below ${INFERENCE_MIN_MAX_TOKENS}: a reasoning model (qwen3-8-flash) spends its first tokens thinking, and with max_tokens 32 or 80 it was paid and gave no answer; nothing was paid`,
133056
- `call inference_chat again with max_tokens ${INFERENCE_MIN_MAX_TOKENS} or more (512, the default, answers a short question), or leave max_tokens out; a larger max_tokens costs little more: a short deepseek-v4-1-flash answer paid 0.001047 USDC`,
133065
+ `call inference_chat again with max_tokens ${INFERENCE_MIN_MAX_TOKENS} or more (512 answers a short question), or leave max_tokens out; a larger max_tokens costs little more: a qwen3-8-flash answer at 2048 tokens paid 0.002175 USDC`,
133057
133066
  { paid: false, maxTokens: args.max_tokens, minMaxTokens: INFERENCE_MIN_MAX_TOKENS }
133058
133067
  );
133059
133068
  }
@@ -136046,7 +136055,8 @@ __export(chat_exports, {
136046
136055
  isProgressLine: () => isProgressLine,
136047
136056
  knownPrivateBalance: () => knownPrivateBalance,
136048
136057
  leastAnswerAtomic: () => leastAnswerAtomic,
136049
- runChat: () => runChat
136058
+ runChat: () => runChat,
136059
+ savedBalance: () => savedBalance
136050
136060
  });
136051
136061
  import { randomBytes as randomBytes27 } from "node:crypto";
136052
136062
  import { createInterface } from "node:readline";
@@ -136067,16 +136077,16 @@ function answerCostWords(entry, maxTokens) {
136067
136077
  const most = leastAnswerAtomic(entry, maxTokens);
136068
136078
  return `up to about ${usdcWords((most + 99n) / 100n * 100n)}`;
136069
136079
  }
136070
- function balanceLine2(store) {
136080
+ function savedBalance(store) {
136071
136081
  try {
136072
136082
  const report = computeBalance({ store, snapshotRead: readBalanceSnapshot(store), source: "local" });
136073
136083
  const value = report.split?.shieldedAtomic;
136074
136084
  if (typeof value === "string" && /^[0-9]{1,31}$/.test(value)) {
136075
- return report.status === "current" || report.status === "empty" ? `Your private balance is ${usdcWords(value)}.` : `Your private balance was ${usdcWords(value)} at its last read; a payment or a deposit since then is not in this number. ${commandLine("balance")} in another terminal reads it again.`;
136085
+ return { atomic: BigInt(value), current: (report.status === "current" || report.status === "empty") && !report.detail?.pendingDeposits?.length };
136076
136086
  }
136077
136087
  } catch {
136078
136088
  }
136079
- return `The private balance is not known in this chat yet. ${commandLine("balance")} in another terminal shows it.`;
136089
+ return void 0;
136080
136090
  }
136081
136091
  function httpStatusOf(error) {
136082
136092
  for (let item = error, depth = 0; item && depth < 4; item = item.cause, depth += 1) {
@@ -136221,6 +136231,18 @@ async function runChat({ io, values, deps = {} }) {
136221
136231
  const state = { model, history: [], spent: 0n, answers: 0, empty: 0, pending: void 0, busy: false, interrupts: 0 };
136222
136232
  const say6 = (line) => io.err(`${line}
136223
136233
  `);
136234
+ const inPlace = deps.statusInPlace ?? Boolean(process.stderr.isTTY);
136235
+ let lastKnown;
136236
+ const readBalance = () => {
136237
+ let saved;
136238
+ try {
136239
+ saved = savedBalance(session.openStore());
136240
+ } catch {
136241
+ saved = void 0;
136242
+ }
136243
+ if (saved?.current) lastKnown = { atomic: saved.atomic, spentThen: state.spent };
136244
+ return saved;
136245
+ };
136224
136246
  const entryOf = (id) => models.find((entry) => entry.id === id);
136225
136247
  const tokensFor = (id, messages, limit) => {
136226
136248
  try {
@@ -136229,17 +136251,21 @@ async function runChat({ io, values, deps = {} }) {
136229
136251
  return CHAT_SHORT_TOKENS;
136230
136252
  }
136231
136253
  };
136254
+ const costWords = (id) => {
136255
+ const tokens = tokensFor(id, [{ role: "user", content: "hi" }], caps.answer);
136256
+ if (id === DEFAULT_MODEL && tokens === CHAT_MAX_TOKENS) return `about ${DEFAULT_MODEL_PAID_WORDS}, as paid in a live test`;
136257
+ return answerCostWords(entryOf(id), tokens);
136258
+ };
136232
136259
  const costLine2 = (id) => {
136233
136260
  try {
136234
- const tokens = tokensFor(id, [{ role: "user", content: "hi" }], caps.answer);
136235
- return `A short answer costs ${answerCostWords(entryOf(id), tokens)}`;
136261
+ return `An answer costs ${costWords(id)}`;
136236
136262
  } catch {
136237
136263
  return "Each answer is paid";
136238
136264
  }
136239
136265
  };
136240
136266
  const questionsWords = () => {
136241
136267
  const asked = state.answers + state.empty;
136242
- return `${asked} question${asked === 1 ? "" : "s"}${state.empty ? ` (${state.empty} came back empty)` : ""}`;
136268
+ return `${asked} question${asked === 1 ? "" : "s"}${state.empty ? ` (${state.empty} paid, no answer)` : ""}`;
136243
136269
  };
136244
136270
  const left = () => caps.chat > state.spent ? caps.chat - state.spent : 0n;
136245
136271
  const capsLine2 = () => `Caps: ${usdcWords(caps.answer)} an answer and ${usdcWords(caps.chat)} for this chat (${usdcWords(left())} left).`;
@@ -136249,7 +136275,7 @@ async function runChat({ io, values, deps = {} }) {
136249
136275
  "Memory only: the chat writes no chat file and no log, and the wallet writes no copy of the conversation, not even encrypted: its payment records keep only the amounts. The conversation is gone when the chat ends. Let an answer arrive before you end the chat: if the chat ends while an answer is on its way, that answer is gone, and a payment already sent may not come back.",
136250
136276
  `Cost: each answer pays the AI provider's price for up to ${CHAT_MAX_TOKENS} tokens (${CHAT_SHORT_TOKENS} for a model whose longer answer is above your cap) plus the 0.001 USDC Shinjuku fee, up front; what an answer does not use is not refunded. ${costLine2(state.model)} with ${state.model}. Each question sends the whole conversation, so a long chat costs more (/new starts again).`,
136251
136277
  `Caps: ${usdcWords(caps.answer)} an answer and ${usdcWords(caps.chat)} for this chat${capsSourceText(caps)}`,
136252
- `Commands: /new starts a new conversation, /cost shows what this chat paid, /balance shows your private balance, /model <id> changes the model (${modelIds2}), /help shows this, /exit (or Ctrl+D) ends the chat${state.pending ? ", /retry finishes the unfinished answer" : ""}.`
136278
+ `Commands: /new starts a new conversation, /cost shows what this chat paid, /balance shows your private balance, /model <id> changes the model (${modelIds2}), /help shows this, /exit (or /quit, Ctrl+D) ends the chat${state.pending ? ", /retry finishes the unfinished answer" : ""}.`
136253
136279
  ].join("\n");
136254
136280
  let reader;
136255
136281
  const onInterrupt = () => {
@@ -136276,7 +136302,8 @@ async function runChat({ io, values, deps = {} }) {
136276
136302
  let held;
136277
136303
  try {
136278
136304
  least = leastAnswerAtomic(entryOf(state.model), CHAT_SHORT_TOKENS);
136279
- held = knownPrivateBalance(session.openStore());
136305
+ const saved = readBalance();
136306
+ held = saved?.current ? saved.atomic : void 0;
136280
136307
  } catch {
136281
136308
  held = void 0;
136282
136309
  }
@@ -136286,11 +136313,8 @@ async function runChat({ io, values, deps = {} }) {
136286
136313
  const runTurn = async (turn) => {
136287
136314
  if (turn.capAtomic === void 0) {
136288
136315
  let limit = caps.answer < left() ? caps.answer : left();
136289
- try {
136290
- const held2 = knownPrivateBalance(session.openStore());
136291
- if (held2 !== void 0 && held2 < limit) limit = held2;
136292
- } catch {
136293
- }
136316
+ const saved = readBalance();
136317
+ if (saved?.current && saved.atomic < limit) limit = saved.atomic;
136294
136318
  turn.maxTokens = tokensFor(turn.model, turn.messages, limit);
136295
136319
  let ceiling;
136296
136320
  try {
@@ -136310,17 +136334,33 @@ async function runChat({ io, values, deps = {} }) {
136310
136334
  }
136311
136335
  turn.capAtomic = caps.answer < room ? caps.answer : room;
136312
136336
  }
136313
- say6(`(asking ${turn.model} privately over Tor; this takes a few seconds)`);
136314
136337
  state.busy = true;
136315
136338
  const startedAt = Date.now();
136316
136339
  let lastLineAt = startedAt;
136340
+ let resent = false;
136317
136341
  const seconds2 = () => Math.round((Date.now() - startedAt) / 1e3);
136342
+ const status = () => {
136343
+ const text3 = `(asking ${turn.model} privately over Tor: ${seconds2()} s)`;
136344
+ io.err(`\r\x1B[K${text3.slice(0, Math.max(20, (process.stderr.columns ?? 80) - 1))}`);
136345
+ };
136346
+ const clearStatus = () => {
136347
+ if (inPlace) io.err("\r\x1B[K");
136348
+ };
136349
+ if (inPlace) status();
136350
+ else say6(`(asking ${turn.model} privately over Tor)`);
136318
136351
  onPhase = (phase) => {
136319
- if (phase !== "seller-resend-unanswered") return;
136352
+ if (phase !== "seller-resend-unanswered" || resent) return;
136353
+ resent = true;
136320
136354
  lastLineAt = Date.now();
136321
- say6(`(the AI provider has not answered yet; asking it again with the same payment, ${seconds2()} s so far: it never pays twice)`);
136355
+ clearStatus();
136356
+ say6("(the AI provider is slow to answer: the wallet asks it again with the same payment, which never pays twice)");
136357
+ if (inPlace) status();
136322
136358
  };
136323
136359
  const waiting = setInterval(() => {
136360
+ if (inPlace) {
136361
+ status();
136362
+ return;
136363
+ }
136324
136364
  if (Date.now() - lastLineAt < STILL_WAITING_MS) return;
136325
136365
  lastLineAt = Date.now();
136326
136366
  say6(`(still waiting for the answer, ${seconds2()} s so far)`);
@@ -136337,10 +136377,13 @@ async function runChat({ io, values, deps = {} }) {
136337
136377
  requestId: turn.requestId
136338
136378
  });
136339
136379
  } catch (error) {
136380
+ clearInterval(waiting);
136381
+ clearStatus();
136340
136382
  failed(error, turn);
136341
136383
  return;
136342
136384
  } finally {
136343
136385
  clearInterval(waiting);
136386
+ clearStatus();
136344
136387
  onPhase = void 0;
136345
136388
  state.busy = false;
136346
136389
  state.interrupts = 0;
@@ -136385,17 +136428,29 @@ async function runChat({ io, values, deps = {} }) {
136385
136428
  say6("Nothing was paid. The AI provider now asks a kind of payment whose record would keep your words, and a private chat never pays that way. Ask again; if this repeats, the AI provider changed how it takes payment.");
136386
136429
  return;
136387
136430
  }
136431
+ const status = httpStatusOf(error);
136432
+ if (providerKept(code2, status)) {
136433
+ const kept2 = /^[1-9][0-9]{0,30}$/.test(String(error?.paidAtomic ?? "")) ? BigInt(error.paidAtomic) : void 0;
136434
+ if (kept2 !== void 0) state.spent += kept2;
136435
+ state.empty += 1;
136436
+ const what = `answered with an error (HTTP ${status})`;
136437
+ const others = models.filter((entry) => entry.id !== turn.model).map((entry) => entry.id).join(", ");
136438
+ say6(`Your payment${kept2 === void 0 ? "" : ` (${usdcWords(kept2)})`} went to the AI provider, but it ${what} and sent no answer. That payment does not come back. Ask again (a new question pays again), or pick another model with /model (${others}). This chat paid ${usdcWords(state.spent)} so far.`);
136439
+ return;
136440
+ }
136441
+ if (INSUFFICIENT2.test(code2 ?? "")) {
136442
+ state.pending = turn;
136443
+ say6(`Your private balance is too low for this answer. Nothing was paid. Add funds in another terminal: ${commandLine("add-funds")} (it asks the amount, from 0.01 USDC), or ask your agent to add funds. When it says ready, type /retry: it asks the same question again.`);
136444
+ return;
136445
+ }
136388
136446
  const again = surelyNothing ? "ask again" : "type /retry (it finishes the same payment and never pays twice)";
136389
136447
  if (!surelyNothing) state.pending = turn;
136390
136448
  const paidWords = surelyNothing ? "Nothing was paid." : "It may be paid already.";
136391
136449
  if (/seller_unanswered_paid/.test(code2 ?? "")) {
136392
- const status = httpStatusOf(error);
136393
- const what = status >= 400 ? `answered with an error (HTTP ${status}) and no answer` : status === 202 ? "has not sent the answer yet" : "did not answer";
136450
+ const what = status === 202 ? "has not sent the answer yet" : "did not answer";
136394
136451
  say6(`Your payment reached the AI provider, but it ${what}. Type /retry: it asks again with the same payment and never pays twice.`);
136395
136452
  } else if (code2 === "inference_tor_required" || code2 === "ECONNREFUSED" || proxyRefusedConnection(error)) {
136396
136453
  say6(`Your private connection (Tor) stopped answering, so the question could not go out. ${paidWords} Start Tor again, then ${again}.`);
136397
- } else if (INSUFFICIENT2.test(code2 ?? "")) {
136398
- say6(`Your private balance is too low for this answer. ${paidWords} Add funds in another terminal: ${commandLine("add-funds")} (it asks the amount, from 0.01 USDC), or ask your agent to add funds. Then ${again}.`);
136399
136454
  } else if (CAP_CODES.test(code2 ?? "")) {
136400
136455
  say6(`This answer costs more than the chat's caps allow. ${paidWords} ${capsLine2()} End the chat (/exit) and start it again with higher caps when you want more: ${commandLine(`chat --max-payment ${higherCap(caps.answer)} --max-session ${higherCap(caps.chat)}`)}`);
136401
136456
  } else if (code2 === "inference_no_provider") {
@@ -136411,7 +136466,7 @@ async function runChat({ io, values, deps = {} }) {
136411
136466
  const command2 = async (text3) => {
136412
136467
  const [name, ...rest] = text3.split(/\s+/);
136413
136468
  const arg = rest.join(" ").trim();
136414
- if (name === "/exit") return true;
136469
+ if (name === "/exit" || name === "/quit" || name === "/q") return true;
136415
136470
  if (name === "/new") {
136416
136471
  state.history = [];
136417
136472
  state.pending = void 0;
@@ -136420,13 +136475,19 @@ async function runChat({ io, values, deps = {} }) {
136420
136475
  } else if (name === "/cost") {
136421
136476
  say6(`This chat paid ${usdcWords(state.spent)} for ${questionsWords()}. ${capsLine2()}`);
136422
136477
  } else if (name === "/balance") {
136423
- let line;
136424
- try {
136425
- line = balanceLine2(session.openStore());
136426
- } catch {
136427
- line = void 0;
136478
+ const saved = readBalance();
136479
+ const again = `A deposit since then is not in this number: ${commandLine("balance")} in another terminal reads it again.`;
136480
+ if (saved?.current) {
136481
+ say6(`Your private balance is ${usdcWords(saved.atomic)}.`);
136482
+ } else if (lastKnown) {
136483
+ const since = state.spent - lastKnown.spentThen;
136484
+ const now = lastKnown.atomic > since ? lastKnown.atomic - since : 0n;
136485
+ say6(`Your private balance is about ${usdcWords(now)}: ${usdcWords(lastKnown.atomic)} at its last read, less the ${usdcWords(since)} this chat paid since then. ${again}`);
136486
+ } else if (saved) {
136487
+ say6(`Your private balance was ${usdcWords(saved.atomic)} at its last read; this chat paid ${usdcWords(state.spent)} since it started. ${again}`);
136488
+ } else {
136489
+ say6(`The private balance is not known in this chat yet. ${commandLine("balance")} in another terminal shows it.`);
136428
136490
  }
136429
- say6(line ?? `The private balance is not known in this chat yet. ${commandLine("balance")} in another terminal shows it.`);
136430
136491
  } else if (name === "/model") {
136431
136492
  const found = models.find((entry) => entry.id === arg);
136432
136493
  if (found) {
@@ -136435,7 +136496,7 @@ async function runChat({ io, values, deps = {} }) {
136435
136496
  } else {
136436
136497
  say6([
136437
136498
  `${arg ? `There is no model ${JSON.stringify(arg).slice(0, 80)}. ` : ""}Model now: ${state.model}. Pick one with /model <id>:`,
136438
- ...models.map((entry) => ` ${entry.id}${entry.note ? ` ${entry.note}` : ""} (${costLine2(entry.id).replace(/^A short answer costs /, "a short answer ")})`)
136499
+ ...models.map((entry) => ` ${entry.id}${entry.note ? ` ${entry.note}` : ""} (${costLine2(entry.id).replace(/^An answer costs /, "an answer ")})`)
136439
136500
  ].join("\n"));
136440
136501
  }
136441
136502
  } else if (name === "/retry") {
@@ -136476,7 +136537,7 @@ async function runChat({ io, values, deps = {} }) {
136476
136537
  await session.close();
136477
136538
  }
136478
136539
  }
136479
- var DEFAULT_MODEL2, CHAT_DEFAULT_MODEL, CHAT_MAX_TOKENS, CHAT_SHORT_TOKENS, DEFAULT_ANSWER_CAP_ATOMIC, DEFAULT_CHAT_CAP_ATOMIC, CHEAPEST_MODEL, STILL_WAITING_MS, higherCap, POSITIVE_ATOMIC9, SHINJUKU_FEE_ATOMIC, SHORT_QUESTION_TOKENS, PASTE_JOIN_MS, INSUFFICIENT2, CAP_CODES, WAIT_CODES, NO_ANSWER_CODES, NOTHING_PAID, CAP_FROM, PROGRESS_LINE, SHOWN_PROGRESS, isProgressLine, fail46, nothingPaid;
136540
+ var DEFAULT_MODEL2, CHAT_DEFAULT_MODEL, CHAT_MAX_TOKENS, CHAT_SHORT_TOKENS, DEFAULT_ANSWER_CAP_ATOMIC, DEFAULT_CHAT_CAP_ATOMIC, CHEAPEST_MODEL, STILL_WAITING_MS, providerKept, higherCap, POSITIVE_ATOMIC9, SHINJUKU_FEE_ATOMIC, SHORT_QUESTION_TOKENS, PASTE_JOIN_MS, INSUFFICIENT2, CAP_CODES, WAIT_CODES, NO_ANSWER_CODES, NOTHING_PAID, CAP_FROM, PROGRESS_LINE, SHOWN_PROGRESS, SELF_PAY_LINE, isProgressLine, fail46, nothingPaid;
136480
136541
  var init_chat = __esm({
136481
136542
  "wallet/src/inference/chat.mjs"() {
136482
136543
  "use strict";
@@ -136499,6 +136560,7 @@ var init_chat = __esm({
136499
136560
  DEFAULT_CHAT_CAP_ATOMIC = 1000000n;
136500
136561
  CHEAPEST_MODEL = "deepseek-v4-1-flash";
136501
136562
  STILL_WAITING_MS = 2e4;
136563
+ providerKept = (code2, status) => code2 === "p03_float_seller_kept_payment" || /seller_unanswered_paid/.test(code2 ?? "") && Number.isInteger(status) && status >= 500;
136502
136564
  higherCap = (atomic4) => usdcFlagText((BigInt(atomic4) * 2n + 9999n) / 10000n * 10000n);
136503
136565
  POSITIVE_ATOMIC9 = /^[1-9][0-9]{0,30}$/;
136504
136566
  SHINJUKU_FEE_ATOMIC = 1000n;
@@ -136510,9 +136572,10 @@ var init_chat = __esm({
136510
136572
  NO_ANSWER_CODES = /^inference_(upstream_failed|response_invalid)$/;
136511
136573
  NOTHING_PAID = /\bnothing was (?:sent or )?paid\b|\bnothing was paid or signed\b/i;
136512
136574
  CAP_FROM = { line: "from this command line", setup: "set in setup", default: "the default" };
136513
- PROGRESS_LINE = /^(?:tor|rpc|history|memo service): |^reading \d+ memos? from the memo service|^reading your private balance again\b|^merge \S+: self-paying\b/;
136575
+ PROGRESS_LINE = /^(?:tor|rpc|history|memo service): |^reading \d+ memos? from the memo service|^reading your private balance again\b|^getting your balance ready: moving \S+ USDC of one deposit\b/;
136514
136576
  SHOWN_PROGRESS = new RegExp(`\\bWARNING\\b|\\bminutes?\\b|${TOR_NOTICE_LINE.source}`);
136515
- isProgressLine = (line) => PROGRESS_LINE.test(line) && !SHOWN_PROGRESS.test(line) && !TOR_TROUBLE_LINE.test(line);
136577
+ SELF_PAY_LINE = /^getting your balance ready: moving \S+ USDC of one deposit\b/;
136578
+ isProgressLine = (line) => PROGRESS_LINE.test(line) && (SELF_PAY_LINE.test(line) || !SHOWN_PROGRESS.test(line) && !TOR_TROUBLE_LINE.test(line));
136516
136579
  fail46 = (deps, message) => typeof deps.publicFailure === "function" ? deps.publicFailure(message) : new Error(message);
136517
136580
  nothingPaid = (error) => error?.name === "InferenceError" && !error.details?.receipt || error?.name === "SpendRefusalError" || NOTHING_PAID.test(String(error?.message ?? ""));
136518
136581
  }
@@ -157682,7 +157745,7 @@ async function main5(args = process.argv.slice(2), { output: output2 = (result)
157682
157745
  timing: createTimingLog(`${walletPath2}.timing.log`, { component: "payer" })
157683
157746
  };
157684
157747
  }
157685
- progress2(`merge ${id}: self-paying ${usdc9(amountAtomic)} USDC of one deposit into a note of this wallet (about 2 minutes; it waits for finality)`);
157748
+ progress2(`getting your balance ready: moving ${usdc9(amountAtomic)} USDC of one deposit inside your private balance (about 2 minutes; it waits for finality)`);
157686
157749
  try {
157687
157750
  const paid = await selfPay({
157688
157751
  store,
@@ -157714,7 +157777,7 @@ async function main5(args = process.argv.slice(2), { output: output2 = (result)
157714
157777
  } catch (error) {
157715
157778
  if (!DEAD.test(String(error?.message))) throw error;
157716
157779
  store.writeWal(`self-pay-${id}`, { ...store.readWal(`self-pay-${id}`), state: "abandoned" });
157717
- progress2(`step ${id} never landed (nothing was spent); run ${commandLine("balance --refresh")}, then ${commandLine("prepare")} again: it moves that deposit under a new id`);
157780
+ progress2(`that step never landed (nothing was spent); run ${commandLine("balance --refresh")}, then ${commandLine("prepare")} again: it moves that deposit in a new step`);
157718
157781
  return { amountAtomic, state: "abandoned" };
157719
157782
  }
157720
157783
  };
@@ -160734,11 +160797,16 @@ var ROUTE_LABEL = Object.freeze({
160734
160797
  changenow: "a ChangeNOW swap",
160735
160798
  "privacy-cash": "your own wallet through the Privacy Cash crowd"
160736
160799
  });
160800
+ var SETUP_LINES_BELOW = "the setup lines below";
160737
160801
  function privacyCost(plan) {
160738
160802
  const route = plan.route === "exchange" ? "the wallet or exchange you send from sees the amount and the one-time address, so it can link you to this deposit (an exchange knows who you are); the chain shows only that sender paying a new address" : plan.route === "changenow" ? "ChangeNOW sees the amount, the one-time address, and what you paid with (Monero hides the sender; BTC or ETH can be traced to you); the chain shows only ChangeNOW paying a new address" : `the chain shows your --from-key-file wallet paying into Privacy Cash and, at least a day and 10 deposits later, Privacy Cash paying a new address; ${plan.crowd?.via === "local" ? "Privacy Cash's server sees neither step (--via local), and the chain can pair the two only by amount and time among the deposits between them" : "Privacy Cash's server sees both steps and can link them (--via sdk; --via local avoids that)"}`;
160739
160803
  const split5 = splitOf(plan) > 1 ? `. ${plan.parts ? "With --split auto" : `With --split ${splitOf(plan)}`}: ${partsText(plan)}, each amount public; the RPC reads the ${splitOf(plan)} one-time addresses one after another, and the deposits land close together, so both can show they are one depositor's (about what one deposit of the total shows); the facilitator pays the fee and rent of each, and each takes one of the pool's deposit places. Your payments still prove under the same per-epoch anchor as every other payment` : "";
160740
160804
  return `${route}. The amount and time of every deposit are public, and Shinjuku's server sees them too; while few other people add money, the time can still link this deposit to your later payments${split5}`;
160741
160805
  }
160806
+ function privacyShort(plan) {
160807
+ if (plan.route !== "exchange" || splitOf(plan) > 1) return privacyCost(plan);
160808
+ return "the wallet or exchange you send from sees the amount and this address (an exchange knows who you are). The amount and time are public, and Shinjuku's server sees them; while few people add money, the time can link this deposit to your later payments";
160809
+ }
160742
160810
  function fundsView(store, plan, { now = Date.now(), running } = {}) {
160743
160811
  const decimals = Number(store.state.decimals ?? 6);
160744
160812
  const stage = currentStage(store, plan);
@@ -160796,7 +160864,7 @@ function fundsView(store, plan, { now = Date.now(), running } = {}) {
160796
160864
  view2.balance = balance ?? null;
160797
160865
  const pockets = balance?.pocketsAtomic && balance.pocketsAtomic !== "0" ? `, pockets ${usdc5(balance.pocketsAtomic, decimals)}` : "";
160798
160866
  view2.status = `ready since ${plan.done?.ready ?? iso2(now)}: ` + (balance?.total ? `total ${balance.total} USDC (shielded ${balance.shinjukuShielded}${pockets})` : `balance ${balance?.status ?? "unknown"} (${cli()} balance --pool ${store.state.pool} says why)`) + (split5 > 1 && Number.isSafeInteger(balance?.fundedAccounts) ? `; payments without waiting this epoch: up to ${balance.fundedAccounts} (one per funded deposit; ${cli()} balance says why)` : "");
160799
- view2.next = process.env.SHINJUKU_WALLET_SETUP_NEXT_BY_PARENT === "1" ? "the setup lines below" : `
160867
+ view2.next = process.env.SHINJUKU_WALLET_SETUP_NEXT_BY_PARENT === "1" ? SETUP_LINES_BELOW : `
160800
160868
  Chat privately in this terminal: ${commandLine("chat")}
160801
160869
  Use private AI from other apps: ${commandLine("infer start")}
160802
160870
  In your agent app, ask "what is my balance" or ask it to pay an x402 URL.
@@ -160828,7 +160896,8 @@ function fundsSummary(view2) {
160828
160896
  return [
160829
160897
  view2.ready ? `add-funds ${view2.fundsId} is ready: ${view2.amount} was added to your private balance${total}.` : `add-funds ${view2.fundsId} is not ready yet: ${status}.`,
160830
160898
  // 0.3.97 T6: a next step that ends with a command line gets no "." after it. A block of lines starts on its own line.
160831
- `Next:${view2.next.startsWith("\n") ? "" : " "}${sentence(view2.next)}`
160899
+ // S3 (mcp56, published 0.3.99): inside setup, setup's own Next line follows, so this one is not printed.
160900
+ ...view2.next === SETUP_LINES_BELOW ? [] : [`Next:${view2.next.startsWith("\n") ? "" : " "}${sentence(view2.next)}`]
160832
160901
  ].join("\n");
160833
160902
  }
160834
160903
  function fundsOutput(values, deps) {
@@ -160857,7 +160926,7 @@ function fundsStatusSummary(view2) {
160857
160926
  "For the whole status as JSON, add --json (or send this command's output to a file or a program)."
160858
160927
  ].join("\n");
160859
160928
  }
160860
- var HELD = /^(shinjuku(?:-wallet)?\b.*\bfailed: |shinjuku(?:-wallet)?\b.*: waiting for funds, nothing failed \(|hint: |Help: |next: |Examples?:|Every flag: | shinjuku(?:-wallet)? | npx -y --no-audit --no-fund shinjuku-shield(?:@\S+)? )/;
160929
+ var HELD = /^(shinjuku(?:-wallet)?\b.*\bfailed: |shinjuku(?:-wallet)?\b.*: waiting for funds, nothing failed \(|hint: |Help: |[Nn]ext: |Examples?:|Every flag: | shinjuku(?:-wallet)? | npx -y --no-audit --no-fund shinjuku-shield(?:@\S+)? )/;
160861
160930
  var STEP_TOR_LINE = /^(?:tor|proxy): /;
160862
160931
  var DENOMINATION_WARNING = /^warning: no pool denomination is configured\b/;
160863
160932
  async function runStep(ctx, argv2) {
@@ -161547,7 +161616,9 @@ async function addFunds(values, deps) {
161547
161616
  plan,
161548
161617
  proxyArgs: proxy.args,
161549
161618
  childEnv: { ...proxy.env, [PASSPHRASE_ENV]: passphrase2, [RPC_ENV2]: rpcUrl },
161550
- say: (stage, text3) => io.err(`add-funds ${plan.fundsId} [${stage}] ${ctx.quiet ? text3.replace(STEP_WORDS, "") : text3}
161619
+ // A1: a quiet terminal run names no plan id and no stage (funds-status and --verbose do).
161620
+ say: (stage, text3) => io.err(ctx.quiet ? `add-funds: ${text3.replace(STEP_WORDS, "")}
161621
+ ` : `add-funds ${plan.fundsId} [${stage}] ${text3}
161551
161622
  `),
161552
161623
  mark: (stage) => {
161553
161624
  if (plan.done?.[stage]) return;
@@ -161556,12 +161627,12 @@ async function addFunds(values, deps) {
161556
161627
  }
161557
161628
  });
161558
161629
  const parts = splitOf(plan) > 1 ? ` in ${partsText(plan)}, each from its own one-time address (${splitOf(plan)} payments in a row without waiting for a pool anchor)` : "";
161559
- ctx.say("start", `${resolved.created ? "new" : "continues"}: ${usdc5(plan.amountAtomic)} USDC${parts} from ${ROUTE_LABEL[plan.route]}; stages ${stagesOf(plan).join(" > ")}. Ctrl-C is safe at any point: run the same command again to continue.`);
161630
+ ctx.say("start", ctx.quiet ? `${resolved.created ? "adds" : "continues to add"} ${usdc5(plan.amountAtomic)} USDC${parts} from ${ROUTE_LABEL[plan.route]}. Ctrl-C is safe at any point: run the same command again to continue.` : `${resolved.created ? "new" : "continues"}: ${usdc5(plan.amountAtomic)} USDC${parts} from ${ROUTE_LABEL[plan.route]}; stages ${stagesOf(plan).join(" > ")}. Ctrl-C is safe at any point: run the same command again to continue.`);
161560
161631
  if (resolved.routeChanged) {
161561
161632
  const address5 = relayedDeposit2(store, plan)?.relay?.owner;
161562
161633
  ctx.say("start", `the route changes from ${resolved.routeChanged.from} to ${resolved.routeChanged.to}${address5 ? `; the one-time address stays ${address5}` : ""}${address5 && resolved.routeChanged.to === "privacy-cash" ? " (it was read when it was recorded: an RPC operator can pair that read with a Privacy Cash deposit made soon after it)" : ""}`);
161563
161634
  }
161564
- ctx.say("start", `privacy: ${privacyCost(plan)}`);
161635
+ ctx.say("start", `privacy: ${ctx.quiet ? privacyShort(plan) : privacyCost(plan)}`);
161565
161636
  const markDone = () => {
161566
161637
  const stage = currentStage(store, plan);
161567
161638
  for (const done of stagesOf(plan)) {
@@ -161598,7 +161669,7 @@ async function addFunds(values, deps) {
161598
161669
  }
161599
161670
  const label = outcome.label ?? next ?? stage;
161600
161671
  if (outcome.kind === "stop") {
161601
- const said = outcome.tries ? [] : (outcome.run?.held ?? []).filter((text3) => !/^(Help|next): /.test(text3));
161672
+ const said = outcome.tries ? [] : (outcome.run?.held ?? []).filter((text3) => !/^(Help|[Nn]ext): /.test(text3));
161602
161673
  if (!said.length && outcome.run?.last && !outcome.tries) said.push(outcome.run.last);
161603
161674
  for (const text3 of said) io.err(` ${text3}
161604
161675
  `);
@@ -162045,8 +162116,9 @@ var GUIDE = Object.freeze({
162045
162116
  });
162046
162117
  var WALLET_FILE = `"$SHIELDED_WALLET_HOME/${GUIDE.pool}/wallet.json"`;
162047
162118
  var FROM_SETUP = " (after setup, config.json gives it: no need to type it)";
162048
- var SETUP_NEXT = `open your agent app and ask "what is my balance". In this terminal, run:
162049
- ${CLI_WORD} balance`;
162119
+ var SETUP_NEXT = `chat privately with an AI in this terminal:
162120
+ ${CLI_WORD} chat
162121
+ Or open your agent app and ask "what is my balance".`;
162050
162122
  var UNSHIELD_EXAMPLE = `${CLI_WORD} unshield --amount 0.5 --to <your wallet address>`;
162051
162123
  function hintForThisRun(text3, values) {
162052
162124
  let hint = String(text3);
@@ -163953,7 +164025,8 @@ async function runSetupCommand(values, deps) {
163953
164025
  const asksOwnFirst = interactive && Boolean(old) && terminal && !ownAddress;
163954
164026
  io.out([
163955
164027
  ...introduced ? [] : [
163956
- "Shinjuku Shield: a self-custody wallet that pays x402 URLs privately from your USDC balance.",
164028
+ // S2 (mcp56 on the published 0.3.99): the help's words (shortHelpText), not "x402 URLs".
164029
+ "Shinjuku Shield: your private wallet on this computer. It pays for AI answers and paid web services from your private balance.",
163957
164030
  "Setup makes the wallet on this machine (your keys never leave it), downloads and checks the proof tools, and writes config.json.",
163958
164031
  // 0.3.97 T4, live proof 2026-10-24: the command on its own line.
163959
164032
  "An agent app then starts it with this command:",
@@ -163987,10 +164060,6 @@ async function runSetupCommand(values, deps) {
163987
164060
  const tor = String(await deps.ask("Send every request through Tor? It hides your IP address; if Tor is not running, setup gives the one command that starts it. [Y/n] ")).trim();
163988
164061
  options.tor = !/^n(o)?$/i.test(tor);
163989
164062
  }
163990
- if (privateInference === void 0) {
163991
- const answer = String(await deps.ask(`Set your LLM to private inference now? ${PRIVATE_INFERENCE_WHY} [y/N] `)).trim();
163992
- privateInference = /^y(es)?$/i.test(answer);
163993
- }
163994
164063
  if (!ownAddress && terminal) ownAddress = await askOwnAddress(deps);
163995
164064
  } else if (asksOwnFirst) {
163996
164065
  ownAddress = await askOwnAddress(deps, old.unshieldTo ?? []);
@@ -164821,8 +164890,8 @@ var COMMANDS = [
164821
164890
  flags: [
164822
164891
  flag2("--prompt", "<text>", 'what to ask; write - to read it from stdin (the private form), or write it alone, in quotes (shinjuku infer "Summarize x402 in one line" ...)', { required: true }),
164823
164892
  flag2("--model", "<id>", `the model (default ${DEFAULT_MODEL2}): ${INFER_MODEL_IDS.join(", ")} (${CLI_WORD} infer models lists their prices)`),
164824
- flag2("--max-tokens", "<n>", "the longest answer to pay for, in tokens; default 512 (give 256 or more; 512 answers a short question: a reasoning model like qwen3-8-flash thinks first, and with fewer tokens it can stop before it answers, still paid); it sets the most this request can cost", { check: "count" }),
164825
- flag2("--max-payment", "<usdc>", "the most this request may pay in all, in USDC (0.02 = 2 cents). It must be at least 0.01: the wallet never sets a call's ceiling below the AI provider's 0.01 USDC floor on its standard offer, and a call on that offer also pays Shinjuku's fee, so 0.02 covers it. What a call pays is the AI provider's quote for --max-tokens plus Shinjuku's fee: a short deepseek-v4-1-flash answer (the default) paid 0.001047 USDC live (the AI provider's price plus the 0.001 USDC fee) on the offer the wallet pays, 0.01 USDC plus the fee when the AI provider offers only its standard payment. A higher ceiling is refused before anything is paid. Default: the cap setup recorded, else 0.05", { check: "atomic", usdc: true }),
164893
+ flag2("--max-tokens", "<n>", "the longest answer to pay for, in tokens; default 2048, 512 for claude-opus-5 (give 256 or more; 512 answers a short question: a reasoning model like qwen3-8-flash thinks first, and with fewer tokens it can stop before it answers, still paid); it sets the most this request can cost", { check: "count" }),
164894
+ flag2("--max-payment", "<usdc>", `the most this request may pay in all, in USDC (0.02 = 2 cents). It must be at least 0.01: the wallet never sets a call's ceiling below the AI provider's 0.01 USDC floor on its standard offer, and a call on that offer also pays Shinjuku's fee, so 0.02 covers it. What a call pays is the AI provider's quote for --max-tokens plus Shinjuku's fee: a ${DEFAULT_MODEL2} answer (the default, ${DEFAULT_MAX_TOKENS} tokens) paid ${DEFAULT_MODEL_PAID_WORDS} live (the AI provider's price plus the 0.001 USDC fee) on the offer the wallet pays, 0.01 USDC plus the fee when the AI provider offers only its standard payment. A higher ceiling is refused before anything is paid. Default: the cap setup recorded, else 0.05`, { check: "atomic", usdc: true }),
164826
164895
  flag2("--max-session", "<usdc>", "the most this run may pay in total, in USDC; at least --max-payment. Default: the cap setup recorded, else 1", { check: "atomic", usdc: true }),
164827
164896
  flag2("--request-id", "<id>", "your stable name for this request; default infer-<random>, printed on stderr before any payment. Rerun with the same id to resume a stopped run: it never pays twice"),
164828
164897
  bool2("--json", "print one JSON line (answer, model, usage, paidAtomic, requestId, receipt) instead of the answer text"),
@@ -164861,7 +164930,7 @@ var COMMANDS = [
164861
164930
  flags: [bool2("--json", "print the list as one JSON line (prices in atomic USDC per 1M tokens)")],
164862
164931
  examples: [`${CLI_WORD} infer models`, `${CLI_WORD} infer models --json`],
164863
164932
  notes: [`${INFER_COST_LINE}.`],
164864
- next: `ask one: echo "Summarize x402 in one line" | ${CLI_WORD} infer - --model deepseek-v4-1-flash`,
164933
+ next: `ask one: echo "Summarize x402 in one line" | ${CLI_WORD} infer - --model ${DEFAULT_MODEL2}`,
164865
164934
  run: (args, values, deps, io) => deps.inferModelsMain(values, { ...deps, io })
164866
164935
  },
164867
164936
  {
@@ -164876,12 +164945,12 @@ var COMMANDS = [
164876
164945
  ],
164877
164946
  examples: [
164878
164947
  `${CLI_WORD} infer serve --max-payment 0.02 --max-session 0.2 ${PAYER_SESSION_BASE} --tor`,
164879
- `curl http://127.0.0.1:11402/v1/chat/completions -H "Authorization: Bearer <api key it printed>" -H "Content-Type: application/json" -d '{"model":"deepseek-v4-1-flash","max_tokens":512,"messages":[{"role":"user","content":"Say hello"}]}'`
164948
+ `curl http://127.0.0.1:11402/v1/chat/completions -H "Authorization: Bearer <api key it printed>" -H "Content-Type: application/json" -d '{"model":"${DEFAULT_MODEL2}","max_tokens":${DEFAULT_MAX_TOKENS},"messages":[{"role":"user","content":"Say hello"}]}'`
164880
164949
  ],
164881
164950
  notes: [
164882
164951
  "Any OpenAI client works: set base_url http://127.0.0.1:11402/v1 and the api key it prints at start (required: a request without it gets 401). GET /v1/models lists the models with prices.",
164883
164952
  "Send an Idempotency-Key header to make a resend of the same request resume the same payment instead of paying again.",
164884
- `A request without max_tokens gets max_tokens 512 (max_tokens sets the most a request can cost), and stream: true is answered as a stream of the paid whole answer (the text comes at the end). Models: ${INFER_MODEL_IDS.join(", ")}.`,
164953
+ `A request without max_tokens gets max_tokens ${DEFAULT_MAX_TOKENS}, 512 for claude-opus-5 (max_tokens sets the most a request can cost), and stream: true is answered as a stream of the paid whole answer (the text comes at the end). Models: ${INFER_MODEL_IDS.join(", ")}.`,
164885
164954
  "It runs only with --tor (or --proxy <url>), so the AI provider never sees your IP address. Web pages cannot call it: a request with an Origin header is refused.",
164886
164955
  `${INFER_COST_LINE}. Each answer carries a shinjuku field with the amount paid and what this server has spent.`,
164887
164956
  "Ctrl+C stops it; stderr then says what it paid in all.",
@@ -164961,7 +165030,7 @@ var COMMANDS = [
164961
165030
  summary: "Chat privately with an AI model in this terminal: each answer is paid from your private balance, behind Tor.",
164962
165031
  env: [],
164963
165032
  flags: [
164964
- flag2("--model", "<id>", `the model to start with (default deepseek-v4-1-flash): ${INFER_MODEL_IDS.join(", ")}`),
165033
+ flag2("--model", "<id>", `the model to start with (default ${DEFAULT_MODEL2}): ${INFER_MODEL_IDS.join(", ")}`),
164965
165034
  flag2("--max-payment", "<usdc>", "the most one answer may pay, in USDC; default: the cap setup recorded, else 0.05", { check: "atomic", usdc: true }),
164966
165035
  flag2("--max-session", "<usdc>", "the most this chat may pay in all, in USDC; default: the cap setup recorded, else 1", { check: "atomic", usdc: true }),
164967
165036
  bool2("--verbose", "also show the wallet's own progress lines between the answers (Tor, the RPC relay, the pool history, the memo reads); without it the chat shows only the conversation, what each answer paid, and every refusal or failure"),
@@ -165048,7 +165117,7 @@ var COMMANDS = [
165048
165117
  flag2("--max-unshield", "<usdc>", "optional: the mcp --max-unshield cap, in USDC (one wallet_unshield send, and this wallet in any 24 hours)", { check: "atomic", usdc: true }),
165049
165118
  bool2("--prefer-tor", "record Tor for mcp: it then runs behind Tor (needs Tor with HTTPTunnelPort 127.0.0.1:9080); mcp --no-tor skips it for one run. Setup itself then runs behind Tor too. setup --tor does the same: it runs setup behind Tor and records Tor for mcp"),
165050
165119
  bool2("--no-tor", "run this setup without Tor although Tor is recorded or asked for (the facilitator then sees this machine's IP address download the release and the proof tools); config.json still records Tor. With --tor it only stops --tor from recording Tor (the run itself still goes over Tor)"),
165051
- bool2("--private-inference", 'answer yes to "Set your LLM to private inference now?": your agent buys AI answers from the AI provider, paid from your private balance over Tor. Records inference.enabled and Tor (as --prefer-tor) in config.json, and prints the path to the first private call. Without it, --yes keeps what config.json records'),
165120
+ bool2("--private-inference", "records that your agent buys AI answers from the AI provider, paid from your private balance over Tor. Records inference.enabled and Tor (as --prefer-tor) in config.json, and prints the path to the first private call. Without it, --yes keeps what config.json records"),
165052
165121
  flag2("--own-address", "<address>", `optional, in your own terminal only (the same as the answer to setup's address question): your own wallet address (Phantom, Solflare, an exchange's deposit address), for sending money back to yourself: the MCP sends to it without asking each time (as mcp --unshield-to), also in an agent app that cannot show a question. Not this wallet's own address. Refused in a script, a pipe, or an agent's tool (no terminal), also with --yes: an agent must never approve an address. A rerun adds it to the ones config.json records (at most ${MAX_OWN_ADDRESSES})`),
165053
165122
  flag2("--passphrase-file", "<file>", "your own passphrase file (8 or more characters); default: setup writes a new random passphrase to <home>/passphrase.txt, readable by you alone. Needed for a wallet that exists already"),
165054
165123
  flag2("--facilitator", "<https origin>", `the facilitator whose release, pool, and proof tools setup uses; default ${GUIDE.facilitator}`, { check: "origin" }),
@@ -165063,9 +165132,9 @@ var COMMANDS = [
165063
165132
  `${CLI_WORD} setup --yes --private-inference`
165064
165133
  ],
165065
165134
  notes: [
165066
- `npx -y --no-audit --no-fund ${COMMAND_AT_VERSION} setup runs the same command. Without --yes a first setup asks in the terminal: Tor, private inference, and your own wallet address (Enter skips it). A rerun in a terminal asks only for your own wallet address: on a complete setup an address given there is saved and nothing else runs; Enter checks every piece again.`,
165067
- `When Tor is recorded or asked for (a yes, --tor, --prefer-tor, or --private-inference), setup itself runs behind Tor: the release, the proof-tool downloads, and the RPC requests go over Tor, so the facilitator never sees this machine's IP address make the wallet. config.json then records Tor for mcp ("tor": true), so the MCP runs behind Tor too. Tor must run first (if it does not, setup gives the one command that starts it); --no-tor runs it without Tor.`,
165068
- `Private inference (a yes, or --private-inference): ${PRIVATE_INFERENCE_WHY} Setup records Tor. At the end, a terminal setup first offers to run add-funds (a --yes run prints ${CLI_WORD} add-funds), then prints ${CLI_WORD} chat (a private AI chat in this terminal), ${CLI_WORD} infer start (the local AI server in the background; it prints base_url and the api key when it starts), and the lines that add the wallet to an agent app. Setup never pays an inference call.`,
165135
+ `npx -y --no-audit --no-fund ${COMMAND_AT_VERSION} setup runs the same command. Without --yes a first setup asks in the terminal: Tor and your own wallet address (Enter skips it). A rerun in a terminal asks only for your own wallet address: on a complete setup an address given there is saved and nothing else runs; Enter checks every piece again.`,
165136
+ `When Tor is recorded or asked for (a yes to the Tor question, --tor, --prefer-tor, or --private-inference), setup itself runs behind Tor: the release, the proof-tool downloads, and the RPC requests go over Tor, so the facilitator never sees this machine's IP address make the wallet. config.json then records Tor for mcp ("tor": true), so the MCP runs behind Tor too. Tor must run first (if it does not, setup gives the one command that starts it); --no-tor runs it without Tor.`,
165137
+ `Private inference (--private-inference): ${PRIVATE_INFERENCE_WHY} Setup records Tor. At the end, a terminal setup first offers to run add-funds (a --yes run prints ${CLI_WORD} add-funds), then prints ${CLI_WORD} chat (a private AI chat in this terminal), ${CLI_WORD} infer start (the local AI server in the background; it prints base_url and the api key when it starts), and the lines that add the wallet to an agent app. Setup never pays an inference call.`,
165069
165138
  "The wallet home is SHIELDED_WALLET_HOME, default ~/.carbon-shielded-wallet (every command of this CLI uses it). setup writes there: config.json, passphrase.txt (mode 0600; on Windows an access list for only you on this computer), the wallet (<pool>/wallet.json, made by init with the production pool, program, network, and USDC), a recovery file recovery-<time>.txt, and proof-tools/<dir>/.",
165070
165139
  "The passphrase is never printed and no MCP tool takes or returns it. The recovery file holds the seed and the key: copy it to two offline places, then delete it from this machine.",
165071
165140
  `The proof tools are the set this wallet build PINS (its SHA256SUMS hash is in the wallet; --trust-proof-tools replaces it). A facilitator whose /api/wallet/release names another set is refused (setup_proof_tools_pin_mismatch: update the wallet, npx -y --no-audit --no-fund ${NPX_PACKAGE}@latest setup). SHA256SUMS must have the pinned SHA-256, and every file must have the SHA-256 its line names, as /skill.md section 7b; bin/ and house/bin/ get the execute bit. A file that does not match is not kept. Each download has a hard size limit.`,
@@ -166114,12 +166183,13 @@ async function askUnshield(args, values, deps) {
166114
166183
  let whole;
166115
166184
  if (values.get("--amount") === "all") {
166116
166185
  try {
166117
- whole = knownPrivateBalance((deps.openWalletStore ?? openWalletStore)(resolve39(values.get("--wallet")), payerPassphrase(values, deps)));
166186
+ whole = savedBalance((deps.openWalletStore ?? openWalletStore)(resolve39(values.get("--wallet")), payerPassphrase(values, deps)));
166118
166187
  } catch {
166119
166188
  whole = void 0;
166120
166189
  }
166121
166190
  }
166122
- const what = values.get("--amount") === "all" ? `your whole private balance${whole !== void 0 ? ` (${usdcOr(whole.toString())})` : ""}` : `${usdcText3(values.get("--amount"))} USDC`;
166191
+ const wholeWords = !whole ? "" : whole.current ? ` (${usdcOr(whole.atomic.toString())})` : ` (about ${usdcOr(whole.atomic.toString())} at its last read)`;
166192
+ const what = values.get("--amount") === "all" ? `your whole private balance${wholeWords}` : `${usdcText3(values.get("--amount"))} USDC`;
166123
166193
  const answer = String(await deps.ask(`Send ${what} to ${values.get("--to")}? [y/N] `)).trim();
166124
166194
  if (!/^y(es)?$/i.test(answer)) throw new PublicFailure(`unshield_declined: you did not answer y, so nothing was sent. To send, run:
166125
166195
  ${cli2()} unshield`);
@@ -167233,7 +167303,7 @@ function failureHint(command2, reason, deps, values, floatError) {
167233
167303
  if (/inference_tor_required/.test(reason)) return "rerun the same command with --tor added (or --proxy <http://host:port>); nothing was paid";
167234
167304
  if (/inference_ceiling_over_cap/.test(reason)) return "raise --max-payment to the ceiling it names plus Shinjuku's fee, or lower --max-tokens; nothing was paid";
167235
167305
  if (/inference_model_unknown/.test(reason)) return `pass --model with one of: ${INFER_MODEL_IDS.join(", ")} (${cli2()} infer models lists the prices)`;
167236
- if (/inference_max_tokens_required/.test(reason)) return "add --max-tokens <n> (256 or more; 512 answers a short question), or leave --max-tokens out for 512: it sets the most the request can cost";
167306
+ if (/inference_max_tokens_required/.test(reason)) return "add --max-tokens <n> (256 or more; 512 answers a short question), or leave --max-tokens out for 2048 (512 for claude-opus-5): it sets the most the request can cost";
167237
167307
  if (/mcp_session_cap_exceeded/.test(reason)) return "nothing was paid: this process reached --max-session. Restart it with a higher --max-session to pay more";
167238
167308
  if (/mcp_cap_invalid/.test(reason)) {
167239
167309
  return `start it with --max-session at least as high as --max-payment, for example: ${cli2()} mcp --max-payment 0.05 --max-session 0.2 ${PAYER_SESSION_BASE}`;
@@ -167877,11 +167947,13 @@ This copy: ${walletVersionLine(deps.walletVersion())}
167877
167947
  `);
167878
167948
  return 0;
167879
167949
  }
167880
- const threeWords = FAMILIES.has(args[0]) && COMMAND_BY_NAME.has(args.slice(0, 3).join(" "));
167881
- const topicWords = threeWords ? [args.slice(0, 3).join(" "), ...args.slice(3)] : FAMILIES.has(args[0]) ? [`${args[0]} ${args[1]}`, ...args.slice(2)] : args;
167950
+ const full = args.length > 1 && args.at(-1) === "--verbose";
167951
+ const words2 = full ? args.slice(0, -1) : args;
167952
+ const threeWords = FAMILIES.has(words2[0]) && COMMAND_BY_NAME.has(words2.slice(0, 3).join(" "));
167953
+ const twoWords = FAMILIES.has(words2[0]) && words2.length > 1;
167954
+ const topicWords = threeWords ? [words2.slice(0, 3).join(" "), ...words2.slice(3)] : twoWords ? [`${words2[0]} ${words2[1]}`, ...words2.slice(2)] : words2;
167882
167955
  const topic = COMMAND_BY_NAME.get(topicWords[0]);
167883
- const full = topicWords.length === 2 && topicWords[1] === "--verbose";
167884
- if (!topic || topicWords.length > 1 && !full) {
167956
+ if (!topic || topicWords.length > 1) {
167885
167957
  const near = suggestion(args[0], COMMAND_NAMES);
167886
167958
  throw new UsageError(`There is no such command to explain.${near ? ` Did you mean ${near}? Run: ${cli2()} help ${near}` : ""}`);
167887
167959
  }