@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 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
 
@@ -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 above and details below. When you resume a session, Codex temporarily takes over the terminal; when Codex exits, codex-workbench redraws the UI.
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` or `r`: resume the selected session in Codex
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
- - `n`: rename the selected session
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 the session list and details pane
107
- - `Left`/`Right` or `h`/`l`: switch project group
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 # executable entrypoint
134
- src/cli.js # main CLI and UI implementation
135
- src/codex-bin.js # Codex executable discovery
136
- test/smoke.js # smoke tests
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
  ```
@@ -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.2",
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 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,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
+ };