@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
@@ -0,0 +1,159 @@
1
+ /**
2
+ * The `claude.hooks` lever (#691): whether this instance runs the host machine's
3
+ * Claude Code hooks.
4
+ *
5
+ * This is the one lever in the `claude:` block that is about code execution
6
+ * rather than data. A hook is a shell command Claude Code runs on `PreToolUse`,
7
+ * `PostToolUse`, `SessionStart` and friends, and since #620 paddock has symlinked
8
+ * the user's `~/.claude/settings.json` into its own home unconditionally — so
9
+ * every hook the user has ever configured fires inside every paddock turn, with
10
+ * no key to turn it off. "Isolate it by just trying paddock" was not true, and
11
+ * this is why.
12
+ *
13
+ * ## Why this cannot be a symlink decision
14
+ *
15
+ * `settings.json` is a mixed bag. It carries `hooks`, and also `permissions`,
16
+ * `model`, `statusLine`, `enabledPlugins`, `env`, `outputStyle` — the things a
17
+ * user configures once and expects everywhere. A symlink is all-or-nothing and
18
+ * the file is not, so `hooks: own` has to leave the rest inherited while dropping
19
+ * one key.
20
+ *
21
+ * So paddock WRITES `<ownHome>/settings.json` itself: parse the user's, drop
22
+ * {@link HOST_ONLY_SETTINGS_KEYS}, serialise the remainder. `hooks: host` is the
23
+ * old symlink, unchanged.
24
+ *
25
+ * ### The staleness this buys, which is real
26
+ *
27
+ * A copy diverges the moment the user edits theirs; a symlink does not — and
28
+ * **that difference is live, not just at the next process start.** Claude Code
29
+ * runs a settings-change watcher (~1s poll + 500ms debounce) that follows the
30
+ * symlink to its real target, clears its whole settings memo and re-applies the
31
+ * result mid-session; verified by mutating a symlink target under a running
32
+ * `query()` and watching `Detected change to … (via symlink target …)` come back.
33
+ * So the honest statement is that a symlink propagates an edit in about a second
34
+ * and the copy propagates it at the next paddock restart. Two things make that
35
+ * an acceptable price rather than a design flaw:
36
+ *
37
+ * 1. **Only users who actually have hooks pay it.** {@link planHostSettings}
38
+ * returns `link` when the user's file has none of the governed keys, which is
39
+ * most files — nothing to filter, so nothing is copied and there is nothing to
40
+ * go stale. The copy exists exactly where filtering is doing work.
41
+ * 2. **It is regenerated on every boot** (`ensureClaudeHome`), so a restart is
42
+ * the whole of the fix, and paddock says so in the boot notice.
43
+ *
44
+ * A file-watcher on the user's file, re-running the filter, would close the gap
45
+ * — and because Claude Code's own watcher fires on content change regardless of
46
+ * who wrote it, paddock re-writing the copy WOULD be picked up live. That is a
47
+ * real option if the cadence ever matters. It is not taken here: it puts a
48
+ * watcher on a path in the user's home, races paddock's own write against the
49
+ * 5s echo-suppression window Claude Code uses for exactly this, and buys a
50
+ * narrower window on a file people edit a handful of times a year.
51
+ *
52
+ * ### Which turns this actually governs today, which is not all of them
53
+ *
54
+ * `<claudeHome>/settings.json` is Claude Code's `userSettings` source, and
55
+ * **herdctl does not load that source on the SDK runtime.** `sdk-adapter.js`
56
+ * defaults `settingSources` to `["project"]` for any agent with a
57
+ * `working_directory` — which is every paddock keeper — so the SDK is invoked
58
+ * with `--setting-sources=project` and never reads this file at all. The CLI
59
+ * runtime passes `--setting-sources` only when an agent declares
60
+ * `setting_sources`, and paddock declares none, so `claude -p` runs on the
61
+ * default `["user","project","local"]` and DOES read it.
62
+ *
63
+ * So the host's hooks execute in the sweeper, in triggers, and in
64
+ * `driveMode: batch` chats — and not, today, in a default SDK chat turn. That
65
+ * makes this lever narrower than #691 assumed, and worth keeping regardless:
66
+ * those paths are real code execution, the asymmetry is a herdctl default rather
67
+ * than a guarantee, and a lever that is correct before the default changes is
68
+ * the only kind worth having. The same caveat applies to the keys it KEEPS —
69
+ * the user's `permissions` and `model` reach the CLI paths and not the SDK ones,
70
+ * which was true before this change too.
71
+ *
72
+ * ## What `own` does NOT stop, and should
73
+ *
74
+ * `hooks` is not the only key in `settings.json` that names a command to run.
75
+ * Read straight out of the SDK's own settings schema
76
+ * (`@anthropic-ai/claude-agent-sdk/sdk.mjs`), these do too:
77
+ *
78
+ * - `apiKeyHelper` — "Path to a script that outputs authentication values"
79
+ * - `awsCredentialExport` — "Path to a script that exports AWS credentials"
80
+ * - `awsAuthRefresh` — "Path to a script that refreshes AWS authentication"
81
+ * - `gcpAuthRefresh` — "Command to refresh GCP authentication"
82
+ * - `proxyAuthHelper` — "Shell command that outputs a Proxy-Authorization header"
83
+ * - `otelHeadersHelper` — "Path to a script that outputs OpenTelemetry headers"
84
+ * - `statusLine`, `subagentStatusLine`, `fileSuggestion` — `{type:"command", …}`
85
+ *
86
+ * #691 scopes this lever to `hooks`, so `hooks` is what {@link
87
+ * HOST_ONLY_SETTINGS_KEYS} contains — widening a default-on security lever past
88
+ * its brief is the maintainer's call, not this module's. It is one line when he
89
+ * makes it, and the trade-off is not obvious in either direction: the first five
90
+ * of those are how a user's login WORKS on a corporate setup, so dropping them
91
+ * under a key named `hooks` would break authentication for exactly the people who
92
+ * did nothing wrong — an argument that they belong under `claude.credentials`
93
+ * rather than here. Until someone decides, `hooks: own` means "no host hooks",
94
+ * not "no host commands", and this comment is the difference.
95
+ */
96
+ /**
97
+ * Isolation is the default, and here it is the only defensible one: `host` means
98
+ * arbitrary commands from outside the data dir execute inside paddock's turns,
99
+ * and that is not something a user should opt out of after the fact.
100
+ */
101
+ export const DEFAULT_HOOKS_MODE = "own";
102
+ /** Type guard, so an unknown config value falls back instead of failing a boot. */
103
+ export function isKnownHooksMode(value) {
104
+ return value === "own" || value === "host";
105
+ }
106
+ /** The Claude home entry this lever filters. */
107
+ export const SETTINGS_ENTRY = "settings.json";
108
+ /**
109
+ * Keys of `settings.json` that `hooks: own` drops. See the module doc for the
110
+ * several others that arguably belong here and are deliberately not.
111
+ */
112
+ export const HOST_ONLY_SETTINGS_KEYS = ["hooks"];
113
+ /**
114
+ * Where paddock records the SHA-256 of each file it generated in its own home,
115
+ * so a later boot can tell its own output from something a human put there.
116
+ *
117
+ * A marker key inside `settings.json` would need no second file, but the schema
118
+ * is Claude Code's and adding a key to it is asking for a validation warning on
119
+ * some future version. A hash sidecar is inert by construction.
120
+ */
121
+ export const GENERATED_MARKER_FILE = ".paddock-generated.json";
122
+ /**
123
+ * Decide what paddock's own `settings.json` should be, given the user's.
124
+ *
125
+ * Pure, so the whole decision is testable without a filesystem.
126
+ *
127
+ * The `skip` case is the security-relevant one. A `settings.json` that will not
128
+ * parse cannot be filtered, and the safe response is to plant NOTHING rather than
129
+ * fall back to a symlink — a symlink would hand over the hooks this lever exists
130
+ * to withhold, and it would do it precisely when something is already wrong.
131
+ * Claude Code will not be able to read that file either, so the instance loses
132
+ * nothing it would otherwise have had.
133
+ */
134
+ export function planHostSettings(mode, raw) {
135
+ if (mode === "host")
136
+ return { action: "link" };
137
+ let parsed;
138
+ try {
139
+ parsed = JSON.parse(raw);
140
+ }
141
+ catch (err) {
142
+ return { action: "skip", reason: `it is not valid JSON (${String(err)})` };
143
+ }
144
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
145
+ return { action: "skip", reason: "its top level is not a JSON object" };
146
+ }
147
+ const settings = parsed;
148
+ const dropped = HOST_ONLY_SETTINGS_KEYS.filter((k) => k in settings);
149
+ if (dropped.length === 0)
150
+ return { action: "link" };
151
+ const kept = {};
152
+ // Insertion order preserved, minus the dropped keys — a user comparing the two
153
+ // files should see a deletion, not a reshuffle.
154
+ for (const [k, v] of Object.entries(settings)) {
155
+ if (!dropped.includes(k))
156
+ kept[k] = v;
157
+ }
158
+ return { action: "write", content: `${JSON.stringify(kept, null, 2)}\n`, dropped: [...dropped] };
159
+ }
@@ -53,4 +53,4 @@ export declare function nodeVersionProblem(nodeVersion: string): string | undefi
53
53
  * flag that fixes it.
