Conversation
There was a problem hiding this comment.
Verification certificate:
- Verdict: REQUEST_CHANGES
- Depth: full
- Confidence: 0.75
- Certificate valid: yes
- Scenarios traced: 1
- Verification bounds:
- Changed files and explicitly traced call paths
Inline findings posted:
- [HIGH][security] Architectural finding (
.claude/skills/kontext-token-mode-bootstrap/scripts/bootstrap-token-mode.mjs:550) - [LOW][maintainability] Architectural finding (
.claude/skills/kontext-token-mode-bootstrap/scripts/bootstrap-token-mode.mjs:12) - [LOW][correctness] Architectural finding (
.claude/skills/kontext-token-mode-bootstrap/scripts/bootstrap-token-mode.mjs:600) - [LOW][docs] Architectural finding (
.claude/skills/kontext-token-mode-bootstrap/SKILL.md:1)
Reviewer summary:
- The PR introduces a new skill
kontext-token-mode-bootstrapto bootstrap a public PKCE app and token-mode integration setup. - It includes a Node.js script
bootstrap-token-mode.mjsthat interacts with the Kontext Management API to create or update applications and integrations. - The script handles environment variable parsing, API authentication, resource creation/updating, and optionally writes the public client ID to a local environment file.
- The PR also updates
README.mdand addsSKILL.mdandagents/openai.yamlfor documentation and metadata.
Scores and diagnostics
- Architecture: 7.0/10
- Organization: 7.0/10
- Cleanliness: 7.0/10
- Overall: 7.0/10
- Candidate findings: 4
- Published inline: 4
- Published summary-only: 0
- Suppressed findings: 0
- Verification verdict: REQUEST_CHANGES
Limitations
-
The script relies on the
gitcommand line tool to check if a file is tracked. Ifgitis not installed or the script is run outside a git repository, the check will silently pass. This is an acceptable limitation given the context. -
Model:
gemini-3.1-pro-preview -
Omitted files: 0
Generated at 2026-03-18T18:16:50.990Z.
| integration = updatedResponse.integration; | ||
| updated = true; | ||
| } | ||
|
|
||
| let validation = null; | ||
| if (validateIntegration) { | ||
| validation = await apiRequest({ | ||
| baseUrl, | ||
| token, | ||
| method: "POST", | ||
| path: `/integrations/${integration.id}/validate`, | ||
| }); | ||
| } | ||
|
|
||
| const refreshed = ( | ||
| await apiRequest({ | ||
| baseUrl, | ||
| token, | ||
| method: "GET", | ||
| path: `/integrations/${integration.id}`, | ||
| }) | ||
| ).integration; | ||
|
|
||
| return { | ||
| integration: refreshed, | ||
| created, | ||
| updated, | ||
| validation, | ||
| }; | ||
| } | ||
|
|
||
| async function ensureAttached({ baseUrl, token, applicationId, integrationId }) { | ||
| const attached = await apiRequest({ | ||
| baseUrl, |
There was a problem hiding this comment.
[HIGH][security] Architectural finding
Impact: Can reduce maintainability or correctness if left unresolved.
Suggested fix: Apply a targeted refactor and add/adjust tests where needed.
Evidence: The script correctly checks if the target environment file is tracked by git and refuses to write to it if it is, preventing accidental commits of sensitive data.
Evidence citations:
- .claude/skills/kontext-token-mode-bootstrap/scripts/bootstrap-token-mode.mjs:550
| function normalizeBaseUrl(value) { | ||
| return value.replace(/\/api\/v1\/?$/, "").replace(/\/$/, ""); | ||
| } | ||
|
|
||
| function normalizeUrl(value) { | ||
| return value.replace(/\/$/, ""); | ||
| } | ||
|
|
||
| function required(name, value) { | ||
| if (!value) { | ||
| throw new Error(`Missing required value: ${name}`); | ||
| } | ||
|
|
||
| return value; | ||
| } | ||
|
|
||
| function parseOptionalTrimmed(value) { | ||
| if (typeof value !== "string") { | ||
| return undefined; | ||
| } | ||
|
|
||
| const trimmed = value.trim(); | ||
| return trimmed.length > 0 ? trimmed : undefined; | ||
| } | ||
|
|
||
| function requiredTrimmed(name, value) { | ||
| const normalized = parseOptionalTrimmed(required(name, value)); | ||
| if (!normalized) { | ||
| throw new Error(`${name} must not be empty.`); | ||
| } | ||
|
|
||
| return normalized; | ||
| } | ||
|
|
||
| function parseStringArray(name, raw, fallback) { | ||
| if (!raw) { | ||
| return fallback; | ||
| } | ||
|
|
||
| let parsed; | ||
| try { | ||
| parsed = JSON.parse(raw); | ||
| } catch (error) { | ||
| throw new Error(`${name} must be valid JSON: ${error.message}`); | ||
| } | ||
|
|
||
| if (!Array.isArray(parsed) || parsed.some((value) => typeof value !== "string")) { | ||
| throw new Error(`${name} must be a JSON array of strings.`); | ||
| } | ||
|
|
||
| return parsed.map((value) => value.trim()).filter(Boolean); | ||
| } | ||
|
|
||
| function parseBoolean(name, value, fallback) { | ||
| const normalized = parseOptionalTrimmed(value); | ||
| if (!normalized) { | ||
| return fallback; | ||
| } | ||
|
|
||
| const lower = normalized.toLowerCase(); | ||
| if (["1", "true", "yes"].includes(lower)) { | ||
| return true; | ||
| } | ||
| if (["0", "false", "no"].includes(lower)) { | ||
| return false; | ||
| } | ||
|
|
||
| throw new Error(`${name} must be one of: true, false, 1, 0, yes, no.`); | ||
| } | ||
|
|
||
| function normalizeAuthMode(value) { | ||
| const normalized = parseOptionalTrimmed(value); | ||
| if (!normalized) { | ||
| return undefined; | ||
| } | ||
|
|
||
| if (!VALID_AUTH_MODES.has(normalized)) { | ||
| throw new Error( | ||
| `KONTEXT_INTEGRATION_AUTH_MODE must be one of: ${Array.from(VALID_AUTH_MODES).join(", ")}`, | ||
| ); | ||
| } | ||
|
|
||
| return normalized; | ||
| } | ||
|
|
||
| function buildOauthConfig({ provider, issuer, scopes }) { | ||
| const oauth = {}; | ||
|
|
||
| if (provider) { | ||
| oauth.provider = provider; | ||
| } | ||
| if (issuer) { | ||
| oauth.issuer = issuer; | ||
| } | ||
| if (Array.isArray(scopes) && scopes.length > 0) { | ||
| oauth.scopes = scopes; | ||
| } | ||
|
|
||
| return Object.keys(oauth).length > 0 ? oauth : undefined; | ||
| } | ||
|
|
||
| function buildIntegrationPayload(input) { | ||
| const payload = {}; | ||
|
|
||
| if (input.name !== undefined) { | ||
| payload.name = input.name; | ||
| } | ||
| if (input.url !== undefined) { | ||
| payload.url = input.url; | ||
| } | ||
| if (input.authMode !== undefined) { | ||
| payload.authMode = input.authMode; | ||
| } | ||
| if (input.oauth !== undefined) { | ||
| payload.oauth = input.oauth; | ||
| } | ||
| if (input.serverToken !== undefined) { | ||
| payload.serverToken = input.serverToken; | ||
| } | ||
|
|
||
| return payload; | ||
| } | ||
|
|
||
| function buildApplicationOauthPayload({ | ||
| redirectUris, | ||
| pkceRequired, | ||
| scopes, | ||
| allowedResources, | ||
| }) { | ||
| const payload = { | ||
| type: "public", | ||
| redirectUris, | ||
| pkceRequired, | ||
| scopes, | ||
| }; | ||
|
|
||
| if (Array.isArray(allowedResources) && allowedResources.length > 0) { | ||
| payload.allowedResources = allowedResources; | ||
| } | ||
|
|
||
| return payload; | ||
| } | ||
|
|
||
| function arraysEqual(left, right) { | ||
| if (left.length !== right.length) { | ||
| return false; | ||
| } | ||
|
|
||
| return left.every((value, index) => value === right[index]); | ||
| } | ||
|
|
||
| function escapeRegex(value) { | ||
| return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); | ||
| } | ||
|
|
||
| async function requestToken({ baseUrl, resource, clientId, clientSecret }) { | ||
| const body = new URLSearchParams({ | ||
| grant_type: "client_credentials", | ||
| scope: "management:all", | ||
| audience: resource, | ||
| }); | ||
|
|
||
| const basicAuth = Buffer.from(`${clientId}:${clientSecret}`).toString("base64"); | ||
|
|
||
| const response = await fetch(`${baseUrl}/oauth2/token`, { | ||
| method: "POST", | ||
| headers: { | ||
| "content-type": "application/x-www-form-urlencoded", | ||
| accept: "application/json", | ||
| authorization: `Basic ${basicAuth}`, | ||
| }, | ||
| body: body.toString(), | ||
| }); | ||
|
|
||
| if (!response.ok) { | ||
| const message = await response.text(); | ||
| throw new Error( | ||
| `Service account authentication failed (${response.status}): ${message}`, | ||
| ); | ||
| } | ||
|
|
||
| const payload = await response.json(); | ||
| return payload.access_token; | ||
| } | ||
|
|
||
| async function apiRequest({ baseUrl, token, method, path: requestPath, body }) { | ||
| const response = await fetch(`${baseUrl}/api/v1${requestPath}`, { | ||
| method, | ||
| headers: { | ||
| authorization: `Bearer ${token}`, | ||
| accept: "application/json", | ||
| ...(body ? { "content-type": "application/json" } : {}), | ||
| }, | ||
| ...(body ? { body: JSON.stringify(body) } : {}), | ||
| }); | ||
|
|
||
| if (!response.ok) { | ||
| const message = await response.text(); | ||
| throw new Error(`${method} ${requestPath} failed (${response.status}): ${message}`); | ||
| } | ||
|
|
||
| if (response.status === 204) { | ||
| return null; | ||
| } | ||
|
|
||
| return response.json(); | ||
| } | ||
|
|
||
| async function listAllApplications({ baseUrl, token }) { | ||
| const items = []; | ||
| let cursor; | ||
|
|
||
| while (true) { | ||
| const query = cursor ? `?cursor=${encodeURIComponent(cursor)}` : ""; | ||
| const response = await apiRequest({ | ||
| baseUrl, | ||
| token, | ||
| method: "GET", | ||
| path: `/applications${query}`, | ||
| }); | ||
|
|
||
| items.push(...(response.items ?? [])); | ||
| if (!response.nextCursor) { | ||
| return items; | ||
| } | ||
|
|
||
| cursor = response.nextCursor; | ||
| } | ||
| } | ||
|
|
||
| async function listAllIntegrations({ baseUrl, token }) { | ||
| const items = []; | ||
| let cursor; | ||
|
|
||
| while (true) { | ||
| const query = cursor ? `?cursor=${encodeURIComponent(cursor)}` : ""; | ||
| const response = await apiRequest({ | ||
| baseUrl, | ||
| token, | ||
| method: "GET", | ||
| path: `/integrations${query}`, | ||
| }); | ||
|
|
||
| items.push(...(response.items ?? [])); | ||
| if (!response.nextCursor) { | ||
| return items; | ||
| } | ||
|
|
||
| cursor = response.nextCursor; | ||
| } | ||
| } | ||
|
|
||
| async function resolveOrCreateApplication({ | ||
| baseUrl, | ||
| token, | ||
| applicationId, | ||
| applicationName, | ||
| createApplication, | ||
| redirectUris, | ||
| pkceRequired, | ||
| scopes, | ||
| allowedResources, | ||
| }) { | ||
| let application; | ||
| let oauth; | ||
| let created = false; | ||
| let updated = false; | ||
| let allowedResourcesNote = null; | ||
|
|
||
| if (applicationId) { | ||
| const response = await apiRequest({ | ||
| baseUrl, | ||
| token, | ||
| method: "GET", | ||
| path: `/applications/${applicationId}`, | ||
| }); | ||
| application = response.application; | ||
| } else { | ||
| application = (await listAllApplications({ baseUrl, token })).find( | ||
| (item) => item.name === applicationName, | ||
| ); | ||
| } | ||
|
|
||
| if (!application) { | ||
| if (!createApplication) { | ||
| throw new Error( | ||
| `Application named "${applicationName}" was not found. Set KONTEXT_CREATE_APPLICATION=true to create it.`, | ||
| ); | ||
| } | ||
|
|
||
| if (redirectUris.length === 0) { | ||
| throw new Error( | ||
| "KONTEXT_APPLICATION_REDIRECT_URIS_JSON is required when creating a public application.", | ||
| ); | ||
| } | ||
|
|
||
| const createdResponse = await apiRequest({ | ||
| baseUrl, | ||
| token, | ||
| method: "POST", | ||
| path: "/applications", | ||
| body: { | ||
| name: applicationName, | ||
| oauth: buildApplicationOauthPayload({ | ||
| redirectUris, | ||
| pkceRequired, | ||
| scopes, | ||
| allowedResources, | ||
| }), | ||
| }, | ||
| }); | ||
|
|
||
| application = createdResponse.application; | ||
| oauth = createdResponse.oauth; | ||
| created = true; | ||
| } else { | ||
| const oauthResponse = await apiRequest({ | ||
| baseUrl, | ||
| token, | ||
| method: "GET", | ||
| path: `/applications/${application.id}/oauth`, | ||
| }); | ||
| oauth = oauthResponse.oauth; | ||
| } | ||
|
|
||
| if (oauth.type !== "public") { | ||
| throw new Error( | ||
| `Application "${application.name}" is a ${oauth.type} client. Token mode bootstrap requires a public application.`, | ||
| ); | ||
| } | ||
|
|
||
| if (!created) { | ||
| const patch = {}; | ||
|
|
||
| if (oauth.pkceRequired !== pkceRequired) { | ||
| patch.pkceRequired = pkceRequired; | ||
| } | ||
|
|
||
| const currentScopes = Array.isArray(oauth.scopes) ? oauth.scopes : []; | ||
| if (!arraysEqual(currentScopes, scopes)) { | ||
| patch.scopes = scopes; | ||
| } | ||
|
|
||
| if (redirectUris.length > 0) { | ||
| const currentRedirectUris = Array.isArray(oauth.redirectUris) | ||
| ? oauth.redirectUris | ||
| : []; | ||
| if (!arraysEqual(currentRedirectUris, redirectUris)) { | ||
| patch.redirectUris = redirectUris; | ||
| } | ||
| } | ||
|
|
||
| if (allowedResources.length > 0) { | ||
| allowedResourcesNote = | ||
| "Existing application reused. allowedResources were not mutated by this script."; | ||
| } | ||
|
|
||
| if (Object.keys(patch).length > 0) { | ||
| const patched = await apiRequest({ | ||
| baseUrl, | ||
| token, | ||
| method: "PATCH", | ||
| path: `/applications/${application.id}/oauth`, | ||
| body: patch, | ||
| }); | ||
| oauth = patched.oauth; | ||
| updated = true; | ||
| } | ||
| } | ||
|
|
||
| return { | ||
| application, | ||
| oauth, | ||
| created, | ||
| updated, | ||
| allowedResourcesNote, | ||
| }; | ||
| } | ||
|
|
||
| function buildDesiredIntegration({ | ||
| integrationName, | ||
| integrationUrl, | ||
| integrationAuthMode, | ||
| oauthProvider, | ||
| oauthIssuer, | ||
| oauthScopes, | ||
| serverToken, | ||
| }) { | ||
| const resolvedServerToken = parseOptionalTrimmed(serverToken); | ||
| if (resolvedServerToken && integrationAuthMode !== "server_token") { | ||
| throw new Error( | ||
| "KONTEXT_SERVER_TOKEN can only be used when KONTEXT_INTEGRATION_AUTH_MODE=server_token.", | ||
| ); | ||
| } | ||
|
|
||
| if ( | ||
| (oauthProvider || oauthIssuer || (oauthScopes && oauthScopes.length > 0)) && | ||
| integrationAuthMode !== "oauth" | ||
| ) { | ||
| throw new Error( | ||
| "OAuth fields can only be used when KONTEXT_INTEGRATION_AUTH_MODE=oauth.", | ||
| ); | ||
| } | ||
|
|
||
| return { | ||
| name: integrationName, | ||
| url: integrationUrl, | ||
| authMode: integrationAuthMode, | ||
| oauth: buildOauthConfig({ | ||
| provider: oauthProvider, | ||
| issuer: oauthIssuer, | ||
| scopes: oauthScopes, | ||
| }), | ||
| serverToken: resolvedServerToken, | ||
| }; | ||
| } | ||
|
|
||
| async function resolveExistingIntegration({ | ||
| baseUrl, | ||
| token, | ||
| integrationId, | ||
| integrationName, | ||
| }) { | ||
| if (integrationId) { | ||
| const response = await apiRequest({ | ||
| baseUrl, | ||
| token, | ||
| method: "GET", | ||
| path: `/integrations/${integrationId}`, | ||
| }); | ||
| return response.integration; | ||
| } | ||
|
|
||
| if (!integrationName) { | ||
| return null; | ||
| } | ||
|
|
||
| return ( | ||
| (await listAllIntegrations({ baseUrl, token })).find( | ||
| (item) => item.name === integrationName, | ||
| ) ?? null | ||
| ); | ||
| } | ||
|
|
||
| function shouldUpdateIntegration(existing, desired) { | ||
| if (!existing) { | ||
| return false; | ||
| } | ||
|
|
||
| if (desired.name !== undefined && desired.name !== existing.name) { | ||
| return true; | ||
| } | ||
| if ( | ||
| desired.url !== undefined && | ||
| normalizeUrl(desired.url) !== normalizeUrl(existing.url) | ||
| ) { | ||
| return true; | ||
| } | ||
| if ( | ||
| desired.authMode !== undefined && | ||
| desired.authMode !== (existing.authMode ?? "none") | ||
| ) { | ||
| return true; | ||
| } | ||
| if (desired.oauth) { | ||
| const existingScopes = Array.isArray(existing.oauth?.scopes) | ||
| ? existing.oauth.scopes | ||
| : []; | ||
| const desiredScopes = Array.isArray(desired.oauth.scopes) | ||
| ? desired.oauth.scopes | ||
| : []; | ||
|
|
||
| if ((desired.oauth.provider ?? null) !== (existing.oauth?.provider ?? null)) { | ||
| return true; | ||
| } | ||
| if ((desired.oauth.issuer ?? null) !== (existing.oauth?.issuer ?? null)) { | ||
| return true; | ||
| } | ||
| if (!arraysEqual(desiredScopes, existingScopes)) { | ||
| return true; | ||
| } | ||
| } | ||
| if (desired.serverToken !== undefined) { | ||
| return true; | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| async function upsertIntegration({ | ||
| baseUrl, | ||
| token, | ||
| existing, | ||
| desired, | ||
| validateIntegration, | ||
| }) { | ||
| let integration = existing; | ||
| let created = false; | ||
| let updated = false; | ||
|
|
||
| if (!integration) { | ||
| if (!desired.name || !desired.url) { | ||
| throw new Error( | ||
| "Creating an integration requires KONTEXT_INTEGRATION_NAME and KONTEXT_INTEGRATION_URL.", | ||
| ); | ||
| } | ||
|
|
||
| const createdResponse = await apiRequest({ | ||
| baseUrl, | ||
| token, | ||
| method: "POST", | ||
| path: "/integrations", | ||
| body: buildIntegrationPayload({ | ||
| name: desired.name, | ||
| url: desired.url, | ||
| authMode: desired.authMode ?? "none", | ||
| oauth: desired.oauth, | ||
| serverToken: desired.serverToken, | ||
| }), | ||
| }); | ||
|
|
||
| integration = createdResponse.integration; | ||
| created = true; | ||
| } else if (shouldUpdateIntegration(integration, desired)) { | ||
| const updatedResponse = await apiRequest({ | ||
| baseUrl, | ||
| token, | ||
| method: "PATCH", | ||
| path: `/integrations/${integration.id}`, | ||
| body: buildIntegrationPayload({ | ||
| name: desired.name, | ||
| url: desired.url, | ||
| authMode: desired.authMode, | ||
| oauth: desired.oauth, | ||
| serverToken: desired.serverToken, | ||
| }), | ||
| }); | ||
|
|
||
| integration = updatedResponse.integration; | ||
| updated = true; | ||
| } | ||
|
|
||
| let validation = null; | ||
| if (validateIntegration) { | ||
| validation = await apiRequest({ | ||
| baseUrl, | ||
| token, | ||
| method: "POST", | ||
| path: `/integrations/${integration.id}/validate`, | ||
| }); | ||
| } | ||
|
|
||
| const refreshed = ( | ||
| await apiRequest({ | ||
| baseUrl, | ||
| token, | ||
| method: "GET", | ||
| path: `/integrations/${integration.id}`, | ||
| }) | ||
| ).integration; | ||
|
|
||
| return { | ||
| integration: refreshed, | ||
| created, | ||
| updated, | ||
| validation, | ||
| }; | ||
| } | ||
|
|
||
| async function ensureAttached({ baseUrl, token, applicationId, integrationId }) { | ||
| const attached = await apiRequest({ | ||
| baseUrl, |
There was a problem hiding this comment.
[LOW][maintainability] Architectural finding
Impact: Can reduce maintainability or correctness if left unresolved.
Suggested fix: Apply a targeted refactor and add/adjust tests where needed.
Evidence: The script is well-organized into small, purpose-specific functions, making it easy to read and maintain.
Evidence citations:
- .claude/skills/kontext-token-mode-bootstrap/scripts/bootstrap-token-mode.mjs:12
|
|
||
| return true; | ||
| } | ||
|
|
||
| async function writeEnvValue({ outputFile, variableName, value }) { | ||
| const absolutePath = path.resolve(outputFile); | ||
| await ensureFileIsNotTracked(absolutePath); | ||
| let contents = ""; | ||
|
|
||
| try { | ||
| contents = await fs.readFile(absolutePath, "utf8"); | ||
| } catch (error) { | ||
| if (error.code !== "ENOENT") { | ||
| throw error; | ||
| } | ||
| } | ||
|
|
||
| const line = `${variableName}=${value}`; | ||
| const pattern = new RegExp(`^${escapeRegex(variableName)}=.*$`, "m"); | ||
| let nextContents; | ||
|
|
||
| if (pattern.test(contents)) { | ||
| nextContents = contents.replace(pattern, line); | ||
| } else if (contents.length === 0) { | ||
| nextContents = `${line}\n`; | ||
| } else { | ||
| const separator = contents.endsWith("\n") ? "" : "\n"; | ||
| nextContents = `${contents}${separator}${line}\n`; | ||
| } | ||
|
|
||
| await fs.writeFile(absolutePath, nextContents, "utf8"); | ||
| return absolutePath; | ||
| } | ||
|
|
||
| async function ensureFileIsNotTracked(absolutePath) { | ||
| let repoRoot; | ||
|
|
||
| try { | ||
| const result = await execFileAsync("git", ["rev-parse", "--show-toplevel"], { | ||
| cwd: path.dirname(absolutePath), | ||
| }); | ||
| repoRoot = result.stdout.trim(); | ||
| } catch { | ||
| return; | ||
| } | ||
|
|
||
| const relativePath = path.relative(repoRoot, absolutePath); | ||
| if (relativePath.startsWith("..")) { | ||
| return; | ||
| } | ||
|
|
||
| try { | ||
| await execFileAsync("git", ["ls-files", "--error-unmatch", relativePath], { | ||
| cwd: repoRoot, | ||
| }); | ||
| throw new Error( | ||
| `Refusing to write ${absolutePath} because it is tracked by git. Use a local-only env file instead.`, | ||
| ); | ||
| } catch (error) { | ||
| if ( | ||
| error instanceof Error && | ||
| error.message.includes("Refusing to write") | ||
| ) { | ||
| throw error; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| function buildRuntimeSummary(integrationName) { | ||
| return { | ||
| retrievalMethod: "kontext.require(integration, token)", | ||
| integrationName, | ||
| firstTimeConnect: "hosted_connect", | ||
| note: "Pass the authenticated Kontext token into runtime code and handle IntegrationConnectionRequiredError for first-time connect.", | ||
| }; | ||
| } | ||
|
|
||
| async function main() { | ||
| const { values, positionals } = parseArgs({ | ||
| options: { | ||
| "api-base-url": { type: "string" }, | ||
| "service-account-client-id": { type: "string" }, |
There was a problem hiding this comment.
[LOW][correctness] Architectural finding
Impact: Can reduce maintainability or correctness if left unresolved.
Suggested fix: Apply a targeted refactor and add/adjust tests where needed.
Evidence: The script correctly parses command line arguments and environment variables, and validates required inputs.
Evidence citations:
- .claude/skills/kontext-token-mode-bootstrap/scripts/bootstrap-token-mode.mjs:600
| --- | ||
| name: kontext-token-mode-bootstrap | ||
| description: Bootstrap a public Kontext application for token-mode runtime credential exchange via the Management API using a service account. Use when replacing hardcoded end-user API tokens with `kontext.require(integration, token)`, creating or reusing a public PKCE app, ensuring and attaching a user-chosen provider integration, and optionally writing the public client ID into a local env file. Do not use this skill for confidential `userId` retrieval or Bring your own auth. | ||
| --- | ||
|
|
||
| # Kontext Token Mode Bootstrap | ||
|
|
||
| Use this skill when the app should keep user credentials at runtime and stop relying on hardcoded provider tokens in source code or env. | ||
|
|
||
| This skill covers one path: | ||
| - bootstrap or reuse a **public** Kontext application with PKCE | ||
| - ensure or update a provider integration chosen by the caller | ||
| - attach that integration to the public app | ||
| - optionally write the **public client ID only** into a local env file | ||
| - rewrite runtime code to use token mode: | ||
|
|
||
| ```ts | ||
| const credential = await kontext.require(integrationName, token); | ||
| ``` | ||
|
|
||
| This skill does **not** cover: | ||
| - confidential `kontext.require(integration, { userId })` retrieval | ||
| - Bring your own auth, issuer, JWKS, or partner connect bootstrap | ||
| - hardcoding a provider recipe like Gmail into the skill itself | ||
|
|
||
| If the request is about confidential backend retrieval with `userId`, use `kontext-sdk-credentials` instead. | ||
| If the request is about trusting the app's own auth system and avoiding double auth, use `kontext-byoa-setup` instead. | ||
|
|
||
| ## Runtime Shape | ||
|
|
||
| Use this flow: | ||
|
|
||
| 1. Admin or setup agent runs the bundled bootstrap script with a service account. | ||
| 2. The script creates or reuses a **public** app with PKCE. | ||
| 3. The script creates, updates, or reuses the target integration based on env inputs. | ||
| 4. The script attaches that integration to the app. | ||
| 5. The script prints the app client ID and can write it into a local env file. | ||
| 6. The app signs the end user in with PKCE. | ||
| 7. Runtime code passes the authenticated Kontext token into `kontext.require(...)`. | ||
| 8. If the integration is not connected yet, handle `IntegrationConnectionRequiredError` and send the user to `connectUrl`. | ||
| 9. Retry after connect and continue the task. | ||
|
|
||
| Provider-specific configuration belongs in the command inputs, not in this skill. The skill should stay generic. | ||
|
|
||
| ## Execution Rules | ||
|
|
||
| Follow these rules in order: | ||
|
|
||
| 1. Validate bootstrap inputs first. | ||
| 2. If `KONTEXT_SERVICE_ACCOUNT_CLIENT_ID` or `KONTEXT_SERVICE_ACCOUNT_CLIENT_SECRET` is missing, stop immediately and tell the user exactly which variable is missing. | ||
| 3. If `KONTEXT_OUTPUT_ENV_FILE` is missing, infer a conventional ignored local env target when the repo already clearly uses one, for example `apps/web/.env.local`, and pass it inline when running the helper. | ||
| 4. If the user names an existing integration such as `google-workspace`, treat that as **reuse and attach first**. Do not try to invent provider templates or broad management recipes unless the user explicitly asked for integration creation details. | ||
| 5. Run the bundled helper from the installed skill directly. Do not vendor or copy the helper into the target repo. | ||
| 6. Keep repo exploration narrow until bootstrap is done or blocked. Do not pivot into unrelated examples, demos, or legacy integrations just because they mention the same provider. | ||
| 7. After bootstrap succeeds, edit the target runtime surface and remove the hardcoded credential path. | ||
|
|
||
| ## Required Inputs | ||
|
|
||
| Service account: | ||
| - `KONTEXT_SERVICE_ACCOUNT_CLIENT_ID` | ||
| - `KONTEXT_SERVICE_ACCOUNT_CLIENT_SECRET` | ||
| - `KONTEXT_API_BASE_URL` (optional, defaults to `https://api.kontext.dev`) | ||
| - `MANAGEMENT_API_RESOURCE` (optional, defaults to `${KONTEXT_API_BASE_URL}/api/v1`) | ||
|
|
||
| Target application: | ||
| - `KONTEXT_APPLICATION_ID`, or | ||
| - `KONTEXT_APPLICATION_NAME` | ||
|
|
||
| Create the app when missing: | ||
| - `KONTEXT_CREATE_APPLICATION=true` | ||
| - `KONTEXT_APPLICATION_REDIRECT_URIS_JSON='["http://localhost:3000/callback"]'` | ||
|
|
||
| Optional public-app settings: | ||
| - `KONTEXT_APPLICATION_SCOPES_JSON` defaults to `["mcp:invoke"]` | ||
| - `KONTEXT_APPLICATION_PKCE_REQUIRED` defaults to `true` | ||
| - `KONTEXT_APPLICATION_ALLOWED_RESOURCES_JSON` defaults to `["mcp-gateway"]` when creating a new app | ||
|
|
||
| Target integration: | ||
| - `KONTEXT_INTEGRATION_ID`, or | ||
| - `KONTEXT_INTEGRATION_NAME` | ||
|
|
||
| Create or update a generic integration: | ||
| - `KONTEXT_INTEGRATION_URL` | ||
| - `KONTEXT_INTEGRATION_AUTH_MODE` | ||
| - `KONTEXT_INTEGRATION_OAUTH_PROVIDER` | ||
| - `KONTEXT_INTEGRATION_OAUTH_ISSUER` | ||
| - `KONTEXT_INTEGRATION_OAUTH_SCOPES_JSON` | ||
| - `KONTEXT_SERVER_TOKEN` | ||
| - `KONTEXT_VALIDATE_INTEGRATION=true` | ||
|
|
||
| Optional env output: | ||
| - `KONTEXT_OUTPUT_ENV_FILE` such as `.env.local` | ||
| - `KONTEXT_PUBLIC_CLIENT_ID_ENV_NAME` defaults to `NEXT_PUBLIC_KONTEXT_CLIENT_ID` | ||
|
|
||
| ## Secret Handling Rules | ||
|
|
||
| Follow these rules strictly: | ||
|
|
||
| - Never print `KONTEXT_SERVICE_ACCOUNT_CLIENT_SECRET` unless the user explicitly asks. | ||
| - Never print `KONTEXT_SERVER_TOKEN` unless the user explicitly asks. | ||
| - Never write service account credentials, shared server tokens, or live provider credentials into tracked files. | ||
| - Only write the **public** client ID into env output files. | ||
| - Never commit secrets or live tokens. | ||
| - If the target env file is tracked, stop and tell the user instead of writing into it. | ||
|
|
||
| ## Workflow | ||
|
|
||
| 1. Confirm the request really wants token mode and a public PKCE app. | ||
| 2. Validate the bootstrap inputs before scanning the repo: | ||
| - service account vars | ||
| - output env target | ||
| - application name or ID | ||
| - integration name or create/update inputs | ||
| 3. Run the bundled setup helper immediately. Do this before broad codebase exploration: | ||
|
|
||
| ```bash | ||
| node scripts/bootstrap-token-mode.mjs | ||
| ``` | ||
|
|
||
| 4. If the helper is blocked, stop with the exact missing input. Do not continue with unrelated repo exploration. | ||
| 5. Find the hardcoded provider credential path in the specific target surface the user asked for: | ||
| - `Bearer ...` | ||
| - `process.env.*TOKEN` | ||
| - `process.env.*KEY` | ||
| - literal provider access tokens | ||
| 6. Read the output: | ||
| - public app client ID | ||
| - integration ID and name | ||
| - whether the integration was created, updated, or reused | ||
| - whether the env file was written | ||
| 7. Replace the hardcoded credential path with token mode: | ||
|
|
||
| ```ts | ||
| const credential = await kontext.require(integrationName, token); | ||
| ``` | ||
|
|
||
| 8. If the runtime surface is an MCP server or backend route, keep the user token flowing into that server call. | ||
| 9. If runtime code can hit a first-time-connect case, handle `IntegrationConnectionRequiredError` and use `err.connectUrl`. | ||
| 10. Summarize the final setup and the exact runtime integration name. | ||
|
|
||
| ## Preferred Command Pattern | ||
|
|
||
| Bootstrap a new public PKCE app, create a generic OAuth integration, attach it, and write the public client ID into `.env.local`: | ||
|
|
||
| ```bash | ||
| KONTEXT_SERVICE_ACCOUNT_CLIENT_ID=... \ | ||
| KONTEXT_SERVICE_ACCOUNT_CLIENT_SECRET=... \ | ||
| KONTEXT_APPLICATION_NAME="My Demo Agent" \ | ||
| KONTEXT_CREATE_APPLICATION=true \ | ||
| KONTEXT_APPLICATION_REDIRECT_URIS_JSON='["http://localhost:3000/callback"]' \ | ||
| KONTEXT_INTEGRATION_NAME="My Provider" \ | ||
| KONTEXT_INTEGRATION_URL="https://provider.example.com/mcp" \ | ||
| KONTEXT_INTEGRATION_AUTH_MODE=oauth \ | ||
| KONTEXT_INTEGRATION_OAUTH_PROVIDER="provider-name" \ | ||
| KONTEXT_INTEGRATION_OAUTH_SCOPES_JSON='["scope-a","scope-b"]' \ | ||
| KONTEXT_OUTPUT_ENV_FILE=.env.local \ | ||
| KONTEXT_PUBLIC_CLIENT_ID_ENV_NAME=NEXT_PUBLIC_KONTEXT_DEMO_CLIENT_ID \ | ||
| node scripts/bootstrap-token-mode.mjs | ||
| ``` | ||
|
|
||
| Attach an existing integration to an existing public app and only print the public client ID: | ||
|
|
||
| ```bash | ||
| KONTEXT_SERVICE_ACCOUNT_CLIENT_ID=... \ | ||
| KONTEXT_SERVICE_ACCOUNT_CLIENT_SECRET=... \ | ||
| KONTEXT_APPLICATION_ID=app_... \ | ||
| KONTEXT_INTEGRATION_ID=int_... \ | ||
| node scripts/bootstrap-token-mode.mjs | ||
| ``` | ||
|
|
||
| Create or update a generic `user_token` integration: | ||
|
|
||
| ```bash | ||
| KONTEXT_SERVICE_ACCOUNT_CLIENT_ID=... \ | ||
| KONTEXT_SERVICE_ACCOUNT_CLIENT_SECRET=... \ | ||
| KONTEXT_APPLICATION_NAME="My Agent" \ | ||
| KONTEXT_CREATE_APPLICATION=true \ | ||
| KONTEXT_APPLICATION_REDIRECT_URIS_JSON='["http://localhost:3000/callback"]' \ | ||
| KONTEXT_INTEGRATION_NAME="My API" \ | ||
| KONTEXT_INTEGRATION_URL="https://mcp.example.com" \ | ||
| KONTEXT_INTEGRATION_AUTH_MODE=user_token \ | ||
| node scripts/bootstrap-token-mode.mjs | ||
| ``` | ||
|
|
||
| ## Runtime Integration Pattern | ||
|
|
||
| For client auth: | ||
|
|
||
| ```ts | ||
| const client = createKontextClient({ | ||
| clientId: process.env.NEXT_PUBLIC_KONTEXT_CLIENT_ID!, | ||
| redirectUri: `${window.location.origin}/callback`, | ||
| onAuthRequired: (url) => { | ||
| window.location.href = url.toString(); | ||
| }, | ||
| }); | ||
| ``` | ||
|
|
||
| For runtime credential exchange: | ||
|
|
||
| ```ts | ||
| const kontext = new Kontext({ | ||
| clientId: process.env.NEXT_PUBLIC_KONTEXT_CLIENT_ID!, | ||
| apiUrl: process.env.KONTEXT_API_URL, | ||
| }); | ||
|
|
||
| const credential = await kontext.require("provider-integration", token); | ||
| ``` | ||
|
|
||
| First-time connect handling: | ||
|
|
||
| ```ts | ||
| import { IntegrationConnectionRequiredError } from "@kontext-dev/js-sdk/errors"; | ||
|
|
||
| try { | ||
| const credential = await kontext.require("provider-integration", token); | ||
| } catch (error) { | ||
| if (error instanceof IntegrationConnectionRequiredError && error.connectUrl) { | ||
| window.location.href = error.connectUrl; | ||
| } | ||
| throw error; | ||
| } | ||
| ``` | ||
|
|
||
| ## Prompt Template | ||
|
|
||
| Use this when the user wants a coding agent to remove hardcoded creds: | ||
|
|
||
| ```text | ||
| Use $kontext-token-mode-bootstrap. | ||
|
|
||
| This app currently uses hardcoded end-user credentials for an external provider. Replace that with Kontext token mode. | ||
|
|
||
| Bootstrap: | ||
| - create or reuse a public Kontext application with PKCE | ||
| - create, update, or reuse the provider integration described by the current env inputs | ||
| - attach that integration to the public app | ||
| - write the public client ID into the local env file if KONTEXT_OUTPUT_ENV_FILE is set | ||
|
|
||
| Runtime: | ||
| - keep the end user on PKCE | ||
| - use kontext.require("<integration>", token) at runtime | ||
| - if the integration is not connected yet, use the hosted connect flow and continue after connect | ||
|
|
||
| Do not switch this to confidential userId mode. | ||
| Do not hardcode provider scopes into the skill. Read them from the current env or prompt. | ||
| ``` | ||
|
|
||
| ## Success Output | ||
|
|
||
| Return a short summary in this shape: | ||
|
|
||
| Token-mode bootstrap configured. | ||
|
|
||
| Application: | ||
| - Name: <application name> | ||
| - Application ID: <application id> | ||
| - OAuth type: public | ||
| - Client ID: <public client id> | ||
|
|
||
| Integration: | ||
| - Name: <integration name> | ||
| - Integration ID: <integration id> | ||
| - Auth mode: <auth mode> | ||
| - Status: <created|updated|reused> | ||
| - App attachment: <attached_now|already_attached> | ||
|
|
||
| Runtime: | ||
| - Retrieval method: kontext.require(integration, token) | ||
| - First-time connect: hosted connect page | ||
|
|
||
| Env output: | ||
| - File: <only show when written> | ||
| - Variable: <only show when written> |
There was a problem hiding this comment.
[LOW][docs] Architectural finding
Impact: Can reduce maintainability or correctness if left unresolved.
Suggested fix: Apply a targeted refactor and add/adjust tests where needed.
Evidence: The documentation in SKILL.md and README.md is clear, comprehensive, and accurately describes the skill's functionality and usage.
Evidence citations:
- .claude/skills/kontext-token-mode-bootstrap/SKILL.md:1
- .claude/skills/kontext-token-mode-bootstrap/SKILL.md:1
7f6619c to
2b21575
Compare
There was a problem hiding this comment.
Verification certificate:
- Verdict: REQUEST_CHANGES
- Depth: lite
- Confidence: 0.75
- Certificate valid: yes
- Scenarios traced: 1
- Verification bounds:
- Changed files and explicitly traced call paths
Inline findings posted:
- [LOW][docs] Architectural finding (
.claude/skills/kontext-token-mode-bootstrap/SKILL.md:45)
Reviewer summary:
- Added an 'Execution Rules' section to explicitly define the order of operations for the skill.
- Reordered the 'Workflow' section to prioritize input validation and running the setup helper before scanning the repository for hardcoded credentials.
Scores and diagnostics
- Architecture: 7.0/10
- Organization: 7.0/10
- Cleanliness: 7.0/10
- Overall: 7.0/10
- Candidate findings: 2
- Published inline: 1
- Published summary-only: 0
- Suppressed findings: 1
- Verification verdict: REQUEST_CHANGES
Limitations
-
Verification is limited to the semantic review of the markdown documentation changes.
-
No runtime code execution paths were traced as the changes are purely instructional for a Claude skill.
-
Model:
gemini-3.1-pro-preview -
Omitted files: 0
Generated at 2026-03-18T18:17:19.232Z.
| ## Execution Rules | ||
|
|
||
| Follow these rules in order: | ||
|
|
||
| 1. Validate bootstrap inputs first. | ||
| 2. If `KONTEXT_SERVICE_ACCOUNT_CLIENT_ID` or `KONTEXT_SERVICE_ACCOUNT_CLIENT_SECRET` is missing, stop immediately and tell the user exactly which variable is missing. | ||
| 3. If `KONTEXT_OUTPUT_ENV_FILE` is missing, infer a conventional ignored local env target when the repo already clearly uses one, for example `apps/web/.env.local`, and pass it inline when running the helper. | ||
| 4. If the user names an existing integration such as `google-workspace`, treat that as **reuse and attach first**. Do not try to invent provider templates or broad management recipes unless the user explicitly asked for integration creation details. | ||
| 5. Run the bundled helper from the installed skill directly. Do not vendor or copy the helper into the target repo. | ||
| 6. Keep repo exploration narrow until bootstrap is done or blocked. Do not pivot into unrelated examples, demos, or legacy integrations just because they mention the same provider. | ||
| 7. After bootstrap succeeds, edit the target runtime surface and remove the hardcoded credential path. | ||
|
|
There was a problem hiding this comment.
[LOW][docs] Architectural finding
Impact: Can reduce maintainability or correctness if left unresolved.
Suggested fix: Apply a targeted refactor and add/adjust tests where needed.
Evidence: The PR introduces a new 'Execution Rules' section that explicitly defines the order of operations, such as validating inputs first, stopping if service account credentials are missing, inferring the output env file, and reusing existing integrations.
Evidence citations:
- .claude/skills/kontext-token-mode-bootstrap/SKILL.md:45:56
There was a problem hiding this comment.
Verification certificate:
- Verdict: REQUEST_CHANGES
- Depth: lite
- Confidence: 0.75
- Certificate valid: yes
- Scenarios traced: 1
- Verification bounds:
- Changed files and explicitly traced call paths
Inline findings posted:
- [LOW][maintainability] Architectural finding (
.claude/skills/kontext-token-mode-bootstrap/scripts/bootstrap-token-mode.mjs:446)
Reviewer summary:
- Renamed integration to provider across the bootstrap script and documentation.
- Added support for preset and custom provider types.
- Updated SKILL.md to reflect the new provider terminology and environment variables.
Scores and diagnostics
- Architecture: 7.0/10
- Organization: 7.0/10
- Cleanliness: 7.0/10
- Overall: 7.0/10
- Candidate findings: 1
- Published inline: 1
- Published summary-only: 0
- Suppressed findings: 0
- Verification verdict: REQUEST_CHANGES
Limitations
-
The diff for
bootstrap-token-mode.mjsis truncated, so the mapping of environment variables tobuildDesiredProviderarguments could not be fully verified. -
Model:
gemini-3.1-pro-preview -
Omitted files: 0
Generated at 2026-03-18T18:36:37.112Z.
| scopes: oauthScopes, | ||
| }), | ||
| serverToken: resolvedServerToken, | ||
| return { |
There was a problem hiding this comment.
[LOW][maintainability] Architectural finding
Impact: Can reduce maintainability or correctness if left unresolved.
Suggested fix: Apply a targeted refactor and add/adjust tests where needed.
Evidence: The indentation in buildDesiredProvider for the create object is slightly misaligned, but it is valid JavaScript.
Evidence citations:
- .claude/skills/kontext-token-mode-bootstrap/scripts/bootstrap-token-mode.mjs:446
Summary
kontext-token-mode-bootstrapskill under.claude/skillsrequireProvider(providerHandle, token)Validation
node --check .claude/skills/kontext-token-mode-bootstrap/scripts/bootstrap-token-mode.mjshttp://localhost:4000Runtime Demopublic app, reuses or updates thegoogle-workspaceprovider, and writes the public client ID env output