--json flag. With it, stdout holds exactly
one JSON object: the result, or the error when the command fails. Progress,
browser instructions, warnings, and suggested next commands go to stderr as
text, so a result never has a next field.
- Terminal
- JSON
--json, stdout also holds only the answer. A list with nothing in it
prints its message (for example No projects found.) on stderr, a list with
more pages ends with a More results: line on stderr that names the command for
the next page, and a declined confirmation prints Cancelled. on stderr and
exits 1.
Only --json prints JSON. Without it the output is text, at a terminal or not:
off a terminal the text drops color and spinners but says the same thing.
Piping the output doesn’t switch to JSON, and no environment variable or
configuration setting chooses the format. Every --help ends with a line that
tells agents and scripts to add --json.
The format never changes what a command does. A command that waits, such as
project platform email connect or project platform telegram connect, waits
with or without --json; only --wait and --no-wait change that. With
--json, what it prints before it waits goes to stderr, so stdout holds only
the final JSON object.
A spinner shows on stderr only when stdout and stderr are both terminals, CI
isn’t on, TERM isn’t dumb, and there’s no --json.
Envelopes
Most commands print the API’s response as Photon sends it, as described below. The documents the CLI defines are in the table further down. Some put the API’s object under a name of their own, such asproject, organization or
operation, and others add keys beside the API’s response, such as
provisioning.
Paginated collections carry the API’s token for the next page under the API’s
name for it: nextPageToken when more records remain, nextCursor for
invoices and listMetadata.after for OAuth connections, both null on the
last page. A list that keeps the API’s envelope, as described below, and whose
--limit takes more than one page holds every page’s items, the last page’s
nextPageToken when more records remain, and the last page’s other top-level
fields. Of the documents the CLI defines, the ones that say where the CLI found
or saved a selection, or where the sign-in comes from, add source. When
there’s no API body to print, because Photon sends none or the command makes
no API call, the document names what changed with a boolean such as
deleted, revoked, removed or cleared.
project list, count, create, view, edit and delete,
account view and edit, the project agent-profile, project api-key,
project webhook, org oauth-client, org member, org invitation,
account avatar, account oauth and account service-key commands,
org leave, org billing payment-method status, and the project platform
commands that read one response (list, operation cancel, email dns,
imessage view, imessage assignment create, list, view and release,
telegram view, and whatsapp-business number list and number view), the
ones that start an operation and wait for it (sms purchase and delete,
imessage purchase, email connect and disconnect, telegram connect and
disconnect, and whatsapp-business number add --waba-id and
number delete), the
project platform voice commands except
profile delete, assignment set and assignment clear, and
project billing invoice list, billing operation view and
billing plan purchase, print the API’s response as Photon sends it instead:
a list keeps the API’s envelope ({"projects": [...], "nextPageToken": "..."},
{"apiKeys": [...]}, {"destinations": [...], "nextPageToken": "..."},
{"oauthClients": [...]}, {"serviceKeys": [...]},
{"authorizedApplications": [...], "listMetadata": {...}}, {"scopes": [...]},
{"platforms": [...]}, {"assignments": [...], "nextPageToken": "..."},
{"resources": [...], "nextPageToken": "..."},
{"profiles": [...], "nextPageToken": "..."}, {"members": [...]},
{"invitations": [...]}, {"invoices": [...], "nextCursor": "..."}), a
single item is the bare object, a deletion, release, removal or revocation
prints the API’s response with no deleted, left, released, removed or
revoked boolean, and a field the API adds shows up in the JSON.
org oauth-client update reads the client before it changes it and prints the
response to the change,
project delete reads the project, to ask for its name, and prints the
response to the deletion, and org member edit, org member remove and
org leave read the organization’s members (and org leave your account) to
find the membership’s version, and print the response to the change.
voice profile edit and the voice profile inbound and outbound commands
read the profile for its version, and print the response to the change.
billing operation view and billing plan purchase wait for the billing
operation, and print the first answer Photon gives that isn’t pending: for a
purchase, the purchase’s own response when it’s already final, or else the
operation read while waiting. A purchase whose operation failed prints the
error document instead, with BILLING_OPERATION_REJECTED. operation view
prints a failed operation like any other and exits 0; its status is
failed.
The project platform commands that start an operation wait for it and print
the last operation read, {"operation": {...}, "resource": {...}}, with the
number, line, domain, bot or WhatsApp sender that a purchase, connect or
number add sets up in resource. A
purchase whose last operation read has no resource reads the operation once more
and prints that read. sms delete, email disconnect, telegram disconnect
and whatsapp-business number delete read the resource to check it (that the
number can send SMS, that it’s an email domain or a Telegram bot, or that it’s
an active WhatsApp Business sender), then release it with
DELETE /v1/projects/{id}/platforms/resources/{resourceId}. With --no-wait,
while the setup runs, email connect and telegram connect print Photon’s
answer to the request that started it, which holds the DNS instructions or the
setup link in operation.detail. email connect prints the operation read
that holds the records instead when that answer doesn’t hold them yet, and
telegram connect prints its read of the setup when it continues one already
in progress. An answer to a request, like the answer to a release that
has already ended, is the operation itself rather than an operation read, so the
command prints it under operation. It’s the one place the CLI shapes these
documents, so the command keeps the operation read’s {operation, resource}
shape on every path. telegram connect prints no resumed field when it
continues a setup already in progress: no Photon response carries it, and the
rule is to print the response as sent. Only its text says it’s continuing, as
kubectl apply -o json prints the object and says created or configured
only in text.
project agent-profile avatar upload prints the profile Photon returns when it
commits the upload, and account avatar upload the account.
Thirty-five commands print a document the CLI defines. Photon answers the two
revocations and voice profile delete with no body, so they name what they
revoked or deleted, voice assignment set and clear change one line or
several, so they list one assignment for each --resource-id, as the table
below describes, billing overview puts the project’s billing next to its
effective terms, billing subscription cancel and resume put the billing
operation of the plan change next to what it means for the subscription, or
say that nothing changed, authorize builds
an authorization request of its own, project current,
project create --link and link (and their deprecated old names,
project create --use and the hidden project use) put the API’s project next
to where the CLI found it or saved it, org show and org use put the API’s
organization next to where the CLI found it or saved it (path is there for
source: "folder" only), org list adds the ID of the organization the CLI
would use, activeOrganizationId, to the API’s response, org clear and
unlink (and the hidden project clear, its deprecated old name) make no API
call, payment-method setup puts the API’s checkout next to whether the
browser opened it, the email, imessage, sms and telegram list commands
put the API’s resource page next to the provisioning operations a second read
finds, whatsapp-business account list lists the project’s one account, or
none, so a project without one prints an empty list and exits 0, as
gh pr list --json and kubectl get -o json do, and
whatsapp-business number add with --source byon or --new-waba hands off
to the dashboard, which no API response describes.
The sign-in commands describe the CLI’s own sign-in: login and
whoami name the account it signs in as, auth status says where the
sign-in comes from and checks it with Photon when it can, and logout makes
no API call. update asks the package registry, or GitHub’s releases for a
standalone binary, instead of Photon’s API, and the operation commands read
the local journal:
operation replay defines fourteen more. When it replays a project delete, a
project webhook delete or an iMessage assignment release that already went
through, Photon answers 404 and sends nothing to print, so the replay’s
result names what is gone: {"deleted": true, "projectId": "..."} for the
project, {"deleted": true, "destinationId": "..."} for the destination and
{"assignmentId": "...", "released": true} for the assignment. The same goes
for an SMS delete, a WhatsApp Business number delete, an email
disconnect or a Telegram disconnect that Photon answers with 404, for the
number, sender, domain or bot, its release or the release’s operation, when
the number, sender, domain or bot is gone (or the number, sender or domain is
retired): {"deleted": true, "resourceId": "..."} for the number or sender
and {"disconnected": true, "resourceId": "..."} for the domain or bot. A
replayed SMS delete or WhatsApp Business number delete that has no release
operation to read, because the number or sender was already retired, prints it
as Photon sends it: {"resource": {...}}. A replayed
project create --link can’t save the folder’s file, because the journal
doesn’t record the folder, so its result is {"project": {...}} with the
API’s project, and it prints the photon link step that saves the file (on
stderr with --json). When an account service-key revoke went through but
the CLI stopped before it finished printing the result, the replay doesn’t
revoke the key again, and the journal doesn’t keep Photon’s response, so its
result names the key: {"revoked": true, "serviceKeyId": "..."}. The
journal records the plan change of a billing subscription cancel or
resume as the plan purchase it is, so its replay’s result is what
billing plan purchase prints: the billing operation as Photon sends it,
which the command itself prints under operation, without subscription.
A replayed email connect or telegram connect doesn’t wait for Photon to
verify the records or for the bot to connect. Its result is Photon’s answer
as the replay got it, with the setup under operation. While the setup is
still running, that’s what the command prints with --no-wait: the DNS
records or the setup link, under operation. When Photon sends the resource
beside the setup, as it does for an email domain whose setup has finished,
it’s under resource.
Errors
Without--json, an error is text on stderr. With --json, it’s the one JSON
object on stdout, in place of the result, and the exit code is the same as
without --json. The one exception is a command that fails after its result
is already on stdout, or while writing it there: the error object goes to
stderr, so stdout never holds two objects.
- Terminal
- JSON
Each step in
next has a description, plus a run with the full command or a
url to open when there is one, and person: true when only a person can do
it, such as adding a payment card:
next holds the fix the API sends in the problem’s
remediation. The CLI doesn’t write steps of its own for API errors.
For invalid input (ARGUMENT_VALIDATION), next holds the steps written for
that error. When a command finds invalid input and the error has no steps of
its own, it gets one step instead: the command with --help, which lists its
valid options. An unknown command, an unknown option, a missing argument or an
ID in the wrong format is rejected before the command runs, and gets one step
without a run: run the command with --help for its valid options. A
--waba-id that isn’t digits is the exception: when it’s the only input
rejected, its error gives the command that lists the project’s connected
WhatsApp Business Account. A full API key given to project api-key revoke
isn’t rejected before the command runs: the command takes it, and its error
gives the command to run with the key’s ID.
The terminal shows the same fields as text: the message with the code and
reason, any issues, the steps under Next, then the request ID and the docs
link. Each step shows its description, then its command or link:
problem appears only with --debug. JSON output includes
problem whenever Photon sent one, and --debug adds a debug field. An error
with no step ends with a
general hint chosen by retryable, the HTTP status and whether the error has a
detail. A retryable error that’s safe to repeat gets none.
When stdout fails
When the CLI can’t write its output to stdout, as with a full disk, it prints one line on stderr and exits1. With --json, the line is the error
object:
1 without a message,
and so does a command whose stderr can’t be written either. A command that
keeps its result in the operation journal
until stdout takes it, such as a new API key, reports a closed pipe too, since
running it again would make a second key: its error names the replay that
prints the result again.
Prompts and confirmation
The CLI asks a question only when stdin, stdout and stderr are all terminals,CI isn’t on, and there’s no --json, --no-input or PHOTON_NO_INPUT.
Questions go to stderr. When it can’t ask, the command fails before it changes
anything, names the option that supplies the answer, and says why it couldn’t
ask.
A command that costs money or can’t be undone asks first. --yes, or -y,
confirms without being asked, and --yes=false is the same as leaving it out.
At a terminal the command asks [y/N]; deleting a project means typing its
name. Answering no changes nothing, prints Cancelled. on stderr and exits
1. Without a terminal and without --yes, the command changes nothing and
fails with CONFIRMATION_REQUIRED and exit code 2. The message says what
would have happened, and next is the same command with --yes:
--force is only for overriding a safety check: login --force replaces a
healthy session, and logout --force removes the local files of a credential
the CLI can’t read.
A command reads stdin only when you ask it to: login --service-key, or an
option given -, such as --token-file -. Piped input is never read
otherwise; a command that needs a value it wasn’t given fails and names the
option.
Exit codes
Exit code
2 is what a script gets when it leaves out --yes on a command
that confirms. It’s a usage error, not an approval: nothing was changed.
No exit code means “a person must act”: that’s a step in next with
person: true. Whether trying again can help is retryable, not an exit
code.
A signal ends a command with 128 plus the signal’s number: 130 for
Ctrl-C (SIGINT), 143 for SIGTERM and 129 for SIGHUP. Each
winds the command down the same way: it stops, prints its interrupted error
(COMMAND_INTERRUPTED, or a command’s own, such as LOGIN_INTERRUPTED), and
leaves the operation journal as Ctrl-C leaves it. A
second signal ends the process at once with 128 plus that signal’s number. A
GitHub Actions cancel sends SIGINT, then SIGTERM if the step is still
running 7.5 seconds later, so a command that hasn’t finished winding down by
then ends in 143.
Help
--help prints a command’s help, and so does photon help before the
command. A group run without one of its commands prints its help too:
--json, a group run on its own prints the same reference as
--help --json (see Beta commands), and photon --json
prints the CLI’s name and version.
Stability
From 3.0.0, the 3.x promise covers only what the CLI defines itself: command names, flags, exit codes, error codes, and the JSON the CLI defines (errors,login, whoami, update and workflow commands). Output that comes from
the API follows the API: the golden tests on SDK update pull requests catch a
change, and the release notes name it. A workflow command is one that makes
more than one call, or reads local state such as org list’s active
organization. When a workflow command prints an API response as Photon sends
it, as project delete and org member edit do, what’s fixed is which
response it prints, and the response’s fields follow the API. For a command
that prints an API response, the golden tests type the response against
Photon’s API SDK, so the pull request that updates the SDK fails when it
removes or changes a field those tests use.
The JSON the CLI defines is the error object, the documents in the
table under Envelopes and, from the prose there, the
{"operation": {...}, "resource": {...}} and {"operation": {...}}
documents of the project platform commands that start an operation, the
--help --json reference and photon --json. The
debug trace isn’t part of it: its shape may change between
releases. What’s fixed doesn’t change or go away in a 3.x release, but a 3.x
release may add fields, commands, flags, error codes and enum values. So a
script should ignore a field it doesn’t know and expect an enum value it
hasn’t seen. A beta command can still change itself.
Beta commands
Commands that call API operations Photon hasn’t finished documenting are labelled beta. Their help description ends in(beta), in the command’s own
help and in its parent’s command list:
--help --json prints a command’s reference as one JSON object on stdout, with
"beta": true on beta commands:
@photon-ai/api with a description, and each label is
reviewed every 90 days.
Debug traces
Global--debug buffers sanitized HTTP exchanges and emits one ordered trace
to stderr when the command finishes:
--json command keeps its single result object on stdout and
writes a separate {"debug":{"http":...}} object to stderr. A failed JSON
command stays one error object, with the exchanges and sanitized cause chain
under error.debug. Commands that make no HTTP requests emit no trace.
Each exchange records the method, sanitized endpoint, query-parameter names,
safe headers, JSON bodies when safe, status, request ID, and duration. Long
traces keep the first three and latest 22 exchanges and report how many were
dropped in between.
Text from the API
Text output writes every control and format character in a string from the API as visible text, so a name or message can’t move the cursor, change colors, break a line or reorder what follows. Control characters print in caret notation (\u001b[31m prints as ^[[31m, a line break as ^J), and
format characters such as a right-to-left override print as \u{202e}.
Joiners are format characters too, so an emoji sequence such as 👩💻 prints as
👩\u{200d}💻. JSON keeps the string as the API sent it: the encoder escapes
U+0000 to U+001F, quotes and backslashes, and writes every other character as
it is.
Color
Human output uses compact tables and labeled detail views. ANSI styling is enabled only for terminal streams, and text labels and symbols always carry the same meaning without color. Styling is disabled for JSON, for redirected output, forTERM=dumb, for a
non-empty NO_COLOR or NO_COLORS, and for --no-color. FORCE_COLOR=1
enables it for a non-terminal stream unless --no-color or --json disables
it. An explicit --color styles a terminal even when NO_COLOR, NO_COLORS,
NODE_DISABLE_COLORS or FORCE_COLOR=0 is set, unless TERM is dumb.
Environment variables
PHOTON_NO_INPUT, PHOTON_NO_UPDATE_NOTIFIER and CI are on when set to
anything except empty, 0 or false.