54
54
  */
55
55
  export declare function explainListenError(err: unknown, host: string, port: string): string;
56
- export declare const USAGE = "paddock \u2014 run a Paddock instance locally\n\nUsage\n npx @edspencer/paddock [options]\n\nOptions\n -p, --port <port> HTTP/WS port (default 4000, or $PORT)\n --host <host> Bind address (default 127.0.0.1)\n -d, --data-dir <path> Projects + state (default ~/.paddock, or $PADDOCK_DATA_DIR)\n --here Open the CURRENT directory as the workspace (see below)\n -o, --open Open the app in your browser once it is listening\n --verbose Show the server's own logs (quiet by default)\n -v, --version Print the Paddock version and exit\n -h, --help Show this help\n\nOpening a directory (--here)\n Run `paddock --here` inside a project and Paddock opens THAT directory as its\n workspace: Claude works in your files, and any Claude Code sessions you already\n have for the directory are offered for import. It:\n\n \u00B7 creates .paddock/ in the directory for this workspace's own state\n \u00B7 creates .chats/ for transcripts, and adds both to .gitignore\n \u00B7 links ~/.claude/projects/<encoded-dir> at this workspace\n\n Once done, later runs in the same directory resume it \u2014 no flag needed.\n Without --here, Paddock never touches the directory you ran it from.\n\nCredentials\n Paddock drives Claude Code, so it needs Claude credentials in the\n environment: CLAUDE_CODE_OAUTH_TOKEN (Max/Pro) or ANTHROPIC_API_KEY (API\n billing). If you already use Claude Code on this machine, an existing login\n under ~/.claude is picked up automatically. Otherwise run `claude setup-token`.\n\nYour data\n Everything lives in one directory \u2014 ~/.paddock unless you pass --data-dir.\n Projects, chat transcripts and settings all persist there between runs. Move\n that directory to move your instance; delete it to start over. Nothing is\n stored anywhere else.\n\nNotes\n Binds loopback with authentication disabled, which is safe for a laptop. To\n expose it on a network, set PADDOCK_AUTH_MODE first \u2014 Paddock refuses to bind\n a routable interface wide open. See AUTH.md.\n\nDocs: https://github.com/edspencer/paddock";
56
+ export declare const USAGE = "paddock \u2014 run a Paddock instance locally\n\nUsage\n npx @edspencer/paddock [options]\n\nOptions\n -p, --port <port> HTTP/WS port (default 4000, or $PORT)\n --host <host> Bind address (default 127.0.0.1)\n -d, --data-dir <path> Projects + state (default ~/.paddock, or $PADDOCK_DATA_DIR)\n --here Open the CURRENT directory as the workspace (see below)\n -o, --open Open the app in your browser once it is listening\n --verbose Show the server's own logs (quiet by default)\n -v, --version Print the Paddock version and exit\n -h, --help Show this help\n\nOpening a directory (--here)\n Run `paddock --here` inside a project and Paddock opens THAT directory as its\n workspace: Claude works in your files, and any Claude Code sessions you already\n have for the directory are offered for import. It:\n\n \u00B7 creates .paddock/ in the directory for this workspace's own state\n \u00B7 creates .chats/ for transcripts, and adds both to .gitignore\n \u00B7 offers your ~/.claude sessions for this directory for import \u2014 nothing\n there is moved, copied or linked until you confirm\n\n Once done, later runs in the same directory resume it \u2014 no flag needed.\n Without --here, Paddock never touches the directory you ran it from.\n\nCredentials\n Paddock drives Claude Code, so it needs Claude credentials \u2014 and if you\n already use Claude Code on this machine, it uses the login you already have.\n That is a macOS Keychain entry on a Mac, or your ~/.claude/.credentials.json\n elsewhere (symlinked in, never copied). Reading a login writes nothing.\n\n Otherwise: a CLAUDE_CODE_OAUTH_TOKEN or ANTHROPIC_API_KEY in the environment,\n or a one-off `CLAUDE_CONFIG_DIR=<data-dir>/claude-home claude login`. With no\n login at all anywhere, run `claude setup-token`. Paddock says at startup when\n it can find none.\n\nSharing your Claude Code state\n Apart from that login, Paddock writes nothing outside its data dir by\n default: transcripts go to each project's .chats/, and your ~/.claude is read\n for config only. Each thing it can share is one key in\n <data-dir>/paddock.config.yaml:\n\n claude:\n transcripts: host # own | host, default own\n credentials: host # own | host, default host\n instructions: host # own | host, default own\n hooks: host # own | host, default own\n\n transcripts: host makes a chat and a `claude --resume` in the same directory\n the same file, live in both directions; deleting such a chat in Paddock\n releases it rather than removing it, because it is your history rather than\n Paddock's copy. credentials: own is the opt-out from sharing the login above.\n\n instructions: host loads your ~/.claude CLAUDE.md, agents/, commands/ and\n plugins/. Off by default, which is a change: your curated CLAUDE.md does not\n reach Paddock's agents unless you say so. Each project's own CLAUDE.md always\n does.\n\n hooks: host runs the shell commands your ~/.claude/settings.json binds to\n tool use. Off by default \u2014 inheriting someone's hooks is not something to\n discover after the fact. Its other keys (permissions, model, statusline)\n apply either way: under hooks: own Paddock writes its own settings.json\n carrying them with hooks dropped, regenerated at startup.\n\nYour data\n Everything lives in one directory \u2014 ~/.paddock unless you pass --data-dir.\n Projects, chat transcripts and settings all persist there between runs. Move\n that directory to move your instance; delete it to start over. Nothing is\n stored anywhere else.\n\nNotes\n Binds loopback with authentication disabled, which is safe for a laptop. To\n expose it on a network, set PADDOCK_AUTH_MODE first \u2014 Paddock refuses to bind\n a routable interface wide open. See AUTH.md.\n\nDocs: https://github.com/edspencer/paddock";
@@ -134,16 +134,50 @@ Opening a directory (--here)
134
134
 
