Skip to content
Merged
20 changes: 10 additions & 10 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ jobs:
name: Package versions
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 22
- name: Verify application package versions agree
Expand Down Expand Up @@ -52,29 +52,29 @@ jobs:
matrix:
os: [ubuntu-latest, macos-latest]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- run: cargo build --locked
- run: cargo test --locked
- run: cargo clippy --locked --all-targets --all-features -- -D warnings
- run: cargo build --locked --workspace
- run: cargo test --locked --workspace
- run: cargo clippy --locked --workspace --all-targets --all-features -- -D warnings

fmt:
name: Rustfmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- run: cargo fmt --check
- run: cargo fmt --all --check

viewer-ui:
name: Viewer frontend
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 22
cache: npm
Expand Down
41 changes: 32 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -316,10 +316,12 @@ The two halves have two owners. The session writes which repositories are open a
nightcrow itself knows nothing about the CLIs you run in its panes — an agent
and a person get the same PTY. Behaviour that *does* need to know a particular
tool lives in a plugin: a separate executable that nightcrow launches and talks
to over a pipe. Plugins are off unless you turn one on, and one can only ever
see a pane you handed it by name.
to over a pipe. Plugins are off unless you turn one on, and one only ever sees a
pane you handed it by name — or, if you also set `watch_on_signal`, a pane that
something running inside it spoke to the plugin from. A plugin is never given a
list of your panes either way.

The bundled plugin is `nightcrow-recovery`. When an opted-in pane's CLI hits its
The bundled plugin is `nightcrow-recovery`. When a watched pane's CLI hits its
usage limit, it waits for the reset time the provider reported and then re-opens
that exact session. It only waits — it does not bypass, raise, or work around
any provider limit, and it sends nothing while a limit is in effect. Claude
Expand Down Expand Up @@ -354,19 +356,34 @@ allowed_resume_flags = ["--resume", "resume", "--session"]
[[startup_command]]
name = "Claude"
command = "claude"
plugin = "recovery" # without this line, no plugin ever sees this pane
plugin = "recovery" # without this line, no plugin sees this pane unless
# watch_on_signal is set (see below)
```

That covers the panes you configured. For the pane you did not — you opened a
shell with `<prefix> t` and typed `claude` into it yourself — add
`watch_on_signal = true` to the `[[plugin]]` block. nightcrow puts a random token
in each pane's environment and nowhere else, so the CLI's own hook can quote it
back and the plugin can ask for "the pane this token names"; a plain shell never
speaks to a plugin, so your shells stay untouched. It is off by default. Such a
pane can be waited for and typed into but never relaunched — nightcrow launched
no command in it, so there is nothing to put back.

For Claude Code, let the plugin install its hook and statusline entries so it
can read the exact session id and reset time instead of guessing from what is
printed on screen. It merges into your existing `~/.claude/settings.json` and
backs it up first:
printed on screen. With a reset time it waits exactly once; without one it falls
back to retrying on a backoff, which can give up. It merges into your existing
`~/.claude/settings.json` and backs it up first:

```bash
nightcrow-recovery install-hooks
nightcrow-recovery uninstall-hooks # removes only what it added
```

Claude Code's `statusLine` holds one command, so installing does replace yours —
but it is then run from the plugin's own statusline with the same input, and what
it prints is what you see. `uninstall-hooks` puts it back.

A pane that is waiting shows its state and deadline on its tab. Cancel it with
`<prefix>` then the recovery key (see [Leader commands](#leader-commands-press-prefix-then-the-key)),
or from the web viewer; typing into the pane yourself also cancels it.
Expand Down Expand Up @@ -609,19 +626,25 @@ live_watch = true # watch expanded dirs and refresh the tree live; set f
name = "Claude" # optional tab label; falls back to the command text
command = "claude" # required; must not be empty
plugin = "recovery" # optional; names the [[plugin]] allowed to act on this
# pane. Omitted — the default — means no plugin ever
# sees it.
# pane. Omitted — the default — means no plugin sees
# it unless that plugin sets watch_on_signal.

[[startup_command]]
command = "cargo test --watch"

# External plugin processes — see "Plugins" above. Up to 8 entries, names unique.
# Nothing runs unless an entry exists AND enabled = true AND a pane opted in.
# Nothing runs unless an entry exists AND enabled = true AND either a pane opted
# in or watch_on_signal is set.
[[plugin]]
name = "recovery" # the name panes opt in with
command = "nightcrow-recovery" # found on PATH or in ~/.nightcrow/plugins
args = [] # passed to the plugin verbatim
enabled = false # off by default
watch_on_signal = false # off by default; when true, a pane no
# [[startup_command]] named is also handed over
# once something inside it quotes that pane's
# token to this plugin. Such a pane is never
# relaunched, only typed into while it lives.
allowed_resume_flags = [] # flags the plugin may append to re-open a
# session; empty refuses every relaunch

Expand Down
23 changes: 22 additions & 1 deletion config.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ live_watch = true # watch expanded dirs and refresh the tree live; set f
# section fits any provider or tool.
#
# Nothing here runs unless you both add an entry AND set enabled = true, and a
# plugin can only act on panes whose [[startup_command]] named it.
# plugin can only act on panes whose [[startup_command]] named it — unless you
# also set watch_on_signal, described further down.
#
# The bundled "recovery" plugin watches an opted-in pane for its provider's
# usage-limit message, waits for the stated reset time to pass, and then resumes
Expand All @@ -126,11 +127,31 @@ live_watch = true # watch expanded dirs and refresh the tree live; set f
# Leave it empty (the default) and the plugin can still wait and type into a
# live pane, but never relaunch one.
#
# watch_on_signal covers the pane you did not configure: you opened a shell with
# <leader> t and started a coding CLI in it by hand. nightcrow gives every pane a
# random token and puts it in that pane's environment only, so the CLI's own hook
# — a child of the CLI, a grandchild of the pane — can quote it back to the
# plugin. The plugin then asks nightcrow for "the pane this token names", and
# nightcrow checks the token really is one of its panes before handing it over.
#
# What you are turning on, precisely: a pane becomes plugin-visible once
# something running inside it has spoken to that plugin. A plain shell never
# does, so your shells stay untouched — but anything you start in a pane that
# talks to this plugin is consenting on that pane's behalf. The plugin is never
# given a list of your panes and cannot ask for one it has not been told about,
# and a pane another plugin already watches is not taken away. Leave it off if
# you want the [[startup_command]] list to be the whole of what a plugin sees.
#
# A pane taken on this way can be waited for and typed into, but never
# relaunched: nightcrow never launched a command in it, so there is nothing to
# put back. allowed_resume_flags has no effect on such a pane.
#
# [[plugin]]
# name = "recovery" # required; the name panes opt in with
# command = "nightcrow-recovery" # required; found on PATH or in the plugin dir
# args = [] # passed to the plugin verbatim
# enabled = false # off by default; set true to actually run it
# watch_on_signal = false # off by default; see the paragraphs above
# allowed_resume_flags = ["--resume", "resume", "--session"]
# # empty by default, which refuses relaunches.
# # These three are what the bundled recovery
Expand Down
Loading