explorbot 0.1.9 → 0.1.10

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 (113) hide show
  1. package/bin/explorbot-cli.ts +70 -8
  2. package/boat/api-tester/src/ai/curler-tools.ts +3 -3
  3. package/boat/api-tester/src/ai/curler.ts +1 -1
  4. package/boat/api-tester/src/apibot.ts +2 -2
  5. package/boat/api-tester/src/config.ts +1 -1
  6. package/dist/bin/explorbot-cli.js +70 -7
  7. package/dist/boat/api-tester/src/ai/curler-tools.js +2 -2
  8. package/dist/boat/api-tester/src/apibot.js +2 -2
  9. package/dist/package.json +1 -1
  10. package/dist/src/ai/bosun.js +5 -1
  11. package/dist/src/ai/experience-compactor.js +235 -50
  12. package/dist/src/ai/historian.js +13 -6
  13. package/dist/src/ai/navigator.js +62 -62
  14. package/dist/src/ai/pilot.js +22 -0
  15. package/dist/src/ai/planner/subpages.js +1 -30
  16. package/dist/src/ai/planner.js +4 -4
  17. package/dist/src/ai/provider.js +1 -1
  18. package/dist/src/ai/rerunner.js +3 -3
  19. package/dist/src/ai/researcher/deep-analysis.js +1 -1
  20. package/dist/src/ai/researcher/fingerprint-worker.js +1 -1
  21. package/dist/src/ai/researcher/locators.js +1 -1
  22. package/dist/src/ai/researcher/sections.js +8 -1
  23. package/dist/src/ai/researcher.js +4 -11
  24. package/dist/src/ai/tools.js +5 -3
  25. package/dist/src/api/request-store.js +20 -0
  26. package/dist/src/api/xhr-capture.js +19 -3
  27. package/dist/src/command-handler.js +1 -1
  28. package/dist/src/commands/add-rule-command.js +1 -1
  29. package/dist/src/commands/base-command.js +20 -0
  30. package/dist/src/commands/clean-command.js +1 -1
  31. package/dist/src/commands/compact-command.js +138 -0
  32. package/dist/src/commands/context-command.js +7 -1
  33. package/dist/src/commands/drill-command.js +4 -1
  34. package/dist/src/commands/experience-command.js +104 -0
  35. package/dist/src/commands/explore-command.js +28 -5
  36. package/dist/src/commands/freesail-command.js +2 -0
  37. package/dist/src/commands/index.js +7 -3
  38. package/dist/src/commands/init-command.js +2 -2
  39. package/dist/src/commands/learn-command.js +1 -1
  40. package/dist/src/commands/navigate-command.js +4 -1
  41. package/dist/src/commands/plan-clear-command.js +4 -1
  42. package/dist/src/commands/plan-command.js +11 -4
  43. package/dist/src/commands/plan-edit-command.js +1 -1
  44. package/dist/src/commands/plan-load-command.js +4 -1
  45. package/dist/src/commands/plan-reload-command.js +4 -1
  46. package/dist/src/commands/plan-save-command.js +1 -1
  47. package/dist/src/commands/research-command.js +5 -2
  48. package/dist/src/commands/start-command.js +5 -1
  49. package/dist/src/commands/test-command.js +7 -1
  50. package/dist/src/experience-tracker.js +191 -56
  51. package/dist/src/explorbot.js +26 -14
  52. package/dist/src/explorer.js +3 -3
  53. package/dist/src/reporter.js +17 -2
  54. package/dist/src/stats.js +2 -0
  55. package/dist/src/suite.js +1 -1
  56. package/dist/src/utils/error-page.js +10 -0
  57. package/dist/src/utils/logger.js +1 -1
  58. package/dist/src/utils/rules-loader.js +1 -1
  59. package/dist/src/utils/test-files.js +1 -1
  60. package/dist/src/utils/url-matcher.js +50 -0
  61. package/package.json +1 -1
  62. package/src/ai/bosun.ts +5 -1
  63. package/src/ai/experience-compactor.ts +270 -63
  64. package/src/ai/historian.ts +12 -7
  65. package/src/ai/navigator.ts +68 -66
  66. package/src/ai/pilot.ts +22 -0
  67. package/src/ai/planner/subpages.ts +1 -24
  68. package/src/ai/planner.ts +5 -5
  69. package/src/ai/provider.ts +1 -1
  70. package/src/ai/rerunner.ts +3 -3
  71. package/src/ai/researcher/deep-analysis.ts +1 -1
  72. package/src/ai/researcher/fingerprint-worker.ts +1 -1
  73. package/src/ai/researcher/locators.ts +2 -2
  74. package/src/ai/researcher/sections.ts +7 -1
  75. package/src/ai/researcher.ts +4 -11
  76. package/src/ai/task-agent.ts +1 -1
  77. package/src/ai/tools.ts +6 -4
  78. package/src/api/request-store.ts +22 -0
  79. package/src/api/xhr-capture.ts +21 -3
  80. package/src/command-handler.ts +1 -1
  81. package/src/commands/add-rule-command.ts +2 -2
  82. package/src/commands/base-command.ts +26 -1
  83. package/src/commands/clean-command.ts +2 -2
  84. package/src/commands/compact-command.ts +156 -0
  85. package/src/commands/context-command.ts +8 -2
  86. package/src/commands/drill-command.ts +5 -2
  87. package/src/commands/experience-command.ts +125 -0
  88. package/src/commands/explore-command.ts +30 -7
  89. package/src/commands/freesail-command.ts +2 -0
  90. package/src/commands/index.ts +7 -3
  91. package/src/commands/init-command.ts +2 -2
  92. package/src/commands/learn-command.ts +2 -2
  93. package/src/commands/navigate-command.ts +5 -2
  94. package/src/commands/plan-clear-command.ts +5 -2
  95. package/src/commands/plan-command.ts +12 -5
  96. package/src/commands/plan-edit-command.ts +2 -2
  97. package/src/commands/plan-load-command.ts +5 -2
  98. package/src/commands/plan-reload-command.ts +5 -2
  99. package/src/commands/plan-save-command.ts +2 -2
  100. package/src/commands/research-command.ts +6 -3
  101. package/src/commands/start-command.ts +6 -2
  102. package/src/commands/test-command.ts +8 -2
  103. package/src/experience-tracker.ts +220 -71
  104. package/src/explorbot.ts +28 -15
  105. package/src/explorer.ts +3 -3
  106. package/src/reporter.ts +17 -3
  107. package/src/stats.ts +4 -0
  108. package/src/suite.ts +1 -1
  109. package/src/utils/error-page.ts +10 -0
  110. package/src/utils/logger.ts +1 -1
  111. package/src/utils/rules-loader.ts +1 -1
  112. package/src/utils/test-files.ts +1 -1
  113. package/src/utils/url-matcher.ts +43 -0
