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.
- package/.env.example +24 -1
- package/CONTRIBUTING.md +275 -0
- package/OPEN_SOURCE_MANIFESTO.md +172 -0
- package/README.md +832 -123
- package/dist/adapters/claude.d.ts +19 -4
- package/dist/adapters/claude.d.ts.map +1 -1
- package/dist/adapters/claude.js +908 -42
- package/dist/adapters/claude.js.map +1 -1
- package/dist/adapters/gemini.d.ts +55 -0
- package/dist/adapters/gemini.d.ts.map +1 -0
- package/dist/adapters/gemini.js +318 -0
- package/dist/adapters/gemini.js.map +1 -0
- package/dist/adapters/grok.d.ts +73 -0
- package/dist/adapters/grok.d.ts.map +1 -0
- package/dist/adapters/grok.js +430 -0
- package/dist/adapters/grok.js.map +1 -0
- package/dist/adapters/openai.d.ts +1 -1
- package/dist/adapters/openai.d.ts.map +1 -1
- package/dist/adapters/openai.js +47 -8
- package/dist/adapters/openai.js.map +1 -1
- package/dist/auth/claude.d.ts +11 -9
- package/dist/auth/claude.d.ts.map +1 -1
- package/dist/auth/claude.js +107 -71
- package/dist/auth/claude.js.map +1 -1
- package/dist/auth/gemini.d.ts +58 -0
- package/dist/auth/gemini.d.ts.map +1 -0
- package/dist/auth/gemini.js +172 -0
- package/dist/auth/gemini.js.map +1 -0
- package/dist/auth/grok.d.ts +73 -0
- package/dist/auth/grok.d.ts.map +1 -0
- package/dist/auth/grok.js +211 -0
- package/dist/auth/grok.js.map +1 -0
- package/dist/auth/index.d.ts +14 -7
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +41 -6
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/keychain.d.ts +20 -7
- package/dist/auth/keychain.d.ts.map +1 -1
- package/dist/auth/keychain.js +85 -29
- package/dist/auth/keychain.js.map +1 -1
- package/dist/auth/openai.d.ts +2 -2
- package/dist/auth/openai.d.ts.map +1 -1
- package/dist/auth/openai.js +30 -32
- package/dist/auth/openai.js.map +1 -1
- package/dist/cli/commands/auth.d.ts +1 -1
- package/dist/cli/commands/auth.d.ts.map +1 -1
- package/dist/cli/commands/auth.js +79 -8
- package/dist/cli/commands/auth.js.map +1 -1
- package/dist/cli/commands/create.d.ts.map +1 -1
- package/dist/cli/commands/create.js +15 -4
- package/dist/cli/commands/create.js.map +1 -1
- package/dist/cli/interactive.d.ts.map +1 -1
- package/dist/cli/interactive.js +1494 -114
- package/dist/cli/interactive.js.map +1 -1
- package/dist/config/defaults.d.ts +9 -1
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +19 -2
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/index.d.ts +19 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +33 -1
- package/dist/config/index.js.map +1 -1
- package/dist/config/schema.d.ts +47 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +29 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/generators/fullstack.d.ts +32 -0
- package/dist/generators/fullstack.d.ts.map +1 -0
- package/dist/generators/fullstack.js +497 -0
- package/dist/generators/fullstack.js.map +1 -0
- package/dist/generators/index.d.ts +4 -3
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +15 -1
- package/dist/generators/index.js.map +1 -1
- package/dist/generators/python.d.ts +17 -1
- package/dist/generators/python.d.ts.map +1 -1
- package/dist/generators/python.js +34 -20
- package/dist/generators/python.js.map +1 -1
- package/dist/generators/templates/fullstack.d.ts +113 -0
- package/dist/generators/templates/fullstack.d.ts.map +1 -0
- package/dist/generators/templates/fullstack.js +1004 -0
- package/dist/generators/templates/fullstack.js.map +1 -0
- package/dist/generators/typescript.d.ts +19 -1
- package/dist/generators/typescript.d.ts.map +1 -1
- package/dist/generators/typescript.js +37 -20
- package/dist/generators/typescript.js.map +1 -1
- package/dist/state/index.d.ts +108 -0
- package/dist/state/index.d.ts.map +1 -1
- package/dist/state/index.js +551 -4
- package/dist/state/index.js.map +1 -1
- package/dist/state/registry.d.ts +52 -0
- package/dist/state/registry.d.ts.map +1 -0
- package/dist/state/registry.js +215 -0
- package/dist/state/registry.js.map +1 -0
- package/dist/types/cli.d.ts +8 -0
- package/dist/types/cli.d.ts.map +1 -1
- package/dist/types/cli.js.map +1 -1
- package/dist/types/consensus.d.ts +186 -4
- package/dist/types/consensus.d.ts.map +1 -1
- package/dist/types/consensus.js +35 -3
- package/dist/types/consensus.js.map +1 -1
- package/dist/types/project.d.ts +76 -0
- package/dist/types/project.d.ts.map +1 -1
- package/dist/types/project.js +1 -1
- package/dist/types/project.js.map +1 -1
- package/dist/types/workflow.d.ts +217 -16
- package/dist/types/workflow.d.ts.map +1 -1
- package/dist/types/workflow.js +40 -1
- package/dist/types/workflow.js.map +1 -1
- package/dist/workflow/auto-fix.d.ts +45 -0
- package/dist/workflow/auto-fix.d.ts.map +1 -0
- package/dist/workflow/auto-fix.js +274 -0
- package/dist/workflow/auto-fix.js.map +1 -0
- package/dist/workflow/consensus.d.ts +70 -2
- package/dist/workflow/consensus.d.ts.map +1 -1
- package/dist/workflow/consensus.js +872 -17
- package/dist/workflow/consensus.js.map +1 -1
- package/dist/workflow/execution-mode.d.ts +10 -4
- package/dist/workflow/execution-mode.d.ts.map +1 -1
- package/dist/workflow/execution-mode.js +547 -58
- package/dist/workflow/execution-mode.js.map +1 -1
- package/dist/workflow/index.d.ts +14 -2
- package/dist/workflow/index.d.ts.map +1 -1
- package/dist/workflow/index.js +69 -6
- package/dist/workflow/index.js.map +1 -1
- package/dist/workflow/milestone-workflow.d.ts +34 -0
- package/dist/workflow/milestone-workflow.d.ts.map +1 -0
- package/dist/workflow/milestone-workflow.js +414 -0
- package/dist/workflow/milestone-workflow.js.map +1 -0
- package/dist/workflow/plan-mode.d.ts +80 -3
- package/dist/workflow/plan-mode.d.ts.map +1 -1
- package/dist/workflow/plan-mode.js +767 -49
- package/dist/workflow/plan-mode.js.map +1 -1
- package/dist/workflow/plan-storage.d.ts +386 -0
- package/dist/workflow/plan-storage.d.ts.map +1 -0
- package/dist/workflow/plan-storage.js +878 -0
- package/dist/workflow/plan-storage.js.map +1 -0
- package/dist/workflow/project-verification.d.ts +37 -0
- package/dist/workflow/project-verification.d.ts.map +1 -0
- package/dist/workflow/project-verification.js +381 -0
- package/dist/workflow/project-verification.js.map +1 -0
- package/dist/workflow/task-workflow.d.ts +37 -0
- package/dist/workflow/task-workflow.d.ts.map +1 -0
- package/dist/workflow/task-workflow.js +386 -0
- package/dist/workflow/task-workflow.js.map +1 -0
- package/dist/workflow/test-runner.d.ts +9 -0
- package/dist/workflow/test-runner.d.ts.map +1 -1
- package/dist/workflow/test-runner.js +101 -5
- package/dist/workflow/test-runner.js.map +1 -1
- package/dist/workflow/ui-designer.d.ts +82 -0
- package/dist/workflow/ui-designer.d.ts.map +1 -0
- package/dist/workflow/ui-designer.js +234 -0
- package/dist/workflow/ui-designer.js.map +1 -0
- package/dist/workflow/ui-setup.d.ts +58 -0
- package/dist/workflow/ui-setup.d.ts.map +1 -0
- package/dist/workflow/ui-setup.js +685 -0
- package/dist/workflow/ui-setup.js.map +1 -0
- package/dist/workflow/ui-verification.d.ts +114 -0
- package/dist/workflow/ui-verification.d.ts.map +1 -0
- package/dist/workflow/ui-verification.js +258 -0
- package/dist/workflow/ui-verification.js.map +1 -0
- package/dist/workflow/workflow-logger.d.ts +110 -0
- package/dist/workflow/workflow-logger.d.ts.map +1 -0
- package/dist/workflow/workflow-logger.js +267 -0
- package/dist/workflow/workflow-logger.js.map +1 -0
- package/dist/workflow/workspace-manager.d.ts +342 -0
- package/dist/workflow/workspace-manager.d.ts.map +1 -0
- package/dist/workflow/workspace-manager.js +733 -0
- package/dist/workflow/workspace-manager.js.map +1 -0
- package/package.json +2 -2
- package/src/adapters/claude.ts +1067 -47
- package/src/adapters/gemini.ts +373 -0
- package/src/adapters/grok.ts +492 -0
- package/src/adapters/openai.ts +48 -9
- package/src/auth/claude.ts +120 -78
- package/src/auth/gemini.ts +207 -0
- package/src/auth/grok.ts +255 -0
- package/src/auth/index.ts +47 -9
- package/src/auth/keychain.ts +95 -28
- package/src/auth/openai.ts +29 -36
- package/src/cli/commands/auth.ts +89 -10
- package/src/cli/commands/create.ts +13 -4
- package/src/cli/interactive.ts +1774 -142
- package/src/config/defaults.ts +19 -2
- package/src/config/index.ts +36 -1
- package/src/config/schema.ts +30 -1
- package/src/generators/fullstack.ts +551 -0
- package/src/generators/index.ts +25 -1
- package/src/generators/python.ts +65 -20
- package/src/generators/templates/fullstack.ts +1047 -0
- package/src/generators/typescript.ts +69 -20
- package/src/state/index.ts +713 -4
- package/src/state/registry.ts +278 -0
- package/src/types/cli.ts +8 -0
- package/src/types/consensus.ts +197 -6
- package/src/types/project.ts +82 -1
- package/src/types/workflow.ts +90 -1
- package/src/workflow/auto-fix.ts +340 -0
- package/src/workflow/consensus.ts +1180 -16
- package/src/workflow/execution-mode.ts +673 -74
- package/src/workflow/index.ts +95 -6
- package/src/workflow/milestone-workflow.ts +576 -0
- package/src/workflow/plan-mode.ts +924 -50
- package/src/workflow/plan-storage.ts +1282 -0
- package/src/workflow/project-verification.ts +471 -0
- package/src/workflow/task-workflow.ts +528 -0
- package/src/workflow/test-runner.ts +120 -5
- package/src/workflow/ui-designer.ts +337 -0
- package/src/workflow/ui-setup.ts +797 -0
- package/src/workflow/ui-verification.ts +357 -0
- package/src/workflow/workflow-logger.ts +353 -0
- package/src/workflow/workspace-manager.ts +912 -0
- package/tests/config/config.test.ts +1 -1
- package/tests/types/consensus.test.ts +3 -3
- package/tests/workflow/plan-mode.test.ts +213 -0
- package/tests/workflow/test-runner.test.ts +5 -3
package/src/auth/claude.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Claude CLI authentication module
|
|
3
|
-
*
|
|
3
|
+
* Checks for Claude Code CLI installation and authentication status
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import
|
|
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
|
-
*
|
|
22
|
-
|
|
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
|
-
|
|
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
|
-
|
|
29
|
-
|
|
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
|
-
//
|
|
33
|
-
//
|
|
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
|
-
|
|
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
|
|
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
|
|
54
|
-
if (
|
|
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('
|
|
60
|
-
console.log('
|
|
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
|
-
|
|
64
|
-
const
|
|
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
|
-
|
|
82
|
-
|
|
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.
|
|
158
|
+
console.log('Login process exited. Please run "claude login" manually if needed.\n');
|
|
94
159
|
return false;
|
|
95
160
|
}
|
|
96
|
-
} catch
|
|
97
|
-
console.
|
|
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
|
|
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
|
+
}
|