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
package/bin/explorbot-cli.ts
CHANGED
|
@@ -8,8 +8,10 @@ import figureSet from 'figures';
|
|
|
8
8
|
import { render } from 'ink';
|
|
9
9
|
import React from 'react';
|
|
10
10
|
import { flushTelemetry } from '../src/ai/provider.js';
|
|
11
|
+
import { RecommendedModelsCommand } from '../src/commands/recommended-models-command.js';
|
|
11
12
|
import { App } from '../src/components/App.js';
|
|
12
13
|
import { StatusPane } from '../src/components/StatusPane.js';
|
|
14
|
+
import { knowledgeOption, wsOption } from '../src/commands/options/index.js';
|
|
13
15
|
import { ConfigParser, EXPLORBOT_ENV_VARS, PROVIDERS } from '../src/config.js';
|
|
14
16
|
import { ExplorBot, type ExplorBotOptions } from '../src/explorbot.js';
|
|
15
17
|
import { remote } from '../src/remote.js';
|
|
@@ -28,7 +30,8 @@ const pkgPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../p
|
|
|
28
30
|
const pkgVersion = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')).version as string;
|
|
29
31
|
|
|
30
32
|
program.name(cli).description('AI-powered web exploration tool').version(pkgVersion, '-V, --version');
|
|
31
|
-
|
|
33
|
+
wsOption.register(program);
|
|
34
|
+
knowledgeOption.register(program);
|
|
32
35
|
|
|
33
36
|
process.on('uncaughtException', async (error) => {
|
|
34
37
|
tag('error').log(`Uncaught exception: ${error instanceof Error ? `${error.message}\n${error.stack}` : String(error)}`);
|
|
@@ -334,12 +337,14 @@ addCommonOptions(program.command('test <planfile> [index]').description('Execute
|
|
|
334
337
|
indexArg = planfile;
|
|
335
338
|
}
|
|
336
339
|
|
|
337
|
-
const
|
|
340
|
+
const peeked = Plan.loadFromFile(planfileArg);
|
|
338
341
|
|
|
339
|
-
const explorBot = new ExplorBot(buildExplorBotOptions(
|
|
342
|
+
const explorBot = new ExplorBot(buildExplorBotOptions(peeked?.startUrl, options));
|
|
340
343
|
await explorBot.start();
|
|
341
344
|
|
|
342
|
-
|
|
345
|
+
let plan = peeked;
|
|
346
|
+
if (plan) explorBot.setCurrentPlan(plan);
|
|
347
|
+
if (!plan) plan = explorBot.loadPlan(planfileArg);
|
|
343
348
|
const pending = plan.getPendingTests();
|
|
344
349
|
log(`Plan loaded: "${plan.title}" (${plan.tests.length} tests, ${pending.length} pending)`);
|
|
345
350
|
|
|
@@ -468,6 +473,8 @@ program
|
|
|
468
473
|
}
|
|
469
474
|
});
|
|
470
475
|
|
|
476
|
+
RecommendedModelsCommand.register(program);
|
|
477
|
+
|
|
471
478
|
program
|
|
472
479
|
.command('init')
|
|
473
480
|
.description('Initialize configuration for a project or for this machine')
|
|
@@ -706,7 +713,7 @@ addCommonOptions(program.command('navigate <url>').description('Navigate to a UR
|
|
|
706
713
|
});
|
|
707
714
|
|
|
708
715
|
addCommonOptions(
|
|
709
|
-
program.command('drill <url>').alias('driller').description('Drill all components on a page to learn interactions').option('--knowledge <path>', 'Save learned interactions to knowledge file at this URL path').option('--max-components <count>', 'Maximum number of components to drill')
|
|
716
|
+
program.command('drill <url>').alias('driller').description('Drill all components on a page to learn interactions').option('--save-knowledge <path>', 'Save learned interactions to knowledge file at this URL path').option('--max-components <count>', 'Maximum number of components to drill')
|
|
710
717
|
).action(async (url, options) => {
|
|
711
718
|
try {
|
|
712
719
|
const explorBot = new ExplorBot(buildExplorBotOptions(url, options));
|
|
@@ -715,7 +722,7 @@ addCommonOptions(
|
|
|
715
722
|
await explorBot.visit(url);
|
|
716
723
|
|
|
717
724
|
const plan = await explorBot.agentDriller().drill({
|
|
718
|
-
knowledgePath: options.
|
|
725
|
+
knowledgePath: options.saveKnowledge,
|
|
719
726
|
maxComponents: Number.parseInt(options.maxComponents || '30', 10),
|
|
720
727
|
interactive: false,
|
|
721
728
|
});
|
|
@@ -3,6 +3,7 @@ import { z } from 'zod';
|
|
|
3
3
|
import { Conversation } from '../../../../src/ai/conversation.ts';
|
|
4
4
|
import { WithSessionDedup } from '../../../../src/ai/planner/session-dedup.ts';
|
|
5
5
|
import type { AIProvider } from '../../../../src/ai/provider.ts';
|
|
6
|
+
import type { KnowledgeTracker } from '../../../../src/knowledge-tracker.ts';
|
|
6
7
|
import { Observability } from '../../../../src/observability.ts';
|
|
7
8
|
import { Plan, Test } from '../../../../src/test-plan.ts';
|
|
8
9
|
import { createDebug, tag } from '../../../../src/utils/logger.ts';
|
|
@@ -33,17 +34,19 @@ export class Chief extends ChiefBase {
|
|
|
33
34
|
private provider: AIProvider;
|
|
34
35
|
private config: ApibotConfig;
|
|
35
36
|
private apiClient: ApiClient | null;
|
|
37
|
+
private knowledgeTracker?: KnowledgeTracker;
|
|
36
38
|
currentPlan: Plan | null = null;
|
|
37
39
|
private lastStyleName = '';
|
|
38
40
|
|
|
39
41
|
MIN_TASKS = 3;
|
|
40
42
|
MAX_TASKS = 10;
|
|
41
43
|
|
|
42
|
-
constructor(provider: AIProvider, config: ApibotConfig, apiClient?: ApiClient | null) {
|
|
44
|
+
constructor(provider: AIProvider, config: ApibotConfig, apiClient?: ApiClient | null, knowledgeTracker?: KnowledgeTracker) {
|
|
43
45
|
super();
|
|
44
46
|
this.provider = provider;
|
|
45
47
|
this.config = config;
|
|
46
48
|
this.apiClient = apiClient || null;
|
|
49
|
+
this.knowledgeTracker = knowledgeTracker;
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
async plan(endpoint: string, opts?: { style?: string; specDefinition?: string }): Promise<Plan> {
|
|
@@ -56,6 +59,9 @@ export class Chief extends ChiefBase {
|
|
|
56
59
|
const sampleData = await this.collectSampleData(endpoint);
|
|
57
60
|
const conversation = this.buildConversation(endpoint, opts?.style, sampleData);
|
|
58
61
|
|
|
62
|
+
const knowledge = this.knowledgeTracker?.renderEndpointKnowledge(endpoint);
|
|
63
|
+
if (knowledge) conversation.addUserText(knowledge);
|
|
64
|
+
|
|
59
65
|
if (opts?.specDefinition) {
|
|
60
66
|
conversation.addUserText(dedent`
|
|
61
67
|
<api_spec>
|
|
@@ -66,7 +66,7 @@ export function createCurlerTools(apiClient: ApiClient, requestState: RequestSto
|
|
|
66
66
|
queryParams: input.queryParams,
|
|
67
67
|
});
|
|
68
68
|
|
|
69
|
-
requestState.
|
|
69
|
+
requestState.addMadeRequest(result);
|
|
70
70
|
|
|
71
71
|
if (result.error) {
|
|
72
72
|
tag('error').log(`${input.method} ${input.path} > Network error: ${result.error}`);
|
|
@@ -2,6 +2,7 @@ import dedent from 'dedent';
|
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import type { AIProvider } from '../../../../src/ai/provider.ts';
|
|
4
4
|
import type { RequestStore } from '../../../../src/api/request-store.ts';
|
|
5
|
+
import type { KnowledgeTracker } from '../../../../src/knowledge-tracker.ts';
|
|
5
6
|
import type { Reporter } from '../../../../src/reporter.ts';
|
|
6
7
|
import { type Test, TestResult } from '../../../../src/test-plan.ts';
|
|
7
8
|
import { createDebug, tag } from '../../../../src/utils/logger.ts';
|
|
@@ -18,12 +19,14 @@ export class Curler {
|
|
|
18
19
|
private apiClient: ApiClient;
|
|
19
20
|
private requestState: RequestStore;
|
|
20
21
|
private reporter: Reporter;
|
|
22
|
+
private knowledgeTracker?: KnowledgeTracker;
|
|
21
23
|
|
|
22
|
-
constructor(provider: AIProvider, apiClient: ApiClient, requestState: RequestStore, reporter: Reporter) {
|
|
24
|
+
constructor(provider: AIProvider, apiClient: ApiClient, requestState: RequestStore, reporter: Reporter, knowledgeTracker?: KnowledgeTracker) {
|
|
23
25
|
this.provider = provider;
|
|
24
26
|
this.apiClient = apiClient;
|
|
25
27
|
this.requestState = requestState;
|
|
26
28
|
this.reporter = reporter;
|
|
29
|
+
this.knowledgeTracker = knowledgeTracker;
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
async test(test: Test, opts?: { specDefinition?: string; baseEndpoint?: string; searchSpec?: (query: string) => string }): Promise<{ success: boolean }> {
|
|
@@ -37,6 +40,9 @@ export class Curler {
|
|
|
37
40
|
const conversation = this.provider.startConversation(this.buildSystemPrompt(), 'curler', this.provider.getAgenticModel('curler'));
|
|
38
41
|
const tools = createCurlerTools(this.apiClient, this.requestState, test, opts?.searchSpec);
|
|
39
42
|
|
|
43
|
+
const knowledge = test.startUrl && this.knowledgeTracker?.renderEndpointKnowledge(test.startUrl);
|
|
44
|
+
if (knowledge) conversation.addUserText(knowledge);
|
|
45
|
+
|
|
40
46
|
const initialPrompt = this.buildTestPrompt(test, opts?.specDefinition, opts?.baseEndpoint);
|
|
41
47
|
conversation.addUserText(initialPrompt);
|
|
42
48
|
|
|
@@ -3,6 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
import { AIProvider } from '../../../src/ai/provider.ts';
|
|
4
4
|
import { RequestStore } from '../../../src/api/request-store.ts';
|
|
5
5
|
import { extractEndpointDefinition, loadSpec, searchEndpoints, validateSpecs } from '../../../src/api/spec-reader.ts';
|
|
6
|
+
import { KnowledgeTracker } from '../../../src/knowledge-tracker.ts';
|
|
6
7
|
import { Reporter } from '../../../src/reporter.ts';
|
|
7
8
|
import { Plan } from '../../../src/test-plan.ts';
|
|
8
9
|
import { setVerboseMode, tag } from '../../../src/utils/logger.ts';
|
|
@@ -20,6 +21,7 @@ export class ApiBot {
|
|
|
20
21
|
private apiClient!: ApiClient;
|
|
21
22
|
private requestState!: RequestStore;
|
|
22
23
|
private reporter!: Reporter;
|
|
24
|
+
private knowledgeTracker!: KnowledgeTracker;
|
|
23
25
|
private options: ApibotOptions;
|
|
24
26
|
private apiSpec: any;
|
|
25
27
|
|
|
@@ -33,7 +35,7 @@ export class ApiBot {
|
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
async start(): Promise<void> {
|
|
36
|
-
this.config = await this.configParser.loadConfig(
|
|
38
|
+
this.config = await this.configParser.loadConfig(this.options);
|
|
37
39
|
this.provider = new AIProvider(this.config.ai);
|
|
38
40
|
await this.provider.validateConnection();
|
|
39
41
|
|
|
@@ -47,6 +49,7 @@ export class ApiBot {
|
|
|
47
49
|
this.configParser.ensureDirectory(outputDir);
|
|
48
50
|
this.requestState = new RequestStore(outputDir);
|
|
49
51
|
this.reporter = new Reporter(this.config.reporter);
|
|
52
|
+
this.knowledgeTracker = new KnowledgeTracker({ knowledgeDir: this.configParser.getKnowledgeDir() });
|
|
50
53
|
|
|
51
54
|
validateSpecs(this.config.api.spec);
|
|
52
55
|
this.apiSpec = await loadSpec(this.config.api.spec!, outputDir);
|
|
@@ -84,21 +87,22 @@ export class ApiBot {
|
|
|
84
87
|
await this.apiClient?.teardown();
|
|
85
88
|
}
|
|
86
89
|
|
|
87
|
-
createAgent<T>(factory: (deps: { ai: AIProvider; config: ApibotConfig; apiClient: ApiClient; requestState: RequestStore }) => T): T {
|
|
90
|
+
createAgent<T>(factory: (deps: { ai: AIProvider; config: ApibotConfig; apiClient: ApiClient; requestState: RequestStore; knowledge: KnowledgeTracker }) => T): T {
|
|
88
91
|
return factory({
|
|
89
92
|
ai: this.provider,
|
|
90
93
|
config: this.config,
|
|
91
94
|
apiClient: this.apiClient,
|
|
92
95
|
requestState: this.requestState,
|
|
96
|
+
knowledge: this.knowledgeTracker,
|
|
93
97
|
});
|
|
94
98
|
}
|
|
95
99
|
|
|
96
100
|
agentChief(): Chief {
|
|
97
|
-
return (this.agents.chief ||= this.createAgent(({ ai, config, apiClient }) => new Chief(ai, config, apiClient)));
|
|
101
|
+
return (this.agents.chief ||= this.createAgent(({ ai, config, apiClient, knowledge }) => new Chief(ai, config, apiClient, knowledge)));
|
|
98
102
|
}
|
|
99
103
|
|
|
100
104
|
agentCurler(): Curler {
|
|
101
|
-
return (this.agents.curler ||= this.createAgent(({ ai, apiClient, requestState }) => new Curler(ai, apiClient, requestState, this.reporter)));
|
|
105
|
+
return (this.agents.curler ||= this.createAgent(({ ai, apiClient, requestState, knowledge }) => new Curler(ai, apiClient, requestState, this.reporter, knowledge)));
|
|
102
106
|
}
|
|
103
107
|
|
|
104
108
|
async plan(target: string, opts: { style?: string; fresh?: boolean } = {}): Promise<Plan> {
|
|
@@ -200,6 +204,8 @@ interface ApibotOptions {
|
|
|
200
204
|
config?: string;
|
|
201
205
|
path?: string;
|
|
202
206
|
endpoint?: string;
|
|
207
|
+
baseEndpoint?: string;
|
|
208
|
+
spec?: string;
|
|
203
209
|
}
|
|
204
210
|
|
|
205
211
|
export type { ApibotOptions };
|
|
@@ -2,6 +2,7 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { Command } from 'commander';
|
|
4
4
|
import { ConfigCommand } from '../../../src/commands/config-command.ts';
|
|
5
|
+
import { RecommendedModelsCommand } from '../../../src/commands/recommended-models-command.ts';
|
|
5
6
|
import { listSites } from '../../../src/global-config.ts';
|
|
6
7
|
import { setPreserveConsoleLogs } from '../../../src/utils/logger.ts';
|
|
7
8
|
import { getStyles } from './ai/chief/styles.ts';
|
|
@@ -13,11 +14,19 @@ function buildOptions(options: any): ApibotOptions {
|
|
|
13
14
|
verbose: options.verbose || options.debug,
|
|
14
15
|
config: options.config,
|
|
15
16
|
path: options.path,
|
|
17
|
+
baseEndpoint: options.endpoint,
|
|
18
|
+
spec: options.spec,
|
|
16
19
|
};
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
function addCommonOptions(cmd: Command): Command {
|
|
20
|
-
return cmd
|
|
23
|
+
return cmd
|
|
24
|
+
.option('-v, --verbose', 'Enable verbose logging')
|
|
25
|
+
.option('--debug', 'Enable debug logging')
|
|
26
|
+
.option('-c, --config <path>', 'Path to configuration file')
|
|
27
|
+
.option('-p, --path <path>', 'Working directory path')
|
|
28
|
+
.option('--endpoint <url>', 'Base API endpoint to test (env: EXPLORBOT_URL)')
|
|
29
|
+
.option('--spec <path>', 'OpenAPI spec file or URL (env: EXPLORBOT_API_SPEC)');
|
|
21
30
|
}
|
|
22
31
|
|
|
23
32
|
function selectTests(tests: any[], index?: string): any[] {
|
|
@@ -90,8 +99,10 @@ export function createApiCommands(name = 'api'): Command {
|
|
|
90
99
|
.action(async (endpoint, options) => {
|
|
91
100
|
const parser = ApibotConfigParser.getInstance();
|
|
92
101
|
const [site] = listSites();
|
|
102
|
+
const runOptions = buildOptions(options);
|
|
103
|
+
runOptions.endpoint = endpoint || site?.url;
|
|
93
104
|
try {
|
|
94
|
-
const config = await parser.loadConfig(
|
|
105
|
+
const config = await parser.loadConfig(runOptions);
|
|
95
106
|
console.log(ConfigCommand.render(config, { configPath: parser.getConfigPath(), root: parser.getProjectRoot(), json: options.json }));
|
|
96
107
|
} catch (error) {
|
|
97
108
|
console.error(error instanceof Error ? error.message : 'Unknown error');
|
|
@@ -99,6 +110,8 @@ export function createApiCommands(name = 'api'): Command {
|
|
|
99
110
|
}
|
|
100
111
|
});
|
|
101
112
|
|
|
113
|
+
RecommendedModelsCommand.register(cmd);
|
|
114
|
+
|
|
102
115
|
addCommonOptions(cmd.command('test <planfile> [index]').description('Execute tests from a plan file. Index: 1, 1-3, *')).action(async (planfile, index, options) => {
|
|
103
116
|
setPreserveConsoleLogs(true);
|
|
104
117
|
try {
|
|
@@ -45,7 +45,7 @@ export class ApibotConfigParser {
|
|
|
45
45
|
Object.assign(process.env, parseEnv(readFileSync(resolved, 'utf8')));
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
async loadConfig(options?: { config?: string; path?: string; endpoint?: string }): Promise<ApibotConfig> {
|
|
48
|
+
async loadConfig(options?: { config?: string; path?: string; endpoint?: string; baseEndpoint?: string; spec?: string }): Promise<ApibotConfig> {
|
|
49
49
|
if (this.config && !options?.config && !options?.path) return this.config;
|
|
50
50
|
|
|
51
51
|
const originalCwd = process.cwd();
|
|
@@ -55,6 +55,7 @@ export class ApibotConfigParser {
|
|
|
55
55
|
|
|
56
56
|
ApibotConfigParser.loadEnv(globalEnvPath());
|
|
57
57
|
ApibotConfigParser.loadEnv('.env');
|
|
58
|
+
this.applyRunOptions(options);
|
|
58
59
|
|
|
59
60
|
const resolvedPath = options?.config || this.findConfigFile();
|
|
60
61
|
if (!resolvedPath) {
|
|
@@ -82,6 +83,8 @@ export class ApibotConfigParser {
|
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
this.config = this.mergeWithDefaults(loadedConfig);
|
|
86
|
+
this.applyEnvSpec(this.config.api);
|
|
87
|
+
if (options?.baseEndpoint) this.config.api.baseEndpoint = options.baseEndpoint.replace(/\/$/, '');
|
|
85
88
|
await resolveConfigModels(this.config.ai);
|
|
86
89
|
this.configPath = resolvedPath;
|
|
87
90
|
this.site = null;
|
|
@@ -126,6 +129,11 @@ export class ApibotConfigParser {
|
|
|
126
129
|
|
|
127
130
|
const resolved = resolveSiteTarget(endpoint, this.site.url);
|
|
128
131
|
if (resolved.baseUrl !== this.site.url) return endpoint;
|
|
132
|
+
|
|
133
|
+
const basePath = new URL(this.getConfig().api.baseEndpoint).pathname.replace(/\/$/, '');
|
|
134
|
+
if (!basePath) return resolved.path;
|
|
135
|
+
if (resolved.path === basePath) return '/';
|
|
136
|
+
if (resolved.path.startsWith(`${basePath}/`)) return resolved.path.slice(basePath.length);
|
|
129
137
|
return resolved.path;
|
|
130
138
|
}
|
|
131
139
|
|
|
@@ -148,13 +156,27 @@ export class ApibotConfigParser {
|
|
|
148
156
|
}
|
|
149
157
|
}
|
|
150
158
|
|
|
159
|
+
private applyRunOptions(options?: { baseEndpoint?: string; spec?: string }): void {
|
|
160
|
+
if (options?.baseEndpoint) process.env.EXPLORBOT_URL = options.baseEndpoint;
|
|
161
|
+
if (options?.spec) process.env.EXPLORBOT_API_SPEC = options.spec;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private applyEnvSpec(api: ApiConfig): void {
|
|
165
|
+
if (!process.env.EXPLORBOT_API_SPEC) return;
|
|
166
|
+
api.spec = [process.env.EXPLORBOT_API_SPEC];
|
|
167
|
+
}
|
|
168
|
+
|
|
151
169
|
private enterGlobalMode(config: ApibotConfig, endpoint?: string): void {
|
|
152
170
|
const site = resolveSiteTarget(endpoint);
|
|
153
171
|
this.site = registerSite(site.baseUrl);
|
|
154
172
|
|
|
173
|
+
let baseEndpoint = site.baseUrl;
|
|
174
|
+
const envUrl = process.env.EXPLORBOT_URL;
|
|
175
|
+
if (envUrl && URL.parse(envUrl)?.origin === site.baseUrl) baseEndpoint = envUrl.replace(/\/$/, '');
|
|
176
|
+
|
|
155
177
|
config.dirs = { output: 'output', knowledge: 'knowledge' };
|
|
156
|
-
config.api = { ...config.api, baseEndpoint
|
|
157
|
-
|
|
178
|
+
config.api = { ...config.api, baseEndpoint };
|
|
179
|
+
materializeKnowledge(this.site.dir);
|
|
158
180
|
}
|
|
159
181
|
|
|
160
182
|
private async loadEnvConfig(): Promise<ApibotConfig> {
|
|
@@ -169,16 +191,14 @@ export class ApibotConfigParser {
|
|
|
169
191
|
|
|
170
192
|
const baseEndpoint = process.env.EXPLORBOT_URL;
|
|
171
193
|
if (!baseEndpoint) {
|
|
172
|
-
throw new Error('No API endpoint to test.
|
|
194
|
+
throw new Error('No API endpoint to test. Pass --endpoint or set EXPLORBOT_URL to the API base endpoint');
|
|
173
195
|
}
|
|
174
196
|
|
|
175
197
|
const outputRoot = resolveOutputRoot();
|
|
176
198
|
materializeKnowledge(outputRoot);
|
|
177
199
|
|
|
178
200
|
const api: ApiConfig = { baseEndpoint };
|
|
179
|
-
|
|
180
|
-
api.spec = [process.env.EXPLORBOT_API_SPEC];
|
|
181
|
-
}
|
|
201
|
+
this.applyEnvSpec(api);
|
|
182
202
|
|
|
183
203
|
let model: any;
|
|
184
204
|
if (provider && modelSpec) model = await createModel(provider, modelSpec);
|
|
@@ -246,7 +266,7 @@ export class ApibotConfigParser {
|
|
|
246
266
|
}
|
|
247
267
|
|
|
248
268
|
private mergeWithDefaults(config: Partial<ApibotConfig>): ApibotConfig {
|
|
249
|
-
return this.deepMerge({ dirs: { output: 'output' } }, config);
|
|
269
|
+
return this.deepMerge({ dirs: { output: 'output' }, api: {} }, config);
|
|
250
270
|
}
|
|
251
271
|
|
|
252
272
|
private deepMerge(target: any, source: any): any {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import {
|
|
2
|
+
import { knowledgeOption, wsOption } from '../../../src/commands/options/index.ts';
|
|
3
3
|
import { createDocsCommands } from '../src/cli.ts';
|
|
4
4
|
|
|
5
5
|
const program = createDocsCommands('doc-collector');
|
|
6
|
-
|
|
6
|
+
wsOption.register(program);
|
|
7
|
+
knowledgeOption.register(program);
|
|
7
8
|
program.parse();
|
|
@@ -4,7 +4,9 @@ import type { AIProvider } from '../../../../src/ai/provider.ts';
|
|
|
4
4
|
import type Explorer from '../../../../src/explorer.ts';
|
|
5
5
|
import type { StateManager } from '../../../../src/state-manager.ts';
|
|
6
6
|
import type { WebPageState } from '../../../../src/state-manager.ts';
|
|
7
|
+
import { parseAriaLocator } from '../../../../src/utils/aria.ts';
|
|
7
8
|
import { tag } from '../../../../src/utils/logger.ts';
|
|
9
|
+
import { parseResearchSections } from '../../../../src/ai/researcher/parser.ts';
|
|
8
10
|
import type { DocbotConfig } from '../config.ts';
|
|
9
11
|
import { type CaptureInteractionState, type DocStateTransition, collectDocInteractions } from './tools.ts';
|
|
10
12
|
|
|
@@ -182,6 +184,9 @@ class Documentarian {
|
|
|
182
184
|
Only list capabilities that are grounded in the provided page research.
|
|
183
185
|
Put actions into "can" only when there is direct evidence in the page context.
|
|
184
186
|
Put actions into "might" only when the UI strongly suggests a capability but proof is incomplete.
|
|
187
|
+
Going to another page is navigation, not a capability. "can" and "might" list only actions the user performs on this page; links and menus are documented in a separate navigation section, never here.
|
|
188
|
+
Raw interaction observations that change the URL are navigation evidence only. Never turn them into "can" or "might" actions.
|
|
189
|
+
Exclude capabilities supplied by unrelated embedded support, marketing, consent, or feedback widgets. Include an embedded interface only when it is part of the current page's documented purpose.
|
|
185
190
|
Describe each action from the end-user perspective.
|
|
186
191
|
Be explicit about scope:
|
|
187
192
|
- one item
|
|
@@ -189,7 +194,7 @@ class Documentarian {
|
|
|
189
194
|
- bulk operations
|
|
190
195
|
- all items
|
|
191
196
|
- page-level
|
|
192
|
-
Avoid implementation details, selectors, and QA wording.
|
|
197
|
+
Avoid implementation details, selectors, and QA wording — except the machine-consumed element field, which is a locator, not prose.
|
|
193
198
|
Avoid duplicate actions with different phrasing.
|
|
194
199
|
</rules>
|
|
195
200
|
|
|
@@ -241,6 +246,8 @@ class Documentarian {
|
|
|
241
246
|
- action: concise user-facing capability phrased as "user can ..."
|
|
242
247
|
- scope: one of one item, list of items, bulk operations, all items, page-level
|
|
243
248
|
- evidence: short reason based on visible UI or research
|
|
249
|
+
- element: required for every "can" and "might" action. Return the proving or suggesting control's locator copied verbatim from the page research table — the value from the ARIA column, or from the CSS column when the ARIA column is empty. Return null only when no single element supports the action. It is machine metadata used for validation and never shown as prose.
|
|
250
|
+
Never copy the human-readable Element description from interaction_observations into element. It is not a locator.
|
|
244
251
|
</output_requirements>
|
|
245
252
|
`;
|
|
246
253
|
}
|
|
@@ -274,8 +281,30 @@ class Documentarian {
|
|
|
274
281
|
return message.includes('Failed to generate JSON') || message.includes('Failed to validate JSON') || message.includes('failed_generation') || message.includes('No object generated') || message.includes('response did not match schema');
|
|
275
282
|
}
|
|
276
283
|
|
|
277
|
-
private normalizeDocumentation(documentation: GeneratedPageDocumentation & Partial<Pick<PageDocumentation, 'interactions'>>, _state: WebPageState,
|
|
284
|
+
private normalizeDocumentation(documentation: GeneratedPageDocumentation & Partial<Pick<PageDocumentation, 'interactions'>>, _state: WebPageState, research: string): PageDocumentation {
|
|
278
285
|
const normalized = { ...documentation };
|
|
286
|
+
const navigationLocators = new Set<string>();
|
|
287
|
+
const researchLocators = new Set<string>();
|
|
288
|
+
for (const section of parseResearchSections(research)) {
|
|
289
|
+
for (const element of section.elements) {
|
|
290
|
+
const aria = element.aria ? `{ role: '${element.aria.role}', text: '${element.aria.text}' }` : null;
|
|
291
|
+
if (aria) researchLocators.add(this.locatorKey(aria));
|
|
292
|
+
if (element.css) researchLocators.add(this.locatorKey(element.css));
|
|
293
|
+
if (element.aria?.role !== 'link') continue;
|
|
294
|
+
if (aria) navigationLocators.add(this.locatorKey(aria));
|
|
295
|
+
if (element.css) navigationLocators.add(this.locatorKey(element.css));
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
normalized.can = normalized.can.filter((capability) => {
|
|
299
|
+
if (!capability.element) return true;
|
|
300
|
+
const key = this.locatorKey(capability.element);
|
|
301
|
+
return researchLocators.has(key) && !navigationLocators.has(key);
|
|
302
|
+
});
|
|
303
|
+
normalized.might = normalized.might.filter((capability) => {
|
|
304
|
+
if (!capability.element) return false;
|
|
305
|
+
const key = this.locatorKey(capability.element);
|
|
306
|
+
return researchLocators.has(key) && !navigationLocators.has(key);
|
|
307
|
+
});
|
|
279
308
|
if (!normalized.interactions) {
|
|
280
309
|
normalized.interactions = undefined;
|
|
281
310
|
}
|
|
@@ -288,6 +317,18 @@ class Documentarian {
|
|
|
288
317
|
});
|
|
289
318
|
}
|
|
290
319
|
|
|
320
|
+
private locatorKey(locator: string): string {
|
|
321
|
+
const aria = parseAriaLocator(locator);
|
|
322
|
+
if (aria) return `aria:${aria.role}:${aria.text}`;
|
|
323
|
+
const trimmed = locator.trim();
|
|
324
|
+
const first = trimmed.at(0);
|
|
325
|
+
const last = trimmed.at(-1);
|
|
326
|
+
if (first === last && (first === '`' || first === '"' || first === "'")) {
|
|
327
|
+
return `css:${trimmed.slice(1, -1).trim()}`;
|
|
328
|
+
}
|
|
329
|
+
return `css:${trimmed}`;
|
|
330
|
+
}
|
|
331
|
+
|
|
291
332
|
private evaluateDocumentationQuality(documentation: PageDocumentation): string[] {
|
|
292
333
|
const notes: string[] = [];
|
|
293
334
|
|
|
@@ -355,6 +396,14 @@ const capabilitySchema = z.object({
|
|
|
355
396
|
action: z.string(),
|
|
356
397
|
scope: z.enum(['one item', 'list of items', 'bulk operations', 'all items', 'page-level']),
|
|
357
398
|
evidence: z.string(),
|
|
399
|
+
element: z.string().nullable().optional(),
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
const generatedCapabilitySchema = z.object({
|
|
403
|
+
action: z.string(),
|
|
404
|
+
scope: z.enum(['one item', 'list of items', 'bulk operations', 'all items', 'page-level']),
|
|
405
|
+
evidence: z.string(),
|
|
406
|
+
element: z.string().nullable(),
|
|
358
407
|
});
|
|
359
408
|
|
|
360
409
|
const stateTransitionSchema = z.object({
|
|
@@ -397,11 +446,14 @@ const stateTransitionSchema = z.object({
|
|
|
397
446
|
|
|
398
447
|
const generatedPageDocumentationSchema = z.object({
|
|
399
448
|
summary: z.string(),
|
|
400
|
-
can: z.array(
|
|
401
|
-
might: z.array(
|
|
449
|
+
can: z.array(generatedCapabilitySchema),
|
|
450
|
+
might: z.array(generatedCapabilitySchema),
|
|
402
451
|
});
|
|
403
452
|
|
|
404
|
-
const pageDocumentationSchema =
|
|
453
|
+
const pageDocumentationSchema = z.object({
|
|
454
|
+
summary: z.string(),
|
|
455
|
+
can: z.array(capabilitySchema),
|
|
456
|
+
might: z.array(capabilitySchema),
|
|
405
457
|
interactions: z.array(stateTransitionSchema).optional(),
|
|
406
458
|
qualityNotes: z.array(z.string()).optional(),
|
|
407
459
|
});
|
|
@@ -225,7 +225,7 @@ function buildTransition(candidate: InteractionCandidate, beforeState: WebPageSt
|
|
|
225
225
|
function describeTargetState(beforeState: WebPageState, afterState: WebPageState, candidate: InteractionCandidate): InteractionState {
|
|
226
226
|
const beforeFocus = detectFocusArea(beforeState.ariaSnapshot || null);
|
|
227
227
|
const afterFocus = detectFocusArea(afterState.ariaSnapshot || null);
|
|
228
|
-
if (afterFocus.
|
|
228
|
+
if (afterFocus.type && (!beforeFocus.type || beforeFocus.name !== afterFocus.name)) {
|
|
229
229
|
return {
|
|
230
230
|
kind: afterFocus.type || 'dialog',
|
|
231
231
|
label: afterFocus.name || candidate.element.name.trim(),
|
|
@@ -2,6 +2,7 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { Command } from 'commander';
|
|
4
4
|
import { ConfigCommand } from '../../../src/commands/config-command.ts';
|
|
5
|
+
import { RecommendedModelsCommand } from '../../../src/commands/recommended-models-command.ts';
|
|
5
6
|
import { remote } from '../../../src/remote.ts';
|
|
6
7
|
import { isVerboseMode, setPreserveConsoleLogs, setQuietMode } from '../../../src/utils/logger.ts';
|
|
7
8
|
import { DocBot, type DocbotOptions } from './docbot.ts';
|
|
@@ -16,6 +17,7 @@ function buildOptions(options: any): DocbotOptions {
|
|
|
16
17
|
incognito: options.incognito,
|
|
17
18
|
session: options.session,
|
|
18
19
|
docsConfig: options.docsConfig,
|
|
20
|
+
baseUrl: options.url,
|
|
19
21
|
};
|
|
20
22
|
}
|
|
21
23
|
|
|
@@ -26,6 +28,7 @@ function addCommonOptions(cmd: Command): Command {
|
|
|
26
28
|
.option('-c, --config <path>', 'Path to explorbot configuration file')
|
|
27
29
|
.option('--docs-config <path>', 'Path to doc collector configuration file')
|
|
28
30
|
.option('-p, --path <path>', 'Working directory path')
|
|
31
|
+
.option('--url <url>', 'Base URL of the site, when the path argument is relative (env: EXPLORBOT_URL)')
|
|
29
32
|
.option('-s, --show', 'Show browser window')
|
|
30
33
|
.option('--headless', 'Run browser in headless mode')
|
|
31
34
|
.option('--incognito', 'Run without recording experiences')
|
|
@@ -36,7 +39,14 @@ export function createDocsCommands(name = 'docs'): Command {
|
|
|
36
39
|
const cmd = new Command(name);
|
|
37
40
|
cmd.description('AI-powered website documentation collector');
|
|
38
41
|
|
|
39
|
-
addCommonOptions(
|
|
42
|
+
addCommonOptions(
|
|
43
|
+
cmd
|
|
44
|
+
.command('collect <path>')
|
|
45
|
+
.description('Crawl pages and generate documentation spec')
|
|
46
|
+
.option('--max-pages <count>', 'Maximum number of pages to document')
|
|
47
|
+
.option('--no-collapse-template-pages', 'Visit every page even when its layout matches a documented page')
|
|
48
|
+
.option('--template-similarity <percent>', 'Structural similarity percent that counts pages as the same layout (default 90)')
|
|
49
|
+
).action(async (startPath, options) => {
|
|
40
50
|
setPreserveConsoleLogs(true);
|
|
41
51
|
|
|
42
52
|
try {
|
|
@@ -50,8 +60,12 @@ export function createDocsCommands(name = 'docs'): Command {
|
|
|
50
60
|
if (options.maxPages) {
|
|
51
61
|
maxPages = Number.parseInt(options.maxPages, 10);
|
|
52
62
|
}
|
|
63
|
+
let templateSimilarity: number | undefined;
|
|
64
|
+
if (options.templateSimilarity) {
|
|
65
|
+
templateSimilarity = Number.parseInt(options.templateSimilarity, 10);
|
|
66
|
+
}
|
|
53
67
|
|
|
54
|
-
const result = await bot.collect(startPath, { maxPages });
|
|
68
|
+
const result = await bot.collect(startPath, { maxPages, collapseTemplatePages: options.collapseTemplatePages, templateSimilarity });
|
|
55
69
|
|
|
56
70
|
console.log(`\nDocumented ${result.pages.length} page(s)`);
|
|
57
71
|
console.log(`Skipped ${result.skipped.length} page(s)`);
|
|
@@ -74,13 +88,15 @@ export function createDocsCommands(name = 'docs'): Command {
|
|
|
74
88
|
.action(async (url, options) => {
|
|
75
89
|
setQuietMode(!isVerboseMode());
|
|
76
90
|
try {
|
|
77
|
-
console.log(await ConfigCommand.summary({ config: options.config, path: options.path, url, json: options.json }));
|
|
91
|
+
console.log(await ConfigCommand.summary({ config: options.config, path: options.path, url: url || options.url, json: options.json }));
|
|
78
92
|
} catch (error) {
|
|
79
93
|
console.error(error instanceof Error ? error.message : 'Unknown error');
|
|
80
94
|
process.exit(1);
|
|
81
95
|
}
|
|
82
96
|
});
|
|
83
97
|
|
|
98
|
+
RecommendedModelsCommand.register(cmd);
|
|
99
|
+
|
|
84
100
|
cmd
|
|
85
101
|
.command('init')
|
|
86
102
|
.description('Initialize doc collector configuration')
|
|
@@ -110,6 +126,8 @@ export function createDocsCommands(name = 'docs'): Command {
|
|
|
110
126
|
interactive: false,
|
|
111
127
|
ignoreErrors: true,
|
|
112
128
|
collapseDynamicPages: true,
|
|
129
|
+
collapseTemplatePages: true,
|
|
130
|
+
templateSimilarity: 90,
|
|
113
131
|
scope: 'site',
|
|
114
132
|
includePaths: [],
|
|
115
133
|
excludePaths: [],
|
|
@@ -119,6 +119,7 @@ class DocbotConfigParser {
|
|
|
119
119
|
interactive: false,
|
|
120
120
|
ignoreErrors: true,
|
|
121
121
|
collapseDynamicPages: true,
|
|
122
|
+
collapseTemplatePages: true,
|
|
122
123
|
scope: 'site',
|
|
123
124
|
includePaths: [],
|
|
124
125
|
excludePaths: [],
|
|
@@ -157,6 +158,8 @@ interface DocbotConfig {
|
|
|
157
158
|
ignoreErrors?: boolean | string[];
|
|
158
159
|
prompt?: string;
|
|
159
160
|
collapseDynamicPages?: boolean;
|
|
161
|
+
collapseTemplatePages?: boolean;
|
|
162
|
+
templateSimilarity?: number;
|
|
160
163
|
scope?: 'site' | 'section' | 'subtree';
|
|
161
164
|
includePaths?: string[];
|
|
162
165
|
excludePaths?: string[];
|