explorbot 0.2.3 → 0.2.5

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 (174) hide show
  1. package/README.md +1 -1
  2. package/bin/explorbot-cli.ts +26 -8
  3. package/boat/api-tester/src/cli.ts +17 -0
  4. package/boat/api-tester/src/config.ts +4 -2
  5. package/boat/doc-collector/bin/doc-collector-cli.ts +2 -0
  6. package/boat/doc-collector/src/ai/documentarian.ts +61 -31
  7. package/boat/doc-collector/src/cli.ts +14 -1
  8. package/boat/doc-collector/src/config.ts +4 -2
  9. package/boat/prima/bin/prima-cli.ts +0 -0
  10. package/boat/prima/src/activity-line.ts +33 -0
  11. package/boat/prima/src/cli.ts +127 -86
  12. package/boat/prima/src/envelope.ts +102 -52
  13. package/boat/prima/src/prima.ts +567 -128
  14. package/boat/prima/src/pw-parser.ts +11 -1
  15. package/boat/prima/src/pw-registry.ts +4 -5
  16. package/boat/prima/src/session-log.ts +126 -0
  17. package/dist/bin/explorbot-cli.js +26 -8
  18. package/dist/boat/api-tester/bin/apibot-cli.js +2 -0
  19. package/dist/boat/api-tester/src/cli.js +17 -0
  20. package/dist/boat/api-tester/src/config.js +4 -2
  21. package/dist/boat/doc-collector/bin/doc-collector-cli.js +2 -0
  22. package/dist/boat/doc-collector/src/ai/documentarian.js +44 -19
  23. package/dist/boat/doc-collector/src/cli.js +14 -1
  24. package/dist/boat/doc-collector/src/config.js +4 -2
  25. package/dist/boat/prima/src/activity-line.js +30 -0
  26. package/dist/boat/prima/src/cli.js +109 -77
  27. package/dist/boat/prima/src/envelope.js +94 -44
  28. package/dist/boat/prima/src/prima.js +533 -119
  29. package/dist/boat/prima/src/pw-parser.js +13 -1
  30. package/dist/boat/prima/src/pw-registry.js +4 -5
  31. package/dist/boat/prima/src/session-log.js +108 -0
  32. package/dist/package.json +3 -2
  33. package/dist/rules/navigator/verification-actions.md +20 -0
  34. package/dist/src/action-result.d.ts +7 -0
  35. package/dist/src/action-result.js +4 -0
  36. package/dist/src/action.d.ts +2 -0
  37. package/dist/src/action.js +41 -2
  38. package/dist/src/ai/captain/web-mode.js +6 -3
  39. package/dist/src/ai/captain.js +2 -0
  40. package/dist/src/ai/navigator.d.ts +34 -0
  41. package/dist/src/ai/navigator.js +237 -181
  42. package/dist/src/ai/pilot.d.ts +7 -0
  43. package/dist/src/ai/pilot.js +90 -2
  44. package/dist/src/ai/provider.d.ts +2 -2
  45. package/dist/src/ai/provider.js +14 -23
  46. package/dist/src/ai/rerunner.js +2 -1
  47. package/dist/src/ai/researcher/cache.d.ts +2 -0
  48. package/dist/src/ai/researcher/cache.js +10 -2
  49. package/dist/src/ai/researcher.js +3 -2
  50. package/dist/src/ai/rules.js +17 -10
  51. package/dist/src/ai/session-analyst.js +2 -0
  52. package/dist/src/ai/task-agent.js +4 -1
  53. package/dist/src/ai/tester.d.ts +6 -3
  54. package/dist/src/ai/tester.js +50 -46
  55. package/dist/src/ai/tools.d.ts +14 -0
  56. package/dist/src/ai/tools.js +117 -37
  57. package/dist/src/commands/config-command.d.ts +51 -0
  58. package/dist/src/commands/config-command.js +117 -0
  59. package/dist/src/commands/index.js +2 -0
  60. package/dist/src/config.d.ts +9 -1
  61. package/dist/src/config.js +53 -4
  62. package/dist/src/execution-controller.d.ts +2 -0
  63. package/dist/src/execution-controller.js +6 -0
  64. package/dist/src/explorbot.d.ts +2 -1
  65. package/dist/src/explorbot.js +7 -2
  66. package/dist/src/explorer.js +2 -3
  67. package/dist/src/playwright-recorder.js +30 -0
  68. package/dist/src/remote.d.ts +55 -0
  69. package/dist/src/remote.js +235 -0
  70. package/dist/src/reporter.d.ts +1 -0
  71. package/dist/src/reporter.js +7 -1
  72. package/dist/src/state-manager.d.ts +2 -1
  73. package/dist/src/state-manager.js +3 -1
  74. package/dist/src/stats.d.ts +1 -0
  75. package/dist/src/stats.js +1 -0
  76. package/dist/src/test-plan.d.ts +3 -0
  77. package/dist/src/test-plan.js +26 -0
  78. package/dist/src/utils/aria.d.ts +2 -8
  79. package/dist/src/utils/aria.js +69 -40
  80. package/dist/src/utils/html.js +1 -0
  81. package/dist/src/utils/logger.d.ts +7 -1
  82. package/dist/src/utils/logger.js +32 -0
  83. package/dist/src/utils/page-readiness.js +18 -1
  84. package/dist/src/utils/url-matcher.js +3 -0
  85. package/dist/src/utils/web-element.d.ts +2 -0
  86. package/dist/src/utils/web-element.js +8 -0
  87. package/dist/src/utils/web-sandbox.d.ts +1 -1
  88. package/dist/src/utils/web-sandbox.js +2 -3
  89. package/docs/api-testing/basics.md +90 -0
  90. package/docs/api-testing/planning.md +57 -0
  91. package/docs/api-testing/running-tests.md +55 -0
  92. package/docs/assets/cloud-report.png +0 -0
  93. package/docs/assets/html-report.png +0 -0
  94. package/docs/assets/langfuse-trace.png +0 -0
  95. package/docs/assets/successful-explore-run.png +0 -0
  96. package/docs/basics/getting-started.md +140 -0
  97. package/docs/basics/prerequisites.md +63 -0
  98. package/docs/basics/providers.md +362 -0
  99. package/docs/basics/running.md +78 -0
  100. package/docs/contributing/ai-integration-tests.md +57 -0
  101. package/docs/contributing/contributing.md +90 -0
  102. package/docs/contributing/demo-videos.md +36 -0
  103. package/docs/contributing/npm-package.md +138 -0
  104. package/docs/contributing/observability.md +227 -0
  105. package/docs/contributing/regression-tests.md +103 -0
  106. package/docs/contributing/testing.md +95 -0
  107. package/docs/doc-collection/basics.md +128 -0
  108. package/docs/doc-collection/crawling.md +67 -0
  109. package/docs/doc-collection/interactive-mode.md +99 -0
  110. package/docs/index.json +87 -0
  111. package/docs/reference/commands.md +997 -0
  112. package/docs/reference/configuration.md +569 -0
  113. package/docs/reference/scripting.md +303 -0
  114. package/docs/reference/websocket.md +50 -0
  115. package/docs/superpowers/plans/2026-08-01-actor-boat.md +925 -0
  116. package/docs/superpowers/plans/2026-08-01-prima-boat.md +1120 -0
  117. package/docs/superpowers/reviews/2026-08-06-prima-vs-playwright-cli.md +268 -0
  118. package/docs/superpowers/specs/2026-08-01-actor-boat-design.md +204 -0
  119. package/docs/superpowers/specs/2026-08-01-prima-boat-design.md +242 -0
  120. package/docs/superpowers/specs/2026-08-03-global-config-design.md +138 -0
  121. package/docs/superpowers/specs/2026-08-07-prima-fixes-design.md +394 -0
  122. package/docs/superpowers/specs/2026-08-18-prima-false-verdicts.md +159 -0
  123. package/docs/web-testing/agents.md +158 -0
  124. package/docs/web-testing/automated-tests.md +134 -0
  125. package/docs/web-testing/basics.md +91 -0
  126. package/docs/web-testing/customization.md +131 -0
  127. package/docs/web-testing/hooks.md +238 -0
  128. package/docs/web-testing/page-interaction.md +84 -0
  129. package/docs/web-testing/planner.md +122 -0
  130. package/docs/web-testing/rerun.md +164 -0
  131. package/docs/web-testing/researcher.md +380 -0
  132. package/docs/workflow/agentic-usage.md +233 -0
  133. package/docs/workflow/application-spec.md +73 -0
  134. package/docs/workflow/ci.md +202 -0
  135. package/docs/workflow/knowledge.md +310 -0
  136. package/docs/workflow/planning-styles.md +67 -0
  137. package/docs/workflow/reporting.md +133 -0
  138. package/docs/workflow/test-plans.md +90 -0
  139. package/package.json +3 -2
  140. package/rules/navigator/verification-actions.md +20 -0
  141. package/src/action-result.ts +11 -0
  142. package/src/action.ts +43 -3
  143. package/src/ai/captain/web-mode.ts +6 -3
  144. package/src/ai/captain.ts +3 -0
  145. package/src/ai/navigator.ts +255 -186
  146. package/src/ai/pilot.ts +104 -2
  147. package/src/ai/provider.ts +14 -24
  148. package/src/ai/rerunner.ts +2 -1
  149. package/src/ai/researcher/cache.ts +12 -2
  150. package/src/ai/researcher.ts +3 -2
  151. package/src/ai/rules.ts +17 -10
  152. package/src/ai/session-analyst.ts +2 -0
  153. package/src/ai/task-agent.ts +3 -1
  154. package/src/ai/tester.ts +52 -45
  155. package/src/ai/tools.ts +136 -37
  156. package/src/commands/config-command.ts +146 -0
  157. package/src/commands/index.ts +2 -0
  158. package/src/config.ts +60 -5
  159. package/src/execution-controller.ts +8 -0
  160. package/src/explorbot.ts +7 -3
  161. package/src/explorer.ts +2 -2
  162. package/src/playwright-recorder.ts +23 -0
  163. package/src/remote.ts +244 -0
  164. package/src/reporter.ts +7 -1
  165. package/src/state-manager.ts +6 -2
  166. package/src/stats.ts +1 -0
  167. package/src/test-plan.ts +29 -0
  168. package/src/utils/aria.ts +65 -45
  169. package/src/utils/html.ts +1 -0
  170. package/src/utils/logger.ts +33 -2
  171. package/src/utils/page-readiness.ts +24 -1
  172. package/src/utils/url-matcher.ts +3 -0
  173. package/src/utils/web-element.ts +9 -0
  174. package/src/utils/web-sandbox.ts +3 -4
