Skip to content

williamwa/noni

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

15 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

noni

Drive interactive CLIs from anywhere. Turn gh auth login, ssh-copy-id, npm publish and friends into a sequence of stateless calls that any agent — or script — can drive.

agent: noni run "gh auth login"
noni:  {id: abc, status: waiting_input, prompt: {type: select, options: [...]}}
agent: noni key abc enter
noni:  {id: abc, status: waiting_input, prompt: {type: yesno, default: "y"}}
agent: noni input abc Y
noni:  {id: abc, status: waiting_input, prompt: {type: password, echo: false}}
agent: noni secret abc --env GH_TOKEN
noni:  {id: abc, status: exited, exit_code: 0}

The agent sees structured prompts and decides what to send. noni doesn't make decisions for you — it just gives you "see clearly + input safely".

Status

v0.1.1-dev — pre-release, in active development. See PLAN.md for the milestone roadmap and DESIGN.md for the protocol/state-machine spec.

Milestone Status
M0 design
M1 daemon + CLI scaffold
M2 stable-state detection + key/secret
M3 prompt detector 🚧 rules done, real-CLI testdata pending
M4 polish + MCP server
M5 release

Linux only for now. macOS works in theory (termios constants are wired) but untested. Windows: not in v0.1.

Architecture

┌────────┐   JSON-RPC 2.0 over   ┌─────────┐   PTY   ┌─────────┐
│  noni  │ ────── Unix socket ──▶│  nonid  │ ──────▶ │  child  │
│  CLI   │ ◀─────────────────────│  daemon │ ◀────── │ process │
└────────┘                       └─────────┘         └─────────┘
  • noni — stateless CLI client. Each invocation is one RPC.
  • nonid — long-lived daemon that owns PTY-backed sessions. Auto-spawned by the CLI on first call.
  • socket$XDG_RUNTIME_DIR/noni/sock (falls back to ~/.noni/sock), mode 0600.

Install

Homebrew (macOS / Linux):

brew install williamwa/tap/noni

Pre-built binary (Linux / macOS, amd64 / arm64):

# pick the asset for your OS/arch from the releases page
curl -fsSL https://github.com/williamwa/noni/releases/latest/download/noni_$(uname -s)_$(uname -m).tar.gz \
  | tar -xz -C /usr/local/bin noni nonid

go install (needs Go 1.22+):

go install github.com/williamwa/noni/cmd/noni@latest
go install github.com/williamwa/noni/cmd/nonid@latest

From source:

git clone https://github.com/williamwa/noni
cd noni
make build           # produces ./bin/noni and ./bin/nonid
make install         # copies both to ~/.local/bin/

Quick start

# non-interactive
noni run -- echo hello

# interactive: bash read with a prompt
OUT=$(noni run --wait 1500 -- bash -c 'read -p "name: " x; echo got:$x')
ID=$(echo "$OUT" | jq -r .session_id)
noni input "$ID" alice
noni wait "$ID" --until exit

# password (echo off → detected as type=password)
noni run --wait 1500 -- bash -c 'read -s -p "pw: " p; echo'

# pass a secret without putting it on the wire
GH_TOKEN=ghp_xxx ./bin/nonid &      # daemon must hold the env var
noni run --wait 1500 -- gh auth login
# … navigate the menus with `noni key <id> down enter` …
noni secret <id> --env GH_TOKEN

# send named keys
noni run -- cat
noni key <id> ctrl-c

# health
noni ping
noni list
noni status <id>

Commands

Command Purpose
noni run [flags] -- <cmd> [args...] Start a command in a PTY
noni input <id> <text> Send text (newline appended unless --no-newline)
noni key <id> <key>... Send named keys: enter, tab, up, down, ctrl-c, f1, …
noni secret <id> --env VAR Send daemon's $VAR as input — never on RPC wire
noni read <id> [--tail N] [--raw] Read current screen
noni stream <id> [--json] [--skip-backlog] Tail PTY output in real time (raw bytes, or JSON frames)
noni wait <id> [--until X] Block until state change / exit / prompt / idle
noni status <id> Snapshot
noni list List active sessions
noni kill <id> [--signal SIG] TERM / KILL / INT / HUP
noni ping Daemon liveness
noni doctor Diagnose daemon, socket, and PTY support
noni version Version

All commands emit JSON to stdout. Non-zero exit codes: 1 user error, 2 daemon/PTY error, 3 timeout.

Prompt types

noni returns a prompt block when a session is waiting_input:

Type Trigger Confidence
password termios ECHO disabled 0.99
yesno (y/n), [Y/n], (yes/no) patterns; default extracted from capital letter 0.9
select > / / * marker + indented option block; selected flag per option 0.85
input trailing : / ? / > 0.7
unknown fallback after 1s idle when nothing matches 0.0

When the detector falls back to unknown, the agent can still inspect screen and decide what to send.

Security

  • Socket is 0600, no TCP listener.
  • noni secret reads from the daemon's environment — secrets never appear in RPC params or the daemon log.
  • Daemon log: ~/.noni/log (sensitive payloads redacted).

Status machine

                ┌────────────────────────────┐
                │                            ▼
[created] ──▶ [running] ──idle+detect──▶ [waiting_input]
                │       ◀──input/key────────┘
                │
                └──exit──▶ [exited] ──60min──▶ [reaped]

See DESIGN.md for the full RPC protocol, error codes, and config schema.

Tests

go test ./...

The detector ships with table-driven tests covering each prompt type. Real-CLI recordings (gh auth login, ssh-copy-id, …) will be added under testdata/ as M3 progresses.

License

TBD.

About

run any interactive command in non-interactive way, so agent can use bash/exec with it easily

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors