@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # codex-workbench
2
2
 
3
- > Terminal workbench for browsing, organizing, and resuming [Codex](https://github.com/openai/codex) sessions — locally and across SSH remotes.
3
+ > A keyboard-driven terminal UI for browsing, organizing, and resuming coding-agent sessions — locally and across SSH remotes.
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/@bramblex/codex-workbench)](https://www.npmjs.com/package/@bramblex/codex-workbench)
6
6
  [![license](https://img.shields.io/npm/l/@bramblex/codex-workbench)](LICENSE)
@@ -8,25 +8,19 @@
8
8
 
9
9
  ---
10
10
 
11
- ## What is it?
11
+ ![Screenshot of codex-workbench interactive TUI showing local, staging, and production sessions](assets/screenshot.png)
12
12
 
13
- codex-workbench gives you a fast, keyboard-driven terminal UI over your Codex sessions. It reads session JSONL files from the Codex sessions directory and lets you **inspect, rename, annotate, fork, archive, hide, and delete** sessions without digging through `~/.codex/sessions/` by hand.
13
+ ---
14
14
 
15
- It also aggregates sessions from **remote machines over SSH** — so you can manage Codex sessions across all your servers from one terminal.
15
+ ## What is it?
16
16
 
17
- Run it without arguments to open the interactive TUI, or use the CLI subcommands for scripting and automation.
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
- ## Features
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
- - **Interactive TUI** three-pane layout: sources/projects sessions details
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
- Make sure Codex is available in your shell `PATH`. Verify everything is wired up:
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
- Run `cwb` with no arguments to open the TUI:
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
- ![Screenshot of codex-workbench interactive TUI showing local, staging, and production sessions](assets/screenshot.png)
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 Codex |
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 (picks directory from active project) |
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 machine must have `codex-workbench` installed and the `cwb` command available in the **non-interactive SSH PATH** (not just your interactive shell). Test it:
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 `~/.codex/codex-workbench.config.json`:
97
+ Create `~/.cwb/config.json`:
177
98
 
178
99
  ```json
179
100
  {
180
101
  "servers": [
181
102
  {
182
103
  "id": "devbox",
183
- "label": "Dev box",
104
+ "label": "SSH · Dev Box",
184
105
  "target": "user@dev.example.com"
185
106
  },
186
107
  {
187
108
  "id": "gpu",
188
- "label": "GPU server",
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
- Remote sources appear alongside `Local` in the TUI and load asynchronously in the background. Most operations (rename, note, hide, new, resume, fork, archive, delete) are forwarded to the remote `cwb`.
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
- | `CODEX_WORKBENCH_META` | `$CODEX_HOME/codex-workbench.json` | Workbench metadata (names, notes) |
216
- | `CODEX_WORKBENCH_CONFIG` | `$CODEX_HOME/codex-workbench.config.json` | SSH remote sources config |
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 (PATH, shell, fallback)
275
+ codex-bin.js # legacy Codex binary discovery wrapper
286
276
  config.js # environment-derived path constants
287
277
  model/
288
- session-store.js # session JSONL parsing and metadata persistence
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 # spawn Codex processes (new, resume, fork, etc.)
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.13",
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 { inspectCodexBin } = require('./codex-bin');
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 default: $CODEX_HOME/codex-workbench.json
32
- CODEX_WORKBENCH_CONFIG default: $CODEX_HOME/codex-workbench.config.json
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 && (session.archived || session.hidden)) return false;
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)', 56);
59
- const flags = [session.archived ? 'archived' : '', session.hidden ? 'hidden' : '', session.note ? 'note' : ''].filter(Boolean).join(',');
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]' : ''}${session.hidden ? '[hidden]' : ''}`);
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 result = inspectCodexBin();
94
+ const providers = getAvailableProviders();
95
+
91
96
  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(' ')}`);
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') return runNewCodexSession(flags.cwd || process.cwd(), flags._, true);
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 SESSIONS_DIR = process.env.CODEX_SESSIONS_DIR || path.join(CODEX_HOME, 'sessions');
9
- const META_PATH = process.env.CODEX_WORKBENCH_META || process.env.CSM_META || path.join(CODEX_HOME, 'codex-workbench.json');
10
- const CONFIG_PATH = process.env.CODEX_WORKBENCH_CONFIG || path.join(CODEX_HOME, 'codex-workbench.config.json');
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
+ };