@peppermint-mcp/wizard 0.2.3 → 0.4.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.
Files changed (2) hide show
  1. package/dist/cli.js +236 -97
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -69,7 +69,7 @@ async function detectClaudeDesktop() {
69
69
  try {
70
70
  const content = readFileSync(configPath, "utf-8");
71
71
  const parsed = jsonc.parse(content);
72
- alreadyInstalled = !!parsed?.mcpServers?.peppermint;
72
+ alreadyInstalled = !!parsed?.mcpServers?.["peppermint-memory"] || !!parsed?.mcpServers?.peppermint;
73
73
  } catch {
74
74
  warnings.push("Config file exists but could not be parsed");
75
75
  }
@@ -77,7 +77,7 @@ async function detectClaudeDesktop() {
77
77
  return {
78
78
  id: "claude-desktop",
79
79
  name: "Claude Desktop",
80
- installMethod: "file-stdio-shim",
80
+ installMethod: "file-native-http",
81
81
  configPath,
82
82
  alreadyInstalled,
83
83
  needsRestart: true,
@@ -106,7 +106,7 @@ async function detectCursor() {
106
106
  try {
107
107
  const content = readFileSync2(configPath, "utf-8");
108
108
  const parsed = jsonc2.parse(content);
109
- alreadyInstalled = !!parsed?.mcpServers?.peppermint;
109
+ alreadyInstalled = !!parsed?.mcpServers?.["peppermint-memory"] || !!parsed?.mcpServers?.peppermint;
110
110
  } catch {
111
111
  warnings.push("Config file exists but could not be parsed");
112
112
  }
@@ -197,6 +197,12 @@ function saveCredentials(creds) {
197
197
  mode: 384
198
198
  });
199
199
  }
200
+ function clearCredentials() {
201
+ const path = getCredentialsPath();
202
+ if (existsSync3(path)) {
203
+ writeFileSync(path, "{}", { encoding: "utf-8", mode: 384 });
204
+ }
205
+ }
200
206
 
201
207
  // src/auth/localhost-oauth.ts
202
208
  import { createServer } from "http";
@@ -382,17 +388,20 @@ async function installClaudeCode(serverUrl, apiKey, dryRun) {
382
388
  };
383
389
  }
384
390
  try {
391
+ for (const name of ["peppermint-memory", "peppermint"]) {
392
+ try {
393
+ await exec3("claude", ["mcp", "remove", "--scope", "user", name], { timeout: 15e3 });
394
+ } catch {
395
+ }
396
+ }
385
397
  await exec3("claude", args, { timeout: 15e3 });
386
398
  return {
387
399
  success: true,
388
400
  message: "Added peppermint-memory via claude mcp add",
389
- needsRestart: false
401
+ needsRestart: true
390
402
  };
391
403
  } catch (err) {
392
404
  const message = err instanceof Error ? err.message : "claude mcp add failed";
393
- if (message.includes("already exists")) {
394
- return { success: true, message: "peppermint-memory already configured", needsRestart: false };
395
- }
396
405
  return { success: false, message, needsRestart: false };
397
406
  }
398
407
  }
@@ -458,6 +467,9 @@ Would create: ${filePath}`;
458
467
  `Config merge produced invalid JSON: ${errors.map((e) => jsonc3.printParseErrorCode(e.error)).join(", ")}`
459
468
  );
460
469
  }
470
+ if (content === updated) {
471
+ return null;
472
+ }
461
473
  if (dryRun) {
462
474
  return `Would write to ${filePath}:
