Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The shell

:shell opens a shell in a buffer. You type a command at the prompt at the bottom, enter runs it, and it becomes a block above the prompt: the command, what it printed, and how it ended. Blocks stay, so you can search them, fold them, copy from them, run them again and send their output to the next command. It’s meant for the commands you run while editing, and for output you want to keep working with; a terminal pane is still the place for a long interactive session.

❯ 1 cargo test -q                         ✗ exit 101 · 4.1s
error[E0308]: mismatched types
 --> src/main.rs:12:5
❯ 2 git status --short                    ✓ 0.02s
 M src/main.rs
⎇ main ~/s/app ❯ |

The prompt shows the shell’s folder, every folder but the last cut to its first letter, and before it what the prompt knows, like the git branch. space o s switches to the shell of this project, or opens one; with several, or from the only one, it lists them with a new one to open. :shell NAME opens another. Each shell has its own folder and variables, and a project can have several. Shells and their blocks last until you close them or the session ends, and what you type in one is nothing to save: :q doesn’t ask about it. A new shell says to type help, which lists what the shell runs itself, what a line can hold, which shell runs the rest, and the keys in the style you use; help TOPIC shows one part.

The prompt

Before the folder, the prompt shows the branch of the repository the shell is in (with jj, the nearest bookmark and the change), and the kubernetes context and namespace kubectl would use. The context comes from kubectl’s config file, the ones KUBECONFIG names or ~/.kube/config, read without running kubectl, so it’s there at once and costs nothing. They’re worked out again after every command, so a cd, a checkout or kubectl config use-context shows on the next prompt.

⎇ main ⎈ prod-eu:payments ~/s/infra ❯ kubectl get pods

In a project with a devenv environment, ❄ devenv shows too (see Project environments).

A context matching a pattern in shell-production-contexts (prod by default; several go with spaces between them, as Luau patterns, like :set shell-production-contexts "prod ^live%-") shows in red, and so does the ❯, so a command about to run against production looks like it. Plugins add pieces of their own, like a cloud profile; help prompt lists them.

Each piece has a color of its own from the theme (shell.segment.git, shell.segment.kubernetes, shell.segment.environment). With a Nerd Font, :set shell-icons nerd uses its icons in place of ⎇ ⎈ ❄.

If you use starship, :set shell-prompt starship shows your starship prompt instead, as your other shells do, with its colors: Greed runs starship prompt after each command with the shell’s folder, variables, the last exit code and how long it took. The ❯ stays Greed’s own, red in production, so starship’s prompt character is left out. Put it in your config to keep it:

greed.option.set("shell-prompt", "starship")

Blocks

A block’s command is the line it starts on, with ❯ before it (◆ for one an agent ran) and its number, the 3 of $3. After the command comes how it went: ◐ while it runs with how long so far, then ✓, ✗ exit 1, ■ INT when it was stopped, ↗ when it was handed to a terminal, or ? for a question to a model that brought no command back, how long it took, where cd went and which variables it set. A block that went quiet while it could be reading a line says it may be waiting for input.

