explorbot 0.4.1 → 0.4.3

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.
Files changed (141) hide show
  1. package/bin/explorbot-cli.ts +14 -4
  2. package/boat/api-tester/src/ai/curler-tools.ts +1 -1
  3. package/boat/api-tester/src/cli.ts +3 -0
  4. package/boat/doc-collector/src/ai/documentarian.ts +57 -5
  5. package/boat/doc-collector/src/ai/tools.ts +1 -1
  6. package/boat/doc-collector/src/cli.ts +18 -2
  7. package/boat/doc-collector/src/config.ts +3 -0
  8. package/boat/doc-collector/src/docbot.ts +56 -11
  9. package/boat/doc-collector/src/docs-renderer.ts +79 -55
  10. package/boat/doc-collector/src/interaction-screenshots.ts +19 -10
  11. package/boat/doc-collector/src/screenshots.ts +106 -3
  12. package/boat/doc-collector/src/template-dedup.ts +31 -0
  13. package/boat/prima/src/cli.ts +3 -0
  14. package/boat/prima/src/prima.ts +3 -4
  15. package/dist/bin/explorbot-cli.js +14 -4
  16. package/dist/boat/api-tester/src/ai/curler-tools.js +1 -1
  17. package/dist/boat/api-tester/src/cli.js +2 -0
  18. package/dist/boat/doc-collector/src/ai/documentarian.js +62 -4
  19. package/dist/boat/doc-collector/src/ai/tools.js +1 -1
  20. package/dist/boat/doc-collector/src/cli.js +15 -2
  21. package/dist/boat/doc-collector/src/config.js +1 -0
  22. package/dist/boat/doc-collector/src/docbot.js +52 -10
  23. package/dist/boat/doc-collector/src/docs-renderer.js +69 -46
  24. package/dist/boat/doc-collector/src/interaction-screenshots.js +18 -11
  25. package/dist/boat/doc-collector/src/screenshots.js +104 -1
  26. package/dist/boat/doc-collector/src/template-dedup.js +27 -0
  27. package/dist/boat/prima/src/cli.js +2 -0
  28. package/dist/boat/prima/src/prima.js +3 -4
  29. package/dist/package.json +1 -1
  30. package/dist/src/action-result.d.ts +2 -2
  31. package/dist/src/action-result.js +15 -17
  32. package/dist/src/action.d.ts +1 -1
  33. package/dist/src/action.js +15 -15
  34. package/dist/src/ai/fisherman/request-haul.d.ts +12 -0
  35. package/dist/src/ai/fisherman/request-haul.js +31 -0
  36. package/dist/src/ai/fisherman/tools.d.ts +50 -0
  37. package/dist/src/ai/{fisherman-tools.js → fisherman/tools.js} +109 -40
  38. package/dist/src/ai/fisherman.d.ts +14 -4
  39. package/dist/src/ai/fisherman.js +96 -18
  40. package/dist/src/ai/pilot.d.ts +13 -1
  41. package/dist/src/ai/pilot.js +40 -22
  42. package/dist/src/ai/provider.js +8 -14
  43. package/dist/src/ai/researcher/coordinates.d.ts +2 -0
  44. package/dist/src/ai/researcher/coordinates.js +13 -1
  45. package/dist/src/ai/researcher/deep-analysis.js +9 -9
  46. package/dist/src/ai/researcher/fingerprint-worker.js +1 -11
  47. package/dist/src/ai/researcher.js +1 -1
  48. package/dist/src/ai/rules.js +10 -0
  49. package/dist/src/ai/tester.js +20 -20
  50. package/dist/src/ai/tools.d.ts +1 -1
  51. package/dist/src/ai/tools.js +18 -74
  52. package/dist/src/api/request-result.js +3 -1
  53. package/dist/src/api/request-store.d.ts +7 -6
  54. package/dist/src/api/request-store.js +65 -37
  55. package/dist/src/api/xhr-capture.d.ts +2 -0
  56. package/dist/src/api/xhr-capture.js +35 -10
  57. package/dist/src/commands/config-command.js +2 -9
  58. package/dist/src/commands/help-json-command.d.ts +31 -0
  59. package/dist/src/commands/help-json-command.js +58 -0
  60. package/dist/src/commands/index.js +2 -0
  61. package/dist/src/commands/init-command.js +3 -7
  62. package/dist/src/commands/plans-command.d.ts +4 -1
  63. package/dist/src/commands/plans-command.js +4 -4
  64. package/dist/src/commands/recommended-models-command.d.ts +9 -0
  65. package/dist/src/commands/recommended-models-command.js +85 -0
  66. package/dist/src/config.d.ts +2 -0
  67. package/dist/src/config.js +7 -2
  68. package/dist/src/experience-tracker.js +1 -1
  69. package/dist/src/explorbot.js +3 -3
  70. package/dist/src/global-config.d.ts +2 -0
  71. package/dist/src/global-config.js +6 -0
  72. package/dist/src/state-manager.d.ts +3 -3
  73. package/dist/src/state-manager.js +8 -8
  74. package/dist/src/test-plan.d.ts +1 -0
  75. package/dist/src/test-plan.js +13 -9
  76. package/dist/src/utils/aria.d.ts +1 -1
  77. package/dist/src/utils/aria.js +23 -2
  78. package/dist/src/utils/cli-section.d.ts +1 -0
  79. package/dist/src/utils/cli-section.js +12 -0
  80. package/dist/src/utils/html-diff.d.ts +1 -0
  81. package/dist/src/utils/html-diff.js +5 -5
  82. package/dist/src/utils/html.d.ts +2 -0
  83. package/dist/src/utils/html.js +5 -4
  84. package/dist/src/utils/overlay.d.ts +19 -55
  85. package/dist/src/utils/overlay.js +94 -148
  86. package/dist/src/utils/region.d.ts +41 -0
  87. package/dist/src/utils/region.js +99 -0
  88. package/dist/src/utils/similarity.d.ts +1 -0
  89. package/dist/src/utils/similarity.js +11 -0
  90. package/dist/src/utils/url-matcher.d.ts +1 -1
  91. package/dist/src/utils/url-matcher.js +4 -4
  92. package/docs/doc-collection/basics.md +11 -5
  93. package/docs/doc-collection/crawling.md +21 -1
  94. package/docs/reference/commands.md +4 -0
  95. package/docs/reference/configuration.md +4 -0
  96. package/docs/superpowers/plans/2026-09-03-fisherman-query-api.md +1361 -0
  97. package/docs/superpowers/plans/2026-09-03-model-resolves-click-ambiguity.md +509 -0
  98. package/docs/workflow/agentic-usage.md +12 -0
  99. package/docs/workflow/application-spec.md +4 -0
  100. package/package.json +1 -1
  101. package/src/action-result.ts +15 -16
  102. package/src/action.ts +16 -15
  103. package/src/ai/fisherman/request-haul.ts +36 -0
  104. package/src/ai/{fisherman-tools.ts → fisherman/tools.ts} +125 -46
  105. package/src/ai/fisherman.ts +110 -20
  106. package/src/ai/pilot.ts +38 -20
  107. package/src/ai/provider.ts +8 -12
  108. package/src/ai/researcher/coordinates.ts +13 -1
  109. package/src/ai/researcher/deep-analysis.ts +9 -9
  110. package/src/ai/researcher/fingerprint-worker.ts +1 -10
  111. package/src/ai/researcher.ts +1 -1
  112. package/src/ai/rules.ts +10 -0
  113. package/src/ai/tester.ts +18 -18
  114. package/src/ai/tools.ts +18 -86
  115. package/src/api/request-result.ts +2 -1
  116. package/src/api/request-store.ts +68 -42
  117. package/src/api/xhr-capture.ts +39 -11
  118. package/src/commands/config-command.ts +2 -7
  119. package/src/commands/help-json-command.ts +74 -0
  120. package/src/commands/index.ts +2 -0
  121. package/src/commands/init-command.ts +4 -11
  122. package/src/commands/plans-command.ts +5 -5
  123. package/src/commands/recommended-models-command.ts +92 -0
  124. package/src/config.ts +9 -2
  125. package/src/experience-tracker.ts +1 -1
  126. package/src/explorbot.ts +3 -3
  127. package/src/global-config.ts +8 -0
  128. package/src/state-manager.ts +10 -9
  129. package/src/test-plan.ts +12 -10
  130. package/src/utils/aria.ts +26 -3
  131. package/src/utils/cli-section.ts +12 -0
  132. package/src/utils/html-diff.ts +6 -6
  133. package/src/utils/html.ts +6 -4
  134. package/src/utils/overlay.ts +105 -192
  135. package/src/utils/region.ts +132 -0
  136. package/src/utils/similarity.ts +9 -0
  137. package/src/utils/url-matcher.ts +3 -3
  138. package/dist/src/ai/fisherman-tools.d.ts +0 -138
  139. package/dist/src/utils/request-map.d.ts +0 -7
  140. package/dist/src/utils/request-map.js +0 -16
  141. package/src/utils/request-map.ts +0 -19
