shinjuku-shielded 0.3.175 → 0.3.176

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.175. The npm packages `shinjuku-shield` and
9
+ This is version 0.3.176. 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 shinjuku@0.3.175 setup
15
+ npx -y shinjuku@0.3.176 setup
16
16
  ```
17
17
 
18
18
  Setup offers to install the `shinjuku` command and, on Windows, adds npm's
@@ -35,13 +35,13 @@ Every wallet command works after `shinjuku`, for example `shinjuku balance`,
35
35
  Claude Code:
36
36
 
37
37
  ```sh
38
- claude mcp add --scope user shinjuku -- npx -y --no-audit --no-fund shinjuku-shield@0.3.175 mcp
38
+ claude mcp add --scope user shinjuku -- npx -y --no-audit --no-fund shinjuku-shield@0.3.176 mcp
39
39
  ```
40
40
 
41
41
  Claude Desktop (`claude_desktop_config.json`) or Cursor (`~/.cursor/mcp.json`):
42
42
 
43
43
  ```json
44
- { "mcpServers": { "shinjuku": { "command": "npx", "args": ["-y", "--no-audit", "--no-fund", "shinjuku-shield@0.3.175", "mcp"] } } }
44
+ { "mcpServers": { "shinjuku": { "command": "npx", "args": ["-y", "--no-audit", "--no-fund", "shinjuku-shield@0.3.176", "mcp"] } } }
45
45
  ```
46
46
 
47
47
  On native Windows, if the host cannot find `npx`, use `"command": "cmd"` and
@@ -54,7 +54,7 @@ Codex (the Codex app and Codex CLI read the same file, `~/.codex/config.toml`):
54
54
  ```toml
55
55
  [mcp_servers.shinjuku]
56
56
  command = "npx"
57
- args = ["-y", "--no-audit", "--no-fund", "shinjuku-shield@0.3.175", "mcp"]
57
+ args = ["-y", "--no-audit", "--no-fund", "shinjuku-shield@0.3.176", "mcp"]
58
58
  startup_timeout_sec = 120
59
59
  tool_timeout_sec = 300
60
60
  ```
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": "dd3bab15896a11e725fd452abbfd4fbad9b412640201141c884a77bd6c81d5dd",
5
- "bytes": 9395751,
6
- "sourceCommit": "9dc2ed8abb49b3cce01ddd09f43128cc0aa5c885",
4
+ "sha256": "eb8212021af756602a223fcb5e329e0eaf488b7b276850014abef3c6a917eb81",
5
+ "bytes": 9401666,
6
+ "sourceCommit": "d6237649219078cad2c31a571967809dc888a3fc",
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",
@@ -1038,5 +1038,5 @@
1038
1038
  "version": "1.8.0"
1039
1039
  }
1040
1040
  ],
1041
- "publishedAt": "2026-11-05T23:21:41.970Z"
1041
+ "publishedAt": "2026-11-06T00:15:32.603Z"
1042
1042
  }
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "shinjuku-shielded",
3
- "version": "0.3.175",
3
+ "version": "0.3.176",
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": "dd3bab15",
7
- "shinjukuSha256": "dd3bab15896a11e725fd452abbfd4fbad9b412640201141c884a77bd6c81d5dd",
6
+ "shinjukuRelease": "eb821202",
7
+ "shinjukuSha256": "eb8212021af756602a223fcb5e329e0eaf488b7b276850014abef3c6a917eb81",
8
8
  "license": "SEE LICENSE IN LICENSE.txt",
9
9
  "author": "Shinjuku Shielded",
10
10
  "homepage": "https://github.com/ShinjukuStaition/shinjuku-shield#readme",
@@ -137281,8 +137281,29 @@ ${canonical5}`;
137281
137281
  return "not_known";
137282
137282
  }
137283
137283
  }
