@owloops/claude-powerline 1.24.3 → 1.25.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 (48) hide show
  1. package/README.md +5 -43
  2. package/dist/browser.d.ts +676 -0
  3. package/dist/browser.js +3 -0
  4. package/dist/index.mjs +12 -12
  5. package/package.json +9 -1
  6. package/plugin/templates/config-full.json +1 -1
  7. package/plugin/templates/config-tui-compact.json +3 -3
  8. package/plugin/templates/config-tui-full.json +4 -4
  9. package/plugin/templates/config-tui-standard.json +4 -4
  10. package/src/browser.ts +203 -0
  11. package/src/config/defaults.ts +79 -0
  12. package/src/config/loader.ts +462 -0
  13. package/src/index.ts +90 -0
  14. package/src/powerline.ts +904 -0
  15. package/src/segments/block.ts +31 -0
  16. package/src/segments/context.ts +221 -0
  17. package/src/segments/git.ts +492 -0
  18. package/src/segments/index.ts +25 -0
  19. package/src/segments/metrics.ts +175 -0
  20. package/src/segments/pricing.ts +454 -0
  21. package/src/segments/renderer.ts +796 -0
  22. package/src/segments/session.ts +207 -0
  23. package/src/segments/tmux.ts +35 -0
  24. package/src/segments/today.ts +191 -0
  25. package/src/themes/dark.ts +52 -0
  26. package/src/themes/gruvbox.ts +52 -0
  27. package/src/themes/index.ts +131 -0
  28. package/src/themes/light.ts +52 -0
  29. package/src/themes/nord.ts +52 -0
  30. package/src/themes/rose-pine.ts +52 -0
  31. package/src/themes/tokyo-night.ts +52 -0
  32. package/src/tui/grid.ts +712 -0
  33. package/src/tui/index.ts +4 -0
  34. package/src/tui/layouts.ts +285 -0
  35. package/src/tui/primitives.ts +175 -0
  36. package/src/tui/renderer.ts +206 -0
  37. package/src/tui/sections.ts +1080 -0
  38. package/src/tui/types.ts +181 -0
  39. package/src/utils/budget.ts +47 -0
  40. package/src/utils/cache.ts +247 -0
  41. package/src/utils/claude.ts +489 -0
  42. package/src/utils/color-support.ts +118 -0
  43. package/src/utils/colors.ts +120 -0
  44. package/src/utils/constants.ts +176 -0
  45. package/src/utils/formatters.ts +160 -0
  46. package/src/utils/logger.ts +5 -0
  47. package/src/utils/terminal-width.ts +117 -0
  48. package/src/utils/terminal.ts +11 -0
