@bramblex/codex-workbench 0.1.13 → 0.1.15
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 +101 -108
- package/package.json +2 -2
- package/src/cli-output.js +38 -26
- package/src/cli.js +18 -3
- package/src/config.js +44 -4
- package/src/model/metadata.js +44 -0
- package/src/model/session-store.js +39 -116
- package/src/model/workbench-config.js +48 -12
- package/src/providers/codex.js +267 -0
- package/src/providers/index.js +59 -0
- package/src/providers/pi.js +326 -0
- package/src/services/codex-runner.js +27 -62
- package/src/services/session-sources.js +52 -8
- package/src/ui/workbench.js +84 -18
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# codex-workbench
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> A keyboard-driven terminal UI for browsing, organizing, and resuming coding-agent sessions — locally and across SSH remotes.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@bramblex/codex-workbench)
|
|
6
6
|
[](LICENSE)
|
|
@@ -8,25 +8,19 @@
|
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+

|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
---
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
## What is it?
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
codex-workbench is an **interactive terminal UI** for coding-agent sessions. Instead of digging through backend-specific session directories by hand, you get a fast, keyboard-driven interface to **browse, search, rename, annotate, fork, archive, and delete** sessions — all without leaving the terminal.
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
It also connects to **remote machines over SSH**, so you can manage sessions across all your servers from a single pane of glass.
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
Built-in backends currently include [Codex](https://github.com/openai/codex) and pi. The backend layer is intentionally provider-based so additional agents can be added without changing the TUI workflow.
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
- **Remote SSH sources** — browse and manage sessions on distant machines with zero remote dependencies beyond `codex-workbench` itself
|
|
25
|
-
- **Session metadata** — assign custom names and notes, hide stale sessions without deleting them
|
|
26
|
-
- **One-key actions** — resume, fork, archive, or delete sessions from the keyboard
|
|
27
|
-
- **Directory picker** — navigate the filesystem to start new sessions in any project
|
|
28
|
-
- **JSON output** — pipe `list --json` into `jq` or other tools
|
|
29
|
-
- **Short aliases** — installed as both `codex-workbench` and `cwb`
|
|
23
|
+
A handful of CLI subcommands are available for scripting, but the TUI is the product.
|
|
30
24
|
|
|
31
25
|
---
|
|
32
26
|
|
|
@@ -36,106 +30,35 @@ Run it without arguments to open the interactive TUI, or use the CLI subcommands
|
|
|
36
30
|
npm install -g @bramblex/codex-workbench
|
|
37
31
|
```
|
|
38
32
|
|
|
39
|
-
|
|
33
|
+
Verify your backends are reachable, then open the workbench:
|
|
40
34
|
|
|
41
35
|
```bash
|
|
42
36
|
codex-workbench doctor
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
Then open the workbench:
|
|
46
|
-
|
|
47
|
-
```bash
|
|
48
|
-
codex-workbench
|
|
49
|
-
# or just:
|
|
50
37
|
cwb
|
|
51
38
|
```
|
|
52
39
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
## CLI commands
|
|
56
|
-
|
|
57
|
-
### Browse sessions
|
|
58
|
-
|
|
59
|
-
```bash
|
|
60
|
-
codex-workbench list # human-readable, grouped by source + project
|
|
61
|
-
codex-workbench list --json # machine-readable full output
|
|
62
|
-
codex-workbench list --json --compact # omit message history (faster for scripting)
|
|
63
|
-
codex-workbench list --cwd ~/projects/foo # filter to one working directory
|
|
64
|
-
codex-workbench list --all # include archived and hidden sessions
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
### Inspect a session
|
|
68
|
-
|
|
69
|
-
```bash
|
|
70
|
-
codex-workbench show <session>
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
`<session>` can be a full session id, a unique prefix, a saved custom name, or a session filename.
|
|
74
|
-
|
|
75
|
-
### Manage sessions
|
|
76
|
-
|
|
77
|
-
```bash
|
|
78
|
-
codex-workbench rename <session> "fix the auth bug"
|
|
79
|
-
codex-workbench note <session> "investigated JWT expiry, seems to be clock skew"
|
|
80
|
-
codex-workbench archive <session>
|
|
81
|
-
codex-workbench unarchive <session>
|
|
82
|
-
codex-workbench hide <session> # remove from default list but keep on disk
|
|
83
|
-
codex-workbench unhide <session>
|
|
84
|
-
codex-workbench fork <session>
|
|
85
|
-
codex-workbench delete <session> --force
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
### Start and resume
|
|
89
|
-
|
|
90
|
-
```bash
|
|
91
|
-
codex-workbench new --cwd ~/projects/foo "Summarize this repo"
|
|
92
|
-
codex-workbench resume <session> "what was the conclusion about the rate limiter?"
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
When you run `new` or `resume`, Codex takes over the terminal. When it exits, codex-workbench returns.
|
|
96
|
-
|
|
97
|
-
### Directories
|
|
98
|
-
|
|
99
|
-
```bash
|
|
100
|
-
codex-workbench dirs --cwd ~/projects
|
|
101
|
-
codex-workbench dirs --json
|
|
102
|
-
codex-workbench mkdir ~/projects my-new-feature
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
### Diagnostics
|
|
106
|
-
|
|
107
|
-
```bash
|
|
108
|
-
codex-workbench doctor
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
### Force-delete a broken session file
|
|
112
|
-
|
|
113
|
-
```bash
|
|
114
|
-
codex-workbench delete <session> --file
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
Only use `--file` when Codex itself cannot remove the session. It deletes the JSONL file directly without going through the Codex CLI.
|
|
40
|
+
That's it. `cwb` with no arguments opens the TUI.
|
|
118
41
|
|
|
119
42
|
---
|
|
120
43
|
|
|
121
44
|
## Interactive TUI
|
|
122
45
|
|
|
123
|
-
|
|
46
|
+
The TUI has three panes: **sources & projects** on the left, **sessions** on the upper right, and **session details** below. Local sessions load instantly; remote SSH sources stream in asynchronously.
|
|
124
47
|
|
|
125
|
-
|
|
48
|
+
When you resume or start a session, the selected backend takes over the terminal. When it exits, the workbench redraws.
|
|
126
49
|
|
|
127
50
|
### Keyboard shortcuts
|
|
128
51
|
|
|
129
52
|
| Key | Action |
|
|
130
53
|
|-----|--------|
|
|
131
|
-
| `Enter` | Resume selected session in
|
|
54
|
+
| `Enter` | Resume selected session in its backend |
|
|
132
55
|
| `Tab` / `S-Tab` | Switch focus between panes |
|
|
133
56
|
| `←` `→` / `h` `l` | Move between sources, sessions, and details |
|
|
134
57
|
| `↑` `↓` / `j` `k` | Move selection up/down |
|
|
135
58
|
| `0` | Show all sources |
|
|
136
59
|
| `1`–`9` | Jump to source |
|
|
137
60
|
| `[` `]` | Previous / next source |
|
|
138
|
-
| `n` | New session (
|
|
61
|
+
| `n` | New session (opens directory picker) |
|
|
139
62
|
| `f` | Fork selected session |
|
|
140
63
|
| `r` | Rename selected session |
|
|
141
64
|
| `o` | Add or edit note |
|
|
@@ -146,8 +69,6 @@ Run `cwb` with no arguments to open the TUI:
|
|
|
146
69
|
|
|
147
70
|
### Directory picker
|
|
148
71
|
|
|
149
|
-
When creating a new session, the directory picker opens:
|
|
150
|
-
|
|
151
72
|
| Key | Action |
|
|
152
73
|
|-----|--------|
|
|
153
74
|
| `↑` `↓` / `j` `k` | Move selection |
|
|
@@ -161,11 +82,11 @@ When creating a new session, the directory picker opens:
|
|
|
161
82
|
|
|
162
83
|
## Remote SSH sources
|
|
163
84
|
|
|
164
|
-
codex-workbench can show sessions from remote machines by running `cwb` over SSH.
|
|
85
|
+
codex-workbench can show sessions from remote machines by running `cwb` over SSH. Remote sources appear alongside `Local` in the TUI and load asynchronously.
|
|
165
86
|
|
|
166
87
|
### Requirements
|
|
167
88
|
|
|
168
|
-
The remote
|
|
89
|
+
The remote must have `codex-workbench` installed and `cwb` available in the **non-interactive SSH PATH**. Test it:
|
|
169
90
|
|
|
170
91
|
```bash
|
|
171
92
|
ssh user@host 'cwb list --json'
|
|
@@ -173,19 +94,19 @@ ssh user@host 'cwb list --json'
|
|
|
173
94
|
|
|
174
95
|
### Configuration
|
|
175
96
|
|
|
176
|
-
Create `~/.
|
|
97
|
+
Create `~/.cwb/config.json`:
|
|
177
98
|
|
|
178
99
|
```json
|
|
179
100
|
{
|
|
180
101
|
"servers": [
|
|
181
102
|
{
|
|
182
103
|
"id": "devbox",
|
|
183
|
-
"label": "Dev
|
|
104
|
+
"label": "SSH · Dev Box",
|
|
184
105
|
"target": "user@dev.example.com"
|
|
185
106
|
},
|
|
186
107
|
{
|
|
187
108
|
"id": "gpu",
|
|
188
|
-
"label": "GPU
|
|
109
|
+
"label": "SSH · GPU Server",
|
|
189
110
|
"target": "gpu-host",
|
|
190
111
|
"command": "/usr/local/bin/cwb",
|
|
191
112
|
"sshArgs": ["-p", "2222"]
|
|
@@ -197,12 +118,63 @@ Create `~/.codex/codex-workbench.config.json`:
|
|
|
197
118
|
| Field | Required | Description |
|
|
198
119
|
|-------|----------|-------------|
|
|
199
120
|
| `target` | Yes | SSH destination (`user@host` or hostname) |
|
|
121
|
+
| `label` | No | Display name in the TUI |
|
|
200
122
|
| `id` | No | Short identifier (defaults to sanitized target) |
|
|
201
|
-
| `label` | No | Display name in the UI |
|
|
202
123
|
| `command` | No | Path to `cwb` on the remote (default: `cwb`) |
|
|
203
124
|
| `sshArgs` | No | Extra SSH flags, e.g. `["-p", "2222"]` |
|
|
204
125
|
|
|
205
|
-
|
|
126
|
+
Operations like rename, note, new, resume, fork, archive, and delete are forwarded to the remote `cwb` transparently.
|
|
127
|
+
|
|
128
|
+
Remote backends are supported as long as the remote `cwb` can read them.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Backends
|
|
133
|
+
|
|
134
|
+
codex-workbench auto-detects installed backends by checking their session directories.
|
|
135
|
+
|
|
136
|
+
| Backend | Sessions | Binary override | Notes |
|
|
137
|
+
|---------|----------|-----------------|-------|
|
|
138
|
+
| `codex` | `$CODEX_SESSIONS_DIR` or `~/.codex/sessions` | `CODEX_BIN` | Uses the Codex CLI for new, resume, fork, archive, unarchive, and delete. |
|
|
139
|
+
| `pi` | `$PI_CODING_AGENT_SESSION_DIR` or `$PI_CODING_AGENT_DIR/sessions` | `PI_BIN` | Uses the pi CLI for new, resume, and fork. Archive/unarchive use workbench metadata; delete removes the session file. |
|
|
140
|
+
|
|
141
|
+
Session metadata such as custom names, notes, and archive state is stored in workbench's own metadata file, not inside backend session files.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## CLI commands
|
|
146
|
+
|
|
147
|
+
The TUI is the primary interface, but every action is also available as a CLI subcommand for scripting and automation.
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
cwb list # human-readable, grouped by source + project
|
|
151
|
+
cwb list --json --compact # machine-readable, omits message history
|
|
152
|
+
cwb list --cwd ~/projects/foo # filter to one working directory
|
|
153
|
+
cwb list --all # include archived sessions
|
|
154
|
+
cwb backends --json # list detected local backends
|
|
155
|
+
|
|
156
|
+
cwb show <session> # full session details
|
|
157
|
+
cwb rename <session> "fix auth" # give a session a memorable name
|
|
158
|
+
cwb note <session> "clock skew" # attach a persistent note
|
|
159
|
+
cwb archive <session> # archive without deleting
|
|
160
|
+
cwb unarchive <session>
|
|
161
|
+
cwb fork <session>
|
|
162
|
+
cwb delete <session> --force
|
|
163
|
+
|
|
164
|
+
cwb new --cwd ~/projects/foo --backend codex "Summarize this repo"
|
|
165
|
+
cwb new --cwd ~/projects/foo --backend pi "Summarize this repo"
|
|
166
|
+
cwb resume <session> "what was the conclusion about the rate limiter?"
|
|
167
|
+
|
|
168
|
+
cwb dirs --cwd ~/projects
|
|
169
|
+
cwb mkdir ~/projects my-new-feature
|
|
170
|
+
|
|
171
|
+
cwb doctor # check available backends and binaries
|
|
172
|
+
cwb delete <session> --file # force-delete broken session file
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
`<session>` can be a full session id, a unique prefix, a saved custom name, or a session filename.
|
|
176
|
+
|
|
177
|
+
When you run `new` or `resume`, the selected backend takes over the terminal. When it exits, codex-workbench returns. In the TUI, `n` scans the current source for available backends; local sources are scanned directly and remote sources are scanned with `cwb backends --json` over SSH.
|
|
206
178
|
|
|
207
179
|
---
|
|
208
180
|
|
|
@@ -210,14 +182,24 @@ Remote sources appear alongside `Local` in the TUI and load asynchronously in th
|
|
|
210
182
|
|
|
211
183
|
| Variable | Default | Description |
|
|
212
184
|
|----------|---------|-------------|
|
|
185
|
+
| `CWB_HOME` | `~/.cwb` | codex-workbench data directory |
|
|
186
|
+
| `CWB_META` | `$CWB_HOME/metadata.json` | Workbench metadata (names, notes, archive state) |
|
|
187
|
+
| `CWB_CONFIG` | `$CWB_HOME/config.json` | SSH remote sources config |
|
|
213
188
|
| `CODEX_HOME` | `~/.codex` | Codex data directory |
|
|
214
189
|
| `CODEX_SESSIONS_DIR` | `$CODEX_HOME/sessions` | Session JSONL files |
|
|
215
|
-
| `
|
|
216
|
-
| `
|
|
190
|
+
| `PI_CODING_AGENT_DIR` | `~/.pi/agent` | pi coding agent data directory |
|
|
191
|
+
| `PI_CODING_AGENT_SESSION_DIR` | `$PI_CODING_AGENT_DIR/sessions` | pi session JSONL files |
|
|
192
|
+
| `CODEX_WORKBENCH_META` | unset | Legacy override for `CWB_META` |
|
|
193
|
+
| `CODEX_WORKBENCH_CONFIG` | unset | Legacy override for `CWB_CONFIG` |
|
|
217
194
|
| `CODEX_BIN` | auto-detected | Force a specific Codex executable |
|
|
195
|
+
| `PI_BIN` | auto-detected | Force a specific pi executable |
|
|
218
196
|
|
|
219
197
|
By default, codex-workbench discovers the `codex` binary through your login shell's `PATH`. Set `CODEX_BIN` to override.
|
|
220
198
|
|
|
199
|
+
`cwb doctor` reports every backend it can see.
|
|
200
|
+
|
|
201
|
+
On first run, workbench moves legacy config files from `~/.codex/` into `~/.cwb/` if the new files do not exist yet.
|
|
202
|
+
|
|
221
203
|
---
|
|
222
204
|
|
|
223
205
|
## Troubleshooting
|
|
@@ -230,10 +212,18 @@ Run `codex-workbench doctor` to see where codex-workbench is looking. Common fix
|
|
|
230
212
|
- Set `CODEX_BIN=/path/to/codex` to point directly at the executable
|
|
231
213
|
- Make sure your shell profile (`~/.zshrc`, `~/.bashrc`) adds Codex to `PATH`
|
|
232
214
|
|
|
233
|
-
### No sessions appear
|
|
215
|
+
### No Codex sessions appear
|
|
234
216
|
|
|
235
217
|
Make sure you've run Codex at least once. Sessions are stored as `.jsonl` files under `$CODEX_SESSIONS_DIR`. Run `ls ~/.codex/sessions/` to verify.
|
|
236
218
|
|
|
219
|
+
### No pi sessions appear
|
|
220
|
+
|
|
221
|
+
Make sure you've run the pi coding agent at least once. Sessions are stored as `.jsonl` files under `$PI_CODING_AGENT_SESSION_DIR` or `$PI_CODING_AGENT_DIR/sessions`. Run `ls ~/.pi/agent/sessions/` to verify.
|
|
222
|
+
|
|
223
|
+
### A backend is missing from doctor
|
|
224
|
+
|
|
225
|
+
Backends appear only when their session directory exists. For a new backend integration, add a provider under `src/providers/` with session discovery, parsing, binary discovery, and command routing.
|
|
226
|
+
|
|
237
227
|
### Remote source shows an error
|
|
238
228
|
|
|
239
229
|
Verify the remote is reachable and has `cwb` in its non-interactive PATH:
|
|
@@ -282,15 +272,20 @@ bin/codex-workbench # executable entry point
|
|
|
282
272
|
src/
|
|
283
273
|
cli.js # CLI argument parsing and command dispatch
|
|
284
274
|
cli-output.js # terminal output formatters
|
|
285
|
-
codex-bin.js # Codex binary discovery
|
|
275
|
+
codex-bin.js # legacy Codex binary discovery wrapper
|
|
286
276
|
config.js # environment-derived path constants
|
|
287
277
|
model/
|
|
288
|
-
|
|
278
|
+
metadata.js # workbench-owned metadata persistence
|
|
279
|
+
session-store.js # provider session aggregation and metadata merge
|
|
289
280
|
format.js # id/time/text formatting helpers
|
|
290
281
|
directories.js # filesystem directory listing and creation
|
|
291
282
|
workbench-config.js # SSH remote source config loader
|
|
283
|
+
providers/
|
|
284
|
+
codex.js # Codex provider
|
|
285
|
+
pi.js # pi provider
|
|
286
|
+
index.js # provider registry
|
|
292
287
|
services/
|
|
293
|
-
codex-runner.js #
|
|
288
|
+
codex-runner.js # backward-compatible provider runner wrapper
|
|
294
289
|
session-sources.js # aggregates local + remote session lists
|
|
295
290
|
ssh-runner.js # runs cwb commands over SSH (sync + async)
|
|
296
291
|
ui/
|
|
@@ -314,8 +309,6 @@ scripts/
|
|
|
314
309
|
npm test
|
|
315
310
|
```
|
|
316
311
|
|
|
317
|
-
This runs syntax checks on all source files and executes the test suite.
|
|
318
|
-
|
|
319
312
|
### Publishing
|
|
320
313
|
|
|
321
314
|
```bash
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bramblex/codex-workbench",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"description": "Terminal workbench for browsing and managing local and SSH Codex sessions.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"LICENSE"
|
|
34
34
|
],
|
|
35
35
|
"scripts": {
|
|
36
|
-
"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
|
+
"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/config-paths.test.js && node test/session-sources.test.js && node test/workbench-config.test.js && node test/smoke.js",
|
|
37
37
|
"pty:codex": "node scripts/pty-codex.js",
|
|
38
38
|
"tui:codex": "node scripts/tui-pty-codex.js",
|
|
39
39
|
"xterm:codex": "node scripts/blessed-xterm-codex.js"
|
package/src/cli-output.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const path = require('path');
|
|
4
|
-
const
|
|
4
|
+
const codex = require('./providers/codex');
|
|
5
|
+
const { getAvailableProviders } = require('./providers');
|
|
5
6
|
const { localTime, shortId, truncate } = require('./model/format');
|
|
6
7
|
|
|
7
8
|
function usage() {
|
|
@@ -10,33 +11,35 @@ function usage() {
|
|
|
10
11
|
Usage:
|
|
11
12
|
codex-workbench [ui]
|
|
12
13
|
codex-workbench doctor
|
|
14
|
+
codex-workbench backends [--json]
|
|
13
15
|
codex-workbench list [--json] [--compact] [--cwd <dir>] [--all]
|
|
14
16
|
codex-workbench show <session>
|
|
15
17
|
codex-workbench rename <session> <name>
|
|
16
18
|
codex-workbench note <session> <note>
|
|
17
|
-
codex-workbench new [--cwd <dir>] [prompt...]
|
|
19
|
+
codex-workbench new [--cwd <dir>] [--backend <backend>] [prompt...]
|
|
18
20
|
codex-workbench dirs [--cwd <dir>] [--json]
|
|
19
21
|
codex-workbench mkdir [--cwd <dir>] <name> [--json]
|
|
20
22
|
codex-workbench resume <session> [prompt...]
|
|
21
23
|
codex-workbench fork <session>
|
|
22
24
|
codex-workbench archive <session>
|
|
23
25
|
codex-workbench unarchive <session>
|
|
24
|
-
codex-workbench hide <session>
|
|
25
|
-
codex-workbench unhide <session>
|
|
26
26
|
codex-workbench delete <session> [--force] [--file]
|
|
27
27
|
|
|
28
28
|
Environment:
|
|
29
|
+
CWB_HOME default: ~/.cwb
|
|
30
|
+
CWB_META default: $CWB_HOME/metadata.json
|
|
31
|
+
CWB_CONFIG default: $CWB_HOME/config.json
|
|
29
32
|
CODEX_HOME default: ~/.codex
|
|
30
33
|
CODEX_SESSIONS_DIR default: $CODEX_HOME/sessions
|
|
31
|
-
CODEX_WORKBENCH_META
|
|
32
|
-
CODEX_WORKBENCH_CONFIG
|
|
34
|
+
CODEX_WORKBENCH_META legacy override for CWB_META
|
|
35
|
+
CODEX_WORKBENCH_CONFIG legacy override for CWB_CONFIG
|
|
33
36
|
CODEX_BIN default: codex from shell PATH
|
|
34
37
|
`);
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
function printList(sessions, opts = {}) {
|
|
38
41
|
const filtered = sessions.filter((session) => {
|
|
39
|
-
if (!opts.all &&
|
|
42
|
+
if (!opts.all && session.archived) return false;
|
|
40
43
|
if (opts.cwd) return path.resolve(session.cwd) === path.resolve(opts.cwd);
|
|
41
44
|
return true;
|
|
42
45
|
});
|
|
@@ -55,8 +58,8 @@ function printList(sessions, opts = {}) {
|
|
|
55
58
|
for (const group of groups.values()) {
|
|
56
59
|
console.log(`\n${group.source ? `${group.source}: ` : ''}${group.cwd}`);
|
|
57
60
|
for (const session of group.sessions) {
|
|
58
|
-
const label = session.name || truncate(session.first || session.last || '(no prompt)',
|
|
59
|
-
const flags = [session.
|
|
61
|
+
const label = session.name || truncate(session.first || session.last || '(no prompt)', 52);
|
|
62
|
+
const flags = [session.backend || '', session.archived ? 'archived' : '', session.note ? 'note' : ''].filter(Boolean).join(',');
|
|
60
63
|
console.log(` ${shortId(session.id)} ${localTime(session.updatedAt)} ${String(session.turns).padStart(2)} turns ${flags ? `[${flags}] ` : ''}${label}`);
|
|
61
64
|
}
|
|
62
65
|
}
|
|
@@ -69,8 +72,9 @@ function compactSession(session) {
|
|
|
69
72
|
}
|
|
70
73
|
|
|
71
74
|
function printShow(session) {
|
|
72
|
-
console.log(`${session.name || '(unnamed)'} ${session.archived ? '[archived]' : ''}
|
|
75
|
+
console.log(`${session.name || '(unnamed)'} ${session.archived ? '[archived]' : ''}`);
|
|
73
76
|
console.log(`id: ${session.id}`);
|
|
77
|
+
console.log(`backend: ${session.backend || 'unknown'}`);
|
|
74
78
|
if (session.sourceLabel) console.log(`source: ${session.sourceLabel}`);
|
|
75
79
|
console.log(`cwd: ${session.cwd}`);
|
|
76
80
|
console.log(`started: ${localTime(session.startedAt)}`);
|
|
@@ -87,24 +91,32 @@ function printShow(session) {
|
|
|
87
91
|
}
|
|
88
92
|
|
|
89
93
|
function printDoctor() {
|
|
90
|
-
const
|
|
94
|
+
const providers = getAvailableProviders();
|
|
95
|
+
|
|
91
96
|
console.log('codex-workbench doctor');
|
|
92
|
-
console.log(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
97
|
+
console.log(`\nBackends detected: ${providers.length ? providers.map((p) => p.label).join(', ') : 'none'}`);
|
|
98
|
+
|
|
99
|
+
for (const provider of providers) {
|
|
100
|
+
console.log(`\n-- ${provider.label} --`);
|
|
101
|
+
const bin = provider.resolveBin();
|
|
102
|
+
if (bin) {
|
|
103
|
+
console.log(` binary: ${bin}`);
|
|
104
|
+
} else {
|
|
105
|
+
console.log(` binary: not found`);
|
|
106
|
+
try { provider.resolveBin(); } catch (err) { console.log(` error: ${err.message}`); }
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
const files = provider.getSessionFiles();
|
|
110
|
+
console.log(` sessions: ${files.length} file${files.length === 1 ? '' : 's'}`);
|
|
111
|
+
} catch (err) {
|
|
112
|
+
console.log(` sessions: error - ${err.message}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (!providers.length) {
|
|
117
|
+
console.log('\nNo backends available. Install Codex CLI or pi coding agent.');
|
|
118
|
+
process.exitCode = 1;
|
|
106
119
|
}
|
|
107
|
-
if (!result.ok) process.exitCode = 1;
|
|
108
120
|
}
|
|
109
121
|
|
|
110
122
|
module.exports = {
|
package/src/cli.js
CHANGED
|
@@ -18,6 +18,9 @@ const {
|
|
|
18
18
|
runNewCodexSession,
|
|
19
19
|
usableCwd,
|
|
20
20
|
} = require('./services/codex-runner');
|
|
21
|
+
const { defaultBackend } = require('./services/session-sources');
|
|
22
|
+
const { listLocalBackends } = require('./services/session-sources');
|
|
23
|
+
const { getProvider } = require('./providers');
|
|
21
24
|
const { runWorkbench } = require('./ui/workbench');
|
|
22
25
|
const { createChildDirectory, listDirectories } = require('./model/directories');
|
|
23
26
|
|
|
@@ -34,6 +37,10 @@ function parseFlags(args) {
|
|
|
34
37
|
if (i + 1 >= args.length) throw new Error('--cwd requires a directory.');
|
|
35
38
|
out.cwd = args[++i];
|
|
36
39
|
}
|
|
40
|
+
else if (arg === '--backend') {
|
|
41
|
+
if (i + 1 >= args.length) throw new Error('--backend requires a backend id.');
|
|
42
|
+
out.backend = args[++i];
|
|
43
|
+
}
|
|
37
44
|
else out._.push(arg);
|
|
38
45
|
}
|
|
39
46
|
return out;
|
|
@@ -45,6 +52,12 @@ async function main() {
|
|
|
45
52
|
|
|
46
53
|
const flags = parseFlags(rest);
|
|
47
54
|
if (cmd === 'doctor') return printDoctor();
|
|
55
|
+
if (cmd === 'backends') {
|
|
56
|
+
const backends = listLocalBackends();
|
|
57
|
+
if (flags.json) console.log(JSON.stringify(backends, null, 2));
|
|
58
|
+
else backends.forEach((backend) => console.log(`${backend.id}\t${backend.label}`));
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
48
61
|
if (cmd === 'dirs') {
|
|
49
62
|
const payload = listDirectories(flags.cwd || process.cwd(), usableCwd);
|
|
50
63
|
if (flags.json) console.log(JSON.stringify(payload, null, 2));
|
|
@@ -68,13 +81,15 @@ async function main() {
|
|
|
68
81
|
if (cmd === 'show') return printShow(resolveSession(flags._[0], sessions));
|
|
69
82
|
if (cmd === 'rename') return updateMetadata(resolveSession(flags._[0], sessions), { name: flags._.slice(1).join(' ') });
|
|
70
83
|
if (cmd === 'note') return updateMetadata(resolveSession(flags._[0], sessions), { note: flags._.slice(1).join(' ') });
|
|
71
|
-
if (cmd === 'new' || cmd === 'start')
|
|
84
|
+
if (cmd === 'new' || cmd === 'start') {
|
|
85
|
+
const backend = flags.backend || defaultBackend();
|
|
86
|
+
getProvider(backend);
|
|
87
|
+
return runNewCodexSession(flags.cwd || process.cwd(), flags._, true, backend);
|
|
88
|
+
}
|
|
72
89
|
if (cmd === 'resume') return runCodexCommand('resume', resolveSession(flags._[0], sessions), flags._.slice(1), true);
|
|
73
90
|
if (cmd === 'fork') return runCodexCommand('fork', resolveSession(flags._[0], sessions), [], true);
|
|
74
91
|
if (cmd === 'archive') return runCodexCommand('archive', resolveSession(flags._[0], sessions));
|
|
75
92
|
if (cmd === 'unarchive') return runCodexCommand('unarchive', resolveSession(flags._[0], sessions));
|
|
76
|
-
if (cmd === 'hide') return updateMetadata(resolveSession(flags._[0], sessions), { hidden: true });
|
|
77
|
-
if (cmd === 'unhide') return updateMetadata(resolveSession(flags._[0], sessions), { hidden: false });
|
|
78
93
|
if (cmd === 'delete') {
|
|
79
94
|
const session = resolveSession(flags._[0], sessions);
|
|
80
95
|
if (flags.file) return deleteSessionFile(session);
|
package/src/config.js
CHANGED
|
@@ -1,18 +1,58 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const fs = require('fs');
|
|
3
4
|
const os = require('os');
|
|
4
5
|
const path = require('path');
|
|
5
6
|
|
|
6
7
|
const HOME = os.homedir();
|
|
8
|
+
|
|
9
|
+
// Codex paths (backward compatible env vars)
|
|
7
10
|
const CODEX_HOME = process.env.CODEX_HOME || path.join(HOME, '.codex');
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
const CODEX_SESSIONS_DIR = process.env.CODEX_SESSIONS_DIR || path.join(CODEX_HOME, 'sessions');
|
|
12
|
+
|
|
13
|
+
// Workbench-owned paths. Keep separate from provider-owned directories.
|
|
14
|
+
const CWB_HOME = process.env.CWB_HOME || path.join(HOME, '.cwb');
|
|
15
|
+
|
|
16
|
+
// pi coding agent paths
|
|
17
|
+
const PI_CODING_AGENT_DIR = process.env.PI_CODING_AGENT_DIR || path.join(HOME, '.pi', 'agent');
|
|
18
|
+
const PI_SESSIONS_DIR = process.env.PI_CODING_AGENT_SESSION_DIR || path.join(PI_CODING_AGENT_DIR, 'sessions');
|
|
19
|
+
|
|
20
|
+
function migrateLegacyFile(legacyPath, nextPath) {
|
|
21
|
+
if (!legacyPath || !nextPath || legacyPath === nextPath) return;
|
|
22
|
+
if (fs.existsSync(nextPath) || !fs.existsSync(legacyPath)) return;
|
|
23
|
+
fs.mkdirSync(path.dirname(nextPath), { recursive: true });
|
|
24
|
+
fs.renameSync(legacyPath, nextPath);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const LEGACY_META_PATH = path.join(CODEX_HOME, 'codex-workbench.json');
|
|
28
|
+
const LEGACY_CONFIG_PATH = path.join(CODEX_HOME, 'codex-workbench.config.json');
|
|
29
|
+
|
|
30
|
+
const META_PATH = process.env.CWB_META ||
|
|
31
|
+
process.env.CODEX_WORKBENCH_META ||
|
|
32
|
+
process.env.CSM_META ||
|
|
33
|
+
path.join(CWB_HOME, 'metadata.json');
|
|
34
|
+
|
|
35
|
+
const CONFIG_PATH = process.env.CWB_CONFIG ||
|
|
36
|
+
process.env.CODEX_WORKBENCH_CONFIG ||
|
|
37
|
+
path.join(CWB_HOME, 'config.json');
|
|
38
|
+
|
|
39
|
+
if (!process.env.CWB_META && !process.env.CODEX_WORKBENCH_META && !process.env.CSM_META) {
|
|
40
|
+
migrateLegacyFile(LEGACY_META_PATH, META_PATH);
|
|
41
|
+
}
|
|
42
|
+
if (!process.env.CWB_CONFIG && !process.env.CODEX_WORKBENCH_CONFIG) {
|
|
43
|
+
migrateLegacyFile(LEGACY_CONFIG_PATH, CONFIG_PATH);
|
|
44
|
+
}
|
|
11
45
|
|
|
12
46
|
module.exports = {
|
|
13
47
|
HOME,
|
|
48
|
+
CWB_HOME,
|
|
14
49
|
CODEX_HOME,
|
|
50
|
+
CODEX_SESSIONS_DIR,
|
|
51
|
+
LEGACY_CONFIG_PATH,
|
|
52
|
+
LEGACY_META_PATH,
|
|
53
|
+
PI_CODING_AGENT_DIR,
|
|
54
|
+
PI_SESSIONS_DIR,
|
|
15
55
|
CONFIG_PATH,
|
|
16
|
-
SESSIONS_DIR,
|
|
56
|
+
SESSIONS_DIR: CODEX_SESSIONS_DIR, // backward compat: default sessions dir (legacy)
|
|
17
57
|
META_PATH,
|
|
18
58
|
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { META_PATH } = require('../config');
|
|
6
|
+
|
|
7
|
+
function readJson(file, fallback) {
|
|
8
|
+
try {
|
|
9
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
10
|
+
} catch {
|
|
11
|
+
return fallback;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function writeJson(file, value) {
|
|
16
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
17
|
+
fs.writeFileSync(file, JSON.stringify(value, null, 2) + '\n');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function loadMeta() {
|
|
21
|
+
const data = readJson(META_PATH, { sessions: {} });
|
|
22
|
+
if (!data.sessions) data.sessions = {};
|
|
23
|
+
return data;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function updateMetadata(session, patch) {
|
|
27
|
+
const meta = loadMeta();
|
|
28
|
+
meta.sessions[session.id] = { ...(meta.sessions[session.id] || {}), ...patch };
|
|
29
|
+
meta.updatedAt = new Date().toISOString();
|
|
30
|
+
writeJson(META_PATH, meta);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function removeMetadata(session) {
|
|
34
|
+
const meta = loadMeta();
|
|
35
|
+
delete meta.sessions[session.id];
|
|
36
|
+
meta.updatedAt = new Date().toISOString();
|
|
37
|
+
writeJson(META_PATH, meta);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = {
|
|
41
|
+
loadMeta,
|
|
42
|
+
removeMetadata,
|
|
43
|
+
updateMetadata,
|
|
44
|
+
};
|