@edspencer/paddock 0.61.0 → 0.62.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 (93) hide show
  1. package/README.md +36 -4
  2. package/package.json +1 -1
  3. package/packages/server/dist/agent-errors.d.ts +41 -0
  4. package/packages/server/dist/agent-errors.js +127 -0
  5. package/packages/server/dist/app.js +57 -9
  6. package/packages/server/dist/claude-credentials.d.ts +116 -0
  7. package/packages/server/dist/claude-credentials.js +103 -0
  8. package/packages/server/dist/claude-home.d.ts +164 -25
  9. package/packages/server/dist/claude-home.js +528 -77
  10. package/packages/server/dist/claude-instructions.d.ts +121 -0
  11. package/packages/server/dist/claude-instructions.js +114 -0
  12. package/packages/server/dist/claude-mcp.d.ts +146 -0
  13. package/packages/server/dist/claude-mcp.js +367 -0
  14. package/packages/server/dist/claude-settings.d.ts +159 -0
  15. package/packages/server/dist/claude-settings.js +159 -0
  16. package/packages/server/dist/cli/args.d.ts +1 -1
  17. package/packages/server/dist/cli/args.js +39 -5
  18. package/packages/server/dist/cli/paddock.js +30 -6
  19. package/packages/server/dist/config.d.ts +166 -30
  20. package/packages/server/dist/config.js +106 -26
  21. package/packages/server/dist/herdctl-agent-config.d.ts +11 -1
  22. package/packages/server/dist/herdctl-agent-config.js +53 -7
  23. package/packages/server/dist/herdctl-agent-names.d.ts +11 -0
  24. package/packages/server/dist/herdctl-agent-names.js +31 -0
  25. package/packages/server/dist/herdctl.d.ts +69 -13
  26. package/packages/server/dist/herdctl.js +70 -18
  27. package/packages/server/dist/instance-config.js +36 -0
  28. package/packages/server/dist/mcp-servers.d.ts +168 -0
  29. package/packages/server/dist/mcp-servers.js +305 -0
  30. package/packages/server/dist/routes/chats.js +10 -4
  31. package/packages/server/dist/sweep.js +13 -1
  32. package/packages/server/dist/transcripts.d.ts +62 -24
  33. package/packages/server/dist/transcripts.js +141 -28
  34. package/packages/server/package.json +1 -1
  35. package/packages/web/dist/assets/{InstanceConfigPage-CzMqTdUU.js → InstanceConfigPage-Ca905oey.js} +1 -1
  36. package/packages/web/dist/assets/{MarkdownRenderer-YSEZHWDe.js → MarkdownRenderer-ChlC1ezH.js} +1 -1
  37. package/packages/web/dist/assets/{ProjectRedirect-DLWxZbqK.js → ProjectRedirect-CslqbhUb.js} +1 -1
  38. package/packages/web/dist/assets/{ProjectView-BFX8ff2n.js → ProjectView-DkNdilGo.js} +3 -3
  39. package/packages/web/dist/assets/{ProjectsGrid-DLOeY_ww.js → ProjectsGrid-BrlvLc-Y.js} +1 -1
  40. package/packages/web/dist/assets/{arc-BwC1ik-b.js → arc-CGqCI5nQ.js} +1 -1
  41. package/packages/web/dist/assets/{architectureDiagram-3BPJPVTR-CsVvXQWw.js → architectureDiagram-3BPJPVTR-Cp4KTxE2.js} +1 -1
  42. package/packages/web/dist/assets/{blockDiagram-GPEHLZMM-DXQk2bIa.js → blockDiagram-GPEHLZMM-yFxaTv04.js} +1 -1
  43. package/packages/web/dist/assets/{c4Diagram-AAUBKEIU-DXiA26It.js → c4Diagram-AAUBKEIU-59lhWjRt.js} +1 -1
  44. package/packages/web/dist/assets/channel-CZs6Z1i5.js +2 -0
  45. package/packages/web/dist/assets/{chunk-2J33WTMH-Dt5nC02A.js → chunk-2J33WTMH-BrpXGnJg.js} +1 -1
  46. package/packages/web/dist/assets/{chunk-4BX2VUAB-DcjnWvl6.js → chunk-4BX2VUAB-UxBDb5pi.js} +1 -1
  47. package/packages/web/dist/assets/{chunk-55IACEB6-CRa3LkU4.js → chunk-55IACEB6-D4ZJyUHW.js} +1 -1
  48. package/packages/web/dist/assets/{chunk-727SXJPM-H3V-11qu.js → chunk-727SXJPM-DWdPj_8E.js} +1 -1
  49. package/packages/web/dist/assets/{chunk-AQP2D5EJ-DPCrENYV.js → chunk-AQP2D5EJ-BIpNbflY.js} +1 -1
  50. package/packages/web/dist/assets/{chunk-FMBD7UC4-C4UO0ZTc.js → chunk-FMBD7UC4-f6iJDfdI.js} +1 -1
  51. package/packages/web/dist/assets/{chunk-ND2GUHAM-BtI37CnI.js → chunk-ND2GUHAM-Bz3TKIJk.js} +1 -1
  52. package/packages/web/dist/assets/{chunk-QZHKN3VN-C5bTaK5j.js → chunk-QZHKN3VN-buQmkafK.js} +1 -1
  53. package/packages/web/dist/assets/classDiagram-4FO5ZUOK-CrbEUUCd.js +2 -0
  54. package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-CrbEUUCd.js +2 -0
  55. package/packages/web/dist/assets/{core-DB3cpm5N.js → core-TyFI6p0n.js} +1 -1
  56. package/packages/web/dist/assets/{cose-bilkent-S5V4N54A-q-cjTZO4.js → cose-bilkent-S5V4N54A-CIkdi0XF.js} +1 -1
  57. package/packages/web/dist/assets/{dagre-BM42HDAG-DYEV5HFo.js → dagre-BM42HDAG-BnLJCfNs.js} +1 -1
  58. package/packages/web/dist/assets/{diagram-2AECGRRQ-D16XCw78.js → diagram-2AECGRRQ-D0OqfDB9.js} +1 -1
  59. package/packages/web/dist/assets/{diagram-5GNKFQAL-CeztryYE.js → diagram-5GNKFQAL-DQ2spGrK.js} +1 -1
  60. package/packages/web/dist/assets/{diagram-KO2AKTUF-D5SLIMDS.js → diagram-KO2AKTUF-RDIVQ_S3.js} +1 -1
  61. package/packages/web/dist/assets/{diagram-LMA3HP47-Bw-sVPxM.js → diagram-LMA3HP47-DKoTfh2C.js} +1 -1
  62. package/packages/web/dist/assets/{diagram-OG6HWLK6-DsRMITgx.js → diagram-OG6HWLK6-6k0OuSAL.js} +1 -1
  63. package/packages/web/dist/assets/{erDiagram-TEJ5UH35-T8Qqc_ku.js → erDiagram-TEJ5UH35-B-oQaYAC.js} +1 -1
  64. package/packages/web/dist/assets/{flowDiagram-I6XJVG4X-Cyx47UWU.js → flowDiagram-I6XJVG4X-B7qpYe9C.js} +1 -1
  65. package/packages/web/dist/assets/{format-D2I0duZa.js → format-PHdEBNOi.js} +1 -1
  66. package/packages/web/dist/assets/{ganttDiagram-6RSMTGT7-DxYJmG9o.js → ganttDiagram-6RSMTGT7-SfBsqMmx.js} +1 -1
  67. package/packages/web/dist/assets/{gitGraphDiagram-PVQCEYII-COK5YOTD.js → gitGraphDiagram-PVQCEYII-CuM4Rl_i.js} +1 -1
  68. package/packages/web/dist/assets/{index-wBXQMkGI.js → index-D_1ogxg1.js} +2 -2
  69. package/packages/web/dist/assets/{infoDiagram-5YYISTIA-BamRN_Nn.js → infoDiagram-5YYISTIA-Dq-32Oj4.js} +1 -1
  70. package/packages/web/dist/assets/{ishikawaDiagram-YF4QCWOH-DuOHdpEH.js → ishikawaDiagram-YF4QCWOH-BLFHCj_o.js} +1 -1
  71. package/packages/web/dist/assets/{journeyDiagram-JHISSGLW-Bri7GKTW.js → journeyDiagram-JHISSGLW-CHP-Yu63.js} +1 -1
  72. package/packages/web/dist/assets/{kanban-definition-UN3LZRKU-BdkgUnzI.js → kanban-definition-UN3LZRKU-DRuehSqn.js} +1 -1
  73. package/packages/web/dist/assets/{linear-DOcNPCpB.js → linear-Bwz2vnH7.js} +1 -1
  74. package/packages/web/dist/assets/{mermaid.core-D9SbVBpN.js → mermaid.core-CeMMnCip.js} +4 -4
  75. package/packages/web/dist/assets/{mindmap-definition-RKZ34NQL-CVmLm7zn.js → mindmap-definition-RKZ34NQL-CEdqeoRW.js} +1 -1
  76. package/packages/web/dist/assets/{pieDiagram-4H26LBE5-D0N10eVU.js → pieDiagram-4H26LBE5-BdDXBx87.js} +1 -1
  77. package/packages/web/dist/assets/{quadrantDiagram-W4KKPZXB-B5gnYdTa.js → quadrantDiagram-W4KKPZXB-Bovd1Zhx.js} +1 -1
  78. package/packages/web/dist/assets/{requirementDiagram-4Y6WPE33-CwtxezNZ.js → requirementDiagram-4Y6WPE33-DiyrKnJ2.js} +1 -1
  79. package/packages/web/dist/assets/{sankeyDiagram-5OEKKPKP-D79O3cux.js → sankeyDiagram-5OEKKPKP-BuX7TRVw.js} +1 -1
  80. package/packages/web/dist/assets/{sequenceDiagram-3UESZ5HK-xhKcI6tI.js → sequenceDiagram-3UESZ5HK-BjetZWJo.js} +1 -1
  81. package/packages/web/dist/assets/{stateDiagram-AJRCARHV-DBYMAjcX.js → stateDiagram-AJRCARHV-4AUBV6dF.js} +1 -1
  82. package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-Cq84MOtb.js +2 -0
  83. package/packages/web/dist/assets/{timeline-definition-PNZ67QCA-e-MYF4lm.js → timeline-definition-PNZ67QCA-BpQ8g8Bm.js} +1 -1
  84. package/packages/web/dist/assets/{vennDiagram-CIIHVFJN-B8UsvRDM.js → vennDiagram-CIIHVFJN-Cj99BWER.js} +1 -1
  85. package/packages/web/dist/assets/{wardley-L42UT6IY-1ucACjTl.js → wardley-L42UT6IY-S7AGTOWw.js} +1 -1
  86. package/packages/web/dist/assets/{wardleyDiagram-YWT4CUSO-DJufLd_u.js → wardleyDiagram-YWT4CUSO-LNXkUA7S.js} +1 -1
  87. package/packages/web/dist/assets/{xychartDiagram-2RQKCTM6-qqaZNlu9.js → xychartDiagram-2RQKCTM6-DGdTh4Xk.js} +1 -1
  88. package/packages/web/dist/index.html +1 -1
  89. package/packages/web/dist/sw.js +1 -1
  90. package/packages/web/dist/assets/channel-DDgTXrGP.js +0 -2
  91. package/packages/web/dist/assets/classDiagram-4FO5ZUOK-DU1kd_Vu.js +0 -2
  92. package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-DU1kd_Vu.js +0 -2
  93. package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-CZtr9Jvq.js +0 -2
