@philip-oesterlin-w3/cli
v0.1.18
Published
W3 Standard CLI — manage secrets and provision servers with bundled Ansible tooling
Readme
@philip-oesterlin-w3/cli
Status: WIP — this CLI is a proposal / RFC. The interface may change while the standard is being reviewed.
The W3 Standard CLI. Manages encrypted secrets with SOPS + age and provisions W3 hosts with Ansible using one cross-platform command surface.
Install
From npm (recommended — ships the prebuilt native binary per platform):
npm install -g @philip-oesterlin-w3/cliFor local development, build and install the current platform from the checkout:
cd cli
bun install
bun run build # builds and installs through npm's global prefix
bun run build:all # emits binaries into packages/<platform>/bin/The published @philip-oesterlin-w3/cli package is a small Node launcher. npm installs one
matching optional native package for the current operating system and CPU:
| Package | Platform |
| :--- | :--- |
| @philip-oesterlin-w3/cli-linux-x64 | Linux x64 |
| @philip-oesterlin-w3/cli-linux-arm64 | Linux ARM64 |
| @philip-oesterlin-w3/cli-darwin-x64 | macOS Intel |
| @philip-oesterlin-w3/cli-darwin-arm64 | macOS Apple silicon |
| @philip-oesterlin-w3/cli-windows-x64 | Windows x64 |
Commands
| Command | What it does |
| :--- | :--- |
| w3 init | Downloads sops + age into ~/.w3/bin, configures the shared age key, and verifies an encrypt-decrypt round-trip. Points you at w3 secrets sync if a local .env is present. |
| w3 uninstall | Removes downloaded sops/age programs, the fallback Ansible controller cache, and the shared age key. The npm-installed CLI and project files remain untouched. |
| w3 audit [url] | Renders a public website in an isolated headless browser and checks technical go-live readiness: links with source context, custom 404 behavior, sitemap coverage, standard-file content, transfer budgets, language/hreflang, axe-core accessibility, SEO, console errors, and failed requests. Without a URL, uses an environment from w3.yaml. |
| w3 secrets edit [file] | Decrypts .env.sops to a secure temp file, opens your editor, re-encrypts on close, and purges the plaintext. |
| w3 secrets decrypt [file] | Writes decrypted values to .env (chmod 600). --stdout prints; --output chooses the path. |
| w3 secrets diff [file] | Shows local .env vs decrypted .env.sops differences (- encrypted, + local). No changes. |
| w3 secrets sync [file] | Interactive multi-select (arrow keys + space, like sv) of which local .env variables to copy into .env.sops, then re-encrypts. --yes selects all. |
| w3 server | Selects one configured environment and makes its server ready. First use creates w3.yaml. |
| w3 server add | Adds an environment on an existing or new server target, then provisions it. |
| w3 server status | Read-only infrastructure checks for every environment. --environment narrows the check. |
| w3 server shell | Selects an environment and opens an interactive SSH shell (ssh -t). On Windows, SSH runs inside WSL. Use --environment to skip the prompt. |
| w3 server logs | Selects an environment and downloads its logs into logs/<environment>/: on-disk app files, app docker compose logs as docker-compose.log, and shared Traefik/proxy logs as traefik.log. Uses SSH only (WSL on Windows); extraction is built into the CLI. Use --environment to skip the prompt and --output PATH to choose another destination. |
| w3 server bitbucket | Syncs Bitbucket Pipelines setup for the project: creates bitbucket-pipelines.yml if missing, upserts deployment variables for every environment in w3.yaml, and refreshes Pipelines SSH for a selected environment's server. Use --environment to pick the SSH target and --yes to skip prompts. |
| w3 server key | Prints the local ~/.ssh/id_ed25519.pub line for pasting into a cloud SSH key field (reads that file inside WSL on Windows). Creates the key if missing. |
| w3 server unban | Shows IP addresses blocked by Fail2ban, confirms, clears all bans, and verifies the result. Use --environment to select a server and --yes for automation. |
| w3 server protect | Interactively sets or changes an environment's basic-auth username and password in w3.yaml. A server sync is required to activate Traefik basic auth; the command offers to sync immediately. Use --environment to select one and --yes to sync without confirmation. |
| w3 server preview --environment <name> | Runs Ansible check/diff for one environment without changing it. |
| w3 server setup | Prepares the bundled Ansible runtime and collections. Development builds may use the cached fallback environment. |
| w3 server provision <host> | Runs the infrastructure playbook for one inventory host. Add --bootstrap for the first root SSH connection. |
| w3 server check [--limit <host>] | Runs Ansible in check/diff mode. |
| w3 server syntax | Syntax-checks the playbook. |
| w3 server list | Lists the inventory through ansible-inventory. |
| w3 server validate <user@host> | Runs the remote Docker/Swarm/backup health check over SSH. |
| w3 server inventory | Advanced legacy inventory editor for direct Ansible use. Normal setup derives temporary inventory from w3.yaml. |
| w3 server config | Alias for w3 server inventory. |
w3 secrets with no subcommand prints this group's help.
Options
| Flag | Applies to | Meaning |
| :--- | :--- | :--- |
| --yes | init, secrets sync, server, server bitbucket | Skip prompts (generate a key / select everything / apply defaults). |
| --file <path> | secrets edit/decrypt/diff/sync | Encrypted env file to operate on. |
| --output <path> | audit, secrets decrypt, server logs | Audit: HTML report path. Decrypt: plaintext output path. Logs: download directory. |
| --stdout | secrets decrypt | Print decrypted values to stdout instead of writing a file. |
| --environment <name> | audit, server | Select the environment explicitly. |
| --max-pages <number> | audit | Maximum number of internal URLs to crawl (default 50, maximum 500). |
| --timeout <seconds> | audit | Per-request timeout (default 10 seconds). |
| --http-only | audit | Skip browser rendering (faster, misses client-rendered content). |
| --no-interactive | audit | Skip the manual Pass/Fail/Skip walkthrough. JSON and non-TTY runs skip it automatically. |
| --json | audit | Print a machine-readable, versioned audit report. |
| --infra <path> | server | Override the bundled infrastructure definition. By default, w3 uses the infrastructure shipped with the platform package. |
| --inventory <path> | server | Override the inventory file. |
| --playbook <path> | server | Override the playbook. |
| --limit <host> | server check | Limit a check to one host or group. |
| --python <command> | server | Override the Python command used to create the controller environment. |
Project environments
Run w3 server from an application repository. On the first run it asks for
the environment, base domain, and server address, discovers key-based SSH
access, and creates the tracked w3.yaml. Later runs select an environment
from that file. Keep private keys and passwords in the SSH agent or secured
Bitbucket deployment variables. The shared playbook and roles are supplied by
the CLI.
Generated configuration includes a YAML Language Server directive that points
directly to w3.schema.json in the installed platform package. Projects do not
receive a schema copy. Supported editors provide validation and completion;
runtime validation remains authoritative.
The canonical model keeps server connection details separate from deployment environments, so staging and production can share a VPS or use separate VPSes:
project: "customer-a"
domain:
name: "customer-a.example.com"
acmeEmail: "[email protected]"
servers:
primary:
address: "192.0.2.10"
sshUser: "deploy"
environments:
staging:
server: "primary"
subdomain: "staging"
service: "web"
containerPort: 3000
stack: "customer-a-staging"
logDirectory: "/var/log/customer-a/staging"
production:
server: "primary"
service: "web"
containerPort: 3000
stack: "customer-a-production"
logDirectory: "/var/log/customer-a/production"Provisioning requires a sudo password when the inventory user does not have
passwordless sudo. On the first run, w3 prompts for it and offers to save it
in the encrypted project file .serverconfig.sops. If you decline, w3 asks
again on every provisioning run. The encrypted file can be committed and used
by CI; it never contains plaintext. If sudo is passwordless, no prompt is
shown.
The CLI searches the current project and its parents for the canonical
w3.yaml. Advanced Ansible commands can still accept --inventory PATH; use
--infra PATH only to override the infrastructure shipped with the CLI.
w3 serverUse w3 server --help for advanced Ansible commands and overrides.
Environment
| Variable | Meaning |
| :--- | :--- |
| W3_HOME | Overrides ~ for the w3 cache + key paths (used in tests/sandboxes). |
| SOPS_AGE_KEY_FILE | Set automatically by w3 to the shared key path. |
| SOPS_AGE_KEY | Age private key supplied by CI; w3 writes it under its temporary W3_HOME. |
Bundled Ansible and infrastructure
Platform packages ship the shared infra/ playbook, roles, Ansible config, and
collection requirements next to the native executable. w3 server uses this
definition by default and keeps its Python virtual environment and Galaxy
collections under ~/.w3/ansible (or the equivalent W3_HOME).
Published platform packages can also include a controller payload containing Python,
Ansible, the required collections, and SOPS. When present, w3 server uses the
payload directly and does not create a virtual environment or contact pip or
Ansible Galaxy. Development builds without a payload retain the Python/venv
fallback. The payload layout and manifest are documented in
controller-artifacts/README.md.
Linux and macOS run Ansible natively. Windows uses WSL because Ansible does not
support Windows as a native control node. Install WSL once with wsl --install,
then run the same w3 server commands from the Windows checkout. The CLI
translates project, cache, and SOPS key paths and installs a Linux SOPS binary
for the WSL controller automatically.
On Windows, w3 runs both SSH checks and Ansible inside WSL so they use the same
keys and configuration. Make the deployment SSH key available in ~/.ssh
inside your default WSL distribution before running w3 server.
Development
bun install
bun run dev -- --help # run the CLI from source
bun run build # build and install the current platform globally
bun run build:all # build every publishable platform package
bunx tsc --noEmit # type checkbun run build uses npm's configured global prefix. Set a user-owned prefix before
building if the system prefix is not writable:
npm config set prefix "$HOME/.local"Publishing
All six npm packages must use the same version. Publish the five packages in
packages/ first, then publish the root @philip-oesterlin-w3/cli package. Publishing the
launcher last prevents it from referring to native package versions that are
not available yet.
Bitbucket Pipelines performs this sequence for tags named v<version>. Commit
an encrypted cli/.env.sops containing NPM_TOKEN, and add SOPS_AGE_KEY as a
secured Bitbucket repository variable. The pipeline uses its freshly built
Linux w3 binary to decrypt the file into an isolated temporary environment,
loads only NPM_TOKEN, and removes the plaintext after publishing.
Create the encrypted file locally without committing the plaintext .env:
printf 'NPM_TOKEN=your-token\n' > .env
bun run dev -- secrets sync .env.sops --yes
rm .env
git add .env.sopsThen create and push a tag such as v0.1.0. Prerelease versions (for example
v0.2.0-beta.1) use the next npm dist-tag; stable versions use latest.
Use npm pack --dry-run in the root and in every platform directory before a
release. The root package should contain only README.md, package.json, and
bin/w3.js; each platform package should contain its package.json, one native
executable, the bundled infra/ definition, and optionally its matching
controller/ payload.
