@bramblex/codex-workbench 0.1.3 → 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 +64 -11
- package/bin/codex-workbench +1 -1
- package/package.json +10 -3
- package/src/cli-output.js +109 -0
- package/src/cli.js +55 -741
- 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,6 +44,9 @@ 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>
|
|
@@ -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
|
|
@@ -98,32 +108,71 @@ Most commands accept a full session id, a unique prefix, a saved name, or a sess
|
|
|
98
108
|
|
|
99
109
|
## Interactive UI
|
|
100
110
|
|
|
101
|
-
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.
|
|
102
112
|
|
|
103
113
|
Common keys:
|
|
104
114
|
|
|
105
|
-
- `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
|
|
106
117
|
- `f`: fork the selected session
|
|
107
118
|
- `v`: print session details and exit
|
|
108
|
-
- `
|
|
119
|
+
- `r`: rename the selected session
|
|
109
120
|
- `o`: add or edit a note
|
|
110
121
|
- `a`: archive the selected session
|
|
111
122
|
- `d`: delete the selected session
|
|
112
|
-
- `Tab`: switch focus between
|
|
113
|
-
- `Left`/`Right` or `h`/`l`:
|
|
123
|
+
- `Tab`: switch focus between projects, sessions, and details
|
|
124
|
+
- `Left`/`Right` or `h`/`l`: move between panes
|
|
114
125
|
- `q`, `Esc`, or `Ctrl+C`: quit
|
|
115
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
|
+
|
|
116
162
|
## Environment
|
|
117
163
|
|
|
118
164
|
```sh
|
|
119
165
|
CODEX_HOME # default: ~/.codex
|
|
120
166
|
CODEX_SESSIONS_DIR # default: $CODEX_HOME/sessions
|
|
121
167
|
CODEX_WORKBENCH_META # default: $CODEX_HOME/codex-workbench.json
|
|
168
|
+
CODEX_WORKBENCH_CONFIG # default: $CODEX_HOME/codex-workbench.config.json
|
|
122
169
|
CODEX_BIN # default: codex from shell PATH
|
|
123
170
|
```
|
|
124
171
|
|
|
125
172
|
`CODEX_WORKBENCH_META` stores local workbench metadata such as custom names and notes. Session content remains in the Codex sessions directory.
|
|
126
173
|
|
|
174
|
+
`CODEX_WORKBENCH_CONFIG` points to the optional local configuration file for SSH remote sources.
|
|
175
|
+
|
|
127
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.
|
|
128
177
|
|
|
129
178
|
## Development
|
|
@@ -136,8 +185,12 @@ npm pack --dry-run
|
|
|
136
185
|
Project layout:
|
|
137
186
|
|
|
138
187
|
```text
|
|
139
|
-
bin/codex-workbench
|
|
140
|
-
src/cli.js
|
|
141
|
-
src/
|
|
142
|
-
|
|
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
|
|
143
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
|
+
};
|