explorbot 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -5
- package/bin/explorbot-cli.ts +29 -7
- package/boat/api-tester/src/config.ts +45 -3
- package/boat/doc-collector/src/ai/documentarian.ts +8 -2
- package/boat/doc-collector/src/ai/tools.ts +29 -16
- package/boat/doc-collector/src/cli.ts +2 -6
- package/boat/doc-collector/src/config.ts +2 -0
- package/boat/doc-collector/src/docbot.ts +42 -14
- package/boat/doc-collector/src/interaction-screenshots.ts +160 -0
- package/boat/doc-collector/src/screenshots.ts +24 -16
- package/dist/bin/explorbot-cli.js +26 -6
- package/dist/boat/api-tester/src/config.js +43 -4
- package/dist/boat/doc-collector/src/ai/documentarian.js +7 -2
- package/dist/boat/doc-collector/src/ai/tools.js +21 -15
- package/dist/boat/doc-collector/src/cli.js +2 -5
- package/dist/boat/doc-collector/src/config.js +1 -0
- package/dist/boat/doc-collector/src/docbot.js +39 -13
- package/dist/boat/doc-collector/src/interaction-screenshots.js +156 -0
- package/dist/boat/doc-collector/src/screenshots.js +25 -16
- package/dist/models.json +30 -0
- package/dist/package.json +8 -2
- package/dist/src/action.d.ts +11 -3
- package/dist/src/action.js +22 -5
- package/dist/src/ai/agent.d.ts +17 -0
- package/dist/src/ai/captain/idle-mode.js +1 -1
- package/dist/src/ai/captain/test-mode.js +1 -1
- package/dist/src/ai/captain/web-mode.js +18 -22
- package/dist/src/ai/captain.js +13 -26
- package/dist/src/ai/driller.d.ts +2 -13
- package/dist/src/ai/driller.js +23 -38
- package/dist/src/ai/historian/codeceptjs.js +1 -1
- package/dist/src/ai/historian/playwright.d.ts +3 -2
- package/dist/src/ai/historian/playwright.js +1 -1
- package/dist/src/ai/historian/screencast.d.ts +2 -1
- package/dist/src/ai/historian/screencast.js +2 -2
- package/dist/src/ai/historian.d.ts +5 -4
- package/dist/src/ai/navigator.d.ts +6 -2
- package/dist/src/ai/navigator.js +26 -22
- package/dist/src/ai/pilot.d.ts +13 -6
- package/dist/src/ai/pilot.js +49 -44
- package/dist/src/ai/planner.d.ts +3 -2
- package/dist/src/ai/planner.js +19 -17
- package/dist/src/ai/provider.js +0 -1
- package/dist/src/ai/quartermaster.d.ts +3 -2
- package/dist/src/ai/quartermaster.js +4 -4
- package/dist/src/ai/rerunner.d.ts +2 -11
- package/dist/src/ai/rerunner.js +9 -22
- package/dist/src/ai/researcher/coordinates.js +4 -3
- package/dist/src/ai/researcher/deep-analysis.d.ts +2 -0
- package/dist/src/ai/researcher/deep-analysis.js +6 -6
- package/dist/src/ai/researcher/locators.d.ts +9 -2
- package/dist/src/ai/researcher/locators.js +65 -8
- package/dist/src/ai/researcher/sections.d.ts +2 -0
- package/dist/src/ai/researcher/sections.js +2 -2
- package/dist/src/ai/researcher.js +29 -54
- package/dist/src/ai/task-agent.d.ts +18 -5
- package/dist/src/ai/task-agent.js +35 -6
- package/dist/src/ai/tester.d.ts +6 -12
- package/dist/src/ai/tester.js +37 -51
- package/dist/src/ai/tools.d.ts +11 -10
- package/dist/src/ai/tools.js +24 -36
- package/dist/src/command-handler.js +1 -1
- package/dist/src/commands/context-aria-command.js +1 -1
- package/dist/src/commands/context-command.js +7 -5
- package/dist/src/commands/context-data-command.js +2 -2
- package/dist/src/commands/context-experience-command.js +2 -2
- package/dist/src/commands/context-html-command.js +2 -2
- package/dist/src/commands/context-knowledge-command.js +1 -1
- package/dist/src/commands/drill-command.js +1 -1
- package/dist/src/commands/explore-command.js +4 -3
- package/dist/src/commands/freesail-command.js +2 -2
- package/dist/src/commands/learn-command.js +4 -4
- package/dist/src/commands/path-command.js +1 -1
- package/dist/src/commands/research-command.js +1 -1
- package/dist/src/commands/test-command.js +1 -1
- package/dist/src/components/App.js +1 -1
- package/dist/src/config.d.ts +17 -0
- package/dist/src/config.js +144 -10
- package/dist/src/experience-tracker.js +1 -1
- package/dist/src/explorbot.d.ts +17 -8
- package/dist/src/explorbot.js +74 -36
- package/dist/src/explorer.d.ts +67 -91
- package/dist/src/explorer.js +327 -567
- package/dist/src/state-manager.d.ts +6 -1
- package/dist/src/state-manager.js +1 -0
- package/dist/src/utils/hooks-runner.js +2 -4
- package/dist/src/utils/test-files.js +1 -1
- package/dist/src/utils/web-annotate.d.ts +5 -0
- package/dist/src/utils/web-annotate.js +58 -0
- package/dist/src/utils/web-eidx.d.ts +2 -0
- package/dist/src/utils/web-eidx.js +20 -0
- package/dist/src/utils/web-element.d.ts +1 -0
- package/dist/src/utils/web-element.js +29 -0
- package/models.json +30 -0
- package/package.json +8 -2
- package/src/action.ts +25 -5
- package/src/ai/agent.ts +20 -0
- package/src/ai/captain/idle-mode.ts +1 -1
- package/src/ai/captain/test-mode.ts +1 -1
- package/src/ai/captain/web-mode.ts +18 -23
- package/src/ai/captain.ts +12 -25
- package/src/ai/driller.ts +50 -67
- package/src/ai/historian/codeceptjs.ts +1 -1
- package/src/ai/historian/playwright.ts +4 -3
- package/src/ai/historian/screencast.ts +4 -3
- package/src/ai/historian.ts +3 -2
- package/src/ai/navigator.ts +30 -25
- package/src/ai/pilot.ts +55 -47
- package/src/ai/planner.ts +22 -20
- package/src/ai/provider.ts +0 -1
- package/src/ai/quartermaster.ts +5 -4
- package/src/ai/rerunner.ts +10 -29
- package/src/ai/researcher/coordinates.ts +4 -3
- package/src/ai/researcher/deep-analysis.ts +8 -6
- package/src/ai/researcher/locators.ts +66 -9
- package/src/ai/researcher/sections.ts +4 -2
- package/src/ai/researcher.ts +32 -61
- package/src/ai/task-agent.ts +45 -11
- package/src/ai/tester.ts +40 -57
- package/src/ai/tools.ts +32 -53
- package/src/command-handler.ts +1 -1
- package/src/commands/context-aria-command.ts +1 -1
- package/src/commands/context-command.ts +7 -5
- package/src/commands/context-data-command.ts +2 -2
- package/src/commands/context-experience-command.ts +2 -2
- package/src/commands/context-html-command.ts +2 -2
- package/src/commands/context-knowledge-command.ts +1 -1
- package/src/commands/drill-command.ts +1 -1
- package/src/commands/explore-command.ts +4 -3
- package/src/commands/freesail-command.ts +2 -2
- package/src/commands/learn-command.ts +4 -4
- package/src/commands/path-command.ts +1 -1
- package/src/commands/research-command.ts +1 -1
- package/src/commands/test-command.ts +1 -1
- package/src/components/App.tsx +1 -1
- package/src/config.ts +173 -11
- package/src/experience-tracker.ts +1 -1
- package/src/explorbot.ts +78 -39
- package/src/explorer.ts +388 -626
- package/src/state-manager.ts +7 -1
- package/src/utils/hooks-runner.ts +2 -4
- package/src/utils/test-files.ts +1 -1
- package/src/utils/web-annotate.ts +64 -0
- package/src/utils/web-eidx.ts +21 -0
- package/src/utils/web-element.ts +30 -0
|
@@ -6,7 +6,7 @@ export class ContextHtmlCommand extends BaseCommand {
|
|
|
6
6
|
description = 'Print combined HTML snapshot for current page';
|
|
7
7
|
async execute(_args) {
|
|
8
8
|
const explorer = this.explorBot.getExplorer();
|
|
9
|
-
const manager =
|
|
9
|
+
const manager = this.explorBot.stateManager();
|
|
10
10
|
const state = manager.getCurrentState();
|
|
11
11
|
if (!state) {
|
|
12
12
|
throw new Error('No active page to snapshot');
|
|
@@ -14,7 +14,7 @@ export class ContextHtmlCommand extends BaseCommand {
|
|
|
14
14
|
let actionResult = ActionResult.fromState(state);
|
|
15
15
|
if (!actionResult.html || actionResult.html.trim().length < 100) {
|
|
16
16
|
tag('info').log('Capturing fresh page content...');
|
|
17
|
-
actionResult = await explorer.
|
|
17
|
+
actionResult = await explorer.capture();
|
|
18
18
|
}
|
|
19
19
|
const html = await actionResult.combinedHtml();
|
|
20
20
|
if (!html) {
|
|
@@ -8,7 +8,7 @@ export class ContextKnowledgeCommand extends BaseCommand {
|
|
|
8
8
|
description = 'Print all matching knowledge for current page';
|
|
9
9
|
async execute(_args) {
|
|
10
10
|
const explorer = this.explorBot.getExplorer();
|
|
11
|
-
const state =
|
|
11
|
+
const state = this.explorBot.stateManager().getCurrentState();
|
|
12
12
|
if (!state) {
|
|
13
13
|
throw new Error('No active page');
|
|
14
14
|
}
|
|
@@ -10,7 +10,7 @@ export class DrillCommand extends BaseCommand {
|
|
|
10
10
|
async execute(args) {
|
|
11
11
|
const knowledgePath = this.parseKnowledgeArg(args);
|
|
12
12
|
const maxComponents = this.parseMaxArg(args);
|
|
13
|
-
const state = this.explorBot.
|
|
13
|
+
const state = this.explorBot.stateManager().getCurrentState();
|
|
14
14
|
if (!state) {
|
|
15
15
|
throw new Error('No active page to drill');
|
|
16
16
|
}
|
|
@@ -4,6 +4,7 @@ import { outputPath } from '../config.js';
|
|
|
4
4
|
import { normalizeUrl } from '../state-manager.js';
|
|
5
5
|
import { Stats } from '../stats.js';
|
|
6
6
|
import { TestResult } from '../test-plan.js';
|
|
7
|
+
import { browserErrorMessage } from "../utils/browser-errors.js";
|
|
7
8
|
import { getCliName } from "../utils/cli-name.js";
|
|
8
9
|
import { ErrorPageError, getStateErrorPageError } from "../utils/error-page.js";
|
|
9
10
|
import { tag } from '../utils/logger.js';
|
|
@@ -34,7 +35,7 @@ export class ExploreCommand extends BaseCommand {
|
|
|
34
35
|
oldTestRefs = new Set();
|
|
35
36
|
priorityFilter;
|
|
36
37
|
getCurrentPageUrl() {
|
|
37
|
-
const state = this.explorBot.
|
|
38
|
+
const state = this.explorBot.stateManager().getCurrentState();
|
|
38
39
|
return state?.fullUrl || state?.url;
|
|
39
40
|
}
|
|
40
41
|
async execute(args) {
|
|
@@ -53,7 +54,7 @@ export class ExploreCommand extends BaseCommand {
|
|
|
53
54
|
Stats.mode ??= 'explore';
|
|
54
55
|
Stats.focus ??= feature;
|
|
55
56
|
const mainUrl = this.getCurrentPageUrl();
|
|
56
|
-
const error = getStateErrorPageError(this.explorBot.
|
|
57
|
+
const error = getStateErrorPageError(this.explorBot.stateManager().getCurrentState());
|
|
57
58
|
if (error) {
|
|
58
59
|
tag('warning').log(error.message);
|
|
59
60
|
return;
|
|
@@ -72,7 +73,7 @@ export class ExploreCommand extends BaseCommand {
|
|
|
72
73
|
return;
|
|
73
74
|
}
|
|
74
75
|
if (mainUrl)
|
|
75
|
-
await this.explorBot.visit(mainUrl);
|
|
76
|
+
await this.explorBot.visit(mainUrl).catch((err) => tag('warning').log(`Could not return to ${mainUrl}: ${browserErrorMessage(err)}`));
|
|
76
77
|
const savedPath = this.explorBot.savePlans(this.completedPlans);
|
|
77
78
|
this.printResults();
|
|
78
79
|
await this.explorBot.printSessionAnalysis();
|
|
@@ -31,7 +31,7 @@ export class FreesailCommand extends BaseCommand {
|
|
|
31
31
|
await loop(async (ctx) => {
|
|
32
32
|
if (maxTests != null && testsRun >= maxTests)
|
|
33
33
|
ctx.stop();
|
|
34
|
-
const stateManager = this.explorBot.
|
|
34
|
+
const stateManager = this.explorBot.stateManager();
|
|
35
35
|
const state = stateManager.getCurrentState();
|
|
36
36
|
if (state && !Researcher.getCachedResearch(state)) {
|
|
37
37
|
await this.explorBot.agentResearcher().research(state, { deep: true, screenshot: true });
|
|
@@ -63,7 +63,7 @@ export class FreesailCommand extends BaseCommand {
|
|
|
63
63
|
return;
|
|
64
64
|
}
|
|
65
65
|
tag('info').log(`Navigating to: ${suggestion.target} - ${suggestion.reason}`);
|
|
66
|
-
await this.explorBot.
|
|
66
|
+
await this.explorBot.openTab();
|
|
67
67
|
await this.explorBot.visit(suggestion.target);
|
|
68
68
|
this.explorBot.clearPlan();
|
|
69
69
|
}, { maxAttempts: Number.POSITIVE_INFINITY });
|
|
@@ -11,11 +11,11 @@ export class LearnCommand extends BaseCommand {
|
|
|
11
11
|
if (!note) {
|
|
12
12
|
const AddKnowledge = (await import('../components/AddKnowledge.js')).default;
|
|
13
13
|
const explorer = this.explorBot.getExplorer();
|
|
14
|
-
const state =
|
|
14
|
+
const state = this.explorBot.stateManager().getCurrentState();
|
|
15
15
|
const initialUrl = state?.url || '';
|
|
16
16
|
const { unmount } = render(React.createElement(AddKnowledge, {
|
|
17
17
|
initialUrl,
|
|
18
|
-
knowledgeTracker:
|
|
18
|
+
knowledgeTracker: this.explorBot.knowledgeTracker(),
|
|
19
19
|
onComplete: () => unmount(),
|
|
20
20
|
onCancel: () => unmount(),
|
|
21
21
|
}), {
|
|
@@ -25,12 +25,12 @@ export class LearnCommand extends BaseCommand {
|
|
|
25
25
|
return;
|
|
26
26
|
}
|
|
27
27
|
const explorer = this.explorBot.getExplorer();
|
|
28
|
-
const state =
|
|
28
|
+
const state = this.explorBot.stateManager().getCurrentState();
|
|
29
29
|
if (!state) {
|
|
30
30
|
throw new Error('No active page to attach knowledge');
|
|
31
31
|
}
|
|
32
32
|
const targetUrl = state.url || state.fullUrl || '/';
|
|
33
|
-
|
|
33
|
+
this.explorBot.knowledgeTracker().addKnowledge(targetUrl, note);
|
|
34
34
|
tag('success').log('Knowledge saved for current page');
|
|
35
35
|
}
|
|
36
36
|
}
|
|
@@ -7,7 +7,7 @@ export class PathCommand extends BaseCommand {
|
|
|
7
7
|
async execute(args) {
|
|
8
8
|
const { opts } = this.parseArgs(args);
|
|
9
9
|
const showLinks = !!opts.links;
|
|
10
|
-
const stateManager = this.explorBot.
|
|
10
|
+
const stateManager = this.explorBot.stateManager();
|
|
11
11
|
const history = stateManager.getStateHistory();
|
|
12
12
|
if (history.length === 0) {
|
|
13
13
|
tag('multiline').log('No navigation history yet.');
|
|
@@ -23,7 +23,7 @@ export class ResearchCommand extends BaseCommand {
|
|
|
23
23
|
if (target) {
|
|
24
24
|
await this.explorBot.agentNavigator().visit(target);
|
|
25
25
|
}
|
|
26
|
-
const state = this.explorBot.
|
|
26
|
+
const state = this.explorBot.stateManager().getCurrentState();
|
|
27
27
|
if (!state) {
|
|
28
28
|
throw new Error('No active page to research');
|
|
29
29
|
}
|
|
@@ -48,7 +48,7 @@ export class TestCommand extends BaseCommand {
|
|
|
48
48
|
toExecute.push(...matching);
|
|
49
49
|
}
|
|
50
50
|
else {
|
|
51
|
-
const state = this.explorBot.
|
|
51
|
+
const state = this.explorBot.stateManager().getCurrentState();
|
|
52
52
|
if (!state) {
|
|
53
53
|
throw new Error('No page loaded. Please navigate to a page first.');
|
|
54
54
|
}
|
|
@@ -47,7 +47,7 @@ export function App({ explorBot, initialShowInput = false, exitOnEmptyInput = fa
|
|
|
47
47
|
userInputPromiseRef.current = { resolve, reject };
|
|
48
48
|
});
|
|
49
49
|
});
|
|
50
|
-
const manager = explorBot.
|
|
50
|
+
const manager = explorBot.stateManager();
|
|
51
51
|
unsubscribe = manager.onStateChange((transition) => {
|
|
52
52
|
if (mounted) {
|
|
53
53
|
setLastTransition(transition);
|
package/dist/src/config.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export declare const PROVIDERS: Record<string, () => Promise<(modelId: string) => any>>;
|
|
1
2
|
interface PlaywrightConfig {
|
|
2
3
|
browser: 'chromium' | 'firefox' | 'webkit';
|
|
3
4
|
url: string;
|
|
@@ -204,6 +205,7 @@ interface ExplorbotConfig {
|
|
|
204
205
|
};
|
|
205
206
|
experience?: {
|
|
206
207
|
maxReadLines?: number;
|
|
208
|
+
disabled?: boolean;
|
|
207
209
|
};
|
|
208
210
|
reporter?: ReporterConfig;
|
|
209
211
|
api?: ApiConfig;
|
|
@@ -213,14 +215,17 @@ interface ExplorbotConfig {
|
|
|
213
215
|
}
|
|
214
216
|
type RuleEntry = string | Record<string, string>;
|
|
215
217
|
export declare const EXPLORBOT_CONFIG_PATHS: string[];
|
|
218
|
+
export declare const EXPLORBOT_ENV_VARS: EnvVar[];
|
|
216
219
|
export type { ExplorbotConfig, PlaywrightConfig, AIConfig, HtmlConfig, ActionConfig, AgentConfig, AgentsConfig, HistorianAgentConfig, ResearcherAgentConfig, NavigatorAgentConfig, PlannerAgentConfig, RerunnerAgentConfig, HealRecipe, Hook, HookConfig, HooksConfig, PlaywrightHook, CodeceptJSHook, HookPatternMap, RuleEntry, ReporterConfig, ApiConfig, WebConfig, ApiHookFn, };
|
|
217
220
|
export declare class ConfigParser {
|
|
218
221
|
static instance: ConfigParser;
|
|
222
|
+
static recommended: Record<string, Record<string, string>> | null;
|
|
219
223
|
config: ExplorbotConfig | null;
|
|
220
224
|
configPath: string | null;
|
|
221
225
|
runtimeBaseUrlOverride: string | null;
|
|
222
226
|
constructor();
|
|
223
227
|
static loadEnv(filePath: string): void;
|
|
228
|
+
static recommendedModels(): Record<string, Record<string, string>>;
|
|
224
229
|
static getInstance(): ConfigParser;
|
|
225
230
|
loadConfig(options?: {
|
|
226
231
|
config?: string;
|
|
@@ -239,6 +244,7 @@ export declare class ConfigParser {
|
|
|
239
244
|
static setupTestConfig(): void;
|
|
240
245
|
static getTestDirectories(): string[];
|
|
241
246
|
static cleanupAllTestDirectories(): void;
|
|
247
|
+
buildEnvConfig(baseUrl: string | undefined, outputRoot: string): Promise<ExplorbotConfig>;
|
|
242
248
|
findConfigFile(): string | null;
|
|
243
249
|
loadConfigModule(configPath: string): Promise<any>;
|
|
244
250
|
resolveConfig(config: ExplorbotConfig, options?: {
|
|
@@ -251,3 +257,14 @@ export declare class ConfigParser {
|
|
|
251
257
|
ensureDirectory(path: string): void;
|
|
252
258
|
}
|
|
253
259
|
export declare function outputPath(...segments: string[]): string;
|
|
260
|
+
export declare function resolveModel(spec: string, role?: ModelRole): Promise<any>;
|
|
261
|
+
export declare function resolveOutputRoot(): string;
|
|
262
|
+
export declare function materializeKnowledge(outputRoot: string): void;
|
|
263
|
+
export declare function createModel(provider: string, modelId: string): Promise<any>;
|
|
264
|
+
type ModelRole = 'model' | 'visionModel' | 'agenticModel';
|
|
265
|
+
interface EnvVar {
|
|
266
|
+
name: string;
|
|
267
|
+
description: string;
|
|
268
|
+
required?: boolean;
|
|
269
|
+
}
|
|
270
|
+
export type { ModelRole, EnvVar };
|
package/dist/src/config.js
CHANGED
|
@@ -6,10 +6,22 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
6
6
|
}
|
|
7
7
|
return path;
|
|
8
8
|
};
|
|
9
|
-
import { existsSync, mkdirSync, readFileSync, rmSync } from 'node:fs';
|
|
10
|
-
import
|
|
9
|
+
import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
10
|
+
import { tmpdir } from 'node:os';
|
|
11
|
+
import path, { basename, dirname, join, resolve } from 'node:path';
|
|
11
12
|
import { parseEnv } from 'node:util';
|
|
13
|
+
import matter from 'gray-matter';
|
|
12
14
|
import { log } from './utils/logger.js';
|
|
15
|
+
export const PROVIDERS = {
|
|
16
|
+
openai: async () => (await import('@ai-sdk/openai')).createOpenAI(),
|
|
17
|
+
anthropic: async () => (await import('@ai-sdk/anthropic')).createAnthropic(),
|
|
18
|
+
google: async () => (await import('@ai-sdk/google')).createGoogleGenerativeAI(),
|
|
19
|
+
groq: async () => (await import('@ai-sdk/groq')).createGroq(),
|
|
20
|
+
mistral: async () => (await import('@ai-sdk/mistral')).createMistral(),
|
|
21
|
+
openrouter: async () => (await import('@openrouter/ai-sdk-provider')).createOpenRouter(),
|
|
22
|
+
sambanova: async () => (await import('sambanova-ai-provider')).createSambaNova(),
|
|
23
|
+
};
|
|
24
|
+
let cachedOutputRoot = null;
|
|
13
25
|
const config = {
|
|
14
26
|
playwright: {
|
|
15
27
|
browser: 'chromium',
|
|
@@ -20,8 +32,21 @@ const config = {
|
|
|
20
32
|
},
|
|
21
33
|
};
|
|
22
34
|
export const EXPLORBOT_CONFIG_PATHS = ['explorbot.config.js', 'explorbot.config.mjs', 'explorbot.config.ts'];
|
|
35
|
+
export const EXPLORBOT_ENV_VARS = [
|
|
36
|
+
{ name: 'EXPLORBOT_AI_PROVIDER', required: true, description: 'Provider name; fills every model role from its recommended models. Turns on config-free mode' },
|
|
37
|
+
{ name: 'EXPLORBOT_AI_MODEL', description: 'Pins the main model — a model id for the provider, or a standalone provider/model-id' },
|
|
38
|
+
{ name: 'EXPLORBOT_URL', required: true, description: 'Base URL to test; the API boat reads it as the base endpoint' },
|
|
39
|
+
{ name: 'EXPLORBOT_VISION_MODEL', description: 'Screenshot analysis; overrides the provider recommendation' },
|
|
40
|
+
{ name: 'EXPLORBOT_AGENTIC_MODEL', description: 'Captain and Pilot decisions; overrides the provider recommendation' },
|
|
41
|
+
{ name: 'EXPLORBOT_OUTPUT', description: 'Output root for states, plans, research, and reports. Defaults to a fresh temp directory' },
|
|
42
|
+
{ name: 'EXPLORBOT_KNOWLEDGE', description: 'Inline knowledge text, applied to every page' },
|
|
43
|
+
{ name: 'EXPLORBOT_KNOWLEDGE_FILE', description: 'Path to a knowledge markdown file' },
|
|
44
|
+
{ name: 'EXPLORBOT_API_SPEC', description: 'OpenAPI spec path for the API boat' },
|
|
45
|
+
{ name: 'EXPLORBOT_NO_BANNER', description: 'Suppress the startup banner, for machine-readable output' },
|
|
46
|
+
];
|
|
23
47
|
export class ConfigParser {
|
|
24
48
|
static instance;
|
|
49
|
+
static recommended = null;
|
|
25
50
|
config = null;
|
|
26
51
|
configPath = null;
|
|
27
52
|
runtimeBaseUrlOverride = null;
|
|
@@ -32,6 +57,10 @@ export class ConfigParser {
|
|
|
32
57
|
return;
|
|
33
58
|
Object.assign(process.env, parseEnv(readFileSync(resolved, 'utf8')));
|
|
34
59
|
}
|
|
60
|
+
static recommendedModels() {
|
|
61
|
+
ConfigParser.recommended ||= JSON.parse(readFileSync(new URL('../models.json', import.meta.url), 'utf8'));
|
|
62
|
+
return ConfigParser.recommended;
|
|
63
|
+
}
|
|
35
64
|
static getInstance() {
|
|
36
65
|
if (!ConfigParser.instance) {
|
|
37
66
|
ConfigParser.instance = new ConfigParser();
|
|
@@ -54,18 +83,25 @@ export class ConfigParser {
|
|
|
54
83
|
ConfigParser.loadEnv('.env');
|
|
55
84
|
try {
|
|
56
85
|
const resolvedPath = options?.config || this.findConfigFile();
|
|
57
|
-
|
|
58
|
-
|
|
86
|
+
let loadedConfig = null;
|
|
87
|
+
let sourcePath = resolvedPath;
|
|
88
|
+
if (resolvedPath) {
|
|
89
|
+
const configModule = await this.loadConfigModule(resolvedPath);
|
|
90
|
+
loadedConfig = configModule.default || configModule;
|
|
91
|
+
if (!loadedConfig) {
|
|
92
|
+
throw new Error('Configuration file is empty or invalid');
|
|
93
|
+
}
|
|
94
|
+
log(`Configuration loaded from: ${resolvedPath}`);
|
|
59
95
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
96
|
+
if (!resolvedPath) {
|
|
97
|
+
const outputRoot = resolveOutputRoot();
|
|
98
|
+
loadedConfig = await this.buildEnvConfig(options?.baseUrl, outputRoot);
|
|
99
|
+
sourcePath = join(outputRoot, 'explorbot.config.js');
|
|
100
|
+
log(`Configuration built from EXPLORBOT_* environment variables. Output: ${outputRoot}`);
|
|
64
101
|
}
|
|
65
102
|
this.config = this.resolveConfig(loadedConfig, options);
|
|
66
103
|
this.runtimeBaseUrlOverride = options?.baseUrl || null;
|
|
67
|
-
this.configPath =
|
|
68
|
-
log(`Configuration loaded from: ${resolvedPath}`);
|
|
104
|
+
this.configPath = sourcePath;
|
|
69
105
|
// Restore original directory after successful config load
|
|
70
106
|
if (options?.path && originalCwd !== process.cwd()) {
|
|
71
107
|
process.chdir(originalCwd);
|
|
@@ -119,6 +155,7 @@ export class ConfigParser {
|
|
|
119
155
|
}
|
|
120
156
|
// For testing purposes only
|
|
121
157
|
static resetForTesting() {
|
|
158
|
+
cachedOutputRoot = null;
|
|
122
159
|
if (ConfigParser.instance) {
|
|
123
160
|
ConfigParser.instance.config = null;
|
|
124
161
|
ConfigParser.instance.configPath = null;
|
|
@@ -169,6 +206,51 @@ export class ConfigParser {
|
|
|
169
206
|
// Ignore cleanup errors
|
|
170
207
|
}
|
|
171
208
|
}
|
|
209
|
+
async buildEnvConfig(baseUrl, outputRoot) {
|
|
210
|
+
const provider = process.env.EXPLORBOT_AI_PROVIDER;
|
|
211
|
+
const modelSpec = process.env.EXPLORBOT_AI_MODEL;
|
|
212
|
+
if (!provider && !modelSpec) {
|
|
213
|
+
throw new Error('No configuration file found. Please create explorbot.config.js or set EXPLORBOT_URL and EXPLORBOT_AI_PROVIDER environment variables');
|
|
214
|
+
}
|
|
215
|
+
if (modelSpec && !provider && !modelSpec.includes('/')) {
|
|
216
|
+
throw new Error('EXPLORBOT_AI_MODEL needs a provider — set EXPLORBOT_AI_PROVIDER, or write it as "provider/model-id"');
|
|
217
|
+
}
|
|
218
|
+
const url = process.env.EXPLORBOT_URL || baseUrl;
|
|
219
|
+
if (!url) {
|
|
220
|
+
throw new Error('No URL to explore. Set EXPLORBOT_URL or pass a URL to the command');
|
|
221
|
+
}
|
|
222
|
+
materializeKnowledge(outputRoot);
|
|
223
|
+
let model;
|
|
224
|
+
if (provider && modelSpec)
|
|
225
|
+
model = await createModel(provider, modelSpec);
|
|
226
|
+
if (provider && !modelSpec)
|
|
227
|
+
model = await resolveModel(provider, 'model');
|
|
228
|
+
if (!provider)
|
|
229
|
+
model = await resolveModel(modelSpec, 'model');
|
|
230
|
+
const ai = {
|
|
231
|
+
model,
|
|
232
|
+
agents: { historian: { enabled: false } },
|
|
233
|
+
};
|
|
234
|
+
let recommended = {};
|
|
235
|
+
if (provider)
|
|
236
|
+
recommended = ConfigParser.recommendedModels()[provider] || {};
|
|
237
|
+
const visionSpec = process.env.EXPLORBOT_VISION_MODEL;
|
|
238
|
+
if (visionSpec)
|
|
239
|
+
ai.visionModel = await resolveModel(visionSpec, 'visionModel');
|
|
240
|
+
if (!visionSpec && recommended.visionModel)
|
|
241
|
+
ai.visionModel = await resolveModel(provider, 'visionModel');
|
|
242
|
+
const agenticSpec = process.env.EXPLORBOT_AGENTIC_MODEL;
|
|
243
|
+
if (agenticSpec)
|
|
244
|
+
ai.agenticModel = await resolveModel(agenticSpec, 'agenticModel');
|
|
245
|
+
if (!agenticSpec && recommended.agenticModel)
|
|
246
|
+
ai.agenticModel = await resolveModel(provider, 'agenticModel');
|
|
247
|
+
return {
|
|
248
|
+
playwright: { browser: 'chromium', url, show: false },
|
|
249
|
+
ai,
|
|
250
|
+
dirs: { knowledge: 'knowledge', experience: 'experience', output: '.' },
|
|
251
|
+
experience: { disabled: true },
|
|
252
|
+
};
|
|
253
|
+
}
|
|
172
254
|
findConfigFile() {
|
|
173
255
|
const possiblePaths = [...EXPLORBOT_CONFIG_PATHS, 'config/explorbot.config.js', 'config/explorbot.config.mjs', 'config/explorbot.config.ts', 'src/config/explorbot.config.js', 'src/config/explorbot.config.mjs', 'src/config/explorbot.config.ts'];
|
|
174
256
|
for (const path of possiblePaths) {
|
|
@@ -268,3 +350,55 @@ export class ConfigParser {
|
|
|
268
350
|
export function outputPath(...segments) {
|
|
269
351
|
return path.join(ConfigParser.getInstance().getOutputDir(), ...segments);
|
|
270
352
|
}
|
|
353
|
+
export async function resolveModel(spec, role = 'model') {
|
|
354
|
+
const separator = spec.indexOf('/');
|
|
355
|
+
if (separator > 0) {
|
|
356
|
+
return createModel(spec.slice(0, separator), spec.slice(separator + 1));
|
|
357
|
+
}
|
|
358
|
+
const recommended = ConfigParser.recommendedModels()[spec];
|
|
359
|
+
if (!recommended) {
|
|
360
|
+
throw new Error(`No recommended models for "${spec}". Write it as "provider/model-id", or use a provider with recommendations: ${Object.keys(ConfigParser.recommendedModels()).join(', ')}`);
|
|
361
|
+
}
|
|
362
|
+
const modelId = recommended[role];
|
|
363
|
+
if (!modelId) {
|
|
364
|
+
throw new Error(`Provider "${spec}" has no recommended ${role}. Set it explicitly as "provider/model-id".`);
|
|
365
|
+
}
|
|
366
|
+
return createModel(spec, modelId);
|
|
367
|
+
}
|
|
368
|
+
export function resolveOutputRoot() {
|
|
369
|
+
if (cachedOutputRoot)
|
|
370
|
+
return cachedOutputRoot;
|
|
371
|
+
const configured = process.env.EXPLORBOT_OUTPUT;
|
|
372
|
+
if (!configured) {
|
|
373
|
+
cachedOutputRoot = mkdtempSync(join(tmpdir(), 'explorbot-'));
|
|
374
|
+
return cachedOutputRoot;
|
|
375
|
+
}
|
|
376
|
+
cachedOutputRoot = resolve(configured);
|
|
377
|
+
mkdirSync(cachedOutputRoot, { recursive: true });
|
|
378
|
+
return cachedOutputRoot;
|
|
379
|
+
}
|
|
380
|
+
export function materializeKnowledge(outputRoot) {
|
|
381
|
+
const inline = process.env.EXPLORBOT_KNOWLEDGE;
|
|
382
|
+
const knowledgeFile = process.env.EXPLORBOT_KNOWLEDGE_FILE;
|
|
383
|
+
if (!inline && !knowledgeFile)
|
|
384
|
+
return;
|
|
385
|
+
const knowledgeDir = join(outputRoot, 'knowledge');
|
|
386
|
+
mkdirSync(knowledgeDir, { recursive: true });
|
|
387
|
+
if (inline) {
|
|
388
|
+
writeFileSync(join(knowledgeDir, 'global.md'), matter.stringify(inline, { url: '*', endpoint: '*' }));
|
|
389
|
+
}
|
|
390
|
+
if (!knowledgeFile)
|
|
391
|
+
return;
|
|
392
|
+
const source = resolve(knowledgeFile);
|
|
393
|
+
if (!existsSync(source)) {
|
|
394
|
+
throw new Error(`Knowledge file from EXPLORBOT_KNOWLEDGE_FILE not found: ${source}`);
|
|
395
|
+
}
|
|
396
|
+
copyFileSync(source, join(knowledgeDir, basename(source)));
|
|
397
|
+
}
|
|
398
|
+
export async function createModel(provider, modelId) {
|
|
399
|
+
const factory = PROVIDERS[provider];
|
|
400
|
+
if (!factory) {
|
|
401
|
+
throw new Error(`Unknown AI provider "${provider}". Supported providers: ${Object.keys(PROVIDERS).join(', ')}`);
|
|
402
|
+
}
|
|
403
|
+
return (await factory())(modelId);
|
|
404
|
+
}
|
|
@@ -35,7 +35,7 @@ export class ExperienceTracker {
|
|
|
35
35
|
constructor(knowledgeTracker, options = {}) {
|
|
36
36
|
const configParser = ConfigParser.getInstance();
|
|
37
37
|
const config = configParser.getConfig();
|
|
38
|
-
this.disabled = options.disabled ?? false;
|
|
38
|
+
this.disabled = options.disabled ?? config.experience?.disabled ?? false;
|
|
39
39
|
this.knowledgeTracker = knowledgeTracker;
|
|
40
40
|
// Resolve experience directory relative to the config file location (project root)
|
|
41
41
|
this.experienceDir = configParser.resolveProjectDir(config.dirs?.experience || 'experience');
|
package/dist/src/explorbot.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AgentDeps } from './ai/agent.js';
|
|
1
2
|
import { Captain } from './ai/captain.js';
|
|
2
3
|
import { Driller } from './ai/driller.js';
|
|
3
4
|
import { ExperienceCompactor } from './ai/experience-compactor.js';
|
|
@@ -12,12 +13,15 @@ import { Rerunner } from './ai/rerunner.js';
|
|
|
12
13
|
import { Researcher } from './ai/researcher.js';
|
|
13
14
|
import { SessionAnalyst } from './ai/session-analyst.js';
|
|
14
15
|
import { Tester } from './ai/tester.js';
|
|
16
|
+
import { RequestStore } from './api/request-store.js';
|
|
15
17
|
import type { ExplorbotConfig } from './config.js';
|
|
16
18
|
import { ConfigParser } from './config.js';
|
|
17
19
|
import { ExperienceTracker } from './experience-tracker.js';
|
|
18
20
|
import Explorer from './explorer.js';
|
|
19
21
|
import { KnowledgeTracker } from './knowledge-tracker.js';
|
|
20
|
-
import {
|
|
22
|
+
import { PlaywrightRecorder } from './playwright-recorder.js';
|
|
23
|
+
import { Reporter } from './reporter.js';
|
|
24
|
+
import { StateManager, WebPageState } from './state-manager.js';
|
|
21
25
|
import type { Suite } from './suite.js';
|
|
22
26
|
import { Plan, type Test } from './test-plan.js';
|
|
23
27
|
export interface ExplorBotOptions {
|
|
@@ -29,7 +33,7 @@ export interface ExplorBotOptions {
|
|
|
29
33
|
show?: boolean;
|
|
30
34
|
headless?: boolean;
|
|
31
35
|
incognito?: boolean;
|
|
32
|
-
session?: string;
|
|
36
|
+
session?: string | boolean;
|
|
33
37
|
}
|
|
34
38
|
export type UserResolveFunction = (error?: Error, showWelcome?: boolean) => Promise<string | null>;
|
|
35
39
|
export declare class ExplorBot {
|
|
@@ -48,6 +52,10 @@ export declare class ExplorBot {
|
|
|
48
52
|
lastReportedTestCount: number;
|
|
49
53
|
_experienceTracker?: ExperienceTracker;
|
|
50
54
|
_knowledgeTracker?: KnowledgeTracker;
|
|
55
|
+
_stateManager?: StateManager;
|
|
56
|
+
_reporter?: Reporter;
|
|
57
|
+
_requestStore?: RequestStore;
|
|
58
|
+
_playwrightRecorder?: PlaywrightRecorder;
|
|
51
59
|
constructor(options?: ExplorBotOptions);
|
|
52
60
|
get isExploring(): boolean;
|
|
53
61
|
setUserResolve(fn: UserResolveFunction): void;
|
|
@@ -56,19 +64,19 @@ export declare class ExplorBot {
|
|
|
56
64
|
stop(): Promise<void>;
|
|
57
65
|
visitInitialState(): Promise<void>;
|
|
58
66
|
visit(url: string): Promise<void>;
|
|
59
|
-
|
|
67
|
+
openTab(): Promise<void>;
|
|
60
68
|
getCurrentState(): WebPageState | null;
|
|
61
69
|
getExplorer(): Explorer;
|
|
62
70
|
knowledgeTracker(): KnowledgeTracker;
|
|
63
71
|
experienceTracker(): ExperienceTracker;
|
|
72
|
+
stateManager(): StateManager;
|
|
73
|
+
reporter(): Reporter;
|
|
74
|
+
requestStore(): RequestStore;
|
|
75
|
+
playwrightRecorder(): PlaywrightRecorder;
|
|
64
76
|
getConfig(): ExplorbotConfig;
|
|
65
77
|
getOptions(): ExplorBotOptions;
|
|
66
78
|
getProvider(): AIProvider;
|
|
67
|
-
createAgent<T>(factory: (deps:
|
|
68
|
-
explorer: Explorer;
|
|
69
|
-
ai: AIProvider;
|
|
70
|
-
config: ExplorbotConfig;
|
|
71
|
-
}) => T): T;
|
|
79
|
+
createAgent<T>(factory: (deps: AgentDeps) => T): T;
|
|
72
80
|
agentResearcher(): Researcher;
|
|
73
81
|
agentNavigator(): Navigator;
|
|
74
82
|
agentPlanner(): Planner;
|
|
@@ -103,4 +111,5 @@ export declare class ExplorBot {
|
|
|
103
111
|
setCurrentPlan(plan?: Plan): void;
|
|
104
112
|
getSessionTests(): Test[];
|
|
105
113
|
printSessionAnalysis(): Promise<void>;
|
|
114
|
+
isHistorianEnabled(): boolean;
|
|
106
115
|
}
|