dev-prism 0.2.0 → 0.4.0

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.
Files changed (63) hide show
  1. package/README.md +37 -11
  2. package/bin/dev-prism.js +23 -235
  3. package/dist/chunk-3ATDGV4Y.js +22 -0
  4. package/dist/chunk-3MSC3CGG.js +78 -0
  5. package/dist/chunk-3NW2OWIU.js +78 -0
  6. package/dist/chunk-3TRRZEFR.js +38 -0
  7. package/dist/chunk-4UNCSJRM.js +70 -0
  8. package/dist/chunk-63II3EL4.js +98 -0
  9. package/dist/chunk-6YMQTISJ.js +84 -0
  10. package/dist/chunk-7YGOMAJG.js +106 -0
  11. package/dist/chunk-AOM6BONB.js +98 -0
  12. package/dist/chunk-AW2FJGXA.js +38 -0
  13. package/dist/chunk-C4WLIOBR.js +67 -0
  14. package/dist/chunk-D6QWWXZD.js +49 -0
  15. package/dist/chunk-FKTFCSU7.js +78 -0
  16. package/dist/chunk-GKXXK2ZH.js +203 -0
  17. package/dist/chunk-H4HPDIY3.js +95 -0
  18. package/dist/chunk-HCCZKLC4.js +64 -0
  19. package/dist/chunk-HZUN6NRB.js +70 -0
  20. package/dist/chunk-J36LRUXM.js +60 -0
  21. package/dist/chunk-JHR4WADC.js +200 -0
  22. package/dist/chunk-JIU574KX.js +41 -0
  23. package/dist/chunk-KZJE62TK.js +203 -0
  24. package/dist/chunk-LNIOSGC4.js +78 -0
  25. package/dist/chunk-LOVO4P3Y.js +41 -0
  26. package/dist/chunk-NNVP5F6I.js +77 -0
  27. package/dist/chunk-OL25TBYX.js +67 -0
  28. package/dist/chunk-P3ETW2KK.js +166 -0
  29. package/dist/chunk-QUMZI5KK.js +98 -0
  30. package/dist/chunk-RC2AUYZ7.js +49 -0
  31. package/dist/chunk-SSQ7XBY2.js +30 -0
  32. package/dist/chunk-SUMJLXT7.js +30 -0
  33. package/dist/chunk-UHI2QJFI.js +200 -0
  34. package/dist/chunk-X5A6H4Q7.js +70 -0
  35. package/dist/chunk-X6FHBEAS.js +200 -0
  36. package/dist/chunk-Y3GR6XK7.js +71 -0
  37. package/dist/commands/claude.js +3 -102
  38. package/dist/commands/create.js +6 -5
  39. package/dist/commands/destroy.js +4 -3
  40. package/dist/commands/info.d.ts +3 -0
  41. package/dist/commands/info.js +7 -0
  42. package/dist/commands/list.d.ts +3 -1
  43. package/dist/commands/list.js +4 -4
  44. package/dist/commands/logs.d.ts +8 -0
  45. package/dist/commands/logs.js +8 -0
  46. package/dist/commands/prune.d.ts +6 -0
  47. package/dist/commands/prune.js +10 -0
  48. package/dist/commands/start.d.ts +7 -0
  49. package/dist/commands/start.js +9 -0
  50. package/dist/commands/stop-all.d.ts +3 -0
  51. package/dist/commands/stop-all.js +9 -0
  52. package/dist/commands/stop.d.ts +3 -0
  53. package/dist/commands/stop.js +7 -0
  54. package/dist/index.d.ts +11 -1
  55. package/dist/index.js +60 -10
  56. package/dist/lib/env.d.ts +2 -1
  57. package/dist/lib/env.js +3 -1
  58. package/dist/lib/ports.js +1 -1
  59. package/dist/lib/store.d.ts +46 -0
  60. package/dist/lib/store.js +6 -0
  61. package/dist/lib/worktree.d.ts +2 -12
  62. package/dist/lib/worktree.js +1 -5
  63. package/package.json +14 -4
package/README.md CHANGED
@@ -17,10 +17,13 @@ All Docker configuration lives in `docker-compose.session.yml` in your project -
17
17
 
18
18
  ## Features
19
19
 
20
- - **Git worktrees** for isolated working directories
20
+ - **Git worktrees** for isolated working directories (or in-place mode)
21
21
  - **Docker Compose** handles all container orchestration
