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.
Files changed (155) hide show
  1. package/bin/explorbot-cli.ts +23 -16
  2. package/boat/api-tester/src/ai/chief.ts +7 -1
  3. package/boat/api-tester/src/ai/curler.ts +7 -1
  4. package/boat/api-tester/src/apibot.ts +10 -4
  5. package/boat/api-tester/src/cli.ts +12 -2
  6. package/boat/api-tester/src/config.ts +28 -8
  7. package/boat/doc-collector/bin/doc-collector-cli.ts +3 -2
  8. package/boat/doc-collector/src/cli.ts +6 -1
  9. package/boat/doc-collector/src/docbot.ts +4 -2
  10. package/boat/prima/bin/prima-cli.ts +2 -0
  11. package/boat/prima/src/cli.ts +23 -8
  12. package/boat/prima/src/envelope.ts +35 -9
  13. package/boat/prima/src/prima.ts +23 -10
  14. package/dist/bin/explorbot-cli.js +24 -16
  15. package/dist/boat/api-tester/bin/apibot-cli.js +3 -2
  16. package/dist/boat/api-tester/src/ai/chief.js +6 -1
  17. package/dist/boat/api-tester/src/ai/curler.js +6 -1
  18. package/dist/boat/api-tester/src/apibot.js +7 -3
  19. package/dist/boat/api-tester/src/cli.js +12 -2
  20. package/dist/boat/api-tester/src/config.js +31 -8
  21. package/dist/boat/doc-collector/bin/doc-collector-cli.js +3 -2
  22. package/dist/boat/doc-collector/src/cli.js +6 -1
  23. package/dist/boat/doc-collector/src/docbot.js +4 -2
  24. package/dist/boat/prima/bin/prima-cli.js +2 -0
  25. package/dist/boat/prima/src/cli.js +22 -8
  26. package/dist/boat/prima/src/envelope.js +24 -6
  27. package/dist/boat/prima/src/prima.js +23 -11
  28. package/dist/package.json +2 -2
  29. package/dist/rules/planner/styles/normal.md +1 -1
  30. package/dist/src/action-result.d.ts +9 -1
  31. package/dist/src/action-result.js +57 -18
  32. package/dist/src/action.d.ts +1 -1
  33. package/dist/src/action.js +87 -12
  34. package/dist/src/ai/captain.js +1 -1
  35. package/dist/src/ai/driller.d.ts +0 -1
  36. package/dist/src/ai/driller.js +8 -20
  37. package/dist/src/ai/fisherman-tools.d.ts +9 -0
  38. package/dist/src/ai/fisherman-tools.js +52 -6
  39. package/dist/src/ai/fisherman.d.ts +4 -2
  40. package/dist/src/ai/fisherman.js +48 -27
  41. package/dist/src/ai/historian/codeceptjs.js +1 -1
  42. package/dist/src/ai/historian/playwright.js +1 -1
  43. package/dist/src/ai/navigator.js +1 -1
  44. package/dist/src/ai/pilot.d.ts +1 -0
  45. package/dist/src/ai/pilot.js +13 -1
  46. package/dist/src/ai/planner.js +9 -7
  47. package/dist/src/ai/provider.js +20 -3
  48. package/dist/src/ai/researcher/deep-analysis.js +1 -3
  49. package/dist/src/ai/researcher.js +5 -3
  50. package/dist/src/ai/rules.js +3 -3
  51. package/dist/src/ai/tester.d.ts +3 -0
  52. package/dist/src/ai/tester.js +40 -3
  53. package/dist/src/ai/tools.d.ts +1 -0
  54. package/dist/src/ai/tools.js +13 -6
  55. package/dist/src/api/request-result.d.ts +2 -0
  56. package/dist/src/api/request-result.js +8 -2
  57. package/dist/src/api/request-store.d.ts +3 -2
  58. package/dist/src/api/request-store.js +66 -14
  59. package/dist/src/api/spec-reader.js +1 -1
  60. package/dist/src/commands/config-command.js +1 -1
  61. package/dist/src/commands/drill-command.js +1 -1
  62. package/dist/src/commands/explore-command.d.ts +6 -0
  63. package/dist/src/commands/explore-command.js +39 -3
  64. package/dist/src/commands/freesail-command.js +10 -1
  65. package/dist/src/commands/options/base-option.d.ts +8 -0
  66. package/dist/src/commands/options/base-option.js +12 -0
  67. package/dist/src/commands/options/index.d.ts +5 -0
  68. package/dist/src/commands/options/index.js +5 -0
  69. package/dist/src/commands/options/knowledge-option.d.ts +7 -0
  70. package/dist/src/commands/options/knowledge-option.js +12 -0
  71. package/dist/src/commands/options/ws-option.d.ts +7 -0
  72. package/dist/src/commands/options/ws-option.js +21 -0
  73. package/dist/src/commands/plans-command.js +6 -6
  74. package/dist/src/config.d.ts +1 -0
  75. package/dist/src/config.js +12 -0
  76. package/dist/src/experience-tracker.js +5 -0
  77. package/dist/src/explorbot.d.ts +0 -1
  78. package/dist/src/explorbot.js +24 -37
  79. package/dist/src/knowledge-tracker.d.ts +20 -7
  80. package/dist/src/knowledge-tracker.js +69 -31
  81. package/dist/src/remote.d.ts +0 -3
  82. package/dist/src/remote.js +0 -18
  83. package/dist/src/state-manager.d.ts +5 -1
  84. package/dist/src/state-manager.js +10 -7
  85. package/dist/src/test-plan.d.ts +3 -0
  86. package/dist/src/test-plan.js +27 -0
  87. package/dist/src/utils/aria.d.ts +1 -1
  88. package/dist/src/utils/aria.js +6 -42
  89. package/dist/src/utils/html-diff.d.ts +4 -0
  90. package/dist/src/utils/html-diff.js +62 -7
  91. package/dist/src/utils/html.d.ts +5 -15
  92. package/dist/src/utils/html.js +14 -85
  93. package/dist/src/utils/overlay.d.ts +56 -11
  94. package/dist/src/utils/overlay.js +191 -21
  95. package/dist/src/utils/request-map.d.ts +7 -0
  96. package/dist/src/utils/request-map.js +16 -0
  97. package/dist/src/utils/url-matcher.js +4 -2
  98. package/docs/api-testing/basics.md +15 -0
  99. package/docs/api-testing/planning.md +10 -1
  100. package/docs/reference/commands.md +32 -5
  101. package/docs/reference/websocket.md +1 -0
  102. package/docs/superpowers/plans/2026-08-29-fisherman-reliability.md +953 -0
  103. package/docs/superpowers/plans/2026-08-29-region-states.md +1292 -0
  104. package/docs/superpowers/plans/2026-08-30-fisherman-live-session-auth.md +457 -0
  105. package/docs/superpowers/specs/2026-08-29-fisherman-reliability-design.md +45 -0
  106. package/docs/superpowers/specs/2026-08-29-region-states-design.md +262 -0
  107. package/docs/superpowers/specs/2026-08-29-region-states-fixes-design.md +269 -0
  108. package/docs/superpowers/specs/2026-08-30-fisherman-live-session-auth-design.md +37 -0
  109. package/docs/workflow/agentic-usage.md +12 -2
  110. package/docs/workflow/ci.md +1 -0
  111. package/docs/workflow/knowledge.md +46 -2
  112. package/package.json +2 -2
  113. package/rules/planner/styles/normal.md +1 -1
  114. package/src/action-result.ts +61 -22
  115. package/src/action.ts +87 -14
  116. package/src/ai/captain.ts +1 -1
  117. package/src/ai/driller.ts +7 -39
  118. package/src/ai/fisherman-tools.ts +56 -7
  119. package/src/ai/fisherman.ts +48 -28
  120. package/src/ai/historian/codeceptjs.ts +1 -1
  121. package/src/ai/historian/playwright.ts +1 -1
  122. package/src/ai/navigator.ts +1 -1
  123. package/src/ai/pilot.ts +9 -1
  124. package/src/ai/planner.ts +9 -8
  125. package/src/ai/provider.ts +21 -3
  126. package/src/ai/researcher/deep-analysis.ts +1 -2
  127. package/src/ai/researcher.ts +4 -3
  128. package/src/ai/rules.ts +3 -3
  129. package/src/ai/tester.ts +40 -3
  130. package/src/ai/tools.ts +17 -9
  131. package/src/api/request-result.ts +10 -2
  132. package/src/api/request-store.ts +60 -13
  133. package/src/api/spec-reader.ts +1 -1
  134. package/src/commands/config-command.ts +1 -1
  135. package/src/commands/drill-command.ts +1 -1
  136. package/src/commands/explore-command.ts +37 -3
  137. package/src/commands/freesail-command.ts +7 -1
  138. package/src/commands/options/base-option.ts +18 -0
  139. package/src/commands/options/index.ts +7 -0
  140. package/src/commands/options/knowledge-option.ts +14 -0
  141. package/src/commands/options/ws-option.ts +24 -0
  142. package/src/commands/plans-command.ts +6 -6
  143. package/src/config.ts +12 -0
  144. package/src/experience-tracker.ts +5 -1
  145. package/src/explorbot.ts +21 -37
  146. package/src/knowledge-tracker.ts +94 -36
  147. package/src/remote.ts +0 -20
  148. package/src/state-manager.ts +13 -7
  149. package/src/test-plan.ts +29 -0
  150. package/src/utils/aria.ts +7 -44
  151. package/src/utils/html-diff.ts +62 -7
  152. package/src/utils/html.ts +14 -91
  153. package/src/utils/overlay.ts +226 -23
  154. package/src/utils/request-map.ts +19 -0
  155. package/src/utils/url-matcher.ts +3 -2
