clauderipple 0.10.0 → 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.
- package/CHANGELOG.md +51 -0
- package/README.es.md +45 -0
- package/README.ja.md +17 -0
- package/README.ko.md +31 -0
- package/README.md +39 -0
- package/README.pt-BR.md +45 -0
- package/README.zh-CN.md +17 -0
- package/dist/app/assets/icon.icns +0 -0
- package/dist/app/assets/icon.ico +0 -0
- package/dist/app/assets/icon.png +0 -0
- package/dist/app/assets/trayDirectTemplate.png +0 -0
- package/dist/app/assets/trayDirectTemplate@2x.png +0 -0
- package/dist/app/assets/trayDownTemplate.png +0 -0
- package/dist/app/assets/trayDownTemplate@2x.png +0 -0
- package/dist/app/assets/trayTemplate.png +0 -0
- package/dist/app/assets/trayTemplate@2x.png +0 -0
- package/dist/app/assets/trayWarnTemplate.png +0 -0
- package/dist/app/assets/trayWarnTemplate@2x.png +0 -0
- package/dist/app/assets/trayWin.png +0 -0
- package/dist/app/assets/trayWin@2x.png +0 -0
- package/dist/app/assets/trayWinDirect.png +0 -0
- package/dist/app/assets/trayWinDirect@2x.png +0 -0
- package/dist/app/assets/trayWinDown.png +0 -0
- package/dist/app/assets/trayWinDown@2x.png +0 -0
- package/dist/app/assets/trayWinWarn.png +0 -0
- package/dist/app/assets/trayWinWarn@2x.png +0 -0
- package/dist/app/dist/claude-desktop.js +67 -0
- package/dist/app/dist/main.js +423 -110
- package/dist/app/dist/preload.js +21 -0
- package/dist/app/dist/protocol.js +121 -0
- package/dist/app/dist/status-model.js +87 -0
- package/dist/app/dist/window.js +133 -0
- package/dist/app/package.json +1 -1
- package/dist/cli/src/awake-setup.js +314 -0
- package/dist/cli/src/bundle.js +9 -5
- package/dist/cli/src/direct.js +159 -0
- package/dist/cli/src/doctor.js +205 -0
- package/dist/cli/src/index.js +184 -31
- package/dist/cli/src/launchd.js +29 -20
- package/dist/cli/src/launcher.js +215 -0
- package/dist/cli/src/power.js +182 -0
- package/dist/cli/src/registration.js +157 -0
- package/dist/cli/src/schtasks.js +2 -0
- package/dist/cli/src/settings.js +24 -0
- package/dist/cli/src/tray.js +5 -2
- package/dist/mod/hooks/i18n.ts +237 -237
- package/dist/mod/hooks/register.tsx +4 -5
- package/dist/mod/hooks/session.ts +6 -2
- package/dist/mod/hooks/ui/agents.tsx +81 -36
- package/dist/mod/hooks/ui/view.ts +8 -5
- package/dist/mod/tests/ui.test.tsx +46 -13
- package/dist/mod/types/index.d.ts +0 -2
- package/dist/remote/src/bridge-client.js +45 -4
- package/dist/router/src/admin.js +105 -2
- package/dist/router/src/awake-win.js +590 -0
- package/dist/router/src/awake.js +432 -0
- package/dist/router/src/config.js +64 -0
- package/dist/router/src/health.js +86 -3
- package/dist/router/src/hold.js +107 -0
- package/dist/router/src/index.js +69 -6
- package/dist/router/src/ingress/server.js +26 -4
- package/dist/router/src/power.js +107 -0
- package/dist/router/src/providers/google/antigravity.js +73 -10
- package/dist/router/src/providers/google/index.js +168 -17
- package/dist/router/src/providers/google/thoughtSignatures.js +13 -0
- package/dist/router/src/providers/google/transport.js +1 -1
- package/dist/router/src/providers/google/usage.js +71 -26
- package/dist/router/src/providers/retry.js +4 -1
- package/dist/router/src/proxy.js +50 -21
- package/dist/router/src/tether-win.js +211 -0
- package/dist/router/src/tether.js +712 -0
- package/dist/router/src/version.js +1 -1
- package/dist/ui/app.js +649 -2
- package/dist/ui/i18n.js +494 -77
- package/dist/ui/index.html +9 -1
- package/dist/ui/style.css +138 -0
- package/docs/ARCHITECTURE.md +294 -6
- package/package.json +2 -2
package/dist/router/src/admin.js
CHANGED
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
// PATCH/DELETE /api/google-accounts/:id rename, pause/resume, clear a cooldown; remove
|
|
15
15
|
// GET /api/update whether a newer version is published, and how this copy was installed
|
|
16
16
|
// POST /api/update start `clauderipple update` in the background (it restarts this router)
|
|
17
|
+
// GET /api/power lid-closed awake + hotspot failover state (power.ts)
|
|
18
|
+
// PUT /api/power change awake mode/until, tether on/off (saved), pin (in memory)
|
|
19
|
+
// POST /api/power/setup start `clauderipple awake setup [--ssid]` (macOS password dialog); GET polls it
|
|
17
20
|
// GET /* static files from packages/ui (the GUI itself)
|
|
18
21
|
import crypto from "node:crypto";
|
|
19
22
|
import { phoneRequest } from "./phone.js";
|
|
@@ -24,7 +27,7 @@ import path from "node:path";
|
|
|
24
27
|
import http from "node:http";
|
|
25
28
|
import { execFile, spawn } from "node:child_process";
|
|
26
29
|
import { fileURLToPath } from "node:url";
|
|
27
|
-
import { adminPort, headerValueProblem, homeDir, searchProviderFor, validate } from "./config.js";
|
|
30
|
+
import { adminPort, headerValueProblem, homeDir, searchProviderFor, ssidProblem, validate } from "./config.js";
|
|
28
31
|
export { adminPort } from "./config.js";
|
|
29
32
|
import { generatedAgents } from "./agents.js";
|
|
30
33
|
import { isJudgeOnlyModel } from "./model-policy.js";
|
|
@@ -60,6 +63,7 @@ import { GoogleOAuthSession } from "./providers/google/login.js";
|
|
|
60
63
|
import { GROK_AUTH_HINT, grokAuthPath, grokHome, readGrokToken, sharedGrokAuth } from "./providers/grok/auth.js";
|
|
61
64
|
import { GROK_DEFAULT_URL, GROK_EFFORT_LEVELS, GROK_FALLBACK_MODELS, grokModelsFromListing } from "./providers/grok/catalog.js";
|
|
62
65
|
import { GROK_WARNING, grokHeaders } from "./providers/grok/index.js";
|
|
66
|
+
import { parsePowerChange, persistPowerChange } from "./power.js";
|
|
63
67
|
const MAX_BODY = 1024 * 1024;
|
|
64
68
|
/** `/api/image` carries reference images inline, so it gets more room than a config save. */
|
|
65
69
|
const MAX_IMAGE_BODY = 32 * 1024 * 1024;
|
|
@@ -190,12 +194,17 @@ async function googleLimitAccounts(deps, name) {
|
|
|
190
194
|
const groups = (await google.usage?.(name)) ?? {};
|
|
191
195
|
const kept = statuses.filter((a) => a.state !== "paused" && a.state !== "needs-login");
|
|
192
196
|
const at = Date.now();
|
|
197
|
+
// The third-party (Claude and GPT) windows only when this provider has such a model selected:
|
|
198
|
+
// beside Gemini's own they made four figures, two of them a pool nobody here spends (2026-10-20).
|
|
199
|
+
const provider = deps.config().providers[name];
|
|
200
|
+
const models = provider && "models" in provider && Array.isArray(provider.models) ? provider.models : [];
|
|
201
|
+
const thirdParty = models.some((m) => /^(claude-|gpt-oss(-|$))/i.test(typeof m === "string" ? m : m.id));
|
|
193
202
|
const out = [];
|
|
194
203
|
kept.forEach((account, index) => {
|
|
195
204
|
const quotient = groups[account.id];
|
|
196
205
|
if (!quotient || !quotient.length)
|
|
197
206
|
return;
|
|
198
|
-
const windows = googleWindowsFromGroups(quotient);
|
|
207
|
+
const windows = googleWindowsFromGroups(quotient).filter((w) => thirdParty || w.kind !== "scoped");
|
|
199
208
|
if (windows.length)
|
|
200
209
|
out.push({ account: index, windows, at });
|
|
201
210
|
});
|
|
@@ -1665,6 +1674,8 @@ export function startAdmin(deps) {
|
|
|
1665
1674
|
const scores = new ModelScoreCatalog({ home: homeDir(), warn: message => deps.log.warn(message), ...(deps.modelScoresFetch ? { fetch: deps.modelScoresFetch } : {}) });
|
|
1666
1675
|
// Set again once the socket is bound (port 0 in tests); the Origin check below compares against it.
|
|
1667
1676
|
let boundPort = adminPort(deps.config());
|
|
1677
|
+
/** The dashboard's `awake setup` run in progress (or the last one), for GET /api/power/setup. */
|
|
1678
|
+
let powerSetup = { running: false };
|
|
1668
1679
|
const server = http.createServer((req, res) => {
|
|
1669
1680
|
void handle(req, res);
|
|
1670
1681
|
});
|
|
@@ -1747,6 +1758,98 @@ export function startAdmin(deps) {
|
|
|
1747
1758
|
sendJson(res, 200, { hosts: deps.remote.reload() });
|
|
1748
1759
|
return;
|
|
1749
1760
|
}
|
|
1761
|
+
if (pathname === "/api/power" && (method === "GET" || method === "PUT")) {
|
|
1762
|
+
if (!deps.power) {
|
|
1763
|
+
sendJson(res, 501, { error: "power control not available" });
|
|
1764
|
+
return;
|
|
1765
|
+
}
|
|
1766
|
+
if (method === "GET") {
|
|
1767
|
+
sendJson(res, 200, deps.power.status());
|
|
1768
|
+
return;
|
|
1769
|
+
}
|
|
1770
|
+
let parsed;
|
|
1771
|
+
try {
|
|
1772
|
+
parsed = JSON.parse((await readBody(req, 4096)).toString("utf8"));
|
|
1773
|
+
}
|
|
1774
|
+
catch {
|
|
1775
|
+
sendJson(res, 400, { error: "invalid JSON" });
|
|
1776
|
+
return;
|
|
1777
|
+
}
|
|
1778
|
+
const change = parsePowerChange(parsed);
|
|
1779
|
+
if (typeof change === "string") {
|
|
1780
|
+
sendJson(res, 400, { error: change });
|
|
1781
|
+
return;
|
|
1782
|
+
}
|
|
1783
|
+
if (persistPowerChange(deps.configFile, change)) {
|
|
1784
|
+
deps.log.info(`admin: power saved (${JSON.stringify({ ...(change.awake ? { awake: change.awake } : {}), ...(change.tether?.enabled !== undefined ? { tether: { enabled: change.tether.enabled } } : {}) })})`);
|
|
1785
|
+
}
|
|
1786
|
+
if (change.tether?.pin !== undefined)
|
|
1787
|
+
deps.power.pin(change.tether.pin);
|
|
1788
|
+
await deps.power.poke();
|
|
1789
|
+
sendJson(res, 200, deps.power.status());
|
|
1790
|
+
return;
|
|
1791
|
+
}
|
|
1792
|
+
// The one privileged step, from the dashboard: the CLI's `awake setup`, which shows macOS's own
|
|
1793
|
+
// password dialog. It runs in the background and is polled, like the ChatGPT sign-in: the user
|
|
1794
|
+
// may take minutes to type, and the app's window gives up on a response with no headers after
|
|
1795
|
+
// 4s (packages/app/src/protocol.ts) — a reply held open until the end would read as a dead router.
|
|
1796
|
+
if (pathname === "/api/power/setup" && (method === "GET" || method === "POST")) {
|
|
1797
|
+
if (!deps.power) {
|
|
1798
|
+
sendJson(res, 501, { error: "power control not available" });
|
|
1799
|
+
return;
|
|
1800
|
+
}
|
|
1801
|
+
if (method === "GET") {
|
|
1802
|
+
sendJson(res, 200, powerSetup);
|
|
1803
|
+
return;
|
|
1804
|
+
}
|
|
1805
|
+
let parsed;
|
|
1806
|
+
try {
|
|
1807
|
+
const text = (await readBody(req, 4096)).toString("utf8");
|
|
1808
|
+
parsed = text.trim() ? JSON.parse(text) : {};
|
|
1809
|
+
}
|
|
1810
|
+
catch {
|
|
1811
|
+
sendJson(res, 400, { error: "invalid JSON" });
|
|
1812
|
+
return;
|
|
1813
|
+
}
|
|
1814
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1815
|
+
sendJson(res, 400, { error: "expected an object" });
|
|
1816
|
+
return;
|
|
1817
|
+
}
|
|
1818
|
+
const extra = Object.keys(parsed).find((key) => key !== "ssid");
|
|
1819
|
+
if (extra) {
|
|
1820
|
+
sendJson(res, 400, { error: `unknown field "${extra}"` });
|
|
1821
|
+
return;
|
|
1822
|
+
}
|
|
1823
|
+
const ssid = parsed.ssid;
|
|
1824
|
+
if (ssid !== undefined) {
|
|
1825
|
+
// Checked here too so a bad name is refused before a password dialog, not after it.
|
|
1826
|
+
const problem = ssidProblem(ssid);
|
|
1827
|
+
if (problem) {
|
|
1828
|
+
sendJson(res, 400, { error: problem });
|
|
1829
|
+
return;
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
if (powerSetup.running) {
|
|
1833
|
+
sendJson(res, 409, { error: "setup is already running", running: true });
|
|
1834
|
+
return;
|
|
1835
|
+
}
|
|
1836
|
+
powerSetup = { running: true, startedAt: new Date().toISOString() };
|
|
1837
|
+
const args = ["awake", "setup", ...(typeof ssid === "string" ? ["--ssid", ssid] : [])];
|
|
1838
|
+
deps.log.info(`admin: awake setup via GUI -> started${typeof ssid === "string" ? ` (hotspot "${ssid}")` : ""}`);
|
|
1839
|
+
const power = deps.power;
|
|
1840
|
+
// The CLI's own dialog times out at 5 min; this leaves it room to say so itself.
|
|
1841
|
+
void (deps.runCli ?? runCli)(args, 330_000).then((result) => ({ ok: result.ok, output: result.output }), (error) => ({ ok: false, output: errorText(error) })).then(async (result) => {
|
|
1842
|
+
powerSetup = { ...powerSetup, running: false, finishedAt: new Date().toISOString(), ...result };
|
|
1843
|
+
deps.log.info(`admin: awake setup via GUI -> ${result.ok ? "ok" : "failed"}`);
|
|
1844
|
+
// Re-read whether the rule is in place now, so the next GET /api/power says so.
|
|
1845
|
+
try {
|
|
1846
|
+
await power.poke();
|
|
1847
|
+
}
|
|
1848
|
+
catch { /* the next tick does it */ }
|
|
1849
|
+
});
|
|
1850
|
+
sendJson(res, 202, { started: true });
|
|
1851
|
+
return;
|
|
1852
|
+
}
|
|
1750
1853
|
// Liveness is answering at all; readiness is 200 only when a request can actually go through.
|
|
1751
1854
|
if (pathname === "/readyz" && method === "GET") {
|
|
1752
1855
|
const readiness = (await buildStatus(deps)).readiness;
|