@sanity/cli 6.0.0 → 6.1.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 (34) hide show
  1. package/README.md +78 -111
  2. package/dist/actions/mcp/detectAvailableEditors.js +19 -10
  3. package/dist/actions/mcp/detectAvailableEditors.js.map +1 -1
  4. package/dist/actions/mcp/editorConfigs.js +222 -158
  5. package/dist/actions/mcp/editorConfigs.js.map +1 -1
  6. package/dist/actions/mcp/promptForMCPSetup.js +16 -7
  7. package/dist/actions/mcp/promptForMCPSetup.js.map +1 -1
  8. package/dist/actions/mcp/setupMCP.js +62 -23
  9. package/dist/actions/mcp/setupMCP.js.map +1 -1
  10. package/dist/actions/mcp/types.js.map +1 -1
  11. package/dist/actions/mcp/validateEditorTokens.js +56 -0
  12. package/dist/actions/mcp/validateEditorTokens.js.map +1 -0
  13. package/dist/actions/telemetry/telemetryDebug.js +2 -2
  14. package/dist/actions/telemetry/telemetryDebug.js.map +1 -1
  15. package/dist/commands/init.js +9 -4
  16. package/dist/commands/init.js.map +1 -1
  17. package/dist/commands/mcp/configure.js +3 -1
  18. package/dist/commands/mcp/configure.js.map +1 -1
  19. package/dist/commands/preview.js +3 -4
  20. package/dist/commands/preview.js.map +1 -1
  21. package/dist/hooks/prerun/setupTelemetry.js +7 -7
  22. package/dist/hooks/prerun/setupTelemetry.js.map +1 -1
  23. package/dist/services/mcp.js +55 -1
  24. package/dist/services/mcp.js.map +1 -1
  25. package/dist/util/getLocalPackageVersion.js +4 -2
  26. package/dist/util/getLocalPackageVersion.js.map +1 -1
  27. package/dist/util/getProjectDefaults.js +22 -28
  28. package/dist/util/getProjectDefaults.js.map +1 -1
  29. package/dist/util/gitConfig.js +45 -0
  30. package/dist/util/gitConfig.js.map +1 -0
  31. package/dist/util/telemetry/telemetryStoreDebug.js +2 -2
  32. package/dist/util/telemetry/telemetryStoreDebug.js.map +1 -1
  33. package/oclif.manifest.json +454 -454
  34. package/package.json +3 -5
@@ -37,9 +37,10 @@ const debug = subdebug('mcp:detectAvailableEditors');
37
37
  }