@@ -1,3 +1,14 @@
1
+ /**
2
+ * Whose transcripts this instance uses — the `claude.transcripts` config key.
3
+ *
4
+ * `own` = paddock's, inside the data dir / project. `host` = this machine's
5
+ * Claude Code, shared live. See the module doc for the mechanism.
6
+ */
7
+ export type TranscriptsMode = "own" | "host";
8
+ /** Isolation is the default: nothing outside the data dir is written (#691). */
9
+ export declare const DEFAULT_TRANSCRIPTS_MODE: TranscriptsMode;
10
+ /** Type guard, so an unknown config value falls back instead of failing a boot. */
11
+ export declare function isKnownTranscriptsMode(value: string): value is TranscriptsMode;
1
12
  /**
2
13
  * Encode a working directory the way Claude Code names its transcript dir.
3
14
  * For paths under 200 chars this is just the non-alphanumeric→'-' replacement
@@ -8,21 +19,21 @@ export declare function encodeProjectDir(dir: string): string;
8
19
  /** Absolute path to the project's local transcript directory. */
9
20
  export declare function projectChatsDir(projectDir: string): string;
10
21
  /**
11
- * Ensure `<chatsHostDir>/.chats/` exists and that Claude Code's encoded
12
- * transcript path for `workingDir` is a symlink pointing at it (migrating an
13
- * existing real transcript dir in the process). Safe to call on every agent
14
- * registration. Never throws — a failure here must not block agent registration
15
- * or chat.
22
+ * The transcript folder a Claude home holds for `workingDir` —
23
+ * `<home>/projects/<encoded-cwd>`.
16
24
  *
17
- * `workingDir` is the agent's cwd (which Claude encodes into the transcript dir
18
- * name); `chatsHostDir` is where the `.chats/` store actually lives. For a
19
- * notebook project these are the same dir (the default). For a REPO-BACKED
20
- * project (issue #187) the keeper's cwd is the nested checkout but the `.chats/`
21
- * store stays in the project's metadata dir (the data repo) — so transcripts
22
- * never pollute the external repo's working tree — hence the two are split.
25
+ * The LITERAL path, before any symlink is followed, which is what makes it worth
26
+ * naming: it is the string Claude Code builds its session file AND its agent
27
+ * memory dir (`<here>/memory`) out of, and the agent harness refuses to write to
28
+ * any path with a `.claude` component in it (#690). Under paddock's own home it
29
+ * never has one, in either {@link TranscriptsMode}.
30
+ */
31
+ export declare function encodedTranscriptDir(claudeHome: string, workingDir: string): string;
32
+ /**
33
+ * Where a project's transcripts are planted, and where they point.
23
34
  *
24
- * `home.path` is the Claude home to plant the symlink in, and it is REQUIRED —
25
- * no default (#620). Callers must pass `cfg.claudeHome`: the engine is
35
+ * `path` is the Claude home to plant the redirect symlink in, and it is REQUIRED
36
+ * — no default (#620). Callers must pass `cfg.claudeHome`: the engine is
26
37
  * constructed with that same resolved value, and a symlink planted in a
27
38
  * different home than the one the engine reads is exactly the "chats list but
28
39
  * open empty" failure (#588). It used to default to the env-derived home for
@@ -31,21 +42,48 @@ export declare function projectChatsDir(projectDir: string): string;
31
42
  * Making the parameter required is what stops that class of bug at the type
32
43
  * level.
33
44
  *
34
- * `home.owned` gates the one destructive branch (#620): migrating an existing
35
- * real transcript directory into `.chats/` copies the files and then REMOVES the
36
- * originals. That is correct self-healing inside a home paddock owns, where such
37
- * a directory can only be paddock's own doing. Inside the user's `~/.claude` it
38
- * is somebody else's data — a `claude` CLI session run in a directory paddock
39
- * happens to also manage — so we leave it strictly alone and let the SDK keep
40
- * writing there. Those transcripts stay importable through adoption (#588),
41
- * which is the deliberate, user-driven version of the same move.
45
+ * Since #691 paddock ALWAYS owns `path`, so the old `owned` flag is gone: the
46
+ * question it used to answer ("may we write here?") now has one answer, and the
47
+ * question that actually varies — whose transcripts these are — is
48
+ * {@link transcripts}.
42
49
  */
