popeye-cli 1.0.1 → 1.1.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 (166) hide show
  1. package/README.md +521 -125
  2. package/dist/adapters/claude.d.ts +16 -4
  3. package/dist/adapters/claude.d.ts.map +1 -1
  4. package/dist/adapters/claude.js +679 -33
  5. package/dist/adapters/claude.js.map +1 -1
  6. package/dist/adapters/gemini.d.ts +55 -0
  7. package/dist/adapters/gemini.d.ts.map +1 -0
  8. package/dist/adapters/gemini.js +318 -0
  9. package/dist/adapters/gemini.js.map +1 -0
  10. package/dist/adapters/openai.d.ts.map +1 -1
  11. package/dist/adapters/openai.js +41 -7
  12. package/dist/adapters/openai.js.map +1 -1
  13. package/dist/auth/claude.d.ts +11 -9
  14. package/dist/auth/claude.d.ts.map +1 -1
  15. package/dist/auth/claude.js +107 -71
  16. package/dist/auth/claude.js.map +1 -1
  17. package/dist/auth/gemini.d.ts +58 -0
  18. package/dist/auth/gemini.d.ts.map +1 -0
  19. package/dist/auth/gemini.js +172 -0
  20. package/dist/auth/gemini.js.map +1 -0
  21. package/dist/auth/index.d.ts +11 -7
  22. package/dist/auth/index.d.ts.map +1 -1
  23. package/dist/auth/index.js +23 -5
  24. package/dist/auth/index.js.map +1 -1
  25. package/dist/auth/keychain.d.ts +20 -7
  26. package/dist/auth/keychain.d.ts.map +1 -1
  27. package/dist/auth/keychain.js +85 -29
  28. package/dist/auth/keychain.js.map +1 -1
  29. package/dist/auth/openai.d.ts +2 -2
  30. package/dist/auth/openai.d.ts.map +1 -1
  31. package/dist/auth/openai.js +30 -32
  32. package/dist/auth/openai.js.map +1 -1
  33. package/dist/cli/interactive.d.ts.map +1 -1
  34. package/dist/cli/interactive.js +1151 -110
  35. package/dist/cli/interactive.js.map +1 -1
  36. package/dist/config/defaults.d.ts +6 -1
  37. package/dist/config/defaults.d.ts.map +1 -1
  38. package/dist/config/defaults.js +10 -2
  39. package/dist/config/defaults.js.map +1 -1
  40. package/dist/config/index.d.ts +10 -0
  41. package/dist/config/index.d.ts.map +1 -1
  42. package/dist/config/index.js +19 -0
  43. package/dist/config/index.js.map +1 -1
  44. package/dist/config/schema.d.ts +20 -0
  45. package/dist/config/schema.d.ts.map +1 -1
  46. package/dist/config/schema.js +7 -0
  47. package/dist/config/schema.js.map +1 -1
  48. package/dist/generators/python.d.ts.map +1 -1
  49. package/dist/generators/python.js +1 -0
  50. package/dist/generators/python.js.map +1 -1
  51. package/dist/generators/typescript.d.ts.map +1 -1
  52. package/dist/generators/typescript.js +1 -0
  53. package/dist/generators/typescript.js.map +1 -1
  54. package/dist/state/index.d.ts +108 -0
  55. package/dist/state/index.d.ts.map +1 -1
  56. package/dist/state/index.js +551 -4
  57. package/dist/state/index.js.map +1 -1
  58. package/dist/state/registry.d.ts +52 -0
  59. package/dist/state/registry.d.ts.map +1 -0
  60. package/dist/state/registry.js +215 -0
  61. package/dist/state/registry.js.map +1 -0
  62. package/dist/types/cli.d.ts +4 -0
  63. package/dist/types/cli.d.ts.map +1 -1
  64. package/dist/types/cli.js.map +1 -1
  65. package/dist/types/consensus.d.ts +69 -4
  66. package/dist/types/consensus.d.ts.map +1 -1
  67. package/dist/types/consensus.js +24 -3
  68. package/dist/types/consensus.js.map +1 -1
  69. package/dist/types/workflow.d.ts +55 -0
  70. package/dist/types/workflow.d.ts.map +1 -1
  71. package/dist/types/workflow.js +16 -0
  72. package/dist/types/workflow.js.map +1 -1
  73. package/dist/workflow/auto-fix.d.ts +45 -0
  74. package/dist/workflow/auto-fix.d.ts.map +1 -0
  75. package/dist/workflow/auto-fix.js +274 -0
  76. package/dist/workflow/auto-fix.js.map +1 -0
  77. package/dist/workflow/consensus.d.ts +44 -2
  78. package/dist/workflow/consensus.d.ts.map +1 -1
  79. package/dist/workflow/consensus.js +565 -17
  80. package/dist/workflow/consensus.js.map +1 -1
  81. package/dist/workflow/execution-mode.d.ts +10 -4
  82. package/dist/workflow/execution-mode.d.ts.map +1 -1
  83. package/dist/workflow/execution-mode.js +547 -58
  84. package/dist/workflow/execution-mode.js.map +1 -1
  85. package/dist/workflow/index.d.ts +14 -2
  86. package/dist/workflow/index.d.ts.map +1 -1
  87. package/dist/workflow/index.js +69 -6
  88. package/dist/workflow/index.js.map +1 -1
  89. package/dist/workflow/milestone-workflow.d.ts +34 -0
  90. package/dist/workflow/milestone-workflow.d.ts.map +1 -0
  91. package/dist/workflow/milestone-workflow.js +414 -0
  92. package/dist/workflow/milestone-workflow.js.map +1 -0
  93. package/dist/workflow/plan-mode.d.ts +14 -1
  94. package/dist/workflow/plan-mode.d.ts.map +1 -1
  95. package/dist/workflow/plan-mode.js +589 -47
  96. package/dist/workflow/plan-mode.js.map +1 -1
  97. package/dist/workflow/plan-storage.d.ts +142 -0
  98. package/dist/workflow/plan-storage.d.ts.map +1 -0
  99. package/dist/workflow/plan-storage.js +331 -0
  100. package/dist/workflow/plan-storage.js.map +1 -0
  101. package/dist/workflow/project-verification.d.ts +37 -0
  102. package/dist/workflow/project-verification.d.ts.map +1 -0
  103. package/dist/workflow/project-verification.js +381 -0
  104. package/dist/workflow/project-verification.js.map +1 -0
  105. package/dist/workflow/task-workflow.d.ts +37 -0
  106. package/dist/workflow/task-workflow.d.ts.map +1 -0
  107. package/dist/workflow/task-workflow.js +383 -0
  108. package/dist/workflow/task-workflow.js.map +1 -0
  109. package/dist/workflow/test-runner.d.ts +1 -0
  110. package/dist/workflow/test-runner.d.ts.map +1 -1
  111. package/dist/workflow/test-runner.js +9 -5
  112. package/dist/workflow/test-runner.js.map +1 -1
  113. package/dist/workflow/ui-designer.d.ts +82 -0
  114. package/dist/workflow/ui-designer.d.ts.map +1 -0
  115. package/dist/workflow/ui-designer.js +234 -0
  116. package/dist/workflow/ui-designer.js.map +1 -0
  117. package/dist/workflow/ui-setup.d.ts +58 -0
  118. package/dist/workflow/ui-setup.d.ts.map +1 -0
  119. package/dist/workflow/ui-setup.js +685 -0
  120. package/dist/workflow/ui-setup.js.map +1 -0
  121. package/dist/workflow/ui-verification.d.ts +114 -0
  122. package/dist/workflow/ui-verification.d.ts.map +1 -0
  123. package/dist/workflow/ui-verification.js +258 -0
  124. package/dist/workflow/ui-verification.js.map +1 -0
  125. package/dist/workflow/workflow-logger.d.ts +110 -0
  126. package/dist/workflow/workflow-logger.d.ts.map +1 -0
  127. package/dist/workflow/workflow-logger.js +267 -0
  128. package/dist/workflow/workflow-logger.js.map +1 -0
  129. package/package.json +2 -2
  130. package/src/adapters/claude.ts +815 -34
  131. package/src/adapters/gemini.ts +373 -0
  132. package/src/adapters/openai.ts +40 -7
  133. package/src/auth/claude.ts +120 -78
  134. package/src/auth/gemini.ts +207 -0
  135. package/src/auth/index.ts +28 -8
  136. package/src/auth/keychain.ts +95 -28
  137. package/src/auth/openai.ts +29 -36
  138. package/src/cli/interactive.ts +1357 -115
  139. package/src/config/defaults.ts +10 -2
  140. package/src/config/index.ts +21 -0
  141. package/src/config/schema.ts +7 -0
  142. package/src/generators/python.ts +1 -0
  143. package/src/generators/typescript.ts +1 -0
  144. package/src/state/index.ts +713 -4
  145. package/src/state/registry.ts +278 -0
  146. package/src/types/cli.ts +4 -0
  147. package/src/types/consensus.ts +65 -6
  148. package/src/types/workflow.ts +35 -0
  149. package/src/workflow/auto-fix.ts +340 -0
  150. package/src/workflow/consensus.ts +750 -16
  151. package/src/workflow/execution-mode.ts +673 -74
  152. package/src/workflow/index.ts +95 -6
  153. package/src/workflow/milestone-workflow.ts +576 -0
  154. package/src/workflow/plan-mode.ts +696 -50
  155. package/src/workflow/plan-storage.ts +482 -0
  156. package/src/workflow/project-verification.ts +471 -0
  157. package/src/workflow/task-workflow.ts +525 -0
  158. package/src/workflow/test-runner.ts +10 -5
  159. package/src/workflow/ui-designer.ts +337 -0
  160. package/src/workflow/ui-setup.ts +797 -0
  161. package/src/workflow/ui-verification.ts +357 -0
  162. package/src/workflow/workflow-logger.ts +353 -0
  163. package/tests/config/config.test.ts +1 -1
  164. package/tests/types/consensus.test.ts +3 -3
  165. package/tests/workflow/plan-mode.test.ts +213 -0
  166. package/tests/workflow/test-runner.test.ts +5 -3
