Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/api-token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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). */
Expand Down Expand Up @@ -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'
Expand Down
3 changes: 2 additions & 1 deletion src/capability-proxy.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
apiTokenMintInstructions,
cliBootstrapInstructions,
featureDisabledMessage,
insufficientScopeMessage,
isScopedApiToken,
Expand Down Expand Up @@ -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 },
)
}
Expand Down
6 changes: 3 additions & 3 deletions src/help.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
4 changes: 2 additions & 2 deletions test/capability-proxy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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/,
)
})

Expand Down Expand Up @@ -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(
Expand Down
6 changes: 3 additions & 3 deletions test/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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/,
)
})

Expand Down Expand Up @@ -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/)
})
Expand Down Expand Up @@ -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],
Expand Down
1 change: 1 addition & 0 deletions test/open-api-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
},
Expand Down
2 changes: 1 addition & 1 deletion test/remote-execute.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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/,
)
})
Loading