@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,75 @@
|
|
|
1
|
+
// src/segments/directory.js — Current working directory display
|
|
2
|
+
// Zero dependencies. Node 18+ ESM.
|
|
3
|
+
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
|
|
6
|
+
export const meta = {
|
|
7
|
+
name: 'directory',
|
|
8
|
+
description: 'Shows the current working directory',
|
|
9
|
+
requires: [],
|
|
10
|
+
defaultConfig: {
|
|
11
|
+
style: 'white',
|
|
12
|
+
format: 'basename',
|
|
13
|
+
icon: false,
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Fish-style path abbreviation: abbreviate all path components except the last
|
|
19
|
+
* to their first character, and replace $HOME with ~.
|
|
20
|
+
*
|
|
21
|
+
* Example: /Users/npow/code/myproject -> ~/c/m/myproject
|
|
22
|
+
*
|
|
23
|
+
* @param {string} dirPath - Full directory path
|
|
24
|
+
* @returns {string}
|
|
25
|
+
*/
|
|
26
|
+
function fishFormat(dirPath) {
|
|
27
|
+
const home = homedir();
|
|
28
|
+
let p = dirPath;
|
|
29
|
+
|
|
30
|
+
// Replace home dir with ~
|
|
31
|
+
if (p === home) return '~';
|
|
32
|
+
if (p.startsWith(home + '/')) {
|
|
33
|
+
p = '~' + p.slice(home.length);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const parts = p.split('/');
|
|
37
|
+
if (parts.length <= 1) return p;
|
|
38
|
+
|
|
39
|
+
// Abbreviate all components except the last to their first character
|
|
40
|
+
const abbreviated = parts.map((part, i) => {
|
|
41
|
+
if (i === parts.length - 1) return part;
|
|
42
|
+
if (part === '~') return '~';
|
|
43
|
+
if (part === '') return '';
|
|
44
|
+
return part[0];
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
return abbreviated.join('/');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @param {object} data - Parsed stdin JSON from Claude Code
|
|
52
|
+
* @param {object} config - Per-segment config from theme
|
|
53
|
+
* @returns {{text: string, style: string}|null}
|
|
54
|
+
*/
|
|
55
|
+
export function render(data, config) {
|
|
56
|
+
const cfg = { ...meta.defaultConfig, ...config };
|
|
57
|
+
|
|
58
|
+
const dirPath = data?.workspace?.current_dir;
|
|
59
|
+
if (!dirPath) return null;
|
|
60
|
+
|
|
61
|
+
let display;
|
|
62
|
+
if (cfg.format === 'full') {
|
|
63
|
+
display = dirPath;
|
|
64
|
+
} else if (cfg.format === 'fish') {
|
|
65
|
+
display = fishFormat(dirPath);
|
|
66
|
+
} else {
|
|
67
|
+
// basename
|
|
68
|
+
const parts = dirPath.split('/');
|
|
69
|
+
display = parts[parts.length - 1] || dirPath;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const text = cfg.icon ? `\uF115 ${display}` : display;
|
|
73
|
+
|
|
74
|
+
return { text, style: cfg.style };
|
|
75
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// src/segments/emoji-story.js — Summarize the session as a growing sequence of emojis
|
|
2
|
+
// Zero dependencies. Node 18+ ESM.
|
|
3
|
+
//
|
|
4
|
+
// On each render, checks conditions in priority order and appends the FIRST
|
|
5
|
+
// matching emoji that isn't already the last element of the story array.
|
|
6
|
+
// The story grows over time, capped at maxLength (default 12).
|
|
7
|
+
// Module-level state: the story array persists across renders within a process.
|
|
8
|
+
|
|
9
|
+
/** @type {string[]} */
|
|
10
|
+
const story = [];
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Condition table — checked in order, first match wins.
|
|
14
|
+
* Each entry: { test(data) -> boolean, emoji: string }
|
|
15
|
+
*/
|
|
16
|
+
const CONDITIONS = [
|
|
17
|
+
{
|
|
18
|
+
test: (d) => (d?.cost?.total_lines_added ?? 0) > 0,
|
|
19
|
+
emoji: '\u{1F4DD}', // 📝
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
test: (d) => (d?.cost?.total_lines_added ?? 0) > 50,
|
|
23
|
+
emoji: '\u270F\uFE0F', // ✏️
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
test: (d) =>
|
|
27
|
+
(d?.cost?.total_lines_removed ?? 0) > (d?.cost?.total_lines_added ?? 0),
|
|
28
|
+
emoji: '\u{1F5D1}\uFE0F', // 🗑️
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
test: (d) => (d?.cost?.total_lines_added ?? 0) > 200,
|
|
32
|
+
emoji: '\u{1F3D7}\uFE0F', // 🏗️
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
test: (d) => (d?.cost?.total_cost_usd ?? 0) > 5,
|
|
36
|
+
emoji: '\u{1F4B0}', // 💰
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
test: (d) => (d?.context_window?.used_percentage ?? 0) >= 50,
|
|
40
|
+
emoji: '\u23F3', // ⏳
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
test: (d) => (d?.context_window?.used_percentage ?? 0) >= 80,
|
|
44
|
+
emoji: '\u{1F525}', // 🔥
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
test: (d) => (d?.context_window?.used_percentage ?? 0) >= 95,
|
|
48
|
+
emoji: '\u{1F480}', // 💀
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
test: (d) => (d?.cost?.total_duration_ms ?? 0) > 1_800_000,
|
|
52
|
+
emoji: '\u2615', // ☕
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
test: (d) => (d?.cost?.total_duration_ms ?? 0) > 3_600_000,
|
|
56
|
+
emoji: '\u{1F3C3}', // 🏃
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
export const meta = {
|
|
61
|
+
name: 'emoji-story',
|
|
62
|
+
description: 'Summarize the session as a growing sequence of emojis',
|
|
63
|
+
requires: [],
|
|
64
|
+
defaultConfig: {
|
|
65
|
+
style: 'dim',
|
|
66
|
+
maxLength: 12,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @param {object} data - Parsed stdin JSON from Claude Code
|
|
72
|
+
* @param {object} config - Per-segment config from theme
|
|
73
|
+
* @returns {{text: string, style: string}|null}
|
|
74
|
+
*/
|
|
75
|
+
export function render(data, config) {
|
|
76
|
+
const cfg = { ...meta.defaultConfig, ...config };
|
|
77
|
+
const maxLen = cfg.maxLength ?? 12;
|
|
78
|
+
const last = story.length > 0 ? story[story.length - 1] : null;
|
|
79
|
+
|
|
80
|
+
// Find the first matching condition whose emoji isn't already the last one
|
|
81
|
+
for (const cond of CONDITIONS) {
|
|
82
|
+
if (cond.test(data) && cond.emoji !== last) {
|
|
83
|
+
story.push(cond.emoji);
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Cap length by shifting from front
|
|
89
|
+
while (story.length > maxLen) {
|
|
90
|
+
story.shift();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (story.length === 0) return null;
|
|
94
|
+
|
|
95
|
+
const text = story.join('');
|
|
96
|
+
const style = cfg.style || 'dim';
|
|
97
|
+
|
|
98
|
+
return { text, style };
|
|
99
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// src/segments/flex-space.js — Flex-space marker for right-alignment
|
|
2
|
+
// Zero dependencies. Node 18+ ESM.
|
|
3
|
+
//
|
|
4
|
+
// The compositor recognises the magic "__FLEX__" token and replaces it
|
|
5
|
+
// with the necessary padding to push subsequent segments to the right.
|
|
6
|
+
// For now, the segment simply emits the marker.
|
|
7
|
+
|
|
8
|
+
export const meta = {
|
|
9
|
+
name: 'flex-space',
|
|
10
|
+
description: 'Emits a marker the compositor uses for right-alignment',
|
|
11
|
+
requires: [],
|
|
12
|
+
defaultConfig: {
|
|
13
|
+
style: '',
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {object} _data - Parsed stdin JSON from Claude Code (unused)
|
|
19
|
+
* @param {object} config - Per-segment config from theme
|
|
20
|
+
* @returns {{text: string, style: string}}
|
|
21
|
+
*/
|
|
22
|
+
export function render(_data, config) {
|
|
23
|
+
const cfg = { ...meta.defaultConfig, ...config };
|
|
24
|
+
return { text: '__FLEX__', style: cfg.style };
|
|
25
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// src/segments/fortune-cookie.js — Developer-themed fortune cookie wisdom
|
|
2
|
+
// Zero dependencies. Node 18+ ESM.
|
|
3
|
+
//
|
|
4
|
+
// Fortune selection:
|
|
5
|
+
// - Uses session_id as a seed when available (deterministic per session).
|
|
6
|
+
// - Falls back to a module-level random pick done once at import time.
|
|
7
|
+
// - Rotates to a new fortune every `rotateEvery` renders (default: 10).
|
|
8
|
+
|
|
9
|
+
export const meta = {
|
|
10
|
+
name: 'fortune-cookie',
|
|
11
|
+
description: 'Shows a rotating developer-themed fortune cookie message',
|
|
12
|
+
requires: [],
|
|
13
|
+
defaultConfig: {
|
|
14
|
+
style: 'dim italic',
|
|
15
|
+
showEmoji: true,
|
|
16
|
+
rotateEvery: 10,
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const FORTUNES = [
|
|
21
|
+
'The bug you seek is in the file you haven\'t read',
|
|
22
|
+
'A commit a day keeps the rebase away',
|
|
23
|
+
'There are only two hard things: cache invalidation, naming things, and off-by-one errors',
|
|
24
|
+
'The best code is no code at all',
|
|
25
|
+
'Legacy code is just code without tests',
|
|
26
|
+
'"It works on my machine" is not a deployment strategy',
|
|
27
|
+
'First, solve the problem. Then, write the code.',
|
|
28
|
+
'Code is read more often than it is written',
|
|
29
|
+
'Weeks of coding can save you hours of planning',
|
|
30
|
+
'There is no cloud, only someone else\'s computer',
|
|
31
|
+
'A well-placed log statement is worth a thousand debugger sessions',
|
|
32
|
+
'The fastest algorithm is the one you don\'t run',
|
|
33
|
+
'Today\'s TODO is tomorrow\'s tech debt',
|
|
34
|
+
'Delete code with confidence; version control remembers',
|
|
35
|
+
'Ship it, then fix it — but actually fix it',
|
|
36
|
+
'The tests you skip today are the bugs you debug tomorrow',
|
|
37
|
+
'Every regex problem creates two problems',
|
|
38
|
+
'Your future self is your most important code reviewer',
|
|
39
|
+
'Production is the only staging environment that matters',
|
|
40
|
+
'Simplicity is the ultimate sophistication, especially in code',
|
|
41
|
+
'If it hurts, do it more often — that\'s what CI is for',
|
|
42
|
+
'Premature optimization is the root of all evil, but so is premature abstraction',
|
|
43
|
+
'git push --force and you shall receive... merge conflicts',
|
|
44
|
+
'The best error message is the one that never shows up',
|
|
45
|
+
'A function should do one thing, and do it well',
|
|
46
|
+
'Comments lie; code doesn\'t. But types are even more honest.',
|
|
47
|
+
'The hardest bugs to fix are the ones you can\'t reproduce',
|
|
48
|
+
'You are not your code. Your code is not you. Ship it.',
|
|
49
|
+
'Good code is its own best documentation',
|
|
50
|
+
'Measure twice, deploy once',
|
|
51
|
+
'There\'s no shame in reading the docs',
|
|
52
|
+
'If you can\'t explain it simply, you don\'t understand it well enough to code it',
|
|
53
|
+
'The best time to refactor was yesterday. The second best time is now.',
|
|
54
|
+
'Any sufficiently advanced configuration is indistinguishable from code',
|
|
55
|
+
'The code compiles; therefore, ship it',
|
|
56
|
+
'A linter catches what pride misses',
|
|
57
|
+
'Debugging is like being a detective in a crime movie where you are also the murderer',
|
|
58
|
+
'Never trust user input. Never trust your own input either.',
|
|
59
|
+
'Workarounds are just features with low self-esteem',
|
|
60
|
+
'One does not simply deploy on Friday',
|
|
61
|
+
'The database is always the bottleneck. Always.',
|
|
62
|
+
'Naming variables well is an act of kindness to your future self',
|
|
63
|
+
'console.log-driven development: timeless, reliable, shameless',
|
|
64
|
+
'All abstractions are leaky. Budget for the drip.',
|
|
65
|
+
'Your dependencies have dependencies. It\'s turtles all the way down.',
|
|
66
|
+
'The PR that\'s "almost ready" is never almost ready',
|
|
67
|
+
'One cannot simply grep their way out of a design problem',
|
|
68
|
+
'The best meetings are pull request reviews',
|
|
69
|
+
'Every line you don\'t write is a line you don\'t have to maintain',
|
|
70
|
+
'The build is red. It is always red. We ship anyway.',
|
|
71
|
+
'Sleep on it. The bug will still be there tomorrow, but you\'ll be smarter.',
|
|
72
|
+
'Copying from Stack Overflow is research. Citing it is professionalism.',
|
|
73
|
+
'The fastest code is the code that never runs',
|
|
74
|
+
'Feature flags: Schrodinger\'s deployment',
|
|
75
|
+
'There are two types of software: the kind people complain about, and the kind nobody uses',
|
|
76
|
+
'If your tests pass on the first try, your tests are wrong',
|
|
77
|
+
'A monolith is just a microservice that kept eating',
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
const EMOJI = '\u{1F960}'; // fortune cookie emoji
|
|
81
|
+
|
|
82
|
+
// Module-level fallback seed (used when session_id is not available).
|
|
83
|
+
// Computed once at import time so it remains stable across renders.
|
|
84
|
+
const FALLBACK_SEED = Math.floor(Math.random() * FORTUNES.length);
|
|
85
|
+
|
|
86
|
+
// Module-level render counter for rotation tracking
|
|
87
|
+
let renderCount = 0;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Simple string-to-integer hash. Produces a non-negative integer from a
|
|
91
|
+
* string, used to derive a deterministic fortune index from session_id.
|
|
92
|
+
*
|
|
93
|
+
* @param {string} str
|
|
94
|
+
* @returns {number}
|
|
95
|
+
*/
|
|
96
|
+
function hashString(str) {
|
|
97
|
+
let hash = 5381;
|
|
98
|
+
for (let i = 0; i < str.length; i++) {
|
|
99
|
+
// hash * 33 + charCode (djb2)
|
|
100
|
+
hash = ((hash << 5) + hash + str.charCodeAt(i)) | 0;
|
|
101
|
+
}
|
|
102
|
+
return Math.abs(hash);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* @param {object} data - Parsed stdin JSON from Claude Code
|
|
107
|
+
* @param {object} config - Per-segment config from theme
|
|
108
|
+
* @returns {{text: string, style: string}}
|
|
109
|
+
*/
|
|
110
|
+
export function render(data, config) {
|
|
111
|
+
const cfg = { ...meta.defaultConfig, ...config };
|
|
112
|
+
const rotateEvery = cfg.rotateEvery > 0 ? cfg.rotateEvery : 10;
|
|
113
|
+
|
|
114
|
+
// Derive a base seed: session_id when available, otherwise the module-level fallback
|
|
115
|
+
const sessionId = data?.session_id;
|
|
116
|
+
const baseSeed = sessionId ? hashString(sessionId) : FALLBACK_SEED;
|
|
117
|
+
|
|
118
|
+
// Rotation offset: advances by 1 every `rotateEvery` renders
|
|
119
|
+
const rotation = Math.floor(renderCount / rotateEvery);
|
|
120
|
+
|
|
121
|
+
// Pick a fortune deterministically from (baseSeed + rotation) mod length
|
|
122
|
+
const index = (baseSeed + rotation) % FORTUNES.length;
|
|
123
|
+
const fortune = FORTUNES[index];
|
|
124
|
+
|
|
125
|
+
renderCount++;
|
|
126
|
+
|
|
127
|
+
const prefix = cfg.showEmoji ? `${EMOJI} ` : '';
|
|
128
|
+
const text = `${prefix}${fortune}`;
|
|
129
|
+
|
|
130
|
+
return { text, style: cfg.style };
|
|
131
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// src/segments/garden.js — ASCII plants that grow as you code
|
|
2
|
+
// Zero dependencies. Node 18+ ESM.
|
|
3
|
+
//
|
|
4
|
+
// Growth stages (based on total_lines_added):
|
|
5
|
+
// 0 lines: (.) — seed dim
|
|
6
|
+
// 1-49: (,) — sprouting dim
|
|
7
|
+
// 50-99: (Y) — seedling green
|
|
8
|
+
// 100-199: (🌱) — small plant green
|
|
9
|
+
// 200-349: (🌿) — growing bold green
|
|
10
|
+
// 350-499: (🌻) — flowering bold yellow
|
|
11
|
+
// 500+: (🌳) — full tree bold green
|
|
12
|
+
//
|
|
13
|
+
// Override: context >= 95% → (🥀) wilted, red
|
|
14
|
+
|
|
15
|
+
const STAGES = [
|
|
16
|
+
{ min: 500, text: '(🌳)', style: 'bold green' },
|
|
17
|
+
{ min: 350, text: '(🌻)', style: 'bold yellow' },
|
|
18
|
+
{ min: 200, text: '(🌿)', style: 'bold green' },
|
|
19
|
+
{ min: 100, text: '(🌱)', style: 'green' },
|
|
20
|
+
{ min: 50, text: '(Y)', style: 'green' },
|
|
21
|
+
{ min: 1, text: '(,)', style: 'dim' },
|
|
22
|
+
{ min: 0, text: '(.)', style: 'dim' },
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const WILTED = { text: '(🥀)', style: 'red' };
|
|
26
|
+
|
|
27
|
+
export const meta = {
|
|
28
|
+
name: 'garden',
|
|
29
|
+
description: 'ASCII plants that grow with each 50 lines of code added',
|
|
30
|
+
requires: [],
|
|
31
|
+
defaultConfig: {
|
|
32
|
+
style: '',
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {object} data - Parsed stdin JSON from Claude Code
|
|
38
|
+
* @param {object} config - Per-segment config from theme
|
|
39
|
+
* @returns {{text: string, style: string}}
|
|
40
|
+
*/
|
|
41
|
+
export function render(data, config) {
|
|
42
|
+
const cfg = { ...meta.defaultConfig, ...config };
|
|
43
|
+
|
|
44
|
+
const contextPct = data?.context_window?.used_percentage ?? 0;
|
|
45
|
+
|
|
46
|
+
// Context >= 95% overrides everything — plant is wilted
|
|
47
|
+
if (contextPct >= 95) {
|
|
48
|
+
return { text: WILTED.text, style: cfg.style || WILTED.style };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const linesAdded = data?.cost?.total_lines_added ?? 0;
|
|
52
|
+
|
|
53
|
+
// Find the first stage whose minimum is met (sorted highest-first)
|
|
54
|
+
const stage = STAGES.find((s) => linesAdded >= s.min) ?? STAGES[STAGES.length - 1];
|
|
55
|
+
|
|
56
|
+
return { text: stage.text, style: cfg.style || stage.style };
|
|
57
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// src/segments/git-branch.js — Current git branch name
|
|
2
|
+
// Zero dependencies. Node 18+ ESM.
|
|
3
|
+
|
|
4
|
+
import { cachedExec } from '../cache.js';
|
|
5
|
+
|
|
6
|
+
export const meta = {
|
|
7
|
+
name: 'git-branch',
|
|
8
|
+
description: 'Shows the current git branch name',
|
|
9
|
+
requires: ['git'],
|
|
10
|
+
defaultConfig: {
|
|
11
|
+
style: 'green',
|
|
12
|
+
icon: false,
|
|
13
|
+
maxLength: 30,
|
|
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 branch = cachedExec('git-branch', 'git rev-parse --abbrev-ref HEAD');
|
|
26
|
+
if (!branch) return null;
|
|
27
|
+
|
|
28
|
+
let display = branch;
|
|
29
|
+
if (display.length > cfg.maxLength) {
|
|
30
|
+
display = display.slice(0, cfg.maxLength - 3) + '...';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const text = cfg.icon ? `\uE0A0 ${display}` : display;
|
|
34
|
+
|
|
35
|
+
return { text, style: cfg.style };
|
|
36
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// src/segments/git-status.js — Git working tree status indicators
|
|
2
|
+
// Zero dependencies. Node 18+ ESM.
|
|
3
|
+
|
|
4
|
+
import { cachedExec } from '../cache.js';
|
|
5
|
+
|
|
6
|
+
export const meta = {
|
|
7
|
+
name: 'git-status',
|
|
8
|
+
description: 'Shows git status indicators: staged, modified, untracked counts',
|
|
9
|
+
requires: ['git'],
|
|
10
|
+
defaultConfig: {
|
|
11
|
+
style: 'yellow',
|
|
12
|
+
format: 'short',
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Parse the trimmed output of `wc -l` to an integer.
|
|
18
|
+
* @param {string} output
|
|
19
|
+
* @returns {number}
|
|
20
|
+
*/
|
|
21
|
+
function parseCount(output) {
|
|
22
|
+
const n = parseInt(output.trim(), 10);
|
|
23
|
+
return Number.isNaN(n) ? 0 : n;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {object} data - Parsed stdin JSON from Claude Code
|
|
28
|
+
* @param {object} config - Per-segment config from theme
|
|
29
|
+
* @returns {{text: string, style: string}|null}
|
|
30
|
+
*/
|
|
31
|
+
export function render(data, config) {
|
|
32
|
+
const cfg = { ...meta.defaultConfig, ...config };
|
|
33
|
+
|
|
34
|
+
const staged = parseCount(cachedExec('git-staged', 'git diff --cached --numstat | wc -l'));
|
|
35
|
+
const modified = parseCount(cachedExec('git-modified', 'git diff --numstat | wc -l'));
|
|
36
|
+
const untracked = parseCount(cachedExec('git-untracked', 'git ls-files --others --exclude-standard | wc -l'));
|
|
37
|
+
|
|
38
|
+
if (staged === 0 && modified === 0 && untracked === 0) return null;
|
|
39
|
+
|
|
40
|
+
let text;
|
|
41
|
+
if (cfg.format === 'detailed') {
|
|
42
|
+
const parts = [];
|
|
43
|
+
if (staged > 0) parts.push(`staged:${staged}`);
|
|
44
|
+
if (modified > 0) parts.push(`mod:${modified}`);
|
|
45
|
+
if (untracked > 0) parts.push(`new:${untracked}`);
|
|
46
|
+
text = parts.join(' ');
|
|
47
|
+
} else {
|
|
48
|
+
const parts = [];
|
|
49
|
+
if (staged > 0) parts.push(`+${staged}`);
|
|
50
|
+
if (modified > 0) parts.push(`~${modified}`);
|
|
51
|
+
if (untracked > 0) parts.push(`?${untracked}`);
|
|
52
|
+
text = parts.join(' ');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return { text, style: cfg.style };
|
|
56
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// src/segments/horoscope.js — Coding horoscope: daily prediction based on day-of-week and session metrics
|
|
2
|
+
// Zero dependencies. Node 18+ ESM.
|
|
3
|
+
//
|
|
4
|
+
// Sign selection: day of month mapped to zodiac sign (1-2=Aries, 3-4=Taurus, ..., 23-24=Pisces, 25+=cycles).
|
|
5
|
+
// Prediction selection: deterministic hash of (dayOfYear + signIndex) picks from 36 horoscope templates.
|
|
6
|
+
// Same sign and prediction all day for a given calendar date.
|
|
7
|
+
|
|
8
|
+
export const meta = {
|
|
9
|
+
name: 'horoscope',
|
|
10
|
+
description: 'Daily coding horoscope based on zodiac sign derived from the day of the month',
|
|
11
|
+
requires: [],
|
|
12
|
+
defaultConfig: {
|
|
13
|
+
style: 'dim italic',
|
|
14
|
+
showSign: true,
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const ZODIAC = [
|
|
19
|
+
{ name: 'Aries', symbol: '\u2648' },
|
|
20
|
+
{ name: 'Taurus', symbol: '\u2649' },
|
|
21
|
+
{ name: 'Gemini', symbol: '\u264A' },
|
|
22
|
+
{ name: 'Cancer', symbol: '\u264B' },
|
|
23
|
+
{ name: 'Leo', symbol: '\u264C' },
|
|
24
|
+
{ name: 'Virgo', symbol: '\u264D' },
|
|
25
|
+
{ name: 'Libra', symbol: '\u264E' },
|
|
26
|
+
{ name: 'Scorpio', symbol: '\u264F' },
|
|
27
|
+
{ name: 'Sagittarius', symbol: '\u2650' },
|
|
28
|
+
{ name: 'Capricorn', symbol: '\u2651' },
|
|
29
|
+
{ name: 'Aquarius', symbol: '\u2652' },
|
|
30
|
+
{ name: 'Pisces', symbol: '\u2653' },
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const PREDICTIONS = [
|
|
34
|
+
'Mercury is in retrograde. Avoid force-pushing.',
|
|
35
|
+
'The stars align for a major refactor.',
|
|
36
|
+
'Today favors writing tests over features.',
|
|
37
|
+
'A mysterious bug will reveal itself before lunch.',
|
|
38
|
+
'Your linter will betray you today.',
|
|
39
|
+
'Commit early, commit often. The cosmos demands it.',
|
|
40
|
+
'An unexpected dependency update brings chaos.',
|
|
41
|
+
'The code review gods smile upon you.',
|
|
42
|
+
'Beware of scope creep during the afternoon.',
|
|
43
|
+
'A senior engineer will question your naming conventions.',
|
|
44
|
+
"Today's lucky number: 0-indexed.",
|
|
45
|
+
'Your pull request will be approved on the first try.',
|
|
46
|
+
'Retrograde warning: do not run migrations today.',
|
|
47
|
+
'The tests you skip today will haunt you tomorrow.',
|
|
48
|
+
'A merge conflict is written in your stars.',
|
|
49
|
+
'Your TODO comments will outlive you.',
|
|
50
|
+
'Pair programming brings unexpected breakthroughs.',
|
|
51
|
+
'Avoid premature optimization. The stars are watching.',
|
|
52
|
+
'A forgotten console.log will make it to production.',
|
|
53
|
+
'Today is a good day to update your README.',
|
|
54
|
+
'The deployment pipeline favors the bold.',
|
|
55
|
+
'A type error lurks where you least expect it.',
|
|
56
|
+
'Your git stash holds forgotten treasures.',
|
|
57
|
+
'The standup will be mercifully short today.',
|
|
58
|
+
'An off-by-one error approaches from the east.',
|
|
59
|
+
'Trust your instincts. Revert that last commit.',
|
|
60
|
+
'A rubber duck will solve your hardest problem.',
|
|
61
|
+
'Null checks in your future. Many null checks.',
|
|
62
|
+
'The intern will find a bug you missed.',
|
|
63
|
+
'Today your regex will actually work on the first try.',
|
|
64
|
+
'Beware of yak shaving disguised as productivity.',
|
|
65
|
+
'The build will break, but not by your hand.',
|
|
66
|
+
'A legacy system calls out for your attention.',
|
|
67
|
+
'Your branch name will spark joy today.',
|
|
68
|
+
'The documentation you write today saves future-you.',
|
|
69
|
+
'Venus enters your CI/CD house. Deploys go smoothly.',
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Derive the zodiac sign index from the day of the month.
|
|
74
|
+
* Days 1-2 = 0 (Aries), 3-4 = 1 (Taurus), ..., 23-24 = 11 (Pisces), then cycles.
|
|
75
|
+
*
|
|
76
|
+
* @param {number} dayOfMonth - 1-31
|
|
77
|
+
* @returns {number} Index into ZODIAC (0-11)
|
|
78
|
+
*/
|
|
79
|
+
function getSignIndex(dayOfMonth) {
|
|
80
|
+
return Math.floor((dayOfMonth - 1) / 2) % ZODIAC.length;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Compute the day of the year (1-366) for a given Date.
|
|
85
|
+
*
|
|
86
|
+
* @param {Date} date
|
|
87
|
+
* @returns {number}
|
|
88
|
+
*/
|
|
89
|
+
function dayOfYear(date) {
|
|
90
|
+
const start = new Date(date.getFullYear(), 0, 0);
|
|
91
|
+
const diff = date - start;
|
|
92
|
+
const oneDay = 86_400_000;
|
|
93
|
+
return Math.floor(diff / oneDay);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Simple deterministic hash: combine dayOfYear and signIndex into an integer.
|
|
98
|
+
* Uses a basic multiplicative hash to spread values across the prediction list.
|
|
99
|
+
*
|
|
100
|
+
* @param {number} doy - Day of year (1-366)
|
|
101
|
+
* @param {number} signIdx - Sign index (0-11)
|
|
102
|
+
* @returns {number} Non-negative integer
|
|
103
|
+
*/
|
|
104
|
+
function deterministicHash(doy, signIdx) {
|
|
105
|
+
// Multiplicative hash with golden-ratio-derived constant.
|
|
106
|
+
// Different enough from simple modulo to avoid obvious patterns.
|
|
107
|
+
let h = (doy * 2654435761 + signIdx * 40503) & 0x7fffffff;
|
|
108
|
+
h = ((h >>> 16) ^ h) & 0x7fffffff;
|
|
109
|
+
return h;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @param {object} data - Parsed stdin JSON from Claude Code
|
|
114
|
+
* @param {object} config - Per-segment config from theme
|
|
115
|
+
* @returns {{text: string, style: string}|null}
|
|
116
|
+
*/
|
|
117
|
+
export function render(data, config) {
|
|
118
|
+
const cfg = { ...meta.defaultConfig, ...config };
|
|
119
|
+
|
|
120
|
+
const now = new Date();
|
|
121
|
+
const dom = now.getDate();
|
|
122
|
+
const doy = dayOfYear(now);
|
|
123
|
+
|
|
124
|
+
const signIdx = getSignIndex(dom);
|
|
125
|
+
const sign = ZODIAC[signIdx];
|
|
126
|
+
|
|
127
|
+
const hash = deterministicHash(doy, signIdx);
|
|
128
|
+
const prediction = PREDICTIONS[hash % PREDICTIONS.length];
|
|
129
|
+
|
|
130
|
+
const prefix = cfg.showSign ? `${sign.symbol} ` : '';
|
|
131
|
+
const text = `${prefix}${prediction}`;
|
|
132
|
+
|
|
133
|
+
return { text, style: cfg.style };
|
|
134
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Built-in segment registry
|
|
2
|
+
// Each segment is imported and registered by its meta.name.
|
|
3
|
+
|
|
4
|
+
import * as modelName from './model-name.js';
|
|
5
|
+
import * as contextBar from './context-bar.js';
|
|
6
|
+
import * as contextPercent from './context-percent.js';
|
|
7
|
+
import * as contextTokens from './context-tokens.js';
|
|
8
|
+
import * as sessionCost from './session-cost.js';
|
|
9
|
+
import * as costBudget from './cost-budget.js';
|
|
10
|
+
import * as gitBranch from './git-branch.js';
|
|
11
|
+
import * as gitStatus from './git-status.js';
|
|
12
|
+
import * as directory from './directory.js';
|
|
13
|
+
import * as sessionTimer from './session-timer.js';
|
|
14
|
+
import * as apiTimer from './api-timer.js';
|
|
15
|
+
import * as linesChanged from './lines-changed.js';
|
|
16
|
+
import * as vimMode from './vim-mode.js';
|
|
17
|
+
import * as version from './version.js';
|
|
18
|
+
import * as outputStyle from './output-style.js';
|
|
19
|
+
import * as separatorPipe from './separator-pipe.js';
|
|
20
|
+
import * as separatorArrow from './separator-arrow.js';
|
|
21
|
+
import * as separatorSpace from './separator-space.js';
|
|
22
|
+
import * as flexSpace from './flex-space.js';
|
|
23
|
+
import * as customText from './custom-text.js';
|
|
24
|
+
import * as tamagotchi from './tamagotchi.js';
|
|
25
|
+
import * as smartNudge from './smart-nudge.js';
|
|
26
|
+
import * as narrator from './narrator.js';
|
|
27
|
+
import * as streak from './streak.js';
|
|
28
|
+
import * as soundtrack from './soundtrack.js';
|
|
29
|
+
import * as vibeCheck from './vibe-check.js';
|
|
30
|
+
import * as achievement from './achievement.js';
|
|
31
|
+
import * as tokenSparkline from './token-sparkline.js';
|
|
32
|
+
import * as fortuneCookie from './fortune-cookie.js';
|
|
33
|
+
import * as horoscope from './horoscope.js';
|
|
34
|
+
import * as coworker from './coworker.js';
|
|
35
|
+
import * as commitMsg from './commit-msg.js';
|
|
36
|
+
import * as garden from './garden.js';
|
|
37
|
+
import * as coffeeCup from './coffee-cup.js';
|
|
38
|
+
import * as battleLog from './battle-log.js';
|
|
39
|
+
import * as cat from './cat.js';
|
|
40
|
+
import * as weatherReport from './weather-report.js';
|
|
41
|
+
import * as emojiStory from './emoji-story.js';
|
|
42
|
+
import * as speedrun from './speedrun.js';
|
|
43
|
+
import * as stockTicker from './stock-ticker.js';
|
|
44
|
+
import * as rpgStats from './rpg-stats.js';
|
|
45
|
+
|
|
46
|
+
const allSegments = [
|
|
47
|
+
modelName, contextBar, contextPercent, contextTokens,
|
|
48
|
+
sessionCost, costBudget, gitBranch, gitStatus,
|
|
49
|
+
directory, sessionTimer, apiTimer, linesChanged,
|
|
50
|
+
vimMode, version, outputStyle,
|
|
51
|
+
separatorPipe, separatorArrow, separatorSpace, flexSpace, customText,
|
|
52
|
+
tamagotchi, vibeCheck, achievement, tokenSparkline,
|
|
53
|
+
smartNudge, narrator, streak, soundtrack, fortuneCookie, horoscope,
|
|
54
|
+
coworker, commitMsg, garden, coffeeCup,
|
|
55
|
+
battleLog, cat, weatherReport,
|
|
56
|
+
emojiStory, speedrun,
|
|
57
|
+
stockTicker, rpgStats,
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
export const segments = {};
|
|
61
|
+
for (const seg of allSegments) {
|
|
62
|
+
if (seg.meta?.name) {
|
|
63
|
+
segments[seg.meta.name] = seg;
|
|
64
|
+
}
|
|
65
|
+
}
|