clementine-agent 1.5.1 → 1.6.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/dist/cli/browser.d.ts +35 -0
- package/dist/cli/browser.js +310 -0
- package/dist/cli/dashboard.js +193 -51
- package/dist/cli/index.js +25 -0
- package/dist/dashboard/builder/serializer.d.ts +9 -9
- package/dist/dashboard/builder/serializer.js +162 -40
- package/dist/tools/builder-tools.js +2 -2
- package/dist/types.d.ts +1 -0
- package/package.json +2 -1
- package/vendor/browser-harness-mcp/README.md +27 -0
- package/vendor/browser-harness-mcp/pyproject.toml +13 -0
- package/vendor/browser-harness-mcp/server.py +133 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clementine CLI — browser-harness integration (Phase 1, beta).
|
|
3
|
+
*
|
|
4
|
+
* Subcommands:
|
|
5
|
+
* clementine browser status — show install / enable / CDP state
|
|
6
|
+
* clementine browser install — set up Python venv + clone harness
|
|
7
|
+
* clementine browser enable — register MCP server in mcp-servers.json
|
|
8
|
+
* clementine browser disable — remove the MCP entry (keeps files)
|
|
9
|
+
*
|
|
10
|
+
* Safety:
|
|
11
|
+
* - All subcommands fail soft. Missing Python or unsupported OS just prints
|
|
12
|
+
* a clear message and exits 0 (or 1 for hard errors); the daemon never
|
|
13
|
+
* auto-installs anything.
|
|
14
|
+
* - Enabling only writes a single entry to ~/.clementine/mcp-servers.json
|
|
15
|
+
* that mcp-bridge.ts already understands. No changes to assistant.ts.
|
|
16
|
+
* - If Python or the MCP server fail at runtime, the SDK logs the error and
|
|
17
|
+
* the rest of Clementine keeps running (every other MCP server is
|
|
18
|
+
* unaffected).
|
|
19
|
+
*/
|
|
20
|
+
export declare function cmdBrowserStatus(): Promise<void>;
|
|
21
|
+
export declare function cmdBrowserInstall(): Promise<void>;
|
|
22
|
+
export declare function cmdBrowserEnable(): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Auto-prompt during `clementine update`. Stays silent unless there's
|
|
25
|
+
* something actionable — mirrors the keychain wizard's behavior.
|
|
26
|
+
*
|
|
27
|
+
* Skips prompting when:
|
|
28
|
+
* - Not in an interactive TTY
|
|
29
|
+
* - The MCP wrapper isn't shipped with this version
|
|
30
|
+
* - Browser harness is already installed AND enabled
|
|
31
|
+
* - User previously dismissed the prompt
|
|
32
|
+
*/
|
|
33
|
+
export declare function maybePromptBrowserHarness(): Promise<void>;
|
|
34
|
+
export declare function cmdBrowserDisable(): Promise<void>;
|
|
35
|
+
//# sourceMappingURL=browser.d.ts.map
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clementine CLI — browser-harness integration (Phase 1, beta).
|
|
3
|
+
*
|
|
4
|
+
* Subcommands:
|
|
5
|
+
* clementine browser status — show install / enable / CDP state
|
|
6
|
+
* clementine browser install — set up Python venv + clone harness
|
|
7
|
+
* clementine browser enable — register MCP server in mcp-servers.json
|
|
8
|
+
* clementine browser disable — remove the MCP entry (keeps files)
|
|
9
|
+
*
|
|
10
|
+
* Safety:
|
|
11
|
+
* - All subcommands fail soft. Missing Python or unsupported OS just prints
|
|
12
|
+
* a clear message and exits 0 (or 1 for hard errors); the daemon never
|
|
13
|
+
* auto-installs anything.
|
|
14
|
+
* - Enabling only writes a single entry to ~/.clementine/mcp-servers.json
|
|
15
|
+
* that mcp-bridge.ts already understands. No changes to assistant.ts.
|
|
16
|
+
* - If Python or the MCP server fail at runtime, the SDK logs the error and
|
|
17
|
+
* the rest of Clementine keeps running (every other MCP server is
|
|
18
|
+
* unaffected).
|
|
19
|
+
*/
|
|
20
|
+
import { execSync, spawnSync } from 'node:child_process';
|
|
21
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
22
|
+
import os from 'node:os';
|
|
23
|
+
import path from 'node:path';
|
|
24
|
+
import { fileURLToPath } from 'node:url';
|
|
25
|
+
import { confirm } from '@inquirer/prompts';
|
|
26
|
+
const BOLD = '\x1b[1m';
|
|
27
|
+
const DIM = '\x1b[0;90m';
|
|
28
|
+
const GREEN = '\x1b[0;32m';
|
|
29
|
+
const YELLOW = '\x1b[1;33m';
|
|
30
|
+
const RED = '\x1b[0;31m';
|
|
31
|
+
const CYAN = '\x1b[0;36m';
|
|
32
|
+
const RESET = '\x1b[0m';
|
|
33
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
34
|
+
const __dirname = path.dirname(__filename);
|
|
35
|
+
const BASE_DIR = process.env.CLEMENTINE_HOME || path.join(os.homedir(), '.clementine');
|
|
36
|
+
const PACKAGE_ROOT = path.resolve(__dirname, '..', '..');
|
|
37
|
+
const MCP_SCRIPT = path.join(PACKAGE_ROOT, 'vendor', 'browser-harness-mcp', 'server.py');
|
|
38
|
+
const HARNESS_HOME = path.join(BASE_DIR, 'browser-harness');
|
|
39
|
+
const VENV_DIR = path.join(BASE_DIR, 'browser-harness-mcp-venv');
|
|
40
|
+
const VENV_PYTHON = path.join(VENV_DIR, 'bin', 'python3');
|
|
41
|
+
const MCP_SERVERS_FILE = path.join(BASE_DIR, 'mcp-servers.json');
|
|
42
|
+
const HARNESS_REPO = 'https://github.com/browser-use/browser-harness.git';
|
|
43
|
+
const SERVER_NAME = 'browser-harness';
|
|
44
|
+
const DISMISSED_MARKER = path.join(BASE_DIR, '.browser-harness-dismissed');
|
|
45
|
+
function commandExists(cmd) {
|
|
46
|
+
const result = spawnSync('which', [cmd], { stdio: 'pipe' });
|
|
47
|
+
return result.status === 0;
|
|
48
|
+
}
|
|
49
|
+
function pythonVersion() {
|
|
50
|
+
try {
|
|
51
|
+
const out = execSync('python3 --version', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
52
|
+
return out.trim();
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function loadMcpServers() {
|
|
59
|
+
if (!existsSync(MCP_SERVERS_FILE))
|
|
60
|
+
return {};
|
|
61
|
+
try {
|
|
62
|
+
return JSON.parse(readFileSync(MCP_SERVERS_FILE, 'utf-8'));
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return {};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function saveMcpServers(servers) {
|
|
69
|
+
if (!existsSync(BASE_DIR))
|
|
70
|
+
mkdirSync(BASE_DIR, { recursive: true });
|
|
71
|
+
writeFileSync(MCP_SERVERS_FILE, JSON.stringify(servers, null, 2) + '\n');
|
|
72
|
+
}
|
|
73
|
+
export async function cmdBrowserStatus() {
|
|
74
|
+
const py = pythonVersion();
|
|
75
|
+
const mcpScriptOk = existsSync(MCP_SCRIPT);
|
|
76
|
+
const venvOk = existsSync(VENV_PYTHON);
|
|
77
|
+
const harnessOk = existsSync(path.join(HARNESS_HOME, 'src'));
|
|
78
|
+
const servers = loadMcpServers();
|
|
79
|
+
const enabled = Object.prototype.hasOwnProperty.call(servers, SERVER_NAME);
|
|
80
|
+
console.log();
|
|
81
|
+
console.log(` ${BOLD}Browser Harness${RESET} ${DIM}(beta)${RESET}`);
|
|
82
|
+
console.log();
|
|
83
|
+
console.log(` ${py ? GREEN + '✓' : RED + '✗'}${RESET} python3 ${DIM}${py ?? 'not found'}${RESET}`);
|
|
84
|
+
console.log(` ${mcpScriptOk ? GREEN + '✓' : RED + '✗'}${RESET} MCP wrapper ${DIM}${MCP_SCRIPT}${RESET}`);
|
|
85
|
+
console.log(` ${venvOk ? GREEN + '✓' : YELLOW + '○'}${RESET} venv installed ${DIM}${VENV_DIR}${RESET}`);
|
|
86
|
+
console.log(` ${harnessOk ? GREEN + '✓' : YELLOW + '○'}${RESET} harness cloned ${DIM}${HARNESS_HOME}${RESET}`);
|
|
87
|
+
console.log(` ${enabled ? GREEN + '✓' : DIM + '○'}${RESET} MCP entry ${DIM}${enabled ? 'enabled' : 'disabled'} in mcp-servers.json${RESET}`);
|
|
88
|
+
console.log();
|
|
89
|
+
if (!py) {
|
|
90
|
+
console.log(` ${YELLOW}Install Python 3.10+ first:${RESET}`);
|
|
91
|
+
console.log(` ${CYAN}brew install python@3.12${RESET}`);
|
|
92
|
+
console.log();
|
|
93
|
+
}
|
|
94
|
+
else if (!venvOk || !harnessOk) {
|
|
95
|
+
console.log(` Next: ${BOLD}clementine browser install${RESET}`);
|
|
96
|
+
console.log();
|
|
97
|
+
}
|
|
98
|
+
else if (!enabled) {
|
|
99
|
+
console.log(` Next: ${BOLD}clementine browser enable${RESET}`);
|
|
100
|
+
console.log();
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
console.log(` ${GREEN}Ready.${RESET} ${DIM}Restart the daemon to pick up changes: clementine restart${RESET}`);
|
|
104
|
+
console.log();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Core install logic. Returns true on success, false on any failure.
|
|
109
|
+
* Prints progress + errors to stdout/stderr but never calls process.exit.
|
|
110
|
+
*/
|
|
111
|
+
async function runInstall() {
|
|
112
|
+
console.log();
|
|
113
|
+
console.log(` ${BOLD}Installing browser-harness${RESET} ${DIM}(beta)${RESET}`);
|
|
114
|
+
console.log();
|
|
115
|
+
if (!pythonVersion()) {
|
|
116
|
+
console.error(` ${RED}python3 not found.${RESET} Install Python 3.10+ first:`);
|
|
117
|
+
console.error(` ${CYAN}brew install python@3.12${RESET}`);
|
|
118
|
+
console.error();
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
if (!existsSync(MCP_SCRIPT)) {
|
|
122
|
+
console.error(` ${RED}MCP wrapper not found at:${RESET} ${MCP_SCRIPT}`);
|
|
123
|
+
console.error(` ${DIM}This means the package was installed without vendor/ files. Reinstall:${RESET}`);
|
|
124
|
+
console.error(` ${CYAN}npm install -g clementine-agent@latest${RESET}`);
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
if (!existsSync(BASE_DIR))
|
|
128
|
+
mkdirSync(BASE_DIR, { recursive: true });
|
|
129
|
+
// Step 1: clone the harness if missing
|
|
130
|
+
if (!existsSync(HARNESS_HOME)) {
|
|
131
|
+
if (!commandExists('git')) {
|
|
132
|
+
console.error(` ${RED}git not found.${RESET} Install git, then re-run.`);
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
console.log(` ${DIM}→ cloning ${HARNESS_REPO}${RESET}`);
|
|
136
|
+
try {
|
|
137
|
+
execSync(`git clone --depth 1 ${HARNESS_REPO} "${HARNESS_HOME}"`, { stdio: 'inherit' });
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
console.error(` ${RED}Clone failed.${RESET} Check network / git access and try again.`);
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
console.log(` ${GREEN}✓${RESET} harness already cloned at ${DIM}${HARNESS_HOME}${RESET}`);
|
|
146
|
+
}
|
|
147
|
+
// Step 2: create venv if missing
|
|
148
|
+
if (!existsSync(VENV_PYTHON)) {
|
|
149
|
+
console.log(` ${DIM}→ creating venv at ${VENV_DIR}${RESET}`);
|
|
150
|
+
try {
|
|
151
|
+
execSync(`python3 -m venv "${VENV_DIR}"`, { stdio: 'inherit' });
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
console.error(` ${RED}venv creation failed.${RESET}`);
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
console.log(` ${GREEN}✓${RESET} venv already exists`);
|
|
160
|
+
}
|
|
161
|
+
// Step 3: install MCP deps + harness deps
|
|
162
|
+
console.log(` ${DIM}→ installing python deps (mcp, websockets, browser-harness)${RESET}`);
|
|
163
|
+
try {
|
|
164
|
+
execSync(`"${VENV_PYTHON}" -m pip install --upgrade pip --quiet`, { stdio: 'inherit' });
|
|
165
|
+
execSync(`"${VENV_PYTHON}" -m pip install --quiet "mcp>=1.0.0" "websockets>=12.0"`, { stdio: 'inherit' });
|
|
166
|
+
// Install harness deps from its pyproject.toml if present
|
|
167
|
+
const harnessPyproject = path.join(HARNESS_HOME, 'pyproject.toml');
|
|
168
|
+
if (existsSync(harnessPyproject)) {
|
|
169
|
+
execSync(`"${VENV_PYTHON}" -m pip install --quiet -e "${HARNESS_HOME}"`, { stdio: 'inherit' });
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
console.error(` ${RED}pip install failed.${RESET} Inspect output above and re-run when fixed.`);
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
console.log();
|
|
177
|
+
console.log(` ${GREEN}✓${RESET} Install complete.`);
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Core enable logic. Returns true on success, false on any failure.
|
|
182
|
+
*/
|
|
183
|
+
function runEnable() {
|
|
184
|
+
if (!existsSync(VENV_PYTHON) || !existsSync(MCP_SCRIPT)) {
|
|
185
|
+
console.error();
|
|
186
|
+
console.error(` ${RED}Not installed yet.${RESET} Run ${BOLD}clementine browser install${RESET} first.`);
|
|
187
|
+
console.error();
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
const servers = loadMcpServers();
|
|
191
|
+
servers[SERVER_NAME] = {
|
|
192
|
+
type: 'stdio',
|
|
193
|
+
command: VENV_PYTHON,
|
|
194
|
+
args: [MCP_SCRIPT],
|
|
195
|
+
env: {
|
|
196
|
+
BROWSER_HARNESS_HOME: HARNESS_HOME,
|
|
197
|
+
BROWSER_CDP_URL: process.env.BROWSER_CDP_URL || 'ws://localhost:9222',
|
|
198
|
+
},
|
|
199
|
+
description: 'Drive the user\'s real Chrome via CDP (browser-use/browser-harness)',
|
|
200
|
+
enabled: true,
|
|
201
|
+
source: 'user',
|
|
202
|
+
};
|
|
203
|
+
saveMcpServers(servers);
|
|
204
|
+
console.log();
|
|
205
|
+
console.log(` ${GREEN}✓${RESET} Registered ${BOLD}${SERVER_NAME}${RESET} in mcp-servers.json`);
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
export async function cmdBrowserInstall() {
|
|
209
|
+
const ok = await runInstall();
|
|
210
|
+
if (!ok)
|
|
211
|
+
process.exit(1);
|
|
212
|
+
console.log();
|
|
213
|
+
console.log(` ${BOLD}Next steps:${RESET}`);
|
|
214
|
+
console.log(` 1. Enable Chrome remote debugging — open Chrome with:`);
|
|
215
|
+
console.log(` ${CYAN}/Applications/Google\\ Chrome.app/Contents/MacOS/Google\\ Chrome \\${RESET}`);
|
|
216
|
+
console.log(` ${CYAN}--remote-debugging-port=9222${RESET}`);
|
|
217
|
+
console.log(` 2. Enable the MCP server: ${BOLD}clementine browser enable${RESET}`);
|
|
218
|
+
console.log(` 3. Restart the daemon: ${BOLD}clementine restart${RESET}`);
|
|
219
|
+
console.log();
|
|
220
|
+
}
|
|
221
|
+
export async function cmdBrowserEnable() {
|
|
222
|
+
const ok = runEnable();
|
|
223
|
+
if (!ok)
|
|
224
|
+
process.exit(1);
|
|
225
|
+
console.log(` ${DIM}Restart the daemon to pick up the change: clementine restart${RESET}`);
|
|
226
|
+
console.log();
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Auto-prompt during `clementine update`. Stays silent unless there's
|
|
230
|
+
* something actionable — mirrors the keychain wizard's behavior.
|
|
231
|
+
*
|
|
232
|
+
* Skips prompting when:
|
|
233
|
+
* - Not in an interactive TTY
|
|
234
|
+
* - The MCP wrapper isn't shipped with this version
|
|
235
|
+
* - Browser harness is already installed AND enabled
|
|
236
|
+
* - User previously dismissed the prompt
|
|
237
|
+
*/
|
|
238
|
+
export async function maybePromptBrowserHarness() {
|
|
239
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY)
|
|
240
|
+
return;
|
|
241
|
+
if (!existsSync(MCP_SCRIPT))
|
|
242
|
+
return;
|
|
243
|
+
const servers = loadMcpServers();
|
|
244
|
+
const enabled = Object.prototype.hasOwnProperty.call(servers, SERVER_NAME);
|
|
245
|
+
const installed = existsSync(VENV_PYTHON);
|
|
246
|
+
if (enabled && installed)
|
|
247
|
+
return;
|
|
248
|
+
if (existsSync(DISMISSED_MARKER))
|
|
249
|
+
return;
|
|
250
|
+
console.log();
|
|
251
|
+
console.log(` ${BOLD}Browser Harness available${RESET} ${DIM}(beta, opt-in)${RESET}`);
|
|
252
|
+
console.log(` ${DIM}Lets Clementine drive your real Chrome — fill forms, post on LinkedIn,${RESET}`);
|
|
253
|
+
console.log(` ${DIM}book appointments — using your live browser session.${RESET}`);
|
|
254
|
+
console.log();
|
|
255
|
+
let answer;
|
|
256
|
+
try {
|
|
257
|
+
answer = await confirm({
|
|
258
|
+
message: 'Install Browser Harness now?',
|
|
259
|
+
default: false,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
// User Ctrl+C'd or terminal closed — treat as decline, don't dismiss permanently
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if (!answer) {
|
|
267
|
+
try {
|
|
268
|
+
writeFileSync(DISMISSED_MARKER, new Date().toISOString() + '\n');
|
|
269
|
+
}
|
|
270
|
+
catch { /* non-fatal */ }
|
|
271
|
+
console.log(` ${DIM}Skipped. To install later: clementine browser install${RESET}`);
|
|
272
|
+
console.log();
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
// User said yes — run install + enable inline
|
|
276
|
+
const installOk = await runInstall();
|
|
277
|
+
if (!installOk) {
|
|
278
|
+
console.error(` ${YELLOW}Install failed.${RESET} ${DIM}You can retry with: clementine browser install${RESET}`);
|
|
279
|
+
console.log();
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
const enableOk = runEnable();
|
|
283
|
+
if (!enableOk) {
|
|
284
|
+
console.error(` ${YELLOW}Enable failed.${RESET} ${DIM}Retry with: clementine browser enable${RESET}`);
|
|
285
|
+
console.log();
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
console.log();
|
|
289
|
+
console.log(` ${GREEN}✓${RESET} Browser Harness installed and enabled.`);
|
|
290
|
+
console.log(` ${DIM}Open Chrome with --remote-debugging-port=9222 to connect.${RESET}`);
|
|
291
|
+
console.log();
|
|
292
|
+
}
|
|
293
|
+
export async function cmdBrowserDisable() {
|
|
294
|
+
const servers = loadMcpServers();
|
|
295
|
+
if (!Object.prototype.hasOwnProperty.call(servers, SERVER_NAME)) {
|
|
296
|
+
console.log();
|
|
297
|
+
console.log(` ${DIM}${SERVER_NAME} is already disabled.${RESET}`);
|
|
298
|
+
console.log();
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
delete servers[SERVER_NAME];
|
|
302
|
+
saveMcpServers(servers);
|
|
303
|
+
console.log();
|
|
304
|
+
console.log(` ${GREEN}✓${RESET} Removed ${BOLD}${SERVER_NAME}${RESET} from mcp-servers.json`);
|
|
305
|
+
console.log(` ${DIM}venv and harness clone are kept. To fully remove:${RESET}`);
|
|
306
|
+
console.log(` ${CYAN}rm -rf "${VENV_DIR}" "${HARNESS_HOME}"${RESET}`);
|
|
307
|
+
console.log(` ${DIM}Restart the daemon: clementine restart${RESET}`);
|
|
308
|
+
console.log();
|
|
309
|
+
}
|
|
310
|
+
//# sourceMappingURL=browser.js.map
|