@edspencer/paddock 0.71.2 → 0.72.1
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 +56 -5
- package/packages/server/dist/cli/args.js +193 -31
- 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/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-DhrXvgkl.js +2 -0
- package/packages/web/dist/assets/DiscoverView-yPLCp_YE.js +2 -0
- package/packages/web/dist/assets/InstanceConfigPage-aOgiBFLq.js +2 -0
- package/packages/web/dist/assets/{MarkdownRenderer-DA5CpdGI.js → MarkdownRenderer-BjrB0TDO.js} +1 -1
- package/packages/web/dist/assets/ProjectMenu-PhwXTmgn.js +2 -0
- package/packages/web/dist/assets/{ProjectRedirect-k10uM_63.js → ProjectRedirect-N4g7WrZm.js} +1 -1
- package/packages/web/dist/assets/ProjectView-B-7AIt--.js +13 -0
- package/packages/web/dist/assets/{ProjectsGrid-DrzZ7R8M.js → ProjectsGrid-DZF5VHJF.js} +1 -1
- package/packages/web/dist/assets/RootHome-C9wEtWHu.js +2 -0
- package/packages/web/dist/assets/Surfaces-BidCkENw.js +2 -0
- package/packages/web/dist/assets/{arc-BKAVFR26.js → arc-BctfAqLY.js} +1 -1
- package/packages/web/dist/assets/architectureDiagram-3BPJPVTR-BJgwSqOO.js +37 -0
- package/packages/web/dist/assets/{blockDiagram-GPEHLZMM-Eu4BV7Ea.js → blockDiagram-GPEHLZMM-BD2skmST.js} +1 -1
- package/packages/web/dist/assets/{c4Diagram-AAUBKEIU-DR8PZaoa.js → c4Diagram-AAUBKEIU-DckMrhMv.js} +1 -1
- package/packages/web/dist/assets/channel-CsZOz8jm.js +2 -0
- package/packages/web/dist/assets/{chunk-2J33WTMH-CgvIIrI1.js → chunk-2J33WTMH-CQDUH7rp.js} +1 -1
- package/packages/web/dist/assets/{chunk-4BX2VUAB-CyJWrZqN.js → chunk-4BX2VUAB-Bae1J961.js} +1 -1
- package/packages/web/dist/assets/{chunk-55IACEB6-DXEnZN1d.js → chunk-55IACEB6-CXxS5wOa.js} +1 -1
- package/packages/web/dist/assets/{chunk-727SXJPM-Co89QTmp.js → chunk-727SXJPM-DSNFkPB3.js} +1 -1
- package/packages/web/dist/assets/{chunk-AQP2D5EJ-BRxTzfev.js → chunk-AQP2D5EJ-B2mqFyB7.js} +1 -1
- package/packages/web/dist/assets/{chunk-FMBD7UC4-DUj18CvF.js → chunk-FMBD7UC4-BfqEy3lu.js} +1 -1
- package/packages/web/dist/assets/{chunk-ND2GUHAM-BQMMJobN.js → chunk-ND2GUHAM-Bm6CBVK7.js} +1 -1
- package/packages/web/dist/assets/{chunk-QZHKN3VN-COx8LZCe.js → chunk-QZHKN3VN-NmnLtgYc.js} +1 -1
- package/packages/web/dist/assets/classDiagram-4FO5ZUOK-DdALkhE9.js +2 -0
- package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-DdALkhE9.js +2 -0
- package/packages/web/dist/assets/{core-DnpcT0s6.js → core-YKeFSK1q.js} +1 -1
- package/packages/web/dist/assets/cose-bilkent-S5V4N54A-qf_mXpa9.js +2 -0
- package/packages/web/dist/assets/{dagre-BM42HDAG-CuaYJexd.js → dagre-BM42HDAG-C31v-j3s.js} +1 -1
- package/packages/web/dist/assets/{diagram-2AECGRRQ-C9_anxrA.js → diagram-2AECGRRQ-C_TiPHdS.js} +1 -1
- package/packages/web/dist/assets/{diagram-5GNKFQAL-Ckf3kFlY.js → diagram-5GNKFQAL-BC8gUFK-.js} +1 -1
- package/packages/web/dist/assets/{diagram-KO2AKTUF-BrpUuY5x.js → diagram-KO2AKTUF-vO_QYWxi.js} +1 -1
- package/packages/web/dist/assets/{diagram-LMA3HP47-B9lyXZXW.js → diagram-LMA3HP47-vxJ-YZOg.js} +1 -1
- package/packages/web/dist/assets/{diagram-OG6HWLK6-DT7ro32m.js → diagram-OG6HWLK6-BNIfJG6l.js} +1 -1
- package/packages/web/dist/assets/{erDiagram-TEJ5UH35-ZZn9bHQh.js → erDiagram-TEJ5UH35-B37HsOFM.js} +1 -1
- package/packages/web/dist/assets/{flowDiagram-I6XJVG4X-BxQ82PY_.js → flowDiagram-I6XJVG4X-DqA5X81H.js} +1 -1
- package/packages/web/dist/assets/ganttDiagram-6RSMTGT7-BMP0zpIg.js +293 -0
- package/packages/web/dist/assets/{gitGraphDiagram-PVQCEYII-BlwODdH9.js → gitGraphDiagram-PVQCEYII-P0nXaCzx.js} +1 -1
- package/packages/web/dist/assets/index-BkY_Nc_i.css +1 -0
- package/packages/web/dist/assets/index-C3MdluUb.js +71 -0
- package/packages/web/dist/assets/{infoDiagram-5YYISTIA-nanSBFyG.js → infoDiagram-5YYISTIA-DpKYQ7VK.js} +1 -1
- package/packages/web/dist/assets/{ishikawaDiagram-YF4QCWOH-yZHr_s28.js → ishikawaDiagram-YF4QCWOH-BZeRMg3C.js} +1 -1
- package/packages/web/dist/assets/{journeyDiagram-JHISSGLW-BKjDqQEQ.js → journeyDiagram-JHISSGLW-Cg2O0qzv.js} +1 -1
- package/packages/web/dist/assets/{kanban-definition-UN3LZRKU-QusvHqc6.js → kanban-definition-UN3LZRKU-DBwVxUvI.js} +1 -1
- package/packages/web/dist/assets/{linear-4xRoD38Z.js → linear-R8qRwYqx.js} +1 -1
- package/packages/web/dist/assets/{mermaid.core-CeulYvDv.js → mermaid.core-jctTs2en.js} +4 -4
- package/packages/web/dist/assets/{mindmap-definition-RKZ34NQL-DSjYt2Kp.js → mindmap-definition-RKZ34NQL-CtmsNfpb.js} +1 -1
- package/packages/web/dist/assets/{pieDiagram-4H26LBE5-Ci0gO3CA.js → pieDiagram-4H26LBE5-C-UiLFDv.js} +1 -1
- package/packages/web/dist/assets/{quadrantDiagram-W4KKPZXB-y2qHkaHP.js → quadrantDiagram-W4KKPZXB-BS4SWa01.js} +1 -1
- package/packages/web/dist/assets/{requirementDiagram-4Y6WPE33-D9hiWbad.js → requirementDiagram-4Y6WPE33-LPPAC6Q1.js} +1 -1
- package/packages/web/dist/assets/{sankeyDiagram-5OEKKPKP-DA3Mvoze.js → sankeyDiagram-5OEKKPKP-tjF8KyS3.js} +1 -1
- package/packages/web/dist/assets/{sequenceDiagram-3UESZ5HK-DWACrljw.js → sequenceDiagram-3UESZ5HK-kmddkdk2.js} +1 -1
- package/packages/web/dist/assets/{stateDiagram-AJRCARHV-CFGEhoTb.js → stateDiagram-AJRCARHV-B07mykTY.js} +1 -1
- package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-r6WdX0dw.js +2 -0
- package/packages/web/dist/assets/{timeline-definition-PNZ67QCA-Cjygpd9e.js → timeline-definition-PNZ67QCA-DrUgi0cT.js} +1 -1
- package/packages/web/dist/assets/{vennDiagram-CIIHVFJN-zQnLaxgv.js → vennDiagram-CIIHVFJN-Dz6p0m7l.js} +1 -1
- package/packages/web/dist/assets/{wardley-L42UT6IY-DGmt-Lo2.js → wardley-L42UT6IY-DM16lA5x.js} +1 -1
- package/packages/web/dist/assets/{wardleyDiagram-YWT4CUSO-Dj3vDukN.js → wardleyDiagram-YWT4CUSO-CCFQkFGB.js} +1 -1
- package/packages/web/dist/assets/{xychartDiagram-2RQKCTM6-C0bGbwbL.js → xychartDiagram-2RQKCTM6-DiUI5Dos.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-Cp32dDmz.js +0 -2
- package/packages/web/dist/assets/DiscoverPage-Cs439wTf.js +0 -2
- package/packages/web/dist/assets/DiscoverView-DoGAlUXT.js +0 -2
- package/packages/web/dist/assets/InstanceConfigPage-BDypeo_r.js +0 -2
- package/packages/web/dist/assets/ProjectMenu-DXXXtXeh.js +0 -2
- package/packages/web/dist/assets/ProjectView-BxjcMlSO.js +0 -13
- package/packages/web/dist/assets/RootHome-rWodzOC8.js +0 -2
- package/packages/web/dist/assets/architectureDiagram-3BPJPVTR-UzIlxaCQ.js +0 -37
- package/packages/web/dist/assets/channel-C-sodtn7.js +0 -2
- package/packages/web/dist/assets/classDiagram-4FO5ZUOK-D2eBPVdq.js +0 -2
- package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-D2eBPVdq.js +0 -2
- package/packages/web/dist/assets/cose-bilkent-S5V4N54A-B-4NadKI.js +0 -2
- package/packages/web/dist/assets/ganttDiagram-6RSMTGT7-BxliloOH.js +0 -293
- package/packages/web/dist/assets/index-Cx9y6c3f.js +0 -71
- package/packages/web/dist/assets/index-bC5uCujx.css +0 -1
- package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-CLfbDXLX.js +0 -2
package/package.json
CHANGED
|
@@ -238,6 +238,33 @@ export async function buildApp(opts = {}) {
|
|
|
238
238
|
catch (err) {
|
|
239
239
|
app.log.error({ err }, "FleetManager init/start failed — chat will be unavailable");
|
|
240
240
|
}
|
|
241
|
+
// #708: this instance ran `claude.transcripts: host` and now runs `own`, so
|
|
242
|
+
// `init` just removed the `.chats` symlinks that period left behind. Until it
|
|
243
|
+
// did, `own` was not isolating (new chats were landing in the user's real
|
|
244
|
+
// Claude home) and a chat delete unlinked the user's own history.
|
|
245
|
+
//
|
|
246
|
+
// Warned rather than silent, because there IS a visible consequence: the chats
|
|
247
|
+
// written while `host` was on are still on disk but no longer in paddock's
|
|
248
|
+
// list. The recovery is deliberately split, and stated that way because it was
|
|
249
|
+
// MEASURED on a real flipped instance rather than assumed — `Import chats`
|
|
250
|
+
// offers a terminal session, but `listAdoptableSessions` excludes anything a
|
|
251
|
+
// run record is attributed to, which is every chat paddock itself drove during
|
|
252
|
+
// the `host` period. Promising a one-click recovery for those would be a lie;
|
|
253
|
+
// moving them is the migration #882 is designing.
|
|
254
|
+
if (herdctl.unplantedChatsLinks.length > 0) {
|
|
255
|
+
app.log.warn(`${herdctl.unplantedChatsLinks.length} project store(s) still pointed at another ` +
|
|
256
|
+
`transcript folder from a previous \`claude.transcripts: host\` setting (#708). ` +
|
|
257
|
+
`While that link existed, \`own\` was not isolating and deleting a chat removed the ` +
|
|
258
|
+
`file it pointed at. Paddock has replaced each link with a real directory and has ` +
|
|
259
|
+
`neither read nor moved anything at the other end. The chats written during that ` +
|
|
260
|
+
`period are still in the folder on the right, and are no longer in paddock's list: ` +
|
|
261
|
+
`ones you started in a terminal can be brought back with "Import chats"; ones ` +
|
|
262
|
+
`paddock itself ran are attributed to this instance and are not offered for import ` +
|
|
263
|
+
`(moving those is #882).\n` +
|
|
264
|
+
herdctl.unplantedChatsLinks
|
|
265
|
+
.map((l) => ` ${l.chatsDir} -> ${l.target}`)
|
|
266
|
+
.join("\n"));
|
|
267
|
+
}
|
|
241
268
|
// --- post-turn curation sweep (overview + changelog) -------------------
|
|
242
269
|
const sweep = new SweepService({
|
|
243
270
|
herdctl,
|
|
@@ -31,6 +31,42 @@ export interface CliOptions {
|
|
|
31
31
|
verbose: boolean;
|
|
32
32
|
help: boolean;
|
|
33
33
|
version: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* `config show`: print every effective value with its provenance, rather than
|
|
36
|
+
* just the decisions someone actually made.
|
|
37
|
+
*
|
|
38
|
+
* The three `config` flags below are OPTIONAL rather than defaulted booleans,
|
|
39
|
+
* unlike the four above. That is not tidiness — `parseArgs([])` is compared
|
|
40
|
+
* structurally in several tests and is the documented shape of "no flags", so
|
|
41
|
+
* adding always-present keys to it would change the meaning of an invocation
|
|
42
|
+
* that has nothing to do with `config`. Absent-when-unset is also what the
|
|
43
|
+
* value flags (`port`, `host`, `dataDir`) already do.
|
|
44
|
+
*/
|
|
45
|
+
resolved?: boolean;
|
|
46
|
+
/** `config show`: emit the whole report as JSON instead of a table. */
|
|
47
|
+
json?: boolean;
|
|
48
|
+
/** `config show`: print the values of fields marked sensitive. */
|
|
49
|
+
showSensitive?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* `config eject`: actually write the file. Without it the command prints the
|
|
52
|
+
* plan and changes nothing.
|
|
53
|
+
*
|
|
54
|
+
* Preview-by-default is the deliberate shape, and it is not timidity about
|
|
55
|
+
* writing a file: ejecting is the one config operation that changes what the
|
|
56
|
+
* file MEANS rather than what it says. Afterwards the instance stops inheriting
|
|
57
|
+
* improved defaults, and the cost is spread across forty keys where no single
|
|
58
|
+
* one looks like a decision. `--write` is the moment the operator agrees to
|
|
59
|
+
* that, having just read the specific list.
|
|
60
|
+
*
|
|
61
|
+
* A flag rather than an interactive confirm because a prompt cannot be answered
|
|
62
|
+
* by the CI job, container build or Ansible task that is the likeliest caller.
|
|
63
|
+
*/
|
|
64
|
+
write?: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* `config eject`: also materialise values an environment variable currently
|
|
67
|
+
* supplies. Off by default — see the argument in config.ts.
|
|
68
|
+
*/
|
|
69
|
+
includeEnv?: boolean;
|
|
34
70
|
}
|
|
35
71
|
/** A usage error. Thrown rather than exiting, so parsing stays testable. */
|
|
36
72
|
export declare class CliError extends Error {
|
|
@@ -47,14 +83,24 @@ export declare const MIN_NODE_MAJOR = 22;
|
|
|
47
83
|
*/
|
|
48
84
|
export declare const SERVICE_ACTIONS: readonly ["install", "uninstall", "status", "start", "stop", "restart"];
|
|
49
85
|
export type ServiceAction = (typeof SERVICE_ACTIONS)[number];
|
|
86
|
+
/**
|
|
87
|
+
* What `paddock config` can be asked to do (#878).
|
|
88
|
+
*
|
|
89
|
+
* `show` reads the resolution; `eject` freezes it into the config file. The two
|
|
90
|
+
* are the halves of #878's "thin by default, explicit on demand" — and they are
|
|
91
|
+
* one verb rather than two because the honest way to present `eject` is next to
|
|
92
|
+
* the command that makes it unnecessary.
|
|
93
|
+
*/
|
|
94
|
+
export declare const CONFIG_ACTIONS: readonly ["show", "eject"];
|
|
95
|
+
export type ConfigAction = (typeof CONFIG_ACTIONS)[number];
|
|
50
96
|
/** The leading words {@link parseCommand} recognises. Anything else is a flag. */
|
|
51
|
-
export declare const VERBS: readonly ["start", "service"];
|
|
97
|
+
export declare const VERBS: readonly ["start", "service", "config"];
|
|
52
98
|
/**
|
|
53
99
|
* A parsed invocation: which verb, plus the flags that followed it.
|
|
54
100
|
*
|
|
55
|
-
* `action` is optional on `service` for exactly one reason —
|
|
56
|
-
* --help`, where there is no action to name and printing usage
|
|
57
|
-
* request. It is `undefined` only when `opts.help` is true.
|
|
101
|
+
* `action` is optional on `service` and `config` for exactly one reason —
|
|
102
|
+
* `paddock service --help`, where there is no action to name and printing usage
|
|
103
|
+
* is the whole request. It is `undefined` only when `opts.help` is true.
|
|
58
104
|
*/
|
|
59
105
|
export type Command = {
|
|
60
106
|
verb: "start";
|
|
@@ -63,6 +109,10 @@ export type Command = {
|
|
|
63
109
|
verb: "service";
|
|
64
110
|
action: ServiceAction | undefined;
|
|
65
111
|
opts: CliOptions;
|
|
112
|
+
} | {
|
|
113
|
+
verb: "config";
|
|
114
|
+
action: ConfigAction | undefined;
|
|
115
|
+
opts: CliOptions;
|
|
66
116
|
};
|
|
67
117
|
/**
|
|
68
118
|
* Split a leading verb off the argv, then parse the rest as flags.
|
|
@@ -107,5 +157,6 @@ export declare function nodeVersionProblem(nodeVersion: string): string | undefi
|
|
|
107
157
|
* names neither the port nor the flag that fixes it.
|
|
108
158
|
*/
|
|
109
159
|
export declare function explainListenError(err: unknown, host: string, port: string): string;
|
|
110
|
-
export declare const USAGE = "paddock \u2014 run a Paddock instance locally\n\nUsage\n npx @edspencer/paddock [options] start the server (the default)\n paddock start [options] the same thing, said out loud\n paddock service <install|uninstall|status>\n run it in the background from login\n (`paddock service --help`)\n\nOptions\n -p, --port <port> HTTP/WS port (default 7233, 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 -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 your own directories\n Where you run this from does not matter \u2014 the instance is decided by its data\n dir alone. Directories are added inside the app, on the Discover screen: it\n reads your Claude Code history, lists the directories you have worked in, and\n links the ones you tick as projects. A new instance opens on it.\n\n Importing a directory does not write into it. No .paddock/, no .chats/, no\n .gitignore edits, no CLAUDE.md \u2014 the project record and the copied transcripts\n both live in the data dir, and the project just points at the path. Your own\n ~/.claude transcripts are copied, never moved or deleted, so your terminal\n `claude` keeps working exactly as before.\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
|
|
160
|
+
export declare const USAGE = "paddock \u2014 run a Paddock instance locally\n\nUsage\n npx @edspencer/paddock [options] start the server (the default)\n paddock start [options] the same thing, said out loud\n paddock service <install|uninstall|status>\n run it in the background from login\n (`paddock service --help`)\n paddock config show [--resolved] what this instance's config resolved\n to, and where each value came from\n paddock config eject [--write] freeze that resolution into\n paddock.config.yaml\n (`paddock config --help`)\n\nOptions\n -p, --port <port> HTTP/WS port (default 7233, 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 -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 your own directories\n Where you run this from does not matter \u2014 the instance is decided by its data\n dir alone. Directories are added inside the app, on the Discover screen: it\n reads your Claude Code history, lists the directories you have worked in, and\n links the ones you tick as projects. A new instance opens on it.\n\n Importing a directory does not write into it. No .paddock/, no .chats/, no\n .gitignore edits, no CLAUDE.md \u2014 the project record and the copied transcripts\n both live in the data dir, and the project just points at the path. Your own\n ~/.claude transcripts are copied, never moved or deleted, so your terminal\n `claude` keeps working exactly as before.\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\nPosture profiles\n One key picks how much Paddock shares and how much its agents may do:\n\n profile: balanced # paranoid | balanced | yolo (PADDOCK_PROFILE)\n\n paranoid shares nothing but the login and turns every capability off \u2014 the\n behaviour Paddock had before profiles existed. balanced (the default)\n inherits the CAPABILITIES your CLI already has \u2014 instructions, MCP servers,\n plugins \u2014 and adds the read-only self-management tools, while keeping your\n chat history Paddock's own. yolo turns the rest on: your transcripts, host\n hooks, the write and project tools, schedule mutation, deeper spawning, the\n browser.\n\n A profile only sets defaults \u2014 any single key still overrides it, and an\n individual key in the config file beats PADDOCK_PROFILE in the environment.\n Profiles never touch your port, bind address, auth or model list: yolo does\n NOT open the bind or relax auth, which stay a separate, explicit decision.\n\n What host actually reaches depends on the machine \u2014 a workstation has a real\n ~/.claude, a container usually has none. The profile sets the levers, the\n environment sets the blast radius.\n\n To see what your profile actually expanded to on THIS machine, with the layer\n each value came from: paddock config show --resolved\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, and each defaults to whatever your profile\n says (see Posture profiles below):\n\n claude:\n transcripts: host # own | host host on yolo only\n credentials: host # own | host host on every profile\n instructions: host # own | host own on paranoid, else host\n hooks: host # own | host host on yolo only\n mcpServers: host # own | host own on paranoid, else host\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/. On from balanced up, so your curated CLAUDE.md does reach Paddock's\n agents; profile: paranoid or instructions: own keeps it out. Each project's\n own CLAUDE.md applies either way.\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\n mcpServers: host attaches the MCP servers declared in your ~/.claude.json \u2014\n the top-level ones, plus a project's own when its directory matches. To give\n this instance a server your machine doesn't have, declare it instead in a\n sibling mcpServers: block of the same file; use env:VAR_NAME anywhere a\n string goes so tokens stay out of it. That keeps a token out of the file, not\n out of `ps`: leave driveMode on its default (session) for a server holding\n one, since batch passes the definition to claude as a command-line argument.\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";
|
|
111
161
|
export declare const SERVICE_USAGE = "paddock service \u2014 keep Paddock running in the background\n\nUsage\n paddock service install [options] register it and start it now\n paddock service uninstall stop it and deregister it\n paddock service status is it registered, is it running, where are the logs\n paddock service start start an installed service that is stopped\n paddock service stop stop it, and leave it installed\n paddock service restart stop it and start it again, re-reading config\n\nOptions (install only \u2014 recorded in the generated unit)\n -p, --port <port> HTTP/WS port (default 7233)\n --host <host> Bind address (default 127.0.0.1)\n -d, --data-dir <path> Only if you want an instance SEPARATE from your\n terminal one. Left out of the unit when you omit it\n AND `PADDOCK_DATA_DIR` is unset in this shell, so\n `paddock service` and a bare `paddock` are the same\n ~/.paddock instance reached two ways. If\n `PADDOCK_DATA_DIR` IS set here, that path is recorded\n in the unit \u2014 the service would otherwise point\n somewhere your terminal does not.\n --verbose Record the server's own logs, not just warnings\n\nAt login, not at boot\n This registers a per-USER service \u2014 a launchd LaunchAgent on macOS, a\n `systemd --user` unit on Linux \u2014 so it runs as you, with your own Claude\n login. That is not incidental: on macOS your Claude login is a Keychain item\n that only a logged-in user session can read. A boot-time system daemon has no\n such session and could not use it.\n\n So Paddock starts when you LOG IN, not when the machine boots. After a\n restart that nobody logs into, Paddock is not running. That is the design, not\n a fault.\n\n On Linux, a user service is also stopped when you log out. To keep it up:\n\n loginctl enable-linger $USER\n\nWhere it lives\n macOS ~/Library/LaunchAgents/net.edspencer.paddock.plist\n logs in <data-dir>/service/\n Linux ~/.config/systemd/user/paddock.service\n logs via journalctl --user -u paddock.service -f\n\nInstalled from npx?\n `service install` refuses. An npx cache path is hash-keyed and npm may prune\n it, so the unit would work until it silently didn't, at some future login.\n Install properly first:\n\n npm i -g @edspencer/paddock && paddock service install\n\nA note on access\n Paddock binds loopback with authentication off, which is right for a laptop.\n A service is up for as long as you are logged in rather than as long as a\n terminal tab, so that window is longer \u2014 but it is not wider: any local\n process that could reach the port could already read the same Claude login as\n you. Set PADDOCK_AUTH_MODE if you want a credential on it anyway.\n\nDocs: https://github.com/edspencer/paddock";
|
|
162
|
+
export declare const CONFIG_USAGE = "paddock config \u2014 what this instance's configuration actually resolved to\n\nUsage\n paddock config show the decisions: your profile, the keys your\n config file sets, the variables your\n environment sets\n paddock config show --resolved EVERY effective value, and which layer it\n came from\n paddock config show --json the same report as JSON, long values in\n full (sensitive ones still hidden)\n paddock config eject what freezing that resolution into\n paddock.config.yaml would write \u2014 a\n preview; writes nothing\n paddock config eject --write actually write it\n\nOptions\n -d, --data-dir <path> Which instance to inspect (default ~/.paddock, or\n $PADDOCK_DATA_DIR) \u2014 the same rule `paddock start`\n uses, so the two always read the same instance\n --resolved show: every field, not just the ones someone set\n --json show: the whole report as JSON (implies --resolved's\n scope)\n --show-sensitive show: print the values of fields marked sensitive\n --write eject: apply the plan. Without it, eject only prints\n --include-env eject: also freeze values an environment variable\n currently supplies (see below \u2014 off by default)\n -h, --help Show this help\n\nWhere a value can come from\n Config resolves in four layers, and each row of --resolved names the one that\n won:\n\n default Paddock's built-in default.\n profile (<name>) Your posture profile. The twelve levers a profile governs\n have no code default of their own any more \u2014 the profile\n IS their default \u2014 so this is a distinct answer from\n \"default\", and switching profile would change it.\n file A key in paddock.config.yaml.\n env <NAME> An environment variable, which beats the file for the\n same key.\n\n One wrinkle worth knowing, and the command shows it plainly: an individual key\n in the FILE beats PADDOCK_PROFILE in the environment. Specific beats general \u2014\n PADDOCK_PROFILE speaks for the levers you did not mention.\n\nWhy show, and not just write it all into the file\n `show` is computed by the loader the server boots with, so it cannot drift\n from what the process would actually do. A materialized file can: it stops\n inheriting improved defaults, says nothing about the variables your container\n sets on top, and no longer describes the whole surface the day a lever is\n added. That is why the thin file is the default and `show` is the answer to\n \"what am I actually running?\".\n\n `eject` is there for when you want it written down anyway \u2014 frozen, in the\n file, reviewable in git. It is a real tradeoff rather than a better `show`,\n so it prints what it would cost you and writes nothing until you pass --write.\n\nWhat eject writes, and what it leaves alone\n It writes the posture levers your profile expanded to, plus the behavioural\n settings the Config screen already round-trips through this file. It does NOT\n write:\n\n the bindings port, host, data dir, projects root, state dir, web dist.\n These resolve to absolute paths on THIS machine, and the\n data dir resolves to the directory holding the file \u2014 so\n ejecting them makes the file unportable and unmountable.\n sensitive keys transcription.endpoint can carry credentials in its URL.\n Bulk-writing a secret-shaped value to disk should not be\n one keystroke away; write that key by hand.\n env-owned keys by default. An environment variable BEATS the file, so\n writing its value in changes nothing today and everything\n the day the variable goes away \u2014 a deferred, silent change\n of provenance. eject names each one it skipped instead.\n --include-env writes them, which is the right flag if you\n are deliberately migrating from env vars to a file.\n\n It always writes `profile:` as well, even though every key that profile\n governs is now explicit. That line is what a lever added in a FUTURE release\n falls back to: without it, a new capability toggle would resolve against the\n built-in default profile rather than the posture you actually ejected from.\n\n Your comments and any keys Paddock does not manage survive \u2014 the file is\n round-tripped, not regenerated \u2014 and the write is atomic.\n\nReading nothing, writing nothing\n `show` resolves config and prints it. It starts no server, contacts nothing,\n and writes no file \u2014 including the data dir itself, which it will report as\n missing rather than create. `eject` writes exactly one file, and only with\n --write.\n\n Values are printed to a terminal that often ends up pasted into an issue, so\n fields marked sensitive are shown as (hidden). None of them is a secret \u2014\n Paddock keeps secrets out of this surface entirely, so there is no API key or\n JWT signing material here to hide or reveal.\n\nDocs: https://github.com/edspencer/paddock";
|
|
@@ -44,10 +44,42 @@ export const SERVICE_ACTIONS = [
|
|
|
44
44
|
"stop",
|
|
45
45
|
"restart",
|
|
46
46
|
];
|
|
47
|
+
/**
|
|
48
|
+
* What `paddock config` can be asked to do (#878).
|
|
49
|
+
*
|
|
50
|
+
* `show` reads the resolution; `eject` freezes it into the config file. The two
|
|
51
|
+
* are the halves of #878's "thin by default, explicit on demand" — and they are
|
|
52
|
+
* one verb rather than two because the honest way to present `eject` is next to
|
|
53
|
+
* the command that makes it unnecessary.
|
|
54
|
+
*/
|
|
55
|
+
export const CONFIG_ACTIONS = ["show", "eject"];
|
|
47
56
|
/** The leading words {@link parseCommand} recognises. Anything else is a flag. */
|
|
48
|
-
export const VERBS = ["start", "service"];
|
|
49
|
-
|
|
50
|
-
|
|
57
|
+
export const VERBS = ["start", "service", "config"];
|
|
58
|
+
/**
|
|
59
|
+
* Split an action off a verb's argv, then parse what follows as flags.
|
|
60
|
+
*
|
|
61
|
+
* Shared by `service` and `config` so the two cannot drift: a misspelled action
|
|
62
|
+
* is caught HERE rather than reaching the flag loop, which would call it an
|
|
63
|
+
* "unknown option" and send the reader looking for a flag that does not exist.
|
|
64
|
+
*/
|
|
65
|
+
function parseAction(verb, actions, rest) {
|
|
66
|
+
const head = rest[0];
|
|
67
|
+
let action;
|
|
68
|
+
if (head !== undefined && !head.startsWith("-")) {
|
|
69
|
+
if (!actions.includes(head)) {
|
|
70
|
+
throw new CliError(`unknown ${verb} action: ${head}\n` +
|
|
71
|
+
`Expected one of: ${actions.join(", ")}.\n` +
|
|
72
|
+
`Run \`paddock ${verb} --help\` for usage.`);
|
|
73
|
+
}
|
|
74
|
+
action = head;
|
|
75
|
+
}
|
|
76
|
+
const opts = parseArgs(action === undefined ? rest : rest.slice(1));
|
|
77
|
+
// `--help` wins over a missing action: asking for usage is not a usage error.
|
|
78
|
+
if (action === undefined && !opts.help) {
|
|
79
|
+
throw new CliError(`\`paddock ${verb}\` needs an action: ${actions.join(", ")}.\n` +
|
|
80
|
+
`Run \`paddock ${verb} --help\` for usage.`);
|
|
81
|
+
}
|
|
82
|
+
return { action, opts };
|
|
51
83
|
}
|
|
52
84
|
/**
|
|
53
85
|
* Split a leading verb off the argv, then parse the rest as flags.
|
|
@@ -78,25 +110,10 @@ function isServiceAction(token) {
|
|
|
78
110
|
export function parseCommand(argv) {
|
|
79
111
|
const [first, ...rest] = argv;
|
|
80
112
|
if (first === "service") {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
// it an "unknown option", which sends the reader looking for a flag.
|
|
86
|
-
if (!isServiceAction(head)) {
|
|
87
|
-
throw new CliError(`unknown service action: ${head}\n` +
|
|
88
|
-
`Expected one of: ${SERVICE_ACTIONS.join(", ")}.\n` +
|
|
89
|
-
"Run `paddock service --help` for usage.");
|
|
90
|
-
}
|
|
91
|
-
action = head;
|
|
92
|
-
}
|
|
93
|
-
const opts = parseArgs(action === undefined ? rest : rest.slice(1));
|
|
94
|
-
// `--help` wins over a missing action: asking for usage is not a usage error.
|
|
95
|
-
if (action === undefined && !opts.help) {
|
|
96
|
-
throw new CliError(`\`paddock service\` needs an action: ${SERVICE_ACTIONS.join(", ")}.\n` +
|
|
97
|
-
"Run `paddock service --help` for usage.");
|
|
98
|
-
}
|
|
99
|
-
return { verb: "service", action, opts };
|
|
113
|
+
return { verb: "service", ...parseAction("service", SERVICE_ACTIONS, rest) };
|
|
114
|
+
}
|
|
115
|
+
if (first === "config") {
|
|
116
|
+
return { verb: "config", ...parseAction("config", CONFIG_ACTIONS, rest) };
|
|
100
117
|
}
|
|
101
118
|
if (first === "start")
|
|
102
119
|
return { verb: "start", opts: parseArgs(rest) };
|
|
@@ -150,6 +167,24 @@ export function parseArgs(argv) {
|
|
|
150
167
|
case "--verbose":
|
|
151
168
|
opts.verbose = true;
|
|
152
169
|
break;
|
|
170
|
+
// `config` flags. Accepted in every position for the reason
|
|
171
|
+
// `parseCommand` documents — one flag grammar everywhere — which is also
|
|
172
|
+
// why `service install --open` has always been silently harmless.
|
|
173
|
+
case "--resolved":
|
|
174
|
+
opts.resolved = true;
|
|
175
|
+
break;
|
|
176
|
+
case "--json":
|
|
177
|
+
opts.json = true;
|
|
178
|
+
break;
|
|
179
|
+
case "--show-sensitive":
|
|
180
|
+
opts.showSensitive = true;
|
|
181
|
+
break;
|
|
182
|
+
case "--write":
|
|
183
|
+
opts.write = true;
|
|
184
|
+
break;
|
|
185
|
+
case "--include-env":
|
|
186
|
+
opts.includeEnv = true;
|
|
187
|
+
break;
|
|
153
188
|
case "-h":
|
|
154
189
|
case "--help":
|
|
155
190
|
opts.help = true;
|
|
@@ -205,6 +240,11 @@ Usage
|
|
|
205
240
|
paddock service <install|uninstall|status>
|
|
206
241
|
run it in the background from login
|
|
207
242
|
(\`paddock service --help\`)
|
|
243
|
+
paddock config show [--resolved] what this instance's config resolved
|
|
244
|
+
to, and where each value came from
|
|
245
|
+
paddock config eject [--write] freeze that resolution into
|
|
246
|
+
paddock.config.yaml
|
|
247
|
+
(\`paddock config --help\`)
|
|
208
248
|
|
|
209
249
|
Options
|
|
210
250
|
-p, --port <port> HTTP/WS port (default 7233, or $PORT)
|
|
@@ -238,18 +278,44 @@ Credentials
|
|
|
238
278
|
login at all anywhere, run \`claude setup-token\`. Paddock says at startup when
|
|
239
279
|
it can find none.
|
|
240
280
|
|
|
281
|
+
Posture profiles
|
|
282
|
+
One key picks how much Paddock shares and how much its agents may do:
|
|
283
|
+
|
|
284
|
+
profile: balanced # paranoid | balanced | yolo (PADDOCK_PROFILE)
|
|
285
|
+
|
|
286
|
+
paranoid shares nothing but the login and turns every capability off — the
|
|
287
|
+
behaviour Paddock had before profiles existed. balanced (the default)
|
|
288
|
+
inherits the CAPABILITIES your CLI already has — instructions, MCP servers,
|
|
289
|
+
plugins — and adds the read-only self-management tools, while keeping your
|
|
290
|
+
chat history Paddock's own. yolo turns the rest on: your transcripts, host
|
|
291
|
+
hooks, the write and project tools, schedule mutation, deeper spawning, the
|
|
292
|
+
browser.
|
|
293
|
+
|
|
294
|
+
A profile only sets defaults — any single key still overrides it, and an
|
|
295
|
+
individual key in the config file beats PADDOCK_PROFILE in the environment.
|
|
296
|
+
Profiles never touch your port, bind address, auth or model list: yolo does
|
|
297
|
+
NOT open the bind or relax auth, which stay a separate, explicit decision.
|
|
298
|
+
|
|
299
|
+
What host actually reaches depends on the machine — a workstation has a real
|
|
300
|
+
~/.claude, a container usually has none. The profile sets the levers, the
|
|
301
|
+
environment sets the blast radius.
|
|
302
|
+
|
|
303
|
+
To see what your profile actually expanded to on THIS machine, with the layer
|
|
304
|
+
each value came from: paddock config show --resolved
|
|
305
|
+
|
|
241
306
|
Sharing your Claude Code state
|
|
242
307
|
Apart from that login, Paddock writes nothing outside its data dir by
|
|
243
308
|
default: transcripts go to each project's .chats/, and your ~/.claude is read
|
|
244
309
|
for config only. Each thing it can share is one key in
|
|
245
|
-
<data-dir>/paddock.config.yaml
|
|
310
|
+
<data-dir>/paddock.config.yaml, and each defaults to whatever your profile
|
|
311
|
+
says (see Posture profiles below):
|
|
246
312
|
|
|
247
313
|
claude:
|
|
248
|
-
transcripts: host # own | host
|
|
249
|
-
credentials: host # own | host
|
|
250
|
-
instructions: host # own | host,
|
|
251
|
-
hooks: host # own | host
|
|
252
|
-
mcpServers: host # own | host,
|
|
314
|
+
transcripts: host # own | host host on yolo only
|
|
315
|
+
credentials: host # own | host host on every profile
|
|
316
|
+
instructions: host # own | host own on paranoid, else host
|
|
317
|
+
hooks: host # own | host host on yolo only
|
|
318
|
+
mcpServers: host # own | host own on paranoid, else host
|
|
253
319
|
|
|
254
320
|
transcripts: host makes a chat and a \`claude --resume\` in the same directory
|
|
255
321
|
the same file, live in both directions; deleting such a chat in Paddock
|
|
@@ -257,9 +323,9 @@ Sharing your Claude Code state
|
|
|
257
323
|
Paddock's copy. credentials: own is the opt-out from sharing the login above.
|
|
258
324
|
|
|
259
325
|
instructions: host loads your ~/.claude CLAUDE.md, agents/, commands/ and
|
|
260
|
-
plugins/.
|
|
261
|
-
|
|
262
|
-
|
|
326
|
+
plugins/. On from balanced up, so your curated CLAUDE.md does reach Paddock's
|
|
327
|
+
agents; profile: paranoid or instructions: own keeps it out. Each project's
|
|
328
|
+
own CLAUDE.md applies either way.
|
|
263
329
|
|
|
264
330
|
hooks: host runs the shell commands your ~/.claude/settings.json binds to
|
|
265
331
|
tool use. Off by default — inheriting someone's hooks is not something to
|
|
@@ -346,3 +412,99 @@ A note on access
|
|
|
346
412
|
you. Set PADDOCK_AUTH_MODE if you want a credential on it anyway.
|
|
347
413
|
|
|
348
414
|
Docs: https://github.com/edspencer/paddock`;
|
|
415
|
+
export const CONFIG_USAGE = `paddock config — what this instance's configuration actually resolved to
|
|
416
|
+
|
|
417
|
+
Usage
|
|
418
|
+
paddock config show the decisions: your profile, the keys your
|
|
419
|
+
config file sets, the variables your
|
|
420
|
+
environment sets
|
|
421
|
+
paddock config show --resolved EVERY effective value, and which layer it
|
|
422
|
+
came from
|
|
423
|
+
paddock config show --json the same report as JSON, long values in
|
|
424
|
+
full (sensitive ones still hidden)
|
|
425
|
+
paddock config eject what freezing that resolution into
|
|
426
|
+
paddock.config.yaml would write — a
|
|
427
|
+
preview; writes nothing
|
|
428
|
+
paddock config eject --write actually write it
|
|
429
|
+
|
|
430
|
+
Options
|
|
431
|
+
-d, --data-dir <path> Which instance to inspect (default ~/.paddock, or
|
|
432
|
+
$PADDOCK_DATA_DIR) — the same rule \`paddock start\`
|
|
433
|
+
uses, so the two always read the same instance
|
|
434
|
+
--resolved show: every field, not just the ones someone set
|
|
435
|
+
--json show: the whole report as JSON (implies --resolved's
|
|
436
|
+
scope)
|
|
437
|
+
--show-sensitive show: print the values of fields marked sensitive
|
|
438
|
+
--write eject: apply the plan. Without it, eject only prints
|
|
439
|
+
--include-env eject: also freeze values an environment variable
|
|
440
|
+
currently supplies (see below — off by default)
|
|
441
|
+
-h, --help Show this help
|
|
442
|
+
|
|
443
|
+
Where a value can come from
|
|
444
|
+
Config resolves in four layers, and each row of --resolved names the one that
|
|
445
|
+
won:
|
|
446
|
+
|
|
447
|
+
default Paddock's built-in default.
|
|
448
|
+
profile (<name>) Your posture profile. The twelve levers a profile governs
|
|
449
|
+
have no code default of their own any more — the profile
|
|
450
|
+
IS their default — so this is a distinct answer from
|
|
451
|
+
"default", and switching profile would change it.
|
|
452
|
+
file A key in paddock.config.yaml.
|
|
453
|
+
env <NAME> An environment variable, which beats the file for the
|
|
454
|
+
same key.
|
|
455
|
+
|
|
456
|
+
One wrinkle worth knowing, and the command shows it plainly: an individual key
|
|
457
|
+
in the FILE beats PADDOCK_PROFILE in the environment. Specific beats general —
|
|
458
|
+
PADDOCK_PROFILE speaks for the levers you did not mention.
|
|
459
|
+
|
|
460
|
+
Why show, and not just write it all into the file
|
|
461
|
+
\`show\` is computed by the loader the server boots with, so it cannot drift
|
|
462
|
+
from what the process would actually do. A materialized file can: it stops
|
|
463
|
+
inheriting improved defaults, says nothing about the variables your container
|
|
464
|
+
sets on top, and no longer describes the whole surface the day a lever is
|
|
465
|
+
added. That is why the thin file is the default and \`show\` is the answer to
|
|
466
|
+
"what am I actually running?".
|
|
467
|
+
|
|
468
|
+
\`eject\` is there for when you want it written down anyway — frozen, in the
|
|
469
|
+
file, reviewable in git. It is a real tradeoff rather than a better \`show\`,
|
|
470
|
+
so it prints what it would cost you and writes nothing until you pass --write.
|
|
471
|
+
|
|
472
|
+
What eject writes, and what it leaves alone
|
|
473
|
+
It writes the posture levers your profile expanded to, plus the behavioural
|
|
474
|
+
settings the Config screen already round-trips through this file. It does NOT
|
|
475
|
+
write:
|
|
476
|
+
|
|
477
|
+
the bindings port, host, data dir, projects root, state dir, web dist.
|
|
478
|
+
These resolve to absolute paths on THIS machine, and the
|
|
479
|
+
data dir resolves to the directory holding the file — so
|
|
480
|
+
ejecting them makes the file unportable and unmountable.
|
|
481
|
+
sensitive keys transcription.endpoint can carry credentials in its URL.
|
|
482
|
+
Bulk-writing a secret-shaped value to disk should not be
|
|
483
|
+
one keystroke away; write that key by hand.
|
|
484
|
+
env-owned keys by default. An environment variable BEATS the file, so
|
|
485
|
+
writing its value in changes nothing today and everything
|
|
486
|
+
the day the variable goes away — a deferred, silent change
|
|
487
|
+
of provenance. eject names each one it skipped instead.
|
|
488
|
+
--include-env writes them, which is the right flag if you
|
|
489
|
+
are deliberately migrating from env vars to a file.
|
|
490
|
+
|
|
491
|
+
It always writes \`profile:\` as well, even though every key that profile
|
|
492
|
+
governs is now explicit. That line is what a lever added in a FUTURE release
|
|
493
|
+
falls back to: without it, a new capability toggle would resolve against the
|
|
494
|
+
built-in default profile rather than the posture you actually ejected from.
|
|
495
|
+
|
|
496
|
+
Your comments and any keys Paddock does not manage survive — the file is
|
|
497
|
+
round-tripped, not regenerated — and the write is atomic.
|
|
498
|
+
|
|
499
|
+
Reading nothing, writing nothing
|
|
500
|
+
\`show\` resolves config and prints it. It starts no server, contacts nothing,
|
|
501
|
+
and writes no file — including the data dir itself, which it will report as
|
|
502
|
+
missing rather than create. \`eject\` writes exactly one file, and only with
|
|
503
|
+
--write.
|
|
504
|
+
|
|
505
|
+
Values are printed to a terminal that often ends up pasted into an issue, so
|
|
506
|
+
fields marked sensitive are shown as (hidden). None of them is a secret —
|
|
507
|
+
Paddock keeps secrets out of this surface entirely, so there is no API key or
|
|
508
|
+
JWT signing material here to hide or reveal.
|
|
509
|
+
|
|
510
|
+
Docs: https://github.com/edspencer/paddock`;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import type { CliOptions, ConfigAction } from "./args.js";
|
|
2
|
+
import type { ConfigSource, ResolvedConfigReport } from "../instance-config.js";
|
|
3
|
+
/**
|
|
4
|
+
* The default view: the decisions, not the resolution. Everything named here is
|
|
5
|
+
* something a human typed somewhere.
|
|
6
|
+
*/
|
|
7
|
+
export declare function formatSummary(report: ResolvedConfigReport): string;
|
|
8
|
+
/** `--resolved`: every field in the settings surface, grouped, with its layer. */
|
|
9
|
+
export declare function formatResolved(report: ResolvedConfigReport, groups: {
|
|
10
|
+
id: string;
|
|
11
|
+
label: string;
|
|
12
|
+
}[], showSensitive: boolean): string;
|
|
13
|
+
/** The report as JSON, with `sensitive` values replaced unless asked for. */
|
|
14
|
+
export declare function toJson(report: ResolvedConfigReport, showSensitive: boolean): string;
|
|
15
|
+
/**
|
|
16
|
+
* `paddock config eject` — freeze the resolution into `paddock.config.yaml`.
|
|
17
|
+
*
|
|
18
|
+
* ## Why this exists next to a command that argues against it
|
|
19
|
+
*
|
|
20
|
+
* `config show` is the answer to "what am I actually running?", and its whole
|
|
21
|
+
* case is that a printed resolution cannot drift the way a materialised file
|
|
22
|
+
* can. `eject` is for the person that argument does not satisfy: someone who
|
|
23
|
+
* wants the posture pinned in git, reviewable in a diff, identical across a
|
|
24
|
+
* fleet, and unaffected by what a later release decides a good default is.
|
|
25
|
+
*
|
|
26
|
+
* That is a legitimate want and a real tradeoff, so the command's job is not
|
|
27
|
+
* just to write the file — it is to make the tradeoff visible **at the moment
|
|
28
|
+
* of use**, where an operator is actually deciding, rather than only in docs
|
|
29
|
+
* they read once. Hence {@link formatEjectPlan}'s closing section, and hence
|
|
30
|
+
* preview-by-default.
|
|
31
|
+
*
|
|
32
|
+
* ## Preview by default, `--write` to apply
|
|
33
|
+
*
|
|
34
|
+
* Ejecting is the one config operation that changes what the file MEANS rather
|
|
35
|
+
* than what it says, and it spreads that change over ~forty keys, no one of
|
|
36
|
+
* which looks like a decision. So the default run prints the plan — every key,
|
|
37
|
+
* its value, the layer it is being frozen out of, and what is deliberately being
|
|
38
|
+
* left out — and writes nothing. A flag rather than an interactive prompt
|
|
39
|
+
* because the likeliest caller is a container build or a config-management task
|
|
40
|
+
* that cannot answer one.
|
|
41
|
+
*
|
|
42
|
+
* ## What it refuses to write, and why each refusal is safe
|
|
43
|
+
*
|
|
44
|
+
* The key set comes from `ejectable` on the field itself (see
|
|
45
|
+
* instance-config.ts): posture and editable keys in, machine-specific bindings
|
|
46
|
+
* and `sensitive` fields out. Two more exclusions are decided here, from
|
|
47
|
+
* provenance rather than from the field:
|
|
48
|
+
*
|
|
49
|
+
* - **A value an environment variable currently supplies is skipped.** This is
|
|
50
|
+
* the subtle one. Env beats file, so writing an env-sourced value into the
|
|
51
|
+
* file changes *nothing observable today* and changes the instance's behaviour
|
|
52
|
+
* on the day that variable stops being set — a deferred, silent transfer of a
|
|
53
|
+
* decision from the environment into a file, with no record that it ever was
|
|
54
|
+
* an environment decision. It is also how a stray `PADDOCK_*` left over on a
|
|
55
|
+
* build box gets baked into a committed config permanently. There is a
|
|
56
|
+
* precedent for treating this as an error rather than a courtesy: the Settings
|
|
57
|
+
* PUT path (`validatePatch`) already refuses to write a key an env var
|
|
58
|
+
* shadows, for the same reason. So eject names each one it skipped and which
|
|
59
|
+
* variable owns it; `--include-env` writes them anyway, which is the correct
|
|
60
|
+
* behaviour for the one case that genuinely wants it — deliberately migrating
|
|
61
|
+
* an instance off a wall of `PADDOCK_*` vars and into a file.
|
|
62
|
+
*
|
|
63
|
+
* - **A key whose effective value is `null` is skipped** — an optional nothing
|
|
64
|
+
* set, like `sweepMinIntervalMs`. `writeInstanceConfig` reads `null` as
|
|
65
|
+
* "delete this key", so there is no value to write; and emitting `key: null`
|
|
66
|
+
* would render as a decision in a file whose entire purpose is to record
|
|
67
|
+
* decisions.
|
|
68
|
+
*
|
|
69
|
+
* Neither exclusion can change what the instance resolves — which is the
|
|
70
|
+
* property the round-trip test pins, and the one that makes the refusals safe
|
|
71
|
+
* rather than merely defensible.
|
|
72
|
+
*
|
|
73
|
+
* ## `profile:` is written even when the environment set it
|
|
74
|
+
*
|
|
75
|
+
* A deliberate exception to the env rule above, and worth the paragraph because
|
|
76
|
+
* it looks like an inconsistency.
|
|
77
|
+
*
|
|
78
|
+
* The env rule exists because writing an env value moves a decision about an
|
|
79
|
+
* *existing lever* into the file invisibly. After a full eject, `profile:`
|
|
80
|
+
* governs no existing lever — every key it would have supplied a default for is
|
|
81
|
+
* now written out explicitly, so the line cannot change any current value. Its
|
|
82
|
+
* only remaining effect is on levers that do not exist yet: a capability toggle
|
|
83
|
+
* added in a later release resolves against the profile, and without this line
|
|
84
|
+
* it would resolve against the built-in default profile rather than the posture
|
|
85
|
+
* the operator actually froze. Ejecting from `paranoid` and silently acquiring
|
|
86
|
+
* `balanced`'s answer to a future lever is the exact drift this command is
|
|
87
|
+
* supposed to protect against.
|
|
88
|
+
*
|
|
89
|
+
* So the line goes in, and the plan says out loud that it did.
|
|
90
|
+
*/
|
|
91
|
+
/** One key the plan would write. */
|
|
92
|
+
export interface EjectEntry {
|
|
93
|
+
key: string;
|
|
94
|
+
group: string;
|
|
95
|
+
value: unknown;
|
|
96
|
+
/** The layer the value is being frozen OUT of. */
|
|
97
|
+
source: ConfigSource;
|
|
98
|
+
origin?: string;
|
|
99
|
+
/** `add` — the file does not name this key. `change` — it does, inertly. */
|
|
100
|
+
action: "add" | "change";
|
|
101
|
+
/** What the file says today; only on `change`. */
|
|
102
|
+
fileValue?: unknown;
|
|
103
|
+
}
|
|
104
|
+
/** Why a field is not in the plan. */
|
|
105
|
+
export type EjectSkipReason = "sensitive" | "binding" | "env" | "unset";
|
|
106
|
+
export interface EjectSkip {
|
|
107
|
+
key: string;
|
|
108
|
+
reason: EjectSkipReason;
|
|
109
|
+
/** The variable name, for `env`. */
|
|
110
|
+
origin?: string;
|
|
111
|
+
}
|
|
112
|
+
export interface EjectPlan {
|
|
113
|
+
report: ResolvedConfigReport;
|
|
114
|
+
entries: EjectEntry[];
|
|
115
|
+
skipped: EjectSkip[];
|
|
116
|
+
/** Keys already set in the file AND already in effect — left untouched. */
|
|
117
|
+
keptFromFile: string[];
|
|
118
|
+
/** The `profile:` line, and whether the file already names it. */
|
|
119
|
+
profile: {
|
|
120
|
+
name: string;
|
|
121
|
+
write: boolean;
|
|
122
|
+
};
|
|
123
|
+
/** Exactly what {@link import("../instance-config.js").writeInstanceConfig} is handed. */
|
|
124
|
+
pairs: {
|
|
125
|
+
key: string;
|
|
126
|
+
value: unknown;
|
|
127
|
+
}[];
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Decide what ejecting would write. Pure — the caller does the IO — so every
|
|
131
|
+
* policy decision above is unit-testable against a hand-built report.
|
|
132
|
+
*/
|
|
133
|
+
export declare function buildEjectPlan(report: ResolvedConfigReport, includeEnv?: boolean): EjectPlan;
|
|
134
|
+
/**
|
|
135
|
+
* Render a plan. `written` switches the tense — the two views are one function
|
|
136
|
+
* so a preview cannot promise something the write does not do.
|
|
137
|
+
*/
|
|
138
|
+
export declare function formatEjectPlan(plan: EjectPlan, groups: {
|
|
139
|
+
id: string;
|
|
140
|
+
label: string;
|
|
141
|
+
}[], written: boolean): string;
|
|
142
|
+
/**
|
|
143
|
+
* Run a `config` action against the data dir the caller has already resolved
|
|
144
|
+
* into `PADDOCK_DATA_DIR`.
|
|
145
|
+
*
|
|
146
|
+
* The two imports are dynamic for the same reason `paddock.ts` defers
|
|
147
|
+
* `start.js`: config resolution reads the environment, so nothing that resolves
|
|
148
|
+
* it may be pulled in before the caller has finished setting it.
|
|
149
|
+
*
|
|
150
|
+
* `createDataDir: false` is the one behavioural request this command makes of
|
|
151
|
+
* the loader, and it holds for `eject` too. Inspecting an instance must not
|
|
152
|
+
* CREATE one — otherwise `paddock config show` on a machine that has never run
|
|
153
|
+
* Paddock leaves an empty `~/.paddock` behind, and the first-run welcome that
|
|
154
|
+
* keys on its absence never prints. `eject --write` does create the directory,
|
|
155
|
+
* because it has to put a file in it, but only on the path that writes.
|
|
156
|
+
*/
|
|
157
|
+
export declare function runConfig(action: ConfigAction, opts: CliOptions): Promise<void>;
|