explorbot 0.4.0 → 0.4.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/bin/explorbot-cli.ts +13 -6
- package/boat/api-tester/src/ai/chief.ts +7 -1
- package/boat/api-tester/src/ai/curler-tools.ts +1 -1
- package/boat/api-tester/src/ai/curler.ts +7 -1
- package/boat/api-tester/src/apibot.ts +10 -4
- package/boat/api-tester/src/cli.ts +15 -2
- package/boat/api-tester/src/config.ts +28 -8
- package/boat/doc-collector/bin/doc-collector-cli.ts +3 -2
- package/boat/doc-collector/src/ai/documentarian.ts +57 -5
- package/boat/doc-collector/src/ai/tools.ts +1 -1
- package/boat/doc-collector/src/cli.ts +21 -3
- package/boat/doc-collector/src/config.ts +3 -0
- package/boat/doc-collector/src/docbot.ts +57 -12
- package/boat/doc-collector/src/docs-renderer.ts +79 -55
- package/boat/doc-collector/src/interaction-screenshots.ts +19 -10
- package/boat/doc-collector/src/screenshots.ts +106 -3
- package/boat/doc-collector/src/template-dedup.ts +31 -0
- package/boat/prima/bin/prima-cli.ts +2 -0
- package/boat/prima/src/cli.ts +5 -0
- package/boat/prima/src/prima.ts +3 -4
- package/dist/bin/explorbot-cli.js +14 -6
- package/dist/boat/api-tester/bin/apibot-cli.js +3 -2
- package/dist/boat/api-tester/src/ai/chief.js +6 -1
- package/dist/boat/api-tester/src/ai/curler-tools.js +1 -1
- package/dist/boat/api-tester/src/ai/curler.js +6 -1
- package/dist/boat/api-tester/src/apibot.js +7 -3
- package/dist/boat/api-tester/src/cli.js +14 -2
- package/dist/boat/api-tester/src/config.js +31 -8
- package/dist/boat/doc-collector/bin/doc-collector-cli.js +3 -2
- package/dist/boat/doc-collector/src/ai/documentarian.js +62 -4
- package/dist/boat/doc-collector/src/ai/tools.js +1 -1
- package/dist/boat/doc-collector/src/cli.js +18 -3
- package/dist/boat/doc-collector/src/config.js +1 -0
- package/dist/boat/doc-collector/src/docbot.js +53 -11
- package/dist/boat/doc-collector/src/docs-renderer.js +69 -46
- package/dist/boat/doc-collector/src/interaction-screenshots.js +18 -11
- package/dist/boat/doc-collector/src/screenshots.js +104 -1
- package/dist/boat/doc-collector/src/template-dedup.js +27 -0
- package/dist/boat/prima/bin/prima-cli.js +2 -0
- package/dist/boat/prima/src/cli.js +5 -0
- package/dist/boat/prima/src/prima.js +3 -4
- package/dist/package.json +1 -1
- package/dist/rules/planner/styles/normal.md +1 -1
- package/dist/src/action-result.d.ts +2 -2
- package/dist/src/action-result.js +15 -17
- package/dist/src/action.d.ts +1 -1
- package/dist/src/action.js +15 -15
- package/dist/src/ai/captain.js +1 -1
- package/dist/src/ai/fisherman/request-haul.d.ts +11 -0
- package/dist/src/ai/fisherman/request-haul.js +28 -0
- package/dist/src/ai/fisherman-tools.d.ts +10 -1
- package/dist/src/ai/fisherman-tools.js +35 -31
- package/dist/src/ai/fisherman.d.ts +3 -2
- package/dist/src/ai/fisherman.js +8 -6
- package/dist/src/ai/navigator.js +1 -1
- package/dist/src/ai/pilot.js +21 -16
- package/dist/src/ai/planner.js +9 -7
- package/dist/src/ai/provider.js +8 -14
- package/dist/src/ai/researcher/coordinates.d.ts +2 -0
- package/dist/src/ai/researcher/coordinates.js +13 -1
- package/dist/src/ai/researcher/deep-analysis.js +9 -9
- package/dist/src/ai/researcher/fingerprint-worker.js +1 -11
- package/dist/src/ai/researcher.js +3 -1
- package/dist/src/ai/rules.js +11 -3
- package/dist/src/ai/tester.js +20 -20
- package/dist/src/ai/tools.d.ts +1 -1
- package/dist/src/ai/tools.js +18 -74
- package/dist/src/api/request-store.d.ts +1 -5
- package/dist/src/api/request-store.js +12 -22
- package/dist/src/api/spec-reader.js +1 -1
- package/dist/src/commands/config-command.js +3 -10
- package/dist/src/commands/drill-command.js +1 -1
- package/dist/src/commands/explore-command.js +12 -1
- package/dist/src/commands/index.js +2 -0
- package/dist/src/commands/init-command.js +3 -7
- package/dist/src/commands/options/base-option.d.ts +8 -0
- package/dist/src/commands/options/base-option.js +12 -0
- package/dist/src/commands/options/index.d.ts +5 -0
- package/dist/src/commands/options/index.js +5 -0
- package/dist/src/commands/options/knowledge-option.d.ts +7 -0
- package/dist/src/commands/options/knowledge-option.js +12 -0
- package/dist/src/commands/options/ws-option.d.ts +7 -0
- package/dist/src/commands/options/ws-option.js +21 -0
- package/dist/src/commands/plans-command.d.ts +4 -1
- package/dist/src/commands/plans-command.js +4 -4
- package/dist/src/commands/recommended-models-command.d.ts +9 -0
- package/dist/src/commands/recommended-models-command.js +85 -0
- package/dist/src/config.d.ts +3 -0
- package/dist/src/config.js +18 -2
- package/dist/src/experience-tracker.js +1 -1
- package/dist/src/explorbot.js +4 -4
- package/dist/src/global-config.d.ts +2 -0
- package/dist/src/global-config.js +6 -0
- package/dist/src/knowledge-tracker.d.ts +20 -7
- package/dist/src/knowledge-tracker.js +69 -31
- package/dist/src/remote.d.ts +0 -3
- package/dist/src/remote.js +0 -18
- package/dist/src/state-manager.d.ts +3 -3
- package/dist/src/state-manager.js +8 -8
- package/dist/src/test-plan.d.ts +1 -0
- package/dist/src/test-plan.js +13 -9
- package/dist/src/utils/aria.d.ts +1 -1
- package/dist/src/utils/aria.js +23 -2
- package/dist/src/utils/cli-section.d.ts +1 -0
- package/dist/src/utils/cli-section.js +12 -0
- package/dist/src/utils/html-diff.d.ts +1 -0
- package/dist/src/utils/html-diff.js +5 -5
- package/dist/src/utils/html.d.ts +2 -0
- package/dist/src/utils/html.js +5 -4
- package/dist/src/utils/overlay.d.ts +19 -55
- package/dist/src/utils/overlay.js +94 -148
- package/dist/src/utils/region.d.ts +41 -0
- package/dist/src/utils/region.js +99 -0
- package/dist/src/utils/similarity.d.ts +1 -0
- package/dist/src/utils/similarity.js +11 -0
- package/dist/src/utils/url-matcher.d.ts +1 -1
- package/dist/src/utils/url-matcher.js +4 -4
- package/docs/api-testing/basics.md +15 -0
- package/docs/api-testing/planning.md +10 -1
- package/docs/doc-collection/basics.md +11 -5
- package/docs/doc-collection/crawling.md +21 -1
- package/docs/reference/commands.md +27 -4
- package/docs/superpowers/plans/2026-09-03-model-resolves-click-ambiguity.md +509 -0
- package/docs/workflow/agentic-usage.md +11 -2
- package/docs/workflow/application-spec.md +4 -0
- package/docs/workflow/knowledge.md +46 -2
- package/package.json +1 -1
- package/rules/planner/styles/normal.md +1 -1
- package/src/action-result.ts +15 -16
- package/src/action.ts +16 -15
- package/src/ai/captain.ts +1 -1
- package/src/ai/fisherman/request-haul.ts +32 -0
- package/src/ai/fisherman-tools.ts +38 -32
- package/src/ai/fisherman.ts +8 -7
- package/src/ai/navigator.ts +1 -1
- package/src/ai/pilot.ts +19 -14
- package/src/ai/planner.ts +9 -8
- package/src/ai/provider.ts +8 -12
- package/src/ai/researcher/coordinates.ts +13 -1
- package/src/ai/researcher/deep-analysis.ts +9 -9
- package/src/ai/researcher/fingerprint-worker.ts +1 -10
- package/src/ai/researcher.ts +2 -1
- package/src/ai/rules.ts +11 -3
- package/src/ai/tester.ts +18 -18
- package/src/ai/tools.ts +18 -86
- package/src/api/request-store.ts +13 -27
- package/src/api/spec-reader.ts +1 -1
- package/src/commands/config-command.ts +3 -8
- package/src/commands/drill-command.ts +1 -1
- package/src/commands/explore-command.ts +12 -1
- package/src/commands/index.ts +2 -0
- package/src/commands/init-command.ts +4 -11
- package/src/commands/options/base-option.ts +18 -0
- package/src/commands/options/index.ts +7 -0
- package/src/commands/options/knowledge-option.ts +14 -0
- package/src/commands/options/ws-option.ts +24 -0
- package/src/commands/plans-command.ts +5 -5
- package/src/commands/recommended-models-command.ts +92 -0
- package/src/config.ts +20 -2
- package/src/experience-tracker.ts +1 -1
- package/src/explorbot.ts +4 -4
- package/src/global-config.ts +8 -0
- package/src/knowledge-tracker.ts +94 -36
- package/src/remote.ts +0 -20
- package/src/state-manager.ts +10 -9
- package/src/test-plan.ts +12 -10
- package/src/utils/aria.ts +26 -3
- package/src/utils/cli-section.ts +12 -0
- package/src/utils/html-diff.ts +6 -6
- package/src/utils/html.ts +6 -4
- package/src/utils/overlay.ts +105 -192
- package/src/utils/region.ts +132 -0
- package/src/utils/similarity.ts +9 -0
- package/src/utils/url-matcher.ts +3 -3
- package/dist/src/utils/request-map.d.ts +0 -7
- package/dist/src/utils/request-map.js +0 -16
- package/src/utils/request-map.ts +0 -19
|
@@ -1,7 +1,7 @@
|
|
|
1
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
|
-
import { ConfigParser, PROVIDERS } from "../config.js";
|
|
4
|
+
import { ConfigParser, PROVIDERS, missingModelRoles } from "../config.js";
|
|
5
5
|
import { findGlobalConfig, globalConfigPath, globalDir, globalEnvPath } from "../global-config.js";
|
|
6
6
|
import { getCliName } from "../utils/cli-name.js";
|
|
7
7
|
import { log, tag } from '../utils/logger.js';
|
|
@@ -82,7 +82,7 @@ export function writeGlobalConfig(provider, apiKey) {
|
|
|
82
82
|
const envKey = PROVIDERS[provider].envKey;
|
|
83
83
|
writeEnvKey(envKey, apiKey || '');
|
|
84
84
|
log(`Stored ${envKey} in ${globalEnvPath()}`);
|
|
85
|
-
const missing =
|
|
85
|
+
const missing = missingModelRoles(provider);
|
|
86
86
|
if (missing.length) {
|
|
87
87
|
tag('warning').log(`No recommended ${missing.join(' and ')} for ${provider} — set the model ids in ${globalConfigPath()}`);
|
|
88
88
|
}
|
|
@@ -139,7 +139,7 @@ export function runInitCommand(options) {
|
|
|
139
139
|
else {
|
|
140
140
|
log(`Env file already exists: ${relativeToCwd(envPath)}`);
|
|
141
141
|
}
|
|
142
|
-
const missing =
|
|
142
|
+
const missing = missingModelRoles(provider);
|
|
143
143
|
if (missing.length) {
|
|
144
144
|
tag('warning').log(`No recommended ${missing.join(' and ')} for ${provider} — set the model ids in ${relativeToCwd(outPath)}`);
|
|
145
145
|
}
|
|
@@ -269,10 +269,6 @@ function isModuleProject(configDir) {
|
|
|
269
269
|
currentDir = parentDir;
|
|
270
270
|
}
|
|
271
271
|
}
|
|
272
|
-
function missingRoles(provider) {
|
|
273
|
-
const recommended = ConfigParser.recommendedModels()[provider] || {};
|
|
274
|
-
return ['model', 'visionModel', 'agenticModel'].filter((role) => !recommended[role]);
|
|
275
|
-
}
|
|
276
272
|
function writeEnvKey(key, value) {
|
|
277
273
|
const envPath = globalEnvPath();
|
|
278
274
|
let content = '# AI provider API keys';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
export declare abstract class BaseOption {
|
|
3
|
+
abstract flags: string;
|
|
4
|
+
abstract description: string;
|
|
5
|
+
collect?: (value: string, previous: any) => any;
|
|
6
|
+
register(program: Command): void;
|
|
7
|
+
abstract apply(options: Record<string, any>, command: Command): void;
|
|
8
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export class BaseOption {
|
|
2
|
+
collect;
|
|
3
|
+
register(program) {
|
|
4
|
+
if (this.collect)
|
|
5
|
+
program.option(this.flags, this.description, this.collect);
|
|
6
|
+
if (!this.collect)
|
|
7
|
+
program.option(this.flags, this.description);
|
|
8
|
+
program.hook('preAction', (_thisCommand, actionCommand) => {
|
|
9
|
+
this.apply(actionCommand.optsWithGlobals(), actionCommand);
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { KnowledgeTracker } from '../../knowledge-tracker.js';
|
|
2
|
+
import { BaseOption } from './base-option.js';
|
|
3
|
+
export class KnowledgeOption extends BaseOption {
|
|
4
|
+
flags = '--knowledge <text>';
|
|
5
|
+
description = 'Knowledge for this run only, not saved to disk. Markdown text; add url: or endpoint: frontmatter to scope it, otherwise it applies everywhere. Repeatable';
|
|
6
|
+
collect = (value, previous = []) => [...previous, value];
|
|
7
|
+
apply(options) {
|
|
8
|
+
for (const text of options.knowledge || []) {
|
|
9
|
+
KnowledgeTracker.appendSessionKnowledge(text);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { remote } from '../../remote.js';
|
|
2
|
+
import { BaseOption } from './base-option.js';
|
|
3
|
+
export class WsOption extends BaseOption {
|
|
4
|
+
flags = '--ws <url>';
|
|
5
|
+
description = 'Stream this run to a remote UI over WebSocket';
|
|
6
|
+
apply(options, command) {
|
|
7
|
+
const url = options.ws || process.env.EXPLORBOT_WS_URL;
|
|
8
|
+
if (!url)
|
|
9
|
+
return;
|
|
10
|
+
remote.attach(String(url), commandPath(command));
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function commandPath(command) {
|
|
14
|
+
const parts = [];
|
|
15
|
+
let node = command;
|
|
16
|
+
while (node) {
|
|
17
|
+
parts.unshift(node.name());
|
|
18
|
+
node = node.parent;
|
|
19
|
+
}
|
|
20
|
+
return parts.slice(1).join(' ') || parts.join(' ');
|
|
21
|
+
}
|
|
@@ -11,7 +11,10 @@ export declare class PlansCommand extends BaseCommand {
|
|
|
11
11
|
getPlanFiles(): PlanFile[];
|
|
12
12
|
printPlans(files: PlanFile[]): void;
|
|
13
13
|
printPlanDetails(plan: Plan, file: PlanFile): void;
|
|
14
|
-
resolvePlanFile(target: string, files: PlanFile[]):
|
|
14
|
+
resolvePlanFile(target: string, files: PlanFile[]): {
|
|
15
|
+
plan: Plan;
|
|
16
|
+
file: PlanFile;
|
|
17
|
+
};
|
|
15
18
|
}
|
|
16
19
|
interface PlanFile {
|
|
17
20
|
name: string;
|
|
@@ -17,8 +17,7 @@ export class PlansCommand extends BaseCommand {
|
|
|
17
17
|
this.printPlans(files);
|
|
18
18
|
return;
|
|
19
19
|
}
|
|
20
|
-
const file = this.resolvePlanFile(target, files);
|
|
21
|
-
const plan = Plan.fromMarkdown(file.path);
|
|
20
|
+
const { plan, file } = this.resolvePlanFile(target, files);
|
|
22
21
|
this.printPlanDetails(plan, file);
|
|
23
22
|
}
|
|
24
23
|
getPlanFiles() {
|
|
@@ -68,16 +67,17 @@ export class PlansCommand extends BaseCommand {
|
|
|
68
67
|
const file = files[index - 1];
|
|
69
68
|
if (!file)
|
|
70
69
|
throw new Error(`Plan #${target} not found. Available: 1-${files.length}`);
|
|
71
|
-
return file;
|
|
70
|
+
return { plan: Plan.fromMarkdown(file.path), file };
|
|
72
71
|
}
|
|
73
72
|
const plan = Plan.loadFromFile(target, this.explorBot.getPlansDir());
|
|
74
73
|
if (!plan?.filePath) {
|
|
75
74
|
throw new Error(`Plan file not found: ${target}`);
|
|
76
75
|
}
|
|
77
|
-
|
|
76
|
+
const file = {
|
|
78
77
|
name: path.basename(plan.filePath),
|
|
79
78
|
path: plan.filePath,
|
|
80
79
|
modifiedAt: statSync(plan.filePath).mtimeMs,
|
|
81
80
|
};
|
|
81
|
+
return { plan, file };
|
|
82
82
|
}
|
|
83
83
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
import { BaseCommand } from './base-command.js';
|
|
3
|
+
export declare class RecommendedModelsCommand extends BaseCommand {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
execute(): Promise<void>;
|
|
7
|
+
static register(program: Command): void;
|
|
8
|
+
static render(json?: boolean): string;
|
|
9
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { ConfigParser, EXPLORBOT_ENV_VARS, MODEL_ROLES, PROVIDERS } from '../config.js';
|
|
3
|
+
import { getCliName } from '../utils/cli-name.js';
|
|
4
|
+
import { renderSection } from '../utils/cli-section.js';
|
|
5
|
+
import { tag } from '../utils/logger.js';
|
|
6
|
+
import { BaseCommand } from './base-command.js';
|
|
7
|
+
const DESCRIPTION = 'Show the models recommended for every AI provider';
|
|
8
|
+
const ROLES = {
|
|
9
|
+
model: { hint: 'fast', env: 'EXPLORBOT_AI_MODEL' },
|
|
10
|
+
visionModel: { hint: 'screenshots', env: 'EXPLORBOT_VISION_MODEL' },
|
|
11
|
+
agenticModel: { hint: 'smart', env: 'EXPLORBOT_AGENTIC_MODEL' },
|
|
12
|
+
};
|
|
13
|
+
const MODEL_ENV_VARS = MODEL_ROLES.map((role) => ROLES[role].env);
|
|
14
|
+
export class RecommendedModelsCommand extends BaseCommand {
|
|
15
|
+
name = 'recommended-models';
|
|
16
|
+
description = DESCRIPTION;
|
|
17
|
+
async execute() {
|
|
18
|
+
tag('info').log(RecommendedModelsCommand.render());
|
|
19
|
+
}
|
|
20
|
+
static register(program) {
|
|
21
|
+
program
|
|
22
|
+
.command('recommended-models')
|
|
23
|
+
.description(DESCRIPTION)
|
|
24
|
+
.option('--json', 'Print the recommendations as JSON')
|
|
25
|
+
.action((options) => console.log(RecommendedModelsCommand.render(options.json)));
|
|
26
|
+
}
|
|
27
|
+
static render(json = false) {
|
|
28
|
+
const recommended = ConfigParser.recommendedModels();
|
|
29
|
+
if (json)
|
|
30
|
+
return JSON.stringify(recommended, null, 2);
|
|
31
|
+
const lines = [
|
|
32
|
+
chalk.bold('Export the provider key, then pick one of two ways to select models:'),
|
|
33
|
+
` ${chalk.bold('EXPLORBOT_AI_PROVIDER')} — every role takes that provider's recommendation below`,
|
|
34
|
+
` ${chalk.bold(MODEL_ENV_VARS.join(' + '))} — no provider variable, pin every role yourself as ${chalk.dim('provider/model-id')}`,
|
|
35
|
+
'',
|
|
36
|
+
];
|
|
37
|
+
for (const [provider, roles] of Object.entries(recommended)) {
|
|
38
|
+
lines.push(chalk.bold.yellow(`${provider}:`));
|
|
39
|
+
const models = [];
|
|
40
|
+
for (const role of MODEL_ROLES) {
|
|
41
|
+
const label = `${role} (${ROLES[role].hint})`;
|
|
42
|
+
if (roles[role])
|
|
43
|
+
models.push([label, roles[role]]);
|
|
44
|
+
if (!roles[role])
|
|
45
|
+
models.push([label, chalk.dim('not served, pair with another provider')]);
|
|
46
|
+
}
|
|
47
|
+
lines.push(...renderSection('models', models, 2));
|
|
48
|
+
const envKey = PROVIDERS[provider]?.envKey;
|
|
49
|
+
if (!envKey) {
|
|
50
|
+
lines.push(` ${chalk.dim('not bundled — import the provider in your config')}`, '');
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const env = [
|
|
54
|
+
['key', envKey],
|
|
55
|
+
['provider', `EXPLORBOT_AI_PROVIDER=${provider}`],
|
|
56
|
+
];
|
|
57
|
+
let label = 'or models';
|
|
58
|
+
for (const role of MODEL_ROLES) {
|
|
59
|
+
if (!roles[role])
|
|
60
|
+
continue;
|
|
61
|
+
env.push([label, `${ROLES[role].env}=${provider}/${roles[role]}`]);
|
|
62
|
+
label = '';
|
|
63
|
+
}
|
|
64
|
+
lines.push(...renderSection('env variables', env, 2));
|
|
65
|
+
}
|
|
66
|
+
const modelVars = ['EXPLORBOT_AI_PROVIDER', ...MODEL_ENV_VARS];
|
|
67
|
+
const current = [];
|
|
68
|
+
for (const variable of EXPLORBOT_ENV_VARS) {
|
|
69
|
+
if (!modelVars.includes(variable.name))
|
|
70
|
+
continue;
|
|
71
|
+
if (process.env[variable.name])
|
|
72
|
+
current.push([variable.name, process.env[variable.name]]);
|
|
73
|
+
}
|
|
74
|
+
for (const [provider, { envKey }] of Object.entries(PROVIDERS)) {
|
|
75
|
+
if (process.env[envKey])
|
|
76
|
+
current.push([envKey, chalk.dim(`set, ${provider} usable`)]);
|
|
77
|
+
}
|
|
78
|
+
if (!current.length)
|
|
79
|
+
lines.push(chalk.dim('Nothing set — export one of the variables above, or write models into your config'), '');
|
|
80
|
+
if (current.length)
|
|
81
|
+
lines.push(...renderSection('Currently set', current));
|
|
82
|
+
lines.push(chalk.bold('Example using OpenRouter:'), ` OPENROUTER_API_KEY=sk-... EXPLORBOT_AI_PROVIDER=openrouter ${getCliName()} <command>`);
|
|
83
|
+
return lines.join('\n');
|
|
84
|
+
}
|
|
85
|
+
}
|
package/dist/src/config.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type SiteRecord } from './global-config.js';
|
|
2
2
|
export declare const PROVIDERS: Record<string, ProviderInfo>;
|
|
3
|
+
export declare const MODEL_ROLES: ModelRole[];
|
|
3
4
|
interface PlaywrightConfig {
|
|
4
5
|
browser: 'chromium' | 'firefox' | 'webkit';
|
|
5
6
|
url: string;
|
|
@@ -255,6 +256,7 @@ export declare class ConfigParser {
|
|
|
255
256
|
static getTestDirectories(): string[];
|
|
256
257
|
static cleanupAllTestDirectories(): void;
|
|
257
258
|
enterGlobalMode(config: ExplorbotConfig, target: string | null): void;
|
|
259
|
+
applyEnvSpec(config: ExplorbotConfig): void;
|
|
258
260
|
buildEnvConfig(baseUrl: string | undefined, outputRoot: string): Promise<ExplorbotConfig>;
|
|
259
261
|
findConfigFile(): string | null;
|
|
260
262
|
loadConfigModule(configPath: string): Promise<any>;
|
|
@@ -269,6 +271,7 @@ export declare class ConfigParser {
|
|
|
269
271
|
}
|
|
270
272
|
export declare function outputPath(...segments: string[]): string;
|
|
271
273
|
export declare function resolveModel(spec: string, role?: ModelRole): Promise<any>;
|
|
274
|
+
export declare function missingModelRoles(provider: string): ModelRole[];
|
|
272
275
|
export declare class ConfigMissingError extends Error {
|
|
273
276
|
}
|
|
274
277
|
export declare function envConfigRequested(): boolean;
|
package/dist/src/config.js
CHANGED
|
@@ -25,6 +25,7 @@ export const PROVIDERS = {
|
|
|
25
25
|
openrouter: { envKey: 'OPENROUTER_API_KEY', load: async () => (await import('@openrouter/ai-sdk-provider')).createOpenRouter() },
|
|
26
26
|
sambanova: { envKey: 'SAMBANOVA_API_KEY', load: async () => (await import('sambanova-ai-provider')).createSambaNova() },
|
|
27
27
|
};
|
|
28
|
+
export const MODEL_ROLES = ['model', 'visionModel', 'agenticModel'];
|
|
28
29
|
let cachedOutputRoot = null;
|
|
29
30
|
const config = {
|
|
30
31
|
playwright: {
|
|
@@ -46,6 +47,7 @@ export const EXPLORBOT_ENV_VARS = [
|
|
|
46
47
|
{ name: 'EXPLORBOT_EPHEMERAL', description: 'Keep no state between runs — output goes to a fresh temp directory instead of the site dir' },
|
|
47
48
|
{ name: 'EXPLORBOT_KNOWLEDGE', description: 'Inline knowledge text, applied to every page' },
|
|
48
49
|
{ name: 'EXPLORBOT_KNOWLEDGE_FILE', description: 'Path to a knowledge markdown file' },
|
|
50
|
+
{ name: 'EXPLORBOT_SPEC', description: 'Docbot application spec directory or index.md, used as page knowledge' },
|
|
49
51
|
{ name: 'EXPLORBOT_API_SPEC', description: 'OpenAPI spec path for the API boat' },
|
|
50
52
|
{ name: 'EXPLORBOT_NO_BANNER', description: 'Suppress the startup banner, for machine-readable output' },
|
|
51
53
|
{ name: 'EXPLORBOT_MAX_DURATION', description: 'Wall-clock budget in minutes for an explore run; same as --max-duration' },
|
|
@@ -129,6 +131,7 @@ export class ConfigParser {
|
|
|
129
131
|
if (resolvedPath && isGlobalConfigPath(resolvedPath)) {
|
|
130
132
|
this.enterGlobalMode(this.config, target);
|
|
131
133
|
}
|
|
134
|
+
this.applyEnvSpec(this.config);
|
|
132
135
|
// Restore original directory after successful config load
|
|
133
136
|
if (options?.path && originalCwd !== process.cwd()) {
|
|
134
137
|
process.chdir(originalCwd);
|
|
@@ -269,8 +272,17 @@ export class ConfigParser {
|
|
|
269
272
|
this.siteStartPath = site.path;
|
|
270
273
|
config.dirs = { knowledge: 'knowledge', experience: 'experience', output: 'output' };
|
|
271
274
|
config.playwright = { ...config.playwright, browser: config.playwright?.browser || 'chromium', url: site.baseUrl };
|
|
275
|
+
materializeKnowledge(this.site.dir);
|
|
272
276
|
log(`Global mode: ${site.baseUrl} stored in ${this.site.dir}`);
|
|
273
277
|
}
|
|
278
|
+
applyEnvSpec(config) {
|
|
279
|
+
const spec = process.env.EXPLORBOT_SPEC;
|
|
280
|
+
if (!spec)
|
|
281
|
+
return;
|
|
282
|
+
if (!config.dirs)
|
|
283
|
+
config.dirs = { knowledge: 'knowledge', experience: 'experience', output: 'output' };
|
|
284
|
+
config.dirs.spec = spec;
|
|
285
|
+
}
|
|
274
286
|
async buildEnvConfig(baseUrl, outputRoot) {
|
|
275
287
|
const provider = process.env.EXPLORBOT_AI_PROVIDER;
|
|
276
288
|
const modelSpec = process.env.EXPLORBOT_AI_MODEL;
|
|
@@ -447,6 +459,10 @@ export async function resolveModel(spec, role = 'model') {
|
|
|
447
459
|
}
|
|
448
460
|
return createModel(spec, modelId);
|
|
449
461
|
}
|
|
462
|
+
export function missingModelRoles(provider) {
|
|
463
|
+
const recommended = ConfigParser.recommendedModels()[provider] || {};
|
|
464
|
+
return MODEL_ROLES.filter((role) => !recommended[role]);
|
|
465
|
+
}
|
|
450
466
|
export class ConfigMissingError extends Error {
|
|
451
467
|
}
|
|
452
468
|
export function envConfigRequested() {
|
|
@@ -467,6 +483,7 @@ export function missingConfigMessage(configFile = 'explorbot.config.js') {
|
|
|
467
483
|
EXPLORBOT_AI_PROVIDER=openrouter EXPLORBOT_URL=https://your-app.example.com ${cli} ...
|
|
468
484
|
|
|
469
485
|
Providers: ${Object.keys(PROVIDERS).join(', ')}
|
|
486
|
+
See the models each one recommends: ${cli} recommended-models
|
|
470
487
|
`;
|
|
471
488
|
}
|
|
472
489
|
export function modelName(model) {
|
|
@@ -500,8 +517,7 @@ export function configuredModels(ai) {
|
|
|
500
517
|
export async function resolveConfigModels(ai) {
|
|
501
518
|
if (!ai)
|
|
502
519
|
return;
|
|
503
|
-
const
|
|
504
|
-
for (const role of roles) {
|
|
520
|
+
for (const role of MODEL_ROLES) {
|
|
505
521
|
if (typeof ai[role] === 'string')
|
|
506
522
|
ai[role] = await resolveModel(ai[role], role);
|
|
507
523
|
}
|
|
@@ -105,7 +105,7 @@ export class ExperienceTracker {
|
|
|
105
105
|
url: state.url ? extractStatePath(state.url) : '',
|
|
106
106
|
title: state.title,
|
|
107
107
|
};
|
|
108
|
-
if (state.overlay.
|
|
108
|
+
if (state.overlay.isOpen && state.overlay.name) {
|
|
109
109
|
frontmatter.region = state.overlay.name;
|
|
110
110
|
if (state.overlay.root)
|
|
111
111
|
frontmatter.root = state.overlay.root;
|
package/dist/src/explorbot.js
CHANGED
|
@@ -132,7 +132,7 @@ export class ExplorBot {
|
|
|
132
132
|
return this.explorer;
|
|
133
133
|
}
|
|
134
134
|
knowledgeTracker() {
|
|
135
|
-
return (this._knowledgeTracker ||= new KnowledgeTracker(this.options.applicationSpec));
|
|
135
|
+
return (this._knowledgeTracker ||= new KnowledgeTracker({ applicationSpec: this.options.applicationSpec }));
|
|
136
136
|
}
|
|
137
137
|
experienceTracker() {
|
|
138
138
|
return (this._experienceTracker ||= new ExperienceTracker(this.knowledgeTracker()));
|
|
@@ -399,10 +399,10 @@ export class ExplorBot {
|
|
|
399
399
|
return plan;
|
|
400
400
|
}
|
|
401
401
|
loadPlans(filename) {
|
|
402
|
-
const
|
|
403
|
-
if (!
|
|
402
|
+
const filePath = Plan.resolveFile(filename, this.getPlansDir());
|
|
403
|
+
if (!filePath)
|
|
404
404
|
throw new Error(`Plan file not found: ${filename}`);
|
|
405
|
-
return parsePlansFromMarkdown(
|
|
405
|
+
return parsePlansFromMarkdown(filePath);
|
|
406
406
|
}
|
|
407
407
|
setCurrentPlan(plan) {
|
|
408
408
|
this.currentPlan = plan;
|
|
@@ -6,6 +6,8 @@ export declare function isGlobalConfigPath(configPath: string): boolean;
|
|
|
6
6
|
export declare function sitesDir(): string;
|
|
7
7
|
export declare function siteFolderName(url: string): string;
|
|
8
8
|
export declare function listSites(): SiteRecord[];
|
|
9
|
+
export declare function findSiteWith(subpath: string): SiteRecord | undefined;
|
|
10
|
+
export declare function listSitePlanDirs(): string[];
|
|
9
11
|
export declare function registerSite(baseUrl: string): SiteRecord;
|
|
10
12
|
export declare function resolveSiteTarget(target?: string, defaultBaseUrl?: string): SiteTarget;
|
|
11
13
|
interface SiteRecord {
|
|
@@ -38,6 +38,12 @@ export function listSites() {
|
|
|
38
38
|
.filter((site) => !!site)
|
|
39
39
|
.sort((a, b) => b.lastRunAt.localeCompare(a.lastRunAt));
|
|
40
40
|
}
|
|
41
|
+
export function findSiteWith(subpath) {
|
|
42
|
+
return listSites().find((site) => existsSync(join(site.dir, subpath)));
|
|
43
|
+
}
|
|
44
|
+
export function listSitePlanDirs() {
|
|
45
|
+
return listSites().map((site) => join(site.dir, 'output', 'plans'));
|
|
46
|
+
}
|
|
41
47
|
export function registerSite(baseUrl) {
|
|
42
48
|
const folder = siteFolderName(baseUrl);
|
|
43
49
|
const dir = join(sitesDir(), folder);
|
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
import { ActionResult } from './action-result.js';
|
|
2
2
|
import { ApplicationSpec } from './application-spec.js';
|
|
3
|
-
export interface Knowledge {
|
|
4
|
-
filePath: string;
|
|
5
|
-
url: string;
|
|
6
|
-
content: string;
|
|
7
|
-
[key: string]: any;
|
|
8
|
-
}
|
|
9
3
|
export declare class KnowledgeTracker {
|
|
10
4
|
knowledgeDir: string;
|
|
11
5
|
knowledgeFiles: Knowledge[];
|
|
6
|
+
sessionKnowledge: Knowledge[];
|
|
12
7
|
isLoaded: boolean;
|
|
13
8
|
applicationSpec?: ApplicationSpec;
|
|
14
|
-
|
|
9
|
+
static appendSessionKnowledge(text: string): void;
|
|
10
|
+
static resetSessionKnowledge(): void;
|
|
11
|
+
constructor(options?: KnowledgeTrackerOptions);
|
|
15
12
|
loadKnowledgeFiles(): void;
|
|
16
13
|
getRelevantKnowledge(state: ActionResult): Knowledge[];
|
|
14
|
+
getEndpointKnowledge(endpoint: string): Knowledge[];
|
|
17
15
|
renderRelevantKnowledge(state: ActionResult): string;
|
|
16
|
+
renderEndpointKnowledge(endpoint: string): string;
|
|
18
17
|
renderRelevantContext(state: ActionResult): string;
|
|
19
18
|
renderApplicationSpec(state: ActionResult): string;
|
|
20
19
|
addKnowledge(urlPattern: string, description: string, opts?: {
|
|
@@ -36,4 +35,18 @@ export declare class KnowledgeTracker {
|
|
|
36
35
|
getMatchingKnowledge(url: string): Knowledge[];
|
|
37
36
|
normalizeUrl(url: string): string;
|
|
38
37
|
getStateParameters(state: ActionResult, keys: string[]): Record<string, any>;
|
|
38
|
+
allKnowledge(): Knowledge[];
|
|
39
|
+
toKnowledge(filePath: string, data: Record<string, any>, content: string): Knowledge;
|
|
40
|
+
renderKnowledge(knowledgeFiles: Knowledge[], scope: string): string;
|
|
41
|
+
}
|
|
42
|
+
export interface Knowledge {
|
|
43
|
+
filePath: string;
|
|
44
|
+
url?: string;
|
|
45
|
+
endpoint?: string;
|
|
46
|
+
content: string;
|
|
47
|
+
[key: string]: any;
|
|
48
|
+
}
|
|
49
|
+
export interface KnowledgeTrackerOptions {
|
|
50
|
+
applicationSpec?: string;
|
|
51
|
+
knowledgeDir?: string;
|
|
39
52
|
}
|
|
@@ -11,61 +11,67 @@ import { loadMarkdownFiles } from './utils/markdown-files.js';
|
|
|
11
11
|
import { mdq } from './utils/markdown-query.js';
|
|
12
12
|
import { isSecretName, registerSecret } from './utils/secrets.js';
|
|
13
13
|
import { slugify } from './utils/strings.js';
|
|
14
|
+
import { extractStatePath, matchesUrl } from './utils/url-matcher.js';
|
|
14
15
|
const debugLog = createDebug('explorbot:knowledge-tracker');
|
|
16
|
+
const sessionEntries = [];
|
|
15
17
|
export class KnowledgeTracker {
|
|
16
18
|
knowledgeDir;
|
|
17
19
|
knowledgeFiles = [];
|
|
20
|
+
sessionKnowledge = [];
|
|
18
21
|
isLoaded = false;
|
|
19
22
|
applicationSpec;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
static appendSessionKnowledge(text) {
|
|
24
|
+
sessionEntries.push(text);
|
|
25
|
+
}
|
|
26
|
+
static resetSessionKnowledge() {
|
|
27
|
+
sessionEntries.length = 0;
|
|
28
|
+
}
|
|
29
|
+
constructor(options = {}) {
|
|
30
|
+
let knowledgeDir = options.knowledgeDir;
|
|
31
|
+
let specPath = options.applicationSpec;
|
|
32
|
+
if (!knowledgeDir) {
|
|
33
|
+
const configParser = ConfigParser.getInstance();
|
|
34
|
+
const config = configParser.getConfig();
|
|
35
|
+
knowledgeDir = configParser.resolveProjectDir(config.dirs?.knowledge || 'knowledge');
|
|
36
|
+
specPath ||= config.dirs?.spec;
|
|
37
|
+
}
|
|
38
|
+
this.knowledgeDir = knowledgeDir;
|
|
24
39
|
if (!existsSync(this.knowledgeDir)) {
|
|
25
40
|
mkdirSync(this.knowledgeDir, { recursive: true });
|
|
26
41
|
}
|
|
27
|
-
const specPath = applicationSpecPath || config.dirs?.spec;
|
|
28
42
|
if (specPath) {
|
|
29
43
|
this.applicationSpec = new ApplicationSpec(specPath);
|
|
30
44
|
tag('info').log(`Loaded application spec with ${this.applicationSpec.pageCount} documented pages`);
|
|
31
45
|
}
|
|
46
|
+
this.sessionKnowledge = sessionEntries.map((entry, index) => {
|
|
47
|
+
const parsed = matter(entry);
|
|
48
|
+
debugLog(`Session knowledge #${index + 1}`);
|
|
49
|
+
return this.toKnowledge(`--knowledge #${index + 1}`, parsed.data, parsed.content.trim());
|
|
50
|
+
});
|
|
32
51
|
}
|
|
33
52
|
loadKnowledgeFiles() {
|
|
34
53
|
if (this.isLoaded)
|
|
35
54
|
return;
|
|
36
55
|
this.knowledgeFiles = [];
|
|
37
56
|
for (const entry of loadMarkdownFiles(this.knowledgeDir, { recursive: true })) {
|
|
38
|
-
this.knowledgeFiles.push(
|
|
39
|
-
filePath: entry.filePath,
|
|
40
|
-
url: entry.data.url || entry.data.path || '*',
|
|
41
|
-
content: this.interpolateVars(entry.content),
|
|
42
|
-
...entry.data,
|
|
43
|
-
});
|
|
57
|
+
this.knowledgeFiles.push(this.toKnowledge(entry.filePath, entry.data, entry.content));
|
|
44
58
|
}
|
|
45
59
|
this.isLoaded = true;
|
|
46
60
|
}
|
|
47
61
|
getRelevantKnowledge(state) {
|
|
48
62
|
this.loadKnowledgeFiles();
|
|
49
|
-
return this.
|
|
50
|
-
|
|
51
|
-
|
|
63
|
+
return this.allKnowledge().filter((knowledge) => knowledge.url && state.isMatchedBy(knowledge));
|
|
64
|
+
}
|
|
65
|
+
getEndpointKnowledge(endpoint) {
|
|
66
|
+
this.loadKnowledgeFiles();
|
|
67
|
+
const path = extractStatePath(endpoint);
|
|
68
|
+
return this.allKnowledge().filter((knowledge) => knowledge.endpoint && matchesUrl(knowledge.endpoint, path));
|
|
52
69
|
}
|
|
53
70
|
renderRelevantKnowledge(state) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
.map((k) => k.content)
|
|
59
|
-
.filter((k) => !!k)
|
|
60
|
-
.join('\n\n');
|
|
61
|
-
tag('operation').log(`Found ${knowledgeFiles.length} relevant knowledge ${pluralize(knowledgeFiles.length, 'file')}`);
|
|
62
|
-
return dedent `
|
|
63
|
-
<knowledge>
|
|
64
|
-
Here is relevant knowledge for this page:
|
|
65
|
-
|
|
66
|
-
${knowledgeContent}
|
|
67
|
-
</knowledge>
|
|
68
|
-
`;
|
|
71
|
+
return this.renderKnowledge(this.getRelevantKnowledge(state), 'page');
|
|
72
|
+
}
|
|
73
|
+
renderEndpointKnowledge(endpoint) {
|
|
74
|
+
return this.renderKnowledge(this.getEndpointKnowledge(endpoint), 'endpoint');
|
|
69
75
|
}
|
|
70
76
|
renderRelevantContext(state) {
|
|
71
77
|
return [this.renderRelevantKnowledge(state), this.renderApplicationSpec(state)].filter(Boolean).join('\n\n');
|
|
@@ -151,7 +157,7 @@ export class KnowledgeTracker {
|
|
|
151
157
|
}
|
|
152
158
|
getExistingUrls() {
|
|
153
159
|
this.loadKnowledgeFiles();
|
|
154
|
-
return this.knowledgeFiles.map((knowledge) => knowledge.url).filter((url) => url && url !== '*');
|
|
160
|
+
return this.knowledgeFiles.map((knowledge) => knowledge.url || '').filter((url) => url && url !== '*');
|
|
155
161
|
}
|
|
156
162
|
getKnowledgeForUrl(urlPattern) {
|
|
157
163
|
this.loadKnowledgeFiles();
|
|
@@ -164,7 +170,7 @@ export class KnowledgeTracker {
|
|
|
164
170
|
const content = knowledge.content.trim();
|
|
165
171
|
const firstLine = mdq(content).meta()[0]?.text.split('\n')[0]?.trim() || '';
|
|
166
172
|
return {
|
|
167
|
-
url: knowledge.url,
|
|
173
|
+
url: knowledge.url || knowledge.endpoint || '',
|
|
168
174
|
firstLine,
|
|
169
175
|
filePath: knowledge.filePath,
|
|
170
176
|
};
|
|
@@ -194,4 +200,36 @@ export class KnowledgeTracker {
|
|
|
194
200
|
}
|
|
195
201
|
return result;
|
|
196
202
|
}
|
|
203
|
+
allKnowledge() {
|
|
204
|
+
return [...this.knowledgeFiles, ...this.sessionKnowledge];
|
|
205
|
+
}
|
|
206
|
+
toKnowledge(filePath, data, content) {
|
|
207
|
+
const knowledge = {
|
|
208
|
+
...data,
|
|
209
|
+
filePath,
|
|
210
|
+
url: data.url || data.path,
|
|
211
|
+
content: this.interpolateVars(content),
|
|
212
|
+
};
|
|
213
|
+
if (!data.url && !data.path && !data.endpoint) {
|
|
214
|
+
knowledge.url = '*';
|
|
215
|
+
knowledge.endpoint = '*';
|
|
216
|
+
}
|
|
217
|
+
return knowledge;
|
|
218
|
+
}
|
|
219
|
+
renderKnowledge(knowledgeFiles, scope) {
|
|
220
|
+
if (knowledgeFiles.length === 0)
|
|
221
|
+
return '';
|
|
222
|
+
const knowledgeContent = knowledgeFiles
|
|
223
|
+
.map((k) => k.content)
|
|
224
|
+
.filter((k) => !!k)
|
|
225
|
+
.join('\n\n');
|
|
226
|
+
tag('operation').log(`Found ${knowledgeFiles.length} relevant knowledge ${pluralize(knowledgeFiles.length, 'file')}`);
|
|
227
|
+
return dedent `
|
|
228
|
+
<knowledge>
|
|
229
|
+
Here is relevant knowledge for this ${scope}:
|
|
230
|
+
|
|
231
|
+
${knowledgeContent}
|
|
232
|
+
</knowledge>
|
|
233
|
+
`;
|
|
234
|
+
}
|
|
197
235
|
}
|
package/dist/src/remote.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { Command } from 'commander';
|
|
2
1
|
import { type ActivityEntry } from './activity.js';
|
|
3
2
|
import { type LogDestination, type TaggedLogEntry } from './utils/logger.js';
|
|
4
3
|
/**
|
|
@@ -19,7 +18,6 @@ export declare class Remote implements LogDestination {
|
|
|
19
18
|
asks: Map<string, (value: string | null) => void>;
|
|
20
19
|
askCounter: number;
|
|
21
20
|
lastActivity: string | null;
|
|
22
|
-
registerOption(program: Command): void;
|
|
23
21
|
attach(url: string, command: string): void;
|
|
24
22
|
isAttached(): boolean;
|
|
25
23
|
send(type: string, data?: Record<string, unknown>): void;
|
|
@@ -42,7 +40,6 @@ export declare class Remote implements LogDestination {
|
|
|
42
40
|
* nothing — so skip repeats, the priming null included. */
|
|
43
41
|
reportActivity(activity: ActivityEntry | null): void;
|
|
44
42
|
errorOf(args: any[] | undefined): string | undefined;
|
|
45
|
-
commandPath(command: Command): string;
|
|
46
43
|
}
|
|
47
44
|
export declare const remote: Remote;
|
|
48
45
|
/** Whatever the run wants to say. Not a schema — the UI renders what it knows
|