smooth-operator-mcp 2.1.1 → 2.2.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/dist/smooth-operator.mjs +318 -44
- package/dist/smooth-operator.mjs.map +3 -3
- package/package.json +1 -1
package/dist/smooth-operator.mjs
CHANGED
|
@@ -882,6 +882,99 @@ var init_installer = __esm({
|
|
|
882
882
|
}
|
|
883
883
|
});
|
|
884
884
|
|
|
885
|
+
// src/server/ui.ts
|
|
886
|
+
var ui_exports = {};
|
|
887
|
+
__export(ui_exports, {
|
|
888
|
+
createUi: () => createUi
|
|
889
|
+
});
|
|
890
|
+
function colorEnabled(stdout) {
|
|
891
|
+
if (!stdout?.isTTY) return false;
|
|
892
|
+
if (process.env.NO_COLOR) return false;
|
|
893
|
+
const term = process.env.TERM ?? "";
|
|
894
|
+
return term !== "dumb";
|
|
895
|
+
}
|
|
896
|
+
function createUi(stdout) {
|
|
897
|
+
const enabled = colorEnabled(stdout);
|
|
898
|
+
const write = typeof stdout?.write === "function" ? stdout.write.bind(stdout) : (() => true);
|
|
899
|
+
const paint = (code, text) => enabled ? `${code}${text}${RESET}` : text;
|
|
900
|
+
return {
|
|
901
|
+
colors: enabled,
|
|
902
|
+
bold: (text) => paint(BOLD, text),
|
|
903
|
+
dim: (text) => paint(DIM, text),
|
|
904
|
+
cyan: (text) => paint(CYAN, text),
|
|
905
|
+
green: (text) => paint(GREEN, text),
|
|
906
|
+
yellow: (text) => paint(YELLOW, text),
|
|
907
|
+
red: (text) => paint(RED, text),
|
|
908
|
+
/** Application banner shown once at the top of the wizard. */
|
|
909
|
+
banner(name, tagline, version = "") {
|
|
910
|
+
const line = "\u2500".repeat(Math.max(name.length + tagline.length + 8, 44));
|
|
911
|
+
write(`
|
|
912
|
+
${paint(CYAN, line)}
|
|
913
|
+
`);
|
|
914
|
+
write(` ${paint(BOLD, name)}${version ? ` ${paint(DIM, `v${version}`)}` : ""}
|
|
915
|
+
`);
|
|
916
|
+
write(` ${paint(CYAN, tagline)}
|
|
917
|
+
`);
|
|
918
|
+
write(`${paint(CYAN, line)}
|
|
919
|
+
|
|
920
|
+
`);
|
|
921
|
+
},
|
|
922
|
+
/** Numbered step header, e.g. "── [2/6] Headless mode ──". */
|
|
923
|
+
step(current, total, title) {
|
|
924
|
+
write(`
|
|
925
|
+
${paint(BOLD, `[${current}/${total}] ${title}`)}
|
|
926
|
+
`);
|
|
927
|
+
},
|
|
928
|
+
/** Indented explanatory paragraph under a question. */
|
|
929
|
+
explain(lines) {
|
|
930
|
+
for (const line of lines) {
|
|
931
|
+
write(` ${paint(DIM, line)}
|
|
932
|
+
`);
|
|
933
|
+
}
|
|
934
|
+
},
|
|
935
|
+
/** One option row in a numbered choice list. */
|
|
936
|
+
option(index, label, description, recommended = false) {
|
|
937
|
+
const badge = recommended ? paint(GREEN, " (recommended)") : "";
|
|
938
|
+
write(` ${paint(CYAN, `${index})`)} ${paint(BOLD, label)}${badge}
|
|
939
|
+
`);
|
|
940
|
+
write(` ${paint(DIM, description)}
|
|
941
|
+
`);
|
|
942
|
+
},
|
|
943
|
+
keyValues(rows) {
|
|
944
|
+
const width = Math.max(...rows.map(([key]) => key.length));
|
|
945
|
+
for (const [key, value] of rows) {
|
|
946
|
+
write(` ${paint(DIM, key.padEnd(width))} ${value}
|
|
947
|
+
`);
|
|
948
|
+
}
|
|
949
|
+
},
|
|
950
|
+
success(text) {
|
|
951
|
+
write(`${paint(GREEN, "\u2714")} ${text}
|
|
952
|
+
`);
|
|
953
|
+
},
|
|
954
|
+
failure(text) {
|
|
955
|
+
write(`${paint(RED, "\u2716")} ${text}
|
|
956
|
+
`);
|
|
957
|
+
},
|
|
958
|
+
note(text) {
|
|
959
|
+
write(` ${paint(YELLOW, "\u203A")} ${paint(DIM, text)}
|
|
960
|
+
`);
|
|
961
|
+
}
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
var RESET, BOLD, DIM, CYAN, GREEN, YELLOW, RED;
|
|
965
|
+
var init_ui = __esm({
|
|
966
|
+
"src/server/ui.ts"() {
|
|
967
|
+
"use strict";
|
|
968
|
+
RESET = "\x1B[0m";
|
|
969
|
+
BOLD = "\x1B[1m";
|
|
970
|
+
DIM = "\x1B[2m";
|
|
971
|
+
CYAN = "\x1B[36m";
|
|
972
|
+
GREEN = "\x1B[32m";
|
|
973
|
+
YELLOW = "\x1B[33m";
|
|
974
|
+
RED = "\x1B[31m";
|
|
975
|
+
}
|
|
976
|
+
});
|
|
977
|
+
|
|
885
978
|
// src/server/installer-wizard.ts
|
|
886
979
|
var installer_wizard_exports = {};
|
|
887
980
|
__export(installer_wizard_exports, {
|
|
@@ -904,26 +997,43 @@ function isInteractive() {
|
|
|
904
997
|
}
|
|
905
998
|
async function promptForHarness(opts) {
|
|
906
999
|
const { createInterface } = await import("node:readline/promises");
|
|
907
|
-
const
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
});
|
|
1000
|
+
const ui = createUi(opts.stdout);
|
|
1001
|
+
const input = opts.stdin;
|
|
1002
|
+
const output = opts.stdout;
|
|
1003
|
+
const rl = createInterface({ input, output });
|
|
911
1004
|
try {
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
1005
|
+
if (opts.stdout.isTTY) {
|
|
1006
|
+
ui.step(0, HARNESS_MENU.length, "Which harness should get a browser?");
|
|
1007
|
+
ui.explain(["The AI harness you use every day. Pick yours; you can re-run this later for others."]);
|
|
1008
|
+
HARNESS_MENU.forEach((entry, index) => ui.option(index + 1, entry.label, entry.description));
|
|
1009
|
+
}
|
|
1010
|
+
while (true) {
|
|
1011
|
+
const answer = await rl.question(`Choose 1-${HARNESS_MENU.length} or type a name [opencode]: `);
|
|
1012
|
+
const trimmed = answer.trim().toLowerCase();
|
|
1013
|
+
if (!trimmed) return "opencode";
|
|
1014
|
+
const numeric = Number.parseInt(trimmed, 10);
|
|
1015
|
+
if (`${numeric}` === trimmed && numeric >= 1 && numeric <= HARNESS_MENU.length) {
|
|
1016
|
+
return HARNESS_MENU[numeric - 1].id;
|
|
1017
|
+
}
|
|
1018
|
+
if (/^\d+$/.test(trimmed)) {
|
|
1019
|
+
continue;
|
|
1020
|
+
}
|
|
1021
|
+
return normalizeHarnessName(trimmed);
|
|
1022
|
+
}
|
|
923
1023
|
} finally {
|
|
924
1024
|
rl.close();
|
|
925
1025
|
}
|
|
926
1026
|
}
|
|
1027
|
+
function normalizeHarnessName(name) {
|
|
1028
|
+
const aliases = {
|
|
1029
|
+
"claude": "claude-code",
|
|
1030
|
+
"github-copilot": "copilot",
|
|
1031
|
+
"codex-cli": "codex",
|
|
1032
|
+
"gemini-cli": "gemini",
|
|
1033
|
+
"vs-code": "vscode"
|
|
1034
|
+
};
|
|
1035
|
+
return aliases[name] ?? name;
|
|
1036
|
+
}
|
|
927
1037
|
function recommendedDefaults(homeDir) {
|
|
928
1038
|
return {
|
|
929
1039
|
mode: "managed",
|
|
@@ -934,52 +1044,188 @@ function recommendedDefaults(homeDir) {
|
|
|
934
1044
|
dataDir: join7(homeDir ?? homedir4(), ".smooth-operator")
|
|
935
1045
|
};
|
|
936
1046
|
}
|
|
937
|
-
|
|
1047
|
+
function tolerantQuestion(rl) {
|
|
1048
|
+
return {
|
|
1049
|
+
async question(prompt) {
|
|
1050
|
+
try {
|
|
1051
|
+
return await rl.question(prompt);
|
|
1052
|
+
} catch (error) {
|
|
1053
|
+
const code = error.code;
|
|
1054
|
+
const aborted = error instanceof Error && error.name === "AbortError";
|
|
1055
|
+
if (aborted || code === "ABORT_ERR" || code === "ERR_USE_AFTER_CLOSE") {
|
|
1056
|
+
return "";
|
|
1057
|
+
}
|
|
1058
|
+
throw error;
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
1063
|
+
async function askYesNo(session, prompt, fallback) {
|
|
1064
|
+
while (true) {
|
|
1065
|
+
const hint = fallback ? "[Y/n]" : "[y/N]";
|
|
1066
|
+
const answer = (await session.question(`${prompt} ${hint}: `)).trim().toLowerCase();
|
|
1067
|
+
if (!answer) return fallback;
|
|
1068
|
+
if (["y", "yes"].includes(answer)) return true;
|
|
1069
|
+
if (["n", "no"].includes(answer)) return false;
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
function parseDomainList(raw) {
|
|
1073
|
+
const domains = raw.split(",").map((part) => part.trim().toLowerCase()).filter(Boolean);
|
|
1074
|
+
const invalid = domains.find((domain) => !/^(?:\*\.)?[a-z0-9-]+(?:\.[a-z0-9-]+)+$/.test(domain));
|
|
1075
|
+
return invalid === void 0 ? domains : void 0;
|
|
1076
|
+
}
|
|
1077
|
+
async function runWizard(harness, opts) {
|
|
1078
|
+
const defaults = recommendedDefaults(opts.homeDir);
|
|
938
1079
|
if (opts.yes) {
|
|
939
|
-
return
|
|
1080
|
+
return defaults;
|
|
940
1081
|
}
|
|
941
1082
|
const stdin = opts.stdin ?? process.stdin;
|
|
942
1083
|
const stdout = opts.stdout ?? process.stdout;
|
|
943
1084
|
const interactive = Boolean(stdin.isTTY && stdout.isTTY && !process.env.CI);
|
|
944
1085
|
if (!interactive) {
|
|
945
|
-
return
|
|
1086
|
+
return defaults;
|
|
946
1087
|
}
|
|
1088
|
+
const ui = createUi(stdout);
|
|
947
1089
|
const { createInterface } = await import("node:readline/promises");
|
|
948
1090
|
const rl = createInterface({
|
|
949
1091
|
input: stdin,
|
|
950
1092
|
output: stdout
|
|
951
1093
|
});
|
|
1094
|
+
const session = tolerantQuestion(rl);
|
|
952
1095
|
try {
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
1096
|
+
ui.banner("SmoothOperator Setup", `Give ${harness} a real Chrome it can drive`, opts.version ?? "2.2.0");
|
|
1097
|
+
ui.note(`Configuring: ${harness}`);
|
|
1098
|
+
ui.note("Answer each question, or press Enter to accept the recommended default.");
|
|
1099
|
+
ui.note(`You can re-run \`smooth-operator install ${harness}\` at any time to change these.`);
|
|
1100
|
+
ui.step(1, 6, "Browser mode");
|
|
1101
|
+
ui.explain([
|
|
1102
|
+
"Who owns the Chrome window your AI drives?",
|
|
1103
|
+
"",
|
|
1104
|
+
"Managed gives the AI its own private Chrome profile at ~/.smooth-operator/browser.",
|
|
1105
|
+
"Your daily browser stays untouched; logins for the AI live separately.",
|
|
1106
|
+
"",
|
|
1107
|
+
"Connect attaches to your real Chrome instead, so the AI uses everything",
|
|
1108
|
+
"you are already signed into. Only pick this if you need your existing logins.",
|
|
1109
|
+
"",
|
|
1110
|
+
"Disabled keeps the server but turns all browsing tools off."
|
|
1111
|
+
]);
|
|
1112
|
+
ui.option(1, "Managed private Chrome", "Isolated profile owned by SmoothOperator. Safest default.", true);
|
|
1113
|
+
ui.option(2, "Personal Chrome (connect)", "Reuse your real browser and its existing sign-ins.");
|
|
1114
|
+
ui.option(3, "Disabled", "No browser. Tools that need a page will report an error.");
|
|
1115
|
+
let mode = "managed";
|
|
957
1116
|
let browserUrl;
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
1117
|
+
let headless = false;
|
|
1118
|
+
let allowedDomains = [];
|
|
1119
|
+
let blockedDomains = [];
|
|
1120
|
+
let allowEval = false;
|
|
1121
|
+
let dataDir = defaults.dataDir;
|
|
1122
|
+
while (true) {
|
|
1123
|
+
const answer = (await session.question("Mode [1]: ")).trim();
|
|
1124
|
+
if (!answer || answer === "1") {
|
|
1125
|
+
mode = "managed";
|
|
1126
|
+
break;
|
|
1127
|
+
}
|
|
1128
|
+
if (answer === "2") {
|
|
1129
|
+
mode = "connect";
|
|
1130
|
+
browserUrl = "http://127.0.0.1:9222";
|
|
1131
|
+
break;
|
|
1132
|
+
}
|
|
1133
|
+
if (answer === "3") {
|
|
1134
|
+
mode = "disabled";
|
|
1135
|
+
break;
|
|
1136
|
+
}
|
|
1137
|
+
ui.failure("Enter 1, 2, or 3.");
|
|
1138
|
+
}
|
|
1139
|
+
let headlessChoice = false;
|
|
1140
|
+
if (mode !== "disabled") {
|
|
1141
|
+
ui.step(2, 6, "Headless mode");
|
|
1142
|
+
ui.explain([
|
|
1143
|
+
"Headless runs Chrome with no visible window - lighter and invisible.",
|
|
1144
|
+
"Visible Chrome lets you watch clicks happen and handle CAPTCHAs or",
|
|
1145
|
+
"logins yourself when a site pauses for human verification."
|
|
1146
|
+
]);
|
|
1147
|
+
headlessChoice = await askYesNo(session, "Run Chrome headless (no window)?", false);
|
|
1148
|
+
ui.step(3, 6, "Allowed domains");
|
|
1149
|
+
ui.explain([
|
|
1150
|
+
"Restrict which sites the AI may open, e.g. docs.example.com, *.wikipedia.org",
|
|
1151
|
+
"Leave empty to allow every site. Blocked domains always win over allowed ones."
|
|
1152
|
+
]);
|
|
1153
|
+
while (true) {
|
|
1154
|
+
const parsed = parseDomainList(await session.question("Allowed domains (comma-separated, Enter for all): "));
|
|
1155
|
+
if (parsed !== void 0) {
|
|
1156
|
+
allowedDomains = parsed;
|
|
1157
|
+
break;
|
|
1158
|
+
}
|
|
1159
|
+
ui.failure("That did not look like a domain list. Example: example.com, *.shop.test");
|
|
1160
|
+
}
|
|
1161
|
+
ui.step(4, 6, "Blocked domains");
|
|
1162
|
+
ui.explain(["Never open these sites, even when everything else is allowed."]);
|
|
1163
|
+
while (true) {
|
|
1164
|
+
const parsed = parseDomainList(await session.question("Blocked domains (comma-separated, Enter for none): "));
|
|
1165
|
+
if (parsed !== void 0) {
|
|
1166
|
+
blockedDomains = parsed;
|
|
1167
|
+
break;
|
|
1168
|
+
}
|
|
1169
|
+
ui.failure("That did not look like a domain list. Example: ads.example.com");
|
|
1170
|
+
}
|
|
1171
|
+
ui.step(5, 6, "JavaScript execution");
|
|
1172
|
+
ui.explain([
|
|
1173
|
+
"browser_evaluate runs arbitrary JavaScript on a page - powerful for scraping",
|
|
1174
|
+
"but it can also trigger bot defenses. Most users never need it on."
|
|
1175
|
+
]);
|
|
1176
|
+
allowEval = await askYesNo(session, "Allow the AI to run JavaScript on pages?", false);
|
|
1177
|
+
ui.step(6, 6, "Data directory");
|
|
1178
|
+
ui.explain([
|
|
1179
|
+
"Where the private Chrome profile, logs, and downloads live.",
|
|
1180
|
+
"Permissions are locked to 0600 so only your user can read them."
|
|
1181
|
+
]);
|
|
1182
|
+
while (dataDir === defaults.dataDir) {
|
|
1183
|
+
const answer = (await session.question(`Data directory [${defaults.dataDir}]: `)).trim();
|
|
1184
|
+
if (!answer) break;
|
|
1185
|
+
if (!answer.startsWith("/")) {
|
|
1186
|
+
ui.failure("Enter an absolute path (starting with /).");
|
|
1187
|
+
continue;
|
|
1188
|
+
}
|
|
1189
|
+
dataDir = answer;
|
|
1190
|
+
break;
|
|
1191
|
+
}
|
|
1192
|
+
headless = headlessChoice;
|
|
1193
|
+
if (mode === "connect") {
|
|
1194
|
+
ui.note("Starting your personal Chrome with remote debugging on port 9222...");
|
|
1195
|
+
try {
|
|
1196
|
+
const launched = await launchPersonalChrome({ dataDir, spawn: opts.spawn, probe: opts.probe ?? defaultProbe, port: 9222 });
|
|
1197
|
+
browserUrl = launched.url;
|
|
1198
|
+
ui.success(`Connected to your Chrome at ${launched.url}`);
|
|
1199
|
+
} catch {
|
|
1200
|
+
browserUrl = "http://127.0.0.1:9222";
|
|
1201
|
+
ui.note("Could not reach Chrome on port 9222 yet - keeping the default URL.");
|
|
1202
|
+
}
|
|
976
1203
|
}
|
|
977
1204
|
}
|
|
1205
|
+
writeSummary(ui, harness, { mode, headless, allowedDomains, blockedDomains, allowEval, dataDir });
|
|
978
1206
|
return { mode, headless, allowedDomains, blockedDomains, allowEval, dataDir, browserUrl };
|
|
979
1207
|
} finally {
|
|
980
1208
|
rl.close();
|
|
981
1209
|
}
|
|
982
1210
|
}
|
|
1211
|
+
function writeSummary(ui, harness, choices) {
|
|
1212
|
+
const modeLabel = {
|
|
1213
|
+
managed: "Managed private Chrome (isolated profile)",
|
|
1214
|
+
connect: "Your personal Chrome via debugging port",
|
|
1215
|
+
disabled: "Disabled - no browser tools"
|
|
1216
|
+
};
|
|
1217
|
+
ui.banner("Configuration Summary", `Ready to configure ${harness}`, "");
|
|
1218
|
+
ui.keyValues([
|
|
1219
|
+
["Browser mode", choices.mode === "connect" ? modeLabel.connect : modeLabel[choices.mode] ?? choices.mode],
|
|
1220
|
+
["Headless", choices.headless ? "yes - no visible window" : "no - you can watch and intervene"],
|
|
1221
|
+
...choices.mode === "disabled" ? [] : [
|
|
1222
|
+
["Allowed sites", choices.allowedDomains.length ? choices.allowedDomains.join(", ") : "all sites"],
|
|
1223
|
+
["Blocked sites", choices.blockedDomains.length ? choices.blockedDomains.join(", ") : "none"],
|
|
1224
|
+
["Page JavaScript", choices.allowEval ? "enabled" : "off (recommended)"],
|
|
1225
|
+
["Data directory", choices.dataDir]
|
|
1226
|
+
]
|
|
1227
|
+
]);
|
|
1228
|
+
}
|
|
983
1229
|
async function defaultProbe(url, timeoutMs) {
|
|
984
1230
|
try {
|
|
985
1231
|
const controller = new AbortController();
|
|
@@ -1094,12 +1340,24 @@ async function launchPersonalChrome(opts) {
|
|
|
1094
1340
|
}
|
|
1095
1341
|
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.`);
|
|
1096
1342
|
}
|
|
1097
|
-
var PROBE_INTERVAL_MS, DEFAULT_PROBE_ATTEMPTS;
|
|
1343
|
+
var PROBE_INTERVAL_MS, DEFAULT_PROBE_ATTEMPTS, HARNESS_MENU;
|
|
1098
1344
|
var init_installer_wizard = __esm({
|
|
1099
1345
|
"src/server/installer-wizard.ts"() {
|
|
1100
1346
|
"use strict";
|
|
1347
|
+
init_ui();
|
|
1101
1348
|
PROBE_INTERVAL_MS = 300;
|
|
1102
1349
|
DEFAULT_PROBE_ATTEMPTS = 33;
|
|
1350
|
+
HARNESS_MENU = [
|
|
1351
|
+
{ id: "opencode", label: "OpenCode", description: "Configures ~/.config/opencode/opencode.json" },
|
|
1352
|
+
{ id: "claude-code", label: "Claude Code", description: "Runs `claude mcp add` for your user scope" },
|
|
1353
|
+
{ id: "copilot", label: "GitHub Copilot CLI", description: "Runs `copilot mcp add`" },
|
|
1354
|
+
{ id: "codex", label: "OpenAI Codex CLI", description: "Runs `codex mcp add`" },
|
|
1355
|
+
{ id: "gemini", label: "Gemini CLI", description: "Runs `gemini mcp add` for your user scope" },
|
|
1356
|
+
{ id: "vscode", label: "VS Code", description: "Runs `code --add-mcp`" },
|
|
1357
|
+
{ id: "cursor", label: "Cursor", description: "Adds SmoothOperator to ~/.cursor/mcp.json" },
|
|
1358
|
+
{ id: "windsurf", label: "Windsurf", description: "Adds SmoothOperator to Windsurf's mcp_config.json" },
|
|
1359
|
+
{ id: "claude-desktop", label: "Claude Desktop", description: "Updates claude_desktop_config.json" }
|
|
1360
|
+
];
|
|
1103
1361
|
}
|
|
1104
1362
|
});
|
|
1105
1363
|
|
|
@@ -1972,7 +2230,7 @@ init_errors();
|
|
|
1972
2230
|
init_logger();
|
|
1973
2231
|
|
|
1974
2232
|
// src/server/version.ts
|
|
1975
|
-
var SERVER_VERSION = "2.
|
|
2233
|
+
var SERVER_VERSION = "2.2.0";
|
|
1976
2234
|
|
|
1977
2235
|
// src/server/mcp.ts
|
|
1978
2236
|
var EmptyInputSchema = z3.object({}).strict();
|
|
@@ -7771,10 +8029,26 @@ async function main(args = process4.argv.slice(2)) {
|
|
|
7771
8029
|
if (!harness) {
|
|
7772
8030
|
throw new AppError("CONFIG_INVALID", "The install command requires a harness target.");
|
|
7773
8031
|
}
|
|
7774
|
-
const wizardChoices = await runWizard2(harness, { yes, stdin: process4.stdin, stdout: process4.stdout, homeDir: homedir5(), env: process4.env });
|
|
8032
|
+
const wizardChoices = await runWizard2(harness, { yes, stdin: process4.stdin, stdout: process4.stdout, homeDir: homedir5(), env: process4.env, version: SERVER_VERSION });
|
|
7775
8033
|
await persistWizardConfig2(wizardChoices, homedir5());
|
|
7776
|
-
|
|
8034
|
+
const installMessage = await installHarness(harness);
|
|
8035
|
+
const { createUi: createUi2 } = await Promise.resolve().then(() => (init_ui(), ui_exports));
|
|
8036
|
+
const ui = createUi2(process4.stdout);
|
|
8037
|
+
if (process4.stdout.isTTY) {
|
|
8038
|
+
ui.banner("Installation Complete", `${harness} can now drive a browser`, SERVER_VERSION);
|
|
8039
|
+
ui.keyValues([
|
|
8040
|
+
["Config file", `${homedir5()}/.smooth-operator/config.json`],
|
|
8041
|
+
["Browser mode", wizardChoices.mode]
|
|
8042
|
+
]);
|
|
8043
|
+
process4.stdout.write("\n");
|
|
8044
|
+
ui.step(0, 2, "Next steps");
|
|
8045
|
+
ui.option(1, "Restart the harness", "Quit and reopen it so it picks up the new MCP server.");
|
|
8046
|
+
ui.option(2, "Verify", "Ask your AI to run server_health and browser_doctor.");
|
|
8047
|
+
ui.success(installMessage);
|
|
8048
|
+
} else {
|
|
8049
|
+
process4.stdout.write(`${installMessage}
|
|
7777
8050
|
`);
|
|
8051
|
+
}
|
|
7778
8052
|
return;
|
|
7779
8053
|
}
|
|
7780
8054
|
if (args.length === 1 && (args[0] === "--version" || args[0] === "-V")) {
|