@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.
Files changed (2) hide show
  1. package/dist/cli.js +124 -15
  2. 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 parsed = jsonc.parse(content);
72
- alreadyInstalled = !!parsed?.mcpServers?.["peppermint-memory"] || !!parsed?.mcpServers?.peppermint;
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 parsed = jsonc2.parse(content);
109
- alreadyInstalled = !!parsed?.mcpServers?.["peppermint-memory"] || !!parsed?.mcpServers?.peppermint;
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 URL(req.url, `http://127.0.0.1:${port}`);
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
- const edits = jsonc3.modify(content, [serverProperty, serverName], void 0, {
492
- formattingOptions: { tabSize: 2, insertSpaces: true }
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 parsed = jsonc4.parse(content);
731
- const hasPeppermint = !!parsed?.mcpServers?.["peppermint-memory"] || !!parsed?.mcpServers?.peppermint;
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 parsed = content ? jsonc5.parse(content) : {};
883
- const existingAllow = parsed?.permissions?.allow || [];
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) process.exit(2);
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.name === "mcp-wizard") {
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}` }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peppermint-mcp/wizard",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "One-command installer for Peppermint MCP across AI coding hosts",
5
5
  "type": "module",
6
6
  "bin": {