@promptctl/cc-candybar 1.42.1 → 1.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/dist/index.mjs +72 -71
  2. package/package.json +5 -6
  3. package/src/check.ts +0 -478
  4. package/src/cli-flags.ts +0 -8
  5. package/src/click/wire.ts +0 -158
  6. package/src/config/action.ts +0 -329
  7. package/src/config/cli.ts +0 -71
  8. package/src/config/default-dsl-config.ts +0 -1645
  9. package/src/config/disclosure.ts +0 -170
  10. package/src/config/dsl-loader.ts +0 -339
  11. package/src/config/dsl-types.ts +0 -581
  12. package/src/config/edit-chrome.ts +0 -559
  13. package/src/config/help.ts +0 -151
  14. package/src/config/ident.ts +0 -22
  15. package/src/config/layout-ops.ts +0 -177
  16. package/src/config/loader/actions.ts +0 -972
  17. package/src/config/loader/cache.ts +0 -206
  18. package/src/config/loader/cross-ref.ts +0 -714
  19. package/src/config/loader/cycles.ts +0 -148
  20. package/src/config/loader/diagnostics.ts +0 -99
  21. package/src/config/loader/discovery.ts +0 -182
  22. package/src/config/loader/edit-mode.ts +0 -137
  23. package/src/config/loader/emit-schema.ts +0 -68
  24. package/src/config/loader/globals.ts +0 -269
  25. package/src/config/loader/helpers.ts +0 -48
  26. package/src/config/loader/layout.ts +0 -693
  27. package/src/config/loader/looks.ts +0 -96
  28. package/src/config/loader/menu-synth.ts +0 -435
  29. package/src/config/loader/merge.ts +0 -115
  30. package/src/config/loader/persist-target.ts +0 -67
  31. package/src/config/loader/presets.ts +0 -119
  32. package/src/config/loader/refs.ts +0 -100
  33. package/src/config/loader/reserved-namespace.ts +0 -38
  34. package/src/config/loader/segments.ts +0 -120
  35. package/src/config/loader/validate-core.ts +0 -737
  36. package/src/config/loader/variables.ts +0 -260
  37. package/src/config/menu-keys.ts +0 -139
  38. package/src/config/option-domain.ts +0 -164
  39. package/src/config/presets.ts +0 -326
  40. package/src/config/settings-menu.ts +0 -775
  41. package/src/daemon/acquire.ts +0 -684
  42. package/src/daemon/cache/git.ts +0 -649
  43. package/src/daemon/cache/render.ts +0 -623
  44. package/src/daemon/cache/session-usage-store.ts +0 -720
  45. package/src/daemon/cache/watchers.ts +0 -249
  46. package/src/daemon/client-debug.ts +0 -120
  47. package/src/daemon/client-stats.ts +0 -130
  48. package/src/daemon/client-transport.ts +0 -273
  49. package/src/daemon/client.ts +0 -78
  50. package/src/daemon/config-overrides-store.ts +0 -663
  51. package/src/daemon/debug-types.ts +0 -91
  52. package/src/daemon/debug.ts +0 -264
  53. package/src/daemon/fork-bomb-breaker.ts +0 -351
  54. package/src/daemon/limits.ts +0 -211
  55. package/src/daemon/log.ts +0 -81
  56. package/src/daemon/parent-watchdog.ts +0 -87
  57. package/src/daemon/paths.ts +0 -211
  58. package/src/daemon/process-fingerprint.ts +0 -146
  59. package/src/daemon/protocol.ts +0 -292
  60. package/src/daemon/render-payload.ts +0 -1256
  61. package/src/daemon/server.ts +0 -1330
  62. package/src/daemon/session-state-file.ts +0 -108
  63. package/src/daemon/session-state.ts +0 -237
  64. package/src/daemon/socket-lease.ts +0 -209
  65. package/src/daemon/socket-ownership.ts +0 -209
  66. package/src/daemon/stats.ts +0 -235
  67. package/src/daemon/verbs/config-validators.ts +0 -250
  68. package/src/daemon/verbs/index.ts +0 -706
  69. package/src/daemon/verbs/state-validators.ts +0 -249
  70. package/src/daemon/verbs/validator-registry.ts +0 -457
  71. package/src/demo/dsl.ts +0 -143
  72. package/src/demo/mock-data.ts +0 -67
  73. package/src/demo/statusline.json5 +0 -94
  74. package/src/dsl/node-registry.ts +0 -374
  75. package/src/dsl/render.ts +0 -803
  76. package/src/help-text.ts +0 -90
  77. package/src/index.ts +0 -210
  78. package/src/install/currency.ts +0 -197
  79. package/src/install/index.ts +0 -557
  80. package/src/proc/launch.ts +0 -459
  81. package/src/proc/stats-handle.ts +0 -13
  82. package/src/render/action.ts +0 -883
  83. package/src/render/active-segment.ts +0 -78
  84. package/src/render/diagnostic-style.ts +0 -23
  85. package/src/render/diagnostic-text.ts +0 -77
  86. package/src/render/error-glyph.ts +0 -53
  87. package/src/render/menu.ts +0 -257
  88. package/src/render/outcome-plan.ts +0 -45
  89. package/src/render/picker.ts +0 -372
  90. package/src/render/segment-color.ts +0 -74
  91. package/src/render/split-lines.ts +0 -51
  92. package/src/render/strip.ts +0 -228
  93. package/src/segments/cache.ts +0 -131
  94. package/src/segments/context.ts +0 -190
  95. package/src/segments/git.ts +0 -1084
  96. package/src/segments/metrics.ts +0 -187
  97. package/src/segments/pricing.ts +0 -452
  98. package/src/segments/session.ts +0 -23
  99. package/src/segments/tmux.ts +0 -74
  100. package/src/template-engine/cells.ts +0 -90
  101. package/src/template-engine/colors.ts +0 -124
  102. package/src/template-engine/engine.ts +0 -108
  103. package/src/template-engine/funcs.ts +0 -232
  104. package/src/template-engine/index.ts +0 -11
  105. package/src/template-engine/layout.ts +0 -133
  106. package/src/template-engine/scope.ts +0 -62
  107. package/src/template-engine/sparkline.ts +0 -79
  108. package/src/themes/index.ts +0 -20
  109. package/src/themes/palette-resolvers.ts +0 -84
  110. package/src/themes/policy.ts +0 -393
  111. package/src/utils/cache.ts +0 -206
  112. package/src/utils/claude.ts +0 -683
  113. package/src/utils/color-support.ts +0 -118
  114. package/src/utils/formatters.ts +0 -99
  115. package/src/utils/logger.ts +0 -5
  116. package/src/utils/outcome.ts +0 -33
  117. package/src/utils/schema-validator.ts +0 -126
  118. package/src/utils/single-flight.ts +0 -57
  119. package/src/utils/terminal-width.ts +0 -51
  120. package/src/utils/terminal.ts +0 -11
  121. package/src/utils/transcript-fs.ts +0 -279
  122. package/src/var-system/index.ts +0 -24
  123. package/src/var-system/sources.ts +0 -1047
  124. package/src/var-system/store.ts +0 -223
  125. package/src/var-system/types.ts +0 -57
  126. package/src/version.ts +0 -17
