@sanity/cli 8.19.0 → 8.21.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.
@@ -225,7 +225,9 @@ function apiValidator({ args, flags }) {
225
225
  'users:invite': deny,
226
226
  'users:list': allow,
227
227
  // Reads the local project and installed package tree.
228
- versions: deny
228
+ versions: deny,
229
+ // Reads the authenticated user from the API using the execution context token.
230
+ whoami: allow
229
231
  };
230
232
 
231
233
  //# sourceMappingURL=mcpPolicy.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/exports/invokeSanityCli/commandPolicies/mcpPolicy.ts"],"sourcesContent":["import {\n allow,\n type CommandPolicySet,\n conditionalDenyFlags,\n conditionalPolicy,\n deny,\n} from '@sanity/cli-core/commandPolicy'\n\nimport {isRemoteAssetSource} from '../../../util/isRemoteAssetSource.js'\n\nfunction apiValidator({\n args,\n flags,\n}: {\n args: Readonly<Record<string, unknown>>\n flags: Readonly<Record<string, unknown>>\n}): boolean {\n const fields: unknown[] = Array.isArray(flags.field) ? flags.field : []\n\n const fieldReadsFromHost = fields.some((field) => {\n if (typeof field !== 'string') return false\n const separatorIndex = field.indexOf('=')\n return separatorIndex > 0 && field[separatorIndex + 1] === '@'\n })\n if (fieldReadsFromHost) return false\n\n const headers: unknown[] = Array.isArray(flags.header) ? flags.header : []\n\n const headerOverridesAuthentication = headers.some((header) => {\n if (typeof header !== 'string') return false\n const separatorIndex = header.indexOf(':')\n if (separatorIndex <= 0) return false\n\n const name = header.slice(0, separatorIndex).trim().toLowerCase()\n return name === 'authorization' || name === 'cookie'\n })\n\n if (headerOverridesAuthentication) return false\n\n const endpoint = typeof args.endpoint === 'string' ? URL.parse(args.endpoint) : null\n const urlEmbedsCredentials =\n endpoint !== null && (endpoint.username !== '' || endpoint.password !== '')\n\n return !urlEmbedsCredentials\n}\n\n/**\n * MCP programmatic mode disables local project/config discovery (see the CLI\n * execution context). Missing project or dataset values may therefore produce\n * a usage error, but cannot cause local filesystem access. Destructive remote\n * operations are allowed and do not by themselves make a command unsafe.\n *\n * Every command this package contributes must have exactly one policy here:\n * - allow: every valid invocation is safe\n * - conditional: safety depends on parsed arguments or flags\n * - deny: no invocation is safe\n *\n * Commands contributed by plugins are not listed here. Each plugin declares\n * policies for its own commands (see `PluginInvocationPolicies` in\n * `@sanity/cli-core/commandPolicy`).\n */\nexport const mcpPolicy: CommandPolicySet = {\n // Special exception, this can be very dangerous but is also super useful\n // to expose. Refuse authentication overrides and host input channels:\n // `--token`, Authorization, and Cookie headers replace the MCP user's authentication,\n // URL-embedded credentials replace it with Basic authentication,\n // `--input` reads the request body from the host's filesystem or stdin, and\n // `-F key=@<file>` / `-F key=@-` field values do the same.\n api: conditionalPolicy({\n deniedFlags: ['input', 'token'],\n validate: apiValidator,\n }),\n\n // --file reads from the machine running the command and must only run locally.\n 'assets:upload': conditionalPolicy({\n deniedFlags: ['content-type', 'file'],\n validate: ({flags}) => typeof flags['from-url'] === 'string' && flags['from-url'].length > 0,\n }),\n\n 'backups:disable': allow,\n // Writes a downloaded backup to the local filesystem.\n 'backups:download': deny,\n 'backups:enable': allow,\n 'backups:list': allow,\n\n // Requires a local Studio project and writes build output to disk.\n build: deny,\n\n // Reads and rewrites local source code.\n codemod: deny,\n\n // --watch polls a job with no deadline; a stuck job would hang the MCP tool\n // call indefinitely. Non-watch invocations are single remote requests.\n 'context:build': conditionalDenyFlags('watch'),\n 'context:create': allow,\n 'context:delete': allow,\n 'context:get': allow,\n // --file reads the import payload from the local filesystem; text, URL and\n // dataset imports are remote-only.\n 'context:imports:create': conditionalDenyFlags('file'),\n 'context:imports:delete': allow,\n 'context:imports:download': allow,\n 'context:imports:get': allow,\n 'context:imports:list': allow,\n // See context:build — --watch has no deadline and would hang the MCP tool call.\n 'context:jobs:get': conditionalDenyFlags('watch'),\n 'context:list': allow,\n 'context:refresh': allow,\n 'context:update': allow,\n\n 'cors:add': allow,\n 'cors:delete': allow,\n 'cors:list': allow,\n\n 'datasets:alias:create': allow,\n 'datasets:alias:delete': allow,\n 'datasets:alias:link': allow,\n 'datasets:alias:unlink': allow,\n 'datasets:copy': allow,\n 'datasets:create': allow,\n 'datasets:delete': allow,\n 'datasets:embeddings:disable': allow,\n 'datasets:embeddings:enable': allow,\n 'datasets:embeddings:status': allow,\n // Writes dataset contents and assets to the local filesystem.\n 'datasets:export': deny,\n // Reads import data from the local filesystem and may replace documents.\n 'datasets:import': deny,\n 'datasets:list': allow,\n 'datasets:visibility:get': allow,\n 'datasets:visibility:set': allow,\n\n // Inspects local project files and can print authentication secrets.\n debug: deny,\n\n // Reads, builds, and deploys a local Studio project.\n deploy: deny,\n\n // Loads a local Studio project and starts a development server.\n dev: deny,\n\n // Opens a browser on the machine running the MCP server.\n 'docs:browse': deny,\n // --web opens a browser on the machine running the MCP server.\n 'docs:read': conditionalDenyFlags('web'),\n 'docs:search': allow,\n\n // Reads and executes local project configuration for diagnostics.\n doctor: deny,\n\n // Reads document input from disk or launches a local editor.\n 'documents:create': deny,\n 'documents:delete': allow,\n 'documents:get': allow,\n 'documents:query': allow,\n // Loads a local Studio schema to validate documents.\n 'documents:validate': deny,\n\n // Executes arbitrary code in the local Studio context.\n exec: deny,\n\n // Loads a local schema and deploys a GraphQL API.\n 'graphql:deploy': deny,\n 'graphql:list': allow,\n // --api loads GraphQL definitions from the local project; explicit project/dataset flags are safe.\n 'graphql:undeploy': conditionalDenyFlags('api'),\n\n 'hooks:attempt': allow,\n 'hooks:create': allow,\n 'hooks:delete': allow,\n 'hooks:list': allow,\n 'hooks:logs': allow,\n\n // Creates or modifies a local project and may install dependencies.\n init: deny,\n\n // Installs packages into the local project.\n install: deny,\n\n // Opens a browser on the machine running the MCP server.\n learn: deny,\n\n // Performs an authentication flow.\n login: deny,\n\n // Performs an authentication operation and clears local credentials.\n logout: deny,\n\n // Reads local project configuration and opens a browser.\n manage: deny,\n\n // Loads local Studio configuration and writes manifest files.\n 'manifest:extract': deny,\n\n // Reads and writes local MCP client configuration.\n 'mcp:configure': deny,\n\n // Writes an aspect definition to the local filesystem.\n 'media:create-aspect': deny,\n 'media:delete-aspect': allow,\n // Reads a local aspect definition before deploying it.\n 'media:deploy-aspect': deny,\n // Writes media assets to the local filesystem.\n 'media:export': deny,\n // A directory or archive source reads media assets from the local\n // filesystem. A URL source does not: Sanity fetches the asset itself, so the\n // only local input is the URL. `--replace-aspects` is meaningless for a URL\n // source (the command rejects the combination), so it is hidden here rather\n // than advertised as usable surface.\n 'media:import': conditionalPolicy({\n deniedFlags: ['replace-aspects'],\n validate: ({args}) => typeof args.source === 'string' && isRemoteAssetSource(args.source),\n }),\n\n // Creates migration source files in the local project.\n 'migrations:create': deny,\n // Reads and loads migration definitions from the local project.\n 'migrations:list': deny,\n // Executes local migration code that may perform arbitrary document mutations.\n 'migrations:run': deny,\n\n // Writes project setup values and scaffolds local applications.\n new: deny,\n\n // --web opens a browser on the machine running the MCP server.\n 'openapi:get': conditionalDenyFlags('web'),\n 'openapi:list': conditionalDenyFlags('web'),\n\n 'organizations:create': allow,\n 'organizations:delete': allow,\n 'organizations:get': allow,\n 'organizations:list': allow,\n 'organizations:update': allow,\n\n // Serves a local production build.\n preview: deny,\n\n 'projects:create': allow,\n 'projects:list': allow,\n // Reads locally stored claim URLs and robot tokens.\n 'projects:unclaimed': deny,\n\n // Loads the local Studio configuration to resolve the datasets containing each schema.\n 'schemas:delete': deny,\n // Loads local schema files before deploying them.\n 'schemas:deploy': deny,\n // Loads local Studio configuration and writes an extracted schema to disk.\n 'schemas:extract': deny,\n // Requires a local project and loads its schema configuration.\n 'schemas:list': deny,\n // Loads and executes a local Studio schema.\n 'schemas:validate': deny,\n\n // Installs skills into local editor configuration directories.\n 'skills:install': deny,\n\n // Changes account telemetry preferences and mutates local cached configuration.\n 'telemetry:disable': deny,\n // Changes account telemetry preferences and mutates local cached configuration.\n 'telemetry:enable': deny,\n 'telemetry:status': allow,\n\n // Creates authentication credentials.\n 'tokens:create': deny,\n // Deletes authentication credentials.\n 'tokens:delete': deny,\n // Exposes authentication credential metadata.\n 'tokens:list': deny,\n // Replaces authentication credentials and exposes the new secret.\n 'tokens:rotate': deny,\n\n // Reads the local schema and source files and writes generated types to the project.\n 'typegen:generate': deny,\n\n // Loads local CLI and workbench configuration to identify the deployed Studio or application.\n undeploy: deny,\n\n // Grants a user access to a project.\n 'users:invite': deny,\n 'users:list': allow,\n\n // Reads the local project and installed package tree.\n versions: deny,\n}\n"],"names":["allow","conditionalDenyFlags","conditionalPolicy","deny","isRemoteAssetSource","apiValidator","args","flags","fields","Array","isArray","field","fieldReadsFromHost","some","separatorIndex","indexOf","headers","header","headerOverridesAuthentication","name","slice","trim","toLowerCase","endpoint","URL","parse","urlEmbedsCredentials","username","password","mcpPolicy","api","deniedFlags","validate","length","build","codemod","debug","deploy","dev","doctor","exec","init","install","learn","login","logout","manage","source","new","preview","undeploy","versions"],"mappings":"AAAA,SACEA,KAAK,EAELC,oBAAoB,EACpBC,iBAAiB,EACjBC,IAAI,QACC,iCAAgC;AAEvC,SAAQC,mBAAmB,QAAO,uCAAsC;AAExE,SAASC,aAAa,EACpBC,IAAI,EACJC,KAAK,EAIN;IACC,MAAMC,SAAoBC,MAAMC,OAAO,CAACH,MAAMI,KAAK,IAAIJ,MAAMI,KAAK,GAAG,EAAE;IAEvE,MAAMC,qBAAqBJ,OAAOK,IAAI,CAAC,CAACF;QACtC,IAAI,OAAOA,UAAU,UAAU,OAAO;QACtC,MAAMG,iBAAiBH,MAAMI,OAAO,CAAC;QACrC,OAAOD,iBAAiB,KAAKH,KAAK,CAACG,iBAAiB,EAAE,KAAK;IAC7D;IACA,IAAIF,oBAAoB,OAAO;IAE/B,MAAMI,UAAqBP,MAAMC,OAAO,CAACH,MAAMU,MAAM,IAAIV,MAAMU,MAAM,GAAG,EAAE;IAE1E,MAAMC,gCAAgCF,QAAQH,IAAI,CAAC,CAACI;QAClD,IAAI,OAAOA,WAAW,UAAU,OAAO;QACvC,MAAMH,iBAAiBG,OAAOF,OAAO,CAAC;QACtC,IAAID,kBAAkB,GAAG,OAAO;QAEhC,MAAMK,OAAOF,OAAOG,KAAK,CAAC,GAAGN,gBAAgBO,IAAI,GAAGC,WAAW;QAC/D,OAAOH,SAAS,mBAAmBA,SAAS;IAC9C;IAEA,IAAID,+BAA+B,OAAO;IAE1C,MAAMK,WAAW,OAAOjB,KAAKiB,QAAQ,KAAK,WAAWC,IAAIC,KAAK,CAACnB,KAAKiB,QAAQ,IAAI;IAChF,MAAMG,uBACJH,aAAa,QAASA,CAAAA,SAASI,QAAQ,KAAK,MAAMJ,SAASK,QAAQ,KAAK,EAAC;IAE3E,OAAO,CAACF;AACV;AAEA;;;;;;;;;;;;;;CAcC,GACD,OAAO,MAAMG,YAA8B;IACzC,yEAAyE;IACzE,sEAAsE;IACtE,sFAAsF;IACtF,iEAAiE;IACjE,4EAA4E;IAC5E,2DAA2D;IAC3DC,KAAK5B,kBAAkB;QACrB6B,aAAa;YAAC;YAAS;SAAQ;QAC/BC,UAAU3B;IACZ;IAEA,+EAA+E;IAC/E,iBAAiBH,kBAAkB;QACjC6B,aAAa;YAAC;YAAgB;SAAO;QACrCC,UAAU,CAAC,EAACzB,KAAK,EAAC,GAAK,OAAOA,KAAK,CAAC,WAAW,KAAK,YAAYA,KAAK,CAAC,WAAW,CAAC0B,MAAM,GAAG;IAC7F;IAEA,mBAAmBjC;IACnB,sDAAsD;IACtD,oBAAoBG;IACpB,kBAAkBH;IAClB,gBAAgBA;IAEhB,mEAAmE;IACnEkC,OAAO/B;IAEP,wCAAwC;IACxCgC,SAAShC;IAET,4EAA4E;IAC5E,uEAAuE;IACvE,iBAAiBF,qBAAqB;IACtC,kBAAkBD;IAClB,kBAAkBA;IAClB,eAAeA;IACf,2EAA2E;IAC3E,mCAAmC;IACnC,0BAA0BC,qBAAqB;IAC/C,0BAA0BD;IAC1B,4BAA4BA;IAC5B,uBAAuBA;IACvB,wBAAwBA;IACxB,gFAAgF;IAChF,oBAAoBC,qBAAqB;IACzC,gBAAgBD;IAChB,mBAAmBA;IACnB,kBAAkBA;IAElB,YAAYA;IACZ,eAAeA;IACf,aAAaA;IAEb,yBAAyBA;IACzB,yBAAyBA;IACzB,uBAAuBA;IACvB,yBAAyBA;IACzB,iBAAiBA;IACjB,mBAAmBA;IACnB,mBAAmBA;IACnB,+BAA+BA;IAC/B,8BAA8BA;IAC9B,8BAA8BA;IAC9B,8DAA8D;IAC9D,mBAAmBG;IACnB,yEAAyE;IACzE,mBAAmBA;IACnB,iBAAiBH;IACjB,2BAA2BA;IAC3B,2BAA2BA;IAE3B,qEAAqE;IACrEoC,OAAOjC;IAEP,qDAAqD;IACrDkC,QAAQlC;IAER,gEAAgE;IAChEmC,KAAKnC;IAEL,yDAAyD;IACzD,eAAeA;IACf,+DAA+D;IAC/D,aAAaF,qBAAqB;IAClC,eAAeD;IAEf,kEAAkE;IAClEuC,QAAQpC;IAER,6DAA6D;IAC7D,oBAAoBA;IACpB,oBAAoBH;IACpB,iBAAiBA;IACjB,mBAAmBA;IACnB,qDAAqD;IACrD,sBAAsBG;IAEtB,uDAAuD;IACvDqC,MAAMrC;IAEN,kDAAkD;IAClD,kBAAkBA;IAClB,gBAAgBH;IAChB,mGAAmG;IACnG,oBAAoBC,qBAAqB;IAEzC,iBAAiBD;IACjB,gBAAgBA;IAChB,gBAAgBA;IAChB,cAAcA;IACd,cAAcA;IAEd,oEAAoE;IACpEyC,MAAMtC;IAEN,4CAA4C;IAC5CuC,SAASvC;IAET,yDAAyD;IACzDwC,OAAOxC;IAEP,mCAAmC;IACnCyC,OAAOzC;IAEP,qEAAqE;IACrE0C,QAAQ1C;IAER,yDAAyD;IACzD2C,QAAQ3C;IAER,8DAA8D;IAC9D,oBAAoBA;IAEpB,mDAAmD;IACnD,iBAAiBA;IAEjB,uDAAuD;IACvD,uBAAuBA;IACvB,uBAAuBH;IACvB,uDAAuD;IACvD,uBAAuBG;IACvB,+CAA+C;IAC/C,gBAAgBA;IAChB,kEAAkE;IAClE,6EAA6E;IAC7E,4EAA4E;IAC5E,4EAA4E;IAC5E,qCAAqC;IACrC,gBAAgBD,kBAAkB;QAChC6B,aAAa;YAAC;SAAkB;QAChCC,UAAU,CAAC,EAAC1B,IAAI,EAAC,GAAK,OAAOA,KAAKyC,MAAM,KAAK,YAAY3C,oBAAoBE,KAAKyC,MAAM;IAC1F;IAEA,uDAAuD;IACvD,qBAAqB5C;IACrB,gEAAgE;IAChE,mBAAmBA;IACnB,+EAA+E;IAC/E,kBAAkBA;IAElB,gEAAgE;IAChE6C,KAAK7C;IAEL,+DAA+D;IAC/D,eAAeF,qBAAqB;IACpC,gBAAgBA,qBAAqB;IAErC,wBAAwBD;IACxB,wBAAwBA;IACxB,qBAAqBA;IACrB,sBAAsBA;IACtB,wBAAwBA;IAExB,mCAAmC;IACnCiD,SAAS9C;IAET,mBAAmBH;IACnB,iBAAiBA;IACjB,oDAAoD;IACpD,sBAAsBG;IAEtB,uFAAuF;IACvF,kBAAkBA;IAClB,kDAAkD;IAClD,kBAAkBA;IAClB,2EAA2E;IAC3E,mBAAmBA;IACnB,+DAA+D;IAC/D,gBAAgBA;IAChB,4CAA4C;IAC5C,oBAAoBA;IAEpB,+DAA+D;IAC/D,kBAAkBA;IAElB,gFAAgF;IAChF,qBAAqBA;IACrB,gFAAgF;IAChF,oBAAoBA;IACpB,oBAAoBH;IAEpB,sCAAsC;IACtC,iBAAiBG;IACjB,sCAAsC;IACtC,iBAAiBA;IACjB,8CAA8C;IAC9C,eAAeA;IACf,kEAAkE;IAClE,iBAAiBA;IAEjB,qFAAqF;IACrF,oBAAoBA;IAEpB,8FAA8F;IAC9F+C,UAAU/C;IAEV,qCAAqC;IACrC,gBAAgBA;IAChB,cAAcH;IAEd,sDAAsD;IACtDmD,UAAUhD;AACZ,EAAC"}
