recker 1.0.34 → 1.0.35-next.1b43fea
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/browser/core/client.js +11 -1
- package/dist/browser/core/error-handler.d.ts +44 -0
- package/dist/browser/core/error-handler.js +520 -0
- package/dist/browser/core/request-promise.d.ts +4 -0
- package/dist/browser/core/request-promise.js +12 -0
- package/dist/browser/core/response.d.ts +4 -0
- package/dist/browser/core/response.js +10 -0
- package/dist/browser/plugins/csv.d.ts +42 -0
- package/dist/browser/plugins/csv.js +386 -0
- package/dist/browser/plugins/hls.js +12 -4
- package/dist/browser/plugins/yaml.d.ts +28 -0
- package/dist/browser/plugins/yaml.js +730 -0
- package/dist/cli/helpers.d.ts +54 -0
- package/dist/cli/helpers.js +548 -0
- package/dist/cli/index.js +67 -28
- package/dist/cli/presets.d.ts +5 -1
- package/dist/cli/presets.js +34 -10
- package/dist/cli/tui/ai-chat.d.ts +6 -1
- package/dist/cli/tui/ai-chat.js +267 -63
- package/dist/cli/tui/shell.d.ts +1 -0
- package/dist/cli/tui/shell.js +706 -36
- package/dist/core/client.js +11 -1
- package/dist/core/error-handler.d.ts +44 -0
- package/dist/core/error-handler.js +520 -0
- package/dist/core/request-promise.d.ts +4 -0
- package/dist/core/request-promise.js +12 -0
- package/dist/core/response.d.ts +4 -0
- package/dist/core/response.js +10 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/plugins/csv.d.ts +42 -0
- package/dist/plugins/csv.js +386 -0
- package/dist/plugins/hls.js +12 -4
- package/dist/plugins/yaml.d.ts +28 -0
- package/dist/plugins/yaml.js +730 -0
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { promises as fs } from 'node:fs';
|
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import colors from '../utils/colors.js';
|
|
6
6
|
import { formatColumns } from '../utils/columns.js';
|
|
7
|
+
import { summarizeErrors, formatErrorSummary } from './helpers.js';
|
|
7
8
|
async function readStdin() {
|
|
8
9
|
if (process.stdin.isTTY) {
|
|
9
10
|
return null;
|
|
@@ -1041,9 +1042,22 @@ ${colors.bold(colors.yellow('Focus Modes:'))}
|
|
|
1041
1042
|
const maxResponseTime = responseTimes.length > 0 ? Math.max(...responseTimes) : 0;
|
|
1042
1043
|
const reqPerSec = result.duration > 0 ? (result.pages.length / (result.duration / 1000)).toFixed(1) : '0';
|
|
1043
1044
|
const statusCounts = new Map();
|
|
1045
|
+
const errorDetails = [];
|
|
1044
1046
|
for (const page of result.pages) {
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
+
if (page.status && page.status > 0) {
|
|
1048
|
+
const key = page.status.toString();
|
|
1049
|
+
statusCounts.set(key, (statusCounts.get(key) || 0) + 1);
|
|
1050
|
+
}
|
|
1051
|
+
else if (page.error) {
|
|
1052
|
+
const { classifyError } = await import('./helpers.js');
|
|
1053
|
+
const classified = classifyError(page.error);
|
|
1054
|
+
const key = classified.category.type;
|
|
1055
|
+
statusCounts.set(key, (statusCounts.get(key) || 0) + 1);
|
|
1056
|
+
errorDetails.push({ url: page.url, error: page.error, status: page.status || 0 });
|
|
1057
|
+
}
|
|
1058
|
+
else {
|
|
1059
|
+
statusCounts.set('UNKNOWN', (statusCounts.get('UNKNOWN') || 0) + 1);
|
|
1060
|
+
}
|
|
1047
1061
|
}
|
|
1048
1062
|
let totalInternalLinks = 0;
|
|
1049
1063
|
let totalExternalLinks = 0;
|
|
@@ -1069,12 +1083,55 @@ ${colors.bold(colors.yellow('Focus Modes:'))}
|
|
|
1069
1083
|
console.log(` ${colors.gray('Throughput:')} ${reqPerSec} req/s`);
|
|
1070
1084
|
console.log(colors.bold('\n HTTP Status:'));
|
|
1071
1085
|
const sortedStatuses = Array.from(statusCounts.entries()).sort((a, b) => b[1] - a[1]);
|
|
1072
|
-
for (const [
|
|
1073
|
-
const
|
|
1074
|
-
const
|
|
1075
|
-
|
|
1086
|
+
for (const [statusKey, count] of sortedStatuses.slice(0, 8)) {
|
|
1087
|
+
const statusNum = parseInt(statusKey);
|
|
1088
|
+
const isHttpStatus = !isNaN(statusNum) && statusNum > 0;
|
|
1089
|
+
let statusLabel;
|
|
1090
|
+
let statusColor;
|
|
1091
|
+
if (isHttpStatus) {
|
|
1092
|
+
statusLabel = statusNum.toString();
|
|
1093
|
+
statusColor = statusNum >= 500 ? colors.red :
|
|
1094
|
+
statusNum >= 400 ? colors.yellow :
|
|
1095
|
+
statusNum >= 300 ? colors.cyan : colors.green;
|
|
1096
|
+
}
|
|
1097
|
+
else {
|
|
1098
|
+
statusLabel = statusKey;
|
|
1099
|
+
statusColor = statusKey.startsWith('HTTP_4') ? colors.yellow :
|
|
1100
|
+
statusKey.startsWith('HTTP_5') ? colors.red :
|
|
1101
|
+
statusKey === 'TIMEOUT' ? colors.yellow :
|
|
1102
|
+
colors.red;
|
|
1103
|
+
}
|
|
1076
1104
|
const pct = ((count / result.pages.length) * 100).toFixed(0);
|
|
1077
|
-
console.log(` ${statusColor(statusLabel.padEnd(
|
|
1105
|
+
console.log(` ${statusColor(statusLabel.padEnd(12))} ${count.toString().padStart(3)} (${pct}%)`);
|
|
1106
|
+
}
|
|
1107
|
+
if (errorDetails.length > 0) {
|
|
1108
|
+
console.log(colors.bold('\n Errors:'));
|
|
1109
|
+
const grouped = new Map();
|
|
1110
|
+
for (const err of errorDetails) {
|
|
1111
|
+
const { classifyError } = await import('./helpers.js');
|
|
1112
|
+
const classified = classifyError(err.error);
|
|
1113
|
+
const key = classified.category.type;
|
|
1114
|
+
if (!grouped.has(key))
|
|
1115
|
+
grouped.set(key, []);
|
|
1116
|
+
grouped.get(key).push(err);
|
|
1117
|
+
}
|
|
1118
|
+
for (const [type, errs] of grouped) {
|
|
1119
|
+
const paths = errs.map(e => {
|
|
1120
|
+
try {
|
|
1121
|
+
return new URL(e.url).pathname;
|
|
1122
|
+
}
|
|
1123
|
+
catch {
|
|
1124
|
+
return e.url;
|
|
1125
|
+
}
|
|
1126
|
+
});
|
|
1127
|
+
console.log(` ${colors.red(type.padEnd(16))} ${errs.length} page${errs.length > 1 ? 's' : ''}`);
|
|
1128
|
+
for (const path of paths.slice(0, 3)) {
|
|
1129
|
+
console.log(` ${colors.gray('→')} ${path.slice(0, 60)}`);
|
|
1130
|
+
}
|
|
1131
|
+
if (paths.length > 3) {
|
|
1132
|
+
console.log(` ${colors.gray(`... and ${paths.length - 3} more`)}`);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1078
1135
|
}
|
|
1079
1136
|
console.log(colors.bold('\n Content:'));
|
|
1080
1137
|
console.log(` ${colors.gray('Internal links:')} ${totalInternalLinks.toLocaleString()}`);
|
|
@@ -1181,27 +1238,9 @@ ${colors.bold(colors.yellow('Focus Modes:'))}
|
|
|
1181
1238
|
console.log(` ${colors.cyan(page.links.length.toString().padStart(3))} ${title}`);
|
|
1182
1239
|
}
|
|
1183
1240
|
}
|
|
1184
|
-
|
|
1185
|
-
const
|
|
1186
|
-
|
|
1187
|
-
return `HTTP ${statusMatch[1]}`;
|
|
1188
|
-
}
|
|
1189
|
-
return error.length > 50 ? error.slice(0, 47) + '...' : error;
|
|
1190
|
-
};
|
|
1191
|
-
if (result.errors.length > 0 && result.errors.length <= 10) {
|
|
1192
|
-
console.log(colors.bold('\n Errors:'));
|
|
1193
|
-
for (const err of result.errors) {
|
|
1194
|
-
const path = new URL(err.url).pathname;
|
|
1195
|
-
console.log(` ${colors.red('✗')} ${path.padEnd(30)} → ${formatError(err.error)}`);
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
else if (result.errors.length > 10) {
|
|
1199
|
-
console.log(colors.bold('\n Errors:'));
|
|
1200
|
-
for (const err of result.errors.slice(0, 5)) {
|
|
1201
|
-
const path = new URL(err.url).pathname;
|
|
1202
|
-
console.log(` ${colors.red('✗')} ${path.padEnd(30)} → ${formatError(err.error)}`);
|
|
1203
|
-
}
|
|
1204
|
-
console.log(colors.gray(` ... and ${result.errors.length - 5} more errors`));
|
|
1241
|
+
if (result.errors.length > 0) {
|
|
1242
|
+
const errorSummary = summarizeErrors(result.errors);
|
|
1243
|
+
console.log(formatErrorSummary(errorSummary));
|
|
1205
1244
|
}
|
|
1206
1245
|
if (outputFile) {
|
|
1207
1246
|
const jsonOutput = {
|
package/dist/cli/presets.d.ts
CHANGED
package/dist/cli/presets.js
CHANGED
|
@@ -10,38 +10,62 @@ const ENV_MAPPING = {
|
|
|
10
10
|
slack: ['SLACK_TOKEN'],
|
|
11
11
|
vercel: ['VERCEL_TOKEN'],
|
|
12
12
|
supabase: ['SUPABASE_URL', 'SUPABASE_KEY'],
|
|
13
|
+
groq: ['GROQ_API_KEY'],
|
|
14
|
+
google: ['GOOGLE_API_KEY'],
|
|
15
|
+
xai: ['XAI_API_KEY'],
|
|
16
|
+
mistral: ['MISTRAL_API_KEY'],
|
|
17
|
+
cohere: ['COHERE_API_KEY'],
|
|
18
|
+
deepseek: ['DEEPSEEK_API_KEY'],
|
|
19
|
+
fireworks: ['FIREWORKS_API_KEY'],
|
|
20
|
+
together: ['TOGETHER_API_KEY'],
|
|
21
|
+
perplexity: ['PERPLEXITY_API_KEY'],
|
|
13
22
|
};
|
|
14
|
-
export function resolvePreset(name) {
|
|
23
|
+
export function resolvePreset(name, options = {}) {
|
|
24
|
+
const { throwOnError = false, silent = false } = options;
|
|
15
25
|
const presetFn = presets[name];
|
|
16
26
|
if (!presetFn) {
|
|
17
|
-
|
|
18
|
-
|
|
27
|
+
const msg = `Unknown preset '@${name}'`;
|
|
28
|
+
if (throwOnError) {
|
|
29
|
+
throw new Error(msg);
|
|
30
|
+
}
|
|
31
|
+
if (!silent) {
|
|
32
|
+
console.error(colors.red(`Error: ${msg}`));
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
19
35
|
}
|
|
20
36
|
const requiredEnvs = ENV_MAPPING[name];
|
|
21
|
-
const
|
|
37
|
+
const presetOptions = {};
|
|
22
38
|
if (requiredEnvs) {
|
|
23
39
|
let missing = false;
|
|
24
40
|
for (const envVar of requiredEnvs) {
|
|
25
41
|
const value = process.env[envVar];
|
|
26
42
|
if (!value) {
|
|
27
|
-
|
|
43
|
+
if (!silent) {
|
|
44
|
+
console.error(colors.yellow(`Warning: Missing env variable ${envVar} for preset @${name}`));
|
|
45
|
+
}
|
|
28
46
|
missing = true;
|
|
29
47
|
}
|
|
30
48
|
else {
|
|
31
49
|
const key = mapEnvToOption(name, envVar);
|
|
32
|
-
|
|
50
|
+
presetOptions[key] = value;
|
|
33
51
|
}
|
|
34
52
|
}
|
|
35
|
-
if (missing) {
|
|
53
|
+
if (missing && !silent) {
|
|
36
54
|
console.log(colors.gray(`Tip: export ${requiredEnvs.join('=... ')}=...`));
|
|
37
55
|
}
|
|
38
56
|
}
|
|
39
57
|
try {
|
|
40
|
-
return presetFn(
|
|
58
|
+
return presetFn(presetOptions);
|
|
41
59
|
}
|
|
42
60
|
catch (error) {
|
|
43
|
-
|
|
44
|
-
|
|
61
|
+
const msg = `Error initializing preset @${name}: ${error.message}`;
|
|
62
|
+
if (throwOnError) {
|
|
63
|
+
throw new Error(msg);
|
|
64
|
+
}
|
|
65
|
+
if (!silent) {
|
|
66
|
+
console.error(colors.red(msg));
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
45
69
|
}
|
|
46
70
|
}
|
|
47
71
|
function mapEnvToOption(preset, env) {
|
|
@@ -1,2 +1,7 @@
|
|
|
1
1
|
import readline from 'node:readline';
|
|
2
|
-
|
|
2
|
+
import type { Client } from '../../core/client.js';
|
|
3
|
+
export interface AIMode {
|
|
4
|
+
aiClients: Map<string, Client>;
|
|
5
|
+
variables?: Record<string, any>;
|
|
6
|
+
}
|
|
7
|
+
export declare function startAIChat(rl: readline.Interface, provider: string | undefined, context: AIMode): Promise<void>;
|
package/dist/cli/tui/ai-chat.js
CHANGED
|
@@ -1,100 +1,304 @@
|
|
|
1
1
|
import readline from 'node:readline';
|
|
2
2
|
import colors from '../../utils/colors.js';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
3
|
+
import { createClient } from '../../core/client.js';
|
|
4
|
+
import { resolvePreset } from '../presets.js';
|
|
5
|
+
const AI_PRESETS = [
|
|
6
|
+
'openai', 'anthropic', 'groq', 'google', 'xai',
|
|
7
|
+
'mistral', 'cohere', 'deepseek', 'fireworks', 'together', 'perplexity'
|
|
8
|
+
];
|
|
9
|
+
const ENV_VAR_MAP = {
|
|
10
|
+
openai: 'OPENAI_API_KEY',
|
|
11
|
+
anthropic: 'ANTHROPIC_API_KEY',
|
|
12
|
+
google: 'GOOGLE_API_KEY',
|
|
13
|
+
groq: 'GROQ_API_KEY',
|
|
14
|
+
xai: 'XAI_API_KEY',
|
|
15
|
+
mistral: 'MISTRAL_API_KEY',
|
|
16
|
+
cohere: 'COHERE_API_KEY',
|
|
17
|
+
deepseek: 'DEEPSEEK_API_KEY',
|
|
18
|
+
fireworks: 'FIREWORKS_API_KEY',
|
|
19
|
+
together: 'TOGETHER_API_KEY',
|
|
20
|
+
perplexity: 'PERPLEXITY_API_KEY',
|
|
21
|
+
};
|
|
22
|
+
export async function startAIChat(rl, provider = 'openai', context) {
|
|
23
|
+
let currentProvider = provider.toLowerCase();
|
|
24
|
+
if (!AI_PRESETS.includes(currentProvider)) {
|
|
25
|
+
console.log(colors.red(`Unknown AI provider: ${currentProvider}`));
|
|
26
|
+
console.log(colors.gray(`Available: ${AI_PRESETS.join(', ')}`));
|
|
15
27
|
return;
|
|
16
28
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const history = [
|
|
25
|
-
{ role: 'system', content: 'You are Recker AI, a helpful and concise assistant in a terminal environment.' }
|
|
26
|
-
];
|
|
27
|
-
rl.setPrompt(colors.magenta('You › '));
|
|
29
|
+
console.clear();
|
|
30
|
+
printHeader(currentProvider);
|
|
31
|
+
let client = await getOrCreateClient(currentProvider, context);
|
|
32
|
+
if (!client)
|
|
33
|
+
return;
|
|
34
|
+
const getPrompt = () => colors.magenta(`${currentProvider} › `);
|
|
35
|
+
rl.setPrompt(getPrompt());
|
|
28
36
|
rl.prompt();
|
|
37
|
+
let isGenerating = false;
|
|
38
|
+
let abortController = null;
|
|
29
39
|
return new Promise((resolve) => {
|
|
40
|
+
const cleanup = () => {
|
|
41
|
+
rl.off('line', onLine);
|
|
42
|
+
rl.off('SIGINT', onSigInt);
|
|
43
|
+
if (process.stdin.isTTY) {
|
|
44
|
+
process.stdin.off('keypress', onKeypress);
|
|
45
|
+
}
|
|
46
|
+
console.log('');
|
|
47
|
+
console.log(colors.gray('Exiting AI mode...'));
|
|
48
|
+
};
|
|
30
49
|
const onLine = async (line) => {
|
|
31
50
|
const input = line.trim();
|
|
32
51
|
if (!input) {
|
|
33
52
|
rl.prompt();
|
|
34
53
|
return;
|
|
35
54
|
}
|
|
36
|
-
if (input.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
55
|
+
if (input.startsWith('/')) {
|
|
56
|
+
const handled = await handleCommand(input, currentProvider, client, context, rl, () => {
|
|
57
|
+
cleanup();
|
|
58
|
+
resolve();
|
|
59
|
+
}, async (newProvider) => {
|
|
60
|
+
currentProvider = newProvider;
|
|
61
|
+
client = await getOrCreateClient(currentProvider, context);
|
|
62
|
+
if (client) {
|
|
63
|
+
rl.setPrompt(getPrompt());
|
|
64
|
+
console.log(colors.green(`Switched to ${currentProvider}`));
|
|
65
|
+
const memory = client.ai.getMemory();
|
|
66
|
+
if (memory.length > 0) {
|
|
67
|
+
console.log(colors.gray(`Memory: ${Math.floor(memory.length / 2)} pairs`));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
rl.prompt();
|
|
71
|
+
});
|
|
72
|
+
if (handled)
|
|
73
|
+
return;
|
|
42
74
|
}
|
|
43
|
-
if (
|
|
44
|
-
|
|
45
|
-
|
|
75
|
+
if (!client || !client.hasAI) {
|
|
76
|
+
console.log(colors.red('AI client not available'));
|
|
77
|
+
rl.prompt();
|
|
46
78
|
return;
|
|
47
79
|
}
|
|
48
|
-
history.push({ role: 'user', content: input });
|
|
49
80
|
rl.pause();
|
|
50
|
-
|
|
51
|
-
|
|
81
|
+
isGenerating = true;
|
|
82
|
+
abortController = new AbortController();
|
|
83
|
+
const model = client._aiConfig?.model || currentProvider;
|
|
84
|
+
process.stdout.write(colors.gray(`${model} › `));
|
|
52
85
|
try {
|
|
53
|
-
const stream = await client.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if (
|
|
61
|
-
|
|
62
|
-
|
|
86
|
+
const stream = await client.ai.chatStream(input);
|
|
87
|
+
let hasContent = false;
|
|
88
|
+
for await (const event of stream) {
|
|
89
|
+
if (abortController?.signal.aborted) {
|
|
90
|
+
console.log(colors.yellow('\n[Interrupted]'));
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
if (event.type === 'text') {
|
|
94
|
+
if (!hasContent) {
|
|
95
|
+
process.stdout.write('\n' + colors.orange(event.content));
|
|
96
|
+
hasContent = true;
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
process.stdout.write(colors.orange(event.content));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else if (event.type === 'error') {
|
|
103
|
+
console.log(colors.red(`\nError: ${event.error}`));
|
|
63
104
|
}
|
|
64
105
|
}
|
|
65
|
-
|
|
66
|
-
|
|
106
|
+
console.log(colors.reset(''));
|
|
107
|
+
const memory = client.ai.getMemory();
|
|
108
|
+
const pairs = Math.floor(memory.length / 2);
|
|
109
|
+
console.log(colors.gray(`Memory: ${pairs}/12 pairs`));
|
|
67
110
|
}
|
|
68
111
|
catch (error) {
|
|
69
|
-
|
|
70
|
-
Error: ${error.message}`));
|
|
71
|
-
if (error.cause)
|
|
72
|
-
console.log(colors.gray(error.cause));
|
|
112
|
+
handleError(error, currentProvider);
|
|
73
113
|
}
|
|
74
114
|
finally {
|
|
115
|
+
isGenerating = false;
|
|
116
|
+
abortController = null;
|
|
75
117
|
rl.resume();
|
|
76
118
|
rl.prompt();
|
|
77
119
|
}
|
|
78
120
|
};
|
|
79
|
-
const cleanup = () => {
|
|
80
|
-
rl.off('line', onLine);
|
|
81
|
-
rl.off('SIGINT', onSigInt);
|
|
82
|
-
process.stdin.off('keypress', onKeypress);
|
|
83
|
-
};
|
|
84
|
-
if (process.stdin.isTTY)
|
|
85
|
-
readline.emitKeypressEvents(process.stdin);
|
|
86
121
|
const onKeypress = (_str, key) => {
|
|
87
122
|
if (key && key.name === 'escape') {
|
|
88
|
-
|
|
89
|
-
|
|
123
|
+
if (isGenerating && abortController) {
|
|
124
|
+
abortController.abort();
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
cleanup();
|
|
128
|
+
resolve();
|
|
129
|
+
}
|
|
90
130
|
}
|
|
91
131
|
};
|
|
92
|
-
process.stdin.
|
|
132
|
+
if (process.stdin.isTTY) {
|
|
133
|
+
readline.emitKeypressEvents(process.stdin);
|
|
134
|
+
process.stdin.on('keypress', onKeypress);
|
|
135
|
+
}
|
|
93
136
|
rl.on('line', onLine);
|
|
94
137
|
const onSigInt = () => {
|
|
95
|
-
|
|
96
|
-
|
|
138
|
+
if (isGenerating && abortController) {
|
|
139
|
+
abortController.abort();
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
cleanup();
|
|
143
|
+
resolve();
|
|
144
|
+
}
|
|
97
145
|
};
|
|
98
146
|
rl.once('SIGINT', onSigInt);
|
|
99
147
|
});
|
|
100
148
|
}
|
|
149
|
+
async function getOrCreateClient(provider, context) {
|
|
150
|
+
let client = context.aiClients.get(provider);
|
|
151
|
+
if (client) {
|
|
152
|
+
return client;
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
const presetConfig = resolvePreset(provider);
|
|
156
|
+
if (!presetConfig) {
|
|
157
|
+
console.log(colors.red(`Unknown preset: ${provider}`));
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
if (!presetConfig._aiConfig) {
|
|
161
|
+
console.log(colors.red(`Preset ${provider} does not support AI features.`));
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
client = createClient(presetConfig);
|
|
165
|
+
context.aiClients.set(provider, client);
|
|
166
|
+
return client;
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
const envVar = ENV_VAR_MAP[provider] || `${provider.toUpperCase()}_API_KEY`;
|
|
170
|
+
console.log(colors.red(`Failed to initialize ${provider}`));
|
|
171
|
+
console.log(colors.gray(`Make sure ${envVar} is set.`));
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
async function handleCommand(input, currentProvider, client, context, rl, onExit, onSwitch) {
|
|
176
|
+
const parts = input.slice(1).split(/\s+/);
|
|
177
|
+
const cmd = parts[0].toLowerCase();
|
|
178
|
+
const args = parts.slice(1);
|
|
179
|
+
switch (cmd) {
|
|
180
|
+
case 'exit':
|
|
181
|
+
case 'quit':
|
|
182
|
+
case 'q':
|
|
183
|
+
onExit();
|
|
184
|
+
return true;
|
|
185
|
+
case 'clear':
|
|
186
|
+
if (client?.hasAI) {
|
|
187
|
+
client.ai.clearMemory();
|
|
188
|
+
console.log(colors.green(`Memory cleared for ${currentProvider}`));
|
|
189
|
+
}
|
|
190
|
+
rl.prompt();
|
|
191
|
+
return true;
|
|
192
|
+
case 'switch':
|
|
193
|
+
case 's':
|
|
194
|
+
const newProvider = args[0]?.toLowerCase();
|
|
195
|
+
if (!newProvider) {
|
|
196
|
+
console.log(colors.yellow('Usage: /switch <provider>'));
|
|
197
|
+
console.log(colors.gray(`Available: ${AI_PRESETS.join(', ')}`));
|
|
198
|
+
rl.prompt();
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
if (!AI_PRESETS.includes(newProvider)) {
|
|
202
|
+
console.log(colors.red(`Unknown provider: ${newProvider}`));
|
|
203
|
+
console.log(colors.gray(`Available: ${AI_PRESETS.join(', ')}`));
|
|
204
|
+
rl.prompt();
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
await onSwitch(newProvider);
|
|
208
|
+
return true;
|
|
209
|
+
case 'model':
|
|
210
|
+
case 'm':
|
|
211
|
+
const model = client?._aiConfig?.model || 'default';
|
|
212
|
+
console.log(colors.cyan(`Current model: ${model}`));
|
|
213
|
+
console.log(colors.gray('Note: Model is set by the preset configuration.'));
|
|
214
|
+
rl.prompt();
|
|
215
|
+
return true;
|
|
216
|
+
case 'memory':
|
|
217
|
+
case 'mem':
|
|
218
|
+
if (client?.hasAI) {
|
|
219
|
+
const memory = client.ai.getMemory();
|
|
220
|
+
const pairs = Math.floor(memory.length / 2);
|
|
221
|
+
console.log(colors.cyan(`Memory: ${pairs}/12 pairs (${memory.length} messages)`));
|
|
222
|
+
if (pairs > 0) {
|
|
223
|
+
console.log(colors.gray('Last exchange:'));
|
|
224
|
+
const lastTwo = memory.slice(-2);
|
|
225
|
+
for (const msg of lastTwo) {
|
|
226
|
+
const role = msg.role === 'user' ? colors.magenta('You') : colors.orange('AI');
|
|
227
|
+
const preview = msg.content.slice(0, 100) + (msg.content.length > 100 ? '...' : '');
|
|
228
|
+
console.log(` ${role}: ${colors.gray(preview)}`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
rl.prompt();
|
|
233
|
+
return true;
|
|
234
|
+
case 'providers':
|
|
235
|
+
case 'list':
|
|
236
|
+
console.log(colors.cyan('Available AI providers:'));
|
|
237
|
+
for (const p of AI_PRESETS) {
|
|
238
|
+
const isActive = p === currentProvider ? colors.green(' (active)') : '';
|
|
239
|
+
const hasClient = context.aiClients.has(p) ? colors.gray(' [loaded]') : '';
|
|
240
|
+
console.log(` ${p}${isActive}${hasClient}`);
|
|
241
|
+
}
|
|
242
|
+
rl.prompt();
|
|
243
|
+
return true;
|
|
244
|
+
case 'help':
|
|
245
|
+
case 'h':
|
|
246
|
+
case '?':
|
|
247
|
+
printHelp();
|
|
248
|
+
rl.prompt();
|
|
249
|
+
return true;
|
|
250
|
+
default:
|
|
251
|
+
console.log(colors.yellow(`Unknown command: /${cmd}`));
|
|
252
|
+
console.log(colors.gray('Type /help for available commands'));
|
|
253
|
+
rl.prompt();
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
function handleError(error, provider) {
|
|
258
|
+
if (error.message?.includes('API key') || error.message?.includes('401') || error.message?.includes('Unauthorized')) {
|
|
259
|
+
const envVar = ENV_VAR_MAP[provider] || `${provider.toUpperCase()}_API_KEY`;
|
|
260
|
+
console.log(colors.red(`\nAuthentication error for ${provider}`));
|
|
261
|
+
console.log(colors.gray(`Set ${envVar} environment variable.`));
|
|
262
|
+
}
|
|
263
|
+
else if (error.message?.includes('429') || error.message?.includes('rate limit')) {
|
|
264
|
+
console.log(colors.yellow(`\nRate limited by ${provider}. Wait a moment and try again.`));
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
console.log(colors.red(`\nError: ${error.message || error}`));
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function printHeader(provider) {
|
|
271
|
+
console.log(colors.bold(colors.cyan('Rek AI Mode')));
|
|
272
|
+
console.log('');
|
|
273
|
+
console.log(` ${colors.gray('Provider:')} ${colors.white(provider)}`);
|
|
274
|
+
console.log('');
|
|
275
|
+
console.log(` ${colors.green('/switch')} ${colors.gray('<provider>')} ${colors.gray('Change AI provider')}`);
|
|
276
|
+
console.log(` ${colors.green('/clear')} ${colors.gray('Clear conversation memory')}`);
|
|
277
|
+
console.log(` ${colors.green('/memory')} ${colors.gray('Show memory status')}`);
|
|
278
|
+
console.log(` ${colors.green('/help')} ${colors.gray('Show all commands')}`);
|
|
279
|
+
console.log('');
|
|
280
|
+
console.log(` ${colors.yellow('ESC')} ${colors.gray('or')} ${colors.yellow('/exit')} ${colors.gray('Exit AI mode')}`);
|
|
281
|
+
console.log('');
|
|
282
|
+
console.log(colors.gray('─'.repeat(50)));
|
|
283
|
+
console.log('');
|
|
284
|
+
}
|
|
285
|
+
function printHelp() {
|
|
286
|
+
console.log(`
|
|
287
|
+
${colors.bold(colors.cyan('AI Mode Commands'))}
|
|
288
|
+
|
|
289
|
+
${colors.green('/switch <provider>')} Switch to another AI provider
|
|
290
|
+
${colors.green('/clear')} Clear conversation memory
|
|
291
|
+
${colors.green('/memory')} Show memory status
|
|
292
|
+
${colors.green('/model')} Show current model
|
|
293
|
+
${colors.green('/providers')} List available providers
|
|
294
|
+
${colors.green('/help')} Show this help
|
|
295
|
+
${colors.green('/exit')} Exit AI mode
|
|
296
|
+
|
|
297
|
+
${colors.bold('Shortcuts')}
|
|
298
|
+
${colors.gray('ESC')} Exit AI mode (or abort generation)
|
|
299
|
+
${colors.gray('Ctrl+C')} Exit AI mode
|
|
300
|
+
|
|
301
|
+
${colors.bold('Available Providers')}
|
|
302
|
+
${AI_PRESETS.join(', ')}
|
|
303
|
+
`);
|
|
304
|
+
}
|