* focus Context7 documentation queries
* narrow documentation query prompt changes
* remove focused from query prompts
* use lookup wording in query prompts
* distinguish documentation lookup from task
* allow live Pi test more time
* add prompt guidance changeset
The skill download step in `ctx7 setup` hits the git tree API on
api.github.com to enumerate a skill's files. When that host is blocked
or unreachable (while the docs host is fine), the fetch throws and setup
reports "Skill failed / fetch failed" (#2936).
Fall back to fetching the single SKILL.md directly from
raw.githubusercontent.com — the URL the docs API already resolves — so
single-file skills install even when api.github.com is not reachable.
* fix(cli): avoid shell for GitHub auth token
* fix(cli): document the shell-free constraint and harden gh token tests
Record why `gh auth token` must stay shell-free so the .cmd/.bat shim gap
is not "fixed" by re-adding `shell`, which would restore the cmd.exe
process that #2918 is about.
- reset mock implementations between tests so they stop leaking
- assert listSkillsFromGitHub's result; the tests passed green without it
- cover the GH_TOKEN fallback, which was previously untested
- reword the changeset: execSync spawned a shell on every platform, and
on Windows that shell was load-bearing rather than "unnecessary"
---------
Co-authored-by: Fahreddin Özcan <ozcanfahrettinn@gmail.com>
Fixes#2860
- Use npx ctx7@latest as the canonical CLI invocation in find-docs SKILL.md
- Add official library naming guidance matching rules/context7-cli.md
- De-emphasize global npm install as the primary workflow
- Add regression tests to keep skill and rule guidance aligned
Co-authored-by: syf2211 <syf2211@users.noreply.github.com>
* chore(deps): bump dependencies (combined dependabot updates)
Combines the safe dependabot dependency bumps into a single change:
- @modelcontextprotocol/sdk 1.25.2 -> 1.29.0 (mcp)
- undici 6.26.0 -> 8.3.0 (mcp)
- zod 4.3.5 -> 4.4.3 (mcp, tools-ai-sdk)
- commander 13.1.0 -> 15.0.0 (cli)
- ora 9.0.0 -> 9.4.0 (cli)
- dotenv 17.2.3 -> 17.4.2 (sdk, tools-ai-sdk, pi)
- @earendil-works/pi-coding-agent 0.75.5 -> 0.78.0 (pi)
eslint 9 -> 10 (#2703) is excluded: it is incompatible with the
pinned typescript-eslint v8 and breaks lint.
Verified: build, typecheck, lint, and tests pass.
* chore: add changesets for runtime dependency bumps
* fix(deps): pin undici to 7.x for Node 20 compatibility
undici 8 requires Node >=22.19.0 (it calls worker_threads.markAsUncloneable
unconditionally at module load), but CI and the release pipeline run Node 20,
which crashed the mcp test suite with 'markAsUncloneable is not a function'.
undici 7.27.0 guards that call and supports Node >=20.18.1.
* fix(sdk): avoid raw SyntaxError on non-JSON error responses
Wrap res.json() in the error path with .catch(() => ({})) so non-JSON
error bodies (HTML 502s, plain-text 429s, Cloudflare challenge pages)
fall through to res.statusText and always surface as a typed
Context7Error instead of a native SyntaxError.
Closes#1964
* chore: add changeset for sdk non-JSON error fix
* test(cli): pin home dir via HOME env instead of mocking os builtin
The storage-paths and auth-utils tests mocked the `os` module to fix
homedir, but that mock resolves inconsistently across Node versions and
worker pooling, leaking the real homedir on CI (/home/runner) and
failing 6 tests. os.homedir() reads $HOME first on POSIX, so stub HOME
(and clear XDG_* vars) for deterministic, order-independent paths with
no builtin-module mock. Also make the device-auth body assertion parse
client_id rather than matching the exact string, since hostname is
appended best-effort and varies by machine.
* fix(cli): recover library ID mangled by Git Bash on Windows
Git Bash rewrites a leading-slash argument like /facebook/react into a
Windows path under the Git install dir (C:/Program Files/Git/facebook/react),
so "ctx7 docs" rejected it as an invalid library ID. This mainly affected
users running ctx7 through Claude Code.
Detect and undo the conversion before validation, and point users at the
//owner/repo escape for install layouts that aren't auto-detected.
* chore: add changeset
* fix(cli): use XDG dirs for context files
* fix(cli): harden XDG migration and cover previews dir
- Move `generate` previews to $XDG_CACHE_HOME/context7/previews (was the
last writer recreating ~/.context7)
- Make legacy->XDG migration best-effort and fall back to reading the
legacy file so loadTokens/readUpdateState never throw or silently log out
- Split update-check read (legacy fallback) from write (always XDG target)
- Ignore relative/empty XDG_* values per the spec
- Fix non-hermetic XDG_STATE_HOME test that moved the real ~/.context7
cli-state into a temp dir; add storage-paths tests and a migration-failure
fallback test
* chore: add changeset for XDG directories
* fix(cli): enforce 0o600 on credentials after migration
rename preserves the legacy file's mode, so a credentials file that was
group/world-readable in ~/.context7 stayed readable after migrating to the
XDG path. chmod the target to 0o600 on migrate, and re-assert it after every
write (writeFileSync's mode is ignored when the file already exists).
---------
Co-authored-by: Fahreddin Özcan <ozcanfahrettinn@gmail.com>
The localhost-callback path is gone. Every install — laptop, SSH,
Codespace, Docker, CI — goes through the same boxed prompt and
verification page. Three reasons to make this the default:
- The localhost flow was broken anywhere the browser couldn't reach
127.0.0.1:52417 (SSH, Docker, Codespaces). Auto-detection via
SSH_CONNECTION / $DISPLAY was a half-fix that depended on env
vars users don't always set.
- Device flow works everywhere, has no random port-binding behavior,
and still ends in the same long-lived ctx7sk- API key.
- One UX path is simpler to support than two.
Drops the --device flag (it was the opt-in for what's now the
default). Older CLI versions (<= 0.5.0) continue to work against the
unchanged auth endpoints, so pinned installs are unaffected.
The legacy localhost machinery in utils/auth.ts is left in place
for now — nothing imports it from commands/auth.ts anymore, and a
follow-up can delete it once we're confident no rollback is needed.
* feat(cli): OAuth 2.0 device authorization flow
Adds RFC 8628 device-code login for headless / remote hosts (SSH,
Codespaces, Docker, CI) where the existing localhost-callback flow
can't work — the browser opens on the user's laptop while the
callback listener runs on the remote host, so the redirect target
is unreachable.
Device flow prints a verification URL and short code, then polls the
new /api/oauth/device/token endpoint. The user visits the URL on any
device, signs in, and approves; the CLI receives the same ctx7sk- API
key it would have gotten from the legacy flow.
- shouldUseDeviceFlow() auto-detects via SSH_CONNECTION /
SSH_CLIENT / SSH_TTY and missing $DISPLAY on Linux.
- ctx7 login --device forces it. ctx7 setup picks it up
automatically when resolveCliAuth needs to authenticate.
- pollDeviceToken returns a "transient" status for network errors
and 5xx responses so a flaky backend or Upstash blip doesn't end
the session — keeps polling until the device_code TTL elapses.
* polish(cli): boxed device-code prompt, Press-Enter, whoami success line
Tightens up the device-flow UX so it matches the patterns from gh /
stripe / wrangler:
- Wrap the user_code + verification URL in a boxen rounded box with
a title, gray border, and the code as the visual headline (green
bold, indented on its own line).
- Add a "Press Enter to open the browser, or Ctrl-C to quit..."
confirmation step in TTY mode so the user can read the code before
the browser steals focus. Skipped under --no-browser or non-TTY.
- Replace the generic "Login successful!" line with
"Logged in as <email> (<team>)" by fetching /api/dashboard/whoami
with the freshly minted token. Falls back to the old text if the
call fails.
- Tighten the mockShouldUseDeviceFlow signature in the test mock so
the spread-into-mock pattern typechecks.
No behavior change to the localhost-callback flow.
* test(cli): cover shouldUseDeviceFlow + start/poll + performDeviceLogin
auth-utils: SSH/$DISPLAY heuristics for shouldUseDeviceFlow; the
form-encoded start-device-authorization request shape and error
propagation; pollDeviceToken status mapping for each RFC 8628 code,
5xx -> transient, network error -> transient, and unknown 4xx ->
throw.
auth-commands: performDeviceLogin happy path (approved -> saveTokens
called), denied/expired return null and don't save, transient errors
keep polling instead of bailing, slow_down bumps the interval
(verified with fake timers), startDeviceAuthorization throwing exits
without polling, browser-open behavior under openBrowser=true/false.
Also covers the performLogin selector: forceDevice=true and
shouldUseDeviceFlow=true both route through performDeviceLogin
without hitting the localhost callback path.
28 new tests; suite is 235/235.
* docs(cli): tighten device-flow comments
Drop restatement; keep only WHY-bearing notes.
* fix(cli): default poll interval to 5s per RFC 8628 §3.2
The CLI was treating `interval` as required and would NaN-crash if
a future server omitted it. Spec requires clients to default to 5
when absent.
DeviceAuthorizationResponse.interval is now optional, and
performDeviceLogin uses DEFAULT_DEVICE_POLL_INTERVAL_SECONDS (5) as
the fallback. Test covers the missing-interval path.
* fix(cli): rfc 8628 spec gaps — backoff, hostname, bare verification_uri
Three small spec-compliance fixes from the §3.5 / §3.3 / §5.4 audit:
- §3.5: poll loop now bumps intervalMs by 5s on `transient` results
(network errors and 5xx) — the RFC requires unilateral backoff on
connection timeouts, and mirroring the slow_down handler is the
simplest correct response.
- §3.3: the boxed prompt now prints the bare verification_uri
alongside verification_uri_complete so screen readers / paper /
another device can still type the short form.
- §5.4: startDeviceAuthorization sends `os.hostname()` so the server
can show it on the verification page; the user can confirm the
device they're authorizing matches the one running the CLI.
Transient-backoff test rewritten with fake timers (the new +5s wait
made the old real-timer assertion blow past the 5s default timeout).
resolveMode treated --api-key as a non-interactive marker and
short-circuited to MCP, but --api-key is equally valid for CLI +
Skills mode (which authenticates skill downloads). Users who
preferred CLI mode were silently locked into MCP unless they
also passed --cli.
Remove options.apiKey from the auto-MCP OR-chain. --mcp / --cli /
--stdio / --oauth / -y still skip the prompt; --api-key alone now
falls through to the interactive mode picker.
* fix(cli): wire --antigravity and remove broken --universal in setup
The setup command advertised --universal and --antigravity flags but
neither was wired through getSelectedAgents, so passing them silently
fell back to auto-detection and wrote to the wrong directory (see #2695).
Remove --universal from setup entirely, and add a full Antigravity
SetupAgent config: skills under .agent/skills, MCP config at
~/.gemini/antigravity/mcp_config.json with serverUrl for HTTP, and
detection of .agent or ~/.gemini/antigravity.
* fix(cli): align antigravity setup with official Google docs
After verifying against Google Codelabs / Google Cloud Community
docs, correct the Antigravity config:
- MCP global path: ~/.gemini/config/mcp_config.json (Antigravity 2.0
shared config, replacing the older ~/.gemini/antigravity/ path
which an outdated github/github-mcp-server install guide cited).
- HTTP key: httpUrl (Gemini convention; antigravity is Gemini-based).
The previous serverUrl was sourced from the same outdated guide.
- Rule: append to GEMINI.md / ~/.gemini/GEMINI.md (Antigravity reads
Gemini-family rules, not a vendor-specific file).
- Project MCP: none documented; projectPaths is empty and setupAgent
/ remove falls back to globalPaths so --project --mcp still writes
to the correct location.
Skills stay at .agent/skills to keep the in-repo IDE_PATHS convention
consistent across setup, skill, and generate commands.
* chore(cli): move Antigravity to 5th in agent selection list
Match the natural ordering users expect in the checkbox prompt:
Claude Code, Cursor, OpenCode, Codex, Antigravity, Gemini CLI.
* fix(cli): antigravity HTTP key is serverUrl, not httpUrl
Antigravity rejects the entry with "serverURL or command must be
specified" when given httpUrl. Switch the HTTP entry back to
serverUrl (the github-mcp-server install guide had this right even
though its file path was outdated).
Also tighten the empty-projectPaths fallback in remove.ts: it
incorrectly leaked global state into project-scope detection,
making `remove --project` report Antigravity whenever a global
~/.gemini/config/mcp_config.json existed. Project-scope detect/
remove now no-ops for agents with no project-level MCP, while
setup still falls back to the global path so --project --mcp
--antigravity writes to the file Antigravity actually reads.
* fix(cli): wire --antigravity into the remove command
Symmetric to the setup fix: --antigravity was missing from
UninstallOptions and getSelectedAgents, so users had no CLI path
to undo a `ctx7 setup --antigravity` install.
* Add JSON output for skills list
* test(cli): resolve tempDir via realpath for macOS compatibility
On macOS, os.tmpdir() returns /var/folders/... but process.cwd()
reports the symlink-resolved /private/var/folders/... after chdir.
The JSON output uses the resolved cwd, so the test assertion mismatched
the unresolved tempDir on macOS.
* chore: add changeset for skills list --json
---------
Co-authored-by: Fahreddin Özcan <ozcanfahrettinn@gmail.com>
* fix(cli): declare @inquirer/core as direct dependency
selectOrInput.ts imports from @inquirer/core directly, but the package
was only resolvable as a transitive of @inquirer/prompts. Under pnpm's
isolated linker this fails with ERR_MODULE_NOT_FOUND at startup.
Fixes#2651
* chore: add changeset
* fix(cli): validate skill names to prevent path traversal on install
Adds boundary validation and containment checks so a remote SKILL.md
with a malicious name field (e.g. `name: ..`) cannot escape the skills
root during `ctx7 skills install`. Previously, the value flowed from
parseSkillFrontmatter to installSkillFiles unchecked, and the existing
traversal guard only verified files stayed inside the attacker-chosen
directory rather than the real skills root, enabling arbitrary file
writes outside `.claude/skills` (e.g. `.claude/settings.json` for
hook-driven RCE). symlinkSkill had the same trust issue and could
`rm(recursive: true)` arbitrary directories.
* chore: add changeset for skill name validation
* chore: soften changeset wording
readJsonConfig only caught file-read errors, not JSON.parse errors.
During `ctx7 remove`, the detector iterates every agent's well-known
config path; an unparseable JSON file at any of them (e.g. a
hand-edited ~/.claude.json) crashed the command with an unhandled
SyntaxError before it could do anything.
Wrap the readJsonConfig call in hasMcpConfig with a try/catch that
logs a warning naming the path and parse error and skips that agent.
Keep readJsonConfig itself strict so write paths in setup and
uninstallMcp continue to surface failures via their existing handling.
* fix(cli): support Windows backslash in skill installation path check
* chore(cli): add changeset for Windows path check fix
* style(cli): format Windows path guard
---------
Co-authored-by: Fahreddin Özcan <ozcanfahrettinn@gmail.com>
* feat(cli): add Gemini CLI support to setup command
Adds Gemini CLI as a supported agent in `ctx7 setup`. Configures MCP
server in `.gemini/settings.json` using `httpUrl` (Gemini's HTTP
streaming transport), appends rules to `GEMINI.md`, and installs skills
to `.gemini/skills/`. Also adds a permission fix tip when skill
installation fails with EACCES.
* chore: add changeset for Gemini CLI setup