1
+ {"version":3,"sources":["../../../../src/exports/invokeSanityCli/commandPolicies/mcpPolicy.ts"],"sourcesContent":["import {\n allow,\n type CommandPolicySet,\n conditionalDenyFlags,\n conditionalPolicy,\n deny,\n} from '@sanity/cli-core/commandPolicy'\n\nimport {isRemoteAssetSource} from '../../../util/isRemoteAssetSource.js'\n\nfunction apiValidator({\n args,\n flags,\n}: {\n args: Readonly<Record<string, unknown>>\n flags: Readonly<Record<string, unknown>>\n}): boolean {\n const fields: unknown[] = Array.isArray(flags.field) ? flags.field : []\n\n const fieldReadsFromHost = fields.some((field) => {\n if (typeof field !== 'string') return false\n const separatorIndex = field.indexOf('=')\n return separatorIndex > 0 && field[separatorIndex + 1] === '@'\n })\n if (fieldReadsFromHost) return false\n\n const headers: unknown[] = Array.isArray(flags.header) ? flags.header : []\n\n const headerOverridesAuthentication = headers.some((header) => {\n if (typeof header !== 'string') return false\n const separatorIndex = header.indexOf(':')\n if (separatorIndex <= 0) return false\n\n const name = header.slice(0, separatorIndex).trim().toLowerCase()\n return name === 'authorization' || name === 'cookie'\n })\n\n if (headerOverridesAuthentication) return false\n\n const endpoint = typeof args.endpoint === 'string' ? URL.parse(args.endpoint) : null\n const urlEmbedsCredentials =\n endpoint !== null && (endpoint.username !== '' || endpoint.password !== '')\n\n return !urlEmbedsCredentials\n}\n\n/**\n * MCP programmatic mode disables local project/config discovery (see the CLI\n * execution context). Missing project or dataset values may therefore produce\n * a usage error, but cannot cause local filesystem access. Destructive remote\n * operations are allowed and do not by themselves make a command unsafe.\n *\n * Every command this package contributes must have exactly one policy here:\n * - allow: every valid invocation is safe\n * - conditional: safety depends on parsed arguments or flags\n * - deny: no invocation is safe\n *\n * Commands contributed by plugins are not listed here. Each plugin declares\n * policies for its own commands (see `PluginInvocationPolicies` in\n * `@sanity/cli-core/commandPolicy`).\n */\nexport const mcpPolicy: CommandPolicySet = {\n // Special exception, this can be very dangerous but is also super useful\n // to expose. Refuse authentication overrides and host input channels:\n // `--token`, Authorization, and Cookie headers replace the MCP user's authentication,\n // URL-embedded credentials replace it with Basic authentication,\n // `--input` reads the request body from the host's filesystem or stdin, and\n // `-F key=@<file>` / `-F key=@-` field values do the same.\n api: conditionalPolicy({\n deniedFlags: ['input', 'token'],\n validate: apiValidator,\n }),\n\n // --file reads from the machine running the command and must only run locally.\n 'assets:upload': conditionalPolicy({\n deniedFlags: ['content-type', 'file'],\n validate: ({flags}) => typeof flags['from-url'] === 'string' && flags['from-url'].length > 0,\n }),\n\n 'backups:disable': allow,\n // Writes a downloaded backup to the local filesystem.\n 'backups:download': deny,\n 'backups:enable': allow,\n 'backups:list': allow,\n\n // Requires a local Studio project and writes build output to disk.\n build: deny,\n\n // Reads and rewrites local source code.\n codemod: deny,\n\n // --watch polls a job with no deadline; a stuck job would hang the MCP tool\n // call indefinitely. Non-watch invocations are single remote requests.\n 'context:build': conditionalDenyFlags('watch'),\n 'context:create': allow,\n 'context:delete': allow,\n 'context:get': allow,\n // --file reads the import payload from the local filesystem; text, URL and\n // dataset imports are remote-only.\n 'context:imports:create': conditionalDenyFlags('file'),\n 'context:imports:delete': allow,\n 'context:imports:download': allow,\n 'context:imports:get': allow,\n 'context:imports:list': allow,\n // See context:build — --watch has no deadline and would hang the MCP tool call.\n 'context:jobs:get': conditionalDenyFlags('watch'),\n 'context:list': allow,\n 'context:refresh': allow,\n 'context:update': allow,\n\n 'cors:add': allow,\n 'cors:delete': allow,\n 'cors:list': allow,\n\n 'datasets:alias:create': allow,\n 'datasets:alias:delete': allow,\n 'datasets:alias:link': allow,\n 'datasets:alias:unlink': allow,\n 'datasets:copy': allow,\n 'datasets:create': allow,\n 'datasets:delete': allow,\n 'datasets:embeddings:disable': allow,\n 'datasets:embeddings:enable': allow,\n 'datasets:embeddings:status': allow,\n // Writes dataset contents and assets to the local filesystem.\n 'datasets:export': deny,\n // Reads import data from the local filesystem and may replace documents.\n 'datasets:import': deny,\n 'datasets:list': allow,\n 'datasets:visibility:get': allow,\n 'datasets:visibility:set': allow,\n\n // Inspects local project files and can print authentication secrets.\n debug: deny,\n\n // Reads, builds, and deploys a local Studio project.\n deploy: deny,\n\n // Loads a local Studio project and starts a development server.\n dev: deny,\n\n // Opens a browser on the machine running the MCP server.\n 'docs:browse': deny,\n // --web opens a browser on the machine running the MCP server.\n 'docs:read': conditionalDenyFlags('web'),\n 'docs:search': allow,\n\n // Reads and executes local project configuration for diagnostics.\n doctor: deny,\n\n // Reads document input from disk or launches a local editor.\n 'documents:create': deny,\n 'documents:delete': allow,\n 'documents:get': allow,\n 'documents:query': allow,\n // Loads a local Studio schema to validate documents.\n 'documents:validate': deny,\n\n // Executes arbitrary code in the local Studio context.\n exec: deny,\n\n // Loads a local schema and deploys a GraphQL API.\n 'graphql:deploy': deny,\n 'graphql:list': allow,\n // --api loads GraphQL definitions from the local project; explicit project/dataset flags are safe.\n 'graphql:undeploy': conditionalDenyFlags('api'),\n\n 'hooks:attempt': allow,\n 'hooks:create': allow,\n 'hooks:delete': allow,\n 'hooks:list': allow,\n 'hooks:logs': allow,\n\n // Creates or modifies a local project and may install dependencies.\n init: deny,\n\n // Installs packages into the local project.\n install: deny,\n\n // Opens a browser on the machine running the MCP server.\n learn: deny,\n\n // Performs an authentication flow.\n login: deny,\n\n // Performs an authentication operation and clears local credentials.\n logout: deny,\n\n // Reads local project configuration and opens a browser.\n manage: deny,\n\n // Loads local Studio configuration and writes manifest files.\n 'manifest:extract': deny,\n\n // Reads and writes local MCP client configuration.\n 'mcp:configure': deny,\n\n // Writes an aspect definition to the local filesystem.\n 'media:create-aspect': deny,\n 'media:delete-aspect': allow,\n // Reads a local aspect definition before deploying it.\n 'media:deploy-aspect': deny,\n // Writes media assets to the local filesystem.\n 'media:export': deny,\n // A directory or archive source reads media assets from the local\n // filesystem. A URL source does not: Sanity fetches the asset itself, so the\n // only local input is the URL. `--replace-aspects` is meaningless for a URL\n // source (the command rejects the combination), so it is hidden here rather\n // than advertised as usable surface.\n 'media:import': conditionalPolicy({\n deniedFlags: ['replace-aspects'],\n validate: ({args}) => typeof args.source === 'string' && isRemoteAssetSource(args.source),\n }),\n\n // Creates migration source files in the local project.\n 'migrations:create': deny,\n // Reads and loads migration definitions from the local project.\n 'migrations:list': deny,\n // Executes local migration code that may perform arbitrary document mutations.\n 'migrations:run': deny,\n\n // Writes project setup values and scaffolds local applications.\n new: deny,\n\n // --web opens a browser on the machine running the MCP server.\n 'openapi:get': conditionalDenyFlags('web'),\n 'openapi:list': conditionalDenyFlags('web'),\n\n 'organizations:create': allow,\n 'organizations:delete': allow,\n 'organizations:get': allow,\n 'organizations:list': allow,\n 'organizations:update': allow,\n\n // Serves a local production build.\n preview: deny,\n\n 'projects:create': allow,\n 'projects:list': allow,\n // Reads locally stored claim URLs and robot tokens.\n 'projects:unclaimed': deny,\n\n // Loads the local Studio configuration to resolve the datasets containing each schema.\n 'schemas:delete': deny,\n // Loads local schema files before deploying them.\n 'schemas:deploy': deny,\n // Loads local Studio configuration and writes an extracted schema to disk.\n 'schemas:extract': deny,\n // Requires a local project and loads its schema configuration.\n 'schemas:list': deny,\n // Loads and executes a local Studio schema.\n 'schemas:validate': deny,\n\n // Installs skills into local editor configuration directories.\n 'skills:install': deny,\n\n // Changes account telemetry preferences and mutates local cached configuration.\n 'telemetry:disable': deny,\n // Changes account telemetry preferences and mutates local cached configuration.\n 'telemetry:enable': deny,\n 'telemetry:status': allow,\n\n // Creates authentication credentials.\n 'tokens:create': deny,\n // Deletes authentication credentials.\n 'tokens:delete': deny,\n // Exposes authentication credential metadata.\n 'tokens:list': deny,\n // Replaces authentication credentials and exposes the new secret.\n 'tokens:rotate': deny,\n\n // Reads the local schema and source files and writes generated types to the project.\n 'typegen:generate': deny,\n\n // Loads local CLI and workbench configuration to identify the deployed Studio or application.\n undeploy: deny,\n\n // Grants a user access to a project.\n 'users:invite': deny,\n 'users:list': allow,\n\n // Reads the local project and installed package tree.\n versions: deny,\n\n // Reads the authenticated user from the API using the execution context token.\n whoami: allow,\n}\n"],"names":["allow","conditionalDenyFlags","conditionalPolicy","deny","isRemoteAssetSource","apiValidator","args","flags","fields","Array","isArray","field","fieldReadsFromHost","some","separatorIndex","indexOf","headers","header","headerOverridesAuthentication","name","slice","trim","toLowerCase","endpoint","URL","parse","urlEmbedsCredentials","username","password","mcpPolicy","api","deniedFlags","validate","length","build","codemod","debug","deploy","dev","doctor","exec","init","install","learn","login","logout","manage","source","new","preview","undeploy","versions","whoami"],"mappings":"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"}
@@ -48,5 +48,19 @@ export async function getTokenDetails(queryString) {
48
48
  url: `/auth/fetch${queryString}`
49
49
  });
