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
27 changes: 22 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,10 @@ pnpm exec vizzly project status
pnpm exec vizzly run "pnpm test"
```

`vizzly login` signs you in. Linking saves the project name in your config and
keeps the upload token in user storage. Run it again to reuse a saved token.
Replace `"pnpm test"` with your test command.
`vizzly login` opens browser consent so you can choose read, build/upload, review,
and comment permissions. Your login can perform those actions within your current
project access. Linking saves the destination in your checkout; it does not issue
another credential. Replace `"pnpm test"` with your test command.

If you linked with an older CLI, run `project link` once in each repository to
save its project selection. Handwritten config may need a manual edit; the
Expand All @@ -118,7 +119,8 @@ Cloud read commands accept `--auth auto|user|token`. The default, `auto`, uses
an explicit flag, environment, or config token first, then your saved login,
then the checkout's linked credential. `user` selects only your saved login;
`token` selects only an upload or explicit API credential. `--token` and
`--auth user` cannot be combined. User login and upload credentials stay separate.
`--auth user` cannot be combined. Your saved login works for uploads too; explicit
project credentials remain available for CI.

Lists show a page of accessible results and tell you how to fetch the next one.
Use `--limit` and `--offset` for scripts; JSON keeps paging details under
Expand All @@ -128,9 +130,24 @@ now honors explicit token settings consistently with other cloud reads.

Vizzly refreshes an expired or nearly expired selected login automatically.
If the saved session is rejected, run `vizzly login` and retry your command.
Connection or server errors preserve your saved session and ask you to retry.
A definite rejected refresh preserves your saved credentials. If a refresh may have
completed but its response was lost, the CLI requires a new login rather than
reusing a possibly consumed refresh token.
Submitted API writes are never automatically replayed after an auth failure.

Use `vizzly whoami` to see the selected API origin and granted permissions. For a
restricted session, use `vizzly login --scopes read` or
`vizzly login --scopes "read builds:write"`, then confirm the selection in your
browser. `--no-browser` prints the authorization URL for remote terminals. Missing
permissions produce `INSUFFICIENT_SCOPE` with an action to authorize the needed
scope; they do not trigger repeated refreshes or switch identities.

User sessions are isolated by API origin and stored in the system credential store
when available, with an owner-only file fallback. Custom `VIZZLY_HOME` directories
use isolated files by default. Concurrent CLI processes coordinate refresh, and
long-running commands renew again as later access tokens expire. Existing generic
login credentials require fresh browser consent.

To upload screenshots you already have:

```bash
Expand Down
11 changes: 8 additions & 3 deletions skills/vizzly/references/cli-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,17 @@ returns one logical page; large limits can require several bounded API requests.
If the next offset is null while more results exist, narrow your filters to stay
within the API's offset limit. An expired selected login is refreshed automatically;
a rejected session asks for login. Report the recovery action when interactive
login isn't authorized. Network failures preserve the session.
login isn't authorized. Network failures preserve the session. `whoami` reports
granted scopes: reads require `read`, uploads require `builds:write`, review
decisions require `reviews:write`, and comments require `comments:write`. A scope
denial requires explicit user consent for additional permissions, not repeated
refreshes or a different identity.

`vizzly project status --json` describes configured and effective upload
destinations separately. Use `project link` only when setup or changing the
upload destination is authorized. It issues or reuses a project credential and
binds the checkout. Browsing another project must not change that binding.
upload destination is authorized. It saves the destination without issuing a credential.
The selected login can upload when it has `builds:write`; explicit project tokens
remain the automation path. Browsing another project must not change that binding.

Use the ID supplied by the task. If no cloud build is named, find recent builds
and choose the one matching the branch, commit, or pull request:
Expand Down
22 changes: 15 additions & 7 deletions src/adapters/cli/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -1519,7 +1519,8 @@ Workflow:
2. List projects: vizzly projects --org <org-slug>
3. Query builds: vizzly builds --project <project-slug> --org <org-slug>

Use --auth user to browse your account when VIZZLY_TOKEN selects a project.
VIZZLY_TOKEN takes precedence over a saved login. Otherwise, your login is used.
The output shows which credential was selected.
Linking is needed only to choose a checkout upload destination.
`
)
Expand All @@ -1539,7 +1540,7 @@ let projectCommand = program
.description('Show or choose the upload destination')
.addHelpText(
'after',
'\nDiscover: vizzly orgs, vizzly projects --auth user\nBrowse: vizzly builds --auth user --project <slug> --org <slug>\nChoose uploads: vizzly project link\nInspect uploads: vizzly project status\nBrowsing another project does not change this checkout’s upload destination.\n'
'\nDiscover: vizzly orgs, vizzly projects\nBrowse: vizzly builds --project <slug> --org <slug>\nChoose uploads: vizzly project link\nInspect uploads: vizzly project status\nBrowsing another project does not change this checkout’s upload destination.\n'
)
.showHelpAfterError('(Run vizzly project --help for available commands)')
.showSuggestionAfterError();
Expand All @@ -1554,8 +1555,6 @@ projectCommand
.description('Link a Vizzly project for cloud uploads')
.option('--org <slug>', 'Organization slug')
.option('--project <slug>', 'Project slug')
.option('--name <name>', 'Credential name shown in Vizzly')
.option('--expires-at <iso>', 'Optional token expiration timestamp')
.addHelpText(
'after',
`
Expand All @@ -1564,8 +1563,9 @@ Examples:
$ vizzly project link vizzly/storybook
$ vizzly project link --org vizzly --project storybook

Note: run "vizzly login" first. The linked credential is project-scoped and is
used for cloud uploads; your user login remains separate for review actions.
Sign in to use the picker. An explicit org/project can be saved before login.
Linking saves the upload destination for this checkout. Your selected login
permissions control which actions are allowed.
`
)
.action(async (selector, options) => {
Expand Down Expand Up @@ -1651,8 +1651,16 @@ program

program
.command('login')
.description('Authenticate with your Vizzly account')
.description('Log in and choose CLI permissions in your browser')
.option('--api-url <url>', 'API URL override')
.option(
'--no-browser',
'Print the authorization URL without opening a browser'
)
.option(
'--scopes <names>',
'Permissions to request, separated by spaces or commas'
)
.action(async options => {
let globalOptions = getGlobalOptions();

Expand Down
12 changes: 6 additions & 6 deletions src/adapters/http/cloud-proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ import { createCloudBuildsRouter } from '../../domains/builds/cloud-builds.route
import { createCloudProjectsRouter } from '../../domains/projects/cloud-projects.routes.js';
import { sendError, sendServiceUnavailable } from './middleware/response.js';

export function createCloudProxyRouter({ authService }) {
let projects = createCloudProjectsRouter({ authService });
let builds = createCloudBuildsRouter({ authService });
let baselines = createCloudBaselinesRouter({ authService });
export function createCloudProxyRouter({ projectService }) {
let projects = createCloudProjectsRouter({ projectService });
let builds = createCloudBuildsRouter({ projectService });
let baselines = createCloudBaselinesRouter({ projectService });

return async function handleCloudProxyRoute(req, res, pathname, parsedUrl) {
if (!pathname.startsWith('/api/cloud')) return false;

if (!authService) {
sendServiceUnavailable(res, 'Auth service');
if (!projectService) {
sendServiceUnavailable(res, 'Project service');
return true;
}

Expand Down
Loading
Loading