aiblueprint-cli 1.4.13 → 1.4.14

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 (25) hide show
  1. package/claude-code-config/scripts/CLAUDE.md +50 -0
  2. package/claude-code-config/scripts/biome.json +37 -0
  3. package/claude-code-config/scripts/bun.lockb +0 -0
  4. package/claude-code-config/scripts/package.json +39 -0
  5. package/claude-code-config/scripts/statusline/__tests__/context.test.ts +229 -0
  6. package/claude-code-config/scripts/statusline/__tests__/formatters.test.ts +108 -0
  7. package/claude-code-config/scripts/statusline/__tests__/statusline.test.ts +309 -0
  8. package/claude-code-config/scripts/statusline/data/.gitignore +8 -0
  9. package/claude-code-config/scripts/statusline/data/.gitkeep +0 -0
  10. package/claude-code-config/scripts/statusline/defaults.json +4 -0
  11. package/claude-code-config/scripts/statusline/docs/ARCHITECTURE.md +166 -0
  12. package/claude-code-config/scripts/statusline/fixtures/mock-transcript.jsonl +4 -0
  13. package/claude-code-config/scripts/statusline/fixtures/test-input.json +35 -0
  14. package/claude-code-config/scripts/statusline/src/index.ts +74 -0
  15. package/claude-code-config/scripts/statusline/src/lib/config-types.ts +4 -0
  16. package/claude-code-config/scripts/statusline/src/lib/menu-factories.ts +224 -0
  17. package/claude-code-config/scripts/statusline/src/lib/presets.ts +177 -0
  18. package/claude-code-config/scripts/statusline/src/lib/render-pure.ts +341 -21
  19. package/claude-code-config/scripts/statusline/src/lib/utils.ts +15 -0
  20. package/claude-code-config/scripts/statusline/src/tests/spend-v2.test.ts +306 -0
  21. package/claude-code-config/scripts/statusline/statusline.config.json +25 -39
  22. package/claude-code-config/scripts/statusline/test-with-fixtures.ts +37 -0
  23. package/claude-code-config/scripts/statusline/test.ts +20 -0
  24. package/claude-code-config/scripts/tsconfig.json +27 -0
  25. package/package.json +1 -1