50
50
  }
51
+ /**
52
+ * List the SSO providers configured for an organization. Members without
53
+ * organization read access only see enabled providers.
54
+ *
55
+ * @param organizationId - The ID of the organization
56
+ */ export async function getOrganizationSSOProviders(organizationId) {
57
+ const client = await getGlobalCliClient({
58
+ apiVersion: AUTH_API_VERSION,
59
+ requireUser: true
60
+ });
61
+ return client.request({
62
+ url: `/auth/organizations/${organizationId}/providers`
63
+ });
64
+ }
51
65
 
52
66
  //# sourceMappingURL=auth.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/services/auth.ts"],"sourcesContent":["import {getGlobalCliClient} from '@sanity/cli-core'\n\nimport {\n type ProvidersResponse,\n type SamlLoginProvider,\n type TokenDetails,\n} from '../actions/auth/types.js'\n\nexport const AUTH_API_VERSION = 'v2025-09-23'\n\nasync function getUnauthenticatedClient() {\n return getGlobalCliClient({apiVersion: AUTH_API_VERSION, unauthenticated: true})\n}\n\n/**\n * Invalidate the current user session\n *\n * @param token - Optional token to invalidate.\n */\nexport async function logout(token?: string) {\n let client = await getGlobalCliClient({apiVersion: AUTH_API_VERSION})\n\n if (token) {\n client = client.withConfig({token})\n }\n\n return client.request({method: 'POST', url: '/auth/logout'})\n}\n\nexport async function getProviders() {\n const client = await getUnauthenticatedClient()\n\n return client.request<ProvidersResponse>({url: '/auth/providers'})\n}\n\nexport async function getVercelProviderUrl() {\n const client = (await getUnauthenticatedClient()).withConfig({apiVersion: 'v1'})\n return client.getUrl('/auth/login/vercel')\n}\n\nexport async function getSSOProviders(orgSlug: string) {\n const client = await getUnauthenticatedClient()\n\n return client.request<SamlLoginProvider[]>({\n url: `/auth/organizations/by-slug/${orgSlug}/providers`,\n })\n}\n\nexport async function getTokenDetails(queryString: string) {\n const client = await getUnauthenticatedClient()\n\n return client.request<TokenDetails>({url: `/auth/fetch${queryString}`})\n}\n"],"names":["getGlobalCliClient","AUTH_API_VERSION","getUnauthenticatedClient","apiVersion","unauthenticated","logout","token","client","withConfig","request","method","url","getProviders","getVercelProviderUrl","getUrl","getSSOProviders","orgSlug","getTokenDetails","queryString"],"mappings":"AAAA,SAAQA,kBAAkB,QAAO,mBAAkB;AAQnD,OAAO,MAAMC,mBAAmB,cAAa;AAE7C,eAAeC;IACb,OAAOF,mBAAmB;QAACG,YAAYF;QAAkBG,iBAAiB;IAAI;AAChF;AAEA;;;;CAIC,GACD,OAAO,eAAeC,OAAOC,KAAc;IACzC,IAAIC,SAAS,MAAMP,mBAAmB;QAACG,YAAYF;IAAgB;IAEnE,IAAIK,OAAO;QACTC,SAASA,OAAOC,UAAU,CAAC;YAACF;QAAK;IACnC;IAEA,OAAOC,OAAOE,OAAO,CAAC;QAACC,QAAQ;QAAQC,KAAK;IAAc;AAC5D;AAEA,OAAO,eAAeC;IACpB,MAAML,SAAS,MAAML;IAErB,OAAOK,OAAOE,OAAO,CAAoB;QAACE,KAAK;IAAiB;AAClE;AAEA,OAAO,eAAeE;IACpB,MAAMN,SAAS,AAAC,CAAA,MAAML,0BAAyB,EAAGM,UAAU,CAAC;QAACL,YAAY;IAAI;IAC9E,OAAOI,OAAOO,MAAM,CAAC;AACvB;AAEA,OAAO,eAAeC,gBAAgBC,OAAe;IACnD,MAAMT,SAAS,MAAML;IAErB,OAAOK,OAAOE,OAAO,CAAsB;QACzCE,KAAK,CAAC,4BAA4B,EAAEK,QAAQ,UAAU,CAAC;IACzD;AACF;AAEA,OAAO,eAAeC,gBAAgBC,WAAmB;IACvD,MAAMX,SAAS,MAAML;IAErB,OAAOK,OAAOE,OAAO,CAAe;QAACE,KAAK,CAAC,WAAW,EAAEO,aAAa;IAAA;AACvE"}
