explorbot 0.2.2 → 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 (214) hide show
  1. package/README.md +1 -1
  2. package/bin/explorbot-cli.ts +59 -38
  3. package/boat/api-tester/src/apibot.ts +4 -2
  4. package/boat/api-tester/src/cli.ts +2 -2
  5. package/boat/api-tester/src/config.ts +43 -10
  6. package/boat/doc-collector/bin/doc-collector-cli.ts +2 -0
  7. package/boat/doc-collector/src/ai/documentarian.ts +61 -31
  8. package/boat/doc-collector/src/cli.ts +1 -0
  9. package/boat/doc-collector/src/config.ts +4 -2
  10. package/boat/doc-collector/src/docs-renderer.ts +18 -4
  11. package/boat/doc-collector/src/state-diagram.ts +61 -14
  12. package/boat/prima/bin/prima-cli.ts +5 -0
  13. package/boat/prima/package.json +16 -0
  14. package/boat/prima/src/activity-line.ts +33 -0
  15. package/boat/prima/src/cli.ts +251 -0
  16. package/boat/prima/src/envelope.ts +169 -0
  17. package/boat/prima/src/prima.ts +1124 -0
  18. package/boat/prima/src/pw-parser.ts +27 -0
  19. package/boat/prima/src/pw-registry.ts +74 -0
  20. package/boat/prima/src/session-log.ts +126 -0
  21. package/dist/bin/explorbot-cli.js +51 -32
  22. package/dist/boat/api-tester/bin/apibot-cli.js +2 -0
  23. package/dist/boat/api-tester/src/apibot.js +3 -2
  24. package/dist/boat/api-tester/src/cli.js +2 -2
  25. package/dist/boat/api-tester/src/config.js +40 -10
  26. package/dist/boat/doc-collector/bin/doc-collector-cli.js +2 -0
  27. package/dist/boat/doc-collector/src/ai/documentarian.js +44 -19
  28. package/dist/boat/doc-collector/src/cli.js +1 -0
  29. package/dist/boat/doc-collector/src/config.js +4 -2
  30. package/dist/boat/doc-collector/src/docs-renderer.js +17 -3
  31. package/dist/boat/doc-collector/src/state-diagram.js +57 -13
  32. package/dist/boat/prima/bin/prima-cli.js +4 -0
  33. package/dist/boat/prima/src/activity-line.js +30 -0
  34. package/dist/boat/prima/src/cli.js +220 -0
  35. package/dist/boat/prima/src/envelope.js +142 -0
  36. package/dist/boat/prima/src/prima.js +1031 -0
  37. package/dist/boat/prima/src/pw-parser.js +30 -0
  38. package/dist/boat/prima/src/pw-registry.js +65 -0
  39. package/dist/boat/prima/src/session-log.js +108 -0
  40. package/dist/models.json +3 -0
  41. package/dist/package.json +8 -3
  42. package/dist/rules/navigator/verification-actions.md +20 -0
  43. package/dist/src/action-result.d.ts +7 -0
  44. package/dist/src/action-result.js +4 -0
  45. package/dist/src/action.d.ts +7 -2
  46. package/dist/src/action.js +41 -6
  47. package/dist/src/ai/captain/mixin.js +3 -4
  48. package/dist/src/ai/captain/web-mode.js +7 -4
  49. package/dist/src/ai/captain.js +2 -0
  50. package/dist/src/ai/navigator.d.ts +11 -0
  51. package/dist/src/ai/navigator.js +21 -12
  52. package/dist/src/ai/pilot.d.ts +4 -0
  53. package/dist/src/ai/pilot.js +48 -2
  54. package/dist/src/ai/planner.d.ts +1 -0
  55. package/dist/src/ai/planner.js +6 -0
  56. package/dist/src/ai/provider.js +2 -2
  57. package/dist/src/ai/rerunner.js +2 -1
  58. package/dist/src/ai/researcher.js +2 -2
  59. package/dist/src/ai/rules.js +17 -10
  60. package/dist/src/ai/task-agent.js +5 -2
  61. package/dist/src/ai/tester.d.ts +1 -0
  62. package/dist/src/ai/tester.js +33 -20
  63. package/dist/src/ai/tools.d.ts +14 -0
  64. package/dist/src/ai/tools.js +113 -36
  65. package/dist/src/application-spec-contract.d.ts +8 -0
  66. package/dist/src/application-spec-contract.js +8 -0
  67. package/dist/src/application-spec.d.ts +15 -0
  68. package/dist/src/application-spec.js +71 -0
  69. package/dist/src/browser-server.d.ts +12 -6
  70. package/dist/src/browser-server.js +74 -19
  71. package/dist/src/commands/clean-command.js +2 -7
  72. package/dist/src/commands/init-command.d.ts +5 -0
  73. package/dist/src/commands/init-command.js +119 -1
  74. package/dist/src/commands/navigate-command.js +1 -1
  75. package/dist/src/commands/research-command.js +1 -1
  76. package/dist/src/commands/sites-command.d.ts +6 -0
  77. package/dist/src/commands/sites-command.js +23 -0
  78. package/dist/src/components/InitWizard.d.ts +10 -0
  79. package/dist/src/components/InitWizard.js +133 -0
  80. package/dist/src/components/InputReadline.d.ts +1 -0
  81. package/dist/src/components/InputReadline.js +7 -4
  82. package/dist/src/config.d.ts +25 -5
  83. package/dist/src/config.js +158 -40
  84. package/dist/src/execution-controller.d.ts +2 -0
  85. package/dist/src/execution-controller.js +6 -0
  86. package/dist/src/explorbot.d.ts +11 -1
  87. package/dist/src/explorbot.js +27 -6
  88. package/dist/src/explorer.d.ts +4 -1
  89. package/dist/src/explorer.js +42 -9
  90. package/dist/src/global-config.d.ts +22 -0
  91. package/dist/src/global-config.js +117 -0
  92. package/dist/src/knowledge-tracker.d.ts +5 -1
  93. package/dist/src/knowledge-tracker.js +14 -1
  94. package/dist/src/playwright-recorder.js +30 -0
  95. package/dist/src/remote.d.ts +54 -0
  96. package/dist/src/remote.js +229 -0
  97. package/dist/src/reporter.d.ts +1 -0
  98. package/dist/src/reporter.js +7 -1
  99. package/dist/src/state-manager.d.ts +2 -1
  100. package/dist/src/stats.d.ts +1 -0
  101. package/dist/src/stats.js +1 -0
  102. package/dist/src/test-plan.d.ts +2 -0
  103. package/dist/src/test-plan.js +7 -0
  104. package/dist/src/utils/aria.d.ts +2 -8
  105. package/dist/src/utils/aria.js +69 -40
  106. package/dist/src/utils/cli-name.js +6 -2
  107. package/dist/src/utils/html.js +1 -0
  108. package/dist/src/utils/logger.d.ts +6 -0
  109. package/dist/src/utils/logger.js +24 -0
  110. package/dist/src/utils/page-readiness.js +18 -1
  111. package/dist/src/utils/test-files.js +1 -2
  112. package/dist/src/utils/url-matcher.d.ts +1 -0
  113. package/dist/src/utils/url-matcher.js +12 -0
  114. package/dist/src/utils/web-element.d.ts +2 -0
  115. package/dist/src/utils/web-element.js +8 -0
  116. package/dist/src/utils/web-sandbox.d.ts +1 -1
  117. package/dist/src/utils/web-sandbox.js +2 -3
  118. package/docs/api-testing/basics.md +90 -0
  119. package/docs/api-testing/planning.md +57 -0
  120. package/docs/api-testing/running-tests.md +55 -0
  121. package/docs/assets/cloud-report.png +0 -0
  122. package/docs/assets/html-report.png +0 -0
  123. package/docs/assets/langfuse-trace.png +0 -0
  124. package/docs/assets/successful-explore-run.png +0 -0
  125. package/docs/basics/getting-started.md +140 -0
  126. package/docs/basics/prerequisites.md +63 -0
  127. package/docs/basics/providers.md +362 -0
  128. package/docs/basics/running.md +78 -0
  129. package/docs/contributing/ai-integration-tests.md +57 -0
  130. package/docs/contributing/contributing.md +90 -0
  131. package/docs/contributing/demo-videos.md +36 -0
  132. package/docs/contributing/npm-package.md +138 -0
  133. package/docs/contributing/observability.md +227 -0
  134. package/docs/contributing/regression-tests.md +103 -0
  135. package/docs/contributing/testing.md +95 -0
  136. package/docs/doc-collection/basics.md +128 -0
  137. package/docs/doc-collection/crawling.md +67 -0
  138. package/docs/doc-collection/interactive-mode.md +99 -0
  139. package/docs/index.json +86 -0
  140. package/docs/reference/commands.md +994 -0
  141. package/docs/reference/configuration.md +569 -0
  142. package/docs/reference/scripting.md +303 -0
  143. package/docs/superpowers/plans/2026-08-01-actor-boat.md +925 -0
  144. package/docs/superpowers/plans/2026-08-01-prima-boat.md +1120 -0
  145. package/docs/superpowers/reviews/2026-08-06-prima-vs-playwright-cli.md +268 -0
  146. package/docs/superpowers/specs/2026-08-01-actor-boat-design.md +204 -0
  147. package/docs/superpowers/specs/2026-08-01-prima-boat-design.md +242 -0
  148. package/docs/superpowers/specs/2026-08-03-global-config-design.md +138 -0
  149. package/docs/superpowers/specs/2026-08-07-prima-fixes-design.md +394 -0
  150. package/docs/web-testing/agents.md +158 -0
  151. package/docs/web-testing/automated-tests.md +134 -0
  152. package/docs/web-testing/basics.md +91 -0
  153. package/docs/web-testing/customization.md +131 -0
  154. package/docs/web-testing/hooks.md +238 -0
  155. package/docs/web-testing/page-interaction.md +84 -0
  156. package/docs/web-testing/planner.md +122 -0
  157. package/docs/web-testing/rerun.md +164 -0
  158. package/docs/web-testing/researcher.md +380 -0
  159. package/docs/workflow/agentic-usage.md +233 -0
  160. package/docs/workflow/application-spec.md +73 -0
  161. package/docs/workflow/ci.md +202 -0
  162. package/docs/workflow/knowledge.md +310 -0
  163. package/docs/workflow/planning-styles.md +67 -0
  164. package/docs/workflow/reporting.md +133 -0
  165. package/docs/workflow/test-plans.md +90 -0
  166. package/models.json +3 -0
  167. package/package.json +8 -3
  168. package/rules/navigator/verification-actions.md +20 -0
  169. package/src/action-result.ts +11 -0
  170. package/src/action.ts +47 -7
  171. package/src/ai/captain/mixin.ts +3 -3
  172. package/src/ai/captain/web-mode.ts +7 -4
  173. package/src/ai/captain.ts +3 -0
  174. package/src/ai/navigator.ts +26 -15
  175. package/src/ai/pilot.ts +53 -2
  176. package/src/ai/planner.ts +7 -0
  177. package/src/ai/provider.ts +2 -4
  178. package/src/ai/rerunner.ts +2 -1
  179. package/src/ai/researcher.ts +2 -2
  180. package/src/ai/rules.ts +17 -10
  181. package/src/ai/task-agent.ts +4 -2
  182. package/src/ai/tester.ts +32 -18
  183. package/src/ai/tools.ts +132 -36
  184. package/src/application-spec-contract.ts +10 -0
  185. package/src/application-spec.ts +87 -0
  186. package/src/browser-server.ts +74 -19
  187. package/src/commands/clean-command.ts +1 -6
  188. package/src/commands/init-command.ts +146 -1
  189. package/src/commands/navigate-command.ts +1 -1
  190. package/src/commands/research-command.ts +1 -1
  191. package/src/commands/sites-command.ts +27 -0
  192. package/src/components/InitWizard.tsx +166 -0
  193. package/src/components/InputReadline.tsx +8 -4
  194. package/src/config.ts +177 -43
  195. package/src/execution-controller.ts +8 -0
  196. package/src/explorbot.ts +34 -7
  197. package/src/explorer.ts +47 -9
  198. package/src/global-config.ts +148 -0
  199. package/src/knowledge-tracker.ts +17 -1
  200. package/src/playwright-recorder.ts +23 -0
  201. package/src/remote.ts +238 -0
  202. package/src/reporter.ts +7 -1
  203. package/src/state-manager.ts +2 -1
  204. package/src/stats.ts +1 -0
  205. package/src/test-plan.ts +9 -0
  206. package/src/utils/aria.ts +65 -45
  207. package/src/utils/cli-name.ts +5 -2
  208. package/src/utils/html.ts +1 -0
  209. package/src/utils/logger.ts +24 -1
  210. package/src/utils/page-readiness.ts +24 -1
  211. package/src/utils/test-files.ts +1 -2
  212. package/src/utils/url-matcher.ts +13 -0
  213. package/src/utils/web-element.ts +9 -0
  214. package/src/utils/web-sandbox.ts +3 -4
