curl install.sh is now the only CLI install channel.
- INSTALL.md: drop npm/pnpm/yarn section; Windows guidance is WSL
- install.sh: Windows error no longer advises the dead npm package
- build-cli-binaries.yml: strip npm section from generated release INSTALL.md
- cli.install-health-check.yml: drop stale npm dist-tag/Homebrew comment
- cli.test-installation.yml: delete no-op npm-fallback job and its
toolchain-versions feeder job; prune summary references
- delete cli.bump-homebrew-tap.yml + bump-homebrew-formula.py (automation
never fired: GITHUB_TOKEN-published releases suppress release-triggered
workflows and HOMEBREW_TAP_TOKEN is dead)
- ts/packages/cli/package.json: drop unused publishConfig (package is private)
- cli-release skill + CLI AGENTS.md: remove Homebrew steps from release docs
- toolchain-versions.json: drop now-unused node_install_compat matrix
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PostHog's own term for the public write-only phc_* credential is the
project API key; the old name read like a secret. Covers the env var,
the build-time bake define, the baked constant, and the workflow env.
The GitHub repo variable itself must be renamed manually.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- trust a persisted apollo_user_id without a fingerprint only in the
keychain security modes with a parseable user config
- rotate to a fresh install_id when a different Apollo user links on
the same machine, instead of refusing to alias
- bootstrap-stitch missing identities from the resolved api key via
session/info (24h throttle, 1.5s timeout, all failures swallowed)
- redact secret-shaped tokens from outgoing event properties
- make analytics.json writes atomic (temp file + rename)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
All human login flows now call linkApolloIdentityForAnalytics after
ctx.login instead of before, and every agent entry point links through
the loginWithAgentIdentity chokepoint (best-effort, never fatal).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
## Summary
Implements the docs half of **PLEN-2793** — renders a typed public
reference for the payloads Composio delivers to customer webhook URLs,
under **API Reference → Webhook Events**.
Pairs with platform PR ComposioHQ/platform#11389, which generates the
spec.
## What's here
- **`docs/public/openapi-webhooks.json`** — the generated OpenAPI
**3.1** spec (top-level `webhooks`: `composio.trigger.message`,
`composio.connected_account.expired`, `composio.trigger.disabled`),
produced from the webhook payload Zod schemas in Apollo (SSOT). Finishes
the earlier untracked WIP: adds the missing `trigger.disabled` event and
reuses shared schemas as `$ref` (`WebhookEventEnvelope`,
`ConnectedAccountDetailed`).
- **`docs/lib/openapi.ts`** — adds the spec as a **second input** to the
v3.1 reference source. Fumadocs (`fumadocs-openapi`) renders the
`webhooks` block natively; operations group under the "Webhook Events"
tag at `api-reference/webhook-events/*`.
- **`webhook-events/index.mdx`** — landing page listing the three
events, cross-linked to the Webhook Subscriptions API and signature
verification.
## Why a separate 3.1 spec
`webhooks` is a 3.1-only top-level field; the main `openapi.json` is
3.0. A separate file keeps the main spec (and its sync pipeline)
untouched and avoids the docs' union-normalisation pass. See the
platform PR for the full rationale.
## Verified locally
- [x] `bun scripts/validate-links.ts` — **0 errors** (the generated
`handle_composio_*` webhook pages resolve; no collision with the
hand-authored `index.mdx`)
- [x] `bun test tests/static/` — 33 pass / 0 fail
- [x] `bun run types:check` — clean
## Notes
- Setup, subscription, and signature-verification docs already exist
(`setting-up-triggers/*`) and are cross-linked, not rewritten.
- Follow-up (optional): auto-sync the webhooks spec from Apollo on prod
deploy (currently committed by hand), mirroring the main-spec
`docs-update-data` pipeline.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: jkomyno <alberto@composio.dev>
This PR:
- splits https://github.com/ComposioHQ/composio/pull/3953 in two: this
PR carries every dependency and GitHub Actions bump **except** the docs
site, which follows in a stacked PR
- consolidates and supersedes Dependabot PRs #3915, #3916, and #3934
through #3942
- adopts TypeScript 7.0.2 for primary compilation while retaining the
`@typescript/typescript6` API lane that TypeScript-ESLint still
requires, following the upstream side-by-side guidance
- refreshes Python core and provider dependencies, lockfiles, and the
Ruff 0.16 lint configuration
- updates every GitHub Action with a verified newer official release,
including majors, while retaining immutable commit SHA pins and
migrating setup-uv cache pruning
- deletes four per-package `eslint.config.mjs` shims: under ESLint 10
the default per-file config lookup re-anchors the root config's globs
into each package, so `pnpm lint` stayed green while the CLI's
try/catch, `process.env` and node-builtin bans went unenforced
- bounds and documents the new `brace-expansion` and `@hono/node-server`
security overrides, raising the `@hono/node-server` floor to 2.0.10 to
clear GHSA-9mqv-5hh9-4cgg
- preserves intentional compatibility fixtures and lanes for AI SDK 6,
Zod 3, TypeScript 5.8, Mastra AI SDK 5, and Python provider constraints
## Context
The docs site is a separate Bun workspace with its own `bun.lock` and is
not a pnpm workspace member, so the two halves share no lockfile and no
build. Splitting them keeps the Fumadocs 11 migration (a breaking API
change with real refactoring) reviewable on its own, independently of
the mechanical version bumps here.
The `brace-expansion` override deliberately spans majors:
GHSA-mh99-v99m-4gvg (HIGH) is published as a single `<=5.0.7` range with
no 1.x or 2.x backport, so narrowing it to the 5.x line puts
`brace-expansion` 2.1.2 back under `core>minimatch>brace-expansion` and
`pnpm audit --prod --audit-level=high` exits 1. Verified both ways; the
trade-off it buys is recorded inline in `pnpm-workspace.yaml`.
Verified on this branch standalone: `pnpm install --frozen-lockfile`,
`pnpm lint`, `pnpm typecheck`, `pnpm build:packages`, `pnpm test` (963
tests, 26/26 tasks), and `pnpm audit --prod --audit-level=high`.
This PR:
- builds on top of https://github.com/ComposioHQ/composio/pull/3944
- reports `checkStatus: "unknown"` instead of fabricating an up-to-date
result when GitHub refreshes fail
- preserves the last successful release cache and throttles failed
attempts separately
- uses `Command.provideEffectDiscard` to start background checks after
parsing and skips explicit `composio version --check` invocations
- validates installed release versions before semver comparison
- adds deterministic unit concurrency coverage and compiled-binary CLI
E2E coverage
## Verification
- `pnpm --filter @composio/cli test` (`963` passed, `1` skipped)
- `pnpm typecheck` (`14/14` tasks)
- `pnpm build:packages` (`19/19` tasks)
- `pnpm --filter @e2e-tests/cli-version test:e2e:cli` (`9` passed)
- `pnpm build:binary` followed by `pnpm install:binary`
- installed Mach-O binary smoke checks for plain version, live and
cached `version --check`, unknown status, and both leading `--log-level`
forms
- scoped ESLint on all changed TypeScript files
- `git diff --check`
Repository-wide `pnpm lint` remains red on 52 existing errors across
unrelated docs and scripts; the changed TypeScript files pass scoped
ESLint.
## Summary
A user reported that a first-time CLI install "didn't get added to their
PATH". Investigation showed the PATH block *is* written correctly — but
the user never learns about it: `install.sh` delegates shell integration
to `composio install` with stderr captured to a temp file, and the CLI's
TerminalUI suppresses all decoration when stderr isn't a TTY. So the
"Updated ~/.zshrc" / "Restart your shell" guidance is silently
swallowed, the installer ends with "To get started, run: composio
--help", and the user gets `command not found` in their current shell.
This is the minimal fix: state explicitly in the installer's closing
message that composio was added to PATH and takes effect after
restarting the shell.
## Changes
- `install.sh`: closing message now reads "Composio was added to your
PATH — restart your shell (or open a new terminal) for it to take
effect." before the get-started commands. The block prints on every
install path (delegated and fallback).
## Type of change
- [x] Bug fix
- [ ] New feature
- [ ] Refactor/Chore
- [ ] Documentation
- [ ] Breaking change
## How Has This Been Tested?
- `bash -n install.sh`
- Stubbed end-to-end run of the post-download section (delegation
success and failure paths) in a sandboxed `$HOME`, verifying the message
prints in both.
- Reproduced the original issue beforehand by running the real installer
(released `@composio/cli@0.2.32`) into a fake `$HOME`: PATH block
written, zero user-facing messaging about it.
## Screenshots (if applicable)
```
Composio was added to your PATH — restart your shell (or open a new terminal) for it to take effect.
Then, to get started, run:
composio --help
composio login
```
## Checklist
- [x] I have read the Code of Conduct and this PR adheres to it
- [x] I ran linters/tests locally and they passed
- [x] I updated documentation as needed
- [x] I added tests or explain why not applicable — shell-script-only
messaging change; no test harness exists for install.sh output
- [x] I added a changeset if this change affects published packages —
not applicable, install.sh is not a published package and
`@composio/cli` is Changesets-ignored
## Additional context
install.sh is served from `next`, so this reaches all new installs
immediately on merge and works with the already-released binary — no CLI
release required.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Part of
[PRDE-1143](https://linear.app/composio/issue/PRDE-1143/prompt-users-to-upgrade-composio-cli-when-new-versions-are-available).
## What
`composio version --check` prints one JSON object to stdout and exits 0:
```json
{"current":"0.2.31","latestStable":"0.2.32","updateAvailable":true,"lastChecked":"2026-07-24T19:15:15.413Z"}
```
The startup upgrade banner is TTY-gated, so scripts and agent-driven
(piped) CLI usage never learn a newer stable exists. `--check` gives
them an explicit query: if `updateAvailable`, run `composio upgrade`.
It reuses the update notice's self-throttled release cache
(`~/.composio/update-check.json`, unchanged shape — PRDE-1144 reads it):
fresh <24h means no network call; stale means a synchronous GitHub fetch
with a 10s timeout whose failures degrade to the cached/absent state
instead of erroring. Purely additive — the existing banner behavior is
untouched.
## Verification
Before writing anything, the existing notice pipeline was verified
empirically with a real stale install (stable `0.2.31` binary, isolated
`$HOME`, real pty): background fetch completes even on fast commands,
the banner fires on the next TTY run, piped output stays silent, and
symlinked installs resolve `release-tag.txt` correctly.
- 4 unit tests for `getUpdateStatus`; full CLI suite 955 passed,
typecheck clean.
- Exercised live from source: pure JSON on stdout, empty stderr, exit 0.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Summary
Automated sync of backend data into the docs site. Triggered by:
`workflow_dispatch`.
## 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 production
- **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
Co-authored-by: sudodaksh <23355449+sudodaksh@users.noreply.github.com>
## Why
`docs.composio.dev/toolkits` has been serving a stale catalog. Toolkits
added to
production since 2026-07-13 — `saperly`, among others — have no page and
404,
while being live in the API and in `composio search`.
The page is statically generated from the committed
`docs/public/data/toolkits.json`,
which this workflow is supposed to refresh every 5 hours. It has failed
on **every
run since 2026-07-13** (60 of the last 100 runs), starting with the run
immediately
after #3770.
## Root cause
#3770 correctly moved this job onto production and added a guard
rejecting any
non-production base URL. But `secrets.COMPOSIO_API_KEY` is shared with
`ts.test-e2e`, `py.test`, `py.check` and `ts.examples-nightly` — all of
which pin
`COMPOSIO_BASE_URL` to staging. It is a staging credential, and it
returns 401
against `backend.composio.dev`.
Evidence it was live, not expired: `ts.examples-nightly` has been green
against
staging using that same key.
Repointing the shared secret at production is not an option — it would
break the
staging suites. So this job takes its own key.
## Changes
1. **`COMPOSIO_DOCS_API_KEY`** — the docs job reads a dedicated
production secret.
The env var name stays `COMPOSIO_API_KEY` because that is what the
generators read.
2. **Failure alerting** — an `if: failure()` step files one tracking
issue, deduped
by label so a 5-hourly cron does not open 60 duplicates.
## Why alerting matters here
This workflow fails silently by construction. When it breaks, the
catalog simply
stops being refreshed, the docs site keeps building from the last good
commit, and
nothing 500s. There is no user-visible signal short of someone noticing
a missing
toolkit page. That is how this went unnoticed for 11 days.
## Required before merge
`COMPOSIO_DOCS_API_KEY` must be set with **production** read access and
these
permission areas: Toolkits (read), Tools (read), Triggers (read), and
**Sessions (read and write)** — `generate-meta-tools.ts` creates a
tool-router
session and then reads its tool list, so write-only is not sufficient.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
The job sets `defaults.run.working-directory: ./docs`, which the alerting step
inherited. That path only exists once checkout has run, so any failure before it
— the app-token step, for instance — would leave the runner unable to start the
step's shell. Combined with continue-on-error, the alert would be dropped
silently for exactly the earliest and least visible failures.
Pin the step to ${{ github.workspace }}, which the runner creates before any
step executes.
Reported by Cursor Bugbot on #3947.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`secrets.COMPOSIO_API_KEY` is shared with ts.test-e2e, py.test, py.check and
ts.examples-nightly. Those pin COMPOSIO_BASE_URL to staging, and the nightly
examples run is currently green against staging with this key — so the secret is
a live staging credential, not an expired one.
#3770 moved this job to production and added a guard rejecting any non-production
base URL, but no production credential was provisioned. The staging key has
returned 401 against backend.composio.dev on every run since, silently freezing
the committed toolkit catalog.
Repointing the shared secret at production would fix docs and break the staging
suites, and py.check sets no base URL at all — a production key there would aim
type-inference tests at production. So take a separate secret instead.
Requires COMPOSIO_DOCS_API_KEY to be set with production read access.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The scheduled `Docs - Update Data` workflow has failed on every run since
2026-07-13 and nobody noticed for 11 days. The failure mode is silent by
construction: when the sync breaks, `docs/public/data/toolkits.json` simply
stops being refreshed, the docs site keeps building from the last good commit,
and nothing 500s. Toolkits added to production after the last green run just
have no page and 404 — which is how `saperly` ended up missing from
docs.composio.dev/toolkits while being live in the API.
Add a `failure()` step that opens one tracking issue, deduped by label so a
5-hourly cron does not file 60 duplicates. The body names the most likely cause
up front: `COMPOSIO_API_KEY` needs production read access against
backend.composio.dev, and a staging-only key returns 401.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The startup upgrade banner is TTY-gated, so scripts and agent-driven (piped)
CLI usage never learn a newer stable exists. `composio version --check`
prints a JSON status to stdout — {current, latestStable, updateAvailable,
lastChecked} — reusing the update notice's self-throttled release cache
(fresh <24h: no network; stale: synchronous fetch, 10s timeout, failures
degrade to the cached/absent state).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Strip the review-round comments per feedback; rationale for the non-obvious
ones lives in the fix commits (c22c7b32 post-alias fallback, b9740b8c credential
ordering).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Revert the gratuitous restructuring of the three binary build scripts. The
earlier version pulled `'bun'` out of the args array and dropped
`as const satisfies ReadonlyArray<string>` to work around a typecheck error —
but a variadic tuple (`['bun', ...posthogBakeArgs(), ...] as const`) keeps its
first element known, so `Command.make(...args)` still typechecks. Each script's
diff is now just the `posthogBakeArgs` import plus the one inserted spread.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The CLI's PATH-setup output is swallowed by the installer's stderr
capture, so first-time users were never told the PATH change only takes
effect in a new shell — they ran `composio --help` in the same terminal,
got "command not found", and concluded the install had failed to set up
PATH. State it explicitly in the closing message.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Going direct to PostHog dropped the org/project context Apollo's Segment path
enriched server-side. org_id is available in ~/.composio/user_data.json — which
the analytics worker already reads — so post-login events now carry it. project_id
is per-session and not reliably in user config, so it is left for a follow-up
rather than fabricated; the PR description is corrected accordingly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Once $create_alias fires, PostHog permanently merges install_id into the Apollo
person. Falling back to that same install_id on logout or on a credential
fingerprint mismatch therefore did NOT anonymise anything — person-level
insights still resolved those events to the previous user. The guard added in
b9740b8c changed the mechanism of the cross-user attribution bug rather than
removing it.
getDistinctId now returns:
- the bare install_id only while the install has never been aliased, so the
login alias still merges the pre-login funnel correctly;
- user_<credential fingerprint> when a credential is active but does not match
the stored identity — pseudonymous per credential, never merged;
- anon_<install_id> when signed out after an alias.
Verified live against a throwaway PostHog project: the bare install_id and the
Apollo id both resolve to person 42ff3120, while post-logout events under
anon_<install_id> resolve to a different person entirely. The earlier trace had
shown both ids collapsing to one person; I read that as the merge working and
missed that it also meant the logout fallback attributed to the logged-out user.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two bugbot findings on the previous fix.
- The credential fingerprint was read back via getUserApiKey, but every login
path resolves identity BEFORE persisting the new credential (link at 327/512/720
vs ctx.login at 342/560/756). So the stored fingerprint described the previous
key — or none on a fresh install — and could never match, leaving every
post-login event anonymous. That defeated the identity stitch entirely. The
credential is now passed in explicitly by the caller (each already holds the
key it just validated), so identity no longer depends on write ordering.
- spawnWorker swallowed DetachedProcessSpawnError and enqueue returned true
regardless, so a failed spawn still marked the alias as sent and it was never
retried — defeating the field added for exactly that purpose. Spawn failure
now propagates through enqueue.
Added a regression test that reproduces the login ordering (no readable
credential at link time); verified it fails without the fix, producing the
install_id instead of the Apollo id.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review follow-ups.
- install.sh ran `login --agent` without an origin marker while its `install`
and `setup` invocations both set one, so installer-driven agent logins were
indistinguishable from manual ones — the exact ambiguity this PR removes for
the other two.
- Hoist the release channel to a module constant; both inputs are constants, so
recomputing it per event was pointless.
- The changelog overstated the gap: a Homebrew user running `composio install`
by hand still emits install events, they just carry no installer origin.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review follow-ups on the direct-to-PostHog telemetry.
- Cross-user attribution (the serious one). getDistinctId trusted the persisted
apollo_user_id regardless of which credential was actually in use, while
getUserApiKey gives COMPOSIO_USER_API_KEY precedence over the stored config.
A script running under user B's env key therefore emitted events attributed
to whoever last logged in — and a backfilling command under B's key would
overwrite the stored id, flipping later events the other way. The pre-rewrite
code derived identity from the key itself and did not have this problem.
Now a fingerprint of the resolving credential is persisted alongside the id,
and the id is only used while that fingerprint still matches; otherwise we
fall back to the anonymous install_id. Unsure attribution is anonymous rather
than wrong.
- clearApolloIdentityForAnalytics ignored the opt-out and called
getOrCreateInstallId, so `composio logout` with telemetry disabled created
~/.composio/analytics.json and minted an install id — contradicting the
invariant asserted elsewhere in these tests. It now reads state and does
nothing when there is nothing to clear.
- An install is aliased at most once, ever. Logging in as a second user on the
same device no longer emits a second $create_alias for an install PostHog has
already identified.
- Dropped the now-unused readApolloUserId.
Tests: stale-identity fallback under a differing env key, logout-while-disabled
creating no file, and no re-alias across logout + login as a different user.
The post-login test now establishes identity through the public API rather than
hand-writing state, so it exercises the credential binding.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Every CLI event now carries journey_stage (install|setup|login|connect|
execute|other) via an exhaustive event->stage map in events.ts, and
cli_channel (stable|beta) derived from the existing
inferSkillReleaseChannel(APP_VERSION). Both are applied by a single
buildEvent factory so no builder hand-writes them.
install.sh now sets COMPOSIO_CLI_INVOCATION_ORIGIN=installer on its
"$exe" install run (matching the setup run), so script installs are
distinguishable from a user manually running `composio install`.
npm and Homebrew installs never run install.sh, so the install stage
only covers install.sh installs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review feedback (KJ):
- Strip the added explanatory comments across dispatch.ts, constants.ts,
_shared.ts, build-binary.ts and the workflow; keep only pre-existing ones.
- Shorten the CHANGELOG entry to a single line.
- Revert the .gitignore addition.
Bugbot findings:
- Already-authenticated installs never stitched (events stayed anonymous after
upgrade, since only `login` persisted the id). Backfill now runs from any
command that already resolves a session: whoami, init, connected-accounts
link, plus the existing login paths.
- `composio login --agent` skipped the stitch; the agent branch now resolves
session info best-effort and links.
- `composio logout` left `apollo_user_id` behind, so later events kept
attributing to the previous person. Added clearApolloIdentityForAnalytics.
- A failed alias was never retried, because any persisted `apollo_user_id`
counted as "already aliased" — including when nothing was ever enqueued (no
project key / telemetry off). Track `aliased_apollo_user_id` separately and
only record it once the enqueue actually happened.
Tests: alias retry after a skipped enqueue, and logout reverting to install_id.
Full suite 956 passed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review follow-ups on the direct-to-PostHog telemetry:
- linkApolloIdentityForAnalytics now checks the disabled/opt-out state BEFORE
touching disk, so a disabled (TELEMETRY_DISABLED/CI/etc.) login no longer
creates ~/.composio/analytics.json with the install_id + Apollo id. Honors
the disabled-means-no-op contract.
- Emit the $create_alias only on the first association of an install with an
Apollo user: a repeat login is redundant, and PostHog rejects re-aliasing one
install into a second account. Gates on the persisted apollo_user_id.
Tests: disabled path now asserts the identity file is absent; alias test
asserts a repeat login does not re-spawn the alias worker.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Short-circuit enqueuePostHogEnvelope when no PostHog project key is
configured, so we never spawn a detached worker that would only no-op.
The pre-rewrite code guarded on the endpoint before spawning; the direct
transport lost it, so every command on a keyless build (the default until
the key is baked, and all OSS forks / local builds) spawned a throwaway
process. Added a parent-side test asserting no spawn without a key.
- Remove three duplicated inline comments at the login call sites (the
function name + its definition comment already document it).
- Ignore .claude/plans/ (handoff artifacts, never committed).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
## Summary
`composio install` uses a temporary file and rename so shell config
updates are atomic. When the config path is a symlink, that rename
replaces the symlink itself. This breaks setups that use tools such as
GNU Stow to manage and sync dotfiles across machines.
Fixes#3922
## Changes
- Resolve valid symlink chains before choosing the temporary file and
rename target.
- Keep regular shell config files on the existing write path.
- Add a regression test using a relative `.zshrc` symlink.
## Type of change
- [x] Bug fix
- [ ] New feature
- [ ] Refactor/Chore
- [ ] Documentation
- [ ] Breaking change
## How Has This Been Tested?
- `mise exec -- pnpm --filter @composio/cli exec vitest run
test/src/commands/install.cmd.test.ts` — 13 passed
- `mise exec -- pnpm --filter @composio/cli typecheck`
- ESLint and Prettier on the two changed files
## Screenshots (if applicable)
Not applicable.
## Checklist
- [x] I have read the Code of Conduct and this PR adheres to it
- [x] I ran linters/tests locally and they passed
- [x] I updated documentation as needed
- [x] I added tests or explain why not applicable
- [x] I added a changeset if this change affects published packages
## Additional context
No changeset is needed because `@composio/cli` is ignored by the
repository's Changesets configuration.
The current `next` branch is missing the `#ssrf_guard` alias in the CLI
Vitest config. I added that alias locally to run the focused test, then
removed it before committing this patch.
---------
Co-authored-by: Alberto Schiabel <jkomyno@users.noreply.github.com>
Co-authored-by: jkomyno <alberto@composio.dev>
This PR:
- follows up on https://github.com/ComposioHQ/composio/pull/3838
- renders the existing `Legacy` badge in v3 and v3.1 endpoint headers
when the OpenAPI operation has `deprecated: true`
- centralizes deprecated-endpoint badge copy across API overview tables
and detail pages
- adds static coverage plus running-server assertions for the reported
routes and active control endpoints
- verifies the fix with `bun run test`, `bun run types:check`, `bun run
lint:links`, `bun run build`, and the focused integration suite
## What
Every link to `dashboard.composio.dev` in the docs now carries
`utm_source=docs`, `utm_medium`, and `utm_campaign`, so sign-ups driven
by docs show up attributable in PostHog instead of relying on (lossy)
referrer data. Previously only the navbar Playground button was tagged.
On top of that, dashboard links are now held to a full link policy:
1. **UTMs required** on every dashboard link.
2. **No `app.composio.dev` or `platform.composio.dev`** anywhere (those
hosts don't exist).
3. **Go-link paths only**: any dashboard link with a path must be
`/~/project/...` or `/~/org/...` (so it resolves inside the user's
workspace), with `/login` as the one allowed exception.
## Link changes
- **MDX content** (30+ files): `utm_medium=content`,
`utm_campaign=<page-slug>` (e.g. `docs-quickstart`), so we can see
*which page* drives sign-ups. Code fences, inline code, and generated
`content/reference/**` are untouched.
- **Components**: toolkits landing Playground CTA
(`utm_medium=toolkits`) and the LLM guardrail markdown
(`utm_medium=llm-guardrails`). Deleted `landing-hero/_stub-links.ts` —
unused everywhere.
- **Go-link rewrites**: quickstart's 3× `/settings` →
`/~/project/settings/api-keys`; changelog's
`/{workspace}/{project}/settings` → `/~/project/settings/general`. (The
two `/settings` links in generated `content/reference/errors.mdx` need
the same fix upstream in the OpenAPI source.)
## Enforcement
- **ESLint** (`docs/eslint.config.mjs`): `no-restricted-syntax` errors
for all three rules on string/template literals in TS/TSX.
- **MDX** (ESLint can't parse it):
`tests/static/dashboard-links.test.ts` (3 tests), running in the
existing `docs-tests` CI workflow via `bun run test`. The banned-host
check scans all authored content including code fences; the UTM and
go-link checks apply to markdown links outside fences.
- Every rule was negative-tested (probe violations flagged, valid
go-links//login pass).
- Convention documented in `docs/AGENTS.md`.
## Zero ESLint errors
`bun run lint` previously exited with 25 pre-existing errors unrelated
to this change. Fixed the two `no-html-link-for-pages` ones properly
(`<Link>` in the toolkits page; intentional full-reload `<a>` in
`global-error.tsx` gets a commented disable). The 23 react-hooks
compiler violations (`set-state-in-effect`, `immutability`, `purity`)
across 15 interactive components are downgraded to `warn` with a comment
— refactoring animation-heavy components here would be risky and out of
scope; tracked to burn down separately and flip back to `error`.
## Verification
- `bun run lint` → **0 errors** (warnings all pre-existing)
- `bun test tests/static/` → 36 pass / 0 fail
- `bun run types:check` → clean
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Linear:
[UXE-188](https://linear.app/composio/issue/UXE-188/docs-utm-attribution-dashboard-link-policy-go-links-banned-hosts)
Hygiene pass on the direct-to-PostHog telemetry: condense verbose block
comments in dispatch.ts/constants.ts.
Source the public write-only PostHog project key from a build-time
COMPOSIO_POSTHOG_KEY env var, baked into the binary via `bun build --define`
(scripts/_shared.ts posthogBakeArgs), instead of a committed source literal.
This mirrors the dashboard's env-driven NEXT_PUBLIC_POSTHOG_TOKEN pattern and,
for this OSS repo, keeps the key out of public git history and out of fork/local
builds (they compile a no-op binary). `--define` is used rather than a second
`--env` because Bun honors only one `--env` and it would clobber the existing
DEBUG_OVERRIDE_* inline. CI passes the key as an Actions variable on the
cross-compile step; empty -> telemetry no-ops.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The CLI analytics path now delivers events straight to PostHog's public
capture API instead of the Apollo /api/v3/cli/analytics endpoint. This removes
the base-URL gate that silently dropped every pre-login event, so install /
setup / plugin-setup telemetry now reaches PostHog keyed by the anonymous
install_id.
At login the CLI persists the Apollo org_member.id into ~/.composio/
analytics.json and fires a single $create_alias (install_id -> org_member.id),
so post-login events and all later CLI activity resolve to the same PostHog
person as web onboarding. distinct_id is now the persisted apollo_user_id when
present, else the bare install_id (no more anon_/user_ prefixing).
Delivery choice: direct-only (the Apollo send is dropped for CLI events). The
detached best-effort worker and all opt-outs are unchanged; no secrets or
tool-arg values are sent.
The public phc_* project-196278 write key is left empty with a TODO — direct
delivery is a safe no-op until the maintainer fills it in. Env overrides:
COMPOSIO_POSTHOG_KEY / COMPOSIO_POSTHOG_INGEST_URL.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extend the dashboard link policy: no app.composio.dev or
platform.composio.dev anywhere, and any dashboard.composio.dev link with
a path must be a go-link (/~/project/... or /~/org/...) or /login.
Enforced via new no-restricted-syntax selectors for TS/TSX and the
static test (renamed to dashboard-links.test.ts) for MDX.
Rewrite the non-conforming links: quickstart /settings -> go-link for
api-keys (also restores UTM params lost to a stray checkout during
verification), changelog {workspace}/{project}/settings -> go-link for
project settings.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Tag every dashboard.composio.dev link in authored docs content and
components with utm_source=docs plus utm_medium/utm_campaign, and enforce
the convention going forward: an ESLint no-restricted-syntax rule covers
TS/TSX and a static bun test covers MDX (runs in docs-tests CI).
- content links use utm_medium=content and utm_campaign=<page-slug>
- content/reference is excluded (generated upstream)
- delete unused landing-hero/_stub-links.ts
- fix the no-html-link-for-pages errors; downgrade the pre-existing
react-hooks compiler violations to warnings until burned down, so
bun run lint exits with zero errors
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
## What
Instruments the CLI's plugin auto-install (`composio setup`) and shell
integration (`composio install`) so the install → setup → activation
funnel is observable in PostHog. Re-lands the previously-deferred setup
lifecycle telemetry (`4e68e652`, reverted by `333d2296`) and extends it
with runtime detection/failure/abandonment signal.
### Part 1 — re-land deferred lifecycle telemetry
- New events: `CLI_INSTALL_*`, `CLI_SETUP_*` lifecycle families,
per-host `CLI_PLUGIN_SETUP_SUCCEEDED` /
`CLI_PLUGIN_UNINSTALL_SUCCEEDED`.
- Adds `'setup'` to `KNOWN_COMMAND_TOKENS` — fixes the current
misattribution where every `composio setup` logged as generic
`CLI_COMMAND_*` with `command_path='composio'`.
- `install.sh` tags the auto-setup call with
`COMPOSIO_CLI_INVOCATION_ORIGIN=installer` so installer-driven setups
are attributable.
### Part 2 — runtime extensions (fired from `setup.ts` / `setup.cmd.ts`,
which the argv-only central dispatch can't see)
- **`CLI_SETUP_HOST_DETECTED`** (per host) — `agent_host`, `available`,
`supported`, `host_version`, `unsupported_reason_code`. Sizes the plugin
channel (Claude vs Codex vs neither).
- **Normalized `unsupported_reason_code`** threaded through
`SetupTargetDetection` (`codex_too_old` / `no_json_inspection` /
`host_command_failed` / `unknown`) instead of string-matching messages.
- **`CLI_PLUGIN_SETUP_FAILED`** (per host) — previously only success was
emitted; failures were lost into the argv-only `CLI_SETUP_FAILED` with
no host/phase.
- **`CLI_SETUP_CANCELLED`** (declined confirm) / **`CLI_SETUP_SKIPPED`**
(`--if-present` with no host) — previously indistinguishable from
success.
- `stdout_is_tty` added to `CLI_SETUP_INVOKED`.
## Why
`composio setup` (plugin auto-install for Claude Code / Codex) and the
curl installer were firing **zero** dedicated telemetry, and `setup` was
misattributed on top of that — the entire plugin-driven acquisition path
was dark. These events make the install → setup → per-host plugin
install → first `composio execute` funnel joinable by `install_id` /
`distinct_id`.
## Layering note
The central `CLI_SETUP_SUCCEEDED`/`FAILED` mean "the command ran /
threw"; the in-command events describe **what actually happened**. A
user-cancelled run emits both `CLI_SETUP_CANCELLED` and
`CLI_SETUP_SUCCEEDED` by design — analyze outcomes off the specific
events (mirrors how `CLI_EXECUTE_SUCCEEDED` coexists with
`CLI_TOOL_INVOCATION_FAILED`).
## Verification
- Typecheck (`tsgo`, src + test): clean.
- Full CLI vitest suite: **85 files, 796 passed, 1 skipped** — includes
new cases in `analytics.events.test.ts` and a new
`setup.telemetry.cmd.test.ts` (stubbed `CommandRunner` covering
detection, install, per-host failure, cancel/skip).
- `test/install-sh-release-resolution.test.sh`: passes (installer-origin
assertion re-added).
- Confirmed the Apollo `/api/v3/cli/analytics` ingest has no event-name
allowlist (validates `event` as a ≤200-char string, forwards arbitrary
names) and traced the live pipe (`enqueue → delivery_succeeded`), so the
new event names flow through unchanged once shipped.
No backend changes required.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- codesmith:footer -->
---
<a
href="https://app.blacksmith.sh/ComposioHQ/codesmith/composio/pr/3902"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v2.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-light-v2.svg"><img
alt="View with Codesmith"
src="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v2.svg"></picture></a>
<a
href="https://backend.blacksmith.sh/track/enable-autofix?expires=1787252592&installation_model_id=428187&pr_number=3902&repository=ComposioHQ%2Fcomposio&return_to=https%3A%2F%2Fgithub.com%2FComposioHQ%2Fcomposio%2Fpull%2F3902&signature=bef1dc2a149a6b593ff6bb391c1e7a204240cc7686ffa5e061ae889589fed72a"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-light.svg"><img
alt="Autofix with Codesmith"
src="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"></picture></a>
<sup>Need help on this PR? Tag <code>/codesmith</code> with what you
need. Autofix is disabled.</sup>
<!-- codesmith:autofix:disabled -->
<!-- /codesmith:footer -->
Re-lands 4e68e652 (reverted by 333d2296) and extends it:
Part 1 (re-land):
- Dedicated CLI_INSTALL_* / CLI_SETUP_* lifecycle families; 'setup' added
to KNOWN_COMMAND_TOKENS so it is no longer misattributed to
command_path='composio'
- Per-host CLI_PLUGIN_SETUP_SUCCEEDED / CLI_PLUGIN_UNINSTALL_SUCCEEDED
fired from installSetupTargets/uninstallSetupTargets
- install.sh auto-setup runs with COMPOSIO_CLI_INVOCATION_ORIGIN=installer
Part 2 (extensions, fired from inside setup.cmd.ts / setup.ts):
- CLI_SETUP_HOST_DETECTED per host after detection, with agent_host,
available, supported, host_version and normalized unsupported_reason_code
(codex_too_old | no_json_inspection | host_command_failed | unknown)
threaded through SetupTargetDetection
- CLI_PLUGIN_SETUP_FAILED per host (phase install|uninstall) tapped before
the existing mapError rethrow
- CLI_SETUP_CANCELLED (user declined confirm) and CLI_SETUP_SKIPPED
(--if-present with no supported host)
- stdout_is_tty added to the setup command properties
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
## Summary
- promote CLI multi-account selection out of the experimental feature
system so stale `multi_account: false` config can no longer disable
`execute --account`, `listen --account`, or `link --alias`
- add `proxy --account <alias|word_id|connected-account-id>` and pin the
resolved connected account into the Tool Router session
- expose account selection consistently in command help and the
generated `composio-cli` skill
- make duplicate-alias link errors explain how to select and inspect the
existing account
## Root cause
Multi-account selection was introduced behind `multi_account` in #3129.
#3163 enabled it by default for stable builds, but the command
implementations still consulted the experimental setting and the custom
help surface still omitted the selector. That left stale or explicit
`multi_account: false` config able to disable selection even though the
feature was intended to be stable. `proxy` never received equivalent
account-selection wiring.
This is an incomplete graduation of the feature rather than a recent
code regression. Existing implicit/default account selection behavior
remains unchanged; this PR does **not** require explicit selection.
Closes#3894.
## Tests
- `vitest run` in `ts/packages/cli`: **84 files passed, 785 tests
passed, 1 skipped**
- focused proxy/link/help regression suite: **26 tests passed**
- CLI source and test typechecks via `tsgo --noEmit`: passed
- `tsx ./scripts/validate-skills.ts`: passed for stable and beta builds
- Prettier and `git diff --check`: passed
## Notes
- No changeset: `@composio/cli` is excluded from Changesets; the CLI
changelog is updated directly.
- No VHS recording: this is a narrow selector-wiring/help regression
covered by command-level tests and requires a real multi-account fixture
to demo safely.
- The root Turbo typecheck entrypoint could not run in this worktree
because the local environment does not have `mise` on `PATH`; the
directly equivalent CLI source/test `tsgo` checks passed.
- GitHub CI: queued/pending at PR creation; no background CI watch
started per workspace policy.