shinjuku-shielded 0.3.159 → 0.3.160

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.160. 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.160
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.160 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.160 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.160", "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.160", "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": "eb6f9ba6c43c52189f2d1f9124f1e23b23e471a168ffc0fe008c818c35f6b981",
5
+ "bytes": 9251599,
6
+ "sourceCommit": "58e77ed499568c5b3ec71e010ed2b0a1f75e20f0",
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-04T04:35:54.717Z"
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.160",
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": "eb6f9ba6",
7
+ "shinjukuSha256": "eb6f9ba6c43c52189f2d1f9124f1e23b23e471a168ffc0fe008c818c35f6b981",
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) {
@@ -125660,6 +125660,8 @@ function clearBackupWithCode(home, code2) {
125660
125660
  return true;
125661
125661
  }
125662
125662
  function backupPlaces({ platform = process.platform, env = process.env } = {}) {
125663
+ const roots = cloudRoots({ platform, env, home: homedir2() });
125664
+ const notSynced = (list) => list.filter((place) => !cloudPlace(place.path, { platform, env, roots: () => roots }));
125663
125665
  const places = [];
125664
125666
  if (platform === "win32") {
125665
125667
  try {
@@ -125681,12 +125683,12 @@ function backupPlaces({ platform = process.platform, env = process.env } = {}) {
125681
125683
  } catch {
125682
125684
  }
125683
125685
  const profile = env.USERPROFILE ?? homedir2();
125684
- for (const [label, path] of [["Desktop", join29(profile, "Desktop")], ["Desktop", join29(profile, "OneDrive", "Desktop")], ["Documents", join29(profile, "Documents")]]) {
125686
+ for (const [label, path] of [["Desktop", join29(profile, "Desktop")], ["Documents", join29(profile, "Documents")]]) {
125685
125687
  if (isFolder(path) && !places.some((place) => place.path.toLowerCase() === path.toLowerCase())) {
125686
125688
  places.push({ label: `${label} (${path}; on this computer: it does not help if this computer breaks)`, path, removable: false });
125687
125689
  }
125688
125690
  }
125689
- return places;
125691
+ return notSynced(places);
125690
125692
  }
125691
125693
  let user = "";
125692
125694
  try {
@@ -125706,7 +125708,66 @@ function backupPlaces({ platform = process.platform, env = process.env } = {}) {
125706
125708
  for (const path of [join29(homedir2(), "Desktop"), homedir2()]) {
125707
125709
  if (isFolder(path)) places.push({ label: `${path} (on this computer: it does not help if this computer breaks)`, path, removable: false });
125708
125710
  }
125709
- return places;
125711
+ return notSynced(places);
125712
+ }
125713
+ function cloudPlace(place, { platform = process.platform, env = process.env, home = homedir2(), roots } = {}) {
125714
+ const text3 = String(place ?? "").trim();
125715
+ if (!text3) return void 0;
125716
+ const fold = platform === "win32" || platform === "darwin";
125717
+ const sep5 = platform === "win32" ? "\\" : "/";
125718
+ const norm = (path) => {
125719
+ const full = (platform === "win32" ? resolve25(localForm(path, platform)) : posix.resolve(path)).replace(/[\\/]+$/, "");
125720
+ return fold ? full.toLowerCase() : full;
125721
+ };
125722
+ const at = norm(text3);
125723
+ for (const root of (roots ?? (() => cloudRoots({ platform, env, home })))()) {
125724
+ const base = norm(root.path);
125725
+ if (base && (at === base || at.startsWith(`${base}${sep5}`))) return root.service;
125726
+ }
125727
+ for (const part of text3.split(/[\\/]+/).map((name) => name.trim().toLowerCase())) {
125728
+ if (part === "onedrive" || part.startsWith("onedrive - ") || part.startsWith("onedrive-")) return "OneDrive";
125729
+ if (part === "dropbox" || part.startsWith("dropbox (") || part.startsWith("dropbox-")) return "Dropbox";
125730
+ if (["my drive", "shared drives", "google drive"].includes(part) || part.startsWith("googledrive")) return "Google Drive";
125731
+ if (["iclouddrive", "icloud drive", "icloud photos", "mobile documents"].includes(part)) return "iCloud";
125732
+ }
125733
+ return void 0;
125734
+ }
125735
+ function cloudRoots({ platform = process.platform, env = process.env, home = homedir2() } = {}) {
125736
+ const roots = [];
125737
+ const add2 = (path, service) => {
125738
+ if (typeof path === "string" && path.trim()) roots.push({ path: path.trim(), service });
125739
+ };
125740
+ const dropbox = (file) => {
125741
+ let info;
125742
+ try {
125743
+ info = JSON.parse(readFileSync(file, "utf8"));
125744
+ } catch {
125745
+ return;
125746
+ }
125747
+ for (const account of Object.values(info ?? {})) add2(account?.path, "Dropbox");
125748
+ };
125749
+ if (platform === "win32") {
125750
+ for (const name of ["OneDrive", "OneDriveConsumer", "OneDriveCommercial"]) add2(env[name], "OneDrive");
125751
+ add2(join29(env.USERPROFILE ?? home, "OneDrive"), "OneDrive");
125752
+ try {
125753
+ const result = spawnSync3(
125754
+ "reg.exe",
125755
+ ["query", "HKCU\\Software\\Microsoft\\OneDrive\\Accounts", "/s", "/v", "UserFolder"],
125756
+ { windowsHide: true, encoding: "utf8", timeout: 1e4 }
125757
+ );
125758
+ for (const match of String(result.stdout ?? "").matchAll(/UserFolder\s+REG_\w+\s+(.+)/g)) add2(match[1], "OneDrive");
125759
+ } catch {
125760
+ }
125761
+ if (env.APPDATA) dropbox(join29(env.APPDATA, "Dropbox", "info.json"));
125762
+ if (env.LOCALAPPDATA) dropbox(join29(env.LOCALAPPDATA, "Dropbox", "info.json"));
125763
+ return roots;
125764
+ }
125765
+ dropbox(posix.join(home, ".dropbox", "info.json"));
125766
+ if (platform === "darwin") {
125767
+ add2(posix.join(home, "Library", "Mobile Documents"), "iCloud");
125768
+ add2(posix.join(home, "Library", "CloudStorage"), "a cloud service");
125769
+ }
125770
+ return roots;
125710
125771
  }
125711
125772
  async function saveBackup(home, folder, {
125712
125773
  removable,
@@ -125714,7 +125775,8 @@ async function saveBackup(home, folder, {
125714
125775
  platform = process.platform,
125715
125776
  restrict = restrictFolderToOwner,
125716
125777
  driveType = windowsDriveType,
125717
- mounts = posixMounts
125778
+ mounts = posixMounts,
125779
+ env = process.env
125718
125780
  } = {}) {
125719
125781
  const typed = String(folder ?? "").trim().replace(/^"(.*)"$/, "$1");
125720
125782
  const types = /* @__PURE__ */ new Map();
@@ -125726,85 +125788,173 @@ async function saveBackup(home, folder, {
125726
125788
  const noFolder = (at) => backupFailed(`there is no folder ${at}. Plug in the drive or type another folder`);
125727
125789
  const refuse13 = (found, at) => {
125728
125790
  if (found === "missing") throw noFolder(at);
125791
+ if (found === "link") throw backupFailed(LINK_WORDS(at));
125729
125792
  if (found) throw backupFailed(found === "unknown" ? UNKNOWN_DRIVE_WORDS(at) : NETWORK_PLACE_WORDS(at));
125730
125793
  };
125794
+ const roots = cloudRoots({ platform, env, home: homedir2() });
125795
+ const refuseCloud = (at) => {
125796
+ const service = cloudPlace(at, { platform, env, roots: () => roots });
125797
+ if (service) throw backupFailed(CLOUD_WORDS(at, service));
125798
+ };
125731
125799
  refuse13(networkPlace(typed, net4), typed);
125732
125800
  const place = localForm(resolve25(typed), platform);
125733
125801
  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 });
125802
+ refuseCloud(place);
125803
+ const onWay = linkOnWay(place, platform);
125804
+ const fullPath = isAbsolute7(typed) && (platform !== "win32" || /^(?:[\\/]{2}[?.][\\/])?[A-Za-z]:[\\/]/.test(typed));
125805
+ if (onWay === "missing" && !fullPath) throw noFolder(place);
125806
+ const trail = { files: [], dirs: [] };
125807
+ let made;
125808
+ try {
125809
+ made = onWay === "missing" ? makeMissingFolder(place, net4, refuse13, noFolder, trail) : refuse13(onWay, place);
125810
+ return await saveInFolder();
125743
125811
  } 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`);
125812
+ removeTrail(trail);
125813
+ throw error;
125745
125814
  }
125746
- const stillHere = (path) => {
125747
- let now;
125815
+ async function saveInFolder() {
125816
+ if (!isAbsolute7(place) || !isFolder(place)) throw noFolder(place);
125817
+ const real = realLocal(place, net4, refuse13);
125818
+ refuseCloud(real);
125819
+ if (made && !samePath(real, made.real, platform)) throw backupFailed(MOVED_WORDS(place));
125820
+ const files = backupFiles(home);
125821
+ const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
125822
+ let target = join29(real, `shinjuku-backup-${date}`);
125823
+ for (let n = 2; existsSync(target); n += 1) target = join29(real, `shinjuku-backup-${date}-${n}`);
125824
+ try {
125825
+ mkdirSync(target, { mode: 448 });
125826
+ trail.dirs.push(identityOf(target));
125827
+ } catch (error) {
125828
+ 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`);
125829
+ }
125830
+ const stillHere = (path) => {
125831
+ let now;
125832
+ try {
125833
+ now = localForm(realpathSync.native(path), platform);
125834
+ } catch {
125835
+ now = void 0;
125836
+ }
125837
+ if (!now || !samePath(now, path, platform)) throw backupFailed(MOVED_WORDS(place));
125838
+ refuse13(networkPlace(now, net4), now);
125839
+ refuseCloud(now);
125840
+ };
125841
+ stillHere(target);
125842
+ let access;
125748
125843
  try {
125749
- now = localForm(realpathSync.native(path), platform);
125844
+ access = restrict(target, platform);
125750
125845
  } catch {
125751
- now = void 0;
125846
+ access = "open";
125847
+ }
125848
+ if (access === "foreign") {
125849
+ throw backupFailed(`this folder can be opened by other accounts (${place}); pick another place`);
125850
+ }
125851
+ const ownerOnly = access === "owner";
125852
+ const saved = [];
125853
+ const names = /* @__PURE__ */ new Set();
125854
+ for (const file of files) {
125855
+ let name = basename6(file);
125856
+ for (let n = 2; names.has(name.toLowerCase()); n += 1) name = `${basename6(file, extname(file))}-${n}${extname(file)}`;
125857
+ names.add(name.toLowerCase());
125858
+ const copy = join29(target, name);
125859
+ stillHere(target);
125860
+ try {
125861
+ copyFileSync(file, copy, constants.COPYFILE_EXCL);
125862
+ trail.files.push(identityOf(copy));
125863
+ if (platform !== "win32") chmodSync(copy, 384);
125864
+ } catch (error) {
125865
+ throw backupFailed(`the copy to ${target} failed (${error?.code === "ENOSPC" ? "the drive is full" : "the drive did not take the file"}). Pick another place`);
125866
+ }
125867
+ stillHere(copy);
125868
+ saved.push({ name, source: file, sha256: sha256Of(file) });
125752
125869
  }
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";
125870
+ for (const entry of saved) {
125871
+ if (sha256Of(join29(target, entry.name)) !== entry.sha256) throw backupFailed(`the copy in ${target} does not match the original. Pick another place`);
125872
+ }
125873
+ const code2 = cleared ? void 0 : backupCode();
125874
+ writeRecord(home, { savedAt: (/* @__PURE__ */ new Date()).toISOString(), folder: target, cleared, ...code2 ? { codeSha256: code2.sha256 } : {} });
125875
+ 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;
125876
+ return { folder: target, files: saved.map(({ name, sha256: sha25610 }) => ({ name, sha256: sha25610 })), ownerOnly, ...warning ? { warning } : {}, ...code2 ? { code: code2.code } : {} };
125762
125877
  }
125763
- if (access === "foreign") {
125878
+ }
125879
+ function localForm(place, platform) {
125880
+ if (platform !== "win32") return place;
125881
+ const local = /^[\\/]{2}[?.][\\/]([A-Za-z]:(?:[\\/].*)?)$/.exec(place)?.[1];
125882
+ return local ? local.length === 2 ? `${local}\\` : local : place;
125883
+ }
125884
+ function makeMissingFolder(place, net4, refuse13, noFolder, trail) {
125885
+ if (!isAbsolute7(place)) throw noFolder(place);
125886
+ const names = [];
125887
+ let parent = place;
125888
+ while (!existsSync(parent)) {
125889
+ const up = dirname28(parent);
125890
+ if (up === parent) throw noFolder(place);
125891
+ names.unshift(basename6(parent));
125892
+ parent = up;
125893
+ }
125894
+ if (!names.length || !isFolder(parent)) throw noFolder(place);
125895
+ if (net4.platform === "win32") {
125896
+ const bad2 = names.find((name) => WINDOWS_RESERVED.test(name.replace(/[. ]+$/, "")) || /[<>:"|?*\u0000-\u001f]/.test(name) || /[. ]$/.test(name));
125897
+ if (bad2) throw backupFailed(`"${bad2}" cannot be a folder name on Windows. Type another folder`);
125898
+ }
125899
+ refuse13(linkOnWay(parent, net4.platform), parent);
125900
+ let at = realLocal(parent, net4, refuse13);
125901
+ const realFolderAt = (path) => {
125902
+ let stat2;
125764
125903
  try {
125765
- rmdirSync(target);
125904
+ stat2 = lstatSync(path);
125766
125905
  } catch {
125906
+ return false;
125767
125907
  }
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);
125908
+ if (stat2.isSymbolicLink() || !stat2.isDirectory()) return false;
125909
+ let now;
125779
125910
  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`);
125911
+ now = localForm(realpathSync.native(path), net4.platform);
125912
+ } catch {
125913
+ return false;
125784
125914
  }
125915
+ return samePath(now, path, net4.platform);
125916
+ };
125917
+ for (const name of names) {
125918
+ if (!realFolderAt(at)) throw backupFailed(MOVED_WORDS(place));
125919
+ const next = join29(at, name);
125785
125920
  try {
125786
- stillHere(copy);
125921
+ mkdirSync(next, { mode: 448 });
125787
125922
  } catch (error) {
125788
- try {
125789
- rmSync(copy, { force: true });
125790
- } catch {
125791
- }
125792
- throw error;
125923
+ 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
125924
  }
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`);
125925
+ trail.dirs.push(identityOf(next));
125926
+ if (!realFolderAt(next)) throw backupFailed(MOVED_WORDS(place));
125927
+ refuse13(networkPlace(next, net4), next);
125928
+ at = next;
125798
125929
  }
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 } : {} };
125930
+ return { real: at };
125803
125931
  }
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;
125932
+ function identityOf(path) {
125933
+ const stat2 = statSync(path, { bigint: true });
125934
+ return { path, dev: stat2.dev, ino: stat2.ino };
125935
+ }
125936
+ function removeTrail(trail) {
125937
+ const same2 = (entry, kind) => {
125938
+ let stat2;
125939
+ try {
125940
+ stat2 = lstatSync(entry.path, { bigint: true });
125941
+ } catch {
125942
+ return false;
125943
+ }
125944
+ return !stat2.isSymbolicLink() && (kind === "file" ? stat2.isFile() : stat2.isDirectory()) && stat2.dev === entry.dev && stat2.ino === entry.ino;
125945
+ };
125946
+ for (const entry of [...trail.files].reverse()) {
125947
+ if (same2(entry, "file")) try {
125948
+ rmSync(entry.path);
125949
+ } catch {
125950
+ }
125951
+ }
125952
+ for (const entry of [...trail.dirs].reverse()) {
125953
+ if (same2(entry, "dir")) try {
125954
+ rmdirSync(entry.path);
125955
+ } catch {
125956
+ }
125957
+ }
125808
125958
  }
125809
125959
  function realLocal(path, net4, refuse13) {
125810
125960
  let real;
@@ -125838,33 +125988,32 @@ function networkPlace(place, { platform = process.platform, driveType = windowsD
125838
125988
  const local = LOCAL_FS.has(mount.type) || mount.type === "drvfs" || mount.type === "9p" && /(^|,)aname=drvfs(,|$)/.test(mount.options ?? "");
125839
125989
  return local ? false : "unknown";
125840
125990
  }
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";
125991
+ function linkOnWay(place, platform) {
125992
+ const local = localForm(place, platform);
125993
+ if (platform === "win32" && !/^[A-Za-z]:/.test(local)) return "unknown";
125845
125994
  const parts = local.split(/[\\/]+/).filter((part, index) => index === 0 || part !== "");
125846
125995
  for (let end2 = 2; end2 <= parts.length; end2 += 1) {
125847
- const prefix = parts.slice(0, end2).join(options.platform === "win32" ? "\\" : "/") || "/";
125996
+ const prefix = parts.slice(0, end2).join(platform === "win32" ? "\\" : "/") || "/";
125848
125997
  let stat2;
125849
125998
  try {
125850
- stat2 = lstatSync(options.platform === "win32" && /^[A-Za-z]:$/.test(prefix) ? `${prefix}\\` : prefix);
125999
+ stat2 = lstatSync(platform === "win32" && /^[A-Za-z]:$/.test(prefix) ? `${prefix}\\` : prefix);
125851
126000
  } catch (error) {
125852
126001
  return error?.code === "ENOENT" || error?.code === "ENOTDIR" ? "missing" : "unknown";
125853
126002
  }
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);
126003
+ if (stat2.isSymbolicLink() && !systemLink(prefix, stat2, platform)) return "link";
125865
126004
  }
125866
126005
  return false;
125867
126006
  }
126007
+ function systemLink(path, stat2, platform) {
126008
+ if (platform === "win32" || stat2.uid !== 0) return false;
126009
+ let holder;
126010
+ try {
126011
+ holder = lstatSync(posix.dirname(path));
126012
+ } catch {
126013
+ return false;
126014
+ }
126015
+ return holder.isDirectory() && !holder.isSymbolicLink() && holder.uid === 0 && (holder.mode & 18) === 0;
126016
+ }
125868
126017
  function windowsDriveType(letter) {
125869
126018
  try {
125870
126019
  const result = spawnSync3(
@@ -125938,7 +126087,7 @@ function recordBackupSkip(home) {
125938
126087
  if (backupRecord(home)?.cleared) return;
125939
126088
  writeRecord(home, { skippedAt: (/* @__PURE__ */ new Date()).toISOString() });
125940
126089
  }
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;
126090
+ 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
126091
  var init_backup = __esm({
125943
126092
  "wallet/src/setup/backup.mjs"() {
125944
126093
  "use strict";
@@ -125963,8 +126112,10 @@ var init_backup = __esm({
125963
126112
  }
125964
126113
  return typeof config?.recoveryFile === "string" && existsSync(config.recoveryFile);
125965
126114
  };
126115
+ 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
126116
  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
126117
  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`;
126118
+ WINDOWS_RESERVED = /^(con|prn|aux|nul|conin\$|conout\$|com[0-9\u00b9\u00b2\u00b3]|lpt[0-9\u00b9\u00b2\u00b3])(\..*)?$/i;
125968
126119
  samePath = (a, b, platform) => platform === "win32" ? resolve25(a).toLowerCase() === resolve25(b).toLowerCase() : resolve25(a) === resolve25(b);
125969
126120
  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
126121
  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 +126141,7 @@ var init_backup = __esm({
125990
126141
  "tmpfs",
125991
126142
  "ramfs"
125992
126143
  ]);
126144
+ 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
126145
  SYSTEM_SID = "S-1-5-18";
125994
126146
  ADMINISTRATORS_SID = "S-1-5-32-544";
125995
126147
  backupNeeded = (home) => canBackUp(home) && backupRecord(home)?.cleared !== true && !backupSkipped(home);
@@ -126064,14 +126216,15 @@ function createBackup({ home, client, elicitTimeoutMs, unshown = () => false, lo
126064
126216
  });
126065
126217
  }
126066
126218
  const code2 = done.saved.code;
126219
+ 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
126220
  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),
126221
+ say: say3,
126069
126222
  ok: true,
126070
126223
  folder: done.saved.folder,
126071
126224
  files: done.saved.files.map((file) => file.name),
126072
126225
  checked: "sha256",
126073
126226
  ...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)."
126227
+ 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
126228
  });
126076
126229
  }
126077
126230
  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 +149654,7 @@ function successorChain(body, pool2) {
149501
149654
  at = next;
149502
149655
  }
149503
149656
  }
149504
- function identityOf(facilitator, release, successors = []) {
149657
+ function identityOf2(facilitator, release, successors = []) {
149505
149658
  const served = { pool: release.pool, program: release.programId, network: release.network, asset: release.asset };
149506
149659
  if (new URL(facilitator).origin === new URL(PRODUCTION.memoService).origin) {
149507
149660
  const pool2 = successors.includes(served.pool) ? served.pool : PRODUCTION.pool;
@@ -149675,7 +149828,7 @@ async function runSetupSteps(options, deps, toolsSignal) {
149675
149828
  successions = void 0;
149676
149829
  }
149677
149830
  }
149678
- identity = identityOf(facilitator, release, successorChain(successions, PRODUCTION.pool));
149831
+ identity = identityOf2(facilitator, release, successorChain(successions, PRODUCTION.pool));
149679
149832
  try {
149680
149833
  releasePools = parseReleasePools(release, facilitator);
149681
149834
  } catch {