claude-code-workflow 6.3.11 → 6.3.13
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/.claude/CLAUDE.md +33 -33
- package/.claude/agents/issue-plan-agent.md +77 -5
- package/.claude/agents/issue-queue-agent.md +122 -18
- package/.claude/commands/issue/execute.md +53 -40
- package/.claude/commands/issue/new.md +113 -11
- package/.claude/commands/issue/plan.md +112 -37
- package/.claude/commands/issue/queue.md +28 -18
- package/.claude/skills/software-manual/scripts/assemble_docsify.py +584 -0
- package/.claude/skills/software-manual/templates/css/docsify-base.css +984 -0
- package/.claude/skills/software-manual/templates/docsify-shell.html +466 -0
- package/.claude/workflows/cli-templates/schemas/issues-jsonl-schema.json +141 -168
- package/.claude/workflows/cli-templates/schemas/solution-schema.json +3 -2
- package/.codex/prompts/issue-execute.md +3 -3
- package/.codex/prompts/issue-queue.md +3 -3
- package/ccw/dist/commands/issue.d.ts.map +1 -1
- package/ccw/dist/commands/issue.js +2 -1
- package/ccw/dist/commands/issue.js.map +1 -1
- package/ccw/src/commands/issue.ts +2 -1
- package/ccw/src/templates/dashboard-css/33-cli-stream-viewer.css +580 -467
- package/ccw/src/templates/dashboard-js/components/cli-stream-viewer.js +532 -461
- package/ccw/src/templates/dashboard-js/components/notifications.js +774 -774
- package/ccw/src/templates/dashboard-js/i18n.js +4 -0
- package/ccw/src/templates/dashboard.html +10 -0
- package/ccw/src/tools/claude-cli-tools.ts +388 -388
- package/codex-lens/src/codexlens/__pycache__/config.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/config.py +19 -3
- package/codex-lens/src/codexlens/search/__pycache__/ranking.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/search/ranking.py +15 -4
- package/codex-lens/src/codexlens/semantic/__pycache__/vector_store.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/vector_store.py +57 -47
- package/codex-lens/src/codexlens/storage/__pycache__/registry.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/storage/registry.py +114 -101
- package/package.json +83 -83
|
@@ -1,461 +1,532 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CLI Stream Viewer Component
|
|
3
|
-
* Real-time streaming output viewer for CLI executions
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// ===== State Management =====
|
|
7
|
-
let cliStreamExecutions = {}; // { executionId: { tool, mode, output, status, startTime, endTime } }
|
|
8
|
-
let activeStreamTab = null;
|
|
9
|
-
let autoScrollEnabled = true;
|
|
10
|
-
let isCliStreamViewerOpen = false;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if (
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
//
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
if (
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
if (
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
|
|
1
|
+
/**
|
|
2
|
+
* CLI Stream Viewer Component
|
|
3
|
+
* Real-time streaming output viewer for CLI executions
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ===== State Management =====
|
|
7
|
+
let cliStreamExecutions = {}; // { executionId: { tool, mode, output, status, startTime, endTime } }
|
|
8
|
+
let activeStreamTab = null;
|
|
9
|
+
let autoScrollEnabled = true;
|
|
10
|
+
let isCliStreamViewerOpen = false;
|
|
11
|
+
let searchFilter = ''; // Search filter for output content
|
|
12
|
+
|
|
13
|
+
const MAX_OUTPUT_LINES = 5000; // Prevent memory issues
|
|
14
|
+
|
|
15
|
+
// ===== Initialization =====
|
|
16
|
+
function initCliStreamViewer() {
|
|
17
|
+
// Initialize keyboard shortcuts
|
|
18
|
+
document.addEventListener('keydown', function(e) {
|
|
19
|
+
if (e.key === 'Escape' && isCliStreamViewerOpen) {
|
|
20
|
+
if (searchFilter) {
|
|
21
|
+
clearSearch();
|
|
22
|
+
} else {
|
|
23
|
+
toggleCliStreamViewer();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
// Ctrl+F to focus search when viewer is open
|
|
27
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 'f' && isCliStreamViewerOpen) {
|
|
28
|
+
e.preventDefault();
|
|
29
|
+
const searchInput = document.getElementById('cliStreamSearchInput');
|
|
30
|
+
if (searchInput) {
|
|
31
|
+
searchInput.focus();
|
|
32
|
+
searchInput.select();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Initialize scroll detection for auto-scroll
|
|
38
|
+
const content = document.getElementById('cliStreamContent');
|
|
39
|
+
if (content) {
|
|
40
|
+
content.addEventListener('scroll', handleStreamContentScroll);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ===== Panel Control =====
|
|
45
|
+
function toggleCliStreamViewer() {
|
|
46
|
+
const viewer = document.getElementById('cliStreamViewer');
|
|
47
|
+
const overlay = document.getElementById('cliStreamOverlay');
|
|
48
|
+
|
|
49
|
+
if (!viewer || !overlay) return;
|
|
50
|
+
|
|
51
|
+
isCliStreamViewerOpen = !isCliStreamViewerOpen;
|
|
52
|
+
|
|
53
|
+
if (isCliStreamViewerOpen) {
|
|
54
|
+
viewer.classList.add('open');
|
|
55
|
+
overlay.classList.add('open');
|
|
56
|
+
|
|
57
|
+
// If no active tab but have executions, select the first one
|
|
58
|
+
if (!activeStreamTab && Object.keys(cliStreamExecutions).length > 0) {
|
|
59
|
+
const firstId = Object.keys(cliStreamExecutions)[0];
|
|
60
|
+
switchStreamTab(firstId);
|
|
61
|
+
} else {
|
|
62
|
+
renderStreamContent(activeStreamTab);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Re-init lucide icons
|
|
66
|
+
if (typeof lucide !== 'undefined') {
|
|
67
|
+
lucide.createIcons();
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
viewer.classList.remove('open');
|
|
71
|
+
overlay.classList.remove('open');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ===== WebSocket Event Handlers =====
|
|
76
|
+
function handleCliStreamStarted(payload) {
|
|
77
|
+
const { executionId, tool, mode, timestamp } = payload;
|
|
78
|
+
|
|
79
|
+
// Create new execution record
|
|
80
|
+
cliStreamExecutions[executionId] = {
|
|
81
|
+
tool: tool || 'cli',
|
|
82
|
+
mode: mode || 'analysis',
|
|
83
|
+
output: [],
|
|
84
|
+
status: 'running',
|
|
85
|
+
startTime: timestamp ? new Date(timestamp).getTime() : Date.now(),
|
|
86
|
+
endTime: null
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// Add system message
|
|
90
|
+
cliStreamExecutions[executionId].output.push({
|
|
91
|
+
type: 'system',
|
|
92
|
+
content: `[${new Date().toLocaleTimeString()}] CLI execution started: ${tool} (${mode} mode)`,
|
|
93
|
+
timestamp: Date.now()
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// If this is the first execution or panel is open, select it
|
|
97
|
+
if (!activeStreamTab || isCliStreamViewerOpen) {
|
|
98
|
+
activeStreamTab = executionId;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
renderStreamTabs();
|
|
102
|
+
renderStreamContent(activeStreamTab);
|
|
103
|
+
updateStreamBadge();
|
|
104
|
+
|
|
105
|
+
// Auto-open panel if configured (optional)
|
|
106
|
+
// if (!isCliStreamViewerOpen) toggleCliStreamViewer();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function handleCliStreamOutput(payload) {
|
|
110
|
+
const { executionId, chunkType, data } = payload;
|
|
111
|
+
|
|
112
|
+
const exec = cliStreamExecutions[executionId];
|
|
113
|
+
if (!exec) return;
|
|
114
|
+
|
|
115
|
+
// Parse and add output lines
|
|
116
|
+
const content = typeof data === 'string' ? data : JSON.stringify(data);
|
|
117
|
+
const lines = content.split('\n');
|
|
118
|
+
|
|
119
|
+
lines.forEach(line => {
|
|
120
|
+
if (line.trim() || lines.length === 1) { // Keep empty lines if it's the only content
|
|
121
|
+
exec.output.push({
|
|
122
|
+
type: chunkType || 'stdout',
|
|
123
|
+
content: line,
|
|
124
|
+
timestamp: Date.now()
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Trim if too long
|
|
130
|
+
if (exec.output.length > MAX_OUTPUT_LINES) {
|
|
131
|
+
exec.output = exec.output.slice(-MAX_OUTPUT_LINES);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Update UI if this is the active tab
|
|
135
|
+
if (activeStreamTab === executionId && isCliStreamViewerOpen) {
|
|
136
|
+
requestAnimationFrame(() => {
|
|
137
|
+
renderStreamContent(executionId);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Update badge to show activity
|
|
142
|
+
updateStreamBadge();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function handleCliStreamCompleted(payload) {
|
|
146
|
+
const { executionId, success, duration, timestamp } = payload;
|
|
147
|
+
|
|
148
|
+
const exec = cliStreamExecutions[executionId];
|
|
149
|
+
if (!exec) return;
|
|
150
|
+
|
|
151
|
+
exec.status = success ? 'completed' : 'error';
|
|
152
|
+
exec.endTime = timestamp ? new Date(timestamp).getTime() : Date.now();
|
|
153
|
+
|
|
154
|
+
// Add completion message
|
|
155
|
+
const durationText = duration ? ` (${formatDuration(duration)})` : '';
|
|
156
|
+
const statusText = success ? 'completed successfully' : 'failed';
|
|
157
|
+
exec.output.push({
|
|
158
|
+
type: 'system',
|
|
159
|
+
content: `[${new Date().toLocaleTimeString()}] CLI execution ${statusText}${durationText}`,
|
|
160
|
+
timestamp: Date.now()
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
renderStreamTabs();
|
|
164
|
+
if (activeStreamTab === executionId) {
|
|
165
|
+
renderStreamContent(executionId);
|
|
166
|
+
}
|
|
167
|
+
updateStreamBadge();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function handleCliStreamError(payload) {
|
|
171
|
+
const { executionId, error, timestamp } = payload;
|
|
172
|
+
|
|
173
|
+
const exec = cliStreamExecutions[executionId];
|
|
174
|
+
if (!exec) return;
|
|
175
|
+
|
|
176
|
+
exec.status = 'error';
|
|
177
|
+
exec.endTime = timestamp ? new Date(timestamp).getTime() : Date.now();
|
|
178
|
+
|
|
179
|
+
// Add error message
|
|
180
|
+
exec.output.push({
|
|
181
|
+
type: 'stderr',
|
|
182
|
+
content: `[ERROR] ${error || 'Unknown error occurred'}`,
|
|
183
|
+
timestamp: Date.now()
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
renderStreamTabs();
|
|
187
|
+
if (activeStreamTab === executionId) {
|
|
188
|
+
renderStreamContent(executionId);
|
|
189
|
+
}
|
|
190
|
+
updateStreamBadge();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ===== UI Rendering =====
|
|
194
|
+
function renderStreamTabs() {
|
|
195
|
+
const tabsContainer = document.getElementById('cliStreamTabs');
|
|
196
|
+
if (!tabsContainer) return;
|
|
197
|
+
|
|
198
|
+
const execIds = Object.keys(cliStreamExecutions);
|
|
199
|
+
|
|
200
|
+
if (execIds.length === 0) {
|
|
201
|
+
tabsContainer.innerHTML = '';
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Sort: running first, then by start time (newest first)
|
|
206
|
+
execIds.sort((a, b) => {
|
|
207
|
+
const execA = cliStreamExecutions[a];
|
|
208
|
+
const execB = cliStreamExecutions[b];
|
|
209
|
+
|
|
210
|
+
if (execA.status === 'running' && execB.status !== 'running') return -1;
|
|
211
|
+
if (execA.status !== 'running' && execB.status === 'running') return 1;
|
|
212
|
+
return execB.startTime - execA.startTime;
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
tabsContainer.innerHTML = execIds.map(id => {
|
|
216
|
+
const exec = cliStreamExecutions[id];
|
|
217
|
+
const isActive = id === activeStreamTab;
|
|
218
|
+
const canClose = exec.status !== 'running';
|
|
219
|
+
|
|
220
|
+
return `
|
|
221
|
+
<div class="cli-stream-tab ${isActive ? 'active' : ''}"
|
|
222
|
+
onclick="switchStreamTab('${id}')"
|
|
223
|
+
data-execution-id="${id}">
|
|
224
|
+
<span class="cli-stream-tab-status ${exec.status}"></span>
|
|
225
|
+
<span class="cli-stream-tab-tool">${escapeHtml(exec.tool)}</span>
|
|
226
|
+
<span class="cli-stream-tab-mode">${exec.mode}</span>
|
|
227
|
+
<button class="cli-stream-tab-close ${canClose ? '' : 'disabled'}"
|
|
228
|
+
onclick="event.stopPropagation(); closeStream('${id}')"
|
|
229
|
+
title="${canClose ? _streamT('cliStream.close') : _streamT('cliStream.cannotCloseRunning')}"
|
|
230
|
+
${canClose ? '' : 'disabled'}>×</button>
|
|
231
|
+
</div>
|
|
232
|
+
`;
|
|
233
|
+
}).join('');
|
|
234
|
+
|
|
235
|
+
// Update count badge
|
|
236
|
+
const countBadge = document.getElementById('cliStreamCountBadge');
|
|
237
|
+
if (countBadge) {
|
|
238
|
+
const runningCount = execIds.filter(id => cliStreamExecutions[id].status === 'running').length;
|
|
239
|
+
countBadge.textContent = execIds.length;
|
|
240
|
+
countBadge.classList.toggle('has-running', runningCount > 0);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function renderStreamContent(executionId) {
|
|
245
|
+
const contentContainer = document.getElementById('cliStreamContent');
|
|
246
|
+
if (!contentContainer) return;
|
|
247
|
+
|
|
248
|
+
const exec = executionId ? cliStreamExecutions[executionId] : null;
|
|
249
|
+
|
|
250
|
+
if (!exec) {
|
|
251
|
+
// Show empty state
|
|
252
|
+
contentContainer.innerHTML = `
|
|
253
|
+
<div class="cli-stream-empty">
|
|
254
|
+
<i data-lucide="terminal"></i>
|
|
255
|
+
<div class="cli-stream-empty-title" data-i18n="cliStream.noStreams">${_streamT('cliStream.noStreams')}</div>
|
|
256
|
+
<div class="cli-stream-empty-hint" data-i18n="cliStream.noStreamsHint">${_streamT('cliStream.noStreamsHint')}</div>
|
|
257
|
+
</div>
|
|
258
|
+
`;
|
|
259
|
+
if (typeof lucide !== 'undefined') lucide.createIcons();
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Check if should auto-scroll
|
|
264
|
+
const wasAtBottom = contentContainer.scrollHeight - contentContainer.scrollTop <= contentContainer.clientHeight + 50;
|
|
265
|
+
|
|
266
|
+
// Filter output lines based on search
|
|
267
|
+
let filteredOutput = exec.output;
|
|
268
|
+
if (searchFilter.trim()) {
|
|
269
|
+
const searchLower = searchFilter.toLowerCase();
|
|
270
|
+
filteredOutput = exec.output.filter(line =>
|
|
271
|
+
line.content.toLowerCase().includes(searchLower)
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Render output lines with search highlighting
|
|
276
|
+
contentContainer.innerHTML = filteredOutput.map(line => {
|
|
277
|
+
let content = escapeHtml(line.content);
|
|
278
|
+
// Highlight search matches
|
|
279
|
+
if (searchFilter.trim()) {
|
|
280
|
+
const searchRegex = new RegExp(`(${escapeRegex(searchFilter)})`, 'gi');
|
|
281
|
+
content = content.replace(searchRegex, '<mark class="cli-stream-highlight">$1</mark>');
|
|
282
|
+
}
|
|
283
|
+
return `<div class="cli-stream-line ${line.type}">${content}</div>`;
|
|
284
|
+
}).join('');
|
|
285
|
+
|
|
286
|
+
// Show filter result count if filtering
|
|
287
|
+
if (searchFilter.trim() && filteredOutput.length !== exec.output.length) {
|
|
288
|
+
const filterInfo = document.createElement('div');
|
|
289
|
+
filterInfo.className = 'cli-stream-filter-info';
|
|
290
|
+
filterInfo.textContent = `${filteredOutput.length} / ${exec.output.length} lines`;
|
|
291
|
+
contentContainer.insertBefore(filterInfo, contentContainer.firstChild);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Auto-scroll if enabled and was at bottom
|
|
295
|
+
if (autoScrollEnabled && wasAtBottom) {
|
|
296
|
+
contentContainer.scrollTop = contentContainer.scrollHeight;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Update status bar
|
|
300
|
+
renderStreamStatus(executionId);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function renderStreamStatus(executionId) {
|
|
304
|
+
const statusContainer = document.getElementById('cliStreamStatus');
|
|
305
|
+
if (!statusContainer) return;
|
|
306
|
+
|
|
307
|
+
const exec = executionId ? cliStreamExecutions[executionId] : null;
|
|
308
|
+
|
|
309
|
+
if (!exec) {
|
|
310
|
+
statusContainer.innerHTML = '';
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const duration = exec.endTime
|
|
315
|
+
? formatDuration(exec.endTime - exec.startTime)
|
|
316
|
+
: formatDuration(Date.now() - exec.startTime);
|
|
317
|
+
|
|
318
|
+
const statusLabel = exec.status === 'running'
|
|
319
|
+
? _streamT('cliStream.running')
|
|
320
|
+
: exec.status === 'completed'
|
|
321
|
+
? _streamT('cliStream.completed')
|
|
322
|
+
: _streamT('cliStream.error');
|
|
323
|
+
|
|
324
|
+
statusContainer.innerHTML = `
|
|
325
|
+
<div class="cli-stream-status-info">
|
|
326
|
+
<div class="cli-stream-status-item">
|
|
327
|
+
<span class="cli-stream-tab-status ${exec.status}"></span>
|
|
328
|
+
<span>${statusLabel}</span>
|
|
329
|
+
</div>
|
|
330
|
+
<div class="cli-stream-status-item">
|
|
331
|
+
<i data-lucide="clock"></i>
|
|
332
|
+
<span>${duration}</span>
|
|
333
|
+
</div>
|
|
334
|
+
<div class="cli-stream-status-item">
|
|
335
|
+
<i data-lucide="file-text"></i>
|
|
336
|
+
<span>${exec.output.length} ${_streamT('cliStream.lines') || 'lines'}</span>
|
|
337
|
+
</div>
|
|
338
|
+
</div>
|
|
339
|
+
<div class="cli-stream-status-actions">
|
|
340
|
+
<button class="cli-stream-toggle-btn ${autoScrollEnabled ? 'active' : ''}"
|
|
341
|
+
onclick="toggleAutoScroll()"
|
|
342
|
+
title="${_streamT('cliStream.autoScroll')}">
|
|
343
|
+
<i data-lucide="arrow-down-to-line"></i>
|
|
344
|
+
<span data-i18n="cliStream.autoScroll">${_streamT('cliStream.autoScroll')}</span>
|
|
345
|
+
</button>
|
|
346
|
+
</div>
|
|
347
|
+
`;
|
|
348
|
+
|
|
349
|
+
if (typeof lucide !== 'undefined') lucide.createIcons();
|
|
350
|
+
|
|
351
|
+
// Update duration periodically for running executions
|
|
352
|
+
if (exec.status === 'running') {
|
|
353
|
+
setTimeout(() => {
|
|
354
|
+
if (activeStreamTab === executionId && cliStreamExecutions[executionId]?.status === 'running') {
|
|
355
|
+
renderStreamStatus(executionId);
|
|
356
|
+
}
|
|
357
|
+
}, 1000);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function switchStreamTab(executionId) {
|
|
362
|
+
if (!cliStreamExecutions[executionId]) return;
|
|
363
|
+
|
|
364
|
+
activeStreamTab = executionId;
|
|
365
|
+
renderStreamTabs();
|
|
366
|
+
renderStreamContent(executionId);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function updateStreamBadge() {
|
|
370
|
+
const badge = document.getElementById('cliStreamBadge');
|
|
371
|
+
if (!badge) return;
|
|
372
|
+
|
|
373
|
+
const runningCount = Object.values(cliStreamExecutions).filter(e => e.status === 'running').length;
|
|
374
|
+
|
|
375
|
+
if (runningCount > 0) {
|
|
376
|
+
badge.textContent = runningCount;
|
|
377
|
+
badge.classList.add('has-running');
|
|
378
|
+
} else {
|
|
379
|
+
badge.textContent = '';
|
|
380
|
+
badge.classList.remove('has-running');
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// ===== User Actions =====
|
|
385
|
+
function closeStream(executionId) {
|
|
386
|
+
const exec = cliStreamExecutions[executionId];
|
|
387
|
+
if (!exec || exec.status === 'running') return;
|
|
388
|
+
|
|
389
|
+
delete cliStreamExecutions[executionId];
|
|
390
|
+
|
|
391
|
+
// Switch to another tab if this was active
|
|
392
|
+
if (activeStreamTab === executionId) {
|
|
393
|
+
const remaining = Object.keys(cliStreamExecutions);
|
|
394
|
+
activeStreamTab = remaining.length > 0 ? remaining[0] : null;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
renderStreamTabs();
|
|
398
|
+
renderStreamContent(activeStreamTab);
|
|
399
|
+
updateStreamBadge();
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function clearCompletedStreams() {
|
|
403
|
+
const toRemove = Object.keys(cliStreamExecutions).filter(
|
|
404
|
+
id => cliStreamExecutions[id].status !== 'running'
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
toRemove.forEach(id => delete cliStreamExecutions[id]);
|
|
408
|
+
|
|
409
|
+
// Update active tab if needed
|
|
410
|
+
if (activeStreamTab && !cliStreamExecutions[activeStreamTab]) {
|
|
411
|
+
const remaining = Object.keys(cliStreamExecutions);
|
|
412
|
+
activeStreamTab = remaining.length > 0 ? remaining[0] : null;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
renderStreamTabs();
|
|
416
|
+
renderStreamContent(activeStreamTab);
|
|
417
|
+
updateStreamBadge();
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function toggleAutoScroll() {
|
|
421
|
+
autoScrollEnabled = !autoScrollEnabled;
|
|
422
|
+
|
|
423
|
+
if (autoScrollEnabled && activeStreamTab) {
|
|
424
|
+
const content = document.getElementById('cliStreamContent');
|
|
425
|
+
if (content) {
|
|
426
|
+
content.scrollTop = content.scrollHeight;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
renderStreamStatus(activeStreamTab);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function handleStreamContentScroll() {
|
|
434
|
+
const content = document.getElementById('cliStreamContent');
|
|
435
|
+
if (!content) return;
|
|
436
|
+
|
|
437
|
+
// If user scrolls up, disable auto-scroll
|
|
438
|
+
const isAtBottom = content.scrollHeight - content.scrollTop <= content.clientHeight + 50;
|
|
439
|
+
if (!isAtBottom && autoScrollEnabled) {
|
|
440
|
+
autoScrollEnabled = false;
|
|
441
|
+
renderStreamStatus(activeStreamTab);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// ===== Helper Functions =====
|
|
446
|
+
function formatDuration(ms) {
|
|
447
|
+
if (ms < 1000) return `${ms}ms`;
|
|
448
|
+
|
|
449
|
+
const seconds = Math.floor(ms / 1000);
|
|
450
|
+
if (seconds < 60) return `${seconds}s`;
|
|
451
|
+
|
|
452
|
+
const minutes = Math.floor(seconds / 60);
|
|
453
|
+
const remainingSeconds = seconds % 60;
|
|
454
|
+
if (minutes < 60) return `${minutes}m ${remainingSeconds}s`;
|
|
455
|
+
|
|
456
|
+
const hours = Math.floor(minutes / 60);
|
|
457
|
+
const remainingMinutes = minutes % 60;
|
|
458
|
+
return `${hours}h ${remainingMinutes}m`;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function escapeHtml(text) {
|
|
462
|
+
if (!text) return '';
|
|
463
|
+
const div = document.createElement('div');
|
|
464
|
+
div.textContent = text;
|
|
465
|
+
return div.innerHTML;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function escapeRegex(string) {
|
|
469
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// ===== Search Functions =====
|
|
473
|
+
function handleSearchInput(event) {
|
|
474
|
+
searchFilter = event.target.value;
|
|
475
|
+
renderStreamContent(activeStreamTab);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function clearSearch() {
|
|
479
|
+
searchFilter = '';
|
|
480
|
+
const searchInput = document.getElementById('cliStreamSearchInput');
|
|
481
|
+
if (searchInput) {
|
|
482
|
+
searchInput.value = '';
|
|
483
|
+
}
|
|
484
|
+
renderStreamContent(activeStreamTab);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Translation helper with fallback (uses global t from i18n.js)
|
|
488
|
+
function _streamT(key) {
|
|
489
|
+
// First try global t() from i18n.js
|
|
490
|
+
if (typeof t === 'function' && t !== _streamT) {
|
|
491
|
+
try {
|
|
492
|
+
return t(key);
|
|
493
|
+
} catch (e) {
|
|
494
|
+
// Fall through to fallbacks
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
// Fallback values
|
|
498
|
+
const fallbacks = {
|
|
499
|
+
'cliStream.noStreams': 'No active CLI executions',
|
|
500
|
+
'cliStream.noStreamsHint': 'Start a CLI command to see streaming output',
|
|
501
|
+
'cliStream.running': 'Running',
|
|
502
|
+
'cliStream.completed': 'Completed',
|
|
503
|
+
'cliStream.error': 'Error',
|
|
504
|
+
'cliStream.autoScroll': 'Auto-scroll',
|
|
505
|
+
'cliStream.close': 'Close',
|
|
506
|
+
'cliStream.cannotCloseRunning': 'Cannot close running execution',
|
|
507
|
+
'cliStream.lines': 'lines',
|
|
508
|
+
'cliStream.searchPlaceholder': 'Search output...',
|
|
509
|
+
'cliStream.filterResults': 'results'
|
|
510
|
+
};
|
|
511
|
+
return fallbacks[key] || key;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// Initialize when DOM is ready
|
|
515
|
+
if (document.readyState === 'loading') {
|
|
516
|
+
document.addEventListener('DOMContentLoaded', initCliStreamViewer);
|
|
517
|
+
} else {
|
|
518
|
+
initCliStreamViewer();
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// ===== Global Exposure =====
|
|
522
|
+
window.toggleCliStreamViewer = toggleCliStreamViewer;
|
|
523
|
+
window.handleCliStreamStarted = handleCliStreamStarted;
|
|
524
|
+
window.handleCliStreamOutput = handleCliStreamOutput;
|
|
525
|
+
window.handleCliStreamCompleted = handleCliStreamCompleted;
|
|
526
|
+
window.handleCliStreamError = handleCliStreamError;
|
|
527
|
+
window.switchStreamTab = switchStreamTab;
|
|
528
|
+
window.closeStream = closeStream;
|
|
529
|
+
window.clearCompletedStreams = clearCompletedStreams;
|
|
530
|
+
window.toggleAutoScroll = toggleAutoScroll;
|
|
531
|
+
window.handleSearchInput = handleSearchInput;
|
|
532
|
+
window.clearSearch = clearSearch;
|