explorbot 0.2.3 → 0.2.4

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 (156) hide show
  1. package/README.md +1 -1
  2. package/bin/explorbot-cli.ts +7 -1
  3. package/boat/api-tester/src/config.ts +4 -2
  4. package/boat/doc-collector/bin/doc-collector-cli.ts +2 -0
  5. package/boat/doc-collector/src/ai/documentarian.ts +61 -31
  6. package/boat/doc-collector/src/config.ts +4 -2
  7. package/boat/prima/bin/prima-cli.ts +0 -0
  8. package/boat/prima/src/activity-line.ts +33 -0
  9. package/boat/prima/src/cli.ts +115 -86
  10. package/boat/prima/src/envelope.ts +80 -52
  11. package/boat/prima/src/prima.ts +547 -128
  12. package/boat/prima/src/pw-parser.ts +11 -1
  13. package/boat/prima/src/pw-registry.ts +4 -5
  14. package/boat/prima/src/session-log.ts +126 -0
  15. package/dist/bin/explorbot-cli.js +7 -1
  16. package/dist/boat/api-tester/bin/apibot-cli.js +2 -0
  17. package/dist/boat/api-tester/src/config.js +4 -2
  18. package/dist/boat/doc-collector/bin/doc-collector-cli.js +2 -0
  19. package/dist/boat/doc-collector/src/ai/documentarian.js +44 -19
  20. package/dist/boat/doc-collector/src/config.js +4 -2
  21. package/dist/boat/prima/src/activity-line.js +30 -0
  22. package/dist/boat/prima/src/cli.js +97 -77
  23. package/dist/boat/prima/src/envelope.js +70 -44
  24. package/dist/boat/prima/src/prima.js +515 -119
  25. package/dist/boat/prima/src/pw-parser.js +13 -1
  26. package/dist/boat/prima/src/pw-registry.js +4 -5
  27. package/dist/boat/prima/src/session-log.js +108 -0
  28. package/dist/package.json +3 -2
  29. package/dist/rules/navigator/verification-actions.md +20 -0
  30. package/dist/src/action-result.d.ts +7 -0
  31. package/dist/src/action-result.js +4 -0
  32. package/dist/src/action.d.ts +2 -0
  33. package/dist/src/action.js +36 -1
  34. package/dist/src/ai/captain/web-mode.js +6 -3
  35. package/dist/src/ai/captain.js +2 -0
  36. package/dist/src/ai/navigator.d.ts +7 -0
  37. package/dist/src/ai/navigator.js +10 -6
  38. package/dist/src/ai/pilot.d.ts +4 -0
  39. package/dist/src/ai/pilot.js +48 -2
  40. package/dist/src/ai/provider.js +2 -2
  41. package/dist/src/ai/rerunner.js +2 -1
  42. package/dist/src/ai/researcher.js +1 -1
  43. package/dist/src/ai/rules.js +17 -10
  44. package/dist/src/ai/task-agent.js +4 -1
  45. package/dist/src/ai/tester.d.ts +1 -1
  46. package/dist/src/ai/tester.js +33 -33
  47. package/dist/src/ai/tools.d.ts +14 -0
  48. package/dist/src/ai/tools.js +113 -36
  49. package/dist/src/config.d.ts +1 -0
  50. package/dist/src/config.js +13 -4
  51. package/dist/src/execution-controller.d.ts +2 -0
  52. package/dist/src/execution-controller.js +6 -0
  53. package/dist/src/explorbot.d.ts +2 -1
  54. package/dist/src/explorbot.js +3 -1
  55. package/dist/src/explorer.js +2 -3
  56. package/dist/src/playwright-recorder.js +30 -0
  57. package/dist/src/remote.d.ts +54 -0
  58. package/dist/src/remote.js +229 -0
  59. package/dist/src/reporter.d.ts +1 -0
  60. package/dist/src/reporter.js +7 -1
  61. package/dist/src/state-manager.d.ts +2 -1
  62. package/dist/src/stats.d.ts +1 -0
  63. package/dist/src/stats.js +1 -0
  64. package/dist/src/test-plan.d.ts +2 -0
  65. package/dist/src/test-plan.js +7 -0
  66. package/dist/src/utils/aria.d.ts +2 -8
  67. package/dist/src/utils/aria.js +69 -40
  68. package/dist/src/utils/html.js +1 -0
  69. package/dist/src/utils/logger.d.ts +6 -0
  70. package/dist/src/utils/logger.js +24 -0
  71. package/dist/src/utils/page-readiness.js +18 -1
  72. package/dist/src/utils/url-matcher.js +3 -0
  73. package/dist/src/utils/web-element.d.ts +2 -0
  74. package/dist/src/utils/web-element.js +8 -0
  75. package/dist/src/utils/web-sandbox.d.ts +1 -1
  76. package/dist/src/utils/web-sandbox.js +2 -3
  77. package/docs/api-testing/basics.md +90 -0
  78. package/docs/api-testing/planning.md +57 -0
  79. package/docs/api-testing/running-tests.md +55 -0
  80. package/docs/assets/cloud-report.png +0 -0
  81. package/docs/assets/html-report.png +0 -0
  82. package/docs/assets/langfuse-trace.png +0 -0
  83. package/docs/assets/successful-explore-run.png +0 -0
  84. package/docs/basics/getting-started.md +140 -0
  85. package/docs/basics/prerequisites.md +63 -0
  86. package/docs/basics/providers.md +362 -0
  87. package/docs/basics/running.md +78 -0
  88. package/docs/contributing/ai-integration-tests.md +57 -0
  89. package/docs/contributing/contributing.md +90 -0
  90. package/docs/contributing/demo-videos.md +36 -0
  91. package/docs/contributing/npm-package.md +138 -0
  92. package/docs/contributing/observability.md +227 -0
  93. package/docs/contributing/regression-tests.md +103 -0
  94. package/docs/contributing/testing.md +95 -0
  95. package/docs/doc-collection/basics.md +128 -0
  96. package/docs/doc-collection/crawling.md +67 -0
  97. package/docs/doc-collection/interactive-mode.md +99 -0
  98. package/docs/index.json +86 -0
  99. package/docs/reference/commands.md +994 -0
  100. package/docs/reference/configuration.md +569 -0
  101. package/docs/reference/scripting.md +303 -0
  102. package/docs/superpowers/plans/2026-08-01-actor-boat.md +925 -0
  103. package/docs/superpowers/plans/2026-08-01-prima-boat.md +1120 -0
  104. package/docs/superpowers/reviews/2026-08-06-prima-vs-playwright-cli.md +268 -0
  105. package/docs/superpowers/specs/2026-08-01-actor-boat-design.md +204 -0
  106. package/docs/superpowers/specs/2026-08-01-prima-boat-design.md +242 -0
  107. package/docs/superpowers/specs/2026-08-03-global-config-design.md +138 -0
  108. package/docs/superpowers/specs/2026-08-07-prima-fixes-design.md +394 -0
  109. package/docs/web-testing/agents.md +158 -0
  110. package/docs/web-testing/automated-tests.md +134 -0
  111. package/docs/web-testing/basics.md +91 -0
  112. package/docs/web-testing/customization.md +131 -0
  113. package/docs/web-testing/hooks.md +238 -0
  114. package/docs/web-testing/page-interaction.md +84 -0
  115. package/docs/web-testing/planner.md +122 -0
  116. package/docs/web-testing/rerun.md +164 -0
  117. package/docs/web-testing/researcher.md +380 -0
  118. package/docs/workflow/agentic-usage.md +233 -0
  119. package/docs/workflow/application-spec.md +73 -0
  120. package/docs/workflow/ci.md +202 -0
  121. package/docs/workflow/knowledge.md +310 -0
  122. package/docs/workflow/planning-styles.md +67 -0
  123. package/docs/workflow/reporting.md +133 -0
  124. package/docs/workflow/test-plans.md +90 -0
  125. package/package.json +3 -2
  126. package/rules/navigator/verification-actions.md +20 -0
  127. package/src/action-result.ts +11 -0
  128. package/src/action.ts +38 -2
  129. package/src/ai/captain/web-mode.ts +6 -3
  130. package/src/ai/captain.ts +3 -0
  131. package/src/ai/navigator.ts +14 -8
  132. package/src/ai/pilot.ts +53 -2
  133. package/src/ai/provider.ts +2 -4
  134. package/src/ai/rerunner.ts +2 -1
  135. package/src/ai/researcher.ts +1 -1
  136. package/src/ai/rules.ts +17 -10
  137. package/src/ai/task-agent.ts +3 -1
  138. package/src/ai/tester.ts +32 -33
  139. package/src/ai/tools.ts +132 -36
  140. package/src/config.ts +15 -4
  141. package/src/execution-controller.ts +8 -0
  142. package/src/explorbot.ts +4 -2
  143. package/src/explorer.ts +2 -2
  144. package/src/playwright-recorder.ts +23 -0
  145. package/src/remote.ts +238 -0
  146. package/src/reporter.ts +7 -1
  147. package/src/state-manager.ts +2 -1
  148. package/src/stats.ts +1 -0
  149. package/src/test-plan.ts +9 -0
  150. package/src/utils/aria.ts +65 -45
  151. package/src/utils/html.ts +1 -0
  152. package/src/utils/logger.ts +24 -1
  153. package/src/utils/page-readiness.ts +24 -1
  154. package/src/utils/url-matcher.ts +3 -0
  155. package/src/utils/web-element.ts +9 -0
  156. package/src/utils/web-sandbox.ts +3 -4