22
- - **Unique ports** per session (calculated from session ID)
22
+ - **Unique ports** per session (calculated from session ID, displayed as clickable URLs)
23
+ - **Auto-assign** session IDs or choose your own
24
+ - **SQLite tracking** — all sessions stored in a local database
23
25
  - **Two modes**: Docker (apps in containers) or Native (apps run locally)
26
+ - **Claude Code integration** built-in (`dev-prism claude`)
24
27
  - **Portable**: Works with any project
25
28
 
26
29
  ## Installation
@@ -36,17 +39,27 @@ pnpm add -D dev-prism
36
39
  ### Create a session
37
40
 
38
41
  ```bash
39
- # Docker mode (default) - apps run in containers
42
+ # Auto-assign session ID
43
+ dev-prism create
44
+
45
+ # Explicit session ID
40
46
  dev-prism create 001
41
47
 
48
+ # Custom branch name
49
+ dev-prism create --branch feature/my-feature
50
+
42
51
  # Native mode - only infrastructure in Docker, apps run via pnpm dev
43
- dev-prism create 001 --mode=native
52
+ dev-prism create --mode native
53
+
54
+ # Exclude specific apps from Docker
55
+ dev-prism create --without web,widget
44
56
 
45
57
  # In-place mode - use current directory instead of creating worktree
46
- dev-prism create 001 --in-place
47
- ```
58
+ dev-prism create --in-place
48
59
 
49
- **Note:** In-place sessions are not shown in `dev-prism list` (which only lists worktree-based sessions). Use `dev-prism info` from within an in-place session directory to see its details.
60
+ # Stream logs after creation instead of detaching
61
+ dev-prism create --no-detach
62
+ ```
50
63
 
51
64
  ### List sessions
52
65
 
@@ -74,11 +87,20 @@ dev-prism stop-all # Stop all sessions
74
87
  dev-prism logs 001
75
88
  ```
76
89
 
77
- ### Destroy a session
90
+ ### Cleanup
78
91
 
79
92
  ```bash
80
93
  dev-prism destroy 001 # Destroy specific session
81
94
  dev-prism destroy --all # Destroy all sessions
95
+ dev-prism prune # Remove all stopped sessions
96
+ dev-prism prune -y # Skip confirmation
97
+ ```
98
+
99
+ ### Claude Code integration
100
+
101
+ ```bash
102
+ dev-prism claude # Install Claude Code skill + CLAUDE.md
103
+ dev-prism claude --force # Overwrite existing files
82
104
  ```
83
105
 
84
106
  ## Port Allocation
@@ -171,9 +193,11 @@ services:
171
193
 
172
194
  ## How It Works
173
195
 
174
- 1. **Create session**: `dev-prism create 001`
175
- - Creates git worktree at `../project-sessions/session-001`
196
+ 1. **Create session**: `dev-prism create`
197
+ - Auto-assigns next available session ID (or use explicit ID)
198
+ - Creates git worktree at `../project-sessions/session-001` (or uses current dir with `--in-place`)
176
199
  - Generates `.env.session` with calculated ports
200
+ - Records session in local SQLite database
177
201
  - Runs `docker compose --env-file .env.session up -d`
178
202
  - Runs setup commands
179
203
 
@@ -182,6 +206,8 @@ services:
182
206
  3. **Docker mode** (`--profile apps`): All services including apps run in containers
183
207
  4. **Native mode**: Only infrastructure runs; apps use `pnpm dev` with `.env.session`
184
208
 
209
+ All session state is tracked in a SQLite database (`~/.dev-prism/sessions.db`), making both worktree and in-place sessions first-class citizens across all commands.
210
+
185
211
  ## Generated Files
186
212
 