@@ -1,27 +1,68 @@
1
1
  /**
2
- * Keychain wrapper for secure credential storage
3
- * Uses keytar for cross-platform keychain access (macOS Keychain, Windows Credential Vault, Linux Secret Service)
2
+ * Credential storage module
3
+ * Uses file-based storage in ~/.popeye/ directory
4
+ * Falls back to environment variables
4
5
  */
5
6
 
6
- import * as keytar from 'keytar';
7
+ import * as fs from 'node:fs';
8
+ import * as path from 'node:path';
9
+ import * as os from 'node:os';
7
10
  import { SERVICE_NAME, KEYCHAIN_ACCOUNTS, ENV_VARS } from '../config/defaults.js';
8
11
 
9
12
  /**
10
- * Get a credential from the system keychain
11
- * Falls back to environment variable if keychain is unavailable
13
+ * Get the credentials file path
14
+ */
15
+ function getCredentialsPath(): string {
16
+ const popeyeDir = path.join(os.homedir(), '.popeye');
17
+
18
+ // Ensure directory exists
19
+ if (!fs.existsSync(popeyeDir)) {
20
+ fs.mkdirSync(popeyeDir, { mode: 0o700, recursive: true });
21
+ }
22
+
23
+ return path.join(popeyeDir, 'credentials.json');
24
+ }
25
+
26
+ /**
27
+ * Load credentials from file
28
+ */
29
+ function loadCredentials(): Record<string, string> {
30
+ try {
31
+ const filePath = getCredentialsPath();
32
+ if (fs.existsSync(filePath)) {
33
+ const data = fs.readFileSync(filePath, 'utf-8');
34
+ return JSON.parse(data);
35
+ }
36
+ } catch {
37
+ // Ignore errors, return empty
38
+ }
39
+ return {};
40
+ }
41
+
42
+ /**
43
+ * Save credentials to file
44
+ */
45
+ function saveCredentials(credentials: Record<string, string>): void {
46
+ const filePath = getCredentialsPath();
47
+ fs.writeFileSync(filePath, JSON.stringify(credentials, null, 2), {
48
+ mode: 0o600, // Owner read/write only
49
+ });
50
+ }
51
+
52
+ /**
53
+ * Get a credential from storage
54
+ * Falls back to environment variable if not found
12
55
  *
13
56
  * @param account - The account name (e.g., 'claude-cli', 'openai-api')
14
57
  * @returns The stored credential or null if not found
15
58
  */
