From 353cb8ef64a01d8d4c4d9acf8a373eedd9a23026 Mon Sep 17 00:00:00 2001 From: Christian Glassiognon <63924603+heyglassy@users.noreply.github.com> Date: Mon, 18 Aug 2025 12:08:37 -0700 Subject: [PATCH 01/21] initial test --- .../use-bun-instead-of-node-vite-npm-pnpm.mdc | 1 + .github/scripts/.gitignore | 34 +++++ .github/scripts/CLAUDE.md | 111 ++++++++++++++++ .github/scripts/README.md | 15 +++ .github/scripts/bun.lock | 39 ++++++ .github/scripts/generate_code_samples.ts | 124 ++++++++++++++++++ .github/scripts/package.json | 14 ++ .github/scripts/tsconfig.json | 29 ++++ apps/invoke.mdx | 65 +++------ 9 files changed, 383 insertions(+), 49 deletions(-) create mode 120000 .github/scripts/.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc create mode 100644 .github/scripts/.gitignore create mode 100644 .github/scripts/CLAUDE.md create mode 100644 .github/scripts/README.md create mode 100644 .github/scripts/bun.lock create mode 100644 .github/scripts/generate_code_samples.ts create mode 100644 .github/scripts/package.json create mode 100644 .github/scripts/tsconfig.json diff --git a/.github/scripts/.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc b/.github/scripts/.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc new file mode 120000 index 0000000..6100270 --- /dev/null +++ b/.github/scripts/.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc @@ -0,0 +1 @@ +../../CLAUDE.md \ No newline at end of file diff --git a/.github/scripts/.gitignore b/.github/scripts/.gitignore new file mode 100644 index 0000000..a14702c --- /dev/null +++ b/.github/scripts/.gitignore @@ -0,0 +1,34 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/.github/scripts/CLAUDE.md b/.github/scripts/CLAUDE.md new file mode 100644 index 0000000..b8100b7 --- /dev/null +++ b/.github/scripts/CLAUDE.md @@ -0,0 +1,111 @@ +--- +description: Use Bun instead of Node.js, npm, pnpm, or vite. +globs: "*.ts, *.tsx, *.html, *.css, *.js, *.jsx, package.json" +alwaysApply: false +--- + +Default to using Bun instead of Node.js. + +- Use `bun ` instead of `node ` or `ts-node ` +- Use `bun test` instead of `jest` or `vitest` +- Use `bun build ` instead of `webpack` or `esbuild` +- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install` +- Use `bun run + + +``` + +With the following `frontend.tsx`: + +```tsx#frontend.tsx +import React from "react"; + +// import .css files directly and it works +import './index.css'; + +import { createRoot } from "react-dom/client"; + +const root = createRoot(document.body); + +export default function Frontend() { + return

Hello, world!