463
475
  ${updated}`;
@@ -507,26 +519,23 @@ function getConfigPath3() {
507
519
  async function installClaudeDesktop(serverUrl, apiKey, dryRun) {
508
520
  const configPath = getConfigPath3();
509
521
  const serverConfig = {
510
- command: "npx",
511
- args: [
512
- "-y",
513
- "mcp-remote@latest",
514
- serverUrl,
515
- "--header",
516
- "Authorization:${PEPPERMINT_AUTH_HEADER}"
517
- ],
518
- env: {
519
- PEPPERMINT_AUTH_HEADER: `Bearer ${apiKey}`
522
+ url: serverUrl,
523
+ headers: {
524
+ Authorization: `Bearer ${apiKey}`
520
525
  }
521
526
  };
522
527
  try {
528
+ removeServerFromConfig(configPath, "mcpServers", "peppermint", dryRun);
523
529
  const result = writeServerToConfig({
524
530
  filePath: configPath,
525
531
  serverProperty: "mcpServers",
526
- serverName: "peppermint",
532
+ serverName: "peppermint-memory",
527
533
  serverConfig,
528
534
  dryRun
529
535
  });
536
+ if (result === null) {
537
+ return { success: true, message: "Already up to date", needsRestart: false };
538
+ }
530
539
  return {
531
540
  success: true,
532
541
  message: dryRun ? result : `Wrote config to ${configPath}`,
@@ -542,7 +551,7 @@ async function removeClaudeDesktop(dryRun) {
542
551
  const removed = removeServerFromConfig(
543
552
  configPath,
544
553
  "mcpServers",
545
- "peppermint",
554
+ "peppermint-memory",
546
555
  dryRun
547
556
  );
548
557
  return {
@@ -567,13 +576,17 @@ async function installCursor(serverUrl, apiKey, dryRun) {
567
576
  }
568
577
  };
569
578
  try {
579
+ removeServerFromConfig(configPath, "mcpServers", "peppermint", dryRun);
570
580
  const result = writeServerToConfig({
571
581
  filePath: configPath,
572
582
  serverProperty: "mcpServers",
573
- serverName: "peppermint",
583
+ serverName: "peppermint-memory",
574
584
  serverConfig,
575
585
  dryRun
576
586
  });
587
+ if (result === null) {
588
+ return { success: true, message: "Already up to date", needsRestart: false };
589
+ }
577
590
  return {
578
591
  success: true,
579
592
  message: dryRun ? result : `Wrote config to ${configPath}`,
@@ -589,7 +602,7 @@ async function removeCursor(dryRun) {
589
602
  const removed = removeServerFromConfig(
590
603
  configPath,
591
604
  "mcpServers",
592
- "peppermint",
605
+ "peppermint-memory",
593
606
  dryRun
594
607
  );
595
608
  return {
@@ -636,6 +649,10 @@ async function installCodex(serverUrl, apiKey, dryRun) {
636
649
  if (apiKey) {
637
650
  persistCodexEnvVar(apiKey);
638
651
  }
652
+ try {
653
+ await exec4("codex", ["mcp", "remove", "peppermint-memory"], { timeout: 15e3 });
654
+ } catch {
655
+ }
639
656
  const env = { ...process.env };
640
657
  if (apiKey) {
641
658
  env.PEPPERMINT_TOKEN = apiKey;
@@ -711,12 +728,12 @@ function checkHostConfig(hostId, configPath) {
711
728
  try {
712
729
  const content = readFileSync6(configPath, "utf-8");
713
730
  const parsed = jsonc4.parse(content);
714
- const hasPeppermint = !!parsed?.mcpServers?.peppermint;
731
+ const hasPeppermint = !!parsed?.mcpServers?.["peppermint-memory"] || !!parsed?.mcpServers?.peppermint;
715
732
  if (!hasPeppermint) {
716
733
  return {
717
734
  hostId,
718
735
  status: "fail",
719
- message: "peppermint entry not found in config"
736
+ message: "peppermint-memory entry not found in config"
720
737
  };
721
738
  }
722
739
  return {
@@ -734,13 +751,17 @@ function checkHostConfig(hostId, configPath) {
734
751
  }
735
752
 
736
753
  // src/skills/index.ts
754
+ import { createHash as createHash2 } from "crypto";
737
755
  import {
738
756
  copyFileSync as copyFileSync2,
739
757
  existsSync as existsSync7,
740
758
  mkdirSync as mkdirSync4,
759
+ readFileSync as readFileSync7,
741
760
  readdirSync,
761
+ renameSync as renameSync2,
742
762
  rmSync,
743
- statSync
763
+ statSync,
764
+ writeFileSync as writeFileSync4
744
765
  } from "fs";
745
766
  import { homedir as homedir7 } from "os";
746
767
  import { dirname as dirname3, join as join7, resolve } from "path";
@@ -751,12 +772,26 @@ var LEGACY_SKILLS = [
751
772
  "peppermint-capture",
752
773
  "peppermint-ask-twin"
753
774
  ];
775
+ var HASH_FILE = ".wizard-hash";
754
776
  function getSkillsBundlePath() {
755
777
  return resolve(__dirname, "..", "skills-bundle", "peppermint");
756
778
  }
757
779
  function getTargetPath() {
758
780
  return join7(homedir7(), ".claude", "skills", "peppermint");
759
781
  }
782
+ function hashDir(dir) {
783
+ const hash = createHash2("sha256");
784
+ const entries = readdirSync(dir).sort();
785
+ for (const entry of entries) {
786
+ const fullPath = join7(dir, entry);
787
+ if (statSync(fullPath).isDirectory()) {
788
+ hash.update(hashDir(fullPath));
789
+ } else {
790
+ hash.update(readFileSync7(fullPath));
791
+ }
792
+ }
793
+ return hash.digest("hex");
794
+ }
760
795
  function copyDirRecursive(src, dest) {
761
796
  mkdirSync4(dest, { recursive: true });
762
797
  for (const entry of readdirSync(src)) {
@@ -786,27 +821,41 @@ function removeLegacySkills(dryRun) {
786
821
  function installSkills(dryRun) {
787
822
  const bundlePath = getSkillsBundlePath();
788
823
  const targetPath = getTargetPath();
824
+ const hashFile = join7(targetPath, HASH_FILE);
789
825
  const alreadyExists = existsSync7(join7(targetPath, "SKILL.md"));
790
826
  if (!existsSync7(bundlePath)) {
791
- return { installed: false, updated: false, targetPath, error: "Skills bundle not found in package" };
827
+ return { installed: false, updated: false, skipped: false, backedUp: false, targetPath, error: "Skills bundle not found in package" };
828
+ }
829
+ const bundleHash = hashDir(bundlePath);
830
+ if (alreadyExists && existsSync7(hashFile)) {
831
+ const installedHash = readFileSync7(hashFile, "utf-8").trim();
832
+ if (installedHash === bundleHash) {
833
+ return { installed: true, updated: false, skipped: true, backedUp: false, targetPath };
834
+ }
792
835
  }
793
836
  if (dryRun) {
794
- return { installed: true, updated: alreadyExists, targetPath };
837
+ return { installed: true, updated: alreadyExists, skipped: false, backedUp: false, targetPath };
838
+ }
839
+ let backedUp = false;
840
+ if (alreadyExists && !existsSync7(hashFile)) {
841
+ const backupPath = `${targetPath}.bak.${Date.now()}`;
842
+ renameSync2(targetPath, backupPath);
843
+ backedUp = true;
844
+ } else if (alreadyExists) {
845
+ rmSync(targetPath, { recursive: true, force: true });
795
846
  }
796
847
  try {
797
- if (existsSync7(targetPath)) {
798
- rmSync(targetPath, { recursive: true, force: true });
799
- }
800
848
  copyDirRecursive(bundlePath, targetPath);
801
- return { installed: true, updated: alreadyExists, targetPath };
849
+ writeFileSync4(join7(targetPath, HASH_FILE), bundleHash, "utf-8");
850
+ return { installed: true, updated: alreadyExists, skipped: false, backedUp, targetPath };
802
851
  } catch (err) {
803
852
  const message = err instanceof Error ? err.message : "Failed to install skills";
804
- return { installed: false, updated: false, targetPath, error: message };
853
+ return { installed: false, updated: false, skipped: false, backedUp: false, targetPath, error: message };
805
854
  }
806
855
  }
807
856
 
808
857
  // src/skills/permissions.ts
809
- import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
858
+ import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
810
859
  import { homedir as homedir8 } from "os";
811
860
  import { dirname as dirname4, join as join8 } from "path";
812
861
  import * as jsonc5 from "jsonc-parser";
@@ -828,7 +877,7 @@ function installPermissions(dryRun) {
828
877
  try {
829
878
  let content = "";
830
879
  if (existsSync8(settingsPath)) {
831
- content = readFileSync7(settingsPath, "utf-8");
880
+ content = readFileSync8(settingsPath, "utf-8");
832
881
  }
833
882
  const parsed = content ? jsonc5.parse(content) : {};
834
883
  const existingAllow = parsed?.permissions?.allow || [];
@@ -848,7 +897,7 @@ function installPermissions(dryRun) {
848
897
  if (!existsSync8(dir)) {
849
898
  mkdirSync5(dir, { recursive: true });
850
899
  }
851
- writeFileSync4(settingsPath, updated, "utf-8");
900
+ writeFileSync5(settingsPath, updated, "utf-8");
852
901
  return { added: toAdd };
853
902
  } catch (err) {
854
903
  const message = err instanceof Error ? err.message : "Failed to update permissions";
@@ -891,60 +940,92 @@ async function removeHost(host, dryRun) {
891
940
  }
892
941
  }
893
942
  async function addCommand(options) {
894
- p.intro(pc.green("\u{1F33F} Peppermint MCP Wizard"));
895
- const s = p.spinner();
896
- s.start("Detecting AI hosts...");
943
+ const nonInteractive = options.yes || options.host?.length || options.authToken || !process.stdin.isTTY;
944
+ if (!process.stdin.isTTY && !nonInteractive) {
945
+ console.error("Error: peppermint-mcp-wizard requires an interactive terminal.");
946
+ console.error("For non-interactive installs, use: --host claude-code --yes");
947
+ process.exit(1);
948
+ }
949
+ if (!nonInteractive) {
950
+ p.intro(pc.green("\u{1F33F} Peppermint MCP Wizard"));
951
+ }
897
952
  const hosts = await detectHosts();
898
- s.stop("Detection complete");
899
- if (hosts.length === 0) {
900
- p.log.error(
901
- "No supported AI hosts detected. Install Claude Code, Claude Desktop, Cursor, or Codex CLI and try again."
902
- );
953
+ if (!nonInteractive) {
954
+ const s = p.spinner();
955
+ s.start("Detecting AI hosts...");
956
+ s.stop("Detection complete");
957
+ if (hosts.length === 0) {
958
+ p.log.error(
959
+ "No supported AI hosts detected. Install Claude Code, Claude Desktop, Cursor, or Codex CLI and try again."
960
+ );
961
+ process.exit(6);
962
+ }
963
+ for (const host of hosts) {
964
+ const status = host.alreadyInstalled ? pc.yellow("already configured") : pc.dim("not configured");
965
+ const version = host.version ? pc.dim(` (${host.version})`) : "";
966
+ p.log.info(`${host.alreadyInstalled ? "\u26A0" : "\u2713"} ${host.name}${version} ${status}`);
967
+ }
968
+ } else if (hosts.length === 0) {
969
+ console.error("No supported AI hosts detected.");
903
970
  process.exit(6);
904
971
  }
905
- for (const host of hosts) {
906
- const status = host.alreadyInstalled ? pc.yellow("already configured") : pc.dim("not configured");
907
- const version = host.version ? pc.dim(` (${host.version})`) : "";
908
- p.log.info(`${host.alreadyInstalled ? "\u26A0" : "\u2713"} ${host.name}${version} ${status}`);
909
- }
910
- const unconfigured = hosts.filter((h) => !h.alreadyInstalled);
911
- const toInstall = unconfigured.length > 0 ? unconfigured : hosts;
912
- const selected = await p.multiselect({
913
- message: "Install Peppermint MCP into which hosts?",
914
- options: toInstall.map((h) => ({
915
- value: h.id,
916
- label: h.name,
917
- hint: h.alreadyInstalled ? "will reinstall" : void 0
918
- })),
919
- initialValues: toInstall.map((h) => h.id)
920
- });
921
- if (p.isCancel(selected)) {
922
- p.cancel("Cancelled.");
923
- process.exit(0);
972
+ let selectedHosts;
973
+ if (options.host?.length) {
974
+ const requestedIds = options.host;
975
+ selectedHosts = hosts.filter((h) => requestedIds.includes(h.id));
976
+ const missing = requestedIds.filter((id) => !hosts.find((h) => h.id === id));
977
+ if (missing.length > 0) {
978
+ const msg = `Host(s) not detected: ${missing.join(", ")}`;
979
+ nonInteractive ? console.error(msg) : p.log.error(msg);
980
+ process.exit(6);
981
+ }
982
+ } else if (nonInteractive) {
983
+ selectedHosts = hosts;
984
+ } else {
985
+ const unconfigured = hosts.filter((h) => !h.alreadyInstalled);
986
+ const toInstall = unconfigured.length > 0 ? unconfigured : hosts;
987
+ const selected = await p.multiselect({
988
+ message: "Install Peppermint MCP into which hosts?",
989
+ options: toInstall.map((h) => ({
990
+ value: h.id,
991
+ label: h.name,
992
+ hint: h.alreadyInstalled ? "will reinstall" : void 0
993
+ })),
994
+ initialValues: toInstall.map((h) => h.id)
995
+ });
996
+ if (p.isCancel(selected)) {
997
+ p.cancel("Cancelled.");
998
+ process.exit(0);
999
+ }
1000
+ selectedHosts = hosts.filter(
1001
+ (h) => selected.includes(h.id)
1002
+ );
924
1003
  }
925
- const selectedHosts = hosts.filter(
926
- (h) => selected.includes(h.id)
927
- );
928
- s.start(`Checking server at ${options.server}...`);
929
1004
  const serverCheck = await checkServerReachable(options.server);
930
1005
  if (!serverCheck.reachable) {
931
- s.stop("Server unreachable");
932
- p.log.error(
933
- `Cannot reach ${options.server}: ${serverCheck.error}
934
- Check your internet connection and try again.`
935
- );
1006
+ const msg = `Cannot reach ${options.server}: ${serverCheck.error}`;
1007
+ nonInteractive ? console.error(msg) : p.log.error(msg);
936
1008
  process.exit(4);
937
1009
  }
938
- s.stop(
939
- `Server reachable ${pc.dim(`(${serverCheck.latencyMs}ms)`)}`
940
- );
1010
+ if (!nonInteractive) {
1011
+ p.log.success(`Server reachable ${pc.dim(`(${serverCheck.latencyMs}ms)`)}`);
1012
+ }
941
1013
  let apiKey;
942
- if (needsAuth(selectedHosts)) {
1014
+ const tokenFromFlag = options.authToken || process.env.PEPPERMINT_AUTH_TOKEN;
1015
+ if (tokenFromFlag) {
1016
+ apiKey = tokenFromFlag;
1017
+ const msg = "Using provided auth token";
1018
+ nonInteractive ? console.log(msg) : p.log.success(msg);
1019
+ } else if (needsAuth(selectedHosts)) {
943
1020
  const base = serverBase(options.server);
944
1021
  const existing = loadCredentials(base);
945
1022
  if (existing) {
946
1023
  apiKey = existing.api_key;
947
- p.log.success(`Authenticated as ${pc.bold(existing.email || "user")} (cached)`);
1024
+ const msg = `Authenticated as ${existing.email || "user"} (cached)`;
1025
+ nonInteractive ? console.log(msg) : p.log.success(pc.bold(msg));
1026
+ } else if (nonInteractive) {
1027
+ console.error("Error: non-interactive mode requires --auth-token or PEPPERMINT_AUTH_TOKEN, or cached credentials.");
1028
+ process.exit(3);
948
1029
  } else {
949
1030
  p.log.info("Opening browser for authentication...");
950
1031
  try {
@@ -975,9 +1056,12 @@ Check your internet connection and try again.`
975
1056
  p.log.info(` ${pc.green("\u2713")} Removed legacy skills: ${pc.dim(removed.join(", "))}`);
