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
+4
View File
@@ -0,0 +1,4 @@
docs/ @shawnesquivel @Sushmithamallesh
ts/packages/cli/ @CryogenicPlanet
ts/ @jkomyno
python/ @jkomyno
+47
View File
@@ -0,0 +1,47 @@
# Code of Conduct
This project and community (including our Discord server) are dedicated to providing a welcoming, safe, and collaborative environment for everyone.
## Our Pledge
We, as maintainers and contributors, pledge to make participation in our community a harassmentfree experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socioeconomic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to a positive environment include:
- Being kind, respectful, and considerate
- Giving and gracefully accepting constructive feedback
- Focusing on what is best for the community
- Showing empathy toward other community members
Examples of unacceptable behavior include:
- Harassment, discrimination, or hate speech
- Public or private harassment, including unwelcome sexual attention
- Trolling, insulting/derogatory comments, and personal or political attacks
- Publishing others' private information ("doxxing") without explicit permission
- Advertising, spam, or offtopic selfpromotion in project channels
- Any behavior that would be illegal or violates platform Terms of Service
## DiscordSpecific Guidelines
Our Discord is an extension of this community:
- Use appropriate channels; keep topics ontopic for each channel
- No unsolicited DMs for support or promotion; ask before DMing
- Respect moderators and follow their directions; do not argue moderation in public channels
- Report suspicious links or abuse to moderators immediately
- Follow Discords Terms of Service and Community Guidelines
## Enforcement Responsibilities
Project maintainers and Discord moderators are responsible for clarifying and enforcing standards. They may take appropriate and fair corrective action in response to any behavior they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code applies within all community spaces—GitHub repositories, issues, pull requests, and our Discord—and when an individual is officially representing the community in public spaces.
## Reporting
If you experience or witness unacceptable behavior, report it to the maintainers at support@composio.dev or via a direct message to the Discord moderators. All goodfaith reports will be reviewed and investigated promptly and fairly.
## Enforcement Guidelines
- Correction: Private, written warning and guidance
- Warning: Official warning with consequences for continued behavior
- Temporary Ban: Temporary removal from community spaces
- Permanent Ban: Permanent removal from community spaces
## Attribution
This Code of Conduct is adapted from common opensource community standards, including the Contributor Covenant.
+136
View File
@@ -0,0 +1,136 @@
name: "🐞 Bug Report"
description: "Report a bug or unexpected behavior in the Composio TypeScript or Python SDK"
title: "[Bug]: <Short description>"
labels: ["bug", "needs-triage"]
body:
- type: markdown
attributes:
value: |
## 🐞 Bug Report
Thanks for taking the time to report a bug! Please provide as much detail as possible to help us investigate and fix it quickly.
- type: dropdown
id: sdk_language
attributes:
label: "SDK Language"
description: "Which Composio SDK are you using?"
options:
- TypeScript / Node.js SDK (`@composio/core`)
- Python SDK (`composio` package)
- Both
- Not sure
default: 0
validations:
required: true
- type: input
id: sdk_version
attributes:
label: "SDK Version"
description: "Version of the SDK (e.g., @composio/core@0.1.55 or composio==0.1.7)"
placeholder: "@composio/core@0.1.55 or composio==0.1.7"
validations:
required: true
- type: input
id: runtime_env
attributes:
label: "Runtime Environment"
description: "Specify Node.js or Python version, plus platform if relevant"
placeholder: |
Node.js v20.10.0 on macOS 14.2
or
Python 3.11 on Ubuntu 22.04
validations:
required: true
- type: dropdown
id: environment
attributes:
label: "Environment"
description: "Where did this issue occur?"
options:
- Local Development
- Production Deployment
- Cloudflare Worker
- Vercel / Serverless Function
- Other
default: 0
validations:
required: true
- type: textarea
id: description
attributes:
label: "Describe the Bug"
description: "What happened? What did you expect to happen instead?"
placeholder: |
When executing `composio.tools.execute('GITHUB_GET_REPO')`, it throws a 401 even with a valid token.
Expected: repository details to be returned.
validations:
required: true
- type: textarea
id: reproduction_steps
attributes:
label: "Steps to Reproduce"
description: "Please include exact steps or code to reproduce the issue"
placeholder: |
1. Initialize Composio
2. Authorize GitHub
3. Call `composio.tools.execute('GITHUB_GET_REPO', {...})`
4. Observe 401 error
validations:
required: true
- type: textarea
id: code_snippet
attributes:
label: "Minimal Reproducible Example"
description: "Paste code or commands to reproduce (use correct language block)"
placeholder: |
```typescript
import { Composio } from '@composio/core';
const composio = new Composio({ apiKey: process.env.COMPOSIO_API_KEY });
const result = await composio.tools.execute('GITHUB_GET_REPO', {
userId: 'default',
arguments: { owner: 'composio', repo: 'sdk' },
});
```
```python
from composio import Composio
composio = Composio(api_key="YOUR_API_KEY")
result = composio.tools.execute("GITHUB_GET_REPO", user_id="default", arguments={"owner": "composio", "repo": "sdk"})
```
render: shell
- type: textarea
id: error_logs
attributes:
label: "Error Output / Stack Trace"
description: "Paste the full error output or traceback"
render: shell
placeholder: |
Error: [ComposioError] Unauthorized request
File "sdk/tools.py", line 82, in execute
raise ExecutionFailed("Unauthorized request")
validations:
required: true
- type: checkboxes
id: reproducibility
attributes:
label: "Reproducibility"
description: "How consistently does the bug occur?"
options:
- label: "Always reproducible"
- label: "Intermittent / Sometimes"
- label: "Happened once, cant reproduce"
- type: textarea
id: additional_context
attributes:
label: "Additional Context or Screenshots"
description: "Add logs, screenshots, or related issues if available"
+101
View File
@@ -0,0 +1,101 @@
name: "🚀 Feature Request"
description: "Suggest a new feature or improvement for the Composio SDKs, Dashboard, or Integrations"
title: "[Feature]: <Short description>"
labels: ["feature-request", "enhancement"]
body:
- type: markdown
attributes:
value: |
## 🚀 Feature Request
Have an idea that can make Composio better? Please describe it below as clearly as possible.
The more context you give, the easier it is for us to prioritize and implement!
- type: dropdown
id: area
attributes:
label: "Area of Improvement"
description: "Which part of Composio does this request relate to?"
options:
- TypeScript / Node.js SDK
- Python SDK
- Dashboard (app.composio.dev)
- Integrations / Toolkits
- MCP Server / Agentic Providers
- Other
default: 0
validations:
required: true
- type: input
id: feature_title
attributes:
label: "Feature Name"
description: "Short descriptive name for the feature"
placeholder: "Add support for GitLab toolkit"
validations:
required: true
- type: textarea
id: feature_description
attributes:
label: "Describe the Feature"
description: "What would you like to see added or improved?"
placeholder: |
Id love to see a GitLab toolkit added to Composio SDK so I can list projects, issues, and merge requests directly from my AI agent.
validations:
required: true
- type: textarea
id: use_case
attributes:
label: "Use Case / Motivation"
description: "Explain why this feature is valuable. What problem does it solve?"
placeholder: |
- My AI agent needs to interact with GitLab repositories.
- I want to automate PR creation and review workflows.
validations:
required: true
- type: textarea
id: proposed_solution
attributes:
label: "Proposed Solution or API Design (Optional)"
description: "How would you like this to work? Feel free to propose SDK methods or UI ideas."
placeholder: |
Example (TypeScript):
```typescript
const gitlabTools = await composio.tools.get('default', { toolkits: ['gitlab'] });
await composio.tools.execute('GITLAB_CREATE_ISSUE', { userId, arguments: {...} });
```
- type: checkboxes
id: impact_scope
attributes:
label: "Who does this impact?"
options:
- label: "Developers using the SDK"
- label: "AI Agent frameworks (LangChain, OpenAI, etc.)"
- label: "Dashboard users"
- label: "End users of connected apps"
- type: textarea
id: related_issues
attributes:
label: "Related Issues / References"
description: "Link any related GitHub issues, docs, or external resources"
placeholder: "#42, https://developers.gitlab.com/api/reference"
- type: checkboxes
id: willingness
attributes:
label: "Would you like to contribute to this feature?"
options:
- label: "Yes, I can help implement it"
- label: "Maybe, I can help test or review"
- label: "No, just sharing the idea"
- type: textarea
id: additional_context
attributes:
label: "Additional Context"
description: "Any extra information, mockups, or screenshots"
+35
View File
@@ -0,0 +1,35 @@
name: "🔧 Tool Request"
description: "Request a new toolkit or tool integration"
title: "[Tool Request]: <Tool/Integration Name>"
labels: ["tool-request", "integrations"]
body:
- type: markdown
attributes:
value: |
## 🔧 Tool Request
Thank you for your interest in requesting a new tool or integration!
**Please submit your tool request on our dedicated request board:**
👉 **[https://request.composio.dev/boards/tool-requests](https://request.composio.dev/boards/tool-requests)**
This helps us:
- Track all tool requests in one place
- Allow the community to upvote popular requests
- Keep you updated on the status of your request
Before submitting, please check existing apps and pending requests to avoid duplicates.
---
_This GitHub issue template is for bug reports and feature requests only. Tool requests should be submitted through the request board above._
- type: checkboxes
id: confirmation
attributes:
label: "Confirmation"
options:
- label: "I understand that I need to submit tool requests on the request board"
required: true
+29
View File
@@ -0,0 +1,29 @@
## Summary
Explain the motivation and context for this change. Link to any related issues.
Fixes #
## Changes
-
-
## Type of change
- [ ] Bug fix
- [ ] New feature
- [ ] Refactor/Chore
- [ ] Documentation
- [ ] Breaking change
## How Has This Been Tested?
Describe the tests you ran and instructions so reviewers can reproduce. Include any relevant config/versions.
## Screenshots (if applicable)
## Checklist
- [ ] I have read the Code of Conduct and this PR adheres to it
- [ ] I ran linters/tests locally and they passed
- [ ] I updated documentation as needed
- [ ] I added tests or explain why not applicable
- [ ] I added a changeset if this change affects published packages
## Additional context
+130
View File
@@ -0,0 +1,130 @@
# Security Policy
## Reporting a Vulnerability
We take the security of Composio seriously. If you believe you have found a security vulnerability, please report it to us through GitHub Security Advisories or email us at **security@composio.dev**
### How to Report
**Please do not report security vulnerabilities through public GitHub issues.**
Instead, please report them using one of the following methods:
- **GitHub Security Advisory (Preferred)**: Report a vulnerability directly through GitHub by visiting: [https://github.com/composiohq/composio/security/advisories/new](https://github.com/composiohq/composio/security/advisories/new)
- **Email**: If you prefer not to use GitHub Security Advisories, you can email security concerns to **security@composio.dev**
### What to Include
Please include as much of the following information as possible:
- Type of vulnerability
- Full paths of source file(s) related to the vulnerability
- Location of the affected source code (tag/branch/commit or direct URL)
- Step-by-step instructions to reproduce the issue
- Proof-of-concept or exploit code (if possible)
- Impact of the vulnerability, including how an attacker might exploit it
### Response Timeline
- We will acknowledge your report within **48 hours**
- We will provide a more detailed response within **7 days** indicating the next steps
- We will keep you informed of the progress toward resolving the issue
- We may ask for additional information or guidance
## Ground Rules
### Do:
- Abide by these Program Terms
- Be patient and make a good faith effort to provide clarifications to any questions we may have about your submission
- Be respectful when interacting with our team, and our team will do the same
- Perform testing only using accounts that are your own personal/test accounts
- Exercise caution when testing to avoid negative impact to data or services
- Respect privacy and make a good faith effort not to change or destroy Composio or personal data
- Stop whenever you are unsure if your test case may cause, or have caused, destructive data or systems damage; report your initial finding(s) and request authorization to continue testing
### Do NOT:
- Leave any system in a more vulnerable state than you found it
- Use or interact with accounts you do not own
- Brute force credentials or guess credentials to gain access to systems or accounts
- Change passwords of any account that is not yours or that you do not have explicit permission to change
- Perform denial of service (DoS) attacks or related tests that would cause availability interruptions or degradation of our services
- Publicly disclose a vulnerability submission without our explicit review and consent
- Engage in any form of social engineering of Composio employees, customers, or partners
- Engage or target any specific Composio employees, customers, or partners during your testing
- Access, extract, or download personal or business information beyond that which is minimally necessary for your Proof-of-Concept purposes
- Do anything that would cause destruction of Composio data or systems
- Test against production systems without permission
- Access or modify user data
- Exploit vulnerabilities beyond what is needed for a proof of concept
## Out-of-Scope Vulnerabilities
Certain vulnerabilities are considered out-of-scope for the Bug Bounty Program. Those out-of-scope vulnerabilities include, but are not limited to:
- Vulnerabilities not involving product or coding flaws, but solely relying upon possession of stolen or compromised credentials
- Vulnerabilities dependent on Phishing in a DNS domain that is not in one of our primary service domains
- Most vulnerabilities that rely on a runtime context within a sandbox, lab, staging, testing or non-production environment
- Vulnerabilities involving stolen or compromised credentials
- Open redirect resulting in a low security impact
- Credential stuffing or physical access to a device
- Any vulnerabilities requiring significant and unlikely interaction by the victim
- Man-in-the-Middle attacks except in mobile applications
- Account enumeration with a pre-defined and known list of UUIDs
- Invite/Promo code enumeration
- Ability to send push notifications/SMS messages/emails without the ability to change content
- Information disclosures related to existence of accounts
- Reports stating that a particular software component is vulnerable without an accompanying proof-of-concept
- Vulnerabilities only affecting users using outdated, unpatched, or unsupported browsers or software
- Stack traces, path disclosure, and directory listings
- CSV injection vulnerabilities
- Best practices concerns without a demonstrable information assurance issue and proof-of-concept
- Ability to take over social media pages
- Negligible security severity
- Speculative reports about theoretical damage please always provide a proof-of-concept
- Self-XSS or similar vulnerabilities that cannot be used to exploit other users
- Vulnerabilities reported by automated scanning tools without additional analysis
- Distributed or denial of service attacks (DDoS/DoS) and/or reports on rate limiting issues
- Content injection or content spoofing issues
- Cross-site Request Forgery (CSRF) with minimal security implications
- Missing cookie flags on non-authentication cookies
- Submissions that require physical access to a victim's computer/device
- SSL/TLS protocol scan reports
- Banner grabbing issues
- Open ports or services without accompanying proof-of-concept
- Physical or social engineering attempts
- Exposed login panels without proof-of-concept
- Dangling IPs
- Subdomain takeovers without proof
- Reports on third-party products, services, or applications not owned by Composio
- Out-of-scope domains
- Leaked API Key or credentials having no impact on Composio assets
## CVEs and 0-Days
### CVE Age Requirement
We will only accept CVEs that meet all of the criteria below:
- Public for at least **60 days**
- Demonstrated impact on our environment (not theoretical)
- Reproducible with clear steps or proof-of-impact
**CVE reports that do not meet the 60-day requirement are out of scope.**
## Supported Versions
We release patches for security vulnerabilities. Please ensure you are using the latest version of Composio.
## Disclosure Policy
- We follow coordinated disclosure practices
- Security advisories will be published after a fix is available
- We appreciate responsible disclosure and will acknowledge reporters in the advisory (unless you prefer to remain anonymous)
---
**Thank you for helping keep Composio and our users safe!**
We follow responsible disclosure practices and work with researchers to ensure vulnerabilities are properly addressed before public disclosure.
+16
View File
@@ -0,0 +1,16 @@
# Support
Thanks for using Composio! If you need help, use the following channels:
- Documentation: https://docs.composio.dev
- Discord: https://discord.gg/composio (best for community help and quick questions)
- Email: support@composio.dev (account issues, private questions, or if you cant use Discord)
Please avoid filing support requests as GitHub issues unless you believe youve found a bug. For security issues, do not open a public issue—follow `.github/SECURITY.md`.
## Before asking for help
- Check open and closed issues
- Search the docs and Discord
- Include versions, logs, minimal repro steps, and expected vs. actual behavior
We do our best to respond within 23 business days.
@@ -0,0 +1,117 @@
name: 'Setup Node.js, pnpm, Bun via mise'
description: 'Sets up Node.js, pnpm, and Bun from mise.toml unless explicit matrix overrides are provided'
inputs:
node-version:
description: 'Node.js version to use. Defaults to mise.toml'
required: false
default: ''
bun-version:
description: 'Bun version to use. Defaults to mise.toml'
required: false
default: ''
enable-caching:
description: 'Enable pnpm/tool cache'
required: false
default: 'true'
enable-turbo-cache:
description: 'Cache .turbo build outputs'
required: false
default: 'false'
outputs:
node-version:
description: 'The installed Node.js version'
value: ${{ steps.versions.outputs.node }}
bun-version:
description: 'The installed Bun version'
value: ${{ steps.versions.outputs.bun }}
pnpm-version:
description: 'The installed pnpm version'
value: ${{ steps.versions.outputs.pnpm }}
runs:
using: 'composite'
steps:
- name: Setup mise
uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1
with:
version: 2026.5.18
install: false
cache: ${{ inputs.enable-caching }}
- name: Install mise-managed Node.js and Bun
shell: bash
env:
NODE_VERSION: ${{ inputs.node-version }}
BUN_VERSION: ${{ inputs.bun-version }}
run: |
tools=()
if [ -z "$NODE_VERSION" ]; then
tools+=(node)
fi
if [ -z "$BUN_VERSION" ]; then
tools+=(bun)
fi
if [ "${#tools[@]}" -gt 0 ]; then
mise install "${tools[@]}"
fi
- name: Setup Node.js override
if: inputs.node-version != ''
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: ${{ inputs.node-version }}
- name: Setup Bun override
if: inputs.bun-version != ''
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: ${{ inputs.bun-version }}
- name: Install mise-managed pnpm
shell: bash
run: mise install npm:pnpm
- name: Prefer mise-managed pnpm
shell: bash
run: |
# Node.js distributions can expose their own pnpm. Keep mise.toml's
# npm:pnpm pin ahead of the Node install, especially for Node overrides.
echo "$(mise where npm:pnpm)/bin" >> "$GITHUB_PATH"
- name: Resolve pnpm store path and Node version
if: inputs.enable-caching == 'true'
id: pnpm-store
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT"
echo "NODE_VERSION=$(node --version | sed 's/^v//')" >> "$GITHUB_OUTPUT"
- name: Cache pnpm store
if: inputs.enable-caching == 'true'
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: ${{ steps.pnpm-store.outputs.STORE_PATH }}
key: ${{ runner.os }}-node${{ steps.pnpm-store.outputs.NODE_VERSION }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-node${{ steps.pnpm-store.outputs.NODE_VERSION }}-pnpm-store-
- name: Cache turbo build outputs
if: inputs.enable-caching == 'true' && inputs.enable-turbo-cache == 'true'
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: .turbo/cache
# Keyed per job: concurrent jobs save different task graphs under the
# same commit, and actions/cache is first-writer-wins on exact keys.
key: ${{ runner.os }}-turbo-${{ github.job }}-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbo-${{ github.job }}-
- name: Report versions
id: versions
shell: bash
run: |
echo "node=$(node --version | sed 's/^v//')" >> "$GITHUB_OUTPUT"
echo "bun=$(bun --version)" >> "$GITHUB_OUTPUT"
echo "pnpm=$(pnpm --version)" >> "$GITHUB_OUTPUT"
@@ -0,0 +1,66 @@
name: 'Setup Python with uv via mise'
description: 'Sets up Python and uv from mise.toml unless explicit matrix overrides are provided'
inputs:
python-version:
description: 'Python version to use. Defaults to mise.toml'
required: false
default: ''
uv-version:
description: 'uv version to install. Defaults to mise.toml'
required: false
default: ''
enable-caching:
description: 'Enable uv/tool cache'
required: false
default: 'true'
outputs:
python-version:
description: 'The installed Python version (e.g., 3.12.0)'
value: ${{ steps.versions.outputs.python }}
runs:
using: 'composite'
steps:
- name: Setup mise
uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1
with:
version: 2026.5.18
install: false
cache: ${{ inputs.enable-caching }}
- name: Install mise-managed Python and uv
shell: bash
env:
PYTHON_VERSION: ${{ inputs.python-version }}
UV_VERSION: ${{ inputs.uv-version }}
run: |
tools=()
if [ -z "$PYTHON_VERSION" ]; then
tools+=(python)
fi
if [ -z "$UV_VERSION" ]; then
tools+=(uv)
fi
if [ "${#tools[@]}" -gt 0 ]; then
mise install "${tools[@]}"
fi
- name: Setup Python override
if: inputs.python-version != ''
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: ${{ inputs.python-version }}
- name: Setup uv override
if: inputs.uv-version != ''
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
with:
version: ${{ inputs.uv-version }}
enable-cache: ${{ inputs.enable-caching }}
- name: Report versions
id: versions
shell: bash
run: echo "python=$(python --version | awk '{print $2}')" >> "$GITHUB_OUTPUT"
+62
View File
@@ -0,0 +1,62 @@
# Dependabot configuration.
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
#
# Grouping keeps the update/security-alert volume manageable (one PR per group
# instead of one per advisory). Routine minor/patch bumps are batched; major
# bumps still arrive individually so they get review.
# Schedule: every Friday at 06:30 UTC / 12:00 IST.
version: 2
updates:
# TypeScript / npm workspace
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
day: "friday"
time: "06:30"
open-pull-requests-limit: 10
groups:
npm-production:
applies-to: version-updates
dependency-type: "production"
patterns: ["*"]
update-types: ["minor", "patch"]
npm-development:
applies-to: version-updates
dependency-type: "development"
patterns: ["*"]
update-types: ["minor", "patch"]
npm-security:
applies-to: security-updates
patterns: ["*"]
# Python SDK and provider packages
- package-ecosystem: "pip"
directories:
- "/python"
- "/python/providers/*"
schedule:
interval: "weekly"
day: "friday"
time: "06:30"
open-pull-requests-limit: 10
groups:
pip-version:
applies-to: version-updates
patterns: ["*"]
update-types: ["minor", "patch"]
pip-security:
applies-to: security-updates
patterns: ["*"]
# GitHub Actions workflow pins
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
day: "friday"
time: "06:30"
groups:
github-actions:
patterns: ["*"]
+111
View File
@@ -0,0 +1,111 @@
#!/usr/bin/env python3
"""Bump version, release-tag URLs, and per-platform sha256 in a Composio Homebrew formula.
Used by .github/workflows/cli.bump-homebrew-tap.yml. Reads inputs from env vars
so the bash side has a clean interface; can be run locally for testing:
TAG=@composio/cli@0.2.29 VERSION=0.2.29 \\
DARWIN_ARM=<sha> DARWIN_X86=<sha> LINUX_ARM=<sha> LINUX_X86=<sha> \\
.github/scripts/bump-homebrew-formula.py path/to/Formula/composio.rb
Exits non-zero if any required env var is missing/invalid or if no edits land.
"""
from __future__ import annotations
import os
import re
import sys
REQUIRED_ENV = ("TAG", "VERSION", "DARWIN_ARM", "DARWIN_X86", "LINUX_ARM", "LINUX_X86")
SHA256_RE = re.compile(r"[0-9a-f]{64}")
def main(argv: list[str]) -> int:
if len(argv) != 2:
print(f"usage: {argv[0]} <formula-path>", file=sys.stderr)
return 2
path = argv[1]
missing = [k for k in REQUIRED_ENV if not os.environ.get(k)]
if missing:
print(f"missing env vars: {', '.join(missing)}", file=sys.stderr)
return 2
tag = os.environ["TAG"]
version = os.environ["VERSION"]
shas = {
"darwin-aarch64": os.environ["DARWIN_ARM"],
"darwin-x64": os.environ["DARWIN_X86"],
"linux-aarch64": os.environ["LINUX_ARM"],
"linux-x64": os.environ["LINUX_X86"],
}
for plat, sha in shas.items():
if not SHA256_RE.fullmatch(sha):
print(f"invalid sha for {plat}: {sha!r}", file=sys.stderr)
return 2
with open(path) as f:
src = f.read()
# Bump version line. Lambda avoids replacement-string backreference parsing
# (e.g. a hostile tag containing \g<1>); same defense on tag/sha below.
src, n_ver = re.subn(
r'^(\s*version\s+)"[^"]+"',
lambda m: f'{m.group(1)}"{version}"',
src,
count=1,
flags=re.M,
)
# Bump the release-tag segment of each download URL.
src, n_url = re.subn(
r"(releases/download/)@composio/cli@[^/]+(/composio-)",
lambda m: f"{m.group(1)}{tag}{m.group(2)}",
src,
)
# Bump each sha256 by pairing it with the URL line directly above.
updated_platforms: set[str] = set()
def repl_sha(match: re.Match[str]) -> str:
url_line, sha_line = match.group(1), match.group(2)
for plat, sha in shas.items():
if f"composio-{plat}.zip" in url_line:
updated_platforms.add(plat)
return url_line + re.sub(
r'"[0-9a-f]{64}"',
lambda _m: f'"{sha}"',
sha_line,
)
return match.group(0)
src, n_sha = re.subn(
r'(url\s+"[^"]+"\s*\n)(\s*sha256\s+"[0-9a-f]{64}")',
repl_sha,
src,
)
expected_platforms = set(shas)
if (
n_ver != 1
or n_url != len(shas)
or n_sha != len(shas)
or updated_platforms != expected_platforms
):
print(
"bump produced unexpected edits "
f"(version={n_ver}, url={n_url}, sha={n_sha}, "
f"updated_platforms={sorted(updated_platforms)})",
file=sys.stderr,
)
return 1
with open(path, "w") as f:
f.write(src)
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv))
+135
View File
@@ -0,0 +1,135 @@
#!/usr/bin/env bash
# Check the validity of all actions used in the workflows.
#
# Example output:
#
.github/scripts/check-action-repos.sh
# Found 18 unique uses: entries
# WARN: unrecognized uses format: ---
# ERROR: ref not found (tag/branch/commit): peter-evans/create-pull-request@271a8d0340265f705b14b601f8cf3c8c27d2d6cf
# ERROR: ref not found (tag/branch/commit): slackapi/slack-github-action@b4590ed38561d3cb7512cbee19cef0e0a6064ff1
set -euo pipefail
fail=0
# Collect all `uses:` entries
mapfile -t USES < <(
yq -r '
.jobs[]?.steps[]? |
select(has("uses")) |
.uses
' .github/workflows/*.yml .github/workflows/*.yaml 2>/dev/null \
| sed '/^null$/d' \
| sort -u
)
if [[ ${#USES[@]} -eq 0 ]]; then
echo "No uses: entries found."
exit 0
fi
echo "Found ${#USES[@]} unique uses: entries"
check_local() {
local p="$1"
if [[ ! -e "$p" ]]; then
echo "ERROR: local action path does not exist: $p"
return 1
fi
if [[ -d "$p" ]]; then
if [[ -f "$p/action.yml" || -f "$p/action.yaml" ]]; then
return 0
fi
echo "ERROR: local action dir missing action.yml/action.yaml: $p"
return 1
fi
# If a file is used directly (rare), just ensure it exists
return 0
}
# For remote actions, verify:
# - repo exists
# - ref exists (tag/branch/sha)
# - action.yml or action.yaml exists at path (or root) at that ref
check_remote() {
local spec="$1" # owner/repo/path@ref OR owner/repo@ref
local before_at="${spec%@*}"
local ref="${spec#*@}"
local owner_repo="${before_at%%/*}/${before_at#*/}"
owner_repo="${owner_repo%%/*}/${owner_repo#*/}" # keep first two segments
local owner="${before_at%%/*}"
local rest="${before_at#*/}"
local repo="${rest%%/*}"
local path=""
if [[ "$before_at" == */*/* ]]; then
path="${before_at#"$owner/$repo/"}"
fi
# 1) repo exists
if ! gh api -H "Accept: application/vnd.github+json" "/repos/$owner/$repo" >/dev/null 2>&1; then
echo "ERROR: repo not found or inaccessible: $owner/$repo (from $spec)"
return 1
fi
# 2) ref exists (try tag, branch, then commit)
if ! gh api -H "Accept: application/vnd.github+json" "/repos/$owner/$repo/git/ref/tags/$ref" >/dev/null 2>&1 \
&& ! gh api -H "Accept: application/vnd.github+json" "/repos/$owner/$repo/git/ref/heads/$ref" >/dev/null 2>&1 \
&& ! gh api -H "Accept: application/vnd.github+json" "/repos/$owner/$repo/commits/$ref" >/dev/null 2>&1
then
echo "ERROR: ref not found (tag/branch/commit): $spec"
return 1
fi
# 3) action metadata exists at path (or root)
local base_path="${path}"
if [[ -z "$base_path" ]]; then
base_path=""
fi
local try_paths=()
if [[ -z "$base_path" ]]; then
try_paths=("action.yml" "action.yaml")
else
try_paths=("$base_path/action.yml" "$base_path/action.yaml")
fi
for p in "${try_paths[@]}"; do
if gh api -H "Accept: application/vnd.github+json" \
"/repos/$owner/$repo/contents/$p?ref=$ref" >/dev/null 2>&1
then
return 0
fi
done
echo "ERROR: action metadata not found at ref: $spec (looked for action.yml/action.yaml under '${base_path:-repo root}')"
return 1
}
for u in "${USES[@]}"; do
# Ignore reusable workflows here: they also use `uses:` but are `./.github/workflows/x.yml` or `owner/repo/.github/workflows/x.yml@ref`
# If you want to validate those too, add logic similar to check_local/check_remote for workflow files.
if [[ "$u" == docker://* ]]; then
echo "SKIP (docker image): $u"
continue
fi
if [[ "$u" == ./* ]]; then
if ! check_local "$u"; then fail=1; fi
continue
fi
# Remote action or reusable workflow reference (both look like owner/repo/...@ref)
if [[ "$u" == *@* && "$u" == */* ]]; then
if ! check_remote "$u"; then fail=1; fi
continue
fi
echo "WARN: unrecognized uses format: $u"
done
exit $fail
+71
View File
@@ -0,0 +1,71 @@
#!/usr/bin/env bash
#
# Report orphaned CI plumbing — the GitHub Actions analogue of the "dead file"
# problem knip/vulture catch for TS/Python:
#
# * composite actions under .github/actions/* that no workflow `uses:`
# * reusable workflows (on: workflow_call) that nothing calls AND that have
# no self-trigger (push/pull_request/schedule/...), i.e. truly unreachable
#
# Report-only: prints findings and always exits 0 so it never blocks CI on a
# heuristic. Vet anything it prints by hand before deleting.
set -uo pipefail
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
workflows_dir="$repo_root/.github/workflows"
actions_dir="$repo_root/.github/actions"
findings=0
note() {
findings=$((findings + 1))
echo " - $1"
}
echo "### Orphaned CI check"
echo ""
echo "Composite actions with no callers:"
if [[ -d "$actions_dir" ]]; then
for action_yml in "$actions_dir"/*/action.yml "$actions_dir"/*/action.yaml; do
[[ -f "$action_yml" ]] || continue
name="$(basename "$(dirname "$action_yml")")"
# A caller references it as `uses: ./.github/actions/<name>`.
if ! grep -rqs "uses:.*\.github/actions/$name\b" "$workflows_dir" "$actions_dir"; then
note "action \`$name\` (.github/actions/$name) is referenced by no workflow"
fi
done
fi
[[ $findings -eq 0 ]] && echo " _(none)_"
before_wf=$findings
echo ""
echo "Reusable workflows that are unreachable:"
for wf in "$workflows_dir"/*.yml "$workflows_dir"/*.yaml; do
[[ -f "$wf" ]] || continue
# Only reusable workflows are candidates.
grep -qs "workflow_call:" "$wf" || continue
base="$(basename "$wf")"
# Called by another workflow?
if grep -rqs "uses:.*\.github/workflows/$base\b" "$workflows_dir"; then
continue
fi
# Self-triggering (has a real event trigger besides workflow_call)?
if grep -Eqs "^[[:space:]]+(push|pull_request|schedule|workflow_dispatch|release|issues|issue_comment|repository_dispatch|merge_group):" "$wf"; then
continue
fi
note "workflow \`$base\` exposes workflow_call but has no caller and no self-trigger"
done
[[ $findings -eq $before_wf ]] && echo " _(none)_"
echo ""
if [[ $findings -eq 0 ]]; then
echo "No orphaned CI plumbing found."
else
echo "Found $findings orphaned CI item(s) above — review before removing."
fi
# Report-only.
exit 0
+45
View File
@@ -0,0 +1,45 @@
#!/usr/bin/env bash
#
# Create the GitHub Release as a DRAFT with every asset attached, or resume an existing
# draft by re-uploading (--clobber). Refuse to mutate a tag that is already published.
#
# Drafts fire no `release: published` event and are excluded from `/releases/latest`, so no
# anonymous consumer (install.sh, Homebrew, the redirect) can observe a release before its
# assets are attached and verified. The release is only flipped to published by a later step.
#
# Inputs (env): RELEASE_TAG, RELEASE_NAME, CHECKOUT_REF, PRERELEASE, GH_TOKEN
# BINARIES_DIR (optional, defaults to the CLI dist dir)
set -euo pipefail
binaries_dir="${BINARIES_DIR:-ts/packages/cli/dist/binaries}"
flags=(
--draft
--title "$RELEASE_NAME"
--target "$CHECKOUT_REF"
--generate-notes
)
# Preserve prerelease status on the draft so betas stay prereleases once published.
if [[ "$PRERELEASE" == "true" ]]; then
flags+=(--prerelease)
fi
# Idempotent for re-runs of a not-yet-published release: reuse an existing draft and clobber
# its assets. Refuse to mutate a tag that is already published.
if gh release view "$RELEASE_TAG" --json isDraft --jq '.isDraft' 2>/dev/null | grep -qx true; then
echo "Draft $RELEASE_TAG already exists — re-uploading assets with --clobber"
gh release upload "$RELEASE_TAG" \
"$binaries_dir"/*.zip \
"$binaries_dir/checksums.txt" --clobber
elif gh release view "$RELEASE_TAG" >/dev/null 2>&1; then
# A red ❌ here is EXPECTED, not a bug, when two runs target the same tag (e.g. two quick CLI
# version bumps): per-tag `concurrency` serializes them, the first publishes, and the second —
# finding the tag already published — fails loudly here rather than silently clobbering a live
# release. If you hit this, confirm the tag is genuinely published before re-running.
echo "::error::Release $RELEASE_TAG is already published — refusing to mutate it. Investigate the publish path."
exit 1
else
gh release create "$RELEASE_TAG" "${flags[@]}" \
"$binaries_dir"/*.zip \
"$binaries_dir/checksums.txt"
fi
+148
View File
@@ -0,0 +1,148 @@
#!/usr/bin/env bash
#
# Resolve the CLI release target and write its metadata to $GITHUB_OUTPUT for the
# build/release jobs of build-cli-binaries.yml. Three modes:
#
# - push to `next` with a ts/packages/cli/package.json version bump → stable release
# - push to `next` without a bump, or workflow_dispatch build-beta → rolling beta
# - workflow_dispatch promote-stable <beta tag> → stable promotion
#
# Inputs (env): EVENT_NAME, ACTION_INPUT, BETA_TAG_INPUT, GITHUB_TOKEN,
# REPOSITORY, RUN_NUMBER, COMMIT_SHA
# Output: key=value lines appended to $GITHUB_OUTPUT
set -euo pipefail
# Latest STABLE @composio/cli release tag, by true semver order (empty if none).
#
# A lexical sort is wrong here: "@composio/cli@0.2.9" sorts AFTER "0.2.10", so once a
# patch reaches double digits `last` would pick the older release and beta versions
# would regress. Parse the version triplet to numbers and sort numerically instead.
latest_stable_tag() {
gh release list \
--repo "$REPOSITORY" \
--exclude-drafts \
--limit 1000 \
--json tagName,isPrerelease \
--jq '[.[]
| select(.tagName | startswith("@composio/cli@"))
| select(.isPrerelease == false)]
| sort_by(.tagName | ltrimstr("@composio/cli@") | split(".") | map(tonumber))
| last | .tagName // empty'
}
# Echo the next "<major>.<minor>.<patch+1>" off the latest stable release, falling
# back to the working-tree package.json when no stable release exists yet.
next_beta_base_version() {
local latest current
latest=$(latest_stable_tag)
if [[ -z "$latest" ]]; then
echo "No stable @composio/cli release found, falling back to package.json" >&2
current=$(node -p "require('./ts/packages/cli/package.json').version")
else
current=${latest#@composio/cli@}
fi
local major minor patch
IFS='.' read -r major minor patch <<<"$current"
echo "${major}.${minor}.$((patch + 1))"
}
emit_beta_target() {
local next_version release_tag
next_version=$(next_beta_base_version)
release_tag="@composio/cli@${next_version}-beta.${RUN_NUMBER}"
{
echo "checkout_ref=${COMMIT_SHA}"
echo "release_name=CLI Beta ${release_tag}"
echo "release_tag=${release_tag}"
echo "release_version=${next_version}"
echo "prerelease=true"
echo "make_latest=false"
} >>"$GITHUB_OUTPUT"
}
emit_stable_target() {
local release_tag=$1 release_version=$2 checkout_ref=$3
{
echo "checkout_ref=${checkout_ref}"
echo "release_name=CLI ${release_tag}"
echo "release_tag=${release_tag}"
echo "release_version=${release_version}"
echo "prerelease=false"
echo "make_latest=true"
} >>"$GITHUB_OUTPUT"
}
# ── Push to next ──
if [[ "$EVENT_NAME" == "push" ]]; then
current_version=$(node -p "require('./ts/packages/cli/package.json').version")
previous_version=$(git show HEAD^:ts/packages/cli/package.json 2>/dev/null \
| node -p "JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')).version" 2>/dev/null || echo "")
# A version bump on push is a stable release; anything else is a rolling beta.
if [[ -n "$previous_version" && "$current_version" != "$previous_version" ]]; then
emit_stable_target "@composio/cli@${current_version}" "$current_version" "$COMMIT_SHA"
else
emit_beta_target
fi
exit 0
fi
# ── workflow_dispatch: build-beta ──
if [[ "$EVENT_NAME" == "workflow_dispatch" && "$ACTION_INPUT" == "build-beta" ]]; then
emit_beta_target
exit 0
fi
# ── workflow_dispatch: promote-stable ──
if [[ "$ACTION_INPUT" != "promote-stable" ]]; then
echo "Unknown action: $ACTION_INPUT" >&2
exit 1
fi
if [[ -z "$BETA_TAG_INPUT" ]]; then
echo "beta_tag input is required for promote-stable" >&2
exit 1
fi
encoded_beta_tag=$(python3 -c 'import os, urllib.parse; print(urllib.parse.quote(os.environ["BETA_TAG_INPUT"], safe=""))')
release_json=$(curl -fsSL \
-H "Authorization: Bearer ${GITHUB_TOKEN}" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/${REPOSITORY}/releases/tags/${encoded_beta_tag}")
is_prerelease=$(jq -r '.prerelease' <<<"$release_json")
if [[ "$is_prerelease" != "true" ]]; then
echo "Release ${BETA_TAG_INPUT} is not a beta prerelease" >&2
exit 1
fi
if [[ ! "$BETA_TAG_INPUT" =~ ^@composio/cli@([0-9]+\.[0-9]+\.[0-9]+)-beta\.[0-9]+$ ]]; then
echo "Beta tag must match @composio/cli@<version>-beta.<number>" >&2
exit 1
fi
stable_version="${BASH_REMATCH[1]}"
stable_tag="@composio/cli@${stable_version}"
# Refuse to re-promote an already-PUBLISHED stable release, but allow resuming an
# existing DRAFT (a prior promote run that built assets but did not publish). The
# REST `/releases/tags/{tag}` endpoint returns 404 for drafts, so use `gh release view`
# — it resolves drafts by name and exposes `isDraft`.
if isdraft=$(gh release view "$stable_tag" --json isDraft --jq '.isDraft' 2>/dev/null); then
if [[ "$isdraft" == "true" ]]; then
echo "Stable release ${stable_tag} exists as a draft — resuming (assets will be re-uploaded)."
else
echo "Stable release ${stable_tag} is already published" >&2
exit 1
fi
fi
target_commitish=$(jq -r '.target_commitish' <<<"$release_json")
if [[ -z "$target_commitish" || "$target_commitish" == "null" ]]; then
echo "Beta release ${BETA_TAG_INPUT} does not expose target_commitish" >&2
exit 1
fi
emit_stable_target "$stable_tag" "$stable_version" "$target_commitish"
+48
View File
@@ -0,0 +1,48 @@
#!/usr/bin/env bash
#
# Loud failure gate: assert the full canonical asset set is attached to the release AND fully
# uploaded (state == "uploaded"). An asset can appear in the listing while still processing,
# which is exactly how a release ends up serving 404s — so require state == "uploaded", with a
# bounded retry while uploads settle.
#
# Inputs (env): RELEASE_TAG, GH_TOKEN
# VERIFY_ATTEMPTS / VERIFY_SLEEP_SECONDS (optional, for fast tests)
set -euo pipefail
# Keep this list in lock-step with the build matrix in build-cli-binaries.yml (4 platforms)
# plus the skill bundle and checksums. Adding a platform to the matrix must extend this list.
expected=(
composio-linux-x64.zip
composio-linux-aarch64.zip
composio-darwin-x64.zip
composio-darwin-aarch64.zip
composio-skill.zip
checksums.txt
)
attempts="${VERIFY_ATTEMPTS:-10}"
sleep_seconds="${VERIFY_SLEEP_SECONDS:-15}"
for attempt in $(seq 1 "$attempts"); do
# Single snapshot per attempt — querying names and state separately would open a
# time-of-check/time-of-use gap in the very gate meant to close one.
payload=$(gh release view "$RELEASE_TAG" --json assets)
mapfile -t uploaded < <(jq -r '.assets[] | select(.state == "uploaded") | .name' <<<"$payload" | sort)
missing=()
for want in "${expected[@]}"; do
printf '%s\n' "${uploaded[@]}" | grep -qxF "$want" || missing+=("$want")
done
if [[ ${#missing[@]} -eq 0 ]]; then
echo "✅ All ${#expected[@]} expected assets present and uploaded."
exit 0
fi
echo "Attempt ${attempt}/${attempts} — missing or not-yet-uploaded: ${missing[*]}"
if [[ "$attempt" -eq "$attempts" ]]; then
echo "::error::Release $RELEASE_TAG is incomplete — missing or not uploaded: ${missing[*]}"
exit 1
fi
sleep "$sleep_seconds"
done
+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