137284
- function newestSendCancel() {
137285
- const found = newestSendJob();
137284
+ function sendTransactionOf(requestId, job = jobs.get(`unshield:${toRecordId(requestId)}`)) {
137285
+ const fromJob = typeof job?.result?.exitTransaction === "string" ? job.result.exitTransaction : void 0;
137286
+ let fromRecord;
137287
+ try {
137288
+ const record = session.openStore().readWal(`self-pay-${moneyId(requestId)}`);
137289
+ fromRecord = typeof record?.exitTransaction === "string" ? record.exitTransaction : void 0;
137290
+ } catch {
137291
+ fromRecord = void 0;
137292
+ }
137293
+ if (fromJob && fromRecord && fromJob !== fromRecord) return { conflict: true };
137294
+ const tx = fromJob ?? fromRecord;
137295
+ return tx ? { tx } : {};
137296
+ }
137297
+ const CANCEL_SEND_WAIT_MS = 15e3;
137298
+ async function newestSendCancel({ signal } = {}) {
137299
+ let found = newestSendJob();
137300
+ if (found?.state === "sending" && !signal?.aborted) {
137301
+ const until = Date.now() + CANCEL_SEND_WAIT_MS;
137302
+ while (found.state === "sending" && !signal?.aborted && Date.now() < until) {
137303
+ await stageOrTimeout(found.job, until - Date.now(), signal);
137304
+ found = { job: found.job, state: sendJobState(found.job) };
137305
+ }
137306
+ }
137286
137307
  const receipts2 = "wallet_receipts (kind moves) shows each send of this wallet and its state";
137287
137308
  if (!found) {
137288
137309
  return refuse12(
@@ -137303,8 +137324,15 @@ ${canonical5}`;
137303
137324
  { ...ids, sendState: "confirming" }
137304
137325
  );
137305
137326
  }
137327
+ const sent = state2 === "sent" || state2 === "sending" ? sendTransactionOf(job.requestId, job) : {};
137328
+ const txFields = sent.tx ? { exitTransaction: sent.tx } : {};
137306
137329
  if (state2 === "sent") {
137307
- return refuse12("mcp_unshield_cancel_done", `${what} was already sent; nothing was changed`, `Nothing to stop. Tell the user it was sent; ${receipts2}, with its transaction`, { ...ids, sendState: "sent" });
137330
+ return refuse12(
137331
+ "mcp_unshield_cancel_done",
137332
+ `${what} was already sent${sent.tx ? ` (transaction ${sent.tx})` : ""}; nothing was changed`,
137333
+ sent.tx ? `Nothing to stop. Tell the user it was sent, with transaction ${sent.tx}` : `Nothing to stop. Tell the user it was sent; call wallet_receipts (kind moves) for its transaction and give the user that transaction${sent.conflict ? " (the wallet's two records of it disagree, so check wallet_receipts)" : ""}`,
137334
+ { ...ids, sendState: "sent", ...txFields }
137335
+ );
137308
137336
  }
137309
137337
  if (state2 === "ended") {
137310
137338
  return refuse12(
@@ -137324,11 +137352,31 @@ ${canonical5}`;
137324
137352
  }
137325
137353
  return refuse12(
137326
137354
  "mcp_unshield_cancel_sending",
137327
- `the user confirmed ${what} and it is being sent now; a confirmed send cannot be stopped; nothing was changed`,
137328
- `Nothing was stopped. Tell the user the send goes on and cannot be cancelled; ${receipts2} (it shows when it lands). Do not call wallet_unshield for it again`,
137329
- { ...ids, sendState: "sending" }
137355
+ `the user confirmed ${what} and it is being sent now${sent.tx ? ` (transaction ${sent.tx}; the wallet confirms it)` : ""}; a confirmed send cannot be stopped; nothing was changed`,
137356
+ `Nothing was stopped. Tell the user the send goes on and cannot be cancelled${sent.tx ? `, with transaction ${sent.tx}` : ""}. ` + sendRetryNext(job.requestId, sent, { amountAtomic: job.amountAtomic, to: job.to }),
137357
+ { ...ids, sendState: "sending", ...txFields }
137330
137358
  );
137331
137359
  }
137360
+ const sendRetryNext = (requestId, sent = {}, { amountAtomic, to } = {}) => `Then call wallet_unshield with request_id "${requestId}"${amountAtomic ? `, amount_atomic "${amountAtomic}"` : ""}${to ? `, and to "${to}"` : ""} (the same arguments: it reads this same send, finishes it after a restart, never asks again and never sends twice) until it answers done, and give the user its transaction${sent.conflict ? " (the wallet's two records of it disagree now: check wallet_receipts)" : ""}; do not offer the history in place of it. Do not call wallet_cancel with this request_id: for a send in parts that stops its unsent payments`;
137361
+ function stageOrTimeout(job, ms, signal) {
137362
+ return new Promise((resolve43) => {
137363
+ let timer;
137364
+ const done = () => {
137365
+ clearTimeout(timer);
137366
+ job.stageWaiters?.delete(done);
137367
+ signal?.removeEventListener?.("abort", done);
137368
+ resolve43();
137369
+ };
137370
+ if (signal?.aborted) {
137371
+ resolve43();
137372
+ return;
137373
+ }
137374
+ (job.stageWaiters ??= /* @__PURE__ */ new Set()).add(done);
137375
+ signal?.addEventListener?.("abort", done, { once: true });
137376
+ timer = setTimeout(done, Math.max(0, ms));
137377
+ timer.unref?.();
137378
+ });
137379
+ }
137332
137380
  function cancelMatches(store, requestId) {
137333
137381
  const found = [];
137334
137382
  let float;
@@ -137610,7 +137658,7 @@ ${canonical5}`;
137610
137658
  const cancel = {
137611
137659
  name: "wallet_cancel",
137612
137660
  title: "Close or stop one unfinished request",
137613
- description: `Use this when the user says "stop that send", also after this app stopped a call before its answer (then never say nothing was sent: leave request_id out and this reads the newest send of this session, or says to check wallet_receipts), or wants to stop something unfinished, by its request_id: a payment (pay_for_service or inference_chat) that can no longer settle, so its reserved balance comes back; a private send in smaller payments (an older wallet version started it): its smaller payments not sent yet stop and stay in the balance; a wallet_add_funds address nothing reached: it closes. A single send (one exit, a send of the whole balance too) cannot be stopped: the answer says its state. A private send's stop is written under the wallet lock: when another step of this wallet holds it (one of the smaller payments being sent, a payment), this call waits for that step, up to about ${CALL_BUDGET_MS / 1e3} s. Only an answer with cancelled: true means the stop is saved; an answer with stage cancelling means nothing is stopped yet: call wallet_cancel again. It finds the request by itself; when one request_id names two of these, pass kind. A payment whose bytes never left the wallet closes at once. A sent payment closes only once the chain proves it can never land (usually about two minutes after its quote expired); until then it is refused, because closing earlier could let a retry pay twice. Pays nothing and moves nothing.`,
137661
+ description: `Use this when the user says "stop that send", also after this app stopped a call before its answer (then never say nothing was sent: leave request_id out and this reads the newest send of this session, or says to check wallet_receipts), or wants to stop something unfinished, by its request_id: a payment (pay_for_service or inference_chat) that can no longer settle, so its reserved balance comes back; a private send in smaller payments (an older wallet version started it): its smaller payments not sent yet stop and stay in the balance; a wallet_add_funds address nothing reached: it closes. A single send (one exit, a send of the whole balance too) cannot be stopped: the answer says its state and, once it is out, its transaction (exitTransaction; without request_id it waits up to 15 s for it): give the user that transaction. A private send's stop is written under the wallet lock: when another step of this wallet holds it (one of the smaller payments being sent, a payment), this call waits for that step, up to about ${CALL_BUDGET_MS / 1e3} s. Only an answer with cancelled: true means the stop is saved; an answer with stage cancelling means nothing is stopped yet: call wallet_cancel again. It finds the request by itself; when one request_id names two of these, pass kind. A payment whose bytes never left the wallet closes at once. A sent payment closes only once the chain proves it can never land (usually about two minutes after its quote expired); until then it is refused, because closing earlier could let a retry pay twice. Pays nothing and moves nothing.`,
137614
137662
  inputSchema: {
137615
137663
  type: "object",
137616
137664
  properties: {
@@ -137620,10 +137668,10 @@ ${canonical5}`;
137620
137668
  additionalProperties: false
137621
137669
  },
137622
137670
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
137623
- async run(args, { progress: progress2 } = {}) {
137671
+ async run(args, { progress: progress2, signal } = {}) {
137624
137672
  const startedAt = Date.now();
137625
137673
  const requestId = args?.request_id;
137626
- if (requestId === void 0 && (args?.kind === void 0 || args.kind === "send")) return cancelWords(newestSendCancel());
137674
+ if (requestId === void 0 && (args?.kind === void 0 || args.kind === "send")) return cancelWords(await newestSendCancel({ signal }));
137627
137675
  if (typeof requestId !== "string" || !REQUEST_ID4.test(requestId)) {
137628
137676
  return refuse12("mcp_argument_invalid", "request_id must be 1-128 characters of A-Z a-z 0-9 . _ : -", "pass the request_id of the payment, send, or address to close");
137629
137677
  }
@@ -138927,6 +138975,7 @@ ${canonical5}`;
138927
138975
  const changed = job.changed;
138928
138976
  job.changed = void 0;
138929
138977
  changed?.resolve();
138978
+ if (job.stageWaiters?.size) for (const wake of [...job.stageWaiters]) wake();
138930
138979
  }
138931
138980
  async function begin(kind, args, { waitMs, ask = true, startedAt = Date.now(), progress: progress2, canProgress = false, signal, rpcId } = {}) {
138932
138981
  const spec = KINDS[kind];
@@ -139992,20 +140041,22 @@ ${canonical5}`;
139992
140041
  const what = `the send of ${usdcText3(record.amountAtomic)} USDC to ${record.mcpTo.slice(0, 4)}...${record.mcpTo.slice(-4)}`;
139993
140042
  const call = `call wallet_unshield with request_id "${requestId}", amount_atomic "${record.amountAtomic}", and to "${record.mcpTo}"`;
139994
140043
  const ask = `${call} to see its progress (it never sends twice)`;
140044
+ const sent = state2 === "sent" || state2 === "sending" ? sendTransactionOf(requestId) : {};
140045
+ const txFields = sent.tx ? { exitTransaction: sent.tx } : {};
139995
140046
  if (state2 === "sent") {
139996
140047
  return refuse12(
139997
140048
  "mcp_unshield_cancel_done",
139998
- `${what} was already sent; nothing was changed`,
139999
- `Nothing to stop. Tell the user it was sent; ${call} to read its transaction (exitTransaction; it never sends twice)`,
140000
- { requestId, sendState: "sent" }
140049
+ `${what} was already sent${sent.tx ? ` (transaction ${sent.tx})` : ""}; nothing was changed`,
140050
+ sent.tx ? `Nothing to stop. Tell the user it was sent, with transaction ${sent.tx}` : `Nothing to stop. Tell the user it was sent; call wallet_receipts (kind moves) for its transaction and give the user that transaction${sent.conflict ? " (the wallet's two records of it disagree, so check wallet_receipts)" : ""}`,
140051
+ { requestId, sendState: "sent", ...txFields }
140001
140052
  );
140002
140053
  }
140003
140054
  const how = sendNowWords(state2, intent);
140004
140055
  return refuse12(
140005
140056
  state2 === "sending" ? "mcp_unshield_cancel_sending" : "mcp_unshield_cancel_single",
140006
- `${what} ${how}; a send of one exit cannot be stopped; nothing was changed`,
140007
- `Nothing was stopped. Tell the user ${what} ${how} and cannot be cancelled; ${ask}`,
140008
- { requestId, sendState: state2 ?? record.state }
140057
+ `${what} ${how}${sent.tx ? ` (transaction ${sent.tx}; the wallet confirms it)` : ""}; a send of one exit cannot be stopped; nothing was changed`,
140058
+ state2 === "sending" ? `Nothing was stopped. Tell the user ${what} ${how} and cannot be cancelled${sent.tx ? `, with transaction ${sent.tx}` : ""}. ${sendRetryNext(requestId, sent, { amountAtomic: record.amountAtomic, to: record.mcpTo })}` : `Nothing was stopped. Tell the user ${what} ${how} and cannot be cancelled; ${ask}`,
140059
+ { requestId, sendState: state2 ?? record.state, ...txFields }
140009
140060
  );
140010
140061
  }
140011
140062
  async function cancelUnshield(args, { startedAt = Date.now(), progress: progress2 } = {}) {
@@ -157972,7 +158023,41 @@ var PROGRESS = /^(downloading the setup files|tor: |history: )/;
157972
158023
  var LABEL = /^ {2}([a-z][a-z -]{1,12}?)( {2,}|(?<=^ {2}.{12}) )(\S.*)$/;
157973
158024
  var COMMAND = /^ {2}(npx |shinjuku |claude |codex |cursor |\$ )/;
157974
158025
  var NEXT = /^(Next|Status|Home|Then): ?(.*)$/;
157975
- function paintSetupLine(line, paint2) {
158026
+ var NOT_PROSE = /^\s|[\\/`$]|\s--|\bnpx\b|\biwr\b|\bshinjuku [a-z]/;
158027
+ var wrapWidth = (columns) => Number.isSafeInteger(columns) && columns >= 40 ? columns - 1 : void 0;
158028
+ function wrapWords(text3, width) {
158029
+ const lines = [];
158030
+ let line = "";
158031
+ for (const word2 of text3.split(" ")) {
158032
+ if (line && `${line} ${word2}`.length > width) {
158033
+ lines.push(line);
158034
+ line = word2;
158035
+ } else line = line ? `${line} ${word2}` : word2;
158036
+ }
158037
+ if (line) lines.push(line);
158038
+ return lines;
158039
+ }
158040
+ function proseKind(line) {
158041
+ if (NOT_PROSE.test(line)) return void 0;
158042
+ if (DONE.test(line)) return { mark: ["\u25C6 ", "crim"], cls: "verm" };
158043
+ if (FAIL.test(line)) return { mark: ["\u25CB ", "crim"], cls: "crim" };
158044
+ if (WARN.test(line)) return { mark: ["! ", "verm"], cls: "verm" };
158045
+ if (STEP.test(line)) return { mark: ["\u25C6 ", "crim"], cls: "bone" };
158046
+ if (PROGRESS.test(line) || COMMAND.test(line) || LABEL.test(line)) return void 0;
158047
+ if (NEXT.test(line)) return { cls: "bone", next: true };
158048
+ return { cls: "dim" };
158049
+ }
158050
+ function paintSetupLine(line, paint2, columns) {
158051
+ const width = wrapWidth(columns);
158052
+ const kind = width && line.length > width && !line.includes("\x1B[") ? proseKind(line) : void 0;
158053
+ if (kind) {
158054
+ const indent = kind.mark ? " " : "";
158055
+ const pieces = wrapWords(line, width - indent.length);
158056
+ if (pieces.length > 1) {
158057
+ const first = kind.mark ? paint2([kind.mark, [pieces[0], kind.cls]]) : kind.next ? paintSetupLine(pieces[0], paint2) : paint2([[pieces[0], kind.cls]]);
158058
+ return [first, ...pieces.slice(1).map((piece) => paint2([[indent, ""], [piece, kind.cls]]))].join("\n");
158059
+ }
158060
+ }
157976
158061
  if (line === "" || line.includes("\x1B[")) return line;
157977
158062
  if (DONE.test(line)) return paint2([["\u25C6 ", "crim"], [line, "verm"]]);
157978
158063
  if (FAIL.test(line)) return paint2([["\u25CB ", "crim"], [line, "crim"]]);
@@ -157990,20 +158075,20 @@ var THEME_CHILD_ENV = "SHINJUKU_WALLET_THEMED";
157990
158075
  function terminalThemeOn({ stdout = process.stdout, env = process.env } = {}) {
157991
158076
  return Boolean(stdout?.isTTY) && (env.CI ?? "") === "" && env.TERM !== "dumb";
157992
158077
  }
157993
- function paintChunk(chunk, paint2) {
158078
+ function paintChunk(chunk, paint2, columns) {
157994
158079
  if (typeof chunk !== "string") return chunk;
157995
158080
  const text3 = chunk;
157996
158081
  if (text3.includes("\x1B") || text3.length <= 2 || !/[A-Za-z]/.test(text3)) return chunk;
157997
158082
  return text3.split("\n").map((line) => {
157998
158083
  const [, back, rest] = /^(\r*)([^]*)$/.exec(line);
157999
- return rest.trim() === "" ? line : `${back}${paintSetupLine(rest, paint2)}`;
158084
+ return rest.trim() === "" ? line : `${back}${paintSetupLine(rest, paint2, back ? void 0 : columns)}`;
158000
158085
  }).join("\n");
158001
158086
  }
158002
158087
  function themeStreams(paint2, streams = [process.stdout, process.stderr]) {
158003
158088
  const undo = streams.map((stream) => {
158004
158089
  const original = stream.write;
158005
158090
  stream.write = function themedWrite(chunk, ...rest) {
158006
- return original.call(stream, paintChunk(chunk, paint2), ...rest);
158091
+ return original.call(stream, paintChunk(chunk, paint2, stream.isTTY ? stream.columns : void 0), ...rest);
158007
158092
  };
158008
158093
  return () => {
158009
158094
  stream.write = original;
@@ -158030,10 +158115,16 @@ function commandFooter(columns = 80, { stream = process.stdout, env = process.en
158030
158115
  ` : "";
158031
158116
  }
158032
158117
  function themedIo(io, paint2) {
158033
- const draw = (text3) => String(text3).split("\n").map((line) => paintSetupLine(line, paint2)).join("\n");
158034
- return { ...io, out: (text3) => io.out(draw(text3)), err: (text3) => io.err(draw(text3)) };
158035
- }
158036
- var themedAsk = (ask, paint2) => (prompt, ...rest) => String(prompt).startsWith("? ") || String(prompt).includes("\x1B[") ? ask(prompt, ...rest) : ask(paint2([["? ", "crim"], [String(prompt), "bone"]]), ...rest);
158118
+ const draw = (text3, stream) => String(text3).split("\n").map((line) => paintSetupLine(line, paint2, stream?.isTTY ? stream.columns : void 0)).join("\n");
158119
+ return { ...io, out: (text3) => io.out(draw(text3, process.stdout)), err: (text3) => io.err(draw(text3, process.stderr)) };
158120
+ }
158121
+ var themedAsk = (ask, paint2, stream = process.stdout) => (prompt, ...rest) => {
158122
+ const text3 = String(prompt);
158123
+ if (text3.startsWith("? ") || text3.includes("\x1B[")) return ask(prompt, ...rest);
158124
+ const width = stream?.isTTY ? wrapWidth(stream.columns) : void 0;
158125
+ const pieces = width && text3.length + 2 > width && !NOT_PROSE.test(text3) ? wrapWords(text3, width - 2) : [text3];
158126
+ return ask([paint2([["? ", "crim"], [pieces[0], "bone"]]), ...pieces.slice(1).map((piece) => paint2([[" ", ""], [piece, "bone"]]))].join("\n"), ...rest);
158127
+ };
158037
158128
  function setupHeader(paint2, columns = 80, title = "set up the wallet") {
158038
158129
  return `\x1B[0m\x1B[2J\x1B[H${commandHeader(columns, title)}
158039
158130
  `;