Output that’s JSON or a table is kept as a value on the block too, and the status says so (JSON · 3 items, table · 12 rows); see Filtering and tables. Places in files the output names, like src/main.rs:12:5, are links: ] e and [ e go to them, and enter on one opens the file there, taking relative paths from the folder the block ran in.

A block shows at most shell-max-lines (5000) lines of its output, the first half and the latest half, saying how many it leaves out between them; space v opens all of it in a buffer of its own. Past shell-max-record (8 MB) the whole output goes to a file in the cache folder, which space v opens.

A prompt of more than shell-confirm-lines (5) lines, as pasting a transcript or a file can leave there, asks y/n before it runs as one command.

Undo at the prompt takes back what you typed since the last command ran, while output keeps streaming in above it; it never reaches back into a command that ran.

Keys

In Helix and Vim you type at the prompt in insert mode. enter on a block, the keys for moving between blocks and files, g o and the space keys work in normal mode; up, down, alt-enter and tab work while typing.

Keys
enterAt the prompt, run what’s there. On a running block’s line above the prompt, open the block in a float. On a block, put its command at the prompt to change; on a file and line it names, open it there; on a row that’s a thing (a file, a pod), do its usual action
alt-enterAnother line at the prompt
up downAt the prompt: the commands typed before that start with what’s typed
ctrl-rPick a command typed before, in any shell
right end ctrl-eAt the end of the prompt, take the rest of the command it suggests
tabComplete a program, its arguments and flags, a file or folder, a variable, $3, buf: or @sel: the only match or what they all start with at once, else a menu, which tab again goes through
ctrl-cInterrupt the running block
ctrl-tType into the running block’s program, in a terminal in the shell’s place
[ p ] pThe block before or after the cursor
g oSelect the block’s output
] e [ eThe next or previous file and line the output names
space r space RRun the block again as it ran, or in the shell’s folder now
space y space YCopy the block’s output, or its command
space xStop the running block for good
space vOpen the block’s whole output in a buffer
space cTake the finished blocks away
/On a block’s output, keep the lines (or a table’s rows) with what you type; elsewhere, search
space s space SSort a table by the column under the cursor; show a block as it came
tab shift-tabOn a block, fold it under its command or unfold it; fold or unfold every block (see Folding blocks)

In the vscode style you’re always typing, so the keys that need normal mode have others: ctrl-c copies when something is selected and interrupts otherwise, ctrl-up and ctrl-down go between blocks, f8 and shift-f8 between the files and lines the output names, ctrl-f filters a block, ctrl-k ctrl-l folds or unfolds a block (ctrl-k ctrl-0 folds them all, ctrl-k ctrl-j unfolds them all), and the space keys above are ctrl-space ones. space . (alt-. in the vscode style) on a block, or on a row that’s a thing (see Rows you can act on), lists what you can do with it.

Folding blocks

A folded block shows only its command line, with how it ended and how many lines it hides (⋯ 240 lines), so a long session stays easy to look through. tab on a block folds or unfolds it, and shift-tab folds or unfolds them all. A block that’s still printing stays folded as its output grows.

Resting the mouse on a folded block shows it in a float without moving the cursor: scroll it there, or click into it to filter it with / and act on its rows. Moving the mouse away closes it.

Blocks can fold by themselves. Blocks that failed never do, since those are the ones to read again:

local greed = require("@greed")
-- Fold a block that printed more than 40 lines as it ends.
greed.option.set("shell-fold-lines", 40)
-- Running a command folds the finished blocks above it.
greed.option.set("shell-fold-older", true)

What runs your commands

The shell runs a few commands itself:

Command
cd [DIR]Change the shell’s folder: a path, - for the one before, nothing for home
export NAME=VALUE ...Set variables for the commands after
unset NAME ...Leave variables out of the commands after
env [reload]List the variables this shell sets or leaves out, and the environment its folder brings (devenv, direnv); reload loads that again
historyThe commands typed before
clearTake the finished blocks away
open PATH ...Open files, or buf:NAME buffers, in the editor
help [TOPIC]What works here, and the keys in the style you use; a topic for one part
which NAME ...What a word at the start of a line runs: a builtin, a table stage, one of your aliases or a program
stop [N ... | all]Stop running blocks by their number, or all of them; without one, list them
watch [-n SECONDS] COMMANDRun a command again and again, every two seconds, its block showing what it printed last with the changes marked

What open opens comes up in your style’s own mode, to read and move around in, not typing as at the prompt. Plugins add commands of their own (Writing plugins), and help builtins lists them with these.

Every other line goes to your shell, so ls and ls | head behave the same: your $SHELL when it’s a POSIX shell (sh, bash, zsh, dash, ksh and the like), fish or nu, and sh otherwise; :set shell-fallback bash picks one. cd and export there carry over to the commands after, as they would in a terminal. If shell-fallback names a shell of another kind, the line still runs in it, and its block says cd and export in it don’t carry over. Your shell’s startup files aren’t read for each line, so its functions aren’t there unless you :set shell-fallback-rc true.

Your aliases are. The shell asks your shell for them once, with its startup files, and a line starting with one runs what it stands for, as in a terminal; the block shows what you typed. The shell’s own commands win over an alias of the same name, ^NAME runs the program NAME whatever else has that name (^env, or $3 | ^sort for the sort program rather than the table stage), and which NAME says what runs. :set shell-aliases false leaves aliases alone.

As in bash and zsh, !! in a line is the command before, !$ its last word, and a line ^old^new runs the command before with old changed to new. The block shows the command as it ran.

Programs get PAGER=cat and GIT_PAGER=cat, so their output comes back as text, GREED_BIN, the greed program running the editor, GREED_SESSION and GREED_BLOCK, the block they run in.

Editing files from a command

A program that opens an editor, like git commit, jj describe, kubectl edit or crontab -e, opens the file in Greed, in the pane the command ran from. :wq (or :q once it’s saved) hands it back and the shell shows there again; :cq stops the program with an error, so git commit gives up. EDITOR, VISUAL, GIT_EDITOR, JJ_EDITOR and KUBE_EDITOR are greed edit --wait, so this wins over an editor set in git’s or jj’s config. An agent’s commands keep your own editor, and :set shell-editor false turns it off; export EDITOR=vim in a shell picks another for that shell.

greed edit FILE... works from any terminal too: it opens the files in the running editor, and with --wait returns once each is done with.

Project environments

In a folder with a devenv.nix, the shell loads devenv’s environment, as devenv shell would, and its commands run with it: the project’s tools on the PATH, its variables, and what enterShell sets up. No direnv is needed, and devenv’s own rule applies: a project loads once you’ve run devenv allow there. The prompt says it’s there, ❄ devenv, with … while it loads (a command typed meanwhile waits for it, and ctrl-c runs it without), or that it failed, which env says more about.

Changing devenv.nix, devenv.yaml, devenv.lock or anything else devenv read loads it again; env reload does too. Leaving the project leaves its environment behind, and what you export wins over it. Every shell in a project shares one load, and an agent’s commands get it too.

A folder with an .envrc and no devenv.nix loads it through direnv, when that’s installed and you’ve run direnv allow there, the prompt saying ❄ direnv. Changing the .envrc loads it again; for other files it reads, env reload does. A project with both, like an .envrc holding use devenv, gets devenv’s straight away.

shell-environments (devenv direnv) says which kinds load and which is asked first; :set shell-environments "" turns them off. Plugins add other kinds.

Terminals

Commands you type run in a pseudo terminal of their own, so they see a terminal: they print their colors and progress bars, and their output and errors come in order. A line longer than the screen stays one line in the buffer, so each client shows it wrapped at its own width.

A program that wants the whole terminal (an editor, a pager, htop, ssh, sudo asking for a password) takes over the shell’s pane as a terminal, and the shell comes back when it’s done with the screen or exits, its block marked ↗. ctrl-t does the same for any running block, to answer a question it’s waiting on. Start a line with ! to run it in a terminal from the start.

Data from the editor

A line can start with something from the editor, piped into the command:

$3 | sortWhat block 3 printed
buf:NAME | wc -lThe text of a buffer: one > buf: made, an open file at that path, or an open file of that name
@sel | jq .The selection in the file you’re in, each range on a line of its own
@file | headThe whole file you’re in, unsaved changes included

And end with where its output goes:

... > buf:NAMEInto a buffer, made if there’s none, emptied first
... >> buf:NAMEAdded to the end of a buffer

Errors still show in the block. One of these alone passes it on: $3 shows block 3’s output again, $3 > buf:keep keeps it in a buffer, and open buf:keep shows that buffer. Such lines run through a pipe rather than a terminal, so a > buf: gets only what the program printed, and can’t be combined with !.

After a command’s first word, $3 is a file holding what block 3 printed, for programs that read files rather than their input:

❯ diff $3 $5
❯ kubectl apply -f $7
❯ open $3

The file ends .json when the block printed JSON. Single quotes keep $3 as it is (awk '{print $3}'), and so does \$3.

Filtering and tables

Most output is text, and stays text. On top of it the shell keeps what it can read out of it, and every layer below works on what’s under it.

Filtering a block

/ on a block’s output keeps the lines with what you type in them, as you type it; several words keep the lines with all of them, in any case (shell-filter-search-case changes that), and a filter that starts with / is a regex (/^error|warn). enter keeps the filter; esc, while typing it or after in normal mode on the block, shows every line again, and space S also undoes a sort. The block shows the lines kept and the status says so (2 of 340 lines · / error), while its record keeps them all, so $3, space y and space v give the whole output. On a block’s command or at the prompt, / searches as usual.

Tables

Output in columns under a header, as kubectl get, docker ps, ps aux and df print it, is also kept as a table on the block, and the status says so (table · 12 rows). So is ls -l’s output. The shell looks for a header in capitals with the columns lined up under it in every row, and when it isn’t sure, the output is text: prose is never taken for a table.

In a table, / keeps rows: a word like STATUS=Running or NAME~api looks in one column, RESTARTS>0 and AGE<1h compare numbers, and other words look in every cell. space s sorts the rows by the column under the cursor, again from the greatest, and a third time as they came.

❯ 3 kubectl get pods                   ✓ 0.41s · 2 of 5 rows · / STATUS=Running api
NAME                  READY   STATUS    RESTARTS        AGE
api-7d9f8b6c5-2xkqz   1/1     Running   0               3d16h
api-7d9f8b6c5-9wlmn   1/1     Running   3 (3d16h ago)   5d2h

A table also goes to the next command through $, where the shell itself runs these on it, each making a new table, as in $3 | where STATUS = Running:

where COL OP VALUEThe rows passing a test: = != ~ !~ < > <= >=
select COL,...Only those columns, in that order
sort COL [-r]Sorted by a column, from the greatest with -r

Column names don’t mind case, and container_id names CONTAINER ID. Numbers are compared as numbers, with sizes (512Mi, 1.5G, 250m of a CPU), percentages and ages (45s, 12m, 3d16h) read as what they mean, so $3 | where AGE < 1h and $3 | sort RESTARTS do what they say; kubectl’s 3 (3d16h ago) restarts count as 3. They chain, and a program after them reads the table as text:

❯ $1 | where STATUS != Running | select NAME,STATUS,AGE
❯ $1 | sort RESTARTS -r | where RESTARTS > 0 | wc -l
❯ $1 | where IMAGE ~ postgres > buf:databases

sort without a column is the sort program, as always.

They work after a program too, in one line: what it prints is read as a table, as a block’s output is, and the stages run on that.

❯ kubectl get pods | where STATUS != Running | select NAME,AGE
❯ docker ps --format json | where Image ~ postgres
❯ $1 | grep api | where RESTARTS > 0

where, select, get, columns, from, lines, filter, map, count, group, first, to and each start the stages after a program, since no program goes by those names; sort, split, uniq, sum and last are programs too, so they’re stages only after one of those, and only when they name a column (a number for last). What the program prints before the stages isn’t shown, only what comes out at the end, and its errors. A stage on output that isn’t a table says so, and what makes one.

More stages

filter EXPRThe rows a Luau expression holds for, with the row’s cells as r and its numbers as n
map COL = EXPRA column made (or changed) by a Luau expression on r and n
countHow many rows there are
first [N]The first N rows, one without N
last NThe last N rows
uniq COLThe first row for each value in a column
sum COLThe numbers in a column added up
group COLEach value in a column and how many rows have it
to json|csv|tsvThe table as JSON, CSV or tab separated values
each COMMANDRun a command for every row, {COL} in it the row’s cell, quoted

filter and map take Luau. In it, r is the row, its cells as text (r.STATUS, r["CONTAINER ID"]), and n the cells that read as numbers, sizes and ages included (n.RESTARTS, n.AGE in seconds). It runs in a sandbox that can read the editor and nothing more, so a line an agent wrote can’t do anything else either.

❯ kubectl get pods | filter n.RESTARTS > 0 and r.STATUS ~= "Running"
❯ kubectl get pods | map hours = n.AGE / 3600 | sort hours -r | first 5
❯ kubectl get pods | group STATUS
❯ kubectl get pods | where STATUS = Evicted | each kubectl delete pod {NAME}
❯ docker ps | to csv > buf:containers.csv

each runs its command once for every row, with {COL} the row’s cell, quoted for your shell; the commands run one after another in your shell and print into the block. What comes after each is all the command, so each echo {NAME} | wc -c counts each name.

Plugins add stages of their own (Writing plugins), and help tables lists them all.

JSON

Output that’s JSON is kept as a value on the block (JSON · 3 items), and an array of objects is a table too, with a column for each key, so / and space s show it lined up in columns, and $3 | where works on it. Many tools print JSON when asked, which is the surest way to a table: kubectl get pods -o json gives every field, where its columns give a few. The shell never runs your command again with other flags; it reads what it printed.

The stages work on an object too: its fields are the columns of one row, so $3 | select name gives its name, and when one of its fields holds an array of objects, like kubectl’s items, the columns it doesn’t have are that array’s, so $3 | where phase = Failed looks through the items. get picks a field:

get PATHA field of JSON, like items or items.0.name (arrays count from 0)

$3 | get items is the items’ table, $3 | get items.0.name the first one’s name, $3 | get metadata.labels an object, printed as JSON, and $3 | get items.name each item’s name.

Saying what it is

When the shell finds no table, say how to read the output, as in $3 | from csv:

columns [-H]Text lined up in columns, under a header or with -H none
from csv|tsv|jsonCSV, tab separated values, or JSON or JSON lines
split SEPEach line cut at SEP into columns 1, 2, …
linesA row for each line, in a column line

columns -H names the columns 1, 2, …, as split does, and from json reads JSON lines too, an object on each line, as many logs are.

They make tables like any other, for where, select and sort: $3 | split : | where 7 ~ zsh | select 1. A buffer or the selection works too: buf:data.csv | from csv | where total > 100. A program in a terminal prints tabs as spaces, so tab separated output reads best from a file, with buf:.

Parsers from plugins

A plugin can read its commands’ output as tables, ahead of the shell’s own look (Writing plugins has how). And when jc is installed, output the shell found no table in, from a command jc knows (ifconfig, dig, mount, uptime and many more), gets jc’s reading of it.

Watching a command

watch COMMAND runs a command again and again, every two seconds (-n 5 for every five), and its block shows what it printed last in place of what it printed before, with the lines that changed since the time before marked. It’s the shell’s own, so it stays a block among the others rather than taking the screen; ^watch runs the watch program in a terminal.

❯ 7 watch kubectl get pods       ◐ 1m12s · every 2s · 1 changed · 14:05:02
NAME                     READY   STATUS             RESTARTS   AGE
api-7d9f8b6c5-2xkqz      1/1     Running            0          3d16h
worker-5c6b7d8f9-abcde   0/1     CrashLoopBackOff   13         2h

The table and its rows follow along: a / filter or a space s sort stays on what each run shows, space . on a pod acts on it as it is now, and $7 | where STATUS != Running reads what it shows. The whole line is watched, pipes and table stages included, so watch kubectl get pods | where RESTARTS > 0 shows only the pods that restarted, as they are now. ctrl-c or space x stops it, keeping what it showed last; it also stops by itself after shell-watch-minutes (60), and space r starts it again. Agents can’t watch, as a watch runs until someone stops it.

kubectl get pods -w needs no watch: kubectl prints a pod again each time it changes, and the block shows each pod once, in its place, lined up under the header, the ones that changed since they first showed marked, rather than a longer and longer list. Its rows are pods to act on, as any kubectl get block’s are. It ends when kubectl does, or with ctrl-c.

Running blocks above the prompt

A watch, or anything else still running, scrolls up out of sight as you run more commands. So every running block but the last gets a line just above the prompt, saying what it shows now (how many rows, for a table) and what changed in it last, or how long it has run:

❯ 9 git status --short                    ✓ 0.02s
 M src/main.rs

◐ 7 watch kubectl get pods  · 3 pods · api-7d9: STATUS Running → Error · 14:05
◐ 8 cargo build --release   · 1m 12s
~/s/infra ❯

What changed names the row: new worker-abc, worker-abc gone, or the first column that changed, with how many more rows did. Columns that change by themselves, like AGE or the (5m ago) of RESTARTS, don’t count, so a quiet watch stays quiet. To hear about changes while you’re in another window, space . on the block (or its line) and “notify on changes” (shell-notify): each change then sends a desktop notification saying the same, while Greed isn’t focused. Again to stop.

Such a line stands for its block, a line or two up from the prompt: ctrl-c or space x there stops that block rather than the latest, space . lists what you can do with it, and space r runs it again. enter (or a click on its number) opens the block in a float, and / opens it there to filter. In the float it’s the block itself: / filters it, enter or space . on a row acts on the pod or file it is, and ctrl-c stops it. A watch’s float follows it as it refreshes, and grows and shrinks with it. esc closes it, keeping the filter, which a watch keeps on each run. space . on any block offers “open in a float” too. A line goes when its block ends.

At the prompt, ctrl-c stops the latest running block and says which others still run. stop lists them by number, stop 7 stops block 7, and stop all stops them all.

From anywhere, even a file you’re editing, space o w lists what runs in every shell, this project’s first, with each one’s output beside it: enter opens it in a float, ctrl-o goes to it in its shell, and ctrl-x stops it.

Rows you can act on

Some tables’ rows are things: an ls -l row is a file or a folder, a ps row a process, a kubectl get row a pod, deployment or other kubernetes object, a docker ps row a container. The status says so (table · 5 pods). On such a row, space . lists what you can do with it, the same actions that kind of thing has anywhere in Greed, and enter does the usual one: a file opens, a folder becomes the shell’s folder, a pod or another kubernetes object is described, a container shows its logs.

❯ 4 kubectl get pods                      ✓ 0.38s · table · 3 pods
NAME                     READY   STATUS             RESTARTS   AGE
api-7d9f8b6c5-2xkqz      1/1     Running            0          3d16h
worker-5c6b7d8f9-abcde   0/1     CrashLoopBackOff   12         2h      ← space .
                                   describe · logs · follow logs · shell
                                   yaml · json · events · delete

What an action does is a command, run as a block of its own, so you see it, can run it again and change it:

❯ 5 kubectl logs worker-5c6b7d8f9-abcde --tail 200 --context prod-eu -n payments

A row remembers where it came from. The kubernetes context and namespace are the ones the command ran against, from its flags or else kubectl’s config as it was when the block finished, so acting on a row of an old block after kubectl config use-context still goes to that row’s cluster, and the command says which. With -A, each row’s namespace is its own. docker --context is kept the same way.

Actions that delete or stop something (delete, restart, killing a process, stopping a container) ask first, and say so when the row is in a context matching shell-production-contexts:

delete worker-5c6b7d8f9-abcde - in PRODUCTION (prod-eu)? y/n

shell on a pod or a container opens a shell in it in a terminal.

What the blocks above showed completes the next command. After kubectl get pods, kubectl logs <tab> offers those pods first, each saying which block it’s from (pod · $4), then whatever kubectl itself offers; so do exec, describe pod, delete deploy and rollout restart deployment for theirs. Only rows from where the command goes are offered: the context and namespace its flags name, or else kubectl’s config now. docker logs <tab> offers the containers of a docker ps above, and kill <tab> the processes of a ps.

Plugins add kinds of rows and actions of their own (Writing plugins).

Asking for a command

A line that starts with ? asks a model for a command instead of running anything: ? the ten biggest files under here. So does a line ending in ? that reads as a question (three words or more, so ls a? stays a glob), and one with a ? between what you’ve typed so far and the question, ls | where ? only files over 1 MB. The model gets the shell’s folder, which shell runs your lines, and your last few commands with how they ended and the end of their output, marked as text someone else wrote. Its command goes to the prompt for you to read, change and run.

It asks the model playing shell-ask-role (fast); :models shows and sets them up. Without one, or without its key, a block marked ? (nothing ran, so it has no exit code) says why, naming the variable that sets the key, like ANTHROPIC_API_KEY.

History and completion

The commands you type at every shell are kept in your data folder (~/.local/share/greed/shell/history.jsonl), the last shell-history-size (5000) of them, so up and ctrl-r find them in every session. An agent’s commands aren’t kept.

As you type, the prompt suggests the rest of the newest command that starts with what’s there, dimmed after the cursor, as fish does: right, end or ctrl-e at the end takes it, and typing on goes past it. :set shell-suggestions false turns it off.

~/s/app ❯ cargo t|est -q --workspace

tab completes what fits where the cursor is, as a shell does: the only match goes in at once, and with several, what they all start with. When that’s all there is, tab shows the menu, and tab again puts each item in place in turn (shift-tab goes back); enter or typing on keeps it. Command names that start with what you typed come first, then those holding it, then looser matches, so hist offers history first.

A menu also opens by itself once typing pauses (completion-delay, 150 ms). It’s a hint until you move into it with tab: enter runs the line as typed, and up and down go through the history. So typing ls -la, pausing and pressing enter runs ls -la. :set completion-enter first makes enter take the menu’s first item instead.

What the shell itself knows is marked greed in the menu, with what it does beside it:

  • A command’s first word: the builtins, your aliases and the programs on your PATH; after $3 |, the stages too.
  • A builtin’s arguments: folders after cd, files and buffers after open, variables after export and unset, topics after help.
  • A stage’s: the columns of the table it reads ($3 | where ), then the comparisons, then the values in that column; from’s formats.
  • $ for variables and earlier blocks, buf: for buffers, and @sel and @file.

A program’s arguments come from what knows them, the first that answers: a completer a plugin added for the program; the program itself when it’s built with Cobra (kubectl, helm, gh, docker, flux, argocd, and any other with Cobra’s completion inside); fish’s completions when fish is installed; carapace’s when it is; and for a flag, the program’s man page, read from the file. Last, when you press tab on a flag, what the program prints for --help (and for a subcommand its help lists, that one’s). Files and folders when none of them knows, or the program says files fit.

~/s/app ❯ kubectl get po
  pods                  Pod
  podtemplates          PodTemplate
  poddisruptionbudgets  PodDisruptionBudget

The programs asked run in the background, in the shell’s folder with its variables, for at most shell-completion-timeout (2000) milliseconds; typing on stops one that’s no longer needed, and an answer is kept a few seconds, so typing more of a word narrows it without asking again. Your program runs only for --help: a Cobra program is asked with its __complete command, the way every shell asks it, and whether a program is Cobra’s is found by looking inside it. :set shell-help-completion never stops --help from running at all, and shell-help-never lists programs it never runs for (reboot, shutdown and the like). shell-completion-sources (cobra fish carapace man help) says which sources to ask, in order.

Agents

Agents run commands in the project’s first shell (opened if there’s none, without taking your screen) with the shell tool group:

Tool
shell_runRun a command line and get back its id, exit code, output and errors (cut to whole lines at their start and end when long), how long it took, where cd went and what it set, the files and lines it names, its JSON, and a table’s columns, first 50 rows and how many there are
shell_readA block by id, or the latest one, yours too
shell_waitWait for a block shell_run left running past its timeout_s
shell_killStop a block an agent ran

An agent’s commands run through a pipe with their input closed, from the project’s root unless they ask for a folder, in sh without its startup files. Their blocks are marked ◆, and their cd and export show in their block without changing your shell. What a command prints goes back to the model marked as someone else’s text, to use as information and not to follow; greed ctl gets it as it is. shell-agent-max-output (30000 bytes) is how much of the output and of the errors an agent gets; where it’s cut says how many lines are left out and that $ID | where, select or get narrows it.

Each block has one id, unique across shells, and in an agent’s command $ID is that block, whichever shell it ran in, so shell_run with $12 | where STATUS != Running works on what block 12 printed. A tool called without an argument it needs, or with one it doesn’t take, says which it takes and runs nothing; greed ctl shell_run --help lists them.

Limits

  • A program in a pseudo terminal that prints faster than the shell reads (seq 1 10000000, a huge cat) can lose lines in between, and its block says how many. A flood of output belongs in a pipe: $-lines, > buf: and agents’ commands run through one, which keeps every line.
  • Blocks don’t survive a restart yet.
  • Only the client a block ran from sizes its terminal.