@jhizzard/termdeck 0.2.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 +21 -0
- package/README.md +242 -0
- package/config/config.example.yaml +58 -0
- package/config/secrets.env.example +17 -0
- package/package.json +65 -0
- package/packages/cli/src/index.js +101 -0
- package/packages/client/public/index.html +3444 -0
- package/packages/server/src/config.js +308 -0
- package/packages/server/src/database.js +130 -0
- package/packages/server/src/engram-bridge/index.js +232 -0
- package/packages/server/src/index.js +581 -0
- package/packages/server/src/rag.js +216 -0
- package/packages/server/src/session-logger.js +166 -0
- package/packages/server/src/session.js +421 -0
- package/packages/server/src/themes.js +250 -0
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
// Session manager - PTY lifecycle, metadata tracking, output analysis
|
|
2
|
+
// Each session wraps a node-pty instance with rich metadata
|
|
3
|
+
|
|
4
|
+
const { v4: uuidv4 } = require('uuid');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
// Strip ANSI escape codes for pattern matching
|
|
9
|
+
function stripAnsi(str) {
|
|
10
|
+
return str
|
|
11
|
+
.replace(/\x1b\[[\?]?[0-9;]*[A-Za-z]/g, '') // CSI sequences (including ?-prefixed like bracketed paste)
|
|
12
|
+
.replace(/\x1b\][^\x07]*\x07/g, '') // OSC sequences
|
|
13
|
+
.replace(/\x1b[()][A-Z0-9]/g, '') // Character set sequences
|
|
14
|
+
.replace(/\x1b[>=<]/g, ''); // Keypad/cursor modes
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Pattern matchers for detecting terminal type and status
|
|
18
|
+
const PATTERNS = {
|
|
19
|
+
claudeCode: {
|
|
20
|
+
prompt: /^[>❯]\s/m,
|
|
21
|
+
thinking: /\b(thinking|Thinking)\b/,
|
|
22
|
+
editing: /^(Edit|Create|Update|Delete)\s/m,
|
|
23
|
+
tool: /^⏺\s/m,
|
|
24
|
+
idle: /^>\s*$/m
|
|
25
|
+
},
|
|
26
|
+
geminiCli: {
|
|
27
|
+
prompt: /^gemini>\s/m,
|
|
28
|
+
thinking: /\b(Generating|Working)\b/,
|
|
29
|
+
},
|
|
30
|
+
pythonServer: {
|
|
31
|
+
uvicorn: /Uvicorn running on/,
|
|
32
|
+
flask: /Running on http/,
|
|
33
|
+
django: /Starting development server/,
|
|
34
|
+
httpServer: /Serving HTTP on/,
|
|
35
|
+
request: /(?:^|\s|")(GET|POST|PUT|DELETE|PATCH)\s+\S+.*?\s(\d{3})/m,
|
|
36
|
+
port: /(?:port\s+(\d+)|(?:on|at)\s+(?:https?:\/\/)?[\w.\[\]:]*:(\d+))/i
|
|
37
|
+
},
|
|
38
|
+
shell: {
|
|
39
|
+
prompt: /[\$#%❯>]\s*$/m,
|
|
40
|
+
// Match lines ending with common shell control sequences that indicate a new prompt
|
|
41
|
+
// We track commands via input echo instead (see _trackInput)
|
|
42
|
+
command: /^[\$#%❯>]\s+(.+)$/m
|
|
43
|
+
},
|
|
44
|
+
// Broad error markers across shells, compilers, scripts, and HTTP servers.
|
|
45
|
+
error: /\b(error|Error|ERROR|exception|Exception|Traceback|fatal|FATAL|segmentation fault|panic|EACCES|ECONNREFUSED|ENOENT|command not found|undefined reference|cannot find module|failed with exit code|\b5\d\d\b)\b/
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
class Session {
|
|
49
|
+
constructor(options) {
|
|
50
|
+
this.id = options.id || uuidv4();
|
|
51
|
+
this.pid = null;
|
|
52
|
+
this.pty = null;
|
|
53
|
+
this.ws = null;
|
|
54
|
+
|
|
55
|
+
// Metadata
|
|
56
|
+
this.meta = {
|
|
57
|
+
type: options.type || 'shell', // shell, claude-code, gemini, python-server, one-shot
|
|
58
|
+
project: options.project || null,
|
|
59
|
+
label: options.label || '',
|
|
60
|
+
command: options.command || '',
|
|
61
|
+
cwd: options.cwd || os.homedir(),
|
|
62
|
+
createdAt: new Date().toISOString(),
|
|
63
|
+
reason: options.reason || 'manual launch',
|
|
64
|
+
|
|
65
|
+
// Dynamic state (updated by output analyzer)
|
|
66
|
+
status: 'starting', // starting, active, idle, thinking, editing, errored, exited
|
|
67
|
+
statusDetail: '',
|
|
68
|
+
lastCommands: [], // rolling buffer of last 10 commands
|
|
69
|
+
lastActivity: new Date().toISOString(),
|
|
70
|
+
detectedPort: null,
|
|
71
|
+
requestCount: 0,
|
|
72
|
+
exitCode: null,
|
|
73
|
+
childProcesses: [],
|
|
74
|
+
|
|
75
|
+
// Theme
|
|
76
|
+
theme: options.theme || 'tokyo-night',
|
|
77
|
+
|
|
78
|
+
// RAG
|
|
79
|
+
ragEnabled: options.ragEnabled !== false,
|
|
80
|
+
ragEvents: [] // buffer before flush
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// Output analysis state
|
|
84
|
+
this._outputBuffer = '';
|
|
85
|
+
this._outputFlushTimer = null;
|
|
86
|
+
this._commandBuffer = '';
|
|
87
|
+
this._inputBuffer = ''; // tracks user keyboard input for command detection
|
|
88
|
+
this.onCommand = null; // callback: (sessionId, command) => void
|
|
89
|
+
this.onStatusChange = null; // callback: (session, oldStatus, newStatus) => void
|
|
90
|
+
this.onErrorDetected = null; // callback: (session, { lastCommand, tail }) => void
|
|
91
|
+
this._statusChangeTimer = null;
|
|
92
|
+
this._pendingStatusChange = null;
|
|
93
|
+
this._lastErrorFireAt = 0;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Analyze PTY output to extract metadata
|
|
97
|
+
analyzeOutput(data) {
|
|
98
|
+
this._outputBuffer += data;
|
|
99
|
+
this.meta.lastActivity = new Date().toISOString();
|
|
100
|
+
|
|
101
|
+
// Strip ANSI codes for reliable pattern matching
|
|
102
|
+
const clean = stripAnsi(data);
|
|
103
|
+
|
|
104
|
+
// Detect terminal type if still generic
|
|
105
|
+
if (this.meta.type === 'shell') {
|
|
106
|
+
this._detectType(clean);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Detect ports before status update (so status can reference the port)
|
|
110
|
+
this._detectPort(clean);
|
|
111
|
+
|
|
112
|
+
// Update status based on type-specific patterns
|
|
113
|
+
this._updateStatus(clean);
|
|
114
|
+
|
|
115
|
+
// Extract commands from shell-like prompts
|
|
116
|
+
this._extractCommands(clean);
|
|
117
|
+
|
|
118
|
+
// Count HTTP requests for server terminals
|
|
119
|
+
this._countRequests(clean);
|
|
120
|
+
|
|
121
|
+
// Error detection — transition to 'errored' and fire onErrorDetected (rate limited 30s)
|
|
122
|
+
this._detectErrors(clean);
|
|
123
|
+
|
|
124
|
+
// Flush buffer periodically (don't hold too much in memory)
|
|
125
|
+
clearTimeout(this._outputFlushTimer);
|
|
126
|
+
this._outputFlushTimer = setTimeout(() => {
|
|
127
|
+
// Keep last 4KB for pattern matching
|
|
128
|
+
if (this._outputBuffer.length > 4096) {
|
|
129
|
+
this._outputBuffer = this._outputBuffer.slice(-4096);
|
|
130
|
+
}
|
|
131
|
+
// Server types: revert to 'listening' after output settles
|
|
132
|
+
if (this.meta.type === 'python-server' && this.meta.status === 'active') {
|
|
133
|
+
this.meta.status = 'listening';
|
|
134
|
+
this.meta.statusDetail = this.meta.detectedPort
|
|
135
|
+
? `Serving on :${this.meta.detectedPort}`
|
|
136
|
+
: 'Server running';
|
|
137
|
+
}
|
|
138
|
+
}, 2000);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
_detectType(data) {
|
|
142
|
+
if (PATTERNS.claudeCode.prompt.test(data) || /claude/i.test(this.meta.command)) {
|
|
143
|
+
this.meta.type = 'claude-code';
|
|
144
|
+
} else if (PATTERNS.geminiCli.prompt.test(data) || /gemini/i.test(this.meta.command)) {
|
|
145
|
+
this.meta.type = 'gemini';
|
|
146
|
+
} else if (
|
|
147
|
+
PATTERNS.pythonServer.uvicorn.test(data) ||
|
|
148
|
+
PATTERNS.pythonServer.flask.test(data) ||
|
|
149
|
+
PATTERNS.pythonServer.django.test(data) ||
|
|
150
|
+
PATTERNS.pythonServer.httpServer.test(data)
|
|
151
|
+
) {
|
|
152
|
+
this.meta.type = 'python-server';
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
_updateStatus(data) {
|
|
157
|
+
const p = PATTERNS;
|
|
158
|
+
const oldStatus = this.meta.status;
|
|
159
|
+
|
|
160
|
+
switch (this.meta.type) {
|
|
161
|
+
case 'claude-code':
|
|
162
|
+
if (p.claudeCode.thinking.test(data)) {
|
|
163
|
+
this.meta.status = 'thinking';
|
|
164
|
+
this.meta.statusDetail = 'Claude is reasoning...';
|
|
165
|
+
} else if (p.claudeCode.editing.test(data)) {
|
|
166
|
+
this.meta.status = 'editing';
|
|
167
|
+
const match = data.match(/^(Edit|Create|Update|Delete)\s+(.+)$/m);
|
|
168
|
+
this.meta.statusDetail = match ? `${match[1]} ${match[2]}` : 'Editing files';
|
|
169
|
+
} else if (p.claudeCode.tool.test(data)) {
|
|
170
|
+
this.meta.status = 'active';
|
|
171
|
+
this.meta.statusDetail = 'Using tools';
|
|
172
|
+
} else if (p.claudeCode.idle.test(data)) {
|
|
173
|
+
this.meta.status = 'idle';
|
|
174
|
+
this.meta.statusDetail = 'Waiting for input';
|
|
175
|
+
}
|
|
176
|
+
break;
|
|
177
|
+
|
|
178
|
+
case 'gemini':
|
|
179
|
+
if (p.geminiCli.thinking.test(data)) {
|
|
180
|
+
this.meta.status = 'thinking';
|
|
181
|
+
this.meta.statusDetail = 'Gemini is generating...';
|
|
182
|
+
} else if (p.geminiCli.prompt.test(data)) {
|
|
183
|
+
this.meta.status = 'idle';
|
|
184
|
+
this.meta.statusDetail = 'Waiting for input';
|
|
185
|
+
}
|
|
186
|
+
break;
|
|
187
|
+
|
|
188
|
+
case 'python-server':
|
|
189
|
+
if (p.pythonServer.request.test(data)) {
|
|
190
|
+
this.meta.status = 'active';
|
|
191
|
+
const match = data.match(p.pythonServer.request);
|
|
192
|
+
if (match) {
|
|
193
|
+
this.meta.statusDetail = `${match[1]} → ${match[2]}`;
|
|
194
|
+
}
|
|
195
|
+
} else {
|
|
196
|
+
this.meta.status = 'listening';
|
|
197
|
+
this.meta.statusDetail = this.meta.detectedPort
|
|
198
|
+
? `Serving on :${this.meta.detectedPort}`
|
|
199
|
+
: 'Server running';
|
|
200
|
+
}
|
|
201
|
+
break;
|
|
202
|
+
|
|
203
|
+
default:
|
|
204
|
+
if (p.shell.prompt.test(data)) {
|
|
205
|
+
this.meta.status = 'idle';
|
|
206
|
+
this.meta.statusDetail = 'Ready';
|
|
207
|
+
} else {
|
|
208
|
+
this.meta.status = 'active';
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Debounce status change events (3s) to avoid flooding RAG with active↔idle flaps
|
|
213
|
+
if (this.meta.status !== oldStatus && this.onStatusChange) {
|
|
214
|
+
clearTimeout(this._statusChangeTimer);
|
|
215
|
+
this._pendingStatusChange = { oldStatus, newStatus: this.meta.status };
|
|
216
|
+
this._statusChangeTimer = setTimeout(() => {
|
|
217
|
+
if (this._pendingStatusChange) {
|
|
218
|
+
this.onStatusChange(this, this._pendingStatusChange.oldStatus, this._pendingStatusChange.newStatus);
|
|
219
|
+
this._pendingStatusChange = null;
|
|
220
|
+
}
|
|
221
|
+
}, 3000);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
_detectPort(data) {
|
|
226
|
+
const match = data.match(PATTERNS.pythonServer.port);
|
|
227
|
+
if (match) {
|
|
228
|
+
// Two capture groups: match[1] for "port XXXX", match[2] for ":XXXX"
|
|
229
|
+
this.meta.detectedPort = parseInt(match[1] || match[2], 10);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Track user input to detect commands (called from server when PTY receives input)
|
|
234
|
+
trackInput(data) {
|
|
235
|
+
for (let i = 0; i < data.length; i++) {
|
|
236
|
+
const ch = data[i];
|
|
237
|
+
const code = ch.charCodeAt(0);
|
|
238
|
+
|
|
239
|
+
if (ch === '\r' || ch === '\n') {
|
|
240
|
+
// Enter — flush the buffer as a command
|
|
241
|
+
const cmd = this._inputBuffer.trim();
|
|
242
|
+
if (cmd.length > 0 && cmd.length < 500) {
|
|
243
|
+
const clean = cmd.replace(/\x1b\[[A-Za-z0-9;]*[A-Za-z]/g, '').trim();
|
|
244
|
+
if (clean.length > 0) {
|
|
245
|
+
this.meta.lastCommands.push({
|
|
246
|
+
command: clean,
|
|
247
|
+
timestamp: new Date().toISOString()
|
|
248
|
+
});
|
|
249
|
+
if (this.meta.lastCommands.length > 10) {
|
|
250
|
+
this.meta.lastCommands.shift();
|
|
251
|
+
}
|
|
252
|
+
if (this.onCommand) {
|
|
253
|
+
this.onCommand(this.id, clean);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
this._inputBuffer = '';
|
|
258
|
+
} else if (ch === '\x7f' || ch === '\b') {
|
|
259
|
+
this._inputBuffer = this._inputBuffer.slice(0, -1);
|
|
260
|
+
} else if (ch === '\x1b') {
|
|
261
|
+
// Skip escape sequences
|
|
262
|
+
if (i + 1 < data.length && data[i + 1] === '[') {
|
|
263
|
+
i += 2; // skip \x1b[
|
|
264
|
+
while (i < data.length && !/[A-Za-z]/.test(data[i])) i++;
|
|
265
|
+
// i now points at the final letter, loop increment will skip it
|
|
266
|
+
}
|
|
267
|
+
} else if (code >= 32) {
|
|
268
|
+
this._inputBuffer += ch;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
_detectErrors(clean) {
|
|
274
|
+
if (!PATTERNS.error.test(clean)) return;
|
|
275
|
+
|
|
276
|
+
const oldStatus = this.meta.status;
|
|
277
|
+
this.meta.status = 'errored';
|
|
278
|
+
this.meta.statusDetail = 'Error detected in output';
|
|
279
|
+
|
|
280
|
+
// Mirror status-change callback so T1 sees 'errored' in status_broadcast without
|
|
281
|
+
// waiting for the 3s debounce.
|
|
282
|
+
if (oldStatus !== 'errored' && this.onStatusChange) {
|
|
283
|
+
try { this.onStatusChange(this, oldStatus, 'errored'); } catch {}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Server-side rate limit: at most one error_detected event every 30s per session
|
|
287
|
+
const now = Date.now();
|
|
288
|
+
if (now - this._lastErrorFireAt < 30000) return;
|
|
289
|
+
this._lastErrorFireAt = now;
|
|
290
|
+
|
|
291
|
+
if (this.onErrorDetected) {
|
|
292
|
+
const lastCommand = this.meta.lastCommands.length > 0
|
|
293
|
+
? this.meta.lastCommands[this.meta.lastCommands.length - 1].command
|
|
294
|
+
: '';
|
|
295
|
+
const tail = this._outputBuffer.slice(-200).replace(/\x1b\[[\?]?[0-9;]*[A-Za-z]/g, '').replace(/\x1b\][^\x07]*\x07/g, '');
|
|
296
|
+
try {
|
|
297
|
+
this.onErrorDetected(this, { lastCommand, tail });
|
|
298
|
+
} catch (err) {
|
|
299
|
+
console.error('[session] onErrorDetected handler error:', err);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
_extractCommands(data) {
|
|
305
|
+
// Output-based command extraction as fallback (e.g. for commands echoed by shell)
|
|
306
|
+
// Primary command tracking is via trackInput()
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
_countRequests(data) {
|
|
310
|
+
const globalRequest = new RegExp(PATTERNS.pythonServer.request.source, 'gm');
|
|
311
|
+
const matches = data.match(globalRequest);
|
|
312
|
+
if (matches) {
|
|
313
|
+
this.meta.requestCount += matches.length;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
toJSON() {
|
|
318
|
+
return {
|
|
319
|
+
id: this.id,
|
|
320
|
+
pid: this.pid,
|
|
321
|
+
meta: { ...this.meta }
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
destroy() {
|
|
326
|
+
clearTimeout(this._outputFlushTimer);
|
|
327
|
+
clearTimeout(this._statusChangeTimer);
|
|
328
|
+
this._outputBuffer = '';
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
class SessionManager {
|
|
333
|
+
constructor(db) {
|
|
334
|
+
this.sessions = new Map();
|
|
335
|
+
this.db = db;
|
|
336
|
+
this._listeners = new Map(); // event listeners
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
create(options) {
|
|
340
|
+
const session = new Session(options);
|
|
341
|
+
this.sessions.set(session.id, session);
|
|
342
|
+
|
|
343
|
+
// Persist to SQLite
|
|
344
|
+
if (this.db) {
|
|
345
|
+
this.db.prepare(`
|
|
346
|
+
INSERT INTO sessions (id, type, project, label, command, cwd, created_at, reason, theme)
|
|
347
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
348
|
+
`).run(
|
|
349
|
+
session.id,
|
|
350
|
+
session.meta.type,
|
|
351
|
+
session.meta.project,
|
|
352
|
+
session.meta.label,
|
|
353
|
+
session.meta.command,
|
|
354
|
+
session.meta.cwd,
|
|
355
|
+
session.meta.createdAt,
|
|
356
|
+
session.meta.reason,
|
|
357
|
+
session.meta.theme
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
this._emit('session:created', session);
|
|
362
|
+
return session;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
get(id) {
|
|
366
|
+
return this.sessions.get(id);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
getAll() {
|
|
370
|
+
return Array.from(this.sessions.values()).map(s => s.toJSON());
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
updateMeta(id, updates) {
|
|
374
|
+
const session = this.sessions.get(id);
|
|
375
|
+
if (!session) return null;
|
|
376
|
+
|
|
377
|
+
Object.assign(session.meta, updates);
|
|
378
|
+
|
|
379
|
+
// Persist theme changes to SQLite
|
|
380
|
+
if (updates.theme && this.db) {
|
|
381
|
+
this.db.prepare('UPDATE sessions SET theme = ? WHERE id = ?')
|
|
382
|
+
.run(updates.theme, id);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
this._emit('session:updated', session);
|
|
386
|
+
return session;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
remove(id) {
|
|
390
|
+
const session = this.sessions.get(id);
|
|
391
|
+
if (!session) return false;
|
|
392
|
+
|
|
393
|
+
session.destroy();
|
|
394
|
+
this.sessions.delete(id);
|
|
395
|
+
|
|
396
|
+
if (this.db) {
|
|
397
|
+
this.db.prepare(`
|
|
398
|
+
UPDATE sessions SET exited_at = ?, exit_code = ? WHERE id = ?
|
|
399
|
+
`).run(new Date().toISOString(), session.meta.exitCode, id);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
this._emit('session:removed', session);
|
|
403
|
+
return true;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
on(event, fn) {
|
|
407
|
+
if (!this._listeners.has(event)) {
|
|
408
|
+
this._listeners.set(event, []);
|
|
409
|
+
}
|
|
410
|
+
this._listeners.get(event).push(fn);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
_emit(event, data) {
|
|
414
|
+
const fns = this._listeners.get(event) || [];
|
|
415
|
+
for (const fn of fns) {
|
|
416
|
+
try { fn(data); } catch (e) { console.error(`[events] handler error for ${event}:`, e); }
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
module.exports = { Session, SessionManager, PATTERNS };
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
// Terminal themes for xterm.js
|
|
2
|
+
// Each theme is a complete xterm ITheme object
|
|
3
|
+
|
|
4
|
+
const themes = {
|
|
5
|
+
'tokyo-night': {
|
|
6
|
+
label: 'Tokyo Night',
|
|
7
|
+
category: 'dark',
|
|
8
|
+
theme: {
|
|
9
|
+
background: '#1a1b26',
|
|
10
|
+
foreground: '#c0caf5',
|
|
11
|
+
cursor: '#c0caf5',
|
|
12
|
+
cursorAccent: '#1a1b26',
|
|
13
|
+
selectionBackground: '#33467c',
|
|
14
|
+
selectionForeground: '#c0caf5',
|
|
15
|
+
black: '#15161e',
|
|
16
|
+
red: '#f7768e',
|
|
17
|
+
green: '#9ece6a',
|
|
18
|
+
yellow: '#e0af68',
|
|
19
|
+
blue: '#7aa2f7',
|
|
20
|
+
magenta: '#bb9af7',
|
|
21
|
+
cyan: '#7dcfff',
|
|
22
|
+
white: '#a9b1d6',
|
|
23
|
+
brightBlack: '#414868',
|
|
24
|
+
brightRed: '#f7768e',
|
|
25
|
+
brightGreen: '#9ece6a',
|
|
26
|
+
brightYellow: '#e0af68',
|
|
27
|
+
brightBlue: '#7aa2f7',
|
|
28
|
+
brightMagenta: '#bb9af7',
|
|
29
|
+
brightCyan: '#7dcfff',
|
|
30
|
+
brightWhite: '#c0caf5'
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
'rose-pine-dawn': {
|
|
35
|
+
label: 'Rosé Pine Dawn',
|
|
36
|
+
category: 'light',
|
|
37
|
+
theme: {
|
|
38
|
+
background: '#faf4ed',
|
|
39
|
+
foreground: '#575279',
|
|
40
|
+
cursor: '#575279',
|
|
41
|
+
cursorAccent: '#faf4ed',
|
|
42
|
+
selectionBackground: '#dfdad9',
|
|
43
|
+
selectionForeground: '#575279',
|
|
44
|
+
black: '#f2e9e1',
|
|
45
|
+
red: '#b4637a',
|
|
46
|
+
green: '#286983',
|
|
47
|
+
yellow: '#ea9d34',
|
|
48
|
+
blue: '#56949f',
|
|
49
|
+
magenta: '#907aa9',
|
|
50
|
+
cyan: '#d7827e',
|
|
51
|
+
white: '#575279',
|
|
52
|
+
brightBlack: '#9893a5',
|
|
53
|
+
brightRed: '#b4637a',
|
|
54
|
+
brightGreen: '#286983',
|
|
55
|
+
brightYellow: '#ea9d34',
|
|
56
|
+
brightBlue: '#56949f',
|
|
57
|
+
brightMagenta: '#907aa9',
|
|
58
|
+
brightCyan: '#d7827e',
|
|
59
|
+
brightWhite: '#575279'
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
'catppuccin-mocha': {
|
|
64
|
+
label: 'Catppuccin Mocha',
|
|
65
|
+
category: 'dark',
|
|
66
|
+
theme: {
|
|
67
|
+
background: '#1e1e2e',
|
|
68
|
+
foreground: '#cdd6f4',
|
|
69
|
+
cursor: '#f5e0dc',
|
|
70
|
+
cursorAccent: '#1e1e2e',
|
|
71
|
+
selectionBackground: '#45475a',
|
|
72
|
+
selectionForeground: '#cdd6f4',
|
|
73
|
+
black: '#45475a',
|
|
74
|
+
red: '#f38ba8',
|
|
75
|
+
green: '#a6e3a1',
|
|
76
|
+
yellow: '#f9e2af',
|
|
77
|
+
blue: '#89b4fa',
|
|
78
|
+
magenta: '#f5c2e7',
|
|
79
|
+
cyan: '#94e2d5',
|
|
80
|
+
white: '#bac2de',
|
|
81
|
+
brightBlack: '#585b70',
|
|
82
|
+
brightRed: '#f38ba8',
|
|
83
|
+
brightGreen: '#a6e3a1',
|
|
84
|
+
brightYellow: '#f9e2af',
|
|
85
|
+
brightBlue: '#89b4fa',
|
|
86
|
+
brightMagenta: '#f5c2e7',
|
|
87
|
+
brightCyan: '#94e2d5',
|
|
88
|
+
brightWhite: '#a6adc8'
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
'github-light': {
|
|
93
|
+
label: 'GitHub Light',
|
|
94
|
+
category: 'light',
|
|
95
|
+
theme: {
|
|
96
|
+
background: '#ffffff',
|
|
97
|
+
foreground: '#24292f',
|
|
98
|
+
cursor: '#044289',
|
|
99
|
+
cursorAccent: '#ffffff',
|
|
100
|
+
selectionBackground: '#0969da33',
|
|
101
|
+
selectionForeground: '#24292f',
|
|
102
|
+
black: '#24292f',
|
|
103
|
+
red: '#cf222e',
|
|
104
|
+
green: '#116329',
|
|
105
|
+
yellow: '#4d2d00',
|
|
106
|
+
blue: '#0550ae',
|
|
107
|
+
magenta: '#8250df',
|
|
108
|
+
cyan: '#1b7c83',
|
|
109
|
+
white: '#6e7781',
|
|
110
|
+
brightBlack: '#57606a',
|
|
111
|
+
brightRed: '#a40e26',
|
|
112
|
+
brightGreen: '#1a7f37',
|
|
113
|
+
brightYellow: '#633c01',
|
|
114
|
+
brightBlue: '#0969da',
|
|
115
|
+
brightMagenta: '#8250df',
|
|
116
|
+
brightCyan: '#3192aa',
|
|
117
|
+
brightWhite: '#8c959f'
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
'dracula': {
|
|
122
|
+
label: 'Dracula',
|
|
123
|
+
category: 'dark',
|
|
124
|
+
theme: {
|
|
125
|
+
background: '#282a36',
|
|
126
|
+
foreground: '#f8f8f2',
|
|
127
|
+
cursor: '#f8f8f2',
|
|
128
|
+
cursorAccent: '#282a36',
|
|
129
|
+
selectionBackground: '#44475a',
|
|
130
|
+
selectionForeground: '#f8f8f2',
|
|
131
|
+
black: '#21222c',
|
|
132
|
+
red: '#ff5555',
|
|
133
|
+
green: '#50fa7b',
|
|
134
|
+
yellow: '#f1fa8c',
|
|
135
|
+
blue: '#bd93f9',
|
|
136
|
+
magenta: '#ff79c6',
|
|
137
|
+
cyan: '#8be9fd',
|
|
138
|
+
white: '#f8f8f2',
|
|
139
|
+
brightBlack: '#6272a4',
|
|
140
|
+
brightRed: '#ff6e6e',
|
|
141
|
+
brightGreen: '#69ff94',
|
|
142
|
+
brightYellow: '#ffffa5',
|
|
143
|
+
brightBlue: '#d6acff',
|
|
144
|
+
brightMagenta: '#ff92df',
|
|
145
|
+
brightCyan: '#a4ffff',
|
|
146
|
+
brightWhite: '#ffffff'
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
'solarized-dark': {
|
|
151
|
+
label: 'Solarized Dark',
|
|
152
|
+
category: 'dark',
|
|
153
|
+
theme: {
|
|
154
|
+
background: '#002b36',
|
|
155
|
+
foreground: '#839496',
|
|
156
|
+
cursor: '#839496',
|
|
157
|
+
cursorAccent: '#002b36',
|
|
158
|
+
selectionBackground: '#073642',
|
|
159
|
+
selectionForeground: '#93a1a1',
|
|
160
|
+
black: '#073642',
|
|
161
|
+
red: '#dc322f',
|
|
162
|
+
green: '#859900',
|
|
163
|
+
yellow: '#b58900',
|
|
164
|
+
blue: '#268bd2',
|
|
165
|
+
magenta: '#d33682',
|
|
166
|
+
cyan: '#2aa198',
|
|
167
|
+
white: '#eee8d5',
|
|
168
|
+
brightBlack: '#586e75',
|
|
169
|
+
brightRed: '#cb4b16',
|
|
170
|
+
brightGreen: '#586e75',
|
|
171
|
+
brightYellow: '#657b83',
|
|
172
|
+
brightBlue: '#839496',
|
|
173
|
+
brightMagenta: '#6c71c4',
|
|
174
|
+
brightCyan: '#93a1a1',
|
|
175
|
+
brightWhite: '#fdf6e3'
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
'nord': {
|
|
180
|
+
label: 'Nord',
|
|
181
|
+
category: 'dark',
|
|
182
|
+
theme: {
|
|
183
|
+
background: '#2e3440',
|
|
184
|
+
foreground: '#d8dee9',
|
|
185
|
+
cursor: '#d8dee9',
|
|
186
|
+
cursorAccent: '#2e3440',
|
|
187
|
+
selectionBackground: '#434c5e',
|
|
188
|
+
selectionForeground: '#d8dee9',
|
|
189
|
+
black: '#3b4252',
|
|
190
|
+
red: '#bf616a',
|
|
191
|
+
green: '#a3be8c',
|
|
192
|
+
yellow: '#ebcb8b',
|
|
193
|
+
blue: '#81a1c1',
|
|
194
|
+
magenta: '#b48ead',
|
|
195
|
+
cyan: '#88c0d0',
|
|
196
|
+
white: '#e5e9f0',
|
|
197
|
+
brightBlack: '#4c566a',
|
|
198
|
+
brightRed: '#bf616a',
|
|
199
|
+
brightGreen: '#a3be8c',
|
|
200
|
+
brightYellow: '#ebcb8b',
|
|
201
|
+
brightBlue: '#81a1c1',
|
|
202
|
+
brightMagenta: '#b48ead',
|
|
203
|
+
brightCyan: '#8fbcbb',
|
|
204
|
+
brightWhite: '#eceff4'
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
'gruvbox-dark': {
|
|
209
|
+
label: 'Gruvbox Dark',
|
|
210
|
+
category: 'dark',
|
|
211
|
+
theme: {
|
|
212
|
+
background: '#282828',
|
|
213
|
+
foreground: '#ebdbb2',
|
|
214
|
+
cursor: '#ebdbb2',
|
|
215
|
+
cursorAccent: '#282828',
|
|
216
|
+
selectionBackground: '#504945',
|
|
217
|
+
selectionForeground: '#ebdbb2',
|
|
218
|
+
black: '#282828',
|
|
219
|
+
red: '#cc241d',
|
|
220
|
+
green: '#98971a',
|
|
221
|
+
yellow: '#d79921',
|
|
222
|
+
blue: '#458588',
|
|
223
|
+
magenta: '#b16286',
|
|
224
|
+
cyan: '#689d6a',
|
|
225
|
+
white: '#a89984',
|
|
226
|
+
brightBlack: '#928374',
|
|
227
|
+
brightRed: '#fb4934',
|
|
228
|
+
brightGreen: '#b8bb26',
|
|
229
|
+
brightYellow: '#fabd2f',
|
|
230
|
+
brightBlue: '#83a598',
|
|
231
|
+
brightMagenta: '#d3869b',
|
|
232
|
+
brightCyan: '#8ec07c',
|
|
233
|
+
brightWhite: '#ebdbb2'
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// Status indicator colors (for the dot next to terminal name)
|
|
239
|
+
const statusColors = {
|
|
240
|
+
starting: '#7aa2f7', // blue
|
|
241
|
+
active: '#9ece6a', // green
|
|
242
|
+
idle: '#888780', // gray
|
|
243
|
+
thinking: '#bb9af7', // purple
|
|
244
|
+
editing: '#e0af68', // amber
|
|
245
|
+
listening: '#7dcfff', // cyan
|
|
246
|
+
errored: '#f7768e', // red
|
|
247
|
+
exited: '#414868' // dim
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
module.exports = { themes, statusColors };
|