popeye-cli 1.1.0 → 1.2.1

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 (150) 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 +340 -27
  5. package/dist/adapters/claude.d.ts +28 -2
  6. package/dist/adapters/claude.d.ts.map +1 -1
  7. package/dist/adapters/claude.js +273 -20
  8. package/dist/adapters/claude.js.map +1 -1
  9. package/dist/adapters/grok.d.ts +73 -0
  10. package/dist/adapters/grok.d.ts.map +1 -0
  11. package/dist/adapters/grok.js +430 -0
  12. package/dist/adapters/grok.js.map +1 -0
  13. package/dist/adapters/openai.d.ts +1 -1
  14. package/dist/adapters/openai.d.ts.map +1 -1
  15. package/dist/adapters/openai.js +6 -1
  16. package/dist/adapters/openai.js.map +1 -1
  17. package/dist/auth/grok.d.ts +73 -0
  18. package/dist/auth/grok.d.ts.map +1 -0
  19. package/dist/auth/grok.js +211 -0
  20. package/dist/auth/grok.js.map +1 -0
  21. package/dist/auth/index.d.ts +9 -6
  22. package/dist/auth/index.d.ts.map +1 -1
  23. package/dist/auth/index.js +23 -6
  24. package/dist/auth/index.js.map +1 -1
  25. package/dist/cli/commands/auth.d.ts +1 -1
  26. package/dist/cli/commands/auth.d.ts.map +1 -1
  27. package/dist/cli/commands/auth.js +79 -8
  28. package/dist/cli/commands/auth.js.map +1 -1
  29. package/dist/cli/commands/create.d.ts.map +1 -1
  30. package/dist/cli/commands/create.js +15 -4
  31. package/dist/cli/commands/create.js.map +1 -1
  32. package/dist/cli/interactive.d.ts.map +1 -1
  33. package/dist/cli/interactive.js +406 -35
  34. package/dist/cli/interactive.js.map +1 -1
  35. package/dist/config/defaults.d.ts +3 -0
  36. package/dist/config/defaults.d.ts.map +1 -1
  37. package/dist/config/defaults.js +9 -0
  38. package/dist/config/defaults.js.map +1 -1
  39. package/dist/config/index.d.ts +9 -0
  40. package/dist/config/index.d.ts.map +1 -1
  41. package/dist/config/index.js +16 -3
  42. package/dist/config/index.js.map +1 -1
  43. package/dist/config/schema.d.ts +27 -0
  44. package/dist/config/schema.d.ts.map +1 -1
  45. package/dist/config/schema.js +24 -3
  46. package/dist/config/schema.js.map +1 -1
  47. package/dist/generators/fullstack.d.ts +32 -0
  48. package/dist/generators/fullstack.d.ts.map +1 -0
  49. package/dist/generators/fullstack.js +497 -0
  50. package/dist/generators/fullstack.js.map +1 -0
  51. package/dist/generators/index.d.ts +4 -3
  52. package/dist/generators/index.d.ts.map +1 -1
  53. package/dist/generators/index.js +15 -1
  54. package/dist/generators/index.js.map +1 -1
  55. package/dist/generators/python.d.ts +17 -1
  56. package/dist/generators/python.d.ts.map +1 -1
  57. package/dist/generators/python.js +34 -21
  58. package/dist/generators/python.js.map +1 -1
  59. package/dist/generators/templates/fullstack.d.ts +113 -0
  60. package/dist/generators/templates/fullstack.d.ts.map +1 -0
  61. package/dist/generators/templates/fullstack.js +1004 -0
  62. package/dist/generators/templates/fullstack.js.map +1 -0
  63. package/dist/generators/typescript.d.ts +19 -1
  64. package/dist/generators/typescript.d.ts.map +1 -1
  65. package/dist/generators/typescript.js +37 -21
  66. package/dist/generators/typescript.js.map +1 -1
  67. package/dist/types/cli.d.ts +4 -0
  68. package/dist/types/cli.d.ts.map +1 -1
  69. package/dist/types/cli.js.map +1 -1
  70. package/dist/types/consensus.d.ts +119 -2
  71. package/dist/types/consensus.d.ts.map +1 -1
  72. package/dist/types/consensus.js +12 -1
  73. package/dist/types/consensus.js.map +1 -1
  74. package/dist/types/project.d.ts +76 -0
  75. package/dist/types/project.d.ts.map +1 -1
  76. package/dist/types/project.js +1 -1
  77. package/dist/types/project.js.map +1 -1
  78. package/dist/types/workflow.d.ts +170 -16
  79. package/dist/types/workflow.d.ts.map +1 -1
  80. package/dist/types/workflow.js +26 -3
  81. package/dist/types/workflow.js.map +1 -1
  82. package/dist/workflow/consensus.d.ts +29 -3
  83. package/dist/workflow/consensus.d.ts.map +1 -1
  84. package/dist/workflow/consensus.js +334 -27
  85. package/dist/workflow/consensus.js.map +1 -1
  86. package/dist/workflow/execution-mode.d.ts +2 -0
  87. package/dist/workflow/execution-mode.d.ts.map +1 -1
  88. package/dist/workflow/execution-mode.js +20 -0
  89. package/dist/workflow/execution-mode.js.map +1 -1
  90. package/dist/workflow/index.d.ts +2 -0
  91. package/dist/workflow/index.d.ts.map +1 -1
  92. package/dist/workflow/index.js +11 -0
  93. package/dist/workflow/index.js.map +1 -1
  94. package/dist/workflow/milestone-workflow.d.ts +2 -0
  95. package/dist/workflow/milestone-workflow.d.ts.map +1 -1
  96. package/dist/workflow/milestone-workflow.js +19 -2
  97. package/dist/workflow/milestone-workflow.js.map +1 -1
  98. package/dist/workflow/plan-mode.d.ts +66 -2
  99. package/dist/workflow/plan-mode.d.ts.map +1 -1
  100. package/dist/workflow/plan-mode.js +187 -11
  101. package/dist/workflow/plan-mode.js.map +1 -1
  102. package/dist/workflow/plan-storage.d.ts +252 -8
  103. package/dist/workflow/plan-storage.d.ts.map +1 -1
  104. package/dist/workflow/plan-storage.js +580 -33
  105. package/dist/workflow/plan-storage.js.map +1 -1
  106. package/dist/workflow/project-verification.js +1 -1
  107. package/dist/workflow/project-verification.js.map +1 -1
  108. package/dist/workflow/task-workflow.d.ts +2 -0
  109. package/dist/workflow/task-workflow.d.ts.map +1 -1
  110. package/dist/workflow/task-workflow.js +23 -1
  111. package/dist/workflow/task-workflow.js.map +1 -1
  112. package/dist/workflow/test-runner.d.ts +8 -0
  113. package/dist/workflow/test-runner.d.ts.map +1 -1
  114. package/dist/workflow/test-runner.js +92 -0
  115. package/dist/workflow/test-runner.js.map +1 -1
  116. package/dist/workflow/workspace-manager.d.ts +342 -0
  117. package/dist/workflow/workspace-manager.d.ts.map +1 -0
  118. package/dist/workflow/workspace-manager.js +733 -0
  119. package/dist/workflow/workspace-manager.js.map +1 -0
  120. package/package.json +1 -1
  121. package/src/adapters/claude.ts +322 -25
  122. package/src/adapters/grok.ts +492 -0
  123. package/src/adapters/openai.ts +8 -2
  124. package/src/auth/grok.ts +255 -0
  125. package/src/auth/index.ts +27 -9
  126. package/src/cli/commands/auth.ts +89 -10
  127. package/src/cli/commands/create.ts +13 -4
  128. package/src/cli/interactive.ts +453 -34
  129. package/src/config/defaults.ts +9 -0
  130. package/src/config/index.ts +17 -3
  131. package/src/config/schema.ts +25 -3
  132. package/src/generators/fullstack.ts +551 -0
  133. package/src/generators/index.ts +25 -1
  134. package/src/generators/python.ts +65 -21
  135. package/src/generators/templates/fullstack.ts +1047 -0
  136. package/src/generators/typescript.ts +69 -21
  137. package/src/types/cli.ts +4 -0
  138. package/src/types/consensus.ts +135 -3
  139. package/src/types/project.ts +82 -1
  140. package/src/types/workflow.ts +58 -4
  141. package/src/workflow/consensus.ts +461 -31
  142. package/src/workflow/execution-mode.ts +32 -0
  143. package/src/workflow/index.ts +12 -0
  144. package/src/workflow/milestone-workflow.ts +24 -2
  145. package/src/workflow/plan-mode.ts +238 -10
  146. package/src/workflow/plan-storage.ts +835 -35
  147. package/src/workflow/project-verification.ts +1 -1
  148. package/src/workflow/task-workflow.ts +29 -1
  149. package/src/workflow/test-runner.ts +110 -0
  150. package/src/workflow/workspace-manager.ts +912 -0
