explorbot 0.2.2 → 0.2.4
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 +59 -38
- 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 +43 -10
- package/boat/doc-collector/bin/doc-collector-cli.ts +2 -0
- package/boat/doc-collector/src/ai/documentarian.ts +61 -31
- package/boat/doc-collector/src/cli.ts +1 -0
- package/boat/doc-collector/src/config.ts +4 -2
- 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/activity-line.ts +33 -0
- package/boat/prima/src/cli.ts +251 -0
- package/boat/prima/src/envelope.ts +169 -0
- package/boat/prima/src/prima.ts +1124 -0
- package/boat/prima/src/pw-parser.ts +27 -0
- package/boat/prima/src/pw-registry.ts +74 -0
- package/boat/prima/src/session-log.ts +126 -0
- package/dist/bin/explorbot-cli.js +51 -32
- package/dist/boat/api-tester/bin/apibot-cli.js +2 -0
- 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 +40 -10
- package/dist/boat/doc-collector/bin/doc-collector-cli.js +2 -0
- package/dist/boat/doc-collector/src/ai/documentarian.js +44 -19
- package/dist/boat/doc-collector/src/cli.js +1 -0
- package/dist/boat/doc-collector/src/config.js +4 -2
- 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/activity-line.js +30 -0
- package/dist/boat/prima/src/cli.js +220 -0
- package/dist/boat/prima/src/envelope.js +142 -0
- package/dist/boat/prima/src/prima.js +1031 -0
- package/dist/boat/prima/src/pw-parser.js +30 -0
- package/dist/boat/prima/src/pw-registry.js +65 -0
- package/dist/boat/prima/src/session-log.js +108 -0
- package/dist/models.json +3 -0
- package/dist/package.json +8 -3
- package/dist/rules/navigator/verification-actions.md +20 -0
- package/dist/src/action-result.d.ts +7 -0
- package/dist/src/action-result.js +4 -0
- package/dist/src/action.d.ts +7 -2
- package/dist/src/action.js +41 -6
- package/dist/src/ai/captain/mixin.js +3 -4
- package/dist/src/ai/captain/web-mode.js +7 -4
- package/dist/src/ai/captain.js +2 -0
- package/dist/src/ai/navigator.d.ts +11 -0
- package/dist/src/ai/navigator.js +21 -12
- package/dist/src/ai/pilot.d.ts +4 -0
- package/dist/src/ai/pilot.js +48 -2
- package/dist/src/ai/planner.d.ts +1 -0
- package/dist/src/ai/planner.js +6 -0
- package/dist/src/ai/provider.js +2 -2
- package/dist/src/ai/rerunner.js +2 -1
- package/dist/src/ai/researcher.js +2 -2
- package/dist/src/ai/rules.js +17 -10
- package/dist/src/ai/task-agent.js +5 -2
- package/dist/src/ai/tester.d.ts +1 -0
- package/dist/src/ai/tester.js +33 -20
- package/dist/src/ai/tools.d.ts +14 -0
- package/dist/src/ai/tools.js +113 -36
- 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 +25 -5
- package/dist/src/config.js +158 -40
- package/dist/src/execution-controller.d.ts +2 -0
- package/dist/src/execution-controller.js +6 -0
- package/dist/src/explorbot.d.ts +11 -1
- package/dist/src/explorbot.js +27 -6
- package/dist/src/explorer.d.ts +4 -1
- package/dist/src/explorer.js +42 -9
- 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/playwright-recorder.js +30 -0
- package/dist/src/remote.d.ts +54 -0
- package/dist/src/remote.js +229 -0
- package/dist/src/reporter.d.ts +1 -0
- package/dist/src/reporter.js +7 -1
- package/dist/src/state-manager.d.ts +2 -1
- package/dist/src/stats.d.ts +1 -0
- package/dist/src/stats.js +1 -0
- package/dist/src/test-plan.d.ts +2 -0
- package/dist/src/test-plan.js +7 -0
- package/dist/src/utils/aria.d.ts +2 -8
- package/dist/src/utils/aria.js +69 -40
- package/dist/src/utils/cli-name.js +6 -2
- package/dist/src/utils/html.js +1 -0
- package/dist/src/utils/logger.d.ts +6 -0
- package/dist/src/utils/logger.js +24 -0
- package/dist/src/utils/page-readiness.js +18 -1
- 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 +12 -0
- package/dist/src/utils/web-element.d.ts +2 -0
- package/dist/src/utils/web-element.js +8 -0
- package/dist/src/utils/web-sandbox.d.ts +1 -1
- package/dist/src/utils/web-sandbox.js +2 -3
- package/docs/api-testing/basics.md +90 -0
- package/docs/api-testing/planning.md +57 -0
- package/docs/api-testing/running-tests.md +55 -0
- package/docs/assets/cloud-report.png +0 -0
- package/docs/assets/html-report.png +0 -0
- package/docs/assets/langfuse-trace.png +0 -0
- package/docs/assets/successful-explore-run.png +0 -0
- package/docs/basics/getting-started.md +140 -0
- package/docs/basics/prerequisites.md +63 -0
- package/docs/basics/providers.md +362 -0
- package/docs/basics/running.md +78 -0
- package/docs/contributing/ai-integration-tests.md +57 -0
- package/docs/contributing/contributing.md +90 -0
- package/docs/contributing/demo-videos.md +36 -0
- package/docs/contributing/npm-package.md +138 -0
- package/docs/contributing/observability.md +227 -0
- package/docs/contributing/regression-tests.md +103 -0
- package/docs/contributing/testing.md +95 -0
- package/docs/doc-collection/basics.md +128 -0
- package/docs/doc-collection/crawling.md +67 -0
- package/docs/doc-collection/interactive-mode.md +99 -0
- package/docs/index.json +86 -0
- package/docs/reference/commands.md +994 -0
- package/docs/reference/configuration.md +569 -0
- package/docs/reference/scripting.md +303 -0
- package/docs/superpowers/plans/2026-08-01-actor-boat.md +925 -0
- package/docs/superpowers/plans/2026-08-01-prima-boat.md +1120 -0
- package/docs/superpowers/reviews/2026-08-06-prima-vs-playwright-cli.md +268 -0
- package/docs/superpowers/specs/2026-08-01-actor-boat-design.md +204 -0
- package/docs/superpowers/specs/2026-08-01-prima-boat-design.md +242 -0
- package/docs/superpowers/specs/2026-08-03-global-config-design.md +138 -0
- package/docs/superpowers/specs/2026-08-07-prima-fixes-design.md +394 -0
- package/docs/web-testing/agents.md +158 -0
- package/docs/web-testing/automated-tests.md +134 -0
- package/docs/web-testing/basics.md +91 -0
- package/docs/web-testing/customization.md +131 -0
- package/docs/web-testing/hooks.md +238 -0
- package/docs/web-testing/page-interaction.md +84 -0
- package/docs/web-testing/planner.md +122 -0
- package/docs/web-testing/rerun.md +164 -0
- package/docs/web-testing/researcher.md +380 -0
- package/docs/workflow/agentic-usage.md +233 -0
- package/docs/workflow/application-spec.md +73 -0
- package/docs/workflow/ci.md +202 -0
- package/docs/workflow/knowledge.md +310 -0
- package/docs/workflow/planning-styles.md +67 -0
- package/docs/workflow/reporting.md +133 -0
- package/docs/workflow/test-plans.md +90 -0
- package/models.json +3 -0
- package/package.json +8 -3
- package/rules/navigator/verification-actions.md +20 -0
- package/src/action-result.ts +11 -0
- package/src/action.ts +47 -7
- package/src/ai/captain/mixin.ts +3 -3
- package/src/ai/captain/web-mode.ts +7 -4
- package/src/ai/captain.ts +3 -0
- package/src/ai/navigator.ts +26 -15
- package/src/ai/pilot.ts +53 -2
- package/src/ai/planner.ts +7 -0
- package/src/ai/provider.ts +2 -4
- package/src/ai/rerunner.ts +2 -1
- package/src/ai/researcher.ts +2 -2
- package/src/ai/rules.ts +17 -10
- package/src/ai/task-agent.ts +4 -2
- package/src/ai/tester.ts +32 -18
- package/src/ai/tools.ts +132 -36
- 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 +177 -43
- package/src/execution-controller.ts +8 -0
- package/src/explorbot.ts +34 -7
- package/src/explorer.ts +47 -9
- package/src/global-config.ts +148 -0
- package/src/knowledge-tracker.ts +17 -1
- package/src/playwright-recorder.ts +23 -0
- package/src/remote.ts +238 -0
- package/src/reporter.ts +7 -1
- package/src/state-manager.ts +2 -1
- package/src/stats.ts +1 -0
- package/src/test-plan.ts +9 -0
- package/src/utils/aria.ts +65 -45
- package/src/utils/cli-name.ts +5 -2
- package/src/utils/html.ts +1 -0
- package/src/utils/logger.ts +24 -1
- package/src/utils/page-readiness.ts +24 -1
- package/src/utils/test-files.ts +1 -2
- package/src/utils/url-matcher.ts +13 -0
- package/src/utils/web-element.ts +9 -0
- package/src/utils/web-sandbox.ts +3 -4
|
@@ -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;
|
|
@@ -171,6 +172,7 @@ interface HtmlConfig {
|
|
|
171
172
|
interface ActionConfig {
|
|
172
173
|
delay?: number;
|
|
173
174
|
retries?: number;
|
|
175
|
+
timeout?: number;
|
|
174
176
|
}
|
|
175
177
|
interface ReporterConfig {
|
|
176
178
|
enabled?: boolean;
|
|
@@ -202,6 +204,7 @@ interface ExplorbotConfig {
|
|
|
202
204
|
knowledge: string;
|
|
203
205
|
experience: string;
|
|
204
206
|
output: string;
|
|
207
|
+
spec?: string;
|
|
205
208
|
};
|
|
206
209
|
experience?: {
|
|
207
210
|
maxReadLines?: number;
|
|
@@ -222,21 +225,27 @@ export declare class ConfigParser {
|
|
|
222
225
|
static recommended: Record<string, Record<string, string>> | null;
|
|
223
226
|
config: ExplorbotConfig | null;
|
|
224
227
|
configPath: string | null;
|
|
225
|
-
|
|
228
|
+
runtimeTarget: string | null;
|
|
229
|
+
site: SiteRecord | null;
|
|
230
|
+
siteStartPath: string;
|
|
226
231
|
constructor();
|
|
227
|
-
static loadEnv(filePath: string): void;
|
|
232
|
+
static loadEnv(filePath: string, keepExisting?: boolean): void;
|
|
228
233
|
static recommendedModels(): Record<string, Record<string, string>>;
|
|
229
234
|
static getInstance(): ConfigParser;
|
|
230
235
|
loadConfig(options?: {
|
|
231
236
|
config?: string;
|
|
232
237
|
path?: string;
|
|
233
238
|
baseUrl?: string;
|
|
239
|
+
from?: string;
|
|
234
240
|
}): Promise<ExplorbotConfig>;
|
|
235
241
|
getConfig(): ExplorbotConfig;
|
|
236
242
|
getConfigPath(): string | null;
|
|
237
243
|
getOutputDir(): string;
|
|
238
244
|
getProjectRoot(): string;
|
|
239
245
|
resolveProjectDir(relativeDir: string): string;
|
|
246
|
+
isGlobalMode(): boolean;
|
|
247
|
+
getSite(): SiteRecord | null;
|
|
248
|
+
resolveTargetPath(target?: string): string;
|
|
240
249
|
getStatesDir(): string;
|
|
241
250
|
getPlansDir(): string;
|
|
242
251
|
getTestsDir(): string;
|
|
@@ -244,6 +253,7 @@ export declare class ConfigParser {
|
|
|
244
253
|
static setupTestConfig(): void;
|
|
245
254
|
static getTestDirectories(): string[];
|
|
246
255
|
static cleanupAllTestDirectories(): void;
|
|
256
|
+
enterGlobalMode(config: ExplorbotConfig, target: string | null): void;
|
|
247
257
|
buildEnvConfig(baseUrl: string | undefined, outputRoot: string): Promise<ExplorbotConfig>;
|
|
248
258
|
findConfigFile(): string | null;
|
|
249
259
|
loadConfigModule(configPath: string): Promise<any>;
|
|
@@ -258,13 +268,23 @@ export declare class ConfigParser {
|
|
|
258
268
|
}
|
|
259
269
|
export declare function outputPath(...segments: string[]): string;
|
|
260
270
|
export declare function resolveModel(spec: string, role?: ModelRole): Promise<any>;
|
|
261
|
-
export declare
|
|
271
|
+
export declare class ConfigMissingError extends Error {
|
|
272
|
+
}
|
|
273
|
+
export declare function envConfigRequested(): boolean;
|
|
274
|
+
export declare function missingConfigMessage(configFile?: string): string;
|
|
275
|
+
export declare function resolveConfigModels(ai?: AIConfig): Promise<void>;
|
|
276
|
+
export declare function resolveOutputRoot(baseUrl?: string): string;
|
|
277
|
+
export declare function resolveStateRoot(baseUrl: string, ephemeral?: boolean): string;
|
|
262
278
|
export declare function materializeKnowledge(outputRoot: string): void;
|
|
263
279
|
export declare function createModel(provider: string, modelId: string): Promise<any>;
|
|
264
280
|
type ModelRole = 'model' | 'visionModel' | 'agenticModel';
|
|
281
|
+
interface ProviderInfo {
|
|
282
|
+
envKey: string;
|
|
283
|
+
load: () => Promise<(modelId: string) => any>;
|
|
284
|
+
}
|
|
265
285
|
interface EnvVar {
|
|
266
286
|
name: string;
|
|
267
287
|
description: string;
|
|
268
288
|
required?: boolean;
|
|
269
289
|
}
|
|
270
|
-
export type { ModelRole, EnvVar };
|
|
290
|
+
export type { ModelRole, EnvVar, ProviderInfo };
|