package/README.md CHANGED
@@ -138,7 +138,7 @@ EXPLORBOT_KNOWLEDGE="Log in as admin@example.com / secret123" \
138
138
  npx explorbot explore /admin/users --max-tests 3
139
139
  ```
140
140
 
141
- Output lands in a per-host state directory, `~/.explorbot/state/<host>/`, so runs against the same app collect in one place and nothing is written to your project. Set `EXPLORBOT_EPHEMERAL=1` to keep nothing between runs. See [Agentic Usage](docs/workflow/agentic-usage.md).
141
+ Output lands in a per-host site directory, `~/.explorbot/sites/<host>/`, so runs against the same app collect in one place and nothing is written to your project. Set `EXPLORBOT_EPHEMERAL=1` to keep nothing between runs. See [Agentic Usage](docs/workflow/agentic-usage.md).
142
142
 
143
143
  ## Teaching Explorbot
144
144
 
@@ -11,10 +11,11 @@ import { App } from '../src/components/App.js';
11
11
  import { StatusPane } from '../src/components/StatusPane.js';
12
12
  import { ConfigParser, EXPLORBOT_ENV_VARS, PROVIDERS } from '../src/config.js';
13
13
  import { ExplorBot, type ExplorBotOptions } from '../src/explorbot.js';
14
+ import { remote } from '../src/remote.js';
14
15
  import { Stats } from '../src/stats.js';
15
16
  import { Plan } from '../src/test-plan.js';
16
17
  import { getCliName } from '../src/utils/cli-name.ts';
17
- import { log, setPreserveConsoleLogs } from '../src/utils/logger.js';
18
+ import { isVerboseMode, log, setPreserveConsoleLogs, setQuietMode } from '../src/utils/logger.js';
18
19
  import { jsonToTable } from '../src/utils/markdown-parser.js';
19
20
  import { parseMarkdownToTerminal } from '../src/utils/markdown-terminal.js';
20
21
  import { type NextStepSection, printNextSteps, relativeToCwd } from '../src/utils/next-steps.ts';
@@ -26,8 +27,9 @@ const pkgPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../p
26
27
  const pkgVersion = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')).version as string;
27
28
 
28
29
  program.name(cli).description('AI-powered web exploration tool').version(pkgVersion, '-V, --version');
30
+ remote.registerOption(program);
29
31
 
30
- if (!process.env.EXPLORBOT_NO_BANNER) {
32
+ if (!process.env.EXPLORBOT_NO_BANNER && !process.argv.includes('prima')) {
31
33
  console.log(`⛵ ${chalk.yellow.bold(`Explorbot v${pkgVersion}`)} ${chalk.dim('Autonomous Testing Agent')}`);
32
34
  }
33
35
 
@@ -99,6 +101,10 @@ async function startTUI(explorBot: ExplorBot): Promise<void> {
99
101
  }
100
102
 
101
103
  async function showStatsAndExit(code: number): Promise<never> {
104
+ if (remote.isAttached()) {
105
+ await remote.close(code);
106
+ process.exit(code);
107
+ }
102
108
  if (Stats.hasActivity()) {
103
109
  await new Promise<void>((resolve) => {
104
110
  const { unmount } = render(
@@ -430,6 +436,23 @@ addCommonOptions(
430
436
  await cmd.execute(args);
431
437
  });
432
438
 
439
+ program
440
+ .command('config [url]')
441
+ .description('Show models, config file and paths used by this run')
442
+ .option('-c, --config <path>', 'Path to configuration file')
443
+ .option('-p, --path <path>', 'Working directory path')
444
+ .option('--json', 'Print the resolved config as JSON')
445
+ .action(async (url, options) => {
446
+ setQuietMode(!isVerboseMode());
447
+ const { ConfigCommand } = await import('../src/commands/config-command.js');
448
+ try {
449
+ console.log(await ConfigCommand.summary({ config: options.config, path: options.path, url, json: options.json }));
450
+ } catch (error) {
451
+ console.error(error instanceof Error ? error.message : 'Unknown error');
452
+ process.exit(1);
453
+ }
454
+ });
455
+
433
456
  program
434
457
  .command('init')
435
458
  .description('Initialize configuration for a project or for this machine')
@@ -906,11 +929,6 @@ ${rows}
906
929
  `;
