@promptctl/cc-candybar 1.25.0 → 1.27.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/dist/index.mjs +83 -76
- package/package.json +6 -6
- package/schema/cc-candybar.schema.json +193 -4
- package/src/check.ts +49 -27
- package/src/click/wire.ts +16 -0
- package/src/config/action.ts +57 -22
- package/src/config/default-dsl-config.ts +424 -55
- package/src/config/dsl-loader.ts +14 -2
- package/src/config/dsl-types.ts +59 -0
- package/src/config/loader/actions.ts +283 -109
- package/src/config/loader/cross-ref.ts +148 -28
- package/src/config/loader/emit-schema.ts +2 -0
- package/src/config/loader/globals.ts +118 -31
- package/src/config/loader/merge.ts +58 -1
- package/src/config/loader/persist-target.ts +32 -0
- package/src/config/loader/presets.ts +107 -0
- package/src/config/option-domain.ts +164 -0
- package/src/config/presets.ts +156 -0
- package/src/daemon/cache/git.ts +1 -1
- package/src/daemon/cache/render.ts +61 -7
- package/src/daemon/config-overrides-store.ts +322 -0
- package/src/daemon/paths.ts +10 -0
- package/src/daemon/render-payload.ts +84 -19
- package/src/daemon/server.ts +68 -55
- package/src/daemon/verbs/config-validators.ts +127 -0
- package/src/daemon/verbs/index.ts +129 -2
- package/src/daemon/verbs/state-validators.ts +98 -586
- package/src/daemon/verbs/validator-registry.ts +457 -0
- package/src/demo/dsl.ts +17 -10
- package/src/dsl/node-registry.ts +54 -39
- package/src/dsl/render.ts +158 -46
- package/src/help-text.ts +59 -0
- package/src/index.ts +2 -47
- package/src/install/index.ts +18 -4
- package/src/render/action.ts +155 -33
- package/src/render/active-segment.ts +78 -0
- package/src/render/menu.ts +16 -11
- package/src/render/picker.ts +51 -13
- package/src/render/segment-color.ts +74 -0
- package/src/segments/git.ts +389 -48
- package/src/template-engine/colors.ts +67 -45
- package/src/template-engine/engine.ts +11 -12
- package/src/themes/index.ts +1 -4
- package/src/themes/palette-resolvers.ts +22 -30
- package/src/themes/policy.ts +37 -16
package/src/dsl/render.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// the input values (kind discriminators, layout length, palette presence)
|
|
12
12
|
// govern output, not whether operations run.
|
|
13
13
|
|
|
14
|
-
import type { RichText,
|
|
14
|
+
import type { RichText, Palette, ThemeKey } from "@promptctl/rich-js";
|
|
15
15
|
import { ColorSpec, Style, lighten, IDENTITY } from "@promptctl/rich-js";
|
|
16
16
|
import type { Engine, Template } from "@promptctl/go-template-js";
|
|
17
17
|
import type {
|
|
@@ -21,6 +21,8 @@ import type {
|
|
|
21
21
|
LayoutNode,
|
|
22
22
|
} from "../config/dsl-types.js";
|
|
23
23
|
import { HUE_STEP_VAR } from "../config/dsl-types.js";
|
|
24
|
+
import { perConfigDomainsFor } from "../config/option-domain.js";
|
|
25
|
+
import { PRESET_FLOOR, presetNames, presetRoot } from "../config/presets.js";
|
|
24
26
|
import type { VariableStore } from "../var-system/store.js";
|
|
25
27
|
import type { SourceRegistry } from "../var-system/sources.js";
|
|
26
28
|
import {
|
|
@@ -30,11 +32,12 @@ import {
|
|
|
30
32
|
} from "../var-system/sources.js";
|
|
31
33
|
import type { BuildLineOptions } from "../render/strip.js";
|
|
32
34
|
import { DEFAULT_PADDING, renderStripCells } from "../render/strip.js";
|
|
33
|
-
import {
|
|
35
|
+
import { paletteForThemeName } from "../themes/index.js";
|
|
34
36
|
import { buildScope } from "../template-engine/scope.js";
|
|
35
37
|
import {
|
|
36
38
|
createCcCandybarEngine,
|
|
37
39
|
evaluateWhen,
|
|
40
|
+
resolveSegmentColors,
|
|
38
41
|
} from "../template-engine/index.js";
|
|
39
42
|
import {
|
|
40
43
|
compileActions,
|
|
@@ -47,6 +50,11 @@ import {
|
|
|
47
50
|
collectMenuDrops,
|
|
48
51
|
type MenuRuntime,
|
|
49
52
|
} from "../render/menu.js";
|
|
53
|
+
import {
|
|
54
|
+
createActiveSegmentRef,
|
|
55
|
+
type ActiveSegmentRef,
|
|
56
|
+
} from "../render/active-segment.js";
|
|
57
|
+
import { segmentColorFuncs } from "../render/segment-color.js";
|
|
50
58
|
// [LAW:one-way-deps] The node-type registry sits below this driver: it owns the
|
|
51
59
|
// compiled node shapes + each kind's compile/render, dispatched via nodeType().
|
|
52
60
|
// render.ts threads the recursion (compileChild/renderChild) + the hue counter in
|
|
@@ -71,14 +79,27 @@ import {
|
|
|
71
79
|
// owns node behavior); this driver only assembles + walks them.
|
|
72
80
|
export interface CompiledConfig {
|
|
73
81
|
readonly segments: CompiledSegments;
|
|
74
|
-
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
82
|
+
// [LAW:dataflow-not-control-flow] EVERY preset's layout, compiled up front and
|
|
83
|
+
// keyed by preset name — the render selects one by name rather than compiling
|
|
84
|
+
// per session. This is the same move `looks` makes one level down (every
|
|
85
|
+
// look's ThemeKey is resolved at load; the render picks one), and it is what
|
|
86
|
+
// lets a per-SESSION preset pick ride a per-ENTRY compilation: one RenderCache
|
|
87
|
+
// entry serves many sessions, so nothing session-shaped may be compiled here.
|
|
88
|
+
// Total over `presetNames` — every selectable name, floor included — so the
|
|
89
|
+
// lookup needs no absent case. A preset declaring no `root` of its own maps
|
|
90
|
+
// to the config's own compiled root: the identity element, not a special
|
|
91
|
+
// case.
|
|
92
|
+
readonly roots: ReadonlyMap<string, CompiledNode>;
|
|
93
|
+
// [LAW:locality-or-seam] The menu runtime the engine's `menu` func closes over.
|
|
81
94
|
readonly menuRuntime: MenuRuntime;
|
|
95
|
+
// [LAW:one-source-of-truth] The single "which segment is rendering" record
|
|
96
|
+
// every segment-scoped template function reads — the menu's identity, the
|
|
97
|
+
// `color` func's palette, the `bgOf` func's background. Surfaced here so the
|
|
98
|
+
// walk can publish into it. One instance per compiled config; mutated
|
|
99
|
+
// synchronously within a single renderDsl walk (renders are sequential +
|
|
100
|
+
// synchronous, so no cross-render leak) — the spatial cousin of the hue
|
|
101
|
+
// cursor, one owner. [LAW:no-ambient-temporal-coupling]
|
|
102
|
+
readonly activeSegment: ActiveSegmentRef;
|
|
82
103
|
// [LAW:types-are-the-program] Variable declaration failures that did NOT
|
|
83
104
|
// prevent the config from loading (type mismatches, bad defaults). The
|
|
84
105
|
// affected variables are absent from the store; segments that reference them
|
|
@@ -296,31 +317,56 @@ export function registerDslConfig(
|
|
|
296
317
|
// [LAW:single-enforcer] Forward the caller's clock (the daemon's `() => new
|
|
297
318
|
// Date()`, a test's frozen clock) to the one engine. Omitted ⇒ undefined ⇒
|
|
298
319
|
// createCcCandybarEngine applies its single default; no second default literal.
|
|
320
|
+
// [LAW:one-source-of-truth] ONE record for "which segment is rendering", read
|
|
321
|
+
// by every segment-scoped template function: `{{ menu }}` takes its identity
|
|
322
|
+
// from the name, `{{ color }}` its palette, `{{ bgOf }}` its background. A
|
|
323
|
+
// per-feature pointer would let two features disagree about which segment is
|
|
324
|
+
// current. Built before the engine so the funcs can close over it; `current`
|
|
325
|
+
// stays null until a render walk publishes one.
|
|
326
|
+
const activeSegment = createActiveSegmentRef();
|
|
299
327
|
// [LAW:locality-or-seam] The menu runtime shares the action runtime (a menu's
|
|
300
|
-
// glyph + body resolve from the same compiled table + store) and
|
|
301
|
-
//
|
|
302
|
-
// can close over it; `current` stays null until a render walk publishes one.
|
|
328
|
+
// glyph + body resolve from the same compiled table + store) and reads the
|
|
329
|
+
// active segment through the shared record above.
|
|
303
330
|
const menuRuntime: MenuRuntime = {
|
|
304
331
|
action: actionRuntime,
|
|
305
|
-
|
|
332
|
+
activeSegment,
|
|
306
333
|
};
|
|
307
334
|
// [LAW:one-source-of-truth] The config's look names — the one PER-CONFIG
|
|
308
|
-
// option domain.
|
|
309
|
-
//
|
|
310
|
-
//
|
|
311
|
-
//
|
|
335
|
+
// option domain. Fed to every consumer (the `looks()` binding below, and —
|
|
336
|
+
// via perConfigDomainsFor, the SAME construction cross-ref.ts and
|
|
337
|
+
// state-validators.ts use — the compiled set-option domains), so the
|
|
338
|
+
// rendered options, a hand-authored `range looks`, and the derived click
|
|
339
|
+
// gate (which reads the same config in deriveActionValidators) trace to
|
|
340
|
+
// one source.
|
|
312
341
|
const lookNames = Object.keys(config.looks);
|
|
342
|
+
const presetOptions = presetNames(config.presets);
|
|
343
|
+
const perConfigDomains = perConfigDomainsFor(config);
|
|
313
344
|
const engine = createCcCandybarEngine(
|
|
314
|
-
undefined,
|
|
315
345
|
{
|
|
316
346
|
...actionFuncs(actionRuntime),
|
|
317
347
|
...pickerFuncs(actionRuntime),
|
|
318
348
|
...menuFuncs(menuRuntime),
|
|
349
|
+
// [LAW:one-source-of-truth] `{{ color }}` reads the palette of the
|
|
350
|
+
// segment currently rendering — the same palette its `bg:`/`fg:` resolve
|
|
351
|
+
// from, published by the walk. Binding it to a palette captured HERE
|
|
352
|
+
// (registration runs once per config load, renders happen per tick) was
|
|
353
|
+
// the two-clocks bug this seam exists to close: a session theme click, a
|
|
354
|
+
// look, or a per-segment hue rotation moved a segment's background while
|
|
355
|
+
// every in-body color stayed where it was, so one segment painted from
|
|
356
|
+
// two palettes at once. Reading live costs nothing structurally — FuncMap
|
|
357
|
+
// bodies run at evaluate time, so parse-once/evaluate-many is untouched.
|
|
358
|
+
// `{{ bgOf }}` rides the same record. [LAW:rich-js-owns-color-math]
|
|
359
|
+
...segmentColorFuncs(activeSegment),
|
|
319
360
|
// [LAW:one-type-per-behavior] The per-config sibling of the static
|
|
320
361
|
// themes()/styles() bindings (template-engine/funcs.ts): zero-arg
|
|
321
362
|
// projection of the "looks" option domain. Injected here — not in the
|
|
322
363
|
// static FuncMap — because the domain is this config's looks block.
|
|
323
364
|
looks: { fn: () => lookNames, argTypes: [] },
|
|
365
|
+
// The presets domain's twin of the binding above — same per-config
|
|
366
|
+
// reason, same shape. A hand-authored `range presets` and a
|
|
367
|
+
// `{{ menu "applyPreset" }}` therefore enumerate the same names the
|
|
368
|
+
// derived click gate admits.
|
|
369
|
+
presets: { fn: () => presetOptions, argTypes: [] },
|
|
324
370
|
},
|
|
325
371
|
opts?.clock,
|
|
326
372
|
);
|
|
@@ -363,7 +409,7 @@ export function registerDslConfig(
|
|
|
363
409
|
parse,
|
|
364
410
|
config.actions,
|
|
365
411
|
stateKeyToVar,
|
|
366
|
-
|
|
412
|
+
perConfigDomains,
|
|
367
413
|
);
|
|
368
414
|
|
|
369
415
|
// [LAW:dataflow-not-control-flow] One variable failing to declare does not
|
|
@@ -427,9 +473,9 @@ export function registerDslConfig(
|
|
|
427
473
|
// per-render basePalette; folding globals.palette in here too would freeze
|
|
428
474
|
// it per segment and the stale copy would shadow basePalette, so a session
|
|
429
475
|
// theme change could never recolor the bar.
|
|
430
|
-
|
|
476
|
+
palette:
|
|
431
477
|
seg.palette !== undefined
|
|
432
|
-
?
|
|
478
|
+
? paletteForThemeName(seg.palette)
|
|
433
479
|
: undefined,
|
|
434
480
|
};
|
|
435
481
|
}
|
|
@@ -463,9 +509,25 @@ export function registerDslConfig(
|
|
|
463
509
|
return nodeType(node.kind).compile(node, cctx);
|
|
464
510
|
};
|
|
465
511
|
|
|
512
|
+
// [LAW:one-source-of-truth] One compiled tree per declared preset, built
|
|
513
|
+
// through the SAME compileNode the config's own root goes through —
|
|
514
|
+
// `presetRoot` resolves the fragment's `root` or falls back to the config's,
|
|
515
|
+
// so the floor preset (the empty fragment) needs no arm and no absent case
|
|
516
|
+
// downstream.
|
|
517
|
+
// Keyed by the SAME domain the menu renders and the click gate admits, so
|
|
518
|
+
// every selectable name has a compiled tree [LAW:one-source-of-truth].
|
|
519
|
+
const roots = new Map<string, CompiledNode>();
|
|
520
|
+
for (const name of presetOptions) {
|
|
521
|
+
// The path travels WITH the tree, so a preset that stages the config's own
|
|
522
|
+
// root diagnoses under `root` — the place its author actually wrote it.
|
|
523
|
+
const { node, path } = presetRoot(config, name);
|
|
524
|
+
roots.set(name, compileNode(node, path));
|
|
525
|
+
}
|
|
526
|
+
|
|
466
527
|
return {
|
|
467
528
|
segments: compiled,
|
|
468
|
-
|
|
529
|
+
roots,
|
|
530
|
+
activeSegment,
|
|
469
531
|
menuRuntime,
|
|
470
532
|
loadWarnings,
|
|
471
533
|
};
|
|
@@ -545,24 +607,44 @@ export interface RenderObservers {
|
|
|
545
607
|
readonly onSegmentError?: (segName: string, message: string) => void;
|
|
546
608
|
}
|
|
547
609
|
|
|
610
|
+
// [LAW:locality-or-seam] The per-render RESOLUTION the caller performs and hands
|
|
611
|
+
// down — the values that are neither config (compiled once) nor payload (input
|
|
612
|
+
// data), but the session's live choices resolved against the config: which
|
|
613
|
+
// theme-adaptation, which preset. Bundled as ONE named bag for exactly the
|
|
614
|
+
// reason RenderObservers is: `look` arrived as a positional tail, `preset` would
|
|
615
|
+
// have been a second one, and the next resolution a third — each a signature
|
|
616
|
+
// every caller re-counts. A new per-render choice is now one field here.
|
|
617
|
+
//
|
|
618
|
+
// Both fields default to their domain's own identity element, so an omitting
|
|
619
|
+
// caller (a compile-only test, the demo) renders the unadapted config — a true
|
|
620
|
+
// default, not a fallback [LAW:no-silent-failure].
|
|
621
|
+
export interface RenderSelection {
|
|
622
|
+
// The resolved look, as a ThemeKey: effectiveLookName over SessionState/
|
|
623
|
+
// globals, then lookKeyByName — resolved by the caller exactly how basePalette
|
|
624
|
+
// is. IDENTITY is the "none" look. Composed with each segment's hue shift into
|
|
625
|
+
// ONE transposition.
|
|
626
|
+
readonly look?: ThemeKey;
|
|
627
|
+
// The resolved preset NAME: effectivePresetName over SessionState/globals,
|
|
628
|
+
// collapsed to the floor if stale. Selects which of `compiled.roots` this
|
|
629
|
+
// render walks. The name (not the fragment) crosses this seam because the
|
|
630
|
+
// fragment's two halves land in two different places — the root here, the
|
|
631
|
+
// globals in `opts`/the payload — and one name keeps them from disagreeing.
|
|
632
|
+
readonly preset?: string;
|
|
633
|
+
}
|
|
634
|
+
|
|
548
635
|
export function renderDsl(
|
|
549
636
|
config: ValidatedConfig,
|
|
550
637
|
compiled: CompiledConfig,
|
|
551
638
|
store: VariableStore,
|
|
552
639
|
registry: SourceRegistry,
|
|
553
640
|
payload: unknown,
|
|
554
|
-
basePalette:
|
|
641
|
+
basePalette: Palette,
|
|
555
642
|
opts: BuildLineOptions,
|
|
556
643
|
observers?: RenderObservers,
|
|
557
|
-
|
|
558
|
-
// theme-adaptation), resolved per render by the caller — effectiveLookName
|
|
559
|
-
// over SessionState/globals, then lookKeyByName — exactly how basePalette
|
|
560
|
-
// resolves. IDENTITY is the domain's own no-adaptation element (the "none"
|
|
561
|
-
// look), so an omitting caller renders unadapted — a true default, not a
|
|
562
|
-
// fallback. Composed with each segment's hue shift into ONE transposition.
|
|
563
|
-
look: ThemeKey = IDENTITY,
|
|
644
|
+
selection?: RenderSelection,
|
|
564
645
|
): string {
|
|
565
646
|
const { perSegmentSink, onSegmentError } = observers ?? {};
|
|
647
|
+
const { look = IDENTITY, preset = PRESET_FLOOR } = selection ?? {};
|
|
566
648
|
// [LAW:one-source-of-truth] Inject the usable width as `term.cols` from the
|
|
567
649
|
// SAME opts.width the strip wraps to (below), so a width-paginated widget reads
|
|
568
650
|
// the exact wrap width — never a cached or independently-measured copy. This is
|
|
@@ -626,19 +708,34 @@ export function renderDsl(
|
|
|
626
708
|
: undefined;
|
|
627
709
|
};
|
|
628
710
|
|
|
629
|
-
// [LAW:single-enforcer] The
|
|
630
|
-
//
|
|
631
|
-
//
|
|
632
|
-
//
|
|
633
|
-
//
|
|
634
|
-
//
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
711
|
+
// [LAW:single-enforcer] The segment seam, owned here as a symmetric pair.
|
|
712
|
+
// `enterSegment` establishes everything a segment's templates may ask about
|
|
713
|
+
// themselves — the name `{{ menu }}` derives its identity from, the palette
|
|
714
|
+
// `{{ color }}` resolves against, the background `{{ bgOf }}` returns — and
|
|
715
|
+
// returns the resolved base Style. `exitSegment` collects the menu bodies the
|
|
716
|
+
// fragments carried as metadata and tears the record back down.
|
|
717
|
+
//
|
|
718
|
+
// [LAW:no-ambient-temporal-coupling] The record is set and cleared around each
|
|
719
|
+
// segment's evaluation by the walk ONLY, so "which segment am I in" is owned
|
|
720
|
+
// state with one writer, never ambient context a reader has to hope is
|
|
721
|
+
// current. Enter/exit are a pair by construction: every path that publishes
|
|
722
|
+
// goes through the first, every path that finishes goes through the second.
|
|
723
|
+
const enterSegment = (
|
|
724
|
+
segName: string,
|
|
725
|
+
palette: Palette,
|
|
726
|
+
bgTemplate: Template<RichText> | undefined,
|
|
727
|
+
fgTemplate: Template<RichText> | undefined,
|
|
728
|
+
): Style =>
|
|
729
|
+
resolveSegmentColors(
|
|
730
|
+
compiled.activeSegment,
|
|
731
|
+
segName,
|
|
732
|
+
palette,
|
|
733
|
+
bgTemplate,
|
|
734
|
+
fgTemplate,
|
|
735
|
+
scope,
|
|
736
|
+
);
|
|
737
|
+
const exitSegment = (fragments: readonly RichText[]): readonly RichText[] => {
|
|
738
|
+
compiled.activeSegment.current = null;
|
|
642
739
|
return collectMenuDrops(fragments);
|
|
643
740
|
};
|
|
644
741
|
|
|
@@ -662,8 +759,8 @@ export function renderDsl(
|
|
|
662
759
|
nextHueShift,
|
|
663
760
|
perSegmentSink,
|
|
664
761
|
onSegmentError,
|
|
665
|
-
|
|
666
|
-
|
|
762
|
+
enterSegment,
|
|
763
|
+
exitSegment,
|
|
667
764
|
focusTint,
|
|
668
765
|
lookupSegment,
|
|
669
766
|
renderChild: renderNode,
|
|
@@ -676,7 +773,22 @@ export function renderDsl(
|
|
|
676
773
|
// emit a "\n"-bearing string (FlexStrip width-overflow wrap); joining the per-
|
|
677
774
|
// line results with "\n" splices those in place — byte-identical to serializing
|
|
678
775
|
// each leaf row independently, since the cells and their order are unchanged.
|
|
679
|
-
|
|
776
|
+
// [LAW:no-defensive-null-guards] The active preset's compiled tree. By the
|
|
777
|
+
// time a name reaches here it must be a member: effectivePresetName collapses
|
|
778
|
+
// unknown names to the floor, and every merged config declares the floor — so
|
|
779
|
+
// the throw is the loud failure for a broken invariant (a hand-built config
|
|
780
|
+
// missing the bundled presets block), never a silent fall back to some other
|
|
781
|
+
// arrangement than the one the bar's own label claims is active.
|
|
782
|
+
const root = compiled.roots.get(preset);
|
|
783
|
+
if (root === undefined) {
|
|
784
|
+
throw new Error(
|
|
785
|
+
`Preset "${preset}" has no compiled layout — registerDslConfig compiles ` +
|
|
786
|
+
`one per declared preset and effectivePresetName collapses unknown ` +
|
|
787
|
+
`names to "${PRESET_FLOOR}"; a miss here is merge/policy drift ` +
|
|
788
|
+
`(have: ${[...compiled.roots.keys()].join(", ")})`,
|
|
789
|
+
);
|
|
790
|
+
}
|
|
791
|
+
return renderNode(root, true)
|
|
680
792
|
.map((line) => renderStripCells(line, opts))
|
|
681
793
|
.join("\n");
|
|
682
794
|
}
|
package/src/help-text.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { DISCLOSURE_GLYPH_CLOSED } from "./config/disclosure";
|
|
2
|
+
|
|
3
|
+
// [LAW:effects-at-boundaries] Pure data, no I/O — index.ts owns the console.log
|
|
4
|
+
// effect. Kept as its own module so the text is importable (and testable) without
|
|
5
|
+
// pulling in index.ts's top-level `main()` call.
|
|
6
|
+
// [LAW:one-source-of-truth] The disclosure glyph comes from config/disclosure.ts
|
|
7
|
+
// (the same constant the theme/look picker itself renders with), so this text
|
|
8
|
+
// can't drift from what a user actually sees on the bar.
|
|
9
|
+
export const HELP_TEXT = `
|
|
10
|
+
cc-candybar - Beautiful powerline statusline for Claude Code
|
|
11
|
+
|
|
12
|
+
Usage: cc-candybar [options]
|
|
13
|
+
|
|
14
|
+
Standalone Commands:
|
|
15
|
+
-h, --help Show this help
|
|
16
|
+
|
|
17
|
+
Debugging:
|
|
18
|
+
CC_CANDYBAR_DEBUG=1 Enable debug logging for troubleshooting
|
|
19
|
+
|
|
20
|
+
Configuration:
|
|
21
|
+
Layout and segment options are defined in .cc-candybar.json5 (place in your
|
|
22
|
+
project dir, cwd, or ~/.config/cc-candybar/config.json5). Use CC_CANDYBAR_CONFIG
|
|
23
|
+
to point at a specific file. See the default config for all available options:
|
|
24
|
+
node dist/index.mjs debug --project-dir . --cwd .
|
|
25
|
+
|
|
26
|
+
The bundled default bar ships a settings drawer — no config needed. Click
|
|
27
|
+
⚙ settings ${DISCLOSURE_GLYPH_CLOSED} on the bar to reveal a clickable theme/look picker and
|
|
28
|
+
other display options.
|
|
29
|
+
|
|
30
|
+
Subcommands:
|
|
31
|
+
install One-shot setup: stages the runtime (native render
|
|
32
|
+
binary + dist bundle) at a stable path, creates the
|
|
33
|
+
URL handler app + cc-candybar:// scheme (macOS), and
|
|
34
|
+
writes the staged entry as the statusLine command in
|
|
35
|
+
~/.claude/settings.json. Re-run to update.
|
|
36
|
+
install-url-handler Just stage the runtime and create + register the URL
|
|
37
|
+
handler app (macOS only).
|
|
38
|
+
url-handle URL Internal — invoked by the URL handler app on
|
|
39
|
+
cmd-click. Parses cc-candybar://<verb>/<value> and
|
|
40
|
+
dispatches (currently: copy to clipboard).
|
|
41
|
+
daemon-stats [--json] Query the running daemon for runtime stats:
|
|
42
|
+
uptime, RSS, cache hit rates, watcher count,
|
|
43
|
+
request totals. Does not spawn a daemon.
|
|
44
|
+
|
|
45
|
+
Config tooling:
|
|
46
|
+
check [config-file] Validate a config on the full render pipeline (parse
|
|
47
|
+
→ merge → validate → register → render) with no
|
|
48
|
+
daemon. With no path, checks the same file the daemon
|
|
49
|
+
would load from here. Exit 0 clean (warnings on
|
|
50
|
+
stderr), 1 invalid, 2 unreadable. "lint" is an alias.
|
|
51
|
+
schema Print the JSON Schema for the config file shape
|
|
52
|
+
(.cc-candybar.json5). Point an editor's $schema at it
|
|
53
|
+
for autocomplete + structural validation.
|
|
54
|
+
vars [--json] Declared variables: source kind, value, last error.
|
|
55
|
+
segments [--json] Segment templates and their last rendered output.
|
|
56
|
+
config [--json] The effective merged config. (All three query the
|
|
57
|
+
running daemon; none spawn one.)
|
|
58
|
+
|
|
59
|
+
`;
|
package/src/index.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { runSchema } from "./config/cli";
|
|
|
15
15
|
import { runCheck } from "./check";
|
|
16
16
|
import { obtainDaemonKick } from "./daemon/acquire";
|
|
17
17
|
import { planOutcome } from "./render/outcome-plan";
|
|
18
|
+
import { HELP_TEXT } from "./help-text";
|
|
18
19
|
|
|
19
20
|
// Read terminal width from the live shell context (no subprocess). Returns
|
|
20
21
|
// undefined when nothing reliable is available; the daemon falls back to its
|
|
@@ -36,53 +37,7 @@ function detectTermCols(): number | undefined {
|
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
function showHelpText(): void {
|
|
39
|
-
console.log(
|
|
40
|
-
cc-candybar - Beautiful powerline statusline for Claude Code
|
|
41
|
-
|
|
42
|
-
Usage: cc-candybar [options]
|
|
43
|
-
|
|
44
|
-
Standalone Commands:
|
|
45
|
-
-h, --help Show this help
|
|
46
|
-
|
|
47
|
-
Debugging:
|
|
48
|
-
CC_CANDYBAR_DEBUG=1 Enable debug logging for troubleshooting
|
|
49
|
-
|
|
50
|
-
Configuration:
|
|
51
|
-
Layout and segment options are defined in .cc-candybar.json5 (place in your
|
|
52
|
-
project dir, cwd, or ~/.config/cc-candybar/config.json5). Use CC_CANDYBAR_CONFIG
|
|
53
|
-
to point at a specific file. See the default config for all available options:
|
|
54
|
-
node dist/index.mjs debug --project-dir . --cwd .
|
|
55
|
-
|
|
56
|
-
Subcommands:
|
|
57
|
-
install One-shot setup: stages the runtime (native render
|
|
58
|
-
binary + dist bundle) at a stable path, creates the
|
|
59
|
-
URL handler app + cc-candybar:// scheme (macOS), and
|
|
60
|
-
writes the staged entry as the statusLine command in
|
|
61
|
-
~/.claude/settings.json. Re-run to update.
|
|
62
|
-
install-url-handler Just stage the runtime and create + register the URL
|
|
63
|
-
handler app (macOS only).
|
|
64
|
-
url-handle URL Internal — invoked by the URL handler app on
|
|
65
|
-
cmd-click. Parses cc-candybar://<verb>/<value> and
|
|
66
|
-
dispatches (currently: copy to clipboard).
|
|
67
|
-
daemon-stats [--json] Query the running daemon for runtime stats:
|
|
68
|
-
uptime, RSS, cache hit rates, watcher count,
|
|
69
|
-
request totals. Does not spawn a daemon.
|
|
70
|
-
|
|
71
|
-
Config tooling:
|
|
72
|
-
check [config-file] Validate a config on the full render pipeline (parse
|
|
73
|
-
→ merge → validate → register → render) with no
|
|
74
|
-
daemon. With no path, checks the same file the daemon
|
|
75
|
-
would load from here. Exit 0 clean (warnings on
|
|
76
|
-
stderr), 1 invalid, 2 unreadable. "lint" is an alias.
|
|
77
|
-
schema Print the JSON Schema for the config file shape
|
|
78
|
-
(.cc-candybar.json5). Point an editor's $schema at it
|
|
79
|
-
for autocomplete + structural validation.
|
|
80
|
-
vars [--json] Declared variables: source kind, value, last error.
|
|
81
|
-
segments [--json] Segment templates and their last rendered output.
|
|
82
|
-
config [--json] The effective merged config. (All three query the
|
|
83
|
-
running daemon; none spawn one.)
|
|
84
|
-
|
|
85
|
-
`);
|
|
40
|
+
console.log(HELP_TEXT);
|
|
86
41
|
}
|
|
87
42
|
|
|
88
43
|
async function main(): Promise<void> {
|
package/src/install/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { tryClickViaDaemon } from "../daemon/client";
|
|
|
7
7
|
import type { PermanentOutcome } from "../daemon/client-transport";
|
|
8
8
|
import { obtainDaemonKick } from "../daemon/acquire";
|
|
9
9
|
import { URL_SCHEME, VERB_COPY } from "../click/wire";
|
|
10
|
+
import { DISCLOSURE_GLYPH_CLOSED } from "../config/disclosure";
|
|
10
11
|
|
|
11
12
|
// [LAW:one-source-of-truth] Replaced at build time by tsdown's `define` option
|
|
12
13
|
// from package.json — the single version stamp install output reports.
|
|
@@ -440,6 +441,21 @@ function formatPermanent(outcome: PermanentOutcome): string {
|
|
|
440
441
|
}
|
|
441
442
|
}
|
|
442
443
|
|
|
444
|
+
// [LAW:effects-at-boundaries] Pure string builder — runInstall performs the
|
|
445
|
+
// actual write. Kept separate so the message content is testable without
|
|
446
|
+
// driving the full (fs + Launch Services) install side effects.
|
|
447
|
+
// [LAW:one-source-of-truth] The disclosure glyph comes from config/disclosure.ts,
|
|
448
|
+
// the same constant the theme/look picker itself renders with, so this tip
|
|
449
|
+
// can't drift from what the bundled default bar actually shows.
|
|
450
|
+
function installSuccessMessage(): string {
|
|
451
|
+
return (
|
|
452
|
+
`✓ install complete.\n` +
|
|
453
|
+
` Restart Claude Code to pick up the new statusline.\n` +
|
|
454
|
+
` Tip: the default bar has a settings drawer — click ⚙ settings\n` +
|
|
455
|
+
` ${DISCLOSURE_GLYPH_CLOSED} to reveal a clickable theme/look picker and other display options.\n`
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
|
|
443
459
|
export function runInstall(rendererArgs: string[]): void {
|
|
444
460
|
const force = rendererArgs.includes("--force");
|
|
445
461
|
const filteredArgs = rendererArgs.filter((a) => a !== "--force");
|
|
@@ -459,10 +475,7 @@ export function runInstall(rendererArgs: string[]): void {
|
|
|
459
475
|
|
|
460
476
|
updateClaudeSettings(staged.binPath, argsToInstall, force);
|
|
461
477
|
|
|
462
|
-
process.stdout.write(
|
|
463
|
-
process.stdout.write(
|
|
464
|
-
` Restart Claude Code to pick up the new statusline.\n`,
|
|
465
|
-
);
|
|
478
|
+
process.stdout.write(installSuccessMessage());
|
|
466
479
|
}
|
|
467
480
|
|
|
468
481
|
function updateClaudeSettings(
|
|
@@ -529,4 +542,5 @@ export const __test__ = {
|
|
|
529
542
|
resolveRenderEntry,
|
|
530
543
|
stageFile,
|
|
531
544
|
stagedEntryKind,
|
|
545
|
+
installSuccessMessage,
|
|
532
546
|
};
|