diff --git a/src/api-token.ts b/src/api-token.ts index d7f91b8..e57a637 100644 --- a/src/api-token.ts +++ b/src/api-token.ts @@ -116,7 +116,7 @@ export function isScopedApiToken(token: string): boolean { * prefer `cliCredentialBootstrap` → `auth bootstrap` instead. */ export function apiTokenMintInstructions(): string { - return `Mint one with the Kody MCP \`api\` tool \`tokenCreate\` (include the \`local-execute\` scope plus the capability scopes this command needs) and pass --token or set ${apiTokenEnvVar}.` + return `For CI/headless only: mint with the Kody MCP \`api\` tool \`tokenCreate\` (include \`org:execute\` plus the capability scopes this command needs, e.g. \`package:execute\` / \`integration:read\`) and pass --token or set ${apiTokenEnvVar}.` } /** Preferred interactive path for agents already on Kody MCP (ADR 0056). */ @@ -151,13 +151,13 @@ export function rejectedOauthBearerMessage(): string { export function insufficientScopeMessage(input: { requiredScope?: string | null - /** CapabilityProxy execute needs `local-execute` plus the capability scopes. */ + /** CapabilityProxy / package-graph: prefer bootstrap over tokenCreate. */ includeLocalExecute?: boolean }): string { const required = input.requiredScope ? ` The server requires "${input.requiredScope}".` : '' const mint = input.includeLocalExecute - ? apiTokenMintInstructions() - : `Mint a token with the Kody MCP \`api\` tool \`tokenCreate\`${ + ? `${cliBootstrapInstructions()} (lifetime short|long). ${apiTokenMintInstructions()}` + : `${cliBootstrapInstructions()} (lifetime short|long). Or for CI/headless, mint with tokenCreate${ input.requiredScope ? ` that includes "${input.requiredScope}"` : ' that includes the required scope' diff --git a/src/capability-proxy.ts b/src/capability-proxy.ts index 3b48bf0..5dcf19b 100644 --- a/src/capability-proxy.ts +++ b/src/capability-proxy.ts @@ -1,5 +1,6 @@ import { apiTokenMintInstructions, + cliBootstrapInstructions, featureDisabledMessage, insufficientScopeMessage, isScopedApiToken, @@ -202,7 +203,7 @@ function describeFailure( } if (status === 403 && stage === 'session') { return new CapabilityProxyError( - `The API token is not allowed to use CapabilityProxy${code ? ` (${code})` : ''}. ${apiTokenMintInstructions()}${detail}`, + `The API token is not allowed to use CapabilityProxy${code ? ` (${code})` : ''}. ${cliBootstrapInstructions()} (lifetime short|long). ${apiTokenMintInstructions()}${detail}`, { status, code }, ) } diff --git a/src/help.ts b/src/help.ts index cd2d624..d1cb0f1 100644 --- a/src/help.ts +++ b/src/help.ts @@ -55,9 +55,9 @@ Usage: Auth priority matches \`execute --local\`: \`--token\` / ${apiTokenEnvVar}; stored bootstrap/API token from \`auth bootstrap\`; then \`kody login\` where applicable. - Mint with the MCP \`api\` tool \`tokenCreate\` (include - \`local-execute\` plus the capability scopes you need), or use - \`auth bootstrap\` after \`cliCredentialBootstrap\`. + Prefer \`cliCredentialBootstrap\` then \`auth bootstrap\` + (\`--lifetime short|long\`). CI/headless: \`tokenCreate\` with + \`org:execute\` plus the capability scopes you need. For \`execute --local\`, a valid \`kody login\` session can also supply Bearer when no scoped token is available (no tokenCreate exchange). diff --git a/test/capability-proxy.test.ts b/test/capability-proxy.test.ts index d5764ac..27c5bfb 100644 --- a/test/capability-proxy.test.ts +++ b/test/capability-proxy.test.ts @@ -104,7 +104,7 @@ test('openCapabilityProxySession names insufficient_scope and the required scope }) await assert.rejects( () => openCapabilityProxySession({ apiUrl: 'https://api.kody.codes', token, fetchFn }), - /insufficient_scope[\s\S]*local-execute[\s\S]*tokenCreate[\s\S]*KODY_API_TOKEN/, + /insufficient_scope[\s\S]*local-execute[\s\S]*cliCredentialBootstrap[\s\S]*tokenCreate[\s\S]*KODY_API_TOKEN/, ) }) @@ -203,7 +203,7 @@ test('callCapabilityProxy surfaces capability errors from string or object bodie path: ['kody', 'emailSend'], args: [{}], }), - /insufficient_scope[\s\S]*email:send[\s\S]*tokenCreate/, + /insufficient_scope[\s\S]*email:send[\s\S]*cliCredentialBootstrap[\s\S]*tokenCreate/, ) const { fetchFn } = respondWith(422, { error: { code: 'invalid_args', message: 'to is required' } }) await assert.rejects( diff --git a/test/cli.test.ts b/test/cli.test.ts index 75553ba..0a8be65 100644 --- a/test/cli.test.ts +++ b/test/cli.test.ts @@ -106,7 +106,7 @@ test('resolveApiToken prefers --token, falls back to KODY_API_TOKEN, and require assert.equal(resolveApiToken({}, { KODY_API_TOKEN: ' env ' }), 'env') assert.throws( () => resolveApiToken({}, {}), - /cliCredentialBootstrap[\s\S]*tokenCreate[\s\S]*local-execute[\s\S]*pass --token or set KODY_API_TOKEN/, + /cliCredentialBootstrap[\s\S]*tokenCreate[\s\S]*org:execute[\s\S]*pass --token or set KODY_API_TOKEN/, ) }) @@ -517,7 +517,7 @@ test('execute without token or login prompts clearly', async () => { assert.match(stderr, /Not logged in, and no API token is set/) assert.match(stderr, /cliCredentialBootstrap|auth bootstrap/) assert.match(stderr, /tokenCreate/) - assert.match(stderr, /local-execute/) + assert.match(stderr, /org:execute/) assert.match(stderr, /KODY_API_TOKEN/) assert.match(stderr, /--token/) }) @@ -685,7 +685,7 @@ test('execute token paths surface feature_disabled and insufficient_scope', asyn details: { required_scope: 'local-execute' }, }, }, - pattern: /insufficient_scope[\s\S]*tokenCreate[\s\S]*local-execute/, + pattern: /insufficient_scope[\s\S]*cliCredentialBootstrap[\s\S]*tokenCreate[\s\S]*local-execute/, }, { args: ['execute', '--token', 'tok', ...moduleArgs], diff --git a/test/open-api-client.test.ts b/test/open-api-client.test.ts index 0076867..357b22e 100644 --- a/test/open-api-client.test.ts +++ b/test/open-api-client.test.ts @@ -67,6 +67,7 @@ test('searchWithApiToken maps insufficient_scope to a mint-token hint', async () assert.equal(error.code, 'insufficient_scope') assert.match(error.message, /insufficient_scope/) assert.match(error.message, /search:read/) + assert.match(error.message, /cliCredentialBootstrap/) assert.match(error.message, /tokenCreate/) return true }, diff --git a/test/remote-execute.test.ts b/test/remote-execute.test.ts index 8091b9b..a2b6759 100644 --- a/test/remote-execute.test.ts +++ b/test/remote-execute.test.ts @@ -143,6 +143,6 @@ test('runRemoteExecuteWithToken surfaces insufficient_scope on session', async ( apiUrl: 'https://api.kody.codes', fetchFn, }), - /insufficient_scope[\s\S]*tokenCreate[\s\S]*local-execute/, + /insufficient_scope[\s\S]*cliCredentialBootstrap[\s\S]*tokenCreate[\s\S]*local-execute/, ) })