@edspencer/paddock 0.57.0 → 0.58.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 (64) hide show
  1. package/package.json +1 -1
  2. package/packages/server/dist/cli/import-chats.js +6 -1
  3. package/packages/server/dist/cli/paddock.d.ts +29 -1
  4. package/packages/server/dist/cli/paddock.js +136 -44
  5. package/packages/server/package.json +1 -1
  6. package/packages/web/dist/assets/{InstanceConfigPage-Bypd_quL.js → InstanceConfigPage-BKE6j_Wy.js} +1 -1
  7. package/packages/web/dist/assets/{MarkdownRenderer-Co8w9iuX.js → MarkdownRenderer-B7_FErIN.js} +1 -1
  8. package/packages/web/dist/assets/{ProjectRedirect-DXrA24Cy.js → ProjectRedirect-DOaB53YM.js} +1 -1
  9. package/packages/web/dist/assets/{ProjectView-B9opP7Sv.js → ProjectView-Dz_QUyPG.js} +3 -3
  10. package/packages/web/dist/assets/{ProjectsGrid-D2cwb2jY.js → ProjectsGrid-CjfDQ37q.js} +1 -1
  11. package/packages/web/dist/assets/{arc-B--FIRB8.js → arc-BxAGgBjd.js} +1 -1
  12. package/packages/web/dist/assets/{architectureDiagram-3BPJPVTR-B6dRWZZ6.js → architectureDiagram-3BPJPVTR-CBz2e-c3.js} +1 -1
  13. package/packages/web/dist/assets/{blockDiagram-GPEHLZMM-DLxFAvvf.js → blockDiagram-GPEHLZMM-ZqClBxB7.js} +1 -1
  14. package/packages/web/dist/assets/{c4Diagram-AAUBKEIU-CNee5ks4.js → c4Diagram-AAUBKEIU-CvFOBN1E.js} +1 -1
  15. package/packages/web/dist/assets/channel-BHzPWerJ.js +2 -0
  16. package/packages/web/dist/assets/{chunk-2J33WTMH-DObw03Xl.js → chunk-2J33WTMH-C44u7-Xg.js} +1 -1
  17. package/packages/web/dist/assets/{chunk-4BX2VUAB-CH2SR6nN.js → chunk-4BX2VUAB-DXG9UM3Z.js} +1 -1
  18. package/packages/web/dist/assets/{chunk-55IACEB6-WlVpegT3.js → chunk-55IACEB6-BLkJFttb.js} +1 -1
  19. package/packages/web/dist/assets/{chunk-727SXJPM-g8GT9Rgd.js → chunk-727SXJPM-DZidMKHZ.js} +1 -1
  20. package/packages/web/dist/assets/{chunk-AQP2D5EJ-DbN4PsDF.js → chunk-AQP2D5EJ-DVowfDYg.js} +1 -1
  21. package/packages/web/dist/assets/{chunk-FMBD7UC4-DSN3BjFG.js → chunk-FMBD7UC4-zeHzmwLI.js} +1 -1
  22. package/packages/web/dist/assets/{chunk-ND2GUHAM-D9R4le0z.js → chunk-ND2GUHAM-Pcd02Wpz.js} +1 -1
  23. package/packages/web/dist/assets/{chunk-QZHKN3VN-D4YgRaKH.js → chunk-QZHKN3VN-yxlVeu8W.js} +1 -1
  24. package/packages/web/dist/assets/classDiagram-4FO5ZUOK-hNchnAu0.js +2 -0
  25. package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-hNchnAu0.js +2 -0
  26. package/packages/web/dist/assets/{core-CysJ2gLj.js → core-CSvc61jo.js} +1 -1
  27. package/packages/web/dist/assets/{cose-bilkent-S5V4N54A-D1PB0KNu.js → cose-bilkent-S5V4N54A-6MJqrbdr.js} +1 -1
  28. package/packages/web/dist/assets/{dagre-BM42HDAG-C6Uuq-dq.js → dagre-BM42HDAG-B3pDB8_y.js} +1 -1
  29. package/packages/web/dist/assets/{diagram-2AECGRRQ-C9JvQ425.js → diagram-2AECGRRQ-BJA5EERJ.js} +1 -1
  30. package/packages/web/dist/assets/{diagram-5GNKFQAL-Cu6pdh2d.js → diagram-5GNKFQAL-CcyfxhVS.js} +1 -1
  31. package/packages/web/dist/assets/{diagram-KO2AKTUF-PmXcUQhY.js → diagram-KO2AKTUF-CPNyNU1j.js} +1 -1
  32. package/packages/web/dist/assets/{diagram-LMA3HP47-9ww6_9Iq.js → diagram-LMA3HP47-Q4klJqX8.js} +1 -1
  33. package/packages/web/dist/assets/{diagram-OG6HWLK6-DWQVjv_n.js → diagram-OG6HWLK6-D0ZBTRoA.js} +1 -1
  34. package/packages/web/dist/assets/{erDiagram-TEJ5UH35-6F7xTuff.js → erDiagram-TEJ5UH35-DQrCT17u.js} +1 -1
  35. package/packages/web/dist/assets/{flowDiagram-I6XJVG4X-SCiGu7m6.js → flowDiagram-I6XJVG4X-CK7nM7wz.js} +1 -1
  36. package/packages/web/dist/assets/{format-yf7Vf9kR.js → format-GJRFyZIp.js} +1 -1
  37. package/packages/web/dist/assets/{ganttDiagram-6RSMTGT7-BNpPwxkL.js → ganttDiagram-6RSMTGT7-CFsNGWGG.js} +1 -1
  38. package/packages/web/dist/assets/{gitGraphDiagram-PVQCEYII-C0gyAJjW.js → gitGraphDiagram-PVQCEYII-xP0bbv60.js} +1 -1
  39. package/packages/web/dist/assets/{index-B-YuzkF6.js → index-CDiCRIso.js} +2 -2
  40. package/packages/web/dist/assets/{infoDiagram-5YYISTIA-CJ2CFPfy.js → infoDiagram-5YYISTIA-BaKZDvMw.js} +1 -1
  41. package/packages/web/dist/assets/{ishikawaDiagram-YF4QCWOH-Cs0D32Ry.js → ishikawaDiagram-YF4QCWOH-Dr0LK4iy.js} +1 -1
  42. package/packages/web/dist/assets/{journeyDiagram-JHISSGLW-Bc9fuI2i.js → journeyDiagram-JHISSGLW-CxusdThR.js} +1 -1
  43. package/packages/web/dist/assets/{kanban-definition-UN3LZRKU-D_tC7QfC.js → kanban-definition-UN3LZRKU-CJYlFeaB.js} +1 -1
  44. package/packages/web/dist/assets/{linear-BzGHkF9K.js → linear-D0XMlvYu.js} +1 -1
  45. package/packages/web/dist/assets/{mermaid.core-DlbsYzE-.js → mermaid.core-CcPpo_y5.js} +4 -4
  46. package/packages/web/dist/assets/{mindmap-definition-RKZ34NQL-Bxeh-ToD.js → mindmap-definition-RKZ34NQL-w064Pb6X.js} +1 -1
  47. package/packages/web/dist/assets/{pieDiagram-4H26LBE5-CDmilo8S.js → pieDiagram-4H26LBE5-D_qwlox1.js} +1 -1
  48. package/packages/web/dist/assets/{quadrantDiagram-W4KKPZXB-D14-JyE9.js → quadrantDiagram-W4KKPZXB-BuMUhsEZ.js} +1 -1
  49. package/packages/web/dist/assets/{requirementDiagram-4Y6WPE33-CJ1PUGu-.js → requirementDiagram-4Y6WPE33-Bp1BqfXY.js} +1 -1
  50. package/packages/web/dist/assets/{sankeyDiagram-5OEKKPKP-BkUK73W0.js → sankeyDiagram-5OEKKPKP-BQR4HfjD.js} +1 -1
  51. package/packages/web/dist/assets/{sequenceDiagram-3UESZ5HK-sbcvsuLp.js → sequenceDiagram-3UESZ5HK-DIAHV11i.js} +1 -1
  52. package/packages/web/dist/assets/{stateDiagram-AJRCARHV-DV-7BdHK.js → stateDiagram-AJRCARHV-BHMJl8rE.js} +1 -1
  53. package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-CScn8fuQ.js +2 -0
  54. package/packages/web/dist/assets/{timeline-definition-PNZ67QCA-C5su21G_.js → timeline-definition-PNZ67QCA-CB5sHevP.js} +1 -1
  55. package/packages/web/dist/assets/{vennDiagram-CIIHVFJN-JNkIJAaQ.js → vennDiagram-CIIHVFJN-BeS1AXva.js} +1 -1
  56. package/packages/web/dist/assets/{wardley-L42UT6IY-qcz6t2vR.js → wardley-L42UT6IY-CJ9C5W6N.js} +1 -1
  57. package/packages/web/dist/assets/{wardleyDiagram-YWT4CUSO-CMrOIZ-c.js → wardleyDiagram-YWT4CUSO-Bf75XvJ_.js} +1 -1
  58. package/packages/web/dist/assets/{xychartDiagram-2RQKCTM6-C9q6Oy9u.js → xychartDiagram-2RQKCTM6-0Qiya8rY.js} +1 -1
  59. package/packages/web/dist/index.html +1 -1
  60. package/packages/web/dist/sw.js +1 -1
  61. package/packages/web/dist/assets/channel-Bw0A52CB.js +0 -2
  62. package/packages/web/dist/assets/classDiagram-4FO5ZUOK-itf_kt-q.js +0 -2
  63. package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-itf_kt-q.js +0 -2
  64. package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-BlkGdXKW.js +0 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edspencer/paddock",
