@edspencer/paddock 0.58.0 → 0.59.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 (65) hide show
  1. package/package.json +1 -1
  2. package/packages/server/dist/cli/here.d.ts +25 -0
  3. package/packages/server/dist/cli/here.js +104 -0
  4. package/packages/server/dist/cli/paddock.d.ts +1 -0
  5. package/packages/server/dist/cli/paddock.js +110 -4
  6. package/packages/server/package.json +1 -1
  7. package/packages/web/dist/assets/{InstanceConfigPage-BKE6j_Wy.js → InstanceConfigPage-Cli_WHsM.js} +1 -1
  8. package/packages/web/dist/assets/{MarkdownRenderer-B7_FErIN.js → MarkdownRenderer-BYK9nmr4.js} +1 -1
  9. package/packages/web/dist/assets/{ProjectRedirect-DOaB53YM.js → ProjectRedirect-DWcMpdZJ.js} +1 -1
  10. package/packages/web/dist/assets/{ProjectView-Dz_QUyPG.js → ProjectView-CvD9WQsW.js} +3 -3
  11. package/packages/web/dist/assets/{ProjectsGrid-CjfDQ37q.js → ProjectsGrid-Bf2cx9-Z.js} +1 -1
  12. package/packages/web/dist/assets/{arc-BxAGgBjd.js → arc-CZ9CFd-g.js} +1 -1
  13. package/packages/web/dist/assets/{architectureDiagram-3BPJPVTR-CBz2e-c3.js → architectureDiagram-3BPJPVTR-BzOXUlLu.js} +1 -1
  14. package/packages/web/dist/assets/{blockDiagram-GPEHLZMM-ZqClBxB7.js → blockDiagram-GPEHLZMM-Bnkcx-Fn.js} +1 -1
  15. package/packages/web/dist/assets/{c4Diagram-AAUBKEIU-CvFOBN1E.js → c4Diagram-AAUBKEIU-C-nqckPS.js} +1 -1
  16. package/packages/web/dist/assets/channel-By51cUN7.js +2 -0
  17. package/packages/web/dist/assets/{chunk-2J33WTMH-C44u7-Xg.js → chunk-2J33WTMH-jVn7J_5N.js} +1 -1
  18. package/packages/web/dist/assets/{chunk-4BX2VUAB-DXG9UM3Z.js → chunk-4BX2VUAB-D5eys7Hi.js} +1 -1
  19. package/packages/web/dist/assets/{chunk-55IACEB6-BLkJFttb.js → chunk-55IACEB6-CqpdXIoL.js} +1 -1
  20. package/packages/web/dist/assets/{chunk-727SXJPM-DZidMKHZ.js → chunk-727SXJPM-SQikk09l.js} +1 -1
  21. package/packages/web/dist/assets/{chunk-AQP2D5EJ-DVowfDYg.js → chunk-AQP2D5EJ-rfBNpUwD.js} +1 -1
  22. package/packages/web/dist/assets/{chunk-FMBD7UC4-zeHzmwLI.js → chunk-FMBD7UC4-CnL0tA20.js} +1 -1
  23. package/packages/web/dist/assets/{chunk-ND2GUHAM-Pcd02Wpz.js → chunk-ND2GUHAM-CZGi4swC.js} +1 -1
  24. package/packages/web/dist/assets/{chunk-QZHKN3VN-yxlVeu8W.js → chunk-QZHKN3VN-Du4WKuCj.js} +1 -1
  25. package/packages/web/dist/assets/classDiagram-4FO5ZUOK-sIDgFl-L.js +2 -0
  26. package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-sIDgFl-L.js +2 -0
  27. package/packages/web/dist/assets/{core-CSvc61jo.js → core-LJ--cHnT.js} +1 -1
  28. package/packages/web/dist/assets/{cose-bilkent-S5V4N54A-6MJqrbdr.js → cose-bilkent-S5V4N54A-CEovKrYZ.js} +1 -1
  29. package/packages/web/dist/assets/{dagre-BM42HDAG-B3pDB8_y.js → dagre-BM42HDAG-jQhK8147.js} +1 -1
  30. package/packages/web/dist/assets/{diagram-2AECGRRQ-BJA5EERJ.js → diagram-2AECGRRQ-BmiauiWQ.js} +1 -1
  31. package/packages/web/dist/assets/{diagram-5GNKFQAL-CcyfxhVS.js → diagram-5GNKFQAL-VYdTkqGm.js} +1 -1
  32. package/packages/web/dist/assets/{diagram-KO2AKTUF-CPNyNU1j.js → diagram-KO2AKTUF-DcOTKmwo.js} +1 -1
  33. package/packages/web/dist/assets/{diagram-LMA3HP47-Q4klJqX8.js → diagram-LMA3HP47-DaehIKcD.js} +1 -1
  34. package/packages/web/dist/assets/{diagram-OG6HWLK6-D0ZBTRoA.js → diagram-OG6HWLK6-DTqk6D4N.js} +1 -1
  35. package/packages/web/dist/assets/{erDiagram-TEJ5UH35-DQrCT17u.js → erDiagram-TEJ5UH35-Dj4rI8M1.js} +1 -1
  36. package/packages/web/dist/assets/{flowDiagram-I6XJVG4X-CK7nM7wz.js → flowDiagram-I6XJVG4X-D0FWSohH.js} +1 -1
  37. package/packages/web/dist/assets/{format-GJRFyZIp.js → format-CgkZTf5F.js} +1 -1
  38. package/packages/web/dist/assets/{ganttDiagram-6RSMTGT7-CFsNGWGG.js → ganttDiagram-6RSMTGT7-DgaRh12R.js} +1 -1
  39. package/packages/web/dist/assets/{gitGraphDiagram-PVQCEYII-xP0bbv60.js → gitGraphDiagram-PVQCEYII-5qiry4ao.js} +1 -1
  40. package/packages/web/dist/assets/{index-CDiCRIso.js → index-CcYeBUNk.js} +2 -2
  41. package/packages/web/dist/assets/{infoDiagram-5YYISTIA-BaKZDvMw.js → infoDiagram-5YYISTIA-CZmDwouw.js} +1 -1
  42. package/packages/web/dist/assets/{ishikawaDiagram-YF4QCWOH-Dr0LK4iy.js → ishikawaDiagram-YF4QCWOH-knkHstDa.js} +1 -1
  43. package/packages/web/dist/assets/{journeyDiagram-JHISSGLW-CxusdThR.js → journeyDiagram-JHISSGLW-ByfdmIVy.js} +1 -1
  44. package/packages/web/dist/assets/{kanban-definition-UN3LZRKU-CJYlFeaB.js → kanban-definition-UN3LZRKU-DSzly2xb.js} +1 -1
  45. package/packages/web/dist/assets/{linear-D0XMlvYu.js → linear-BX6qYIpB.js} +1 -1
  46. package/packages/web/dist/assets/{mermaid.core-CcPpo_y5.js → mermaid.core-BXPz6_-1.js} +4 -4
  47. package/packages/web/dist/assets/{mindmap-definition-RKZ34NQL-w064Pb6X.js → mindmap-definition-RKZ34NQL-fzfZWy91.js} +1 -1
  48. package/packages/web/dist/assets/{pieDiagram-4H26LBE5-D_qwlox1.js → pieDiagram-4H26LBE5-M99j1K9t.js} +1 -1
  49. package/packages/web/dist/assets/{quadrantDiagram-W4KKPZXB-BuMUhsEZ.js → quadrantDiagram-W4KKPZXB-B7fKq1ce.js} +1 -1
  50. package/packages/web/dist/assets/{requirementDiagram-4Y6WPE33-Bp1BqfXY.js → requirementDiagram-4Y6WPE33-xCBFrXav.js} +1 -1
  51. package/packages/web/dist/assets/{sankeyDiagram-5OEKKPKP-BQR4HfjD.js → sankeyDiagram-5OEKKPKP-Dp9KLMhd.js} +1 -1
  52. package/packages/web/dist/assets/{sequenceDiagram-3UESZ5HK-DIAHV11i.js → sequenceDiagram-3UESZ5HK-CNNVWpta.js} +1 -1
  53. package/packages/web/dist/assets/{stateDiagram-AJRCARHV-BHMJl8rE.js → stateDiagram-AJRCARHV-CzKRXREb.js} +1 -1
  54. package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-LKJwobEd.js +2 -0
  55. package/packages/web/dist/assets/{timeline-definition-PNZ67QCA-CB5sHevP.js → timeline-definition-PNZ67QCA-DfvQTe5J.js} +1 -1
  56. package/packages/web/dist/assets/{vennDiagram-CIIHVFJN-BeS1AXva.js → vennDiagram-CIIHVFJN-2PBM01eq.js} +1 -1
  57. package/packages/web/dist/assets/{wardley-L42UT6IY-CJ9C5W6N.js → wardley-L42UT6IY-DC1EIjci.js} +1 -1
  58. package/packages/web/dist/assets/{wardleyDiagram-YWT4CUSO-Bf75XvJ_.js → wardleyDiagram-YWT4CUSO-4kYoKkfS.js} +1 -1
  59. package/packages/web/dist/assets/{xychartDiagram-2RQKCTM6-0Qiya8rY.js → xychartDiagram-2RQKCTM6-Bin8O1V8.js} +1 -1
  60. package/packages/web/dist/index.html +1 -1
  61. package/packages/web/dist/sw.js +1 -1
  62. package/packages/web/dist/assets/channel-BHzPWerJ.js +0 -2
  63. package/packages/web/dist/assets/classDiagram-4FO5ZUOK-hNchnAu0.js +0 -2
  64. package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-hNchnAu0.js +0 -2
  65. package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-CScn8fuQ.js +0 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edspencer/paddock",
