clauderipple 0.9.5 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/CHANGELOG.md +118 -0
  2. package/README.es.md +45 -0
  3. package/README.ja.md +17 -0
  4. package/README.ko.md +31 -0
  5. package/README.md +70 -0
  6. package/README.pt-BR.md +45 -0
  7. package/README.zh-CN.md +17 -0
  8. package/dist/app/assets/icon.icns +0 -0
  9. package/dist/app/assets/icon.ico +0 -0
  10. package/dist/app/assets/icon.png +0 -0
  11. package/dist/app/assets/trayDirectTemplate.png +0 -0
  12. package/dist/app/assets/trayDirectTemplate@2x.png +0 -0
  13. package/dist/app/assets/trayDownTemplate.png +0 -0
  14. package/dist/app/assets/trayDownTemplate@2x.png +0 -0
  15. package/dist/app/assets/trayTemplate.png +0 -0
  16. package/dist/app/assets/trayTemplate@2x.png +0 -0
  17. package/dist/app/assets/trayWarnTemplate.png +0 -0
  18. package/dist/app/assets/trayWarnTemplate@2x.png +0 -0
  19. package/dist/app/assets/trayWin.png +0 -0
  20. package/dist/app/assets/trayWin@2x.png +0 -0
  21. package/dist/app/assets/trayWinDirect.png +0 -0
  22. package/dist/app/assets/trayWinDirect@2x.png +0 -0
  23. package/dist/app/assets/trayWinDown.png +0 -0
  24. package/dist/app/assets/trayWinDown@2x.png +0 -0
  25. package/dist/app/assets/trayWinWarn.png +0 -0
  26. package/dist/app/assets/trayWinWarn@2x.png +0 -0
  27. package/dist/app/dist/claude-desktop.js +67 -0
  28. package/dist/app/dist/main.js +423 -110
  29. package/dist/app/dist/preload.js +21 -0
  30. package/dist/app/dist/protocol.js +121 -0
  31. package/dist/app/dist/status-model.js +87 -0
  32. package/dist/app/dist/window.js +133 -0
  33. package/dist/app/package.json +1 -1
  34. package/dist/cli/src/awake-setup.js +314 -0
  35. package/dist/cli/src/bundle.js +9 -5
  36. package/dist/cli/src/direct.js +159 -0
  37. package/dist/cli/src/doctor.js +205 -0
  38. package/dist/cli/src/hooks/agent-title.js +7 -4
  39. package/dist/cli/src/index.js +227 -31
  40. package/dist/cli/src/launchd.js +29 -20
  41. package/dist/cli/src/launcher.js +215 -0
  42. package/dist/cli/src/power.js +182 -0
  43. package/dist/cli/src/registration.js +157 -0
  44. package/dist/cli/src/remote.js +263 -0
  45. package/dist/cli/src/schtasks.js +50 -40
  46. package/dist/cli/src/settings.js +26 -1
  47. package/dist/cli/src/tray.js +5 -2
  48. package/dist/mod/hooks/i18n.ts +473 -227
  49. package/dist/mod/hooks/judge.ts +35 -17
  50. package/dist/mod/hooks/modes.ts +56 -1
  51. package/dist/mod/hooks/register.tsx +237 -32
  52. package/dist/mod/hooks/session.ts +102 -1
  53. package/dist/mod/hooks/settings.js +7 -3
  54. package/dist/mod/hooks/settings.ts +9 -4
  55. package/dist/mod/hooks/stats.ts +25 -1
  56. package/dist/mod/hooks/ui/agents.tsx +903 -79
  57. package/dist/mod/hooks/ui/alerts.tsx +20 -17
  58. package/dist/mod/hooks/ui/art.ts +33 -7
  59. package/dist/mod/hooks/ui/band.tsx +262 -81
  60. package/dist/mod/hooks/ui/flat.ts +265 -0
  61. package/dist/mod/hooks/ui/kit.tsx +70 -55
  62. package/dist/mod/hooks/ui/look.ts +129 -0
  63. package/dist/mod/hooks/ui/settings.tsx +71 -46
  64. package/dist/mod/hooks/ui/setup.tsx +15 -13
  65. package/dist/mod/hooks/ui/theme.ts +3 -0
  66. package/dist/mod/hooks/ui/tools.ts +124 -0
  67. package/dist/mod/hooks/ui/view.ts +120 -4
  68. package/dist/mod/hooks/workers.ts +33 -6
  69. package/dist/mod/tests/register.test.tsx +155 -11
  70. package/dist/mod/tests/ui.test.tsx +408 -27
  71. package/dist/mod/types/index.d.ts +66 -2
  72. package/dist/remote/package.json +28 -0
  73. package/dist/remote/src/away.js +85 -0
  74. package/dist/remote/src/bridge-client.js +336 -0
  75. package/dist/remote/src/composer-menu.js +11 -0
  76. package/dist/remote/src/config.js +102 -0
  77. package/dist/remote/src/desktop.js +1018 -0
  78. package/dist/remote/src/e2ee.js +126 -0
  79. package/dist/remote/src/e2ee.ts +126 -0
  80. package/dist/remote/src/eventlog.js +298 -0
  81. package/dist/remote/src/expo-push.js +127 -0
  82. package/dist/remote/src/folders.js +79 -0
  83. package/dist/remote/src/fsutil.js +39 -0
  84. package/dist/remote/src/handoff.js +38 -0
  85. package/dist/remote/src/host-service.js +168 -0
  86. package/dist/remote/src/images.js +215 -0
  87. package/dist/remote/src/local-control.js +138 -0
  88. package/dist/remote/src/main.js +264 -0
  89. package/dist/remote/src/permissions.js +124 -0
  90. package/dist/remote/src/power.js +76 -0
  91. package/dist/remote/src/processes.js +24 -0
  92. package/dist/remote/src/protocol.js +32 -0
  93. package/dist/remote/src/protocol.ts +284 -0
  94. package/dist/remote/src/push-content.js +32 -0
  95. package/dist/remote/src/push.js +134 -0
  96. package/dist/remote/src/receipts.js +111 -0
  97. package/dist/remote/src/relay.js +438 -0
  98. package/dist/remote/src/router.js +208 -0
  99. package/dist/remote/src/sdk-launcher.js +18 -0
  100. package/dist/remote/src/server.js +706 -0
  101. package/dist/remote/src/service.js +157 -0
  102. package/dist/remote/src/sessions.js +1800 -0
  103. package/dist/remote/src/sidebar-storage.js +211 -0
  104. package/dist/remote/src/sidebar.js +173 -0
  105. package/dist/remote/src/tool-details.js +205 -0
  106. package/dist/remote/src/tool-details.ts +153 -0
  107. package/dist/remote/src/worktree.js +99 -0
  108. package/dist/remote/web/app.ts +746 -0
  109. package/dist/remote/web/conn.ts +193 -0
  110. package/dist/remote/web/demo.ts +134 -0
  111. package/dist/remote/web/dom.ts +124 -0
  112. package/dist/remote/web/esc.ts +4 -0
  113. package/dist/remote/web/i18n.ts +374 -0
  114. package/dist/remote/web/icon.svg +1 -0
  115. package/dist/remote/web/index.html +24 -0
  116. package/dist/remote/web/manifest.webmanifest +10 -0
  117. package/dist/remote/web/mascot.ts +60 -0
  118. package/dist/remote/web/md.ts +78 -0
  119. package/dist/remote/web/notify.ts +55 -0
  120. package/dist/remote/web/pushstate.ts +27 -0
  121. package/dist/remote/web/secure.ts +109 -0
  122. package/dist/remote/web/sidebar.ts +106 -0
  123. package/dist/remote/web/style.css +311 -0
  124. package/dist/remote/web/sw.ts +48 -0
  125. package/dist/remote/web/timeline.ts +224 -0
  126. package/dist/remote/web/views.ts +323 -0
  127. package/dist/router/src/admin.js +331 -5
  128. package/dist/router/src/awake-win.js +590 -0
  129. package/dist/router/src/awake.js +432 -0
  130. package/dist/router/src/config.js +94 -0
  131. package/dist/router/src/health.js +86 -3
  132. package/dist/router/src/hold.js +107 -0
  133. package/dist/router/src/index.js +108 -6
  134. package/dist/router/src/ingress/server.js +26 -4
  135. package/dist/router/src/ingress/translate.js +6 -2
  136. package/dist/router/src/phone.js +82 -0
  137. package/dist/router/src/power.js +107 -0
  138. package/dist/router/src/providers/anthropic-usage.js +33 -1
  139. package/dist/router/src/providers/google/antigravity.js +73 -10
  140. package/dist/router/src/providers/google/index.js +191 -14
  141. package/dist/router/src/providers/google/prompt-refusal.js +29 -0
  142. package/dist/router/src/providers/google/thoughtSignatures.js +13 -0
  143. package/dist/router/src/providers/google/transport.js +1 -1
  144. package/dist/router/src/providers/google/usage.js +302 -0
  145. package/dist/router/src/providers/grok/index.js +18 -0
  146. package/dist/router/src/providers/grok/usage.js +77 -0
  147. package/dist/router/src/providers/openai/translate.js +13 -2
  148. package/dist/router/src/providers/opencode-usage.js +116 -0
  149. package/dist/router/src/providers/retry.js +4 -1
  150. package/dist/router/src/proxy.js +371 -261
  151. package/dist/router/src/remote-host.js +320 -0
  152. package/dist/router/src/remote-tunnel.js +320 -0
  153. package/dist/router/src/requestlog.js +48 -39
  154. package/dist/router/src/tether-win.js +211 -0
  155. package/dist/router/src/tether.js +712 -0
  156. package/dist/router/src/usage-history.js +227 -0
  157. package/dist/router/src/version.js +1 -1
  158. package/dist/ui/app.js +1083 -59
  159. package/dist/ui/i18n.js +670 -69
  160. package/dist/ui/index.html +33 -6
  161. package/dist/ui/style.css +177 -0
  162. package/docs/ARCHITECTURE.md +402 -14
  163. package/package.json +8 -2
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ // window.rippleApp — what the dashboard can ask of the app it runs in, and nothing more: the
3
+ // connection state, and the recovery actions that work with the router dead (they run the CLI in
4
+ // the main process). In a plain browser the object does not exist and the dashboard behaves as it
5
+ // always has. The page runs sandboxed with context isolation.
6
+ const { contextBridge, ipcRenderer } = require("electron");
7
+ contextBridge.exposeInMainWorld("rippleApp", {
8
+ platform: process.platform,
9
+ state: () => ipcRenderer.invoke("ripple-app:state"),
10
+ onState: (callback) => {
11
+ ipcRenderer.on("ripple-app:state", (_event, state) => callback(state));
12
+ },
13
+ refresh: () => ipcRenderer.invoke("ripple-app:act", "refresh"),
14
+ repair: () => ipcRenderer.invoke("ripple-app:act", "repair"),
15
+ directOn: () => ipcRenderer.invoke("ripple-app:act", "direct-on"),
16
+ directOff: () => ipcRenderer.invoke("ripple-app:act", "direct-off"),
17
+ restartClaude: () => ipcRenderer.invoke("ripple-app:act", "restart-claude"),
18
+ enableLoginItem: () => ipcRenderer.invoke("ripple-app:act", "login-item"),
19
+ openLogs: () => ipcRenderer.invoke("ripple-app:act", "logs"),
20
+ recentLog: () => ipcRenderer.invoke("ripple-app:log"),
21
+ });
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ // ripple://app/ — the dashboard drawn from this app's own copy of packages/ui, with /api/* passed
3
+ // on to the router's admin port. The router used to serve the page too, so when it died the
4
+ // dashboard died with it, exactly when it was needed (2026-10-20). Now only the data needs the
5
+ // router; a router that does not answer gives a 503 the dashboard reads as "Ripple is down".
6
+ var __importDefault = (this && this.__importDefault) || function (mod) {
7
+ return (mod && mod.__esModule) ? mod : { "default": mod };
8
+ };
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.ORIGIN = exports.SCHEME = void 0;
11
+ exports.registerScheme = registerScheme;
12
+ exports.routerDownResponse = routerDownResponse;
13
+ exports.handleScheme = handleScheme;
14
+ const electron_1 = require("electron");
15
+ const node_fs_1 = __importDefault(require("node:fs"));
16
+ const node_http_1 = __importDefault(require("node:http"));
17
+ const node_path_1 = __importDefault(require("node:path"));
18
+ const node_stream_1 = require("node:stream");
19
+ const status_model_1 = require("./status-model");
20
+ exports.SCHEME = "ripple";
21
+ exports.ORIGIN = `${exports.SCHEME}://app`;
22
+ /** Before app ready: a standard, secure scheme gets an origin, localStorage, fetch and the page's CSP 'self'. */
23
+ function registerScheme() {
24
+ electron_1.protocol.registerSchemesAsPrivileged([{ scheme: exports.SCHEME, privileges: { standard: true, secure: true, supportFetchAPI: true, stream: true } }]);
25
+ }
26
+ const MIME = {
27
+ ".html": "text/html; charset=utf-8",
28
+ ".js": "text/javascript; charset=utf-8",
29
+ ".css": "text/css; charset=utf-8",
30
+ ".svg": "image/svg+xml",
31
+ ".png": "image/png",
32
+ ".json": "application/json",
33
+ ".ico": "image/x-icon",
34
+ };
35
+ /** Quick enough that a dead router reads as dead, long enough for a login or a provider probe. */
36
+ const CONNECT_TIMEOUT_MS = 4_000;
37
+ const IDLE_TIMEOUT_MS = 180_000;
38
+ function json(status, body) {
39
+ return new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json", "cache-control": "no-store" } });
40
+ }
41
+ function routerDownResponse() {
42
+ return json(503, { error: "ClaudeRipple is not running", routerDown: true });
43
+ }
44
+ /**
45
+ * The admin API, asked as the CLI and the tray ask it: no Origin header (the router turns away a
46
+ * cross-site Origin on writes, and a custom scheme's would be one) and a Host it accepts.
47
+ */
48
+ async function forward(request, admin) {
49
+ const url = new URL(request.url);
50
+ const body = request.method === "GET" || request.method === "HEAD" ? null : Buffer.from(await request.arrayBuffer());
51
+ const headers = { host: `127.0.0.1:${admin.port}`, accept: request.headers.get("accept") ?? "*/*" };
52
+ const type = request.headers.get("content-type");
53
+ if (type)
54
+ headers["content-type"] = type;
55
+ if (body)
56
+ headers["content-length"] = String(body.length);
57
+ return new Promise((resolve) => {
58
+ let settled = false;
59
+ const done = (response) => {
60
+ if (settled)
61
+ return;
62
+ settled = true;
63
+ resolve(response);
64
+ };
65
+ const req = node_http_1.default.request(
66
+ // A connection of its own per request: the idle timer below is set on the socket, and on a
67
+ // pooled socket those timers piled up (MaxListenersExceededWarning, measured here).
68
+ { host: admin.hostname, port: admin.port, method: request.method, path: `${url.pathname}${url.search}`, headers, agent: false }, (res) => {
69
+ clearTimeout(connectTimer);
70
+ res.socket?.setTimeout(IDLE_TIMEOUT_MS, () => res.destroy());
71
+ const out = new Headers();
72
+ for (const name of ["content-type", "cache-control", "content-disposition"]) {
73
+ const value = res.headers[name];
74
+ if (typeof value === "string")
75
+ out.set(name, value);
76
+ }
77
+ const status = res.statusCode ?? 502;
78
+ const nullBody = status === 204 || status === 304 || request.method === "HEAD";
79
+ if (nullBody)
80
+ res.resume();
81
+ done(new Response(nullBody ? null : node_stream_1.Readable.toWeb(res), { status, headers: out }));
82
+ });
83
+ const connectTimer = setTimeout(() => {
84
+ req.destroy();
85
+ done(routerDownResponse());
86
+ }, CONNECT_TIMEOUT_MS);
87
+ req.on("error", () => {
88
+ clearTimeout(connectTimer);
89
+ done(routerDownResponse());
90
+ });
91
+ if (body)
92
+ req.write(body);
93
+ req.end();
94
+ });
95
+ }
96
+ async function serveFile(root, rel) {
97
+ const full = node_path_1.default.resolve(root, rel);
98
+ if (full !== root && !full.startsWith(root.endsWith(node_path_1.default.sep) ? root : root + node_path_1.default.sep))
99
+ return json(400, { error: "invalid path" });
100
+ try {
101
+ const data = await node_fs_1.default.promises.readFile(full);
102
+ return new Response(data, { status: 200, headers: { "content-type": MIME[node_path_1.default.extname(full).toLowerCase()] ?? "application/octet-stream", "cache-control": "no-cache" } });
103
+ }
104
+ catch {
105
+ return json(404, { error: "not found" });
106
+ }
107
+ }
108
+ /** After app ready. `admin` is read per request, so a port changed in config.json is followed. */
109
+ function handleScheme(uiRoot, admin) {
110
+ electron_1.protocol.handle(exports.SCHEME, async (request) => {
111
+ const url = new URL(request.url);
112
+ if (url.host !== "app")
113
+ return json(404, { error: "not found" });
114
+ const route = (0, status_model_1.routeRequest)(url.pathname);
115
+ if (!route)
116
+ return json(400, { error: "invalid path" });
117
+ if (route.kind === "api")
118
+ return forward(request, new URL(admin()));
119
+ return serveFile(uiRoot, route.rel);
120
+ });
121
+ }
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ // The tray's reading of what `clauderipple doctor` / `repair` / `direct` answer. Pure: no Electron,
3
+ // no I/O, so `node --test` runs it as it is. What the dashboard draws from the same answers lives in
4
+ // packages/ui/app.js (the connection card); this is only what the main process decides itself.
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.parseJsonOutput = parseJsonOutput;
7
+ exports.tailLines = tailLines;
8
+ exports.trayState = trayState;
9
+ exports.shouldNotifyDown = shouldNotifyDown;
10
+ exports.repairDetails = repairDetails;
11
+ exports.routeRequest = routeRequest;
12
+ /**
13
+ * The CLI prints one JSON object on stdout. Anything else on stdout (a warning a dependency printed,
14
+ * a stray blank line) must not lose it, so the last line that parses as an object wins, and failing
15
+ * that the whole text.
16
+ */
17
+ function parseJsonOutput(stdout) {
18
+ const lines = stdout.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
19
+ for (let i = lines.length - 1; i >= 0; i--) {
20
+ const parsed = tryObject(lines[i]);
21
+ if (parsed)
22
+ return parsed;
23
+ }
24
+ const whole = tryObject(stdout.trim());
25
+ return whole ? whole : null;
26
+ }
27
+ function tryObject(text) {
28
+ if (!text.startsWith("{"))
29
+ return null;
30
+ try {
31
+ const value = JSON.parse(text);
32
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
33
+ }
34
+ catch {
35
+ return null;
36
+ }
37
+ }
38
+ /** The last `count` non-empty lines of a log's tail. */
39
+ function tailLines(text, count) {
40
+ return text.split(/\r?\n/).filter((l) => l.trim().length > 0).slice(-count);
41
+ }
42
+ /** The tray icon: the direct mode is a choice, not an outage, and gets its own mark. */
43
+ function trayState(health, doctor) {
44
+ if (doctor?.mode === "direct")
45
+ return "direct";
46
+ return health;
47
+ }
48
+ /**
49
+ * Whether the "Ripple is not responding" notification should go out now. Once per outage (the
50
+ * caller clears `alreadyNotified` when the router answers again), not while an update or one of our
51
+ * own actions restarts it on purpose, and never in the direct mode, where the router being off is
52
+ * the point.
53
+ */
54
+ function shouldNotifyDown(o) {
55
+ return !o.alreadyNotified && !o.updating && !o.busy && o.downForMs >= o.graceMs && o.mode !== "direct";
56
+ }
57
+ /** Lines for the "Details" fold after a repair: each step as ✓/✗ with what it said. */
58
+ function repairDetails(out, raw) {
59
+ if (!out?.steps?.length)
60
+ return raw ? tailLines(raw, 12) : [];
61
+ return out.steps.map((s) => `${s.ok ? "✓" : "✗"} ${s.name ?? "?"}${s.detail ? ` — ${s.detail}` : ""}`);
62
+ }
63
+ /**
64
+ * Where a ripple:// request goes: the dashboard's own files, or the router's admin API. Returns
65
+ * null for a path that would leave the UI folder.
66
+ */
67
+ function routeRequest(pathname) {
68
+ if (pathname === "/api" || pathname.startsWith("/api/"))
69
+ return { kind: "api" };
70
+ let decoded;
71
+ try {
72
+ decoded = decodeURIComponent(pathname);
73
+ }
74
+ catch {
75
+ return null;
76
+ }
77
+ if (decoded.includes("\0"))
78
+ return null;
79
+ const rel = decoded === "/" || decoded === "" ? "index.html" : decoded === "/favicon.ico" ? "favicon-32.png" : decoded.replace(/^\/+/, "");
80
+ const parts = rel.split(/[\\/]+/);
81
+ if (parts.some((p) => p === ".." || p === ""))
82
+ return null;
83
+ // A path with no extension is the dashboard routing itself (as the router serves it).
84
+ if (!/\.[a-z0-9]+$/i.test(rel))
85
+ return { kind: "file", rel: "index.html" };
86
+ return { kind: "file", rel };
87
+ }
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ // The app's one window: the whole dashboard, served from the app itself (protocol.ts), so it opens
3
+ // and can repair things with the router dead. A preload bridge (preload.ts) gives the dashboard the
4
+ // few things only the app can do: doctor, repair, the direct switch, reopening Claude.
5
+ var __importDefault = (this && this.__importDefault) || function (mod) {
6
+ return (mod && mod.__esModule) ? mod : { "default": mod };
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.openWindow = openWindow;
10
+ exports.mainWindow = mainWindow;
11
+ exports.windowVisible = windowVisible;
12
+ exports.sendToWindow = sendToWindow;
13
+ const electron_1 = require("electron");
14
+ const node_path_1 = __importDefault(require("node:path"));
15
+ const protocol_1 = require("./protocol");
16
+ const DEFAULT = { width: 1040, height: 740 };
17
+ const MIN = { width: 760, height: 540 };
18
+ let win = null;
19
+ /** A saved position is used only if most of it is still on some display (a monitor may be gone). */
20
+ function fits(b) {
21
+ return electron_1.screen.getAllDisplays().some(({ workArea: a }) => {
22
+ const w = Math.min(b.x + b.width, a.x + a.width) - Math.max(b.x, a.x);
23
+ const h = Math.min(b.y + b.height, a.y + a.height) - Math.max(b.y, a.y);
24
+ return w >= Math.min(200, b.width) && h >= Math.min(120, b.height);
25
+ });
26
+ }
27
+ function isOurs(url) {
28
+ return url.startsWith(`${protocol_1.ORIGIN}/`) || url === protocol_1.ORIGIN;
29
+ }
30
+ /**
31
+ * Opens the window, or brings it forward. `view` is a dashboard section ("health", "slots", …):
32
+ * the tray asks for "health" when something is wrong.
33
+ */
34
+ function openWindow(deps, view) {
35
+ if (win && !win.isDestroyed()) {
36
+ if (view)
37
+ void win.webContents.executeJavaScript(`location.hash = ${JSON.stringify(`#${view}`)}`).catch(() => undefined);
38
+ if (win.isMinimized())
39
+ win.restore();
40
+ win.show();
41
+ focusApp();
42
+ win.focus();
43
+ deps.shown();
44
+ return;
45
+ }
46
+ const saved = deps.savedBounds();
47
+ const bounds = saved && fits(saved) ? saved : null;
48
+ const mac = process.platform === "darwin";
49
+ win = new electron_1.BrowserWindow({
50
+ ...(bounds ?? DEFAULT),
51
+ minWidth: MIN.width,
52
+ minHeight: MIN.height,
53
+ show: false,
54
+ title: "ClaudeRipple",
55
+ // The dashboard's own sidebar sits under the traffic lights; elsewhere the system frame stays.
56
+ ...(mac ? { titleBarStyle: "hiddenInset", trafficLightPosition: { x: 18, y: 18 } } : {}),
57
+ // Its first frame is the page's own background, not a white flash in dark mode.
58
+ backgroundColor: electron_1.nativeTheme.shouldUseDarkColors ? "#1c1c1e" : "#f5f5f7",
59
+ autoHideMenuBar: true,
60
+ // The app icon (macOS takes it from the bundle): Windows reads every size from the .ico.
61
+ icon: mac ? undefined : node_path_1.default.join(__dirname, "..", "assets", process.platform === "win32" ? "icon.ico" : "icon.png"),
62
+ webPreferences: {
63
+ preload: node_path_1.default.join(__dirname, "preload.js"),
64
+ contextIsolation: true,
65
+ sandbox: true,
66
+ nodeIntegration: false,
67
+ spellcheck: false,
68
+ },
69
+ });
70
+ if (!mac)
71
+ win.removeMenu();
72
+ // Links the dashboard opens (release notes, sign-in pages) belong in the user's browser.
73
+ win.webContents.setWindowOpenHandler(({ url }) => {
74
+ if (/^https?:\/\//i.test(url))
75
+ void electron_1.shell.openExternal(url);
76
+ return { action: "deny" };
77
+ });
78
+ win.webContents.on("will-navigate", (event, url) => {
79
+ if (isOurs(url))
80
+ return;
81
+ event.preventDefault();
82
+ if (/^https?:\/\//i.test(url))
83
+ void electron_1.shell.openExternal(url);
84
+ });
85
+ const remember = () => {
86
+ if (win && !win.isDestroyed() && !win.isMinimized() && !win.isFullScreen())
87
+ deps.saveBounds(win.getNormalBounds());
88
+ };
89
+ win.on("resized", remember);
90
+ win.on("moved", remember);
91
+ win.on("close", remember);
92
+ win.once("ready-to-show", () => {
93
+ win?.show();
94
+ focusApp();
95
+ win?.focus();
96
+ dockIcon();
97
+ });
98
+ win.on("closed", () => {
99
+ win = null;
100
+ // A tray app again once its window is gone.
101
+ if (mac)
102
+ electron_1.app.dock?.hide();
103
+ });
104
+ if (mac) {
105
+ // The window's Dock icon is the running bundle's: installed from npm that is Electron.app, so the
106
+ // Dock showed Electron's atom (2026-10-20). Ours is set on it whatever bundle runs, once the Dock
107
+ // icon is up: set while the app had none, it was dropped when show() put the bundle's back.
108
+ void electron_1.app.dock?.show().then(dockIcon);
109
+ }
110
+ void win.loadURL(`${protocol_1.ORIGIN}/?lang=${encodeURIComponent(deps.lang)}${view ? `#${view}` : ""}`);
111
+ deps.shown();
112
+ }
113
+ /** Our icon on the Dock, on macOS: set again once the window shows, which put the bundle's back. */
114
+ function dockIcon() {
115
+ if (process.platform === "darwin")
116
+ electron_1.app.dock?.setIcon(node_path_1.default.join(__dirname, "..", "assets", "icon.png"));
117
+ }
118
+ /** The tray app has no Dock icon until a window opens, so a window opened from it could come up behind the frontmost app. */
119
+ function focusApp() {
120
+ if (process.platform === "darwin")
121
+ electron_1.app.focus({ steal: true });
122
+ }
123
+ function mainWindow() {
124
+ return win && !win.isDestroyed() ? win : null;
125
+ }
126
+ function windowVisible() {
127
+ return !!win && !win.isDestroyed() && win.isVisible() && !win.isMinimized();
128
+ }
129
+ /** Sends the bridge's state to the page, if the window is open. */
130
+ function sendToWindow(channel, payload) {
131
+ if (win && !win.isDestroyed())
132
+ win.webContents.send(channel, payload);
133
+ }
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "private": true,
3
- "version": "0.9.5",
3
+ "version": "0.11.0",
4
4
  "type": "commonjs"
5
5
  }