@cloudcli-ai/cloudcli 1.29.2 → 1.29.4
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/dist/assets/{index-ByHZ8-LL.css → index-BBAE1OJ_.css} +1 -1
- package/dist/assets/{index-Ile1bTCU.js → index-Cyya3LhN.js} +229 -229
- package/dist/index.html +2 -2
- package/dist-server/server/claude-sdk.js +702 -0
- package/dist-server/server/claude-sdk.js.map +1 -0
- package/dist-server/server/cli.js +642 -0
- package/dist-server/server/cli.js.map +1 -0
- package/dist-server/server/constants/config.js +6 -0
- package/dist-server/server/constants/config.js.map +1 -0
- package/dist-server/server/cursor-cli.js +277 -0
- package/dist-server/server/cursor-cli.js.map +1 -0
- package/dist-server/server/database/db.js +535 -0
- package/dist-server/server/database/db.js.map +1 -0
- package/dist-server/server/database/schema.js +97 -0
- package/dist-server/server/database/schema.js.map +1 -0
- package/dist-server/server/gemini-cli.js +408 -0
- package/dist-server/server/gemini-cli.js.map +1 -0
- package/dist-server/server/gemini-response-handler.js +72 -0
- package/dist-server/server/gemini-response-handler.js.map +1 -0
- package/dist-server/server/index.js +2175 -0
- package/dist-server/server/index.js.map +1 -0
- package/dist-server/server/load-env.js +32 -0
- package/dist-server/server/load-env.js.map +1 -0
- package/dist-server/server/middleware/auth.js +111 -0
- package/dist-server/server/middleware/auth.js.map +1 -0
- package/dist-server/server/openai-codex.js +373 -0
- package/dist-server/server/openai-codex.js.map +1 -0
- package/dist-server/server/projects.js +2325 -0
- package/dist-server/server/projects.js.map +1 -0
- package/dist-server/server/providers/claude/adapter.js +266 -0
- package/dist-server/server/providers/claude/adapter.js.map +1 -0
- package/dist-server/server/providers/claude/status.js +121 -0
- package/dist-server/server/providers/claude/status.js.map +1 -0
- package/dist-server/server/providers/codex/adapter.js +232 -0
- package/dist-server/server/providers/codex/adapter.js.map +1 -0
- package/dist-server/server/providers/codex/status.js +70 -0
- package/dist-server/server/providers/codex/status.js.map +1 -0
- package/dist-server/server/providers/cursor/adapter.js +342 -0
- package/dist-server/server/providers/cursor/adapter.js.map +1 -0
- package/dist-server/server/providers/cursor/status.js +118 -0
- package/dist-server/server/providers/cursor/status.js.map +1 -0
- package/dist-server/server/providers/gemini/adapter.js +174 -0
- package/dist-server/server/providers/gemini/adapter.js.map +1 -0
- package/dist-server/server/providers/gemini/status.js +104 -0
- package/dist-server/server/providers/gemini/status.js.map +1 -0
- package/dist-server/server/providers/registry.js +58 -0
- package/dist-server/server/providers/registry.js.map +1 -0
- package/dist-server/server/providers/types.js +117 -0
- package/dist-server/server/providers/types.js.map +1 -0
- package/dist-server/server/providers/utils.js +28 -0
- package/dist-server/server/providers/utils.js.map +1 -0
- package/dist-server/server/routes/agent.js +1147 -0
- package/dist-server/server/routes/agent.js.map +1 -0
- package/dist-server/server/routes/auth.js +117 -0
- package/dist-server/server/routes/auth.js.map +1 -0
- package/dist-server/server/routes/cli-auth.js +25 -0
- package/dist-server/server/routes/cli-auth.js.map +1 -0
- package/dist-server/server/routes/codex.js +294 -0
- package/dist-server/server/routes/codex.js.map +1 -0
- package/dist-server/server/routes/commands.js +531 -0
- package/dist-server/server/routes/commands.js.map +1 -0
- package/dist-server/server/routes/cursor.js +529 -0
- package/dist-server/server/routes/cursor.js.map +1 -0
- package/dist-server/server/routes/gemini.js +21 -0
- package/dist-server/server/routes/gemini.js.map +1 -0
- package/dist-server/server/routes/git.js +1259 -0
- package/dist-server/server/routes/git.js.map +1 -0
- package/dist-server/server/routes/mcp-utils.js +46 -0
- package/dist-server/server/routes/mcp-utils.js.map +1 -0
- package/dist-server/server/routes/mcp.js +480 -0
- package/dist-server/server/routes/mcp.js.map +1 -0
- package/dist-server/server/routes/messages.js +56 -0
- package/dist-server/server/routes/messages.js.map +1 -0
- package/dist-server/server/routes/plugins.js +266 -0
- package/dist-server/server/routes/plugins.js.map +1 -0
- package/dist-server/server/routes/projects.js +505 -0
- package/dist-server/server/routes/projects.js.map +1 -0
- package/dist-server/server/routes/settings.js +249 -0
- package/dist-server/server/routes/settings.js.map +1 -0
- package/dist-server/server/routes/taskmaster.js +1832 -0
- package/dist-server/server/routes/taskmaster.js.map +1 -0
- package/dist-server/server/routes/user.js +115 -0
- package/dist-server/server/routes/user.js.map +1 -0
- package/dist-server/server/services/notification-orchestrator.js +177 -0
- package/dist-server/server/services/notification-orchestrator.js.map +1 -0
- package/dist-server/server/services/vapid-keys.js +26 -0
- package/dist-server/server/services/vapid-keys.js.map +1 -0
- package/dist-server/server/sessionManager.js +194 -0
- package/dist-server/server/sessionManager.js.map +1 -0
- package/dist-server/server/utils/colors.js +20 -0
- package/dist-server/server/utils/colors.js.map +1 -0
- package/dist-server/server/utils/commandParser.js +255 -0
- package/dist-server/server/utils/commandParser.js.map +1 -0
- package/dist-server/server/utils/frontmatter.js +16 -0
- package/dist-server/server/utils/frontmatter.js.map +1 -0
- package/dist-server/server/utils/gitConfig.js +36 -0
- package/dist-server/server/utils/gitConfig.js.map +1 -0
- package/dist-server/server/utils/mcp-detector.js +183 -0
- package/dist-server/server/utils/mcp-detector.js.map +1 -0
- package/dist-server/server/utils/plugin-loader.js +413 -0
- package/dist-server/server/utils/plugin-loader.js.map +1 -0
- package/dist-server/server/utils/plugin-process-manager.js +163 -0
- package/dist-server/server/utils/plugin-process-manager.js.map +1 -0
- package/dist-server/server/utils/runtime-paths.js +30 -0
- package/dist-server/server/utils/runtime-paths.js.map +1 -0
- package/dist-server/server/utils/taskmaster-websocket.js +118 -0
- package/dist-server/server/utils/taskmaster-websocket.js.map +1 -0
- package/dist-server/server/utils/url-detection.js +58 -0
- package/dist-server/server/utils/url-detection.js.map +1 -0
- package/dist-server/shared/modelConstants.js +87 -0
- package/dist-server/shared/modelConstants.js.map +1 -0
- package/dist-server/shared/networkHosts.js +20 -0
- package/dist-server/shared/networkHosts.js.map +1 -0
- package/package.json +23 -14
- package/server/claude-sdk.js +13 -4
- package/server/cli.js +13 -9
- package/server/cursor-cli.js +8 -1
- package/server/database/db.js +24 -61
- package/server/database/schema.js +102 -0
- package/server/gemini-cli.js +17 -1
- package/server/index.js +20 -96
- package/server/load-env.js +11 -6
- package/server/openai-codex.js +9 -1
- package/server/projects.js +38 -44
- package/server/providers/claude/status.js +136 -0
- package/server/providers/codex/status.js +78 -0
- package/server/providers/cursor/adapter.js +5 -10
- package/server/providers/cursor/status.js +128 -0
- package/server/providers/gemini/status.js +111 -0
- package/server/providers/registry.js +25 -2
- package/server/providers/types.js +13 -0
- package/server/routes/cli-auth.js +14 -421
- package/server/routes/commands.js +6 -4
- package/server/routes/cursor.js +6 -240
- package/server/tsconfig.json +33 -0
- package/server/utils/colors.js +21 -0
- package/server/utils/runtime-paths.js +37 -0
- package/server/utils/url-detection.js +71 -0
- package/shared/modelConstants.js +4 -2
- package/server/database/init.sql +0 -99
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
export const APP_CONFIG_TABLE_SQL = `CREATE TABLE IF NOT EXISTS app_config (
|
|
2
|
+
key TEXT PRIMARY KEY,
|
|
3
|
+
value TEXT NOT NULL,
|
|
4
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
5
|
+
);`;
|
|
6
|
+
|
|
7
|
+
export const USER_NOTIFICATION_PREFERENCES_TABLE_SQL = `CREATE TABLE IF NOT EXISTS user_notification_preferences (
|
|
8
|
+
user_id INTEGER PRIMARY KEY,
|
|
9
|
+
preferences_json TEXT NOT NULL,
|
|
10
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
11
|
+
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
|
12
|
+
);`;
|
|
13
|
+
|
|
14
|
+
export const VAPID_KEYS_TABLE_SQL = `CREATE TABLE IF NOT EXISTS vapid_keys (
|
|
15
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
16
|
+
public_key TEXT NOT NULL,
|
|
17
|
+
private_key TEXT NOT NULL,
|
|
18
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
19
|
+
);`;
|
|
20
|
+
|
|
21
|
+
export const PUSH_SUBSCRIPTIONS_TABLE_SQL = `CREATE TABLE IF NOT EXISTS push_subscriptions (
|
|
22
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
23
|
+
user_id INTEGER NOT NULL,
|
|
24
|
+
endpoint TEXT NOT NULL UNIQUE,
|
|
25
|
+
keys_p256dh TEXT NOT NULL,
|
|
26
|
+
keys_auth TEXT NOT NULL,
|
|
27
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
28
|
+
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
|
29
|
+
);`;
|
|
30
|
+
|
|
31
|
+
export const SESSION_NAMES_TABLE_SQL = `CREATE TABLE IF NOT EXISTS session_names (
|
|
32
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
33
|
+
session_id TEXT NOT NULL,
|
|
34
|
+
provider TEXT NOT NULL DEFAULT 'claude',
|
|
35
|
+
custom_name TEXT NOT NULL,
|
|
36
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
37
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
38
|
+
UNIQUE(session_id, provider)
|
|
39
|
+
);`;
|
|
40
|
+
|
|
41
|
+
export const SESSION_NAMES_LOOKUP_INDEX_SQL = `CREATE INDEX IF NOT EXISTS idx_session_names_lookup ON session_names(session_id, provider);`;
|
|
42
|
+
|
|
43
|
+
export const DATABASE_SCHEMA_SQL = `PRAGMA foreign_keys = ON;
|
|
44
|
+
|
|
45
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
46
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
47
|
+
username TEXT UNIQUE NOT NULL,
|
|
48
|
+
password_hash TEXT NOT NULL,
|
|
49
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
50
|
+
last_login DATETIME,
|
|
51
|
+
is_active BOOLEAN DEFAULT 1,
|
|
52
|
+
git_name TEXT,
|
|
53
|
+
git_email TEXT,
|
|
54
|
+
has_completed_onboarding BOOLEAN DEFAULT 0
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
|
|
58
|
+
CREATE INDEX IF NOT EXISTS idx_users_active ON users(is_active);
|
|
59
|
+
|
|
60
|
+
CREATE TABLE IF NOT EXISTS api_keys (
|
|
61
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
62
|
+
user_id INTEGER NOT NULL,
|
|
63
|
+
key_name TEXT NOT NULL,
|
|
64
|
+
api_key TEXT UNIQUE NOT NULL,
|
|
65
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
66
|
+
last_used DATETIME,
|
|
67
|
+
is_active BOOLEAN DEFAULT 1,
|
|
68
|
+
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
CREATE INDEX IF NOT EXISTS idx_api_keys_key ON api_keys(api_key);
|
|
72
|
+
CREATE INDEX IF NOT EXISTS idx_api_keys_user_id ON api_keys(user_id);
|
|
73
|
+
CREATE INDEX IF NOT EXISTS idx_api_keys_active ON api_keys(is_active);
|
|
74
|
+
|
|
75
|
+
CREATE TABLE IF NOT EXISTS user_credentials (
|
|
76
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
77
|
+
user_id INTEGER NOT NULL,
|
|
78
|
+
credential_name TEXT NOT NULL,
|
|
79
|
+
credential_type TEXT NOT NULL,
|
|
80
|
+
credential_value TEXT NOT NULL,
|
|
81
|
+
description TEXT,
|
|
82
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
83
|
+
is_active BOOLEAN DEFAULT 1,
|
|
84
|
+
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
CREATE INDEX IF NOT EXISTS idx_user_credentials_user_id ON user_credentials(user_id);
|
|
88
|
+
CREATE INDEX IF NOT EXISTS idx_user_credentials_type ON user_credentials(credential_type);
|
|
89
|
+
CREATE INDEX IF NOT EXISTS idx_user_credentials_active ON user_credentials(is_active);
|
|
90
|
+
|
|
91
|
+
${USER_NOTIFICATION_PREFERENCES_TABLE_SQL}
|
|
92
|
+
|
|
93
|
+
${VAPID_KEYS_TABLE_SQL}
|
|
94
|
+
|
|
95
|
+
${PUSH_SUBSCRIPTIONS_TABLE_SQL}
|
|
96
|
+
|
|
97
|
+
${SESSION_NAMES_TABLE_SQL}
|
|
98
|
+
|
|
99
|
+
${SESSION_NAMES_LOOKUP_INDEX_SQL}
|
|
100
|
+
|
|
101
|
+
${APP_CONFIG_TABLE_SQL}
|
|
102
|
+
`;
|
package/server/gemini-cli.js
CHANGED
|
@@ -10,6 +10,7 @@ import sessionManager from './sessionManager.js';
|
|
|
10
10
|
import GeminiResponseHandler from './gemini-response-handler.js';
|
|
11
11
|
import { notifyRunFailed, notifyRunStopped } from './services/notification-orchestrator.js';
|
|
12
12
|
import { createNormalizedMessage } from './providers/types.js';
|
|
13
|
+
import { getStatusChecker } from './providers/registry.js';
|
|
13
14
|
|
|
14
15
|
let activeGeminiProcesses = new Map(); // Track active processes by session ID
|
|
15
16
|
|
|
@@ -380,6 +381,15 @@ async function spawnGemini(command, options = {}, ws) {
|
|
|
380
381
|
notifyTerminalState({ code });
|
|
381
382
|
resolve();
|
|
382
383
|
} else {
|
|
384
|
+
// code 127 = shell "command not found" — check installation
|
|
385
|
+
if (code === 127) {
|
|
386
|
+
const installed = getStatusChecker('gemini')?.checkInstalled() ?? true;
|
|
387
|
+
if (!installed) {
|
|
388
|
+
const socketSessionId = typeof ws.getSessionId === 'function' ? ws.getSessionId() : finalSessionId;
|
|
389
|
+
ws.send(createNormalizedMessage({ kind: 'error', content: 'Gemini CLI is not installed. Please install it first: https://github.com/google-gemini/gemini-cli', sessionId: socketSessionId, provider: 'gemini' }));
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
383
393
|
notifyTerminalState({
|
|
384
394
|
code,
|
|
385
395
|
error: code === null ? 'Gemini CLI process was terminated or timed out' : null
|
|
@@ -394,8 +404,14 @@ async function spawnGemini(command, options = {}, ws) {
|
|
|
394
404
|
const finalSessionId = capturedSessionId || sessionId || processKey;
|
|
395
405
|
activeGeminiProcesses.delete(finalSessionId);
|
|
396
406
|
|
|
407
|
+
// Check if Gemini CLI is installed for a clearer error message
|
|
408
|
+
const installed = getStatusChecker('gemini')?.checkInstalled() ?? true;
|
|
409
|
+
const errorContent = !installed
|
|
410
|
+
? 'Gemini CLI is not installed. Please install it first: https://github.com/google-gemini/gemini-cli'
|
|
411
|
+
: error.message;
|
|
412
|
+
|
|
397
413
|
const errorSessionId = typeof ws.getSessionId === 'function' ? ws.getSessionId() : finalSessionId;
|
|
398
|
-
ws.send(createNormalizedMessage({ kind: 'error', content:
|
|
414
|
+
ws.send(createNormalizedMessage({ kind: 'error', content: errorContent, sessionId: errorSessionId, provider: 'gemini' }));
|
|
399
415
|
notifyTerminalState({ error });
|
|
400
416
|
|
|
401
417
|
reject(error);
|
package/server/index.js
CHANGED
|
@@ -3,33 +3,15 @@
|
|
|
3
3
|
import './load-env.js';
|
|
4
4
|
import fs from 'fs';
|
|
5
5
|
import path from 'path';
|
|
6
|
-
import {
|
|
7
|
-
import { dirname } from 'path';
|
|
8
|
-
|
|
9
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
-
const __dirname = dirname(__filename);
|
|
11
|
-
|
|
12
|
-
const installMode = fs.existsSync(path.join(__dirname, '..', '.git')) ? 'git' : 'npm';
|
|
13
|
-
|
|
14
|
-
// ANSI color codes for terminal output
|
|
15
|
-
const colors = {
|
|
16
|
-
reset: '\x1b[0m',
|
|
17
|
-
bright: '\x1b[1m',
|
|
18
|
-
cyan: '\x1b[36m',
|
|
19
|
-
green: '\x1b[32m',
|
|
20
|
-
yellow: '\x1b[33m',
|
|
21
|
-
blue: '\x1b[34m',
|
|
22
|
-
dim: '\x1b[2m',
|
|
23
|
-
};
|
|
6
|
+
import { findAppRoot, getModuleDir } from './utils/runtime-paths.js';
|
|
24
7
|
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
};
|
|
8
|
+
const __dirname = getModuleDir(import.meta.url);
|
|
9
|
+
// The server source runs from /server, while the compiled output runs from /dist-server/server.
|
|
10
|
+
// Resolving the app root once keeps every repo-level lookup below aligned across both layouts.
|
|
11
|
+
const APP_ROOT = findAppRoot(__dirname);
|
|
12
|
+
const installMode = fs.existsSync(path.join(APP_ROOT, '.git')) ? 'git' : 'npm';
|
|
13
|
+
|
|
14
|
+
import { c } from './utils/colors.js';
|
|
33
15
|
|
|
34
16
|
console.log('SERVER_PORT from env:', process.env.SERVER_PORT);
|
|
35
17
|
|
|
@@ -226,68 +208,7 @@ const server = http.createServer(app);
|
|
|
226
208
|
const ptySessionsMap = new Map();
|
|
227
209
|
const PTY_SESSION_TIMEOUT = 30 * 60 * 1000;
|
|
228
210
|
const SHELL_URL_PARSE_BUFFER_LIMIT = 32768;
|
|
229
|
-
|
|
230
|
-
const TRAILING_URL_PUNCTUATION_REGEX = /[)\]}>.,;:!?]+$/;
|
|
231
|
-
|
|
232
|
-
function stripAnsiSequences(value = '') {
|
|
233
|
-
return value.replace(ANSI_ESCAPE_SEQUENCE_REGEX, '');
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
function normalizeDetectedUrl(url) {
|
|
237
|
-
if (!url || typeof url !== 'string') return null;
|
|
238
|
-
|
|
239
|
-
const cleaned = url.trim().replace(TRAILING_URL_PUNCTUATION_REGEX, '');
|
|
240
|
-
if (!cleaned) return null;
|
|
241
|
-
|
|
242
|
-
try {
|
|
243
|
-
const parsed = new URL(cleaned);
|
|
244
|
-
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
245
|
-
return null;
|
|
246
|
-
}
|
|
247
|
-
return parsed.toString();
|
|
248
|
-
} catch {
|
|
249
|
-
return null;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
function extractUrlsFromText(value = '') {
|
|
254
|
-
const directMatches = value.match(/https?:\/\/[^\s<>"'`\\\x1b\x07]+/gi) || [];
|
|
255
|
-
|
|
256
|
-
// Handle wrapped terminal URLs split across lines by terminal width.
|
|
257
|
-
const wrappedMatches = [];
|
|
258
|
-
const continuationRegex = /^[A-Za-z0-9\-._~:/?#\[\]@!$&'()*+,;=%]+$/;
|
|
259
|
-
const lines = value.split(/\r?\n/);
|
|
260
|
-
for (let i = 0; i < lines.length; i++) {
|
|
261
|
-
const line = lines[i].trim();
|
|
262
|
-
const startMatch = line.match(/https?:\/\/[^\s<>"'`\\\x1b\x07]+/i);
|
|
263
|
-
if (!startMatch) continue;
|
|
264
|
-
|
|
265
|
-
let combined = startMatch[0];
|
|
266
|
-
let j = i + 1;
|
|
267
|
-
while (j < lines.length) {
|
|
268
|
-
const continuation = lines[j].trim();
|
|
269
|
-
if (!continuation) break;
|
|
270
|
-
if (!continuationRegex.test(continuation)) break;
|
|
271
|
-
combined += continuation;
|
|
272
|
-
j++;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
wrappedMatches.push(combined.replace(/\r?\n\s*/g, ''));
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
return Array.from(new Set([...directMatches, ...wrappedMatches]));
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
function shouldAutoOpenUrlFromOutput(value = '') {
|
|
282
|
-
const normalized = value.toLowerCase();
|
|
283
|
-
return (
|
|
284
|
-
normalized.includes('browser didn\'t open') ||
|
|
285
|
-
normalized.includes('open this url') ||
|
|
286
|
-
normalized.includes('continue in your browser') ||
|
|
287
|
-
normalized.includes('press enter to open') ||
|
|
288
|
-
normalized.includes('open_url:')
|
|
289
|
-
);
|
|
290
|
-
}
|
|
211
|
+
import { stripAnsiSequences, normalizeDetectedUrl, extractUrlsFromText, shouldAutoOpenUrlFromOutput } from './utils/url-detection.js';
|
|
291
212
|
|
|
292
213
|
// Single WebSocket server that handles both paths
|
|
293
214
|
const wss = new WebSocketServer({
|
|
@@ -405,11 +326,11 @@ app.use('/api/sessions', authenticateToken, messagesRoutes);
|
|
|
405
326
|
app.use('/api/agent', agentRoutes);
|
|
406
327
|
|
|
407
328
|
// Serve public files (like api-docs.html)
|
|
408
|
-
app.use(express.static(path.join(
|
|
329
|
+
app.use(express.static(path.join(APP_ROOT, 'public')));
|
|
409
330
|
|
|
410
331
|
// Static files served after API routes
|
|
411
332
|
// Add cache control: HTML files should not be cached, but assets can be cached
|
|
412
|
-
app.use(express.static(path.join(
|
|
333
|
+
app.use(express.static(path.join(APP_ROOT, 'dist'), {
|
|
413
334
|
setHeaders: (res, filePath) => {
|
|
414
335
|
if (filePath.endsWith('.html')) {
|
|
415
336
|
// Prevent HTML caching to avoid service worker issues after builds
|
|
@@ -431,7 +352,7 @@ app.use(express.static(path.join(__dirname, '../dist'), {
|
|
|
431
352
|
app.post('/api/system/update', authenticateToken, async (req, res) => {
|
|
432
353
|
try {
|
|
433
354
|
// Get the project root directory (parent of server directory)
|
|
434
|
-
const projectRoot =
|
|
355
|
+
const projectRoot = APP_ROOT;
|
|
435
356
|
|
|
436
357
|
console.log('Starting system update from directory:', projectRoot);
|
|
437
358
|
|
|
@@ -573,12 +494,15 @@ app.put('/api/sessions/:sessionId/rename', authenticateToken, async (req, res) =
|
|
|
573
494
|
}
|
|
574
495
|
});
|
|
575
496
|
|
|
576
|
-
// Delete project endpoint
|
|
497
|
+
// Delete project endpoint
|
|
498
|
+
// force=true to allow removal even when sessions exist
|
|
499
|
+
// deleteData=true to also delete session/memory files on disk (destructive)
|
|
577
500
|
app.delete('/api/projects/:projectName', authenticateToken, async (req, res) => {
|
|
578
501
|
try {
|
|
579
502
|
const { projectName } = req.params;
|
|
580
503
|
const force = req.query.force === 'true';
|
|
581
|
-
|
|
504
|
+
const deleteData = req.query.deleteData === 'true';
|
|
505
|
+
await deleteProject(projectName, force, deleteData);
|
|
582
506
|
res.json({ success: true });
|
|
583
507
|
} catch (error) {
|
|
584
508
|
res.status(500).json({ error: error.message });
|
|
@@ -2273,7 +2197,7 @@ app.get('*', (req, res) => {
|
|
|
2273
2197
|
|
|
2274
2198
|
// Only serve index.html for HTML routes, not for static assets
|
|
2275
2199
|
// Static assets should already be handled by express.static middleware above
|
|
2276
|
-
const indexPath = path.join(
|
|
2200
|
+
const indexPath = path.join(APP_ROOT, 'dist', 'index.html');
|
|
2277
2201
|
|
|
2278
2202
|
// Check if dist/index.html exists (production build available)
|
|
2279
2203
|
if (fs.existsSync(indexPath)) {
|
|
@@ -2388,7 +2312,7 @@ async function startServer() {
|
|
|
2388
2312
|
configureWebPush();
|
|
2389
2313
|
|
|
2390
2314
|
// Check if running in production mode (dist folder exists)
|
|
2391
|
-
const distIndexPath = path.join(
|
|
2315
|
+
const distIndexPath = path.join(APP_ROOT, 'dist', 'index.html');
|
|
2392
2316
|
const isProduction = fs.existsSync(distIndexPath);
|
|
2393
2317
|
|
|
2394
2318
|
// Log Claude implementation mode
|
|
@@ -2402,7 +2326,7 @@ async function startServer() {
|
|
|
2402
2326
|
console.log(`${c.info('[INFO]')} To run in development mode with hot-module replacement, go to http://${DISPLAY_HOST}:${VITE_PORT}`);
|
|
2403
2327
|
|
|
2404
2328
|
server.listen(SERVER_PORT, HOST, async () => {
|
|
2405
|
-
const appInstallPath =
|
|
2329
|
+
const appInstallPath = APP_ROOT;
|
|
2406
2330
|
|
|
2407
2331
|
console.log('');
|
|
2408
2332
|
console.log(c.dim('═'.repeat(63)));
|
package/server/load-env.js
CHANGED
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import os from 'os';
|
|
4
4
|
import path from 'path';
|
|
5
|
-
import {
|
|
6
|
-
import { dirname } from 'path';
|
|
5
|
+
import { findAppRoot, getModuleDir } from './utils/runtime-paths.js';
|
|
7
6
|
|
|
8
|
-
const
|
|
9
|
-
|
|
7
|
+
const __dirname = getModuleDir(import.meta.url);
|
|
8
|
+
// Resolve the repo/app root via the nearest /server folder so this file keeps finding the
|
|
9
|
+
// same top-level .env file from both /server/load-env.js and /dist-server/server/load-env.js.
|
|
10
|
+
const APP_ROOT = findAppRoot(__dirname);
|
|
10
11
|
|
|
11
12
|
try {
|
|
12
|
-
const envPath = path.join(
|
|
13
|
+
const envPath = path.join(APP_ROOT, '.env');
|
|
13
14
|
const envFile = fs.readFileSync(envPath, 'utf8');
|
|
14
15
|
envFile.split('\n').forEach(line => {
|
|
15
16
|
const trimmedLine = line.trim();
|
|
@@ -24,6 +25,10 @@ try {
|
|
|
24
25
|
console.log('No .env file found or error reading it:', e.message);
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
// Keep the default database in a stable user-level location so rebuilding dist-server
|
|
29
|
+
// never changes where the backend stores auth.db when DATABASE_PATH is not set explicitly.
|
|
30
|
+
const DEFAULT_DATABASE_PATH = path.join(os.homedir(), '.cloudcli', 'auth.db');
|
|
31
|
+
|
|
27
32
|
if (!process.env.DATABASE_PATH) {
|
|
28
|
-
process.env.DATABASE_PATH =
|
|
33
|
+
process.env.DATABASE_PATH = DEFAULT_DATABASE_PATH;
|
|
29
34
|
}
|
package/server/openai-codex.js
CHANGED
|
@@ -17,6 +17,7 @@ import { Codex } from '@openai/codex-sdk';
|
|
|
17
17
|
import { notifyRunFailed, notifyRunStopped } from './services/notification-orchestrator.js';
|
|
18
18
|
import { codexAdapter } from './providers/codex/adapter.js';
|
|
19
19
|
import { createNormalizedMessage } from './providers/types.js';
|
|
20
|
+
import { getStatusChecker } from './providers/registry.js';
|
|
20
21
|
|
|
21
22
|
// Track active sessions
|
|
22
23
|
const activeCodexSessions = new Map();
|
|
@@ -308,7 +309,14 @@ export async function queryCodex(command, options = {}, ws) {
|
|
|
308
309
|
|
|
309
310
|
if (!wasAborted) {
|
|
310
311
|
console.error('[Codex] Error:', error);
|
|
311
|
-
|
|
312
|
+
|
|
313
|
+
// Check if Codex SDK is available for a clearer error message
|
|
314
|
+
const installed = getStatusChecker('codex')?.checkInstalled() ?? true;
|
|
315
|
+
const errorContent = !installed
|
|
316
|
+
? 'Codex CLI is not configured. Please set up authentication first.'
|
|
317
|
+
: error.message;
|
|
318
|
+
|
|
319
|
+
sendMessage(ws, createNormalizedMessage({ kind: 'error', content: errorContent, sessionId: currentSessionId, provider: 'codex' }));
|
|
312
320
|
if (!terminalFailure) {
|
|
313
321
|
notifyRunFailed({
|
|
314
322
|
userId: ws?.userId || null,
|
package/server/projects.js
CHANGED
|
@@ -62,8 +62,7 @@ import fsSync from 'fs';
|
|
|
62
62
|
import path from 'path';
|
|
63
63
|
import readline from 'readline';
|
|
64
64
|
import crypto from 'crypto';
|
|
65
|
-
import
|
|
66
|
-
import { open } from 'sqlite';
|
|
65
|
+
import Database from 'better-sqlite3';
|
|
67
66
|
import os from 'os';
|
|
68
67
|
import sessionManager from './sessionManager.js';
|
|
69
68
|
import { applyCustomSessionNames } from './database/db.js';
|
|
@@ -1164,8 +1163,9 @@ async function isProjectEmpty(projectName) {
|
|
|
1164
1163
|
}
|
|
1165
1164
|
}
|
|
1166
1165
|
|
|
1167
|
-
//
|
|
1168
|
-
|
|
1166
|
+
// Remove a project from the UI.
|
|
1167
|
+
// When deleteData=true, also delete session/memory files on disk (destructive).
|
|
1168
|
+
async function deleteProject(projectName, force = false, deleteData = false) {
|
|
1169
1169
|
const projectDir = path.join(os.homedir(), '.claude', 'projects', projectName);
|
|
1170
1170
|
|
|
1171
1171
|
try {
|
|
@@ -1175,48 +1175,50 @@ async function deleteProject(projectName, force = false) {
|
|
|
1175
1175
|
}
|
|
1176
1176
|
|
|
1177
1177
|
const config = await loadProjectConfig();
|
|
1178
|
-
let projectPath = config[projectName]?.path || config[projectName]?.originalPath;
|
|
1179
1178
|
|
|
1180
|
-
//
|
|
1181
|
-
if (
|
|
1182
|
-
projectPath =
|
|
1183
|
-
|
|
1179
|
+
// Destructive path: delete underlying data when explicitly requested
|
|
1180
|
+
if (deleteData) {
|
|
1181
|
+
let projectPath = config[projectName]?.path || config[projectName]?.originalPath;
|
|
1182
|
+
if (!projectPath) {
|
|
1183
|
+
projectPath = await extractProjectDirectory(projectName);
|
|
1184
|
+
}
|
|
1184
1185
|
|
|
1185
|
-
|
|
1186
|
-
|
|
1186
|
+
// Remove the Claude project directory (session logs, memory, subagent data)
|
|
1187
|
+
await fs.rm(projectDir, { recursive: true, force: true });
|
|
1187
1188
|
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1189
|
+
// Delete Codex sessions associated with this project
|
|
1190
|
+
if (projectPath) {
|
|
1191
|
+
try {
|
|
1192
|
+
const codexSessions = await getCodexSessions(projectPath, { limit: 0 });
|
|
1193
|
+
for (const session of codexSessions) {
|
|
1194
|
+
try {
|
|
1195
|
+
await deleteCodexSession(session.id);
|
|
1196
|
+
} catch (err) {
|
|
1197
|
+
console.warn(`Failed to delete Codex session ${session.id}:`, err.message);
|
|
1198
|
+
}
|
|
1197
1199
|
}
|
|
1200
|
+
} catch (err) {
|
|
1201
|
+
console.warn('Failed to delete Codex sessions:', err.message);
|
|
1198
1202
|
}
|
|
1199
|
-
} catch (err) {
|
|
1200
|
-
console.warn('Failed to delete Codex sessions:', err.message);
|
|
1201
|
-
}
|
|
1202
1203
|
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1204
|
+
// Delete Cursor sessions directory if it exists
|
|
1205
|
+
try {
|
|
1206
|
+
const hash = crypto.createHash('md5').update(projectPath).digest('hex');
|
|
1207
|
+
const cursorProjectDir = path.join(os.homedir(), '.cursor', 'chats', hash);
|
|
1208
|
+
await fs.rm(cursorProjectDir, { recursive: true, force: true });
|
|
1209
|
+
} catch (err) {
|
|
1210
|
+
// Cursor dir may not exist, ignore
|
|
1211
|
+
}
|
|
1210
1212
|
}
|
|
1211
1213
|
}
|
|
1212
1214
|
|
|
1213
|
-
//
|
|
1215
|
+
// Always remove from project config
|
|
1214
1216
|
delete config[projectName];
|
|
1215
1217
|
await saveProjectConfig(config);
|
|
1216
1218
|
|
|
1217
1219
|
return true;
|
|
1218
1220
|
} catch (error) {
|
|
1219
|
-
console.error(`Error
|
|
1221
|
+
console.error(`Error removing project ${projectName}:`, error);
|
|
1220
1222
|
throw error;
|
|
1221
1223
|
}
|
|
1222
1224
|
}
|
|
@@ -1305,16 +1307,10 @@ async function getCursorSessions(projectPath) {
|
|
|
1305
1307
|
} catch (_) { }
|
|
1306
1308
|
|
|
1307
1309
|
// Open SQLite database
|
|
1308
|
-
const db =
|
|
1309
|
-
filename: storeDbPath,
|
|
1310
|
-
driver: sqlite3.Database,
|
|
1311
|
-
mode: sqlite3.OPEN_READONLY
|
|
1312
|
-
});
|
|
1310
|
+
const db = new Database(storeDbPath, { readonly: true, fileMustExist: true });
|
|
1313
1311
|
|
|
1314
1312
|
// Get metadata from meta table
|
|
1315
|
-
const metaRows =
|
|
1316
|
-
SELECT key, value FROM meta
|
|
1317
|
-
`);
|
|
1313
|
+
const metaRows = db.prepare('SELECT key, value FROM meta').all();
|
|
1318
1314
|
|
|
1319
1315
|
// Parse metadata
|
|
1320
1316
|
let metadata = {};
|
|
@@ -1336,11 +1332,9 @@ async function getCursorSessions(projectPath) {
|
|
|
1336
1332
|
}
|
|
1337
1333
|
|
|
1338
1334
|
// Get message count
|
|
1339
|
-
const messageCountResult =
|
|
1340
|
-
SELECT COUNT(*) as count FROM blobs
|
|
1341
|
-
`);
|
|
1335
|
+
const messageCountResult = db.prepare('SELECT COUNT(*) as count FROM blobs').get();
|
|
1342
1336
|
|
|
1343
|
-
|
|
1337
|
+
db.close();
|
|
1344
1338
|
|
|
1345
1339
|
// Extract session info
|
|
1346
1340
|
const sessionName = metadata.title || metadata.sessionTitle || 'Untitled Session';
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Provider Status
|
|
3
|
+
*
|
|
4
|
+
* Checks whether Claude Code CLI is installed and whether the user
|
|
5
|
+
* has valid authentication credentials.
|
|
6
|
+
*
|
|
7
|
+
* @module providers/claude/status
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { execFileSync } from 'child_process';
|
|
11
|
+
import { promises as fs } from 'fs';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
import os from 'os';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Check if Claude Code CLI is installed and available.
|
|
17
|
+
* Uses CLAUDE_CLI_PATH env var if set, otherwise looks for 'claude' in PATH.
|
|
18
|
+
* @returns {boolean}
|
|
19
|
+
*/
|
|
20
|
+
export function checkInstalled() {
|
|
21
|
+
const cliPath = process.env.CLAUDE_CLI_PATH || 'claude';
|
|
22
|
+
try {
|
|
23
|
+
execFileSync(cliPath, ['--version'], { stdio: 'ignore', timeout: 5000 });
|
|
24
|
+
return true;
|
|
25
|
+
} catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Full status check: installation + authentication.
|
|
32
|
+
* @returns {Promise<import('../types.js').ProviderStatus>}
|
|
33
|
+
*/
|
|
34
|
+
export async function checkStatus() {
|
|
35
|
+
const installed = checkInstalled();
|
|
36
|
+
|
|
37
|
+
if (!installed) {
|
|
38
|
+
return {
|
|
39
|
+
installed,
|
|
40
|
+
authenticated: false,
|
|
41
|
+
email: null,
|
|
42
|
+
method: null,
|
|
43
|
+
error: 'Claude Code CLI is not installed'
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const credentialsResult = await checkCredentials();
|
|
48
|
+
|
|
49
|
+
if (credentialsResult.authenticated) {
|
|
50
|
+
return {
|
|
51
|
+
installed,
|
|
52
|
+
authenticated: true,
|
|
53
|
+
email: credentialsResult.email || 'Authenticated',
|
|
54
|
+
method: credentialsResult.method || null,
|
|
55
|
+
error: null
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
installed,
|
|
61
|
+
authenticated: false,
|
|
62
|
+
email: credentialsResult.email || null,
|
|
63
|
+
method: credentialsResult.method || null,
|
|
64
|
+
error: credentialsResult.error || 'Not authenticated'
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ─── Internal helpers ───────────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
async function loadSettingsEnv() {
|
|
71
|
+
try {
|
|
72
|
+
const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
|
|
73
|
+
const content = await fs.readFile(settingsPath, 'utf8');
|
|
74
|
+
const settings = JSON.parse(content);
|
|
75
|
+
|
|
76
|
+
if (settings?.env && typeof settings.env === 'object') {
|
|
77
|
+
return settings.env;
|
|
78
|
+
}
|
|
79
|
+
} catch {
|
|
80
|
+
// Ignore missing or malformed settings.
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Checks Claude authentication credentials.
|
|
88
|
+
*
|
|
89
|
+
* Priority 1: ANTHROPIC_API_KEY environment variable
|
|
90
|
+
* Priority 1b: ~/.claude/settings.json env values
|
|
91
|
+
* Priority 2: ~/.claude/.credentials.json OAuth tokens
|
|
92
|
+
*/
|
|
93
|
+
async function checkCredentials() {
|
|
94
|
+
if (process.env.ANTHROPIC_API_KEY && process.env.ANTHROPIC_API_KEY.trim()) {
|
|
95
|
+
return { authenticated: true, email: 'API Key Auth', method: 'api_key' };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const settingsEnv = await loadSettingsEnv();
|
|
99
|
+
|
|
100
|
+
if (typeof settingsEnv.ANTHROPIC_API_KEY === 'string' && settingsEnv.ANTHROPIC_API_KEY.trim()) {
|
|
101
|
+
return { authenticated: true, email: 'API Key Auth', method: 'api_key' };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (typeof settingsEnv.ANTHROPIC_AUTH_TOKEN === 'string' && settingsEnv.ANTHROPIC_AUTH_TOKEN.trim()) {
|
|
105
|
+
return { authenticated: true, email: 'Configured via settings.json', method: 'api_key' };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
const credPath = path.join(os.homedir(), '.claude', '.credentials.json');
|
|
110
|
+
const content = await fs.readFile(credPath, 'utf8');
|
|
111
|
+
const creds = JSON.parse(content);
|
|
112
|
+
|
|
113
|
+
const oauth = creds.claudeAiOauth;
|
|
114
|
+
if (oauth && oauth.accessToken) {
|
|
115
|
+
const isExpired = oauth.expiresAt && Date.now() >= oauth.expiresAt;
|
|
116
|
+
if (!isExpired) {
|
|
117
|
+
return {
|
|
118
|
+
authenticated: true,
|
|
119
|
+
email: creds.email || creds.user || null,
|
|
120
|
+
method: 'credentials_file'
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
authenticated: false,
|
|
126
|
+
email: creds.email || creds.user || null,
|
|
127
|
+
method: 'credentials_file',
|
|
128
|
+
error: 'OAuth token has expired. Please re-authenticate with claude login'
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return { authenticated: false, email: null, method: null };
|
|
133
|
+
} catch {
|
|
134
|
+
return { authenticated: false, email: null, method: null };
|
|
135
|
+
}
|
|
136
|
+
}
|