@commandgarden/cli 1.1.4 → 1.2.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.
Files changed (2) hide show
  1. package/dist/main.js +198 -74
  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,68 @@ 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
+ if (!opts.noOpen) openBrowser(`http://127.0.0.1:${appPort}`);
17248
+ return opts.background ? { status: "already-running", message } : message;
17161
17249
  }
17250
+ unlinkSync3(pidPath);
17162
17251
  }
17163
17252
  mkdirSync2(cgHome, { recursive: true });
17164
17253
  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) {
17254
+ const lockPath = join3(cgHome, "app.lock");
17255
+ const lock = acquireLock(lockPath);
17256
+ if (!lock.acquired) {
17257
+ return { status: "locked", message: `Another cg up/gui start is already in progress (PID ${lock.holderPid}).` };
17258
+ }
17259
+ try {
17260
+ console.error("Starting GUI...");
17261
+ const logPath = join3(cgHome, "app.log");
17262
+ const logFd = openSync2(logPath, "a");
17263
+ const child2 = spawn2("node", [appScript], { detached: true, stdio: ["ignore", logFd, logFd] });
17264
+ closeSync2(logFd);
17265
+ let spawnFailed = false;
17266
+ child2.on("error", () => {
17267
+ spawnFailed = true;
17268
+ });
17269
+ if (child2.pid) writeFileSync3(pidPath, String(child2.pid));
17270
+ child2.unref();
17271
+ if (opts.noOpen) {
17272
+ return { status: "started", message: `GUI started (PID: ${child2.pid ?? "unknown"}).`, pid: child2.pid };
17273
+ }
17172
17274
  const appUrl = `http://127.0.0.1:${appPort}`;
17173
- const ready = await waitForServer(appUrl, child2);
17174
- if (ready) {
17275
+ const outcome = await pollUntilReady({
17276
+ checkReady: async () => {
17277
+ try {
17278
+ await fetch(appUrl);
17279
+ return true;
17280
+ } catch {
17281
+ return false;
17282
+ }
17283
+ },
17284
+ isAlive: () => !spawnFailed && (!child2.pid || isProcessAlive(child2.pid)),
17285
+ maxAttempts: 40,
17286
+ intervalMs: 500
17287
+ });
17288
+ if (outcome === "ready") {
17175
17289
  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.`;
17290
+ return { status: "started", message: `GUI started (PID: ${child2.pid ?? "unknown"}).`, pid: child2.pid };
17291
+ }
17292
+ if (child2.pid) try {
17293
+ process.kill(child2.pid, "SIGTERM");
17294
+ } catch {
17183
17295
  }
17296
+ if (existsSync5(pidPath)) unlinkSync3(pidPath);
17297
+ return { status: "failed", message: `GUI failed to start. Check ${logPath} for errors.` };
17298
+ } finally {
17299
+ releaseLock(lockPath);
17184
17300
  }
17185
- return `GUI started (PID: ${child2.pid ?? "unknown"}).`;
17186
17301
  }
17187
17302
  const child = spawn2("node", [appScript], { stdio: "inherit" });
17188
17303
  if (!opts.noOpen) {
@@ -17195,21 +17310,21 @@ async function executeGuiStart(baseUrl, cgHome, appScript, opts) {
17195
17310
  }
17196
17311
  function executeGuiStop(cgHome) {
17197
17312
  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);
17313
+ if (!existsSync5(pidPath)) return "GUI is not running (no PID file found).";
17314
+ const pid = parseInt(readFileSync7(pidPath, "utf-8").trim(), 10);
17200
17315
  try {
17201
17316
  process.kill(pid, "SIGTERM");
17202
- unlinkSync2(pidPath);
17317
+ unlinkSync3(pidPath);
17203
17318
  return `GUI stopped (PID: ${pid}).`;
17204
17319
  } catch {
17205
- unlinkSync2(pidPath);
17320
+ unlinkSync3(pidPath);
17206
17321
  return `GUI process ${pid} not found (stale PID file cleaned up).`;
17207
17322
  }
17208
17323
  }
17209
17324
  function executeGuiStatus(cgHome) {
17210
17325
  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);
17326
+ if (!existsSync5(pidPath)) return "GUI: not running (no PID file found).";
17327
+ const pid = parseInt(readFileSync7(pidPath, "utf-8").trim(), 10);
17213
17328
  try {
17214
17329
  process.kill(pid, 0);
17215
17330
  return `GUI: running (PID: ${pid}).`;
@@ -17234,23 +17349,28 @@ async function waitForServer(url, child) {
17234
17349
  return false;
17235
17350
  }
17236
17351
  function openBrowser(url) {
17352
+ console.error("Opening browser...");
17237
17353
  const child = process.platform === "darwin" ? spawn2("open", [url], { stdio: "ignore" }) : process.platform === "win32" ? spawn2("cmd", ["/c", "start", "", url], { stdio: "ignore" }) : spawn2("xdg-open", [url], { stdio: "ignore" });
17354
+ child.on("error", () => {
17355
+ });
17238
17356
  child.unref();
17239
17357
  }
17240
17358
 
17241
17359
  // src/commands/up-down.ts
17242
- async function executeUp(baseUrl, cgHome, daemonScript, appScript, configPath) {
17360
+ async function executeUp(baseUrl, cgHome, daemonScript, appScript, configPath, opts = {}) {
17243
17361
  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));
17362
+ const daemonResult = await executeDaemonStart(baseUrl, cgHome, daemonScript);
17363
+ lines.push(daemonResult.message);
17364
+ const canProceed = daemonResult.status === "started" || daemonResult.status === "already-running";
17365
+ if (!canProceed) {
17366
+ return lines.join("\n");
17252
17367
  }
17253
- lines.push(await executeGuiStart(baseUrl, cgHome, appScript, { background: true, configPath }));
17368
+ const guiResult = await executeGuiStart(baseUrl, cgHome, appScript, {
17369
+ background: true,
17370
+ configPath,
17371
+ noOpen: opts.noOpen
17372
+ });
17373
+ lines.push(typeof guiResult === "string" ? guiResult : guiResult.message);
17254
17374
  return lines.join("\n");
17255
17375
  }
17256
17376
  async function executeDown(cgHome) {
@@ -17266,7 +17386,7 @@ var __filename = fileURLToPath(import.meta.url);
17266
17386
  var __dirname = dirname2(__filename);
17267
17387
  var CLI_VERSION = (() => {
17268
17388
  try {
17269
- const pkg = JSON.parse(readFileSync7(resolve(__dirname, "..", "package.json"), "utf-8"));
17389
+ const pkg = JSON.parse(readFileSync8(resolve(__dirname, "..", "package.json"), "utf-8"));
17270
17390
  return pkg.version ?? "0.0.0";
17271
17391
  } catch {
17272
17392
  return "0.0.0";
@@ -17316,7 +17436,8 @@ daemon.command("status").description("Check daemon status").action(async () => {
17316
17436
  console.log(await executeDaemonStatus(BASE_URL));
17317
17437
  });
17318
17438
  daemon.command("start").description("Start the daemon in background").action(async () => {
17319
- console.log(await executeDaemonStart(BASE_URL, CG_HOME, getDaemonScript()));
17439
+ const result = await executeDaemonStart(BASE_URL, CG_HOME, getDaemonScript());
17440
+ console.log(result.message);
17320
17441
  });
17321
17442
  daemon.command("stop").description("Stop the daemon").action(async () => {
17322
17443
  console.log(await executeDaemonStop(CG_HOME));
@@ -17353,11 +17474,12 @@ config.command("set <key> <value>").description("Set a configuration value (e.g.
17353
17474
  });
17354
17475
  var gui = program2.command("gui").description("Manage the GUI app server");
17355
17476
  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(), {
17477
+ const result = await executeGuiStart(BASE_URL, CG_HOME, getAppScript(), {
17357
17478
  background: opts.background,
17358
17479
  noOpen: opts.open === false,
17359
17480
  configPath: CONFIG_PATH
17360
- }));
17481
+ });
17482
+ console.log(typeof result === "string" ? result : result.message);
17361
17483
  });
17362
17484
  gui.command("stop").description("Stop the GUI").action(() => {
17363
17485
  console.log(executeGuiStop(CG_HOME));
@@ -17365,8 +17487,10 @@ gui.command("stop").description("Stop the GUI").action(() => {
17365
17487
  gui.command("status").description("Check GUI status").action(() => {
17366
17488
  console.log(executeGuiStatus(CG_HOME));
17367
17489
  });
17368
- program2.command("up").description("Start daemon + GUI, open browser").action(async () => {
17369
- console.log(await executeUp(BASE_URL, CG_HOME, getDaemonScript(), getAppScript(), CONFIG_PATH));
17490
+ program2.command("up").description("Start daemon + GUI, open browser (use --no-open to skip)").option("--no-open", "Do not open browser").action(async (opts) => {
17491
+ console.log(await executeUp(BASE_URL, CG_HOME, getDaemonScript(), getAppScript(), CONFIG_PATH, {
17492
+ noOpen: opts.open === false
17493
+ }));
17370
17494
  });
17371
17495
  program2.command("down").description("Stop GUI + daemon").action(async () => {
17372
17496
  console.log(await executeDown(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.2.0",
4
4
  "description": "Enterprise browser automation CLI",
5
5
  "type": "module",
6
6
  "license": "MIT",