@bramblex/codex-workbench 0.1.14 → 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 +60 -20
- 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
|
-
> A keyboard-driven terminal UI for browsing, organizing, and resuming
|
|
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)
|
|
@@ -14,9 +14,11 @@
|
|
|
14
14
|
|
|
15
15
|
## What is it?
|
|
16
16
|
|
|
17
|
-
codex-workbench is an **interactive terminal UI** for
|
|
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
|
-
It also connects to **remote machines over SSH**, so you can manage
|
|
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
|
+
|
|
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.
|
|
20
22
|
|
|
21
23
|
A handful of CLI subcommands are available for scripting, but the TUI is the product.
|
|
22
24
|
|
|
@@ -28,7 +30,7 @@ A handful of CLI subcommands are available for scripting, but the TUI is the pro
|
|
|
28
30
|
npm install -g @bramblex/codex-workbench
|
|
29
31
|
```
|
|
30
32
|
|
|
31
|
-
Verify
|
|
33
|
+
Verify your backends are reachable, then open the workbench:
|
|
32
34
|
|
|
33
35
|
```bash
|
|
34
36
|
codex-workbench doctor
|
|
@@ -43,13 +45,13 @@ That's it. `cwb` with no arguments opens the TUI.
|
|
|
43
45
|
|
|
44
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.
|
|
45
47
|
|
|
46
|
-
When you resume or start a session,
|
|
48
|
+
When you resume or start a session, the selected backend takes over the terminal. When it exits, the workbench redraws.
|
|
47
49
|
|
|
48
50
|
### Keyboard shortcuts
|
|
49
51
|
|
|
50
52
|
| Key | Action |
|
|
51
53
|
|-----|--------|
|
|
52
|
-
| `Enter` | Resume selected session in
|
|
54
|
+
| `Enter` | Resume selected session in its backend |
|
|
53
55
|
| `Tab` / `S-Tab` | Switch focus between panes |
|
|
54
56
|
| `←` `→` / `h` `l` | Move between sources, sessions, and details |
|
|
55
57
|
| `↑` `↓` / `j` `k` | Move selection up/down |
|
|
@@ -92,7 +94,7 @@ ssh user@host 'cwb list --json'
|
|
|
92
94
|
|
|
93
95
|
### Configuration
|
|
94
96
|
|
|
95
|
-
Create `~/.
|
|
97
|
+
Create `~/.cwb/config.json`:
|
|
96
98
|
|
|
97
99
|
```json
|
|
98
100
|
{
|
|
@@ -121,7 +123,22 @@ Create `~/.codex/codex-workbench.config.json`:
|
|
|
121
123
|
| `command` | No | Path to `cwb` on the remote (default: `cwb`) |
|
|
122
124
|
| `sshArgs` | No | Extra SSH flags, e.g. `["-p", "2222"]` |
|
|
123
125
|
|
|
124
|
-
Operations like rename, note,
|
|
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.
|
|
125
142
|
|
|
126
143
|
---
|
|
127
144
|
|
|
@@ -133,31 +150,31 @@ The TUI is the primary interface, but every action is also available as a CLI su
|
|
|
133
150
|
cwb list # human-readable, grouped by source + project
|
|
134
151
|
cwb list --json --compact # machine-readable, omits message history
|
|
135
152
|
cwb list --cwd ~/projects/foo # filter to one working directory
|
|
136
|
-
cwb list --all # include archived
|
|
153
|
+
cwb list --all # include archived sessions
|
|
154
|
+
cwb backends --json # list detected local backends
|
|
137
155
|
|
|
138
156
|
cwb show <session> # full session details
|
|
139
157
|
cwb rename <session> "fix auth" # give a session a memorable name
|
|
140
158
|
cwb note <session> "clock skew" # attach a persistent note
|
|
141
159
|
cwb archive <session> # archive without deleting
|
|
142
160
|
cwb unarchive <session>
|
|
143
|
-
cwb hide <session> # remove from default list, keep on disk
|
|
144
|
-
cwb unhide <session>
|
|
145
161
|
cwb fork <session>
|
|
146
162
|
cwb delete <session> --force
|
|
147
163
|
|
|
148
|
-
cwb new --cwd ~/projects/foo "Summarize this repo"
|
|
164
|
+
cwb new --cwd ~/projects/foo --backend codex "Summarize this repo"
|
|
165
|
+
cwb new --cwd ~/projects/foo --backend pi "Summarize this repo"
|
|
149
166
|
cwb resume <session> "what was the conclusion about the rate limiter?"
|
|
150
167
|
|
|
151
168
|
cwb dirs --cwd ~/projects
|
|
152
169
|
cwb mkdir ~/projects my-new-feature
|
|
153
170
|
|
|
154
|
-
cwb doctor # check
|
|
171
|
+
cwb doctor # check available backends and binaries
|
|
155
172
|
cwb delete <session> --file # force-delete broken session file
|
|
156
173
|
```
|
|
157
174
|
|
|
158
175
|
`<session>` can be a full session id, a unique prefix, a saved custom name, or a session filename.
|
|
159
176
|
|
|
160
|
-
When you run `new` or `resume`,
|
|
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.
|
|
161
178
|
|
|
162
179
|
---
|
|
163
180
|
|
|
@@ -165,14 +182,24 @@ When you run `new` or `resume`, Codex takes over the terminal. When it exits, co
|
|
|
165
182
|
|
|
166
183
|
| Variable | Default | Description |
|
|
167
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 |
|
|
168
188
|
| `CODEX_HOME` | `~/.codex` | Codex data directory |
|
|
169
189
|
| `CODEX_SESSIONS_DIR` | `$CODEX_HOME/sessions` | Session JSONL files |
|
|
170
|
-
| `
|
|
171
|
-
| `
|
|
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` |
|
|
172
194
|
| `CODEX_BIN` | auto-detected | Force a specific Codex executable |
|
|
195
|
+
| `PI_BIN` | auto-detected | Force a specific pi executable |
|
|
173
196
|
|
|
174
197
|
By default, codex-workbench discovers the `codex` binary through your login shell's `PATH`. Set `CODEX_BIN` to override.
|
|
175
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
|
+
|
|
176
203
|
---
|
|
177
204
|
|
|
178
205
|
## Troubleshooting
|
|
@@ -185,10 +212,18 @@ Run `codex-workbench doctor` to see where codex-workbench is looking. Common fix
|
|
|
185
212
|
- Set `CODEX_BIN=/path/to/codex` to point directly at the executable
|
|
186
213
|
- Make sure your shell profile (`~/.zshrc`, `~/.bashrc`) adds Codex to `PATH`
|
|
187
214
|
|
|
188
|
-
### No sessions appear
|
|
215
|
+
### No Codex sessions appear
|
|
189
216
|
|
|
190
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.
|
|
191
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
|
+
|
|
192
227
|
### Remote source shows an error
|
|
193
228
|
|
|
194
229
|
Verify the remote is reachable and has `cwb` in its non-interactive PATH:
|
|
@@ -237,15 +272,20 @@ bin/codex-workbench # executable entry point
|
|
|
237
272
|
src/
|
|
238
273
|
cli.js # CLI argument parsing and command dispatch
|
|
239
274
|
cli-output.js # terminal output formatters
|
|
240
|
-
codex-bin.js # Codex binary discovery
|
|
275
|
+
codex-bin.js # legacy Codex binary discovery wrapper
|
|
241
276
|
config.js # environment-derived path constants
|
|
242
277
|
model/
|
|
243
|
-
|
|
278
|
+
metadata.js # workbench-owned metadata persistence
|
|
279
|
+
session-store.js # provider session aggregation and metadata merge
|
|
244
280
|
format.js # id/time/text formatting helpers
|
|
245
281
|
directories.js # filesystem directory listing and creation
|
|
246
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
|
|
247
287
|
services/
|
|
248
|
-
codex-runner.js #
|
|
288
|
+
codex-runner.js # backward-compatible provider runner wrapper
|
|
249
289
|
session-sources.js # aggregates local + remote session lists
|
|
250
290
|
ssh-runner.js # runs cwb commands over SSH (sync + async)
|
|
251
291
|
ui/
|
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
|
+
};
|