@npow/oh-my-claude 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/README.md +317 -0
- package/bin/omc.js +403 -0
- package/docs/architecture.md +198 -0
- package/docs/segment-contract.md +186 -0
- package/docs/theme-format.md +156 -0
- package/package.json +35 -0
- package/src/cache.js +102 -0
- package/src/color.js +105 -0
- package/src/compositor.js +163 -0
- package/src/config.js +146 -0
- package/src/plugins.js +72 -0
- package/src/runner.js +160 -0
- package/src/segments/achievement.js +68 -0
- package/src/segments/api-timer.js +55 -0
- package/src/segments/battle-log.js +55 -0
- package/src/segments/cat.js +89 -0
- package/src/segments/coffee-cup.js +81 -0
- package/src/segments/commit-msg.js +95 -0
- package/src/segments/context-bar.js +50 -0
- package/src/segments/context-percent.js +40 -0
- package/src/segments/context-tokens.js +52 -0
- package/src/segments/cost-budget.js +43 -0
- package/src/segments/coworker.js +137 -0
- package/src/segments/custom-text.js +25 -0
- package/src/segments/directory.js +75 -0
- package/src/segments/emoji-story.js +99 -0
- package/src/segments/flex-space.js +25 -0
- package/src/segments/fortune-cookie.js +131 -0
- package/src/segments/garden.js +57 -0
- package/src/segments/git-branch.js +36 -0
- package/src/segments/git-status.js +56 -0
- package/src/segments/horoscope.js +134 -0
- package/src/segments/index.js +65 -0
- package/src/segments/lines-changed.js +29 -0
- package/src/segments/model-name.js +28 -0
- package/src/segments/narrator.js +129 -0
- package/src/segments/output-style.js +25 -0
- package/src/segments/rpg-stats.js +119 -0
- package/src/segments/separator-arrow.js +22 -0
- package/src/segments/separator-pipe.js +22 -0
- package/src/segments/separator-space.js +22 -0
- package/src/segments/session-cost.js +72 -0
- package/src/segments/session-timer.js +53 -0
- package/src/segments/smart-nudge.js +97 -0
- package/src/segments/soundtrack.js +133 -0
- package/src/segments/speedrun.js +94 -0
- package/src/segments/stock-ticker.js +71 -0
- package/src/segments/streak.js +131 -0
- package/src/segments/tamagotchi.js +95 -0
- package/src/segments/token-sparkline.js +73 -0
- package/src/segments/version.js +27 -0
- package/src/segments/vibe-check.js +109 -0
- package/src/segments/vim-mode.js +29 -0
- package/src/segments/weather-report.js +88 -0
- package/themes/default.json +59 -0
- package/themes/minimal.json +37 -0
- package/themes/powerline.json +73 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// src/segments/battle-log.js — Session framed as a dungeon crawl
|
|
2
|
+
// Zero dependencies. Node 18+ ESM.
|
|
3
|
+
//
|
|
4
|
+
// Context percentage maps to dungeon depth:
|
|
5
|
+
// >= 95% FINAL BOSS
|
|
6
|
+
// >= 80% Boss Battle
|
|
7
|
+
// >= 60% Deep Dungeon
|
|
8
|
+
// >= 40% Mid Dungeon
|
|
9
|
+
// >= 20% Exploring
|
|
10
|
+
// < 20% Base Camp
|
|
11
|
+
//
|
|
12
|
+
// Loot bonus: if lines_added > 0, appends gold count.
|
|
13
|
+
|
|
14
|
+
export const meta = {
|
|
15
|
+
name: 'battle-log',
|
|
16
|
+
description: 'Session framed as a dungeon crawl based on context depth',
|
|
17
|
+
requires: [],
|
|
18
|
+
defaultConfig: {
|
|
19
|
+
style: '',
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const DEPTHS = [
|
|
24
|
+
{ min: 95, text: () => '\u2694\uFE0F FINAL BOSS (95%)', style: 'bold red' },
|
|
25
|
+
{ min: 80, text: (p) => `\u2694\uFE0F Boss Battle (${p}%)`, style: 'bold yellow' },
|
|
26
|
+
{ min: 60, text: (p) => `\uD83C\uDFF0 Deep Dungeon (${p}%)`, style: 'yellow' },
|
|
27
|
+
{ min: 40, text: (p) => `\uD83D\uDDE1\uFE0F Mid Dungeon (${p}%)`, style: 'cyan' },
|
|
28
|
+
{ min: 20, text: (p) => `\uD83D\uDEAA Exploring (${p}%)`, style: 'green' },
|
|
29
|
+
{ min: -Infinity, text: (p) => `\uD83C\uDFD5\uFE0F Base Camp (${p}%)`, style: 'dim' },
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {object} data - Parsed stdin JSON from Claude Code
|
|
34
|
+
* @param {object} config - Per-segment config from theme
|
|
35
|
+
* @returns {{text: string, style: string}|null}
|
|
36
|
+
*/
|
|
37
|
+
export function render(data, config) {
|
|
38
|
+
const cfg = { ...meta.defaultConfig, ...config };
|
|
39
|
+
|
|
40
|
+
const pct = data?.context_window?.used_percentage;
|
|
41
|
+
if (pct == null) return null;
|
|
42
|
+
|
|
43
|
+
const rounded = Math.round(pct);
|
|
44
|
+
const depth = DEPTHS.find((d) => rounded >= d.min);
|
|
45
|
+
let text = depth.text(rounded);
|
|
46
|
+
|
|
47
|
+
const linesAdded = data?.cost?.total_lines_added ?? 0;
|
|
48
|
+
if (linesAdded > 0) {
|
|
49
|
+
text += ` +${linesAdded} gold`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const style = cfg.style || depth.style;
|
|
53
|
+
|
|
54
|
+
return { text, style };
|
|
55
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// src/segments/cat.js — A cat that does cat things based on session state
|
|
2
|
+
// Zero dependencies. Node 18+ ESM.
|
|
3
|
+
//
|
|
4
|
+
// States (priority order):
|
|
5
|
+
// 1. Sits on context window — context >= 90%
|
|
6
|
+
// 2. Pushes lines off desk — lines_removed > lines_added AND lines_removed > 20
|
|
7
|
+
// 3. Sleeping — api_duration > total_duration * 0.5 AND total_duration > 120s
|
|
8
|
+
// 4. Watches intently — lines_added > 200
|
|
9
|
+
// 5. Knocks wallet off table — cost > $10
|
|
10
|
+
// 6. Yawns — duration > 1 hour
|
|
11
|
+
// 7. Perks up — duration < 1 minute
|
|
12
|
+
// 8. Default — just a cat
|
|
13
|
+
|
|
14
|
+
export const meta = {
|
|
15
|
+
name: 'cat',
|
|
16
|
+
description: 'A cat that does cat things based on session state',
|
|
17
|
+
requires: [],
|
|
18
|
+
defaultConfig: {
|
|
19
|
+
style: '',
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Determine the cat's current behavior from session metrics.
|
|
25
|
+
* Priority order is enforced by early returns.
|
|
26
|
+
*
|
|
27
|
+
* @param {object} data - Parsed stdin JSON from Claude Code
|
|
28
|
+
* @returns {{ text: string, style: string }}
|
|
29
|
+
*/
|
|
30
|
+
function resolveCat(data) {
|
|
31
|
+
const context = data?.context_window?.used_percentage ?? 0;
|
|
32
|
+
const linesAdded = data?.cost?.total_lines_added ?? 0;
|
|
33
|
+
const linesRemoved = data?.cost?.total_lines_removed ?? 0;
|
|
34
|
+
const cost = data?.cost?.total_cost_usd ?? 0;
|
|
35
|
+
const totalDuration = data?.cost?.total_duration_ms ?? 0;
|
|
36
|
+
const apiDuration = data?.cost?.total_api_duration_ms ?? 0;
|
|
37
|
+
|
|
38
|
+
// 1. Sits on context window
|
|
39
|
+
if (context >= 90) {
|
|
40
|
+
return { text: '=^._.^= *sits on context window*', style: 'bold yellow' };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 2. Pushes lines off desk
|
|
44
|
+
if (linesRemoved > linesAdded && linesRemoved > 20) {
|
|
45
|
+
return { text: `=^._.^= *pushes ${linesRemoved} lines off desk*`, style: 'red' };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 3. Sleeping — API wait dominates a non-trivial session
|
|
49
|
+
if (totalDuration > 120_000 && apiDuration > totalDuration * 0.5) {
|
|
50
|
+
return { text: '=^._.^= zzz', style: 'dim' };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 4. Watches intently
|
|
54
|
+
if (linesAdded > 200) {
|
|
55
|
+
return { text: '=^._.^= *watches intently*', style: 'cyan' };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 5. Knocks wallet off table
|
|
59
|
+
if (cost > 10) {
|
|
60
|
+
return { text: '=^._.^= *knocks wallet off table*', style: 'yellow' };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 6. Yawns — long session
|
|
64
|
+
if (totalDuration > 3_600_000) {
|
|
65
|
+
return { text: '=^._.^= *yawns*', style: 'dim' };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 7. Perks up — fresh session
|
|
69
|
+
if (totalDuration < 60_000) {
|
|
70
|
+
return { text: '=^._.^= *perks up*', style: 'green' };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 8. Default
|
|
74
|
+
return { text: '=^._.^=', style: 'dim' };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @param {object} data - Parsed stdin JSON from Claude Code
|
|
79
|
+
* @param {object} config - Per-segment config from theme
|
|
80
|
+
* @returns {{text: string, style: string}}
|
|
81
|
+
*/
|
|
82
|
+
export function render(data, config) {
|
|
83
|
+
const cfg = { ...meta.defaultConfig, ...config };
|
|
84
|
+
|
|
85
|
+
const cat = resolveCat(data);
|
|
86
|
+
const style = cfg.style || cat.style;
|
|
87
|
+
|
|
88
|
+
return { text: cat.text, style };
|
|
89
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// src/segments/coffee-cup.js — Coffee cup that drains over a 2-hour session
|
|
2
|
+
// Zero dependencies. Node 18+ ESM.
|
|
3
|
+
//
|
|
4
|
+
// Based on total_duration_ms:
|
|
5
|
+
// < 15min: [████] bold
|
|
6
|
+
// 15-30min: [███░] bold
|
|
7
|
+
// 30-45min: [██░░] (none)
|
|
8
|
+
// 45-60min: [█░░░] yellow
|
|
9
|
+
// 60-90min: [░░░░] dim
|
|
10
|
+
// 90min+: [ ] refill? bold red
|
|
11
|
+
|
|
12
|
+
const MIN_15 = 15 * 60 * 1000;
|
|
13
|
+
const MIN_30 = 30 * 60 * 1000;
|
|
14
|
+
const MIN_45 = 45 * 60 * 1000;
|
|
15
|
+
const MIN_60 = 60 * 60 * 1000;
|
|
16
|
+
const MIN_90 = 90 * 60 * 1000;
|
|
17
|
+
|
|
18
|
+
export const meta = {
|
|
19
|
+
name: 'coffee-cup',
|
|
20
|
+
description: 'A coffee cup that drains as the session progresses',
|
|
21
|
+
requires: [],
|
|
22
|
+
defaultConfig: {
|
|
23
|
+
style: '',
|
|
24
|
+
charFull: '\u2588', // █
|
|
25
|
+
charEmpty: '\u2591', // ░
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Build the coffee cup display string.
|
|
31
|
+
*
|
|
32
|
+
* @param {number} fullCount - Number of full blocks (0-4)
|
|
33
|
+
* @param {string} charFull - Character for full portion
|
|
34
|
+
* @param {string} charEmpty - Character for empty portion
|
|
35
|
+
* @param {boolean} refill - Whether to append " refill?"
|
|
36
|
+
* @returns {string}
|
|
37
|
+
*/
|
|
38
|
+
function buildCup(fullCount, charFull, charEmpty, refill) {
|
|
39
|
+
if (refill) {
|
|
40
|
+
return '[ ] refill?';
|
|
41
|
+
}
|
|
42
|
+
const full = charFull.repeat(fullCount);
|
|
43
|
+
const empty = charEmpty.repeat(4 - fullCount);
|
|
44
|
+
return `[${full}${empty}]`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {object} data - Parsed stdin JSON from Claude Code
|
|
49
|
+
* @param {object} config - Per-segment config from theme
|
|
50
|
+
* @returns {{text: string, style: string}}
|
|
51
|
+
*/
|
|
52
|
+
export function render(data, config) {
|
|
53
|
+
const cfg = { ...meta.defaultConfig, ...config };
|
|
54
|
+
|
|
55
|
+
const durationMs = data?.cost?.total_duration_ms ?? 0;
|
|
56
|
+
|
|
57
|
+
let text;
|
|
58
|
+
let levelStyle;
|
|
59
|
+
|
|
60
|
+
if (durationMs >= MIN_90) {
|
|
61
|
+
text = buildCup(0, cfg.charFull, cfg.charEmpty, true);
|
|
62
|
+
levelStyle = 'bold red';
|
|
63
|
+
} else if (durationMs >= MIN_60) {
|
|
64
|
+
text = buildCup(0, cfg.charFull, cfg.charEmpty, false);
|
|
65
|
+
levelStyle = 'dim';
|
|
66
|
+
} else if (durationMs >= MIN_45) {
|
|
67
|
+
text = buildCup(1, cfg.charFull, cfg.charEmpty, false);
|
|
68
|
+
levelStyle = 'yellow';
|
|
69
|
+
} else if (durationMs >= MIN_30) {
|
|
70
|
+
text = buildCup(2, cfg.charFull, cfg.charEmpty, false);
|
|
71
|
+
levelStyle = '';
|
|
72
|
+
} else if (durationMs >= MIN_15) {
|
|
73
|
+
text = buildCup(3, cfg.charFull, cfg.charEmpty, false);
|
|
74
|
+
levelStyle = 'bold';
|
|
75
|
+
} else {
|
|
76
|
+
text = buildCup(4, cfg.charFull, cfg.charEmpty, false);
|
|
77
|
+
levelStyle = 'bold';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return { text, style: cfg.style || levelStyle };
|
|
81
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// src/segments/commit-msg.js — Preview what the commit message SHOULD be based on session activity
|
|
2
|
+
// Zero dependencies. Node 18+ ESM.
|
|
3
|
+
//
|
|
4
|
+
// Message selection (priority order, first match wins):
|
|
5
|
+
// 1. removed > added * 2 AND removed > 20 -> "fix: remove everything"
|
|
6
|
+
// 2. removed > added AND removed > 10 -> "refactor: simplify"
|
|
7
|
+
// 3. lines_added > 500 -> "feat: rewrite the entire codebase"
|
|
8
|
+
// 4. lines_added > 200 -> "feat: something incredible"
|
|
9
|
+
// 5. lines_added > 100 -> "feat: new feature"
|
|
10
|
+
// 6. lines_added > 50 -> "chore: updates"
|
|
11
|
+
// 7. lines_added > 10 -> "fix: the thing"
|
|
12
|
+
// 8. lines_added > 0 -> "style: whitespace"
|
|
13
|
+
// 9. default -> "docs: update README"
|
|
14
|
+
//
|
|
15
|
+
// Display: git commit -m "{message}" (or just the message if showPrefix is false)
|
|
16
|
+
|
|
17
|
+
export const meta = {
|
|
18
|
+
name: 'commit-msg',
|
|
19
|
+
description: 'Preview what the commit message should be based on session activity',
|
|
20
|
+
requires: [],
|
|
21
|
+
defaultConfig: {
|
|
22
|
+
style: 'dim',
|
|
23
|
+
showPrefix: true,
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Determine the commit message based on session line change metrics.
|
|
29
|
+
*
|
|
30
|
+
* @param {object} data - Parsed stdin JSON from Claude Code
|
|
31
|
+
* @returns {string}
|
|
32
|
+
*/
|
|
33
|
+
function resolveMessage(data) {
|
|
34
|
+
const linesAdded = data?.cost?.total_lines_added ?? 0;
|
|
35
|
+
const linesRemoved = data?.cost?.total_lines_removed ?? 0;
|
|
36
|
+
|
|
37
|
+
// 1. Massive net deletion
|
|
38
|
+
if (linesRemoved > linesAdded * 2 && linesRemoved > 20) {
|
|
39
|
+
return 'fix: remove everything';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 2. Net deletion
|
|
43
|
+
if (linesRemoved > linesAdded && linesRemoved > 10) {
|
|
44
|
+
return 'refactor: simplify';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 3. Huge additions
|
|
48
|
+
if (linesAdded > 500) {
|
|
49
|
+
return 'feat: rewrite the entire codebase';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 4. Large additions
|
|
53
|
+
if (linesAdded > 200) {
|
|
54
|
+
return 'feat: something incredible';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 5. Moderate additions
|
|
58
|
+
if (linesAdded > 100) {
|
|
59
|
+
return 'feat: new feature';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 6. Small additions
|
|
63
|
+
if (linesAdded > 50) {
|
|
64
|
+
return 'chore: updates';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 7. Minor additions
|
|
68
|
+
if (linesAdded > 10) {
|
|
69
|
+
return 'fix: the thing';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 8. Tiny additions
|
|
73
|
+
if (linesAdded > 0) {
|
|
74
|
+
return 'style: whitespace';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 9. Default — nothing changed
|
|
78
|
+
return 'docs: update README';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @param {object} data - Parsed stdin JSON from Claude Code
|
|
83
|
+
* @param {object} config - Per-segment config from theme
|
|
84
|
+
* @returns {{text: string, style: string}}
|
|
85
|
+
*/
|
|
86
|
+
export function render(data, config) {
|
|
87
|
+
const cfg = { ...meta.defaultConfig, ...config };
|
|
88
|
+
const message = resolveMessage(data);
|
|
89
|
+
|
|
90
|
+
const text = cfg.showPrefix
|
|
91
|
+
? `git commit -m "${message}"`
|
|
92
|
+
: message;
|
|
93
|
+
|
|
94
|
+
return { text, style: cfg.style };
|
|
95
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// src/segments/context-bar.js — Visual progress bar for context window usage
|
|
2
|
+
// Zero dependencies. Node 18+ ESM.
|
|
3
|
+
|
|
4
|
+
export const meta = {
|
|
5
|
+
name: 'context-bar',
|
|
6
|
+
description: 'Visual progress bar showing context window usage percentage',
|
|
7
|
+
requires: [],
|
|
8
|
+
defaultConfig: {
|
|
9
|
+
width: 20,
|
|
10
|
+
charFilled: '\u2593',
|
|
11
|
+
charEmpty: '\u2591',
|
|
12
|
+
warnAt: 60,
|
|
13
|
+
criticalAt: 80,
|
|
14
|
+
showPercent: true,
|
|
15
|
+
styleOk: 'green',
|
|
16
|
+
styleWarn: 'bold yellow',
|
|
17
|
+
styleCritical: 'bold red',
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {object} data - Parsed stdin JSON from Claude Code
|
|
23
|
+
* @param {object} config - Per-segment config from theme
|
|
24
|
+
* @returns {{text: string, style: string}|null}
|
|
25
|
+
*/
|
|
26
|
+
export function render(data, config) {
|
|
27
|
+
const cfg = { ...meta.defaultConfig, ...config };
|
|
28
|
+
|
|
29
|
+
const pct = data?.context_window?.used_percentage;
|
|
30
|
+
if (pct == null) return null;
|
|
31
|
+
|
|
32
|
+
const filled = Math.round((pct / 100) * cfg.width);
|
|
33
|
+
const empty = cfg.width - filled;
|
|
34
|
+
let bar = cfg.charFilled.repeat(filled) + cfg.charEmpty.repeat(empty);
|
|
35
|
+
|
|
36
|
+
if (cfg.showPercent) {
|
|
37
|
+
bar += ` ${Math.round(pct)}%`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let style;
|
|
41
|
+
if (pct >= cfg.criticalAt) {
|
|
42
|
+
style = cfg.styleCritical;
|
|
43
|
+
} else if (pct >= cfg.warnAt) {
|
|
44
|
+
style = cfg.styleWarn;
|
|
45
|
+
} else {
|
|
46
|
+
style = cfg.styleOk;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return { text: bar, style };
|
|
50
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// src/segments/context-percent.js — Context window usage as percentage
|
|
2
|
+
// Zero dependencies. Node 18+ ESM.
|
|
3
|
+
|
|
4
|
+
export const meta = {
|
|
5
|
+
name: 'context-percent',
|
|
6
|
+
description: 'Shows context window usage as a percentage',
|
|
7
|
+
requires: [],
|
|
8
|
+
defaultConfig: {
|
|
9
|
+
style: 'white',
|
|
10
|
+
warnAt: 60,
|
|
11
|
+
criticalAt: 80,
|
|
12
|
+
styleWarn: 'bold yellow',
|
|
13
|
+
styleCritical: 'bold red',
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {object} data - Parsed stdin JSON from Claude Code
|
|
19
|
+
* @param {object} config - Per-segment config from theme
|
|
20
|
+
* @returns {{text: string, style: string}|null}
|
|
21
|
+
*/
|
|
22
|
+
export function render(data, config) {
|
|
23
|
+
const cfg = { ...meta.defaultConfig, ...config };
|
|
24
|
+
|
|
25
|
+
const pct = data?.context_window?.used_percentage;
|
|
26
|
+
if (pct == null) return null;
|
|
27
|
+
|
|
28
|
+
const rounded = Math.round(pct);
|
|
29
|
+
|
|
30
|
+
let style;
|
|
31
|
+
if (pct >= cfg.criticalAt) {
|
|
32
|
+
style = cfg.styleCritical;
|
|
33
|
+
} else if (pct >= cfg.warnAt) {
|
|
34
|
+
style = cfg.styleWarn;
|
|
35
|
+
} else {
|
|
36
|
+
style = cfg.style;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return { text: `${rounded}%`, style };
|
|
40
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// src/segments/context-tokens.js — Token usage as "XXk/YYYk"
|
|
2
|
+
// Zero dependencies. Node 18+ ESM.
|
|
3
|
+
|
|
4
|
+
export const meta = {
|
|
5
|
+
name: 'context-tokens',
|
|
6
|
+
description: 'Shows token usage as used/total (e.g., "84k/200k")',
|
|
7
|
+
requires: [],
|
|
8
|
+
defaultConfig: {
|
|
9
|
+
style: 'dim',
|
|
10
|
+
format: 'short',
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Format a number in short form (e.g., 84000 -> "84k").
|
|
16
|
+
* @param {number} n
|
|
17
|
+
* @returns {string}
|
|
18
|
+
*/
|
|
19
|
+
function shortFormat(n) {
|
|
20
|
+
if (n >= 1000) {
|
|
21
|
+
return `${Math.round(n / 1000)}k`;
|
|
22
|
+
}
|
|
23
|
+
return String(n);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Format a number with commas (e.g., 84000 -> "84,000").
|
|
28
|
+
* @param {number} n
|
|
29
|
+
* @returns {string}
|
|
30
|
+
*/
|
|
31
|
+
function fullFormat(n) {
|
|
32
|
+
return n.toLocaleString('en-US');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {object} data - Parsed stdin JSON from Claude Code
|
|
37
|
+
* @param {object} config - Per-segment config from theme
|
|
38
|
+
* @returns {{text: string, style: string}|null}
|
|
39
|
+
*/
|
|
40
|
+
export function render(data, config) {
|
|
41
|
+
const cfg = { ...meta.defaultConfig, ...config };
|
|
42
|
+
|
|
43
|
+
const inputTokens = data?.cost?.total_input_tokens;
|
|
44
|
+
const windowSize = data?.context_window?.context_window_size;
|
|
45
|
+
|
|
46
|
+
if (!inputTokens || !windowSize) return null;
|
|
47
|
+
|
|
48
|
+
const fmt = cfg.format === 'full' ? fullFormat : shortFormat;
|
|
49
|
+
const text = `${fmt(inputTokens)}/${fmt(windowSize)}`;
|
|
50
|
+
|
|
51
|
+
return { text, style: cfg.style };
|
|
52
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// src/segments/cost-budget.js — Cost vs budget display
|
|
2
|
+
// Zero dependencies. Node 18+ ESM.
|
|
3
|
+
|
|
4
|
+
export const meta = {
|
|
5
|
+
name: 'cost-budget',
|
|
6
|
+
description: 'Shows current cost vs budget (e.g., "$3.50/$10.00")',
|
|
7
|
+
requires: [],
|
|
8
|
+
defaultConfig: {
|
|
9
|
+
style: 'white',
|
|
10
|
+
budget: 10.0,
|
|
11
|
+
precision: 2,
|
|
12
|
+
warnAt: 0.8,
|
|
13
|
+
styleWarn: 'yellow',
|
|
14
|
+
styleCritical: 'red',
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {object} data - Parsed stdin JSON from Claude Code
|
|
20
|
+
* @param {object} config - Per-segment config from theme
|
|
21
|
+
* @returns {{text: string, style: string}|null}
|
|
22
|
+
*/
|
|
23
|
+
export function render(data, config) {
|
|
24
|
+
const cfg = { ...meta.defaultConfig, ...config };
|
|
25
|
+
|
|
26
|
+
const cost = data?.cost?.total_cost_usd;
|
|
27
|
+
if (cost == null) return null;
|
|
28
|
+
|
|
29
|
+
const ratio = cost / cfg.budget;
|
|
30
|
+
|
|
31
|
+
let style;
|
|
32
|
+
if (ratio >= 1.0) {
|
|
33
|
+
style = cfg.styleCritical;
|
|
34
|
+
} else if (ratio >= cfg.warnAt) {
|
|
35
|
+
style = cfg.styleWarn;
|
|
36
|
+
} else {
|
|
37
|
+
style = cfg.style;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const text = `$${cost.toFixed(cfg.precision)}/$${cfg.budget.toFixed(cfg.precision)}`;
|
|
41
|
+
|
|
42
|
+
return { text, style };
|
|
43
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// src/segments/coworker.js — Fake Slack messages from a simulated coworker watching you code
|
|
2
|
+
// Zero dependencies. Node 18+ ESM.
|
|
3
|
+
//
|
|
4
|
+
// Message selection (priority order, first match wins):
|
|
5
|
+
// 1. context >= 90% -> "we need to talk about your context usage"
|
|
6
|
+
// 2. context >= 75% -> "getting a bit full in here"
|
|
7
|
+
// 3. cost >= $20 -> "finance wants to chat"
|
|
8
|
+
// 4. cost >= $10 -> "that's coming out of your bonus"
|
|
9
|
+
// 5. lines_added > 500 -> "are you rewriting the whole thing?"
|
|
10
|
+
// 6. lines_added > 200 -> "ship it already"
|
|
11
|
+
// 7. removed > added AND removed > 50 -> "delete is my favorite key too"
|
|
12
|
+
// 8. duration > 2hr -> "do you even go outside?"
|
|
13
|
+
// 9. duration > 1hr -> "still going huh"
|
|
14
|
+
// 10. duration < 1min -> "oh here we go again"
|
|
15
|
+
// 11. default -> random pick from "looking good" / "LGTM" / "carry on"
|
|
16
|
+
//
|
|
17
|
+
// Anti-flicker: message only changes every 5 renders.
|
|
18
|
+
|
|
19
|
+
export const meta = {
|
|
20
|
+
name: 'coworker',
|
|
21
|
+
description: 'Fake Slack messages from a simulated coworker reacting to your session',
|
|
22
|
+
requires: [],
|
|
23
|
+
defaultConfig: {
|
|
24
|
+
style: 'dim',
|
|
25
|
+
botName: 'bot',
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// Module-level state for anti-flicker
|
|
30
|
+
let lastMessage = '';
|
|
31
|
+
let renderCount = 0;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Simple deterministic pick from an array based on a numeric seed.
|
|
35
|
+
*
|
|
36
|
+
* @param {string[]} choices
|
|
37
|
+
* @param {number} seed
|
|
38
|
+
* @returns {string}
|
|
39
|
+
*/
|
|
40
|
+
function pick(choices, seed) {
|
|
41
|
+
return choices[Math.abs(seed) % choices.length];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Resolve the coworker message based on session state, in priority order.
|
|
46
|
+
*
|
|
47
|
+
* @param {object} data - Parsed stdin JSON from Claude Code
|
|
48
|
+
* @param {string} botName - Display name for the fake coworker
|
|
49
|
+
* @param {number} seed - Deterministic seed for default message picks
|
|
50
|
+
* @returns {string}
|
|
51
|
+
*/
|
|
52
|
+
function resolveMessage(data, botName, seed) {
|
|
53
|
+
const contextPct = data?.context_window?.used_percentage ?? 0;
|
|
54
|
+
const cost = data?.cost?.total_cost_usd ?? 0;
|
|
55
|
+
const linesAdded = data?.cost?.total_lines_added ?? 0;
|
|
56
|
+
const linesRemoved = data?.cost?.total_lines_removed ?? 0;
|
|
57
|
+
const duration = data?.cost?.total_duration_ms ?? 0;
|
|
58
|
+
|
|
59
|
+
// 1. Context >= 90%
|
|
60
|
+
if (contextPct >= 90) {
|
|
61
|
+
return `@${botName}: 'we need to talk about your context usage'`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 2. Context >= 75%
|
|
65
|
+
if (contextPct >= 75) {
|
|
66
|
+
return `@${botName}: 'getting a bit full in here'`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 3. Cost >= $20
|
|
70
|
+
if (cost >= 20) {
|
|
71
|
+
return `@${botName}: 'finance wants to chat'`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 4. Cost >= $10
|
|
75
|
+
if (cost >= 10) {
|
|
76
|
+
return `@${botName}: 'that's coming out of your bonus'`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 5. Lines added > 500
|
|
80
|
+
if (linesAdded > 500) {
|
|
81
|
+
return `@${botName}: 'are you rewriting the whole thing?'`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 6. Lines added > 200
|
|
85
|
+
if (linesAdded > 200) {
|
|
86
|
+
return `@${botName}: 'ship it already'`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// 7. Net deletion with significant removal
|
|
90
|
+
if (linesRemoved > linesAdded && linesRemoved > 50) {
|
|
91
|
+
return `@${botName}: 'delete is my favorite key too'`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 8. Duration > 2 hours
|
|
95
|
+
if (duration > 7_200_000) {
|
|
96
|
+
return `@${botName}: 'do you even go outside?'`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// 9. Duration > 1 hour
|
|
100
|
+
if (duration > 3_600_000) {
|
|
101
|
+
return `@${botName}: 'still going huh'`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 10. Duration < 1 minute
|
|
105
|
+
if (duration < 60_000) {
|
|
106
|
+
return `@${botName}: 'oh here we go again'`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 11. Default — pick one deterministically
|
|
110
|
+
return pick(
|
|
111
|
+
[
|
|
112
|
+
`@${botName}: 'looking good'`,
|
|
113
|
+
`@${botName}: 'LGTM'`,
|
|
114
|
+
`@${botName}: 'carry on'`,
|
|
115
|
+
],
|
|
116
|
+
seed,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* @param {object} data - Parsed stdin JSON from Claude Code
|
|
122
|
+
* @param {object} config - Per-segment config from theme
|
|
123
|
+
* @returns {{text: string, style: string}}
|
|
124
|
+
*/
|
|
125
|
+
export function render(data, config) {
|
|
126
|
+
const cfg = { ...meta.defaultConfig, ...config };
|
|
127
|
+
|
|
128
|
+
// Only update the message every 5 renders to prevent flickering
|
|
129
|
+
if (renderCount % 5 === 0) {
|
|
130
|
+
const seed = Math.floor(renderCount / 5);
|
|
131
|
+
lastMessage = resolveMessage(data, cfg.botName, seed);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
renderCount++;
|
|
135
|
+
|
|
136
|
+
return { text: lastMessage, style: cfg.style };
|
|
137
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// src/segments/custom-text.js — User-configurable static text segment
|
|
2
|
+
// Zero dependencies. Node 18+ ESM.
|
|
3
|
+
|
|
4
|
+
export const meta = {
|
|
5
|
+
name: 'custom-text',
|
|
6
|
+
description: 'Shows a user-configurable static text string',
|
|
7
|
+
requires: [],
|
|
8
|
+
defaultConfig: {
|
|
9
|
+
style: 'dim',
|
|
10
|
+
text: '',
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {object} _data - Parsed stdin JSON from Claude Code (unused)
|
|
16
|
+
* @param {object} config - Per-segment config from theme
|
|
17
|
+
* @returns {{text: string, style: string}|null}
|
|
18
|
+
*/
|
|
19
|
+
export function render(_data, config) {
|
|
20
|
+
const cfg = { ...meta.defaultConfig, ...config };
|
|
21
|
+
|
|
22
|
+
if (!cfg.text) return null;
|
|
23
|
+
|
|
24
|
+
return { text: cfg.text, style: cfg.style };
|
|
25
|
+
}
|