explorbot 0.1.31 → 0.1.32-beta.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 (177) hide show
  1. package/dist/boat/api-tester/example/apibot.config.js +30 -0
  2. package/dist/package.json +10 -25
  3. package/dist/src/action-result.d.ts +133 -0
  4. package/dist/src/action-result.js +22 -7
  5. package/dist/src/action.d.ts +50 -0
  6. package/dist/src/action.js +10 -5
  7. package/dist/src/activity.d.ts +26 -0
  8. package/dist/src/ai/agent.d.ts +3 -0
  9. package/dist/src/ai/captain/file-tools.d.ts +28 -0
  10. package/dist/src/ai/captain/idle-mode.d.ts +11 -0
  11. package/dist/src/ai/captain/mixin.d.ts +10 -0
  12. package/dist/src/ai/captain/test-mode.d.ts +13 -0
  13. package/dist/src/ai/captain/web-mode.d.ts +13 -0
  14. package/dist/src/ai/captain.d.ts +143 -0
  15. package/dist/src/ai/conversation.d.ts +31 -0
  16. package/dist/src/ai/driller.d.ts +161 -0
  17. package/dist/src/ai/experience-compactor.d.ts +53 -0
  18. package/dist/src/ai/fisherman-tools.d.ts +90 -0
  19. package/dist/src/ai/fisherman.d.ts +30 -0
  20. package/dist/src/ai/historian/codeceptjs.d.ts +15 -0
  21. package/dist/src/ai/historian/experience.d.ts +40 -0
  22. package/dist/src/ai/historian/mixin.d.ts +2 -0
  23. package/dist/src/ai/historian/playwright.d.ts +20 -0
  24. package/dist/src/ai/historian/screencast.d.ts +32 -0
  25. package/dist/src/ai/historian/utils.d.ts +4 -0
  26. package/dist/src/ai/historian.d.ts +108 -0
  27. package/dist/src/ai/navigator.d.ts +56 -0
  28. package/dist/src/ai/pilot.d.ts +83 -0
  29. package/dist/src/ai/planner/session-dedup.d.ts +12 -0
  30. package/dist/src/ai/planner/styles.d.ts +6 -0
  31. package/dist/src/ai/planner/subpages.d.ts +35 -0
  32. package/dist/src/ai/planner.d.ts +79 -0
  33. package/dist/src/ai/provider.d.ts +50 -0
  34. package/dist/src/ai/provider.js +10 -3
  35. package/dist/src/ai/quartermaster.d.ts +57 -0
  36. package/dist/src/ai/rerunner.d.ts +49 -0
  37. package/dist/src/ai/researcher/cache.d.ts +6 -0
  38. package/dist/src/ai/researcher/coordinates.d.ts +56 -0
  39. package/dist/src/ai/researcher/deep-analysis.d.ts +74 -0
  40. package/dist/src/ai/researcher/fingerprint-worker.d.ts +1 -0
  41. package/dist/src/ai/researcher/focus.d.ts +8 -0
  42. package/dist/src/ai/researcher/locators.d.ts +36 -0
  43. package/dist/src/ai/researcher/mixin.d.ts +2 -0
  44. package/dist/src/ai/researcher/parser.d.ts +35 -0
  45. package/dist/src/ai/researcher/research-result.d.ts +16 -0
  46. package/dist/src/ai/researcher/sections.d.ts +19 -0
  47. package/dist/src/ai/rules.d.ts +15 -0
  48. package/dist/src/ai/session-analyst.d.ts +11 -0
  49. package/dist/src/ai/task-agent.d.ts +28 -0
  50. package/dist/src/ai/tester.d.ts +103 -0
  51. package/dist/src/ai/tools.d.ts +41 -0
  52. package/dist/src/api/api-client.d.ts +17 -0
  53. package/dist/src/api/request-result.d.ts +44 -0
  54. package/dist/src/api/request-store.d.ts +29 -0
  55. package/dist/src/api/spec-reader.d.ts +5 -0
  56. package/dist/src/api/xhr-capture.d.ts +10 -0
  57. package/dist/src/browser-server.d.ts +10 -0
  58. package/dist/src/command-handler.d.ts +68 -0
  59. package/dist/src/commands/add-rule-command.d.ts +11 -0
  60. package/dist/src/commands/base-command.d.ts +26 -0
  61. package/dist/src/commands/clean-command.d.ts +12 -0
  62. package/dist/src/commands/compact-command.d.ts +20 -0
  63. package/dist/src/commands/context-aria-command.d.ts +6 -0
  64. package/dist/src/commands/context-command.d.ts +11 -0
  65. package/dist/src/commands/context-data-command.d.ts +6 -0
  66. package/dist/src/commands/context-experience-command.d.ts +6 -0
  67. package/dist/src/commands/context-html-command.d.ts +6 -0
  68. package/dist/src/commands/context-knowledge-command.d.ts +6 -0
  69. package/dist/src/commands/debug-command.d.ts +6 -0
  70. package/dist/src/commands/drill-command.d.ts +10 -0
  71. package/dist/src/commands/exit-command.d.ts +7 -0
  72. package/dist/src/commands/experience-command.d.ts +15 -0
  73. package/dist/src/commands/explore-command.d.ts +51 -0
  74. package/dist/src/commands/freesail-command.d.ts +12 -0
  75. package/dist/src/commands/help-command.d.ts +6 -0
  76. package/dist/src/commands/index.d.ts +4 -0
  77. package/dist/src/commands/init-command.d.ts +7 -0
  78. package/dist/src/commands/knows-command.d.ts +10 -0
  79. package/dist/src/commands/learn-command.d.ts +7 -0
  80. package/dist/src/commands/navigate-command.d.ts +7 -0
  81. package/dist/src/commands/path-command.d.ts +10 -0
  82. package/dist/src/commands/plan-clear-command.d.ts +7 -0
  83. package/dist/src/commands/plan-command.d.ts +14 -0
  84. package/dist/src/commands/plan-edit-command.d.ts +7 -0
  85. package/dist/src/commands/plan-load-command.d.ts +7 -0
  86. package/dist/src/commands/plan-reload-command.d.ts +7 -0
  87. package/dist/src/commands/plan-save-command.d.ts +7 -0
  88. package/dist/src/commands/plans-command.d.ts +21 -0
  89. package/dist/src/commands/rerun-command.d.ts +7 -0
  90. package/dist/src/commands/research-command.d.ts +11 -0
  91. package/dist/src/commands/runs-command.d.ts +7 -0
  92. package/dist/src/commands/start-command.d.ts +7 -0
  93. package/dist/src/commands/status-command.d.ts +6 -0
  94. package/dist/src/commands/test-command.d.ts +11 -0
  95. package/dist/src/components/ActivityPane.d.ts +6 -0
  96. package/dist/src/components/AddKnowledge.d.ts +8 -0
  97. package/dist/src/components/AddRule.d.ts +9 -0
  98. package/dist/src/components/App.d.ts +9 -0
  99. package/dist/src/components/Autocomplete.d.ts +3 -0
  100. package/dist/src/components/InputPane.d.ts +13 -0
  101. package/dist/src/components/InputReadline.d.ts +17 -0
  102. package/dist/src/components/LogPane.d.ts +6 -0
  103. package/dist/src/components/PlanEditor.d.ts +9 -0
  104. package/dist/src/components/PlanPane.d.ts +13 -0
  105. package/dist/src/components/SessionTimer.d.ts +6 -0
  106. package/dist/src/components/StateTransitionPane.d.ts +8 -0
  107. package/dist/src/components/StatusPane.d.ts +4 -0
  108. package/dist/src/components/TaskPane.d.ts +9 -0
  109. package/dist/src/components/Welcome.d.ts +3 -0
  110. package/dist/src/components/WelcomeChecklist.d.ts +9 -0
  111. package/dist/src/components/WelcomeCommands.d.ts +6 -0
  112. package/dist/src/components/autocomplete-store.d.ts +10 -0
  113. package/dist/src/components/parse-keypress.d.ts +12 -0
  114. package/dist/src/config.d.ts +251 -0
  115. package/dist/src/execution-controller.d.ts +25 -0
  116. package/dist/src/experience-tracker.d.ts +107 -0
  117. package/dist/src/experience-tracker.js +2 -1
  118. package/dist/src/explorbot.d.ts +107 -0
  119. package/dist/src/explorer.d.ts +140 -0
  120. package/dist/src/explorer.js +13 -9
  121. package/dist/src/index.d.ts +6 -0
  122. package/dist/src/index.js +2 -55
  123. package/dist/src/knowledge-tracker.d.ts +33 -0
  124. package/dist/src/knowledge-tracker.js +9 -2
  125. package/dist/src/observability.d.ts +13 -0
  126. package/dist/src/observability.js +2 -1
  127. package/dist/src/playwright-recorder.d.ts +31 -0
  128. package/dist/src/reporter.d.ts +34 -0
  129. package/dist/src/state-manager.d.ts +185 -0
  130. package/dist/src/state-manager.js +0 -7
  131. package/dist/src/stats.d.ts +24 -0
  132. package/dist/src/suite.d.ts +24 -0
  133. package/dist/src/test-plan.d.ts +166 -0
  134. package/dist/src/utils/aria.d.ts +20 -0
  135. package/dist/src/utils/browser-errors.d.ts +8 -0
  136. package/dist/src/utils/cli-name.d.ts +1 -0
  137. package/dist/src/utils/code-extractor.d.ts +1 -0
  138. package/dist/src/utils/context-formatter.d.ts +25 -0
  139. package/dist/src/utils/error-page.d.ts +12 -0
  140. package/dist/src/utils/expandable.d.ts +4 -0
  141. package/dist/src/utils/hooks-runner.d.ts +15 -0
  142. package/dist/src/utils/html-diff.d.ts +19 -0
  143. package/dist/src/utils/html.d.ts +124 -0
  144. package/dist/src/utils/log-filters.d.ts +6 -0
  145. package/dist/src/utils/logger.d.ts +35 -0
  146. package/dist/src/utils/loop.d.ts +35 -0
  147. package/dist/src/utils/markdown-parser.d.ts +17 -0
  148. package/dist/src/utils/markdown-query.d.ts +40 -0
  149. package/dist/src/utils/markdown-terminal.d.ts +1 -0
  150. package/dist/src/utils/next-steps.d.ts +11 -0
  151. package/dist/src/utils/page-readiness.d.ts +6 -0
  152. package/dist/src/utils/research-parser.d.ts +1 -0
  153. package/dist/src/utils/retry.d.ts +8 -0
  154. package/dist/src/utils/rules-loader.d.ts +12 -0
  155. package/dist/src/utils/secrets.d.ts +4 -0
  156. package/dist/src/utils/secrets.js +28 -0
  157. package/dist/src/utils/step-analyzer.d.ts +11 -0
  158. package/dist/src/utils/strings.d.ts +3 -0
  159. package/dist/src/utils/test-files.d.ts +3 -0
  160. package/dist/src/utils/test-plan-markdown.d.ts +9 -0
  161. package/dist/src/utils/throttle.d.ts +2 -0
  162. package/dist/src/utils/unique-names.d.ts +2 -0
  163. package/dist/src/utils/url-matcher.d.ts +6 -0
  164. package/dist/src/utils/web-element.d.ts +44 -0
  165. package/dist/src/utils/xpath.d.ts +17 -0
  166. package/package.json +10 -25
  167. package/src/action-result.ts +22 -7
  168. package/src/action.ts +12 -5
  169. package/src/ai/provider.ts +10 -3
  170. package/src/experience-tracker.ts +2 -1
  171. package/src/explorer.ts +14 -9
  172. package/src/index.ts +9 -0
  173. package/src/knowledge-tracker.ts +7 -1
  174. package/src/observability.ts +2 -1
  175. package/src/state-manager.ts +0 -8
  176. package/src/utils/secrets.ts +29 -0
  177. package/src/index.tsx +0 -62
