Skip to content
Merged
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
9 changes: 5 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
go: ["1.26.6"]
go: ["1.27.1"]
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
Expand All @@ -31,17 +31,18 @@ jobs:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version: "1.26.6"
go-version: "1.27.1"
- run: go vet ./...
- run: go install honnef.co/go/tools/cmd/staticcheck@latest && staticcheck ./...
# Pinned: an unpinned @latest lets an upstream release turn main red with no local change.
- run: go install honnef.co/go/tools/cmd/staticcheck@v0.8.1 && staticcheck ./...

build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version: "1.26.6"
go-version: "1.27.1"
- run: |
GOOS=linux GOARCH=amd64 go build -o /dev/null .
GOOS=darwin GOARCH=arm64 go build -o /dev/null .
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
fetch-depth: 0
- uses: actions/setup-go@v7
with:
go-version: "1.26.6"
go-version: "1.27.1"
- uses: goreleaser/goreleaser-action@v7
with:
version: "~> v2"
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ jobs:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version: "1.26.6"
- run: go install golang.org/x/vuln/cmd/govulncheck@latest
go-version: "1.27.1"
- run: go install golang.org/x/vuln/cmd/govulncheck@v1.7.0
- run: govulncheck ./...

sast:
Expand All @@ -34,8 +34,8 @@ jobs:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version: "1.26.6"
- run: go install github.com/securego/gosec/v2/cmd/gosec@latest
go-version: "1.27.1"
- run: go install github.com/securego/gosec/v2/cmd/gosec@v2.29.0
- run: gosec ./...

secrets:
Expand Down
6 changes: 3 additions & 3 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ builds:
goarch: arm64

archives:
- format: tar.gz
- formats: [tar.gz]
name_template: "connect-cli_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
format_overrides:
- goos: windows
format: zip
formats: [zip]

checksum:
name_template: "checksums.txt"
Expand All @@ -45,7 +45,7 @@ brews:
token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}"
name: connect-cli
homepage: "https://github.com/redpine-ai/connect-cli"
description: "CLI for the Connect platform — MCP client for AI agents"
description: "Redpine CLI — search Redpine Connect and call MCP tools from the terminal"
license: "Apache-2.0"
install: |
bin.install "redpine"
Expand Down
95 changes: 61 additions & 34 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,58 +1,85 @@
# connect-cli