38
38
  /**
39
39
  * Check if an editor's config is usable and whether Sanity MCP is already configured.
40
+ * If configured, extracts the existing auth token.
40
41
  * Returns null only if config exists but can't be parsed (to avoid data loss).
41
42
  */ async function checkEditorConfig(name, configPath) {
42
- const { configKey, format } = EDITOR_CONFIGS[name];
43
+ const { configKey, format, readToken } = EDITOR_CONFIGS[name];
43
44
  // Config file doesn't exist - can create it
44
45
  if (!existsSync(configPath)) {
45
46
  return {
@@ -58,10 +59,17 @@ const debug = subdebug('mcp:detectAvailableEditors');
58
59
  ;
59
60
  }
60
61
  // Check if Sanity MCP is already configured
61
- const configured = Boolean(config[configKey]?.Sanity);
62
+ const sanityConfig = config[configKey]?.Sanity;
63
+ const configured = Boolean(sanityConfig);
64
+ // Extract existing token if configured
65
+ let existingToken;
66
+ if (configured && typeof sanityConfig === 'object' && sanityConfig !== null) {
67
+ existingToken = readToken(sanityConfig);
68
+ }
62
69
  return {
63
70
  configPath,
64
71
  configured,
72
+ existingToken,
65
73
  name
66
74
  };
67
75
  } catch (err) {
@@ -73,15 +81,16 @@ const debug = subdebug('mcp:detectAvailableEditors');
73
81
  * Detect which editors are installed and have parseable configs.
74
82
  * Editors with unparseable configs are skipped to avoid data loss.
75
83
  */ export async function detectAvailableEditors() {
76
- const editors = [];
77
- for (const [name, config] of Object.entries(EDITOR_CONFIGS)){
84
+ // Detect all editors in parallel to avoid stacking timeouts —
85
+ // CLI-based editors (Claude Code, Codex CLI, OpenCode) each have a
86
+ // 5s execa timeout, so sequential detection can add ~15s on machines
87
+ // where none are installed.
88
+ const results = await Promise.all(Object.entries(EDITOR_CONFIGS).map(async ([name, config])=>{
78
89
  const configPath = await config.detect();
79
- if (configPath) {
80
- const editor = await checkEditorConfig(name, configPath);
81
- if (editor) editors.push(editor);
82
- }
83
- }
84
- return editors;
90
+ if (!configPath) return null;
91
+ return checkEditorConfig(name, configPath);
92
+ }));
93
+ return results.filter((editor)=>editor !== null);
85
94
  }
86
95
 
87
96
  //# sourceMappingURL=detectAvailableEditors.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/mcp/detectAvailableEditors.ts"],"sourcesContent":["import {existsSync} from 'node:fs'\nimport fs from 'node:fs/promises'\n\nimport {subdebug} from '@sanity/cli-core'\nimport {type ParseError, parse as parseJsonc} from 'jsonc-parser'\nimport {parse as parseToml} from 'smol-toml'\n\nimport {EDITOR_CONFIGS, type EditorName} from './editorConfigs.js'\n\nconst debug = subdebug('mcp:detectAvailableEditors')\n\ninterface Editor {\n configPath: string\n /** Whether Sanity MCP is already configured for this editor */\n configured: boolean\n name: EditorName\n}\n\ninterface MCPConfig {\n [key: string]: Record<string, unknown> | undefined\n}\n\n/**\n * Safely parse config file content\n * Returns parsed config or null if unparseable\n */\nfunction parseConfig(content: string, format: 'jsonc' | 'toml'): MCPConfig | null {\n const trimmed = content.trim()\n if (trimmed === '') {\n return {} // Empty file - safe to write, treat as empty config\n }\n\n if (format === 'toml') {\n try {\n const parsed = parseToml(content)\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n return null\n }\n\n return parsed as MCPConfig\n } catch {\n return null\n }\n }\n\n const errors: ParseError[] = []\n const parsed = parseJsonc(content, errors, {allowTrailingComma: true})\n\n if (errors.length > 0 || typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n return null // Parse failed\n }\n\n return parsed as MCPConfig\n}\n\n/**\n * Check if an editor's config is usable and whether Sanity MCP is already configured.\n * Returns null only if config exists but can't be parsed (to avoid data loss).\n */\nasync function checkEditorConfig(name: EditorName, configPath: string): Promise<Editor | null> {\n const {configKey, format} = EDITOR_CONFIGS[name]\n\n // Config file doesn't exist - can create it\n if (!existsSync(configPath)) {\n return {configPath, configured: false, name}\n }\n\n // Config exists - try to parse it\n try {\n const content = await fs.readFile(configPath, 'utf8')\n const config = parseConfig(content, format)\n\n if (config === null) {\n debug('Skipping %s: could not parse %s', name, configPath)\n return null // Can't parse - skip this editor\n }\n\n // Check if Sanity MCP is already configured\n const configured = Boolean(config[configKey]?.Sanity)\n\n return {configPath, configured, name}\n } catch (err) {\n debug('Skipping %s: could not read %s: %s', name, configPath, err)\n return null\n }\n}\n\n/**\n * Detect which editors are installed and have parseable configs.\n * Editors with unparseable configs are skipped to avoid data loss.\n */\nexport async function detectAvailableEditors(): Promise<Editor[]> {\n const editors: Editor[] = []\n\n for (const [name, config] of Object.entries(EDITOR_CONFIGS)) {\n const configPath = await config.detect()\n if (configPath) {\n const editor = await checkEditorConfig(name as EditorName, configPath)\n if (editor) editors.push(editor)\n }\n }\n\n return editors\n}\n"],"names":["existsSync","fs","subdebug","parse","parseJsonc","parseToml","EDITOR_CONFIGS","debug","parseConfig","content","format","trimmed","trim","parsed","Array","isArray","errors","allowTrailingComma","length","checkEditorConfig","name","configPath","configKey","configured","readFile","config","Boolean","Sanity","err","detectAvailableEditors","editors","Object","entries","detect","editor","push"],"mappings":"AAAA,SAAQA,UAAU,QAAO,UAAS;AAClC,OAAOC,QAAQ,mBAAkB;AAEjC,SAAQC,QAAQ,QAAO,mBAAkB;AACzC,SAAyBC,SAASC,UAAU,QAAO,eAAc;AACjE,SAAQD,SAASE,SAAS,QAAO,YAAW;AAE5C,SAAQC,cAAc,QAAwB,qBAAoB;AAElE,MAAMC,QAAQL,SAAS;AAavB;;;CAGC,GACD,SAASM,YAAYC,OAAe,EAAEC,MAAwB;IAC5D,MAAMC,UAAUF,QAAQG,IAAI;IAC5B,IAAID,YAAY,IAAI;QAClB,OAAO,CAAC,EAAE,oDAAoD;;IAChE;IAEA,IAAID,WAAW,QAAQ;QACrB,IAAI;YACF,MAAMG,SAASR,UAAUI;YACzB,IAAI,OAAOI,WAAW,YAAYA,WAAW,QAAQC,MAAMC,OAAO,CAACF,SAAS;gBAC1E,OAAO;YACT;YAEA,OAAOA;QACT,EAAE,OAAM;YACN,OAAO;QACT;IACF;IAEA,MAAMG,SAAuB,EAAE;IAC/B,MAAMH,SAAST,WAAWK,SAASO,QAAQ;QAACC,oBAAoB;IAAI;IAEpE,IAAID,OAAOE,MAAM,GAAG,KAAK,OAAOL,WAAW,YAAYA,WAAW,QAAQC,MAAMC,OAAO,CAACF,SAAS;QAC/F,OAAO,KAAK,eAAe;;IAC7B;IAEA,OAAOA;AACT;AAEA;;;CAGC,GACD,eAAeM,kBAAkBC,IAAgB,EAAEC,UAAkB;IACnE,MAAM,EAACC,SAAS,EAAEZ,MAAM,EAAC,GAAGJ,cAAc,CAACc,KAAK;IAEhD,4CAA4C;IAC5C,IAAI,CAACpB,WAAWqB,aAAa;QAC3B,OAAO;YAACA;YAAYE,YAAY;YAAOH;QAAI;IAC7C;IAEA,kCAAkC;IAClC,IAAI;QACF,MAAMX,UAAU,MAAMR,GAAGuB,QAAQ,CAACH,YAAY;QAC9C,MAAMI,SAASjB,YAAYC,SAASC;QAEpC,IAAIe,WAAW,MAAM;YACnBlB,MAAM,mCAAmCa,MAAMC;YAC/C,OAAO,KAAK,iCAAiC;;QAC/C;QAEA,4CAA4C;QAC5C,MAAME,aAAaG,QAAQD,MAAM,CAACH,UAAU,EAAEK;QAE9C,OAAO;YAACN;YAAYE;YAAYH;QAAI;IACtC,EAAE,OAAOQ,KAAK;QACZrB,MAAM,sCAAsCa,MAAMC,YAAYO;QAC9D,OAAO;IACT;AACF;AAEA;;;CAGC,GACD,OAAO,eAAeC;IACpB,MAAMC,UAAoB,EAAE;IAE5B,KAAK,MAAM,CAACV,MAAMK,OAAO,IAAIM,OAAOC,OAAO,CAAC1B,gBAAiB;QAC3D,MAAMe,aAAa,MAAMI,OAAOQ,MAAM;QACtC,IAAIZ,YAAY;YACd,MAAMa,SAAS,MAAMf,kBAAkBC,MAAoBC;YAC3D,IAAIa,QAAQJ,QAAQK,IAAI,CAACD;QAC3B;IACF;IAEA,OAAOJ;AACT"}
1
+ {"version":3,"sources":["../../../src/actions/mcp/detectAvailableEditors.ts"],"sourcesContent":["import {existsSync} from 'node:fs'\nimport fs from 'node:fs/promises'\n\nimport {subdebug} from '@sanity/cli-core'\nimport {type ParseError, parse as parseJsonc} from 'jsonc-parser'\nimport {parse as parseToml} from 'smol-toml'\n\nimport {EDITOR_CONFIGS, type EditorName} from './editorConfigs.js'\nimport {type Editor} from './types.js'\n\nconst debug = subdebug('mcp:detectAvailableEditors')\n\ninterface MCPConfig {\n [key: string]: Record<string, unknown> | undefined\n}\n\n/**\n * Safely parse config file content\n * Returns parsed config or null if unparseable\n */\nfunction parseConfig(content: string, format: 'jsonc' | 'toml'): MCPConfig | null {\n const trimmed = content.trim()\n if (trimmed === '') {\n return {} // Empty file - safe to write, treat as empty config\n }\n\n if (format === 'toml') {\n try {\n const parsed = parseToml(content)\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n return null\n }\n\n return parsed as MCPConfig\n } catch {\n return null\n }\n }\n\n const errors: ParseError[] = []\n const parsed = parseJsonc(content, errors, {allowTrailingComma: true})\n\n if (errors.length > 0 || typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n return null // Parse failed\n }\n\n return parsed as MCPConfig\n}\n\n/**\n * Check if an editor's config is usable and whether Sanity MCP is already configured.\n * If configured, extracts the existing auth token.\n * Returns null only if config exists but can't be parsed (to avoid data loss).\n */\nasync function checkEditorConfig(name: EditorName, configPath: string): Promise<Editor | null> {\n const {configKey, format, readToken} = EDITOR_CONFIGS[name]\n\n // Config file doesn't exist - can create it\n if (!existsSync(configPath)) {\n return {configPath, configured: false, name}\n }\n\n // Config exists - try to parse it\n try {\n const content = await fs.readFile(configPath, 'utf8')\n const config = parseConfig(content, format)\n\n if (config === null) {\n debug('Skipping %s: could not parse %s', name, configPath)\n return null // Can't parse - skip this editor\n }\n\n // Check if Sanity MCP is already configured\n const sanityConfig = config[configKey]?.Sanity\n const configured = Boolean(sanityConfig)\n\n // Extract existing token if configured\n let existingToken: string | undefined\n if (configured && typeof sanityConfig === 'object' && sanityConfig !== null) {\n existingToken = readToken(sanityConfig as Record<string, unknown>)\n }\n\n return {configPath, configured, existingToken, name}\n } catch (err) {\n debug('Skipping %s: could not read %s: %s', name, configPath, err)\n return null\n }\n}\n\n/**\n * Detect which editors are installed and have parseable configs.\n * Editors with unparseable configs are skipped to avoid data loss.\n */\nexport async function detectAvailableEditors(): Promise<Editor[]> {\n // Detect all editors in parallel to avoid stacking timeouts —\n // CLI-based editors (Claude Code, Codex CLI, OpenCode) each have a\n // 5s execa timeout, so sequential detection can add ~15s on machines\n // where none are installed.\n const results = await Promise.all(\n Object.entries(EDITOR_CONFIGS).map(async ([name, config]) => {\n const configPath = await config.detect()\n if (!configPath) return null\n return checkEditorConfig(name as EditorName, configPath)\n }),\n )\n\n return results.filter((editor): editor is Editor => editor !== null)\n}\n"],"names":["existsSync","fs","subdebug","parse","parseJsonc","parseToml","EDITOR_CONFIGS","debug","parseConfig","content","format","trimmed","trim","parsed","Array","isArray","errors","allowTrailingComma","length","checkEditorConfig","name","configPath","configKey","readToken","configured","readFile","config","sanityConfig","Sanity","Boolean","existingToken","err","detectAvailableEditors","results","Promise","all","Object","entries","map","detect","filter","editor"],"mappings":"AAAA,SAAQA,UAAU,QAAO,UAAS;AAClC,OAAOC,QAAQ,mBAAkB;AAEjC,SAAQC,QAAQ,QAAO,mBAAkB;AACzC,SAAyBC,SAASC,UAAU,QAAO,eAAc;AACjE,SAAQD,SAASE,SAAS,QAAO,YAAW;AAE5C,SAAQC,cAAc,QAAwB,qBAAoB;AAGlE,MAAMC,QAAQL,SAAS;AAMvB;;;CAGC,GACD,SAASM,YAAYC,OAAe,EAAEC,MAAwB;IAC5D,MAAMC,UAAUF,QAAQG,IAAI;IAC5B,IAAID,YAAY,IAAI;QAClB,OAAO,CAAC,EAAE,oDAAoD;;IAChE;IAEA,IAAID,WAAW,QAAQ;QACrB,IAAI;YACF,MAAMG,SAASR,UAAUI;YACzB,IAAI,OAAOI,WAAW,YAAYA,WAAW,QAAQC,MAAMC,OAAO,CAACF,SAAS;gBAC1E,OAAO;YACT;YAEA,OAAOA;QACT,EAAE,OAAM;YACN,OAAO;QACT;IACF;IAEA,MAAMG,SAAuB,EAAE;IAC/B,MAAMH,SAAST,WAAWK,SAASO,QAAQ;QAACC,oBAAoB;IAAI;IAEpE,IAAID,OAAOE,MAAM,GAAG,KAAK,OAAOL,WAAW,YAAYA,WAAW,QAAQC,MAAMC,OAAO,CAACF,SAAS;QAC/F,OAAO,KAAK,eAAe;;IAC7B;IAEA,OAAOA;AACT;AAEA;;;;CAIC,GACD,eAAeM,kBAAkBC,IAAgB,EAAEC,UAAkB;IACnE,MAAM,EAACC,SAAS,EAAEZ,MAAM,EAAEa,SAAS,EAAC,GAAGjB,cAAc,CAACc,KAAK;IAE3D,4CAA4C;IAC5C,IAAI,CAACpB,WAAWqB,aAAa;QAC3B,OAAO;YAACA;YAAYG,YAAY;YAAOJ;QAAI;IAC7C;IAEA,kCAAkC;IAClC,IAAI;QACF,MAAMX,UAAU,MAAMR,GAAGwB,QAAQ,CAACJ,YAAY;QAC9C,MAAMK,SAASlB,YAAYC,SAASC;QAEpC,IAAIgB,WAAW,MAAM;YACnBnB,MAAM,mCAAmCa,MAAMC;YAC/C,OAAO,KAAK,iCAAiC;;QAC/C;QAEA,4CAA4C;QAC5C,MAAMM,eAAeD,MAAM,CAACJ,UAAU,EAAEM;QACxC,MAAMJ,aAAaK,QAAQF;QAE3B,uCAAuC;QACvC,IAAIG;QACJ,IAAIN,cAAc,OAAOG,iBAAiB,YAAYA,iBAAiB,MAAM;YAC3EG,gBAAgBP,UAAUI;QAC5B;QAEA,OAAO;YAACN;YAAYG;YAAYM;YAAeV;QAAI;IACrD,EAAE,OAAOW,KAAK;QACZxB,MAAM,sCAAsCa,MAAMC,YAAYU;QAC9D,OAAO;IACT;AACF;AAEA;;;CAGC,GACD,OAAO,eAAeC;IACpB,8DAA8D;IAC9D,mEAAmE;IACnE,qEAAqE;IACrE,4BAA4B;IAC5B,MAAMC,UAAU,MAAMC,QAAQC,GAAG,CAC/BC,OAAOC,OAAO,CAAC/B,gBAAgBgC,GAAG,CAAC,OAAO,CAAClB,MAAMM,OAAO;QACtD,MAAML,aAAa,MAAMK,OAAOa,MAAM;QACtC,IAAI,CAAClB,YAAY,OAAO;QACxB,OAAOF,kBAAkBC,MAAoBC;IAC/C;IAGF,OAAOY,QAAQO,MAAM,CAAC,CAACC,SAA6BA,WAAW;AACjE"}
@@ -11,194 +11,258 @@ const defaultHttpConfig = (token)=>({
11
11
  url: MCP_SERVER_URL
12
12
  });
13
13
  const homeDir = os.homedir();
14
+ // -- Detect functions --
15
+ async function detectClaudeCode() {
16
+ try {
17
+ await execa('claude', [
18
+ '--version'
19
+ ], {
20
+ stdio: 'pipe',
21
+ timeout: 5000
22
+ });
23
+ return path.join(homeDir, '.claude.json');
24
+ } catch {
25
+ return null;
26
+ }
27
+ }
28
+ async function detectCodexCli() {
29
+ try {
30
+ await execa('codex', [
31
+ '--version'
32
+ ], {
33
+ stdio: 'pipe',
34
+ timeout: 5000
35
+ });
36
+ const codexHome = process.env.CODEX_HOME || path.join(homeDir, '.codex');
37
+ return path.join(codexHome, 'config.toml');
38
+ } catch {
39
+ return null;
40
+ }
41
+ }
42
+ async function detectCursor() {
43
+ const cursorDir = path.join(homeDir, '.cursor');
44
+ return existsSync(cursorDir) ? path.join(cursorDir, 'mcp.json') : null;
45
+ }
46
+ async function detectGeminiCli() {
47
+ const geminiDir = path.join(homeDir, '.gemini');
48
+ return existsSync(geminiDir) ? path.join(geminiDir, 'settings.json') : null;
49
+ }
50
+ async function detectGitHubCopilotCli() {
51
+ const copilotDir = process.platform === 'linux' && process.env.XDG_CONFIG_HOME ? path.join(process.env.XDG_CONFIG_HOME, 'copilot') : path.join(homeDir, '.copilot');
52
+ return existsSync(copilotDir) ? path.join(copilotDir, 'mcp-config.json') : null;
53
+ }
54
+ async function detectOpenCode() {
55
+ try {
56
+ await execa('opencode', [
57
+ '--version'
58
+ ], {
59
+ stdio: 'pipe',
60
+ timeout: 5000
61
+ });
62
+ return path.join(homeDir, '.config/opencode/opencode.json');
63
+ } catch {
64
+ return null;
65
+ }
66
+ }
67
+ async function detectVSCode() {
68
+ let configDir = null;
69
+ switch(process.platform){
70
+ case 'darwin':
71
+ {
72
+ configDir = path.join(homeDir, 'Library/Application Support/Code/User');
73
+ break;
74
+ }
75
+ case 'win32':
76
+ {
77
+ if (process.env.APPDATA) {
78
+ configDir = path.join(process.env.APPDATA, 'Code/User');
79
+ }
80
+ break;
81
+ }
82
+ default:
83
+ {
84
+ configDir = path.join(homeDir, '.config/Code/User');
85
+ }
86
+ }
87
+ return configDir && existsSync(configDir) ? path.join(configDir, 'mcp.json') : null;
88
+ }
89
+ async function detectVSCodeInsiders() {
90
+ let configDir = null;
91
+ switch(process.platform){
92
+ case 'darwin':
93
+ {
94
+ configDir = path.join(homeDir, 'Library/Application Support/Code - Insiders/User');
95
+ break;
96
+ }
97
+ case 'win32':
98
+ {
99
+ if (process.env.APPDATA) {
100
+ configDir = path.join(process.env.APPDATA, 'Code - Insiders/User');
101
+ }
102
+ break;
103
+ }
104
+ default:
105
+ {
106
+ configDir = path.join(homeDir, '.config/Code - Insiders/User');
107
+ }
108
+ }
109
+ return configDir && existsSync(configDir) ? path.join(configDir, 'mcp.json') : null;
110
+ }
111
+ async function detectZed() {
112
+ let configDir = null;
113
+ switch(process.platform){
114
+ case 'win32':
115
+ {
116
+ if (process.env.APPDATA) {
117
+ configDir = path.join(process.env.APPDATA, 'Zed');
118
+ }
119
+ break;
120
+ }
121
+ default:
122
+ {
123
+ configDir = path.join(homeDir, '.config/zed');
124
+ }
125
+ }
126
+ return configDir && existsSync(configDir) ? path.join(configDir, 'settings.json') : null;
127
+ }
128
+ // -- Read token helpers --
129
+ /**
130
+ * Extract a Bearer token from a headers-like object.
131
+ * Looks for `Authorization: "Bearer <token>"` and returns the token portion.
132
+ */ function extractBearerToken(headers) {
133
+ if (typeof headers !== 'object' || headers === null) return undefined;
134
+ const auth = headers.Authorization;
135
+ if (typeof auth !== 'string') return undefined;
136
+ const match = auth.match(/^Bearer\s+(.+)$/);
137
+ return match?.[1];
138
+ }
139
+ function readTokenFromHeaders(serverConfig) {
140
+ return extractBearerToken(serverConfig.headers);
141
+ }
142
+ function readTokenFromHttpHeaders(serverConfig) {
143
+ return extractBearerToken(serverConfig.http_headers);
144
+ }
145
+ // -- Build server config functions --
146
+ function buildClaudeCodeServerConfig(token) {
147
+ return defaultHttpConfig(token);
148
+ }
149
+ function buildCodexCliServerConfig(token) {
150
+ return {
151
+ http_headers: {
152
+ Authorization: `Bearer ${token}`
153
+ },
154
+ type: 'http',
155
+ url: MCP_SERVER_URL
156
+ };
157
+ }
158
+ function buildCursorServerConfig(token) {
159
+ return defaultHttpConfig(token);
160
+ }
161
+ function buildGeminiCliServerConfig(token) {
162
+ return defaultHttpConfig(token);
163
+ }
164
+ function buildGitHubCopilotCliServerConfig(token) {
165
+ return {
166
+ headers: {
167
+ Authorization: `Bearer ${token}`
168
+ },
169
+ tools: [
170
+ '*'
171
+ ],
172
+ type: 'http',
173
+ url: MCP_SERVER_URL
174
+ };
175
+ }
176
+ function buildOpenCodeServerConfig(token) {
177
+ return {
178
+ headers: {
179
+ Authorization: `Bearer ${token}`
180
+ },
181
+ type: 'remote',
182
+ url: MCP_SERVER_URL
183
+ };
184
+ }
185
+ function buildVSCodeServerConfig(token) {
186
+ return defaultHttpConfig(token);
187
+ }
188
+ function buildVSCodeInsidersServerConfig(token) {
189
+ return defaultHttpConfig(token);
190
+ }
191
+ function buildZedServerConfig(token) {
192
+ return {
193
+ headers: {
194
+ Authorization: `Bearer ${token}`
195
+ },
196
+ settings: {},
197
+ url: MCP_SERVER_URL
198
+ };
199
+ }
14
200
  /**
15
201
  * Centralized editor configuration including detection logic.
16
202
  * To add a new editor: add an entry here - EditorName type is derived automatically.
17
203
  */ export const EDITOR_CONFIGS = {
18
204
  'Claude Code': {
19
- buildServerConfig: defaultHttpConfig,
205
+ buildServerConfig: buildClaudeCodeServerConfig,
20
206
  configKey: 'mcpServers',
21
- detect: async ()=>{
22
- try {
23
- await execa('claude', [
24
- '--version'
25
- ], {
26
- stdio: 'pipe',
27
- timeout: 5000
28
- });
29
- return path.join(homeDir, '.claude.json');
30
- } catch {
31
- return null;
32
- }
33
- },
34
- format: 'jsonc'
207
+ detect: detectClaudeCode,
208
+ format: 'jsonc',
209
+ readToken: readTokenFromHeaders
35
210
  },
36
211
  'Codex CLI': {
37
- buildServerConfig: (token)=>({
38
- http_headers: {
39
- Authorization: `Bearer ${token}`
40
- },
41
- type: 'http',
42
- url: MCP_SERVER_URL
43
- }),
212
+ buildServerConfig: buildCodexCliServerConfig,
44
213
  configKey: 'mcp_servers',
45
- detect: async ()=>{
46
- try {
47
- await execa('codex', [
48
- '--version'
49
- ], {
50
- stdio: 'pipe',
51
- timeout: 5000
52
- });
53
- const codexHome = process.env.CODEX_HOME || path.join(homeDir, '.codex');
54
- return path.join(codexHome, 'config.toml');
55
- } catch {
56
- return null;
57
- }
58
- },
59
- format: 'toml'
214
+ detect: detectCodexCli,
215
+ format: 'toml',
216
+ readToken: readTokenFromHttpHeaders
60
217
  },
61
218
  Cursor: {
62
- buildServerConfig: defaultHttpConfig,
219
+ buildServerConfig: buildCursorServerConfig,
63
220
  configKey: 'mcpServers',
64
- detect: async ()=>{
65
- const cursorDir = path.join(homeDir, '.cursor');
66
- return existsSync(cursorDir) ? path.join(cursorDir, 'mcp.json') : null;
67
- },
68
- format: 'jsonc'
221
+ detect: detectCursor,
222
+ format: 'jsonc',
223
+ readToken: readTokenFromHeaders
69
224
  },
70
225
  'Gemini CLI': {
71
- buildServerConfig: defaultHttpConfig,
226
+ buildServerConfig: buildGeminiCliServerConfig,
72
227
  configKey: 'mcpServers',
73
- detect: async ()=>{
74
- const geminiDir = path.join(homeDir, '.gemini');
75
- return existsSync(geminiDir) ? path.join(geminiDir, 'settings.json') : null;
76
- },
77
- format: 'jsonc'
228
+ detect: detectGeminiCli,
229
+ format: 'jsonc',
230
+ readToken: readTokenFromHeaders
78
231
  },
79
232
  'GitHub Copilot CLI': {
80
- buildServerConfig: (token)=>({
81
- headers: {
82
- Authorization: `Bearer ${token}`
83
- },
84
- tools: [
85
- '*'
86
- ],
87
- type: 'http',
88
- url: MCP_SERVER_URL
89
- }),
233
+ buildServerConfig: buildGitHubCopilotCliServerConfig,
90
234
  configKey: 'mcpServers',
91
- detect: async ()=>{
92
- const copilotDir = process.platform === 'linux' && process.env.XDG_CONFIG_HOME ? path.join(process.env.XDG_CONFIG_HOME, 'copilot') : path.join(homeDir, '.copilot');
93
- return existsSync(copilotDir) ? path.join(copilotDir, 'mcp-config.json') : null;
94
- },
95
- format: 'jsonc'
235
+ detect: detectGitHubCopilotCli,
236
+ format: 'jsonc',
237
+ readToken: readTokenFromHeaders
96
238
  },
97
239
  OpenCode: {
98
- buildServerConfig: (token)=>({
99
- headers: {
100
- Authorization: `Bearer ${token}`
101
- },
102
- type: 'remote',
103
- url: MCP_SERVER_URL
104
- }),
240
+ buildServerConfig: buildOpenCodeServerConfig,
105
241
  configKey: 'mcp',
106
- detect: async ()=>{
107
- try {
108
- await execa('opencode', [
109
- '--version'
110
- ], {
111
- stdio: 'pipe',
112
- timeout: 5000
113
- });
114
- return path.join(homeDir, '.config/opencode/opencode.json');
115
- } catch {
116
- return null;
117
- }
118
- },
119
- format: 'jsonc'
242
+ detect: detectOpenCode,
243
+ format: 'jsonc',
244
+ readToken: readTokenFromHeaders
120
245
  },
121
246
  'VS Code': {
122
- buildServerConfig: defaultHttpConfig,
247
+ buildServerConfig: buildVSCodeServerConfig,
123
248
  configKey: 'servers',
124
- detect: async ()=>{
125
- let configDir = null;
126
- switch(process.platform){
127
- case 'darwin':
128
- {
129
- configDir = path.join(homeDir, 'Library/Application Support/Code/User');
130
- break;
131
- }
132
- case 'win32':
133
- {
134
- if (process.env.APPDATA) {
135
- configDir = path.join(process.env.APPDATA, 'Code/User');
136
- }
137
- break;
138
- }
139
- default:
140
- {
141
- configDir = path.join(homeDir, '.config/Code/User');
142
- }
143
- }
144
- return configDir && existsSync(configDir) ? path.join(configDir, 'mcp.json') : null;
145
- },
146
- format: 'jsonc'
249
+ detect: detectVSCode,
250
+ format: 'jsonc',
251
+ readToken: readTokenFromHeaders
147
252
  },
148
253
  'VS Code Insiders': {
149
- buildServerConfig: defaultHttpConfig,
254
+ buildServerConfig: buildVSCodeInsidersServerConfig,
150
255
  configKey: 'servers',
151
- detect: async ()=>{
152
- let configDir = null;
153
- switch(process.platform){
154
- case 'darwin':
155
- {
156
- configDir = path.join(homeDir, 'Library/Application Support/Code - Insiders/User');
157
- break;
158
- }
159
- case 'win32':
160
- {
161
- if (process.env.APPDATA) {
162
- configDir = path.join(process.env.APPDATA, 'Code - Insiders/User');
163
- }
164
- break;
165
- }
166
- default:
167
- {
168
- configDir = path.join(homeDir, '.config/Code - Insiders/User');
169
- }
170
- }
171
- return configDir && existsSync(configDir) ? path.join(configDir, 'mcp.json') : null;
172
- },
173
- format: 'jsonc'
256
+ detect: detectVSCodeInsiders,
257
+ format: 'jsonc',
258
+ readToken: readTokenFromHeaders
174
259
  },
175
260
  Zed: {
176
- buildServerConfig: (token)=>({
177
- headers: {
178
- Authorization: `Bearer ${token}`
179
- },
180
- settings: {},
181
- url: MCP_SERVER_URL
182
- }),
261
+ buildServerConfig: buildZedServerConfig,
183
262
  configKey: 'context_servers',
184
- detect: async ()=>{
185
- let configDir = null;
186
- switch(process.platform){
187
- case 'win32':
188
- {
189
- if (process.env.APPDATA) {
190
- configDir = path.join(process.env.APPDATA, 'Zed');
191
- }
192
- break;
193
- }
194
- default:
195
- {
196
- configDir = path.join(homeDir, '.config/zed');
197
- }
198
- }
199
- return configDir && existsSync(configDir) ? path.join(configDir, 'settings.json') : null;
200
- },
201
- format: 'jsonc'
263
+ detect: detectZed,
264
+ format: 'jsonc',
265
+ readToken: readTokenFromHeaders
202
266
  }
203
267
  };
204
268
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/mcp/editorConfigs.ts"],"sourcesContent":["import {existsSync} from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\n\nimport {execa} from 'execa'\n\nimport {MCP_SERVER_URL} from '../../services/mcp.js'\n\ninterface EditorConfig {\n buildServerConfig: (token: string) => Record<string, unknown>\n configKey: string\n /** Returns the config file path if editor is detected, null otherwise */\n detect: () => Promise<string | null>\n format: 'jsonc' | 'toml'\n}\n\nconst defaultHttpConfig = (token: string) => ({\n headers: {Authorization: `Bearer ${token}`},\n type: 'http',\n url: MCP_SERVER_URL,\n})\n\nconst homeDir = os.homedir()\n\n/**\n * Centralized editor configuration including detection logic.\n * To add a new editor: add an entry here - EditorName type is derived automatically.\n */\nexport const EDITOR_CONFIGS = {\n 'Claude Code': {\n buildServerConfig: defaultHttpConfig,\n configKey: 'mcpServers',\n detect: async () => {\n try {\n await execa('claude', ['--version'], {stdio: 'pipe', timeout: 5000})\n return path.join(homeDir, '.claude.json')\n } catch {\n return null\n }\n },\n format: 'jsonc',\n },\n 'Codex CLI': {\n buildServerConfig: (token) => ({\n http_headers: {Authorization: `Bearer ${token}`},\n type: 'http',\n url: MCP_SERVER_URL,\n }),\n configKey: 'mcp_servers',\n detect: async () => {\n try {\n await execa('codex', ['--version'], {stdio: 'pipe', timeout: 5000})\n const codexHome = process.env.CODEX_HOME || path.join(homeDir, '.codex')\n return path.join(codexHome, 'config.toml')\n } catch {\n return null\n }\n },\n format: 'toml',\n },\n Cursor: {\n buildServerConfig: defaultHttpConfig,\n configKey: 'mcpServers',\n detect: async () => {\n const cursorDir = path.join(homeDir, '.cursor')\n return existsSync(cursorDir) ? path.join(cursorDir, 'mcp.json') : null\n },\n format: 'jsonc',\n },\n 'Gemini CLI': {\n buildServerConfig: defaultHttpConfig,\n configKey: 'mcpServers',\n detect: async () => {\n const geminiDir = path.join(homeDir, '.gemini')\n return existsSync(geminiDir) ? path.join(geminiDir, 'settings.json') : null\n },\n format: 'jsonc',\n },\n 'GitHub Copilot CLI': {\n buildServerConfig: (token: string) => ({\n headers: {Authorization: `Bearer ${token}`},\n tools: ['*'],\n type: 'http',\n url: MCP_SERVER_URL,\n }),\n configKey: 'mcpServers',\n detect: async () => {\n const copilotDir =\n process.platform === 'linux' && process.env.XDG_CONFIG_HOME\n ? path.join(process.env.XDG_CONFIG_HOME, 'copilot')\n : path.join(homeDir, '.copilot')\n return existsSync(copilotDir) ? path.join(copilotDir, 'mcp-config.json') : null\n },\n format: 'jsonc',\n },\n OpenCode: {\n buildServerConfig: (token) => ({\n headers: {Authorization: `Bearer ${token}`},\n type: 'remote',\n url: MCP_SERVER_URL,\n }),\n configKey: 'mcp',\n detect: async () => {\n try {\n await execa('opencode', ['--version'], {stdio: 'pipe', timeout: 5000})\n return path.join(homeDir, '.config/opencode/opencode.json')\n } catch {\n return null\n }\n },\n format: 'jsonc',\n },\n 'VS Code': {\n buildServerConfig: defaultHttpConfig,\n configKey: 'servers',\n detect: async () => {\n let configDir: string | null = null\n switch (process.platform) {\n case 'darwin': {\n configDir = path.join(homeDir, 'Library/Application Support/Code/User')\n break\n }\n case 'win32': {\n if (process.env.APPDATA) {\n configDir = path.join(process.env.APPDATA, 'Code/User')\n }\n break\n }\n default: {\n configDir = path.join(homeDir, '.config/Code/User')\n }\n }\n return configDir && existsSync(configDir) ? path.join(configDir, 'mcp.json') : null\n },\n format: 'jsonc',\n },\n 'VS Code Insiders': {\n buildServerConfig: defaultHttpConfig,\n configKey: 'servers',\n detect: async () => {\n let configDir: string | null = null\n switch (process.platform) {\n case 'darwin': {\n configDir = path.join(homeDir, 'Library/Application Support/Code - Insiders/User')\n break\n }\n case 'win32': {\n if (process.env.APPDATA) {\n configDir = path.join(process.env.APPDATA, 'Code - Insiders/User')\n }\n break\n }\n default: {\n configDir = path.join(homeDir, '.config/Code - Insiders/User')\n }\n }\n return configDir && existsSync(configDir) ? path.join(configDir, 'mcp.json') : null\n },\n format: 'jsonc',\n },\n Zed: {\n buildServerConfig: (token) => ({\n headers: {Authorization: `Bearer ${token}`},\n settings: {},\n url: MCP_SERVER_URL,\n }),\n configKey: 'context_servers',\n detect: async () => {\n let configDir: string | null = null\n switch (process.platform) {\n case 'win32': {\n if (process.env.APPDATA) {\n configDir = path.join(process.env.APPDATA, 'Zed')\n }\n break\n }\n default: {\n configDir = path.join(homeDir, '.config/zed')\n }\n }\n return configDir && existsSync(configDir) ? path.join(configDir, 'settings.json') : null\n },\n format: 'jsonc',\n },\n} satisfies Record<string, EditorConfig>\n\n/** Derived from EDITOR_CONFIGS keys - add a new editor there and this updates automatically */\nexport type EditorName = keyof typeof EDITOR_CONFIGS\n"],"names":["existsSync","os","path","execa","MCP_SERVER_URL","defaultHttpConfig","token","headers","Authorization","type","url","homeDir","homedir","EDITOR_CONFIGS","buildServerConfig","configKey","detect","stdio","timeout","join","format","http_headers","codexHome","process","env","CODEX_HOME","Cursor","cursorDir","geminiDir","tools","copilotDir","platform","XDG_CONFIG_HOME","OpenCode","configDir","APPDATA","Zed","settings"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/actions/mcp/editorConfigs.ts"],"sourcesContent":["import {existsSync} from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\n\nimport {execa} from 'execa'\n\nimport {MCP_SERVER_URL} from '../../services/mcp.js'\n\ninterface EditorConfig {\n buildServerConfig: (token: string) => Record<string, unknown>\n configKey: string\n /** Returns the config file path if editor is detected, null otherwise */\n detect: () => Promise<string | null>\n format: 'jsonc' | 'toml'\n /** Extracts the auth token from a parsed Sanity server config block */\n readToken: (serverConfig: Record<string, unknown>) => string | undefined\n}\n\nconst defaultHttpConfig = (token: string) => ({\n headers: {Authorization: `Bearer ${token}`},\n type: 'http',\n url: MCP_SERVER_URL,\n})\n\nconst homeDir = os.homedir()\n\n// -- Detect functions --\n\nasync function detectClaudeCode(): Promise<string | null> {\n try {\n await execa('claude', ['--version'], {stdio: 'pipe', timeout: 5000})\n return path.join(homeDir, '.claude.json')\n } catch {\n return null\n }\n}\n\nasync function detectCodexCli(): Promise<string | null> {\n try {\n await execa('codex', ['--version'], {stdio: 'pipe', timeout: 5000})\n const codexHome = process.env.CODEX_HOME || path.join(homeDir, '.codex')\n return path.join(codexHome, 'config.toml')\n } catch {\n return null\n }\n}\n\nasync function detectCursor(): Promise<string | null> {\n const cursorDir = path.join(homeDir, '.cursor')\n return existsSync(cursorDir) ? path.join(cursorDir, 'mcp.json') : null\n}\n\nasync function detectGeminiCli(): Promise<string | null> {\n const geminiDir = path.join(homeDir, '.gemini')\n return existsSync(geminiDir) ? path.join(geminiDir, 'settings.json') : null\n}\n\nasync function detectGitHubCopilotCli(): Promise<string | null> {\n const copilotDir =\n process.platform === 'linux' && process.env.XDG_CONFIG_HOME\n ? path.join(process.env.XDG_CONFIG_HOME, 'copilot')\n : path.join(homeDir, '.copilot')\n return existsSync(copilotDir) ? path.join(copilotDir, 'mcp-config.json') : null\n}\n\nasync function detectOpenCode(): Promise<string | null> {\n try {\n await execa('opencode', ['--version'], {stdio: 'pipe', timeout: 5000})\n return path.join(homeDir, '.config/opencode/opencode.json')\n } catch {\n return null\n }\n}\n\nasync function detectVSCode(): Promise<string | null> {\n let configDir: string | null = null\n switch (process.platform) {\n case 'darwin': {\n configDir = path.join(homeDir, 'Library/Application Support/Code/User')\n break\n }\n case 'win32': {\n if (process.env.APPDATA) {\n configDir = path.join(process.env.APPDATA, 'Code/User')\n }\n break\n }\n default: {\n configDir = path.join(homeDir, '.config/Code/User')\n }\n }\n return configDir && existsSync(configDir) ? path.join(configDir, 'mcp.json') : null\n}\n\nasync function detectVSCodeInsiders(): Promise<string | null> {\n let configDir: string | null = null\n switch (process.platform) {\n case 'darwin': {\n configDir = path.join(homeDir, 'Library/Application Support/Code - Insiders/User')\n break\n }\n case 'win32': {\n if (process.env.APPDATA) {\n configDir = path.join(process.env.APPDATA, 'Code - Insiders/User')\n }\n break\n }\n default: {\n configDir = path.join(homeDir, '.config/Code - Insiders/User')\n }\n }\n return configDir && existsSync(configDir) ? path.join(configDir, 'mcp.json') : null\n}\n\nasync function detectZed(): Promise<string | null> {\n let configDir: string | null = null\n switch (process.platform) {\n case 'win32': {\n if (process.env.APPDATA) {\n configDir = path.join(process.env.APPDATA, 'Zed')\n }\n break\n }\n default: {\n configDir = path.join(homeDir, '.config/zed')\n }\n }\n return configDir && existsSync(configDir) ? path.join(configDir, 'settings.json') : null\n}\n\n// -- Read token helpers --\n\n/**\n * Extract a Bearer token from a headers-like object.\n * Looks for `Authorization: \"Bearer <token>\"` and returns the token portion.\n */\nfunction extractBearerToken(headers: unknown): string | undefined {\n if (typeof headers !== 'object' || headers === null) return undefined\n const auth = (headers as Record<string, unknown>).Authorization\n if (typeof auth !== 'string') return undefined\n const match = auth.match(/^Bearer\\s+(.+)$/)\n return match?.[1]\n}\n\nfunction readTokenFromHeaders(serverConfig: Record<string, unknown>): string | undefined {\n return extractBearerToken(serverConfig.headers)\n}\n\nfunction readTokenFromHttpHeaders(serverConfig: Record<string, unknown>): string | undefined {\n return extractBearerToken(serverConfig.http_headers)\n}\n\n// -- Build server config functions --\n\nfunction buildClaudeCodeServerConfig(token: string): Record<string, unknown> {\n return defaultHttpConfig(token)\n}\n\nfunction buildCodexCliServerConfig(token: string): Record<string, unknown> {\n return {\n http_headers: {Authorization: `Bearer ${token}`},\n type: 'http',\n url: MCP_SERVER_URL,\n }\n}\n\nfunction buildCursorServerConfig(token: string): Record<string, unknown> {\n return defaultHttpConfig(token)\n}\n\nfunction buildGeminiCliServerConfig(token: string): Record<string, unknown> {\n return defaultHttpConfig(token)\n}\n\nfunction buildGitHubCopilotCliServerConfig(token: string): Record<string, unknown> {\n return {\n headers: {Authorization: `Bearer ${token}`},\n tools: ['*'],\n type: 'http',\n url: MCP_SERVER_URL,\n }\n}\n\nfunction buildOpenCodeServerConfig(token: string): Record<string, unknown> {\n return {\n headers: {Authorization: `Bearer ${token}`},\n type: 'remote',\n url: MCP_SERVER_URL,\n }\n}\n\nfunction buildVSCodeServerConfig(token: string): Record<string, unknown> {\n return defaultHttpConfig(token)\n}\n\nfunction buildVSCodeInsidersServerConfig(token: string): Record<string, unknown> {\n return defaultHttpConfig(token)\n}\n\nfunction buildZedServerConfig(token: string): Record<string, unknown> {\n return {\n headers: {Authorization: `Bearer ${token}`},\n settings: {},\n url: MCP_SERVER_URL,\n }\n}\n\n/**\n * Centralized editor configuration including detection logic.\n * To add a new editor: add an entry here - EditorName type is derived automatically.\n */\nexport const EDITOR_CONFIGS = {\n 'Claude Code': {\n buildServerConfig: buildClaudeCodeServerConfig,\n configKey: 'mcpServers',\n detect: detectClaudeCode,\n format: 'jsonc',\n readToken: readTokenFromHeaders,\n },\n 'Codex CLI': {\n buildServerConfig: buildCodexCliServerConfig,\n configKey: 'mcp_servers',\n detect: detectCodexCli,\n format: 'toml',\n readToken: readTokenFromHttpHeaders,\n },\n Cursor: {\n buildServerConfig: buildCursorServerConfig,\n configKey: 'mcpServers',\n detect: detectCursor,\n format: 'jsonc',\n readToken: readTokenFromHeaders,\n },\n 'Gemini CLI': {\n buildServerConfig: buildGeminiCliServerConfig,\n configKey: 'mcpServers',\n detect: detectGeminiCli,\n format: 'jsonc',\n readToken: readTokenFromHeaders,\n },\n 'GitHub Copilot CLI': {\n buildServerConfig: buildGitHubCopilotCliServerConfig,\n configKey: 'mcpServers',\n detect: detectGitHubCopilotCli,\n format: 'jsonc',\n readToken: readTokenFromHeaders,\n },\n OpenCode: {\n buildServerConfig: buildOpenCodeServerConfig,\n configKey: 'mcp',\n detect: detectOpenCode,\n format: 'jsonc',\n readToken: readTokenFromHeaders,\n },\n 'VS Code': {\n buildServerConfig: buildVSCodeServerConfig,\n configKey: 'servers',\n detect: detectVSCode,\n format: 'jsonc',\n readToken: readTokenFromHeaders,\n },\n 'VS Code Insiders': {\n buildServerConfig: buildVSCodeInsidersServerConfig,\n configKey: 'servers',\n detect: detectVSCodeInsiders,\n format: 'jsonc',\n readToken: readTokenFromHeaders,\n },\n Zed: {\n buildServerConfig: buildZedServerConfig,\n configKey: 'context_servers',\n detect: detectZed,\n format: 'jsonc',\n readToken: readTokenFromHeaders,\n },\n} satisfies Record<string, EditorConfig>\n\n/** Derived from EDITOR_CONFIGS keys - add a new editor there and this updates automatically */\nexport type EditorName = keyof typeof EDITOR_CONFIGS\n"],"names":["existsSync","os","path","execa","MCP_SERVER_URL","defaultHttpConfig","token","headers","Authorization","type","url","homeDir","homedir","detectClaudeCode","stdio","timeout","join","detectCodexCli","codexHome","process","env","CODEX_HOME","detectCursor","cursorDir","detectGeminiCli","geminiDir","detectGitHubCopilotCli","copilotDir","platform","XDG_CONFIG_HOME","detectOpenCode","detectVSCode","configDir","APPDATA","detectVSCodeInsiders","detectZed","extractBearerToken","undefined","auth","match","readTokenFromHeaders","serverConfig","readTokenFromHttpHeaders","http_headers","buildClaudeCodeServerConfig","buildCodexCliServerConfig","buildCursorServerConfig","buildGeminiCliServerConfig","buildGitHubCopilotCliServerConfig","tools","buildOpenCodeServerConfig","buildVSCodeServerConfig","buildVSCodeInsidersServerConfig","buildZedServerConfig","settings","EDITOR_CONFIGS","buildServerConfig","configKey","detect","format","readToken","Cursor","OpenCode","Zed"],"mappings":"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"}
@@ -1,19 +1,28 @@
1
1
  import { checkbox } from '@sanity/cli-core/ux';
