@pasko70/pibo 1.0.1 → 1.0.3
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.
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { writeFile } from 'node:fs/promises';
|
|
3
|
+
import { detectDesktopEnv, hasDesktopDisplay, printLinuxVirtualDisplayHint } from './desktop-env.js';
|
|
4
|
+
export const PIBO_XVFB_SERVICE_NAME = 'pibo-xvfb.service';
|
|
5
|
+
export const PIBO_XVFB_SERVICE_PATH = `/etc/systemd/system/${PIBO_XVFB_SERVICE_NAME}`;
|
|
6
|
+
export function createPiboXvfbServiceUnit() {
|
|
7
|
+
return [
|
|
8
|
+
'[Unit]',
|
|
9
|
+
'Description=Virtual X display for Pibo browser automation',
|
|
10
|
+
'After=network.target',
|
|
11
|
+
'',
|
|
12
|
+
'[Service]',
|
|
13
|
+
'Type=simple',
|
|
14
|
+
'ExecStart=/usr/bin/Xvfb :0 -screen 0 1920x1080x24 -ac -nolisten tcp',
|
|
15
|
+
'Restart=always',
|
|
16
|
+
'RestartSec=2',
|
|
17
|
+
'',
|
|
18
|
+
'[Install]',
|
|
19
|
+
'WantedBy=multi-user.target',
|
|
20
|
+
'',
|
|
21
|
+
].join('\n');
|
|
22
|
+
}
|
|
23
|
+
function isRootUser() {
|
|
24
|
+
return typeof process.getuid === 'function' && process.getuid() === 0;
|
|
25
|
+
}
|
|
26
|
+
async function waitForDisplaySocket(timeoutMs = 5000) {
|
|
27
|
+
const deadline = Date.now() + timeoutMs;
|
|
28
|
+
while (Date.now() < deadline) {
|
|
29
|
+
if (existsSync('/tmp/.X11-unix/X0'))
|
|
30
|
+
return true;
|
|
31
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
32
|
+
}
|
|
33
|
+
return existsSync('/tmp/.X11-unix/X0');
|
|
34
|
+
}
|
|
35
|
+
export async function ensureLinuxVirtualDisplay(options) {
|
|
36
|
+
if (process.platform !== 'linux')
|
|
37
|
+
return false;
|
|
38
|
+
if (hasDesktopDisplay(detectDesktopEnv()))
|
|
39
|
+
return false;
|
|
40
|
+
if (!isRootUser()) {
|
|
41
|
+
console.log('No desktop display detected and automatic virtual display setup requires root.');
|
|
42
|
+
printLinuxVirtualDisplayHint(' ');
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
console.log('No desktop display detected. Provisioning virtual X display for browser-use.');
|
|
46
|
+
await options.runInherited('apt-get', ['update']);
|
|
47
|
+
await options.runInherited('apt-get', ['install', '-y', 'xvfb', 'xauth', 'x11-xserver-utils']);
|
|
48
|
+
await writeFile(PIBO_XVFB_SERVICE_PATH, createPiboXvfbServiceUnit(), 'utf8');
|
|
49
|
+
await options.runInherited('systemctl', ['daemon-reload']);
|
|
50
|
+
await options.runInherited('systemctl', ['enable', '--now', PIBO_XVFB_SERVICE_NAME]);
|
|
51
|
+
process.env.DISPLAY = ':0';
|
|
52
|
+
const ready = await waitForDisplaySocket();
|
|
53
|
+
if (!ready) {
|
|
54
|
+
throw new Error('Virtual X display service was installed but DISPLAY :0 did not become ready.');
|
|
55
|
+
}
|
|
56
|
+
console.log('Virtual X display ready on DISPLAY=:0');
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
@@ -49,7 +49,32 @@ function runBuffered(command, args, env = process.env) {
|
|
|
49
49
|
});
|
|
50
50
|
});
|
|
51
51
|
}
|
|
52
|
-
function
|
|
52
|
+
function isRootUser() {
|
|
53
|
+
return typeof process.getuid === 'function' && process.getuid() === 0;
|
|
54
|
+
}
|
|
55
|
+
async function resolveUvCommand() {
|
|
56
|
+
const uv = await runBuffered('uv', ['--version']);
|
|
57
|
+
if (uv.ok)
|
|
58
|
+
return 'uv';
|
|
59
|
+
const localUv = join(homedir(), '.local', 'bin', 'uv');
|
|
60
|
+
if (existsSync(localUv)) {
|
|
61
|
+
const local = await runBuffered(localUv, ['--version']);
|
|
62
|
+
if (local.ok)
|
|
63
|
+
return localUv;
|
|
64
|
+
}
|
|
65
|
+
if (process.platform === 'linux' && isRootUser()) {
|
|
66
|
+
console.log('uv was not found on PATH. Installing uv automatically.');
|
|
67
|
+
await runInheritedCommand('sh', ['-c', 'curl -LsSf https://astral.sh/uv/install.sh | sh']);
|
|
68
|
+
const installed = await runBuffered(localUv, ['--version'], {
|
|
69
|
+
...process.env,
|
|
70
|
+
PATH: `${join(homedir(), '.local', 'bin')}:${process.env.PATH ?? ''}`,
|
|
71
|
+
});
|
|
72
|
+
if (installed.ok)
|
|
73
|
+
return localUv;
|
|
74
|
+
}
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
export function runInheritedCommand(command, args, env = process.env) {
|
|
53
78
|
return new Promise((resolve, reject) => {
|
|
54
79
|
console.log(`Running: ${command} ${args.join(' ')}`);
|
|
55
80
|
const child = spawn(command, args, {
|
|
@@ -82,9 +107,10 @@ function runInherited(command, args, env = process.env) {
|
|
|
82
107
|
}
|
|
83
108
|
export async function printToolPythonRuntimeDoctor(name, spec) {
|
|
84
109
|
const paths = getToolPythonRuntimePaths(name, spec);
|
|
85
|
-
const
|
|
110
|
+
const uvCommand = await resolveUvCommand();
|
|
111
|
+
const uv = uvCommand ? await runBuffered(uvCommand, ['--version']) : { ok: false, output: 'missing' };
|
|
86
112
|
const python = uv.ok
|
|
87
|
-
? await runBuffered(
|
|
113
|
+
? await runBuffered(uvCommand, ['python', 'find', spec.pythonVersion])
|
|
88
114
|
: { ok: false, output: 'skipped because uv is missing' };
|
|
89
115
|
console.log(`${name}`);
|
|
90
116
|
console.log(` uv: ${uv.ok ? uv.output || 'ok' : `missing (${uv.output})`}`);
|
|
@@ -122,13 +148,13 @@ export async function printToolPythonRuntimeDoctor(name, spec) {
|
|
|
122
148
|
}
|
|
123
149
|
}
|
|
124
150
|
export async function installToolPythonRuntime(name, spec) {
|
|
125
|
-
const
|
|
126
|
-
if (!
|
|
151
|
+
const uvCommand = await resolveUvCommand();
|
|
152
|
+
if (!uvCommand) {
|
|
127
153
|
throw new Error(formatCliError({
|
|
128
154
|
code: ErrorCode.CLIENT_ERROR,
|
|
129
155
|
type: 'CLI_TOOL_UV_MISSING',
|
|
130
156
|
message: 'uv was not found on PATH',
|
|
131
|
-
details: uv
|
|
157
|
+
details: 'spawn uv ENOENT',
|
|
132
158
|
suggestion: 'Install uv first. macOS/Linux: curl -LsSf https://astral.sh/uv/install.sh | sh. Windows PowerShell: irm https://astral.sh/uv/install.ps1 | iex.',
|
|
133
159
|
}));
|
|
134
160
|
}
|
|
@@ -136,9 +162,9 @@ export async function installToolPythonRuntime(name, spec) {
|
|
|
136
162
|
await mkdir(paths.rootDir, { recursive: true });
|
|
137
163
|
await mkdir(paths.homeDir, { recursive: true });
|
|
138
164
|
if (!existsSync(paths.venvDir)) {
|
|
139
|
-
await
|
|
165
|
+
await runInheritedCommand(uvCommand, ['venv', paths.venvDir, '--python', spec.pythonVersion]);
|
|
140
166
|
}
|
|
141
|
-
await
|
|
167
|
+
await runInheritedCommand(uvCommand, [
|
|
142
168
|
'pip',
|
|
143
169
|
'install',
|
|
144
170
|
'--python',
|
|
@@ -146,7 +172,7 @@ export async function installToolPythonRuntime(name, spec) {
|
|
|
146
172
|
spec.packageName,
|
|
147
173
|
]);
|
|
148
174
|
if (spec.postInstallArgs?.length) {
|
|
149
|
-
await
|
|
175
|
+
await runInheritedCommand(paths.executablePath, spec.postInstallArgs, getToolPythonRuntimeEnv(paths, spec));
|
|
150
176
|
}
|
|
151
177
|
return paths;
|
|
152
178
|
}
|
package/dist/tools/registry.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { detectDesktopEnv, hasDesktopDisplay, printDesktopEnvStatus, printLinuxVirtualDisplayHint } from './desktop-env.js';
|
|
3
3
|
import { BROWSER_USE_GUIDE, REMOTE_BROWSER_GUIDE } from './guides.js';
|
|
4
|
-
import {
|
|
4
|
+
import { ensureLinuxVirtualDisplay } from './linux-virtual-display.js';
|
|
5
|
+
import { getToolPythonRuntimePaths, installToolPythonRuntime, runInheritedCommand, printToolPythonRuntimeDoctor, removeToolPythonRuntime, } from './python-runtime.js';
|
|
5
6
|
const MAX_AGENT_CONTEXT_SNIPPET_LENGTH = 480;
|
|
6
7
|
const INSTALLED_TOOLS_CONTEXT_PATH = '.pibo/context/installed-pibo-tools.md';
|
|
7
8
|
const REGISTRY = [
|
|
@@ -90,6 +91,9 @@ export function getInstalledCliToolContextFile() {
|
|
|
90
91
|
}
|
|
91
92
|
export async function installCliTool(entry, runSetup) {
|
|
92
93
|
if (runSetup) {
|
|
94
|
+
if (entry.name === 'browser-use') {
|
|
95
|
+
await ensureLinuxVirtualDisplay({ runInherited: runInheritedCommand });
|
|
96
|
+
}
|
|
93
97
|
await installToolPythonRuntime(entry.name, entry.runtime);
|
|
94
98
|
}
|
|
95
99
|
const status = getCliToolStatus(entry);
|