@@ -26,7 +26,7 @@ const pkgVersion = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')).version as stri
26
26
 
27
27
  program.name(cli).description('AI-powered web exploration tool').version(pkgVersion, '-V, --version');
28
28
  program.hook('preAction', () => {
29
- console.log(chalk.dim(`${cli} v${pkgVersion}`));
29
+ console.log(`⛵ ${chalk.yellow.bold(`Explorbot v${pkgVersion}`)} ${chalk.dim('Autonomous Testing Agent')}`);
30
30
  });
31
31
 
32
32
  interface CLIOptions {
@@ -193,17 +193,21 @@ addCommonOptions(program.command('plan <path>').description('Generate test plan
193
193
  const cliFlags = [options.path ? `--path ${options.path}` : '', options.session ? '--session' : ''].filter(Boolean).join(' ');
194
194
  const cliSuffix = cliFlags ? ` ${cliFlags}` : '';
195
195
 
196
- const lines: string[] = [];
197
- lines.push('Run commands:');
198
- lines.push(`\`${cli} test ${planFile} 1${cliSuffix}\` → run first new test`);
199
- lines.push(`\`${cli} test ${planFile} *${cliSuffix}\` run all new tests`);
196
+ const { PlanCommand } = await import('../src/commands/plan-command.js');
197
+ const planCommand = new PlanCommand(explorBot);
198
+ planCommand.suggestions = [
199
+ { command: `test ${planFile} 1${cliSuffix}`, hint: 'run first new test' },
200
+ { command: `test ${planFile} *${cliSuffix}`, hint: 'run all new tests' },
201
+ ];
200
202
  if (suite && suite.automatedTestCount > 0) {
201
203
  for (const f of suite.getAutomatedTestFiles()) {
202
- lines.push(`\`${cli} rerun ${path.relative(process.cwd(), f)}${cliSuffix}\` → re-run automated tests`);
204
+ planCommand.suggestions.push({
205
+ command: `rerun ${path.relative(process.cwd(), f)}${cliSuffix}`,
206
+ hint: 're-run automated tests',
207
+ });
203
208
  }
204
209
  }
205
-
206
- log(parseMarkdownToTerminal(lines.join('\n')));
210
+ planCommand.printSuggestions();
207
211
 
208
212
  await explorBot.stop();
209
213
  await showStatsAndExit(0);
@@ -492,6 +496,64 @@ program
492
496
  }
493
497
  });
