FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

async/cli at v0.2.3 · GitHub

/ cli Public

Repository files navigation

@async/cli

Filesystem-routed commands for local projects and user-global tools.

@async/cli turns directories into a CLI. A command like:

cli gh pull 123 --rebase

is routed to a directory, and the words that did not match become the script's arguments:

.cli/gh/pull/script.ts        argv: ["123", "--rebase"]

No registry, no config file, no argument framework. One lookup rule covers two trees: a repo-local .cli/ overlay that travels with the project, and a user-global ~/.cli for your personal tools. Proven commands get promoted from one to the other with a single command.

Docs map: this README is the guided tour. ROUTING.md is the normative routing rules. API_SURFACE.md is the complete API reference. SPEC.md is the design contract.

Install

pnpm add -D @async/cli    # per project
pnpm add -g @async/cli    # or globally

Requires Node 24+. The package ships two identical binaries, cli and async-cli, for setups where cli is taken.

Quick start

# scaffold .cli/gh/pull/script.ts in this repo
cli --new gh pull

# it is immediately runnable (scaffolded overlays are auto-trusted)
cli gh pull 123 --rebase      # script.ts gets ["123", "--rebase"]

# see what exists, and what would run
cli --list
cli --which gh pull

# promote it to your personal ~/.cli once it earns it
cli --mv gh pull --to root

Cloned a repo that ships its own .cli/? Commands are visible immediately but refuse to run until you approve them once:

cli --list          # inspection never needs trust
cli --trust         # approve this repo's overlay, then run normally

How routing works

The full rules with worked examples live in ROUTING.md. The short version:

  1. Starting at your working directory, walk upward and collect every .cli/ directory, nearest first, stopping at the Git root. Append ~/.cli last.
  2. The first tree that contains any directory matching your first words captures the command — nearer beats farther, always.
  3. Inside that tree, the longest path of words with a runnable script.* wins; leftover words become the script's argv. -- stops word matching early.
  4. help, lib, node_modules, hidden (.x), and underscore-prefixed (_x) names never route, so helper code can sit next to scripts.

Rule 2 is what makes overlays powerful: a repo can define .cli/gh/script.ts and take over the whole gh ... namespace while you work in that repo, even though your ~/.cli/gh/clone exists. That is deliberate — and it is exactly why local overlays are trust-gated. cli --list marks anything shadowed, cli --which shows what was hidden.

Writing commands

A command script is a plain Node ESM program — no SDK, no wrapper:

// cli: Open a pull request against main
// cli-cwd: project-root
const [id, ...rest] = process.argv.slice(2);
console.log(`pulling ${id} in ${process.cwd()}`);
Contract Detail
Arguments process.argv.slice(2), exactly as typed after the command
Description First line // cli: ... shows in --list, help, MCP
Working dir Caller's cwd; // cli-cwd: project-root or script-dir to change
Stdio / exit Inherited; your exit code is the command's exit code
Languages .js/.mjs run directly; .ts/.mts via Node 24 type stripping
Helpers Put shared code in lib/ or _anything/ — never routed
Environment CLI_SCRIPT, CLI_ROOT, CLI_SCOPE, CLI_PROJECT_ROOT, CLI_COMMAND, CLI_CALLER_CWD

Templates: keep reusable starting points in _templates/<name>/ in any command tree and scaffold from them with cli --new api users --template worker.

Managing commands

cli --new gh pr                 # scaffold (nearest local overlay, or --root)
cli --edit gh pr                # open the script in $VISUAL / $EDITOR
cli --rm gh pr                  # delete (nested commands require --force)
cli --cp gh pr --to root        # copy local -> user-global
cli --mv gh pr --to local       # move user-global -> this repo

Transfers move whole command directories, preserve the command path, refuse to overwrite, and warn when a script imports through ../ (such imports may break outside their original tree).

Trust

Local overlays are arbitrary code that arrives with a git clone, and shadowing means they can capture commands you type from muscle memory. So execution is gated, direnv-style:

cli --trust           # approve the overlays discovered from here
cli --trust --status  # trusted | changed | untrusted, per overlay
cli --untrust         # revoke

