@commandgarden/cli 1.1.4 → 1.1.5

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 (2) hide show
  1. package/dist/main.js +185 -71
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -12202,7 +12202,7 @@ var {
12202
12202
 
12203
12203
  // src/main.ts
12204
12204
  import { join as join4, dirname as dirname2, resolve } from "path";
12205
- import { readFileSync as readFileSync7 } from "fs";
12205
+ import { readFileSync as readFileSync8 } from "fs";
12206
12206
  import { homedir } from "os";
12207
12207
  import { fileURLToPath } from "url";
12208
12208
 
@@ -12640,7 +12640,7 @@ function stepSummary(step) {
12640
12640
  // src/commands/validate.ts
12641
12641
  import { readFileSync as readFileSync3 } from "fs";
12642
12642
 
12643
- // ../shared/dist/capabilities.js
12643
+ // node_modules/@commandgarden/shared/dist/capabilities.js
12644
12644
  var CAPABILITIES = [
12645
12645
  "navigate",
12646
12646
  "cookie_read",
@@ -16702,7 +16702,7 @@ var coerce = {
16702
16702
  };
16703
16703
  var NEVER = INVALID;
16704
16704
 
16705
- // ../shared/dist/pipeline.js
16705
+ // node_modules/@commandgarden/shared/dist/pipeline.js
16706
16706
  var STEP_CAPABILITY_MAP = {
16707
16707
  navigate: "navigate",
16708
16708
  wait: "navigate",
@@ -16795,7 +16795,7 @@ var pipelineStepSchema = external_exports.discriminatedUnion("step", [
16795
16795
  jsEvaluateStepSchema
16796
16796
  ]);
16797
16797
 
16798
- // ../shared/dist/connector.js
16798
+ // node_modules/@commandgarden/shared/dist/connector.js
16799
16799
  var capabilityEnum = external_exports.enum(CAPABILITIES);
16800
16800
  var connectorArgSchema = external_exports.object({
16801
16801
  name: external_exports.string(),
@@ -16822,7 +16822,7 @@ var connectorSchema = external_exports.object({
16822
16822
  pipeline: external_exports.array(pipelineStepSchema).min(1)
16823
16823
  });
16824
16824
 
16825
- // ../shared/dist/loader.js
16825
+ // node_modules/@commandgarden/shared/dist/loader.js
16826
16826
  var import_yaml = __toESM(require_dist(), 1);
16827
16827
  function parseConnectorYaml(yamlContent) {
16828
16828
  let parsed;
@@ -16893,7 +16893,7 @@ function validateConnectorSemantics(connector) {
16893
16893
  return errors;
16894
16894
  }
16895
16895
 
16896
- // ../shared/dist/events.js
16896
+ // node_modules/@commandgarden/shared/dist/events.js
16897
16897
  import { randomUUID } from "crypto";
16898
16898
 
16899
16899
  // src/commands/validate.ts
@@ -16932,8 +16932,47 @@ function executeValidate(filePath) {
16932
16932
 
16933
16933
  // src/commands/daemon-cmd.ts
16934
16934
  import { spawn } from "child_process";
16935
- import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync2, unlinkSync, mkdirSync } from "fs";
16935
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, existsSync as existsSync3, unlinkSync as unlinkSync2, mkdirSync, openSync, closeSync } from "fs";
16936
16936
  import { join as join2 } from "path";
16937
+
16938
+ // src/poll.ts
16939
+ async function pollUntilReady(opts) {
16940
+ for (let i = 0; i < opts.maxAttempts; i++) {
16941
+ if (!opts.isAlive()) return "died";
16942
+ if (await opts.checkReady()) return "ready";
16943
+ await new Promise((r) => setTimeout(r, opts.intervalMs));
16944
+ }
16945
+ return "timeout";
16946
+ }
16947
+
16948
+ // src/process-alive.ts
16949
+ function isProcessAlive(pid) {
16950
+ try {
16951
+ process.kill(pid, 0);
16952
+ return true;
16953
+ } catch {
16954
+ return false;
16955
+ }
16956
+ }
16957
+
16958
+ // src/lockfile.ts
16959
+ import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync2, unlinkSync } from "fs";
16960
+ function acquireLock(lockPath) {
16961
+ if (existsSync2(lockPath)) {
16962
+ const holderPid = parseInt(readFileSync4(lockPath, "utf-8").trim(), 10);
16963
+ if (isProcessAlive(holderPid)) return { acquired: false, holderPid };
16964
+ }
16965
+ writeFileSync(lockPath, String(process.pid));
16966
+ return { acquired: true };
16967
+ }
16968
+ function releaseLock(lockPath) {
16969
+ try {
16970
+ unlinkSync(lockPath);
16971
+ } catch {
16972
+ }
16973
+ }
16974
+
16975
+ // src/commands/daemon-cmd.ts
16937
16976
  async function executeDaemonStatus(baseUrl) {
16938
16977
  try {
16939
16978
  const resp = await fetch(`${baseUrl}/api/status`);
@@ -16950,33 +16989,82 @@ async function executeDaemonStatus(baseUrl) {
16950
16989
  async function executeDaemonStart(baseUrl, cgHome, daemonScript) {
16951
16990
  try {
16952
16991
  const resp = await fetch(`${baseUrl}/api/status`);
16953
- if (resp.ok) return "Daemon is already running.";
16992
+ if (resp.ok) return { status: "already-running", message: "Daemon is already running." };
16954
16993
  } catch {
16955
16994
  }
16956
- const pidPath = join2(cgHome, "daemon.pid");
16957
16995
  mkdirSync(cgHome, { recursive: true });
16958
- const child = spawn("node", [daemonScript], {
16959
- detached: true,
16960
- stdio: "ignore"
16961
- });
16962
- if (child.pid) {
16963
- writeFileSync(pidPath, String(child.pid));
16996
+ const lockPath = join2(cgHome, "daemon.lock");
16997
+ const lock = acquireLock(lockPath);
16998
+ if (!lock.acquired) {
16999
+ return {
17000
+ status: "locked",
17001
+ message: `Another cg up/daemon start is already in progress (PID ${lock.holderPid}).`
17002
+ };
17003
+ }
17004
+ try {
17005
+ const pidPath = join2(cgHome, "daemon.pid");
17006
+ const logPath = join2(cgHome, "daemon.log");
17007
+ console.error("Starting daemon...");
17008
+ const logFd = openSync(logPath, "a");
17009
+ const child = spawn("node", [daemonScript], { detached: true, stdio: ["ignore", logFd, logFd] });
17010
+ closeSync(logFd);
17011
+ let spawnFailed = false;
17012
+ child.on("error", () => {
17013
+ spawnFailed = true;
17014
+ });
17015
+ if (child.pid) writeFileSync2(pidPath, String(child.pid));
17016
+ child.unref();
17017
+ const outcome = await pollUntilReady({
17018
+ checkReady: async () => {
17019
+ try {
17020
+ const resp = await fetch(`${baseUrl}/api/status`);
17021
+ return resp.ok;
17022
+ } catch {
17023
+ return false;
17024
+ }
17025
+ },
17026
+ isAlive: () => !spawnFailed && (!child.pid || isProcessAlive(child.pid)),
17027
+ maxAttempts: 20,
17028
+ intervalMs: 500
17029
+ });
17030
+ if (outcome === "ready") {
17031
+ return { status: "started", message: `Daemon started (PID: ${child.pid ?? "unknown"}).`, pid: child.pid };
17032
+ }
17033
+ if (outcome === "died") {
17034
+ if (existsSync3(pidPath)) unlinkSync2(pidPath);
17035
+ const tail = readLogTail(logPath, 10);
17036
+ return { status: "failed", message: `Daemon failed to start.
17037
+ ${tail}` };
17038
+ }
17039
+ return {
17040
+ status: "unresponsive",
17041
+ message: `Daemon started (PID: ${child.pid ?? "unknown"}) but is not responding. Check ${logPath} for details.`,
17042
+ pid: child.pid
17043
+ };
17044
+ } finally {
17045
+ releaseLock(lockPath);
17046
+ }
17047
+ }
17048
+ function readLogTail(logPath, lines) {
17049
+ try {
17050
+ const content = readFileSync5(logPath, "utf-8");
17051
+ return content.split("\n").slice(-lines).join("\n").trim();
17052
+ } catch {
17053
+ return "(no log available)";
16964
17054
  }
16965
- child.unref();
16966
- return `Daemon started (PID: ${child.pid ?? "unknown"}).`;
16967
17055
  }
16968
17056
  async function executeDaemonStop(cgHome) {
16969
17057
  const pidPath = join2(cgHome, "daemon.pid");
16970
- if (!existsSync2(pidPath)) {
17058
+ if (!existsSync3(pidPath)) {
16971
17059
  return "Daemon is not running (no PID file found).";
16972
17060
  }
16973
- const pid = parseInt(readFileSync4(pidPath, "utf-8").trim(), 10);
17061
+ const pid = parseInt(readFileSync5(pidPath, "utf-8").trim(), 10);
16974
17062
  try {
16975
17063
  process.kill(pid, "SIGTERM");
16976
- unlinkSync(pidPath);
17064
+ unlinkSync2(pidPath);
16977
17065
  return `Daemon stopped (PID: ${pid}).`;
16978
17066
  } catch {
16979
- unlinkSync(pidPath);
17067
+ unlinkSync2(pidPath);
16980
17068
  return `Daemon process ${pid} not found (stale PID file cleaned up).`;
16981
17069
  }
16982
17070
  }
@@ -17106,16 +17194,16 @@ async function executeAuditShow(client, id) {
17106
17194
  }
17107
17195
 
17108
17196
  // src/commands/config-cmd.ts
17109
- import { readFileSync as readFileSync5, existsSync as existsSync3 } from "fs";
17197
+ import { readFileSync as readFileSync6, existsSync as existsSync4 } from "fs";
17110
17198
  function executeConfigShow(configPath) {
17111
- if (!existsSync3(configPath)) {
17199
+ if (!existsSync4(configPath)) {
17112
17200
  return [
17113
17201
  "No config file found.",
17114
17202
  `Expected at: ${configPath}`,
17115
17203
  'Using defaults. Run "commandgarden config set <key> <value>" to create one.'
17116
17204
  ].join("\n");
17117
17205
  }
17118
- return readFileSync5(configPath, "utf-8");
17206
+ return readFileSync6(configPath, "utf-8");
17119
17207
  }
17120
17208
  async function executeConfigSet(client, key, value) {
17121
17209
  try {
@@ -17129,12 +17217,12 @@ async function executeConfigSet(client, key, value) {
17129
17217
  // src/commands/gui-cmd.ts
17130
17218
  var import_yaml2 = __toESM(require_dist(), 1);
17131
17219
  import { spawn as spawn2 } from "child_process";
17132
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync2, existsSync as existsSync4, unlinkSync as unlinkSync2, mkdirSync as mkdirSync2, openSync, closeSync } from "fs";
17220
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync3, existsSync as existsSync5, unlinkSync as unlinkSync3, mkdirSync as mkdirSync2, openSync as openSync2, closeSync as closeSync2 } from "fs";
17133
17221
  import { join as join3 } from "path";
17134
17222
  function readAppPort(configPath) {
17135
17223
  try {
17136
- if (existsSync4(configPath)) {
17137
- const config2 = (0, import_yaml2.parse)(readFileSync6(configPath, "utf-8"));
17224
+ if (existsSync5(configPath)) {
17225
+ const config2 = (0, import_yaml2.parse)(readFileSync7(configPath, "utf-8"));
17138
17226
  const port = config2?.app?.port;
17139
17227
  if (typeof port === "number") return port;
17140
17228
  }
@@ -17148,41 +17236,67 @@ async function executeGuiStart(baseUrl, cgHome, appScript, opts) {
17148
17236
  const resp = await fetch(`${baseUrl}/api/status`);
17149
17237
  if (!resp.ok) throw new Error();
17150
17238
  } catch {
17151
- return "Daemon is not running. Start it with: cg daemon start (or use cg up)";
17239
+ const message = "Daemon is not running. Start it with: cg daemon start (or use cg up)";
17240
+ return opts.background ? { status: "failed", message } : message;
17152
17241
  }
17153
17242
  const pidPath = join3(cgHome, "app.pid");
17154
- if (existsSync4(pidPath)) {
17155
- const pid = parseInt(readFileSync6(pidPath, "utf-8").trim(), 10);
17156
- try {
17157
- process.kill(pid, 0);
17158
- return "GUI is already running.";
17159
- } catch {
17160
- unlinkSync2(pidPath);
17243
+ if (existsSync5(pidPath)) {
17244
+ const pid = parseInt(readFileSync7(pidPath, "utf-8").trim(), 10);
17245
+ if (isProcessAlive(pid)) {
17246
+ const message = "GUI is already running.";
17247
+ return opts.background ? { status: "already-running", message } : message;
17161
17248
  }
17249
+ unlinkSync3(pidPath);
17162
17250
  }
17163
17251
  mkdirSync2(cgHome, { recursive: true });
17164
17252
  if (opts.background) {
17165
- const logPath = join3(cgHome, "app.log");
17166
- const logFd = openSync(logPath, "a");
17167
- const child2 = spawn2("node", [appScript], { detached: true, stdio: ["ignore", logFd, logFd] });
17168
- closeSync(logFd);
17169
- if (child2.pid) writeFileSync2(pidPath, String(child2.pid));
17170
- child2.unref();
17171
- if (!opts.noOpen) {
17253
+ const lockPath = join3(cgHome, "app.lock");
17254
+ const lock = acquireLock(lockPath);
17255
+ if (!lock.acquired) {
17256
+ return { status: "locked", message: `Another cg up/gui start is already in progress (PID ${lock.holderPid}).` };
17257
+ }
17258
+ try {
17259
+ console.error("Starting GUI...");
17260
+ const logPath = join3(cgHome, "app.log");
17261
+ const logFd = openSync2(logPath, "a");
17262
+ const child2 = spawn2("node", [appScript], { detached: true, stdio: ["ignore", logFd, logFd] });
17263
+ closeSync2(logFd);
17264
+ let spawnFailed = false;
17265
+ child2.on("error", () => {
17266
+ spawnFailed = true;
17267
+ });
17268
+ if (child2.pid) writeFileSync3(pidPath, String(child2.pid));
17269
+ child2.unref();
17270
+ if (opts.noOpen) {
17271
+ return { status: "started", message: `GUI started (PID: ${child2.pid ?? "unknown"}).`, pid: child2.pid };
17272
+ }
17172
17273
  const appUrl = `http://127.0.0.1:${appPort}`;
17173
- const ready = await waitForServer(appUrl, child2);
17174
- if (ready) {
17274
+ const outcome = await pollUntilReady({
17275
+ checkReady: async () => {
17276
+ try {
17277
+ await fetch(appUrl);
17278
+ return true;
17279
+ } catch {
17280
+ return false;
17281
+ }
17282
+ },
17283
+ isAlive: () => !spawnFailed && (!child2.pid || isProcessAlive(child2.pid)),
17284
+ maxAttempts: 40,
17285
+ intervalMs: 500
17286
+ });
17287
+ if (outcome === "ready") {
17175
17288
  openBrowser(appUrl);
17176
- } else {
17177
- if (child2.pid) try {
17178
- process.kill(child2.pid, "SIGTERM");
17179
- } catch {
17180
- }
17181
- if (existsSync4(pidPath)) unlinkSync2(pidPath);
17182
- return `GUI failed to start. Check ${logPath} for errors.`;
17289
+ return { status: "started", message: `GUI started (PID: ${child2.pid ?? "unknown"}).`, pid: child2.pid };
17290
+ }
17291
+ if (child2.pid) try {
17292
+ process.kill(child2.pid, "SIGTERM");
17293
+ } catch {
17183
17294
  }
17295
+ if (existsSync5(pidPath)) unlinkSync3(pidPath);
17296
+ return { status: "failed", message: `GUI failed to start. Check ${logPath} for errors.` };
17297
+ } finally {
17298
+ releaseLock(lockPath);
17184
17299
  }
17185
- return `GUI started (PID: ${child2.pid ?? "unknown"}).`;
17186
17300
  }
17187
17301
  const child = spawn2("node", [appScript], { stdio: "inherit" });
17188
17302
  if (!opts.noOpen) {
@@ -17195,21 +17309,21 @@ async function executeGuiStart(baseUrl, cgHome, appScript, opts) {
17195
17309
  }
17196
17310
  function executeGuiStop(cgHome) {
17197
17311
  const pidPath = join3(cgHome, "app.pid");
17198
- if (!existsSync4(pidPath)) return "GUI is not running (no PID file found).";
17199
- const pid = parseInt(readFileSync6(pidPath, "utf-8").trim(), 10);
17312
+ if (!existsSync5(pidPath)) return "GUI is not running (no PID file found).";
17313
+ const pid = parseInt(readFileSync7(pidPath, "utf-8").trim(), 10);
17200
17314
  try {
17201
17315
  process.kill(pid, "SIGTERM");
17202
- unlinkSync2(pidPath);
17316
+ unlinkSync3(pidPath);
17203
17317
  return `GUI stopped (PID: ${pid}).`;
17204
17318
  } catch {
17205
- unlinkSync2(pidPath);
17319
+ unlinkSync3(pidPath);
17206
17320
  return `GUI process ${pid} not found (stale PID file cleaned up).`;
17207
17321
  }
17208
17322
  }
17209
17323
  function executeGuiStatus(cgHome) {
17210
17324
  const pidPath = join3(cgHome, "app.pid");
17211
- if (!existsSync4(pidPath)) return "GUI: not running (no PID file found).";
17212
- const pid = parseInt(readFileSync6(pidPath, "utf-8").trim(), 10);
17325
+ if (!existsSync5(pidPath)) return "GUI: not running (no PID file found).";
17326
+ const pid = parseInt(readFileSync7(pidPath, "utf-8").trim(), 10);
17213
17327
  try {
17214
17328
  process.kill(pid, 0);
17215
17329
  return `GUI: running (PID: ${pid}).`;
@@ -17241,16 +17355,14 @@ function openBrowser(url) {
17241
17355
  // src/commands/up-down.ts
17242
17356
  async function executeUp(baseUrl, cgHome, daemonScript, appScript, configPath) {
17243
17357
  const lines = [];
17244
- lines.push(await executeDaemonStart(baseUrl, cgHome, daemonScript));
17245
- for (let i = 0; i < 10; i++) {
17246
- try {
17247
- const resp = await fetch(`${baseUrl}/api/status`);
17248
- if (resp.ok) break;
17249
- } catch {
17250
- }
17251
- await new Promise((r) => setTimeout(r, 500));
17358
+ const daemonResult = await executeDaemonStart(baseUrl, cgHome, daemonScript);
17359
+ lines.push(daemonResult.message);
17360
+ const canProceed = daemonResult.status === "started" || daemonResult.status === "already-running";
17361
+ if (!canProceed) {
17362
+ return lines.join("\n");
17252
17363
  }
17253
- lines.push(await executeGuiStart(baseUrl, cgHome, appScript, { background: true, configPath }));
17364
+ const guiResult = await executeGuiStart(baseUrl, cgHome, appScript, { background: true, configPath });
17365
+ lines.push(typeof guiResult === "string" ? guiResult : guiResult.message);
17254
17366
  return lines.join("\n");
17255
17367
  }
17256
17368
  async function executeDown(cgHome) {
@@ -17266,7 +17378,7 @@ var __filename = fileURLToPath(import.meta.url);
17266
17378
  var __dirname = dirname2(__filename);
17267
17379
  var CLI_VERSION = (() => {
17268
17380
  try {
17269
- const pkg = JSON.parse(readFileSync7(resolve(__dirname, "..", "package.json"), "utf-8"));
17381
+ const pkg = JSON.parse(readFileSync8(resolve(__dirname, "..", "package.json"), "utf-8"));
17270
17382
  return pkg.version ?? "0.0.0";
17271
17383
  } catch {
17272
17384
  return "0.0.0";
@@ -17316,7 +17428,8 @@ daemon.command("status").description("Check daemon status").action(async () => {
17316
17428
  console.log(await executeDaemonStatus(BASE_URL));
17317
17429
  });
17318
17430
  daemon.command("start").description("Start the daemon in background").action(async () => {
17319
- console.log(await executeDaemonStart(BASE_URL, CG_HOME, getDaemonScript()));
17431
+ const result = await executeDaemonStart(BASE_URL, CG_HOME, getDaemonScript());
17432
+ console.log(result.message);
17320
17433
  });
17321
17434
  daemon.command("stop").description("Stop the daemon").action(async () => {
17322
17435
  console.log(await executeDaemonStop(CG_HOME));
@@ -17353,11 +17466,12 @@ config.command("set <key> <value>").description("Set a configuration value (e.g.
17353
17466
  });
17354
17467
  var gui = program2.command("gui").description("Manage the GUI app server");
17355
17468
  gui.command("start", { isDefault: true }).description("Start the GUI (foreground by default)").option("-b, --background", "Run in background").option("--no-open", "Do not open browser").action(async (opts) => {
17356
- console.log(await executeGuiStart(BASE_URL, CG_HOME, getAppScript(), {
17469
+ const result = await executeGuiStart(BASE_URL, CG_HOME, getAppScript(), {
17357
17470
  background: opts.background,
17358
17471
  noOpen: opts.open === false,
17359
17472
  configPath: CONFIG_PATH
17360
- }));
17473
+ });
17474
+ console.log(typeof result === "string" ? result : result.message);
17361
17475
  });
17362
17476
  gui.command("stop").description("Stop the GUI").action(() => {
17363
17477
  console.log(executeGuiStop(CG_HOME));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@commandgarden/cli",
3
- "version": "1.1.4",
3
+ "version": "1.1.5",
4
4
  "description": "Enterprise browser automation CLI",
5
5
  "type": "module",
6
6
  "license": "MIT",