494
498
 
499
+ program
500
+ .command('compact [target]')
501
+ .description('Compact stored experience files; pass filename or URL substring to limit scope')
502
+ .option('--dry-run', 'Preview without running AI or writing files')
503
+ .option('--no-merge', 'Skip the cross-URL merge step when compacting all')
504
+ .option('-p, --path <path>', 'Working directory path')
505
+ .option('-c, --config <path>', 'Path to configuration file')
506
+ .option('-v, --verbose', 'Enable verbose logging')
507
+ .action(async (target, options) => {
508
+ try {
509
+ const explorBot = new ExplorBot({
510
+ path: options.path,
511
+ config: options.config,
512
+ verbose: options.verbose,
513
+ });
514
+ await explorBot.startProviderOnly();
515
+
516
+ const { CompactCommand } = await import('../src/commands/compact-command.js');
517
+ const argParts = [target, options.dryRun && '--dry-run', options.merge === false && '--no-merge'].filter(Boolean).join(' ');
518
+ const command = new CompactCommand(explorBot);
519
+ await command.execute(argParts);
520
+ command.printSuggestions();
521
+
522
+ await showStatsAndExit(0);
523
+ } catch (error) {
524
+ console.error('Failed:', error instanceof Error ? error.message : 'Unknown error');
525
+ await showStatsAndExit(1);
526
+ }
527
+ });
528
+
529
+ program
530
+ .command('experience [filter] [index]')
531
+ .description('List stored experiences grouped by URL; pass URL substring to filter; pass tag (A.1) or index to expand a section')
532
+ .option('-p, --path <path>', 'Working directory path')
533
+ .option('-c, --config <path>', 'Path to configuration file')
534
+ .option('--recent', 'Only files modified within the last 30 days')
535
+ .option('--old', 'Only files modified more than 30 days ago')
536
+ .action(async (filter, index, options) => {
537
+ try {
538
+ await ConfigParser.getInstance().loadConfig({
539
+ config: options.config,
540
+ path: options.path || process.cwd(),
541
+ });
542
+ const { ExperienceCommand } = await import('../src/commands/experience-command.js');
543
+ const explorBot = new ExplorBot({ path: options.path });
544
+ const command = new ExperienceCommand(explorBot);
545
+ const flags: string[] = [];
546
+ if (options.recent) flags.push('--recent');
547
+ if (options.old) flags.push('--old');
548
+ const args = [...flags, filter, index].filter(Boolean).join(' ');
549
+ await command.execute(args);
550
+ command.printSuggestions();
551
+ } catch (error) {
552
+ console.error('Failed:', error instanceof Error ? error.message : 'Unknown error');
553
+ process.exit(1);
554
+ }
555
+ });
556
+
495
557
  addCommonOptions(program.command('research <url>').description('Research a page and print UI analysis').option('--data', 'Include data extraction in research').option('--deep', 'Enable deep analysis (expand hidden elements)').option('--no-fix', 'Skip locator fix cycle (for debugging)')).action(
496
558
  async (url, options) => {
497
559
  try {
@@ -1,13 +1,13 @@
1
- import { tool } from 'ai';
2
- import { expect } from 'expect';
3
1
  import { readFileSync } from 'node:fs';
2
+ import { tool } from 'ai';
4
3
  import dedent from 'dedent';
4
+ import { expect } from 'expect';
5
5
  import { z } from 'zod';
6
+ import type { RequestStore } from '../../../../src/api/request-store.ts';
6
7
  import type { Test, TestResultType } from '../../../../src/test-plan.ts';
7
8
  import { TestResult } from '../../../../src/test-plan.ts';
8
9
  import { tag } from '../../../../src/utils/logger.ts';
9
10
  import type { ApiClient } from '../api-client.ts';
10
- import type { RequestStore } from '../../../../src/api/request-store.ts';
11
11
 
12
12
  const readResponseData = (responseFile: string) => {
13
13
  return JSON.parse(readFileSync(responseFile, 'utf8'));
@@ -1,12 +1,12 @@
1
1
  import dedent from 'dedent';
2
2
  import { z } from 'zod';
3
3
  import type { AIProvider } from '../../../../src/ai/provider.ts';
4
+ import type { RequestStore } from '../../../../src/api/request-store.ts';
4
5
  import type { Reporter } from '../../../../src/reporter.ts';
5
6
  import { type Test, TestResult } from '../../../../src/test-plan.ts';
6
7
  import { createDebug, tag } from '../../../../src/utils/logger.ts';
7
8
  import { loop } from '../../../../src/utils/loop.ts';
8
9
  import type { ApiClient } from '../api-client.ts';
9
- import type { RequestStore } from '../../../../src/api/request-store.ts';
10
10
  import { createCurlerTools } from './curler-tools.ts';
11
11
 
12
12
  const debugLog = createDebug('explorbot:curler');
@@ -1,6 +1,8 @@
1
1
  import { existsSync, mkdirSync } from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { AIProvider } from '../../../src/ai/provider.ts';
4
+ import { RequestStore } from '../../../src/api/request-store.ts';
5
+ import { extractEndpointDefinition, loadSpec, searchEndpoints, validateSpecs } from '../../../src/api/spec-reader.ts';
4
6
  import { Reporter } from '../../../src/reporter.ts';
5
7
  import { Plan } from '../../../src/test-plan.ts';
6
8
  import { setVerboseMode, tag } from '../../../src/utils/logger.ts';
@@ -8,8 +10,6 @@ import { Chief } from './ai/chief.ts';
8
10
  import { Curler } from './ai/curler.ts';
9
11
  import { ApiClient } from './api-client.ts';
10
12
  import { type ApibotConfig, ApibotConfigParser } from './config.ts';
11
- import { RequestStore } from '../../../src/api/request-store.ts';
12
- import { extractEndpointDefinition, loadSpec, searchEndpoints, validateSpecs } from '../../../src/api/spec-reader.ts';
13
13
 
14
14
  export class ApiBot {
15
15
  private configParser: ApibotConfigParser;
@@ -1,7 +1,7 @@
1
1
  import { existsSync, mkdirSync, readFileSync } from 'node:fs';
2
2
  import path, { resolve } from 'node:path';
3
3
  import { parseEnv } from 'node:util';
4
- import { EXPLORBOT_CONFIG_PATHS, type AIConfig, type ApiConfig as BaseApiConfig, type ApiHookFn } from '../../../src/config.ts';
4
+ import { type AIConfig, type ApiHookFn, type ApiConfig as BaseApiConfig, EXPLORBOT_CONFIG_PATHS } from '../../../src/config.ts';
5
5
 
6
6
  export type { AIConfig };
7
7
 
@@ -23,7 +23,7 @@ const pkgPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../p
23
23
  const pkgVersion = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')).version;
24
24
  program.name(cli).description('AI-powered web exploration tool').version(pkgVersion, '-V, --version');
25
25
  program.hook('preAction', () => {
26
- console.log(chalk.dim(`${cli} v${pkgVersion}`));
26
+ console.log(`⛵ ${chalk.yellow.bold(`Explorbot v${pkgVersion}`)} ${chalk.dim('Autonomous Testing Agent')}`);
27
27
  });
28
28
  function buildExplorBotOptions(from, options) {
29
29
  return {
@@ -158,16 +158,21 @@ addCommonOptions(program.command('plan <path>').description('Generate test plan
158
158
  const planFile = savedPath ? path.basename(savedPath) : 'plan.md';
159
159
  const cliFlags = [options.path ? `--path ${options.path}` : '', options.session ? '--session' : ''].filter(Boolean).join(' ');
160
160
  const cliSuffix = cliFlags ? ` ${cliFlags}` : '';
161
- const lines = [];
162
- lines.push('Run commands:');
163
- lines.push(`\`${cli} test ${planFile} 1${cliSuffix}\` → run first new test`);
164
- lines.push(`\`${cli} test ${planFile} *${cliSuffix}\` run all new tests`);
161
+ const { PlanCommand } = await import('../src/commands/plan-command.js');
162
+ const planCommand = new PlanCommand(explorBot);
163
+ planCommand.suggestions = [
164
+ { command: `test ${planFile} 1${cliSuffix}`, hint: 'run first new test' },
165
+ { command: `test ${planFile} *${cliSuffix}`, hint: 'run all new tests' },
166
+ ];
165
167
  if (suite && suite.automatedTestCount > 0) {
166
168
  for (const f of suite.getAutomatedTestFiles()) {
167
- lines.push(`\`${cli} rerun ${path.relative(process.cwd(), f)}${cliSuffix}\` → re-run automated tests`);
169
+ planCommand.suggestions.push({
170
+ command: `rerun ${path.relative(process.cwd(), f)}${cliSuffix}`,
171
+ hint: 're-run automated tests',
172
+ });
168
173
  }
169
174
  }
170
- log(parseMarkdownToTerminal(lines.join('\n')));
175
+ planCommand.printSuggestions();
171
176
  await explorBot.stop();
172
177
  await showStatsAndExit(0);
173
178
  }
@@ -439,6 +444,64 @@ program
439
444
  process.exit(1);
440
445
  }
441
446
  });
447
+ program
448
+ .command('compact [target]')
449
+ .description('Compact stored experience files; pass filename or URL substring to limit scope')
450
+ .option('--dry-run', 'Preview without running AI or writing files')
451
+ .option('--no-merge', 'Skip the cross-URL merge step when compacting all')
452
+ .option('-p, --path <path>', 'Working directory path')
453
+ .option('-c, --config <path>', 'Path to configuration file')
454
+ .option('-v, --verbose', 'Enable verbose logging')
455
+ .action(async (target, options) => {
456
+ try {
457
+ const explorBot = new ExplorBot({
458
+ path: options.path,
459
+ config: options.config,
460
+ verbose: options.verbose,
461
+ });
462
+ await explorBot.startProviderOnly();
463
+ const { CompactCommand } = await import('../src/commands/compact-command.js');
464
+ const argParts = [target, options.dryRun && '--dry-run', options.merge === false && '--no-merge'].filter(Boolean).join(' ');
465
+ const command = new CompactCommand(explorBot);
466
+ await command.execute(argParts);
467
+ command.printSuggestions();
468
+ await showStatsAndExit(0);
469
+ }
470
+ catch (error) {
471
+ console.error('Failed:', error instanceof Error ? error.message : 'Unknown error');
472
+ await showStatsAndExit(1);
473
+ }
474
+ });
475
+ program
476
+ .command('experience [filter] [index]')
477
+ .description('List stored experiences grouped by URL; pass URL substring to filter; pass tag (A.1) or index to expand a section')
478
+ .option('-p, --path <path>', 'Working directory path')
479
+ .option('-c, --config <path>', 'Path to configuration file')
480
+ .option('--recent', 'Only files modified within the last 30 days')
481
+ .option('--old', 'Only files modified more than 30 days ago')
482
+ .action(async (filter, index, options) => {
483
+ try {
484
+ await ConfigParser.getInstance().loadConfig({
485
+ config: options.config,
486
+ path: options.path || process.cwd(),
487
+ });
488
+ const { ExperienceCommand } = await import('../src/commands/experience-command.js');
489
+ const explorBot = new ExplorBot({ path: options.path });
490
+ const command = new ExperienceCommand(explorBot);
491
+ const flags = [];
492
+ if (options.recent)
493
+ flags.push('--recent');
494
+ if (options.old)
495
+ flags.push('--old');
496
+ const args = [...flags, filter, index].filter(Boolean).join(' ');
497
+ await command.execute(args);
498
+ command.printSuggestions();
499
+ }
500
+ catch (error) {
501
+ console.error('Failed:', error instanceof Error ? error.message : 'Unknown error');
502
+ process.exit(1);
503
+ }
504
+ });
442
505
  addCommonOptions(program.command('research <url>').description('Research a page and print UI analysis').option('--data', 'Include data extraction in research').option('--deep', 'Enable deep analysis (expand hidden elements)').option('--no-fix', 'Skip locator fix cycle (for debugging)')).action(async (url, options) => {
443
506
  try {
444
507
  const explorBot = new ExplorBot(buildExplorBotOptions(url, options));
@@ -1,7 +1,7 @@
1
- import { tool } from 'ai';
2
- import { expect } from 'expect';
3
1
  import { readFileSync } from 'node:fs';
2
+ import { tool } from 'ai';
4
3
  import dedent from 'dedent';
4
+ import { expect } from 'expect';
5
5
  import { z } from 'zod';
6
6
  import { TestResult } from "../../../../src/test-plan.js";
7
7
  import { tag } from "../../../../src/utils/logger.js";
@@ -1,6 +1,8 @@
1
1
  import { existsSync, mkdirSync } from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { AIProvider } from "../../../src/ai/provider.js";
4
+ import { RequestStore } from "../../../src/api/request-store.js";
5
+ import { extractEndpointDefinition, loadSpec, searchEndpoints, validateSpecs } from "../../../src/api/spec-reader.js";
4
6
  import { Reporter } from "../../../src/reporter.js";
5
7
  import { Plan } from "../../../src/test-plan.js";
6
8
  import { setVerboseMode, tag } from "../../../src/utils/logger.js";
@@ -8,8 +10,6 @@ import { Chief } from "./ai/chief.js";
8
10
  import { Curler } from "./ai/curler.js";
9
11
  import { ApiClient } from "./api-client.js";
10
12
  import { ApibotConfigParser } from "./config.js";
11
- import { RequestStore } from "../../../src/api/request-store.js";
12
- import { extractEndpointDefinition, loadSpec, searchEndpoints, validateSpecs } from "../../../src/api/spec-reader.js";
13
13
  export class ApiBot {
14
14
  configParser;
15
15
  provider;
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "explorbot",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "description": "CLI app built with React Ink, CodeceptJS, and Playwright",
5
5
  "license": "Elastic-2.0",
6
6
  "type": "module",
@@ -372,7 +372,11 @@ export class Bosun extends TaskAgent {
372
372
  const actionResult = ActionResult.fromState(state);
373
373
  const successfulInteractions = results.filter((r) => r.result === 'success' && r.code);
374
374
  for (const interaction of successfulInteractions) {
375
- await experienceTracker.saveSuccessfulResolution(actionResult, `Drill ${interaction.action}: ${interaction.component}`, interaction.code, interaction.description);
375
+ experienceTracker.writeAction(actionResult, {
376
+ title: `Drill ${interaction.action}: ${interaction.component}`,
377
+ code: interaction.code,
378
+ explanation: interaction.description,
379
+ });
376
380
  }
377
381
  if (successfulInteractions.length > 0) {
378
382
  tag('success').log(`Saved ${successfulInteractions.length} interactions to experience`);