@@ -0,0 +1,255 @@
1
+ /**
2
+ * xAI Grok API authentication module
3
+ * Handles API key validation and storage
4
+ */
5
+
6
+ import * as readline from 'node:readline';
7
+ import OpenAI from 'openai';
8
+ import {
9
+ getCredential,
10
+ setCredential,
11
+ deleteCredential,
12
+ maskCredential,
13
+ } from './keychain.js';
14
+ import { ENV_VARS } from '../config/defaults.js';
15
+
16
+ /**
17
+ * Grok API URL (OpenAI-compatible)
18
+ */
19
+ export const GROK_API_URL = 'https://api.x.ai/v1';
20
+
21
+ /**
22
+ * Keychain account for Grok
23
+ */
24
+ const GROK_ACCOUNT = 'grok-api';
25
+
26
+ /**
27
+ * Grok authentication status
28
+ */
29
+ export interface GrokAuthStatus {
30
+ authenticated: boolean;
31
+ keyLastFour?: string;
32
+ error?: string;
33
+ }
34
+
35
+ /**
36
+ * Validate a Grok API key by making a test API call
37
+ *
38
+ * @param apiKey - The API key to validate
39
+ * @returns True if the key is valid
40
+ */
41
+ export async function validateGrokToken(apiKey: string): Promise<boolean> {
42
+ try {
43
+ const client = new OpenAI({
44
+ apiKey,
45
+ baseURL: GROK_API_URL,
46
+ });
47
+
48
+ // Test the key by making a simple request
49
+ await client.chat.completions.create({
50
+ model: 'grok-3',
51
+ messages: [{ role: 'user', content: 'Say "OK"' }],
52
+ max_tokens: 5,
53
+ });
54
+
55
+ return true;
56
+ } catch (error) {
57
+ // Check for authentication errors
58
+ const errorMessage = error instanceof Error ? error.message : '';
59
+ if (
60
+ errorMessage.includes('401') ||
61
+ errorMessage.includes('Invalid API') ||
62
+ errorMessage.includes('Unauthorized')
63
+ ) {
64
+ return false;
65
+ }
66
+ // For other errors (e.g., rate limits), assume the key might be valid
67
+ console.warn('Could not fully validate Grok key:', error);
68
+ return true;
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Get the Grok API credential
74
+ */
75
+ export async function getGrokCredential(): Promise<string | null> {
76
+ // First check file storage
77
+ const stored = await getCredential(GROK_ACCOUNT);
78
+ if (stored) return stored;
79
+
80
+ // Fallback to environment variable
81
+ return process.env[ENV_VARS.GROK_KEY] || null;
82
+ }
83
+
84
+ /**
85
+ * Set the Grok API credential
86
+ */
87
+ export async function setGrokCredential(apiKey: string): Promise<void> {
88
+ return setCredential(GROK_ACCOUNT, apiKey);
89
+ }
90
+
91
+ /**
92
+ * Delete the Grok API credential
93
+ */
94
+ export async function deleteGrokCredential(): Promise<boolean> {
95
+ return deleteCredential(GROK_ACCOUNT);
96
+ }
97
+
98
+ /**
99
+ * Check if Grok is already authenticated
100
+ */
101
+ export async function checkGrokAuth(): Promise<GrokAuthStatus> {
102
+ try {
103
+ const apiKey = await getGrokCredential();
104
+
105
+ if (!apiKey) {
106
+ return { authenticated: false };
107
+ }
108
+
109
+ // Validate the key
110
+ const isValid = await validateGrokToken(apiKey);
111
+
112
+ if (!isValid) {
113
+ return {
114
+ authenticated: false,
115
+ error: 'Stored API key is invalid',
116
+ };
117
+ }
118
+
119
+ return {
120
+ authenticated: true,
121
+ keyLastFour: maskCredential(apiKey),
122
+ };
123
+ } catch (error) {
124
+ return {
125
+ authenticated: false,
126
+ error: error instanceof Error ? error.message : 'Unknown error',
127
+ };
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Prompt for API key in the terminal
133
+ *
134
+ * @returns The entered API key or null if cancelled
135
+ */
136
+ export async function promptForGrokAPIKey(): Promise<string | null> {
137
+ return new Promise((resolve) => {
138
+ const rl = readline.createInterface({
139
+ input: process.stdin,
140
+ output: process.stdout,
141
+ });
142
+
143
+ console.log('\nGet your API key from: https://console.x.ai/\n');
144
+
145
+ rl.question('Enter your Grok API key: ', (answer) => {
146
+ rl.close();
147
+ const key = answer.trim();
148
+ if (key) {
149
+ resolve(key);
150
+ } else {
151
+ resolve(null);
152
+ }
153
+ });
154
+ });
155
+ }
156
+
157
+ /**
158
+ * Authenticate with Grok API
159
+ *
160
+ * @returns True if authentication was successful
161
+ */
162
+ export async function authenticateGrok(): Promise<boolean> {
163
+ // Check if already authenticated
164
+ const existingAuth = await checkGrokAuth();
165
+ if (existingAuth.authenticated) {
166
+ console.log('Already authenticated with Grok API');
167
+ return true;
168
+ }
169
+
170
+ console.log('Grok API key required for AI reviews.');
171
+
172
+ try {
173
+ // Prompt for the API key
174
+ const apiKey = await promptForGrokAPIKey();
175
+
176
+ if (!apiKey) {
177
+ console.error('\nNo API key provided');
178
+ return false;
179
+ }
180
+
181
+ // Validate the token
182
+ console.log('\nValidating API key...');
183
+ const isValid = await validateGrokToken(apiKey);
184
+
185
+ if (!isValid) {
186
+ console.error('Invalid Grok API key');
187
+ return false;
188
+ }
189
+
190
+ // Store the token
191
+ await setGrokCredential(apiKey);
192
+ console.log('Grok API authenticated successfully!\n');
193
+
194
+ return true;
195
+ } catch (error) {
196
+ console.error(`Authentication error: ${error instanceof Error ? error.message : error}`);
197
+ return false;
198
+ }
199
+ }
200
+
201
+ /**
202
+ * Authenticate with a provided API key (for CLI --api-key option)
203
+ *
204
+ * @param apiKey - The API key to use
205
+ * @returns True if authentication was successful
206
+ */
207
+ export async function authenticateGrokWithKey(apiKey: string): Promise<boolean> {
208
+ // Validate the token
209
+ const isValid = await validateGrokToken(apiKey);
210
+
211
+ if (!isValid) {
212
+ console.error('Invalid Grok API key');
213
+ return false;
214
+ }
215
+
216
+ // Store the token
217
+ await setGrokCredential(apiKey);
218
+ console.log('Grok API authenticated successfully!\n');
219
+
220
+ return true;
221
+ }
222
+
223
+ /**
224
+ * Logout from Grok API
225
+ * Removes stored credentials
226
+ */
227
+ export async function logoutGrok(): Promise<void> {
228
+ const deleted = await deleteGrokCredential();
229
+ if (deleted) {
230
+ console.log('Grok API credentials removed.');
231
+ } else {
232
+ console.log('No Grok API credentials found.');
233
+ }
234
+ }
235
+
236
+ /**
237
+ * Get the Grok API key for API calls
238
+ */
239
+ export async function getGrokToken(): Promise<string | null> {
240
+ return getGrokCredential();
241
+ }
242
+
243
+ /**
244
+ * Ensure Grok is authenticated
245
+ * Prompts for authentication if not already authenticated
246
+ */
247
+ export async function ensureGrokAuth(): Promise<boolean> {
248
+ const status = await checkGrokAuth();
249
+
250
+ if (status.authenticated) {
251
+ return true;
252
+ }
253
+
254
+ return authenticateGrok();
255
+ }
package/src/auth/index.ts CHANGED
@@ -1,18 +1,20 @@
1
1
  /**
2
2
  * Authentication orchestration module
3
- * Coordinates authentication for Claude CLI, OpenAI API, and Gemini API
3
+ * Coordinates authentication for Claude CLI, OpenAI API, Gemini API, and Grok API
4
4
  */
5
5
 
6
6
  import { checkClaudeCLIAuth, authenticateClaude, logoutClaude, type ClaudeAuthStatus } from './claude.js';
7
7
  import { checkOpenAIAuth, authenticateOpenAI, logoutOpenAI, type OpenAIAuthStatus } from './openai.js';
8
8
  import { checkGeminiAuth, authenticateGemini, logoutGemini, type GeminiAuthStatus } from './gemini.js';
9
- import { clearAllCredentials } from './keychain.js';
9
+ import { checkGrokAuth, authenticateGrok, logoutGrok, type GrokAuthStatus } from './grok.js';
10
+ import { clearAllCredentials, deleteCredential } from './keychain.js';
10
11
  import type { AuthStatus } from '../types/index.js';
11
12
 
12
13
  // Re-export individual auth modules
13
14
  export * from './claude.js';
14
15
  export * from './openai.js';
15
16
  export * from './gemini.js';
17
+ export * from './grok.js';
16
18
  export * from './keychain.js';
17
19
  export * from './server.js';
18
20
 
@@ -23,6 +25,7 @@ export interface CombinedAuthStatus {
23
25
  claude: ClaudeAuthStatus;
24
26
  openai: OpenAIAuthStatus;
25
27
  gemini: GeminiAuthStatus;
28
+ grok: GrokAuthStatus;
26
29
  fullyAuthenticated: boolean;
27
30
  hasArbitrator: boolean;
28
31
  }
@@ -31,18 +34,20 @@ export interface CombinedAuthStatus {
31
34
  * Get the authentication status for all services
32
35
  */
33
36
  export async function getAuthStatus(): Promise<CombinedAuthStatus> {
34
- const [claudeStatus, openaiStatus, geminiStatus] = await Promise.all([
37
+ const [claudeStatus, openaiStatus, geminiStatus, grokStatus] = await Promise.all([
35
38
  checkClaudeCLIAuth(),
36
39
  checkOpenAIAuth(),
37
40
  checkGeminiAuth(),
41
+ checkGrokAuth(),
38
42
  ]);
39
43
 
40
44
  return {
41
45
  claude: claudeStatus,
42
46
  openai: openaiStatus,
43
47
  gemini: geminiStatus,
48
+ grok: grokStatus,
44
49
  fullyAuthenticated: claudeStatus.authenticated && openaiStatus.authenticated,
45
- hasArbitrator: geminiStatus.authenticated || openaiStatus.authenticated,
50
+ hasArbitrator: geminiStatus.authenticated || openaiStatus.authenticated || grokStatus.authenticated,
46
51
  };
47
52
  }
48
53
 
@@ -67,6 +72,10 @@ export async function getAuthStatusForDisplay(): Promise<AuthStatus> {
67
72
  authenticated: status.gemini.authenticated,
68
73
  keyLastFour: status.gemini.keyLastFour,
69
74
  },
75
+ grok: {
76
+ authenticated: status.grok.authenticated,
77
+ keyLastFour: status.grok.keyLastFour,
78
+ },
70
79
  };
71
80
  }
72
81
 
@@ -109,11 +118,11 @@ export async function ensureAuthenticated(): Promise<boolean> {
109
118
  /**
110
119
  * Authenticate a specific service
111
120
  *
112
- * @param service - The service to authenticate ('claude', 'openai', 'gemini', or 'all')
121
+ * @param service - The service to authenticate ('claude', 'openai', 'gemini', 'grok', or 'all')
113
122
  * @returns True if authentication was successful
114
123
  */
115
124
  export async function authenticateService(
116
- service: 'claude' | 'openai' | 'gemini' | 'all'
125
+ service: 'claude' | 'openai' | 'gemini' | 'grok' | 'all'
117
126
  ): Promise<boolean> {
118
127
  switch (service) {
119
128
  case 'claude':
@@ -122,6 +131,8 @@ export async function authenticateService(
122
131
  return authenticateOpenAI();
123
132
  case 'gemini':
124
133
  return authenticateGemini();
134
+ case 'grok':
135
+ return authenticateGrok();
125
136
  case 'all':
126
137
  return ensureAuthenticated();
127
138
  }
@@ -130,9 +141,9 @@ export async function authenticateService(
130
141
  /**
131
142
  * Logout from a specific service or all services
132
143
  *
133
- * @param service - The service to logout from ('claude', 'openai', 'gemini', or 'all')
144
+ * @param service - The service to logout from ('claude', 'openai', 'gemini', 'grok', or 'all')
134
145
  */
135
- export async function logout(service: 'claude' | 'openai' | 'gemini' | 'all'): Promise<void> {
146
+ export async function logout(service: 'claude' | 'openai' | 'gemini' | 'grok' | 'all'): Promise<void> {
136
147
  switch (service) {
137
148
  case 'claude':
138
149
  await logoutClaude();
@@ -143,8 +154,13 @@ export async function logout(service: 'claude' | 'openai' | 'gemini' | 'all'): P
143
154
  case 'gemini':
144
155
  await logoutGemini();
145
156
  break;
157
+ case 'grok':
158
+ await logoutGrok();
159
+ break;
146
160
  case 'all':
147
161
  await clearAllCredentials();
162
+ // Also clear grok credential
163
+ await deleteCredential('grok-api');
148
164
  console.log('All credentials removed.');
149
165
  break;
150
166
  }
@@ -156,7 +172,7 @@ export async function logout(service: 'claude' | 'openai' | 'gemini' | 'all'): P
156
172
  * @param service - The service to check
157
173
  * @returns True if the service is authenticated
158
174
  */
159
- export async function isAuthenticated(service: 'claude' | 'openai' | 'gemini' | 'both' | 'all'): Promise<boolean> {
175
+ export async function isAuthenticated(service: 'claude' | 'openai' | 'gemini' | 'grok' | 'both' | 'all'): Promise<boolean> {
160
176
  const status = await getAuthStatus();
161
177
 
162
178
  switch (service) {
@@ -166,6 +182,8 @@ export async function isAuthenticated(service: 'claude' | 'openai' | 'gemini' |
166
182
  return status.openai.authenticated;
167
183
  case 'gemini':
168
184
  return status.gemini.authenticated;
185
+ case 'grok':
186
+ return status.grok.authenticated;
169
187
  case 'both':
170
188
  return status.fullyAuthenticated;
171
189
  case 'all':
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Authentication commands
3
- * Handles login, logout, and status for Claude and OpenAI
3
+ * Handles login, logout, and status for Claude, OpenAI, Gemini, and Grok
4
4
  */
5
5
 
6
6
  import { Command } from 'commander';
@@ -11,6 +11,8 @@ import {
11
11
  isAuthenticated,
12
12
  } from '../../auth/index.js';
13
13
  import { authenticateOpenAIWithKey } from '../../auth/openai.js';
14
+ import { authenticateGeminiWithKey } from '../../auth/gemini.js';
15
+ import { authenticateGrokWithKey } from '../../auth/grok.js';
14
16
  import {
15
17
  printHeader,
16
18
  printAuthStatus,
@@ -46,6 +48,9 @@ export function createAuthCommand(): Command {
46
48
  console.log();
47
49
  printInfo('Run "popeye-cli auth login" to authenticate missing services.');
48
50
  }
51
+ if (status.grok && !status.grok.authenticated) {
52
+ printInfo('Run "popeye-cli auth grok" to authenticate Grok (optional).');
53
+ }
49
54
  } catch (error) {
50
55
  failSpinner('Failed to check status');
51
56
  printError(error instanceof Error ? error.message : 'Unknown error');
@@ -57,12 +62,12 @@ export function createAuthCommand(): Command {
57
62
  auth
58
63
  .command('login')
59
64
  .description('Authenticate with services')
60
- .argument('[service]', 'Service to authenticate (claude, openai, all)', 'all')
61
- .option('--api-key <key>', 'OpenAI API key (for openai service)')
62
- .action(async (service: 'claude' | 'openai' | 'all', options) => {
65
+ .argument('[service]', 'Service to authenticate (claude, openai, gemini, grok, all)', 'all')
66
+ .option('--api-key <key>', 'API key (for openai, gemini, or grok service)')
67
+ .action(async (service: 'claude' | 'openai' | 'gemini' | 'grok' | 'all', options) => {
63
68
  // Validate service
64
- if (!['claude', 'openai', 'all'].includes(service)) {
65
- printError(`Invalid service: ${service}. Use 'claude', 'openai', or 'all'.`);
69
+ if (!['claude', 'openai', 'gemini', 'grok', 'all'].includes(service)) {
70
+ printError(`Invalid service: ${service}. Use 'claude', 'openai', 'gemini', 'grok', or 'all'.`);
66
71
  process.exit(1);
67
72
  }
68
73
 
@@ -113,11 +118,11 @@ export function createAuthCommand(): Command {
113
118
  auth
114
119
  .command('logout')
115
120
  .description('Remove stored credentials')
116
- .argument('[service]', 'Service to logout from (claude, openai, all)', 'all')
117
- .action(async (service: 'claude' | 'openai' | 'all') => {
121
+ .argument('[service]', 'Service to logout from (claude, openai, gemini, grok, all)', 'all')
122
+ .action(async (service: 'claude' | 'openai' | 'gemini' | 'grok' | 'all') => {
118
123
  // Validate service
119
- if (!['claude', 'openai', 'all'].includes(service)) {
120
- printError(`Invalid service: ${service}. Use 'claude', 'openai', or 'all'.`);
124
+ if (!['claude', 'openai', 'gemini', 'grok', 'all'].includes(service)) {
125
+ printError(`Invalid service: ${service}. Use 'claude', 'openai', 'gemini', 'grok', or 'all'.`);
121
126
  process.exit(1);
122
127
  }
123
128
 
@@ -190,5 +195,79 @@ export function createAuthCommand(): Command {
190
195
  }
191
196
  });
192
197
 
198
+ // Gemini-specific subcommand
199
+ auth
200
+ .command('gemini')
201
+ .description('Authenticate with Gemini API')
202
+ .option('--api-key <key>', 'Gemini API key')
203
+ .action(async (options) => {
204
+ printHeader('Gemini API Authentication');
205
+
206
+ if (options.apiKey) {
207
+ startSpinner('Validating API key...');
208
+ const success = await authenticateGeminiWithKey(options.apiKey);
209
+
210
+ if (success) {
211
+ succeedSpinner('Gemini API authenticated!');
212
+ } else {
213
+ failSpinner('Invalid API key');
214
+ process.exit(1);
215
+ }
216
+ return;
217
+ }
218
+
219
+ const alreadyAuth = await isAuthenticated('gemini');
220
+ if (alreadyAuth) {
221
+ printInfo('Already authenticated with Gemini API');
222
+ return;
223
+ }
224
+
225
+ const success = await authenticateService('gemini');
226
+
227
+ if (success) {
228
+ printSuccess('Gemini API authenticated!');
229
+ } else {
230
+ printError('Gemini API authentication failed');
231
+ process.exit(1);
232
+ }
233
+ });
234
+
235
+ // Grok-specific subcommand
236
+ auth
237
+ .command('grok')
238
+ .description('Authenticate with xAI Grok API')
239
+ .option('--api-key <key>', 'Grok API key')
240
+ .action(async (options) => {
241
+ printHeader('Grok API Authentication');
242
+
243
+ if (options.apiKey) {
244
+ startSpinner('Validating API key...');
245
+ const success = await authenticateGrokWithKey(options.apiKey);
246
+
247
+ if (success) {
248
+ succeedSpinner('Grok API authenticated!');
249
+ } else {
250
+ failSpinner('Invalid API key');
251
+ process.exit(1);
252
+ }
253
+ return;
254
+ }
255
+
256
+ const alreadyAuth = await isAuthenticated('grok');
257
+ if (alreadyAuth) {
258
+ printInfo('Already authenticated with Grok API');
259
+ return;
260
+ }
261
+
262
+ const success = await authenticateService('grok');
263
+
264
+ if (success) {
265
+ printSuccess('Grok API authenticated!');
266
+ } else {
267
+ printError('Grok API authentication failed');
268
+ process.exit(1);
269
+ }
270
+ });
271
+
193
272
  return auth;
194
273
  }
@@ -35,7 +35,7 @@ export function createCreateCommand(): Command {
35
35
  .option('-n, --name <name>', 'Project name')
36
36
  .option(
37
37
  '-l, --language <lang>',
38
- 'Output language (python, typescript)',
38
+ 'Output language (python, typescript, fullstack)',
39
39
  'python'
40
40
  )
41
41
  .option(
@@ -66,8 +66,8 @@ export function createCreateCommand(): Command {
66
66
  try {
67
67
  // Validate inputs
68
68
  const language = options.language as OutputLanguage;
69
- if (!['python', 'typescript'].includes(language)) {
70
- printError(`Invalid language: ${language}. Use 'python' or 'typescript'.`);
69
+ if (!['python', 'typescript', 'fullstack'].includes(language)) {
70
+ printError(`Invalid language: ${language}. Use 'python', 'typescript', or 'fullstack'.`);
71
71
  process.exit(1);
72
72
  }
73
73
 
@@ -192,7 +192,16 @@ export function createCreateCommand(): Command {
192
192
  printSuccess('Your project is ready!');
193
193
  console.log();
194
194
  printInfo(`cd ${projectDir}`);
195
- printInfo(language === 'python' ? 'python -m pytest tests/' : 'npm test');
195
+ if (language === 'python') {
196
+ printInfo('python -m pytest tests/');
197
+ } else if (language === 'typescript') {
198
+ printInfo('npm test');
199
+ } else if (language === 'fullstack') {
200
+ printInfo('docker-compose up # Run both frontend and backend');
201
+ printInfo('# Or run separately:');
202
+ printInfo('cd apps/frontend && npm install && npm run dev');
203
+ printInfo('cd apps/backend && pip install -e . && uvicorn src.backend.main:app --reload');
204
+ }
196
205
  } else {
197
206
  printHeader('Project Creation Failed');
198
207