@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.
Files changed (57) hide show
  1. package/README.md +317 -0
  2. package/bin/omc.js +403 -0
  3. package/docs/architecture.md +198 -0
  4. package/docs/segment-contract.md +186 -0
  5. package/docs/theme-format.md +156 -0
  6. package/package.json +35 -0
  7. package/src/cache.js +102 -0
  8. package/src/color.js +105 -0
  9. package/src/compositor.js +163 -0
  10. package/src/config.js +146 -0
  11. package/src/plugins.js +72 -0
  12. package/src/runner.js +160 -0
  13. package/src/segments/achievement.js +68 -0
  14. package/src/segments/api-timer.js +55 -0
  15. package/src/segments/battle-log.js +55 -0
  16. package/src/segments/cat.js +89 -0
  17. package/src/segments/coffee-cup.js +81 -0
  18. package/src/segments/commit-msg.js +95 -0
  19. package/src/segments/context-bar.js +50 -0
  20. package/src/segments/context-percent.js +40 -0
  21. package/src/segments/context-tokens.js +52 -0
  22. package/src/segments/cost-budget.js +43 -0
  23. package/src/segments/coworker.js +137 -0
  24. package/src/segments/custom-text.js +25 -0
  25. package/src/segments/directory.js +75 -0
  26. package/src/segments/emoji-story.js +99 -0
  27. package/src/segments/flex-space.js +25 -0
  28. package/src/segments/fortune-cookie.js +131 -0
  29. package/src/segments/garden.js +57 -0
  30. package/src/segments/git-branch.js +36 -0
  31. package/src/segments/git-status.js +56 -0
  32. package/src/segments/horoscope.js +134 -0
  33. package/src/segments/index.js +65 -0
  34. package/src/segments/lines-changed.js +29 -0
  35. package/src/segments/model-name.js +28 -0
  36. package/src/segments/narrator.js +129 -0
  37. package/src/segments/output-style.js +25 -0
  38. package/src/segments/rpg-stats.js +119 -0
  39. package/src/segments/separator-arrow.js +22 -0
  40. package/src/segments/separator-pipe.js +22 -0
  41. package/src/segments/separator-space.js +22 -0
  42. package/src/segments/session-cost.js +72 -0
  43. package/src/segments/session-timer.js +53 -0
  44. package/src/segments/smart-nudge.js +97 -0
  45. package/src/segments/soundtrack.js +133 -0
  46. package/src/segments/speedrun.js +94 -0
  47. package/src/segments/stock-ticker.js +71 -0
  48. package/src/segments/streak.js +131 -0
  49. package/src/segments/tamagotchi.js +95 -0
  50. package/src/segments/token-sparkline.js +73 -0
  51. package/src/segments/version.js +27 -0
  52. package/src/segments/vibe-check.js +109 -0
  53. package/src/segments/vim-mode.js +29 -0
  54. package/src/segments/weather-report.js +88 -0
  55. package/themes/default.json +59 -0
  56. package/themes/minimal.json +37 -0
  57. package/themes/powerline.json +73 -0
