@nutthead/cc-statusline 0.1.3 → 0.1.8
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 +6 -17
- package/bin/cc-statusline.js +64 -0
- package/index.ts +20 -14
- package/package.json +6 -1
- package/resources/plans/theme-plan.md +310 -0
- package/src/cli.ts +78 -0
- package/src/utils.ts +5 -0
package/README.md
CHANGED
|
@@ -1,33 +1,22 @@
|
|
|
1
1
|
# Claude Code Status Line
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## Install
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
|
|
6
|
+
bunx @nutthead/cc-statusline install
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Use `--overwrite` to replace an existing installation.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
bun run build:binary
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
To copy to `~/.claude`:
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
bun run install:binary
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
## Configure your Claude Code's statusline
|
|
11
|
+
## Configure
|
|
22
12
|
|
|
23
|
-
|
|
13
|
+
Add to `~/.claude/settings.json`:
|
|
24
14
|
|
|
25
15
|
```json
|
|
26
16
|
{
|
|
27
17
|
"statusLine": {
|
|
28
18
|
"type": "command",
|
|
29
|
-
"command": "
|
|
30
|
-
"padding": 0
|
|
19
|
+
"command": "~/.claude/statusline"
|
|
31
20
|
}
|
|
32
21
|
}
|
|
33
22
|
```
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { execSync } from "node:child_process";
|
|
5
|
+
import { existsSync, mkdirSync, copyFileSync, unlinkSync } from "node:fs";
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
var BINARY_NAME = "statusline";
|
|
9
|
+
var CLAUDE_DIR = join(homedir(), ".claude");
|
|
10
|
+
var TARGET_PATH = join(CLAUDE_DIR, BINARY_NAME);
|
|
11
|
+
function build() {
|
|
12
|
+
console.log("Building statusline binary...");
|
|
13
|
+
execSync("mkdir -p target && bun build --compile ./index.ts --outfile target/statusline", {
|
|
14
|
+
stdio: "inherit"
|
|
15
|
+
});
|
|
16
|
+
console.log("Build complete.");
|
|
17
|
+
}
|
|
18
|
+
function install(overwrite) {
|
|
19
|
+
build();
|
|
20
|
+
if (!existsSync(CLAUDE_DIR)) {
|
|
21
|
+
mkdirSync(CLAUDE_DIR, { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
if (existsSync(TARGET_PATH)) {
|
|
24
|
+
if (!overwrite) {
|
|
25
|
+
console.error(`Error: ${TARGET_PATH} already exists.`);
|
|
26
|
+
console.error("Use --overwrite to replace the existing file.");
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
console.log(`Overwriting existing file at ${TARGET_PATH}...`);
|
|
30
|
+
unlinkSync(TARGET_PATH);
|
|
31
|
+
}
|
|
32
|
+
const sourcePath = join(process.cwd(), "target", BINARY_NAME);
|
|
33
|
+
copyFileSync(sourcePath, TARGET_PATH);
|
|
34
|
+
console.log(`Installed statusline to ${TARGET_PATH}`);
|
|
35
|
+
}
|
|
36
|
+
function printUsage() {
|
|
37
|
+
console.log(`Usage: cc-statusline <command> [options]
|
|
38
|
+
|
|
39
|
+
Commands:
|
|
40
|
+
install Build and install statusline to ~/.claude/
|
|
41
|
+
|
|
42
|
+
Options:
|
|
43
|
+
--overwrite Overwrite existing file if it exists
|
|
44
|
+
--help, -h Show this help message`);
|
|
45
|
+
}
|
|
46
|
+
function main() {
|
|
47
|
+
const args = process.argv.slice(2);
|
|
48
|
+
const command = args.find((arg) => !arg.startsWith("-"));
|
|
49
|
+
const flags = new Set(args.filter((arg) => arg.startsWith("-")));
|
|
50
|
+
if (flags.has("--help") || flags.has("-h") || args.length === 0) {
|
|
51
|
+
printUsage();
|
|
52
|
+
process.exit(0);
|
|
53
|
+
}
|
|
54
|
+
switch (command) {
|
|
55
|
+
case "install":
|
|
56
|
+
install(flags.has("--overwrite"));
|
|
57
|
+
break;
|
|
58
|
+
default:
|
|
59
|
+
console.error(`Unknown command: ${command}`);
|
|
60
|
+
printUsage();
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
main();
|
package/index.ts
CHANGED
|
@@ -6,31 +6,37 @@ import {
|
|
|
6
6
|
currentDirStatus,
|
|
7
7
|
currentGitStatus,
|
|
8
8
|
currentModelStatus,
|
|
9
|
+
currentSessionId,
|
|
9
10
|
} from "./src/utils";
|
|
10
11
|
|
|
11
12
|
import c from "ansi-colors";
|
|
12
13
|
|
|
13
14
|
await configure(logtapeConfig);
|
|
14
15
|
|
|
16
|
+
let statusLine = null;
|
|
15
17
|
const input = await Bun.stdin.stream().json();
|
|
16
|
-
log.debug("
|
|
18
|
+
log.debug("input: {input}", input);
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
if (input) {
|
|
21
|
+
const result = statusSchema.safeParse(input);
|
|
19
22
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
if (result.success) {
|
|
24
|
+
const status = result.data;
|
|
25
|
+
const dirStatus = c.blue(currentDirStatus(status));
|
|
26
|
+
const gitStatus = c.green(await currentGitStatus());
|
|
27
|
+
const modelStatus = c.magenta(currentModelStatus(status));
|
|
28
|
+
const sessionId = c.blue(currentSessionId(status));
|
|
29
|
+
const separator = c.bold.gray("⋮");
|
|
27
30
|
|
|
28
|
-
|
|
31
|
+
statusLine = `${dirStatus} ${separator} ${gitStatus}\n${modelStatus} ${separator} ${sessionId}`;
|
|
32
|
+
} else {
|
|
33
|
+
log.error("Failed to parse input: {error}", {
|
|
34
|
+
error: JSON.stringify(z.treeifyError(result.error)),
|
|
35
|
+
});
|
|
36
|
+
statusLine = `[malformed status]`;
|
|
37
|
+
}
|
|
29
38
|
} else {
|
|
30
|
-
|
|
31
|
-
error: JSON.stringify(z.treeifyError(result.error)),
|
|
32
|
-
});
|
|
33
|
-
statusLine = `[]`;
|
|
39
|
+
statusLine = `[no status]`;
|
|
34
40
|
}
|
|
35
41
|
|
|
36
42
|
console.log(statusLine);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nutthead/cc-statusline",
|
|
3
3
|
"description": "Status Line for Claude Code",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.8",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
7
7
|
"import": "./index.ts",
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
}
|
|
10
10
|
},
|
|
11
11
|
"type": "module",
|
|
12
|
+
"bin": {
|
|
13
|
+
"cc-statusline": "bin/cc-statusline.js"
|
|
14
|
+
},
|
|
12
15
|
"private": false,
|
|
13
16
|
"license": "MIT",
|
|
14
17
|
"author": {
|
|
@@ -31,6 +34,8 @@
|
|
|
31
34
|
],
|
|
32
35
|
"scripts": {
|
|
33
36
|
"build:binary": "mkdir -p target && bun build --compile ./index.ts --outfile target/statusline",
|
|
37
|
+
"build:cli": "bun build ./src/cli.ts --outfile ./bin/cc-statusline.js --target node",
|
|
38
|
+
"prepublishOnly": "bun run build:cli",
|
|
34
39
|
"install:binary": "cp target/statusline ~/.claude/"
|
|
35
40
|
},
|
|
36
41
|
"devDependencies": {
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
# Theming System: Render Function Approach
|
|
2
|
+
|
|
3
|
+
## Selected Approach
|
|
4
|
+
|
|
5
|
+
**Render Function** with support for custom components. Theme authors export a function that receives context and returns the formatted statusline string.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Theme API Design
|
|
10
|
+
|
|
11
|
+
### Basic Theme Structure
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
// ~/.config/statusline/themes/my-theme.ts
|
|
15
|
+
import type { ThemeContext } from 'statusline/theme';
|
|
16
|
+
|
|
17
|
+
export default async function render(ctx: ThemeContext): Promise<string> {
|
|
18
|
+
const { status, helpers, colors: c } = ctx;
|
|
19
|
+
|
|
20
|
+
const dir = c.blue(`🗂️ ${helpers.abbreviatePath(status.workspace.current_dir)}`);
|
|
21
|
+
const git = c.green(`🌿 ${await helpers.gitBranch()}`);
|
|
22
|
+
const model = c.magenta(`⏣ ${helpers.modelName()}`);
|
|
23
|
+
|
|
24
|
+
return `${dir} │ ${git}\n${model}`;
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### ThemeContext Interface
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
interface ThemeContext {
|
|
32
|
+
// Raw status data from Claude Code
|
|
33
|
+
status: StatusLineInput;
|
|
34
|
+
|
|
35
|
+
// ansi-colors instance for styling
|
|
36
|
+
colors: typeof import('ansi-colors');
|
|
37
|
+
|
|
38
|
+
// Helper functions (async where needed)
|
|
39
|
+
helpers: {
|
|
40
|
+
// Path utilities
|
|
41
|
+
abbreviatePath(path: string): string;
|
|
42
|
+
homePath(): string;
|
|
43
|
+
|
|
44
|
+
// Git utilities (async)
|
|
45
|
+
gitBranch(): Promise<string>;
|
|
46
|
+
gitStatus(): Promise<GitStatusResult>;
|
|
47
|
+
isGitRepo(): Promise<boolean>;
|
|
48
|
+
|
|
49
|
+
// Model utilities
|
|
50
|
+
modelName(): string; // "opus-4.5"
|
|
51
|
+
modelDisplayName(): string; // "Claude Opus 4.5"
|
|
52
|
+
|
|
53
|
+
// Session utilities
|
|
54
|
+
sessionId(): string;
|
|
55
|
+
|
|
56
|
+
// Cost/metrics utilities
|
|
57
|
+
formatCost(): string; // "$0.42"
|
|
58
|
+
formatDuration(): string; // "2m 34s"
|
|
59
|
+
|
|
60
|
+
// Generic formatting
|
|
61
|
+
truncate(str: string, maxLen: number): string;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Implementation Plan
|
|
69
|
+
|
|
70
|
+
### Files to Create/Modify
|
|
71
|
+
|
|
72
|
+
1. **`src/theme.ts`** (NEW) - Theme types, loader, and context builder
|
|
73
|
+
2. **`src/defaultTheme.ts`** (NEW) - Built-in default theme
|
|
74
|
+
3. **`index.ts`** - Add CLI argument parsing for `--theme`
|
|
75
|
+
4. **`src/utils.ts`** - Export helpers for theme context
|
|
76
|
+
|
|
77
|
+
### Step 1: Define Theme Types (`src/theme.ts`)
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import type { StatusLineInput } from './statusLineSchema';
|
|
81
|
+
import type c from 'ansi-colors';
|
|
82
|
+
|
|
83
|
+
export interface ThemeHelpers {
|
|
84
|
+
abbreviatePath(path: string): string;
|
|
85
|
+
gitBranch(): Promise<string>;
|
|
86
|
+
gitStatus(): Promise<GitStatusResult>;
|
|
87
|
+
isGitRepo(): Promise<boolean>;
|
|
88
|
+
modelName(): string;
|
|
89
|
+
modelDisplayName(): string;
|
|
90
|
+
sessionId(): string;
|
|
91
|
+
formatCost(): string;
|
|
92
|
+
formatDuration(): string;
|
|
93
|
+
truncate(str: string, maxLen: number): string;
|
|
94
|
+
homePath(): string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface ThemeContext {
|
|
98
|
+
status: StatusLineInput;
|
|
99
|
+
colors: typeof c;
|
|
100
|
+
helpers: ThemeHelpers;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export type ThemeFunction = (ctx: ThemeContext) => string | Promise<string>;
|
|
104
|
+
|
|
105
|
+
export interface ThemeModule {
|
|
106
|
+
default: ThemeFunction;
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Step 2: Theme Loader (`src/theme.ts`)
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
export async function loadTheme(themePath: string): Promise<ThemeFunction> {
|
|
114
|
+
// Resolve path (handle ~ expansion, relative paths)
|
|
115
|
+
const resolvedPath = resolvePath(themePath);
|
|
116
|
+
|
|
117
|
+
// Dynamic import (Bun handles .ts automatically)
|
|
118
|
+
const module = await import(resolvedPath) as ThemeModule;
|
|
119
|
+
|
|
120
|
+
if (typeof module.default !== 'function') {
|
|
121
|
+
throw new Error(`Theme must export a default function`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return module.default;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function createThemeContext(status: StatusLineInput): ThemeContext {
|
|
128
|
+
return {
|
|
129
|
+
status,
|
|
130
|
+
colors: c,
|
|
131
|
+
helpers: buildHelpers(status),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Step 3: Default Theme (`src/defaultTheme.ts`)
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
import type { ThemeContext } from './theme';
|
|
140
|
+
|
|
141
|
+
export default async function render(ctx: ThemeContext): Promise<string> {
|
|
142
|
+
const { status, helpers, colors: c } = ctx;
|
|
143
|
+
|
|
144
|
+
const dirStatus = c.blue(formatDirectory(status, helpers));
|
|
145
|
+
const gitStatus = c.green(await formatGit(helpers));
|
|
146
|
+
const modelStatus = c.magenta(formatModel(helpers));
|
|
147
|
+
const sessionId = c.blue(formatSession(helpers));
|
|
148
|
+
const separator = c.bold.gray('⋮');
|
|
149
|
+
|
|
150
|
+
return `${dirStatus} ${separator} ${gitStatus}\n${modelStatus} ${separator} ${sessionId}`;
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Step 4: CLI Argument Parsing (`index.ts`)
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
// Parse --theme argument
|
|
158
|
+
const args = process.argv.slice(2);
|
|
159
|
+
const themeIndex = args.indexOf('--theme');
|
|
160
|
+
const themePath = themeIndex !== -1 ? args[themeIndex + 1] : null;
|
|
161
|
+
|
|
162
|
+
// Load theme (default or custom)
|
|
163
|
+
const renderTheme = themePath
|
|
164
|
+
? await loadTheme(themePath)
|
|
165
|
+
: (await import('./src/defaultTheme')).default;
|
|
166
|
+
|
|
167
|
+
// Create context and render
|
|
168
|
+
const ctx = createThemeContext(status);
|
|
169
|
+
const statusLine = await renderTheme(ctx);
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Step 5: Refactor Helpers (`src/utils.ts`)
|
|
173
|
+
|
|
174
|
+
Export existing functions and add new helpers:
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
// Existing (keep)
|
|
178
|
+
export function abbreviatePath(path: string): string { ... }
|
|
179
|
+
export async function currentBranchName(cwd?: string): Promise<BranchResult> { ... }
|
|
180
|
+
|
|
181
|
+
// New helpers
|
|
182
|
+
export function formatCost(cost: number): string {
|
|
183
|
+
return `$${cost.toFixed(2)}`;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function formatDuration(ms: number): string {
|
|
187
|
+
const seconds = Math.floor(ms / 1000);
|
|
188
|
+
const minutes = Math.floor(seconds / 60);
|
|
189
|
+
if (minutes > 0) {
|
|
190
|
+
return `${minutes}m ${seconds % 60}s`;
|
|
191
|
+
}
|
|
192
|
+
return `${seconds}s`;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function truncate(str: string, maxLen: number): string {
|
|
196
|
+
if (str.length <= maxLen) return str;
|
|
197
|
+
return str.slice(0, maxLen - 1) + '…';
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## CLI Usage
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
# Use default theme
|
|
207
|
+
statusline
|
|
208
|
+
|
|
209
|
+
# Use custom theme (absolute path)
|
|
210
|
+
statusline --theme /path/to/my-theme.ts
|
|
211
|
+
|
|
212
|
+
# Use custom theme (relative to home)
|
|
213
|
+
statusline --theme ~/.config/statusline/themes/minimal.ts
|
|
214
|
+
|
|
215
|
+
# Environment variable (fallback)
|
|
216
|
+
STATUSLINE_THEME=~/.config/statusline/themes/nerd.ts statusline
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Example Themes
|
|
222
|
+
|
|
223
|
+
### Minimal Single-Line Theme
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
// minimal.ts
|
|
227
|
+
import type { ThemeContext } from 'statusline/theme';
|
|
228
|
+
|
|
229
|
+
export default async function render({ status, helpers, colors: c }: ThemeContext): Promise<string> {
|
|
230
|
+
const dir = helpers.abbreviatePath(status.workspace.current_dir);
|
|
231
|
+
const branch = await helpers.gitBranch();
|
|
232
|
+
return c.dim(`${dir} (${branch})`);
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Powerline-Style Theme
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
// powerline.ts
|
|
240
|
+
import type { ThemeContext } from 'statusline/theme';
|
|
241
|
+
|
|
242
|
+
export default async function render({ status, helpers, colors: c }: ThemeContext): Promise<string> {
|
|
243
|
+
const dir = c.bgBlue.white(` ${helpers.abbreviatePath(status.workspace.current_dir)} `);
|
|
244
|
+
const git = c.bgGreen.black(` ${await helpers.gitBranch()} `);
|
|
245
|
+
const model = c.bgMagenta.white(` ${helpers.modelName()} `);
|
|
246
|
+
|
|
247
|
+
return `${dir}${git}${model}`;
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Theme with Custom Component (Time)
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
// with-clock.ts
|
|
255
|
+
import type { ThemeContext } from 'statusline/theme';
|
|
256
|
+
|
|
257
|
+
export default async function render({ status, helpers, colors: c }: ThemeContext): Promise<string> {
|
|
258
|
+
const time = new Date().toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
|
|
259
|
+
|
|
260
|
+
const dir = c.blue(`🗂️ ${helpers.abbreviatePath(status.workspace.current_dir)}`);
|
|
261
|
+
const git = c.green(`🌿 ${await helpers.gitBranch()}`);
|
|
262
|
+
const clock = c.yellow(`🕐 ${time}`);
|
|
263
|
+
|
|
264
|
+
return `${dir} │ ${git} │ ${clock}`;
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## Error Handling
|
|
271
|
+
|
|
272
|
+
1. **Theme load failure**: Fall back to default theme, log warning
|
|
273
|
+
2. **Theme render error**: Catch, show error message in statusline, log details
|
|
274
|
+
3. **Invalid export**: Validate `module.default` is a function
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
try {
|
|
278
|
+
const renderTheme = themePath ? await loadTheme(themePath) : defaultTheme;
|
|
279
|
+
statusLine = await renderTheme(ctx);
|
|
280
|
+
} catch (error) {
|
|
281
|
+
logger.error('Theme error', { error, themePath });
|
|
282
|
+
statusLine = c.red(`Theme error: ${error.message}`);
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## Type Distribution
|
|
289
|
+
|
|
290
|
+
For theme authors to get type hints, provide types via:
|
|
291
|
+
|
|
292
|
+
**Option 1**: Export types from main package
|
|
293
|
+
```typescript
|
|
294
|
+
// Theme authors can reference
|
|
295
|
+
/// <reference path="./node_modules/statusline/theme.d.ts" />
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
**Option 2**: Publish `@statusline/types` package (if this grows)
|
|
299
|
+
|
|
300
|
+
**Option 3**: Include `.d.ts` alongside binary (simplest for now)
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## Verification
|
|
305
|
+
|
|
306
|
+
1. Run with default theme: `echo '{}' | bun run index.ts`
|
|
307
|
+
2. Run with custom theme: `echo '{}' | bun run index.ts --theme ./examples/minimal.ts`
|
|
308
|
+
3. Test error handling: Use invalid theme path, theme with syntax error
|
|
309
|
+
4. Test async helpers: Verify git operations work in themes
|
|
310
|
+
5. Run existing tests: `bun test`
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { execSync } from "node:child_process";
|
|
4
|
+
import { existsSync, mkdirSync, copyFileSync, unlinkSync } from "node:fs";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
|
|
8
|
+
const BINARY_NAME = "statusline";
|
|
9
|
+
const CLAUDE_DIR = join(homedir(), ".claude");
|
|
10
|
+
const TARGET_PATH = join(CLAUDE_DIR, BINARY_NAME);
|
|
11
|
+
|
|
12
|
+
function build(): void {
|
|
13
|
+
console.log("Building statusline binary...");
|
|
14
|
+
execSync("mkdir -p target && bun build --compile ./index.ts --outfile target/statusline", {
|
|
15
|
+
stdio: "inherit",
|
|
16
|
+
});
|
|
17
|
+
console.log("Build complete.");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function install(overwrite: boolean): void {
|
|
21
|
+
// Build first
|
|
22
|
+
build();
|
|
23
|
+
|
|
24
|
+
// Ensure ~/.claude directory exists
|
|
25
|
+
if (!existsSync(CLAUDE_DIR)) {
|
|
26
|
+
mkdirSync(CLAUDE_DIR, { recursive: true });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Check if target file exists
|
|
30
|
+
if (existsSync(TARGET_PATH)) {
|
|
31
|
+
if (!overwrite) {
|
|
32
|
+
console.error(`Error: ${TARGET_PATH} already exists.`);
|
|
33
|
+
console.error("Use --overwrite to replace the existing file.");
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
console.log(`Overwriting existing file at ${TARGET_PATH}...`);
|
|
37
|
+
unlinkSync(TARGET_PATH); // Remove first to avoid ETXTBSY if binary is running
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Copy binary to destination
|
|
41
|
+
const sourcePath = join(process.cwd(), "target", BINARY_NAME);
|
|
42
|
+
copyFileSync(sourcePath, TARGET_PATH);
|
|
43
|
+
console.log(`Installed statusline to ${TARGET_PATH}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function printUsage(): void {
|
|
47
|
+
console.log(`Usage: cc-statusline <command> [options]
|
|
48
|
+
|
|
49
|
+
Commands:
|
|
50
|
+
install Build and install statusline to ~/.claude/
|
|
51
|
+
|
|
52
|
+
Options:
|
|
53
|
+
--overwrite Overwrite existing file if it exists
|
|
54
|
+
--help, -h Show this help message`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function main(): void {
|
|
58
|
+
const args = process.argv.slice(2);
|
|
59
|
+
const command = args.find((arg) => !arg.startsWith("-"));
|
|
60
|
+
const flags = new Set(args.filter((arg) => arg.startsWith("-")));
|
|
61
|
+
|
|
62
|
+
if (flags.has("--help") || flags.has("-h") || args.length === 0) {
|
|
63
|
+
printUsage();
|
|
64
|
+
process.exit(0);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
switch (command) {
|
|
68
|
+
case "install":
|
|
69
|
+
install(flags.has("--overwrite"));
|
|
70
|
+
break;
|
|
71
|
+
default:
|
|
72
|
+
console.error(`Unknown command: ${command}`);
|
|
73
|
+
printUsage();
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
main();
|
package/src/utils.ts
CHANGED
|
@@ -156,6 +156,10 @@ function currentDirStatus(status: Status) {
|
|
|
156
156
|
return dirStatus;
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
function currentSessionId(status: Status) {
|
|
160
|
+
return `📝 ${status.session_id}`;
|
|
161
|
+
}
|
|
162
|
+
|
|
159
163
|
export {
|
|
160
164
|
abbreviateModelId,
|
|
161
165
|
abbreviatePath,
|
|
@@ -163,6 +167,7 @@ export {
|
|
|
163
167
|
currentDirStatus,
|
|
164
168
|
currentGitStatus,
|
|
165
169
|
currentModelStatus,
|
|
170
|
+
currentSessionId,
|
|
166
171
|
};
|
|
167
172
|
|
|
168
173
|
export type { BranchResult };
|