popeye-cli 1.0.1 → 1.2.0

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 (216) hide show
  1. package/.env.example +24 -1
  2. package/CONTRIBUTING.md +275 -0
  3. package/OPEN_SOURCE_MANIFESTO.md +172 -0
  4. package/README.md +832 -123
  5. package/dist/adapters/claude.d.ts +19 -4
  6. package/dist/adapters/claude.d.ts.map +1 -1
  7. package/dist/adapters/claude.js +908 -42
  8. package/dist/adapters/claude.js.map +1 -1
  9. package/dist/adapters/gemini.d.ts +55 -0
  10. package/dist/adapters/gemini.d.ts.map +1 -0
  11. package/dist/adapters/gemini.js +318 -0
  12. package/dist/adapters/gemini.js.map +1 -0
  13. package/dist/adapters/grok.d.ts +73 -0
  14. package/dist/adapters/grok.d.ts.map +1 -0
  15. package/dist/adapters/grok.js +430 -0
  16. package/dist/adapters/grok.js.map +1 -0
  17. package/dist/adapters/openai.d.ts +1 -1
  18. package/dist/adapters/openai.d.ts.map +1 -1
  19. package/dist/adapters/openai.js +47 -8
  20. package/dist/adapters/openai.js.map +1 -1
  21. package/dist/auth/claude.d.ts +11 -9
  22. package/dist/auth/claude.d.ts.map +1 -1
  23. package/dist/auth/claude.js +107 -71
  24. package/dist/auth/claude.js.map +1 -1
  25. package/dist/auth/gemini.d.ts +58 -0
  26. package/dist/auth/gemini.d.ts.map +1 -0
  27. package/dist/auth/gemini.js +172 -0
  28. package/dist/auth/gemini.js.map +1 -0
  29. package/dist/auth/grok.d.ts +73 -0
  30. package/dist/auth/grok.d.ts.map +1 -0
  31. package/dist/auth/grok.js +211 -0
  32. package/dist/auth/grok.js.map +1 -0
  33. package/dist/auth/index.d.ts +14 -7
  34. package/dist/auth/index.d.ts.map +1 -1
  35. package/dist/auth/index.js +41 -6
  36. package/dist/auth/index.js.map +1 -1
  37. package/dist/auth/keychain.d.ts +20 -7
  38. package/dist/auth/keychain.d.ts.map +1 -1
  39. package/dist/auth/keychain.js +85 -29
  40. package/dist/auth/keychain.js.map +1 -1
  41. package/dist/auth/openai.d.ts +2 -2
  42. package/dist/auth/openai.d.ts.map +1 -1
  43. package/dist/auth/openai.js +30 -32
  44. package/dist/auth/openai.js.map +1 -1
  45. package/dist/cli/commands/auth.d.ts +1 -1
  46. package/dist/cli/commands/auth.d.ts.map +1 -1
  47. package/dist/cli/commands/auth.js +79 -8
  48. package/dist/cli/commands/auth.js.map +1 -1
  49. package/dist/cli/commands/create.d.ts.map +1 -1
  50. package/dist/cli/commands/create.js +15 -4
  51. package/dist/cli/commands/create.js.map +1 -1
  52. package/dist/cli/interactive.d.ts.map +1 -1
  53. package/dist/cli/interactive.js +1494 -114
  54. package/dist/cli/interactive.js.map +1 -1
  55. package/dist/config/defaults.d.ts +9 -1
  56. package/dist/config/defaults.d.ts.map +1 -1
  57. package/dist/config/defaults.js +19 -2
  58. package/dist/config/defaults.js.map +1 -1
  59. package/dist/config/index.d.ts +19 -0
  60. package/dist/config/index.d.ts.map +1 -1
  61. package/dist/config/index.js +33 -1
  62. package/dist/config/index.js.map +1 -1
  63. package/dist/config/schema.d.ts +47 -0
  64. package/dist/config/schema.d.ts.map +1 -1
  65. package/dist/config/schema.js +29 -1
  66. package/dist/config/schema.js.map +1 -1
  67. package/dist/generators/fullstack.d.ts +32 -0
  68. package/dist/generators/fullstack.d.ts.map +1 -0
  69. package/dist/generators/fullstack.js +497 -0
  70. package/dist/generators/fullstack.js.map +1 -0
  71. package/dist/generators/index.d.ts +4 -3
  72. package/dist/generators/index.d.ts.map +1 -1
  73. package/dist/generators/index.js +15 -1
  74. package/dist/generators/index.js.map +1 -1
  75. package/dist/generators/python.d.ts +17 -1
  76. package/dist/generators/python.d.ts.map +1 -1
  77. package/dist/generators/python.js +34 -20
  78. package/dist/generators/python.js.map +1 -1
  79. package/dist/generators/templates/fullstack.d.ts +113 -0
  80. package/dist/generators/templates/fullstack.d.ts.map +1 -0
  81. package/dist/generators/templates/fullstack.js +1004 -0
  82. package/dist/generators/templates/fullstack.js.map +1 -0
  83. package/dist/generators/typescript.d.ts +19 -1
  84. package/dist/generators/typescript.d.ts.map +1 -1
  85. package/dist/generators/typescript.js +37 -20
  86. package/dist/generators/typescript.js.map +1 -1
  87. package/dist/state/index.d.ts +108 -0
  88. package/dist/state/index.d.ts.map +1 -1
  89. package/dist/state/index.js +551 -4
  90. package/dist/state/index.js.map +1 -1
  91. package/dist/state/registry.d.ts +52 -0
  92. package/dist/state/registry.d.ts.map +1 -0
  93. package/dist/state/registry.js +215 -0
  94. package/dist/state/registry.js.map +1 -0
  95. package/dist/types/cli.d.ts +8 -0
  96. package/dist/types/cli.d.ts.map +1 -1
  97. package/dist/types/cli.js.map +1 -1
  98. package/dist/types/consensus.d.ts +186 -4
  99. package/dist/types/consensus.d.ts.map +1 -1
  100. package/dist/types/consensus.js +35 -3
  101. package/dist/types/consensus.js.map +1 -1
  102. package/dist/types/project.d.ts +76 -0
  103. package/dist/types/project.d.ts.map +1 -1
  104. package/dist/types/project.js +1 -1
  105. package/dist/types/project.js.map +1 -1
  106. package/dist/types/workflow.d.ts +217 -16
  107. package/dist/types/workflow.d.ts.map +1 -1
  108. package/dist/types/workflow.js +40 -1
  109. package/dist/types/workflow.js.map +1 -1
  110. package/dist/workflow/auto-fix.d.ts +45 -0
  111. package/dist/workflow/auto-fix.d.ts.map +1 -0
  112. package/dist/workflow/auto-fix.js +274 -0
  113. package/dist/workflow/auto-fix.js.map +1 -0
  114. package/dist/workflow/consensus.d.ts +70 -2
  115. package/dist/workflow/consensus.d.ts.map +1 -1
  116. package/dist/workflow/consensus.js +872 -17
  117. package/dist/workflow/consensus.js.map +1 -1
  118. package/dist/workflow/execution-mode.d.ts +10 -4
  119. package/dist/workflow/execution-mode.d.ts.map +1 -1
  120. package/dist/workflow/execution-mode.js +547 -58
  121. package/dist/workflow/execution-mode.js.map +1 -1
  122. package/dist/workflow/index.d.ts +14 -2
  123. package/dist/workflow/index.d.ts.map +1 -1
  124. package/dist/workflow/index.js +69 -6
  125. package/dist/workflow/index.js.map +1 -1
  126. package/dist/workflow/milestone-workflow.d.ts +34 -0
  127. package/dist/workflow/milestone-workflow.d.ts.map +1 -0
  128. package/dist/workflow/milestone-workflow.js +414 -0
  129. package/dist/workflow/milestone-workflow.js.map +1 -0
  130. package/dist/workflow/plan-mode.d.ts +80 -3
  131. package/dist/workflow/plan-mode.d.ts.map +1 -1
  132. package/dist/workflow/plan-mode.js +767 -49
  133. package/dist/workflow/plan-mode.js.map +1 -1
  134. package/dist/workflow/plan-storage.d.ts +386 -0
  135. package/dist/workflow/plan-storage.d.ts.map +1 -0
  136. package/dist/workflow/plan-storage.js +878 -0
  137. package/dist/workflow/plan-storage.js.map +1 -0
  138. package/dist/workflow/project-verification.d.ts +37 -0
  139. package/dist/workflow/project-verification.d.ts.map +1 -0
  140. package/dist/workflow/project-verification.js +381 -0
  141. package/dist/workflow/project-verification.js.map +1 -0
  142. package/dist/workflow/task-workflow.d.ts +37 -0
  143. package/dist/workflow/task-workflow.d.ts.map +1 -0
  144. package/dist/workflow/task-workflow.js +386 -0
  145. package/dist/workflow/task-workflow.js.map +1 -0
  146. package/dist/workflow/test-runner.d.ts +9 -0
  147. package/dist/workflow/test-runner.d.ts.map +1 -1
  148. package/dist/workflow/test-runner.js +101 -5
  149. package/dist/workflow/test-runner.js.map +1 -1
  150. package/dist/workflow/ui-designer.d.ts +82 -0
  151. package/dist/workflow/ui-designer.d.ts.map +1 -0
  152. package/dist/workflow/ui-designer.js +234 -0
  153. package/dist/workflow/ui-designer.js.map +1 -0
  154. package/dist/workflow/ui-setup.d.ts +58 -0
  155. package/dist/workflow/ui-setup.d.ts.map +1 -0
  156. package/dist/workflow/ui-setup.js +685 -0
  157. package/dist/workflow/ui-setup.js.map +1 -0
  158. package/dist/workflow/ui-verification.d.ts +114 -0
  159. package/dist/workflow/ui-verification.d.ts.map +1 -0
  160. package/dist/workflow/ui-verification.js +258 -0
  161. package/dist/workflow/ui-verification.js.map +1 -0
  162. package/dist/workflow/workflow-logger.d.ts +110 -0
  163. package/dist/workflow/workflow-logger.d.ts.map +1 -0
  164. package/dist/workflow/workflow-logger.js +267 -0
  165. package/dist/workflow/workflow-logger.js.map +1 -0
  166. package/dist/workflow/workspace-manager.d.ts +342 -0
  167. package/dist/workflow/workspace-manager.d.ts.map +1 -0
  168. package/dist/workflow/workspace-manager.js +733 -0
  169. package/dist/workflow/workspace-manager.js.map +1 -0
  170. package/package.json +2 -2
  171. package/src/adapters/claude.ts +1067 -47
  172. package/src/adapters/gemini.ts +373 -0
  173. package/src/adapters/grok.ts +492 -0
  174. package/src/adapters/openai.ts +48 -9
  175. package/src/auth/claude.ts +120 -78
  176. package/src/auth/gemini.ts +207 -0
  177. package/src/auth/grok.ts +255 -0
  178. package/src/auth/index.ts +47 -9
  179. package/src/auth/keychain.ts +95 -28
  180. package/src/auth/openai.ts +29 -36
  181. package/src/cli/commands/auth.ts +89 -10
  182. package/src/cli/commands/create.ts +13 -4
  183. package/src/cli/interactive.ts +1774 -142
  184. package/src/config/defaults.ts +19 -2
  185. package/src/config/index.ts +36 -1
  186. package/src/config/schema.ts +30 -1
  187. package/src/generators/fullstack.ts +551 -0
  188. package/src/generators/index.ts +25 -1
  189. package/src/generators/python.ts +65 -20
  190. package/src/generators/templates/fullstack.ts +1047 -0
  191. package/src/generators/typescript.ts +69 -20
  192. package/src/state/index.ts +713 -4
  193. package/src/state/registry.ts +278 -0
  194. package/src/types/cli.ts +8 -0
  195. package/src/types/consensus.ts +197 -6
  196. package/src/types/project.ts +82 -1
  197. package/src/types/workflow.ts +90 -1
  198. package/src/workflow/auto-fix.ts +340 -0
  199. package/src/workflow/consensus.ts +1180 -16
  200. package/src/workflow/execution-mode.ts +673 -74
  201. package/src/workflow/index.ts +95 -6
  202. package/src/workflow/milestone-workflow.ts +576 -0
  203. package/src/workflow/plan-mode.ts +924 -50
  204. package/src/workflow/plan-storage.ts +1282 -0
  205. package/src/workflow/project-verification.ts +471 -0
  206. package/src/workflow/task-workflow.ts +528 -0
  207. package/src/workflow/test-runner.ts +120 -5
  208. package/src/workflow/ui-designer.ts +337 -0
  209. package/src/workflow/ui-setup.ts +797 -0
  210. package/src/workflow/ui-verification.ts +357 -0
  211. package/src/workflow/workflow-logger.ts +353 -0
  212. package/src/workflow/workspace-manager.ts +912 -0
  213. package/tests/config/config.test.ts +1 -1
  214. package/tests/types/consensus.test.ts +3 -3
  215. package/tests/workflow/plan-mode.test.ts +213 -0
  216. package/tests/workflow/test-runner.test.ts +5 -3