907
930
  };
908
931
 
909
- const addEnvHelp = (cmd: Command) => {
910
- cmd.addHelpText('after', envHelp);
911
- for (const sub of cmd.commands) addEnvHelp(sub);
912
- };
913
-
914
- addEnvHelp(program);
932
+ program.addHelpText('after', envHelp);
915
933
 
916
934
  program.parse();
@@ -1,9 +1,12 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { Command } from 'commander';
4
+ import { ConfigCommand } from '../../../src/commands/config-command.ts';
5
+ import { listSites } from '../../../src/global-config.ts';
4
6
  import { setPreserveConsoleLogs } from '../../../src/utils/logger.ts';
5
7
  import { getStyles } from './ai/chief/styles.ts';
6
8
  import { ApiBot, type ApibotOptions } from './apibot.ts';
9
+ import { ApibotConfigParser } from './config.ts';
7
10
 
8
11
  function buildOptions(options: any): ApibotOptions {
9
12
  return {
@@ -82,6 +85,20 @@ export function createApiCommands(name = 'api'): Command {
82
85
  }
83
86
  });
84
87
 
88
+ addCommonOptions(cmd.command('config [endpoint]').description('Show models, config file and paths used by this run'))
89
+ .option('--json', 'Print the resolved config as JSON')
90
+ .action(async (endpoint, options) => {
91
+ const parser = ApibotConfigParser.getInstance();
92
+ const [site] = listSites();
93
+ try {
94
+ const config = await parser.loadConfig({ config: options.config, path: options.path, endpoint: endpoint || site?.url });
95
+ console.log(ConfigCommand.render(config, { configPath: parser.getConfigPath(), root: parser.getProjectRoot(), json: options.json }));
96
+ } catch (error) {
97
+ console.error(error instanceof Error ? error.message : 'Unknown error');
98
+ process.exit(1);
99
+ }
100
+ });
101
+
85
102
  addCommonOptions(cmd.command('test <planfile> [index]').description('Execute tests from a plan file. Index: 1, 1-3, *')).action(async (planfile, index, options) => {
86
103
  setPreserveConsoleLogs(true);
87
104
  try {
@@ -1,5 +1,6 @@
1
1
  import { existsSync, mkdirSync, readFileSync } from 'node:fs';
2
2
  import path, { resolve } from 'node:path';
3
+ import { pathToFileURL } from 'node:url';
3
4
  import { parseEnv } from 'node:util';
4
5
  import { type AIConfig, type ApiHookFn, type ApiConfig as BaseApiConfig, ConfigMissingError, EXPLORBOT_CONFIG_PATHS, createModel, envConfigRequested, materializeKnowledge, missingConfigMessage, resolveConfigModels, resolveModel, resolveOutputRoot } from '../../../src/config.ts';
5
6
  import { type SiteRecord, findGlobalConfig, globalEnvPath, isGlobalConfigPath, registerSite, resolveSiteTarget } from '../../../src/global-config.ts';
@@ -213,10 +214,11 @@ export class ApibotConfigParser {
213
214
 
214
215
  private async loadConfigModule(configPath: string): Promise<any> {
215
216
  const ext = configPath.split('.').pop();
217
+ const moduleUrl = pathToFileURL(resolve(configPath)).href;
216
218
 
217
219
  if (ext === 'ts') {
218
220
  try {
219
- return await import(configPath);
221
+ return await import(moduleUrl);
220
222
  } catch {
221
223
  const require = (await import('node:module')).createRequire(import.meta.url);
222
224
  return require(configPath);
@@ -224,7 +226,7 @@ export class ApibotConfigParser {
224
226
  }
225
227
 
226
228
  if (ext === 'js' || ext === 'mjs') {
227
- return await import(configPath);
229
+ return await import(moduleUrl);
228
230
  }
229
231
 
230
232
  const content = readFileSync(configPath, 'utf8');
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env bun
2
+ import { remote } from '../../../src/remote.ts';
2
3
  import { createDocsCommands } from '../src/cli.ts';
3
4
 
4
5
  const program = createDocsCommands('doc-collector');
6
+ remote.registerOption(program);
5
7
  program.parse();
@@ -67,17 +67,32 @@ class Documentarian {
67
67
  const message = error instanceof Error ? error.message : String(error);
68
68
  tag('warning').log(`Interactive documentation failed: ${message}.`);
69
69
  if (meaningfulInteractions.length > 0) {
70
- tag('info').log(`Preserving ${meaningfulInteractions.length} observed interaction(s) without AI summary.`);
71
- return this.normalizeDocumentation(
72
- {
73
- summary: `Observed ${meaningfulInteractions.length} interaction(s); AI-generated summary was unavailable.`,
74
- can: [],
75
- might: [],
76
- interactions: meaningfulInteractions,
77
- },
78
- state,
79
- research
80
- );
70
+ tag('info').log(`Retrying static documentation while preserving ${meaningfulInteractions.length} observed interaction(s).`);
71
+ return this.documentStatic(state, research)
72
+ .then((documentation) =>
73
+ this.normalizeDocumentation(
74
+ {
75
+ ...documentation,
76
+ interactions: meaningfulInteractions,
77
+ },
78
+ state,
79
+ research
80
+ )
81
+ )
82
+ .catch((fallbackError) => {
83
+ const fallbackMessage = fallbackError instanceof Error ? fallbackError.message : String(fallbackError);
84
+ tag('warning').log(`Static documentation fallback failed: ${fallbackMessage}. Preserving observed interactions without AI summary.`);
85
+ return this.normalizeDocumentation(
86
+ {
87
+ summary: `Observed ${meaningfulInteractions.length} interaction(s); AI-generated summary was unavailable.`,
88
+ can: [],
89
+ might: [],
90
+ interactions: meaningfulInteractions,
91
+ },
92
+ state,
93
+ research
94
+ );
95
+ });
81
96
  }
82
97
  return this.documentStatic(state, research);
83
98
  }
@@ -113,13 +128,13 @@ class Documentarian {
113
128
  },
114
129
  ];
115
130
 
116
- const response = await this.provider.generateObject(messages, pageDocumentationSchema, undefined, {
131
+ const response = await this.provider.generateObject(messages, generatedPageDocumentationSchema, undefined, {
117
132
  agentName: 'documentarian',
118
133
  });
119
134
 
120
135
  return this.normalizeDocumentation(
121
136
  {
122
- ...(response.object as PageDocumentation),
137
+ ...(response.object as GeneratedPageDocumentation),
123
138
  interactions,
124
139
  },
125
140
  state,
@@ -139,11 +154,11 @@ class Documentarian {
139
154
  },
140
155
  ];
141
156
 
142
- const response = await this.provider.generateObject(messages, pageDocumentationSchema, undefined, {
157
+ const response = await this.provider.generateObject(messages, generatedPageDocumentationSchema, undefined, {
143
158
  agentName: 'documentarian',
144
159
  });
145
160
 
146
- return this.normalizeDocumentation(response.object as PageDocumentation, state, research);
161
+ return this.normalizeDocumentation(response.object as GeneratedPageDocumentation, state, research);
147
162
  }
148
163
 
149
164
  private getSystemPrompt(): string {
@@ -259,7 +274,7 @@ class Documentarian {
259
274
  return message.includes('Failed to generate JSON') || message.includes('Failed to validate JSON') || message.includes('failed_generation') || message.includes('No object generated') || message.includes('response did not match schema');
260
275
  }
261
276
 
262
- private normalizeDocumentation(documentation: PageDocumentation, _state: WebPageState, _research: string): PageDocumentation {
277
+ private normalizeDocumentation(documentation: GeneratedPageDocumentation & Partial<Pick<PageDocumentation, 'interactions'>>, _state: WebPageState, _research: string): PageDocumentation {
263
278
  const normalized = { ...documentation };
264
279
  if (!normalized.interactions) {
265
280
  normalized.interactions = undefined;
@@ -267,10 +282,10 @@ class Documentarian {
267
282
 
268
283
  const qualityNotes = this.evaluateDocumentationQuality(normalized);
269
284
 
270
- return {
285
+ return pageDocumentationSchema.parse({
271
286
  ...normalized,
272
287
  qualityNotes,
273
- };
288
+ });
274
289
  }
275
290
 
276
291
  private evaluateDocumentationQuality(documentation: PageDocumentation): string[] {
@@ -346,39 +361,54 @@ const stateTransitionSchema = z.object({
346
361
  action: z.string(),
347
362
  before: z.string(),
348
363
  after: z.string(),
349
- targetUrl: z.string().nullable(),
350
- discoveredUrls: z.array(z.string()).nullable(),
351
- newCapabilities: z.array(z.string()).nullable(),
364
+ targetUrl: z.string().optional(),
365
+ discoveredUrls: z.array(z.string()).optional(),
366
+ newCapabilities: z.array(z.string()).optional(),
352
367
  element: z
353
368
  .object({
354
369
  role: z.string(),
355
370
  name: z.string(),
356
371
  section: z.string(),
357
- container: z.string().nullable(),
358
- locator: z.string().nullable(),
372
+ container: z.string().optional(),
373
+ locator: z.string().optional(),
359
374
  })
360
- .nullable(),
375
+ .optional(),
361
376
  changes: z
362
377
  .object({
363
378
  urlChanged: z.boolean(),
364
379
  newElements: z.number(),
365
380
  removedElements: z.number(),
366
381
  })
367
- .nullable(),
382
+ .optional(),
383
+ targetState: z
384
+ .object({
385
+ kind: z.enum(['page', 'dialog', 'modal', 'section']),
386
+ label: z.string(),
387
+ url: z.string(),
388
+ })
389
+ .optional(),
390
+ screenshot: z
391
+ .object({
392
+ title: z.string(),
393
+ relativePath: z.string(),
394
+ })
395
+ .optional(),
368
396
  });
369
397
 
370
- const pageDocumentationSchema = z.object({
398
+ const generatedPageDocumentationSchema = z.object({
371
399
  summary: z.string(),
372
400
  can: z.array(capabilitySchema),
373
401
  might: z.array(capabilitySchema),
374
- interactions: z.array(stateTransitionSchema).nullable(),
402
+ });
403
+
404
+ const pageDocumentationSchema = generatedPageDocumentationSchema.extend({
405
+ interactions: z.array(stateTransitionSchema).optional(),
406
+ qualityNotes: z.array(z.string()).optional(),
375
407
  });
376
408
 
377
409
  type StateTransition = DocStateTransition;
378
- type PageDocumentation = Omit<z.infer<typeof pageDocumentationSchema>, 'interactions'> & {
379
- interactions?: StateTransition[];
380
- qualityNotes?: string[];
381
- };
410
+ type GeneratedPageDocumentation = z.infer<typeof generatedPageDocumentationSchema>;
411
+ type PageDocumentation = z.infer<typeof pageDocumentationSchema>;
382
412
 
383
413
  export { Documentarian };
384
414
  export type { PageDocumentation, StateTransition };
@@ -1,7 +1,8 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { Command } from 'commander';
4
- import { setPreserveConsoleLogs } from '../../../src/utils/logger.ts';
4
+ import { ConfigCommand } from '../../../src/commands/config-command.ts';
5
+ import { isVerboseMode, setPreserveConsoleLogs, setQuietMode } from '../../../src/utils/logger.ts';
5
6
  import { DocBot, type DocbotOptions } from './docbot.ts';
6
7
 
7
8
  function buildOptions(options: any): DocbotOptions {
@@ -65,6 +66,18 @@ export function createDocsCommands(name = 'docs'): Command {
65
66
  }
66
67
  });
67
68
 
69
+ addCommonOptions(cmd.command('config [url]').description('Show models, config file and paths used by this run'))
70
+ .option('--json', 'Print the resolved config as JSON')
71
+ .action(async (url, options) => {
72
+ setQuietMode(!isVerboseMode());
73
+ try {
74
+ console.log(await ConfigCommand.summary({ config: options.config, path: options.path, url, json: options.json }));
75
+ } catch (error) {
76
+ console.error(error instanceof Error ? error.message : 'Unknown error');
77
+ process.exit(1);
78
+ }
79
+ });
80
+
68
81
  cmd
69
82
  .command('init')
70
83
  .description('Initialize doc collector configuration')
@@ -1,5 +1,6 @@
1
1
  import { existsSync, readFileSync } from 'node:fs';
2
2
  import path, { resolve } from 'node:path';
3
+ import { pathToFileURL } from 'node:url';
3
4
  import { parseEnv } from 'node:util';
4
5
  import { ConfigParser } from '../../../src/config.ts';
5
6
 
@@ -90,10 +91,11 @@ class DocbotConfigParser {
90
91
 
91
92
  private async loadConfigModule(configPath: string): Promise<any> {
92
93
  const ext = configPath.split('.').pop();
94
+ const moduleUrl = pathToFileURL(resolve(configPath)).href;
93
95
 
94
96
  if (ext === 'ts') {
95
97
  try {
96
- return await import(configPath);
98
+ return await import(moduleUrl);
97
99
  } catch {
98
100
  const require = (await import('node:module')).createRequire(import.meta.url);
99
101
  return require(configPath);
@@ -101,7 +103,7 @@ class DocbotConfigParser {
101
103
  }
102
104
 
103
105
  if (ext === 'js' || ext === 'mjs') {
104
- return await import(configPath);
106
+ return await import(moduleUrl);
105
107
  }
106
108
 
107
109
  return JSON.parse(readFileSync(configPath, 'utf8'));
File without changes
@@ -0,0 +1,33 @@
1
+ import chalk from 'chalk';
2
+ import { type ActivityEntry, addActivityListener, removeActivityListener } from '../../../src/activity.ts';
3
+ import { isVerboseMode } from '../../../src/utils/logger.ts';
4
+
5
+ const RESET_LINE = '\r\u001b[2K';
6
+
7
+ const stream = process.stderr;
8
+ let tracking = false;
9
+
10
+ export function trackActivityLine(): void {
11
+ if (tracking) return;
12
+ if (!stream.isTTY) return;
13
+ if (isVerboseMode()) return;
14
+
15
+ tracking = true;
16
+ addActivityListener(writeActivityLine);
17
+ }
18
+
19
+ export function clearActivityLine(): void {
20
+ if (!tracking) return;
21
+
22
+ tracking = false;
23
+ removeActivityListener(writeActivityLine);
24
+ stream.write(RESET_LINE);
25
+ }
26
+
27
+ function writeActivityLine(activity: ActivityEntry | null): void {
28
+ if (!activity) return;
29
+
30
+ const width = (stream.columns || 80) - 2;
31
+ const message = Array.from(activity.message.replace(/\s+/g, ' ').trim()).slice(0, width).join('');
32
+ stream.write(`${RESET_LINE}${chalk.gray(message)}`);
33
+ }