@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.
Files changed (2) hide show
  1. package/dist/cli.js +135 -30
  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
  }
@@ -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
- url: serverUrl,
523
- headers: {
524
- Authorization: `Bearer ${apiKey}`
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
- url: serverUrl,
574
- headers: {
575
- Authorization: `Bearer ${apiKey}`
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 parsed = jsonc4.parse(content);
731
- const hasPeppermint = !!parsed?.mcpServers?.["peppermint-memory"] || !!parsed?.mcpServers?.peppermint;
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 parsed = content ? jsonc5.parse(content) : {};
883
- const existingAllow = parsed?.permissions?.allow || [];
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(_hosts) {
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(selectedHosts)) {
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) process.exit(2);
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"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peppermint-mcp/wizard",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "One-command installer for Peppermint MCP across AI coding hosts",
5
5
  "type": "module",
6
6
  "bin": {