@promptctl/cc-candybar 1.0.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 (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +145 -0
  3. package/bin/cc-candybar +6 -0
  4. package/dist/index.mjs +185 -0
  5. package/package.json +99 -0
  6. package/plugin/.claude-plugin/plugin.json +11 -0
  7. package/plugin/bin/preview.sh +305 -0
  8. package/plugin/commands/candybar.md +403 -0
  9. package/plugin/templates/config-essential.json +36 -0
  10. package/plugin/templates/config-full.json +55 -0
  11. package/plugin/templates/config-standard.json +39 -0
  12. package/plugin/templates/config-tui-compact.json +48 -0
  13. package/plugin/templates/config-tui-full.json +89 -0
  14. package/plugin/templates/config-tui-standard.json +56 -0
  15. package/plugin/templates/config-tui.json +18 -0
  16. package/plugin/templates/nerd-fonts-sample.txt +5 -0
  17. package/schema/cc-candybar.schema.json +1379 -0
  18. package/src/click/wire.ts +113 -0
  19. package/src/config/action.ts +91 -0
  20. package/src/config/cli.ts +170 -0
  21. package/src/config/default-dsl-config.ts +661 -0
  22. package/src/config/dsl-loader.ts +265 -0
  23. package/src/config/dsl-types.ts +425 -0
  24. package/src/config/loader/actions.ts +530 -0
  25. package/src/config/loader/cache.ts +206 -0
  26. package/src/config/loader/cross-ref.ts +326 -0
  27. package/src/config/loader/cycles.ts +148 -0
  28. package/src/config/loader/diagnostics.ts +99 -0
  29. package/src/config/loader/discovery.ts +182 -0
  30. package/src/config/loader/emit-schema.ts +63 -0
  31. package/src/config/loader/globals.ts +42 -0
  32. package/src/config/loader/helpers.ts +48 -0
  33. package/src/config/loader/layout.ts +688 -0
  34. package/src/config/loader/merge.ts +40 -0
  35. package/src/config/loader/refs.ts +96 -0
  36. package/src/config/loader/segments.ts +120 -0
  37. package/src/config/loader/validate-core.ts +674 -0
  38. package/src/config/loader/variables.ts +260 -0
  39. package/src/daemon/acquire.ts +411 -0
  40. package/src/daemon/cache/git.ts +553 -0
  41. package/src/daemon/cache/render.ts +449 -0
  42. package/src/daemon/cache/session-usage-store.ts +446 -0
  43. package/src/daemon/cache/watchers.ts +245 -0
  44. package/src/daemon/client-debug.ts +120 -0
  45. package/src/daemon/client-stats.ts +129 -0
  46. package/src/daemon/client-transport.ts +273 -0
  47. package/src/daemon/client.ts +75 -0
  48. package/src/daemon/debug-types.ts +91 -0
  49. package/src/daemon/debug.ts +264 -0
  50. package/src/daemon/limits.ts +154 -0
  51. package/src/daemon/log.ts +69 -0
  52. package/src/daemon/parent-watchdog.ts +80 -0
  53. package/src/daemon/paths.ts +127 -0
  54. package/src/daemon/protocol.ts +235 -0
  55. package/src/daemon/render-payload.ts +611 -0
  56. package/src/daemon/server.ts +1103 -0
  57. package/src/daemon/session-state-file.ts +108 -0
  58. package/src/daemon/session-state.ts +237 -0
  59. package/src/daemon/stats.ts +229 -0
  60. package/src/daemon/verbs/index.ts +458 -0
  61. package/src/daemon/verbs/state-validators.ts +708 -0
  62. package/src/demo/dsl.ts +117 -0
  63. package/src/demo/mock-data.ts +67 -0
  64. package/src/demo/statusline.json5 +92 -0
  65. package/src/dsl/node-registry.ts +281 -0
  66. package/src/dsl/render.ts +558 -0
  67. package/src/index.ts +206 -0
  68. package/src/install/index.ts +410 -0
  69. package/src/proc/launch.ts +451 -0
  70. package/src/proc/stats-handle.ts +13 -0
  71. package/src/render/action.ts +458 -0
  72. package/src/render/diagnostic-style.ts +23 -0
  73. package/src/render/diagnostic-text.ts +77 -0
  74. package/src/render/error-glyph.ts +53 -0
  75. package/src/render/outcome-plan.ts +45 -0
  76. package/src/render/picker.ts +231 -0
  77. package/src/render/split-lines.ts +51 -0
  78. package/src/render/strip.ts +103 -0
  79. package/src/segments/cache.ts +131 -0
  80. package/src/segments/context.ts +190 -0
  81. package/src/segments/git.ts +561 -0
  82. package/src/segments/metrics.ts +101 -0
  83. package/src/segments/pricing.ts +452 -0
  84. package/src/segments/session.ts +188 -0
  85. package/src/segments/tmux.ts +74 -0
  86. package/src/template-engine/cells.ts +90 -0
  87. package/src/template-engine/colors.ts +102 -0
  88. package/src/template-engine/engine.ts +108 -0
  89. package/src/template-engine/funcs.ts +216 -0
  90. package/src/template-engine/index.ts +11 -0
  91. package/src/template-engine/layout.ts +112 -0
  92. package/src/template-engine/scope.ts +62 -0
  93. package/src/themes/index.ts +19 -0
  94. package/src/themes/palette-resolvers.ts +86 -0
  95. package/src/themes/policy.ts +79 -0
  96. package/src/themes/session-random.ts +88 -0
  97. package/src/utils/cache.ts +206 -0
  98. package/src/utils/claude.ts +616 -0
  99. package/src/utils/color-support.ts +118 -0
  100. package/src/utils/formatters.ts +77 -0
  101. package/src/utils/logger.ts +5 -0
  102. package/src/utils/outcome.ts +33 -0
  103. package/src/utils/schema-validator.ts +126 -0
  104. package/src/utils/single-flight.ts +57 -0
  105. package/src/utils/terminal-width.ts +43 -0
  106. package/src/utils/terminal.ts +11 -0
  107. package/src/utils/transcript-fs.ts +162 -0
  108. package/src/var-system/index.ts +24 -0
  109. package/src/var-system/sources.ts +1038 -0
  110. package/src/var-system/store.ts +223 -0
  111. package/src/var-system/types.ts +57 -0
@@ -0,0 +1,223 @@
1
+ // [LAW:one-source-of-truth] The daemon's MobX store is THE place where
2
+ // every variable's value lives. Templates, segments, and click handlers
3
+ // all read through this store; there is no parallel cache, no shadow
4
+ // state, no per-renderer copy.
5
+ //
6
+ // Two node kinds — `box` for externally-driven values (input JSON,
7
+ // shell output, fs watchers, TTL timers) and `computed` for derived
8
+ // values (templates, git fields wrapping shell). MobX auto-tracks
9
+ // dependencies when a computed's deriver reads other variables; the
10
+ // invalidation graph is built without us declaring it.
11
+
12
+ import {
13
+ observable,
14
+ computed,
15
+ runInAction as mobxRunInAction,
16
+ type IObservableValue,
17
+ type IComputedValue,
18
+ } from "mobx";
19
+ import { typeOf, type VarType, type VarValue } from "./types";
20
+
21
+ export interface VarNode {
22
+ readonly name: string;
23
+ readonly type: VarType;
24
+ readonly kind: "box" | "computed";
25
+ read(): VarValue;
26
+ // [LAW:types-are-the-program] Age is a property of the node, not of an
27
+ // external bookkeeping layer — duplicating it in a side map would let the
28
+ // two diverge. `number` for box nodes (epoch ms of last set, including the
29
+ // initial-value set at construction); `null` for computed nodes, whose
30
+ // freshness is governed by MobX invalidation, not a single timestamp.
31
+ lastUpdatedMs(): number | null;
32
+ }
33
+
34
+ class BoxNode implements VarNode {
35
+ readonly kind = "box" as const;
36
+ private readonly cell: IObservableValue<VarValue>;
37
+ // [LAW:single-enforcer] One write path (`set`) updates both the value and
38
+ // the timestamp; introspection reads from the same place renderers do.
39
+ private lastSetAt: number;
40
+
41
+ constructor(
42
+ readonly name: string,
43
+ readonly type: VarType,
44
+ initial: VarValue,
45
+ ) {
46
+ assertType(name, type, initial, "initial value");
47
+ this.cell = observable.box(initial, { deep: false });
48
+ this.lastSetAt = Date.now();
49
+ }
50
+
51
+ read(): VarValue {
52
+ return this.cell.get();
53
+ }
54
+
55
+ set(value: VarValue): void {
56
+ assertType(this.name, this.type, value, "set value");
57
+ this.cell.set(value);
58
+ this.lastSetAt = Date.now();
59
+ }
60
+
61
+ lastUpdatedMs(): number {
62
+ return this.lastSetAt;
63
+ }
64
+ }
65
+
66
+ class ComputedNode implements VarNode {
67
+ readonly kind = "computed" as const;
68
+ private readonly cell: IComputedValue<VarValue>;
69
+
70
+ constructor(
71
+ readonly name: string,
72
+ readonly type: VarType,
73
+ deriver: () => VarValue,
74
+ ) {
75
+ // [LAW:one-source-of-truth] keepAlive caches the value across reads
76
+ // so `.get()` re-runs the deriver only when a tracked dep
77
+ // invalidates — without it, MobX treats an unobserved computed as
78
+ // "not cached" and re-runs on every read. The render path is pull-
79
+ // only (no autorun), so keepAlive is the only mode that gives the
80
+ // reactive-cache contract the proposal promises.
81
+ this.cell = computed(
82
+ () => {
83
+ const v = deriver();
84
+ assertType(this.name, this.type, v, "computed result");
85
+ return v;
86
+ },
87
+ { keepAlive: true },
88
+ );
89
+ }
90
+
91
+ read(): VarValue {
92
+ return this.cell.get();
93
+ }
94
+
95
+ lastUpdatedMs(): null {
96
+ // [LAW:no-defensive-null-guards] Computed nodes have no single
97
+ // "updated" moment — the cache is valid until a tracked dep changes.
98
+ // Returning null is structurally distinct from "updated at 0," so a
99
+ // consumer can render "—" for computed and a real age for boxes.
100
+ return null;
101
+ }
102
+ }
103
+
104
+ function assertType(
105
+ name: string,
106
+ declared: VarType,
107
+ value: VarValue,
108
+ context: string,
109
+ ): void {
110
+ const actual = typeOf(value);
111
+ if (actual !== declared) {
112
+ throw new TypeError(
113
+ `Variable "${name}": ${context} type ${actual} does not match declared type ${declared}`,
114
+ );
115
+ }
116
+ }
117
+
118
+ // [LAW:single-enforcer] All declarations and reads go through one
119
+ // VariableStore instance per daemon. Two stores cannot coexist for the
120
+ // same daemon — the dep graph would split, click handlers would mutate
121
+ // one while renders read the other.
122
+
123
+ export class VariableStore {
124
+ private readonly nodes = new Map<string, VarNode>();
125
+
126
+ defineBox(name: string, type: VarType, initial: VarValue): void {
127
+ this.assertNotDefined(name);
128
+ this.nodes.set(name, new BoxNode(name, type, initial));
129
+ }
130
+
131
+ // Computed deriver receives a `read` function that returns the value
132
+ // of any variable in the store. Calling `read(other)` from inside the
133
+ // deriver is what registers the dependency with MobX — the deriver's
134
+ // body is the dep graph.
135
+ defineComputed(
136
+ name: string,
137
+ type: VarType,
138
+ deriver: (read: (other: string) => VarValue) => VarValue,
139
+ ): void {
140
+ this.assertNotDefined(name);
141
+ const reader = (other: string): VarValue => this.read(other);
142
+ this.nodes.set(name, new ComputedNode(name, type, () => deriver(reader)));
143
+ }
144
+
145
+ read(name: string): VarValue {
146
+ return this.requireNode(name).read();
147
+ }
148
+
149
+ setBox(name: string, value: VarValue): void {
150
+ const node = this.requireNode(name);
151
+ if (node.kind !== "box") {
152
+ throw new TypeError(
153
+ `Variable "${name}" is a ${node.kind}, not a box (use defineBox to create a settable variable)`,
154
+ );
155
+ }
156
+ // [LAW:single-enforcer] All mutations go through one action. MobX
157
+ // strict-mode (the default in v6) rejects modifications outside an
158
+ // action once the observable has observers — and our keepAlive
159
+ // computeds always do. Wrapping setBox here means callers do not
160
+ // need to remember; runInAction stays useful for batching multiple
161
+ // sets so dependents invalidate once.
162
+ mobxRunInAction(() => (node as BoxNode).set(value));
163
+ }
164
+
165
+ // Wrap multi-variable updates so dependents only invalidate once per
166
+ // batch. Used by the render path to push a whole input payload before
167
+ // any computed re-evaluates.
168
+ runInAction(fn: () => void): void {
169
+ mobxRunInAction(fn);
170
+ }
171
+
172
+ has(name: string): boolean {
173
+ return this.nodes.has(name);
174
+ }
175
+
176
+ getType(name: string): VarType {
177
+ return this.requireNode(name).type;
178
+ }
179
+
180
+ getKind(name: string): "box" | "computed" {
181
+ return this.requireNode(name).kind;
182
+ }
183
+
184
+ // [LAW:types-are-the-program] Introspection (src/daemon/debug.ts) needs
185
+ // the whole node — type, kind, lastUpdatedMs — in one lookup, but
186
+ // returning the BoxNode directly would leak `.set` structurally even
187
+ // though VarNode does not advertise it. The returned wrapper is a fresh
188
+ // object exposing only the VarNode surface — `.set` is unreachable at
189
+ // any level (no structural escape, no plain-JS reach-through). The
190
+ // mutation path remains gated behind `setBox`, which wraps in
191
+ // runInAction to satisfy MobX strict-mode.
192
+ //
193
+ // [LAW:single-enforcer] One requireNode call per consumer-row — the
194
+ // round-1 dedup fix in introspectVars relies on the caller getting both
195
+ // type/kind and a read() in one go without paying for a second
196
+ // requireNode. The wrapper preserves that.
197
+ getNode(name: string): VarNode {
198
+ const node = this.requireNode(name);
199
+ return {
200
+ name: node.name,
201
+ type: node.type,
202
+ kind: node.kind,
203
+ read: () => node.read(),
204
+ lastUpdatedMs: () => node.lastUpdatedMs(),
205
+ };
206
+ }
207
+
208
+ names(): string[] {
209
+ return [...this.nodes.keys()];
210
+ }
211
+
212
+ private requireNode(name: string): VarNode {
213
+ const node = this.nodes.get(name);
214
+ if (!node) throw new ReferenceError(`Unknown variable "${name}"`);
215
+ return node;
216
+ }
217
+
218
+ private assertNotDefined(name: string): void {
219
+ if (this.nodes.has(name)) {
220
+ throw new Error(`Variable "${name}" is already declared`);
221
+ }
222
+ }
223
+ }
@@ -0,0 +1,57 @@
1
+ // [LAW:one-type-per-behavior] Every variable, every source kind, every
2
+ // template result lands in this same union. Adding a new value type would
3
+ // be a code change that ripples through the cast helpers and the filter
4
+ // pipeline — there is no "extra" variant available at the type layer.
5
+
6
+ export type VarType = "string" | "number" | "boolean";
7
+ export type VarValue = string | number | boolean;
8
+
9
+ export function typeOf(v: VarValue): VarType {
10
+ const t = typeof v;
11
+ if (t === "string" || t === "number" || t === "boolean") return t;
12
+ // [LAW:no-defensive-null-guards] This is a trust-boundary check —
13
+ // values arrive from user-authored templates and external sources, so
14
+ // an unsupported runtime value must fail loudly here, not silently
15
+ // downstream.
16
+ throw new TypeError(
17
+ `Variable values must be string|number|boolean (got ${t})`,
18
+ );
19
+ }
20
+
21
+ // Cast helpers used by the filter pipeline (chunk 2 will consume these).
22
+ // Each cast either returns a typed value or throws with a useful message.
23
+ // Total casts (toString) never throw; partial casts (toNumber, toBool)
24
+ // throw on ambiguous input rather than silently coercing.
25
+
26
+ export function toString(v: VarValue): string {
27
+ return typeof v === "string" ? v : String(v);
28
+ }
29
+
30
+ export function toNumber(v: VarValue): number {
31
+ if (typeof v === "number") return v;
32
+ if (typeof v === "boolean") return v ? 1 : 0;
33
+ const trimmed = v.trim();
34
+ if (trimmed === "") {
35
+ throw new TypeError(`Cannot cast empty string to number`);
36
+ }
37
+ const n = Number(trimmed);
38
+ if (!Number.isFinite(n)) {
39
+ throw new TypeError(`Cannot cast ${JSON.stringify(v)} to number`);
40
+ }
41
+ return n;
42
+ }
43
+
44
+ export function toBool(v: VarValue): boolean {
45
+ if (typeof v === "boolean") return v;
46
+ if (typeof v === "number") {
47
+ if (v === 0 || v === 1) return v === 1;
48
+ throw new TypeError(
49
+ `Cannot cast number ${v} to bool (only 0 and 1 are accepted)`,
50
+ );
51
+ }
52
+ if (v === "true") return true;
53
+ if (v === "false") return false;
54
+ throw new TypeError(
55
+ `Cannot cast ${JSON.stringify(v)} to bool (expected "true" or "false")`,
56
+ );
57
+ }