1
+ {"version":3,"sources":["../../src/services/auth.ts"],"sourcesContent":["import {getGlobalCliClient} from '@sanity/cli-core'\n\nimport {\n type ProvidersResponse,\n type SamlLoginProvider,\n type TokenDetails,\n} from '../actions/auth/types.js'\n\nexport const AUTH_API_VERSION = 'v2025-09-23'\n\nasync function getUnauthenticatedClient() {\n return getGlobalCliClient({apiVersion: AUTH_API_VERSION, unauthenticated: true})\n}\n\n/**\n * Invalidate the current user session\n *\n * @param token - Optional token to invalidate.\n */\nexport async function logout(token?: string) {\n let client = await getGlobalCliClient({apiVersion: AUTH_API_VERSION})\n\n if (token) {\n client = client.withConfig({token})\n }\n\n return client.request({method: 'POST', url: '/auth/logout'})\n}\n\nexport async function getProviders() {\n const client = await getUnauthenticatedClient()\n\n return client.request<ProvidersResponse>({url: '/auth/providers'})\n}\n\nexport async function getVercelProviderUrl() {\n const client = (await getUnauthenticatedClient()).withConfig({apiVersion: 'v1'})\n return client.getUrl('/auth/login/vercel')\n}\n\nexport async function getSSOProviders(orgSlug: string) {\n const client = await getUnauthenticatedClient()\n\n return client.request<SamlLoginProvider[]>({\n url: `/auth/organizations/by-slug/${orgSlug}/providers`,\n })\n}\n\nexport async function getTokenDetails(queryString: string) {\n const client = await getUnauthenticatedClient()\n\n return client.request<TokenDetails>({url: `/auth/fetch${queryString}`})\n}\n\n/**\n * List the SSO providers configured for an organization. Members without\n * organization read access only see enabled providers.\n *\n * @param organizationId - The ID of the organization\n */\nexport async function getOrganizationSSOProviders(organizationId: string) {\n const client = await getGlobalCliClient({apiVersion: AUTH_API_VERSION, requireUser: true})\n\n return client.request<SamlLoginProvider[]>({\n url: `/auth/organizations/${organizationId}/providers`,\n })\n}\n"],"names":["getGlobalCliClient","AUTH_API_VERSION","getUnauthenticatedClient","apiVersion","unauthenticated","logout","token","client","withConfig","request","method","url","getProviders","getVercelProviderUrl","getUrl","getSSOProviders","orgSlug","getTokenDetails","queryString","getOrganizationSSOProviders","organizationId","requireUser"],"mappings":"AAAA,SAAQA,kBAAkB,QAAO,mBAAkB;AAQnD,OAAO,MAAMC,mBAAmB,cAAa;AAE7C,eAAeC;IACb,OAAOF,mBAAmB;QAACG,YAAYF;QAAkBG,iBAAiB;IAAI;AAChF;AAEA;;;;CAIC,GACD,OAAO,eAAeC,OAAOC,KAAc;IACzC,IAAIC,SAAS,MAAMP,mBAAmB;QAACG,YAAYF;IAAgB;IAEnE,IAAIK,OAAO;QACTC,SAASA,OAAOC,UAAU,CAAC;YAACF;QAAK;IACnC;IAEA,OAAOC,OAAOE,OAAO,CAAC;QAACC,QAAQ;QAAQC,KAAK;IAAc;AAC5D;AAEA,OAAO,eAAeC;IACpB,MAAML,SAAS,MAAML;IAErB,OAAOK,OAAOE,OAAO,CAAoB;QAACE,KAAK;IAAiB;AAClE;AAEA,OAAO,eAAeE;IACpB,MAAMN,SAAS,AAAC,CAAA,MAAML,0BAAyB,EAAGM,UAAU,CAAC;QAACL,YAAY;IAAI;IAC9E,OAAOI,OAAOO,MAAM,CAAC;AACvB;AAEA,OAAO,eAAeC,gBAAgBC,OAAe;IACnD,MAAMT,SAAS,MAAML;IAErB,OAAOK,OAAOE,OAAO,CAAsB;QACzCE,KAAK,CAAC,4BAA4B,EAAEK,QAAQ,UAAU,CAAC;IACzD;AACF;AAEA,OAAO,eAAeC,gBAAgBC,WAAmB;IACvD,MAAMX,SAAS,MAAML;IAErB,OAAOK,OAAOE,OAAO,CAAe;QAACE,KAAK,CAAC,WAAW,EAAEO,aAAa;IAAA;AACvE;AAEA;;;;;CAKC,GACD,OAAO,eAAeC,4BAA4BC,cAAsB;IACtE,MAAMb,SAAS,MAAMP,mBAAmB;QAACG,YAAYF;QAAkBoB,aAAa;IAAI;IAExF,OAAOd,OAAOE,OAAO,CAAsB;QACzCE,KAAK,CAAC,oBAAoB,EAAES,eAAe,UAAU,CAAC;IACxD;AACF"}
@@ -9,13 +9,16 @@ function getClient(token) {
9
9
  }
