@cloudcli-ai/cloudcli 1.29.3 → 1.29.5

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 (55) hide show
  1. package/dist/assets/{index-D-9h5kNf.css → index-BBAE1OJ_.css} +1 -1
  2. package/dist/assets/{index-y8yqvoLv.js → index-Ci1GOyV7.js} +200 -200
  3. package/dist/index.html +2 -2
  4. package/dist-server/server/claude-sdk.js +11 -3
  5. package/dist-server/server/claude-sdk.js.map +1 -1
  6. package/dist-server/server/cursor-cli.js +7 -1
  7. package/dist-server/server/cursor-cli.js.map +1 -1
  8. package/dist-server/server/gemini-cli.js +15 -1
  9. package/dist-server/server/gemini-cli.js.map +1 -1
  10. package/dist-server/server/index.js +7 -76
  11. package/dist-server/server/index.js.map +1 -1
  12. package/dist-server/server/openai-codex.js +7 -1
  13. package/dist-server/server/openai-codex.js.map +1 -1
  14. package/dist-server/server/projects.js +41 -47
  15. package/dist-server/server/projects.js.map +1 -1
  16. package/dist-server/server/providers/claude/status.js +121 -0
  17. package/dist-server/server/providers/claude/status.js.map +1 -0
  18. package/dist-server/server/providers/codex/status.js +70 -0
  19. package/dist-server/server/providers/codex/status.js.map +1 -0
  20. package/dist-server/server/providers/cursor/adapter.js +5 -10
  21. package/dist-server/server/providers/cursor/adapter.js.map +1 -1
  22. package/dist-server/server/providers/cursor/status.js +118 -0
  23. package/dist-server/server/providers/cursor/status.js.map +1 -0
  24. package/dist-server/server/providers/gemini/status.js +104 -0
  25. package/dist-server/server/providers/gemini/status.js.map +1 -0
  26. package/dist-server/server/providers/registry.js +21 -2
  27. package/dist-server/server/providers/registry.js.map +1 -1
  28. package/dist-server/server/providers/types.js +11 -0
  29. package/dist-server/server/providers/types.js.map +1 -1
  30. package/dist-server/server/routes/cli-auth.js +14 -395
  31. package/dist-server/server/routes/cli-auth.js.map +1 -1
  32. package/dist-server/server/routes/cursor.js +6 -22
  33. package/dist-server/server/routes/cursor.js.map +1 -1
  34. package/dist-server/server/utils/colors.js +20 -0
  35. package/dist-server/server/utils/colors.js.map +1 -0
  36. package/dist-server/server/utils/url-detection.js +58 -0
  37. package/dist-server/server/utils/url-detection.js.map +1 -0
  38. package/package.json +4 -6
  39. package/server/claude-sdk.js +13 -4
  40. package/server/cursor-cli.js +8 -1
  41. package/server/gemini-cli.js +17 -1
  42. package/server/index.js +7 -83
  43. package/server/openai-codex.js +9 -1
  44. package/server/projects.js +38 -44
  45. package/server/providers/claude/status.js +136 -0
  46. package/server/providers/codex/status.js +78 -0
  47. package/server/providers/cursor/adapter.js +5 -10
  48. package/server/providers/cursor/status.js +128 -0
  49. package/server/providers/gemini/status.js +111 -0
  50. package/server/providers/registry.js +25 -2
  51. package/server/providers/types.js +13 -0
  52. package/server/routes/cli-auth.js +14 -421
  53. package/server/routes/cursor.js +6 -22
  54. package/server/utils/colors.js +21 -0
  55. package/server/utils/url-detection.js +71 -0
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Codex Provider Status
3
+ *
4
+ * Checks whether the user has valid Codex authentication credentials.
5
+ * Codex uses an SDK that makes direct API calls (no external binary),
6
+ * so installation check always returns true if the server is running.
7
+ *
8
+ * @module providers/codex/status
9
+ */
10
+
11
+ import { promises as fs } from 'fs';
12
+ import path from 'path';
13
+ import os from 'os';
14
+
15
+ /**
16
+ * Check if Codex is installed.
17
+ * Codex SDK is bundled with this application — no external binary needed.
18
+ * @returns {boolean}
19
+ */
20
+ export function checkInstalled() {
21
+ return true;
22
+ }
23
+
24
+ /**
25
+ * Full status check: installation + authentication.
26
+ * @returns {Promise<import('../types.js').ProviderStatus>}
27
+ */
28
+ export async function checkStatus() {
29
+ const installed = checkInstalled();
30
+ const result = await checkCredentials();
31
+
32
+ return {
33
+ installed,
34
+ authenticated: result.authenticated,
35
+ email: result.email || null,
36
+ error: result.error || null
37
+ };
38
+ }
39
+
40
+ // ─── Internal helpers ───────────────────────────────────────────────────────
41
+
42
+ async function checkCredentials() {
43
+ try {
44
+ const authPath = path.join(os.homedir(), '.codex', 'auth.json');
45
+ const content = await fs.readFile(authPath, 'utf8');
46
+ const auth = JSON.parse(content);
47
+
48
+ const tokens = auth.tokens || {};
49
+
50
+ if (tokens.id_token || tokens.access_token) {
51
+ let email = 'Authenticated';
52
+ if (tokens.id_token) {
53
+ try {
54
+ const parts = tokens.id_token.split('.');
55
+ if (parts.length >= 2) {
56
+ const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf8'));
57
+ email = payload.email || payload.user || 'Authenticated';
58
+ }
59
+ } catch {
60
+ email = 'Authenticated';
61
+ }
62
+ }
63
+
64
+ return { authenticated: true, email };
65
+ }
66
+
67
+ if (auth.OPENAI_API_KEY) {
68
+ return { authenticated: true, email: 'API Key Auth' };
69
+ }
70
+
71
+ return { authenticated: false, email: null, error: 'No valid tokens found' };
72
+ } catch (error) {
73
+ if (error.code === 'ENOENT') {
74
+ return { authenticated: false, email: null, error: 'Codex not configured' };
75
+ }
76
+ return { authenticated: false, email: null, error: error.message };
77
+ }
78
+ }
@@ -20,21 +20,16 @@ const PROVIDER = 'cursor';
20
20
  * @returns {Promise<Array<{id: string, sequence: number, rowid: number, content: object}>>}
