@edspencer/paddock 0.71.1 → 0.72.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.
- package/package.json +1 -1
- package/packages/server/dist/app.js +27 -0
- package/packages/server/dist/cli/args.d.ts +67 -8
- package/packages/server/dist/cli/args.js +213 -33
- package/packages/server/dist/cli/config.d.ts +157 -0
- package/packages/server/dist/cli/config.js +462 -0
- package/packages/server/dist/cli/paddock.js +53 -3
- package/packages/server/dist/cli/service/backend.d.ts +33 -0
- package/packages/server/dist/cli/service/index.d.ts +3 -3
- package/packages/server/dist/cli/service/index.js +124 -2
- package/packages/server/dist/cli/service/launchd.d.ts +27 -4
- package/packages/server/dist/cli/service/launchd.js +88 -14
- package/packages/server/dist/cli/service/systemd.d.ts +21 -2
- package/packages/server/dist/cli/service/systemd.js +58 -5
- package/packages/server/dist/config.d.ts +36 -1
- package/packages/server/dist/config.js +59 -44
- package/packages/server/dist/herdctl.d.ts +45 -0
- package/packages/server/dist/herdctl.js +58 -4
- package/packages/server/dist/instance-config.d.ts +149 -21
- package/packages/server/dist/instance-config.js +228 -5
- package/packages/server/dist/last-activity.d.ts +21 -0
- package/packages/server/dist/last-activity.js +7 -2
- package/packages/server/dist/profiles.d.ts +154 -0
- package/packages/server/dist/profiles.js +214 -0
- package/packages/server/dist/routes/transcripts.d.ts +61 -0
- package/packages/server/dist/routes/transcripts.js +676 -0
- package/packages/server/dist/routes.d.ts +3 -0
- package/packages/server/dist/routes.js +2 -0
- package/packages/server/dist/transcript-move.d.ts +62 -0
- package/packages/server/dist/transcript-move.js +179 -0
- package/packages/server/dist/transcripts-migration-execute.d.ts +144 -0
- package/packages/server/dist/transcripts-migration-execute.js +630 -0
- package/packages/server/dist/transcripts-migration.d.ts +336 -0
- package/packages/server/dist/transcripts-migration.js +917 -0
- package/packages/server/dist/transcripts.d.ts +46 -2
- package/packages/server/dist/transcripts.js +126 -3
- package/packages/server/package.json +1 -1
- package/packages/web/dist/assets/DiscoverPage-DXI9FjmJ.js +2 -0
- package/packages/web/dist/assets/DiscoverView-D9oqVkT4.js +2 -0
- package/packages/web/dist/assets/InstanceConfigPage-DyH1j5oR.js +2 -0
- package/packages/web/dist/assets/{MarkdownRenderer-DdoNl0Ao.js → MarkdownRenderer-D95ofnQ8.js} +1 -1
- package/packages/web/dist/assets/ProjectMenu-BO70tGwy.js +2 -0
- package/packages/web/dist/assets/{ProjectRedirect-QuGeN-qx.js → ProjectRedirect-BxX_aADU.js} +1 -1
- package/packages/web/dist/assets/ProjectView-BbCyKoh5.js +13 -0
- package/packages/web/dist/assets/{ProjectsGrid-CUFNVTif.js → ProjectsGrid-BafOHL43.js} +1 -1
- package/packages/web/dist/assets/RootHome-BGkMypUJ.js +2 -0
- package/packages/web/dist/assets/Surfaces-B8SyV4r3.js +2 -0
- package/packages/web/dist/assets/{arc-C0DRwCCT.js → arc-DGyr--oL.js} +1 -1
- package/packages/web/dist/assets/architectureDiagram-3BPJPVTR-BNczNNmx.js +37 -0
- package/packages/web/dist/assets/{blockDiagram-GPEHLZMM-DKmCbgWk.js → blockDiagram-GPEHLZMM-DiTvGSrV.js} +1 -1
- package/packages/web/dist/assets/{c4Diagram-AAUBKEIU-CJ8LiLsj.js → c4Diagram-AAUBKEIU-CKZ_jy81.js} +1 -1
- package/packages/web/dist/assets/channel-CjwDi1A9.js +2 -0
- package/packages/web/dist/assets/{chunk-2J33WTMH-C3P-O4C3.js → chunk-2J33WTMH-5o96lthu.js} +1 -1
- package/packages/web/dist/assets/{chunk-4BX2VUAB-Cs9u7x61.js → chunk-4BX2VUAB-38_pZfd8.js} +1 -1
- package/packages/web/dist/assets/{chunk-55IACEB6-C1st284U.js → chunk-55IACEB6-Db73LvlE.js} +1 -1
- package/packages/web/dist/assets/{chunk-727SXJPM-BjCAWo7B.js → chunk-727SXJPM-C-Q1-wUu.js} +1 -1
- package/packages/web/dist/assets/{chunk-AQP2D5EJ-BP6S9w2Q.js → chunk-AQP2D5EJ-ClJigITk.js} +1 -1
- package/packages/web/dist/assets/{chunk-FMBD7UC4-C3ySTaPs.js → chunk-FMBD7UC4-BVzSX2hw.js} +1 -1
- package/packages/web/dist/assets/{chunk-ND2GUHAM-eNPD4vzc.js → chunk-ND2GUHAM-DJlL-ELa.js} +1 -1
- package/packages/web/dist/assets/{chunk-QZHKN3VN-BbBpgHkO.js → chunk-QZHKN3VN-BC0QEk6w.js} +1 -1
- package/packages/web/dist/assets/classDiagram-4FO5ZUOK-CxiNtY3i.js +2 -0
- package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-CxiNtY3i.js +2 -0
- package/packages/web/dist/assets/{core-ZwDDec0y.js → core-VUdoTe9w.js} +1 -1
- package/packages/web/dist/assets/cose-bilkent-S5V4N54A-DVqArcBy.js +2 -0
- package/packages/web/dist/assets/{dagre-BM42HDAG-DKIEUM4M.js → dagre-BM42HDAG-BgFGyUdv.js} +1 -1
- package/packages/web/dist/assets/{diagram-2AECGRRQ-CqlX7j8t.js → diagram-2AECGRRQ-BqdZmJt3.js} +1 -1
- package/packages/web/dist/assets/{diagram-5GNKFQAL-Dph5apqo.js → diagram-5GNKFQAL-DeLPu8jT.js} +1 -1
- package/packages/web/dist/assets/{diagram-KO2AKTUF-TW6LyHGt.js → diagram-KO2AKTUF-BqTiTGay.js} +1 -1
- package/packages/web/dist/assets/{diagram-LMA3HP47-BxxIIv5a.js → diagram-LMA3HP47-CbpAvCgl.js} +1 -1
- package/packages/web/dist/assets/{diagram-OG6HWLK6-B8M0DeV7.js → diagram-OG6HWLK6-C4h0V97s.js} +1 -1
- package/packages/web/dist/assets/{erDiagram-TEJ5UH35-Bfpy6UQa.js → erDiagram-TEJ5UH35-Dwz77uRx.js} +1 -1
- package/packages/web/dist/assets/{flowDiagram-I6XJVG4X-DstRzxek.js → flowDiagram-I6XJVG4X-DSikJnZR.js} +1 -1
- package/packages/web/dist/assets/ganttDiagram-6RSMTGT7-DkAGNZb3.js +293 -0
- package/packages/web/dist/assets/{gitGraphDiagram-PVQCEYII-CX4IozE-.js → gitGraphDiagram-PVQCEYII-BQtzt9hP.js} +1 -1
- package/packages/web/dist/assets/index-BN3rLMpD.js +71 -0
- package/packages/web/dist/assets/index-Dfzb1DvL.css +1 -0
- package/packages/web/dist/assets/{infoDiagram-5YYISTIA-CG2qCk6u.js → infoDiagram-5YYISTIA-YcaBUjeC.js} +1 -1
- package/packages/web/dist/assets/{ishikawaDiagram-YF4QCWOH-CPErvl3a.js → ishikawaDiagram-YF4QCWOH-BfLZB88u.js} +1 -1
- package/packages/web/dist/assets/{journeyDiagram-JHISSGLW-Bm6krUsf.js → journeyDiagram-JHISSGLW-DwaFhd6C.js} +1 -1
- package/packages/web/dist/assets/{kanban-definition-UN3LZRKU-B4CC8CAx.js → kanban-definition-UN3LZRKU-e9P3WxsD.js} +1 -1
- package/packages/web/dist/assets/{linear-CJ1DbrOX.js → linear-CsXS5LTz.js} +1 -1
- package/packages/web/dist/assets/{mermaid.core-Dk2kkhf9.js → mermaid.core-DsA72Qyi.js} +4 -4
- package/packages/web/dist/assets/{mindmap-definition-RKZ34NQL-CThvf_YD.js → mindmap-definition-RKZ34NQL-DyLOITDI.js} +1 -1
- package/packages/web/dist/assets/{pieDiagram-4H26LBE5-DvkHcng6.js → pieDiagram-4H26LBE5-C36aM3D-.js} +1 -1
- package/packages/web/dist/assets/{quadrantDiagram-W4KKPZXB-CqhrTh9q.js → quadrantDiagram-W4KKPZXB-Cq1zTk6a.js} +1 -1
- package/packages/web/dist/assets/{requirementDiagram-4Y6WPE33-CZykiLym.js → requirementDiagram-4Y6WPE33-B6MctUk7.js} +1 -1
- package/packages/web/dist/assets/{sankeyDiagram-5OEKKPKP-D-TzCKMx.js → sankeyDiagram-5OEKKPKP-CaNRUoPR.js} +1 -1
- package/packages/web/dist/assets/{sequenceDiagram-3UESZ5HK-C5pIItVu.js → sequenceDiagram-3UESZ5HK-DDj_tZiQ.js} +1 -1
- package/packages/web/dist/assets/{stateDiagram-AJRCARHV-DE6G2JfB.js → stateDiagram-AJRCARHV-DDaMlLyK.js} +1 -1
- package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-Bu6Swk2T.js +2 -0
- package/packages/web/dist/assets/{timeline-definition-PNZ67QCA-C1VK2OfU.js → timeline-definition-PNZ67QCA-bMP-7Wam.js} +1 -1
- package/packages/web/dist/assets/{vennDiagram-CIIHVFJN-CtOozEwa.js → vennDiagram-CIIHVFJN-9nzKgc0Y.js} +1 -1
- package/packages/web/dist/assets/{wardley-L42UT6IY-DvHGK-lb.js → wardley-L42UT6IY-DIWbAFW5.js} +1 -1
- package/packages/web/dist/assets/{wardleyDiagram-YWT4CUSO-DxKwZ3-G.js → wardleyDiagram-YWT4CUSO-CVL3skbs.js} +1 -1
- package/packages/web/dist/assets/{xychartDiagram-2RQKCTM6-HM4j3l6y.js → xychartDiagram-2RQKCTM6-DHoVPvll.js} +1 -1
- package/packages/web/dist/index.html +2 -2
- package/packages/web/dist/sw.js +1 -1
- package/packages/web/dist/assets/Chip-Db--PJFH.js +0 -2
- package/packages/web/dist/assets/DiscoverPage-BFo4FTzZ.js +0 -2
- package/packages/web/dist/assets/DiscoverView-C4hOB3nr.js +0 -2
- package/packages/web/dist/assets/InstanceConfigPage-U78M95nW.js +0 -2
- package/packages/web/dist/assets/ProjectMenu-BxQu14ze.js +0 -2
- package/packages/web/dist/assets/ProjectView-FUcyJMUf.js +0 -13
- package/packages/web/dist/assets/RootHome-SIxo-xX9.js +0 -2
- package/packages/web/dist/assets/architectureDiagram-3BPJPVTR-Wwpf4cvW.js +0 -37
- package/packages/web/dist/assets/channel-DEmtNKeB.js +0 -2
- package/packages/web/dist/assets/classDiagram-4FO5ZUOK-dAGAuccL.js +0 -2
- package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-dAGAuccL.js +0 -2
- package/packages/web/dist/assets/cose-bilkent-S5V4N54A-DiM4eeF5.js +0 -2
- package/packages/web/dist/assets/ganttDiagram-6RSMTGT7-Bg0oFbAl.js +0 -293
- package/packages/web/dist/assets/index-ASW1Jl0Y.js +0 -71
- package/packages/web/dist/assets/index-bC5uCujx.css +0 -1
- package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-BP6oowh1.js +0 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `paddock service install | uninstall | status`
|
|
2
|
+
* `paddock service install | uninstall | status | start | stop | restart`
|
|
3
|
+
* (#796; the lifecycle three added in #873).
|
|
3
4
|
*
|
|
4
5
|
* Registers Paddock as a **per-user** background service — a launchd
|
|
5
6
|
* LaunchAgent on macOS, a `systemd --user` unit on Linux — so it is up from
|
|
@@ -26,7 +27,7 @@ import os from "node:os";
|
|
|
26
27
|
import path from "node:path";
|
|
27
28
|
import { CliError } from "../args.js";
|
|
28
29
|
import { buildSpec, defaultDataDir, hostFromArgv, isNpxPath, NPX_REFUSAL, portFromArgv, serviceDir, } from "./spec.js";
|
|
29
|
-
import { spawnRunner } from "./backend.js";
|
|
30
|
+
import { spawnRunner, } from "./backend.js";
|
|
30
31
|
import { createLaunchdBackend } from "./launchd.js";
|
|
31
32
|
import { createSystemdBackend } from "./systemd.js";
|
|
32
33
|
import { waitForReady, READY_TIMEOUT_LABEL } from "./ready.js";
|
|
@@ -77,6 +78,25 @@ function specFor(opts, ctx) {
|
|
|
77
78
|
const AT_LOGIN = " Paddock starts when you LOG IN, not when the machine boots — it runs as\n" +
|
|
78
79
|
" you, so it can use the Claude login you already have. After a restart\n" +
|
|
79
80
|
" that nobody logs into, Paddock is not running.";
|
|
81
|
+
/**
|
|
82
|
+
* What `status` says about a unit installed before #872.
|
|
83
|
+
*
|
|
84
|
+
* Upgrading the package leaves the unit file alone, so the people still holding
|
|
85
|
+
* the broken shape are exactly the people who will never notice — the symptom
|
|
86
|
+
* is Paddock being absent one morning, with nothing in the logs, because it was
|
|
87
|
+
* signalled rather than crashed. Naming the fix here is the only path from that
|
|
88
|
+
* symptom back to a working service.
|
|
89
|
+
*/
|
|
90
|
+
function staleUnitNote(backend) {
|
|
91
|
+
const key = backend.platform === "darwin"
|
|
92
|
+
? "KeepAlive is conditional on a non-zero exit"
|
|
93
|
+
: "Restart=on-failure";
|
|
94
|
+
return (` ⚠ This unit predates a fix (#872): ${key}, so a graceful stop —\n` +
|
|
95
|
+
" the kind sleep, logout or a stray `kill` sends — puts Paddock down\n" +
|
|
96
|
+
" until you start it again by hand. Re-run to update the unit:\n" +
|
|
97
|
+
"\n" +
|
|
98
|
+
" paddock service install");
|
|
99
|
+
}
|
|
80
100
|
/**
|
|
81
101
|
* Say what is about to happen, before the part that looks like a hang (#861).
|
|
82
102
|
*
|
|
@@ -135,6 +155,7 @@ async function install(opts, ctx, backend) {
|
|
|
135
155
|
...(linger !== undefined ? ["", linger] : []),
|
|
136
156
|
"",
|
|
137
157
|
" paddock service status is it up, and where",
|
|
158
|
+
" paddock service restart bounce it after editing config",
|
|
138
159
|
" paddock service uninstall stop it and remove the unit",
|
|
139
160
|
"",
|
|
140
161
|
].join("\n"));
|
|
@@ -194,16 +215,117 @@ function status(opts, ctx, backend) {
|
|
|
194
215
|
backend.logsHint(spec),
|
|
195
216
|
"",
|
|
196
217
|
AT_LOGIN,
|
|
218
|
+
...(state.keepsAlive ? [] : ["", staleUnitNote(backend)]),
|
|
197
219
|
...(linger !== undefined && !state.running ? ["", linger] : []),
|
|
198
220
|
"",
|
|
199
221
|
].join("\n"));
|
|
200
222
|
}
|
|
223
|
+
/**
|
|
224
|
+
* The URL an installed unit is actually serving, or undefined if none is.
|
|
225
|
+
*
|
|
226
|
+
* Read out of the unit rather than recomputed from today's flags, for the same
|
|
227
|
+
* reason `status` does it: the port in the file is the port the server is on.
|
|
228
|
+
*/
|
|
229
|
+
function installedUrl(state) {
|
|
230
|
+
return `http://${hostFromArgv(state.argv)}:${portFromArgv(state.argv)}`;
|
|
231
|
+
}
|
|
232
|
+
/** "It isn't installed" — the same answer, wherever it is asked. */
|
|
233
|
+
function notInstalled(backend) {
|
|
234
|
+
return [
|
|
235
|
+
"",
|
|
236
|
+
" Paddock is not installed as a service.",
|
|
237
|
+
` Looked for: ${backend.unitPath}`,
|
|
238
|
+
"",
|
|
239
|
+
" paddock service install register it and start it now",
|
|
240
|
+
"",
|
|
241
|
+
].join("\n");
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* `start` / `restart` (#873), which differ only in what they tolerate.
|
|
245
|
+
*
|
|
246
|
+
* `start` on something already running is reported and not acted on — asking
|
|
247
|
+
* launchd to bootstrap a loaded job is an error, and "it is already up" is the
|
|
248
|
+
* outcome the user wanted anyway. `restart` has no such case: bouncing a
|
|
249
|
+
* stopped service is a legitimate way to start it, and the sequence each
|
|
250
|
+
* backend uses handles both states.
|
|
251
|
+
*
|
|
252
|
+
* Both then WAIT. Neither supervisor's "started" means the port answers — it
|
|
253
|
+
* means the fork succeeded — so printing a URL on the strength of it produces a
|
|
254
|
+
* success message that can sit over a crash-loop (a port clash restarts every
|
|
255
|
+
* `ThrottleInterval`, and `launchctl print` says `running` for most of that
|
|
256
|
+
* window). The wait is what makes the last line true.
|
|
257
|
+
*/
|
|
258
|
+
async function startOrRestart(action, ctx, backend) {
|
|
259
|
+
const before = backend.status();
|
|
260
|
+
if (!before.registered) {
|
|
261
|
+
console.log(notInstalled(backend));
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
const url = installedUrl(before);
|
|
265
|
+
if (action === "start" && before.running) {
|
|
266
|
+
console.log(`\n Paddock is already running${pid(before)}.\n\n URL: ${url}\n`);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
console.log(`\n ${action === "start" ? "Starting" : "Restarting"} Paddock…`);
|
|
270
|
+
if (action === "start")
|
|
271
|
+
backend.start();
|
|
272
|
+
else
|
|
273
|
+
backend.restart();
|
|
274
|
+
const readiness = await (ctx.checkReady ?? ((u) => waitForReady({ url: u })))(url);
|
|
275
|
+
const after = backend.status();
|
|
276
|
+
console.log([
|
|
277
|
+
"",
|
|
278
|
+
readiness === "ready"
|
|
279
|
+
? ` ✓ Paddock is running${pid(after)} at ${url}`
|
|
280
|
+
: ` Paddock was ${action === "start" ? "started" : "restarted"}, but ${url} did not\n` +
|
|
281
|
+
` answer within ${READY_TIMEOUT_LABEL}. It may still be coming up — check:`,
|
|
282
|
+
...(readiness === "ready" ? [] : ["", " paddock service status", backend.logsHint(specForLogs(ctx, after))]),
|
|
283
|
+
"",
|
|
284
|
+
].join("\n"));
|
|
285
|
+
}
|
|
286
|
+
/** `stop` (#873): put it down, leave it installed. */
|
|
287
|
+
function stop(backend) {
|
|
288
|
+
const state = backend.status();
|
|
289
|
+
if (!state.registered) {
|
|
290
|
+
console.log(notInstalled(backend));
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if (!state.running) {
|
|
294
|
+
console.log(`\n Paddock is already stopped.\n Unit: ${backend.unitPath}\n`);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
backend.stop();
|
|
298
|
+
console.log([
|
|
299
|
+
"",
|
|
300
|
+
" Paddock is stopped. The unit is still installed.",
|
|
301
|
+
` Unit: ${backend.unitPath}`,
|
|
302
|
+
"",
|
|
303
|
+
" It comes back when you log in again, or now with:",
|
|
304
|
+
"",
|
|
305
|
+
" paddock service start",
|
|
306
|
+
"",
|
|
307
|
+
].join("\n"));
|
|
308
|
+
}
|
|
309
|
+
function pid(state) {
|
|
310
|
+
return state.pid !== undefined ? ` (pid ${state.pid})` : "";
|
|
311
|
+
}
|
|
312
|
+
/** The spec `logsHint` needs, built from the INSTALLED unit's data dir. */
|
|
313
|
+
function specForLogs(ctx, state) {
|
|
314
|
+
const i = state.argv.indexOf("--data-dir");
|
|
315
|
+
const dataDir = i >= 0 && state.argv[i + 1] !== undefined ? state.argv[i + 1] : undefined;
|
|
316
|
+
const { spec } = specFor({ open: false, verbose: false, help: false, version: false }, { ...ctx, ...(dataDir !== undefined ? { envDataDir: dataDir } : {}) });
|
|
317
|
+
return spec;
|
|
318
|
+
}
|
|
201
319
|
export async function runService(action, opts, ctx) {
|
|
202
320
|
const backend = backendFor(ctx);
|
|
203
321
|
if (action === "install")
|
|
204
322
|
return install(opts, ctx, backend);
|
|
205
323
|
if (action === "uninstall")
|
|
206
324
|
return uninstall(ctx, backend);
|
|
325
|
+
if (action === "start" || action === "restart")
|
|
326
|
+
return startOrRestart(action, ctx, backend);
|
|
327
|
+
if (action === "stop")
|
|
328
|
+
return stop(backend);
|
|
207
329
|
return status(opts, ctx, backend);
|
|
208
330
|
}
|
|
209
331
|
/** Exported for the entrypoint's context assembly, and for tests. */
|
|
@@ -5,10 +5,22 @@ export declare function plistPath(homeDir?: string): string;
|
|
|
5
5
|
/**
|
|
6
6
|
* Render the plist.
|
|
7
7
|
*
|
|
8
|
-
* `KeepAlive` is
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* `KeepAlive` is unconditional `<true/>`, and that is a deliberate reversal
|
|
9
|
+
* (#872). It used to be `{ SuccessfulExit: false }` — "restart it if it exits
|
|
10
|
+
* nonzero, leave it alone if it exits cleanly" — which reads like the polite
|
|
11
|
+
* choice and is, for a service, a trap. `start.ts` handles SIGTERM by shutting
|
|
12
|
+
* down cleanly and exiting `0`, so **every** SIGTERM the OS routinely sends a
|
|
13
|
+
* LaunchAgent (sleep, logout, a stray `kill`) looked to launchd like "it meant
|
|
14
|
+
* to stop", and Paddock stayed down until the next login. A hard crash was the
|
|
15
|
+
* survivable case; the graceful stop was the terminal one.
|
|
16
|
+
*
|
|
17
|
+
* The worry that motivated the dict — that `<true/>` fights a deliberate stop —
|
|
18
|
+
* does not hold for the stop this CLI actually performs. `launchctl bootout`
|
|
19
|
+
* unloads the job from the domain, and KeepAlive only applies to a job that is
|
|
20
|
+
* still loaded, so `uninstall` still stops Paddock in one call. What `<true/>`
|
|
21
|
+
* does fight is `launchctl stop`, which is why nothing here uses it.
|
|
22
|
+
*
|
|
23
|
+
* Intent belongs in an explicit command, not in an exit code.
|
|
12
24
|
*
|
|
13
25
|
* `EnvironmentVariables` carries PATH and nothing else. It carries PATH because
|
|
14
26
|
* launchd hands a process a stub PATH that a version manager's `node`, and the
|
|
@@ -32,6 +44,17 @@ export declare function renderPlist(spec: ServiceSpec): string;
|
|
|
32
44
|
* would be the larger risk.
|
|
33
45
|
*/
|
|
34
46
|
export declare function parsePlistArgv(xml: string): string[];
|
|
47
|
+
/**
|
|
48
|
+
* Does an installed plist carry the unconditional `KeepAlive` from #872?
|
|
49
|
+
*
|
|
50
|
+
* Deliberately narrow: it matches `<key>KeepAlive</key>` followed by `<true/>`
|
|
51
|
+
* and nothing else. The shape it must NOT accept is the old
|
|
52
|
+
* `<dict><key>SuccessfulExit</key><false/></dict>`, which is the one that
|
|
53
|
+
* leaves a service dead after a graceful stop — so a `false` here is the signal
|
|
54
|
+
* `status` turns into "re-run install", and defaulting to `true` on anything
|
|
55
|
+
* unrecognised would silence exactly the case this exists to catch.
|
|
56
|
+
*/
|
|
57
|
+
export declare function plistKeepsAlive(xml: string): boolean;
|
|
35
58
|
/**
|
|
36
59
|
* Read liveness out of `launchctl print gui/<uid>/<label>`.
|
|
37
60
|
*
|
|
@@ -51,10 +51,22 @@ function xmlEscape(s) {
|
|
|
51
51
|
/**
|
|
52
52
|
* Render the plist.
|
|
53
53
|
*
|
|
54
|
-
* `KeepAlive` is
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
54
|
+
* `KeepAlive` is unconditional `<true/>`, and that is a deliberate reversal
|
|
55
|
+
* (#872). It used to be `{ SuccessfulExit: false }` — "restart it if it exits
|
|
56
|
+
* nonzero, leave it alone if it exits cleanly" — which reads like the polite
|
|
57
|
+
* choice and is, for a service, a trap. `start.ts` handles SIGTERM by shutting
|
|
58
|
+
* down cleanly and exiting `0`, so **every** SIGTERM the OS routinely sends a
|
|
59
|
+
* LaunchAgent (sleep, logout, a stray `kill`) looked to launchd like "it meant
|
|
60
|
+
* to stop", and Paddock stayed down until the next login. A hard crash was the
|
|
61
|
+
* survivable case; the graceful stop was the terminal one.
|
|
62
|
+
*
|
|
63
|
+
* The worry that motivated the dict — that `<true/>` fights a deliberate stop —
|
|
64
|
+
* does not hold for the stop this CLI actually performs. `launchctl bootout`
|
|
65
|
+
* unloads the job from the domain, and KeepAlive only applies to a job that is
|
|
66
|
+
* still loaded, so `uninstall` still stops Paddock in one call. What `<true/>`
|
|
67
|
+
* does fight is `launchctl stop`, which is why nothing here uses it.
|
|
68
|
+
*
|
|
69
|
+
* Intent belongs in an explicit command, not in an exit code.
|
|
58
70
|
*
|
|
59
71
|
* `EnvironmentVariables` carries PATH and nothing else. It carries PATH because
|
|
60
72
|
* launchd hands a process a stub PATH that a version manager's `node`, and the
|
|
@@ -82,10 +94,7 @@ ${strings}
|
|
|
82
94
|
<key>RunAtLoad</key>
|
|
83
95
|
<true/>
|
|
84
96
|
<key>KeepAlive</key>
|
|
85
|
-
<
|
|
86
|
-
<key>SuccessfulExit</key>
|
|
87
|
-
<false/>
|
|
88
|
-
</dict>
|
|
97
|
+
<true/>
|
|
89
98
|
<key>ThrottleInterval</key>
|
|
90
99
|
<integer>10</integer>
|
|
91
100
|
<key>WorkingDirectory</key>
|
|
@@ -123,6 +132,19 @@ export function parsePlistArgv(xml) {
|
|
|
123
132
|
.replace(/</g, "<")
|
|
124
133
|
.replace(/&/g, "&"));
|
|
125
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* Does an installed plist carry the unconditional `KeepAlive` from #872?
|
|
137
|
+
*
|
|
138
|
+
* Deliberately narrow: it matches `<key>KeepAlive</key>` followed by `<true/>`
|
|
139
|
+
* and nothing else. The shape it must NOT accept is the old
|
|
140
|
+
* `<dict><key>SuccessfulExit</key><false/></dict>`, which is the one that
|
|
141
|
+
* leaves a service dead after a graceful stop — so a `false` here is the signal
|
|
142
|
+
* `status` turns into "re-run install", and defaulting to `true` on anything
|
|
143
|
+
* unrecognised would silence exactly the case this exists to catch.
|
|
144
|
+
*/
|
|
145
|
+
export function plistKeepsAlive(xml) {
|
|
146
|
+
return /<key>KeepAlive<\/key>\s*<true\s*\/>/.test(xml);
|
|
147
|
+
}
|
|
126
148
|
/**
|
|
127
149
|
* Read liveness out of `launchctl print gui/<uid>/<label>`.
|
|
128
150
|
*
|
|
@@ -139,6 +161,10 @@ export function parseLaunchctlPrint(out) {
|
|
|
139
161
|
...(pid !== null ? { pid: pid[1] } : {}),
|
|
140
162
|
};
|
|
141
163
|
}
|
|
164
|
+
/** One phrasing for "launchctl said no", so every caller reports it the same way. */
|
|
165
|
+
function launchctlFailure(verb, r) {
|
|
166
|
+
return (`launchctl ${verb} failed (exit ${String(r.status)}).\n` + `${r.stderr || r.stdout}`.trim());
|
|
167
|
+
}
|
|
142
168
|
function target() {
|
|
143
169
|
return `gui/${process.getuid?.() ?? 0}/${LAUNCHD_LABEL}`;
|
|
144
170
|
}
|
|
@@ -164,10 +190,8 @@ export function createLaunchdBackend(run, homeDir = os.homedir()) {
|
|
|
164
190
|
// they are shims over this one that report success in cases where it
|
|
165
191
|
// reports the actual error.
|
|
166
192
|
const boot = run("launchctl", ["bootstrap", domain(), file]);
|
|
167
|
-
if (boot.status !== 0)
|
|
168
|
-
throw new Error(
|
|
169
|
-
`${boot.stderr || boot.stdout}`.trim());
|
|
170
|
-
}
|
|
193
|
+
if (boot.status !== 0)
|
|
194
|
+
throw new Error(launchctlFailure("bootstrap", boot));
|
|
171
195
|
// `RunAtLoad` should already have started it; `kickstart -k` makes that
|
|
172
196
|
// true rather than probable, and is the same command a later restart uses.
|
|
173
197
|
run("launchctl", ["kickstart", "-k", target()]);
|
|
@@ -179,10 +203,59 @@ export function createLaunchdBackend(run, homeDir = os.homedir()) {
|
|
|
179
203
|
fs.rmSync(file, { force: true });
|
|
180
204
|
return existed;
|
|
181
205
|
},
|
|
206
|
+
/**
|
|
207
|
+
* Load the job back into the GUI domain and make sure it is running (#873).
|
|
208
|
+
*
|
|
209
|
+
* `bootstrap`, not `launchctl start`: `stop` below BOOTS OUT, so a stopped
|
|
210
|
+
* Paddock is not merely idle, it is absent from the domain — and `start` on
|
|
211
|
+
* a label launchd has never heard of fails. Bootstrapping is also what a
|
|
212
|
+
* login does, so `service start` and the next login take the same path.
|
|
213
|
+
*/
|
|
214
|
+
start() {
|
|
215
|
+
const boot = run("launchctl", ["bootstrap", domain(), file]);
|
|
216
|
+
if (boot.status !== 0)
|
|
217
|
+
throw new Error(launchctlFailure("bootstrap", boot));
|
|
218
|
+
run("launchctl", ["kickstart", target()]);
|
|
219
|
+
},
|
|
220
|
+
/**
|
|
221
|
+
* `bootout`, not `launchctl stop`.
|
|
222
|
+
*
|
|
223
|
+
* With #872's unconditional `KeepAlive`, `stop` is a request launchd
|
|
224
|
+
* immediately overrules — it stops the process and then relaunches it,
|
|
225
|
+
* which is exactly the wrestling match the old conditional KeepAlive was
|
|
226
|
+
* meant to avoid. `bootout` unloads the job, so KeepAlive no longer applies
|
|
227
|
+
* and it stays down. The plist is untouched, so `status` still reports it
|
|
228
|
+
* registered, `start` puts it back, and the next LOGIN starts it again via
|
|
229
|
+
* `RunAtLoad` — a stop is for this session, not forever. Uninstall is the
|
|
230
|
+
* one that is forever.
|
|
231
|
+
*/
|
|
232
|
+
stop() {
|
|
233
|
+
const out = run("launchctl", ["bootout", target()]);
|
|
234
|
+
// Exit 3 / "No such process" is launchd for "it was not loaded", which is
|
|
235
|
+
// the state the caller asked for. Anything else is worth surfacing.
|
|
236
|
+
if (out.status !== 0 && !/no such process/i.test(`${out.stderr}${out.stdout}`)) {
|
|
237
|
+
throw new Error(launchctlFailure("bootout", out));
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
/**
|
|
241
|
+
* Boot out, bootstrap, kick. Not `kickstart -k` alone, which needs the job
|
|
242
|
+
* to be loaded and so fails on the case `restart` most needs to handle: a
|
|
243
|
+
* service that is stopped. This sequence is identical to `install`'s minus
|
|
244
|
+
* writing the plist, which also gives `restart` the property the issue asks
|
|
245
|
+
* for — the unit is re-read on the way up, so an edited plist takes effect.
|
|
246
|
+
*/
|
|
247
|
+
restart() {
|
|
248
|
+
run("launchctl", ["bootout", target()]);
|
|
249
|
+
const boot = run("launchctl", ["bootstrap", domain(), file]);
|
|
250
|
+
if (boot.status !== 0)
|
|
251
|
+
throw new Error(launchctlFailure("bootstrap", boot));
|
|
252
|
+
run("launchctl", ["kickstart", "-k", target()]);
|
|
253
|
+
},
|
|
182
254
|
status() {
|
|
183
255
|
if (!fs.existsSync(file))
|
|
184
|
-
return { registered: false, running: false, argv: [] };
|
|
185
|
-
const
|
|
256
|
+
return { registered: false, running: false, argv: [], keepsAlive: true };
|
|
257
|
+
const xml = fs.readFileSync(file, "utf8");
|
|
258
|
+
const argv = parsePlistArgv(xml);
|
|
186
259
|
const printed = run("launchctl", ["print", target()]);
|
|
187
260
|
const parsed = printed.status === 0
|
|
188
261
|
? parseLaunchctlPrint(printed.stdout)
|
|
@@ -194,6 +267,7 @@ export function createLaunchdBackend(run, homeDir = os.homedir()) {
|
|
|
194
267
|
// Strip the interpreter and script: what a reader wants from `status`
|
|
195
268
|
// is the Paddock invocation, not the absolute path to node twice.
|
|
196
269
|
argv: argv.slice(2),
|
|
270
|
+
keepsAlive: plistKeepsAlive(xml),
|
|
197
271
|
};
|
|
198
272
|
},
|
|
199
273
|
logsHint(spec) {
|
|
@@ -31,8 +31,17 @@ export declare function unitPath(homeDir?: string, xdgConfigHome?: string): stri
|
|
|
31
31
|
/**
|
|
32
32
|
* Render the unit.
|
|
33
33
|
*
|
|
34
|
-
* `Restart=
|
|
35
|
-
*
|
|
34
|
+
* `Restart=always`, matching launchd's `KeepAlive=<true/>` (#872). It was
|
|
35
|
+
* `on-failure`, which for a service is the wrong default for the same reason it
|
|
36
|
+
* was wrong there: `start.ts` exits `0` on SIGTERM, so a logout — or anything
|
|
37
|
+
* else that signals the user manager — was read as an intended stop and Paddock
|
|
38
|
+
* stayed down.
|
|
39
|
+
*
|
|
40
|
+
* `always` does not make the unit unstoppable. systemd never restarts a unit it
|
|
41
|
+
* was itself asked to stop, so `systemctl --user stop` (and therefore
|
|
42
|
+
* `uninstall`) remains the clean, explicit way to put it down. That is the
|
|
43
|
+
* difference between an exit code, which has to guess at intent, and a command,
|
|
44
|
+
* which carries it.
|
|
36
45
|
*
|
|
37
46
|
* `WantedBy=default.target` is what makes `systemctl --user enable` mean
|
|
38
47
|
* "start at login" for a user unit; `multi-user.target` is the system-unit
|
|
@@ -43,6 +52,16 @@ export declare function unitPath(homeDir?: string, xdgConfigHome?: string): stri
|
|
|
43
52
|
* answers to "where are the logs".
|
|
44
53
|
*/
|
|
45
54
|
export declare function renderUnit(spec: ServiceSpec): string;
|
|
55
|
+
/**
|
|
56
|
+
* Does an installed unit carry `Restart=always` (#872)?
|
|
57
|
+
*
|
|
58
|
+
* The counterpart to `plistKeepsAlive`, and narrow for the same reason: the
|
|
59
|
+
* value it has to reject is the `on-failure` this file used to write, which
|
|
60
|
+
* leaves Paddock down after the graceful exit `start.ts` performs on SIGTERM.
|
|
61
|
+
* Anything unrecognised reads as "not the fixed shape", which at worst tells a
|
|
62
|
+
* hand-edited unit's owner to re-run install.
|
|
63
|
+
*/
|
|
64
|
+
export declare function unitKeepsAlive(text: string): boolean;
|
|
46
65
|
/** Read `ExecStart=` back out of an installed unit, split into an argv. */
|
|
47
66
|
export declare function parseUnitArgv(text: string): string[];
|
|
48
67
|
export declare function createSystemdBackend(run: Runner, homeDir?: string, xdgConfigHome?: string): ServiceBackend;
|
|
@@ -62,8 +62,17 @@ export function unitPath(homeDir, xdgConfigHome) {
|
|
|
62
62
|
/**
|
|
63
63
|
* Render the unit.
|
|
64
64
|
*
|
|
65
|
-
* `Restart=
|
|
66
|
-
*
|
|
65
|
+
* `Restart=always`, matching launchd's `KeepAlive=<true/>` (#872). It was
|
|
66
|
+
* `on-failure`, which for a service is the wrong default for the same reason it
|
|
67
|
+
* was wrong there: `start.ts` exits `0` on SIGTERM, so a logout — or anything
|
|
68
|
+
* else that signals the user manager — was read as an intended stop and Paddock
|
|
69
|
+
* stayed down.
|
|
70
|
+
*
|
|
71
|
+
* `always` does not make the unit unstoppable. systemd never restarts a unit it
|
|
72
|
+
* was itself asked to stop, so `systemctl --user stop` (and therefore
|
|
73
|
+
* `uninstall`) remains the clean, explicit way to put it down. That is the
|
|
74
|
+
* difference between an exit code, which has to guess at intent, and a command,
|
|
75
|
+
* which carries it.
|
|
67
76
|
*
|
|
68
77
|
* `WantedBy=default.target` is what makes `systemctl --user enable` mean
|
|
69
78
|
* "start at login" for a user unit; `multi-user.target` is the system-unit
|
|
@@ -84,13 +93,25 @@ Type=simple
|
|
|
84
93
|
ExecStart=${exec}
|
|
85
94
|
WorkingDirectory=${spec.workingDirectory}
|
|
86
95
|
Environment=PATH=${spec.pathEnv}
|
|
87
|
-
Restart=
|
|
96
|
+
Restart=always
|
|
88
97
|
RestartSec=10
|
|
89
98
|
|
|
90
99
|
[Install]
|
|
91
100
|
WantedBy=default.target
|
|
92
101
|
`;
|
|
93
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Does an installed unit carry `Restart=always` (#872)?
|
|
105
|
+
*
|
|
106
|
+
* The counterpart to `plistKeepsAlive`, and narrow for the same reason: the
|
|
107
|
+
* value it has to reject is the `on-failure` this file used to write, which
|
|
108
|
+
* leaves Paddock down after the graceful exit `start.ts` performs on SIGTERM.
|
|
109
|
+
* Anything unrecognised reads as "not the fixed shape", which at worst tells a
|
|
110
|
+
* hand-edited unit's owner to re-run install.
|
|
111
|
+
*/
|
|
112
|
+
export function unitKeepsAlive(text) {
|
|
113
|
+
return /^Restart=always\s*$/m.test(text);
|
|
114
|
+
}
|
|
94
115
|
/** Read `ExecStart=` back out of an installed unit, split into an argv. */
|
|
95
116
|
export function parseUnitArgv(text) {
|
|
96
117
|
const m = /^ExecStart=(.*)$/m.exec(text);
|
|
@@ -98,6 +119,14 @@ export function parseUnitArgv(text) {
|
|
|
98
119
|
return [];
|
|
99
120
|
return m[1].trim().split(/\s+/).filter(Boolean);
|
|
100
121
|
}
|
|
122
|
+
/** Run a `systemctl` and turn a non-zero exit into the manager's own words. */
|
|
123
|
+
function systemctlOrThrow(run, args) {
|
|
124
|
+
const r = run("systemctl", args);
|
|
125
|
+
if (r.status === 0)
|
|
126
|
+
return;
|
|
127
|
+
throw new Error(`systemctl ${args.join(" ")} failed (exit ${String(r.status)}).\n` +
|
|
128
|
+
`${r.stderr || r.stdout}`.trim());
|
|
129
|
+
}
|
|
101
130
|
export function createSystemdBackend(run, homeDir, xdgConfigHome) {
|
|
102
131
|
// Both forwarded, neither defaulted here: `unitPath` owns the precedence, and
|
|
103
132
|
// defaulting `homeDir` to `os.homedir()` at this layer would collapse "the
|
|
@@ -129,10 +158,33 @@ export function createSystemdBackend(run, homeDir, xdgConfigHome) {
|
|
|
129
158
|
run("systemctl", ["--user", "daemon-reload"]);
|
|
130
159
|
return existed;
|
|
131
160
|
},
|
|
161
|
+
/**
|
|
162
|
+
* The lifecycle three (#873), which on systemd are just the verbs.
|
|
163
|
+
*
|
|
164
|
+
* None of them touches `enable`/`disable`: those decide whether Paddock
|
|
165
|
+
* comes back at your NEXT login, which is a different question from whether
|
|
166
|
+
* it is running now, and quietly answering both would make `stop` a
|
|
167
|
+
* surprise the following morning. `uninstall` is where that link is cut.
|
|
168
|
+
*
|
|
169
|
+
* `restart` re-reads the unit itself, but a `daemon-reload` first is what
|
|
170
|
+
* makes an EDITED unit take effect rather than the manager's cached copy —
|
|
171
|
+
* the same reason `install` reloads before enabling.
|
|
172
|
+
*/
|
|
173
|
+
start() {
|
|
174
|
+
systemctlOrThrow(run, ["--user", "start", SYSTEMD_UNIT]);
|
|
175
|
+
},
|
|
176
|
+
stop() {
|
|
177
|
+
systemctlOrThrow(run, ["--user", "stop", SYSTEMD_UNIT]);
|
|
178
|
+
},
|
|
179
|
+
restart() {
|
|
180
|
+
run("systemctl", ["--user", "daemon-reload"]);
|
|
181
|
+
systemctlOrThrow(run, ["--user", "restart", SYSTEMD_UNIT]);
|
|
182
|
+
},
|
|
132
183
|
status() {
|
|
133
184
|
if (!fs.existsSync(file))
|
|
134
|
-
return { registered: false, running: false, argv: [] };
|
|
135
|
-
const
|
|
185
|
+
return { registered: false, running: false, argv: [], keepsAlive: true };
|
|
186
|
+
const text = fs.readFileSync(file, "utf8");
|
|
187
|
+
const argv = parseUnitArgv(text);
|
|
136
188
|
const active = run("systemctl", ["--user", "is-active", SYSTEMD_UNIT]);
|
|
137
189
|
const pid = run("systemctl", ["--user", "show", "-p", "MainPID", "--value", SYSTEMD_UNIT]);
|
|
138
190
|
const mainPid = pid.stdout.trim();
|
|
@@ -141,6 +193,7 @@ export function createSystemdBackend(run, homeDir, xdgConfigHome) {
|
|
|
141
193
|
running: active.stdout.trim() === "active",
|
|
142
194
|
...(mainPid !== "" && mainPid !== "0" ? { pid: mainPid } : {}),
|
|
143
195
|
argv: argv.slice(2),
|
|
196
|
+
keepsAlive: unitKeepsAlive(text),
|
|
144
197
|
};
|
|
145
198
|
},
|
|
146
199
|
logsHint() {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type DriveMode } from "./models.js";
|
|
2
|
+
import { type ProfileName } from "./profiles.js";
|
|
2
3
|
import { type TranscriptsMode } from "./transcripts.js";
|
|
3
4
|
import { type CredentialsMode } from "./claude-credentials.js";
|
|
4
5
|
import { type InstructionsMode } from "./claude-instructions.js";
|
|
@@ -145,6 +146,17 @@ export interface OpenApiConfig {
|
|
|
145
146
|
path: string;
|
|
146
147
|
}
|
|
147
148
|
export interface PaddockConfig {
|
|
149
|
+
/**
|
|
150
|
+
* The resolved posture profile (#878) — `paranoid` | `balanced` | `yolo`.
|
|
151
|
+
*
|
|
152
|
+
* Reported for display and provenance only; nothing reads it to make a
|
|
153
|
+
* decision. It has already been expanded into the individual posture fields
|
|
154
|
+
* below by the time this object exists, and any of those the operator set
|
|
155
|
+
* explicitly (file key or env var) has overridden the profile's value — so the
|
|
156
|
+
* profile name tells you where the UNSET levers came from, not what every
|
|
157
|
+
* lever is. See `profiles.ts` for the precedence chain.
|
|
158
|
+
*/
|
|
159
|
+
profile: ProfileName;
|
|
148
160
|
/** HTTP/WS port. */
|
|
149
161
|
port: number;
|
|
150
162
|
/** Bind host. Defaults to loopback (`127.0.0.1`) — safe by default (#435). */
|
|
@@ -485,6 +497,13 @@ export interface PaddockConfigFile {
|
|
|
485
497
|
* lenient-parsed. See `schema-version.ts` for when to bump it.
|
|
486
498
|
*/
|
|
487
499
|
schemaVersion?: number | string;
|
|
500
|
+
/**
|
|
501
|
+
* Posture profile (#878): `paranoid` | `balanced` | `yolo`. Expands to
|
|
502
|
+
* defaults for the `claude:` modes, `maxSpawnDepth` and the capability
|
|
503
|
+
* toggles; any of those set explicitly here or in the env still wins.
|
|
504
|
+
* `PADDOCK_PROFILE` overrides this key. Absent ⇒ `balanced`.
|
|
505
|
+
*/
|
|
506
|
+
profile?: string;
|
|
488
507
|
port?: number | string;
|
|
489
508
|
host?: string;
|
|
490
509
|
dataDir?: string;
|
|
@@ -680,7 +699,23 @@ export declare function loadConfigFile(dataDir: string): PaddockConfigFile;
|
|
|
680
699
|
* installed under an arbitrary user directory (`npx`, global install).
|
|
681
700
|
*/
|
|
682
701
|
export declare function resolveDefaultWebDist(moduleUrl: string): string;
|
|
683
|
-
|
|
702
|
+
/** Options for {@link loadPaddockConfig}. */
|
|
703
|
+
export interface LoadConfigOptions {
|
|
704
|
+
/**
|
|
705
|
+
* Create the data root if it is missing. Default `true` — every caller that
|
|
706
|
+
* goes on to RUN something needs it, and creating it early is what makes the
|
|
707
|
+
* symlink resolution below consistent.
|
|
708
|
+
*
|
|
709
|
+
* `paddock config show` (#878) passes `false`. Inspecting an instance must not
|
|
710
|
+
* bring one into being: otherwise running it on a machine that has never
|
|
711
|
+
* started Paddock leaves an empty `~/.paddock` behind, and the first-run
|
|
712
|
+
* welcome — which keys on that directory's absence — never prints again.
|
|
713
|
+
* Skipping the mkdir costs nothing else, because `canonical()` already handles
|
|
714
|
+
* a path whose tail does not exist yet.
|
|
715
|
+
*/
|
|
716
|
+
createDataDir?: boolean;
|
|
717
|
+
}
|
|
718
|
+
export declare function loadPaddockConfig(opts?: LoadConfigOptions): PaddockConfig;
|
|
684
719
|
/**
|
|
685
720
|
* The USER's own Claude home — `~/.claude`, always. A read-only source for
|
|
686
721
|
* adoption and the user-config bridge, never a destination (#620).
|