@bramblex/codex-workbench 0.1.14 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -20
- package/package.json +2 -2
- package/src/cli-output.js +38 -26
- package/src/cli.js +18 -3
- package/src/config.js +44 -4
- package/src/model/metadata.js +44 -0
- package/src/model/session-store.js +39 -116
- package/src/model/workbench-config.js +48 -12
- package/src/providers/codex.js +267 -0
- package/src/providers/index.js +59 -0
- package/src/providers/pi.js +326 -0
- package/src/services/codex-runner.js +27 -62
- package/src/services/session-sources.js +52 -8
- package/src/ui/workbench.js +89 -21
package/src/ui/workbench.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const path = require('path');
|
|
4
4
|
require('./blessed-compat');
|
|
5
5
|
const blessed = require('blessed');
|
|
6
|
+
const pkg = require('../../package.json');
|
|
6
7
|
const { printList, printShow } = require('../cli-output');
|
|
7
8
|
const { deleteSessionFile } = require('../model/session-store');
|
|
8
9
|
const { localTime, shortId, truncate } = require('../model/format');
|
|
@@ -10,6 +11,7 @@ const {
|
|
|
10
11
|
LOCAL_SOURCE,
|
|
11
12
|
createSourceDirectory,
|
|
12
13
|
listSourceDirectories,
|
|
14
|
+
listSourceBackends,
|
|
13
15
|
loadLocalWorkbenchSessions,
|
|
14
16
|
loadRemoteSourceSessions,
|
|
15
17
|
loadWorkbenchSessions,
|
|
@@ -26,6 +28,7 @@ async function runWorkbench() {
|
|
|
26
28
|
return printList(loadWorkbenchSessions().sessions);
|
|
27
29
|
}
|
|
28
30
|
|
|
31
|
+
const appTitle = `Codex Workbench v${pkg.version}`;
|
|
29
32
|
let sessions = [];
|
|
30
33
|
let sources = [];
|
|
31
34
|
let sourceErrors = [];
|
|
@@ -44,7 +47,7 @@ async function runWorkbench() {
|
|
|
44
47
|
const screen = blessed.screen({
|
|
45
48
|
smartCSR: true,
|
|
46
49
|
fullUnicode: true,
|
|
47
|
-
title:
|
|
50
|
+
title: appTitle,
|
|
48
51
|
});
|
|
49
52
|
|
|
50
53
|
const header = blessed.box({
|
|
@@ -55,7 +58,7 @@ async function runWorkbench() {
|
|
|
55
58
|
height: 3,
|
|
56
59
|
padding: { left: 1, right: 1 },
|
|
57
60
|
style: { fg: 'white', bg: 'blue' },
|
|
58
|
-
content:
|
|
61
|
+
content: appTitle,
|
|
59
62
|
});
|
|
60
63
|
|
|
61
64
|
const projectsList = blessed.list({
|
|
@@ -149,6 +152,27 @@ async function runWorkbench() {
|
|
|
149
152
|
style: { border: { fg: 'red' }, fg: 'white', bg: 'black' },
|
|
150
153
|
});
|
|
151
154
|
|
|
155
|
+
const backendPicker = blessed.list({
|
|
156
|
+
parent: screen,
|
|
157
|
+
label: ' Backend ',
|
|
158
|
+
top: 'center',
|
|
159
|
+
left: 'center',
|
|
160
|
+
width: 42,
|
|
161
|
+
height: 8,
|
|
162
|
+
border: 'line',
|
|
163
|
+
hidden: true,
|
|
164
|
+
mouse: true,
|
|
165
|
+
keys: true,
|
|
166
|
+
vi: false,
|
|
167
|
+
style: {
|
|
168
|
+
border: { fg: 'yellow' },
|
|
169
|
+
selected: { fg: 'black', bg: 'yellow', bold: true },
|
|
170
|
+
item: { fg: 'white' },
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
let backendPickerState = null;
|
|
175
|
+
|
|
152
176
|
const sessionsForSource = (sourceId) => sessions.filter((session) => session.sourceId === sourceId);
|
|
153
177
|
|
|
154
178
|
const buildGroups = () => {
|
|
@@ -223,12 +247,13 @@ async function runWorkbench() {
|
|
|
223
247
|
|
|
224
248
|
const sessionLabel = (session) => {
|
|
225
249
|
const flags = [
|
|
250
|
+
session.backend || '',
|
|
226
251
|
session.name ? 'renamed' : '',
|
|
227
252
|
session.note ? 'note' : '',
|
|
228
253
|
].filter(Boolean).join(',');
|
|
229
254
|
const title = session.name || session.first || session.last || '(no prompt)';
|
|
230
255
|
const flagText = flags ? `[${flags}]` : '';
|
|
231
|
-
return `${shortId(session.id)} ${String(session.turns).padStart(2)}t ${truncate(localTime(session.updatedAt), 18)} ${flagText} ${truncate(title,
|
|
256
|
+
return `${shortId(session.id)} ${String(session.turns).padStart(2)}t ${truncate(localTime(session.updatedAt), 18)} ${flagText} ${truncate(title, 88)}`;
|
|
232
257
|
};
|
|
233
258
|
|
|
234
259
|
const detailContent = (session) => {
|
|
@@ -238,6 +263,7 @@ async function runWorkbench() {
|
|
|
238
263
|
title,
|
|
239
264
|
'',
|
|
240
265
|
`id: ${session.id}`,
|
|
266
|
+
`backend: ${session.backend || 'unknown'}`,
|
|
241
267
|
`source: ${session.sourceLabel || 'Local'}`,
|
|
242
268
|
`cwd: ${session.cwd}`,
|
|
243
269
|
`started: ${localTime(session.startedAt)}`,
|
|
@@ -256,7 +282,7 @@ async function runWorkbench() {
|
|
|
256
282
|
status.style.fg = isError ? 'red' : 'white';
|
|
257
283
|
};
|
|
258
284
|
|
|
259
|
-
const visibleSession = (session) => !session.archived
|
|
285
|
+
const visibleSession = (session) => !session.archived;
|
|
260
286
|
|
|
261
287
|
const sortSessionList = (list) => {
|
|
262
288
|
list.sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt));
|
|
@@ -440,7 +466,7 @@ async function runWorkbench() {
|
|
|
440
466
|
const render = () => {
|
|
441
467
|
applyLayout();
|
|
442
468
|
const visible = currentSessions();
|
|
443
|
-
header.setContent(`
|
|
469
|
+
header.setContent(` ${appTitle}\n ${visible.length}/${sessions.length} visible ${groupDisplayName(currentGroup())}`);
|
|
444
470
|
detailBox.setContent(detailContent(selectedSession()));
|
|
445
471
|
updateFocusStyles();
|
|
446
472
|
screen.render();
|
|
@@ -463,6 +489,31 @@ async function runWorkbench() {
|
|
|
463
489
|
question.ask(label, (err, answer) => resolve(!err && Boolean(answer)));
|
|
464
490
|
});
|
|
465
491
|
|
|
492
|
+
const askBackend = (source) => new Promise((resolve) => {
|
|
493
|
+
let backends = [];
|
|
494
|
+
try {
|
|
495
|
+
backends = listSourceBackends(source);
|
|
496
|
+
} catch (err) {
|
|
497
|
+
setMessage(`error: ${err.message}`, true);
|
|
498
|
+
render();
|
|
499
|
+
resolve(null);
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
if (backends.length <= 1) {
|
|
503
|
+
resolve(backends[0] ? backends[0].id : null);
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
backendPickerState = { backends, resolve };
|
|
508
|
+
backendPicker.clearItems();
|
|
509
|
+
backendPicker.setItems(backends.map((backend) => `${backend.id} ${backend.label || backend.id}`));
|
|
510
|
+
backendPicker.select(0);
|
|
511
|
+
backendPicker.show();
|
|
512
|
+
backendPicker.setFront();
|
|
513
|
+
backendPicker.focus();
|
|
514
|
+
screen.render();
|
|
515
|
+
});
|
|
516
|
+
|
|
466
517
|
const directoryPicker = createDirectoryPicker({
|
|
467
518
|
askInput,
|
|
468
519
|
focusOnClose: () => focusPanel(projectsList, 'projects'),
|
|
@@ -470,7 +521,16 @@ async function runWorkbench() {
|
|
|
470
521
|
truncate,
|
|
471
522
|
});
|
|
472
523
|
|
|
473
|
-
const
|
|
524
|
+
const closeBackendPicker = (backend = null) => {
|
|
525
|
+
if (!backendPickerState) return;
|
|
526
|
+
const { resolve } = backendPickerState;
|
|
527
|
+
backendPickerState = null;
|
|
528
|
+
backendPicker.hide();
|
|
529
|
+
focusPanel(sessionsList, 'sessions');
|
|
530
|
+
resolve(backend);
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
const promptOpen = () => prompt.visible || question.visible || directoryPicker.isOpen() || Boolean(backendPickerState);
|
|
474
534
|
|
|
475
535
|
const leaveScreen = () => {
|
|
476
536
|
closed = true;
|
|
@@ -573,17 +633,23 @@ async function runWorkbench() {
|
|
|
573
633
|
createDirectory: (parent, name) => createSourceDirectory(source, parent, name),
|
|
574
634
|
});
|
|
575
635
|
|
|
576
|
-
const runNewCodexAndReturn = (cwd, args = []) => {
|
|
636
|
+
const runNewCodexAndReturn = async (cwd, args = []) => {
|
|
577
637
|
const source = currentSource();
|
|
578
638
|
const resolvedCwd = source.remote ? cwd : usableCwd(cwd);
|
|
639
|
+
const backend = await askBackend(source);
|
|
640
|
+
if (!backend) {
|
|
641
|
+
setMessage('New session cancelled.');
|
|
642
|
+
render();
|
|
643
|
+
return null;
|
|
644
|
+
}
|
|
579
645
|
screen.leave();
|
|
580
646
|
let status = 0;
|
|
581
647
|
try {
|
|
582
|
-
status = runSourceNewSession(source, resolvedCwd, args);
|
|
648
|
+
status = runSourceNewSession(source, resolvedCwd, args, backend);
|
|
583
649
|
} finally {
|
|
584
650
|
screen.enter();
|
|
585
651
|
}
|
|
586
|
-
const label = source.remote ? `${source.label}: ${resolvedCwd}` : resolvedCwd
|
|
652
|
+
const label = source.remote ? `${source.label}: ${resolvedCwd}` : `${resolvedCwd} (${backend})`;
|
|
587
653
|
if (status === 0) refreshAfterAction(`New session finished in ${label}.`, false, resolvedCwd, source.id);
|
|
588
654
|
else refreshAfterAction(`new session exited with code ${status}.`, true, resolvedCwd, source.id);
|
|
589
655
|
return status;
|
|
@@ -695,6 +761,16 @@ async function runWorkbench() {
|
|
|
695
761
|
focusPanel(projectsList, 'projects');
|
|
696
762
|
});
|
|
697
763
|
|
|
764
|
+
backendPicker.key(['enter'], () => {
|
|
765
|
+
if (!backendPickerState) return;
|
|
766
|
+
const backend = backendPickerState.backends[backendPicker.selected];
|
|
767
|
+
closeBackendPicker(backend ? backend.id : null);
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
backendPicker.key(['escape', 'q'], () => {
|
|
771
|
+
closeBackendPicker(null);
|
|
772
|
+
});
|
|
773
|
+
|
|
698
774
|
screen.on('resize', () => {
|
|
699
775
|
applyLayout();
|
|
700
776
|
if (directoryPicker.isOpen()) directoryPicker.applyLayout();
|
|
@@ -766,10 +842,10 @@ async function runWorkbench() {
|
|
|
766
842
|
setMessage('New project cancelled.');
|
|
767
843
|
return render();
|
|
768
844
|
}
|
|
769
|
-
runNewCodexAndReturn(dir);
|
|
845
|
+
await runNewCodexAndReturn(dir);
|
|
770
846
|
return;
|
|
771
847
|
}
|
|
772
|
-
runNewCodexAndReturn(currentProjectCwd());
|
|
848
|
+
await runNewCodexAndReturn(currentProjectCwd());
|
|
773
849
|
});
|
|
774
850
|
|
|
775
851
|
screen.key(['r'], () => runAction(async (session) => {
|
|
@@ -799,11 +875,8 @@ async function runWorkbench() {
|
|
|
799
875
|
const status = runCodexAndReturn('delete', session, ['--force'], `Deleted ${shortId(session.id)}.`);
|
|
800
876
|
if (status !== 0) {
|
|
801
877
|
if (session.sourceRemote) {
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
updateSourceMetadata(session, { hidden: true });
|
|
805
|
-
refreshAfterAction(`Hidden ${shortId(session.id)}.`);
|
|
806
|
-
}
|
|
878
|
+
setMessage(`Remote delete failed for ${shortId(session.id)}.`, true);
|
|
879
|
+
render();
|
|
807
880
|
return;
|
|
808
881
|
}
|
|
809
882
|
const removeFile = await askConfirm(`Codex could not delete ${shortId(session.id)}. Delete its session file?`);
|
|
@@ -812,11 +885,6 @@ async function runWorkbench() {
|
|
|
812
885
|
refreshAfterAction(`Deleted file for ${shortId(session.id)}.`);
|
|
813
886
|
return;
|
|
814
887
|
}
|
|
815
|
-
const hideSession = await askConfirm(`Hide ${shortId(session.id)} from workbench instead?`);
|
|
816
|
-
if (hideSession) {
|
|
817
|
-
updateSourceMetadata(session, { hidden: true });
|
|
818
|
-
refreshAfterAction(`Hidden ${shortId(session.id)}.`);
|
|
819
|
-
}
|
|
820
888
|
}
|
|
821
889
|
}));
|
|
822
890
|
|