@peppermint-mcp/wizard 0.4.0 → 0.4.2
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 +124 -15
- 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
|
}
|
|
@@ -727,8 +732,8 @@ function checkHostConfig(hostId, configPath) {
|
|
|
727
732
|
}
|
|
728
733
|
try {
|
|
729
734
|
const content = readFileSync6(configPath, "utf-8");
|
|
730
|
-
const
|
|
731
|
-
const hasPeppermint = !!
|
|
735
|
+
const parsed2 = jsonc4.parse(content);
|
|
736
|
+
const hasPeppermint = !!parsed2?.mcpServers?.["peppermint-memory"] || !!parsed2?.mcpServers?.peppermint;
|
|
732
737
|
if (!hasPeppermint) {
|
|
733
738
|
return {
|
|
734
739
|
hostId,
|
|
@@ -879,8 +884,8 @@ function installPermissions(dryRun) {
|
|
|
879
884
|
if (existsSync8(settingsPath)) {
|
|
880
885
|
content = readFileSync8(settingsPath, "utf-8");
|
|
881
886
|
}
|
|
882
|
-
const
|
|
883
|
-
const existingAllow =
|
|
887
|
+
const parsed2 = content ? jsonc5.parse(content) : {};
|
|
888
|
+
const existingAllow = parsed2?.permissions?.allow || [];
|
|
884
889
|
const toAdd = REQUIRED_PERMISSIONS.filter((p2) => !existingAllow.includes(p2));
|
|
885
890
|
if (toAdd.length === 0) {
|
|
886
891
|
return { added: [] };
|
|
@@ -905,6 +910,86 @@ function installPermissions(dryRun) {
|
|
|
905
910
|
}
|
|
906
911
|
}
|
|
907
912
|
|
|
913
|
+
// src/telemetry.ts
|
|
914
|
+
var SENTRY_DSN = process.env.PEPPERMINT_WIZARD_SENTRY_DSN || "https://ee35ace1116aadd55a0ac5dc2226f5b2@o4510957452197888.ingest.us.sentry.io/4511341954203648";
|
|
915
|
+
var parsed = null;
|
|
916
|
+
function parseDSN() {
|
|
917
|
+
if (parsed) return parsed;
|
|
918
|
+
try {
|
|
919
|
+
const url = new URL(SENTRY_DSN);
|
|
920
|
+
const projectId = url.pathname.replace(/\//g, "");
|
|
921
|
+
if (!url.username || !projectId) return null;
|
|
922
|
+
parsed = { publicKey: url.username, host: `${url.protocol}//${url.host}`, projectId };
|
|
923
|
+
return parsed;
|
|
924
|
+
} catch {
|
|
925
|
+
return null;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
var wizardVersion = "unknown";
|
|
929
|
+
try {
|
|
930
|
+
const { readFileSync: readFileSync9 } = await import("fs");
|
|
931
|
+
const { resolve: resolve2, dirname: dirname5 } = await import("path");
|
|
932
|
+
const { fileURLToPath: fileURLToPath2 } = await import("url");
|
|
933
|
+
const pkgPath = resolve2(dirname5(fileURLToPath2(import.meta.url)), "..", "package.json");
|
|
934
|
+
const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
|
|
935
|
+
wizardVersion = pkg.version ?? "unknown";
|
|
936
|
+
} catch {
|
|
937
|
+
}
|
|
938
|
+
function uuid4() {
|
|
939
|
+
return "xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
940
|
+
const r = Math.random() * 16 | 0;
|
|
941
|
+
return (c === "x" ? r : r & 3 | 8).toString(16);
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
function captureException(error, context) {
|
|
945
|
+
const dsn = parseDSN();
|
|
946
|
+
if (!dsn) return;
|
|
947
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
948
|
+
const stack = err.stack;
|
|
949
|
+
const frames = (stack?.split("\n").slice(1) ?? []).map((line) => {
|
|
950
|
+
const m = line.match(/at\s+(.+?)\s+\((.+?):(\d+):\d+\)/) ?? line.match(/at\s+(.+?):(\d+):\d+/);
|
|
951
|
+
if (!m) return null;
|
|
952
|
+
return m.length === 4 ? { function: m[1], filename: m[2], lineno: Number(m[3]) } : { filename: m[1], lineno: Number(m[2]) };
|
|
953
|
+
}).filter(Boolean);
|
|
954
|
+
const event = {
|
|
955
|
+
event_id: uuid4(),
|
|
956
|
+
timestamp: Date.now() / 1e3,
|
|
957
|
+
platform: "node",
|
|
958
|
+
level: "error",
|
|
959
|
+
release: `peppermint-mcp-wizard@${wizardVersion}`,
|
|
960
|
+
exception: {
|
|
961
|
+
values: [
|
|
962
|
+
{
|
|
963
|
+
type: err.name,
|
|
964
|
+
value: err.message,
|
|
965
|
+
stacktrace: frames.length > 0 ? { frames: frames.reverse() } : void 0
|
|
966
|
+
}
|
|
967
|
+
]
|
|
968
|
+
},
|
|
969
|
+
tags: context?.tags,
|
|
970
|
+
extra: context?.extra,
|
|
971
|
+
contexts: {
|
|
972
|
+
runtime: { name: "node", version: process.version },
|
|
973
|
+
os: { name: process.platform, version: process.arch }
|
|
974
|
+
}
|
|
975
|
+
};
|
|
976
|
+
const envelope = [
|
|
977
|
+
JSON.stringify({ event_id: event.event_id, dsn: SENTRY_DSN, sent_at: (/* @__PURE__ */ new Date()).toISOString() }),
|
|
978
|
+
JSON.stringify({ type: "event", length: 0 }),
|
|
979
|
+
JSON.stringify(event)
|
|
980
|
+
].join("\n");
|
|
981
|
+
const envelopeUrl = `${dsn.host}/api/${dsn.projectId}/envelope/`;
|
|
982
|
+
fetch(envelopeUrl, {
|
|
983
|
+
method: "POST",
|
|
984
|
+
headers: { "Content-Type": "application/x-sentry-envelope", "X-Sentry-Auth": `Sentry sentry_key=${dsn.publicKey}, sentry_version=7` },
|
|
985
|
+
body: envelope
|
|
986
|
+
}).catch(() => {
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
async function flush(timeoutMs = 2e3) {
|
|
990
|
+
await new Promise((resolve2) => setTimeout(resolve2, Math.min(timeoutMs, 2e3)));
|
|
991
|
+
}
|
|
992
|
+
|
|
908
993
|
// src/cli.ts
|
|
909
994
|
var DEFAULT_SERVER = "https://api.peppermint.com/mcp/";
|
|
910
995
|
function serverBase(serverUrl) {
|
|
@@ -1004,7 +1089,9 @@ async function addCommand(options) {
|
|
|
1004
1089
|
const serverCheck = await checkServerReachable(options.server);
|
|
1005
1090
|
if (!serverCheck.reachable) {
|
|
1006
1091
|
const msg = `Cannot reach ${options.server}: ${serverCheck.error}`;
|
|
1092
|
+
captureException(new Error(msg), { tags: { command: "add", phase: "server_check" } });
|
|
1007
1093
|
nonInteractive ? console.error(msg) : p.log.error(msg);
|
|
1094
|
+
await flush();
|
|
1008
1095
|
process.exit(4);
|
|
1009
1096
|
}
|
|
1010
1097
|
if (!nonInteractive) {
|
|
@@ -1034,7 +1121,9 @@ async function addCommand(options) {
|
|
|
1034
1121
|
p.log.success(`Authenticated as ${pc.bold(creds.email || "user")}`);
|
|
1035
1122
|
} catch (err) {
|
|
1036
1123
|
const msg = err instanceof Error ? err.message : "Authentication failed";
|
|
1124
|
+
captureException(err, { tags: { command: "add", phase: "auth" } });
|
|
1037
1125
|
p.log.error(msg);
|
|
1126
|
+
await flush();
|
|
1038
1127
|
process.exit(3);
|
|
1039
1128
|
}
|
|
1040
1129
|
}
|
|
@@ -1045,6 +1134,11 @@ async function addCommand(options) {
|
|
|
1045
1134
|
const result = await installHost(host, options.server, apiKey, options.dryRun);
|
|
1046
1135
|
const icon = result.success ? pc.green("\u2713") : pc.red("\u2717");
|
|
1047
1136
|
p.log.info(` ${icon} ${host.name} ${pc.dim(result.message)}`);
|
|
1137
|
+
if (!result.success) {
|
|
1138
|
+
captureException(new Error(result.message), {
|
|
1139
|
+
tags: { host: host.id, command: "add", phase: "install" }
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1048
1142
|
results.push({ host, result });
|
|
1049
1143
|
}
|
|
1050
1144
|
const hasClaudeHost = hosts.some(
|
|
@@ -1063,10 +1157,16 @@ async function addCommand(options) {
|
|
|
1063
1157
|
const backup = skillResult.backedUp ? pc.dim(" (previous version backed up)") : "";
|
|
1064
1158
|
p.log.info(` ${pc.green("\u2713")} ${verb} Peppermint skill${backup} ${pc.dim(skillResult.targetPath)}`);
|
|
1065
1159
|
} else if (skillResult.error) {
|
|
1160
|
+
captureException(new Error(skillResult.error), {
|
|
1161
|
+
tags: { command: "add", phase: "skills" }
|
|
1162
|
+
});
|
|
1066
1163
|
p.log.info(` ${pc.red("\u2717")} Skill install failed: ${pc.dim(skillResult.error)}`);
|
|
1067
1164
|
}
|
|
1068
1165
|
const permsResult = installPermissions(options.dryRun);
|
|
1069
1166
|
if (permsResult.error) {
|
|
1167
|
+
captureException(new Error(permsResult.error), {
|
|
1168
|
+
tags: { command: "add", phase: "permissions" }
|
|
1169
|
+
});
|
|
1070
1170
|
p.log.info(` ${pc.red("\u2717")} Permissions: ${pc.dim(permsResult.error)}`);
|
|
1071
1171
|
} else if (permsResult.added.length > 0) {
|
|
1072
1172
|
p.log.info(` ${pc.green("\u2713")} Added ${permsResult.added.length} tool permissions to Claude Code settings`);
|
|
@@ -1077,6 +1177,11 @@ async function addCommand(options) {
|
|
|
1077
1177
|
for (const { host } of results) {
|
|
1078
1178
|
const check = checkHostConfig(host.id, host.configPath);
|
|
1079
1179
|
const icon = check.status === "pass" ? pc.green("\u2713") : check.status === "warn" ? pc.yellow("\u26A0") : pc.red("\u2717");
|
|
1180
|
+
if (check.status === "fail") {
|
|
1181
|
+
captureException(new Error(check.message), {
|
|
1182
|
+
tags: { host: host.id, command: "add", phase: "verify" }
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1080
1185
|
p.log.info(` ${icon} ${host.name} ${pc.dim(check.message)}`);
|
|
1081
1186
|
}
|
|
1082
1187
|
}
|
|
@@ -1085,7 +1190,10 @@ async function addCommand(options) {
|
|
|
1085
1190
|
p.outro(
|
|
1086
1191
|
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
1192
|
);
|
|
1088
|
-
if (failed.length > 0)
|
|
1193
|
+
if (failed.length > 0) {
|
|
1194
|
+
await flush();
|
|
1195
|
+
process.exit(2);
|
|
1196
|
+
}
|
|
1089
1197
|
}
|
|
1090
1198
|
async function listCommand(options) {
|
|
1091
1199
|
p.intro(pc.green("\u{1F33F} Peppermint MCP Wizard \u2014 List"));
|
|
@@ -1169,13 +1277,14 @@ async function removeCommand(options) {
|
|
|
1169
1277
|
const creds = loadCredentials(base);
|
|
1170
1278
|
if (creds && !options.dryRun) {
|
|
1171
1279
|
try {
|
|
1280
|
+
const keyPrefix = creds.api_key.slice(0, 8);
|
|
1172
1281
|
const res = await fetch(`${base}/auth/api-keys`, {
|
|
1173
1282
|
headers: { Authorization: `Bearer ${creds.api_key}` }
|
|
1174
1283
|
});
|
|
1175
1284
|
if (res.ok) {
|
|
1176
1285
|
const keys = await res.json();
|
|
1177
1286
|
for (const key of keys) {
|
|
1178
|
-
if (key.
|
|
1287
|
+
if (key.key_prefix === keyPrefix) {
|
|
1179
1288
|
await fetch(`${base}/auth/api-keys/${key.id}`, {
|
|
1180
1289
|
method: "DELETE",
|
|
1181
1290
|
headers: { Authorization: `Bearer ${creds.api_key}` }
|