Trust records a content hash of the whole overlay; any change flips it to changed and blocks execution (exit 3) until you re-approve. Your own actions count as consent: --new, --cp/--mv --to local, and --add --to local keep the target overlay trusted without ceremony. ~/.cli is always trusted. Set ASYNC_CLI_TRUST=off to disable the gate in controlled environments (such as CI).

Sharing commands

Any Git repository with a .cli/ tree is a command pack:

cli --add https://github.com/org/pack.git                # into ~/.cli
cli --add https://github.com/org/pack.git --prefix vendor # namespaced: cli vendor <cmd>
cli --add https://github.com/org/pack.git --to local      # into this repo

Installs are all-or-nothing: conflicts are listed and nothing is written unless you pass --force.

Agents and tooling

The same command tree is discoverable by machines:

cli --agents --write   # pin a pointer block into AGENTS.md (--claude for CLAUDE.md)
cli --list --json      # stable inventory: commands, descriptions, scripts, shadows
cli --mcp              # MCP stdio server: commands become callable tools

--mcp needs zero dependencies and exposes each non-shadowed command as a tool (gh pull becomes gh__pull) taking { "args": [...] }. Untrusted overlays are excluded and local trust is rechecked before execution.

Starting MCP mode authorizes the connected stdio client to invoke every listed command and pass arguments. Keep that stream local to a trusted MCP host, and apply any narrower tool approval policy in the host. See SECURITY.md for the complete boundary.

Completions

eval "$(cli --completions bash)"    # bash
eval "$(cli --completions zsh)"     # zsh
cli --completions fish | source     # fish

Tab-completion covers command segments (shadow-aware) and built-in flags.

Doctor

cli --doctor          # human report, exit 1 on errors
cli --doctor --json   # { problems, summary } for tooling

Finds ambiguous script directories, ../ imports that break transfers, empty command directories, untrusted or drifted overlays, stale --agents blocks, missing descriptions, and shadowed commands.

CLI reference

Command Does
cli <words...> [args...] Route and run a command
cli help [prefix] Usage, or commands below a prefix
cli --list [--json] Inventory, including shadowed entries
cli --which <words...> Selected script and what it shadows
cli --new <words...> [--root] [--template <name>] Scaffold a command
cli --edit <words...> Open the script in $VISUAL/$EDITOR
cli --rm <words...> [--root] [--force] Remove a command directory
cli --cp <words...> [--to root|local] Copy between trees
cli --mv <words...> [--to root|local] Move between trees
cli --add <git-url> [--to root|local] [--prefix <name>] [--force] Install a command pack
cli --trust [--status] / cli --untrust Manage overlay trust
cli --doctor [--json] Audit the command trees
cli --completions <bash|zsh|fish> Emit shell completions
cli --mcp Serve commands over MCP stdio
cli --agents [--write|--check] [--claude] Manage the context pointer block
cli --version Print the package version

Exit codes: the script's own for command runs; 2 for usage and routing errors; 3 for trust refusals; 1 for --doctor with errors and --agents --check drift.

Development

pnpm run build
pnpm test
pnpm run pack:check
pnpm run release:check

Pipeline

This repo's CI, Pages, previews, and release lifecycle are generated by @async/pipeline from pipeline.ts. Do not hand-edit .github/workflows/async-pipeline.yml or the locks under .locks/pipeline/. Edit pipeline.ts, then regenerate and check:

pnpm run pipeline:sync:generate
pnpm run pipeline:sync:check
pnpm run pipeline:github:check

Common pipeline commands:

pnpm run pipeline:verify        # local run of the verify job
pnpm run pipeline:pages         # build the docs site task
pnpm run release:check          # forced full verify used before releases

Releases ride the pipeline update train: when @async/pipeline publishes a release, it dispatches an async-dep-bump event to this repo, and the generated dependency-bump job bumps the pinned dependency, regenerates the synced surfaces, verifies with release:check, and lands on main when green or opens a pull request when not.

Maintainers who want the shell to prefer this checkout over an npm-installed copy can link the local binaries:

pnpm run local:link
pnpm run local:status
pnpm run local:unlink

About

Filesystem-routed project and user-global CLIs

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages


Back | FazBrowse Home | New Git URL