@bramblex/codex-workbench 0.1.6 → 0.1.8

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 CHANGED
@@ -125,6 +125,9 @@ Common keys:
125
125
  - `d`: delete the selected session
126
126
  - `Tab`: switch focus between projects, sessions, and details
127
127
  - `Left`/`Right` or `h`/`l`: move between panes
128
+ - `0`: show all sources
129
+ - `1`-`9`: jump to a source
130
+ - `[`/`]`: switch to the previous or next source
128
131
  - `q`, `Esc`, or `Ctrl+C`: quit
129
132
 
130
133
  In the directory picker, use `Up`/`Down` or `j`/`k` to move, `Left`/`h` for the parent directory, `Right`/`l` for the selected child directory, `n` to create a child directory, and `Enter` to choose the selected directory.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bramblex/codex-workbench",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Terminal workbench for browsing and managing local and SSH Codex sessions.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -32,7 +32,7 @@
32
32
  "LICENSE"
33
33
  ],
34
34
  "scripts": {
35
- "test": "node -e \"const fs=require('fs'),{spawnSync}=require('child_process');function files(d){return fs.readdirSync(d,{withFileTypes:true}).flatMap(e=>e.isDirectory()?files(d+'/'+e.name):e.name.endsWith('.js')?[d+'/'+e.name]:[])}for(const f of files('src')){const r=spawnSync(process.execPath,['--check',f],{stdio:'inherit'});if(r.status)process.exit(r.status)}\" && node --check bin/codex-workbench && node --check scripts/pty-codex.js && node --check scripts/tui-pty-codex.js && node --check scripts/blessed-xterm-codex.js && node test/codex-bin.test.js && node test/session-sources.test.js && node test/smoke.js",
35
+ "test": "node -e \"const fs=require('fs'),{spawnSync}=require('child_process');function files(d){return fs.readdirSync(d,{withFileTypes:true}).flatMap(e=>e.isDirectory()?files(d+'/'+e.name):e.name.endsWith('.js')?[d+'/'+e.name]:[])}for(const f of files('src')){const r=spawnSync(process.execPath,['--check',f],{stdio:'inherit'});if(r.status)process.exit(r.status)}\" && node --check bin/codex-workbench && node --check scripts/pty-codex.js && node --check scripts/tui-pty-codex.js && node --check scripts/blessed-xterm-codex.js && node test/codex-bin.test.js && node test/blessed-compat.test.js && node test/session-sources.test.js && node test/smoke.js",
36
36
  "pty:codex": "node scripts/pty-codex.js",
37
37
  "tui:codex": "node scripts/tui-pty-codex.js",
38
38
  "xterm:codex": "node scripts/blessed-xterm-codex.js"
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+
3
+ const Tput = require('blessed/lib/tput');
4
+
5
+ function patchBlessedTerminfo() {
6
+ if (Tput.prototype._codexWorkbenchPatched) return;
7
+
8
+ const compile = Tput.prototype._compile;
9
+ Tput.prototype._compile = function patchedCompile(info, key, str) {
10
+ if (key === 'plab_norm') return () => '';
11
+ return compile.call(this, info, key, str);
12
+ };
13
+
14
+ Tput.prototype._codexWorkbenchPatched = true;
15
+ }
16
+
17
+ patchBlessedTerminfo();
18
+
19
+ module.exports = {
20
+ patchBlessedTerminfo,
21
+ };
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const path = require('path');
4
+ require('./blessed-compat');
4
5
  const blessed = require('blessed');
5
6
  const { createChildDirectory, directoryNameError, listDirectories } = require('../model/directories');
6
7
 
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const path = require('path');
4
+ require('./blessed-compat');
4
5
  const blessed = require('blessed');
5
6
  const { printList, printShow } = require('../cli-output');
6
7
  const { deleteSessionFile } = require('../model/session-store');
@@ -190,11 +191,26 @@ async function runWorkbench() {
190
191
  return `${group.source.label}: ${group.cwd}`;
191
192
  };
192
193
 
194
+ const sourceShortcut = (source) => {
195
+ const index = sources.findIndex((item) => item.id === source.id);
196
+ return index >= 0 && index < 9 ? String(index + 1) : '';
197
+ };
198
+
199
+ const machineLabel = (source, count) => {
200
+ const shortcut = sourceShortcut(source);
201
+ const prefix = shortcut ? `${shortcut} ` : '';
202
+ const maxLabel = Math.max(8, projectWidth - 18);
203
+ const text = `${prefix}${truncate(source.label, maxLabel)} (${count})`;
204
+ const width = Math.max(12, projectWidth - 4);
205
+ const head = `- ${text} `;
206
+ return `${head}${'-'.repeat(width)}`.slice(0, width);
207
+ };
208
+
193
209
  const projectLabel = (group) => {
194
- if (group.kind === 'all') return `All (${sessions.length})`;
210
+ if (group.kind === 'all') return `0 All (${sessions.length})`;
195
211
  if (group.kind === 'source') {
196
212
  const count = sessionsForSource(group.source.id).length;
197
- return `${truncate(group.source.label, Math.max(10, projectWidth - 6))} (${count})`;
213
+ return machineLabel(group.source, count);
198
214
  }
199
215
  const count = sessions.filter((session) => session.sourceId === group.source.id && session.cwd === group.cwd).length;
200
216
  const base = path.basename(group.cwd) || group.cwd;
@@ -409,11 +425,11 @@ async function runWorkbench() {
409
425
 
410
426
  const firstLine = message || 'Ready';
411
427
  if (projectFocused) {
412
- status.setContent(`${firstLine}\nProjects: ↑/↓ select project n new project →/Enter sessions Tab focus q quit`);
428
+ status.setContent(`${firstLine}\nSources: ↑/↓ select 0 all 1-9 machine [/] prev/next n new sessions q quit`);
413
429
  } else if (detailFocused) {
414
- status.setContent(`${firstLine}\nDetails: ↑/↓ scroll n new session ← sessions → projects Tab focus q quit`);
430
+ status.setContent(`${firstLine}\nDetails: ↑/↓ scroll 1-9 machine [/] prev/next n new ← sessions q quit`);
415
431
  } else {
416
- status.setContent(`${firstLine}\nSessions: ↑/↓ select Enter resume r rename n new session f fork v view o note a archive d delete q quit`);
432
+ status.setContent(`${firstLine}\nSessions: ↑/↓ select Enter resume 1-9 machine [/] prev/next r rename n new d delete`);
417
433
  }
418
434
  };
419
435
 
@@ -483,6 +499,35 @@ async function runWorkbench() {
483
499
  render();
484
500
  };
485
501
 
502
+ const selectSourceIndex = (sourceIndex) => {
503
+ const source = sources[sourceIndex];
504
+ if (!source) return;
505
+ const nextIndex = groups.findIndex((group) => group.kind === 'source' && group.source.id === source.id);
506
+ if (nextIndex === -1) return;
507
+ selectGroup(nextIndex);
508
+ setMessage(`Switched to ${source.label}.`);
509
+ render();
510
+ };
511
+
512
+ const currentSourceIndex = () => {
513
+ const group = currentGroup();
514
+ if (group.kind === 'source' || group.kind === 'project') {
515
+ return sources.findIndex((source) => source.id === group.source.id);
516
+ }
517
+ const session = selectedSession();
518
+ if (session) return sources.findIndex((source) => source.id === session.sourceId);
519
+ return -1;
520
+ };
521
+
522
+ const switchSource = (delta) => {
523
+ if (!sources.length) return;
524
+ const currentIndex = currentSourceIndex();
525
+ const nextIndex = currentIndex === -1
526
+ ? (delta > 0 ? 0 : sources.length - 1)
527
+ : (currentIndex + delta + sources.length) % sources.length;
528
+ selectSourceIndex(nextIndex);
529
+ };
530
+
486
531
  const runCodexAndReturn = (command, session, args = [], doneText = `${command} finished.`) => {
487
532
  screen.leave();
488
533
  let status = 0;
@@ -667,6 +712,30 @@ async function runWorkbench() {
667
712
  else focusPanel(detailBox, 'details');
668
713
  });
669
714
 
715
+ screen.key(['0'], () => {
716
+ if (promptOpen()) return;
717
+ selectGroup(0);
718
+ setMessage('Switched to all sources.');
719
+ render();
720
+ });
721
+
722
+ for (let i = 1; i <= 9; i += 1) {
723
+ screen.key([String(i)], () => {
724
+ if (promptOpen()) return;
725
+ selectSourceIndex(i - 1);
726
+ });
727
+ }
728
+
729
+ screen.key([']'], () => {
730
+ if (promptOpen()) return;
731
+ switchSource(1);
732
+ });
733
+
734
+ screen.key(['['], () => {
735
+ if (promptOpen()) return;
736
+ switchSource(-1);
737
+ });
738
+
670
739
  screen.key(['q', 'escape', 'C-c'], () => {
671
740
  if (promptOpen()) return;
672
741
  leaveScreen();