smooth-operator-mcp 2.2.1 → 2.3.0
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 +1 -1
- package/dist/smooth-operator.mjs +101 -32
- package/dist/smooth-operator.mjs.map +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ Or straight from GitHub:
|
|
|
27
27
|
npm install -g github:Gitshop77/Smooth-Operator && smooth-operator install opencode
|
|
28
28
|
~~~
|
|
29
29
|
|
|
30
|
-
Wizard asks
|
|
30
|
+
Wizard asks 7 questions (browser — any Chromium-based browser is detected, managed vs personal Chrome, headless, domains, JS). Run `smooth-operator install` with no harness to pick one interactively (TTY only; piped/CI runs print usage and exit). `--yes` skips every prompt, so give it a target: `smooth-operator install opencode --yes`. Personal Chrome → wizard launches Chrome on `9222` for you.
|
|
31
31
|
|
|
32
32
|
Requires Node 22.23.2+ and Chrome. Profile at `~/.smooth-operator/browser` — sign in once.
|
|
33
33
|
|
package/dist/smooth-operator.mjs
CHANGED
|
@@ -287,7 +287,8 @@ var init_errors = __esm({
|
|
|
287
287
|
var discovery_exports = {};
|
|
288
288
|
__export(discovery_exports, {
|
|
289
289
|
chromeExecutableSearchPaths: () => chromeExecutableSearchPaths,
|
|
290
|
-
findChromeExecutable: () => findChromeExecutable
|
|
290
|
+
findChromeExecutable: () => findChromeExecutable,
|
|
291
|
+
findChromiumExecutables: () => findChromiumExecutables
|
|
291
292
|
});
|
|
292
293
|
import * as nodeFs from "node:fs";
|
|
293
294
|
import { homedir as homedir2 } from "node:os";
|
|
@@ -296,6 +297,9 @@ import { env as env2 } from "node:process";
|
|
|
296
297
|
function findChromeExecutable(fs = nodeFs) {
|
|
297
298
|
return chromeExecutableCandidates().find((candidate) => fs.existsSync(candidate.path)) ?? null;
|
|
298
299
|
}
|
|
300
|
+
function findChromiumExecutables(fs = nodeFs) {
|
|
301
|
+
return chromeExecutableCandidates().filter((candidate) => fs.existsSync(candidate.path));
|
|
302
|
+
}
|
|
299
303
|
function chromeExecutableSearchPaths() {
|
|
300
304
|
return chromeExecutableCandidates().map((candidate) => candidate.path);
|
|
301
305
|
}
|
|
@@ -309,16 +313,23 @@ function chromeExecutableCandidates() {
|
|
|
309
313
|
function macOsCandidates() {
|
|
310
314
|
const applicationDirectories = ["/Applications", join2(homedir2(), "Applications")];
|
|
311
315
|
return [
|
|
312
|
-
...applicationBundleCandidates(applicationDirectories, "Google Chrome", "Google Chrome", "stable"),
|
|
313
|
-
...applicationBundleCandidates(applicationDirectories, "Google Chrome Beta", "Google Chrome Beta", "beta"),
|
|
314
|
-
...applicationBundleCandidates(applicationDirectories, "Google Chrome Dev", "Google Chrome Dev", "dev"),
|
|
315
|
-
...applicationBundleCandidates(applicationDirectories, "Google Chrome Canary", "Google Chrome Canary", "canary")
|
|
316
|
+
...applicationBundleCandidates(applicationDirectories, "Google Chrome", "Google Chrome", "Google Chrome", "stable"),
|
|
317
|
+
...applicationBundleCandidates(applicationDirectories, "Google Chrome Beta", "Google Chrome Beta", "Google Chrome Beta", "beta"),
|
|
318
|
+
...applicationBundleCandidates(applicationDirectories, "Google Chrome Dev", "Google Chrome Dev", "Google Chrome Dev", "dev"),
|
|
319
|
+
...applicationBundleCandidates(applicationDirectories, "Google Chrome Canary", "Google Chrome Canary", "Google Chrome Canary", "canary"),
|
|
320
|
+
...applicationBundleCandidates(applicationDirectories, "Brave Browser", "Brave Browser", "Brave", "stable"),
|
|
321
|
+
...applicationBundleCandidates(applicationDirectories, "Microsoft Edge", "Microsoft Edge", "Microsoft Edge", "stable"),
|
|
322
|
+
...applicationBundleCandidates(applicationDirectories, "Chromium", "Chromium", "Chromium", "stable"),
|
|
323
|
+
...applicationBundleCandidates(applicationDirectories, "Vivaldi", "Vivaldi", "Vivaldi", "stable"),
|
|
324
|
+
...applicationBundleCandidates(applicationDirectories, "Arc", "Arc", "Arc", "stable"),
|
|
325
|
+
...applicationBundleCandidates(applicationDirectories, "Opera", "Opera", "Opera", "stable")
|
|
316
326
|
];
|
|
317
327
|
}
|
|
318
|
-
function applicationBundleCandidates(applicationDirectories, bundleName, executableName, channel) {
|
|
328
|
+
function applicationBundleCandidates(applicationDirectories, bundleName, executableName, label, channel) {
|
|
319
329
|
return applicationDirectories.map((applicationDirectory) => ({
|
|
320
330
|
path: join2(applicationDirectory, `${bundleName}.app`, "Contents", "MacOS", executableName),
|
|
321
|
-
channel
|
|
331
|
+
channel,
|
|
332
|
+
label
|
|
322
333
|
}));
|
|
323
334
|
}
|
|
324
335
|
function windowsCandidates() {
|
|
@@ -328,29 +339,39 @@ function windowsCandidates() {
|
|
|
328
339
|
env2.LOCALAPPDATA
|
|
329
340
|
].filter((directory) => Boolean(directory));
|
|
330
341
|
return [
|
|
331
|
-
...windowsChannelCandidates(directories, "Chrome", "stable"),
|
|
332
|
-
...windowsChannelCandidates(directories, "Chrome Beta", "beta"),
|
|
333
|
-
...windowsChannelCandidates(directories, "Chrome Dev", "dev"),
|
|
334
|
-
...windowsChannelCandidates(directories, "Chrome SxS", "canary")
|
|
342
|
+
...windowsChannelCandidates(directories, ["Google", "Chrome"], "chrome.exe", "Google Chrome", "stable"),
|
|
343
|
+
...windowsChannelCandidates(directories, ["Google", "Chrome Beta"], "chrome.exe", "Google Chrome Beta", "beta"),
|
|
344
|
+
...windowsChannelCandidates(directories, ["Google", "Chrome Dev"], "chrome.exe", "Google Chrome Dev", "dev"),
|
|
345
|
+
...windowsChannelCandidates(directories, ["Google", "Chrome SxS"], "chrome.exe", "Google Chrome Canary", "canary"),
|
|
346
|
+
...windowsChannelCandidates(directories, ["BraveSoftware", "Brave-Browser"], "brave.exe", "Brave", "stable"),
|
|
347
|
+
...windowsChannelCandidates(directories, ["Microsoft", "Edge"], "msedge.exe", "Microsoft Edge", "stable"),
|
|
348
|
+
...windowsChannelCandidates(directories, ["Chromium"], "chrome.exe", "Chromium", "stable"),
|
|
349
|
+
...windowsChannelCandidates(directories, ["Vivaldi"], "vivaldi.exe", "Vivaldi", "stable")
|
|
335
350
|
];
|
|
336
351
|
}
|
|
337
|
-
function windowsChannelCandidates(directories,
|
|
352
|
+
function windowsChannelCandidates(directories, directoryParts, executable, label, channel) {
|
|
338
353
|
return directories.map((baseDirectory) => ({
|
|
339
|
-
path: win32.join(baseDirectory,
|
|
340
|
-
channel
|
|
354
|
+
path: win32.join(baseDirectory, ...directoryParts, "Application", executable),
|
|
355
|
+
channel,
|
|
356
|
+
label
|
|
341
357
|
}));
|
|
342
358
|
}
|
|
343
359
|
function linuxCandidates() {
|
|
344
360
|
const searchDirectories = (env2.PATH ?? "").split(delimiter).filter(Boolean);
|
|
345
361
|
const commands = [
|
|
346
|
-
["google-chrome", "stable"],
|
|
347
|
-
["google-chrome-stable", "stable"],
|
|
348
|
-
["google-chrome-beta", "beta"],
|
|
349
|
-
["google-chrome-unstable", "dev"],
|
|
350
|
-
["
|
|
351
|
-
["
|
|
362
|
+
["google-chrome", "Google Chrome", "stable"],
|
|
363
|
+
["google-chrome-stable", "Google Chrome", "stable"],
|
|
364
|
+
["google-chrome-beta", "Google Chrome Beta", "beta"],
|
|
365
|
+
["google-chrome-unstable", "Google Chrome Dev", "dev"],
|
|
366
|
+
["brave-browser", "Brave", "stable"],
|
|
367
|
+
["microsoft-edge", "Microsoft Edge", "stable"],
|
|
368
|
+
["microsoft-edge-stable", "Microsoft Edge", "stable"],
|
|
369
|
+
["chromium", "Chromium", "stable"],
|
|
370
|
+
["chromium-browser", "Chromium", "stable"],
|
|
371
|
+
["vivaldi", "Vivaldi", "stable"],
|
|
372
|
+
["vivaldi-stable", "Vivaldi", "stable"]
|
|
352
373
|
];
|
|
353
|
-
return commands.flatMap(([command, channel]) => searchDirectories.map((directory) => ({ path: join2(directory, command), channel })));
|
|
374
|
+
return commands.flatMap(([command, label, channel]) => searchDirectories.map((directory) => ({ path: join2(directory, command), channel, label })));
|
|
354
375
|
}
|
|
355
376
|
var init_discovery = __esm({
|
|
356
377
|
"src/server/browser/discovery.ts"() {
|
|
@@ -1037,6 +1058,7 @@ __export(installer_wizard_exports, {
|
|
|
1037
1058
|
runWizard: () => runWizard
|
|
1038
1059
|
});
|
|
1039
1060
|
import { join as join7 } from "node:path";
|
|
1061
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
1040
1062
|
import { homedir as homedir4 } from "node:os";
|
|
1041
1063
|
function isRecord4(value) {
|
|
1042
1064
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
@@ -1093,9 +1115,43 @@ function recommendedDefaults(homeDir) {
|
|
|
1093
1115
|
allowedDomains: [],
|
|
1094
1116
|
blockedDomains: [],
|
|
1095
1117
|
allowEval: false,
|
|
1096
|
-
dataDir: join7(homeDir ?? homedir4(), ".smooth-operator")
|
|
1118
|
+
dataDir: join7(homeDir ?? homedir4(), ".smooth-operator"),
|
|
1119
|
+
browserExecutablePath: void 0
|
|
1097
1120
|
};
|
|
1098
1121
|
}
|
|
1122
|
+
async function askBrowser(session, ui) {
|
|
1123
|
+
const { findChromiumExecutables: findChromiumExecutables2 } = await Promise.resolve().then(() => (init_discovery(), discovery_exports));
|
|
1124
|
+
const detected = findChromiumExecutables2();
|
|
1125
|
+
if (detected.length > 0) {
|
|
1126
|
+
detected.forEach((candidate, index) => {
|
|
1127
|
+
ui.option(index + 1, candidate.label, candidate.path, index === 0);
|
|
1128
|
+
});
|
|
1129
|
+
while (true) {
|
|
1130
|
+
const answer = (await session.question(`Browser [1]: `)).trim();
|
|
1131
|
+
if (!answer || answer === "1") return detected[0].path;
|
|
1132
|
+
const numeric = Number.parseInt(answer, 10);
|
|
1133
|
+
if (`${numeric}` === answer && numeric >= 1 && numeric <= detected.length) {
|
|
1134
|
+
return detected[numeric - 1].path;
|
|
1135
|
+
}
|
|
1136
|
+
if (/^\d+$/.test(answer)) continue;
|
|
1137
|
+
if (answer.startsWith("/") && existsSync2(answer)) return answer;
|
|
1138
|
+
ui.failure("Enter a listed number or an existing absolute path.");
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
while (true) {
|
|
1142
|
+
const answer = (await session.question("Browser executable path (Enter = auto-detect): ")).trim();
|
|
1143
|
+
if (!answer) return void 0;
|
|
1144
|
+
if (!answer.startsWith("/")) {
|
|
1145
|
+
ui.failure("Enter an absolute path.");
|
|
1146
|
+
continue;
|
|
1147
|
+
}
|
|
1148
|
+
if (!existsSync2(answer)) {
|
|
1149
|
+
ui.failure("That path does not exist.");
|
|
1150
|
+
continue;
|
|
1151
|
+
}
|
|
1152
|
+
return answer;
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1099
1155
|
function tolerantQuestion(rl) {
|
|
1100
1156
|
return {
|
|
1101
1157
|
async question(prompt) {
|
|
@@ -1149,7 +1205,7 @@ async function runWizard(harness, opts) {
|
|
|
1149
1205
|
ui.note(`Configuring: ${harness}`);
|
|
1150
1206
|
ui.note("Answer each question, or press Enter to accept the recommended default.");
|
|
1151
1207
|
ui.note(`You can re-run \`smooth-operator install ${harness}\` at any time to change these.`);
|
|
1152
|
-
ui.step(1,
|
|
1208
|
+
ui.step(1, WIZARD_STEP_TOTAL, "Browser mode");
|
|
1153
1209
|
ui.explain([
|
|
1154
1210
|
"Who owns the Chrome window your AI drives?",
|
|
1155
1211
|
"",
|
|
@@ -1188,16 +1244,24 @@ async function runWizard(harness, opts) {
|
|
|
1188
1244
|
}
|
|
1189
1245
|
ui.failure("Enter 1, 2, or 3.");
|
|
1190
1246
|
}
|
|
1247
|
+
let browserExecutablePath;
|
|
1191
1248
|
let headlessChoice = false;
|
|
1192
1249
|
if (mode !== "disabled") {
|
|
1193
|
-
ui.step(2,
|
|
1250
|
+
ui.step(2, WIZARD_STEP_TOTAL, "Browser");
|
|
1251
|
+
ui.explain([
|
|
1252
|
+
"Any Chromium-based browser works: Chrome, Brave, Edge, Chromium,",
|
|
1253
|
+
"Vivaldi, Arc, Opera. The AI gets its own isolated profile inside the",
|
|
1254
|
+
"browser you pick - your everyday profiles are never touched."
|
|
1255
|
+
]);
|
|
1256
|
+
browserExecutablePath = await askBrowser(session, ui);
|
|
1257
|
+
ui.step(3, WIZARD_STEP_TOTAL, "Headless mode");
|
|
1194
1258
|
ui.explain([
|
|
1195
1259
|
"Headless runs Chrome with no visible window - lighter and invisible.",
|
|
1196
1260
|
"Visible Chrome lets you watch clicks happen and handle CAPTCHAs or",
|
|
1197
1261
|
"logins yourself when a site pauses for human verification."
|
|
1198
1262
|
]);
|
|
1199
1263
|
headlessChoice = await askYesNo(session, "Run Chrome headless (no window)?", false);
|
|
1200
|
-
ui.step(
|
|
1264
|
+
ui.step(4, WIZARD_STEP_TOTAL, "Allowed domains");
|
|
1201
1265
|
ui.explain([
|
|
1202
1266
|
"Restrict which sites the AI may open, e.g. docs.example.com, *.wikipedia.org",
|
|
1203
1267
|
"Leave empty to allow every site. Blocked domains always win over allowed ones."
|
|
@@ -1210,7 +1274,7 @@ async function runWizard(harness, opts) {
|
|
|
1210
1274
|
}
|
|
1211
1275
|
ui.failure("That did not look like a domain list. Example: example.com, *.shop.test");
|
|
1212
1276
|
}
|
|
1213
|
-
ui.step(
|
|
1277
|
+
ui.step(5, WIZARD_STEP_TOTAL, "Blocked domains");
|
|
1214
1278
|
ui.explain(["Never open these sites, even when everything else is allowed."]);
|
|
1215
1279
|
while (true) {
|
|
1216
1280
|
const parsed = parseDomainList(await session.question("Blocked domains (comma-separated, Enter for none): "));
|
|
@@ -1220,13 +1284,13 @@ async function runWizard(harness, opts) {
|
|
|
1220
1284
|
}
|
|
1221
1285
|
ui.failure("That did not look like a domain list. Example: ads.example.com");
|
|
1222
1286
|
}
|
|
1223
|
-
ui.step(
|
|
1287
|
+
ui.step(6, WIZARD_STEP_TOTAL, "JavaScript execution");
|
|
1224
1288
|
ui.explain([
|
|
1225
1289
|
"browser_evaluate runs arbitrary JavaScript on a page - powerful for scraping",
|
|
1226
1290
|
"but it can also trigger bot defenses. Most users never need it on."
|
|
1227
1291
|
]);
|
|
1228
1292
|
allowEval = await askYesNo(session, "Allow the AI to run JavaScript on pages?", false);
|
|
1229
|
-
ui.step(
|
|
1293
|
+
ui.step(7, WIZARD_STEP_TOTAL, "Data directory");
|
|
1230
1294
|
ui.explain([
|
|
1231
1295
|
"Where the private Chrome profile, logs, and downloads live.",
|
|
1232
1296
|
"Permissions are locked to 0600 so only your user can read them."
|
|
@@ -1254,8 +1318,8 @@ async function runWizard(harness, opts) {
|
|
|
1254
1318
|
}
|
|
1255
1319
|
}
|
|
1256
1320
|
}
|
|
1257
|
-
writeSummary(ui, harness, { mode, headless, allowedDomains, blockedDomains, allowEval, dataDir });
|
|
1258
|
-
return { mode, headless, allowedDomains, blockedDomains, allowEval, dataDir, browserUrl };
|
|
1321
|
+
writeSummary(ui, harness, { mode, headless, allowedDomains, blockedDomains, allowEval, dataDir, browserExecutablePath });
|
|
1322
|
+
return { mode, headless, allowedDomains, blockedDomains, allowEval, dataDir, browserUrl, browserExecutablePath };
|
|
1259
1323
|
} finally {
|
|
1260
1324
|
rl.close();
|
|
1261
1325
|
}
|
|
@@ -1269,6 +1333,7 @@ function writeSummary(ui, harness, choices) {
|
|
|
1269
1333
|
ui.banner("Configuration Summary", `Ready to configure ${harness}`, "");
|
|
1270
1334
|
ui.keyValues([
|
|
1271
1335
|
["Browser mode", choices.mode === "connect" ? modeLabel.connect : modeLabel[choices.mode] ?? choices.mode],
|
|
1336
|
+
...choices.browserExecutablePath ? [["Browser", choices.browserExecutablePath]] : [],
|
|
1272
1337
|
["Headless", choices.headless ? "yes - no visible window" : "no - you can watch and intervene"],
|
|
1273
1338
|
...choices.mode === "disabled" ? [] : [
|
|
1274
1339
|
["Allowed sites", choices.allowedDomains.length ? choices.allowedDomains.join(", ") : "all sites"],
|
|
@@ -1325,6 +1390,9 @@ async function persistWizardConfig(choices, homeDir) {
|
|
|
1325
1390
|
if (choices.browserUrl) {
|
|
1326
1391
|
chosenBrowser.url = choices.browserUrl;
|
|
1327
1392
|
}
|
|
1393
|
+
if (choices.browserExecutablePath) {
|
|
1394
|
+
chosenBrowser.executablePath = choices.browserExecutablePath;
|
|
1395
|
+
}
|
|
1328
1396
|
const chosenSecurity = {
|
|
1329
1397
|
allowEval: choices.allowEval,
|
|
1330
1398
|
allowedDomains: choices.allowedDomains,
|
|
@@ -1393,7 +1461,7 @@ async function launchPersonalChrome(opts) {
|
|
|
1393
1461
|
}
|
|
1394
1462
|
throw new AppError2("BROWSER_CONNECT_TIMEOUT", `Chrome DevTools endpoint on port ${port} did not become ready after ${attempts} probes. Close Chrome or choose another port.`);
|
|
1395
1463
|
}
|
|
1396
|
-
var PROBE_INTERVAL_MS, DEFAULT_PROBE_ATTEMPTS, HARNESS_MENU;
|
|
1464
|
+
var PROBE_INTERVAL_MS, DEFAULT_PROBE_ATTEMPTS, HARNESS_MENU, WIZARD_STEP_TOTAL;
|
|
1397
1465
|
var init_installer_wizard = __esm({
|
|
1398
1466
|
"src/server/installer-wizard.ts"() {
|
|
1399
1467
|
"use strict";
|
|
@@ -1411,6 +1479,7 @@ var init_installer_wizard = __esm({
|
|
|
1411
1479
|
{ id: "windsurf", label: "Windsurf", description: "Adds SmoothOperator to Windsurf's mcp_config.json" },
|
|
1412
1480
|
{ id: "claude-desktop", label: "Claude Desktop", description: "Updates claude_desktop_config.json" }
|
|
1413
1481
|
];
|
|
1482
|
+
WIZARD_STEP_TOTAL = 7;
|
|
1414
1483
|
}
|
|
1415
1484
|
});
|
|
1416
1485
|
|
|
@@ -2283,7 +2352,7 @@ init_errors();
|
|
|
2283
2352
|
init_logger();
|
|
2284
2353
|
|
|
2285
2354
|
// src/server/version.ts
|
|
2286
|
-
var SERVER_VERSION = "2.
|
|
2355
|
+
var SERVER_VERSION = "2.3.0";
|
|
2287
2356
|
|
|
2288
2357
|
// src/server/mcp.ts
|
|
2289
2358
|
var EmptyInputSchema = z3.object({}).strict();
|