@@ -1,43 +1,213 @@
1
1
  import { detectFocusArea } from './aria.js';
2
- import { HTML_EXTRACTION_LIMITS, HTML_SELECTORS, HTML_VISIBILITY_LIMITS, extractHeadings } from './html.js';
3
- export const OVERLAY_SELECTORS = {
4
- semanticOverlays: ['[role="dialog"]', '[role="listbox"]', '[role="menu"]', '[role="tooltip"]:not([style*="display: none"]):not([style*="visibility: hidden"])', '[class*="modal"]', '[class*="dialog"]', '[class*="overlay"]', '[class*="popup"]', '[class*="drawer"]', '[class*="lightbox"]'],
5
- modalOverlays: ['[role="dialog"]', '[role="alertdialog"]', '[aria-modal="true"]', '[class*="modal"]', '[class*="dialog"]', '[class*="overlay"]', '[class*="popup"]', '[class*="drawer"]', '[class*="lightbox"]'],
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
- static fromHtml(html) {
19
- const headings = extractHeadings(html);
20
- const name = [headings.h1, headings.h2, headings.h3, headings.h4].filter(Boolean).join(' ');
21
- return new Overlay({ type: 'modal', name: name || null });
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
- static captureConfig() {
34
- return {
35
- interactiveContentSelector: HTML_SELECTORS.interactiveContent,
36
- limits: HTML_EXTRACTION_LIMITS,
37
- overlaySelectors: OVERLAY_SELECTORS.modalOverlays,
38
- overlaySemanticSelector: OVERLAY_SELECTORS.overlaySemanticSelector,
39
- visibilityLimits: HTML_VISIBILITY_LIMITS,
40
- geometryFallback: false,
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:` points at the captures. `check` adds `### Expected outcomes`; `ask`, `research`, and `verify` add `### Answer`, `### Research`, or `### Assertions`; `pw` adds `### Value` when its expression returns one. `network:` appears under `### Artifacts` only when requests were captured, and everything else recorded for a command is behind `prima status <hash>`.
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` |