@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,190 @@
|
|
|
1
|
+
import type { ParsedEntry, ClaudeHookData } from "../utils/claude";
|
|
2
|
+
|
|
3
|
+
import { debug } from "../utils/logger";
|
|
4
|
+
import { parseJsonlFile } from "../utils/claude";
|
|
5
|
+
import { ABSENT, failed, ok, type Outcome } from "../utils/outcome";
|
|
6
|
+
|
|
7
|
+
export interface ContextInfo {
|
|
8
|
+
totalTokens: number;
|
|
9
|
+
// Used / remaining percentages. Sourced from Claude's native
|
|
10
|
+
// context_window.used_percentage / remaining_percentage when present; a
|
|
11
|
+
// plain token-ratio is the only fallback (no auto-compact buffer guess).
|
|
12
|
+
percentage: number;
|
|
13
|
+
contextLeftPercentage: number;
|
|
14
|
+
maxTokens: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface ContextUsageThresholds {
|
|
18
|
+
LOW: number;
|
|
19
|
+
MEDIUM: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// [LAW:one-source-of-truth] The context-window size is NEVER guessed from the
|
|
23
|
+
// model name. Claude Code reports the real size for the active model in
|
|
24
|
+
// `context_window.context_window_size` (1M for the [1m] variants, 200k
|
|
25
|
+
// otherwise) — that field is the single authority. This constant is the
|
|
26
|
+
// last-resort floor for ancient clients that omit `context_window` entirely;
|
|
27
|
+
// it is not a per-model table and must not grow into one.
|
|
28
|
+
const DEFAULT_CONTEXT_WINDOW = 200000;
|
|
29
|
+
|
|
30
|
+
export class ContextProvider {
|
|
31
|
+
private readonly thresholds: ContextUsageThresholds = {
|
|
32
|
+
LOW: 50,
|
|
33
|
+
MEDIUM: 80,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
getContextUsageThresholds(): ContextUsageThresholds {
|
|
37
|
+
return this.thresholds;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Token-ratio percentages — the fallback ONLY. Used when Claude doesn't
|
|
41
|
+
// report used_percentage / remaining_percentage natively (transcript path,
|
|
42
|
+
// or a native window whose percentages are still null pre-first-call). No
|
|
43
|
+
// auto-compact buffer: that was a hardcoded guess at Claude's threshold and
|
|
44
|
+
// a soft second source; the native remaining_percentage is authoritative.
|
|
45
|
+
private ratioPercentages(
|
|
46
|
+
totalTokens: number,
|
|
47
|
+
contextLimit: number,
|
|
48
|
+
): Pick<ContextInfo, "percentage" | "contextLeftPercentage"> {
|
|
49
|
+
const percentage = Math.min(
|
|
50
|
+
100,
|
|
51
|
+
Math.max(0, Math.round((totalTokens / contextLimit) * 100)),
|
|
52
|
+
);
|
|
53
|
+
return { percentage, contextLeftPercentage: Math.max(0, 100 - percentage) };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Calculate context info from native Claude Code context_window data (preferred).
|
|
58
|
+
* Requires Claude Code 2.0.70+ with current_usage field.
|
|
59
|
+
*/
|
|
60
|
+
calculateContextFromHookData(hookData: ClaudeHookData): ContextInfo | null {
|
|
61
|
+
const cw = hookData.context_window;
|
|
62
|
+
if (!cw?.current_usage) {
|
|
63
|
+
debug(
|
|
64
|
+
"No current_usage in hook data, falling back to transcript parsing",
|
|
65
|
+
);
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const currentUsage = cw.current_usage;
|
|
70
|
+
// [LAW:no-defensive-null-guards] context_window_size is a required `number`
|
|
71
|
+
// within context_window; reaching here proves cw is present, so the size
|
|
72
|
+
// is too. No `|| default` — that would mask a malformed payload as 200k.
|
|
73
|
+
const contextLimit = cw.context_window_size;
|
|
74
|
+
const totalTokens =
|
|
75
|
+
(currentUsage.input_tokens || 0) +
|
|
76
|
+
(currentUsage.cache_creation_input_tokens || 0) +
|
|
77
|
+
(currentUsage.cache_read_input_tokens || 0);
|
|
78
|
+
|
|
79
|
+
debug(
|
|
80
|
+
`Native current_usage: input=${currentUsage.input_tokens}, cache_create=${currentUsage.cache_creation_input_tokens}, cache_read=${currentUsage.cache_read_input_tokens}, total=${totalTokens} (limit: ${contextLimit})`,
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// [LAW:one-source-of-truth] Claude's reported used/remaining percentages
|
|
84
|
+
// are authoritative; the token-ratio is only a floor for the window whose
|
|
85
|
+
// percentages are still null (pre-first-call). remaining_percentage is NOT
|
|
86
|
+
// recomputed from a local buffer — it measures real headroom to the limit.
|
|
87
|
+
const ratio = this.ratioPercentages(totalTokens, contextLimit);
|
|
88
|
+
return {
|
|
89
|
+
totalTokens,
|
|
90
|
+
maxTokens: contextLimit,
|
|
91
|
+
percentage:
|
|
92
|
+
cw.used_percentage != null
|
|
93
|
+
? Math.round(cw.used_percentage)
|
|
94
|
+
: ratio.percentage,
|
|
95
|
+
contextLeftPercentage:
|
|
96
|
+
cw.remaining_percentage != null
|
|
97
|
+
? Math.round(cw.remaining_percentage)
|
|
98
|
+
: ratio.contextLeftPercentage,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Calculate context tokens by parsing the transcript file (fallback).
|
|
104
|
+
* Used for older Claude Code versions that don't provide context_window.
|
|
105
|
+
*
|
|
106
|
+
* [LAW:no-silent-failure] An unreadable transcript is `failed` (the payload
|
|
107
|
+
* boundary logs it); a transcript with no usable usage entry is `absent`.
|
|
108
|
+
* The old catch-to-null collapsed both into "no data".
|
|
109
|
+
*/
|
|
110
|
+
async calculateContextTokensFromTranscript(
|
|
111
|
+
transcriptPath: string,
|
|
112
|
+
contextLimit: number,
|
|
113
|
+
): Promise<Outcome<ContextInfo>> {
|
|
114
|
+
try {
|
|
115
|
+
debug(`Calculating context tokens from transcript: ${transcriptPath}`);
|
|
116
|
+
|
|
117
|
+
const parsedEntries = await parseJsonlFile(transcriptPath);
|
|
118
|
+
|
|
119
|
+
if (parsedEntries.length === 0) {
|
|
120
|
+
debug("No entries in transcript");
|
|
121
|
+
return ABSENT;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let mostRecentEntry: ParsedEntry | null = null;
|
|
125
|
+
|
|
126
|
+
for (let i = parsedEntries.length - 1; i >= 0; i--) {
|
|
127
|
+
const entry = parsedEntries[i];
|
|
128
|
+
if (!entry) continue;
|
|
129
|
+
|
|
130
|
+
if (!entry.message?.usage?.input_tokens) continue;
|
|
131
|
+
if (entry.isSidechain === true) continue;
|
|
132
|
+
|
|
133
|
+
mostRecentEntry = entry;
|
|
134
|
+
debug(
|
|
135
|
+
`Context segment: Found most recent entry at ${entry.timestamp.toISOString()}, stopping search`,
|
|
136
|
+
);
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (mostRecentEntry?.message?.usage) {
|
|
141
|
+
const usage = mostRecentEntry.message.usage;
|
|
142
|
+
const totalTokens =
|
|
143
|
+
(usage.input_tokens || 0) +
|
|
144
|
+
(usage.cache_read_input_tokens || 0) +
|
|
145
|
+
(usage.cache_creation_input_tokens || 0);
|
|
146
|
+
|
|
147
|
+
debug(
|
|
148
|
+
`Most recent main chain context: ${totalTokens} tokens (limit: ${contextLimit})`,
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
return ok({
|
|
152
|
+
totalTokens,
|
|
153
|
+
maxTokens: contextLimit,
|
|
154
|
+
...this.ratioPercentages(totalTokens, contextLimit),
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
debug("No main chain entries with usage data found");
|
|
159
|
+
return ABSENT;
|
|
160
|
+
} catch (error) {
|
|
161
|
+
return failed(
|
|
162
|
+
`context transcript: ${error instanceof Error ? error.message : String(error)}`,
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Get context info using native data if available, falling back to transcript parsing.
|
|
169
|
+
*/
|
|
170
|
+
async getContextInfo(
|
|
171
|
+
hookData: ClaudeHookData,
|
|
172
|
+
): Promise<Outcome<ContextInfo>> {
|
|
173
|
+
const nativeContext = this.calculateContextFromHookData(hookData);
|
|
174
|
+
if (nativeContext) {
|
|
175
|
+
return ok(nativeContext);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// [LAW:one-source-of-truth] current_usage can be null (pre-first-call or
|
|
179
|
+
// post-/compact) while context_window_size is still present and
|
|
180
|
+
// authoritative — prefer it here too, and only fall to the floor when the
|
|
181
|
+
// client omits context_window entirely.
|
|
182
|
+
const contextLimit =
|
|
183
|
+
hookData.context_window?.context_window_size ?? DEFAULT_CONTEXT_WINDOW;
|
|
184
|
+
|
|
185
|
+
return this.calculateContextTokensFromTranscript(
|
|
186
|
+
hookData.transcript_path,
|
|
187
|
+
contextLimit,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
}
|