upfynai-code 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/LICENSE +22 -0
- package/bin/cli.js +86 -0
- package/dist/assets/CanvasPanel-B48gAKVY.js +538 -0
- package/dist/assets/CanvasPanel-B48gAKVY.js.map +1 -0
- package/dist/assets/CanvasPanel-BsOG3EVs.css +1 -0
- package/dist/assets/index-CEhTwG68.css +1 -0
- package/dist/assets/index-GqAGWpJI.js +70 -0
- package/dist/assets/index-GqAGWpJI.js.map +1 -0
- package/dist/index.html +18 -0
- package/index.html +17 -0
- package/package.json +67 -0
- package/src/App.tsx +226 -0
- package/src/components/canvas/CanvasPanel.tsx +62 -0
- package/src/components/canvas/layout/graph-builder.ts +136 -0
- package/src/components/canvas/shapes/CompactionNodeShape.tsx +76 -0
- package/src/components/canvas/shapes/SessionNodeShape.tsx +93 -0
- package/src/components/canvas/shapes/StatuslineWidgetShape.tsx +125 -0
- package/src/components/canvas/shapes/TextResponseNodeShape.tsx +86 -0
- package/src/components/canvas/shapes/ToolCallNodeShape.tsx +107 -0
- package/src/components/canvas/shapes/ToolResultNodeShape.tsx +87 -0
- package/src/components/canvas/shapes/shared-styles.ts +35 -0
- package/src/components/chat/ChatPanel.tsx +96 -0
- package/src/components/chat/InputBar.tsx +81 -0
- package/src/components/chat/MessageList.tsx +130 -0
- package/src/components/chat/PermissionDialog.tsx +70 -0
- package/src/components/layout/FolderSelector.tsx +152 -0
- package/src/components/layout/ModelSelector.tsx +65 -0
- package/src/components/layout/SessionManager.tsx +115 -0
- package/src/components/statusline/StatuslineBar.tsx +114 -0
- package/src/main.tsx +10 -0
- package/src/server/claude-session.ts +156 -0
- package/src/server/index.ts +149 -0
- package/src/services/stream-consumer.ts +330 -0
- package/src/statusline-core/bin/statusline.sh +121 -0
- package/src/statusline-core/commands/sls-config.md +42 -0
- package/src/statusline-core/commands/sls-doctor.md +35 -0
- package/src/statusline-core/commands/sls-help.md +48 -0
- package/src/statusline-core/commands/sls-layout.md +38 -0
- package/src/statusline-core/commands/sls-preview.md +34 -0
- package/src/statusline-core/commands/sls-theme.md +40 -0
- package/src/statusline-core/installer.js +228 -0
- package/src/statusline-core/layouts/compact.sh +21 -0
- package/src/statusline-core/layouts/full.sh +62 -0
- package/src/statusline-core/layouts/standard.sh +39 -0
- package/src/statusline-core/lib/core.sh +389 -0
- package/src/statusline-core/lib/helpers.sh +81 -0
- package/src/statusline-core/lib/json-parser.sh +71 -0
- package/src/statusline-core/themes/catppuccin.sh +32 -0
- package/src/statusline-core/themes/default.sh +37 -0
- package/src/statusline-core/themes/gruvbox.sh +32 -0
- package/src/statusline-core/themes/nord.sh +32 -0
- package/src/statusline-core/themes/tokyo-night.sh +32 -0
- package/src/store/canvas-store.ts +50 -0
- package/src/store/chat-store.ts +60 -0
- package/src/store/permission-store.ts +29 -0
- package/src/store/session-store.ts +52 -0
- package/src/store/statusline-store.ts +160 -0
- package/src/styles/global.css +117 -0
- package/src/themes/index.ts +149 -0
- package/src/types/canvas-graph.ts +24 -0
- package/src/types/sdk-messages.ts +156 -0
- package/src/types/statusline-fields.ts +67 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.json +26 -0
- package/vite.config.ts +24 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import type { SDKMessage, ContentBlockToolUse, ToolResultContent } from '../types/sdk-messages';
|
|
2
|
+
import { useSessionStore } from '../store/session-store';
|
|
3
|
+
import { useChatStore } from '../store/chat-store';
|
|
4
|
+
import { useStatuslineStore } from '../store/statusline-store';
|
|
5
|
+
import { usePermissionStore } from '../store/permission-store';
|
|
6
|
+
import { useCanvasStore } from '../store/canvas-store';
|
|
7
|
+
|
|
8
|
+
let eventSource: EventSource | null = null;
|
|
9
|
+
let messageCounter = 0;
|
|
10
|
+
|
|
11
|
+
function nextId(): string {
|
|
12
|
+
return `msg-${++messageCounter}-${Date.now()}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function connectSSE(baseUrl = '') {
|
|
16
|
+
if (eventSource) {
|
|
17
|
+
eventSource.close();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
eventSource = new EventSource(`${baseUrl}/api/stream`);
|
|
21
|
+
|
|
22
|
+
eventSource.addEventListener('message', (event) => {
|
|
23
|
+
if (!event.data) return;
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const msg: SDKMessage = JSON.parse(event.data);
|
|
27
|
+
handleMessage(msg);
|
|
28
|
+
} catch {
|
|
29
|
+
// Ignore parse errors
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
eventSource.addEventListener('open', () => {
|
|
34
|
+
useSessionStore.getState().setConnected(true);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
eventSource.addEventListener('error', () => {
|
|
38
|
+
useSessionStore.getState().setConnected(false);
|
|
39
|
+
// Auto-reconnect is handled by EventSource
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return eventSource;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function disconnectSSE() {
|
|
46
|
+
if (eventSource) {
|
|
47
|
+
eventSource.close();
|
|
48
|
+
eventSource = null;
|
|
49
|
+
}
|
|
50
|
+
useSessionStore.getState().setConnected(false);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function handleMessage(msg: SDKMessage) {
|
|
54
|
+
const chat = useChatStore.getState();
|
|
55
|
+
const session = useSessionStore.getState();
|
|
56
|
+
const statusline = useStatuslineStore.getState();
|
|
57
|
+
const permissions = usePermissionStore.getState();
|
|
58
|
+
const canvas = useCanvasStore.getState();
|
|
59
|
+
|
|
60
|
+
switch (msg.type) {
|
|
61
|
+
case 'system': {
|
|
62
|
+
if ('subtype' in msg && msg.subtype === 'init') {
|
|
63
|
+
session.setInit({
|
|
64
|
+
sessionId: msg.session_id,
|
|
65
|
+
model: msg.model,
|
|
66
|
+
cwd: msg.cwd,
|
|
67
|
+
tools: msg.tools,
|
|
68
|
+
permissionMode: msg.permissionMode,
|
|
69
|
+
});
|
|
70
|
+
statusline.updateFromInit(msg.model, msg.cwd, msg.session_id, msg.permissionMode);
|
|
71
|
+
|
|
72
|
+
// Fetch git status
|
|
73
|
+
fetchGitStatus(msg.cwd);
|
|
74
|
+
|
|
75
|
+
// Canvas: session node
|
|
76
|
+
canvas.addNode({
|
|
77
|
+
id: `session-${msg.session_id}`,
|
|
78
|
+
type: 'session',
|
|
79
|
+
label: msg.model,
|
|
80
|
+
data: { model: msg.model, cwd: msg.cwd, session_id: msg.session_id, tools: msg.tools },
|
|
81
|
+
timestamp: Date.now(),
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
chat.addMessage({
|
|
85
|
+
id: nextId(),
|
|
86
|
+
role: 'system',
|
|
87
|
+
content: `Connected to Claude Code (${msg.model})`,
|
|
88
|
+
timestamp: Date.now(),
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
if ('subtype' in msg && msg.subtype === 'compact_boundary') {
|
|
92
|
+
statusline.markCompaction(msg.compact_metadata.pre_tokens);
|
|
93
|
+
|
|
94
|
+
// Canvas: compaction node
|
|
95
|
+
canvas.addNode({
|
|
96
|
+
id: `compact-${Date.now()}`,
|
|
97
|
+
type: 'compaction',
|
|
98
|
+
label: 'Compaction',
|
|
99
|
+
data: { pre_tokens: msg.compact_metadata.pre_tokens, trigger: msg.compact_metadata.trigger },
|
|
100
|
+
timestamp: Date.now(),
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
chat.addMessage({
|
|
104
|
+
id: nextId(),
|
|
105
|
+
role: 'system',
|
|
106
|
+
content: `Context compacted (was ${Math.round(msg.compact_metadata.pre_tokens / 1000)}k tokens)`,
|
|
107
|
+
timestamp: Date.now(),
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
case 'stream_event': {
|
|
114
|
+
session.setStreaming(true);
|
|
115
|
+
const evt = msg.event;
|
|
116
|
+
|
|
117
|
+
if (evt.type === 'content_block_delta' && evt.delta) {
|
|
118
|
+
if (evt.delta.type === 'text_delta' && evt.delta.text) {
|
|
119
|
+
chat.appendStreamDelta(evt.delta.text);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (evt.type === 'content_block_start' && evt.content_block) {
|
|
124
|
+
if (evt.content_block.type === 'tool_use' && evt.content_block.name) {
|
|
125
|
+
chat.setStreamingToolName(evt.content_block.name);
|
|
126
|
+
statusline.updateSkill(evt.content_block.name);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (evt.type === 'message_stop') {
|
|
131
|
+
session.setStreaming(false);
|
|
132
|
+
}
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
case 'assistant': {
|
|
137
|
+
// Finalize any streaming text
|
|
138
|
+
chat.finalizeStreaming(msg.message.usage);
|
|
139
|
+
|
|
140
|
+
// Process content blocks
|
|
141
|
+
for (const block of msg.message.content) {
|
|
142
|
+
if (block.type === 'tool_use') {
|
|
143
|
+
const toolBlock = block as ContentBlockToolUse;
|
|
144
|
+
chat.addMessage({
|
|
145
|
+
id: nextId(),
|
|
146
|
+
role: 'tool_call',
|
|
147
|
+
content: JSON.stringify(toolBlock.input, null, 2),
|
|
148
|
+
toolName: toolBlock.name,
|
|
149
|
+
toolUseId: toolBlock.id,
|
|
150
|
+
timestamp: Date.now(),
|
|
151
|
+
});
|
|
152
|
+
statusline.updateSkill(toolBlock.name);
|
|
153
|
+
|
|
154
|
+
// Canvas: tool call node
|
|
155
|
+
canvas.addNode({
|
|
156
|
+
id: `tool-${toolBlock.id}`,
|
|
157
|
+
type: 'toolCall',
|
|
158
|
+
label: toolBlock.name,
|
|
159
|
+
data: { name: toolBlock.name, input: toolBlock.input, id: toolBlock.id },
|
|
160
|
+
timestamp: Date.now(),
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Canvas: text response for non-tool text blocks
|
|
165
|
+
const textBlocks = msg.message.content.filter(b => b.type === 'text');
|
|
166
|
+
if (textBlocks.length > 0) {
|
|
167
|
+
const fullText = textBlocks.map(b => (b as { text: string }).text).join('');
|
|
168
|
+
if (fullText.trim()) {
|
|
169
|
+
canvas.addNode({
|
|
170
|
+
id: `text-${msg.uuid}`,
|
|
171
|
+
type: 'textResponse',
|
|
172
|
+
label: 'Response',
|
|
173
|
+
data: { text: fullText },
|
|
174
|
+
timestamp: Date.now(),
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Update statusline with usage
|
|
181
|
+
if (msg.message.usage) {
|
|
182
|
+
statusline.updateFromUsage(msg.message.usage);
|
|
183
|
+
}
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
case 'user': {
|
|
188
|
+
if (Array.isArray(msg.message.content)) {
|
|
189
|
+
for (const block of msg.message.content) {
|
|
190
|
+
if ((block as ToolResultContent).type === 'tool_result') {
|
|
191
|
+
const result = block as ToolResultContent;
|
|
192
|
+
chat.addMessage({
|
|
193
|
+
id: nextId(),
|
|
194
|
+
role: 'tool_result',
|
|
195
|
+
content: typeof result.content === 'string'
|
|
196
|
+
? result.content.slice(0, 2000)
|
|
197
|
+
: JSON.stringify(result.content).slice(0, 2000),
|
|
198
|
+
toolUseId: result.tool_use_id,
|
|
199
|
+
isError: result.is_error,
|
|
200
|
+
timestamp: Date.now(),
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Canvas: tool result node
|
|
204
|
+
canvas.addNode({
|
|
205
|
+
id: `result-${result.tool_use_id}`,
|
|
206
|
+
type: 'toolResult',
|
|
207
|
+
label: 'Result',
|
|
208
|
+
data: {
|
|
209
|
+
toolName: result.tool_use_id,
|
|
210
|
+
content: typeof result.content === 'string' ? result.content.slice(0, 200) : '',
|
|
211
|
+
is_error: result.is_error,
|
|
212
|
+
},
|
|
213
|
+
timestamp: Date.now(),
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
case 'result': {
|
|
222
|
+
session.setStreaming(false);
|
|
223
|
+
chat.finalizeStreaming();
|
|
224
|
+
chat.setWaiting(false);
|
|
225
|
+
|
|
226
|
+
statusline.updateFromResult({
|
|
227
|
+
cost: msg.total_cost_usd,
|
|
228
|
+
duration: msg.duration_ms,
|
|
229
|
+
numTurns: msg.num_turns,
|
|
230
|
+
usage: msg.usage,
|
|
231
|
+
modelUsage: msg.modelUsage,
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
if (msg.is_error && msg.errors) {
|
|
235
|
+
chat.addMessage({
|
|
236
|
+
id: nextId(),
|
|
237
|
+
role: 'system',
|
|
238
|
+
content: `Error: ${msg.errors.join(', ')}`,
|
|
239
|
+
isError: true,
|
|
240
|
+
timestamp: Date.now(),
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
case 'permission_request': {
|
|
247
|
+
permissions.addRequest({
|
|
248
|
+
requestId: msg.requestId,
|
|
249
|
+
toolName: msg.toolName,
|
|
250
|
+
toolInput: msg.toolInput,
|
|
251
|
+
timestamp: Date.now(),
|
|
252
|
+
});
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async function fetchGitStatus(cwd: string) {
|
|
259
|
+
try {
|
|
260
|
+
const res = await fetch(`/api/git-status?cwd=${encodeURIComponent(cwd)}`);
|
|
261
|
+
if (res.ok) {
|
|
262
|
+
const data = await res.json();
|
|
263
|
+
useStatuslineStore.getState().updateGit(data);
|
|
264
|
+
}
|
|
265
|
+
} catch {
|
|
266
|
+
// Git status not available
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export async function sendPrompt(prompt: string) {
|
|
271
|
+
const chat = useChatStore.getState();
|
|
272
|
+
const session = useSessionStore.getState();
|
|
273
|
+
|
|
274
|
+
// Add user message to chat
|
|
275
|
+
chat.addMessage({
|
|
276
|
+
id: nextId(),
|
|
277
|
+
role: 'user',
|
|
278
|
+
content: prompt,
|
|
279
|
+
timestamp: Date.now(),
|
|
280
|
+
});
|
|
281
|
+
chat.setInput('');
|
|
282
|
+
chat.setWaiting(true);
|
|
283
|
+
|
|
284
|
+
try {
|
|
285
|
+
const res = await fetch('/api/prompt', {
|
|
286
|
+
method: 'POST',
|
|
287
|
+
headers: { 'Content-Type': 'application/json' },
|
|
288
|
+
body: JSON.stringify({
|
|
289
|
+
prompt,
|
|
290
|
+
sessionId: session.sessionId,
|
|
291
|
+
cwd: session.cwd,
|
|
292
|
+
}),
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
if (!res.ok) {
|
|
296
|
+
const err = await res.json();
|
|
297
|
+
chat.addMessage({
|
|
298
|
+
id: nextId(),
|
|
299
|
+
role: 'system',
|
|
300
|
+
content: `Failed to send: ${err.error || 'Unknown error'}`,
|
|
301
|
+
isError: true,
|
|
302
|
+
timestamp: Date.now(),
|
|
303
|
+
});
|
|
304
|
+
chat.setWaiting(false);
|
|
305
|
+
}
|
|
306
|
+
} catch (err: any) {
|
|
307
|
+
chat.addMessage({
|
|
308
|
+
id: nextId(),
|
|
309
|
+
role: 'system',
|
|
310
|
+
content: `Connection error: ${err.message}`,
|
|
311
|
+
isError: true,
|
|
312
|
+
timestamp: Date.now(),
|
|
313
|
+
});
|
|
314
|
+
chat.setWaiting(false);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export async function respondPermission(requestId: string, approved: boolean) {
|
|
319
|
+
usePermissionStore.getState().removeRequest(requestId);
|
|
320
|
+
|
|
321
|
+
try {
|
|
322
|
+
await fetch('/api/permission/respond', {
|
|
323
|
+
method: 'POST',
|
|
324
|
+
headers: { 'Content-Type': 'application/json' },
|
|
325
|
+
body: JSON.stringify({ requestId, approved }),
|
|
326
|
+
});
|
|
327
|
+
} catch {
|
|
328
|
+
// Best effort
|
|
329
|
+
}
|
|
330
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# skill-statusline v2.0 — Entry point
|
|
3
|
+
# Delegates to modular v2 engine if installed, falls back to v1 inline
|
|
4
|
+
|
|
5
|
+
STATUSLINE_DIR="${HOME}/.claude/statusline"
|
|
6
|
+
|
|
7
|
+
if [ -f "${STATUSLINE_DIR}/core.sh" ]; then
|
|
8
|
+
# v2: modular engine with themes, layouts, accurate context
|
|
9
|
+
exec bash "${STATUSLINE_DIR}/core.sh"
|
|
10
|
+
fi
|
|
11
|
+
|
|
12
|
+
# ── v1 fallback (inline legacy script) ──
|
|
13
|
+
# This runs if only statusline-command.sh was copied without the statusline/ directory
|
|
14
|
+
|
|
15
|
+
input=$(cat)
|
|
16
|
+
|
|
17
|
+
json_val() {
|
|
18
|
+
echo "$input" | grep -o "\"$1\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" | head -1 | sed 's/.*:.*"\(.*\)"/\1/'
|
|
19
|
+
}
|
|
20
|
+
json_num() {
|
|
21
|
+
echo "$input" | grep -o "\"$1\"[[:space:]]*:[[:space:]]*[0-9.]*" | head -1 | sed 's/.*:[[:space:]]*//'
|
|
22
|
+
}
|
|
23
|
+
to_fwd() {
|
|
24
|
+
echo "$1" | tr '\\' '/' | sed 's|//\+|/|g'
|
|
25
|
+
}
|
|
26
|
+
rpad() {
|
|
27
|
+
local str="$1" w="$2"
|
|
28
|
+
local plain
|
|
29
|
+
plain=$(printf '%b' "$str" | sed $'s/\033\\[[0-9;]*m//g')
|
|
30
|
+
local vlen=${#plain}
|
|
31
|
+
local need=$(( w - vlen ))
|
|
32
|
+
printf '%b' "$str"
|
|
33
|
+
[ "$need" -gt 0 ] && printf "%${need}s" ""
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
RST='\033[0m'; BOLD='\033[1m'
|
|
37
|
+
CYAN='\033[38;2;6;182;212m'; PURPLE='\033[38;2;168;85;247m'
|
|
38
|
+
GREEN='\033[38;2;34;197;94m'; YELLOW='\033[38;2;245;158;11m'
|
|
39
|
+
RED='\033[38;2;239;68;68m'; ORANGE='\033[38;2;251;146;60m'
|
|
40
|
+
WHITE='\033[38;2;228;228;231m'; PINK='\033[38;2;236;72;153m'
|
|
41
|
+
SEP_C='\033[38;2;55;55;62m'; DIM_BAR='\033[38;2;40;40;45m'
|
|
42
|
+
|
|
43
|
+
cwd=$(json_val "current_dir")
|
|
44
|
+
[ -z "$cwd" ] && cwd=$(json_val "cwd")
|
|
45
|
+
if [ -z "$cwd" ]; then dir_label="~"; clean_cwd=""
|
|
46
|
+
else
|
|
47
|
+
clean_cwd=$(to_fwd "$cwd")
|
|
48
|
+
dir_label=$(echo "$clean_cwd" | awk -F'/' '{if(NF>3) print $(NF-2)"/"$(NF-1)"/"$NF; else if(NF>2) print $(NF-1)"/"$NF; else print $0}')
|
|
49
|
+
[ -z "$dir_label" ] && dir_label="~"
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
model_display=$(json_val "display_name"); model_id=$(json_val "id")
|
|
53
|
+
[ -z "$model_display" ] && model_display="unknown"
|
|
54
|
+
model_ver=""; [ -n "$model_id" ] && model_ver=$(echo "$model_id" | sed -n 's/.*-\([0-9]*\)-\([0-9]*\)$/\1.\2/p')
|
|
55
|
+
if [ -n "$model_ver" ] && ! echo "$model_display" | grep -q '[0-9]'; then model_full="${model_display} ${model_ver}"; else model_full="$model_display"; fi
|
|
56
|
+
|
|
57
|
+
pct=$(json_num "used_percentage"); [ -z "$pct" ] && pct="0"; pct=$(echo "$pct" | cut -d. -f1)
|
|
58
|
+
if [ "$pct" -gt 75 ] 2>/dev/null; then CTX_CLR="$RED"
|
|
59
|
+
elif [ "$pct" -gt 40 ] 2>/dev/null; then CTX_CLR="$ORANGE"
|
|
60
|
+
else CTX_CLR="$WHITE"; fi
|
|
61
|
+
BAR_WIDTH=40; filled=$(( pct * BAR_WIDTH / 100 )); [ "$filled" -gt "$BAR_WIDTH" ] && filled=$BAR_WIDTH
|
|
62
|
+
empty=$(( BAR_WIDTH - filled )); bar_filled=""; bar_empty=""
|
|
63
|
+
i=0; while [ $i -lt $filled ]; do bar_filled="${bar_filled}█"; i=$((i+1)); done
|
|
64
|
+
i=0; while [ $i -lt $empty ]; do bar_empty="${bar_empty}░"; i=$((i+1)); done
|
|
65
|
+
ctx_bar="${CTX_CLR}${bar_filled}${RST}${DIM_BAR}${bar_empty}${RST} ${CTX_CLR}${pct}%${RST}"
|
|
66
|
+
|
|
67
|
+
branch="no-git"; gh_user=""; gh_repo=""; git_dirty=""
|
|
68
|
+
if [ -n "$clean_cwd" ]; then
|
|
69
|
+
branch=$(git --no-optional-locks -C "$clean_cwd" symbolic-ref --short HEAD 2>/dev/null)
|
|
70
|
+
[ -z "$branch" ] && branch=$(git --no-optional-locks -C "$clean_cwd" rev-parse --short HEAD 2>/dev/null)
|
|
71
|
+
if [ -n "$branch" ]; then
|
|
72
|
+
remote_url=$(git --no-optional-locks -C "$clean_cwd" remote get-url origin 2>/dev/null)
|
|
73
|
+
if [ -n "$remote_url" ]; then
|
|
74
|
+
gh_user=$(echo "$remote_url" | sed 's|.*github\.com[:/]\([^/]*\)/.*|\1|'); [ "$gh_user" = "$remote_url" ] && gh_user=""
|
|
75
|
+
gh_repo=$(echo "$remote_url" | sed 's|.*/\([^/]*\)\.git$|\1|; s|.*/\([^/]*\)$|\1|'); [ "$gh_repo" = "$remote_url" ] && gh_repo=""
|
|
76
|
+
fi
|
|
77
|
+
git --no-optional-locks -C "$clean_cwd" diff --cached --quiet 2>/dev/null || git_dirty="${GREEN}+${RST}"
|
|
78
|
+
git --no-optional-locks -C "$clean_cwd" diff --quiet 2>/dev/null || git_dirty="${git_dirty}${YELLOW}~${RST}"
|
|
79
|
+
fi
|
|
80
|
+
[ -z "$branch" ] && branch="no-git"
|
|
81
|
+
fi
|
|
82
|
+
[ -n "$gh_repo" ] && gh_label="${gh_user}/${gh_repo}/${branch}" || gh_label="$branch"
|
|
83
|
+
|
|
84
|
+
cost_raw=$(json_num "total_cost_usd")
|
|
85
|
+
if [ -z "$cost_raw" ] || [ "$cost_raw" = "0" ]; then cost_label='$0.00'
|
|
86
|
+
else cost_label=$(awk -v c="$cost_raw" 'BEGIN { if (c < 0.01) printf "$%.4f", c; else printf "$%.2f", c }'); fi
|
|
87
|
+
|
|
88
|
+
total_in=$(json_num "total_input_tokens"); total_out=$(json_num "total_output_tokens")
|
|
89
|
+
[ -z "$total_in" ] && total_in="0"; [ -z "$total_out" ] && total_out="0"
|
|
90
|
+
fmt_tok() { awk -v t="$1" 'BEGIN { if (t >= 1000000) printf "%.1fM", t/1000000; else if (t >= 1000) printf "%.0fk", t/1000; else printf "%d", t }'; }
|
|
91
|
+
tok_in=$(fmt_tok "$total_in"); tok_out=$(fmt_tok "$total_out")
|
|
92
|
+
tok_total=$(awk -v i="$total_in" -v o="$total_out" 'BEGIN { printf "%d", i + o }')
|
|
93
|
+
token_label="${tok_in} + ${tok_out} = $(fmt_tok "$tok_total")"
|
|
94
|
+
|
|
95
|
+
skill_label="Idle"
|
|
96
|
+
if [ -n "$clean_cwd" ]; then
|
|
97
|
+
search_path="$clean_cwd"
|
|
98
|
+
while [ -n "$search_path" ] && [ "$search_path" != "/" ]; do
|
|
99
|
+
proj_hash=$(echo "$search_path" | sed 's|^/\([a-zA-Z]\)/|\U\1--|; s|^[A-Z]:/|&|; s|:/|--|; s|/|-|g')
|
|
100
|
+
proj_dir="$HOME/.claude/projects/${proj_hash}"
|
|
101
|
+
if [ -d "$proj_dir" ]; then tpath=$(ls -t "$proj_dir"/*.jsonl 2>/dev/null | head -1); [ -n "$tpath" ] && break; fi
|
|
102
|
+
search_path=$(echo "$search_path" | sed 's|/[^/]*$||')
|
|
103
|
+
done
|
|
104
|
+
if [ -n "$tpath" ] && [ -f "$tpath" ]; then
|
|
105
|
+
recent_block=$(tail -200 "$tpath" 2>/dev/null)
|
|
106
|
+
last_tool=$(echo "$recent_block" | grep -o '"type":"tool_use","id":"[^"]*","name":"[^"]*"' | tail -1 | sed 's/.*"name":"\([^"]*\)".*/\1/')
|
|
107
|
+
if [ -n "$last_tool" ]; then
|
|
108
|
+
case "$last_tool" in
|
|
109
|
+
Task) skill_label="Agent" ;; Read) skill_label="Read" ;; Write) skill_label="Write" ;;
|
|
110
|
+
Edit) skill_label="Edit" ;; Glob) skill_label="Search(Files)" ;; Grep) skill_label="Search(Content)" ;;
|
|
111
|
+
Bash) skill_label="Terminal" ;; WebSearch) skill_label="Web Search" ;; *) skill_label="$last_tool" ;;
|
|
112
|
+
esac
|
|
113
|
+
fi
|
|
114
|
+
fi
|
|
115
|
+
fi
|
|
116
|
+
|
|
117
|
+
C1=38; S=$(printf '%b' " ${SEP_C}│${RST} ")
|
|
118
|
+
printf ' '; rpad "${PINK}Skill:${RST} ${PINK}${skill_label}${RST}" "$C1"; printf '%b' "$S"; printf '%b\n' "${WHITE}GitHub:${RST} ${WHITE}${gh_label}${RST}${git_dirty}"
|
|
119
|
+
printf ' '; rpad "${PURPLE}Model:${RST} ${PURPLE}${BOLD}${model_full}${RST}" "$C1"; printf '%b' "$S"; printf '%b\n' "${CYAN}Dir:${RST} ${CYAN}${dir_label}${RST}"
|
|
120
|
+
printf ' '; rpad "${YELLOW}Tokens:${RST} ${YELLOW}${token_label}${RST}" "$C1"; printf '%b' "$S"; printf '%b\n' "${GREEN}Cost:${RST} ${GREEN}${cost_label}${RST}"
|
|
121
|
+
printf ' '; printf '%b' "${CTX_CLR}Context:${RST} ${ctx_bar}"
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Show or modify statusline configuration options. Examples: /sls-config, /sls-config bar_width 50
|
|
3
|
+
argument-hint: "[key] [value]"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Statusline Configuration
|
|
7
|
+
|
|
8
|
+
View or modify statusline configuration.
|
|
9
|
+
|
|
10
|
+
## Current Config
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
!cat ~/.claude/statusline-config.json 2>/dev/null || echo '{"theme":"default","layout":"standard"}'
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Available Options
|
|
17
|
+
|
|
18
|
+
| Option | Type | Default | Description |
|
|
19
|
+
|--------|------|---------|-------------|
|
|
20
|
+
| `compaction_warning_threshold` | number | 85 | Context % at which "X% left" warning appears |
|
|
21
|
+
| `bar_width` | number | 40 | Width of the context progress bar in characters |
|
|
22
|
+
| `cache_ttl_seconds` | number | 5 | How long git/skill cache lives before refresh |
|
|
23
|
+
| `show_burn_rate` | boolean | false | Show cost-per-minute calculation |
|
|
24
|
+
| `show_vim_mode` | boolean | true | Show vim mode indicator (full layout only) |
|
|
25
|
+
| `show_agent_name` | boolean | true | Show active agent name (full layout only) |
|
|
26
|
+
|
|
27
|
+
## Instructions
|
|
28
|
+
|
|
29
|
+
If `$ARGUMENTS` has two values (key and value):
|
|
30
|
+
1. Run `ccsl config set <key> <value>` in the terminal
|
|
31
|
+
2. Show the result
|
|
32
|
+
3. Tell them changes take effect on next statusline refresh
|
|
33
|
+
|
|
34
|
+
If `$ARGUMENTS` has one value (just a key):
|
|
35
|
+
1. Read `~/.claude/statusline-config.json`
|
|
36
|
+
2. Show the current value of that specific option
|
|
37
|
+
3. Suggest how to change it
|
|
38
|
+
|
|
39
|
+
If no arguments:
|
|
40
|
+
1. Run `ccsl config` in the terminal to show all current settings
|
|
41
|
+
2. Show the output
|
|
42
|
+
3. Tell them they can change options with `/sls-config <key> <value>`
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Run statusline diagnostics — checks installation, config, performance, and identifies issues
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Statusline Doctor
|
|
6
|
+
|
|
7
|
+
Run comprehensive diagnostics on the statusline installation.
|
|
8
|
+
|
|
9
|
+
## Instructions
|
|
10
|
+
|
|
11
|
+
1. Run `ccsl doctor` in the terminal
|
|
12
|
+
2. Show the full output to the user
|
|
13
|
+
3. If there are any issues (indicated by X or warning symbols), explain what each issue means and how to fix it
|
|
14
|
+
|
|
15
|
+
## What It Checks
|
|
16
|
+
|
|
17
|
+
| Check | What It Verifies |
|
|
18
|
+
|-------|-----------------|
|
|
19
|
+
| bash | Bash is available and its version |
|
|
20
|
+
| git | Git is available (needed for GitHub field) |
|
|
21
|
+
| settings.json | Has `statusLine` config pointing to the command |
|
|
22
|
+
| statusline-command.sh | Entry point script exists at ~/.claude/ |
|
|
23
|
+
| v2 engine | core.sh exists in ~/.claude/statusline/ |
|
|
24
|
+
| Theme file | Active theme .sh file exists |
|
|
25
|
+
| Layout file | Active layout .sh file exists |
|
|
26
|
+
| CLAUDE.md | Agent redirect section is present (prevents built-in agent conflicts) |
|
|
27
|
+
| Config | statusline-config.json is valid JSON |
|
|
28
|
+
| Performance | Benchmark: <50ms excellent, <100ms good, >100ms slow |
|
|
29
|
+
|
|
30
|
+
## Common Fixes
|
|
31
|
+
|
|
32
|
+
- **Missing files**: Run `ccsl install` or `ccsl update`
|
|
33
|
+
- **Missing CLAUDE.md section**: Run `ccsl update`
|
|
34
|
+
- **Slow performance**: Normal on Windows Git Bash cold start — actual Claude Code rendering is faster
|
|
35
|
+
- **Invalid config**: Delete `~/.claude/statusline-config.json` and run `ccsl install --quick`
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Show all available statusline slash commands and CLI commands
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Statusline Help
|
|
6
|
+
|
|
7
|
+
Show the user all available statusline commands.
|
|
8
|
+
|
|
9
|
+
## Slash Commands (use inside Claude Code)
|
|
10
|
+
|
|
11
|
+
| Command | Description |
|
|
12
|
+
|---------|-------------|
|
|
13
|
+
| `/sls-theme` | List themes |
|
|
14
|
+
| `/sls-theme <name>` | Set theme (default, nord, tokyo-night, catppuccin, gruvbox) |
|
|
15
|
+
| `/sls-layout` | List layouts |
|
|
16
|
+
| `/sls-layout <name>` | Set layout (compact, standard, full) |
|
|
17
|
+
| `/sls-preview` | Preview with current settings |
|
|
18
|
+
| `/sls-preview <theme> <layout>` | Preview with overrides |
|
|
19
|
+
| `/sls-config` | Show all config options |
|
|
20
|
+
| `/sls-config <key> <value>` | Set a config option |
|
|
21
|
+
| `/sls-doctor` | Run diagnostics |
|
|
22
|
+
| `/sls-help` | This help |
|
|
23
|
+
|
|
24
|
+
## CLI Commands (use in any terminal)
|
|
25
|
+
|
|
26
|
+
| Command | Description |
|
|
27
|
+
|---------|-------------|
|
|
28
|
+
| `ccsl install` | Interactive install wizard |
|
|
29
|
+
| `ccsl install --quick` | Quick install with defaults |
|
|
30
|
+
| `ccsl uninstall` | Remove everything |
|
|
31
|
+
| `ccsl update` | Update scripts, keep config |
|
|
32
|
+
| `ccsl theme` / `ccsl theme set <name>` | List/set theme |
|
|
33
|
+
| `ccsl layout` / `ccsl layout set <name>` | List/set layout |
|
|
34
|
+
| `ccsl preview [--theme x] [--layout x]` | Preview rendering |
|
|
35
|
+
| `ccsl config` / `ccsl config set <k> <v>` | Show/set options |
|
|
36
|
+
| `ccsl doctor` | Run diagnostics |
|
|
37
|
+
| `ccsl version` | Show version |
|
|
38
|
+
| `ccsl help` | Show CLI help |
|
|
39
|
+
|
|
40
|
+
## Quick Examples
|
|
41
|
+
|
|
42
|
+
- Change to Nord theme: `/sls-theme nord`
|
|
43
|
+
- Switch to full layout (6 rows): `/sls-layout full`
|
|
44
|
+
- Preview Tokyo Night + compact: `/sls-preview tokyo-night compact`
|
|
45
|
+
- Lower compaction warning to 80%: `/sls-config compaction_warning_threshold 80`
|
|
46
|
+
- Check installation health: `/sls-doctor`
|
|
47
|
+
|
|
48
|
+
Present this information clearly to the user in a formatted way.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: List available statusline layouts or set a layout. Examples: /sls-layout, /sls-layout full
|
|
3
|
+
argument-hint: "[layout-name]"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Statusline Layout
|
|
7
|
+
|
|
8
|
+
Manage the statusline layout for Claude Code.
|
|
9
|
+
|
|
10
|
+
## Current Config
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
!cat ~/.claude/statusline-config.json 2>/dev/null || echo '{"theme":"default","layout":"standard"}'
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Available Layouts
|
|
17
|
+
|
|
18
|
+
| Layout | Rows | What It Shows |
|
|
19
|
+
|--------|------|---------------|
|
|
20
|
+
| `compact` | 2 | Model, Dir, Context%, Cost — minimal footprint |
|
|
21
|
+
| `standard` | 4 | Skill, Model, GitHub, Dir, Tokens, Cost, Context bar — balanced |
|
|
22
|
+
| `full` | 6 | Everything: adds Session tokens, Duration, Lines, Cache, Vim mode, Agent name |
|
|
23
|
+
|
|
24
|
+
## Instructions
|
|
25
|
+
|
|
26
|
+
If `$ARGUMENTS` is provided (a layout name):
|
|
27
|
+
1. Run `ccsl layout set $ARGUMENTS` in the terminal
|
|
28
|
+
2. Show the result to the user
|
|
29
|
+
3. Tell them to restart Claude Code or start a new conversation for the layout to take effect
|
|
30
|
+
|
|
31
|
+
If no arguments provided:
|
|
32
|
+
1. Run `ccsl layout` in the terminal to list layouts with the current selection highlighted
|
|
33
|
+
2. Show the output to the user
|
|
34
|
+
3. Tell them they can set a layout with `/sls-layout <name>` or `ccsl layout set <name>`
|
|
35
|
+
|
|
36
|
+
**Valid layout names:** compact, standard, full
|
|
37
|
+
|
|
38
|
+
If the user provides an invalid layout name, show them the valid options above.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Preview the statusline with sample data. Optionally specify theme/layout overrides. Examples: /sls-preview, /sls-preview nord full
|
|
3
|
+
argument-hint: "[theme] [layout]"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Statusline Preview
|
|
7
|
+
|
|
8
|
+
Preview the statusline rendering with sample data.
|
|
9
|
+
|
|
10
|
+
## Current Config
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
!cat ~/.claude/statusline-config.json 2>/dev/null || echo '{"theme":"default","layout":"standard"}'
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Instructions
|
|
17
|
+
|
|
18
|
+
Parse `$ARGUMENTS` for optional theme and layout overrides. Arguments can be in any order — detect which is a theme and which is a layout.
|
|
19
|
+
|
|
20
|
+
**Valid themes:** default, nord, tokyo-night, catppuccin, gruvbox
|
|
21
|
+
**Valid layouts:** compact, standard, full
|
|
22
|
+
|
|
23
|
+
Build the ccsl preview command:
|
|
24
|
+
- No arguments: run `ccsl preview`
|
|
25
|
+
- Theme only: run `ccsl preview --theme <name>`
|
|
26
|
+
- Layout only: run `ccsl preview --layout <name>`
|
|
27
|
+
- Both: run `ccsl preview --theme <name> --layout <name>`
|
|
28
|
+
|
|
29
|
+
Run the command in the terminal and show the output to the user.
|
|
30
|
+
|
|
31
|
+
After showing the preview, remind the user:
|
|
32
|
+
- To apply a theme: `/sls-theme <name>` or `ccsl theme set <name>`
|
|
33
|
+
- To apply a layout: `/sls-layout <name>` or `ccsl layout set <name>`
|
|
34
|
+
- Changes take effect on Claude Code restart
|