976
1057
  }
977
1058
  const skillResult = installSkills(options.dryRun);
978
- if (skillResult.installed) {
1059
+ if (skillResult.skipped) {
1060
+ p.log.info(` ${pc.green("\u2713")} Peppermint skill ${pc.dim("up to date")}`);
1061
+ } else if (skillResult.installed) {
979
1062
  const verb = skillResult.updated ? "Updated" : "Installed";
980
- p.log.info(` ${pc.green("\u2713")} ${verb} Peppermint skill ${pc.dim(skillResult.targetPath)}`);
1063
+ const backup = skillResult.backedUp ? pc.dim(" (previous version backed up)") : "";
1064
+ p.log.info(` ${pc.green("\u2713")} ${verb} Peppermint skill${backup} ${pc.dim(skillResult.targetPath)}`);
981
1065
  } else if (skillResult.error) {
982
1066
  p.log.info(` ${pc.red("\u2717")} Skill install failed: ${pc.dim(skillResult.error)}`);
983
1067
  }
@@ -1039,38 +1123,93 @@ async function doctorCommand(options) {
1039
1123
  p.outro("Health check complete");
1040
1124
  }
1041
1125
  async function removeCommand(options) {
1042
- p.intro(pc.green("\u{1F33F} Peppermint MCP Wizard \u2014 Remove"));
1126
+ const nonInteractive = options.yes || !process.stdin.isTTY;
1127
+ if (!process.stdin.isTTY && !nonInteractive) {
1128
+ console.error("Error: peppermint-mcp-wizard requires an interactive terminal.");
1129
+ console.error("For non-interactive removes, use: --yes");
1130
+ process.exit(1);
1131
+ }
1132
+ if (!nonInteractive) {
1133
+ p.intro(pc.green("\u{1F33F} Peppermint MCP Wizard \u2014 Remove"));
1134
+ }
1043
1135
  const hosts = await detectHosts();
1044
1136
  const installed = hosts.filter((h) => h.alreadyInstalled);
1045
1137
  if (installed.length === 0) {
1046
- p.log.info("Peppermint is not installed in any detected hosts.");
1138
+ const msg = "Peppermint is not installed in any detected hosts.";
1139
+ nonInteractive ? console.log(msg) : p.log.info(msg);
1047
1140
  process.exit(0);
1048
1141
  }
1049
- const selected = await p.multiselect({
1050
- message: "Remove Peppermint MCP from which hosts?",
1051
- options: installed.map((h) => ({
1052
- value: h.id,
1053
- label: h.name
1054
- })),
1055
- initialValues: installed.map((h) => h.id)
1056
- });
1057
- if (p.isCancel(selected)) {
1058
- p.cancel("Cancelled.");
1059
- process.exit(0);
1142
+ let selectedHosts;
1143
+ if (nonInteractive) {
1144
+ selectedHosts = installed;
1145
+ } else {
1146
+ const selected = await p.multiselect({
1147
+ message: "Remove Peppermint MCP from which hosts?",
1148
+ options: installed.map((h) => ({
1149
+ value: h.id,
1150
+ label: h.name
1151
+ })),
1152
+ initialValues: installed.map((h) => h.id)
1153
+ });
1154
+ if (p.isCancel(selected)) {
1155
+ p.cancel("Cancelled.");
1156
+ process.exit(0);
1157
+ }
1158
+ selectedHosts = hosts.filter(
1159
+ (h) => selected.includes(h.id)
1160
+ );
1060
1161
  }
1061
- const selectedHosts = hosts.filter(
1062
- (h) => selected.includes(h.id)
1063
- );
1064
1162
  for (const host of selectedHosts) {
1065
1163
  const result = await removeHost(host, options.dryRun);
1066
1164
  const icon = result.success ? pc.green("\u2713") : pc.red("\u2717");
1067
- p.log.info(`${icon} ${host.name} ${pc.dim(result.message)}`);
1165
+ nonInteractive ? console.log(`${result.success ? "\u2713" : "\u2717"} ${host.name} ${result.message}`) : p.log.info(`${icon} ${host.name} ${pc.dim(result.message)}`);
1166
+ }
1167
+ if (options.revokeToken) {
1168
+ const base = serverBase(options.server);
1169
+ const creds = loadCredentials(base);
1170
+ if (creds && !options.dryRun) {
1171
+ try {
1172
+ const res = await fetch(`${base}/auth/api-keys`, {
1173
+ headers: { Authorization: `Bearer ${creds.api_key}` }
1174
+ });
1175
+ if (res.ok) {
1176
+ const keys = await res.json();
1177
+ for (const key of keys) {
1178
+ if (key.name === "mcp-wizard") {
1179
+ await fetch(`${base}/auth/api-keys/${key.id}`, {
1180
+ method: "DELETE",
1181
+ headers: { Authorization: `Bearer ${creds.api_key}` }
1182
+ });
1183
+ }
1184
+ }
1185
+ }
1186
+ clearCredentials();
1187
+ const msg = "Revoked API key and cleared credentials";
1188
+ nonInteractive ? console.log(`\u2713 ${msg}`) : p.log.info(`${pc.green("\u2713")} ${msg}`);
1189
+ } catch {
1190
+ const msg = "Failed to revoke API key (credentials cleared locally)";
1191
+ clearCredentials();
1192
+ nonInteractive ? console.log(`\u26A0 ${msg}`) : p.log.info(`${pc.yellow("\u26A0")} ${msg}`);
1193
+ }
1194
+ } else if (creds && options.dryRun) {
1195
+ const msg = "Would revoke API key and clear credentials";
1196
+ nonInteractive ? console.log(msg) : p.log.info(msg);
1197
+ }
1198
+ }
1199
+ if (!nonInteractive) {
1200
+ p.outro("Removal complete");
1068
1201
  }
1069
- p.outro("Removal complete");
1070
1202
  }
1071
1203
  var program = new Command().name("peppermint-mcp-wizard").description("One-command installer for Peppermint MCP").version("0.1.0");
1072
- program.command("add", { isDefault: true }).description("Detect hosts, authenticate, install MCP config").option("--server <url>", "MCP server URL", DEFAULT_SERVER).option("--dry-run", "Print changes without writing", false).option("--no-verify", "Skip post-install verification").action((opts) => addCommand({ server: opts.server, dryRun: opts.dryRun, verify: opts.verify }));
1204
+ program.command("add", { isDefault: true }).description("Detect hosts, authenticate, install MCP config").option("--server <url>", "MCP server URL", DEFAULT_SERVER).option("--dry-run", "Print changes without writing", false).option("--no-verify", "Skip post-install verification").option("--host <id...>", "Install to specific hosts (claude-code, claude-desktop, cursor, codex)").option("--yes", "Skip all prompts (non-interactive)", false).option("--auth-token <token>", "API key or token for auth (skips browser OAuth)").action((opts) => addCommand({
1205
+ server: opts.server,
1206
+ dryRun: opts.dryRun,
1207
+ verify: opts.verify,
1208
+ host: opts.host,
1209
+ yes: opts.yes,
1210
+ authToken: opts.authToken
1211
+ }));
1073
1212
  program.command("list").description("List detected AI hosts and their Peppermint status").option("--server <url>", "MCP server URL", DEFAULT_SERVER).action((opts) => listCommand({ server: opts.server }));
1074
1213
  program.command("doctor").description("Run health checks on existing installation").option("--server <url>", "MCP server URL", DEFAULT_SERVER).action((opts) => doctorCommand({ server: opts.server }));
1075
- program.command("remove").description("Remove Peppermint MCP from selected hosts").option("--server <url>", "MCP server URL", DEFAULT_SERVER).option("--dry-run", "Print changes without writing", false).action((opts) => removeCommand({ server: opts.server, dryRun: opts.dryRun }));
1214
+ program.command("remove").description("Remove Peppermint MCP from selected hosts").option("--server <url>", "MCP server URL", DEFAULT_SERVER).option("--dry-run", "Print changes without writing", false).option("--yes", "Skip all prompts (non-interactive)", false).option("--revoke-token", "Revoke API key on the server", true).option("--no-revoke-token", "Keep API key for future re-installs").action((opts) => removeCommand({ server: opts.server, dryRun: opts.dryRun, yes: opts.yes, revokeToken: opts.revokeToken }));
1076
1215
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peppermint-mcp/wizard",
3
- "version": "0.2.3",
3
+ "version": "0.4.0",
4
4
  "description": "One-command installer for Peppermint MCP across AI coding hosts",
5
5
  "type": "module",
6
6
  "bin": {