@@ -1,7 +1,9 @@
1
- import { existsSync, mkdirSync, statSync, writeFileSync } from 'node:fs';
1
+ import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
2
2
  import { dirname, extname, join, resolve } from 'node:path';
3
3
  import chalk from 'chalk';
4
4
  import dedent from 'dedent';
5
+ import { ConfigParser, PROVIDERS } from "../config.js";
6
+ import { findGlobalConfig, globalConfigPath, globalDir, globalEnvPath } from "../global-config.js";
5
7
  import { getCliName } from "../utils/cli-name.js";
6
8
  import { log, tag } from '../utils/logger.js';
7
9
  import { relativeToCwd } from "../utils/next-steps.js";
@@ -58,6 +60,41 @@ LANGFUSE_BASE_URL=
58
60
  # Testomat.io API key to publish run results
59
61
  TESTOMATIO=
60
62
  `;
63
+ export async function runInit(options) {
64
+ if (options.global || options.provider) {
65
+ await runGlobalInit(options);
66
+ return;
67
+ }
68
+ if (options.configPath || options.path || !process.stdin.isTTY) {
69
+ runInitCommand(options);
70
+ return;
71
+ }
72
+ const choice = await renderInitWizard('choose');
73
+ if (choice === 'local')
74
+ runInitCommand(options);
75
+ }
76
+ export function writeGlobalConfig(provider, apiKey) {
77
+ if (!PROVIDERS[provider]) {
78
+ throw new Error(`Unknown AI provider "${provider}". Supported providers: ${Object.keys(PROVIDERS).join(', ')}`);
79
+ }
80
+ mkdirSync(globalDir(), { recursive: true });
81
+ writeFileSync(globalConfigPath(), globalConfigTemplate(provider), 'utf8');
82
+ log(`Created global config: ${globalConfigPath()}`);
83
+ const envKey = PROVIDERS[provider].envKey;
84
+ writeEnvKey(envKey, apiKey || '');
85
+ log(`Stored ${envKey} in ${globalEnvPath()}`);
86
+ const missing = missingRoles(provider);
87
+ if (missing.length) {
88
+ tag('warning').log(`No recommended ${missing.join(' and ')} for ${provider} — set the model ids in ${globalConfigPath()}`);
89
+ }
90
+ if (!apiKey && !process.env[envKey]) {
91
+ tag('warning').log(`Add your API key to ${globalEnvPath()}`);
92
+ }
93
+ log('');
94
+ log('Explorbot now runs from any directory:');
95
+ tag('substep').log(chalk.yellow(`${getCliName()} explore https://your-app.example.com`));
96
+ tag('substep').log(chalk.yellow(`${getCliName()} sites`));
97
+ }
61
98
  export function runInitCommand(options) {
62
99
  const configPath = options.configPath ?? './explorbot.config.js';
63
100
  const force = options.force ?? false;
@@ -125,3 +162,84 @@ export function runInitCommand(options) {
125
162
  }
126
163
  }