21
21
  */
22
22
  async function loadCursorBlobs(sessionId, projectPath) {
23
- // Lazy-import sqlite so the module doesn't fail if sqlite3 is unavailable
24
- const { default: sqlite3 } = await import('sqlite3');
25
- const { open } = await import('sqlite');
23
+ // Lazy-import better-sqlite3 so the module doesn't fail if it's unavailable
24
+ const { default: Database } = await import('better-sqlite3');
26
25
 
27
26
  const cwdId = crypto.createHash('md5').update(projectPath || process.cwd()).digest('hex');
28
27
  const storeDbPath = path.join(os.homedir(), '.cursor', 'chats', cwdId, sessionId, 'store.db');
29
28
 
30
- const db = await open({
31
- filename: storeDbPath,
32
- driver: sqlite3.Database,
33
- mode: sqlite3.OPEN_READONLY,
34
- });
29
+ const db = new Database(storeDbPath, { readonly: true, fileMustExist: true });
35
30
 
36
31
  try {
37
- const allBlobs = await db.all('SELECT rowid, id, data FROM blobs');
32
+ const allBlobs = db.prepare('SELECT rowid, id, data FROM blobs').all();
38
33
 
39
34
  const blobMap = new Map();
40
35
  const parentRefs = new Map();
@@ -129,7 +124,7 @@ async function loadCursorBlobs(sessionId, projectPath) {
129
124
 
130
125
  return messages;
131
126
  } finally {
132
- await db.close();
127
+ db.close();
133
128
  }
134
129
  }
135
130
 
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Cursor Provider Status
3
+ *
4
+ * Checks whether cursor-agent CLI is installed and whether the user
5
+ * is logged in.
6
+ *
7
+ * @module providers/cursor/status
8
+ */
9
+
10
+ import { execFileSync, spawn } from 'child_process';
11
+
12
+ /**
13
+ * Check if cursor-agent CLI is installed.
14
+ * @returns {boolean}
15
+ */
16
+ export function checkInstalled() {
17
+ try {
18
+ execFileSync('cursor-agent', ['--version'], { stdio: 'ignore', timeout: 5000 });
19
+ return true;
20
+ } catch {
21
+ return false;
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Full status check: installation + authentication.
27
+ * @returns {Promise<import('../types.js').ProviderStatus>}
28
+ */
29
+ export async function checkStatus() {
30
+ const installed = checkInstalled();
31
+
32
+ if (!installed) {
33
+ return {
34
+ installed,
35
+ authenticated: false,
36
+ email: null,
37
+ error: 'Cursor CLI is not installed'
38
+ };
39
+ }
40
+
41
+ const result = await checkCursorLogin();
42
+
43
+ return {
44
+ installed,
45
+ authenticated: result.authenticated,
46
+ email: result.email || null,
47
+ error: result.error || null
48
+ };
49
+ }
50
+
51
+ // ─── Internal helpers ───────────────────────────────────────────────────────
52
+
53
+ function checkCursorLogin() {
54
+ return new Promise((resolve) => {
55
+ let processCompleted = false;
56
+
57
+ const timeout = setTimeout(() => {
58
+ if (!processCompleted) {
59
+ processCompleted = true;
60
+ if (childProcess) {
61
+ childProcess.kill();
62
+ }
63
+ resolve({
64
+ authenticated: false,
65
+ email: null,
66
+ error: 'Command timeout'
67
+ });
68
+ }
69
+ }, 5000);
70
+
71
+ let childProcess;
72
+ try {
73
+ childProcess = spawn('cursor-agent', ['status']);
74
+ } catch {
75
+ clearTimeout(timeout);
76
+ processCompleted = true;
77
+ resolve({
78
+ authenticated: false,
79
+ email: null,
80
+ error: 'Cursor CLI not found or not installed'
81
+ });
82
+ return;
83
+ }
84
+
85
+ let stdout = '';
86
+ let stderr = '';
87
+
88
+ childProcess.stdout.on('data', (data) => {
89
+ stdout += data.toString();
90
+ });
91
+
92
+ childProcess.stderr.on('data', (data) => {
93
+ stderr += data.toString();
94
+ });
95
+
96
+ childProcess.on('close', (code) => {
97
+ if (processCompleted) return;
98
+ processCompleted = true;
99
+ clearTimeout(timeout);
100
+
101
+ if (code === 0) {
102
+ const emailMatch = stdout.match(/Logged in as ([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/i);
103
+
104
+ if (emailMatch) {
105
+ resolve({ authenticated: true, email: emailMatch[1] });
106
+ } else if (stdout.includes('Logged in')) {
107
+ resolve({ authenticated: true, email: 'Logged in' });
108
+ } else {
109
+ resolve({ authenticated: false, email: null, error: 'Not logged in' });
110
+ }
111
+ } else {
112
+ resolve({ authenticated: false, email: null, error: stderr || 'Not logged in' });
113
+ }
114
+ });
115
+
116
+ childProcess.on('error', () => {
117
+ if (processCompleted) return;
118
+ processCompleted = true;
119
+ clearTimeout(timeout);
120
+
121
+ resolve({
122
+ authenticated: false,
123
+ email: null,
124
+ error: 'Cursor CLI not found or not installed'
125
+ });
126
+ });
127
+ });
128
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Gemini Provider Status
3
+ *
4
+ * Checks whether Gemini CLI is installed and whether the user
5
+ * has valid authentication credentials.
6
+ *
7
+ * @module providers/gemini/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 Gemini CLI is installed.
17
+ * Uses GEMINI_PATH env var if set, otherwise looks for 'gemini' in PATH.
18
+ * @returns {boolean}
19
+ */
20
+ export function checkInstalled() {
21
+ const cliPath = process.env.GEMINI_PATH || 'gemini';
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
+ error: 'Gemini CLI is not installed'
43
+ };
44
+ }
45
+
46
+ const result = await checkCredentials();
47
+
48
+ return {
49
+ installed,
50
+ authenticated: result.authenticated,
51
+ email: result.email || null,
52
+ error: result.error || null
53
+ };
54
+ }
55
+
56
+ // ─── Internal helpers ───────────────────────────────────────────────────────
57
+
58
+ async function checkCredentials() {
59
+ if (process.env.GEMINI_API_KEY && process.env.GEMINI_API_KEY.trim()) {
60
+ return { authenticated: true, email: 'API Key Auth' };
61
+ }
62
+
63
+ try {
64
+ const credsPath = path.join(os.homedir(), '.gemini', 'oauth_creds.json');
65
+ const content = await fs.readFile(credsPath, 'utf8');
66
+ const creds = JSON.parse(content);
67
+
68
+ if (creds.access_token) {
69
+ let email = 'OAuth Session';
70
+
71
+ try {
72
+ const tokenRes = await fetch(`https://oauth2.googleapis.com/tokeninfo?access_token=${creds.access_token}`);
73
+ if (tokenRes.ok) {
74
+ const tokenInfo = await tokenRes.json();
75
+ if (tokenInfo.email) {
76
+ email = tokenInfo.email;
77
+ }
78
+ } else if (!creds.refresh_token) {
79
+ return {
80
+ authenticated: false,
81
+ email: null,
82
+ error: 'Access token invalid and no refresh token found'
83
+ };
84
+ } else {
85
+ // Token might be expired but we have a refresh token, so CLI will refresh it
86
+ email = await getActiveAccountEmail() || email;
87
+ }
88
+ } catch {
89
+ // Network error, fallback to checking local accounts file
90
+ email = await getActiveAccountEmail() || email;
91
+ }
92
+
93
+ return { authenticated: true, email };
94
+ }
95
+
96
+ return { authenticated: false, email: null, error: 'No valid tokens found in oauth_creds' };
97
+ } catch {
98
+ return { authenticated: false, email: null, error: 'Gemini CLI not configured' };
99
+ }
100
+ }
101
+
102
+ async function getActiveAccountEmail() {
103
+ try {
104
+ const accPath = path.join(os.homedir(), '.gemini', 'google_accounts.json');
105
+ const accContent = await fs.readFile(accPath, 'utf8');
106
+ const accounts = JSON.parse(accContent);
107
+ return accounts.active || null;
108
+ } catch {
109
+ return null;
110
+ }
111
+ }
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * Provider Registry
3
3
  *
4
- * Centralizes provider adapter lookup. All code that needs a provider adapter
5
- * should go through this registry instead of importing individual adapters directly.
4
+ * Centralizes provider adapter and status checker lookup. All code that needs
5
+ * a provider adapter or status checker should go through this registry instead
6
+ * of importing individual modules directly.
6
7
  *
7
8
  * @module providers/registry
8
9
  */
@@ -12,6 +13,11 @@ import { cursorAdapter } from './cursor/adapter.js';
12
13
  import { codexAdapter } from './codex/adapter.js';
13
14
  import { geminiAdapter } from './gemini/adapter.js';
14
15
 
16
+ import * as claudeStatus from './claude/status.js';
17
+ import * as cursorStatus from './cursor/status.js';
18
+ import * as codexStatus from './codex/status.js';
19
+ import * as geminiStatus from './gemini/status.js';
20
+
15
21
  /**
16
22
  * @typedef {import('./types.js').ProviderAdapter} ProviderAdapter
17
23
  * @typedef {import('./types.js').SessionProvider} SessionProvider
@@ -20,12 +26,20 @@ import { geminiAdapter } from './gemini/adapter.js';
20
26
  /** @type {Map<string, ProviderAdapter>} */
21
27
  const providers = new Map();
22
28
 
29
+ /** @type {Map<string, { checkInstalled: () => boolean, checkStatus: () => Promise<import('./types.js').ProviderStatus> }>} */
30
+ const statusCheckers = new Map();
31
+
23
32
  // Register built-in providers
24
33
  providers.set('claude', claudeAdapter);
25
34
  providers.set('cursor', cursorAdapter);
26
35
  providers.set('codex', codexAdapter);
27
36
  providers.set('gemini', geminiAdapter);
28
37
 
38
+ statusCheckers.set('claude', claudeStatus);
39
+ statusCheckers.set('cursor', cursorStatus);
40
+ statusCheckers.set('codex', codexStatus);
41
+ statusCheckers.set('gemini', geminiStatus);
42
+
29
43
  /**
30
44
  * Get a provider adapter by name.
31
45
  * @param {string} name - Provider name (e.g., 'claude', 'cursor', 'codex', 'gemini')
@@ -35,6 +49,15 @@ export function getProvider(name) {
35
49
  return providers.get(name);
36
50
  }
37
51
 
52
+ /**
53
+ * Get a provider status checker by name.
54
+ * @param {string} name - Provider name
55
+ * @returns {{ checkInstalled: () => boolean, checkStatus: () => Promise<import('./types.js').ProviderStatus> } | undefined}
56
+ */
57
+ export function getStatusChecker(name) {
58
+ return statusCheckers.get(name);
59
+ }
60
+
38
61
  /**
39
62
  * Get all registered provider names.
40
63
  * @returns {string[]}
@@ -69,6 +69,19 @@
69
69
  * @property {object} [tokenUsage] - Token usage data (provider-specific)
70
70
  */
71
71
 
72
+ // ─── Provider Status ────────────────────────────────────────────────────────
73
+
74
+ /**
75
+ * Result of a provider status check (installation + authentication).
76
+ *
77
+ * @typedef {Object} ProviderStatus
78
+ * @property {boolean} installed - Whether the provider's CLI/SDK is available
79
+ * @property {boolean} authenticated - Whether valid credentials exist
80
+ * @property {string|null} email - User email or auth method identifier
81
+ * @property {string|null} [method] - Auth method (e.g. 'api_key', 'credentials_file')
82
+ * @property {string|null} [error] - Error message if not installed or not authenticated
83
+ */
84
+
72
85
  // ─── Provider Adapter Interface ──────────────────────────────────────────────
73
86
 
74
87
  /**