43
50
  export interface ClaudeHomeTarget {
44
- /** Absolute path to the Claude home to plant the redirect symlink in. */
51
+ /** Absolute path to paddock's own Claude home (`cfg.claudeHome`). */
45
52
  path: string;
46
- /** Whether paddock owns this home — see `PaddockConfig.ownsClaudeHome`. */
47
- owned: boolean;
53
+ /** Whose transcripts this instance uses (`cfg.claude.transcripts`). */
54
+ transcripts: TranscriptsMode;
55
+ /** The user's own `~/.claude` — the store under `transcripts: "host"`. */
56
+ userHome: string;
48
57
  }
58
+ /**
59
+ * Ensure the project's transcript store exists and that Claude Code's encoded
60
+ * transcript path for `workingDir` is a symlink pointing at it (migrating an
61
+ * existing real transcript dir in the process). Safe to call on every agent
62
+ * registration. Never throws — a failure here must not block agent registration
63
+ * or chat.
64
+ *
65
+ * `workingDir` is the agent's cwd (which Claude encodes into the transcript dir
66
+ * name); `chatsHostDir` is where the `.chats/` store actually lives. For a
67
+ * notebook project these are the same dir (the default). For a REPO-BACKED
68
+ * project (issue #187) the keeper's cwd is the nested checkout but the `.chats/`
69
+ * store stays in the project's metadata dir (the data repo) — so transcripts
70
+ * never pollute the external repo's working tree — hence the two are split.
71
+ *
72
+ * The store is `<chatsHostDir>/.chats/` under `transcripts: "own"` and the user's
73
+ * own `~/.claude/projects/<encoded-cwd>/` under `transcripts: "host"`; everything
74
+ * below is the same code either way.
75
+ *
76
+ * ## The one write into `~/.claude`, and why it is not the #682 mistake
77
+ *
78
+ * Under `host` this creates `~/.claude/projects/<encoded-cwd>/` if it is missing,
79
+ * because a symlink to a non-existent directory is not something Claude Code can
80
+ * `mkdir -p` through. That is a directory Claude Code itself would create the
81
+ * first time the user ran `claude` there, made under a config key whose entire
82
+ * meaning is "share the host's transcripts". #682 was the opposite: planting a
83
+ * link that silently REDIRECTED the user's future sessions into paddock's store
84
+ * on an instance that had asked for no such thing. Under `own` — the default —
85
+ * nothing under `~/.claude` is created, read or written at all.
86
+ */
49
87
  export declare function ensureProjectChats(workingDir: string, chatsHostDir: string, home: ClaudeHomeTarget): Promise<void>;