@@ -0,0 +1,109 @@
1
+ // src/segments/vibe-check.js — Session mood indicator derived from metrics
2
+ // Zero dependencies. Node 18+ ESM.
3
+
4
+ export const meta = {
5
+ name: 'vibe-check',
6
+ description: 'Shows a one-word mood indicator derived from session metrics',
7
+ requires: [],
8
+ defaultConfig: {
9
+ style: '',
10
+ showEmoji: true,
11
+ },
12
+ };
13
+
14
+ /**
15
+ * Vibes in priority order (highest wins). Each entry:
16
+ * test(data) — returns true when this vibe applies
17
+ * emoji — emoji prefix
18
+ * label — text label
19
+ * style — ANSI style string
20
+ */
21
+ const VIBES = [
22
+ {
23
+ test: (d) => (d?.context_window?.used_percentage ?? 0) >= 80,
24
+ emoji: '\u{1F9D8}',
25
+ label: 'time to compact',
26
+ style: 'bold red',
27
+ },
28
+ {
29
+ test: (d) => (d?.context_window?.used_percentage ?? 0) >= 65,
30
+ emoji: '\u{1F630}',
31
+ label: 'sweating',
32
+ style: 'bold yellow',
33
+ },
34
+ {
35
+ test: (d) => (d?.cost?.total_cost_usd ?? 0) >= 15,
36
+ emoji: '\u{1F4B8}',
37
+ label: 'burning cash',
38
+ style: 'bold magenta',
39
+ },
40
+ {
41
+ test: (d) =>
42
+ (d?.cost?.total_lines_added ?? 0) >= 100 &&
43
+ (d?.context_window?.used_percentage ?? 0) >= 40,
44
+ emoji: '\u{1F525}',
45
+ label: 'cooking',
46
+ style: 'bold yellow',
47
+ },
48
+ {
49
+ test: (d) =>
50
+ (d?.cost?.total_duration_ms ?? 0) >= 1_800_000 &&
51
+ (d?.context_window?.used_percentage ?? 0) < 60,
52
+ emoji: '\u{1F3AF}',
53
+ label: 'locked in',
54
+ style: 'cyan',
55
+ },
56
+ {
57
+ test: (d) => (d?.cost?.total_lines_added ?? 0) >= 200,
58
+ emoji: '\u{1F680}',
59
+ label: 'shipping',
60
+ style: 'bold green',
61
+ },
62
+ {
63
+ test: (d) => (d?.cost?.total_lines_added ?? 0) >= 50,
64
+ emoji: '\u{1F3D7}\uFE0F',
65
+ label: 'building',
66
+ style: 'green',
67
+ },
68
+ {
69
+ test: (d) =>
70
+ (d?.cost?.total_lines_added ?? 0) < 10 &&
71
+ (d?.cost?.total_duration_ms ?? 0) >= 300_000,
72
+ emoji: '\u{1F50D}',
73
+ label: 'exploring',
74
+ style: 'blue',
75
+ },
76
+ {
77
+ test: (d) => (d?.cost?.total_duration_ms ?? 0) < 120_000,
78
+ emoji: '\u2615',
79
+ label: 'warming up',
80
+ style: 'dim',
81
+ },
82
+ ];
83
+
84
+ const DEFAULT_VIBE = {
85
+ emoji: '\u2728',
86
+ label: 'vibing',
87
+ style: 'cyan',
88
+ };
89
+
90
+ /**
91
+ * @param {object} data - Parsed stdin JSON from Claude Code
92
+ * @param {object} config - Per-segment config from theme
93
+ * @returns {{text: string, style: string}}
94
+ */
95
+ export function render(data, config) {
96
+ const cfg = { ...meta.defaultConfig, ...config };
97
+
98
+ // Find the first matching vibe (priority order), or fall back to default
99
+ const vibe = VIBES.find((v) => v.test(data)) ?? DEFAULT_VIBE;
100
+
101
+ const text = cfg.showEmoji
102
+ ? `${vibe.emoji} ${vibe.label}`
103
+ : vibe.label;
104
+
105
+ // Allow per-segment style override; fall back to vibe-specific style
106
+ const style = cfg.style || vibe.style;
107
+
108
+ return { text, style };
109
+ }
@@ -0,0 +1,29 @@
1
+ // src/segments/vim-mode.js — Display current vim mode
2
+ // Zero dependencies. Node 18+ ESM.
3
+
4
+ export const meta = {
5
+ name: 'vim-mode',
6
+ description: 'Shows the current vim mode (NORMAL/INSERT) when vim is active',
7
+ requires: [],
8
+ defaultConfig: {
9
+ styleNormal: 'bold green',
10
+ styleInsert: 'bold blue',
11
+ },
12
+ };
13
+
14
+ /**
15
+ * @param {object} data - Parsed stdin JSON from Claude Code
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
+ const mode = data?.vim?.mode;
23
+ if (!mode) return null;
24
+
25
+ const upper = mode.toUpperCase();
26
+ const style = upper === 'INSERT' ? cfg.styleInsert : cfg.styleNormal;
27
+
28
+ return { text: upper, style };
29
+ }
@@ -0,0 +1,88 @@
1
+ // src/segments/weather-report.js — Session conditions as weather forecast
2
+ // Zero dependencies. Node 18+ ESM.
3
+ //
4
+ // Conditions (priority order):
5
+ // 1. Stormy — context >= 85%
6
+ // 2. Overcast — context >= 65%
7
+ // 3. Hail — cost > $15
8
+ // 4. Windy — lines_added > 200
9
+ // 5. Turbulent — lines_removed > lines_added
10
+ // 6. Partly Cloudy — duration > 1 hour
11
+ // 7. Dawn — duration < 2 minutes
12
+ // 8. Clear Skies — default
13
+
14
+ export const meta = {
15
+ name: 'weather-report',
16
+ description: 'Session conditions as a weather forecast',
17
+ requires: [],
18
+ defaultConfig: {
19
+ style: '',
20
+ },
21
+ };
22
+
23
+ /**
24
+ * Determine the weather condition 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 resolveWeather(data) {
31
+ const context = data?.context_window?.used_percentage ?? 0;
32
+ const cost = data?.cost?.total_cost_usd ?? 0;
33
+ const linesAdded = data?.cost?.total_lines_added ?? 0;
34
+ const linesRemoved = data?.cost?.total_lines_removed ?? 0;
35
+ const totalDuration = data?.cost?.total_duration_ms ?? 0;
36
+
37
+ // 1. Stormy
38
+ if (context >= 85) {
39
+ return { text: '\u26C8\uFE0F Stormy', style: 'bold red' };
40
+ }
41
+
42
+ // 2. Overcast
43
+ if (context >= 65) {
44
+ return { text: '\uD83C\uDF27\uFE0F Overcast', style: 'yellow' };
45
+ }
46
+
47
+ // 3. Hail
48
+ if (cost > 15) {
49
+ return { text: `\uD83C\uDF28\uFE0F Hail ($${cost.toFixed(2)})`, style: 'bold yellow' };
50
+ }
51
+
52
+ // 4. Windy
53
+ if (linesAdded > 200) {
54
+ return { text: `\uD83D\uDCA8 Windy (+${linesAdded})`, style: 'cyan' };
55
+ }
56
+
57
+ // 5. Turbulent
58
+ if (linesRemoved > linesAdded) {
59
+ return { text: '\uD83C\uDF2A\uFE0F Turbulent', style: 'yellow' };
60
+ }
61
+
62
+ // 6. Partly Cloudy
63
+ if (totalDuration > 3_600_000) {
64
+ return { text: '\uD83C\uDF24\uFE0F Partly Cloudy', style: '' };
65
+ }
66
+
67
+ // 7. Dawn
68
+ if (totalDuration < 120_000) {
69
+ return { text: '\uD83C\uDF05 Dawn', style: 'dim' };
70
+ }
71
+
72
+ // 8. Clear Skies
73
+ return { text: '\u2600\uFE0F Clear Skies', style: 'green' };
74
+ }
75
+
76
+ /**
77
+ * @param {object} data - Parsed stdin JSON from Claude Code
78
+ * @param {object} config - Per-segment config from theme
79
+ * @returns {{text: string, style: string}}
80
+ */
81
+ export function render(data, config) {
82
+ const cfg = { ...meta.defaultConfig, ...config };
83
+
84
+ const weather = resolveWeather(data);
85
+ const style = cfg.style || weather.style;
86
+
87
+ return { text: weather.text, style };
88
+ }
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "default",
3
+ "description": "Clean two-line statusline. No special fonts required. Works everywhere.",
4
+ "lines": [
5
+ {
6
+ "left": ["model-name", "directory", "git-branch", "git-status"],
7
+ "right": ["lines-changed", "session-timer"]
8
+ },
9
+ {
10
+ "left": ["context-bar"],
11
+ "right": ["session-cost"]
12
+ }
13
+ ],
14
+ "separator": " ",
15
+ "segments": {
16
+ "model-name": {
17
+ "style": "bold cyan",
18
+ "icon": false
19
+ },
20
+ "directory": {
21
+ "style": "white",
22
+ "format": "basename"
23
+ },
24
+ "git-branch": {
25
+ "style": "green",
26
+ "icon": false,
27
+ "maxLength": 30
28
+ },
29
+ "git-status": {
30
+ "style": "yellow",
31
+ "format": "short"
32
+ },
33
+ "lines-changed": {
34
+ "style": "dim",
35
+ "hideIfZero": true
36
+ },
37
+ "session-timer": {
38
+ "style": "dim"
39
+ },
40
+ "context-bar": {
41
+ "width": 20,
42
+ "charFilled": "\u2593",
43
+ "charEmpty": "\u2591",
44
+ "warnAt": 60,
45
+ "criticalAt": 80,
46
+ "showPercent": true,
47
+ "styleOk": "green",
48
+ "styleWarn": "bold yellow",
49
+ "styleCritical": "bold red"
50
+ },
51
+ "session-cost": {
52
+ "style": "dim",
53
+ "format": "$",
54
+ "plan": null,
55
+ "styleWarn": "bold yellow",
56
+ "styleCritical": "bold red"
57
+ }
58
+ }
59
+ }
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "minimal",
3
+ "description": "Single-line, text-only. No bars, no icons, no fuss.",
4
+ "lines": [
5
+ {
6
+ "left": ["directory", "git-branch", "git-status"],
7
+ "right": ["context-percent", "session-cost"]
8
+ }
9
+ ],
10
+ "separator": " \u00b7 ",
11
+ "segments": {
12
+ "directory": {
13
+ "style": "white",
14
+ "format": "basename"
15
+ },
16
+ "git-branch": {
17
+ "style": "green",
18
+ "icon": false,
19
+ "maxLength": 20
20
+ },
21
+ "git-status": {
22
+ "style": "yellow",
23
+ "format": "short"
24
+ },
25
+ "context-percent": {
26
+ "style": "white",
27
+ "warnAt": 60,
28
+ "criticalAt": 80,
29
+ "styleWarn": "yellow",
30
+ "styleCritical": "red"
31
+ },
32
+ "session-cost": {
33
+ "style": "dim",
34
+ "format": "$"
35
+ }
36
+ }
37
+ }
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "powerline",
3
+ "description": "Full powerline with Nerd Font icons and arrow separators. Requires a Nerd Font.",
4
+ "lines": [
5
+ {
6
+ "left": ["model-name", "separator-arrow", "directory", "separator-arrow", "git-branch", "git-status"],
7
+ "right": ["lines-changed", "separator-arrow", "session-timer"]
8
+ },
9
+ {
10
+ "left": ["context-bar", "separator-arrow", "context-tokens"],
11
+ "right": ["session-cost", "separator-arrow", "vim-mode"]
12
+ }
13
+ ],
14
+ "separator": " ",
15
+ "segments": {
16
+ "model-name": {
17
+ "style": "bold cyan",
18
+ "icon": true
19
+ },
20
+ "directory": {
21
+ "style": "blue",
22
+ "format": "fish",
23
+ "icon": true
24
+ },
25
+ "git-branch": {
26
+ "style": "green",
27
+ "icon": true,
28
+ "maxLength": 25
29
+ },
30
+ "git-status": {
31
+ "style": "yellow",
32
+ "format": "detailed"
33
+ },
34
+ "lines-changed": {
35
+ "style": "dim",
36
+ "hideIfZero": true
37
+ },
38
+ "session-timer": {
39
+ "style": "dim",
40
+ "icon": true
41
+ },
42
+ "context-bar": {
43
+ "width": 15,
44
+ "charFilled": "\u2588",
45
+ "charEmpty": "\u2591",
46
+ "warnAt": 60,
47
+ "criticalAt": 80,
48
+ "showPercent": true,
49
+ "styleOk": "green",
50
+ "styleWarn": "yellow",
51
+ "styleCritical": "red"
52
+ },
53
+ "context-tokens": {
54
+ "style": "dim",
55
+ "format": "short"
56
+ },
57
+ "session-cost": {
58
+ "style": "white",
59
+ "format": "$",
60
+ "warnAt": 5.0,
61
+ "styleWarn": "yellow",
62
+ "icon": true
63
+ },
64
+ "vim-mode": {
65
+ "styleNormal": "bold green",
66
+ "styleInsert": "bold blue"
67
+ },
68
+ "separator-arrow": {
69
+ "style": "dim",
70
+ "char": "\ue0b0"
71
+ }
72
+ }
73
+ }