rt

module
v0.0.0-...-085d4c8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 11, 2026 License: MIT

README

rt: Ripta's collection of tools

Expectations:

  • tools read from STDIN, write to STDOUT, and hopefully print errors to STDERR;
  • tools are meant to be combined with others, e.g., hs might be less useful to you, because it prints file hashes in binary output instead of hex (but enc hex converts it to hex strings).

You can install the all-in-one hyperbinary, which excludes any tools with CGO dependencies:

go install github.com/ripta/rt/hypercmd/rt@latest

or install all tools as individual binaries:

go install github.com/ripta/rt/cmd/...@latest

or pick-and-choose each tool to individually install:

  • calc for arbitrary-precision arithmetic
  • cg to run a command and annotate its output with timestamps
  • enc to encode and decode STDIN
  • grpcto to frame and unframe gRPC messages
  • hs to hash STDIN
  • lipsum to generate placeholder text
  • place for macOS Location Services (requires macOS and CGO)
  • streamdiff to help you pick out field changes off a stream of JSON
  • structfiles to examine and compare a pile of structured files
  • toto to inspect some protobuf messages
  • uni for unicode utils
  • yfmt to reindent YAML while preserving comments

or, last but not least, install a lighter version of the hyperbinary, which excludes tools with CGO, terminal, or filesystem requirements, but compiles to WASM:

GOOS=wasip1 GOARCH=wasm CGO_ENABLED=0 go build -o rt_lite.wasm -v ./hypercmd/rt_lite

Pull requests welcome, though you should probably check first before sinking any time.

calc

Arbitrary-precision calculator. Expressions can be passed as arguments, piped via stdin, or entered in an interactive REPL.

❯ calc '355/113'
3.14159

In REPL mode, results are stored as $N variables for reuse:

❯ calc
calc: type an expression to calculate, ".help" for help, or ^D to exit
calc:000> 2**128
340282366920938463463374607431768211456
calc:001> $0 + 1
340282366920938463463374607431768211457

Use -d to control decimal places (default 30), and -v for verbose output showing the internal construction:

❯ calc -d 50 '√2'
1.41421356237309504880168872420969807856967187537695

❯ calc -v '22/7'
calc:000/ Construction: Multiply(Inverse(Named("1", Int(1))), Multiply(Int(22), Inverse(Int(7))))
3.142857142857142857142857142857

Sessions can be saved and loaded with .save and .load.

Functions are called with C-style syntax, e.g. sin(0), log(8, 2), or atan2(1, 1). The library, also listed in the REPL with .show functions:

Group Functions
Basic abs(x)
Trigonometric sin(x), cos(x), tan(x), asin(x), acos(x), atan(x), atan2(y, x)
Exponential and logarithmic exp(x), ln(x), log10(x), log2(x), log(x, base)
Roots sqrt(x), cbrt(x)
Rounding floor(x), ceil(x), round(x)
Comparison min(x, ...), max(x, ...)
Hyperbolic sinh(x), cosh(x), tanh(x)
Combinatorial factorial(n), gamma(x)

Trig functions take and return radians; work in degrees by converting explicitly, e.g. sin(45 * PI / 180).

cg

Run a command with cg run and annotate each output line with a stream indicator: O for stdout, E for stderr, I for cg's own lifecycle messages. At the end of the run, a one-line summary reports the exit code, wall duration, and per-stream line counts. Bare cg is dispatch-only: it owns the run, resolution, and mcp subcommands and no longer execs programs itself.

go install github.com/ripta/rt/cmd/cg@latest
❯ cg run -- echo hello
O: hello
I: Finished exitcode=0 in 2ms (out=1 err=0)

❯ cg run -- sh -c 'echo out; echo err >&2'
O: out
E: err
I: Finished exitcode=0 in 3ms (out=1 err=1)

The child's exit code propagates to the shell. If the child is killed by a signal, the summary reports the signal number instead. SIGINT and SIGTERM are forwarded to the child.

-c / --capture writes the child's stdout and stderr to $TMPDIR/cg/<ID>/ and appends a short run ID to the summary line. Resolution subcommands thread the ID through follow-up calls:

❯ cg run -c -- sh -c 'echo out; echo err >&2'
I: Finished exitcode=0 in 3ms (out=1 err=1) id=Q3F9K2

❯ cg out Q3F9K2
/tmp/cg/Q3F9K2/stdout

❯ cg paths Q3F9K2
/tmp/cg/Q3F9K2/stdout
/tmp/cg/Q3F9K2/stderr

❯ rg -i FOO $(cg out Q3F9K2)

cg ls lists recent runs, most-recent-first; cg ls -n N overrides the default cap of 20, and -n 0 (or any negative value) lists everything. --state, --exit-code, --since/--before, --cwd, and --session-id narrow the listing further; all are optional and compose with each other and with --pool (see below for the full grammar). --cwd DIR matches a run's recorded working directory, resolving relative paths and symlinks before comparing. --session-id matches a run's or pool's recorded session (see below). A signaled run shows its negated signal number in EXIT (-15 for SIGTERM) rather than an encoded exit code. Capture never deletes anything; cg prune is the explicit cleanup hook:

❯ cg ls
CG ID    EXIT   RUNTIME   COMMAND
Q3F9K2      0       3ms   sh -c 'echo out; echo err >&2'
M7P4QX     42       2ms   sh -c 'exit 42'

❯ cg prune                  # keep the 50 most recent by mtime
❯ cg prune --keep 10
❯ cg prune --older-than 7d
❯ cg prune --dry-run

EXIT and RUNTIME are right-justified to a shared width across the listing; CG ID and COMMAND stay left-aligned. EXIT is always a bare number (or ? when a run has none), so a wide STATE word like pool:abandoned never drags EXIT's column wide the way a combined column would.

