OpenCI CLI
Command-line tools for OpenCI authentication, team switching, secrets, workflow paths, and local development.
Install
Install with Dart 3.11.5 or later:
dart install openci_cli_plus
openci --help
If openci is not found, follow the dart install PATH setup.
Run openci update to update to the latest stable version on pub.dev.
If you previously installed genuineci_cli, run dart install openci_cli_plus
once to switch packages. The new openci command uses your existing login
profiles and language settings.
In an interactive terminal, commands check for a newer stable version and ask
Update now? [y/N]. Enter y or yes to install it, then rerun your original
command with the updated CLI. Enter n, no, or press Enter to continue without
updating. Updates use dart install, so the Dart SDK must be on PATH.
Automatic checks time out after two seconds and silently skip network errors.
They are skipped for help, version, and update commands, when CI is set, or
when stdin, stdout, or stderr is redirected. Use
openci --no-check-updates <command> to skip the check explicitly.
openci update also works in scripts
without confirmation and returns a nonzero exit code if checking or installing
fails.
The remote commands work from any directory. Local development commands require an OpenCI checkout and the services described below.
Shell completion
Bash and Zsh completion is provided by cli_completion. Completion scripts are
installed automatically when running openci --help or another command in an
interactive terminal outside CI. Restart your shell afterwards, or install them
explicitly and follow the printed instructions:
openci install-completion-files
For Zsh, ensure autoload -Uz compinit and compinit run in ~/.zshrc before
the completion script is sourced. Reload with source ~/.zshrc.
Press Tab to complete every command and subcommand, their options (including global flags), and known argument values:
openci reg<Tab> # register
openci register <Tab> # secret, secretFile, options
openci sync <Tab> # paths, secrets, options
openci use <Tab> # japanese, english, options
openci help register <Tab> # secret, secretFile, options
openci login --ser<Tab> # --server
openci login --server <Tab> # default and saved HTTPS servers
openci login --team-id <Tab> # saved team IDs for the selected server
Login value completion reads local credential profiles only. --firebase-api-key
also suggests the default and saved Firebase Web API keys for the selected server.
New server URLs, team IDs, and Web API keys can still be entered directly.
Completion does not fetch team lists or refresh tokens. Completion requests and
completion setup commands skip automatic update checks.
Use openci uninstall-completion-files to remove the scripts.
Usage
Log in to a remote server with the same email and password as the dashboard:
openci login
The default server is https://openci-worker-01.tail4beb18.ts.net.
To connect to another server, pass the optional --server <url> option.
Enter your email and password at the prompts. The password is hidden and is only
sent to Firebase Authentication; it is never saved. After the server confirms
your team membership, the CLI saves and activates the remote credential
profile, keeping the local profile. Failed login attempts preserve existing
credentials.
If you belong to multiple teams, specify --team-id <id> using the displayed
list. For a self-hosted Firebase project, also pass its Web API key with
--firebase-api-key <key>; the default is the dashboard's openci-b1b91 project.
The remote server URL must use HTTPS.
Secret commands use this profile and automatically refresh expiring Firebase ID
tokens. Credentials, including the refresh token, are stored in the openci
application config directory with owner-only permissions on macOS/Linux. If an
older development build stored your credentials in a openci configuration
directory, log in again; those credentials are not migrated.
Check the active profile, server, and current team:
openci status
Profile: remote
Server: https://ci.example.com
Selected team ID: team-id
Team name: My team
The command fetches the current team name from the saved server using the active
profile's team ID. It works from any directory and supports redirected output.
It preserves the selected team and other profiles; expiring Firebase tokens are
refreshed automatically. The saved profile, server, and team ID are displayed
even if the team name cannot be fetched. If the selected team is no longer
available, run openci switch team. Errors go to stderr and return a nonzero
exit code. A fresh installation shows a login hint and exits successfully.
List all teams available to the active profile:
openci list teams
Alpha (team-a)
* OpenCI (team-b)
Each line shows a team name and ID, sorted by name and then ID. * marks the
selected team. Listing teams does not change the selected team or active profile;
expiring Firebase tokens are refreshed automatically. It works with redirected
output and does not require a selected team. An empty list shows a message and
exits successfully; authentication, connection, and invalid-response errors go
to stderr and return a nonzero exit code.
Switch the active profile's team without logging in again:
openci switch team
Login, status, team listing and switching fetch memberships from the
teamManagement Firebase callable Function, backed by FSC. The active profile's
Firebase project (or the ID token's audience) and functions_region select the
endpoint. --server remains the address for other legacy APIs. A local profile
uses its saved Auth/Functions Emulator settings (Functions defaults to port 5001).
To activate the remote or local profile, run the corresponding login command first.
The picker shows each team's name and ID, with * marking the saved current
team and > marking the highlighted choice. It starts on the current team if
that team is still available, otherwise on the first candidate.
- Use the up/down arrow keys to move; navigation wraps at either end.
- Press Enter to confirm, even if only one team is available.
- Press Esc or Ctrl+C, or end the input, to cancel.
Confirmation updates only the active profile's teamId. The remote and
local profiles each keep their own current team; selecting a team updates
the existing profile. Server and Auth Emulator settings, refreshed tokens, and
other profiles are preserved. Choosing the current team succeeds without a
write. Cancellation or a failed save preserves the previous selected team.
Expiring authentication tokens may still be refreshed before the picker opens.
If the active profile or its credentials change during selection, the CLI
refuses to overwrite them and asks you to run openci switch team again.
Subsequent openci list secrets, openci register secret,
openci register secretFile, openci delete secret SECRET_NAME, and
openci sync --secrets commands use the newly
selected team. Existing openci/generated/secrets.g.dart files are updated by running
openci sync --secrets in the workflow project.
Team switching requires interactive stdin and stdout with ANSI support. Run it directly in a terminal, without piped input or redirected output. Success and unchanged-selection messages go to stdout; errors and cancellations go to stderr and return a nonzero exit code.
Troubleshooting team selection:
| Condition | What to check |
|---|---|
| Login required or HTTP 401/403 | Log in again for the intended server with openci login or openci login --local. |
| No team memberships | Create or join a team in the dashboard for that server. |
| Interactive terminal required | Use an ANSI-capable terminal with interactive stdin and stdout; remove pipes and output redirection. |
| HTTP error or connection failure | Check the active profile's server URL and network connection. For local development, check that the Auth and Functions emulators are running. |
| Invalid team list | Check that the saved server URL points to a compatible OpenCI API. |
| Changed profile or credentials | Run openci switch team again using the current authentication. |
| Save failure | Check the credentials file, its permissions, and available disk space. |
Choose English or Japanese messages with openci use english or
openci use japanese. The language setting is saved for future invocations.
List secret names for the active profile's team:
openci list secrets
Names are printed one per line, sorted by name, without secret values. If the team has no secrets, the command prints a message and exits successfully. It works from any directory, supports redirected output, and does not generate workflow files. Control characters in names are displayed as escaped text.
Delete a secret by name from the active profile's team:
openci delete secret SECRET_NAME
The command deletes the named secret immediately, without a confirmation prompt,
and works in scripts. It returns exit code 0 on success, 1 if the secret is not
found or the request fails, and 64 for invalid arguments. Secret values are never
fetched or displayed. After deletion, run openci sync --secrets from your
workflow project to update its generated secret definitions.
Register a secret for the active profile's team:
openci register secret
Enter the secret name (for example, API_TOKEN), then its value in an interactive
terminal. The value is hidden while typing. After you press Enter, the prompt
shows ******, regardless of the value's length.
Registering an existing name updates its value. The CLI uses the dashboard's
existing API, which trims leading and trailing whitespace from values. Values
are not printed or saved locally. Firebase tokens are refreshed when needed.
After saving, the command automatically runs openci sync to update both
openci/generated/paths.g.dart and openci/generated/secrets.g.dart. Run it from
your workflow project or a subdirectory. If sync fails, the secret remains saved
and the command returns exit code 1; fix the reported problem and retry
openci sync. Cancelling input or a failed save does not run sync.
Register a file as a Base64 secret:
openci register secretFile
Type a file path to filter the displayed candidates. Press Tab or the up/down
arrow keys to complete a candidate, then Enter to enter a folder or select a
file. Relative paths, absolute paths, ~/, spaces, and Unicode filenames are
supported. Type . to include hidden files such as .env. Press Esc or Ctrl+C
to cancel. The picker requires an interactive terminal with ANSI support.
The selected file is read as bytes and Base64-encoded. Its basename, including
the extension, is uppercased, runs of characters other than letters, digits and
underscores become _, and _BASE64 is appended. Names beginning with a digit
are prefixed with _. For example, google-services.json becomes
GOOGLE_SERVICES_JSON_BASE64. Selecting an existing name updates that secret.
Empty files are rejected. Neither the file contents nor the encoded value is
printed or saved locally. After saving, the command automatically runs the same
sync as register secret, updating both generated files. A sync failure leaves
the uploaded secret saved and returns exit code 1; retry openci sync after
fixing the reported problem.
Local development
The legacy openci dev start command and its PostgreSQL/API/Orchard Compose
stack have been retired. The CLI's team and Secrets commands use Firebase
Functions. See Firebase development for the Auth
Emulator and FSC deployment for the
Functions, Data Connect and Cloud Run setup.
To log in locally, start the Auth, Functions and FSC emulators, create an Auth user and its FSC team membership, then run:
openci login --local
Enter the credentials of a user you created in the Auth Emulator UI. The CLI
signs in at 127.0.0.1:9099, then calls teamManagement on the Functions Emulator
at port 5001. It selects test-team only if FSC returns it among the user's
memberships. See the team migration guide.
Successful login saves and activates the local Firebase profile, including
the refresh credentials and firebase_auth_emulator_host. Later token refreshes
use the same emulator. Failed or cancelled login preserves saved profiles and
never falls back to real Firebase. To replace an older local profile containing
an internal API key, run openci login --local again after preparing the user
and team; successful login replaces only that profile. The password is never
saved. The remote --server, --team-id, and --firebase-api-key options cannot
be combined with --local.
Run openci sync from your workflow project to generate both typed secret
definitions and workspace paths:
openci sync
The files are written to openci/generated/secrets.g.dart and
openci/generated/paths.g.dart. The generated directory is created if needed.
Use --secrets or --paths to generate only that file; passing both flags
generates both. Each selected generator runs even if the other fails, and the
command exits unsuccessfully if either selected generator fails.
With an authenticated credential profile, generate only secret definitions:
openci sync --secrets
The command uses the active credential profile and finds the nearest ancestor
containing an openci directory, starting from the current directory. It
replaces openci/generated/secrets.g.dart with constant Secret references from
openci_workflow. Each reference preserves the original environment variable
name in .key and reads its runtime value through .value:
import 'generated/secrets.g.dart';
final secret = Secrets.iosBuildNumber;
final key = secret.key; // IOS_BUILD_NUMBER
final value = secret.value; // The environment variable's value as a String.
Creating or passing a reference and reading .key do not require an environment
variable. Reading .value throws a StateError if the variable is absent; an
empty value remains an empty string. Secret values are never downloaded or
written to the generated file.
Run it again after adding or removing secrets. Fetch or generation failures leave
the existing file unchanged.
When regenerating an existing workflow, replace value usages such as
Secrets.apiToken with Secrets.apiToken.value, including string interpolation.
Use openci_workflow 0.1.5 or later, which exports Secret. Deploy the server's
updated secret-definition parser before running workflows with the new format;
the parser continues to support previously generated String getters.
Generate typed workspace paths without logging in or starting local services:
openci sync --paths
Run this from your workflow project or one of its subdirectories. The command
reads the workspace list in the root pubspec.yaml and each listed package's
name, recursively collects their subdirectories, then writes
openci/generated/paths.g.dart following the directory hierarchy. For example,
apps/dashboard/android/app becomes
WorkspacePaths.root.apps.dashboard.android.app. If workspace is absent, the
command treats the root package as a single-package project and collects its
subdirectories. For example, android/app becomes
WorkspacePaths.root.android.app, and lib becomes WorkspacePaths.root.lib.
An explicitly empty workspace: [] still generates only the root accessor.
Hidden directories, symlinks, and generated or dependency directories (build,
coverage, node_modules, Pods, ephemeral, and xcuserdata) are excluded at
every level. openci/generated is also excluded so repeated syncs produce
stable paths. Directory names determine the getters; package names are used to
validate the workspace.
Dart keywords and Object member names get a trailing underscore: switch
becomes switch_, class becomes class_, and hash_code becomes hashCode_.
The directory paths themselves are preserved. Sibling names that map to the same
getter, such as switch and switch_, are rejected.
Run it again after adding, moving or renaming workspace directories. Read or
generation failures preserve the existing file.
The previous sync secrets and sync paths subcommands are replaced by these
flags. When upgrading existing workflows, change imports to
generated/secrets.g.dart and generated/paths.g.dart, then remove the old
files from openci/ after updating their imports.
Import the generated file in your workflow and run it from the repository root, as the worker does, because these paths are relative to that root:
import 'package:openci_workflow/openci_workflow.dart';
import 'generated/paths.g.dart';
final openCI = await OpenCI.init(
workflowName: 'Dashboard CI',
ciTriggers: [CITrigger.push(branch: 'develop')],
currentWorkingDirectory: WorkspacePaths.root.apps.dashboard,
);
await openCI.flutter.staticAnalysis();
await openCI.flutter.unitTests();
openCI.flutter uses the same working directory as openCI.run().
It resolves currentWorkingDirectory relative to the workspace root, or uses
the workspace root when no directory is configured. Both Flutter methods also
accept dir, for example
await openCI.flutter.unitTests(dir: WorkspacePaths.root.apps.dashboard);.
The override is relative to the workspace root and applies only to that call;
later calls without dir continue to use the configured working directory.
WorkspacePaths.root represents . and WorkspacePaths.root.apps represents
apps. Both can also be passed directly to methods accepting a String path.
openci setup asc-keys creates a key on Apple Silicon Macs (macOS arm64)
and saves its credentials as three secrets for the active OpenCI team. It also
prepares a certificate private key for iOS signing, keeping any existing key.
First run openci login (or openci login --local) and select the intended team.
Setup checks access to that team's secrets before contacting Apple, displays
the server, team ID, and secret names, and indicates which existing secrets
will be replaced.
It reuses a regular asc 5.11.0 file in OpenCI's cache at
<cache>/tools/asc/5.11.0/macOS_arm64/asc. If absent, it downloads the pinned
official release,
verifies its SHA-256, sets owner-only executable permissions, and moves it into
the cache alongside its MIT license. Failed downloads are cleaned up.
Before running either an existing or newly installed binary, it verifies the
cached file's SHA-256 again. It then runs asc version using the absolute cache
path and requires exit code 0 and version 5.11.0. A checksum mismatch prevents
execution; an execution failure, unexpected version, or 10-second timeout stops
setup with an error. A timed-out process is terminated.
After verification, it prompts for an Apple ID email address in an interactive
terminal. Surrounding whitespace is trimmed; empty input or EOF stops setup
with exit code 1. Ctrl+C at the email prompt interrupts it with shell exit code 130. Input and prompt output (stderr) must both be attached to a terminal.
The prompt leaves stdin open and does not change terminal modes. It then starts
the verified asc binary with web auth login --apple-id <email> --output table,
inheriting the terminal's stdin, stdout, and stderr. Password and two-factor
prompts are handled directly by asc, which manages its own saved credentials
and web sessions. OpenCI does not read those inputs.
The command waits for asc to exit and propagates a nonzero exit code (negative
signal codes are mapped to 128 + signal). A failure to start asc produces a
localized error. Cancellation inside asc follows asc's exit status; asc 5.11.0
returns 1 when its password prompt is interrupted.
After login exits with code 0, OpenCI checks the same Apple ID with
web auth status --apple-id <email> --output json. The check requires exit code
0 and a JSON object with authenticated: true; asc can also exit with code 0
for an unauthenticated session. A false value stops setup with a sign-in message.
Execution failures, malformed responses, and a 30-second timeout produce
localized errors. Raw status output is limited to 16 KiB and is not printed; a
timed-out process is terminated. The authentication result retains the optional
providerName, providerId, and publicProviderId fields. Once authentication is
confirmed, OpenCI displays the returned name (when available) and IDs of the
currently selected App Store Connect provider. If neither ID is available, setup
stops. Other team IDs are not used as substitutes. This response describes the
current provider, not a list of teams. The pinned asc 5.11.0 does not emit
providerName, so its output continues to display IDs only.
After displaying the provider, OpenCI asks whether to create a new key
named OpenCI with APP_MANAGER access to all apps and save it to the
displayed OpenCI destination. Only y or yes proceeds;
other input or EOF cancels without requesting a key. The Apple account must be
an Account Holder or Admin to create a team key. The verified binary runs
web api-keys create --apple-id <email> --provider-id <id> (and/or
--public-provider-id <id>) with --name OpenCI --role APP_MANAGER, an
explicit output directory, and --output json. Users do not enter provider IDs.
Both returned IDs are passed when available so asc can verify they match.
For a key created by the previous CLI, pass its original directory to
openci setup asc-keys --key-directory <directory> to resume registration.
Each issuance uses a new owner-only directory under
~/Library/Application Support/openci/asc-api-keys/. Its path is printed
before creation. asc saves the one-time AuthKey_<KEY_ID>.p8 with mode 0600;
OpenCI validates the result and saves key.json containing the Key ID,
Issuer ID, provider IDs, role, and private-key path with mode 0600. Private key
contents and raw asc output are never printed. asc handles any reauthentication
through /dev/tty and applies its own network timeouts.
Setup prepares four secrets for the active team:
| Secret name | Value |
|---|---|
OPENCI_GENERATED_ASC_KEY_ID |
The Key ID as text |
OPENCI_GENERATED_ASC_ISSUER_ID |
The Issuer ID as text |
OPENCI_GENERATED_P8_BASE64 |
The complete P8 file encoded as Base64, matching register secret-file |
OPENCI_GENERATED_IOS_CERTIFICATE_PRIVATE_KEY |
A persistent RSA private key generated on the server if absent; existing keys are preserved |
The server encrypts each value in its existing secrets table. Setup refreshes OpenCI authentication before saving and stops if the active profile or credentials changed while Apple authentication was in progress. Secret values are not logged. After saving the three ASC values, setup checks whether the certificate private key exists. If absent, it calls the server's iOS signing key endpoint and verifies that the secret was saved. The private key is not returned to the CLI. The server preserves existing keys even during concurrent requests. Deploy the server's updated key-generation endpoint before using this CLI update.
Success means the ASC saves succeeded and the certificate key is present;
local ASC files are retained. Saves are sequential, so a failure or interruption
can leave a partial update. Retry with the same local ASC key. This step prepares
the private key; signing certificates and provisioning profiles are obtained
during the build. Run openci sync --secrets afterward to generate the references
used by workflows.
Creation is never automatically retried. On failure or interruption, downloaded files are retained; check the displayed directory and App Store Connect before issuing another key. If a key was saved locally, retry only the OpenCI save:
openci setup asc-keys --key-directory '/path/to/asc-api-keys/key-xxxxxx'
This reads key.json and its matching .p8, displays the destination and key
IDs, and asks for confirmation. It does not install or run asc or contact Apple.
Existing ASC secrets are replaced only after confirmation. Save or certificate
key preparation failures exit with code 1 and print this retry command. Each
retry saves all three ASC values, including any that were already saved, then
prepares the certificate key if missing. Existing certificate keys are kept.
Provider switching remains a future step.
Use openci setup asc-keys --help to view its help.
To prepare only the certificate private key for the active OpenCI team, run:
openci setup ios-certificate-key
This command requires OpenCI login and a selected team. It generates and saves
OPENCI_GENERATED_IOS_CERTIFICATE_PRIVATE_KEY on the server if missing, or keeps
the existing key. It verifies that the secret is present before reporting success.
It does not require Apple login, ASC keys, or local signing tools, and does not
issue or replace ASC credentials. The private key is not downloaded or printed.
You can rerun the same command after a failure. Run openci sync --secrets
afterward to update the workflow secret references.
Code generation
Run these commands from apps/openci_cli:
dart run slang
dart run build_runner build
Translations use slang.yaml through the Slang CLI. build_runner generates
the Freezed and JSON models.
Secrets backend
Secrets commands use the Firebase teamSecrets callable in asia-northeast1. The Firebase project is inferred from the current login ID token. Firebase Auth emulator profiles use the Functions emulator on loopback port 5001. Static API-key profiles must log in with Firebase. See the cutover guide for deployment and existing-secret migration.