cartethyia 2.3.0 → 2.3.2
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 +25 -0
- package/dist/dashboard/assets/{BannedPage-ClPA4nQv.js → BannedPage-BprrKF3t.js} +1 -1
- package/dist/dashboard/assets/{CliToolDetailPage-Bevb0yY1.js → CliToolDetailPage-CeeIxvCm.js} +1 -1
- package/dist/dashboard/assets/{CliToolsPage-DsryPzyc.js → CliToolsPage-BQjMUpJJ.js} +1 -1
- package/dist/dashboard/assets/{ConsoleLogPage-CTzqdm6k.js → ConsoleLogPage-CAzWm-tU.js} +1 -1
- package/dist/dashboard/assets/{CustomizationPage-CMxyDZej.js → CustomizationPage-B3SKxD3y.js} +1 -1
- package/dist/dashboard/assets/{HealthEventsModal-CxkQ4JaG.js → HealthEventsModal-DQNKf-5H.js} +1 -1
- package/dist/dashboard/assets/{LoginPage-CL2GR7wU.js → LoginPage-OXNbMIZC.js} +1 -1
- package/dist/dashboard/assets/{OverviewPage-b0Ggk2qb.js → OverviewPage-UKI1t9nU.js} +1 -1
- package/dist/dashboard/assets/{ProviderDetailPage-Y1Q4UPbO.js → ProviderDetailPage-Bh_MZ03h.js} +1 -1
- package/dist/dashboard/assets/{ProvidersPage-C-GI5Qrf.js → ProvidersPage-sP8nDPZN.js} +1 -1
- package/dist/dashboard/assets/{ProxyPage-d7vbm79h.js → ProxyPage-Ci1Ps9K3.js} +1 -1
- package/dist/dashboard/assets/{QuotaPage-BmLGXSiw.js → QuotaPage-A1jKKb0S.js} +1 -1
- package/dist/dashboard/assets/{SetupPage-BZ2NsM8f.js → SetupPage-Ce4ldWt7.js} +1 -1
- package/dist/dashboard/assets/{StudioPage-txsvSOwb.js → StudioPage-CFDyo2dT.js} +1 -1
- package/dist/dashboard/assets/{UsagePage-D8C_HZBY.js → UsagePage-BLCXYWis.js} +1 -1
- package/dist/dashboard/assets/{entry-DcX5sMzh.js → entry-B2Cv6U9r.js} +3 -3
- package/dist/dashboard/assets/{index-By44uTGZ.js → index-C7EcMumQ.js} +2 -2
- package/dist/dashboard/index.html +1 -1
- package/package.json +2 -2
- package/scripts/cli/carte-launcher.cjs +5 -1
- package/scripts/cli/cli-launcher.cjs +32 -11
- package/scripts/cli/cli.ts +132 -4
|
@@ -21,17 +21,34 @@ const startupSignalPath = trayMode
|
|
|
21
21
|
? path.join(os.tmpdir(), `cartethyia-tray-${process.pid}-${randomUUID()}.json`)
|
|
22
22
|
: null;
|
|
23
23
|
let spawnError;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
24
|
+
// On Windows, `cmd /c start "" /b` gives bun its own console group so the
|
|
25
|
+
// launching terminal can close without signalling the gateway. Elsewhere
|
|
26
|
+
// `detached: true` is enough. Either way the child is unref'd below so the
|
|
27
|
+
// launcher can exit without taking the gateway down with it.
|
|
28
|
+
const useWindowsDetach = process.platform === "win32" && trayMode;
|
|
29
|
+
const child = useWindowsDetach
|
|
30
|
+
? spawn("cmd", ["/c", "start", "", "/b", "bun", "run", cliPath, ...process.argv.slice(2)], {
|
|
31
|
+
cwd: packageRoot,
|
|
32
|
+
detached: true,
|
|
33
|
+
stdio: "ignore",
|
|
34
|
+
windowsHide: true,
|
|
35
|
+
env: {
|
|
36
|
+
...process.env,
|
|
37
|
+
CARTETHYIA_CONFIG_DIR: configDir,
|
|
38
|
+
CARTETHYIA_TRAY_STARTUP_SIGNAL: startupSignalPath,
|
|
39
|
+
},
|
|
40
|
+
})
|
|
41
|
+
: spawn("bun", ["run", cliPath, ...process.argv.slice(2)], {
|
|
42
|
+
cwd: packageRoot,
|
|
43
|
+
detached: trayMode,
|
|
44
|
+
stdio: trayMode ? "ignore" : "inherit",
|
|
45
|
+
windowsHide: trayMode,
|
|
46
|
+
env: {
|
|
47
|
+
...process.env,
|
|
48
|
+
CARTETHYIA_CONFIG_DIR: configDir,
|
|
49
|
+
...(trayMode ? { CARTETHYIA_TRAY_STARTUP_SIGNAL: startupSignalPath } : {}),
|
|
50
|
+
},
|
|
51
|
+
});
|
|
35
52
|
|
|
36
53
|
child.once("error", (error) => {
|
|
37
54
|
spawnError = error;
|
|
@@ -61,6 +78,10 @@ if (trayMode) {
|
|
|
61
78
|
return;
|
|
62
79
|
}
|
|
63
80
|
console.log(`Cartethyia is ready at ${url}`);
|
|
81
|
+
// The gateway child is already detached and unref'd; this Node process is
|
|
82
|
+
// the only thing still holding the launching terminal open. Exit once the
|
|
83
|
+
// URL has been shown so the shell prompt comes back.
|
|
84
|
+
setTimeout(() => process.exit(0), 1_500);
|
|
64
85
|
};
|
|
65
86
|
const poll = () => {
|
|
66
87
|
if (settled) return;
|
package/scripts/cli/cli.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { join } from "node:path";
|
|
|
6
6
|
import SysTray from "systray2";
|
|
7
7
|
import { isNewerRelease } from "../../src/console/updates/version-compare";
|
|
8
8
|
import { PACKAGE_ROOT, resolvePackagePath, resolveUserConfigDir } from "../../src/config/package-paths";
|
|
9
|
+
import { resolvePort } from "../../src/config/runtime";
|
|
9
10
|
import { MINIMUM_BUN_VERSION, bunVersionAtLeast } from "../shared/env";
|
|
10
11
|
|
|
11
12
|
process.env.CARTETHYIA_CONFIG_DIR ??= resolveUserConfigDir();
|
|
@@ -13,13 +14,18 @@ process.env.CARTETHYIA_CONFIG_DIR ??= resolveUserConfigDir();
|
|
|
13
14
|
const HELP = `Cartethyia ${MINIMUM_BUN_VERSION}+ (Bun is required)
|
|
14
15
|
|
|
15
16
|
Usage:
|
|
16
|
-
carte
|
|
17
|
+
carte start, then show an arrow-key menu (no GUI needed)
|
|
18
|
+
carte --tray start with a system-tray icon instead
|
|
17
19
|
cartethyia setup [--non-interactive]
|
|
18
20
|
cartethyia start
|
|
21
|
+
cartethyia menu
|
|
19
22
|
cartethyia tray
|
|
20
23
|
cartethyia update
|
|
21
24
|
|
|
22
|
-
|
|
25
|
+
carte starts the gateway and opens a menu driven by the arrow keys and Enter:
|
|
26
|
+
open the console, open settings, hide to the background, check for updates, or
|
|
27
|
+
exit. It needs no desktop environment, so it works over SSH and headless.
|
|
28
|
+
carte --tray uses a system-tray icon instead, which needs a graphical session.
|
|
23
29
|
Setup explains Lite (embedded SQLite) and Full (external PostgreSQL), then saves
|
|
24
30
|
configuration under the current user's Cartethyia config directory.
|
|
25
31
|
`;
|
|
@@ -125,6 +131,60 @@ function openUrl(url: string): Promise<void> {
|
|
|
125
131
|
return child.exited.then(() => undefined);
|
|
126
132
|
}
|
|
127
133
|
|
|
134
|
+
/**
|
|
135
|
+
* A single-select menu driven by the arrow keys and Enter, built on stdin raw
|
|
136
|
+
* mode so no menu dependency is added. It only reads keys and returns an
|
|
137
|
+
* index; the caller keeps owning the gateway process.
|
|
138
|
+
*
|
|
139
|
+
* Returns -1 when stdin is not a TTY (piped, CI, or a non-interactive run) so
|
|
140
|
+
* callers can fall back without blocking on input that will never arrive.
|
|
141
|
+
*/
|
|
142
|
+
function chooseFromMenu(title: string, options: readonly string[]): Promise<number> {
|
|
143
|
+
const stdin = process.stdin;
|
|
144
|
+
if (!stdin.isTTY) return Promise.resolve(-1);
|
|
145
|
+
const { promise, resolve } = Promise.withResolvers<number>();
|
|
146
|
+
let index = 0;
|
|
147
|
+
|
|
148
|
+
const render = (): void => {
|
|
149
|
+
process.stdout.write(`\u001b[2J\u001b[H${title}\n\n`);
|
|
150
|
+
options.forEach((option, position) => {
|
|
151
|
+
const marker = position === index ? "\u2192" : " ";
|
|
152
|
+
process.stdout.write(` ${marker} ${option}\n`);
|
|
153
|
+
});
|
|
154
|
+
process.stdout.write("\n\u2191\u2193 move \u00b7 Enter choose \u00b7 Esc/q exit\n");
|
|
155
|
+
};
|
|
156
|
+
const cleanup = (choice: number): void => {
|
|
157
|
+
stdin.setRawMode(false);
|
|
158
|
+
stdin.off("keypress", onKey);
|
|
159
|
+
stdin.pause();
|
|
160
|
+
process.stdout.write("\u001b[2J\u001b[H");
|
|
161
|
+
resolve(choice);
|
|
162
|
+
};
|
|
163
|
+
const onKey = (_chunk: unknown, key: { name?: string }): void => {
|
|
164
|
+
if (key.name === "up") {
|
|
165
|
+
index = (index - 1 + options.length) % options.length;
|
|
166
|
+
render();
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (key.name === "down") {
|
|
170
|
+
index = (index + 1) % options.length;
|
|
171
|
+
render();
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (key.name === "return") {
|
|
175
|
+
cleanup(index);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (key.name === "escape" || key.name === "q") cleanup(-1);
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
stdin.setRawMode(true);
|
|
182
|
+
stdin.resume();
|
|
183
|
+
render();
|
|
184
|
+
stdin.on("keypress", onKey);
|
|
185
|
+
return promise;
|
|
186
|
+
}
|
|
187
|
+
|
|
128
188
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
129
189
|
return value !== null && typeof value === "object";
|
|
130
190
|
}
|
|
@@ -289,8 +349,73 @@ async function stopTray(tray: SysTray, ready: boolean, exited: boolean): Promise
|
|
|
289
349
|
if (!tray.killed) tray.process.kill();
|
|
290
350
|
}
|
|
291
351
|
|
|
352
|
+
/**
|
|
353
|
+
* The default `carte` experience: start the gateway, then offer an arrow-key
|
|
354
|
+
* menu. This needs no desktop environment, so it works over SSH, on a headless
|
|
355
|
+
* box, and on Windows where the tray icon lands in the hidden-icons overflow
|
|
356
|
+
* and is never seen. `carte --tray` opts into the icon instead.
|
|
357
|
+
*/
|
|
358
|
+
async function startMenu(): Promise<void> {
|
|
359
|
+
const port = resolvePort();
|
|
360
|
+
const drainToken = randomBytes(32).toString("hex");
|
|
361
|
+
const dashboardUrl = `http://127.0.0.1:${port}/console`;
|
|
362
|
+
const settingsUrl = `${dashboardUrl}/settings`;
|
|
363
|
+
const currentVersion = await readInstalledVersion();
|
|
364
|
+
|
|
365
|
+
const gateway = await startGateway(port, drainToken);
|
|
366
|
+
let stopping = false;
|
|
367
|
+
const shutDown = async (): Promise<void> => {
|
|
368
|
+
if (stopping) return;
|
|
369
|
+
stopping = true;
|
|
370
|
+
await stopGateway(gateway, port, drainToken);
|
|
371
|
+
};
|
|
372
|
+
process.once("SIGINT", () => void shutDown());
|
|
373
|
+
process.once("SIGTERM", () => void shutDown());
|
|
374
|
+
|
|
375
|
+
const latest = await readLatestPublishedVersion();
|
|
376
|
+
const updateLabel =
|
|
377
|
+
latest !== null && isNewerRelease(currentVersion, latest)
|
|
378
|
+
? `Update to ${latest}`
|
|
379
|
+
: "Check for updates";
|
|
380
|
+
|
|
381
|
+
const options = [
|
|
382
|
+
`Open Console \u00b7 ${dashboardUrl}`,
|
|
383
|
+
"Settings \u00b7 backup & restore, reset password",
|
|
384
|
+
"Hide to background and keep running",
|
|
385
|
+
updateLabel,
|
|
386
|
+
"Exit",
|
|
387
|
+
] as const;
|
|
388
|
+
|
|
389
|
+
console.log(`Cartethyia ${currentVersion} is running at ${dashboardUrl}`);
|
|
390
|
+
for (;;) {
|
|
391
|
+
const choice = await chooseFromMenu(
|
|
392
|
+
`Cartethyia ${currentVersion} \u2014 running at ${dashboardUrl}`,
|
|
393
|
+
options,
|
|
394
|
+
);
|
|
395
|
+
if (choice === -1 || choice === 4) break;
|
|
396
|
+
if (choice === 0) {
|
|
397
|
+
await openUrl(dashboardUrl);
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
if (choice === 1) {
|
|
401
|
+
await openUrl(settingsUrl);
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
if (choice === 2) {
|
|
405
|
+
// The gateway is already a detached child, so returning here leaves it
|
|
406
|
+
// running while this process lets go of the terminal.
|
|
407
|
+
console.log(`Cartethyia keeps running at ${dashboardUrl}. Stop it with: cartethyia stop`);
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
if (choice === 3) {
|
|
411
|
+
const code = await npmUpdate();
|
|
412
|
+
console.log(code === 0 ? "Update finished." : "Update failed.");
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
await shutDown();
|
|
416
|
+
}
|
|
417
|
+
|
|
292
418
|
async function startTray(): Promise<void> {
|
|
293
|
-
const { resolvePort } = await import("../../src/config/runtime");
|
|
294
419
|
const port = resolvePort();
|
|
295
420
|
const drainToken = randomBytes(32).toString("hex");
|
|
296
421
|
const currentVersion = await readInstalledVersion();
|
|
@@ -535,7 +660,7 @@ async function startTray(): Promise<void> {
|
|
|
535
660
|
async function main(): Promise<void> {
|
|
536
661
|
assertBunVersion();
|
|
537
662
|
const [command] = process.argv.slice(2);
|
|
538
|
-
if (command === "setup" || command === "start" || command === "tray") {
|
|
663
|
+
if (command === "setup" || command === "start" || command === "tray" || command === "menu") {
|
|
539
664
|
await loadUserConfig();
|
|
540
665
|
}
|
|
541
666
|
|
|
@@ -548,6 +673,9 @@ async function main(): Promise<void> {
|
|
|
548
673
|
case "start":
|
|
549
674
|
await import("../../src/main");
|
|
550
675
|
return;
|
|
676
|
+
case "menu":
|
|
677
|
+
await startMenu();
|
|
678
|
+
return;
|
|
551
679
|
case "tray":
|
|
552
680
|
await startTray();
|
|
553
681
|
return;
|