commandmate 0.1.10 → 0.1.12

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 (154) hide show
  1. package/.env.example +8 -3
  2. package/.next/BUILD_ID +1 -1
  3. package/.next/app-build-manifest.json +10 -10
  4. package/.next/app-path-routes-manifest.json +1 -1
  5. package/.next/build-manifest.json +2 -2
  6. package/.next/cache/.tsbuildinfo +1 -1
  7. package/.next/cache/config.json +3 -3
  8. package/.next/cache/webpack/client-production/0.pack +0 -0
  9. package/.next/cache/webpack/client-production/1.pack +0 -0
  10. package/.next/cache/webpack/client-production/2.pack +0 -0
  11. package/.next/cache/webpack/client-production/index.pack +0 -0
  12. package/.next/cache/webpack/client-production/index.pack.old +0 -0
  13. package/.next/cache/webpack/edge-server-production/0.pack +0 -0
  14. package/.next/cache/webpack/edge-server-production/index.pack +0 -0
  15. package/.next/cache/webpack/server-production/0.pack +0 -0
  16. package/.next/cache/webpack/server-production/index.pack +0 -0
  17. package/.next/next-server.js.nft.json +1 -1
  18. package/.next/prerender-manifest.json +1 -1
  19. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  20. package/.next/server/app/_not-found.html +1 -1
  21. package/.next/server/app/_not-found.rsc +1 -1
  22. package/.next/server/app/api/external-apps/[id]/health/route.js +11 -12
  23. package/.next/server/app/api/external-apps/[id]/route.js +14 -15
  24. package/.next/server/app/api/external-apps/route.js +12 -13
  25. package/.next/server/app/api/hooks/claude-done/route.js +6 -6
  26. package/.next/server/app/api/hooks/claude-done/route.js.nft.json +1 -1
  27. package/.next/server/app/api/repositories/clone/[jobId]/route.js +1 -1
  28. package/.next/server/app/api/repositories/clone/route.js +1 -1
  29. package/.next/server/app/api/repositories/route.js +1 -1
  30. package/.next/server/app/api/repositories/route.js.nft.json +1 -1
  31. package/.next/server/app/api/repositories/scan/route.js +1 -1
  32. package/.next/server/app/api/repositories/sync/route.js +1 -1
  33. package/.next/server/app/api/slash-commands.body +1 -1
  34. package/.next/server/app/api/worktrees/[id]/auto-yes/route.js +1 -1
  35. package/.next/server/app/api/worktrees/[id]/auto-yes/route.js.nft.json +1 -1
  36. package/.next/server/app/api/worktrees/[id]/cli-tool/route.js +1 -1
  37. package/.next/server/app/api/worktrees/[id]/current-output/route.js +1 -1
  38. package/.next/server/app/api/worktrees/[id]/current-output/route.js.nft.json +1 -1
  39. package/.next/server/app/api/worktrees/[id]/files/[...path]/route.js +1 -1
  40. package/.next/server/app/api/worktrees/[id]/interrupt/route.js +1 -1
  41. package/.next/server/app/api/worktrees/[id]/interrupt/route.js.nft.json +1 -1
  42. package/.next/server/app/api/worktrees/[id]/kill-session/route.js +1 -1
  43. package/.next/server/app/api/worktrees/[id]/kill-session/route.js.nft.json +1 -1
  44. package/.next/server/app/api/worktrees/[id]/logs/[filename]/route.js +1 -1
  45. package/.next/server/app/api/worktrees/[id]/logs/route.js +7 -7
  46. package/.next/server/app/api/worktrees/[id]/memos/[memoId]/route.js +1 -1
  47. package/.next/server/app/api/worktrees/[id]/memos/route.js +1 -1
  48. package/.next/server/app/api/worktrees/[id]/messages/route.js +1 -1
  49. package/.next/server/app/api/worktrees/[id]/prompt-response/route.js +1 -1
  50. package/.next/server/app/api/worktrees/[id]/prompt-response/route.js.nft.json +1 -1
  51. package/.next/server/app/api/worktrees/[id]/respond/route.js +1 -1
  52. package/.next/server/app/api/worktrees/[id]/respond/route.js.nft.json +1 -1
  53. package/.next/server/app/api/worktrees/[id]/route.js +1 -1
  54. package/.next/server/app/api/worktrees/[id]/route.js.nft.json +1 -1
  55. package/.next/server/app/api/worktrees/[id]/search/route.js +1 -1
  56. package/.next/server/app/api/worktrees/[id]/send/route.js +1 -1
  57. package/.next/server/app/api/worktrees/[id]/send/route.js.nft.json +1 -1
  58. package/.next/server/app/api/worktrees/[id]/slash-commands/route.js +1 -1
  59. package/.next/server/app/api/worktrees/[id]/start-polling/route.js +1 -1
  60. package/.next/server/app/api/worktrees/[id]/start-polling/route.js.nft.json +1 -1
  61. package/.next/server/app/api/worktrees/[id]/tree/[...path]/route.js +1 -1
  62. package/.next/server/app/api/worktrees/[id]/tree/route.js +1 -1
  63. package/.next/server/app/api/worktrees/[id]/upload/[...path]/route.js +1 -1
  64. package/.next/server/app/api/worktrees/[id]/viewed/route.js +1 -1
  65. package/.next/server/app/api/worktrees/route.js +1 -1
  66. package/.next/server/app/api/worktrees/route.js.nft.json +1 -1
  67. package/.next/server/app/index.html +2 -2
  68. package/.next/server/app/index.rsc +2 -2
  69. package/.next/server/app/page_client-reference-manifest.js +1 -1
  70. package/.next/server/app/proxy/[...path]/route.js +12 -13
  71. package/.next/server/app/worktrees/[id]/files/[...path]/page_client-reference-manifest.js +1 -1
  72. package/.next/server/app/worktrees/[id]/page.js +4 -4
  73. package/.next/server/app/worktrees/[id]/page_client-reference-manifest.js +1 -1
  74. package/.next/server/app/worktrees/[id]/simple-terminal/page_client-reference-manifest.js +1 -1
  75. package/.next/server/app/worktrees/[id]/terminal/page_client-reference-manifest.js +1 -1
  76. package/.next/server/app-paths-manifest.json +8 -8
  77. package/.next/server/chunks/1318.js +4 -4
  78. package/.next/server/chunks/2597.js +1 -0
  79. package/.next/server/chunks/2648.js +1 -0
  80. package/.next/server/chunks/7425.js +97 -48
  81. package/.next/server/chunks/9723.js +1 -1
  82. package/.next/server/functions-config-manifest.json +1 -1
  83. package/.next/server/middleware-manifest.json +5 -5
  84. package/.next/server/pages/404.html +1 -1
  85. package/.next/server/pages/500.html +1 -1
  86. package/.next/server/server-reference-manifest.json +1 -1
  87. package/.next/server/src/middleware.js +2 -2
  88. package/.next/server/src/middleware.js.map +1 -1
  89. package/.next/static/chunks/app/worktrees/[id]/page-58fcf2e63c056743.js +1 -0
  90. package/.next/trace +5 -5
  91. package/dist/cli/commands/init.d.ts.map +1 -1
  92. package/dist/cli/commands/init.js +6 -4
  93. package/dist/cli/commands/start.d.ts +2 -0
  94. package/dist/cli/commands/start.d.ts.map +1 -1
  95. package/dist/cli/commands/start.js +64 -17
  96. package/dist/cli/commands/status.d.ts +4 -1
  97. package/dist/cli/commands/status.d.ts.map +1 -1
  98. package/dist/cli/commands/status.js +95 -6
  99. package/dist/cli/commands/stop.d.ts +2 -0
  100. package/dist/cli/commands/stop.d.ts.map +1 -1
  101. package/dist/cli/commands/stop.js +27 -10
  102. package/dist/cli/index.js +16 -2
  103. package/dist/cli/types/index.d.ts +20 -0
  104. package/dist/cli/types/index.d.ts.map +1 -1
  105. package/dist/cli/utils/daemon-factory.d.ts +105 -0
  106. package/dist/cli/utils/daemon-factory.d.ts.map +1 -0
  107. package/dist/cli/utils/daemon-factory.js +117 -0
  108. package/dist/cli/utils/daemon.d.ts.map +1 -1
  109. package/dist/cli/utils/daemon.js +4 -0
  110. package/dist/cli/utils/env-setup.d.ts +24 -12
  111. package/dist/cli/utils/env-setup.d.ts.map +1 -1
  112. package/dist/cli/utils/env-setup.js +64 -43
  113. package/dist/cli/utils/input-validators.d.ts +103 -0
  114. package/dist/cli/utils/input-validators.d.ts.map +1 -0
  115. package/dist/cli/utils/input-validators.js +163 -0
  116. package/dist/cli/utils/install-context.d.ts +53 -0
  117. package/dist/cli/utils/install-context.d.ts.map +1 -0
  118. package/dist/cli/utils/install-context.js +96 -0
  119. package/dist/cli/utils/pid-manager.d.ts +34 -0
  120. package/dist/cli/utils/pid-manager.d.ts.map +1 -1
  121. package/dist/cli/utils/pid-manager.js +43 -0
  122. package/dist/cli/utils/port-allocator.d.ts +108 -0
  123. package/dist/cli/utils/port-allocator.d.ts.map +1 -0
  124. package/dist/cli/utils/port-allocator.js +166 -0
  125. package/dist/cli/utils/resource-resolvers.d.ts +92 -0
  126. package/dist/cli/utils/resource-resolvers.d.ts.map +1 -0
  127. package/dist/cli/utils/resource-resolvers.js +175 -0
  128. package/dist/cli/utils/worktree-detector.d.ts +82 -0
  129. package/dist/cli/utils/worktree-detector.d.ts.map +1 -0
  130. package/dist/cli/utils/worktree-detector.js +221 -0
  131. package/dist/lib/errors.d.ts +111 -0
  132. package/dist/lib/errors.d.ts.map +1 -0
  133. package/dist/lib/errors.js +153 -0
  134. package/dist/server/server.js +3 -0
  135. package/dist/server/src/cli/utils/install-context.js +96 -0
  136. package/dist/server/src/config/system-directories.js +40 -0
  137. package/dist/server/src/lib/auto-yes-manager.js +324 -0
  138. package/dist/server/src/lib/auto-yes-resolver.js +34 -0
  139. package/dist/server/src/lib/claude-session.js +134 -28
  140. package/dist/server/src/lib/cli-patterns.js +6 -1
  141. package/dist/server/src/lib/db-instance.js +12 -2
  142. package/dist/server/src/lib/db-migrations.js +68 -1
  143. package/dist/server/src/lib/db-path-resolver.js +99 -0
  144. package/dist/server/src/lib/db.js +21 -0
  145. package/dist/server/src/lib/env.js +52 -3
  146. package/dist/server/src/lib/worktrees.js +36 -1
  147. package/dist/server/src/types/external-apps.js +20 -0
  148. package/package.json +1 -1
  149. package/.next/server/chunks/1528.js +0 -1
  150. package/.next/server/chunks/7213.js +0 -1
  151. package/.next/static/chunks/app/worktrees/[id]/page-aea2d5e7e28955be.js +0 -1
  152. /package/.next/static/{8o5rUyZun0GklIHWDgkmv → 564GHwluX5xIv9qpqLJV2}/_buildManifest.js +0 -0
  153. /package/.next/static/{8o5rUyZun0GklIHWDgkmv → 564GHwluX5xIv9qpqLJV2}/_ssgManifest.js +0 -0
  154. /package/.next/static/chunks/app/{page-96a8aa2ec30a44e9.js → page-fe35d61f14b90a51.js} +0 -0
