@gajae-gateway/cli 0.1.2 → 0.1.3

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/dist/main.js CHANGED
@@ -129,7 +129,8 @@ function renderList(columns, rows, options, envelope) {
129
129
 
130
130
  // src/restart-stack.ts
131
131
  import { randomUUID } from "crypto";
132
- import { mkdir as mkdir2, readFile as readFile2, rename, stat, writeFile as writeFile2 } from "fs/promises";
132
+ import { access, mkdir as mkdir2, readFile as readFile2, rename, stat, writeFile as writeFile2 } from "fs/promises";
133
+ import { homedir as homedir2 } from "os";
133
134
  import { join as join2 } from "path";
134
135
 
135
136
  // src/services.ts
@@ -288,6 +289,12 @@ function systemdUnitDir(env, userHome) {
288
289
  const base = env.XDG_CONFIG_HOME || join(userHome, ".config");
289
290
  return join(base, "systemd", "user");
290
291
  }
292
+ function launchAgentPlistPath(spec, launchAgentsDir) {
293
+ return join(launchAgentsDir, `dev.gajaeway.${spec.id}.plist`);
294
+ }
295
+ function systemdUnitPath(spec, unitDir) {
296
+ return join(unitDir, systemdUnitName(spec));
297
+ }
291
298
  function restartStackCommands(platform, uid) {
292
299
  if (platform === "linux")
293
300
  return [["systemctl", "--user", "restart", GATEWAY_UNIT]];
@@ -462,6 +469,33 @@ function defaultServiceProbe(platform) {
462
469
  async function defaultBinaryModifiedAt(path) {
463
470
  return (await stat(path)).mtimeMs;
464
471
  }
472
+ function defaultIsServiceInstalled(options) {
473
+ return async (label) => {
474
+ const spec = serviceSpecs().find((s) => s.label === label);
475
+ if (spec === undefined)
476
+ return false;
477
+ if (options.platform === "darwin") {
478
+ const userHome2 = options.env.HOME || homedir2();
479
+ const agentsDir = options.launchAgentsDir || join2(userHome2, "Library", "LaunchAgents");
480
+ const plistPath = launchAgentPlistPath(spec, agentsDir);
481
+ try {
482
+ await access(plistPath);
483
+ return true;
484
+ } catch {
485
+ return false;
486
+ }
487
+ }
488
+ const userHome = options.env.HOME || homedir2();
489
+ const unitDirPath = options.unitDir || systemdUnitDir(options.env, userHome);
490
+ const unitPath = systemdUnitPath(spec, unitDirPath);
491
+ try {
492
+ await access(unitPath);
493
+ return true;
494
+ } catch {
495
+ return false;
496
+ }
497
+ };
498
+ }
465
499
  function floorSecond(ms) {
466
500
  return Math.floor(ms / 1000) * 1000;
467
501
  }
@@ -470,6 +504,12 @@ async function runRestartStack(options) {
470
504
  const runner = options.runner ?? defaultCommandRunner;
471
505
  const probe = options.probe ?? defaultServiceProbe(platform);
472
506
  const modifiedAt = options.binaryModifiedAt ?? defaultBinaryModifiedAt;
507
+ const isInstalled = options.isServiceInstalled ?? defaultIsServiceInstalled({
508
+ platform,
509
+ env: options.env ?? process.env,
510
+ launchAgentsDir: options.launchAgentsDir,
511
+ unitDir: options.unitDir
512
+ });
473
513
  const now = options.now ?? Date.now;
474
514
  const sleep = options.sleep ?? ((ms) => Bun.sleep(ms));
475
515
  const timeoutMs = options.verifyTimeoutMs ?? VERIFY_TIMEOUT_MS;
@@ -486,6 +526,13 @@ async function runRestartStack(options) {
486
526
  continue;
487
527
  }
488
528
  try {
529
+ const installed = await isInstalled(step.label);
530
+ if (!installed) {
531
+ step.result = "skipped";
532
+ step.detail = "service is not installed";
533
+ await writeReceipt(options.home, receipt);
534
+ continue;
535
+ }
489
536
  if (step.command !== undefined) {
490
537
  issuedAt = now();
491
538
  const status = await runner(step.command);
@@ -501,8 +548,9 @@ async function runRestartStack(options) {
501
548
  step.result = "failed";
502
549
  step.detail = error instanceof Error ? error.message : String(error);
503
550
  }
504
- if (step.result !== "ok")
551
+ if (step.result !== "ok" && step.result !== "skipped") {
505
552
  receipt.state = step.result === "stale" ? "stale" : "failed";
553
+ }
506
554
  await writeReceipt(options.home, receipt);
507
555
  }
508
556
  if (receipt.state === "running")
@@ -42,6 +42,7 @@ export interface ServiceProcess {
42
42
  }
43
43
  export type CommandRunner = (command: readonly string[]) => number | PromiseLike<number>;
44
44
  export type ServiceProbe = (label: string) => Promise<ServiceProcess | undefined>;
45
+ export type IsServiceInstalled = (label: string) => Promise<boolean>;
45
46
  export interface RunRestartOptions {
46
47
  readonly home: string;
47
48
  readonly id: string;
@@ -49,6 +50,8 @@ export interface RunRestartOptions {
49
50
  readonly uid?: number;
50
51
  readonly runner?: CommandRunner;
51
52
  readonly probe?: ServiceProbe;
53
+ /** Checks if a service is installed; if not provided, defaults to checking the service manager's files. */
54
+ readonly isServiceInstalled?: IsServiceInstalled;
52
55
  /** Epoch ms modification time of a deployed binary. */
53
56
  readonly binaryModifiedAt?: (path: string) => Promise<number>;
54
57
  readonly now?: () => number;
@@ -56,6 +59,12 @@ export interface RunRestartOptions {
56
59
  /** How long one service may take to come back verified; covers the gateway's ordered shutdown. */
57
60
  readonly verifyTimeoutMs?: number;
58
61
  readonly pollMs?: number;
62
+ /** Environment variables; used to determine systemd unit dir on linux. Defaults to process.env. */
63
+ readonly env?: NodeJS.ProcessEnv;
64
+ /** LaunchAgents directory on darwin. Defaults to ~/Library/LaunchAgents. */
65
+ readonly launchAgentsDir?: string;
66
+ /** systemd unit directory on linux. Defaults to $XDG_CONFIG_HOME/systemd/user. */
67
+ readonly unitDir?: string;
59
68
  }
60
69
  export declare function restartReceiptPath(home: string): string;
61
70
  export declare function readRestartReceipt(home: string): Promise<RestartReceipt | undefined>;
@@ -1 +1 @@
1
- {"version":3,"file":"restart-stack.d.ts","sourceRoot":"","sources":["../src/restart-stack.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"restart-stack.d.ts","sourceRoot":"","sources":["../src/restart-stack.ts"],"names":[],"mappings":"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"}
@@ -52,6 +52,11 @@ export declare function escapeXml(value: string): string;
52
52
  export declare function renderLaunchAgent(spec: ServiceSpec, binDir: string, home: string, path: string): string;
53
53
  export declare function systemdUnitName(spec: ServiceSpec): string;
54
54
  export declare function renderSystemdUnit(spec: ServiceSpec, binDir: string, home: string, path: string): string;
55
+ export declare function systemdUnitDir(env: NodeJS.ProcessEnv, userHome: string): string;
56
+ /** Gets the path where a service's LaunchAgent plist is stored on darwin. */
57
+ export declare function launchAgentPlistPath(spec: ServiceSpec, launchAgentsDir: string): string;
58
+ /** Gets the path where a service's systemd unit file is stored on linux. */
59
+ export declare function systemdUnitPath(spec: ServiceSpec, unitDir: string): string;
55
60
  /**
56
61
  * The ordered commands that realign the whole stack.
57
62
  *
@@ -1 +1 @@
1
- {"version":3,"file":"services.d.ts","sourceRoot":"","sources":["../src/services.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"services.d.ts","sourceRoot":"","sources":["../src/services.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gajae-gateway/cli",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "description": "Owner CLI for the gajaeway gateway: status, sessions, chat, monitors, and worker lanes over the gateway socket. Requires the Bun runtime (uses Bun.file/Bun.spawn).",
6
6
  "license": "AGPL-3.0-only",
@@ -41,8 +41,8 @@
41
41
  "prepublishOnly": "bun run build"
42
42
  },
43
43
  "dependencies": {
44
- "@gajae-gateway/sdk": "0.1.2",
45
- "@gajae-gateway/protocol": "0.1.2"
44
+ "@gajae-gateway/sdk": "0.1.3",
45
+ "@gajae-gateway/protocol": "0.1.3"
46
46
  },
47
47
  "engines": {
48
48
  "bun": ">=1.4.0"
@@ -1,13 +1,17 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { mkdir, readFile, rename, stat, writeFile } from "node:fs/promises";
2
+ import { access, mkdir, readFile, rename, stat, writeFile } from "node:fs/promises";
3
+ import { homedir } from "node:os";
3
4
  import { join } from "node:path";
4
5
  import {
5
6
  currentPlatform,
7
+ launchAgentPlistPath,
6
8
  restartOrder,
7
9
  restartStackCommands,
8
10
  type ServicePlatform,
9
11
  serviceSpecs,
12
+ systemdUnitDir,
10
13
  systemdUnitName,
14
+ systemdUnitPath,
11
15
  } from "./services";
12
16
 
13
17
  /**
@@ -58,6 +62,7 @@ export interface ServiceProcess {
58
62
 
59
63
  export type CommandRunner = (command: readonly string[]) => number | PromiseLike<number>;
60
64
  export type ServiceProbe = (label: string) => Promise<ServiceProcess | undefined>;
65
+ export type IsServiceInstalled = (label: string) => Promise<boolean>;
61
66
 
62
67
  export interface RunRestartOptions {
63
68
  readonly home: string;
@@ -66,6 +71,8 @@ export interface RunRestartOptions {
66
71
  readonly uid?: number;
67
72
  readonly runner?: CommandRunner;
68
73
  readonly probe?: ServiceProbe;
74
+ /** Checks if a service is installed; if not provided, defaults to checking the service manager's files. */
75
+ readonly isServiceInstalled?: IsServiceInstalled;
69
76
  /** Epoch ms modification time of a deployed binary. */
70
77
  readonly binaryModifiedAt?: (path: string) => Promise<number>;
71
78
  readonly now?: () => number;
@@ -73,6 +80,12 @@ export interface RunRestartOptions {
73
80
  /** How long one service may take to come back verified; covers the gateway's ordered shutdown. */
74
81
  readonly verifyTimeoutMs?: number;
75
82
  readonly pollMs?: number;
83
+ /** Environment variables; used to determine systemd unit dir on linux. Defaults to process.env. */
84
+ readonly env?: NodeJS.ProcessEnv;
85
+ /** LaunchAgents directory on darwin. Defaults to ~/Library/LaunchAgents. */
86
+ readonly launchAgentsDir?: string;
87
+ /** systemd unit directory on linux. Defaults to $XDG_CONFIG_HOME/systemd/user. */
88
+ readonly unitDir?: string;
76
89
  }
77
90
 
78
91
  const RECEIPT_FILE = "restart-stack.json";
@@ -166,6 +179,42 @@ async function defaultBinaryModifiedAt(path: string): Promise<number> {
166
179
  return (await stat(path)).mtimeMs;
167
180
  }
168
181
 
182
+ /** Checks if a service is installed by verifying the service manager's definition file exists. */
183
+ function defaultIsServiceInstalled(options: {
184
+ readonly platform: ServicePlatform;
185
+ readonly env: NodeJS.ProcessEnv;
186
+ readonly launchAgentsDir?: string;
187
+ readonly unitDir?: string;
188
+ }): IsServiceInstalled {
189
+ return async (label: string) => {
190
+ const spec = serviceSpecs().find((s) => s.label === label);
191
+ if (spec === undefined) return false;
192
+
193
+ if (options.platform === "darwin") {
194
+ const userHome = options.env.HOME || homedir();
195
+ const agentsDir = options.launchAgentsDir || join(userHome, "Library", "LaunchAgents");
196
+ const plistPath = launchAgentPlistPath(spec, agentsDir);
197
+ try {
198
+ await access(plistPath);
199
+ return true;
200
+ } catch {
201
+ return false;
202
+ }
203
+ }
204
+
205
+ // linux
206
+ const userHome = options.env.HOME || homedir();
207
+ const unitDirPath = options.unitDir || systemdUnitDir(options.env, userHome);
208
+ const unitPath = systemdUnitPath(spec, unitDirPath);
209
+ try {
210
+ await access(unitPath);
211
+ return true;
212
+ } catch {
213
+ return false;
214
+ }
215
+ };
216
+ }
217
+
169
218
  function floorSecond(ms: number): number {
170
219
  return Math.floor(ms / 1_000) * 1_000;
171
220
  }
@@ -180,6 +229,14 @@ export async function runRestartStack(options: RunRestartOptions): Promise<Resta
180
229
  const runner = options.runner ?? defaultCommandRunner;
181
230
  const probe = options.probe ?? defaultServiceProbe(platform);
182
231
  const modifiedAt = options.binaryModifiedAt ?? defaultBinaryModifiedAt;
232
+ const isInstalled =
233
+ options.isServiceInstalled ??
234
+ defaultIsServiceInstalled({
235
+ platform,
236
+ env: options.env ?? process.env,
237
+ launchAgentsDir: options.launchAgentsDir,
238
+ unitDir: options.unitDir,
239
+ });
183
240
  const now = options.now ?? Date.now;
184
241
  const sleep = options.sleep ?? ((ms: number) => Bun.sleep(ms));
185
242
  const timeoutMs = options.verifyTimeoutMs ?? VERIFY_TIMEOUT_MS;
@@ -201,6 +258,15 @@ export async function runRestartStack(options: RunRestartOptions): Promise<Resta
201
258
  continue;
202
259
  }
203
260
  try {
261
+ // Check if the service is installed before trying to restart it
262
+ const installed = await isInstalled(step.label);
263
+ if (!installed) {
264
+ step.result = "skipped";
265
+ step.detail = "service is not installed";
266
+ await writeReceipt(options.home, receipt);
267
+ continue;
268
+ }
269
+
204
270
  if (step.command !== undefined) {
205
271
  issuedAt = now();
206
272
  const status = await runner(step.command);
@@ -215,7 +281,9 @@ export async function runRestartStack(options: RunRestartOptions): Promise<Resta
215
281
  step.result = "failed";
216
282
  step.detail = error instanceof Error ? error.message : String(error);
217
283
  }
218
- if (step.result !== "ok") receipt.state = step.result === "stale" ? "stale" : "failed";
284
+ if (step.result !== "ok" && step.result !== "skipped") {
285
+ receipt.state = step.result === "stale" ? "stale" : "failed";
286
+ }
219
287
  await writeReceipt(options.home, receipt);
220
288
  }
221
289
  if (receipt.state === "running") receipt.state = "ok";
package/src/services.ts CHANGED
@@ -235,11 +235,21 @@ export function renderSystemdUnit(spec: ServiceSpec, binDir: string, home: strin
235
235
  return unit.join("\n");
236
236
  }
237
237
 
238
- function systemdUnitDir(env: NodeJS.ProcessEnv, userHome: string): string {
238
+ export function systemdUnitDir(env: NodeJS.ProcessEnv, userHome: string): string {
239
239
  const base = env.XDG_CONFIG_HOME || join(userHome, ".config");
240
240
  return join(base, "systemd", "user");
241
241
  }
242
242
 
243
+ /** Gets the path where a service's LaunchAgent plist is stored on darwin. */
244
+ export function launchAgentPlistPath(spec: ServiceSpec, launchAgentsDir: string): string {
245
+ return join(launchAgentsDir, `dev.gajaeway.${spec.id}.plist`);
246
+ }
247
+
248
+ /** Gets the path where a service's systemd unit file is stored on linux. */
249
+ export function systemdUnitPath(spec: ServiceSpec, unitDir: string): string {
250
+ return join(unitDir, systemdUnitName(spec));
251
+ }
252
+
243
253
  /**
244
254
  * The ordered commands that realign the whole stack.
245
255
  *