feat(cli): add --json output to list, get, doctor, and service status - #6
Merged
Merged
Conversation
loadRoutes() treated a routes.json it could not read (for example EACCES or EISDIR) as empty without calling onWarning, while loadRoutesRaw() already reported it. Report it the same way, so callers can tell an unreadable registry apart from an empty one. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01BSDFAb4giiESfUnAHgHG3S
--json is a global flag with one contract: stdout carries only the JSON document, warnings and errors go to stderr, a command that fails exits non-zero, and keys are camelCase like routes.json and portless.json. Commands without JSON output reject the flag, and a --json after the child command still reaches the child. list --json returns the routes the proxy serves (live processes and aliases) with their public URLs, without persisting the stale-route cleanup. It exits 1 when routes.json cannot be read or parsed, instead of printing an empty list. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01BSDFAb4giiESfUnAHgHG3S
Merged
rqbazan
added a commit
that referenced
this pull request
Sep 15, 2026
Bump to 0.0.4 and add the changelog entry. The release ships --json output from #6 for list, get, doctor, and service status, and the new warning when routes.json cannot be read. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01BSDFAb4giiESfUnAHgHG3S
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Scripts and agents that need portless state either parse the colored text of
portless listor readroutes.jsonthemselves and re-implement which routes are live: aliases havepid: 0, stale PIDs must be skipped,PORTLESS_STATE_DIRand the sudo home must be resolved. They drift from portless as soon as any of that changes.This adds one
--jsonconvention to the CLI so they can ask portless instead.Change
--jsonis a global flag with a single contract:routes.jsonandportless.json. Optional fields are omitted when unset.list,get,doctor, andservice statusaccept it. Any other command, app runs included, exits 1 withError: --json is only supported by list, get, doctor, service status.A--jsonafter the child command still reaches the child.portless list --jsonhostname,pathPrefix,port,pid(0for an alias),alias,url, andtailscaleUrlorngrokUrlwhen shared.portless get <name> --jsonname,hostname,pathPrefix,url,proxyPort,tls. Likeget, it does not check whether the service is running.portless doctor --jsonversion,node,platform,arch,stateDir,proxyPort,tls,tlds,lanMode,findings(status,message,hint),failures,warnings. Exits 1 when a check fails, likedoctor.portless service status --jsoninstalled,managerState,proxyPort,proxyRunning,tls,tlds,lanMode,lanIp,wildcard,stateDir,serviceEntry.list --jsonresolves state likelistand returns the same routes the proxy serves (RouteStore.loadRoutes()), without persisting the stale-route cleanup. Unlikelist, aroutes.jsonthat cannot be read or parsed is an error (exit 1, message on stderr), so a caller can tell "no routes" apart from "cannot tell".To detect an unreadable file,
RouteStore.loadRoutes()now reports read failures (for exampleEACCES) throughonWarning, the wayloadRoutesRaw()already did. Commands that already print the corrupted-file warning now print this one too, on stderr. Before, they treated an unreadable file as empty without saying so.Human-readable output is otherwise unchanged.
Docs updated: README (Commands and a new JSON output section),
skills/portless/SKILL.md, theapps/docscommands page, and--helpfor the CLI,get,doctor, andservice.Upstream, vercel-labs/portless#257 proposes
--jsonforlistandgetonly, parsed per command, with snake_case keys (target_port,kind) and no path prefixes. This PR makes the flag general and keeps key names aligned with portless's own files.Verification
cli.test.ts:list --json(live routes, aliases, path prefixes, tunnel URLs, no stale-route write-back, empty list, corrupted file, unreadable file),--jsonbefore the command,get --jsonwith and without--path,doctor --json, rejection byalias.cli-utils.test.ts: which commands accept--json.service.test.ts:service status --json.routes.test.ts:loadRouteswarns when the file cannot be read.listoutput, and--jsonafter the child command reaching the child.pnpm format:check && pnpm lint && pnpm type-check && pnpm build && pnpm test && pnpm test:e2e: all green (999 unit tests passed and 1 skipped, 15 docs tests, 16 e2e tests passed and 2 skipped).node -e ""alone takes 130 ms,portless list162 ms,portless list --json161 ms, andportless get x --no-worktree --json158 ms. The JSON path does no more work thanlist.No version bump here. The release follows in its own PR.
🤖 Generated with Claude Code
https://claude.ai/code/session_01BSDFAb4giiESfUnAHgHG3S