@ariestools/cli
v0.3.0
Published
Aries Tools CLI - A suite of tools by Arie Trouw
Readme
@ariestools/cli
Aries Tools CLI — a suite by Arie Trouw for AI-client tooling, XL1 datalake provisioning, content hashing (strict + fuzzy + perceptual), XL1 wallet management, payload witnessing, and XYO protocol utilities.
Installation
npm install -g @ariestools/cli
# or
pnpm add -g @ariestools/cliThe aries bin starts Node with --disable-sigusr1 from its shebang
(#!/usr/bin/env -S node --disable-sigusr1), so a SIGUSR1 from another
process never opens the V8 inspector in it. npm links the bin, so the kernel
applies the shebang; pnpm's shim and npm's Windows shims pass the flag
themselves. env -S is in macOS, FreeBSD, and GNU coreutils 8.30 and later;
a system env without it (some BusyBox builds, such as Alpine's) cannot
start the bin through its shebang: run it there as
node --disable-sigusr1 "$(command -v aries)" …, or install with pnpm.
Quick start
aries --help
aries info
aries secrets status
ariesi hash --algorithm pdq ./photo.jpg
ariesi plex libraries
ariesi gate pair ticker BTC_USDT
aries wallet create
aries witness url --url https://example.com
aries datalake listLocal datalake (standalone install)
The published CLI embeds the local datalake dev server. You do not need
private monorepo packages (aries-datalake-plane, etc.) installed.
# Isolated state (recommended for CI / Immortalizer)
export ARIES_HOME=/tmp/aries-test-$$
# Free ports by default (or pass --control-port / --plane-port)
aries datalake up
aries datalake list
aries datalake downProgrammatic client (no shell-out per request)
The programmatic datalake client is the root export of
@ariestools/datalake-cli,
the package aries datalake runs. It replaces @ariestools/cli/datalake, which
this package no longer exports:
import {
createDatalakeClient,
RestPayloadsClient,
} from '@ariestools/datalake-cli'
// After `aries datalake up` (or with baseUrl + authToken options)
const control = createDatalakeClient()
const lakes = await control.list()
// …The same export provides startRetainedDatalake and ExactRootArchive;
its README documents them. The datalake dev daemon ships beside its
aries-datalake bin, and the other runtime daemons under dist/bin/daemons/
inside this package.
Top-level commands
The reference includes public aries commands and internal ariesi commands.
Internal commands require the private CLI build and an internal license tier;
the public installation cannot run them.
aries ai App identities (every tier) and AI client tools (Pro)
ariesi bank Manage bank witness settings
aries chain Run a local published-chain S3/REST server
aries datalake Provision and manage on-demand XL1 datalakes
ariesi gate Query the Gate exchange API (internal build)
ariesi hash [file] Generate a hash (sha256, xyo, fuzzy, perceptual)
aries info Display version and environment info
ariesi npmjs Inspect and lint npmjs org packages
ariesi pentair Manage Pentair ScreenLogic systems
ariesi plex Inspect and manage Plex Media Server (internal build)
aries secrets Secrets foundation, static AI instructions, the human-only vault path, and the local broker
aries wallet XL1 wallet — seed phrases, accounts, transactions, contacts
aries witness Observe data and produce XYO-compliant payloads
aries xyo XYO protocol utilitiesGlobal options
Available on every command:
| Option | Description |
|---|---|
| -v, --verbose | Enable verbose output |
| --help-ai | Print static AI-oriented help for a registered command path |
| --version | Show version number |
| --help | Show help |
aries secrets — Secrets foundation, the human-only vault path, and the local broker
The current implementation provides versioned, static discovery and status,
the read-only inventory listing, local bastion profile and provider
configuration, eight human-only commands, init, create, reveal, set,
generate, delete, list, and describe, and the local broker under
aries secrets broker, each documented in the next sections:
aries secrets status
aries secrets status --json
aries secrets --help-ai
aries secrets --help-ai --json
aries secrets init # human-only; no --json form
aries secrets create --name "Deploy token" --input-fd 3 3<file # human-only; value from fd 3, else the prompt
aries secrets reveal --secret-id sec_… # human-only; the value, once, to the terminal
aries secrets set --secret-id sec_… --expected-version <v> --input-fd 3 3<file # human-only; compare-and-swap replace
aries secrets generate --name "Signing key" --kind random-bytes --length 64 # human-only; drawn in-process, never printed
aries secrets delete --secret-id sec_… --expected-version <v> # human-only; compare-and-swap delete, not recoverable
aries secrets list # human-only; metadata only
aries secrets describe --secret-id sec_… # human-only; metadata only
aries secrets broker status # read-only probe of the local broker
aries secrets broker install # human-only; pins and registers the brokerstatus reports the implementation phase (app-identity) plus five
live observations: bastionConfigured is true when the bastion profile store
holds at least one profile and data.bastionStore names the store path and
its state (configured, empty, or unreadable — a refused or corrupt store
is reported, never thrown); data.providers carries the negotiated state of
every provider entry on the selected profile; and data.humanOnlyPath states
whether the human-only path is available on this platform; and
data.broker reports, from the filesystem alone and without connecting,
whether the broker is available on this platform (reasonUnavailable is
platform-unsupported where it is not), whether a local broker is installed
for this secrets root, its supervisor, login start, pinned profile and
runtime, whether the socket is present, and its last recorded state
(probed is always false); and data.identity reports, from the filesystem
alone, whether the app-identity store exists and the advisory single active
signer lease (see aries ai identity). data.state is
not-initialized until a vault exists on the selected profile and
human-only-storage once one does. There is no initialized state and no
authenticated operator: init performs no operator or recovery bootstrap, so
the realm stays provisional and nothing accepts a grant.
aries --help-ai [--json] discovers registered AI-help subtrees. AI help is
embedded package content and runs before .env, settings, licensing, provider,
or broker initialization. Its manifest marks future commands as planned, the
human-only commands as human-only (risk human-only, no --json option),
and carries a humanOnlyPath block: brokered: false, humanAttested:
true, the eight conditions every human-only command enforces, the platforms
on which they can be satisfied (darwin, linux), unlock:
'per-invocation', and lease: 'not-available'. The broker's read-only
status, doctor, and logs are available; its lifecycle commands are
human-only-lifecycle; and a broker block states its transport (a Unix
socket with kernel-attested peer credentials on darwin and linux,
win32 unavailable), that it starts only when a human installs it, and that
it exposes no agent operation. App identities have a manifest of their
own, aries ai identity --help-ai [--json], which the root manifest lists
beside secrets.
Agent-operational secret access is not implemented. This foundation cannot
connect to a remote bastion, manage grants or approvals, or broker a secret
to any program. The local broker a human installs verifies proofs on its own
peer-checked connections and dispatches nothing; since aries ai identity
it issues a challenge only for the single active signer lease a human
installed, and the only CLI commands that talk to it beyond the read-only
probe are the human-only ai identity unlock, lock, verify, rotate,
revoke, and recover. The inventory listing
below observes presence metadata only: it initializes no provider and reads no
secret value. The bastion profile and provider commands record non-secret
configuration only: they contact no bastion and create no socket or vault.
The one path that touches a vault is human-only and unbrokered, described
under aries secrets init, aries secrets create, aries secrets reveal,
and the set, generate, delete, list, and describe sections below.
The refusal contract
All eight human-only commands — init, create, reveal, set,
generate, delete, list, and describe — refuse unless standard input
and standard output are both interactive terminals, the platform provides
/dev/tty, stty, and a process-ancestry probe (an allow-list, so win32
and any unknown platform are refused outright), the process was started
with --disable-sigusr1 (the installed bin's shebang supplies it;
node aries.mjs … does not), with no other Node option outside a short
allowlist (--disable-warning=…, --max-old-space-size=…,
--enable-source-maps) and none in NODE_OPTIONS, with no
NODE_COMPILE_CACHE and no other NODE_* variable outside NODE_ENV,
NODE_PATH, NODE_VERSION, NODE_AUTH_TOKEN, and Node's certificate,
proxy, warning, color, and REPL settings (a poisoned compile cache runs in
place of the bundle), and with no
LD_*, DYLD_*, or OPENSSL_* variable set, no AI agent host is detected
through a documented environment signal or an agent binary in the process
ancestry (an ambiguous probe fails closed), and argv carries no
structured-output option (--json, --no-json) and no delivery option
(--delivery, --destination, --output, --out, --out-file, --fd,
--env, --clipboard, --temp-file, --to) anywhere — including before
the secrets word. A secret value still has two ways in, both of them input
channels rather than delivery: the masked prompt on the attached terminal,
and --input-fd <fd> on create and set, which reads the value from a
descriptor the caller opened. Every refusal is HUMAN_CHANNEL_REQUIRED
(exit 2) with a fixed message on standard error that never reflects an argv
value and nothing at all on standard output, and the two kinds differ in
what they record: a refusal by the channel gate — a pipe, a non-terminal, an
unsupported platform, an unsafe launch environment, a detected or ambiguous
agent host — is recorded as a human-attested refused audit event, while an
argv refusal is caught by the preflight before any store is opened and
writes no audit event at all. The gate conditions are evaluated in the order
above, so a piped or captured caller, and a process launched without the
flag or with a preload, is refused before any ps probe runs. Past the
gate, a V8 inspector open in the process is refused the same way at every
masked prompt, before an --input-fd read, before reveal writes the
value, and before generate draws one (the broker lifecycle commands
refuse it too, and broker install checks again before it commits the
runtime). Passing the gate refuses the non-interactive case; it is not proof
of a human, because a pseudo-terminal remains agent-drivable.
None of the eight loads a .env file. The argument preflight, which runs
before any .env is read, marks them .env-free, so a .env in the
working directory, the directory the package manager was invoked from, the
package root, or ~/.aries sets nothing in the environment they run in: no
LD_*, DYLD_*, or OPENSSL_* variable, no NODE_OPTIONS, and no wallet
password. Export a variable in the shell if one of these commands needs it.
The gate's own children, the ps ancestry probe and stty, run with only
PATH=/usr/bin:/bin and a plain-locale LANG, whatever the environment of
the command holds.
No MCP tool and no GUI reaches this path. The aries mcp servers register no
tool that names a secret and import nothing from the secrets action tree; the
Electron shell and the dashboard import nothing from that tree either — not
the human-only modules, not the barrel that re-exports them, not the provider
seam, and not the encrypted-file vault package — and neither declares
@ariestools/cli-lib as a dependency (the dashboard reads only
secrets inventory list --json by running the packed CLI, which reads no
value). The vault package itself is not a GUI or IPC API: the only consumers
of its unlock, create, re-key, and record primitives are the human-only
command modules that handle plaintext and the provider seam they load it
through. The vault path uses no socket, no daemon, and no lease — a command
unlocks the vault for that invocation and locks it again before it returns;
the local broker neither opens nor holds a vault.
Storage layout under ~/.aries/secrets
ARIES_HOME relocates ~/.aries in full. Everything the CLI writes below it
is created 0600 inside 0700 directories. The three stores it owns —
config/bastions.json, realms/<realmId>/secrets-index.json, and
audit/human-attested.jsonl — are also checked when they are read: a group
or other permission bit, a foreign owner, a symlink, a non-regular file, or
an oversized one fails the command and names the chmod or chown fix
rather than repairing it. The vault files are written the same way and are
refused on a symlink, a non-regular file, or an oversized one, and their
directory is created 0700. Three rows below are operator-authored and are
never written or moved by the CLI: inventory.json, read as declarations
only, and realms/<realmId>/denies.json, which refuses every
secret-touching operation in this milestone by existing at all, are not
permission-checked either; inventory-sources.json, which decides which
repositories the inventory reads, is refused when read on a symlink, a
non-regular file, a foreign owner, or a group or other permission bit, on
the file or on the secrets root that holds it.
| Path | Contents |
| --- | --- |
| config/bastions.json | The bastion profile envelope: realms, bastions, endpoints, provider entries, and the active selection. Non-secret. ARIES_SECRETS_BASTIONS redirects it (absolute path). |
| vaults/<providerId>/vault.json | The encrypted-file vault header, format v1: plaintext parameters only — the vault ID, PBKDF2-SHA256 at 100,000 iterations, AES-256-GCM with a 12-byte IV, and the creation time. |
| vaults/<providerId>/vault-password.json | The password verifier: the PBKDF2 salt and a SHA-256 hash of the derived key. Never the password. |
| vaults/<providerId>/records.json | Every secret, as ciphertext with its authenticated metadata. The display name lives inside the encrypted body. |
| realms/<realmId>/secrets-index.json | The routing index: secret ID → { providerId, digest }, and nothing else. |
| realms/<realmId>/denies.json | Deny records, consulted before every secret-touching operation. Nothing in this milestone writes it. |
| audit/human-attested.jsonl | The hash-chained, append-only human-attested audit log: one event per line, no secret value and no grant evidence. |
| inventory.json | The machine-wide managed-secret declarations, written by the operator and only read here. Presence metadata only. ARIES_SECRETS_INVENTORY redirects it. |
| inventory-sources.json | The operator's opt-in list of repositories whose inventories are read, as { path, repoId } pairs. Written by the operator, only read here. |
| broker.sock | The local broker's socket, recorded on a local profile when it is added and bound only by a broker a human installed and started for that profile (0600, renamed into place). |
| broker/ | The local broker's state: the pinned runtimes runtime/v<major>-<sha16>/, installation.json, the display-only broker.json, the instance lock broker.lock, a launchd plist under supervisor/ when not installed at login, and logs/ (broker.log and its rotations, supervisor.log). |
| audit/broker-lifecycle.jsonl | The hash-chained, append-only broker lifecycle audit log, kept apart from the vault chain: install, start, stop, restart, and uninstall, and every gate refusal of a lifecycle verb. |
| identities/ | The app-identity store, written only by the human-only aries ai identity verbs: identity-store.json (store ID, KDF parameters, passphrase verifier), identities.json (public records, wrapped signing keys, enrollments with their MACs, tombstones), recovery.json (wrapped recovery keys and recovery-phrase ciphertexts), and the journal .identity-transaction.json. Never a plaintext key, mnemonic, or passphrase. The broker reads identities.json only. The lease identities.lock sits beside the directory. |
| audit/identity.jsonl | The hash-chained identity audit log: every gated run of an identity verb, refusals included, with its identity's public address and enrollment, never a value. Past 1 MiB a human-only run seals it: the old log is kept unchanged as audit/identity.sealed.<head>.jsonl, and the new one starts with a checkpoint carrying what the sealed one proved. |
| broker/signer.json | The broker's advisory, value-free record of the single active signer lease (identity, address, enrollment, and times, or why the last lease ended). Display only: any process of this user can write it. |
A supervised broker's unit is the one file written outside the secrets root:
a launchd plist installed with --at-login goes to ~/Library/LaunchAgents,
and a systemd user unit to the user manager's unit directory
(~/.config/systemd/user by default).
aries secrets inventory — Managed secrets visibility
aries secrets inventory list
aries secrets inventory list --repo owner/myapp
aries secrets inventory list --source env --env-file ./deploy/.env --jsonsecrets is always registered at the free tier, so every example works
unchanged in the internal ariesi build.
Managed secrets are declared in one schema at two scopes, each a JSON file
with a root-level inventory list:
~/.aries/secrets/inventory.json(override withARIES_SECRETS_INVENTORY;ARIES_HOMErelocates~/.aries) — machine-wide entries, each optionally naming itsrepos. The legacy~/.config/aries/secrets.jsonis still read when the new file is absent andARIES_SECRETS_INVENTORYis not set, with a notice on stderr anddata.legacyInventoryPathunder--json; move it to the new path.<repo>/.config/aries/secrets.json— entries needed by that repository's agents, read only for repositories the operator lists ininventory-sources.json(below). These files are safe to commit: the closed entry shape (service,account, optionallabel,env, and — global only —repos) structurally rejects value-bearing fields, so a committed file can never become a secret store.
{
"version": 1,
"inventory": [
{
"account": "ci-token",
"env": "GITHUB_TOKEN",
"label": "GitHub token",
"service": "github"
}
]
}Identifiers are ASCII-only. service and account start and end with a
letter or digit and may contain @._!- in between. Each repos ID is one or
more /-separated segments of [A-Za-z0-9._-] with no . or .. segment.
A label may use non-ASCII letters, but it must be in Unicode NFC and must
not contain control or format code points (which include bidirectional and
zero-width characters), line or paragraph separators, private-use, lone
surrogate, unassigned, or default-ignorable code points. There is no emoji
exception: U+200D (zero-width joiner) and U+FE0F (emoji presentation
selector) are rejected, so a label such as ⚠️ or a joined emoji sequence
fails. An existing inventory file with such a label, including
inventory.json, now fails to load; the error names the entry index to fix.
Presence is metadata only and never returns a value: on macOS the default
auto source probes the Keychain by (service, account) existence without
requesting the password; elsewhere (or with --source env) presence means the
mapped env variable is set and non-empty after .env loading. The --json
envelope follows the secrets result schema and contains identifiers and
presence booleans only — suitable as the dashboard panel's data source.
The Keychain probe exists only on macOS; there is no native credential-store
probe for Linux or Windows yet. Off macOS, auto falls back to environment
variables, so an entry without an env mapping reports unknown, and human
output says so in one stderr line; --source keychain there reports every
entry unknown with reason unsupported-platform, and human output says
that on stderr instead. An explicit --source env prints no such line.
--json discloses how the source was chosen as data.sourceResolution:
requested (auto, env, or keychain), resolved (env or keychain,
the same as data.source), reason (explicit or platform-default), and
keychainAvailable.
Repository inventories are read only from repositories the operator lists in
~/.aries/secrets/inventory-sources.json (under the secrets root, so
ARIES_HOME relocates it). Nothing scans for repositories: a repository that
is not listed contributes nothing to the default view, however it got onto
the disk, and repositories the removed scan below ARIES_CODE_ROOT used to
find are not read until they are listed.
{
"version": 1,
"repositories": [
{ "path": "/Users/me/GitHub/owner/myapp", "repoId": "owner/myapp" }
]
}Each path is an absolute directory and each repoId a valid repos ID
(above). The shape is closed, holds at most 200 repositories, and repeats no
path or repoId. A listed repository's rows are attributed to its
repoId, never to its directory name. The CLI never writes this file and
has no command for it; the operator writes it, like inventory.json. A
symbolic link or anything but a regular file is refused with an instruction
to replace it with a regular file the operator owns. A file owned by another
user, or carrying any group or other permission bit, is refused with the
chown or chmod 600 fix, and so is a secrets root holding it that is owned
by another user or carries any group or other permission bit (chown or
chmod 700), since whoever can write that directory can replace the file. A
file written with the default umask (0644 in a 0755 root) is therefore
refused until chmod 700 ~/.aries/secrets && chmod 600
~/.aries/secrets/inventory-sources.json. On Windows those checks are
reported as permissionCheck: "unavailable-on-platform" rather than
performed. A malformed file also fails the command. --json discloses the
file as data.repositorySources (path, status: loaded or missing,
and permissionCheck); when it is missing, human output of the default view
prints one stderr line naming its path. A listed path that is missing or is
not a directory is counted as unreadable in data.skipped.
--repo treats an argument that is a valid repository ID as a name and
anything else as a path. A name is a listed repoId, else exactly
<ARIES_CODE_ROOT>/<name> (default ~/GitHub), with no suffix matching; it
is never resolved against the working directory, so a directory there cannot
shadow the repository named. Pass a path relative to the working directory
as ./dir. A path is a listed path, else an existing directory read on
request. A listed repository reports data.repoTrusted: true. One that is
not listed is read only because it was named (data.repoTrusted: false;
human output marks the scope (unlisted)), and its env and label are
repository-authored; its ID is the name, or for a path its relative path
below ARIES_CODE_ROOT or its directory name outside it. An unlisted
repository's ID may not be one the sources file lists for another path.
data.repoTrusted is null for the default view. Presence is still probed
for a key an unlisted repository declares when inventory.json does not
declare it and no listed repository disputes it, so naming a directory tells
the caller whether a Keychain item or mapped variable exists for a key that
directory chose; no value is read.
The --repo view is the default view narrowed to one repository: the
requested repository is merged with the operator's file and every listed
repository, and only its keys are shown, so a key another listed repository
disputes is conflicting-declarations there too, with its collision. The
requested repository is read strictly: when its inventory is malformed or
cannot be read safely, or its listed path is missing, the command fails with
the reason (the path and, for a malformed file, the entry index) and exit 1.
Each (service, account) key is listed once. When inventory.json declares
a key, the operator's entry wins: its env, label, and repos are listed
unchanged, a repository that also declares the key is never added to its
repos, and the row carries declaredBy: "operator". When only
repositories declare a key and they give it the same env and label, the
row is attributed to each of them (declaredBy: "repository"). When they
differ on either field, including one repository setting a field another
leaves unset, no repository wins: the row has both fields null, presence
unknown with reason conflicting-declarations, and no Keychain or
environment probe runs for it. Every key a repository declares that the
operator also declares, and every conflicting key, is reported in
data.collisions (service, account, the declaring repos, the disputed
fields, and resolution: operator or conflicting); human output
prints one stderr line naming at most five of them. --repo applies the
same rules across the same declarers, so a repository cannot change an
operator entry, or win a key another listed repository disputes, in its own
view either.
ARIES_CODE_ROOT and ARIES_SECRETS_ENV_FILE (the env file loaded when
--env-file is not given) are read once, from the real process environment,
when the CLI starts, the way the secrets commands already read ARIES_HOME,
ARIES_SECRETS_INVENTORY, and ARIES_SECRETS_BASTIONS. Export them in the
shell: a .env file in the working directory, in the directory the package
manager was invoked from, in the package root (npm_config_local_prefix), or
at ~/.aries/.env cannot set or move any of them, although the variables it
sets still count toward env presence. The home directory these defaults sit
under is also taken once at start, and a .env never sets HOME,
USERPROFILE, HOMEDRIVE, HOMEPATH, USER, LOGNAME, or PATH, so a
process started without HOME cannot have its secrets root moved by one
either. Nor does a .env set the wallet's ARIES_WALLET_PASSWORD,
ARIES_WALLET_NON_INTERACTIVE, XL1_WALLET_HOME, or ARIES_WALLET_HOME, or
any name under a reserved prefix: ARIES_IDENTITY_*, and the preload class
NODE_* (all but NODE_ENV), LD_*, DYLD_*, and OPENSSL_*, which would
otherwise run a repository's code in every process Aries starts. Dropping a
reserved name prints one fixed line to stderr; set NODE_OPTIONS,
NODE_EXTRA_CA_CERTS and the rest in the real environment instead. A .env
that is not a regular file (a FIFO, a device, or a directory) is skipped, as
a malformed one is.
An unlisted --repo path whose derived ID would be invalid (a space, a
non-ASCII letter, a control or bidirectional character in a directory name)
is refused. A repository inventory is read only through real .config and
.config/aries directories and only when secrets.json is a regular file,
never a symbolic link or a FIFO. A listed repository inventory that is
malformed or cannot be read that way is skipped rather than failing the
listing: --json counts it in data.skipped (invalidRepositoryInventory,
unreadableRepositoryInventory), and human output prints one count-only
line on stderr. The counts cover every listed repository for the default
view, and every listed repository other than the requested one for --repo.
A malformed inventory.json still fails the command. Error messages print
every character outside printable ASCII as a \uXXXX escape, except that a
chmod or chown fix, and a path in a stderr notice, is printed as it is
when it passes the display rule for labels, so a path with a non-ASCII letter
stays runnable.
aries secrets bastion — Bastion profiles
aries secrets bastion add --name "Laptop bastion"
aries secrets bastion list
aries secrets bastion show # active or sole profile
aries secrets bastion show "Laptop bastion" # by display name
aries secrets bastion show --bastion bpf_… # by profile ID
aries secrets bastion use "Laptop bastion"
aries secrets bastion remove "Laptop bastion"
aries secrets bastion list --jsonA bastion profile is a closed, versioned, non-secret record naming a realm,
a bastion, its endpoint, and its providers; it holds no password, token, or
key material. Profiles live in one envelope at
~/.aries/secrets/config/bastions.json (ARIES_HOME relocates ~/.aries;
ARIES_SECRETS_BASTIONS redirects the file for tests and must be absolute),
written 0600 inside a 0700 directory. A file or directory with group or
other permission bits, a foreign owner, a file over 256 KiB, or an unparseable
file fails every command with BASTION_CONFIGURATION_INVALID (exit 9), naming
only the path and the fix. A redirected store is disclosed on stderr in human
output and as data.bastionStore under --json.
add --name <displayName> [--kind local-ipc]mints the profile, realm, and bastion identifiers, derives the audience, and records the socket path<secretsRoot>/broker.sockwithout touching the filesystem; only a local broker a human installs for the profile ever binds it. The first profile written to an empty store becomes the active selection. Every new profile starts with oneencrypted-fileprovider entry (encrypted-local, vault directory<secretsRoot>/vaults/encrypted-local); no vault is created.--kind remoteis refused withCAPABILITY_UNSUPPORTEDuntil Phase 9. A display name that collides with an existing profile ID or display name is an error, never an overwrite.listreads the store only: it never contacts a bastion or starts a broker, succeeds on a missing store with an empty table, and marks aremoteprofileunusable in this build.show [<profile>] [--bastion <profile>]resolves by profile ID first, then display name, else the active profile, else the sole profile; the result names which namespace matched. With nothing selectable it fails withBASTION_NOT_SELECTED. A local endpoint is labeled as served only by a local broker a human installs for that profile (probe it withsecrets broker status).use <profile>writesactiveProfileIdand nothing else. Selection is not authorization; aremoteprofile is refused.remove <profile>deletes the local record and clears an active selection that pointed at it. It revokes nothing: grants, delegations, and enrollments at the removed realm remain in force and must be revoked at the bastion separately, which the result states in both output forms.
add, use, and remove are mutating: they print the store path on stderr
for every write, and an agent must not run them without explicit operator
instruction (the AI-help manifest carries that rule). Every command takes
--json; the envelope follows the secrets result schema and typed failures
map to the secrets exit codes.
aries secrets provider — Provider entries
aries secrets provider list
aries secrets provider show # active or sole provider
aries secrets provider show backup # by provider ID
aries secrets provider add --id backup --kind encrypted-file
aries secrets provider add --id backup --kind encrypted-file --vault-path /srv/vaults/backup
aries secrets provider configure backup --vault-path /srv/vaults/backup
aries secrets provider use backup
aries secrets provider remove backup
aries secrets provider test # active or sole provider
aries secrets provider test backup --json
aries secrets provider list --bastion "Laptop bastion" --jsonA provider entry is the non-secret placement record on a bastion profile:
providerId, kind, and configurationRef (for encrypted-file, the
absolute vault directory, default <secretsRoot>/vaults/<providerId>). It
holds no password and no key material; the vault's own files hold those.
Every command takes the inherited --bastion <profile> (profile ID first,
then display name; omitted means the active profile, else the sole profile,
else BASTION_NOT_SELECTED) and --json, discloses the store as
data.bastionStore, and refuses a remote profile with
CAPABILITY_UNSUPPORTED.
add --id <providerId> --kind encrypted-file [--vault-path <dir>]appends one entry. The kind union is closed at the option table and again at the record parser, somemoryandmacos-keychainare refused by both. A duplicate ID is an error, never an overwrite. No vault and no directory is created: initialization needs a password and is human-only.listprints every entry withinitialized— whethervault.jsonexists atconfigurationRef, a stat only, with no unlock and no header parse — and marks the active provider.show [<providerId>]resolves by ID, else the active provider (set byuse), else the sole provider, and carries two fixed notes: unlock is per-invocation with no lease, and compare-and-swap is cooperative (it holds only among writers honoring the vault store lock).configure <providerId> --vault-path <dir>replacesconfigurationRefonly; the kind is immutable and nothing is created, moved, or unlocked.remove <providerId>deletes the entry and clears an active provider selection that pointed at it. The vault directory is not deleted, which the result states in both output forms. The last provider of a profile cannot be removed; remove the profile instead.use <providerId>recordsactiveProviderId, which stands in for an omitted provider selector onprovider show,provider test, and the human-onlylistonly;configureandremovetake their positional identifier. Placement oncreateandgeneratestill requires--providerwhenever the profile lists more than one provider; there is no default provider.test [<providerId>]resolves likeshowand reports the adapter's declared capabilities,maximumSecretBytes, a bounded health check, the per-invocation unlock mode, and the cooperative compare-and-swap caveat. It never prompts, unlocks, lists, or reads: the adapter is built around a locked placeholder session, and a locked vault — the designed steady state — succeeds with healthlockedand exit 0. Only an unavailable provider fails (PROVIDER_UNAVAILABLE, exit 7): not initialized (next.commandnames the human-onlyinit), a corrupt header (reported with a redacted diagnostic, never a path), or a health check that misses its deadline (retryable).statusruns the same negotiation for every provider entry on the selected profile and reportsproviderCountand per-provider rows under the same ok-when-locked rule; it still succeeds when every provider is unavailable.
--vault-path must be absolute and is refused when equal to or inside the
resolved XL1 wallet root, or inside the directory named by XL1_WALLET_HOME
or ARIES_WALLET_HOME when either is set, so a vault never shares a
directory with a wallet store; the refusal names the rule, never the path.
add, configure, remove, and use are mutating: they print the store
path on stderr for every write, and an agent must not run them without
explicit operator instruction.
aries secrets init — Human-only vault creation
aries secrets init # mints "Local bastion" when no profile exists
aries secrets init --name "Laptop bastion" # display name for the minted profile
aries secrets init --bastion "Laptop bastion" # an existing profile
aries secrets init --provider-id backup # another entry; added to the profile when absentinit is the first human-only, unbrokered command. From an interactive
terminal it resolves the bastion profile (or mints a local one when none
exists), ensures an encrypted-file provider entry for --provider-id
(default encrypted-local, vault directory
<secretsRoot>/vaults/<providerId>), refuses an existing vault at that
entry's location (VERSION_CONFLICT, exit 8; the refusal names the rule,
never the path), prompts for the vault password twice on /dev/tty with
the vault's 12-character minimum, creates the vault, opens it once to confirm
it answers under that password, locks it again, and prints the profile,
provider, and vault summary. Unlock is per-invocation: no lease, session
file, or key survives the command. The password is read from the terminal
alone — never from an environment variable or a command-line argument.
init initializes one vault and one local profile only. It performs no
operator or recovery bootstrap: the realm stays provisional in the plan's
sense, nothing accepts a grant, and neither status nor --help-ai
describes the operator as authenticated.
Every human-only command enforces these rules, each covered by a test:
- standard input and standard output must both be interactive terminals; a
piped, redirected, or captured channel fails with
HUMAN_CHANNEL_REQUIRED(exit 2) on stderr and writes nothing to stdout; - a detected AI agent host (Claude Code, Codex, Cursor, Aider, and the other documented signals, or an agent binary in the process ancestry) is refused, and an ambiguous probe fails closed;
- there is no
--jsonand no delivery option;--json,--no-json,--delivery,--destination,--output,--out,--out-file,--fd,--env,--clipboard,--temp-file, or--toanywhere in argv — including before thesecretsword — is refused with a fixed message before yargs runs, and the refused value is never reflected; - the command is not mirrored as an MCP tool and is reachable over no IPC, socket, or remote transport;
- every run, channel-gate refusal, and failure past the gate appends a
human-attested, unbrokered event to
<secretsRoot>/audit/human-attested.jsonl; an argv refusal is caught by the preflight and appends none. Anintentorcompletedevent that cannot be written fails the command withAUDIT_UNAVAILABLE(intentis appended before the password prompt,completedafter the operation and before anything reaches standard output, so a vault change already made stands); an unwritablerefusedorfailedevent is reported on stderr and the original failure stands; - the realm's deny records are consulted before a secret-touching operation
(
initalone is exempt, because no realm exists before it mints one);create,reveal,set,generate,delete,list, anddescriberefuse withPOLICY_INVALID(exit 5) when the deny set cannot be read, which in this milestone includes any existing<secretsRoot>/realms/<realmId>/denies.json, since nothing writes one yet; - on
win32no human-only command can pass the channel gate: with both standard streams on a terminal it fails withHUMAN_CHANNEL_REQUIREDand reasonplatform-unsupported, whichstatusand the manifest state, and a piped stream is refused earlier asstdin-not-ttyorstdout-not-tty.
Agents must not invoke a human-only command, wrap it, or treat it as agent access: it identifies no principal, evaluates no grant, and confers nothing on any agent principal.
aries secrets create — Human-only secret storage
aries secrets create --name "Deploy token" --input-fd 3 3<./token.txt # value from descriptor 3
aries secrets create --name "Deploy token" # masked prompt, typed twice
aries secrets create --name "API key" --provider backup # placement on a named entrycreate stores one secret in the operator's own vault. From an interactive
terminal it resolves the bastion profile (--bastion, else the active
profile, else the sole profile — it never mints one), places the secret on
--provider (required when the profile lists more than one provider,
implied when it lists exactly one; the active provider never satisfies
placement), consults the realm's deny records, appends the intent audit
event, prompts for the vault password on /dev/tty, collects the value,
writes the record, indexes it, locks the vault, and prints the secret ID
and version — never the value.
The value comes from exactly one of two sources. --input-fd <n> reads a
trusted producer's descriptor, which must be numbered 3 or higher, open,
and not a terminal: standard input is never accepted, because it may be
model-composed or captured in a transcript. The read is bounded at 65536
bytes and exactly one trailing newline is removed. Without --input-fd
the value is typed on the terminal, masked, and confirmed. In both cases
the record is stored as text/plain, and the descriptor is read before
the password prompt so a closed or reserved descriptor is refused early.
The display name (--name, at most 256 bytes, no control characters) is
stored inside the encrypted record body, where the vault's associated data
authenticates it.
The write spans two stores under two leases and is not atomic, so its order
is fixed: the provider record is created first, then the index entry. If
the index write fails, the record is deleted again under the same session
and the command reports the index failure with provider effect none; if
that deletion cannot be proven, the command reports
PROVIDER_OUTCOME_UNKNOWN (exit 7) naming the record's opaque ID, which
may then remain in the vault unindexed until it is reconciled.
The secret index
Each realm has one routing index at
<secretsRoot>/realms/<realmId>/secrets-index.json, written 0600 inside
a 0700 realm directory through the same journaled, atomic store that
holds the bastion profiles. It maps a secret ID to { providerId, digest }
and nothing else: no display name, content type, byte length, or timestamp
ever lands in it, so the file discloses routing alone. The digest is
SHA-256 over the canonical JSON of { secretId, providerId, opaqueId },
computed at create and re-verified on every resolve; opaqueId equals the
secret ID by construction, because the human path mints a canonical
22-character sec_ token that the encrypted-file vault stores verbatim.
The digest is unkeyed: it detects an accidental edit or a truncated write,
and nothing more — any party with write access to the file can recompute
it. Phase 3 replaces it with a broker-held keyed MAC. Do not delete the
index: every secret it routes becomes unreachable by ID.
aries secrets reveal — Human-only reveal to the terminal
aries secrets reveal --secret-id sec_… # the value, once, then a newline
aries secrets reveal --secret-id sec_… --bastion "Laptop" # an explicit profilereveal resolves --secret-id through the realm's secret index alone. A
missing entry is INVALID_ARGUMENT (exit 2); an entry naming a provider
the profile no longer lists, or one whose digest no longer verifies, is
BASTION_CONFIGURATION_INVALID (exit 9) — each fails closed, and no
provider is ever opened or searched to find a matching record. --provider
is refused with INVALID_ARGUMENT for the same reason: the index is the
only routing input, so a reference can never be redirected from the
command line. The command then consults the deny records, appends the
intent event, prompts for the vault password, reads the record, locks the
vault, appends the completed event, and writes the value exactly once to
standard output followed by one newline — nothing else is written there.
There is no other delivery: no destination, file, descriptor, environment,
clipboard, or temporary file, and no structured output. The value never
appears in an audit line or on standard error.
aries secrets set — Human-only compare-and-swap replace
aries secrets set --secret-id sec_… --expected-version <v> --input-fd 3 3<./new.txt # value from descriptor 3
aries secrets set --secret-id sec_… --expected-version <v> # masked prompt, typed twiceset replaces one secret's value in the operator's own vault. It resolves
--secret-id through the realm's secret index alone, exactly as reveal
does (--provider is refused with INVALID_ARGUMENT), consults the deny
records, appends the intent event, prompts for the vault password, collects
the new value from --input-fd (3 or higher; read before the password prompt)
or the masked terminal prompt with confirmation, and performs one
compare-and-swap replace: --expected-version must equal the record's
current version token exactly as the last create, generate, set, or
describe printed it, or the command fails with VERSION_CONFLICT (exit 8)
and nothing changes. The display name and content type are preserved from
the record. The reference does not change, so the index is untouched; the
command prints the new version and the superseded one, never the value. A
replace whose effect the provider cannot prove is PROVIDER_OUTCOME_UNKNOWN
(exit 7), naming the record and stating that the index still routes it.
aries secrets generate — Human-only in-process generation
aries secrets generate --name "Signing key" --kind random-bytes --length 64
aries secrets generate --name "Webhook token" --kind base64url-token
aries secrets generate --name "Database password" --kind password --length 24 --provider backupgenerate draws a value in the operator's own process from Web Crypto and
stores it exactly as create would: placement on --provider (or the sole
provider), the deny consultation, the intent event, the password prompt,
the provider record first with the display name inside the encrypted body,
then the routing entry in the realm's index, with the same rollback when the
index write fails. It prints the secret ID, kind, size, and version — the
value is never printed, logged, audited, or returned, and exists only in the
record's ciphertext; read it back with reveal. --kind is a closed list
and --length counts the kind's unit inside an inclusive range:
| Kind | Stored as | --length | Default | Range |
| --- | --- | --- | --- | --- |
| random-bytes | application/octet-stream, raw bytes | bytes | 32 | 8–1024 |
| base64url-token | text/plain | characters | 32 | 8–512 |
| password | text/plain | characters | 32 | 8–256 |
password uses a fixed 70-symbol printable alphabet with the ambiguous
0 O 1 l I removed and no space, quote, backslash, $, backtick, or
redirection character; base64url-token uses the 64 base64url symbols. Both
are uniform: a byte that would bias the draw is rejected, never reduced. An
unlisted kind or an out-of-range length is INVALID_ARGUMENT (exit 2) before
anything runs.
aries secrets delete — Human-only compare-and-swap delete
aries secrets delete --secret-id sec_… --expected-version <v>delete resolves --secret-id through the index alone, consults the deny
records, appends the intent event, prompts for the vault password, and
deletes the record under the --expected-version guard; a stale guard is
VERSION_CONFLICT (exit 8) and nothing changes. It then removes the routing
entry from the realm's index: the tombstone is the removal itself, so the ID
no longer resolves through any command and a later describe or reveal
stops at the index with INVALID_ARGUMENT. The vault record is gone and is
not recoverable through aries: the encrypted-file provider discards the
ciphertext and keeps no tombstone, which the receipt reports as
unrecoverable. The audit line records the secret ID and the fresh tombstone
version only. If the record is deleted but the index cannot be updated, the
command reports PROVIDER_OUTCOME_UNKNOWN (exit 7) naming both facts — the
record is gone, the index may still route the ID. Until you reconcile,
describe on that ID fails either at the vault with RESOURCE_UNAVAILABLE,
with list showing it as missing, or at the index with INVALID_ARGUMENT
if the entry did go after all.
aries secrets list — Human-only metadata listing
aries secrets list # the active provider, or the sole provider
aries secrets list --provider backup # one provider entrylist resolves the provider entry (--provider, else the active provider,
else the sole one), consults the deny records for the realm's catalog,
prompts for the vault password, and prints metadata alone: for every index
entry routed to that provider, the secret ID, version, incarnation, content
type, byte length, display name, and created and updated timestamps, read
from the vault's own description; an entry whose record the provider no
longer holds is shown as missing, one whose index digest does not verify
as integrity-failed (the vault is not consulted for it), and one whose
record the vault holds but will not describe as undescribable with the
failure code — a single unreadable record never hides the rest of the
catalog. Entries routed to other providers on the profile are counted, not
listed. The
command also enumerates the provider's own records and reports every record
the index does not route to that provider as unindexed, with its opaque
ID, version, byte length, and timestamps — never its value or display name.
An unindexed record is reachable by no aries command until it is reconciled,
because the index is the only routing input. No value is ever printed.
aries secrets describe — Human-only metadata for one secret
aries secrets describe --secret-id sec_…describe resolves --secret-id through the index alone (--provider is
refused), consults the deny records, prompts for the vault password, and
prints the record's current version, incarnation, created and updated
timestamps, content type, byte length, and display name — never the value,
which is decrypted only to measure it and wiped inside the library. A
record the vault does not hold is RESOURCE_UNAVAILABLE (exit 7); an ID the
index does not route is INVALID_ARGUMENT (exit 2) before any vault is
opened. The printed version is the --expected-version for the next set
or delete.
aries secrets broker — The local broker
aries secrets broker status [--json] # read-only: one hello and one ping
aries secrets broker doctor [--json] # read-only: records, runtime, unit, log, one handshake
aries secrets broker logs [--lines N] [--follow] [--json] # read-only: the broker's own log
aries secrets broker install [--profile P] [--supervisor launchd|systemd-user|none] [--at-login] [--node PATH] # human-only
aries secrets broker start # human-only
aries secrets broker stop [--force] # human-only
aries secrets broker restart # human-only
aries secrets broker uninstall [--purge-runtime] [--purge-logs] # human-only
aries secrets broker doctor --deep # human-onlyThe local broker is one long-running process per secrets root, on macOS and
Linux only, serving one local bastion profile on
<secretsRoot>/broker.sock. It accepts a connection only when the kernel
reports the connecting process's uid as its own (and never uid 0), and it
answers a handshake, ping, challenge issue, and proof verification. A
verified proof grants nothing (dispatch: 'not-available'): the broker holds
no provider, vault, or account, and at most one app-identity signing key,
the single active signer lease that the human-only aries ai identity
unlock or verify installs (protocol 1.1). It issues a challenge only for
that lease's identity, and while the lease lasts any process running as you
can authenticate as it; see aries ai identity. Only a human installs and starts it — a supervisor, when it
has one, restarts it after a crash and starts it at login only with
--at-login, and
no settings key enables it — and help, secrets status, and inventory list
never connect to it.
The read-only commands are available to agents and spawn nothing:
statuschecks the endpoint before connecting (a socket this user owns, with no group or other bit, in the owner-only secrets root; anything else isENDPOINT_UNTRUSTEDand nothing is sent), then reports the broker's pid, boot id, bundle digest, protocol, and pinned profile, and whether this CLI carries a newer broker release (updateAvailable). A stopped broker isBROKER_NOT_RUNNING(exit 7), a silent oneBROKER_UNRESPONSIVE, another protocol majorBROKER_PROTOCOL_MISMATCH(no fallback), and a selected profile other than the pinned oneBASTION_MISMATCH.doctorruns a fixed list of checks over the installation, the pinned runtime and its digest, the recorded interpreter (by path only, never executed), the supervisor unit, the recorded state and refusals, a restart loop, and one handshake, and fails withBROKER_UNHEALTHY(exit 7) when a check fails.logsprintsbroker.logand its rotations, only lines of the broker's own event vocabulary, from an owner-only file;--followkeeps reading in this process until interrupted. Lease events are printed without the identity they name.statusanddoctoralso report, from the filesystem and without changing their outcome, the advisorysignerlease frombroker/signer.jsonand whether the pinned runtime can hold a lease (signerSupport;doctor'ssigner-supportwarning): a runtime pinned from a release older than protocol 1.1, or registered with a unit this CLI would not write, readsneeds-update, fixed bysecrets broker installandsecrets broker restart; one with no supervisor readsunknown, andai identity unlockandverifycheck it when they connect.
The lifecycle commands are human-only. They refuse unless standard input and
output are both terminals, no agent host is detected, and argv carries no
--json or delivery option, with HUMAN_CHANNEL_REQUIRED (exit 2) — the
refusal contract of the vault commands above — and they are audited in
audit/broker-lifecycle.jsonl.
installpins the selected (or--profile) local profile, the broker runtime this CLI carries (--fromnames another bundle), a stablenode(--node, or the first onPATH, recorded without resolving symlinks), and a supervisor:launchdby default on macOS,systemd-useron Linux when a user manager answers, otherwisenone. The runtime is copied to a content-addressedbroker/runtime/v<major>-<sha16>/broker.mjsand self-tested before you confirm; that copy is the only one that ever runs, so upgrading or removing the CLI never touches a running broker.installrefuses a secrets root reached through a symlink (SECRETS_ROOT_NOT_CANONICAL) and a socket path over the length policy (SOCKET_PATH_TOO_LONG) before writing anything, needs--allow-downgradefor a lower protocol major, warns before pinning an older release, and starts nothing unless--restartis given.--at-loginalso starts the broker at login (launchd: the plist goes to~/Library/LaunchAgents; systemd: the unit is enabled); without it, runstartafter each login. Aries never enables systemd linger.startre-checks the interpreter and the runtime digest, starts the broker (through its supervisor, if any), and waits for its handshake.stopstops a supervised broker through its supervisor; an unsupervised one is signalled only after its handshake confirms the recorded pid (--forcesends SIGKILL instead of SIGTERM).restartrelaunches through the supervisor and checks that the broker now runs the pinned runtime; run it after a re-pin.uninstallstops the broker, unregisters its unit, removes the installation record (and, once nothing answers, the stale socket and state record), and with--purge-runtimeor--purge-logsthe runtimes or logs.doctor --deepalso re-runs the pinned runtime's self-test under the recorded interpreter and asks the supervisor about the job, read-only.
Editing or removing the pinned profile changes what the broker serves: a
provider edit rebinds it (outstanding challenges are invalidated), bastion
use never re-points it, and removing the pinned profile stops it until a
human re-installs. The broker's exit codes, supervisor policy, and what it
does and does not prove are in the repository's
packages/secrets-broker-host/README.md.
aries chain — Local published-chain server
# Plain HTTP on loopback (default)
aries chain up
# Locally trusted HTTPS at https://chain.aries.test:8791 (macOS)
brew install mkcert
aries chain up --ssl autoThe server exposes the published-chain buckets at /blocks, /state, and
/indexes. --ssl auto keeps the listener on 127.0.0.1, installs or reuses
mkcert's local development CA, generates a certificate under
~/.aries/chain/tls, and adds an idempotent chain.aries.test entry to
/etc/hosts. The first run may request administrator authorization. The CA and
hosts entry remain installed after chain down; chain reset removes the
generated Aries certificate files.
| Option | Default | Description |
|---|---|---|
| --port <number> | 8791 | Listener port |
| --host <address> | 127.0.0.1 | Bind address; auto TLS requires the default |
| --ssl off\|auto | off | Enable macOS automatic local HTTPS setup |
| --backing memory | memory | Ephemeral storage, wiped on shutdown |
| --timeout <seconds> | 10 | Startup health-check timeout |
The ready banner prints the S3_ENDPOINT used by ariesi xyo s3 .... Aries
automatically selects path-style S3 addressing and trusts the generated CA for
the chain.aries.test endpoint. S3_CA_BUNDLE can provide a different private
CA file for another HTTPS S3-compatible endpoint.
ariesi bank — Manage bank witness settings
ariesi bank capitalone config get
ariesi bank capitalone config set base-url https://api-sandbox.capitalone.com
ariesi bank capitalone config set token-url https://api-sandbox.capitalone.com/oauth2/token
ariesi bank capitalone config set client-id "$CAPITALONE_CLIENT_ID"
ariesi bank capitalone config set client-secret "$CAPITALONE_CLIENT_SECRET"
ariesi bank capitalone config set accounts-path /accounts
ariesi bank capitalone config set balances-path '/accounts/{accountId}/balances'
ariesi bank capitalone config set transactions-path '/accounts/{accountId}/transactions'Capital One config is stored in ~/.aries/bank/capitalone.json with restricted
file permissions. ARIES_HOME overrides the ~/.aries root.
Capital One U.S. Customer Transactions is a private DevExchange product. Aries
therefore requires the endpoint paths supplied by your Capital One partner docs
for aries witness bank --resource accounts.
The public sandbox Retrieve Consumer Bank Products product is a deposit product
catalog, not personal account data. Use aries witness bank --resource products
for that API; Aries uses Capital One's documented sandbox defaults unless you
override base-url or token-url.
Capital One config keys
| Key | Env var | Default | Description |
|---|---|---|---|
| env | ARIES_CAPITALONE_ENV | sandbox | Environment label: sandbox or production |
| base-url | ARIES_CAPITALONE_BASE_URL | | Capital One API base URL |
| token-url | ARIES_CAPITALONE_TOKEN_URL | | OAuth client-credentials token URL |
| client-id | ARIES_CAPITALONE_CLIENT_ID | | OAuth client id |
| client-secret | ARIES_CAPITALONE_CLIENT_SECRET | | OAuth client secret |
| scope | ARIES_CAPITALONE_SCOPE | | Optional OAuth scope |
| accounts-path | ARIES_CAPITALONE_ACCOUNTS_PATH | | Accounts endpoint path |
| balances-path | ARIES_CAPITALONE_BALANCES_PATH | | Balances endpoint path; may include {accountId} |
| transactions-path | ARIES_CAPITALONE_TRANSACTIONS_PATH | | Transactions endpoint path; may include {accountId} |
config get redacts client-secret; pass --show-secrets to print it. Access
tokens are not persisted; pass --access-token or set ARIES_CAPITALONE_ACCESS_TOKEN
for one run.
ariesi pentair — Manage Pentair ScreenLogic systems
ariesi pentair config get
ariesi pentair config get address
ariesi pentair config set address 192.0.2.10
ariesi pentair config set port 80
ariesi pentair config set system-name "Pentair ScreenLogic"
ariesi pentair circuit runtime set --circuit-id 6 --hours 24 --onPentair config is stored in ~/.aries/pentair/config.json and is used by the
Pentair witness and circuit commands. ARIES_HOME overrides the ~/.aries root.
Config keys
| Key | Default | Description |
|---|---|---|
| address | | ScreenLogic adapter IP for direct connection; omit to use UDP discovery |
| port | 80 | ScreenLogic adapter TCP port for direct connection |
| password | SCREENLOGIC_PASSWORD | ScreenLogic password for direct connection |
| system-name | Pentair ScreenLogic | ScreenLogic system name for direct connection |
| response-timeout | 15000 | Milliseconds to wait for each ScreenLogic command response |
| search-timeout | 5000 | Milliseconds to wait for each UDP discovery attempt |
| search-attempts | 3 | Number of UDP discovery attempts before failing |
| series-file | ~/.aries/pentair/intellichem.jsonl | JSONL file used for saved IntelliChem samples and graphing |
config get redacts password; pass --show-secrets to print it.
ariesi pentair circuit runtime set
Set the ScreenLogic circuit egg timer. Runtime values are sent to ScreenLogic as
minutes; --hours 24 becomes 1440.
| Option | Description |
|---|---|
| --circuit-id <id> | Pentair circuit id to update |
| --hours <n> | Runtime in hours; mutually exclusive with --minutes |
| --minutes <n> | Runtime in minutes; mutually exclusive with --hours |
| --on | Turn the circuit on after setting the runtime |
| --json | Output as JSON |
Connection override flags are also accepted: --address, --port,
--password, --system-name, --response-timeout, --search-timeout, and
--search-attempts.
ariesi plex — Plex Media Server tools
The Plex command is available from the internal ariesi build with
license.tier=internal.
ariesi plex libraries
List libraries configured in a Plex Media Server. By default, Aries tries the local server at http://127.0.0.1:32400 and discovers the Plex token from PLEX_TOKEN, X_PLEX_TOKEN, PLEX_AUTH_TOKEN, local Plex .LocalAdminToken, or a local Plex Preferences.xml. If local API auth still blocks access, Aries falls back to the local Plex library database and then Plex Media Scanner --list.
| Option | Default | Description |
|---|---|---|
| --url <url> | PLEX_URL or http://127.0.0.1:32400 | Plex server URL |
| --token <token> | env/local Preferences.xml | Plex auth token |
| --preferences <path> | auto-detect | Plex Preferences.xml path for token discovery |
| --database <path> | auto-detect | Plex library database path for local fallback |
| --no-fallback | | Disable fallback to Plex Media Scanner --list when the API is unavailable |
| --json | false | Output as JSON |
ariesi plex lint [--library <name-or-id>]
Run read-only lint checks against the Plex setup. The first rule, multiple-sources, reports items that have more than one original source media copy. Plex optimized versions and files matching Plex's local extras naming conventions do not count as sources.
| Option | Default | Description |
|---|---|---|
| -l, --library <name-or-id> | all libraries | Plex library name or ID, e.g. Movies or 9 |
| --database <path> | auto-detect | Plex library database path |
| --json | false | Output as JSON |
ariesi plex items --library <name-or-id>
List top-level items in a Plex library from the local Plex database, including media resolution and optimization details.
| Option | Default | Description |
|---|---|---|
| -l, --library <name-or-id> | required | Plex library name or ID, e.g. Movies or 9 |
| --database <path> | auto-detect | Plex library database path |
| --fields <list> | media,optimizations | Comma-separated details: media, optimizations, files, all |
| --limit <n> | | Maximum number of items to inspect |
| --json | false | Output as JSON |
ariesi plex items optimize --tv --library <name-or-id>
Queue Plex "Optimized for TV" versions for items that do not already have one. Aries inspects the local Plex database first, skips items already marked Optimized for TV, and then queues missing