3
- "version": "0.57.0",
3
+ "version": "0.58.0",
4
4
  "description": "Project-first launchpad for Claude Code: server-hosted, persistent, resumable sessions organized by project.",
5
5
  "keywords": [
6
6
  "claude",
@@ -37,6 +37,7 @@
37
37
  * Applied to the environment BEFORE the config is read.
38
38
  * --json Emit the raw result as JSON instead of prose.
39
39
  */
40
+ import { pathToFileURL } from "node:url";
40
41
  import { loadPaddockConfig } from "../config.js";
41
42
  import { ProjectStore, ROOT_KEY } from "../projects.js";
42
43
  import { HerdctlService } from "../herdctl.js";
@@ -187,7 +188,11 @@ async function main() {
187
188
  }
188
189
  }
189
190
  // Only run when invoked directly, so the arg parser stays unit-testable.
190
- if (process.argv[1] !== undefined && import.meta.url === `file://${process.argv[1]}`) {
191
+ // `pathToFileURL`, not `"file://" + argv[1]`: the naive concatenation leaves any
192
+ // space or non-ASCII character in the path un-encoded, so the comparison fails
193
+ // and the script silently does nothing when run from such a directory — the same
194
+ // percent-encoding trap as #636.
195
+ if (process.argv[1] !== undefined && pathToFileURL(process.argv[1]).href === import.meta.url) {
191
196
  main()
192
197
  .then((code) => process.exit(code))
193
198
  .catch((err) => {
@@ -1,2 +1,30 @@
1
1
  #!/usr/bin/env node
2
- export {};
2
+ export interface CliOptions {
3
+ port?: string;
4
+ host?: string;
5
+ dataDir?: string;
6
+ open: boolean;
7
+ verbose: boolean;
8
+ help: boolean;
9
+ version: boolean;
10
+ }
11
+ /** A usage error. Thrown rather than exiting, so `parseArgs` stays testable. */
12
+ export declare class CliError extends Error {
13
+ }
14
+ export declare function parseArgs(argv: string[]): CliOptions;
15
+ /**
16
+ * Why this Node is unusable, or `undefined` if it is fine.
17
+ *
18
+ * Split from the exit so it can be tested. `engines` only warns by default, and
19
+ * the failure it eventually produces is an unexplained syntax error deep inside
20
+ * a dependency — which is nobody's idea of a first-run experience.
21
+ */
22
+ export declare function nodeVersionProblem(nodeVersion: string): string | undefined;
23
+ /**
24
+ * Translate a listen failure into something a human can act on.
25
+ *
26
+ * `EADDRINUSE` on 4000 is the single most likely first-run failure — it is a
27
+ * popular port and Paddock's own default — and Node's raw error names neither
28
+ * the port nor the flag that fixes it.
29
+ */
30
+ export declare function explainListenError(err: unknown, host: string, port: string): string;
@@ -1,34 +1,28 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * `paddock` — the npx / global-install entrypoint (#TBD).
3
+ * `paddock` — the npx / global-install entrypoint (#637, #638).
4
4
  *
5
- * PROTOTYPE. This is the user-facing front door for `npx @edspencer/paddock`:
6
- * someone who has never seen this repo runs one command and gets a working
7
- * instance on localhost. Everything here is preflight and defaults — the server
8
- * itself is unchanged, and `start()` is the same lifecycle `node dist/index.js`
9
- * uses.
5
+ * The user-facing front door for `npx @edspencer/paddock`: someone who has never
6
+ * seen this repo runs one command and gets a working instance on localhost.
7
+ * Everything here is preflight, defaults and presentation — the server itself is
8
+ * untouched, and `start()` is the same lifecycle `node dist/index.js` uses.
10
9
  *
11
- * The design constraint that shapes this file: the server is already
12
- * *tolerant* — every setting has a default, an empty data dir self-initializes,
13
- * and auth defaults to `none` on a loopback bind. So a bare `start()` would in
14
- * fact boot. What it would NOT do is tell a first-time user why their chats
15
- * fail, or where their data went. Hence:
10
+ * The design constraint that shapes this file: the server is already *tolerant*
11
+ * — every setting has a default, an empty data dir self-initializes, and auth
12
+ * defaults to `none` on a loopback bind. So a bare `start()` would in fact boot.
13
+ * What it would NOT do is tell a first-time user why their chats fail, where
14
+ * their data went, or which of the several hundred lines of JSON that just
15
+ * scrolled past contained the URL.
16
16
  *
17
- * 1. Node version — checked explicitly, because the `engines` field only
18
- * warns and the eventual failure is an opaque syntax error.
19
- * 2. Data dir — defaults to `~/.paddock`, NOT the server's `./data`, which is
20
- * relative to cwd and would litter whatever directory you happened to run
21
- * npx from (and silently give you a different instance next time).
22
- * 3. `claude` on PATH — prepended from our own node_modules/.bin. Chats do not
23
- * need it (the SDK runtime resolves its own bundled binary), but the
24
- * sweeper and triggers shell out to `claude` by name.
25
- * 4. Credentials — a warning, never a hard failure: we cannot reliably detect
26
- * a macOS Keychain login, so refusing to start would lock out valid users.
17
+ * Everything below the arg parser is side-effecting by nature, so the pure parts
18
+ * (`parseArgs`, `nodeVersionProblem`, `explainListenError`) are exported and
19
+ * unit-tested; the rest is exercised by running the built binary.
27
20
  */
28
21
  import fs from "node:fs";
29
22
  import os from "node:os";
30
23
  import path from "node:path";
31
- import { fileURLToPath } from "node:url";
24
+ import { spawn } from "node:child_process";
25
+ import { fileURLToPath, pathToFileURL } from "node:url";
32
26
  const MIN_NODE_MAJOR = 22;
33
27
  /** This module's own directory: `<…>/packages/server/dist/cli`. */
34
28
  const moduleDir = path.dirname(fileURLToPath(import.meta.url));
@@ -43,8 +37,7 @@ const moduleDir = path.dirname(fileURLToPath(import.meta.url));
43
37
  * deps at `<pkg>/node_modules` only — two levels FURTHER up
44
38
  *
45
39
  * A fixed `../..` hop is right for the repo and wrong for the published tarball,
46
- * where it lands on `packages/server` and finds no `node_modules` at all. Walking
47
- * up is correct for both without branching on which one we are in.
40
+ * where it lands on `packages/server` and finds no `node_modules` at all.
48
41
  */
49
42
  function findUp(rel) {
50
43
  let dir = moduleDir;
@@ -59,14 +52,17 @@ function findUp(rel) {
59
52
  }
60
53
  /** Nearest ancestor holding a `package.json` — `packages/server` in both layouts. */
61
54
  const packageRoot = findUp("package.json") ?? path.resolve(moduleDir, "../..");
62
- function parseArgs(argv) {
63
- const opts = { help: false, version: false };
55
+ /** A usage error. Thrown rather than exiting, so `parseArgs` stays testable. */
56
+ export class CliError extends Error {
57
+ }
58
+ export function parseArgs(argv) {
59
+ const opts = { open: false, verbose: false, help: false, version: false };
64
60
  for (let i = 0; i < argv.length; i++) {
65
61
  const arg = argv[i];
66
62
  const next = () => {
67
63
  const v = argv[++i];
68
64
  if (v === undefined)
69
- fail(`${arg} needs a value`);
65
+ throw new CliError(`${arg} needs a value`);
70
66
  return v;
71
67
  };
72
68
  switch (arg) {
@@ -81,6 +77,13 @@ function parseArgs(argv) {
81
77
  case "--data-dir":
82
78
  opts.dataDir = next();
83
79
  break;
80
+ case "-o":
81
+ case "--open":
82
+ opts.open = true;
83
+ break;
84
+ case "--verbose":
85
+ opts.verbose = true;
86
+ break;
84
87
  case "-h":
85
88
  case "--help":
86
89
  opts.help = true;
@@ -90,7 +93,7 @@ function parseArgs(argv) {
90
93
  opts.version = true;
91
94
  break;
92
95
  default:
93
- fail(`unknown option: ${arg}\nRun \`paddock --help\` for usage.`);
96
+ throw new CliError(`unknown option: ${arg}\nRun \`paddock --help\` for usage.`);
94
97
  }
95
98
  }
96
99
  return opts;
@@ -117,6 +120,8 @@ Options
117
120
  -p, --port <port> HTTP/WS port (default 4000, or $PORT)
118
121
  --host <host> Bind address (default 127.0.0.1)
119
122
  -d, --data-dir <path> Projects + state (default ~/.paddock, or $PADDOCK_DATA_DIR)
123
+ -o, --open Open the app in your browser once it is listening
124
+ --verbose Show the server's own logs (quiet by default)
120
125
  -v, --version Print the Paddock version and exit
121
126
  -h, --help Show this help
122
127
 
@@ -126,20 +131,31 @@ Credentials
126
131
  billing). If you already use Claude Code on this machine, an existing login
127
132
  under ~/.claude is picked up automatically. Otherwise run \`claude setup-token\`.
128
133
 
134
+ Your data
135
+ Everything lives in one directory — ~/.paddock unless you pass --data-dir.
136
+ Projects, chat transcripts and settings all persist there between runs. Move
137
+ that directory to move your instance; delete it to start over. Nothing is
138
+ stored anywhere else.
139
+
129
140
  Notes
130
141
  Binds loopback with authentication disabled, which is safe for a laptop. To
131
142
  expose it on a network, set PADDOCK_AUTH_MODE first — Paddock refuses to bind
132
143
  a routable interface wide open. See AUTH.md.
133
144
 
134
145
  Docs: https://github.com/edspencer/paddock`;
135
- /** Node's own version gate. `engines` only warns by default, and the failure it
136
- * eventually produces is an unexplained syntax error deep in a dependency. */
137
- function checkNodeVersion() {
138
- const major = Number(process.versions.node.split(".")[0]);
139
- if (Number.isFinite(major) && major < MIN_NODE_MAJOR) {
140
- fail(`Node ${process.versions.node} is too old — Paddock needs Node ${MIN_NODE_MAJOR}+.\n` +
141
- `Upgrade Node, then re-run. (nodejs.org, or \`nvm install ${MIN_NODE_MAJOR}\`)`);
142
- }
146
+ /**
147
+ * Why this Node is unusable, or `undefined` if it is fine.
148
+ *
149
+ * Split from the exit so it can be tested. `engines` only warns by default, and
150
+ * the failure it eventually produces is an unexplained syntax error deep inside
151
+ * a dependency — which is nobody's idea of a first-run experience.
152
+ */
153
+ export function nodeVersionProblem(nodeVersion) {
154
+ const major = Number(nodeVersion.split(".")[0]);
155
+ if (!Number.isFinite(major) || major >= MIN_NODE_MAJOR)
156
+ return undefined;
157
+ return (`Node ${nodeVersion} is too old — Paddock needs Node ${MIN_NODE_MAJOR}+.\n` +
158
+ `Upgrade Node, then re-run. (nodejs.org, or \`nvm install ${MIN_NODE_MAJOR}\`)`);
143
159
  }
144
160
  /**
145
161
  * Put our bundled `claude` on PATH for child processes.
@@ -209,8 +225,54 @@ function noteFirstRun(dataDir) {
209
225
  "",
210
226
  ].join("\n"));
211
227
  }
228
+ /** Open `url` in the default browser. Best-effort: failing is never fatal. */
229
+ function openBrowser(url) {
230
+ const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
231
+ try {
232
+ const child = spawn(cmd, [url], {
233
+ stdio: "ignore",
234
+ detached: true,
235
+ shell: process.platform === "win32", // `start` is a cmd builtin, not a binary
236
+ });
237
+ // A headless box has no xdg-open. The URL is printed either way, so an
238
+ // unopenable browser is a non-event rather than a failed run.
239
+ child.on("error", () => { });
240
+ child.unref();
241
+ }
242
+ catch {
243
+ /* as above */
244
+ }
245
+ }
246
+ /**
247
+ * Translate a listen failure into something a human can act on.
248
+ *
249
+ * `EADDRINUSE` on 4000 is the single most likely first-run failure — it is a
250
+ * popular port and Paddock's own default — and Node's raw error names neither
251
+ * the port nor the flag that fixes it.
252
+ */
253
+ export function explainListenError(err, host, port) {
254
+ const code = err?.code;
255
+ if (code === "EADDRINUSE") {
256
+ return (`port ${port} is already in use.\n` +
257
+ `Something else is listening on ${host}:${port} — possibly another Paddock.\n` +
258
+ `Pick a different port: paddock --port ${Number(port) + 1}`);
259
+ }
260
+ if (code === "EACCES") {
261
+ return (`not allowed to bind ${host}:${port}.\n` +
262
+ `Ports below 1024 need elevated privileges — use a higher one: paddock --port 4000`);
263
+ }
264
+ return String(err?.message ?? err);
265
+ }
212
266
  async function main() {
213
- const opts = parseArgs(process.argv.slice(2));
267
+ let opts;
268
+ try {
269
+ opts = parseArgs(process.argv.slice(2));
270
+ }
271
+ catch (err) {
272
+ if (err instanceof CliError)
273
+ fail(err.message);
274
+ throw err;
275
+ }
214
276
  if (opts.help) {
215
277
  console.log(USAGE);
216
278
  return;
@@ -219,7 +281,9 @@ async function main() {
219
281
  console.log(readVersion());
220
282
  return;
221
283
  }
222
- checkNodeVersion();
284
+ const problem = nodeVersionProblem(process.versions.node);
285
+ if (problem !== undefined)
286
+ fail(problem);
223
287
  addBundledBinsToPath();
224
288
  // Apply CLI defaults as env vars. This is the whole integration surface: the
225
289
  // server resolves config inside `buildApp()`, so anything set before the
@@ -232,16 +296,44 @@ async function main() {
232
296
  process.env.PORT = opts.port;
233
297
  if (opts.host)
234
298
  process.env.HOST = opts.host;
299
+ // Quiet by default. The server emits a few hundred lines on the way up, which
300
+ // scrolls the one line the user actually needs — the URL — off the top of the
301
+ // terminal. TWO loggers have to be told, and they are unrelated:
302
+ // LOG_LEVEL Paddock's own pino logger (config.ts).
303
+ // HERDCTL_LOG_LEVEL @herdctl/core's `createLogger`, which writes the
304
+ // `[fleet-manager] …` lines via console.info — pino's
305
+ // level cannot reach those.
306
+ // An explicit value for either is left alone, and `--verbose` skips both.
307
+ if (!opts.verbose) {
308
+ if (process.env.LOG_LEVEL === undefined)
309
+ process.env.LOG_LEVEL = "warn";
310
+ if (process.env.HERDCTL_LOG_LEVEL === undefined)
311
+ process.env.HERDCTL_LOG_LEVEL = "warn";
312
+ }
235
313
  warnIfNoCredentials(process.env.CLAUDE_HOME ?? path.join(os.homedir(), ".claude"));
236
314
  const port = process.env.PORT ?? "4000";
237
315
  const host = process.env.HOST ?? "127.0.0.1";
238
316
  // Imported dynamically, AFTER the env above is set: a static import would
239
317
  // pull in app.ts -> config.ts and resolve config against the wrong data dir.
240
318
  const { start } = await import("../start.js");
241
- await start();
242
- console.log(`\n Paddock is running at http://${host}:${port}\n Press Ctrl-C to stop.\n`);
319
+ try {
320
+ await start();
321
+ }
322
+ catch (err) {
323
+ fail(explainListenError(err, host, port));
324
+ }
325
+ const url = `http://${host}:${port}`;
326
+ console.log(`\n Paddock is running at ${url}\n Data: ${dataDir}\n Press Ctrl-C to stop.\n`);
327
+ if (opts.open)
328
+ openBrowser(url);
329
+ }
330
+ // Run only when executed directly, so the unit tests can import the pure parts.
331
+ // `pathToFileURL`, not `"file://" + argv[1]`: the naive concatenation breaks on
332
+ // any path containing a space or non-ASCII character — the same percent-encoding
333
+ // trap as #636.
334
+ if (process.argv[1] !== undefined && pathToFileURL(process.argv[1]).href === import.meta.url) {
335
+ main().catch((err) => {
336
+ console.error("fatal:", err);
337
+ process.exit(1);
338
+ });
243
339
  }
244
- main().catch((err) => {
245
- console.error("fatal:", err);
246
- process.exit(1);
247
- });
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paddock/server",
3
- "version": "0.57.0",
3
+ "version": "0.58.0",
4
4
  "private": true,
5
5
  "type": "module"
6
6
  }
@@ -1,2 +1,2 @@
1
- import{r as o,b as f,j as e,A as b,k as E,l as A,m as O,M as F,n as I}from"./index-B-YuzkF6.js";function $(){const[t,s]=o.useState(null),[l,i]=o.useState(!0),[r,c]=o.useState(null),[d,m]=o.useState({}),[h,k]=o.useState(!1),[j,p]=o.useState(null),[N,u]=o.useState(!1);o.useEffect(()=>{let a=!0;return i(!0),f.getInstanceConfig().then(n=>{a&&(s(n),c(null))}).catch(n=>a&&c(n instanceof Error?n.message:String(n))).finally(()=>a&&i(!1)),()=>{a=!1}},[]);const v=(a,n)=>{u(!1),p(null),m(g=>({...g,[a]:n}))},S=a=>Object.prototype.hasOwnProperty.call(d,a.key)?d[a.key]:a.value,y=o.useMemo(()=>t?t.groups.flatMap(a=>a.fields):[],[t]),x=o.useMemo(()=>y.filter(a=>a.editable&&!a.envOverridden).filter(a=>Object.prototype.hasOwnProperty.call(d,a.key)).filter(a=>!B(d[a.key],a.value)).map(a=>a.key),[y,d]),w=async()=>{if(x.length===0)return;const a={};for(const n of x)a[n]=d[n];k(!0),p(null);try{await f.updateInstanceConfig(a);const n=await f.getInstanceConfig();s(n),m({}),u(!0)}catch(n){p(n instanceof A||n instanceof Error?n.message:String(n))}finally{k(!1)}},C=()=>{m({}),p(null),u(!1)};return e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"min-h-0 flex-1 overflow-y-auto px-4 py-5 sm:px-6",children:e.jsxs("div",{className:"mx-auto max-w-2xl",children:[e.jsx(L,{saved:N,configPath:t==null?void 0:t.configPath}),l&&e.jsx("p",{className:"text-sm text-paddock-500",children:"Loading…"}),r&&e.jsxs("p",{className:"text-sm text-red-600 dark:text-red-400",role:"alert",children:["Failed to load settings: ",r]}),t&&t.groups.map(a=>e.jsx(M,{title:a.label,description:a.description,children:e.jsx("div",{className:"grid grid-cols-1 gap-4",children:a.fields.map(n=>e.jsx(P,{field:n,value:S(n),onChange:g=>v(n.key,g)},n.key))})},a.id))]})}),t&&e.jsxs("footer",{className:"flex items-center gap-3 border-t border-paddock-200 px-4 py-3 dark:border-paddock-800 sm:px-6",children:[j&&e.jsxs("span",{className:"flex items-center gap-1.5 text-[13px] text-red-600 dark:text-red-400",role:"alert",children:[e.jsx(b,{width:14,height:14,className:"shrink-0"}),j]}),e.jsx("span",{className:"ml-auto text-[12px] text-paddock-400",children:x.length>0?`${x.length} unsaved change${x.length===1?"":"s"}`:"No changes"}),e.jsx("button",{type:"button",className:"btn-subtle",onClick:C,disabled:x.length===0||h,children:"Reset"}),e.jsx("button",{type:"button",className:"btn-primary",onClick:w,disabled:x.length===0||h,children:h?"Saving…":"Save changes"})]})]})}function L({saved:t,configPath:s}){return e.jsxs("div",{role:"status",className:`mb-6 flex items-start gap-2 rounded-lg border px-3 py-2.5 text-[13px] leading-snug ${t?"border-emerald-300 bg-emerald-50 text-emerald-800 dark:border-emerald-700/60 dark:bg-emerald-900/20 dark:text-emerald-300":"border-amber-300 bg-amber-50 text-amber-800 dark:border-amber-700/60 dark:bg-amber-900/20 dark:text-amber-300"}`,children:[t?e.jsx(E,{width:15,height:15,className:"mt-0.5 shrink-0"}):e.jsx(b,{width:15,height:15,className:"mt-0.5 shrink-0"}),e.jsx("span",{children:t?e.jsxs(e.Fragment,{children:[e.jsx("strong",{children:"Saved to disk."})," These changes take effect only after the server restarts."]}):e.jsxs(e.Fragment,{children:["Changes here are written to ",e.jsx("code",{className:"font-mono text-[12px]",children:T(s)})," ","and take effect only after the server restarts — the running instance keeps its current config until then."]})})]})}function M({title:t,description:s,children:l}){return e.jsxs("section",{className:"mb-6",children:[e.jsx("h2",{className:"text-sm font-semibold uppercase tracking-wide text-paddock-500",children:t}),s&&e.jsx("p",{className:"mb-3 mt-0.5 text-[13px] text-paddock-500",children:s}),e.jsx("div",{className:`card ${s?"":"mt-2"}`,children:l})]})}function P({field:t,value:s,onChange:l}){const i=!t.editable||t.envOverridden,r=`cfg-${t.key}`;return e.jsxs("div",{children:[e.jsxs("div",{className:"flex items-center justify-between gap-3",children:[e.jsxs("label",{htmlFor:i?void 0:r,className:"text-sm font-medium",children:[t.label,t.sensitive&&e.jsx("span",{className:"ml-1.5 align-middle text-[10px] font-semibold uppercase tracking-wide text-amber-600 dark:text-amber-400",children:"sensitive"})]}),t.type==="boolean"&&!i&&e.jsx("input",{id:r,type:"checkbox",className:"h-4 w-4 accent-accent",checked:!!s,onChange:c=>l(c.target.checked)})]}),t.help&&e.jsx("p",{className:"mt-0.5 text-[12px] leading-snug text-paddock-500",children:t.help}),i?e.jsx(V,{field:t,value:s}):t.type!=="boolean"&&e.jsx(R,{field:t,value:s,onChange:l,inputId:r}),t.envOverridden&&e.jsxs("p",{className:"mt-1 flex items-start gap-1.5 text-[12px] leading-snug text-amber-600 dark:text-amber-400",children:[e.jsx(b,{width:13,height:13,className:"mt-0.5 shrink-0"}),e.jsxs("span",{children:["Overridden by environment variable ",e.jsx("code",{className:"font-mono",children:t.envVar})," — edit that env var (and restart) to change it."]})]})]})}function R({field:t,value:s,onChange:l,inputId:i}){if(t.type==="enum")return e.jsx("select",{id:i,className:"input mt-1.5",value:String(s??""),onChange:r=>l(r.target.value),children:(t.enumValues??[]).map(r=>e.jsx("option",{value:r,children:r},r))});if(t.type==="number")return e.jsx("input",{id:i,type:"number",className:"input mt-1.5",value:s==null?"":String(s),placeholder:t.default===null?"default":String(t.default),onChange:r=>l(r.target.value===""?null:Number(r.target.value))});if(t.type==="text"){const r=s==null||s===t.default,c=s==null?String(t.default??""):String(s),d=typeof t.default=="string"&&!r;return e.jsxs("div",{className:"mt-1.5",children:[e.jsx("textarea",{id:i,className:"input min-h-[9rem] resize-y font-mono text-[12px] leading-relaxed",rows:10,spellCheck:!1,value:c,onChange:m=>l(m.target.value)}),e.jsxs("div",{className:"mt-1 flex items-center justify-between gap-3 text-[12px] text-paddock-500",children:[e.jsx("span",{children:r?"Using the built-in default.":s===""?"Empty — nothing will be appended.":`${c.length.toLocaleString()} characters.`}),d&&e.jsx("button",{type:"button",className:"shrink-0 underline underline-offset-2 hover:text-paddock-700 dark:hover:text-paddock-300",onClick:()=>l(null),children:"Restore default"})]})]})}if(t.type==="string-list"){const r=Array.isArray(s)?s.join(", "):String(s??"");return e.jsx("input",{id:i,type:"text",className:"input mt-1.5",value:r,onChange:c=>l(c.target.value.split(",").map(d=>d.trim()).filter(Boolean))})}return e.jsx("input",{id:i,type:"text",className:"input mt-1.5",value:s==null?"":String(s),placeholder:t.default===null?"":String(t.default),onChange:r=>l(r.target.value)})}function V({field:t,value:s}){return e.jsx("div",{className:"mt-1.5 rounded-md border border-dashed border-paddock-300 bg-paddock-50 px-2.5 py-1.5 text-[13px] text-paddock-600 dark:border-paddock-700 dark:bg-paddock-900/40 dark:text-paddock-300",children:t.type==="boolean"?e.jsx("span",{className:"font-mono",children:s?"true":"false"}):s==null||s===""?e.jsx("span",{className:"italic text-paddock-400",children:t.type==="text"&&s===""?"(empty — nothing appended)":"(not set)"}):t.type==="text"?e.jsx("span",{className:"block max-h-64 overflow-y-auto whitespace-pre-wrap font-mono text-[12px] leading-relaxed",children:String(s)}):e.jsx("span",{className:"break-all font-mono",children:Array.isArray(s)?s.join(", "):String(s)})})}function B(t,s){return Array.isArray(t)&&Array.isArray(s)?t.length===s.length&&t.every((l,i)=>l===s[i]):t===s}function T(t){if(!t)return"paddock.config.yaml";const s=t.split("/");return s[s.length-1]||t}function z(){const t=O(),s=(t==null?void 0:t.openNav)??(()=>{});return e.jsxs("div",{className:"flex h-full min-h-0 flex-col",children:[e.jsxs("header",{className:"pt-safe flex items-center gap-2 border-b border-paddock-200 px-3 pb-2.5 dark:border-paddock-800 sm:px-6 lg:py-4",children:[e.jsx("button",{type:"button",onClick:s,"aria-label":"Open menu",className:"btn-subtle -ml-1 shrink-0 px-2 py-1.5 lg:hidden",children:e.jsx(F,{width:20,height:20})}),e.jsx(I,{width:18,height:18,className:"shrink-0 text-paddock-400"}),e.jsx("h1",{className:"text-[15px] font-semibold tracking-tight",children:"Config"})]}),e.jsx($,{})]})}export{z as InstanceConfigPage};
1
+ import{r as o,b as f,j as e,A as b,k as E,l as A,m as O,M as F,n as I}from"./index-CDiCRIso.js";function $(){const[t,s]=o.useState(null),[l,i]=o.useState(!0),[r,c]=o.useState(null),[d,m]=o.useState({}),[h,k]=o.useState(!1),[j,p]=o.useState(null),[N,u]=o.useState(!1);o.useEffect(()=>{let a=!0;return i(!0),f.getInstanceConfig().then(n=>{a&&(s(n),c(null))}).catch(n=>a&&c(n instanceof Error?n.message:String(n))).finally(()=>a&&i(!1)),()=>{a=!1}},[]);const v=(a,n)=>{u(!1),p(null),m(g=>({...g,[a]:n}))},S=a=>Object.prototype.hasOwnProperty.call(d,a.key)?d[a.key]:a.value,y=o.useMemo(()=>t?t.groups.flatMap(a=>a.fields):[],[t]),x=o.useMemo(()=>y.filter(a=>a.editable&&!a.envOverridden).filter(a=>Object.prototype.hasOwnProperty.call(d,a.key)).filter(a=>!B(d[a.key],a.value)).map(a=>a.key),[y,d]),w=async()=>{if(x.length===0)return;const a={};for(const n of x)a[n]=d[n];k(!0),p(null);try{await f.updateInstanceConfig(a);const n=await f.getInstanceConfig();s(n),m({}),u(!0)}catch(n){p(n instanceof A||n instanceof Error?n.message:String(n))}finally{k(!1)}},C=()=>{m({}),p(null),u(!1)};return e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"min-h-0 flex-1 overflow-y-auto px-4 py-5 sm:px-6",children:e.jsxs("div",{className:"mx-auto max-w-2xl",children:[e.jsx(L,{saved:N,configPath:t==null?void 0:t.configPath}),l&&e.jsx("p",{className:"text-sm text-paddock-500",children:"Loading…"}),r&&e.jsxs("p",{className:"text-sm text-red-600 dark:text-red-400",role:"alert",children:["Failed to load settings: ",r]}),t&&t.groups.map(a=>e.jsx(M,{title:a.label,description:a.description,children:e.jsx("div",{className:"grid grid-cols-1 gap-4",children:a.fields.map(n=>e.jsx(P,{field:n,value:S(n),onChange:g=>v(n.key,g)},n.key))})},a.id))]})}),t&&e.jsxs("footer",{className:"flex items-center gap-3 border-t border-paddock-200 px-4 py-3 dark:border-paddock-800 sm:px-6",children:[j&&e.jsxs("span",{className:"flex items-center gap-1.5 text-[13px] text-red-600 dark:text-red-400",role:"alert",children:[e.jsx(b,{width:14,height:14,className:"shrink-0"}),j]}),e.jsx("span",{className:"ml-auto text-[12px] text-paddock-400",children:x.length>0?`${x.length} unsaved change${x.length===1?"":"s"}`:"No changes"}),e.jsx("button",{type:"button",className:"btn-subtle",onClick:C,disabled:x.length===0||h,children:"Reset"}),e.jsx("button",{type:"button",className:"btn-primary",onClick:w,disabled:x.length===0||h,children:h?"Saving…":"Save changes"})]})]})}function L({saved:t,configPath:s}){return e.jsxs("div",{role:"status",className:`mb-6 flex items-start gap-2 rounded-lg border px-3 py-2.5 text-[13px] leading-snug ${t?"border-emerald-300 bg-emerald-50 text-emerald-800 dark:border-emerald-700/60 dark:bg-emerald-900/20 dark:text-emerald-300":"border-amber-300 bg-amber-50 text-amber-800 dark:border-amber-700/60 dark:bg-amber-900/20 dark:text-amber-300"}`,children:[t?e.jsx(E,{width:15,height:15,className:"mt-0.5 shrink-0"}):e.jsx(b,{width:15,height:15,className:"mt-0.5 shrink-0"}),e.jsx("span",{children:t?e.jsxs(e.Fragment,{children:[e.jsx("strong",{children:"Saved to disk."})," These changes take effect only after the server restarts."]}):e.jsxs(e.Fragment,{children:["Changes here are written to ",e.jsx("code",{className:"font-mono text-[12px]",children:T(s)})," ","and take effect only after the server restarts — the running instance keeps its current config until then."]})})]})}function M({title:t,description:s,children:l}){return e.jsxs("section",{className:"mb-6",children:[e.jsx("h2",{className:"text-sm font-semibold uppercase tracking-wide text-paddock-500",children:t}),s&&e.jsx("p",{className:"mb-3 mt-0.5 text-[13px] text-paddock-500",children:s}),e.jsx("div",{className:`card ${s?"":"mt-2"}`,children:l})]})}function P({field:t,value:s,onChange:l}){const i=!t.editable||t.envOverridden,r=`cfg-${t.key}`;return e.jsxs("div",{children:[e.jsxs("div",{className:"flex items-center justify-between gap-3",children:[e.jsxs("label",{htmlFor:i?void 0:r,className:"text-sm font-medium",children:[t.label,t.sensitive&&e.jsx("span",{className:"ml-1.5 align-middle text-[10px] font-semibold uppercase tracking-wide text-amber-600 dark:text-amber-400",children:"sensitive"})]}),t.type==="boolean"&&!i&&e.jsx("input",{id:r,type:"checkbox",className:"h-4 w-4 accent-accent",checked:!!s,onChange:c=>l(c.target.checked)})]}),t.help&&e.jsx("p",{className:"mt-0.5 text-[12px] leading-snug text-paddock-500",children:t.help}),i?e.jsx(V,{field:t,value:s}):t.type!=="boolean"&&e.jsx(R,{field:t,value:s,onChange:l,inputId:r}),t.envOverridden&&e.jsxs("p",{className:"mt-1 flex items-start gap-1.5 text-[12px] leading-snug text-amber-600 dark:text-amber-400",children:[e.jsx(b,{width:13,height:13,className:"mt-0.5 shrink-0"}),e.jsxs("span",{children:["Overridden by environment variable ",e.jsx("code",{className:"font-mono",children:t.envVar})," — edit that env var (and restart) to change it."]})]})]})}function R({field:t,value:s,onChange:l,inputId:i}){if(t.type==="enum")return e.jsx("select",{id:i,className:"input mt-1.5",value:String(s??""),onChange:r=>l(r.target.value),children:(t.enumValues??[]).map(r=>e.jsx("option",{value:r,children:r},r))});if(t.type==="number")return e.jsx("input",{id:i,type:"number",className:"input mt-1.5",value:s==null?"":String(s),placeholder:t.default===null?"default":String(t.default),onChange:r=>l(r.target.value===""?null:Number(r.target.value))});if(t.type==="text"){const r=s==null||s===t.default,c=s==null?String(t.default??""):String(s),d=typeof t.default=="string"&&!r;return e.jsxs("div",{className:"mt-1.5",children:[e.jsx("textarea",{id:i,className:"input min-h-[9rem] resize-y font-mono text-[12px] leading-relaxed",rows:10,spellCheck:!1,value:c,onChange:m=>l(m.target.value)}),e.jsxs("div",{className:"mt-1 flex items-center justify-between gap-3 text-[12px] text-paddock-500",children:[e.jsx("span",{children:r?"Using the built-in default.":s===""?"Empty — nothing will be appended.":`${c.length.toLocaleString()} characters.`}),d&&e.jsx("button",{type:"button",className:"shrink-0 underline underline-offset-2 hover:text-paddock-700 dark:hover:text-paddock-300",onClick:()=>l(null),children:"Restore default"})]})]})}if(t.type==="string-list"){const r=Array.isArray(s)?s.join(", "):String(s??"");return e.jsx("input",{id:i,type:"text",className:"input mt-1.5",value:r,onChange:c=>l(c.target.value.split(",").map(d=>d.trim()).filter(Boolean))})}return e.jsx("input",{id:i,type:"text",className:"input mt-1.5",value:s==null?"":String(s),placeholder:t.default===null?"":String(t.default),onChange:r=>l(r.target.value)})}function V({field:t,value:s}){return e.jsx("div",{className:"mt-1.5 rounded-md border border-dashed border-paddock-300 bg-paddock-50 px-2.5 py-1.5 text-[13px] text-paddock-600 dark:border-paddock-700 dark:bg-paddock-900/40 dark:text-paddock-300",children:t.type==="boolean"?e.jsx("span",{className:"font-mono",children:s?"true":"false"}):s==null||s===""?e.jsx("span",{className:"italic text-paddock-400",children:t.type==="text"&&s===""?"(empty — nothing appended)":"(not set)"}):t.type==="text"?e.jsx("span",{className:"block max-h-64 overflow-y-auto whitespace-pre-wrap font-mono text-[12px] leading-relaxed",children:String(s)}):e.jsx("span",{className:"break-all font-mono",children:Array.isArray(s)?s.join(", "):String(s)})})}function B(t,s){return Array.isArray(t)&&Array.isArray(s)?t.length===s.length&&t.every((l,i)=>l===s[i]):t===s}function T(t){if(!t)return"paddock.config.yaml";const s=t.split("/");return s[s.length-1]||t}function z(){const t=O(),s=(t==null?void 0:t.openNav)??(()=>{});return e.jsxs("div",{className:"flex h-full min-h-0 flex-col",children:[e.jsxs("header",{className:"pt-safe flex items-center gap-2 border-b border-paddock-200 px-3 pb-2.5 dark:border-paddock-800 sm:px-6 lg:py-4",children:[e.jsx("button",{type:"button",onClick:s,"aria-label":"Open menu",className:"btn-subtle -ml-1 shrink-0 px-2 py-1.5 lg:hidden",children:e.jsx(F,{width:20,height:20})}),e.jsx(I,{width:18,height:18,className:"shrink-0 text-paddock-400"}),e.jsx("h1",{className:"text-[15px] font-semibold tracking-tight",children:"Config"})]}),e.jsx($,{})]})}export{z as InstanceConfigPage};
2
2