@coopcli/specsketch 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/README.md CHANGED
@@ -51,7 +51,10 @@ specsketch <path-to-openspec-change-dir> [--port <n>]
51
51
  ```
52
52
 
53
53
  - The directory is created if it doesn't exist; its basename becomes the change name.
54
- - `--port` defaults to 8787. Binds to 127.0.0.1 exclusivelythere is no `--host`.
54
+ - `--port` sets the preferred port (default 8787). If it's already in use say another
55
+ specsketch session is running — the server moves to the next available port and the
56
+ printed URL reflects the one actually bound. Binds to 127.0.0.1 exclusively — there is
57
+ no `--host`.
55
58
  - **Model picker** (top bar): `claude-opus-4-8` (default), `claude-sonnet-5`, or
56
59
  `claude-fable-5`. The choice is saved with the change directory. Fable runs with
57
60
  server-side refusal fallbacks to Opus and requires your org to allow 30-day data
@@ -65,7 +68,7 @@ specsketch <path-to-openspec-change-dir> [--port <n>]
65
68
  | Startup warning "No Anthropic credentials detected" | Set `ANTHROPIC_API_KEY` or run `ant auth login`, then restart |
66
69
  | Generate fails with a 401 / "anthropic-auth" | Same as above — the server found no credentials, or the login profile expired (`ant auth status`) |
67
70
  | "Node NN detected — specsketch needs Node >= 22" | Upgrade Node (https://nodejs.org) |
68
- | Port already in use | Pass `--port <n>` |
71
+ | Port already in use | Handled automatically — the server moves to the next free port and prints it; `--port <n>` sets the preferred starting port |
69
72
  | Regeneration changed a file you hand-edited | Expected when the drawing implicates it — the diagram is the source of truth; use `git diff`/`git checkout` to recover, and keep change dirs in version control |
70
73
 
71
74
  More: [getting started page](https://coopcli.com/products/specsketch) ·
package/dist/cli/index.js CHANGED
@@ -374,7 +374,10 @@ var USAGE = `Usage: specsketch <path-to-openspec-change-dir> [--port <n>]
374
374
 
375
375
  The directory is created if it doesn't exist. Diagrams autosave to
376
376
  diagram.excalidraw inside it; Generate Spec writes proposal.md, design.md,
377
- tasks.md and specs/<capability>/spec.md alongside.`;
377
+ tasks.md and specs/<capability>/spec.md alongside.
378
+
379
+ --port sets the preferred port (default 8787); if it's already in use the
380
+ server moves to the next available port and prints the actual URL.`;
378
381
  var UsageError = class extends Error {
379
382
  };
380
383
  function parseCliArgs(argv) {
@@ -396,6 +399,18 @@ function parseCliArgs(argv) {
396
399
  }
397
400
  return { dir, port };
398
401
  }
402
+ var PORT_SCAN_LIMIT = 100;
403
+ async function bindAvailablePort(start, tryBind) {
404
+ const end = Math.min(start + PORT_SCAN_LIMIT - 1, 65535);
405
+ for (let port = start; port <= end; port++) {
406
+ try {
407
+ return { port: await tryBind(port), moved: port !== start };
408
+ } catch (err) {
409
+ if (err.code !== "EADDRINUSE") throw err;
410
+ }
411
+ }
412
+ throw new Error(`no free port between ${start} and ${end} \u2014 free one up or pass --port`);
413
+ }
399
414
  var MIME = {
400
415
  ".html": "text/html; charset=utf-8",
401
416
  ".js": "text/javascript; charset=utf-8",
@@ -457,7 +472,7 @@ function preflight(log = console.log) {
457
472
  process.exit(1);
458
473
  }
459
474
  }
460
- function main(argv) {
475
+ async function main(argv) {
461
476
  preflight();
462
477
  let args;
463
478
  try {
@@ -479,20 +494,42 @@ ${USAGE}` : err);
479
494
  const res = staticResponse(clientDir, new URL(c.req.url).pathname);
480
495
  return res ?? c.notFound();
481
496
  });
482
- serve({ fetch: app.fetch, port: args.port, hostname: "127.0.0.1" }, () => {
483
- const source = detectCredentialSource();
484
- console.log(`coopcli specsketch`);
485
- console.log(` workspace: ${workspace.dir}`);
486
- console.log(` open: http://127.0.0.1:${args.port}`);
487
- if (source) {
488
- console.log(` auth: ${source}`);
489
- } else {
490
- console.log(`
497
+ const tryBind = (port2) => new Promise((bound, failed) => {
498
+ const server = serve(
499
+ { fetch: app.fetch, port: port2, hostname: "127.0.0.1" },
500
+ (info) => bound(info.port)
501
+ );
502
+ server.once("error", (err) => {
503
+ try {
504
+ server.close();
505
+ } catch {
506
+ }
507
+ failed(err);
508
+ });
509
+ });
510
+ let port;
511
+ let moved;
512
+ try {
513
+ ({ port, moved } = await bindAvailablePort(args.port, tryBind));
514
+ } catch (err) {
515
+ console.error(`specsketch: ${err instanceof Error ? err.message : String(err)}`);
516
+ process.exit(1);
517
+ }
518
+ const source = detectCredentialSource();
519
+ console.log(`coopcli specsketch`);
520
+ console.log(` workspace: ${workspace.dir}`);
521
+ console.log(` open: http://127.0.0.1:${port}`);
522
+ if (moved) {
523
+ console.log(` port: ${args.port} was in use \u2014 moved to ${port}`);
524
+ }
525
+ if (source) {
526
+ console.log(` auth: ${source}`);
527
+ } else {
528
+ console.log(`
491
529
  \u26A0 ${CREDENTIALS_HINT}
492
530
  `);
493
- }
494
- });
531
+ }
495
532
  }
496
533
 
497
534
  // server/cli-entry.ts
498
- main(process.argv.slice(2));
535
+ void main(process.argv.slice(2));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coopcli/specsketch",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "coopcli specsketch — draw an architecture diagram, generate an OpenSpec change proposal into your openspec/ change directory. Local-only.",
5
5
  "type": "module",
6
6
  "bin": {