diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 158005f4..00f70ad1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ on: jobs: sdk: - runs-on: ubuntu-latest + runs-on: ${{ (startsWith(vars.CI_RUNNER, 'blacksmith-') && vars.CI_RUNNER) || 'blacksmith-4vcpu-ubuntu-2404' }} strategy: fail-fast: false matrix: @@ -17,7 +17,7 @@ jobs: run: working-directory: sdks/typescript-sdk steps: - - uses: actions/checkout@v4 + - uses: useblacksmith/checkout@v1 - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} @@ -42,9 +42,9 @@ jobs: run: npm run lint mcp: - runs-on: ubuntu-latest + runs-on: ${{ (startsWith(vars.CI_RUNNER, 'blacksmith-') && vars.CI_RUNNER) || 'blacksmith-4vcpu-ubuntu-2404' }} steps: - - uses: actions/checkout@v4 + - uses: useblacksmith/checkout@v1 - uses: actions/setup-node@v4 with: node-version: 24 diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index b5e8cfd4..fa4de10a 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -18,21 +18,26 @@ jobs: # github.event.pull_request.user.login == 'new-developer' || # github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' - runs-on: ubuntu-latest + runs-on: ${{ (startsWith(vars.CI_RUNNER_SMALL, 'blacksmith-') && vars.CI_RUNNER_SMALL) || 'blacksmith-2vcpu-ubuntu-2404' }} + timeout-minutes: 30 permissions: contents: read - pull-requests: read + pull-requests: write issues: read id-token: write + concurrency: + group: claude-review-${{ github.event.pull_request.number }} + cancel-in-progress: true steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: useblacksmith/checkout@v1 with: - fetch-depth: 1 + fetch-depth: 50 - name: Run Claude Code Review id: claude-review + continue-on-error: true uses: anthropics/claude-code-action@v1 with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} @@ -41,4 +46,3 @@ jobs: prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}' # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md # or https://code.claude.com/docs/en/cli-reference for available options - diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index 6b15fac7..2409615f 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -1,50 +1,102 @@ -name: Claude Code +name: Claude Code (@claude) on: issue_comment: - types: [created] + types: [created, edited] pull_request_review_comment: - types: [created] + types: [created, edited] issues: - types: [opened, assigned] + types: [opened, assigned, edited] pull_request_review: - types: [submitted] + types: [submitted, edited] jobs: claude: if: | - (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || - (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) - runs-on: ubuntu-latest + github.actor != 'claude[bot]' && ( + ( + github.event_name == 'issue_comment' && + contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) && + contains(github.event.comment.body, '@claude') + ) || + ( + github.event_name == 'pull_request_review_comment' && + contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) && + contains(github.event.comment.body, '@claude') + ) || + ( + github.event_name == 'pull_request_review' && + contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.review.author_association) && + contains(github.event.review.body, '@claude') + ) || + ( + github.event_name == 'issues' && + contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.issue.author_association) && + (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')) + ) + ) + runs-on: ${{ (startsWith(vars.CI_RUNNER_SMALL, 'blacksmith-') && vars.CI_RUNNER_SMALL) || 'blacksmith-2vcpu-ubuntu-2404' }} + timeout-minutes: 45 permissions: - contents: read - pull-requests: read - issues: read + contents: write + pull-requests: write + issues: write id-token: write - actions: read # Required for Claude to read CI results on PRs + actions: read + checks: read + concurrency: + group: claude-interactive-${{ github.repository }}-${{ github.event.issue.number || github.event.pull_request.number || github.run_id }} + cancel-in-progress: false + steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: useblacksmith/checkout@v1 with: - fetch-depth: 1 + fetch-depth: 50 + + - name: Verify Claude auth secret + env: + CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + run: | + if [ -z "$CLAUDE_CODE_OAUTH_TOKEN" ] && [ -z "$ANTHROPIC_API_KEY" ]; then + echo "::error::Set CLAUDE_CODE_OAUTH_TOKEN or ANTHROPIC_API_KEY in this repository's Actions secrets." + exit 1 + fi - name: Run Claude Code id: claude uses: anthropics/claude-code-action@v1 with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} - # This is an optional setting that allows Claude to read CI results on PRs additional_permissions: | actions: read + checks: read + + claude_args: | + --append-system-prompt "Read AGENTS.md before making changes and follow the documentation repository rules. + + This is the public Terminal49 API documentation repository. + + Project structure: + - docs/ contains the Mintlify site content and configuration. + - docs/openapi.json is the source of truth for API reference content. + - Terminal49-API.postman_collection.json is generated from docs/openapi.json and must not be edited manually. + - WRITING_GUIDE.md contains writing standards, voice, terminology, and content guidelines. + - docs/AGENTS.md may contain more specific rules for docs/ changes. - # Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it. - # prompt: 'Update the pull request description to include a summary of changes.' + Safety and security: + - Do not commit API keys, internal URLs, customer data, or proprietary information. + - Use placeholders such as Token YOUR_API_KEY in examples. + - Keep changes narrow and docs-focused unless the user explicitly asks for broader work. - # Optional: Add claude_args to customize behavior and configuration - # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md - # or https://code.claude.com/docs/en/cli-reference for available options - # claude_args: '--allowed-tools Bash(gh pr *)' + Useful commands: + - cd docs && mintlify dev + - spectral lint --ruleset .spectral.mjs docs/openapi.json + - openapi2postmanv2 -s docs/openapi.json -o Terminal49-API.postman_collection.json -p -O folderStrategy=Tags + If API behavior or schemas change, update docs/openapi.json first. Regenerate the Postman collection only when explicitly needed. + Shell execution is disabled for public-repository safety; when validation needs shell commands, report the exact commands for a maintainer to run." + --allowedTools Glob,Grep,Read,Write,Edit,Task diff --git a/.github/workflows/deploy_postman.yml b/.github/workflows/deploy_postman.yml index bc13c624..40684918 100644 --- a/.github/workflows/deploy_postman.yml +++ b/.github/workflows/deploy_postman.yml @@ -8,10 +8,10 @@ on: jobs: deploy-postman-collection: - runs-on: ubuntu-latest + runs-on: ${{ (startsWith(vars.CI_RUNNER_SMALL, 'blacksmith-') && vars.CI_RUNNER_SMALL) || 'blacksmith-2vcpu-ubuntu-2404' }} steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: useblacksmith/checkout@v1 - name: Install jq (for Postman API call) run: sudo apt-get update && sudo apt-get install -y jq @@ -52,4 +52,4 @@ jobs: else echo "❌ Error: Failed to update Postman collection via API." exit 1 - fi \ No newline at end of file + fi diff --git a/.github/workflows/generate_postman.yml b/.github/workflows/generate_postman.yml index 3447278c..50af63a0 100644 --- a/.github/workflows/generate_postman.yml +++ b/.github/workflows/generate_postman.yml @@ -7,12 +7,12 @@ on: jobs: generate-postman-collection: - runs-on: ubuntu-latest + runs-on: ${{ (startsWith(vars.CI_RUNNER_SMALL, 'blacksmith-') && vars.CI_RUNNER_SMALL) || 'blacksmith-2vcpu-ubuntu-2404' }} permissions: contents: write # Needed for committing back to the repo steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: useblacksmith/checkout@v1 with: fetch-depth: 0 # Full history for proper diffing @@ -49,4 +49,3 @@ jobs: run: | echo "Pushing updated collection to ${{ github.ref_name }}..." git push origin HEAD:${{ github.ref_name }} - \ No newline at end of file diff --git a/.github/workflows/publish_typescript_sdk.yml b/.github/workflows/publish_typescript_sdk.yml index 13ce39dc..57000907 100644 --- a/.github/workflows/publish_typescript_sdk.yml +++ b/.github/workflows/publish_typescript_sdk.yml @@ -16,14 +16,14 @@ permissions: jobs: publish: - runs-on: ubuntu-latest + runs-on: ${{ (startsWith(vars.CI_RUNNER_SMALL, 'blacksmith-') && vars.CI_RUNNER_SMALL) || 'blacksmith-2vcpu-ubuntu-2404' }} steps: - name: Resolve release tag id: release-tag run: echo "tag=${{ github.event.release.tag_name || inputs.tag }}" >> "$GITHUB_OUTPUT" - name: Checkout - uses: actions/checkout@v4 + uses: useblacksmith/checkout@v1 with: ref: ${{ steps.release-tag.outputs.tag }} diff --git a/.github/workflows/release_please_sdk.yml b/.github/workflows/release_please_sdk.yml index 41ccd98d..45285351 100644 --- a/.github/workflows/release_please_sdk.yml +++ b/.github/workflows/release_please_sdk.yml @@ -13,7 +13,7 @@ permissions: jobs: release-please: - runs-on: ubuntu-latest + runs-on: ${{ (startsWith(vars.CI_RUNNER_SMALL, 'blacksmith-') && vars.CI_RUNNER_SMALL) || 'blacksmith-2vcpu-ubuntu-2404' }} steps: - uses: googleapis/release-please-action@v4 id: release diff --git a/packages/mcp/TESTING.md b/packages/mcp/TESTING.md index 37f8ffe8..16f8ff6a 100644 --- a/packages/mcp/TESTING.md +++ b/packages/mcp/TESTING.md @@ -301,12 +301,12 @@ name: Test MCP Server on: [push, pull_request] -jobs: - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 +jobs: + test: + runs-on: blacksmith-2vcpu-ubuntu-2404 + steps: + - uses: useblacksmith/checkout@v1 + - uses: actions/setup-node@v4 with: node-version: '20' diff --git a/sdks/typescript-sdk/scripts/clean-generated-docs.mjs b/sdks/typescript-sdk/scripts/clean-generated-docs.mjs index 4a12141b..cd5cd791 100644 --- a/sdks/typescript-sdk/scripts/clean-generated-docs.mjs +++ b/sdks/typescript-sdk/scripts/clean-generated-docs.mjs @@ -1,11 +1,45 @@ /** Wipe the TypeDoc output directory before regeneration. */ import fs from 'node:fs'; +import os from 'node:os'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const outputDir = path.resolve(__dirname, '../../../docs/sdk/reference'); +const frontmatterCachePath = path.join( + os.tmpdir(), + 'terminal49-sdk-docs-frontmatter-cache.json', +); + +function listMdxFiles(dir) { + if (!fs.existsSync(dir)) return []; + const entries = fs.readdirSync(dir, { withFileTypes: true }); + return entries.flatMap((entry) => { + const fullPath = path.join(dir, entry.name); + if (entry.isDirectory()) return listMdxFiles(fullPath); + return entry.isFile() && entry.name.endsWith('.mdx') ? [fullPath] : []; + }); +} + +function frontmatterFor(content) { + return content.match(/^---\n([\s\S]*?)\n---\n/)?.[1]; +} + +const frontmatterByPath = {}; +for (const filePath of listMdxFiles(outputDir)) { + const content = fs.readFileSync(filePath, 'utf8'); + const frontmatter = frontmatterFor(content); + if (!frontmatter) continue; + + const relativePath = path + .relative(outputDir, filePath) + .split(path.sep) + .join(path.posix.sep); + frontmatterByPath[relativePath] = frontmatter; +} + +fs.writeFileSync(frontmatterCachePath, JSON.stringify(frontmatterByPath)); fs.rmSync(outputDir, { recursive: true, force: true }); fs.mkdirSync(outputDir, { recursive: true }); diff --git a/sdks/typescript-sdk/scripts/postprocess-generated-docs.mjs b/sdks/typescript-sdk/scripts/postprocess-generated-docs.mjs index eeccb021..326ce705 100644 --- a/sdks/typescript-sdk/scripts/postprocess-generated-docs.mjs +++ b/sdks/typescript-sdk/scripts/postprocess-generated-docs.mjs @@ -7,6 +7,7 @@ * 3. Cleans up module page titles (e.g. "client" → "Client Module"). */ import fs from 'node:fs'; +import os from 'node:os'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -14,6 +15,10 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const outputDir = path.resolve(__dirname, '../../../docs/sdk/reference'); const routePrefix = '/sdk/reference'; +const frontmatterCachePath = path.join( + os.tmpdir(), + 'terminal49-sdk-docs-frontmatter-cache.json', +); /** Human-readable overrides for module index page titles. */ const MODULE_TITLE_OVERRIDES = { @@ -22,6 +27,10 @@ const MODULE_TITLE_OVERRIDES = { options: 'Options', }; +const frontmatterByPath = fs.existsSync(frontmatterCachePath) + ? JSON.parse(fs.readFileSync(frontmatterCachePath, 'utf8')) + : {}; + function listMdxFiles(dir) { const entries = fs.readdirSync(dir, { withFileTypes: true }); return entries.flatMap((entry) => { @@ -64,7 +73,20 @@ function rewriteMdxLinks(content, filePath) { }); } +function stripFrontmatter(content) { + return content.replace(/^---\n[\s\S]*?\n---\n+/, ''); +} + function ensureFrontmatter(content, filePath) { + const relativePath = path + .relative(outputDir, filePath) + .split(path.sep) + .join(path.posix.sep); + const cachedFrontmatter = frontmatterByPath[relativePath]; + if (cachedFrontmatter) { + return `---\n${cachedFrontmatter}\n---\n\n${stripFrontmatter(content)}`; + } + if (content.startsWith('---\n')) return content; const title = frontmatterTitle(content, filePath); return `---\ntitle: ${JSON.stringify(title)}\n---\n\n${content}`; @@ -76,3 +98,5 @@ for (const filePath of listMdxFiles(outputDir)) { const withFrontmatter = ensureFrontmatter(withLinks, filePath); fs.writeFileSync(filePath, withFrontmatter); } + +fs.rmSync(frontmatterCachePath, { force: true });