explorbot 0.2.3 → 0.2.5
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 +26 -8
- package/boat/api-tester/src/cli.ts +17 -0
- package/boat/api-tester/src/config.ts +4 -2
- 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 +14 -1
- package/boat/doc-collector/src/config.ts +4 -2
- package/boat/prima/bin/prima-cli.ts +0 -0
- package/boat/prima/src/activity-line.ts +33 -0
- package/boat/prima/src/cli.ts +127 -86
- package/boat/prima/src/envelope.ts +102 -52
- package/boat/prima/src/prima.ts +567 -128
- package/boat/prima/src/pw-parser.ts +11 -1
- package/boat/prima/src/pw-registry.ts +4 -5
- package/boat/prima/src/session-log.ts +126 -0
- package/dist/bin/explorbot-cli.js +26 -8
- package/dist/boat/api-tester/bin/apibot-cli.js +2 -0
- package/dist/boat/api-tester/src/cli.js +17 -0
- package/dist/boat/api-tester/src/config.js +4 -2
- 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 +14 -1
- package/dist/boat/doc-collector/src/config.js +4 -2
- package/dist/boat/prima/src/activity-line.js +30 -0
- package/dist/boat/prima/src/cli.js +109 -77
- package/dist/boat/prima/src/envelope.js +94 -44
- package/dist/boat/prima/src/prima.js +533 -119
- package/dist/boat/prima/src/pw-parser.js +13 -1
- package/dist/boat/prima/src/pw-registry.js +4 -5
- package/dist/boat/prima/src/session-log.js +108 -0
- package/dist/package.json +3 -2
- 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 +2 -0
- package/dist/src/action.js +41 -2
- package/dist/src/ai/captain/web-mode.js +6 -3
- package/dist/src/ai/captain.js +2 -0
- package/dist/src/ai/navigator.d.ts +34 -0
- package/dist/src/ai/navigator.js +237 -181
- package/dist/src/ai/pilot.d.ts +7 -0
- package/dist/src/ai/pilot.js +90 -2
- package/dist/src/ai/provider.d.ts +2 -2
- package/dist/src/ai/provider.js +14 -23
- package/dist/src/ai/rerunner.js +2 -1
- package/dist/src/ai/researcher/cache.d.ts +2 -0
- package/dist/src/ai/researcher/cache.js +10 -2
- package/dist/src/ai/researcher.js +3 -2
- package/dist/src/ai/rules.js +17 -10
- package/dist/src/ai/session-analyst.js +2 -0
- package/dist/src/ai/task-agent.js +4 -1
- package/dist/src/ai/tester.d.ts +6 -3
- package/dist/src/ai/tester.js +50 -46
- package/dist/src/ai/tools.d.ts +14 -0
- package/dist/src/ai/tools.js +117 -37
- package/dist/src/commands/config-command.d.ts +51 -0
- package/dist/src/commands/config-command.js +117 -0
- package/dist/src/commands/index.js +2 -0
- package/dist/src/config.d.ts +9 -1
- package/dist/src/config.js +53 -4
- package/dist/src/execution-controller.d.ts +2 -0
- package/dist/src/execution-controller.js +6 -0
- package/dist/src/explorbot.d.ts +2 -1
- package/dist/src/explorbot.js +7 -2
- package/dist/src/explorer.js +2 -3
- package/dist/src/playwright-recorder.js +30 -0
- package/dist/src/remote.d.ts +55 -0
- package/dist/src/remote.js +235 -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/state-manager.js +3 -1
- package/dist/src/stats.d.ts +1 -0
- package/dist/src/stats.js +1 -0
- package/dist/src/test-plan.d.ts +3 -0
- package/dist/src/test-plan.js +26 -0
- package/dist/src/utils/aria.d.ts +2 -8
- package/dist/src/utils/aria.js +69 -40
- package/dist/src/utils/html.js +1 -0
- package/dist/src/utils/logger.d.ts +7 -1
- package/dist/src/utils/logger.js +32 -0
- package/dist/src/utils/page-readiness.js +18 -1
- package/dist/src/utils/url-matcher.js +3 -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 +87 -0
- package/docs/reference/commands.md +997 -0
- package/docs/reference/configuration.md +569 -0
- package/docs/reference/scripting.md +303 -0
- package/docs/reference/websocket.md +50 -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/superpowers/specs/2026-08-18-prima-false-verdicts.md +159 -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/package.json +3 -2
- package/rules/navigator/verification-actions.md +20 -0
- package/src/action-result.ts +11 -0
- package/src/action.ts +43 -3
- package/src/ai/captain/web-mode.ts +6 -3
- package/src/ai/captain.ts +3 -0
- package/src/ai/navigator.ts +255 -186
- package/src/ai/pilot.ts +104 -2
- package/src/ai/provider.ts +14 -24
- package/src/ai/rerunner.ts +2 -1
- package/src/ai/researcher/cache.ts +12 -2
- package/src/ai/researcher.ts +3 -2
- package/src/ai/rules.ts +17 -10
- package/src/ai/session-analyst.ts +2 -0
- package/src/ai/task-agent.ts +3 -1
- package/src/ai/tester.ts +52 -45
- package/src/ai/tools.ts +136 -37
- package/src/commands/config-command.ts +146 -0
- package/src/commands/index.ts +2 -0
- package/src/config.ts +60 -5
- package/src/execution-controller.ts +8 -0
- package/src/explorbot.ts +7 -3
- package/src/explorer.ts +2 -2
- package/src/playwright-recorder.ts +23 -0
- package/src/remote.ts +244 -0
- package/src/reporter.ts +7 -1
- package/src/state-manager.ts +6 -2
- package/src/stats.ts +1 -0
- package/src/test-plan.ts +29 -0
- package/src/utils/aria.ts +65 -45
- package/src/utils/html.ts +1 -0
- package/src/utils/logger.ts +33 -2
- package/src/utils/page-readiness.ts +24 -1
- package/src/utils/url-matcher.ts +3 -0
- package/src/utils/web-element.ts +9 -0
- package/src/utils/web-sandbox.ts +3 -4
|
@@ -13,5 +13,15 @@ export function isFunctionExpression(expr: string): { valid: boolean; error?: st
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export function toCodeceptWrapper(expr: string): string {
|
|
16
|
-
return `I.usePlaywrightTo('pw', async (playwright) => (${expr.trim()})(playwright))`;
|
|
16
|
+
return `return I.usePlaywrightTo('pw', async (playwright) => (${expr.trim()})(playwright))`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function takePwValue(held: unknown): string | undefined {
|
|
20
|
+
if (held === undefined || held === null) return undefined;
|
|
21
|
+
if (typeof held === 'string') return held;
|
|
22
|
+
try {
|
|
23
|
+
return JSON.stringify(held, null, 2);
|
|
24
|
+
} catch {
|
|
25
|
+
return String(held);
|
|
26
|
+
}
|
|
17
27
|
}
|
|
@@ -20,9 +20,8 @@ export function readDescriptors(dir = registryDir()): PwServerDescriptor[] {
|
|
|
20
20
|
return descriptors;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
export function selectDescriptor(descriptors: PwServerDescriptor[], opts: {
|
|
24
|
-
const
|
|
25
|
-
const candidates = descriptors.filter((descriptor) => path.resolve(descriptor.workspaceDir) === workspaceDir);
|
|
23
|
+
export function selectDescriptor(descriptors: PwServerDescriptor[], opts: { title?: string } = {}): { match?: PwServerDescriptor; candidates: PwServerDescriptor[] } {
|
|
24
|
+
const candidates = descriptors;
|
|
26
25
|
if (!candidates.length) return { candidates };
|
|
27
26
|
|
|
28
27
|
if (opts.title) {
|
|
@@ -53,13 +52,13 @@ function parseDescriptor(file: string): PwServerDescriptor | null {
|
|
|
53
52
|
return null;
|
|
54
53
|
}
|
|
55
54
|
|
|
56
|
-
if (!data?.endpoint || !data?.title
|
|
55
|
+
if (!data?.endpoint || !data?.title) return null;
|
|
57
56
|
|
|
58
57
|
return {
|
|
59
58
|
file,
|
|
60
59
|
title: data.title,
|
|
61
60
|
endpoint: data.endpoint,
|
|
62
|
-
workspaceDir: data.workspaceDir,
|
|
61
|
+
workspaceDir: data.workspaceDir || '',
|
|
63
62
|
browserName: data.browser?.browserName || DEFAULT_BROWSER,
|
|
64
63
|
playwrightLib: data.playwrightLib || '',
|
|
65
64
|
};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { outputPath } from '../../../src/config.ts';
|
|
4
|
+
import { safeFilename } from '../../../src/utils/strings.ts';
|
|
5
|
+
import { type EnvelopeData, renderEnvelope } from './envelope.ts';
|
|
6
|
+
|
|
7
|
+
const EXPECTATION_STATUS = { passed: 'passed', failed: 'failed', unverified: 'skipped' };
|
|
8
|
+
|
|
9
|
+
export function sessionsDir(): string {
|
|
10
|
+
return outputPath('prima', 'sessions');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function sessionFile(key: string): string {
|
|
14
|
+
return path.join(sessionsDir(), `${safeFilename(key)}.jsonl`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function latestSessionFile(): string | null {
|
|
18
|
+
const dir = sessionsDir();
|
|
19
|
+
if (!existsSync(dir)) return null;
|
|
20
|
+
|
|
21
|
+
const files = readdirSync(dir)
|
|
22
|
+
.filter((name) => name.endsWith('.jsonl'))
|
|
23
|
+
.map((name) => path.join(dir, name))
|
|
24
|
+
.sort((a, b) => statSync(b).mtimeMs - statSync(a).mtimeMs);
|
|
25
|
+
|
|
26
|
+
return files[0] || null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function recordCommand(file: string, session: SessionRun, envelope: EnvelopeData, durationMs: number): void {
|
|
30
|
+
mkdirSync(path.dirname(file), { recursive: true });
|
|
31
|
+
if (recordedEndpoint(file) !== session.endpoint) writeFileSync(file, jsonLine({ action: 'createRun', endpoint: session.endpoint, params: { title: session.title } }), 'utf-8');
|
|
32
|
+
appendFileSync(file, jsonLine({ action: 'addTest', testId: commandTest(envelope, durationMs) }), 'utf-8');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function readSession(file: string): { title: string; tests: any[] } {
|
|
36
|
+
const entries = readEntries(file);
|
|
37
|
+
const run = entries.find((entry) => entry.action === 'createRun');
|
|
38
|
+
const tests = entries.filter((entry) => entry.action === 'addTest').map((entry) => entry.testId);
|
|
39
|
+
return { title: run?.params?.title || 'prima session', tests };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function commandTest(envelope: EnvelopeData, durationMs: number): any {
|
|
43
|
+
let status = 'failed';
|
|
44
|
+
if (envelope.ok) status = 'passed';
|
|
45
|
+
|
|
46
|
+
const test: any = {
|
|
47
|
+
rid: envelope.status,
|
|
48
|
+
title: envelope.command,
|
|
49
|
+
suite_title: 'Prima session',
|
|
50
|
+
status,
|
|
51
|
+
file: '<prima>',
|
|
52
|
+
description: [`page: ${envelope.page.url}`, envelope.stepFiles && `artifacts: ${envelope.stepFiles}`].filter(Boolean).join('\n'),
|
|
53
|
+
code: (envelope.used || []).join('\n'),
|
|
54
|
+
steps: commandSteps(envelope),
|
|
55
|
+
logs: renderEnvelope(envelope),
|
|
56
|
+
time: Math.round(durationMs),
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
if (envelope.failure) test.error = { message: envelope.failure.error };
|
|
60
|
+
return test;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function commandSteps(envelope: EnvelopeData): any[] {
|
|
64
|
+
const steps: any[] = [];
|
|
65
|
+
|
|
66
|
+
for (const step of envelope.steps || []) {
|
|
67
|
+
const entry: any = { category: 'user', title: step.label, status: 'passed', duration: 0, log: step.proof };
|
|
68
|
+
if (!step.ok) {
|
|
69
|
+
entry.status = 'failed';
|
|
70
|
+
entry.error = step.proof;
|
|
71
|
+
}
|
|
72
|
+
steps.push(entry);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
for (const expectation of envelope.expectations || []) {
|
|
76
|
+
const entry: any = { category: 'user', title: `expected: ${expectation.text}`, status: EXPECTATION_STATUS[expectation.status], duration: 0 };
|
|
77
|
+
if (expectation.status === 'failed') entry.error = 'the run did not reach this outcome';
|
|
78
|
+
steps.push(entry);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
for (const assertion of envelope.assertions || []) {
|
|
82
|
+
const entry: any = { category: 'user', title: assertion.code, status: 'passed', duration: 0, log: assertion.proof.join('\n') };
|
|
83
|
+
if (!assertion.passed) {
|
|
84
|
+
entry.status = 'failed';
|
|
85
|
+
entry.error = 'the assertion did not hold on the page';
|
|
86
|
+
}
|
|
87
|
+
steps.push(entry);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return steps;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function recordedEndpoint(file: string): string | null {
|
|
94
|
+
const [first] = readEntries(file);
|
|
95
|
+
return first?.endpoint || null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function readEntries(file: string): any[] {
|
|
99
|
+
if (!existsSync(file)) return [];
|
|
100
|
+
|
|
101
|
+
const entries: any[] = [];
|
|
102
|
+
for (const line of readFileSync(file, 'utf-8').split('\n')) {
|
|
103
|
+
if (!line.trim()) continue;
|
|
104
|
+
const entry = parseLine(line);
|
|
105
|
+
if (entry) entries.push(entry);
|
|
106
|
+
}
|
|
107
|
+
return entries;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function parseLine(line: string): any {
|
|
111
|
+
try {
|
|
112
|
+
return JSON.parse(line);
|
|
113
|
+
} catch {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function jsonLine(entry: unknown): string {
|
|
119
|
+
return `${JSON.stringify(entry)}\n`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface SessionRun {
|
|
123
|
+
key: string;
|
|
124
|
+
endpoint: string;
|
|
125
|
+
title: string;
|
|
126
|
+
}
|
|
@@ -11,10 +11,11 @@ import { App } from '../src/components/App.js';
|
|
|
11
11
|
import { StatusPane } from '../src/components/StatusPane.js';
|
|
12
12
|
import { ConfigParser, EXPLORBOT_ENV_VARS, PROVIDERS } from '../src/config.js';
|
|
13
13
|
import { ExplorBot } from '../src/explorbot.js';
|
|
14
|
+
import { remote } from '../src/remote.js';
|
|
14
15
|
import { Stats } from '../src/stats.js';
|
|
15
16
|
import { Plan } from '../src/test-plan.js';
|
|
16
17
|
import { getCliName } from "../src/utils/cli-name.js";
|
|
17
|
-
import { log, setPreserveConsoleLogs } from '../src/utils/logger.js';
|
|
18
|
+
import { isVerboseMode, log, setPreserveConsoleLogs, setQuietMode } from '../src/utils/logger.js';
|
|
18
19
|
import { jsonToTable } from '../src/utils/markdown-parser.js';
|
|
19
20
|
import { parseMarkdownToTerminal } from '../src/utils/markdown-terminal.js';
|
|
20
21
|
import { printNextSteps, relativeToCwd } from "../src/utils/next-steps.js";
|
|
@@ -23,7 +24,8 @@ const cli = getCliName();
|
|
|
23
24
|
const pkgPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../package.json');
|
|
24
25
|
const pkgVersion = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')).version;
|
|
25
26
|
program.name(cli).description('AI-powered web exploration tool').version(pkgVersion, '-V, --version');
|
|
26
|
-
|
|
27
|
+
remote.registerOption(program);
|
|
28
|
+
if (!process.env.EXPLORBOT_NO_BANNER && !process.argv.includes('prima')) {
|
|
27
29
|
console.log(`⛵ ${chalk.yellow.bold(`Explorbot v${pkgVersion}`)} ${chalk.dim('Autonomous Testing Agent')}`);
|
|
28
30
|
}
|
|
29
31
|
function buildExplorBotOptions(from, options) {
|
|
@@ -74,6 +76,10 @@ async function startTUI(explorBot) {
|
|
|
74
76
|
});
|
|
75
77
|
}
|
|
76
78
|
async function showStatsAndExit(code) {
|
|
79
|
+
if (remote.isAttached()) {
|
|
80
|
+
await remote.close(code);
|
|
81
|
+
process.exit(code);
|
|
82
|
+
}
|
|
77
83
|
if (Stats.hasActivity()) {
|
|
78
84
|
await new Promise((resolve) => {
|
|
79
85
|
const { unmount } = render(React.createElement(StatusPane, {
|
|
@@ -380,6 +386,23 @@ addCommonOptions(program
|
|
|
380
386
|
const cmd = new FreesailCommand(explorBot);
|
|
381
387
|
await cmd.execute(args);
|
|
382
388
|
});
|
|
389
|
+
program
|
|
390
|
+
.command('config [url]')
|
|
391
|
+
.description('Show models, config file and paths used by this run')
|
|
392
|
+
.option('-c, --config <path>', 'Path to configuration file')
|
|
393
|
+
.option('-p, --path <path>', 'Working directory path')
|
|
394
|
+
.option('--json', 'Print the resolved config as JSON')
|
|
395
|
+
.action(async (url, options) => {
|
|
396
|
+
setQuietMode(!isVerboseMode());
|
|
397
|
+
const { ConfigCommand } = await import('../src/commands/config-command.js');
|
|
398
|
+
try {
|
|
399
|
+
console.log(await ConfigCommand.summary({ config: options.config, path: options.path, url, json: options.json }));
|
|
400
|
+
}
|
|
401
|
+
catch (error) {
|
|
402
|
+
console.error(error instanceof Error ? error.message : 'Unknown error');
|
|
403
|
+
process.exit(1);
|
|
404
|
+
}
|
|
405
|
+
});
|
|
383
406
|
program
|
|
384
407
|
.command('init')
|
|
385
408
|
.description('Initialize configuration for a project or for this machine')
|
|
@@ -820,10 +843,5 @@ ${rows}
|
|
|
820
843
|
${cli} explore /login --max-tests 3
|
|
821
844
|
`;
|
|
822
845
|
};
|
|
823
|
-
|
|
824
|
-
cmd.addHelpText('after', envHelp);
|
|
825
|
-
for (const sub of cmd.commands)
|
|
826
|
-
addEnvHelp(sub);
|
|
827
|
-
};
|
|
828
|
-
addEnvHelp(program);
|
|
846
|
+
program.addHelpText('after', envHelp);
|
|
829
847
|
program.parse();
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { Command } from 'commander';
|
|
4
|
+
import { ConfigCommand } from "../../../src/commands/config-command.js";
|
|
5
|
+
import { listSites } from "../../../src/global-config.js";
|
|
4
6
|
import { setPreserveConsoleLogs } from "../../../src/utils/logger.js";
|
|
5
7
|
import { getStyles } from "./ai/chief/styles.js";
|
|
6
8
|
import { ApiBot } from "./apibot.js";
|
|
9
|
+
import { ApibotConfigParser } from "./config.js";
|
|
7
10
|
function buildOptions(options) {
|
|
8
11
|
return {
|
|
9
12
|
verbose: options.verbose || options.debug,
|
|
@@ -68,6 +71,20 @@ export function createApiCommands(name = 'api') {
|
|
|
68
71
|
process.exit(1);
|
|
69
72
|
}
|
|
70
73
|
});
|
|
74
|
+
addCommonOptions(cmd.command('config [endpoint]').description('Show models, config file and paths used by this run'))
|
|
75
|
+
.option('--json', 'Print the resolved config as JSON')
|
|
76
|
+
.action(async (endpoint, options) => {
|
|
77
|
+
const parser = ApibotConfigParser.getInstance();
|
|
78
|
+
const [site] = listSites();
|
|
79
|
+
try {
|
|
80
|
+
const config = await parser.loadConfig({ config: options.config, path: options.path, endpoint: endpoint || site?.url });
|
|
81
|
+
console.log(ConfigCommand.render(config, { configPath: parser.getConfigPath(), root: parser.getProjectRoot(), json: options.json }));
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
console.error(error instanceof Error ? error.message : 'Unknown error');
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
71
88
|
addCommonOptions(cmd.command('test <planfile> [index]').description('Execute tests from a plan file. Index: 1, 1-3, *')).action(async (planfile, index, options) => {
|
|
72
89
|
setPreserveConsoleLogs(true);
|
|
73
90
|
try {
|
|
@@ -8,6 +8,7 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
8
8
|
};
|
|
9
9
|
import { existsSync, mkdirSync, readFileSync } from 'node:fs';
|
|
10
10
|
import path, { resolve } from 'node:path';
|
|
11
|
+
import { pathToFileURL } from 'node:url';
|
|
11
12
|
import { parseEnv } from 'node:util';
|
|
12
13
|
import { ConfigMissingError, EXPLORBOT_CONFIG_PATHS, createModel, envConfigRequested, materializeKnowledge, missingConfigMessage, resolveConfigModels, resolveModel, resolveOutputRoot } from "../../../src/config.js";
|
|
13
14
|
import { findGlobalConfig, globalEnvPath, isGlobalConfigPath, registerSite, resolveSiteTarget } from "../../../src/global-config.js";
|
|
@@ -181,9 +182,10 @@ export class ApibotConfigParser {
|
|
|
181
182
|
}
|
|
182
183
|
async loadConfigModule(configPath) {
|
|
183
184
|
const ext = configPath.split('.').pop();
|
|
185
|
+
const moduleUrl = pathToFileURL(resolve(configPath)).href;
|
|
184
186
|
if (ext === 'ts') {
|
|
185
187
|
try {
|
|
186
|
-
return await import(__rewriteRelativeImportExtension(
|
|
188
|
+
return await import(__rewriteRelativeImportExtension(moduleUrl));
|
|
187
189
|
}
|
|
188
190
|
catch {
|
|
189
191
|
const require = (await import('node:module')).createRequire(import.meta.url);
|
|
@@ -191,7 +193,7 @@ export class ApibotConfigParser {
|
|
|
191
193
|
}
|
|
192
194
|
}
|
|
193
195
|
if (ext === 'js' || ext === 'mjs') {
|
|
194
|
-
return await import(__rewriteRelativeImportExtension(
|
|
196
|
+
return await import(__rewriteRelativeImportExtension(moduleUrl));
|
|
195
197
|
}
|
|
196
198
|
const content = readFileSync(configPath, 'utf8');
|
|
197
199
|
return JSON.parse(content);
|
|
@@ -55,13 +55,22 @@ class Documentarian {
|
|
|
55
55
|
const message = error instanceof Error ? error.message : String(error);
|
|
56
56
|
tag('warning').log(`Interactive documentation failed: ${message}.`);
|
|
57
57
|
if (meaningfulInteractions.length > 0) {
|
|
58
|
-
tag('info').log(`
|
|
59
|
-
return this.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
might: [],
|
|
58
|
+
tag('info').log(`Retrying static documentation while preserving ${meaningfulInteractions.length} observed interaction(s).`);
|
|
59
|
+
return this.documentStatic(state, research)
|
|
60
|
+
.then((documentation) => this.normalizeDocumentation({
|
|
61
|
+
...documentation,
|
|
63
62
|
interactions: meaningfulInteractions,
|
|
64
|
-
}, state, research)
|
|
63
|
+
}, state, research))
|
|
64
|
+
.catch((fallbackError) => {
|
|
65
|
+
const fallbackMessage = fallbackError instanceof Error ? fallbackError.message : String(fallbackError);
|
|
66
|
+
tag('warning').log(`Static documentation fallback failed: ${fallbackMessage}. Preserving observed interactions without AI summary.`);
|
|
67
|
+
return this.normalizeDocumentation({
|
|
68
|
+
summary: `Observed ${meaningfulInteractions.length} interaction(s); AI-generated summary was unavailable.`,
|
|
69
|
+
can: [],
|
|
70
|
+
might: [],
|
|
71
|
+
interactions: meaningfulInteractions,
|
|
72
|
+
}, state, research);
|
|
73
|
+
});
|
|
65
74
|
}
|
|
66
75
|
return this.documentStatic(state, research);
|
|
67
76
|
}
|
|
@@ -94,7 +103,7 @@ class Documentarian {
|
|
|
94
103
|
content: this.buildPrompt(state, `${research}${this.buildInteractionContext(interactions)}`),
|
|
95
104
|
},
|
|
96
105
|
];
|
|
97
|
-
const response = await this.provider.generateObject(messages,
|
|
106
|
+
const response = await this.provider.generateObject(messages, generatedPageDocumentationSchema, undefined, {
|
|
98
107
|
agentName: 'documentarian',
|
|
99
108
|
});
|
|
100
109
|
return this.normalizeDocumentation({
|
|
@@ -113,7 +122,7 @@ class Documentarian {
|
|
|
113
122
|
content: this.buildPrompt(state, research, simplified),
|
|
114
123
|
},
|
|
115
124
|
];
|
|
116
|
-
const response = await this.provider.generateObject(messages,
|
|
125
|
+
const response = await this.provider.generateObject(messages, generatedPageDocumentationSchema, undefined, {
|
|
117
126
|
agentName: 'documentarian',
|
|
118
127
|
});
|
|
119
128
|
return this.normalizeDocumentation(response.object, state, research);
|
|
@@ -230,10 +239,10 @@ class Documentarian {
|
|
|
230
239
|
normalized.interactions = undefined;
|
|
231
240
|
}
|
|
232
241
|
const qualityNotes = this.evaluateDocumentationQuality(normalized);
|
|
233
|
-
return {
|
|
242
|
+
return pageDocumentationSchema.parse({
|
|
234
243
|
...normalized,
|
|
235
244
|
qualityNotes,
|
|
236
|
-
};
|
|
245
|
+
});
|
|
237
246
|
}
|
|
238
247
|
evaluateDocumentationQuality(documentation) {
|
|
239
248
|
const notes = [];
|
|
@@ -296,30 +305,46 @@ const stateTransitionSchema = z.object({
|
|
|
296
305
|
action: z.string(),
|
|
297
306
|
before: z.string(),
|
|
298
307
|
after: z.string(),
|
|
299
|
-
targetUrl: z.string().
|
|
300
|
-
discoveredUrls: z.array(z.string()).
|
|
301
|
-
newCapabilities: z.array(z.string()).
|
|
308
|
+
targetUrl: z.string().optional(),
|
|
309
|
+
discoveredUrls: z.array(z.string()).optional(),
|
|
310
|
+
newCapabilities: z.array(z.string()).optional(),
|
|
302
311
|
element: z
|
|
303
312
|
.object({
|
|
304
313
|
role: z.string(),
|
|
305
314
|
name: z.string(),
|
|
306
315
|
section: z.string(),
|
|
307
|
-
container: z.string().
|
|
308
|
-
locator: z.string().
|
|
316
|
+
container: z.string().optional(),
|
|
317
|
+
locator: z.string().optional(),
|
|
309
318
|
})
|
|
310
|
-
.
|
|
319
|
+
.optional(),
|
|
311
320
|
changes: z
|
|
312
321
|
.object({
|
|
313
322
|
urlChanged: z.boolean(),
|
|
314
323
|
newElements: z.number(),
|
|
315
324
|
removedElements: z.number(),
|
|
316
325
|
})
|
|
317
|
-
.
|
|
326
|
+
.optional(),
|
|
327
|
+
targetState: z
|
|
328
|
+
.object({
|
|
329
|
+
kind: z.enum(['page', 'dialog', 'modal', 'section']),
|
|
330
|
+
label: z.string(),
|
|
331
|
+
url: z.string(),
|
|
332
|
+
})
|
|
333
|
+
.optional(),
|
|
334
|
+
screenshot: z
|
|
335
|
+
.object({
|
|
336
|
+
title: z.string(),
|
|
337
|
+
relativePath: z.string(),
|
|
338
|
+
})
|
|
339
|
+
.optional(),
|
|
318
340
|
});
|
|
319
|
-
const
|
|
341
|
+
const generatedPageDocumentationSchema = z.object({
|
|
320
342
|
summary: z.string(),
|
|
321
343
|
can: z.array(capabilitySchema),
|
|
322
344
|
might: z.array(capabilitySchema),
|
|
323
|
-
|
|
345
|
+
});
|
|
346
|
+
const pageDocumentationSchema = generatedPageDocumentationSchema.extend({
|
|
347
|
+
interactions: z.array(stateTransitionSchema).optional(),
|
|
348
|
+
qualityNotes: z.array(z.string()).optional(),
|
|
324
349
|
});
|
|
325
350
|
export { Documentarian };
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { Command } from 'commander';
|
|
4
|
-
import {
|
|
4
|
+
import { ConfigCommand } from "../../../src/commands/config-command.js";
|
|
5
|
+
import { isVerboseMode, setPreserveConsoleLogs, setQuietMode } from "../../../src/utils/logger.js";
|
|
5
6
|
import { DocBot } from "./docbot.js";
|
|
6
7
|
function buildOptions(options) {
|
|
7
8
|
return {
|
|
@@ -56,6 +57,18 @@ export function createDocsCommands(name = 'docs') {
|
|
|
56
57
|
process.exit(1);
|
|
57
58
|
}
|
|
58
59
|
});
|
|
60
|
+
addCommonOptions(cmd.command('config [url]').description('Show models, config file and paths used by this run'))
|
|
61
|
+
.option('--json', 'Print the resolved config as JSON')
|
|
62
|
+
.action(async (url, options) => {
|
|
63
|
+
setQuietMode(!isVerboseMode());
|
|
64
|
+
try {
|
|
65
|
+
console.log(await ConfigCommand.summary({ config: options.config, path: options.path, url, json: options.json }));
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
console.error(error instanceof Error ? error.message : 'Unknown error');
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
59
72
|
cmd
|
|
60
73
|
.command('init')
|
|
61
74
|
.description('Initialize doc collector configuration')
|
|
@@ -8,6 +8,7 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
8
8
|
};
|
|
9
9
|
import { existsSync, readFileSync } from 'node:fs';
|
|
10
10
|
import path, { resolve } from 'node:path';
|
|
11
|
+
import { pathToFileURL } from 'node:url';
|
|
11
12
|
import { parseEnv } from 'node:util';
|
|
12
13
|
import { ConfigParser } from "../../../src/config.js";
|
|
13
14
|
class DocbotConfigParser {
|
|
@@ -84,9 +85,10 @@ class DocbotConfigParser {
|
|
|
84
85
|
}
|
|
85
86
|
async loadConfigModule(configPath) {
|
|
86
87
|
const ext = configPath.split('.').pop();
|
|
88
|
+
const moduleUrl = pathToFileURL(resolve(configPath)).href;
|
|
87
89
|
if (ext === 'ts') {
|
|
88
90
|
try {
|
|
89
|
-
return await import(__rewriteRelativeImportExtension(
|
|
91
|
+
return await import(__rewriteRelativeImportExtension(moduleUrl));
|
|
90
92
|
}
|
|
91
93
|
catch {
|
|
92
94
|
const require = (await import('node:module')).createRequire(import.meta.url);
|
|
@@ -94,7 +96,7 @@ class DocbotConfigParser {
|
|
|
94
96
|
}
|
|
95
97
|
}
|
|
96
98
|
if (ext === 'js' || ext === 'mjs') {
|
|
97
|
-
return await import(__rewriteRelativeImportExtension(
|
|
99
|
+
return await import(__rewriteRelativeImportExtension(moduleUrl));
|
|
98
100
|
}
|
|
99
101
|
return JSON.parse(readFileSync(configPath, 'utf8'));
|
|
100
102
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { addActivityListener, removeActivityListener } from "../../../src/activity.js";
|
|
3
|
+
import { isVerboseMode } from "../../../src/utils/logger.js";
|
|
4
|
+
const RESET_LINE = '\r\u001b[2K';
|
|
5
|
+
const stream = process.stderr;
|
|
6
|
+
let tracking = false;
|
|
7
|
+
export function trackActivityLine() {
|
|
8
|
+
if (tracking)
|
|
9
|
+
return;
|
|
10
|
+
if (!stream.isTTY)
|
|
11
|
+
return;
|
|
12
|
+
if (isVerboseMode())
|
|
13
|
+
return;
|
|
14
|
+
tracking = true;
|
|
15
|
+
addActivityListener(writeActivityLine);
|
|
16
|
+
}
|
|
17
|
+
export function clearActivityLine() {
|
|
18
|
+
if (!tracking)
|
|
19
|
+
return;
|
|
20
|
+
tracking = false;
|
|
21
|
+
removeActivityListener(writeActivityLine);
|
|
22
|
+
stream.write(RESET_LINE);
|
|
23
|
+
}
|
|
24
|
+
function writeActivityLine(activity) {
|
|
25
|
+
if (!activity)
|
|
26
|
+
return;
|
|
27
|
+
const width = (stream.columns || 80) - 2;
|
|
28
|
+
const message = Array.from(activity.message.replace(/\s+/g, ' ').trim()).slice(0, width).join('');
|
|
29
|
+
stream.write(`${RESET_LINE}${chalk.gray(message)}`);
|
|
30
|
+
}
|