@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 +3 -0
- package/package.json +2 -2
- package/src/ui/blessed-compat.js +21 -0
- package/src/ui/directory-picker.js +1 -0
- package/src/ui/workbench.js +74 -5
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.
|
|
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
|
+
};
|
package/src/ui/workbench.js
CHANGED
|
@@ -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
|
|
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}\
|
|
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
|
|
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
|
|
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();
|