@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.
@@ -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: 'Codex Workbench',
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: 'Codex Workbench',
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, 90)}`;
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 && !session.hidden;
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(` Codex Workbench\n ${visible.length}/${sessions.length} visible ${groupDisplayName(currentGroup())}`);
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 promptOpen = () => prompt.visible || question.visible || directoryPicker.isOpen();
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
- const hideSession = await askConfirm(`Remote delete failed for ${shortId(session.id)}. Hide from remote workbench instead?`);
803
- if (hideSession) {
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