@@ -1,329 +0,0 @@
1
- // [LAW:types-are-the-program] The author-facing SHAPE of a decoupled, named
2
- // ACTION — the config-file schema for `DslConfig.actions`. Pure data (no engine,
3
- // no rich-js): the strongest theorem about what a user can declare. The loader
4
- // narrows `unknown` to these; the runtime (render/action.ts) and the validator
5
- // derivation (daemon/verbs/state-validators.ts) consume them.
6
- //
7
- // [LAW:locality-or-seam] An action is the SEAM between the clickable
8
- // REPRESENTATION (a template region, `{{ action "name" … }}`) and the BEHAVIOR
9
- // (what the click does). They are joined by NAME: re-glyph a button without
10
- // touching behavior; re-target an action without touching the template. This is
11
- // the successor to the widget surface — a widget couples representation and
12
- // behavior in one declaration; an action splits them so one template expresses
13
- // anything (text, state-driven display, clickable regions) and the action table
14
- // is the single, statically-enumerable set of effects those regions can fire.
15
- //
16
- // [LAW:one-source-of-truth] Because a `set` action carries its key and value
17
- // SOURCE as literal data (a literal `to`, an option domain, or numeric bounds),
18
- // the writable-key gate DERIVES from the action table (deriveActionValidators).
19
- // A template references a NAME; it cannot smuggle an un-gated write. The rendered
20
- // click and the gate share ONE source — the action declaration.
21
- //
22
- // [LAW:one-source-of-truth] The option-domain + effect-verb vocabulary lives
23
- // HERE — action.ts is the surviving home now that the widget surface is gone.
24
- // These are the shapes a picker draws options from and the set/copy/open/int
25
- // discriminator the loader and the validator-derivation match on.
26
-
27
- // [LAW:one-source-of-truth] The domain a picker draws options from. Resolved
28
- // through option-domain.ts's registry — themes/styles are registry-backed
29
- // static lists, "looks" is the one PER-CONFIG domain (the merged `looks`
30
- // block's names, threaded as data rather than consulted from a module
31
- // constant), and an inline array is its own domain, needing no registration
32
- // at all. Re-exported here so ActionDecl stays self-contained to read.
33
- import type { OptionDomain } from "./option-domain.js";
34
- export type { OptionDomain } from "./option-domain.js";
35
-
36
- // [LAW:types-are-the-program] The top-level discriminator of an ActionDecl — the
37
- // click effect is keyed by which of these is present. The loader proves
38
- // exactly-one-of; the renderer and validator-derivation match with no fallthrough.
39
- //
40
- // [LAW:one-source-of-truth] `persist` is `set`'s PERSISTENT twin
41
- // (candybar-config-engine-71o.2): `set` mutates per-session SessionState,
42
- // `persist` mutates the config's `globals` DEFAULT through the daemon-owned
43
- // overrides layer (never the hand-authored config file). `reset` clears one
44
- // persisted override — the gated undo `persist` needs, since a machine-owned
45
- // write with no way back would be a one-way ratchet.
46
- //
47
- // [LAW:one-source-of-truth] `undo`/`redo` (brandon-layout-edit-2gc.2) are
48
- // `reset`'s FINE-GRAINED siblings: `reset` clears one named key outright
49
- // (the coarse "forget this override" case); `undo`/`redo` step ONE GLOBAL
50
- // history of every `persist`/`reset` write ever made to the overrides layer
51
- // — every key, not just structural layout edits — back and forth. Neither
52
- // carries a key: the history is a single stack over the whole overrides
53
- // file (config-overrides-store.ts owns it), so the action is a bare marker,
54
- // like `int: true` is for a set-int cursor.
55
- export const ACTION_KEYS = [
56
- "set",
57
- "persist",
58
- "copy",
59
- "open",
60
- "reset",
61
- "undo",
62
- "redo",
63
- ] as const;
64
- export type ActionKey = (typeof ACTION_KEYS)[number];
65
-
66
- // [LAW:types-are-the-program] An ActionDecl is the click effect a named action
67
- // binds to. The top-level discriminator is which of `set`/`copy`/`open` is
68
- // present (the CacheDecl/widget-Action pattern); a `set` is sub-discriminated by
69
- // its value SOURCE — `to` (literal), `from` (option-bound), or `min/max/by`
70
- // (bounded step). The loader proves exactly-one-of at each level; the renderer
71
- // and the validator-derivation match on the present key with no fallthrough.
72
- //
73
- // set + to — write a literal value -> allow-list {to}
74
- // set + from — write the option the template binds at render
75
- // (a picker ranges the domain — a registered name like
76
- // "themes", or an inline literal array) -> allow-list
77
- // {options}
78
- // set + min/max/by — write wrap(current ± by) clamped to [min,max]
79
- // (a stepper affordance) -> range [min,max]
80
- // set + int — write any integer the render binds (a paged cursor:
81
- // -1 closed / 0..N pages, clamp owned by the renderer)
82
- // -> int gate (unbounded). The missing primitive a
83
- // width-paginated picker needs — its page key accepts any
84
- // integer, which no bounded/literal arm can express.
85
- // set + cycle — write the SUCCESSOR of the current value in the
86
- // member list, wrapping; a current value outside the
87
- // domain counts as the first member (so the second
88
- // member is the "first click" target — order members
89
- // default-state-first). The bounded stepper's sibling:
90
- // a stepper steps a range, a cycle steps an enumerated
91
- // domain (toggles, N-state cyclers, accordion paths)
92
- // -> allow-list {members}
93
- // copy — copy templated text to the clipboard -> no gate
94
- // open — open a templated target in the editor -> no gate
95
- // undo — step the config-overrides layer's GLOBAL history one
96
- // entry back (any persist/reset write, not just a
97
- // layout op) -> no gate, no key: there is nothing a
98
- // template could smuggle, since the value restored is
99
- // whatever the daemon's own history recorded, never
100
- // wire input
101
- // redo — the inverse of undo: re-apply the most recently
102
- // undone entry -> no gate, no key
103
- // removeSegment — (persist only) remove the named segment from the
104
- // preset-root the `persist` key addresses
105
- // (`presets.<name>.rootOps`) -> allow-list {one op
106
- // token — see src/config/layout-ops.ts}
107
- // insertSegment +
108
- // anchor + relation — (persist only) insert a named segment before/after
109
- // an existing one, same key shape -> allow-list {one
110
- // op token}
111
- // insertSegmentFrom +
112
- // anchor + relation — (persist only) insertSegment's domain-sourced
113
- // sibling (brandon-layout-edit-2gc.3): the segment
114
- // name is picked from an option domain at render
115
- // (a `{{ menu }}`'s bound option) rather than fixed
116
- // at author time -> allow-list {one op token per
117
- // domain member}
118
- //
119
- // [LAW:one-source-of-truth] `set` writes SessionState and `persist` writes
120
- // the config-overrides layer, so only those two derive a validator (through
121
- // the SAME shared registry algebra — see validator-registry.ts). copy/open/
122
- // reset write nothing SPEC-shaped (reset's target is gated by key membership,
123
- // not a value domain) — they derive nothing. The vocabulary grows by arms (a
124
- // future `run`/`open-url`), not by validator plumbing.
125
- //
126
- // [LAW:one-type-per-behavior] `persist` mirrors `set`'s four value-source
127
- // arms verbatim (to/from/min-max-by/cycle) MINUS `int`: an unbounded page
128
- // cursor is a UI-only paging concept (a picker's own navigation state) with
129
- // no meaning as a persisted config default.
130
- //
131
- // [LAW:locality-or-seam] `removeSegment`/`insertSegment` are `persist`-ONLY
132
- // (brandon-layout-edit-2gc.1) — a structural edit is always a durable,
133
- // machine-owned write by design (the ticket's own instruction: reuse 71o's
134
- // writer, land in the SAME overrides layer), so there is no SessionState
135
- // twin. Every operation is fully literal at config-author time — the
136
- // segment names and relation are DATA the loader proves at load, not a
137
- // runtime picker — so each declared action has exactly one legal request,
138
- // gated the same one-member-allow-list way a literal `to` already is.
139
- export type ActionDecl =
140
- | { readonly set: string; readonly to: string }
141
- | { readonly set: string; readonly from: OptionDomain }
142
- | {
143
- readonly set: string;
144
- readonly min: number;
145
- readonly max: number;
146
- readonly by: number;
147
- }
148
- | { readonly set: string; readonly int: true }
149
- | { readonly set: string; readonly cycle: readonly string[] }
150
- | { readonly persist: string; readonly to: string }
151
- | { readonly persist: string; readonly from: OptionDomain }
152
- | {
153
- readonly persist: string;
154
- readonly min: number;
155
- readonly max: number;
156
- readonly by: number;
157
- }
158
- | { readonly persist: string; readonly cycle: readonly string[] }
159
- | { readonly persist: string; readonly removeSegment: string }
160
- | {
161
- readonly persist: string;
162
- readonly insertSegment: string;
163
- readonly anchor: string;
164
- readonly relation: "before" | "after";
165
- }
166
- // [LAW:one-source-of-truth] brandon-layout-edit-2gc.3's DOMAIN-SOURCED
167
- // sibling of `insertSegment`: the same tree op, but the segment name comes
168
- // from the template's bound option (a picker/menu cell) instead of being
169
- // fixed at config-author time — exactly the `to`-vs-`from` split `set`/
170
- // `persist` already draw, one arm over. `anchor`/`relation` stay literal
171
- // (the POSITION is still author-time data; only WHICH segment lands there
172
- // is picked at render). This is what makes a `{{ menu "insertHere" }}`
173
- // legal over a structural edit: `requireOptionKind` (render/picker.ts)
174
- // admits it alongside set-option/persist-option, and the click writes
175
- // `encodeLayoutOp({ op: "insert", segment: <picked>, anchor, relation })` —
176
- // the SAME wire shape a literal `insertSegment` action emits, so undo/redo
177
- // and the daemon's apply-layout-op handler need no changes at all.
178
- | {
179
- readonly persist: string;
180
- readonly insertSegmentFrom: OptionDomain;
181
- readonly anchor: string;
182
- readonly relation: "before" | "after";
183
- }
184
- | { readonly copy: string }
185
- | { readonly open: string }
186
- | { readonly reset: string }
187
- | { readonly undo: true }
188
- | { readonly redo: true }
189
- | DualActionDecl;
190
-
191
- // [LAW:one-source-of-truth] The key whose PRESENCE makes an action
192
- // dual-destination, and whose VALUE names the SessionState key that chooses
193
- // the destination at click time. Spelled once: the loader dispatches on it,
194
- // the compiler reads it, and the settings menu's synthesis writes it.
195
- export const PERSIST_WHEN = "persistWhen";
196
-
197
- // [LAW:types-are-the-program] ONE setting, ONE control, TWO stores
198
- // (candybar-settings-ui-aok.3). Before this arm, a setting with both a
199
- // session and a durable half cost the author two declarations and the reader
200
- // two controls — `{{ menu "applyTheme" }}` beside `📌{{ menu
201
- // "applyThemeForever" }}` — a second representation of one setting that had
202
- // to be reconciled at every glance.
203
- //
204
- // A dual decl carries BOTH destination keys (`set` = the SessionState key,
205
- // `persist` = the config-overrides key — they differ where history made them
206
- // differ, e.g. session "theme" over globals field "palette") and ONE value
207
- // source shared by both. `persistWhen` names the session key whose boolean
208
- // value SELECTS the destination; the value written is identical either way,
209
- // so the destination is the only thing that varies, and it varies as DATA
210
- // [LAW:dataflow-not-control-flow].
211
- //
212
- // [LAW:single-enforcer] This adds NO gate surface. `actionDestinations`
213
- // below explodes one dual decl into exactly the two single-destination decls
214
- // it is equivalent to, and both validator derivations (state-validators.ts,
215
- // config-validators.ts) fold over that explosion — so the writable (key,
216
- // spec) pairs stay statically enumerable from the declarations, derived by
217
- // the same code that has always derived them.
218
- //
219
- // The `int` / `removeSegment` / `insertSegment*` sources are deliberately
220
- // absent: `int` is a UI-only page cursor with no durable meaning, and the
221
- // structural edits are persist-only by design (see the arms above), so
222
- // neither has two destinations to choose between.
223
- export type DualActionDecl =
224
- | {
225
- readonly set: string;
226
- readonly persist: string;
227
- readonly persistWhen: string;
228
- readonly to: string;
229
- }
230
- | {
231
- readonly set: string;
232
- readonly persist: string;
233
- readonly persistWhen: string;
234
- readonly from: OptionDomain;
235
- }
236
- | {
237
- readonly set: string;
238
- readonly persist: string;
239
- readonly persistWhen: string;
240
- readonly cycle: readonly string[];
241
- }
242
- | {
243
- readonly set: string;
244
- readonly persist: string;
245
- readonly persistWhen: string;
246
- readonly min: number;
247
- readonly max: number;
248
- readonly by: number;
249
- };
250
-
251
- // [LAW:parse-dont-validate] The one discriminator for the dual arm, and the
252
- // reason every `"set" in a` consumer stays correct: a dual decl carries both
253
- // `set` and `persist`, so a consumer that must treat the two destinations
254
- // separately asks THIS first (or folds through actionDestinations below), and
255
- // a consumer that only asks "does this bind a session write" (actionBindsSet)
256
- // keeps its existing answer with no change at all.
257
- export function actionIsDual(a: ActionDecl): a is DualActionDecl {
258
- return PERSIST_WHEN in a;
259
- }
260
-
261
- // [LAW:one-source-of-truth] THE explosion of an action into the
262
- // single-destination declarations it writes through — `[a]` for every
263
- // ordinary action, `[session, durable]` for a dual. Every consumer that
264
- // reasons about DESTINATIONS (both validator derivations) flatMaps through
265
- // this instead of learning the dual shape, so the gate a dual derives is by
266
- // construction the union of the gates its two halves would have derived
267
- // separately — there is no second derivation to keep in agreement.
268
- export function actionDestinations(a: ActionDecl): readonly ActionDecl[] {
269
- if (!actionIsDual(a)) return [a];
270
- // [LAW:types-are-the-program] Written out per value source rather than
271
- // spread-and-cast: the two halves are then ordinary, fully-typed `set` and
272
- // `persist` members — the exact declarations an author would have written —
273
- // and adding a value source to DualActionDecl fails to compile here until
274
- // its explosion is stated.
275
- if ("to" in a) {
276
- return [
277
- { set: a.set, to: a.to },
278
- { persist: a.persist, to: a.to },
279
- ];
280
- }
281
- if ("from" in a) {
282
- return [
283
- { set: a.set, from: a.from },
284
- { persist: a.persist, from: a.from },
285
- ];
286
- }
287
- if ("cycle" in a) {
288
- return [
289
- { set: a.set, cycle: a.cycle },
290
- { persist: a.persist, cycle: a.cycle },
291
- ];
292
- }
293
- return [
294
- { set: a.set, min: a.min, max: a.max, by: a.by },
295
- { persist: a.persist, min: a.min, max: a.max, by: a.by },
296
- ];
297
- }
298
-
299
- // [LAW:dataflow-not-control-flow] Does this action write a SessionState key? A
300
- // `set` action composes a set-state click URL whose first segment is session.id;
301
- // copy/open/persist/reset embed none. One predicate the loader's session.id
302
- // requirement folds over — no per-arm branching at the callsite.
303
- export function actionBindsSet(a: ActionDecl): boolean {
304
- return "set" in a;
305
- }
306
-
307
- // [LAW:dataflow-not-control-flow] Does this action write the config-overrides
308
- // layer? Mirrors actionBindsSet for the `persist` arm.
309
- export function actionBindsPersist(a: ActionDecl): boolean {
310
- return "persist" in a;
311
- }
312
-
313
- // [LAW:dataflow-not-control-flow] Does this action clear a config-overrides
314
- // key? `reset` carries session.id on the wire too (for click-error surfacing,
315
- // same as set/persist), so it joins the same requirement.
316
- export function actionBindsReset(a: ActionDecl): boolean {
317
- return "reset" in a;
318
- }
319
-
320
- // [LAW:dataflow-not-control-flow] Does this action step the config-overrides
321
- // history? `undo`/`redo` carry session.id on the wire too — same reason as
322
- // `reset`: an empty stack is a loud, session-scoped click.error, not a
323
- // silent no-op (the ticket's own done-gate).
324
- export function actionBindsUndo(a: ActionDecl): boolean {
325
- return "undo" in a;
326
- }
327
- export function actionBindsRedo(a: ActionDecl): boolean {
328
- return "redo" in a;
329
- }
package/src/config/cli.ts DELETED
@@ -1,71 +0,0 @@
1
- // [LAW:single-enforcer] Config-tooling CLI entry point for `schema`. It carries
2
- // no schema LOGIC — it serves the build-generated artifact (derived from the
3
- // loader's declarations), so it is a binding, not a reimplementation
4
- // [LAW:one-source-of-truth]. Config *validation* lives in `cc-candybar check`
5
- // (src/check.ts) — the full-pipeline verdict command; the old `lint` is its
6
- // alias.
7
-
8
- import fs from "node:fs";
9
- import path from "node:path";
10
- import process from "node:process";
11
-
12
- const EXIT_VALID = 0;
13
- const EXIT_USAGE = 2;
14
-
15
- // Read the build-generated JSON Schema for the config file shape (RawDslConfig),
16
- // or null when the artifact is absent. Pure read — `runSchema` owns the side
17
- // effects, so the locate-and-read path is testable.
18
- export function loadSchemaText(): string | null {
19
- const schemaPath = locateSchema();
20
- return schemaPath === null ? null : fs.readFileSync(schemaPath, "utf-8");
21
- }
22
-
23
- // `cc-candybar schema` — print the schema so an editor can annotate a config with
24
- // `"$schema": "<this output, saved somewhere>"` (or the stable published URL) for
25
- // autocomplete + structural validation. Emitted from the loader schemas at build
26
- // time (scripts/gen-schema.ts → emitConfigSchema); served verbatim here (the
27
- // emitter runs at build, not ship time).
28
- export function runSchema(): void {
29
- // [LAW:no-silent-failure] A schema that exists but cannot be read (EACCES, a
30
- // deletion racing the existsSync in locateSchema) is a distinct failure from
31
- // schema-not-found — report it as what it is, not a misleading top-level
32
- // render error.
33
- let text: string | null;
34
- try {
35
- text = loadSchemaText();
36
- } catch (e) {
37
- process.stderr.write(
38
- `schema: cannot read bundled schema: ${e instanceof Error ? e.message : String(e)}\n`,
39
- );
40
- process.exit(EXIT_USAGE);
41
- }
42
- if (text === null) {
43
- process.stderr.write(
44
- "schema: bundled schema not found (expected schema/cc-candybar.schema.json). " +
45
- "Run `pnpm gen:schema` from a source checkout.\n",
46
- );
47
- process.exit(EXIT_USAGE);
48
- }
49
- process.stdout.write(text);
50
- process.exit(EXIT_VALID);
51
- }
52
-
53
- // [LAW:locality-or-seam] The schema sits at `<package-root>/schema/...`. Anchor
54
- // on the running CLI entry (argv[1] — `dist/index.mjs` in prod, since every
55
- // subcommand execs the Node fallback) and walk up to the nearest ancestor that
56
- // holds the artifact, so the prod and dev layouts aren't special-cased. argv[1]
57
- // (not import.meta) keeps this resolvable identically under the ESM bundle and
58
- // any module setting.
59
- function locateSchema(): string | null {
60
- const rel = path.join("schema", "cc-candybar.schema.json");
61
- const anchor = process.argv[1];
62
- if (anchor === undefined) return null;
63
- let dir = path.dirname(path.resolve(anchor));
64
- for (;;) {
65
- const candidate = path.join(dir, rel);
66
- if (fs.existsSync(candidate)) return candidate;
67
- const parent = path.dirname(dir);
68
- if (parent === dir) return null;
69
- dir = parent;
70
- }
71
- }