explorbot 0.1.16 → 0.1.18
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 +14 -1
- package/boat/doc-collector/bin/doc-collector-cli.ts +5 -0
- package/boat/doc-collector/package.json +24 -0
- package/boat/doc-collector/src/ai/documentarian.ts +184 -0
- package/boat/doc-collector/src/cli.ts +119 -0
- package/boat/doc-collector/src/config.ts +162 -0
- package/boat/doc-collector/src/docbot.ts +391 -0
- package/boat/doc-collector/src/docs-renderer.ts +187 -0
- package/boat/doc-collector/src/path-filter.ts +46 -0
- package/boat/doc-collector/src/research-navigation.ts +90 -0
- package/dist/bin/explorbot-cli.js +15 -1
- package/dist/boat/doc-collector/bin/doc-collector-cli.js +4 -0
- package/dist/boat/doc-collector/src/ai/documentarian.js +157 -0
- package/dist/boat/doc-collector/src/cli.js +104 -0
- package/dist/boat/doc-collector/src/config.js +129 -0
- package/dist/boat/doc-collector/src/docbot.js +326 -0
- package/dist/boat/doc-collector/src/docs-renderer.js +141 -0
- package/dist/boat/doc-collector/src/path-filter.js +35 -0
- package/dist/boat/doc-collector/src/research-navigation.js +71 -0
- package/dist/package.json +4 -1
- package/dist/src/ai/pilot.js +3 -8
- package/dist/src/ai/researcher/coordinates.js +1 -1
- package/dist/src/ai/researcher/parser.js +3 -0
- package/dist/src/ai/researcher.js +2 -1
- package/dist/src/ai/tester.js +1 -0
- package/dist/src/commands/explore-command.js +359 -43
- package/dist/src/config.js +10 -3
- package/dist/src/explorbot.js +19 -5
- package/dist/src/explorer.js +14 -1
- package/dist/src/state-manager.js +3 -0
- package/dist/src/utils/test-plan-markdown.js +8 -1
- package/dist/src/utils/url-matcher.js +5 -3
- package/dist/src/utils/web-element.js +3 -2
- package/package.json +4 -1
- package/src/ai/pilot.ts +3 -8
- package/src/ai/researcher/coordinates.ts +1 -1
- package/src/ai/researcher/parser.ts +3 -0
- package/src/ai/researcher.ts +2 -1
- package/src/ai/tester.ts +1 -0
- package/src/commands/explore-command.ts +362 -42
- package/src/config.ts +13 -3
- package/src/explorbot.ts +22 -7
- package/src/explorer.ts +12 -1
- package/src/state-manager.ts +4 -0
- package/src/utils/test-plan-markdown.ts +8 -1
- package/src/utils/url-matcher.ts +5 -2
- package/src/utils/web-element.ts +3 -2
package/src/config.ts
CHANGED
|
@@ -266,6 +266,7 @@ export class ConfigParser {
|
|
|
266
266
|
private static instance: ConfigParser;
|
|
267
267
|
private config: ExplorbotConfig | null = null;
|
|
268
268
|
private configPath: string | null = null;
|
|
269
|
+
private runtimeBaseUrlOverride: string | null = null;
|
|
269
270
|
|
|
270
271
|
private constructor() {}
|
|
271
272
|
|
|
@@ -285,8 +286,9 @@ export class ConfigParser {
|
|
|
285
286
|
public async loadConfig(options?: {
|
|
286
287
|
config?: string;
|
|
287
288
|
path?: string;
|
|
289
|
+
baseUrl?: string;
|
|
288
290
|
}): Promise<ExplorbotConfig> {
|
|
289
|
-
if (this.config && !options?.config && !options?.path) {
|
|
291
|
+
if (this.config && !options?.config && !options?.path && this.runtimeBaseUrlOverride === (options?.baseUrl || null)) {
|
|
290
292
|
return this.config;
|
|
291
293
|
}
|
|
292
294
|
|
|
@@ -317,7 +319,8 @@ export class ConfigParser {
|
|
|
317
319
|
throw new Error('Configuration file is empty or invalid');
|
|
318
320
|
}
|
|
319
321
|
|
|
320
|
-
this.config = this.resolveConfig(loadedConfig as ExplorbotConfig);
|
|
322
|
+
this.config = this.resolveConfig(loadedConfig as ExplorbotConfig, options);
|
|
323
|
+
this.runtimeBaseUrlOverride = options?.baseUrl || null;
|
|
321
324
|
this.configPath = resolvedPath;
|
|
322
325
|
|
|
323
326
|
log(`Configuration loaded from: ${resolvedPath}`);
|
|
@@ -372,6 +375,7 @@ export class ConfigParser {
|
|
|
372
375
|
if (ConfigParser.instance) {
|
|
373
376
|
ConfigParser.instance.config = null;
|
|
374
377
|
ConfigParser.instance.configPath = null;
|
|
378
|
+
ConfigParser.instance.runtimeBaseUrlOverride = null;
|
|
375
379
|
}
|
|
376
380
|
}
|
|
377
381
|
|
|
@@ -455,11 +459,17 @@ export class ConfigParser {
|
|
|
455
459
|
}
|
|
456
460
|
}
|
|
457
461
|
|
|
458
|
-
private resolveConfig(config: ExplorbotConfig): ExplorbotConfig {
|
|
462
|
+
private resolveConfig(config: ExplorbotConfig, options?: { baseUrl?: string }): ExplorbotConfig {
|
|
459
463
|
if (config.web?.url && !config.playwright?.url) {
|
|
460
464
|
config.playwright = config.playwright || { browser: 'chromium', url: '' };
|
|
461
465
|
config.playwright.url = config.web.url;
|
|
462
466
|
}
|
|
467
|
+
|
|
468
|
+
if (options?.baseUrl) {
|
|
469
|
+
config.playwright = config.playwright || { browser: 'chromium', url: '' };
|
|
470
|
+
config.playwright.url = options.baseUrl;
|
|
471
|
+
}
|
|
472
|
+
|
|
463
473
|
return config;
|
|
464
474
|
}
|
|
465
475
|
|
package/src/explorbot.ts
CHANGED
|
@@ -27,12 +27,14 @@ import { KnowledgeTracker } from './knowledge-tracker.ts';
|
|
|
27
27
|
import { WebPageState } from './state-manager.ts';
|
|
28
28
|
import type { Suite } from './suite.ts';
|
|
29
29
|
import { Plan, type Test } from './test-plan.ts';
|
|
30
|
+
import { parsePlansFromMarkdown } from './utils/test-plan-markdown.ts';
|
|
30
31
|
import { setVerboseMode, tag } from './utils/logger.ts';
|
|
31
32
|
import { relativeToCwd } from './utils/next-steps.ts';
|
|
32
33
|
import { sanitizeFilename } from './utils/strings.ts';
|
|
33
34
|
|
|
34
35
|
export interface ExplorBotOptions {
|
|
35
36
|
from?: string;
|
|
37
|
+
baseUrl?: string;
|
|
36
38
|
verbose?: boolean;
|
|
37
39
|
config?: string;
|
|
38
40
|
path?: string;
|
|
@@ -349,7 +351,7 @@ export class ExplorBot {
|
|
|
349
351
|
this.agents.planner = undefined;
|
|
350
352
|
}
|
|
351
353
|
|
|
352
|
-
async plan(feature?: string, opts: { fresh?: boolean; style?: string; extend?: Plan; completedPlans?: Plan[] } = {}) {
|
|
354
|
+
async plan(feature?: string, opts: { fresh?: boolean; style?: string; extend?: Plan; completedPlans?: Plan[]; noSave?: boolean } = {}) {
|
|
353
355
|
this.planFeature = feature;
|
|
354
356
|
|
|
355
357
|
if (opts.fresh) {
|
|
@@ -379,7 +381,7 @@ export class ExplorBot {
|
|
|
379
381
|
return this.currentPlan;
|
|
380
382
|
}
|
|
381
383
|
|
|
382
|
-
this.savePlan();
|
|
384
|
+
if (!opts.noSave) this.savePlan();
|
|
383
385
|
|
|
384
386
|
return this.currentPlan;
|
|
385
387
|
}
|
|
@@ -409,19 +411,20 @@ export class ExplorBot {
|
|
|
409
411
|
return planPath;
|
|
410
412
|
}
|
|
411
413
|
|
|
412
|
-
generatePlanFilename(): string {
|
|
414
|
+
generatePlanFilename(feature?: string): string {
|
|
413
415
|
const state = this.explorer?.getStateManager().getCurrentState();
|
|
414
416
|
const urlPath = state?.url || '/';
|
|
415
417
|
const urlPart = sanitizeFilename(urlPath) || 'root';
|
|
416
418
|
const suffix = '.md';
|
|
417
|
-
|
|
418
|
-
|
|
419
|
+
const f = feature ?? this.planFeature;
|
|
420
|
+
if (!f) return urlPart.slice(0, 256 - suffix.length) + suffix;
|
|
421
|
+
const featurePart = `_${sanitizeFilename(f)}`;
|
|
419
422
|
const maxFeatureLen = 256 - suffix.length - urlPart.length;
|
|
420
423
|
if (maxFeatureLen <= 1) return urlPart.slice(0, 256 - suffix.length) + suffix;
|
|
421
424
|
return urlPart + featurePart.slice(0, maxFeatureLen) + suffix;
|
|
422
425
|
}
|
|
423
426
|
|
|
424
|
-
|
|
427
|
+
resolvePlanPath(filename: string): string {
|
|
425
428
|
let planPath = filename;
|
|
426
429
|
|
|
427
430
|
if (path.isAbsolute(filename)) {
|
|
@@ -438,14 +441,26 @@ export class ExplorBot {
|
|
|
438
441
|
}
|
|
439
442
|
}
|
|
440
443
|
|
|
444
|
+
return planPath;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
loadPlan(filename: string): Plan {
|
|
448
|
+
const planPath = this.resolvePlanPath(filename);
|
|
441
449
|
if (!existsSync(planPath)) {
|
|
442
450
|
throw new Error(`Plan file not found: ${planPath}`);
|
|
443
451
|
}
|
|
444
|
-
|
|
445
452
|
this.setCurrentPlan(Plan.fromMarkdown(planPath));
|
|
446
453
|
return this.currentPlan!;
|
|
447
454
|
}
|
|
448
455
|
|
|
456
|
+
loadPlans(filename: string): Plan[] {
|
|
457
|
+
const planPath = this.resolvePlanPath(filename);
|
|
458
|
+
if (!existsSync(planPath)) {
|
|
459
|
+
throw new Error(`Plan file not found: ${planPath}`);
|
|
460
|
+
}
|
|
461
|
+
return parsePlansFromMarkdown(planPath);
|
|
462
|
+
}
|
|
463
|
+
|
|
449
464
|
setCurrentPlan(plan?: Plan): void {
|
|
450
465
|
this.currentPlan = plan;
|
|
451
466
|
if (plan && !this.sessionPlans.includes(plan)) {
|
package/src/explorer.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { createTest } from 'codeceptjs/lib/mocha/test';
|
|
|
8
8
|
import { ActionResult } from './action-result.ts';
|
|
9
9
|
import Action from './action.js';
|
|
10
10
|
import { AIProvider } from './ai/provider.js';
|
|
11
|
+
import type { BrowserContextOptions } from 'playwright';
|
|
11
12
|
import { visuallyAnnotateContainers } from './ai/researcher/coordinates.ts';
|
|
12
13
|
import { RequestStore } from './api/request-store.ts';
|
|
13
14
|
import { XhrCapture } from './api/xhr-capture.ts';
|
|
@@ -238,7 +239,17 @@ class Explorer {
|
|
|
238
239
|
}
|
|
239
240
|
await this.connectOrLaunchBrowser();
|
|
240
241
|
const hasSession = this.options?.session && existsSync(this.options.session);
|
|
241
|
-
const
|
|
242
|
+
const helperOptions = this.playwrightHelper.options || {};
|
|
243
|
+
// CodeceptJS skips _createContextPage when sessions/storageState are involved, so we
|
|
244
|
+
// build contextOptions ourselves. Most keys share a name with Playwright's
|
|
245
|
+
// BrowserContextOptions and are copied as-is; `emulate` must be flattened, `basicAuth`
|
|
246
|
+
// renamed to `httpCredentials`, and `storageState` comes from the --session flag.
|
|
247
|
+
const contextOptions: BrowserContextOptions = {
|
|
248
|
+
...helperOptions,
|
|
249
|
+
};
|
|
250
|
+
if (helperOptions.emulate) Object.assign(contextOptions, helperOptions.emulate);
|
|
251
|
+
if (helperOptions.basicAuth) contextOptions.httpCredentials = helperOptions.basicAuth;
|
|
252
|
+
if (hasSession) contextOptions.storageState = this.options!.session;
|
|
242
253
|
await this.playwrightHelper._createContextPage(contextOptions);
|
|
243
254
|
await this.playwrightRecorder.start(this.playwrightHelper.browserContext);
|
|
244
255
|
this.setupXhrCapture();
|
package/src/state-manager.ts
CHANGED
|
@@ -547,6 +547,10 @@ export class StateManager {
|
|
|
547
547
|
}
|
|
548
548
|
|
|
549
549
|
export function normalizeUrl(url: string): string {
|
|
550
|
+
if (url.startsWith('/')) {
|
|
551
|
+
return url.replace(/^\/+/, '').replace(/\/+$/g, '');
|
|
552
|
+
}
|
|
553
|
+
|
|
550
554
|
try {
|
|
551
555
|
const parsed = new URL(url, 'http://localhost');
|
|
552
556
|
const path = parsed.pathname.replace(/^\/+|\/+$/g, '');
|
|
@@ -149,8 +149,15 @@ export function parsePlansFromMarkdown(filePath: string): Plan[] {
|
|
|
149
149
|
|
|
150
150
|
if (line.startsWith('<!-- test')) {
|
|
151
151
|
currentTest = null;
|
|
152
|
-
|
|
152
|
+
let block = line;
|
|
153
|
+
let j = i;
|
|
154
|
+
while (!block.includes('-->') && j + 1 < lines.length) {
|
|
155
|
+
j++;
|
|
156
|
+
block += `\n${lines[j].trim()}`;
|
|
157
|
+
}
|
|
158
|
+
const priorityMatch = block.match(/priority:\s*(\w+)/);
|
|
153
159
|
priority = (priorityMatch?.[1] as 'critical' | 'important' | 'high' | 'normal' | 'low') || 'normal';
|
|
160
|
+
i = j;
|
|
154
161
|
continue;
|
|
155
162
|
}
|
|
156
163
|
|
package/src/utils/url-matcher.ts
CHANGED
|
@@ -82,10 +82,13 @@ export function matchesUrl(pattern: string, path: string): boolean {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
export function extractStatePath(url: string): string {
|
|
85
|
-
if (url.startsWith('/'))
|
|
85
|
+
if (url.startsWith('/')) {
|
|
86
|
+
return `/${url.replace(/^\/+/, '')}`;
|
|
87
|
+
}
|
|
86
88
|
try {
|
|
87
89
|
const urlObj = new URL(url);
|
|
88
|
-
|
|
90
|
+
const normalizedPathname = `/${urlObj.pathname.replace(/^\/+/, '')}`;
|
|
91
|
+
return `${normalizedPathname}${urlObj.search}${urlObj.hash}`;
|
|
89
92
|
} catch {
|
|
90
93
|
return url;
|
|
91
94
|
}
|
package/src/utils/web-element.ts
CHANGED
|
@@ -122,7 +122,8 @@ export class WebElement {
|
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
static async fromEidxList(page: any, eidxList: string[]): Promise<WebElement[]> {
|
|
125
|
-
|
|
125
|
+
const validEidxList = eidxList.filter((eidx) => /^e\d+$/i.test(eidx));
|
|
126
|
+
if (validEidxList.length === 0) return [];
|
|
126
127
|
|
|
127
128
|
const rawList: RawElementData[] = await page.evaluate(
|
|
128
129
|
([list, extractFnStr, config]: [string[], string, ElementExtractionConfig]) => {
|
|
@@ -136,7 +137,7 @@ export class WebElement {
|
|
|
136
137
|
}
|
|
137
138
|
return results;
|
|
138
139
|
},
|
|
139
|
-
[
|
|
140
|
+
[validEidxList, getElementDataExtractorSource(), ELEMENT_EXTRACTION_CONFIG] as [string[], string, ElementExtractionConfig]
|
|
140
141
|
);
|
|
141
142
|
|
|
142
143
|
return rawList.map((d) => WebElement.fromRawData(d));
|