@demigodmode/pi-web-agent 0.4.0 → 0.5.1
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/README.md +5 -0
- package/dist/commands/web-agent-config.d.ts +8 -0
- package/dist/commands/web-agent-config.js +89 -4
- package/dist/extension.js +1 -1
- package/dist/fetch/browser-resolution.d.ts +7 -2
- package/dist/fetch/browser-resolution.js +111 -17
- package/dist/types.d.ts +1 -1
- package/package.json +5 -6
package/README.md
CHANGED
|
@@ -18,6 +18,10 @@ That sounds obvious, but a lot of agent tooling gets fuzzy right there. This pac
|
|
|
18
18
|
pi install npm:@demigodmode/pi-web-agent
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
+
After installing, reload or restart Pi. Run `/web-agent` for the action menu, or `/web-agent doctor` to check whether the package loaded cleanly and whether headless rendering can find a browser.
|
|
22
|
+
|
|
23
|
+
Headless rendering currently requires a detectable Chromium-family browser: Chrome, Chromium, Edge, or Brave. Firefox/Safari-only systems can still use search and plain HTTP reads, but browser-rendered fallback pages need a supported Chromium-family browser for now.
|
|
24
|
+
|
|
21
25
|
Later on, update installed packages with:
|
|
22
26
|
|
|
23
27
|
```bash
|
|
@@ -65,6 +69,7 @@ Primary UI:
|
|
|
65
69
|
Helper commands:
|
|
66
70
|
|
|
67
71
|
```text
|
|
72
|
+
/web-agent doctor
|
|
68
73
|
/web-agent show
|
|
69
74
|
/web-agent reset project
|
|
70
75
|
/web-agent reset global
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import { type ExtensionAPI } from '@mariozechner/pi-coding-agent';
|
|
2
2
|
import { loadPresentationConfigLayers, type LoadedPresentationConfig } from '../presentation/config-store.js';
|
|
3
|
+
import { type BrowserResolutionResult } from '../fetch/browser-resolution.js';
|
|
3
4
|
import type { PresentationConfig, PresentationConfigOverride, PresentationScope } from '../presentation/types.js';
|
|
4
5
|
type CommandDeps = {
|
|
5
6
|
load?: () => ReturnType<typeof loadPresentationConfigLayers>;
|
|
6
7
|
save?: (scope: PresentationScope, config: PresentationConfigOverride) => Promise<void>;
|
|
7
8
|
reset?: (scope: PresentationScope) => Promise<void>;
|
|
9
|
+
resolveBrowser?: () => Promise<BrowserResolutionResult>;
|
|
10
|
+
runtime?: {
|
|
11
|
+
nodeVersion: string;
|
|
12
|
+
platform: string;
|
|
13
|
+
arch: string;
|
|
14
|
+
};
|
|
15
|
+
checkTypebox?: () => Promise<boolean>;
|
|
8
16
|
};
|
|
9
17
|
export type SettingsDraftState = {
|
|
10
18
|
scope: PresentationScope;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { getSettingsListTheme } from '@mariozechner/pi-coding-agent';
|
|
2
|
-
import { Container, SettingsList, Text } from '@mariozechner/pi-tui';
|
|
1
|
+
import { DynamicBorder, getSettingsListTheme } from '@mariozechner/pi-coding-agent';
|
|
2
|
+
import { Container, SelectList, SettingsList, Text } from '@mariozechner/pi-tui';
|
|
3
3
|
import { DEFAULT_PRESENTATION_CONFIG, mergePresentationConfigLayers, resolvePresentationMode } from '../presentation/config.js';
|
|
4
4
|
import { loadPresentationConfigLayers, resetPresentationConfigScope, savePresentationConfigScope } from '../presentation/config-store.js';
|
|
5
|
+
import { resolveBrowserExecutable } from '../fetch/browser-resolution.js';
|
|
5
6
|
const PRESENTATION_TOOL_NAMES = ['web_explore'];
|
|
6
7
|
function parseScopeToken(token) {
|
|
7
8
|
return token === 'global' || token === 'project' ? token : undefined;
|
|
@@ -12,6 +13,15 @@ function clonePresentationConfig(config) {
|
|
|
12
13
|
tools: { ...config.tools }
|
|
13
14
|
};
|
|
14
15
|
}
|
|
16
|
+
async function defaultCheckTypebox() {
|
|
17
|
+
try {
|
|
18
|
+
await import('typebox');
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
15
25
|
function formatConfigSummary(config) {
|
|
16
26
|
const lines = [`defaultMode: ${config.defaultMode}`];
|
|
17
27
|
for (const toolName of PRESENTATION_TOOL_NAMES) {
|
|
@@ -137,6 +147,40 @@ export function handleSettingsShortcut(data) {
|
|
|
137
147
|
}
|
|
138
148
|
return undefined;
|
|
139
149
|
}
|
|
150
|
+
async function openActionMenu(ctx) {
|
|
151
|
+
return ctx.ui.custom((tui, theme, _kb, done) => {
|
|
152
|
+
const container = new Container();
|
|
153
|
+
const items = [
|
|
154
|
+
{ value: 'settings', label: 'Settings', description: 'Edit presentation modes' },
|
|
155
|
+
{ value: 'show', label: 'Show config', description: 'Print effective config paths and modes' },
|
|
156
|
+
{ value: 'doctor', label: 'Doctor', description: 'Check runtime dependencies and browser detection' },
|
|
157
|
+
{ value: 'reset-project', label: 'Reset project config', description: 'Delete project-level overrides' },
|
|
158
|
+
{ value: 'reset-global', label: 'Reset global config', description: 'Delete global overrides' }
|
|
159
|
+
];
|
|
160
|
+
container.addChild(new DynamicBorder((text) => theme.fg('accent', text)));
|
|
161
|
+
container.addChild(new Text(theme.fg('accent', theme.bold('pi-web-agent')), 1, 0));
|
|
162
|
+
const list = new SelectList(items, Math.min(items.length, 8), {
|
|
163
|
+
selectedPrefix: (text) => theme.fg('accent', text),
|
|
164
|
+
selectedText: (text) => theme.fg('accent', text),
|
|
165
|
+
description: (text) => theme.fg('muted', text),
|
|
166
|
+
scrollInfo: (text) => theme.fg('dim', text),
|
|
167
|
+
noMatch: (text) => theme.fg('warning', text)
|
|
168
|
+
});
|
|
169
|
+
list.onSelect = (item) => done(item.value);
|
|
170
|
+
list.onCancel = () => done(undefined);
|
|
171
|
+
container.addChild(list);
|
|
172
|
+
container.addChild(new Text(theme.fg('dim', '↑↓ navigate • enter select • esc cancel'), 1, 0));
|
|
173
|
+
container.addChild(new DynamicBorder((text) => theme.fg('accent', text)));
|
|
174
|
+
return {
|
|
175
|
+
render: (width) => container.render(width),
|
|
176
|
+
invalidate: () => container.invalidate(),
|
|
177
|
+
handleInput: (data) => {
|
|
178
|
+
list.handleInput?.(data);
|
|
179
|
+
tui.requestRender?.();
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
});
|
|
183
|
+
}
|
|
140
184
|
async function openSettingsUi(ctx, loaded, initialScope) {
|
|
141
185
|
return ctx.ui.custom((_tui, theme, _kb, done) => {
|
|
142
186
|
let state = createSettingsDraftState(loaded, initialScope);
|
|
@@ -182,10 +226,51 @@ export function registerWebAgentConfigCommands(pi, deps = {}) {
|
|
|
182
226
|
const load = deps.load ?? (() => loadPresentationConfigLayers());
|
|
183
227
|
const save = deps.save ?? ((scope, config) => savePresentationConfigScope({}, scope, config));
|
|
184
228
|
const reset = deps.reset ?? ((scope) => resetPresentationConfigScope({}, scope));
|
|
229
|
+
const resolveBrowser = deps.resolveBrowser ?? (() => resolveBrowserExecutable({}));
|
|
230
|
+
const runtime = deps.runtime ?? {
|
|
231
|
+
nodeVersion: process.version,
|
|
232
|
+
platform: process.platform,
|
|
233
|
+
arch: process.arch
|
|
234
|
+
};
|
|
235
|
+
const checkTypebox = deps.checkTypebox ?? defaultCheckTypebox;
|
|
185
236
|
pi.registerCommand('web-agent', {
|
|
186
237
|
description: 'Open settings or manage pi-web-agent presentation config',
|
|
187
238
|
handler: async (args, ctx) => {
|
|
188
|
-
|
|
239
|
+
let [action, maybeScope] = (args ?? '').trim().split(/\s+/).filter(Boolean);
|
|
240
|
+
if (!action) {
|
|
241
|
+
const selectedAction = await openActionMenu(ctx);
|
|
242
|
+
if (!selectedAction)
|
|
243
|
+
return;
|
|
244
|
+
if (selectedAction === 'reset-project') {
|
|
245
|
+
action = 'reset';
|
|
246
|
+
maybeScope = 'project';
|
|
247
|
+
}
|
|
248
|
+
else if (selectedAction === 'reset-global') {
|
|
249
|
+
action = 'reset';
|
|
250
|
+
maybeScope = 'global';
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
action = selectedAction;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
if (action === 'doctor') {
|
|
257
|
+
const [typeboxOk, browser] = await Promise.all([checkTypebox(), resolveBrowser()]);
|
|
258
|
+
const lines = [
|
|
259
|
+
'pi-web-agent: loaded',
|
|
260
|
+
`runtime: node ${runtime.nodeVersion} ${runtime.platform} ${runtime.arch}`,
|
|
261
|
+
`typebox: ${typeboxOk ? 'ok' : 'missing'}`
|
|
262
|
+
];
|
|
263
|
+
if (browser.ok) {
|
|
264
|
+
lines.push(`browser: ${browser.browser} ${browser.executablePath}`);
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
lines.push(`browser: missing (${browser.error.code})`);
|
|
268
|
+
lines.push(browser.error.message);
|
|
269
|
+
lines.push('Install Chrome, Chromium, Edge, or Brave and run /web-agent doctor again.');
|
|
270
|
+
}
|
|
271
|
+
ctx.ui.notify(lines.join('\n'), 'info');
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
189
274
|
if (action === 'show') {
|
|
190
275
|
const loaded = await load();
|
|
191
276
|
ctx.ui.notify([
|
|
@@ -243,7 +328,7 @@ export function registerWebAgentConfigCommands(pi, deps = {}) {
|
|
|
243
328
|
ctx.ui.notify(`Saved ${result.scope} config`, 'info');
|
|
244
329
|
return;
|
|
245
330
|
}
|
|
246
|
-
ctx.ui.notify('Use /web-agent, /web-agent show, /web-agent reset project, or /web-agent settings', 'info');
|
|
331
|
+
ctx.ui.notify('Use /web-agent, /web-agent show, /web-agent doctor, /web-agent reset project, or /web-agent settings', 'info');
|
|
247
332
|
}
|
|
248
333
|
});
|
|
249
334
|
}
|
package/dist/extension.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Type } from '
|
|
1
|
+
import { Type } from 'typebox';
|
|
2
2
|
import { registerWebAgentConfigCommands } from './commands/web-agent-config.js';
|
|
3
3
|
import { DEFAULT_PRESENTATION_CONFIG, resolvePresentationMode } from './presentation/config.js';
|
|
4
4
|
import { loadPresentationConfigLayers } from './presentation/config-store.js';
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
type SupportedPlatform = NodeJS.Platform | string;
|
|
2
|
+
type BrowserName = 'configured' | 'chrome' | 'edge' | 'brave' | 'chromium';
|
|
1
3
|
export type BrowserResolutionResult = {
|
|
2
4
|
ok: true;
|
|
3
5
|
executablePath: string;
|
|
4
|
-
browser:
|
|
6
|
+
browser: BrowserName;
|
|
5
7
|
} | {
|
|
6
8
|
ok: false;
|
|
7
9
|
error: {
|
|
@@ -9,7 +11,10 @@ export type BrowserResolutionResult = {
|
|
|
9
11
|
message: string;
|
|
10
12
|
};
|
|
11
13
|
};
|
|
12
|
-
export declare function resolveBrowserExecutable({ configuredPath, fileExists }: {
|
|
14
|
+
export declare function resolveBrowserExecutable({ configuredPath, platform, env, fileExists }: {
|
|
13
15
|
configuredPath?: string;
|
|
16
|
+
platform?: SupportedPlatform;
|
|
17
|
+
env?: NodeJS.ProcessEnv | Record<string, string | undefined>;
|
|
14
18
|
fileExists?: (path: string) => Promise<boolean>;
|
|
15
19
|
}): Promise<BrowserResolutionResult>;
|
|
20
|
+
export {};
|
|
@@ -1,14 +1,82 @@
|
|
|
1
|
-
const WINDOWS_CANDIDATES =
|
|
2
|
-
|
|
3
|
-
'
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
'
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
const WINDOWS_CANDIDATES = [
|
|
2
|
+
{
|
|
3
|
+
browser: 'chrome',
|
|
4
|
+
paths: [
|
|
5
|
+
'C:/Program Files/Google/Chrome/Application/chrome.exe',
|
|
6
|
+
'C:/Program Files (x86)/Google/Chrome/Application/chrome.exe'
|
|
7
|
+
],
|
|
8
|
+
commands: ['chrome.exe', 'chrome', 'google-chrome']
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
browser: 'edge',
|
|
12
|
+
paths: [
|
|
13
|
+
'C:/Program Files/Microsoft/Edge/Application/msedge.exe',
|
|
14
|
+
'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe'
|
|
15
|
+
],
|
|
16
|
+
commands: ['msedge.exe', 'msedge', 'microsoft-edge']
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
browser: 'brave',
|
|
20
|
+
paths: [
|
|
21
|
+
'C:/Program Files/BraveSoftware/Brave-Browser/Application/brave.exe',
|
|
22
|
+
'C:/Program Files (x86)/BraveSoftware/Brave-Browser/Application/brave.exe'
|
|
23
|
+
],
|
|
24
|
+
commands: ['brave.exe', 'brave']
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
browser: 'chromium',
|
|
28
|
+
paths: [
|
|
29
|
+
'C:/Program Files/Chromium/Application/chrome.exe',
|
|
30
|
+
'C:/Program Files (x86)/Chromium/Application/chrome.exe'
|
|
31
|
+
],
|
|
32
|
+
commands: ['chromium.exe', 'chromium']
|
|
33
|
+
}
|
|
34
|
+
];
|
|
35
|
+
const MACOS_CANDIDATES = [
|
|
36
|
+
{
|
|
37
|
+
browser: 'chrome',
|
|
38
|
+
paths: ['/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'],
|
|
39
|
+
commands: ['google-chrome', 'chrome']
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
browser: 'edge',
|
|
43
|
+
paths: ['/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge'],
|
|
44
|
+
commands: ['microsoft-edge', 'msedge']
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
browser: 'brave',
|
|
48
|
+
paths: ['/Applications/Brave Browser.app/Contents/MacOS/Brave Browser'],
|
|
49
|
+
commands: ['brave-browser', 'brave']
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
browser: 'chromium',
|
|
53
|
+
paths: ['/Applications/Chromium.app/Contents/MacOS/Chromium'],
|
|
54
|
+
commands: ['chromium', 'chromium-browser']
|
|
55
|
+
}
|
|
56
|
+
];
|
|
57
|
+
const LINUX_CANDIDATES = [
|
|
58
|
+
{
|
|
59
|
+
browser: 'chrome',
|
|
60
|
+
paths: ['/usr/bin/google-chrome', '/usr/local/bin/google-chrome', '/opt/google/chrome/chrome'],
|
|
61
|
+
commands: ['google-chrome', 'google-chrome-stable', 'chrome']
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
browser: 'edge',
|
|
65
|
+
paths: ['/usr/bin/microsoft-edge', '/opt/microsoft/msedge/msedge'],
|
|
66
|
+
commands: ['microsoft-edge', 'microsoft-edge-stable', 'msedge']
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
browser: 'brave',
|
|
70
|
+
paths: ['/usr/bin/brave-browser', '/usr/local/bin/brave-browser', '/opt/brave.com/brave/brave'],
|
|
71
|
+
commands: ['brave-browser', 'brave']
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
browser: 'chromium',
|
|
75
|
+
paths: ['/usr/bin/chromium', '/usr/bin/chromium-browser', '/snap/bin/chromium'],
|
|
76
|
+
commands: ['chromium', 'chromium-browser']
|
|
77
|
+
}
|
|
78
|
+
];
|
|
79
|
+
export async function resolveBrowserExecutable({ configuredPath, platform = process.platform, env = process.env, fileExists = defaultFileExists }) {
|
|
12
80
|
if (configuredPath) {
|
|
13
81
|
if (await fileExists(configuredPath)) {
|
|
14
82
|
return {
|
|
@@ -25,14 +93,19 @@ export async function resolveBrowserExecutable({ configuredPath, fileExists = de
|
|
|
25
93
|
}
|
|
26
94
|
};
|
|
27
95
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
96
|
+
const candidates = getCandidatesForPlatform(platform);
|
|
97
|
+
for (const candidate of candidates) {
|
|
98
|
+
for (const path of candidate.paths) {
|
|
99
|
+
if (await fileExists(path)) {
|
|
100
|
+
return { ok: true, executablePath: path, browser: candidate.browser };
|
|
101
|
+
}
|
|
31
102
|
}
|
|
32
103
|
}
|
|
33
|
-
for (const
|
|
34
|
-
|
|
35
|
-
|
|
104
|
+
for (const candidate of candidates) {
|
|
105
|
+
for (const path of getPathCommandCandidates(candidate.commands, platform, env)) {
|
|
106
|
+
if (await fileExists(path)) {
|
|
107
|
+
return { ok: true, executablePath: path, browser: candidate.browser };
|
|
108
|
+
}
|
|
36
109
|
}
|
|
37
110
|
}
|
|
38
111
|
return {
|
|
@@ -43,6 +116,27 @@ export async function resolveBrowserExecutable({ configuredPath, fileExists = de
|
|
|
43
116
|
}
|
|
44
117
|
};
|
|
45
118
|
}
|
|
119
|
+
function getCandidatesForPlatform(platform) {
|
|
120
|
+
if (platform === 'win32')
|
|
121
|
+
return WINDOWS_CANDIDATES;
|
|
122
|
+
if (platform === 'darwin')
|
|
123
|
+
return MACOS_CANDIDATES;
|
|
124
|
+
if (platform === 'linux')
|
|
125
|
+
return LINUX_CANDIDATES;
|
|
126
|
+
return [...WINDOWS_CANDIDATES, ...MACOS_CANDIDATES, ...LINUX_CANDIDATES];
|
|
127
|
+
}
|
|
128
|
+
function getPathCommandCandidates(commands, platform, env) {
|
|
129
|
+
const pathValue = env.PATH ?? env.Path ?? env.path;
|
|
130
|
+
if (!pathValue)
|
|
131
|
+
return [];
|
|
132
|
+
const delimiter = platform === 'win32' ? ';' : ':';
|
|
133
|
+
const dirs = pathValue.split(delimiter).filter(Boolean);
|
|
134
|
+
const extensions = platform === 'win32' ? ['', '.exe'] : [''];
|
|
135
|
+
return dirs.flatMap((dir) => commands.flatMap((command) => extensions.map((extension) => {
|
|
136
|
+
const normalizedCommand = command.toLowerCase().endsWith(extension) ? command : `${command}${extension}`;
|
|
137
|
+
return `${dir.replace(/[\\/]$/, '')}/${normalizedCommand}`;
|
|
138
|
+
})));
|
|
139
|
+
}
|
|
46
140
|
async function defaultFileExists(path) {
|
|
47
141
|
try {
|
|
48
142
|
const { access } = await import('node:fs/promises');
|
package/dist/types.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ export type FetchMetadata = {
|
|
|
19
19
|
cacheHit: boolean;
|
|
20
20
|
contentType?: string;
|
|
21
21
|
truncated?: boolean;
|
|
22
|
-
browser?: 'configured' | 'chrome' | 'edge';
|
|
22
|
+
browser?: 'configured' | 'chrome' | 'edge' | 'brave' | 'chromium';
|
|
23
23
|
navigationMs?: number;
|
|
24
24
|
};
|
|
25
25
|
export type ExtractedContent = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@demigodmode/pi-web-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Pi package for reliable web access with explicit search, fetch, and headless boundaries.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/extension.js",
|
|
@@ -56,11 +56,11 @@
|
|
|
56
56
|
"@mozilla/readability": "^0.6.0",
|
|
57
57
|
"cheerio": "^1.1.0",
|
|
58
58
|
"jsdom": "^26.0.0",
|
|
59
|
-
"playwright-core": "^1.54.0"
|
|
59
|
+
"playwright-core": "^1.54.0",
|
|
60
|
+
"typebox": "^1.1.37"
|
|
60
61
|
},
|
|
61
62
|
"devDependencies": {
|
|
62
|
-
"@mariozechner/pi-coding-agent": "^0.
|
|
63
|
-
"@sinclair/typebox": "^0.34.41",
|
|
63
|
+
"@mariozechner/pi-coding-agent": "^0.69.0",
|
|
64
64
|
"@types/jsdom": "^21.1.7",
|
|
65
65
|
"@types/node": "^24.0.0",
|
|
66
66
|
"@vitest/coverage-v8": "^3.2.4",
|
|
@@ -69,7 +69,6 @@
|
|
|
69
69
|
"vitest": "^3.2.0"
|
|
70
70
|
},
|
|
71
71
|
"peerDependencies": {
|
|
72
|
-
"@mariozechner/pi-coding-agent": "*"
|
|
73
|
-
"@sinclair/typebox": "*"
|
|
72
|
+
"@mariozechner/pi-coding-agent": "*"
|
|
74
73
|
}
|
|
75
74
|
}
|