10
10
  });
11
11
  }
12
- async function fetchAllPages(client, url) {
12
+ async function fetchAllPages(client, url, query = {}) {
13
13
  const items = [];
14
14
  let cursor = null;
15
15
  do {
16
16
  const response = await client.request({
17
- query: cursor === null ? {} : {
18
- nextCursor: cursor
17
+ query: {
18
+ ...query,
19
+ ...cursor === null ? {} : {
20
+ nextCursor: cursor
21
+ }
19
22
  },
20
23
  url
21
24
  });
@@ -80,19 +83,26 @@ async function postRobot(url, { expiresAt, ...body }, query = {}) {
80
83
  const { organizationId, ...body } = options;
81
84
  return postRobot(`/access/organization/${organizationId}/robots`, body);
82
85
  }
83
- /**
84
- * Delete a token from a project
85
- * @param options - The options for deleting a token from a project; `tokenId`
86
- * is the id reported by `getTokens`/`createToken`
87
- * @returns A promise that resolves when the token is deleted
88
- *
89
- * @internal
90
- */ export async function deleteToken(options) {
91
- const { projectId, tokenId } = options;
86
+ function robotsUrl({ resourceId, resourceType }) {
87
+ return `/access/${resourceType}/${resourceId}/robots`;
88
+ }
89
+ function isManagedByOrganization(robot, organizationId) {
90
+ return robot.managedBy?.resourceType === 'organization' && robot.managedBy.resourceId === organizationId;
91
+ }
92
+ /** Delete a token from the requested scope. @internal */ export async function deleteToken(scope, tokenId) {
92
93
  const client = await getClient();
94
+ const url = `${robotsUrl(scope)}/${tokenId}`;
95
+ if (scope.resourceType === 'organization') {
96
+ const robot = await client.request({
97
+ url
98
+ });
99
+ if (!isManagedByOrganization(robot, scope.resourceId)) {
100
+ throw new Error(`Token "${tokenId}" is not managed by organization "${scope.resourceId}". Use tokens list --organization ${scope.resourceId} to find tokens managed by this organization.`);
101
+ }
102
+ }
93
103
  return client.request({
94
104
  method: 'DELETE',
95
- url: `/access/project/${projectId}/robots/${tokenId}`
105
+ url
96
106
  });
97
107
  }
98
108
  /**
@@ -110,16 +120,16 @@ async function postRobot(url, { expiresAt, ...body }, query = {}) {
110
120
  url: '/access/robots/me/rotate'
111
121
  });
112
122
  }
113
- /**
114
- * Get all tokens for a project. Tokens managed by an organization are
115
- * excluded: they cannot be managed at the project scope.
116
- * @param projectId - The project ID
117
- * @returns A promise that resolves to an array of robots
118
- *
119
- * @internal
120
- */ export async function getTokens(projectId) {
123
+ /** List tokens managed at the requested scope. @internal */ export async function getTokens(scope) {
121
124
  const client = await getClient();
122
- const robots = await fetchAllPages(client, `/access/project/${projectId}/robots`);
125
+ const query = scope.resourceType === 'organization' ? {
126
+ includeChildren: 'true',
127
+ limit: '500'
128
+ } : {};
129
+ const robots = await fetchAllPages(client, robotsUrl(scope), query);
130
+ if (scope.resourceType === 'organization') {
131
+ return robots.filter((robot)=>isManagedByOrganization(robot, scope.resourceId));
132
+ }
123
133
  return robots.filter((robot)=>robot.managedBy?.resourceType !== 'organization');
124
134
  }
125
135
  /**
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/services/tokens.ts"],"sourcesContent":["import {getGlobalCliClient} from '@sanity/cli-core'\nimport {type SanityClient} from '@sanity/client'\n\nimport {TOKENS_API_VERSION} from '../actions/tokens/constants.js'\nimport {\n type Membership,\n type Robot,\n type RobotWithToken,\n type Role,\n} from '../actions/tokens/types.js'\n\ninterface PaginatedResponse<T> {\n data: T[]\n nextCursor: string | null\n}\n\nfunction getClient(token?: string): Promise<SanityClient> {\n return getGlobalCliClient({\n apiVersion: TOKENS_API_VERSION,\n requireUser: true,\n ...(token === undefined ? {} : {token}),\n })\n}\n\nasync function fetchAllPages<T>(client: SanityClient, url: string): Promise<T[]> {\n const items: T[] = []\n let cursor: string | null = null\n\n do {\n const response: PaginatedResponse<T> = await client.request<PaginatedResponse<T>>({\n query: cursor === null ? {} : {nextCursor: cursor},\n url,\n })\n items.push(...response.data)\n cursor = response.nextCursor\n } while (cursor !== null)\n\n return items\n}\n\n/**\n * Get the membership granting a robot access to the given project\n * @param robot - The robot to inspect\n * @param projectId - The project ID\n * @returns The project membership, if any\n *\n * @internal\n */\nexport function getProjectMembership(robot: Robot, projectId: string): Membership | undefined {\n return robot.memberships.find(\n (membership) => membership.resourceType === 'project' && membership.resourceId === projectId,\n )\n}\n\ninterface RobotBody {\n label: string\n memberships: Membership[]\n\n expiresAt?: string\n}\n\nasync function postRobot(\n url: string,\n {expiresAt, ...body}: RobotBody,\n query: Record<string, string> = {},\n): Promise<RobotWithToken> {\n const client = await getClient()\n\n return client.request<RobotWithToken>({\n body: {...body, ...(expiresAt === undefined ? {} : {expiresAt})},\n method: 'POST',\n query,\n url,\n })\n}\n\ninterface CreateTokenOptions {\n label: string\n projectId: string\n roleNames: string[]\n\n expiresAt?: string\n sendNotification?: boolean\n}\n\n/**\n * Add a token to a project\n * @param options - The options for adding a token to a project\n * @returns A promise that resolves to the created robot, including its secret token\n *\n * @internal\n */\nexport async function createToken(options: CreateTokenOptions): Promise<RobotWithToken> {\n const {expiresAt, label, projectId, roleNames, sendNotification} = options\n\n return postRobot(\n `/access/project/${projectId}/robots`,\n {\n expiresAt,\n label,\n memberships: [{resourceId: projectId, resourceType: 'project', roleNames}],\n },\n sendNotification === false ? {sendNotification: 'false'} : {},\n )\n}\n\ninterface CreateOrganizationTokenOptions extends RobotBody {\n organizationId: string\n}\n\n/**\n * Create an organization token, which can hold organization permissions and roles on several projects\n * @param options - The options for the organization token\n * @returns A promise that resolves to the created robot, including its secret token\n *\n * @internal\n */\nexport async function createOrganizationToken(\n options: CreateOrganizationTokenOptions,\n): Promise<RobotWithToken> {\n const {organizationId, ...body} = options\n\n return postRobot(`/access/organization/${organizationId}/robots`, body)\n}\n\ninterface DeleteTokenOptions {\n projectId: string\n tokenId: string\n}\n\n/**\n * Delete a token from a project\n * @param options - The options for deleting a token from a project; `tokenId`\n * is the id reported by `getTokens`/`createToken`\n * @returns A promise that resolves when the token is deleted\n *\n * @internal\n */\nexport async function deleteToken(options: DeleteTokenOptions): Promise<void> {\n const {projectId, tokenId} = options\n\n const client = await getClient()\n\n return client.request({\n method: 'DELETE',\n url: `/access/project/${projectId}/robots/${tokenId}`,\n })\n}\n\n/**\n * Rotate a robot token, replacing its secret while preserving the robot,\n * its roles, and the token's expiry. The request authenticates as the robot\n * itself; the previous secret is revoked immediately.\n * @param token - The current robot token secret\n * @returns A promise that resolves to the robot with its new secret token\n *\n * @internal\n */\nexport async function rotateToken(token: string): Promise<RobotWithToken> {\n const client = await getClient(token)\n\n return client.request<RobotWithToken>({\n method: 'POST',\n url: '/access/robots/me/rotate',\n })\n}\n\n/**\n * Get all tokens for a project. Tokens managed by an organization are\n * excluded: they cannot be managed at the project scope.\n * @param projectId - The project ID\n * @returns A promise that resolves to an array of robots\n *\n * @internal\n */\nexport async function getTokens(projectId: string): Promise<Robot[]> {\n const client = await getClient()\n\n const robots = await fetchAllPages<Robot>(client, `/access/project/${projectId}/robots`)\n\n return robots.filter((robot) => robot.managedBy?.resourceType !== 'organization')\n}\n\n/**\n * Get all roles for a project\n * @param projectId - The project ID\n * @returns A promise that resolves to an array of project roles\n *\n * @internal\n */\nexport async function getTokenRoles(projectId: string): Promise<Role[]> {\n const client = await getClient()\n\n return fetchAllPages<Role>(client, `/access/project/${projectId}/roles`)\n}\n"],"names":["getGlobalCliClient","TOKENS_API_VERSION","getClient","token","apiVersion","requireUser","undefined","fetchAllPages","client","url","items","cursor","response","request","query","nextCursor","push","data","getProjectMembership","robot","projectId","memberships","find","membership","resourceType","resourceId","postRobot","expiresAt","body","method","createToken","options","label","roleNames","sendNotification","createOrganizationToken","organizationId","deleteToken","tokenId","rotateToken","getTokens","robots","filter","managedBy","getTokenRoles"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/services/tokens.ts"],"sourcesContent":["import {getGlobalCliClient} from '@sanity/cli-core'\nimport {type SanityClient} from '@sanity/client'\n\nimport {TOKENS_API_VERSION} from '../actions/tokens/constants.js'\nimport {\n type Membership,\n type Robot,\n type RobotWithToken,\n type Role,\n type TokenScope,\n} from '../actions/tokens/types.js'\n\ninterface PaginatedResponse<T> {\n data: T[]\n nextCursor: string | null\n}\n\nfunction getClient(token?: string): Promise<SanityClient> {\n return getGlobalCliClient({\n apiVersion: TOKENS_API_VERSION,\n requireUser: true,\n ...(token === undefined ? {} : {token}),\n })\n}\n\nasync function fetchAllPages<T>(\n client: SanityClient,\n url: string,\n query: Record<string, string> = {},\n): Promise<T[]> {\n const items: T[] = []\n let cursor: string | null = null\n\n do {\n const response: PaginatedResponse<T> = await client.request<PaginatedResponse<T>>({\n query: {...query, ...(cursor === null ? {} : {nextCursor: cursor})},\n url,\n })\n items.push(...response.data)\n cursor = response.nextCursor\n } while (cursor !== null)\n\n return items\n}\n\n/**\n * Get the membership granting a robot access to the given project\n * @param robot - The robot to inspect\n * @param projectId - The project ID\n * @returns The project membership, if any\n *\n * @internal\n */\nexport function getProjectMembership(robot: Robot, projectId: string): Membership | undefined {\n return robot.memberships.find(\n (membership) => membership.resourceType === 'project' && membership.resourceId === projectId,\n )\n}\n\ninterface RobotBody {\n label: string\n memberships: Membership[]\n\n expiresAt?: string\n}\n\nasync function postRobot(\n url: string,\n {expiresAt, ...body}: RobotBody,\n query: Record<string, string> = {},\n): Promise<RobotWithToken> {\n const client = await getClient()\n\n return client.request<RobotWithToken>({\n body: {...body, ...(expiresAt === undefined ? {} : {expiresAt})},\n method: 'POST',\n query,\n url,\n })\n}\n\ninterface CreateTokenOptions {\n label: string\n projectId: string\n roleNames: string[]\n\n expiresAt?: string\n sendNotification?: boolean\n}\n\n/**\n * Add a token to a project\n * @param options - The options for adding a token to a project\n * @returns A promise that resolves to the created robot, including its secret token\n *\n * @internal\n */\nexport async function createToken(options: CreateTokenOptions): Promise<RobotWithToken> {\n const {expiresAt, label, projectId, roleNames, sendNotification} = options\n\n return postRobot(\n `/access/project/${projectId}/robots`,\n {\n expiresAt,\n label,\n memberships: [{resourceId: projectId, resourceType: 'project', roleNames}],\n },\n sendNotification === false ? {sendNotification: 'false'} : {},\n )\n}\n\ninterface CreateOrganizationTokenOptions extends RobotBody {\n organizationId: string\n}\n\n/**\n * Create an organization token, which can hold organization permissions and roles on several projects\n * @param options - The options for the organization token\n * @returns A promise that resolves to the created robot, including its secret token\n *\n * @internal\n */\nexport async function createOrganizationToken(\n options: CreateOrganizationTokenOptions,\n): Promise<RobotWithToken> {\n const {organizationId, ...body} = options\n\n return postRobot(`/access/organization/${organizationId}/robots`, body)\n}\n\nfunction robotsUrl({resourceId, resourceType}: TokenScope): string {\n return `/access/${resourceType}/${resourceId}/robots`\n}\n\nfunction isManagedByOrganization(robot: Robot, organizationId: string): boolean {\n return (\n robot.managedBy?.resourceType === 'organization' &&\n robot.managedBy.resourceId === organizationId\n )\n}\n\n/** Delete a token from the requested scope. @internal */\nexport async function deleteToken(scope: TokenScope, tokenId: string): Promise<void> {\n const client = await getClient()\n const url = `${robotsUrl(scope)}/${tokenId}`\n\n if (scope.resourceType === 'organization') {\n const robot = await client.request<Robot>({url})\n if (!isManagedByOrganization(robot, scope.resourceId)) {\n throw new Error(\n `Token \"${tokenId}\" is not managed by organization \"${scope.resourceId}\". Use tokens list --organization ${scope.resourceId} to find tokens managed by this organization.`,\n )\n }\n }\n\n return client.request({method: 'DELETE', url})\n}\n\n/**\n * Rotate a robot token, replacing its secret while preserving the robot,\n * its roles, and the token's expiry. The request authenticates as the robot\n * itself; the previous secret is revoked immediately.\n * @param token - The current robot token secret\n * @returns A promise that resolves to the robot with its new secret token\n *\n * @internal\n */\nexport async function rotateToken(token: string): Promise<RobotWithToken> {\n const client = await getClient(token)\n\n return client.request<RobotWithToken>({\n method: 'POST',\n url: '/access/robots/me/rotate',\n })\n}\n\n/** List tokens managed at the requested scope. @internal */\nexport async function getTokens(scope: TokenScope): Promise<Robot[]> {\n const client = await getClient()\n const query: Record<string, string> =\n scope.resourceType === 'organization' ? {includeChildren: 'true', limit: '500'} : {}\n const robots = await fetchAllPages<Robot>(client, robotsUrl(scope), query)\n\n if (scope.resourceType === 'organization') {\n return robots.filter((robot) => isManagedByOrganization(robot, scope.resourceId))\n }\n\n return robots.filter((robot) => robot.managedBy?.resourceType !== 'organization')\n}\n\n/**\n * Get all roles for a project\n * @param projectId - The project ID\n * @returns A promise that resolves to an array of project roles\n *\n * @internal\n */\nexport async function getTokenRoles(projectId: string): Promise<Role[]> {\n const client = await getClient()\n\n return fetchAllPages<Role>(client, `/access/project/${projectId}/roles`)\n}\n"],"names":["getGlobalCliClient","TOKENS_API_VERSION","getClient","token","apiVersion","requireUser","undefined","fetchAllPages","client","url","query","items","cursor","response","request","nextCursor","push","data","getProjectMembership","robot","projectId","memberships","find","membership","resourceType","resourceId","postRobot","expiresAt","body","method","createToken","options","label","roleNames","sendNotification","createOrganizationToken","organizationId","robotsUrl","isManagedByOrganization","managedBy","deleteToken","scope","tokenId","Error","rotateToken","getTokens","includeChildren","limit","robots","filter","getTokenRoles"],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import { promptForProject } from '../prompts/promptForProject.js';
2
+ export async function resolveTokenScope(options) {
3
+ const { getProjectId, grant, organization } = options;
4
+ if (organization) return {
5
+ resourceId: organization,
6
+ resourceType: 'organization'
7
+ };
8
+ const projectId = await getProjectId({
9
+ fallback: ()=>promptForProject({
10
+ requiredPermissions: [
11
+ {
12
+ grant,
13
+ permission: 'sanity.project.tokens'
14
+ }
15
+ ]
16
+ })
17
+ });
18
+ return {
19
+ resourceId: projectId,
20
+ resourceType: 'project'
21
+ };
22
+ }
23
+
24
+ //# sourceMappingURL=resolveTokenScope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/util/resolveTokenScope.ts"],"sourcesContent":["import {type SanityCommandInterface} from '@sanity/cli-core'\n\nimport {type TokenScope} from '../actions/tokens/types.js'\nimport {promptForProject} from '../prompts/promptForProject.js'\n\nexport async function resolveTokenScope(options: {\n getProjectId: SanityCommandInterface['getProjectId']\n grant: 'delete' | 'read'\n organization?: string\n}): Promise<TokenScope> {\n const {getProjectId, grant, organization} = options\n if (organization) return {resourceId: organization, resourceType: 'organization'}\n\n const projectId = await getProjectId({\n fallback: () =>\n promptForProject({requiredPermissions: [{grant, permission: 'sanity.project.tokens'}]}),\n })\n return {resourceId: projectId, resourceType: 'project'}\n}\n"],"names":["promptForProject","resolveTokenScope","options","getProjectId","grant","organization","resourceId","resourceType","projectId","fallback","requiredPermissions","permission"],"mappings":"AAGA,SAAQA,gBAAgB,QAAO,iCAAgC;AAE/D,OAAO,eAAeC,kBAAkBC,OAIvC;IACC,MAAM,EAACC,YAAY,EAAEC,KAAK,EAAEC,YAAY,EAAC,GAAGH;IAC5C,IAAIG,cAAc,OAAO;QAACC,YAAYD;QAAcE,cAAc;IAAc;IAEhF,MAAMC,YAAY,MAAML,aAAa;QACnCM,UAAU,IACRT,iBAAiB;gBAACU,qBAAqB;oBAAC;wBAACN;wBAAOO,YAAY;oBAAuB;iBAAE;YAAA;IACzF;IACA,OAAO;QAACL,YAAYE;QAAWD,cAAc;IAAS;AACxD"}