explorbot 0.3.5 → 0.4.1
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 +23 -16
- package/boat/api-tester/src/ai/chief.ts +7 -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 +12 -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/cli.ts +6 -1
- package/boat/doc-collector/src/docbot.ts +4 -2
- package/boat/prima/bin/prima-cli.ts +2 -0
- package/boat/prima/src/cli.ts +23 -8
- package/boat/prima/src/envelope.ts +35 -9
- package/boat/prima/src/prima.ts +23 -10
- package/dist/bin/explorbot-cli.js +24 -16
- 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.js +6 -1
- package/dist/boat/api-tester/src/apibot.js +7 -3
- package/dist/boat/api-tester/src/cli.js +12 -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/cli.js +6 -1
- package/dist/boat/doc-collector/src/docbot.js +4 -2
- package/dist/boat/prima/bin/prima-cli.js +2 -0
- package/dist/boat/prima/src/cli.js +22 -8
- package/dist/boat/prima/src/envelope.js +24 -6
- package/dist/boat/prima/src/prima.js +23 -11
- package/dist/package.json +2 -2
- package/dist/rules/planner/styles/normal.md +1 -1
- package/dist/src/action-result.d.ts +9 -1
- package/dist/src/action-result.js +57 -18
- package/dist/src/action.d.ts +1 -1
- package/dist/src/action.js +87 -12
- package/dist/src/ai/captain.js +1 -1
- package/dist/src/ai/driller.d.ts +0 -1
- package/dist/src/ai/driller.js +8 -20
- package/dist/src/ai/fisherman-tools.d.ts +9 -0
- package/dist/src/ai/fisherman-tools.js +52 -6
- package/dist/src/ai/fisherman.d.ts +4 -2
- package/dist/src/ai/fisherman.js +48 -27
- package/dist/src/ai/historian/codeceptjs.js +1 -1
- package/dist/src/ai/historian/playwright.js +1 -1
- package/dist/src/ai/navigator.js +1 -1
- package/dist/src/ai/pilot.d.ts +1 -0
- package/dist/src/ai/pilot.js +13 -1
- package/dist/src/ai/planner.js +9 -7
- package/dist/src/ai/provider.js +20 -3
- package/dist/src/ai/researcher/deep-analysis.js +1 -3
- package/dist/src/ai/researcher.js +5 -3
- package/dist/src/ai/rules.js +3 -3
- package/dist/src/ai/tester.d.ts +3 -0
- package/dist/src/ai/tester.js +40 -3
- package/dist/src/ai/tools.d.ts +1 -0
- package/dist/src/ai/tools.js +13 -6
- package/dist/src/api/request-result.d.ts +2 -0
- package/dist/src/api/request-result.js +8 -2
- package/dist/src/api/request-store.d.ts +3 -2
- package/dist/src/api/request-store.js +66 -14
- package/dist/src/api/spec-reader.js +1 -1
- package/dist/src/commands/config-command.js +1 -1
- package/dist/src/commands/drill-command.js +1 -1
- package/dist/src/commands/explore-command.d.ts +6 -0
- package/dist/src/commands/explore-command.js +39 -3
- package/dist/src/commands/freesail-command.js +10 -1
- 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.js +6 -6
- package/dist/src/config.d.ts +1 -0
- package/dist/src/config.js +12 -0
- package/dist/src/experience-tracker.js +5 -0
- package/dist/src/explorbot.d.ts +0 -1
- package/dist/src/explorbot.js +24 -37
- 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 +5 -1
- package/dist/src/state-manager.js +10 -7
- package/dist/src/test-plan.d.ts +3 -0
- package/dist/src/test-plan.js +27 -0
- package/dist/src/utils/aria.d.ts +1 -1
- package/dist/src/utils/aria.js +6 -42
- package/dist/src/utils/html-diff.d.ts +4 -0
- package/dist/src/utils/html-diff.js +62 -7
- package/dist/src/utils/html.d.ts +5 -15
- package/dist/src/utils/html.js +14 -85
- package/dist/src/utils/overlay.d.ts +56 -11
- package/dist/src/utils/overlay.js +191 -21
- package/dist/src/utils/request-map.d.ts +7 -0
- package/dist/src/utils/request-map.js +16 -0
- package/dist/src/utils/url-matcher.js +4 -2
- package/docs/api-testing/basics.md +15 -0
- package/docs/api-testing/planning.md +10 -1
- package/docs/reference/commands.md +32 -5
- package/docs/reference/websocket.md +1 -0
- package/docs/superpowers/plans/2026-08-29-fisherman-reliability.md +953 -0
- package/docs/superpowers/plans/2026-08-29-region-states.md +1292 -0
- package/docs/superpowers/plans/2026-08-30-fisherman-live-session-auth.md +457 -0
- package/docs/superpowers/specs/2026-08-29-fisherman-reliability-design.md +45 -0
- package/docs/superpowers/specs/2026-08-29-region-states-design.md +262 -0
- package/docs/superpowers/specs/2026-08-29-region-states-fixes-design.md +269 -0
- package/docs/superpowers/specs/2026-08-30-fisherman-live-session-auth-design.md +37 -0
- package/docs/workflow/agentic-usage.md +12 -2
- package/docs/workflow/ci.md +1 -0
- package/docs/workflow/knowledge.md +46 -2
- package/package.json +2 -2
- package/rules/planner/styles/normal.md +1 -1
- package/src/action-result.ts +61 -22
- package/src/action.ts +87 -14
- package/src/ai/captain.ts +1 -1
- package/src/ai/driller.ts +7 -39
- package/src/ai/fisherman-tools.ts +56 -7
- package/src/ai/fisherman.ts +48 -28
- package/src/ai/historian/codeceptjs.ts +1 -1
- package/src/ai/historian/playwright.ts +1 -1
- package/src/ai/navigator.ts +1 -1
- package/src/ai/pilot.ts +9 -1
- package/src/ai/planner.ts +9 -8
- package/src/ai/provider.ts +21 -3
- package/src/ai/researcher/deep-analysis.ts +1 -2
- package/src/ai/researcher.ts +4 -3
- package/src/ai/rules.ts +3 -3
- package/src/ai/tester.ts +40 -3
- package/src/ai/tools.ts +17 -9
- package/src/api/request-result.ts +10 -2
- package/src/api/request-store.ts +60 -13
- package/src/api/spec-reader.ts +1 -1
- package/src/commands/config-command.ts +1 -1
- package/src/commands/drill-command.ts +1 -1
- package/src/commands/explore-command.ts +37 -3
- package/src/commands/freesail-command.ts +7 -1
- 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 +6 -6
- package/src/config.ts +12 -0
- package/src/experience-tracker.ts +5 -1
- package/src/explorbot.ts +21 -37
- package/src/knowledge-tracker.ts +94 -36
- package/src/remote.ts +0 -20
- package/src/state-manager.ts +13 -7
- package/src/test-plan.ts +29 -0
- package/src/utils/aria.ts +7 -44
- package/src/utils/html-diff.ts +62 -7
- package/src/utils/html.ts +14 -91
- package/src/utils/overlay.ts +226 -23
- package/src/utils/request-map.ts +19 -0
- package/src/utils/url-matcher.ts +3 -2
|
@@ -1,43 +1,213 @@
|
|
|
1
1
|
import { detectFocusArea } from './aria.js';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
overlaySemanticSelector: '[role="dialog"], [role="alertdialog"], [aria-modal="true"], [role="listbox"], [role="menu"], [role="tooltip"]',
|
|
7
|
-
};
|
|
2
|
+
import { pathToXPath } from './html-diff.js';
|
|
3
|
+
import { extractHeadings } from './html.js';
|
|
4
|
+
import { createDebug } from './logger.js';
|
|
5
|
+
const debugLog = createDebug('explorbot:overlay');
|
|
8
6
|
export class Overlay {
|
|
9
7
|
type;
|
|
10
8
|
name;
|
|
9
|
+
root;
|
|
10
|
+
html;
|
|
11
|
+
xpath;
|
|
12
|
+
parent;
|
|
11
13
|
constructor(data = {}) {
|
|
12
14
|
this.type = data.type ?? null;
|
|
13
15
|
this.name = data.name ?? null;
|
|
16
|
+
this.root = data.root ?? null;
|
|
17
|
+
this.html = data.html ?? null;
|
|
18
|
+
this.xpath = data.xpath ?? null;
|
|
19
|
+
this.parent = data.parent ?? null;
|
|
14
20
|
}
|
|
15
21
|
get detected() {
|
|
22
|
+
return this.type !== null && this.type !== 'region';
|
|
23
|
+
}
|
|
24
|
+
get present() {
|
|
16
25
|
return this.type !== null;
|
|
17
26
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
27
|
+
describe() {
|
|
28
|
+
if (!this.present)
|
|
29
|
+
return '';
|
|
30
|
+
let text = `${this.type} "${this.name || 'unnamed'}" opened`;
|
|
31
|
+
if (this.root)
|
|
32
|
+
text += `, scope: ${this.root}`;
|
|
33
|
+
return text;
|
|
34
|
+
}
|
|
35
|
+
withGeometry(geometry) {
|
|
36
|
+
return new Overlay({
|
|
37
|
+
type: this.type,
|
|
38
|
+
name: this.name || geometry.name,
|
|
39
|
+
root: geometry.root,
|
|
40
|
+
html: geometry.html,
|
|
41
|
+
xpath: geometry.xpath,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
withParent(parent) {
|
|
45
|
+
return new Overlay({
|
|
46
|
+
type: this.type,
|
|
47
|
+
name: this.name,
|
|
48
|
+
root: this.root,
|
|
49
|
+
html: this.html,
|
|
50
|
+
xpath: this.xpath,
|
|
51
|
+
parent: { type: parent.type, name: parent.name, root: parent.root, xpath: parent.xpath },
|
|
52
|
+
});
|
|
22
53
|
}
|
|
23
54
|
static fromAria(snapshot) {
|
|
24
55
|
return new Overlay(detectFocusArea(snapshot));
|
|
25
56
|
}
|
|
26
57
|
static resolve(data) {
|
|
27
|
-
if (data.overlayHtml)
|
|
28
|
-
return Overlay.fromHtml(data.overlayHtml);
|
|
29
58
|
if (data.overlay)
|
|
30
59
|
return new Overlay(data.overlay);
|
|
31
60
|
return Overlay.fromAria(data.ariaSnapshot ?? null);
|
|
32
61
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
62
|
+
}
|
|
63
|
+
export class OverlayPage {
|
|
64
|
+
page;
|
|
65
|
+
constructor(page) {
|
|
66
|
+
this.page = page;
|
|
67
|
+
}
|
|
68
|
+
async detectRegion(diff) {
|
|
69
|
+
if (!diff.sameUrl && diff.similarity < SOFT_NAVIGATION_SIMILARITY)
|
|
70
|
+
return null;
|
|
71
|
+
for (const subRoot of this.appearedSubRoots(diff)) {
|
|
72
|
+
const probe = await this.probe(subRoot.elementXPath);
|
|
73
|
+
if (probe?.found && probe.onScreen && !probe.centerBelongs) {
|
|
74
|
+
debugLog('Appeared region is hidden or covered, trying the next candidate');
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const overlay = this.toOverlay(subRoot, probe, diff.previousHtml);
|
|
78
|
+
if (!overlay.name && !overlay.root) {
|
|
79
|
+
debugLog('Appeared region has no name and no semantic root, ignoring it');
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
debugLog(`Region detected: ${overlay.describe()}`);
|
|
83
|
+
return overlay;
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
async isStillOpen(overlay) {
|
|
88
|
+
if (!overlay.xpath)
|
|
89
|
+
return true;
|
|
90
|
+
const probe = await this.probe(overlay.xpath);
|
|
91
|
+
if (!probe)
|
|
92
|
+
return true;
|
|
93
|
+
if (!probe.found)
|
|
94
|
+
return false;
|
|
95
|
+
if (probe.onScreen && !probe.centerBelongs && !probe.coveredByFloating)
|
|
96
|
+
return false;
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
appearedSubRoots(diff) {
|
|
100
|
+
const candidates = [];
|
|
101
|
+
for (const part of diff.parts) {
|
|
102
|
+
const appeared = part.added.find((line) => line.startsWith('ELEMENT:'));
|
|
103
|
+
if (!appeared)
|
|
104
|
+
continue;
|
|
105
|
+
if (part.subtree.length < SUBROOT_MIN_HTML)
|
|
106
|
+
continue;
|
|
107
|
+
if (diff.pageSize > 0 && part.rawSize > REGION_MAX_RATIO * diff.pageSize) {
|
|
108
|
+
debugLog(`Appeared subtree spans ${Math.round((part.rawSize / diff.pageSize) * 100)}% of the page — a new state, not a region`);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
candidates.push({
|
|
112
|
+
container: part.container,
|
|
113
|
+
elementXPath: pathToXPath(appeared.slice('ELEMENT:'.length)),
|
|
114
|
+
subtree: part.subtree,
|
|
115
|
+
size: part.subtree.length,
|
|
116
|
+
rawSize: part.rawSize,
|
|
117
|
+
appearedSelector: part.appearedSelector,
|
|
118
|
+
fresh: !!this.freshHeading(part.subtree, diff.previousHtml),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
if (candidates.length === 0)
|
|
122
|
+
return [];
|
|
123
|
+
const totalRawSize = candidates.reduce((sum, c) => sum + c.rawSize, 0);
|
|
124
|
+
const largest = Math.max(...candidates.map((c) => c.rawSize));
|
|
125
|
+
if (largest < REGION_DOMINANCE * totalRawSize) {
|
|
126
|
+
debugLog('Changes are scattered across the page, no dominant region');
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
129
|
+
candidates.sort((a, b) => Number(b.fresh) - Number(a.fresh) || b.size - a.size);
|
|
130
|
+
return candidates;
|
|
42
131
|
}
|
|
132
|
+
async probe(xpath) {
|
|
133
|
+
if (!this.page)
|
|
134
|
+
return null;
|
|
135
|
+
return this.page
|
|
136
|
+
.evaluate(({ probeSource, config }) => {
|
|
137
|
+
const probe = new Function(`return ${probeSource}`)();
|
|
138
|
+
return probe(config);
|
|
139
|
+
}, { probeSource: inspectRegion.toString(), config: { xpath } })
|
|
140
|
+
.catch((err) => {
|
|
141
|
+
debugLog('Region probe failed:', err.message);
|
|
142
|
+
return null;
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
toOverlay(subRoot, probe, previousHtml) {
|
|
146
|
+
let type = 'region';
|
|
147
|
+
if (probe?.centerBelongs && probe.floating)
|
|
148
|
+
type = 'modal';
|
|
149
|
+
let root = null;
|
|
150
|
+
if (subRoot.container !== 'body' && !subRoot.container.startsWith('//'))
|
|
151
|
+
root = subRoot.container;
|
|
152
|
+
if (!root && subRoot.appearedSelector)
|
|
153
|
+
root = subRoot.appearedSelector;
|
|
154
|
+
return new Overlay({ type, name: this.nameFrom(subRoot.subtree, previousHtml), root, html: subRoot.subtree, xpath: subRoot.elementXPath });
|
|
155
|
+
}
|
|
156
|
+
nameFrom(html, previousHtml) {
|
|
157
|
+
const fresh = this.freshHeading(html, previousHtml);
|
|
158
|
+
if (fresh)
|
|
159
|
+
return fresh;
|
|
160
|
+
const candidates = this.headingsOf(html);
|
|
161
|
+
if (candidates.length === 0)
|
|
162
|
+
return null;
|
|
163
|
+
return candidates.join(' ');
|
|
164
|
+
}
|
|
165
|
+
freshHeading(html, previousHtml) {
|
|
166
|
+
const fresh = this.headingsOf(html).filter((heading) => !previousHtml.includes(heading));
|
|
167
|
+
return fresh[0] ?? null;
|
|
168
|
+
}
|
|
169
|
+
headingsOf(html) {
|
|
170
|
+
const headings = extractHeadings(html);
|
|
171
|
+
return [headings.h1, headings.h2, headings.h3, headings.h4].filter(Boolean);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const SUBROOT_MIN_HTML = 5_000;
|
|
175
|
+
const SOFT_NAVIGATION_SIMILARITY = 50;
|
|
176
|
+
const REGION_MAX_RATIO = 0.6;
|
|
177
|
+
const REGION_DOMINANCE = 0.7;
|
|
178
|
+
// Serialized via toString() into page.evaluate — must stay a plain function with no outer-scope references.
|
|
179
|
+
function inspectRegion(config) {
|
|
180
|
+
const probe = { found: false, onScreen: false, floating: false, centerBelongs: false, coveredByFloating: false };
|
|
181
|
+
const isFloating = (start) => {
|
|
182
|
+
let node = start;
|
|
183
|
+
while (node && node !== document.body) {
|
|
184
|
+
const style = window.getComputedStyle(node);
|
|
185
|
+
if (style.position === 'fixed' || style.position === 'absolute' || (Number.parseInt(style.zIndex || '0', 10) || 0) > 0)
|
|
186
|
+
return true;
|
|
187
|
+
node = node.parentElement;
|
|
188
|
+
}
|
|
189
|
+
return false;
|
|
190
|
+
};
|
|
191
|
+
const result = document.evaluate(config.xpath, document, null, 9, null);
|
|
192
|
+
const node = result.singleNodeValue;
|
|
193
|
+
if (!node || node.nodeType !== 1)
|
|
194
|
+
return probe;
|
|
195
|
+
const element = node;
|
|
196
|
+
const rect = element.getBoundingClientRect();
|
|
197
|
+
if (rect.width === 0 && rect.height === 0)
|
|
198
|
+
return probe;
|
|
199
|
+
probe.found = true;
|
|
200
|
+
probe.floating = isFloating(element);
|
|
201
|
+
const left = Math.max(rect.left, 0);
|
|
202
|
+
const top = Math.max(rect.top, 0);
|
|
203
|
+
const right = Math.min(rect.right, window.innerWidth);
|
|
204
|
+
const bottom = Math.min(rect.bottom, window.innerHeight);
|
|
205
|
+
if (right <= left || bottom <= top)
|
|
206
|
+
return probe;
|
|
207
|
+
probe.onScreen = true;
|
|
208
|
+
const hit = document.elementFromPoint((left + right) / 2, (top + bottom) / 2);
|
|
209
|
+
probe.centerBelongs = !!hit && (hit === element || element.contains(hit));
|
|
210
|
+
if (hit && !probe.centerBelongs)
|
|
211
|
+
probe.coveredByFloating = isFloating(hit);
|
|
212
|
+
return probe;
|
|
43
213
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
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
|
+
}
|
|
@@ -9,6 +9,8 @@ export function isDynamicSegment(segment) {
|
|
|
9
9
|
catch {
|
|
10
10
|
/* config not loaded yet */
|
|
11
11
|
}
|
|
12
|
+
if (/^v\d+$/i.test(segment))
|
|
13
|
+
return false;
|
|
12
14
|
// numeric: /users/123
|
|
13
15
|
if (/^\d+$/.test(segment))
|
|
14
16
|
return true;
|
|
@@ -18,8 +20,8 @@ export function isDynamicSegment(segment) {
|
|
|
18
20
|
// ULID: /items/01ARZ3NDEKTSV4RRFFQ69G5FAV
|
|
19
21
|
if (/^[0-9A-HJKMNP-TV-Z]{26}$/.test(segment))
|
|
20
22
|
return true;
|
|
21
|
-
// hex ID (4+ chars): /suite/70dae98a
|
|
22
|
-
if (/^[a-f0-9]{4,}$/i.test(segment))
|
|
23
|
+
// hex ID (4+ chars): /suite/70dae98a — short letters-only hex reads as a word
|
|
24
|
+
if (/^[a-f0-9]{4,}$/i.test(segment) && (segment.length >= 8 || /\d/.test(segment)))
|
|
23
25
|
return true;
|
|
24
26
|
// hex-prefixed slug (8+ hex before dash): /suite/95ef0c94-mobile
|
|
25
27
|
if (/^[a-f0-9]{8,}-/i.test(segment))
|
|
@@ -56,6 +56,21 @@ api: {
|
|
|
56
56
|
|
|
57
57
|
A matching `teardown` hook runs after all tests finish — use it to clean up data.
|
|
58
58
|
|
|
59
|
+
### Without a config file
|
|
60
|
+
|
|
61
|
+
Chief and Curler need three things: where the API is, what its spec says, and how to authenticate. Pass all three on the command line and no config file is needed:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npx explorbot api plan /users \
|
|
65
|
+
--endpoint https://api.example.com/v1 \
|
|
66
|
+
--spec ./openapi.yaml \
|
|
67
|
+
--knowledge 'Send X-Api-Key: ${env.API_KEY} on every request'
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`--endpoint` and `--spec` each have an environment twin — `EXPLORBOT_URL` and `EXPLORBOT_API_SPEC` — and the flag wins when both are set. `--knowledge` adds to the facts `EXPLORBOT_KNOWLEDGE` and `EXPLORBOT_KNOWLEDGE_FILE` bring in rather than replacing them. Configure your models once with `npx explorbot init --global` and every run stores its plans and requests per host under `~/.explorbot/sites/<host>/`, so a later `api test` against the same API picks up where the last one left off. Knowledge given on the command line lasts for the run; `api know` is what writes it down.
|
|
71
|
+
|
|
72
|
+
`--endpoint` keeps its path prefix: given `https://api.example.com/v1`, steps stay relative (`/users`) and Curler sends them to `https://api.example.com/v1/users`. `api test`, which takes a plan file rather than an endpoint, reads it from the flag or the variable.
|
|
73
|
+
|
|
59
74
|
### A dedicated API project
|
|
60
75
|
|
|
61
76
|
If you don't have a web `explorbot.config.js`, run `npx explorbot api init`. It asks for your base endpoint, spec, and a one-line description of the API, then writes a standalone `apibot.config.ts` (with an `ai` and `api` section) plus `output/` and `knowledge/` directories. When both files exist, `apibot.config.*` takes precedence over `explorbot.config.*`.
|
|
@@ -21,7 +21,16 @@ endpoint: "/users"
|
|
|
21
21
|
CRUD for users. Admin role required for writes. IDs are UUIDs.
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
Chief loads knowledge matching the endpoint it's planning. Running `know` again on the same endpoint appends to the file. See [knowledge](../workflow/knowledge.md) for how matching and files work.
|
|
24
|
+
Chief loads knowledge matching the endpoint it's planning, and Curler loads it again for the endpoint it's testing, so auth headers and payload rules reach the requests themselves. Running `know` again on the same endpoint appends to the file. See [knowledge](../workflow/knowledge.md) for how matching and files work.
|
|
25
|
+
|
|
26
|
+
For a fact that should not be stored — a token, a one-off fixture — pass `--knowledge` instead. It applies to the run only:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx explorbot api explore /users --knowledge '---
|
|
30
|
+
endpoint: /users/*
|
|
31
|
+
---
|
|
32
|
+
Send X-Api-Key: ${env.API_KEY} on every request'
|
|
33
|
+
```
|
|
25
34
|
|
|
26
35
|
## Choose a planning style
|
|
27
36
|
|
|
@@ -33,7 +33,7 @@ Inside the TUI, use the matching slash command: `/explore`, `/research`, `/plan`
|
|
|
33
33
|
| Generate test plan | `npx explorbot plan <path>` | `/plan [--focus <feature>]` | Writes plan markdown |
|
|
34
34
|
| List saved plans | `npx explorbot plans [plan]` | `/plans [plan]` | Show plans and their tests |
|
|
35
35
|
| Navigate to a URL | `npx explorbot navigate <url>` | `/navigate <target>` | Reachability probe + session capture |
|
|
36
|
-
| Drill page components | `npx explorbot drill <url>` | `/drill [--knowledge <path>] [--max-components <n>]` | Learn interactions |
|
|
36
|
+
| Drill page components | `npx explorbot drill <url>` | `/drill [--save-knowledge <path>] [--max-components <n>]` | Learn interactions |
|
|
37
37
|
| Execute plan tests | `npx explorbot test <planfile> [index]` | `/test [scenario\|number\|*]` | Run scenarios |
|
|
38
38
|
| Re-run generated tests | `npx explorbot rerun <file> [index]` | `/rerun <file> [index]` | With AI auto-healing |
|
|
39
39
|
| List generated tests | `npx explorbot runs [file]` | `/runs [file]` | Index + dry-run |
|
|
@@ -69,6 +69,16 @@ Every CLI command that drives a browser accepts these options (`start`, `explore
|
|
|
69
69
|
| `--incognito` | Run without recording experiences |
|
|
70
70
|
| `--session [file]` | Save/restore browser session (cookies, localStorage) from file |
|
|
71
71
|
|
|
72
|
+
### `--knowledge`
|
|
73
|
+
|
|
74
|
+
Passes facts to the run without creating a file in `knowledge/`. Plain text applies everywhere; add frontmatter to scope it to a page or an API endpoint. Repeat the flag for several facts. See [Knowledge](../workflow/knowledge.md#per-session-knowledge).
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npx explorbot explore /pay --knowledge 'Test card 4111 1111 1111 1111, any future expiry'
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Like `--ws`, it is a program-level option rather than a per-command one: it works on every command — including `api`, `docs` and `prima` — and can go anywhere on the line. It is listed under `npx explorbot --help` rather than in each command's own help.
|
|
81
|
+
|
|
72
82
|
### `--session`
|
|
73
83
|
|
|
74
84
|
Saves browser state (cookies, localStorage, sessionStorage) to a JSON file. The next run restores the session, so you skip login and setup steps.
|
|
@@ -104,8 +114,10 @@ EXPLORBOT_AI_PROVIDER=openrouter \
|
|
|
104
114
|
| `EXPLORBOT_EPHEMERAL` | Keep no state between runs — output goes to a fresh temp directory instead of the site dir |
|
|
105
115
|
| `EXPLORBOT_KNOWLEDGE` | Inline knowledge text, applied to every page |
|
|
106
116
|
| `EXPLORBOT_KNOWLEDGE_FILE` | Path to a knowledge markdown file |
|
|
117
|
+
| `EXPLORBOT_SPEC` | Docbot application spec directory or index.md, used as page knowledge |
|
|
107
118
|
| `EXPLORBOT_API_SPEC` | OpenAPI spec path for the API boat |
|
|
108
119
|
| `EXPLORBOT_NO_BANNER` | Suppress the startup banner, for machine-readable output |
|
|
120
|
+
| `EXPLORBOT_MAX_DURATION` | Wall-clock budget in minutes for an explore run; same as --max-duration |
|
|
109
121
|
<!-- END env -->
|
|
110
122
|
|
|
111
123
|
`npx explorbot config` prints the values a run actually uses — models per role, the config file behind them, the output, knowledge and experience directories, and every `EXPLORBOT_*` variable currently set. The boats answer for their own configuration the same way: `npx explorbot api config`, `npx explorbot docs config`, `npx explorbot prima config`. Add `--json` on any of them to get the same values as an object a script can read.
|
|
@@ -234,6 +246,7 @@ The CLI form navigates to `<path>` first. The TUI form always runs on the curren
|
|
|
234
246
|
| Option | Description |
|
|
235
247
|
|---|---|
|
|
236
248
|
| `--max-tests <n>` | Hard cap on tests executed in this run. Sub-page expansion stops once the cap is hit. |
|
|
249
|
+
| `--max-duration <minutes>` | Wall-clock budget for the whole run. New tests, planning, and sub-page expansion stop before the limit; an in-flight test is cut at the hard cutoff so the report, Testomatio run, and teardown always complete. Set it a few minutes below your CI job timeout. |
|
|
237
250
|
| `--focus <feature>` | Narrow planning to a single feature area (e.g. `--focus checkout`). The focus also becomes part of the saved plan filename. |
|
|
238
251
|
| `--configure <spec>` | Reuse a saved plan, mix old + new tests, filter by style/priority, control sub-page behavior. See below. |
|
|
239
252
|
| `--dry-run` | Mark every picked test as `skipped` instead of executing. New-test planning still runs (so you can preview what would be picked) but no AI tester actions and no plan-file writes. |
|
|
@@ -351,6 +364,7 @@ npx explorbot freesail /dashboard --scope /app --max-tests 20
|
|
|
351
364
|
| `--shallow` | Breadth-first: pick the globally least-visited page |
|
|
352
365
|
| `--scope <prefix>` | Restrict navigation to URLs starting with this prefix |
|
|
353
366
|
| `--max-tests <n>` | Maximum number of tests to run |
|
|
367
|
+
| `--max-duration <minutes>` | Wall-clock budget in minutes for the whole run; freesail stops starting new pages before the limit |
|
|
354
368
|
|
|
355
369
|
### research
|
|
356
370
|
|
|
@@ -431,6 +445,8 @@ npx explorbot test 3 --from-plan output/plans/login.md # index first, plan via
|
|
|
431
445
|
| `--grep <pattern>` | Run only tests whose scenario matches the pattern |
|
|
432
446
|
| `--from-plan <file>` | Load this plan file when the first argument is a test index |
|
|
433
447
|
|
|
448
|
+
The plan names the site it runs against: the URL of its `### Prerequisite` section, or the `## Requirements` URL of its first test. With a [global installation](configuration.md#running-from-anywhere-the-global-installation) that is enough to run a plan from any directory without a project config — `npx explorbot test ~/plans/checkout.md` registers the site and stores its output under `~/.explorbot/sites/<host>/`. Naming a saved plan is enough too: `npx explorbot test checkout` looks for `checkout.md` in the current directory, then in the plans directory of every registered site.
|
|
449
|
+
|
|
434
450
|
### drill
|
|
435
451
|
|
|
436
452
|
Drill all components on a page to learn interactions.
|
|
@@ -439,18 +455,18 @@ Drill all components on a page to learn interactions.
|
|
|
439
455
|
# CLI
|
|
440
456
|
npx explorbot drill /components
|
|
441
457
|
npx explorbot drill /components --max-components 10
|
|
442
|
-
npx explorbot drill /login --knowledge /login
|
|
458
|
+
npx explorbot drill /login --save-knowledge /login
|
|
443
459
|
```
|
|
444
460
|
|
|
445
461
|
```
|
|
446
462
|
# TUI
|
|
447
463
|
/drill
|
|
448
|
-
/drill --knowledge /login --max-components 10
|
|
464
|
+
/drill --save-knowledge /login --max-components 10
|
|
449
465
|
```
|
|
450
466
|
|
|
451
467
|
| Option | Description |
|
|
452
468
|
|---|---|
|
|
453
|
-
| `--knowledge <path>` | Save learned interactions to a knowledge file at this URL path |
|
|
469
|
+
| `--save-knowledge <path>` | Save learned interactions to a knowledge file at this URL path |
|
|
454
470
|
| `--max-components <count>` | Maximum number of components to drill |
|
|
455
471
|
|
|
456
472
|
## Test Rerun
|
|
@@ -588,9 +604,15 @@ Crawl pages and generate a documentation spec with `Purpose`, `User Can`, and `U
|
|
|
588
604
|
```bash
|
|
589
605
|
npx explorbot docs collect /users/sign_in
|
|
590
606
|
npx explorbot docs collect /docs/openapi#tag/project-analytics-tags --max-pages 20
|
|
607
|
+
npx explorbot docs collect /dashboard --url https://app.example.com
|
|
591
608
|
npx explorbot docs collect https://teleportal.ua/ua/serials/stb/kod --path explorbot-testing --show --session --max-pages 20
|
|
592
609
|
```
|
|
593
610
|
|
|
611
|
+
| Option | Description |
|
|
612
|
+
|---|---|
|
|
613
|
+
| `--url <url>` | Base URL of the site, for a relative path argument. Same as `EXPLORBOT_URL`; an absolute path argument carries its own |
|
|
614
|
+
| `--max-pages <count>` | Stop after documenting this many pages |
|
|
615
|
+
|
|
594
616
|
Output is written to:
|
|
595
617
|
|
|
596
618
|
- `output/docs/spec.md`
|
|
@@ -685,7 +707,9 @@ html: /home/you/.explorbot/sites/app.example.com/output/prima/2026-08-04T10-04-2
|
|
|
685
707
|
|
|
686
708
|
`used:` is code that already executed: for `go` the CodeceptJS step it ran, for `pw` the Playwright expression you passed, which a CodeceptJS test needs wrapped in `I.usePlaywrightTo(...)`. Log lines can precede the envelope, so start parsing at the first `###` line.
|
|
687
709
|
|
|
688
|
-
`### Changes` renders on every action envelope, saying `no change` when the tree is identical — so a successful command proves what it did instead of leaving you to check. `check` and `do` report per step rather than in aggregate: `### Steps` names each step with the code it ran and what proved it, and `page after each step:`
|
|
710
|
+
`### Changes` renders on every action envelope, saying `no change` when the tree is identical — so a successful command proves what it did instead of leaving you to check. `check` and `do` report per step rather than in aggregate: `### Steps` names each step with the code it ran and what proved it, and `page after each step:` names the per-step captures. `check` adds `### Expected outcomes`; `ask`, `research`, and `verify` add `### Answer`, `### Research`, or `### Assertions`; `pw` adds `### Value` when its expression returns one.
|
|
711
|
+
|
|
712
|
+
`### Artifacts` names the files every command leaves on disk: the aria tree and the html always, `screenshot:` and `network:` when a screenshot was taken or requests were captured. The aria tree carries values and checked states, so one `grep` over it settles a question the envelope did not answer, without asking the page again. `prima status <hash>` reprints those paths for an earlier command and lists whatever else was kept under the hash; it only reads recorded files, so it needs no browser and outlives the session.
|
|
689
713
|
|
|
690
714
|
**A failed action is a failure.** Nothing is retried along a different route and no other element is substituted, so `ok: true` means the action you asked for is the one that landed. A failure adds `### Failure` with the error and the compact ARIA of the page, so you can retarget from the envelope itself instead of opening the artifact files.
|
|
691
715
|
|
|
@@ -711,6 +735,7 @@ Every command takes these:
|
|
|
711
735
|
| `-i, --instance <name>` | Which prima-owned browser to talk to; parallel work needs one each |
|
|
712
736
|
| `--session [file]` | Cookies and storage persisted across processes; ignored while attached, since the attached session keeps its own |
|
|
713
737
|
| `--url <url>` | Page to open when the session has no page yet |
|
|
738
|
+
| `--spec <path>` | A Docbot application spec directory or its `index.md`, read as page knowledge. Same as `EXPLORBOT_SPEC` / `PRIMA_CLI_SPEC` |
|
|
714
739
|
| `--ephemeral` | Keep no state between runs. Applies to config-free runs only — with a config file the output directory comes from the config |
|
|
715
740
|
| `--framework <name>` | Parsed but not active yet; reported code is CodeceptJS whatever you pass |
|
|
716
741
|
| `-c, --config <path>`, `-p, --path <path>` | As on every other Explorbot command |
|
|
@@ -760,6 +785,8 @@ Prima follows the same [configuration ladder](#environment-variables) as every o
|
|
|
760
785
|
EXPLORBOT_AI_PROVIDER=groq npx explorbot prima go https://app.example.com
|
|
761
786
|
```
|
|
762
787
|
|
|
788
|
+
The three inputs a run needs beyond the model come from flags or the environment, so no file has to exist: `--url` / `PRIMA_CLI_URL` for the site, `--spec` / `PRIMA_CLI_SPEC` for collected documentation, and `--knowledge` / `PRIMA_CLI_KNOWLEDGE` for facts such as credentials. Every `EXPLORBOT_*` variable has a `PRIMA_CLI_*` twin that prima reads first.
|
|
789
|
+
|
|
763
790
|
`pw` still works when no model is usable at all; commands that need one say so and point at the fallback.
|
|
764
791
|
|
|
765
792
|
## Plan Management
|
|
@@ -19,6 +19,7 @@ Every message is JSON with a `type` and a `ts`, plus whatever that type carries.
|
|
|
19
19
|
| `screenshot` | the screenshot file just written |
|
|
20
20
|
| `research` | the researcher's map of a page: markdown and its file |
|
|
21
21
|
| `report` | the analyst's end-of-session report: markdown and its file |
|
|
22
|
+
| `docs` | the doc collector's spec index: markdown and its file |
|
|
22
23
|
| `activity` | what the run is doing this second |
|
|
23
24
|
| `log` | a log line and its level |
|
|
24
25
|
| `ask` | a question for a human, carrying an `askId` |
|