3
- "version": "0.58.0",
3
+ "version": "0.59.0",
4
4
  "description": "Project-first launchpad for Claude Code: server-hosted, persistent, resumable sessions organized by project.",
5
5
  "keywords": [
6
6
  "claude",
@@ -0,0 +1,25 @@
1
+ /** Directory name that both stores `--here` state and marks the workspace. */
2
+ export declare const HERE_MARKER = ".paddock";
3
+ /** Has this directory been opened with `--here` before? */
4
+ export declare function isHereWorkspace(dir: string): boolean;
5
+ /**
6
+ * How many of `dir`'s existing Claude Code sessions are worth offering.
7
+ *
8
+ * **`encodePathForCli`, not `transcripts.ts`'s `encodeProjectDir`.** The latter
9
+ * omits Claude Code's truncate-and-hash branch for paths over 200 characters,
10
+ * on the explicit assumption that "paddock's project dirs are always short".
11
+ * An arbitrary user directory is exactly where that assumption fails.
12
+ *
13
+ * `MIN_TRANSCRIPT_BYTES` is shared with `AdoptableIndex` so the number quoted
14
+ * here matches what the UI will actually offer to import — a count that
15
+ * disagreed with the import screen would be worse than no count.
16
+ */
17
+ export declare function countClaudeSessions(claudeHome: string, dir: string): number;
18
+ /**
19
+ * Add `entry` to `dir`'s `.gitignore` if no equivalent line is already there.
20
+ *
21
+ * Mirrors `ProjectStore.ensureRootGitignore`: read, tolerate the equivalent
22
+ * forms, append rather than overwrite. `--here` runs inside somebody's real
23
+ * repository, so clobbering their `.gitignore` is not an option.
24
+ */
25
+ export declare function ensureGitignored(dir: string, entry: string): void;
@@ -0,0 +1,104 @@
1
+ /**
2
+ * `--here` support: opening the user's own directory as a Paddock workspace (#640).
3
+ *
4
+ * ## Why this needs no new concept
5
+ *
6
+ * A *project* cannot live outside `projectsRoot` — `dirFor` joins the key onto
7
+ * the root with no branch, deliberately. But the **root workspace** is a
8
+ * different thing: its key is `""`, so `dirFor("")` resolves to `projectsRoot`
9
+ * itself, and `projectsRoot` is user-configurable. Pointing
10
+ * `PADDOCK_PROJECTS_DIR` at a directory therefore makes that directory the root
11
+ * workspace, named after its own basename, with no schema change anywhere.
12
+ *
13
+ * Session adoption then works by construction: `AdoptableIndex`'s notebook
14
+ * branch matches on exact `cwd === project.workingDir` equality, and in this
15
+ * mode the workspace's working directory IS the user's cwd.
16
+ *
17
+ * ## Why the data dir goes inside the directory
18
+ *
19
+ * `--here` puts state in `<dir>/.paddock` rather than the shared `~/.paddock`.
20
+ * Two reasons, and the second is the important one:
21
+ *
22
+ * 1. **Self-containment.** Opening directory A and later directory B must not
23
+ * have them share one job store, herdctl config and sidecar set — that is
24
+ * how A's run history leaks into B's UI.
25
+ * 2. **It gives us an unambiguous marker.** A second, flagless run in the same
26
+ * directory should resume it (the `git` model: `--here` is `git init`,
27
+ * `.paddock/` is `.git`). Keying that off `project.yaml` would be ambiguous
28
+ * — that filename already exists in the wild, including in every project
29
+ * directory of the `edspencer/projects` notes repo — whereas `.paddock/` is
30
+ * ours and its presence means exactly one thing.
31
+ */
32
+ import fs from "node:fs";
33
+ import path from "node:path";
34
+ import { encodePathForCli } from "@herdctl/core";
35
+ import { MIN_TRANSCRIPT_BYTES } from "../adoptable.js";
36
+ /** Directory name that both stores `--here` state and marks the workspace. */
37
+ export const HERE_MARKER = ".paddock";
38
+ /** Has this directory been opened with `--here` before? */
39
+ export function isHereWorkspace(dir) {
40
+ try {
41
+ return fs.statSync(path.join(dir, HERE_MARKER)).isDirectory();
42
+ }
43
+ catch {
44
+ return false;
45
+ }
46
+ }
47
+ /**
48
+ * How many of `dir`'s existing Claude Code sessions are worth offering.
49
+ *
50
+ * **`encodePathForCli`, not `transcripts.ts`'s `encodeProjectDir`.** The latter
51
+ * omits Claude Code's truncate-and-hash branch for paths over 200 characters,
52
+ * on the explicit assumption that "paddock's project dirs are always short".
53
+ * An arbitrary user directory is exactly where that assumption fails.
54
+ *
55
+ * `MIN_TRANSCRIPT_BYTES` is shared with `AdoptableIndex` so the number quoted
56
+ * here matches what the UI will actually offer to import — a count that
57
+ * disagreed with the import screen would be worse than no count.
58
+ */
59
+ export function countClaudeSessions(claudeHome, dir) {
60
+ const folder = path.join(claudeHome, "projects", encodePathForCli(dir));
61
+ let entries;
62
+ try {
63
+ entries = fs.readdirSync(folder);
64
+ }
65
+ catch {
66
+ return 0;
67
+ }
68
+ let n = 0;
69
+ for (const entry of entries) {
70
+ if (!entry.endsWith(".jsonl"))
71
+ continue;
72
+ try {
73
+ if (fs.statSync(path.join(folder, entry)).size >= MIN_TRANSCRIPT_BYTES)
74
+ n++;
75
+ }
76
+ catch {
77
+ /* raced or unreadable — not offerable either way */
78
+ }
79
+ }
80
+ return n;
81
+ }
82
+ /**
83
+ * Add `entry` to `dir`'s `.gitignore` if no equivalent line is already there.
84
+ *
85
+ * Mirrors `ProjectStore.ensureRootGitignore`: read, tolerate the equivalent
86
+ * forms, append rather than overwrite. `--here` runs inside somebody's real
87
+ * repository, so clobbering their `.gitignore` is not an option.
88
+ */
89
+ export function ensureGitignored(dir, entry) {
90
+ const file = path.join(dir, ".gitignore");
91
+ let existing = "";
92
+ try {
93
+ existing = fs.readFileSync(file, "utf8");
94
+ }
95
+ catch {
96
+ /* no .gitignore yet */
97
+ }
98
+ const bare = entry.replace(/^\/|\/$/g, "");
99
+ const have = new Set(existing.split("\n").map((l) => l.trim()));
100
+ if (have.has(`/${bare}/`) || have.has(`${bare}/`) || have.has(bare))
101
+ return;
102
+ const body = existing && !existing.endsWith("\n") ? `${existing}\n` : existing;
103
+ fs.writeFileSync(file, `${body}${entry}\n`, "utf8");
104
+ }
@@ -3,6 +3,7 @@ export interface CliOptions {
3
3
  port?: string;
4
4
  host?: string;
5
5
  dataDir?: string;
6
+ here: boolean;
6
7
  open: boolean;
7
8
  verbose: boolean;
8
9
  help: boolean;
@@ -23,6 +23,7 @@ import os from "node:os";
23
23
  import path from "node:path";
24
24
  import { spawn } from "node:child_process";
25
25
  import { fileURLToPath, pathToFileURL } from "node:url";
26
+ import { HERE_MARKER, isHereWorkspace, countClaudeSessions, ensureGitignored } from "./here.js";
26
27
  const MIN_NODE_MAJOR = 22;
27
28
  /** This module's own directory: `<…>/packages/server/dist/cli`. */
28
29
  const moduleDir = path.dirname(fileURLToPath(import.meta.url));
@@ -56,7 +57,13 @@ const packageRoot = findUp("package.json") ?? path.resolve(moduleDir, "../..");
56
57
  export class CliError extends Error {
57
58
  }
58
59
  export function parseArgs(argv) {
59
- const opts = { open: false, verbose: false, help: false, version: false };
60
+ const opts = {
61
+ here: false,
62
+ open: false,
63
+ verbose: false,
64
+ help: false,
65
+ version: false,
66
+ };
60
67
  for (let i = 0; i < argv.length; i++) {
61
68
  const arg = argv[i];
62
69
  const next = () => {
@@ -77,6 +84,9 @@ export function parseArgs(argv) {
77
84
  case "--data-dir":
78
85
  opts.dataDir = next();
79
86
  break;
87
+ case "--here":
88
+ opts.here = true;
89
+ break;
80
90
  case "-o":
81
91
  case "--open":
82
92
  opts.open = true;
@@ -120,11 +130,24 @@ Options
120
130
  -p, --port <port> HTTP/WS port (default 4000, or $PORT)
121
131
  --host <host> Bind address (default 127.0.0.1)
122
132
  -d, --data-dir <path> Projects + state (default ~/.paddock, or $PADDOCK_DATA_DIR)
133
+ --here Open the CURRENT directory as the workspace (see below)
123
134
  -o, --open Open the app in your browser once it is listening
124
135
  --verbose Show the server's own logs (quiet by default)
125
136
  -v, --version Print the Paddock version and exit
126
137
  -h, --help Show this help
127
138
 
139
+ Opening a directory (--here)
140
+ Run \`paddock --here\` inside a project and Paddock opens THAT directory as its
141
+ workspace: Claude works in your files, and any Claude Code sessions you already
142
+ have for the directory are offered for import. It:
143
+
144
+ · creates .paddock/ in the directory for this workspace's own state
145
+ · creates .chats/ for transcripts, and adds both to .gitignore
146
+ · links ~/.claude/projects/<encoded-dir> at this workspace
147
+
148
+ Once done, later runs in the same directory resume it — no flag needed.
149
+ Without --here, Paddock never touches the directory you ran it from.
150
+
128
151
  Credentials
129
152
  Paddock drives Claude Code, so it needs Claude credentials in the
130
153
  environment: CLAUDE_CODE_OAUTH_TOKEN (Max/Pro) or ANTHROPIC_API_KEY (API
@@ -225,6 +248,48 @@ function noteFirstRun(dataDir) {
225
248
  "",
226
249
  ].join("\n"));
227
250
  }
251
+ /**
252
+ * Say what `--here` is about to do to this directory, on the run that does it.
253
+ *
254
+ * The flag IS the consent — there is no prompt, and no `--yes` to remember.
255
+ * But consent only means something if you can know what you agreed to, so the
256
+ * effects are announced rather than asked about. Printed only on the first
257
+ * `--here` in a directory; resuming an already-opened one says nothing.
258
+ */
259
+ function announceHereConsent(dir, dataDir) {
260
+ console.log([
261
+ "",
262
+ ` Opening ${dir} as a Paddock workspace.`,
263
+ ` · ${path.relative(dir, dataDir) || HERE_MARKER}/ and .chats/ created here, and added to .gitignore`,
264
+ " · ~/.claude sessions for this directory are linked at the workspace",
265
+ " Later runs here resume it — no flag needed.",
266
+ "",
267
+ ].join("\n"));
268
+ }
269
+ /**
270
+ * Tell the user their existing Claude history could be opened here.
271
+ *
272
+ * The only way anyone discovers `--here` exists. Read-only: a bare run counts
273
+ * transcripts and prints, and changes nothing on disk.
274
+ */
275
+ function offerHereIfSessionsExist(dir) {
276
+ const claudeHome = process.env.CLAUDE_HOME ?? path.join(os.homedir(), ".claude");
277
+ let sessions = 0;
278
+ try {
279
+ sessions = countClaudeSessions(claudeHome, dir);
280
+ }
281
+ catch {
282
+ return; // detection is a courtesy; never let it break a boot
283
+ }
284
+ if (sessions === 0)
285
+ return;
286
+ console.log([
287
+ "",
288
+ ` This directory has ${sessions} Claude Code session${sessions === 1 ? "" : "s"}.`,
289
+ " Run `paddock --here` to open it as a workspace and import them.",
290
+ "",
291
+ ].join("\n"));
292
+ }
228
293
  /** Open `url` in the default browser. Best-effort: failing is never fatal. */
229
294
  function openBrowser(url) {
230
295
  const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
@@ -285,12 +350,44 @@ async function main() {
285
350
  if (problem !== undefined)
286
351
  fail(problem);
287
352
  addBundledBinsToPath();
353
+ // Canonical, because working directories "MUST be canonical so session
354
+ // discovery (which encodes the real path) can find Claude transcripts"
355
+ // (config.ts). On macOS a /tmp vs /private/tmp divergence would silently
356
+ // break adoption — and macOS is most laptops.
357
+ let cwd = process.cwd();
358
+ try {
359
+ cwd = fs.realpathSync(cwd);
360
+ }
361
+ catch {
362
+ /* keep the literal path; the server will surface any real problem */
363
+ }
364
+ // `--here` is the consent. A flagless run in a directory ALREADY opened this
365
+ // way resumes it — the git model, where `--here` is `git init` and `.paddock/`
366
+ // is `.git`. A flagless run anywhere else touches nothing.
367
+ const resuming = !opts.here && isHereWorkspace(cwd);
368
+ const hereMode = opts.here || resuming;
288
369
  // Apply CLI defaults as env vars. This is the whole integration surface: the
289
370
  // server resolves config inside `buildApp()`, so anything set before the
290
371
  // dynamic import below is picked up with no special-casing in config.ts.
291
372
  // Precedence is explicit flag > existing env > our default.
292
- const dataDir = path.resolve(opts.dataDir ?? process.env.PADDOCK_DATA_DIR ?? path.join(os.homedir(), ".paddock"));
293
- noteFirstRun(dataDir);
373
+ const dataDir = path.resolve(opts.dataDir ??
374
+ process.env.PADDOCK_DATA_DIR ??
375
+ (hereMode ? path.join(cwd, HERE_MARKER) : path.join(os.homedir(), ".paddock")));
376
+ if (hereMode) {
377
+ if (!resuming)
378
+ announceHereConsent(cwd, dataDir);
379
+ // The root workspace IS `projectsRoot` (its key is ""), so pointing this at
380
+ // the user's directory makes that directory the workspace. No schema change,
381
+ // no new project type — see cli/here.ts.
382
+ process.env.PADDOCK_PROJECTS_DIR = cwd;
383
+ fs.mkdirSync(dataDir, { recursive: true });
384
+ ensureGitignored(cwd, `/${HERE_MARKER}/`);
385
+ ensureGitignored(cwd, "/.chats/");
386
+ }
387
+ else {
388
+ noteFirstRun(dataDir);
389
+ offerHereIfSessionsExist(cwd);
390
+ }
294
391
  process.env.PADDOCK_DATA_DIR = dataDir;
295
392
  if (opts.port)
296
393
  process.env.PORT = opts.port;
@@ -323,7 +420,16 @@ async function main() {
323
420
  fail(explainListenError(err, host, port));
324
421
  }
325
422
  const url = `http://${host}:${port}`;
326
- console.log(`\n Paddock is running at ${url}\n Data: ${dataDir}\n Press Ctrl-C to stop.\n`);
423
+ // ALWAYS name the workspace, not just in --here mode. Behaviour that varies
424
+ // with cwd is only safe if it is observable: "why is this instance different
425
+ // today" should be answerable by reading the two lines the tool just printed.
426
+ console.log([
427
+ "",
428
+ ` Paddock is running at ${url}`,
429
+ ` Workspace: ${hereMode ? cwd : dataDir}${resuming ? " (resumed)" : ""}`,
430
+ " Press Ctrl-C to stop.",
431
+ "",
432
+ ].join("\n"));
327
433
  if (opts.open)
328
434
  openBrowser(url);
329
435
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paddock/server",
3
- "version": "0.58.0",
3
+ "version": "0.59.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-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};
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-CcYeBUNk.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