Command Reference
Every eph command, its flags, and what it prints. Most commands operate on
the workspace found by searching up from the current directory for a .eph
file; eph system prune is the exception and works globally.
eph [--verbose] <command> [args]
Global flags
| Flag | Description |
|---|---|
-v, --verbose | Enable debug logging (written to stderr) with timestamps and levels. |
-h, --help | Print help. Works on subcommands too (eph up --help). |
-V, --version | Print the version. |
Logging always goes to stderr; command output goes to stdout. By
default, progress lines (“Creating service web”) print bare and warnings and
errors are prefixed warning: / error:; -v switches to timestamped,
leveled debug logging.
eph up [SERVICE...]
Start services. With no arguments, starts every service in the .eph file.
Without a role graph, names select exactly those services. With roles, a name
also pulls in every service from the roles it depends on, but not peer services
from the named service’s own role. An unknown service name is an error.
| Flag | Description |
|---|---|
--role ROLE | Bring up this role and everything it depends on. Repeatable; combines with SERVICE names (the union starts). Requires a roles_order. |
--skip-hooks | Bring services up healthy but run no pre-start or post-start hooks. |
eph up # all services
eph up postgres redis # just these two
eph up --role dep # the dep tier and its dependencies (prewarm)
eph up --role app # the app plus every role it depends on
eph up --skip-hooks # skip codegen/migrations this once
Behavior:
- Idempotent with reconciliation. A matching running service is reused and a matching stopped resource is restarted. Effective source, image, port, resolved environment, volume, health, build, or command drift removes the old backend and creates the requested one. Reused services rerun declared health checks. See Core Concepts.
- Hooks bracket it. Each service’s
pre-starthooks run just before it is created; once every targeted service is healthy, allpost-starthooks run in a second phase. Both run on everyeph up, with eph’s resolved environment injected, and a failure aborts the command. Full rules in The.ephFile. --roletakes the dependency closure: the role plus every role it transitively depends on, since a role cannot run without what is below it. Withroles_order=dep,app,--role appstarts both tiers and--role depstarts onlydep. Using--rolewithout aroles_orderin the file is an error. See Roles and ordering.- A positional service also respects the graph. With a
webservice inapp,eph up webstartswebplus every service in the roles belowapp. Otherappservices remain stopped. Use--role appto select the whole role. - Prints each started service and its assigned host port.
eph up,eph down, andeph cleanon the same workspace serialize against each other; a second command started while one is still running waits and prints a notice rather than racing it. See Persisted state.- After a successful
up, a filesystem-only scan checks whether any other workspace’s recorded path has been deleted (a removed worktree or clone), and prints a one-line note on stderr pointing ateph system prunewhen it finds one. It never touches Docker, never fails theupitself, and never counts the current workspace.
eph down [--rm] [SERVICE...]
Stop services. With no arguments, stops all. Without a role graph, names stop exactly those services. With roles, a name also stops every service in roles that depend on its role, but not peer services from the named service’s own role.
| Flag | Description |
|---|---|
--role ROLE | Stop this role and everything that depends on it, in reverse start order. Repeatable; combines with SERVICE names. Requires a roles_order. |
-r, --rm | Also remove the stopped containers. |
--skip-hooks | Stop without running pre-stop or post-stop hooks. |
eph down # stop all, keep containers
eph down --rm # stop all and remove containers
eph down postgres # stop just postgres
eph down --role dep # stop the dep tier and everything above it
eph down --skip-hooks # bypass a broken teardown hook
Behavior:
- Without
--rm, containers and their data remain for a fast restart. With--rm, containers are removed (named-volume data is kept) and the nexteph upcreates fresh ones. - Each service runs
pre-stopbefore stopping andpost-stopafter. A failingpre-stopaborts thedownand leaves the service running; a failingpost-stopaborts the rest of the teardown. See The.ephFile. --roletakes the dependent closure: the role plus every role that transitively depends on it, because a dependency cannot go away while the roles that need it are up. Withroles_order=dep,app,eph down --role depstops bothappanddep. Without a role graph,eph downstops exactly what it targets.- A positional service also protects dependents. If
dbbelongs todep,eph down dbstopsdband every service in roles abovedep. Another service indepremains running. Use--role depto stop the whole role. - Two per-source exceptions: compose services are always torn down with
docker compose down, so--rmmakes no difference for them, and run services are always killed (there is no container to keep). A targetedeph down <service>persists the updated state immediately. A faileddocker compose down(for example, a missingdocker composeplugin) is a real error and aborts the rest of the teardown, rather than being silently swallowed. - A bare
eph down(no service names) also tears down recorded state that no longer matches the.ephfile. Teardown works from whatstate.jsonsays eph actually started, not just the sections currently declared: a service you renamed or deleted from the file is still stopped and its container removed if state remembers starting it. A targetedeph down <service>only accepts names that still exist in the file, so it cannot reach a renamed entry by its old name; use the bare form to sweep those up.
eph clean
Full reset for the workspace. Stops and removes every service’s container (or Compose project, or process), removes every per-workspace named volume, and deletes the persisted state directory.
| Flag | Description |
|---|---|
--skip-hooks | Tear everything down without running pre-clean, pre-stop, post-stop, or post-clean hooks. |
eph clean
eph clean --skip-hooks # reset even if a teardown hook is broken
Workspace cleaned:
Services stopped and removed: 3
Named volumes removed: 2
Persisted state: removed
The counts are measured, not the number of services declared in the
.eph file: they count only what was actually stopped or removed. A
workspace whose services never started reports zeros across the board.
This deletes the data in named volumes. Bind mounts (host paths) are never touched, and volumes internal to a Compose file are left to
docker compose.
For each declared service, clean runs pre-clean, then the same
pre-stop/post-stop teardown as down, removes managed named volumes, and
runs post-clean. Clean hooks run even for an already-stopped service. A
failing pre-clean leaves that service untouched; a failing post-clean is
reported after its resources are removed. --skip-hooks bypasses all four
phases.
Behavior beyond the declared services:
- Renamed or deleted sections are still cleaned up. Like a bare
eph down,cleantears down fromstate.json’s record of what eph actually started, so a service you renamed or removed from the.ephfile is still stopped, its container removed, and its state entry dropped. - A final sweep catches anything state does not know about either.
cleanalso removes any leftover Docker container or volume still carrying the workspace’seph-<short_id>-name prefix, for a service renamed before its state was ever recorded, or a container left behind by a crash beforeeph upfinished writing state. This is the one placecleanlooks past both the.ephfile andstate.json, becausecleanpromises a full reset.
eph system ls
List every workspace eph has state for, oldest first, with the signals
eph system prune uses to select it. Read-only; needs Docker for the
container and volume counts.
ID PATH LAST SEEN PROCS CONTAINERS VOLUMES BRANCH WORKSPACE
c28e01270821529c present 19d 0 0/1 1 unmerged /Users/me/projects/app
9072a61a8e3970aa present 19d 0 0/1 1 merged /Users/me/.t3/worktrees/app/t3code-f1e706bf
fd9533db50776005 missing 15d 0 0/1 1 - /Users/me/conductor/workspaces/app/bissau
6cf3c526c07d5c84 present 1d 0 0/1 1 merged+dirty /Users/me/.cursor/worktrees/app/emkl
f31ebfdd2f907406 no .eph 21h 2 0/1 1 unmerged /Users/me/projects/app/.claude/worktrees/left-behind
447009e7cbf541ad present 11m 2 1/1 1 unmerged /Users/me/.t3/worktrees/app/t3code-6af612ee
6 workspaces (4 present), 4 live run= processes, 1 running container, 6 volumes
| Column | Meaning |
|---|---|
PATH | present (directory with a .eph file), no .eph, empty, not a directory, or missing. |
LAST SEEN | Time since the last up, dev, down, clean, env, run, or status in that workspace. |
PROCS | run= processes still alive under the identity eph recorded at launch. |
CONTAINERS | Running / total containers in the workspace’s namespace. |
BRANCH | merged (every change is in the repository’s default branch and the tree is clean), merged+dirty (merged, but uncommitted or untracked files remain), unmerged, or - (not a git checkout, or git is unavailable). |
BRANCH is answered by the local git binary with no network access, so it
is as fresh as the last fetch. A branch reads as merged when its tip is an
ancestor of the default branch, when every commit has a patch-equivalent on
the default branch (rebase merge), when its squashed patch matches a commit on
the default branch (squash merge), or when merging it would change nothing.
If the checkout is behind its own upstream branch, the upstream tip is judged,
since that is what got merged. The default branch’s own checkout, and a fresh
worktree sitting on the default branch’s tip with no commits, read unmerged.
eph system prune [--force] [--compatibility-v042] [--force-non-empty] [--force-live] [--idle DURATION] [--merged] [--yolo] [-y] [--yes]
Cross-workspace prune for resources left behind by finished workspaces. It
scans the eph state root (the platform default, or EPH_STATE_ROOT when set;
see Persisted state), reads each workspace’s
recorded path, and removes resources for workspaces whose path is gone, is an
empty directory, or no longer contains a .eph file. Workspaces that still
exist are listed under “Kept” with the same columns as
eph system ls, and --idle or --merged selects them by
those signals.
| Flag | Description |
|---|---|
--force | Enable --compatibility-v042, --force-non-empty, and --force-live. Still prompts for confirmation. |
--compatibility-v042 | Also prune 8-character state directories that have no workspace metadata. |
--force-non-empty | Also prune workspaces whose recorded path still exists and contains files. |
--force-live | Remove a stale workspace’s resources even if it still has a running container (or, for --force-non-empty, a live run= process). |
--idle DURATION | Also prune workspaces no eph command has touched for at least DURATION (90s, 30m, 12h, 2d). |
--merged | Also prune workspaces whose git branch is merged into the repository’s default branch and whose working tree is clean. |
--yolo | Enable --merged, --idle 12h (unless --idle is given), and --force-live. Still prompts for confirmation. |
-y, --yes | Skip the removal confirmation prompt. |
eph system prune
eph system prune --idle 2d
eph system prune --merged --idle 7d
eph system prune --yolo
eph system prune --yolo --yes
eph system prune --force
eph system prune --force --yes
eph system prune --compatibility-v042
eph system prune --force-non-empty
eph system prune --force-non-empty --yes
eph system prune --force-live --yes
There is no separate dry-run flag. Every run starts by printing what it would
remove; it removes nothing until you answer y at the prompt or pass
-y/--yes. To preview, run the selection you intend and answer n, or run
it with stdin redirected from /dev/null, where prune prints the report and
stops.
Would remove 2 workspaces (3 containers, 1 volume, 1 network, 1 image, 2 state directories):
ID REASON RESOURCES WORKSPACE
a1b2c3d4e5f60718 missing workspace 2 containers, 1 volume, 1 network, 1 image C:\Users\me\.codex\worktrees\1234\app
e5f60718293a4b5c missing workspace 1 container C:\Users\me\.codex\worktrees\5678\app
Kept 2 workspaces (path still exists; oldest first):
ID LAST SEEN PROCS CONTAINERS VOLUMES BRANCH WORKSPACE
0818f8723c1772c9 14h 2 0/1 1 merged C:\Users\me\.t3\worktrees\app\t3code-6e3b2781
b6ce879752027b28 7m 2 1/1 1 unmerged C:\Users\me\projects\app
2 workspaces, 4 live run= processes, 1 running container, 2 volumes
Select these with --idle DURATION, --merged, or --force-non-empty.
Remove resources for 2 workspaces? [y/N] y
Removing resources for workspace a1b2c3d4e5f60718
Removing container eph-a1b2c3d4e5f60718-postgres
...
Removed 2 workspaces (3 containers, 1 volume, 1 network, 1 image, 2 state directories).
RESOURCES lists what each workspace’s removal covers besides its state
directory; state only means nothing else was left behind. When nothing is
selected the report opens with Nothing to prune. and still prints the Kept
table.
Behavior:
--forceis the complete destructive override. It includes legacy state, existing non-empty workspace paths, and live resources.--forcealone previews that complete scope and still asks;--force --yesremoves it without asking.- By default, a recorded workspace path is eligible only when it is missing,
empty, no longer a directory, or no longer contains a
.ephfile (a half-removed worktree).--mergedand--idleadd existing workspaces by signal: a merged, clean branch, or no eph command for the given duration.last_seenis refreshed byup,dev,down,clean,env,run, andstatus, so a workspace an agent reads every day never reads as idle.--force-non-emptymakes every existing non-empty directory eligible. Each is a global selection, so read the preview before you confirm. When several apply, the report names the most specific reason:merged branch, thenidle workspace, thennon-empty workspace directory. --yolois the start-of-day sweep:--merged --idle 12h --force-live, so a workspace left running overnight is stopped along with its volumes rather than skipped for its running container. An explicit--idleoverrides the 12h default. It never touches a worktree or its commits, and it still prompts, so pair it with-yfor scripts.- The “Kept” table lists every workspace that still exists and was not
selected, oldest first, with its idle age, live
run=processes, running and total containers, volumes, and branch status. It is printed with the preview, not again after removal. The completion report after a real removal is the one-line total plus anything the removal itself turned up (skips, hook warnings); the per-workspace list was already shown in the preview. - Each resource removal is logged to stderr as it happens;
-vadds the lock, Docker inventory, and state-scan steps. The report stays on stdout for callers that capture or pipe it. Prune locks and inventories stale workspaces in batches of 64, listing each Docker resource type once per batch and matching the snapshot to workspace namespaces in memory, so large state roots neither repeat Docker API calls per workspace nor hold more lock files open than the process is allowed. - A workspace’s recorded path only decides whether it is stale, not whether
something is still running against it: before removing anything for a
stale workspace, prune checks that workspace’s actual Docker containers for
signs of life. A workspace that was merely moved or renamed while its
services keep running looks exactly like a deleted one from the recorded
path alone; without the check, prune would force-kill those live containers
and delete their volume data with no warning. If any container is running,
the workspace is reported under “Skipped” instead (“stop them or re-run
with —force-live”) and left untouched.
--force-liveauthorizes removing it anyway. The preview applies the same checks, so it always matches what the removal would do. - A live
run=process does not block a missing, empty,.eph-less, idle, or merged workspace; prune terminates it along with the rest. Its recorded identity includes the working directory eph launched it in, so a process that still matches while its directory is gone is an orphan by construction (a moved workspace reports the new directory and stops matching), and idle and merged selections already say nobody is using the workspace. The one exception is--force-non-empty, which carries no such signal: there a liverun=process still blocks unless--force-live(or--force) is passed. - Prune prints what it is about to remove and then asks
Remove resources for N workspaces? [y/N]before deleting anything, the same waydocker system prunedoes. Anything other thanyoryes(a bare Enter included) aborts with nothing removed, and the command still exits successfully. Pass-y/--yesto skip the prompt. When stdin is not a terminal (a script or CI job, for instance) prune cannot ask, so without--yesit prints the report, says nothing was removed, and exits successfully. No prompt appears when there is nothing to remove. - Docker resources are removed by eph’s workspace namespace
(
eph-<short_id>-...), so containers, built images, named volumes, Compose containers, and Compose networks can all be pruned even when the original.ephor compose file is gone. The workspace state directory is deleted last. - Prune honors teardown hooks. It uses a valid current
.ephwhen available, otherwise it uses the last teardown snapshot saved instate.json. A live service runspre-clean,pre-stop, stop, andpost-stop; an already-stopped service runs only its clean hooks. Namespace resources are removed beforepost-clean. When the workspace directory is gone, hooks run from its state directory but retain the recordedEPH_WORKSPACE_ROOT. Hook failures and unresolved hook variables are printed as warnings with captured output, then prune continues. A warning shared by several workspaces is printed once with the workspace count. Docker, process, and state removal errors still fail the command. The preview never executes hooks. - There is no prune-specific
--skip-hooks. System prune is already best effort for hooks, so a broken cleanup script cannot block resource removal. State written before teardown snapshots were introduced still prunes and reports that hooks were unavailable; everyupsince then saves a snapshot (empty when the file has no teardown hooks), so the warning fades as old state is pruned. - For
run=services, only a PID whose current process identity matches the identity eph recorded at launch is killed. A process entry without identity, and a mismatched PID that may have been reused, are skipped with a warning. A command that detached grandchildren outside the shell tree eph launched leaves processes prune cannot discover; stop those manually. - An 8-character state directory without
workspace.jsonis skipped by default.--compatibility-v042prunes that directory byshort_idnamespace alone. Run--compatibility-v042without--yesto preview that scope. - Prune holds an OS-level lock file (
prune.lockin the state root) for its whole run, so two prunes never operate at once. The lock is released the instant the holding process exits, crash included, so a second prune started while one is already running fails immediately with a clear error instead of racing it or wedging on a leftover lock file. - A real prune also takes each candidate’s workspace lifecycle lock before it
inventories Docker. If
up,down,clean, or foregrounddevstartup is already changing that workspace, prune waits for it and then inventories the resulting resources. This keeps the live-resource guard accurate for existing non-empty workspaces. - Workspace lock files (
<short_id>.lock, beside each state directory) are left behind byeph cleanand by prune itself. A real prune finishes by removing every lock file whose state directory is gone and that no eph command currently holds, so the state root does not fill with empty files.
eph dev [SERVICE] [--clean] [--watch GLOB]... [--skip-hooks]
Run the whole dev stack as one foreground process: bring services up, run
post-start hooks (seeding), foreground a run= service with eph’s stdin,
stdout, and stderr wired through, and tear down what it started when stopped.
The full walkthrough, including preview servers and the $PORT readiness
gate, is in Running Your App.
| Flag | Description |
|---|---|
--clean | On the final stop, tear the whole workspace down with eph clean (drops named volumes and their data) instead of the default eph down. |
--watch GLOB | Restart the stack when a file matching GLOB changes. Repeatable; globs are relative to the workspace root with gitignore-style separators. |
--skip-hooks | Bring the stack up and tear it down without running any lifecycle hooks, matching eph up --skip-hooks / eph down --skip-hooks together. |
eph dev # foreground the sole run= service; eph down on stop
eph dev web # foreground a specific run= service by name
eph dev --clean # full reset on the final stop
eph dev --skip-hooks # bring up and tear down with no lifecycle hooks
eph dev --watch "**/*.rs" --watch "*.toml" # restart on source changes
Behavior:
- With no
SERVICE, the solerun=service is foregrounded; name one when the file defines several. A.ephwith norun=service is an error. - Hooks run in exactly the order
eph upuses. Each backing service’spre-startruns immediately before that service starts (so it can reference services already up); the foregrounded app’s ownpre-startruns immediately before it starts, seeing every backing service’s assigned port.post-starthooks for every service, foreground app included, run together in a second phase once everything is up, so apost-starthook may reference any service’s port. Final teardown runs stop hooks and, under--clean, the clean-specific hooks too.--skip-hooksskips every applicable phase. - On stop (the preview server’s stop, or Ctrl-C), only the services
eph devstarted itself are torn down; services that were already running when it began (a prewarmed tier) are left up. A hard kill (SIGKILL) cannot run teardown; recover witheph down. - If the app exits on its own,
eph devexits non-zero and leaves the backing services up, except in watch mode, where it reports the exit and waits for the next file change to restart. - When the environment sets
$PORT(a preview server’sautoPort),eph devopens that port as a forwarding gate to the app only afterpost-starthooks finish, so a watching preview cannot go live before seeding is done. - A
--watchrestart is a full down and up (all hooks fire, volumes always kept); changes are debounced, and churn under.gitis ignored.
eph status
Show the workspace and which services are running. Reconciles saved state against the live Docker daemon and tracked PIDs, so manually removed containers drop out.
eph status
Workspace: /home/you/projects/myapp
ID: a1b2c3d4e5f60718
Running services:
postgres -> http://localhost:54321
redis -> http://localhost:54322
Stopped services:
minio
The ID: shown is the short ID; eph info also shows the full SHA-256
workspace ID. All four service types are reconciled: image and dockerfile
by container name, run by tracked process, and compose by the Compose
project’s com.docker.compose.project label.
eph env [-f FORMAT]
Print the top-level environment variables from the .eph file, with
${service.property} references resolved against running services. Built
for shell eval; see Shell Integration.
| Flag | Values | Default |
|---|---|---|
-f, --format | export, fish, powershell, json | export |
eval "$(eph env)" # bash / zsh / sh
eph env -f fish | source # fish
eph env --format powershell | Out-String | Invoke-Expression # PowerShell
env_json="$(eph env -f json)" && jq -r .DATABASE_URL <<<"$env_json"
- Only top-level variables are printed; service
env.*values are not. - If a value still contains an unresolved
${service.property}, shell formats unset that variable and then execute a failing statement. JSON omits the variable.eph envreports the missing reference on stderr and exits nonzero in every format. This clears stale values while making the incomplete environment observable to both shell evaluation and scripts. - All running services resolve, including
composeservices (theirexpose.<name>ports resolve as${service.port.<name>}). --format jsonkeys appear in the.ephfile’s declaration order.- An unknown format is an error
(
unknown format: ..., use: export, fish, powershell, json).
eph run <CMD>...
Run a command in the workspace root with eph’s resolved environment already
set: the same variables eph env prints, plus the EPH_* metadata (see
Hook environment).
eph run ./scripts/seed.sh # the script sees DATABASE_URL, EPH_*, ...
eph run psql "$DATABASE_URL" # $DATABASE_URL expanded by YOUR shell
eph run sh -c 'psql "$DATABASE_URL" < dump.sql' # sh -c for shell features
- The command is executed directly, not through a shell, so eph does not
expand
$VAR, globs, or pipes in the arguments. Wrap the command insh -c '...'when you need shell features driven by eph’s injected variables. - The command is not started if any top-level variable still references a
stopped service. Run
eph upfirst. - Exits with the command’s native process status. Windows exit codes are not
narrowed to eight bits; Unix signal exits use the shell convention
128 + signal. - Unlike a
post-starthook,eph runexecutes only when you invoke it. Use it for repeatable operations: seeding, resets, ad-hoc queries. - Every token after
runbelongs to the command, including ones shaped like eph’s own flags:eph run -v ./script.sh,eph run -h, andeph run --fooall pass-v/-h/--foostraight through as the command’s own arguments, with no--separator needed. A flag placed beforerun(eph -v run ...) is still eph’s own: only the tokens beforerunon the command line are eph’s flags (-v/--verbose).
eph logs [SERVICE] [-f] [-n N]
Show service logs across every service type from one command: run services
read from the log file eph captures their output to, image and dockerfile
services proxy docker logs, and compose services proxy
docker compose logs.
| Flag | Description |
|---|---|
-f, --follow | Stream new output as it arrives (like tail -f); Ctrl-C to stop. Works with or without a SERVICE. |
-n, --tail N | Show only the last N lines before printing or streaming. |
eph logs # every service interleaved, each line tagged [name]
eph logs -f # follow all services at once
eph logs worker # just the worker service (raw, untagged)
eph logs -f worker # follow worker
eph logs -n 50 postgres # last 50 lines
- Logs are shown even for a stopped service, so a
runservice that died on startup still leaves an inspectable trace (its output is captured to<state-dir>/logs/<service>.log;eph infoshows the state directory). - A
runservice’s log file is truncated on each fresh start, so it reflects the current run. - With no
SERVICE, every service streams concurrently, interleaved in arrival order, each line prefixed with a right-aligned, color-coded[name]tag. Lines are emitted whole, never split mid-line. Colors go only to a terminal and are suppressed whenNO_COLORis set or output is piped. eph cleanremoves the captured log files with the rest of the state.
eph check
Parse and validate the .eph file without touching Docker. Reports the
environment variables and services it found, or a parse error with a line
number.
eph check
Valid .eph file: /home/you/projects/myapp/.eph
Environment variables: 2
DATABASE_URL
REDIS_URL
Services: 2
postgres (image: postgres:16-alpine)
redis (image: redis:7-alpine)
eph info
Print workspace metadata. Does not touch Docker.
eph info
Workspace path: /home/you/projects/myapp
Workspace ID: a1b2c3d4e5f60718293a... (full SHA-256)
Short ID: a1b2c3d4e5f60718
Container prefix: eph-a1b2c3d4e5f60718
.eph file: /home/you/projects/myapp/.eph
State directory: /home/you/.local/share/eph/a1b2c3d4e5f60718
Use the container prefix and short ID to find this workspace’s resources with
the docker CLI. The state directory’s parent (the eph above the short ID)
honors an absolute EPH_STATE_ROOT; relative overrides are rejected. See
Persisted state.
eph skills install [--dir DIR] [--force]
Install the agent skills bundled into the eph binary into the repository, so
a coding agent working in the checkout discovers how to use eph (drive
eph up, load eph env, tear down). The skills are embedded in the binary,
so this works offline.
| Flag | Description |
|---|---|
--dir DIR | Skills directory to install into, relative to the repo root. Repeatable. Defaults to .claude/skills and .agents/skills. |
--force | Overwrite an existing skill file even if it was edited locally. |
eph skills install
created: .claude/skills/using-eph/SKILL.md
created: .agents/skills/using-eph/SKILL.md
Commit these files so your agents discover them on checkout.
- The target is the git repository root containing the current directory, so the skills land at the top of the repo regardless of where you run it. It falls back to the current directory outside a git repo, printing a warning on stderr that names the directory it installed into, since that fallback is easy to trigger by accident (running from the wrong place) and easy to miss otherwise.
- A
--dirvalue must be a plain relative path: an absolute path, a..component, or a Windows drive-relative path likeC:foo(no separator after the colon) are all rejected, naming the offending directory.C:foois rejected alongside the others because, despite not being absolute, joining it onto the repo root replaces the root outright instead of nesting inside it, the same escape an absolute path or a..gets. - A file that already matches what the binary would write is reported as
unchanged. One that differs is left untouched and reported asskippedunless you pass--force, so a local edit is never clobbered silently. - Commit the written files.
eph skills checkreports whether they match the installed binary;eph skills install --forcereplaces drifted copies.
eph skills check [--dir DIR]
Verify the installed skills match the binary’s embedded source, without changing anything. Prints one line per file and exits non-zero if any is missing or has drifted, so CI can run it as a drift guard.
eph skills check
up to date: .claude/skills/using-eph/SKILL.md
up to date: .agents/skills/using-eph/SKILL.md
The rendered skill contains no build version, so matching text is byte-stable. The check fails only when a file is missing or its content differs from the binary’s embedded source.
eph skills list
List the skills bundled into this eph binary, with the version they ship in.
eph skills list
eph update [--check] [--force]
Update eph to the latest GitHub release, replacing the running binary in
place. The updater is native (no dependency on curl or a shell): it resolves
the latest published release, downloads the archive built for this platform,
verifies it against the release’s SHA-256 checksums.txt, and swaps it over
the running executable. It installs the same bits as
scripts/install.sh, so a self-update and a fresh
install converge.
| Flag | Description |
|---|---|
--check | Report whether an update is available without installing anything. |
--force | Reinstall the latest release even when already up to date. |
eph update
Updating eph from v0.3.0 to v0.4.1.
eph updated to v0.4.1.
Check without installing:
eph update --check
update available: v0.4.1 (current v0.3.0).
Run `eph update` to install it.
Behavior:
- A release binary carries a clean
vX.Y.Zversion, soeph updatecompares it against the latest release and reports up to date, an available update, or (with--force) a reinstall. A development build (installed withcargo install --path .ormake install, whose version carries agit describesuffix) has no clean release to compare against and is always offered the latest published release. - The download is checksum-verified before a single byte is extracted, the same SHA-256 guarantee the install script provides.
- The swap is platform-correct: on Unix an atomic rename replaces the binary
while the running process keeps its open image; on Windows, where a running
.execannot be overwritten, the old image is moved aside and cleaned up after the process exits. Either way, restart any long-runningeph devor watch session to pick up the new version. EPH_REPOandEPH_BASE_URLoverride the GitHub repository and download base URL, matching the install scripts’ environment variables (see Getting Started; useful for a mirror or an internal fork).- Passive out-of-date nag. Every other command checks at startup whether a
newer release exists and prints a one-line reminder on stderr when one does.
The check reads a cached latest-release lookup (it never blocks the command)
and refreshes that cache at most once a day in a detached background
process, so a failed lookup never affects the command you ran. The cache is
namespaced per
EPH_REPO, so pointingEPH_REPOat a fork to test a build cannot poison (or borrow) the default repo’s cached nag. It stays silent for source builds, when stderr is not a terminal (scripts, pipes, CI), and whenEPH_NO_UPDATE_CHECKis set, so it never disturbseval "$(eph env)"or machine-readable output.
Commands that do not exist (by design)
The list above is the complete command set. One thing people look for is deliberately absent:
- There is no
eph initor scaffolder. Create the.ephfile by hand (see Getting Started) and validate it witheph check.