@bramblex/codex-workbench 0.1.3 → 0.1.5

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
@@ -1,8 +1,8 @@
1
1
  # codex-workbench
2
2
 
3
- A small terminal workbench for browsing and managing local Codex sessions.
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
 
@@ -40,10 +40,13 @@ codex-workbench list
40
40
  ```sh
41
41
  codex-workbench [ui]
42
42
  codex-workbench doctor
43
- codex-workbench list [--json] [--cwd <dir>] [--all]
43
+ codex-workbench list [--json] [--compact] [--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>
@@ -70,10 +73,20 @@ Use `list` to find sessions:
70
73
  ```sh
71
74
  codex-workbench list
72
75
  codex-workbench list --json
76
+ codex-workbench list --json --compact
73
77
  codex-workbench list --cwd /path/to/project
74
78
  codex-workbench list --all
75
79
  ```
76
80
 
81
+ Use `--compact` with `--json` when another tool only needs session summaries. It omits the full message history and keeps remote SSH listings small.
82
+
83
+ Use `new` to start a fresh Codex session in a project directory:
84
+
85
+ ```sh
86
+ codex-workbench new --cwd /path/to/project
87
+ codex-workbench new --cwd /path/to/project "Summarize this repo"
88
+ ```
89
+
77
90
  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
91
 
79
92
  ```sh
@@ -98,32 +111,71 @@ Most commands accept a full session id, a unique prefix, a saved name, or a sess
98
111
 
99
112
  ## Interactive UI
100
113
 
101
- The UI groups sessions by working directory, with sessions above and details below. When you resume a session, Codex temporarily takes over the terminal; when Codex exits, codex-workbench redraws the UI.
114
+ 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
115
 
103
116
  Common keys:
104
117
 
105
- - `Enter` or `r`: resume the selected session in Codex
118
+ - `Enter`: resume the selected session in Codex
119
+ - `n`: create a new project/session from Projects, or a new session from Sessions/Details
106
120
  - `f`: fork the selected session
107
121
  - `v`: print session details and exit
108
- - `n`: rename the selected session
122
+ - `r`: rename the selected session
109
123
  - `o`: add or edit a note
110
124
  - `a`: archive the selected session
111
125
  - `d`: delete the selected session
112
- - `Tab`: switch focus between the session list and details pane
113
- - `Left`/`Right` or `h`/`l`: switch project group
126
+ - `Tab`: switch focus between projects, sessions, and details
127
+ - `Left`/`Right` or `h`/`l`: move between panes
114
128
  - `q`, `Esc`, or `Ctrl+C`: quit
115
129
 
130
+ 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.
131
+
132
+ ## Remote Servers
133
+
134
+ 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.
135
+
136
+ Create `~/.codex/codex-workbench.config.json`:
137
+
138
+ ```json
139
+ {
140
+ "servers": [
141
+ {
142
+ "id": "a",
143
+ "label": "A server",
144
+ "target": "user@example.com"
145
+ },
146
+ {
147
+ "id": "b",
148
+ "label": "B server",
149
+ "target": "b-host",
150
+ "command": "/usr/local/bin/cwb",
151
+ "sshArgs": ["-p", "2222"]
152
+ }
153
+ ]
154
+ }
155
+ ```
156
+
157
+ 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`.
158
+
159
+ You can verify a remote source directly with:
160
+
161
+ ```sh
162
+ ssh user@example.com 'cwb list --json'
163
+ ```
164
+
116
165
  ## Environment
117
166
 
118
167
  ```sh
119
168
  CODEX_HOME # default: ~/.codex
120
169
  CODEX_SESSIONS_DIR # default: $CODEX_HOME/sessions
121
170
  CODEX_WORKBENCH_META # default: $CODEX_HOME/codex-workbench.json
171
+ CODEX_WORKBENCH_CONFIG # default: $CODEX_HOME/codex-workbench.config.json
122
172
  CODEX_BIN # default: codex from shell PATH
123
173
  ```
124
174
 
125
175
  `CODEX_WORKBENCH_META` stores local workbench metadata such as custom names and notes. Session content remains in the Codex sessions directory.
126
176
 
177
+ `CODEX_WORKBENCH_CONFIG` points to the optional local configuration file for SSH remote sources.
178
+
127
179
  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
180
 
129
181
  ## Development
@@ -136,8 +188,12 @@ npm pack --dry-run
136
188
  Project layout:
137
189
 
138
190
  ```text