2
+ function getEditorLabel(editor) {
3
+ if (editor.configured && editor.authStatus === 'unauthorized') {
4
+ return `${editor.name} (auth expired)`;
5
+ }
6
+ if (editor.configured && !editor.existingToken) {
7
+ return `${editor.name} (missing credentials)`;
8
+ }
9
+ return editor.name;
10
+ }
2
11
  /**
3
- * Prompt user to select which editors to configure
4
- * Shows existing config status - unconfigured editors are pre-selected,
5
- * configured editors show "(already installed)" and are not pre-selected
12
+ * Prompt user to select which editors to configure.
13
+ *
14
+ * Expects only actionable editors (unconfigured, or configured with
15
+ * invalid/missing credentials). Annotates entries with auth status.
6
16
  */ export async function promptForMCPSetup(editors) {
7
17
  const editorChoices = editors.map((e)=>({
8
- checked: !e.configured,
9
- name: e.configured ? `${e.name} (already installed)` : e.name,
18
+ checked: true,
19
+ name: getEditorLabel(e),
10
20
  value: e.name
11
21
  }));
12
- const result = await checkbox({
22
+ const selectedNames = await checkbox({
13
23
  choices: editorChoices,
14
24
  message: 'Configure Sanity MCP server?'
15
25
  });
16
- const selectedNames = result;
17
26
  // User can deselect all to skip
18
27
  if (!selectedNames || selectedNames.length === 0) {
19
28
  return null;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/mcp/promptForMCPSetup.ts"],"sourcesContent":["import {checkbox} from '@sanity/cli-core/ux'\n\nimport {type Editor} from './types.js'\n\n/**\n * Prompt user to select which editors to configure\n * Shows existing config status - unconfigured editors are pre-selected,\n * configured editors show \"(already installed)\" and are not pre-selected\n */\nexport async function promptForMCPSetup(editors: Editor[]): Promise<Editor[] | null> {\n const editorChoices = editors.map((e) => ({\n checked: !e.configured, // Only pre-select if NOT already configured\n name: e.configured ? `${e.name} (already installed)` : e.name,\n value: e.name,\n }))\n\n const result = await checkbox({\n choices: editorChoices,\n message: 'Configure Sanity MCP server?',\n })\n\n const selectedNames = result\n\n // User can deselect all to skip\n if (!selectedNames || selectedNames.length === 0) {\n return null\n }\n\n return editors.filter((e) => selectedNames.includes(e.name))\n}\n"],"names":["checkbox","promptForMCPSetup","editors","editorChoices","map","e","checked","configured","name","value","result","choices","message","selectedNames","length","filter","includes"],"mappings":"AAAA,SAAQA,QAAQ,QAAO,sBAAqB;AAI5C;;;;CAIC,GACD,OAAO,eAAeC,kBAAkBC,OAAiB;IACvD,MAAMC,gBAAgBD,QAAQE,GAAG,CAAC,CAACC,IAAO,CAAA;YACxCC,SAAS,CAACD,EAAEE,UAAU;YACtBC,MAAMH,EAAEE,UAAU,GAAG,GAAGF,EAAEG,IAAI,CAAC,oBAAoB,CAAC,GAAGH,EAAEG,IAAI;YAC7DC,OAAOJ,EAAEG,IAAI;QACf,CAAA;IAEA,MAAME,SAAS,MAAMV,SAAS;QAC5BW,SAASR;QACTS,SAAS;IACX;IAEA,MAAMC,gBAAgBH;IAEtB,gCAAgC;IAChC,IAAI,CAACG,iBAAiBA,cAAcC,MAAM,KAAK,GAAG;QAChD,OAAO;IACT;IAEA,OAAOZ,QAAQa,MAAM,CAAC,CAACV,IAAMQ,cAAcG,QAAQ,CAACX,EAAEG,IAAI;AAC5D"}
1
+ {"version":3,"sources":["../../../src/actions/mcp/promptForMCPSetup.ts"],"sourcesContent":["import {checkbox} from '@sanity/cli-core/ux'\n\nimport {type Editor} from './types.js'\n\nfunction getEditorLabel(editor: Editor): string {\n if (editor.configured && editor.authStatus === 'unauthorized') {\n return `${editor.name} (auth expired)`\n }\n if (editor.configured && !editor.existingToken) {\n return `${editor.name} (missing credentials)`\n }\n return editor.name\n}\n\n/**\n * Prompt user to select which editors to configure.\n *\n * Expects only actionable editors (unconfigured, or configured with\n * invalid/missing credentials). Annotates entries with auth status.\n */\nexport async function promptForMCPSetup(editors: Editor[]): Promise<Editor[] | null> {\n const editorChoices = editors.map((e) => ({\n checked: true, // Pre-select all actionable editors\n name: getEditorLabel(e),\n value: e.name,\n }))\n\n const selectedNames = await checkbox({\n choices: editorChoices,\n message: 'Configure Sanity MCP server?',\n })\n\n // User can deselect all to skip\n if (!selectedNames || selectedNames.length === 0) {\n return null\n }\n\n return editors.filter((e) => selectedNames.includes(e.name))\n}\n"],"names":["checkbox","getEditorLabel","editor","configured","authStatus","name","existingToken","promptForMCPSetup","editors","editorChoices","map","e","checked","value","selectedNames","choices","message","length","filter","includes"],"mappings":"AAAA,SAAQA,QAAQ,QAAO,sBAAqB;AAI5C,SAASC,eAAeC,MAAc;IACpC,IAAIA,OAAOC,UAAU,IAAID,OAAOE,UAAU,KAAK,gBAAgB;QAC7D,OAAO,GAAGF,OAAOG,IAAI,CAAC,eAAe,CAAC;IACxC;IACA,IAAIH,OAAOC,UAAU,IAAI,CAACD,OAAOI,aAAa,EAAE;QAC9C,OAAO,GAAGJ,OAAOG,IAAI,CAAC,sBAAsB,CAAC;IAC/C;IACA,OAAOH,OAAOG,IAAI;AACpB;AAEA;;;;;CAKC,GACD,OAAO,eAAeE,kBAAkBC,OAAiB;IACvD,MAAMC,gBAAgBD,QAAQE,GAAG,CAAC,CAACC,IAAO,CAAA;YACxCC,SAAS;YACTP,MAAMJ,eAAeU;YACrBE,OAAOF,EAAEN,IAAI;QACf,CAAA;IAEA,MAAMS,gBAAgB,MAAMd,SAAS;QACnCe,SAASN;QACTO,SAAS;IACX;IAEA,gCAAgC;IAChC,IAAI,CAACF,iBAAiBA,cAAcG,MAAM,KAAK,GAAG;QAChD,OAAO;IACT;IAEA,OAAOT,QAAQU,MAAM,CAAC,CAACP,IAAMG,cAAcK,QAAQ,CAACR,EAAEN,IAAI;AAC5D"}