16
59
  export async function getCredential(account: string): Promise<string | null> {
17
- try {
18
- const password = await keytar.getPassword(SERVICE_NAME, account);
19
- if (password) {
20
- return password;
21
- }
22
- } catch {
23
- // Keychain unavailable, fall back to environment variables
24
- console.warn(`Keychain unavailable for ${account}, checking environment variables`);
60
+ // First check file storage
61
+ const credentials = loadCredentials();
62
+ const key = `${SERVICE_NAME}:${account}`;
63
+
64
+ if (credentials[key]) {
65
+ return credentials[key];
25
66
  }
26
67
 
27
68
  // Fallback to environment variables
@@ -31,43 +72,47 @@ export async function getCredential(account: string): Promise<string | null> {
31
72
  if (account === KEYCHAIN_ACCOUNTS.CLAUDE) {
32
73
  return process.env[ENV_VARS.ANTHROPIC_KEY] || null;
33
74
  }
75
+ if (account === KEYCHAIN_ACCOUNTS.GEMINI) {
76
+ return process.env[ENV_VARS.GEMINI_KEY] || null;
77
+ }
34
78
 
35
79
  return null;
36
80
  }
37
81
 
38
82
  /**
39
- * Store a credential in the system keychain
83
+ * Store a credential
40
84
  *
41
85
  * @param account - The account name
42
86
  * @param password - The credential to store
43
87
  */
44
88
  export async function setCredential(account: string, password: string): Promise<void> {
45
- try {
46
- await keytar.setPassword(SERVICE_NAME, account, password);
47
- } catch (error) {
48
- throw new Error(
49
- `Failed to store credential in keychain: ${error instanceof Error ? error.message : 'Unknown error'}`
50
- );
51
- }
89
+ const credentials = loadCredentials();
90
+ const key = `${SERVICE_NAME}:${account}`;
91
+ credentials[key] = password;
92
+ saveCredentials(credentials);
52
93
  }
53
94
 
54
95
  /**
55
- * Delete a credential from the system keychain
96
+ * Delete a credential from storage
56
97
  *
57
98
  * @param account - The account name
58
99
  * @returns True if the credential was deleted, false if it didn't exist
59
100
  */
60
101
  export async function deleteCredential(account: string): Promise<boolean> {
61
- try {
62
- return await keytar.deletePassword(SERVICE_NAME, account);
63
- } catch (error) {
64
- console.warn(`Failed to delete credential from keychain: ${error}`);
65
- return false;
102
+ const credentials = loadCredentials();
103
+ const key = `${SERVICE_NAME}:${account}`;
104
+
105
+ if (credentials[key]) {
106
+ delete credentials[key];
107
+ saveCredentials(credentials);
108
+ return true;
66
109
  }
110
+
111
+ return false;
67
112
  }
68
113
 
69
114
  /**
70
- * Check if a credential exists in the keychain
115
+ * Check if a credential exists
71
116
  *
72
117
  * @param account - The account name
73
118
  * @returns True if the credential exists
@@ -119,12 +164,34 @@ export async function deleteOpenAICredential(): Promise<boolean> {
119
164
  return deleteCredential(KEYCHAIN_ACCOUNTS.OPENAI);
120
165
  }
121
166
 
167
+ /**
168
+ * Get the Gemini API credential
169
+ */
170
+ export async function getGeminiCredential(): Promise<string | null> {
171
+ return getCredential(KEYCHAIN_ACCOUNTS.GEMINI);
172
+ }
173
+
174
+ /**
175
+ * Set the Gemini API credential
176
+ */
177
+ export async function setGeminiCredential(apiKey: string): Promise<void> {
178
+ return setCredential(KEYCHAIN_ACCOUNTS.GEMINI, apiKey);
179
+ }
180
+
181
+ /**
182
+ * Delete the Gemini API credential
183
+ */
184
+ export async function deleteGeminiCredential(): Promise<boolean> {
185
+ return deleteCredential(KEYCHAIN_ACCOUNTS.GEMINI);
186
+ }
187
+
122
188
  /**
123
189
  * Clear all stored credentials
124
190
  */
125
191
  export async function clearAllCredentials(): Promise<void> {
126
192
  await deleteClaudeCredential();
127
193
  await deleteOpenAICredential();
194
+ await deleteGeminiCredential();
128
195
  }
129
196
 
130
197
  /**
@@ -3,15 +3,14 @@
3
3
  * Handles API key validation and storage
4
4
  */
5
5
 
6
+ import * as readline from 'node:readline';
6
7
  import OpenAI from 'openai';
7
- import open from 'open';
8
8
  import {
9
9
  getOpenAICredential,
10
10
  setOpenAICredential,
11
11
  deleteOpenAICredential,
12
12
  maskCredential,
13
13
  } from './keychain.js';
14
- import { startAuthCallbackServer, findAvailablePort } from './server.js';
15
14
 
16
15
  /**
17
16
  * OpenAI authentication status
@@ -112,38 +111,32 @@ export async function checkOpenAIAuth(): Promise<OpenAIAuthStatus> {
112
111
  }
113
112
 
114
113
  /**
115
- * Launch the browser-based token entry popup
114
+ * Prompt for API key in the terminal
116
115
  *
117
116
  * @returns The entered API key or null if cancelled
118
117
  */
119
- export async function launchTokenEntryPopup(): Promise<string | null> {
120
- try {
121
- const port = await findAvailablePort(3000, 3100);
122
-
123
- console.log('Opening browser for API key entry...\n');
124
-
125
- // Start the token entry server
126
- const authPromise = startAuthCallbackServer({
127
- port,
128
- type: 'openai',
129
- timeout: 300000, // 5 minutes
118
+ export async function promptForAPIKey(): Promise<string | null> {
119
+ return new Promise((resolve) => {
120
+ const rl = readline.createInterface({
121
+ input: process.stdin,
122
+ output: process.stdout,
130
123
  });
131
124
 
132
- // Open the browser
133
- await open(`http://127.0.0.1:${port}`);
134
-
135
- // Wait for the token
136
- const result = await authPromise;
137
-
138
- if (result.success && result.token) {
139
- return result.token;
140
- }
141
-
142
- return null;
143
- } catch (error) {
144
- console.error(`Failed to launch token entry: ${error}`);
145
- return null;
146
- }
125
+ console.log('\nGet your API key from: https://platform.openai.com/api-keys\n');
126
+
127
+ rl.question('Enter your OpenAI API key (starts with sk-): ', (answer) => {
128
+ rl.close();
129
+ const key = answer.trim();
130
+ if (key && key.startsWith('sk-')) {
131
+ resolve(key);
132
+ } else if (key) {
133
+ console.log('\nWarning: Key does not start with "sk-", but trying anyway...');
134
+ resolve(key);
135
+ } else {
136
+ resolve(null);
137
+ }
138
+ });
139
+ });
147
140
  }
148
141
 
149
142
  /**
@@ -162,17 +155,17 @@ export async function authenticateOpenAI(): Promise<boolean> {
162
155
  console.log('OpenAI API key required.');
163
156
 
164
157
  try {
165
- // Launch the token entry popup
166
- const token = await launchTokenEntryPopup();
158
+ // Prompt for the API key
159
+ const apiKey = await promptForAPIKey();
167
160
 
168
- if (!token) {
169
- console.error('No API key provided');
161
+ if (!apiKey) {
162
+ console.error('\nNo API key provided');
170
163
  return false;
171
164
  }
172
165
 
173
166
  // Validate the token
174
- console.log('Validating API key...');
175
- const isValid = await validateOpenAIToken(token);
167
+ console.log('\nValidating API key...');
168
+ const isValid = await validateOpenAIToken(apiKey);
176
169
 
177
170
  if (!isValid) {
178
171
  console.error('Invalid OpenAI API key');
@@ -180,7 +173,7 @@ export async function authenticateOpenAI(): Promise<boolean> {
180
173
  }
181
174
 
182
175
  // Store the token
183
- await setOpenAICredential(token);
176
+ await setOpenAICredential(apiKey);
184
177
  console.log('OpenAI API authenticated successfully!\n');
185
178
 
186
179
  return true;