187
213
  ```
@@ -207,4 +233,4 @@ To use in another project:
207
233
  1. Install: `pnpm add -D dev-prism`
208
234
  2. Create `session.config.mjs` with port offsets
209
235
  3. Create `docker-compose.session.yml` with `${VAR}` placeholders
210
- 4. Run `dev-prism create 001`
236
+ 4. Run `dev-prism create`
package/bin/dev-prism.js CHANGED
@@ -5,13 +5,19 @@ import { createSession } from '../dist/commands/create.js';
5
5
  import { destroySession } from '../dist/commands/destroy.js';
6
6
  import { listSessions } from '../dist/commands/list.js';
7
7
  import { installClaude } from '../dist/commands/claude.js';
8
+ import { showInfo } from '../dist/commands/info.js';
9
+ import { startSession } from '../dist/commands/start.js';
10
+ import { stopSession } from '../dist/commands/stop.js';
11
+ import { stopAllSessions } from '../dist/commands/stop-all.js';
12
+ import { pruneSessions } from '../dist/commands/prune.js';
13
+ import { streamLogs } from '../dist/commands/logs.js';
8
14
 
9
15
  const program = new Command();
10
16
 
11
17
  program
12
18
  .name('dev-prism')
13
19
  .description('CLI tool for managing isolated parallel development sessions')
14
- .version('0.1.0');
20
+ .version('0.4.0');
15
21
 
16
22
  program
17
23
  .command('create [sessionId]')
@@ -44,61 +50,17 @@ program
44
50
  program
45
51
  .command('list')
46
52
  .description('List all active development sessions')
47
- .action(async () => {
53
+ .option('-a, --all', 'Show sessions across all projects')
54
+ .action(async (options) => {
48
55
  const projectRoot = process.cwd();
49
- await listSessions(projectRoot);
56
+ await listSessions(projectRoot, { all: options.all });
50
57
  });
51
58
 
52
59
  program
53
60
  .command('info')
54
61
  .description('Show session info for current directory (useful for --in-place sessions)')
55
62
  .action(async () => {
56
- const cwd = process.cwd();
57
- const chalk = (await import('chalk')).default;
58
- const { existsSync, readFileSync } = await import('node:fs');
59
- const { resolve } = await import('node:path');
60
- const docker = await import('../dist/lib/docker.js');
61
-
62
- const envFile = resolve(cwd, '.env.session');
63
- if (!existsSync(envFile)) {
64
- console.log(chalk.yellow('No .env.session found in current directory.'));
65
- console.log(chalk.gray('Run `dev-prism create --in-place` to create a session here.'));
66
- process.exit(1);
67
- }
68
-
69
- // Parse .env.session
70
- const envContent = readFileSync(envFile, 'utf-8');
71
- const env = {};
72
- for (const line of envContent.split('\n')) {
73
- const match = line.match(/^([^=]+)=(.*)$/);
74
- if (match) {
75
- env[match[1]] = match[2];
76
- }
77
- }
78
-
79
- const sessionId = env.SESSION_ID || 'unknown';
80
- const running = await docker.isRunning({ cwd });
81
-
82
- console.log(chalk.blue(`\nSession ${sessionId}`));
83
- console.log(chalk.gray(`Directory: ${cwd}`));
84
- console.log(running ? chalk.green('Status: running') : chalk.yellow('Status: stopped'));
85
-
86
- console.log(chalk.gray('\nPorts:'));
87
- for (const [key, value] of Object.entries(env)) {
88
- if (key.includes('PORT')) {
89
- console.log(chalk.gray(` ${key}: ${value}`));
90
- }
91
- }
92
-
93
- console.log(chalk.gray('\nURLs:'));
94
- for (const [key, value] of Object.entries(env)) {
95
- if (key.includes('APP') || key.includes('WEB') || key.includes('WIDGET')) {
96
- if (key.includes('PORT')) {
97
- console.log(chalk.cyan(` ${key.replace('_PORT', '')}: http://localhost:${value}`));
98
- }
99
- }
100
- }
101
- console.log('');
63
+ await showInfo(process.cwd());
102
64
  });
103
65
 
104
66
  program
@@ -108,19 +70,10 @@ program
108
70
  .option('-W, --without <apps>', 'Exclude apps (comma-separated: app,web,widget)', (val) => val.split(','))
109
71
  .action(async (sessionId, options) => {
110
72
  const projectRoot = process.cwd();
111
- const { loadConfig, getSessionDir } = await import('../dist/lib/config.js');
112
- const docker = await import('../dist/lib/docker.js');
113
-
114
- const config = await loadConfig(projectRoot);
115
- const sessionDir = getSessionDir(config, projectRoot, sessionId);
116
- let profiles;
117
- if (options.mode === 'docker') {
118
- const allApps = config.apps ?? [];
119
- const excludeApps = options.without ?? [];
120
- profiles = allApps.filter((app) => !excludeApps.includes(app));
121
- }
122
-
123
- await docker.up({ cwd: sessionDir, profiles });
73
+ await startSession(projectRoot, sessionId, {
74
+ mode: options.mode,
75
+ without: options.without,
76
+ });
124
77
  });