@@ -19,7 +19,7 @@ const db_1 = require("./db");
19
19
  * Current schema version
20
20
  * Increment this when adding new migrations
21
21
  */
22
- exports.CURRENT_SCHEMA_VERSION = 15;
22
+ exports.CURRENT_SCHEMA_VERSION = 16;
23
23
  /**
24
24
  * Migration registry
25
25
  * All migrations should be added to this array in order
@@ -650,6 +650,73 @@ const migrations = [
650
650
  `);
651
651
  console.log('✓ Removed initial_branch column from worktrees table');
652
652
  }
653
+ },
654
+ {
655
+ version: 16,
656
+ name: 'add-issue-no-to-external-apps',
657
+ up: (db) => {
658
+ // Issue #136: Add issue_no column to external_apps table
659
+ // This column identifies which worktree/issue an external app belongs to
660
+ // NULL = main app (non-worktree), integer = issue number
661
+ db.exec(`
662
+ ALTER TABLE external_apps ADD COLUMN issue_no INTEGER;
663
+ `);
664
+ // Create index for efficient querying by issue_no
665
+ db.exec(`
666
+ CREATE INDEX IF NOT EXISTS idx_external_apps_issue_no ON external_apps(issue_no);
667
+ `);
668
+ console.log('✓ Added issue_no column to external_apps table');
669
+ console.log('✓ Created index idx_external_apps_issue_no');
670
+ },
671
+ down: (db) => {
672
+ // SQLite doesn't support DROP COLUMN directly
673
+ // Recreate table without issue_no column
674
+ db.exec(`
675
+ -- 1. Create backup table without issue_no
676
+ CREATE TABLE external_apps_backup AS
677
+ SELECT id, name, display_name, description, path_prefix, target_port,
678
+ target_host, app_type, websocket_enabled, websocket_path_pattern,
679
+ enabled, created_at, updated_at
680
+ FROM external_apps;
681
+
682
+ -- 2. Drop original table (this also drops indexes)
683
+ DROP TABLE external_apps;
684
+
685
+ -- 3. Recreate table without issue_no
686
+ CREATE TABLE external_apps (
687
+ id TEXT PRIMARY KEY,
688
+ name TEXT NOT NULL UNIQUE,
689
+ display_name TEXT NOT NULL,
690
+ description TEXT,
691
+ path_prefix TEXT NOT NULL UNIQUE,
692
+ target_port INTEGER NOT NULL,
693
+ target_host TEXT DEFAULT 'localhost',
694
+ app_type TEXT NOT NULL CHECK(app_type IN ('sveltekit', 'streamlit', 'nextjs', 'other')),
695
+ websocket_enabled INTEGER DEFAULT 0,
696
+ websocket_path_pattern TEXT,
697
+ enabled INTEGER DEFAULT 1,
698
+ created_at INTEGER NOT NULL,
699
+ updated_at INTEGER NOT NULL
700
+ );
701
+
702
+ -- 4. Restore data
703
+ INSERT INTO external_apps (id, name, display_name, description, path_prefix,
704
+ target_port, target_host, app_type, websocket_enabled, websocket_path_pattern,
705
+ enabled, created_at, updated_at)
706
+ SELECT id, name, display_name, description, path_prefix,
707
+ target_port, target_host, app_type, websocket_enabled, websocket_path_pattern,
708
+ enabled, created_at, updated_at
709
+ FROM external_apps_backup;
710
+
711
+ -- 5. Drop backup
712
+ DROP TABLE external_apps_backup;
713
+
714
+ -- 6. Recreate original indexes
715
+ CREATE INDEX idx_external_apps_path_prefix ON external_apps(path_prefix);
716
+ CREATE INDEX idx_external_apps_enabled ON external_apps(enabled);
717
+ `);
718
+ console.log('✓ Removed issue_no column from external_apps table');
719
+ }
653
720
  }
654
721
  ];
655
722
  /**
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ /**
3
+ * DB Path Resolver
4
+ * Issue #135: DB path resolution logic fix
5
+ * Issue #136: Import from install-context.ts to avoid circular imports
6
+ *
7
+ * Provides consistent DB path resolution for both global and local installs.
8
+ * This module centralizes DB path logic to follow SRP (Single Responsibility Principle).
9
+ *
10
+ * @module db-path-resolver
11
+ */
12
+ var __importDefault = (this && this.__importDefault) || function (mod) {
13
+ return (mod && mod.__esModule) ? mod : { "default": mod };
14
+ };
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.getDefaultDbPath = getDefaultDbPath;
17
+ exports.getIssueDbPath = getIssueDbPath;
18
+ exports.validateDbPath = validateDbPath;
19
+ const path_1 = __importDefault(require("path"));
20
+ const os_1 = require("os");
21
+ const install_context_1 = require("../cli/utils/install-context");
22
+ const system_directories_1 = require("../config/system-directories");
23
+ /**
24
+ * Get the default database path based on install type
25
+ *
26
+ * For global installs: ~/.commandmate/data/cm.db
27
+ * For local installs: <cwd>/data/cm.db (as absolute path)
28
+ *
29
+ * @returns Absolute path to the default database file
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * const dbPath = getDefaultDbPath();
34
+ * // Global: /Users/username/.commandmate/data/cm.db
35
+ * // Local: /path/to/project/data/cm.db
36
+ * ```
37
+ */
38
+ function getDefaultDbPath() {
39
+ if ((0, install_context_1.isGlobalInstall)()) {
40
+ return path_1.default.join((0, os_1.homedir)(), '.commandmate', 'data', 'cm.db');
41
+ }
42
+ return path_1.default.resolve(process.cwd(), 'data', 'cm.db');
43
+ }
44
+ /**
45
+ * Get the database path for a specific issue (worktree)
46
+ * Issue #136: Worktree-specific DB path
47
+ *
48
+ * @param issueNo - The issue number
49
+ * @returns Absolute path to the issue-specific database file
50
+ *
51
+ * @example
52
+ * ```typescript
53
+ * const dbPath = getIssueDbPath(135);
54
+ * // Returns: /Users/username/.commandmate/data/cm-135.db
55
+ * ```
56
+ */
57
+ function getIssueDbPath(issueNo) {
58
+ const configDir = (0, install_context_1.getConfigDir)();
59
+ return path_1.default.join(configDir, 'data', `cm-${issueNo}.db`);
60
+ }
61
+ /**
62
+ * Validate database path for security
63
+ *
64
+ * SEC-001: Protects against writing to system directories
65
+ * - Global install: DB path must be within home directory
66
+ * - Local install: DB path must not be in system directories
67
+ *
68
+ * @param dbPath - The database path to validate
69
+ * @returns The resolved absolute path if valid
70
+ * @throws Error if path is in a forbidden location
71
+ *
72
+ * @example
73
+ * ```typescript
74
+ * // Valid paths
75
+ * validateDbPath('~/.commandmate/data/cm.db'); // Global
76
+ * validateDbPath('./data/cm.db'); // Local
77
+ *
78
+ * // Invalid paths (throws)
79
+ * validateDbPath('/etc/cm.db');
80
+ * validateDbPath('/var/lib/cm.db');
81
+ * ```
82
+ */
83
+ function validateDbPath(dbPath) {
84
+ const resolvedPath = path_1.default.resolve(dbPath);
85
+ if ((0, install_context_1.isGlobalInstall)()) {
86
+ // Global install: DB path must be within home directory
87
+ const homeDir = (0, os_1.homedir)();
88
+ if (!resolvedPath.startsWith(homeDir)) {
89
+ throw new Error(`Security error: DB path must be within home directory: ${resolvedPath}`);
90
+ }
91
+ }
92
+ else {
93
+ // Local install: DB path must not be in system directories (SEC-001)
94
+ if ((0, system_directories_1.isSystemDirectory)(resolvedPath)) {
95
+ throw new Error(`Security error: DB path cannot be in system directory: ${resolvedPath}`);
96
+ }
97
+ }
98
+ return resolvedPath;
99
+ }
@@ -41,6 +41,7 @@ exports.saveInitialBranch = saveInitialBranch;
41
41
  exports.getInitialBranch = getInitialBranch;
