@peppermint-mcp/wizard 0.4.1 → 0.4.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.
- package/dist/cli.js +135 -30
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -68,8 +68,8 @@ async function detectClaudeDesktop() {
|
|
|
68
68
|
if (existsSync(configPath)) {
|
|
69
69
|
try {
|
|
70
70
|
const content = readFileSync(configPath, "utf-8");
|
|
71
|
-
const
|
|
72
|
-
alreadyInstalled = !!
|
|
71
|
+
const parsed2 = jsonc.parse(content);
|
|
72
|
+
alreadyInstalled = !!parsed2?.mcpServers?.["peppermint-memory"] || !!parsed2?.mcpServers?.peppermint;
|
|
73
73
|
} catch {
|
|
74
74
|
warnings.push("Config file exists but could not be parsed");
|
|
75
75
|
}
|
|
@@ -105,8 +105,8 @@ async function detectCursor() {
|
|
|
105
105
|
if (configExists) {
|
|
106
106
|
try {
|
|
107
107
|
const content = readFileSync2(configPath, "utf-8");
|
|
108
|
-
const
|
|
109
|
-
alreadyInstalled = !!
|
|
108
|
+
const parsed2 = jsonc2.parse(content);
|
|
109
|
+
alreadyInstalled = !!parsed2?.mcpServers?.["peppermint-memory"] || !!parsed2?.mcpServers?.peppermint;
|
|
110
110
|
} catch {
|
|
111
111
|
warnings.push("Config file exists but could not be parsed");
|
|
112
112
|
}
|
|
@@ -207,7 +207,7 @@ function clearCredentials() {
|
|
|
207
207
|
// src/auth/localhost-oauth.ts
|
|
208
208
|
import { createServer } from "http";
|
|
209
209
|
import { randomBytes, createHash } from "crypto";
|
|
210
|
-
import { URL, URLSearchParams } from "url";
|
|
210
|
+
import { URL as URL2, URLSearchParams } from "url";
|
|
211
211
|
import open from "open";
|
|
212
212
|
var AUTH_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
213
213
|
function generatePKCE() {
|
|
@@ -279,7 +279,7 @@ function waitForCallback(port, expectedState) {
|
|
|
279
279
|
reject(new Error("Authentication timed out (5 minutes). Please try again."));
|
|
280
280
|
}, AUTH_TIMEOUT_MS);
|
|
281
281
|
const server = createServer((req, res) => {
|
|
282
|
-
const url = new
|
|
282
|
+
const url = new URL2(req.url, `http://127.0.0.1:${port}`);
|
|
283
283
|
if (url.pathname !== "/callback") {
|
|
284
284
|
res.writeHead(404);
|
|
285
285
|
res.end("Not found");
|
|
@@ -488,9 +488,14 @@ function removeServerFromConfig(filePath, serverProperty, serverName, dryRun) {
|
|
|
488
488
|
return false;
|
|
489
489
|
}
|
|
490
490
|
const content = readFileSync4(filePath, "utf-8");
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
491
|
+
let edits;
|
|
492
|
+
try {
|
|
493
|
+
edits = jsonc3.modify(content, [serverProperty, serverName], void 0, {
|
|
494
|
+
formattingOptions: { tabSize: 2, insertSpaces: true }
|
|
495
|
+
});
|
|
496
|
+
} catch {
|
|
497
|
+
return false;
|
|
498
|
+
}
|
|
494
499
|
if (edits.length === 0) {
|
|
495
500
|
return false;
|
|
496
501
|
}
|
|
@@ -518,12 +523,10 @@ function getConfigPath3() {
|
|
|
518
523
|
}
|
|
519
524
|
async function installClaudeDesktop(serverUrl, apiKey, dryRun) {
|
|
520
525
|
const configPath = getConfigPath3();
|
|
521
|
-
const serverConfig = {
|
|
522
|
-
|
|
523
|
-
headers: {
|
|
524
|
-
|
|
525
|
-
}
|
|
526
|
-
};
|
|
526
|
+
const serverConfig = { url: serverUrl };
|
|
527
|
+
if (apiKey) {
|
|
528
|
+
serverConfig.headers = { Authorization: `Bearer ${apiKey}` };
|
|
529
|
+
}
|
|
527
530
|
try {
|
|
528
531
|
removeServerFromConfig(configPath, "mcpServers", "peppermint", dryRun);
|
|
529
532
|
const result = writeServerToConfig({
|
|
@@ -569,12 +572,10 @@ function getConfigPath4() {
|
|
|
569
572
|
}
|
|
570
573
|
async function installCursor(serverUrl, apiKey, dryRun) {
|
|
571
574
|
const configPath = getConfigPath4();
|
|
572
|
-
const serverConfig = {
|
|
573
|
-
|
|
574
|
-
headers: {
|
|
575
|
-
|
|
576
|
-
}
|
|
577
|
-
};
|
|
575
|
+
const serverConfig = { url: serverUrl };
|
|
576
|
+
if (apiKey) {
|
|
577
|
+
serverConfig.headers = { Authorization: `Bearer ${apiKey}` };
|
|
578
|
+
}
|
|
578
579
|
try {
|
|
579
580
|
removeServerFromConfig(configPath, "mcpServers", "peppermint", dryRun);
|
|
580
581
|
const result = writeServerToConfig({
|
|
@@ -727,8 +728,8 @@ function checkHostConfig(hostId, configPath) {
|
|
|
727
728
|
}
|
|
728
729
|
try {
|
|
729
730
|
const content = readFileSync6(configPath, "utf-8");
|
|
730
|
-
const
|
|
731
|
-
const hasPeppermint = !!
|
|
731
|
+
const parsed2 = jsonc4.parse(content);
|
|
732
|
+
const hasPeppermint = !!parsed2?.mcpServers?.["peppermint-memory"] || !!parsed2?.mcpServers?.peppermint;
|
|
732
733
|
if (!hasPeppermint) {
|
|
733
734
|
return {
|
|
734
735
|
hostId,
|
|
@@ -879,8 +880,8 @@ function installPermissions(dryRun) {
|
|
|
879
880
|
if (existsSync8(settingsPath)) {
|
|
880
881
|
content = readFileSync8(settingsPath, "utf-8");
|
|
881
882
|
}
|
|
882
|
-
const
|
|
883
|
-
const existingAllow =
|
|
883
|
+
const parsed2 = content ? jsonc5.parse(content) : {};
|
|
884
|
+
const existingAllow = parsed2?.permissions?.allow || [];
|
|
884
885
|
const toAdd = REQUIRED_PERMISSIONS.filter((p2) => !existingAllow.includes(p2));
|
|
885
886
|
if (toAdd.length === 0) {
|
|
886
887
|
return { added: [] };
|
|
@@ -905,12 +906,95 @@ function installPermissions(dryRun) {
|
|
|
905
906
|
}
|
|
906
907
|
}
|
|
907
908
|
|
|
909
|
+
// src/telemetry.ts
|
|
910
|
+
var SENTRY_DSN = process.env.PEPPERMINT_WIZARD_SENTRY_DSN || "https://ee35ace1116aadd55a0ac5dc2226f5b2@o4510957452197888.ingest.us.sentry.io/4511341954203648";
|
|
911
|
+
var parsed = null;
|
|
912
|
+
function parseDSN() {
|
|
913
|
+
if (parsed) return parsed;
|
|
914
|
+
try {
|
|
915
|
+
const url = new URL(SENTRY_DSN);
|
|
916
|
+
const projectId = url.pathname.replace(/\//g, "");
|
|
917
|
+
if (!url.username || !projectId) return null;
|
|
918
|
+
parsed = { publicKey: url.username, host: `${url.protocol}//${url.host}`, projectId };
|
|
919
|
+
return parsed;
|
|
920
|
+
} catch {
|
|
921
|
+
return null;
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
var wizardVersion = "unknown";
|
|
925
|
+
try {
|
|
926
|
+
const { readFileSync: readFileSync9 } = await import("fs");
|
|
927
|
+
const { resolve: resolve2, dirname: dirname5 } = await import("path");
|
|
928
|
+
const { fileURLToPath: fileURLToPath2 } = await import("url");
|
|
929
|
+
const pkgPath = resolve2(dirname5(fileURLToPath2(import.meta.url)), "..", "package.json");
|
|
930
|
+
const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
|
|
931
|
+
wizardVersion = pkg.version ?? "unknown";
|
|
932
|
+
} catch {
|
|
933
|
+
}
|
|
934
|
+
function uuid4() {
|
|
935
|
+
return "xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
936
|
+
const r = Math.random() * 16 | 0;
|
|
937
|
+
return (c === "x" ? r : r & 3 | 8).toString(16);
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
function captureException(error, context) {
|
|
941
|
+
const dsn = parseDSN();
|
|
942
|
+
if (!dsn) return;
|
|
943
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
944
|
+
const stack = err.stack;
|
|
945
|
+
const frames = (stack?.split("\n").slice(1) ?? []).map((line) => {
|
|
946
|
+
const m = line.match(/at\s+(.+?)\s+\((.+?):(\d+):\d+\)/) ?? line.match(/at\s+(.+?):(\d+):\d+/);
|
|
947
|
+
if (!m) return null;
|
|
948
|
+
return m.length === 4 ? { function: m[1], filename: m[2], lineno: Number(m[3]) } : { filename: m[1], lineno: Number(m[2]) };
|
|
949
|
+
}).filter(Boolean);
|
|
950
|
+
const event = {
|
|
951
|
+
event_id: uuid4(),
|
|
952
|
+
timestamp: Date.now() / 1e3,
|
|
953
|
+
platform: "node",
|
|
954
|
+
level: "error",
|
|
955
|
+
release: `peppermint-mcp-wizard@${wizardVersion}`,
|
|
956
|
+
exception: {
|
|
957
|
+
values: [
|
|
958
|
+
{
|
|
959
|
+
type: err.name,
|
|
960
|
+
value: err.message,
|
|
961
|
+
stacktrace: frames.length > 0 ? { frames: frames.reverse() } : void 0
|
|
962
|
+
}
|
|
963
|
+
]
|
|
964
|
+
},
|
|
965
|
+
tags: context?.tags,
|
|
966
|
+
extra: context?.extra,
|
|
967
|
+
contexts: {
|
|
968
|
+
runtime: { name: "node", version: process.version },
|
|
969
|
+
os: { name: process.platform, version: process.arch }
|
|
970
|
+
}
|
|
971
|
+
};
|
|
972
|
+
const envelope = [
|
|
973
|
+
JSON.stringify({ event_id: event.event_id, dsn: SENTRY_DSN, sent_at: (/* @__PURE__ */ new Date()).toISOString() }),
|
|
974
|
+
JSON.stringify({ type: "event", length: 0 }),
|
|
975
|
+
JSON.stringify(event)
|
|
976
|
+
].join("\n");
|
|
977
|
+
const envelopeUrl = `${dsn.host}/api/${dsn.projectId}/envelope/`;
|
|
978
|
+
fetch(envelopeUrl, {
|
|
979
|
+
method: "POST",
|
|
980
|
+
headers: { "Content-Type": "application/x-sentry-envelope", "X-Sentry-Auth": `Sentry sentry_key=${dsn.publicKey}, sentry_version=7` },
|
|
981
|
+
body: envelope
|
|
982
|
+
}).catch(() => {
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
async function flush(timeoutMs = 2e3) {
|
|
986
|
+
await new Promise((resolve2) => setTimeout(resolve2, Math.min(timeoutMs, 2e3)));
|
|
987
|
+
}
|
|
988
|
+
|
|
908
989
|
// src/cli.ts
|
|
909
990
|
var DEFAULT_SERVER = "https://api.peppermint.com/mcp/";
|
|
910
991
|
function serverBase(serverUrl) {
|
|
911
992
|
return serverUrl.replace(/\/mcp\/?$/, "");
|
|
912
993
|
}
|
|
913
|
-
function needsAuth(
|
|
994
|
+
function needsAuth(serverUrl) {
|
|
995
|
+
if (serverUrl && (serverUrl.includes("localhost") || serverUrl.includes("127.0.0.1"))) {
|
|
996
|
+
return false;
|
|
997
|
+
}
|
|
914
998
|
return true;
|
|
915
999
|
}
|
|
916
1000
|
async function installHost(host, serverUrl, apiKey, dryRun) {
|
|
@@ -918,10 +1002,8 @@ async function installHost(host, serverUrl, apiKey, dryRun) {
|
|
|
918
1002
|
case "claude-code":
|
|
919
1003
|
return installClaudeCode(serverUrl, apiKey, dryRun);
|
|
920
1004
|
case "claude-desktop":
|
|
921
|
-
if (!apiKey) throw new Error("API key required for Claude Desktop");
|
|
922
1005
|
return installClaudeDesktop(serverUrl, apiKey, dryRun);
|
|
923
1006
|
case "cursor":
|
|
924
|
-
if (!apiKey) throw new Error("API key required for Cursor");
|
|
925
1007
|
return installCursor(serverUrl, apiKey, dryRun);
|
|
926
1008
|
case "codex":
|
|
927
1009
|
return installCodex(serverUrl, apiKey, dryRun);
|
|
@@ -1004,7 +1086,9 @@ async function addCommand(options) {
|
|
|
1004
1086
|
const serverCheck = await checkServerReachable(options.server);
|
|
1005
1087
|
if (!serverCheck.reachable) {
|
|
1006
1088
|
const msg = `Cannot reach ${options.server}: ${serverCheck.error}`;
|
|
1089
|
+
captureException(new Error(msg), { tags: { command: "add", phase: "server_check" } });
|
|
1007
1090
|
nonInteractive ? console.error(msg) : p.log.error(msg);
|
|
1091
|
+
await flush();
|
|
1008
1092
|
process.exit(4);
|
|
1009
1093
|
}
|
|
1010
1094
|
if (!nonInteractive) {
|
|
@@ -1016,7 +1100,7 @@ async function addCommand(options) {
|
|
|
1016
1100
|
apiKey = tokenFromFlag;
|
|
1017
1101
|
const msg = "Using provided auth token";
|
|
1018
1102
|
nonInteractive ? console.log(msg) : p.log.success(msg);
|
|
1019
|
-
} else if (needsAuth(
|
|
1103
|
+
} else if (needsAuth(options.server)) {
|
|
1020
1104
|
const base = serverBase(options.server);
|
|
1021
1105
|
const existing = loadCredentials(base);
|
|
1022
1106
|
if (existing) {
|
|
@@ -1034,7 +1118,9 @@ async function addCommand(options) {
|
|
|
1034
1118
|
p.log.success(`Authenticated as ${pc.bold(creds.email || "user")}`);
|
|
1035
1119
|
} catch (err) {
|
|
1036
1120
|
const msg = err instanceof Error ? err.message : "Authentication failed";
|
|
1121
|
+
captureException(err, { tags: { command: "add", phase: "auth" } });
|
|
1037
1122
|
p.log.error(msg);
|
|
1123
|
+
await flush();
|
|
1038
1124
|
process.exit(3);
|
|
1039
1125
|
}
|
|
1040
1126
|
}
|
|
@@ -1045,6 +1131,11 @@ async function addCommand(options) {
|
|
|
1045
1131
|
const result = await installHost(host, options.server, apiKey, options.dryRun);
|
|
1046
1132
|
const icon = result.success ? pc.green("\u2713") : pc.red("\u2717");
|
|
1047
1133
|
p.log.info(` ${icon} ${host.name} ${pc.dim(result.message)}`);
|
|
1134
|
+
if (!result.success) {
|
|
1135
|
+
captureException(new Error(result.message), {
|
|
1136
|
+
tags: { host: host.id, command: "add", phase: "install" }
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1048
1139
|
results.push({ host, result });
|
|
1049
1140
|
}
|
|
1050
1141
|
const hasClaudeHost = hosts.some(
|
|
@@ -1063,10 +1154,16 @@ async function addCommand(options) {
|
|
|
1063
1154
|
const backup = skillResult.backedUp ? pc.dim(" (previous version backed up)") : "";
|
|
1064
1155
|
p.log.info(` ${pc.green("\u2713")} ${verb} Peppermint skill${backup} ${pc.dim(skillResult.targetPath)}`);
|
|
1065
1156
|
} else if (skillResult.error) {
|
|
1157
|
+
captureException(new Error(skillResult.error), {
|
|
1158
|
+
tags: { command: "add", phase: "skills" }
|
|
1159
|
+
});
|
|
1066
1160
|
p.log.info(` ${pc.red("\u2717")} Skill install failed: ${pc.dim(skillResult.error)}`);
|
|
1067
1161
|
}
|
|
1068
1162
|
const permsResult = installPermissions(options.dryRun);
|
|
1069
1163
|
if (permsResult.error) {
|
|
1164
|
+
captureException(new Error(permsResult.error), {
|
|
1165
|
+
tags: { command: "add", phase: "permissions" }
|
|
1166
|
+
});
|
|
1070
1167
|
p.log.info(` ${pc.red("\u2717")} Permissions: ${pc.dim(permsResult.error)}`);
|
|
1071
1168
|
} else if (permsResult.added.length > 0) {
|
|
1072
1169
|
p.log.info(` ${pc.green("\u2713")} Added ${permsResult.added.length} tool permissions to Claude Code settings`);
|
|
@@ -1077,6 +1174,11 @@ async function addCommand(options) {
|
|
|
1077
1174
|
for (const { host } of results) {
|
|
1078
1175
|
const check = checkHostConfig(host.id, host.configPath);
|
|
1079
1176
|
const icon = check.status === "pass" ? pc.green("\u2713") : check.status === "warn" ? pc.yellow("\u26A0") : pc.red("\u2717");
|
|
1177
|
+
if (check.status === "fail") {
|
|
1178
|
+
captureException(new Error(check.message), {
|
|
1179
|
+
tags: { host: host.id, command: "add", phase: "verify" }
|
|
1180
|
+
});
|
|
1181
|
+
}
|
|
1080
1182
|
p.log.info(` ${icon} ${host.name} ${pc.dim(check.message)}`);
|
|
1081
1183
|
}
|
|
1082
1184
|
}
|
|
@@ -1085,7 +1187,10 @@ async function addCommand(options) {
|
|
|
1085
1187
|
p.outro(
|
|
1086
1188
|
failed.length > 0 ? pc.red(`${failed.length} host(s) failed. Check the output above.`) : hasClaudeHost ? pc.green("Done!") + (needRestart.length > 0 ? pc.dim(` Restart ${needRestart.map((r) => r.host.name).join(", ")} to finish.`) : "") + "\n\n " + pc.cyan("Peppermint skill installed. Start a new Claude Code session and type") + "\n " + pc.cyan("@pep or /peppermint to begin onboarding.") : needRestart.length > 0 ? pc.green("Done!") + pc.dim(` Restart ${needRestart.map((r) => r.host.name).join(", ")} to finish.`) : pc.green("Done! Peppermint MCP is ready.")
|
|
1087
1189
|
);
|
|
1088
|
-
if (failed.length > 0)
|
|
1190
|
+
if (failed.length > 0) {
|
|
1191
|
+
await flush();
|
|
1192
|
+
process.exit(2);
|
|
1193
|
+
}
|
|
1089
1194
|
}
|
|
1090
1195
|
async function listCommand(options) {
|
|
1091
1196
|
p.intro(pc.green("\u{1F33F} Peppermint MCP Wizard \u2014 List"));
|