@bramblex/codex-workbench 0.1.2 → 0.1.4
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 +71 -12
- package/bin/codex-workbench +1 -1
- package/package.json +10 -3
- package/src/cli-output.js +109 -0
- package/src/cli.js +60 -723
- package/src/config.js +18 -0
- package/src/model/directories.js +38 -0
- package/src/model/format.js +21 -0
- package/src/model/session-store.js +156 -0
- package/src/model/workbench-config.js +38 -0
- package/src/services/codex-runner.js +75 -0
- package/src/services/session-sources.js +148 -0
- package/src/services/ssh-runner.js +43 -0
- package/src/ui/directory-picker.js +219 -0
- package/src/ui/workbench.js +657 -0
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# codex-workbench
|
|
2
2
|
|
|
3
|
-
A small terminal workbench for browsing and managing
|
|
3
|
+
A small terminal workbench for browsing and managing Codex sessions.
|
|
4
4
|
|
|
5
|
-
It reads Codex session JSONL files, groups sessions by project directory, and lets you inspect, rename, annotate, resume, fork, archive, unarchive, or delete sessions from either a command-line interface or an interactive terminal UI.
|
|
5
|
+
It reads local Codex session JSONL files, can aggregate compatible remote workbenches over SSH, groups sessions by source and project directory, and lets you inspect, rename, annotate, resume, fork, archive, unarchive, or delete sessions from either a command-line interface or an interactive terminal UI.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -44,13 +44,16 @@ codex-workbench list [--json] [--cwd <dir>] [--all]
|
|
|
44
44
|
codex-workbench show <session>
|
|
45
45
|
codex-workbench rename <session> <name>
|
|
46
46
|
codex-workbench note <session> <note>
|
|
47
|
+
codex-workbench new [--cwd <dir>] [prompt...]
|
|
48
|
+
codex-workbench dirs [--cwd <dir>] [--json]
|
|
49
|
+
codex-workbench mkdir [--cwd <dir>] <name> [--json]
|
|
47
50
|
codex-workbench resume <session> [prompt...]
|
|
48
51
|
codex-workbench fork <session>
|
|
49
52
|
codex-workbench archive <session>
|
|
50
53
|
codex-workbench unarchive <session>
|
|
51
54
|
codex-workbench hide <session>
|
|
52
55
|
codex-workbench unhide <session>
|
|
53
|
-
codex-workbench delete <session> [--force]
|
|
56
|
+
codex-workbench delete <session> [--force] [--file]
|
|
54
57
|
```
|
|
55
58
|
|
|
56
59
|
Run without arguments to open the interactive UI:
|
|
@@ -74,6 +77,13 @@ codex-workbench list --cwd /path/to/project
|
|
|
74
77
|
codex-workbench list --all
|
|
75
78
|
```
|
|
76
79
|
|
|
80
|
+
Use `new` to start a fresh Codex session in a project directory:
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
codex-workbench new --cwd /path/to/project
|
|
84
|
+
codex-workbench new --cwd /path/to/project "Summarize this repo"
|
|
85
|
+
```
|
|
86
|
+
|
|
77
87
|
Use `hide` for sessions that Codex itself can no longer resume, archive, or delete. Hidden sessions are removed from the default list but remain visible with `--all`:
|
|
78
88
|
|
|
79
89
|
```sh
|
|
@@ -82,6 +92,12 @@ codex-workbench unhide <session>
|
|
|
82
92
|
codex-workbench list --all
|
|
83
93
|
```
|
|
84
94
|
|
|
95
|
+
Use `delete --file` only for broken sessions that Codex can no longer remove. It deletes the local session JSONL file directly:
|
|
96
|
+
|
|
97
|
+
```sh
|
|
98
|
+
codex-workbench delete <session> --file
|
|
99
|
+
```
|
|
100
|
+
|
|
85
101
|
Use `doctor` to check which Codex executable the CLI will launch:
|
|
86
102
|
|
|
87
103
|
```sh
|
|
@@ -92,32 +108,71 @@ Most commands accept a full session id, a unique prefix, a saved name, or a sess
|
|
|
92
108
|
|
|
93
109
|
## Interactive UI
|
|
94
110
|
|
|
95
|
-
The UI groups sessions by working directory, with sessions
|
|
111
|
+
The UI groups sessions by source and working directory, with sources/projects on the left, sessions on the upper right, and details below. When you start or resume a session, Codex temporarily takes over the terminal; when Codex exits, codex-workbench redraws the UI.
|
|
96
112
|
|
|
97
113
|
Common keys:
|
|
98
114
|
|
|
99
|
-
- `Enter
|
|
115
|
+
- `Enter`: resume the selected session in Codex
|
|
116
|
+
- `n`: create a new project/session from Projects, or a new session from Sessions/Details
|
|
100
117
|
- `f`: fork the selected session
|
|
101
118
|
- `v`: print session details and exit
|
|
102
|
-
- `
|
|
119
|
+
- `r`: rename the selected session
|
|
103
120
|
- `o`: add or edit a note
|
|
104
121
|
- `a`: archive the selected session
|
|
105
122
|
- `d`: delete the selected session
|
|
106
|
-
- `Tab`: switch focus between
|
|
107
|
-
- `Left`/`Right` or `h`/`l`:
|
|
123
|
+
- `Tab`: switch focus between projects, sessions, and details
|
|
124
|
+
- `Left`/`Right` or `h`/`l`: move between panes
|
|
108
125
|
- `q`, `Esc`, or `Ctrl+C`: quit
|
|
109
126
|
|
|
127
|
+
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.
|
|
128
|
+
|
|
129
|
+
## Remote Servers
|
|
130
|
+
|
|
131
|
+
codex-workbench can show remote sessions in the interactive UI by calling `cwb` over SSH. The remote server must have `codex-workbench` installed and the configured `cwb` command available to SSH non-interactive commands.
|
|
132
|
+
|
|
133
|
+
Create `~/.codex/codex-workbench.config.json`:
|
|
134
|
+
|
|
135
|
+
```json
|
|
136
|
+
{
|
|
137
|
+
"servers": [
|
|
138
|
+
{
|
|
139
|
+
"id": "a",
|
|
140
|
+
"label": "A server",
|
|
141
|
+
"target": "user@example.com"
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"id": "b",
|
|
145
|
+
"label": "B server",
|
|
146
|
+
"target": "b-host",
|
|
147
|
+
"command": "/usr/local/bin/cwb",
|
|
148
|
+
"sshArgs": ["-p", "2222"]
|
|
149
|
+
}
|
|
150
|
+
]
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
The UI will render `Local`, `A server`, and `B server` as source groups. Remote list, rename, note, hide, directory browsing, new session, resume, fork, archive, and delete commands are executed as SSH calls to the remote `cwb`.
|
|
155
|
+
|
|
156
|
+
You can verify a remote source directly with:
|
|
157
|
+
|
|
158
|
+
```sh
|
|
159
|
+
ssh user@example.com 'cwb list --json'
|
|
160
|
+
```
|
|
161
|
+
|
|
110
162
|
## Environment
|
|
111
163
|
|
|
112
164
|
```sh
|
|
113
165
|
CODEX_HOME # default: ~/.codex
|
|
114
166
|
CODEX_SESSIONS_DIR # default: $CODEX_HOME/sessions
|
|
115
167
|
CODEX_WORKBENCH_META # default: $CODEX_HOME/codex-workbench.json
|
|
168
|
+
CODEX_WORKBENCH_CONFIG # default: $CODEX_HOME/codex-workbench.config.json
|
|
116
169
|
CODEX_BIN # default: codex from shell PATH
|
|
117
170
|
```
|
|
118
171
|
|
|
119
172
|
`CODEX_WORKBENCH_META` stores local workbench metadata such as custom names and notes. Session content remains in the Codex sessions directory.
|
|
120
173
|
|
|
174
|
+
`CODEX_WORKBENCH_CONFIG` points to the optional local configuration file for SSH remote sources.
|
|
175
|
+
|
|
121
176
|
By default, codex-workbench launches `codex` through your shell so your normal shell `PATH` applies. Set `CODEX_BIN` if you want to force a specific executable path.
|
|
122
177
|
|
|
123
178
|
## Development
|
|
@@ -130,8 +185,12 @@ npm pack --dry-run
|
|
|
130
185
|
Project layout:
|
|
131
186
|
|
|
132
187
|
```text
|
|
133
|
-
bin/codex-workbench
|
|
134
|
-
src/cli.js
|
|
135
|
-
src/
|
|
136
|
-
|
|
188
|
+
bin/codex-workbench # executable entrypoint
|
|
189
|
+
src/cli.js # thin CLI router
|
|
190
|
+
src/cli-output.js # terminal output presenters
|
|
191
|
+
src/codex-bin.js # Codex executable discovery
|
|
192
|
+
src/model/ # session parsing, metadata, config, and format helpers
|
|
193
|
+
src/services/ # Codex process runners and session source adapters
|
|
194
|
+
src/ui/ # interactive UI and components
|
|
195
|
+
test/ # smoke and service tests
|
|
137
196
|
```
|
package/bin/codex-workbench
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bramblex/codex-workbench",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Terminal workbench for browsing and managing local Codex sessions.",
|
|
3
|
+
"version": "0.1.4",
|
|
4
|
+
"description": "Terminal workbench for browsing and managing local and SSH Codex sessions.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -32,12 +32,19 @@
|
|
|
32
32
|
"LICENSE"
|
|
33
33
|
],
|
|
34
34
|
"scripts": {
|
|
35
|
-
"test": "node --check
|
|
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",
|
|
36
|
+
"pty:codex": "node scripts/pty-codex.js",
|
|
37
|
+
"tui:codex": "node scripts/tui-pty-codex.js",
|
|
38
|
+
"xterm:codex": "node scripts/blessed-xterm-codex.js"
|
|
36
39
|
},
|
|
37
40
|
"engines": {
|
|
38
41
|
"node": ">=18"
|
|
39
42
|
},
|
|
40
43
|
"dependencies": {
|
|
41
44
|
"blessed": "^0.1.81"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"blessed-xterm": "^1.5.1",
|
|
48
|
+
"node-pty": "^1.2.0-beta.13"
|
|
42
49
|
}
|
|
43
50
|
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { inspectCodexBin } = require('./codex-bin');
|
|
5
|
+
const { localTime, shortId, truncate } = require('./model/format');
|
|
6
|
+
|
|
7
|
+
function usage() {
|
|
8
|
+
console.log(`codex-workbench
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
codex-workbench [ui]
|
|
12
|
+
codex-workbench doctor
|
|
13
|
+
codex-workbench list [--json] [--cwd <dir>] [--all]
|
|
14
|
+
codex-workbench show <session>
|
|
15
|
+
codex-workbench rename <session> <name>
|
|
16
|
+
codex-workbench note <session> <note>
|
|
17
|
+
codex-workbench new [--cwd <dir>] [prompt...]
|
|
18
|
+
codex-workbench dirs [--cwd <dir>] [--json]
|
|
19
|
+
codex-workbench mkdir [--cwd <dir>] <name> [--json]
|
|
20
|
+
codex-workbench resume <session> [prompt...]
|
|
21
|
+
codex-workbench fork <session>
|
|
22
|
+
codex-workbench archive <session>
|
|
23
|
+
codex-workbench unarchive <session>
|
|
24
|
+
codex-workbench hide <session>
|
|
25
|
+
codex-workbench unhide <session>
|
|
26
|
+
codex-workbench delete <session> [--force] [--file]
|
|
27
|
+
|
|
28
|
+
Environment:
|
|
29
|
+
CODEX_HOME default: ~/.codex
|
|
30
|
+
CODEX_SESSIONS_DIR default: $CODEX_HOME/sessions
|
|
31
|
+
CODEX_WORKBENCH_META default: $CODEX_HOME/codex-workbench.json
|
|
32
|
+
CODEX_WORKBENCH_CONFIG default: $CODEX_HOME/codex-workbench.config.json
|
|
33
|
+
CODEX_BIN default: codex from shell PATH
|
|
34
|
+
`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function printList(sessions, opts = {}) {
|
|
38
|
+
const filtered = sessions.filter((session) => {
|
|
39
|
+
if (!opts.all && (session.archived || session.hidden)) return false;
|
|
40
|
+
if (opts.cwd) return path.resolve(session.cwd) === path.resolve(opts.cwd);
|
|
41
|
+
return true;
|
|
42
|
+
});
|
|
43
|
+
if (opts.json) {
|
|
44
|
+
console.log(JSON.stringify(filtered, null, 2));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const groups = new Map();
|
|
48
|
+
for (const session of filtered) {
|
|
49
|
+
const source = session.sourceLabel || '';
|
|
50
|
+
const key = `${source}\0${session.cwd}`;
|
|
51
|
+
if (!groups.has(key)) groups.set(key, { source, cwd: session.cwd, sessions: [] });
|
|
52
|
+
groups.get(key).sessions.push(session);
|
|
53
|
+
}
|
|
54
|
+
for (const group of groups.values()) {
|
|
55
|
+
console.log(`\n${group.source ? `${group.source}: ` : ''}${group.cwd}`);
|
|
56
|
+
for (const session of group.sessions) {
|
|
57
|
+
const label = session.name || truncate(session.first || session.last || '(no prompt)', 56);
|
|
58
|
+
const flags = [session.archived ? 'archived' : '', session.hidden ? 'hidden' : '', session.note ? 'note' : ''].filter(Boolean).join(',');
|
|
59
|
+
console.log(` ${shortId(session.id)} ${localTime(session.updatedAt)} ${String(session.turns).padStart(2)} turns ${flags ? `[${flags}] ` : ''}${label}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (!filtered.length) console.log('No sessions found.');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function printShow(session) {
|
|
66
|
+
console.log(`${session.name || '(unnamed)'} ${session.archived ? '[archived]' : ''}${session.hidden ? '[hidden]' : ''}`);
|
|
67
|
+
console.log(`id: ${session.id}`);
|
|
68
|
+
if (session.sourceLabel) console.log(`source: ${session.sourceLabel}`);
|
|
69
|
+
console.log(`cwd: ${session.cwd}`);
|
|
70
|
+
console.log(`started: ${localTime(session.startedAt)}`);
|
|
71
|
+
console.log(`updated: ${localTime(session.updatedAt)}`);
|
|
72
|
+
if (session.file) console.log(`file: ${session.file}`);
|
|
73
|
+
console.log(`turns: ${session.turns}`);
|
|
74
|
+
if (session.note) console.log(`note: ${session.note}`);
|
|
75
|
+
console.log('\nMessages:');
|
|
76
|
+
for (const msg of session.messages) {
|
|
77
|
+
if (msg.role === 'developer') continue;
|
|
78
|
+
const prefix = msg.role === 'assistant' ? 'A' : msg.role === 'user' ? 'U' : msg.role.slice(0, 1).toUpperCase();
|
|
79
|
+
console.log(` ${prefix}: ${truncate(msg.text, 180)}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function printDoctor() {
|
|
84
|
+
const result = inspectCodexBin();
|
|
85
|
+
console.log('codex-workbench doctor');
|
|
86
|
+
console.log(`status: ${result.ok ? 'ok' : 'error'}`);
|
|
87
|
+
if (result.path) console.log(`codex: ${result.path}`);
|
|
88
|
+
if (result.source) console.log(`source: ${result.source}`);
|
|
89
|
+
if (result.error) console.log(`error: ${result.error}`);
|
|
90
|
+
console.log('\nChecks:');
|
|
91
|
+
for (const check of result.checks) {
|
|
92
|
+
const parts = [
|
|
93
|
+
check.source,
|
|
94
|
+
check.mode ? `mode=${check.mode}` : '',
|
|
95
|
+
check.shell ? `shell=${check.shell}` : '',
|
|
96
|
+
check.path ? `path=${check.path}` : '',
|
|
97
|
+
`executable=${check.executable ? 'yes' : 'no'}`,
|
|
98
|
+
].filter(Boolean);
|
|
99
|
+
console.log(` - ${parts.join(' ')}`);
|
|
100
|
+
}
|
|
101
|
+
if (!result.ok) process.exitCode = 1;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = {
|
|
105
|
+
printDoctor,
|
|
106
|
+
printList,
|
|
107
|
+
printShow,
|
|
108
|
+
usage,
|
|
109
|
+
};
|