@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
@@ -4,17 +4,20 @@ import { logSymbols } from '@sanity/cli-core/ux';
4
4
  import { createMCPToken, MCP_SERVER_URL } from '../../services/mcp.js';
5
5
  import { detectAvailableEditors } from './detectAvailableEditors.js';
6
6
  import { promptForMCPSetup } from './promptForMCPSetup.js';
7
+ import { validateEditorTokens } from './validateEditorTokens.js';
7
8
  import { writeMCPConfig } from './writeMCPConfig.js';
8
9
  const mcpDebug = subdebug('mcp:setup');
9
10
  const NO_EDITORS_DETECTED_MESSAGE = `Couldn't auto-configure Sanity MCP server for your editor. Visit ${MCP_SERVER_URL} for setup instructions.`;
10
11
  /**
11
12
  * Main MCP setup orchestration
12
- * Opt-out by default: runs automatically unless skipMcp flag is set
13
- */ export async function setupMCP(mcp) {
13
+ * Opt-out by default: runs automatically unless skip option is set
14
+ */ export async function setupMCP(options) {
15
+ const { explicit = false, skip = false } = options ?? {};
14
16
  // 1. Check for explicit opt-out
15
- if (mcp === false) {
17
+ if (skip) {
16
18
  ux.warn('Skipping MCP configuration due to --no-mcp flag');
17
19
  return {
20
+ alreadyConfiguredEditors: [],
18
21
  configuredEditors: [],
19
22
  detectedEditors: [],
20
23
  skipped: true
@@ -25,44 +28,79 @@ const NO_EDITORS_DETECTED_MESSAGE = `Couldn't auto-configure Sanity MCP server f
25
28
  const detectedEditors = editors.map((e)=>e.name);
26
29
  mcpDebug('Detected %d editors: %s', detectedEditors.length, detectedEditors);
27
30
  if (editors.length === 0) {
28
- ux.warn(NO_EDITORS_DETECTED_MESSAGE);
31
+ if (explicit) {
32
+ ux.warn(NO_EDITORS_DETECTED_MESSAGE);
33
+ }
29
34
  return {
35
+ alreadyConfiguredEditors: [],
30
36
  configuredEditors: [],
31
37
  detectedEditors,
32
38
  skipped: true
33
39
  };
34
40
  }
35
- // 3. Prompt user (shows existing config status, only pre-selects unconfigured editors)
36
- const selected = await promptForMCPSetup(editors);
37
- if (!selected || selected.length === 0) {
38
- // User deselected all editors
39
- ux.stdout('MCP configuration skipped');
41
+ // 3. Validate existing tokens against the Sanity API
42
+ await validateEditorTokens(editors);
43
+ // 4. Check if there's anything actionable
44
+ const actionable = editors.filter((e)=>!e.configured || e.authStatus !== 'valid');
45
+ if (actionable.length === 0) {
46
+ mcpDebug('All editors configured with valid credentials');
47
+ const alreadyConfiguredEditors = editors.filter((e)=>e.configured && e.authStatus === 'valid').map((e)=>e.name);
48
+ if (explicit) {
49
+ ux.stdout(`${logSymbols.success} All detected editors are already configured`);
50
+ }
40
51
  return {
52
+ alreadyConfiguredEditors,
41
53
  configuredEditors: [],
42
54
  detectedEditors,
43
55
  skipped: true
44
56
  };
45
57
  }
46
- // 4. Create child token for MCP
47
- let token;
48
- try {
49
- token = await createMCPToken();
50
- } catch (error) {
51
- const err = error instanceof Error ? error : new Error(String(error));
52
- mcpDebug('Error creating MCP token', error);
53
- ux.warn(`Could not configure MCP: ${err.message}`);
54
- ux.warn('You can set up MCP manually later using https://mcp.sanity.io');
58
+ // Non-actionable editors are already configured with valid credentials
59
+ const alreadyConfiguredEditors = editors.filter((e)=>!actionable.includes(e)).map((e)=>e.name);
60
+ // 5. Prompt user (shows only actionable editors, annotates auth issues)
61
+ const selected = await promptForMCPSetup(actionable);
62
+ if (!selected || selected.length === 0) {
63
+ // User deselected all editors
64
+ ux.stdout('MCP configuration skipped');
55
65
  return {
66
+ alreadyConfiguredEditors,
56
67
  configuredEditors: [],
57
68
  detectedEditors,
58
- error: err,
59
- skipped: false
69
+ skipped: true
60
70
  };
61
71
  }
62
- // 5. Write configs for each selected editor
72
+ // 6. Get a token reuse a valid existing one or create a new one
73
+ let token;
74
+ // Look for an existing valid token we can reuse
75
+ const validEditor = editors.find((e)=>e.authStatus === 'valid' && e.existingToken);
76
+ if (validEditor?.existingToken) {
77
+ mcpDebug('Reusing valid token from %s', validEditor.name);
78
+ token = validEditor.existingToken;
79
+ }
80
+ // Fall back to creating a new token
81
+ if (!token) {
82
+ try {
83
+ token = await createMCPToken();
84
+ } catch (error) {
85
+ const err = error instanceof Error ? error : new Error(String(error));
86
+ mcpDebug('Error creating MCP token', error);
87
+ ux.warn(`Could not configure MCP: ${err.message}`);
88
+ ux.warn('You can set up MCP manually later using https://mcp.sanity.io');
89
+ return {
90
+ alreadyConfiguredEditors,
91
+ configuredEditors: [],
92
+ detectedEditors,
93
+ error: err,
94
+ skipped: false
95
+ };
96
+ }
97
+ }
98
+ // 7. Write configs for each selected editor
99
+ const configuredEditors = [];
63
100
  try {
64
101
  for (const editor of selected){
65
102
  await writeMCPConfig(editor, token);
103
+ configuredEditors.push(editor.name);
66
104
  }
67
105
  } catch (error) {
68
106
  const err = error instanceof Error ? error : new Error(String(error));
@@ -70,15 +108,16 @@ const NO_EDITORS_DETECTED_MESSAGE = `Couldn't auto-configure Sanity MCP server f
70
108
  ux.warn(`Could not configure MCP: ${err.message}`);
71
109
  ux.warn('You can set up MCP manually later using https://mcp.sanity.io');
72
110
  return {
73
- configuredEditors: [],
111
+ alreadyConfiguredEditors,
112
+ configuredEditors,
74
113
  detectedEditors,
75
114
  error: err,
76
115
  skipped: false
77
116
  };
78
117
  }
79
- const configuredEditors = selected.map((e)=>e.name);
80
118
  ux.stdout(`${logSymbols.success} MCP configured for ${configuredEditors.join(', ')}`);
81
119
  return {
120
+ alreadyConfiguredEditors,
82
121
  configuredEditors,
83
122
  detectedEditors,
84
123
  skipped: false
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/mcp/setupMCP.ts"],"sourcesContent":["import {ux} from '@oclif/core'\nimport {subdebug} from '@sanity/cli-core'\nimport {logSymbols} from '@sanity/cli-core/ux'\n\nimport {createMCPToken, MCP_SERVER_URL} from '../../services/mcp.js'\nimport {detectAvailableEditors} from './detectAvailableEditors.js'\nimport {type EditorName} from './editorConfigs.js'\nimport {promptForMCPSetup} from './promptForMCPSetup.js'\nimport {writeMCPConfig} from './writeMCPConfig.js'\n\nconst mcpDebug = subdebug('mcp:setup')\n\nconst NO_EDITORS_DETECTED_MESSAGE = `Couldn't auto-configure Sanity MCP server for your editor. Visit ${MCP_SERVER_URL} for setup instructions.`\n\ninterface MCPSetupResult {\n configuredEditors: EditorName[]\n detectedEditors: EditorName[]\n skipped: boolean\n\n error?: Error\n}\n\n/**\n * Main MCP setup orchestration\n * Opt-out by default: runs automatically unless skipMcp flag is set\n */\nexport async function setupMCP(mcp?: boolean): Promise<MCPSetupResult> {\n // 1. Check for explicit opt-out\n if (mcp === false) {\n ux.warn('Skipping MCP configuration due to --no-mcp flag')\n return {\n configuredEditors: [],\n detectedEditors: [],\n skipped: true,\n }\n }\n\n // 2. Detect available editors (filters out unparseable configs)\n const editors = await detectAvailableEditors()\n const detectedEditors = editors.map((e) => e.name)\n\n mcpDebug('Detected %d editors: %s', detectedEditors.length, detectedEditors)\n\n if (editors.length === 0) {\n ux.warn(NO_EDITORS_DETECTED_MESSAGE)\n return {\n configuredEditors: [],\n detectedEditors,\n skipped: true,\n }\n }\n\n // 3. Prompt user (shows existing config status, only pre-selects unconfigured editors)\n const selected = await promptForMCPSetup(editors)\n\n if (!selected || selected.length === 0) {\n // User deselected all editors\n ux.stdout('MCP configuration skipped')\n return {\n configuredEditors: [],\n detectedEditors,\n skipped: true,\n }\n }\n\n // 4. Create child token for MCP\n let token: string\n try {\n token = await createMCPToken()\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error))\n mcpDebug('Error creating MCP token', error)\n ux.warn(`Could not configure MCP: ${err.message}`)\n ux.warn('You can set up MCP manually later using https://mcp.sanity.io')\n return {\n configuredEditors: [],\n detectedEditors,\n error: err,\n skipped: false,\n }\n }\n\n // 5. Write configs for each selected editor\n try {\n for (const editor of selected) {\n await writeMCPConfig(editor, token)\n }\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error))\n mcpDebug('Error writing MCP config', error)\n ux.warn(`Could not configure MCP: ${err.message}`)\n ux.warn('You can set up MCP manually later using https://mcp.sanity.io')\n return {\n configuredEditors: [],\n detectedEditors,\n error: err,\n skipped: false,\n }\n }\n\n const configuredEditors = selected.map((e) => e.name)\n ux.stdout(`${logSymbols.success} MCP configured for ${configuredEditors.join(', ')}`)\n\n return {\n configuredEditors,\n detectedEditors,\n skipped: false,\n }\n}\n"],"names":["ux","subdebug","logSymbols","createMCPToken","MCP_SERVER_URL","detectAvailableEditors","promptForMCPSetup","writeMCPConfig","mcpDebug","NO_EDITORS_DETECTED_MESSAGE","setupMCP","mcp","warn","configuredEditors","detectedEditors","skipped","editors","map","e","name","length","selected","stdout","token","error","err","Error","String","message","editor","success","join"],"mappings":"AAAA,SAAQA,EAAE,QAAO,cAAa;AAC9B,SAAQC,QAAQ,QAAO,mBAAkB;AACzC,SAAQC,UAAU,QAAO,sBAAqB;AAE9C,SAAQC,cAAc,EAAEC,cAAc,QAAO,wBAAuB;AACpE,SAAQC,sBAAsB,QAAO,8BAA6B;AAElE,SAAQC,iBAAiB,QAAO,yBAAwB;AACxD,SAAQC,cAAc,QAAO,sBAAqB;AAElD,MAAMC,WAAWP,SAAS;AAE1B,MAAMQ,8BAA8B,CAAC,iEAAiE,EAAEL,eAAe,wBAAwB,CAAC;AAUhJ;;;CAGC,GACD,OAAO,eAAeM,SAASC,GAAa;IAC1C,gCAAgC;IAChC,IAAIA,QAAQ,OAAO;QACjBX,GAAGY,IAAI,CAAC;QACR,OAAO;YACLC,mBAAmB,EAAE;YACrBC,iBAAiB,EAAE;YACnBC,SAAS;QACX;IACF;IAEA,gEAAgE;IAChE,MAAMC,UAAU,MAAMX;IACtB,MAAMS,kBAAkBE,QAAQC,GAAG,CAAC,CAACC,IAAMA,EAAEC,IAAI;IAEjDX,SAAS,2BAA2BM,gBAAgBM,MAAM,EAAEN;IAE5D,IAAIE,QAAQI,MAAM,KAAK,GAAG;QACxBpB,GAAGY,IAAI,CAACH;QACR,OAAO;YACLI,mBAAmB,EAAE;YACrBC;YACAC,SAAS;QACX;IACF;IAEA,uFAAuF;IACvF,MAAMM,WAAW,MAAMf,kBAAkBU;IAEzC,IAAI,CAACK,YAAYA,SAASD,MAAM,KAAK,GAAG;QACtC,8BAA8B;QAC9BpB,GAAGsB,MAAM,CAAC;QACV,OAAO;YACLT,mBAAmB,EAAE;YACrBC;YACAC,SAAS;QACX;IACF;IAEA,gCAAgC;IAChC,IAAIQ;IACJ,IAAI;QACFA,QAAQ,MAAMpB;IAChB,EAAE,OAAOqB,OAAO;QACd,MAAMC,MAAMD,iBAAiBE,QAAQF,QAAQ,IAAIE,MAAMC,OAAOH;QAC9DhB,SAAS,4BAA4BgB;QACrCxB,GAAGY,IAAI,CAAC,CAAC,yBAAyB,EAAEa,IAAIG,OAAO,EAAE;QACjD5B,GAAGY,IAAI,CAAC;QACR,OAAO;YACLC,mBAAmB,EAAE;YACrBC;YACAU,OAAOC;YACPV,SAAS;QACX;IACF;IAEA,4CAA4C;IAC5C,IAAI;QACF,KAAK,MAAMc,UAAUR,SAAU;YAC7B,MAAMd,eAAesB,QAAQN;QAC/B;IACF,EAAE,OAAOC,OAAO;QACd,MAAMC,MAAMD,iBAAiBE,QAAQF,QAAQ,IAAIE,MAAMC,OAAOH;QAC9DhB,SAAS,4BAA4BgB;QACrCxB,GAAGY,IAAI,CAAC,CAAC,yBAAyB,EAAEa,IAAIG,OAAO,EAAE;QACjD5B,GAAGY,IAAI,CAAC;QACR,OAAO;YACLC,mBAAmB,EAAE;YACrBC;YACAU,OAAOC;YACPV,SAAS;QACX;IACF;IAEA,MAAMF,oBAAoBQ,SAASJ,GAAG,CAAC,CAACC,IAAMA,EAAEC,IAAI;IACpDnB,GAAGsB,MAAM,CAAC,GAAGpB,WAAW4B,OAAO,CAAC,oBAAoB,EAAEjB,kBAAkBkB,IAAI,CAAC,OAAO;IAEpF,OAAO;QACLlB;QACAC;QACAC,SAAS;IACX;AACF"}
1
+ {"version":3,"sources":["../../../src/actions/mcp/setupMCP.ts"],"sourcesContent":["import {ux} from '@oclif/core'\nimport {subdebug} from '@sanity/cli-core'\nimport {logSymbols} from '@sanity/cli-core/ux'\n\nimport {createMCPToken, MCP_SERVER_URL} from '../../services/mcp.js'\nimport {detectAvailableEditors} from './detectAvailableEditors.js'\nimport {type EditorName} from './editorConfigs.js'\nimport {promptForMCPSetup} from './promptForMCPSetup.js'\nimport {validateEditorTokens} from './validateEditorTokens.js'\nimport {writeMCPConfig} from './writeMCPConfig.js'\n\nconst mcpDebug = subdebug('mcp:setup')\n\nconst NO_EDITORS_DETECTED_MESSAGE = `Couldn't auto-configure Sanity MCP server for your editor. Visit ${MCP_SERVER_URL} for setup instructions.`\n\ninterface MCPSetupOptions {\n /**\n * Whether the user explicitly requested MCP configuration (e.g. `sanity mcp configure`).\n * When true, shows status messages even when there's nothing to do.\n * When false/undefined (e.g. called from `sanity init`), stays quiet.\n */\n explicit?: boolean\n\n /**\n * If true, skip MCP configuration entirely (e.g. --no-mcp flag).\n */\n skip?: boolean\n}\n\ninterface MCPSetupResult {\n /** Editors that were already configured with valid credentials (nothing to do) */\n alreadyConfiguredEditors: EditorName[]\n configuredEditors: EditorName[]\n detectedEditors: EditorName[]\n skipped: boolean\n\n error?: Error\n}\n\n/**\n * Main MCP setup orchestration\n * Opt-out by default: runs automatically unless skip option is set\n */\nexport async function setupMCP(options?: MCPSetupOptions): Promise<MCPSetupResult> {\n const {explicit = false, skip = false} = options ?? {}\n\n // 1. Check for explicit opt-out\n if (skip) {\n ux.warn('Skipping MCP configuration due to --no-mcp flag')\n return {\n alreadyConfiguredEditors: [],\n configuredEditors: [],\n detectedEditors: [],\n skipped: true,\n }\n }\n\n // 2. Detect available editors (filters out unparseable configs)\n const editors = await detectAvailableEditors()\n const detectedEditors = editors.map((e) => e.name)\n\n mcpDebug('Detected %d editors: %s', detectedEditors.length, detectedEditors)\n\n if (editors.length === 0) {\n if (explicit) {\n ux.warn(NO_EDITORS_DETECTED_MESSAGE)\n }\n return {\n alreadyConfiguredEditors: [],\n configuredEditors: [],\n detectedEditors,\n skipped: true,\n }\n }\n\n // 3. Validate existing tokens against the Sanity API\n await validateEditorTokens(editors)\n\n // 4. Check if there's anything actionable\n const actionable = editors.filter((e) => !e.configured || e.authStatus !== 'valid')\n\n if (actionable.length === 0) {\n mcpDebug('All editors configured with valid credentials')\n const alreadyConfiguredEditors = editors\n .filter((e) => e.configured && e.authStatus === 'valid')\n .map((e) => e.name)\n if (explicit) {\n ux.stdout(`${logSymbols.success} All detected editors are already configured`)\n }\n return {\n alreadyConfiguredEditors,\n configuredEditors: [],\n detectedEditors,\n skipped: true,\n }\n }\n\n // Non-actionable editors are already configured with valid credentials\n const alreadyConfiguredEditors = editors.filter((e) => !actionable.includes(e)).map((e) => e.name)\n\n // 5. Prompt user (shows only actionable editors, annotates auth issues)\n const selected = await promptForMCPSetup(actionable)\n\n if (!selected || selected.length === 0) {\n // User deselected all editors\n ux.stdout('MCP configuration skipped')\n return {\n alreadyConfiguredEditors,\n configuredEditors: [],\n detectedEditors,\n skipped: true,\n }\n }\n\n // 6. Get a token reuse a valid existing one or create a new one\n let token: string | undefined\n\n // Look for an existing valid token we can reuse\n const validEditor = editors.find((e) => e.authStatus === 'valid' && e.existingToken)\n if (validEditor?.existingToken) {\n mcpDebug('Reusing valid token from %s', validEditor.name)\n token = validEditor.existingToken\n }\n\n // Fall back to creating a new token\n if (!token) {\n try {\n token = await createMCPToken()\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error))\n mcpDebug('Error creating MCP token', error)\n ux.warn(`Could not configure MCP: ${err.message}`)\n ux.warn('You can set up MCP manually later using https://mcp.sanity.io')\n return {\n alreadyConfiguredEditors,\n configuredEditors: [],\n detectedEditors,\n error: err,\n skipped: false,\n }\n }\n }\n\n // 7. Write configs for each selected editor\n const configuredEditors: EditorName[] = []\n try {\n for (const editor of selected) {\n await writeMCPConfig(editor, token)\n configuredEditors.push(editor.name)\n }\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error))\n mcpDebug('Error writing MCP config', error)\n ux.warn(`Could not configure MCP: ${err.message}`)\n ux.warn('You can set up MCP manually later using https://mcp.sanity.io')\n return {\n alreadyConfiguredEditors,\n configuredEditors,\n detectedEditors,\n error: err,\n skipped: false,\n }\n }\n\n ux.stdout(`${logSymbols.success} MCP configured for ${configuredEditors.join(', ')}`)\n\n return {\n alreadyConfiguredEditors,\n configuredEditors,\n detectedEditors,\n skipped: false,\n }\n}\n"],"names":["ux","subdebug","logSymbols","createMCPToken","MCP_SERVER_URL","detectAvailableEditors","promptForMCPSetup","validateEditorTokens","writeMCPConfig","mcpDebug","NO_EDITORS_DETECTED_MESSAGE","setupMCP","options","explicit","skip","warn","alreadyConfiguredEditors","configuredEditors","detectedEditors","skipped","editors","map","e","name","length","actionable","filter","configured","authStatus","stdout","success","includes","selected","token","validEditor","find","existingToken","error","err","Error","String","message","editor","push","join"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/mcp/types.ts"],"sourcesContent":["import {type EditorName} from './editorConfigs.js'\n\nexport interface Editor {\n configPath: string\n /** Whether Sanity MCP is already configured for this editor */\n configured: boolean\n name: EditorName\n}\n"],"names":[],"mappings":"AAEA,WAKC"}
1
+ {"version":3,"sources":["../../../src/actions/mcp/types.ts"],"sourcesContent":["import {type EditorName} from './editorConfigs.js'\n\n/** Auth credential status for a configured editor */\nexport type AuthStatus = 'unauthorized' | 'valid'\n\nexport interface Editor {\n configPath: string\n /** Whether Sanity MCP is already configured for this editor */\n configured: boolean\n name: EditorName\n\n /**\n * Auth status of the existing token. Only set for editors that have\n * a Sanity MCP config with a token that has been validated against the API.\n */\n authStatus?: AuthStatus\n /** The existing auth token found in the editor config, if any */\n existingToken?: string\n}\n"],"names":[],"mappings":"AAKA,WAaC"}
@@ -0,0 +1,56 @@
1
+ import { subdebug } from '@sanity/cli-core';
2
+ import { validateMCPToken } from '../../services/mcp.js';
3
+ const debug = subdebug('mcp:validateEditorTokens');
4
+ /**
5
+ * Validate existing MCP tokens for all configured editors.
6
+ *
7
+ * Collects unique tokens, validates each once against the Sanity API,
8
+ * and sets `authStatus` on each editor that has a token.
9
+ * Editors without a config or token are left unchanged.
10
+ */ export async function validateEditorTokens(editors) {
11
+ // Collect unique tokens and map them to their editors
12
+ const tokenToEditors = new Map();
13
+ for (const editor of editors){
14
+ if (editor.existingToken) {
15
+ const existing = tokenToEditors.get(editor.existingToken);
16
+ if (existing) {
17
+ existing.push(editor);
18
+ } else {
19
+ tokenToEditors.set(editor.existingToken, [
20
+ editor
21
+ ]);
22
+ }
23
+ }
24
+ }
25
+ if (tokenToEditors.size === 0) {
26
+ debug('No existing tokens to validate');
27
+ return;
28
+ }
29
+ const editorCount = [
30
+ ...tokenToEditors.values()
31
+ ].reduce((sum, eds)=>sum + eds.length, 0);
32
+ debug('Validating %d unique token(s) across %d editor(s)', tokenToEditors.size, editorCount);
33
+ // Validate all unique tokens in parallel to avoid stacking timeouts
34
+ await Promise.all([
35
+ ...tokenToEditors.entries()
36
+ ].map(async ([token, tokenEditors])=>{
37
+ let status;
38
+ try {
39
+ const valid = await validateMCPToken(token);
40
+ status = valid ? 'valid' : 'unauthorized';
41
+ } catch (err) {
42
+ // Network errors, timeouts, or unexpected failures — assume the token
43
+ // is valid rather than falsely marking it as expired. We only mark
44
+ // tokens as unauthorized when the server explicitly says so (401/403).
45
+ debug('Token validation error (assuming valid): %s', err);
46
+ status = 'valid';
47
+ }
48
+ debug('Token ending ...%s is %s (used by %s)', token.slice(-4), status, tokenEditors.map((e)=>e.name).join(', '));
49
+ // Apply status to all editors sharing this token
50
+ for (const editor of tokenEditors){
51
+ editor.authStatus = status;
52
+ }
53
+ }));
54
+ }
55
+
56
+ //# sourceMappingURL=validateEditorTokens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/actions/mcp/validateEditorTokens.ts"],"sourcesContent":["import {subdebug} from '@sanity/cli-core'\n\nimport {validateMCPToken} from '../../services/mcp.js'\nimport {type AuthStatus, type Editor} from './types.js'\n\nconst debug = subdebug('mcp:validateEditorTokens')\n\n/**\n * Validate existing MCP tokens for all configured editors.\n *\n * Collects unique tokens, validates each once against the Sanity API,\n * and sets `authStatus` on each editor that has a token.\n * Editors without a config or token are left unchanged.\n */\nexport async function validateEditorTokens(editors: Editor[]): Promise<void> {\n // Collect unique tokens and map them to their editors\n const tokenToEditors = new Map<string, Editor[]>()\n for (const editor of editors) {\n if (editor.existingToken) {\n const existing = tokenToEditors.get(editor.existingToken)\n if (existing) {\n existing.push(editor)\n } else {\n tokenToEditors.set(editor.existingToken, [editor])\n }\n }\n }\n\n if (tokenToEditors.size === 0) {\n debug('No existing tokens to validate')\n return\n }\n\n const editorCount = [...tokenToEditors.values()].reduce((sum, eds) => sum + eds.length, 0)\n debug('Validating %d unique token(s) across %d editor(s)', tokenToEditors.size, editorCount)\n\n // Validate all unique tokens in parallel to avoid stacking timeouts\n await Promise.all(\n [...tokenToEditors.entries()].map(async ([token, tokenEditors]) => {\n let status: AuthStatus\n try {\n const valid = await validateMCPToken(token)\n status = valid ? 'valid' : 'unauthorized'\n } catch (err) {\n // Network errors, timeouts, or unexpected failures — assume the token\n // is valid rather than falsely marking it as expired. We only mark\n // tokens as unauthorized when the server explicitly says so (401/403).\n debug('Token validation error (assuming valid): %s', err)\n status = 'valid'\n }\n\n debug(\n 'Token ending ...%s is %s (used by %s)',\n token.slice(-4),\n status,\n tokenEditors.map((e) => e.name).join(', '),\n )\n\n // Apply status to all editors sharing this token\n for (const editor of tokenEditors) {\n editor.authStatus = status\n }\n }),\n )\n}\n"],"names":["subdebug","validateMCPToken","debug","validateEditorTokens","editors","tokenToEditors","Map","editor","existingToken","existing","get","push","set","size","editorCount","values","reduce","sum","eds","length","Promise","all","entries","map","token","tokenEditors","status","valid","err","slice","e","name","join","authStatus"],"mappings":"AAAA,SAAQA,QAAQ,QAAO,mBAAkB;AAEzC,SAAQC,gBAAgB,QAAO,wBAAuB;AAGtD,MAAMC,QAAQF,SAAS;AAEvB;;;;;;CAMC,GACD,OAAO,eAAeG,qBAAqBC,OAAiB;IAC1D,sDAAsD;IACtD,MAAMC,iBAAiB,IAAIC;IAC3B,KAAK,MAAMC,UAAUH,QAAS;QAC5B,IAAIG,OAAOC,aAAa,EAAE;YACxB,MAAMC,WAAWJ,eAAeK,GAAG,CAACH,OAAOC,aAAa;YACxD,IAAIC,UAAU;gBACZA,SAASE,IAAI,CAACJ;YAChB,OAAO;gBACLF,eAAeO,GAAG,CAACL,OAAOC,aAAa,EAAE;oBAACD;iBAAO;YACnD;QACF;IACF;IAEA,IAAIF,eAAeQ,IAAI,KAAK,GAAG;QAC7BX,MAAM;QACN;IACF;IAEA,MAAMY,cAAc;WAAIT,eAAeU,MAAM;KAAG,CAACC,MAAM,CAAC,CAACC,KAAKC,MAAQD,MAAMC,IAAIC,MAAM,EAAE;IACxFjB,MAAM,qDAAqDG,eAAeQ,IAAI,EAAEC;IAEhF,oEAAoE;IACpE,MAAMM,QAAQC,GAAG,CACf;WAAIhB,eAAeiB,OAAO;KAAG,CAACC,GAAG,CAAC,OAAO,CAACC,OAAOC,aAAa;QAC5D,IAAIC;QACJ,IAAI;YACF,MAAMC,QAAQ,MAAM1B,iBAAiBuB;YACrCE,SAASC,QAAQ,UAAU;QAC7B,EAAE,OAAOC,KAAK;YACZ,sEAAsE;YACtE,mEAAmE;YACnE,uEAAuE;YACvE1B,MAAM,+CAA+C0B;YACrDF,SAAS;QACX;QAEAxB,MACE,yCACAsB,MAAMK,KAAK,CAAC,CAAC,IACbH,QACAD,aAAaF,GAAG,CAAC,CAACO,IAAMA,EAAEC,IAAI,EAAEC,IAAI,CAAC;QAGvC,iDAAiD;QACjD,KAAK,MAAMzB,UAAUkB,aAAc;YACjClB,OAAO0B,UAAU,GAAGP;QACtB;IACF;AAEJ"}
@@ -1,4 +1,4 @@
1
- import { subdebug } from '@sanity/cli-core';
2
- export const telemetryDebug = subdebug('telemetry');
1
+ import debugIt from 'debug';
2
+ export const telemetryDebug = debugIt('telemetry:sanity:cli');
3
3
 
4
4
  //# sourceMappingURL=telemetryDebug.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/telemetry/telemetryDebug.ts"],"sourcesContent":["import {subdebug} from '@sanity/cli-core'\n\nexport const telemetryDebug = subdebug('telemetry')\n"],"names":["subdebug","telemetryDebug"],"mappings":"AAAA,SAAQA,QAAQ,QAAO,mBAAkB;AAEzC,OAAO,MAAMC,iBAAiBD,SAAS,aAAY"}
1
+ {"version":3,"sources":["../../../src/actions/telemetry/telemetryDebug.ts"],"sourcesContent":["import debugIt from 'debug'\n\nexport const telemetryDebug = debugIt('telemetry:sanity:cli')\n"],"names":["debugIt","telemetryDebug"],"mappings":"AAAA,OAAOA,aAAa,QAAO;AAE3B,OAAO,MAAMC,iBAAiBD,QAAQ,wBAAuB"}
@@ -399,7 +399,9 @@ export class InitCommand extends SanityCommand {
399
399
  workDir
400
400
  });
401
401
  // Set up MCP integration
402
- const mcpResult = await setupMCP(this.flags.mcp);
402
+ const mcpResult = await setupMCP({
403
+ skip: !this.flags.mcp
404
+ });
403
405
  this._trace.log({
404
406
  configuredEditors: mcpResult.configuredEditors,
405
407
  detectedEditors: mcpResult.detectedEditors,
@@ -410,6 +412,12 @@ export class InitCommand extends SanityCommand {
410
412
  this._trace.error(mcpResult.error);
411
413
  }
412
414
  const mcpConfigured = mcpResult.configuredEditors;
415
+ // Show checkmark for editors that were already configured
416
+ const { alreadyConfiguredEditors } = mcpResult;
417
+ if (alreadyConfiguredEditors.length > 0) {
418
+ const label = alreadyConfiguredEditors.length === 1 ? `${alreadyConfiguredEditors[0]} already configured for Sanity MCP` : `${alreadyConfiguredEditors.length} editors already configured for Sanity MCP`;
419
+ spinner(label).start().succeed();
420
+ }
413
421
  if (isNextJs) {
414
422
  await checkNextJsReactCompatibility({
415
423
  detectedFramework,
@@ -751,11 +759,9 @@ export class InitCommand extends SanityCommand {
751
759
  userAction: 'none'
752
760
  };
753
761
  }
754
- const datasetInfo = 'Your content will be stored in a dataset that can be public or private, depending on\nwhether you want to query your content with or without authentication.\nThe default dataset configuration has a public dataset named "production".';
755
762
  if (datasets.length === 0) {
756
763
  debug('No datasets found for project, prompting for name');
757
764
  if (opts.showDefaultConfigPrompt) {
758
- this.log(datasetInfo);
759
765
  defaultConfig = await promptForDefaultConfig();
760
766
  }
761
767
  const name = defaultConfig ? 'production' : await promptForDatasetName({
@@ -793,7 +799,6 @@ export class InitCommand extends SanityCommand {
793
799
  const existingDatasetNames = datasets.map((ds)=>ds.name);
794
800
  debug('User wants to create a new dataset, prompting for name');
795
801
  if (opts.showDefaultConfigPrompt && !existingDatasetNames.includes('production')) {
796
- this.log(datasetInfo);
797
802
  defaultConfig = await promptForDefaultConfig();
798
803
  }
799
804
  const newDatasetName = defaultConfig ? 'production' : await promptForDatasetName({