Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .cursor-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "onecli",
"owner": {
"name": "OneCLI"
},
"metadata": {
"description": "OneCLI gateway plugins for Cursor, Codex, and Claude Code."
},
"plugins": [
{
"name": "onecli",
"source": "plugins/cursor",
"description": "Route agent HTTPS traffic through the OneCLI gateway with injected credentials."
}
]
}
22 changes: 19 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,30 @@

Connect AI coding agents to external APIs without managing credentials. The OneCLI gateway injects stored credentials into outbound requests automatically, so you don't need API keys in your environment or OAuth flows in your terminal.

This repo ships the OneCLI gateway plugin for two agent platforms from a single shared codebase:
This repo ships the OneCLI gateway plugin for three agent platforms from a single shared codebase:

| Plugin | Platform | Path |
|--------|----------|------|
| **onecli** for Claude Code | [Claude Code](https://claude.com/claude-code) | [`plugins/claude/`](plugins/claude/) |
| **onecli** for Codex | [OpenAI Codex](https://developers.openai.com/codex) | [`plugins/codex/`](plugins/codex/) |
| **onecli** for Cursor | [Cursor](https://cursor.com) | [`plugins/cursor/`](plugins/cursor/) |

**Supported services**: GitHub, Gmail, Google Calendar, Google Drive, Google Docs, Google Sheets, Jira, Confluence, AWS, Datadog, Notion, Cloudflare, Todoist, Outlook, Microsoft Word, YouTube, and more.

## How it works

All HTTPS traffic from the agent routes through the OneCLI gateway (`HTTPS_PROXY`), which intercepts requests and injects the right credentials (OAuth tokens, API keys, AWS SigV4 signatures). If a service isn't connected yet, the gateway returns a `connect_url` the agent shows you. Policy rules (block, rate limit, manual approval) are enforced at the gateway on every request.

The two plugins share the same runtime but activate differently, matching what each platform's hooks can do.
The three plugins share the same runtime but activate differently, matching what each platform's hooks can do.

On Claude Code, the `SessionStart` hook fetches the gateway config once, writes live exports to `~/.onecli/env.sh`, and wires `BASH_ENV` so every Bash command picks them up. A `SessionEnd` hook cleans up.

On Codex, hooks run as child processes and cannot mutate the session environment, and there is no `SessionEnd` event. The `SessionStart` hook therefore writes `~/.onecli/env.sh` as a **credential-free loader**; a conservative `PreToolUse` hook auto-sources it for outbound Bash commands (such as `curl`, `gh`, `git push`, `npm install`), fetching fresh gateway exports per command via `bin/onecli-codex-env.mjs`. Cleanup is an explicit skill (`onecli-cleanup`), deliberately not wired to the turn-scoped `Stop` event.

On Cursor, the `sessionStart` hook writes the same loader, fetches gateway config when an API key exists, and returns session-scoped `env` exports. A conservative `preToolUse` hook (Shell matcher) auto-sources the loader as a fallback when session env is not inherited. `sessionEnd` cleans up automatically.

See [`docs/hook-activation.md`](docs/hook-activation.md) for why activation differs per platform, why `preToolUse` uses a command allowlist (shared with Codex), and the deprecation path toward session-only activation.

## Install on Claude Code

From the Claude Code Directory: **Customize → Directory → Plugins**, search **OneCLI**, click **Install**. Or from the marketplace in this repo:
Expand All @@ -41,17 +46,28 @@ codex plugin add onecli@onecli

Start a new thread, then invoke the `onecli-setup` skill (`@onecli:onecli-setup`). See [`plugins/codex/README.md`](plugins/codex/README.md).

## Install on Cursor

```bash
ln -sf "$(pwd)/plugins/cursor" ~/.cursor/plugins/local/onecli
```

Reload Cursor, then invoke the `onecli-setup` skill. See [`plugins/cursor/README.md`](plugins/cursor/README.md).

## Repo layout

```
src/ TypeScript sources (single source of truth)
shared/runtime.mts gateway config, key resolution, CA bundle, quoting, probing
claude/ Claude Code hooks
codex/ Codex hooks + env helper
cursor/ Cursor hooks + env helper
plugins/
claude/ self-contained Claude Code plugin (built hooks committed)
codex/ self-contained Codex plugin (built hooks committed)
cursor/ self-contained Cursor plugin (built hooks committed)
.claude-plugin/marketplace.json Claude Code marketplace → ./plugins/claude
.cursor-plugin/marketplace.json Cursor marketplace → ./plugins/cursor
.claude-plugin/plugin.json root compatibility shim (see below)
hooks/hooks.json hooks for the root shim
.agents/plugins/marketplace.json Codex marketplace → ./plugins/codex
Expand All @@ -67,7 +83,7 @@ Both platforms copy only the plugin directory into their install cache, so each
```bash
npm install
npm run typecheck # tsc over src/
npm run build # tsup: src/ → plugins/claude/hooks, plugins/codex/{hooks,bin}
npm run build # tsup: src/ → plugins/{claude,codex,cursor}/{hooks,bin}
npm run test # python3 tests/test_workflows.py
```

Expand Down
90 changes: 90 additions & 0 deletions docs/hook-activation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Hook activation across platforms

Why OneCLI uses different hook strategies on Claude Code, Codex, and Cursor — and where `preToolUse` / `PreToolUse` fits today.

## Summary

| Platform | Primary activation | `preToolUse` role |
| -------- | ------------------ | ----------------- |
| **Claude Code** | `sessionStart` + `BASH_ENV` | None — every Bash subprocess auto-sources `~/.onecli/env.sh` |
| **Codex** | `sessionStart` loader + `PreToolUse` rewrite | Required — hooks cannot persist session env |
| **Cursor** | `sessionStart` env + `preToolUse` rewrite | Fallback — session env exists but is not reliably inherited by all shells/subagents |

All platforms route HTTPS through `HTTPS_PROXY` once the gateway env is active. Credentials never live in the agent context.

---

## Why three different models?

Each agent platform exposes different hook capabilities:

**Claude Code** can write live exports to `~/.onecli/env.sh` and set `BASH_ENV` in `~/.claude/settings.json`. Every Bash invocation sources the file automatically. No per-command hook is needed.

**Codex and Cursor** run hooks as child processes. They cannot permanently mutate the parent session environment the way Claude's `BASH_ENV` wiring does. Instead:

1. `sessionStart` writes a **credential-free loader** at `~/.onecli/env.sh`.
2. Sourcing the loader runs `onecli-*-env.mjs`, which fetches fresh gateway exports from OneCLI Cloud for that command.
3. `preToolUse` / `PreToolUse` rewrites selected Shell/Bash commands to prefix:
`. "$HOME/.onecli/env.sh" && <original>`

**Cursor-specific:** `sessionStart` also returns session-scoped `env` (including `HTTPS_PROXY`). When Cursor propagates that env to all agent shells, gateway routing works without rewrites. In practice, parent shells and some subagent contexts still miss it — so `preToolUse` remains the reliable path (verified via `api.github.com/rate_limit`: `60` = direct, `~11400` = gateway).

Implementation: [`src/codex/pre-tool-use.mts`](../src/codex/pre-tool-use.mts) and [`src/cursor/pre-tool-use.mts`](../src/cursor/pre-tool-use.mts) share the same `shouldRewrite()` logic today (allowlist, not yet extracted to `src/shared/`).

---

## Why the conservative command allowlist?

Sourcing the loader is not free — it calls OneCLI Cloud on each rewrite. The allowlist limits rewrites to commands likely to hit the network:

- HTTP clients with URLs: `curl`, `wget`, …
- Service CLIs: `gh`, network `aws` / `git` / `terraform` / package-manager subcommands
- Interpreters when the command line contains a URL: `node`, `python`, `npx`, …

Local-only commands are skipped (`git status`, `terraform fmt`, `aws configure`, …). Tests in `tests/test_workflows.py` lock this contract.

**Gap:** Unknown network CLIs (`stripe`, `flyctl`, `kubectl`, …) are not auto-rewritten unless they match URL heuristics. Agents can still prefix manually:

```bash
. ~/.onecli/env.sh && <command>
```

Skills document this fallback (`onecli-gateway`).

---

## Deprecation path: session-only activation

**Goal:** Rely on `sessionStart` (or platform-equivalent persistent env injection) and remove `preToolUse` / `PreToolUse` rewrites.

| Milestone | Cursor | Codex |
| --------- | ------ | ----- |
| **Today** | Hybrid: session env + conservative `preToolUse` | Loader + conservative `PreToolUse` |
| **Cursor step 1** | Cursor reliably applies `sessionStart` `env` to all Shell/subagent processes | — |
| **Cursor step 2** | Drop `preToolUse`; keep loader + skills for manual edge cases | — |
| **Codex blocker** | — | No session env mutation API; needs platform change or `onecli run` wrapper |
| **End state** | Claude-like: one fetch at session start, universal proxy env | Same, if Codex adds session env or official `BASH_ENV` equivalent |

`preToolUse` is **intentionally a compatibility layer**, not the long-term design for Cursor. Remove it once session env propagation is verified across parent agents, subagents, and background shells.

Signals that step 1 is done:

- `HTTPS_PROXY` set in shell without rewrite markers (`ONECLI_CURSOR_AUTOSOURCED`)
- `python3 tests/verify_cursor_agent_gateway.py` passes in parent agent Shell (not only subagents)
- No regression in OneCLI dashboard Activity for plain `curl` / `gh` calls

---

## Future options (not implemented)

Documented for later if the allowlist becomes painful to maintain:

1. **Shared module** — Extract `shouldRewrite()` to `src/shared/pre-tool-use.mts`; thin Codex/Cursor wrappers for I/O format only.

2. **Inverse blocklist** — Rewrite all Shell commands except known-local ones (`ls`, `git status`, `npm test`, …). Broader coverage; more loader invocations on local work.

3. **Cached loader exports** — Cache gateway config for N seconds per session inside `onecli-*-env.mjs` so default-open rewrite (option 2) does not hit OneCLI Cloud on every command.

4. **Platform wrapper** — `onecli run cursor` / `onecli run codex` exports gateway env to the whole process tree (same short-circuit as today when `HTTPS_PROXY` is already a OneCLI proxy).

None of these are required for the initial Cursor plugin merge. The allowlist matches Codex behavior and is covered by tests.
5 changes: 3 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"scripts": {
"build": "tsup",
"typecheck": "tsc -p tsconfig.json",
"test": "python3 tests/test_workflows.py"
"test": "python3 tests/test_workflows.py",
"test:live-cursor": "python3 tests/test_cursor_live_e2e.py"
},
"devDependencies": {
"@types/node": "^22.0.0",
Expand Down
2 changes: 2 additions & 0 deletions plugins/codex/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ Codex hooks run as child processes, so they cannot mutate the Codex session envi
. ~/.onecli/env.sh && curl -s "https://api.github.com/user"
```

See [`docs/hook-activation.md`](../../docs/hook-activation.md) for why this allowlist exists, how it compares to Claude's `BASH_ENV` model, and future options.

If the helper fails (no API key, OneCLI Cloud unreachable, proxy down), the command still runs without the gateway, and the reason is printed to stderr.

Codex documents `SessionStart`, `PreToolUse`, `SubagentStart`/`SubagentStop`, and turn-scoped `Stop`, but no true `SessionEnd`. Cleanup is therefore not automatic: use the `onecli-cleanup` skill (or `hooks/session-end.mjs`) for explicit deactivation or uninstall. Wiring it to `Stop` would remove the gateway loader after every turn.
Expand Down
16 changes: 16 additions & 0 deletions plugins/cursor/.cursor-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "onecli-local",
"owner": {
"name": "OneCLI"
},
"metadata": {
"description": "Local marketplace for the OneCLI Cursor gateway plugin."
},
"plugins": [
{
"name": "onecli",
"source": ".",
"description": "Route agent HTTPS traffic through the OneCLI gateway with injected credentials."
}
]
}
25 changes: 25 additions & 0 deletions plugins/cursor/.cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "onecli",
"displayName": "OneCLI Gateway",
"version": "0.2.4",
"description": "Connect Cursor to external APIs through the OneCLI gateway without local service credential management.",
"author": {
"name": "OneCLI",
"email": "plugins@onecli.sh"
},
"homepage": "https://onecli.sh",
"repository": "https://github.com/onecli/onecli-plugin",
"license": "Apache-2.0",
"keywords": [
"onecli",
"gateway",
"proxy",
"credentials",
"cursor",
"cursor-plugin"
],
"category": "developer-tools",
"skills": "./skills/",
"rules": "./rules/",
"hooks": "./hooks/hooks.json"
}
76 changes: 76 additions & 0 deletions plugins/cursor/INSTALL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Install OneCLI for Cursor (local repo)

For a step-by-step guide, see [CURSOR_SETUP.md](./docs/CURSOR_SETUP.md). Screenshots are in [PR #7](https://github.com/onecli/onecli-plugin/pull/7).

## 1. Build the plugin

From the `onecli-plugin` repo root:

```bash
npm install
npm run build
```

## 2. Add via Customize → Plugins → + Add

1. Open **Customize** (left sidebar) → **Plugins** tab → **+ Add**
2. Select this folder:

```
onecli-plugin/plugins/cursor
```

Cursor expects `.cursor-plugin/marketplace.json` in the folder you pick (now included).

3. Install the **onecli** plugin from the local marketplace entry.

## 3. Enable third-party extensibility

In **Cursor Settings → Features**, turn on:

**Include third-party Plugins, Skills, and other configs**

Without this, plugin hooks may not register (skills/rules can still load).

## 4. Reload and start a new Agent session

- `Cmd+Shift+P` → **Developer: Reload Window**
- Open a **new** Agent composer chat (so `sessionStart` runs)

## 5. Configure OneCLI (once)

Invoke the **onecli-setup** skill, or ensure `~/.onecli/credentials/api-key` exists.

## 6. Verify hooks are loaded

**Settings → Hooks** should list:

- `sessionStart` → `node ./hooks/session-start.mjs`
- `preToolUse` (Shell) → `node ./hooks/pre-tool-use.mjs`
- `sessionEnd` → `node ./hooks/session-end.mjs`

If plugin hooks are missing (known Cursor bug), use the project-level fallback in your workspace:

```bash
# from secrets-leak-protection repo root
./onecli-plugin/plugins/cursor/scripts/install-project-hooks.sh
```

## 7. Verify gateway routing (subagent-safe)

Ask any Agent to run **only**:

```bash
curl -s https://api.github.com/rate_limit | python3 -c "import json,sys; print(json.load(sys.stdin)['resources']['core']['limit'])"
```

**Expected when gateway is active:** `11400` (or similar high GitHub App limit)
**Direct / unauthenticated:** `60`

Then check **OneCLI dashboard → Activity** for a new `GET api.github.com/rate_limit` row.

Or run the automated check from repo root:

```bash
python3.12 onecli-plugin/tests/verify_cursor_agent_gateway.py
```
56 changes: 56 additions & 0 deletions plugins/cursor/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# OneCLI Plugin for Cursor

Connect Cursor Agent to external APIs through the OneCLI gateway without managing service credentials locally. The gateway injects stored credentials (OAuth tokens, API keys, AWS SigV4 signatures) at the proxy boundary.

## Installation

See [INSTALL.md](./INSTALL.md) for install steps and [docs/CURSOR_SETUP.md](./docs/CURSOR_SETUP.md) for the walkthrough (screenshots in [PR #7](https://github.com/onecli/onecli-plugin/pull/7)).

Quick symlink fallback:

```bash
ln -sf "$(pwd)/plugins/cursor" ~/.cursor/plugins/local/onecli
```

## Runtime behavior

Cursor hooks use a hybrid activation model (see [`docs/hook-activation.md`](../../docs/hook-activation.md) for the full rationale and deprecation path):

- **`sessionStart`** writes `~/.onecli/env.sh` as a non-secret loader, fetches gateway config when an API key exists, and returns session-scoped `env` exports plus `additional_context`.
- **`preToolUse` (Shell)** conservatively rewrites outbound network commands to auto-source the loader when session env is not inherited (same allowlist as Codex — not Cursor-specific).
- **`sessionEnd`** removes the loader file automatically.

`preToolUse` is a **compatibility fallback**. The intended end state is session-only activation once Cursor reliably applies `sessionStart` env to all agent shells and subagents.

Manual sourcing remains available:

```bash
. ~/.onecli/env.sh && curl -s "https://api.github.com/user"
```

If the helper fails (no API key, OneCLI Cloud unreachable, proxy down), the command still runs without the gateway and the reason is printed to stderr.

## Skills

| Skill | Purpose |
| ----- | ------- |
| `onecli-setup` | Configure the OneCLI API key |
| `onecli-status` | Show gateway status and connected services |
| `onecli-gateway` | Core gateway usage rules for the agent |
| `onecli-providers` | Reference of supported services and endpoints |
| `integration-architect` | Design multi-service API workflows |
| `onecli-cleanup` | Explicit deactivation / uninstall cleanup |

## Development

Hook scripts (`hooks/*.mjs`) and the env helper (`bin/onecli-cursor-env.mjs`) are built artifacts. Edit the TypeScript sources in `../../src/cursor/` and run `npm run build` from the repo root.

```bash
npm install
npm run build
npm run test
```

## License

Apache-2.0
Loading