@@ -13,5 +13,15 @@ export function isFunctionExpression(expr: string): { valid: boolean; error?: st
13
13
  }
14
14
 
15
15
  export function toCodeceptWrapper(expr: string): string {
16
- return `I.usePlaywrightTo('pw', async (playwright) => (${expr.trim()})(playwright))`;
16
+ return `return I.usePlaywrightTo('pw', async (playwright) => (${expr.trim()})(playwright))`;
17
+ }
18
+
19
+ export function takePwValue(held: unknown): string | undefined {
20
+ if (held === undefined || held === null) return undefined;
21
+ if (typeof held === 'string') return held;
22
+ try {
23
+ return JSON.stringify(held, null, 2);
24
+ } catch {
25
+ return String(held);
26
+ }
17
27
  }
@@ -20,9 +20,8 @@ export function readDescriptors(dir = registryDir()): PwServerDescriptor[] {
20
20
  return descriptors;
21
21
  }
22
22
 
23
- export function selectDescriptor(descriptors: PwServerDescriptor[], opts: { workspaceDir: string; title?: string }): { match?: PwServerDescriptor; candidates: PwServerDescriptor[] } {
24
- const workspaceDir = path.resolve(opts.workspaceDir);
25
- const candidates = descriptors.filter((descriptor) => path.resolve(descriptor.workspaceDir) === workspaceDir);
23
+ export function selectDescriptor(descriptors: PwServerDescriptor[], opts: { title?: string } = {}): { match?: PwServerDescriptor; candidates: PwServerDescriptor[] } {
24
+ const candidates = descriptors;
26
25
  if (!candidates.length) return { candidates };
27
26
 
28
27
  if (opts.title) {
@@ -53,13 +52,13 @@ function parseDescriptor(file: string): PwServerDescriptor | null {
53
52
  return null;
54
53
  }
55
54
 
56
- if (!data?.endpoint || !data?.title || !data?.workspaceDir) return null;
55
+ if (!data?.endpoint || !data?.title) return null;
57
56
 
58
57
  return {
59
58
  file,
60
59
  title: data.title,
61
60
  endpoint: data.endpoint,
62
- workspaceDir: data.workspaceDir,
61
+ workspaceDir: data.workspaceDir || '',
63
62
  browserName: data.browser?.browserName || DEFAULT_BROWSER,
64
63
  playwrightLib: data.playwrightLib || '',
65
64
  };
@@ -0,0 +1,126 @@
1
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { outputPath } from '../../../src/config.ts';
4
+ import { safeFilename } from '../../../src/utils/strings.ts';
5
+ import { type EnvelopeData, renderEnvelope } from './envelope.ts';
6
+
7
+ const EXPECTATION_STATUS = { passed: 'passed', failed: 'failed', unverified: 'skipped' };
8
+
9
+ export function sessionsDir(): string {
10
+ return outputPath('prima', 'sessions');
11
+ }
12
+
13
+ export function sessionFile(key: string): string {
14
+ return path.join(sessionsDir(), `${safeFilename(key)}.jsonl`);
15
+ }
16
+
17
+ export function latestSessionFile(): string | null {
18
+ const dir = sessionsDir();
19
+ if (!existsSync(dir)) return null;
20
+
21
+ const files = readdirSync(dir)
22
+ .filter((name) => name.endsWith('.jsonl'))
23
+ .map((name) => path.join(dir, name))
24
+ .sort((a, b) => statSync(b).mtimeMs - statSync(a).mtimeMs);
25
+
26
+ return files[0] || null;
27
+ }
28
+
29
+ export function recordCommand(file: string, session: SessionRun, envelope: EnvelopeData, durationMs: number): void {
30
+ mkdirSync(path.dirname(file), { recursive: true });
31
+ if (recordedEndpoint(file) !== session.endpoint) writeFileSync(file, jsonLine({ action: 'createRun', endpoint: session.endpoint, params: { title: session.title } }), 'utf-8');
32
+ appendFileSync(file, jsonLine({ action: 'addTest', testId: commandTest(envelope, durationMs) }), 'utf-8');
33
+ }
34
+
35
+ export function readSession(file: string): { title: string; tests: any[] } {
36
+ const entries = readEntries(file);
37
+ const run = entries.find((entry) => entry.action === 'createRun');
38
+ const tests = entries.filter((entry) => entry.action === 'addTest').map((entry) => entry.testId);
39
+ return { title: run?.params?.title || 'prima session', tests };
40
+ }
41
+
42
+ function commandTest(envelope: EnvelopeData, durationMs: number): any {
43
+ let status = 'failed';
44
+ if (envelope.ok) status = 'passed';
45
+
46
+ const test: any = {
47
+ rid: envelope.status,
48
+ title: envelope.command,
49
+ suite_title: 'Prima session',
50
+ status,
51
+ file: '<prima>',
52
+ description: [`page: ${envelope.page.url}`, envelope.stepFiles && `artifacts: ${envelope.stepFiles}`].filter(Boolean).join('\n'),
53
+ code: (envelope.used || []).join('\n'),
54
+ steps: commandSteps(envelope),
55
+ logs: renderEnvelope(envelope),
56
+ time: Math.round(durationMs),
57
+ };
58
+
59
+ if (envelope.failure) test.error = { message: envelope.failure.error };
60
+ return test;
61
+ }
62
+
63
+ function commandSteps(envelope: EnvelopeData): any[] {
64
+ const steps: any[] = [];
65
+
66
+ for (const step of envelope.steps || []) {
67
+ const entry: any = { category: 'user', title: step.label, status: 'passed', duration: 0, log: step.proof };
68
+ if (!step.ok) {
69
+ entry.status = 'failed';
70
+ entry.error = step.proof;
71
+ }
72
+ steps.push(entry);
73
+ }
74
+
75
+ for (const expectation of envelope.expectations || []) {
76
+ const entry: any = { category: 'user', title: `expected: ${expectation.text}`, status: EXPECTATION_STATUS[expectation.status], duration: 0 };
77
+ if (expectation.status === 'failed') entry.error = 'the run did not reach this outcome';
78
+ steps.push(entry);
79
+ }
80
+
81
+ for (const assertion of envelope.assertions || []) {
82
+ const entry: any = { category: 'user', title: assertion.code, status: 'passed', duration: 0, log: assertion.proof.join('\n') };
83
+ if (!assertion.passed) {
84
+ entry.status = 'failed';
85
+ entry.error = 'the assertion did not hold on the page';
86
+ }
87
+ steps.push(entry);
88
+ }
89
+
90
+ return steps;
91
+ }
92
+
93
+ function recordedEndpoint(file: string): string | null {
94
+ const [first] = readEntries(file);
95
+ return first?.endpoint || null;
96
+ }
97
+
98
+ function readEntries(file: string): any[] {
99
+ if (!existsSync(file)) return [];
100
+
101
+ const entries: any[] = [];
102
+ for (const line of readFileSync(file, 'utf-8').split('\n')) {
103
+ if (!line.trim()) continue;
104
+ const entry = parseLine(line);
105
+ if (entry) entries.push(entry);
106
+ }
107
+ return entries;
108
+ }
109
+
110
+ function parseLine(line: string): any {
111
+ try {
112
+ return JSON.parse(line);
113
+ } catch {
114
+ return null;
115
+ }
116
+ }
117
+
118
+ function jsonLine(entry: unknown): string {
119
+ return `${JSON.stringify(entry)}\n`;
120
+ }
121
+
122
+ export interface SessionRun {
123
+ key: string;
124
+ endpoint: string;
125
+ title: string;
126
+ }
@@ -11,6 +11,7 @@ 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 } 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.js";
@@ -23,7 +24,8 @@ const cli = getCliName();
23
24
  const pkgPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../package.json');
24
25
  const pkgVersion = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')).version;
25
26
  program.name(cli).description('AI-powered web exploration tool').version(pkgVersion, '-V, --version');
26
- if (!process.env.EXPLORBOT_NO_BANNER) {
27
+ remote.registerOption(program);
28
+ if (!process.env.EXPLORBOT_NO_BANNER && !process.argv.includes('prima')) {
27
29
  console.log(`⛵ ${chalk.yellow.bold(`Explorbot v${pkgVersion}`)} ${chalk.dim('Autonomous Testing Agent')}`);
28
30
  }
29
31
  function buildExplorBotOptions(from, options) {
@@ -74,6 +76,10 @@ async function startTUI(explorBot) {
74
76
  });
75
77
  }
76
78
  async function showStatsAndExit(code) {
79
+ if (remote.isAttached()) {
80
+ await remote.close(code);
81
+ process.exit(code);
82
+ }
77
83
  if (Stats.hasActivity()) {
78
84
  await new Promise((resolve) => {
79
85
  const { unmount } = render(React.createElement(StatusPane, {
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env bun
2
+ import { remote } from "../../../src/remote.js";
2
3
  import { createApiCommands } from "../src/cli.js";
3
4
  const program = createApiCommands('apibot');
5
+ remote.registerOption(program);
4
6
  program.parse();
@@ -8,6 +8,7 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
8
8
  };
9
9
  import { existsSync, mkdirSync, readFileSync } from 'node:fs';
10
10
  import path, { resolve } from 'node:path';
11
+ import { pathToFileURL } from 'node:url';
11
12
  import { parseEnv } from 'node:util';
12
13
  import { ConfigMissingError, EXPLORBOT_CONFIG_PATHS, createModel, envConfigRequested, materializeKnowledge, missingConfigMessage, resolveConfigModels, resolveModel, resolveOutputRoot } from "../../../src/config.js";
13
14
  import { findGlobalConfig, globalEnvPath, isGlobalConfigPath, registerSite, resolveSiteTarget } from "../../../src/global-config.js";
@@ -181,9 +182,10 @@ export class ApibotConfigParser {
181
182
  }
182
183
  async loadConfigModule(configPath) {
183
184
  const ext = configPath.split('.').pop();
185
+ const moduleUrl = pathToFileURL(resolve(configPath)).href;
184
186
  if (ext === 'ts') {
185
187
  try {
186
- return await import(__rewriteRelativeImportExtension(configPath));
188
+ return await import(__rewriteRelativeImportExtension(moduleUrl));
187
189
  }
188
190
  catch {
189
191
  const require = (await import('node:module')).createRequire(import.meta.url);
@@ -191,7 +193,7 @@ export class ApibotConfigParser {
191
193
  }
192
194
  }
193
195
  if (ext === 'js' || ext === 'mjs') {
194
- return await import(__rewriteRelativeImportExtension(configPath));
196
+ return await import(__rewriteRelativeImportExtension(moduleUrl));
195
197
  }
196
198
  const content = readFileSync(configPath, 'utf8');
197
199
  return JSON.parse(content);
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env bun
2
+ import { remote } from "../../../src/remote.js";
2
3
  import { createDocsCommands } from "../src/cli.js";
3
4
  const program = createDocsCommands('doc-collector');
5
+ remote.registerOption(program);
4
6
  program.parse();
@@ -55,13 +55,22 @@ class Documentarian {
55
55
  const message = error instanceof Error ? error.message : String(error);
56
56
  tag('warning').log(`Interactive documentation failed: ${message}.`);
57
57
  if (meaningfulInteractions.length > 0) {
58
- tag('info').log(`Preserving ${meaningfulInteractions.length} observed interaction(s) without AI summary.`);
59
- return this.normalizeDocumentation({
60
- summary: `Observed ${meaningfulInteractions.length} interaction(s); AI-generated summary was unavailable.`,
61
- can: [],
62
- might: [],
58
+ tag('info').log(`Retrying static documentation while preserving ${meaningfulInteractions.length} observed interaction(s).`);
59
+ return this.documentStatic(state, research)
60
+ .then((documentation) => this.normalizeDocumentation({
61
+ ...documentation,
63
62
  interactions: meaningfulInteractions,
64
- }, state, research);
63
+ }, state, research))
64
+ .catch((fallbackError) => {
65
+ const fallbackMessage = fallbackError instanceof Error ? fallbackError.message : String(fallbackError);
66
+ tag('warning').log(`Static documentation fallback failed: ${fallbackMessage}. Preserving observed interactions without AI summary.`);
67
+ return this.normalizeDocumentation({
68
+ summary: `Observed ${meaningfulInteractions.length} interaction(s); AI-generated summary was unavailable.`,
69
+ can: [],
70
+ might: [],
71
+ interactions: meaningfulInteractions,
72
+ }, state, research);
73
+ });
65
74
  }
66
75
  return this.documentStatic(state, research);
67
76
  }
@@ -94,7 +103,7 @@ class Documentarian {
94
103
  content: this.buildPrompt(state, `${research}${this.buildInteractionContext(interactions)}`),
95
104
  },
96
105
  ];
97
- const response = await this.provider.generateObject(messages, pageDocumentationSchema, undefined, {
106
+ const response = await this.provider.generateObject(messages, generatedPageDocumentationSchema, undefined, {
98
107
  agentName: 'documentarian',
99
108
  });
100
109
  return this.normalizeDocumentation({
@@ -113,7 +122,7 @@ class Documentarian {
113
122
  content: this.buildPrompt(state, research, simplified),
114
123
  },
115
124
  ];
116
- const response = await this.provider.generateObject(messages, pageDocumentationSchema, undefined, {
125
+ const response = await this.provider.generateObject(messages, generatedPageDocumentationSchema, undefined, {
117
126
  agentName: 'documentarian',
118
127
  });
119
128
  return this.normalizeDocumentation(response.object, state, research);
@@ -230,10 +239,10 @@ class Documentarian {
230
239
  normalized.interactions = undefined;
231
240
  }
232
241
  const qualityNotes = this.evaluateDocumentationQuality(normalized);
233
- return {
242
+ return pageDocumentationSchema.parse({
234
243
  ...normalized,
235
244
  qualityNotes,
236
- };
245
+ });
237
246
  }
238
247
  evaluateDocumentationQuality(documentation) {
239
248
  const notes = [];
@@ -296,30 +305,46 @@ const stateTransitionSchema = z.object({
296
305
  action: z.string(),
297
306
  before: z.string(),
298
307
  after: z.string(),
299
- targetUrl: z.string().nullable(),
300
- discoveredUrls: z.array(z.string()).nullable(),
301
- newCapabilities: z.array(z.string()).nullable(),
308
+ targetUrl: z.string().optional(),
309
+ discoveredUrls: z.array(z.string()).optional(),
310
+ newCapabilities: z.array(z.string()).optional(),
302
311
  element: z
303
312
  .object({
304
313
  role: z.string(),
305
314
  name: z.string(),
306
315
  section: z.string(),
307
- container: z.string().nullable(),
308
- locator: z.string().nullable(),
316
+ container: z.string().optional(),
317
+ locator: z.string().optional(),
309
318
  })
310
- .nullable(),
319
+ .optional(),
311
320
  changes: z
312
321
  .object({
313
322
  urlChanged: z.boolean(),
314
323
  newElements: z.number(),
315
324
  removedElements: z.number(),
316
325
  })
317
- .nullable(),
326
+ .optional(),
327
+ targetState: z
328
+ .object({
329
+ kind: z.enum(['page', 'dialog', 'modal', 'section']),
330
+ label: z.string(),
331
+ url: z.string(),
332
+ })
333
+ .optional(),
334
+ screenshot: z
335
+ .object({
336
+ title: z.string(),
337
+ relativePath: z.string(),
338
+ })
339
+ .optional(),
318
340
  });
319
- const pageDocumentationSchema = z.object({
341
+ const generatedPageDocumentationSchema = z.object({
320
342
  summary: z.string(),
321
343
  can: z.array(capabilitySchema),
322
344
  might: z.array(capabilitySchema),
323
- interactions: z.array(stateTransitionSchema).nullable(),
345
+ });
346
+ const pageDocumentationSchema = generatedPageDocumentationSchema.extend({
347
+ interactions: z.array(stateTransitionSchema).optional(),
348
+ qualityNotes: z.array(z.string()).optional(),
324
349
  });
325
350
  export { Documentarian };
@@ -8,6 +8,7 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
8
8
  };
9
9
  import { existsSync, readFileSync } from 'node:fs';
10
10
  import path, { resolve } from 'node:path';
11
+ import { pathToFileURL } from 'node:url';
11
12
  import { parseEnv } from 'node:util';
12
13
  import { ConfigParser } from "../../../src/config.js";
13
14
  class DocbotConfigParser {
@@ -84,9 +85,10 @@ class DocbotConfigParser {
84
85
  }
85
86
  async loadConfigModule(configPath) {
86
87
  const ext = configPath.split('.').pop();
88
+ const moduleUrl = pathToFileURL(resolve(configPath)).href;
87
89
  if (ext === 'ts') {
88
90
  try {
89
- return await import(__rewriteRelativeImportExtension(configPath));
91
+ return await import(__rewriteRelativeImportExtension(moduleUrl));
90
92
  }
91
93
  catch {
92
94
  const require = (await import('node:module')).createRequire(import.meta.url);
@@ -94,7 +96,7 @@ class DocbotConfigParser {
94
96
  }
95
97
  }
96
98
  if (ext === 'js' || ext === 'mjs') {
97
- return await import(__rewriteRelativeImportExtension(configPath));
99
+ return await import(__rewriteRelativeImportExtension(moduleUrl));
98
100
  }
99
101
  return JSON.parse(readFileSync(configPath, 'utf8'));
100
102
  }
@@ -0,0 +1,30 @@
1
+ import chalk from 'chalk';
2
+ import { addActivityListener, removeActivityListener } from "../../../src/activity.js";
3
+ import { isVerboseMode } from "../../../src/utils/logger.js";
4
+ const RESET_LINE = '\r\u001b[2K';
5
+ const stream = process.stderr;
6
+ let tracking = false;
7
+ export function trackActivityLine() {
8
+ if (tracking)
9
+ return;
10
+ if (!stream.isTTY)
11
+ return;
12
+ if (isVerboseMode())
13
+ return;
14
+ tracking = true;
15
+ addActivityListener(writeActivityLine);
16
+ }
17
+ export function clearActivityLine() {
18
+ if (!tracking)
19
+ return;
20
+ tracking = false;
21
+ removeActivityListener(writeActivityLine);
22
+ stream.write(RESET_LINE);
23
+ }
24
+ function writeActivityLine(activity) {
25
+ if (!activity)
26
+ return;
27
+ const width = (stream.columns || 80) - 2;
28
+ const message = Array.from(activity.message.replace(/\s+/g, ' ').trim()).slice(0, width).join('');
29
+ stream.write(`${RESET_LINE}${chalk.gray(message)}`);
30
+ }