@@ -0,0 +1,176 @@
1
+ export const RESET_CODE = "\x1b[0m";
2
+
3
+ export const SYMBOLS = {
4
+ right: "\uE0B0",
5
+ left_rounded: "\uE0B6",
6
+ right_rounded: "\uE0B4",
7
+ branch: "⎇",
8
+ model: "✱",
9
+ git_clean: "✓",
10
+ git_dirty: "●",
11
+ git_conflicts: "⚠",
12
+ git_ahead: "↑",
13
+ git_behind: "↓",
14
+ git_worktree: "⧉",
15
+ git_tag: "⌂",
16
+ git_sha: "♯",
17
+ git_upstream: "→",
18
+ git_stash: "⧇",
19
+ git_time: "◷",
20
+ session_cost: "§",
21
+ block_cost: "◱",
22
+ today_cost: "☉",
23
+ context_time: "◔",
24
+ metrics_response: "⧖",
25
+ metrics_last_response: "Δ",
26
+ metrics_duration: "⧗",
27
+ metrics_messages: "◆",
28
+ metrics_lines_added: "+",
29
+ metrics_lines_removed: "-",
30
+ metrics_burn: "↗",
31
+ version: "◈",
32
+ bar_filled: "▪",
33
+ bar_empty: "▫",
34
+ env: "⚙",
35
+ session_id: "⌗",
36
+ weekly_cost: "◑",
37
+ dir: "📁",
38
+ activity: "⚡",
39
+ } as const;
40
+
41
+ export const BOX_CHARS = {
42
+ topLeft: "╭",
43
+ topRight: "╮",
44
+ bottomLeft: "╰",
45
+ bottomRight: "╯",
46
+ horizontal: "─",
47
+ vertical: "│",
48
+ teeLeft: "├",
49
+ teeRight: "┤",
50
+ } as const;
51
+
52
+ export const BOX_CHARS_TEXT = {
53
+ topLeft: "+",
54
+ topRight: "+",
55
+ bottomLeft: "+",
56
+ bottomRight: "+",
57
+ horizontal: "-",
58
+ vertical: "|",
59
+ teeLeft: "+",
60
+ teeRight: "+",
61
+ } as const;
62
+
63
+ import type { BoxChars } from "../tui/types";
64
+
65
+ export const BOX_PRESETS: Record<string, BoxChars> = {
66
+ rounded: BOX_CHARS,
67
+ square: {
68
+ topLeft: "┌",
69
+ topRight: "┐",
70
+ bottomLeft: "└",
71
+ bottomRight: "┘",
72
+ horizontal: "─",
73
+ vertical: "│",
74
+ teeLeft: "├",
75
+ teeRight: "┤",
76
+ },
77
+ heavy: {
78
+ topLeft: "┏",
79
+ topRight: "┓",
80
+ bottomLeft: "┗",
81
+ bottomRight: "┛",
82
+ horizontal: "━",
83
+ vertical: "┃",
84
+ teeLeft: "┣",
85
+ teeRight: "┫",
86
+ },
87
+ double: {
88
+ topLeft: "╔",
89
+ topRight: "╗",
90
+ bottomLeft: "╚",
91
+ bottomRight: "╝",
92
+ horizontal: "═",
93
+ vertical: "║",
94
+ teeLeft: "╠",
95
+ teeRight: "╣",
96
+ },
97
+ dashed: {
98
+ topLeft: "╭",
99
+ topRight: "╮",
100
+ bottomLeft: "╰",
101
+ bottomRight: "╯",
102
+ horizontal: "┄",
103
+ vertical: "┊",
104
+ teeLeft: "├",
105
+ teeRight: "┤",
106
+ },
107
+ "heavy-dashed": {
108
+ topLeft: "┏",
109
+ topRight: "┓",
110
+ bottomLeft: "┗",
111
+ bottomRight: "┛",
112
+ horizontal: "┅",
113
+ vertical: "┇",
114
+ teeLeft: "┣",
115
+ teeRight: "┫",
116
+ },
117
+ mixed: {
118
+ topLeft: "┍",
119
+ topRight: "┑",
120
+ bottomLeft: "┕",
121
+ bottomRight: "┙",
122
+ horizontal: "━",
123
+ vertical: "│",
124
+ teeLeft: "┝",
125
+ teeRight: "┥",
126
+ },
127
+ ascii: BOX_CHARS_TEXT,
128
+ invisible: {
129
+ topLeft: " ",
130
+ topRight: " ",
131
+ bottomLeft: " ",
132
+ bottomRight: " ",
133
+ horizontal: " ",
134
+ vertical: " ",
135
+ teeLeft: " ",
136
+ teeRight: " ",
137
+ },
138
+ };
139
+
140
+ export const TEXT_SYMBOLS = {
141
+ right: "\uE0B0",
142
+ left_rounded: "\uE0B6",
143
+ right_rounded: "\uE0B4",
144
+ branch: "~",
145
+ model: "M",
146
+ git_clean: "=",
147
+ git_dirty: "*",
148
+ git_conflicts: "!",
149
+ git_ahead: "^",
150
+ git_behind: "v",
151
+ git_worktree: "W",
152
+ git_tag: "T",
153
+ git_sha: "#",
154
+ git_upstream: ">>",
155
+ git_stash: "S",
156
+ git_time: "@",
157
+ session_cost: "S",
158
+ block_cost: "B",
159
+ today_cost: "D",
160
+ context_time: "C",
161
+ metrics_response: "R",
162
+ metrics_last_response: "L",
163
+ metrics_duration: "T",
164
+ metrics_messages: "#",
165
+ metrics_lines_added: "+",
166
+ metrics_lines_removed: "-",
167
+ metrics_burn: "~/h",
168
+ version: "V",
169
+ bar_filled: "=",
170
+ bar_empty: "-",
171
+ env: "$",
172
+ session_id: "#",
173
+ weekly_cost: "W",
174
+ dir: "D",
175
+ activity: "A",
176
+ } as const;
@@ -0,0 +1,160 @@
1
+ interface TokenBreakdown {
2
+ input: number;
3
+ output: number;
4
+ cacheCreation: number;
5
+ cacheRead: number;
6
+ }
7
+
8
+ export function formatCost(cost: number | null): string {
9
+ if (cost === null) return "$0.00";
10
+ if (cost < 0.01) return "<$0.01";
11
+ return `$${cost.toFixed(2)}`;
12
+ }
13
+
14
+ export function formatTokens(tokens: number | null): string {
15
+ if (tokens === null) return "0 tokens";
16
+ if (tokens === 0) return "0 tokens";
17
+ if (tokens >= 1_000_000) {
18
+ return `${(tokens / 1_000_000).toFixed(1)}M tokens`;
19
+ } else if (tokens >= 1_000) {
20
+ return `${(tokens / 1_000).toFixed(1)}K tokens`;
21
+ }
22
+ return `${tokens} tokens`;
23
+ }
24
+
25
+ export function formatTokenBreakdown(breakdown: TokenBreakdown | null): string {
26
+ if (!breakdown) return "0 tokens";
27
+
28
+ const parts: string[] = [];
29
+
30
+ if (breakdown.input > 0) {
31
+ parts.push(`${formatTokenCount(breakdown.input)} in`);
32
+ }
33
+
34
+ if (breakdown.output > 0) {
35
+ parts.push(`${formatTokenCount(breakdown.output)} out`);
36
+ }
37
+
38
+ if (breakdown.cacheCreation > 0 || breakdown.cacheRead > 0) {
39
+ const totalCached = breakdown.cacheCreation + breakdown.cacheRead;
40
+ parts.push(`${formatTokenCount(totalCached)} cached`);
41
+ }
42
+
43
+ return parts.length > 0 ? parts.join(" + ") : "0 tokens";
44
+ }
45
+
46
+ export function formatTimeSince(seconds: number): string {
47
+ if (seconds < 60) return `${seconds}s`;
48
+ if (seconds < 3600) return `${Math.floor(seconds / 60)}m`;
49
+ if (seconds < 86400) return `${Math.floor(seconds / 3600)}h`;
50
+ if (seconds < 604800) return `${Math.floor(seconds / 86400)}d`;
51
+ return `${Math.floor(seconds / 604800)}w`;
52
+ }
53
+
54
+ export function formatDuration(seconds: number): string {
55
+ if (seconds < 60) {
56
+ return `${seconds.toFixed(0)}s`;
57
+ } else if (seconds < 3600) {
58
+ return `${(seconds / 60).toFixed(0)}m`;
59
+ } else if (seconds < 86400) {
60
+ return `${(seconds / 3600).toFixed(1)}h`;
61
+ } else {
62
+ return `${(seconds / 86400).toFixed(1)}d`;
63
+ }
64
+ }
65
+
66
+ const CLAUDE_MODEL_PATTERN =
67
+ /^(?:(?:global|apac|au|eu|us|us-east-\d|us-west-\d|eu-west-\d|eu-central-\d)\.)?(?:anthropic\.|azure_ai\/|bedrock\/|vertex_ai\/)?claude-(?:(?<family>opus|sonnet|haiku)-(?<newMajor>\d+)(?:-(?<newMinor>\d))?|(?<oldMajor>\d+)(?:-(?<oldMinor>\d))?-(?<oldFamily>opus|sonnet|haiku))(?:[-@]\d{8})?(?:-v\d+:\d+)?(?:-latest)?$/i;
68
+
69
+ export function formatModelName(rawName: string): string {
70
+ if (!rawName) {
71
+ return "Claude";
72
+ }
73
+
74
+ const match = rawName.trim().match(CLAUDE_MODEL_PATTERN);
75
+ if (!match?.groups) {
76
+ return rawName;
77
+ }
78
+
79
+ const { family, newMajor, newMinor, oldMajor, oldMinor, oldFamily } =
80
+ match.groups;
81
+
82
+ const modelFamily = family || oldFamily;
83
+ const major = newMajor || oldMajor;
84
+ const minor = newMinor || oldMinor;
85
+
86
+ if (modelFamily && major) {
87
+ const capitalizedFamily =
88
+ modelFamily.charAt(0).toUpperCase() + modelFamily.slice(1).toLowerCase();
89
+ const version = minor ? `${major}.${minor}` : major;
90
+ return `${capitalizedFamily} ${version}`;
91
+ }
92
+
93
+ return rawName;
94
+ }
95
+
96
+ export function abbreviateFishStyle(dirPath: string): string {
97
+ const sep = dirPath.includes("/") ? "/" : "\\";
98
+ const parts = dirPath.split(sep);
99
+ return parts
100
+ .map((part, index) => {
101
+ if (index === parts.length - 1) {
102
+ return part;
103
+ }
104
+ if (part === "~" || part === "") {
105
+ return part;
106
+ }
107
+ return part.charAt(0);
108
+ })
109
+ .join(sep);
110
+ }
111
+
112
+ export function formatResponseTime(seconds: number): string {
113
+ if (seconds < 60) {
114
+ return `${seconds.toFixed(1)}s`;
115
+ }
116
+ return `${(seconds / 60).toFixed(1)}m`;
117
+ }
118
+
119
+ export function formatTokenCount(tokens: number | null): string {
120
+ return formatTokens(tokens).replace(" tokens", "");
121
+ }
122
+
123
+ export function formatBurnRate(rate: number | null | undefined): string {
124
+ if (rate === null || rate === undefined || rate <= 0) return "";
125
+ return rate < 1 ? `${(rate * 100).toFixed(0)}c/h` : `$${rate.toFixed(2)}/h`;
126
+ }
127
+
128
+ export function collapseHome(dirPath: string, homeDir?: string): string {
129
+ const home =
130
+ homeDir ??
131
+ globalThis.process?.env?.HOME ??
132
+ globalThis.process?.env?.USERPROFILE;
133
+ if (home && dirPath.startsWith(home)) {
134
+ return dirPath.replace(home, "~");
135
+ }
136
+ return dirPath;
137
+ }
138
+
139
+ export function formatTimeRemaining(totalMinutes: number): string {
140
+ const hours = Math.floor(totalMinutes / 60);
141
+ const minutes = totalMinutes % 60;
142
+ return hours > 0 ? `${hours}h ${minutes}m left` : `${minutes}m left`;
143
+ }
144
+
145
+ export function formatLongTimeRemaining(totalMinutes: number): string {
146
+ if (totalMinutes >= 1440) {
147
+ const days = Math.floor(totalMinutes / 1440);
148
+ const hours = Math.floor((totalMinutes % 1440) / 60);
149
+ return hours > 0 ? `${days}d ${hours}h` : `${days}d`;
150
+ } else if (totalMinutes >= 60) {
151
+ const hours = Math.floor(totalMinutes / 60);
152
+ const minutes = totalMinutes % 60;
153
+ return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
154
+ }
155
+ return `${totalMinutes}m`;
156
+ }
157
+
158
+ export function minutesUntilReset(epochSeconds: number): number {
159
+ return Math.round(Math.max(0, epochSeconds * 1000 - Date.now()) / 60000);
160
+ }
@@ -0,0 +1,5 @@
1
+ export function debug(message: string, ...args: unknown[]): void {
2
+ if (process.env.CLAUDE_POWERLINE_DEBUG) {
3
+ console.error(`[DEBUG] ${message}`, ...args);
4
+ }
5
+ }
@@ -0,0 +1,117 @@
1
+ import { execSync } from "node:child_process";
2
+
3
+ const VALID_TTY_PATTERN = /^[a-zA-Z0-9/]+$/;
4
+
5
+ function findParentTty(): string | null {
6
+ if (process.platform === "win32") return null;
7
+
8
+ let pid = process.pid.toString();
9
+
10
+ for (let i = 0; i < 10; i++) {
11
+ try {
12
+ const info = execSync(`ps -o ppid=,tty= -p ${pid}`, {
13
+ encoding: "utf8",
14
+ stdio: ["pipe", "pipe", "ignore"],
15
+ }).trim();
16
+ const parts = info.split(/\s+/);
17
+ const ppid = parts[0];
18
+ const tty = parts[1];
19
+
20
+ if (tty && tty !== "?" && tty !== "??" && VALID_TTY_PATTERN.test(tty)) {
21
+ return tty;
22
+ }
23
+
24
+ if (!ppid || ppid === "1" || ppid === "0") break;
25
+ pid = ppid;
26
+ } catch {
27
+ break;
28
+ }
29
+ }
30
+
31
+ return null;
32
+ }
33
+
34
+ function getWindowsTerminalWidth(): number | null {
35
+ try {
36
+ const output = execSync("mode con", {
37
+ encoding: "utf8",
38
+ stdio: ["pipe", "pipe", "ignore"],
39
+ windowsHide: true,
40
+ });
41
+ const match = output.match(/Columns:\s*(\d+)/i);
42
+ if (match?.[1]) {
43
+ const parsed = parseInt(match[1], 10);
44
+ if (!isNaN(parsed) && parsed > 0) return parsed;
45
+ }
46
+ } catch {}
47
+ return null;
48
+ }
49
+
50
+ function getUnixTerminalWidth(): number | null {
51
+ const tty = findParentTty();
52
+ if (tty) {
53
+ try {
54
+ const size = execSync(`stty size < /dev/${tty}`, {
55
+ encoding: "utf8",
56
+ stdio: ["pipe", "pipe", "ignore"],
57
+ shell: "/bin/sh",
58
+ }).trim();
59
+ const width = size.split(" ")[1];
60
+ if (width) {
61
+ const parsed = parseInt(width, 10);
62
+ if (!isNaN(parsed) && parsed > 0) return parsed;
63
+ }
64
+ } catch {}
65
+ }
66
+
67
+ try {
68
+ const width = execSync("tput cols 2>/dev/null", {
69
+ encoding: "utf8",
70
+ stdio: ["pipe", "pipe", "ignore"],
71
+ }).trim();
72
+
73
+ const parsed = parseInt(width, 10);
74
+ if (!isNaN(parsed) && parsed > 0) return parsed;
75
+ } catch {}
76
+
77
+ return null;
78
+ }
79
+
80
+ /**
81
+ * @info Reserves characters for Claude Code's right-side UI messages
82
+ * (e.g., "Current: 2.1.78 · latest: 2.1.78", "Thinking off")
83
+ */
84
+ const RESERVED_CHARS = 45;
85
+
86
+ export function getTerminalWidth(): number | null {
87
+ const applyReserve = (w: number) => Math.max(1, w - RESERVED_CHARS);
88
+
89
+ const envColumns = process.env.COLUMNS;
90
+ if (envColumns) {
91
+ const parsed = parseInt(envColumns, 10);
92
+ if (!isNaN(parsed) && parsed > 0) return applyReserve(parsed);
93
+ }
94
+
95
+ if (process.stdout.columns && process.stdout.columns > 0) {
96
+ return applyReserve(process.stdout.columns);
97
+ }
98
+
99
+ if (process.platform === "win32") {
100
+ const width = getWindowsTerminalWidth();
101
+ if (width) return applyReserve(width);
102
+ }
103
+
104
+ const width = getUnixTerminalWidth();
105
+ return width ? applyReserve(width) : null;
106
+ }
107
+
108
+ export function getRawTerminalWidth(): number | null {
109
+ // Skip COLUMNS env and process.stdout.columns — Claude Code sets those
110
+ // to an already-reserved panel width. We need the actual terminal width
111
+ // so the grid engine can apply its own widthReserve.
112
+ if (process.platform === "win32") {
113
+ return getWindowsTerminalWidth();
114
+ }
115
+
116
+ return getUnixTerminalWidth();
117
+ }
@@ -0,0 +1,11 @@
1
+ export const ESC = String.fromCharCode(27);
2
+ const ANSI_REGEX = new RegExp(`${ESC}\\[[0-9;]*m`, "g");
3
+ export const ANSI_SPLIT = new RegExp(`(${ESC}\\[[0-9;]*m)`);
4
+
5
+ export function stripAnsi(str: string): string {
6
+ return str.replace(ANSI_REGEX, "");
7
+ }
8
+
9
+ export function visibleLength(str: string): number {
10
+ return stripAnsi(str).length;
11
+ }