explorbot 0.2.2 → 0.2.3
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/README.md +1 -1
- package/bin/explorbot-cli.ts +52 -37
- package/boat/api-tester/src/apibot.ts +4 -2
- package/boat/api-tester/src/cli.ts +2 -2
- package/boat/api-tester/src/config.ts +39 -8
- package/boat/doc-collector/src/cli.ts +1 -0
- package/boat/doc-collector/src/docs-renderer.ts +18 -4
- package/boat/doc-collector/src/state-diagram.ts +61 -14
- package/boat/prima/bin/prima-cli.ts +5 -0
- package/boat/prima/package.json +16 -0
- package/boat/prima/src/cli.ts +222 -0
- package/boat/prima/src/envelope.ts +141 -0
- package/boat/prima/src/prima.ts +705 -0
- package/boat/prima/src/pw-parser.ts +17 -0
- package/boat/prima/src/pw-registry.ts +75 -0
- package/dist/bin/explorbot-cli.js +44 -31
- package/dist/boat/api-tester/src/apibot.js +3 -2
- package/dist/boat/api-tester/src/cli.js +2 -2
- package/dist/boat/api-tester/src/config.js +36 -8
- package/dist/boat/doc-collector/src/cli.js +1 -0
- package/dist/boat/doc-collector/src/docs-renderer.js +17 -3
- package/dist/boat/doc-collector/src/state-diagram.js +57 -13
- package/dist/boat/prima/bin/prima-cli.js +4 -0
- package/dist/boat/prima/src/cli.js +200 -0
- package/dist/boat/prima/src/envelope.js +116 -0
- package/dist/boat/prima/src/prima.js +635 -0
- package/dist/boat/prima/src/pw-parser.js +18 -0
- package/dist/boat/prima/src/pw-registry.js +66 -0
- package/dist/models.json +3 -0
- package/dist/package.json +6 -2
- package/dist/src/action.d.ts +5 -2
- package/dist/src/action.js +5 -5
- package/dist/src/ai/captain/mixin.js +3 -4
- package/dist/src/ai/captain/web-mode.js +1 -1
- package/dist/src/ai/navigator.d.ts +4 -0
- package/dist/src/ai/navigator.js +11 -6
- package/dist/src/ai/planner.d.ts +1 -0
- package/dist/src/ai/planner.js +6 -0
- package/dist/src/ai/researcher.js +1 -1
- package/dist/src/ai/task-agent.js +1 -1
- package/dist/src/ai/tester.d.ts +1 -0
- package/dist/src/ai/tester.js +13 -0
- package/dist/src/application-spec-contract.d.ts +8 -0
- package/dist/src/application-spec-contract.js +8 -0
- package/dist/src/application-spec.d.ts +15 -0
- package/dist/src/application-spec.js +71 -0
- package/dist/src/browser-server.d.ts +12 -6
- package/dist/src/browser-server.js +74 -19
- package/dist/src/commands/clean-command.js +2 -7
- package/dist/src/commands/init-command.d.ts +5 -0
- package/dist/src/commands/init-command.js +119 -1
- package/dist/src/commands/navigate-command.js +1 -1
- package/dist/src/commands/research-command.js +1 -1
- package/dist/src/commands/sites-command.d.ts +6 -0
- package/dist/src/commands/sites-command.js +23 -0
- package/dist/src/components/InitWizard.d.ts +10 -0
- package/dist/src/components/InitWizard.js +133 -0
- package/dist/src/components/InputReadline.d.ts +1 -0
- package/dist/src/components/InputReadline.js +7 -4
- package/dist/src/config.d.ts +24 -5
- package/dist/src/config.js +146 -37
- package/dist/src/explorbot.d.ts +9 -0
- package/dist/src/explorbot.js +24 -5
- package/dist/src/explorer.d.ts +4 -1
- package/dist/src/explorer.js +40 -6
- package/dist/src/global-config.d.ts +22 -0
- package/dist/src/global-config.js +117 -0
- package/dist/src/knowledge-tracker.d.ts +5 -1
- package/dist/src/knowledge-tracker.js +14 -1
- package/dist/src/utils/cli-name.js +6 -2
- package/dist/src/utils/test-files.js +1 -2
- package/dist/src/utils/url-matcher.d.ts +1 -0
- package/dist/src/utils/url-matcher.js +9 -0
- package/models.json +3 -0
- package/package.json +6 -2
- package/src/action.ts +9 -5
- package/src/ai/captain/mixin.ts +3 -3
- package/src/ai/captain/web-mode.ts +1 -1
- package/src/ai/navigator.ts +12 -7
- package/src/ai/planner.ts +7 -0
- package/src/ai/researcher.ts +1 -1
- package/src/ai/task-agent.ts +1 -1
- package/src/ai/tester.ts +15 -0
- package/src/application-spec-contract.ts +10 -0
- package/src/application-spec.ts +87 -0
- package/src/browser-server.ts +74 -19
- package/src/commands/clean-command.ts +1 -6
- package/src/commands/init-command.ts +146 -1
- package/src/commands/navigate-command.ts +1 -1
- package/src/commands/research-command.ts +1 -1
- package/src/commands/sites-command.ts +27 -0
- package/src/components/InitWizard.tsx +166 -0
- package/src/components/InputReadline.tsx +8 -4
- package/src/config.ts +162 -39
- package/src/explorbot.ts +30 -5
- package/src/explorer.ts +45 -7
- package/src/global-config.ts +148 -0
- package/src/knowledge-tracker.ts +17 -1
- package/src/utils/cli-name.ts +5 -2
- package/src/utils/test-files.ts +1 -2
- package/src/utils/url-matcher.ts +10 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, readdirSync, rmSync, statSync, unlinkSync } from 'node:fs';
|
|
2
|
-
import {
|
|
2
|
+
import { join } from 'node:path';
|
|
3
3
|
import { ConfigParser, outputPath } from '../config.js';
|
|
4
4
|
import { tag } from '../utils/logger.js';
|
|
5
5
|
import { BaseCommand } from './base-command.js';
|
|
@@ -13,12 +13,7 @@ export const CLEAN_TARGETS = {
|
|
|
13
13
|
};
|
|
14
14
|
function getExperienceDir() {
|
|
15
15
|
const configParser = ConfigParser.getInstance();
|
|
16
|
-
|
|
17
|
-
const configPath = configParser.getConfigPath();
|
|
18
|
-
if (configPath) {
|
|
19
|
-
return join(dirname(configPath), config.dirs?.experience || 'experience');
|
|
20
|
-
}
|
|
21
|
-
return config.dirs?.experience || 'experience';
|
|
16
|
+
return configParser.resolveProjectDir(configParser.getConfig().dirs?.experience || 'experience');
|
|
22
17
|
}
|
|
23
18
|
function cleanDirectoryContents(dirPath) {
|
|
24
19
|
if (!existsSync(dirPath))
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
export declare function runInit(options: InitCommandOptions): Promise<void>;
|
|
2
|
+
export declare function writeGlobalConfig(provider: string, apiKey?: string): void;
|
|
1
3
|
export declare function runInitCommand(options: InitCommandOptions): void;
|
|
2
4
|
type InitCommandOptions = {
|
|
3
5
|
configPath?: string;
|
|
4
6
|
force?: boolean;
|
|
5
7
|
path?: string;
|
|
8
|
+
global?: boolean;
|
|
9
|
+
provider?: string;
|
|
10
|
+
apiKey?: string;
|
|
6
11
|
};
|
|
7
12
|
export {};
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, statSync, writeFileSync } from 'node:fs';
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { dirname, extname, join, resolve } from 'node:path';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import dedent from 'dedent';
|
|
5
|
+
import { ConfigParser, PROVIDERS } from "../config.js";
|
|
6
|
+
import { findGlobalConfig, globalConfigPath, globalDir, globalEnvPath } from "../global-config.js";
|
|
5
7
|
import { getCliName } from "../utils/cli-name.js";
|
|
6
8
|
import { log, tag } from '../utils/logger.js';
|
|
7
9
|
import { relativeToCwd } from "../utils/next-steps.js";
|
|
@@ -58,6 +60,41 @@ LANGFUSE_BASE_URL=
|
|
|
58
60
|
# Testomat.io API key to publish run results
|
|
59
61
|
TESTOMATIO=
|
|
60
62
|
`;
|
|
63
|
+
export async function runInit(options) {
|
|
64
|
+
if (options.global || options.provider) {
|
|
65
|
+
await runGlobalInit(options);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (options.configPath || options.path || !process.stdin.isTTY) {
|
|
69
|
+
runInitCommand(options);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const choice = await renderInitWizard('choose');
|
|
73
|
+
if (choice === 'local')
|
|
74
|
+
runInitCommand(options);
|
|
75
|
+
}
|
|
76
|
+
export function writeGlobalConfig(provider, apiKey) {
|
|
77
|
+
if (!PROVIDERS[provider]) {
|
|
78
|
+
throw new Error(`Unknown AI provider "${provider}". Supported providers: ${Object.keys(PROVIDERS).join(', ')}`);
|
|
79
|
+
}
|
|
80
|
+
mkdirSync(globalDir(), { recursive: true });
|
|
81
|
+
writeFileSync(globalConfigPath(), globalConfigTemplate(provider), 'utf8');
|
|
82
|
+
log(`Created global config: ${globalConfigPath()}`);
|
|
83
|
+
const envKey = PROVIDERS[provider].envKey;
|
|
84
|
+
writeEnvKey(envKey, apiKey || '');
|
|
85
|
+
log(`Stored ${envKey} in ${globalEnvPath()}`);
|
|
86
|
+
const missing = missingRoles(provider);
|
|
87
|
+
if (missing.length) {
|
|
88
|
+
tag('warning').log(`No recommended ${missing.join(' and ')} for ${provider} — set the model ids in ${globalConfigPath()}`);
|
|
89
|
+
}
|
|
90
|
+
if (!apiKey && !process.env[envKey]) {
|
|
91
|
+
tag('warning').log(`Add your API key to ${globalEnvPath()}`);
|
|
92
|
+
}
|
|
93
|
+
log('');
|
|
94
|
+
log('Explorbot now runs from any directory:');
|
|
95
|
+
tag('substep').log(chalk.yellow(`${getCliName()} explore https://your-app.example.com`));
|
|
96
|
+
tag('substep').log(chalk.yellow(`${getCliName()} sites`));
|
|
97
|
+
}
|
|
61
98
|
export function runInitCommand(options) {
|
|
62
99
|
const configPath = options.configPath ?? './explorbot.config.js';
|
|
63
100
|
const force = options.force ?? false;
|
|
@@ -125,3 +162,84 @@ export function runInitCommand(options) {
|
|
|
125
162
|
}
|
|
126
163
|
}
|
|
127
164
|
}
|
|
165
|
+
async function runGlobalInit(options) {
|
|
166
|
+
const existing = findGlobalConfig();
|
|
167
|
+
if (existing && !options.force) {
|
|
168
|
+
log(`Global config already exists: ${existing}`);
|
|
169
|
+
log('Use --force to overwrite existing file');
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
if (options.provider) {
|
|
173
|
+
writeGlobalConfig(options.provider, options.apiKey);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (!process.stdin.isTTY) {
|
|
177
|
+
log('Cannot run the setup wizard outside an interactive terminal');
|
|
178
|
+
log(`Pass a provider instead: ${getCliName()} init --global --provider ${Object.keys(PROVIDERS)[0]}`);
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
await renderInitWizard('global');
|
|
182
|
+
}
|
|
183
|
+
async function renderInitWizard(mode) {
|
|
184
|
+
const [{ render }, React, InitWizard] = await Promise.all([import('ink'), import('react'), import('../components/InitWizard.js').then((m) => m.default)]);
|
|
185
|
+
return new Promise((resolve) => {
|
|
186
|
+
const finish = (choice) => {
|
|
187
|
+
unmount();
|
|
188
|
+
resolve(choice);
|
|
189
|
+
};
|
|
190
|
+
const { unmount } = render(React.createElement(InitWizard, {
|
|
191
|
+
mode,
|
|
192
|
+
globalConfigExists: !!findGlobalConfig(),
|
|
193
|
+
onLocal: () => finish('local'),
|
|
194
|
+
onComplete: () => finish('global'),
|
|
195
|
+
onCancel: () => finish(null),
|
|
196
|
+
}), { exitOnCtrlC: false, patchConsole: false });
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
function globalConfigTemplate(provider) {
|
|
200
|
+
const { envKey } = PROVIDERS[provider];
|
|
201
|
+
const recommended = ConfigParser.recommendedModels()[provider] || {};
|
|
202
|
+
const roles = [
|
|
203
|
+
['model', 'fast model with tool calling capabilities'],
|
|
204
|
+
['visionModel', 'vision model for screenshot analysis'],
|
|
205
|
+
['agenticModel', 'agentic model for decision making'],
|
|
206
|
+
];
|
|
207
|
+
const models = roles.map(([role, comment]) => ` // ${comment}\n ${role}: '${provider}/${recommended[role] || '<model-id>'}',`).join('\n');
|
|
208
|
+
return `// Global Explorbot configuration — used by every directory without its own explorbot.config.js.
|
|
209
|
+
// Models are written as 'provider/model-id' so they resolve without a local node_modules.
|
|
210
|
+
// The key is read from ${envKey} in ~/.explorbot/.env
|
|
211
|
+
// Model ids are snapshotted from the recommendations of this Explorbot version.
|
|
212
|
+
// https://github.com/testomatio/explorbot/blob/main/docs/basics/providers.md
|
|
213
|
+
const config = {
|
|
214
|
+
ai: {
|
|
215
|
+
${models}
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
reporter: {
|
|
219
|
+
// Save a local HTML report after each run.
|
|
220
|
+
html: true,
|
|
221
|
+
// Save a local markdown report after each run.
|
|
222
|
+
markdown: true,
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
export default config;
|
|
227
|
+
`;
|
|
228
|
+
}
|
|
229
|
+
function missingRoles(provider) {
|
|
230
|
+
const recommended = ConfigParser.recommendedModels()[provider] || {};
|
|
231
|
+
return ['model', 'visionModel', 'agenticModel'].filter((role) => !recommended[role]);
|
|
232
|
+
}
|
|
233
|
+
function writeEnvKey(key, value) {
|
|
234
|
+
const envPath = globalEnvPath();
|
|
235
|
+
let content = '# AI provider API keys';
|
|
236
|
+
if (existsSync(envPath))
|
|
237
|
+
content = readFileSync(envPath, 'utf8').trimEnd();
|
|
238
|
+
const lines = content.split('\n');
|
|
239
|
+
const index = lines.findIndex((line) => line.startsWith(`${key}=`));
|
|
240
|
+
if (index < 0)
|
|
241
|
+
lines.push(`${key}=${value}`);
|
|
242
|
+
if (index >= 0 && value)
|
|
243
|
+
lines[index] = `${key}=${value}`;
|
|
244
|
+
writeFileSync(envPath, `${lines.join('\n').trimEnd()}\n`, 'utf8');
|
|
245
|
+
}
|
|
@@ -12,7 +12,7 @@ export class NavigateCommand extends BaseCommand {
|
|
|
12
12
|
if (!destination) {
|
|
13
13
|
throw new Error('Navigate command requires a target URI or state');
|
|
14
14
|
}
|
|
15
|
-
await this.explorBot.
|
|
15
|
+
await this.explorBot.visit(destination);
|
|
16
16
|
tag('success').log(`Navigation requested: ${destination}`);
|
|
17
17
|
}
|
|
18
18
|
}
|
|
@@ -21,7 +21,7 @@ export class ResearchCommand extends BaseCommand {
|
|
|
21
21
|
const noFix = !!opts.noFix;
|
|
22
22
|
const target = remaining.join(' ');
|
|
23
23
|
if (target) {
|
|
24
|
-
await this.explorBot.
|
|
24
|
+
await this.explorBot.visit(target);
|
|
25
25
|
}
|
|
26
26
|
const state = this.explorBot.stateManager().getCurrentState();
|
|
27
27
|
if (!state) {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { listSites, sitesDir } from '../global-config.js';
|
|
2
|
+
import { getCliName } from '../utils/cli-name.js';
|
|
3
|
+
import { tag } from '../utils/logger.js';
|
|
4
|
+
import { BaseCommand } from './base-command.js';
|
|
5
|
+
export class SitesCommand extends BaseCommand {
|
|
6
|
+
name = 'sites';
|
|
7
|
+
description = 'List sites registered in the global installation';
|
|
8
|
+
async execute() {
|
|
9
|
+
const sites = listSites();
|
|
10
|
+
if (sites.length === 0) {
|
|
11
|
+
tag('info').log(`No sites registered in ${sitesDir()}`);
|
|
12
|
+
tag('info').log(`Explore one to register it: ${getCliName()} explore https://app.example.com`);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const width = sites.reduce((max, site) => Math.max(max, site.folder.length), 0);
|
|
16
|
+
tag('info').log(`Registered sites (${sites.length}):`);
|
|
17
|
+
for (const site of sites) {
|
|
18
|
+
tag('info').log(` ${site.folder.padEnd(width)} ${site.url} last run ${site.lastRunAt.slice(0, 16).replace('T', ' ')}`);
|
|
19
|
+
}
|
|
20
|
+
tag('info').log('');
|
|
21
|
+
tag('info').log(`Stored in ${sitesDir()}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface InitWizardProps {
|
|
3
|
+
mode: 'choose' | 'global';
|
|
4
|
+
globalConfigExists: boolean;
|
|
5
|
+
onLocal: () => void;
|
|
6
|
+
onComplete: () => void;
|
|
7
|
+
onCancel: () => void;
|
|
8
|
+
}
|
|
9
|
+
declare const InitWizard: React.FC<InitWizardProps>;
|
|
10
|
+
export default InitWizard;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import figureSet from 'figures';
|
|
2
|
+
import { Box, Text, useInput } from 'ink';
|
|
3
|
+
import React, { useState } from 'react';
|
|
4
|
+
import { AIProvider } from '../ai/provider.js';
|
|
5
|
+
import { writeGlobalConfig } from '../commands/init-command.js';
|
|
6
|
+
import { ConfigParser, PROVIDERS, createModel } from '../config.js';
|
|
7
|
+
import { globalDir } from '../global-config.js';
|
|
8
|
+
import InputReadline from './InputReadline.js';
|
|
9
|
+
const PROVIDER_NAMES = Object.keys(PROVIDERS);
|
|
10
|
+
const InitWizard = ({ mode, globalConfigExists, onLocal, onComplete, onCancel }) => {
|
|
11
|
+
const [step, setStep] = useState(mode === 'choose' ? 'target' : 'provider');
|
|
12
|
+
const [targetIndex, setTargetIndex] = useState(0);
|
|
13
|
+
const [providerIndex, setProviderIndex] = useState(0);
|
|
14
|
+
const [apiKey, setApiKey] = useState('');
|
|
15
|
+
const [status, setStatus] = useState('');
|
|
16
|
+
const [error, setError] = useState('');
|
|
17
|
+
const provider = PROVIDER_NAMES[providerIndex];
|
|
18
|
+
const envKey = PROVIDERS[provider].envKey;
|
|
19
|
+
const save = () => {
|
|
20
|
+
writeGlobalConfig(provider, apiKey.trim());
|
|
21
|
+
onComplete();
|
|
22
|
+
};
|
|
23
|
+
const validate = async () => {
|
|
24
|
+
const modelId = defaultModelId(provider);
|
|
25
|
+
if (!modelId) {
|
|
26
|
+
save();
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
setStatus(`Sending a test request to ${provider}...`);
|
|
30
|
+
setError('');
|
|
31
|
+
if (apiKey.trim())
|
|
32
|
+
process.env[envKey] = apiKey.trim();
|
|
33
|
+
try {
|
|
34
|
+
const model = await createModel(provider, modelId);
|
|
35
|
+
await new AIProvider({ model }).validateConnection();
|
|
36
|
+
save();
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
setStatus('');
|
|
40
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
useInput((input, key) => {
|
|
44
|
+
if (key.ctrl && input === 'c') {
|
|
45
|
+
onCancel();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (step === 'key')
|
|
49
|
+
return;
|
|
50
|
+
if (key.escape) {
|
|
51
|
+
onCancel();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (step === 'target') {
|
|
55
|
+
if (key.upArrow)
|
|
56
|
+
setTargetIndex(0);
|
|
57
|
+
if (key.downArrow && !globalConfigExists)
|
|
58
|
+
setTargetIndex(1);
|
|
59
|
+
if (key.return && targetIndex === 0)
|
|
60
|
+
onLocal();
|
|
61
|
+
if (key.return && targetIndex === 1)
|
|
62
|
+
setStep('provider');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (step === 'provider') {
|
|
66
|
+
if (key.upArrow)
|
|
67
|
+
setProviderIndex((index) => Math.max(0, index - 1));
|
|
68
|
+
if (key.downArrow)
|
|
69
|
+
setProviderIndex((index) => Math.min(PROVIDER_NAMES.length - 1, index + 1));
|
|
70
|
+
if (key.return)
|
|
71
|
+
setStep('key');
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (status)
|
|
75
|
+
return;
|
|
76
|
+
if (input.toLowerCase() === 'y')
|
|
77
|
+
void validate();
|
|
78
|
+
if (input.toLowerCase() === 'n')
|
|
79
|
+
save();
|
|
80
|
+
if (input.toLowerCase() === 'r') {
|
|
81
|
+
setError('');
|
|
82
|
+
setStep('key');
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
return (React.createElement(Box, { flexDirection: "column", padding: 1 },
|
|
86
|
+
React.createElement(Box, { marginBottom: 1 },
|
|
87
|
+
React.createElement(Text, { color: "cyan", bold: true }, "Explorbot setup")),
|
|
88
|
+
step === 'target' && (React.createElement(Box, { flexDirection: "column" },
|
|
89
|
+
React.createElement(Text, null, "Where should explorbot be initialized?"),
|
|
90
|
+
React.createElement(Text, { color: targetIndex === 0 ? 'blue' : undefined },
|
|
91
|
+
targetIndex === 0 ? `${figureSet.pointer} ` : ' ',
|
|
92
|
+
"Local ",
|
|
93
|
+
React.createElement(Text, { dimColor: true }, "\u2014 creates the config file in the current directory")),
|
|
94
|
+
React.createElement(Text, { color: targetIndex === 1 ? 'blue' : undefined, dimColor: globalConfigExists },
|
|
95
|
+
targetIndex === 1 ? `${figureSet.pointer} ` : ' ',
|
|
96
|
+
"Global ",
|
|
97
|
+
React.createElement(Text, { dimColor: true }, "\u2014 initializes explorbot to run from anywhere on this machine"),
|
|
98
|
+
globalConfigExists && React.createElement(Text, { dimColor: true }, " (already installed)")))),
|
|
99
|
+
step === 'provider' && (React.createElement(Box, { flexDirection: "column" },
|
|
100
|
+
React.createElement(Text, null, "Pick an AI provider:"),
|
|
101
|
+
PROVIDER_NAMES.map((name, index) => (React.createElement(Text, { key: name, color: index === providerIndex ? 'blue' : undefined },
|
|
102
|
+
index === providerIndex ? `${figureSet.pointer} ` : ' ',
|
|
103
|
+
name))))),
|
|
104
|
+
step === 'key' && (React.createElement(Box, { flexDirection: "column" },
|
|
105
|
+
React.createElement(Text, null,
|
|
106
|
+
"Enter the API key for ",
|
|
107
|
+
provider,
|
|
108
|
+
" ",
|
|
109
|
+
React.createElement(Text, { dimColor: true },
|
|
110
|
+
"(stored as ",
|
|
111
|
+
envKey,
|
|
112
|
+
")")),
|
|
113
|
+
React.createElement(Box, { borderStyle: "single", borderColor: "blue", paddingX: 1 },
|
|
114
|
+
React.createElement(InputReadline, { value: apiKey, onChange: setApiKey, onSubmit: () => setStep('validate'), placeholder: process.env[envKey] ? 'leave empty to keep the key from your environment' : 'paste the key', isActive: true, showPrompt: false, mask: true })))),
|
|
115
|
+
step === 'validate' && (React.createElement(Box, { flexDirection: "column" },
|
|
116
|
+
!status && !error && React.createElement(Text, null, "Check the key with a test AI call? (y/n)"),
|
|
117
|
+
status && React.createElement(Text, { color: "yellow" }, status),
|
|
118
|
+
error && (React.createElement(Box, { flexDirection: "column" },
|
|
119
|
+
React.createElement(Text, { color: "red" }, error),
|
|
120
|
+
React.createElement(Text, { dimColor: true }, "r: re-enter the key | n: save anyway"))))),
|
|
121
|
+
React.createElement(Box, { marginTop: 1 },
|
|
122
|
+
React.createElement(Text, { dimColor: true },
|
|
123
|
+
"Config goes to ",
|
|
124
|
+
globalDir(),
|
|
125
|
+
" | ",
|
|
126
|
+
step === 'key' ? 'Enter: continue' : '↑↓: select | Enter: confirm',
|
|
127
|
+
" | Ctrl+C: exit"))));
|
|
128
|
+
};
|
|
129
|
+
function defaultModelId(provider) {
|
|
130
|
+
const recommended = ConfigParser.recommendedModels()[provider] || {};
|
|
131
|
+
return recommended.model || recommended.agenticModel || recommended.visionModel || '';
|
|
132
|
+
}
|
|
133
|
+
export default InitWizard;
|
|
@@ -3,7 +3,7 @@ import React from 'react';
|
|
|
3
3
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
4
4
|
import { setAutocompleteState } from './autocomplete-store.js';
|
|
5
5
|
import parseKeypress, { nonAlphanumericKeys } from './parse-keypress.js';
|
|
6
|
-
const InputReadline = React.memo(({ commandHandler, exitOnEmptyInput = false, onSubmit, onChange, onCommandStart, onCommandComplete, isActive = true, visible = true, value: controlledValue, placeholder = '', showPrompt = true }) => {
|
|
6
|
+
const InputReadline = React.memo(({ commandHandler, exitOnEmptyInput = false, onSubmit, onChange, onCommandStart, onCommandComplete, isActive = true, visible = true, value: controlledValue, placeholder = '', showPrompt = true, mask = false }) => {
|
|
7
7
|
const isControlled = controlledValue !== undefined;
|
|
8
8
|
const [inputState, setInputState] = useState({
|
|
9
9
|
value: controlledValue || '',
|
|
@@ -537,11 +537,14 @@ const InputReadline = React.memo(({ commandHandler, exitOnEmptyInput = false, on
|
|
|
537
537
|
commandHandler.setExitOnEmptyInput(exitOnEmptyInput);
|
|
538
538
|
}
|
|
539
539
|
}, [commandHandler, exitOnEmptyInput]);
|
|
540
|
-
|
|
541
|
-
|
|
540
|
+
let renderedValue = displayValue;
|
|
541
|
+
if (mask)
|
|
542
|
+
renderedValue = '•'.repeat(displayValue.length);
|
|
543
|
+
const beforeCursor = renderedValue.slice(0, cursorPosition);
|
|
544
|
+
const afterCursor = renderedValue.slice(cursorPosition);
|
|
542
545
|
const beforeLines = beforeCursor.split('\n');
|
|
543
546
|
const afterLines = afterCursor.split('\n');
|
|
544
|
-
const lines =
|
|
547
|
+
const lines = renderedValue.split('\n');
|
|
545
548
|
const cursorLineIndex = beforeLines.length - 1;
|
|
546
549
|
const cursorLineBefore = beforeLines[cursorLineIndex] || '';
|
|
547
550
|
const cursorLineAfter = afterLines[0] || '';
|
package/dist/src/config.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import { type SiteRecord } from './global-config.js';
|
|
2
|
+
export declare const PROVIDERS: Record<string, ProviderInfo>;
|
|
2
3
|
interface PlaywrightConfig {
|
|
3
4
|
browser: 'chromium' | 'firefox' | 'webkit';
|
|
4
5
|
url: string;
|
|
@@ -202,6 +203,7 @@ interface ExplorbotConfig {
|
|
|
202
203
|
knowledge: string;
|
|
203
204
|
experience: string;
|
|
204
205
|
output: string;
|
|
206
|
+
spec?: string;
|
|
205
207
|
};
|
|
206
208
|
experience?: {
|
|
207
209
|
maxReadLines?: number;
|
|
@@ -222,21 +224,27 @@ export declare class ConfigParser {
|
|
|
222
224
|
static recommended: Record<string, Record<string, string>> | null;
|
|
223
225
|
config: ExplorbotConfig | null;
|
|
224
226
|
configPath: string | null;
|
|
225
|
-
|
|
227
|
+
runtimeTarget: string | null;
|
|
228
|
+
site: SiteRecord | null;
|
|
229
|
+
siteStartPath: string;
|
|
226
230
|
constructor();
|
|
227
|
-
static loadEnv(filePath: string): void;
|
|
231
|
+
static loadEnv(filePath: string, keepExisting?: boolean): void;
|
|
228
232
|
static recommendedModels(): Record<string, Record<string, string>>;
|
|
229
233
|
static getInstance(): ConfigParser;
|
|
230
234
|
loadConfig(options?: {
|
|
231
235
|
config?: string;
|
|
232
236
|
path?: string;
|
|
233
237
|
baseUrl?: string;
|
|
238
|
+
from?: string;
|
|
234
239
|
}): Promise<ExplorbotConfig>;
|
|
235
240
|
getConfig(): ExplorbotConfig;
|
|
236
241
|
getConfigPath(): string | null;
|
|
237
242
|
getOutputDir(): string;
|
|
238
243
|
getProjectRoot(): string;
|
|
239
244
|
resolveProjectDir(relativeDir: string): string;
|
|
245
|
+
isGlobalMode(): boolean;
|
|
246
|
+
getSite(): SiteRecord | null;
|
|
247
|
+
resolveTargetPath(target?: string): string;
|
|
240
248
|
getStatesDir(): string;
|
|
241
249
|
getPlansDir(): string;
|
|
242
250
|
getTestsDir(): string;
|
|
@@ -244,6 +252,7 @@ export declare class ConfigParser {
|
|
|
244
252
|
static setupTestConfig(): void;
|
|
245
253
|
static getTestDirectories(): string[];
|
|
246
254
|
static cleanupAllTestDirectories(): void;
|
|
255
|
+
enterGlobalMode(config: ExplorbotConfig, target: string | null): void;
|
|
247
256
|
buildEnvConfig(baseUrl: string | undefined, outputRoot: string): Promise<ExplorbotConfig>;
|
|
248
257
|
findConfigFile(): string | null;
|
|
249
258
|
loadConfigModule(configPath: string): Promise<any>;
|
|
@@ -258,13 +267,23 @@ export declare class ConfigParser {
|
|
|
258
267
|
}
|
|
259
268
|
export declare function outputPath(...segments: string[]): string;
|
|
260
269
|
export declare function resolveModel(spec: string, role?: ModelRole): Promise<any>;
|
|
261
|
-
export declare
|
|
270
|
+
export declare class ConfigMissingError extends Error {
|
|
271
|
+
}
|
|
272
|
+
export declare function envConfigRequested(): boolean;
|
|
273
|
+
export declare function missingConfigMessage(configFile?: string): string;
|
|
274
|
+
export declare function resolveConfigModels(ai?: AIConfig): Promise<void>;
|
|
275
|
+
export declare function resolveOutputRoot(baseUrl?: string): string;
|
|
276
|
+
export declare function resolveStateRoot(baseUrl: string, ephemeral?: boolean): string;
|
|
262
277
|
export declare function materializeKnowledge(outputRoot: string): void;
|
|
263
278
|
export declare function createModel(provider: string, modelId: string): Promise<any>;
|
|
264
279
|
type ModelRole = 'model' | 'visionModel' | 'agenticModel';
|
|
280
|
+
interface ProviderInfo {
|
|
281
|
+
envKey: string;
|
|
282
|
+
load: () => Promise<(modelId: string) => any>;
|
|
283
|
+
}
|
|
265
284
|
interface EnvVar {
|
|
266
285
|
name: string;
|
|
267
286
|
description: string;
|
|
268
287
|
required?: boolean;
|
|
269
288
|
}
|
|
270
|
-
export type { ModelRole, EnvVar };
|
|
289
|
+
export type { ModelRole, EnvVar, ProviderInfo };
|