forge-cw 0.1.1 → 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/cw/.bundle.json CHANGED
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "repo": "https://github.com/avarajar/cw.git",
3
- "ref": "08e93dc31ab2c1a31ea7cbf6d77f82ed2878a397"
3
+ "ref": "2411230d86ea745e5e08ac64993e35f8f107e69d"
4
4
  }
@@ -24,7 +24,7 @@ claude_plugin() {
24
24
  _claude_base_env
25
25
  case "$op" in
26
26
  list) HARNESS_ARGV=(claude plugin list) ;;
27
- add) HARNESS_ARGV=(claude plugin add "$plugin") ;;
27
+ add) HARNESS_ARGV=(claude plugin install "$plugin") ;;
28
28
  *) return 1 ;;
29
29
  esac
30
30
  return 0
package/dist/index.js CHANGED
@@ -4332,6 +4332,52 @@ var init_cw_install = __esm({
4332
4332
  }
4333
4333
  });
4334
4334
 
4335
+ // ../core/dist/port-in-use.js
4336
+ function waitForListening(server) {
4337
+ if (server.listening)
4338
+ return Promise.resolve();
4339
+ return new Promise((resolve5, reject) => {
4340
+ const onError = (err) => {
4341
+ server.off("listening", onListening);
4342
+ reject(err);
4343
+ };
4344
+ const onListening = () => {
4345
+ server.off("error", onError);
4346
+ resolve5();
4347
+ };
4348
+ server.once("error", onError);
4349
+ server.once("listening", onListening);
4350
+ });
4351
+ }
4352
+ async function probePort(port, timeoutMs = 1500) {
4353
+ try {
4354
+ const res = await fetch(`http://127.0.0.1:${port}/api/health`, { signal: AbortSignal.timeout(timeoutMs) });
4355
+ const body = await res.json().catch(() => null);
4356
+ return body?.status === "ok" && typeof body.modules === "number" ? "forge" : "other";
4357
+ } catch (err) {
4358
+ return err.name === "TimeoutError" ? "unresponsive" : "other";
4359
+ }
4360
+ }
4361
+ function portInUseMessage(port, holder) {
4362
+ const next = `Start on another port with --port ${port + 1} (or FORGE_PORT=${port + 1}).`;
4363
+ const find = `Find it with: lsof -nP -iTCP:${port} -sTCP:LISTEN`;
4364
+ if (holder === "forge")
4365
+ return `Forge is already running at http://localhost:${port}.`;
4366
+ if (holder === "unresponsive") {
4367
+ return [
4368
+ `Port ${port} is taken by a process that does not answer, often a Forge suspended with Ctrl+Z.`,
4369
+ `${find}, then bring it back with \`fg\` in its terminal or stop it with \`kill <pid>\`.`,
4370
+ next
4371
+ ].join("\n ");
4372
+ }
4373
+ return [`Port ${port} is in use by another program.`, find, next].join("\n ");
4374
+ }
4375
+ var init_port_in_use = __esm({
4376
+ "../core/dist/port-in-use.js"() {
4377
+ "use strict";
4378
+ }
4379
+ });
4380
+
4335
4381
  // ../core/dist/index.js
4336
4382
  var dist_exports = {};
4337
4383
  __export(dist_exports, {
@@ -4357,10 +4403,13 @@ __export(dist_exports, {
4357
4403
  getForgeDir: () => getForgeDir,
4358
4404
  liveframeRoutes: () => liveframeRoutes,
4359
4405
  pathWithCw: () => pathWithCw,
4406
+ portInUseMessage: () => portInUseMessage,
4407
+ probePort: () => probePort,
4360
4408
  quietSqliteWarning: () => quietSqliteWarning,
4361
4409
  resolveListenOptions: () => resolveListenOptions,
4362
4410
  skillRoutes: () => skillRoutes,
4363
- supports: () => supports
4411
+ supports: () => supports,
4412
+ waitForListening: () => waitForListening
4364
4413
  });
4365
4414
  var init_dist = __esm({
4366
4415
  "../core/dist/index.js"() {
@@ -4383,6 +4432,7 @@ var init_dist = __esm({
4383
4432
  init_pty_routes();
4384
4433
  init_liveframe_routes();
4385
4434
  init_liveframe();
4435
+ init_port_in_use();
4386
4436
  }
4387
4437
  });
4388
4438
 
@@ -4404,7 +4454,7 @@ var packageRoot = join15(import.meta.dirname, "..");
4404
4454
  var isCheckout = existsSync14(join15(packageRoot, "src"));
4405
4455
  var consoleDist = isCheckout ? join15(packageRoot, "../console/dist") : join15(packageRoot, "console");
4406
4456
  async function main() {
4407
- const { ensureForgeDir: ensureForgeDir2, createForgeServer: createForgeServer2, createDatabase: createDatabase2, resolveListenOptions: resolveListenOptions2, ensureCw: ensureCw2, pathWithCw: pathWithCw2, quietSqliteWarning: quietSqliteWarning2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
4457
+ const { ensureForgeDir: ensureForgeDir2, createForgeServer: createForgeServer2, createDatabase: createDatabase2, resolveListenOptions: resolveListenOptions2, ensureCw: ensureCw2, pathWithCw: pathWithCw2, quietSqliteWarning: quietSqliteWarning2, waitForListening: waitForListening2, probePort: probePort2, portInUseMessage: portInUseMessage2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
4408
4458
  quietSqliteWarning2();
4409
4459
  const args = parseArgs(process.argv.slice(2));
4410
4460
  const { forgeDir, created } = ensureForgeDir2();
@@ -4443,6 +4493,23 @@ async function main() {
4443
4493
  }
4444
4494
  const { serve } = await import("@hono/node-server");
4445
4495
  const httpServer = serve({ fetch: server.app.fetch, port, hostname: host });
4496
+ try {
4497
+ await waitForListening2(httpServer);
4498
+ } catch (err) {
4499
+ if (err.code !== "EADDRINUSE") throw err;
4500
+ const holder = await probePort2(port);
4501
+ console.log(`
4502
+ ${portInUseMessage2(port, holder)}
4503
+ `);
4504
+ if (holder === "forge" && args.open && process.env.FORGE_NO_OPEN !== "1") {
4505
+ try {
4506
+ const open = (await import("open")).default;
4507
+ await open(`http://localhost:${port}`);
4508
+ } catch {
4509
+ }
4510
+ }
4511
+ process.exit(holder === "forge" ? 0 : 1);
4512
+ }
4446
4513
  server.attachTerminalWs(httpServer);
4447
4514
  console.log(`
4448
4515
  Forge Console running at http://localhost:${port}
@@ -4461,4 +4528,7 @@ ${authToken ? " Auth: bearer token required\n" : ""}
4461
4528
  }
4462
4529
  }
4463
4530
  }
4464
- main().catch(console.error);
4531
+ main().catch((err) => {
4532
+ console.error(err);
4533
+ process.exit(1);
4534
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forge-cw",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "A visual dashboard for your coding agents on Claude Code, Codex, Pi or OpenCode. Installs CW for you.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -39,8 +39,8 @@
39
39
  "@types/node": "^22.0.0",
40
40
  "esbuild": "^0.25.0",
41
41
  "typescript": "^5.8.0",
42
- "@forge-dev/console": "0.1.0",
43
- "@forge-dev/core": "0.1.0"
42
+ "@forge-dev/core": "0.1.0",
43
+ "@forge-dev/console": "0.1.0"
44
44
  },
45
45
  "scripts": {
46
46
  "build": "tsc",