@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # codex-workbench
2
2
 
3
- > A keyboard-driven terminal UI 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)
@@ -14,9 +14,11 @@
14
14
 
15
15
  ## What is it?
16
16
 
17
- codex-workbench is an **interactive terminal UI** for your Codex sessions. Instead of digging through `~/.codex/sessions/` by hand, you get a fast, keyboard-driven interface to **browse, search, rename, annotate, fork, archive, and delete** sessions — all without leaving the terminal.
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 Codex sessions across all your servers from a single pane of glass.
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 Codex is reachable, then open the workbench:
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, Codex takes over the terminal. When it exits, the workbench redraws.
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 Codex |
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 `~/.codex/codex-workbench.config.json`:
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, hide, new, resume, fork, archive, and delete are forwarded to the remote `cwb` transparently.
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 and hidden sessions
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 Codex binary discovery
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`, Codex takes over the terminal. When it exits, codex-workbench returns.
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
- | `CODEX_WORKBENCH_META` | `$CODEX_HOME/codex-workbench.json` | Workbench metadata (names, notes) |
171
- | `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` |
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 (PATH, shell, fallback)
275
+ codex-bin.js # legacy Codex binary discovery wrapper
241
276
  config.js # environment-derived path constants
242
277
  model/
243
- 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
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 # spawn Codex processes (new, resume, fork, etc.)
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.14",
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
+ };