125
78
 
126
79
  program
@@ -128,18 +81,7 @@ program
128
81
  .description('Stop Docker services for a session (without destroying)')
129
82
  .action(async (sessionId) => {
130
83
  const projectRoot = process.cwd();
131
- const { loadConfig, getSessionDir } = await import('../dist/lib/config.js');
132
- const { execa } = await import('execa');
133
-
134
- const config = await loadConfig(projectRoot);
135
- const sessionDir = getSessionDir(config, projectRoot, sessionId);
136
-
137
- // Use stop instead of down to preserve volumes
138
- await execa(
139
- 'docker',
140
- ['compose', '-f', 'docker-compose.session.yml', '--env-file', '.env.session', 'stop'],
141
- { cwd: sessionDir, stdio: 'inherit' }
142
- );
84
+ await stopSession(projectRoot, sessionId);
143
85
  });
144
86
 
145
87
  program
@@ -150,30 +92,11 @@ program
150
92
  .option('-n, --tail <lines>', 'Number of lines to show from the end', '50')
151
93
  .action(async (sessionId, options) => {
152
94
  const projectRoot = process.cwd();
153
- const { loadConfig, getSessionDir } = await import('../dist/lib/config.js');
154
- const { execa } = await import('execa');
155
-
156
- const config = await loadConfig(projectRoot);
157
- const sessionDir = getSessionDir(config, projectRoot, sessionId);
158
- let profileFlags = [];
159
- if (options.mode === 'docker') {
160
- const allApps = config.apps ?? [];
161
- const excludeApps = options.without ?? [];
162
- const profiles = allApps.filter((app) => !excludeApps.includes(app));
163
- profileFlags = profiles.flatMap((p) => ['--profile', p]);
164
- }
165
-
166
- const args = [
167
- 'compose',
168
- '-f', 'docker-compose.session.yml',
169
- '--env-file', '.env.session',
170
- ...profileFlags,
171
- 'logs',
172
- '-f',
173
- '--tail', options.tail,
174
- ];
175
-
176
- await execa('docker', args, { cwd: sessionDir, stdio: 'inherit' });
95
+ await streamLogs(projectRoot, sessionId, {
96
+ mode: options.mode,
97
+ without: options.without,
98
+ tail: options.tail,
99
+ });
177
100
  });
178
101
 
179
102
  program
@@ -181,62 +104,7 @@ program
181
104
  .description('Stop all running sessions (preserves data)')
182
105
  .action(async () => {
183
106
  const projectRoot = process.cwd();
184
- const chalk = (await import('chalk')).default;
185
- const { loadConfig, getSessionDir } = await import('../dist/lib/config.js');
186
- const { getSessionWorktrees } = await import('../dist/lib/worktree.js');
187
- const docker = await import('../dist/lib/docker.js');
188
- const { existsSync } = await import('node:fs');
189
- const { resolve } = await import('node:path');
190
-
191
- const config = await loadConfig(projectRoot);
192
- const sessions = await getSessionWorktrees(projectRoot);
193
-
194
- if (sessions.length === 0) {
195
- console.log(chalk.gray('No sessions found.'));
196
- return;
197
- }
198
-
199
- // Find running sessions
200
- const runningSessions = [];
201
- for (const session of sessions) {
202
- const envFile = resolve(session.path, '.env.session');
203
- if (existsSync(envFile)) {
204
- const running = await docker.isRunning({ cwd: session.path });
205
- if (running) {
206
- runningSessions.push(session);
207
- }
208
- }
209
- }
210
-
211
- if (runningSessions.length === 0) {
212
- console.log(chalk.gray('No running sessions found.'));
213
- return;
214
- }
215
-
216
- console.log(chalk.blue(`Stopping ${runningSessions.length} running session(s)...\n`));
217
-
218
- // Get all app profiles and service names to ensure we stop everything
219
- const allApps = config.apps ?? [];
220
- const profileFlags = allApps.flatMap((p) => ['--profile', p]);
221
- // Explicitly list all services to stop (infrastructure + apps)
222
- const allServices = ['postgres', 'mailpit', ...allApps];
223
-
224
- const { execa } = await import('execa');
225
- for (const session of runningSessions) {
226
- console.log(chalk.gray(` Stopping session ${session.sessionId}...`));
227
- try {
228
- await execa(
229
- 'docker',
230
- ['compose', '-f', 'docker-compose.session.yml', '--env-file', '.env.session', ...profileFlags, 'stop', ...allServices],
231
- { cwd: session.path, stdio: 'pipe' }
232
- );
233
- console.log(chalk.green(` Session ${session.sessionId} stopped.`));
234
- } catch (error) {
235
- console.log(chalk.yellow(` Warning: Could not stop session ${session.sessionId}`));
236
- }
237
- }
238
-
239
- console.log(chalk.green(`\nStopped ${runningSessions.length} session(s).`));
107
+ await stopAllSessions(projectRoot);
240
108
  });
