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.
- package/.env.example +8 -3
- package/.next/BUILD_ID +1 -1
- package/.next/app-build-manifest.json +10 -10
- package/.next/app-path-routes-manifest.json +1 -1
- package/.next/build-manifest.json +2 -2
- package/.next/cache/.tsbuildinfo +1 -1
- package/.next/cache/config.json +3 -3
- package/.next/cache/webpack/client-production/0.pack +0 -0
- package/.next/cache/webpack/client-production/1.pack +0 -0
- package/.next/cache/webpack/client-production/2.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack.old +0 -0
- package/.next/cache/webpack/edge-server-production/0.pack +0 -0
- package/.next/cache/webpack/edge-server-production/index.pack +0 -0
- package/.next/cache/webpack/server-production/0.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack +0 -0
- package/.next/next-server.js.nft.json +1 -1
- package/.next/prerender-manifest.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +1 -1
- package/.next/server/app/api/external-apps/[id]/health/route.js +11 -12
- package/.next/server/app/api/external-apps/[id]/route.js +14 -15
- package/.next/server/app/api/external-apps/route.js +12 -13
- package/.next/server/app/api/hooks/claude-done/route.js +6 -6
- package/.next/server/app/api/hooks/claude-done/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/clone/[jobId]/route.js +1 -1
- package/.next/server/app/api/repositories/clone/route.js +1 -1
- package/.next/server/app/api/repositories/route.js +1 -1
- package/.next/server/app/api/repositories/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/scan/route.js +1 -1
- package/.next/server/app/api/repositories/sync/route.js +1 -1
- package/.next/server/app/api/slash-commands.body +1 -1
- package/.next/server/app/api/worktrees/[id]/auto-yes/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/auto-yes/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/cli-tool/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/current-output/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/current-output/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/files/[...path]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/interrupt/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/interrupt/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/kill-session/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/kill-session/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/logs/[filename]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/logs/route.js +7 -7
- package/.next/server/app/api/worktrees/[id]/memos/[memoId]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/memos/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/messages/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/prompt-response/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/prompt-response/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/respond/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/respond/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/search/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/send/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/send/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/slash-commands/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/start-polling/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/start-polling/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/tree/[...path]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/tree/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/upload/[...path]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/viewed/route.js +1 -1
- package/.next/server/app/api/worktrees/route.js +1 -1
- package/.next/server/app/api/worktrees/route.js.nft.json +1 -1
- package/.next/server/app/index.html +2 -2
- package/.next/server/app/index.rsc +2 -2
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/proxy/[...path]/route.js +12 -13
- package/.next/server/app/worktrees/[id]/files/[...path]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/worktrees/[id]/page.js +4 -4
- package/.next/server/app/worktrees/[id]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/worktrees/[id]/simple-terminal/page_client-reference-manifest.js +1 -1
- package/.next/server/app/worktrees/[id]/terminal/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +8 -8
- package/.next/server/chunks/1318.js +4 -4
- package/.next/server/chunks/2597.js +1 -0
- package/.next/server/chunks/2648.js +1 -0
- package/.next/server/chunks/7425.js +97 -48
- package/.next/server/chunks/9723.js +1 -1
- package/.next/server/functions-config-manifest.json +1 -1
- package/.next/server/middleware-manifest.json +5 -5
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/server/src/middleware.js +2 -2
- package/.next/server/src/middleware.js.map +1 -1
- package/.next/static/chunks/app/worktrees/[id]/page-58fcf2e63c056743.js +1 -0
- package/.next/trace +5 -5
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +6 -4
- package/dist/cli/commands/start.d.ts +2 -0
- package/dist/cli/commands/start.d.ts.map +1 -1
- package/dist/cli/commands/start.js +64 -17
- package/dist/cli/commands/status.d.ts +4 -1
- package/dist/cli/commands/status.d.ts.map +1 -1
- package/dist/cli/commands/status.js +95 -6
- package/dist/cli/commands/stop.d.ts +2 -0
- package/dist/cli/commands/stop.d.ts.map +1 -1
- package/dist/cli/commands/stop.js +27 -10
- package/dist/cli/index.js +16 -2
- package/dist/cli/types/index.d.ts +20 -0
- package/dist/cli/types/index.d.ts.map +1 -1
- package/dist/cli/utils/daemon-factory.d.ts +105 -0
- package/dist/cli/utils/daemon-factory.d.ts.map +1 -0
- package/dist/cli/utils/daemon-factory.js +117 -0
- package/dist/cli/utils/daemon.d.ts.map +1 -1
- package/dist/cli/utils/daemon.js +4 -0
- package/dist/cli/utils/env-setup.d.ts +24 -12
- package/dist/cli/utils/env-setup.d.ts.map +1 -1
- package/dist/cli/utils/env-setup.js +64 -43
- package/dist/cli/utils/input-validators.d.ts +103 -0
- package/dist/cli/utils/input-validators.d.ts.map +1 -0
- package/dist/cli/utils/input-validators.js +163 -0
- package/dist/cli/utils/install-context.d.ts +53 -0
- package/dist/cli/utils/install-context.d.ts.map +1 -0
- package/dist/cli/utils/install-context.js +96 -0
- package/dist/cli/utils/pid-manager.d.ts +34 -0
- package/dist/cli/utils/pid-manager.d.ts.map +1 -1
- package/dist/cli/utils/pid-manager.js +43 -0
- package/dist/cli/utils/port-allocator.d.ts +108 -0
- package/dist/cli/utils/port-allocator.d.ts.map +1 -0
- package/dist/cli/utils/port-allocator.js +166 -0
- package/dist/cli/utils/resource-resolvers.d.ts +92 -0
- package/dist/cli/utils/resource-resolvers.d.ts.map +1 -0
- package/dist/cli/utils/resource-resolvers.js +175 -0
- package/dist/cli/utils/worktree-detector.d.ts +82 -0
- package/dist/cli/utils/worktree-detector.d.ts.map +1 -0
- package/dist/cli/utils/worktree-detector.js +221 -0
- package/dist/lib/errors.d.ts +111 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +153 -0
- package/dist/server/server.js +3 -0
- package/dist/server/src/cli/utils/install-context.js +96 -0
- package/dist/server/src/config/system-directories.js +40 -0
- package/dist/server/src/lib/auto-yes-manager.js +324 -0
- package/dist/server/src/lib/auto-yes-resolver.js +34 -0
- package/dist/server/src/lib/claude-session.js +134 -28
- package/dist/server/src/lib/cli-patterns.js +6 -1
- package/dist/server/src/lib/db-instance.js +12 -2
- package/dist/server/src/lib/db-migrations.js +68 -1
- package/dist/server/src/lib/db-path-resolver.js +99 -0
- package/dist/server/src/lib/db.js +21 -0
- package/dist/server/src/lib/env.js +52 -3
- package/dist/server/src/lib/worktrees.js +36 -1
- package/dist/server/src/types/external-apps.js +20 -0
- package/package.json +1 -1
- package/.next/server/chunks/1528.js +0 -1
- package/.next/server/chunks/7213.js +0 -1
- package/.next/static/chunks/app/worktrees/[id]/page-aea2d5e7e28955be.js +0 -1
- /package/.next/static/{8o5rUyZun0GklIHWDgkmv → 564GHwluX5xIv9qpqLJV2}/_buildManifest.js +0 -0
- /package/.next/static/{8o5rUyZun0GklIHWDgkmv → 564GHwluX5xIv9qpqLJV2}/_ssgManifest.js +0 -0
- /package/.next/static/chunks/app/{page-96a8aa2ec30a44e9.js → page-fe35d61f14b90a51.js} +0 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Auto Yes Manager - Server-side in-memory state management
|
|
4
|
+
*
|
|
5
|
+
* Manages auto-yes mode per worktree using an in-memory Map.
|
|
6
|
+
* State is automatically cleared on server restart.
|
|
7
|
+
*
|
|
8
|
+
* Issue #138: Added server-side polling functionality to handle
|
|
9
|
+
* auto-yes responses when browser tabs are in background.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.MAX_CONCURRENT_POLLERS = exports.MAX_CONSECUTIVE_ERRORS = exports.MAX_BACKOFF_MS = exports.POLLING_INTERVAL_MS = void 0;
|
|
13
|
+
exports.isValidWorktreeId = isValidWorktreeId;
|
|
14
|
+
exports.calculateBackoffInterval = calculateBackoffInterval;
|
|
15
|
+
exports.isAutoYesExpired = isAutoYesExpired;
|
|
16
|
+
exports.getAutoYesState = getAutoYesState;
|
|
17
|
+
exports.setAutoYesEnabled = setAutoYesEnabled;
|
|
18
|
+
exports.clearAllAutoYesStates = clearAllAutoYesStates;
|
|
19
|
+
exports.getActivePollerCount = getActivePollerCount;
|
|
20
|
+
exports.clearAllPollerStates = clearAllPollerStates;
|
|
21
|
+
exports.getLastServerResponseTimestamp = getLastServerResponseTimestamp;
|
|
22
|
+
exports.startAutoYesPolling = startAutoYesPolling;
|
|
23
|
+
exports.stopAutoYesPolling = stopAutoYesPolling;
|
|
24
|
+
exports.stopAllAutoYesPolling = stopAllAutoYesPolling;
|
|
25
|
+
const cli_session_1 = require("./cli-session");
|
|
26
|
+
const prompt_detector_1 = require("./prompt-detector");
|
|
27
|
+
const auto_yes_resolver_1 = require("./auto-yes-resolver");
|
|
28
|
+
const tmux_1 = require("./tmux");
|
|
29
|
+
const manager_1 = require("./cli-tools/manager");
|
|
30
|
+
const cli_patterns_1 = require("./cli-patterns");
|
|
31
|
+
// =============================================================================
|
|
32
|
+
// Constants (Issue #138)
|
|
33
|
+
// =============================================================================
|
|
34
|
+
/** Polling interval in milliseconds */
|
|
35
|
+
exports.POLLING_INTERVAL_MS = 2000;
|
|
36
|
+
/** Maximum backoff interval in milliseconds (60 seconds) */
|
|
37
|
+
exports.MAX_BACKOFF_MS = 60000;
|
|
38
|
+
/** Number of consecutive errors before applying backoff */
|
|
39
|
+
exports.MAX_CONSECUTIVE_ERRORS = 5;
|
|
40
|
+
/** Maximum concurrent pollers (DoS protection) */
|
|
41
|
+
exports.MAX_CONCURRENT_POLLERS = 50;
|
|
42
|
+
/** Timeout duration: 1 hour in milliseconds */
|
|
43
|
+
const AUTO_YES_TIMEOUT_MS = 3600000;
|
|
44
|
+
/** Worktree ID validation pattern (security: prevent command injection) */
|
|
45
|
+
const WORKTREE_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
46
|
+
/** In-memory storage for auto-yes states (globalThis for hot reload persistence) */
|
|
47
|
+
const autoYesStates = globalThis.__autoYesStates ??
|
|
48
|
+
(globalThis.__autoYesStates = new Map());
|
|
49
|
+
/** In-memory storage for poller states (globalThis for hot reload persistence) */
|
|
50
|
+
const autoYesPollerStates = globalThis.__autoYesPollerStates ??
|
|
51
|
+
(globalThis.__autoYesPollerStates = new Map());
|
|
52
|
+
// =============================================================================
|
|
53
|
+
// Utility Functions
|
|
54
|
+
// =============================================================================
|
|
55
|
+
/**
|
|
56
|
+
* Validate worktree ID format (security measure)
|
|
57
|
+
* Only allows alphanumeric characters, hyphens, and underscores.
|
|
58
|
+
*/
|
|
59
|
+
function isValidWorktreeId(worktreeId) {
|
|
60
|
+
if (!worktreeId || worktreeId.length === 0)
|
|
61
|
+
return false;
|
|
62
|
+
return WORKTREE_ID_PATTERN.test(worktreeId);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Calculate backoff interval based on consecutive errors
|
|
66
|
+
*/
|
|
67
|
+
function calculateBackoffInterval(consecutiveErrors) {
|
|
68
|
+
if (consecutiveErrors < exports.MAX_CONSECUTIVE_ERRORS) {
|
|
69
|
+
return exports.POLLING_INTERVAL_MS;
|
|
70
|
+
}
|
|
71
|
+
// Exponential backoff: 2^(errors - 4) * 2000
|
|
72
|
+
// 5 errors: 2^1 * 2000 = 4000
|
|
73
|
+
// 6 errors: 2^2 * 2000 = 8000
|
|
74
|
+
// etc.
|
|
75
|
+
const backoffMultiplier = Math.pow(2, consecutiveErrors - exports.MAX_CONSECUTIVE_ERRORS + 1);
|
|
76
|
+
const backoffMs = exports.POLLING_INTERVAL_MS * backoffMultiplier;
|
|
77
|
+
return Math.min(backoffMs, exports.MAX_BACKOFF_MS);
|
|
78
|
+
}
|
|
79
|
+
// =============================================================================
|
|
80
|
+
// Auto-Yes State Management (Existing)
|
|
81
|
+
// =============================================================================
|
|
82
|
+
/**
|
|
83
|
+
* Check if an auto-yes state has expired
|
|
84
|
+
*/
|
|
85
|
+
function isAutoYesExpired(state) {
|
|
86
|
+
return Date.now() > state.expiresAt;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Get the auto-yes state for a worktree
|
|
90
|
+
* Returns null if no state exists or if expired (auto-disables on expiry)
|
|
91
|
+
*/
|
|
92
|
+
function getAutoYesState(worktreeId) {
|
|
93
|
+
const state = autoYesStates.get(worktreeId);
|
|
94
|
+
if (!state)
|
|
95
|
+
return null;
|
|
96
|
+
// Auto-disable if expired
|
|
97
|
+
if (isAutoYesExpired(state)) {
|
|
98
|
+
const disabledState = {
|
|
99
|
+
...state,
|
|
100
|
+
enabled: false,
|
|
101
|
+
};
|
|
102
|
+
autoYesStates.set(worktreeId, disabledState);
|
|
103
|
+
return disabledState;
|
|
104
|
+
}
|
|
105
|
+
return state;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Set the auto-yes enabled state for a worktree
|
|
109
|
+
*/
|
|
110
|
+
function setAutoYesEnabled(worktreeId, enabled) {
|
|
111
|
+
if (enabled) {
|
|
112
|
+
const now = Date.now();
|
|
113
|
+
const state = {
|
|
114
|
+
enabled: true,
|
|
115
|
+
enabledAt: now,
|
|
116
|
+
expiresAt: now + AUTO_YES_TIMEOUT_MS,
|
|
117
|
+
};
|
|
118
|
+
autoYesStates.set(worktreeId, state);
|
|
119
|
+
return state;
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
const existing = autoYesStates.get(worktreeId);
|
|
123
|
+
const state = {
|
|
124
|
+
enabled: false,
|
|
125
|
+
enabledAt: existing?.enabledAt ?? 0,
|
|
126
|
+
expiresAt: existing?.expiresAt ?? 0,
|
|
127
|
+
};
|
|
128
|
+
autoYesStates.set(worktreeId, state);
|
|
129
|
+
return state;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Clear all auto-yes states (for testing)
|
|
134
|
+
*/
|
|
135
|
+
function clearAllAutoYesStates() {
|
|
136
|
+
autoYesStates.clear();
|
|
137
|
+
}
|
|
138
|
+
// =============================================================================
|
|
139
|
+
// Server-side Polling (Issue #138)
|
|
140
|
+
// =============================================================================
|
|
141
|
+
/**
|
|
142
|
+
* Get the number of active pollers
|
|
143
|
+
*/
|
|
144
|
+
function getActivePollerCount() {
|
|
145
|
+
return autoYesPollerStates.size;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Clear all poller states (for testing)
|
|
149
|
+
*/
|
|
150
|
+
function clearAllPollerStates() {
|
|
151
|
+
stopAllAutoYesPolling();
|
|
152
|
+
autoYesPollerStates.clear();
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Get the last server response timestamp for a worktree
|
|
156
|
+
* Used by clients to prevent duplicate responses
|
|
157
|
+
*/
|
|
158
|
+
function getLastServerResponseTimestamp(worktreeId) {
|
|
159
|
+
const pollerState = autoYesPollerStates.get(worktreeId);
|
|
160
|
+
return pollerState?.lastServerResponseTimestamp ?? null;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Update the last server response timestamp
|
|
164
|
+
*/
|
|
165
|
+
function updateLastServerResponseTimestamp(worktreeId, timestamp) {
|
|
166
|
+
const pollerState = autoYesPollerStates.get(worktreeId);
|
|
167
|
+
if (pollerState) {
|
|
168
|
+
pollerState.lastServerResponseTimestamp = timestamp;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Reset error count for a poller
|
|
173
|
+
*/
|
|
174
|
+
function resetErrorCount(worktreeId) {
|
|
175
|
+
const pollerState = autoYesPollerStates.get(worktreeId);
|
|
176
|
+
if (pollerState) {
|
|
177
|
+
pollerState.consecutiveErrors = 0;
|
|
178
|
+
pollerState.currentInterval = exports.POLLING_INTERVAL_MS;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Increment error count and apply backoff if needed
|
|
183
|
+
*/
|
|
184
|
+
function incrementErrorCount(worktreeId) {
|
|
185
|
+
const pollerState = autoYesPollerStates.get(worktreeId);
|
|
186
|
+
if (pollerState) {
|
|
187
|
+
pollerState.consecutiveErrors++;
|
|
188
|
+
pollerState.currentInterval = calculateBackoffInterval(pollerState.consecutiveErrors);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Internal polling function (setTimeout recursive)
|
|
193
|
+
*/
|
|
194
|
+
async function pollAutoYes(worktreeId, cliToolId) {
|
|
195
|
+
// Check if poller was stopped
|
|
196
|
+
const pollerState = autoYesPollerStates.get(worktreeId);
|
|
197
|
+
if (!pollerState)
|
|
198
|
+
return;
|
|
199
|
+
// Check if auto-yes is still enabled
|
|
200
|
+
const autoYesState = getAutoYesState(worktreeId);
|
|
201
|
+
if (!autoYesState?.enabled || isAutoYesExpired(autoYesState)) {
|
|
202
|
+
stopAutoYesPolling(worktreeId);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
try {
|
|
206
|
+
// 1. Capture tmux output
|
|
207
|
+
const output = await (0, cli_session_1.captureSessionOutput)(worktreeId, cliToolId, 5000);
|
|
208
|
+
// 2. Strip ANSI codes and detect prompt
|
|
209
|
+
const cleanOutput = (0, cli_patterns_1.stripAnsi)(output);
|
|
210
|
+
const promptDetection = (0, prompt_detector_1.detectPrompt)(cleanOutput);
|
|
211
|
+
if (!promptDetection.isPrompt || !promptDetection.promptData) {
|
|
212
|
+
// No prompt detected, schedule next poll
|
|
213
|
+
scheduleNextPoll(worktreeId, cliToolId);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
// 3. Resolve auto answer
|
|
217
|
+
const answer = (0, auto_yes_resolver_1.resolveAutoAnswer)(promptDetection.promptData);
|
|
218
|
+
if (answer === null) {
|
|
219
|
+
// Cannot auto-answer this prompt
|
|
220
|
+
scheduleNextPoll(worktreeId, cliToolId);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
// 4. Send answer to tmux
|
|
224
|
+
const manager = manager_1.CLIToolManager.getInstance();
|
|
225
|
+
const cliTool = manager.getTool(cliToolId);
|
|
226
|
+
const sessionName = cliTool.getSessionName(worktreeId);
|
|
227
|
+
// Send answer followed by Enter
|
|
228
|
+
await (0, tmux_1.sendKeys)(sessionName, answer, false);
|
|
229
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
230
|
+
await (0, tmux_1.sendKeys)(sessionName, '', true);
|
|
231
|
+
// 5. Update timestamp
|
|
232
|
+
updateLastServerResponseTimestamp(worktreeId, Date.now());
|
|
233
|
+
// 6. Reset error count on success
|
|
234
|
+
resetErrorCount(worktreeId);
|
|
235
|
+
// Log success (without sensitive content)
|
|
236
|
+
console.info(`[Auto-Yes Poller] Sent response for worktree: ${worktreeId}`);
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
// Increment error count on failure
|
|
240
|
+
incrementErrorCount(worktreeId);
|
|
241
|
+
// Log error (without sensitive details)
|
|
242
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
243
|
+
console.warn(`[Auto-Yes Poller] Error for worktree ${worktreeId}: ${errorMessage}`);
|
|
244
|
+
}
|
|
245
|
+
// Schedule next poll
|
|
246
|
+
scheduleNextPoll(worktreeId, cliToolId);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Schedule the next polling iteration
|
|
250
|
+
*/
|
|
251
|
+
function scheduleNextPoll(worktreeId, cliToolId) {
|
|
252
|
+
const pollerState = autoYesPollerStates.get(worktreeId);
|
|
253
|
+
if (!pollerState)
|
|
254
|
+
return;
|
|
255
|
+
pollerState.timerId = setTimeout(() => {
|
|
256
|
+
pollAutoYes(worktreeId, cliToolId);
|
|
257
|
+
}, pollerState.currentInterval);
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Start server-side auto-yes polling for a worktree
|
|
261
|
+
*/
|
|
262
|
+
function startAutoYesPolling(worktreeId, cliToolId) {
|
|
263
|
+
// Validate worktree ID (security)
|
|
264
|
+
if (!isValidWorktreeId(worktreeId)) {
|
|
265
|
+
return { started: false, reason: 'invalid worktree ID' };
|
|
266
|
+
}
|
|
267
|
+
// Check if auto-yes is enabled
|
|
268
|
+
const autoYesState = getAutoYesState(worktreeId);
|
|
269
|
+
if (!autoYesState?.enabled) {
|
|
270
|
+
return { started: false, reason: 'auto-yes not enabled' };
|
|
271
|
+
}
|
|
272
|
+
// Check concurrent poller limit (DoS protection)
|
|
273
|
+
// If this worktree already has a poller, don't count it toward the limit
|
|
274
|
+
const existingPoller = autoYesPollerStates.has(worktreeId);
|
|
275
|
+
if (!existingPoller && autoYesPollerStates.size >= exports.MAX_CONCURRENT_POLLERS) {
|
|
276
|
+
return { started: false, reason: 'max concurrent pollers reached' };
|
|
277
|
+
}
|
|
278
|
+
// Stop existing poller if any
|
|
279
|
+
if (existingPoller) {
|
|
280
|
+
stopAutoYesPolling(worktreeId);
|
|
281
|
+
}
|
|
282
|
+
// Create new poller state
|
|
283
|
+
const pollerState = {
|
|
284
|
+
timerId: null,
|
|
285
|
+
cliToolId,
|
|
286
|
+
consecutiveErrors: 0,
|
|
287
|
+
currentInterval: exports.POLLING_INTERVAL_MS,
|
|
288
|
+
lastServerResponseTimestamp: null,
|
|
289
|
+
};
|
|
290
|
+
autoYesPollerStates.set(worktreeId, pollerState);
|
|
291
|
+
// Start polling immediately
|
|
292
|
+
pollerState.timerId = setTimeout(() => {
|
|
293
|
+
pollAutoYes(worktreeId, cliToolId);
|
|
294
|
+
}, exports.POLLING_INTERVAL_MS);
|
|
295
|
+
console.info(`[Auto-Yes Poller] Started for worktree: ${worktreeId}, cliTool: ${cliToolId}`);
|
|
296
|
+
return { started: true };
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Stop server-side auto-yes polling for a worktree
|
|
300
|
+
*/
|
|
301
|
+
function stopAutoYesPolling(worktreeId) {
|
|
302
|
+
const pollerState = autoYesPollerStates.get(worktreeId);
|
|
303
|
+
if (!pollerState)
|
|
304
|
+
return;
|
|
305
|
+
// Clear timer
|
|
306
|
+
if (pollerState.timerId) {
|
|
307
|
+
clearTimeout(pollerState.timerId);
|
|
308
|
+
}
|
|
309
|
+
// Remove state
|
|
310
|
+
autoYesPollerStates.delete(worktreeId);
|
|
311
|
+
console.info(`[Auto-Yes Poller] Stopped for worktree: ${worktreeId}`);
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Stop all server-side auto-yes polling (graceful shutdown)
|
|
315
|
+
*/
|
|
316
|
+
function stopAllAutoYesPolling() {
|
|
317
|
+
for (const [worktreeId, pollerState] of autoYesPollerStates.entries()) {
|
|
318
|
+
if (pollerState.timerId) {
|
|
319
|
+
clearTimeout(pollerState.timerId);
|
|
320
|
+
}
|
|
321
|
+
console.info(`[Auto-Yes Poller] Stopped for worktree: ${worktreeId} (shutdown)`);
|
|
322
|
+
}
|
|
323
|
+
autoYesPollerStates.clear();
|
|
324
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Auto Yes Resolver - Determines automatic answer for prompt data
|
|
4
|
+
*
|
|
5
|
+
* Rules:
|
|
6
|
+
* - yes/no prompt -> 'y'
|
|
7
|
+
* - multiple_choice with default option -> default option number
|
|
8
|
+
* - multiple_choice without default -> first option number
|
|
9
|
+
* - option requiring text input -> null (skip)
|
|
10
|
+
* - unknown type -> null (skip)
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.resolveAutoAnswer = resolveAutoAnswer;
|
|
14
|
+
/**
|
|
15
|
+
* Resolve the automatic answer for a given prompt
|
|
16
|
+
* @returns The answer string to send, or null if auto-answer is not possible
|
|
17
|
+
*/
|
|
18
|
+
function resolveAutoAnswer(promptData) {
|
|
19
|
+
if (promptData.type === 'yes_no') {
|
|
20
|
+
return 'y';
|
|
21
|
+
}
|
|
22
|
+
if (promptData.type === 'multiple_choice') {
|
|
23
|
+
const defaultOpt = promptData.options.find(o => o.isDefault);
|
|
24
|
+
const target = defaultOpt ?? promptData.options[0];
|
|
25
|
+
if (!target) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
if (target.requiresTextInput) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
return target.number.toString();
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
@@ -4,19 +4,84 @@
|
|
|
4
4
|
* Manages Claude CLI sessions within tmux for each worktree
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.CLAUDE_PROMPT_POLL_INTERVAL = exports.CLAUDE_PROMPT_WAIT_TIMEOUT = exports.CLAUDE_POST_PROMPT_DELAY = exports.CLAUDE_INIT_POLL_INTERVAL = exports.CLAUDE_INIT_TIMEOUT = void 0;
|
|
7
8
|
exports.getSessionName = getSessionName;
|
|
8
9
|
exports.isClaudeInstalled = isClaudeInstalled;
|
|
9
10
|
exports.isClaudeRunning = isClaudeRunning;
|
|
10
11
|
exports.getClaudeSessionState = getClaudeSessionState;
|
|
12
|
+
exports.waitForPrompt = waitForPrompt;
|
|
11
13
|
exports.startClaudeSession = startClaudeSession;
|
|
12
14
|
exports.sendMessageToClaude = sendMessageToClaude;
|
|
13
15
|
exports.captureClaudeOutput = captureClaudeOutput;
|
|
14
16
|
exports.stopClaudeSession = stopClaudeSession;
|
|
15
17
|
exports.restartClaudeSession = restartClaudeSession;
|
|
16
18
|
const tmux_1 = require("./tmux");
|
|
19
|
+
const cli_patterns_1 = require("./cli-patterns");
|
|
17
20
|
const child_process_1 = require("child_process");
|
|
18
21
|
const util_1 = require("util");
|
|
19
22
|
const execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
23
|
+
// ----- Helper Functions -----
|
|
24
|
+
/**
|
|
25
|
+
* Extract error message from unknown error type
|
|
26
|
+
* Provides consistent error message extraction across the module (DRY)
|
|
27
|
+
*
|
|
28
|
+
* @param error - Unknown error object
|
|
29
|
+
* @returns Error message string
|
|
30
|
+
*/
|
|
31
|
+
function getErrorMessage(error) {
|
|
32
|
+
return error instanceof Error ? error.message : String(error);
|
|
33
|
+
}
|
|
34
|
+
// ----- Timeout and Polling Constants (OCP-001) -----
|
|
35
|
+
// These constants are exported to allow configuration and testing.
|
|
36
|
+
// Changing these values affects Claude CLI session startup behavior.
|
|
37
|
+
/**
|
|
38
|
+
* Claude CLI initialization max wait time (milliseconds)
|
|
39
|
+
*
|
|
40
|
+
* This timeout allows sufficient time for Claude CLI to:
|
|
41
|
+
* - Load and initialize its internal state
|
|
42
|
+
* - Authenticate with Anthropic servers (if needed)
|
|
43
|
+
* - Display the interactive prompt
|
|
44
|
+
*
|
|
45
|
+
* 15 seconds provides headroom for slower networks or cold starts.
|
|
46
|
+
*/
|
|
47
|
+
exports.CLAUDE_INIT_TIMEOUT = 15000;
|
|
48
|
+
/**
|
|
49
|
+
* Initialization polling interval (milliseconds)
|
|
50
|
+
*
|
|
51
|
+
* How frequently we check if Claude CLI has finished initializing.
|
|
52
|
+
* 300ms balances responsiveness with avoiding excessive polling overhead.
|
|
53
|
+
*/
|
|
54
|
+
exports.CLAUDE_INIT_POLL_INTERVAL = 300;
|
|
55
|
+
/**
|
|
56
|
+
* Stability delay after prompt detection (milliseconds)
|
|
57
|
+
*
|
|
58
|
+
* This delay is necessary because Claude CLI renders its UI progressively:
|
|
59
|
+
* 1. The prompt character (> or U+276F) appears first
|
|
60
|
+
* 2. Additional UI elements (tips, suggestions) may render afterward
|
|
61
|
+
* 3. Sending input too quickly can interrupt this rendering process
|
|
62
|
+
*
|
|
63
|
+
* The 500ms value was empirically determined to provide sufficient buffer
|
|
64
|
+
* for Claude CLI to complete its initialization rendering while maintaining
|
|
65
|
+
* responsive user experience. (DOC-001)
|
|
66
|
+
*
|
|
67
|
+
* @see Issue #152 - First message not being sent after session start
|
|
68
|
+
*/
|
|
69
|
+
exports.CLAUDE_POST_PROMPT_DELAY = 500;
|
|
70
|
+
/**
|
|
71
|
+
* Prompt wait timeout before message send (milliseconds)
|
|
72
|
+
*
|
|
73
|
+
* When sending a message, we first verify Claude is at a prompt state.
|
|
74
|
+
* This timeout limits how long we wait for Claude to return to prompt
|
|
75
|
+
* if it's still processing a previous request.
|
|
76
|
+
*/
|
|
77
|
+
exports.CLAUDE_PROMPT_WAIT_TIMEOUT = 5000;
|
|
78
|
+
/**
|
|
79
|
+
* Prompt wait polling interval (milliseconds)
|
|
80
|
+
*
|
|
81
|
+
* How frequently we check for prompt state before sending messages.
|
|
82
|
+
* 200ms provides quick response while minimizing CPU usage.
|
|
83
|
+
*/
|
|
84
|
+
exports.CLAUDE_PROMPT_POLL_INTERVAL = 200;
|
|
20
85
|
/**
|
|
21
86
|
* Cached Claude CLI path
|
|
22
87
|
*/
|
|
@@ -122,6 +187,35 @@ async function getClaudeSessionState(worktreeId) {
|
|
|
122
187
|
lastActivity: new Date(),
|
|
123
188
|
};
|
|
124
189
|
}
|
|
190
|
+
/**
|
|
191
|
+
* Wait for session to be at prompt state
|
|
192
|
+
* Polls for prompt detection using CLAUDE_PROMPT_PATTERN (DRY-001)
|
|
193
|
+
*
|
|
194
|
+
* @param sessionName - tmux session name
|
|
195
|
+
* @param timeout - Timeout in milliseconds (default: CLAUDE_PROMPT_WAIT_TIMEOUT)
|
|
196
|
+
* @throws {Error} If prompt is not detected within timeout
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* ```typescript
|
|
200
|
+
* await waitForPrompt('mcbd-claude-feature-foo');
|
|
201
|
+
* // Session is now ready to receive input
|
|
202
|
+
* ```
|
|
203
|
+
*/
|
|
204
|
+
async function waitForPrompt(sessionName, timeout = exports.CLAUDE_PROMPT_WAIT_TIMEOUT) {
|
|
205
|
+
const startTime = Date.now();
|
|
206
|
+
const pollInterval = exports.CLAUDE_PROMPT_POLL_INTERVAL;
|
|
207
|
+
while (Date.now() - startTime < timeout) {
|
|
208
|
+
// Use -50 lines to capture more context including status bars
|
|
209
|
+
const output = await (0, tmux_1.capturePane)(sessionName, { startLine: -50 });
|
|
210
|
+
// DRY-001: Use CLAUDE_PROMPT_PATTERN from cli-patterns.ts
|
|
211
|
+
// Strip ANSI escape sequences before pattern matching (Issue #152)
|
|
212
|
+
if (cli_patterns_1.CLAUDE_PROMPT_PATTERN.test((0, cli_patterns_1.stripAnsi)(output))) {
|
|
213
|
+
return; // Prompt detected
|
|
214
|
+
}
|
|
215
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
216
|
+
}
|
|
217
|
+
throw new Error(`Prompt detection timeout (${timeout}ms)`);
|
|
218
|
+
}
|
|
125
219
|
/**
|
|
126
220
|
* Start a Claude CLI session in tmux
|
|
127
221
|
*
|
|
@@ -162,18 +256,25 @@ async function startClaudeSession(options) {
|
|
|
162
256
|
const claudePath = await getClaudePath();
|
|
163
257
|
// Start Claude CLI in interactive mode using dynamically resolved path
|
|
164
258
|
await (0, tmux_1.sendKeys)(sessionName, claudePath, true);
|
|
165
|
-
// Wait for Claude to initialize with dynamic detection
|
|
166
|
-
//
|
|
167
|
-
const maxWaitTime =
|
|
168
|
-
const pollInterval =
|
|
259
|
+
// Wait for Claude to initialize with dynamic detection (OCP-001)
|
|
260
|
+
// Use constants instead of hardcoded values
|
|
261
|
+
const maxWaitTime = exports.CLAUDE_INIT_TIMEOUT;
|
|
262
|
+
const pollInterval = exports.CLAUDE_INIT_POLL_INTERVAL;
|
|
169
263
|
const startTime = Date.now();
|
|
264
|
+
let initialized = false;
|
|
170
265
|
while (Date.now() - startTime < maxWaitTime) {
|
|
171
266
|
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
172
267
|
try {
|
|
173
268
|
const output = await (0, tmux_1.capturePane)(sessionName, { startLine: -50 });
|
|
174
|
-
// Claude is ready when we see the prompt
|
|
175
|
-
|
|
176
|
-
|
|
269
|
+
// Claude is ready when we see the prompt or separator line (DRY-001, DRY-002)
|
|
270
|
+
// Use patterns from cli-patterns.ts for consistency
|
|
271
|
+
// Strip ANSI escape sequences before pattern matching (Issue #152)
|
|
272
|
+
const cleanOutput = (0, cli_patterns_1.stripAnsi)(output);
|
|
273
|
+
if (cli_patterns_1.CLAUDE_PROMPT_PATTERN.test(cleanOutput) || cli_patterns_1.CLAUDE_SEPARATOR_PATTERN.test(cleanOutput)) {
|
|
274
|
+
// Wait for stability after prompt detection (CONS-007, DOC-001)
|
|
275
|
+
await new Promise((resolve) => setTimeout(resolve, exports.CLAUDE_POST_PROMPT_DELAY));
|
|
276
|
+
console.log(`Claude initialized in ${Date.now() - startTime}ms`);
|
|
277
|
+
initialized = true;
|
|
177
278
|
break;
|
|
178
279
|
}
|
|
179
280
|
}
|
|
@@ -181,11 +282,14 @@ async function startClaudeSession(options) {
|
|
|
181
282
|
// Ignore capture errors during initialization
|
|
182
283
|
}
|
|
183
284
|
}
|
|
184
|
-
|
|
285
|
+
// Throw error on timeout instead of silently continuing (CONS-005, IMP-001)
|
|
286
|
+
if (!initialized) {
|
|
287
|
+
throw new Error(`Claude initialization timeout (${exports.CLAUDE_INIT_TIMEOUT}ms)`);
|
|
288
|
+
}
|
|
289
|
+
console.log(`Started Claude session: ${sessionName}`);
|
|
185
290
|
}
|
|
186
291
|
catch (error) {
|
|
187
|
-
|
|
188
|
-
throw new Error(`Failed to start Claude session: ${errorMessage}`);
|
|
292
|
+
throw new Error(`Failed to start Claude session: ${getErrorMessage(error)}`);
|
|
189
293
|
}
|
|
190
294
|
}
|
|
191
295
|
/**
|
|
@@ -207,21 +311,25 @@ async function sendMessageToClaude(worktreeId, message) {
|
|
|
207
311
|
if (!exists) {
|
|
208
312
|
throw new Error(`Claude session ${sessionName} does not exist. Start the session first.`);
|
|
209
313
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
//
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
314
|
+
// Verify prompt state before sending (CONS-006, DRY-001)
|
|
315
|
+
// Use -50 lines to ensure we capture the prompt even with status bars
|
|
316
|
+
const output = await (0, tmux_1.capturePane)(sessionName, { startLine: -50 });
|
|
317
|
+
// Strip ANSI escape sequences before pattern matching (Issue #152)
|
|
318
|
+
if (!cli_patterns_1.CLAUDE_PROMPT_PATTERN.test((0, cli_patterns_1.stripAnsi)(output))) {
|
|
319
|
+
// Wait for prompt if not at prompt state
|
|
320
|
+
// Use longer timeout (10s) to handle slow responses
|
|
321
|
+
try {
|
|
322
|
+
await waitForPrompt(sessionName, 10000);
|
|
323
|
+
}
|
|
324
|
+
catch {
|
|
325
|
+
// Log warning but don't block - Claude might be in a special state
|
|
326
|
+
console.warn(`[sendMessageToClaude] Prompt not detected, sending anyway`);
|
|
327
|
+
}
|
|
224
328
|
}
|
|
329
|
+
// Send message using sendKeys consistently (CONS-001)
|
|
330
|
+
await (0, tmux_1.sendKeys)(sessionName, message, false); // Message without Enter
|
|
331
|
+
await (0, tmux_1.sendKeys)(sessionName, '', true); // Enter key
|
|
332
|
+
console.log(`Sent message to Claude session: ${sessionName}`);
|
|
225
333
|
}
|
|
226
334
|
/**
|
|
227
335
|
* Capture Claude session output
|
|
@@ -247,8 +355,7 @@ async function captureClaudeOutput(worktreeId, lines = 1000) {
|
|
|
247
355
|
return await (0, tmux_1.capturePane)(sessionName, { startLine: -lines });
|
|
248
356
|
}
|
|
249
357
|
catch (error) {
|
|
250
|
-
|
|
251
|
-
throw new Error(`Failed to capture Claude output: ${errorMessage}`);
|
|
358
|
+
throw new Error(`Failed to capture Claude output: ${getErrorMessage(error)}`);
|
|
252
359
|
}
|
|
253
360
|
}
|
|
254
361
|
/**
|
|
@@ -282,8 +389,7 @@ async function stopClaudeSession(worktreeId) {
|
|
|
282
389
|
return killed;
|
|
283
390
|
}
|
|
284
391
|
catch (error) {
|
|
285
|
-
|
|
286
|
-
console.error(`Error stopping Claude session: ${errorMessage}`);
|
|
392
|
+
console.error(`Error stopping Claude session: ${getErrorMessage(error)}`);
|
|
287
393
|
return false;
|
|
288
394
|
}
|
|
289
395
|
}
|
|
@@ -33,8 +33,13 @@ exports.CODEX_THINKING_PATTERN = /•\s*(Planning|Searching|Exploring|Running|Th
|
|
|
33
33
|
/**
|
|
34
34
|
* Claude prompt pattern (waiting for input)
|
|
35
35
|
* Supports both legacy '>' and new '❯' (U+276F) prompt characters
|
|
36
|
+
* Issue #132: Also matches prompts with recommended commands (e.g., "❯ /work-plan")
|
|
37
|
+
*
|
|
38
|
+
* Matches:
|
|
39
|
+
* - Empty prompt: "❯ " or "> "
|
|
40
|
+
* - Prompt with command: "❯ /work-plan" or "> npm install"
|
|
36
41
|
*/
|
|
37
|
-
exports.CLAUDE_PROMPT_PATTERN = /^[>❯]\s
|
|
42
|
+
exports.CLAUDE_PROMPT_PATTERN = /^[>❯](\s*$|\s+\S)/m;
|
|
38
43
|
/**
|
|
39
44
|
* Claude separator pattern
|
|
40
45
|
*/
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Database instance singleton
|
|
4
4
|
* Provides a shared database connection for API routes
|
|
5
|
+
*
|
|
6
|
+
* Issue #135: DB path resolution fix
|
|
7
|
+
* Uses getEnv().CM_DB_PATH for consistent path handling across all install types
|
|
5
8
|
*/
|
|
6
9
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
7
10
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
@@ -12,10 +15,14 @@ exports.closeDbInstance = closeDbInstance;
|
|
|
12
15
|
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
13
16
|
const path_1 = __importDefault(require("path"));
|
|
14
17
|
const db_migrations_1 = require("./db-migrations");
|
|
18
|
+
const env_1 = require("./env");
|
|
15
19
|
let dbInstance = null;
|
|
16
20
|
/**
|
|
17
21
|
* Get or create the database instance
|
|
18
22
|
*
|
|
23
|
+
* Issue #135: Now uses getEnv().CM_DB_PATH instead of direct DATABASE_PATH access
|
|
24
|
+
* This ensures consistent DB path resolution for both global and local installs.
|
|
25
|
+
*
|
|
19
26
|
* @returns Singleton database instance
|
|
20
27
|
*
|
|
21
28
|
* @example
|
|
@@ -26,13 +33,16 @@ let dbInstance = null;
|
|
|
26
33
|
*/
|
|
27
34
|
function getDbInstance() {
|
|
28
35
|
if (!dbInstance) {
|
|
29
|
-
|
|
36
|
+
// Issue #135: Use getEnv() for consistent DB path resolution
|
|
37
|
+
const env = (0, env_1.getEnv)();
|
|
38
|
+
const dbPath = env.CM_DB_PATH;
|
|
30
39
|
// Ensure the database directory exists
|
|
31
40
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
32
41
|
const fs = require('fs');
|
|
33
42
|
const dir = path_1.default.dirname(dbPath);
|
|
34
43
|
if (!fs.existsSync(dir)) {
|
|
35
|
-
|
|
44
|
+
// SEC-003: Set directory permissions to 0o700 (owner only)
|
|
45
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
36
46
|
}
|
|
37
47
|
dbInstance = new better_sqlite3_1.default(dbPath);
|
|
38
48
|
(0, db_migrations_1.runMigrations)(dbInstance);
|