bb-cc-lite 0.1.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Softcane
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,62 @@
1
+ # bb-cc-lite
2
+
3
+ [![CI](https://github.com/softcane/bb-cc-lite/actions/workflows/ci.yml/badge.svg)](https://github.com/softcane/bb-cc-lite/actions/workflows/ci.yml)
4
+
5
+ Claude Code does not always fail loudly. Sometimes it loops, fills context, spends money, and still looks productive.
6
+
7
+ `bb-cc-lite` is **Black Box Claude Code Lite**: a small traffic light for your Claude Code status line.
8
+
9
+ ![bb-cc-lite statusline examples](./assets/statusline-demo.gif)
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ npx bb-cc-lite install --scope local
15
+ ```
16
+
17
+ Restart Claude Code in the project. The line appears at the bottom.
18
+
19
+ For faster tool-loop detection:
20
+
21
+ ```bash
22
+ npx bb-cc-lite install --scope local --hooks
23
+ ```
24
+
25
+ Hooks are optional. They run in the background and skip `UserPromptSubmit`.
26
+
27
+ ## The Line
28
+
29
+ ```text
30
+ bb: Healthy | ctx 42% | $0.18 | cache warm | continue normally
31
+ bb: Careful | ctx 82% | cache writes high | ask Claude for a 6-bullet handoff before more work
32
+ bb: Stop | Bash failed 3x running tests | fix the test setup manually, then ask Claude to rerun only that test
33
+ ```
34
+
35
+ `Healthy` means keep going. `Careful` means slow down. `Stop` means take over before Claude burns more turns.
36
+
37
+ ## Why
38
+
39
+ ```bash
40
+ bb-cc-lite why
41
+ ```
42
+
43
+ ```text
44
+ Last decision: Stop.
45
+ Reason: Bash failed 3x running tests. Claude is retrying a broken test loop.
46
+ Next action: fix the test setup manually, then ask Claude to rerun only that test.
47
+ ```
48
+
49
+ ## Privacy
50
+
51
+ By default, `bb-cc-lite` does not upload transcripts, store raw prompts, store raw tool output, store file contents, or print raw prompt/tool text.
52
+
53
+ It reads Claude Code status JSON from stdin, tails the local transcript defensively, and stores derived metadata only: counts, reason codes, token totals, costs, and hashed session ids.
54
+
55
+ ## Undo
56
+
57
+ ```bash
58
+ bb-cc-lite doctor
59
+ bb-cc-lite uninstall --scope local
60
+ ```
61
+
62
+ Install preserves an existing Claude Code `statusLine` unless you pass `--replace`.
Binary file
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,208 @@
1
+ #!/usr/bin/env node
2
+ import { formatDoctorChecks, runDoctor } from "./doctor.js";
3
+ import { mergeHookSummary, parseHookPayload } from "./hooks.js";
4
+ import { hashValue } from "./paths.js";
5
+ import { estimateCostUsd, loadPricing } from "./pricing.js";
6
+ import { renderStatusLine } from "./renderer.js";
7
+ import { decide } from "./signals.js";
8
+ import { installStatusLine, uninstallStatusLine } from "./settings.js";
9
+ import { parseStatusLineInput, readStdin, mergeUsage } from "./status-input.js";
10
+ import { hookSummary, latestDecision, recordDecision, recordHookEvent } from "./store.js";
11
+ import { parseTranscriptTail } from "./transcript.js";
12
+ async function main() {
13
+ const args = parseArgs(process.argv.slice(2));
14
+ switch (args.command) {
15
+ case "install":
16
+ await commandInstall(args);
17
+ break;
18
+ case "uninstall":
19
+ await commandUninstall(args);
20
+ break;
21
+ case "statusline":
22
+ await commandStatusLine();
23
+ break;
24
+ case "why":
25
+ await commandWhy(args);
26
+ break;
27
+ case "doctor":
28
+ await commandDoctor(args);
29
+ break;
30
+ case "hook":
31
+ await commandHook(args);
32
+ break;
33
+ case "help":
34
+ case "--help":
35
+ case "-h":
36
+ printHelp();
37
+ break;
38
+ default:
39
+ if (!args.command) {
40
+ printHelp();
41
+ }
42
+ else {
43
+ console.error(`Unknown command: ${args.command}`);
44
+ printHelp();
45
+ process.exitCode = 1;
46
+ }
47
+ }
48
+ }
49
+ async function commandInstall(args) {
50
+ const result = await installStatusLine({
51
+ scope: scopeFlag(args),
52
+ replace: Boolean(args.flags.replace),
53
+ hooks: Boolean(args.flags.hooks),
54
+ projectDir: stringFlag(args, "project"),
55
+ homeDir: stringFlag(args, "home")
56
+ });
57
+ console.log(result.message);
58
+ if (result.command) {
59
+ console.log(`Command: ${result.command}`);
60
+ }
61
+ if (result.status === "skipped") {
62
+ console.log(`Manual replace: bb-cc-lite install --scope ${result.target.scope} --replace`);
63
+ }
64
+ }
65
+ async function commandUninstall(args) {
66
+ const result = await uninstallStatusLine({
67
+ scope: scopeFlag(args),
68
+ force: Boolean(args.flags.force),
69
+ projectDir: stringFlag(args, "project"),
70
+ homeDir: stringFlag(args, "home")
71
+ });
72
+ console.log(result.message);
73
+ if (result.status === "refused") {
74
+ process.exitCode = 1;
75
+ }
76
+ }
77
+ async function commandStatusLine() {
78
+ try {
79
+ const raw = await readStdin();
80
+ const input = parseStatusLineInput(raw);
81
+ const sessionKey = hashValue(input.sessionId);
82
+ const transcript = mergeHookSummary(await parseTranscriptTail(input.transcriptPath), sessionKey
83
+ ? await hookSummary(sessionKey)
84
+ : {
85
+ failedToolResults: 0,
86
+ toolCalls: 0,
87
+ compactionEvents: 0,
88
+ repeatedFailures: []
89
+ });
90
+ const usage = mergeUsage(input.usage, transcript.usage);
91
+ if (input.costUsd === undefined) {
92
+ const estimated = estimateCostUsd(input.model.id || input.model.displayName, usage, await loadPricing());
93
+ if (estimated !== undefined) {
94
+ input.costUsd = estimated;
95
+ input.costSource = "estimated";
96
+ }
97
+ }
98
+ const previous = sessionKey ? await latestDecision(sessionKey) : undefined;
99
+ const decision = decide(input, transcript, { previous });
100
+ await recordDecision(decision);
101
+ const width = input.terminalWidth || process.stdout.columns;
102
+ process.stdout.write(`${renderStatusLine(decision, width)}\n`);
103
+ }
104
+ catch {
105
+ process.stdout.write("bb: Careful | statusline crashed | run bb-cc-lite doctor\n");
106
+ }
107
+ }
108
+ async function commandHook(args) {
109
+ try {
110
+ const fallbackEventName = stringFlag(args, "bb-cc-lite-hook") || args.positionals.find((value) => value !== "--bb-cc-lite-hook");
111
+ const event = parseHookPayload(await readStdin(), fallbackEventName);
112
+ if (event) {
113
+ await recordHookEvent(event);
114
+ }
115
+ }
116
+ catch {
117
+ // Hooks are telemetry-only and must never block Claude Code.
118
+ }
119
+ }
120
+ async function commandWhy(args) {
121
+ const sessionKey = stringFlag(args, "session") ? hashValue(stringFlag(args, "session")) : undefined;
122
+ const decision = await latestDecision(sessionKey);
123
+ if (args.flags.json) {
124
+ console.log(JSON.stringify(decision || null, null, 2));
125
+ return;
126
+ }
127
+ if (!decision) {
128
+ console.log("No bb-cc-lite decision has been recorded yet. Run the statusline command from Claude Code first.");
129
+ return;
130
+ }
131
+ console.log(formatWhy(decision));
132
+ }
133
+ async function commandDoctor(args) {
134
+ const checks = await runDoctor({
135
+ scope: scopeFlag(args),
136
+ projectDir: stringFlag(args, "project"),
137
+ homeDir: stringFlag(args, "home"),
138
+ transcriptPath: stringFlag(args, "transcript"),
139
+ refreshPricing: Boolean(args.flags["refresh-pricing"])
140
+ });
141
+ console.log(formatDoctorChecks(checks));
142
+ if (checks.some((check) => check.level === "FAIL")) {
143
+ process.exitCode = 1;
144
+ }
145
+ }
146
+ function formatWhy(decision) {
147
+ const cost = decision.costUsd === undefined
148
+ ? ""
149
+ : `\nCost evidence: ${decision.costSource === "estimated" ? "estimated " : ""}$${decision.costUsd.toFixed(4)}.`;
150
+ return [
151
+ `Last decision: ${decision.state}.`,
152
+ `Reason: ${decision.primaryEvidence}. ${decision.impact}.`,
153
+ `Next action: ${decision.action}.${cost}`
154
+ ].join("\n");
155
+ }
156
+ function parseArgs(argv) {
157
+ const [command = "help", ...rest] = argv;
158
+ const flags = {};
159
+ const positionals = [];
160
+ for (let index = 0; index < rest.length; index += 1) {
161
+ const arg = rest[index];
162
+ if (!arg.startsWith("--")) {
163
+ positionals.push(arg);
164
+ continue;
165
+ }
166
+ const [rawKey, inlineValue] = arg.slice(2).split("=", 2);
167
+ if (inlineValue !== undefined) {
168
+ flags[rawKey] = inlineValue;
169
+ continue;
170
+ }
171
+ const next = rest[index + 1];
172
+ if (next && !next.startsWith("--")) {
173
+ flags[rawKey] = next;
174
+ index += 1;
175
+ }
176
+ else {
177
+ flags[rawKey] = true;
178
+ }
179
+ }
180
+ return { command, flags, positionals };
181
+ }
182
+ function scopeFlag(args) {
183
+ const scope = stringFlag(args, "scope") || "local";
184
+ if (scope === "local" || scope === "project" || scope === "user") {
185
+ return scope;
186
+ }
187
+ throw new Error(`Invalid --scope ${scope}; expected local, project, or user`);
188
+ }
189
+ function stringFlag(args, name) {
190
+ const value = args.flags[name];
191
+ return typeof value === "string" ? value : undefined;
192
+ }
193
+ function printHelp() {
194
+ console.log(`bb-cc-lite
195
+
196
+ Usage:
197
+ bb-cc-lite install [--scope local|project|user] [--replace] [--hooks]
198
+ bb-cc-lite statusline
199
+ bb-cc-lite why [--json]
200
+ bb-cc-lite doctor [--scope local|project|user] [--transcript <path>] [--refresh-pricing]
201
+ bb-cc-lite uninstall [--scope local|project|user]
202
+ `);
203
+ }
204
+ main().catch((error) => {
205
+ console.error(error instanceof Error ? error.message : String(error));
206
+ process.exitCode = 1;
207
+ });
208
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import { type SettingsScope } from "./settings.js";
2
+ export interface DoctorOptions {
3
+ scope?: SettingsScope;
4
+ projectDir?: string;
5
+ homeDir?: string;
6
+ transcriptPath?: string;
7
+ refreshPricing?: boolean;
8
+ }
9
+ export interface DoctorCheck {
10
+ level: "OK" | "WARN" | "FAIL";
11
+ name: string;
12
+ message: string;
13
+ }
14
+ export declare function runDoctor(options?: DoctorOptions): Promise<DoctorCheck[]>;
15
+ export declare function formatDoctorChecks(checks: DoctorCheck[]): string;
package/dist/doctor.js ADDED
@@ -0,0 +1,116 @@
1
+ import { access } from "node:fs/promises";
2
+ import { constants } from "node:fs";
3
+ import { pricingCachePath } from "./paths.js";
4
+ import { refreshPricing } from "./pricing.js";
5
+ import { hasBbHooks, isBbStatusLine, readHooks, readStatusLine, resolveSettingsTarget } from "./settings.js";
6
+ export async function runDoctor(options = {}) {
7
+ const checks = [];
8
+ checks.push(checkNodeVersion());
9
+ const target = resolveSettingsTarget(options);
10
+ try {
11
+ const statusLine = await readStatusLine(target.scope, target.projectDir, target.homeDir);
12
+ if (statusLine && isBbStatusLine(statusLine)) {
13
+ checks.push({ level: "OK", name: "settings", message: `bb-cc-lite statusLine is installed in ${target.settingsPath}` });
14
+ }
15
+ else if (statusLine) {
16
+ checks.push({ level: "WARN", name: "settings", message: `custom statusLine is configured in ${target.settingsPath}` });
17
+ }
18
+ else {
19
+ checks.push({ level: "WARN", name: "settings", message: `no statusLine found in ${target.settingsPath}` });
20
+ }
21
+ }
22
+ catch (error) {
23
+ checks.push({
24
+ level: "FAIL",
25
+ name: "settings",
26
+ message: error instanceof Error ? error.message : `could not read ${target.settingsPath}`
27
+ });
28
+ }
29
+ try {
30
+ const hooks = await readHooks(target.scope, target.projectDir, target.homeDir);
31
+ if (hasBbHooks(hooks, target.homeDir)) {
32
+ checks.push({ level: "OK", name: "hooks", message: `optional bb-cc-lite hooks are installed in ${target.settingsPath}` });
33
+ }
34
+ else {
35
+ checks.push({ level: "WARN", name: "hooks", message: "optional bb-cc-lite hooks are not installed; run install --hooks to enable faster telemetry" });
36
+ }
37
+ }
38
+ catch (error) {
39
+ checks.push({
40
+ level: "FAIL",
41
+ name: "hooks",
42
+ message: error instanceof Error ? error.message : `could not read hooks from ${target.settingsPath}`
43
+ });
44
+ }
45
+ if (options.transcriptPath) {
46
+ try {
47
+ await access(options.transcriptPath, constants.R_OK);
48
+ checks.push({ level: "OK", name: "transcript", message: "transcript path is readable" });
49
+ }
50
+ catch {
51
+ checks.push({ level: "FAIL", name: "transcript", message: "transcript path is not readable" });
52
+ }
53
+ }
54
+ else {
55
+ checks.push({
56
+ level: "WARN",
57
+ name: "transcript",
58
+ message: "no transcript path supplied; pass --transcript <path> to check access"
59
+ });
60
+ }
61
+ addAnthropicBaseUrlCheck(checks);
62
+ await addLiteLLMChecks(checks, options.refreshPricing || false);
63
+ return checks;
64
+ }
65
+ export function formatDoctorChecks(checks) {
66
+ return checks.map((check) => `${check.level} ${check.name}: ${check.message}`).join("\n");
67
+ }
68
+ function checkNodeVersion() {
69
+ const major = Number(process.versions.node.split(".")[0]);
70
+ if (Number.isFinite(major) && major >= 20) {
71
+ return { level: "OK", name: "node", message: `Node ${process.versions.node}` };
72
+ }
73
+ return { level: "FAIL", name: "node", message: `Node ${process.versions.node}; bb-cc-lite requires Node >=20` };
74
+ }
75
+ async function addLiteLLMChecks(checks, shouldRefreshPricing) {
76
+ try {
77
+ await access(pricingCachePath(), constants.R_OK);
78
+ checks.push({ level: "OK", name: "litellm-pricing", message: `pricing cache exists at ${pricingCachePath()}` });
79
+ }
80
+ catch {
81
+ checks.push({ level: "WARN", name: "litellm-pricing", message: "using bundled pricing fallback; run doctor --refresh-pricing to cache LiteLLM prices" });
82
+ }
83
+ if (shouldRefreshPricing) {
84
+ try {
85
+ const table = await refreshPricing();
86
+ checks.push({ level: "OK", name: "litellm-pricing-refresh", message: `cached ${Object.keys(table.models).length} LiteLLM pricing entries` });
87
+ }
88
+ catch (error) {
89
+ checks.push({
90
+ level: "WARN",
91
+ name: "litellm-pricing-refresh",
92
+ message: error instanceof Error ? error.message : "could not refresh LiteLLM pricing"
93
+ });
94
+ }
95
+ }
96
+ }
97
+ function addAnthropicBaseUrlCheck(checks) {
98
+ const value = process.env.ANTHROPIC_BASE_URL;
99
+ if (!value) {
100
+ checks.push({ level: "OK", name: "anthropic-base-url", message: "ANTHROPIC_BASE_URL is unset; Claude Code will use the default Anthropic endpoint" });
101
+ return;
102
+ }
103
+ let host = "custom endpoint";
104
+ try {
105
+ host = new URL(value).host;
106
+ }
107
+ catch {
108
+ // Keep a non-sensitive generic label for malformed values.
109
+ }
110
+ checks.push({
111
+ level: "WARN",
112
+ name: "anthropic-base-url",
113
+ message: `ANTHROPIC_BASE_URL is set to ${host}; custom endpoints must support Claude Code model aliases`
114
+ });
115
+ }
116
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { DerivedHookEvent, TranscriptSummary } from "./types.js";
2
+ export declare const SAFE_HOOK_EVENTS: readonly ["PostToolUse", "PostToolUseFailure", "PostToolBatch", "PreCompact", "PostCompact", "Stop", "SessionEnd"];
3
+ export declare function parseHookPayload(raw: string, fallbackEventName?: string): DerivedHookEvent | undefined;
4
+ export declare function mergeHookSummary(transcript: TranscriptSummary, hookData: {
5
+ failedToolResults: number;
6
+ toolCalls: number;
7
+ compactionEvents: number;
8
+ repeatedFailures: Array<{
9
+ toolName: string;
10
+ count: number;
11
+ purpose?: string;
12
+ }>;
13
+ latestTimestamp?: string;
14
+ }): TranscriptSummary;
package/dist/hooks.js ADDED
@@ -0,0 +1,127 @@
1
+ import { hashValue } from "./paths.js";
2
+ import { asRecord, numberField, stringField } from "./status-input.js";
3
+ const TEST_COMMAND_RE = /\b(npm|pnpm|yarn|bun)\s+(run\s+)?(test|vitest|jest)|\b(vitest|jest|mocha|pytest|cargo\s+test|go\s+test|rspec|playwright\s+test)\b/i;
4
+ export const SAFE_HOOK_EVENTS = [
5
+ "PostToolUse",
6
+ "PostToolUseFailure",
7
+ "PostToolBatch",
8
+ "PreCompact",
9
+ "PostCompact",
10
+ "Stop",
11
+ "SessionEnd"
12
+ ];
13
+ export function parseHookPayload(raw, fallbackEventName) {
14
+ let parsed;
15
+ try {
16
+ parsed = JSON.parse(raw.trim() || "{}");
17
+ }
18
+ catch {
19
+ return undefined;
20
+ }
21
+ const root = asRecord(parsed);
22
+ if (!root) {
23
+ return undefined;
24
+ }
25
+ const hookEventName = stringField(root.hook_event_name) || stringField(root.event) || fallbackEventName || "unknown";
26
+ const sessionId = stringField(root.session_id) || stringField(root.sessionId);
27
+ const base = {
28
+ timestamp: stringField(root.timestamp) || new Date().toISOString(),
29
+ hookEventName,
30
+ sessionKey: hashValue(sessionId)
31
+ };
32
+ if (hookEventName === "PostToolUseFailure") {
33
+ const toolName = safeToolName(stringField(root.tool_name) || stringField(root.toolName));
34
+ return {
35
+ ...base,
36
+ kind: "tool_failure",
37
+ toolName,
38
+ purpose: classifyToolPurpose(toolName, root.tool_input ?? root.toolInput)
39
+ };
40
+ }
41
+ if (hookEventName === "PostToolUse") {
42
+ const toolName = safeToolName(stringField(root.tool_name) || stringField(root.toolName));
43
+ return {
44
+ ...base,
45
+ kind: "tool_success",
46
+ toolName,
47
+ purpose: classifyToolPurpose(toolName, root.tool_input ?? root.toolInput)
48
+ };
49
+ }
50
+ if (hookEventName === "PostToolBatch") {
51
+ return {
52
+ ...base,
53
+ kind: "tool_batch",
54
+ toolCount: countBatchTools(root)
55
+ };
56
+ }
57
+ if (hookEventName === "PreCompact" || hookEventName === "PostCompact") {
58
+ return {
59
+ ...base,
60
+ kind: "compaction"
61
+ };
62
+ }
63
+ if (hookEventName === "Stop" || hookEventName === "StopFailure") {
64
+ return {
65
+ ...base,
66
+ kind: "stop"
67
+ };
68
+ }
69
+ if (hookEventName === "SessionEnd") {
70
+ return {
71
+ ...base,
72
+ kind: "session_end"
73
+ };
74
+ }
75
+ return undefined;
76
+ }
77
+ export function mergeHookSummary(transcript, hookData) {
78
+ const repeatedFailures = new Map();
79
+ for (const failure of [...transcript.repeatedFailures, ...hookData.repeatedFailures]) {
80
+ const key = `${failure.toolName}:${failure.purpose || ""}`;
81
+ const existing = repeatedFailures.get(key);
82
+ repeatedFailures.set(key, {
83
+ toolName: failure.toolName,
84
+ purpose: failure.purpose,
85
+ count: Math.max(existing?.count || 0, failure.count)
86
+ });
87
+ }
88
+ return {
89
+ ...transcript,
90
+ toolCalls: Math.max(transcript.toolCalls, hookData.toolCalls),
91
+ failedToolResults: Math.max(transcript.failedToolResults, hookData.failedToolResults),
92
+ repeatedFailures: [...repeatedFailures.values()].filter((failure) => failure.count >= 2),
93
+ compactionEvents: Math.max(transcript.compactionEvents, hookData.compactionEvents),
94
+ latestTimestamp: transcript.latestTimestamp && hookData.latestTimestamp
95
+ ? transcript.latestTimestamp > hookData.latestTimestamp
96
+ ? transcript.latestTimestamp
97
+ : hookData.latestTimestamp
98
+ : transcript.latestTimestamp || hookData.latestTimestamp
99
+ };
100
+ }
101
+ function classifyToolPurpose(toolName, input) {
102
+ if (toolName !== "Bash") {
103
+ return undefined;
104
+ }
105
+ const command = stringField(asRecord(input)?.command);
106
+ return command && TEST_COMMAND_RE.test(command) ? "tests" : undefined;
107
+ }
108
+ function countBatchTools(root) {
109
+ const values = [root.tools, root.tool_uses, root.toolUses, root.results, root.tool_results, root.toolResults];
110
+ for (const value of values) {
111
+ if (Array.isArray(value)) {
112
+ return value.length;
113
+ }
114
+ const count = numberField(value);
115
+ if (count !== undefined) {
116
+ return Math.max(0, Math.floor(count));
117
+ }
118
+ }
119
+ return 0;
120
+ }
121
+ function safeToolName(toolName) {
122
+ if (!toolName) {
123
+ return "tool";
124
+ }
125
+ return /^[A-Za-z][A-Za-z0-9_-]{0,32}$/u.test(toolName) ? toolName : "tool";
126
+ }
127
+ //# sourceMappingURL=hooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export declare const APP_DIR_NAME = "bb-cc-lite";
2
+ export declare function appHome(homeDir?: string): string;
3
+ export declare function eventStorePath(): string;
4
+ export declare function backupDir(homeDir?: string): string;
5
+ export declare function pricingCachePath(): string;
6
+ export declare function cliPath(): string;
7
+ export declare function hashValue(value: string | undefined): string | undefined;
8
+ export declare function quoteShell(value: string): string;
package/dist/paths.js ADDED
@@ -0,0 +1,32 @@
1
+ import { createHash } from "node:crypto";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join, resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ export const APP_DIR_NAME = "bb-cc-lite";
6
+ export function appHome(homeDir = homedir()) {
7
+ return process.env.BB_CC_LITE_HOME || join(homeDir, ".claude", APP_DIR_NAME);
8
+ }
9
+ export function eventStorePath() {
10
+ return process.env.BB_CC_LITE_STORE || join(appHome(), "events.json");
11
+ }
12
+ export function backupDir(homeDir) {
13
+ return join(appHome(homeDir), "backups");
14
+ }
15
+ export function pricingCachePath() {
16
+ return process.env.BB_CC_LITE_PRICING_CACHE || join(appHome(), "litellm-pricing.json");
17
+ }
18
+ export function cliPath() {
19
+ return fileURLToPath(import.meta.url).endsWith("/paths.js")
20
+ ? resolve(dirname(fileURLToPath(import.meta.url)), "cli.js")
21
+ : resolve(process.argv[1] || "");
22
+ }
23
+ export function hashValue(value) {
24
+ if (!value) {
25
+ return undefined;
26
+ }
27
+ return createHash("sha256").update(value).digest("hex").slice(0, 16);
28
+ }
29
+ export function quoteShell(value) {
30
+ return `'${value.replaceAll("'", "'\\''")}'`;
31
+ }
32
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,CAAC,MAAM,YAAY,GAAG,YAAY,CAAC;AAEzC,MAAM,UAAU,OAAO,CAAC,OAAO,GAAG,OAAO,EAAE;IACzC,OAAO,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,IAAI,CAAC,OAAO,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC;AAC/E,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,OAAO,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,aAAa,CAAC,CAAC;AACxE,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,OAAgB;IACxC,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC,CAAC;AAC3C,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,OAAO,OAAO,CAAC,GAAG,CAAC,wBAAwB,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,sBAAsB,CAAC,CAAC;AACzF,CAAC;AAED,MAAM,UAAU,OAAO;IACrB,OAAO,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC;QACzD,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC;QAC5D,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,KAAyB;IACjD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACvE,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,OAAO,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC;AAC/C,CAAC"}