139
- bin/codex-workbench # executable entrypoint
140
- src/cli.js # main CLI and UI implementation
141
- src/codex-bin.js # Codex executable discovery
142
- test/smoke.js # smoke tests
191
+ bin/codex-workbench # executable entrypoint
192
+ src/cli.js # thin CLI router
193
+ src/cli-output.js # terminal output presenters
194
+ src/codex-bin.js # Codex executable discovery
195
+ src/model/ # session parsing, metadata, config, and format helpers
196
+ src/services/ # Codex process runners and session source adapters
197
+ src/ui/ # interactive UI and components
198
+ test/ # smoke and service tests
143
199
  ```
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
- require('../src/cli');
4
+ require('../src/cli').run();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bramblex/codex-workbench",
3
- "version": "0.1.3",
4
- "description": "Terminal workbench for browsing and managing local Codex sessions.",
3
+ "version": "0.1.5",
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 src/cli.js && node --check src/codex-bin.js && node test/codex-bin.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/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,116 @@
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] [--compact] [--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
+ const payload = opts.compact ? filtered.map(compactSession) : filtered;
45
+ console.log(JSON.stringify(payload, null, 2));
46
+ return;
47
+ }
48
+ const groups = new Map();
49
+ for (const session of filtered) {
50
+ const source = session.sourceLabel || '';
51
+ const key = `${source}\0${session.cwd}`;
52
+ if (!groups.has(key)) groups.set(key, { source, cwd: session.cwd, sessions: [] });
53
+ groups.get(key).sessions.push(session);
54
+ }
55
+ for (const group of groups.values()) {
56
+ console.log(`\n${group.source ? `${group.source}: ` : ''}${group.cwd}`);
57
+ for (const session of group.sessions) {
58
+ const label = session.name || truncate(session.first || session.last || '(no prompt)', 56);
59
+ const flags = [session.archived ? 'archived' : '', session.hidden ? 'hidden' : '', session.note ? 'note' : ''].filter(Boolean).join(',');
60
+ console.log(` ${shortId(session.id)} ${localTime(session.updatedAt)} ${String(session.turns).padStart(2)} turns ${flags ? `[${flags}] ` : ''}${label}`);
61
+ }
62
+ }
63
+ if (!filtered.length) console.log('No sessions found.');
64
+ }
65
+
66
+ function compactSession(session) {
67
+ const { messages, ...compact } = session;
68
+ return compact;
69
+ }
70
+
71
+ function printShow(session) {
72
+ console.log(`${session.name || '(unnamed)'} ${session.archived ? '[archived]' : ''}${session.hidden ? '[hidden]' : ''}`);
73
+ console.log(`id: ${session.id}`);
74
+ if (session.sourceLabel) console.log(`source: ${session.sourceLabel}`);
75
+ console.log(`cwd: ${session.cwd}`);
76
+ console.log(`started: ${localTime(session.startedAt)}`);
77
+ console.log(`updated: ${localTime(session.updatedAt)}`);
78
+ if (session.file) console.log(`file: ${session.file}`);
79
+ console.log(`turns: ${session.turns}`);
80
+ if (session.note) console.log(`note: ${session.note}`);
81
+ console.log('\nMessages:');
82
+ for (const msg of session.messages || []) {
83
+ if (msg.role === 'developer') continue;
84
+ const prefix = msg.role === 'assistant' ? 'A' : msg.role === 'user' ? 'U' : msg.role.slice(0, 1).toUpperCase();
85
+ console.log(` ${prefix}: ${truncate(msg.text, 180)}`);
86
+ }
87
+ }
88
+
89
+ function printDoctor() {
90
+ const result = inspectCodexBin();
91
+ console.log('codex-workbench doctor');
92
+ console.log(`status: ${result.ok ? 'ok' : 'error'}`);
93
+ if (result.path) console.log(`codex: ${result.path}`);
94
+ if (result.source) console.log(`source: ${result.source}`);
95
+ if (result.error) console.log(`error: ${result.error}`);
96
+ console.log('\nChecks:');
97
+ for (const check of result.checks) {
98
+ const parts = [
99
+ check.source,
100
+ check.mode ? `mode=${check.mode}` : '',
101
+ check.shell ? `shell=${check.shell}` : '',
102
+ check.path ? `path=${check.path}` : '',
103
+ `executable=${check.executable ? 'yes' : 'no'}`,
104
+ ].filter(Boolean);
105
+ console.log(` - ${parts.join(' ')}`);
106
+ }
107
+ if (!result.ok) process.exitCode = 1;
108
+ }
109
+
110
+ module.exports = {
111
+ compactSession,
112
+ printDoctor,
113
+ printList,
114
+ printShow,
115
+ usage,
116
+ };