@enruana/claude-orka 0.1.0 → 0.1.2

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 (3) hide show
  1. package/bin/orka.js +14 -12
  2. package/dist/cli.js +2433 -0
  3. package/package.json +5 -2
package/dist/cli.js ADDED
@@ -0,0 +1,2433 @@
1
+ #!/usr/bin/env node
2
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
+ }) : x)(function(x) {
5
+ if (typeof require !== "undefined") return require.apply(this, arguments);
6
+ throw Error('Dynamic require of "' + x + '" is not supported');
7
+ });
8
+
9
+ // src/cli/index.ts
10
+ import { Command } from "commander";
11
+
12
+ // src/core/StateManager.ts
13
+ import path2 from "path";
14
+ import fs2 from "fs-extra";
15
+
16
+ // src/utils/tmux.ts
17
+ import execa from "execa";
18
+
19
+ // src/utils/logger.ts
20
+ var Logger = class {
21
+ level = 1 /* INFO */;
22
+ setLevel(level) {
23
+ this.level = level;
24
+ }
25
+ debug(...args) {
26
+ if (this.level <= 0 /* DEBUG */) {
27
+ console.log("[DEBUG]", ...args);
28
+ }
29
+ }
30
+ info(...args) {
31
+ if (this.level <= 1 /* INFO */) {
32
+ console.log("[INFO]", ...args);
33
+ }
34
+ }
35
+ warn(...args) {
36
+ if (this.level <= 2 /* WARN */) {
37
+ console.warn("[WARN]", ...args);
38
+ }
39
+ }
40
+ error(...args) {
41
+ if (this.level <= 3 /* ERROR */) {
42
+ console.error("[ERROR]", ...args);
43
+ }
44
+ }
45
+ };
46
+ var logger = new Logger();
47
+
48
+ // src/utils/tmux.ts
49
+ var TmuxError = class extends Error {
50
+ constructor(message, command, originalError) {
51
+ super(message);
52
+ this.command = command;
53
+ this.originalError = originalError;
54
+ this.name = "TmuxError";
55
+ }
56
+ };
57
+ var TmuxCommands = class {
58
+ /**
59
+ * Verificar si tmux está disponible
60
+ */
61
+ static async isAvailable() {
62
+ try {
63
+ await execa("which", ["tmux"]);
64
+ return true;
65
+ } catch {
66
+ return false;
67
+ }
68
+ }
69
+ /**
70
+ * Crear una nueva sesión tmux en modo detached
71
+ */
72
+ static async createSession(name, projectPath) {
73
+ try {
74
+ logger.debug(`Creating tmux session: ${name} at ${projectPath}`);
75
+ await execa("tmux", ["new-session", "-d", "-s", name, "-c", projectPath]);
76
+ logger.info(`Tmux session created: ${name}`);
77
+ } catch (error) {
78
+ throw new TmuxError(
79
+ `Failed to create tmux session: ${name}`,
80
+ `tmux new-session -d -s ${name} -c ${projectPath}`,
81
+ error
82
+ );
83
+ }
84
+ }
85
+ /**
86
+ * Abrir una terminal que se adjunte a una sesión tmux existente
87
+ * (Solo macOS por ahora)
88
+ */
89
+ static async openTerminalWindow(sessionName) {
90
+ try {
91
+ logger.debug(`Opening terminal window for session: ${sessionName}`);
92
+ const platform = process.platform;
93
+ if (platform === "darwin") {
94
+ const script = `tell application "Terminal"
95
+ do script "tmux attach -t ${sessionName}"
96
+ activate
97
+ end tell`;
98
+ await execa("osascript", ["-e", script]);
99
+ logger.info("Terminal window opened (Terminal.app)");
100
+ } else if (platform === "linux") {
101
+ try {
102
+ await execa("gnome-terminal", ["--", "tmux", "attach", "-t", sessionName]);
103
+ logger.info("Terminal window opened (gnome-terminal)");
104
+ } catch {
105
+ try {
106
+ await execa("xterm", ["-e", `tmux attach -t ${sessionName}`]);
107
+ logger.info("Terminal window opened (xterm)");
108
+ } catch {
109
+ logger.warn("Could not open terminal window on Linux");
110
+ throw new Error("No suitable terminal emulator found");
111
+ }
112
+ }
113
+ } else {
114
+ logger.warn(`Platform ${platform} not supported for opening terminal windows`);
115
+ throw new Error(`Platform ${platform} not supported`);
116
+ }
117
+ } catch (error) {
118
+ throw new TmuxError(
119
+ `Failed to open terminal window for session: ${sessionName}`,
120
+ `osascript/terminal`,
121
+ error
122
+ );
123
+ }
124
+ }
125
+ /**
126
+ * Cerrar una sesión tmux
127
+ */
128
+ static async killSession(sessionName) {
129
+ try {
130
+ logger.debug(`Killing tmux session: ${sessionName}`);
131
+ await execa("tmux", ["kill-session", "-t", sessionName]);
132
+ logger.info(`Tmux session killed: ${sessionName}`);
133
+ } catch (error) {
134
+ throw new TmuxError(
135
+ `Failed to kill tmux session: ${sessionName}`,
136
+ `tmux kill-session -t ${sessionName}`,
137
+ error
138
+ );
139
+ }
140
+ }
141
+ /**
142
+ * Verificar si una sesión existe
143
+ */
144
+ static async sessionExists(sessionName) {
145
+ try {
146
+ await execa("tmux", ["has-session", "-t", sessionName]);
147
+ return true;
148
+ } catch {
149
+ return false;
150
+ }
151
+ }
152
+ /**
153
+ * Obtener el ID del pane principal de una sesión
154
+ */
155
+ static async getMainPaneId(sessionName) {
156
+ try {
157
+ logger.debug(`Getting main pane ID for session: ${sessionName}`);
158
+ const { stdout } = await execa("tmux", [
159
+ "list-panes",
160
+ "-t",
161
+ sessionName,
162
+ "-F",
163
+ "#{pane_id}"
164
+ ]);
165
+ const paneId = stdout.split("\n")[0];
166
+ logger.debug(`Main pane ID: ${paneId}`);
167
+ return paneId;
168
+ } catch (error) {
169
+ throw new TmuxError(
170
+ `Failed to get main pane ID for session: ${sessionName}`,
171
+ `tmux list-panes -t ${sessionName} -F '#{pane_id}'`,
172
+ error
173
+ );
174
+ }
175
+ }
176
+ /**
177
+ * Obtener el ID de la ventana principal de una sesión
178
+ */
179
+ static async getMainWindowId(sessionName) {
180
+ try {
181
+ logger.debug(`Getting main window ID for session: ${sessionName}`);
182
+ const { stdout } = await execa("tmux", [
183
+ "list-windows",
184
+ "-t",
185
+ sessionName,
186
+ "-F",
187
+ "#{window_id}"
188
+ ]);
189
+ const windowId = stdout.split("\n")[0];
190
+ logger.debug(`Main window ID: ${windowId}`);
191
+ return windowId;
192
+ } catch (error) {
193
+ throw new TmuxError(
194
+ `Failed to get main window ID for session: ${sessionName}`,
195
+ `tmux list-windows -t ${sessionName} -F '#{window_id}'`,
196
+ error
197
+ );
198
+ }
199
+ }
200
+ /**
201
+ * Dividir un pane (crear fork)
202
+ * @param sessionName Nombre de la sesión
203
+ * @param vertical Si es true, divide verticalmente (-h), si es false horizontalmente (-v)
204
+ * @returns ID del nuevo pane creado
205
+ */
206
+ static async splitPane(sessionName, vertical = false) {
207
+ try {
208
+ const direction = vertical ? "-h" : "-v";
209
+ logger.debug(`Splitting pane in session ${sessionName} (${vertical ? "vertical" : "horizontal"})`);
210
+ await execa("tmux", ["split-window", "-t", sessionName, direction]);
211
+ const { stdout } = await execa("tmux", [
212
+ "list-panes",
213
+ "-t",
214
+ sessionName,
215
+ "-F",
216
+ "#{pane_id}"
217
+ ]);
218
+ const panes = stdout.split("\n");
219
+ const newPaneId = panes[panes.length - 1];
220
+ logger.info(`New pane created: ${newPaneId}`);
221
+ return newPaneId;
222
+ } catch (error) {
223
+ throw new TmuxError(
224
+ `Failed to split pane in session: ${sessionName}`,
225
+ `tmux split-window -t ${sessionName} ${vertical ? "-h" : "-v"}`,
226
+ error
227
+ );
228
+ }
229
+ }
230
+ /**
231
+ * Listar todos los panes de una sesión
232
+ * @param sessionName Nombre de la sesión
233
+ * @returns Array de IDs de panes
234
+ */
235
+ static async listPanes(sessionName) {
236
+ try {
237
+ const { stdout } = await execa("tmux", [
238
+ "list-panes",
239
+ "-t",
240
+ sessionName,
241
+ "-F",
242
+ "#{pane_id}"
243
+ ]);
244
+ return stdout.trim().split("\n").filter(Boolean);
245
+ } catch (error) {
246
+ throw new TmuxError(
247
+ `Failed to list panes for session: ${sessionName}`,
248
+ `tmux list-panes -t ${sessionName}`,
249
+ error
250
+ );
251
+ }
252
+ }
253
+ /**
254
+ * Cerrar un pane específico
255
+ */
256
+ static async killPane(paneId) {
257
+ try {
258
+ logger.debug(`Killing pane: ${paneId}`);
259
+ await execa("tmux", ["kill-pane", "-t", paneId]);
260
+ logger.info(`Pane killed: ${paneId}`);
261
+ } catch (error) {
262
+ throw new TmuxError(
263
+ `Failed to kill pane: ${paneId}`,
264
+ `tmux kill-pane -t ${paneId}`,
265
+ error
266
+ );
267
+ }
268
+ }
269
+ /**
270
+ * Enviar texto a un pane (SIN Enter)
271
+ * IMPORTANTE: No envía Enter, debe llamarse a sendEnter() por separado
272
+ */
273
+ static async sendKeys(paneId, text) {
274
+ try {
275
+ logger.debug(`Sending keys to pane ${paneId}: ${text.substring(0, 50)}...`);
276
+ await execa("tmux", ["send-keys", "-t", paneId, text]);
277
+ } catch (error) {
278
+ throw new TmuxError(
279
+ `Failed to send keys to pane: ${paneId}`,
280
+ `tmux send-keys -t ${paneId} "${text}"`,
281
+ error
282
+ );
283
+ }
284
+ }
285
+ /**
286
+ * Enviar SOLO Enter a un pane
287
+ */
288
+ static async sendEnter(paneId) {
289
+ try {
290
+ logger.debug(`Sending Enter to pane: ${paneId}`);
291
+ await execa("tmux", ["send-keys", "-t", paneId, "Enter"]);
292
+ } catch (error) {
293
+ throw new TmuxError(
294
+ `Failed to send Enter to pane: ${paneId}`,
295
+ `tmux send-keys -t ${paneId} Enter`,
296
+ error
297
+ );
298
+ }
299
+ }
300
+ /**
301
+ * Enviar teclas especiales (flechas, escape, etc.)
302
+ * @param paneId ID del pane
303
+ * @param key Nombre de la tecla: 'Up', 'Down', 'Left', 'Right', 'Escape', 'Space', etc.
304
+ */
305
+ static async sendSpecialKey(paneId, key) {
306
+ try {
307
+ logger.debug(`Sending special key '${key}' to pane: ${paneId}`);
308
+ await execa("tmux", ["send-keys", "-t", paneId, key]);
309
+ } catch (error) {
310
+ throw new TmuxError(
311
+ `Failed to send special key '${key}' to pane: ${paneId}`,
312
+ `tmux send-keys -t ${paneId} ${key}`,
313
+ error
314
+ );
315
+ }
316
+ }
317
+ /**
318
+ * Capturar el contenido de un pane
319
+ * @param paneId ID del pane
320
+ * @param startLine Línea desde donde empezar a capturar (negativo = desde el final)
321
+ * @returns Contenido del pane
322
+ */
323
+ static async capturePane(paneId, startLine = -100) {
324
+ try {
325
+ logger.debug(`Capturing pane ${paneId} from line ${startLine}`);
326
+ const { stdout } = await execa("tmux", [
327
+ "capture-pane",
328
+ "-t",
329
+ paneId,
330
+ "-p",
331
+ "-S",
332
+ startLine.toString()
333
+ ]);
334
+ return stdout;
335
+ } catch (error) {
336
+ throw new TmuxError(
337
+ `Failed to capture pane: ${paneId}`,
338
+ `tmux capture-pane -t ${paneId} -p -S ${startLine}`,
339
+ error
340
+ );
341
+ }
342
+ }
343
+ /**
344
+ * Listar todas las sesiones tmux
345
+ */
346
+ static async listSessions() {
347
+ try {
348
+ const { stdout } = await execa("tmux", ["list-sessions", "-F", "#{session_id}:#{session_name}"]);
349
+ return stdout.split("\n").map((line) => {
350
+ const [id, name] = line.split(":");
351
+ return { id, name };
352
+ });
353
+ } catch (error) {
354
+ if (error.stderr?.includes("no server running")) {
355
+ return [];
356
+ }
357
+ throw new TmuxError(
358
+ "Failed to list tmux sessions",
359
+ "tmux list-sessions",
360
+ error
361
+ );
362
+ }
363
+ }
364
+ };
365
+
366
+ // src/utils/claude-history.ts
367
+ import fs from "fs-extra";
368
+ import os from "os";
369
+ import path from "path";
370
+ var CLAUDE_HISTORY_PATH = path.join(os.homedir(), ".claude", "history.jsonl");
371
+ async function readClaudeHistory() {
372
+ try {
373
+ const exists = await fs.pathExists(CLAUDE_HISTORY_PATH);
374
+ if (!exists) {
375
+ logger.warn(`Claude history file not found: ${CLAUDE_HISTORY_PATH}`);
376
+ return [];
377
+ }
378
+ const content = await fs.readFile(CLAUDE_HISTORY_PATH, "utf-8");
379
+ const lines = content.trim().split("\n").filter(Boolean);
380
+ const entries = [];
381
+ for (const line of lines) {
382
+ try {
383
+ const entry = JSON.parse(line);
384
+ entries.push(entry);
385
+ } catch (err) {
386
+ logger.warn(`Failed to parse history line: ${line}`);
387
+ }
388
+ }
389
+ return entries;
390
+ } catch (error) {
391
+ logger.error(`Error reading Claude history: ${error}`);
392
+ return [];
393
+ }
394
+ }
395
+ async function getExistingSessionIds() {
396
+ const entries = await readClaudeHistory();
397
+ return new Set(entries.map((e) => e.sessionId));
398
+ }
399
+ async function detectNewSessionId(previousIds, maxWaitMs = 1e4, pollIntervalMs = 500) {
400
+ const startTime = Date.now();
401
+ while (Date.now() - startTime < maxWaitMs) {
402
+ const currentIds = await getExistingSessionIds();
403
+ for (const id of currentIds) {
404
+ if (!previousIds.has(id)) {
405
+ logger.info(`Detected new Claude session: ${id}`);
406
+ return id;
407
+ }
408
+ }
409
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
410
+ }
411
+ logger.warn("Timeout waiting for new Claude session ID");
412
+ return null;
413
+ }
414
+
415
+ // src/core/StateManager.ts
416
+ var StateManager = class {
417
+ projectPath;
418
+ orkaDir;
419
+ statePath;
420
+ constructor(projectPath) {
421
+ this.projectPath = path2.resolve(projectPath);
422
+ this.orkaDir = path2.join(this.projectPath, ".claude-orka");
423
+ this.statePath = path2.join(this.orkaDir, "state.json");
424
+ }
425
+ /**
426
+ * Initialize StateManager
427
+ * Creates necessary folders if they don't exist
428
+ */
429
+ async initialize() {
430
+ logger.debug("Initializing StateManager");
431
+ await this.ensureDirectories();
432
+ if (!await fs2.pathExists(this.statePath)) {
433
+ logger.info("Creating initial state.json");
434
+ const initialState = {
435
+ version: "1.0.0",
436
+ projectPath: this.projectPath,
437
+ sessions: [],
438
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
439
+ };
440
+ await this.save(initialState);
441
+ }
442
+ logger.info("StateManager initialized");
443
+ }
444
+ /**
445
+ * Create directory structure
446
+ */
447
+ async ensureDirectories() {
448
+ await fs2.ensureDir(this.orkaDir);
449
+ logger.debug("Directories ensured");
450
+ }
451
+ /**
452
+ * Read current state
453
+ */
454
+ async read() {
455
+ try {
456
+ const content = await fs2.readFile(this.statePath, "utf-8");
457
+ return JSON.parse(content);
458
+ } catch (error) {
459
+ logger.error("Failed to read state:", error);
460
+ throw new Error(`Failed to read state: ${error.message}`);
461
+ }
462
+ }
463
+ /**
464
+ * Save state
465
+ */
466
+ async save(state) {
467
+ try {
468
+ state.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
469
+ await fs2.writeFile(this.statePath, JSON.stringify(state, null, 2), "utf-8");
470
+ logger.debug("State saved");
471
+ } catch (error) {
472
+ logger.error("Failed to save state:", error);
473
+ throw new Error(`Failed to save state: ${error.message}`);
474
+ }
475
+ }
476
+ // --- OPERACIONES DE SESIONES ---
477
+ /**
478
+ * Obtener el estado completo
479
+ */
480
+ async getState() {
481
+ return await this.read();
482
+ }
483
+ /**
484
+ * Listar todas las sesiones con filtros opcionales
485
+ */
486
+ async listSessions(filters) {
487
+ const state = await this.read();
488
+ let sessions = state.sessions;
489
+ if (filters?.status) {
490
+ sessions = sessions.filter((s) => s.status === filters.status);
491
+ }
492
+ if (filters?.name) {
493
+ sessions = sessions.filter(
494
+ (s) => s.name.toLowerCase().includes(filters.name.toLowerCase())
495
+ );
496
+ }
497
+ return sessions;
498
+ }
499
+ /**
500
+ * Agregar una nueva sesión
501
+ */
502
+ async addSession(session) {
503
+ const state = await this.read();
504
+ state.sessions.push(session);
505
+ await this.save(state);
506
+ logger.info(`Session added: ${session.id}`);
507
+ }
508
+ /**
509
+ * Obtener una sesión por ID
510
+ */
511
+ async getSession(sessionId) {
512
+ const state = await this.read();
513
+ return state.sessions.find((s) => s.id === sessionId) || null;
514
+ }
515
+ /**
516
+ * Obtener todas las sesiones con filtros opcionales
517
+ */
518
+ async getAllSessions(filters) {
519
+ const state = await this.read();
520
+ let sessions = state.sessions;
521
+ if (filters?.status) {
522
+ sessions = sessions.filter((s) => s.status === filters.status);
523
+ }
524
+ if (filters?.name) {
525
+ sessions = sessions.filter((s) => s.name.includes(filters.name));
526
+ }
527
+ return sessions;
528
+ }
529
+ /**
530
+ * Actualizar el estado de una sesión
531
+ */
532
+ async updateSessionStatus(sessionId, status) {
533
+ const state = await this.read();
534
+ const session = state.sessions.find((s) => s.id === sessionId);
535
+ if (!session) {
536
+ throw new Error(`Session not found: ${sessionId}`);
537
+ }
538
+ session.status = status;
539
+ session.lastActivity = (/* @__PURE__ */ new Date()).toISOString();
540
+ await this.save(state);
541
+ logger.info(`Session ${sessionId} status updated to: ${status}`);
542
+ }
543
+ /**
544
+ * Actualizar una sesión completa
545
+ */
546
+ async updateSession(sessionId, updates) {
547
+ const state = await this.read();
548
+ const sessionIndex = state.sessions.findIndex((s) => s.id === sessionId);
549
+ if (sessionIndex === -1) {
550
+ throw new Error(`Session not found: ${sessionId}`);
551
+ }
552
+ state.sessions[sessionIndex] = {
553
+ ...state.sessions[sessionIndex],
554
+ ...updates,
555
+ lastActivity: (/* @__PURE__ */ new Date()).toISOString()
556
+ };
557
+ await this.save(state);
558
+ logger.debug(`Session ${sessionId} updated`);
559
+ }
560
+ /**
561
+ * Reemplazar una sesión completa
562
+ */
563
+ async replaceSession(session) {
564
+ const state = await this.read();
565
+ const sessionIndex = state.sessions.findIndex((s) => s.id === session.id);
566
+ if (sessionIndex === -1) {
567
+ throw new Error(`Session not found: ${session.id}`);
568
+ }
569
+ state.sessions[sessionIndex] = session;
570
+ await this.save(state);
571
+ logger.debug(`Session ${session.id} replaced`);
572
+ }
573
+ /**
574
+ * Eliminar una sesión permanentemente
575
+ */
576
+ async deleteSession(sessionId) {
577
+ const state = await this.read();
578
+ state.sessions = state.sessions.filter((s) => s.id !== sessionId);
579
+ await this.save(state);
580
+ logger.info(`Session deleted: ${sessionId}`);
581
+ }
582
+ // --- OPERACIONES DE FORKS ---
583
+ /**
584
+ * Agregar un fork a una sesión
585
+ */
586
+ async addFork(sessionId, fork) {
587
+ const state = await this.read();
588
+ const session = state.sessions.find((s) => s.id === sessionId);
589
+ if (!session) {
590
+ throw new Error(`Session not found: ${sessionId}`);
591
+ }
592
+ session.forks.push(fork);
593
+ session.lastActivity = (/* @__PURE__ */ new Date()).toISOString();
594
+ await this.save(state);
595
+ logger.info(`Fork added to session ${sessionId}: ${fork.id}`);
596
+ }
597
+ /**
598
+ * Obtener un fork específico
599
+ */
600
+ async getFork(sessionId, forkId) {
601
+ const session = await this.getSession(sessionId);
602
+ if (!session) return null;
603
+ return session.forks.find((f) => f.id === forkId) || null;
604
+ }
605
+ /**
606
+ * Actualizar el estado de un fork
607
+ */
608
+ async updateForkStatus(sessionId, forkId, status) {
609
+ const state = await this.read();
610
+ const session = state.sessions.find((s) => s.id === sessionId);
611
+ if (!session) {
612
+ throw new Error(`Session not found: ${sessionId}`);
613
+ }
614
+ const fork = session.forks.find((f) => f.id === forkId);
615
+ if (!fork) {
616
+ throw new Error(`Fork not found: ${forkId}`);
617
+ }
618
+ fork.status = status;
619
+ session.lastActivity = (/* @__PURE__ */ new Date()).toISOString();
620
+ await this.save(state);
621
+ logger.info(`Fork ${forkId} status updated to: ${status}`);
622
+ }
623
+ /**
624
+ * Actualizar el path del contexto de un fork
625
+ */
626
+ async updateForkContext(sessionId, forkId, contextPath) {
627
+ const state = await this.read();
628
+ const session = state.sessions.find((s) => s.id === sessionId);
629
+ if (!session) {
630
+ throw new Error(`Session not found: ${sessionId}`);
631
+ }
632
+ const fork = session.forks.find((f) => f.id === forkId);
633
+ if (!fork) {
634
+ throw new Error(`Fork not found: ${forkId}`);
635
+ }
636
+ fork.contextPath = contextPath;
637
+ await this.save(state);
638
+ logger.debug(`Fork ${forkId} context updated: ${contextPath}`);
639
+ }
640
+ /**
641
+ * Actualizar un fork completo
642
+ */
643
+ async updateFork(sessionId, forkId, updates) {
644
+ const state = await this.read();
645
+ const session = state.sessions.find((s) => s.id === sessionId);
646
+ if (!session) {
647
+ throw new Error(`Session not found: ${sessionId}`);
648
+ }
649
+ const forkIndex = session.forks.findIndex((f) => f.id === forkId);
650
+ if (forkIndex === -1) {
651
+ throw new Error(`Fork not found: ${forkId}`);
652
+ }
653
+ session.forks[forkIndex] = {
654
+ ...session.forks[forkIndex],
655
+ ...updates
656
+ };
657
+ session.lastActivity = (/* @__PURE__ */ new Date()).toISOString();
658
+ await this.save(state);
659
+ logger.debug(`Fork ${forkId} updated`);
660
+ }
661
+ /**
662
+ * Eliminar un fork
663
+ */
664
+ async deleteFork(sessionId, forkId) {
665
+ const state = await this.read();
666
+ const session = state.sessions.find((s) => s.id === sessionId);
667
+ if (!session) {
668
+ throw new Error(`Session not found: ${sessionId}`);
669
+ }
670
+ const forkIndex = session.forks.findIndex((f) => f.id === forkId);
671
+ if (forkIndex === -1) {
672
+ throw new Error(`Fork not found: ${forkId}`);
673
+ }
674
+ session.forks.splice(forkIndex, 1);
675
+ session.lastActivity = (/* @__PURE__ */ new Date()).toISOString();
676
+ await this.save(state);
677
+ logger.debug(`Fork ${forkId} deleted`);
678
+ }
679
+ // --- OPERACIONES DE CONTEXTOS ---
680
+ /**
681
+ * Guardar un contexto en archivo
682
+ */
683
+ async saveContext(type, id, content) {
684
+ const contextPath = type === "session" ? this.getSessionContextPath(id) : this.getForkContextPath(id);
685
+ const fullPath = path2.join(this.projectPath, contextPath);
686
+ await fs2.writeFile(fullPath, content, "utf-8");
687
+ logger.info(`Context saved: ${contextPath}`);
688
+ return contextPath;
689
+ }
690
+ /**
691
+ * Leer un contexto desde archivo
692
+ */
693
+ async readContext(contextPath) {
694
+ const fullPath = path2.join(this.projectPath, contextPath);
695
+ if (!await fs2.pathExists(fullPath)) {
696
+ throw new Error(`Context file not found: ${contextPath}`);
697
+ }
698
+ return await fs2.readFile(fullPath, "utf-8");
699
+ }
700
+ // --- HELPERS ---
701
+ /**
702
+ * Obtener el path para el contexto de una sesión
703
+ */
704
+ getSessionContextPath(sessionId) {
705
+ return `.claude-orka/sessions/${sessionId}.md`;
706
+ }
707
+ /**
708
+ * Obtener el path para el contexto de un fork
709
+ */
710
+ getForkContextPath(forkId) {
711
+ return `.claude-orka/forks/${forkId}.md`;
712
+ }
713
+ /**
714
+ * Obtener el path para un export manual
715
+ */
716
+ getExportPath(forkId, name) {
717
+ return `.claude-orka/exports/${forkId}-${name}.md`;
718
+ }
719
+ };
720
+
721
+ // src/core/SessionManager.ts
722
+ import { v4 as uuidv4 } from "uuid";
723
+ import path3 from "path";
724
+ import fs3 from "fs-extra";
725
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
726
+ var SessionManager = class {
727
+ stateManager;
728
+ projectPath;
729
+ constructor(projectPath) {
730
+ this.projectPath = projectPath;
731
+ this.stateManager = new StateManager(projectPath);
732
+ }
733
+ /**
734
+ * Initialize el manager
735
+ */
736
+ async initialize() {
737
+ await this.stateManager.initialize();
738
+ }
739
+ /**
740
+ * Obtener el state
741
+ */
742
+ async getState() {
743
+ return await this.stateManager.getState();
744
+ }
745
+ // ==========================================
746
+ // SESIONES
747
+ // ==========================================
748
+ /**
749
+ * Crear una nueva sesión de Claude Code
750
+ */
751
+ async createSession(name, openTerminal = true) {
752
+ const sessionId = uuidv4();
753
+ const sessionName = name || `Session-${Date.now()}`;
754
+ const tmuxSessionId = `orka-${sessionId}`;
755
+ logger.info(`Creating session: ${sessionName}`);
756
+ await TmuxCommands.createSession(tmuxSessionId, this.projectPath);
757
+ if (openTerminal) {
758
+ await TmuxCommands.openTerminalWindow(tmuxSessionId);
759
+ }
760
+ await sleep(2e3);
761
+ const paneId = await TmuxCommands.getMainPaneId(tmuxSessionId);
762
+ logger.debug(`Main pane ID: ${paneId}`);
763
+ const claudeSessionId = uuidv4();
764
+ await this.initializeClaude(paneId, {
765
+ type: "new",
766
+ sessionId: claudeSessionId,
767
+ sessionName
768
+ });
769
+ const session = {
770
+ id: sessionId,
771
+ name: sessionName,
772
+ tmuxSessionId,
773
+ status: "active",
774
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
775
+ lastActivity: (/* @__PURE__ */ new Date()).toISOString(),
776
+ main: {
777
+ claudeSessionId,
778
+ tmuxPaneId: paneId,
779
+ status: "active"
780
+ },
781
+ forks: []
782
+ };
783
+ await this.stateManager.addSession(session);
784
+ logger.info(`Session created: ${sessionName} (${sessionId})`);
785
+ return session;
786
+ }
787
+ /**
788
+ * Restaurar una sesión guardada
789
+ */
790
+ async resumeSession(sessionId, openTerminal = true) {
791
+ const session = await this.getSession(sessionId);
792
+ if (!session) {
793
+ throw new Error(`Session ${sessionId} not found`);
794
+ }
795
+ logger.info(`Resuming session: ${session.name}`);
796
+ const tmuxSessionId = `orka-${sessionId}`;
797
+ await TmuxCommands.createSession(tmuxSessionId, this.projectPath);
798
+ if (openTerminal) {
799
+ await TmuxCommands.openTerminalWindow(tmuxSessionId);
800
+ }
801
+ await sleep(2e3);
802
+ const paneId = await TmuxCommands.getMainPaneId(tmuxSessionId);
803
+ await this.initializeClaude(paneId, {
804
+ type: "resume",
805
+ resumeSessionId: session.main.claudeSessionId,
806
+ sessionName: session.name
807
+ });
808
+ session.tmuxSessionId = tmuxSessionId;
809
+ session.main.tmuxPaneId = paneId;
810
+ session.main.status = "active";
811
+ session.status = "active";
812
+ session.lastActivity = (/* @__PURE__ */ new Date()).toISOString();
813
+ await this.stateManager.replaceSession(session);
814
+ const forksToRestore = session.forks.filter((f) => f.status !== "merged");
815
+ if (forksToRestore.length > 0) {
816
+ logger.info(`Restoring ${forksToRestore.length} fork(s)...`);
817
+ for (const fork of forksToRestore) {
818
+ await this.resumeFork(sessionId, fork.id);
819
+ }
820
+ }
821
+ logger.info(`Session resumed: ${session.name}`);
822
+ return session;
823
+ }
824
+ /**
825
+ * Close a session (save and kill tmux)
826
+ */
827
+ async closeSession(sessionId) {
828
+ const session = await this.getSession(sessionId);
829
+ if (!session) {
830
+ throw new Error(`Session ${sessionId} not found`);
831
+ }
832
+ logger.info(`Closing session: ${session.name}`);
833
+ const activeForks = session.forks.filter((f) => f.status === "active");
834
+ for (const fork of activeForks) {
835
+ await this.closeFork(sessionId, fork.id);
836
+ }
837
+ if (session.tmuxSessionId) {
838
+ await TmuxCommands.killSession(session.tmuxSessionId);
839
+ }
840
+ session.main.status = "saved";
841
+ session.main.tmuxPaneId = void 0;
842
+ session.status = "saved";
843
+ session.lastActivity = (/* @__PURE__ */ new Date()).toISOString();
844
+ await this.stateManager.replaceSession(session);
845
+ logger.info(`Session closed: ${session.name}`);
846
+ }
847
+ /**
848
+ * Eliminar una sesión permanentemente
849
+ */
850
+ async deleteSession(sessionId) {
851
+ const session = await this.getSession(sessionId);
852
+ if (!session) {
853
+ throw new Error(`Session ${sessionId} not found`);
854
+ }
855
+ logger.info(`Deleting session: ${session.name}`);
856
+ if (session.status === "active") {
857
+ await this.closeSession(sessionId);
858
+ }
859
+ await this.stateManager.deleteSession(sessionId);
860
+ logger.info(`Session deleted: ${session.name}`);
861
+ }
862
+ /**
863
+ * Listar sesiones con filtros opcionales
864
+ */
865
+ async listSessions(filters) {
866
+ return await this.stateManager.listSessions(filters);
867
+ }
868
+ /**
869
+ * Obtener una sesión por ID
870
+ */
871
+ async getSession(sessionId) {
872
+ return await this.stateManager.getSession(sessionId);
873
+ }
874
+ // ==========================================
875
+ // FORKS
876
+ // ==========================================
877
+ /**
878
+ * Crear un fork (rama de conversación)
879
+ */
880
+ async createFork(sessionId, name, vertical = false) {
881
+ const session = await this.getSession(sessionId);
882
+ if (!session) {
883
+ throw new Error(`Session ${sessionId} not found`);
884
+ }
885
+ const forkId = uuidv4();
886
+ const forkName = name || `Fork-${session.forks.length + 1}`;
887
+ logger.info(`Creating fork: ${forkName} in session ${session.name}`);
888
+ await TmuxCommands.splitPane(session.tmuxSessionId, vertical);
889
+ await sleep(1e3);
890
+ const allPanes = await TmuxCommands.listPanes(session.tmuxSessionId);
891
+ const forkPaneId = allPanes[allPanes.length - 1];
892
+ logger.debug(`Fork pane ID: ${forkPaneId}`);
893
+ const existingIds = await getExistingSessionIds();
894
+ logger.debug(`Existing sessions before fork: ${existingIds.size}`);
895
+ await this.initializeClaude(forkPaneId, {
896
+ type: "fork",
897
+ parentSessionId: session.main.claudeSessionId,
898
+ forkName
899
+ });
900
+ logger.info("Detecting fork session ID from history...");
901
+ const detectedForkId = await detectNewSessionId(existingIds, 3e4, 500);
902
+ if (!detectedForkId) {
903
+ throw new Error(
904
+ "Failed to detect fork session ID. Fork may not have been created. Check if the parent session is valid."
905
+ );
906
+ }
907
+ logger.info(`Fork session ID detected: ${detectedForkId}`);
908
+ const fork = {
909
+ id: forkId,
910
+ name: forkName,
911
+ claudeSessionId: detectedForkId,
912
+ // ✅ ID real detectado
913
+ tmuxPaneId: forkPaneId,
914
+ status: "active",
915
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
916
+ };
917
+ session.forks.push(fork);
918
+ session.lastActivity = (/* @__PURE__ */ new Date()).toISOString();
919
+ await this.stateManager.replaceSession(session);
920
+ logger.info(`Fork created: ${forkName} (${forkId})`);
921
+ return fork;
922
+ }
923
+ /**
924
+ * Restaurar un fork guardado
925
+ */
926
+ async resumeFork(sessionId, forkId) {
927
+ const session = await this.getSession(sessionId);
928
+ if (!session) {
929
+ throw new Error(`Session ${sessionId} not found`);
930
+ }
931
+ const fork = session.forks.find((f) => f.id === forkId);
932
+ if (!fork) {
933
+ throw new Error(`Fork ${forkId} not found`);
934
+ }
935
+ logger.info(`Resuming fork: ${fork.name}`);
936
+ await TmuxCommands.splitPane(session.tmuxSessionId, false);
937
+ await sleep(1e3);
938
+ const allPanes = await TmuxCommands.listPanes(session.tmuxSessionId);
939
+ const forkPaneId = allPanes[allPanes.length - 1];
940
+ await this.initializeClaude(forkPaneId, {
941
+ type: "resume",
942
+ resumeSessionId: fork.claudeSessionId,
943
+ sessionName: fork.name
944
+ });
945
+ fork.tmuxPaneId = forkPaneId;
946
+ fork.status = "active";
947
+ session.lastActivity = (/* @__PURE__ */ new Date()).toISOString();
948
+ await this.stateManager.replaceSession(session);
949
+ logger.info(`Fork resumed: ${fork.name}`);
950
+ return fork;
951
+ }
952
+ /**
953
+ * Cerrar un fork
954
+ */
955
+ async closeFork(sessionId, forkId) {
956
+ const session = await this.getSession(sessionId);
957
+ if (!session) {
958
+ throw new Error(`Session ${sessionId} not found`);
959
+ }
960
+ const fork = session.forks.find((f) => f.id === forkId);
961
+ if (!fork) {
962
+ throw new Error(`Fork ${forkId} not found`);
963
+ }
964
+ logger.info(`Closing fork: ${fork.name}`);
965
+ if (fork.tmuxPaneId) {
966
+ await TmuxCommands.killPane(fork.tmuxPaneId);
967
+ }
968
+ fork.status = "saved";
969
+ fork.tmuxPaneId = void 0;
970
+ session.lastActivity = (/* @__PURE__ */ new Date()).toISOString();
971
+ await this.stateManager.replaceSession(session);
972
+ logger.info(`Fork closed: ${fork.name}`);
973
+ }
974
+ /**
975
+ * Eliminar un fork permanentemente
976
+ */
977
+ async deleteFork(sessionId, forkId) {
978
+ const session = await this.getSession(sessionId);
979
+ if (!session) {
980
+ throw new Error(`Session ${sessionId} not found`);
981
+ }
982
+ const forkIndex = session.forks.findIndex((f) => f.id === forkId);
983
+ if (forkIndex === -1) {
984
+ throw new Error(`Fork ${forkId} not found`);
985
+ }
986
+ const fork = session.forks[forkIndex];
987
+ logger.info(`Deleting fork: ${fork.name}`);
988
+ if (fork.status === "active") {
989
+ await this.closeFork(sessionId, forkId);
990
+ }
991
+ session.forks.splice(forkIndex, 1);
992
+ session.lastActivity = (/* @__PURE__ */ new Date()).toISOString();
993
+ await this.stateManager.replaceSession(session);
994
+ logger.info(`Fork deleted: ${fork.name}`);
995
+ }
996
+ // ==========================================
997
+ // COMANDOS
998
+ // ==========================================
999
+ /**
1000
+ * Enviar comando a main
1001
+ */
1002
+ async sendToMain(sessionId, command) {
1003
+ const session = await this.getSession(sessionId);
1004
+ if (!session) {
1005
+ throw new Error(`Session ${sessionId} not found`);
1006
+ }
1007
+ if (!session.main.tmuxPaneId) {
1008
+ throw new Error("Main pane is not active");
1009
+ }
1010
+ logger.info(`Sending command to main: ${command}`);
1011
+ await TmuxCommands.sendKeys(session.main.tmuxPaneId, command);
1012
+ await TmuxCommands.sendEnter(session.main.tmuxPaneId);
1013
+ session.lastActivity = (/* @__PURE__ */ new Date()).toISOString();
1014
+ await this.stateManager.replaceSession(session);
1015
+ }
1016
+ /**
1017
+ * Enviar comando a un fork
1018
+ */
1019
+ async sendToFork(sessionId, forkId, command) {
1020
+ const session = await this.getSession(sessionId);
1021
+ if (!session) {
1022
+ throw new Error(`Session ${sessionId} not found`);
1023
+ }
1024
+ const fork = session.forks.find((f) => f.id === forkId);
1025
+ if (!fork) {
1026
+ throw new Error(`Fork ${forkId} not found`);
1027
+ }
1028
+ if (!fork.tmuxPaneId) {
1029
+ throw new Error("Fork pane is not active");
1030
+ }
1031
+ logger.info(`Sending command to fork ${fork.name}: ${command}`);
1032
+ await TmuxCommands.sendKeys(fork.tmuxPaneId, command);
1033
+ await TmuxCommands.sendEnter(fork.tmuxPaneId);
1034
+ session.lastActivity = (/* @__PURE__ */ new Date()).toISOString();
1035
+ await this.stateManager.replaceSession(session);
1036
+ }
1037
+ // ==========================================
1038
+ // EXPORT & MERGE
1039
+ // ==========================================
1040
+ /**
1041
+ * Generar export de un fork con resumen
1042
+ * Envía un prompt a Claude pidiendo que genere resumen y exporte
1043
+ */
1044
+ async generateForkExport(sessionId, forkId) {
1045
+ const session = await this.getSession(sessionId);
1046
+ if (!session) {
1047
+ throw new Error(`Session ${sessionId} not found`);
1048
+ }
1049
+ const fork = session.forks.find((f) => f.id === forkId);
1050
+ if (!fork) {
1051
+ throw new Error(`Fork ${forkId} not found`);
1052
+ }
1053
+ logger.info(`Generating export for fork: ${fork.name}`);
1054
+ const exportsDir = path3.join(this.projectPath, ".claude-orka", "exports");
1055
+ await fs3.ensureDir(exportsDir);
1056
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1057
+ const exportName = `fork-${fork.name}-${timestamp}.md`;
1058
+ const relativeExportPath = `.claude-orka/exports/${exportName}`;
1059
+ const prompt = `
1060
+ Please generate a complete summary of this fork conversation "${fork.name}" and save it to the file:
1061
+ \`${relativeExportPath}\`
1062
+
1063
+ The summary should include:
1064
+
1065
+ ## Executive Summary
1066
+ - What was attempted to achieve in this fork
1067
+ - Why this exploration branch was created
1068
+
1069
+ ## Changes Made
1070
+ - Detailed list of changes, modified files, written code
1071
+ - Technical decisions made
1072
+
1073
+ ## Results
1074
+ - What works correctly
1075
+ - What problems were encountered
1076
+ - What remains pending
1077
+
1078
+ ## Recommendations
1079
+ - Suggested next steps
1080
+ - How to integrate this to main
1081
+ - Important considerations
1082
+
1083
+ Write the summary in Markdown format and save it to the specified file.
1084
+ `.trim();
1085
+ if (!fork.tmuxPaneId) {
1086
+ throw new Error("Fork pane is not active. Cannot send export command.");
1087
+ }
1088
+ await TmuxCommands.sendKeys(fork.tmuxPaneId, prompt);
1089
+ await TmuxCommands.sendEnter(fork.tmuxPaneId);
1090
+ fork.contextPath = relativeExportPath;
1091
+ await this.stateManager.replaceSession(session);
1092
+ logger.info(`Export generation requested. Path: ${relativeExportPath}`);
1093
+ logger.warn("IMPORTANT: Wait for Claude to complete before calling merge()");
1094
+ return relativeExportPath;
1095
+ }
1096
+ /**
1097
+ * Hacer merge de un fork a main
1098
+ * PREREQUISITO: Debes llamar a generateForkExport() primero y esperar
1099
+ */
1100
+ async mergeFork(sessionId, forkId) {
1101
+ const session = await this.getSession(sessionId);
1102
+ if (!session) {
1103
+ throw new Error(`Session ${sessionId} not found`);
1104
+ }
1105
+ const fork = session.forks.find((f) => f.id === forkId);
1106
+ if (!fork) {
1107
+ throw new Error(`Fork ${forkId} not found`);
1108
+ }
1109
+ if (!fork.contextPath) {
1110
+ throw new Error(
1111
+ "Fork does not have an exported context. Call generateForkExport() first."
1112
+ );
1113
+ }
1114
+ logger.info(`Merging fork ${fork.name} to main`);
1115
+ const fullPath = path3.join(this.projectPath, fork.contextPath);
1116
+ const exists = await fs3.pathExists(fullPath);
1117
+ if (!exists) {
1118
+ throw new Error(
1119
+ `Export file not found: ${fork.contextPath}. Make sure generateForkExport() completed.`
1120
+ );
1121
+ }
1122
+ const mergePrompt = `
1123
+ I have completed work on the fork "${fork.name}".
1124
+ Please read the file \`${fork.contextPath}\` which contains:
1125
+ 1. An executive summary of the work completed
1126
+ 2. The complete context of the fork conversation
1127
+
1128
+ Analyze the content and help me integrate the changes and learnings from the fork into this main conversation.
1129
+ `.trim();
1130
+ if (!session.main.tmuxPaneId) {
1131
+ throw new Error("Main pane is not active. Cannot send merge command.");
1132
+ }
1133
+ await TmuxCommands.sendKeys(session.main.tmuxPaneId, mergePrompt);
1134
+ await TmuxCommands.sendEnter(session.main.tmuxPaneId);
1135
+ fork.status = "merged";
1136
+ fork.mergedToMain = true;
1137
+ fork.mergedAt = (/* @__PURE__ */ new Date()).toISOString();
1138
+ if (fork.tmuxPaneId) {
1139
+ await TmuxCommands.killPane(fork.tmuxPaneId);
1140
+ fork.tmuxPaneId = void 0;
1141
+ }
1142
+ session.lastActivity = (/* @__PURE__ */ new Date()).toISOString();
1143
+ await this.stateManager.replaceSession(session);
1144
+ logger.info(`Fork ${fork.name} merged to main`);
1145
+ }
1146
+ /**
1147
+ * Export manual de un fork (deprecated - usa generateForkExport)
1148
+ */
1149
+ async exportFork(sessionId, forkId) {
1150
+ logger.warn("exportFork() is deprecated. Use generateForkExport() instead.");
1151
+ return await this.generateForkExport(sessionId, forkId);
1152
+ }
1153
+ // ==========================================
1154
+ // HELPERS PRIVADOS
1155
+ // ==========================================
1156
+ /**
1157
+ * Initialize Claude en un pane con prompt inicial
1158
+ */
1159
+ async initializeClaude(paneId, options) {
1160
+ const { type, sessionId, resumeSessionId, parentSessionId, sessionName, forkName } = options;
1161
+ await TmuxCommands.sendKeys(paneId, `cd ${this.projectPath}`);
1162
+ await TmuxCommands.sendEnter(paneId);
1163
+ await sleep(500);
1164
+ let command = "";
1165
+ switch (type) {
1166
+ case "new":
1167
+ const newPrompt = `Hello, this is a new main session called "${sessionName}". We are working on the project.`;
1168
+ command = `claude --session-id ${sessionId} "${newPrompt}"`;
1169
+ break;
1170
+ case "resume":
1171
+ const resumePrompt = `Resuming session "${sessionName}".`;
1172
+ command = `claude --resume ${resumeSessionId} "${resumePrompt}"`;
1173
+ break;
1174
+ case "fork":
1175
+ const forkPrompt = `This is a fork called "${forkName}". Keep in mind we are exploring an alternative to the main conversation.`;
1176
+ command = `claude --resume ${parentSessionId} --fork-session "${forkPrompt}"`;
1177
+ break;
1178
+ }
1179
+ logger.info(`Executing: ${command}`);
1180
+ await TmuxCommands.sendKeys(paneId, command);
1181
+ await TmuxCommands.sendEnter(paneId);
1182
+ await sleep(8e3);
1183
+ }
1184
+ };
1185
+
1186
+ // src/core/ClaudeOrka.ts
1187
+ var ClaudeOrka = class {
1188
+ sessionManager;
1189
+ /**
1190
+ * Create a ClaudeOrka instance
1191
+ * @param projectPath Absolute path to the project
1192
+ */
1193
+ constructor(projectPath) {
1194
+ this.sessionManager = new SessionManager(projectPath);
1195
+ }
1196
+ /**
1197
+ * Initialize ClaudeOrka
1198
+ * Creates the .claude-orka/ structure if it doesn't exist
1199
+ */
1200
+ async initialize() {
1201
+ logger.info("Initializing ClaudeOrka");
1202
+ await this.sessionManager.initialize();
1203
+ }
1204
+ // --- SESSIONS ---
1205
+ /**
1206
+ * Create a new Claude Code session
1207
+ * @param name Optional name for the session
1208
+ * @param openTerminal Whether to open a terminal window (default: true)
1209
+ * @returns Created session
1210
+ */
1211
+ async createSession(name, openTerminal) {
1212
+ return await this.sessionManager.createSession(name, openTerminal);
1213
+ }
1214
+ /**
1215
+ * Resume a saved session
1216
+ * @param sessionId Session ID to resume
1217
+ * @param openTerminal Whether to open a terminal window (default: true)
1218
+ * @returns Resumed session
1219
+ */
1220
+ async resumeSession(sessionId, openTerminal) {
1221
+ return await this.sessionManager.resumeSession(sessionId, openTerminal);
1222
+ }
1223
+ /**
1224
+ * Close a session
1225
+ * @param sessionId Session ID
1226
+ */
1227
+ async closeSession(sessionId) {
1228
+ await this.sessionManager.closeSession(sessionId);
1229
+ }
1230
+ /**
1231
+ * Permanently delete a session
1232
+ * @param sessionId Session ID
1233
+ */
1234
+ async deleteSession(sessionId) {
1235
+ await this.sessionManager.deleteSession(sessionId);
1236
+ }
1237
+ /**
1238
+ * List sessions with optional filters
1239
+ * @param filters Optional filters (status, name)
1240
+ * @returns Array of sessions
1241
+ */
1242
+ async listSessions(filters) {
1243
+ return await this.sessionManager.listSessions(filters);
1244
+ }
1245
+ /**
1246
+ * Get a session by ID
1247
+ * @param sessionId Session ID
1248
+ * @returns Session or null if not found
1249
+ */
1250
+ async getSession(sessionId) {
1251
+ return await this.sessionManager.getSession(sessionId);
1252
+ }
1253
+ /**
1254
+ * Get complete project summary
1255
+ * Includes statistics of all sessions and their forks
1256
+ * @returns Project summary with all sessions and statistics
1257
+ */
1258
+ async getProjectSummary() {
1259
+ const sessions = await this.sessionManager.listSessions();
1260
+ const state = await this.sessionManager.getState();
1261
+ const sessionSummaries = sessions.map((session) => {
1262
+ const forkSummaries = session.forks.map((fork) => ({
1263
+ id: fork.id,
1264
+ name: fork.name,
1265
+ claudeSessionId: fork.claudeSessionId,
1266
+ status: fork.status,
1267
+ createdAt: fork.createdAt,
1268
+ hasContext: !!fork.contextPath,
1269
+ contextPath: fork.contextPath,
1270
+ mergedToMain: fork.mergedToMain || false,
1271
+ mergedAt: fork.mergedAt
1272
+ }));
1273
+ const activeForks = session.forks.filter((f) => f.status === "active").length;
1274
+ const savedForks = session.forks.filter((f) => f.status === "saved").length;
1275
+ const mergedForks = session.forks.filter((f) => f.status === "merged").length;
1276
+ return {
1277
+ id: session.id,
1278
+ name: session.name,
1279
+ claudeSessionId: session.main.claudeSessionId,
1280
+ status: session.status,
1281
+ createdAt: session.createdAt,
1282
+ lastActivity: session.lastActivity,
1283
+ totalForks: session.forks.length,
1284
+ activeForks,
1285
+ savedForks,
1286
+ mergedForks,
1287
+ forks: forkSummaries
1288
+ };
1289
+ });
1290
+ const activeSessions = sessions.filter((s) => s.status === "active").length;
1291
+ const savedSessions = sessions.filter((s) => s.status === "saved").length;
1292
+ return {
1293
+ projectPath: state.projectPath,
1294
+ totalSessions: sessions.length,
1295
+ activeSessions,
1296
+ savedSessions,
1297
+ sessions: sessionSummaries,
1298
+ lastUpdated: state.lastUpdated
1299
+ };
1300
+ }
1301
+ // --- FORKS ---
1302
+ /**
1303
+ * Create a fork (conversation branch)
1304
+ * @param sessionId Session ID
1305
+ * @param name Optional fork name
1306
+ * @param vertical Whether to split vertically (default: false = horizontal)
1307
+ * @returns Created fork
1308
+ */
1309
+ async createFork(sessionId, name, vertical) {
1310
+ return await this.sessionManager.createFork(sessionId, name, vertical);
1311
+ }
1312
+ /**
1313
+ * Close a fork
1314
+ * @param sessionId Session ID
1315
+ * @param forkId Fork ID
1316
+ */
1317
+ async closeFork(sessionId, forkId) {
1318
+ await this.sessionManager.closeFork(sessionId, forkId);
1319
+ }
1320
+ /**
1321
+ * Resume a saved fork
1322
+ * @param sessionId Session ID
1323
+ * @param forkId Fork ID
1324
+ * @returns Resumed fork
1325
+ */
1326
+ async resumeFork(sessionId, forkId) {
1327
+ return await this.sessionManager.resumeFork(sessionId, forkId);
1328
+ }
1329
+ /**
1330
+ * Permanently delete a fork
1331
+ * @param sessionId Session ID
1332
+ * @param forkId Fork ID
1333
+ */
1334
+ async deleteFork(sessionId, forkId) {
1335
+ await this.sessionManager.deleteFork(sessionId, forkId);
1336
+ }
1337
+ // --- COMANDOS ---
1338
+ /**
1339
+ * Send command to a session or fork
1340
+ * @param sessionId Session ID
1341
+ * @param command Command to send
1342
+ * @param target Fork ID (optional, if not specified goes to main)
1343
+ */
1344
+ async send(sessionId, command, target) {
1345
+ if (target) {
1346
+ await this.sessionManager.sendToFork(sessionId, target, command);
1347
+ } else {
1348
+ await this.sessionManager.sendToMain(sessionId, command);
1349
+ }
1350
+ }
1351
+ // --- EXPORT & MERGE ---
1352
+ /**
1353
+ * Export fork context (old method - uses manual capture)
1354
+ * @deprecated Use generateForkExport() instead for Claude to generate the summary
1355
+ * @param sessionId Session ID
1356
+ * @param forkId Fork ID
1357
+ * @returns Path to the exported file
1358
+ */
1359
+ async export(sessionId, forkId) {
1360
+ return await this.sessionManager.exportFork(sessionId, forkId);
1361
+ }
1362
+ /**
1363
+ * Generate fork export with summary
1364
+ *
1365
+ * Sends a prompt to Claude requesting:
1366
+ * 1. Generate executive summary of the conversation
1367
+ * 2. Export using /export to the specified path
1368
+ *
1369
+ * IMPORTANT: This method is async but returns immediately.
1370
+ * Claude will execute tasks in the background. Wait a few seconds before calling merge().
1371
+ *
1372
+ * @param sessionId Session ID
1373
+ * @param forkId Fork ID
1374
+ * @returns Path where Claude will save the export
1375
+ */
1376
+ async generateForkExport(sessionId, forkId) {
1377
+ return await this.sessionManager.generateForkExport(sessionId, forkId);
1378
+ }
1379
+ /**
1380
+ * Merge a fork to main
1381
+ *
1382
+ * PREREQUISITE: You must call generateForkExport() first and wait for Claude to complete
1383
+ *
1384
+ * @param sessionId Session ID
1385
+ * @param forkId Fork ID
1386
+ */
1387
+ async merge(sessionId, forkId) {
1388
+ await this.sessionManager.mergeFork(sessionId, forkId);
1389
+ }
1390
+ /**
1391
+ * Generate export and merge a fork to main (recommended method)
1392
+ *
1393
+ * Workflow:
1394
+ * 1. Generates export with summary (Claude does the work)
1395
+ * 2. Wait for the file to be created
1396
+ * 3. Merge to main
1397
+ *
1398
+ * @param sessionId Session ID
1399
+ * @param forkId Fork ID
1400
+ * @param waitTime Wait time in ms for Claude to complete (default: 15000)
1401
+ */
1402
+ async generateExportAndMerge(sessionId, forkId, waitTime = 15e3) {
1403
+ await this.generateForkExport(sessionId, forkId);
1404
+ logger.info(`Waiting ${waitTime}ms for Claude to complete export...`);
1405
+ await new Promise((resolve) => setTimeout(resolve, waitTime));
1406
+ await this.merge(sessionId, forkId);
1407
+ }
1408
+ /**
1409
+ * Generate export, merge and close a fork (complete flow)
1410
+ *
1411
+ * @param sessionId Session ID
1412
+ * @param forkId Fork ID
1413
+ * @param waitTime Wait time in ms for Claude to complete (default: 15000)
1414
+ */
1415
+ async generateExportMergeAndClose(sessionId, forkId, waitTime = 15e3) {
1416
+ await this.generateExportAndMerge(sessionId, forkId, waitTime);
1417
+ }
1418
+ /**
1419
+ * Export and merge a fork to main (old method)
1420
+ * @deprecated Usa generateExportAndMerge() en su lugar
1421
+ */
1422
+ async exportAndMerge(sessionId, forkId) {
1423
+ await this.export(sessionId, forkId);
1424
+ await this.merge(sessionId, forkId);
1425
+ }
1426
+ /**
1427
+ * Export, merge and close a fork (old method)
1428
+ * @deprecated Usa generateExportMergeAndClose() en su lugar
1429
+ */
1430
+ async mergeAndClose(sessionId, forkId) {
1431
+ await this.exportAndMerge(sessionId, forkId);
1432
+ await this.closeFork(sessionId, forkId);
1433
+ }
1434
+ };
1435
+
1436
+ // src/cli/utils/output.ts
1437
+ import chalk from "chalk";
1438
+ import Table from "cli-table3";
1439
+ var Output = class {
1440
+ /**
1441
+ * Success message
1442
+ */
1443
+ static success(message) {
1444
+ console.log(chalk.green("\u2713"), message);
1445
+ }
1446
+ /**
1447
+ * Error message
1448
+ */
1449
+ static error(message) {
1450
+ console.error(chalk.red("\u2717"), message);
1451
+ }
1452
+ /**
1453
+ * Warning message
1454
+ */
1455
+ static warn(message) {
1456
+ console.warn(chalk.yellow("\u26A0"), message);
1457
+ }
1458
+ /**
1459
+ * Info message
1460
+ */
1461
+ static info(message) {
1462
+ console.log(chalk.blue("\u2139"), message);
1463
+ }
1464
+ /**
1465
+ * Header
1466
+ */
1467
+ static header(message) {
1468
+ console.log("\n" + chalk.bold.cyan(message));
1469
+ console.log(chalk.gray("\u2500".repeat(message.length)));
1470
+ }
1471
+ /**
1472
+ * Section
1473
+ */
1474
+ static section(title) {
1475
+ console.log("\n" + chalk.bold(title));
1476
+ }
1477
+ /**
1478
+ * Display session details
1479
+ */
1480
+ static session(session) {
1481
+ const statusColor = session.status === "active" ? chalk.green : chalk.yellow;
1482
+ const statusEmoji = session.status === "active" ? "\u2713" : "\u{1F4BE}";
1483
+ console.log(`
1484
+ ${statusEmoji} ${chalk.bold(session.name)}`);
1485
+ console.log(` ${chalk.gray("ID:")} ${session.id}`);
1486
+ console.log(` ${chalk.gray("Claude Session:")} ${session.main.claudeSessionId}`);
1487
+ console.log(` ${chalk.gray("Status:")} ${statusColor(session.status)}`);
1488
+ console.log(` ${chalk.gray("Created:")} ${new Date(session.createdAt).toLocaleString()}`);
1489
+ console.log(` ${chalk.gray("Last Activity:")} ${new Date(session.lastActivity).toLocaleString()}`);
1490
+ if (session.status === "active") {
1491
+ console.log(` ${chalk.gray("Tmux Session:")} ${session.tmuxSessionId}`);
1492
+ }
1493
+ if (session.forks.length > 0) {
1494
+ console.log(` ${chalk.gray("Forks:")} ${session.forks.length}`);
1495
+ console.log(
1496
+ ` ${chalk.green("Active:")} ${session.forks.filter((f) => f.status === "active").length}`
1497
+ );
1498
+ console.log(
1499
+ ` ${chalk.yellow("Saved:")} ${session.forks.filter((f) => f.status === "saved").length}`
1500
+ );
1501
+ console.log(
1502
+ ` ${chalk.blue("Merged:")} ${session.forks.filter((f) => f.status === "merged").length}`
1503
+ );
1504
+ }
1505
+ }
1506
+ /**
1507
+ * Display fork details
1508
+ */
1509
+ static fork(fork) {
1510
+ const statusColor = fork.status === "active" ? chalk.green : fork.status === "merged" ? chalk.blue : chalk.yellow;
1511
+ const statusEmoji = fork.status === "active" ? "\u2713" : fork.status === "merged" ? "\u{1F500}" : "\u{1F4BE}";
1512
+ console.log(`
1513
+ ${statusEmoji} ${chalk.bold(fork.name)}`);
1514
+ console.log(` ${chalk.gray("ID:")} ${fork.id}`);
1515
+ console.log(` ${chalk.gray("Claude Session:")} ${fork.claudeSessionId}`);
1516
+ console.log(` ${chalk.gray("Status:")} ${statusColor(fork.status)}`);
1517
+ console.log(` ${chalk.gray("Created:")} ${new Date(fork.createdAt).toLocaleString()}`);
1518
+ if (fork.status === "active" && fork.tmuxPaneId) {
1519
+ console.log(` ${chalk.gray("Tmux Pane:")} ${fork.tmuxPaneId}`);
1520
+ }
1521
+ if (fork.contextPath) {
1522
+ console.log(` ${chalk.gray("Export:")} ${fork.contextPath}`);
1523
+ }
1524
+ if (fork.mergedToMain && fork.mergedAt) {
1525
+ console.log(
1526
+ ` ${chalk.gray("Merged:")} ${new Date(fork.mergedAt).toLocaleString()}`
1527
+ );
1528
+ }
1529
+ }
1530
+ /**
1531
+ * Display sessions table
1532
+ */
1533
+ static sessionsTable(sessions) {
1534
+ if (sessions.length === 0) {
1535
+ this.warn("No sessions found");
1536
+ return;
1537
+ }
1538
+ const table = new Table({
1539
+ head: [
1540
+ chalk.bold("Name"),
1541
+ chalk.bold("Status"),
1542
+ chalk.bold("Forks"),
1543
+ chalk.bold("Created"),
1544
+ chalk.bold("ID")
1545
+ ],
1546
+ colWidths: [25, 12, 20, 20, 40]
1547
+ });
1548
+ for (const session of sessions) {
1549
+ const statusColor = session.status === "active" ? chalk.green : chalk.yellow;
1550
+ const activeForks = session.forks.filter((f) => f.status === "active").length;
1551
+ const savedForks = session.forks.filter((f) => f.status === "saved").length;
1552
+ const mergedForks = session.forks.filter((f) => f.status === "merged").length;
1553
+ table.push([
1554
+ session.name,
1555
+ statusColor(session.status),
1556
+ `${chalk.green(activeForks)}/${chalk.yellow(savedForks)}/${chalk.blue(mergedForks)}`,
1557
+ new Date(session.createdAt).toLocaleDateString(),
1558
+ chalk.gray(session.id.substring(0, 8) + "...")
1559
+ ]);
1560
+ }
1561
+ console.log(table.toString());
1562
+ }
1563
+ /**
1564
+ * Display forks table
1565
+ */
1566
+ static forksTable(forks) {
1567
+ if (forks.length === 0) {
1568
+ this.warn("No forks found");
1569
+ return;
1570
+ }
1571
+ const table = new Table({
1572
+ head: [
1573
+ chalk.bold("Name"),
1574
+ chalk.bold("Status"),
1575
+ chalk.bold("Export"),
1576
+ chalk.bold("Created"),
1577
+ chalk.bold("ID")
1578
+ ],
1579
+ colWidths: [25, 12, 15, 20, 40]
1580
+ });
1581
+ for (const fork of forks) {
1582
+ const statusColor = fork.status === "active" ? chalk.green : fork.status === "merged" ? chalk.blue : chalk.yellow;
1583
+ const hasExport = fork.contextPath ? chalk.green("\u2713") : chalk.gray("\u2717");
1584
+ table.push([
1585
+ fork.name,
1586
+ statusColor(fork.status),
1587
+ hasExport,
1588
+ new Date(fork.createdAt).toLocaleDateString(),
1589
+ chalk.gray(fork.id.substring(0, 8) + "...")
1590
+ ]);
1591
+ }
1592
+ console.log(table.toString());
1593
+ }
1594
+ /**
1595
+ * Display project summary
1596
+ */
1597
+ static projectSummary(summary) {
1598
+ this.header("\u{1F4CA} Project Summary");
1599
+ console.log(`
1600
+ ${chalk.gray("Project Path:")} ${summary.projectPath}`);
1601
+ console.log(`${chalk.gray("Total Sessions:")} ${summary.totalSessions}`);
1602
+ console.log(` ${chalk.green("Active:")} ${summary.activeSessions}`);
1603
+ console.log(` ${chalk.yellow("Saved:")} ${summary.savedSessions}`);
1604
+ console.log(
1605
+ `${chalk.gray("Last Updated:")} ${new Date(summary.lastUpdated).toLocaleString()}`
1606
+ );
1607
+ if (summary.sessions.length === 0) {
1608
+ console.log("\n" + chalk.gray("No sessions available"));
1609
+ return;
1610
+ }
1611
+ this.section("\n\u{1F4DD} Sessions:");
1612
+ for (const session of summary.sessions) {
1613
+ const statusEmoji = session.status === "active" ? "\u2713" : "\u{1F4BE}";
1614
+ const statusColor = session.status === "active" ? chalk.green : chalk.yellow;
1615
+ console.log(`
1616
+ ${statusEmoji} ${chalk.bold(session.name)}`);
1617
+ console.log(` ${chalk.gray("ID:")} ${session.id}`);
1618
+ console.log(` ${chalk.gray("Claude Session:")} ${session.claudeSessionId}`);
1619
+ console.log(` ${chalk.gray("Status:")} ${statusColor(session.status)}`);
1620
+ console.log(
1621
+ ` ${chalk.gray("Created:")} ${new Date(session.createdAt).toLocaleString()}`
1622
+ );
1623
+ console.log(
1624
+ ` ${chalk.gray("Last Activity:")} ${new Date(session.lastActivity).toLocaleString()}`
1625
+ );
1626
+ console.log(` ${chalk.gray("Total Forks:")} ${session.totalForks}`);
1627
+ console.log(
1628
+ ` ${chalk.green("Active:")} ${session.activeForks} | ${chalk.yellow("Saved:")} ${session.savedForks} | ${chalk.blue("Merged:")} ${session.mergedForks}`
1629
+ );
1630
+ if (session.forks.length > 0) {
1631
+ console.log(`
1632
+ ${chalk.bold("Forks:")}`);
1633
+ for (const fork of session.forks) {
1634
+ const forkEmoji = fork.status === "active" ? "\u2713" : fork.status === "merged" ? "\u{1F500}" : "\u{1F4BE}";
1635
+ const forkColor = fork.status === "active" ? chalk.green : fork.status === "merged" ? chalk.blue : chalk.yellow;
1636
+ console.log(`
1637
+ ${forkEmoji} ${chalk.bold(fork.name)}`);
1638
+ console.log(` ${chalk.gray("ID:")} ${fork.id}`);
1639
+ console.log(` ${chalk.gray("Claude Session:")} ${fork.claudeSessionId}`);
1640
+ console.log(` ${chalk.gray("Status:")} ${forkColor(fork.status)}`);
1641
+ console.log(
1642
+ ` ${chalk.gray("Created:")} ${new Date(fork.createdAt).toLocaleString()}`
1643
+ );
1644
+ if (fork.hasContext) {
1645
+ console.log(` ${chalk.gray("Export:")} ${chalk.green("\u2713")} ${fork.contextPath}`);
1646
+ }
1647
+ if (fork.mergedToMain && fork.mergedAt) {
1648
+ console.log(
1649
+ ` ${chalk.gray("Merged:")} ${new Date(fork.mergedAt).toLocaleString()}`
1650
+ );
1651
+ }
1652
+ }
1653
+ }
1654
+ }
1655
+ }
1656
+ /**
1657
+ * Display JSON output
1658
+ */
1659
+ static json(data) {
1660
+ console.log(JSON.stringify(data, null, 2));
1661
+ }
1662
+ /**
1663
+ * Display empty line
1664
+ */
1665
+ static newline() {
1666
+ console.log();
1667
+ }
1668
+ };
1669
+
1670
+ // src/cli/utils/errors.ts
1671
+ import chalk2 from "chalk";
1672
+ import fs4 from "fs";
1673
+ import path4 from "path";
1674
+ var CLIError = class extends Error {
1675
+ constructor(message, exitCode = 1) {
1676
+ super(message);
1677
+ this.exitCode = exitCode;
1678
+ this.name = "CLIError";
1679
+ }
1680
+ };
1681
+ function handleError(error) {
1682
+ if (error instanceof CLIError) {
1683
+ Output.error(error.message);
1684
+ process.exit(error.exitCode);
1685
+ }
1686
+ if (error instanceof Error) {
1687
+ Output.error(`Unexpected error: ${error.message}`);
1688
+ console.error(chalk2.gray(error.stack));
1689
+ process.exit(1);
1690
+ }
1691
+ Output.error("An unknown error occurred");
1692
+ process.exit(1);
1693
+ }
1694
+ function validateSessionId(sessionId) {
1695
+ if (!sessionId || sessionId.length === 0) {
1696
+ throw new CLIError("Session ID is required");
1697
+ }
1698
+ }
1699
+ function validateForkId(forkId) {
1700
+ if (!forkId || forkId.length === 0) {
1701
+ throw new CLIError("Fork ID is required");
1702
+ }
1703
+ }
1704
+ function validateInitialized(projectPath) {
1705
+ const orkaDir = path4.join(projectPath, ".claude-orka");
1706
+ if (!fs4.existsSync(orkaDir)) {
1707
+ throw new CLIError(
1708
+ 'Project not initialized. Run "orka init" first.',
1709
+ 2
1710
+ );
1711
+ }
1712
+ }
1713
+
1714
+ // src/cli/commands/init.ts
1715
+ function initCommand(program2) {
1716
+ program2.command("init").description("Initialize Claude-Orka in the current project").action(async () => {
1717
+ try {
1718
+ const projectPath = process.cwd();
1719
+ Output.info(`Initializing Claude-Orka in: ${projectPath}`);
1720
+ const orka = new ClaudeOrka(projectPath);
1721
+ await orka.initialize();
1722
+ Output.success("Claude-Orka initialized successfully!");
1723
+ Output.info("You can now create sessions with: orka session create");
1724
+ } catch (error) {
1725
+ handleError(error);
1726
+ }
1727
+ });
1728
+ }
1729
+
1730
+ // src/cli/commands/status.ts
1731
+ function statusCommand(program2) {
1732
+ program2.command("status").description("Show project status and all sessions").option("-j, --json", "Output as JSON").action(async (options) => {
1733
+ try {
1734
+ const projectPath = process.cwd();
1735
+ validateInitialized(projectPath);
1736
+ const orka = new ClaudeOrka(projectPath);
1737
+ await orka.initialize();
1738
+ const summary = await orka.getProjectSummary();
1739
+ if (options.json) {
1740
+ Output.json(summary);
1741
+ } else {
1742
+ Output.projectSummary(summary);
1743
+ }
1744
+ } catch (error) {
1745
+ handleError(error);
1746
+ }
1747
+ });
1748
+ }
1749
+
1750
+ // src/cli/commands/session.ts
1751
+ import ora from "ora";
1752
+ function sessionCommand(program2) {
1753
+ const session = program2.command("session").description("Manage Claude sessions");
1754
+ session.command("create [name]").description("Create a new Claude session").option("--no-terminal", "Do not open terminal window").action(async (name, options) => {
1755
+ try {
1756
+ const projectPath = process.cwd();
1757
+ validateInitialized(projectPath);
1758
+ const orka = new ClaudeOrka(projectPath);
1759
+ await orka.initialize();
1760
+ const spinner = ora("Creating session...").start();
1761
+ const newSession = await orka.createSession(name, options.terminal);
1762
+ spinner.succeed("Session created!");
1763
+ Output.session(newSession);
1764
+ Output.newline();
1765
+ Output.info(`You can now interact with Claude in the tmux window.`);
1766
+ Output.info(`To create a fork: orka fork create ${newSession.id}`);
1767
+ } catch (error) {
1768
+ handleError(error);
1769
+ }
1770
+ });
1771
+ session.command("list").description("List all sessions").option("-s, --status <status>", "Filter by status (active, saved)").option("-j, --json", "Output as JSON").action(async (options) => {
1772
+ try {
1773
+ const projectPath = process.cwd();
1774
+ validateInitialized(projectPath);
1775
+ const orka = new ClaudeOrka(projectPath);
1776
+ await orka.initialize();
1777
+ const filters = options.status ? { status: options.status } : void 0;
1778
+ const sessions = await orka.listSessions(filters);
1779
+ if (options.json) {
1780
+ Output.json(sessions);
1781
+ } else {
1782
+ Output.header("\u{1F4CB} Sessions");
1783
+ Output.sessionsTable(sessions);
1784
+ }
1785
+ } catch (error) {
1786
+ handleError(error);
1787
+ }
1788
+ });
1789
+ session.command("get <session-id>").description("Get session details").option("-j, --json", "Output as JSON").action(async (sessionId, options) => {
1790
+ try {
1791
+ const projectPath = process.cwd();
1792
+ validateInitialized(projectPath);
1793
+ validateSessionId(sessionId);
1794
+ const orka = new ClaudeOrka(projectPath);
1795
+ await orka.initialize();
1796
+ const session2 = await orka.getSession(sessionId);
1797
+ if (!session2) {
1798
+ Output.error(`Session not found: ${sessionId}`);
1799
+ process.exit(1);
1800
+ }
1801
+ if (options.json) {
1802
+ Output.json(session2);
1803
+ } else {
1804
+ Output.session(session2);
1805
+ if (session2.forks.length > 0) {
1806
+ Output.section("\n\u{1F33F} Forks:");
1807
+ for (const fork of session2.forks) {
1808
+ Output.fork(fork);
1809
+ }
1810
+ }
1811
+ }
1812
+ } catch (error) {
1813
+ handleError(error);
1814
+ }
1815
+ });
1816
+ session.command("resume <session-id>").description("Resume a saved session").option("--no-terminal", "Do not open terminal window").action(async (sessionId, options) => {
1817
+ try {
1818
+ const projectPath = process.cwd();
1819
+ validateInitialized(projectPath);
1820
+ validateSessionId(sessionId);
1821
+ const orka = new ClaudeOrka(projectPath);
1822
+ await orka.initialize();
1823
+ const spinner = ora("Resuming session...").start();
1824
+ const resumedSession = await orka.resumeSession(sessionId, options.terminal);
1825
+ spinner.succeed("Session resumed!");
1826
+ Output.session(resumedSession);
1827
+ Output.newline();
1828
+ Output.info("Session and all saved forks have been restored.");
1829
+ Output.info("Claude will remember the context of all conversations.");
1830
+ if (resumedSession.forks.length > 0) {
1831
+ const activeForks = resumedSession.forks.filter((f) => f.status === "active");
1832
+ if (activeForks.length > 0) {
1833
+ Output.newline();
1834
+ Output.section("Restored forks:");
1835
+ for (const fork of activeForks) {
1836
+ Output.fork(fork);
1837
+ }
1838
+ }
1839
+ }
1840
+ } catch (error) {
1841
+ handleError(error);
1842
+ }
1843
+ });
1844
+ session.command("close <session-id>").description("Close a session (saves it for later)").action(async (sessionId) => {
1845
+ try {
1846
+ const projectPath = process.cwd();
1847
+ validateInitialized(projectPath);
1848
+ validateSessionId(sessionId);
1849
+ const orka = new ClaudeOrka(projectPath);
1850
+ await orka.initialize();
1851
+ const spinner = ora("Closing session...").start();
1852
+ await orka.closeSession(sessionId);
1853
+ spinner.succeed("Session closed!");
1854
+ Output.info("Session has been saved. You can resume it later with:");
1855
+ Output.info(` orka session resume ${sessionId}`);
1856
+ } catch (error) {
1857
+ handleError(error);
1858
+ }
1859
+ });
1860
+ session.command("delete <session-id>").description("Permanently delete a session").action(async (sessionId) => {
1861
+ try {
1862
+ const projectPath = process.cwd();
1863
+ validateInitialized(projectPath);
1864
+ validateSessionId(sessionId);
1865
+ const orka = new ClaudeOrka(projectPath);
1866
+ await orka.initialize();
1867
+ const spinner = ora("Deleting session...").start();
1868
+ await orka.deleteSession(sessionId);
1869
+ spinner.succeed("Session deleted!");
1870
+ Output.warn("Session has been permanently deleted.");
1871
+ } catch (error) {
1872
+ handleError(error);
1873
+ }
1874
+ });
1875
+ }
1876
+
1877
+ // src/cli/commands/fork.ts
1878
+ import ora2 from "ora";
1879
+ function forkCommand(program2) {
1880
+ const fork = program2.command("fork").description("Manage conversation forks");
1881
+ fork.command("create <session-id> [name]").description("Create a new fork in a session").option("-v, --vertical", "Split vertically instead of horizontally").action(async (sessionId, name, options) => {
1882
+ try {
1883
+ const projectPath = process.cwd();
1884
+ validateInitialized(projectPath);
1885
+ validateSessionId(sessionId);
1886
+ const orka = new ClaudeOrka(projectPath);
1887
+ await orka.initialize();
1888
+ const spinner = ora2("Creating fork...").start();
1889
+ spinner.text = "Creating fork and detecting session ID...";
1890
+ const newFork = await orka.createFork(sessionId, name, options.vertical);
1891
+ spinner.succeed("Fork created!");
1892
+ Output.fork(newFork);
1893
+ Output.newline();
1894
+ Output.info("Fork created in a new tmux pane.");
1895
+ Output.info("You can now explore an alternative approach in this fork.");
1896
+ Output.newline();
1897
+ Output.info(`To merge this fork: orka merge ${sessionId} ${newFork.id}`);
1898
+ } catch (error) {
1899
+ handleError(error);
1900
+ }
1901
+ });
1902
+ fork.command("list <session-id>").description("List all forks in a session").option("-s, --status <status>", "Filter by status (active, saved, merged)").option("-j, --json", "Output as JSON").action(async (sessionId, options) => {
1903
+ try {
1904
+ const projectPath = process.cwd();
1905
+ validateInitialized(projectPath);
1906
+ validateSessionId(sessionId);
1907
+ const orka = new ClaudeOrka(projectPath);
1908
+ await orka.initialize();
1909
+ const session = await orka.getSession(sessionId);
1910
+ if (!session) {
1911
+ Output.error(`Session not found: ${sessionId}`);
1912
+ process.exit(1);
1913
+ }
1914
+ let forks = session.forks;
1915
+ if (options.status) {
1916
+ forks = forks.filter((f) => f.status === options.status);
1917
+ }
1918
+ if (options.json) {
1919
+ Output.json(forks);
1920
+ } else {
1921
+ Output.header(`\u{1F33F} Forks in ${session.name}`);
1922
+ Output.forksTable(forks);
1923
+ }
1924
+ } catch (error) {
1925
+ handleError(error);
1926
+ }
1927
+ });
1928
+ fork.command("resume <session-id> <fork-id>").description("Resume a saved fork").action(async (sessionId, forkId) => {
1929
+ try {
1930
+ const projectPath = process.cwd();
1931
+ validateInitialized(projectPath);
1932
+ validateSessionId(sessionId);
1933
+ validateForkId(forkId);
1934
+ const orka = new ClaudeOrka(projectPath);
1935
+ await orka.initialize();
1936
+ const spinner = ora2("Resuming fork...").start();
1937
+ const resumedFork = await orka.resumeFork(sessionId, forkId);
1938
+ spinner.succeed("Fork resumed!");
1939
+ Output.fork(resumedFork);
1940
+ Output.newline();
1941
+ Output.info("Fork has been restored in a new tmux pane.");
1942
+ Output.info("Claude will remember the context of this conversation.");
1943
+ } catch (error) {
1944
+ handleError(error);
1945
+ }
1946
+ });
1947
+ fork.command("close <session-id> <fork-id>").description("Close a fork (saves it for later)").action(async (sessionId, forkId) => {
1948
+ try {
1949
+ const projectPath = process.cwd();
1950
+ validateInitialized(projectPath);
1951
+ validateSessionId(sessionId);
1952
+ validateForkId(forkId);
1953
+ const orka = new ClaudeOrka(projectPath);
1954
+ await orka.initialize();
1955
+ const spinner = ora2("Closing fork...").start();
1956
+ await orka.closeFork(sessionId, forkId);
1957
+ spinner.succeed("Fork closed!");
1958
+ Output.info("Fork has been saved. You can resume it later with:");
1959
+ Output.info(` orka fork resume ${sessionId} ${forkId}`);
1960
+ } catch (error) {
1961
+ handleError(error);
1962
+ }
1963
+ });
1964
+ fork.command("delete <session-id> <fork-id>").description("Permanently delete a fork").action(async (sessionId, forkId) => {
1965
+ try {
1966
+ const projectPath = process.cwd();
1967
+ validateInitialized(projectPath);
1968
+ validateSessionId(sessionId);
1969
+ validateForkId(forkId);
1970
+ const orka = new ClaudeOrka(projectPath);
1971
+ await orka.initialize();
1972
+ const spinner = ora2("Deleting fork...").start();
1973
+ await orka.deleteFork(sessionId, forkId);
1974
+ spinner.succeed("Fork deleted!");
1975
+ Output.warn("Fork has been permanently deleted.");
1976
+ } catch (error) {
1977
+ handleError(error);
1978
+ }
1979
+ });
1980
+ }
1981
+
1982
+ // src/cli/commands/merge.ts
1983
+ import ora3 from "ora";
1984
+ function mergeCommand(program2) {
1985
+ const merge = program2.command("merge").description("Merge and export operations");
1986
+ merge.command("export <session-id> <fork-id>").description("Generate export summary for a fork").action(async (sessionId, forkId) => {
1987
+ try {
1988
+ const projectPath = process.cwd();
1989
+ validateInitialized(projectPath);
1990
+ validateSessionId(sessionId);
1991
+ validateForkId(forkId);
1992
+ const orka = new ClaudeOrka(projectPath);
1993
+ await orka.initialize();
1994
+ Output.info("Generating export summary...");
1995
+ Output.warn("This will send a prompt to Claude to generate a summary.");
1996
+ Output.warn("Wait for Claude to finish before running the merge command.");
1997
+ const exportPath = await orka.generateForkExport(sessionId, forkId);
1998
+ Output.success("Export prompt sent to Claude!");
1999
+ Output.info(`Export will be saved to: ${exportPath}`);
2000
+ Output.newline();
2001
+ Output.info("Next steps:");
2002
+ Output.info(" 1. Wait for Claude to generate and save the summary (~15-30 seconds)");
2003
+ Output.info(" 2. Run: orka merge do " + sessionId + " " + forkId);
2004
+ } catch (error) {
2005
+ handleError(error);
2006
+ }
2007
+ });
2008
+ merge.command("do <session-id> <fork-id>").description("Merge a fork to main (requires export first)").action(async (sessionId, forkId) => {
2009
+ try {
2010
+ const projectPath = process.cwd();
2011
+ validateInitialized(projectPath);
2012
+ validateSessionId(sessionId);
2013
+ validateForkId(forkId);
2014
+ const orka = new ClaudeOrka(projectPath);
2015
+ await orka.initialize();
2016
+ const spinner = ora3("Merging fork to main...").start();
2017
+ await orka.merge(sessionId, forkId);
2018
+ spinner.succeed("Fork merged to main!");
2019
+ Output.info("The fork context has been sent to the main conversation.");
2020
+ Output.info("Claude in main now has access to the fork exploration.");
2021
+ Output.warn("Fork has been closed and marked as merged.");
2022
+ } catch (error) {
2023
+ handleError(error);
2024
+ }
2025
+ });
2026
+ merge.command("auto <session-id> <fork-id>").description("Generate export and merge automatically").option("-w, --wait <ms>", "Wait time in milliseconds for Claude to complete export", "15000").action(async (sessionId, forkId, options) => {
2027
+ try {
2028
+ const projectPath = process.cwd();
2029
+ validateInitialized(projectPath);
2030
+ validateSessionId(sessionId);
2031
+ validateForkId(forkId);
2032
+ const orka = new ClaudeOrka(projectPath);
2033
+ await orka.initialize();
2034
+ const waitTime = parseInt(options.wait);
2035
+ Output.info("Starting automatic export and merge...");
2036
+ const spinner = ora3("Generating export summary...").start();
2037
+ await orka.generateExportAndMerge(sessionId, forkId, waitTime);
2038
+ spinner.succeed("Fork merged to main!");
2039
+ Output.info("The fork context has been sent to the main conversation.");
2040
+ Output.info("Claude in main now has access to the fork exploration.");
2041
+ Output.warn("Fork has been closed and marked as merged.");
2042
+ } catch (error) {
2043
+ handleError(error);
2044
+ }
2045
+ });
2046
+ }
2047
+
2048
+ // src/cli/commands/doctor.ts
2049
+ import execa2 from "execa";
2050
+ import fs5 from "fs-extra";
2051
+ import path5 from "path";
2052
+ import chalk3 from "chalk";
2053
+ function doctorCommand(program2) {
2054
+ program2.command("doctor").description("Check system dependencies and configuration").action(async () => {
2055
+ try {
2056
+ console.log(chalk3.bold.cyan("\n\u{1F50D} Claude-Orka Doctor\n"));
2057
+ console.log("Checking system dependencies and configuration...\n");
2058
+ const results = [];
2059
+ results.push(await checkNodeVersion());
2060
+ results.push(await checkTmux());
2061
+ results.push(await checkClaude());
2062
+ results.push(await checkProjectInit());
2063
+ results.push(await checkWritePermissions());
2064
+ results.push(await checkClaudeDir());
2065
+ displayResults(results);
2066
+ const criticalFailures = results.filter((r) => r.status === "fail");
2067
+ if (criticalFailures.length > 0) {
2068
+ process.exit(1);
2069
+ }
2070
+ } catch (error) {
2071
+ handleError(error);
2072
+ }
2073
+ });
2074
+ }
2075
+ async function checkNodeVersion() {
2076
+ try {
2077
+ const version = process.version;
2078
+ const major = parseInt(version.slice(1).split(".")[0]);
2079
+ if (major >= 18) {
2080
+ return {
2081
+ name: "Node.js",
2082
+ status: "pass",
2083
+ message: `${version} (>= 18.0.0)`
2084
+ };
2085
+ } else {
2086
+ return {
2087
+ name: "Node.js",
2088
+ status: "fail",
2089
+ message: `${version} (requires >= 18.0.0)`,
2090
+ fix: "Install Node.js 18 or higher from https://nodejs.org"
2091
+ };
2092
+ }
2093
+ } catch (error) {
2094
+ return {
2095
+ name: "Node.js",
2096
+ status: "fail",
2097
+ message: "Not found",
2098
+ fix: "Install Node.js from https://nodejs.org"
2099
+ };
2100
+ }
2101
+ }
2102
+ async function checkTmux() {
2103
+ try {
2104
+ const { stdout } = await execa2("tmux", ["-V"]);
2105
+ const version = stdout.trim();
2106
+ return {
2107
+ name: "tmux",
2108
+ status: "pass",
2109
+ message: version
2110
+ };
2111
+ } catch (error) {
2112
+ return {
2113
+ name: "tmux",
2114
+ status: "fail",
2115
+ message: "Not found",
2116
+ details: "tmux is required for session management",
2117
+ fix: "Install tmux:\n macOS: brew install tmux\n Ubuntu: sudo apt-get install tmux"
2118
+ };
2119
+ }
2120
+ }
2121
+ async function checkClaude() {
2122
+ try {
2123
+ const { stdout } = await execa2("claude", ["--version"]);
2124
+ const version = stdout.trim();
2125
+ return {
2126
+ name: "Claude CLI",
2127
+ status: "pass",
2128
+ message: version
2129
+ };
2130
+ } catch (error) {
2131
+ return {
2132
+ name: "Claude CLI",
2133
+ status: "fail",
2134
+ message: "Not found",
2135
+ details: "Claude CLI is required for AI sessions",
2136
+ fix: "Install Claude CLI from https://claude.ai/download"
2137
+ };
2138
+ }
2139
+ }
2140
+ async function checkProjectInit() {
2141
+ const projectPath = process.cwd();
2142
+ const orkaDir = path5.join(projectPath, ".claude-orka");
2143
+ const stateFile = path5.join(orkaDir, "state.json");
2144
+ try {
2145
+ const dirExists = await fs5.pathExists(orkaDir);
2146
+ const stateExists = await fs5.pathExists(stateFile);
2147
+ if (dirExists && stateExists) {
2148
+ return {
2149
+ name: "Project initialization",
2150
+ status: "pass",
2151
+ message: "Initialized",
2152
+ details: ".claude-orka/ directory and state.json found"
2153
+ };
2154
+ } else if (dirExists) {
2155
+ return {
2156
+ name: "Project initialization",
2157
+ status: "warn",
2158
+ message: "Partially initialized",
2159
+ details: ".claude-orka/ exists but state.json is missing",
2160
+ fix: "Run: orka init"
2161
+ };
2162
+ } else {
2163
+ return {
2164
+ name: "Project initialization",
2165
+ status: "warn",
2166
+ message: "Not initialized",
2167
+ details: "Project is not initialized",
2168
+ fix: "Run: orka init"
2169
+ };
2170
+ }
2171
+ } catch (error) {
2172
+ return {
2173
+ name: "Project initialization",
2174
+ status: "fail",
2175
+ message: "Error checking",
2176
+ details: error.message
2177
+ };
2178
+ }
2179
+ }
2180
+ async function checkWritePermissions() {
2181
+ const projectPath = process.cwd();
2182
+ try {
2183
+ const testFile = path5.join(projectPath, ".claude-orka-write-test");
2184
+ await fs5.writeFile(testFile, "test");
2185
+ await fs5.remove(testFile);
2186
+ return {
2187
+ name: "Write permissions",
2188
+ status: "pass",
2189
+ message: "OK",
2190
+ details: "Can write to project directory"
2191
+ };
2192
+ } catch (error) {
2193
+ return {
2194
+ name: "Write permissions",
2195
+ status: "fail",
2196
+ message: "Cannot write",
2197
+ details: `No write permission in ${projectPath}`,
2198
+ fix: "Check directory permissions"
2199
+ };
2200
+ }
2201
+ }
2202
+ async function checkClaudeDir() {
2203
+ const homeDir = process.env.HOME || process.env.USERPROFILE || "";
2204
+ const claudeDir = path5.join(homeDir, ".claude");
2205
+ const historyFile = path5.join(claudeDir, "history.jsonl");
2206
+ try {
2207
+ const dirExists = await fs5.pathExists(claudeDir);
2208
+ const historyExists = await fs5.pathExists(historyFile);
2209
+ if (dirExists && historyExists) {
2210
+ return {
2211
+ name: "Claude directory",
2212
+ status: "pass",
2213
+ message: "Found",
2214
+ details: `~/.claude/history.jsonl exists`
2215
+ };
2216
+ } else if (dirExists) {
2217
+ return {
2218
+ name: "Claude directory",
2219
+ status: "warn",
2220
+ message: "History file missing",
2221
+ details: "~/.claude/ exists but history.jsonl not found",
2222
+ fix: "Run Claude CLI at least once to create history"
2223
+ };
2224
+ } else {
2225
+ return {
2226
+ name: "Claude directory",
2227
+ status: "warn",
2228
+ message: "Not found",
2229
+ details: "~/.claude/ directory not found",
2230
+ fix: "Run Claude CLI at least once to create the directory"
2231
+ };
2232
+ }
2233
+ } catch (error) {
2234
+ return {
2235
+ name: "Claude directory",
2236
+ status: "fail",
2237
+ message: "Error checking",
2238
+ details: error.message
2239
+ };
2240
+ }
2241
+ }
2242
+ function displayResults(results) {
2243
+ console.log(chalk3.bold("Results:\n"));
2244
+ for (const result of results) {
2245
+ let icon;
2246
+ let color;
2247
+ switch (result.status) {
2248
+ case "pass":
2249
+ icon = "\u2713";
2250
+ color = chalk3.green;
2251
+ break;
2252
+ case "warn":
2253
+ icon = "\u26A0";
2254
+ color = chalk3.yellow;
2255
+ break;
2256
+ case "fail":
2257
+ icon = "\u2717";
2258
+ color = chalk3.red;
2259
+ break;
2260
+ }
2261
+ console.log(
2262
+ `${color(icon)} ${chalk3.bold(result.name)}: ${color(result.message)}`
2263
+ );
2264
+ if (result.details) {
2265
+ console.log(` ${chalk3.gray(result.details)}`);
2266
+ }
2267
+ if (result.fix) {
2268
+ console.log(` ${chalk3.cyan("Fix:")} ${result.fix}`);
2269
+ }
2270
+ console.log();
2271
+ }
2272
+ const passed = results.filter((r) => r.status === "pass").length;
2273
+ const warned = results.filter((r) => r.status === "warn").length;
2274
+ const failed = results.filter((r) => r.status === "fail").length;
2275
+ console.log(chalk3.bold("Summary:"));
2276
+ console.log(` ${chalk3.green("\u2713")} Passed: ${passed}`);
2277
+ console.log(` ${chalk3.yellow("\u26A0")} Warnings: ${warned}`);
2278
+ console.log(` ${chalk3.red("\u2717")} Failed: ${failed}`);
2279
+ console.log();
2280
+ if (failed === 0 && warned === 0) {
2281
+ Output.success("All checks passed! Claude-Orka is ready to use.");
2282
+ } else if (failed === 0) {
2283
+ Output.warn("Some warnings found. Claude-Orka should work but check the warnings above.");
2284
+ } else {
2285
+ Output.error("Some critical checks failed. Fix the errors above before using Claude-Orka.");
2286
+ }
2287
+ }
2288
+
2289
+ // src/cli/commands/prepare.ts
2290
+ import execa3 from "execa";
2291
+ import chalk4 from "chalk";
2292
+ import ora4 from "ora";
2293
+ function prepareCommand(program2) {
2294
+ program2.command("prepare").description("Install and configure system dependencies").option("-y, --yes", "Skip confirmation prompts").action(async (options) => {
2295
+ try {
2296
+ console.log(chalk4.bold.cyan("\n\u{1F527} Claude-Orka Preparation\n"));
2297
+ console.log("This will help you install required dependencies:\n");
2298
+ console.log(" \u2022 tmux (terminal multiplexer)");
2299
+ console.log(" \u2022 Claude CLI (if needed)\n");
2300
+ if (!options.yes) {
2301
+ const readline = __require("readline").createInterface({
2302
+ input: process.stdin,
2303
+ output: process.stdout
2304
+ });
2305
+ const answer = await new Promise((resolve) => {
2306
+ readline.question("Continue? (y/n): ", resolve);
2307
+ });
2308
+ readline.close();
2309
+ if (answer.toLowerCase() !== "y") {
2310
+ Output.warn("Installation cancelled");
2311
+ return;
2312
+ }
2313
+ }
2314
+ const system = await detectSystem();
2315
+ console.log(chalk4.gray(`
2316
+ Detected: ${system.platform}`));
2317
+ await installTmux(system);
2318
+ await checkClaudeCLI();
2319
+ console.log(chalk4.bold.green("\n\u2713 Preparation complete!\n"));
2320
+ console.log("Run " + chalk4.cyan("orka doctor") + " to verify everything is working.");
2321
+ } catch (error) {
2322
+ handleError(error);
2323
+ }
2324
+ });
2325
+ }
2326
+ async function detectSystem() {
2327
+ const platform = process.platform;
2328
+ const info = { platform };
2329
+ if (platform === "darwin") {
2330
+ try {
2331
+ await execa3("which", ["brew"]);
2332
+ info.hasHomebrew = true;
2333
+ info.packageManager = "brew";
2334
+ } catch {
2335
+ info.hasHomebrew = false;
2336
+ }
2337
+ } else if (platform === "linux") {
2338
+ try {
2339
+ await execa3("which", ["apt-get"]);
2340
+ info.hasApt = true;
2341
+ info.packageManager = "apt";
2342
+ } catch {
2343
+ try {
2344
+ await execa3("which", ["yum"]);
2345
+ info.hasYum = true;
2346
+ info.packageManager = "yum";
2347
+ } catch {
2348
+ }
2349
+ }
2350
+ }
2351
+ return info;
2352
+ }
2353
+ async function installTmux(system) {
2354
+ console.log(chalk4.bold("\n\u{1F4E6} Installing tmux...\n"));
2355
+ try {
2356
+ await execa3("which", ["tmux"]);
2357
+ const { stdout } = await execa3("tmux", ["-V"]);
2358
+ Output.success(`tmux is already installed: ${stdout}`);
2359
+ return;
2360
+ } catch {
2361
+ }
2362
+ const spinner = ora4("Installing tmux...").start();
2363
+ try {
2364
+ if (system.platform === "darwin") {
2365
+ if (!system.hasHomebrew) {
2366
+ spinner.fail("Homebrew is not installed");
2367
+ console.log(chalk4.yellow("\nPlease install Homebrew first:"));
2368
+ console.log(
2369
+ chalk4.cyan(
2370
+ '/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'
2371
+ )
2372
+ );
2373
+ console.log("\nThen run: " + chalk4.cyan("brew install tmux"));
2374
+ return;
2375
+ }
2376
+ await execa3("brew", ["install", "tmux"]);
2377
+ spinner.succeed("tmux installed via Homebrew");
2378
+ } else if (system.platform === "linux") {
2379
+ if (system.hasApt) {
2380
+ await execa3("sudo", ["apt-get", "update"]);
2381
+ await execa3("sudo", ["apt-get", "install", "-y", "tmux"]);
2382
+ spinner.succeed("tmux installed via apt");
2383
+ } else if (system.hasYum) {
2384
+ await execa3("sudo", ["yum", "install", "-y", "tmux"]);
2385
+ spinner.succeed("tmux installed via yum");
2386
+ } else {
2387
+ spinner.fail("Unknown package manager");
2388
+ console.log(chalk4.yellow("\nPlease install tmux manually:"));
2389
+ console.log(chalk4.cyan(" https://github.com/tmux/tmux/wiki/Installing"));
2390
+ }
2391
+ } else {
2392
+ spinner.fail(`Unsupported platform: ${system.platform}`);
2393
+ console.log(chalk4.yellow("\nPlease install tmux manually:"));
2394
+ console.log(chalk4.cyan(" https://github.com/tmux/tmux/wiki/Installing"));
2395
+ }
2396
+ } catch (error) {
2397
+ spinner.fail("Failed to install tmux");
2398
+ console.log(chalk4.red(`
2399
+ Error: ${error.message}`));
2400
+ console.log(chalk4.yellow("\nPlease install tmux manually:"));
2401
+ console.log(chalk4.cyan(" macOS: brew install tmux"));
2402
+ console.log(chalk4.cyan(" Ubuntu: sudo apt-get install tmux"));
2403
+ console.log(chalk4.cyan(" CentOS: sudo yum install tmux"));
2404
+ }
2405
+ }
2406
+ async function checkClaudeCLI() {
2407
+ console.log(chalk4.bold("\n\u{1F916} Checking Claude CLI...\n"));
2408
+ try {
2409
+ const { stdout } = await execa3("claude", ["--version"]);
2410
+ Output.success(`Claude CLI is installed: ${stdout}`);
2411
+ } catch {
2412
+ Output.warn("Claude CLI is not installed");
2413
+ console.log(chalk4.yellow("\nClaude CLI is required for Claude-Orka to work."));
2414
+ console.log(chalk4.cyan("\nInstallation options:"));
2415
+ console.log(" 1. Visit: https://claude.ai/download");
2416
+ console.log(" 2. Or use npm: npm install -g @anthropic-ai/claude-cli");
2417
+ console.log(
2418
+ chalk4.gray("\nNote: You may need to restart your terminal after installation.")
2419
+ );
2420
+ }
2421
+ }
2422
+
2423
+ // src/cli/index.ts
2424
+ var program = new Command();
2425
+ program.name("orka").description("Claude-Orka: Orchestrate Claude Code sessions with tmux").version("1.0.0");
2426
+ prepareCommand(program);
2427
+ initCommand(program);
2428
+ doctorCommand(program);
2429
+ statusCommand(program);
2430
+ sessionCommand(program);
2431
+ forkCommand(program);
2432
+ mergeCommand(program);
2433
+ program.parse();