shinjuku-shielded 0.3.159 → 0.3.161

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,19 +6,19 @@ 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.159. The npm packages `shinjuku-shield` and
9
+ This is version 0.3.161. 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
- npm install -g shinjuku@0.3.159
15
+ npm install -g shinjuku@0.3.161
16
16
  shinjuku setup
17
17
  ```
18
18
 
19
19
  Type the two lines as they are. `npx install shinjuku` does not work: npx
20
20
  reads `install` as the name of another npm package. To set up without
21
- installing, type `npx shinjuku@0.3.159 setup`.
21
+ installing, type `npx shinjuku@0.3.161 setup`.
22
22
 
23
23
  Setup makes the wallet on this machine, a private passphrase file, and the
24
24
  checked proof tools (about 191 MB), and writes `config.json`. It also writes
@@ -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.159 mcp
39
+ claude mcp add --scope user shinjuku -- npx -y --no-audit --no-fund shinjuku-shield@0.3.161 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.159", "mcp"] } } }
45
+ { "mcpServers": { "shinjuku": { "command": "npx", "args": ["-y", "--no-audit", "--no-fund", "shinjuku-shield@0.3.161", "mcp"] } } }
46
46
  ```
47
47
 
48
48
  On native Windows, if the host cannot find `npx`, use `"command": "cmd"` and
@@ -55,7 +55,7 @@ Codex (the Codex app and Codex CLI read the same file, `~/.codex/config.toml`):
55
55
  ```toml
56
56
  [mcp_servers.shinjuku]
57
57
  command = "npx"
58
- args = ["-y", "--no-audit", "--no-fund", "shinjuku-shield@0.3.159", "mcp"]
58
+ args = ["-y", "--no-audit", "--no-fund", "shinjuku-shield@0.3.161", "mcp"]
59
59
  startup_timeout_sec = 120
60
60
  tool_timeout_sec = 300
61
61
  ```
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": "cefda627cdfe4d2186085a15dd230f822175f98c4f3a10c4b5f1e1327114d627",
5
- "bytes": 9244374,
6
- "sourceCommit": "3e2a43e33968834e73c6ca8d2a2e1ba669ce04b8",
4
+ "sha256": "9bbc535c39d7f3ca35992be853e343d8374075618e0130ef2ab4f526a300b091",
5
+ "bytes": 9260169,
6
+ "sourceCommit": "157e988ee5f4ca6c609f49b296feabd64217eba7",
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-04T03:27:04.696Z"
1041
+ "publishedAt": "2026-11-04T06:13:30.270Z"
1042
1042
  }
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "shinjuku-shielded",
3
- "version": "0.3.159",
3
+ "version": "0.3.161",
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": "cefda627",
7
- "shinjukuSha256": "cefda627cdfe4d2186085a15dd230f822175f98c4f3a10c4b5f1e1327114d627",
6
+ "shinjukuRelease": "9bbc535c",
7
+ "shinjukuSha256": "9bbc535c39d7f3ca35992be853e343d8374075618e0130ef2ab4f526a300b091",
8
8
  "license": "SEE LICENSE IN LICENSE.txt",
9
9
  "author": "Shinjuku Shielded",
10
10
  "homepage": "https://github.com/ShinjukuStaition/shinjuku-shield#readme",