; +} + +root.render(); +``` + +Then, run index.ts + +```sh +bun --hot ./index.ts +``` + +For more information, read the Bun API docs in `node_modules/bun-types/docs/**.md`. diff --git a/.github/scripts/README.md b/.github/scripts/README.md new file mode 100644 index 0000000..0444459 --- /dev/null +++ b/.github/scripts/README.md @@ -0,0 +1,15 @@ +# scripts + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run +``` + +This project was created using `bun init` in bun v1.2.17. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. diff --git a/.github/scripts/bun.lock b/.github/scripts/bun.lock new file mode 100644 index 0000000..cc1bb9d --- /dev/null +++ b/.github/scripts/bun.lock @@ -0,0 +1,39 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "scripts", + "dependencies": { + "@types/js-yaml": "^4.0.9", + "js-yaml": "^4.1.0", + }, + "devDependencies": { + "@types/bun": "latest", + }, + "peerDependencies": { + "typescript": "^5", + }, + }, + }, + "packages": { + "@types/bun": ["@types/bun@1.2.20", "", { "dependencies": { "bun-types": "1.2.20" } }, "sha512-dX3RGzQ8+KgmMw7CsW4xT5ITBSCrSbfHc36SNT31EOUg/LA9JWq0VDdEXDRSe1InVWpd2yLUM1FUF/kEOyTzYA=="], + + "@types/js-yaml": ["@types/js-yaml@4.0.9", "", {}, "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg=="], + + "@types/node": ["@types/node@24.3.0", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow=="], + + "@types/react": ["@types/react@19.1.10", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-EhBeSYX0Y6ye8pNebpKrwFJq7BoQ8J5SO6NlvNwwHjSj6adXJViPQrKlsyPw7hLBLvckEMO1yxeGdR82YBBlDg=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "bun-types": ["bun-types@1.2.20", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-pxTnQYOrKvdOwyiyd/7sMt9yFOenN004Y6O4lCcCUoKVej48FS5cvTw9geRaEcB9TsDZaJKAxPTVvi8tFsVuXA=="], + + "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + + "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="], + + "typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="], + + "undici-types": ["undici-types@7.10.0", "", {}, "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="], + } +} diff --git a/.github/scripts/generate_code_samples.ts b/.github/scripts/generate_code_samples.ts new file mode 100644 index 0000000..6eb5633 --- /dev/null +++ b/.github/scripts/generate_code_samples.ts @@ -0,0 +1,124 @@ +#!/usr/bin/env bun + +import { readdir, readFile, writeFile } from "fs/promises"; +import path from "path"; +import yaml from "js-yaml"; // for YAML support + +const OPENAPI_URL = process.env.OPENAPI_URL; +if (!OPENAPI_URL) { + console.error("❌ Missing OPENAPI_URL environment variable"); + process.exit(1); +} + +const TARGET_DIRS = ["browsers", "apps"]; // adjust as needed + +function toTitleCase(input: string): string { + if (!input) return ""; + return input.charAt(0).toUpperCase() + input.slice(1).toLowerCase(); +} + +async function fetchOpenAPISpec() { + console.log(`📥 Fetching OpenAPI spec from ${OPENAPI_URL}`); + const res = await fetch(OPENAPI_URL!); + if (!res.ok) + throw new Error(`Failed to fetch OpenAPI spec: ${res.statusText}`); + const text = await res.text(); + + // Try JSON first, fallback to YAML + try { + return JSON.parse(text); + } catch { + return yaml.load(text); + } +} + +async function getMdxFiles(dir: string): Promise { + const entries = await readdir(dir, { withFileTypes: true }); + const files: string[] = []; + for (const entry of entries) { + const fullPath = path.join(dir, entry.name); + if (entry.isDirectory()) { + files.push(...(await getMdxFiles(fullPath))); + } else if (entry.isFile() && entry.name.endsWith(".mdx")) { + files.push(fullPath); + } + } + return files; +} + +function extractCodeSamples( + spec: any, + endpoint: string, + method?: string +): string { + const pathItem = spec.paths?.[endpoint]; + if (!pathItem) return `⚠️ No spec found for ${endpoint}`; + + const blocks: string[] = []; + + const methods = method ? [method.toLowerCase()] : Object.keys(pathItem); // all methods if not specified + + for (const m of methods) { + const op = pathItem[m]; + if (op?.["x-codeSamples"]) { + for (const sample of op["x-codeSamples"]) { + const rawLang = typeof sample.lang === "string" ? sample.lang : ""; + const lang = rawLang.toLowerCase(); + + let fenceInfo: string; + if (["typescript", "ts", "javascript", "js"].includes(lang)) { + fenceInfo = "typescript Typescript"; + } else if (rawLang) { + fenceInfo = `${lang} ${toTitleCase(rawLang)}`; + } else { + fenceInfo = ""; + } + + blocks.push(`\n\`\`\`${fenceInfo}\n${sample.source.trim()}\n\`\`\`\n`); + } + } + } + + return blocks.length > 0 + ? blocks.join("\n") + : `⚠️ No code samples for ${ + method ? method.toUpperCase() : "any" + } ${endpoint}`; +} + +async function processFile(file: string, spec: any) { + let content = await readFile(file, "utf8"); + + // Matches {{ get /path }} or {{ post /path }} or {{ /path }} + const regex = + /\{\{\s*(?:(get|post|put|delete|patch|options|head)\s+)?(\/[^\s}]+)\s*\}\}/gi; + + let changed = false; + content = content.replace(regex, (_, method, endpoint) => { + changed = true; + return extractCodeSamples(spec, endpoint, method); + }); + + if (changed) { + console.log(`✏️ Updating ${file}`); + await writeFile(file, content, "utf8"); + } +} + +async function main() { + const spec = await fetchOpenAPISpec(); + + for (const dir of TARGET_DIRS) { + const files = await getMdxFiles(dir); + for (const file of files) { + await processFile(file, spec); + } + } + + console.log("✅ Done updating docs"); +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/.github/scripts/package.json b/.github/scripts/package.json new file mode 100644 index 0000000..769e60e --- /dev/null +++ b/.github/scripts/package.json @@ -0,0 +1,14 @@ +{ + "name": "scripts", + "private": true, + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5" + }, + "dependencies": { + "@types/js-yaml": "^4.0.9", + "js-yaml": "^4.1.0" + } +} diff --git a/.github/scripts/tsconfig.json b/.github/scripts/tsconfig.json new file mode 100644 index 0000000..bfa0fea --- /dev/null +++ b/.github/scripts/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + // Environment setup & latest features + "lib": ["ESNext"], + "target": "ESNext", + "module": "Preserve", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +} diff --git a/apps/invoke.mdx b/apps/invoke.mdx index 5810ca6..9fbd699 100644 --- a/apps/invoke.mdx +++ b/apps/invoke.mdx @@ -4,52 +4,16 @@ title: "Invoking" ## Via API -You can invoke your app by making a `POST` request to Kernel's API. **For automations and agents that take longer than 100 seconds, use [async invocations](/apps/invoke#asynchronous-invocations).** +You can invoke your app by making a `POST` request to Kernel's API. **For automations and agents that take longer than 100 seconds, use [async invocations](/apps/invoke#asynchronous-invocations).** - - Synchronous invocations time out after 100 seconds. - +Synchronous invocations time out after 100 seconds. -```typescript Typescript/Javascript -import { Kernel } from '@onkernel/sdk'; - -const { - id, - status, - status_reason, - output -} = await Kernel.invocations.create({ - app_name: app_name, - action_name: action_name, - version: "latest" -}); -``` - -```python Python -import kernel - -result = kernel.invocations.create({ - "app_name": app_name, - "action_name": action_name, - "version": "latest" -}) -invocation_id, status, status_reason, output = ( - result["id"], - result["status"], - result["status_reason"], - result["output"] -) - result["id"], - result["status"], - result["status_reason"], - result["output"] -) -``` - +{{ post /invocations }} ### Asynchronous invocations + For long running jobs, use asynchronous invocations to trigger Kernel actions without waiting for the result. You can then poll its [status](/apps/status) for the result. @@ -57,15 +21,16 @@ For long running jobs, use asynchronous invocations to trigger Kernel actions wi import { Kernel } from '@onkernel/sdk'; const { - id, - status, // status will be QUEUED +id, +status, // status will be QUEUED } = await Kernel.invocations.create({ - app_name: app_name, - action_name: action_name, - version: "latest", - async: true, +app_name: app_name, +action_name: action_name, +version: "latest", +async: true, }); -``` + +```` ```python Python import kernel @@ -83,7 +48,7 @@ invocation_id, status = ( result["id"], result["status"] # status will be QUEUED ) -``` +```` @@ -96,9 +61,11 @@ kernel invoke ``` ### Payload parameter + `--payload` allows you to invoke the action with specified parameters. This enables your action to receive and handle dynamic inputs at runtime. For example: + ```bash -kernel invoke +kernel invoke --payload '{"tshirt_size": "small", "color": "black", "shipping_address": "2 Mint Plz, San Francisco CA 94103"}' ``` From e106ba45ca834735e2038b752949cc4671c22851 Mon Sep 17 00:00:00 2001 From: Christian Glassiognon <63924603+heyglassy@users.noreply.github.com> Date: Mon, 18 Aug 2025 12:12:35 -0700 Subject: [PATCH 02/21] test --- .github/generate_code_snippets.yaml | 38 ++++++++++++++++++++++++ .github/scripts/generate_code_samples.ts | 13 ++++---- 2 files changed, 46 insertions(+), 5 deletions(-) create mode 100644 .github/generate_code_snippets.yaml diff --git a/.github/generate_code_snippets.yaml b/.github/generate_code_snippets.yaml new file mode 100644 index 0000000..8ceaa0a --- /dev/null +++ b/.github/generate_code_snippets.yaml @@ -0,0 +1,38 @@ +name: Update Docs with Code Samples from OpenAPI + +on: + push: + branches: + - christian/docs_deployment_step_for_gen + +permissions: + contents: write # allow pushing commits back to the PR branch + +jobs: + update-docs: + runs-on: ubuntu-latest + env: + OPENAPI_URL: ${{ secrets.OPENAPI_URL }} # set this in repo secrets + steps: + - name: Checkout PR branch + uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref }} + fetch-depth: 0 + + - name: Install Bun + uses: oven-sh/setup-bun@v1 + + - name: Install dependencies + run: bun install + + - name: Run update script + run: bun run scripts/generate_code_samples.ts + + - name: Commit changes + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add . + git commit -m "docs: update code samples from OpenAPI" || echo "No changes" + git push origin HEAD:${{ github.head_ref }} \ No newline at end of file diff --git a/.github/scripts/generate_code_samples.ts b/.github/scripts/generate_code_samples.ts index 6eb5633..f1e9b3e 100644 --- a/.github/scripts/generate_code_samples.ts +++ b/.github/scripts/generate_code_samples.ts @@ -4,11 +4,14 @@ import { readdir, readFile, writeFile } from "fs/promises"; import path from "path"; import yaml from "js-yaml"; // for YAML support -const OPENAPI_URL = process.env.OPENAPI_URL; -if (!OPENAPI_URL) { - console.error("❌ Missing OPENAPI_URL environment variable"); - process.exit(1); -} +// const OPENAPI_URL = process.env.OPENAPI_URL; +// if (!OPENAPI_URL) { +// console.error("❌ Missing OPENAPI_URL environment variable"); +// process.exit(1); +// } + +const OPENAPI_URL = + "https://app.stainless.com/api/spec/documented/kernel/openapi.documented.yml"; const TARGET_DIRS = ["browsers", "apps"]; // adjust as needed From 0f46884416245d9acb73feb30bc58a7b5aa137be Mon Sep 17 00:00:00 2001 From: Christian Glassiognon <63924603+heyglassy@users.noreply.github.com> Date: Mon, 18 Aug 2025 12:13:30 -0700 Subject: [PATCH 03/21] test --- .github/generate_code_snippets.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/generate_code_snippets.yaml b/.github/generate_code_snippets.yaml index 8ceaa0a..ba878d4 100644 --- a/.github/generate_code_snippets.yaml +++ b/.github/generate_code_snippets.yaml @@ -11,8 +11,6 @@ permissions: jobs: update-docs: runs-on: ubuntu-latest - env: - OPENAPI_URL: ${{ secrets.OPENAPI_URL }} # set this in repo secrets steps: - name: Checkout PR branch uses: actions/checkout@v4 From 01880bc6c16a172d436e34446fdc320e18123fd6 Mon Sep 17 00:00:00 2001 From: Christian Glassiognon <63924603+heyglassy@users.noreply.github.com> Date: Mon, 18 Aug 2025 12:15:38 -0700 Subject: [PATCH 04/21] gippity --- .github/{ => workflows}/generate_code_snippets.yaml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) rename .github/{ => workflows}/generate_code_snippets.yaml (73%) diff --git a/.github/generate_code_snippets.yaml b/.github/workflows/generate_code_snippets.yaml similarity index 73% rename from .github/generate_code_snippets.yaml rename to .github/workflows/generate_code_snippets.yaml index ba878d4..34f7ea2 100644 --- a/.github/generate_code_snippets.yaml +++ b/.github/workflows/generate_code_snippets.yaml @@ -3,19 +3,20 @@ name: Update Docs with Code Samples from OpenAPI on: push: branches: - - christian/docs_deployment_step_for_gen + - "christian/docs_deployment_step_for_gen" permissions: - contents: write # allow pushing commits back to the PR branch + contents: write jobs: update-docs: runs-on: ubuntu-latest + if: github.actor != 'github-actions[bot]' # prevent infinite loop steps: - - name: Checkout PR branch + - name: Checkout branch uses: actions/checkout@v4 with: - ref: ${{ github.head_ref }} + ref: ${{ github.ref_name }} fetch-depth: 0 - name: Install Bun @@ -33,4 +34,4 @@ jobs: git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git add . git commit -m "docs: update code samples from OpenAPI" || echo "No changes" - git push origin HEAD:${{ github.head_ref }} \ No newline at end of file + git push origin HEAD:${{ github.ref_name }} \ No newline at end of file From 5b85b2ad8576b386fff41e95298afa285eefc189 Mon Sep 17 00:00:00 2001 From: Christian Glassiognon <63924603+heyglassy@users.noreply.github.com> Date: Mon, 18 Aug 2025 12:16:32 -0700 Subject: [PATCH 05/21] bun install at runtime --- .github/workflows/generate_code_snippets.yaml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/generate_code_snippets.yaml b/.github/workflows/generate_code_snippets.yaml index 34f7ea2..fbef38e 100644 --- a/.github/workflows/generate_code_snippets.yaml +++ b/.github/workflows/generate_code_snippets.yaml @@ -22,9 +22,6 @@ jobs: - name: Install Bun uses: oven-sh/setup-bun@v1 - - name: Install dependencies - run: bun install - - name: Run update script run: bun run scripts/generate_code_samples.ts @@ -34,4 +31,4 @@ jobs: git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git add . git commit -m "docs: update code samples from OpenAPI" || echo "No changes" - git push origin HEAD:${{ github.ref_name }} \ No newline at end of file + git push origin HEAD:${{ github.ref_name }} From cd2b6a8ab8afc72a05a58f90c2214c4f7cbdfdab Mon Sep 17 00:00:00 2001 From: Christian Glassiognon <63924603+heyglassy@users.noreply.github.com> Date: Mon, 18 Aug 2025 12:16:57 -0700 Subject: [PATCH 06/21] mod not found --- .github/workflows/generate_code_snippets.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/generate_code_snippets.yaml b/.github/workflows/generate_code_snippets.yaml index fbef38e..f419bf4 100644 --- a/.github/workflows/generate_code_snippets.yaml +++ b/.github/workflows/generate_code_snippets.yaml @@ -23,7 +23,7 @@ jobs: uses: oven-sh/setup-bun@v1 - name: Run update script - run: bun run scripts/generate_code_samples.ts + run: bun run .github/scripts/generate_code_samples.ts - name: Commit changes run: | From ab503e0c4288ad2e26ec5f8110a1ef503b30928f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 18 Aug 2025 19:17:12 +0000 Subject: [PATCH 07/21] docs: update code samples from OpenAPI --- apps/invoke.mdx | 61 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/apps/invoke.mdx b/apps/invoke.mdx index 9fbd699..7788cc3 100644 --- a/apps/invoke.mdx +++ b/apps/invoke.mdx @@ -9,7 +9,66 @@ You can invoke your app by making a `POST` request to Kernel's API. **For automa Synchronous invocations time out after 100 seconds. -{{ post /invocations }} + +```typescript Typescript +import Kernel from '@onkernel/sdk'; + +const client = new Kernel({ + apiKey: 'My API Key', +}); + +const invocation = await client.invocations.create({ + action_name: 'analyze', + app_name: 'my-app', + version: '1.0.0', +}); + +console.log(invocation.id); +``` + + +```python Python +from kernel import Kernel + +client = Kernel( + api_key="My API Key", +) +invocation = client.invocations.create( + action_name="analyze", + app_name="my-app", + version="1.0.0", +) +print(invocation.id) +``` + + +```go Go +package main + +import ( + "context" + "fmt" + + "github.com/onkernel/kernel-go-sdk" + "github.com/onkernel/kernel-go-sdk/option" +) + +func main() { + client := kernel.NewClient( + option.WithAPIKey("My API Key"), + ) + invocation, err := client.Invocations.New(context.TODO(), kernel.InvocationNewParams{ + ActionName: "analyze", + AppName: "my-app", + Version: "1.0.0", + }) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", invocation.ID) +} +``` + ### Asynchronous invocations From 72b347268fd69f041294f72c9b8d32e67af49edf Mon Sep 17 00:00:00 2001 From: Christian Glassiognon <63924603+heyglassy@users.noreply.github.com> Date: Mon, 18 Aug 2025 12:29:53 -0700 Subject: [PATCH 08/21] codegen done --- README.md | 21 +++++++++++++++-- apps/invoke.mdx | 61 +------------------------------------------------ 2 files changed, 20 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index ac7552c..c3dee72 100644 --- a/README.md +++ b/README.md @@ -7,13 +7,30 @@ Follow @rfgarcia

- This is the documentation for the Kernel platform. It's connected to [docs.onkernel.com](https://docs.onkernel.com). +## Code Snippets + +In order to sync our code snippets in our docs ( not playground ) with the OpenAPI spec, we use a GitHub Action. You can run it locally to see the changes that will be made. + +```bash +bun run .github/scripts/generate_code_samples.ts +``` + +When deploying, we generate the changes in a github action and push that to a branch called `gh_action_generated_docs`. Mintlify will then deploy the docs from that branch. + +## Local Development + +To run the docs locally, you can use the following command: + +```bash +mintlify dev +``` + ## Contributing We welcome contributions to the documentation. Please feel free to submit a pull request with your changes. See [CONTRIBUTING.md](CONTRIBUTING.md) and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) for more details. ## License -This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details. \ No newline at end of file +This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details. diff --git a/apps/invoke.mdx b/apps/invoke.mdx index 7788cc3..9fbd699 100644 --- a/apps/invoke.mdx +++ b/apps/invoke.mdx @@ -9,66 +9,7 @@ You can invoke your app by making a `POST` request to Kernel's API. **For automa Synchronous invocations time out after 100 seconds. - -```typescript Typescript -import Kernel from '@onkernel/sdk'; - -const client = new Kernel({ - apiKey: 'My API Key', -}); - -const invocation = await client.invocations.create({ - action_name: 'analyze', - app_name: 'my-app', - version: '1.0.0', -}); - -console.log(invocation.id); -``` - - -```python Python -from kernel import Kernel - -client = Kernel( - api_key="My API Key", -) -invocation = client.invocations.create( - action_name="analyze", - app_name="my-app", - version="1.0.0", -) -print(invocation.id) -``` - - -```go Go -package main - -import ( - "context" - "fmt" - - "github.com/onkernel/kernel-go-sdk" - "github.com/onkernel/kernel-go-sdk/option" -) - -func main() { - client := kernel.NewClient( - option.WithAPIKey("My API Key"), - ) - invocation, err := client.Invocations.New(context.TODO(), kernel.InvocationNewParams{ - ActionName: "analyze", - AppName: "my-app", - Version: "1.0.0", - }) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", invocation.ID) -} -``` - +{{ post /invocations }} ### Asynchronous invocations From 39ac73307f47ff0430343d22da6b89b994cdb46b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 18 Aug 2025 19:30:05 +0000 Subject: [PATCH 09/21] docs: update code samples from OpenAPI --- apps/invoke.mdx | 61 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/apps/invoke.mdx b/apps/invoke.mdx index 9fbd699..7788cc3 100644 --- a/apps/invoke.mdx +++ b/apps/invoke.mdx @@ -9,7 +9,66 @@ You can invoke your app by making a `POST` request to Kernel's API. **For automa Synchronous invocations time out after 100 seconds. -{{ post /invocations }} + +```typescript Typescript +import Kernel from '@onkernel/sdk'; + +const client = new Kernel({ + apiKey: 'My API Key', +}); + +const invocation = await client.invocations.create({ + action_name: 'analyze', + app_name: 'my-app', + version: '1.0.0', +}); + +console.log(invocation.id); +``` + + +```python Python +from kernel import Kernel + +client = Kernel( + api_key="My API Key", +) +invocation = client.invocations.create( + action_name="analyze", + app_name="my-app", + version="1.0.0", +) +print(invocation.id) +``` + + +```go Go +package main + +import ( + "context" + "fmt" + + "github.com/onkernel/kernel-go-sdk" + "github.com/onkernel/kernel-go-sdk/option" +) + +func main() { + client := kernel.NewClient( + option.WithAPIKey("My API Key"), + ) + invocation, err := client.Invocations.New(context.TODO(), kernel.InvocationNewParams{ + ActionName: "analyze", + AppName: "my-app", + Version: "1.0.0", + }) + if err != nil { + panic(err.Error()) + } + fmt.Printf("%+v\n", invocation.ID) +} +``` + ### Asynchronous invocations From bece7c025bc1c48ff623056c58696e30a612b1d6 Mon Sep 17 00:00:00 2001 From: Christian Glassiognon <63924603+heyglassy@users.noreply.github.com> Date: Mon, 18 Aug 2025 12:30:56 -0700 Subject: [PATCH 10/21] one more thing --- .github/scripts/generate_code_samples.ts | 6 -- .github/workflows/generate_code_snippets.yaml | 6 +- apps/invoke.mdx | 61 +------------------ 3 files changed, 3 insertions(+), 70 deletions(-) diff --git a/.github/scripts/generate_code_samples.ts b/.github/scripts/generate_code_samples.ts index f1e9b3e..9163c2b 100644 --- a/.github/scripts/generate_code_samples.ts +++ b/.github/scripts/generate_code_samples.ts @@ -4,12 +4,6 @@ import { readdir, readFile, writeFile } from "fs/promises"; import path from "path"; import yaml from "js-yaml"; // for YAML support -// const OPENAPI_URL = process.env.OPENAPI_URL; -// if (!OPENAPI_URL) { -// console.error("❌ Missing OPENAPI_URL environment variable"); -// process.exit(1); -// } - const OPENAPI_URL = "https://app.stainless.com/api/spec/documented/kernel/openapi.documented.yml"; diff --git a/.github/workflows/generate_code_snippets.yaml b/.github/workflows/generate_code_snippets.yaml index f419bf4..e5faf84 100644 --- a/.github/workflows/generate_code_snippets.yaml +++ b/.github/workflows/generate_code_snippets.yaml @@ -3,7 +3,7 @@ name: Update Docs with Code Samples from OpenAPI on: push: branches: - - "christian/docs_deployment_step_for_gen" + - main permissions: contents: write @@ -27,8 +27,6 @@ jobs: - name: Commit changes run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git add . git commit -m "docs: update code samples from OpenAPI" || echo "No changes" - git push origin HEAD:${{ github.ref_name }} + git push origin HEAD:gh_action_generated_docs diff --git a/apps/invoke.mdx b/apps/invoke.mdx index 7788cc3..9fbd699 100644 --- a/apps/invoke.mdx +++ b/apps/invoke.mdx @@ -9,66 +9,7 @@ You can invoke your app by making a `POST` request to Kernel's API. **For automa Synchronous invocations time out after 100 seconds. - -```typescript Typescript -import Kernel from '@onkernel/sdk'; - -const client = new Kernel({ - apiKey: 'My API Key', -}); - -const invocation = await client.invocations.create({ - action_name: 'analyze', - app_name: 'my-app', - version: '1.0.0', -}); - -console.log(invocation.id); -``` - - -```python Python -from kernel import Kernel - -client = Kernel( - api_key="My API Key", -) -invocation = client.invocations.create( - action_name="analyze", - app_name="my-app", - version="1.0.0", -) -print(invocation.id) -``` - - -```go Go -package main - -import ( - "context" - "fmt" - - "github.com/onkernel/kernel-go-sdk" - "github.com/onkernel/kernel-go-sdk/option" -) - -func main() { - client := kernel.NewClient( - option.WithAPIKey("My API Key"), - ) - invocation, err := client.Invocations.New(context.TODO(), kernel.InvocationNewParams{ - ActionName: "analyze", - AppName: "my-app", - Version: "1.0.0", - }) - if err != nil { - panic(err.Error()) - } - fmt.Printf("%+v\n", invocation.ID) -} -``` - +{{ post /invocations }} ### Asynchronous invocations From 64e0b9f1e09e8c2edf7da1a760dcf23e8d1ee3c1 Mon Sep 17 00:00:00 2001 From: Christian Glassiognon <63924603+heyglassy@users.noreply.github.com> Date: Mon, 18 Aug 2025 12:38:17 -0700 Subject: [PATCH 11/21] better syntax --- .github/scripts/generate_code_samples.ts | 15 +++++++++++++-- apps/invoke.mdx | 4 +--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/.github/scripts/generate_code_samples.ts b/.github/scripts/generate_code_samples.ts index 9163c2b..3c3201f 100644 --- a/.github/scripts/generate_code_samples.ts +++ b/.github/scripts/generate_code_samples.ts @@ -87,15 +87,26 @@ async function processFile(file: string, spec: any) { let content = await readFile(file, "utf8"); // Matches {{ get /path }} or {{ post /path }} or {{ /path }} - const regex = + const mustacheRegex = /\{\{\s*(?:(get|post|put|delete|patch|options|head)\s+)?(\/[^\s}]+)\s*\}\}/gi; + // Matches get /path + // or /path + const tagRegex = + /\s*(?:(get|post|put|delete|patch|options|head)\s+)?(\/[^\s<]+)\s*<\/OpenAPICodeGroup>/gi; + let changed = false; - content = content.replace(regex, (_, method, endpoint) => { + content = content.replace(mustacheRegex, (_, method, endpoint) => { changed = true; return extractCodeSamples(spec, endpoint, method); }); + content = content.replace(tagRegex, (_, method, endpoint) => { + changed = true; + const blocks = extractCodeSamples(spec, endpoint, method).trim(); + return `\n${blocks}\n`; + }); + if (changed) { console.log(`✏️ Updating ${file}`); await writeFile(file, content, "utf8"); diff --git a/apps/invoke.mdx b/apps/invoke.mdx index 9fbd699..a219679 100644 --- a/apps/invoke.mdx +++ b/apps/invoke.mdx @@ -8,9 +8,7 @@ You can invoke your app by making a `POST` request to Kernel's API. **For automa Synchronous invocations time out after 100 seconds. - -{{ post /invocations }} - +post /invocations ### Asynchronous invocations From 4baef961ec4b869a6ab0ca3f00152c2cc2ee0d5f Mon Sep 17 00:00:00 2001 From: Christian Glassiognon <63924603+heyglassy@users.noreply.github.com> Date: Mon, 18 Aug 2025 12:40:40 -0700 Subject: [PATCH 12/21] clean --- .../use-bun-instead-of-node-vite-npm-pnpm.mdc | 1 - .github/scripts/CLAUDE.md | 111 ------------------ .github/scripts/README.md | 15 --- 3 files changed, 127 deletions(-) delete mode 120000 .github/scripts/.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc delete mode 100644 .github/scripts/CLAUDE.md delete mode 100644 .github/scripts/README.md diff --git a/.github/scripts/.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc b/.github/scripts/.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc deleted file mode 120000 index 6100270..0000000 --- a/.github/scripts/.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc +++ /dev/null @@ -1 +0,0 @@ -../../CLAUDE.md \ No newline at end of file diff --git a/.github/scripts/CLAUDE.md b/.github/scripts/CLAUDE.md deleted file mode 100644 index b8100b7..0000000 --- a/.github/scripts/CLAUDE.md +++ /dev/null @@ -1,111 +0,0 @@ ---- -description: Use Bun instead of Node.js, npm, pnpm, or vite. -globs: "*.ts, *.tsx, *.html, *.css, *.js, *.jsx, package.json" -alwaysApply: false ---- - -Default to using Bun instead of Node.js. - -- Use `bun ` instead of `node ` or `ts-node ` -- Use `bun test` instead of `jest` or `vitest` -- Use `bun build ` instead of `webpack` or `esbuild` -- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install` -- Use `bun run - - -``` - -With the following `frontend.tsx`: - -```tsx#frontend.tsx -import React from "react"; - -// import .css files directly and it works -import './index.css'; - -import { createRoot } from "react-dom/client"; - -const root = createRoot(document.body); - -export default function Frontend() { - return

Hello, world!

; -} - -root.render(); -``` - -Then, run index.ts - -```sh -bun --hot ./index.ts -``` - -For more information, read the Bun API docs in `node_modules/bun-types/docs/**.md`. diff --git a/.github/scripts/README.md b/.github/scripts/README.md deleted file mode 100644 index 0444459..0000000 --- a/.github/scripts/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# scripts - -To install dependencies: - -```bash -bun install -``` - -To run: - -```bash -bun run -``` - -This project was created using `bun init` in bun v1.2.17. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. From 59ee225116a70beacc7eacb19dc7a801892f1825 Mon Sep 17 00:00:00 2001 From: Christian Glassiognon <63924603+heyglassy@users.noreply.github.com> Date: Mon, 18 Aug 2025 12:48:15 -0700 Subject: [PATCH 13/21] update docs --- README.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index c3dee72..8335771 100644 --- a/README.md +++ b/README.md @@ -11,13 +11,11 @@ This is the documentation for the Kernel platform. It's connected to [docs.onker ## Code Snippets -In order to sync our code snippets in our docs ( not playground ) with the OpenAPI spec, we use a GitHub Action. You can run it locally to see the changes that will be made. +In order to sync our code snippets in our docs ( not playground ) with the OpenAPI spec, we put `[get|put|post|delete] [path]` in the docs. -```bash -bun run .github/scripts/generate_code_samples.ts -``` +For example, if we want to add a code snippet for the `GET /api/v1/users` endpoint, we would put `get /api/v1/users` in the docs. -When deploying, we generate the changes in a github action and push that to a branch called `gh_action_generated_docs`. Mintlify will then deploy the docs from that branch. +We then use a GitHub Action to generate the code snippets from the docs and push them to the `gh_action_generated_docs` branch. Mintlify will then deploy the docs from that branch. ## Local Development From a538d02016478283b171f4ca0e02d70257f6c36b Mon Sep 17 00:00:00 2001 From: Mason Williams <43387599+masnwilliams@users.noreply.github.com> Date: Tue, 19 Aug 2025 12:48:25 -0400 Subject: [PATCH 14/21] feat(docs): auto-generate OpenAPI code snippets (#26) --- .github/scripts/code_samples.config.json | 7 + .github/scripts/generate_code_samples.ts | 343 +++++++++++++++++- apps/invoke.mdx | 41 +-- apps/status.mdx | 34 +- apps/stop.mdx | 16 +- browsers/create-a-browser.mdx | 26 +- browsers/live-view.mdx | 19 +- browsers/termination.mdx | 42 +-- .../delete-browsers-id-fs-watch-watch_id.mdx | 24 ++ snippets/openapi/delete-browsers-id.mdx | 23 ++ snippets/openapi/delete-browsers.mdx | 23 ++ .../delete-invocations-id-browsers.mdx | 23 ++ snippets/openapi/get-apps.mdx | 24 ++ .../openapi/get-browsers-id-fs-file_info.mdx | 27 ++ .../openapi/get-browsers-id-fs-list_files.mdx | 27 ++ .../openapi/get-browsers-id-fs-read_file.mdx | 32 ++ ...t-browsers-id-fs-watch-watch_id-events.mdx | 27 ++ .../get-browsers-id-replays-replay_id.mdx | 32 ++ snippets/openapi/get-browsers-id-replays.mdx | 26 ++ snippets/openapi/get-browsers-id.mdx | 26 ++ snippets/openapi/get-browsers.mdx | 24 ++ .../openapi/get-deployments-id-events.mdx | 26 ++ snippets/openapi/get-deployments-id.mdx | 26 ++ snippets/openapi/get-deployments.mdx | 24 ++ .../openapi/get-invocations-id-events.mdx | 26 ++ snippets/openapi/get-invocations-id.mdx | 26 ++ snippets/openapi/patch-invocations-id.mdx | 27 ++ .../openapi/post-browsers-id-fs-watch.mdx | 27 ++ ...ost-browsers-id-replays-replay_id-stop.mdx | 24 ++ snippets/openapi/post-browsers-id-replays.mdx | 26 ++ snippets/openapi/post-browsers.mdx | 24 ++ snippets/openapi/post-deployments.mdx | 30 ++ snippets/openapi/post-invocations-async.mdx | 34 ++ snippets/openapi/post-invocations.mdx | 32 ++ .../put-browsers-id-fs-create_directory.mdx | 24 ++ .../put-browsers-id-fs-delete_directory.mdx | 24 ++ .../put-browsers-id-fs-delete_file.mdx | 24 ++ snippets/openapi/put-browsers-id-fs-move.mdx | 25 ++ ...ut-browsers-id-fs-set_file_permissions.mdx | 25 ++ .../openapi/put-browsers-id-fs-write_file.mdx | 25 ++ 40 files changed, 1189 insertions(+), 176 deletions(-) create mode 100644 .github/scripts/code_samples.config.json create mode 100644 snippets/openapi/delete-browsers-id-fs-watch-watch_id.mdx create mode 100644 snippets/openapi/delete-browsers-id.mdx create mode 100644 snippets/openapi/delete-browsers.mdx create mode 100644 snippets/openapi/delete-invocations-id-browsers.mdx create mode 100644 snippets/openapi/get-apps.mdx create mode 100644 snippets/openapi/get-browsers-id-fs-file_info.mdx create mode 100644 snippets/openapi/get-browsers-id-fs-list_files.mdx create mode 100644 snippets/openapi/get-browsers-id-fs-read_file.mdx create mode 100644 snippets/openapi/get-browsers-id-fs-watch-watch_id-events.mdx create mode 100644 snippets/openapi/get-browsers-id-replays-replay_id.mdx create mode 100644 snippets/openapi/get-browsers-id-replays.mdx create mode 100644 snippets/openapi/get-browsers-id.mdx create mode 100644 snippets/openapi/get-browsers.mdx create mode 100644 snippets/openapi/get-deployments-id-events.mdx create mode 100644 snippets/openapi/get-deployments-id.mdx create mode 100644 snippets/openapi/get-deployments.mdx create mode 100644 snippets/openapi/get-invocations-id-events.mdx create mode 100644 snippets/openapi/get-invocations-id.mdx create mode 100644 snippets/openapi/patch-invocations-id.mdx create mode 100644 snippets/openapi/post-browsers-id-fs-watch.mdx create mode 100644 snippets/openapi/post-browsers-id-replays-replay_id-stop.mdx create mode 100644 snippets/openapi/post-browsers-id-replays.mdx create mode 100644 snippets/openapi/post-browsers.mdx create mode 100644 snippets/openapi/post-deployments.mdx create mode 100644 snippets/openapi/post-invocations-async.mdx create mode 100644 snippets/openapi/post-invocations.mdx create mode 100644 snippets/openapi/put-browsers-id-fs-create_directory.mdx create mode 100644 snippets/openapi/put-browsers-id-fs-delete_directory.mdx create mode 100644 snippets/openapi/put-browsers-id-fs-delete_file.mdx create mode 100644 snippets/openapi/put-browsers-id-fs-move.mdx create mode 100644 snippets/openapi/put-browsers-id-fs-set_file_permissions.mdx create mode 100644 snippets/openapi/put-browsers-id-fs-write_file.mdx diff --git a/.github/scripts/code_samples.config.json b/.github/scripts/code_samples.config.json new file mode 100644 index 0000000..e2ba10d --- /dev/null +++ b/.github/scripts/code_samples.config.json @@ -0,0 +1,7 @@ +{ + "variants": { + "post /invocations": [ + { "name": "async", "overrides": { "async": true } } + ] + } +} diff --git a/.github/scripts/generate_code_samples.ts b/.github/scripts/generate_code_samples.ts index 3c3201f..4df4c01 100644 --- a/.github/scripts/generate_code_samples.ts +++ b/.github/scripts/generate_code_samples.ts @@ -1,6 +1,6 @@ #!/usr/bin/env bun -import { readdir, readFile, writeFile } from "fs/promises"; +import { readdir, readFile, writeFile, mkdir, unlink } from "fs/promises"; import path from "path"; import yaml from "js-yaml"; // for YAML support @@ -8,12 +8,228 @@ const OPENAPI_URL = "https://app.stainless.com/api/spec/documented/kernel/openapi.documented.yml"; const TARGET_DIRS = ["browsers", "apps"]; // adjust as needed +const SNIPPETS_ROOT = path.resolve("snippets/openapi"); + +// Only emit code samples for these languages (normalized): e.g., ["typescript", "python"] +const TARGET_LANGUAGES = ["typescript", "python"] as const; +type SupportedLanguage = (typeof TARGET_LANGUAGES)[number] | "javascript" | "go"; + +function normalizeLang(input: string): SupportedLanguage { + const lc = (input || "").toLowerCase(); + if (lc === "ts" || lc === "typescript") return "typescript"; + if (lc === "py" || lc === "python") return "python"; + if (lc === "js" || lc === "javascript" || lc === "node" || lc === "node.js" || lc === "nodejs") return "javascript"; + if (lc === "go" || lc === "golang") return "go"; + return lc as SupportedLanguage; +} + +function renderFenceInfo(lang: SupportedLanguage, rawLang: string): string { + if (lang === "typescript" || lang === "javascript") return "typescript index.ts"; + if (lang === "python") return "python main.py"; + return `${lang} ${toTitleCase(rawLang)}`; +} + +function renderValueForLanguage(lang: SupportedLanguage, value: unknown): string { + const type = typeof value; + if (type === "boolean") { + const b = value as boolean; + return lang === "python" ? (b ? "True" : "False") : b ? "true" : "false"; + } + if (type === "number") return String(value); + if (type === "string") { + // Use double quotes across both for simplicity + return JSON.stringify(value); + } + // Fallback to JSON for objects/arrays; Python will be JSON-like which is acceptable for docs + try { + return JSON.stringify(value); + } catch { + return String(value); + } +} + +function injectParamsIntoObjectLiteral( + lang: SupportedLanguage, + objectLiteral: string, + params: Record +): string { + let updated = objectLiteral; + for (const [key, val] of Object.entries(params)) { + const valueStr = renderValueForLanguage(lang, val); + const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + // Try to replace existing key first (handles both quoted and unquoted keys) + const keyPatterns = [ + new RegExp(`(([,\{\n\r\t\s])${escapedKey}\\s*:\\s*)([^,\n}]+)`, "m"), // JS/TS style key: value + new RegExp(`(([,\{\n\r\t\s])\"${escapedKey}\"\\s*:\\s*)([^,\n}]+)`, "m"), // JSON/Python style "key": value + ]; + let replaced = false; + for (const pattern of keyPatterns) { + if (pattern.test(updated)) { + updated = updated.replace(pattern, `$1${valueStr}`); + replaced = true; + break; + } + } + if (!replaced) { + // Insert after the first opening brace + if (lang === "python") { + const insertion = `"${key}": ${valueStr}, `; + updated = updated.replace(/\{\s*/, (m) => m + insertion); + } else { + const isMultiline = /\{\s*\n/.test(updated); + const indentMatch = updated.match(/\{\s*\n([ \t]*)/); + const indent = indentMatch?.[1] ?? ""; + const insertion = isMultiline + ? `${indent}${key}: ${valueStr},\n${indent}` + : `${key}: ${valueStr}, `; + if (isMultiline) { + // Normalize to a single newline after '{' then insert our line + updated = updated.replace(/\{\s*/, '{\n'); + updated = updated.replace(/\{\n/, `{\n${insertion}`); + } else { + updated = updated.replace(/\{\s*/, (m) => m + insertion); + } + } + } + } + return updated; +} + +function injectParamsIntoPythonArgs( + argList: string, + params: Record +): string { + let updated = argList; + // Ensure trailing comma handling is clean; if arguments are on multiple lines, add newline before inserted kwarg + for (const [key, val] of Object.entries(params)) { + const valueStr = renderValueForLanguage("python", val); + const pattern = new RegExp(`(\\b${key}\\s*=)\\s*([^,\n)]+)`, "m"); + if (pattern.test(updated)) { + updated = updated.replace(pattern, `$1 ${valueStr}`); + } else { + const trimmed = updated.trim(); + if (trimmed.length === 0) { + updated = `${key}=${valueStr}`; + } else { + const isMultiline = /\n/.test(updated); + const hasTrailingComma = /,\s*$/.test(updated); + if (isMultiline) { + const firstArgIndentMatch = updated.match(/^([ \t]+)[A-Za-z_]/m); + const lastLineIndentMatch = updated.match(/\n([ \t]*)[^\n]*$/); + const indent = firstArgIndentMatch?.[1] ?? lastLineIndentMatch?.[1] ?? " "; + const prefix = updated.replace(/\s*$/, ""); + const commaPrefix = hasTrailingComma ? prefix : `${prefix},`; + updated = `${commaPrefix}\n${indent}${key}=${valueStr},`; + } else { + const sep = hasTrailingComma ? " " : ", "; + updated = `${updated.replace(/\s*$/, "")}${sep}${key}=${valueStr}`; + } + } + } + } + return updated; +} + +function applyOverridesToSource( + lang: SupportedLanguage, + src: string, + overrides?: Record +): string { + if (!overrides || Object.keys(overrides).length === 0) return src; + // Heuristic: find the first object literal in a create(...) call and inject params + const createCall = /(invocations\.(create|new)\()([\s\S]*?)(\))/m; + const match = src.match(createCall); + if (!match) return src; + const before = src.slice(0, match.index ?? 0); + const after = src.slice((match.index ?? 0) + match[0].length); + const inside = match[3] ?? ""; + const objectMatch = inside.match(/\{[\s\S]*?\}/m); + if (objectMatch) { + const objBefore = inside.slice(0, objectMatch.index!); + const obj = objectMatch[0]; + const objAfter = inside.slice((objectMatch.index || 0) + objectMatch[0].length); + const injected = injectParamsIntoObjectLiteral(lang, obj, overrides); + const newInside = objBefore + injected + objAfter; + return before + match[1] + newInside + match[4] + after; + } + if (lang === "python") { + const injectedArgs = injectParamsIntoPythonArgs(inside, overrides); + const needsNewlineBeforeParen = /\n/.test(injectedArgs) && !/\n\s*$/.test(injectedArgs); + const joiner = needsNewlineBeforeParen ? "\n" : ""; + return before + match[1] + injectedArgs + joiner + match[4] + after; + } + return src; +} + +function parseOverridesString(raw: string): Record | undefined { + const trim = raw.trim(); + // Strip one layer of surrounding braces if present + let inner = trim; + if (inner.startsWith("{") && inner.endsWith("}")) { + inner = inner.slice(1, -1).trim(); + } + // If wrapped as {{ ... }}, strip outer again + if (inner.startsWith("{") && inner.endsWith("}")) { + inner = inner.slice(1, -1).trim(); + } + // Rebuild as JSON object string; heuristically quote keys + let jsonish = `{ ${inner} }`; + // Quote unquoted keys (simple heuristic) + jsonish = jsonish.replace(/([,{\s])([A-Za-z_][A-Za-z0-9_]*)\s*:/g, '$1"$2":'); + // Normalize single quotes to double quotes + jsonish = jsonish.replace(/'([^']*)'/g, '"$1"'); + try { + return JSON.parse(jsonish); + } catch { + return undefined; + } +} + +function extractOverridesFromAttributes(attrs: string | undefined): Record | undefined { + if (!attrs) return undefined; + const idx = attrs.indexOf("overrides="); + if (idx === -1) return undefined; + // Find first '{' after 'overrides=' + const start = attrs.indexOf('{', idx); + if (start === -1) return undefined; + let depth = 0; + let end = -1; + for (let i = start; i < attrs.length; i++) { + const ch = attrs[i]; + if (ch === '{') depth++; + else if (ch === '}') { + depth--; + if (depth === 0) { + end = i; + break; + } + } + } + if (end === -1) return undefined; + const raw = attrs.slice(start, end + 1); + return parseOverridesString(raw); +} function toTitleCase(input: string): string { if (!input) return ""; return input.charAt(0).toUpperCase() + input.slice(1).toLowerCase(); } +type VariantConfig = { name: string; overrides?: Record }; +type CodeSamplesConfig = { + variants?: Record; // key: "method /path" lowercased +}; + +async function readConfig(): Promise { + const configPath = path.resolve(".github/scripts/code_samples.config.json"); + try { + const text = await readFile(configPath, "utf8"); + return JSON.parse(text) as CodeSamplesConfig; + } catch { + return {}; + } +} + async function fetchOpenAPISpec() { console.log(`📥 Fetching OpenAPI spec from ${OPENAPI_URL}`); const res = await fetch(OPENAPI_URL!); @@ -46,7 +262,8 @@ async function getMdxFiles(dir: string): Promise { function extractCodeSamples( spec: any, endpoint: string, - method?: string + method?: string, + overrides?: Record ): string { const pathItem = spec.paths?.[endpoint]; if (!pathItem) return `⚠️ No spec found for ${endpoint}`; @@ -60,18 +277,19 @@ function extractCodeSamples( if (op?.["x-codeSamples"]) { for (const sample of op["x-codeSamples"]) { const rawLang = typeof sample.lang === "string" ? sample.lang : ""; - const lang = rawLang.toLowerCase(); + const normalized = normalizeLang(rawLang); + const allowedNormalized = ["typescript", "javascript", "python"] as const; + if (!(allowedNormalized as readonly string[]).includes(normalized)) continue; + const displayLang: "typescript" | "python" = + normalized === "python" ? "python" : "typescript"; - let fenceInfo: string; - if (["typescript", "ts", "javascript", "js"].includes(lang)) { - fenceInfo = "typescript Typescript"; - } else if (rawLang) { - fenceInfo = `${lang} ${toTitleCase(rawLang)}`; - } else { - fenceInfo = ""; - } - - blocks.push(`\n\`\`\`${fenceInfo}\n${sample.source.trim()}\n\`\`\`\n`); + const fenceInfo = renderFenceInfo(displayLang, rawLang); + const transformedSource = applyOverridesToSource( + displayLang, + String(sample.source ?? "").trim(), + overrides + ); + blocks.push(`\n\`\`\`${fenceInfo}\n${transformedSource}\n\`\`\`\n`); } } } @@ -83,28 +301,109 @@ function extractCodeSamples( } ${endpoint}`; } +function slugifyEndpoint(method: string, endpoint: string): string { + const m = (method || "").toLowerCase().replace(/[^a-z]/g, ""); + let ep = endpoint.replace(/^\//, ""); + // Unwrap path params like {id} -> id + ep = ep.replace(/\{([^}]+)\}/g, "$1"); + // Sanitize and normalize + ep = ep + .replace(/[^a-zA-Z0-9\-/_]/g, "-") + .replace(/[\/]/g, "-") + .replace(/--+/g, "-") + .replace(/^-+|-+$/g, ""); + return `${m}-${ep}`.toLowerCase(); +} + +async function writeSnippetFile(filePath: string, blocks: string) { + const dir = path.dirname(filePath); + await mkdir(dir, { recursive: true }); + const content = `\n${blocks.trim()}\n\n`; + await writeFile(filePath, content, "utf8"); +} + +async function generateSnippets(spec: any) { + const config = await readConfig(); + const paths = spec.paths || {}; + for (const endpoint of Object.keys(paths)) { + const pathItem = paths[endpoint]; + for (const method of Object.keys(pathItem)) { + const key = `${method.toLowerCase()} ${endpoint}`.toLowerCase(); + const baseBlocks = extractCodeSamples(spec, endpoint, method).trim(); + if (!baseBlocks.startsWith("⚠️ ")) { + const baseSlug = slugifyEndpoint(method, endpoint); + const baseFile = path.join(SNIPPETS_ROOT, `${baseSlug}.mdx`); + await writeSnippetFile(baseFile, baseBlocks); + console.log(`🧩 Wrote snippet: ${path.relative(process.cwd(), baseFile)}`); + } + const variants = config.variants?.[key] || []; + for (const variant of variants) { + const vBlocks = extractCodeSamples(spec, endpoint, method, variant.overrides).trim(); + if (!vBlocks.startsWith("⚠️ ")) { + const vSlug = `${slugifyEndpoint(method, endpoint)}-${variant.name}`; + const vFile = path.join(SNIPPETS_ROOT, `${vSlug}.mdx`); + await writeSnippetFile(vFile, vBlocks); + console.log(`🧩 Wrote snippet: ${path.relative(process.cwd(), vFile)}`); + } + } + } + } +} + +async function cleanupOldSnippets() { + try { + const entries = await readdir(SNIPPETS_ROOT, { withFileTypes: true }); + const stale = entries + .filter((e) => e.isFile()) + .map((e) => e.name) + .filter((name) => name.endsWith("-.mdx")); + for (const name of stale) { + const full = path.join(SNIPPETS_ROOT, name); + await unlink(full); + console.log(`🧹 Removed stale snippet: ${path.relative(process.cwd(), full)}`); + } + } catch { + // ignore if folder doesn't exist yet + } +} + async function processFile(file: string, spec: any) { let content = await readFile(file, "utf8"); // Matches {{ get /path }} or {{ post /path }} or {{ /path }} const mustacheRegex = - /\{\{\s*(?:(get|post|put|delete|patch|options|head)\s+)?(\/[^\s}]+)\s*\}\}/gi; + /\{\{\s*(?:(get|post|put|delete|patch|options|head)\s+)?(\/[^\s}]+)(?:\s+(\{[\s\S]*?\}))?\s*\}\}/gi; // Matches get /path // or /path const tagRegex = - /\s*(?:(get|post|put|delete|patch|options|head)\s+)?(\/[^\s<]+)\s*<\/OpenAPICodeGroup>/gi; + /]*)>\s*(?:(get|post|put|delete|patch|options|head)\s+)?(\/[^\s<]+)(?:\s+(\{[\s\S]*?\}))?\s*<\/OpenAPICodeGroup>/gi; let changed = false; - content = content.replace(mustacheRegex, (_, method, endpoint) => { + content = content.replace(mustacheRegex, (_, method, endpoint, jsonOverrides) => { changed = true; - return extractCodeSamples(spec, endpoint, method); + let overrides: Record | undefined = undefined; + if (jsonOverrides) { + try { + overrides = JSON.parse(jsonOverrides); + } catch { + console.warn(`Failed to parse overrides JSON in ${file} for ${endpoint}`); + } + } + return extractCodeSamples(spec, endpoint, method, overrides); }); - content = content.replace(tagRegex, (_, method, endpoint) => { + content = content.replace(tagRegex, (_, attrs, method, endpoint, jsonOverrides) => { changed = true; - const blocks = extractCodeSamples(spec, endpoint, method).trim(); - return `\n${blocks}\n`; + let overrides: Record | undefined = undefined; + const attrOverrides = extractOverridesFromAttributes(attrs); + if (attrOverrides) overrides = { ...attrOverrides }; + if (jsonOverrides) { + const inline = parseOverridesString(jsonOverrides); + if (inline) overrides = { ...(overrides || {}), ...inline }; + } + const blocks = extractCodeSamples(spec, endpoint, method, overrides).trim(); + return `\n${blocks}\n`; }); if (changed) { @@ -116,6 +415,10 @@ async function processFile(file: string, spec: any) { async function main() { const spec = await fetchOpenAPISpec(); + // Always generate snippet files from OpenAPI + await cleanupOldSnippets(); + await generateSnippets(spec); + for (const dir of TARGET_DIRS) { const files = await getMdxFiles(dir); for (const file of files) { diff --git a/apps/invoke.mdx b/apps/invoke.mdx index a219679..16cca48 100644 --- a/apps/invoke.mdx +++ b/apps/invoke.mdx @@ -2,53 +2,22 @@ title: "Invoking" --- +import SynchronousInvocation from '/snippets/openapi/post-invocations.mdx'; +import AsyncInvocation from '/snippets/openapi/post-invocations-async.mdx'; + ## Via API You can invoke your app by making a `POST` request to Kernel's API. **For automations and agents that take longer than 100 seconds, use [async invocations](/apps/invoke#asynchronous-invocations).** Synchronous invocations time out after 100 seconds. -post /invocations + ### Asynchronous invocations For long running jobs, use asynchronous invocations to trigger Kernel actions without waiting for the result. You can then poll its [status](/apps/status) for the result. - -```typescript Typescript/Javascript -import { Kernel } from '@onkernel/sdk'; - -const { -id, -status, // status will be QUEUED -} = await Kernel.invocations.create({ -app_name: app_name, -action_name: action_name, -version: "latest", -async: true, -}); - -```` - -```python Python -import kernel - -result = kernel.invocations.create({ - "app_name": app_name, - "action_name": action_name, - "version": "latest", - "async": True -}) -invocation_id, status = ( - result["id"], - result["status"] # status will be QUEUED -) - result["id"], - result["status"] # status will be QUEUED -) -```` - - + ## Via CLI diff --git a/apps/status.mdx b/apps/status.mdx index 8fcbf2f..ca3bb80 100644 --- a/apps/status.mdx +++ b/apps/status.mdx @@ -4,39 +4,9 @@ title: "Status" Once you've [deployed](/apps/deploy) an app and invoked it, you can check its status. - -```typescript Typescript/Javascript -import { Kernel } from '@onkernel/sdk'; +import GetInvocationStatus from '/snippets/openapi/get-invocations-id.mdx'; -const { - id, - app_name, - action_name, - payload, - output, - started_at, - finished_at, - status, - status_reason -} = await Kernel.invocations.get(invocation_id); -``` - -```python Python -import kernel - -data = kernel.invocations.get(invocation_id) -invocation_id = data["id"] -app_name = data["app_name"] -action_name = data["action_name"] -payload = data["payload"] -output = data["output"] -started_at = data["started_at"] -finished_at = data["finished_at"] -status = data["status"] -status_reason = data["status_reason"] -``` - - + An invocation ends once its code execution finishes. diff --git a/apps/stop.mdx b/apps/stop.mdx index 5bc6132..d74e9db 100644 --- a/apps/stop.mdx +++ b/apps/stop.mdx @@ -9,21 +9,11 @@ Terminating an invocation also destroys any browsers associated with it. ## Via API -You can also do this by making a `DELETE` request to Kernel's API: +You can also do this via the API: - -```typescript Typescript/Javascript -import { Kernel } from '@onkernel/sdk'; +import StopInvocation from '/snippets/openapi/patch-invocations-id.mdx'; -await Kernel.invocations.delete(invocationId); -``` - -```python Python -import kernel - -kernel.invocations.delete(invocation_id) -``` - + ## Via CLI diff --git a/browsers/create-a-browser.mdx b/browsers/create-a-browser.mdx index e68269c..8fed5c7 100644 --- a/browsers/create-a-browser.mdx +++ b/browsers/create-a-browser.mdx @@ -8,20 +8,9 @@ Kernel browsers were designed to be lightweight, fast, and efficient for cloud-b Use our SDK to create a browser: - -```typescript Typescript/Javascript -import { Kernel } from '@onkernel/sdk'; -const kernel = new Kernel(); -const kernelBrowser = await kernel.browsers.create(); -``` +import CreateBrowserSnippet from '/snippets/openapi/post-browsers.mdx'; -```Python Python -import kernel -client = Kernel() -kernel_browser = client.browsers.create() -``` - - + ## 2. Connect to the browser with the Chrome DevTools Protocol @@ -50,16 +39,9 @@ browser = playwright.chromium.connect_over_cdp(kernel_browser.cdp_ws_url) When you're finished with the browser, you can delete it: - -```typescript Typescript/Javascript -await client.browsers.deleteByID(kernel_browser.session_id); -``` +import DeleteBrowserSnippet from '/snippets/openapi/delete-browsers-id.mdx'; -```Python Python -client.browsers.delete_by_id(kernel_browser.session_id) -``` - - + You can also delete the browser by [specifiying a timeout](/browsers/termination#timeouts-for-non-persisted-browsers) when you create the browser. ## Full example diff --git a/browsers/live-view.mdx b/browsers/live-view.mdx index 0c2cbc8..63d9122 100644 --- a/browsers/live-view.mdx +++ b/browsers/live-view.mdx @@ -6,22 +6,9 @@ Humans-in-the-loop can access the live view of Kernel browsers in real-time to r To access the live view, visit the `browser_live_view_url` provided when you create a Kernel browser: - -```typescript Typescript/Javascript -import { Kernel } from '@onkernel/sdk'; -const kernel = new Kernel(); -const kernelBrowser = await kernel.browsers.create(); -console.log(kernelBrowser.browser_live_view_url); -``` - -```python Python -import kernel -client = Kernel() -kernel_browser = client.browsers.create() -print(kernel_browser.browser_live_view_url) -``` - - +import CreateBrowserForLiveView from '/snippets/openapi/post-browsers.mdx'; + + ## Query parameters diff --git a/browsers/termination.mdx b/browsers/termination.mdx index ca4062a..5c0996c 100644 --- a/browsers/termination.mdx +++ b/browsers/termination.mdx @@ -8,31 +8,9 @@ Kernel browsers should be terminated after you're done with them. You can delete a browser by making a `DELETE` request to Kernel's API: - -```typescript Typescript/Javascript -import Kernel from '@onkernel/sdk'; -const kernel = new Kernel(); +import DeleteBrowser from '/snippets/openapi/delete-browsers-id.mdx'; -// Delete a persisted browser -await kernel.browsers.delete({ persistent_id: 'persistent_id' }); - -// Delete a non-persisted browser -await kernel.browsers.deleteByID('htzv5orfit78e1m2biiifpbv'); -``` - -```python Python -from kernel import Kernel -client = Kernel() - -# Delete a persisted browser -client.browsers.delete( - persistent_id="persistent_id", -) - -# Delete a non-persisted browser -client.browsers.delete_by_id('htzv5orfit78e1m2biiifpbv') -``` - + ## Timeouts for non-persisted browsers @@ -40,16 +18,6 @@ If you don't manually delete a `non-persisted` browser, deletion will happen aut You can set a custom timeout of up to 24 hours via the API: - -```typescript Typescript/Javascript -import { Kernel } from '@onkernel/sdk'; -const kernel = new Kernel(); -const kernelBrowser = await kernel.browsers.create({ timeout_seconds: 300 }); -``` - -```python Python -import kernel -client = Kernel() -kernel_browser = client.browsers.create(timeout_seconds=300) -``` - \ No newline at end of file +import CreateBrowserTimeout from '/snippets/openapi/post-browsers.mdx'; + + \ No newline at end of file diff --git a/snippets/openapi/delete-browsers-id-fs-watch-watch_id.mdx b/snippets/openapi/delete-browsers-id-fs-watch-watch_id.mdx new file mode 100644 index 0000000..4d2f442 --- /dev/null +++ b/snippets/openapi/delete-browsers-id-fs-watch-watch_id.mdx @@ -0,0 +1,24 @@ + +```typescript index.ts +import Kernel from '@onkernel/sdk'; + +const client = new Kernel({ + apiKey: 'My API Key', +}); + +await client.browsers.fs.watch.stop('watch_id', { id: 'id' }); +``` + + +```python main.py +from kernel import Kernel + +client = Kernel( + api_key="My API Key", +) +client.browsers.fs.watch.stop( + watch_id="watch_id", + id="id", +) +``` + diff --git a/snippets/openapi/delete-browsers-id.mdx b/snippets/openapi/delete-browsers-id.mdx new file mode 100644 index 0000000..5074238 --- /dev/null +++ b/snippets/openapi/delete-browsers-id.mdx @@ -0,0 +1,23 @@ + +```typescript index.ts +import Kernel from '@onkernel/sdk'; + +const client = new Kernel({ + apiKey: 'My API Key', +}); + +await client.browsers.deleteByID('htzv5orfit78e1m2biiifpbv'); +``` + + +```python main.py +from kernel import Kernel + +client = Kernel( + api_key="My API Key", +) +client.browsers.delete_by_id( + "id", +) +``` + diff --git a/snippets/openapi/delete-browsers.mdx b/snippets/openapi/delete-browsers.mdx new file mode 100644 index 0000000..ff2dcf4 --- /dev/null +++ b/snippets/openapi/delete-browsers.mdx @@ -0,0 +1,23 @@ + +```typescript index.ts +import Kernel from '@onkernel/sdk'; + +const client = new Kernel({ + apiKey: 'My API Key', +}); + +await client.browsers.delete({ persistent_id: 'persistent_id' }); +``` + + +```python main.py +from kernel import Kernel + +client = Kernel( + api_key="My API Key", +) +client.browsers.delete( + persistent_id="persistent_id", +) +``` + diff --git a/snippets/openapi/delete-invocations-id-browsers.mdx b/snippets/openapi/delete-invocations-id-browsers.mdx new file mode 100644 index 0000000..7b83d1d --- /dev/null +++ b/snippets/openapi/delete-invocations-id-browsers.mdx @@ -0,0 +1,23 @@ + +```typescript index.ts +import Kernel from '@onkernel/sdk'; + +const client = new Kernel({ + apiKey: 'My API Key', +}); + +await client.invocations.deleteBrowsers('id'); +``` + + +```python main.py +from kernel import Kernel + +client = Kernel( + api_key="My API Key", +) +client.invocations.delete_browsers( + "id", +) +``` + diff --git a/snippets/openapi/get-apps.mdx b/snippets/openapi/get-apps.mdx new file mode 100644 index 0000000..0e3d2a5 --- /dev/null +++ b/snippets/openapi/get-apps.mdx @@ -0,0 +1,24 @@ + +```typescript index.ts +import Kernel from '@onkernel/sdk'; + +const client = new Kernel({ + apiKey: 'My API Key', +}); + +const apps = await client.apps.list(); + +console.log(apps); +``` + + +```python main.py +from kernel import Kernel + +client = Kernel( + api_key="My API Key", +) +apps = client.apps.list() +print(apps) +``` + diff --git a/snippets/openapi/get-browsers-id-fs-file_info.mdx b/snippets/openapi/get-browsers-id-fs-file_info.mdx new file mode 100644 index 0000000..217294c --- /dev/null +++ b/snippets/openapi/get-browsers-id-fs-file_info.mdx @@ -0,0 +1,27 @@ + +```typescript index.ts +import Kernel from '@onkernel/sdk'; + +const client = new Kernel({ + apiKey: 'My API Key', +}); + +const response = await client.browsers.fs.fileInfo('id', { path: '/J!' }); + +console.log(response.is_dir); +``` + + +```python main.py +from kernel import Kernel + +client = Kernel( + api_key="My API Key", +) +response = client.browsers.fs.file_info( + id="id", + path="/J!", +) +print(response.is_dir) +``` + diff --git a/snippets/openapi/get-browsers-id-fs-list_files.mdx b/snippets/openapi/get-browsers-id-fs-list_files.mdx new file mode 100644 index 0000000..e6ec680 --- /dev/null +++ b/snippets/openapi/get-browsers-id-fs-list_files.mdx @@ -0,0 +1,27 @@ + +```typescript index.ts +import Kernel from '@onkernel/sdk'; + +const client = new Kernel({ + apiKey: 'My API Key', +}); + +const response = await client.browsers.fs.listFiles('id', { path: '/J!' }); + +console.log(response); +``` + + +```python main.py +from kernel import Kernel + +client = Kernel( + api_key="My API Key", +) +response = client.browsers.fs.list_files( + id="id", + path="/J!", +) +print(response) +``` + diff --git a/snippets/openapi/get-browsers-id-fs-read_file.mdx b/snippets/openapi/get-browsers-id-fs-read_file.mdx new file mode 100644 index 0000000..5acd622 --- /dev/null +++ b/snippets/openapi/get-browsers-id-fs-read_file.mdx @@ -0,0 +1,32 @@ + +```typescript index.ts +import Kernel from '@onkernel/sdk'; + +const client = new Kernel({ + apiKey: 'My API Key', +}); + +const response = await client.browsers.fs.readFile('id', { path: '/J!' }); + +console.log(response); + +const content = await response.blob(); +console.log(content); +``` + + +```python main.py +from kernel import Kernel + +client = Kernel( + api_key="My API Key", +) +response = client.browsers.fs.read_file( + id="id", + path="/J!", +) +print(response) +content = response.read() +print(content) +``` + diff --git a/snippets/openapi/get-browsers-id-fs-watch-watch_id-events.mdx b/snippets/openapi/get-browsers-id-fs-watch-watch_id-events.mdx new file mode 100644 index 0000000..9f0b92d --- /dev/null +++ b/snippets/openapi/get-browsers-id-fs-watch-watch_id-events.mdx @@ -0,0 +1,27 @@ + +```typescript index.ts +import Kernel from '@onkernel/sdk'; + +const client = new Kernel({ + apiKey: 'My API Key', +}); + +const response = await client.browsers.fs.watch.events('watch_id', { id: 'id' }); + +console.log(response.path); +``` + + +```python main.py +from kernel import Kernel + +client = Kernel( + api_key="My API Key", +) +response = client.browsers.fs.watch.events( + watch_id="watch_id", + id="id", +) +print(response.path) +``` + diff --git a/snippets/openapi/get-browsers-id-replays-replay_id.mdx b/snippets/openapi/get-browsers-id-replays-replay_id.mdx new file mode 100644 index 0000000..bd6ca10 --- /dev/null +++ b/snippets/openapi/get-browsers-id-replays-replay_id.mdx @@ -0,0 +1,32 @@ + +```typescript index.ts +import Kernel from '@onkernel/sdk'; + +const client = new Kernel({ + apiKey: 'My API Key', +}); + +const response = await client.browsers.replays.download('replay_id', { id: 'id' }); + +console.log(response); + +const content = await response.blob(); +console.log(content); +``` + + +```python main.py +from kernel import Kernel + +client = Kernel( + api_key="My API Key", +) +response = client.browsers.replays.download( + replay_id="replay_id", + id="id", +) +print(response) +content = response.read() +print(content) +``` + diff --git a/snippets/openapi/get-browsers-id-replays.mdx b/snippets/openapi/get-browsers-id-replays.mdx new file mode 100644 index 0000000..81a3d26 --- /dev/null +++ b/snippets/openapi/get-browsers-id-replays.mdx @@ -0,0 +1,26 @@ + +```typescript index.ts +import Kernel from '@onkernel/sdk'; + +const client = new Kernel({ + apiKey: 'My API Key', +}); + +const replays = await client.browsers.replays.list('id'); + +console.log(replays); +``` + + +```python main.py +from kernel import Kernel + +client = Kernel( + api_key="My API Key", +) +replays = client.browsers.replays.list( + "id", +) +print(replays) +``` + diff --git a/snippets/openapi/get-browsers-id.mdx b/snippets/openapi/get-browsers-id.mdx new file mode 100644 index 0000000..b242cc3 --- /dev/null +++ b/snippets/openapi/get-browsers-id.mdx @@ -0,0 +1,26 @@ + +```typescript index.ts +import Kernel from '@onkernel/sdk'; + +const client = new Kernel({ + apiKey: 'My API Key', +}); + +const browser = await client.browsers.retrieve('htzv5orfit78e1m2biiifpbv'); + +console.log(browser.session_id); +``` + + +```python main.py +from kernel import Kernel + +client = Kernel( + api_key="My API Key", +) +browser = client.browsers.retrieve( + "id", +) +print(browser.session_id) +``` + diff --git a/snippets/openapi/get-browsers.mdx b/snippets/openapi/get-browsers.mdx new file mode 100644 index 0000000..1b2e48c --- /dev/null +++ b/snippets/openapi/get-browsers.mdx @@ -0,0 +1,24 @@ + +```typescript index.ts +import Kernel from '@onkernel/sdk'; + +const client = new Kernel({ + apiKey: 'My API Key', +}); + +const browsers = await client.browsers.list(); + +console.log(browsers); +``` + + +```python main.py +from kernel import Kernel + +client = Kernel( + api_key="My API Key", +) +browsers = client.browsers.list() +print(browsers) +``` + diff --git a/snippets/openapi/get-deployments-id-events.mdx b/snippets/openapi/get-deployments-id-events.mdx new file mode 100644 index 0000000..f478852 --- /dev/null +++ b/snippets/openapi/get-deployments-id-events.mdx @@ -0,0 +1,26 @@ + +```typescript index.ts +import Kernel from '@onkernel/sdk'; + +const client = new Kernel({ + apiKey: 'My API Key', +}); + +const response = await client.deployments.follow('id'); + +console.log(response); +``` + + +```python main.py +from kernel import Kernel + +client = Kernel( + api_key="My API Key", +) +response = client.deployments.follow( + id="id", +) +print(response) +``` + diff --git a/snippets/openapi/get-deployments-id.mdx b/snippets/openapi/get-deployments-id.mdx new file mode 100644 index 0000000..a930638 --- /dev/null +++ b/snippets/openapi/get-deployments-id.mdx @@ -0,0 +1,26 @@ + +```typescript index.ts +import Kernel from '@onkernel/sdk'; + +const client = new Kernel({ + apiKey: 'My API Key', +}); + +const deployment = await client.deployments.retrieve('id'); + +console.log(deployment.id); +``` + + +```python main.py +from kernel import Kernel + +client = Kernel( + api_key="My API Key", +) +deployment = client.deployments.retrieve( + "id", +) +print(deployment.id) +``` + diff --git a/snippets/openapi/get-deployments.mdx b/snippets/openapi/get-deployments.mdx new file mode 100644 index 0000000..74ab696 --- /dev/null +++ b/snippets/openapi/get-deployments.mdx @@ -0,0 +1,24 @@ + +```typescript index.ts +import Kernel from '@onkernel/sdk'; + +const client = new Kernel({ + apiKey: 'My API Key', +}); + +const deployments = await client.deployments.list(); + +console.log(deployments); +``` + + +```python main.py +from kernel import Kernel + +client = Kernel( + api_key="My API Key", +) +deployments = client.deployments.list() +print(deployments) +``` + diff --git a/snippets/openapi/get-invocations-id-events.mdx b/snippets/openapi/get-invocations-id-events.mdx new file mode 100644 index 0000000..8883176 --- /dev/null +++ b/snippets/openapi/get-invocations-id-events.mdx @@ -0,0 +1,26 @@ + +```typescript index.ts +import Kernel from '@onkernel/sdk'; + +const client = new Kernel({ + apiKey: 'My API Key', +}); + +const response = await client.invocations.follow('id'); + +console.log(response); +``` + + +```python main.py +from kernel import Kernel + +client = Kernel( + api_key="My API Key", +) +response = client.invocations.follow( + "id", +) +print(response) +``` + diff --git a/snippets/openapi/get-invocations-id.mdx b/snippets/openapi/get-invocations-id.mdx new file mode 100644 index 0000000..c61b012 --- /dev/null +++ b/snippets/openapi/get-invocations-id.mdx @@ -0,0 +1,26 @@ + +```typescript index.ts +import Kernel from '@onkernel/sdk'; + +const client = new Kernel({ + apiKey: 'My API Key', +}); + +const invocation = await client.invocations.retrieve('rr33xuugxj9h0bkf1rdt2bet'); + +console.log(invocation.id); +``` + + +```python main.py +from kernel import Kernel + +client = Kernel( + api_key="My API Key", +) +invocation = client.invocations.retrieve( + "id", +) +print(invocation.id) +``` + diff --git a/snippets/openapi/patch-invocations-id.mdx b/snippets/openapi/patch-invocations-id.mdx new file mode 100644 index 0000000..18879fc --- /dev/null +++ b/snippets/openapi/patch-invocations-id.mdx @@ -0,0 +1,27 @@ + +```typescript index.ts +import Kernel from '@onkernel/sdk'; + +const client = new Kernel({ + apiKey: 'My API Key', +}); + +const invocation = await client.invocations.update('id', { status: 'succeeded' }); + +console.log(invocation.id); +``` + + +```python main.py +from kernel import Kernel + +client = Kernel( + api_key="My API Key", +) +invocation = client.invocations.update( + id="id", + status="succeeded", +) +print(invocation.id) +``` + diff --git a/snippets/openapi/post-browsers-id-fs-watch.mdx b/snippets/openapi/post-browsers-id-fs-watch.mdx new file mode 100644 index 0000000..ccd514d --- /dev/null +++ b/snippets/openapi/post-browsers-id-fs-watch.mdx @@ -0,0 +1,27 @@ + +```typescript index.ts +import Kernel from '@onkernel/sdk'; + +const client = new Kernel({ + apiKey: 'My API Key', +}); + +const response = await client.browsers.fs.watch.start('id', { path: 'path' }); + +console.log(response.watch_id); +``` + + +```python main.py +from kernel import Kernel + +client = Kernel( + api_key="My API Key", +) +response = client.browsers.fs.watch.start( + id="id", + path="path", +) +print(response.watch_id) +``` + diff --git a/snippets/openapi/post-browsers-id-replays-replay_id-stop.mdx b/snippets/openapi/post-browsers-id-replays-replay_id-stop.mdx new file mode 100644 index 0000000..eec20a0 --- /dev/null +++ b/snippets/openapi/post-browsers-id-replays-replay_id-stop.mdx @@ -0,0 +1,24 @@ + +```typescript index.ts +import Kernel from '@onkernel/sdk'; + +const client = new Kernel({ + apiKey: 'My API Key', +}); + +await client.browsers.replays.stop('replay_id', { id: 'id' }); +``` + + +```python main.py +from kernel import Kernel + +client = Kernel( + api_key="My API Key", +) +client.browsers.replays.stop( + replay_id="replay_id", + id="id", +) +``` + diff --git a/snippets/openapi/post-browsers-id-replays.mdx b/snippets/openapi/post-browsers-id-replays.mdx new file mode 100644 index 0000000..aaf3460 --- /dev/null +++ b/snippets/openapi/post-browsers-id-replays.mdx @@ -0,0 +1,26 @@ + +```typescript index.ts +import Kernel from '@onkernel/sdk'; + +const client = new Kernel({ + apiKey: 'My API Key', +}); + +const response = await client.browsers.replays.start('id'); + +console.log(response.replay_id); +``` + + +```python main.py +from kernel import Kernel + +client = Kernel( + api_key="My API Key", +) +response = client.browsers.replays.start( + id="id", +) +print(response.replay_id) +``` + diff --git a/snippets/openapi/post-browsers.mdx b/snippets/openapi/post-browsers.mdx new file mode 100644 index 0000000..84a73bf --- /dev/null +++ b/snippets/openapi/post-browsers.mdx @@ -0,0 +1,24 @@ + +```typescript index.ts +import Kernel from '@onkernel/sdk'; + +const client = new Kernel({ + apiKey: 'My API Key', +}); + +const browser = await client.browsers.create(); + +console.log(browser.session_id); +``` + + +```python main.py +from kernel import Kernel + +client = Kernel( + api_key="My API Key", +) +browser = client.browsers.create() +print(browser.session_id) +``` + diff --git a/snippets/openapi/post-deployments.mdx b/snippets/openapi/post-deployments.mdx new file mode 100644 index 0000000..db97b41 --- /dev/null +++ b/snippets/openapi/post-deployments.mdx @@ -0,0 +1,30 @@ + +```typescript index.ts +import Kernel from '@onkernel/sdk'; + +const client = new Kernel({ + apiKey: 'My API Key', +}); + +const deployment = await client.deployments.create({ + entrypoint_rel_path: 'src/app.py', + file: fs.createReadStream('path/to/file'), +}); + +console.log(deployment.id); +``` + + +```python main.py +from kernel import Kernel + +client = Kernel( + api_key="My API Key", +) +deployment = client.deployments.create( + entrypoint_rel_path="src/app.py", + file=b"raw file contents", +) +print(deployment.id) +``` + diff --git a/snippets/openapi/post-invocations-async.mdx b/snippets/openapi/post-invocations-async.mdx new file mode 100644 index 0000000..4dd698c --- /dev/null +++ b/snippets/openapi/post-invocations-async.mdx @@ -0,0 +1,34 @@ + +```typescript index.ts +import Kernel from '@onkernel/sdk'; + +const client = new Kernel({ + apiKey: 'My API Key', +}); + +const invocation = await client.invocations.create({ + async: true, + action_name: 'analyze', + app_name: 'my-app', + version: '1.0.0', +}); + +console.log(invocation.id); +``` + + +```python main.py +from kernel import Kernel + +client = Kernel( + api_key="My API Key", +) +invocation = client.invocations.create( + action_name="analyze", + app_name="my-app", + version="1.0.0", + async=True, +) +print(invocation.id) +``` + diff --git a/snippets/openapi/post-invocations.mdx b/snippets/openapi/post-invocations.mdx new file mode 100644 index 0000000..dff4147 --- /dev/null +++ b/snippets/openapi/post-invocations.mdx @@ -0,0 +1,32 @@ + +```typescript index.ts +import Kernel from '@onkernel/sdk'; + +const client = new Kernel({ + apiKey: 'My API Key', +}); + +const invocation = await client.invocations.create({ + action_name: 'analyze', + app_name: 'my-app', + version: '1.0.0', +}); + +console.log(invocation.id); +``` + + +```python main.py +from kernel import Kernel + +client = Kernel( + api_key="My API Key", +) +invocation = client.invocations.create( + action_name="analyze", + app_name="my-app", + version="1.0.0", +) +print(invocation.id) +``` + diff --git a/snippets/openapi/put-browsers-id-fs-create_directory.mdx b/snippets/openapi/put-browsers-id-fs-create_directory.mdx new file mode 100644 index 0000000..3a19a58 --- /dev/null +++ b/snippets/openapi/put-browsers-id-fs-create_directory.mdx @@ -0,0 +1,24 @@ + +```typescript index.ts +import Kernel from '@onkernel/sdk'; + +const client = new Kernel({ + apiKey: 'My API Key', +}); + +await client.browsers.fs.createDirectory('id', { path: '/J!' }); +``` + + +```python main.py +from kernel import Kernel + +client = Kernel( + api_key="My API Key", +) +client.browsers.fs.create_directory( + id="id", + path="/J!", +) +``` + diff --git a/snippets/openapi/put-browsers-id-fs-delete_directory.mdx b/snippets/openapi/put-browsers-id-fs-delete_directory.mdx new file mode 100644 index 0000000..50b9f8d --- /dev/null +++ b/snippets/openapi/put-browsers-id-fs-delete_directory.mdx @@ -0,0 +1,24 @@ + +```typescript index.ts +import Kernel from '@onkernel/sdk'; + +const client = new Kernel({ + apiKey: 'My API Key', +}); + +await client.browsers.fs.deleteDirectory('id', { path: '/J!' }); +``` + + +```python main.py +from kernel import Kernel + +client = Kernel( + api_key="My API Key", +) +client.browsers.fs.delete_directory( + id="id", + path="/J!", +) +``` + diff --git a/snippets/openapi/put-browsers-id-fs-delete_file.mdx b/snippets/openapi/put-browsers-id-fs-delete_file.mdx new file mode 100644 index 0000000..86a9a9b --- /dev/null +++ b/snippets/openapi/put-browsers-id-fs-delete_file.mdx @@ -0,0 +1,24 @@ + +```typescript index.ts +import Kernel from '@onkernel/sdk'; + +const client = new Kernel({ + apiKey: 'My API Key', +}); + +await client.browsers.fs.deleteFile('id', { path: '/J!' }); +``` + + +```python main.py +from kernel import Kernel + +client = Kernel( + api_key="My API Key", +) +client.browsers.fs.delete_file( + id="id", + path="/J!", +) +``` + diff --git a/snippets/openapi/put-browsers-id-fs-move.mdx b/snippets/openapi/put-browsers-id-fs-move.mdx new file mode 100644 index 0000000..5cf4ef3 --- /dev/null +++ b/snippets/openapi/put-browsers-id-fs-move.mdx @@ -0,0 +1,25 @@ + +```typescript index.ts +import Kernel from '@onkernel/sdk'; + +const client = new Kernel({ + apiKey: 'My API Key', +}); + +await client.browsers.fs.move('id', { dest_path: '/J!', src_path: '/J!' }); +``` + + +```python main.py +from kernel import Kernel + +client = Kernel( + api_key="My API Key", +) +client.browsers.fs.move( + id="id", + dest_path="/J!", + src_path="/J!", +) +``` + diff --git a/snippets/openapi/put-browsers-id-fs-set_file_permissions.mdx b/snippets/openapi/put-browsers-id-fs-set_file_permissions.mdx new file mode 100644 index 0000000..b4e207f --- /dev/null +++ b/snippets/openapi/put-browsers-id-fs-set_file_permissions.mdx @@ -0,0 +1,25 @@ + +```typescript index.ts +import Kernel from '@onkernel/sdk'; + +const client = new Kernel({ + apiKey: 'My API Key', +}); + +await client.browsers.fs.setFilePermissions('id', { mode: '0611', path: '/J!' }); +``` + + +```python main.py +from kernel import Kernel + +client = Kernel( + api_key="My API Key", +) +client.browsers.fs.set_file_permissions( + id="id", + mode="0611", + path="/J!", +) +``` + diff --git a/snippets/openapi/put-browsers-id-fs-write_file.mdx b/snippets/openapi/put-browsers-id-fs-write_file.mdx new file mode 100644 index 0000000..9a9067e --- /dev/null +++ b/snippets/openapi/put-browsers-id-fs-write_file.mdx @@ -0,0 +1,25 @@ + +```typescript index.ts +import Kernel from '@onkernel/sdk'; + +const client = new Kernel({ + apiKey: 'My API Key', +}); + +await client.browsers.fs.writeFile('id', fs.createReadStream('path/to/file'), { path: '/J!' }); +``` + + +```python main.py +from kernel import Kernel + +client = Kernel( + api_key="My API Key", +) +client.browsers.fs.write_file( + id="id", + contents=b"raw file contents", + path="/J!", +) +``` + From c7147e6107c756c062e92f0069423d612f8d7ebf Mon Sep 17 00:00:00 2001 From: Mason Williams Date: Tue, 19 Aug 2025 14:38:18 -0400 Subject: [PATCH 15/21] docs(snippets): update code block labels and imports --- .github/scripts/code_samples.config.json | 4 + .github/scripts/generate_code_samples.ts | 92 ++++++++++++++----- apps/status.mdx | 4 +- apps/stop.mdx | 5 +- browsers/create-a-browser.mdx | 7 +- browsers/live-view.mdx | 4 +- browsers/termination.mdx | 7 +- .../delete-browsers-id-fs-watch-watch_id.mdx | 6 +- snippets/openapi/delete-browsers-id.mdx | 6 +- snippets/openapi/delete-browsers.mdx | 6 +- .../delete-invocations-id-browsers.mdx | 6 +- snippets/openapi/get-apps.mdx | 6 +- .../openapi/get-browsers-id-fs-file_info.mdx | 6 +- .../openapi/get-browsers-id-fs-list_files.mdx | 6 +- .../openapi/get-browsers-id-fs-read_file.mdx | 6 +- ...t-browsers-id-fs-watch-watch_id-events.mdx | 6 +- .../get-browsers-id-replays-replay_id.mdx | 6 +- snippets/openapi/get-browsers-id-replays.mdx | 6 +- snippets/openapi/get-browsers-id.mdx | 6 +- snippets/openapi/get-browsers.mdx | 6 +- .../openapi/get-deployments-id-events.mdx | 6 +- snippets/openapi/get-deployments-id.mdx | 6 +- snippets/openapi/get-deployments.mdx | 6 +- .../openapi/get-invocations-id-events.mdx | 6 +- snippets/openapi/get-invocations-id.mdx | 6 +- snippets/openapi/patch-invocations-id.mdx | 6 +- .../openapi/post-browsers-id-fs-watch.mdx | 6 +- ...ost-browsers-id-replays-replay_id-stop.mdx | 6 +- snippets/openapi/post-browsers-id-replays.mdx | 6 +- snippets/openapi/post-browsers-live-view.mdx | 24 +++++ snippets/openapi/post-browsers-timeout.mdx | 24 +++++ snippets/openapi/post-browsers.mdx | 6 +- snippets/openapi/post-deployments.mdx | 6 +- snippets/openapi/post-invocations-async.mdx | 9 +- snippets/openapi/post-invocations.mdx | 6 +- .../put-browsers-id-fs-create_directory.mdx | 6 +- .../put-browsers-id-fs-delete_directory.mdx | 6 +- .../put-browsers-id-fs-delete_file.mdx | 6 +- snippets/openapi/put-browsers-id-fs-move.mdx | 6 +- ...ut-browsers-id-fs-set_file_permissions.mdx | 6 +- .../openapi/put-browsers-id-fs-write_file.mdx | 6 +- 41 files changed, 228 insertions(+), 138 deletions(-) create mode 100644 snippets/openapi/post-browsers-live-view.mdx create mode 100644 snippets/openapi/post-browsers-timeout.mdx diff --git a/.github/scripts/code_samples.config.json b/.github/scripts/code_samples.config.json index e2ba10d..f168e7f 100644 --- a/.github/scripts/code_samples.config.json +++ b/.github/scripts/code_samples.config.json @@ -2,6 +2,10 @@ "variants": { "post /invocations": [ { "name": "async", "overrides": { "async": true } } + ], + "post /browsers": [ + { "name": "timeout", "overrides": { "timeout_seconds": 300 } }, + { "name": "live-view", "overrides": { "log": "browser.browser_live_view_url" } } ] } } diff --git a/.github/scripts/generate_code_samples.ts b/.github/scripts/generate_code_samples.ts index 4df4c01..e3ce663 100644 --- a/.github/scripts/generate_code_samples.ts +++ b/.github/scripts/generate_code_samples.ts @@ -24,8 +24,8 @@ function normalizeLang(input: string): SupportedLanguage { } function renderFenceInfo(lang: SupportedLanguage, rawLang: string): string { - if (lang === "typescript" || lang === "javascript") return "typescript index.ts"; - if (lang === "python") return "python main.py"; + if (lang === "typescript" || lang === "javascript") return "typescript Typescript/Javascript"; + if (lang === "python") return "python Python"; return `${lang} ${toTitleCase(rawLang)}`; } @@ -92,6 +92,10 @@ function injectParamsIntoObjectLiteral( } } } + // For JS/TS single-line objects, remove any trailing comma before the closing brace + if (lang !== "python") { + updated = updated.replace(/,\s*}/, " }"); + } return updated; } @@ -136,29 +140,67 @@ function applyOverridesToSource( overrides?: Record ): string { if (!overrides || Object.keys(overrides).length === 0) return src; - // Heuristic: find the first object literal in a create(...) call and inject params - const createCall = /(invocations\.(create|new)\()([\s\S]*?)(\))/m; - const match = src.match(createCall); - if (!match) return src; - const before = src.slice(0, match.index ?? 0); - const after = src.slice((match.index ?? 0) + match[0].length); - const inside = match[3] ?? ""; - const objectMatch = inside.match(/\{[\s\S]*?\}/m); - if (objectMatch) { - const objBefore = inside.slice(0, objectMatch.index!); - const obj = objectMatch[0]; - const objAfter = inside.slice((objectMatch.index || 0) + objectMatch[0].length); - const injected = injectParamsIntoObjectLiteral(lang, obj, overrides); - const newInside = objBefore + injected + objAfter; - return before + match[1] + newInside + match[4] + after; + let transformed = src; + + // Heuristic: find the first object literal in a X.create(...) call and inject params (excluding special keys like 'log') + const { log: _logOverride, ...injectionOverrides } = overrides as Record; + if (Object.keys(injectionOverrides).length > 0) { + const createCall = /(\b\w+\.(create|new)\()([\s\S]*?)(\))/m; + const match = transformed.match(createCall); + if (match) { + const before = transformed.slice(0, match.index ?? 0); + const after = transformed.slice((match.index ?? 0) + match[0].length); + const inside = match[3] ?? ""; + const objectMatch = inside.match(/\{[\s\S]*?\}/m); + if (objectMatch) { + const objBefore = inside.slice(0, objectMatch.index!); + const obj = objectMatch[0]; + const objAfter = inside.slice((objectMatch.index || 0) + objectMatch[0].length); + const injected = injectParamsIntoObjectLiteral(lang, obj, injectionOverrides); + const newInside = objBefore + injected + objAfter; + transformed = before + match[1] + newInside + match[4] + after; + } else if (lang === "python") { + const injectedArgs = injectParamsIntoPythonArgs(inside, injectionOverrides); + const needsNewlineBeforeParen = /\n/.test(injectedArgs) && !/\n\s*$/.test(injectedArgs); + const joiner = needsNewlineBeforeParen ? "\n" : ""; + transformed = before + match[1] + injectedArgs + joiner + match[4] + after; + } else { + // JS/TS with no object literal: insert one and inject + const injectedObj = injectParamsIntoObjectLiteral(lang, "{}", injectionOverrides); + transformed = before + match[1] + injectedObj + match[4] + after; + } + } } - if (lang === "python") { - const injectedArgs = injectParamsIntoPythonArgs(inside, overrides); - const needsNewlineBeforeParen = /\n/.test(injectedArgs) && !/\n\s*$/.test(injectedArgs); - const joiner = needsNewlineBeforeParen ? "\n" : ""; - return before + match[1] + injectedArgs + joiner + match[4] + after; + + // Apply log override if present: replace argument of the last console.log/print call + const logExpr = (overrides as Record)?.log; + if (typeof logExpr === "string" && logExpr.trim().length > 0) { + if (lang === "python") { + const regex = /print\(([^\)]*)\)/g; + let lastMatch: RegExpExecArray | null = null; + let m: RegExpExecArray | null; + while ((m = regex.exec(transformed)) !== null) lastMatch = m; + if (lastMatch) { + const start = lastMatch.index; + const end = start + lastMatch[0].length; + transformed = + transformed.slice(0, start) + `print(${logExpr})` + transformed.slice(end); + } + } else { + const regex = /console\.log\(([^\)]*)\)/g; + let lastMatch: RegExpExecArray | null = null; + let m: RegExpExecArray | null; + while ((m = regex.exec(transformed)) !== null) lastMatch = m; + if (lastMatch) { + const start = lastMatch.index; + const end = start + lastMatch[0].length; + transformed = + transformed.slice(0, start) + `console.log(${logExpr})` + transformed.slice(end); + } + } } - return src; + + return transformed; } function parseOverridesString(raw: string): Record | undefined { @@ -318,7 +360,7 @@ function slugifyEndpoint(method: string, endpoint: string): string { async function writeSnippetFile(filePath: string, blocks: string) { const dir = path.dirname(filePath); await mkdir(dir, { recursive: true }); - const content = `\n${blocks.trim()}\n\n`; + const content = `\n${blocks.trim()}\n\n`; await writeFile(filePath, content, "utf8"); } @@ -403,7 +445,7 @@ async function processFile(file: string, spec: any) { if (inline) overrides = { ...(overrides || {}), ...inline }; } const blocks = extractCodeSamples(spec, endpoint, method, overrides).trim(); - return `\n${blocks}\n`; + return `\n${blocks}\n`; }); if (changed) { diff --git a/apps/status.mdx b/apps/status.mdx index ca3bb80..6b3c717 100644 --- a/apps/status.mdx +++ b/apps/status.mdx @@ -2,10 +2,10 @@ title: "Status" --- -Once you've [deployed](/apps/deploy) an app and invoked it, you can check its status. - import GetInvocationStatus from '/snippets/openapi/get-invocations-id.mdx'; +Once you've [deployed](/apps/deploy) an app and invoked it, you can check its status. + diff --git a/apps/stop.mdx b/apps/stop.mdx index d74e9db..4164b3c 100644 --- a/apps/stop.mdx +++ b/apps/stop.mdx @@ -2,6 +2,8 @@ title: "Stopping" --- +import StopInvocation from '/snippets/openapi/patch-invocations-id.mdx'; + You can terminate an invocation that's running. You can use this to stop automations or agents stuck in an infinite loop. @@ -11,10 +13,7 @@ Terminating an invocation also destroys any browsers associated with it. ## Via API You can also do this via the API: -import StopInvocation from '/snippets/openapi/patch-invocations-id.mdx'; - - ## Via CLI Use `ctrl-c` in the terminal tab where you launched the invocation. diff --git a/browsers/create-a-browser.mdx b/browsers/create-a-browser.mdx index 8fed5c7..832e735 100644 --- a/browsers/create-a-browser.mdx +++ b/browsers/create-a-browser.mdx @@ -2,14 +2,15 @@ title: "Create a Browser" --- +import CreateBrowserSnippet from '/snippets/openapi/post-browsers.mdx'; +import DeleteBrowserSnippet from '/snippets/openapi/delete-browsers-id.mdx'; + Kernel browsers were designed to be lightweight, fast, and efficient for cloud-based browser automations at scale. They can be used as part of the Kernel [app platform](/apps/develop) or connected to from another service with the Chrome DevTools Protocol. ## 1. Create a Kernel browser Use our SDK to create a browser: -import CreateBrowserSnippet from '/snippets/openapi/post-browsers.mdx'; - ## 2. Connect to the browser with the Chrome DevTools Protocol @@ -39,8 +40,6 @@ browser = playwright.chromium.connect_over_cdp(kernel_browser.cdp_ws_url) When you're finished with the browser, you can delete it: -import DeleteBrowserSnippet from '/snippets/openapi/delete-browsers-id.mdx'; - You can also delete the browser by [specifiying a timeout](/browsers/termination#timeouts-for-non-persisted-browsers) when you create the browser. diff --git a/browsers/live-view.mdx b/browsers/live-view.mdx index 63d9122..970825d 100644 --- a/browsers/live-view.mdx +++ b/browsers/live-view.mdx @@ -2,12 +2,12 @@ title: "Live View" --- +import CreateBrowserForLiveView from '/snippets/openapi/post-browsers-live-view.mdx'; + Humans-in-the-loop can access the live view of Kernel browsers in real-time to resolve errors or take unscripted actions. To access the live view, visit the `browser_live_view_url` provided when you create a Kernel browser: -import CreateBrowserForLiveView from '/snippets/openapi/post-browsers.mdx'; - ## Query parameters diff --git a/browsers/termination.mdx b/browsers/termination.mdx index 5c0996c..9798e67 100644 --- a/browsers/termination.mdx +++ b/browsers/termination.mdx @@ -2,14 +2,15 @@ title: "Termination & Timeouts" --- +import DeleteBrowser from '/snippets/openapi/delete-browsers-id.mdx'; +import CreateBrowserTimeout from '/snippets/openapi/post-browsers-timeout.mdx'; + Kernel browsers should be terminated after you're done with them. ## Via API You can delete a browser by making a `DELETE` request to Kernel's API: -import DeleteBrowser from '/snippets/openapi/delete-browsers-id.mdx'; - ## Timeouts for non-persisted browsers @@ -18,6 +19,4 @@ If you don't manually delete a `non-persisted` browser, deletion will happen aut You can set a custom timeout of up to 24 hours via the API: -import CreateBrowserTimeout from '/snippets/openapi/post-browsers.mdx'; - \ No newline at end of file diff --git a/snippets/openapi/delete-browsers-id-fs-watch-watch_id.mdx b/snippets/openapi/delete-browsers-id-fs-watch-watch_id.mdx index 4d2f442..95a91e2 100644 --- a/snippets/openapi/delete-browsers-id-fs-watch-watch_id.mdx +++ b/snippets/openapi/delete-browsers-id-fs-watch-watch_id.mdx @@ -1,5 +1,5 @@ - -```typescript index.ts + +```typescript Typescript/Javascript import Kernel from '@onkernel/sdk'; const client = new Kernel({ @@ -10,7 +10,7 @@ await client.browsers.fs.watch.stop('watch_id', { id: 'id' }); ``` -```python main.py +```python Python from kernel import Kernel client = Kernel( diff --git a/snippets/openapi/delete-browsers-id.mdx b/snippets/openapi/delete-browsers-id.mdx index 5074238..cdf3340 100644 --- a/snippets/openapi/delete-browsers-id.mdx +++ b/snippets/openapi/delete-browsers-id.mdx @@ -1,5 +1,5 @@ - -```typescript index.ts + +```typescript Typescript/Javascript import Kernel from '@onkernel/sdk'; const client = new Kernel({ @@ -10,7 +10,7 @@ await client.browsers.deleteByID('htzv5orfit78e1m2biiifpbv'); ``` -```python main.py +```python Python from kernel import Kernel client = Kernel( diff --git a/snippets/openapi/delete-browsers.mdx b/snippets/openapi/delete-browsers.mdx index ff2dcf4..09f6c4f 100644 --- a/snippets/openapi/delete-browsers.mdx +++ b/snippets/openapi/delete-browsers.mdx @@ -1,5 +1,5 @@ - -```typescript index.ts + +```typescript Typescript/Javascript import Kernel from '@onkernel/sdk'; const client = new Kernel({ @@ -10,7 +10,7 @@ await client.browsers.delete({ persistent_id: 'persistent_id' }); ``` -```python main.py +```python Python from kernel import Kernel client = Kernel( diff --git a/snippets/openapi/delete-invocations-id-browsers.mdx b/snippets/openapi/delete-invocations-id-browsers.mdx index 7b83d1d..9f8806c 100644 --- a/snippets/openapi/delete-invocations-id-browsers.mdx +++ b/snippets/openapi/delete-invocations-id-browsers.mdx @@ -1,5 +1,5 @@ - -```typescript index.ts + +```typescript Typescript/Javascript import Kernel from '@onkernel/sdk'; const client = new Kernel({ @@ -10,7 +10,7 @@ await client.invocations.deleteBrowsers('id'); ``` -```python main.py +```python Python from kernel import Kernel client = Kernel( diff --git a/snippets/openapi/get-apps.mdx b/snippets/openapi/get-apps.mdx index 0e3d2a5..e86399f 100644 --- a/snippets/openapi/get-apps.mdx +++ b/snippets/openapi/get-apps.mdx @@ -1,5 +1,5 @@ - -```typescript index.ts + +```typescript Typescript/Javascript import Kernel from '@onkernel/sdk'; const client = new Kernel({ @@ -12,7 +12,7 @@ console.log(apps); ``` -```python main.py +```python Python from kernel import Kernel client = Kernel( diff --git a/snippets/openapi/get-browsers-id-fs-file_info.mdx b/snippets/openapi/get-browsers-id-fs-file_info.mdx index 217294c..7a8875e 100644 --- a/snippets/openapi/get-browsers-id-fs-file_info.mdx +++ b/snippets/openapi/get-browsers-id-fs-file_info.mdx @@ -1,5 +1,5 @@ - -```typescript index.ts + +```typescript Typescript/Javascript import Kernel from '@onkernel/sdk'; const client = new Kernel({ @@ -12,7 +12,7 @@ console.log(response.is_dir); ``` -```python main.py +```python Python from kernel import Kernel client = Kernel( diff --git a/snippets/openapi/get-browsers-id-fs-list_files.mdx b/snippets/openapi/get-browsers-id-fs-list_files.mdx index e6ec680..60d0851 100644 --- a/snippets/openapi/get-browsers-id-fs-list_files.mdx +++ b/snippets/openapi/get-browsers-id-fs-list_files.mdx @@ -1,5 +1,5 @@ - -```typescript index.ts + +```typescript Typescript/Javascript import Kernel from '@onkernel/sdk'; const client = new Kernel({ @@ -12,7 +12,7 @@ console.log(response); ``` -```python main.py +```python Python from kernel import Kernel client = Kernel( diff --git a/snippets/openapi/get-browsers-id-fs-read_file.mdx b/snippets/openapi/get-browsers-id-fs-read_file.mdx index 5acd622..55976f8 100644 --- a/snippets/openapi/get-browsers-id-fs-read_file.mdx +++ b/snippets/openapi/get-browsers-id-fs-read_file.mdx @@ -1,5 +1,5 @@ - -```typescript index.ts + +```typescript Typescript/Javascript import Kernel from '@onkernel/sdk'; const client = new Kernel({ @@ -15,7 +15,7 @@ console.log(content); ``` -```python main.py +```python Python from kernel import Kernel client = Kernel( diff --git a/snippets/openapi/get-browsers-id-fs-watch-watch_id-events.mdx b/snippets/openapi/get-browsers-id-fs-watch-watch_id-events.mdx index 9f0b92d..fb4d252 100644 --- a/snippets/openapi/get-browsers-id-fs-watch-watch_id-events.mdx +++ b/snippets/openapi/get-browsers-id-fs-watch-watch_id-events.mdx @@ -1,5 +1,5 @@ - -```typescript index.ts + +```typescript Typescript/Javascript import Kernel from '@onkernel/sdk'; const client = new Kernel({ @@ -12,7 +12,7 @@ console.log(response.path); ``` -```python main.py +```python Python from kernel import Kernel client = Kernel( diff --git a/snippets/openapi/get-browsers-id-replays-replay_id.mdx b/snippets/openapi/get-browsers-id-replays-replay_id.mdx index bd6ca10..0c47cb2 100644 --- a/snippets/openapi/get-browsers-id-replays-replay_id.mdx +++ b/snippets/openapi/get-browsers-id-replays-replay_id.mdx @@ -1,5 +1,5 @@ - -```typescript index.ts + +```typescript Typescript/Javascript import Kernel from '@onkernel/sdk'; const client = new Kernel({ @@ -15,7 +15,7 @@ console.log(content); ``` -```python main.py +```python Python from kernel import Kernel client = Kernel( diff --git a/snippets/openapi/get-browsers-id-replays.mdx b/snippets/openapi/get-browsers-id-replays.mdx index 81a3d26..27eca8d 100644 --- a/snippets/openapi/get-browsers-id-replays.mdx +++ b/snippets/openapi/get-browsers-id-replays.mdx @@ -1,5 +1,5 @@ - -```typescript index.ts + +```typescript Typescript/Javascript import Kernel from '@onkernel/sdk'; const client = new Kernel({ @@ -12,7 +12,7 @@ console.log(replays); ``` -```python main.py +```python Python from kernel import Kernel client = Kernel( diff --git a/snippets/openapi/get-browsers-id.mdx b/snippets/openapi/get-browsers-id.mdx index b242cc3..bc84e3b 100644 --- a/snippets/openapi/get-browsers-id.mdx +++ b/snippets/openapi/get-browsers-id.mdx @@ -1,5 +1,5 @@ - -```typescript index.ts + +```typescript Typescript/Javascript import Kernel from '@onkernel/sdk'; const client = new Kernel({ @@ -12,7 +12,7 @@ console.log(browser.session_id); ``` -```python main.py +```python Python from kernel import Kernel client = Kernel( diff --git a/snippets/openapi/get-browsers.mdx b/snippets/openapi/get-browsers.mdx index 1b2e48c..ffc2c1c 100644 --- a/snippets/openapi/get-browsers.mdx +++ b/snippets/openapi/get-browsers.mdx @@ -1,5 +1,5 @@ - -```typescript index.ts + +```typescript Typescript/Javascript import Kernel from '@onkernel/sdk'; const client = new Kernel({ @@ -12,7 +12,7 @@ console.log(browsers); ``` -```python main.py +```python Python from kernel import Kernel client = Kernel( diff --git a/snippets/openapi/get-deployments-id-events.mdx b/snippets/openapi/get-deployments-id-events.mdx index f478852..b40d565 100644 --- a/snippets/openapi/get-deployments-id-events.mdx +++ b/snippets/openapi/get-deployments-id-events.mdx @@ -1,5 +1,5 @@ - -```typescript index.ts + +```typescript Typescript/Javascript import Kernel from '@onkernel/sdk'; const client = new Kernel({ @@ -12,7 +12,7 @@ console.log(response); ``` -```python main.py +```python Python from kernel import Kernel client = Kernel( diff --git a/snippets/openapi/get-deployments-id.mdx b/snippets/openapi/get-deployments-id.mdx index a930638..36935ca 100644 --- a/snippets/openapi/get-deployments-id.mdx +++ b/snippets/openapi/get-deployments-id.mdx @@ -1,5 +1,5 @@ - -```typescript index.ts + +```typescript Typescript/Javascript import Kernel from '@onkernel/sdk'; const client = new Kernel({ @@ -12,7 +12,7 @@ console.log(deployment.id); ``` -```python main.py +```python Python from kernel import Kernel client = Kernel( diff --git a/snippets/openapi/get-deployments.mdx b/snippets/openapi/get-deployments.mdx index 74ab696..7cfb64f 100644 --- a/snippets/openapi/get-deployments.mdx +++ b/snippets/openapi/get-deployments.mdx @@ -1,5 +1,5 @@ - -```typescript index.ts + +```typescript Typescript/Javascript import Kernel from '@onkernel/sdk'; const client = new Kernel({ @@ -12,7 +12,7 @@ console.log(deployments); ``` -```python main.py +```python Python from kernel import Kernel client = Kernel( diff --git a/snippets/openapi/get-invocations-id-events.mdx b/snippets/openapi/get-invocations-id-events.mdx index 8883176..fb08ac8 100644 --- a/snippets/openapi/get-invocations-id-events.mdx +++ b/snippets/openapi/get-invocations-id-events.mdx @@ -1,5 +1,5 @@ - -```typescript index.ts + +```typescript Typescript/Javascript import Kernel from '@onkernel/sdk'; const client = new Kernel({ @@ -12,7 +12,7 @@ console.log(response); ``` -```python main.py +```python Python from kernel import Kernel client = Kernel( diff --git a/snippets/openapi/get-invocations-id.mdx b/snippets/openapi/get-invocations-id.mdx index c61b012..7dbd194 100644 --- a/snippets/openapi/get-invocations-id.mdx +++ b/snippets/openapi/get-invocations-id.mdx @@ -1,5 +1,5 @@ - -```typescript index.ts + +```typescript Typescript/Javascript import Kernel from '@onkernel/sdk'; const client = new Kernel({ @@ -12,7 +12,7 @@ console.log(invocation.id); ``` -```python main.py +```python Python from kernel import Kernel client = Kernel( diff --git a/snippets/openapi/patch-invocations-id.mdx b/snippets/openapi/patch-invocations-id.mdx index 18879fc..6eebf72 100644 --- a/snippets/openapi/patch-invocations-id.mdx +++ b/snippets/openapi/patch-invocations-id.mdx @@ -1,5 +1,5 @@ - -```typescript index.ts + +```typescript Typescript/Javascript import Kernel from '@onkernel/sdk'; const client = new Kernel({ @@ -12,7 +12,7 @@ console.log(invocation.id); ``` -```python main.py +```python Python from kernel import Kernel client = Kernel( diff --git a/snippets/openapi/post-browsers-id-fs-watch.mdx b/snippets/openapi/post-browsers-id-fs-watch.mdx index ccd514d..fcd1dbf 100644 --- a/snippets/openapi/post-browsers-id-fs-watch.mdx +++ b/snippets/openapi/post-browsers-id-fs-watch.mdx @@ -1,5 +1,5 @@ - -```typescript index.ts + +```typescript Typescript/Javascript import Kernel from '@onkernel/sdk'; const client = new Kernel({ @@ -12,7 +12,7 @@ console.log(response.watch_id); ``` -```python main.py +```python Python from kernel import Kernel client = Kernel( diff --git a/snippets/openapi/post-browsers-id-replays-replay_id-stop.mdx b/snippets/openapi/post-browsers-id-replays-replay_id-stop.mdx index eec20a0..7658510 100644 --- a/snippets/openapi/post-browsers-id-replays-replay_id-stop.mdx +++ b/snippets/openapi/post-browsers-id-replays-replay_id-stop.mdx @@ -1,5 +1,5 @@ - -```typescript index.ts + +```typescript Typescript/Javascript import Kernel from '@onkernel/sdk'; const client = new Kernel({ @@ -10,7 +10,7 @@ await client.browsers.replays.stop('replay_id', { id: 'id' }); ``` -```python main.py +```python Python from kernel import Kernel client = Kernel( diff --git a/snippets/openapi/post-browsers-id-replays.mdx b/snippets/openapi/post-browsers-id-replays.mdx index aaf3460..4b7a515 100644 --- a/snippets/openapi/post-browsers-id-replays.mdx +++ b/snippets/openapi/post-browsers-id-replays.mdx @@ -1,5 +1,5 @@ - -```typescript index.ts + +```typescript Typescript/Javascript import Kernel from '@onkernel/sdk'; const client = new Kernel({ @@ -12,7 +12,7 @@ console.log(response.replay_id); ``` -```python main.py +```python Python from kernel import Kernel client = Kernel( diff --git a/snippets/openapi/post-browsers-live-view.mdx b/snippets/openapi/post-browsers-live-view.mdx new file mode 100644 index 0000000..b511024 --- /dev/null +++ b/snippets/openapi/post-browsers-live-view.mdx @@ -0,0 +1,24 @@ + +```typescript Typescript/Javascript +import Kernel from '@onkernel/sdk'; + +const client = new Kernel({ + apiKey: 'My API Key', +}); + +const browser = await client.browsers.create(); + +console.log(browser.browser_live_view_url); +``` + + +```python Python +from kernel import Kernel + +client = Kernel( + api_key="My API Key", +) +browser = client.browsers.create() +print(browser.browser_live_view_url) +``` + diff --git a/snippets/openapi/post-browsers-timeout.mdx b/snippets/openapi/post-browsers-timeout.mdx new file mode 100644 index 0000000..d5e0644 --- /dev/null +++ b/snippets/openapi/post-browsers-timeout.mdx @@ -0,0 +1,24 @@ + +```typescript Typescript/Javascript +import Kernel from '@onkernel/sdk'; + +const client = new Kernel({ + apiKey: 'My API Key', +}); + +const browser = await client.browsers.create({timeout_seconds: 300 }); + +console.log(browser.session_id); +``` + + +```python Python +from kernel import Kernel + +client = Kernel( + api_key="My API Key", +) +browser = client.browsers.create(timeout_seconds=300) +print(browser.session_id) +``` + diff --git a/snippets/openapi/post-browsers.mdx b/snippets/openapi/post-browsers.mdx index 84a73bf..264136b 100644 --- a/snippets/openapi/post-browsers.mdx +++ b/snippets/openapi/post-browsers.mdx @@ -1,5 +1,5 @@ - -```typescript index.ts + +```typescript Typescript/Javascript import Kernel from '@onkernel/sdk'; const client = new Kernel({ @@ -12,7 +12,7 @@ console.log(browser.session_id); ``` -```python main.py +```python Python from kernel import Kernel client = Kernel( diff --git a/snippets/openapi/post-deployments.mdx b/snippets/openapi/post-deployments.mdx index db97b41..6212992 100644 --- a/snippets/openapi/post-deployments.mdx +++ b/snippets/openapi/post-deployments.mdx @@ -1,5 +1,5 @@ - -```typescript index.ts + +```typescript Typescript/Javascript import Kernel from '@onkernel/sdk'; const client = new Kernel({ @@ -15,7 +15,7 @@ console.log(deployment.id); ``` -```python main.py +```python Python from kernel import Kernel client = Kernel( diff --git a/snippets/openapi/post-invocations-async.mdx b/snippets/openapi/post-invocations-async.mdx index 4dd698c..1e62844 100644 --- a/snippets/openapi/post-invocations-async.mdx +++ b/snippets/openapi/post-invocations-async.mdx @@ -1,5 +1,5 @@ - -```typescript index.ts + +```typescript Typescript/Javascript import Kernel from '@onkernel/sdk'; const client = new Kernel({ @@ -10,14 +10,13 @@ const invocation = await client.invocations.create({ async: true, action_name: 'analyze', app_name: 'my-app', - version: '1.0.0', -}); + version: '1.0.0' }); console.log(invocation.id); ``` -```python main.py +```python Python from kernel import Kernel client = Kernel( diff --git a/snippets/openapi/post-invocations.mdx b/snippets/openapi/post-invocations.mdx index dff4147..31759ec 100644 --- a/snippets/openapi/post-invocations.mdx +++ b/snippets/openapi/post-invocations.mdx @@ -1,5 +1,5 @@ - -```typescript index.ts + +```typescript Typescript/Javascript import Kernel from '@onkernel/sdk'; const client = new Kernel({ @@ -16,7 +16,7 @@ console.log(invocation.id); ``` -```python main.py +```python Python from kernel import Kernel client = Kernel( diff --git a/snippets/openapi/put-browsers-id-fs-create_directory.mdx b/snippets/openapi/put-browsers-id-fs-create_directory.mdx index 3a19a58..c7607d3 100644 --- a/snippets/openapi/put-browsers-id-fs-create_directory.mdx +++ b/snippets/openapi/put-browsers-id-fs-create_directory.mdx @@ -1,5 +1,5 @@ - -```typescript index.ts + +```typescript Typescript/Javascript import Kernel from '@onkernel/sdk'; const client = new Kernel({ @@ -10,7 +10,7 @@ await client.browsers.fs.createDirectory('id', { path: '/J!' }); ``` -```python main.py +```python Python from kernel import Kernel client = Kernel( diff --git a/snippets/openapi/put-browsers-id-fs-delete_directory.mdx b/snippets/openapi/put-browsers-id-fs-delete_directory.mdx index 50b9f8d..a2758b6 100644 --- a/snippets/openapi/put-browsers-id-fs-delete_directory.mdx +++ b/snippets/openapi/put-browsers-id-fs-delete_directory.mdx @@ -1,5 +1,5 @@ - -```typescript index.ts + +```typescript Typescript/Javascript import Kernel from '@onkernel/sdk'; const client = new Kernel({ @@ -10,7 +10,7 @@ await client.browsers.fs.deleteDirectory('id', { path: '/J!' }); ``` -```python main.py +```python Python from kernel import Kernel client = Kernel( diff --git a/snippets/openapi/put-browsers-id-fs-delete_file.mdx b/snippets/openapi/put-browsers-id-fs-delete_file.mdx index 86a9a9b..ee84b33 100644 --- a/snippets/openapi/put-browsers-id-fs-delete_file.mdx +++ b/snippets/openapi/put-browsers-id-fs-delete_file.mdx @@ -1,5 +1,5 @@ - -```typescript index.ts + +```typescript Typescript/Javascript import Kernel from '@onkernel/sdk'; const client = new Kernel({ @@ -10,7 +10,7 @@ await client.browsers.fs.deleteFile('id', { path: '/J!' }); ``` -```python main.py +```python Python from kernel import Kernel client = Kernel( diff --git a/snippets/openapi/put-browsers-id-fs-move.mdx b/snippets/openapi/put-browsers-id-fs-move.mdx index 5cf4ef3..8fc4cfb 100644 --- a/snippets/openapi/put-browsers-id-fs-move.mdx +++ b/snippets/openapi/put-browsers-id-fs-move.mdx @@ -1,5 +1,5 @@ - -```typescript index.ts + +```typescript Typescript/Javascript import Kernel from '@onkernel/sdk'; const client = new Kernel({ @@ -10,7 +10,7 @@ await client.browsers.fs.move('id', { dest_path: '/J!', src_path: '/J!' }); ``` -```python main.py +```python Python from kernel import Kernel client = Kernel( diff --git a/snippets/openapi/put-browsers-id-fs-set_file_permissions.mdx b/snippets/openapi/put-browsers-id-fs-set_file_permissions.mdx index b4e207f..f898c3b 100644 --- a/snippets/openapi/put-browsers-id-fs-set_file_permissions.mdx +++ b/snippets/openapi/put-browsers-id-fs-set_file_permissions.mdx @@ -1,5 +1,5 @@ - -```typescript index.ts + +```typescript Typescript/Javascript import Kernel from '@onkernel/sdk'; const client = new Kernel({ @@ -10,7 +10,7 @@ await client.browsers.fs.setFilePermissions('id', { mode: '0611', path: '/J!' }) ``` -```python main.py +```python Python from kernel import Kernel client = Kernel( diff --git a/snippets/openapi/put-browsers-id-fs-write_file.mdx b/snippets/openapi/put-browsers-id-fs-write_file.mdx index 9a9067e..9b6c5de 100644 --- a/snippets/openapi/put-browsers-id-fs-write_file.mdx +++ b/snippets/openapi/put-browsers-id-fs-write_file.mdx @@ -1,5 +1,5 @@ - -```typescript index.ts + +```typescript Typescript/Javascript import Kernel from '@onkernel/sdk'; const client = new Kernel({ @@ -10,7 +10,7 @@ await client.browsers.fs.writeFile('id', fs.createReadStream('path/to/file'), { ``` -```python main.py +```python Python from kernel import Kernel client = Kernel( From 4a791443f63e7a481278b151d20012ead5b4f3ec Mon Sep 17 00:00:00 2001 From: Christian Glassiognon <63924603+heyglassy@users.noreply.github.com> Date: Tue, 19 Aug 2025 12:01:58 -0700 Subject: [PATCH 16/21] update script so that it generates before we merge to main --- .github/workflows/generate_code_snippets.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/generate_code_snippets.yaml b/.github/workflows/generate_code_snippets.yaml index e5faf84..fa2d0b6 100644 --- a/.github/workflows/generate_code_snippets.yaml +++ b/.github/workflows/generate_code_snippets.yaml @@ -2,7 +2,7 @@ name: Update Docs with Code Samples from OpenAPI on: push: - branches: + branches-ignore: - main permissions: From 8108b14debb6319ba566107a3ef953ec064df32c Mon Sep 17 00:00:00 2001 From: Christian Glassiognon <63924603+heyglassy@users.noreply.github.com> Date: Tue, 19 Aug 2025 12:36:18 -0700 Subject: [PATCH 17/21] move script to run on dev branches and not main --- .github/scripts/generate_code_samples.ts | 166 ++++++++++++-------- README.md | 26 ++- snippets/openapi/post-invocations-async.mdx | 3 +- 3 files changed, 127 insertions(+), 68 deletions(-) diff --git a/.github/scripts/generate_code_samples.ts b/.github/scripts/generate_code_samples.ts index e3ce663..2e6188d 100644 --- a/.github/scripts/generate_code_samples.ts +++ b/.github/scripts/generate_code_samples.ts @@ -12,24 +12,38 @@ const SNIPPETS_ROOT = path.resolve("snippets/openapi"); // Only emit code samples for these languages (normalized): e.g., ["typescript", "python"] const TARGET_LANGUAGES = ["typescript", "python"] as const; -type SupportedLanguage = (typeof TARGET_LANGUAGES)[number] | "javascript" | "go"; +type SupportedLanguage = + | (typeof TARGET_LANGUAGES)[number] + | "javascript" + | "go"; function normalizeLang(input: string): SupportedLanguage { const lc = (input || "").toLowerCase(); if (lc === "ts" || lc === "typescript") return "typescript"; if (lc === "py" || lc === "python") return "python"; - if (lc === "js" || lc === "javascript" || lc === "node" || lc === "node.js" || lc === "nodejs") return "javascript"; + if ( + lc === "js" || + lc === "javascript" || + lc === "node" || + lc === "node.js" || + lc === "nodejs" + ) + return "javascript"; if (lc === "go" || lc === "golang") return "go"; return lc as SupportedLanguage; } function renderFenceInfo(lang: SupportedLanguage, rawLang: string): string { - if (lang === "typescript" || lang === "javascript") return "typescript Typescript/Javascript"; + if (lang === "typescript" || lang === "javascript") + return "typescript Typescript/Javascript"; if (lang === "python") return "python Python"; return `${lang} ${toTitleCase(rawLang)}`; } -function renderValueForLanguage(lang: SupportedLanguage, value: unknown): string { +function renderValueForLanguage( + lang: SupportedLanguage, + value: unknown +): string { const type = typeof value; if (type === "boolean") { const b = value as boolean; @@ -84,7 +98,7 @@ function injectParamsIntoObjectLiteral( : `${key}: ${valueStr}, `; if (isMultiline) { // Normalize to a single newline after '{' then insert our line - updated = updated.replace(/\{\s*/, '{\n'); + updated = updated.replace(/\{\s*/, "{\n"); updated = updated.replace(/\{\n/, `{\n${insertion}`); } else { updated = updated.replace(/\{\s*/, (m) => m + insertion); @@ -120,7 +134,8 @@ function injectParamsIntoPythonArgs( if (isMultiline) { const firstArgIndentMatch = updated.match(/^([ \t]+)[A-Za-z_]/m); const lastLineIndentMatch = updated.match(/\n([ \t]*)[^\n]*$/); - const indent = firstArgIndentMatch?.[1] ?? lastLineIndentMatch?.[1] ?? " "; + const indent = + firstArgIndentMatch?.[1] ?? lastLineIndentMatch?.[1] ?? " "; const prefix = updated.replace(/\s*$/, ""); const commaPrefix = hasTrailingComma ? prefix : `${prefix},`; updated = `${commaPrefix}\n${indent}${key}=${valueStr},`; @@ -143,7 +158,10 @@ function applyOverridesToSource( let transformed = src; // Heuristic: find the first object literal in a X.create(...) call and inject params (excluding special keys like 'log') - const { log: _logOverride, ...injectionOverrides } = overrides as Record; + const { log: _logOverride, ...injectionOverrides } = overrides as Record< + string, + unknown + >; if (Object.keys(injectionOverrides).length > 0) { const createCall = /(\b\w+\.(create|new)\()([\s\S]*?)(\))/m; const match = transformed.match(createCall); @@ -155,18 +173,33 @@ function applyOverridesToSource( if (objectMatch) { const objBefore = inside.slice(0, objectMatch.index!); const obj = objectMatch[0]; - const objAfter = inside.slice((objectMatch.index || 0) + objectMatch[0].length); - const injected = injectParamsIntoObjectLiteral(lang, obj, injectionOverrides); + const objAfter = inside.slice( + (objectMatch.index || 0) + objectMatch[0].length + ); + const injected = injectParamsIntoObjectLiteral( + lang, + obj, + injectionOverrides + ); const newInside = objBefore + injected + objAfter; transformed = before + match[1] + newInside + match[4] + after; } else if (lang === "python") { - const injectedArgs = injectParamsIntoPythonArgs(inside, injectionOverrides); - const needsNewlineBeforeParen = /\n/.test(injectedArgs) && !/\n\s*$/.test(injectedArgs); + const injectedArgs = injectParamsIntoPythonArgs( + inside, + injectionOverrides + ); + const needsNewlineBeforeParen = + /\n/.test(injectedArgs) && !/\n\s*$/.test(injectedArgs); const joiner = needsNewlineBeforeParen ? "\n" : ""; - transformed = before + match[1] + injectedArgs + joiner + match[4] + after; + transformed = + before + match[1] + injectedArgs + joiner + match[4] + after; } else { // JS/TS with no object literal: insert one and inject - const injectedObj = injectParamsIntoObjectLiteral(lang, "{}", injectionOverrides); + const injectedObj = injectParamsIntoObjectLiteral( + lang, + "{}", + injectionOverrides + ); transformed = before + match[1] + injectedObj + match[4] + after; } } @@ -184,7 +217,9 @@ function applyOverridesToSource( const start = lastMatch.index; const end = start + lastMatch[0].length; transformed = - transformed.slice(0, start) + `print(${logExpr})` + transformed.slice(end); + transformed.slice(0, start) + + `print(${logExpr})` + + transformed.slice(end); } } else { const regex = /console\.log\(([^\)]*)\)/g; @@ -195,7 +230,9 @@ function applyOverridesToSource( const start = lastMatch.index; const end = start + lastMatch[0].length; transformed = - transformed.slice(0, start) + `console.log(${logExpr})` + transformed.slice(end); + transformed.slice(0, start) + + `console.log(${logExpr})` + + transformed.slice(end); } } } @@ -203,19 +240,11 @@ function applyOverridesToSource( return transformed; } -function parseOverridesString(raw: string): Record | undefined { - const trim = raw.trim(); - // Strip one layer of surrounding braces if present - let inner = trim; - if (inner.startsWith("{") && inner.endsWith("}")) { - inner = inner.slice(1, -1).trim(); - } - // If wrapped as {{ ... }}, strip outer again - if (inner.startsWith("{") && inner.endsWith("}")) { - inner = inner.slice(1, -1).trim(); - } +function parseOverridesString( + raw: string +): Record | undefined { // Rebuild as JSON object string; heuristically quote keys - let jsonish = `{ ${inner} }`; + let jsonish = `{ ${raw} }`; // Quote unquoted keys (simple heuristic) jsonish = jsonish.replace(/([,{\s])([A-Za-z_][A-Za-z0-9_]*)\s*:/g, '$1"$2":'); // Normalize single quotes to double quotes @@ -227,19 +256,21 @@ function parseOverridesString(raw: string): Record | undefined } } -function extractOverridesFromAttributes(attrs: string | undefined): Record | undefined { +function extractOverridesFromAttributes( + attrs: string | undefined +): Record | undefined { if (!attrs) return undefined; const idx = attrs.indexOf("overrides="); if (idx === -1) return undefined; // Find first '{' after 'overrides=' - const start = attrs.indexOf('{', idx); + const start = attrs.indexOf("{", idx); if (start === -1) return undefined; let depth = 0; let end = -1; for (let i = start; i < attrs.length; i++) { const ch = attrs[i]; - if (ch === '{') depth++; - else if (ch === '}') { + if (ch === "{") depth++; + else if (ch === "}") { depth--; if (depth === 0) { end = i; @@ -320,8 +351,13 @@ function extractCodeSamples( for (const sample of op["x-codeSamples"]) { const rawLang = typeof sample.lang === "string" ? sample.lang : ""; const normalized = normalizeLang(rawLang); - const allowedNormalized = ["typescript", "javascript", "python"] as const; - if (!(allowedNormalized as readonly string[]).includes(normalized)) continue; + const allowedNormalized = [ + "typescript", + "javascript", + "python", + ] as const; + if (!(allowedNormalized as readonly string[]).includes(normalized)) + continue; const displayLang: "typescript" | "python" = normalized === "python" ? "python" : "typescript"; @@ -376,16 +412,25 @@ async function generateSnippets(spec: any) { const baseSlug = slugifyEndpoint(method, endpoint); const baseFile = path.join(SNIPPETS_ROOT, `${baseSlug}.mdx`); await writeSnippetFile(baseFile, baseBlocks); - console.log(`🧩 Wrote snippet: ${path.relative(process.cwd(), baseFile)}`); + console.log( + `🧩 Wrote snippet: ${path.relative(process.cwd(), baseFile)}` + ); } const variants = config.variants?.[key] || []; for (const variant of variants) { - const vBlocks = extractCodeSamples(spec, endpoint, method, variant.overrides).trim(); + const vBlocks = extractCodeSamples( + spec, + endpoint, + method, + variant.overrides + ).trim(); if (!vBlocks.startsWith("⚠️ ")) { const vSlug = `${slugifyEndpoint(method, endpoint)}-${variant.name}`; const vFile = path.join(SNIPPETS_ROOT, `${vSlug}.mdx`); await writeSnippetFile(vFile, vBlocks); - console.log(`🧩 Wrote snippet: ${path.relative(process.cwd(), vFile)}`); + console.log( + `🧩 Wrote snippet: ${path.relative(process.cwd(), vFile)}` + ); } } } @@ -402,7 +447,9 @@ async function cleanupOldSnippets() { for (const name of stale) { const full = path.join(SNIPPETS_ROOT, name); await unlink(full); - console.log(`🧹 Removed stale snippet: ${path.relative(process.cwd(), full)}`); + console.log( + `🧹 Removed stale snippet: ${path.relative(process.cwd(), full)}` + ); } } catch { // ignore if folder doesn't exist yet @@ -412,41 +459,32 @@ async function cleanupOldSnippets() { async function processFile(file: string, spec: any) { let content = await readFile(file, "utf8"); - // Matches {{ get /path }} or {{ post /path }} or {{ /path }} - const mustacheRegex = - /\{\{\s*(?:(get|post|put|delete|patch|options|head)\s+)?(\/[^\s}]+)(?:\s+(\{[\s\S]*?\}))?\s*\}\}/gi; - // Matches get /path // or /path const tagRegex = /]*)>\s*(?:(get|post|put|delete|patch|options|head)\s+)?(\/[^\s<]+)(?:\s+(\{[\s\S]*?\}))?\s*<\/OpenAPICodeGroup>/gi; let changed = false; - content = content.replace(mustacheRegex, (_, method, endpoint, jsonOverrides) => { - changed = true; - let overrides: Record | undefined = undefined; - if (jsonOverrides) { - try { - overrides = JSON.parse(jsonOverrides); - } catch { - console.warn(`Failed to parse overrides JSON in ${file} for ${endpoint}`); + content = content.replace( + tagRegex, + (_, attrs, method, endpoint, jsonOverrides) => { + changed = true; + let overrides: Record | undefined = undefined; + const attrOverrides = extractOverridesFromAttributes(attrs); + if (attrOverrides) overrides = { ...attrOverrides }; + if (jsonOverrides) { + const inline = parseOverridesString(jsonOverrides); + if (inline) overrides = { ...(overrides || {}), ...inline }; } + const blocks = extractCodeSamples( + spec, + endpoint, + method, + overrides + ).trim(); + return `\n${blocks}\n`; } - return extractCodeSamples(spec, endpoint, method, overrides); - }); - - content = content.replace(tagRegex, (_, attrs, method, endpoint, jsonOverrides) => { - changed = true; - let overrides: Record | undefined = undefined; - const attrOverrides = extractOverridesFromAttributes(attrs); - if (attrOverrides) overrides = { ...attrOverrides }; - if (jsonOverrides) { - const inline = parseOverridesString(jsonOverrides); - if (inline) overrides = { ...(overrides || {}), ...inline }; - } - const blocks = extractCodeSamples(spec, endpoint, method, overrides).trim(); - return `\n${blocks}\n`; - }); + ); if (changed) { console.log(`✏️ Updating ${file}`); diff --git a/README.md b/README.md index 8335771..c9dd00c 100644 --- a/README.md +++ b/README.md @@ -11,11 +11,31 @@ This is the documentation for the Kernel platform. It's connected to [docs.onker ## Code Snippets -In order to sync our code snippets in our docs ( not playground ) with the OpenAPI spec, we put `[get|put|post|delete] [path]` in the docs. +Code samples in the docs are generated from our OpenAPI spec so the examples stay in sync with the API. There are two ways the generator is invoked: -For example, if we want to add a code snippet for the `GET /api/v1/users` endpoint, we would put `get /api/v1/users` in the docs. +- Custom MDX tag: use `get /api/v1/users` (or omit the verb to use the default behavior). -We then use a GitHub Action to generate the code snippets from the docs and push them to the `gh_action_generated_docs` branch. Mintlify will then deploy the docs from that branch. +How the generator works (current behavior): + +- The script is `.github/scripts/generate_code_samples.ts` and is executed with Bun. It fetches the OpenAPI spec from the URL configured at the top of that script. +- It reads `x-codeSamples` entries for each operation and extracts samples for TypeScript/JavaScript and Python. Samples are normalized and may be transformed by simple "overrides" (see the script for the override parsing and injection heuristics). +- It writes snippet files under `snippets/openapi/` as MDX files containing a `` with the generated code fences. It also updates any MDX files under `apps/` and `browsers/` that contain the inline or tag forms by replacing them with the generated `` blocks. +- The generator can produce a base snippet and additional variant snippets controlled by `.github/scripts/code_samples.config.json` (variants are keyed by `"method /path"`). +- The script also removes stale snippet files matching the `-.mdx` suffix pattern. + +How it affects the repository: + +- New or updated files are created under `snippets/openapi/*.mdx`. +- MDX pages in `apps/` and `browsers/` may be modified in-place to replace ``/mustache tags with generated `` blocks. +- A GitHub Action (`.github/workflows/generate_code_snippets.yaml`) runs the script on push (except to `main`), commits any changes, and pushes them to the `gh_action_generated_docs` branch so Mintlify can deploy the generated docs. + +Notes and gotchas: + +- The generator runs remotely against the OpenAPI URL defined in the script, so it needs network access and the spec to include `x-codeSamples` for useful output. +- The override parsing and code injection are heuristic — complex sample sources might not be transformed exactly as intended. See the script for details on how keys/`log` overrides are applied. +- The GitHub Action installs Bun and runs the script; if you run it locally, install Bun and run `bun run .github/scripts/generate_code_samples.ts` from the repo root. + +Example: to add a snippet placeholder to a page, add `get /api/v1/users` and let the generator fill `snippets/openapi` and update the page during the next run. ## Local Development diff --git a/snippets/openapi/post-invocations-async.mdx b/snippets/openapi/post-invocations-async.mdx index 1e62844..f464999 100644 --- a/snippets/openapi/post-invocations-async.mdx +++ b/snippets/openapi/post-invocations-async.mdx @@ -10,7 +10,8 @@ const invocation = await client.invocations.create({ async: true, action_name: 'analyze', app_name: 'my-app', - version: '1.0.0' }); + version: '1.0.0' +}); console.log(invocation.id); ``` From 67df9c1db42fdc2c204f44f1d98d0f8f7cf30d74 Mon Sep 17 00:00:00 2001 From: Christian Glassiognon <63924603+heyglassy@users.noreply.github.com> Date: Tue, 19 Aug 2025 12:40:03 -0700 Subject: [PATCH 18/21] test --- .github/workflows/generate_code_snippets.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/generate_code_snippets.yaml b/.github/workflows/generate_code_snippets.yaml index fa2d0b6..d53ddc8 100644 --- a/.github/workflows/generate_code_snippets.yaml +++ b/.github/workflows/generate_code_snippets.yaml @@ -29,4 +29,4 @@ jobs: run: | git add . git commit -m "docs: update code samples from OpenAPI" || echo "No changes" - git push origin HEAD:gh_action_generated_docs + git push origin "HEAD:${{ github.ref }}" From d9e648e425f0683668349247d101b534a5e97183 Mon Sep 17 00:00:00 2001 From: Christian Glassiognon <63924603+heyglassy@users.noreply.github.com> Date: Tue, 19 Aug 2025 12:42:47 -0700 Subject: [PATCH 19/21] test --- .github/workflows/generate_code_snippets.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/generate_code_snippets.yaml b/.github/workflows/generate_code_snippets.yaml index d53ddc8..abab2ac 100644 --- a/.github/workflows/generate_code_snippets.yaml +++ b/.github/workflows/generate_code_snippets.yaml @@ -27,6 +27,8 @@ jobs: - name: Commit changes run: | + git config --global user.name "Kernel Docs Bot" + git config --global user.email "kernel@onkernel.com" git add . git commit -m "docs: update code samples from OpenAPI" || echo "No changes" git push origin "HEAD:${{ github.ref }}" From c9f102faf9b7a341105517356a287447b9ee2c58 Mon Sep 17 00:00:00 2001 From: Kernel Docs Bot Date: Tue, 19 Aug 2025 19:43:01 +0000 Subject: [PATCH 20/21] docs: update code samples from OpenAPI --- snippets/openapi/post-invocations-async.mdx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/snippets/openapi/post-invocations-async.mdx b/snippets/openapi/post-invocations-async.mdx index f464999..1e62844 100644 --- a/snippets/openapi/post-invocations-async.mdx +++ b/snippets/openapi/post-invocations-async.mdx @@ -10,8 +10,7 @@ const invocation = await client.invocations.create({ async: true, action_name: 'analyze', app_name: 'my-app', - version: '1.0.0' -}); + version: '1.0.0' }); console.log(invocation.id); ``` From b5e2b2f44ac13fe4db34e86b2e427828b394784c Mon Sep 17 00:00:00 2001 From: Christian Glassiognon <63924603+heyglassy@users.noreply.github.com> Date: Tue, 19 Aug 2025 12:44:27 -0700 Subject: [PATCH 21/21] test again --- .github/workflows/generate_code_snippets.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/generate_code_snippets.yaml b/.github/workflows/generate_code_snippets.yaml index abab2ac..753d74a 100644 --- a/.github/workflows/generate_code_snippets.yaml +++ b/.github/workflows/generate_code_snippets.yaml @@ -27,8 +27,8 @@ jobs: - name: Commit changes run: | - git config --global user.name "Kernel Docs Bot" - git config --global user.email "kernel@onkernel.com" + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" git add . git commit -m "docs: update code samples from OpenAPI" || echo "No changes" git push origin "HEAD:${{ github.ref }}"