clideck 1.22.2
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 +21 -0
- package/README.md +77 -0
- package/activity.js +56 -0
- package/agent-presets.json +93 -0
- package/assets/clideck-themes.jpg +0 -0
- package/bin/clideck.js +2 -0
- package/config.js +96 -0
- package/handlers.js +297 -0
- package/opencode-bridge.js +148 -0
- package/opencode-plugin/clideck-bridge.js +24 -0
- package/package.json +47 -0
- package/paths.js +41 -0
- package/plugin-loader.js +285 -0
- package/plugins/trim-clip/clideck-plugin.json +13 -0
- package/plugins/trim-clip/client.js +31 -0
- package/plugins/trim-clip/index.js +10 -0
- package/plugins/voice-input/clideck-plugin.json +49 -0
- package/plugins/voice-input/client.js +196 -0
- package/plugins/voice-input/index.js +342 -0
- package/plugins/voice-input/python/mel_filters.npz +0 -0
- package/plugins/voice-input/python/whisper_turbo.py +416 -0
- package/plugins/voice-input/python/worker.py +135 -0
- package/public/fx/bold-beep-idle.mp3 +0 -0
- package/public/fx/default-beep.mp3 +0 -0
- package/public/fx/echo-beep-idle.mp3 +0 -0
- package/public/fx/musical-beep-idle.mp3 +0 -0
- package/public/fx/small-bleep-idle.mp3 +0 -0
- package/public/fx/soft-beep.mp3 +0 -0
- package/public/fx/space-idle.mp3 +0 -0
- package/public/img/claude-code.png +0 -0
- package/public/img/clideck-logo-icon.png +0 -0
- package/public/img/clideck-logo-terminal-panel.png +0 -0
- package/public/img/codex.png +0 -0
- package/public/img/gemini.png +0 -0
- package/public/img/opencode.png +0 -0
- package/public/index.html +243 -0
- package/public/js/app.js +794 -0
- package/public/js/color-mode.js +51 -0
- package/public/js/confirm.js +27 -0
- package/public/js/creator.js +201 -0
- package/public/js/drag.js +134 -0
- package/public/js/folder-picker.js +81 -0
- package/public/js/hotkeys.js +90 -0
- package/public/js/nav.js +56 -0
- package/public/js/profiles.js +22 -0
- package/public/js/prompts.js +325 -0
- package/public/js/settings.js +489 -0
- package/public/js/state.js +15 -0
- package/public/js/terminals.js +905 -0
- package/public/js/toast.js +62 -0
- package/public/js/utils.js +27 -0
- package/public/tailwind.css +1 -0
- package/server.js +126 -0
- package/sessions.js +375 -0
- package/telemetry-receiver.js +129 -0
- package/themes.js +247 -0
- package/transcript.js +90 -0
- package/utils.js +66 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// Minimal OTLP HTTP/JSON log receiver.
|
|
2
|
+
// CLI agents export telemetry events here; we capture agent session IDs
|
|
3
|
+
// (for resume) and detect whether telemetry is configured (setup prompts).
|
|
4
|
+
|
|
5
|
+
const activity = new Map(); // sessionId → has received events
|
|
6
|
+
const pendingSetup = new Map(); // sessionId → timer (waiting for first event)
|
|
7
|
+
let broadcastFn = null;
|
|
8
|
+
let sessionsFn = null;
|
|
9
|
+
|
|
10
|
+
function init(broadcast, getSessions) {
|
|
11
|
+
broadcastFn = broadcast;
|
|
12
|
+
sessionsFn = getSessions;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Flatten OTLP attribute arrays into plain objects
|
|
16
|
+
function parseAttrs(attrs) {
|
|
17
|
+
const out = {};
|
|
18
|
+
if (!attrs) return out;
|
|
19
|
+
for (const a of attrs) {
|
|
20
|
+
const v = a.value;
|
|
21
|
+
out[a.key] = v.stringValue ?? v.intValue ?? v.doubleValue ?? v.boolValue ?? '';
|
|
22
|
+
}
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Express-compatible handler for POST /v1/logs
|
|
27
|
+
function handleLogs(req, res) {
|
|
28
|
+
const body = req.body;
|
|
29
|
+
if (!body?.resourceLogs) return res.writeHead(200).end('{}');
|
|
30
|
+
|
|
31
|
+
for (const rl of body.resourceLogs) {
|
|
32
|
+
const resAttrs = parseAttrs(rl.resource?.attributes);
|
|
33
|
+
const sessionId = resAttrs['clideck.session_id'];
|
|
34
|
+
|
|
35
|
+
// DEBUG: log all incoming telemetry with resource attributes
|
|
36
|
+
const serviceName = resAttrs['service.name'] || 'unknown';
|
|
37
|
+
let resolvedId = sessionId;
|
|
38
|
+
|
|
39
|
+
// Fallback: if agent doesn't include clideck.session_id (e.g. Gemini ignores
|
|
40
|
+
// OTEL_RESOURCE_ATTRIBUTES), match by finding a pending session for this agent
|
|
41
|
+
if (!resolvedId) {
|
|
42
|
+
for (const [id, pending] of pendingSetup) {
|
|
43
|
+
const sess = sessionsFn?.()?.get(id);
|
|
44
|
+
if (!sess) continue;
|
|
45
|
+
// Match: no activity yet, and agent type matches
|
|
46
|
+
if (!activity.has(id) && pending.bin && serviceName.includes(pending.bin)) { resolvedId = id; break; }
|
|
47
|
+
}
|
|
48
|
+
if (resolvedId) {
|
|
49
|
+
console.log(`Telemetry: matched ${serviceName} to session ${resolvedId.slice(0, 8)} (no clideck.session_id — fallback match)`);
|
|
50
|
+
} else {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Any log record for this session means telemetry is working
|
|
56
|
+
const firstEvent = !activity.has(resolvedId);
|
|
57
|
+
cancelPendingSetup(resolvedId);
|
|
58
|
+
activity.set(resolvedId, true);
|
|
59
|
+
|
|
60
|
+
const sess = sessionsFn?.()?.get(resolvedId);
|
|
61
|
+
const agent = resAttrs['service.name'] || sess?.name || resolvedId.slice(0, 8);
|
|
62
|
+
|
|
63
|
+
if (firstEvent) console.log(`Telemetry: first event from ${agent} (${resolvedId.slice(0, 8)})`);
|
|
64
|
+
|
|
65
|
+
// Process each log record — capture session ID for resume
|
|
66
|
+
let captured = false;
|
|
67
|
+
for (const sl of rl.scopeLogs || []) {
|
|
68
|
+
for (const lr of sl.logRecords || []) {
|
|
69
|
+
const attrs = parseAttrs(lr.attributes);
|
|
70
|
+
|
|
71
|
+
const agentSessionId = attrs['session.id'] || attrs['conversation.id'];
|
|
72
|
+
if (agentSessionId && sess) {
|
|
73
|
+
// Prefer interactive session ID (Gemini sends non-interactive init events first)
|
|
74
|
+
const dominated = sess.sessionToken && attrs['interactive'] === true;
|
|
75
|
+
if (!sess.sessionToken || dominated) {
|
|
76
|
+
sess.sessionToken = agentSessionId;
|
|
77
|
+
console.log(`Telemetry: captured session ID ${agentSessionId} for ${agent} (${resolvedId.slice(0, 8)})`);
|
|
78
|
+
captured = true;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// DEBUG: dump attributes if we haven't captured a session ID yet
|
|
85
|
+
if (!captured && sess && !sess.sessionToken && resolvedId) {
|
|
86
|
+
const allAttrs = [];
|
|
87
|
+
for (const sl of rl.scopeLogs || []) {
|
|
88
|
+
for (const lr of sl.logRecords || []) allAttrs.push(Object.keys(parseAttrs(lr.attributes)));
|
|
89
|
+
}
|
|
90
|
+
console.log(`Telemetry [${agent}]: no session.id found. Record attrs: ${JSON.stringify([...new Set(allAttrs.flat())])}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
res.writeHead(200).end('{}');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Watch a newly spawned session — if no telemetry arrives, notify frontend
|
|
98
|
+
function watchSession(sessionId, bin) {
|
|
99
|
+
if (pendingSetup.has(sessionId)) return;
|
|
100
|
+
const timer = setTimeout(() => {
|
|
101
|
+
pendingSetup.delete(sessionId);
|
|
102
|
+
// Don't fire if telemetry arrived between timer start and now
|
|
103
|
+
if (!activity.has(sessionId)) {
|
|
104
|
+
broadcastFn?.({ type: 'session.needsSetup', id: sessionId });
|
|
105
|
+
}
|
|
106
|
+
}, 10000);
|
|
107
|
+
pendingSetup.set(sessionId, { timer, bin });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function cancelPendingSetup(sessionId) {
|
|
111
|
+
const pending = pendingSetup.get(sessionId);
|
|
112
|
+
if (pending) {
|
|
113
|
+
clearTimeout(pending.timer);
|
|
114
|
+
pendingSetup.delete(sessionId);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function clear(id) {
|
|
119
|
+
activity.delete(id);
|
|
120
|
+
const pending = pendingSetup.get(id);
|
|
121
|
+
if (pending) { clearTimeout(pending.timer); pendingSetup.delete(id); }
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Returns true if we've received telemetry events for this session
|
|
125
|
+
function hasEvents(id) {
|
|
126
|
+
return activity.has(id);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
module.exports = { init, handleLogs, clear, hasEvents, watchSession };
|
package/themes.js
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
// Built-in terminal theme presets.
|
|
2
|
+
// Each theme is a valid xterm.js ITheme object.
|
|
3
|
+
// Users can add custom themes via custom-themes.json in the project root.
|
|
4
|
+
|
|
5
|
+
const { readFileSync, existsSync } = require('fs');
|
|
6
|
+
const { join } = require('path');
|
|
7
|
+
|
|
8
|
+
const BUILT_IN = [
|
|
9
|
+
{
|
|
10
|
+
id: 'default',
|
|
11
|
+
name: 'Midnight',
|
|
12
|
+
accent: '#3b82f6',
|
|
13
|
+
theme: {
|
|
14
|
+
background: '#020617',
|
|
15
|
+
foreground: '#e2e8f0',
|
|
16
|
+
cursor: '#e2e8f0',
|
|
17
|
+
selectionBackground: '#334155',
|
|
18
|
+
black: '#1e293b', red: '#ef4444', green: '#22c55e', yellow: '#eab308',
|
|
19
|
+
blue: '#3b82f6', magenta: '#a855f7', cyan: '#06b6d4', white: '#e2e8f0',
|
|
20
|
+
brightBlack: '#475569', brightRed: '#f87171', brightGreen: '#4ade80', brightYellow: '#facc15',
|
|
21
|
+
brightBlue: '#60a5fa', brightMagenta: '#c084fc', brightCyan: '#22d3ee', brightWhite: '#f8fafc',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: 'dracula',
|
|
26
|
+
name: 'Dracula',
|
|
27
|
+
accent: '#bd93f9',
|
|
28
|
+
theme: {
|
|
29
|
+
background: '#282a36',
|
|
30
|
+
foreground: '#f8f8f2',
|
|
31
|
+
cursor: '#f8f8f2',
|
|
32
|
+
selectionBackground: '#44475a',
|
|
33
|
+
black: '#21222c', red: '#ff5555', green: '#50fa7b', yellow: '#f1fa8c',
|
|
34
|
+
blue: '#bd93f9', magenta: '#ff79c6', cyan: '#8be9fd', white: '#f8f8f2',
|
|
35
|
+
brightBlack: '#6272a4', brightRed: '#ff6e6e', brightGreen: '#69ff94', brightYellow: '#ffffa5',
|
|
36
|
+
brightBlue: '#d6acff', brightMagenta: '#ff92df', brightCyan: '#a4ffff', brightWhite: '#ffffff',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: 'nord',
|
|
41
|
+
name: 'Nord',
|
|
42
|
+
accent: '#81a1c1',
|
|
43
|
+
theme: {
|
|
44
|
+
background: '#2e3440',
|
|
45
|
+
foreground: '#d8dee9',
|
|
46
|
+
cursor: '#d8dee9',
|
|
47
|
+
selectionBackground: '#434c5e',
|
|
48
|
+
black: '#3b4252', red: '#bf616a', green: '#a3be8c', yellow: '#ebcb8b',
|
|
49
|
+
blue: '#81a1c1', magenta: '#b48ead', cyan: '#88c0d0', white: '#e5e9f0',
|
|
50
|
+
brightBlack: '#4c566a', brightRed: '#bf616a', brightGreen: '#a3be8c', brightYellow: '#ebcb8b',
|
|
51
|
+
brightBlue: '#81a1c1', brightMagenta: '#b48ead', brightCyan: '#8fbcbb', brightWhite: '#eceff4',
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: 'catppuccin-mocha',
|
|
56
|
+
name: 'Catppuccin Mocha',
|
|
57
|
+
accent: '#89b4fa',
|
|
58
|
+
theme: {
|
|
59
|
+
background: '#1e1e2e',
|
|
60
|
+
foreground: '#cdd6f4',
|
|
61
|
+
cursor: '#f5e0dc',
|
|
62
|
+
selectionBackground: '#45475a',
|
|
63
|
+
black: '#45475a', red: '#f38ba8', green: '#a6e3a1', yellow: '#f9e2af',
|
|
64
|
+
blue: '#89b4fa', magenta: '#f5c2e7', cyan: '#94e2d5', white: '#bac2de',
|
|
65
|
+
brightBlack: '#585b70', brightRed: '#f38ba8', brightGreen: '#a6e3a1', brightYellow: '#f9e2af',
|
|
66
|
+
brightBlue: '#89b4fa', brightMagenta: '#f5c2e7', brightCyan: '#94e2d5', brightWhite: '#a6adc8',
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: 'solarized-dark',
|
|
71
|
+
name: 'Solarized Dark',
|
|
72
|
+
accent: '#268bd2',
|
|
73
|
+
theme: {
|
|
74
|
+
background: '#002b36',
|
|
75
|
+
foreground: '#839496',
|
|
76
|
+
cursor: '#839496',
|
|
77
|
+
selectionBackground: '#073642',
|
|
78
|
+
black: '#073642', red: '#dc322f', green: '#859900', yellow: '#b58900',
|
|
79
|
+
blue: '#268bd2', magenta: '#d33682', cyan: '#2aa198', white: '#eee8d5',
|
|
80
|
+
brightBlack: '#586e75', brightRed: '#cb4b16', brightGreen: '#586e75', brightYellow: '#657b83',
|
|
81
|
+
brightBlue: '#839496', brightMagenta: '#6c71c4', brightCyan: '#93a1a1', brightWhite: '#fdf6e3',
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: 'one-dark',
|
|
86
|
+
name: 'One Dark',
|
|
87
|
+
accent: '#61afef',
|
|
88
|
+
theme: {
|
|
89
|
+
background: '#282c34',
|
|
90
|
+
foreground: '#abb2bf',
|
|
91
|
+
cursor: '#528bff',
|
|
92
|
+
selectionBackground: '#3e4451',
|
|
93
|
+
black: '#3f4451', red: '#e06c75', green: '#98c379', yellow: '#e5c07b',
|
|
94
|
+
blue: '#61afef', magenta: '#c678dd', cyan: '#56b6c2', white: '#d7dae0',
|
|
95
|
+
brightBlack: '#4f5666', brightRed: '#be5046', brightGreen: '#98c379', brightYellow: '#d19a66',
|
|
96
|
+
brightBlue: '#61afef', brightMagenta: '#c678dd', brightCyan: '#56b6c2', brightWhite: '#e6e6e6',
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
id: 'monokai',
|
|
101
|
+
name: 'Monokai',
|
|
102
|
+
accent: '#66d9ef',
|
|
103
|
+
theme: {
|
|
104
|
+
background: '#272822',
|
|
105
|
+
foreground: '#f8f8f2',
|
|
106
|
+
cursor: '#f8f8f0',
|
|
107
|
+
selectionBackground: '#49483e',
|
|
108
|
+
black: '#272822', red: '#f92672', green: '#a6e22e', yellow: '#f4bf75',
|
|
109
|
+
blue: '#66d9ef', magenta: '#ae81ff', cyan: '#a1efe4', white: '#f8f8f2',
|
|
110
|
+
brightBlack: '#75715e', brightRed: '#f92672', brightGreen: '#a6e22e', brightYellow: '#f4bf75',
|
|
111
|
+
brightBlue: '#66d9ef', brightMagenta: '#ae81ff', brightCyan: '#a1efe4', brightWhite: '#f9f8f5',
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
id: 'tokyo-night',
|
|
116
|
+
name: 'Tokyo Night',
|
|
117
|
+
accent: '#7aa2f7',
|
|
118
|
+
theme: {
|
|
119
|
+
background: '#1a1b26',
|
|
120
|
+
foreground: '#c0caf5',
|
|
121
|
+
cursor: '#c0caf5',
|
|
122
|
+
selectionBackground: '#33467c',
|
|
123
|
+
black: '#15161e', red: '#f7768e', green: '#9ece6a', yellow: '#e0af68',
|
|
124
|
+
blue: '#7aa2f7', magenta: '#bb9af7', cyan: '#7dcfff', white: '#a9b1d6',
|
|
125
|
+
brightBlack: '#414868', brightRed: '#f7768e', brightGreen: '#9ece6a', brightYellow: '#e0af68',
|
|
126
|
+
brightBlue: '#7aa2f7', brightMagenta: '#bb9af7', brightCyan: '#7dcfff', brightWhite: '#c0caf5',
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
id: 'github-dark',
|
|
131
|
+
name: 'GitHub Dark',
|
|
132
|
+
accent: '#58a6ff',
|
|
133
|
+
theme: {
|
|
134
|
+
background: '#0d1117',
|
|
135
|
+
foreground: '#c9d1d9',
|
|
136
|
+
cursor: '#c9d1d9',
|
|
137
|
+
selectionBackground: '#264f78',
|
|
138
|
+
black: '#484f58', red: '#ff7b72', green: '#3fb950', yellow: '#d29922',
|
|
139
|
+
blue: '#58a6ff', magenta: '#bc8cff', cyan: '#39c5cf', white: '#b1bac4',
|
|
140
|
+
brightBlack: '#6e7681', brightRed: '#ffa198', brightGreen: '#56d364', brightYellow: '#e3b341',
|
|
141
|
+
brightBlue: '#79c0ff', brightMagenta: '#d2a8ff', brightCyan: '#56d4dd', brightWhite: '#f0f6fc',
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
id: 'gruvbox-dark',
|
|
146
|
+
name: 'Gruvbox Dark',
|
|
147
|
+
accent: '#d79921',
|
|
148
|
+
theme: {
|
|
149
|
+
background: '#282828',
|
|
150
|
+
foreground: '#ebdbb2',
|
|
151
|
+
cursor: '#ebdbb2',
|
|
152
|
+
selectionBackground: '#3c3836',
|
|
153
|
+
black: '#282828', red: '#cc241d', green: '#98971a', yellow: '#d79921',
|
|
154
|
+
blue: '#458588', magenta: '#b16286', cyan: '#689d6a', white: '#a89984',
|
|
155
|
+
brightBlack: '#928374', brightRed: '#fb4934', brightGreen: '#b8bb26', brightYellow: '#fabd2f',
|
|
156
|
+
brightBlue: '#83a598', brightMagenta: '#d3869b', brightCyan: '#8ec07c', brightWhite: '#ebdbb2',
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
// ── Light themes ──
|
|
160
|
+
{
|
|
161
|
+
id: 'github-light',
|
|
162
|
+
name: 'GitHub Light',
|
|
163
|
+
accent: '#0969da',
|
|
164
|
+
theme: {
|
|
165
|
+
background: '#f6f8fa',
|
|
166
|
+
foreground: '#24292f',
|
|
167
|
+
cursor: '#044289',
|
|
168
|
+
selectionBackground: '#b6d4fe',
|
|
169
|
+
black: '#24292f', red: '#cf222e', green: '#116329', yellow: '#9a6700',
|
|
170
|
+
blue: '#0550ae', magenta: '#8250df', cyan: '#1b7c83', white: '#6e7781',
|
|
171
|
+
brightBlack: '#57606a', brightRed: '#a40e26', brightGreen: '#1a7f37', brightYellow: '#7d4e00',
|
|
172
|
+
brightBlue: '#0969da', brightMagenta: '#a475f9', brightCyan: '#3192aa', brightWhite: '#8c959f',
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
id: 'solarized-light',
|
|
177
|
+
name: 'Solarized Light',
|
|
178
|
+
accent: '#268bd2',
|
|
179
|
+
theme: {
|
|
180
|
+
background: '#fdf6e3',
|
|
181
|
+
foreground: '#657b83',
|
|
182
|
+
cursor: '#586e75',
|
|
183
|
+
selectionBackground: '#eee8d5',
|
|
184
|
+
black: '#073642', red: '#dc322f', green: '#859900', yellow: '#b58900',
|
|
185
|
+
blue: '#268bd2', magenta: '#d33682', cyan: '#2aa198', white: '#93a1a1',
|
|
186
|
+
brightBlack: '#586e75', brightRed: '#cb4b16', brightGreen: '#859900', brightYellow: '#b58900',
|
|
187
|
+
brightBlue: '#268bd2', brightMagenta: '#6c71c4', brightCyan: '#2aa198', brightWhite: '#839496',
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
id: 'catppuccin-latte',
|
|
192
|
+
name: 'Catppuccin Latte',
|
|
193
|
+
accent: '#1e66f5',
|
|
194
|
+
theme: {
|
|
195
|
+
background: '#eff1f5',
|
|
196
|
+
foreground: '#4c4f69',
|
|
197
|
+
cursor: '#dc8a78',
|
|
198
|
+
selectionBackground: '#ccd0da',
|
|
199
|
+
black: '#5c5f77', red: '#d20f39', green: '#40a02b', yellow: '#df8e1d',
|
|
200
|
+
blue: '#1e66f5', magenta: '#ea76cb', cyan: '#179299', white: '#7c7f93',
|
|
201
|
+
brightBlack: '#6c6f85', brightRed: '#d20f39', brightGreen: '#40a02b', brightYellow: '#df8e1d',
|
|
202
|
+
brightBlue: '#1e66f5', brightMagenta: '#ea76cb', brightCyan: '#179299', brightWhite: '#8c8fa1',
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
id: 'one-light',
|
|
207
|
+
name: 'One Light',
|
|
208
|
+
accent: '#4078f2',
|
|
209
|
+
theme: {
|
|
210
|
+
background: '#fafafa',
|
|
211
|
+
foreground: '#383a42',
|
|
212
|
+
cursor: '#526eff',
|
|
213
|
+
selectionBackground: '#d4d8e0',
|
|
214
|
+
black: '#383a42', red: '#e45649', green: '#50a14f', yellow: '#c18401',
|
|
215
|
+
blue: '#4078f2', magenta: '#a626a4', cyan: '#0184bc', white: '#696c77',
|
|
216
|
+
brightBlack: '#696c77', brightRed: '#e45649', brightGreen: '#50a14f', brightYellow: '#c18401',
|
|
217
|
+
brightBlue: '#4078f2', brightMagenta: '#a626a4', brightCyan: '#0184bc', brightWhite: '#a0a1a7',
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
id: 'rose-pine-dawn',
|
|
222
|
+
name: 'Rosé Pine Dawn',
|
|
223
|
+
accent: '#907aa9',
|
|
224
|
+
theme: {
|
|
225
|
+
background: '#faf4ed',
|
|
226
|
+
foreground: '#575279',
|
|
227
|
+
cursor: '#575279',
|
|
228
|
+
selectionBackground: '#dfdad9',
|
|
229
|
+
black: '#575279', red: '#b4637a', green: '#286983', yellow: '#ea9d34',
|
|
230
|
+
blue: '#56949f', magenta: '#907aa9', cyan: '#d7827e', white: '#797593',
|
|
231
|
+
brightBlack: '#797593', brightRed: '#b4637a', brightGreen: '#286983', brightYellow: '#ea9d34',
|
|
232
|
+
brightBlue: '#56949f', brightMagenta: '#907aa9', brightCyan: '#d7827e', brightWhite: '#9893a5',
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
];
|
|
236
|
+
|
|
237
|
+
// Load user custom themes from ~/.clideck/custom-themes.json
|
|
238
|
+
function loadCustom() {
|
|
239
|
+
const { DATA_DIR } = require('./paths');
|
|
240
|
+
const p = join(DATA_DIR, 'custom-themes.json');
|
|
241
|
+
if (!existsSync(p)) return [];
|
|
242
|
+
try { return JSON.parse(readFileSync(p, 'utf8')); } catch { return []; }
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const THEMES = [...BUILT_IN, ...loadCustom()];
|
|
246
|
+
|
|
247
|
+
module.exports = THEMES;
|
package/transcript.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
const { appendFile, mkdirSync, existsSync, readdirSync, readFileSync, unlinkSync } = require('fs');
|
|
2
|
+
const { join, basename } = require('path');
|
|
3
|
+
const { DATA_DIR } = require('./paths');
|
|
4
|
+
|
|
5
|
+
const DIR = join(DATA_DIR, 'transcripts');
|
|
6
|
+
const ANSI_RE = /\x1b[\[\]()#;?]*[0-9;]*[a-zA-Z@`~]|\x1b\].*?(?:\x07|\x1b\\)|\x1b.|\r|\x07/g;
|
|
7
|
+
const MAX_CACHE = 50 * 1024;
|
|
8
|
+
|
|
9
|
+
const inputBuf = {};
|
|
10
|
+
const outputBuf = {};
|
|
11
|
+
const cache = {};
|
|
12
|
+
let broadcast = null;
|
|
13
|
+
|
|
14
|
+
function init(bc, validIds) {
|
|
15
|
+
broadcast = bc;
|
|
16
|
+
if (!existsSync(DIR)) mkdirSync(DIR, { recursive: true });
|
|
17
|
+
for (const file of readdirSync(DIR).filter(f => f.endsWith('.jsonl'))) {
|
|
18
|
+
const id = basename(file, '.jsonl');
|
|
19
|
+
if (validIds && !validIds.has(id)) { try { unlinkSync(join(DIR, file)); } catch {} continue; }
|
|
20
|
+
try {
|
|
21
|
+
const lines = readFileSync(join(DIR, file), 'utf8').trim().split('\n');
|
|
22
|
+
cache[id] = lines.map(l => { try { return JSON.parse(l).text; } catch { return ''; } }).join('\n');
|
|
23
|
+
if (cache[id].length > MAX_CACHE) cache[id] = cache[id].slice(-MAX_CACHE);
|
|
24
|
+
} catch {}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function fpath(id) { return join(DIR, `${id}.jsonl`); }
|
|
29
|
+
|
|
30
|
+
function store(id, role, text) {
|
|
31
|
+
appendFile(fpath(id), JSON.stringify({ ts: Date.now(), role, text }) + '\n', () => {});
|
|
32
|
+
if (!cache[id]) cache[id] = '';
|
|
33
|
+
cache[id] += '\n' + text;
|
|
34
|
+
if (cache[id].length > MAX_CACHE) cache[id] = cache[id].slice(-MAX_CACHE);
|
|
35
|
+
if (broadcast) broadcast({ type: 'transcript.append', id, text });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function trackInput(id, data) {
|
|
39
|
+
if (!inputBuf[id]) inputBuf[id] = { text: '', esc: false };
|
|
40
|
+
const buf = inputBuf[id];
|
|
41
|
+
for (const ch of data) {
|
|
42
|
+
if (ch === '\x1b') { buf.esc = true; continue; }
|
|
43
|
+
if (buf.esc) {
|
|
44
|
+
if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || ch === '~') buf.esc = false;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (ch === '\r' || ch === '\n') {
|
|
48
|
+
const line = buf.text.trim();
|
|
49
|
+
if (line) store(id, 'user', line);
|
|
50
|
+
buf.text = '';
|
|
51
|
+
} else if (ch === '\x7f' || ch === '\x08') {
|
|
52
|
+
const chars = Array.from(buf.text);
|
|
53
|
+
chars.pop();
|
|
54
|
+
buf.text = chars.join('');
|
|
55
|
+
} else if (ch >= ' ') {
|
|
56
|
+
buf.text += ch;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function trackOutput(id, data) {
|
|
62
|
+
if (!outputBuf[id]) outputBuf[id] = { text: '', timer: null };
|
|
63
|
+
const buf = outputBuf[id];
|
|
64
|
+
buf.text += data;
|
|
65
|
+
clearTimeout(buf.timer);
|
|
66
|
+
buf.timer = setTimeout(() => flush(id), 300);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function flush(id) {
|
|
70
|
+
const buf = outputBuf[id];
|
|
71
|
+
if (!buf?.text) return;
|
|
72
|
+
const clean = buf.text.replace(ANSI_RE, '').replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, '');
|
|
73
|
+
const lines = clean.split('\n').map(l => l.trim()).filter(l => l.length > 2);
|
|
74
|
+
buf.text = '';
|
|
75
|
+
if (lines.length) store(id, 'agent', lines.join('\n'));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function getCache() { return { ...cache }; }
|
|
79
|
+
|
|
80
|
+
function clear(id) {
|
|
81
|
+
flush(id);
|
|
82
|
+
delete inputBuf[id];
|
|
83
|
+
if (outputBuf[id]) {
|
|
84
|
+
clearTimeout(outputBuf[id].timer);
|
|
85
|
+
delete outputBuf[id];
|
|
86
|
+
}
|
|
87
|
+
delete cache[id];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
module.exports = { init, trackInput, trackOutput, getCache, clear };
|
package/utils.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
const { chmodSync, statSync, readdirSync } = require('fs');
|
|
2
|
+
const { dirname, join } = require('path');
|
|
3
|
+
|
|
4
|
+
function ensurePtyHelper() {
|
|
5
|
+
if (process.platform === 'win32') return;
|
|
6
|
+
try {
|
|
7
|
+
const pkgDir = dirname(require.resolve('node-pty/package.json'));
|
|
8
|
+
const helper = join(pkgDir, 'prebuilds', `${process.platform}-${process.arch}`, 'spawn-helper');
|
|
9
|
+
const mode = statSync(helper).mode & 0o777;
|
|
10
|
+
if ((mode & 0o111) === 0) chmodSync(helper, mode | 0o111);
|
|
11
|
+
} catch {}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function parseCommand(str) {
|
|
15
|
+
const parts = [];
|
|
16
|
+
let current = '';
|
|
17
|
+
let inQuote = null;
|
|
18
|
+
for (const ch of str) {
|
|
19
|
+
if (inQuote) {
|
|
20
|
+
if (ch === inQuote) inQuote = null;
|
|
21
|
+
else current += ch;
|
|
22
|
+
} else if (ch === '"' || ch === "'") {
|
|
23
|
+
inQuote = ch;
|
|
24
|
+
} else if (ch === ' ' || ch === '\t') {
|
|
25
|
+
if (current) { parts.push(current); current = ''; }
|
|
26
|
+
} else {
|
|
27
|
+
current += ch;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (current) parts.push(current);
|
|
31
|
+
if (!parts.length) return [defaultShell];
|
|
32
|
+
// On Windows, node-pty can't resolve non-.exe commands via PATH (e.g. claude, codex).
|
|
33
|
+
// Wrap through cmd.exe /c so Windows handles PATHEXT resolution natively.
|
|
34
|
+
if (process.platform === 'win32' && !parts[0].match(/\.(exe|com)$/i) && !/^[a-z]:\\/i.test(parts[0])) {
|
|
35
|
+
return [process.env.COMSPEC || 'cmd.exe', '/c', ...parts];
|
|
36
|
+
}
|
|
37
|
+
return parts;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function resolveValidDir(dir) {
|
|
41
|
+
try {
|
|
42
|
+
if (dir && statSync(dir).isDirectory()) return dir;
|
|
43
|
+
} catch {}
|
|
44
|
+
return require('os').homedir();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function listDirs(path) {
|
|
48
|
+
try {
|
|
49
|
+
return readdirSync(path, { withFileTypes: true })
|
|
50
|
+
.filter(d => d.isDirectory() && !d.name.startsWith('.'))
|
|
51
|
+
.map(d => d.name)
|
|
52
|
+
.sort();
|
|
53
|
+
} catch (e) {
|
|
54
|
+
return { error: e.message };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const defaultShell = process.platform === 'win32' ? (process.env.COMSPEC || 'cmd.exe') : '/bin/zsh';
|
|
59
|
+
|
|
60
|
+
function binName(command) {
|
|
61
|
+
const m = command.match(/^(['"])(.*?)\1/);
|
|
62
|
+
const exec = m ? m[2] : command;
|
|
63
|
+
return exec.split(/[\\/]/).pop().split(/\s/)[0].replace(/\.(exe|cmd)$/i, '');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = { ensurePtyHelper, parseCommand, resolveValidDir, listDirs, defaultShell, binName };
|