50
88
  /**
51
89
  * Read a session's FIRST user message text, untruncated, straight from its
@@ -1,23 +1,49 @@
1
1
  /**
2
- * Transcript relocation (backing-store Phase 3).
2
+ * Transcript relocation (backing-store Phase 3), and the `transcripts` lever (#691).
3
3
  *
4
4
  * Claude Code writes a session transcript to `<claudeHome>/projects/<encoded-cwd>/`,
5
- * where `<claudeHome>` is `CLAUDE_CONFIG_DIR` (paddock's own `<dataDir>/claude-home`
6
- * since #620) and `<encoded-cwd>` is the agent's absolute working directory with every
7
- * non-[A-Za-z0-9] char replaced by '-'. That makes transcripts path-coupled and
8
- * not portable. We relocate them INTO the project by making the encoded path a
9
- * **symlink to `<projectDir>/.chats/`** — Claude then writes through the symlink
10
- * into the project dir (validated empirically), and herdctl's discovery, resume,
11
- * delete and rename all resolve through it transparently. The project dir becomes
12
- * self-contained and travels with the repo / NAS.
5
+ * where `<claudeHome>` is `CLAUDE_CONFIG_DIR` — always paddock's own
6
+ * `<dataDir>/claude-home` since #691 — and `<encoded-cwd>` is the agent's absolute
7
+ * working directory with every non-[A-Za-z0-9] char replaced by '-'. That makes
8
+ * transcripts path-coupled and not portable, so paddock makes that encoded path a
9
+ * **symlink** and Claude Code writes through it (validated empirically); herdctl's
10
+ * discovery, resume, delete and rename all resolve through it transparently.
11
+ *
12
+ * What the symlink POINTS AT is the one thing {@link TranscriptsMode} chooses:
13
+ *
14
+ * - **`own`** (default) — `<projectDir>/.chats/`. Transcripts are paddock's own
15
+ * copy, the project dir is self-contained and travels with the repo / NAS.
16
+ * - **`host`** — `~/.claude/projects/<encoded-cwd>/`, the user's REAL Claude Code
17
+ * folder for that same directory. One set of files, both directions: an append
18
+ * made by `claude --resume` in a terminal shows up in paddock with no restart
19
+ * and no re-import, and vice versa.
20
+ *
21
+ * ## Why `host` is an outward symlink and not a repointed config dir
22
+ *
23
+ * The obvious implementation of "share the user's transcripts" is to point
24
+ * `CLAUDE_CONFIG_DIR` at `~/.claude`, which is what 0.61.1 did. It drags memory,
25
+ * `.claude.json` and the credential-store service name along as collateral — and
26
+ * it breaks agent memory (#690), because the agent harness refuses to write to any
27
+ * path containing a `.claude` component and the memory dir is
28
+ * `<claudeHome>/projects/<enc>/memory`. Keeping paddock's own home and pointing one
29
+ * symlink outward fixes that by construction: the LITERAL path handed to the agent
30
+ * is always under `<dataDir>/claude-home/…`, which has no `.claude` component,
31
+ * while the files it resolves to are the user's. Pinned by
32
+ * `test/unit/transcripts.test.ts` in BOTH modes — do not "simplify" this back.
13
33
  *
14
34
  * `ensureProjectChats` is idempotent and self-healing: on first run for a project
15
35
  * whose encoded path is still a real directory (existing transcripts), it migrates
16
- * those files into `.chats/` and replaces the directory with the symlink.
36
+ * those files into the store and replaces the directory with the symlink.
17
37
  */