@@ -0,0 +1,132 @@
1
+ import type { HtmlDiffPart } from './html-diff.js';
2
+ import { pathToXPath } from './html-diff.js';
3
+ import { extractHeadings } from './html.js';
4
+ import { createDebug } from './logger.js';
5
+
6
+ const debugLog = createDebug('explorbot:region');
7
+
8
+ export class Region {
9
+ readonly type: RegionType | null;
10
+ readonly name: string | null;
11
+ readonly root: string | null;
12
+ readonly html: string | null;
13
+ readonly xpath: string | null;
14
+ readonly parent: RegionData | null;
15
+
16
+ constructor(data: RegionData = {}) {
17
+ this.type = data.type ?? null;
18
+ this.name = data.name ?? null;
19
+ this.root = data.root ?? null;
20
+ this.html = data.html ?? null;
21
+ this.xpath = data.xpath ?? null;
22
+ this.parent = data.parent ?? null;
23
+ }
24
+
25
+ get isOpen(): boolean {
26
+ return this.type !== null;
27
+ }
28
+
29
+ get isModal(): boolean {
30
+ return false;
31
+ }
32
+
33
+ describe(): string {
34
+ if (!this.isOpen) return '';
35
+ let text = `${this.type} "${this.name || 'unnamed'}" opened`;
36
+ if (this.root) text += `, scope: ${this.root}`;
37
+ return text;
38
+ }
39
+
40
+ withParent(parent: Region): Region {
41
+ const Ctor = this.constructor as new (data: RegionData) => Region;
42
+ return new Ctor({
43
+ type: this.type,
44
+ name: this.name,
45
+ root: this.root,
46
+ html: this.html,
47
+ xpath: this.xpath,
48
+ parent: { type: parent.type, name: parent.name, root: parent.root, xpath: parent.xpath },
49
+ });
50
+ }
51
+ }
52
+
53
+ export function addedSubtrees(diff: RegionDiff): AddedSubtree[] {
54
+ const candidates: AddedSubtree[] = [];
55
+ for (const part of diff.parts) {
56
+ const appeared = part.added.find((line) => line.startsWith('ELEMENT:'));
57
+ if (!appeared) continue;
58
+ if (diff.pageSize > 0 && part.rawSize > REGION_MAX_RATIO * diff.pageSize) {
59
+ debugLog(`Added subtree spans ${Math.round((part.rawSize / diff.pageSize) * 100)}% of the page — a new state, not a region`);
60
+ continue;
61
+ }
62
+ const headings = headingsOf(part.subtree);
63
+ const newHeading = headings.find((heading) => !diff.previousHtml.includes(heading)) ?? null;
64
+ let name = newHeading;
65
+ if (!name && headings.length > 0) name = headings.join(' ');
66
+ const namedContainer = part.container !== 'body' && !part.container.startsWith('//');
67
+ let root: string | null = null;
68
+ if (namedContainer && part.containerWasEmpty) root = part.container;
69
+ if (!root) root = part.appearedSelector ?? null;
70
+ if (!root && namedContainer) root = part.container;
71
+ candidates.push({
72
+ xpath: pathToXPath(appeared.slice('ELEMENT:'.length)),
73
+ html: part.subtree,
74
+ size: part.subtree.length,
75
+ rawSize: part.rawSize,
76
+ sizable: part.subtree.length >= REGION_MIN_HTML,
77
+ name,
78
+ root,
79
+ hasNewHeading: !!newHeading,
80
+ });
81
+ }
82
+ const sizable = candidates.filter((candidate) => candidate.sizable);
83
+ const totalRawSize = sizable.reduce((sum, candidate) => sum + candidate.rawSize, 0);
84
+ const largest = Math.max(0, ...sizable.map((candidate) => candidate.rawSize));
85
+ if (largest < REGION_DOMINANCE * totalRawSize) {
86
+ debugLog('Changes are scattered across the page, no dominant region');
87
+ return [];
88
+ }
89
+ const identified = candidates.filter((candidate) => candidate.name || candidate.root);
90
+ if (identified.length < candidates.length) debugLog('Dropped added content with no name and no scoping root');
91
+ identified.sort((a, b) => Number(b.hasNewHeading) - Number(a.hasNewHeading) || b.size - a.size);
92
+ return identified;
93
+ }
94
+
95
+ const headingsOf = (html: string): string[] => {
96
+ const headings = extractHeadings(html);
97
+ return [headings.h1, headings.h2, headings.h3, headings.h4].filter(Boolean) as string[];
98
+ };
99
+
100
+ const REGION_MIN_HTML = 5_000;
101
+ const REGION_MAX_RATIO = 0.6;
102
+ const REGION_DOMINANCE = 0.7;
103
+
104
+ export type RegionType = 'overlay' | 'region';
105
+
106
+ export type RegionData = {
107
+ type?: RegionType | null;
108
+ name?: string | null;
109
+ root?: string | null;
110
+ html?: string | null;
111
+ xpath?: string | null;
112
+ parent?: RegionData | null;
113
+ };
114
+
115
+ export interface RegionDiff {
116
+ parts: HtmlDiffPart[];
117
+ pageSize: number;
118
+ similarity: number;
119
+ sameUrl: boolean;
120
+ previousHtml: string;
121
+ }
122
+
123
+ export interface AddedSubtree {
124
+ xpath: string;
125
+ html: string;
126
+ size: number;
127
+ rawSize: number;
128
+ sizable: boolean;
129
+ name: string | null;
130
+ root: string | null;
131
+ hasNewHeading: boolean;
132
+ }
@@ -0,0 +1,9 @@
1
+ export function diceSimilarity(a: Set<string>, b: Set<string>): number {
2
+ let intersection = 0;
3
+ for (const item of a) {
4
+ if (b.has(item)) intersection++;
5
+ }
6
+ const total = a.size + b.size;
7
+ if (total === 0) return 100;
8
+ return Math.round(((2 * intersection) / total) * 100);
9
+ }
@@ -4,7 +4,7 @@ import { ConfigParser } from '../config.js';
4
4
  export function isDynamicSegment(segment: string): boolean {
5
5
  try {
6
6
  const configRegex = ConfigParser.getInstance().getConfig().dynamicPageRegex;
7
- if (configRegex) return new RegExp(configRegex, 'i').test(segment);
7
+ if (configRegex && new RegExp(configRegex, 'i').test(segment)) return true;
8
8
  } catch {
9
9
  /* config not loaded yet */
10
10
  }
@@ -52,10 +52,10 @@ export function generalizeSegment(segment: string): string {
52
52
  return '[^/]+';
53
53
  }
54
54
 
55
- export function generalizeUrl(url: string): string {
55
+ export function generalizeUrl(url: string, replaceSegment: (segment: string) => string = generalizeSegment): string {
56
56
  return url
57
57
  .split('/')
58
- .map((seg) => (seg.length > 0 && isDynamicSegment(seg) ? generalizeSegment(seg) : seg))
58
+ .map((seg) => (seg.length > 0 && isDynamicSegment(seg) ? replaceSegment(seg) : seg))
59
59
  .join('/');
60
60
  }
61
61
 
@@ -1,138 +0,0 @@
1
- import type { ApiClient } from '../api/api-client.js';
2
- import type { RequestStore } from '../api/request-store.js';
3
- export declare function createFishermanTools(apiClient: ApiClient, requestStore: RequestStore, opts: {
4
- spec?: any;
5
- baseEndpoint?: string;
6
- }): {
7
- tools: {
8
- getEndpointSpec: import("@ai-sdk/provider-utils").ExecutableTool<import("ai").Tool<{
9
- method: any;
10
- path: any;
11
- }, {
12
- source: string;
13
- method: any;
14
- path: any;
15
- definition: string;
16
- rejectedCapture: {
17
- status: number;
18
- requestBody: any;
19
- };
20
- usable?: undefined;
21
- rejectedRequestBody?: undefined;
22
- status?: undefined;
23
- requestBody?: undefined;
24
- error?: undefined;
25
- } | {
26
- source: string;
27
- method: any;
28
- path: any;
29
- usable: boolean;
30
- rejectedRequestBody: any;
31
- status: number;
32
- definition?: undefined;
33
- rejectedCapture?: undefined;
34
- requestBody?: undefined;
35
- error?: undefined;
36
- } | {
37
- source: string;
38
- method: string;
39
- path: string;
40
- status: number;
41
- requestBody: any;
42
- definition?: undefined;
43
- rejectedCapture?: undefined;
44
- usable?: undefined;
45
- rejectedRequestBody?: undefined;
46
- error?: undefined;
47
- } | {
48
- source: string;
49
- definition: string;
50
- method?: undefined;
51
- path?: undefined;
52
- rejectedCapture?: undefined;
53
- usable?: undefined;
54
- rejectedRequestBody?: undefined;
55
- status?: undefined;
56
- requestBody?: undefined;
57
- error?: undefined;
58
- } | {
59
- source: string;
60
- error: any;
61
- method?: undefined;
62
- path?: undefined;
63
- definition?: undefined;
64
- rejectedCapture?: undefined;
65
- usable?: undefined;
66
- rejectedRequestBody?: undefined;
67
- status?: undefined;
68
- requestBody?: undefined;
69
- }, import("@ai-sdk/provider-utils").Context>>;
70
- request: import("@ai-sdk/provider-utils").ExecutableTool<import("ai").Tool<any, {
71
- success: boolean;
72
- error: string;
73
- status?: undefined;
74
- statusText?: undefined;
75
- category?: undefined;
76
- errorPreview?: undefined;
77
- extracted?: undefined;
78
- } | {
79
- success: boolean;
80
- status: number;
81
- statusText: string;
82
- category: ResponseCategory;
83
- errorPreview: string;
84
- error?: undefined;
85
- extracted?: undefined;
86
- } | {
87
- success: boolean;
88
- status: number;
89
- extracted: Record<string, any>;
90
- error?: undefined;
91
- statusText?: undefined;
92
- category?: undefined;
93
- errorPreview?: undefined;
94
- }, import("@ai-sdk/provider-utils").Context>>;
95
- finish: import("@ai-sdk/provider-utils").ExecutableTool<import("ai").Tool<{
96
- summary: string;
97
- created: {
98
- type: string;
99
- id?: string | number;
100
- title?: string;
101
- }[];
102
- failed?: {
103
- type: string;
104
- reason: string;
105
- }[];
106
- }, {
107
- finished: boolean;
108
- error: string;
109
- } | {
110
- finished: boolean;
111
- error?: undefined;
112
- }, import("@ai-sdk/provider-utils").Context>>;
113
- stop: import("@ai-sdk/provider-utils").ExecutableTool<import("ai").Tool<{
114
- reason: any;
115
- }, {
116
- stopped: boolean;
117
- }, import("@ai-sdk/provider-utils").Context>>;
118
- };
119
- getResult: () => FishermanResult;
120
- isFinished: () => boolean;
121
- finishFromText: (text?: string) => void;
122
- };
123
- export interface FishermanResult {
124
- success: boolean;
125
- summary: string;
126
- created: Array<{
127
- type: string;
128
- id?: string | number;
129
- title?: string;
130
- request?: string;
131
- }>;
132
- failed: Array<{
133
- type: string;
134
- reason: string;
135
- }>;
136
- }
137
- type ResponseCategory = 'validation' | 'authorization' | 'not_found' | 'conflict' | 'temporary' | 'server' | 'client';
138
- export {};
@@ -1,7 +0,0 @@
1
- import type { RequestResult } from '../api/request-result.js';
2
- export declare class RequestMap {
3
- requests: Map<string, RequestResult>;
4
- constructor(requests?: RequestResult[]);
5
- add(request: RequestResult): void;
6
- get(id: string | number): RequestResult | undefined;
7
- }
@@ -1,16 +0,0 @@
1
- export class RequestMap {
2
- requests = new Map();
3
- constructor(requests = []) {
4
- for (const request of requests)
5
- this.add(request);
6
- }
7
- add(request) {
8
- const { id } = request.extractIdAndTitle();
9
- if (id === undefined)
10
- return;
11
- this.requests.set(String(id), request);
12
- }
13
- get(id) {
14
- return this.requests.get(String(id));
15
- }
16
- }
@@ -1,19 +0,0 @@
1
- import type { RequestResult } from '../api/request-result.ts';
2
-
3
- export class RequestMap {
4
- private requests = new Map<string, RequestResult>();
5
-
6
- constructor(requests: RequestResult[] = []) {
7
- for (const request of requests) this.add(request);
8
- }
9
-
10
- add(request: RequestResult): void {
11
- const { id } = request.extractIdAndTitle();
12
- if (id === undefined) return;
13
- this.requests.set(String(id), request);
14
- }
15
-
16
- get(id: string | number): RequestResult | undefined {
17
- return this.requests.get(String(id));
18
- }
19
- }