@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,118 +0,0 @@
1
- import process from "node:process";
2
- import tty from "node:tty";
3
-
4
- export function getColorSupport(): "none" | "ansi" | "ansi256" | "truecolor" {
5
- const { env } = process;
6
-
7
- let colorEnabled = true;
8
-
9
- if (env.NO_COLOR && env.NO_COLOR !== "") {
10
- colorEnabled = false;
11
- }
12
-
13
- const forceColor = env.FORCE_COLOR;
14
- if (forceColor && forceColor !== "") {
15
- if (forceColor === "false" || forceColor === "0") {
16
- return "none";
17
- }
18
- if (forceColor === "true" || forceColor === "1") {
19
- return "ansi";
20
- }
21
- if (forceColor === "2") {
22
- return "ansi256";
23
- }
24
- if (forceColor === "3") {
25
- return "truecolor";
26
- }
27
- return "ansi";
28
- }
29
-
30
- if (!colorEnabled) {
31
- return "none";
32
- }
33
-
34
- if (env.TERM === "dumb") {
35
- return "none";
36
- }
37
-
38
- if (env.CI) {
39
- if (
40
- ["GITHUB_ACTIONS", "GITEA_ACTIONS", "CIRCLECI"].some((key) => key in env)
41
- ) {
42
- return "truecolor";
43
- }
44
- return "ansi";
45
- }
46
-
47
- if (env.COLORTERM === "truecolor") {
48
- return "truecolor";
49
- }
50
-
51
- const truecolorTerminals = [
52
- "xterm-kitty",
53
- "xterm-ghostty",
54
- "wezterm",
55
- "alacritty",
56
- "foot",
57
- "contour",
58
- ];
59
-
60
- if (truecolorTerminals.includes(env.TERM || "")) {
61
- return "truecolor";
62
- }
63
-
64
- if (env.TERM_PROGRAM) {
65
- switch (env.TERM_PROGRAM) {
66
- case "iTerm.app":
67
- return "truecolor";
68
- case "Apple_Terminal":
69
- return "ansi256";
70
- case "vscode":
71
- return "truecolor";
72
- case "Tabby":
73
- return "truecolor";
74
- }
75
- }
76
-
77
- if (/-256(color)?$/i.test(env.TERM || "")) {
78
- return "ansi256";
79
- }
80
-
81
- if (/-truecolor$/i.test(env.TERM || "")) {
82
- return "truecolor";
83
- }
84
-
85
- if (
86
- /^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(
87
- env.TERM || "",
88
- )
89
- ) {
90
- return "ansi";
91
- }
92
-
93
- if (env.COLORTERM) {
94
- return "ansi";
95
- }
96
-
97
- if (tty?.WriteStream?.prototype?.hasColors) {
98
- try {
99
- const colors = tty.WriteStream.prototype.hasColors();
100
- if (!colors) {
101
- return "none";
102
- }
103
-
104
- const has256Colors = tty.WriteStream.prototype.hasColors(256);
105
- const has16mColors = tty.WriteStream.prototype.hasColors(16777216);
106
-
107
- if (has16mColors) {
108
- return "truecolor";
109
- } else if (has256Colors) {
110
- return "ansi256";
111
- } else {
112
- return "ansi";
113
- }
114
- } catch {}
115
- }
116
-
117
- return "ansi";
118
- }
@@ -1,99 +0,0 @@
1
- const CLAUDE_MODEL_PATTERN =
2
- /^(?:(?:global|apac|au|eu|us|us-east-\d|us-west-\d|eu-west-\d|eu-central-\d)\.)?(?:anthropic\.|azure_ai\/|bedrock\/|vertex_ai\/)?claude-(?:(?<family>opus|sonnet|haiku)-(?<newMajor>\d+)(?:-(?<newMinor>\d))?|(?<oldMajor>\d+)(?:-(?<oldMinor>\d))?-(?<oldFamily>opus|sonnet|haiku))(?:[-@]\d{8})?(?:-v\d+:\d+)?(?:-latest)?$/i;
3
-
4
- const FRIENDLY_MODEL_PATTERN =
5
- /^(?<family>opus|sonnet|haiku)\s+(?<major>\d+)(?:\.(?<minor>\d))?$/i;
6
-
7
- export function formatModelName(rawName: string): string {
8
- if (!rawName) {
9
- return "Claude";
10
- }
11
-
12
- // [LAW:one-source-of-truth] strip variant decorations (e.g. " (1M context)",
13
- // "[1m]") so all callers see canonical "Family X.Y" output regardless of
14
- // whether the input came from model.id or model.display_name.
15
- const stripped = rawName
16
- .trim()
17
- .replace(/\s*\([^)]*\)\s*$/, "")
18
- .replace(/\s*\[[^\]]*\]\s*$/, "")
19
- .trim();
20
-
21
- const match = stripped.match(CLAUDE_MODEL_PATTERN);
22
- if (match?.groups) {
23
- const { family, newMajor, newMinor, oldMajor, oldMinor, oldFamily } =
24
- match.groups;
25
-
26
- const modelFamily = family || oldFamily;
27
- const major = newMajor || oldMajor;
28
- const minor = newMinor || oldMinor;
29
-
30
- if (modelFamily && major) {
31
- const capitalizedFamily =
32
- modelFamily.charAt(0).toUpperCase() +
33
- modelFamily.slice(1).toLowerCase();
34
- const version = minor ? `${major}.${minor}` : major;
35
- return `${capitalizedFamily} ${version}`;
36
- }
37
- }
38
-
39
- const friendly = stripped.match(FRIENDLY_MODEL_PATTERN);
40
- if (friendly?.groups) {
41
- const family = friendly.groups.family!;
42
- const major = friendly.groups.major!;
43
- const minor = friendly.groups.minor;
44
- const capitalizedFamily =
45
- family.charAt(0).toUpperCase() + family.slice(1).toLowerCase();
46
- const version = minor ? `${major}.${minor}` : major;
47
- return `${capitalizedFamily} ${version}`;
48
- }
49
-
50
- return stripped || rawName;
51
- }
52
-
53
- export function shortenModelName(formatted: string): string {
54
- // [LAW:one-type-per-behavior] same parser, different rendering — operates on
55
- // the canonical output of formatModelName so callers don't reparse raw IDs.
56
- const match = formatted.match(FRIENDLY_MODEL_PATTERN);
57
- if (!match?.groups) return formatted;
58
- const family = match.groups.family!;
59
- const major = match.groups.major!;
60
- const minor = match.groups.minor;
61
- const initial = family.charAt(0).toUpperCase();
62
- const version = minor ? `${major}.${minor}` : major;
63
- return `${initial}${version}`;
64
- }
65
-
66
- // [LAW:decomposition] Fish-shell `prompt_pwd` abbreviation: collapse every path
67
- // segment EXCEPT the leaf to its leading character, keeping any leading dots so
68
- // `.config` → `.c`. A pure string→string transform that knows nothing about
69
- // home-collapse or project-relative logic — those live in the directory
70
- // template and this abbreviates whatever collapsed display path they produce.
71
- // `~/code/cc-candybar` → `~/c/cc-candybar`; a leading "/" is preserved because
72
- // the empty pre-slash segment abbreviates to empty (`/usr/local/bin` → `/u/l/bin`).
73
- export function abbreviatePath(path: string): string {
74
- const segments = path.split("/");
75
- const lastIndex = segments.length - 1;
76
- return segments
77
- .map((seg, i) => {
78
- if (i === lastIndex) return seg;
79
- // Leading dot-run plus the first following char (fish keeps dotfiles
80
- // legible: `.config` → `.c`, `..` → `..`). No following char (empty
81
- // segment) means no match — return it unchanged.
82
- const abbreviated = seg.match(/^\.*./);
83
- return abbreviated ? abbreviated[0] : seg;
84
- })
85
- .join("/");
86
- }
87
-
88
- // [LAW:one-source-of-truth] Locale-grouped integer rendering. Callers that
89
- // want "50,000" instead of "50000" go through this rather than calling
90
- // toLocaleString() ad-hoc — the legacy context segment used the latter
91
- // pattern inline, and the DSL formatter (template-engine/funcs.ts)
92
- // delegates here so the two producers agree by construction.
93
- //
94
- // No locale argument: the default-locale behaviour is exactly what the
95
- // legacy renderer did (`n.toLocaleString()`), so byte-parity holds with
96
- // whatever locale the host process picks at startup.
97
- export function formatInteger(n: number): string {
98
- return n.toLocaleString();
99
- }
@@ -1,5 +0,0 @@
1
- export function debug(message: string, ...args: unknown[]): void {
2
- if (process.env.CC_CANDYBAR_DEBUG) {
3
- console.error(`[DEBUG] ${message}`, ...args);
4
- }
5
- }
@@ -1,33 +0,0 @@
1
- // [LAW:types-are-the-program] The provider-outcome vocabulary: a data fetch
2
- // either produced a value, found that the domain genuinely has none (no
3
- // upstream configured, no transcript yet), or failed to get an answer at all
4
- // (timeout, spawn error, unreadable file). Before this type, the third state
5
- // was unrepresentable — failure had to wear a real value's clothes (0, "",
6
- // a basename) and the lie flowed all the way to the rendered bar.
7
- //
8
- // [LAW:dataflow-not-control-flow] Failure is a value that flows to a
9
- // boundary, not a swallowed branch. Producers classify once, where the
10
- // command semantics are known; consumers fold once, at the edge where the
11
- // log effect and the payload mapping live.
12
- export type Outcome<T> =
13
- | { readonly kind: "ok"; readonly value: T }
14
- | { readonly kind: "absent" }
15
- | { readonly kind: "failed"; readonly reason: string };
16
-
17
- export function ok<T>(value: T): Outcome<T> {
18
- return { kind: "ok", value };
19
- }
20
-
21
- export const ABSENT: Outcome<never> = { kind: "absent" };
22
-
23
- export function failed(reason: string): Outcome<never> {
24
- return { kind: "failed", reason };
25
- }
26
-
27
- // [LAW:dataflow-not-control-flow] Total fold for consumers that only need a
28
- // value-or-fallback view (e.g. var-system's typed projection). Both non-ok
29
- // arms collapse to the fallback; consumers that must distinguish absent from
30
- // failed (the logging boundaries) match on `kind` instead.
31
- export function orElse<T>(outcome: Outcome<T> | undefined, fallback: T): T {
32
- return outcome?.kind === "ok" ? outcome.value : fallback;
33
- }
@@ -1,126 +0,0 @@
1
- // [LAW:single-enforcer] All hookData schema validation flows through
2
- // validateHookData. One trust boundary, one check.
3
- //
4
- // [LAW:dataflow-not-control-flow] Every check runs unconditionally. Results
5
- // accumulate into a ValidationReport — callers decide what to do with them.
6
- // No early exits, no control-flow branches that skip checks.
7
-
8
- import type { ClaudeHookData } from "./claude";
9
-
10
- export interface ValidationReport {
11
- // Required fields that were absent or had wrong types.
12
- missingRequired: string[];
13
- typeMismatches: Array<{ path: string; expected: string; got: string }>;
14
- // Top-level keys not in the known schema — Anthropic may have added new fields.
15
- unknownTopLevelFields: string[];
16
- }
17
-
18
- // Top-level keys Anthropic sends (plus hook_event_name, which cc-candybar adds).
19
- // Adding a new Anthropic field here suppresses the "unknown field" log for it.
20
- const KNOWN_TOP_LEVEL = new Set([
21
- "hook_event_name", // cc-candybar internal
22
- "session_id",
23
- "session_name",
24
- "transcript_path",
25
- "cwd",
26
- "model",
27
- "workspace",
28
- "version",
29
- "output_style",
30
- "cost",
31
- "context_window",
32
- "exceeds_200k_tokens",
33
- "effort",
34
- "thinking",
35
- "rate_limits",
36
- "vim",
37
- "agent",
38
- "worktree",
39
- ]);
40
-
41
- // Required fields: [dot-separated path, expected typeof result]
42
- // "object" means non-null, non-array object. Checked in declaration order.
43
- const REQUIRED_FIELDS: Array<
44
- [string, "string" | "number" | "boolean" | "object"]
45
- > = [
46
- ["session_id", "string"],
47
- ["transcript_path", "string"],
48
- ["cwd", "string"],
49
- ["model", "object"],
50
- ["model.id", "string"],
51
- ["model.display_name", "string"],
52
- ["workspace", "object"],
53
- ["workspace.current_dir", "string"],
54
- ["workspace.project_dir", "string"],
55
- ];
56
-
57
- /**
58
- * Validate raw hookData received over the wire against the known Anthropic schema.
59
- *
60
- * Returns the data typed as ClaudeHookData alongside a ValidationReport.
61
- * Never throws — divergences are reported, not thrown. The daemon decides
62
- * how to surface them (dlog warn/info).
63
- *
64
- * [LAW:no-defensive-null-guards] Validation at the trust boundary is correct.
65
- * Everywhere else in the codebase, hookData fields are used without guards
66
- * because this boundary guarantees their presence.
67
- */
68
- export function validateHookData(raw: unknown): {
69
- data: ClaudeHookData;
70
- report: ValidationReport;
71
- } {
72
- const report: ValidationReport = {
73
- missingRequired: [],
74
- typeMismatches: [],
75
- unknownTopLevelFields: [],
76
- };
77
-
78
- const obj: Record<string, unknown> =
79
- raw !== null && typeof raw === "object" && !Array.isArray(raw)
80
- ? (raw as Record<string, unknown>)
81
- : {};
82
-
83
- for (const [path, expectedType] of REQUIRED_FIELDS) {
84
- const value = resolvePath(obj, path);
85
- if (value === undefined || value === null) {
86
- report.missingRequired.push(path);
87
- } else {
88
- const actualType = Array.isArray(value) ? "array" : typeof value;
89
- const mismatch =
90
- expectedType === "object"
91
- ? actualType !== "object"
92
- : actualType !== expectedType;
93
- if (mismatch) {
94
- report.typeMismatches.push({
95
- path,
96
- expected: expectedType,
97
- got: actualType,
98
- });
99
- }
100
- }
101
- }
102
-
103
- for (const key of Object.keys(obj)) {
104
- if (!KNOWN_TOP_LEVEL.has(key)) {
105
- report.unknownTopLevelFields.push(key);
106
- }
107
- }
108
-
109
- return { data: raw as ClaudeHookData, report };
110
- }
111
-
112
- function resolvePath(obj: Record<string, unknown>, dotPath: string): unknown {
113
- let cur: unknown = obj;
114
- for (const key of dotPath.split(".")) {
115
- if (
116
- cur === null ||
117
- cur === undefined ||
118
- typeof cur !== "object" ||
119
- Array.isArray(cur)
120
- ) {
121
- return undefined;
122
- }
123
- cur = (cur as Record<string, unknown>)[key];
124
- }
125
- return cur;
126
- }
@@ -1,57 +0,0 @@
1
- // [LAW:one-source-of-truth] A keyed in-flight coalescer: while a computation
2
- // for a given key is running, every concurrent caller for that same key shares
3
- // the ONE in-flight promise instead of starting its own. The fs gate
4
- // (transcript-fs.ts) bounds how many fs ops run at once; it does NOT dedupe the
5
- // work — K concurrent renders each still launch their own (now-bounded)
6
- // whole-tree scan. This is the missing piece: K renders trigger ONE scan, not
7
- // K. The gate makes that one scan's cost bounded; this makes there be one.
8
- //
9
- // [LAW:types-are-the-program] Coalescing is expressed by ROUTING through one
10
- // owner of "is this key already computing", not by a guard scattered at each
11
- // callsite. The selection is dataflow — `inflight.get(key) ?? start(...)` —
12
- // both arms yield a `Promise<T>`, so the operation (return the shared promise)
13
- // always runs; only the value varies. There is no `if (alreadyRunning) return`
14
- // branch that skips work.
15
- //
16
- // Scope of sharing is exactly the in-flight WINDOW: the entry is removed when
17
- // the promise settles (success OR failure), so this is a coalescer, never a
18
- // cache. A fresh call after completion starts a new computation — staleness is
19
- // impossible because nothing is retained past settle. Result caching, when
20
- // wanted, is a separate concern owned by the caller (the disk/LRU caches).
21
- //
22
- // This also dissolves the render-timeout orphaning problem
23
- // (brandon-daemon-memory-leak-gn4.3): a render that abandons its await (the
24
- // daemon's 200ms response timeout fires) does not cancel or duplicate the
25
- // shared computation — there is only ever one scan in flight per key, so the
26
- // timed-out render leaves behind the single canonical computation that the next
27
- // render coalesces onto. A timeout therefore adds zero new fs work.
28
- export class SingleFlight {
29
- private readonly inflight = new Map<string, Promise<unknown>>();
30
-
31
- run<T>(key: string, factory: () => Promise<T>): Promise<T> {
32
- return (
33
- (this.inflight.get(key) as Promise<T> | undefined) ??
34
- this.start(key, factory)
35
- );
36
- }
37
-
38
- private start<T>(key: string, factory: () => Promise<T>): Promise<T> {
39
- const promise = factory();
40
- this.inflight.set(key, promise);
41
- // Deregister on settle. The identity check guards the (impossible-by-key
42
- // but cheap-to-prove) case where a newer promise has replaced this one:
43
- // only the promise that registered itself clears itself.
44
- const deregister = (): void => {
45
- if (this.inflight.get(key) === promise) this.inflight.delete(key);
46
- };
47
- promise.then(deregister, deregister);
48
- return promise;
49
- }
50
-
51
- // Number of computations currently in flight — a read-only observability
52
- // surface for tests asserting the coalescing contract (one in-flight entry
53
- // per key). Never read as control flow.
54
- get size(): number {
55
- return this.inflight.size;
56
- }
57
- }
@@ -1,51 +0,0 @@
1
- // [LAW:single-enforcer] Terminal width has exactly one authoritative source:
2
- // the live client's shell context (env / ioctl), captured at the wire boundary
3
- // and threaded through as request data. This module is a pure resolver from
4
- // (caller-supplied hint, ambient env, stderr TTY) to "width with reserve
5
- // applied, or null." Subprocess-based fallbacks belong at the wire boundary,
6
- // not here. stderr (not stdout) is the TTY-side fallback: when this resolver
7
- // runs in a hook context, stdout is the captured statusline pipe while stderr
8
- // stays attached to the parent terminal.
9
- //
10
- // [LAW:dataflow-not-control-flow] The function always runs the same code path.
11
- // Variability lives in the inputs (hint set or not, env set or not, stderr a
12
- // TTY or not), never in whether work runs.
13
-
14
- // @info Reserves columns for Claude Code's statusline left gutter. Claude Code
15
- // prints each statusline row inset by a fixed left margin, so a row rendered at
16
- // the full raw column count would be shifted right past the terminal edge and
17
- // soft-wrap its trailing cells. The reserve is exactly that left margin — NOT a
18
- // right-side overlay budget. The version/autoupdate/"Remote Control" hints render
19
- // on their own right-aligned lines below the bar and never overlap a statusline
20
- // row, so they cost zero usable width here. Verified empirically against a live
21
- // Claude Code statusline (the older value of 45 was a guess for a right-side
22
- // overlay that does not sit on the statusline row); re-measure if Claude Code's
23
- // statusline gutter changes.
24
- const RESERVED_CHARS = 2;
25
-
26
- // [LAW:single-enforcer] The canonical raw-cols → usable-cols transform.
27
- // Every consumer that needs to honor Claude Code's statusline gutter routes
28
- // through here; there is no parallel `cols - N` math anywhere. Exposed so callers
29
- // that already have a raw width (e.g. the daemon's wire-fallback path,
30
- // the demo reading process.stdout.columns) can apply the reserve without
31
- // re-entering the env/stderr resolution chain in getTerminalWidth.
32
- export function applyClaudeCodeReserve(rawCols: number): number {
33
- return Math.max(1, rawCols - RESERVED_CHARS);
34
- }
35
-
36
- export function getTerminalWidth(termColsHint?: number): number | null {
37
- if (termColsHint && termColsHint > 0)
38
- return applyClaudeCodeReserve(termColsHint);
39
-
40
- const envColumns = process.env.COLUMNS;
41
- if (envColumns) {
42
- const parsed = parseInt(envColumns, 10);
43
- if (!isNaN(parsed) && parsed > 0) return applyClaudeCodeReserve(parsed);
44
- }
45
-
46
- if (process.stderr.columns && process.stderr.columns > 0) {
47
- return applyClaudeCodeReserve(process.stderr.columns);
48
- }
49
-
50
- return null;
51
- }
@@ -1,11 +0,0 @@
1
- export const ESC = String.fromCharCode(27);
2
- const ANSI_REGEX = new RegExp(`${ESC}\\[[0-9;]*m`, "g");
3
- export const ANSI_SPLIT = new RegExp(`(${ESC}\\[[0-9;]*m)`);
4
-
5
- export function stripAnsi(str: string): string {
6
- return str.replace(ANSI_REGEX, "");
7
- }
8
-
9
- export function visibleLength(str: string): number {
10
- return stripAnsi(str).length;
11
- }