eZacto powered by CONFLICT

ez, the command line

ez is the native command-line client over the generated @conflict-hq/ezacto-client package. It supports named organization configs, token login, identity checks, redacted config output, and both human and JSON output.

Install

npm install -g @conflict-hq/ezacto-cli
ez --help

Node 22 or later. The package is published from ConflictHQ/ezacto-oss under the AGPL v3; npx @conflict-hq/ezacto-cli --help runs it without installing.

Use

# Safest for an already-issued scoped API token: it never enters shell history.
printf '%s\n' "$EZACTO_TOKEN" | ez login --token-stdin --org conflict

ez whoami
ez whoami --json
ez config
ez log 2h northpeak devops -m 'release work'
ez timer start northpeak devops -m 'incident follow-up'
ez timer status
ez timer stop
ez week
ez logout

--base-url selects a different ezacto deployment during login. Plain HTTP is accepted only for localhost development servers; remote credentials require HTTPS. --org selects a named organization, and the most recent login becomes active.

The default config is $XDG_CONFIG_HOME/ezacto/config.json, falling back to ~/.config/ezacto/config.json. EZACTO_CONFIG or --config can override it. Writes are atomic and the credential file is mode 0600; command output never prints the token. --token is supported for automation but --token-stdin or the EZACTO_TOKEN environment variable avoids exposing a token in process lists and shell history.

Time commands resolve project names/codes and task names through the native API. Durations accept compact hour/minute forms such as 2h, 90m, and 1h30m. --date selects the spent date for log or timer start; --week accepts any date in the Monday–Sunday week to render. Starting a timer relies on the same API invariant as the web app: the previous running timer is stopped atomically before the new one starts.

Packaging

The CLI bundles the shared report capability inventory into dist/money.js. @ezacto/core is a build dependency only; it must never appear in the published runtime dependencies. Build before packing, and publish version 0.1.3 through the existing npm-v0.1.3 release workflow after the fix is merged. Version 0.1.2 cannot be repaired in place; pin 0.1.1 until the patch is published.

Run npm run test:package -w @conflict-hq/ezacto-cli from the repository root after building the workspaces. It packs both release candidates, globally installs them into a temporary prefix outside the checkout, and exercises help, the bundled report inventory, and a SQLite backup through the installed ez command. This check runs in CI and before either package is published; it needs access to npm for public dependencies and cleans up its temporary installation afterward.

Instance module availability

ez --help and ez report list work offline and describe commands supported by this client. They do not promise that the selected instance enables every module or that the current token has every permission. A report name in the inventory is not a successful capability check.

Remote invoice commands, reports and exports retain the server's module and permission checks. A 403 with module_disabled means the operator has switched the module off; signing in again or granting a token another scope cannot enable it. JSON failures preserve the server code, HTTP status and request ID. The CLI does not fall back to another endpoint or provider after a refusal.

The SSO switch controls interactive provider sign-in, not validation of an already-issued API token. ez login stores a token; it does not start an SSO flow. ez backup, restore, backup-decrypt and export --verify operate on local files. backup --sync-to is an explicit operator copy to the configured S3-compatible destination, not the instance's managed backup service. These recovery commands remain available with BACKUPS_ENABLED=false; possession of the files and destination credentials is still required.

Report permissions

ez report list names the scope required by each report. ez uninvoiced and organization reports require reports:read, available to accounting, executive managers and administrators. Members can use ez report run my-hours --from YYYY-MM-DD --to YYYY-MM-DD with time_entries:read. Project budgets use projects:read and the server enforces project visibility.

Before resolving filters or loading a report, the CLI checks the scopes stored at login. If a required scope is missing, it names that scope and the stored profile. API failures retain their code, message, HTTP status and request ID in --json output and readable error text. Permissions are still enforced by the server; local config cannot grant access.

Editing draft invoices

ez sudo invoice edit 1400 --subject "October retainer" --notes-file notes.txt \
  --issue-date 2026-10-05 --due-date 2026-11-04 --terms net_30
ez sudo invoice edit 1400 --line 2 --quantity 3 --rate 150.50 --description "Review"
ez sudo invoice unlink-time 1400 --entries 101,102

sudo requires an administrator API token with invoices:read and invoices:write. It prompts privately for the same administrator's verified email and password, then any enrolled authenticator/recovery or email second factor. Password sign-in must be enabled. For automation, pass --reauth-stdin and pipe a private JSON object with email, password, and optional code (authenticator or recovery code); credentials are never command-line arguments. Email-code reauthentication is interactive.

The server issues an elevation bound to that API token and that draft invoice for at most 15 minutes. The CLI holds it in memory and revokes it when the command finishes; it never saves the password or elevation in config. Ordinary ez invoice edit and unlink-time calls without elevation receive HTTP 403.

edit reviews the draft before reauthentication and refuses a changed document. Each write carries a fresh Idempotency-Key and expected_version. Line numbers are one-based; rates are decimal currency units. Header and line edits are separate commands: if the header saves and a later line fails, the CLI reports the saved header so the operator can review it before retrying.

unlink-time additionally requires time_entries:write. It releases only the explicit stopped entries linked to the named draft, atomically; an ineligible or cross-invoice selection changes nothing. Invoice line amounts remain as saved and need review. Every edit/release records the actor, elevation and exact before/after values in the same database transaction.