@edspencer/paddock 0.71.2 → 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 +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-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-DA5CpdGI.js → MarkdownRenderer-D95ofnQ8.js} +1 -1
- package/packages/web/dist/assets/ProjectMenu-BO70tGwy.js +2 -0
- package/packages/web/dist/assets/{ProjectRedirect-k10uM_63.js → ProjectRedirect-BxX_aADU.js} +1 -1
- package/packages/web/dist/assets/ProjectView-BbCyKoh5.js +13 -0
- package/packages/web/dist/assets/{ProjectsGrid-DrzZ7R8M.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-BKAVFR26.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-Eu4BV7Ea.js → blockDiagram-GPEHLZMM-DiTvGSrV.js} +1 -1
- package/packages/web/dist/assets/{c4Diagram-AAUBKEIU-DR8PZaoa.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-CgvIIrI1.js → chunk-2J33WTMH-5o96lthu.js} +1 -1
- package/packages/web/dist/assets/{chunk-4BX2VUAB-CyJWrZqN.js → chunk-4BX2VUAB-38_pZfd8.js} +1 -1
- package/packages/web/dist/assets/{chunk-55IACEB6-DXEnZN1d.js → chunk-55IACEB6-Db73LvlE.js} +1 -1
- package/packages/web/dist/assets/{chunk-727SXJPM-Co89QTmp.js → chunk-727SXJPM-C-Q1-wUu.js} +1 -1
- package/packages/web/dist/assets/{chunk-AQP2D5EJ-BRxTzfev.js → chunk-AQP2D5EJ-ClJigITk.js} +1 -1
- package/packages/web/dist/assets/{chunk-FMBD7UC4-DUj18CvF.js → chunk-FMBD7UC4-BVzSX2hw.js} +1 -1
- package/packages/web/dist/assets/{chunk-ND2GUHAM-BQMMJobN.js → chunk-ND2GUHAM-DJlL-ELa.js} +1 -1
- package/packages/web/dist/assets/{chunk-QZHKN3VN-COx8LZCe.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-DnpcT0s6.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-CuaYJexd.js → dagre-BM42HDAG-BgFGyUdv.js} +1 -1
- package/packages/web/dist/assets/{diagram-2AECGRRQ-C9_anxrA.js → diagram-2AECGRRQ-BqdZmJt3.js} +1 -1
- package/packages/web/dist/assets/{diagram-5GNKFQAL-Ckf3kFlY.js → diagram-5GNKFQAL-DeLPu8jT.js} +1 -1
- package/packages/web/dist/assets/{diagram-KO2AKTUF-BrpUuY5x.js → diagram-KO2AKTUF-BqTiTGay.js} +1 -1
- package/packages/web/dist/assets/{diagram-LMA3HP47-B9lyXZXW.js → diagram-LMA3HP47-CbpAvCgl.js} +1 -1
- package/packages/web/dist/assets/{diagram-OG6HWLK6-DT7ro32m.js → diagram-OG6HWLK6-C4h0V97s.js} +1 -1
- package/packages/web/dist/assets/{erDiagram-TEJ5UH35-ZZn9bHQh.js → erDiagram-TEJ5UH35-Dwz77uRx.js} +1 -1
- package/packages/web/dist/assets/{flowDiagram-I6XJVG4X-BxQ82PY_.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-BlwODdH9.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-nanSBFyG.js → infoDiagram-5YYISTIA-YcaBUjeC.js} +1 -1
- package/packages/web/dist/assets/{ishikawaDiagram-YF4QCWOH-yZHr_s28.js → ishikawaDiagram-YF4QCWOH-BfLZB88u.js} +1 -1
- package/packages/web/dist/assets/{journeyDiagram-JHISSGLW-BKjDqQEQ.js → journeyDiagram-JHISSGLW-DwaFhd6C.js} +1 -1
- package/packages/web/dist/assets/{kanban-definition-UN3LZRKU-QusvHqc6.js → kanban-definition-UN3LZRKU-e9P3WxsD.js} +1 -1
- package/packages/web/dist/assets/{linear-4xRoD38Z.js → linear-CsXS5LTz.js} +1 -1
- package/packages/web/dist/assets/{mermaid.core-CeulYvDv.js → mermaid.core-DsA72Qyi.js} +4 -4
- package/packages/web/dist/assets/{mindmap-definition-RKZ34NQL-DSjYt2Kp.js → mindmap-definition-RKZ34NQL-DyLOITDI.js} +1 -1
- package/packages/web/dist/assets/{pieDiagram-4H26LBE5-Ci0gO3CA.js → pieDiagram-4H26LBE5-C36aM3D-.js} +1 -1
- package/packages/web/dist/assets/{quadrantDiagram-W4KKPZXB-y2qHkaHP.js → quadrantDiagram-W4KKPZXB-Cq1zTk6a.js} +1 -1
- package/packages/web/dist/assets/{requirementDiagram-4Y6WPE33-D9hiWbad.js → requirementDiagram-4Y6WPE33-B6MctUk7.js} +1 -1
- package/packages/web/dist/assets/{sankeyDiagram-5OEKKPKP-DA3Mvoze.js → sankeyDiagram-5OEKKPKP-CaNRUoPR.js} +1 -1
- package/packages/web/dist/assets/{sequenceDiagram-3UESZ5HK-DWACrljw.js → sequenceDiagram-3UESZ5HK-DDj_tZiQ.js} +1 -1
- package/packages/web/dist/assets/{stateDiagram-AJRCARHV-CFGEhoTb.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-Cjygpd9e.js → timeline-definition-PNZ67QCA-bMP-7Wam.js} +1 -1
- package/packages/web/dist/assets/{vennDiagram-CIIHVFJN-zQnLaxgv.js → vennDiagram-CIIHVFJN-9nzKgc0Y.js} +1 -1
- package/packages/web/dist/assets/{wardley-L42UT6IY-DGmt-Lo2.js → wardley-L42UT6IY-DIWbAFW5.js} +1 -1
- package/packages/web/dist/assets/{wardleyDiagram-YWT4CUSO-Dj3vDukN.js → wardleyDiagram-YWT4CUSO-CVL3skbs.js} +1 -1
- package/packages/web/dist/assets/{xychartDiagram-2RQKCTM6-C0bGbwbL.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-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
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `paddock config show` — what this instance's config actually resolved to (#878).
|
|
3
|
+
*
|
|
4
|
+
* ## Why the command exists
|
|
5
|
+
*
|
|
6
|
+
* #878 ships a `profile:` key so an operator's config file can stay thin: a
|
|
7
|
+
* posture name plus the handful of levers they disagree with. The objection to a
|
|
8
|
+
* thin file is real — "I want it all clearly written down" — and the answer is
|
|
9
|
+
* not to write it all down. A materialised file is a snapshot that goes stale the
|
|
10
|
+
* moment a default improves or a lever is added, and it cannot show you the env
|
|
11
|
+
* vars your container sets on top. Printing the resolution can't drift, because
|
|
12
|
+
* it is computed by the same loader the server boots with.
|
|
13
|
+
*
|
|
14
|
+
* ## Provenance is the deliverable, not the values
|
|
15
|
+
*
|
|
16
|
+
* A list of effective values is a thing you can already get by reading the Config
|
|
17
|
+
* screen. What you cannot get anywhere is the LAYER each one came from — and the
|
|
18
|
+
* layer is what tells you where to go to change it. So every row names one of
|
|
19
|
+
* `env` / `file` / `profile` / `default`, and `profile` is kept distinct from
|
|
20
|
+
* `default` because for the twelve posture keys there is no code default any more
|
|
21
|
+
* (see `ConfigSource` in instance-config.ts).
|
|
22
|
+
*
|
|
23
|
+
* ## What plain `config show` does
|
|
24
|
+
*
|
|
25
|
+
* It prints the DECISIONS — the profile, the keys your file sets, the variables
|
|
26
|
+
* your environment sets — and nothing else. That mirrors the shape #878 argues
|
|
27
|
+
* for: thin by default, explicit on demand. Two failure modes it avoids: an
|
|
28
|
+
* argument-less command that errors at you is rude, and one that dumps forty rows
|
|
29
|
+
* makes `--resolved` pointless. The summary is a reading of the same report
|
|
30
|
+
* `--resolved` prints in full, so the two can never disagree.
|
|
31
|
+
*
|
|
32
|
+
* The formatting functions here are pure and unit-tested; {@link runConfig} is
|
|
33
|
+
* the only part that touches the filesystem, and it is exercised by running the
|
|
34
|
+
* built binary.
|
|
35
|
+
*/
|
|
36
|
+
import { CliError } from "./args.js";
|
|
37
|
+
/**
|
|
38
|
+
* Longest value printed in the table before it is elided. The environment prompt
|
|
39
|
+
* is ~600 characters of paragraph text and would own the terminal; `--json`
|
|
40
|
+
* prints every value in full for anyone who needs one.
|
|
41
|
+
*/
|
|
42
|
+
const MAX_VALUE_CHARS = 52;
|
|
43
|
+
/**
|
|
44
|
+
* Stand-in for a `sensitive` field's value. Provenance is still reported, so the
|
|
45
|
+
* row still answers "where would I change this?".
|
|
46
|
+
*
|
|
47
|
+
* Redaction is uniform on the flag rather than judged per field, and that is the
|
|
48
|
+
* point. `FIELDS` documents itself as never carrying a secret value — but that is
|
|
49
|
+
* an intention maintained by hand, not an enforced invariant, and one of the
|
|
50
|
+
* three fields it already marks is `transcription.endpoint`: a URL an operator
|
|
51
|
+
* typed, which can perfectly well read `https://user:token@host`. The output of
|
|
52
|
+
* this command is designed to be pasted into an issue. So the safe reading of the
|
|
53
|
+
* flag wins by default and `--show-sensitive` is one keystroke away — which also
|
|
54
|
+
* means a field added with `sensitive: true` in two years is redacted here
|
|
55
|
+
* without anyone having to remember this file exists.
|
|
56
|
+
*/
|
|
57
|
+
const HIDDEN = "(hidden)";
|
|
58
|
+
/** Render one value as a single line of terminal text. */
|
|
59
|
+
function renderValue(v) {
|
|
60
|
+
if (v === null || v === undefined)
|
|
61
|
+
return "(unset)";
|
|
62
|
+
if (Array.isArray(v))
|
|
63
|
+
return v.length > 0 ? v.join(", ") : "(empty)";
|
|
64
|
+
if (typeof v === "string")
|
|
65
|
+
return v === "" ? '""' : v;
|
|
66
|
+
return String(v);
|
|
67
|
+
}
|
|
68
|
+
/** Collapse to one line and elide. Returns whether anything was lost. */
|
|
69
|
+
function oneLine(text) {
|
|
70
|
+
const flat = text.replace(/\s+/g, " ").trim();
|
|
71
|
+
return flat.length > MAX_VALUE_CHARS
|
|
72
|
+
? { text: `${flat.slice(0, MAX_VALUE_CHARS - 1)}…`, elided: true }
|
|
73
|
+
: { text: flat, elided: false };
|
|
74
|
+
}
|
|
75
|
+
/** The source column: the layer, and enough of the origin to act on it. */
|
|
76
|
+
function renderSource(f) {
|
|
77
|
+
switch (f.source) {
|
|
78
|
+
case "env":
|
|
79
|
+
return `env ${f.origin}`;
|
|
80
|
+
case "profile":
|
|
81
|
+
return `profile (${f.origin})`;
|
|
82
|
+
case "file":
|
|
83
|
+
return "file";
|
|
84
|
+
default:
|
|
85
|
+
return "default";
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function pad(s, width) {
|
|
89
|
+
return s.length >= width ? s : s + " ".repeat(width - s.length);
|
|
90
|
+
}
|
|
91
|
+
/** The three header lines both views share. */
|
|
92
|
+
function header(report) {
|
|
93
|
+
const p = report.profile;
|
|
94
|
+
const from = p.source === "env"
|
|
95
|
+
? `${p.origin}`
|
|
96
|
+
: p.source === "file"
|
|
97
|
+
? "config file"
|
|
98
|
+
: "built-in default";
|
|
99
|
+
const lines = [
|
|
100
|
+
` Data dir ${report.dataDir}`,
|
|
101
|
+
` Config file ${report.configPath}${report.configFileExists ? "" : " (not present)"}`,
|
|
102
|
+
` Profile ${p.name} (${from})`,
|
|
103
|
+
];
|
|
104
|
+
if (p.unrecognised) {
|
|
105
|
+
lines.push(` ${p.origin ?? p.unrecognised.origin} names "${p.unrecognised.value}", which is not a known profile — ` +
|
|
106
|
+
`fell back to ${p.name}`);
|
|
107
|
+
}
|
|
108
|
+
if (report.configFileError)
|
|
109
|
+
lines.push(` ⚠ ${report.configFileError}`);
|
|
110
|
+
return lines;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* File values a higher layer beat. Rendered by both views because the operator
|
|
114
|
+
* who edited a file and saw nothing change is precisely who runs this command,
|
|
115
|
+
* and today nothing anywhere tells them why.
|
|
116
|
+
*/
|
|
117
|
+
function shadowedSection(report) {
|
|
118
|
+
const shadowed = report.fields.filter((f) => f.shadowedFileValue !== undefined);
|
|
119
|
+
if (shadowed.length === 0)
|
|
120
|
+
return [];
|
|
121
|
+
const width = Math.max(...shadowed.map((f) => f.key.length));
|
|
122
|
+
return [
|
|
123
|
+
"",
|
|
124
|
+
"Set in the config file but NOT in effect",
|
|
125
|
+
...shadowed.map((f) => {
|
|
126
|
+
const wrote = renderValue(f.shadowedFileValue);
|
|
127
|
+
const why = f.source === "env"
|
|
128
|
+
? `${f.origin} wins for the same key`
|
|
129
|
+
: `the resolved value is ${renderValue(f.value)}`;
|
|
130
|
+
return ` ${pad(f.key, width)} file says ${wrote} — ${why}`;
|
|
131
|
+
}),
|
|
132
|
+
];
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* The default view: the decisions, not the resolution. Everything named here is
|
|
136
|
+
* something a human typed somewhere.
|
|
137
|
+
*/
|
|
138
|
+
export function formatSummary(report) {
|
|
139
|
+
const fromFile = report.fields.filter((f) => f.source === "file");
|
|
140
|
+
const fromEnv = report.fields.filter((f) => f.source === "env");
|
|
141
|
+
const lines = ["Paddock config", ...header(report), "", "Set in the config file"];
|
|
142
|
+
if (fromFile.length === 0) {
|
|
143
|
+
lines.push(report.configFileExists ? " (nothing this instance reads)" : " (no config file)");
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
const width = Math.max(...fromFile.map((f) => f.key.length));
|
|
147
|
+
for (const f of fromFile) {
|
|
148
|
+
lines.push(` ${pad(f.key, width)} ${oneLine(renderValue(f.sensitive ? HIDDEN : f.value)).text}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
lines.push("", "Set in the environment");
|
|
152
|
+
if (fromEnv.length === 0) {
|
|
153
|
+
lines.push(" (nothing)");
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
const width = Math.max(...fromEnv.map((f) => (f.origin ?? "").length));
|
|
157
|
+
for (const f of fromEnv) {
|
|
158
|
+
lines.push(` ${pad(f.origin ?? "", width)} ${f.key} = ${oneLine(renderValue(f.sensitive ? HIDDEN : f.value)).text}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
lines.push(...shadowedSection(report));
|
|
162
|
+
lines.push("", `Everything else follows the ${report.profile.name} profile and Paddock's built-in defaults.`, "Run `paddock config show --resolved` to print every effective value and where it came from.");
|
|
163
|
+
return lines.join("\n");
|
|
164
|
+
}
|
|
165
|
+
/** `--resolved`: every field in the settings surface, grouped, with its layer. */
|
|
166
|
+
export function formatResolved(report, groups, showSensitive) {
|
|
167
|
+
const keyWidth = Math.max(...report.fields.map((f) => f.key.length));
|
|
168
|
+
const rendered = report.fields.map((f) => {
|
|
169
|
+
const hide = f.sensitive && !showSensitive && f.value !== null;
|
|
170
|
+
return { field: f, ...oneLine(renderValue(hide ? HIDDEN : f.value)), hidden: hide };
|
|
171
|
+
});
|
|
172
|
+
const valueWidth = Math.max(...rendered.map((r) => r.text.length));
|
|
173
|
+
const lines = ["Paddock config — every effective value, and the layer it came from", ...header(report)];
|
|
174
|
+
for (const g of groups) {
|
|
175
|
+
const rows = rendered.filter((r) => r.field.group === g.id);
|
|
176
|
+
if (rows.length === 0)
|
|
177
|
+
continue;
|
|
178
|
+
lines.push("", g.label);
|
|
179
|
+
for (const r of rows) {
|
|
180
|
+
lines.push(` ${pad(r.field.key, keyWidth)} ${pad(r.text, valueWidth)} ${renderSource(r.field)}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
lines.push(...shadowedSection(report));
|
|
184
|
+
// A legend, listing only the layers actually present — a reader should not
|
|
185
|
+
// have to learn what `profile (yolo)` means on an instance that has no
|
|
186
|
+
// profile-sourced values left.
|
|
187
|
+
const present = new Set(report.fields.map((f) => f.source));
|
|
188
|
+
const legend = [
|
|
189
|
+
["env", "env NAME", "that variable, which beats the config file for the same key"],
|
|
190
|
+
["file", "file", report.configPath],
|
|
191
|
+
[
|
|
192
|
+
"profile",
|
|
193
|
+
`profile (${report.profile.name})`,
|
|
194
|
+
"the profile — these levers have no code default of their own",
|
|
195
|
+
],
|
|
196
|
+
["default", "default", "Paddock's built-in default"],
|
|
197
|
+
];
|
|
198
|
+
const shown = legend.filter(([source]) => present.has(source));
|
|
199
|
+
if (shown.length > 0) {
|
|
200
|
+
const width = Math.max(...shown.map(([, term]) => term.length));
|
|
201
|
+
lines.push("", "Where the values came from", ...shown.map(([, term, meaning]) => ` ${pad(term, width)} ${meaning}`));
|
|
202
|
+
}
|
|
203
|
+
const notes = [];
|
|
204
|
+
if (rendered.some((r) => r.hidden)) {
|
|
205
|
+
notes.push(
|
|
206
|
+
// NOT "--json prints it": --json redacts too, and pointing at it as an
|
|
207
|
+
// escape hatch would be a false claim in the one place a reader is
|
|
208
|
+
// deciding whether their secret just went to a terminal.
|
|
209
|
+
`${HIDDEN} — a field marked sensitive. Pass --show-sensitive to print it.`);
|
|
210
|
+
}
|
|
211
|
+
if (rendered.some((r) => r.elided)) {
|
|
212
|
+
notes.push(`Values longer than ${MAX_VALUE_CHARS} characters are elided; --json prints them in full.`);
|
|
213
|
+
}
|
|
214
|
+
if (notes.length > 0)
|
|
215
|
+
lines.push("", ...notes.map((n) => n));
|
|
216
|
+
return lines.join("\n");
|
|
217
|
+
}
|
|
218
|
+
/** The report as JSON, with `sensitive` values replaced unless asked for. */
|
|
219
|
+
export function toJson(report, showSensitive) {
|
|
220
|
+
return JSON.stringify({
|
|
221
|
+
...report,
|
|
222
|
+
fields: report.fields.map((f) => f.sensitive && !showSensitive && f.value !== null
|
|
223
|
+
? { ...f, value: null, redacted: true }
|
|
224
|
+
: f),
|
|
225
|
+
}, null, 2);
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Decide what ejecting would write. Pure — the caller does the IO — so every
|
|
229
|
+
* policy decision above is unit-testable against a hand-built report.
|
|
230
|
+
*/
|
|
231
|
+
export function buildEjectPlan(report, includeEnv = false) {
|
|
232
|
+
const entries = [];
|
|
233
|
+
const skipped = [];
|
|
234
|
+
const keptFromFile = [];
|
|
235
|
+
for (const f of report.fields) {
|
|
236
|
+
if (!f.ejectable) {
|
|
237
|
+
// `sensitive` and the machine-specific bindings are the only two ways a
|
|
238
|
+
// field can be non-ejectable, and `sensitive` is on the field, so the
|
|
239
|
+
// reason is recoverable without duplicating the policy out of the catalog.
|
|
240
|
+
skipped.push({ key: f.key, reason: f.sensitive ? "sensitive" : "binding" });
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (f.source === "env" && !includeEnv) {
|
|
244
|
+
skipped.push({ key: f.key, reason: "env", ...(f.origin ? { origin: f.origin } : {}) });
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
if (f.value === null) {
|
|
248
|
+
skipped.push({ key: f.key, reason: "unset" });
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
// Already in the file and already winning: leave the operator's own line
|
|
252
|
+
// exactly as they wrote it, comments, quoting and all. The key is present,
|
|
253
|
+
// which is all an ejected file needs; rewriting it would only churn.
|
|
254
|
+
if (f.source === "file") {
|
|
255
|
+
keptFromFile.push(f.key);
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
entries.push({
|
|
259
|
+
key: f.key,
|
|
260
|
+
group: f.group,
|
|
261
|
+
value: f.value,
|
|
262
|
+
source: f.source,
|
|
263
|
+
...(f.origin ? { origin: f.origin } : {}),
|
|
264
|
+
// The file naming a key that lost — to an env var, or to another key's
|
|
265
|
+
// cascade (`selfMcpEnabled: false` collapsing `selfMcpWriteEnabled`) — is
|
|
266
|
+
// the one case where eject overwrites something an operator typed. It is
|
|
267
|
+
// also the case where the file currently asserts something untrue, so
|
|
268
|
+
// replacing it with the effective value is the point rather than a cost.
|
|
269
|
+
...(f.shadowedFileValue !== undefined
|
|
270
|
+
? { action: "change", fileValue: f.shadowedFileValue }
|
|
271
|
+
: { action: "add" }),
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
const writeProfile = report.profile.source !== "file";
|
|
275
|
+
const pairs = entries.map(({ key, value }) => ({ key, value }));
|
|
276
|
+
// Written FIRST so it lands at the top of a fresh document, where a reader
|
|
277
|
+
// meets the posture before the forty keys expanding it.
|
|
278
|
+
if (writeProfile)
|
|
279
|
+
pairs.unshift({ key: "profile", value: report.profile.name });
|
|
280
|
+
return {
|
|
281
|
+
report,
|
|
282
|
+
entries,
|
|
283
|
+
skipped,
|
|
284
|
+
keptFromFile,
|
|
285
|
+
profile: { name: report.profile.name, write: writeProfile },
|
|
286
|
+
pairs,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
/** `n item` / `n items`. */
|
|
290
|
+
function plural(n, one, many = `${one}s`) {
|
|
291
|
+
return `${n} ${n === 1 ? one : many}`;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* The cost of ejecting, stated in terms of THIS plan's keys.
|
|
295
|
+
*
|
|
296
|
+
* #878 requires the staleness tradeoff to be surfaced where someone can act on
|
|
297
|
+
* it, not just documented. A generic "you will stop getting improved defaults"
|
|
298
|
+
* is easy to read past, so the two keys where that bites hardest and most
|
|
299
|
+
* concretely are named with their actual contents when the plan contains them.
|
|
300
|
+
*/
|
|
301
|
+
function costSection(plan) {
|
|
302
|
+
const has = (key) => plan.entries.find((e) => e.key === key);
|
|
303
|
+
const lines = [
|
|
304
|
+
"",
|
|
305
|
+
"What you give up",
|
|
306
|
+
" Every key below stops following Paddock's defaults and becomes yours to",
|
|
307
|
+
" maintain. Upgrades will no longer change any of them.",
|
|
308
|
+
];
|
|
309
|
+
const models = has("models");
|
|
310
|
+
if (Array.isArray(models?.value)) {
|
|
311
|
+
lines.push(` models pinned to today's ${plural(models.value.length, "model")}. A model added in a`, " later release will not be offered until you re-run this.");
|
|
312
|
+
}
|
|
313
|
+
if (has("environmentPrompt")) {
|
|
314
|
+
lines.push(" environmentPrompt pinned to the current text. Improvements to what agents", " are told about running in Paddock stop reaching you.");
|
|
315
|
+
}
|
|
316
|
+
lines.push("", " A lever added in a later release will not be in this file at all, so the", " file stops being the complete record you ejected it to be. `profile:` keeps", " such a lever on your posture rather than the built-in default — but keeping", " the file complete means re-running eject after upgrades.", "", " None of this is one-way. Delete the keys you would rather inherit again,", " leave `profile:` in place, and they go back to following it —", " `paddock config show` will tell you which layer each one is coming from.");
|
|
317
|
+
return lines;
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Render a plan. `written` switches the tense — the two views are one function
|
|
321
|
+
* so a preview cannot promise something the write does not do.
|
|
322
|
+
*/
|
|
323
|
+
export function formatEjectPlan(plan, groups, written) {
|
|
324
|
+
const { report } = plan;
|
|
325
|
+
const lines = [
|
|
326
|
+
written ? "Paddock config — ejected" : "Paddock config — eject preview",
|
|
327
|
+
...header(report),
|
|
328
|
+
];
|
|
329
|
+
const total = plan.pairs.length;
|
|
330
|
+
// Re-running eject on an already-ejected instance is the normal way to find
|
|
331
|
+
// out whether an upgrade added a lever, so it should read as an answer rather
|
|
332
|
+
// than as a write that did nothing.
|
|
333
|
+
if (total === 0) {
|
|
334
|
+
lines.push("", `Nothing to write — every key eject manages is already in ${report.configPath}.`, ...(plan.skipped.length > 0 ? notWrittenSection(plan) : []));
|
|
335
|
+
return lines.join("\n");
|
|
336
|
+
}
|
|
337
|
+
lines.push("", written
|
|
338
|
+
? `Wrote ${plural(total, "key")} to ${report.configPath}`
|
|
339
|
+
: `Would write ${plural(total, "key")} to ${report.configPath}` +
|
|
340
|
+
(report.configFileExists ? "" : " (creating it)"));
|
|
341
|
+
if (plan.profile.write) {
|
|
342
|
+
lines.push(` profile: ${plan.profile.name} — so a lever added in a later release still`, " follows this posture rather than the built-in default");
|
|
343
|
+
}
|
|
344
|
+
const keyWidth = Math.max(8, ...plan.entries.map((e) => e.key.length));
|
|
345
|
+
const rendered = plan.entries.map((e) => ({ entry: e, ...oneLine(renderValue(e.value)) }));
|
|
346
|
+
const valueWidth = Math.max(1, ...rendered.map((r) => r.text.length));
|
|
347
|
+
for (const g of groups) {
|
|
348
|
+
const rows = rendered.filter((r) => r.entry.group === g.id);
|
|
349
|
+
if (rows.length === 0)
|
|
350
|
+
continue;
|
|
351
|
+
lines.push("", g.label);
|
|
352
|
+
for (const r of rows) {
|
|
353
|
+
const from = r.entry.action === "change"
|
|
354
|
+
? `replaces ${oneLine(renderValue(r.entry.fileValue)).text}, which is not in effect`
|
|
355
|
+
: `from ${renderSource({ source: r.entry.source, origin: r.entry.origin })}`;
|
|
356
|
+
lines.push(` ${pad(r.entry.key, keyWidth)} ${pad(r.text, valueWidth)} ${from}`);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
if (plan.keptFromFile.length > 0) {
|
|
360
|
+
lines.push("", `Already in the file, left untouched (${plan.keptFromFile.length})`, ` ${plan.keptFromFile.join(", ")}`);
|
|
361
|
+
}
|
|
362
|
+
lines.push(...notWrittenSection(plan));
|
|
363
|
+
lines.push(...costSection(plan));
|
|
364
|
+
lines.push("", written
|
|
365
|
+
? "`paddock config show --resolved` now reports these as `file`."
|
|
366
|
+
: "Nothing was written. Re-run with --write to apply.");
|
|
367
|
+
return lines.join("\n");
|
|
368
|
+
}
|
|
369
|
+
/** The deliberate omissions, grouped by reason so the list stays readable. */
|
|
370
|
+
function notWrittenSection(plan) {
|
|
371
|
+
const by = (r) => plan.skipped.filter((s) => s.reason === r);
|
|
372
|
+
const env = by("env");
|
|
373
|
+
const sensitive = by("sensitive");
|
|
374
|
+
const binding = by("binding");
|
|
375
|
+
const unset = by("unset");
|
|
376
|
+
if (plan.skipped.length === 0)
|
|
377
|
+
return [];
|
|
378
|
+
const lines = ["", "Not written"];
|
|
379
|
+
if (env.length > 0) {
|
|
380
|
+
lines.push(` ${plural(env.length, "key")} the environment supplies — an environment variable beats the`, " file, so freezing its value here would change nothing now and change this", " instance the day the variable goes away. Pass --include-env to write them.", ...(() => {
|
|
381
|
+
const w = Math.max(...env.map((s) => s.key.length));
|
|
382
|
+
return env.map((s) => ` ${pad(s.key, w)} ${s.origin ?? "env"}`);
|
|
383
|
+
})());
|
|
384
|
+
}
|
|
385
|
+
if (sensitive.length > 0) {
|
|
386
|
+
lines.push(` ${plural(sensitive.length, "key")} marked sensitive — never bulk-written to disk, because one of`, " them (transcription.endpoint) can carry a credential in its URL. Set one by", " hand if you want it in the file.", ` ${sensitive.map((s) => s.key).join(", ")}`);
|
|
387
|
+
}
|
|
388
|
+
if (binding.length > 0) {
|
|
389
|
+
lines.push(" Process and filesystem bindings — absolute paths and a port specific to", " this machine, which would make the file unportable:", ` ${binding.map((s) => s.key).join(", ")}`);
|
|
390
|
+
}
|
|
391
|
+
if (unset.length > 0) {
|
|
392
|
+
lines.push(` ${plural(unset.length, "optional key")} nothing has set, so there is no value to freeze:`, ` ${unset.map((s) => s.key).join(", ")}`);
|
|
393
|
+
}
|
|
394
|
+
return lines;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Run a `config` action against the data dir the caller has already resolved
|
|
398
|
+
* into `PADDOCK_DATA_DIR`.
|
|
399
|
+
*
|
|
400
|
+
* The two imports are dynamic for the same reason `paddock.ts` defers
|
|
401
|
+
* `start.js`: config resolution reads the environment, so nothing that resolves
|
|
402
|
+
* it may be pulled in before the caller has finished setting it.
|
|
403
|
+
*
|
|
404
|
+
* `createDataDir: false` is the one behavioural request this command makes of
|
|
405
|
+
* the loader, and it holds for `eject` too. Inspecting an instance must not
|
|
406
|
+
* CREATE one — otherwise `paddock config show` on a machine that has never run
|
|
407
|
+
* Paddock leaves an empty `~/.paddock` behind, and the first-run welcome that
|
|
408
|
+
* keys on its absence never prints. `eject --write` does create the directory,
|
|
409
|
+
* because it has to put a file in it, but only on the path that writes.
|
|
410
|
+
*/
|
|
411
|
+
export async function runConfig(action, opts) {
|
|
412
|
+
const { loadPaddockConfig } = await import("../config.js");
|
|
413
|
+
const { resolveConfigReport, writeInstanceConfig, GROUPS } = await import("../instance-config.js");
|
|
414
|
+
let cfg;
|
|
415
|
+
try {
|
|
416
|
+
cfg = loadPaddockConfig({ createDataDir: false });
|
|
417
|
+
}
|
|
418
|
+
catch (err) {
|
|
419
|
+
// An unparseable file, a `PADDOCK_CONFIG` pointing at nothing, a file from a
|
|
420
|
+
// newer schema, a Claude home that resolves somewhere refused — the loader
|
|
421
|
+
// rejects all of these, and rejecting them here is the RIGHT answer: the
|
|
422
|
+
// honest reply to "what is my config?" when it will not load is "your
|
|
423
|
+
// instance would not boot". On its own it reads as though the inspection tool
|
|
424
|
+
// is broken, so say whose problem it is.
|
|
425
|
+
//
|
|
426
|
+
// Scoped to the LOAD deliberately, and worded about the configuration rather
|
|
427
|
+
// than about a file. A failure below this line is a bug in this command, and
|
|
428
|
+
// claiming `paddock start` would fail too would be a false statement at the
|
|
429
|
+
// moment someone most needs a true one — while a `CLAUDE_CONFIG_DIR` refusal
|
|
430
|
+
// is a real boot failure with no config file involved at all.
|
|
431
|
+
throw new CliError(`${err.message}\nThis is what \`paddock start\` would fail with too.`);
|
|
432
|
+
}
|
|
433
|
+
const report = resolveConfigReport(cfg);
|
|
434
|
+
if (action === "eject") {
|
|
435
|
+
const plan = buildEjectPlan(report, opts.includeEnv === true);
|
|
436
|
+
if (opts.write === true) {
|
|
437
|
+
try {
|
|
438
|
+
writeInstanceConfig(report.configPath, plan.pairs);
|
|
439
|
+
}
|
|
440
|
+
catch (err) {
|
|
441
|
+
// An unwritable path, a read-only mount, a directory where the file
|
|
442
|
+
// should be. Raised as a CliError so `paddock.ts` reports it as this
|
|
443
|
+
// command failing rather than appending its "`paddock start` would fail
|
|
444
|
+
// on the same file" note, which is true of a malformed config and a
|
|
445
|
+
// lie about a permissions problem.
|
|
446
|
+
throw new CliError(`could not write ${report.configPath}: ${err.message}\n` +
|
|
447
|
+
`Nothing was changed. Check the path is writable, then re-run.`);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
console.log(formatEjectPlan(plan, GROUPS, opts.write === true));
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
if (action !== "show")
|
|
454
|
+
throw new Error(`unhandled config action: ${String(action)}`);
|
|
455
|
+
if (opts.json) {
|
|
456
|
+
console.log(toJson(report, opts.showSensitive === true));
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
console.log(opts.resolved
|
|
460
|
+
? formatResolved(report, GROUPS, opts.showSensitive === true)
|
|
461
|
+
: formatSummary(report));
|
|
462
|
+
}
|
|
@@ -23,7 +23,7 @@ import os from "node:os";
|
|
|
23
23
|
import path from "node:path";
|
|
24
24
|
import { spawn } from "node:child_process";
|
|
25
25
|
import { fileURLToPath } from "node:url";
|
|
26
|
-
import { CliError, USAGE, SERVICE_USAGE, parseCommand, nodeVersionProblem, explainListenError, } from "./args.js";
|
|
26
|
+
import { CliError, USAGE, SERVICE_USAGE, CONFIG_USAGE, parseCommand, nodeVersionProblem, explainListenError, } from "./args.js";
|
|
27
27
|
import { runService, safeHomeDir } from "./service/index.js";
|
|
28
28
|
/**
|
|
29
29
|
* This module's own file and directory: `<…>/packages/server/dist/cli/`.
|
|
@@ -187,6 +187,52 @@ async function service(command) {
|
|
|
187
187
|
throw err;
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
|
+
/**
|
|
191
|
+
* Which instance this invocation is about: an explicit flag, else the
|
|
192
|
+
* environment, else `~/.paddock`.
|
|
193
|
+
*
|
|
194
|
+
* Shared by `start` and `config` deliberately. A `config show` that resolved its
|
|
195
|
+
* data dir even slightly differently would report on an instance other than the
|
|
196
|
+
* one `paddock start` runs — and would do it convincingly, since every value it
|
|
197
|
+
* printed would be real. One function, so the two cannot diverge.
|
|
198
|
+
*/
|
|
199
|
+
function resolveDataDir(opts) {
|
|
200
|
+
return path.resolve(opts.dataDir ?? process.env.PADDOCK_DATA_DIR ?? path.join(os.homedir(), ".paddock"));
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* `paddock config …` — print the resolved configuration (#878).
|
|
204
|
+
*
|
|
205
|
+
* Sets `PADDOCK_DATA_DIR` before the dynamic import for the same reason the
|
|
206
|
+
* start path does: `loadPaddockConfig` reads the environment, so the environment
|
|
207
|
+
* has to be finished first. Nothing else `main` does on the way to `start()` —
|
|
208
|
+
* the PATH edit, the first-run notice, the log-level defaults — applies to a
|
|
209
|
+
* command that only reads.
|
|
210
|
+
*/
|
|
211
|
+
async function config(command) {
|
|
212
|
+
const { action, opts } = command;
|
|
213
|
+
// Unreachable: `parseCommand` only omits the action when `--help` was given,
|
|
214
|
+
// which `main` has already handled. Typed rather than asserted.
|
|
215
|
+
if (action === undefined) {
|
|
216
|
+
console.log(CONFIG_USAGE);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
process.env.PADDOCK_DATA_DIR = resolveDataDir(opts);
|
|
220
|
+
const { runConfig } = await import("./config.js");
|
|
221
|
+
try {
|
|
222
|
+
await runConfig(action, opts);
|
|
223
|
+
}
|
|
224
|
+
catch (err) {
|
|
225
|
+
// A config that will not load reaches here as a CliError already carrying
|
|
226
|
+
// the "start would fail too" note; anything else is reported as itself,
|
|
227
|
+
// because it is a bug in this command rather than in the configuration.
|
|
228
|
+
// Same shape as `service` above.
|
|
229
|
+
if (err instanceof CliError)
|
|
230
|
+
fail(err.message);
|
|
231
|
+
if (err instanceof Error)
|
|
232
|
+
fail(err.message);
|
|
233
|
+
throw err;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
190
236
|
async function main() {
|
|
191
237
|
let command;
|
|
192
238
|
try {
|
|
@@ -199,7 +245,7 @@ async function main() {
|
|
|
199
245
|
}
|
|
200
246
|
const opts = command.opts;
|
|
201
247
|
if (opts.help) {
|
|
202
|
-
console.log(command.verb === "service" ? SERVICE_USAGE : USAGE);
|
|
248
|
+
console.log(command.verb === "service" ? SERVICE_USAGE : command.verb === "config" ? CONFIG_USAGE : USAGE);
|
|
203
249
|
return;
|
|
204
250
|
}
|
|
205
251
|
if (opts.version) {
|
|
@@ -213,6 +259,10 @@ async function main() {
|
|
|
213
259
|
await service(command);
|
|
214
260
|
return;
|
|
215
261
|
}
|
|
262
|
+
if (command.verb === "config") {
|
|
263
|
+
await config(command);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
216
266
|
addBundledBinsToPath();
|
|
217
267
|
// NOTHING here reads `process.cwd()`, and that is the point (#798). `--here`
|
|
218
268
|
// used to make the current directory the workspace by setting
|
|
@@ -235,7 +285,7 @@ async function main() {
|
|
|
235
285
|
// server resolves config inside `buildApp()`, so anything set before the
|
|
236
286
|
// dynamic import below is picked up with no special-casing in config.ts.
|
|
237
287
|
// Precedence is explicit flag > existing env > our default.
|
|
238
|
-
const dataDir =
|
|
288
|
+
const dataDir = resolveDataDir(opts);
|
|
239
289
|
noteFirstRun(dataDir);
|
|
240
290
|
process.env.PADDOCK_DATA_DIR = dataDir;
|
|
241
291
|
if (opts.port)
|
|
@@ -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).
|