@hellcoder/companion 0.124.0 → 0.125.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/bin/cli.ts +9 -1
- package/dist/assets/{AgentsPage-Byq8wvdn.js → AgentsPage-0Fi6tbQN.js} +1 -1
- package/dist/assets/{CronManager-Bq2lOMbZ.js → CronManager-DNN709nr.js} +1 -1
- package/dist/assets/{DashboardPage-V77HIt8S.js → DashboardPage-6cNRrqGQ.js} +1 -1
- package/dist/assets/{IntegrationsPage-C3aW9cDI.js → IntegrationsPage-DrE-8KPE.js} +1 -1
- package/dist/assets/{LinearOAuthSettingsPage-_eAbciJN.js → LinearOAuthSettingsPage-Czi_NsX1.js} +1 -1
- package/dist/assets/{LinearSettingsPage-DDHFHt7j.js → LinearSettingsPage-C1RHlPwX.js} +1 -1
- package/dist/assets/{MagicUIDashboard-Cl18AKjV.js → MagicUIDashboard-BOZc4rKn.js} +1 -1
- package/dist/assets/{MagicUIView-C0aiMTet.js → MagicUIView-BVM8CqGg.js} +1 -1
- package/dist/assets/{Playground-CvoHRMBd.js → Playground-DWAal5bL.js} +1 -1
- package/dist/assets/{PromptsPage-CzTs1_Se.js → PromptsPage-D2YI_-95.js} +1 -1
- package/dist/assets/{RunsPage-B7xru-Xy.js → RunsPage-dn5rSkPu.js} +1 -1
- package/dist/assets/{SandboxManager-C3tFkiWz.js → SandboxManager-vWbQKeLT.js} +1 -1
- package/dist/assets/{SettingsPage-BZ2fBrHD.js → SettingsPage-CrV1Tb8F.js} +1 -1
- package/dist/assets/{TailscalePage-DFRiFKcb.js → TailscalePage-ba0g8QT1.js} +1 -1
- package/dist/assets/{index-C7nK12tB.js → index-e4RnFbra.js} +4 -4
- package/dist/assets/{sw-register-Dc72vh_Y.js → sw-register-BBun0drK.js} +1 -1
- package/dist/index.html +1 -1
- package/dist/sw.js +1 -1
- package/package.json +1 -1
- package/server/index.ts +2 -2
- package/server/routes/system-routes.test.ts +57 -0
- package/server/routes/system-routes.ts +20 -24
- package/server/service.test.ts +232 -0
- package/server/service.ts +286 -23
- package/server/settings-manager.ts +1 -0
package/server/service.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
import { join } from "node:path";
|
|
9
9
|
import { homedir } from "node:os";
|
|
10
10
|
import { execSync } from "node:child_process";
|
|
11
|
+
import { createServer } from "node:net";
|
|
11
12
|
import { DEFAULT_PORT_PROD } from "./constants.js";
|
|
12
13
|
import { getServicePath } from "./path-resolver.js";
|
|
13
14
|
import { COMPANION_HOME } from "./paths.js";
|
|
@@ -32,6 +33,16 @@ const SYSTEMD_DIR = join(homedir(), ".config", "systemd", "user");
|
|
|
32
33
|
const UNIT_NAME = "the-companion.service";
|
|
33
34
|
const UNIT_PATH = join(SYSTEMD_DIR, UNIT_NAME);
|
|
34
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Exit code the server uses to ask an external supervisor (a system-wide
|
|
38
|
+
* systemd unit, or anything configured via COMPANION_SERVICE_MODE=1) to
|
|
39
|
+
* restart it after an in-app update. It must be non-zero so that units with
|
|
40
|
+
* `Restart=on-failure` respawn the process, and it deliberately differs from
|
|
41
|
+
* 42, which our own user-unit template lists in `SuccessExitStatus=`.
|
|
42
|
+
* 75 is EX_TEMPFAIL from sysexits.h ("temporary failure, try again").
|
|
43
|
+
*/
|
|
44
|
+
export const UPDATE_RESTART_EXIT_CODE = 75;
|
|
45
|
+
|
|
35
46
|
// ─── Platform check ─────────────────────────────────────────────────────────────
|
|
36
47
|
|
|
37
48
|
function ensureSupportedPlatform(): void {
|
|
@@ -225,6 +236,114 @@ function systemctlUser(cmd: string): string {
|
|
|
225
236
|
});
|
|
226
237
|
}
|
|
227
238
|
|
|
239
|
+
/**
|
|
240
|
+
* Run a command against the system-wide systemd instance (no `--user`).
|
|
241
|
+
* Read-only queries (show / is-active) work unprivileged; mutating commands
|
|
242
|
+
* need root or a polkit rule.
|
|
243
|
+
*/
|
|
244
|
+
function systemctlSystem(cmd: string): string {
|
|
245
|
+
return execSync(`systemctl ${cmd}`, {
|
|
246
|
+
encoding: "utf-8",
|
|
247
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export interface SystemUnitInfo {
|
|
252
|
+
activeState: string;
|
|
253
|
+
mainPid?: number;
|
|
254
|
+
invocationId?: string;
|
|
255
|
+
fragmentPath?: string;
|
|
256
|
+
port?: number;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Look up a system-wide `the-companion.service` (e.g. one an admin placed in
|
|
261
|
+
* /etc/systemd/system with `User=...`). Returns undefined when no such unit
|
|
262
|
+
* is loaded or systemctl is unavailable. Never throws.
|
|
263
|
+
*/
|
|
264
|
+
export function getSystemUnitInfo(): SystemUnitInfo | undefined {
|
|
265
|
+
if (!isLinux()) return undefined;
|
|
266
|
+
let output: string;
|
|
267
|
+
try {
|
|
268
|
+
output = systemctlSystem(
|
|
269
|
+
`show ${UNIT_NAME} --property=LoadState,ActiveState,MainPID,InvocationID,FragmentPath,Environment --no-pager`,
|
|
270
|
+
);
|
|
271
|
+
} catch {
|
|
272
|
+
return undefined;
|
|
273
|
+
}
|
|
274
|
+
if (typeof output !== "string") return undefined;
|
|
275
|
+
const prop = (name: string): string | undefined => {
|
|
276
|
+
const m = output.match(new RegExp(`^${name}=(.*)$`, "m"));
|
|
277
|
+
return m ? m[1].trim() : undefined;
|
|
278
|
+
};
|
|
279
|
+
// LoadState=not-found is what systemd reports for units that don't exist.
|
|
280
|
+
if (prop("LoadState") !== "loaded") return undefined;
|
|
281
|
+
|
|
282
|
+
const pid = Number(prop("MainPID"));
|
|
283
|
+
const portMatch = prop("Environment")?.match(/(?:^|\s)PORT=(\d+)/);
|
|
284
|
+
return {
|
|
285
|
+
activeState: prop("ActiveState") || "unknown",
|
|
286
|
+
mainPid: Number.isFinite(pid) && pid > 0 ? pid : undefined,
|
|
287
|
+
invocationId: prop("InvocationID") || undefined,
|
|
288
|
+
fragmentPath: prop("FragmentPath") || undefined,
|
|
289
|
+
port: portMatch ? Number(portMatch[1]) : undefined,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** Explains how to manage a system-wide unit, which we can't do for the user. */
|
|
294
|
+
function printSystemUnitHint(action: string, info: SystemUnitInfo): void {
|
|
295
|
+
console.log("The Companion is managed by a system-wide systemd unit:");
|
|
296
|
+
console.log(` Unit: ${info.fragmentPath ?? UNIT_NAME}`);
|
|
297
|
+
console.log(` State: ${info.activeState}`);
|
|
298
|
+
console.log("");
|
|
299
|
+
console.log(`Use: sudo systemctl ${action} ${UNIT_NAME}`);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function isRoot(): boolean {
|
|
303
|
+
return typeof process.getuid === "function" && process.getuid() === 0;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Handle start/stop/restart when only a system-wide unit exists. As root we
|
|
308
|
+
* can drive it directly; otherwise print the sudo command and fail, rather
|
|
309
|
+
* than falling through to `systemctl --user` (misleading) or, for `start`,
|
|
310
|
+
* installing a second, conflicting user unit.
|
|
311
|
+
*/
|
|
312
|
+
function controlSystemUnit(action: "start" | "stop" | "restart", info: SystemUnitInfo): void {
|
|
313
|
+
if (isRoot()) {
|
|
314
|
+
try {
|
|
315
|
+
systemctlSystem(`${action} ${UNIT_NAME}`);
|
|
316
|
+
} catch (err: unknown) {
|
|
317
|
+
console.error(`Failed to ${action} the system service with systemctl:`);
|
|
318
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
319
|
+
process.exit(1);
|
|
320
|
+
}
|
|
321
|
+
const past = action === "stop" ? "stopped" : action === "start" ? "started" : "restarted";
|
|
322
|
+
console.log(`The Companion system service has been ${past}.`);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
printSystemUnitHint(action, info);
|
|
326
|
+
process.exit(1);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Resolve true when something is already listening on `port`. Binding with
|
|
331
|
+
* no host uses the dual-stack wildcard, which conflicts with a listener on
|
|
332
|
+
* any address, so this also catches servers bound to 0.0.0.0 or 127.0.0.1.
|
|
333
|
+
*/
|
|
334
|
+
export function isPortInUse(port: number): Promise<boolean> {
|
|
335
|
+
return new Promise((resolve) => {
|
|
336
|
+
const server = createServer();
|
|
337
|
+
server.once("error", (err: NodeJS.ErrnoException) => {
|
|
338
|
+
resolve(err.code === "EADDRINUSE");
|
|
339
|
+
});
|
|
340
|
+
server.once("listening", () => {
|
|
341
|
+
server.close(() => resolve(false));
|
|
342
|
+
});
|
|
343
|
+
server.listen(port);
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
228
347
|
// ─── Install ────────────────────────────────────────────────────────────────────
|
|
229
348
|
|
|
230
349
|
export async function install(opts?: { port?: number }): Promise<void> {
|
|
@@ -285,9 +404,31 @@ async function installLinux(opts?: { port?: number }): Promise<void> {
|
|
|
285
404
|
process.exit(1);
|
|
286
405
|
}
|
|
287
406
|
|
|
407
|
+
// A system-wide unit already supervises the Companion on this host. A
|
|
408
|
+
// second (user) unit would fight it for the port and crash-loop, or, if it
|
|
409
|
+
// ever won, start with the wrong environment and an empty session store.
|
|
410
|
+
const systemUnit = getSystemUnitInfo();
|
|
411
|
+
if (systemUnit) {
|
|
412
|
+
console.error("The Companion is already installed as a system-wide systemd unit:");
|
|
413
|
+
console.error(` ${systemUnit.fragmentPath ?? UNIT_NAME} (${systemUnit.activeState})`);
|
|
414
|
+
console.error("");
|
|
415
|
+
console.error("Refusing to install a second, per-user unit.");
|
|
416
|
+
console.error(`Manage it with: sudo systemctl <start|stop|restart|status> ${UNIT_NAME}`);
|
|
417
|
+
console.error("In-app updates work there too (see COMPANION_SERVICE_MODE in the docs).");
|
|
418
|
+
process.exit(1);
|
|
419
|
+
}
|
|
420
|
+
|
|
288
421
|
const binPath = resolveBinPath();
|
|
289
422
|
const port = opts?.port ?? DEFAULT_PORT_PROD;
|
|
290
423
|
|
|
424
|
+
if (await isPortInUse(port)) {
|
|
425
|
+
console.error(`Port ${port} is already in use.`);
|
|
426
|
+
console.error("Another Companion (or something else) is listening there, so the");
|
|
427
|
+
console.error("service would crash-loop. Stop it first, or install on another port:");
|
|
428
|
+
console.error(" the-companion install --port <port>");
|
|
429
|
+
process.exit(1);
|
|
430
|
+
}
|
|
431
|
+
|
|
291
432
|
// Create log directory
|
|
292
433
|
mkdirSync(LOG_DIR, { recursive: true });
|
|
293
434
|
|
|
@@ -356,6 +497,16 @@ async function uninstallDarwin(): Promise<void> {
|
|
|
356
497
|
|
|
357
498
|
async function uninstallLinux(): Promise<void> {
|
|
358
499
|
if (!isSystemdUnitInstalled()) {
|
|
500
|
+
const systemUnit = getSystemUnitInfo();
|
|
501
|
+
if (systemUnit) {
|
|
502
|
+
// Never remove an admin-managed unit on the user's behalf.
|
|
503
|
+
console.log("The Companion is managed by a system-wide systemd unit, which");
|
|
504
|
+
console.log("'the-companion uninstall' does not remove. To remove it:");
|
|
505
|
+
console.log(` sudo systemctl disable --now ${UNIT_NAME}`);
|
|
506
|
+
console.log(` sudo rm ${systemUnit.fragmentPath ?? `/etc/systemd/system/${UNIT_NAME}`}`);
|
|
507
|
+
console.log(" sudo systemctl daemon-reload");
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
359
510
|
console.log("The Companion is not installed as a service.");
|
|
360
511
|
return;
|
|
361
512
|
}
|
|
@@ -428,6 +579,8 @@ async function startDarwin(): Promise<void> {
|
|
|
428
579
|
|
|
429
580
|
async function startLinux(): Promise<void> {
|
|
430
581
|
if (!isSystemdUnitInstalled()) {
|
|
582
|
+
const systemUnit = getSystemUnitInfo();
|
|
583
|
+
if (systemUnit) return controlSystemUnit("start", systemUnit);
|
|
431
584
|
console.log("Service not yet installed. Installing now...");
|
|
432
585
|
await installLinux();
|
|
433
586
|
return; // installLinux uses enable --now which starts the service
|
|
@@ -484,6 +637,8 @@ async function stopDarwin(): Promise<void> {
|
|
|
484
637
|
|
|
485
638
|
async function stopLinux(): Promise<void> {
|
|
486
639
|
if (!isSystemdUnitInstalled()) {
|
|
640
|
+
const systemUnit = getSystemUnitInfo();
|
|
641
|
+
if (systemUnit) return controlSystemUnit("stop", systemUnit);
|
|
487
642
|
console.log("The Companion is not installed as a service.");
|
|
488
643
|
return;
|
|
489
644
|
}
|
|
@@ -540,6 +695,8 @@ async function restartDarwin(): Promise<void> {
|
|
|
540
695
|
|
|
541
696
|
async function restartLinux(): Promise<void> {
|
|
542
697
|
if (!isSystemdUnitInstalled()) {
|
|
698
|
+
const systemUnit = getSystemUnitInfo();
|
|
699
|
+
if (systemUnit) return controlSystemUnit("restart", systemUnit);
|
|
543
700
|
console.log("The Companion is not installed as a service.");
|
|
544
701
|
return;
|
|
545
702
|
}
|
|
@@ -565,38 +722,135 @@ export interface ServiceStatus {
|
|
|
565
722
|
running: boolean;
|
|
566
723
|
pid?: number;
|
|
567
724
|
port?: number;
|
|
725
|
+
/** Linux only: "system" when managed by a system-wide unit. */
|
|
726
|
+
scope?: "user" | "system";
|
|
568
727
|
}
|
|
569
728
|
|
|
570
729
|
/**
|
|
571
|
-
*
|
|
572
|
-
*
|
|
730
|
+
* How the current process is supervised, which decides how an in-app update
|
|
731
|
+
* restarts it:
|
|
732
|
+
* - "launchd" / "systemd-user": our own `the-companion install` units; the
|
|
733
|
+
* updater asks the service manager to restart us.
|
|
734
|
+
* - "systemd-system": a system-wide `the-companion.service` (typically with
|
|
735
|
+
* `User=...`), which an unprivileged process can't restart, so the updater
|
|
736
|
+
* exits with UPDATE_RESTART_EXIT_CODE and lets `Restart=` respawn it.
|
|
737
|
+
* - "external": COMPANION_SERVICE_MODE=1, i.e. the operator promises some
|
|
738
|
+
* supervisor restarts the process on a non-zero exit. Same exit strategy.
|
|
573
739
|
*/
|
|
574
|
-
export
|
|
740
|
+
export type ServiceKind = "launchd" | "systemd-user" | "systemd-system" | "external";
|
|
741
|
+
|
|
742
|
+
function serviceModeEnv(): "on" | "off" | undefined {
|
|
743
|
+
const raw = process.env.COMPANION_SERVICE_MODE?.trim().toLowerCase();
|
|
744
|
+
if (!raw) return undefined;
|
|
745
|
+
if (["1", "true", "yes", "on"].includes(raw)) return "on";
|
|
746
|
+
if (["0", "false", "no", "off"].includes(raw)) return "off";
|
|
747
|
+
return undefined;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
/**
|
|
751
|
+
* Detect how (and whether) the current process runs as a managed service.
|
|
752
|
+
* Never calls process.exit() and never throws.
|
|
753
|
+
*
|
|
754
|
+
* COMPANION_SERVICE_MODE=0 forces service mode off; =1 forces it on when no
|
|
755
|
+
* known service manager is detected.
|
|
756
|
+
*/
|
|
757
|
+
export function detectServiceKind(): ServiceKind | null {
|
|
758
|
+
const envMode = serviceModeEnv();
|
|
759
|
+
if (envMode === "off") return null;
|
|
760
|
+
|
|
575
761
|
if (isDarwin()) {
|
|
576
762
|
const installedService = getInstalledLaunchdService();
|
|
577
|
-
if (
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
return false;
|
|
763
|
+
if (installedService) {
|
|
764
|
+
try {
|
|
765
|
+
const output = execSync(`launchctl list "${installedService.label}"`, {
|
|
766
|
+
encoding: "utf-8",
|
|
767
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
768
|
+
});
|
|
769
|
+
if (/"PID"\s*=\s*\d+/.test(output)) return "launchd";
|
|
770
|
+
} catch { /* not loaded */ }
|
|
586
771
|
}
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
} catch {
|
|
595
|
-
return false;
|
|
772
|
+
} else if (isLinux()) {
|
|
773
|
+
if (isSystemdUnitInstalled()) {
|
|
774
|
+
try {
|
|
775
|
+
if (systemctlUser(`is-active ${UNIT_NAME}`).trim() === "active") {
|
|
776
|
+
return "systemd-user";
|
|
777
|
+
}
|
|
778
|
+
} catch { /* inactive or no user bus */ }
|
|
596
779
|
}
|
|
780
|
+
if (envMode !== "on" && isUnderSystemUnit()) return "systemd-system";
|
|
597
781
|
}
|
|
598
782
|
|
|
599
|
-
return
|
|
783
|
+
return envMode === "on" ? "external" : null;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/**
|
|
787
|
+
* True when this process was started by systemd as the system-wide
|
|
788
|
+
* the-companion.service. systemd exports INVOCATION_ID to every service it
|
|
789
|
+
* starts; matching it against the unit's InvocationID proves we are that
|
|
790
|
+
* unit's process rather than, say, a foreground run on a host that also has
|
|
791
|
+
* the unit installed.
|
|
792
|
+
*/
|
|
793
|
+
function isUnderSystemUnit(): boolean {
|
|
794
|
+
const invocationId = process.env.INVOCATION_ID?.trim();
|
|
795
|
+
if (!invocationId) return false;
|
|
796
|
+
const info = getSystemUnitInfo();
|
|
797
|
+
if (!info) return false;
|
|
798
|
+
if (info.activeState !== "active" && info.activeState !== "reloading") return false;
|
|
799
|
+
// Very old systemd versions don't expose InvocationID; fall back to the
|
|
800
|
+
// active state alone there.
|
|
801
|
+
return !info.invocationId || info.invocationId === invocationId.replace(/-/g, "");
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
let detectedServiceKind: ServiceKind | null = null;
|
|
805
|
+
|
|
806
|
+
/**
|
|
807
|
+
* Safe check for whether the current process is running as a managed service.
|
|
808
|
+
* Unlike status(), this never calls process.exit() and works on all platforms.
|
|
809
|
+
* The detected kind is remembered for getDetectedServiceKind().
|
|
810
|
+
*/
|
|
811
|
+
export function isRunningAsService(): boolean {
|
|
812
|
+
detectedServiceKind = detectServiceKind();
|
|
813
|
+
return detectedServiceKind !== null;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
/** The kind found by the last isRunningAsService() call (null if none). */
|
|
817
|
+
export function getDetectedServiceKind(): ServiceKind | null {
|
|
818
|
+
return detectedServiceKind;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
export interface UpdateRestartPlan {
|
|
822
|
+
/** Command to spawn (detached) before exiting, if any. */
|
|
823
|
+
command?: string[];
|
|
824
|
+
/** Extra environment for the command. */
|
|
825
|
+
env?: Record<string, string>;
|
|
826
|
+
/** Exit code for the current process once the command was spawned. */
|
|
827
|
+
exitCode: number;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
/**
|
|
831
|
+
* How to get the new version running after `bun install -g` succeeded.
|
|
832
|
+
* A null/undefined kind keeps the historical platform-based behaviour.
|
|
833
|
+
*/
|
|
834
|
+
export function getUpdateRestartPlan(kind: ServiceKind | null | undefined): UpdateRestartPlan {
|
|
835
|
+
if (kind === "systemd-system" || kind === "external") {
|
|
836
|
+
// Nothing to spawn: an unprivileged process can't restart a system unit,
|
|
837
|
+
// and the non-zero exit makes Restart=on-failure (or always) respawn us.
|
|
838
|
+
return { exitCode: UPDATE_RESTART_EXIT_CODE };
|
|
839
|
+
}
|
|
840
|
+
const uid = typeof process.getuid === "function" ? process.getuid() : undefined;
|
|
841
|
+
if (kind === "systemd-user" || (!kind && process.platform === "linux")) {
|
|
842
|
+
return {
|
|
843
|
+
command: ["systemctl", "--user", "restart", UNIT_NAME],
|
|
844
|
+
env: { XDG_RUNTIME_DIR: process.env.XDG_RUNTIME_DIR || `/run/user/${uid ?? 1000}` },
|
|
845
|
+
exitCode: 0,
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
return {
|
|
849
|
+
command: uid !== undefined
|
|
850
|
+
? ["launchctl", "kickstart", "-k", `gui/${uid}/${LABEL}`]
|
|
851
|
+
: ["launchctl", "kickstart", "-k", LABEL],
|
|
852
|
+
exitCode: 0,
|
|
853
|
+
};
|
|
600
854
|
}
|
|
601
855
|
|
|
602
856
|
/**
|
|
@@ -687,7 +941,16 @@ async function statusDarwin(): Promise<ServiceStatus> {
|
|
|
687
941
|
|
|
688
942
|
async function statusLinux(): Promise<ServiceStatus> {
|
|
689
943
|
if (!isSystemdUnitInstalled()) {
|
|
690
|
-
|
|
944
|
+
const systemUnit = getSystemUnitInfo();
|
|
945
|
+
if (!systemUnit) return { installed: false, running: false };
|
|
946
|
+
const running = systemUnit.activeState === "active" && !!systemUnit.mainPid;
|
|
947
|
+
return {
|
|
948
|
+
installed: true,
|
|
949
|
+
running,
|
|
950
|
+
pid: running ? systemUnit.mainPid : undefined,
|
|
951
|
+
port: systemUnit.port ?? DEFAULT_PORT_PROD,
|
|
952
|
+
scope: "system",
|
|
953
|
+
};
|
|
691
954
|
}
|
|
692
955
|
|
|
693
956
|
// Read port from the unit file
|
|
@@ -16,6 +16,7 @@ export const DEFAULT_ANTHROPIC_MODEL = "claude-opus-5-5";
|
|
|
16
16
|
export const DEFAULT_DASHBOARD_MODEL = "claude-haiku-4-5";
|
|
17
17
|
export const DASHBOARD_MODEL_OPTIONS = [
|
|
18
18
|
"claude-haiku-4-5",
|
|
19
|
+
"claude-haiku-5-5",
|
|
19
20
|
"claude-sonnet-4-6",
|
|
20
21
|
"claude-sonnet-5",
|
|
21
22
|
] as const;
|