Tags: pinecone-io/cli
Tags
Agentic auth improvements: lazy login, `--json` consistency, and `pc … …target` fixes (#83) ## Problem After previous changes to daemonize the login flow and improve overall I/O, there were still some rough edges when working with the CLI through an agent. Lack of consistent `--json` flag representation was still an issue for a few flags, primarily around not supporting a shorthand option. The `pc target` command also needed changes to better support "headless", or agentic operation effectively. In-client documentation around the specifics of some of these operations also need to be improved to better guide users and agents when authenticating. ## Solution This PR improves the CLI's behavior in agentic/non-TTY contexts (Claude Code, Cursor, other AI coding agents). It builds on the daemon-backed login flow from #82 ### `-j` shorthand added to login commands `pc login` and `pc auth login` were missing the `-j` shorthand for `--json`, inconsistent with every other command. Fixed. ### Lazy auth completion Added `login.EnsureAuthenticated`, called from a `PersistentPreRun` hook on the root command. After a successful browser login, the next command run — any command — automatically detects the completed session, reloads credentials, and initializes the target org/project context. A second `pc login --json` call is no longer required before running other commands. Commands that don't require authentication are explicitly exempted: `pc login`, `pc logout`, `pc auth login/logout/configure/clear/status/_daemon`, `pc auth local-keys`/`list`, `pc target` (which handles its own auth after local-state early returns), `pc version`, and all `pc config` subcommands. ### `pc target` fixes - **TTY auto-detection** — `pc target` now detects non-TTY stdout and enables JSON mode automatically, consistent with `pc login`. - **No-flags JSON mode** — `pc target --json` with no targeting flags now returns the current target context as JSON (equivalent to `--show --json`) instead of an error. This early return is correctly placed before auth and API calls, so it works for API-key users and requires no credentials. - **`--show` and `--clear` unblocked** — These are local-state-only operations; they now return before the auth gate and before any API calls. - **Re-auth URL stays on stderr** — When `pc target` triggers an org-switch re-auth, the auth URL is printed to stderr only. Stdout remains a single JSON document, keeping output compatible with `jq .` and other single-document parsers. - **Missing `return` after `exit.Error`** — Added `return` guards after all `exit.Error` calls in target and root pre-run, preventing fall-through when the exit handler is mocked in tests. ### `applyAuthContext` / `RunPostAuthSetup` refactor Extracted the org/project state-setting logic from `RunPostAuthSetup` into a new `applyAuthContext` helper that returns the user's email. This eliminates a redundant `oauth.Token` + `ParseClaimsUnverified` call that `RunPostAuthSetup` was making immediately after `applyAuthContext` had already fetched the same data. `applyAuthContext` now also always writes `state.TargetProj` (clearing it when the org has no projects), preventing stale project data from a previous session from appearing in the authenticated JSON response. ### Root pre-run JSON error output `EnsureAuthenticated` errors in the root `PersistentPreRun` now check both TTY state and the command's own `--json`/`-j` flag, so auth errors are machine-readable when a caller explicitly requests JSON even in a TTY context. ### Deduplication Extracted `printTargetContextJSON()` in `target.go`, replacing three identical blocks that each read `state.GetTargetContext()`, masked the API key, and printed JSON. ### Documentation Updated `pc login`, `pc auth login`, and `pc target` help text to document the interactive vs. agentic flows, the two-call pending/authenticated pattern, session resumability, and the lazy context initialization behavior. Also fixed a typo in `pc target`'s example (`-project` → `--project`). ## 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 ### Interactive login (TTY) - [ ] `pc login` opens a browser and completes auth, sets target org/project, prints success message - [ ] `pc auth login` same as above ### Agentic login (non-TTY / `--json`) - [ ] `pc login --json` returns `{"status":"pending","url":"...","session_id":"..."}` and exits immediately - [ ] `-j` shorthand works identically on both `pc login` and `pc auth login` - [ ] Second `pc login --json` after completing browser auth returns `{"status":"authenticated",...}` with org/project populated - [ ] Running any other command (e.g. `pc index list --json`) after browser auth — without a second `pc login` call — succeeds and sets target context automatically - [ ] Piping output (`pc login | cat`) triggers non-TTY path automatically without needing `--json` ### Lazy auth / `EnsureAuthenticated` - [ ] After `pc login --json` + browser auth, `pc index list --json` works without a second login call - [ ] `pc auth status` works with no credentials (not blocked by auth gate) - [ ] `pc auth local-keys list` works with no credentials (not blocked by auth gate) - [ ] `pc version` works with no credentials - [ ] All `pc config` subcommands work with no credentials ### `pc target` - [ ] `pc target --show` works with no credentials - [ ] `pc target --clear` works with no credentials - [ ] `pc target --json` (no flags) returns current target context as JSON, works with no credentials - [ ] `pc target --json --org "name"` sets org and returns updated context as JSON - [ ] `pc target --json --org "name" --project "name"` sets both and returns updated context - [ ] `pc target` in a TTY launches interactive selector as before - [ ] `pc target` in a non-TTY (or with `--json`) without targeting flags returns current context rather than erroring ### Auth errors - [ ] Running a command requiring auth with no credentials returns `{"error":"..."}` to stdout when stdout is non-TTY - [ ] Running a command requiring auth with no credentials and `--json` flag returns `{"error":"..."}` to stdout even in a TTY - [ ] "authentication in progress" error includes the auth URL when daemon is still running <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Adds a global authentication pre-check for most commands and changes login/target behavior in non-TTY JSON mode, which could affect automation and command execution paths if the skip list or auth detection is incorrect. > > **Overview** > **Adds a root-level auth gate for most commands.** `pc` now runs `login.EnsureAuthenticated()` in `PersistentPreRun`, with a curated skip list for commands that manage credentials/local state, and auto-detects JSON mode (non-TTY stdout or `--json/-j`) to format auth errors appropriately. > > **Improves non-interactive/agentic UX.** `login` and `auth login` gain `-j` shorthand and updated help/examples; `target` now auto-enables JSON mode in non-TTY contexts, supports `pc target --json` as an auth-free “show current context” path, and centralizes JSON context printing. > > **Refactors and extends post-auth setup.** Login utilities split context initialization into `applyAuthContext()`, ensure target project state is cleared when no projects exist, keep stdout clean during wait-mode by printing auth URLs to stderr, and introduce `EnsureAuthenticated()` to lazily complete pending daemon sessions and auto-initialize org/project context after successful browser auth. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit ddb7ecd. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Daemonize `login` OAuth listener (#82) Reworks the OAuth login flow to work correctly in agentic contexts (Claude Code, Cursor, other AI coding agents) where stdin/stdout are not interactive TTYs. ### Problem The previous `pc login` flow blocked on stdin waiting for `[Enter]`, and the auth URL was either hidden or percent-encoded in ways that made it hard for agents to extract. There was no machine-readable way to surface the URL and get a non-blocking result. ### Solution: daemon-backed two-call pattern **First call** (`pc login --json`): spawns a detached background daemon that owns the OAuth callback server on `127.0.0.1:59049`, emits `{"status":"pending","url":"...","session_id":"..."}` to stdout, and exits immediately. The daemon keeps listening for the browser redirect. **Second call** (`pc login --json`): detects the pending session, polls the daemon's result file until auth completes, then emits `{"status":"authenticated","email":"...","org_id":"...","org_name":"...","project_id":"...","project_name":"..."}`. If the second call is killed before the daemon finishes, a third call resumes seamlessly — the daemon keeps running regardless. Non-agentic users (`pc login` without `--json`, TTY stdout) are completely unaffected. ### Key details - **Session state** is stored in `~/.config/pinecone/sessions/` (mode 0600, 5-minute TTL) — PKCE verifier is passed to the daemon via environment variable and never touches disk. - **Atomic result writes** use write-to-temp-then-rename to prevent partial reads in the polling loop. - **`pc target` re-auth** uses a `Wait: true` mode that blocks until the token is acquired (daemon path, but synchronous), printing the auth URL to stderr so it's visible without polluting stdout. - **Windows** daemon uses `CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS` so it survives terminal close. - **TTY auto-detection** is applied in `GetAndSetAccessToken` so all callers (including `pc target`) correctly use the daemon path in non-TTY environments. - **Port conflict detection** in the interactive path surfaces a clear error with the existing auth URL if a daemon is already holding the callback port. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes the CLI OAuth login flow to spawn a detached background process and persist session state/results on disk, which can impact authentication reliability across platforms and failure modes. Also modifies re-auth behavior in `pc target` to block until tokens are acquired. > > **Overview** > **Daemonizes `pc login --json` OAuth flow** by introducing a hidden `pc auth _daemon` subcommand that runs the local callback server in a detached process, exchanges the auth code, stores the token, and writes a session result file. > > **Adds resumable, file-backed login sessions** (`sessions/` state+result) so JSON login can return immediately with `{"status":"pending"...}` and be polled/resumed on subsequent invocations, with cleanup/expiry handling and cross-platform process detachment. > > **Refactors token acquisition and post-auth setup**: splits interactive vs JSON paths, adds `Options.Wait` for callers that must block until credentials exist, and updates `pc target` re-auth to use `Wait: true` while keeping JSON output behavior separate via `RunPostAuthSetup`. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 596f676. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Post-cask migration: quarantine fix and documentation updates (#81) ## Problem While testing the formula-to-cask migration, macOS Gatekeeper was blocking the `pc` binary after a cask install. This is expected behavior — Homebrew casks explicitly apply the `com.apple.quarantine` attribute, unlike formula installs. The postflight block strips this attribute after installation. ## Solution - Adds `postflight` block to cask config to strip macOS quarantine attribute, resolving Gatekeeper prompt on first run after cask install. - Updates `README` to reflect cask-based installation across all Homebrew commands and adds install script as a first class installation method. The quarantine removal is guarded with `OS.mac?` since `xattr` is macOS-only and Homebrew 4.5.0+ supports casks on Linux. This is a stopgap until the binary is code-signed and notarized via Apple Developer. ## Type of Change - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] 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 - [X] Infrastructure change (CI configs, etc) - [ ] Non-code change (docs, etc) - [ ] None of the above: (explain here) ## Test Plan Re-tag and release, test installation flow. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Moderate risk because it changes Homebrew cask post-install behavior (runs `xattr` on macOS) and could affect end-user install experience, though the change is small and OS-guarded. > > **Overview** > Resolves macOS Gatekeeper prompts after Homebrew **cask** installs by adding a `postflight` step in `.goreleaser.yaml` that recursively removes `com.apple.quarantine` from the staged cask payload on macOS. > > Updates `README.md` installation guidance to emphasize cask-based Homebrew commands, notes the Linux Homebrew cask version requirement, and adds an *install script* option with examples for pinning versions and custom install paths. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 302c409. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
add preflight block to custom_block in goreleaser.yaml to allow clean… …ing up old binaries before installing (#80) ## Problem When reinstalling the CLI, you may run into conflicts with previously existing binaries since these may not be properly cleaned up by homebrew. For us, this seems like an issue with the binary alias install for `pc`: ``` brew uninstall pinecone-io/tap/pinecone && brew install --cask pinecone-io/tap/pinecone Warning: Treating pinecone-io/tap/pinecone as a formula. For the cask, use pinecone-io/tap/pinecone or specify the `--cask` flag. To silence this message, use the `--formula` flag. Uninstalling /opt/homebrew/Cellar/pinecone/0.0.60... (107 files, 49.2MB) ==> Auto-updating Homebrew... Adjust how often this is run with `$HOMEBREW_AUTO_UPDATE_SECS` or disable with `$HOMEBREW_NO_AUTO_UPDATE=1`. Hide these hints with `$HOMEBREW_NO_ENV_HINTS=1` (see `man brew`). ==> Fetching downloads for: pinecone-io/tap/pinecone ✔︎ Cask pinecone (0.4.0) Verified 25.9MB/ 25.9MB ==> Installing Cask pinecone ==> Purging files for version 0.4.0 of Cask pinecone Error: It seems there is already a Binary at '/opt/homebrew/bin/pc'. ``` ## Solution add preflight block to custom_block in goreleaser.yaml to allow cleaning up old binaries before installing ## Type of Change - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] 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 - [X] Infrastructure change (CI configs, etc) - [ ] Non-code change (docs, etc) - [ ] None of the above: (explain here) ## Test Plan Rerun the installation. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk: only adjusts release tooling by adding a cask `preflight` cleanup, with no runtime code changes. Potential impact is limited to Homebrew installs if the delete logic removes unexpected files in `HOMEBREW_PREFIX/bin`. > > **Overview** > Prevents Homebrew cask reinstall failures by adding a `preflight` block to the GoReleaser `homebrew_casks.custom_block` that deletes any existing `pc` or `pinecone` entries under `HOMEBREW_PREFIX/bin` (including symlinks) before installing the cask binary alias. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit fa65cdc. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Remove publishing dev version on `publish.yaml` (#78) ## Problem There was some initial setup done for a `pinecone-dev` version of the CLI that gets released via goreleaser -> brew. This looks like it was relying on a previous `homebrew-pinecone` repo which no longer exists. I don't think we've ever utilized the dev build of the CLI for anything, and since we're changing the overall goreleaser -> brew process, it's a good time to clean this up. ## Solution Remove dev release from publish.yaml workflow for now. ## Type of Change - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] 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 - [X] Infrastructure change (CI configs, etc) - [ ] Non-code change (docs, etc) - [ ] None of the above: (explain here) ## Test Plan N/A Make sure this isn't run at all when the publish workflow is triggered. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk CI-only change that deletes an unused workflow trigger; main release/publish steps remain unchanged. > > **Overview** > Removes the `publish.yaml` step that extracted the released version from GoReleaser metadata and used a PAT to dispatch `update-dev-formula.yaml` in the `homebrew-pinecone` repo. > > The publish workflow now only runs GoReleaser (plus existing setup) without attempting any dev/Homebrew formula update automation. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 440ec30. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
make sure NewIndexConnection uses index.private_host if it's available ( #65) ## Problem There was a bug caught while testing BYOC. The `sdk.NewIndexConnection` function does not properly account for `index.private_host` if it exists, and only uses `index.host` to resolve the connection. ## Solution Update `sdk.NewIndexConnection` to use `index.PrivateHost > index.Host` when one is present. ## Type of Change - [X] Bug fix (non-breaking change which fixes an issue) - [ ] 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 Test data plane operations with a BYOC index.
Allow attribution tags through `go-pinecone` (`PINECONE_CLI_ATTRIBUTI… …ON_TAG`) (#63) ## Problem We need a mechanism for being able to further tag usage of the CLI. ## Solution - Allow appending attribution tags to network operations from the CLI via the `go-pinecone` SDK `SourceTag`. - Read from `PINECONE_CLI_ATTRIBUTION_TAG` and append to the existing `pinecone-cli` source tag. - Add a small unit test for the function reading from env. ## Type of Change - [ ] 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 CI - unit & integration tests
Clean up `presenters` pointer handling (#59) ## Problem While testing I ran into instances where an empty response, such as in `query` could result in a nil reference error. A lot of our presenters for table-output, and non-json output, don't do enough nil-checking on the inbound pointers. ## Solution Clean up the presenters package and individual presentational functions to better deal with nils, add a small `PrintEmptyState` function and test, and try and cover cases where we're accessing pointers without explicitly checking. ## Type of Change - [X] Bug fix (non-breaking change which fixes an issue) - [ ] 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 CI - unit & integration tests
Refactor / fix `exit` package (#53) ## Problem Previously, I refactored the `exit` package a bit to expose functions that wrapped `zerolog`. This seemed clever at the time, but I ended up introducing a bug where chaining `exit.Error().Err(error)...` would return a `zerolog.Event` instead of an exitEvent, which meant the process wouldn't actually exit. This could lead to odd logging and behavior include sigsev violations. ## Solution Reflecting on this code, the implementation seemed a bit too fancy and error-prone. I simplified things down to functions that do the same thing as the previous builder pattern-style `exitEvent`. Replaced all usages of `exit.Error()` and `exit.Success()`, updated unit tests to validate new functions. There larger scale changes I'd like to take on involving error handling and logging in later PRs. ## Type of Change - [X] Bug fix (non-breaking change which fixes an issue) - [ ] 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 CI - unit and integration tests Testing involves generally triggering error and success messages that are meant to end the process. A good example is calling `$ pc target --org my-org --project invalid-project-name` - this will throw an error which would cause a sigsev: Before: <img width="839" height="426" alt="Screenshot 2025-11-14 at 6 47 44 PM" src="proxy.php?url=https%3A%2F%2Fgithub.com%2F%3Ca+href%3D"https://github.com/user-attachments/assets/e41482d4-f321-4026-8672-0f3ef6bee6f1">https://github.com/user-attachments/assets/e41482d4-f321-4026-8672-0f3ef6bee6f1" /> After: <img width="845" height="160" alt="Screenshot 2025-11-14 at 6 48 28 PM" src="proxy.php?url=https%3A%2F%2Fgithub.com%2F%3Ca+href%3D"https://github.com/user-attachments/assets/d39bc37a-e711-4d2e-b555-bdae4db6cca1">https://github.com/user-attachments/assets/d39bc37a-e711-4d2e-b555-bdae4db6cca1" />
Add `TokenError`, improve `exit` package utilities, improve error log… …ging (#50) ## Problem With the CLI public preview release last week, we've had some reports of errors come through. I noticed we had some deficiencies around both logging / error output (inconsistent output, duplicate log output, etc). We also were not properly handling the oauth token API responses from auth0. Since these HTTP requests are not going through the SDK and are handled manually, there was a need to add more robust inspection of token response payloads, and properly surface user-friendly error messages on issues with the authentication service. The biggest issue here is the lack of proper error messaging when a user token has expired (24 hours total or 12 hours inactive). ## Solution - Implement new `TokenError` struct and various utilities for parsing an error from auth responses, and then bubbling that error up through the call stack so it's presented in a reasonable way. I did some testing against what we get back from the auth service on token expiration, and followed [RFC6749 The OAuth 2.0 Authorization Framework](https://datatracker.ietf.org/doc/html/rfc6749) in terms of the shape of the response and expected error codes from the token endpoints. This may have been a bit overkill, but since the CLI will continue handling it's own client authentication, we should probably start from as robust a place as we can. - Refactor the `exit` package a bit. This was mainly for ergonomics, since logging was also tied to calling exit. This was the cause of a lot of logging duplication in spots which I was responsible for implementing. I basically wanted to wrap a zerolog.Event to allow easier chaining at callsites, etc. - General clean up of logging and usage around `exit.Error` or `exit.Success`. For example, we don't generally need to return after calling exit. There were places where errors were being swallowed or not properly bubbled up, etc. - Add unit tests for `/oauth/error.go` and `exit.go`. There's still a lot of unit test coverage missing in general, but trying to add in coverage to new features and things as I go seems prudent. ## 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 For general logging and error cases, test to make sure user-facing messages display as expected. Also validate logging for info, debug, etc using `PINECONE_LOG_LEVEL="debug"`. Testing `TokenError` is difficult as we don't have a ton of direct control over the authentication server and its responses. The most consistent testing I was able to do was keeping a stale/expired user token in my cache, and making calls with it. This will attempt a token refresh call, which will return `"error": "invalid_grant"`. This case is consistently testable, so that's what I focused on. The rest of the functionality I hopefully covered through unit testing suites. ||Expired user token error| |---|---| |Before|<img width="960" height="164" alt="Screenshot 2025-10-24 at 6 17 10 PM" src="proxy.php?url=https%3A%2F%2Fgithub.com%2F%3Ca+href%3D"https://github.com/user-attachments/assets/d4417383-41b8-4c53-bd57-12b9a2fab03f">https://github.com/user-attachments/assets/d4417383-41b8-4c53-bd57-12b9a2fab03f" />| |After|<img width="1118" height="148" alt="Screenshot 2025-10-24 at 6 18 35 PM" src="proxy.php?url=https%3A%2F%2Fgithub.com%2F%3Ca+href%3D"https://github.com/user-attachments/assets/fb471210-e7ae-4a2b-bc42-028c854831b8">https://github.com/user-attachments/assets/fb471210-e7ae-4a2b-bc42-028c854831b8" />| --- - To see the specific tasks where the Asana app for GitHub is being used, see below: - https://app.asana.com/0/0/1211580374844618
PreviousNext