`redpine` — MCP client CLI for [Redpine Connect](https://app.redpine.ai). Search documents, list
collections, and call upstream MCP tools from the terminal or from an agent. Built for agents
first: human-readable output in a TTY, JSON envelope (`{"status": "ok", "data": ...}` /
`{"status": "error", "error": {...}}`) when piped or with `--json`.
`redpine` — command-line client for [Redpine Connect](https://docs.redpine.ai). Search
licensed collections, preview and unlock results, list collections, check balance, and call
upstream MCP tools from a terminal or from an agent. Agents first: JSON envelope
(`{"status": "ok", "data": ...}` / `{"status": "error", "error": {...}}`) whenever stdout is
not a terminal or `--json` is passed; human-readable in a TTY or with `--pretty`.

## What it does

- `redpine auth login` / `redpine auth set-key` — OAuth browser flow or API key auth
(`internal/config`, `internal/command/auth`)
- `redpine search` — full-text/semantic search against the public Connect API, with a
filter DSL: repeatable `--filter key=value` (comma-separated = any-of, `!=` to exclude,
`>=`/`<=` for ranges) or `--filter-json` for full OR/nesting (`internal/command/search`)
- `redpine collections list` — list searchable collections
- `redpine tools list` / `redpine tools call` — discover and invoke upstream MCP tools,
designed for pipe-chaining between agent tool calls (`internal/mcp`)
- `redpine whoami`, `redpine update` — session/version introspection
- `redpine auth login` / `auth set-key` / `auth logout` / `auth status` — OAuth browser flow
or API key auth (`internal/config`, `internal/command/auth`). `auth status` is `whoami`.
- `redpine search <collection> <query>` — MCP `search` tool, billed per result. Filter DSL:
repeatable `--filter key=value` (comma-separated = any-of, `!=` to exclude, `>=`/`<=` for
ranges) or `--filter-json` for full OR/nesting (`internal/command/search`).
- `redpine preview <collection> <query>` — MCP `preview` tool wrapping the same search
arguments (`search.BuildSearchArgs`): free teasers plus the price, returns a `queryId`.
- `redpine confirm <queryId> [resultId...]` (alias `unlock`) — MCP `confirm` tool. This is
the call that bills.
- `redpine collections` (also `collections list`) — MCP `list_collections`.
- `redpine balance` — MCP `get_balance`, free.
- `redpine tools list` / `tools info` / `tools call` — MCP `find-tools`, `inspect-tool`, and
a direct `tools/call`; built for pipe-chaining between agent tool calls (`internal/mcp`,
`internal/command/tools`).
- `redpine whoami` — token source and type; labels `sk_test_` keys as sandbox.
- `redpine docs [topic]` — opens `docs.redpine.ai/docs/<topic>`; short names such as `auth`,
`sdk`, `preview` are aliased (`internal/command/docs`).
- `redpine update` — self-update from GitHub releases, verified against `checksums.txt`.
A newer release is a one-line stderr notice on other commands, never a block.

Commands live under `internal/command/<name>`; the MCP transport/client lives in
`internal/mcp`; auth/session config in `internal/config`.
`internal/mcp`; auth/session config in `internal/config`; the factory that wires them in
`internal/factory`.

## Wire facts an agent must not break

- Everything is MCP streamable HTTP: `POST {server}/mcp`, `DELETE {server}/mcp`. No REST.
Protocol version `mcp.ProtocolVersion` (`2025-03-26`), sent in `initialize` and as the
`MCP-Protocol-Version` header. `Accept: application/json, text/event-stream`,
`User-Agent: redpine-cli/<version>`, 120 s request timeout (`internal/mcp/transport.go`).
- Session id round-trips in `Mcp-Session-Id`, cached per server URL under `os.TempDir()`.
- Config resolution order, identical for the token and the server URL: flag, then
`REDPINE_API_KEY` / `REDPINE_BASE_URL`, then the legacy `CONNECT_API_KEY` /
`CONNECT_SERVER_URL`, then keyring / config file. The SDKs use the same names and order;
keep them in step (`internal/config/config.go`, `auth.go`).
- `clientInfo.name` in `initialize` is `connect-cli`. The server keys client detection off
it; do not rename without changing connect-api's `mcp/client_detection.py`.
- `find-tools --format json` output has a balance banner appended after the JSON; the client
decodes with `json.NewDecoder(...).Decode`, which tolerates trailing text. Do not switch to
`json.Unmarshal`.

## Install

```bash
brew install redpine-ai/tap/connect-cli
```

Formula lives in `redpine-ai/homebrew-tap`, published automatically by GoReleaser on
every tagged release (see `.goreleaser.yml`).
Formula lives in `redpine-ai/homebrew-tap`, published automatically by GoReleaser on every
tagged release (see `.goreleaser.yml`; note GoReleaser v2 spells the archive key `formats:`).

## Releases

Push a `v*` tag → `.github/workflows/release.yml` runs GoReleaser → builds
linux/darwin/windows (amd64/arm64, no windows/arm64) → GitHub release + Homebrew tap update.
Never hand-edit the tap repo.
linux/darwin/windows (amd64/arm64, no windows/arm64) → GitHub release with `checksums.txt`
+ Homebrew tap update. Never hand-edit the tap repo. `redpine update` refuses a release
that has no `checksums.txt`.

## CI

`.github/workflows/ci.yml` runs `test`/`lint`/`build` jobs. `test` and `build` run on a
2-OS matrix (`ubuntu-latest`, `macos-latest`) against Go `1.26`; `lint` (staticcheck) runs
ubuntu-only. `.github/workflows/security.yml` runs govulncheck, gosec, and gitleaks on every
PR, on push to `main`, and weekly.
`.github/workflows/ci.yml` runs `test`/`lint`/`build`. `test` and `build` run on a 2-OS
matrix (`ubuntu-latest`, `macos-latest`) against Go `1.27.1` (also the `go` directive in
`go.mod`); `lint` (staticcheck, pinned) runs ubuntu-only. `.github/workflows/security.yml`
runs govulncheck, gosec (both pinned) and gitleaks on every PR, on push to `main`, and
weekly. Run `gofmt -l .` before pushing; CI does not, reviewers will.

See `CONTRIBUTING.md` for build/test/lint commands and the PR checklist.

## Status (2026-08-18)

- Module: `github.com/redpine-ai/connect-cli`, binary name `redpine` — confirmed `go.mod:1-3`,
`.goreleaser.yml` (`binary: redpine`).
- Go toolchain pinned at `go 1.26.6` in `go.mod` (bumped from `1.26.4` this session to clear a
govulncheck failure — GO-2026-6218/6090/6089/5972/5026, all fixed in `go1.26.6`).
- `--filter`/`--filter-json` on `redpine search` shipped recently (commit `7444758`,
"feat(search): add --filter and --filter-json to the search command").
- No `CONTRIBUTING.md` existed before RED-1336 (this session added it alongside this file).
- License file (`LICENSE`) is Apache 2.0, but `.goreleaser.yml`'s Homebrew formula declares
`license: "MIT"` — pre-existing mismatch, not touched by this session; flag before relying
on either for compliance purposes.
## Known gaps

- `search` and `preview` take one collection. The MCP `search` tool has no `collections`
(multi-collection) field yet, so the CLI cannot offer it until the server does.
- `include_figures` is not exposed: the CLI renders only `text` content blocks, so image
blocks would be silently dropped.
- Assisted search and the quota endpoint are REST-only on the server; the CLI has no MCP
route to them.
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Prerequisites

- Go 1.26.x (see the `go` directive in `go.mod` for the minimum patch version)
- Go 1.27.x (see the `go` directive in `go.mod` for the minimum patch version)
- `goreleaser` (only needed if you want to test a release build locally; CI handles real releases)

## Build
Expand All @@ -26,7 +26,7 @@ go test ./... -race
go vet ./...
```

CI (`ci.yml`) runs these on a 2-OS matrix (`ubuntu-latest`, `macos-latest`) against Go `1.26`.
CI (`ci.yml`) runs these on a 2-OS matrix (`ubuntu-latest`, `macos-latest`) against Go `1.27`.

## Lint

Expand Down
84 changes: 57 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,39 @@
# Redpine CLI

MCP client for [Redpine Connect](https://app.redpine.ai). Search documents, list collections, and call upstream MCP tools from the terminal.
`redpine` is the command-line client for [Redpine Connect](https://docs.redpine.ai): search licensed research collections, preview results before paying for them, and call upstream MCP tools from a terminal or an agent.

Built for agents — JSON output by default when piped, human-readable in the terminal.
Built for agents: JSON on a pipe, human-readable in a terminal, one envelope and one set of exit codes everywhere.

## Install

```bash
brew install redpine-ai/tap/connect-cli
```

## Usage
Binaries for Linux, macOS and Windows are on the [releases page](https://github.com/redpine-ai/connect-cli/releases). `redpine update` upgrades a release-page install in place and verifies the download against the release checksums.

## Authenticate

```bash
redpine auth login # browser-based OAuth
redpine auth set-key sk_live_... # or store an API key
export REDPINE_API_KEY=sk_test_... # or use the environment (sandbox keys work too)
redpine whoami
```

A sandbox key (`sk_test_`) returns synthetic fixtures at no cost; `whoami` says so.

## Search

```bash
# authenticate
redpine auth login # browser-based OAuth
redpine auth set-key sk_live_... # or use an API key
# search a collection (billed per result)
redpine search corpus "how does authentication work"
redpine search corpus "rate limiting" --limit 5

# search
redpine search "how does authentication work"
redpine search "rate limiting" --collection api-docs --limit 5
# preview for free, then pay only for what you want
redpine preview corpus "crispr delivery"
redpine confirm qry_a1b2c3d4e5f6 # unlock every previewed result
redpine confirm qry_a1b2c3d4e5f6 abc123 def456 # unlock only these

# filter — repeatable; key=value, key!=value to exclude, key>=N for ranges
redpine search corpus "crispr" --filter issn=1664-302X
Expand All @@ -32,49 +46,65 @@ redpine search corpus "crispr" --filter 'journal_metric.2yr_mean_citedness>=5'
redpine search corpus "crispr" \
--filter-json '{"or":[{"field":"issn","eq":"1664-302X"},{"field":"issn","eq":"1932-6203"}]}'

# collections
redpine collections list
# what can I search, and what do I have left?
redpine collections
redpine balance
```

`preview` takes the same `--limit`, `--filter` and `--filter-json` as `search`. Filters are documented at [docs.redpine.ai/docs/filtering](https://docs.redpine.ai/docs/filtering).

# upstream MCP tools
## MCP tools

```bash
redpine tools list
redpine tools info analytics--run_query
redpine tools call analytics--run_query query="SELECT * FROM events" limit=10

# pass JSON input (useful for agents)
echo '{"query": "test"}' | redpine tools call analytics--run_query
redpine tools call analytics--run_query --input '{"query": "test"}'

# pipe chaining auto-wires ids between calls
redpine tools call media--create_workspace --input '{"filter":{"query":"X"}}' \
| redpine tools call media--daily_briefing
```

## Output

Terminal (TTY) gets human-readable output. Pipes and scripts get JSON.
A terminal gets human-readable output. A pipe or a script gets the JSON envelope.

```bash
# automatic — JSON when piped
redpine search "query" | jq '.data'

# force JSON in terminal
redpine search "query" --json

# force human-readable in pipes
redpine search "query" --pretty
redpine search corpus "query" | jq '.data' # JSON, automatically
redpine search corpus "query" --json # force JSON in a terminal
redpine collections --pretty | less # force human-readable in a pipe
```

JSON responses follow a consistent envelope:

```json
{"status": "ok", "data": { ... }}
{"status": "error", "error": {"code": "...", "message": "...", "suggestions": [...]}}
{"status": "error", "error": {"code": "...", "message": "...", "hint": "...", "suggestions": [...]}}
```

Exit codes: `0` success, `1` error, `2` auth, `3` bad input, `4` server error.

## Other commands

```bash
redpine docs # open docs.redpine.ai
redpine docs filtering # open a page; short names like auth, sdk, preview work too
redpine completion zsh # shell completion (Homebrew installs it for you)
redpine update # upgrade a release-page install; Homebrew users: brew upgrade connect-cli
```

## Environment variables

| Variable | Description |
|----------|-------------|
| `CONNECT_API_KEY` | API key (skips `redpine auth login`) |
| `CONNECT_SERVER_URL` | Server URL override |
| `NO_COLOR` | Disable colored output |
| `REDPINE_API_KEY` | API key; skips `redpine auth login`. `CONNECT_API_KEY` is still read as a fallback |
| `REDPINE_BASE_URL` | Server URL override. `CONNECT_SERVER_URL` is still read as a fallback |
| `NO_COLOR` | Disable coloured output |
| `CLICOLOR_FORCE` | Force coloured output |

The same two names configure the [SDKs](https://docs.redpine.ai/docs/sdks), so one shell setup serves both.

## License

Expand Down
Loading