@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.
- package/LICENSE +21 -0
- package/README.md +145 -0
- package/bin/cc-candybar +6 -0
- package/dist/index.mjs +185 -0
- package/package.json +99 -0
- package/plugin/.claude-plugin/plugin.json +11 -0
- package/plugin/bin/preview.sh +305 -0
- package/plugin/commands/candybar.md +403 -0
- package/plugin/templates/config-essential.json +36 -0
- package/plugin/templates/config-full.json +55 -0
- package/plugin/templates/config-standard.json +39 -0
- package/plugin/templates/config-tui-compact.json +48 -0
- package/plugin/templates/config-tui-full.json +89 -0
- package/plugin/templates/config-tui-standard.json +56 -0
- package/plugin/templates/config-tui.json +18 -0
- package/plugin/templates/nerd-fonts-sample.txt +5 -0
- package/schema/cc-candybar.schema.json +1379 -0
- package/src/click/wire.ts +113 -0
- package/src/config/action.ts +91 -0
- package/src/config/cli.ts +170 -0
- package/src/config/default-dsl-config.ts +661 -0
- package/src/config/dsl-loader.ts +265 -0
- package/src/config/dsl-types.ts +425 -0
- package/src/config/loader/actions.ts +530 -0
- package/src/config/loader/cache.ts +206 -0
- package/src/config/loader/cross-ref.ts +326 -0
- package/src/config/loader/cycles.ts +148 -0
- package/src/config/loader/diagnostics.ts +99 -0
- package/src/config/loader/discovery.ts +182 -0
- package/src/config/loader/emit-schema.ts +63 -0
- package/src/config/loader/globals.ts +42 -0
- package/src/config/loader/helpers.ts +48 -0
- package/src/config/loader/layout.ts +688 -0
- package/src/config/loader/merge.ts +40 -0
- package/src/config/loader/refs.ts +96 -0
- package/src/config/loader/segments.ts +120 -0
- package/src/config/loader/validate-core.ts +674 -0
- package/src/config/loader/variables.ts +260 -0
- package/src/daemon/acquire.ts +411 -0
- package/src/daemon/cache/git.ts +553 -0
- package/src/daemon/cache/render.ts +449 -0
- package/src/daemon/cache/session-usage-store.ts +446 -0
- package/src/daemon/cache/watchers.ts +245 -0
- package/src/daemon/client-debug.ts +120 -0
- package/src/daemon/client-stats.ts +129 -0
- package/src/daemon/client-transport.ts +273 -0
- package/src/daemon/client.ts +75 -0
- package/src/daemon/debug-types.ts +91 -0
- package/src/daemon/debug.ts +264 -0
- package/src/daemon/limits.ts +154 -0
- package/src/daemon/log.ts +69 -0
- package/src/daemon/parent-watchdog.ts +80 -0
- package/src/daemon/paths.ts +127 -0
- package/src/daemon/protocol.ts +235 -0
- package/src/daemon/render-payload.ts +611 -0
- package/src/daemon/server.ts +1103 -0
- package/src/daemon/session-state-file.ts +108 -0
- package/src/daemon/session-state.ts +237 -0
- package/src/daemon/stats.ts +229 -0
- package/src/daemon/verbs/index.ts +458 -0
- package/src/daemon/verbs/state-validators.ts +708 -0
- package/src/demo/dsl.ts +117 -0
- package/src/demo/mock-data.ts +67 -0
- package/src/demo/statusline.json5 +92 -0
- package/src/dsl/node-registry.ts +281 -0
- package/src/dsl/render.ts +558 -0
- package/src/index.ts +206 -0
- package/src/install/index.ts +410 -0
- package/src/proc/launch.ts +451 -0
- package/src/proc/stats-handle.ts +13 -0
- package/src/render/action.ts +458 -0
- package/src/render/diagnostic-style.ts +23 -0
- package/src/render/diagnostic-text.ts +77 -0
- package/src/render/error-glyph.ts +53 -0
- package/src/render/outcome-plan.ts +45 -0
- package/src/render/picker.ts +231 -0
- package/src/render/split-lines.ts +51 -0
- package/src/render/strip.ts +103 -0
- package/src/segments/cache.ts +131 -0
- package/src/segments/context.ts +190 -0
- package/src/segments/git.ts +561 -0
- package/src/segments/metrics.ts +101 -0
- package/src/segments/pricing.ts +452 -0
- package/src/segments/session.ts +188 -0
- package/src/segments/tmux.ts +74 -0
- package/src/template-engine/cells.ts +90 -0
- package/src/template-engine/colors.ts +102 -0
- package/src/template-engine/engine.ts +108 -0
- package/src/template-engine/funcs.ts +216 -0
- package/src/template-engine/index.ts +11 -0
- package/src/template-engine/layout.ts +112 -0
- package/src/template-engine/scope.ts +62 -0
- package/src/themes/index.ts +19 -0
- package/src/themes/palette-resolvers.ts +86 -0
- package/src/themes/policy.ts +79 -0
- package/src/themes/session-random.ts +88 -0
- package/src/utils/cache.ts +206 -0
- package/src/utils/claude.ts +616 -0
- package/src/utils/color-support.ts +118 -0
- package/src/utils/formatters.ts +77 -0
- package/src/utils/logger.ts +5 -0
- package/src/utils/outcome.ts +33 -0
- package/src/utils/schema-validator.ts +126 -0
- package/src/utils/single-flight.ts +57 -0
- package/src/utils/terminal-width.ts +43 -0
- package/src/utils/terminal.ts +11 -0
- package/src/utils/transcript-fs.ts +162 -0
- package/src/var-system/index.ts +24 -0
- package/src/var-system/sources.ts +1038 -0
- package/src/var-system/store.ts +223 -0
- 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
|
+
}
|