You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
## Problem
`pc login` was unusable in agentic contexts:
- The OAuth URL was printed as ANSI-styled prose on `stdout` (`Visit
https://... to authorize the CLI.`), making it impossible for agents to
reliably extract the URL from the output.
- In non-interactive contexts, the command attempted to read from
`stdin` unnecessarily.
- There was no structured output mode — no `--json` flag, no
machine-readable URL emission.
- When already authenticated, the command returned nothing to `stdout`,
giving agents no way to detect the existing session.
- All human-readable output (prompts, status messages, hints) were going
to `stdout`, mixing with any data output that would be relevant for
agent / machine interaction.
## Solution
### Auto-detected JSON output
When `stdout` is not a TTY (machine / agent capturing output), JSON mode
is enabled automatically — no flag
required. `--json` is also available as an explicit override for users
who want structured output in a terminal. The format decision is
resolved once at the top of `Run()` and passed down;
`GetAndSetAccessToken` and other helpers use it directly without
re-running TTY detection.
### Structured output at each stage
Immediately before blocking on the OAuth callback:
```
{
"status": "pending",
"url": "https://login.pinecone.io/..."
}
```
After successful authentication:
```
{
"status": "authenticated",
"email": "user@example.com",
"org_id": "...",
"project_id": "..."
}
```
When already logged in:
```
{
"status": "already_authenticated",
"email": "user@example.com",
"org_id": "..."
}
```
### TTY-gated interactivity
The "Press [Enter]" prompt and `stdin`-reading goroutine are gated on
`term.IsTerminal(int(os.Stdin.Fd()))`. Non-interactive runs skip `stdin`
entirely and block cleanly on the OAuth callback. The prompt is
preserved for TTY sessions regardless of JSON mode — a user passing
--json in a terminal is still at a keyboard and benefits from
browser-open.
### All human-readable output routed to stderr
The prose URL line, Enter prompt, success messages, info messages, and
hints all go to `stderr`. In JSON mode, `stdout` carries only the
structured objects above. In prose mode, TTY users see everything in
their terminal as before.
### Fixed HTML escaping in JSON output
`text.IndentJSON` and `text.InlineJSON` now use `json.Encoder` with
`SetEscapeHTML(false)`. The default Go JSON marshaler escapes & as
`\u0026` — correct for embedding JSON in HTML, wrong for CLI output.
This was causing OAuth URLs in the pending JSON object to be
un-pasteable. Unit tests added to prevent regression.
### Removed dead code
- `IO` struct and parameter removed from `Run()` — was accepted but
never used; all writes go directly to `os.Stdout/os.Stderr/os.Stdin`.
- Dead `--quiet/io.Discard` branches removed from both login command
files.
- `msg.Blank()` added to the msg package for consistent stderr
blank-line spacing
## Type of Change
- [X] Bug fix (non-breaking change which fixes an issue)
- [X] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to not work as expected)
- [ ] This change requires a documentation update
- [ ] Infrastructure change (CI configs, etc)
- [ ] Non-code change (docs, etc)
- [ ] None of the above: (explain here)
## Test Plan
You can test locally by piping stdin / stdout, or passing the `--json`
flag manually.
```bash
# force redirecting stdout to trigger JSON output automatically
./dist/pc_darwin_all/pc login | cat
{
"status": "pending",
"url": "https://login.pinecone.io/oauth/authorize?audience=https%3A%2F%2Fus-central1-production-console.cloudfunctions.net%2Fapi%2Fv1&client_id=A4ONXSaOGstwwir0zUztoI6zjyt9zsRH&code_challenge=9LpXGYNRqvQRgd4NWSCIyBGAiZClfzkM_SeFI3_280I&code_challenge_method=S256&redirect_uri=http%3A%2F%2F127.0.0.1%3A59049%2Fauth-callback&response_type=code&scope=openid+profile+email+offline_access&sourceTag=pinecone_cli&state=l_CIfL4pkyhj7N7DmCI1qd0OD72_nCyi1ikGvYvCGdw"
}
Press [Enter] to open the browser, or manually paste the URL above.
{
"status": "authenticated",
"email": "austin.d@pinecone.io",
"org_id": "-NF9kuQiPWth9QLPg5NS",
"project_id": "504ef178-3aac-49ce-93cd-86dc262e6e43"
}
# login with explicit --json flagged, make sure the browser prompt works when it's displayed
./dist/pc_darwin_all/pc login --json
{
"status": "pending",
"url": "https://login.pinecone.io/oauth/authorize?audience=https%3A%2F%2Fus-central1-production-console.cloudfunctions.net%2Fapi%2Fv1&client_id=A4ONXSaOGstwwir0zUztoI6zjyt9zsRH&code_challenge=YoI6PnfYkLeSKzoOzfSQIKxlHduoawCij2LWu2XFg7w&code_challenge_method=S256&redirect_uri=http%3A%2F%2F127.0.0.1%3A59049%2Fauth-callback&response_type=code&scope=openid+profile+email+offline_access&sourceTag=pinecone_cli&state=qEVw2RwQnHX32iQ5wtK7yzxalAZE8wDWtFDH8eTqN6Y"
}
Press [Enter] to open the browser, or manually paste the URL above.
{
"status": "authenticated",
"email": "austin.d@pinecone.io",
"org_id": "-NF9kuQiPWth9QLPg5NS",
"project_id": "504ef178-3aac-49ce-93cd-86dc262e6e43"
}
# pipe stderr explicitly and trigger --json or stdout explicitly to check that stdout output is clean
./dist/pc_darwin_all/pc login --json 2>/dev/null
./dist/pc_darwin_all/pc login 2>/dev/null | cat
./dist/pc_darwin_all/pc login < /dev/null | cat
{
"status": "pending",
"url": "https://login.pinecone.io/oauth/authorize?audience=https%3A%2F%2Fus-central1-production-console.cloudfunctions.net%2Fapi%2Fv1&client_id=A4ONXSaOGstwwir0zUztoI6zjyt9zsRH&code_challenge=BSh06S5jkmTH8lucj8CAVwy5AhGKITxjX7V4knal0Mw&code_challenge_method=S256&redirect_uri=http%3A%2F%2F127.0.0.1%3A59049%2Fauth-callback&response_type=code&scope=openid+profile+email+offline_access&sourceTag=pinecone_cli&state=7y-Jygv-RPLGY4X-kMPJHfd4PeMxphll3x8xA-0MRKY"
}
{
"status": "authenticated",
"email": "austin.d@pinecone.io",
"org_id": "-NF9kuQiPWth9QLPg5NS",
"project_id": "504ef178-3aac-49ce-93cd-86dc262e6e43"
}
```
Make sure the traditional login experience works as expected:
```bash
./dist/pc_darwin_all/pc login
Visit https://login.pinecone.io/oauth/authorize?audience=https%3A%2F%2Fus-central1-production-console.cloudfunctions.net%2Fapi%2Fv1&client_id=A4ONXSaOGstwwir0zUztoI6zjyt9zsRH&code_challenge=2seMya2Pxhr_ug_shJZkhMxVnVdVT0gD3H8zKACJzYk&code_challenge_method=S256&redirect_uri=http%3A%2F%2F127.0.0.1%3A59049%2Fauth-callback&response_type=code&scope=openid+profile+email+offline_access&sourceTag=pinecone_cli&state=Hm31H8dOMNjGtTdMr4n0Erfa42JqsZo7DEs5vZZ9gfY to authorize the CLI.
Press [Enter] to open the browser, or manually paste the URL above.
[SUCCESS] Logged in as austin.d@pinecone.io. Defaulted to organization ID: -NF9kuQiPWth9QLPg5NS
[INFO] Target org set to pinecone-official.
[INFO] Target project set cmek-integration-tests.
Hint: Run pc target to change the target context.
Hint: Now try pc index -h to learn about index operations.
```
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Changes `pc login`/`pc auth login` output behavior and login helper
signatures, which could affect existing scripts and interactive UX,
though core OAuth exchange remains the same.
>
> **Overview**
> Improves `pc login` and `pc auth login` for non-interactive/agentic
use by adding `--json` and *auto-emitting JSON when stdout is not a
TTY*, while keeping human-readable messaging on interactive terminals.
>
> The login flow now prints structured JSON status objects (e.g.
`pending` with the OAuth URL, `authenticated` / `already_authenticated`
with claims) to stdout, moves prompts and prose to stderr, and gates the
"Press [Enter]" stdin reader on interactive stdin to avoid hanging in
piped runs.
>
> Updates shared helpers (`login.Run`, `GetAndSetAccessToken`) to take
`login.Options{Json: ...}`, adds `msg.Blank()` for consistent spacing,
and adjusts JSON encoding (`text.InlineJSON`/`IndentJSON`) to disable
HTML escaping (with new tests).
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
64aea12. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
0 commit comments