--output/-o picks the rendering: table (default, shown above) is ID/exit/runtime/command; wide inserts a SESSION column after CG ID (a run's session ID, or - for a standalone run that has none), inserts STATE after RUNTIME, and adds SYSTEM and USER cpu-time columns before command; json prints a {"runs": [...]} envelope, one object per run in the same shape cg meta returns (including cwd and session_id), with manifest set instead of the usual finished-run fields on a collapsed pool row. STATE is the same running/finished/failed/abandoned/unknown vocabulary as --state, with a pool: prefix on a collapsed pool row's own state:

❯ cg ls -o wide
CG ID    SESSION   EXIT   RUNTIME   STATE      SYSTEM   USER   COMMAND
Q3F9K2   -            0       3ms   finished      2ms    1ms   sh -c 'echo out; echo err >&2'
M7P4QX   -           42       2ms   finished      2ms    1ms   sh -c 'exit 42'

❯ cg ls -o json
{
  "runs": [
    {"id": "Q3F9K2", "state": "finished", "command": ["sh", "-c", "echo out; echo err >&2"], "cwd": "/home/rt", ...},
    {"id": "M7P4QX", "state": "finished", "command": ["sh", "-c", "exit 42"], "cwd": "/home/rt", ...}
  ]
}

cg run takes the execution flags. -v / --verbose prefixes every line with a timestamp and adds a started/finished preamble; --format controls the layout using Go's time.Format syntax. --buffered defers child output until the command finishes, grouping by stream. --log-parse json|logfmt reformats structured log lines inline. cg flags must precede the command; everything after the first positional or -- is passed through to the child untouched.

cg stats reports how long earlier runs of a command took. It takes the same shape as cg run, so swap run for stats. cg flags must precede the command.

cg stats [flags] -- COMMAND [ARGS...]
Flag Default Meaning
--cwd DIR the current directory The directory the runs started in. Symlinks are resolved before comparing.
--since TIME none Only count runs started at or after TIME. Same grammar as cg ls --since.
--exit-code EXPR 0 Only count runs with this exit code. Same grammar as cg ls --exit-code.
--samples N 20 Count at most the N most recent runs per entry. The maximum is 1000.
--pool MODE any any counts every run, none only standalone runs, and only only pooled runs.
-o, --output table table or json.

The first entry is the exact match. It equals the command's argv and nothing else. It is always present, with a count of 0 when there are no samples. A prefix entry follows for stored runs that start with the whole argv and have more arguments. It appears only when it has samples. Each shorter prefix of the argv that has samples comes after that, longest first.

Every stored run counts once, at the longest prefix of the query that it matches. The levels never overlap. For make test, take stored runs of make test ×3, make test JOBS=10 ×5, and make lint ×2. The exact entry counts 3, the make test prefix entry counts 5, and the make prefix entry counts 2.

The table marks a prefix row with a trailing …, so make test … reads as "make test and more". Prefix entries in JSON have match_type prefix. A prefix entry whose command_prefix equals the whole command holds runs that start with it and have more arguments. An argv of more than 32 tokens has no entry for longer runs, because cg cannot prove that such a run starts with the whole query. That run counts at the deepest prefix level that it matches, and prefix levels between 32 tokens and the full argv are not reported.

cg cannot know which arguments change a duration. It reports every level. The caller applies its own judgment.

Pooled and standalone runs never share an entry. Pool members with parallelism above 1 compete for CPU. Under --pool any, a level with both kinds of samples yields one standalone entry and one entry per distinct parallelism. Standalone comes first, then pooled by ascending parallelism.

By default, cg stats counts runs that exited with code 0. Signaled runs never count, including cancelled ones. A run that failed in 2 seconds says nothing about how long a passing run takes.

❯ cg stats -- make test
cwd: /home/rt/projects/rt

MATCH   POOL     COUNT  MIN   P50    P90    MAX    AVG    LAST   LAST AT               COMMAND
exact   -        10     4m0s  11m30s 12m0s  12m15s 10m55s 11m41s 2026-10-09T18:22:04Z  make test
exact   pool x4  3      6m2s  7m10s  8m5s   8m5s   7m20s  7m10s  2026-10-08T09:41:10Z  make test
prefix  -        5      3m1s  4m20s  5m10s  5m10s  4m15s  4m50s  2026-10-09T10:03:44Z  make test …
prefix  -        2      2s    1m1s   2m0s   2m0s   1m1s   2m0s   2026-10-09T17:30:12Z  make …

❯ cg stats -o json -- make test
{"cwd": "/home/rt/projects/rt", "matches": [
  {"match_type": "exact", "command": ["make", "test"], "pool": false,
   "stats": {"count": 10, "min_ms": 240000, "max_ms": 735000, "avg_ms": 655000,
             "p50_ms": 690000, "p90_ms": 720000, "last_ms": 701000,
             "last_at": "2026-10-09T18:22:04Z"}},
  ...
]}

Durations in JSON are integer milliseconds. p50_ms and p90_ms resist a single cold-cache run that skews avg_ms. last_ms and last_at show drift after a dependency bump. count says how far to trust the rest. A pooled entry carries parallelism when it is known. The exact entry that --pool only returns with a count of 0 omits it.

A run appends one line to a history log once its meta.json is written. The log lives outside the capture root, so cg prune and $TMPDIR cleanup do not touch it. Its directory is $XDG_STATE_HOME/cg, or ~/.local/state/cg when XDG_STATE_HOME is unset. The directory is mode 0700 and its files are mode 0600. cg tightens a directory or file it finds with looser modes.

File Purpose
history.jsonl The log. One JSON record per line.
history.1.jsonl The previous generation.
history.key The 32-byte key for the argv and cwd MACs.
history.lock The lock that serializes appends and rotation. The first-query import holds it exclusively. Scans hold it shared.
history.imported The marker that the first-query import has run.

Appends take history.lock exclusively, so concurrent finishes never interleave partial lines. The same lock serializes rotation. When history.jsonl passes 8 MB, it is renamed to history.1.jsonl and replaces any older generation. The first-query import also holds the lock exclusively. Queries read both files while holding the lock shared, so a rotation cannot land between the two reads. Two generations hold far more runs than --samples needs. The log needs no compaction.

A run record holds its version in v, the kind (run), the run ID, the start time, duration_ms, the exit code, the signal, whether the run was pooled, its parallelism, a MAC of the cwd, and a MAC for each argv prefix. Prefixes are hashed up to 32 tokens. A longer argv also gets a MAC of its full argv, so an exact match still works. Prefix levels between 32 tokens and the full length are not reported. Readers skip records whose version or kind they do not know.

The log never holds your command line. It holds keyed MACs of argv prefixes and of the cwd. Each MAC is HMAC-SHA256 under the key in history.key, truncated to 16 bytes. Argv can carry secrets, such as tokens passed as flags. A permanent plaintext copy would outlive the short-lived meta.json. A plain hash of make test can be reversed by hashing common commands. A keyed MAC cannot be reversed without the key. A query can report only what its own input names, because it recomputes the MACs from the argv and cwd it was given.

The key sits next to the log at mode 0600. Someone who can read both can test guesses offline. That takes the same access as reading ~/.zsh_history. Losing the key makes existing history unmatchable. cg generates a new key, and the old records age out through rotation.

A history write is best-effort. A failure never changes the run's exit code, meta.json, or tool output. cg run reports it on stderr. The supervisors drop it.

The first query imports the finished runs and pools still in the capture root, so history does not start empty. It runs when history.imported is missing, and it skips run IDs already in the log. A pooled member takes its parallelism from its pool manifest. A member whose pool directory is gone is skipped, since its parallelism is unknown. Deleting history.jsonl alone does not re-import, because the marker stays. Delete history.imported to force a re-import.

The log also holds a record for each finished pool, with the pool's wall time. These pool records are internal. cg stats and cg_stats do not show them. They feed two things. One is the cg_run_many auto timeout. The other is the history object, with source: "pool", on a timed-out response.

cg note records free-form memos that outlive a single run. A note carries an ID, a required message, an optional set of key=value tags, and a creation timestamp. Notes persist under $TMPDIR/cg/notes/ and are shared with the cg_note_* MCP tools. A note written from the shell is visible to an agent, and a note written by an agent is visible here.

cg note add takes the message as an argument or on stdin. Repeat -k to attach tags. cg note ls lists notes newest-first; -n overrides the default cap of 20, and -k key or -k key=value filters by tag. cg note grep searches message bodies with --text (a fixed string) or --pattern (an RE2 regex). cg note rm deletes notes by ID.

❯ cg note add 'baseline suite green at HEAD' -k run=Q3F9K2 -k branch=cg5
D2BHTQ

❯ cg note add 'migration still running; hold re-runs'
68NBND

❯ cg note ls
68NBND  2026-07-13T02:18:15Z
  migration still running; hold re-runs

D2BHTQ  2026-07-13T02:18:15Z  branch=cg5 run=Q3F9K2
  baseline suite green at HEAD

❯ cg note ls -k run=Q3F9K2
D2BHTQ  2026-07-13T02:18:15Z  branch=cg5 run=Q3F9K2
  baseline suite green at HEAD

❯ cg note grep --text baseline
D2BHTQ  2026-07-13T02:18:15Z  branch=cg5 run=Q3F9K2
  baseline suite green at HEAD

❯ cg note rm D2BHTQ
D2BHTQ

cg mcp starts a stdio MCP server that exposes the capture-run model as native tools, using the same on-disk storage the shell subcommands use — a run started with cg run -c is visible to cg_list, and a run started by cg_run is visible to cg ls. Register with Claude Code:

claude mcp add cg cg mcp

Or by hand in the MCP host config:

{
  "mcpServers": {
    "cg": { "command": "cg", "args": ["mcp"] }
  }
}

The server registers seventeen tools:

Tool Purpose
cg_info Report server diagnostics: start time, uptime, cwd, session_id, capture_root, and build info.
cg_run Run a command with capture; returns metadata and head/tail excerpts.
cg_run_many Run a flat pool of commands with a parallelism knob and a fail policy; returns a pool ID and a per-run summary.
cg_list List recent runs, most-recent-first.
cg_meta Return run state and metadata.
cg_wait Block until a run finishes or a timeout elapses.
cg_cancel Signal a run's process group, with optional escalation.
cg_paths Return absolute paths for a run's stdout, stderr, and meta.json.
cg_stdout Fetch captured stdout with byte limits and head/tail windowing.
cg_stderr Fetch captured stderr with byte limits and head/tail windowing.
cg_grep Search captured output and return matching lines.
cg_prune Evict runs by count or age.
cg_stats Report how long earlier runs of a command took, as cg stats -o json does.
cg_note_add Record a free-form note with an optional set of key=value tags.
cg_note_list List notes newest-first, with an optional key filter and limit.
cg_note_delete Delete a note by ID.
cg_note_grep Search note bodies and return whole matching notes.

Sharing depends on both sides resolving the same TMPDIR. A sandboxed shell, such as Claude Code's Bash tool, usually gets its own TMPDIR while the MCP server keeps the user's. cg_info reports the server's capture_root. Setting TMPDIR to its parent lets the CLI read the server's runs, so an agent can script cg meta, cg grep, and cg wait over many runs and keep their output out of its context. The sandbox usually blocks writes there, so start runs with cg_run or cg_run_many.

The notes store is shared the same way capture runs are. A note written with cg note add is visible to cg_note_list, and a note written by cg_note_add is visible to cg note ls.

A non-zero child exit code is data, not an MCP error: cg_run returns successfully with exit_code: N and the caller decides how to react.

Runs survive cg mcp restarts. Each cg_run hands the child to a small detached supervisor process whose lifetime matches the run's. Restarting the server does not kill or lose in-flight runs; a fresh server picks them up from the run directory. Two caveats. First, cg_wait keeps an in-process fast path only for runs the current server started. After a restart, waits on pre-existing runs fall back to filesystem polling. Same result, slightly coarser latency.

Second, cg_cancel signals only runs and pools the current server started. It takes the pid from the supervisor's status pipe, never from a file under the capture root, because anything running as the same user can write there. After a restart, cancelling a pre-existing in-flight run is an error; use cg cancel from a shell instead.

If a supervisor dies before recording the run's exit — a SIGKILL, say — the run never gets its meta.json. Such a run lists as abandoned in cg ls and in cg_list, which also accepts state: abandoned as a filter. cg prune treats abandoned runs as evictable alongside finished ones. A run whose supervisor still holds the run lock is live and is never pruned.

A run directory with no lock file, no pid file, and no start.json at all carries no liveness signal whatsoever, which happens when a supervisor dies before it can even acquire the lock. Such a run lists as unknown rather than running in cg ls and in cg_list, which also accepts state: unknown as a filter. When start.json is missing, both cg ls and cg_list fall back to the run directory's mtime for an approximate elapsed time: cg ls marks it with a ~ prefix, and cg_list sets started_at_approx: true alongside the mtime-derived started_at.

cg ls --state and cg_list's state input both take the same enum: all|finished|running|failed|abandoned|unknown. cg ls defaults to all, listing every state, and cg_list does too. This is a change from cg_list's earlier default of finished — a caller that didn't pass state explicitly now sees running, failed, abandoned, and unknown rows it didn't before; pass state: "finished" to keep the old behavior.

--exit-code on cg ls and exit_code on cg_list filter finished runs by exit code: a bare N means equals, or prefix with !=, >=, >, <, or <= for the other five comparisons. Runs with no exit code (running, abandoned, unknown, start-failed) never match. A collapsed pool summary row has no single exit code to compare — it always passes through untouched, regardless of the filter; expand with --pool any (or a pool ID) to filter individual member rows instead.

--since/--before on cg ls and since/before on cg_list filter by start time. since is inclusive ("at or after"); before is exclusive ("strictly before"). TIME accepts a relative duration meaning ago (4h, 7d, the same grammar cg prune --older-than uses), a full RFC3339 timestamp, or a bare YYYY-MM-DD date interpreted as local-timezone midnight. Unlike --exit-code, these bounds do apply to pool rows, using the pool's own precise started_at — the exemption is specific to exit codes, which pools genuinely don't have one of.

A cg mcp server mints a session_id when it starts and stamps it on every run and pool it spawns. cg_info reports the running server's own session, and cg_meta reports the session recorded against a run. --session-id on cg ls and session_id on cg_list filter by it, spanning standalone runs and pool rows alike, so an agent can list every run and pool its conversation produced with one filter. A standalone cg run has no server behind it and carries no session. An unmatched session lists nothing rather than erroring. The ID is minted fresh per server process, so a cg mcp restart begins a new session.

cg_run_many runs a flat pool of commands. Each argv in commands runs repeat times, through at most parallelism workers. Parallelism defaults to 1, which executes runs in listed order with repeats consecutive. on_error decides what a failure does to the rest of the pool: continue (default) runs everything, stop schedules nothing new, and kill additionally cancels in-flight runs. cwd and env are shared across the pool. wait and wait_timeout_ms work as in cg_run; a timeout returns the partial summary while the pool keeps running.

cg_run_many is not a workflow engine. There are no dependencies between runs, no conditionals, and no per-run fallback; the calling agent is the control-flow engine. There is also no cg run-many shell counterpart, since the shell already has xargs -P and make -j.

The result is a summary: counts, the commands array echoed once, and one flat record per run referencing its command by index. Failed runs carry tail excerpts of both streams, sized by excerpt_bytes with 0 disabling them, under a 16 KB pool-wide budget; failures past the budget carry excerpt_omitted: true instead. Successful runs carry no excerpts. Skipped and pending runs are visible in the summary; a pending run has no run ID yet.

A pool is one more ID in the run namespace: a directory under the capture root holding pool.json and no stream files. Members are ordinary sibling run directories, so cg_meta, cg_stdout, cg_stderr, and cg_grep work on any member run ID from the summary. Scheduling lives in a small detached pool supervisor, following the same pattern as single runs. A server restart therefore loses nothing: in-flight runs finish, pending jobs still get scheduled, and a fresh server's cg_wait on the pool ID aggregates via polling. A SIGKILLed pool supervisor leaves an abandoned pool, listed and evictable like an abandoned run.

The other tools understand pools. cg_wait on a pool ID blocks until the pool finishes and returns the same summary as the sync call. cg_list and cg ls collapse members behind one row per pool with state and counts; the pool filter expands them: a pool ID lists that pool's members, none lists only standalone runs, and any lists everything uncollapsed. cg_meta on a pool ID returns the pool state and the manifest. cg_prune evicts a pool and its members as one unit, and never a member from under a live pool. cg_cancel accepts pool IDs: the default SIGTERM stops scheduling and lets in-flight runs finish, while SIGINT additionally cancels them.

Every distinct command in a pool passes the approval gate below before anything spawns. A denial fails the whole call with nothing started, and repeat does not multiply prompts.

A blocking cg_run, cg_run_many, or cg_wait call sends MCP progress notifications while it waits. cg_wait sends them for a run and for a pool. The interval is fixed at 60 seconds. The server sends them only when the client attached a progress token to the call. Each notification carries two fields. progress is the number of seconds elapsed. message is a status line. For a run, it gives the elapsed time and the stdout and stderr sizes. For a pool, it gives the elapsed time and the done and running counts. The server stops sending before it returns, so no notification follows the result.

Keep-alives matter because MCP clients abort a tool call that stays silent for too long. Claude Code aborts a stdio call that is idle for 30 minutes. CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT sets that limit. The limit is 5 minutes for network servers. Each progress notification resets the clock, so a long wait_timeout_ms no longer trips it. Other clients differ. When the client sends no progress token, raise CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT or the client's equivalent for long runs.

An agent has to pick wait_timeout_ms before a run starts. The duration history removes the guess. cg_stats takes command, cwd, since, exit_code, samples, and pool. It returns the same JSON as cg stats -o json. cwd defaults to the server's working directory, as it does for cg_run. The defaults match the CLI.

cg_run and cg_run_many take a boolean wait_timeout_auto. It is off by default, so existing callers keep the 60000 ms default. An explicit wait_timeout_ms always wins. Otherwise the timeout is max(1.5 x the longest expected duration, 60000) ms. The response reports the value it used as wait_timeout_ms_used. With no history, the timeout is 60000.

The expected duration for cg_run comes from the exact, standalone match for the command and cwd, with the default filters. Prefix matches never count, since make says little about make test. For cg_run_many, the first source with samples wins:

  1. The wall time of earlier pools with the same cwd, commands in the same order, repeat, and parallelism, in which every member succeeded.
  2. An estimate from each distinct command's exact match among pooled runs at the same parallelism. The estimate is ceil(commands x repeat / parallelism) x the longest max_ms. One command without samples voids it.
  3. The 60000 ms default.

Every timed-out response carries elapsed_ms, measured from the recorded start of the run or pool. This covers timed_out: true from cg_run and cg_run_many, and finished: false from cg_wait on a run or a pool. The response also carries a history object when samples exist. It is omitted otherwise. Finished responses carry neither elapsed_ms nor history. cg_run and cg_run_many report wait_timeout_ms_used on every call that waited.

{"id": "K3FQ2M", "timed_out": true, "elapsed_ms": 62000,
 "history": {"source": "run", "count": 10, "p50_ms": 690000,
             "p90_ms": 720000, "max_ms": 735000}}

source says where the numbers came from. run is the exact standalone match. pool is the wall time of earlier identical pools. Both carry count, p50_ms, p90_ms, and max_ms. estimate is the member estimate. It carries only max_ms, which holds the estimate. The estimate counts only runs that started before the pool did, so a pool's own members never feed its estimate. An agent that timed out can size its next cg_wait from elapsed_ms and history.

cg_run checks each command against an approval matcher before running it. The default mode prompts for unmatched commands when the client supports elicitation, and otherwise fails closed; cg mcp --blindly-allow skips the gate entirely. Rules live in ~/.config/cg/approve.yaml (global) and .cg.yaml / .cg/approve.yaml / .claude/cg.yaml (project), merged at startup:

version: 1
mode: enforce        # enforce (default), allow-all, or deny-all
deny:
  - regex: '^/tmp/'
    message: do not run executables from temporary directories
allow:
  - prefix: [go, test]
  - prefix: [./scripts/build.sh]
  - regex: '^/opt/foo/bin/[^ ]+(\s|$)'

In enforce mode the matcher checks deny rules, then allow rules, then restrict rules, then prompts; deny always wins. Each rule matches by exact argv, prefix tokens, glob, or regex. argv[0] is resolved to an absolute path before matching. For exact and prefix rules the first token's shape decides how it matches: a bare program name (go) matches the invoked basename however the command was spelled, an absolute path pins the exact executable, and a relative path (./scripts/build.sh) resolves against the project root. glob and regex rules match the canonical absolute join by default and accept as_basename: true to match the basename join instead; the shape inference covers exact and prefix, so as_basename is rejected there. Shells and inline-code interpreters are denied by default and cannot be re-allowed.

A restrict section adds a fourth tier below allow: a command that matches a restrict rule but no allow rule is refused rather than prompted, so a policy can enumerate the safe subset of a tool and fail the rest closed within that scope. The tiers run deny > allow > restrict > prompt, so an allow carves a command out of a restrict scope while an explicit deny still beats everything. restrict rules take the same four kinds as allow and deny and accept an optional message.

allow:
  - prefix: [git, show]
  - prefix: [git, log]
restrict:
  - prefix: [git]
    message: only read-only git is permitted here

Here git show and git log run, every other git subcommand is refused with the message, and a command outside the git scope still prompts.

enc

go install github.com/ripta/rt/cmd/enc@latest

Encode and decode strings using various encodings:

  • a85 for ascii85;
  • b32 for base32 (RFC 4848 standard encoding, ABCDEFGHIJKLMNOPQRSTUVWXYZ234567);
  • b32c for base32 with Crockford's alphabet (0123456789ABCDEFGHJKMNPQRSTVWXYZ);
  • b58 for base58;
  • b64 for base64 (RFC 4648 standard encoding, ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/);
  • hex for lowercase hexadecimal;
  • url for URL escape/unescape; and
  • varsel for encoding raw bytes into Unicode variation selectors (VS-1 to VS-256).

hs

go install github.com/ripta/rt/cmd/hs@latest

Hash the input and print the resulting hash in binary bytes. Run with -h to see the list of supported hash functions that are compiled into the binary, which is approximately:

  • sha1 for SHA-1;
  • sha224 for SHA-224;
  • sha256 for SHA-256;
  • sha3 for SHA-3/512;
  • sha384 for SHA-384; and
  • sha512 for SHA-512.

To output hexadecimal, pipe the output to enc hex. My knowledge graph uses a different representation for hashes, so it's useful to me to not have the hex representation.

❯ head -n 2 hamlet.txt
To be, or not to be: that is the question:
Whether 'tis nobler in the mind to suffer

❯ cat hamlet.txt | hs sha256 | enc hex
e26671d53d74b6751373ad34768580af77847aa1513203d9a06c292617ab5c4b%

❯ cat hamlet.txt | hs sha256 | enc base64
4mZx1T10tnUTc600doWAr3eEeqFRMgPZoGwpJherXEs=%

(ICYDK, that % at the end is zsh's PROMPT_EOL_MARK.)

grpcto

go install github.com/ripta/rt/cmd/grpcto@latest

Frame and unframe raw bytes in a gRPC envelope. For example, assuming a proto message crafted using either toto (included in this repo) or protoc --encode (the official protobuf compiler), you can frame the message using:

echo 'hello:"world"' \
    | protoc --encode foo.bar.v1.Thing ./thing.proto \
    | grpcto frame > message.raw

where the resulting message.raw can be sent directly to a running gRPC service using curl:

curl -X POST --data-binary @message.raw -o response.raw -H 'content-type: application/grpc' --raw https://localhost:8443/foo.bar.v1.Thinger/Thing

and the response.raw can be unframed and decoded using protoc:

cat response.raw \
    | grpcto unframe \
    | protoc --decode_raw

lipsum

Generate some placeholder text, beyond just lorem ipsum. It also does some optional rate-limiting, printing one word at a time.

go install github.com/ripta/rt/cmd/lipsum@latest

place

Talk to macOS Location Services from the command line.

go install github.com/ripta/rt/cmd/place@latest

Query as plaintext:

❯ place
Latitude: 34.009414
Longitude: -118.162233
Accuracy: 45.751999
Last observed: 2022-02-02T21:24:40-08:00

or as JSON by giving -j or --json.

streamdiff

Helps you pick out field changes off a stream of JSON.

go install github.com/ripta/rt/cmd/streamdiff@latest

It's technically usable on any stream as long as the format is one JSON per line.

It's convenient for viewing Kubernetes resource changes over time.

For example, you can start a watch (-w) on pods (kubectl get pods) and pipe it to streamdiff. Most fields won't be printed, except when they change. Consider this output:

❯ kubectl get pods -o json -w | streamdiff
T+23s Pod:pomerium-cache-6c9f84b747-cr2rx
  (1/2): spec.nodeName \ -> gke-vqjp-preemptible-065-38c45f41-wtnb
  (2/2): status.conditions \ -> [map[lastProbeTime:<nil> lastTransitionTime:2023-06-22T06:27:43Z status:True type:PodScheduled]]

T+24s Pod:pomerium-cache-6c9f84b747-cr2rx
  (1/6): status.conditions.0 \ -> map[lastProbeTime:<nil> lastTransitionTime:2023-06-22T06:27:43Z status:True type:Initialized]
  (2/6): status.conditions.1 \ -> map[lastProbeTime:<nil> lastTransitionTime:2023-06-22T06:27:43Z message:containers with unready status: [cache] reason:ContainersNotReady status:False type:Ready]
  (3/6): status.conditions.2 \ -> map[lastProbeTime:<nil> lastTransitionTime:2023-06-22T06:27:43Z message:containers with unready status: [cache] reason:ContainersNotReady status:False type:ContainersReady]
  (4/6): status.startTime \ -> 2023-06-22T06:27:43Z
  (5/6): status.containerStatuses \ -> [map[image:us.gcr.io/dc-02/gke-vqjp/pomerium-cache:v1.0.23.1390 imageID: lastState:map[] name:cache ready:false restartCount:0 started:false state:map[waiting:map[reason:ContainerCreating]]]]
  (6/6): status.hostIP \ -> 10.52.0.34

T+26s Pod:pomerium-cache-6c9f84b747-cr2rx
  (1/8): status.containerStatuses.0.ready false -> true
  (2/8): status.containerStatuses.0.started false -> true
  (3/8): status.containerStatuses.0.state.waiting map[reason:ContainerCreating] -> \
  (4/8): status.containerStatuses.0.state.running \ -> map[startedAt:2023-06-22T06:27:46Z]
  (5/8): status.containerStatuses.0.containerID \ -> containerd://293972feb5b498c80a585137299990c77f44ea46d6236432aba08e72108c35dc
  (6/8): status.phase Pending -> Running
  (7/8): status.podIP \ -> 10.53.1.92
  (8/8): status.podIPs \ -> [map[ip:10.53.1.92]]

While there is still some noise, it clearly shows when the pod was assigned to a node, when the pod finished initializing, and when it changed phases from Pending to Running.

In addition to a running log (as above), you can also run streamdiff -i, which updates status on the same line instead of printing a new line for every resource update. YMMV.

❯ kubectl get nodes -o json -w | streamdiff -i
\ Node:gke-vqjp-ondemand-370-504f82ce-r0d8	status.conditions.0.{type: FrequentContainerdRestart; status: True -> False} 
\ Node:gke-vqjp-preemptible-065-38c45f41-kvjd	status.conditions.0.lastHeartbeatTime: 2023-06-22T06:44:18Z -> 2023-06-22T06:49:19Z
| Node:gke-vqjp-preemptible-065-38c45f41-pklf	status.conditions.0.lastHeartbeatTime: 2023-06-22T06:44:15Z -> 2023-06-22T06:49:16Z
/ Node:gke-vqjp-preemptible-065-38c45f41-wtnb	status.conditions.0.lastHeartbeatTime: 2023-06-22T06:45:05Z -> 2023-06-22T06:50:11Z

structfiles (sf)

Proof of concept tool to examine and compare a pile of structured files (e.g., Kubernetes manifests) strewn across multiple directories or files, with any number of documents per file.

Supports YAML, JSON, TOML, HCLv2, GOB, CSV, MessagePack, and EDN as input and output, with some caveats:

  • HCLv2 output is experimental, due to the way that HCLv2 is schema-driven and the lack of a way to represent the schema in structfiles.
  • CSV does not support nested maps. CSV treats each row as a separate document. The first row of a CSV file is assumed to be the header.
  • YAML, JSON, and GOB support multiple documents in one stream.
  • EDN decoding forces stringification of map keys, and does not yet support the entire EDN spec, e.g., {:foo #{a 2}} still trips up the converter.
  • Logfmt does not support nested maps. Each log line is treated as a separate document.

Resulting diff currently only in unified diff of YAML (see example).

go install github.com/ripta/rt/cmd/sf@latest

For a list of supported formats and format-specific options, run sf formats:

FORMAT    EXTENSIONS      INPUT   OPTIONS      OUTPUT   OPTIONS
csv       .csv            yes     sep:string   yes      sep:string
edn       .edn            yes     -            yes      indent:int prefix:string
gob       .gob            yes     -            yes      -
hcl2      .hcl            yes     -            yes      -
json      .json           yes     -            yes      indent:int no_indent:bool
logfmt    .logfmt         yes     -            yes      -
msgpack   .mpk .msgpack   yes     -            yes      -
toml      .toml           yes     -            yes      indent:int
yaml      .yml .yaml      yes     -            yes      indent:int

The simplest subcommand is eval, which reads one or more files and prints the data back out, like a pretty-printer. The default format is JSON with an indentation of 2 spaces.

❯ cat $dangit
{"foo":
"bar"}

❯ sf eval $dangit
{
  "foo": "bar"
}

❯ sf eval -f json $dangit
{
  "foo": "bar"
}

❯ sf eval -f json -o no_indent=true $dangit
{"foo":"bar"}

Of course, you can use it to convert between formats by specifying the desired output format:

❯ cat $nabbit
{"foo":[1,2,"bar"]}

❯ sf eval -f toml $nabbit
foo = [1.0, 2.0, "bar"]

❯ rt sf eval -f hcl2 $nabbit
foo = [1, 2, "bar"]

Some formats may expect different shape data though:

❯ rt sf eval -f csv $nabbit
Error: interface conversion: interface {} is []interface {}, not string

As a special case, you can also read from STDIN by specifying stdin:// (or -), which assumes JSON or YAML. To optionally control the format parser, use stdin://FORMAT.

❯ mj foo=bar | sf eval -f yaml -
---
foo: bar

❯ mj foo=bar | sf eval -f yaml stdin://json
---
foo: bar

❯ generate-gob | sf eval -f json stdin://gob
{"vals":[1,2,3]}

For a more advanced example, compare two directories of Kubernetes manifests containing all-in-one manifests (foo_aio) and one-resource-per-file (foo_each), using -k:

❯ sf diff -k ./samples/manifests/foo_aio ./samples/manifests/foo_each
--- ./samples/manifests/foo_aio
+++ ./samples/manifests/foo_each
@@ -25,7 +25,7 @@
             "name": "web",
             "ports": [
               {
-                "containerPort": 80
+                "containerPort": 8080
               }
             ]
           }
@@ -60,7 +60,7 @@
         }
       }
     },