42
42
  exports.getWorktreeIdsByRepository = getWorktreeIdsByRepository;
43
43
  exports.deleteRepositoryWorktrees = deleteRepositoryWorktrees;
44
+ exports.deleteWorktreesByIds = deleteWorktreesByIds;
44
45
  const crypto_1 = require("crypto");
45
46
  function mapChatMessage(row) {
46
47
  return {
@@ -875,3 +876,23 @@ function deleteRepositoryWorktrees(db, repositoryPath) {
875
876
  const result = stmt.run(repositoryPath);
876
877
  return { deletedCount: result.changes };
877
878
  }
879
+ /**
880
+ * Delete worktrees by their IDs
881
+ * Related data (chat_messages, session_states, worktree_memos) will be
882
+ * automatically deleted via CASCADE foreign key constraints.
883
+ *
884
+ * @param db - Database instance
885
+ * @param worktreeIds - Array of worktree IDs to delete
886
+ * @returns Object containing the count of deleted worktrees
887
+ */
888
+ function deleteWorktreesByIds(db, worktreeIds) {
889
+ if (worktreeIds.length === 0) {
890
+ return { deletedCount: 0 };
891
+ }
892
+ const placeholders = worktreeIds.map(() => '?').join(',');
893
+ const stmt = db.prepare(`
894
+ DELETE FROM worktrees WHERE id IN (${placeholders})
895
+ `);
896
+ const result = stmt.run(...worktreeIds);
897
+ return { deletedCount: result.changes };
898
+ }
@@ -5,6 +5,9 @@
5
5
  *
6
6
  * Issue #76: Environment variable fallback support
7
7
  * Supports both new (CM_*) and legacy (MCBD_*) environment variable names
8
+ *
9
+ * Issue #135: DB path resolution fix
10
+ * Uses getDefaultDbPath() from db-path-resolver.ts for consistent DB path handling
8
11
  */
9
12
  var __importDefault = (this && this.__importDefault) || function (mod) {
10
13
  return (mod && mod.__esModule) ? mod : { "default": mod };
@@ -14,11 +17,14 @@ exports.ENV_MAPPING = void 0;
14
17
  exports.resetWarnedKeys = resetWarnedKeys;
15
18
  exports.getEnvWithFallback = getEnvWithFallback;
16
19
  exports.getEnvByKey = getEnvByKey;
20
+ exports.resetDatabasePathWarning = resetDatabasePathWarning;
21
+ exports.getDatabasePathWithDeprecationWarning = getDatabasePathWithDeprecationWarning;
17
22
  exports.getLogConfig = getLogConfig;
18
23
  exports.getEnv = getEnv;
19
24
  exports.validateEnv = validateEnv;
20
25
  exports.isAuthRequired = isAuthRequired;
21
26
  const path_1 = __importDefault(require("path"));
27
+ const db_path_resolver_1 = require("./db-path-resolver");
22
28
  // ============================================================
23
29
  // Environment Variable Mapping (for fallback support)
24
30
  // Issue #76: CommandMate rename - Phase 1
@@ -79,6 +85,36 @@ function getEnvWithFallback(newKey, oldKey) {
79
85
  function getEnvByKey(key) {
80
86
  return getEnvWithFallback(key, exports.ENV_MAPPING[key]);
81
87
  }
88
+ // ============================================================
89
+ // [Issue #135] DATABASE_PATH Deprecation Support
90
+ // ============================================================
91
+ /**
92
+ * Set to track warned keys for DATABASE_PATH deprecation (separate from ENV_MAPPING warnings)
93
+ */
94
+ let databasePathWarned = false;
95
+ /**
96
+ * Reset DATABASE_PATH warning state (for testing purposes)
97
+ */
98
+ function resetDatabasePathWarning() {
99
+ databasePathWarned = false;
100
+ }
101
+ /**
102
+ * Get DATABASE_PATH with deprecation warning
103
+ *
104
+ * SEC-004: Logs security event when deprecated DATABASE_PATH is used
105
+ *
106
+ * @returns DATABASE_PATH value if set, undefined otherwise
107
+ */
108
+ function getDatabasePathWithDeprecationWarning() {
109
+ const dbPath = process.env.DATABASE_PATH;
110
+ if (dbPath) {
111
+ if (!databasePathWarned) {
112
+ console.warn('[DEPRECATED] DATABASE_PATH is deprecated. Use CM_DB_PATH instead.');
113
+ databasePathWarned = true;
114
+ }
115
+ }
116
+ return dbPath;
117
+ }
82
118
  /**
83
119
  * Validate log level
84
120
  */
@@ -125,9 +161,11 @@ function getEnv() {
125
161
  const port = parseInt(getEnvByKey('CM_PORT') || '3000', 10);
126
162
  const bind = getEnvByKey('CM_BIND') || '127.0.0.1';
127
163
  const authToken = getEnvByKey('CM_AUTH_TOKEN');
164
+ // Issue #135: DB path resolution with proper fallback chain
165
+ // Priority: CM_DB_PATH > DATABASE_PATH (deprecated) > getDefaultDbPath()
128
166
  const databasePath = getEnvByKey('CM_DB_PATH')
129
- || process.env.DATABASE_PATH
130
- || path_1.default.join(process.cwd(), 'data', 'cm.db');
167
+ || getDatabasePathWithDeprecationWarning()
168
+ || (0, db_path_resolver_1.getDefaultDbPath)();
131
169
  // Validate values
132
170
  if (!rootDir) {
133
171
  throw new Error('CM_ROOT_DIR (or MCBD_ROOT_DIR) is required');
@@ -142,12 +180,23 @@ function getEnv() {
142
180
  if (bind === '0.0.0.0' && !authToken) {
143
181
  throw new Error('CM_AUTH_TOKEN (or MCBD_AUTH_TOKEN) is required when CM_BIND=0.0.0.0');
144
182
  }
183
+ // Issue #135: Validate DB path for security (SEC-001)
184
+ let validatedDbPath;
185
+ try {
186
+ validatedDbPath = (0, db_path_resolver_1.validateDbPath)(databasePath);
187
+ }
188
+ catch {
189
+ // If validation fails, fall back to default path
190
+ // This can happen if DATABASE_PATH points to a system directory
191
+ console.warn(`[Security] Invalid DB path "${databasePath}", using default.`);
192
+ validatedDbPath = (0, db_path_resolver_1.validateDbPath)((0, db_path_resolver_1.getDefaultDbPath)());
193
+ }
145
194
  return {
146
195
  CM_ROOT_DIR: path_1.default.resolve(rootDir),
147
196
  CM_PORT: port,
148
197
  CM_BIND: bind,
149
198
  CM_AUTH_TOKEN: authToken,
150
- CM_DB_PATH: path_1.default.resolve(databasePath),
199
+ CM_DB_PATH: validatedDbPath,
151
200
  };
152
201
  }
153
202
  /**
@@ -215,6 +215,11 @@ async function scanMultipleRepositories(repositoryPaths) {
215
215
  /**
216
216
  * Sync scanned worktrees to database
217
217
  *
218
+ * This function performs a true sync:
219
+ * 1. Groups worktrees by repository
220
+ * 2. For each repository, removes worktrees that no longer exist
221
+ * 3. Upserts all current worktrees
222
+ *
218
223
  * @param db - Database instance
219
224
  * @param worktrees - Array of worktrees to sync
220
225
  *
@@ -225,7 +230,37 @@ async function scanMultipleRepositories(repositoryPaths) {
225
230
  * ```
226
231
  */
227
232
  function syncWorktreesToDB(db, worktrees) {
233
+ // If no worktrees provided, do nothing (avoid accidentally deleting all data)
234
+ if (worktrees.length === 0) {
235
+ return;
236
+ }
237
+ // Group worktrees by repository path
238
+ const worktreesByRepo = new Map();
228
239
  for (const worktree of worktrees) {
229
- (0, db_1.upsertWorktree)(db, worktree);
240
+ const repoPath = worktree.repositoryPath || '';
241
+ if (!worktreesByRepo.has(repoPath)) {
242
+ worktreesByRepo.set(repoPath, []);
243
+ }
244
+ worktreesByRepo.get(repoPath).push(worktree);
245
+ }
246
+ // Process each repository
247
+ for (const [repoPath, repoWorktrees] of worktreesByRepo) {
248
+ if (!repoPath)
249
+ continue;
250
+ // Get current worktree IDs in DB for this repository
251
+ const existingIds = (0, db_1.getWorktreeIdsByRepository)(db, repoPath);
252
+ // Get new worktree IDs from scan
253
+ const newIds = new Set(repoWorktrees.map(wt => wt.id));
254
+ // Find worktrees that no longer exist (deleted)
255
+ const deletedIds = existingIds.filter(id => !newIds.has(id));
256
+ // Delete removed worktrees from DB
257
+ if (deletedIds.length > 0) {
258
+ const result = (0, db_1.deleteWorktreesByIds)(db, deletedIds);
259
+ console.log(`Removed ${result.deletedCount} deleted worktree(s) from ${repoPath}`);
260
+ }
261
+ // Upsert current worktrees
262
+ for (const worktree of repoWorktrees) {
263
+ (0, db_1.upsertWorktree)(db, worktree);
264
+ }
230
265
  }
231
266
  }
@@ -4,3 +4,23 @@
4
4
  * Issue #42: Proxy routing for multiple frontend applications
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.isWorktreeExternalApp = isWorktreeExternalApp;
8
+ /**
9
+ * Type guard to check if an ExternalApp is a WorktreeExternalApp
10
+ * Issue #136: Helper for type narrowing
11
+ *
12
+ * @param app - The external app to check
13
+ * @returns true if the app has a valid issueNo
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * const app = getExternalAppById('some-id');
18
+ * if (isWorktreeExternalApp(app)) {
19
+ * // TypeScript knows app.issueNo is number
20
+ * console.log(`Worktree for issue #${app.issueNo}`);
21
+ * }
22
+ * ```
23
+ */
24
+ function isWorktreeExternalApp(app) {
25
+ return typeof app.issueNo === 'number' && app.issueNo > 0;
26
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "commandmate",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "Git worktree management with Claude CLI and tmux sessions",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1 +0,0 @@
1
- "use strict";exports.id=1528,exports.ids=[1528],exports.modules={19377:(t,e,n)=>{n.d(e,{Wg:()=>c,bs:()=>function t(e){switch(e){case"claude":return{promptPattern:i,separatorPattern:a,thinkingPattern:o,skipPatterns:[/^─{10,}$/,/^[>❯]\s*$/,o,/^\s*[⎿⏋]\s+Tip:/,/^\s*Tip:/,/^\s*\?\s*for shortcuts/,/to interrupt\)/]};case"codex":return{promptPattern:l,separatorPattern:p,thinkingPattern:s,skipPatterns:[/^─.*─+$/,/^›\s*$/,/^›\s+(Implement|Find and fix|Type)/,s,/^\s*\d+%\s+context left/,/^\s*for shortcuts$/,/╭─+╮/,/╰─+╯/]};case"gemini":return{promptPattern:u,separatorPattern:/^gemini\s+--\s+/m,thinkingPattern:/(?!)/m,skipPatterns:[/^gemini\s+--\s+/,u,/^\s*$/]};default:return t("claude")}},vp:()=>d});let r=(0,n(43895).h)("cli-patterns"),o=RegExp(`[✻✽⏺\xb7∴✢✳✶⦿◉●○◌◎⊙⊚⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]\\s+.+…|to interrupt\\)`,"m"),s=/•\s*(Planning|Searching|Exploring|Running|Thinking|Working|Reading|Writing|Analyzing)/m,i=/^[>❯]\s*$/m,a=/^─{10,}$/m,l=/^›\s+.+/m,p=/^─.*Worked for.*─+$/m,u=/^(%|\$|.*@.*[%$#])\s*$/m;function c(t,e){let n;let i=r.withContext({cliToolId:t});switch(i.debug("detectThinking:check",{contentLength:e.length}),t){case"claude":default:n=o.test(e);break;case"codex":n=s.test(e);break;case"gemini":n=!1}return i.debug("detectThinking:result",{isThinking:n}),n}let m=/\x1b\[[0-9;]*[a-zA-Z]|\x1b\][^\x07]*\x07|\[[0-9;]*m/g;function d(t){return t.replace(m,"")}},89194:(t,e,n)=>{n.d(e,{Lg:()=>i,NA:()=>a});var r=n(10927),o=n(97213);let s=(0,n(43895).h)("cli-session");async function i(t,e){let n=o.g.getInstance().getTool(e).getSessionName(t);return await (0,r.Hk)(n)}async function a(t,e,n=1e3){let i=s.withContext({worktreeId:t,cliToolId:e});i.debug("captureSessionOutput:start",{requestedLines:n});let a=o.g.getInstance().getTool(e),l=a.getSessionName(t);if(!await (0,r.Hk)(l))throw i.debug("captureSessionOutput:sessionNotFound",{sessionName:l}),Error(`${a.name} session ${l} does not exist`);try{let t=await (0,r.xq)(l,{startLine:-n}),e=t.split("\n").length;return i.debug("captureSessionOutput:success",{actualLines:e,lastFewLines:t.split("\n").slice(-3).join(" | ")}),t}catch(e){let t=e instanceof Error?e.message:String(e);throw i.error("captureSessionOutput:failed",{error:t}),Error(`Failed to capture ${a.name} output: ${t}`)}}},37848:(t,e,n)=>{n.d(e,{Hb:()=>a,LI:()=>l,dU:()=>p});var r=n(55315),o=n.n(r);let s={CM_ROOT_DIR:"MCBD_ROOT_DIR",CM_PORT:"MCBD_PORT",CM_BIND:"MCBD_BIND",CM_AUTH_TOKEN:"MCBD_AUTH_TOKEN",CM_LOG_LEVEL:"MCBD_LOG_LEVEL",CM_LOG_FORMAT:"MCBD_LOG_FORMAT",CM_LOG_DIR:"MCBD_LOG_DIR",CM_DB_PATH:"MCBD_DB_PATH"},i=new Set;function a(t){return function(t,e){let n=process.env[t];if(void 0!==n)return n;let r=process.env[e];if(void 0!==r)return i.has(e)||(console.warn(`[DEPRECATED] ${e} is deprecated, use ${t} instead`),i.add(e)),r}(t,s[t])}function l(){let t=a("CM_LOG_LEVEL")?.toLowerCase(),e=a("CM_LOG_FORMAT")?.toLowerCase();return{level:void 0!==t&&["debug","info","warn","error"].includes(t)?t:"info",format:"json"===e?"json":"text"}}function p(){let t=a("CM_ROOT_DIR")||process.cwd(),e=parseInt(a("CM_PORT")||"3000",10),n=a("CM_BIND")||"127.0.0.1",r=a("CM_AUTH_TOKEN"),s=a("CM_DB_PATH")||process.env.DATABASE_PATH||o().join(process.cwd(),"data","cm.db");if(!t)throw Error("CM_ROOT_DIR (or MCBD_ROOT_DIR) is required");if(isNaN(e)||e<1||e>65535)throw Error(`Invalid CM_PORT: ${a("CM_PORT")}. Must be between 1 and 65535.`);if("127.0.0.1"!==n&&"0.0.0.0"!==n&&"localhost"!==n)throw Error(`Invalid CM_BIND: ${n}. Must be '127.0.0.1', '0.0.0.0', or 'localhost'.`);if("0.0.0.0"===n&&!r)throw Error("CM_AUTH_TOKEN (or MCBD_AUTH_TOKEN) is required when CM_BIND=0.0.0.0");return{CM_ROOT_DIR:o().resolve(t),CM_PORT:e,CM_BIND:n,CM_AUTH_TOKEN:r,CM_DB_PATH:o().resolve(s)}}},43895:(t,e,n)=>{n.d(e,{Y:()=>l,h:()=>p});var r=n(37848);let o=[{pattern:/Bearer\s+[A-Za-z0-9\-._~+/]+=*/gi,replacement:"Bearer [REDACTED]"},{pattern:/(password|passwd|pwd)[=:]\s*\S+/gi,replacement:"$1=[REDACTED]"},{pattern:/(token|secret|api_key|apikey|auth)[=:]\s*\S+/gi,replacement:"$1=[REDACTED]"},{pattern:/CM_AUTH_TOKEN=\S+/gi,replacement:"CM_AUTH_TOKEN=[REDACTED]"},{pattern:/MCBD_AUTH_TOKEN=\S+/gi,replacement:"MCBD_AUTH_TOKEN=[REDACTED]"},{pattern:/Authorization:\s*\S+/gi,replacement:"Authorization: [REDACTED]"},{pattern:/-----BEGIN\s+\w+\s+PRIVATE\s+KEY-----[\s\S]*?-----END\s+\w+\s+PRIVATE\s+KEY-----/g,replacement:"[SSH_KEY_REDACTED]"}],s=/password|secret|token|key|auth/i,i={debug:0,info:1,warn:2,error:3};function a(t,e,n,a,l){let p=(0,r.LI)().level;if(i[t]<i[p])return;let u=a?function t(e){if("string"==typeof e){let t=e;for(let{pattern:e,replacement:n}of o)t=t.replace(e,n);return t}if("object"==typeof e&&null!==e){if(Array.isArray(e))return e.map(t);let n={};for(let[r,o]of Object.entries(e))s.test(r)?n[r]="[REDACTED]":n[r]=t(o);return n}return e}(a):void 0,c=function(t){if("json"===(0,r.LI)().format)return JSON.stringify(t);let{timestamp:e,level:n,module:o,action:s,data:i,worktreeId:a,cliToolId:l,requestId:p}=t,u=[a,l].filter(Boolean),c=u.length>0?` [${u.join(":")}]`:"",m=p?` (${p.slice(0,8)})`:"",d=i?` ${JSON.stringify(i)}`:"";return`[${e}] [${n.toUpperCase()}] [${o}]${c}${m} ${s}${d}`}({level:t,module:e,action:n,timestamp:new Date().toISOString(),...l,...u&&{data:u}});switch(t){case"error":console.error(c);break;case"warn":console.warn(c);break;default:console.log(c)}}function l(){return"undefined"!=typeof crypto&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,t=>{let e=16*Math.random()|0;return("x"===t?e:3&e|8).toString(16)})}function p(t){let e=n=>({debug:(e,r)=>a("debug",t,e,r,n),info:(e,r)=>a("info",t,e,r,n),warn:(e,r)=>a("warn",t,e,r,n),error:(e,r)=>a("error",t,e,r,n),withContext:t=>e({...n,...t})});return e()}},63661:(t,e,n)=>{n.d(e,{F:()=>o,J:()=>i});let r=(0,n(43895).h)("prompt-detector");function o(t){r.debug("detectPrompt:start",{outputLength:t.length});let e=t.split("\n").slice(-10).join("\n"),n=function(t){let e=t.split("\n"),n=/^\s*([❯ ]\s*)?(\d+)\.\s*(.+)$/,r=[],o=-1,i=-1;for(let t=e.length-1;t>=0&&t>=e.length-50;t--){let s=e[t].trim(),a=e[t],l=s.match(n);if(l){let e=!!(l[1]&&l[1].includes("❯")),n=parseInt(l[2],10),o=l[3].trim();r.unshift({number:n,label:o,isDefault:e}),-1===i&&(i=t)}else if(r.length>0&&s&&!s.match(/^[-─]+$/)){let e=a.match(/^\s{2,}[^\d]/)&&!a.match(/^\s*\d+\./),n=s.length<5&&!s.endsWith("?");if(e||n)continue;o=t;break}}let a=r.some(t=>t.isDefault);if(r.length<2||!a)return{isPrompt:!1,cleanContent:t.trim()};let l="";if(o>=0){let t=[];for(let n=Math.max(0,o-5);n<=o;n++){let r=e[n].trim();r&&!r.match(/^[-─]+$/)&&t.push(r)}l=t.join(" ")}else l="Please select an option:";return{isPrompt:!0,promptData:{type:"multiple_choice",question:l.trim(),options:r.map(t=>{let e=s.some(e=>e.test(t.label));return{number:t.number,label:t.label,isDefault:t.isDefault,requiresTextInput:e}}),status:"pending"},cleanContent:l.trim()}}(t);if(n.isPrompt)return r.info("detectPrompt:multipleChoice",{isPrompt:!0,question:n.promptData?.question,optionsCount:n.promptData?.options?.length}),n;let o=e.match(/^(.+)\s+\(y\/n\)\s*$/m);if(o)return{isPrompt:!0,promptData:{type:"yes_no",question:o[1].trim(),options:["yes","no"],status:"pending"},cleanContent:o[1].trim()};let i=e.match(/^(.+)\s+\[y\/N\]\s*$/m);if(i)return{isPrompt:!0,promptData:{type:"yes_no",question:i[1].trim(),options:["yes","no"],status:"pending",defaultOption:"no"},cleanContent:i[1].trim()};let a=e.match(/^(.+)\s+\[Y\/n\]\s*$/m);if(a)return{isPrompt:!0,promptData:{type:"yes_no",question:a[1].trim(),options:["yes","no"],status:"pending",defaultOption:"yes"},cleanContent:a[1].trim()};let l=e.match(/^(.+)\s+\(yes\/no\)\s*$/m);if(l)return{isPrompt:!0,promptData:{type:"yes_no",question:l[1].trim(),options:["yes","no"],status:"pending"},cleanContent:l[1].trim()};let p=e.match(/^(.*?)Approve\?\s*$/m);if(p){let t=p[1].trim();return{isPrompt:!0,promptData:{type:"yes_no",question:t?`${t} Approve?`:"Approve?",options:["yes","no"],status:"pending"},cleanContent:t||"Approve?"}}return r.debug("detectPrompt:complete",{isPrompt:!1}),{isPrompt:!1,cleanContent:t.trim()}}let s=[/type\s+here/i,/tell\s+(me|claude)/i,/enter\s+/i,/custom/i,/differently/i];function i(t,e="yes_no"){let n=t.toLowerCase().trim();if("multiple_choice"===e){if(/^\d+$/.test(n))return n;throw Error(`Invalid answer for multiple choice: ${t}. Expected a number.`)}if("yes"===n||"y"===n)return"y";if("no"===n||"n"===n)return"n";throw Error(`Invalid answer: ${t}. Expected 'yes', 'no', 'y', or 'n'.`)}}};
@@ -1 +0,0 @@
1
- "use strict";exports.id=7213,exports.ids=[7213],exports.modules={62648:(t,e,s)=>{s.d(e,{Lm:()=>w,Uv:()=>c,YI:()=>m,_f:()=>h,xd:()=>d,ym:()=>u});var i=s(10927),o=s(61282);let n=(0,s(21764).promisify)(o.exec),a=null;async function r(){if(a)return a;if(process.env.CLAUDE_PATH)return a=process.env.CLAUDE_PATH;try{let{stdout:t}=await n("which claude",{timeout:5e3});return a=t.trim()}catch{for(let t of["/opt/homebrew/bin/claude","/usr/local/bin/claude","/usr/bin/claude"])try{return await n(`test -x "${t}"`,{timeout:1e3}),a=t}catch{}throw Error("Claude CLI not found. Set CLAUDE_PATH environment variable or install Claude CLI.")}}function l(t){return`mcbd-claude-${t}`}async function c(){try{return await n("which claude",{timeout:5e3}),!0}catch{return!1}}async function m(t){let e=l(t);return await (0,i.Hk)(e)}async function u(t){let{worktreeId:e,worktreePath:s}=t;if(!await c())throw Error("Claude CLI is not installed or not in PATH");let o=l(e);if(await (0,i.Hk)(o)){console.log(`Claude session ${o} already exists`);return}try{await (0,i.ed)({sessionName:o,workingDirectory:s,historyLimit:5e4});let t=await r();await (0,i.Is)(o,t,!0);let e=Date.now();for(;Date.now()-e<1e4;){await new Promise(t=>setTimeout(t,500));try{let t=await (0,i.xq)(o,{startLine:-50});if(/^>\s*$/m.test(t)||/^─{10,}$/m.test(t)){console.log(`✓ Claude initialized in ${Date.now()-e}ms`);break}}catch{}}console.log(`✓ Started Claude session: ${o}`)}catch(e){let t=e instanceof Error?e.message:String(e);throw Error(`Failed to start Claude session: ${t}`)}}async function d(t,e){let s=l(t);if(!await (0,i.Hk)(s))throw Error(`Claude session ${s} does not exist. Start the session first.`);try{await (0,i.Is)(s,e,!1),await new Promise(t=>setTimeout(t,100)),await n(`tmux send-keys -t "${s}" C-m`),await new Promise(t=>setTimeout(t,200)),console.log(`✓ Sent message to Claude session: ${s}`)}catch(e){let t=e instanceof Error?e.message:String(e);throw Error(`Failed to send message to Claude: ${t}`)}}async function w(t,e=1e3){let s=l(t);if(!await (0,i.Hk)(s))throw Error(`Claude session ${s} does not exist`);try{return await (0,i.xq)(s,{startLine:-e})}catch(e){let t=e instanceof Error?e.message:String(e);throw Error(`Failed to capture Claude output: ${t}`)}}async function h(t){let e=l(t);try{await (0,i.Hk)(e)&&(await (0,i.Is)(e,"",!1),await n(`tmux send-keys -t "${e}" C-d`),await new Promise(t=>setTimeout(t,500)));let t=await (0,i.AJ)(e);return t&&console.log(`✓ Stopped Claude session: ${e}`),t}catch(e){let t=e instanceof Error?e.message:String(e);return console.error(`Error stopping Claude session: ${t}`),!1}}},97213:(t,e,s)=>{s.d(e,{g:()=>h});var i=s(61282),o=s(21764),n=s(10927);let a=(0,o.promisify)(i.exec);class r{async isInstalled(){try{return await a(`which ${this.command}`,{timeout:5e3}),!0}catch{return!1}}getSessionName(t){return`mcbd-${this.id}-${t}`}async interrupt(t){let e=this.getSessionName(t);await (0,n.ZV)(e,"Escape")}}var l=s(62648);class c extends r{async isInstalled(){return await (0,l.Uv)()}async isRunning(t){return await (0,l.YI)(t)}async startSession(t,e){await (0,l.ym)({worktreeId:t,worktreePath:e})}async sendMessage(t,e){await (0,l.xd)(t,e)}async killSession(t){await (0,l._f)(t)}constructor(...t){super(...t),this.id="claude",this.name="Claude Code",this.command="claude"}}let m=(0,o.promisify)(i.exec);class u extends r{async isRunning(t){let e=this.getSessionName(t);return await (0,n.Hk)(e)}async startSession(t,e){if(!await this.isInstalled())throw Error("Codex CLI is not installed or not in PATH");let s=this.getSessionName(t);if(await (0,n.Hk)(s)){console.log(`Codex session ${s} already exists`);return}try{await (0,n.ed)({sessionName:s,workingDirectory:e,historyLimit:5e4}),await new Promise(t=>setTimeout(t,100)),await (0,n.Is)(s,"codex",!0),await new Promise(t=>setTimeout(t,3e3)),await (0,n.Is)(s,"2",!0),await new Promise(t=>setTimeout(t,500)),console.log(`✓ Started Codex session: ${s}`)}catch(e){let t=e instanceof Error?e.message:String(e);throw Error(`Failed to start Codex session: ${t}`)}}async sendMessage(t,e){let s=this.getSessionName(t);if(!await (0,n.Hk)(s))throw Error(`Codex session ${s} does not exist. Start the session first.`);try{await (0,n.Is)(s,e,!1),await new Promise(t=>setTimeout(t,100)),await m(`tmux send-keys -t "${s}" C-m`),await new Promise(t=>setTimeout(t,200)),console.log(`✓ Sent message to Codex session: ${s}`)}catch(e){let t=e instanceof Error?e.message:String(e);throw Error(`Failed to send message to Codex: ${t}`)}}async killSession(t){let e=this.getSessionName(t);try{await (0,n.Hk)(e)&&(await m(`tmux send-keys -t "${e}" C-d`),await new Promise(t=>setTimeout(t,500))),await (0,n.AJ)(e)&&console.log(`✓ Stopped Codex session: ${e}`)}catch(e){let t=e instanceof Error?e.message:String(e);throw console.error(`Error stopping Codex session: ${t}`),e}}constructor(...t){super(...t),this.id="codex",this.name="Codex CLI",this.command="codex"}}let d=(0,o.promisify)(i.exec);class w extends r{async isRunning(t){let e=this.getSessionName(t);return await (0,n.Hk)(e)}async startSession(t,e){if(!await this.isInstalled())throw Error("Gemini CLI is not installed or not in PATH");let s=this.getSessionName(t);if(await (0,n.Hk)(s)){console.log(`Gemini session ${s} already exists`);return}try{await (0,n.ed)({sessionName:s,workingDirectory:e,historyLimit:5e4}),console.log(`✓ Started Gemini session: ${s}`)}catch(e){let t=e instanceof Error?e.message:String(e);throw Error(`Failed to start Gemini session: ${t}`)}}async sendMessage(t,e){let s=this.getSessionName(t);if(!await (0,n.Hk)(s))throw Error(`Gemini session ${s} does not exist. Start the session first.`);try{let t=e.replace(/'/g,"'\\''");await (0,n.Is)(s,`echo '${t}' | gemini`,!0),console.log(`✓ Sent message to Gemini session: ${s}`)}catch(e){let t=e instanceof Error?e.message:String(e);throw Error(`Failed to send message to Gemini: ${t}`)}}async killSession(t){let e=this.getSessionName(t);try{await (0,n.Hk)(e)&&(await d(`tmux send-keys -t "${e}" C-d`),await new Promise(t=>setTimeout(t,500))),await (0,n.AJ)(e)&&console.log(`✓ Stopped Gemini session: ${e}`)}catch(e){let t=e instanceof Error?e.message:String(e);throw console.error(`Error stopping Gemini session: ${t}`),e}}constructor(...t){super(...t),this.id="gemini",this.name="Gemini CLI",this.command="gemini"}}class h{constructor(){this.tools=new Map,this.tools.set("claude",new c),this.tools.set("codex",new u),this.tools.set("gemini",new w)}static getInstance(){return h.instance||(h.instance=new h),h.instance}getTool(t){let e=this.tools.get(t);if(!e)throw Error(`CLI tool '${t}' not found`);return e}getAllTools(){return Array.from(this.tools.values())}async getToolInfo(t){let e=this.getTool(t),s=await e.isInstalled();return{id:e.id,name:e.name,command:e.command,installed:s}}async getAllToolsInfo(){return Promise.all(this.getAllTools().map(async t=>{let e=await t.isInstalled();return{id:t.id,name:t.name,command:t.command,installed:e}}))}async getInstalledTools(){return(await this.getAllToolsInfo()).filter(t=>t.installed)}}},10927:(t,e,s)=>{s.d(e,{AJ:()=>c,Hk:()=>n,Is:()=>r,ZV:()=>m,ed:()=>a,xq:()=>l});var i=s(61282);let o=(0,s(21764).promisify)(i.exec);async function n(t){try{return await o(`tmux has-session -t "${t}"`,{timeout:5e3}),!0}catch{return!1}}async function a(t,e){let s,i,n;"string"==typeof t?(s=t,i=e,n=5e4):(s=t.sessionName,i=t.workingDirectory,n=t.historyLimit||5e4);try{await o(`tmux new-session -d -s "${s}" -c "${i}"`,{timeout:5e3}),await o(`tmux set-option -t "${s}" history-limit ${n}`,{timeout:5e3})}catch(e){let t=e instanceof Error?e.message:String(e);throw Error(`Failed to create tmux session: ${t}`)}}async function r(t,e,s=!0){let i=e.replace(/'/g,"'\\''"),n=s?`tmux send-keys -t "${t}" '${i}' C-m`:`tmux send-keys -t "${t}" '${i}'`;try{await o(n,{timeout:5e3})}catch(e){let t=e instanceof Error?e.message:String(e);throw Error(`Failed to send keys to tmux session: ${t}`)}}async function l(t,e){let s,i;"number"==typeof e?(s=-e,i="-"):e?(s=e.startLine??-1e4,i=e.endLine??"-"):(s=-1e3,i="-");try{let{stdout:e}=await o(`tmux capture-pane -t "${t}" -p -e -S ${s} -E ${i}`,{timeout:5e3,maxBuffer:10485760});return e}catch(e){let t=e instanceof Error?e.message:String(e);throw Error(`Failed to capture pane: ${t}`)}}async function c(t){try{return await o(`tmux kill-session -t "${t}"`,{timeout:5e3}),!0}catch(e){let t=e instanceof Error?e.message:String(e);if(t?.includes("no server running")||t?.includes("can't find session"))return!1;throw Error(`Failed to kill tmux session: ${t}`)}}async function m(t,e){try{await o(`tmux send-keys -t "${t}" ${e}`,{timeout:5e3})}catch(e){let t=e instanceof Error?e.message:String(e);throw Error(`Failed to send special key: ${t}`)}}}};