18
38
  import { promises as fs, createReadStream } from "node:fs";
19
39
  import { createInterface } from "node:readline";
20
40
  import path from "node:path";
41
+ /** Isolation is the default: nothing outside the data dir is written (#691). */
42
+ export const DEFAULT_TRANSCRIPTS_MODE = "own";
43
+ /** Type guard, so an unknown config value falls back instead of failing a boot. */
44
+ export function isKnownTranscriptsMode(value) {
45
+ return value === "own" || value === "host";
46
+ }
21
47
  /**
22
48
  * Encode a working directory the way Claude Code names its transcript dir.
23
49
  * For paths under 200 chars this is just the non-alphanumeric→'-' replacement
@@ -31,36 +57,123 @@ export function encodeProjectDir(dir) {
31
57
  export function projectChatsDir(projectDir) {
32
58
  return path.join(projectDir, ".chats");
33
59
  }
60
+ /**
61
+ * The transcript folder a Claude home holds for `workingDir` —
62
+ * `<home>/projects/<encoded-cwd>`.
63
+ *
64
+ * The LITERAL path, before any symlink is followed, which is what makes it worth
65
+ * naming: it is the string Claude Code builds its session file AND its agent
66
+ * memory dir (`<here>/memory`) out of, and the agent harness refuses to write to
67
+ * any path with a `.claude` component in it (#690). Under paddock's own home it
68
+ * never has one, in either {@link TranscriptsMode}.
69
+ */
70
+ export function encodedTranscriptDir(claudeHome, workingDir) {
71
+ return path.join(claudeHome, "projects", encodeProjectDir(workingDir));
72
+ }
73
+ /**
74
+ * Point `<chatsHostDir>/.chats/` at the user's own transcript folder, so
75
+ * paddock's own by-path readers keep working under `transcripts: "host"`.
76
+ *
77
+ * NOT redundant with the redirect symlink, and not in #691's sketch — it is
78
+ * needed because roughly a dozen server modules (`subagents.ts`, `usage.ts`,
79
+ * `tooldetails.ts`, `localcommand.ts`, `recovery.ts`, `readFirstUserText`,
80
+ * the usage-limit notice scan) resolve a transcript as
81
+ * `<projectDir>/.chats/<sessionId>.jsonl` rather than through the Claude home.
82
+ * Without this the chat itself renders (herdctl reads through the home) while
83
+ * sub-agent panels, token usage, tool details, slash-command names and full
84
+ * previews all silently come back empty — which is what 0.61.1's `--here` does
85
+ * today. Both links point at the SAME real folder; nothing is chained.
86
+ *
87
+ * Non-destructive: a `.chats/` that is a real directory with anything in it is
88
+ * left exactly as it is (a mode switch does not get to delete a store), at the
89
+ * cost of those readers seeing the old copy until the operator moves it.
90
+ */
91
+ async function pointChatsDirAt(chatsDir, store) {
92
+ // The parent may not exist yet — `ensureSweeperHome` calls in with a
93
+ // `<dataDir>/sweepers/<slug>` that nothing has created. Under `own` the
94
+ // recursive mkdir of `.chats/` itself covered that; here the leaf is a symlink,
95
+ // and `fs.symlink` into a missing parent is an ENOENT that the caller's
96
+ // catch-all would swallow — silently leaving the agent with no redirect at all.
97
+ await fs.mkdir(path.dirname(chatsDir), { recursive: true });
98
+ const st = await fs.lstat(chatsDir).catch(() => null);
99
+ if (st?.isSymbolicLink()) {
100
+ const target = await fs.readlink(chatsDir).catch(() => "");
101
+ if (path.resolve(path.dirname(chatsDir), target) === path.resolve(store))
102
+ return;
103
+ await fs.rm(chatsDir, { force: true });
104
+ }
105
+ else if (st?.isDirectory()) {
106
+ if ((await fs.readdir(chatsDir).catch(() => ["?"])).length > 0)
107
+ return;
108
+ await fs.rmdir(chatsDir);
109
+ }
110
+ else if (st) {
111
+ return; // a regular file sitting on the name: not ours to remove
112
+ }
113
+ await fs.symlink(store, chatsDir);
114
+ }
115
+ /**
116
+ * Ensure the project's transcript store exists and that Claude Code's encoded
117
+ * transcript path for `workingDir` is a symlink pointing at it (migrating an
118
+ * existing real transcript dir in the process). Safe to call on every agent
119
+ * registration. Never throws — a failure here must not block agent registration
120
+ * or chat.
121
+ *
122
+ * `workingDir` is the agent's cwd (which Claude encodes into the transcript dir
123
+ * name); `chatsHostDir` is where the `.chats/` store actually lives. For a
124
+ * notebook project these are the same dir (the default). For a REPO-BACKED
125
+ * project (issue #187) the keeper's cwd is the nested checkout but the `.chats/`
126
+ * store stays in the project's metadata dir (the data repo) — so transcripts
127
+ * never pollute the external repo's working tree — hence the two are split.
128
+ *
129
+ * The store is `<chatsHostDir>/.chats/` under `transcripts: "own"` and the user's
130
+ * own `~/.claude/projects/<encoded-cwd>/` under `transcripts: "host"`; everything
131
+ * below is the same code either way.
132
+ *
133
+ * ## The one write into `~/.claude`, and why it is not the #682 mistake
134
+ *
135
+ * Under `host` this creates `~/.claude/projects/<encoded-cwd>/` if it is missing,
136
+ * because a symlink to a non-existent directory is not something Claude Code can
137
+ * `mkdir -p` through. That is a directory Claude Code itself would create the
138
+ * first time the user ran `claude` there, made under a config key whose entire
139
+ * meaning is "share the host's transcripts". #682 was the opposite: planting a
140
+ * link that silently REDIRECTED the user's future sessions into paddock's store
141
+ * on an instance that had asked for no such thing. Under `own` — the default —
142
+ * nothing under `~/.claude` is created, read or written at all.
143
+ */
34
144
  export async function ensureProjectChats(workingDir, chatsHostDir, home) {
35
- const claudeHomePath = home.path;
36
145
  try {
37
146
  const chatsDir = projectChatsDir(chatsHostDir);
38
- await fs.mkdir(chatsDir, { recursive: true });
39
- const encoded = path.join(claudeHomePath, "projects", encodeProjectDir(workingDir));
147
+ const store = home.transcripts === "host"
148
+ ? encodedTranscriptDir(home.userHome, workingDir)
149
+ : chatsDir;
150
+ await fs.mkdir(store, { recursive: true });
151
+ if (home.transcripts === "host")
152
+ await pointChatsDirAt(chatsDir, store);
153
+ const encoded = encodedTranscriptDir(home.path, workingDir);
40
154
  await fs.mkdir(path.dirname(encoded), { recursive: true });
41
155
  const st = await fs.lstat(encoded).catch(() => null);
42
- // Already a symlink — point it at .chats if it drifted, else done.
156
+ // Already a symlink — point it at the store if it drifted, else done. The
157
+ // drift case is also how a `transcripts` flip takes effect on the next boot.
43
158
  if (st?.isSymbolicLink()) {
44
159
  const target = await fs.readlink(encoded).catch(() => "");
45
160
  const resolved = path.resolve(path.dirname(encoded), target);
46
- if (resolved !== path.resolve(chatsDir)) {
161
+ if (resolved !== path.resolve(store)) {
47
162
  await fs.rm(encoded, { force: true });
48
- await fs.symlink(chatsDir, encoded);
163
+ await fs.symlink(store, encoded);
49
164
  }
50
165
  return;
51
166
  }
52
- // A real directory of existing transcripts. In a home paddock does NOT own
53
- // these are the user's own CLI transcripts (#620) — leave them exactly where
54
- // they are. The SDK keeps reading and appending to them, and adoption is how
55
- // the user pulls them into the project if they want to. Bailing here also
56
- // means we never plant a symlink over them.
57
- if (st?.isDirectory() && !home.owned)
58
- return;
59
- // A real directory of existing transcripts — migrate into .chats, then link.
167
+ // A real directory of existing transcripts — migrate into the store, then
168
+ // link. This is inside a home paddock owns, so such a directory can only be
169
+ // paddock's own doing: a boot where the symlink could not be planted, or an
170
+ // agent that ran before registration. (Pre-#691 this branch had to be gated
171
+ // on ownership, because the home could BE `~/.claude` and the files somebody
172
+ // else's. It cannot any more — see `resolveClaudeHome`.)
60
173
  if (st?.isDirectory()) {
61
174
  for (const entry of await fs.readdir(encoded)) {
62
175
  const from = path.join(encoded, entry);
63
- const to = path.join(chatsDir, entry);
176
+ const to = path.join(store, entry);
64
177
  if (await fs.lstat(to).then(() => true).catch(() => false))
65
178
  continue; // don't clobber
66
179
  // cp+rm is robust across filesystems (rename would EXDEV across mounts).
@@ -73,11 +186,11 @@ export async function ensureProjectChats(workingDir, chatsHostDir, home) {
73
186
  await fs.rm(from, { recursive: true, force: true });
74
187
  }
75
188
  await fs.rmdir(encoded).catch(() => undefined);
76
- await fs.symlink(chatsDir, encoded);
189
+ await fs.symlink(store, encoded);
77
190
  return;
78
191
  }
79
- // Nothing there yet — just create the symlink so future turns land in .chats.
80
- await fs.symlink(chatsDir, encoded);
192
+ // Nothing there yet — just create the symlink so future turns land in the store.
193
+ await fs.symlink(store, encoded);
81
194
  }
82
195
  catch {
83
196
  /* non-fatal: fall back to Claude Code's default location for this project */
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paddock/server",
3
- "version": "0.61.0",
3
+ "version": "0.62.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-wBXQMkGI.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-D_1ogxg1.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