-    "schedule": "*/1 * * * *"
+    "schedule": "* * * * *"
   }
 }
 {
@@ -73,8 +73,8 @@
   "spec": {
     "ports": [
       {
-        "port": 80,
+        "port": 8080,
-        "targetPort": 80
+        "targetPort": 8080
       }
     ],
     "selector": {

You can diff multiple files against one file by using the :: delimiter. Arguments before the delimiter are taken as one input, while arguments after are taken as the second input to the diff:

❯ sf diff -k ./samples/manifests/foo_aio :: ./samples/manifests/foo_each/*.yaml

You can compare piles of structured files of differing formats and control the output format being diffed with -f

❯ sf diff -f json ./samples/configs/yaml_each ./samples/configs/toml
--- ./samples/configs/yaml_each
+++ ./samples/configs/toml
@@ -32,7 +32,7 @@
       "role": "backend"
     }
   },
-  "title": "YAML Example One"
+  "title": "TOML Example One"
 }
 {
   "autoscaling_rules": [
@@ -68,5 +68,5 @@
       "role": "frontend"
     }
   },
-  "title": "YAML Example Two"
+  "title": "TOML Example Two"
 }

For tab-delimited output, use the CSV format and set the separator to tab: -f csv -o sep=$'\t'

toto

Some dynamic protobuf inspection tools.

go install github.com/ripta/rt/cmd/toto@latest

You can build file descriptor set, and use protoc to inspect it:

toto compile samples
cat samples/.file_descriptor_set | protoc --decode_raw

Or generate an example protobuf message and dynamically convert it to json:

toto sample | toto recode -p samples/.file_descriptor_set -f json samples.data.v1.Envelope

The toto compile step is necessary, because you can't currently parse proto files directly in go (or at least, I wasn't able to).

uni

Unicode-related stuff.

# For a smaller installation, excluding the Unicode Han Database:
go install github.com/ripta/rt/cmd/uni@latest

# To include Unicode Han Database, which adds about 25MB to the binary:
go install -tags unihan github.com/ripta/rt/cmd/uni@latest

Size comparison:

❯ stat -f '%z %N' uni unihan
 6572386 uni
34410114 unihan

List characters:

❯ uni list java cecak
U+A981 	ꦁ	[EA A6 81   ]	<M,Mn>	JAVANESE SIGN CECAK
U+A9B3 	꦳	[EA A6 B3   ]	<M,Mn>	JAVANESE SIGN CECAK TELU

List characters with fewer details:

❯ uni list java cecak -o hexbytes,name
[EA A6 81   ]	JAVANESE SIGN CECAK
[EA A6 B3   ]	JAVANESE SIGN CECAK TELU

Show only the aggregate count (-c), skipping output (-o none):

❯ uni list java cecak -o none -c
Matched 2 runes

Show only characters in a specific character category, e.g.:

# All "Pd" (punctuation, dash)
❯ uni list -C Pd

# All "S" (symbols)
❯ uni list -C S

# All "N" (numbers) that aren't "No" (other)
❯ uni list -C N,!No

# All "Lu" (letters, uppercase) and "Ll" (letters, lowercase)
❯ uni list -C Lu,Ll

# All Cyrillic uppercase and lowercase letters (i.e., excluding modifiers and subscripts)
❯ uni list -C Lu,Ll cyrillic

# All iotified Cyrillic letters not containing 'small'
❯ uni list cyrillic iotified !small

Show only characters in a specific script, e.g.:

# All Sundanese characters, by codepoint name:
❯ uni list sundanese

# All Sundanese characters, by script name, which needs the --all flag:
❯ uni list -S Sundanese --all

Show only certain codepoints by character or codepoint:

# All lowercase ASCII characters:
❯ uni list -r a-z

# Uppercase A-G and lowercase a-g ASCII characters:
❯ uni list -r A-G,a-g

# Special characters from colon (codepoint 3A) to at sign (codepoint 40):
❯ uni list -r u+3a-40

# Emojis between 🤤 and 🤗 (order does not matter):
❯ uni list -r 🤤-🤗
❯ uni list -r 🤗-🤤

# Combine filters: emojis between 🤤 and 🤗 whose name includes "hand":
❯ uni list -r 🤤-🤗 hand

Don't forget to escape ! in your shell if necessary.

List all character categories, their names, and counts:

❯ uni cats
KEY   NAME                    RUNE COUNT
C     Other                   139751
Cc    Control                 65
Cf    Format                  170
Co    Private Use             137468
[...]

List all scripts and counts:

❯ uni scripts
NAME                     RUNE COUNT
Adlam                    88
Ahom                     65
Anatolian_Hieroglyphs    583
[...]

Describe characters:

❯ echo 𝗀𝘨| uni describe
U+1D5C0 𝗀       [F0 9D 97 80]   <L,Ll>  MATHEMATICAL SANS-SERIF SMALL G
U+1D628 𝘨       [F0 9D 98 A8]   <L,Ll>  MATHEMATICAL SANS-SERIF ITALIC SMALL G
U+000A  "\n"    [0A         ]   <C,Cc>  <control>

Map characters for fun:

❯ echo Hello World | uni map smallcaps
Hᴇʟʟᴏ Wᴏʀʟᴅ

❯ echo Hello World | uni map italics
𝐻𝑒𝑙𝑙𝑜 𝑊𝑜𝑟𝑙𝑑

Canonically compose runes:

❯ echo 감 | uni nfc
감

❯ echo 감 | uni nfd
감

Sometimes it may be useful to decompose runes before describing:

❯ echo 쭈꾸쭈꾸 | uni d
U+CB48  쭈      [EC AD 88   ]   <L,Lo>  <Hangul Syllable>
U+AFB8  꾸      [EA BE B8   ]   <L,Lo>  <Hangul Syllable>
U+CB48  쭈      [EC AD 88   ]   <L,Lo>  <Hangul Syllable>
U+AFB8  꾸      [EA BE B8   ]   <L,Lo>  <Hangul Syllable>
U+000A  "\n"    [0A         ]   <C,Cc>  <control>

❯ echo 쭈꾸쭈꾸 | uni nfd | uni describe
U+110D  ᄍ      [E1 84 8D   ]   <L,Lo>  HANGUL CHOSEONG SSANGCIEUC
U+116E          [E1 85 AE   ]   <L,Lo>  HANGUL JUNGSEONG U
U+1101  ᄁ      [E1 84 81   ]   <L,Lo>  HANGUL CHOSEONG SSANGKIYEOK
U+116E          [E1 85 AE   ]   <L,Lo>  HANGUL JUNGSEONG U
U+110D  ᄍ      [E1 84 8D   ]   <L,Lo>  HANGUL CHOSEONG SSANGCIEUC
U+116E          [E1 85 AE   ]   <L,Lo>  HANGUL JUNGSEONG U
U+1101  ᄁ      [E1 84 81   ]   <L,Lo>  HANGUL CHOSEONG SSANGKIYEOK
U+116E          [E1 85 AE   ]   <L,Lo>  HANGUL JUNGSEONG U
U+000A  "\n"    [0A         ]   <C,Cc>  <control>

Sort input with different collation (-l):

❯ cat input.txt
Œthelwald
Zeus
Achilles

❯ cat input.txt | uni sort -l en-US
Achilles
Œthelwald
Zeus

❯ cat input.txt | uni sort -l da
Achilles
Zeus
Œthelwald

❯ cat input.txt | uni sort -l da -r
Œthelwald
Zeus
Achilles

yfmt

Reindent YAML while preserving comments.

go install github.com/ripta/rt/cmd/yfmt@latest

This tool treats comments as nodes and therefore will not preserve comment indentation. For example:

❯ cat in.yaml
# does this work?
foo:
   - 123   # I hope
           # maybe
   - 456

❯ yfmt < in.yaml
# does this work?
foo:
  - 123 # I hope
  # maybe
  - 456

Directories

Path Synopsis
cmd
calc command
cg command
enc command
grpcto command
hs command
lipsum command
place command
sf command
shast command
streamdiff command
toto command
uni command
yfmt command
hypercmd
rt command
rt_lite command
pkg
cg
Package cg assembles the full `cg` command tree: the capture-run model's own subcommands (package model), the MCP server (package mcp), and the approval-rules subcommands (package approvecmd).
Package cg assembles the full `cg` command tree: the capture-run model's own subcommands (package model), the MCP server (package mcp), and the approval-rules subcommands (package approvecmd).
cg/approve
Package approve implements the cg_run approval gate: a layered YAML rules file, a load-once config store, and an argv-native matcher that decides whether a command runs, is refused, or needs an interactive prompt.
Package approve implements the cg_run approval gate: a layered YAML rules file, a load-once config store, and an argv-native matcher that decides whether a command runs, is refused, or needs an interactive prompt.
cg/approvecmd
Package approvecmd implements the `cg check` and `cg lint` subcommands.
Package approvecmd implements the `cg check` and `cg lint` subcommands.
cg/mcp
Package mcp implements `cg mcp`, a stdio MCP server that exposes cg's capture-run model as native MCP tools.
Package mcp implements `cg mcp`, a stdio MCP server that exposes cg's capture-run model as native MCP tools.
cg/model
Package model implements cg's capture-run domain: the on-disk model for runs and pools (meta, start, debug, and pool manifest records), execution (RunSupervised, PoolSupervised, and the supervise-run/supervise-pool commands that back them), resolution and locking, notes, grep, prune, and the `cg run` annotator.
Package model implements cg's capture-run domain: the on-disk model for runs and pools (meta, start, debug, and pool manifest records), execution (RunSupervised, PoolSupervised, and the supervise-run/supervise-pool commands that back them), resolution and locking, notes, grep, prune, and the `cg run` annotator.
enc
enc/varsel
Emoji Encoder implementation from https://github.com/paulgb/emoji-encoder/blob/main/app/encoding.ts
Emoji Encoder implementation from https://github.com/paulgb/emoji-encoder/blob/main/app/encoding.ts
shast
Package shast parses shell command strings with mvdan.cc/sh/v3/syntax and exposes the result as JSON, since that package has no JSON marshaling of its own and a naive encoding/json pass over it loses position info and every interface field's concrete type.
Package shast parses shell command strings with mvdan.cc/sh/v3/syntax and exposes the result as JSON, since that package has no JSON marshaling of its own and a naive encoding/json pass over it loses position info and every interface field's concrete type.
structfiles/hcl2any
Package hcl2any is a version of github.com/hashicorp/hcl/cmd/hcldec that doesn't require a schema to decode HCL2 files.
Package hcl2any is a version of github.com/hashicorp/hcl/cmd/hcldec that doesn't require a schema to decode HCL2 files.
uni
samples

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL