@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,40 @@
1
+ // [LAW:one-source-of-truth] The single point that consults DEFAULT_DSL_CONFIG to
2
+ // fill missing keys. A user file declares only what differs; the cascade here
3
+ // (shallow-merge globals, by-name merge variables/segments/actions, wholesale
4
+ // root replacement) is the one place "absent means inherit" is decided.
5
+ // This file changes when the merge semantics change.
6
+
7
+ import { type DslConfig, type RawDslConfig } from "../dsl-types.js";
8
+ import { DEFAULT_DSL_CONFIG } from "../default-dsl-config.js";
9
+
10
+ /**
11
+ * Merge a RawDslConfig on top of a default DslConfig. Pure function.
12
+ *
13
+ * globals : shallow merge per field (user wins per-field)
14
+ * variables : merge by name (user wins per-name)
15
+ * segments : merge by name (user wins per-name)
16
+ * root : the canonical layout tree. Authored via the A-grammar (`root`)
17
+ * replaces wholesale; absent → default's tree.
18
+ * [LAW:one-source-of-truth] `layout:` is rejected at parse time
19
+ * with a migration error (removed in 2de.19), so only `root`
20
+ * ever reaches this function.
21
+ */
22
+ export function mergeWithDefault(
23
+ raw: RawDslConfig,
24
+ dflt: DslConfig = DEFAULT_DSL_CONFIG,
25
+ ): DslConfig {
26
+ return {
27
+ globals: { ...dflt.globals, ...(raw.globals ?? {}) },
28
+ variables: { ...dflt.variables, ...(raw.variables ?? {}) },
29
+ segments: { ...dflt.segments, ...(raw.segments ?? {}) },
30
+ root: raw.root !== undefined ? raw.root : dflt.root,
31
+ // [LAW:one-source-of-truth] actions merge by name, same cascade — a user
32
+ // declares only the actions that differ from the bundled default (which
33
+ // ships none).
34
+ actions: { ...dflt.actions, ...(raw.actions ?? {}) },
35
+ // [LAW:one-source-of-truth] helpers merge by name, same cascade — a user
36
+ // overrides one formatter helper by re-declaring its name; the rest inherit
37
+ // from the bundled default.
38
+ helpers: { ...dflt.helpers, ...(raw.helpers ?? {}) },
39
+ };
40
+ }
@@ -0,0 +1,96 @@
1
+ // [LAW:dataflow-not-control-flow] Best-effort extraction of the references a
2
+ // template string makes — dotted variable refs, `action "name"` refs, and
3
+ // `picker "apply" "page"` refs. Pure text walks over `{{ … }}` blocks: no
4
+ // full template parse (that is the engine's compile-time job). This file changes
5
+ // when the surface grammar of those refs changes; the cross-ref/cycle passes
6
+ // consume the sets it returns without re-deriving them.
7
+
8
+ const TEMPLATE_BLOCK_RE = /{{([\s\S]*?)}}/g;
9
+ const STRING_LITERAL_RE = /"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`[^`]*`/g;
10
+ const DOTTED_REF_RE =
11
+ /(?<![A-Za-z0-9_)])\.([A-Za-z_][\w]*(?:\.[A-Za-z_][\w]*)*)/g;
12
+
13
+ // [LAW:dataflow-not-control-flow] Extract every `.<id>(.<id>)*` token inside
14
+ // `{{ ... }}` blocks after stripping string literals. The result is a set of
15
+ // dotted reference candidates; the caller decides which are valid.
16
+ export function extractTemplateRefs(template: string): Set<string> {
17
+ const refs = new Set<string>();
18
+ let m: RegExpExecArray | null;
19
+ TEMPLATE_BLOCK_RE.lastIndex = 0;
20
+ while ((m = TEMPLATE_BLOCK_RE.exec(template)) !== null) {
21
+ const block = m[1]!.replace(STRING_LITERAL_RE, "");
22
+ let r: RegExpExecArray | null;
23
+ DOTTED_REF_RE.lastIndex = 0;
24
+ while ((r = DOTTED_REF_RE.exec(block)) !== null) {
25
+ refs.add(r[1]!);
26
+ }
27
+ }
28
+ return refs;
29
+ }
30
+
31
+ // [LAW:dataflow-not-control-flow] Extract every `action "name"` call from a
32
+ // template, for the load-time existence check. Same best-effort code-span /
33
+ // string-literal walk as extractTemplateRefs: the `action` keyword lives in a
34
+ // CODE span and its NAME is the very next string literal (the display/boundValue
35
+ // literals that follow are preceded by a non-`action` span, so they are never
36
+ // misread as the name).
37
+ const ACTION_ARG_RE = /\baction\s+$/;
38
+ export function extractActionRefs(template: string): Set<string> {
39
+ const refs = new Set<string>();
40
+ TEMPLATE_BLOCK_RE.lastIndex = 0;
41
+ let m: RegExpExecArray | null;
42
+ while ((m = TEMPLATE_BLOCK_RE.exec(template)) !== null) {
43
+ const block = m[1]!;
44
+ let cursor = 0;
45
+ let s: RegExpExecArray | null;
46
+ STRING_LITERAL_RE.lastIndex = 0;
47
+ while ((s = STRING_LITERAL_RE.exec(block)) !== null) {
48
+ if (ACTION_ARG_RE.test(block.slice(cursor, s.index))) {
49
+ refs.add(s[0].slice(1, -1));
50
+ }
51
+ cursor = s.index + s[0].length;
52
+ }
53
+ }
54
+ return refs;
55
+ }
56
+
57
+ // [LAW:dataflow-not-control-flow] Extract the action names a `picker` call
58
+ // references — its FIRST TWO string-literal args are the apply action and the
59
+ // page action (`{{ picker "applyTheme" "themePage" true true }}`). Same code/
60
+ // string-span walk as extractActionRefs; the picker keyword arms the next literal
61
+ // as the apply name and the one after it as the page name (the trailing bool args
62
+ // are not string literals, so they are never captured).
63
+ const PICKER_ARG_RE = /\bpicker\s+$/;
64
+ export function extractPickerRefs(template: string): Set<string> {
65
+ const refs = new Set<string>();
66
+ TEMPLATE_BLOCK_RE.lastIndex = 0;
67
+ let m: RegExpExecArray | null;
68
+ while ((m = TEMPLATE_BLOCK_RE.exec(template)) !== null) {
69
+ const block = m[1]!;
70
+ let cursor = 0;
71
+ let pending = 0; // remaining name args to capture for the current picker call
72
+ let s: RegExpExecArray | null;
73
+ STRING_LITERAL_RE.lastIndex = 0;
74
+ while ((s = STRING_LITERAL_RE.exec(block)) !== null) {
75
+ if (PICKER_ARG_RE.test(block.slice(cursor, s.index))) pending = 2;
76
+ if (pending > 0) {
77
+ refs.add(s[0].slice(1, -1));
78
+ pending--;
79
+ }
80
+ cursor = s.index + s[0].length;
81
+ }
82
+ }
83
+ return refs;
84
+ }
85
+
86
+ // A ref resolves if (a) the full dotted name is a declared variable, OR
87
+ // (b) it's a strict prefix of some declared variable (namespace navigation
88
+ // like .session in `.session.id` when only `session.id` is declared).
89
+ export function refResolves(ref: string, allVars: Set<string>): boolean {
90
+ if (allVars.has(ref)) return true;
91
+ const prefix = `${ref}.`;
92
+ for (const name of allVars) {
93
+ if (name.startsWith(prefix)) return true;
94
+ }
95
+ return false;
96
+ }
@@ -0,0 +1,120 @@
1
+ // [LAW:types-are-the-program] The segment schema: a required `template` plus
2
+ // optional layout/paint/visibility fields and a nested `vars` block (validated by
3
+ // the variable schema). Declared as DATA (SEGMENT_SCHEMA) and interpreted by the
4
+ // generic `record` engine — a plain record with no cross-field invariant, so two
5
+ // value-shaped fields (`width`, `vars`) carry their bespoke parse as field specs.
6
+ // This file changes when a segment field is added or removed.
7
+
8
+ import {
9
+ JUSTIFY_MODES,
10
+ TRUNCATE_MODES,
11
+ type SegmentDecl,
12
+ type VariableDecl,
13
+ } from "../dsl-types.js";
14
+ import { findKeyLine } from "./diagnostics.js";
15
+ import {
16
+ describeType,
17
+ describeValue,
18
+ isPlainObject,
19
+ optionalEnumSpec,
20
+ optionalStringSpec,
21
+ paletteSpec,
22
+ record,
23
+ recordJson,
24
+ requireStringSpec,
25
+ type FieldSpec,
26
+ type FieldSpecMap,
27
+ type JsonNode,
28
+ type RecordSchema,
29
+ type ValidateCtx,
30
+ } from "./validate-core.js";
31
+ import { validateVariables, variablesMapJson } from "./variables.js";
32
+
33
+ export function validateSegments(
34
+ ctx: ValidateCtx,
35
+ raw: unknown,
36
+ ): Record<string, SegmentDecl> {
37
+ if (raw === undefined) return {};
38
+ if (!isPlainObject(raw)) {
39
+ ctx.issues.push({
40
+ path: "segments",
41
+ message: `segments must be an object, got ${describeType(raw)}`,
42
+ line: findKeyLine(ctx.source, ["segments"]),
43
+ });
44
+ return {};
45
+ }
46
+
47
+ const out: Record<string, SegmentDecl> = {};
48
+ for (const [name, decl] of Object.entries(raw)) {
49
+ const parsed = record(ctx, SEGMENT_SCHEMA, `segments.${name}`, decl);
50
+ if (parsed !== null) out[name] = parsed;
51
+ }
52
+ return out;
53
+ }
54
+
55
+ // [LAW:types-are-the-program] `width` is `"auto"` or a positive integer — a union
56
+ // the generic string/enum specs cannot express, so it carries its own parse and
57
+ // bespoke message as DATA. Absent → omitted; present-and-wrong → issue + omitted.
58
+ function widthSpec(): FieldSpec<"auto" | number> {
59
+ return {
60
+ required: false,
61
+ json: { anyOf: [{ const: "auto" }, { type: "integer", minimum: 1 }] },
62
+ parse: (ctx, path, field, raw) => {
63
+ const v = raw[field];
64
+ if (v === undefined) return undefined;
65
+ if (v === "auto") return "auto";
66
+ if (typeof v === "number" && Number.isInteger(v) && v > 0) return v;
67
+ ctx.issues.push({
68
+ path: `${path}.${field}`,
69
+ message: `width must be "auto" or a positive integer, got ${describeValue(v)}`,
70
+ line: findKeyLine(ctx.source, [...path.split("."), field]),
71
+ });
72
+ return undefined;
73
+ },
74
+ };
75
+ }
76
+
77
+ // [LAW:decomposition] The nested `vars` block defers to the variable schema —
78
+ // recursion by reuse of the already-migrated per-name taggedUnion, not a new
79
+ // combinator. Absent → omitted; present → the parsed map (possibly empty, with
80
+ // its own issues already reported), mirroring the old `if (raw.vars !== undefined)`.
81
+ function varsSpec(): FieldSpec<Readonly<Record<string, VariableDecl>>> {
82
+ return {
83
+ required: false,
84
+ // [LAW:one-source-of-truth] The nested `vars` schema is the SAME name →
85
+ // VariableDecl map the top-level `variables` block emits — one source.
86
+ json: variablesMapJson(),
87
+ parse: (ctx, path, field, raw) => {
88
+ const v = raw[field];
89
+ if (v === undefined) return undefined;
90
+ return validateVariables(ctx, `${path}.${field}`, v);
91
+ },
92
+ };
93
+ }
94
+
95
+ // [LAW:dataflow-not-control-flow] The segment's shape as DATA over its fields, in
96
+ // declaration order. The engine runs every spec, rejects unknown keys, and fails
97
+ // the segment when `template` is absent or invalid — the isPlainObject guard,
98
+ // unknown-key loop, result-threading, and optional-omission the old body hand-rolled.
99
+ const SEGMENT_FIELDS: FieldSpecMap<SegmentDecl> = {
100
+ template: requireStringSpec(),
101
+ width: widthSpec(),
102
+ justify: optionalEnumSpec(JUSTIFY_MODES),
103
+ truncate: optionalEnumSpec(TRUNCATE_MODES),
104
+ bg: optionalStringSpec(),
105
+ fg: optionalStringSpec(),
106
+ when: optionalStringSpec(),
107
+ palette: paletteSpec(),
108
+ vars: varsSpec(),
109
+ };
110
+
111
+ const SEGMENT_SCHEMA: RecordSchema<SegmentDecl> = {
112
+ noun: "segment key",
113
+ fields: SEGMENT_FIELDS,
114
+ };
115
+
116
+ // [LAW:one-source-of-truth] The `segments` block is a name → SegmentDecl map,
117
+ // derived from the SAME SEGMENT_SCHEMA the validator interprets.
118
+ export function segmentsJson(): JsonNode {
119
+ return { type: "object", additionalProperties: recordJson(SEGMENT_SCHEMA) };
120
+ }