@@ -0,0 +1,224 @@
1
+ import type { StatuslineConfig } from "./config-types";
2
+
3
+ export interface MenuOption {
4
+ path: string;
5
+ label: string;
6
+ type: "boolean" | "cycle" | "section";
7
+ getValue?: (config: StatuslineConfig) => boolean | string;
8
+ toggle?: (config: StatuslineConfig) => void;
9
+ cycle?: (config: StatuslineConfig, direction: 1 | -1) => void;
10
+ choices?: string[];
11
+ indent?: number;
12
+ hidden?: (config: StatuslineConfig) => boolean;
13
+ }
14
+
15
+ type ConfigPath = string;
16
+ type GetValue<T> = (config: StatuslineConfig) => T;
17
+ type SetValue<T> = (config: StatuslineConfig, value: T) => void;
18
+
19
+ function getNestedValue(obj: unknown, path: string): unknown {
20
+ return path.split(".").reduce((current, key) => {
21
+ if (current && typeof current === "object" && key in current) {
22
+ return (current as Record<string, unknown>)[key];
23
+ }
24
+ return undefined;
25
+ }, obj);
26
+ }
27
+
28
+ function setNestedValue(obj: unknown, path: string, value: unknown): void {
29
+ const keys = path.split(".");
30
+ const lastKey = keys.pop();
31
+ if (!lastKey) return;
32
+
33
+ let current = obj;
34
+ for (const key of keys) {
35
+ if (current && typeof current === "object" && key in current) {
36
+ current = (current as Record<string, unknown>)[key];
37
+ } else {
38
+ return;
39
+ }
40
+ }
41
+
42
+ if (current && typeof current === "object") {
43
+ (current as Record<string, unknown>)[lastKey] = value;
44
+ }
45
+ }
46
+
47
+ export function section(
48
+ path: string,
49
+ label: string,
50
+ options?: {
51
+ enabledPath?: string;
52
+ },
53
+ ): MenuOption {
54
+ if (options?.enabledPath) {
55
+ return {
56
+ path,
57
+ label,
58
+ type: "section",
59
+ getValue: (c) => getNestedValue(c, options.enabledPath!) as boolean,
60
+ toggle: (c) => {
61
+ const current = getNestedValue(c, options.enabledPath!) as boolean;
62
+ setNestedValue(c, options.enabledPath!, !current);
63
+ },
64
+ };
65
+ }
66
+ return { path, label, type: "section" };
67
+ }
68
+
69
+ export function toggle(
70
+ path: string,
71
+ label: string,
72
+ options?: {
73
+ indent?: number;
74
+ hidden?: (config: StatuslineConfig) => boolean;
75
+ },
76
+ ): MenuOption {
77
+ return {
78
+ path,
79
+ label,
80
+ type: "boolean",
81
+ getValue: (c) => getNestedValue(c, path) as boolean,
82
+ toggle: (c) => {
83
+ const current = getNestedValue(c, path) as boolean;
84
+ setNestedValue(c, path, !current);
85
+ },
86
+ indent: options?.indent ?? 1,
87
+ hidden: options?.hidden,
88
+ };
89
+ }
90
+
91
+ export function cycle<T extends string | number>(
92
+ path: string,
93
+ label: string,
94
+ choices: readonly T[],
95
+ options?: {
96
+ indent?: number;
97
+ hidden?: (config: StatuslineConfig) => boolean;
98
+ displayValue?: (value: T) => string;
99
+ },
100
+ ): MenuOption {
101
+ const stringChoices = choices.map((c) =>
102
+ options?.displayValue ? options.displayValue(c) : String(c),
103
+ );
104
+
105
+ return {
106
+ path,
107
+ label,
108
+ type: "cycle",
109
+ choices: stringChoices,
110
+ getValue: (c) => {
111
+ const value = getNestedValue(c, path) as T;
112
+ return options?.displayValue
113
+ ? options.displayValue(value)
114
+ : String(value);
115
+ },
116
+ cycle: (c, dir) => {
117
+ const current = getNestedValue(c, path) as T;
118
+ const currentIndex = choices.indexOf(current);
119
+ const next = (currentIndex + dir + choices.length) % choices.length;
120
+ setNestedValue(c, path, choices[next]);
121
+ },
122
+ indent: options?.indent ?? 1,
123
+ hidden: options?.hidden,
124
+ };
125
+ }
126
+
127
+ export function progressBarOptions(
128
+ basePath: string,
129
+ parentHidden: (config: StatuslineConfig) => boolean,
130
+ ): MenuOption[] {
131
+ const barPath = `${basePath}.progressBar`;
132
+
133
+ return [
134
+ toggle(`${barPath}.enabled`, "Progress bar", {
135
+ indent: 2,
136
+ hidden: parentHidden,
137
+ }),
138
+ cycle(`${barPath}.style`, "Style", PROGRESS_BAR_STYLES, {
139
+ indent: 3,
140
+ hidden: (c) =>
141
+ parentHidden(c) ||
142
+ !(getNestedValue(c, `${barPath}.enabled`) as boolean),
143
+ }),
144
+ cycle(`${barPath}.length`, "Length", PROGRESS_BAR_LENGTHS, {
145
+ indent: 3,
146
+ hidden: (c) =>
147
+ parentHidden(c) ||
148
+ !(getNestedValue(c, `${barPath}.enabled`) as boolean),
149
+ }),
150
+ cycle(`${barPath}.color`, "Color", PROGRESS_BAR_COLORS, {
151
+ indent: 3,
152
+ hidden: (c) =>
153
+ parentHidden(c) ||
154
+ !(getNestedValue(c, `${barPath}.enabled`) as boolean),
155
+ }),
156
+ cycle(`${barPath}.background`, "Background", PROGRESS_BAR_BACKGROUNDS, {
157
+ indent: 3,
158
+ hidden: (c) =>
159
+ parentHidden(c) ||
160
+ !(getNestedValue(c, `${barPath}.enabled`) as boolean),
161
+ }),
162
+ ];
163
+ }
164
+
165
+ export function percentageOptions(
166
+ basePath: string,
167
+ parentHidden: (config: StatuslineConfig) => boolean,
168
+ ): MenuOption[] {
169
+ const pctPath = basePath;
170
+
171
+ return [
172
+ toggle(`${pctPath}.enabled`, "Percentage display", {
173
+ indent: 1,
174
+ hidden: parentHidden,
175
+ }),
176
+ toggle(`${pctPath}.showValue`, "Show percentage value", {
177
+ indent: 2,
178
+ hidden: (c) =>
179
+ parentHidden(c) ||
180
+ !(getNestedValue(c, `${pctPath}.enabled`) as boolean),
181
+ }),
182
+ ...progressBarOptions(
183
+ pctPath,
184
+ (c) =>
185
+ parentHidden(c) ||
186
+ !(getNestedValue(c, `${pctPath}.enabled`) as boolean),
187
+ ),
188
+ ];
189
+ }
190
+
191
+ export const SEPARATORS = [
192
+ "|",
193
+ "•",
194
+ "·",
195
+ "⋅",
196
+ "●",
197
+ "◆",
198
+ "▪",
199
+ "▸",
200
+ "›",
201
+ "→",
202
+ ] as const;
203
+ export const PATH_DISPLAY_MODES = ["truncated", "basename", "full"] as const;
204
+ export const PROGRESS_BAR_STYLES = ["filled", "rectangle", "braille"] as const;
205
+ export const PROGRESS_BAR_LENGTHS = [5, 10, 15] as const;
206
+ export const PROGRESS_BAR_COLORS = [
207
+ "progressive",
208
+ "green",
209
+ "yellow",
210
+ "red",
211
+ "peach",
212
+ "black",
213
+ "white",
214
+ ] as const;
215
+ export const PROGRESS_BAR_BACKGROUNDS = [
216
+ "none",
217
+ "dark",
218
+ "gray",
219
+ "light",
220
+ "blue",
221
+ "purple",
222
+ "cyan",
223
+ "peach",
224
+ ] as const;
@@ -0,0 +1,177 @@
1
+ import { defaultConfig, type StatuslineConfig } from "./config";
2
+
3
+ export interface Preset {
4
+ name: string;
5
+ description: string;
6
+ config: StatuslineConfig;
7
+ }
8
+
9
+ const minimalConfig: StatuslineConfig = {
10
+ ...defaultConfig,
11
+ oneLine: true,
12
+ showSonnetModel: false,
13
+ pathDisplayMode: "basename",
14
+ git: {
15
+ enabled: true,
16
+ showBranch: true,
17
+ showDirtyIndicator: true,
18
+ showChanges: false,
19
+ showStaged: false,
20
+ showUnstaged: false,
21
+ },
22
+ session: {
23
+ infoSeparator: null,
24
+ cost: { enabled: true, format: "decimal1" },
25
+ duration: { enabled: false },
26
+ tokens: { enabled: false, showMax: false, showDecimals: false },
27
+ percentage: {
28
+ enabled: true,
29
+ showValue: true,
30
+ progressBar: {
31
+ enabled: false,
32
+ length: 10,
33
+ style: "braille",
34
+ color: "progressive",
35
+ background: "none",
36
+ },
37
+ },
38
+ },
39
+ limits: {
40
+ enabled: true,
41
+ showTimeLeft: false,
42
+ showPacingDelta: false,
43
+ cost: { enabled: false, format: "decimal1" },
44
+ percentage: {
45
+ enabled: true,
46
+ showValue: true,
47
+ progressBar: {
48
+ enabled: false,
49
+ length: 10,
50
+ style: "braille",
51
+ color: "progressive",
52
+ background: "none",
53
+ },
54
+ },
55
+ },
56
+ weeklyUsage: {
57
+ enabled: false,
58
+ showTimeLeft: false,
59
+ showPacingDelta: false,
60
+ cost: { enabled: false, format: "decimal1" },
61
+ percentage: {
62
+ enabled: true,
63
+ showValue: true,
64
+ progressBar: {
65
+ enabled: false,
66
+ length: 10,
67
+ style: "braille",
68
+ color: "progressive",
69
+ background: "none",
70
+ },
71
+ },
72
+ },
73
+ dailySpend: {
74
+ cost: { enabled: false, format: "decimal1" },
75
+ },
76
+ context: {
77
+ ...defaultConfig.context,
78
+ },
79
+ };
80
+
81
+ const fullConfig: StatuslineConfig = {
82
+ ...defaultConfig,
83
+ oneLine: false,
84
+ showSonnetModel: true,
85
+ pathDisplayMode: "truncated",
86
+ git: {
87
+ enabled: true,
88
+ showBranch: true,
89
+ showDirtyIndicator: true,
90
+ showChanges: true,
91
+ showStaged: true,
92
+ showUnstaged: true,
93
+ },
94
+ session: {
95
+ infoSeparator: null,
96
+ cost: { enabled: true, format: "decimal2" },
97
+ duration: { enabled: true },
98
+ tokens: { enabled: true, showMax: true, showDecimals: true },
99
+ percentage: {
100
+ enabled: true,
101
+ showValue: true,
102
+ progressBar: {
103
+ enabled: true,
104
+ length: 15,
105
+ style: "braille",
106
+ color: "progressive",
107
+ background: "dark",
108
+ },
109
+ },
110
+ },
111
+ limits: {
112
+ enabled: true,
113
+ showTimeLeft: true,
114
+ showPacingDelta: true,
115
+ cost: { enabled: true, format: "decimal1" },
116
+ percentage: {
117
+ enabled: true,
118
+ showValue: true,
119
+ progressBar: {
120
+ enabled: true,
121
+ length: 15,
122
+ style: "braille",
123
+ color: "progressive",
124
+ background: "dark",
125
+ },
126
+ },
127
+ },
128
+ weeklyUsage: {
129
+ enabled: true,
130
+ showTimeLeft: true,
131
+ showPacingDelta: true,
132
+ cost: { enabled: true, format: "decimal1" },
133
+ percentage: {
134
+ enabled: true,
135
+ showValue: true,
136
+ progressBar: {
137
+ enabled: true,
138
+ length: 15,
139
+ style: "braille",
140
+ color: "progressive",
141
+ background: "dark",
142
+ },
143
+ },
144
+ },
145
+ dailySpend: {
146
+ cost: { enabled: true, format: "decimal1" },
147
+ },
148
+ context: {
149
+ ...defaultConfig.context,
150
+ },
151
+ };
152
+
153
+ export const PRESETS: Preset[] = [
154
+ {
155
+ name: "Minimal",
156
+ description: "Essential info only: cost, percentage, branch",
157
+ config: minimalConfig,
158
+ },
159
+ {
160
+ name: "Balanced",
161
+ description: "Default settings with progress bars",
162
+ config: defaultConfig,
163
+ },
164
+ {
165
+ name: "Full",
166
+ description: "Everything enabled with all details",
167
+ config: fullConfig,
168
+ },
169
+ ];
170
+
171
+ export function applyPreset(presetName: string): StatuslineConfig | null {
172
+ const preset = PRESETS.find(
173
+ (p) => p.name.toLowerCase() === presetName.toLowerCase(),
174
+ );
175
+ if (!preset) return null;
176
+ return JSON.parse(JSON.stringify(preset.config));
177
+ }