127
164
  }
165
+ async function runGlobalInit(options) {
166
+ const existing = findGlobalConfig();
167
+ if (existing && !options.force) {
168
+ log(`Global config already exists: ${existing}`);
169
+ log('Use --force to overwrite existing file');
170
+ process.exit(1);
171
+ }
172
+ if (options.provider) {
173
+ writeGlobalConfig(options.provider, options.apiKey);
174
+ return;
175
+ }
176
+ if (!process.stdin.isTTY) {
177
+ log('Cannot run the setup wizard outside an interactive terminal');
178
+ log(`Pass a provider instead: ${getCliName()} init --global --provider ${Object.keys(PROVIDERS)[0]}`);
179
+ process.exit(1);
180
+ }
181
+ await renderInitWizard('global');
182
+ }
183
+ async function renderInitWizard(mode) {
184
+ const [{ render }, React, InitWizard] = await Promise.all([import('ink'), import('react'), import('../components/InitWizard.js').then((m) => m.default)]);
185
+ return new Promise((resolve) => {
186
+ const finish = (choice) => {
187
+ unmount();
188
+ resolve(choice);
189
+ };
190
+ const { unmount } = render(React.createElement(InitWizard, {
191
+ mode,
192
+ globalConfigExists: !!findGlobalConfig(),
193
+ onLocal: () => finish('local'),
194
+ onComplete: () => finish('global'),
195
+ onCancel: () => finish(null),
196
+ }), { exitOnCtrlC: false, patchConsole: false });
197
+ });
198
+ }
199
+ function globalConfigTemplate(provider) {
200
+ const { envKey } = PROVIDERS[provider];
201
+ const recommended = ConfigParser.recommendedModels()[provider] || {};
202
+ const roles = [
203
+ ['model', 'fast model with tool calling capabilities'],
204
+ ['visionModel', 'vision model for screenshot analysis'],
205
+ ['agenticModel', 'agentic model for decision making'],
206
+ ];
207
+ const models = roles.map(([role, comment]) => ` // ${comment}\n ${role}: '${provider}/${recommended[role] || '<model-id>'}',`).join('\n');
208
+ return `// Global Explorbot configuration — used by every directory without its own explorbot.config.js.
209
+ // Models are written as 'provider/model-id' so they resolve without a local node_modules.
210
+ // The key is read from ${envKey} in ~/.explorbot/.env
211
+ // Model ids are snapshotted from the recommendations of this Explorbot version.
212
+ // https://github.com/testomatio/explorbot/blob/main/docs/basics/providers.md
213
+ const config = {
214
+ ai: {
215
+ ${models}
216
+ },
217
+
218
+ reporter: {
219
+ // Save a local HTML report after each run.
220
+ html: true,
221
+ // Save a local markdown report after each run.
222
+ markdown: true,
223
+ },
224
+ };
225
+
226
+ export default config;
227
+ `;
228
+ }
229
+ function missingRoles(provider) {
230
+ const recommended = ConfigParser.recommendedModels()[provider] || {};
231
+ return ['model', 'visionModel', 'agenticModel'].filter((role) => !recommended[role]);
232
+ }
233
+ function writeEnvKey(key, value) {
234
+ const envPath = globalEnvPath();
235
+ let content = '# AI provider API keys';
236
+ if (existsSync(envPath))
237
+ content = readFileSync(envPath, 'utf8').trimEnd();
238
+ const lines = content.split('\n');
239
+ const index = lines.findIndex((line) => line.startsWith(`${key}=`));
240
+ if (index < 0)
241
+ lines.push(`${key}=${value}`);
242
+ if (index >= 0 && value)
243
+ lines[index] = `${key}=${value}`;
244
+ writeFileSync(envPath, `${lines.join('\n').trimEnd()}\n`, 'utf8');
245
+ }
@@ -12,7 +12,7 @@ export class NavigateCommand extends BaseCommand {
12
12
  if (!destination) {
13
13
  throw new Error('Navigate command requires a target URI or state');
14
14
  }
15
- await this.explorBot.agentNavigator().visit(destination);
15
+ await this.explorBot.visit(destination);
16
16
  tag('success').log(`Navigation requested: ${destination}`);
17
17
  }