@@ -61,7 +61,7 @@ var __toCommonJS = (mod7) => __copyProps(__defProp({}, "__esModule", { value: tr
61
61
  import { readFileSync as realReadFileSync } from "node:fs";
62
62
  import { relative, resolve, sep } from "node:path";
63
63
  import { fileURLToPath } from "node:url";
64
- import { accessSync, appendFileSync, chmodSync, closeSync, constants, copyFileSync, createReadStream, createWriteStream, existsSync, fchmodSync, fstatSync, fsyncSync, linkSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFile, readSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, rmdirSync, statSync, statfsSync, truncateSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
64
+ import { accessSync, appendFileSync, chmodSync, closeSync, constants, copyFileSync, createReadStream, createWriteStream, existsSync, fchmodSync, fstatSync, fsyncSync, linkSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFile, readSync, readdirSync, realpathSync, renameSync, rmSync, rmdirSync, statSync, statfsSync, truncateSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
65
65
  function readFileSync(path, options) {
66
66
  const target = typeof path === "string" ? path : path instanceof URL ? fileURLToPath(path) : void 0;
67
67
  if (target !== void 0) {
@@ -48011,12 +48011,12 @@ var init_proxy = __esm({
48011
48011
  TOR_DEB_KEY = "https://deb.torproject.org/torproject.org/A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89.asc";
48012
48012
  TOR_DEB_KEYRING = "/usr/share/keyrings/deb.torproject.org-keyring.gpg";
48013
48013
  TOR_INSTALL = Object.freeze({
48014
- windows: `iwr ${TOR_EXPERT_BUNDLE_URL} -OutFile $env:TEMP\\tor.tar.gz; if ((Get-FileHash $env:TEMP\\tor.tar.gz -Algorithm SHA256).Hash -ne '${TOR_EXPERT_BUNDLE_SHA256}') { Remove-Item $env:TEMP\\tor.tar.gz; throw 'The Tor download does not match its pinned sha256, so it was deleted and not run' }; mkdir -Force $env:LOCALAPPDATA\\tor | Out-Null; & $env:SystemRoot\\System32\\tar.exe -xzf $env:TEMP\\tor.tar.gz -C $env:LOCALAPPDATA\\tor; & $env:LOCALAPPDATA\\tor\\tor\\tor.exe --HTTPTunnelPort '127.0.0.1:9080 IsolateDestAddr' --SocksPort 0`,
48014
+ windows: `iwr ${TOR_EXPERT_BUNDLE_URL} -OutFile $env:TEMP\\tor.tar.gz; if ((Get-FileHash $env:TEMP\\tor.tar.gz -Algorithm SHA256).Hash -ne '${TOR_EXPERT_BUNDLE_SHA256}') { Remove-Item $env:TEMP\\tor.tar.gz; throw 'The Tor download does not match its pinned sha256, so it was deleted and not run' }; mkdir -Force $env:LOCALAPPDATA\\tor | Out-Null; & $env:SystemRoot\\System32\\tar.exe -xzf $env:TEMP\\tor.tar.gz -C $env:LOCALAPPDATA\\tor; Start-Process -WindowStyle Hidden -FilePath $env:LOCALAPPDATA\\tor\\tor\\tor.exe -ArgumentList '--HTTPTunnelPort "127.0.0.1:9080 IsolateDestAddr" --SocksPort 0'`,
48015
48015
  macos: `brew install tor && (grep -qxF '${TUNNEL_LINE}' "$(brew --prefix)/etc/tor/torrc" || echo '${TUNNEL_LINE}' >> "$(brew --prefix)/etc/tor/torrc") && brew services restart tor`,
48016
48016
  linux: `sudo apt install -y apt-transport-https gnupg wget && wget -qO- ${TOR_DEB_KEY} | gpg --dearmor | sudo tee ${TOR_DEB_KEYRING} >/dev/null && printf 'Types: deb\\nURIs: https://deb.torproject.org/torproject.org/\\nSuites: %s\\nComponents: main\\nSigned-By: ${TOR_DEB_KEYRING}\\n' "$(. /etc/os-release && echo "$VERSION_CODENAME")" | sudo tee /etc/apt/sources.list.d/tor.sources >/dev/null && sudo apt update && sudo apt install -y tor deb.torproject.org-keyring && (grep -qxF '${TUNNEL_LINE}' /etc/tor/torrc || echo '${TUNNEL_LINE}' | sudo tee -a /etc/tor/torrc) && sudo service tor restart`
48017
48017
  });
48018
48018
  torStart = (platform = process.platform, tunnelHost) => {
48019
- const base = platform === "win32" ? { where: "PowerShell (keep its window open while the wallet runs)", command: TOR_INSTALL.windows } : platform === "darwin" ? { where: "Terminal", command: TOR_INSTALL.macos } : { where: "a terminal", command: TOR_INSTALL.linux };
48019
+ const base = platform === "win32" ? { where: "PowerShell (Tor then runs in the background, so you can close that window)", command: TOR_INSTALL.windows } : platform === "darwin" ? { where: "Terminal", command: TOR_INSTALL.macos } : { where: "a terminal", command: TOR_INSTALL.linux };
48020
48020
  return LOCAL_TUNNEL.test(tunnelHost ?? "") ? { ...base, command: base.command.replaceAll("127.0.0.1:9080", tunnelHost) } : base;
48021
48021
  };
48022
48022
  LOCAL_TUNNEL = /^127\.0\.0\.1:\d{1,5}$/;
@@ -48036,12 +48036,12 @@ ${command}`;
48036
48036
  TOR_WINDOW = Object.freeze({ win32: "PowerShell", darwin: "Terminal" });
48037
48037
  torNotRunningMessage = (address4, platform = process.platform, { fromConfig = false } = {}) => {
48038
48038
  const { command } = torStart(platform);
48039
- const keep = platform === "win32" ? " and keep that window open" : "";
48039
+ const keep = platform === "win32" ? " (Tor then runs in the background, so you can close that window)" : "";
48040
48040
  return `tor_not_running: Tor is not running on this machine, so nothing was sent.${fromConfig ? TOR_FROM_CONFIG_SENTENCE : ""} Tor hides your IP address. Start it with the command below in ${TOR_WINDOW[platform] ?? "a terminal"}${keep}:
48041
48041
  ${command}`;
48042
48042
  };
48043
- torUpdateHow = (platform = process.platform) => platform === "win32" ? `${TOR_STOP_OLD_WINDOWS} Then start a newer Tor with the command below in PowerShell and keep that window open` : `Install a newer Tor with the command below in ${TOR_WINDOW[platform] ?? "a terminal"}`;
48044
- TOR_STOP_OLD_WINDOWS = "Stop your old Tor first: close the window it runs in, or for a Tor in WSL run wsl -u root service tor stop .";
48043
+ torUpdateHow = (platform = process.platform) => platform === "win32" ? `${TOR_STOP_OLD_WINDOWS} Then start a newer Tor with the command below in PowerShell (it runs in the background, so you can close that window)` : `Install a newer Tor with the command below in ${TOR_WINDOW[platform] ?? "a terminal"}`;
48044
+ TOR_STOP_OLD_WINDOWS = "Stop your old Tor first: in PowerShell run Stop-Process -Name tor , or for a Tor in WSL run wsl -u root service tor stop .";
48045
48045
  torTooOldWhy = (version3) => `Your Tor program is too old (${version3}). It has a known fault that can link two of your payments, so the wallet sent nothing.`;
48046
48046
  torTooOldMessage = (version3, platform = process.platform, { fromConfig = false, tunnelHost } = {}) => {
48047
48047
  const { command } = torStart(platform, tunnelHost);
@@ -54272,8 +54272,22 @@ function hostShellWord(word, platform = process.platform) {
54272
54272
  return `"${word}"`;
54273
54273
  }
54274
54274
  function hostConfigFile(target, { platform = process.platform, env = process.env, home = homedir() } = {}) {
54275
+ const set = (name) => typeof env[name] === "string" && env[name].trim() ? env[name].trim() : void 0;
54275
54276
  if (target === "cursor") return join8(home, ".cursor", "mcp.json");
54276
54277
  if (target === "codex") return join8(env.CODEX_HOME || join8(home, ".codex"), "config.toml");
54278
+ if (target === "vscode") {
54279
+ const appData = platform === "win32" ? env.APPDATA || join8(home, "AppData", "Roaming") : platform === "darwin" ? join8(home, "Library", "Application Support") : env.XDG_CONFIG_HOME || join8(home, ".config");
54280
+ return join8(appData, "Code", "User", "mcp.json");
54281
+ }
54282
+ if (target === "devin") {
54283
+ return join8(platform === "win32" ? join8(home, "AppData", "Roaming", "devin") : join8(env.XDG_CONFIG_HOME || join8(home, ".config"), "devin"), "mcp_config.json");
54284
+ }
54285
+ if (target === "cline") {
54286
+ return set("CLINE_MCP_SETTINGS_PATH") ?? join8(set("CLINE_DATA_DIR") ?? join8(set("CLINE_DIR") ?? join8(home, ".cline"), "data"), "settings", "cline_mcp_settings.json");
54287
+ }
54288
+ if (target === "continue") return join8(set("CONTINUE_GLOBAL_DIR") ? resolve6(set("CONTINUE_GLOBAL_DIR")) : join8(home, ".continue"), "mcpServers", `${MCP_SERVER_NAME}.yaml`);
54289
+ if (target === "gemini") return join8(set("GEMINI_CLI_HOME") ?? home, ".gemini", "settings.json");
54290
+ if (target === "antigravity") return join8(home, ".gemini", "config", "mcp_config.json");
54277
54291
  if (target === "claude-desktop") {
54278
54292
  if (platform === "win32") return join8(env.APPDATA || join8(home, "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
54279
54293
  if (platform === "darwin") return join8(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
@@ -54281,8 +54295,17 @@ function hostConfigFile(target, { platform = process.platform, env = process.env
54281
54295
  }
54282
54296
  return void 0;
54283
54297
  }
54284
- function hostConfigText({ home, env = process.env, platform = process.platform, npmVersion, skip = /* @__PURE__ */ new Set() } = {}) {
54298
+ function hostConfigText({ home, env = process.env, platform = process.platform, npmVersion, skip = /* @__PURE__ */ new Set(), userHome = homedir() } = {}) {
54285
54299
  const entry = mcpServerEntry({ home, platform, npmVersion });
54300
+ const more = ["vscode", "devin", "cline", "continue", "gemini", "antigravity"].filter((target) => !skip.has(target)).flatMap((target) => {
54301
+ const file = hostConfigFile(target, { platform, env, home: userHome });
54302
+ if (target === "continue") return [`${HOST_NAMES[target]}: save these lines as ${file}:`, ...continueBlockText(hostEntry(target, entry)).trimEnd().split("\n").map((line) => ` ${line}`)];
54303
+ const key = hostServersKey(target);
54304
+ return [
54305
+ `${HOST_NAMES[target]} (${file}): in that file, add these lines inside the { } after "${key}" (put a comma after the entry before them; a file with no "${key}" gets "${key}": { } first):`,
54306
+ ...`${JSON.stringify(MCP_SERVER_NAME)}: ${JSON.stringify(hostEntry(target, entry), null, 2)}`.split("\n").map((line) => ` ${line}`)
54307
+ ];
54308
+ });
54286
54309
  const member = `${JSON.stringify(MCP_SERVER_NAME)}: ${JSON.stringify(entry, null, 2)}`;
54287
54310
  const files = [["claude-desktop", "Claude Desktop (claude_desktop_config.json)"], ["cursor", "Cursor (~/.cursor/mcp.json)"]].filter(([target]) => !skip.has(target)).map(([, label]) => label);
54288
54311
  return [
@@ -54294,9 +54317,48 @@ function hostConfigText({ home, env = process.env, platform = process.platform,
54294
54317
  ...skip.has("codex") ? [] : [
54295
54318
  "Codex (the Codex app and Codex CLI), at the end of ~/.codex/config.toml:",
54296
54319
  ...codexTomlBlock(entry).trimEnd().split("\n").map((line) => ` ${line}`)
54297
- ]
54320
+ ],
54321
+ ...more
54322
+ ].join("\n");
54323
+ }
54324
+ function continueBlockText(entry) {
54325
+ return [
54326
+ "# The Shinjuku Shield wallet's server, written by its setup.",
54327
+ ...CONTINUE_HEADER,
54328
+ ` - ${JSON.stringify({ name: MCP_SERVER_NAME, ...entry })}`,
54329
+ ""
54298
54330
  ].join("\n");
54299
54331
  }
54332
+ function continueServersIn(text3) {
54333
+ const lines = text3.replace(/^/, "").split(/\r?\n/).filter((line) => line.trim() && !/^\s*#/.test(line));
54334
+ const items = lines.filter((line) => /^\s*-\s/.test(line));
54335
+ if (items.length !== 1 || !sameJson(lines.filter((line) => !/^\s*-\s/.test(line)).map((line) => line.trim()), CONTINUE_HEADER)) return void 0;
54336
+ let value2;
54337
+ try {
54338
+ value2 = JSON.parse(items[0].replace(/^\s*-\s*/, ""));
54339
+ } catch {
54340
+ return void 0;
54341
+ }
54342
+ if (!isPlainObject(value2) || typeof value2.name !== "string") return void 0;
54343
+ const { name, ...entry } = value2;
54344
+ return { [name]: entry };
54345
+ }
54346
+ function addToContinueHost(file, entry, { now = /* @__PURE__ */ new Date(), force = false, platform = process.platform, restrict = restrictToOwnerWindows } = {}) {
54347
+ const result = changeJsonFile(file, (text3) => {
54348
+ if (!text3.trim()) return continueBlockText(entry);
54349
+ const servers = continueServersIn(text3);
54350
+ if (!servers) {
54351
+ throw new Error("setup_host_config_unreadable: Continue's block file for the wallet was changed by hand, so setup left it alone; add the printed entry by hand");
54352
+ }
54353
+ const [[name, existing]] = Object.entries(servers);
54354
+ if (name === MCP_SERVER_NAME && sameJson(existing, entry)) return void 0;
54355
+ if (!force && !(name === MCP_SERVER_NAME && isOlderSetupEntry(existing, entry))) {
54356
+ throw new Error(`setup_host_config_conflict: Continue's block file for the wallet holds a different server, so setup left it alone; rerun setup with --force to replace it (the old file is backed up first), or edit it by hand`);
54357
+ }
54358
+ return continueBlockText(entry);
54359
+ }, { now, platform, restrict, codec: TEXT_CODEC });
54360
+ return { file, ...result, replaced: [] };
54361
+ }
54300
54362
  function codexTomlBlock(entry) {
54301
54363
  const lines = [
54302
54364
  `[mcp_servers.${tomlKey(MCP_SERVER_NAME)}]`,
@@ -54581,31 +54643,32 @@ function restrictToOwnerWindows(file, { run: run2 = execFileSync2 } = {}) {
54581
54643
  throw new Error("setup_host_config_acl_failed: setup could not limit the file to only you on this computer (icacls), so it did not write it; add the printed entry by hand");
54582
54644
  }
54583
54645
  }
54584
- function addToJsonHost(file, entry, { now = /* @__PURE__ */ new Date(), force = false, platform = process.platform, restrict = restrictToOwnerWindows } = {}) {
54646
+ function addToJsonHost(file, entry, { now = /* @__PURE__ */ new Date(), force = false, platform = process.platform, restrict = restrictToOwnerWindows, key = "mcpServers" } = {}) {
54585
54647
  try {
54586
- return addToStrictJsonHost(file, entry, { now, force, platform, restrict });
54648
+ return addToStrictJsonHost(file, entry, { now, force, platform, restrict, key });
54587
54649
  } catch (error) {
54588
54650
  if (error?.jsonc !== true) throw error;
54589
- const result = changeJsonFile(file, (text3) => jsoncWithMember(text3, entry, { force }), { now, platform, restrict, codec: TEXT_CODEC });
54651
+ const result = changeJsonFile(file, (text3) => jsoncWithMember(text3, entry, { force, key }), { now, platform, restrict, codec: TEXT_CODEC });
54590
54652
  return { file, ...result, replaced: [] };
54591
54653
  }
54592
54654
  }
54593
- function addToStrictJsonHost(file, entry, { now, force, platform, restrict }) {
54655
+ function addToStrictJsonHost(file, entry, { now, force, platform, restrict, key }) {
54594
54656
  let replaced = [];
54595
54657
  const result = changeJsonFile(file, (current) => {
54596
- if (current.mcpServers !== void 0 && !isPlainObject(current.mcpServers)) {
54597
- throw new Error("setup_host_config_unreadable: the host's config file has an mcpServers that is not a JSON object, so setup left it alone; fix it or add the printed entry by hand");
54658
+ const held = current[key];
54659
+ if (held !== void 0 && !isPlainObject(held)) {
54660
+ throw new Error(`setup_host_config_unreadable: the host's config file has a ${key} that is not a JSON object, so setup left it alone; fix it or add the printed entry by hand`);
54598
54661
  }
54599
- replaced = OLD_MCP_SERVER_NAMES.filter((name) => current.mcpServers !== void 0 && Object.hasOwn(current.mcpServers, name));
54600
- const existing = current.mcpServers !== void 0 && Object.hasOwn(current.mcpServers, MCP_SERVER_NAME) ? current.mcpServers[MCP_SERVER_NAME] : void 0;
54662
+ replaced = OLD_MCP_SERVER_NAMES.filter((name) => held !== void 0 && Object.hasOwn(held, name));
54663
+ const existing = held !== void 0 && Object.hasOwn(held, MCP_SERVER_NAME) ? held[MCP_SERVER_NAME] : void 0;
54601
54664
  if (existing !== void 0) {
54602
54665
  if (sameJson(existing, entry) && replaced.length === 0) return void 0;
54603
54666
  if (!force && !sameJson(existing, entry) && !isOlderSetupEntry(existing, entry)) {
54604
54667
  throw new Error(`setup_host_config_conflict: the host's config file already has a different "${MCP_SERVER_NAME}" server, so setup left it alone; rerun setup with --force to replace it (the old file is backed up first), or edit it by hand`);
54605
54668
  }
54606
54669
  }
54607
- const servers = Object.fromEntries(Object.entries(current.mcpServers ?? {}).filter(([name]) => !OLD_MCP_SERVER_NAMES.includes(name)));
54608
- return { ...current, mcpServers: { ...servers, [MCP_SERVER_NAME]: entry } };
54670
+ const servers = Object.fromEntries(Object.entries(held ?? {}).filter(([name]) => !OLD_MCP_SERVER_NAMES.includes(name)));
54671
+ return { ...current, [key]: { ...servers, [MCP_SERVER_NAME]: entry } };
54609
54672
  }, { now, platform, restrict });
54610
54673
  return { file, ...result, replaced };
54611
54674
  }
@@ -54725,13 +54788,13 @@ function memberSlot(src, object) {
54725
54788
  }
54726
54789
  return { at: close, comma: src[at] === "," ? void 0 : last.value.end };
54727
54790
  }
54728
- function jsoncWithMember(text3, entry, { force }) {
54791
+ function jsoncWithMember(text3, entry, { force, key = "mcpServers" }) {
54729
54792
  const unreadable = () => {
54730
54793
  throw new Error("setup_host_config_unreadable: the host's config file has comments and names the wallet's server in a way setup does not change by itself, so setup left it alone; add the printed entry by hand");
54731
54794
  };
54732
54795
  const root = parseJsonc(text3);
54733
54796
  if (root.kind !== "object") unreadable();
54734
- const servers = root.members.filter((member) => member.key === "mcpServers");
54797
+ const servers = root.members.filter((member) => member.key === key);
54735
54798
  if (servers.length > 1 || servers[0] && servers[0].value.kind !== "object") unreadable();
54736
54799
  const holder = servers[0]?.value;
54737
54800
  if (holder && holder.members.some((member) => OLD_MCP_SERVER_NAMES.includes(member.key))) unreadable();
@@ -54752,7 +54815,7 @@ ${indent}`) });
54752
54815
  const slot3 = memberSlot(text3, object);
54753
54816
  const indent = `${indentAt(text3, object.start)} `;
54754
54817
  const member = holder ? `${JSON.stringify(MCP_SERVER_NAME)}: ${JSON.stringify(entry, null, 2).replace(/\n/g, `
54755
- ${indent}`)}` : `"mcpServers": ${JSON.stringify({ [MCP_SERVER_NAME]: entry }, null, 2).replace(/\n/g, `
54818
+ ${indent}`)}` : `${JSON.stringify(key)}: ${JSON.stringify({ [MCP_SERVER_NAME]: entry }, null, 2).replace(/\n/g, `
54756
54819
  ${indent}`)}`;
54757
54820
  const lineStart = text3.lastIndexOf("\n", slot3.at - 1) + 1;
54758
54821
  edits.push(/^[ \t]*$/.test(text3.slice(lineStart, slot3.at)) ? { at: lineStart, end: lineStart, text: `${indent}${member}
@@ -54764,11 +54827,34 @@ ${indentAt(text3, slot3.at)}` });
54764
54827
  let next = text3;
54765
54828
  for (const edit of edits.sort((a, b) => b.at - a.at)) next = `${next.slice(0, edit.at)}${edit.text}${next.slice(edit.end)}`;
54766
54829
  const after = parseJsonc(next).value;
54767
- const want = { ...root.value, mcpServers: { ...root.value.mcpServers ?? {}, [MCP_SERVER_NAME]: entry } };
54830
+ const want = { ...root.value, [key]: { ...root.value[key] ?? {}, [MCP_SERVER_NAME]: entry } };
54768
54831
  if (!sameJson(after, want)) unreadable();
54769
54832
  return next;
54770
54833
  }
54771
- function changeJsonFile(file, change, { now, platform, restrict, codec = JSON_CODEC }) {
54834
+ function changeJsonFile(file, change, options) {
54835
+ const target = realFileOf(file);
54836
+ for (let attempt = 0; attempt < 2; attempt += 1) {
54837
+ const result = changeFileOnce(target, change, options, attempt);
54838
+ if (result !== RACED) return result;
54839
+ }
54840
+ throw new Error("setup_host_config_changed: the app changed its settings file while setup wrote it, so setup left the file as the app wrote it; run setup again");
54841
+ }
54842
+ function realFileOf(file) {
54843
+ let link2;
54844
+ try {
54845
+ link2 = lstatSync(file).isSymbolicLink();
54846
+ } catch (error) {
54847
+ if (error?.code === "ENOENT") return file;
54848
+ throw error;
54849
+ }
54850
+ if (!link2) return file;
54851
+ try {
54852
+ return realpathSync(file);
54853
+ } catch {
54854
+ throw new Error("setup_host_config_link_broken: the host's config file is a link to a file that does not exist, so setup did not change it; fix the link or add the printed entry by hand");
54855
+ }
54856
+ }
54857
+ function changeFileOnce(file, change, { now, platform, restrict, codec = JSON_CODEC }, attempt) {
54772
54858
  let current = codec.empty();
54773
54859
  let bytes2;
54774
54860
  if (existsSync(file)) {
@@ -54781,7 +54867,7 @@ function changeJsonFile(file, change, { now, platform, restrict, codec = JSON_CO
54781
54867
  let backup;
54782
54868
  if (bytes2 !== void 0) {
54783
54869
  mode = statSync(file).mode & 511;
54784
- backup = `${file}.bak-${now.toISOString().replace(/[:.]/g, "-")}`;
54870
+ backup = `${file}.bak-${now.toISOString().replace(/[:.]/g, "-")}${attempt ? `-${attempt + 1}` : ""}`;
54785
54871
  const fd = openSync(backup, "wx", 384);
54786
54872
  let restrictFailed = false;
54787
54873
  try {
@@ -54813,6 +54899,11 @@ function changeJsonFile(file, change, { now, platform, restrict, codec = JSON_CO
54813
54899
  writeFileSync(temp, codec.write(next), { mode: mode ?? 384, flag: "wx" });
54814
54900
  }
54815
54901
  if (mode !== void 0 && platform !== "win32") chmodSync(temp, mode);
54902
+ const nowBytes = existsSync(file) ? readFileSync(file) : void 0;
54903
+ if (bytes2 === void 0 ? nowBytes !== void 0 : nowBytes === void 0 || !nowBytes.equals(bytes2)) {
54904
+ rmSync(temp, { force: true });
54905
+ return RACED;
54906
+ }
54816
54907
  renameSync(temp, file);
54817
54908
  } catch (error) {
54818
54909
  rmSync(temp, { force: true });
@@ -54820,7 +54911,7 @@ function changeJsonFile(file, change, { now, platform, restrict, codec = JSON_CO
54820
54911
  }
54821
54912
  return { changed: true, ...backup ? { backup } : {} };
54822
54913
  }
54823
- var import_web396, CONFIG_SCHEMA, CONFIG_FILE, NPM_PACKAGE_NAMES, COMMAND_PACKAGE, NPX_PACKAGE, NPX_INSTALL_FLAGS, MCP_SERVER_NAME, OLD_MCP_SERVER_NAMES, P03_BINDING, PRODUCTION, RETIRED_POOLS, NO_LIMIT_ATOMIC, DEFAULT_CAPS, OLD_DEFAULT_SETUP_CAPS, OLD_DEFAULT_WALLET_BUDGETS, capText, POSITIVE_ATOMIC, HOME_FOLDER, OLDER_HOME_FOLDER, defaultHomeWords, configPath, homeWalletPath, invalid, isPubkey, MAX_OWN_ADDRESSES, isOwnAddressText, salvagePath, clearConfigSalvage, HOST_TARGETS, HOST_NAMES, CODEX_STARTUP_TIMEOUT_SEC, CODEX_TOOL_TIMEOUT_SEC, PINNED_PACKAGE, LAUNCHER_PACKAGE, NPM_VERSION, knownNpmVersion, RELEASES_URL, npmStartEnvironment, NPM_START_KEYS, defaultWalletHome, WINDOWS_SHELL_CODE, tomlString, tomlKey, codexEntryOf, isPlainObject, sameJson, claudeCodeEntry, READ_ONLY_TOOL_NAMES, claudeCodeAllowRules, claudeCodeSettingsFile, UNREADABLE_JSON, JSON_CODEC, indentAt, TEXT_CODEC;
54914
+ var import_web396, CONFIG_SCHEMA, CONFIG_FILE, NPM_PACKAGE_NAMES, COMMAND_PACKAGE, NPX_PACKAGE, NPX_INSTALL_FLAGS, MCP_SERVER_NAME, OLD_MCP_SERVER_NAMES, P03_BINDING, PRODUCTION, RETIRED_POOLS, NO_LIMIT_ATOMIC, DEFAULT_CAPS, OLD_DEFAULT_SETUP_CAPS, OLD_DEFAULT_WALLET_BUDGETS, capText, POSITIVE_ATOMIC, HOME_FOLDER, OLDER_HOME_FOLDER, defaultHomeWords, configPath, homeWalletPath, invalid, isPubkey, MAX_OWN_ADDRESSES, isOwnAddressText, salvagePath, clearConfigSalvage, HOST_TARGETS, HOST_NAMES, hostServersKey, hostEntry, CODEX_STARTUP_TIMEOUT_SEC, CODEX_TOOL_TIMEOUT_SEC, PINNED_PACKAGE, LAUNCHER_PACKAGE, NPM_VERSION, knownNpmVersion, RELEASES_URL, npmStartEnvironment, NPM_START_KEYS, defaultWalletHome, WINDOWS_SHELL_CODE, CONTINUE_HEADER, tomlString, tomlKey, codexEntryOf, isPlainObject, sameJson, claudeCodeEntry, READ_ONLY_TOOL_NAMES, claudeCodeAllowRules, claudeCodeSettingsFile, UNREADABLE_JSON, JSON_CODEC, indentAt, TEXT_CODEC, RACED;
54824
54915
  var init_config = __esm({
54825
54916
  "wallet/src/setup/config.mjs"() {
54826
54917
  "use strict";
@@ -54880,8 +54971,21 @@ var init_config = __esm({
54880
54971
  isOwnAddressText = (value2) => isPubkey(value2);
54881
54972
  salvagePath = (home) => join8(home, "config.salvage.json");
54882
54973
  clearConfigSalvage = (home) => rmSync(salvagePath(home), { force: true });
54883
- HOST_TARGETS = Object.freeze(["claude-code", "claude-desktop", "cursor", "codex"]);
54884
- HOST_NAMES = Object.freeze({ "claude-code": "Claude Code", "claude-desktop": "Claude Desktop", cursor: "Cursor", codex: "Codex" });
54974
+ HOST_TARGETS = Object.freeze(["claude-code", "claude-desktop", "cursor", "codex", "vscode", "devin", "cline", "continue", "gemini", "antigravity"]);
54975
+ HOST_NAMES = Object.freeze({
54976
+ "claude-code": "Claude Code",
54977
+ "claude-desktop": "Claude Desktop",
54978
+ cursor: "Cursor",
54979
+ codex: "Codex",
54980
+ vscode: "VS Code",
54981
+ devin: "Devin Desktop",
54982
+ cline: "Cline",
54983
+ continue: "Continue",
54984
+ gemini: "Gemini CLI",
54985
+ antigravity: "Antigravity"
54986
+ });
54987
+ hostServersKey = (target) => target === "vscode" ? "servers" : "mcpServers";
54988
+ hostEntry = (target, entry) => ["vscode", "cline", "continue"].includes(target) ? { type: "stdio", ...entry } : entry;
54885
54989
  CODEX_STARTUP_TIMEOUT_SEC = 120;
54886
54990
  CODEX_TOOL_TIMEOUT_SEC = 300;
54887
54991
  PINNED_PACKAGE = /^shinjuku(?:-shield(?:ed)?)?@(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$/;
@@ -54893,6 +54997,7 @@ var init_config = __esm({
54893
54997
  NPM_START_KEYS = ["npm_config_offline", "npm_config_prefer_offline"];
54894
54998
  defaultWalletHome = (home = homedir()) => setupHome({}, home);
54895
54999
  WINDOWS_SHELL_CODE = /[%$`"\r\n]/;
55000
+ CONTINUE_HEADER = Object.freeze(["name: Shinjuku Shield", "version: 0.0.1", "schema: v1", "mcpServers:"]);
54896
55001
  tomlString = (value2) => `"${String(value2).replace(/[\\"]/g, (c) => `\\${c}`).replace(/[\u0000-\u001f\u007f]/g, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, "0")}`)}"`;
54897
55002
  tomlKey = (key) => /^[A-Za-z0-9_-]+$/.test(key) ? key : tomlString(key);
54898
55003
  codexEntryOf = (server) => ({
@@ -54947,6 +55052,7 @@ var init_config = __esm({
54947
55052
  });
54948
55053
  indentAt = (src, at) => /^[ \t]*/.exec(src.slice(src.lastIndexOf("\n", at - 1) + 1))[0];
54949
55054
  TEXT_CODEC = Object.freeze({ empty: () => "", read: (text3) => text3, write: (next) => next });
55055
+ RACED = /* @__PURE__ */ Symbol("raced");
54950
55056
  }
54951
55057
  });
54952
55058
 
@@ -125660,6 +125766,8 @@ function clearBackupWithCode(home, code2) {
125660
125766
  return true;
125661
125767
  }
125662
125768
  function backupPlaces({ platform = process.platform, env = process.env } = {}) {
125769
+ const roots = cloudRoots({ platform, env, home: homedir2() });
125770
+ const notSynced = (list) => list.filter((place) => !cloudPlace(place.path, { platform, env, roots: () => roots }));
125663
125771
  const places = [];
125664
125772
  if (platform === "win32") {
125665
125773
  try {
@@ -125681,12 +125789,12 @@ function backupPlaces({ platform = process.platform, env = process.env } = {}) {
125681
125789
  } catch {
125682
125790
  }
125683
125791
  const profile = env.USERPROFILE ?? homedir2();
125684
- for (const [label, path] of [["Desktop", join29(profile, "Desktop")], ["Desktop", join29(profile, "OneDrive", "Desktop")], ["Documents", join29(profile, "Documents")]]) {
125792
+ for (const [label, path] of [["Desktop", join29(profile, "Desktop")], ["Documents", join29(profile, "Documents")]]) {
125685
125793
  if (isFolder(path) && !places.some((place) => place.path.toLowerCase() === path.toLowerCase())) {
125686
125794
  places.push({ label: `${label} (${path}; on this computer: it does not help if this computer breaks)`, path, removable: false });
125687
125795
  }
125688
125796
  }
125689
- return places;
125797
+ return notSynced(places);
125690
125798
  }
125691
125799
  let user = "";
125692
125800
  try {
@@ -125706,7 +125814,66 @@ function backupPlaces({ platform = process.platform, env = process.env } = {}) {
125706
125814
  for (const path of [join29(homedir2(), "Desktop"), homedir2()]) {
125707
125815
  if (isFolder(path)) places.push({ label: `${path} (on this computer: it does not help if this computer breaks)`, path, removable: false });
125708
125816
  }
125709
- return places;
125817
+ return notSynced(places);
125818
+ }
125819
+ function cloudPlace(place, { platform = process.platform, env = process.env, home = homedir2(), roots } = {}) {
125820
+ const text3 = String(place ?? "").trim();
125821
+ if (!text3) return void 0;
125822
+ const fold = platform === "win32" || platform === "darwin";
125823
+ const sep5 = platform === "win32" ? "\\" : "/";
125824
+ const norm = (path) => {
125825
+ const full = (platform === "win32" ? resolve25(localForm(path, platform)) : posix.resolve(path)).replace(/[\\/]+$/, "");
125826
+ return fold ? full.toLowerCase() : full;
125827
+ };
125828
+ const at = norm(text3);
125829
+ for (const root of (roots ?? (() => cloudRoots({ platform, env, home })))()) {
125830
+ const base = norm(root.path);
125831
+ if (base && (at === base || at.startsWith(`${base}${sep5}`))) return root.service;
125832
+ }
125833
+ for (const part of text3.split(/[\\/]+/).map((name) => name.trim().toLowerCase())) {
125834
+ if (part === "onedrive" || part.startsWith("onedrive - ") || part.startsWith("onedrive-")) return "OneDrive";
125835
+ if (part === "dropbox" || part.startsWith("dropbox (") || part.startsWith("dropbox-")) return "Dropbox";
125836
+ if (["my drive", "shared drives", "google drive"].includes(part) || part.startsWith("googledrive")) return "Google Drive";
125837
+ if (["iclouddrive", "icloud drive", "icloud photos", "mobile documents"].includes(part)) return "iCloud";
125838
+ }
125839
+ return void 0;
125840
+ }
125841
+ function cloudRoots({ platform = process.platform, env = process.env, home = homedir2() } = {}) {
125842
+ const roots = [];
125843
+ const add2 = (path, service) => {
125844
+ if (typeof path === "string" && path.trim()) roots.push({ path: path.trim(), service });
125845
+ };
125846
+ const dropbox = (file) => {
125847
+ let info;
125848
+ try {
125849
+ info = JSON.parse(readFileSync(file, "utf8"));
125850
+ } catch {
125851
+ return;
125852
+ }
125853
+ for (const account of Object.values(info ?? {})) add2(account?.path, "Dropbox");
125854
+ };
125855
+ if (platform === "win32") {
125856
+ for (const name of ["OneDrive", "OneDriveConsumer", "OneDriveCommercial"]) add2(env[name], "OneDrive");
125857
+ add2(join29(env.USERPROFILE ?? home, "OneDrive"), "OneDrive");
125858
+ try {
125859
+ const result = spawnSync3(
125860
+ "reg.exe",
125861
+ ["query", "HKCU\\Software\\Microsoft\\OneDrive\\Accounts", "/s", "/v", "UserFolder"],
125862
+ { windowsHide: true, encoding: "utf8", timeout: 1e4 }
125863
+ );
125864
+ for (const match of String(result.stdout ?? "").matchAll(/UserFolder\s+REG_\w+\s+(.+)/g)) add2(match[1], "OneDrive");
125865
+ } catch {
125866
+ }
125867
+ if (env.APPDATA) dropbox(join29(env.APPDATA, "Dropbox", "info.json"));
125868
+ if (env.LOCALAPPDATA) dropbox(join29(env.LOCALAPPDATA, "Dropbox", "info.json"));
125869
+ return roots;
125870
+ }
125871
+ dropbox(posix.join(home, ".dropbox", "info.json"));
125872
+ if (platform === "darwin") {
125873
+ add2(posix.join(home, "Library", "Mobile Documents"), "iCloud");
125874
+ add2(posix.join(home, "Library", "CloudStorage"), "a cloud service");
125875
+ }
125876
+ return roots;
125710
125877
  }
125711
125878
  async function saveBackup(home, folder, {
125712
125879
  removable,
@@ -125714,7 +125881,8 @@ async function saveBackup(home, folder, {
125714
125881
  platform = process.platform,
125715
125882
  restrict = restrictFolderToOwner,
125716
125883
  driveType = windowsDriveType,
125717
- mounts = posixMounts
125884
+ mounts = posixMounts,
125885
+ env = process.env
125718
125886
  } = {}) {
125719
125887
  const typed = String(folder ?? "").trim().replace(/^"(.*)"$/, "$1");
125720
125888
  const types = /* @__PURE__ */ new Map();
@@ -125726,85 +125894,173 @@ async function saveBackup(home, folder, {
125726
125894
  const noFolder = (at) => backupFailed(`there is no folder ${at}. Plug in the drive or type another folder`);
125727
125895
  const refuse13 = (found, at) => {
125728
125896
  if (found === "missing") throw noFolder(at);
125897
+ if (found === "link") throw backupFailed(LINK_WORDS(at));
125729
125898
  if (found) throw backupFailed(found === "unknown" ? UNKNOWN_DRIVE_WORDS(at) : NETWORK_PLACE_WORDS(at));
125730
125899
  };
125900
+ const roots = cloudRoots({ platform, env, home: homedir2() });
125901
+ const refuseCloud = (at) => {
125902
+ const service = cloudPlace(at, { platform, env, roots: () => roots });
125903
+ if (service) throw backupFailed(CLOUD_WORDS(at, service));
125904
+ };
125731
125905
  refuse13(networkPlace(typed, net4), typed);
125732
125906
  const place = localForm(resolve25(typed), platform);
125733
125907
  refuse13(networkPlace(place, net4), place);
125734
- refuse13(linkToNetwork(place, net4), place);
125735
- if (!isAbsolute7(place) || !isFolder(place)) throw noFolder(place);
125736
- const real = realLocal(place, net4, refuse13);
125737
- const files = backupFiles(home);
125738
- const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
125739
- let target = join29(real, `shinjuku-backup-${date}`);
125740
- for (let n = 2; existsSync(target); n += 1) target = join29(real, `shinjuku-backup-${date}-${n}`);
125741
- try {
125742
- mkdirSync(target, { mode: 448 });
125908
+ refuseCloud(place);
125909
+ const onWay = linkOnWay(place, platform);
125910
+ const fullPath = isAbsolute7(typed) && (platform !== "win32" || /^(?:[\\/]{2}[?.][\\/])?[A-Za-z]:[\\/]/.test(typed));
125911
+ if (onWay === "missing" && !fullPath) throw noFolder(place);
125912
+ const trail = { files: [], dirs: [] };
125913
+ let made;
125914
+ try {
125915
+ made = onWay === "missing" ? makeMissingFolder(place, net4, refuse13, noFolder, trail) : refuse13(onWay, place);
125916
+ return await saveInFolder();
125743
125917
  } catch (error) {
125744
- throw backupFailed(`the wallet could not make a folder in ${place} (${error?.code === "EACCES" || error?.code === "EPERM" ? "no permission to write there" : error?.code === "ENOSPC" ? "the drive is full" : "it cannot write there"}). Pick another place`);
125918
+ removeTrail(trail);
125919
+ throw error;
125745
125920
  }
125746
- const stillHere = (path) => {
125747
- let now;
125921
+ async function saveInFolder() {
125922
+ if (!isAbsolute7(place) || !isFolder(place)) throw noFolder(place);
125923
+ const real = realLocal(place, net4, refuse13);
125924
+ refuseCloud(real);
125925
+ if (made && !samePath(real, made.real, platform)) throw backupFailed(MOVED_WORDS(place));
125926
+ const files = backupFiles(home);
125927
+ const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
125928
+ let target = join29(real, `shinjuku-backup-${date}`);
125929
+ for (let n = 2; existsSync(target); n += 1) target = join29(real, `shinjuku-backup-${date}-${n}`);
125930
+ try {
125931
+ mkdirSync(target, { mode: 448 });
125932
+ trail.dirs.push(identityOf(target));
125933
+ } catch (error) {
125934
+ throw backupFailed(`the wallet could not make a folder in ${place} (${error?.code === "EACCES" || error?.code === "EPERM" ? "no permission to write there" : error?.code === "ENOSPC" ? "the drive is full" : "it cannot write there"}). Pick another place`);
125935
+ }
125936
+ const stillHere = (path) => {
125937
+ let now;
125938
+ try {
125939
+ now = localForm(realpathSync.native(path), platform);
125940
+ } catch {
125941
+ now = void 0;
125942
+ }
125943
+ if (!now || !samePath(now, path, platform)) throw backupFailed(MOVED_WORDS(place));
125944
+ refuse13(networkPlace(now, net4), now);
125945
+ refuseCloud(now);
125946
+ };
125947
+ stillHere(target);
125948
+ let access;
125748
125949
  try {
125749
- now = localForm(realpathSync.native(path), platform);
125950
+ access = restrict(target, platform);
125750
125951
  } catch {
125751
- now = void 0;
125952
+ access = "open";
125953
+ }
125954
+ if (access === "foreign") {
125955
+ throw backupFailed(`this folder can be opened by other accounts (${place}); pick another place`);
125956
+ }
125957
+ const ownerOnly = access === "owner";
125958
+ const saved = [];
125959
+ const names = /* @__PURE__ */ new Set();
125960
+ for (const file of files) {
125961
+ let name = basename6(file);
125962
+ for (let n = 2; names.has(name.toLowerCase()); n += 1) name = `${basename6(file, extname(file))}-${n}${extname(file)}`;
125963
+ names.add(name.toLowerCase());
125964
+ const copy = join29(target, name);
125965
+ stillHere(target);
125966
+ try {
125967
+ copyFileSync(file, copy, constants.COPYFILE_EXCL);
125968
+ trail.files.push(identityOf(copy));
125969
+ if (platform !== "win32") chmodSync(copy, 384);
125970
+ } catch (error) {
125971
+ throw backupFailed(`the copy to ${target} failed (${error?.code === "ENOSPC" ? "the drive is full" : "the drive did not take the file"}). Pick another place`);
125972
+ }
125973
+ stillHere(copy);
125974
+ saved.push({ name, source: file, sha256: sha256Of(file) });
125752
125975
  }
125753
- if (!now || !samePath(now, path, platform)) throw backupFailed(MOVED_WORDS(place));
125754
- refuse13(networkPlace(now, net4), now);
125755
- };
125756
- stillHere(target);
125757
- let access;
125758
- try {
125759
- access = restrict(target, platform);
125760
- } catch {
125761
- access = "open";
125976
+ for (const entry of saved) {
125977
+ if (sha256Of(join29(target, entry.name)) !== entry.sha256) throw backupFailed(`the copy in ${target} does not match the original. Pick another place`);
125978
+ }
125979
+ const code2 = cleared ? void 0 : backupCode();
125980
+ writeRecord(home, { savedAt: (/* @__PURE__ */ new Date()).toISOString(), folder: target, cleared, ...code2 ? { codeSha256: code2.sha256 } : {} });
125981
+ const warning = removable === true ? "Anyone who gets this USB stick can spend your money: keep it somewhere safe." : !ownerOnly ? "Other people who use this computer or drive may be able to open it: keep it somewhere only you can reach." : void 0;
125982
+ return { folder: target, files: saved.map(({ name, sha256: sha25610 }) => ({ name, sha256: sha25610 })), ownerOnly, ...warning ? { warning } : {}, ...code2 ? { code: code2.code } : {} };
125762
125983
  }
125763
- if (access === "foreign") {
125984
+ }
125985
+ function localForm(place, platform) {
125986
+ if (platform !== "win32") return place;
125987
+ const local = /^[\\/]{2}[?.][\\/]([A-Za-z]:(?:[\\/].*)?)$/.exec(place)?.[1];
125988
+ return local ? local.length === 2 ? `${local}\\` : local : place;
125989
+ }
125990
+ function makeMissingFolder(place, net4, refuse13, noFolder, trail) {
125991
+ if (!isAbsolute7(place)) throw noFolder(place);
125992
+ const names = [];
125993
+ let parent = place;
125994
+ while (!existsSync(parent)) {
125995
+ const up = dirname28(parent);
125996
+ if (up === parent) throw noFolder(place);
125997
+ names.unshift(basename6(parent));
125998
+ parent = up;
125999
+ }
126000
+ if (!names.length || !isFolder(parent)) throw noFolder(place);
126001
+ if (net4.platform === "win32") {
126002
+ const bad2 = names.find((name) => WINDOWS_RESERVED.test(name.replace(/[. ]+$/, "")) || /[<>:"|?*\u0000-\u001f]/.test(name) || /[. ]$/.test(name));
126003
+ if (bad2) throw backupFailed(`"${bad2}" cannot be a folder name on Windows. Type another folder`);
126004
+ }
126005
+ refuse13(linkOnWay(parent, net4.platform), parent);
126006
+ let at = realLocal(parent, net4, refuse13);
126007
+ const realFolderAt = (path) => {
126008
+ let stat2;
125764
126009
  try {
125765
- rmdirSync(target);
126010
+ stat2 = lstatSync(path);
125766
126011
  } catch {
126012
+ return false;
125767
126013
  }
125768
- throw backupFailed(`this folder can be opened by other accounts (${place}); pick another place`);
125769
- }
125770
- const ownerOnly = access === "owner";
125771
- const saved = [];
125772
- const names = /* @__PURE__ */ new Set();
125773
- for (const file of files) {
125774
- let name = basename6(file);
125775
- for (let n = 2; names.has(name.toLowerCase()); n += 1) name = `${basename6(file, extname(file))}-${n}${extname(file)}`;
125776
- names.add(name.toLowerCase());
125777
- const copy = join29(target, name);
125778
- stillHere(target);
126014
+ if (stat2.isSymbolicLink() || !stat2.isDirectory()) return false;
126015
+ let now;
125779
126016
  try {
125780
- copyFileSync(file, copy, constants.COPYFILE_EXCL);
125781
- if (platform !== "win32") chmodSync(copy, 384);
125782
- } catch (error) {
125783
- throw backupFailed(`the copy to ${target} failed (${error?.code === "ENOSPC" ? "the drive is full" : "the drive did not take the file"}). Pick another place`);
126017
+ now = localForm(realpathSync.native(path), net4.platform);
126018
+ } catch {
126019
+ return false;
125784
126020
  }
126021
+ return samePath(now, path, net4.platform);
126022
+ };
126023
+ for (const name of names) {
126024
+ if (!realFolderAt(at)) throw backupFailed(MOVED_WORDS(place));
126025
+ const next = join29(at, name);
125785
126026
  try {
125786
- stillHere(copy);
126027
+ mkdirSync(next, { mode: 448 });
125787
126028
  } catch (error) {
125788
- try {
125789
- rmSync(copy, { force: true });
125790
- } catch {
125791
- }
125792
- throw error;
126029
+ throw backupFailed(`the wallet could not make the folder ${place} (${error?.code === "EACCES" || error?.code === "EPERM" ? "no permission to write there" : error?.code === "ENOSPC" ? "the drive is full" : error?.code === "EEXIST" ? "another program made a part of it meanwhile" : "it cannot write there"}). Pick another place`);
125793
126030
  }
125794
- saved.push({ name, source: file, sha256: sha256Of(file) });
125795
- }
125796
- for (const entry of saved) {
125797
- if (sha256Of(join29(target, entry.name)) !== entry.sha256) throw backupFailed(`the copy in ${target} does not match the original. Pick another place`);
126031
+ trail.dirs.push(identityOf(next));
126032
+ if (!realFolderAt(next)) throw backupFailed(MOVED_WORDS(place));
126033
+ refuse13(networkPlace(next, net4), next);
126034
+ at = next;
125798
126035
  }
125799
- const code2 = cleared ? void 0 : backupCode();
125800
- writeRecord(home, { savedAt: (/* @__PURE__ */ new Date()).toISOString(), folder: target, cleared, ...code2 ? { codeSha256: code2.sha256 } : {} });
125801
- const warning = removable === true ? "Anyone who gets this USB stick can spend your money: keep it somewhere safe." : !ownerOnly ? "Other people who use this computer or drive may be able to open it: keep it somewhere only you can reach." : void 0;
125802
- return { folder: target, files: saved.map(({ name, sha256: sha25610 }) => ({ name, sha256: sha25610 })), ownerOnly, ...warning ? { warning } : {}, ...code2 ? { code: code2.code } : {} };
126036
+ return { real: at };
125803
126037
  }
125804
- function localForm(place, platform) {
125805
- if (platform !== "win32") return place;
125806
- const local = /^[\\/]{2}[?.][\\/]([A-Za-z]:(?:[\\/].*)?)$/.exec(place)?.[1];
125807
- return local ? local.length === 2 ? `${local}\\` : local : place;
126038
+ function identityOf(path) {
126039
+ const stat2 = statSync(path, { bigint: true });
126040
+ return { path, dev: stat2.dev, ino: stat2.ino };
126041
+ }
126042
+ function removeTrail(trail) {
126043
+ const same2 = (entry, kind) => {
126044
+ let stat2;
126045
+ try {
126046
+ stat2 = lstatSync(entry.path, { bigint: true });
126047
+ } catch {
126048
+ return false;
126049
+ }
126050
+ return !stat2.isSymbolicLink() && (kind === "file" ? stat2.isFile() : stat2.isDirectory()) && stat2.dev === entry.dev && stat2.ino === entry.ino;
126051
+ };
126052
+ for (const entry of [...trail.files].reverse()) {
126053
+ if (same2(entry, "file")) try {
126054
+ rmSync(entry.path);
126055
+ } catch {
126056
+ }
126057
+ }
126058
+ for (const entry of [...trail.dirs].reverse()) {
126059
+ if (same2(entry, "dir")) try {
126060
+ rmdirSync(entry.path);
126061
+ } catch {
126062
+ }
126063
+ }
125808
126064
  }
125809
126065
  function realLocal(path, net4, refuse13) {
125810
126066
  let real;
@@ -125838,33 +126094,32 @@ function networkPlace(place, { platform = process.platform, driveType = windowsD
125838
126094
  const local = LOCAL_FS.has(mount.type) || mount.type === "drvfs" || mount.type === "9p" && /(^|,)aname=drvfs(,|$)/.test(mount.options ?? "");
125839
126095
  return local ? false : "unknown";
125840
126096
  }
125841
- function linkToNetwork(place, options, hops = 0) {
125842
- if (hops > 20) return "unknown";
125843
- const local = localForm(place, options.platform);
125844
- if (options.platform === "win32" && !/^[A-Za-z]:/.test(local)) return "unknown";
126097
+ function linkOnWay(place, platform) {
126098
+ const local = localForm(place, platform);
126099
+ if (platform === "win32" && !/^[A-Za-z]:/.test(local)) return "unknown";
125845
126100
  const parts = local.split(/[\\/]+/).filter((part, index) => index === 0 || part !== "");
125846
126101
  for (let end2 = 2; end2 <= parts.length; end2 += 1) {
125847
- const prefix = parts.slice(0, end2).join(options.platform === "win32" ? "\\" : "/") || "/";
126102
+ const prefix = parts.slice(0, end2).join(platform === "win32" ? "\\" : "/") || "/";
125848
126103
  let stat2;
125849
126104
  try {
125850
- stat2 = lstatSync(options.platform === "win32" && /^[A-Za-z]:$/.test(prefix) ? `${prefix}\\` : prefix);
126105
+ stat2 = lstatSync(platform === "win32" && /^[A-Za-z]:$/.test(prefix) ? `${prefix}\\` : prefix);
125851
126106
  } catch (error) {
125852
126107
  return error?.code === "ENOENT" || error?.code === "ENOTDIR" ? "missing" : "unknown";
125853
126108
  }
125854
- if (!stat2.isSymbolicLink()) continue;
125855
- let target;
125856
- try {
125857
- target = readlinkSync(prefix);
125858
- } catch {
125859
- return "unknown";
125860
- }
125861
- const next = resolve25(dirname28(prefix), target, ...parts.slice(end2));
125862
- const found = networkPlace(target, options) || networkPlace(next, options);
125863
- if (found) return found;
125864
- return linkToNetwork(next, options, hops + 1);
126109
+ if (stat2.isSymbolicLink() && !systemLink(prefix, stat2, platform)) return "link";
125865
126110
  }
125866
126111
  return false;
125867
126112
  }
126113
+ function systemLink(path, stat2, platform) {
126114
+ if (platform === "win32" || stat2.uid !== 0) return false;
126115
+ let holder;
126116
+ try {
126117
+ holder = lstatSync(posix.dirname(path));
126118
+ } catch {
126119
+ return false;
126120
+ }
126121
+ return holder.isDirectory() && !holder.isSymbolicLink() && holder.uid === 0 && (holder.mode & 18) === 0;
126122
+ }
125868
126123
  function windowsDriveType(letter) {
125869
126124
  try {
125870
126125
  const result = spawnSync3(
@@ -125938,7 +126193,7 @@ function recordBackupSkip(home) {
125938
126193
  if (backupRecord(home)?.cleared) return;
125939
126194
  writeRecord(home, { skippedAt: (/* @__PURE__ */ new Date()).toISOString() });
125940
126195
  }
125941
- var backupRecordFile, sha256Of, isFolder, backupFailed, canBackUp, NETWORK_PLACE_WORDS, MOVED_WORDS, samePath, UNKNOWN_DRIVE_WORDS, NETWORK_FS, LOCAL_FS, SYSTEM_SID, ADMINISTRATORS_SID, backupNeeded;
126196
+ var backupRecordFile, sha256Of, isFolder, backupFailed, canBackUp, CLOUD_WORDS, NETWORK_PLACE_WORDS, MOVED_WORDS, WINDOWS_RESERVED, samePath, UNKNOWN_DRIVE_WORDS, NETWORK_FS, LOCAL_FS, LINK_WORDS, SYSTEM_SID, ADMINISTRATORS_SID, backupNeeded;
125942
126197
  var init_backup = __esm({
125943
126198
  "wallet/src/setup/backup.mjs"() {
125944
126199
  "use strict";
@@ -125963,8 +126218,10 @@ var init_backup = __esm({
125963
126218
  }
125964
126219
  return typeof config?.recoveryFile === "string" && existsSync(config.recoveryFile);
125965
126220
  };
126221
+ CLOUD_WORDS = (place, service) => `${place} is synced to ${service === "a cloud service" ? "a cloud service" : `${service}'s cloud`}, so the backup would be uploaded there. Pick a USB drive or a folder that is not synced`;
125966
126222
  NETWORK_PLACE_WORDS = (place) => `${place} is on a network share or a network drive. A backup there can be read by the people who run that share, and the computer reaches it outside Tor, so the wallet does not save it there. Pick a USB drive or a folder on this computer`;
125967
126223
  MOVED_WORDS = (place) => `the folder ${place} changed while the wallet saved the backup (a part of its path now leads somewhere else), so the wallet stopped and removed what it wrote. Pick a USB drive or a folder on this computer, and try again`;
126224
+ WINDOWS_RESERVED = /^(con|prn|aux|nul|conin\$|conout\$|com[0-9\u00b9\u00b2\u00b3]|lpt[0-9\u00b9\u00b2\u00b3])(\..*)?$/i;
125968
126225
  samePath = (a, b, platform) => platform === "win32" ? resolve25(a).toLowerCase() === resolve25(b).toLowerCase() : resolve25(a) === resolve25(b);
125969
126226
  UNKNOWN_DRIVE_WORDS = (place) => `the wallet could not tell whether the drive of ${place} is on this computer or on a network, so it does not save the backup there. Pick a USB drive or a folder on this computer, or try again`;
125970
126227
  NETWORK_FS = /^(nfs4?|cifs|smb[23]?|smbfs|afpfs|webdav|davfs|ncpfs|afs|ceph|glusterfs|lustre|fuse\.(sshfs|rclone|s3fs|davfs2?|gvfsd-fuse|smbnetfs))$/;
@@ -125990,6 +126247,7 @@ var init_backup = __esm({
125990
126247
  "tmpfs",
125991
126248
  "ramfs"
125992
126249
  ]);
126250
+ LINK_WORDS = (place) => `a part of ${place} is a link to another place (a junction or a symbolic link), so the wallet does not save the backup there. Pick a USB drive or a normal folder`;
125993
126251
  SYSTEM_SID = "S-1-5-18";
125994
126252
  ADMINISTRATORS_SID = "S-1-5-32-544";
125995
126253
  backupNeeded = (home) => canBackUp(home) && backupRecord(home)?.cleared !== true && !backupSkipped(home);
@@ -126064,14 +126322,15 @@ function createBackup({ home, client, elicitTimeoutMs, unshown = () => false, lo
126064
126322
  });
126065
126323
  }
126066
126324
  const code2 = done.saved.code;
126325
+ const say3 = code2 ? `Your backup code is ${code2}: say it back to me to add money. This backup is on this computer, so it does not help if this computer breaks; copy it to a USB stick too. ${savedLine(done.saved, done.removable)}` : savedLine(done.saved, done.removable);
126067
126326
  return json2({
126068
- say: code2 ? `Your backup code is ${code2}: say it back to me to add money with this backup. ${savedLine(done.saved, done.removable)} It does not help if this computer breaks.` : savedLine(done.saved, done.removable),
126327
+ say: say3,
126069
126328
  ok: true,
126070
126329
  folder: done.saved.folder,
126071
126330
  files: done.saved.files.map((file) => file.name),
126072
126331
  checked: "sha256",
126073
126332
  ...code2 ? { waitsForCode: true } : {},
126074
- next: code2 ? "Tell the user the say line as it is, with the code. Only when the user says that code back, call wallet_backup with code set to it; never send the code yourself. A USB stick needs no code." : "Tell the user where the backup is. Deposits can go on now (call wallet_add_funds if the user asked for it)."
126333
+ next: code2 ? `Say this to the user word for word, with the code (the user needs the code to add money): "${say3}" Only when the user says that code back, call wallet_backup with code set to it; never send the code yourself. A USB stick needs no code.` : "Tell the user where the backup is. Deposits can go on now (call wallet_add_funds if the user asked for it)."
126075
126334
  });
126076
126335
  }
126077
126336
  const boxWords = (asked) => asked.why === "failed" ? `The backup was not saved: ${asked.plain}. ` : asked.why === "declined" ? "The user closed the backup box without a choice. " : asked.why === "no_place" ? "The backup box came back with no place picked. " : asked.why === "timeout" ? "The backup box got no answer in time. " : "";
@@ -149501,7 +149760,7 @@ function successorChain(body, pool2) {
149501
149760
  at = next;
149502
149761
  }
149503
149762
  }
149504
- function identityOf(facilitator, release, successors = []) {
149763
+ function identityOf2(facilitator, release, successors = []) {
149505
149764
  const served = { pool: release.pool, program: release.programId, network: release.network, asset: release.asset };
149506
149765
  if (new URL(facilitator).origin === new URL(PRODUCTION.memoService).origin) {
149507
149766
  const pool2 = successors.includes(served.pool) ? served.pool : PRODUCTION.pool;
@@ -149675,7 +149934,7 @@ async function runSetupSteps(options, deps, toolsSignal) {
149675
149934
  successions = void 0;
149676
149935
  }
149677
149936
  }
149678
- identity = identityOf(facilitator, release, successorChain(successions, PRODUCTION.pool));
149937
+ identity = identityOf2(facilitator, release, successorChain(successions, PRODUCTION.pool));
149679
149938
  try {
149680
149939
  releasePools = parseReleasePools(release, facilitator);
149681
149940
  } catch {
@@ -156059,8 +156318,13 @@ function readJsonFile(file) {
156059
156318
  }
156060
156319
  if (size > HOST_CONFIG_MAX_BYTES) return null;
156061
156320
  try {
156062
- const text3 = readFileSync(file, "utf8");
156063
- const value2 = text3.trim() ? JSON.parse(text3) : {};
156321
+ const text3 = readFileSync(file, "utf8").replace(/^/, "");
156322
+ let value2;
156323
+ try {
156324
+ value2 = text3.trim() ? JSON.parse(text3) : {};
156325
+ } catch {
156326
+ value2 = parseJsonc(text3).value;
156327
+ }
156064
156328
  return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
156065
156329
  } catch {
156066
156330
  return null;
@@ -156093,7 +156357,23 @@ function claudeCodeState(env, home) {
156093
156357
  function agentHostsOf(env, home) {
156094
156358
  const jsonHost = (target) => {
156095
156359
  const config = readJsonFile(hostConfigFile(target, { env, home: homedir3() }));
156096
- return config === void 0 ? false : config === null ? null : runsThisWallet(config.mcpServers, home);
156360
+ return config === void 0 ? false : config === null ? null : runsThisWallet(config[hostServersKey(target)], home);
156361
+ };
156362
+ const continueHost = () => {
156363
+ const file = hostConfigFile("continue", { env, home: homedir3() });
156364
+ let size;
156365
+ try {
156366
+ size = statSync(file).size;
156367
+ } catch (error) {
156368
+ return error?.code === "ENOENT" ? false : null;
156369
+ }
156370
+ if (size > HOST_CONFIG_MAX_BYTES) return null;
156371
+ try {
156372
+ const servers = continueServersIn(readFileSync(file, "utf8"));
156373
+ return servers ? runsThisWallet(servers, home) : null;
156374
+ } catch {
156375
+ return null;
156376
+ }
156097
156377
  };
156098
156378
  const codexHost = () => {
156099
156379
  const file = hostConfigFile("codex", { env, home: homedir3() });
@@ -156115,7 +156395,9 @@ function agentHostsOf(env, home) {
156115
156395
  { app: "Claude Code", ...claudeCodeState(env, home) },
156116
156396
  { app: "Claude Desktop", linked: jsonHost("claude-desktop") },
156117
156397
  { app: "Cursor", linked: jsonHost("cursor") },
156118
- { app: "Codex", linked: codexHost() }
156398
+ { app: "Codex", linked: codexHost() },
156399
+ ...["vscode", "devin", "cline", "gemini", "antigravity"].map((target) => ({ app: HOST_NAMES[target], linked: jsonHost(target) })),
156400
+ { app: HOST_NAMES.continue, linked: continueHost() }
156119
156401
  ];
156120
156402
  }
156121
156403
  function readHomeData({ env = process.env, walletVersion: walletVersion2, torRun } = {}) {
@@ -158738,12 +159020,14 @@ function privateInferencePath(config) {
158738
159020
  }
158739
159021
  function agentAppLines({ skip = /* @__PURE__ */ new Set() }) {
158740
159022
  if (inHomeJob()) return "To add the wallet to an agent app (Claude, Codex, Cursor and others), press [c] on the home screen.";
158741
- const lines = [];
158742
- for (const target of HOST_TARGETS) {
158743
- const app = target === "codex" ? "Codex (the Codex app or Codex CLI)" : HOST_NAMES[target];
158744
- if (!skip.has(target)) lines.push(`${app}, run this once (setup adds the wallet there):`, ` ${cli4()} setup --add-to ${target}`);
158745
- }
158746
- return lines.join("\n");
159023
+ const targets = HOST_TARGETS.filter((target) => !skip.has(target));
159024
+ return targets.length ? hostLines(targets).join("\n") : "";
159025
+ }
159026
+ function hostLines(targets) {
159027
+ const commands = targets.map((target) => `${cli4()} setup --add-to ${target}`);
159028
+ const width = Math.max(...commands.map((command) => command.length));
159029
+ const label = { codex: "Codex (the Codex app or Codex CLI)", devin: "Devin Desktop (the app that was Windsurf)", vscode: "VS Code (GitHub Copilot)" };
159030
+ return targets.map((target, index) => ` ${commands[index].padEnd(width)} ${label[target] ?? HOST_NAMES[target]}`);
158747
159031
  }
158748
159032
  function setupNextText({ home, deps, npmVersion, config, fundAsked, rerun, targets = [], added = /* @__PURE__ */ new Set() }) {
158749
159033
  const parts = [];
@@ -158756,20 +159040,18 @@ function setupNextText({ home, deps, npmVersion, config, fundAsked, rerun, targe
158756
159040
  env: deps.env,
158757
159041
  platform: deps.platform ?? process.platform,
158758
159042
  npmVersion,
159043
+ userHome: deps.homedir(),
158759
159044
  skip: new Set(HOST_TARGETS.filter((target) => !failed.includes(target)))
158760
159045
  });
158761
159046
  if (byHand) parts.push(`Or add it by hand:
158762
159047
  ${byHand}
158763
159048
  `);
158764
159049
  } else if (rerun && !targets.length) {
158765
- parts.push([
158766
- "To add the wallet to an agent app, run one of these:",
158767
- ...HOST_TARGETS.map((target) => ` ${cli4()} setup --add-to ${target}`),
158768
- ""
158769
- ].join("\n"));
159050
+ parts.push(["To add the wallet to an agent app, run the line for that app:", ...hostLines(HOST_TARGETS), ""].join("\n"));
158770
159051
  } else {
158771
159052
  const hosts = agentAppLines({ skip: added });
158772
- if (hosts) parts.push(`${added.size ? "To add the wallet to another agent app" : "Use the wallet in your agent app"}:
159053
+ const head = inHomeJob() ? added.size ? "To add the wallet to another agent app" : "Use the wallet in your agent app" : `${added.size ? "To add the wallet to another agent app" : "To use the wallet in your agent app"}, run the line for that app once (setup adds the wallet there)`;
159054
+ if (hosts) parts.push(`${head}:
158773
159055
  ${hosts}
158774
159056
  `);
158775
159057
  }
@@ -159312,7 +159594,7 @@ async function addToHosts(targets, home, deps, { force = false, npmVersion, ask,
159312
159594
  continue;
159313
159595
  }
159314
159596
  try {
159315
- const write = (entry, writeForce) => (target === "codex" ? addToCodexHost : addToJsonHost)(file, entry, { force: writeForce });
159597
+ const write = (entry, writeForce) => target === "codex" ? addToCodexHost(file, entry, { force: writeForce }) : target === "continue" ? addToContinueHost(file, hostEntry(target, entry), { force: writeForce }) : addToJsonHost(file, hostEntry(target, entry), { force: writeForce, key: hostServersKey(target) });
159316
159598
  let added = write(mcpServerEntry(options), force);
159317
159599
  for (const old of added.replaced ?? []) io.out(`Replaced the old ${old} entry in ${app}.
159318
159600
  `);
@@ -159326,7 +159608,7 @@ async function addToHosts(targets, home, deps, { force = false, npmVersion, ask,
159326
159608
  done.add(target);
159327
159609
  } catch (error) {
159328
159610
  const code2 = String(error.message).match(/^(setup_host_config_[a-z_]+): /)?.[1];
159329
- const why = code2 === "setup_host_config_conflict" ? `${file} already has another server named "${MCP_SERVER_NAME}", and the wallet did not change that file; to use the wallet there, put the entry below in its place` : code2 === "setup_host_config_unreadable" ? `the wallet cannot read the settings in ${file}, so it did not change that file; fix that file, then add the entry below to it by hand` : code2 === "setup_host_config_acl_failed" ? `the wallet could not limit a new copy of ${file} to you alone, so it did not change that file; add the entry below to it by hand` : `the wallet could not write ${file}, so that file is unchanged; add the entry below to it by hand`;
159611
+ const why = code2 === "setup_host_config_conflict" ? `${file} already has another server named "${MCP_SERVER_NAME}", and the wallet did not change that file; to use the wallet there, put the entry below in its place` : code2 === "setup_host_config_unreadable" ? `the wallet cannot read the settings in ${file}, so it did not change that file; fix that file, then add the entry below to it by hand` : code2 === "setup_host_config_acl_failed" ? `the wallet could not limit a new copy of ${file} to you alone, so it did not change that file; add the entry below to it by hand` : code2 === "setup_host_config_changed" ? `${app} changed its settings while setup ran, so the wallet left ${file} as ${app} wrote it; run this step again` : code2 === "setup_host_config_link_broken" ? `${file} is a link to a file that does not exist, so the wallet did not change it; fix that link, or add the entry below by hand` : `the wallet could not write ${file}, so that file is unchanged; add the entry below to it by hand`;
159330
159612
  io.out(`Not added to ${app}: ${why}.
159331
159613
  `);
159332
159614
  }
@@ -160659,7 +160941,7 @@ var COMMANDS = [
160659
160941
  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 key (the address this wallet itself receives on). 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})`),
160660
160942
  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"),
160661
160943
  flag2("--facilitator", "<https origin>", `the server whose release, pool, and setup files setup uses; default ${GUIDE.facilitator}`, { check: "origin" }),
160662
- flag2("--add-to", "<claude-code|claude-desktop|cursor|codex>", `also add the server (npx -y --no-audit --no-fund ${NPX_AT_VERSION} mcp: the host starts this npm version; a wallet not installed from npm writes shinjuku-shield without a version) to this agent host's config; repeat it for more. Without it, setup prints the configs`, { repeat: true }),
160944
+ flag2("--add-to", `<${HOST_TARGETS.join("|")}>`, `also add the server (npx -y --no-audit --no-fund ${NPX_AT_VERSION} mcp: the host starts this npm version; a wallet not installed from npm writes shinjuku-shield without a version) to this agent host's config; repeat it for more. Without it, setup prints the configs`, { repeat: true, check: "host" }),
160663
160945
  // 0.3.97 key rename: the host key is MCP_SERVER_NAME; the old keys go by themselves.
160664
160946
  bool2("--force", `with --add-to: replace a different ${MCP_SERVER_NAME} entry already in a JSON host config (the old file is backed up first, mode 0600), or in Claude Code (claude mcp remove of that one server, then add). An old ${OLD_MCP_SERVER_NAMES.join(" or ")} entry is replaced by itself, without --force`),
160665
160947
  flag2("--trust-proof-tools", "<sha256>", `ADVANCED: install the set of setup files whose SHA256SUMS has this SHA-256 instead of the set this wallet build pins (needed for a server the build does not pin). Check the set out of band first; the normal fix for a new set is to update the wallet: ${UPDATE_SETUP}`)
@@ -160677,7 +160959,8 @@ var COMMANDS = [
160677
160959
  "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.",
160678
160960
  `The setup files are the set this wallet build PINS (its SHA256SUMS hash is in the wallet; --trust-proof-tools replaces it). A server whose /api/wallet/release names another set is refused (setup_proof_tools_pin_mismatch; to update the wallet, ${UPDATE_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.`,
160679
160961
  "Your own --passphrase-file must be private: on Linux and macOS setup refuses a file that group or other may read or write, or that another user owns (setup_passphrase_file_insecure: chmod 600 <file>). On Windows setup cannot read the access list; it accepts the file with a warning.",
160680
- `--add-to for Claude Desktop or Cursor: a host config whose mcpServers is not a JSON object, or that has a different ${MCP_SERVER_NAME} entry (without --force), is left alone. Otherwise the old file is copied to <file>.bak-<time> (mode 0600 on Linux and macOS; on Windows the folder's access list) and the new file replaces it in one rename. An entry an older setup wrote (no version, another version, or an old package name, shinjuku or shinjuku-shielded; the same home) is replaced without --force, in a JSON host and in Claude Code. An entry under an old key (${OLD_MCP_SERVER_NAMES.join(" or ")}) is replaced by itself, in every host, so the host never starts two wallet servers. For Claude Code, a different ${MCP_SERVER_NAME} server (not an older entry of this wallet) needs --force.`,
160962
+ `--add-to writes each app's own user file: vscode (VS Code with Copilot) mcp.json in VS Code's User folder, under "servers"; devin (Devin Desktop, the app that was Windsurf) mcp_config.json in AppData\\Roaming\\devin on Windows, else ~/.config/devin; cline ~/.cline/data/settings/cline_mcp_settings.json (or CLINE_MCP_SETTINGS_PATH, CLINE_DATA_DIR, CLINE_DIR); continue its own block file ~/.continue/mcpServers/shinjuku.yaml (or in CONTINUE_GLOBAL_DIR); gemini (Gemini CLI) ~/.gemini/settings.json (or in GEMINI_CLI_HOME); antigravity (Antigravity) ~/.gemini/config/mcp_config.json. Every other key and server in a file stays as it is.`,
160963
+ `--add-to for a JSON file (Claude Desktop, Cursor and the apps above): a host config whose server list is not a JSON object, or that has a different ${MCP_SERVER_NAME} entry (without --force), is left alone. Otherwise the old file is copied to <file>.bak-<time> (mode 0600 on Linux and macOS; on Windows the folder's access list) and the new file replaces it in one rename. An entry an older setup wrote (no version, another version, or an old package name, shinjuku or shinjuku-shielded; the same home) is replaced without --force, in a JSON host and in Claude Code. An entry under an old key (${OLD_MCP_SERVER_NAMES.join(" or ")}) is replaced by itself, in every host, so the host never starts two wallet servers. For Claude Code, a different ${MCP_SERVER_NAME} server (not an older entry of this wallet) needs --force.`,
160681
160964
  `--add-to codex: the Codex app and Codex CLI read one file, config.toml in CODEX_HOME (else ~/.codex). Setup writes the [mcp_servers.${MCP_SERVER_NAME}] tables at its end with startup_timeout_sec = 120 (a first start downloads the wallet) and tool_timeout_sec = 300, and keeps every other line; the same rules as a JSON host (a different entry stays without --force; the old file is backed up first). Then type /mcp in Codex to see the tools.`,
160682
160965
  "The proof programs run natively: Windows x64, Linux x64 and Linux arm64 each download their own set (no WSL). Setup downloads the programs first and starts each one once before it makes a key; when Windows app control or an antivirus blocks one, setup stops and says which. macOS and other systems: not supported yet; setup says so and creates nothing.",
160683
160966
  "Run it again at any time: it never changes an existing wallet, keeps the recorded caps unless you pass new ones, checks every setup file again, downloads what is missing or newer, and rewrites config.json.",
@@ -161412,8 +161695,8 @@ function shortHelpText() {
161412
161695
  "Everyday commands:",
161413
161696
  ...rows2.map(([word, about]) => ` ${word.padEnd(width)}${about}`),
161414
161697
  "",
161415
- "Add the wallet to your agent app (claude-code, claude-desktop, cursor, or codex):",
161416
- ` ${cli4()} setup --add-to claude-code`,
161698
+ "Add the wallet to your agent app: run the line for that app once.",
161699
+ ...hostLines(HOST_TARGETS),
161417
161700
  "",
161418
161701
  `One command, with an example: ${cli4()} help <command>`,
161419
161702
  `All commands: ${cli4()} help all`,
@@ -161578,6 +161861,9 @@ var flagLabel = (index) => `the flag at position ${index + 1}`;
161578
161861
  var TRANSFER_START_FLAGS = /* @__PURE__ */ new Set(["--mint", "--token-mint", "--amount-atomic", "--amount", "--to", "--max-slippage-bps", "--allow-impact", "--yes"]);
161579
161862
  var NOT_SHOWN = "; the value given is not one (it is not repeated here)";
161580
161863
  function checkValue(spec, value2) {
161864
+ if (spec.check === "host" && (Array.isArray(value2) ? value2 : [value2]).some((name) => !HOST_TARGETS.includes(name))) {
161865
+ return `${spec.name} takes the name of an agent app: ${HOST_TARGETS.join(", ")}${NOT_SHOWN}`;
161866
+ }
161581
161867
  if (spec.check === "atomic" && !(spec.usdc === "or-all" && value2 === "all") && !/^[1-9][0-9]*$/.test(value2)) {
161582
161868
  return `${spec.name} must be a whole number of atomic units above 0 (1 USDC = 1000000)${NOT_SHOWN}`;
161583
161869
  }
@@ -164730,6 +165016,7 @@ ${HOST_TARGETS.map((host, index) => ` ${index + 1} ${hostNames[host] ?? host}`
164730
165016
  env: deps.env,
164731
165017
  platform: deps.platform ?? process.platform,
164732
165018
  npmVersion,
165019
+ userHome: deps.homedir(),
164733
165020
  skip: new Set(HOST_TARGETS.filter((target) => target !== host))
164734
165021
  });
164735
165022
  if (byHand) io.out(`${byHand}