135
135
  · creates .paddock/ in the directory for this workspace's own state
136
136
  · creates .chats/ for transcripts, and adds both to .gitignore
137
- · links ~/.claude/projects/<encoded-dir> at this workspace
137
+ · offers your ~/.claude sessions for this directory for import — nothing
138
+ there is moved, copied or linked until you confirm
138
139
 
139
140
  Once done, later runs in the same directory resume it — no flag needed.
140
141
  Without --here, Paddock never touches the directory you ran it from.
141
142
 
142
143
  Credentials
143
- Paddock drives Claude Code, so it needs Claude credentials in the
144
- environment: CLAUDE_CODE_OAUTH_TOKEN (Max/Pro) or ANTHROPIC_API_KEY (API
145
- billing). If you already use Claude Code on this machine, an existing login
146
- under ~/.claude is picked up automatically. Otherwise run \`claude setup-token\`.
144
+ Paddock drives Claude Code, so it needs Claude credentials — and if you
145
+ already use Claude Code on this machine, it uses the login you already have.
146
+ That is a macOS Keychain entry on a Mac, or your ~/.claude/.credentials.json
147
+ elsewhere (symlinked in, never copied). Reading a login writes nothing.
148
+
149
+ Otherwise: a CLAUDE_CODE_OAUTH_TOKEN or ANTHROPIC_API_KEY in the environment,
150
+ or a one-off \`CLAUDE_CONFIG_DIR=<data-dir>/claude-home claude login\`. With no
151
+ login at all anywhere, run \`claude setup-token\`. Paddock says at startup when
152
+ it can find none.
153
+
154
+ Sharing your Claude Code state
155
+ Apart from that login, Paddock writes nothing outside its data dir by
156
+ default: transcripts go to each project's .chats/, and your ~/.claude is read
157
+ for config only. Each thing it can share is one key in
158
+ <data-dir>/paddock.config.yaml:
159
+
160
+ claude:
161
+ transcripts: host # own | host, default own
162
+ credentials: host # own | host, default host
163
+ instructions: host # own | host, default own
164
+ hooks: host # own | host, default own
165
+
166
+ transcripts: host makes a chat and a \`claude --resume\` in the same directory
167
+ the same file, live in both directions; deleting such a chat in Paddock
168
+ releases it rather than removing it, because it is your history rather than
169
+ Paddock's copy. credentials: own is the opt-out from sharing the login above.
170
+
171
+ instructions: host loads your ~/.claude CLAUDE.md, agents/, commands/ and
172
+ plugins/. Off by default, which is a change: your curated CLAUDE.md does not
173
+ reach Paddock's agents unless you say so. Each project's own CLAUDE.md always
174
+ does.
175
+
176
+ hooks: host runs the shell commands your ~/.claude/settings.json binds to
177
+ tool use. Off by default — inheriting someone's hooks is not something to
178
+ discover after the fact. Its other keys (permissions, model, statusline)
179
+ apply either way: under hooks: own Paddock writes its own settings.json
180
+ carrying them with hooks dropped, regenerated at startup.
147
181
 
148
182
  Your data
149
183
  Everything lives in one directory — ~/.paddock unless you pass --data-dir.
@@ -119,6 +119,13 @@ function announceHereConsent(dir, dataDir) {
119
119
  "",
120
120
  ` Opening ${dir} as a Paddock workspace.`,
121
121
  ` · ${path.relative(dir, dataDir) || HERE_MARKER}/ and .chats/ created here, and added to .gitignore`,
122
+ // Worth stating because it is not what you would predict: paddock reading
123
+ // the home a user's sessions live in does not turn them into paddock's
124
+ // chats. Verified against a running instance — a pre-existing transcript
125
+ // for the directory comes back from `adoptable-chats` (offered) and NOT
126
+ // from `chats` (listed), because session discovery is attribution-based,
127
+ // not "whatever is in the folder". So #663's confirmation still gates
128
+ // every import.
122
129
  " · ~/.claude sessions for this directory are offered for import (nothing is moved)",
123
130
  " Later runs here resume it — no flag needed.",
124
131
  "",
@@ -131,7 +138,9 @@ function announceHereConsent(dir, dataDir) {
131
138
  * transcripts and prints, and changes nothing on disk.
132
139
  */
133
140
  function offerHereIfSessionsExist(dir) {
134
- const claudeHome = process.env.CLAUDE_HOME ?? path.join(os.homedir(), ".claude");
141
+ // The USER's own home, always: this is looking for `claude` history they made
142
+ // in a terminal, which is by definition in `~/.claude` and never in paddock's.
143
+ const claudeHome = path.join(os.homedir(), ".claude");
135
144
  let sessions = 0;
136
145
  try {
137
146
  sessions = countClaudeSessions(claudeHome, dir);
@@ -204,6 +213,13 @@ async function main() {
204
213
  // is `.git`. A flagless run anywhere else touches nothing.
205
214
  const resuming = !opts.here && isHereWorkspace(cwd);
206
215
  const hereMode = opts.here || resuming;
216
+ // No Claude-home choice happens here any more (#691). The CLI used to point
217
+ // `CLAUDE_HOME` at `~/.claude` so a macOS Keychain login would be visible
218
+ // (#683); paddock now always owns its home, and what a user actually wanted
219
+ // from that — shared transcripts, shared login — are separate `claude:` config
220
+ // keys. `claude.credentials` restores the Keychain half and defaults to `host`,
221
+ // so a Mac whose only login is a `claude /login` still works out of the box
222
+ // with no flag: see `claude-credentials.ts`.
207
223
  // Apply CLI defaults as env vars. This is the whole integration surface: the
208
224
  // server resolves config inside `buildApp()`, so anything set before the
209
225
  // dynamic import below is picked up with no special-casing in config.ts.
@@ -239,19 +255,27 @@ async function main() {
239
255
  // `[fleet-manager] …` lines via console.info — pino's
240
256
  // level cannot reach those.
241
257
  // An explicit value for either is left alone, and `--verbose` skips both.
258
+ // A LEVEL is not enough, which is what #684 was about. Background job failures
259
+ // are logged at `error` — level 50, above every threshold either variable can
260
+ // set — and one of them is a bare `console.error` inside the engine. So quiet
261
+ // mode is also stated as a fact the server can act on: `agent-errors.ts` reads
262
+ // this to collapse a recognised, non-fatal failure to its cause instead of
263
+ // printing a stack trace with 2 KB of system prompt in it. Internal to the
264
+ // CLI; `--verbose` leaves it unset and nothing is suppressed.
242
265
  if (!opts.verbose) {
243
266
  if (process.env.LOG_LEVEL === undefined)
244
267
  process.env.LOG_LEVEL = "warn";
245
268
  if (process.env.HERDCTL_LOG_LEVEL === undefined)
246
269
  process.env.HERDCTL_LOG_LEVEL = "warn";
270
+ process.env.PADDOCK_QUIET = "1";
247
271
  }
248
272
  // No credential preflight here: `ensureClaudeHome` (claude-home.ts) already
249
273
  // warns at boot, and it is the only check that can be right. It tests the home
250
- // paddock will ACTUALLY use — `<dataDir>/claude-home` since #620 — after the
251
- // bridge has symlinked in any `~/.claude/.credentials.json`, and its remedy
252
- // names the `CLAUDE_CONFIG_DIR` keychain scoping that is the real reason a
253
- // logged-in user can still land with no credentials. It logs at `warn`, which
254
- // survives the quiet default set just above.
274
+ // paddock will ACTUALLY use — after the bridge has symlinked in any
275
+ // `~/.claude/.credentials.json`, and, on darwin, after probing the Keychain for
276
+ // the login `CLAUDE_CONFIG_DIR` scoping hides (#683). It logs at `warn`, which
277
+ // survives the quiet default set just above. Continuity (chosen above) usually
278
+ // means it has nothing to say, because there is nothing left to bridge.
255
279
  const port = process.env.PORT ?? "4000";
256
280
  const host = process.env.HOST ?? "127.0.0.1";
257
281
  // Imported dynamically, AFTER the env above is set: a static import would
@@ -1,4 +1,10 @@
1
1
  import { type DriveMode } from "./models.js";
2
+ import { type TranscriptsMode } from "./transcripts.js";
3
+ import { type CredentialsMode } from "./claude-credentials.js";
4
+ import { type InstructionsMode } from "./claude-instructions.js";
5
+ import { type HooksMode } from "./claude-settings.js";
6
+ import { type McpServersMode, type McpServerDefs } from "./claude-mcp.js";
7
+ import { type McpServersConfigFile } from "./mcp-servers.js";
2
8
  import { type RecoveryConfig } from "./recovery-config.js";
3
9
  import { type CurationConfig } from "./curation-config.js";
4
10
  import { type ManagementApiConfig, type ManagementApiConfigFile } from "./management-config.js";
@@ -62,6 +68,75 @@ export interface BrandConfig {
62
68
  /** Accent color as a hex string driving the primary buttons + logo chip. */
63
69
  accent: string;
64
70
  }
71
+ /**
72
+ * What this instance shares with the host machine's Claude Code (#691).
73
+ *
74
+ * One key per concern, one vocabulary: `own` is paddock's, isolated inside the
75
+ * data dir; `host` is this machine's Claude Code. `own` everywhere is the
76
+ * guarantee an operator should be able to read straight off the config file —
77
+ * **nothing outside the data dir is read or written**.
78
+ *
79
+ * All five levers of #691's sequence now exist. Each is a key here rather than
80
+ * another meaning welded onto the Claude home, which is how the three incidents
81
+ * (#682, #683, #689) that motivated this all happened. What remains of #691 is
82
+ * step 6 — a place for a user to declare their OWN MCP servers, which is a
83
+ * different question from whose existing ones this instance borrows.
84
+ */
85
+ export interface ClaudeConfig {
86
+ /**
87
+ * Whose session transcripts this instance uses. `own` (default) keeps them in
88
+ * each project's `.chats/`; `host` shares the user's real
89
+ * `~/.claude/projects/<encoded-cwd>/` folder live, in both directions.
90
+ *
91
+ * Driven by `PADDOCK_CLAUDE_TRANSCRIPTS`, then the `claude.transcripts:` key.
92
+ * See `transcripts.ts` for the mechanism and why it is not a repointed
93
+ * `CLAUDE_CONFIG_DIR`.
94
+ */
95
+ transcripts: TranscriptsMode;
96
+ /**
97
+ * Whose Claude Code login this instance uses. `host` (the default, and the ONE
98
+ * key that does not default to `own`) also reads this machine's own login — the
99
+ * macOS Keychain entry on darwin, the user's `~/.claude/.credentials.json`
100
+ * elsewhere; `own` uses only what is inside paddock's own Claude home.
101
+ *
102
+ * Driven by `PADDOCK_CLAUDE_CREDENTIALS`, then the `claude.credentials:` key.
103
+ * See `claude-credentials.ts` for the mechanism, and for why sharing a login is
104
+ * the safe default when everything else here isolates.
105
+ */
106
+ credentials: CredentialsMode;
107
+ /**
108
+ * Whose user-level instructions this instance loads — the user's `~/.claude`
109
+ * `CLAUDE.md`, `agents/`, `commands/` and `plugins/`. `own` (default) loads
110
+ * none of them; `host` symlinks them in, which is what every version before
111
+ * this one did unconditionally.
112
+ *
113
+ * Driven by `PADDOCK_CLAUDE_INSTRUCTIONS`, then the `claude.instructions:` key.
114
+ * See `claude-instructions.ts` — including the case AGAINST this default,
115
+ * which is real and is kept there rather than deleted.
116
+ */
117
+ instructions: InstructionsMode;
118
+ /**
119
+ * Whether this instance runs the host machine's Claude Code hooks — shell
120
+ * commands `~/.claude/settings.json` binds to tool use and session lifecycle.
121
+ * `own` (default) writes paddock's own `settings.json` carrying every other key
122
+ * of the user's with `hooks` dropped; `host` symlinks theirs in whole.
123
+ *
124
+ * Driven by `PADDOCK_CLAUDE_HOOKS`, then the `claude.hooks:` key. The one lever
125
+ * here that governs code execution rather than data — see `claude-settings.ts`.
126
+ */
127
+ hooks: HooksMode;
128
+ /**
129
+ * Whose MCP servers this instance's keepers get. `own` (default) attaches only
130
+ * the servers paddock itself provides; `host` also attaches the ones declared
131
+ * in the user's `~/.claude.json` — the top-level `mcpServers` plus any scoped
132
+ * to a project's own working directory.
133
+ *
134
+ * Driven by `PADDOCK_CLAUDE_MCP_SERVERS`, then the `claude.mcpServers:` key.
135
+ * The one lever whose source is NOT inside `~/.claude` — see `claude-mcp.ts`
136
+ * for why that made it break silently and separately from the other four.
137
+ */
138
+ mcpServers: McpServersMode;
139
+ }
65
140
  /** OpenAPI / Swagger-UI reference surface config (see PaddockConfig.openapi). */
66
141
  export interface OpenApiConfig {
67
142
  /** Mount the `/open-api` UI + `/open-api.json` spec. Opt-in — defaults false. */
@@ -96,8 +171,9 @@ export interface PaddockConfig {
96
171
  /**
97
172
  * Absolute path to the Claude home THIS INSTANCE owns — the directory whose
98
173
  * `projects/<encoded-cwd>/` folders hold Claude Code's session transcripts.
99
- * Defaults to `<dataDir>/claude-home` (#620); see {@link resolveClaudeHome}
100
- * for the full precedence.
174
+ * `<dataDir>/claude-home` unless an operator relocates it; paddock ALWAYS owns
175
+ * it (#691) and refuses to start if it resolves to the user's own `~/.claude`.
176
+ * See {@link resolveClaudeHome}.
101
177
  *
102
178
  * Resolved ONCE here (#588) rather than re-read from `process.env` at each call
103
179
  * site, because it must be the SAME value everywhere: paddock's transcript
@@ -123,19 +199,15 @@ export interface PaddockConfig {
123
199
  */
124
200
  legacyClaudeHome: string;
125
201
  /**
126
- * Does paddock OWN {@link claudeHome} — i.e. is it something other than the
127
- * user's `~/.claude`?
202
+ * What this instance shares with the host's Claude Code (#691) — currently
203
+ * just `transcripts`. See {@link ClaudeConfig}.
128
204
  *
129
- * The single gate on every mutating transcript operation (#620). Paddock's
130
- * self-healing relocation is allowed to migrate a stray real transcript
131
- * directory into `.chats/` inside a home it owns, because that directory is
132
- * its own doing. Inside the user's home it is somebody else's data — a
133
- * `claude` CLI session run in a directory paddock happens to also manage — and
134
- * moving it is the destructive behaviour this flag exists to prevent.
135
- *
136
- * Derived, not configured: `resolve(claudeHome) !== resolve(legacyClaudeHome)`.
205
+ * Replaces the derived `ownsClaudeHome` flag, which was the single lever every
206
+ * distinct concern hung off. Paddock now always owns its home, so "may we
207
+ * write here?" has one answer and the question that actually varies — whose
208
+ * transcripts these are — is asked directly.
137
209
  */
138
- ownsClaudeHome: boolean;
210
+ claude: ClaudeConfig;
139
211
  /** Provider-agnostic user-authentication config (see AUTH.md). */
140
212
  auth: AuthConfig;
141
213
  /** Voice-dictation (Whisper) capability (per-instance; default off). */
@@ -288,6 +360,31 @@ export interface PaddockConfig {
288
360
  errors: string[];
289
361
  warnings: string[];
290
362
  };
363
+ /**
364
+ * MCP servers this instance declares for itself (#691 step 6), resolved from
365
+ * the top-level `mcpServers:` block with every `env:VAR_NAME` reference read
366
+ * from the environment. Every project's keeper is attached all of them, and
367
+ * they win a name collision against anything inherited via
368
+ * `claude.mcpServers: host`.
369
+ *
370
+ * **Holds resolved secret material** — an MCP server's `env` is where a stdio
371
+ * server's API token lives — exactly as {@link managementApi}'s client tokens
372
+ * do. It must never be serialised into an API response; `instance-config.ts`
373
+ * publishes only the fields in its own `FIELDS` table, and this is
374
+ * deliberately not one of them.
375
+ */
376
+ mcpServers: McpServerDefs;
377
+ /**
378
+ * Operator-facing problems found while resolving {@link mcpServers}: a
379
+ * declaration paddock cannot carry (`errors`, server dropped) or one whose
380
+ * `env:` reference did not resolve / whose secret is inlined (`warnings`).
381
+ * Carried, not logged, for the same reason as
382
+ * {@link managementApiDiagnostics}. Never contains a value from the block.
383
+ */
384
+ mcpServersDiagnostics: {
385
+ errors: string[];
386
+ warnings: string[];
387
+ };
291
388
  /**
292
389
  * Instance default for the hook-management MCP (Epic G / G5, GG-4) — the
293
390
  * `mcp__paddock_manage__{list,set,remove}_hook` tools that let a project agent
@@ -385,8 +482,31 @@ export interface PaddockConfigFile {
385
482
  stateDir?: string;
386
483
  herdctlConfigPath?: string;
387
484
  webDist?: string;
388
- /** Claude home override; beneath `CLAUDE_HOME`/`CLAUDE_CONFIG_DIR` (#620). */
485
+ /** Where paddock's OWN Claude home lives; beneath `CLAUDE_CONFIG_DIR` (#691). */
389
486
  claudeHome?: string;
487
+ /**
488
+ * What this instance shares with the host's Claude Code (#691). Each key takes
489
+ * `own` or `host`; a matching `PADDOCK_CLAUDE_*` env var still overrides it
490
+ * (file < env). Absent ⇒ fully isolated.
491
+ */
492
+ claude?: {
493
+ transcripts?: string;
494
+ credentials?: string;
495
+ instructions?: string;
496
+ hooks?: string;
497
+ mcpServers?: string;
498
+ };
499
+ /**
500
+ * MCP servers this instance declares for ITSELF (#691 step 6) — a sibling of
501
+ * `claude:`, not a member of it, because it answers a different question:
502
+ * `claude.mcpServers` borrows whatever this machine already has, while this
503
+ * says what paddock should have regardless.
504
+ *
505
+ * File-only, like `managementApi`: a map of servers does not express as a
506
+ * scalar env var. Token material belongs in the environment and is referenced
507
+ * as `env:VAR_NAME` from any string here — see `mcp-servers.ts`.
508
+ */
509
+ mcpServers?: McpServersConfigFile;
390
510
  auth?: {
391
511
  mode?: string;
392
512
  userHeader?: string;
@@ -561,35 +681,43 @@ export declare function loadPaddockConfig(): PaddockConfig;
561
681
  */
562
682
  export declare function userClaudeHome(): string;
563
683
  /**
564
- * Resolve the Claude home paddock runs its agents against.
684
+ * Resolve where paddock's OWN Claude home lives.
565
685
  *
566
686
  * Precedence, highest first:
567
687
  *
568
- * 1. **`CLAUDE_HOME`** — paddock's own long-standing override. Setting it to
569
- * `$HOME/.claude` restores the pre-#620 behaviour exactly, which is the
570
- * documented escape hatch if the relocation causes trouble.
571
- * 2. **`CLAUDE_CONFIG_DIR`** — the variable *Claude Code itself* resolves its
688
+ * 1. **`CLAUDE_CONFIG_DIR`** — the variable *Claude Code itself* resolves its
572
689
  * home from. Honouring it is load-bearing, not a courtesy: herdctl
573
690
  * deliberately refuses to clobber an operator-set `CLAUDE_CONFIG_DIR`
574
691
  * (herdctl#423), so if paddock picked a different home the SDK would write
575
692
  * transcripts to one tree while herdctl read from another — the exact
576
693
  * split-brain #588 fixed. Deferring to it makes that state unreachable.
577
- * 3. A `claudeHome:` key in the instance config file.
578
- * 4. **`<dataDir>/claude-home`** — the default since #620.
694
+ * 2. A `claudeHome:` key in the instance config file.
695
+ * 3. **`<dataDir>/claude-home`** — the default.
579
696
  *
580
- * On the default, and why it changed: paddock already nests herdctl's whole
581
- * state dir, the projects tree and the generated `herdctl.yaml` under its data
582
- * dir. Transcripts were the one exception, reached by planting symlinks in the
583
- * user's home — not a design choice but a constraint, because until
584
- * herdctl#423 nothing set `CLAUDE_CONFIG_DIR` and the SDK wrote to `~/.claude`
585
- * no matter what paddock configured. With that gone, owning the home makes the
586
- * data dir relocatable as a unit, leaves `~/.claude` alone, and (because there
587
- * is no longer a `.claude` path component) unblocks agent memory writes, which
588
- * the harness refuses for any path containing `.claude`.
697
+ * ## Paddock always OWNS this home, and the one value it refuses
698
+ *
699
+ * `CLAUDE_HOME` used to sit above all of this, and setting it to `~/.claude` was
700
+ * the documented escape hatch back to the pre-#620 layout. It is gone (#691),
701
+ * because pointing the home at the user's own directory is what welded five
702
+ * unrelated concerns to one lever: it decided whose transcripts were at risk,
703
+ * which keychain entry was visible, where agent memory landed, and whether
704
+ * delete meant delete. Those are now separate keys (`claude:`), so the home no
705
+ * longer needs to move for any of them.
706
+ *
707
+ * Which leaves exactly one value that must not be allowed through, from any
708
+ * source: the user's own `~/.claude`. It re-welds the lever, and it re-breaks
709
+ * agent memory (#690) — the memory dir is `<claudeHome>/projects/<enc>/memory`,
710
+ * and the agent harness refuses to write to any path with a `.claude` component.
711
+ * So it REFUSES TO START rather than silently degrading, which is the same
712
+ * fail-closed shape as the bind-safety guard. `CLAUDE_CONFIG_DIR` cannot simply
713
+ * be ignored instead: it is Claude Code's own variable, and disagreeing with an
714
+ * operator-set value is the herdctl#423 split-brain again.
589
715
  *
590
716
  * NOT canonical()-ed: this exact string is what herdctl hands Claude Code as
591
717
  * `CLAUDE_CONFIG_DIR`, and canonicalising would silently diverge from the
592
718
  * literal path the two of them encode into `<home>/projects/<encoded-cwd>`.
719
+ * The refusal check DOES resolve both sides, so a symlinked or trailing-slash
720
+ * spelling of `~/.claude` cannot slip past it.
593
721
  *
594
722
  * This is the RESOLVER, not the accessor. It is read once by
595
723
  * {@link loadPaddockConfig} into {@link PaddockConfig.claudeHome}; runtime code
@@ -597,5 +725,13 @@ export declare function userClaudeHome(): string;
597
725
  * engine can never end up resolving two different homes (#588).
598
726
  */
599
727
  export declare function resolveClaudeHome(dataDir: string, fromFile?: string): string;
728
+ /**
729
+ * Why `home` is unusable as paddock's own Claude home, or `undefined` if it is
730
+ * fine. Pure, so the whole decision is testable without touching `$HOME`.
731
+ *
732
+ * `fromEnv` only changes which fix is named first — the refusal itself is the
733
+ * same whichever layer supplied the value.
734
+ */
735
+ export declare function claudeHomeRefusal(home: string, userHome: string, fromEnv: boolean): string | undefined;
600
736
  /** Directory name of paddock's own Claude home inside the data dir (#620). */
601
737
  export declare const DEFAULT_CLAUDE_HOME_DIRNAME = "claude-home";