241
109
 
242
110
  program
@@ -245,87 +113,7 @@ program
245
113
  .option('-y, --yes', 'Skip confirmation prompt')
246
114
  .action(async (options) => {
247
115
  const projectRoot = process.cwd();
248
- const chalk = (await import('chalk')).default;
249
- const { loadConfig } = await import('../dist/lib/config.js');
250
- const { getSessionWorktrees, removeWorktree } = await import('../dist/lib/worktree.js');
251
- const docker = await import('../dist/lib/docker.js');
252
- const { existsSync } = await import('node:fs');
253
- const { resolve } = await import('node:path');
254
- const readline = await import('node:readline');
255
-
256
- const config = await loadConfig(projectRoot);
257
- const sessions = await getSessionWorktrees(projectRoot);
258
-
259
- if (sessions.length === 0) {
260
- console.log(chalk.gray('No sessions found.'));
261
- return;
262
- }
263
-
264
- // Find stopped sessions
265
- const stoppedSessions = [];
266
- for (const session of sessions) {
267
- const envFile = resolve(session.path, '.env.session');
268
- let running = false;
269
- if (existsSync(envFile)) {
270
- running = await docker.isRunning({ cwd: session.path });
271
- }
272
- if (!running) {
273
- stoppedSessions.push(session);
274
- }
275
- }
276
-
277
- if (stoppedSessions.length === 0) {
278
- console.log(chalk.gray('No stopped sessions to prune.'));
279
- return;
280
- }
281
-
282
- console.log(chalk.yellow(`\nFound ${stoppedSessions.length} stopped session(s) to prune:`));
283
- for (const session of stoppedSessions) {
284
- console.log(chalk.gray(` - Session ${session.sessionId} (${session.branch})`));
285
- }
286
- console.log('');
287
-
288
- // Confirm unless --yes flag provided
289
- if (!options.yes) {
290
- const rl = readline.createInterface({
291
- input: process.stdin,
292
- output: process.stdout,
293
- });
294
-
295
- const answer = await new Promise((resolve) => {
296
- rl.question(chalk.red('Are you sure you want to delete these sessions? This cannot be undone. [y/N] '), resolve);
297
- });
298
- rl.close();
299
-
300
- if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
301
- console.log(chalk.gray('Cancelled.'));
302
- return;
303
- }
304
- }
305
-
306
- console.log(chalk.blue('\nPruning stopped sessions...\n'));
307
-
308
- for (const session of stoppedSessions) {
309
- console.log(chalk.gray(` Removing session ${session.sessionId}...`));
310
- try {
311
- // Clean up any docker resources
312
- const envFile = resolve(session.path, '.env.session');
313
- if (existsSync(envFile)) {
314
- try {
315
- await docker.down({ cwd: session.path });
316
- } catch {
317
- // Ignore errors - containers might already be removed
318
- }
319
- }
320
- // Remove worktree and branch
321
- await removeWorktree(projectRoot, session.path, session.branch);
322
- console.log(chalk.green(` Session ${session.sessionId} removed.`));
323
- } catch (error) {
324
- console.log(chalk.yellow(` Warning: Could not fully remove session ${session.sessionId}`));
325
- }
326
- }
327
-
328
- console.log(chalk.green(`\nPruned ${stoppedSessions.length} session(s).`));
116
+ await pruneSessions(projectRoot, { yes: options.yes });
329
117
  });
330
118
 
331
119
  program
