qaos 0.0.0 → 0.0.2
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/dist/app.d.ts +2 -5
- package/dist/app.js +253 -5
- package/dist/cli.js +90 -17
- package/dist/commands/auth.d.ts +41 -0
- package/dist/commands/auth.js +137 -0
- package/dist/commands/hist.d.ts +21 -0
- package/dist/commands/hist.js +41 -0
- package/dist/components/auth/auth-ui.d.ts +8 -0
- package/dist/components/auth/auth-ui.js +47 -0
- package/dist/components/auth/loading-spinner.d.ts +2 -0
- package/dist/components/auth/loading-spinner.js +15 -0
- package/dist/components/hist/hist-ui.d.ts +7 -0
- package/dist/components/hist/hist-ui.js +139 -0
- package/dist/components/landing/splash.d.ts +2 -0
- package/dist/components/landing/splash.js +14 -0
- package/dist/components/run/run-ui.d.ts +11 -0
- package/dist/components/run/run-ui.js +298 -0
- package/dist/components/run/spinner.d.ts +6 -0
- package/dist/components/run/spinner.js +15 -0
- package/dist/components/run/thinking-animation.d.ts +6 -0
- package/dist/components/run/thinking-animation.js +16 -0
- package/dist/components/settings/settings-ui.d.ts +5 -0
- package/dist/components/settings/settings-ui.js +52 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.js +22 -0
- package/dist/constants/colors.d.ts +8 -0
- package/dist/constants/colors.js +8 -0
- package/dist/constants/commands.d.ts +1 -0
- package/dist/constants/commands.js +1 -0
- package/dist/constants/help-instructions.d.ts +4 -0
- package/dist/constants/help-instructions.js +126 -0
- package/dist/constants/ws-enums.d.ts +7 -0
- package/dist/constants/ws-enums.js +8 -0
- package/dist/contexts/language-context.d.ts +13 -0
- package/dist/contexts/language-context.js +39 -0
- package/dist/dev-config.d.ts +3 -0
- package/dist/dev-config.js +3 -0
- package/dist/entry-functions.d.ts +26 -0
- package/dist/entry-functions.js +357 -0
- package/dist/i18n/language-service.d.ts +18 -0
- package/dist/i18n/language-service.js +34 -0
- package/dist/i18n/locales/en.json +176 -0
- package/dist/i18n/locales/fr.json +176 -0
- package/dist/prod-config.d.ts +3 -0
- package/dist/prod-config.js +3 -0
- package/dist/services/auth-server.d.ts +17 -0
- package/dist/services/auth-server.js +109 -0
- package/dist/services/auth-service.d.ts +68 -0
- package/dist/services/auth-service.js +194 -0
- package/dist/services/browser-action-service.d.ts +212 -0
- package/dist/services/browser-action-service.js +655 -0
- package/dist/services/browser-service.d.ts +116 -0
- package/dist/services/browser-service.js +368 -0
- package/dist/services/communication-service.d.ts +35 -0
- package/dist/services/communication-service.js +197 -0
- package/dist/services/dom-extraction-service.d.ts +89 -0
- package/dist/services/dom-extraction-service.js +472 -0
- package/dist/services/dom-views.d.ts +144 -0
- package/dist/services/dom-views.js +899 -0
- package/dist/services/socket-service.d.ts +43 -0
- package/dist/services/socket-service.js +236 -0
- package/dist/services/state-service.d.ts +26 -0
- package/dist/services/state-service.js +344 -0
- package/dist/types/app-props.d.ts +17 -0
- package/dist/types/app-props.js +1 -0
- package/dist/types/config-data.d.ts +3 -0
- package/dist/types/config-data.js +1 -0
- package/dist/types/initialize-connection-response.d.ts +4 -0
- package/dist/types/initialize-connection-response.js +1 -0
- package/dist/types/next-actions-response.d.ts +22 -0
- package/dist/types/next-actions-response.js +1 -0
- package/dist/types/run-args.d.ts +6 -0
- package/dist/types/run-args.js +1 -0
- package/dist/types/run-ui-state.d.ts +54 -0
- package/dist/types/run-ui-state.js +1 -0
- package/dist/types/send-new-state-request.d.ts +13 -0
- package/dist/types/send-new-state-request.js +1 -0
- package/dist/utils/config-validator.d.ts +8 -0
- package/dist/utils/config-validator.js +88 -0
- package/dist/utils/logger.d.ts +1 -0
- package/dist/utils/logger.js +6 -0
- package/package.json +11 -4
package/dist/app.d.ts
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
export default function App({ name }: Props): React.JSX.Element;
|
|
6
|
-
export {};
|
|
2
|
+
import { AppProps } from './types/app-props.js';
|
|
3
|
+
export default function App(args: AppProps): React.JSX.Element;
|
package/dist/app.js
CHANGED
|
@@ -1,7 +1,255 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import Splash from './components/landing/splash.js';
|
|
2
3
|
import { Text } from 'ink';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
import { run, runWorkflow, getActions } from './entry-functions.js';
|
|
5
|
+
import RunUi, { RunConnecting, RunError } from './components/run/run-ui.js';
|
|
6
|
+
import SettingsUI from './components/settings/settings-ui.js';
|
|
7
|
+
import { useLanguage } from './contexts/language-context.js';
|
|
8
|
+
import AuthUI from './components/auth/auth-ui.js';
|
|
9
|
+
import HistUI from './components/hist/hist-ui.js';
|
|
10
|
+
import { authenticate, logout, getAuthStatus } from './commands/auth.js';
|
|
11
|
+
// Exit delay constants (in milliseconds)
|
|
12
|
+
const EXIT_DELAY_SUCCESS = 1000;
|
|
13
|
+
const EXIT_DELAY_INFO = 1500;
|
|
14
|
+
const EXIT_DELAY_ERROR = 2000;
|
|
15
|
+
export default function App(args) {
|
|
16
|
+
const { input: command, flags } = args.args;
|
|
17
|
+
const { t } = useLanguage();
|
|
18
|
+
const [runResult, setRunResult] = useState(null);
|
|
19
|
+
const [loading, setLoading] = useState(false);
|
|
20
|
+
const [runUiState, setRunUiState] = useState(null);
|
|
21
|
+
const [authStatus, setAuthStatus] = useState('idle');
|
|
22
|
+
const [authMessage, setAuthMessage] = useState('');
|
|
23
|
+
const [authError, setAuthError] = useState('');
|
|
24
|
+
const createInitialRunUiState = () => ({
|
|
25
|
+
phase: 'idle',
|
|
26
|
+
connection: 'connecting',
|
|
27
|
+
thinking: '',
|
|
28
|
+
tasks: [],
|
|
29
|
+
actions: [],
|
|
30
|
+
});
|
|
31
|
+
const reduceRunUiState = (prevState, event) => {
|
|
32
|
+
const baseState = prevState ?? createInitialRunUiState();
|
|
33
|
+
switch (event.type) {
|
|
34
|
+
case 'config_loaded':
|
|
35
|
+
return {
|
|
36
|
+
...baseState,
|
|
37
|
+
tasks: event.tasks,
|
|
38
|
+
};
|
|
39
|
+
case 'connection_status':
|
|
40
|
+
return {
|
|
41
|
+
...baseState,
|
|
42
|
+
connection: event.status,
|
|
43
|
+
phase: event.status === 'connected'
|
|
44
|
+
? 'connected'
|
|
45
|
+
: baseState.phase === 'idle'
|
|
46
|
+
? 'connecting'
|
|
47
|
+
: baseState.phase,
|
|
48
|
+
};
|
|
49
|
+
case 'run_started':
|
|
50
|
+
return {
|
|
51
|
+
...baseState,
|
|
52
|
+
phase: 'running',
|
|
53
|
+
runId: event.runId,
|
|
54
|
+
reportUrl: event.reportUrl,
|
|
55
|
+
errorMessage: undefined,
|
|
56
|
+
};
|
|
57
|
+
case 'run_completed':
|
|
58
|
+
return {
|
|
59
|
+
...baseState,
|
|
60
|
+
phase: 'completed',
|
|
61
|
+
};
|
|
62
|
+
case 'server_state': {
|
|
63
|
+
const taskStatusMap = event.taskStatus ?? {};
|
|
64
|
+
const updatedTasks = baseState.tasks.length > 0
|
|
65
|
+
? baseState.tasks.map(task => ({
|
|
66
|
+
...task,
|
|
67
|
+
status: taskStatusMap[task.id] ?? task.status,
|
|
68
|
+
}))
|
|
69
|
+
: Object.keys(taskStatusMap).map(taskId => ({
|
|
70
|
+
id: taskId,
|
|
71
|
+
description: `Task ${taskId}`,
|
|
72
|
+
status: taskStatusMap[taskId],
|
|
73
|
+
}));
|
|
74
|
+
return {
|
|
75
|
+
...baseState,
|
|
76
|
+
thinking: event.thinking,
|
|
77
|
+
tasks: updatedTasks,
|
|
78
|
+
actions: event.actions ?? baseState.actions,
|
|
79
|
+
qaosProgress: event.qaosProgress ?? baseState.qaosProgress,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
case 'error':
|
|
83
|
+
return {
|
|
84
|
+
...baseState,
|
|
85
|
+
phase: 'error',
|
|
86
|
+
errorMessage: event.message,
|
|
87
|
+
};
|
|
88
|
+
default:
|
|
89
|
+
return baseState;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
if (command === 'run') {
|
|
94
|
+
setRunUiState(createInitialRunUiState());
|
|
95
|
+
run({
|
|
96
|
+
configFilePath: flags.config || './qaos-config.json',
|
|
97
|
+
headed: flags.headed,
|
|
98
|
+
onUiEvent: event => {
|
|
99
|
+
setRunUiState(prevState => reduceRunUiState(prevState, event));
|
|
100
|
+
},
|
|
101
|
+
}, t)
|
|
102
|
+
.then(res => {
|
|
103
|
+
setRunResult(res);
|
|
104
|
+
})
|
|
105
|
+
.catch(err => {
|
|
106
|
+
setRunResult({ error: err.message });
|
|
107
|
+
setRunUiState(prevState => reduceRunUiState(prevState, {
|
|
108
|
+
type: 'error',
|
|
109
|
+
message: err.message,
|
|
110
|
+
}));
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
if (command === 'workflow') {
|
|
114
|
+
setLoading(true);
|
|
115
|
+
const actions = getActions();
|
|
116
|
+
runWorkflow(actions, t)
|
|
117
|
+
.then(res => {
|
|
118
|
+
setRunResult(res);
|
|
119
|
+
setLoading(false);
|
|
120
|
+
})
|
|
121
|
+
.catch(err => {
|
|
122
|
+
setRunResult({ error: err.message });
|
|
123
|
+
setLoading(false);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
if (command === 'auth') {
|
|
127
|
+
setAuthStatus('validating');
|
|
128
|
+
setAuthMessage(t('run.processingAuth'));
|
|
129
|
+
(async () => {
|
|
130
|
+
try {
|
|
131
|
+
// Handle logout
|
|
132
|
+
if (flags.logout) {
|
|
133
|
+
const result = await logout(t);
|
|
134
|
+
setAuthMessage(result.message);
|
|
135
|
+
setAuthStatus('success');
|
|
136
|
+
setTimeout(() => process.exit(0), EXIT_DELAY_SUCCESS);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
// Handle API token authentication
|
|
140
|
+
if (flags.api) {
|
|
141
|
+
const result = await authenticate({
|
|
142
|
+
method: 'api',
|
|
143
|
+
token: flags.api,
|
|
144
|
+
}, t);
|
|
145
|
+
setAuthMessage(result.message);
|
|
146
|
+
if (result.success) {
|
|
147
|
+
setAuthStatus('success');
|
|
148
|
+
setTimeout(() => process.exit(0), EXIT_DELAY_SUCCESS);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
setAuthStatus('error');
|
|
152
|
+
setAuthError(result.error || t('auth.unknownError'));
|
|
153
|
+
setTimeout(() => process.exit(1), EXIT_DELAY_ERROR);
|
|
154
|
+
}
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
// Handle UI authentication (default when no flags provided and not authenticated)
|
|
158
|
+
const status = await getAuthStatus();
|
|
159
|
+
if (!status.authenticated) {
|
|
160
|
+
// User is not authenticated, initiate UI auth flow
|
|
161
|
+
setAuthStatus('validating');
|
|
162
|
+
setAuthMessage(t('run.openingBrowser'));
|
|
163
|
+
const result = await authenticate({
|
|
164
|
+
method: 'ui',
|
|
165
|
+
onProgress: (authStatus) => {
|
|
166
|
+
if (authStatus === 'opening_browser') {
|
|
167
|
+
setAuthMessage(t('run.openingBrowserShort'));
|
|
168
|
+
}
|
|
169
|
+
else if (authStatus === 'waiting_auth') {
|
|
170
|
+
setAuthMessage(t('run.waitingAuth'));
|
|
171
|
+
setAuthStatus('validating');
|
|
172
|
+
}
|
|
173
|
+
else if (authStatus === 'success') {
|
|
174
|
+
setAuthMessage(t('run.authComplete'));
|
|
175
|
+
setAuthStatus('success');
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
}, t);
|
|
179
|
+
setAuthMessage(result.message);
|
|
180
|
+
if (result.success) {
|
|
181
|
+
setAuthStatus('success');
|
|
182
|
+
setTimeout(() => process.exit(0), EXIT_DELAY_SUCCESS);
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
setAuthStatus('error');
|
|
186
|
+
setAuthError(result.error || t('auth.unknownError'));
|
|
187
|
+
setTimeout(() => process.exit(1), EXIT_DELAY_ERROR);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
// User is already authenticated, show status
|
|
192
|
+
setAuthMessage(t('auth.alreadyLoggedIn'));
|
|
193
|
+
setAuthStatus('success');
|
|
194
|
+
setTimeout(() => process.exit(0), EXIT_DELAY_INFO);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
catch (err) {
|
|
198
|
+
setAuthStatus('error');
|
|
199
|
+
setAuthMessage('Authentication failed');
|
|
200
|
+
setAuthError(err instanceof Error ? err.message : t('auth.unknownError'));
|
|
201
|
+
setTimeout(() => process.exit(1), EXIT_DELAY_ERROR);
|
|
202
|
+
}
|
|
203
|
+
})();
|
|
204
|
+
}
|
|
205
|
+
}, [command]);
|
|
206
|
+
switch (command) {
|
|
207
|
+
case undefined:
|
|
208
|
+
return React.createElement(Splash, null);
|
|
209
|
+
case 'run':
|
|
210
|
+
if (!runUiState)
|
|
211
|
+
return React.createElement(Text, null, t('run.preparing'));
|
|
212
|
+
if (runUiState.phase === 'error') {
|
|
213
|
+
return (React.createElement(RunError, { message: runUiState.errorMessage || t('auth.unknownError') }));
|
|
214
|
+
}
|
|
215
|
+
if (runUiState.phase === 'completed') {
|
|
216
|
+
// TODO : Antoine rendre ca mieux
|
|
217
|
+
setTimeout(() => {
|
|
218
|
+
process.exit(0);
|
|
219
|
+
}, EXIT_DELAY_SUCCESS);
|
|
220
|
+
return (React.createElement(React.Fragment, null,
|
|
221
|
+
React.createElement(Text, { color: "green", bold: true }, t('workflow.allTasksComplete')),
|
|
222
|
+
runUiState.reportUrl && (React.createElement(Text, null,
|
|
223
|
+
t('run.reportReady'),
|
|
224
|
+
' ',
|
|
225
|
+
React.createElement(Text, { color: "cyan", bold: true }, `\x1b]8;;${runUiState.reportUrl}\x07${runUiState.reportUrl}\x1b]8;;\x07`))),
|
|
226
|
+
React.createElement(Text, { dimColor: true }, t('workflow.exiting'))));
|
|
227
|
+
}
|
|
228
|
+
if (runUiState.phase !== 'running') {
|
|
229
|
+
return React.createElement(RunConnecting, { uiState: runUiState });
|
|
230
|
+
}
|
|
231
|
+
return React.createElement(RunUi, { uiState: runUiState });
|
|
232
|
+
case 'workflow':
|
|
233
|
+
if (loading)
|
|
234
|
+
return React.createElement(Text, null, t('run.executing'));
|
|
235
|
+
if (!runResult)
|
|
236
|
+
return React.createElement(Text, null, t('run.noResult'));
|
|
237
|
+
if (runResult.error) {
|
|
238
|
+
return React.createElement(Text, { color: "red" },
|
|
239
|
+
"Error: ",
|
|
240
|
+
runResult.error);
|
|
241
|
+
}
|
|
242
|
+
// Workflow outputs to console, just show completion message
|
|
243
|
+
return (React.createElement(React.Fragment, null,
|
|
244
|
+
React.createElement(Text, { color: "green", bold: true }, t('run.complete')),
|
|
245
|
+
React.createElement(Text, null, t('run.checkConsole'))));
|
|
246
|
+
case 'hist':
|
|
247
|
+
return (React.createElement(HistUI, { limit: flags.limit, projectId: flags.project }));
|
|
248
|
+
case 'auth':
|
|
249
|
+
return (React.createElement(AuthUI, { status: authStatus, message: authMessage, errorMessage: authError }));
|
|
250
|
+
case 'settings':
|
|
251
|
+
return React.createElement(SettingsUI, { onExit: () => process.exit(0) });
|
|
252
|
+
default:
|
|
253
|
+
return React.createElement(Text, { color: "red" }, t('errors.unknownCommand', { command }));
|
|
254
|
+
}
|
|
7
255
|
}
|
package/dist/cli.js
CHANGED
|
@@ -1,24 +1,97 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
2
|
+
const originalEmitWarning = process.emitWarning.bind(process);
|
|
3
|
+
process.emitWarning = ((warning, ...args) => {
|
|
4
|
+
const warningMessage = typeof warning === 'string' ? warning : warning.message;
|
|
5
|
+
const warningTypeOutput = typeof args[0] === 'string' ? args[0] : undefined;
|
|
6
|
+
const warningType = typeof warning === 'string' ? warningTypeOutput : warning.name;
|
|
7
|
+
if (warningType === 'ExperimentalWarning' &&
|
|
8
|
+
warningMessage.includes('Importing JSON modules is an experimental feature')) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
return originalEmitWarning(warning, ...args);
|
|
12
|
+
});
|
|
13
|
+
const [{ default: React }, { render, Text }, { default: meow }, { default: App }, { getHelpInstructions }, { validCommands }, { LanguageProvider }, { authService }, { createTranslator },] = await Promise.all([
|
|
14
|
+
import('react'),
|
|
15
|
+
import('ink'),
|
|
16
|
+
import('meow'),
|
|
17
|
+
import('./app.js'),
|
|
18
|
+
import('./constants/help-instructions.js'),
|
|
19
|
+
import('./constants/commands.js'),
|
|
20
|
+
import('./contexts/language-context.js'),
|
|
21
|
+
import('./services/auth-service.js'),
|
|
22
|
+
import('./i18n/language-service.js'),
|
|
23
|
+
]);
|
|
24
|
+
// Load language preference synchronously for CLI initialization
|
|
25
|
+
const userLanguage = authService.getLanguageSync();
|
|
26
|
+
const t = createTranslator(userLanguage);
|
|
27
|
+
const cli = meow(getHelpInstructions(t), {
|
|
17
28
|
importMeta: import.meta,
|
|
18
29
|
flags: {
|
|
19
|
-
|
|
30
|
+
config: {
|
|
31
|
+
type: 'string',
|
|
32
|
+
alias: 'c',
|
|
33
|
+
description: 'Path to config file (default: ./qaos-config.json)',
|
|
34
|
+
},
|
|
35
|
+
headed: {
|
|
36
|
+
type: 'boolean',
|
|
37
|
+
alias: 'h',
|
|
38
|
+
description: 'Run browser in headed mode (default: headless)',
|
|
39
|
+
default: false,
|
|
40
|
+
},
|
|
41
|
+
api: {
|
|
42
|
+
type: 'string',
|
|
43
|
+
alias: 'a',
|
|
44
|
+
description: 'Authenticate with API token',
|
|
45
|
+
},
|
|
46
|
+
ui: {
|
|
47
|
+
type: 'boolean',
|
|
48
|
+
alias: 'u',
|
|
49
|
+
description: 'Authenticate with browser UI (OAuth)',
|
|
50
|
+
default: false,
|
|
51
|
+
},
|
|
52
|
+
logout: {
|
|
53
|
+
type: 'boolean',
|
|
54
|
+
description: 'Logout and remove credentials',
|
|
55
|
+
default: false,
|
|
56
|
+
},
|
|
57
|
+
settings: {
|
|
58
|
+
type: 'boolean',
|
|
59
|
+
alias: 's',
|
|
60
|
+
description: 'Open settings interface',
|
|
61
|
+
default: false,
|
|
62
|
+
},
|
|
63
|
+
limit: {
|
|
64
|
+
type: 'number',
|
|
65
|
+
alias: 'l',
|
|
66
|
+
description: 'Number of recent runs to display (default: 10, max: 50)',
|
|
67
|
+
},
|
|
68
|
+
project: {
|
|
20
69
|
type: 'string',
|
|
70
|
+
alias: 'p',
|
|
71
|
+
description: 'Filter runs by project ID',
|
|
21
72
|
},
|
|
22
73
|
},
|
|
23
74
|
});
|
|
24
|
-
|
|
75
|
+
const command = cli.input[0];
|
|
76
|
+
if (cli.input.length > 1 && !validCommands.includes(command)) {
|
|
77
|
+
render(React.createElement(LanguageProvider, null,
|
|
78
|
+
React.createElement(Text, { color: "red" },
|
|
79
|
+
"Error: Unknown command '$",
|
|
80
|
+
command,
|
|
81
|
+
"'. Use 'qaos --help' for details.")));
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
render(React.createElement(LanguageProvider, null,
|
|
85
|
+
React.createElement(App, { args: {
|
|
86
|
+
input: command,
|
|
87
|
+
flags: {
|
|
88
|
+
...cli.flags,
|
|
89
|
+
api: cli.flags['api'],
|
|
90
|
+
ui: cli.flags['ui'],
|
|
91
|
+
logout: cli.flags['logout'],
|
|
92
|
+
settings: cli.flags['settings'],
|
|
93
|
+
limit: cli.flags['limit'],
|
|
94
|
+
project: cli.flags['project'],
|
|
95
|
+
},
|
|
96
|
+
} })));
|
|
97
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
type TranslationFunction = (key: string, params?: Record<string, string | number>) => string;
|
|
2
|
+
export interface AuthArgs {
|
|
3
|
+
method?: 'api' | 'ui';
|
|
4
|
+
token?: string;
|
|
5
|
+
onProgress?: (status: 'opening_browser' | 'waiting_auth' | 'success') => void;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Handle CLI authentication via browser UI
|
|
9
|
+
* Opens a browser to the auth page and captures the token from a local server
|
|
10
|
+
*/
|
|
11
|
+
export declare function authenticateViaUI(websiteUrl: string, t: TranslationFunction, onProgress?: (status: 'opening_browser' | 'waiting_auth' | 'success') => void): Promise<{
|
|
12
|
+
success: boolean;
|
|
13
|
+
message: string;
|
|
14
|
+
token?: string;
|
|
15
|
+
error?: string;
|
|
16
|
+
}>;
|
|
17
|
+
/**
|
|
18
|
+
* Handle CLI authentication
|
|
19
|
+
* Supports both API token and UI-based authentication
|
|
20
|
+
*/
|
|
21
|
+
export declare function authenticate(args: AuthArgs, t: TranslationFunction): Promise<{
|
|
22
|
+
success: boolean;
|
|
23
|
+
message: string;
|
|
24
|
+
error?: string;
|
|
25
|
+
token?: string;
|
|
26
|
+
}>;
|
|
27
|
+
/**
|
|
28
|
+
* Get current authentication status
|
|
29
|
+
*/
|
|
30
|
+
export declare function getAuthStatus(): Promise<{
|
|
31
|
+
authenticated: boolean;
|
|
32
|
+
serverUrl?: string;
|
|
33
|
+
}>;
|
|
34
|
+
/**
|
|
35
|
+
* Clear authentication (logout)
|
|
36
|
+
*/
|
|
37
|
+
export declare function logout(t: TranslationFunction): Promise<{
|
|
38
|
+
success: boolean;
|
|
39
|
+
message: string;
|
|
40
|
+
}>;
|
|
41
|
+
export {};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { authService } from '../services/auth-service.js';
|
|
2
|
+
import { startAuthServer } from '../services/auth-server.js';
|
|
3
|
+
import open from 'open';
|
|
4
|
+
import { randomBytes } from 'crypto';
|
|
5
|
+
import { configPromise } from '../config.js';
|
|
6
|
+
/**
|
|
7
|
+
* Handle CLI authentication via browser UI
|
|
8
|
+
* Opens a browser to the auth page and captures the token from a local server
|
|
9
|
+
*/
|
|
10
|
+
export async function authenticateViaUI(websiteUrl, t, onProgress) {
|
|
11
|
+
const callbackPort = 9999;
|
|
12
|
+
try {
|
|
13
|
+
// Start local server to receive auth callback
|
|
14
|
+
onProgress?.('opening_browser');
|
|
15
|
+
const allowedOrigin = new URL(websiteUrl).origin;
|
|
16
|
+
const callbackState = randomBytes(16).toString('hex');
|
|
17
|
+
const authServer = await startAuthServer(callbackPort, {
|
|
18
|
+
allowedOrigin,
|
|
19
|
+
callbackState,
|
|
20
|
+
});
|
|
21
|
+
const callbackUrl = authServer.getCallbackUrl();
|
|
22
|
+
// Build auth URL with callback
|
|
23
|
+
const authUrl = new URL('/cli-auth', websiteUrl);
|
|
24
|
+
authUrl.searchParams.set('callback_url', callbackUrl);
|
|
25
|
+
// Open browser
|
|
26
|
+
await open(authUrl.toString());
|
|
27
|
+
onProgress?.('waiting_auth');
|
|
28
|
+
// Wait for token from browser
|
|
29
|
+
const tokenData = await Promise.race([
|
|
30
|
+
authServer.waitForToken(),
|
|
31
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error(t('auth.timeout'))), 5 * 60 * 1000)),
|
|
32
|
+
]);
|
|
33
|
+
await authServer.close();
|
|
34
|
+
// Validate and save token
|
|
35
|
+
const validationResult = await authService.validateToken(tokenData.token, websiteUrl);
|
|
36
|
+
if (!validationResult.valid) {
|
|
37
|
+
return {
|
|
38
|
+
success: false,
|
|
39
|
+
message: t('auth.tokenValidationFailed'),
|
|
40
|
+
error: validationResult.error || t('auth.invalidToken'),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
await authService.saveToken(tokenData.token, websiteUrl, validationResult.userId);
|
|
44
|
+
onProgress?.('success');
|
|
45
|
+
return {
|
|
46
|
+
success: true,
|
|
47
|
+
message: t('auth.successWithPath', {
|
|
48
|
+
path: authService.getAuthFilePath(),
|
|
49
|
+
}),
|
|
50
|
+
token: tokenData.token,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
return {
|
|
55
|
+
success: false,
|
|
56
|
+
message: 'Authentication failed',
|
|
57
|
+
error: error instanceof Error ? error.message : t('auth.unknownError'),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Handle CLI authentication
|
|
63
|
+
* Supports both API token and UI-based authentication
|
|
64
|
+
*/
|
|
65
|
+
export async function authenticate(args, t) {
|
|
66
|
+
try {
|
|
67
|
+
const method = args.method || 'ui ';
|
|
68
|
+
const config = await configPromise;
|
|
69
|
+
const serverUrl = config.API_SERVER_URL;
|
|
70
|
+
const websiteUrl = config.WEBSITE_URL;
|
|
71
|
+
if (method === 'api') {
|
|
72
|
+
if (!args.token) {
|
|
73
|
+
return {
|
|
74
|
+
success: false,
|
|
75
|
+
message: t('auth.noTokenProvided'),
|
|
76
|
+
error: t('auth.usage'),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
const validationResult = await authService.validateToken(args.token, serverUrl);
|
|
80
|
+
if (!validationResult.valid) {
|
|
81
|
+
return {
|
|
82
|
+
success: false,
|
|
83
|
+
message: t('auth.tokenValidationFailed'),
|
|
84
|
+
error: validationResult.error || t('auth.invalidToken'),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
await authService.saveToken(args.token, serverUrl, validationResult.userId);
|
|
88
|
+
return {
|
|
89
|
+
success: true,
|
|
90
|
+
message: t('auth.successWithPath', {
|
|
91
|
+
path: authService.getAuthFilePath(),
|
|
92
|
+
}),
|
|
93
|
+
token: args.token,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
// UI method - open browser for authentication
|
|
98
|
+
return await authenticateViaUI(websiteUrl, t, args.onProgress);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
return {
|
|
103
|
+
success: false,
|
|
104
|
+
message: 'Authentication failed',
|
|
105
|
+
error: error instanceof Error ? error.message : t('auth.unknownError'),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Get current authentication status
|
|
111
|
+
*/
|
|
112
|
+
export async function getAuthStatus() {
|
|
113
|
+
const isAuthenticated = await authService.isAuthenticated();
|
|
114
|
+
const serverUrl = await authService.getServerUrl();
|
|
115
|
+
return {
|
|
116
|
+
authenticated: isAuthenticated,
|
|
117
|
+
serverUrl: serverUrl || undefined,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Clear authentication (logout)
|
|
122
|
+
*/
|
|
123
|
+
export async function logout(t) {
|
|
124
|
+
try {
|
|
125
|
+
await authService.clearAuth();
|
|
126
|
+
return {
|
|
127
|
+
success: true,
|
|
128
|
+
message: t('auth.logoutSuccess'),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
return {
|
|
133
|
+
success: false,
|
|
134
|
+
message: `Failed to logout: ${error instanceof Error ? error.message : t('auth.unknownError')}`,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface RunSummary {
|
|
2
|
+
id: string;
|
|
3
|
+
project_id: string;
|
|
4
|
+
title: string;
|
|
5
|
+
site_url: string;
|
|
6
|
+
status: string;
|
|
7
|
+
score: number | null;
|
|
8
|
+
problem_count: number;
|
|
9
|
+
test_count: number;
|
|
10
|
+
length_ms: number;
|
|
11
|
+
created_at: string;
|
|
12
|
+
}
|
|
13
|
+
export interface FetchRunsResult {
|
|
14
|
+
success: boolean;
|
|
15
|
+
runs?: RunSummary[];
|
|
16
|
+
error?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function fetchRuns(options: {
|
|
19
|
+
limit?: number;
|
|
20
|
+
projectId?: string;
|
|
21
|
+
}): Promise<FetchRunsResult>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { authService } from '../services/auth-service.js';
|
|
2
|
+
import { configPromise } from '../config.js';
|
|
3
|
+
export async function fetchRuns(options) {
|
|
4
|
+
const token = await authService.getAccessToken();
|
|
5
|
+
if (!token) {
|
|
6
|
+
return { success: false, error: 'not_authenticated' };
|
|
7
|
+
}
|
|
8
|
+
const defaultServerUrl = (await configPromise).API_SERVER_URL;
|
|
9
|
+
const serverUrl = (await authService.getServerUrl()) || defaultServerUrl;
|
|
10
|
+
const url = new URL('/api/cli/runs', serverUrl);
|
|
11
|
+
if (options.limit) {
|
|
12
|
+
url.searchParams.set('limit', String(options.limit));
|
|
13
|
+
}
|
|
14
|
+
if (options.projectId) {
|
|
15
|
+
url.searchParams.set('projectId', options.projectId);
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
const response = await fetch(url.toString(), {
|
|
19
|
+
headers: {
|
|
20
|
+
Authorization: `Bearer ${token}`,
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
const data = (await response.json());
|
|
24
|
+
if (!response.ok) {
|
|
25
|
+
if (response.status === 404) {
|
|
26
|
+
return {
|
|
27
|
+
success: false,
|
|
28
|
+
error: `project_not_found:${options.projectId}`,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
return { success: false, error: data.error || `HTTP ${response.status}` };
|
|
32
|
+
}
|
|
33
|
+
return { success: true, runs: data.runs || [] };
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
return {
|
|
37
|
+
success: false,
|
|
38
|
+
error: `network_error:${error instanceof Error ? error.message : String(error)}`,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export type AuthStatus = 'idle' | 'validating' | 'success' | 'error';
|
|
3
|
+
export interface AuthUIProps {
|
|
4
|
+
status: AuthStatus;
|
|
5
|
+
message: string;
|
|
6
|
+
errorMessage?: string;
|
|
7
|
+
}
|
|
8
|
+
export default function AuthUI({ status, message, errorMessage }: AuthUIProps): React.JSX.Element;
|