chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:38:34 +08:00
commit 0549b088a4
2405 changed files with 810255 additions and 0 deletions
+347
View File
@@ -0,0 +1,347 @@
name: Build CLI Binaries
on:
push:
branches: [next]
paths:
- 'ts/packages/cli/**'
- 'ts/packages/cli-local-tools/**'
- '.github/actions/setup-node-pnpm-bun/action.yml'
- '.github/scripts/cli-release/**'
- '.github/workflows/build-cli-binaries.yml'
- 'mise.toml'
- 'mise.lock'
workflow_dispatch:
inputs:
action:
description: 'What to do'
type: choice
options:
- build-beta
- promote-stable
default: build-beta
beta_tag:
description: 'Existing beta release tag to promote (only for promote-stable, e.g. @composio/cli@0.2.20-beta.42)'
required: false
# Least privilege by default: only the release job creates/edits releases and uploads assets.
# `prepare` reads releases (gh release list) and `build` uploads workflow artifacts — both work
# with read access. The release job opts up to `contents: write` for itself.
permissions:
contents: read
# Print Turborepo telemetry events to the logs instead of sending them.
env:
TURBO_TELEMETRY_DEBUG: 1
jobs:
prepare:
name: Resolve Release Metadata
runs-on: ubuntu-latest
outputs:
checkout_ref: ${{ steps.resolve.outputs.checkout_ref }}
release_name: ${{ steps.resolve.outputs.release_name }}
release_tag: ${{ steps.resolve.outputs.release_tag }}
release_version: ${{ steps.resolve.outputs.release_version }}
prerelease: ${{ steps.resolve.outputs.prerelease }}
make_latest: ${{ steps.resolve.outputs.make_latest }}
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ github.sha }}
- name: Resolve release target
id: resolve
env:
EVENT_NAME: ${{ github.event_name }}
ACTION_INPUT: ${{ inputs.action }}
BETA_TAG_INPUT: ${{ inputs.beta_tag }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPOSITORY: ${{ github.repository }}
RUN_NUMBER: ${{ github.run_number }}
COMMIT_SHA: ${{ github.sha }}
run: bash .github/scripts/cli-release/resolve-release-target.sh
build:
name: Build ${{ matrix.artifact }}
needs: prepare
if: needs.prepare.result == 'success'
runs-on: ${{ matrix.runner }}
strategy:
# Never publish a partial platform set: with fail-fast disabled, the build job's
# result is `success` only when EVERY matrix leg passes, so a single failure makes
# `needs.build.result != 'success'` and the release job is skipped. It also surfaces
# all platform failures at once instead of cancelling siblings on the first error.
fail-fast: false
matrix:
include:
- target: bun-linux-x64
artifact: composio-linux-x64
runner: ubuntu-latest
local_tools_target: ''
- target: bun-linux-arm64
artifact: composio-linux-aarch64
runner: ubuntu-latest
local_tools_target: ''
- target: bun-darwin-x64
artifact: composio-darwin-x64
runner: macos-15-intel
local_tools_target: ''
- target: bun-darwin-arm64
artifact: composio-darwin-aarch64
runner: macos-15
local_tools_target: darwin-arm64
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ needs.prepare.outputs.checkout_ref }}
- name: Setup Node.js, pnpm, Bun
uses: ./.github/actions/setup-node-pnpm-bun
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Setup Swift 6.2 for local-tool sidecars
if: matrix.local_tools_target != ''
uses: swift-actions/setup-swift@364295d9c23900ce04d4e5cc708387921b4e50f9 # v3
with:
swift-version: '6.2'
- name: Verify Swift toolchain
if: matrix.local_tools_target != ''
run: swift --version
- name: Build packages
run: pnpm build:packages
- name: Build local-tool sidecar binaries
if: matrix.local_tools_target != ''
run: pnpm --filter @composio/cli-local-tools build:local-tool-binaries -- --target ${{ matrix.local_tools_target }}
- name: Verify local-tool sidecars
if: matrix.local_tools_target != ''
run: |
test -x ts/packages/cli-local-tools/local-tools-binaries/beeper-imessage/${{ matrix.local_tools_target }}/imessage-cli
test -x ts/packages/cli-local-tools/local-tools-binaries/peekaboo/${{ matrix.local_tools_target }}/peekaboo
test -x ts/packages/cli-local-tools/local-tools-binaries/composio-native-ui/${{ matrix.local_tools_target }}/composio-native-ui
- name: Cross-compile CLI binary
working-directory: ts/packages/cli
run: pnpm build:binary:cross --target ${{ matrix.target }}
- name: Verify binary
working-directory: ts/packages/cli
run: |
ls -la dist/binaries/${{ matrix.artifact }}
file dist/binaries/${{ matrix.artifact }}
- name: Test binary
if: matrix.target == 'bun-linux-x64'
working-directory: ts/packages/cli
env:
COMPOSIO_USER_API_KEY: test-dummy-api-key-for-ci
run: |
./dist/binaries/${{ matrix.artifact }} --version
./dist/binaries/${{ matrix.artifact }} --help | head -5
- name: Create archive
working-directory: ts/packages/cli
env:
RELEASE_TAG: ${{ needs.prepare.outputs.release_tag }}
run: pnpm build:binary:package
- name: Verify release metadata in archive
working-directory: ts/packages/cli
run: unzip -p dist/binaries/${{ matrix.artifact }}.zip ${{ matrix.artifact }}/release-tag.txt | grep -Fx '${{ needs.prepare.outputs.release_tag }}'
- name: Verify local-tool sidecars in archive
if: matrix.local_tools_target != ''
working-directory: ts/packages/cli
run: |
unzip -Z1 dist/binaries/${{ matrix.artifact }}.zip | grep -F "local-tools-binaries/beeper-imessage/${{ matrix.local_tools_target }}/imessage-cli"
unzip -Z1 dist/binaries/${{ matrix.artifact }}.zip | grep -F "local-tools-binaries/peekaboo/${{ matrix.local_tools_target }}/peekaboo"
unzip -Z1 dist/binaries/${{ matrix.artifact }}.zip | grep -F "local-tools-binaries/composio-native-ui/${{ matrix.local_tools_target }}/composio-native-ui"
- name: Upload artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ matrix.artifact }}
path: ts/packages/cli/dist/binaries/${{ matrix.artifact }}.zip
retention-days: 7
release:
name: Release (draft → verify → publish)
needs: [prepare, build]
runs-on: ubuntu-latest
permissions:
contents: write # create/edit the GitHub Release and upload assets
# fail-fast: false on the build matrix ⇒ this is `success` only if ALL platforms built,
# so a partial set can never reach the release path.
if: needs.build.result == 'success'
# Serialize runs that target the SAME release tag (e.g. two quick pushes, or a re-run
# racing the original) so they cannot interleave uploads. Keyed on the resolved tag so
# unrelated beta builds are not serialized behind each other. Job-level concurrency may
# read `needs.*` outputs (workflow-level cannot). The serialized loser fails loudly at the
# "already published" guard in create-or-resume-draft.sh — that red ❌ is by design.
concurrency:
group: cli-release-${{ needs.prepare.outputs.release_tag }}
cancel-in-progress: false
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_TAG: ${{ needs.prepare.outputs.release_tag }}
RELEASE_NAME: ${{ needs.prepare.outputs.release_name }}
PRERELEASE: ${{ needs.prepare.outputs.prerelease }}
MAKE_LATEST: ${{ needs.prepare.outputs.make_latest }}
CHECKOUT_REF: ${{ needs.prepare.outputs.checkout_ref }}
RELEASE_HELPERS_DIR: ${{ github.workspace }}/.release-workflow/.github/scripts/cli-release
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ needs.prepare.outputs.checkout_ref }}
- name: Setup Node.js, pnpm, Bun
uses: ./.github/actions/setup-node-pnpm-bun
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download all artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: ts/packages/cli/dist/binaries
merge-multiple: true
# Package skills BEFORE generating checksums so composio-skill.zip is included in
# checksums.txt alongside the platform binaries (generate-checksums hashes every .zip
# in dist/binaries).
- name: Package skill files
working-directory: ts/packages/cli
env:
PRERELEASE: ${{ needs.prepare.outputs.prerelease }}
run: |
pnpm run validate:skills
if [[ "$PRERELEASE" == "true" ]]; then
channel=beta
else
channel=stable
fi
pnpm run build:skills -- --channel "$channel" --output-dir ./dist/skills
cd dist/skills && zip -r ../binaries/composio-skill.zip composio-cli/
- name: Generate checksums
working-directory: ts/packages/cli
run: bun run ./scripts/generate-checksums.ts
- name: Display checksums
run: cat ts/packages/cli/dist/binaries/checksums.txt
- name: Checkout workflow release helpers
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ github.workflow_sha }}
path: .release-workflow
sparse-checkout: |
.github/scripts/cli-release
sparse-checkout-cone-mode: false
# Build the release as a DRAFT first. Drafts fire no `release: published` event and are
# excluded from the `/releases/latest` redirect, so no anonymous consumer (install.sh,
# Homebrew, the redirect) can ever observe a release before its assets are attached and
# verified. The release is only flipped to published as the final step below.
- name: Create draft release with all assets
run: bash "$RELEASE_HELPERS_DIR/create-or-resume-draft.sh"
# Loud failure gate: assert the full canonical asset set is attached AND fully uploaded.
# An asset can appear in the list while still processing (`state != "uploaded"`), which is
# exactly how a release ends up serving 404s, so we require state == "uploaded".
- name: Verify all release assets are present and uploaded
run: bash "$RELEASE_HELPERS_DIR/verify-assets.sh"
# Publishing is the ONLY step that exposes the release. Do not edit the body here: a known
# GitHub PATCH race drops body changes made in the same call as the draft flip, and the
# notes were already generated at draft creation. Prerelease status was set on the draft.
- name: Publish release (flip draft → published)
run: gh release edit "$RELEASE_TAG" --draft=false --latest="$MAKE_LATEST"
test-installation:
name: Test Installation
needs: [prepare, release]
if: always() && !cancelled() && needs.release.result == 'success'
uses: ./.github/workflows/cli.test-installation.yml
with:
version: ${{ needs.prepare.outputs.release_tag }}
create-install-instructions:
name: Create Install Instructions
needs: [prepare, release]
runs-on: ubuntu-latest
if: always() && !cancelled() && needs.release.result == 'success'
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Create installation README
run: |
RELEASE_TAG="${{ needs.prepare.outputs.release_tag }}"
cat > INSTALL.md << 'EOF'
# Composio CLI Installation
## Quick Install (Recommended)
### Linux and macOS
```bash
curl -fsSL https://raw.githubusercontent.com/ComposioHQ/composio/next/install.sh | bash
```
### Install specific version
```bash
curl -fsSL https://raw.githubusercontent.com/ComposioHQ/composio/next/install.sh | bash -s -- RELEASE_TAG_PLACEHOLDER
```
## Manual Installation
1. Download the appropriate binary for your platform from the [releases page](https://github.com/ComposioHQ/composio/releases)
2. Extract the binary
3. Move it to a directory in your PATH (e.g., `/usr/local/bin`)
4. Make it executable: `chmod +x composio`
## Alternative: npm/pnpm Installation
```bash
npm install -g @composio/cli
# or
pnpm add -g @composio/cli
```
## Usage
```bash
composio --help
composio login
composio generate
```
## Supported Platforms
- Linux x64
- Linux ARM64
- macOS x64 (Intel)
- macOS ARM64 (Apple Silicon)
Windows support coming soon!
EOF
sed -i.bak "s|RELEASE_TAG_PLACEHOLDER|$RELEASE_TAG|g" INSTALL.md && rm -f INSTALL.md.bak
- name: Upload install instructions
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: install-instructions
path: INSTALL.md
@@ -0,0 +1,78 @@
name: Claude Docs Review
on:
pull_request:
types: [ opened, synchronize ]
# Run on docs changes (content, components, styles)
paths:
- "docs/content/**/*.mdx"
- "docs/components/**/*.tsx"
- "docs/app/**/*.tsx"
- "docs/app/**/*.css"
permissions:
contents: read
jobs:
claude-review:
# Optional: Filter by PR author
# if: |
# github.event.pull_request.user.login == 'external-contributor' ||
# github.event.pull_request.user.login == 'new-developer' ||
# github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 1
- name: Run Claude Code Review
id: claude-review
uses: anthropics/claude-code-action@536f2c32a39763739000b0e1ac69ca2647d97ce9 # v1.0.170
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
track_progress: true # Forces tag mode with tracking comments
allowed_bots: "devin-ai-integration[bot],decimal-pr-bot[bot],github-actions[bot],dependabot[bot],zen-agent,sdkrelease,sdkrelease[bot]"
# Optional: Specify model (defaults to Claude Sonnet 4, uncomment for Claude Opus 4)
# model: "claude-opus-4-20250514"
# Review instructions in docs/agent-guidance/agents/docs-reviewer.md
prompt: |
Review this PR using the guidelines in docs/agent-guidance/agents/docs-reviewer.md
1. Read docs/agent-guidance/agents/docs-reviewer.md for full review criteria
2. Run `git diff origin/${{ github.event.pull_request.base.ref }}...HEAD` to see changes
3. Apply the review checklist to changed files
4. If issues: Comment with specific fixes. If none: Approve with "Looks good"
# Optional: Customize review based on file types
# direct_prompt: |
# Review this PR focusing on:
# - For TypeScript files: Type safety and proper interface usage
# - For API endpoints: Security, input validation, and error handling
# - For React components: Performance, accessibility, and best practices
# - For tests: Coverage, edge cases, and test quality
# Optional: Different prompts for different authors
# direct_prompt: |
# ${{ github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' &&
# 'Welcome! Please review this PR from a first-time contributor. Be encouraging and provide detailed explanations for any suggestions.' ||
# 'Please provide a thorough code review focusing on our coding standards and best practices.' }}
# https://code.claude.com/docs/en/cli-reference#cli-flags
# -- model: "claude-opus-4-20250514"
claude_args: |
--allowedTools "Bash(*)"
# Optional: Skip review for certain conditions
# if: |
# !contains(github.event.pull_request.title, '[skip-review]') &&
# !contains(github.event.pull_request.title, '[WIP]')
+66
View File
@@ -0,0 +1,66 @@
name: Claude Code
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened, assigned]
pull_request_review:
types: [submitted]
permissions:
contents: read
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
permissions:
contents: read
pull-requests: read
issues: read
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 1
submodules: true
- name: Run Claude Code
id: claude
uses: anthropics/claude-code-action@536f2c32a39763739000b0e1ac69ca2647d97ce9 # v1.0.170
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
allowed_bots: "devin-ai-integration[bot],decimal-pr-bot[bot],github-actions[bot],dependabot[bot],zen-agent"
# Optional: Custom settings and environment variables for Claude
# settings: |
# {
# "env": {
# "NODE_ENV": "test",
# "CI": "true"
# }
# }
# https://code.claude.com/docs/en/cli-reference#cli-flags
# -- model: "claude-opus-4-20250514"
# claude_args: |
# --allowedTools "Bash(pnpm install),Bash(pnpm build:packages),Bash(pnpm build:binary),Bash(pnpm test:*)"
# --model: "claude-opus-4-20250514"
# --append-system-prompt: |
# Follow our coding standards
# Ensure all new code has tests
# Use TypeScript for new files
# Optional: Customize the trigger phrase (default: @claude)
# trigger_phrase: "/claude"
# Optional: Trigger when specific user is assigned to an issue
# assignee_trigger: "claude-bot"
+120
View File
@@ -0,0 +1,120 @@
name: Bump Homebrew Tap
on:
release:
types: [published]
workflow_dispatch:
inputs:
tag:
description: 'Release tag to sync (e.g. @composio/cli@0.2.28)'
required: true
type: string
permissions:
contents: read
concurrency:
group: bump-homebrew-tap
cancel-in-progress: false
jobs:
bump:
if: github.event_name == 'workflow_dispatch' || startsWith(github.event.release.tag_name, '@composio/cli@')
runs-on: ubuntu-latest
steps:
- name: Resolve release tag and version
id: meta
env:
TAG_INPUT: ${{ inputs.tag }}
TAG_RELEASE: ${{ github.event.release.tag_name }}
run: |
set -euo pipefail
tag="${TAG_INPUT:-$TAG_RELEASE}"
if [[ -z "$tag" ]]; then
echo "::error::No tag resolved"; exit 1
fi
if [[ ! "$tag" =~ ^@composio/cli@ ]]; then
echo "::notice::Tag $tag is not a CLI release, skipping."
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
# Skip beta/prerelease tags — only stable bumps the formula.
if [[ "$tag" == *"-beta."* || "$tag" == *"-rc."* || "$tag" == *"-alpha."* ]]; then
echo "::notice::Tag $tag is a prerelease, skipping."
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
version="${tag#@composio/cli@}"
echo "tag=$tag" >> "$GITHUB_OUTPUT"
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "skip=false" >> "$GITHUB_OUTPUT"
- name: Download checksums
if: steps.meta.outputs.skip != 'true'
id: sha
env:
TAG: ${{ steps.meta.outputs.tag }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
gh release download "$TAG" \
--repo "${{ github.repository }}" \
--pattern checksums.txt \
--output checksums.txt
extract() {
grep " $1\$" checksums.txt | awk '{print $1}' || true
}
{
echo "darwin_arm=$(extract composio-darwin-aarch64.zip)"
echo "darwin_x86=$(extract composio-darwin-x64.zip)"
echo "linux_arm=$(extract composio-linux-aarch64.zip)"
echo "linux_x86=$(extract composio-linux-x64.zip)"
} >> "$GITHUB_OUTPUT"
- name: Checkout source repo (for bump script)
if: steps.meta.outputs.skip != 'true'
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Checkout homebrew tap
if: steps.meta.outputs.skip != 'true'
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
repository: ComposioHQ/homebrew-tap
token: ${{ secrets.HOMEBREW_TAP_TOKEN }}
path: tap
- name: Update formula
if: steps.meta.outputs.skip != 'true'
env:
VERSION: ${{ steps.meta.outputs.version }}
TAG: ${{ steps.meta.outputs.tag }}
DARWIN_ARM: ${{ steps.sha.outputs.darwin_arm }}
DARWIN_X86: ${{ steps.sha.outputs.darwin_x86 }}
LINUX_ARM: ${{ steps.sha.outputs.linux_arm }}
LINUX_X86: ${{ steps.sha.outputs.linux_x86 }}
run: |
set -euo pipefail
formula="tap/Formula/composio.rb"
[[ -f "$formula" ]] || { echo "::error::$formula not found"; exit 1; }
.github/scripts/bump-homebrew-formula.py "$formula"
echo "--- updated formula ---"
cat "$formula"
- name: Commit and push
if: steps.meta.outputs.skip != 'true'
working-directory: tap
env:
VERSION: ${{ steps.meta.outputs.version }}
run: |
set -euo pipefail
git config user.name "composio-release-bot"
git config user.email "composio-release-bot@users.noreply.github.com"
if git diff --quiet; then
echo "Formula already up to date for $VERSION; nothing to commit."
exit 0
fi
git add Formula/composio.rb
git commit -m "composio $VERSION"
git push origin HEAD
@@ -0,0 +1,75 @@
name: CLI Install Health Check
on:
schedule:
- cron: '0 * * * *'
workflow_dispatch:
permissions:
contents: read # gh release list reads the published releases
jobs:
health-check:
name: Test CLI Installation
runs-on: ubuntu-latest
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
# Resolve the newest PUBLISHED stable @composio/cli release — exactly what install.sh and
# the /releases/latest redirect (Homebrew, humans) resolve and download from.
#
# Deliberately NOT `npm view @composio/cli version`: npm's latest dist-tag is bumped by
# changesets minutes before the binary workflow finishes the draft build and publishes the
# GitHub release. Pinning to the npm version would 404 during that healthy publish gap and
# false-page hourly. Drafts are excluded here, so this only ever resolves a fully-published
# release — the in-flight one stays invisible until its assets are verified and attached.
- name: Resolve newest published release
id: resolve
run: |
set -euo pipefail
tag=$(gh release list --repo "${{ github.repository }}" --limit 1000 --exclude-drafts --exclude-pre-releases --json tagName \
--jq '.[] | select(.tagName | startswith("@composio/cli@")) | .tagName' \
| sed -n '1p')
if [[ -z "$tag" ]]; then
echo "::error::No published @composio/cli release found"
exit 1
fi
echo "tag=${tag}" >> "$GITHUB_OUTPUT"
echo "Latest published release: ${tag}"
# Install the PINNED newest tag. This is the path that 404s when a release ships without
# assets. The no-arg `curl | bash` below is asset-aware and silently falls back to the
# previous good release, so it would stay green even when the newest release is broken —
# which is exactly why the previous canary never caught the recurring outage.
- name: Install newest stable (pinned — fails on a release with missing assets)
run: |
set -euo pipefail
curl -fsSL https://composio.dev/install | bash -s -- "${{ steps.resolve.outputs.tag }}"
echo "$HOME/.composio" >> "$GITHUB_PATH"
- name: Verify pinned installation
run: |
set -euo pipefail
ls -la "$HOME/.composio" || true
which composio
composio --version
# Secondary: the default no-arg flow most users run. Asset-aware and self-healing, so it
# is a coverage net rather than the primary signal.
- name: Install via default no-arg flow
run: |
set -euo pipefail
rm -rf "$HOME/.composio"
curl -fsSL https://composio.dev/install | bash
"$HOME/.composio/composio" --version
- name: Send Slack Notification (Failure)
if: failure()
uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3
with:
webhook: ${{ secrets.SLACK_RELEASE_WEBHOOK_URL }}
webhook-type: incoming-webhook
payload: |
{
"text": "⚠️ CLI Install Health Check Failed!\n*Repository:* ${{ github.repository }}\n*Workflow:* ${{ github.workflow }}\n*Run:* ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\n*Commit:* ${{ github.sha }}"
}
+638
View File
@@ -0,0 +1,638 @@
name: Test Installation
on:
push:
tags:
# Temporary compatibility window: support both legacy v* and package-scoped CLI tags.
- 'v*'
- '@composio/cli@*'
workflow_dispatch:
inputs:
version:
description: 'Version to test (e.g., 1.0.0 or @composio/cli@1.0.0)'
required: true
workflow_call:
inputs:
version:
description: 'Release tag or semver version to test'
required: false
default: 'latest'
type: string
permissions:
contents: read
jobs:
toolchain-versions:
name: Read toolchain test matrix
runs-on: ubuntu-latest
outputs:
node_install_compat: ${{ steps.versions.outputs.node_install_compat }}
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Read versions
id: versions
run: echo "node_install_compat=$(jq -c '.node_install_compat' toolchain-versions.json)" >> "$GITHUB_OUTPUT"
install-script-unit-tests:
name: Install Script Unit Tests
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Test release resolution fallback
run: bash test/install-sh-release-resolution.test.sh
test-install-script:
name: Test Install Script
needs: install-script-unit-tests
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
# Ubuntu x64 versions
- os: ubuntu-22.04
name: 'Ubuntu 22.04 x64'
shell: bash
- os: ubuntu-22.04
name: 'Ubuntu 22.04 x64 (zsh)'
shell: zsh
- os: ubuntu-latest
name: 'Ubuntu Latest x64'
shell: bash
# Ubuntu ARM64 versions (using Depot)
- os: depot-ubuntu-24.04-arm
name: 'Ubuntu 24.04 ARM64'
shell: bash
- os: depot-ubuntu-22.04-arm
name: 'Ubuntu 22.04 ARM64'
shell: bash
- os: depot-ubuntu-22.04-arm
name: 'Ubuntu 22.04 ARM64 (zsh)'
shell: zsh
# macOS versions
- os: macos-15-intel
name: 'macOS 15 (Intel)'
shell: bash
- os: macos-15-intel
name: 'macOS 15 (Intel, zsh)'
shell: zsh
- os: macos-14
name: 'macOS 14 (Sonoma)'
shell: bash
- os: macos-15
name: 'macOS 15 (Apple Silicon)'
shell: bash
- os: macos-15
name: 'macOS 15 (Apple Silicon, zsh)'
shell: zsh
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Resolve and validate target release
id: release_target
shell: bash
env:
VERSION_INPUT: ${{ inputs.version }}
run: |
raw_input="$VERSION_INPUT"
if [[ -z "$raw_input" || "$raw_input" == "latest" ]]; then
echo "use_latest=true" >> "$GITHUB_OUTPUT"
echo "release_tag=" >> "$GITHUB_OUTPUT"
exit 0
fi
# For manual runs, accept a full package tag or semver and normalize to a package tag.
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
if [[ "$raw_input" =~ ^@composio/cli@ ]]; then
echo "use_latest=false" >> "$GITHUB_OUTPUT"
echo "release_tag=$raw_input" >> "$GITHUB_OUTPUT"
exit 0
elif [[ "$raw_input" =~ ^[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$ ]]; then
echo "use_latest=false" >> "$GITHUB_OUTPUT"
echo "release_tag=@composio/cli@$raw_input" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "Invalid version: '$raw_input'"
echo "Expected format like: 1.2.3, 1.2.3-beta.1, 1.2.3+build.7, or @composio/cli@1.2.3"
exit 1
fi
# For workflow_call / push, accept a full package tag, semver, or legacy v* tag.
if [[ "$raw_input" =~ ^@composio/cli@ ]]; then
echo "use_latest=false" >> "$GITHUB_OUTPUT"
echo "release_tag=$raw_input" >> "$GITHUB_OUTPUT"
elif [[ "$raw_input" =~ ^[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$ ]]; then
echo "use_latest=false" >> "$GITHUB_OUTPUT"
echo "release_tag=@composio/cli@$raw_input" >> "$GITHUB_OUTPUT"
else
echo "use_latest=false" >> "$GITHUB_OUTPUT"
echo "release_tag=$raw_input" >> "$GITHUB_OUTPUT"
exit 0
fi
- name: Setup shell environment
run: |
if [[ "${{ matrix.shell }}" == "zsh" ]]; then
# Install zsh if not present
if ! command -v zsh &> /dev/null; then
if [[ "${{ runner.os }}" == "Linux" ]]; then
sudo apt-get update
sudo apt-get install -y zsh
elif [[ "${{ runner.os }}" == "macOS" ]]; then
# zsh is default on macOS
echo "zsh already available"
fi
fi
# Create .zshrc if it doesn't exist
touch ~/.zshrc
# Set zsh as the shell for this session
export SHELL=$(which zsh)
echo "SHELL=$(which zsh)" >> $GITHUB_ENV
else
# Ensure bash is available and create .bashrc
touch ~/.bashrc
export SHELL=$(which bash)
echo "SHELL=$(which bash)" >> $GITHUB_ENV
fi
- name: Test install script (latest version)
if: steps.release_target.outputs.use_latest == 'true'
shell: bash
run: |
echo "Testing installation with auto-detected latest CLI version..."
# Test the install script without version (it will auto-detect latest CLI release)
bash install.sh
# Verify installation
source ~/.bashrc 2>/dev/null || source ~/.zshrc 2>/dev/null || true
export PATH="$HOME/.composio:$PATH"
# Check if binary exists
if [[ -f "$HOME/.composio/composio" ]]; then
echo "✅ Binary installed successfully"
ls -la "$HOME/.composio/composio"
else
echo "❌ Binary not found"
exit 1
fi
# Test binary execution
if "$HOME/.composio/composio" --version; then
echo "✅ Binary executes successfully"
else
echo "❌ Binary execution failed"
exit 1
fi
- name: Test install script (specific version)
if: steps.release_target.outputs.use_latest != 'true'
shell: bash
env:
RELEASE_TAG: ${{ steps.release_target.outputs.release_tag }}
run: |
echo "Testing installation with version: ${RELEASE_TAG}"
# Test the install script with specific version
bash install.sh "${RELEASE_TAG}"
# Verify installation
source ~/.bashrc 2>/dev/null || source ~/.zshrc 2>/dev/null || true
export PATH="$HOME/.composio:$PATH"
# Check if binary exists
if [[ -f "$HOME/.composio/composio" ]]; then
echo "✅ Binary installed successfully"
ls -la "$HOME/.composio/composio"
else
echo "❌ Binary not found"
exit 1
fi
# Test binary execution
if "$HOME/.composio/composio" --version; then
echo "✅ Binary executes successfully"
else
echo "❌ Binary execution failed"
exit 1
fi
- name: Test PATH integration
shell: bash
run: |
echo "Testing PATH integration with ${{ matrix.shell }}..."
# Debug: Check what files exist and their contents
echo "=== DEBUGGING ==="
echo "Checking shell config files:"
ls -la ~/ | grep -E '\.(bash|zsh)' || echo "No shell config files found"
if [[ -f ~/.bashrc ]]; then
echo "=== ~/.bashrc contents ==="
cat ~/.bashrc
fi
if [[ -f ~/.zshrc ]]; then
echo "=== ~/.zshrc contents ==="
cat ~/.zshrc
fi
echo "=== Installation directory ==="
ls -la ~/.composio/ || echo "~/.composio/ not found"
echo "=== Current PATH before sourcing ==="
echo "$PATH"
if [[ "${{ matrix.shell }}" == "zsh" ]]; then
if [[ -f ~/.zshrc ]]; then
echo "Testing interactive zsh startup..."
if zsh -ic 'echo "PATH after startup: $PATH"; command -v composio; composio --version; composio --help | head -5'; then
echo "✅ composio found in PATH via zsh"
else
echo "❌ composio not found in PATH via zsh"
exit 1
fi
else
echo "❌ ~/.zshrc not found - install script failed to create it"
exit 1
fi
else
if [[ -f ~/.bashrc ]]; then
echo "Testing interactive bash startup..."
if bash -ic 'echo "PATH after startup: $PATH"; command -v composio; composio --version; composio --help | head -5'; then
echo "✅ composio found in PATH via bash"
else
echo "❌ composio not found in PATH via bash"
exit 1
fi
else
echo "❌ ~/.bashrc not found - install script failed to create it"
exit 1
fi
fi
- name: Test bundled support files
shell: bash
run: |
echo "Checking bundled support files..."
install_dir="${COMPOSIO_INSTALL_DIR:-$HOME/.composio}"
release_tag=$(cat "$install_dir/release-tag.txt")
platform=$(uname -ms)
case $platform in
'Darwin x86_64') target=darwin-x64 ;;
'Darwin arm64') target=darwin-aarch64 ;;
'Linux aarch64' | 'Linux arm64')
target=linux-aarch64 ;;
'Linux x86_64') target=linux-x64 ;;
*) echo "❌ Unsupported platform: $platform"; exit 1 ;;
esac
if [[ $target = darwin-x64 ]]; then
if [[ $(sysctl -n sysctl.proc_translated 2>/dev/null) = 1 ]]; then
target=darwin-aarch64
fi
fi
archive_name="composio-$target.zip"
archive_url="https://github.com/ComposioHQ/composio/releases/download/$release_tag/$archive_name"
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
curl --fail --silent --location --output "$tmpdir/$archive_name" "$archive_url"
expected_paths="$(
unzip -Z1 "$tmpdir/$archive_name" \
| grep -v '/$' \
| sed "s#^composio-$target/##" \
| grep -v '^composio$' \
| sort
)"
if [ -z "$expected_paths" ]; then
echo "❌ No bundled support files found in $archive_name"
exit 1
fi
missing=""
echo "$expected_paths" | while IFS= read -r relative_path; do
if [ -f "$install_dir/$relative_path" ]; then
echo "✅ Found $relative_path"
else
echo "MISSING:$relative_path"
fi
done > "$tmpdir/check_output"
cat "$tmpdir/check_output" | grep -v '^MISSING:' || true
missing="$(grep '^MISSING:' "$tmpdir/check_output" | sed 's/^MISSING://' || true)"
if [ -n "$missing" ]; then
printf '❌ Missing bundled support files after install:\n' >&2
echo "$missing" | while IFS= read -r p; do
printf ' %s\n' "$p" >&2
done
exit 1
fi
- name: Test shell config updates
run: |
echo "Checking shell configuration updates..."
if [[ "${{ matrix.shell }}" == "zsh" ]]; then
config_file=~/.zshrc
else
config_file=~/.bashrc
fi
if grep -q "COMPOSIO_INSTALL_DIR" "$config_file"; then
echo "✅ $config_file updated with COMPOSIO_INSTALL_DIR"
else
echo "❌ $config_file not updated with COMPOSIO_INSTALL_DIR"
echo "=== $config_file contents ==="
cat "$config_file"
exit 1
fi
if grep -q 'export PATH=.*COMPOSIO_INSTALL_DIR.*PATH' "$config_file"; then
echo "✅ $config_file updated with PATH"
else
echo "❌ $config_file PATH not updated"
echo "=== $config_file contents ==="
cat "$config_file"
exit 1
fi
- name: Test custom install directory
shell: bash
env:
USE_LATEST: ${{ steps.release_target.outputs.use_latest }}
RELEASE_TAG: ${{ steps.release_target.outputs.release_tag }}
run: |
echo "Testing custom install directory..."
# Clean up previous installation
rm -rf ~/.composio
# Test with custom directory using the same version as the workflow
export COMPOSIO_INSTALL_DIR="/tmp/custom-composio"
if [[ "$USE_LATEST" == "true" ]]; then
bash install.sh
else
bash install.sh "${RELEASE_TAG}"
fi
# Verify custom installation
if [[ -f "/tmp/custom-composio/composio" ]]; then
echo "✅ Custom directory installation successful"
ls -la "/tmp/custom-composio/composio"
else
echo "❌ Custom directory installation failed"
exit 1
fi
# Test execution
if "/tmp/custom-composio/composio" --version; then
echo "✅ Custom installation executes successfully"
else
echo "❌ Custom installation execution failed"
exit 1
fi
- name: Test uninstallation
run: |
echo "Testing uninstallation..."
# Remove binary
rm -rf ~/.composio /tmp/custom-composio
# Check if binary is gone
if [[ ! -f "$HOME/.composio/composio" ]]; then
echo "✅ Binary removed successfully"
else
echo "❌ Binary removal failed"
exit 1
fi
- name: Test error handling
shell: bash
run: |
echo "Testing error handling..."
# Test with invalid version
if bash install.sh v999.999.999 2>&1 | grep -q "Failed"; then
echo "✅ Error handling works for invalid version"
else
echo "❌ Error handling failed"
# Don't exit 1 here as this is expected to fail
fi
test-npm-fallback:
name: Test npm Installation Fallback
needs: toolchain-versions
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-15]
node-version: ${{ fromJSON(needs.toolchain-versions.outputs.node_install_compat) }}
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: ${{ matrix.node-version }}
- name: Test npm installation
run: |
echo "Testing npm installation as fallback..."
# This would normally install from npm registry
# For testing, we just verify the command works
if command -v npm &> /dev/null; then
echo "✅ npm is available"
npm --version
else
echo "❌ npm not available"
exit 1
fi
test-architecture-detection:
name: Test Architecture Detection
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
arch: x64
expected: 'Linux x64'
- os: depot-ubuntu-24.04-arm
arch: arm64
expected: 'Linux ARM64'
- os: macos-15-intel
arch: x64
expected: 'macOS x64'
- os: macos-15
arch: arm64
expected: 'macOS ARM64'
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Test architecture detection
run: |
echo "Testing architecture detection for: ${{ matrix.expected }}"
platform=$(uname -ms)
echo "Detected platform: $platform"
case $platform in
'Darwin x86_64')
detected="macOS x64"
;;
'Darwin arm64')
detected="macOS ARM64"
;;
'Linux x86_64')
detected="Linux x64"
;;
'Linux aarch64'|'Linux arm64')
detected="Linux ARM64"
;;
*)
echo "❌ Unknown platform: $platform"
exit 1
;;
esac
if [[ "$detected" == "${{ matrix.expected }}" ]]; then
echo "✅ Correctly detected: $detected"
else
echo "❌ Detection mismatch: expected '${{ matrix.expected }}', got '$detected'"
exit 1
fi
test-prerequisites:
name: Test Prerequisites
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-15]
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Test required tools
run: |
echo "Testing required tools..."
# Test curl
if command -v curl &> /dev/null; then
echo "✅ curl available"
curl --version | head -1
else
echo "❌ curl not available"
exit 1
fi
# Test unzip
if command -v unzip &> /dev/null; then
echo "✅ unzip available"
unzip -v | head -1
else
echo "❌ unzip not available"
exit 1
fi
- name: Test without prerequisites
run: |
echo "Testing error handling when prerequisites are missing..."
# Create a version of the script that will fail prerequisite check
sed 's/command -v curl/command -v nonexistent-tool/' install.sh > test-install.sh
if bash test-install.sh 2>&1 | grep -q "required to install"; then
echo "✅ Prerequisites check works"
else
echo "❌ Prerequisites check failed"
exit 1
fi
rm test-install.sh
summary:
name: Installation Test Summary
needs:
[
install-script-unit-tests,
test-install-script,
test-npm-fallback,
test-architecture-detection,
test-prerequisites,
]
runs-on: ubuntu-latest
if: always()
steps:
- name: Test Results Summary
run: |
echo "## Installation Test Results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [[ "${{ needs.install-script-unit-tests.result }}" == "success" ]]; then
echo "✅ **Install Script Unit Tests**: PASSED" >> $GITHUB_STEP_SUMMARY
else
echo "❌ **Install Script Unit Tests**: FAILED" >> $GITHUB_STEP_SUMMARY
fi
if [[ "${{ needs.test-install-script.result }}" == "success" ]]; then
echo "✅ **Install Script Tests**: PASSED" >> $GITHUB_STEP_SUMMARY
else
echo "❌ **Install Script Tests**: FAILED" >> $GITHUB_STEP_SUMMARY
fi
if [[ "${{ needs.test-npm-fallback.result }}" == "success" ]]; then
echo "✅ **npm Fallback Tests**: PASSED" >> $GITHUB_STEP_SUMMARY
else
echo "❌ **npm Fallback Tests**: FAILED" >> $GITHUB_STEP_SUMMARY
fi
if [[ "${{ needs.test-architecture-detection.result }}" == "success" ]]; then
echo "✅ **Architecture Detection**: PASSED" >> $GITHUB_STEP_SUMMARY
else
echo "❌ **Architecture Detection**: FAILED" >> $GITHUB_STEP_SUMMARY
fi
if [[ "${{ needs.test-prerequisites.result }}" == "success" ]]; then
echo "✅ **Prerequisites Check**: PASSED" >> $GITHUB_STEP_SUMMARY
else
echo "❌ **Prerequisites Check**: FAILED" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Test Matrix Coverage:**" >> $GITHUB_STEP_SUMMARY
echo "- **Linux x64**: Ubuntu 22.04, Latest" >> $GITHUB_STEP_SUMMARY
echo "- **Linux ARM64**: Ubuntu 22.04, 24.04 (via Depot)" >> $GITHUB_STEP_SUMMARY
echo "- **macOS x64**: macOS 15 (Intel)" >> $GITHUB_STEP_SUMMARY
echo "- **macOS ARM64**: macOS 14, macOS 15 (Apple Silicon)" >> $GITHUB_STEP_SUMMARY
echo "- **Shells**: Bash and Zsh" >> $GITHUB_STEP_SUMMARY
echo "- **Node.js**: 18, 20, 22 (npm fallback)" >> $GITHUB_STEP_SUMMARY
echo "- **Total platforms**: 4 architectures across 15+ environments" >> $GITHUB_STEP_SUMMARY
+107
View File
@@ -0,0 +1,107 @@
name: Dead Code
# Surfaces likely-dead code on every PR so orphaned files/exports/deps get
# noticed instead of rotting (see the root Dockerfile cleanup, #3783).
#
# Report-only by design: every job writes findings to the run's Step Summary
# and never fails the build. These tools carry false positives (public API
# surface, dynamic imports, import-map targets), so a red X here would train
# people to ignore it. Tighten a job to blocking only once its config is
# refined enough that a clean run is the steady state.
on:
push:
branches: [master, next]
paths:
- 'ts/**'
- 'python/**'
- '.github/actions/**'
- '.github/workflows/**'
- '.github/scripts/check-orphan-ci.sh'
- 'knip.json'
- 'package.json'
- 'pnpm-workspace.yaml'
- 'pnpm-lock.yaml'
- 'mise.toml'
- 'mise.lock'
pull_request:
branches: [master, next]
paths:
- 'ts/**'
- 'python/**'
- '.github/actions/**'
- '.github/workflows/**'
- '.github/scripts/check-orphan-ci.sh'
- 'knip.json'
- 'package.json'
- 'pnpm-workspace.yaml'
- 'pnpm-lock.yaml'
- 'mise.toml'
- 'mise.lock'
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
permissions:
contents: read
jobs:
knip:
name: TypeScript (knip)
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup Node.js, pnpm, Bun
uses: ./.github/actions/setup-node-pnpm-bun
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run knip (report-only)
run: |
{
echo '## Knip — TypeScript dead code'
echo ''
echo 'Unused files, exports, types and dependencies. False positives'
echo 'usually mean a missing `entry` in `knip.json`; vet before deleting.'
echo ''
echo '```'
pnpm dlx knip@5 --no-exit-code --no-progress 2>&1 || true
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
vulture:
name: Python (vulture)
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup Python with UV
uses: ./.github/actions/setup-python-uv
- name: Run vulture (report-only)
run: |
{
echo '## Vulture — Python dead code'
echo ''
echo 'Likely-unused functions, classes and variables. Suppress'
echo 'confirmed false positives in `python/config/vulture_allowlist.py`.'
echo ''
echo '```'
(cd python && uv run nox -s dead_code) 2>&1 || true
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
orphaned-ci:
name: GitHub Actions (orphan check)
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Check for orphaned workflows and composite actions
run: bash .github/scripts/check-orphan-ci.sh >> "$GITHUB_STEP_SUMMARY"
+45
View File
@@ -0,0 +1,45 @@
name: Docs - Check Links
on:
pull_request:
paths:
- 'docs/**'
- '.github/actions/setup-node-pnpm-bun/action.yml'
- '.github/workflows/docs-check-links.yml'
- 'mise.toml'
- 'mise.lock'
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
permissions:
contents: read
jobs:
check-links:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./docs
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup Node.js, pnpm, Bun
uses: ./.github/actions/setup-node-pnpm-bun
- name: Cache bun dependencies
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.bun/install/cache
key: ${{ runner.os }}-bun-${{ hashFiles('docs/bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-
- name: Install dependencies
run: bun install
- name: Validate links
run: bun run lint:links
+61
View File
@@ -0,0 +1,61 @@
name: Docs - Sync Algolia Search
on:
push:
branches:
- next
paths:
- 'docs/**'
- '.github/actions/setup-node-pnpm-bun/action.yml'
- '.github/workflows/docs-search-sync.yml'
- 'mise.toml'
- 'mise.lock'
workflow_dispatch:
permissions:
contents: read
jobs:
sync-search:
runs-on: ubuntu-latest
# The Algolia admin key is stored as an environment secret, not a repo
# secret. Declaring the environment is what exposes `secrets.*` (and any
# env-scoped `vars.*`) to this job — without it the key resolves empty and
# the sync silently skips.
environment: Production
defaults:
run:
working-directory: ./docs
env:
ALGOLIA_APP_ID: ${{ vars.ALGOLIA_APP_ID || '62HI9PQZ1L' }}
ALGOLIA_ADMIN_API_KEY: ${{ secrets.ALGOLIA_ADMIN_API_KEY }}
ALGOLIA_INDEX_NAME: ${{ vars.ALGOLIA_INDEX_NAME || 'docs_composio' }}
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup Node.js, pnpm, Bun
uses: ./.github/actions/setup-node-pnpm-bun
- name: Cache bun dependencies
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.bun/install/cache
key: ${{ runner.os }}-bun-${{ hashFiles('docs/bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-
- name: Install dependencies
run: bun install
- name: Validate search index generation
run: bun run sync:search --dry-run
- name: Sync Algolia search index
if: env.ALGOLIA_APP_ID != '' && env.ALGOLIA_ADMIN_API_KEY != ''
run: bun run sync:search
- name: Skip sync without Algolia secrets
if: env.ALGOLIA_APP_ID == '' || env.ALGOLIA_ADMIN_API_KEY == ''
run: echo "Skipping Algolia sync because ALGOLIA_APP_ID or ALGOLIA_ADMIN_API_KEY is not configured."
+75
View File
@@ -0,0 +1,75 @@
name: Docs - Tests
on:
pull_request:
paths:
- 'docs/**'
- '.github/actions/setup-node-pnpm-bun/action.yml'
- '.github/workflows/docs-tests.yml'
- 'mise.toml'
- 'mise.lock'
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./docs
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup Node.js, pnpm, Bun
uses: ./.github/actions/setup-node-pnpm-bun
- name: Cache bun dependencies
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.bun/install/cache
key: ${{ runner.os }}-bun-${{ hashFiles('docs/bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-
- name: Install dependencies
run: bun install
- name: Run static tests
run: bun run test
- name: Build
run: bun run build
- name: Start server and run integration tests
run: |
bun run start &
SERVER_PID=$!
echo "Waiting for server to start..."
for i in $(seq 1 30); do
if curl -s http://localhost:3000 > /dev/null 2>&1; then
echo "Server is ready!"
break
fi
if [ $i -eq 30 ]; then
echo "Server failed to start within 30 seconds"
kill $SERVER_PID 2>/dev/null || true
exit 1
fi
sleep 1
done
set +e
bun run test:integration
TEST_EXIT=$?
set -e
kill $SERVER_PID 2>/dev/null || true
exit $TEST_EXIT
@@ -0,0 +1,48 @@
name: Docs - TypeScript Code Validation
on:
pull_request:
paths:
- 'docs/**'
- '.github/actions/setup-node-pnpm-bun/action.yml'
- '.github/workflows/docs-typescript-check.yml'
- 'mise.toml'
- 'mise.lock'
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
permissions:
contents: read
jobs:
typescript-check:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./docs
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup Node.js, pnpm, Bun
uses: ./.github/actions/setup-node-pnpm-bun
- name: Cache bun dependencies
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.bun/install/cache
key: ${{ runner.os }}-bun-${{ hashFiles('docs/bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-
- name: Install dependencies
run: bun install
- name: TypeScript type check
run: bun run types:check
- name: Build (validates Twoslash TypeScript code blocks)
run: bun run build
+156
View File
@@ -0,0 +1,156 @@
name: Docs - Update Data
on:
schedule:
- cron: '0 */5 * * *'
repository_dispatch:
types: [apollo-production-deploy]
workflow_dispatch:
permissions:
contents: read
jobs:
update-data:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./docs
permissions:
contents: write
pull-requests: write
env:
COMPOSIO_API_KEY: ${{ secrets.COMPOSIO_API_KEY }}
steps:
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
client-id: ${{ vars.RELEASE_BOT_CLIENT_ID }}
private-key: ${{ secrets.RELEASE_BOT_APP_PRIVATE_KEY }}
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
token: ${{ steps.app-token.outputs.token }}
- name: Log trigger source
env:
EVENT_NAME: ${{ github.event_name }}
HERMES_COMMIT: ${{ github.event.client_payload.hermes_commit }}
DEPLOY_TIMESTAMP: ${{ github.event.client_payload.timestamp }}
run: |
echo "Workflow triggered by: $EVENT_NAME"
if [ "$EVENT_NAME" = "repository_dispatch" ]; then
echo "Triggered by Apollo production deployment"
echo "Hermes commit: $HERMES_COMMIT"
echo "Timestamp: $DEPLOY_TIMESTAMP"
fi
- name: Setup Node.js, pnpm, Bun
uses: ./.github/actions/setup-node-pnpm-bun
- name: Configure staging API endpoints
env:
COMPOSIO_BASE_URL_STAGING: ${{ secrets.COMPOSIO_BASE_URL_STAGING }}
run: |
staging_base="${COMPOSIO_BASE_URL_STAGING%/}"
if [ -z "$staging_base" ]; then
echo "::error::COMPOSIO_BASE_URL_STAGING is not set"
exit 1
fi
if [[ "$staging_base" == "https://backend.composio.dev" || "$staging_base" == "https://backend.composio.dev/"* ]]; then
echo "::error::COMPOSIO_BASE_URL_STAGING points at production"
exit 1
fi
staging_origin="${staging_base%/api/v3.1}"
staging_origin="${staging_origin%/api/v3}"
staging_v3_base="$staging_origin/api/v3"
staging_v31_base="$staging_origin/api/v3.1"
{
echo "COMPOSIO_API_BASE=$staging_v3_base"
echo "OPENAPI_SPEC_URL=$staging_v3_base/openapi.json"
echo "OPENAPI_V31_SPEC_URL=$staging_v31_base/openapi.json"
} >> "$GITHUB_ENV"
echo "Configured staging API endpoints"
- name: Cache bun dependencies
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.bun/install/cache
key: ${{ runner.os }}-bun-${{ hashFiles('docs/bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-
- name: Install dependencies
run: bun install
- name: Generate toolkits data
run: bun run generate:toolkits
- name: Fetch OpenAPI spec
run: bun run scripts/fetch-openapi.mjs
- name: Generate API index pages
run: bun run generate:api-index
- name: Generate meta tools reference
run: bun run generate:meta-tools
- name: Check for changes
id: changes
run: |
cd ..
git add -N docs/public/data/ docs/public/openapi.json docs/public/openapi-v3.json docs/content/reference/api-reference/ docs/content/reference/v3/api-reference/ docs/content/toolkits/meta-tools/ 2>/dev/null || true
if git diff --quiet docs/public/data/ docs/public/openapi.json docs/public/openapi-v3.json docs/content/reference/api-reference/ docs/content/reference/v3/api-reference/ docs/content/toolkits/meta-tools/ 2>/dev/null; then
echo "has_changes=false" >> "$GITHUB_OUTPUT"
echo "No changes detected"
else
echo "has_changes=true" >> "$GITHUB_OUTPUT"
echo "Changes detected:"
git diff --stat docs/public/data/ docs/public/openapi.json docs/public/openapi-v3.json docs/content/reference/api-reference/ docs/content/reference/v3/api-reference/ docs/content/toolkits/meta-tools/ 2>/dev/null || true
fi
- name: Create Pull Request
id: create-pr
if: steps.changes.outputs.has_changes == 'true'
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
token: ${{ steps.app-token.outputs.token }}
commit-message: 'docs: update toolkits and API data'
title: 'docs: update toolkits, API spec, and meta tools data'
body: |
## Summary
Automated sync of backend data into the docs site. Triggered by: `${{ github.event_name }}`${{ github.event_name == 'repository_dispatch' && format(' (Hermes commit: {0})', github.event.client_payload.hermes_commit) || '' }}.
## What changed
- **Toolkit catalog** (`docs/public/data/toolkits.json`, `toolkits-list.json`) — refreshed list of available toolkits, auth schemes, and tools from the backend API
- **OpenAPI specs** (`docs/public/openapi.json`, `docs/public/openapi-v3.json`) — latest v3.1 and v3.0 API specifications fetched from staging
- **API reference pages** (`docs/content/reference/api-reference/`, `docs/content/reference/v3/api-reference/`) — regenerated index pages for both API versions
- **Meta tools reference** (`docs/public/data/meta-tools.json`, `docs/content/toolkits/meta-tools/*.mdx`) — updated meta tool schemas and reference docs
branch: docs/auto-update-data
base: next
add-paths: |
docs/public/data/
docs/public/openapi.json
docs/public/openapi-v3.json
docs/content/reference/api-reference/
docs/content/reference/v3/api-reference/
docs/content/toolkits/meta-tools/
- name: Request review from trigger actor
if: steps.create-pr.outputs.pull-request-number
continue-on-error: true
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
PR_NUMBER: ${{ steps.create-pr.outputs.pull-request-number }}
AUTHOR: ${{ github.actor }}
run: |
gh pr edit "$PR_NUMBER" --add-reviewer "$AUTHOR" || \
gh pr edit "$PR_NUMBER" --add-reviewer "Sushmithamallesh"
@@ -0,0 +1,110 @@
name: Changelog Notification
on:
push:
branches: [next]
paths:
- 'docs/content/changelog/**.mdx'
permissions:
contents: read
jobs:
notify-changelog:
name: Send Changelog Notification
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0 # Fetch full history to detect changes across all commits in push
- name: Get Changed Changelog Files
id: changed-files
run: |
# Get the commit range for this push
BEFORE="${{ github.event.before }}"
AFTER="${{ github.event.after }}"
echo "Push event details:"
echo " Before: $BEFORE"
echo " After: $AFTER"
echo " Commits in push: $(git rev-list --count "$BEFORE".."$AFTER" 2>/dev/null || echo "N/A")"
# Handle first push to branch (before SHA is all zeros)
if [[ "$BEFORE" =~ ^0+$ ]]; then
echo "First push to branch detected, checking only the latest commit"
CHANGED_FILES=$(git diff --name-only --diff-filter=AM HEAD^..HEAD | grep '^docs/content/changelog/.*\.mdx$' || echo "")
else
echo "Checking all commits in push range: $BEFORE..$AFTER"
# Get the list of added/modified changelog files across all commits in this push
CHANGED_FILES=$(git diff --name-only --diff-filter=AM "$BEFORE".."$AFTER" | grep '^docs/content/changelog/.*\.mdx$' || echo "")
fi
if [ -z "$CHANGED_FILES" ]; then
echo "No changelog files changed in this push"
echo "has_changelog=false" >> $GITHUB_OUTPUT
else
echo "Changelog files found:"
echo "$CHANGED_FILES"
# Get the first changelog file (in case multiple were added)
CHANGELOG_FILE=$(echo "$CHANGED_FILES" | head -n 1)
echo ""
echo "Processing changelog: $CHANGELOG_FILE"
echo "changelog_file=$CHANGELOG_FILE" >> $GITHUB_OUTPUT
echo "has_changelog=true" >> $GITHUB_OUTPUT
fi
- name: Extract Changelog Metadata
if: steps.changed-files.outputs.has_changelog == 'true'
id: extract-metadata
run: |
CHANGELOG_FILE="${{ steps.changed-files.outputs.changelog_file }}"
# Extract title from frontmatter
TITLE=$(grep "^title:" "$CHANGELOG_FILE" | sed "s/^title: *['\"]*//" | sed "s/['\"]* *$//")
# Extract date from frontmatter
DATE=$(grep "^date:" "$CHANGELOG_FILE" | sed "s/^date: *['\"]*//" | sed "s/['\"]* *$//")
# Format date for URL (YYYY-MM-DD to YYYY/MM/DD)
URL_DATE=$(echo "$DATE" | sed 's/-/\//g')
# Build the changelog URL
CHANGELOG_URL="https://docs.composio.dev/docs/changelog/${URL_DATE}"
# Get the commit author who added/modified this changelog
AUTHOR=$(git log -1 --format='%an' -- "$CHANGELOG_FILE")
AUTHOR_EMAIL=$(git log -1 --format='%ae' -- "$CHANGELOG_FILE")
# Build properly escaped JSON payload using jq
SLACK_PAYLOAD=$(jq -n \
--arg title "$TITLE" \
--arg date "$DATE" \
--arg url "$CHANGELOG_URL" \
--arg author "$AUTHOR" \
'{text: "*New Changelog Published*\n\n*\($title)*\n\nDate: \($date)\n<\($url)|View Changelog>\nAuthor: \($author)"}')
echo "title=$TITLE" >> $GITHUB_OUTPUT
echo "date=$DATE" >> $GITHUB_OUTPUT
echo "url=$CHANGELOG_URL" >> $GITHUB_OUTPUT
echo "author=$AUTHOR" >> $GITHUB_OUTPUT
echo "author_email=$AUTHOR_EMAIL" >> $GITHUB_OUTPUT
# Save the JSON payload for the next step
echo "slack_payload<<EOF" >> $GITHUB_OUTPUT
echo "$SLACK_PAYLOAD" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
echo "📝 Changelog Title: $TITLE"
echo "📅 Date: $DATE"
echo "🔗 URL: $CHANGELOG_URL"
echo "👤 Author: $AUTHOR ($AUTHOR_EMAIL)"
- name: Send Slack Notification
if: steps.changed-files.outputs.has_changelog == 'true'
uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3
with:
webhook: ${{ secrets.SLACK_CUSTOMER_COMMS_WEBHOOK_URL }}
webhook-type: incoming-webhook
payload: ${{ steps.extract-metadata.outputs.slack_payload }}
@@ -0,0 +1,93 @@
name: Changelog → Docs PR
on:
push:
branches: [next]
paths:
- 'docs/content/changelog/**.mdx'
permissions:
contents: read
jobs:
suggest-docs-updates:
name: Suggest Documentation Updates
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
- name: Detect new changelog files
id: detect
run: |
BEFORE="${{ github.event.before }}"
AFTER="${{ github.event.after }}"
if [[ "$BEFORE" =~ ^0+$ ]]; then
FILES=$(git diff --name-only --diff-filter=AM HEAD^..HEAD | grep '^docs/content/changelog/.*\.mdx$' || echo "")
else
FILES=$(git diff --name-only --diff-filter=AM "$BEFORE".."$AFTER" | grep '^docs/content/changelog/.*\.mdx$' || echo "")
fi
if [ -z "$FILES" ]; then
echo "found=false" >> $GITHUB_OUTPUT
else
echo "found=true" >> $GITHUB_OUTPUT
echo "files<<EOF" >> $GITHUB_OUTPUT
echo "$FILES" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
fi
- name: Codex updates docs
if: steps.detect.outputs.found == 'true'
uses: openai/codex-action@52fe01ec70a42f454c9d2ebd47598f9fd6893d56 # v1.11
with:
openai-api-key: ${{ secrets.OPENAI_API_KEY }}
sandbox: workspace-write
prompt: |
New changelog entries were merged to next. Read the agent instructions and update documentation accordingly.
1. Read docs/agent-guidance/agents/changelog-docs-updater.md for full instructions
2. Read each of these changelog files:
${{ steps.detect.outputs.files }}
3. For each change, search docs/content/docs/ for pages that need updating
4. Make the documentation updates following the agent instructions
5. If no docs changes are needed, make no file changes and explain why
- name: Create PR if docs changed
if: steps.detect.outputs.found == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [ -z "$(git status --porcelain)" ]; then
echo "No documentation changes needed"
exit 0
fi
BRANCH="docs/changelog-update-${GITHUB_SHA::7}"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git checkout -b "$BRANCH"
git add -A
git commit -m "docs: update documentation for new changelog entries"
git push -u origin "$BRANCH"
gh pr create \
--base next \
--head "$BRANCH" \
--title "docs: update documentation for new changelog entries" \
--reviewer "$GITHUB_ACTOR" \
--body "$(cat <<'EOF'
## Summary
Automated documentation updates triggered by new changelog entries merged to next.
Generated by Codex via GitHub Actions.
EOF
)"
+122
View File
@@ -0,0 +1,122 @@
name: Docs Health Check
on:
schedule:
- cron: '0 * * * *' # Every hour
workflow_dispatch: # Manual trigger
permissions:
contents: read
jobs:
health-check:
name: Check docs accessibility
runs-on: ubuntu-latest
steps:
- name: Check endpoints
id: check
run: |
BASE_URL="https://docs.composio.dev"
FAIL_FILE=$(mktemp)
CHECKED=0
FAIL_COUNT=0
check_url() {
local label="$1"
local url="$2"
shift 2
CHECKED=$((CHECKED + 1))
local attempt=0
local STATUS="000"
while [ "$attempt" -lt 2 ]; do
if STATUS=$(curl -sL -o /dev/null -w "%{http_code}" --max-time 15 "$@" "$url" 2>/dev/null); then
:
else
STATUS="000"
fi
if [ "$STATUS" -ge 400 2>/dev/null ] || [ "$STATUS" = "000" ]; then
attempt=$((attempt + 1))
[ "$attempt" -lt 2 ] && sleep 3
else
break
fi
done
if [ "$STATUS" -ge 400 2>/dev/null ] || [ "$STATUS" = "000" ]; then
echo "FAIL: ${label} → ${STATUS}"
echo "${label} → ${STATUS}" >> "$FAIL_FILE"
FAIL_COUNT=$((FAIL_COUNT + 1))
else
echo " OK: ${label} → ${STATUS}"
fi
}
# LLM/AI agent entry points
check_url "/llms.txt" "${BASE_URL}/llms.txt"
check_url "/llms-full.txt" "${BASE_URL}/llms-full.txt"
check_url "/docs/quickstart.md" "${BASE_URL}/docs/quickstart.md"
check_url "/docs/tools-and-toolkits.md" "${BASE_URL}/docs/tools-and-toolkits.md"
# Main pages
check_url "/docs" "${BASE_URL}/docs"
check_url "/docs/quickstart" "${BASE_URL}/docs/quickstart"
check_url "/docs/authentication" "${BASE_URL}/docs/authentication"
check_url "/docs/tools-and-toolkits" "${BASE_URL}/docs/tools-and-toolkits"
check_url "/docs/users-and-sessions" "${BASE_URL}/docs/users-and-sessions"
check_url "/docs/configuring-sessions" "${BASE_URL}/docs/configuring-sessions"
check_url "/cookbooks" "${BASE_URL}/cookbooks"
check_url "/toolkits" "${BASE_URL}/toolkits"
check_url "/reference" "${BASE_URL}/reference"
# Provider pages
check_url "/docs/providers/openai" "${BASE_URL}/docs/providers/openai"
check_url "/docs/providers/vercel" "${BASE_URL}/docs/providers/vercel"
check_url "/docs/providers/anthropic" "${BASE_URL}/docs/providers/anthropic"
# Key sub-pages
check_url "/docs/tools-direct/fetching-tools" "${BASE_URL}/docs/tools-direct/fetching-tools"
check_url "/docs/tools-direct/executing-tools" "${BASE_URL}/docs/tools-direct/executing-tools"
check_url "/docs/authenticating-users/in-chat-authentication" "${BASE_URL}/docs/authenticating-users/in-chat-authentication"
check_url "/docs/common-faq" "${BASE_URL}/docs/common-faq"
# Markdown content negotiation
check_url "/docs/quickstart (markdown)" "${BASE_URL}/docs/quickstart" -H "Accept: text/markdown"
check_url "/docs/tools-and-toolkits (markdown)" "${BASE_URL}/docs/tools-and-toolkits" -H "Accept: text/markdown"
check_url "/docs/authentication (markdown)" "${BASE_URL}/docs/authentication" -H "Accept: text/markdown"
check_url "/docs/providers/openai (markdown)" "${BASE_URL}/docs/providers/openai" -H "Accept: text/markdown"
check_url "/cookbooks (markdown)" "${BASE_URL}/cookbooks" -H "Accept: text/markdown"
if [ "$FAIL_COUNT" -gt 0 ]; then
echo "has_failures=true" >> $GITHUB_OUTPUT
# Build the Slack message with real newlines
FAIL_LIST=$(sed 's/^/• /' "$FAIL_FILE")
RUN_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
PAYLOAD=$(jq -n \
--arg fails "$FAIL_LIST" \
--arg count "$FAIL_COUNT" \
--arg total "$CHECKED" \
--arg url "$RUN_URL" \
'{text: ("🚨 *Docs Health Check*\n\n" + $count + "/" + $total + " endpoints failing:\n" + $fails + "\n\n<" + $url + "|View logs>")}')
echo "slack_payload<<EOF" >> $GITHUB_OUTPUT
echo "$PAYLOAD" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
else
echo "has_failures=false" >> $GITHUB_OUTPUT
echo "All $CHECKED endpoints healthy"
fi
rm -f "$FAIL_FILE"
- name: Notify Slack
if: steps.check.outputs.has_failures == 'true'
uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3
with:
webhook: ${{ secrets.SLACK_POD_DX_WEBHOOK_URL }}
webhook-type: incoming-webhook
payload: ${{ steps.check.outputs.slack_payload }}
- name: Fail on unhealthy endpoints
if: steps.check.outputs.has_failures == 'true'
run: exit 1
+175
View File
@@ -0,0 +1,175 @@
name: Docs - Sync guides on SDK changes
on:
push:
branches: [next]
paths:
- 'ts/packages/core/src/**'
- 'ts/packages/cli/src/**'
- 'ts/packages/providers/*/src/**'
- 'python/composio/**'
workflow_dispatch:
permissions:
contents: read
jobs:
sync-docs:
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: write
pull-requests: write
id-token: write
steps:
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
client-id: ${{ vars.RELEASE_BOT_CLIENT_ID }}
private-key: ${{ secrets.RELEASE_BOT_APP_PRIVATE_KEY }}
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
token: ${{ steps.app-token.outputs.token }}
fetch-depth: 0
- name: Get SDK diff
id: diff
env:
BEFORE: ${{ github.event.before }}
AFTER: ${{ github.event.after }}
run: |
if [ -z "$AFTER" ] || ! git cat-file -e "$AFTER" 2>/dev/null; then
AFTER=$(git rev-parse HEAD)
fi
if [ -z "$BEFORE" ] || [[ "$BEFORE" =~ ^0+$ ]] || ! git cat-file -e "$BEFORE" 2>/dev/null; then
echo "BEFORE ref unavailable — falling back to HEAD~1"
BEFORE=$(git rev-parse HEAD~1)
fi
git diff "$BEFORE".."$AFTER" -- \
ts/packages/core/src/ \
ts/packages/cli/src/ \
ts/packages/providers/*/src/ \
python/composio/ > /tmp/sdk-diff.patch
if [ ! -s /tmp/sdk-diff.patch ]; then
echo "has_changes=false" >> "$GITHUB_OUTPUT"
else
echo "has_changes=true" >> "$GITHUB_OUTPUT"
echo "$(wc -l < /tmp/sdk-diff.patch) lines of SDK changes"
fi
- name: Check docs for staleness
if: steps.diff.outputs.has_changes == 'true'
uses: anthropics/claude-code-action/base-action@536f2c32a39763739000b0e1ac69ca2647d97ce9 # v1.0.170
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
claude_args: |
--allowedTools "Read,Write,Edit,Glob,Grep"
prompt: |
SDK source code was just updated. Check if any documentation needs to change.
1. Read the SDK diff at /tmp/sdk-diff.patch to understand what changed
2. Focus on user-facing changes: new/renamed/removed methods, changed parameters, new features, changed behavior. Skip internal refactors, test changes, and type-only changes.
3. If there are user-facing changes, search docs/content/docs/ for pages that reference the affected APIs, patterns, or features
4. Update any docs that would be stale or incorrect after this SDK change — guides, FAQs, examples, quickstart, etc.
5. Do NOT touch docs/content/reference/ (those are auto-generated separately)
6. Do NOT touch docs/content/changelog/ (changelogs are written separately)
7. Do NOT edit docs/package.json or docs/bun.lock — the `@composio/*` version pins are managed automatically by the dependency-realignment step below, which runs after you and overwrites any changes you make to them
8. If no docs changes are needed, make no file changes
9. Do NOT add type assertions or casts to force a snippet to compile against an API that is missing from the published `@composio/*` package. If a feature isn't released yet, document the released behavior and let the Twoslash build flag the gap.
- name: Setup Node.js, pnpm, Bun
if: steps.diff.outputs.has_changes == 'true'
uses: ./.github/actions/setup-node-pnpm-bun
- name: Align docs SDK deps with latest published releases
if: steps.diff.outputs.has_changes == 'true'
working-directory: ./docs
run: |
# Docs is a standalone Bun app outside the pnpm workspace: it pins
# published @composio/* versions instead of linking workspace source,
# so its Twoslash snippets and example apps only ever demonstrate APIs
# that users can actually install. Realign those pins to the latest
# released versions on every sync so they never drift behind the SDK
# (the drift that previously forced workaround casts into snippets).
# This runs AFTER the content pass so the committed pins are always
# authoritative — even if that pass edited package.json/bun.lock, this
# step resets the @composio/* pins and regenerates the lockfile.
# The PR's Twoslash build (docs-typescript-check.yml) gates the result:
# if a documented feature isn't published yet, the build fails loudly
# instead of the gap being papered over.
set -euo pipefail
# Discard any edits the content pass made to these managed files so the
# realignment is the sole source of truth for the dependency pins.
git checkout -- package.json bun.lock
changed=0
for section in dependencies devDependencies; do
pkgs=$(jq -r --arg s "$section" '.[$s] // {} | keys[] | select(startswith("@composio/"))' package.json)
for pkg in $pkgs; do
latest=$(npm view "$pkg" version 2>/dev/null || true)
if [ -z "$latest" ]; then
echo "::warning::could not resolve a published version for $pkg; leaving its pin unchanged"
continue
fi
current=$(jq -r --arg s "$section" --arg p "$pkg" '.[$s][$p]' package.json)
want="^$latest"
if [ "$current" != "$want" ]; then
echo "$section.$pkg: $current -> $want"
npm pkg set "$section.$pkg=$want"
changed=1
fi
done
done
if [ "$changed" -eq 1 ]; then
bun install
else
echo "All @composio/* pins already match the latest published releases."
fi
- name: Create PR if changed
id: create-pr
if: steps.diff.outputs.has_changes == 'true'
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
token: ${{ steps.app-token.outputs.token }}
commit-message: 'docs: update guides for SDK changes'
title: 'docs: update guides for SDK changes'
body: |
## Summary
Automated docs update triggered by SDK source changes on `next`.
- Claude reviewed the SDK diff and updated guides, FAQs, or examples
that reference changed APIs or features.
- The docs `@composio/*` dependencies were realigned to their latest
published releases so Twoslash snippets and example apps validate
against versions users can actually install.
## Review checklist
- [ ] Changes accurately reflect the new SDK behavior
- [ ] No unrelated docs were modified
- [ ] Code examples are correct and complete
- [ ] If a documented feature is not published yet, the Twoslash build
will fail — wait for the release instead of working around it
Generated by Claude Code via GitHub Actions.
branch: docs/auto-sdk-sync
base: next
add-paths: |
docs/content/docs/
docs/package.json
docs/bun.lock
- name: Request review from pusher
if: steps.create-pr.outputs.pull-request-number
continue-on-error: true
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
PR_NUMBER: ${{ steps.create-pr.outputs.pull-request-number }}
AUTHOR: ${{ github.actor }}
run: gh pr edit "$PR_NUMBER" --add-reviewer "$AUTHOR"
@@ -0,0 +1,101 @@
name: Docs - Sync Connect Clients
on:
repository_dispatch:
types: [dashboard-production-deploy]
workflow_dispatch:
permissions:
contents: read
jobs:
sync-clients:
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: write
pull-requests: write
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: next
fetch-depth: 1
- name: Get client definitions
env:
EVENT_NAME: ${{ github.event_name }}
CLIENT_DEFINITIONS: ${{ github.event.client_payload.client_definitions }}
DASHBOARD_TAG: ${{ github.event.client_payload.dashboard_tag }}
GH_TOKEN: ${{ secrets.CI_BOT_TOKEN }}
run: |
set -e
if [ "$EVENT_NAME" = "repository_dispatch" ]; then
if [ -z "$CLIENT_DEFINITIONS" ]; then
echo "::error::repository_dispatch payload missing client_definitions"
exit 1
fi
echo "$CLIENT_DEFINITIONS" | base64 -d > /tmp/client-definitions.ts
echo "Received client-definitions.ts from dashboard deploy (tag: $DASHBOARD_TAG)"
else
echo "Manual trigger — fetching from dashboard repo"
gh api \
repos/ComposioHQ/composio_dashboard/contents/src/app/\(connect\)/\[org\]/~/connect/clients/_components/client-definitions.ts?ref=main \
--jq '.content' | base64 -d > /tmp/client-definitions.ts
fi
if [ ! -s /tmp/client-definitions.ts ]; then
echo "::error::client-definitions.ts is empty"
exit 1
fi
echo "$(wc -l < /tmp/client-definitions.ts) lines"
- name: Sync Connect clients
uses: anthropics/claude-code-action@536f2c32a39763739000b0e1ac69ca2647d97ce9 # v1.0.170
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
allowed_tools: 'Read,Write,Edit,Glob,Grep,Bash(curl *)'
prompt: |
Sync the Composio Connect clients page from the dashboard repo.
1. Read docs/agent-guidance/agents/connect-clients-sync.md for full instructions
2. The client definitions have already been fetched to /tmp/client-definitions.ts — read that file
3. Read the current docs/content/docs/composio-connect.mdx
4. Compare and update if there are any differences (new clients, changed steps, updated descriptions)
5. Download any new client logos to docs/public/images/clients/
6. If no changes are needed, make no file changes
- name: Create PR if changed
id: create-pr
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
commit-message: 'docs: sync Connect clients from dashboard'
title: 'docs: sync Connect clients from dashboard'
body: |
## Summary
Automated sync of AI client definitions from `ComposioHQ/composio_dashboard`.
Updates client setup steps, descriptions, and logos on the Composio Connect page.
## What may have changed
- New or removed AI clients
- Updated setup steps (deeplinks, config changes)
- Category changes (popular/ide/other)
- Client logos
Generated by Claude Code via GitHub Actions.
branch: docs/auto-sync-connect-clients
base: next
add-paths: |
docs/content/docs/composio-connect.mdx
docs/public/images/clients/
- name: Request review from trigger actor
if: steps.create-pr.outputs.pull-request-number
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ steps.create-pr.outputs.pull-request-number }}
AUTHOR: ${{ github.actor }}
run: gh pr edit "$PR_NUMBER" --add-reviewer "$AUTHOR"
+118
View File
@@ -0,0 +1,118 @@
name: Generate SDK Docs
on:
push:
branches: [next]
paths:
- 'ts/packages/core/src/**'
- 'ts/packages/core/scripts/generate-docs.ts'
- 'python/composio/**'
- 'python/scripts/generate-docs.py'
- '.github/actions/setup-node-pnpm-bun/action.yml'
- '.github/actions/setup-python-uv/action.yml'
- '.github/workflows/generate-sdk-docs.yml'
- 'mise.toml'
- 'mise.lock'
workflow_dispatch:
permissions:
contents: read
jobs:
generate-ts-docs:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
client-id: ${{ vars.RELEASE_BOT_CLIENT_ID }}
private-key: ${{ secrets.RELEASE_BOT_APP_PRIVATE_KEY }}
- name: Checkout Code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
token: ${{ steps.app-token.outputs.token }}
- name: Setup Node.js, pnpm, Bun
uses: ./.github/actions/setup-node-pnpm-bun
- run: pnpm install --frozen-lockfile
- run: pnpm --filter @composio/core generate:docs
- name: Create Pull Request
id: create-pr
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
token: ${{ steps.app-token.outputs.token }}
commit-message: 'docs: auto-generate TypeScript SDK reference'
title: 'docs: update TypeScript SDK reference from source'
body: |
## Summary
Auto-generated TypeScript SDK reference docs from `ts/packages/core/src/`.
Regenerates pages at `docs/content/reference/sdk-reference/typescript/` to reflect changes in the core package's public API (new methods, updated signatures, changed types).
branch: docs/auto-update-ts-sdk-reference
base: next
add-paths: docs/content/reference/sdk-reference/typescript/
- name: Request review from pusher
if: steps.create-pr.outputs.pull-request-number
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
PR_NUMBER: ${{ steps.create-pr.outputs.pull-request-number }}
AUTHOR: ${{ github.actor }}
run: gh pr edit "$PR_NUMBER" --add-reviewer "$AUTHOR"
generate-python-docs:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
client-id: ${{ vars.RELEASE_BOT_CLIENT_ID }}
private-key: ${{ secrets.RELEASE_BOT_APP_PRIVATE_KEY }}
- name: Checkout Code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
token: ${{ steps.app-token.outputs.token }}
- name: Setup Python with UV
uses: ./.github/actions/setup-python-uv
- run: cd python && uv run --with griffe python scripts/generate-docs.py
- name: Create Pull Request
id: create-pr
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
token: ${{ steps.app-token.outputs.token }}
commit-message: 'docs: auto-generate Python SDK reference'
title: 'docs: update Python SDK reference from source'
body: |
## Summary
Auto-generated Python SDK reference docs from `python/composio/`.
Regenerates pages at `docs/content/reference/sdk-reference/python/` to reflect changes in the Python package's public API (new methods, updated signatures, changed types).
branch: docs/auto-update-python-sdk-reference
base: next
add-paths: docs/content/reference/sdk-reference/python/
- name: Request review from pusher
if: steps.create-pr.outputs.pull-request-number
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
PR_NUMBER: ${{ steps.create-pr.outputs.pull-request-number }}
AUTHOR: ${{ github.actor }}
run: gh pr edit "$PR_NUMBER" --add-reviewer "$AUTHOR"
+470
View File
@@ -0,0 +1,470 @@
name: Issue Triage
on:
issues:
types: [opened, reopened, edited]
workflow_dispatch:
inputs:
issue_number:
description: "Issue number to triage retrospectively"
required: true
type: string
permissions:
contents: read
issues: write
id-token: write
jobs:
classify:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Load issue context for dispatch runs
if: github.event_name == 'workflow_dispatch'
id: issue-context
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
ISSUE_NUMBER: ${{ inputs.issue_number }}
with:
script: |
const issue_number = Number(process.env.ISSUE_NUMBER);
if (!Number.isInteger(issue_number) || issue_number <= 0) {
core.setFailed(`Invalid issue_number: ${process.env.ISSUE_NUMBER}`);
return;
}
const { data: issue } = await github.rest.issues.get({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number,
});
if (issue.pull_request) {
core.setFailed(`Issue #${issue_number} is a pull request, not an issue.`);
return;
}
core.setOutput("issue_number", String(issue.number));
core.setOutput("issue_title", issue.title || "");
core.setOutput("issue_body", issue.body || "");
core.setOutput("issue_state", issue.state || "open");
- name: Write triage schema
run: |
cat > /tmp/issue-triage-schema.json <<'EOF'
{
"type": "object",
"additionalProperties": false,
"properties": {
"category": {
"type": "string",
"enum": ["docs", "py", "ts", "cli", "tool-request", "feature-request", "support", "other"]
},
"is_bug": {
"type": "boolean"
},
"reason": {
"type": "string"
},
"should_comment": {
"type": "boolean"
},
"comment_body": {
"type": "string"
}
},
"required": ["category", "is_bug", "reason", "should_comment", "comment_body"]
}
EOF
- name: Install Claude Code CLI
run: npm install -g @anthropic-ai/claude-code
- name: Classify issue with Claude Sonnet
id: classify
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ISSUE_TITLE: ${{ github.event_name == 'workflow_dispatch' && steps.issue-context.outputs.issue_title || github.event.issue.title }}
ISSUE_BODY: ${{ github.event_name == 'workflow_dispatch' && steps.issue-context.outputs.issue_body || github.event.issue.body }}
run: |
set -uo pipefail
which claude
claude --version || true
PROMPT=$(cat <<'EOF'
Read the issue title and body from the ISSUE_TITLE and ISSUE_BODY environment variables.
Classify the issue for routing and determine whether it is a bug report.
Allowed categories:
- docs
- py
- ts
- cli
- tool-request
- feature-request
- support
- other
Use semantic meaning, not only keywords.
Choose tool-request when the issue is primarily asking for a new integration, toolkit, app, or tool.
Choose feature-request when the issue is asking for a product, SDK, docs, or CLI improvement that is not a tool request.
Choose support when the issue is primarily a product/support question, account/integration configuration problem, or a hosted Composio platform/tooling problem rather than a code defect in this repository. This includes managed OAuth app configuration, provider app verification or consent-screen blocks, requested scopes for hosted/managed integrations, dashboard/back-office setup, billing/account questions, usage questions where the SDK appears to be behaving as designed, and bugs in existing hosted integrations/tools/toolkits that require backend/tool-catalog/support follow-up rather than SDK code changes.
For example, an issue about Google Drive managed OAuth scopes or Google blocking the managed app should be support, not ts and not a bug, unless the issue clearly identifies a defect in SDK code.
For example, an issue like GitHub #3291 where an existing hosted tool such as SPOTIFY_GET_PLAYLIST_ITEMS calls a deprecated third-party API endpoint, fails with 403, or needs a backend/tool definition update should be support and a bug, not ts, not py, not cli, and not tool-request.
Choose cli only when the issue is specifically about the CLI experience or ts/packages/cli.
Choose py only when the issue is primarily about Python SDK code in this repository.
Choose ts only when the issue is primarily about TypeScript SDK code or TS packages other than the CLI in this repository.
Choose docs only when the issue is primarily about documentation.
Never choose tool-request for a broken existing integration or tool. Those are bugs, not tool requests, even if the title/body mentions a tool name or toolkit slug. If the broken existing integration/tool does not identify an SDK, CLI, docs, or Python code defect in this repository, choose support so it is forwarded to Plain.
Set is_bug to true when the issue is primarily reporting broken behavior, a defect, an error, a regression, or unexpected behavior, including support-routed hosted integration/tool bugs.
Set is_bug to false for feature requests, questions, maintenance, cleanup, and true new-tool requests.
Set should_comment to true when the issue appears incorrect, non-actionable, missing the information needed to reproduce or investigate, or when category is support.
Set should_comment to false otherwise.
If should_comment is true, provide comment_body as a short issue comment addressed to the reporter.
The comment must:
- clearly say it is an automated Claude triage note
- clearly say Claude cannot reply back in a conversation
- ask for the specific missing details needed to investigate, especially repro steps, expected behavior, actual behavior, environment, logs, or screenshots when relevant
- for support issues, say this does not look like an SDK bug and that it is being routed to support
- stay concise and professional
If should_comment is false, set comment_body to an empty string.
EOF
)
set +e
claude -p "$PROMPT" \
--model claude-sonnet-4-6 \
--max-turns 10 \
--output-format json \
--dangerously-skip-permissions \
--append-system-prompt "Respond with ONLY a raw JSON object (no markdown fences, no prose) that matches the schema in /tmp/issue-triage-schema.json." \
> /tmp/claude-response.json 2> /tmp/claude-stderr.log
CLAUDE_EXIT=$?
set -e
echo "--- claude exit code: $CLAUDE_EXIT ---"
echo "--- claude stderr ---"
cat /tmp/claude-stderr.log || true
echo "--- end stderr ---"
echo "--- raw claude response ---"
cat /tmp/claude-response.json || true
echo
echo "--- end raw ---"
if [ "$CLAUDE_EXIT" -ne 0 ]; then
echo "Claude CLI failed with exit code $CLAUDE_EXIT"
exit 1
fi
STRUCTURED=$(jq -r '.result' /tmp/claude-response.json)
# Strip optional ```json fences just in case
STRUCTURED=$(echo "$STRUCTURED" | sed -E 's/^```(json)?//; s/```$//' | jq -c .)
echo "--- parsed structured output ---"
echo "$STRUCTURED"
echo "--- end parsed ---"
{
echo "structured_output<<CLAUDE_EOF"
echo "$STRUCTURED"
echo "CLAUDE_EOF"
} >> "$GITHUB_OUTPUT"
- name: Apply labels, assignees, and tool-request handling
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
STRUCTURED_OUTPUT: ${{ steps.classify.outputs.structured_output }}
PLAIN_API_KEY: ${{ secrets.PLAIN_API_KEY }}
with:
script: |
const text = process.env.STRUCTURED_OUTPUT || "";
let parsed;
try {
parsed = JSON.parse(text);
} catch (error) {
core.setFailed(`Could not parse structured_output as JSON: ${text}`);
return;
}
const allowed = new Set(["docs", "py", "ts", "cli", "tool-request", "feature-request", "support", "other"]);
if (!allowed.has(parsed.category)) {
core.setFailed(`Unexpected category: ${parsed.category}`);
return;
}
const category = parsed.category;
const isBug = Boolean(parsed.is_bug);
const reason = parsed.reason || "";
const shouldComment = Boolean(parsed.should_comment);
const commentBody = parsed.comment_body || "";
const issue_number = context.eventName === "workflow_dispatch"
? Number("${{ steps.issue-context.outputs.issue_number }}")
: context.payload.issue.number;
const owner = context.repo.owner;
const repo = context.repo.repo;
const issueState = context.eventName === "workflow_dispatch"
? "${{ steps.issue-context.outputs.issue_state }}"
: context.payload.issue.state;
const { data: issue } = await github.rest.issues.get({
owner,
repo,
issue_number,
});
const labels = [];
const assignees = [];
const suppressToolRequestHandling = isBug && category === "tool-request";
const isSupport = category === "support";
if (suppressToolRequestHandling) {
core.info(
`Suppressing tool-request handling for issue #${issue_number} because it is classified as a bug. Original reason: ${reason}`
);
}
if (isBug && !isSupport) {
labels.push("bug");
}
if (category === "docs") {
labels.push("docs");
assignees.push("shawnesquivel", "Sushmithamallesh");
} else if (category === "py") {
labels.push("py");
assignees.push("jkomyno");
} else if (category === "ts") {
labels.push("ts");
assignees.push("jkomyno");
} else if (category === "cli") {
labels.push("cli");
assignees.push("CryogenicPlanet");
} else if (category === "feature-request") {
labels.push("feature-request");
} else if (isSupport) {
labels.push("support");
} else if (category === "tool-request" && !suppressToolRequestHandling) {
labels.push("tool-request");
}
async function ensureLabel(name, color, description) {
try {
await github.rest.issues.getLabel({ owner, repo, name });
} catch (error) {
if (error.status !== 404) throw error;
await github.rest.issues.createLabel({ owner, repo, name, color, description });
}
}
if (isSupport) {
await ensureLabel("support", "0e8a16", "Needs support team follow-up");
for (const staleLabel of ["bug", "ts"]) {
try {
await github.rest.issues.removeLabel({ owner, repo, issue_number, name: staleLabel });
} catch (error) {
if (error.status !== 404) throw error;
}
}
try {
await github.rest.issues.removeAssignees({
owner,
repo,
issue_number,
assignees: ["jkomyno"],
});
} catch (error) {
if (error.status !== 404) throw error;
}
}
if (labels.length > 0) {
await github.rest.issues.addLabels({
owner,
repo,
issue_number,
labels,
});
}
if (assignees.length > 0) {
await github.rest.issues.addAssignees({
owner,
repo,
issue_number,
assignees,
});
}
if (
!isSupport &&
(category !== "tool-request" || suppressToolRequestHandling) &&
shouldComment &&
commentBody.trim().length > 0
) {
await github.rest.issues.createComment({
owner,
repo,
issue_number,
body: commentBody.trim(),
});
}
async function plainGraphql(query, variables) {
const apiKey = process.env.PLAIN_API_KEY;
if (!apiKey) {
core.warning("PLAIN_API_KEY is not configured; skipping Plain support forwarding.");
return null;
}
const response = await fetch("https://core-api.uk.plain.com/graphql/v1", {
method: "POST",
headers: {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ query, variables }),
});
const json = await response.json().catch(() => ({}));
if (!response.ok || json.errors) {
core.warning(`Plain API request failed: ${JSON.stringify(json)}`);
return null;
}
return json.data;
}
async function getIssueAuthorPlainCustomer() {
const login = issue.user?.login || "unknown";
let publicEmail = issue.user?.email || null;
let displayName = issue.user?.name || login;
if (login !== "unknown") {
try {
const { data: user } = await github.rest.users.getByUsername({ username: login });
publicEmail = user.email || publicEmail;
displayName = user.name || displayName;
} catch (error) {
core.warning(`Could not fetch GitHub user profile for ${login}: ${error.message}`);
}
}
return {
email: publicEmail || (issue.user?.id ? `${issue.user.id}+${login}@users.noreply.github.com` : `${login}@users.noreply.github.com`),
fullName: displayName,
shortName: login,
externalId: `github:user:${login}`,
};
}
async function forwardSupportIssueToPlain() {
const plainCustomer = await getIssueAuthorPlainCustomer();
const customerData = await plainGraphql(
`mutation upsertCustomer($input: UpsertCustomerInput!) {
upsertCustomer(input: $input) {
result
customer { id }
error { message type code fields { field message type } }
}
}`,
{
input: {
identifier: { emailAddress: plainCustomer.email },
onCreate: {
fullName: plainCustomer.fullName,
shortName: plainCustomer.shortName,
email: { email: plainCustomer.email, isVerified: Boolean(issue.user?.email) },
externalId: plainCustomer.externalId,
},
onUpdate: {
fullName: { value: plainCustomer.fullName },
shortName: { value: plainCustomer.shortName },
externalId: { value: plainCustomer.externalId },
},
},
}
);
const customer = customerData?.upsertCustomer?.customer;
const customerError = customerData?.upsertCustomer?.error;
if (!customer?.id) {
core.warning(`Could not upsert Plain support customer: ${JSON.stringify(customerError)}`);
return null;
}
const body = issue.body || "_No issue body provided._";
const plainText = [
`GitHub issue routed as support: ${issue.html_url}`,
`Reporter: @${issue.user?.login || "unknown"}`,
`Classification reason: ${reason || "No reason provided."}`,
"",
"Issue body:",
body.length > 8000 ? `${body.slice(0, 8000)}\n\n[truncated]` : body,
].join("\n");
const threadData = await plainGraphql(
`mutation createThread($input: CreateThreadInput!) {
createThread(input: $input) {
thread { id ref }
error { message type code fields { field message type } }
}
}`,
{
input: {
title: `[GitHub #${issue.number}] ${issue.title}`,
customerIdentifier: { customerId: customer.id },
externalId: `github:${owner}/${repo}:issues/${issue.number}`,
components: [{ componentText: { text: plainText } }],
},
}
);
const thread = threadData?.createThread?.thread;
const threadError = threadData?.createThread?.error;
if (!thread?.id) {
core.warning(`Could not create Plain support thread: ${JSON.stringify(threadError)}`);
return null;
}
core.info(`Forwarded support issue #${issue.number} to Plain thread ${thread.ref || thread.id}`);
return thread;
}
if (isSupport) {
const thread = await forwardSupportIssueToPlain();
await github.rest.issues.createComment({
owner,
repo,
issue_number,
body: thread
? `🤖 Automated Claude triage note: this does not look like an SDK bug, so I've labeled it as \`support\` and forwarded it to Plain for the support team. Claude cannot reply back in a conversation.`
: `🤖 Automated Claude triage note: this does not look like an SDK bug, so I've labeled it as \`support\` for support team follow-up. Claude cannot reply back in a conversation.`,
});
}
if (category === "tool-request" && !suppressToolRequestHandling && issueState === "open") {
await github.rest.issues.createComment({
owner,
repo,
issue_number,
body: [
"🤖 beep boop — hey, I think this is a tool request! Please file it at https://request.composio.dev/boards/tool-requests so the team can track and prioritize it.",
"",
"I've tagged this as `tool-request` and closed it here.",
"",
"If you think I got this wrong, feel free to reopen the issue.",
].join("\n"),
});
await github.rest.issues.update({
owner,
repo,
issue_number,
state: "closed",
});
}
+85
View File
@@ -0,0 +1,85 @@
name: Checks
on:
push:
branches:
- master
paths:
- 'python/**/*.py'
- '.github/actions/setup-python-uv/action.yml'
- '.github/workflows/py.check.yaml'
- 'mise.toml'
- 'mise.lock'
pull_request:
types: [opened, synchronize]
paths:
- 'python/**/*.py'
- '.github/actions/setup-python-uv/action.yml'
- '.github/workflows/py.check.yaml'
- 'mise.toml'
- 'mise.lock'
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
permissions:
contents: read
jobs:
common-checks:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 50
- name: Setup Python with UV
id: setup-python
uses: ./.github/actions/setup-python-uv
- name: nox cache
id: nox-cache
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: python/.nox
key: nox-${{ hashFiles('pyproject.toml') }}-py${{ steps.setup-python.outputs.python-version }}
restore-keys: |
nox-
- name: Mypy cache
id: mypy-cache
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: python/.mypy_cache
key: mypy-${{ hashFiles('pyproject.toml') }}-py${{ steps.setup-python.outputs.python-version }}
restore-keys: |
mypy-
- name: Ruff cache
id: ruff-cache
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: python/.ruff_cache
key: ruff-${{ hashFiles('pyproject.toml') }}-py${{ steps.setup-python.outputs.python-version }}
restore-keys: |
ruff-
- name: Install deps
run: |
cd python/
make env
- name: Lint and type checks
shell: bash
run: |
cd python/
uv run nox -s chk
- name: Type inference tests
shell: bash
run: |
cd python/
uv run nox -s type_inference
env:
COMPOSIO_API_KEY: ${{ secrets.COMPOSIO_API_KEY }}
+43
View File
@@ -0,0 +1,43 @@
name: Release Python Packages
on:
push:
tags:
- 'py@*'
workflow_dispatch:
inputs:
skip_tests:
description: 'Skip running tests'
required: false
type: boolean
default: false
permissions:
contents: read
jobs:
publish-core:
name: Build and release SDK
defaults:
run:
working-directory: ./python
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup Python with UV
uses: ./.github/actions/setup-python-uv
- name: Build Artifacts
run: |
make env
make build
- name: Publish Artifacts
if: github.event_name == 'push'
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
with:
packages-dir: ./python/dist
user: ${{ secrets.PYPI_USERNAME }}
password: ${{ secrets.PYPI_PASSWORD }}
skip-existing: true
+265
View File
@@ -0,0 +1,265 @@
name: Test Python SDK
on:
push:
branches: [master, next]
paths:
- 'python/**/*.py'
- '.github/workflows/py.test.yml'
- '.github/actions/setup-python-uv/action.yml'
- 'mise.toml'
- 'mise.lock'
- 'toolchain-versions.json'
- 'python/pyproject.toml'
- 'python/requirements*.txt'
pull_request:
branches: [master, next]
paths:
- 'python/**/*.py'
- '.github/workflows/py.test.yml'
- '.github/actions/setup-python-uv/action.yml'
- 'mise.toml'
- 'mise.lock'
- 'toolchain-versions.json'
- 'python/pyproject.toml'
- 'python/requirements*.txt'
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
permissions:
contents: read
jobs:
toolchain-versions:
name: Read toolchain test matrix
runs-on: ubuntu-latest
outputs:
python: ${{ steps.versions.outputs.python }}
steps:
- name: Checkout Code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Read versions
id: versions
run: echo "python=$(jq -c '.python' toolchain-versions.json)" >> "$GITHUB_OUTPUT"
test:
name: Test Python SDK
needs: toolchain-versions
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ${{ fromJSON(needs.toolchain-versions.outputs.python) }}
steps:
- name: Checkout Code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version: ${{ matrix.python-version }}
- name: Cache uv dependencies
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/uv
key: uv-${{ matrix.python-version }}-${{ hashFiles('python/pyproject.toml', 'mise.toml') }}
restore-keys: |
uv-${{ matrix.python-version }}-
- name: Install UV
run: |
UV_VERSION="$(sed -n 's/^uv[[:space:]]*=[[:space:]]*"\([^"]*\)"/\1/p' mise.toml)"
pip install "uv==${UV_VERSION}"
- name: Install Dependencies
run: |
cd python/
uv venv --python ${{ matrix.python-version }}
source .venv/bin/activate
uv pip install -e .
uv pip install -e providers/langchain
uv pip install -e providers/autogen
uv pip install pytest pytest-mock
- name: Run Import Tests
run: |
cd python/
source .venv/bin/activate
python -c "from composio import Composio; print('✓ Basic import successful')"
# Guards against the autogen provider becoming un-importable on a fresh
# install (e.g. a broken framework dependency). A hard import here fails
# loudly, unlike the importorskip-guarded unit tests, which would skip.
python -c "import composio_autogen; print('✓ Autogen provider import successful')"
- name: Run Unit Tests
run: |
cd python/
source .venv/bin/activate
python -m pytest tests/ -v --tb=short
- name: Test Package Installation
run: |
cd python/
# Test that the package can be installed and imported in a fresh environment
uv venv test-env --python ${{ matrix.python-version }}
source test-env/bin/activate
uv pip install -e .
python -c "
from composio import Composio, ToolkitVersionParam
from composio.types import Modifiers
from composio.core.types import ToolkitVersion
print('✓ All critical imports successful')
"
test-circular-imports:
name: Test Circular Import Prevention
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup Python with UV
uses: ./.github/actions/setup-python-uv
- name: Install Dependencies
run: |
cd python/
uv venv
source .venv/bin/activate
uv pip install -e .
- name: Test Circular Import Prevention
run: |
cd python/
source .venv/bin/activate
python -c "
# Test various import combinations that previously caused circular imports
import sys
# Test 1: Basic imports
from composio import Composio
print('✓ Basic Composio import')
# Test 2: Import toolkit version types
from composio import ToolkitVersionParam
from composio.core.types import ToolkitVersion
print('✓ Toolkit version types import')
# Test 3: Import from types module
from composio.types import Modifiers
print('✓ Types module import')
# Test 4: Import core models
from composio.core.models.tools import Tools
print('✓ Core models import')
# Test 5: Cross-module imports that caused the original circular import
from composio.core.models.tools import Modifiers as CoreModifiers
from composio.types import Modifiers as TypesModifiers
assert CoreModifiers is TypesModifiers
print('✓ Cross-module import consistency')
print('✓ All circular import prevention tests passed!')
"
lint-and-format:
name: Lint and Format Check
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup Python with UV
uses: ./.github/actions/setup-python-uv
- name: Install Dependencies
run: |
cd python/
uv venv
source .venv/bin/activate
uv pip install -e .
uv pip install ruff
- name: Run Ruff Linter
run: |
cd python/
source .venv/bin/activate
ruff check --config config/ruff.toml composio/ tests/
- name: Run Ruff Formatter Check
run: |
cd python/
source .venv/bin/activate
ruff format --check --config config/ruff.toml composio/ tests/
integration-tests:
name: Integration Tests
runs-on: ubuntu-22.04 # Use Ubuntu 22.04 for stability
permissions: read-all
env:
COMPOSIO_API_KEY: ${{ secrets.COMPOSIO_API_KEY }}
COMPOSIO_BASE_URL: ${{ secrets.COMPOSIO_BASE_URL_STAGING }}
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Install UV
id: setup-python
uses: ./.github/actions/setup-python-uv
- name: Cache UV dependencies
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/uv
key: uv-${{ hashFiles('mise.lock') }}-py${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('python/pyproject.toml') }}
restore-keys: |
uv-${{ hashFiles('mise.lock') }}-py${{ steps.setup-python.outputs.python-version }}-
- name: Install Python Dependencies
working-directory: ./python
run: |
# Let UV manage Python version to satisfy workspace requirements
uv sync --group dev
- name: Check API key
id: api-key
run: |
if [ -z "$COMPOSIO_API_KEY" ]; then
echo "available=false" >> "$GITHUB_OUTPUT"
if [ "$GITHUB_ACTOR" = "dependabot[bot]" ]; then
echo "::notice::Skipping secret-backed Python integration tests for Dependabot; missing COMPOSIO_API_KEY."
exit 0
fi
echo "::error::COMPOSIO_API_KEY is not set"
exit 1
fi
echo "available=true" >> "$GITHUB_OUTPUT"
echo "✅ API key is configured"
- name: Run Integration Tests
if: steps.api-key.outputs.available == 'true'
working-directory: ./python
run: |
echo "🧪 Running Integration Tests..."
# Run with per-test timeout of 2 minutes and overall timeout
# pytest-timeout should be installed via pyproject.toml dev dependencies
uv run pytest composio/integration_test/ -v --tb=short --maxfail=3 --timeout=120 --timeout-method=thread
timeout-minutes: 10
- name: Upload Test Results
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: integration-test-results
path: |
python/composio/integration_test/
retention-days: 7
@@ -0,0 +1,25 @@
name: Secrets Detection
on:
pull_request:
types: [opened, synchronize, reopened]
branches:
- master
- next
permissions:
contents: read
jobs:
secrets:
# The called reusable workflow declares these scopes; a narrower caller
# grant clamps its token and the secret-scanning check 403s into a warning.
permissions:
contents: read
pull-requests: write
issues: write
security-events: write
uses: ComposioHQ/.github/.github/workflows/secrets-detection.yml@a6c053d4cb1820a7a85bc7837efe47cc0a559e26 # master (#12: SHA-pinned actions)
secrets: inherit
with:
slack_channel: "buzz-security"
+46
View File
@@ -0,0 +1,46 @@
name: Close Stale Issues and PRs
on:
schedule:
# Run daily at 00:00 UTC
- cron: "0 0 * * *"
workflow_dispatch:
permissions:
issues: write
pull-requests: write
jobs:
stale:
runs-on: ubuntu-latest
steps:
- name: Close Stale Issues
uses: actions/stale@1e223db275d687790206a7acac4d1a11bd6fe629 # v10.4.0
with:
# General settings
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 60
days-before-close: 2
ascending: true
# Issue settings
stale-issue-label: "stale"
exempt-issue-labels: "pinned,security,bug,needs-triage,roadmap,enhancement,good first issue"
stale-issue-message: |
beep boop inactive for 60 days, closing in 48 hours
close-issue-message: |
closed due to inactivity. feel free to reopen if the issue persists.
# Pull request settings
stale-pr-label: "stale"
exempt-pr-labels: "pinned,security,needs-review,work-in-progress,dependencies"
stale-pr-message: |
beep boop inactive for 60 days, closing in 48 hours
close-pr-message: |
closed due to inactivity. feel free to reopen if still relevant.
# Optional: Remove stale label when updated
remove-stale-when-updated: true
# Enable debug logs (set to false in production)
debug-only: false
+92
View File
@@ -0,0 +1,92 @@
name: Audit Typescript SDK
on:
push:
branches: [master, next]
paths:
- 'ts/**'
- '.github/actions/setup-node-pnpm-bun/action.yml'
- '.github/workflows/ts.audit.yml'
- 'mise.toml'
- 'mise.lock'
- 'package.json'
- 'pnpm-lock.yaml'
- 'pnpm-workspace.yaml'
pull_request:
branches: [master, next]
paths:
- 'ts/**'
- '.github/actions/setup-node-pnpm-bun/action.yml'
- '.github/workflows/ts.audit.yml'
- 'mise.toml'
- 'mise.lock'
- 'package.json'
- 'pnpm-lock.yaml'
- 'pnpm-workspace.yaml'
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
permissions:
contents: read
jobs:
audit:
name: Audit
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- name: Checkout Code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup Node.js, pnpm, Bun
uses: ./.github/actions/setup-node-pnpm-bun
with:
enable-caching: 'false'
- name: Verify mise.lock is fresh
run: |
mise lock --platform linux-x64,linux-arm64,macos-arm64,macos-x64
git diff --exit-code mise.lock
- name: Run pnpm audit (production dependencies only)
id: audit
continue-on-error: true
run: pnpm audit --prod > audit-output.txt 2>&1
- name: Comment on PR if audit failed
if: steps.audit.outcome == 'failure' && github.event_name == 'pull_request'
run: |
{
echo "⚠️ **Security Audit Warning**"
echo ""
echo "The \`pnpm audit --prod\` check found security vulnerabilities in production dependencies."
echo ""
echo "Please review and fix the vulnerabilities. You can try running:"
echo "\`\`\`bash"
echo "pnpm audit --fix --prod"
echo "\`\`\`"
echo ""
echo "<details>"
echo "<summary>Audit output</summary>"
echo ""
echo "\`\`\`"
cat audit-output.txt
echo "\`\`\`"
echo ""
echo "</details>"
} > audit-comment.txt
- name: Post audit comment to PR
if: steps.audit.outcome == 'failure' && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
uses: thollander/actions-comment-pull-request@24bffb9b452ba05a4f3f77933840a6a841d1b32b # v3.0.1
with:
file-path: audit-comment.txt
comment-tag: pnpm-audit-security-warning
- name: Fail on high/critical advisories
run: pnpm audit --prod --audit-level=high
+62
View File
@@ -0,0 +1,62 @@
name: Build Typescript SDK
on:
push:
branches: [master]
paths:
- 'ts/**'
- '.github/actions/setup-node-pnpm-bun/action.yml'
- '.github/workflows/ts.build.yml'
- 'mise.toml'
- 'mise.lock'
- 'turbo.jsonc'
- 'package.json'
- 'pnpm-lock.yaml'
- 'pnpm-workspace.yaml'
- 'tsdown.config.base.ts'
pull_request:
paths:
- 'ts/**'
- '.github/actions/setup-node-pnpm-bun/action.yml'
- '.github/workflows/ts.build.yml'
- 'mise.toml'
- 'mise.lock'
- 'turbo.jsonc'
- 'package.json'
- 'pnpm-lock.yaml'
- 'pnpm-workspace.yaml'
- 'tsdown.config.base.ts'
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
permissions:
contents: read
# Print Turborepo telemetry events to the logs instead of sending them.
env:
TURBO_TELEMETRY_DEBUG: 1
jobs:
build:
name: Build Typescript SDK
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup Node.js, pnpm, Bun
uses: ./.github/actions/setup-node-pnpm-bun
with:
enable-turbo-cache: 'true'
- name: Install Dependencies
run: pnpm install --frozen-lockfile
- name: Run Linting
run: pnpm lint
- name: Run Build
run: pnpm run build:packages
+70
View File
@@ -0,0 +1,70 @@
name: Examples Nightly (staging)
# Executes the provider examples against the STAGING backend with real
# credentials. Non-blocking by design: it runs on a schedule (and on demand),
# never as a required PR check, so staging/LLM flakiness cannot gate merges.
on:
schedule:
- cron: '0 6 * * *' # 06:00 UTC nightly
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false
permissions:
contents: read
env:
# Staging is not a secret; the API keys are.
COMPOSIO_BASE_URL: https://staging-backend.composio.dev
COMPOSIO_API_KEY: ${{ secrets.COMPOSIO_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
jobs:
examples-staging:
name: ${{ matrix.provider }} examples against staging
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
provider: [mastra, openai]
steps:
- name: Checkout Code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup Node.js, pnpm, Bun
uses: ./.github/actions/setup-node-pnpm-bun
with:
enable-turbo-cache: 'true'
- name: Install Dependencies
run: pnpm install --frozen-lockfile
- name: Build SDK packages
run: pnpm run build:packages
# Both secrets must be present for scheduled and manually dispatched
# validation. Otherwise smoke can self-skip and the LLM steps can no-op,
# leaving the whole regression net green while testing nothing.
- name: Require staging credentials
if: ${{ env.COMPOSIO_API_KEY == '' || env.OPENAI_API_KEY == '' }}
run: |
echo "::error::COMPOSIO_API_KEY and OPENAI_API_KEY are required for examples validation."
exit 1
# Deterministic regression net — needs only COMPOSIO_API_KEY, no LLM.
# Self-skips when the secret is absent (e.g. fork-triggered dispatch).
- name: ${{ matrix.provider }} smoke (wrapping + tool-router)
run: pnpm --filter ${{ matrix.provider }}-example --fail-if-no-match run smoke
# Full agent run — only when an LLM key is configured.
- name: ${{ matrix.provider }} direct-tools example
if: ${{ env.OPENAI_API_KEY != '' }}
run: bun ts/examples/${{ matrix.provider }}/src/index.ts
- name: ${{ matrix.provider }} tool-router example
if: ${{ env.OPENAI_API_KEY != '' }}
run: bun ts/examples/${{ matrix.provider }}/src/tool-router.ts
+69
View File
@@ -0,0 +1,69 @@
name: Examples TypeScript SDK
on:
push:
branches: [master, next]
paths:
- 'ts/examples/**'
- 'ts/packages/core/**'
- 'ts/packages/providers/**'
- 'eslint.config.mjs'
- 'package.json'
- '.github/actions/setup-node-pnpm-bun/action.yml'
- '.github/workflows/ts.examples.yml'
- 'mise.toml'
- 'mise.lock'
- 'turbo.jsonc'
pull_request:
branches: [master, next]
paths:
- 'ts/examples/**'
- 'ts/packages/core/**'
- 'ts/packages/providers/**'
- 'eslint.config.mjs'
- 'package.json'
- '.github/actions/setup-node-pnpm-bun/action.yml'
- '.github/workflows/ts.examples.yml'
- 'mise.toml'
- 'mise.lock'
- 'turbo.jsonc'
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
permissions:
contents: read
jobs:
examples:
name: Examples TypeScript SDK
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup Node.js, pnpm, Bun
uses: ./.github/actions/setup-node-pnpm-bun
with:
enable-turbo-cache: 'true'
- name: Install Dependencies
run: pnpm install --frozen-lockfile
# Examples depend on the built @composio/* workspace packages for type
# resolution and the Workers bundle. turbo typecheck also builds these via
# ^build, but building explicitly keeps the wrangler step self-contained.
- name: Build SDK packages
run: pnpm run build:packages
- name: Typecheck examples
run: pnpm run typecheck:examples
- name: Lint examples
run: pnpm run lint:examples
# Prove the Cloudflare Workers entry bundles without secrets or deploying.
- name: Cloudflare Workers dry-run build
run: pnpm exec turbo cf:dry-run --filter='./ts/examples/*'
+115
View File
@@ -0,0 +1,115 @@
name: TS SDK Release
on:
workflow_dispatch:
push:
branches: [next]
permissions:
contents: read
# Print Turborepo telemetry events to the logs instead of sending them.
env:
TURBO_TELEMETRY_DEBUG: 1
jobs:
release:
name: Release Typescript SDK
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
# Mint a short-lived GitHub App installation token instead of a long-lived
# PAT. App tokens don't expire out from under us (the old CI_BOT_TOKEN PAT
# did), and — unlike the default GITHUB_TOKEN — events they create (the
# "Release: update version" PR and its eventual merge) still trigger
# downstream workflows like build-cli-binaries.yml, so publishing fires.
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
client-id: ${{ vars.RELEASE_BOT_CLIENT_ID }}
private-key: ${{ secrets.RELEASE_BOT_APP_PRIVATE_KEY }}
- name: Checkout Code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0 # Required for git show HEAD^ when comparing published package versions
token: ${{ steps.app-token.outputs.token }}
- name: Setup Node.js, pnpm, Bun
uses: ./.github/actions/setup-node-pnpm-bun
with:
# Pin Node.js 24 for the release job only: the npm it bundles supports
# OIDC trusted publishing, which the toolchain's Node 22 (mise.toml)
# does not. Intentionally a release-only override, not centralized.
node-version: '24.17.0'
- name: Install Dependencies
run: pnpm install --frozen-lockfile
- name: Run Linting
run: pnpm lint
- name: Run Build
run: pnpm run build:packages
- name: Create Release Pull Request & Publish packages
id: changesets
uses: changesets/action@a45c4d594aa4e2c509dc14a9f2b3b67ba3780d0d # v1.9.0
if: github.event_name != 'workflow_dispatch' # only run on master merges
env:
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
GITHUB_ACCESS_TOKEN: ${{ steps.app-token.outputs.token }}
with:
publish: pnpm changeset:release
commit: 'Release: update version'
title: 'Release: update version'
- name: Set Version Info
if: steps.changesets.outputs.published == 'true'
env:
PUBLISHED_PACKAGES: ${{ steps.changesets.outputs.publishedPackages }}
run: |
SLACK_VERSIONS=""
while read -r pkg; do
NAME=$(jq -r '.name // empty' <<<"$pkg" 2>/dev/null || echo "")
NEW_VERSION=$(jq -r '.version // empty' <<<"$pkg" 2>/dev/null || echo "")
if [ -z "$NAME" ] || [ -z "$NEW_VERSION" ]; then
continue
fi
VERSION_LINE="⚡️ $NAME: \`$NEW_VERSION\`"
PKG_PATH=$(pnpm list --filter "$NAME" --json 2>/dev/null | jq -r '.[0].path // empty' 2>/dev/null || echo "")
if [ -n "$PKG_PATH" ]; then
REL_PKG_JSON_PATH="${PKG_PATH#$GITHUB_WORKSPACE/}/package.json"
OLD_VERSION=$(git show "HEAD^:$REL_PKG_JSON_PATH" 2>/dev/null | jq -r '.version // empty' 2>/dev/null || echo "")
if [ -n "$OLD_VERSION" ] && [ "$OLD_VERSION" != "$NEW_VERSION" ]; then
VERSION_LINE="⚡️ $NAME: \`$OLD_VERSION\` -> \`$NEW_VERSION\`"
fi
fi
if [ -n "$SLACK_VERSIONS" ]; then
SLACK_VERSIONS="${SLACK_VERSIONS}"$'\n'"${VERSION_LINE}"
else
SLACK_VERSIONS="$VERSION_LINE"
fi
done < <(jq -c 'if type == "array" then .[] else empty end' <<<"$PUBLISHED_PACKAGES" 2>/dev/null || true)
if [ -z "$SLACK_VERSIONS" ]; then
SLACK_VERSIONS="No version info available"
fi
# Build a single-line JSON payload so multiline version info is safely escaped.
SLACK_PAYLOAD=$(jq -nc \
--arg repository "$GITHUB_REPOSITORY" \
--arg versions "$SLACK_VERSIONS" \
--arg sha "$GITHUB_SHA" \
'{text: "🚀 New SDK version published!\n*Repository:* \($repository)\n*Versions:*\n\($versions)\n*Commit:* \($sha)"}')
echo "SLACK_PAYLOAD=$SLACK_PAYLOAD" >> "$GITHUB_ENV"
- name: Send Slack Notification
if: steps.changesets.outputs.published == 'true'
uses: slackapi/slack-github-action@45a88b9581bfab2566dc881e2cd66d334e621e2c # v3.0.3
with:
webhook: ${{ secrets.SLACK_RELEASE_WEBHOOK_URL }}
webhook-type: incoming-webhook
payload: ${{ env.SLACK_PAYLOAD }}
+364
View File
@@ -0,0 +1,364 @@
name: E2E Test Typescript SDK
on:
push:
branches: [master, next]
paths:
- 'ts/**'
- '.github/actions/setup-node-pnpm-bun/action.yml'
- '.github/workflows/ts.test-e2e.yml'
- 'mise.toml'
- 'mise.lock'
- 'toolchain-versions.json'
pull_request:
paths:
- 'ts/**'
- '.github/actions/setup-node-pnpm-bun/action.yml'
- '.github/workflows/ts.test-e2e.yml'
- 'mise.toml'
- 'mise.lock'
- 'toolchain-versions.json'
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
permissions:
contents: read
# Print Turborepo telemetry events to the logs instead of sending them.
env:
TURBO_TELEMETRY_DEBUG: 1
jobs:
toolchain-versions:
name: Read toolchain test matrix
runs-on: ubuntu-24.04
permissions: read-all
outputs:
node: ${{ steps.versions.outputs.node }}
deno: ${{ steps.versions.outputs.deno }}
steps:
- name: Checkout Code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Read versions
id: versions
run: |
echo "node=$(jq -c '.node' toolchain-versions.json)" >> "$GITHUB_OUTPUT"
echo "deno=$(jq -c '.deno' toolchain-versions.json)" >> "$GITHUB_OUTPUT"
# Node.js E2E tests, running in Docker to isolate environment
# Each test suite defines its own versions.node in e2e.test.ts
# Tests that don't support the matrix version are automatically skipped
test-e2e-node-docker:
name: E2E Test Node.js ${{ matrix.node-version }}
needs: toolchain-versions
runs-on: ubuntu-24.04
permissions: read-all
strategy:
fail-fast: false
matrix:
node-version: ${{ fromJSON(needs.toolchain-versions.outputs.node) }}
env:
CI: 1
COMPOSIO_API_KEY: ${{ secrets.COMPOSIO_API_KEY }}
COMPOSIO_BASE_URL: ${{ secrets.COMPOSIO_BASE_URL_STAGING }}
COMPOSIO_USER_API_KEY: ${{ secrets.COMPOSIO_USER_API_KEY }}
COMPOSIO_E2E_NODE_VERSION: ${{ matrix.node-version }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
steps:
- name: Checkout Code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Normalize Composio API keys
run: |
{
echo "COMPOSIO_USER_API_KEY=${COMPOSIO_USER_API_KEY}"
echo "COMPOSIO_API_KEY=${COMPOSIO_API_KEY}"
echo "COMPOSIO_BASE_URL=${COMPOSIO_BASE_URL}"
} >> "$GITHUB_ENV"
- name: Setup Node.js, pnpm, Bun
id: setup-toolchain
uses: ./.github/actions/setup-node-pnpm-bun
with:
enable-turbo-cache: 'true'
- name: Install Dependencies
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm run build:packages
- name: Check API keys
id: api-keys
env:
IS_FORK_PR: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork }}
run: |
missing=()
for name in COMPOSIO_API_KEY OPENAI_API_KEY ANTHROPIC_API_KEY; do
if [ -z "${!name}" ]; then
missing+=("$name")
fi
done
if [ "${#missing[@]}" -gt 0 ]; then
echo "available=false" >> "$GITHUB_OUTPUT"
if [ "$GITHUB_ACTOR" = "dependabot[bot]" ] || [ "$IS_FORK_PR" = "true" ]; then
echo "::notice::Skipping secret-backed E2E tests; missing ${missing[*]}."
exit 0
fi
printf '::error::%s is not set\n' "${missing[@]}"
exit 1
fi
echo "available=true" >> "$GITHUB_OUTPUT"
echo "✅ API keys are configured"
- name: Pre-build Docker image for e2e tests
if: steps.api-keys.outputs.available == 'true'
run: |
docker build -f ts/e2e-tests/_utils/Dockerfile.node \
--build-arg NODE_VERSION=${{ matrix.node-version }} \
--label composio.e2e=true \
--label composio.runtime=node \
--label "composio.node_version=${{ matrix.node-version }}" \
-t "composio-e2e-node:${{ matrix.node-version }}" \
.
- name: Run Node.js E2E Tests
if: steps.api-keys.outputs.available == 'true'
run: pnpm run test:e2e:node
# Deno E2E tests, running in Docker to isolate environment
# Each test suite defines its own versions.deno in e2e.test.ts
# Tests that don't support the matrix version are automatically skipped
test-e2e-deno-docker:
name: E2E Test Deno ${{ matrix.deno-version }}
needs: toolchain-versions
runs-on: ubuntu-24.04
permissions: read-all
strategy:
fail-fast: false
matrix:
deno-version: ${{ fromJSON(needs.toolchain-versions.outputs.deno) }}
env:
CI: 1
COMPOSIO_API_KEY: ${{ secrets.COMPOSIO_API_KEY }}
COMPOSIO_BASE_URL: ${{ secrets.COMPOSIO_BASE_URL_STAGING }}
COMPOSIO_USER_API_KEY: ${{ secrets.COMPOSIO_USER_API_KEY }}
COMPOSIO_E2E_DENO_VERSION: ${{ matrix.deno-version }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
steps:
- name: Checkout Code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Normalize Composio API keys
run: |
{
echo "COMPOSIO_USER_API_KEY=${COMPOSIO_USER_API_KEY}"
echo "COMPOSIO_API_KEY=${COMPOSIO_API_KEY}"
echo "COMPOSIO_BASE_URL=${COMPOSIO_BASE_URL}"
} >> "$GITHUB_ENV"
- name: Setup Deno
uses: denoland/setup-deno@22d081ff2d3a40755e97629de92e3bcbfa7cf2ed # v2.0.5
with:
deno-version: ${{ matrix.deno-version }}
- name: Setup Node.js, pnpm, Bun
id: setup-toolchain
uses: ./.github/actions/setup-node-pnpm-bun
with:
enable-turbo-cache: 'true'
- name: Install Dependencies
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm run build:packages
- name: Check API keys
id: api-keys
env:
IS_FORK_PR: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork }}
run: |
missing=()
for name in COMPOSIO_API_KEY OPENAI_API_KEY; do
if [ -z "${!name}" ]; then
missing+=("$name")
fi
done
if [ "${#missing[@]}" -gt 0 ]; then
echo "available=false" >> "$GITHUB_OUTPUT"
if [ "$GITHUB_ACTOR" = "dependabot[bot]" ] || [ "$IS_FORK_PR" = "true" ]; then
echo "::notice::Skipping secret-backed E2E tests; missing ${missing[*]}."
exit 0
fi
printf '::error::%s is not set\n' "${missing[@]}"
exit 1
fi
echo "available=true" >> "$GITHUB_OUTPUT"
echo "✅ API keys are configured"
- name: Run Deno E2E Tests
if: steps.api-keys.outputs.available == 'true'
run: pnpm run test:e2e:deno
# CLI E2E tests, running in scratch Docker to isolate environment
test-e2e-cli-docker:
name: E2E Test CLI (scratch)
runs-on: ubuntu-24.04
permissions: read-all
env:
CI: 1
COMPOSIO_USER_API_KEY: ${{ secrets.COMPOSIO_USER_API_KEY }}
COMPOSIO_API_KEY: ${{ secrets.COMPOSIO_API_KEY }}
COMPOSIO_BASE_URL: ${{ secrets.COMPOSIO_BASE_URL_STAGING }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
steps:
- name: Checkout Code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Normalize Composio API keys
run: |
{
echo "COMPOSIO_USER_API_KEY=${COMPOSIO_USER_API_KEY}"
echo "COMPOSIO_API_KEY=${COMPOSIO_API_KEY}"
echo "COMPOSIO_BASE_URL=${COMPOSIO_BASE_URL}"
} >> "$GITHUB_ENV"
- name: Setup Node.js, pnpm, Bun
id: setup-toolchain
uses: ./.github/actions/setup-node-pnpm-bun
with:
enable-turbo-cache: 'true'
- name: Install Dependencies
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm run build:packages
- name: Check API keys
id: api-keys
env:
IS_FORK_PR: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork }}
run: |
missing=()
for name in COMPOSIO_API_KEY OPENAI_API_KEY COMPOSIO_USER_API_KEY; do
if [ -z "${!name}" ]; then
missing+=("$name")
fi
done
if [ "${#missing[@]}" -gt 0 ]; then
echo "available=false" >> "$GITHUB_OUTPUT"
if [ "$GITHUB_ACTOR" = "dependabot[bot]" ] || [ "$IS_FORK_PR" = "true" ]; then
echo "::notice::Skipping secret-backed E2E tests; missing ${missing[*]}."
exit 0
fi
printf '::error::%s is not set\n' "${missing[@]}"
exit 1
fi
echo "available=true" >> "$GITHUB_OUTPUT"
echo "✅ API keys are configured"
- name: Pre-build Docker image for CLI e2e tests
run: |
CLI_VERSION=$(node -p "require('./ts/packages/cli/package.json').version")
echo "COMPOSIO_E2E_CLI_VERSION=${CLI_VERSION}" >> "$GITHUB_ENV"
docker build -f ts/e2e-tests/_utils/Dockerfile.cli \
--build-arg CLI_VERSION="${CLI_VERSION}" \
--build-arg NODE_VERSION="${{ steps.setup-toolchain.outputs.node-version }}" \
--label composio.e2e=true \
--label composio.runtime=cli \
--label "composio.cli_version=${CLI_VERSION}" \
-t "composio-e2e-cli:${CLI_VERSION}" \
.
- name: Run CLI E2E Tests
if: steps.api-keys.outputs.available == 'true'
run: pnpm run test:e2e:cli
# Cloudflare Workerd E2E tests
test-e2e-cloudflare:
name: E2E Test Cloudflare Workerd
runs-on: ubuntu-24.04
permissions: read-all
env:
COMPOSIO_API_KEY: ${{ secrets.COMPOSIO_API_KEY }}
COMPOSIO_BASE_URL: ${{ secrets.COMPOSIO_BASE_URL_STAGING }}
COMPOSIO_USER_API_KEY: ${{ secrets.COMPOSIO_USER_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
steps:
- name: Checkout Code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Normalize Composio API keys
run: |
{
echo "COMPOSIO_USER_API_KEY=${COMPOSIO_USER_API_KEY}"
echo "COMPOSIO_API_KEY=${COMPOSIO_API_KEY}"
echo "COMPOSIO_BASE_URL=${COMPOSIO_BASE_URL}"
} >> "$GITHUB_ENV"
- name: Setup Node.js, pnpm, Bun
uses: ./.github/actions/setup-node-pnpm-bun
with:
enable-turbo-cache: 'true'
node-version: '22.22.3'
- name: Install Dependencies
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm run build:packages
- name: Check API keys
id: api-keys
env:
IS_FORK_PR: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork }}
run: |
missing=()
for name in COMPOSIO_API_KEY OPENAI_API_KEY; do
if [ -z "${!name}" ]; then
missing+=("$name")
fi
done
if [ "${#missing[@]}" -gt 0 ]; then
echo "available=false" >> "$GITHUB_OUTPUT"
if [ "$GITHUB_ACTOR" = "dependabot[bot]" ] || [ "$IS_FORK_PR" = "true" ]; then
echo "::notice::Skipping secret-backed E2E tests; missing ${missing[*]}."
exit 0
fi
printf '::error::%s is not set\n' "${missing[@]}"
exit 1
fi
echo "available=true" >> "$GITHUB_OUTPUT"
echo "✅ API keys are configured"
- name: Run Cloudflare Workers E2E Tests
if: steps.api-keys.outputs.available == 'true'
run: pnpm run test:e2e:cloudflare
env:
COMPOSIO_USER_API_KEY: ${{ secrets.COMPOSIO_USER_API_KEY }}
COMPOSIO_API_KEY: ${{ secrets.COMPOSIO_API_KEY }}
COMPOSIO_BASE_URL: ${{ secrets.COMPOSIO_BASE_URL_STAGING }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
+51
View File
@@ -0,0 +1,51 @@
name: Test Typescript SDK
on:
push:
branches: [master, next]
paths:
- 'ts/**'
- '.github/actions/setup-node-pnpm-bun/action.yml'
- '.github/workflows/ts.test.yml'
- 'mise.toml'
- 'mise.lock'
- 'turbo.jsonc'
pull_request:
branches: [master, next]
paths:
- 'ts/**'
- '.github/actions/setup-node-pnpm-bun/action.yml'
- '.github/workflows/ts.test.yml'
- 'mise.toml'
- 'mise.lock'
- 'turbo.jsonc'
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
permissions:
contents: read
jobs:
test:
name: Test Typescript SDK
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup Node.js, pnpm, Bun
uses: ./.github/actions/setup-node-pnpm-bun
with:
enable-turbo-cache: 'true'
- name: Install Dependencies
run: pnpm install --frozen-lockfile
- name: Check Python helpers codegen is up to date
run: pnpm --filter @composio/experimental run check:python-helpers
- name: Run Tests
run: pnpm run test
+48
View File
@@ -0,0 +1,48 @@
name: Typecheck Typescript SDK
on:
push:
branches: [master, next]
paths:
- 'ts/**'
- '.github/actions/setup-node-pnpm-bun/action.yml'
- '.github/workflows/ts.typecheck.yml'
- 'mise.toml'
- 'mise.lock'
- 'turbo.jsonc'
pull_request:
branches: [master, next]
paths:
- 'ts/**'
- '.github/actions/setup-node-pnpm-bun/action.yml'
- '.github/workflows/ts.typecheck.yml'
- 'mise.toml'
- 'mise.lock'
- 'turbo.jsonc'
concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
permissions:
contents: read
jobs:
typecheck:
name: Typecheck Typescript SDK
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup Node.js, pnpm, Bun
uses: ./.github/actions/setup-node-pnpm-bun
with:
enable-turbo-cache: 'true'
- name: Install Dependencies
run: pnpm install --frozen-lockfile
- name: Run Typecheck
run: pnpm run typecheck