@@ -0,0 +1,35 @@
1
+ import { type Span } from '@opentelemetry/api';
2
+ export type LogType = 'info' | 'success' | 'error' | 'warning' | 'debug' | 'substep' | 'operation' | 'step' | 'multiline' | 'details' | 'html' | 'input';
3
+ export interface TaggedLogEntry {
4
+ type: LogType;
5
+ content: string;
6
+ timestamp?: Date;
7
+ originalArgs?: any[];
8
+ namespace?: string;
9
+ maxLines?: number;
10
+ }
11
+ type LogEntry = TaggedLogEntry;
12
+ export declare const tag: (type: LogType) => {
13
+ log: (...args: any[]) => void;
14
+ };
15
+ export declare const log: (...args: any[]) => void;
16
+ export declare const logSuccess: (...args: any[]) => void;
17
+ export declare const logError: (...args: any[]) => void;
18
+ export declare const logWarning: (...args: any[]) => void;
19
+ export declare const logSubstep: (...args: any[]) => void;
20
+ export declare const createDebug: (namespace: string) => (...args: any[]) => void;
21
+ export declare const getMethodsOfObject: (obj: any) => string[];
22
+ export declare const startLogCapture: () => void;
23
+ export declare const stopLogCapture: () => string[];
24
+ export declare const setVerboseMode: (enabled: boolean) => void;
25
+ export declare const setPreserveConsoleLogs: (enabled: boolean) => void;
26
+ export declare const isVerboseMode: () => boolean;
27
+ export declare const setDebugMode: (enabled: boolean) => void;
28
+ export declare const isDebugMode: () => boolean;
29
+ export declare const registerLogPane: (addLog: (entry: LogEntry) => void) => void;
30
+ export declare const unregisterLogPane: (addLog: (entry: LogEntry) => void) => void;
31
+ export declare const addTruncateTag: (tagName: string) => void;
32
+ export declare const setStepSpanParent: (span: Span | null) => void;
33
+ export declare const setLogCallback: (addLog: (entry: LogEntry) => void) => void;
34
+ export declare const pluralize: (count: number, singular: string, plural?: string) => string;
35
+ export {};
@@ -0,0 +1,35 @@
1
+ export declare class StopError extends Error {
2
+ constructor(message?: string);
3
+ }
4
+ export declare class InterruptError extends Error {
5
+ userInput: string | null;
6
+ constructor(userInput: string | null);
7
+ }
8
+ export interface LoopContext {
9
+ stop: () => void;
10
+ iteration: number;
11
+ pause: (prompt?: string) => Promise<string | null>;
12
+ userInput: string | null;
13
+ setUserInput: (input: string) => void;
14
+ }
15
+ export interface CatchContext {
16
+ error: Error;
17
+ stop: () => void;
18
+ iteration: number;
19
+ }
20
+ export interface LoopOptions {
21
+ maxAttempts?: number;
22
+ interruptible?: boolean;
23
+ interruptPrompt?: string;
24
+ onInterrupt?: (userInput: string | null, context: LoopContext) => Promise<void> | void;
25
+ catch?: (context: CatchContext) => Promise<void> | void;
26
+ observability?: {
27
+ name?: string;
28
+ agent?: string;
29
+ sessionId?: string;
30
+ tags?: string[];
31
+ metadata?: Record<string, unknown>;
32
+ };
33
+ }
34
+ export declare function pause(prompt?: string): Promise<string | null>;
35
+ export declare function loop<T>(handler: (context: LoopContext) => Promise<T>, options?: LoopOptions): Promise<any>;
@@ -0,0 +1,17 @@
1
+ export interface MarkdownSection {
2
+ name: string;
3
+ depth: number;
4
+ rawMarkdown: string;
5
+ }
6
+ export declare function parseSections(markdown: string, opts?: {
7
+ minDepth?: number;
8
+ maxDepth?: number;
9
+ }): MarkdownSection[];
10
+ export declare function extractLabeledCode(markdown: string, labelPattern: RegExp): string | null;
11
+ export declare function tableToJson(markdown: string): Record<string, string>[];
12
+ export declare function jsonToTable(rows: Record<string, string>[], columns?: string[]): string;
13
+ export declare function findTableRaw(markdown: string): string | null;
14
+ export declare function findTableLineRange(markdown: string): {
15
+ start: number;
16
+ end: number;
17
+ } | null;
@@ -0,0 +1,40 @@
1
+ import { type Token } from 'marked';
2
+ export type SelectorType = 'section' | 'section1' | 'section2' | 'section3' | 'section4' | 'section5' | 'section6' | 'table' | 'heading' | 'paragraph' | 'list' | 'item' | 'code' | 'blockquote' | 'hr' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
3
+ export interface TextMatcher {
4
+ mode: 'exact' | 'contains' | 'regex';
5
+ value: string;
6
+ negated: boolean;
7
+ }
8
+ export interface QuerySegment {
9
+ selector: SelectorType;
10
+ textMatch?: TextMatcher;
11
+ index: number | null;
12
+ slice: {
13
+ from?: number;
14
+ to?: number;
15
+ } | null;
16
+ }
17
+ export interface MatchedRange {
18
+ token: Token;
19
+ start: number;
20
+ length: number;
21
+ innerTokens?: MatchedRange[];
22
+ }
23
+ export declare function parseQuery(input: string): QuerySegment[];
24
+ export declare class MarkdownQuery {
25
+ source: string;
26
+ matches: MatchedRange[];
27
+ constructor(source: string, matches?: MatchedRange[]);
28
+ query(selector: string): MarkdownQuery;
29
+ text(): string;
30
+ get(): string;
31
+ toJson(): Record<string, string>[];
32
+ replace(content: string): string;
33
+ count(): number;
34
+ first(): MarkdownQuery;
35
+ last(): MarkdownQuery;
36
+ before(): MarkdownQuery;
37
+ after(): MarkdownQuery;
38
+ each(): MarkdownQuery[];
39
+ }
40
+ export declare function mdq(source: string): MarkdownQuery;
@@ -0,0 +1 @@
1
+ export declare function parseMarkdownToTerminal(markdown: string): string;
@@ -0,0 +1,11 @@
1
+ export interface NextStepCommand {
2
+ label: string;
3
+ command: string;
4
+ }
5
+ export interface NextStepSection {
6
+ label: string;
7
+ path?: string;
8
+ commands?: NextStepCommand[];
9
+ }
10
+ export declare function relativeToCwd(absPath: string): string;
11
+ export declare function printNextSteps(sections: NextStepSection[]): void;
@@ -0,0 +1,6 @@
1
+ export declare function waitForPageReadiness(page: any, options?: PageReadinessOptions): Promise<void>;
2
+ interface PageReadinessOptions {
3
+ timeout?: number;
4
+ spinnerSelectors?: string[];
5
+ }
6
+ export {};
@@ -0,0 +1 @@
1
+ export { RESEARCH_COLUMN_ORDER, type ResearchElement, type ResearchSection, extractContainerFromBlockquote, extractValidContainers, formatResearchSummary, mapRowToElement, parseResearchSections, rebuildSectionMarkdown, } from '../ai/researcher/parser.js';
@@ -0,0 +1,8 @@
1
+ export interface RetryOptions {
2
+ maxAttempts?: number;
3
+ baseDelay?: number;
4
+ maxDelay?: number;
5
+ backoffMultiplier?: number;
6
+ retryCondition?: (error: Error) => boolean;
7
+ }
8
+ export declare function withRetry<T>(operation: () => Promise<T>, options?: RetryOptions): Promise<T>;
@@ -0,0 +1,12 @@
1
+ export declare class RulesLoader {
2
+ static loadRules(agentName: string, rulesConfig: RuleEntry[], currentUrl: string): string;
3
+ static loadStyles(agentName: string, styleNames: string[]): Record<string, string>;
4
+ static listStyleNames(agentName: string): string[];
5
+ static getActiveStyle(styles: Record<string, string>, iteration: number, override?: string): {
6
+ name: string;
7
+ approach: string;
8
+ };
9
+ static extractRules(agentName: string, targetDir: string): string[];
10
+ }
11
+ type RuleEntry = string | Record<string, string>;
12
+ export type { RuleEntry };
@@ -0,0 +1,4 @@
1
+ export declare function registerSecret(value: string): void;
2
+ export declare function redactSecrets(text: string): string;
3
+ export declare function clearRegisteredSecrets(): void;
4
+ export declare function isSecretName(name: string): boolean;
@@ -0,0 +1,28 @@
1
+ const MIN_SECRET_LENGTH = 4;
2
+ const secretValues = new Set();
3
+ export function registerSecret(value) {
4
+ if (!value)
5
+ return;
6
+ if (value.length < MIN_SECRET_LENGTH)
7
+ return;
8
+ secretValues.add(value);
9
+ }
10
+ export function redactSecrets(text) {
11
+ if (!text)
12
+ return text;
13
+ let result = text;
14
+ for (const value of secretValues) {
15
+ if (!result.includes(value))
16
+ continue;
17
+ result = result.split(value).join('***REDACTED***');
18
+ }
19
+ return result;
20
+ }
21
+ export function clearRegisteredSecrets() {
22
+ secretValues.clear();
23
+ }
24
+ const SECRET_NAME_TOKENS = ['password', 'passwd', 'secret', 'token', 'apikey', 'api_key', 'credential', 'private_key', 'privatekey', 'access_key'];
25
+ export function isSecretName(name) {
26
+ const lower = name.toLowerCase();
27
+ return SECRET_NAME_TOKENS.some((token) => lower.includes(token));
28
+ }
@@ -0,0 +1,11 @@
1
+ import type { SessionStep } from '../experience-tracker.js';
2
+ import type { StepData } from '../test-plan.js';
3
+ export declare const CODECEPT_TOOLS: readonly ["click", "hover", "pressKey", "form"];
4
+ export type CodeceptToolName = (typeof CODECEPT_TOOLS)[number];
5
+ export declare function isCodeceptToolName(toolName: string): toolName is CodeceptToolName;
6
+ export declare function getCodeceptToolName(commandName: string): CodeceptToolName | null;
7
+ export declare function getCodeceptToolNameFromCode(code: string): CodeceptToolName | null;
8
+ export declare function stripComments(code: string): string;
9
+ export declare function isNonReusableCode(code: string): boolean;
10
+ export declare function toReusableSessionStep(step: StepData): SessionStep | null;
11
+ export declare function mergeUniqueStepsByCode(primary: SessionStep[], secondary: SessionStep[]): SessionStep[];
@@ -0,0 +1,3 @@
1
+ export declare function truncateJson(input: any): string;
2
+ export declare function sanitizeFilename(name: string): string;
3
+ export declare function safeFilename(name: string, ext?: string, maxBytes?: number): string;
@@ -0,0 +1,3 @@
1
+ export declare function loadTestSuites(testsDir: string): any[];
2
+ export declare function printTestList(suites: any[]): void;
3
+ export declare function dryRunTestFile(filePath: string): Promise<void>;
@@ -0,0 +1,9 @@
1
+ import { Plan } from '../test-plan.js';
2
+ export declare function planToCompactAiContext(plan: Plan): string;
3
+ export declare function parsePlanFromMarkdown(filePath: string): Plan;
4
+ export declare function parsePlansFromMarkdown(filePath: string): Plan[];
5
+ export declare function savePlanToMarkdown(plan: Plan, filePath: string): void;
6
+ export declare function savePlansToMarkdown(plans: Plan[], filePath: string): void;
7
+ export declare function planToAiContext(plan: Plan, options?: {
8
+ skipSteps?: boolean;
9
+ }): string;
@@ -0,0 +1,2 @@
1
+ export declare function throttle<T>(fn: () => Promise<T> | T, intervalSeconds?: number): Promise<T | undefined>;
2
+ export declare function __clearThrottleCacheForTests(): void;
@@ -0,0 +1,2 @@
1
+ export declare function uniqSessionName(): string;
2
+ export declare function uniqExplorationName(): string;
@@ -0,0 +1,6 @@
1
+ export declare function isDynamicSegment(segment: string): boolean;
2
+ export declare function hasDynamicUrlSegment(url: string): boolean;
3
+ export declare function generalizeSegment(segment: string): string;
4
+ export declare function generalizeUrl(url: string): string;
5
+ export declare function matchesUrl(pattern: string, path: string): boolean;
6
+ export declare function extractStatePath(url: string): string;
@@ -0,0 +1,44 @@
1
+ import { type RawElementData } from './html.js';
2
+ import { type XPathMatch } from './xpath.js';
3
+ export { extractElementData } from './html.js';
4
+ export declare class WebElement {
5
+ tag: string;
6
+ role: string;
7
+ xpath: string;
8
+ clickXPath: string;
9
+ attrs: Record<string, string>;
10
+ text: string;
11
+ outerHTML: string;
12
+ x: number;
13
+ y: number;
14
+ constructor(data: {
15
+ tag: string;
16
+ role?: string;
17
+ xpath: string;
18
+ clickXPath: string;
19
+ attrs: Record<string, string>;
20
+ text: string;
21
+ outerHTML?: string;
22
+ x: number;
23
+ y: number;
24
+ });
25
+ get description(): string;
26
+ get keyAttrs(): string;
27
+ get coordinates(): string;
28
+ get eidx(): string | null;
29
+ get isNavigationLink(): boolean;
30
+ get filteredClasses(): string[];
31
+ get areaHints(): string[];
32
+ get contextLabel(): string;
33
+ get variantHints(): string[];
34
+ static fromRawData(d: RawElementData, role?: string): WebElement;
35
+ static fromXPathMatch(m: XPathMatch): WebElement;
36
+ static fromPlaywrightLocator(locator: any): Promise<WebElement | null>;
37
+ static fromEidx(page: any, eidx: string): Promise<WebElement | null>;
38
+ static fromEidxList(page: any, eidxList: string[]): Promise<WebElement[]>;
39
+ static findByXPath(html: string, xpath: string): Promise<{
40
+ totalFound: number;
41
+ elements: WebElement[];
42
+ error?: string;
43
+ }>;
44
+ }
@@ -0,0 +1,17 @@
1
+ export interface XPathMatch {
2
+ tag: string;
3
+ outerHTML: string;
4
+ text: string;
5
+ absoluteXPath: string;
6
+ allAttrs: Record<string, string>;
7
+ }
8
+ export interface XPathResult {
9
+ totalFound: number;
10
+ matches: XPathMatch[];
11
+ error?: string;
12
+ }
13
+ export declare const isDynamicId: (id: string) => boolean;
14
+ export declare const isGenericClass: (cls: string) => boolean;
15
+ export declare function buildClickableXPath(el: XPathMatch): string;
16
+ export declare function cssToAncestorXPath(css: string): string | null;
17
+ export declare function evaluateXPath(html: string, xpath: string): Promise<XPathResult>;
package/package.json CHANGED
@@ -1,39 +1,30 @@
1
1
  {
2
2
  "name": "explorbot",
3
- "version": "0.1.31",
3
+ "version": "0.1.32-beta.1",
4
4
  "description": "CLI app built with React Ink, CodeceptJS, and Playwright",
5
5
  "license": "Elastic-2.0",
6
6
  "type": "module",
7
7
  "main": "dist/src/index.js",
8
+ "types": "dist/src/index.d.ts",
8
9
  "exports": {
9
10
  ".": {
10
- "bun": "./src/index.tsx",
11
+ "types": "./dist/src/index.d.ts",
12
+ "bun": "./src/index.ts",
11
13
  "import": "./dist/src/index.js"
12
14
  }
13
15
  },
14
16
  "bin": {
15
17
  "explorbot": "./dist/bin/explorbot-cli.js"
16
18
  },
17
- "files": [
18
- "dist/",
19
- "src/**/*.ts",
20
- "src/**/*.tsx",
21
- "bin/**/*.ts",
22
- "boat/api-tester/src/**/*.ts",
23
- "boat/doc-collector/src/**/*.ts",
24
- "boat/doc-collector/bin/**/*.ts",
25
- "boat/doc-collector/package.json",
26
- "rules/",
27
- "assets/sample-files/"
28
- ],
19
+ "files": ["dist/", "src/**/*.ts", "src/**/*.tsx", "bin/**/*.ts", "boat/api-tester/src/**/*.ts", "boat/doc-collector/src/**/*.ts", "boat/doc-collector/bin/**/*.ts", "boat/doc-collector/package.json", "rules/", "assets/sample-files/"],
29
20
  "scripts": {
30
- "build": "bun run src/index.tsx build && bun run build:bin",
21
+ "build": "bun run build:bin",
31
22
  "build:bin": "bun build bin/explorbot-cli.ts --outdir bin --target node --external commander --format esm",
32
23
  "build:npm": "bash scripts/build-npm.sh",
33
24
  "prepublishOnly": "npm run build:npm",
34
- "dev": "bun run src/index.tsx",
35
- "start": "node dist/index.js",
36
- "init": "bun run src/index.tsx init",
25
+ "dev": "bun run bin/explorbot-cli.ts start",
26
+ "start": "node dist/bin/explorbot-cli.js",
27
+ "init": "bun run bin/explorbot-cli.ts init",
37
28
  "test": "codeceptjs run",
38
29
  "test:headless": "codeceptjs run --headless",
39
30
  "test:ui": "bun test tests/ui",
@@ -52,13 +43,7 @@
52
43
  "check:fix": "biome check --write .",
53
44
  "langfuse:export": "bun run .claude/skills/explorbot-debug/langfuse-export.ts"
54
45
  },
55
- "keywords": [
56
- "cli",
57
- "react",
58
- "ink",
59
- "codeceptjs",
60
- "playwright"
61
- ],
46
+ "keywords": ["cli", "react", "ink", "codeceptjs", "playwright"],
62
47
  "repository": {
63
48
  "type": "git",
64
49
  "url": "https://github.com/testomatio/explorbot"
@@ -68,6 +68,7 @@ export class ActionResult implements ActionResultData {
68
68
  private _screenshot: Buffer | undefined = undefined;
69
69
  readonly htmlFile: string | undefined = undefined;
70
70
  private _html: string | undefined = undefined;
71
+ private snapshotCache = new Map<string, string>();
71
72
  readonly logFile: string | undefined = undefined;
72
73
  private _browserLogs: any[] | undefined = undefined;
73
74
  readonly ariaSnapshotFile: string | undefined = undefined;
@@ -151,6 +152,7 @@ export class ActionResult implements ActionResultData {
151
152
 
152
153
  set html(value: string) {
153
154
  this._html = value;
155
+ this.snapshotCache.clear();
154
156
  }
155
157
 
156
158
  get screenshot(): Buffer | undefined {
@@ -263,21 +265,26 @@ export class ActionResult implements ActionResultData {
263
265
 
264
266
  async simplifiedHtml(htmlConfig?: HtmlConfig): Promise<string> {
265
267
  const normalizedConfig = this.normalizeHtmlConfig(htmlConfig);
266
- return await minifyHtml(htmlMinimalUISnapshot(this.html ?? '', normalizedConfig?.minimal));
268
+ const cacheKey = `simplified:${JSON.stringify(normalizedConfig?.minimal ?? null)}`;
269
+ return this.cachedSnapshot(cacheKey, () => minifyHtml(htmlMinimalUISnapshot(this.html ?? '', normalizedConfig?.minimal)));
267
270
  }
268
271
 
269
272
  async combinedHtml(htmlConfig?: HtmlConfig & { keepPositions?: boolean }): Promise<string> {
270
273
  const normalizedConfig = this.normalizeHtmlConfig(htmlConfig);
271
- const combinedHtml = await minifyHtml(htmlCombinedSnapshot(this.html ?? '', normalizedConfig?.combined, { keepPositions: htmlConfig?.keepPositions }));
272
- debugLog(`----${this.url}----`);
273
- debugLog(`Combined HTML: \n${combinedHtml}`);
274
- debugLog('----');
275
- return combinedHtml;
274
+ const cacheKey = `combined:${!!htmlConfig?.keepPositions}:${JSON.stringify(normalizedConfig?.combined ?? null)}`;
275
+ return this.cachedSnapshot(cacheKey, async () => {
276
+ const combinedHtml = await minifyHtml(htmlCombinedSnapshot(this.html ?? '', normalizedConfig?.combined, { keepPositions: htmlConfig?.keepPositions }));
277
+ debugLog(`----${this.url}----`);
278
+ debugLog(`Combined HTML: \n${combinedHtml}`);
279
+ debugLog('----');
280
+ return combinedHtml;
281
+ });
276
282
  }
277
283
 
278
284
  async textHtml(htmlConfig?: HtmlConfig): Promise<string> {
279
285
  const normalizedConfig = this.normalizeHtmlConfig(htmlConfig);
280
- return await minifyHtml(htmlTextSnapshot(this.html ?? '', normalizedConfig?.text));
286
+ const cacheKey = `text:${JSON.stringify(normalizedConfig?.text ?? null)}`;
287
+ return this.cachedSnapshot(cacheKey, () => minifyHtml(htmlTextSnapshot(this.html ?? '', normalizedConfig?.text)));
281
288
  }
282
289
 
283
290
  getInteractiveARIA(): string {
@@ -296,6 +303,14 @@ export class ActionResult implements ActionResultData {
296
303
  return parser.getConfig().html;
297
304
  }
298
305
 
306
+ private async cachedSnapshot(key: string, compute: () => Promise<string>): Promise<string> {
307
+ const cached = this.snapshotCache.get(key);
308
+ if (cached !== undefined) return cached;
309
+ const value = await compute();
310
+ this.snapshotCache.set(key, value);
311
+ return value;
312
+ }
313
+
299
314
  static fromState(state: WebPageState): ActionResult {
300
315
  let html: string | undefined = undefined;
301
316
  let screenshot: Buffer | undefined = undefined;
package/src/action.ts CHANGED
@@ -303,11 +303,11 @@ class Action {
303
303
 
304
304
  if (isPlaywright) {
305
305
  const page = this.playwrightHelper.page;
306
- const asyncFn = new Function('page', `return (async () => { ${sanitizedCode} })()`);
306
+ const asyncFn = createSandboxedFunction(['page'], `return (async () => { ${sanitizedCode} })()`);
307
307
  await asyncFn(page);
308
308
  await sleep(this.config.action?.delay || 500);
309
309
  } else {
310
- const codeFunction = new Function('I', 'tryTo', 'retryTo', 'within', 'hopeThat', 'step', 'faker', sanitizedCode);
310
+ const codeFunction = createSandboxedFunction(['I', 'tryTo', 'retryTo', 'within', 'hopeThat', 'step', 'faker'], sanitizedCode);
311
311
  codeFunction(this.actor, tryTo, retryTo, within, hopeThat, step, faker);
312
312
  await recorder.add(() => sleep(this.config.action?.delay || 500));
313
313
  await recorder.promise();
@@ -366,7 +366,7 @@ class Action {
366
366
  if (!sanitizedCode) {
367
367
  throw new Error('No valid I.* commands found in code block');
368
368
  }
369
- codeFunction = new Function('I', 'tryTo', 'retryTo', 'within', 'hopeThat', 'step', sanitizedCode);
369
+ codeFunction = createSandboxedFunction(['I', 'tryTo', 'retryTo', 'within', 'hopeThat', 'step'], sanitizedCode);
370
370
  }
371
371
  codeFunction(this.actor, tryTo, retryTo, within, hopeThat, step);
372
372
  await recorder.promise();
@@ -480,14 +480,21 @@ async function captureTitle(page: any, actor: any): Promise<string> {
480
480
  return '';
481
481
  }
482
482
 
483
- function sanitizeCodeBlock(code: string): string {
483
+ const SHADOWED_GLOBALS = ['process', 'global', 'globalThis', 'fetch', 'Bun', 'require', 'module', 'exports'];
484
+
485
+ export function sanitizeCodeBlock(code: string): string {
484
486
  return code
485
487
  .split('\n')
486
488
  .map((line) => line.trim())
487
- .filter((line) => line.startsWith('I.') || line.startsWith('page.') || line.startsWith('await '))
489
+ .filter((line) => line.startsWith('I.') || line.startsWith('page.') || line.startsWith('await page.') || line.startsWith('await I.'))
488
490
  .join('\n');
489
491
  }
490
492
 
493
+ export function createSandboxedFunction(argNames: string[], body: string): (...args: any[]) => any {
494
+ const fn = new Function(...argNames, ...SHADOWED_GLOBALS, `'use strict';\n${body}`);
495
+ return (...args: any[]) => fn(...args);
496
+ }
497
+
491
498
  function hasPlaywrightCommands(code: string): boolean {
492
499
  return code.split('\n').some((line) => {
493
500
  const trimmed = line.trim();
@@ -31,7 +31,7 @@ function extractCachedTokens(usage: any): number {
31
31
  return typeof fromRaw === 'number' ? fromRaw : 0;
32
32
  }
33
33
 
34
- function rejectAfterIdle(ms: number, signal: { cancelled: boolean }): Promise<never> {
34
+ function rejectAfterIdle(ms: number, signal: { cancelled: boolean }, controller: AbortController): Promise<never> {
35
35
  return new Promise((_, reject) => {
36
36
  const tick = () => {
37
37
  if (signal.cancelled) return;
@@ -39,6 +39,7 @@ function rejectAfterIdle(ms: number, signal: { cancelled: boolean }): Promise<ne
39
39
  setTimeout(tick, ms);
40
40
  return;
41
41
  }
42
+ controller.abort();
42
43
  reject(new Error('AI request timeout'));
43
44
  };
44
45
  setTimeout(tick, ms);
@@ -367,13 +368,16 @@ export class Provider {
367
368
  const response = await withRetry(async () => {
368
369
  const timeout = config.timeout || 30000;
369
370
  const cancel = { cancelled: false };
371
+ const controller = new AbortController();
372
+ const combinedSignal = AbortSignal.any([controller.signal, executionController.getAbortSignal()].filter(Boolean) as AbortSignal[]);
370
373
  try {
371
374
  const result = (await Promise.race([
372
375
  generateText({
373
376
  messages,
374
377
  ...config,
378
+ abortSignal: combinedSignal,
375
379
  }),
376
- rejectAfterIdle(timeout, cancel),
380
+ rejectAfterIdle(timeout, cancel, controller),
377
381
  ])) as any;
378
382
  const hasToolCall = (result.toolCalls?.length || 0) > 0;
379
383
  if (!result.text && !hasToolCall && result.finishReason === 'length') {
@@ -455,13 +459,16 @@ export class Provider {
455
459
  const response = await withRetry(async () => {
456
460
  const timeout = config.timeout || 30000;
457
461
  const cancel = { cancelled: false };
462
+ const controller = new AbortController();
463
+ const combinedSignal = AbortSignal.any([controller.signal, executionController.getAbortSignal()].filter(Boolean) as AbortSignal[]);
458
464
  try {
459
465
  return (await Promise.race([
460
466
  generateObject({
461
467
  messages,
462
468
  ...config,
469
+ abortSignal: combinedSignal,
463
470
  }),
464
- rejectAfterIdle(timeout, cancel),
471
+ rejectAfterIdle(timeout, cancel, controller),
465
472
  ])) as any;
466
473
  } finally {
467
474
  cancel.cancelled = true;
@@ -8,6 +8,7 @@ import { KnowledgeTracker } from './knowledge-tracker.js';
8
8
  import type { WebPageState } from './state-manager.js';
9
9
  import { createDebug, tag } from './utils/logger.js';
10
10
  import { mdq } from './utils/markdown-query.js';
11
+ import { redactSecrets } from './utils/secrets.js';
11
12
  import { isNonReusableCode } from './utils/step-analyzer.ts';
12
13
  import { extractStatePath } from './utils/url-matcher.js';
13
14
 
@@ -109,7 +110,7 @@ export class ExperienceTracker {
109
110
  return;
110
111
  }
111
112
  const filePath = this.getExperienceFilePath(stateHash);
112
- const fileContent = matter.stringify(content, frontmatter || {});
113
+ const fileContent = matter.stringify(redactSecrets(content), frontmatter || {});
113
114
  writeFileSync(filePath, fileContent, 'utf8');
114
115
  }
115
116
 
package/src/explorer.ts CHANGED
@@ -727,17 +727,22 @@ class Explorer {
727
727
 
728
728
  this.watchActiveTestPage();
729
729
 
730
- codeceptjs.event.dispatcher.emit('test.before', codeceptjsTest);
731
- codeceptjs.event.dispatcher.emit('test.start', codeceptjsTest);
732
- codeceptjs.event.dispatcher.on('step.passed', (step: any) => stepHandler(step, 'passed'));
733
- codeceptjs.event.dispatcher.on('step.failed', (step: any, error: any) => {
730
+ const onStepPassed = (step: any) => stepHandler(step, 'passed');
731
+ const onStepFailed = (step: any, error: any) => {
734
732
  stepHandler(step, 'failed', error?.message || String(error), error?.stack);
735
- });
736
- codeceptjs.event.dispatcher.on('test.after', () => {
737
- codeceptjs.event.dispatcher.off('step.passed', stepHandler);
738
- codeceptjs.event.dispatcher.off('step.failed', stepHandler);
733
+ };
734
+ const onTestAfter = () => {
735
+ codeceptjs.event.dispatcher.off('step.passed', onStepPassed);
736
+ codeceptjs.event.dispatcher.off('step.failed', onStepFailed);
737
+ codeceptjs.event.dispatcher.off('test.after', onTestAfter);
739
738
  this.unwatchActiveTestPages();
740
- });
739
+ };
740
+
741
+ codeceptjs.event.dispatcher.emit('test.before', codeceptjsTest);
742
+ codeceptjs.event.dispatcher.emit('test.start', codeceptjsTest);
743
+ codeceptjs.event.dispatcher.on('step.passed', onStepPassed);
744
+ codeceptjs.event.dispatcher.on('step.failed', onStepFailed);
745
+ codeceptjs.event.dispatcher.on('test.after', onTestAfter);
741
746
 
742
747
  return true;
743
748
  }
package/src/index.ts ADDED
@@ -0,0 +1,9 @@
1
+ export { ExplorBot } from './explorbot.ts';
2
+ export type { ExplorBotOptions, UserResolveFunction } from './explorbot.ts';
3
+
4
+ export { Plan, Test, TestResult, TestStatus } from './test-plan.ts';
5
+ export type { Note, StepData, TestResultType, TestStatusType } from './test-plan.ts';
6
+
7
+ export type { Link, StateTransition, WebPageState } from './state-manager.ts';
8
+
9
+ export type { AIConfig, ExplorbotConfig, PlaywrightConfig } from './config.ts';
@@ -5,6 +5,7 @@ import { ActionResult } from './action-result.js';
5
5
  import { ConfigParser } from './config.js';
6
6
  import { getCliName } from './utils/cli-name.ts';
7
7
  import { createDebug } from './utils/logger.js';
8
+ import { isSecretName, registerSecret } from './utils/secrets.js';
8
9
 
9
10
  const debugLog = createDebug('explorbot:knowledge-tracker');
10
11
 
@@ -138,9 +139,14 @@ export class KnowledgeTracker {
138
139
  const namespace = expr.slice(0, dotIndex);
139
140
  const key = expr.slice(dotIndex + 1);
140
141
 
141
- if (namespace === 'env') return process.env[key] ?? '';
142
+ if (namespace === 'env') {
143
+ const value = process.env[key] ?? '';
144
+ if (isSecretName(key)) registerSecret(value);
145
+ return value;
146
+ }
142
147
 
143
148
  if (namespace === 'config') {
149
+ if (isSecretName(key)) return '';
144
150
  const config = ConfigParser.getInstance().getConfig();
145
151
  const value = key.split('.').reduce((obj: any, k) => obj?.[k], config);
146
152
  if (value !== undefined && typeof value !== 'object') return String(value);