18
18
  }
@@ -21,7 +21,7 @@ export class ResearchCommand extends BaseCommand {
21
21
  const noFix = !!opts.noFix;
22
22
  const target = remaining.join(' ');
23
23
  if (target) {
24
- await this.explorBot.agentNavigator().visit(target);
24
+ await this.explorBot.visit(target);
25
25
  }
26
26
  const state = this.explorBot.stateManager().getCurrentState();
27
27
  if (!state) {
@@ -0,0 +1,6 @@
1
+ import { BaseCommand } from './base-command.js';
2
+ export declare class SitesCommand extends BaseCommand {
3
+ name: string;
4
+ description: string;
5
+ execute(): Promise<void>;
6
+ }
@@ -0,0 +1,23 @@
1
+ import { listSites, sitesDir } from '../global-config.js';
2
+ import { getCliName } from '../utils/cli-name.js';
3
+ import { tag } from '../utils/logger.js';
4
+ import { BaseCommand } from './base-command.js';
5
+ export class SitesCommand extends BaseCommand {
6
+ name = 'sites';
7
+ description = 'List sites registered in the global installation';
8
+ async execute() {
9
+ const sites = listSites();
10
+ if (sites.length === 0) {
11
+ tag('info').log(`No sites registered in ${sitesDir()}`);
12
+ tag('info').log(`Explore one to register it: ${getCliName()} explore https://app.example.com`);
13
+ return;
14
+ }
15
+ const width = sites.reduce((max, site) => Math.max(max, site.folder.length), 0);
16
+ tag('info').log(`Registered sites (${sites.length}):`);
17
+ for (const site of sites) {
18
+ tag('info').log(` ${site.folder.padEnd(width)} ${site.url} last run ${site.lastRunAt.slice(0, 16).replace('T', ' ')}`);
19
+ }
20
+ tag('info').log('');
21
+ tag('info').log(`Stored in ${sitesDir()}`);
22
+ }
23
+ }
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ interface InitWizardProps {
3
+ mode: 'choose' | 'global';
4
+ globalConfigExists: boolean;
5
+ onLocal: () => void;
6
+ onComplete: () => void;
7
+ onCancel: () => void;
8
+ }
9
+ declare const InitWizard: React.FC<InitWizardProps>;
10
+ export default InitWizard;
@@ -0,0 +1,133 @@
1
+ import figureSet from 'figures';
2
+ import { Box, Text, useInput } from 'ink';
3
+ import React, { useState } from 'react';
4
+ import { AIProvider } from '../ai/provider.js';
5
+ import { writeGlobalConfig } from '../commands/init-command.js';
6
+ import { ConfigParser, PROVIDERS, createModel } from '../config.js';
7
+ import { globalDir } from '../global-config.js';
8
+ import InputReadline from './InputReadline.js';
9
+ const PROVIDER_NAMES = Object.keys(PROVIDERS);
10
+ const InitWizard = ({ mode, globalConfigExists, onLocal, onComplete, onCancel }) => {
11
+ const [step, setStep] = useState(mode === 'choose' ? 'target' : 'provider');
12
+ const [targetIndex, setTargetIndex] = useState(0);
13
+ const [providerIndex, setProviderIndex] = useState(0);
14
+ const [apiKey, setApiKey] = useState('');
15
+ const [status, setStatus] = useState('');
16
+ const [error, setError] = useState('');
17
+ const provider = PROVIDER_NAMES[providerIndex];
18
+ const envKey = PROVIDERS[provider].envKey;
19
+ const save = () => {
20
+ writeGlobalConfig(provider, apiKey.trim());
21
+ onComplete();
22
+ };
23
+ const validate = async () => {
24
+ const modelId = defaultModelId(provider);
25
+ if (!modelId) {
26
+ save();
27
+ return;
28
+ }
29
+ setStatus(`Sending a test request to ${provider}...`);
30
+ setError('');
31
+ if (apiKey.trim())
32
+ process.env[envKey] = apiKey.trim();
33
+ try {
34
+ const model = await createModel(provider, modelId);
35
+ await new AIProvider({ model }).validateConnection();
36
+ save();
37
+ }
38
+ catch (err) {
39
+ setStatus('');
40
+ setError(err instanceof Error ? err.message : String(err));
41
+ }
42
+ };
43
+ useInput((input, key) => {
44
+ if (key.ctrl && input === 'c') {
45
+ onCancel();
46
+ return;
47
+ }
48
+ if (step === 'key')
49
+ return;
50
+ if (key.escape) {
51
+ onCancel();
52
+ return;
53
+ }
54
+ if (step === 'target') {
55
+ if (key.upArrow)
56
+ setTargetIndex(0);
57
+ if (key.downArrow && !globalConfigExists)
58
+ setTargetIndex(1);
59
+ if (key.return && targetIndex === 0)
60
+ onLocal();
61
+ if (key.return && targetIndex === 1)
62
+ setStep('provider');
63
+ return;
64
+ }
65
+ if (step === 'provider') {
66
+ if (key.upArrow)
67
+ setProviderIndex((index) => Math.max(0, index - 1));
68
+ if (key.downArrow)
69
+ setProviderIndex((index) => Math.min(PROVIDER_NAMES.length - 1, index + 1));
70
+ if (key.return)
71
+ setStep('key');
72
+ return;
73
+ }
74
+ if (status)
75
+ return;
76
+ if (input.toLowerCase() === 'y')
77
+ void validate();
78
+ if (input.toLowerCase() === 'n')
79
+ save();
80
+ if (input.toLowerCase() === 'r') {
81
+ setError('');
82
+ setStep('key');
83
+ }
84
+ });
85
+ return (React.createElement(Box, { flexDirection: "column", padding: 1 },
86
+ React.createElement(Box, { marginBottom: 1 },
87
+ React.createElement(Text, { color: "cyan", bold: true }, "Explorbot setup")),
88
+ step === 'target' && (React.createElement(Box, { flexDirection: "column" },
89
+ React.createElement(Text, null, "Where should explorbot be initialized?"),
90
+ React.createElement(Text, { color: targetIndex === 0 ? 'blue' : undefined },
91
+ targetIndex === 0 ? `${figureSet.pointer} ` : ' ',
92
+ "Local ",
93
+ React.createElement(Text, { dimColor: true }, "\u2014 creates the config file in the current directory")),
94
+ React.createElement(Text, { color: targetIndex === 1 ? 'blue' : undefined, dimColor: globalConfigExists },
95
+ targetIndex === 1 ? `${figureSet.pointer} ` : ' ',
96
+ "Global ",
97
+ React.createElement(Text, { dimColor: true }, "\u2014 initializes explorbot to run from anywhere on this machine"),
98
+ globalConfigExists && React.createElement(Text, { dimColor: true }, " (already installed)")))),
99
+ step === 'provider' && (React.createElement(Box, { flexDirection: "column" },
100
+ React.createElement(Text, null, "Pick an AI provider:"),
101
+ PROVIDER_NAMES.map((name, index) => (React.createElement(Text, { key: name, color: index === providerIndex ? 'blue' : undefined },
102
+ index === providerIndex ? `${figureSet.pointer} ` : ' ',
103
+ name))))),
104
+ step === 'key' && (React.createElement(Box, { flexDirection: "column" },
105
+ React.createElement(Text, null,
106
+ "Enter the API key for ",
107
+ provider,
108
+ " ",
109
+ React.createElement(Text, { dimColor: true },
110
+ "(stored as ",
111
+ envKey,
112
+ ")")),
113
+ React.createElement(Box, { borderStyle: "single", borderColor: "blue", paddingX: 1 },
114
+ React.createElement(InputReadline, { value: apiKey, onChange: setApiKey, onSubmit: () => setStep('validate'), placeholder: process.env[envKey] ? 'leave empty to keep the key from your environment' : 'paste the key', isActive: true, showPrompt: false, mask: true })))),
115
+ step === 'validate' && (React.createElement(Box, { flexDirection: "column" },
116
+ !status && !error && React.createElement(Text, null, "Check the key with a test AI call? (y/n)"),
117
+ status && React.createElement(Text, { color: "yellow" }, status),
118
+ error && (React.createElement(Box, { flexDirection: "column" },
119
+ React.createElement(Text, { color: "red" }, error),
120
+ React.createElement(Text, { dimColor: true }, "r: re-enter the key | n: save anyway"))))),
121
+ React.createElement(Box, { marginTop: 1 },
122
+ React.createElement(Text, { dimColor: true },
123
+ "Config goes to ",
124
+ globalDir(),
125
+ " | ",
126
+ step === 'key' ? 'Enter: continue' : '↑↓: select | Enter: confirm',
127
+ " | Ctrl+C: exit"))));
128
+ };
129
+ function defaultModelId(provider) {
130
+ const recommended = ConfigParser.recommendedModels()[provider] || {};
131
+ return recommended.model || recommended.agenticModel || recommended.visionModel || '';
132
+ }
133
+ export default InitWizard;
@@ -12,6 +12,7 @@ interface InputReadlineProps {
12
12
  value?: string;
13
13
  placeholder?: string;
14
14
  showPrompt?: boolean;
15
+ mask?: boolean;
15
16
  }
16
17
  declare const InputReadline: React.FC<InputReadlineProps>;
17
18
  export default InputReadline;
@@ -3,7 +3,7 @@ import React from 'react';
3
3
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
4
4
  import { setAutocompleteState } from './autocomplete-store.js';
5
5
  import parseKeypress, { nonAlphanumericKeys } from './parse-keypress.js';
6
- const InputReadline = React.memo(({ commandHandler, exitOnEmptyInput = false, onSubmit, onChange, onCommandStart, onCommandComplete, isActive = true, visible = true, value: controlledValue, placeholder = '', showPrompt = true }) => {
6
+ const InputReadline = React.memo(({ commandHandler, exitOnEmptyInput = false, onSubmit, onChange, onCommandStart, onCommandComplete, isActive = true, visible = true, value: controlledValue, placeholder = '', showPrompt = true, mask = false }) => {
7
7
  const isControlled = controlledValue !== undefined;
8
8
  const [inputState, setInputState] = useState({
9
9
  value: controlledValue || '',
@@ -537,11 +537,14 @@ const InputReadline = React.memo(({ commandHandler, exitOnEmptyInput = false, on
537
537
  commandHandler.setExitOnEmptyInput(exitOnEmptyInput);
538
538
  }
539
539
  }, [commandHandler, exitOnEmptyInput]);
540
- const beforeCursor = displayValue.slice(0, cursorPosition);
541
- const afterCursor = displayValue.slice(cursorPosition);
540
+ let renderedValue = displayValue;
541
+ if (mask)
542
+ renderedValue = '•'.repeat(displayValue.length);
543
+ const beforeCursor = renderedValue.slice(0, cursorPosition);
544
+ const afterCursor = renderedValue.slice(cursorPosition);
542
545
  const beforeLines = beforeCursor.split('\n');
543
546
  const afterLines = afterCursor.split('\n');
544
- const lines = displayValue.split('\n');
547
+ const lines = renderedValue.split('\n');
545
548
  const cursorLineIndex = beforeLines.length - 1;
546
549
  const cursorLineBefore = beforeLines[cursorLineIndex] || '';
547
550
  const cursorLineAfter = afterLines[0] || '';
@@ -1,4 +1,5 @@
1
- export declare const PROVIDERS: Record<string, () => Promise<(modelId: string) => any>>;
1
+ import { type SiteRecord } from './global-config.js';
2
+ export declare const PROVIDERS: Record<string, ProviderInfo>;
2
3
  interface PlaywrightConfig {
3
4
  browser: 'chromium' | 'firefox' | 'webkit';
4
5
  url: string;
@@ -171,6 +172,7 @@ interface HtmlConfig {
171
172
  interface ActionConfig {
172
173
  delay?: number;
173
174
  retries?: number;
175
+ timeout?: number;
174
176
  }
175
177
  interface ReporterConfig {
176
178
  enabled?: boolean;
@@ -202,6 +204,7 @@ interface ExplorbotConfig {
202
204
  knowledge: string;
203
205
  experience: string;
204
206
  output: string;
207
+ spec?: string;
205
208
  };
206
209
  experience?: {
207
210
  maxReadLines?: number;
@@ -222,21 +225,27 @@ export declare class ConfigParser {
222
225
  static recommended: Record<string, Record<string, string>> | null;
223
226
  config: ExplorbotConfig | null;
224
227
  configPath: string | null;
225
- runtimeBaseUrlOverride: string | null;
228
+ runtimeTarget: string | null;
229
+ site: SiteRecord | null;
230
+ siteStartPath: string;
226
231
  constructor();
227
- static loadEnv(filePath: string): void;
232
+ static loadEnv(filePath: string, keepExisting?: boolean): void;
228
233
  static recommendedModels(): Record<string, Record<string, string>>;
229
234
  static getInstance(): ConfigParser;
230
235
  loadConfig(options?: {
231
236
  config?: string;
232
237
  path?: string;
233
238
  baseUrl?: string;
239
+ from?: string;
234
240
  }): Promise<ExplorbotConfig>;
235
241
  getConfig(): ExplorbotConfig;
236
242
  getConfigPath(): string | null;
237
243
  getOutputDir(): string;
238
244
  getProjectRoot(): string;
239
245
  resolveProjectDir(relativeDir: string): string;
246
+ isGlobalMode(): boolean;
247
+ getSite(): SiteRecord | null;
248
+ resolveTargetPath(target?: string): string;
240
249
  getStatesDir(): string;
241
250
  getPlansDir(): string;
242
251
  getTestsDir(): string;
@@ -244,6 +253,7 @@ export declare class ConfigParser {
244
253
  static setupTestConfig(): void;
245
254
  static getTestDirectories(): string[];
246
255
  static cleanupAllTestDirectories(): void;
256
+ enterGlobalMode(config: ExplorbotConfig, target: string | null): void;
247
257
  buildEnvConfig(baseUrl: string | undefined, outputRoot: string): Promise<ExplorbotConfig>;
248
258
  findConfigFile(): string | null;
249
259
  loadConfigModule(configPath: string): Promise<any>;
@@ -258,13 +268,23 @@ export declare class ConfigParser {
258
268
  }
259
269
  export declare function outputPath(...segments: string[]): string;
260
270
  export declare function resolveModel(spec: string, role?: ModelRole): Promise<any>;
261
- export declare function resolveOutputRoot(): string;
271
+ export declare class ConfigMissingError extends Error {
272
+ }
273
+ export declare function envConfigRequested(): boolean;
274
+ export declare function missingConfigMessage(configFile?: string): string;
275
+ export declare function resolveConfigModels(ai?: AIConfig): Promise<void>;
276
+ export declare function resolveOutputRoot(baseUrl?: string): string;
277
+ export declare function resolveStateRoot(baseUrl: string, ephemeral?: boolean): string;
262
278
  export declare function materializeKnowledge(outputRoot: string): void;
263
279
  export declare function createModel(provider: string, modelId: string): Promise<any>;
264
280
  type ModelRole = 'model' | 'visionModel' | 'agenticModel';
281
+ interface ProviderInfo {
282
+ envKey: string;
283
+ load: () => Promise<(modelId: string) => any>;
284
+ }
265
285
  interface EnvVar {
266
286
  name: string;
267
287
  description: string;
268
288
  required?: boolean;
269
289
  }
270
- export type { ModelRole, EnvVar };
290
+ export type { ModelRole, EnvVar, ProviderInfo };