explorbot 0.4.8 → 0.4.9
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.
- package/boat/api-tester/src/cli.ts +1 -1
- package/boat/api-tester/src/config.ts +34 -26
- package/dist/boat/api-tester/src/cli.js +1 -1
- package/dist/boat/api-tester/src/config.js +32 -26
- package/dist/package.json +1 -1
- package/dist/src/ai/fisherman/tools.js +10 -4
- package/dist/src/ai/pilot.js +11 -6
- package/dist/src/ai/tools.js +8 -1
- package/dist/src/commands/config-command.d.ts +2 -0
- package/dist/src/commands/config-command.js +8 -2
- package/dist/src/commands/init-command.js +6 -26
- package/dist/src/commands/sites-command.js +6 -1
- package/dist/src/config.d.ts +5 -4
- package/dist/src/config.js +25 -23
- package/dist/src/global-config.d.ts +6 -0
- package/dist/src/global-config.js +82 -7
- package/dist/src/utils/html.js +6 -0
- package/dist/src/utils/merge.d.ts +1 -0
- package/dist/src/utils/merge.js +11 -0
- package/docs/reference/commands.md +10 -2
- package/docs/reference/configuration.md +37 -4
- package/docs/superpowers/plans/2026-09-15-mdq-package.md +2029 -0
- package/docs/superpowers/specs/2026-09-14-mdq-package-design.md +397 -0
- package/package.json +1 -1
- package/src/ai/fisherman/tools.ts +10 -4
- package/src/ai/pilot.ts +11 -6
- package/src/ai/tools.ts +7 -1
- package/src/commands/config-command.ts +9 -2
- package/src/commands/init-command.ts +6 -27
- package/src/commands/sites-command.ts +6 -1
- package/src/config.ts +28 -25
- package/src/global-config.ts +81 -6
- package/src/utils/html.ts +6 -0
- package/src/utils/merge.ts +13 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export declare const EXPLORBOT_CONFIG_PATHS: string[];
|
|
1
2
|
export declare function globalDir(): string;
|
|
2
3
|
export declare function globalEnvPath(): string;
|
|
3
4
|
export declare function globalConfigPath(): string;
|
|
@@ -6,6 +7,11 @@ export declare function isGlobalConfigPath(configPath: string): boolean;
|
|
|
6
7
|
export declare function sitesDir(): string;
|
|
7
8
|
export declare function siteFolderName(url: string): string;
|
|
8
9
|
export declare function listSites(): SiteRecord[];
|
|
10
|
+
export declare function findSiteConfig(dir: string): string | null;
|
|
11
|
+
export declare function loadSiteConfig(dir: string, baseUrl: string): Promise<{
|
|
12
|
+
path: string;
|
|
13
|
+
config: any;
|
|
14
|
+
}>;
|
|
9
15
|
export declare function findSiteWith(subpath: string): SiteRecord | undefined;
|
|
10
16
|
export declare function listSitePlanDirs(): string[];
|
|
11
17
|
export declare function registerSite(baseUrl: string): SiteRecord;
|
|
@@ -1,8 +1,19 @@
|
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
1
9
|
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
2
10
|
import os from 'node:os';
|
|
3
|
-
import { join } from 'node:path';
|
|
11
|
+
import { join, resolve } from 'node:path';
|
|
12
|
+
import { pathToFileURL } from 'node:url';
|
|
13
|
+
import dedent from 'dedent';
|
|
4
14
|
const GLOBAL_CONFIG_NAMES = ['config.js', 'config.mjs', 'config.ts'];
|
|
5
15
|
const SITE_DIRS = ['knowledge', 'experience', 'output'];
|
|
16
|
+
export const EXPLORBOT_CONFIG_PATHS = ['explorbot.config.js', 'explorbot.config.mjs', 'explorbot.config.ts'];
|
|
6
17
|
export function globalDir() {
|
|
7
18
|
return join(os.homedir(), '.explorbot');
|
|
8
19
|
}
|
|
@@ -13,12 +24,7 @@ export function globalConfigPath() {
|
|
|
13
24
|
return join(globalDir(), 'config.js');
|
|
14
25
|
}
|
|
15
26
|
export function findGlobalConfig() {
|
|
16
|
-
|
|
17
|
-
const fullPath = join(globalDir(), name);
|
|
18
|
-
if (existsSync(fullPath))
|
|
19
|
-
return fullPath;
|
|
20
|
-
}
|
|
21
|
-
return null;
|
|
27
|
+
return firstExisting(globalDir(), GLOBAL_CONFIG_NAMES);
|
|
22
28
|
}
|
|
23
29
|
export function isGlobalConfigPath(configPath) {
|
|
24
30
|
return GLOBAL_CONFIG_NAMES.some((name) => join(globalDir(), name) === configPath);
|
|
@@ -38,6 +44,47 @@ export function listSites() {
|
|
|
38
44
|
.filter((site) => !!site)
|
|
39
45
|
.sort((a, b) => b.lastRunAt.localeCompare(a.lastRunAt));
|
|
40
46
|
}
|
|
47
|
+
export function findSiteConfig(dir) {
|
|
48
|
+
return firstExisting(dir, EXPLORBOT_CONFIG_PATHS);
|
|
49
|
+
}
|
|
50
|
+
function ensureSiteConfig(dir, baseUrl) {
|
|
51
|
+
const existing = findSiteConfig(dir);
|
|
52
|
+
if (existing)
|
|
53
|
+
return existing;
|
|
54
|
+
const path = join(dir, EXPLORBOT_CONFIG_PATHS[0]);
|
|
55
|
+
writeFileSync(path, siteConfigTemplate(baseUrl), 'utf8');
|
|
56
|
+
return path;
|
|
57
|
+
}
|
|
58
|
+
export async function loadSiteConfig(dir, baseUrl) {
|
|
59
|
+
const path = ensureSiteConfig(dir, baseUrl);
|
|
60
|
+
const module = await import(__rewriteRelativeImportExtension(pathToFileURL(resolve(path)).href));
|
|
61
|
+
const config = module.default || module;
|
|
62
|
+
validateSiteConfig(config, path, baseUrl);
|
|
63
|
+
return { path, config };
|
|
64
|
+
}
|
|
65
|
+
function validateSiteConfig(config, configPath, baseUrl) {
|
|
66
|
+
const url = config?.web?.url;
|
|
67
|
+
if (!url) {
|
|
68
|
+
throw new Error(dedent `
|
|
69
|
+
Site config is missing web.url.
|
|
70
|
+
${configPath}
|
|
71
|
+
|
|
72
|
+
Add it so the config states which site it configures:
|
|
73
|
+
web: { url: '${baseUrl}' },
|
|
74
|
+
`);
|
|
75
|
+
}
|
|
76
|
+
const declared = URL.parse(url)?.origin;
|
|
77
|
+
if (declared === baseUrl)
|
|
78
|
+
return;
|
|
79
|
+
throw new Error(dedent `
|
|
80
|
+
Site config declares a different site.
|
|
81
|
+
${configPath}
|
|
82
|
+
web.url: ${url}
|
|
83
|
+
site: ${baseUrl}
|
|
84
|
+
|
|
85
|
+
Fix web.url, or explore ${url} to register it as its own site.
|
|
86
|
+
`);
|
|
87
|
+
}
|
|
41
88
|
export function findSiteWith(subpath) {
|
|
42
89
|
return listSites().find((site) => existsSync(join(site.dir, subpath)));
|
|
43
90
|
}
|
|
@@ -88,6 +135,34 @@ export function resolveSiteTarget(target, defaultBaseUrl) {
|
|
|
88
135
|
}
|
|
89
136
|
return { baseUrl: site.url, path };
|
|
90
137
|
}
|
|
138
|
+
function firstExisting(dir, names) {
|
|
139
|
+
for (const name of names) {
|
|
140
|
+
const fullPath = join(dir, name);
|
|
141
|
+
if (existsSync(fullPath))
|
|
142
|
+
return fullPath;
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
function siteConfigTemplate(baseUrl) {
|
|
147
|
+
return `// Config for ${baseUrl}
|
|
148
|
+
// Extends ~/.explorbot/config.js — set only what differs.
|
|
149
|
+
const config = {
|
|
150
|
+
web: {
|
|
151
|
+
url: '${baseUrl}',
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
// ai: {
|
|
155
|
+
// model: 'openrouter/openai/gpt-oss-120b',
|
|
156
|
+
// },
|
|
157
|
+
|
|
158
|
+
// playwright: {
|
|
159
|
+
// show: true,
|
|
160
|
+
// },
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
export default config;
|
|
164
|
+
`;
|
|
165
|
+
}
|
|
91
166
|
function readSite(folder) {
|
|
92
167
|
const dir = join(sitesDir(), folder);
|
|
93
168
|
const metaPath = join(dir, 'site.json');
|
package/dist/src/utils/html.js
CHANGED
|
@@ -1283,6 +1283,12 @@ function cleanElement(element) {
|
|
|
1283
1283
|
'aria-labelledby',
|
|
1284
1284
|
'aria-describedby',
|
|
1285
1285
|
'aria-owns',
|
|
1286
|
+
'aria-checked',
|
|
1287
|
+
'aria-expanded',
|
|
1288
|
+
'aria-selected',
|
|
1289
|
+
'aria-pressed',
|
|
1290
|
+
'aria-current',
|
|
1291
|
+
'aria-disabled',
|
|
1286
1292
|
'role',
|
|
1287
1293
|
'title',
|
|
1288
1294
|
'href',
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function deepMerge(target: any, source: any): any;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function deepMerge(target, source) {
|
|
2
|
+
const result = { ...target };
|
|
3
|
+
for (const key in source) {
|
|
4
|
+
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key]) && source[key].constructor === Object) {
|
|
5
|
+
result[key] = deepMerge(result[key] || {}, source[key]);
|
|
6
|
+
continue;
|
|
7
|
+
}
|
|
8
|
+
result[key] = source[key];
|
|
9
|
+
}
|
|
10
|
+
return result;
|
|
11
|
+
}
|
|
@@ -127,7 +127,7 @@ EXPLORBOT_AI_PROVIDER=openrouter \
|
|
|
127
127
|
|
|
128
128
|
`npx explorbot recommended-models` prints, per provider, the model this version recommends for each role, and the two ways to select it. Both need the provider's API key exported. Set `EXPLORBOT_AI_PROVIDER=<name>` and every role takes that provider's recommendation; leave it out and pin the roles yourself with `EXPLORBOT_AI_MODEL`, `EXPLORBOT_VISION_MODEL` and `EXPLORBOT_AGENTIC_MODEL`, each written as `provider/model-id` — the command prints those three lines filled in, ready to paste. A role a provider does not serve is named as such, so you know to pair it with another. It closes with the model variables and provider keys currently exported, and a ready-to-run OpenRouter one-liner. It reads nothing but the bundled recommendations, so it answers before any configuration exists and every CLI carries it: `npx explorbot api recommended-models`, `npx explorbot docs recommended-models`, `npx prima recommended-models`. `--json` prints the bundled recommendations as an object.
|
|
129
129
|
|
|
130
|
-
Explorbot resolves its configuration in this order: the path given to `--config`, then `explorbot.config.*` in the working directory, then the `EXPLORBOT_*` variables, and finally `~/.explorbot/config.*` from the global installation. A bare provider name fills every model role from the recommendations in [Providers](../basics/providers.md); a `provider/model-id` spec pins one model and splits on the first slash, so `openrouter/openai/gpt-oss-120b:nitro` selects OpenRouter with model `openai/gpt-oss-120b:nitro`. Supported providers: `openai`, `anthropic`, `google`, `groq`, `mistral`, `openrouter`, `sambanova`.
|
|
130
|
+
Explorbot resolves its configuration in this order: the path given to `--config`, then `explorbot.config.*` in the working directory, then the `EXPLORBOT_*` variables, and finally `~/.explorbot/config.*` from the global installation, which each site then extends with its own [per-site config](configuration.md#per-site-configuration). A bare provider name fills every model role from the recommendations in [Providers](../basics/providers.md); a `provider/model-id` spec pins one model and splits on the first slash, so `openrouter/openai/gpt-oss-120b:nitro` selects OpenRouter with model `openai/gpt-oss-120b:nitro`. Supported providers: `openai`, `anthropic`, `google`, `groq`, `mistral`, `openrouter`, `sambanova`.
|
|
131
131
|
|
|
132
132
|
In this mode output goes to `~/.explorbot/sites/<host>/output/` (or `EXPLORBOT_OUTPUT`, or a temp directory with `EXPLORBOT_EPHEMERAL=1`), experience is kept beside it unless the run is ephemeral, and the Historian is off, so no generated test files appear. See [Agentic Usage](../workflow/agentic-usage.md) for the full picture.
|
|
133
133
|
|
|
@@ -934,12 +934,20 @@ See [Configuration](configuration.md#running-from-anywhere-the-global-installati
|
|
|
934
934
|
|
|
935
935
|
### `npx explorbot sites`
|
|
936
936
|
|
|
937
|
-
List the sites registered in the global installation — folder name, base URL, and
|
|
937
|
+
List the sites registered in the global installation — folder name, base URL, last run, and the [per-site config](configuration.md#per-site-configuration) each one uses. Sites register themselves the first time you explore them by URL.
|
|
938
938
|
|
|
939
939
|
```bash
|
|
940
940
|
npx explorbot sites
|
|
941
941
|
```
|
|
942
942
|
|
|
943
|
+
```
|
|
944
|
+
Registered sites (2):
|
|
945
|
+
app.example.com https://app.example.com last run 2026-09-16 09:57
|
|
946
|
+
/home/you/.explorbot/sites/app.example.com/explorbot.config.js
|
|
947
|
+
other.example.com https://other.example.com last run 2026-09-01 10:00
|
|
948
|
+
inherits global config
|
|
949
|
+
```
|
|
950
|
+
|
|
943
951
|
### `npx explorbot clean [target]`
|
|
944
952
|
|
|
945
953
|
Clean generated files. Targets: `states`, `research`, `plans`, `tests`, `experiences`, `output`.
|
|
@@ -413,7 +413,7 @@ Explorbot looks for a config file in this order:
|
|
|
413
413
|
7. `src/config/explorbot.config.js`
|
|
414
414
|
8. `src/config/explorbot.config.mjs`
|
|
415
415
|
9. `src/config/explorbot.config.ts`
|
|
416
|
-
10. `~/.explorbot/config.js` (or `.mjs`, `.ts`) — the global installation
|
|
416
|
+
10. `~/.explorbot/config.js` (or `.mjs`, `.ts`) — the global installation, extended per site by `~/.explorbot/sites/<host>/explorbot.config.js`
|
|
417
417
|
|
|
418
418
|
Or pass a custom path:
|
|
419
419
|
|
|
@@ -431,14 +431,15 @@ Env files fill in rather than override: the `.env` of the working directory is r
|
|
|
431
431
|
|
|
432
432
|
```
|
|
433
433
|
~/.explorbot/
|
|
434
|
-
├── config.js
|
|
434
|
+
├── config.js # AI models and keys, shared by every site
|
|
435
435
|
├── .env
|
|
436
436
|
└── sites/
|
|
437
437
|
├── app.example.com/
|
|
438
|
-
│ ├──
|
|
438
|
+
│ ├── explorbot.config.js # this site's settings, extends config.js
|
|
439
|
+
│ ├── site.json # base URL, first and last run
|
|
439
440
|
│ ├── knowledge/
|
|
440
441
|
│ ├── experience/
|
|
441
|
-
│ └── output/
|
|
442
|
+
│ └── output/ # states, plans, reports, tests
|
|
442
443
|
└── localhost_3000/
|
|
443
444
|
```
|
|
444
445
|
|
|
@@ -456,6 +457,38 @@ npx explorbot sites # list registered sites
|
|
|
456
457
|
|
|
457
458
|
A `dirs` section in the global config is ignored in favor of the layout above. A `web.url` is allowed and acts as the default site for commands that pass no URL of their own.
|
|
458
459
|
|
|
460
|
+
#### Per-site configuration
|
|
461
|
+
|
|
462
|
+
`~/.explorbot/config.js` holds what every site shares — models, keys, reporter settings. Anything one site needs differently goes in its own `explorbot.config.js`, written into the site folder the first time that site is explored:
|
|
463
|
+
|
|
464
|
+
```javascript
|
|
465
|
+
// Config for https://app.example.com
|
|
466
|
+
// Extends ~/.explorbot/config.js — set only what differs.
|
|
467
|
+
const config = {
|
|
468
|
+
web: {
|
|
469
|
+
url: 'https://app.example.com',
|
|
470
|
+
},
|
|
471
|
+
|
|
472
|
+
ai: {
|
|
473
|
+
model: 'openrouter/anthropic/claude-sonnet-5',
|
|
474
|
+
},
|
|
475
|
+
|
|
476
|
+
playwright: {
|
|
477
|
+
show: true,
|
|
478
|
+
},
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
export default config;
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
The two are merged section by section, and the site wins. A site that overrides `ai.model` keeps the global `ai.visionModel`. Use it to give a slow or unusual app a stronger model, a visible browser, or its own reporter settings, without changing how every other site runs.
|
|
485
|
+
|
|
486
|
+
`web.url` is required, and must be the site the folder belongs to — it is what makes the file readable on its own rather than meaningful only by where it sits. Explorbot refuses to run when it is missing or names a different site, instead of quietly ignoring the mismatch. To configure a different site, explore it and edit the config in its own folder.
|
|
487
|
+
|
|
488
|
+
`dirs` and the base URL stay owned by the layout above and cannot be overridden. The file is never rewritten once created, and a site without one simply uses the global config.
|
|
489
|
+
|
|
490
|
+
Per-site configs apply to the global installation only. A directory with its own `explorbot.config.js` and the `EXPLORBOT_*` environment mode below both resolve to a single config with nothing to extend.
|
|
491
|
+
|
|
459
492
|
### Running without a config file
|
|
460
493
|
|
|
461
494
|
When the working directory has no config file and `EXPLORBOT_AI_PROVIDER` (or `EXPLORBOT_AI_MODEL`) is set, Explorbot synthesizes a configuration from `EXPLORBOT_*` environment variables, in preference to a global installation. Output goes to the site folder `~/.explorbot/sites/<host>/` (`EXPLORBOT_OUTPUT` overrides it, `EXPLORBOT_EPHEMERAL=1` sends it to a temp directory instead), experience is written there and reused by later runs against the same host unless the run is ephemeral, and the Historian is off. This is meant for one-liner CI jobs, demos, and coding agents — see [Agentic Usage](../workflow/agentic-usage.md) for the variable list and the trade-offs.
|