hedgequantx 2.6.161 → 2.6.163
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/package.json +1 -1
- package/src/menus/ai-agent-connect.js +181 -0
- package/src/menus/ai-agent-models.js +219 -0
- package/src/menus/ai-agent-oauth.js +292 -0
- package/src/menus/ai-agent-ui.js +141 -0
- package/src/menus/ai-agent.js +88 -1489
- package/src/pages/algo/copy-engine.js +449 -0
- package/src/pages/algo/copy-trading.js +11 -543
- package/src/pages/algo/smart-logs-data.js +218 -0
- package/src/pages/algo/smart-logs.js +9 -214
- package/src/pages/algo/ui-constants.js +144 -0
- package/src/pages/algo/ui-summary.js +184 -0
- package/src/pages/algo/ui.js +42 -526
- package/src/pages/stats-calculations.js +191 -0
- package/src/pages/stats-ui.js +381 -0
- package/src/pages/stats.js +14 -507
- package/src/services/ai/client-analysis.js +194 -0
- package/src/services/ai/client-models.js +333 -0
- package/src/services/ai/client.js +6 -489
- package/src/services/ai/index.js +2 -257
- package/src/services/ai/providers/direct-providers.js +323 -0
- package/src/services/ai/providers/index.js +8 -472
- package/src/services/ai/providers/other-providers.js +104 -0
- package/src/services/ai/proxy-install.js +249 -0
- package/src/services/ai/proxy-manager.js +29 -411
- package/src/services/ai/proxy-remote.js +161 -0
- package/src/services/ai/supervisor-optimize.js +215 -0
- package/src/services/ai/supervisor-sync.js +178 -0
- package/src/services/ai/supervisor.js +50 -515
- package/src/services/ai/validation.js +250 -0
- package/src/services/hqx-server-events.js +110 -0
- package/src/services/hqx-server-handlers.js +217 -0
- package/src/services/hqx-server-latency.js +136 -0
- package/src/services/hqx-server.js +51 -403
- package/src/services/position-constants.js +28 -0
- package/src/services/position-exit-logic.js +174 -0
- package/src/services/position-manager.js +90 -629
- package/src/services/position-momentum.js +206 -0
- package/src/services/projectx/accounts.js +142 -0
- package/src/services/projectx/index.js +40 -289
- package/src/services/projectx/trading.js +180 -0
- package/src/services/rithmic/contracts.js +218 -0
- package/src/services/rithmic/handlers.js +2 -208
- package/src/services/rithmic/index.js +28 -712
- package/src/services/rithmic/latency-tracker.js +182 -0
- package/src/services/rithmic/market-data-decoders.js +229 -0
- package/src/services/rithmic/market-data.js +1 -278
- package/src/services/rithmic/orders-fast.js +246 -0
- package/src/services/rithmic/orders.js +1 -251
- package/src/services/rithmic/proto-decoders.js +403 -0
- package/src/services/rithmic/protobuf.js +7 -443
- package/src/services/rithmic/specs.js +146 -0
- package/src/services/rithmic/trade-history.js +254 -0
- package/src/services/strategy/hft-signal-calc.js +147 -0
- package/src/services/strategy/hft-tick.js +33 -133
- package/src/services/tradovate/index.js +6 -119
- package/src/services/tradovate/orders.js +145 -0
package/src/menus/ai-agent.js
CHANGED
|
@@ -1,38 +1,17 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AI Agent Menu
|
|
3
|
-
* Configure multiple AI provider connections
|
|
4
|
-
*/
|
|
5
|
-
|
|
1
|
+
/** AI Agent Menu - Configure multiple AI provider connections */
|
|
6
2
|
const chalk = require('chalk');
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
const { getLogoWidth, drawBoxHeader, drawBoxHeaderContinue, drawBoxFooter, displayBanner } = require('../ui');
|
|
3
|
+
const { getLogoWidth, drawBoxHeaderContinue, drawBoxFooter, displayBanner } = require('../ui');
|
|
10
4
|
const { prompts } = require('../utils');
|
|
11
5
|
const aiService = require('../services/ai');
|
|
12
6
|
const { getCategories, getProvidersByCategory } = require('../services/ai/providers');
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
const oauthIflow = require('../services/ai/oauth-iflow');
|
|
18
|
-
const proxyManager = require('../services/ai/proxy-manager');
|
|
7
|
+
const { makeLine, make2ColRow, menuRow2, menuItem, getProviderColor, getBoxDimensions } = require('./ai-agent-ui');
|
|
8
|
+
const { setupOAuthConnection, getOAuthConfig } = require('./ai-agent-oauth');
|
|
9
|
+
const { selectModelFromList, selectModel } = require('./ai-agent-models');
|
|
10
|
+
const { collectCredentials, validateAndFetchModels, addConnectedAgent } = require('./ai-agent-connect');
|
|
19
11
|
|
|
20
|
-
/**
|
|
21
|
-
* Main AI Agent menu
|
|
22
|
-
*/
|
|
12
|
+
/** Main AI Agent menu */
|
|
23
13
|
const aiAgentMenu = async () => {
|
|
24
|
-
const boxWidth =
|
|
25
|
-
const W = boxWidth - 2;
|
|
26
|
-
|
|
27
|
-
const makeLine = (content, align = 'left') => {
|
|
28
|
-
const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
29
|
-
const padding = W - plainLen;
|
|
30
|
-
if (align === 'center') {
|
|
31
|
-
const leftPad = Math.floor(padding / 2);
|
|
32
|
-
return chalk.cyan('║') + ' '.repeat(leftPad) + content + ' '.repeat(padding - leftPad) + chalk.cyan('║');
|
|
33
|
-
}
|
|
34
|
-
return chalk.cyan('║') + ' ' + content + ' '.repeat(Math.max(0, padding - 1)) + chalk.cyan('║');
|
|
35
|
-
};
|
|
14
|
+
const { boxWidth, W } = getBoxDimensions();
|
|
36
15
|
|
|
37
16
|
console.clear();
|
|
38
17
|
displayBanner();
|
|
@@ -43,32 +22,27 @@ const aiAgentMenu = async () => {
|
|
|
43
22
|
const agentCount = agents.length;
|
|
44
23
|
|
|
45
24
|
if (agentCount === 0) {
|
|
46
|
-
console.log(makeLine(chalk.white('STATUS: NO AGENTS CONNECTED')
|
|
25
|
+
console.log(makeLine(W, chalk.white('STATUS: NO AGENTS CONNECTED')));
|
|
47
26
|
} else {
|
|
48
|
-
console.log(makeLine(chalk.green(`STATUS: ${agentCount} AGENT${agentCount > 1 ? 'S' : ''} CONNECTED`)
|
|
27
|
+
console.log(makeLine(W, chalk.green(`STATUS: ${agentCount} AGENT${agentCount > 1 ? 'S' : ''} CONNECTED`)));
|
|
49
28
|
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
50
29
|
|
|
51
30
|
// List all agents
|
|
52
31
|
for (let i = 0; i < agents.length; i++) {
|
|
53
32
|
const agent = agents[i];
|
|
54
|
-
// Show ACTIVE marker (if single agent, it's always active)
|
|
55
33
|
const isActive = agent.isActive || agents.length === 1;
|
|
56
34
|
const activeMarker = isActive ? ' [ACTIVE]' : '';
|
|
57
|
-
const providerColor = agent.providerId
|
|
58
|
-
agent.providerId === 'openai' ? chalk.green :
|
|
59
|
-
agent.providerId === 'openrouter' ? chalk.yellow : chalk.cyan;
|
|
35
|
+
const providerColor = getProviderColor(agent.providerId);
|
|
60
36
|
|
|
61
|
-
// Calculate max lengths to fit in box
|
|
62
37
|
const prefix = `[${i + 1}] `;
|
|
63
38
|
const suffix = ` - ${agent.model || 'N/A'}`;
|
|
64
39
|
const maxNameLen = W - prefix.length - activeMarker.length - suffix.length - 2;
|
|
65
40
|
|
|
66
|
-
// Truncate agent name if too long
|
|
67
41
|
const displayName = agent.name.length > maxNameLen
|
|
68
42
|
? agent.name.substring(0, maxNameLen - 3) + '...'
|
|
69
43
|
: agent.name;
|
|
70
44
|
|
|
71
|
-
console.log(makeLine(
|
|
45
|
+
console.log(makeLine(W,
|
|
72
46
|
chalk.white(prefix) +
|
|
73
47
|
providerColor(displayName) +
|
|
74
48
|
chalk.green(activeMarker) +
|
|
@@ -79,44 +53,18 @@ const aiAgentMenu = async () => {
|
|
|
79
53
|
|
|
80
54
|
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
81
55
|
|
|
82
|
-
// Menu in 2 columns
|
|
83
|
-
const colWidth = Math.floor(W / 2);
|
|
84
|
-
|
|
85
|
-
const menuRow2 = (col1, col2 = '') => {
|
|
86
|
-
const c1Plain = col1.replace(/\x1b\[[0-9;]*m/g, '');
|
|
87
|
-
const c2Plain = col2.replace(/\x1b\[[0-9;]*m/g, '');
|
|
88
|
-
|
|
89
|
-
const pad1Left = Math.floor((colWidth - c1Plain.length) / 2);
|
|
90
|
-
const pad1Right = colWidth - c1Plain.length - pad1Left;
|
|
91
|
-
|
|
92
|
-
const col2Width = W - colWidth;
|
|
93
|
-
const pad2Left = Math.floor((col2Width - c2Plain.length) / 2);
|
|
94
|
-
const pad2Right = col2Width - c2Plain.length - pad2Left;
|
|
95
|
-
|
|
96
|
-
const line =
|
|
97
|
-
' '.repeat(pad1Left) + col1 + ' '.repeat(pad1Right) +
|
|
98
|
-
' '.repeat(pad2Left) + col2 + ' '.repeat(pad2Right);
|
|
99
|
-
|
|
100
|
-
console.log(chalk.cyan('║') + line + chalk.cyan('║'));
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
const menuItem = (key, label, color) => {
|
|
104
|
-
const text = `[${key}] ${label.padEnd(14)}`;
|
|
105
|
-
return color(text);
|
|
106
|
-
};
|
|
107
|
-
|
|
108
56
|
// Menu options in 2 columns
|
|
109
57
|
if (agentCount > 0) {
|
|
110
58
|
if (agentCount > 1) {
|
|
111
|
-
menuRow2(menuItem('+', 'ADD AGENT', chalk.green), menuItem('S', 'SET ACTIVE', chalk.cyan));
|
|
112
|
-
menuRow2(menuItem('M', 'CHANGE MODEL', chalk.yellow), menuItem('R', 'REMOVE AGENT', chalk.red));
|
|
113
|
-
menuRow2(menuItem('X', 'REMOVE ALL', chalk.red), menuItem('<', 'BACK', chalk.white));
|
|
59
|
+
console.log(menuRow2(W, menuItem('+', 'ADD AGENT', chalk.green), menuItem('S', 'SET ACTIVE', chalk.cyan)));
|
|
60
|
+
console.log(menuRow2(W, menuItem('M', 'CHANGE MODEL', chalk.yellow), menuItem('R', 'REMOVE AGENT', chalk.red)));
|
|
61
|
+
console.log(menuRow2(W, menuItem('X', 'REMOVE ALL', chalk.red), menuItem('<', 'BACK', chalk.white)));
|
|
114
62
|
} else {
|
|
115
|
-
menuRow2(menuItem('+', 'ADD AGENT', chalk.green), menuItem('M', 'CHANGE MODEL', chalk.yellow));
|
|
116
|
-
menuRow2(menuItem('R', 'REMOVE AGENT', chalk.red), menuItem('<', 'BACK', chalk.white));
|
|
63
|
+
console.log(menuRow2(W, menuItem('+', 'ADD AGENT', chalk.green), menuItem('M', 'CHANGE MODEL', chalk.yellow)));
|
|
64
|
+
console.log(menuRow2(W, menuItem('R', 'REMOVE AGENT', chalk.red), menuItem('<', 'BACK', chalk.white)));
|
|
117
65
|
}
|
|
118
66
|
} else {
|
|
119
|
-
menuRow2(menuItem('+', 'ADD AGENT', chalk.green), menuItem('<', 'BACK', chalk.white));
|
|
67
|
+
console.log(menuRow2(W, menuItem('+', 'ADD AGENT', chalk.green), menuItem('<', 'BACK', chalk.white)));
|
|
120
68
|
}
|
|
121
69
|
|
|
122
70
|
drawBoxFooter(boxWidth);
|
|
@@ -134,19 +82,13 @@ const aiAgentMenu = async () => {
|
|
|
134
82
|
case '+':
|
|
135
83
|
return await selectCategory();
|
|
136
84
|
case 's':
|
|
137
|
-
if (agentCount > 1)
|
|
138
|
-
return await selectActiveAgent();
|
|
139
|
-
}
|
|
85
|
+
if (agentCount > 1) return await selectActiveAgent();
|
|
140
86
|
return await aiAgentMenu();
|
|
141
87
|
case 'm':
|
|
142
|
-
if (agentCount > 0)
|
|
143
|
-
return await selectAgentForModelChange();
|
|
144
|
-
}
|
|
88
|
+
if (agentCount > 0) return await selectAgentForModelChange();
|
|
145
89
|
return await aiAgentMenu();
|
|
146
90
|
case 'r':
|
|
147
|
-
if (agentCount > 0)
|
|
148
|
-
return await selectAgentToRemove();
|
|
149
|
-
}
|
|
91
|
+
if (agentCount > 0) return await selectAgentToRemove();
|
|
150
92
|
return await aiAgentMenu();
|
|
151
93
|
case 'x':
|
|
152
94
|
if (agentCount > 1) {
|
|
@@ -163,67 +105,34 @@ const aiAgentMenu = async () => {
|
|
|
163
105
|
}
|
|
164
106
|
};
|
|
165
107
|
|
|
166
|
-
/**
|
|
167
|
-
* Show agent details
|
|
168
|
-
*/
|
|
108
|
+
/** Show agent details */
|
|
169
109
|
const showAgentDetails = async (agent) => {
|
|
170
|
-
const boxWidth =
|
|
171
|
-
const W = boxWidth - 2;
|
|
172
|
-
|
|
173
|
-
const makeLine = (content) => {
|
|
174
|
-
const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
175
|
-
const padding = W - plainLen;
|
|
176
|
-
return chalk.cyan('║') + ' ' + content + ' '.repeat(Math.max(0, padding - 1)) + chalk.cyan('║');
|
|
177
|
-
};
|
|
110
|
+
const { boxWidth, W } = getBoxDimensions();
|
|
178
111
|
|
|
179
112
|
console.clear();
|
|
180
113
|
displayBanner();
|
|
181
114
|
drawBoxHeaderContinue('AGENT DETAILS', boxWidth);
|
|
182
115
|
|
|
183
|
-
const providerColor = agent.providerId
|
|
184
|
-
agent.providerId === 'openai' ? chalk.green :
|
|
185
|
-
agent.providerId === 'openrouter' ? chalk.yellow : chalk.cyan;
|
|
116
|
+
const providerColor = getProviderColor(agent.providerId);
|
|
186
117
|
|
|
187
|
-
console.log(makeLine(chalk.white('NAME: ') + providerColor(agent.name)));
|
|
188
|
-
console.log(makeLine(chalk.white('PROVIDER: ') + chalk.white(agent.provider?.name || agent.providerId)));
|
|
189
|
-
console.log(makeLine(chalk.white('MODEL: ') + chalk.white(agent.model || 'N/A')));
|
|
190
|
-
console.log(makeLine(chalk.white('STATUS: ') + (agent.isActive ? chalk.green('ACTIVE') : chalk.white('STANDBY'))));
|
|
118
|
+
console.log(makeLine(W, chalk.white('NAME: ') + providerColor(agent.name)));
|
|
119
|
+
console.log(makeLine(W, chalk.white('PROVIDER: ') + chalk.white(agent.provider?.name || agent.providerId)));
|
|
120
|
+
console.log(makeLine(W, chalk.white('MODEL: ') + chalk.white(agent.model || 'N/A')));
|
|
121
|
+
console.log(makeLine(W, chalk.white('STATUS: ') + (agent.isActive ? chalk.green('ACTIVE') : chalk.white('STANDBY'))));
|
|
191
122
|
|
|
192
123
|
console.log(chalk.cyan('╠' + '═'.repeat(W) + '╣'));
|
|
193
124
|
|
|
194
|
-
// Menu in 2 columns
|
|
195
|
-
const colWidth = Math.floor(W / 2);
|
|
196
|
-
|
|
197
|
-
const menuRow = (col1, col2 = '') => {
|
|
198
|
-
const c1Plain = col1.replace(/\x1b\[[0-9;]*m/g, '');
|
|
199
|
-
const c2Plain = col2.replace(/\x1b\[[0-9;]*m/g, '');
|
|
200
|
-
|
|
201
|
-
const pad1Left = Math.floor((colWidth - c1Plain.length) / 2);
|
|
202
|
-
const pad1Right = colWidth - c1Plain.length - pad1Left;
|
|
203
|
-
|
|
204
|
-
const col2Width = W - colWidth;
|
|
205
|
-
const pad2Left = Math.floor((col2Width - c2Plain.length) / 2);
|
|
206
|
-
const pad2Right = col2Width - c2Plain.length - pad2Left;
|
|
207
|
-
|
|
208
|
-
const line =
|
|
209
|
-
' '.repeat(pad1Left) + col1 + ' '.repeat(pad1Right) +
|
|
210
|
-
' '.repeat(pad2Left) + col2 + ' '.repeat(pad2Right);
|
|
211
|
-
|
|
212
|
-
console.log(chalk.cyan('║') + line + chalk.cyan('║'));
|
|
213
|
-
};
|
|
214
|
-
|
|
215
125
|
if (!agent.isActive) {
|
|
216
|
-
|
|
217
|
-
|
|
126
|
+
console.log(menuRow2(W, chalk.cyan('[A] SET AS ACTIVE'), chalk.yellow('[M] CHANGE MODEL')));
|
|
127
|
+
console.log(menuRow2(W, chalk.red('[R] REMOVE'), chalk.white('[<] BACK')));
|
|
218
128
|
} else {
|
|
219
|
-
|
|
220
|
-
|
|
129
|
+
console.log(menuRow2(W, chalk.yellow('[M] CHANGE MODEL'), chalk.red('[R] REMOVE')));
|
|
130
|
+
console.log(menuRow2(W, chalk.white('[<] BACK'), ''));
|
|
221
131
|
}
|
|
222
132
|
|
|
223
133
|
drawBoxFooter(boxWidth);
|
|
224
134
|
|
|
225
135
|
const choice = await prompts.textInput(chalk.cyan('SELECT:'));
|
|
226
|
-
|
|
227
136
|
const agentDisplayName = agent.model ? `${agent.name} (${agent.model})` : agent.name;
|
|
228
137
|
|
|
229
138
|
switch ((choice || '').toLowerCase()) {
|
|
@@ -235,7 +144,7 @@ const showAgentDetails = async (agent) => {
|
|
|
235
144
|
}
|
|
236
145
|
return await aiAgentMenu();
|
|
237
146
|
case 'm':
|
|
238
|
-
return await selectModel(agent);
|
|
147
|
+
return await selectModel(agent, aiAgentMenu);
|
|
239
148
|
case 'r':
|
|
240
149
|
aiService.removeAgent(agent.id);
|
|
241
150
|
console.log(chalk.yellow(`\n ${agentDisplayName} REMOVED`));
|
|
@@ -249,18 +158,9 @@ const showAgentDetails = async (agent) => {
|
|
|
249
158
|
}
|
|
250
159
|
};
|
|
251
160
|
|
|
252
|
-
/**
|
|
253
|
-
* Select active agent
|
|
254
|
-
*/
|
|
161
|
+
/** Select active agent */
|
|
255
162
|
const selectActiveAgent = async () => {
|
|
256
|
-
const boxWidth =
|
|
257
|
-
const W = boxWidth - 2;
|
|
258
|
-
|
|
259
|
-
const makeLine = (content) => {
|
|
260
|
-
const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
261
|
-
const padding = W - plainLen;
|
|
262
|
-
return chalk.cyan('║') + ' ' + content + ' '.repeat(Math.max(0, padding - 1)) + chalk.cyan('║');
|
|
263
|
-
};
|
|
163
|
+
const { boxWidth, W } = getBoxDimensions();
|
|
264
164
|
|
|
265
165
|
console.clear();
|
|
266
166
|
displayBanner();
|
|
@@ -271,23 +171,18 @@ const selectActiveAgent = async () => {
|
|
|
271
171
|
for (let i = 0; i < agents.length; i++) {
|
|
272
172
|
const agent = agents[i];
|
|
273
173
|
const activeMarker = agent.isActive ? chalk.yellow(' (CURRENT)') : '';
|
|
274
|
-
const providerColor = agent.providerId
|
|
275
|
-
agent.providerId === 'openai' ? chalk.green : chalk.cyan;
|
|
276
|
-
|
|
174
|
+
const providerColor = getProviderColor(agent.providerId);
|
|
277
175
|
const modelDisplay = agent.model ? chalk.gray(` (${agent.model})`) : '';
|
|
278
|
-
console.log(makeLine(
|
|
279
|
-
chalk.white(`[${i + 1}] `) + providerColor(agent.name) + modelDisplay + activeMarker
|
|
280
|
-
));
|
|
176
|
+
console.log(makeLine(W, chalk.white(`[${i + 1}] `) + providerColor(agent.name) + modelDisplay + activeMarker));
|
|
281
177
|
}
|
|
282
178
|
|
|
283
|
-
console.log(makeLine(''));
|
|
284
|
-
console.log(makeLine(chalk.white('[<] BACK')));
|
|
179
|
+
console.log(makeLine(W, ''));
|
|
180
|
+
console.log(makeLine(W, chalk.white('[<] BACK')));
|
|
285
181
|
|
|
286
182
|
drawBoxFooter(boxWidth);
|
|
287
183
|
|
|
288
184
|
const choice = await prompts.textInput(chalk.cyan('SELECT AGENT:'));
|
|
289
185
|
|
|
290
|
-
// Empty input or < = go back
|
|
291
186
|
if (!choice || choice.trim() === '' || choice === '<' || choice?.toLowerCase() === 'b') {
|
|
292
187
|
return await aiAgentMenu();
|
|
293
188
|
}
|
|
@@ -305,24 +200,15 @@ const selectActiveAgent = async () => {
|
|
|
305
200
|
return await aiAgentMenu();
|
|
306
201
|
};
|
|
307
202
|
|
|
308
|
-
/**
|
|
309
|
-
* Select agent to change model
|
|
310
|
-
*/
|
|
203
|
+
/** Select agent to change model */
|
|
311
204
|
const selectAgentForModelChange = async () => {
|
|
312
205
|
const agents = aiService.getAgents();
|
|
313
206
|
|
|
314
207
|
if (agents.length === 1) {
|
|
315
|
-
return await selectModel(agents[0]);
|
|
208
|
+
return await selectModel(agents[0], aiAgentMenu);
|
|
316
209
|
}
|
|
317
210
|
|
|
318
|
-
const boxWidth =
|
|
319
|
-
const W = boxWidth - 2;
|
|
320
|
-
|
|
321
|
-
const makeLine = (content) => {
|
|
322
|
-
const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
323
|
-
const padding = W - plainLen;
|
|
324
|
-
return chalk.cyan('║') + ' ' + content + ' '.repeat(Math.max(0, padding - 1)) + chalk.cyan('║');
|
|
325
|
-
};
|
|
211
|
+
const { boxWidth, W } = getBoxDimensions();
|
|
326
212
|
|
|
327
213
|
console.clear();
|
|
328
214
|
displayBanner();
|
|
@@ -330,19 +216,16 @@ const selectAgentForModelChange = async () => {
|
|
|
330
216
|
|
|
331
217
|
for (let i = 0; i < agents.length; i++) {
|
|
332
218
|
const agent = agents[i];
|
|
333
|
-
console.log(makeLine(
|
|
334
|
-
chalk.white(`[${i + 1}] `) + chalk.cyan(agent.name) + chalk.white(` - ${agent.model}`)
|
|
335
|
-
));
|
|
219
|
+
console.log(makeLine(W, chalk.white(`[${i + 1}] `) + chalk.cyan(agent.name) + chalk.white(` - ${agent.model}`)));
|
|
336
220
|
}
|
|
337
221
|
|
|
338
|
-
console.log(makeLine(''));
|
|
339
|
-
console.log(makeLine(chalk.white('[<] BACK')));
|
|
222
|
+
console.log(makeLine(W, ''));
|
|
223
|
+
console.log(makeLine(W, chalk.white('[<] BACK')));
|
|
340
224
|
|
|
341
225
|
drawBoxFooter(boxWidth);
|
|
342
226
|
|
|
343
227
|
const choice = await prompts.textInput(chalk.cyan('SELECT AGENT:'));
|
|
344
228
|
|
|
345
|
-
// Empty input or < = go back
|
|
346
229
|
if (!choice || choice.trim() === '' || choice === '<' || choice?.toLowerCase() === 'b') {
|
|
347
230
|
return await aiAgentMenu();
|
|
348
231
|
}
|
|
@@ -352,12 +235,10 @@ const selectAgentForModelChange = async () => {
|
|
|
352
235
|
return await aiAgentMenu();
|
|
353
236
|
}
|
|
354
237
|
|
|
355
|
-
return await selectModel(agents[index]);
|
|
238
|
+
return await selectModel(agents[index], aiAgentMenu);
|
|
356
239
|
};
|
|
357
240
|
|
|
358
|
-
/**
|
|
359
|
-
* Select agent to remove
|
|
360
|
-
*/
|
|
241
|
+
/** Select agent to remove */
|
|
361
242
|
const selectAgentToRemove = async () => {
|
|
362
243
|
const agents = aiService.getAgents();
|
|
363
244
|
|
|
@@ -368,14 +249,7 @@ const selectAgentToRemove = async () => {
|
|
|
368
249
|
return await aiAgentMenu();
|
|
369
250
|
}
|
|
370
251
|
|
|
371
|
-
const boxWidth =
|
|
372
|
-
const W = boxWidth - 2;
|
|
373
|
-
|
|
374
|
-
const makeLine = (content) => {
|
|
375
|
-
const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
376
|
-
const padding = W - plainLen;
|
|
377
|
-
return chalk.cyan('║') + ' ' + content + ' '.repeat(Math.max(0, padding - 1)) + chalk.cyan('║');
|
|
378
|
-
};
|
|
252
|
+
const { boxWidth, W } = getBoxDimensions();
|
|
379
253
|
|
|
380
254
|
console.clear();
|
|
381
255
|
displayBanner();
|
|
@@ -384,19 +258,16 @@ const selectAgentToRemove = async () => {
|
|
|
384
258
|
for (let i = 0; i < agents.length; i++) {
|
|
385
259
|
const agent = agents[i];
|
|
386
260
|
const modelDisplay = agent.model ? chalk.gray(` (${agent.model})`) : '';
|
|
387
|
-
console.log(makeLine(
|
|
388
|
-
chalk.white(`[${i + 1}] `) + chalk.red(agent.name) + modelDisplay
|
|
389
|
-
));
|
|
261
|
+
console.log(makeLine(W, chalk.white(`[${i + 1}] `) + chalk.red(agent.name) + modelDisplay));
|
|
390
262
|
}
|
|
391
263
|
|
|
392
|
-
console.log(makeLine(''));
|
|
393
|
-
console.log(makeLine(chalk.white('[<] BACK')));
|
|
264
|
+
console.log(makeLine(W, ''));
|
|
265
|
+
console.log(makeLine(W, chalk.white('[<] BACK')));
|
|
394
266
|
|
|
395
267
|
drawBoxFooter(boxWidth);
|
|
396
268
|
|
|
397
269
|
const choice = await prompts.textInput(chalk.cyan('SELECT AGENT TO REMOVE:'));
|
|
398
270
|
|
|
399
|
-
// Empty input or < = go back
|
|
400
271
|
if (!choice || choice.trim() === '' || choice === '<' || choice?.toLowerCase() === 'b') {
|
|
401
272
|
return await aiAgentMenu();
|
|
402
273
|
}
|
|
@@ -414,27 +285,9 @@ const selectAgentToRemove = async () => {
|
|
|
414
285
|
return await aiAgentMenu();
|
|
415
286
|
};
|
|
416
287
|
|
|
417
|
-
/**
|
|
418
|
-
* Select provider category
|
|
419
|
-
*/
|
|
288
|
+
/** Select provider category */
|
|
420
289
|
const selectCategory = async () => {
|
|
421
|
-
const boxWidth =
|
|
422
|
-
const W = boxWidth - 2;
|
|
423
|
-
const col1Width = Math.floor(W / 2);
|
|
424
|
-
|
|
425
|
-
const makeLine = (content) => {
|
|
426
|
-
const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
427
|
-
const padding = W - plainLen;
|
|
428
|
-
return chalk.cyan('║') + ' ' + content + ' '.repeat(Math.max(0, padding - 1)) + chalk.cyan('║');
|
|
429
|
-
};
|
|
430
|
-
|
|
431
|
-
const make2ColRow = (left, right) => {
|
|
432
|
-
const leftPlain = left.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
433
|
-
const rightPlain = right.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
434
|
-
const leftPadded = ' ' + left + ' '.repeat(Math.max(0, col1Width - leftPlain - 1));
|
|
435
|
-
const rightPadded = right + ' '.repeat(Math.max(0, W - col1Width - rightPlain));
|
|
436
|
-
return chalk.cyan('║') + leftPadded + rightPadded + chalk.cyan('║');
|
|
437
|
-
};
|
|
290
|
+
const { boxWidth, W } = getBoxDimensions();
|
|
438
291
|
|
|
439
292
|
console.clear();
|
|
440
293
|
displayBanner();
|
|
@@ -442,30 +295,22 @@ const selectCategory = async () => {
|
|
|
442
295
|
|
|
443
296
|
const categories = getCategories();
|
|
444
297
|
|
|
445
|
-
|
|
446
|
-
console.log(make2ColRow(
|
|
298
|
+
console.log(make2ColRow(W,
|
|
447
299
|
chalk.cyan('[1]') + chalk.yellow(' UNIFIED (RECOMMENDED)'),
|
|
448
300
|
chalk.cyan('[2]') + chalk.yellow(' DIRECT PROVIDERS')
|
|
449
301
|
));
|
|
450
|
-
console.log(make2ColRow(
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
));
|
|
454
|
-
console.log(makeLine(''));
|
|
455
|
-
console.log(make2ColRow(
|
|
302
|
+
console.log(make2ColRow(W, chalk.white(' 1 API = 100+ models'), chalk.white(' Connect to each provider')));
|
|
303
|
+
console.log(makeLine(W, ''));
|
|
304
|
+
console.log(make2ColRow(W,
|
|
456
305
|
chalk.cyan('[3]') + chalk.yellow(' LOCAL (FREE)'),
|
|
457
306
|
chalk.cyan('[4]') + chalk.yellow(' CUSTOM')
|
|
458
307
|
));
|
|
459
|
-
console.log(make2ColRow(
|
|
460
|
-
chalk.white(' Run on your machine'),
|
|
461
|
-
chalk.white(' Self-hosted solutions')
|
|
462
|
-
));
|
|
308
|
+
console.log(make2ColRow(W, chalk.white(' Run on your machine'), chalk.white(' Self-hosted solutions')));
|
|
463
309
|
|
|
464
310
|
drawBoxFooter(boxWidth);
|
|
465
311
|
|
|
466
312
|
const choice = await prompts.textInput(chalk.cyan('SELECT (1-4):'));
|
|
467
313
|
|
|
468
|
-
// Empty input = go back
|
|
469
314
|
if (!choice || choice.trim() === '' || choice === '<' || choice?.toLowerCase() === 'b') {
|
|
470
315
|
return await aiAgentMenu();
|
|
471
316
|
}
|
|
@@ -475,32 +320,14 @@ const selectCategory = async () => {
|
|
|
475
320
|
return await aiAgentMenu();
|
|
476
321
|
}
|
|
477
322
|
|
|
478
|
-
|
|
479
|
-
return await selectProvider(selectedCategory.id);
|
|
323
|
+
return await selectProvider(categories[index].id);
|
|
480
324
|
};
|
|
481
325
|
|
|
482
|
-
/**
|
|
483
|
-
* Select AI provider from category
|
|
484
|
-
*/
|
|
326
|
+
/** Select AI provider from category */
|
|
485
327
|
const selectProvider = async (categoryId) => {
|
|
486
|
-
const boxWidth =
|
|
487
|
-
const W = boxWidth - 2;
|
|
328
|
+
const { boxWidth, W } = getBoxDimensions();
|
|
488
329
|
const col1Width = Math.floor(W / 2);
|
|
489
330
|
|
|
490
|
-
const makeLine = (content) => {
|
|
491
|
-
const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
492
|
-
const padding = W - plainLen;
|
|
493
|
-
return chalk.cyan('║') + ' ' + content + ' '.repeat(Math.max(0, padding - 1)) + chalk.cyan('║');
|
|
494
|
-
};
|
|
495
|
-
|
|
496
|
-
const make2ColRow = (left, right) => {
|
|
497
|
-
const leftPlain = left.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
498
|
-
const rightPlain = right.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
499
|
-
const leftPadded = ' ' + left + ' '.repeat(Math.max(0, col1Width - leftPlain - 1));
|
|
500
|
-
const rightPadded = right + ' '.repeat(Math.max(0, W - col1Width - rightPlain));
|
|
501
|
-
return chalk.cyan('║') + leftPadded + rightPadded + chalk.cyan('║');
|
|
502
|
-
};
|
|
503
|
-
|
|
504
331
|
console.clear();
|
|
505
332
|
displayBanner();
|
|
506
333
|
|
|
@@ -511,49 +338,42 @@ const selectProvider = async (categoryId) => {
|
|
|
511
338
|
const providers = getProvidersByCategory(categoryId);
|
|
512
339
|
|
|
513
340
|
if (providers.length === 0) {
|
|
514
|
-
console.log(makeLine(chalk.white('NO PROVIDERS IN THIS CATEGORY')));
|
|
341
|
+
console.log(makeLine(W, chalk.white('NO PROVIDERS IN THIS CATEGORY')));
|
|
515
342
|
drawBoxFooter(boxWidth);
|
|
516
343
|
await prompts.waitForEnter();
|
|
517
344
|
return await selectCategory();
|
|
518
345
|
}
|
|
519
346
|
|
|
520
|
-
// Display providers in 2 columns
|
|
347
|
+
// Display providers in 2 columns
|
|
521
348
|
for (let i = 0; i < providers.length; i += 2) {
|
|
522
349
|
const left = providers[i];
|
|
523
350
|
const right = providers[i + 1];
|
|
524
351
|
|
|
525
|
-
// Provider names - number in cyan, name in yellow
|
|
526
352
|
const leftNum = `[${i + 1}]`;
|
|
527
353
|
const rightNum = right ? `[${i + 2}]` : '';
|
|
528
354
|
const leftName = ` ${left.name}`;
|
|
529
355
|
const rightName = right ? ` ${right.name}` : '';
|
|
530
356
|
|
|
531
|
-
|
|
532
|
-
const rightFull = rightNum + rightName;
|
|
533
|
-
|
|
534
|
-
console.log(make2ColRow(
|
|
357
|
+
console.log(make2ColRow(W,
|
|
535
358
|
chalk.cyan(leftNum) + chalk.yellow(leftName.length > col1Width - leftNum.length - 3 ? leftName.substring(0, col1Width - leftNum.length - 6) + '...' : leftName),
|
|
536
359
|
right ? chalk.cyan(rightNum) + chalk.yellow(rightName.length > col1Width - rightNum.length - 3 ? rightName.substring(0, col1Width - rightNum.length - 6) + '...' : rightName) : ''
|
|
537
360
|
));
|
|
538
361
|
|
|
539
|
-
// Descriptions (truncated)
|
|
540
362
|
const leftDesc = ' ' + left.description;
|
|
541
363
|
const rightDesc = right ? ' ' + right.description : '';
|
|
542
364
|
|
|
543
|
-
console.log(make2ColRow(
|
|
365
|
+
console.log(make2ColRow(W,
|
|
544
366
|
chalk.white(leftDesc.length > col1Width - 3 ? leftDesc.substring(0, col1Width - 6) + '...' : leftDesc),
|
|
545
367
|
chalk.white(rightDesc.length > col1Width - 3 ? rightDesc.substring(0, col1Width - 6) + '...' : rightDesc)
|
|
546
368
|
));
|
|
547
369
|
|
|
548
|
-
console.log(makeLine(''));
|
|
370
|
+
console.log(makeLine(W, ''));
|
|
549
371
|
}
|
|
550
372
|
|
|
551
373
|
drawBoxFooter(boxWidth);
|
|
552
374
|
|
|
553
|
-
const
|
|
554
|
-
const choice = await prompts.textInput(chalk.cyan(`SELECT (1-${maxNum}):`));
|
|
375
|
+
const choice = await prompts.textInput(chalk.cyan(`SELECT (1-${providers.length}):`));
|
|
555
376
|
|
|
556
|
-
// Empty input = go back
|
|
557
377
|
if (!choice || choice.trim() === '' || choice === '<' || choice?.toLowerCase() === 'b') {
|
|
558
378
|
return await selectCategory();
|
|
559
379
|
}
|
|
@@ -563,32 +383,14 @@ const selectProvider = async (categoryId) => {
|
|
|
563
383
|
return await selectCategory();
|
|
564
384
|
}
|
|
565
385
|
|
|
566
|
-
|
|
567
|
-
return await selectProviderOption(selectedProvider);
|
|
386
|
+
return await selectProviderOption(providers[index]);
|
|
568
387
|
};
|
|
569
388
|
|
|
570
|
-
/**
|
|
571
|
-
* Select connection option for provider
|
|
572
|
-
*/
|
|
389
|
+
/** Select connection option for provider */
|
|
573
390
|
const selectProviderOption = async (provider) => {
|
|
574
|
-
const boxWidth =
|
|
575
|
-
const W = boxWidth - 2;
|
|
391
|
+
const { boxWidth, W } = getBoxDimensions();
|
|
576
392
|
const col1Width = Math.floor(W / 2);
|
|
577
393
|
|
|
578
|
-
const makeLine = (content) => {
|
|
579
|
-
const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
580
|
-
const padding = W - plainLen;
|
|
581
|
-
return chalk.cyan('║') + ' ' + content + ' '.repeat(Math.max(0, padding - 1)) + chalk.cyan('║');
|
|
582
|
-
};
|
|
583
|
-
|
|
584
|
-
const make2ColRow = (left, right) => {
|
|
585
|
-
const leftPlain = left.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
586
|
-
const rightPlain = right.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
587
|
-
const leftPadded = ' ' + left + ' '.repeat(Math.max(0, col1Width - leftPlain - 1));
|
|
588
|
-
const rightPadded = right + ' '.repeat(Math.max(0, W - col1Width - rightPlain));
|
|
589
|
-
return chalk.cyan('║') + leftPadded + rightPadded + chalk.cyan('║');
|
|
590
|
-
};
|
|
591
|
-
|
|
592
394
|
// If only one option, skip selection
|
|
593
395
|
if (provider.options.length === 1) {
|
|
594
396
|
return await setupConnection(provider, provider.options[0]);
|
|
@@ -598,46 +400,42 @@ const selectProviderOption = async (provider) => {
|
|
|
598
400
|
displayBanner();
|
|
599
401
|
drawBoxHeaderContinue(provider.name, boxWidth);
|
|
600
402
|
|
|
601
|
-
console.log(makeLine(chalk.white('SELECT CONNECTION METHOD:')));
|
|
602
|
-
console.log(makeLine(''));
|
|
403
|
+
console.log(makeLine(W, chalk.white('SELECT CONNECTION METHOD:')));
|
|
404
|
+
console.log(makeLine(W, ''));
|
|
603
405
|
|
|
604
|
-
// Display options in 2 columns
|
|
406
|
+
// Display options in 2 columns
|
|
605
407
|
for (let i = 0; i < provider.options.length; i += 2) {
|
|
606
408
|
const left = provider.options[i];
|
|
607
409
|
const right = provider.options[i + 1];
|
|
608
410
|
|
|
609
|
-
|
|
610
|
-
console.log(make2ColRow(
|
|
411
|
+
console.log(make2ColRow(W,
|
|
611
412
|
chalk.cyan(`[${i + 1}]`) + chalk.yellow(` ${left.label}`),
|
|
612
413
|
right ? chalk.cyan(`[${i + 2}]`) + chalk.yellow(` ${right.label}`) : ''
|
|
613
414
|
));
|
|
614
415
|
|
|
615
|
-
// First description line
|
|
616
416
|
const leftDesc1 = left.description[0] ? ' ' + left.description[0] : '';
|
|
617
417
|
const rightDesc1 = right?.description[0] ? ' ' + right.description[0] : '';
|
|
618
|
-
console.log(make2ColRow(
|
|
418
|
+
console.log(make2ColRow(W,
|
|
619
419
|
chalk.white(leftDesc1.length > col1Width - 2 ? leftDesc1.substring(0, col1Width - 5) + '...' : leftDesc1),
|
|
620
420
|
chalk.white(rightDesc1.length > col1Width - 2 ? rightDesc1.substring(0, col1Width - 5) + '...' : rightDesc1)
|
|
621
421
|
));
|
|
622
422
|
|
|
623
|
-
// Second description line if exists
|
|
624
423
|
const leftDesc2 = left.description[1] ? ' ' + left.description[1] : '';
|
|
625
424
|
const rightDesc2 = right?.description[1] ? ' ' + right.description[1] : '';
|
|
626
425
|
if (leftDesc2 || rightDesc2) {
|
|
627
|
-
console.log(make2ColRow(
|
|
426
|
+
console.log(make2ColRow(W,
|
|
628
427
|
chalk.white(leftDesc2.length > col1Width - 2 ? leftDesc2.substring(0, col1Width - 5) + '...' : leftDesc2),
|
|
629
428
|
chalk.white(rightDesc2.length > col1Width - 2 ? rightDesc2.substring(0, col1Width - 5) + '...' : rightDesc2)
|
|
630
429
|
));
|
|
631
430
|
}
|
|
632
431
|
|
|
633
|
-
console.log(makeLine(''));
|
|
432
|
+
console.log(makeLine(W, ''));
|
|
634
433
|
}
|
|
635
434
|
|
|
636
435
|
drawBoxFooter(boxWidth);
|
|
637
436
|
|
|
638
437
|
const choice = await prompts.textInput(chalk.cyan('SELECT:'));
|
|
639
438
|
|
|
640
|
-
// Empty input = go back
|
|
641
439
|
if (!choice || choice.trim() === '' || choice === '<' || choice?.toLowerCase() === 'b') {
|
|
642
440
|
return await selectProvider(provider.category);
|
|
643
441
|
}
|
|
@@ -647,1237 +445,38 @@ const selectProviderOption = async (provider) => {
|
|
|
647
445
|
return await selectProvider(provider.category);
|
|
648
446
|
}
|
|
649
447
|
|
|
650
|
-
|
|
651
|
-
return await setupConnection(provider, selectedOption);
|
|
652
|
-
};
|
|
653
|
-
|
|
654
|
-
/**
|
|
655
|
-
* Open URL in default browser
|
|
656
|
-
* @returns {Promise<boolean>} true if browser opened, false if failed
|
|
657
|
-
*/
|
|
658
|
-
const openBrowser = (url) => {
|
|
659
|
-
return new Promise((resolve) => {
|
|
660
|
-
const { exec } = require('child_process');
|
|
661
|
-
const platform = process.platform;
|
|
662
|
-
|
|
663
|
-
let cmd;
|
|
664
|
-
if (platform === 'darwin') cmd = `open "${url}"`;
|
|
665
|
-
else if (platform === 'win32') cmd = `start "" "${url}"`;
|
|
666
|
-
else cmd = `xdg-open "${url}"`;
|
|
667
|
-
|
|
668
|
-
exec(cmd, (err) => {
|
|
669
|
-
resolve(!err);
|
|
670
|
-
});
|
|
671
|
-
});
|
|
672
|
-
};
|
|
673
|
-
|
|
674
|
-
/**
|
|
675
|
-
* Get instructions for each credential type
|
|
676
|
-
*/
|
|
677
|
-
const getCredentialInstructions = (provider, option, field) => {
|
|
678
|
-
const instructions = {
|
|
679
|
-
apiKey: {
|
|
680
|
-
title: 'API KEY REQUIRED',
|
|
681
|
-
steps: [
|
|
682
|
-
'1. CLICK THE LINK BELOW TO OPEN IN BROWSER',
|
|
683
|
-
'2. SIGN IN OR CREATE AN ACCOUNT',
|
|
684
|
-
'3. GENERATE A NEW API KEY',
|
|
685
|
-
'4. COPY AND PASTE IT HERE'
|
|
686
|
-
]
|
|
687
|
-
},
|
|
688
|
-
sessionKey: {
|
|
689
|
-
title: 'SESSION KEY REQUIRED (SUBSCRIPTION PLAN)',
|
|
690
|
-
steps: [
|
|
691
|
-
'1. OPEN THE LINK BELOW IN YOUR BROWSER',
|
|
692
|
-
'2. SIGN IN WITH YOUR SUBSCRIPTION ACCOUNT',
|
|
693
|
-
'3. OPEN DEVELOPER TOOLS (F12 OR CMD+OPT+I)',
|
|
694
|
-
'4. GO TO APPLICATION > COOKIES',
|
|
695
|
-
'5. FIND "sessionKey" OR SIMILAR TOKEN',
|
|
696
|
-
'6. COPY THE VALUE AND PASTE IT HERE'
|
|
697
|
-
]
|
|
698
|
-
},
|
|
699
|
-
accessToken: {
|
|
700
|
-
title: 'ACCESS TOKEN REQUIRED (SUBSCRIPTION PLAN)',
|
|
701
|
-
steps: [
|
|
702
|
-
'1. OPEN THE LINK BELOW IN YOUR BROWSER',
|
|
703
|
-
'2. SIGN IN WITH YOUR SUBSCRIPTION ACCOUNT',
|
|
704
|
-
'3. OPEN DEVELOPER TOOLS (F12 OR CMD+OPT+I)',
|
|
705
|
-
'4. GO TO APPLICATION > COOKIES OR LOCAL STORAGE',
|
|
706
|
-
'5. FIND "accessToken" OR "token"',
|
|
707
|
-
'6. COPY THE VALUE AND PASTE IT HERE'
|
|
708
|
-
]
|
|
709
|
-
},
|
|
710
|
-
endpoint: {
|
|
711
|
-
title: 'ENDPOINT URL',
|
|
712
|
-
steps: [
|
|
713
|
-
'1. ENTER THE API ENDPOINT URL',
|
|
714
|
-
'2. USUALLY http://localhost:PORT FOR LOCAL'
|
|
715
|
-
]
|
|
716
|
-
},
|
|
717
|
-
model: {
|
|
718
|
-
title: 'MODEL NAME',
|
|
719
|
-
steps: [
|
|
720
|
-
'1. ENTER THE MODEL NAME TO USE',
|
|
721
|
-
'2. CHECK PROVIDER DOCS FOR AVAILABLE MODELS'
|
|
722
|
-
]
|
|
723
|
-
}
|
|
724
|
-
};
|
|
725
|
-
|
|
726
|
-
return instructions[field] || { title: field.toUpperCase(), steps: [] };
|
|
727
|
-
};
|
|
728
|
-
|
|
729
|
-
/**
|
|
730
|
-
* Get OAuth config for provider
|
|
731
|
-
*/
|
|
732
|
-
const getOAuthConfig = (providerId) => {
|
|
733
|
-
const configs = {
|
|
734
|
-
anthropic: {
|
|
735
|
-
name: 'CLAUDE PRO/MAX',
|
|
736
|
-
accountName: 'Claude',
|
|
737
|
-
oauthModule: oauthAnthropic,
|
|
738
|
-
authorizeArgs: ['max'],
|
|
739
|
-
optionId: 'oauth_max',
|
|
740
|
-
agentName: 'Claude Pro/Max',
|
|
741
|
-
codeFormat: 'abc123...#xyz789...'
|
|
742
|
-
},
|
|
743
|
-
openai: {
|
|
744
|
-
name: 'CHATGPT PLUS/PRO',
|
|
745
|
-
accountName: 'ChatGPT',
|
|
746
|
-
oauthModule: oauthOpenai,
|
|
747
|
-
authorizeArgs: [],
|
|
748
|
-
optionId: 'oauth_plus',
|
|
749
|
-
agentName: 'ChatGPT Plus/Pro',
|
|
750
|
-
codeFormat: 'authorization_code'
|
|
751
|
-
},
|
|
752
|
-
gemini: {
|
|
753
|
-
name: 'GEMINI ADVANCED',
|
|
754
|
-
accountName: 'Google',
|
|
755
|
-
oauthModule: oauthGemini,
|
|
756
|
-
authorizeArgs: [],
|
|
757
|
-
optionId: 'oauth_advanced',
|
|
758
|
-
agentName: 'Gemini Advanced',
|
|
759
|
-
codeFormat: 'authorization_code'
|
|
760
|
-
},
|
|
761
|
-
qwen: {
|
|
762
|
-
name: 'QWEN CHAT',
|
|
763
|
-
accountName: 'Qwen',
|
|
764
|
-
oauthModule: oauthQwen,
|
|
765
|
-
authorizeArgs: [],
|
|
766
|
-
optionId: 'oauth_chat',
|
|
767
|
-
agentName: 'Qwen Chat',
|
|
768
|
-
isDeviceFlow: true
|
|
769
|
-
},
|
|
770
|
-
iflow: {
|
|
771
|
-
name: 'IFLOW',
|
|
772
|
-
accountName: 'iFlow',
|
|
773
|
-
oauthModule: oauthIflow,
|
|
774
|
-
authorizeArgs: [],
|
|
775
|
-
optionId: 'oauth_sub',
|
|
776
|
-
agentName: 'iFlow',
|
|
777
|
-
codeFormat: 'authorization_code'
|
|
778
|
-
}
|
|
779
|
-
};
|
|
780
|
-
return configs[providerId];
|
|
781
|
-
};
|
|
782
|
-
|
|
783
|
-
/**
|
|
784
|
-
* Setup OAuth connection for any provider with OAuth support
|
|
785
|
-
* Uses CLIProxyAPI for proper OAuth handling and API access
|
|
786
|
-
*/
|
|
787
|
-
const setupOAuthConnection = async (provider) => {
|
|
788
|
-
const config = getOAuthConfig(provider.id);
|
|
789
|
-
if (!config) {
|
|
790
|
-
console.log(chalk.red('OAuth not supported for this provider'));
|
|
791
|
-
await prompts.waitForEnter();
|
|
792
|
-
return await selectProviderOption(provider);
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
// Use CLIProxyAPI for OAuth flow - it handles token exchange and API calls
|
|
796
|
-
return await setupProxyOAuth(provider, config);
|
|
448
|
+
return await setupConnection(provider, provider.options[index]);
|
|
797
449
|
};
|
|
798
450
|
|
|
799
|
-
/**
|
|
800
|
-
* Setup OAuth via Manual Code Entry (unified flow for local and VPS)
|
|
801
|
-
* User copies the authorization code from the URL or page
|
|
802
|
-
*/
|
|
803
|
-
const setupRemoteOAuth = async (provider, config) => {
|
|
804
|
-
const boxWidth = getLogoWidth();
|
|
805
|
-
const W = boxWidth - 2;
|
|
806
|
-
|
|
807
|
-
const makeLine = (content) => {
|
|
808
|
-
const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
809
|
-
const padding = W - plainLen;
|
|
810
|
-
return chalk.cyan('║') + ' ' + content + ' '.repeat(Math.max(0, padding - 1)) + chalk.cyan('║');
|
|
811
|
-
};
|
|
812
|
-
|
|
813
|
-
// Get the right OAuth module for this provider
|
|
814
|
-
const oauthModules = {
|
|
815
|
-
anthropic: oauthAnthropic,
|
|
816
|
-
openai: oauthOpenai,
|
|
817
|
-
gemini: oauthGemini,
|
|
818
|
-
qwen: oauthQwen,
|
|
819
|
-
iflow: oauthIflow
|
|
820
|
-
};
|
|
821
|
-
|
|
822
|
-
const oauthModule = oauthModules[provider.id];
|
|
823
|
-
if (!oauthModule) {
|
|
824
|
-
console.log(chalk.red(`OAuth not supported for ${provider.id}`));
|
|
825
|
-
await prompts.waitForEnter();
|
|
826
|
-
return await selectProviderOption(provider);
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
// Generate OAuth URL using the provider's oauth module
|
|
830
|
-
const authResult = oauthModule.authorize(config.optionId || 'max');
|
|
831
|
-
const url = authResult.url;
|
|
832
|
-
const verifier = authResult.verifier;
|
|
833
|
-
|
|
834
|
-
// Show instructions
|
|
835
|
-
console.clear();
|
|
836
|
-
displayBanner();
|
|
837
|
-
drawBoxHeaderContinue(`CONNECT ${config.name}`, boxWidth);
|
|
838
|
-
|
|
839
|
-
console.log(makeLine(chalk.yellow('CONNECT YOUR ACCOUNT')));
|
|
840
|
-
console.log(makeLine(''));
|
|
841
|
-
console.log(makeLine(chalk.white('1. OPEN THE LINK BELOW IN YOUR BROWSER')));
|
|
842
|
-
console.log(makeLine(''));
|
|
843
|
-
console.log(makeLine(chalk.white(`2. LOGIN WITH YOUR ${config.accountName.toUpperCase()} ACCOUNT`)));
|
|
844
|
-
console.log(makeLine(''));
|
|
845
|
-
console.log(makeLine(chalk.white('3. CLICK "AUTHORIZE"')));
|
|
846
|
-
console.log(makeLine(''));
|
|
847
|
-
console.log(makeLine(chalk.green('4. COPY THE CODE FROM THE URL BAR')));
|
|
848
|
-
console.log(makeLine(chalk.white(' Look for: code=XXXXXX in the URL')));
|
|
849
|
-
console.log(makeLine(chalk.white(' Copy everything after code= until &')));
|
|
850
|
-
console.log(makeLine(''));
|
|
851
|
-
console.log(makeLine(chalk.white('5. PASTE THE CODE BELOW')));
|
|
852
|
-
console.log(makeLine(''));
|
|
853
|
-
|
|
854
|
-
drawBoxFooter(boxWidth);
|
|
855
|
-
|
|
856
|
-
// Display URL outside the box for easy copy
|
|
857
|
-
console.log();
|
|
858
|
-
console.log(chalk.yellow(' OPEN THIS URL IN YOUR BROWSER:'));
|
|
859
|
-
console.log();
|
|
860
|
-
console.log(chalk.cyan(` ${url}`));
|
|
861
|
-
console.log();
|
|
862
|
-
|
|
863
|
-
// Get code from user
|
|
864
|
-
const code = await prompts.textInput(chalk.cyan('PASTE AUTHORIZATION CODE:'));
|
|
865
|
-
|
|
866
|
-
if (!code || code.trim() === '<' || code.trim() === '') {
|
|
867
|
-
return await selectProviderOption(provider);
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
// Exchange code for tokens
|
|
871
|
-
const spinner = ora({ text: 'Exchanging code for tokens...', color: 'cyan' }).start();
|
|
872
|
-
|
|
873
|
-
const result = await oauthModule.exchange(code.trim(), verifier);
|
|
874
|
-
|
|
875
|
-
if (result.type === 'failed') {
|
|
876
|
-
spinner.fail(`Authentication failed: ${result.error || 'Invalid code'}`);
|
|
877
|
-
await prompts.waitForEnter();
|
|
878
|
-
return await selectProviderOption(provider);
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
spinner.succeed('Authorization successful!');
|
|
882
|
-
|
|
883
|
-
// Save credentials
|
|
884
|
-
const credentials = {
|
|
885
|
-
oauth: {
|
|
886
|
-
access: result.access,
|
|
887
|
-
refresh: result.refresh,
|
|
888
|
-
expires: result.expires,
|
|
889
|
-
apiKey: result.apiKey,
|
|
890
|
-
email: result.email
|
|
891
|
-
}
|
|
892
|
-
};
|
|
893
|
-
|
|
894
|
-
// Try to fetch models with the new token
|
|
895
|
-
spinner.text = 'Fetching available models from API...';
|
|
896
|
-
spinner.start();
|
|
897
|
-
|
|
898
|
-
let models = [];
|
|
899
|
-
let fetchError = null;
|
|
900
|
-
try {
|
|
901
|
-
const { fetchModelsWithOAuth } = require('../services/ai/client');
|
|
902
|
-
models = await fetchModelsWithOAuth(provider.id, result.access);
|
|
903
|
-
} catch (e) {
|
|
904
|
-
fetchError = e.message;
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
// RULE: Models MUST come from API - no hardcoded fallback
|
|
908
|
-
if (!models || models.length === 0) {
|
|
909
|
-
spinner.fail('Could not fetch models from API');
|
|
910
|
-
console.log();
|
|
911
|
-
console.log(chalk.red(' ERROR: Unable to retrieve models from provider API'));
|
|
912
|
-
console.log(chalk.white(' Possible causes:'));
|
|
913
|
-
console.log(chalk.gray(' - OAuth token may not have permission to list models'));
|
|
914
|
-
console.log(chalk.gray(' - Network issue or API temporarily unavailable'));
|
|
915
|
-
console.log(chalk.gray(' - Provider API may have changed'));
|
|
916
|
-
if (fetchError) {
|
|
917
|
-
console.log(chalk.gray(` - Error: ${fetchError}`));
|
|
918
|
-
}
|
|
919
|
-
console.log();
|
|
920
|
-
console.log(chalk.yellow(' Please try again or use API Key authentication instead.'));
|
|
921
|
-
await prompts.waitForEnter();
|
|
922
|
-
return await selectProviderOption(provider);
|
|
923
|
-
}
|
|
924
|
-
|
|
925
|
-
spinner.succeed(`Found ${models.length} models`);
|
|
926
|
-
|
|
927
|
-
// Let user select model from list
|
|
928
|
-
const selectedModel = await selectModelFromList(models, config.name);
|
|
929
|
-
if (!selectedModel) {
|
|
930
|
-
return await selectProviderOption(provider);
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
// Add agent
|
|
934
|
-
try {
|
|
935
|
-
await aiService.addAgent(provider.id, config.optionId, credentials, selectedModel, config.agentName);
|
|
936
|
-
|
|
937
|
-
console.log(chalk.green(`\n CONNECTED TO ${config.name}`));
|
|
938
|
-
console.log(chalk.white(` MODEL: ${selectedModel}`));
|
|
939
|
-
console.log(chalk.white(' UNLIMITED USAGE WITH YOUR SUBSCRIPTION'));
|
|
940
|
-
} catch (error) {
|
|
941
|
-
console.log(chalk.red(`\n FAILED TO SAVE: ${error.message}`));
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
await prompts.waitForEnter();
|
|
945
|
-
return await aiAgentMenu();
|
|
946
|
-
};
|
|
947
|
-
|
|
948
|
-
// NOTE: promptForModelName was removed - models MUST come from API (RULES.md)
|
|
949
|
-
|
|
950
|
-
/**
|
|
951
|
-
* Setup OAuth using CLIProxyAPI
|
|
952
|
-
* CLIProxyAPI handles OAuth flow, token storage, and API calls
|
|
953
|
-
* Models are fetched from CLIProxyAPI /v1/models endpoint
|
|
954
|
-
*/
|
|
955
|
-
const setupProxyOAuth = async (provider, config) => {
|
|
956
|
-
const boxWidth = getLogoWidth();
|
|
957
|
-
const W = boxWidth - 2;
|
|
958
|
-
|
|
959
|
-
const makeLine = (content) => {
|
|
960
|
-
const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
961
|
-
const padding = W - plainLen;
|
|
962
|
-
return chalk.cyan('║') + ' ' + content + ' '.repeat(Math.max(0, padding - 1)) + chalk.cyan('║');
|
|
963
|
-
};
|
|
964
|
-
|
|
965
|
-
// Step 1: Ensure CLIProxyAPI is installed and running
|
|
966
|
-
const spinner = ora({ text: 'Setting up CLIProxyAPI...', color: 'cyan' }).start();
|
|
967
|
-
|
|
968
|
-
try {
|
|
969
|
-
await proxyManager.ensureRunning();
|
|
970
|
-
spinner.succeed('CLIProxyAPI ready');
|
|
971
|
-
} catch (error) {
|
|
972
|
-
spinner.fail(`Failed to start CLIProxyAPI: ${error.message}`);
|
|
973
|
-
console.log();
|
|
974
|
-
console.log(chalk.yellow(' CLIProxyAPI is required for OAuth authentication.'));
|
|
975
|
-
console.log(chalk.gray(' It will be downloaded automatically on first use.'));
|
|
976
|
-
console.log();
|
|
977
|
-
await prompts.waitForEnter();
|
|
978
|
-
return await selectProviderOption(provider);
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
// Step 2: Get OAuth URL from CLIProxyAPI
|
|
982
|
-
spinner.text = 'Getting authorization URL...';
|
|
983
|
-
spinner.start();
|
|
984
|
-
|
|
985
|
-
let authUrl, authState;
|
|
986
|
-
try {
|
|
987
|
-
const authInfo = await proxyManager.getAuthUrl(provider.id);
|
|
988
|
-
authUrl = authInfo.url;
|
|
989
|
-
authState = authInfo.state;
|
|
990
|
-
spinner.succeed('Authorization URL ready');
|
|
991
|
-
} catch (error) {
|
|
992
|
-
spinner.fail(`Failed to get auth URL: ${error.message}`);
|
|
993
|
-
await prompts.waitForEnter();
|
|
994
|
-
return await selectProviderOption(provider);
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
// Step 3: Show instructions to user
|
|
998
|
-
console.clear();
|
|
999
|
-
displayBanner();
|
|
1000
|
-
drawBoxHeaderContinue(`CONNECT ${config.name}`, boxWidth);
|
|
1001
|
-
|
|
1002
|
-
console.log(makeLine(chalk.yellow('CONNECT YOUR ACCOUNT')));
|
|
1003
|
-
console.log(makeLine(''));
|
|
1004
|
-
console.log(makeLine(chalk.white('1. OPEN THE LINK BELOW IN YOUR BROWSER')));
|
|
1005
|
-
console.log(makeLine(''));
|
|
1006
|
-
console.log(makeLine(chalk.white(`2. LOGIN WITH YOUR ${config.accountName.toUpperCase()} ACCOUNT`)));
|
|
1007
|
-
console.log(makeLine(''));
|
|
1008
|
-
console.log(makeLine(chalk.white('3. CLICK "AUTHORIZE"')));
|
|
1009
|
-
console.log(makeLine(''));
|
|
1010
|
-
console.log(makeLine(chalk.green('4. WAIT FOR CONFIRMATION HERE')));
|
|
1011
|
-
console.log(makeLine(chalk.white(' (The page will close automatically)')));
|
|
1012
|
-
console.log(makeLine(''));
|
|
1013
|
-
|
|
1014
|
-
drawBoxFooter(boxWidth);
|
|
1015
|
-
|
|
1016
|
-
// Display URL
|
|
1017
|
-
console.log();
|
|
1018
|
-
console.log(chalk.yellow(' OPEN THIS URL IN YOUR BROWSER:'));
|
|
1019
|
-
console.log();
|
|
1020
|
-
console.log(chalk.cyan(` ${authUrl}`));
|
|
1021
|
-
console.log();
|
|
1022
|
-
|
|
1023
|
-
// Try to open browser automatically
|
|
1024
|
-
const browserOpened = await openBrowser(authUrl);
|
|
1025
|
-
if (browserOpened) {
|
|
1026
|
-
console.log(chalk.gray(' (Browser opened automatically)'));
|
|
1027
|
-
} else {
|
|
1028
|
-
console.log(chalk.gray(' (Copy and paste the URL in your browser)'));
|
|
1029
|
-
}
|
|
1030
|
-
console.log();
|
|
1031
|
-
|
|
1032
|
-
// Step 4: Wait for OAuth callback
|
|
1033
|
-
// Detect if we're on a remote server (SSH) - callback won't work automatically
|
|
1034
|
-
const isRemote = process.env.SSH_CONNECTION || process.env.SSH_CLIENT ||
|
|
1035
|
-
(process.env.DISPLAY === undefined && process.platform === 'linux');
|
|
1036
|
-
|
|
1037
|
-
let authSuccess = false;
|
|
1038
|
-
|
|
1039
|
-
if (isRemote) {
|
|
1040
|
-
// Remote/VPS: Ask for callback URL directly (no point waiting)
|
|
1041
|
-
console.log(chalk.yellow(' After authorizing, paste the callback URL from your browser:'));
|
|
1042
|
-
console.log(chalk.gray(' (The page showing http://localhost:54545/callback?code=...)'));
|
|
1043
|
-
console.log();
|
|
1044
|
-
|
|
1045
|
-
// Use inquirer directly for reliable input on VPS
|
|
1046
|
-
const inquirer = require('inquirer');
|
|
1047
|
-
let callbackUrl = '';
|
|
1048
|
-
try {
|
|
1049
|
-
const result = await inquirer.prompt([{
|
|
1050
|
-
type: 'input',
|
|
1051
|
-
name: 'callbackUrl',
|
|
1052
|
-
message: 'Callback URL:',
|
|
1053
|
-
prefix: ' '
|
|
1054
|
-
}]);
|
|
1055
|
-
callbackUrl = result.callbackUrl || '';
|
|
1056
|
-
} catch (e) {
|
|
1057
|
-
// Inquirer failed, try readline directly
|
|
1058
|
-
const readline = require('readline');
|
|
1059
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1060
|
-
callbackUrl = await new Promise(resolve => {
|
|
1061
|
-
rl.question(' Callback URL: ', answer => {
|
|
1062
|
-
rl.close();
|
|
1063
|
-
resolve(answer || '');
|
|
1064
|
-
});
|
|
1065
|
-
});
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
if (callbackUrl && callbackUrl.trim()) {
|
|
1069
|
-
const submitSpinner = ora({ text: 'Submitting callback...', color: 'cyan' }).start();
|
|
1070
|
-
try {
|
|
1071
|
-
await proxyManager.submitCallback(callbackUrl.trim(), provider.id);
|
|
1072
|
-
// Callback succeeded - CLIProxyAPI handles the token exchange
|
|
1073
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
1074
|
-
authSuccess = true;
|
|
1075
|
-
submitSpinner.succeed('Authorization successful!');
|
|
1076
|
-
} catch (submitError) {
|
|
1077
|
-
submitSpinner.fail(`Failed: ${submitError.message}`);
|
|
1078
|
-
await prompts.waitForEnter();
|
|
1079
|
-
return await selectProviderOption(provider);
|
|
1080
|
-
}
|
|
1081
|
-
} else {
|
|
1082
|
-
console.log(chalk.gray(' Cancelled.'));
|
|
1083
|
-
await prompts.waitForEnter();
|
|
1084
|
-
return await selectProviderOption(provider);
|
|
1085
|
-
}
|
|
1086
|
-
} else {
|
|
1087
|
-
// Local: Wait for automatic callback
|
|
1088
|
-
const waitSpinner = ora({ text: 'Waiting for authorization...', color: 'cyan' }).start();
|
|
1089
|
-
|
|
1090
|
-
try {
|
|
1091
|
-
await proxyManager.waitForAuth(authState, 300000, (status) => {
|
|
1092
|
-
waitSpinner.text = status;
|
|
1093
|
-
});
|
|
1094
|
-
authSuccess = true;
|
|
1095
|
-
waitSpinner.succeed('Authorization successful!');
|
|
1096
|
-
} catch (error) {
|
|
1097
|
-
waitSpinner.fail(`Authorization failed: ${error.message}`);
|
|
1098
|
-
await prompts.waitForEnter();
|
|
1099
|
-
return await selectProviderOption(provider);
|
|
1100
|
-
}
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
if (!authSuccess) {
|
|
1104
|
-
return await selectProviderOption(provider);
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
// Wrap entire post-auth flow in try-catch to catch any uncaught errors
|
|
1108
|
-
try {
|
|
1109
|
-
// Step 5: Fetch models from CLIProxyAPI (filtered by provider)
|
|
1110
|
-
const modelSpinner = ora({ text: 'Fetching available models from API...', color: 'cyan' }).start();
|
|
1111
|
-
|
|
1112
|
-
let models = [];
|
|
1113
|
-
try {
|
|
1114
|
-
// Get models filtered by provider using owned_by field from API
|
|
1115
|
-
models = await proxyManager.getModels(provider.id);
|
|
1116
|
-
modelSpinner.succeed(`Found ${models.length} models for ${provider.name}`);
|
|
1117
|
-
} catch (error) {
|
|
1118
|
-
modelSpinner.fail(`Failed to fetch models: ${error.message}`);
|
|
1119
|
-
await prompts.waitForEnter();
|
|
1120
|
-
return await selectProviderOption(provider);
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
|
-
if (!models || models.length === 0) {
|
|
1124
|
-
console.log();
|
|
1125
|
-
console.log(chalk.red(' ERROR: No models found for this provider'));
|
|
1126
|
-
console.log(chalk.gray(' Make sure your subscription is active and authorization completed.'));
|
|
1127
|
-
console.log();
|
|
1128
|
-
await prompts.waitForEnter();
|
|
1129
|
-
return await selectProviderOption(provider);
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
|
-
// Step 6: Let user select model
|
|
1133
|
-
const selectedModel = await selectModelFromList(models, config.name);
|
|
1134
|
-
if (!selectedModel) {
|
|
1135
|
-
return await selectProviderOption(provider);
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
|
-
// Step 7: Save agent config (CLIProxyAPI stores the actual tokens)
|
|
1139
|
-
// We just save a reference to use the proxy
|
|
1140
|
-
const credentials = {
|
|
1141
|
-
useProxy: true,
|
|
1142
|
-
provider: provider.id
|
|
1143
|
-
};
|
|
1144
|
-
|
|
1145
|
-
try {
|
|
1146
|
-
await aiService.addAgent(provider.id, config.optionId, credentials, selectedModel, config.agentName);
|
|
1147
|
-
|
|
1148
|
-
console.log(chalk.green(`\n CONNECTED TO ${config.name}`));
|
|
1149
|
-
console.log(chalk.white(` MODEL: ${selectedModel}`));
|
|
1150
|
-
console.log(chalk.white(' UNLIMITED USAGE WITH YOUR SUBSCRIPTION'));
|
|
1151
|
-
} catch (error) {
|
|
1152
|
-
console.log(chalk.red(`\n FAILED TO SAVE: ${error.message}`));
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
|
-
await prompts.waitForEnter();
|
|
1156
|
-
return await aiAgentMenu();
|
|
1157
|
-
} catch (unexpectedError) {
|
|
1158
|
-
// Catch any uncaught errors in the post-auth flow
|
|
1159
|
-
console.log();
|
|
1160
|
-
console.log(chalk.red(` UNEXPECTED ERROR: ${unexpectedError.message}`));
|
|
1161
|
-
console.log(chalk.gray(` Stack: ${unexpectedError.stack}`));
|
|
1162
|
-
console.log();
|
|
1163
|
-
await prompts.waitForEnter();
|
|
1164
|
-
return await selectProviderOption(provider);
|
|
1165
|
-
}
|
|
1166
|
-
};
|
|
1167
|
-
|
|
1168
|
-
/**
|
|
1169
|
-
* Setup OAuth with browser redirect flow (Claude, OpenAI, Gemini, iFlow)
|
|
1170
|
-
* Legacy method - kept for API key users
|
|
1171
|
-
*/
|
|
1172
|
-
const setupBrowserOAuth = async (provider, config) => {
|
|
1173
|
-
const boxWidth = getLogoWidth();
|
|
1174
|
-
const W = boxWidth - 2;
|
|
1175
|
-
|
|
1176
|
-
const makeLine = (content) => {
|
|
1177
|
-
const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
1178
|
-
const padding = W - plainLen;
|
|
1179
|
-
return chalk.cyan('║') + ' ' + content + ' '.repeat(Math.max(0, padding - 1)) + chalk.cyan('║');
|
|
1180
|
-
};
|
|
1181
|
-
|
|
1182
|
-
console.clear();
|
|
1183
|
-
displayBanner();
|
|
1184
|
-
drawBoxHeaderContinue(`${config.name} LOGIN`, boxWidth);
|
|
1185
|
-
|
|
1186
|
-
console.log(makeLine(chalk.yellow('OAUTH AUTHENTICATION')));
|
|
1187
|
-
console.log(makeLine(''));
|
|
1188
|
-
console.log(makeLine(chalk.white('1. A BROWSER WINDOW WILL OPEN')));
|
|
1189
|
-
console.log(makeLine(chalk.white(`2. LOGIN WITH YOUR ${config.accountName.toUpperCase()} ACCOUNT`)));
|
|
1190
|
-
console.log(makeLine(chalk.white('3. COPY THE AUTHORIZATION CODE')));
|
|
1191
|
-
console.log(makeLine(chalk.white('4. PASTE IT HERE')));
|
|
1192
|
-
console.log(makeLine(''));
|
|
1193
|
-
console.log(makeLine(chalk.white('OPENING BROWSER IN 3 SECONDS...')));
|
|
1194
|
-
|
|
1195
|
-
drawBoxFooter(boxWidth);
|
|
1196
|
-
|
|
1197
|
-
// Generate OAuth URL
|
|
1198
|
-
const authResult = config.oauthModule.authorize(...config.authorizeArgs);
|
|
1199
|
-
const url = authResult.url;
|
|
1200
|
-
const verifier = authResult.verifier;
|
|
1201
|
-
const state = authResult.state;
|
|
1202
|
-
const redirectUri = authResult.redirectUri;
|
|
1203
|
-
|
|
1204
|
-
// Wait a moment then open browser
|
|
1205
|
-
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
1206
|
-
const browserOpened = await openBrowser(url);
|
|
1207
|
-
|
|
1208
|
-
// Redraw with code input
|
|
1209
|
-
console.clear();
|
|
1210
|
-
displayBanner();
|
|
1211
|
-
drawBoxHeaderContinue(`${config.name} LOGIN`, boxWidth);
|
|
1212
|
-
|
|
1213
|
-
if (browserOpened) {
|
|
1214
|
-
console.log(makeLine(chalk.green('BROWSER OPENED')));
|
|
1215
|
-
console.log(makeLine(''));
|
|
1216
|
-
console.log(makeLine(chalk.white('AFTER LOGGING IN, YOU WILL SEE A CODE')));
|
|
1217
|
-
console.log(makeLine(chalk.white('COPY THE ENTIRE CODE AND PASTE IT BELOW')));
|
|
1218
|
-
console.log(makeLine(''));
|
|
1219
|
-
if (config.codeFormat) {
|
|
1220
|
-
console.log(makeLine(chalk.white(`THE CODE LOOKS LIKE: ${config.codeFormat}`)));
|
|
1221
|
-
console.log(makeLine(''));
|
|
1222
|
-
}
|
|
1223
|
-
console.log(makeLine(chalk.white('TYPE < TO CANCEL')));
|
|
1224
|
-
drawBoxFooter(boxWidth);
|
|
1225
|
-
console.log();
|
|
1226
|
-
} else {
|
|
1227
|
-
console.log(makeLine(chalk.yellow('COULD NOT OPEN BROWSER (VPS/SSH?)')));
|
|
1228
|
-
console.log(makeLine(''));
|
|
1229
|
-
|
|
1230
|
-
// Different instructions for different providers
|
|
1231
|
-
if (provider.id === 'anthropic') {
|
|
1232
|
-
console.log(makeLine(chalk.white('AFTER LOGGING IN, YOU WILL SEE A CODE')));
|
|
1233
|
-
console.log(makeLine(chalk.white('COPY THE ENTIRE CODE AND PASTE IT BELOW')));
|
|
1234
|
-
console.log(makeLine(''));
|
|
1235
|
-
console.log(makeLine(chalk.white('THE CODE LOOKS LIKE: abc123...#xyz789...')));
|
|
1236
|
-
} else {
|
|
1237
|
-
// Gemini, OpenAI, iFlow redirect to localhost - need full URL
|
|
1238
|
-
console.log(makeLine(chalk.white('AFTER LOGGING IN, YOU WILL SEE A BLANK PAGE')));
|
|
1239
|
-
console.log(makeLine(chalk.white('THE URL WILL START WITH: localhost:...')));
|
|
1240
|
-
console.log(makeLine(''));
|
|
1241
|
-
console.log(makeLine(chalk.green('COPY THE ENTIRE URL FROM THE ADDRESS BAR')));
|
|
1242
|
-
console.log(makeLine(chalk.white('AND PASTE IT BELOW')));
|
|
1243
|
-
}
|
|
1244
|
-
console.log(makeLine(''));
|
|
1245
|
-
console.log(makeLine(chalk.white('TYPE < TO CANCEL')));
|
|
1246
|
-
drawBoxFooter(boxWidth);
|
|
1247
|
-
// Display URL outside the box for easy copy-paste (no line breaks)
|
|
1248
|
-
console.log();
|
|
1249
|
-
console.log(chalk.yellow(' OPEN THIS URL IN YOUR BROWSER:'));
|
|
1250
|
-
console.log();
|
|
1251
|
-
console.log(chalk.cyan(` ${url}`));
|
|
1252
|
-
console.log();
|
|
1253
|
-
}
|
|
1254
|
-
|
|
1255
|
-
const promptText = provider.id === 'anthropic'
|
|
1256
|
-
? 'PASTE AUTHORIZATION CODE:'
|
|
1257
|
-
: 'PASTE FULL CALLBACK URL:';
|
|
1258
|
-
|
|
1259
|
-
const input = await prompts.textInput(chalk.cyan(promptText));
|
|
1260
|
-
|
|
1261
|
-
if (!input || input === '<') {
|
|
1262
|
-
return await selectProviderOption(provider);
|
|
1263
|
-
}
|
|
1264
|
-
|
|
1265
|
-
// Extract code from URL if user pasted full callback URL
|
|
1266
|
-
let code = input.trim();
|
|
1267
|
-
if (code.includes('code=')) {
|
|
1268
|
-
try {
|
|
1269
|
-
const urlObj = new URL(code.replace('localhost', 'http://localhost'));
|
|
1270
|
-
code = urlObj.searchParams.get('code') || code;
|
|
1271
|
-
} catch (e) {
|
|
1272
|
-
// Try regex extraction as fallback
|
|
1273
|
-
const match = code.match(/[?&]code=([^&]+)/);
|
|
1274
|
-
if (match) code = match[1];
|
|
1275
|
-
}
|
|
1276
|
-
}
|
|
1277
|
-
|
|
1278
|
-
// Exchange code for tokens
|
|
1279
|
-
const spinner = ora({ text: 'EXCHANGING CODE FOR TOKENS...', color: 'cyan' }).start();
|
|
1280
|
-
|
|
1281
|
-
let result;
|
|
1282
|
-
if (provider.id === 'anthropic') {
|
|
1283
|
-
result = await config.oauthModule.exchange(code, verifier);
|
|
1284
|
-
} else if (provider.id === 'gemini') {
|
|
1285
|
-
result = await config.oauthModule.exchange(code);
|
|
1286
|
-
} else if (provider.id === 'iflow') {
|
|
1287
|
-
result = await config.oauthModule.exchange(code, redirectUri);
|
|
1288
|
-
} else {
|
|
1289
|
-
result = await config.oauthModule.exchange(code, verifier);
|
|
1290
|
-
}
|
|
1291
|
-
|
|
1292
|
-
if (result.type === 'failed') {
|
|
1293
|
-
spinner.fail(`AUTHENTICATION FAILED: ${result.error || 'Invalid code'}`);
|
|
1294
|
-
await prompts.waitForEnter();
|
|
1295
|
-
return await selectProviderOption(provider);
|
|
1296
|
-
}
|
|
1297
|
-
|
|
1298
|
-
spinner.text = 'FETCHING AVAILABLE MODELS...';
|
|
1299
|
-
|
|
1300
|
-
// Store OAuth credentials
|
|
1301
|
-
const credentials = {
|
|
1302
|
-
oauth: {
|
|
1303
|
-
access: result.access,
|
|
1304
|
-
refresh: result.refresh,
|
|
1305
|
-
expires: result.expires,
|
|
1306
|
-
apiKey: result.apiKey,
|
|
1307
|
-
email: result.email
|
|
1308
|
-
}
|
|
1309
|
-
};
|
|
1310
|
-
|
|
1311
|
-
// For iFlow, the API key is the main credential
|
|
1312
|
-
if (provider.id === 'iflow' && result.apiKey) {
|
|
1313
|
-
credentials.apiKey = result.apiKey;
|
|
1314
|
-
}
|
|
1315
|
-
|
|
1316
|
-
// Fetch available models for the provider
|
|
1317
|
-
let models = [];
|
|
1318
|
-
try {
|
|
1319
|
-
const { fetchModelsWithOAuth } = require('../services/ai/client');
|
|
1320
|
-
models = await fetchModelsWithOAuth(provider.id, result.access);
|
|
1321
|
-
} catch (e) {
|
|
1322
|
-
// Fallback to default models if fetch fails
|
|
1323
|
-
}
|
|
1324
|
-
|
|
1325
|
-
// NO hardcoded fallback - models MUST come from API
|
|
1326
|
-
// Rule: ZERO fake/mock data - API only
|
|
1327
|
-
if (!models || models.length === 0) {
|
|
1328
|
-
spinner.fail('NO MODELS AVAILABLE FROM API');
|
|
1329
|
-
console.log(chalk.red('\n Could not fetch models from provider API'));
|
|
1330
|
-
console.log(chalk.gray(' Please check your OAuth credentials or try again'));
|
|
1331
|
-
await prompts.waitForEnter();
|
|
1332
|
-
return await selectProviderOption(provider);
|
|
1333
|
-
} else {
|
|
1334
|
-
spinner.succeed(`FOUND ${models.length} MODELS`);
|
|
1335
|
-
}
|
|
1336
|
-
|
|
1337
|
-
// Let user select model
|
|
1338
|
-
const selectedModel = await selectModelFromList(models, config.name);
|
|
1339
|
-
if (!selectedModel) {
|
|
1340
|
-
return await selectProviderOption(provider);
|
|
1341
|
-
}
|
|
1342
|
-
|
|
1343
|
-
// Add agent with OAuth credentials
|
|
1344
|
-
try {
|
|
1345
|
-
await aiService.addAgent(provider.id, config.optionId, credentials, selectedModel, config.agentName);
|
|
1346
|
-
|
|
1347
|
-
console.log(chalk.green(`\n CONNECTED TO ${config.name}`));
|
|
1348
|
-
console.log(chalk.white(` MODEL: ${selectedModel}`));
|
|
1349
|
-
console.log(chalk.white(' UNLIMITED USAGE WITH YOUR SUBSCRIPTION'));
|
|
1350
|
-
} catch (error) {
|
|
1351
|
-
console.log(chalk.red(`\n FAILED TO SAVE: ${error.message}`));
|
|
1352
|
-
}
|
|
1353
|
-
|
|
1354
|
-
await prompts.waitForEnter();
|
|
1355
|
-
return await aiAgentMenu();
|
|
1356
|
-
};
|
|
1357
|
-
|
|
1358
|
-
/**
|
|
1359
|
-
* Setup OAuth with device flow (Qwen)
|
|
1360
|
-
*/
|
|
1361
|
-
const setupDeviceFlowOAuth = async (provider, config) => {
|
|
1362
|
-
const boxWidth = getLogoWidth();
|
|
1363
|
-
const W = boxWidth - 2;
|
|
1364
|
-
|
|
1365
|
-
const makeLine = (content) => {
|
|
1366
|
-
const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
1367
|
-
const padding = W - plainLen;
|
|
1368
|
-
return chalk.cyan('║') + ' ' + content + ' '.repeat(Math.max(0, padding - 1)) + chalk.cyan('║');
|
|
1369
|
-
};
|
|
1370
|
-
|
|
1371
|
-
console.clear();
|
|
1372
|
-
displayBanner();
|
|
1373
|
-
drawBoxHeaderContinue(`${config.name} LOGIN`, boxWidth);
|
|
1374
|
-
|
|
1375
|
-
console.log(makeLine(chalk.yellow('DEVICE FLOW AUTHENTICATION')));
|
|
1376
|
-
console.log(makeLine(''));
|
|
1377
|
-
console.log(makeLine(chalk.white('INITIATING DEVICE AUTHORIZATION...')));
|
|
1378
|
-
|
|
1379
|
-
drawBoxFooter(boxWidth);
|
|
1380
|
-
|
|
1381
|
-
// Initiate device flow
|
|
1382
|
-
const spinner = ora({ text: 'GETTING DEVICE CODE...', color: 'cyan' }).start();
|
|
1383
|
-
|
|
1384
|
-
const deviceResult = await config.oauthModule.initiateDeviceFlow();
|
|
1385
|
-
|
|
1386
|
-
if (deviceResult.type === 'failed') {
|
|
1387
|
-
spinner.fail(`FAILED: ${deviceResult.error}`);
|
|
1388
|
-
await prompts.waitForEnter();
|
|
1389
|
-
return await selectProviderOption(provider);
|
|
1390
|
-
}
|
|
1391
|
-
|
|
1392
|
-
spinner.stop();
|
|
1393
|
-
|
|
1394
|
-
// Show user code and verification URL
|
|
1395
|
-
console.clear();
|
|
1396
|
-
displayBanner();
|
|
1397
|
-
drawBoxHeaderContinue(`${config.name} LOGIN`, boxWidth);
|
|
1398
|
-
|
|
1399
|
-
console.log(makeLine(chalk.yellow('DEVICE FLOW AUTHENTICATION')));
|
|
1400
|
-
console.log(makeLine(''));
|
|
1401
|
-
console.log(makeLine(chalk.white('1. OPEN THE URL BELOW IN YOUR BROWSER')));
|
|
1402
|
-
console.log(makeLine(''));
|
|
1403
|
-
console.log(makeLine(chalk.white('2. ENTER THIS CODE WHEN PROMPTED:')));
|
|
1404
|
-
console.log(makeLine(''));
|
|
1405
|
-
console.log(makeLine(chalk.green.bold(` ${deviceResult.userCode}`)));
|
|
1406
|
-
console.log(makeLine(''));
|
|
1407
|
-
console.log(makeLine(chalk.white('3. AUTHORIZE THE APPLICATION')));
|
|
1408
|
-
console.log(makeLine(''));
|
|
1409
|
-
console.log(makeLine(chalk.white('WAITING FOR AUTHORIZATION...')));
|
|
1410
|
-
console.log(makeLine(chalk.white('(THIS WILL AUTO-DETECT WHEN YOU AUTHORIZE)')));
|
|
1411
|
-
|
|
1412
|
-
drawBoxFooter(boxWidth);
|
|
1413
|
-
|
|
1414
|
-
// Display URL outside the box for easy copy-paste
|
|
1415
|
-
const verificationUrl = deviceResult.verificationUriComplete || deviceResult.verificationUri;
|
|
1416
|
-
console.log();
|
|
1417
|
-
console.log(chalk.yellow(' OPEN THIS URL IN YOUR BROWSER:'));
|
|
1418
|
-
console.log();
|
|
1419
|
-
console.log(chalk.cyan(` ${verificationUrl}`));
|
|
1420
|
-
console.log();
|
|
1421
|
-
|
|
1422
|
-
// Poll for token
|
|
1423
|
-
const pollSpinner = ora({ text: 'WAITING FOR AUTHORIZATION...', color: 'cyan' }).start();
|
|
1424
|
-
|
|
1425
|
-
let pollResult;
|
|
1426
|
-
const maxAttempts = 60;
|
|
1427
|
-
let interval = deviceResult.interval || 5;
|
|
1428
|
-
|
|
1429
|
-
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
1430
|
-
await new Promise(resolve => setTimeout(resolve, interval * 1000));
|
|
1431
|
-
|
|
1432
|
-
pollResult = await config.oauthModule.pollForToken(deviceResult.deviceCode, deviceResult.verifier);
|
|
1433
|
-
|
|
1434
|
-
if (pollResult.type === 'success') {
|
|
1435
|
-
break;
|
|
1436
|
-
} else if (pollResult.type === 'slow_down') {
|
|
1437
|
-
interval = Math.min(interval + 2, 10);
|
|
1438
|
-
pollSpinner.text = `WAITING FOR AUTHORIZATION... (slowing down)`;
|
|
1439
|
-
} else if (pollResult.type === 'failed') {
|
|
1440
|
-
pollSpinner.fail(`AUTHENTICATION FAILED: ${pollResult.error}`);
|
|
1441
|
-
await prompts.waitForEnter();
|
|
1442
|
-
return await selectProviderOption(provider);
|
|
1443
|
-
}
|
|
1444
|
-
|
|
1445
|
-
pollSpinner.text = `WAITING FOR AUTHORIZATION... (${attempt + 1}/${maxAttempts})`;
|
|
1446
|
-
}
|
|
1447
|
-
|
|
1448
|
-
if (!pollResult || pollResult.type !== 'success') {
|
|
1449
|
-
pollSpinner.fail('AUTHENTICATION TIMED OUT');
|
|
1450
|
-
await prompts.waitForEnter();
|
|
1451
|
-
return await selectProviderOption(provider);
|
|
1452
|
-
}
|
|
1453
|
-
|
|
1454
|
-
pollSpinner.text = 'FETCHING AVAILABLE MODELS FROM API...';
|
|
1455
|
-
|
|
1456
|
-
// Store OAuth credentials
|
|
1457
|
-
const credentials = {
|
|
1458
|
-
oauth: {
|
|
1459
|
-
access: pollResult.access,
|
|
1460
|
-
refresh: pollResult.refresh,
|
|
1461
|
-
expires: pollResult.expires,
|
|
1462
|
-
resourceUrl: pollResult.resourceUrl
|
|
1463
|
-
}
|
|
1464
|
-
};
|
|
1465
|
-
|
|
1466
|
-
// Fetch models from API - NO hardcoded fallback (RULES.md)
|
|
1467
|
-
let models = [];
|
|
1468
|
-
let fetchError = null;
|
|
1469
|
-
try {
|
|
1470
|
-
const { fetchModelsWithOAuth } = require('../services/ai/client');
|
|
1471
|
-
models = await fetchModelsWithOAuth(provider.id, pollResult.access);
|
|
1472
|
-
} catch (e) {
|
|
1473
|
-
fetchError = e.message;
|
|
1474
|
-
}
|
|
1475
|
-
|
|
1476
|
-
if (!models || models.length === 0) {
|
|
1477
|
-
pollSpinner.fail('Could not fetch models from API');
|
|
1478
|
-
console.log();
|
|
1479
|
-
console.log(chalk.red(' ERROR: Unable to retrieve models from provider API'));
|
|
1480
|
-
console.log(chalk.white(' Possible causes:'));
|
|
1481
|
-
console.log(chalk.gray(' - OAuth token may not have permission to list models'));
|
|
1482
|
-
console.log(chalk.gray(' - Network issue or API temporarily unavailable'));
|
|
1483
|
-
if (fetchError) {
|
|
1484
|
-
console.log(chalk.gray(` - Error: ${fetchError}`));
|
|
1485
|
-
}
|
|
1486
|
-
console.log();
|
|
1487
|
-
console.log(chalk.yellow(' Please try again or use API Key authentication instead.'));
|
|
1488
|
-
await prompts.waitForEnter();
|
|
1489
|
-
return await selectProviderOption(provider);
|
|
1490
|
-
}
|
|
1491
|
-
|
|
1492
|
-
pollSpinner.succeed(`Found ${models.length} models`);
|
|
1493
|
-
|
|
1494
|
-
// Let user select model from API list
|
|
1495
|
-
const selectedModel = await selectModelFromList(models, config.name);
|
|
1496
|
-
if (!selectedModel) {
|
|
1497
|
-
return await selectProviderOption(provider);
|
|
1498
|
-
}
|
|
1499
|
-
|
|
1500
|
-
// Add agent with OAuth credentials
|
|
1501
|
-
try {
|
|
1502
|
-
await aiService.addAgent(provider.id, config.optionId, credentials, selectedModel, config.agentName);
|
|
1503
|
-
|
|
1504
|
-
console.log(chalk.green(`\n CONNECTED TO ${config.name}`));
|
|
1505
|
-
console.log(chalk.white(` MODEL: ${selectedModel}`));
|
|
1506
|
-
console.log(chalk.white(' UNLIMITED USAGE WITH YOUR SUBSCRIPTION'));
|
|
1507
|
-
} catch (error) {
|
|
1508
|
-
console.log(chalk.red(`\n FAILED TO SAVE: ${error.message}`));
|
|
1509
|
-
}
|
|
1510
|
-
|
|
1511
|
-
await prompts.waitForEnter();
|
|
1512
|
-
return await aiAgentMenu();
|
|
1513
|
-
};
|
|
1514
|
-
|
|
1515
|
-
/**
|
|
1516
|
-
* Setup connection with credentials
|
|
1517
|
-
*/
|
|
451
|
+
/** Setup connection with credentials */
|
|
1518
452
|
const setupConnection = async (provider, option) => {
|
|
1519
453
|
// Handle OAuth flow separately
|
|
1520
454
|
if (option.authType === 'oauth') {
|
|
1521
|
-
return await setupOAuthConnection(provider);
|
|
1522
|
-
}
|
|
1523
|
-
|
|
1524
|
-
const boxWidth = getLogoWidth();
|
|
1525
|
-
const W = boxWidth - 2;
|
|
1526
|
-
|
|
1527
|
-
const makeLine = (content) => {
|
|
1528
|
-
const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
1529
|
-
const padding = W - plainLen;
|
|
1530
|
-
return chalk.cyan('║') + ' ' + content + ' '.repeat(Math.max(0, padding - 1)) + chalk.cyan('║');
|
|
1531
|
-
};
|
|
1532
|
-
|
|
1533
|
-
// Collect credentials based on fields
|
|
1534
|
-
const credentials = {};
|
|
1535
|
-
|
|
1536
|
-
for (const field of option.fields) {
|
|
1537
|
-
// Show instructions for this field
|
|
1538
|
-
console.clear();
|
|
1539
|
-
displayBanner();
|
|
1540
|
-
drawBoxHeaderContinue(`CONNECT TO ${provider.name}`, boxWidth);
|
|
1541
|
-
|
|
1542
|
-
const instructions = getCredentialInstructions(provider, option, field);
|
|
1543
|
-
|
|
1544
|
-
console.log(makeLine(chalk.yellow(instructions.title)));
|
|
1545
|
-
console.log(makeLine(''));
|
|
1546
|
-
|
|
1547
|
-
// Show steps
|
|
1548
|
-
for (const step of instructions.steps) {
|
|
1549
|
-
console.log(makeLine(chalk.white(step)));
|
|
1550
|
-
}
|
|
1551
|
-
|
|
1552
|
-
console.log(makeLine(''));
|
|
1553
|
-
|
|
1554
|
-
// Show URL for reference (no browser needed - user already has API key)
|
|
1555
|
-
if (option.url && (field === 'apiKey' || field === 'sessionKey' || field === 'accessToken')) {
|
|
1556
|
-
console.log(makeLine(chalk.cyan('GET KEY: ') + chalk.white(option.url)));
|
|
1557
|
-
}
|
|
1558
|
-
|
|
1559
|
-
// Show default for endpoint
|
|
1560
|
-
if (field === 'endpoint' && option.defaultEndpoint) {
|
|
1561
|
-
console.log(makeLine(chalk.white(`DEFAULT: ${option.defaultEndpoint}`)));
|
|
1562
|
-
}
|
|
1563
|
-
|
|
1564
|
-
console.log(makeLine(''));
|
|
1565
|
-
console.log(makeLine(chalk.white('TYPE < TO GO BACK')));
|
|
1566
|
-
|
|
1567
|
-
drawBoxFooter(boxWidth);
|
|
1568
|
-
console.log();
|
|
1569
|
-
|
|
1570
|
-
let value;
|
|
1571
|
-
|
|
1572
|
-
switch (field) {
|
|
1573
|
-
case 'apiKey':
|
|
1574
|
-
value = await prompts.textInput(chalk.cyan('PASTE API KEY:'));
|
|
1575
|
-
if (!value || value.trim() === '<' || value.trim() === '') return await selectProviderOption(provider);
|
|
1576
|
-
credentials.apiKey = value.trim();
|
|
1577
|
-
break;
|
|
1578
|
-
|
|
1579
|
-
case 'sessionKey':
|
|
1580
|
-
value = await prompts.textInput(chalk.cyan('PASTE SESSION KEY:'));
|
|
1581
|
-
if (!value || value.trim() === '<' || value.trim() === '') return await selectProviderOption(provider);
|
|
1582
|
-
credentials.sessionKey = value.trim();
|
|
1583
|
-
break;
|
|
1584
|
-
|
|
1585
|
-
case 'accessToken':
|
|
1586
|
-
value = await prompts.textInput(chalk.cyan('PASTE ACCESS TOKEN:'));
|
|
1587
|
-
if (!value || value.trim() === '<' || value.trim() === '') return await selectProviderOption(provider);
|
|
1588
|
-
credentials.accessToken = value.trim();
|
|
1589
|
-
break;
|
|
1590
|
-
|
|
1591
|
-
case 'endpoint':
|
|
1592
|
-
const defaultEndpoint = option.defaultEndpoint || '';
|
|
1593
|
-
value = await prompts.textInput(chalk.cyan(`ENDPOINT [${defaultEndpoint || 'required'}]:`));
|
|
1594
|
-
if (!value || value.trim() === '<' || value.trim() === '') {
|
|
1595
|
-
if (!defaultEndpoint) return await selectProviderOption(provider);
|
|
1596
|
-
}
|
|
1597
|
-
credentials.endpoint = (value && value.trim() !== '<' ? value : defaultEndpoint).trim();
|
|
1598
|
-
if (!credentials.endpoint) return await selectProviderOption(provider);
|
|
1599
|
-
break;
|
|
1600
|
-
|
|
1601
|
-
case 'model':
|
|
1602
|
-
value = await prompts.textInput(chalk.cyan('MODEL NAME:'));
|
|
1603
|
-
if (!value || value.trim() === '<' || value.trim() === '') return await selectProviderOption(provider);
|
|
1604
|
-
credentials.model = value.trim();
|
|
1605
|
-
break;
|
|
1606
|
-
}
|
|
455
|
+
return await setupOAuthConnection(provider, selectProviderOption, selectModelFromList, aiAgentMenu);
|
|
1607
456
|
}
|
|
1608
457
|
|
|
1609
|
-
//
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
const validation = await aiService.validateConnection(provider.id, option.id, credentials);
|
|
1614
|
-
|
|
1615
|
-
if (!validation.valid) {
|
|
1616
|
-
spinner.fail(`CONNECTION FAILED: ${validation.error}`);
|
|
1617
|
-
await prompts.waitForEnter();
|
|
458
|
+
// Collect credentials
|
|
459
|
+
const credentials = await collectCredentials(provider, option);
|
|
460
|
+
if (!credentials) {
|
|
1618
461
|
return await selectProviderOption(provider);
|
|
1619
462
|
}
|
|
1620
463
|
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
const { fetchAnthropicModels, fetchGeminiModels, fetchOpenAIModels } = require('../services/ai/client');
|
|
1625
|
-
|
|
1626
|
-
let models = null;
|
|
1627
|
-
if (provider.id === 'anthropic') {
|
|
1628
|
-
models = await fetchAnthropicModels(credentials.apiKey);
|
|
1629
|
-
} else if (provider.id === 'gemini') {
|
|
1630
|
-
models = await fetchGeminiModels(credentials.apiKey);
|
|
1631
|
-
} else {
|
|
1632
|
-
const endpoint = credentials.endpoint || provider.endpoint;
|
|
1633
|
-
models = await fetchOpenAIModels(endpoint, credentials.apiKey);
|
|
1634
|
-
}
|
|
1635
|
-
|
|
1636
|
-
if (!models || models.length === 0) {
|
|
1637
|
-
spinner.fail('COULD NOT FETCH MODELS FROM API');
|
|
1638
|
-
console.log(chalk.white(' Check your API key or network connection.'));
|
|
464
|
+
// Validate and fetch models
|
|
465
|
+
const result = await validateAndFetchModels(provider, option, credentials);
|
|
466
|
+
if (!result.valid) {
|
|
1639
467
|
await prompts.waitForEnter();
|
|
1640
468
|
return await selectProviderOption(provider);
|
|
1641
469
|
}
|
|
1642
470
|
|
|
1643
|
-
spinner.succeed(`FOUND ${models.length} MODELS`);
|
|
1644
|
-
|
|
1645
471
|
// Let user select a model
|
|
1646
|
-
const selectedModel = await selectModelFromList(models, provider.name);
|
|
472
|
+
const selectedModel = await selectModelFromList(result.models, provider.name);
|
|
1647
473
|
if (!selectedModel) {
|
|
1648
474
|
return await selectProviderOption(provider);
|
|
1649
475
|
}
|
|
1650
476
|
|
|
1651
477
|
// Add as new agent with selected model
|
|
1652
|
-
|
|
1653
|
-
await aiService.addAgent(provider.id, option.id, credentials, selectedModel, provider.name);
|
|
1654
|
-
|
|
1655
|
-
console.log(chalk.green(`\n AGENT ADDED: ${provider.name}`));
|
|
1656
|
-
console.log(chalk.white(` MODEL: ${selectedModel}`));
|
|
1657
|
-
} catch (error) {
|
|
1658
|
-
console.log(chalk.red(`\n FAILED TO SAVE: ${error.message}`));
|
|
1659
|
-
}
|
|
1660
|
-
|
|
1661
|
-
await prompts.waitForEnter();
|
|
1662
|
-
return await aiAgentMenu();
|
|
1663
|
-
};
|
|
1664
|
-
|
|
1665
|
-
/**
|
|
1666
|
-
* Select model from a list (used when adding new agent)
|
|
1667
|
-
* @param {Array} models - Array of model IDs from API
|
|
1668
|
-
* @param {string} providerName - Provider name for display
|
|
1669
|
-
* @returns {string|null} Selected model ID or null if cancelled
|
|
1670
|
-
*
|
|
1671
|
-
* Data source: models array comes from provider API (/v1/models)
|
|
1672
|
-
*/
|
|
1673
|
-
const selectModelFromList = async (models, providerName) => {
|
|
1674
|
-
const boxWidth = getLogoWidth();
|
|
1675
|
-
const W = boxWidth - 2;
|
|
1676
|
-
|
|
1677
|
-
const makeLine = (content) => {
|
|
1678
|
-
const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
1679
|
-
const padding = W - plainLen;
|
|
1680
|
-
return chalk.cyan('║') + ' ' + content + ' '.repeat(Math.max(0, padding - 1)) + chalk.cyan('║');
|
|
1681
|
-
};
|
|
1682
|
-
|
|
1683
|
-
console.clear();
|
|
1684
|
-
displayBanner();
|
|
1685
|
-
drawBoxHeaderContinue(`SELECT MODEL - ${providerName}`, boxWidth);
|
|
1686
|
-
|
|
1687
|
-
if (!models || models.length === 0) {
|
|
1688
|
-
console.log(makeLine(chalk.red('NO MODELS AVAILABLE')));
|
|
1689
|
-
console.log(makeLine(chalk.white('[<] BACK')));
|
|
1690
|
-
drawBoxFooter(boxWidth);
|
|
1691
|
-
await prompts.waitForEnter();
|
|
1692
|
-
return null;
|
|
1693
|
-
}
|
|
1694
|
-
|
|
1695
|
-
// Sort models (newest first)
|
|
1696
|
-
const sortedModels = [...models].sort((a, b) => b.localeCompare(a));
|
|
1697
|
-
|
|
1698
|
-
// Display models in 2 columns
|
|
1699
|
-
const rows = Math.ceil(sortedModels.length / 2);
|
|
1700
|
-
const colWidth = Math.floor((W - 4) / 2);
|
|
1701
|
-
|
|
1702
|
-
for (let i = 0; i < rows; i++) {
|
|
1703
|
-
const leftIndex = i;
|
|
1704
|
-
const rightIndex = i + rows;
|
|
1705
|
-
|
|
1706
|
-
// Left column
|
|
1707
|
-
const leftModel = sortedModels[leftIndex];
|
|
1708
|
-
const leftNum = chalk.cyan(`[${leftIndex + 1}]`);
|
|
1709
|
-
const leftName = leftModel.length > colWidth - 6
|
|
1710
|
-
? leftModel.substring(0, colWidth - 9) + '...'
|
|
1711
|
-
: leftModel;
|
|
1712
|
-
const leftText = `${leftNum} ${chalk.yellow(leftName)}`;
|
|
1713
|
-
const leftPlain = `[${leftIndex + 1}] ${leftName}`;
|
|
1714
|
-
|
|
1715
|
-
// Right column (if exists)
|
|
1716
|
-
let rightText = '';
|
|
1717
|
-
let rightPlain = '';
|
|
1718
|
-
if (rightIndex < sortedModels.length) {
|
|
1719
|
-
const rightModel = sortedModels[rightIndex];
|
|
1720
|
-
const rightNum = chalk.cyan(`[${rightIndex + 1}]`);
|
|
1721
|
-
const rightName = rightModel.length > colWidth - 6
|
|
1722
|
-
? rightModel.substring(0, colWidth - 9) + '...'
|
|
1723
|
-
: rightModel;
|
|
1724
|
-
rightText = `${rightNum} ${chalk.yellow(rightName)}`;
|
|
1725
|
-
rightPlain = `[${rightIndex + 1}] ${rightName}`;
|
|
1726
|
-
}
|
|
1727
|
-
|
|
1728
|
-
// Pad left column and combine
|
|
1729
|
-
const leftPadding = colWidth - leftPlain.length;
|
|
1730
|
-
const line = leftText + ' '.repeat(Math.max(2, leftPadding)) + rightText;
|
|
1731
|
-
console.log(makeLine(line));
|
|
1732
|
-
}
|
|
1733
|
-
|
|
1734
|
-
console.log(makeLine(''));
|
|
1735
|
-
console.log(makeLine(chalk.white('[<] BACK')));
|
|
1736
|
-
|
|
1737
|
-
drawBoxFooter(boxWidth);
|
|
1738
|
-
|
|
1739
|
-
const choice = await prompts.textInput(chalk.cyan('SELECT MODEL:'));
|
|
1740
|
-
|
|
1741
|
-
// Empty input or < = go back
|
|
1742
|
-
if (!choice || choice.trim() === '' || choice === '<' || choice?.toLowerCase() === 'b') {
|
|
1743
|
-
return null;
|
|
1744
|
-
}
|
|
1745
|
-
|
|
1746
|
-
const index = parseInt(choice) - 1;
|
|
1747
|
-
if (isNaN(index) || index < 0 || index >= sortedModels.length) {
|
|
1748
|
-
return await selectModelFromList(models, providerName);
|
|
1749
|
-
}
|
|
1750
|
-
|
|
1751
|
-
return sortedModels[index];
|
|
1752
|
-
};
|
|
1753
|
-
|
|
1754
|
-
/**
|
|
1755
|
-
* Select/change model for an agent
|
|
1756
|
-
* Fetches available models from the provider's API
|
|
1757
|
-
*/
|
|
1758
|
-
const selectModel = async (agent) => {
|
|
1759
|
-
const boxWidth = getLogoWidth();
|
|
1760
|
-
const W = boxWidth - 2;
|
|
1761
|
-
|
|
1762
|
-
const makeLine = (content) => {
|
|
1763
|
-
const plainLen = content.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
1764
|
-
const padding = W - plainLen;
|
|
1765
|
-
return chalk.cyan('║') + ' ' + content + ' '.repeat(Math.max(0, padding - 1)) + chalk.cyan('║');
|
|
1766
|
-
};
|
|
1767
|
-
|
|
1768
|
-
console.clear();
|
|
1769
|
-
displayBanner();
|
|
1770
|
-
drawBoxHeaderContinue(`SELECT MODEL - ${agent.name}`, boxWidth);
|
|
1771
|
-
|
|
1772
|
-
console.log(makeLine(chalk.white('FETCHING AVAILABLE MODELS FROM API...')));
|
|
1773
|
-
drawBoxFooter(boxWidth);
|
|
1774
|
-
|
|
1775
|
-
// Fetch models from real API
|
|
1776
|
-
const { fetchAnthropicModels, fetchAnthropicModelsOAuth, fetchGeminiModels, fetchOpenAIModels } = require('../services/ai/client');
|
|
1777
|
-
|
|
1778
|
-
let models = null;
|
|
1779
|
-
const agentCredentials = aiService.getAgentCredentials(agent.id);
|
|
1780
|
-
|
|
1781
|
-
if (agent.providerId === 'anthropic') {
|
|
1782
|
-
// Check if OAuth credentials or OAuth-like token (sk-ant-oat...)
|
|
1783
|
-
const token = agentCredentials?.apiKey || agentCredentials?.accessToken || agentCredentials?.sessionKey;
|
|
1784
|
-
const isOAuthToken = agentCredentials?.oauth?.access || (token && token.startsWith('sk-ant-oat'));
|
|
1785
|
-
|
|
1786
|
-
if (isOAuthToken) {
|
|
1787
|
-
// Use OAuth endpoint with Bearer token
|
|
1788
|
-
const accessToken = agentCredentials?.oauth?.access || token;
|
|
1789
|
-
models = await fetchAnthropicModelsOAuth(accessToken);
|
|
1790
|
-
} else {
|
|
1791
|
-
// Standard API key
|
|
1792
|
-
models = await fetchAnthropicModels(token);
|
|
1793
|
-
}
|
|
1794
|
-
} else if (agent.providerId === 'gemini') {
|
|
1795
|
-
// Google Gemini API
|
|
1796
|
-
models = await fetchGeminiModels(agentCredentials?.apiKey);
|
|
1797
|
-
} else {
|
|
1798
|
-
// OpenAI-compatible providers
|
|
1799
|
-
const endpoint = agentCredentials?.endpoint || agent.provider?.endpoint;
|
|
1800
|
-
models = await fetchOpenAIModels(endpoint, agentCredentials?.apiKey);
|
|
1801
|
-
}
|
|
1802
|
-
|
|
1803
|
-
// Redraw with results
|
|
1804
|
-
console.clear();
|
|
1805
|
-
displayBanner();
|
|
1806
|
-
drawBoxHeaderContinue(`SELECT MODEL - ${agent.name}`, boxWidth);
|
|
1807
|
-
|
|
1808
|
-
if (!models || models.length === 0) {
|
|
1809
|
-
console.log(makeLine(chalk.red('COULD NOT FETCH MODELS FROM API')));
|
|
1810
|
-
console.log(makeLine(chalk.white('Check your API key or network connection.')));
|
|
1811
|
-
console.log(makeLine(''));
|
|
1812
|
-
console.log(makeLine(chalk.white('[<] BACK')));
|
|
1813
|
-
drawBoxFooter(boxWidth);
|
|
1814
|
-
|
|
1815
|
-
await prompts.waitForEnter();
|
|
1816
|
-
return await aiAgentMenu();
|
|
1817
|
-
}
|
|
1818
|
-
|
|
1819
|
-
// Sort models (newest first typically)
|
|
1820
|
-
models.sort((a, b) => b.localeCompare(a));
|
|
1821
|
-
|
|
1822
|
-
// Display models in 2 columns
|
|
1823
|
-
const rows = Math.ceil(models.length / 2);
|
|
1824
|
-
const colWidth = Math.floor((W - 4) / 2);
|
|
1825
|
-
|
|
1826
|
-
for (let i = 0; i < rows; i++) {
|
|
1827
|
-
const leftIndex = i;
|
|
1828
|
-
const rightIndex = i + rows;
|
|
1829
|
-
|
|
1830
|
-
// Left column
|
|
1831
|
-
const leftModel = models[leftIndex];
|
|
1832
|
-
const leftNum = chalk.cyan(`[${leftIndex + 1}]`);
|
|
1833
|
-
const leftCurrent = leftModel === agent.model ? chalk.green(' *') : '';
|
|
1834
|
-
const leftName = leftModel.length > colWidth - 8
|
|
1835
|
-
? leftModel.substring(0, colWidth - 11) + '...'
|
|
1836
|
-
: leftModel;
|
|
1837
|
-
const leftText = `${leftNum} ${chalk.yellow(leftName)}${leftCurrent}`;
|
|
1838
|
-
const leftPlain = `[${leftIndex + 1}] ${leftName}${leftModel === agent.model ? ' *' : ''}`;
|
|
1839
|
-
|
|
1840
|
-
// Right column (if exists)
|
|
1841
|
-
let rightText = '';
|
|
1842
|
-
let rightPlain = '';
|
|
1843
|
-
if (rightIndex < models.length) {
|
|
1844
|
-
const rightModel = models[rightIndex];
|
|
1845
|
-
const rightNum = chalk.cyan(`[${rightIndex + 1}]`);
|
|
1846
|
-
const rightCurrent = rightModel === agent.model ? chalk.green(' *') : '';
|
|
1847
|
-
const rightName = rightModel.length > colWidth - 8
|
|
1848
|
-
? rightModel.substring(0, colWidth - 11) + '...'
|
|
1849
|
-
: rightModel;
|
|
1850
|
-
rightText = `${rightNum} ${chalk.yellow(rightName)}${rightCurrent}`;
|
|
1851
|
-
rightPlain = `[${rightIndex + 1}] ${rightName}${rightModel === agent.model ? ' *' : ''}`;
|
|
1852
|
-
}
|
|
1853
|
-
|
|
1854
|
-
// Pad left column and combine
|
|
1855
|
-
const leftPadding = colWidth - leftPlain.length;
|
|
1856
|
-
const line = leftText + ' '.repeat(Math.max(2, leftPadding)) + rightText;
|
|
1857
|
-
console.log(makeLine(line));
|
|
1858
|
-
}
|
|
1859
|
-
|
|
1860
|
-
console.log(makeLine(''));
|
|
1861
|
-
console.log(makeLine(chalk.white('[<] BACK') + chalk.white(' * = CURRENT')));
|
|
1862
|
-
|
|
1863
|
-
drawBoxFooter(boxWidth);
|
|
1864
|
-
|
|
1865
|
-
const choice = await prompts.textInput(chalk.cyan('SELECT MODEL:'));
|
|
1866
|
-
|
|
1867
|
-
// Empty input or < = go back
|
|
1868
|
-
if (!choice || choice.trim() === '' || choice === '<' || choice?.toLowerCase() === 'b') {
|
|
1869
|
-
return await aiAgentMenu();
|
|
1870
|
-
}
|
|
1871
|
-
|
|
1872
|
-
const index = parseInt(choice) - 1;
|
|
1873
|
-
if (isNaN(index) || index < 0 || index >= models.length) {
|
|
1874
|
-
return await selectModel(agent);
|
|
1875
|
-
}
|
|
1876
|
-
|
|
1877
|
-
const selectedModel = models[index];
|
|
1878
|
-
aiService.updateAgent(agent.id, { model: selectedModel });
|
|
478
|
+
await addConnectedAgent(provider, option, credentials, selectedModel);
|
|
1879
479
|
|
|
1880
|
-
console.log(chalk.green(`\n MODEL CHANGED TO: ${selectedModel}`));
|
|
1881
480
|
await prompts.waitForEnter();
|
|
1882
481
|
return await aiAgentMenu();
|
|
1883
482
|
};
|