@@ -1,11 +1,10 @@
1
1
  /**
2
2
  * Claude CLI authentication module
3
- * Handles OAuth flow and token management for Claude CLI
3
+ * Checks for Claude Code CLI installation and authentication status
4
4
  */
5
5
 
6
- import open from 'open';
6
+ import { spawn } from 'node:child_process';
7
7
  import { getClaudeCredential, setClaudeCredential, deleteClaudeCredential } from './keychain.js';
8
- import { startAuthCallbackServer, findAvailablePort, getCallbackUrl } from './server.js';
9
8
 
10
9
  /**
11
10
  * Claude authentication status
@@ -15,110 +14,157 @@ export interface ClaudeAuthStatus {
15
14
  user?: string;
16
15
  expires?: string;
17
16
  error?: string;
17
+ cliInstalled?: boolean;
18
18
  }
19
19
 
20
20
  /**
21
- * Check if Claude CLI is already authenticated
22
- * This checks for existing credentials in the keychain or environment
21
+ * Run a command and capture output
22
+ */
23
+ function runCommand(command: string, args: string[]): Promise<{ stdout: string; stderr: string; code: number }> {
24
+ return new Promise((resolve) => {
25
+ const proc = spawn(command, args, {
26
+ stdio: ['ignore', 'pipe', 'pipe'],
27
+ shell: true,
28
+ });
29
+
30
+ let stdout = '';
31
+ let stderr = '';
32
+
33
+ proc.stdout?.on('data', (data) => {
34
+ stdout += data.toString();
35
+ });
36
+
37
+ proc.stderr?.on('data', (data) => {
38
+ stderr += data.toString();
39
+ });
40
+
41
+ proc.on('close', (code) => {
42
+ resolve({ stdout, stderr, code: code ?? 1 });
43
+ });
44
+
45
+ proc.on('error', () => {
46
+ resolve({ stdout, stderr, code: 1 });
47
+ });
48
+ });
49
+ }
50
+
51
+ /**
52
+ * Check if Claude Code CLI is installed
53
+ */
54
+ export async function isClaudeCLIInstalled(): Promise<boolean> {
55
+ try {
56
+ const result = await runCommand('claude', ['--version']);
57
+ return result.code === 0;
58
+ } catch {
59
+ return false;
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Check if Claude Code CLI is authenticated
65
+ * Uses 'claude auth status' to check authentication
23
66
  */
24
67
  export async function checkClaudeCLIAuth(): Promise<ClaudeAuthStatus> {
25
68
  try {
26
- const token = await getClaudeCredential();
69
+ // First check if CLI is installed
70
+ const installed = await isClaudeCLIInstalled();
71
+ if (!installed) {
72
+ return {
73
+ authenticated: false,
74
+ cliInstalled: false,
75
+ error: 'Claude Code CLI is not installed',
76
+ };
77
+ }
27
78
 
28
- if (!token) {
29
- return { authenticated: false };
79
+ // Check auth status by running a simple command
80
+ // The SDK will fail if not authenticated
81
+ const result = await runCommand('claude', ['-p', 'echo "auth check"', '--output-format', 'json']);
82
+
83
+ if (result.code === 0) {
84
+ // Also check keychain for cached status
85
+ const cached = await getClaudeCredential();
86
+
87
+ return {
88
+ authenticated: true,
89
+ cliInstalled: true,
90
+ user: cached ? 'authenticated' : 'claude-user',
91
+ };
92
+ }
93
+
94
+ // Check if the error indicates auth issues
95
+ const output = result.stdout + result.stderr;
96
+ if (output.includes('not logged in') || output.includes('authenticate') || output.includes('login')) {
97
+ return {
98
+ authenticated: false,
99
+ cliInstalled: true,
100
+ error: 'Not logged in to Claude Code',
101
+ };
30
102
  }
31
103
 
32
- // For now, we assume if there's a token, it's valid
33
- // In production, you would validate the token with the Claude API
104
+ // Some other error - assume authenticated if CLI is installed
105
+ // The actual auth check will happen when we try to use the SDK
34
106
  return {
35
107
  authenticated: true,
36
- user: 'claude-user', // Would be extracted from token in production
108
+ cliInstalled: true,
37
109
  };
38
110
  } catch (error) {
39
111
  return {
40
112
  authenticated: false,
113
+ cliInstalled: false,
41
114
  error: error instanceof Error ? error.message : 'Unknown error',
42
115
  };
43
116
  }
44
117
  }
45
118
 
46
119
  /**
47
- * Authenticate with Claude CLI using browser-based OAuth flow
48
- *
49
- * @returns Promise that resolves to true if authentication was successful
120
+ * Authenticate with Claude Code CLI
121
+ * Opens the Claude login flow
50
122
  */
51
123
  export async function authenticateClaude(): Promise<boolean> {
124
+ // Check if CLI is installed
125
+ const installed = await isClaudeCLIInstalled();
126
+
127
+ if (!installed) {
128
+ console.log('\nClaude Code CLI is not installed.');
129
+ console.log('\nTo install Claude Code CLI:');
130
+ console.log(' npm install -g @anthropic-ai/claude-code');
131
+ console.log('\nOr visit: https://claude.ai/download\n');
132
+ return false;
133
+ }
134
+
52
135
  // Check if already authenticated
53
- const existingAuth = await checkClaudeCLIAuth();
54
- if (existingAuth.authenticated) {
55
- console.log('Already authenticated with Claude CLI');
136
+ const status = await checkClaudeCLIAuth();
137
+ if (status.authenticated) {
138
+ console.log('Already authenticated with Claude Code CLI.');
139
+ await setClaudeCredential('authenticated');
56
140
  return true;
57
141
  }
58
142
 
59
- console.log('Claude CLI authentication required.');
60
- console.log('Opening browser for login...\n');
143
+ console.log('\nClaude Code CLI authentication required.');
144
+ console.log('\nPlease run the following command in your terminal:');
145
+ console.log('\n claude login\n');
146
+ console.log('After logging in, restart Popeye.\n');
61
147
 
148
+ // Try to open the login flow
62
149
  try {
63
- // Find an available port for the callback server
64
- const port = await findAvailablePort(3000, 3100);
65
- const callbackUrl = getCallbackUrl(port, 'claude');
66
-
67
- // Start the callback server
68
- const authPromise = startAuthCallbackServer({
69
- port,
70
- type: 'claude',
71
- timeout: 300000, // 5 minutes
72
- });
73
-
74
- // Build the login URL
75
- // Note: This is a placeholder URL. The actual Claude OAuth URL would be provided by Anthropic
76
- const loginUrl = buildClaudeLoginUrl(callbackUrl);
77
-
78
- console.log(`Waiting for authentication...`);
79
- console.log(`(Browser opened to: ${loginUrl})\n`);
150
+ console.log('Attempting to open Claude login...\n');
151
+ const result = await runCommand('claude', ['login']);
80
152
 
81
- // Open the browser to the login URL
82
- await open(loginUrl);
83
-
84
- // Wait for the callback
85
- const result = await authPromise;
86
-
87
- if (result.success && result.token) {
88
- // Store the token securely
89
- await setClaudeCredential(result.token);
90
- console.log('Claude CLI authenticated successfully!\n');
153
+ if (result.code === 0) {
154
+ await setClaudeCredential('authenticated');
155
+ console.log('Claude Code CLI authenticated successfully!\n');
91
156
  return true;
92
157
  } else {
93
- console.error(`Authentication failed: ${result.error || 'Unknown error'}`);
158
+ console.log('Login process exited. Please run "claude login" manually if needed.\n');
94
159
  return false;
95
160
  }
96
- } catch (error) {
97
- console.error(`Authentication error: ${error instanceof Error ? error.message : error}`);
161
+ } catch {
162
+ console.log('Could not start login automatically.');
163
+ console.log('Please run "claude login" manually.\n');
98
164
  return false;
99
165
  }
100
166
  }
101
167
 
102
- /**
103
- * Build the Claude login URL with the callback redirect
104
- *
105
- * @param callbackUrl - The local callback URL
106
- * @returns The full login URL
107
- */
108
- function buildClaudeLoginUrl(callbackUrl: string): string {
109
- // This is a placeholder implementation
110
- // The actual URL would be provided by Anthropic's OAuth configuration
111
- const baseUrl = 'https://claude.ai/login';
112
- const params = new URLSearchParams({
113
- redirect_uri: callbackUrl,
114
- client_id: 'popeye-cli',
115
- response_type: 'code',
116
- scope: 'cli',
117
- });
118
-
119
- return `${baseUrl}?${params.toString()}`;
120
- }
121
-
122
168
  /**
123
169
  * Logout from Claude CLI
124
170
  * Removes stored credentials
@@ -126,26 +172,23 @@ function buildClaudeLoginUrl(callbackUrl: string): string {
126
172
  export async function logoutClaude(): Promise<void> {
127
173
  const deleted = await deleteClaudeCredential();
128
174
  if (deleted) {
129
- console.log('Claude CLI credentials removed.');
130
- } else {
131
- console.log('No Claude CLI credentials found.');
175
+ console.log('Claude CLI credentials removed from Popeye.');
132
176
  }
177
+
178
+ console.log('\nTo fully logout from Claude Code CLI, run:');
179
+ console.log(' claude logout\n');
133
180
  }
134
181
 
135
182
  /**
136
183
  * Refresh Claude CLI authentication
137
- * Re-authenticates if the current token is expired or invalid
138
184
  */
139
185
  export async function refreshClaudeAuth(): Promise<boolean> {
140
- // Remove existing credentials
141
186
  await deleteClaudeCredential();
142
-
143
- // Re-authenticate
144
187
  return authenticateClaude();
145
188
  }
146
189
 
147
190
  /**
148
- * Get the Claude CLI token for API calls
191
+ * Get the Claude CLI token (placeholder for API compatibility)
149
192
  */
150
193
  export async function getClaudeToken(): Promise<string | null> {
151
194
  return getClaudeCredential();
@@ -153,7 +196,6 @@ export async function getClaudeToken(): Promise<string | null> {
153
196
 
154
197
  /**
155
198
  * Ensure Claude CLI is authenticated
156
- * Prompts for authentication if not already authenticated
157
199
  */
158
200
  export async function ensureClaudeAuth(): Promise<boolean> {
159
201
  const status = await checkClaudeCLIAuth();
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Google Gemini API authentication module
3
+ * Handles API key validation and storage
4
+ */
5
+
6
+ import * as readline from 'node:readline';
7
+ import { GoogleGenerativeAI } from '@google/generative-ai';
8
+ import {
9
+ getGeminiCredential,
10
+ setGeminiCredential,
11
+ deleteGeminiCredential,
12
+ maskCredential,
13
+ } from './keychain.js';
14
+
15
+ /**
16
+ * Gemini authentication status
17
+ */
18
+ export interface GeminiAuthStatus {
19
+ authenticated: boolean;
20
+ keyLastFour?: string;
21
+ error?: string;
22
+ }
23
+
24
+ /**
25
+ * Validate a Gemini API key by making a test API call
26
+ *
27
+ * @param apiKey - The API key to validate
28
+ * @returns True if the key is valid
29
+ */
30
+ export async function validateGeminiToken(apiKey: string): Promise<boolean> {
31
+ try {
32
+ const client = new GoogleGenerativeAI(apiKey);
33
+ const model = client.getGenerativeModel({ model: 'gemini-2.0-flash' });
34
+ // Test the key by making a simple request
35
+ await model.generateContent('Say "OK"');
36
+ return true;
37
+ } catch (error) {
38
+ // Check for authentication errors
39
+ const errorMessage = error instanceof Error ? error.message : '';
40
+ if (errorMessage.includes('API_KEY_INVALID') || errorMessage.includes('401')) {
41
+ return false;
42
+ }
43
+ // For other errors, assume the key might be valid
44
+ console.warn('Could not fully validate Gemini key:', error);
45
+ return true;
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Check if Gemini is already authenticated
51
+ * Checks keychain first, then environment variable
52
+ */
53
+ export async function checkGeminiAuth(): Promise<GeminiAuthStatus> {
54
+ try {
55
+ const apiKey = await getGeminiCredential();
56
+
57
+ if (!apiKey) {
58
+ return { authenticated: false };
59
+ }
60
+
61
+ // Validate the key
62
+ const isValid = await validateGeminiToken(apiKey);
63
+
64
+ if (!isValid) {
65
+ return {
66
+ authenticated: false,
67
+ error: 'Stored API key is invalid',
68
+ };
69
+ }
70
+
71
+ return {
72
+ authenticated: true,
73
+ keyLastFour: maskCredential(apiKey),
74
+ };
75
+ } catch (error) {
76
+ return {
77
+ authenticated: false,
78
+ error: error instanceof Error ? error.message : 'Unknown error',
79
+ };
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Prompt for API key in the terminal
85
+ *
86
+ * @returns The entered API key or null if cancelled
87
+ */
88
+ export async function promptForGeminiAPIKey(): Promise<string | null> {
89
+ return new Promise((resolve) => {
90
+ const rl = readline.createInterface({
91
+ input: process.stdin,
92
+ output: process.stdout,
93
+ });
94
+
95
+ console.log('\nGet your API key from: https://aistudio.google.com/app/apikey\n');
96
+
97
+ rl.question('Enter your Gemini API key: ', (answer) => {
98
+ rl.close();
99
+ const key = answer.trim();
100
+ if (key) {
101
+ resolve(key);
102
+ } else {
103
+ resolve(null);
104
+ }
105
+ });
106
+ });
107
+ }
108
+
109
+ /**
110
+ * Authenticate with Gemini API
111
+ *
112
+ * @returns True if authentication was successful
113
+ */
114
+ export async function authenticateGemini(): Promise<boolean> {
115
+ // Check if already authenticated
116
+ const existingAuth = await checkGeminiAuth();
117
+ if (existingAuth.authenticated) {
118
+ console.log('Already authenticated with Gemini API');
119
+ return true;
120
+ }
121
+
122
+ console.log('Gemini API key required for arbitration.');
123
+
124
+ try {
125
+ // Prompt for the API key
126
+ const apiKey = await promptForGeminiAPIKey();
127
+
128
+ if (!apiKey) {
129
+ console.error('\nNo API key provided');
130
+ return false;
131
+ }
132
+
133
+ // Validate the token
134
+ console.log('\nValidating API key...');
135
+ const isValid = await validateGeminiToken(apiKey);
136
+
137
+ if (!isValid) {
138
+ console.error('Invalid Gemini API key');
139
+ return false;
140
+ }
141
+
142
+ // Store the token
143
+ await setGeminiCredential(apiKey);
144
+ console.log('Gemini API authenticated successfully!\n');
145
+
146
+ return true;
147
+ } catch (error) {
148
+ console.error(`Authentication error: ${error instanceof Error ? error.message : error}`);
149
+ return false;
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Authenticate with a provided API key (for CLI --api-key option)
155
+ *
156
+ * @param apiKey - The API key to use
157
+ * @returns True if authentication was successful
158
+ */
159
+ export async function authenticateGeminiWithKey(apiKey: string): Promise<boolean> {
160
+ // Validate the token
161
+ const isValid = await validateGeminiToken(apiKey);
162
+
163
+ if (!isValid) {
164
+ console.error('Invalid Gemini API key');
165
+ return false;
166
+ }
167
+
168
+ // Store the token
169
+ await setGeminiCredential(apiKey);
170
+ console.log('Gemini API authenticated successfully!\n');
171
+
172
+ return true;
173
+ }
174
+
175
+ /**
176
+ * Logout from Gemini API
177
+ * Removes stored credentials
178
+ */
179
+ export async function logoutGemini(): Promise<void> {
180
+ const deleted = await deleteGeminiCredential();
181
+ if (deleted) {
182
+ console.log('Gemini API credentials removed.');
183
+ } else {
184
+ console.log('No Gemini API credentials found.');
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Get the Gemini API key for API calls
190
+ */
191
+ export async function getGeminiToken(): Promise<string | null> {
192
+ return getGeminiCredential();
193
+ }
194
+
195
+ /**
196
+ * Ensure Gemini is authenticated
197
+ * Prompts for authentication if not already authenticated
198
+ */
199
+ export async function ensureGeminiAuth(): Promise<boolean> {
200
+ const status = await checkGeminiAuth();
201
+
202
+ if (status.authenticated) {
203
+ return true;
204
+ }
205
+
206
+ return authenticateGemini();
207
+ }