Skip to content

Commit 2e3767b

Browse files
committed
✨ Add user auth support and CLI help examples
- Add user JWT fallback in config-loader (enables `vizzly login` for interactive CLI) - Add help examples to builds, comparisons, approve, reject, comment, baselines, api commands - Add `npm run cli` script with .envrc sourcing for local dev - Add .envrc to .gitignore - Fix missing 'review' category in help grouping
1 parent fb28a1a commit 2e3767b

File tree

4 files changed

+112
-5
lines changed

4 files changed

+112
-5
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,4 @@ tests/client/
3535
playwright-report/
3636
test-results/
3737
.claude
38+
.envrc

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
"LICENSE"
5757
],
5858
"scripts": {
59+
"cli": "source .envrc && node bin/vizzly.js",
5960
"start": "node src/index.js",
6061
"build": "npm run clean && npm run compile && npm run build:reporter && npm run build:reporter-ssr && npm run copy-types",
6162
"clean": "rimraf dist",

src/cli.js

Lines changed: 97 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ const formatHelp = (cmd, helper) => {
148148

149149
let grouped = {
150150
core: [],
151+
review: [],
151152
setup: [],
152153
advanced: [],
153154
auth: [],
@@ -646,6 +647,18 @@ program
646647
)
647648
.option('--offset <n>', 'Skip first N results', val => parseInt(val, 10), 0)
648649
.option('--comparisons', 'Include comparisons when fetching a specific build')
650+
.addHelpText(
651+
'after',
652+
`
653+
Examples:
654+
$ vizzly builds # List recent builds
655+
$ vizzly builds --branch main # Filter by branch
656+
$ vizzly builds --status completed # Filter by status
657+
$ vizzly builds -b abc123-def456 # Get specific build by ID
658+
$ vizzly builds -b abc123 --comparisons # Include comparisons
659+
$ vizzly builds --json # Output as JSON for scripting
660+
`
661+
)
649662
.action(async options => {
650663
const globalOptions = program.opts();
651664

@@ -677,6 +690,18 @@ program
677690
50
678691
)
679692
.option('--offset <n>', 'Skip first N results', val => parseInt(val, 10), 0)
693+
.addHelpText(
694+
'after',
695+
`
696+
Examples:
697+
$ vizzly comparisons -b abc123 # List comparisons for a build
698+
$ vizzly comparisons --id def456 # Get specific comparison by ID
699+
$ vizzly comparisons --name "Button" # Search by screenshot name
700+
$ vizzly comparisons --name "Login*" # Wildcard search
701+
$ vizzly comparisons --status changed # Only changed comparisons
702+
$ vizzly comparisons --json # Output as JSON for scripting
703+
`
704+
)
680705
.action(async options => {
681706
const globalOptions = program.opts();
682707

@@ -721,6 +746,18 @@ program
721746
.description('List and query local TDD baselines')
722747
.option('--name <pattern>', 'Filter baselines by name (supports wildcards)')
723748
.option('--info <name>', 'Get detailed info for a specific baseline')
749+
.addHelpText(
750+
'after',
751+
`
752+
Examples:
753+
$ vizzly baselines # List all local baselines
754+
$ vizzly baselines --name "Button*" # Filter by name pattern
755+
$ vizzly baselines --info "homepage" # Get details for specific baseline
756+
$ vizzly baselines --json # Output as JSON for scripting
757+
758+
Note: Baselines are stored locally in .vizzly/baselines/ during TDD mode.
759+
`
760+
)
724761
.action(async options => {
725762
const globalOptions = program.opts();
726763

@@ -740,7 +777,7 @@ program
740777
program
741778
.command('api')
742779
.description('Make raw API requests (for power users)')
743-
.argument('<endpoint>', 'API endpoint (e.g., /sdk/builds)')
780+
.argument('<endpoint>', 'API endpoint (e.g., /api/sdk/builds)')
744781
.option(
745782
'-X, --method <method>',
746783
'HTTP method (GET or POST for approve/reject/comment)',
@@ -757,6 +794,20 @@ program
757794
'Add query param (key=value), can be repeated',
758795
(val, prev) => (prev ? [...prev, val] : [val])
759796
)
797+
.addHelpText(
798+
'after',
799+
`
800+
Examples:
801+
$ vizzly api /api/sdk/builds # List builds
802+
$ vizzly api /api/sdk/builds -q limit=5 # With query params
803+
$ vizzly api /api/sdk/builds/abc123 # Get specific build
804+
$ vizzly api /api/sdk/comparisons/abc123/approve -X POST
805+
$ vizzly api /api/sdk/builds/abc123/comments -X POST -d '{"content":"Nice!"}'
806+
807+
Note: POST is restricted to approve, reject, and comment endpoints.
808+
Most operations have dedicated commands (builds, comparisons, approve, etc.).
809+
`
810+
)
760811
.action(async (endpoint, options) => {
761812
const globalOptions = program.opts();
762813

@@ -776,8 +827,22 @@ program
776827
program
777828
.command('approve')
778829
.description('Approve a comparison')
779-
.argument('<comparison-id>', 'Comparison ID to approve')
830+
.argument('<comparison-id>', 'Comparison ID to approve (UUID format)')
780831
.option('-m, --comment <message>', 'Optional comment explaining the approval')
832+
.addHelpText(
833+
'after',
834+
`
835+
Examples:
836+
$ vizzly approve abc123-def456-7890 # Approve a comparison
837+
$ vizzly approve abc123 -m "LGTM" # Approve with comment
838+
$ vizzly approve abc123 --json # Output as JSON for scripting
839+
840+
Workflow:
841+
1. List comparisons: vizzly comparisons -b <build-id>
842+
2. Review the changes in the web UI or via URLs in the output
843+
3. Approve: vizzly approve <comparison-id>
844+
`
845+
)
781846
.action(async (comparisonId, options) => {
782847
const globalOptions = program.opts();
783848

@@ -796,8 +861,22 @@ program
796861
program
797862
.command('reject')
798863
.description('Reject a comparison')
799-
.argument('<comparison-id>', 'Comparison ID to reject')
864+
.argument('<comparison-id>', 'Comparison ID to reject (UUID format)')
800865
.option('-r, --reason <message>', 'Required reason for rejection')
866+
.addHelpText(
867+
'after',
868+
`
869+
Examples:
870+
$ vizzly reject abc123 -r "Button color is wrong"
871+
$ vizzly reject abc123 --reason "Needs design review"
872+
$ vizzly reject abc123 -r "Regression" --json
873+
874+
Workflow:
875+
1. List comparisons: vizzly comparisons -b <build-id>
876+
2. Review the changes in the web UI or via URLs in the output
877+
3. Reject with reason: vizzly reject <comparison-id> -r "reason"
878+
`
879+
)
801880
.action(async (comparisonId, options) => {
802881
const globalOptions = program.opts();
803882

@@ -816,13 +895,27 @@ program
816895
program
817896
.command('comment')
818897
.description('Add a comment to a build')
819-
.argument('<build-id>', 'Build ID to comment on')
898+
.argument('<build-id>', 'Build ID to comment on (UUID format)')
820899
.argument('<message>', 'Comment message')
821900
.option(
822901
'-t, --type <type>',
823902
'Comment type: general, approval, rejection',
824903
'general'
825904
)
905+
.addHelpText(
906+
'after',
907+
`
908+
Examples:
909+
$ vizzly comment abc123 "Looks good overall"
910+
$ vizzly comment abc123 "Approved" -t approval
911+
$ vizzly comment abc123 "Please fix the header" -t rejection
912+
$ vizzly comment abc123 "CI feedback" --json
913+
914+
Workflow:
915+
1. Get build ID: vizzly builds --branch main
916+
2. Add comment: vizzly comment <build-id> "Your message"
917+
`
918+
)
826919
.action(async (buildId, message, options) => {
827920
const globalOptions = program.opts();
828921

src/utils/config-loader.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
getBuildName,
88
getParallelId,
99
} from './environment-config.js';
10-
import { getProjectMapping } from './global-config.js';
10+
import { getAccessToken, getProjectMapping } from './global-config.js';
1111
import * as output from './output.js';
1212

1313
const DEFAULT_CONFIG = {
@@ -131,6 +131,18 @@ export async function loadConfig(configPath = null, cliOverrides = {}) {
131131

132132
applyCLIOverrides(config, cliOverrides);
133133

134+
// 6. Fall back to user auth token if no other token found
135+
// This enables interactive commands (builds, comparisons, approve, etc.)
136+
// to work without a project token when the user is logged in
137+
if (!config.apiKey) {
138+
let userToken = await getAccessToken();
139+
if (userToken) {
140+
config.apiKey = userToken;
141+
config.isUserAuth = true; // Flag to indicate this is user auth, not project token
142+
output.debug('config', 'using token from user login');
143+
}
144+
}
145+
134146
return config;
135147
}
136148

0 commit comments

Comments
 (0)