@@ -0,0 +1,22 @@
1
+ // src/lib/ports.ts
2
+ function calculatePorts(config, sessionId) {
3
+ const sessionNum = parseInt(sessionId, 10);
4
+ const basePort = config.portBase + sessionNum * 100;
5
+ const ports = {};
6
+ for (const [name, offset] of Object.entries(config.ports)) {
7
+ ports[name] = basePort + offset;
8
+ }
9
+ return ports;
10
+ }
11
+ function formatPortsTable(ports) {
12
+ const lines = [];
13
+ for (const [name, port] of Object.entries(ports)) {
14
+ lines.push(` ${name}: http://localhost:${port}`);
15
+ }
16
+ return lines.join("\n");
17
+ }
18
+
19
+ export {
20
+ calculatePorts,
21
+ formatPortsTable
22
+ };
@@ -0,0 +1,78 @@
1
+ import {
2
+ removeWorktree
3
+ } from "./chunk-Y3GR6XK7.js";
4
+ import {
5
+ down
6
+ } from "./chunk-GBN67HYD.js";
7
+ import {
8
+ loadConfig
9
+ } from "./chunk-25WQHUYW.js";
10
+ import {
11
+ SessionStore
12
+ } from "./chunk-6YMQTISJ.js";
13
+
14
+ // src/commands/destroy.ts
15
+ import { existsSync } from "fs";
16
+ import { resolve } from "path";
17
+ import chalk from "chalk";
18
+ async function destroySession(projectRoot, sessionId, options) {
19
+ const config = await loadConfig(projectRoot);
20
+ const store = new SessionStore();
21
+ try {
22
+ if (options.all) {
23
+ console.log(chalk.blue("Destroying all sessions..."));
24
+ const sessions = store.listByProject(projectRoot);
25
+ if (sessions.length === 0) {
26
+ console.log(chalk.gray("No sessions found."));
27
+ return;
28
+ }
29
+ for (const session2 of sessions) {
30
+ await destroySingleSession(projectRoot, session2.session_id, session2.session_dir, session2.branch, session2.in_place === 1);
31
+ store.markDestroyed(projectRoot, session2.session_id);
32
+ }
33
+ console.log(chalk.green(`
34
+ Destroyed ${sessions.length} session(s).`));
35
+ return;
36
+ }
37
+ if (!sessionId) {
38
+ console.error(chalk.red("Error: Session ID required. Use --all to destroy all sessions."));
39
+ process.exit(1);
40
+ }
41
+ if (!/^\d{3}$/.test(sessionId)) {
42
+ console.error(chalk.red("Error: Session ID must be exactly 3 digits (001-999)"));
43
+ process.exit(1);
44
+ }
45
+ const session = store.findSession(projectRoot, sessionId);
46
+ if (!session) {
47
+ console.error(chalk.red(`Error: Session ${sessionId} not found.`));
48
+ process.exit(1);
49
+ }
50
+ await destroySingleSession(projectRoot, sessionId, session.session_dir, session.branch, session.in_place === 1);
51
+ store.markDestroyed(projectRoot, sessionId);
52
+ console.log(chalk.green(`
53
+ Session ${sessionId} destroyed.`));
54
+ } finally {
55
+ store.close();
56
+ }
57
+ }
58
+ async function destroySingleSession(projectRoot, sessionId, sessionDir, branchName, inPlace) {
59
+ console.log(chalk.blue(`
60
+ Destroying session ${sessionId}...`));
61
+ const envFile = resolve(sessionDir, ".env.session");
62
+ if (existsSync(envFile)) {
63
+ console.log(chalk.gray(" Stopping Docker containers..."));
64
+ try {
65
+ await down({ cwd: sessionDir });
66
+ } catch {
67
+ }
68
+ }
69
+ if (!inPlace) {
70
+ console.log(chalk.gray(" Removing git worktree..."));
71
+ await removeWorktree(projectRoot, sessionDir, branchName);
72
+ }
73
+ console.log(chalk.green(` Session ${sessionId} destroyed.`));
74
+ }
75
+
76
+ export {
77
+ destroySession
78
+ };
@@ -0,0 +1,78 @@
1
+ import {
2
+ removeWorktree
3
+ } from "./chunk-Y3GR6XK7.js";
4
+ import {
5
+ down
6
+ } from "./chunk-GBN67HYD.js";
7
+ import {
8
+ loadConfig
9
+ } from "./chunk-25WQHUYW.js";
10
+ import {
11
+ SessionStore
12
+ } from "./chunk-H4HPDIY3.js";
13
+
14
+ // src/commands/destroy.ts
15
+ import { existsSync } from "fs";
16
+ import { resolve } from "path";
17
+ import chalk from "chalk";
18
+ async function destroySession(projectRoot, sessionId, options) {
19
+ const config = await loadConfig(projectRoot);
20
+ const store = new SessionStore();
21
+ try {
22
+ if (options.all) {
23
+ console.log(chalk.blue("Destroying all sessions..."));
24
+ const sessions = store.listByProject(projectRoot);
25
+ if (sessions.length === 0) {
26
+ console.log(chalk.gray("No sessions found."));
27
+ return;
28
+ }
29
+ for (const session2 of sessions) {
30
+ await destroySingleSession(projectRoot, session2.session_id, session2.session_dir, session2.branch, session2.in_place === 1);
31
+ store.markDestroyed(projectRoot, session2.session_id);
32
+ }
33
+ console.log(chalk.green(`
34
+ Destroyed ${sessions.length} session(s).`));
35
+ return;
36
+ }
37
+ if (!sessionId) {
38
+ console.error(chalk.red("Error: Session ID required. Use --all to destroy all sessions."));
39
+ process.exit(1);
40
+ }
41
+ if (!/^\d{3}$/.test(sessionId)) {
42
+ console.error(chalk.red("Error: Session ID must be exactly 3 digits (001-999)"));
43
+ process.exit(1);
44
+ }
45
+ const session = store.findSession(projectRoot, sessionId);
46
+ if (!session) {
47
+ console.error(chalk.red(`Error: Session ${sessionId} not found.`));
48
+ process.exit(1);
49
+ }
50
+ await destroySingleSession(projectRoot, sessionId, session.session_dir, session.branch, session.in_place === 1);
51
+ store.markDestroyed(projectRoot, sessionId);
52
+ console.log(chalk.green(`
53
+ Session ${sessionId} destroyed.`));
54
+ } finally {
55
+ store.close();
56
+ }
57
+ }
58
+ async function destroySingleSession(projectRoot, sessionId, sessionDir, branchName, inPlace) {
59
+ console.log(chalk.blue(`
60
+ Destroying session ${sessionId}...`));
61
+ const envFile = resolve(sessionDir, ".env.session");
62
+ if (existsSync(envFile)) {
63
+ console.log(chalk.gray(" Stopping Docker containers..."));
64
+ try {
65
+ await down({ cwd: sessionDir });
66
+ } catch {
67
+ }
68
+ }
69
+ if (!inPlace) {
70
+ console.log(chalk.gray(" Removing git worktree..."));
71
+ await removeWorktree(projectRoot, sessionDir, branchName);
72
+ }
73
+ console.log(chalk.green(` Session ${sessionId} destroyed.`));
74
+ }
75
+
76
+ export {
77
+ destroySession
78
+ };
@@ -0,0 +1,38 @@
1
+ import {
2
+ up
3
+ } from "./chunk-GBN67HYD.js";
4
+ import {
5
+ loadConfig
6
+ } from "./chunk-25WQHUYW.js";
7
+ import {
8
+ SessionStore
9
+ } from "./chunk-H4HPDIY3.js";
10
+
11
+ // src/commands/start.ts
12
+ import chalk from "chalk";
13
+ async function startSession(projectRoot, sessionId, options) {
14
+ const config = await loadConfig(projectRoot);
15
+ const store = new SessionStore();
16
+ let sessionDir;
17
+ try {
18
+ const session = store.findSession(projectRoot, sessionId);
19
+ if (!session) {
20
+ console.error(chalk.red(`Error: Session ${sessionId} not found.`));
21
+ process.exit(1);
22
+ }
23
+ sessionDir = session.session_dir;
24
+ } finally {
25
+ store.close();
26
+ }
27
+ let profiles;
28
+ if (options.mode === "docker") {
29
+ const allApps = config.apps ?? [];
30
+ const excludeApps = options.without ?? [];
31
+ profiles = allApps.filter((app) => !excludeApps.includes(app));
32
+ }
33
+ await up({ cwd: sessionDir, profiles });
34
+ }
35
+
36
+ export {
37
+ startSession
38
+ };