Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
- [How it works](how-it-works.md): the PTY emulation, input routing, and activity grouping
- [Sessions](sessions.md): the task recipe format and where it lives
- [Agent session resume](agent-resume.md): how `fleetcom` captures and resumes supported `claude`, `codex`, and `grok` sessions
- [Storage paths](#storage-paths): the socket, the lock, and the session paths
- [Storage paths](#storage-paths): runtime and session paths
- [First-run walkthrough](#first-run-walkthrough): a first run, start to finish
- [Security](#security): the trust boundary, on-disk state, and what is not protected
- [Operational constraints](#operational-constraints): process and protocol boundaries
Expand All @@ -25,11 +25,11 @@ From a repository clone:

## Storage paths

Runtime state contains the daemon socket and lock. Configuration contains durable session recipes. The paths resolve independently.
Runtime state contains the daemon socket, lock, and log. Configuration contains durable session recipes. The paths resolve independently.

### Runtime directory (socket + lock)
### Runtime directory (socket + lock + log)

The runtime directory holds `default.sock`, the mode-`0600` client↔daemon socket, and `daemon.lock`, the single-instance `flock`. The daemon records its PID in the lock file; `--kill` uses that PID rather than waiting for the socket. `fleetcom` creates the directory with mode `0700`. An existing path must be a real directory owned by the current user, so symlinks and directories owned by another user are rejected.
The runtime directory holds `default.sock`, the client↔daemon socket; `daemon.lock`, the single-instance `flock`; and `daemon.log`, the stderr of an autostarted daemon. The daemon records its PID in the lock file; `--kill` uses that PID rather than waiting for the socket. [Security](#security) documents the permissions and the ownership checks this directory must satisfy.

Resolved in this order:

Expand All @@ -51,7 +51,7 @@ Holds saved sessions under a `sessions/` subdirectory: one sanitized-name `.json
| 2 | Linux | `${XDG_CONFIG_HOME:-~/.config}/fleetcom/sessions` |
| 2 | macOS | `~/Library/Application Support/fleetcom/sessions` |

The platform default is [`dirs::config_dir()`](https://docs.rs/dirs/latest/dirs/fn.config_dir.html) joined with `fleetcom`. The first save creates missing session directories with mode `0700`; recipe files use mode `0600`.
The platform default is [`dirs::config_dir()`](https://docs.rs/dirs/latest/dirs/fn.config_dir.html) joined with `fleetcom`. The first save creates any missing session directories.

## First-run walkthrough

Expand All @@ -62,7 +62,7 @@ Run `fleetcom`. The first invocation starts the daemon and opens an empty dashbo
```text
fleetcom 0 running · 0 idle · 0 done by state · dir · custom

❯ n run · @ dir · s sort · w save · o load
❯ n run · @ dir · / find · s sort · w save · o load
↑↓ select · enter attach · space peek · m tag · g group · R rename · r rerun · X kill · q detach · Q quit
```

Expand All @@ -75,7 +75,7 @@ Press `n`, enter a command, and press `Enter`. The command runs in its own PTY a
✻ cargo watch -x test test result: ok. 42 passed 9s
✻ npm run dev VITE v5.0 ready in 312 ms 4s

❯ n run · @ dir · s sort · w save · o load
❯ n run · @ dir · / find · s sort · w save · o load
↑↓ select · enter attach · space peek · m tag · g group · R rename · r rerun · X kill · q detach · Q quit
```

Expand Down Expand Up @@ -107,7 +107,7 @@ Each row is `glyph · tag · command · latest output · age`. The age counts fr
✻ npm run dev VITE v5.0 ready in 312 ms 1m
```

`s` cycles through state, directory, and custom grouping. The header renders the active mode in bold. In custom mode, `g` assigns the selected task to a named group. Named sections sort alphabetically; Unassigned appears last when at least one task has no group:
`s` cycles through state, directory, and custom grouping. The header renders the active mode in bold. In custom mode, `g` assigns the selected task to a named group. Named sections sort without regard to case, so `API` and `api` are adjacent. They remain separate because group identity is case-sensitive. Unassigned appears last when at least one task has no group:

```text
fleetcom 2 running · 0 idle · 0 done by state · dir · custom
Expand Down Expand Up @@ -145,7 +145,7 @@ The attached status bar shows both: `[attached] api tests · cargo watch -x test

| Path | Mode | Contents |
| -- | -- | -- |
| [runtime directory](#runtime-directory-socket--lock) | `0700` | the socket, lock, daemon log, and any capture roots resolved beneath it |
| [runtime directory](#runtime-directory-socket--lock--log) | `0700` | the socket, lock, daemon log, and any capture roots resolved beneath it |
| `<runtime>/default.sock` | `0600` | the client↔daemon socket |
| `<runtime>/daemon.lock` | `0666 & ~umask` when new; otherwise unchanged | the owning daemon's PID, trustworthy only while its `flock` is held |
| `<runtime>/daemon.log` | `0666 & ~umask` when new; otherwise unchanged | stderr from the autostarted daemon |
Expand Down Expand Up @@ -187,7 +187,7 @@ Because the daemon holds each PTY master, daemon termination closes the terminal

### Environment and directory

Each launch uses the launching client's environment and working directory, sent once per connection during the hello handshake. Connect from a venv terminal and your spawns, reruns, and session loads all see that venv, whichever client originally autostarted the daemon. Environment is never written to disk; session files store only directories, commands, group assignments, and display names.
Each client sends its environment and working directory once during the connection handshake. Spawns, reruns, and session loads initiated by that client use the same launch context. [Security](#security) covers what persists.

### Scrollback depth is fixed per supervisor

Expand Down
23 changes: 18 additions & 5 deletions docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
| `Space` | Peek at the selected task |
| `n` | New command in the invocation directory |
| `@` | New command in a directory you pick |
| `/` | Jump the selection to a task by name, command, or group (opens the [find palette](#the--find-palette)) |
| `s` | Cycle grouping: by state / by directory / by custom group |
| `m` | Tag the selected task "in use" (toggles) |
| `g` | Assign the selected task to a group (opens the group picker) |
Expand Down Expand Up @@ -118,12 +119,14 @@ A `TERM`-ignoring member can also survive when its leader exits during shutdown.
`s` cycles three grouping modes: state, dir, custom. The header shows the strip `by state · dir · custom` with the active mode bold and the rest dim.

- By state: In use / Running / Idle / Completed. A running task files under Idle after 10 s without output; Completed stays one section (`✓`/`✗` show exit status).
- By dir: one section per working directory; the invocation directory first, the rest alphabetical.
- By custom group: one section per group name, sorted by name, with Unassigned last. Fresh spawns remain unassigned unless they inherit a group, and the Unassigned section exists only while it has a member.
- By dir: one section per working directory; the invocation directory first, then the remaining labels sorted without regard to case.
- By custom group: one section per group name, sorted without regard to case, with Unassigned last. Fresh spawns remain unassigned unless they inherit a group, and the Unassigned section exists only while it has a member.

Dashboard section labels and within-section directory tiebreaks use the same case-insensitive order. Names that differ only by case sort next to each other in a deterministic order. Group identity remains case-sensitive, so `API` and `api` stay separate sections.

Groups belong to task state: an assignment survives client detach and rerun (`r`), and switching grouping modes does not modify it. `g` reassigns the selected task through the [group picker](#the-g-group-picker).

`m` toggles the "in use" tag and marks the task with `◆`. In state mode, tagged tasks form the In use section at the top. In custom mode, a tag moves the task to the top of its existing group rather than creating a global section. Within each group, the order is tagged, running, idle, completed; each bucket then sorts by directory and spawn order.
`m` toggles the "in use" tag and marks the task with `◆`. In state mode, tagged tasks form the In use section at the top. In custom mode, a tag moves the task to the top of its existing group rather than creating a global section. Within a dir or custom section, tasks sort as tagged, live, then completed; each class then sorts by directory and spawn order. Idle state does not affect row order in these modes, so a quiet task keeps its position and shows `∙`. State mode instead moves quiet tasks from Running to Idle.

In custom mode only, a new command inherits the selected task's group, through both `n` and the `@` picker. The spawn prompt shows the destination as `❯ dir ▸ group ▸ command`, each segment present only when it applies: the dir segment for a non-default directory, the group segment when a group will be inherited. State- and dir-mode spawns start unassigned.

Expand All @@ -145,9 +148,19 @@ The daemon removes control characters, trims surrounding whitespace, and limits

Typing filters the rows; `Backspace` deletes one character and the matches re-filter; `↑`/`↓` move the highlight; `Esc` cancels. Completion updates on each input, permitting navigation and launch without leaving the dashboard. `←`/`→` move the caret within the typed path (`→` descends only when the caret is at the end), and `Ctrl-A`/`Ctrl-E` (or `Home`/`End`) jump to either end; the same caret keys work in every `fleetcom` text field.

## The `/` find palette

`/` opens a bottom panel listing tasks that match the query. Each row reads `<glyph> <label> · <section>`: the status glyph, display name (or command when unnamed), and current section. Empty input lists the whole fleet. Results follow dashboard order. With no tasks, `/` does nothing.

Matching checks case-insensitive substrings in the name, command, and group. For example, `eep` finds `sleep 5`. A display name adds a searchable field without replacing the command, so a task named `api tests` can still match `cargo`.

The working directory is not a match field.

`Enter` moves the dashboard selection to the highlighted task and closes the panel; it does not attach. Press `Enter` again from the dashboard to attach, or `Space` to peek. With no matches, `Enter` leaves the panel open. `↑`/`↓` move the highlight. `Esc` closes the panel without changing the selection.

## The `g` group picker

`g` on a selected task opens a bottom panel with the same structure as the `@` picker: a typed-name field plus the matching rows. Row 0 is always Unassigned, so the list is never empty; the fleet's existing group names follow, sorted, filtered by case-insensitive prefix as you type. The task's current group is marked `(current)`.
`g` on a selected task opens a bottom panel with a typed-name field and matching rows. Row 0 is always Unassigned, so the list is never empty. Existing group names follow in case-insensitive order and are filtered by case-insensitive prefix. The task's current group is marked `(current)`.

`Enter` acts on the highlighted row, and the hint line names the action:

Expand All @@ -161,7 +174,7 @@ The daemon normalizes every group name received from the picker or a [session](s

## The `o` session picker

`o` opens a bottom panel listing the saved [sessions](sessions.md): `↑`/`↓` move the highlight, `Enter` loads, `Esc` cancels. While [recovery snapshots](sessions.md#recovery) exist, the hint adds `tab recovery (N)` and `Tab` (or `Shift-Tab`) flips the panel to them; `Tab` again returns to the saved list. Each list keeps its own highlight. With no snapshots, `Tab` does nothing and the hint omits it.
`o` opens a bottom panel listing the saved [sessions](sessions.md), sorted by name ignoring case: `↑`/`↓` move the highlight, `Enter` loads, `Esc` cancels. While [recovery snapshots](sessions.md#recovery) exist, the hint adds `tab recovery (N)` and `Tab` (or `Shift-Tab`) flips the panel to them; `Tab` again returns to the saved list. Each list keeps its own highlight. With no snapshots, `Tab` does nothing and the hint omits it.

A recovery row reads `<age> ago · <tasks> task(s) · <label>`: the file's age, its command count, and its stored label (normally `autosaved <timestamp>`). `Enter` loads the highlighted snapshot; the status line confirms the load and suggests saving it. Press `w` to save the recovered fleet as a named session.

Expand Down
4 changes: 2 additions & 2 deletions docs/how-it-works.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ Every task runs in its own pseudo-terminal, emulated with `alacritty_terminal`.

## Input fidelity

Attached input follows the terminal modes reported by the child. Modified Enter becomes `ESC CR` when the terminal reports the modifier. Paste receives bracketed-paste markers only when the child enables them. Mouse events go to children that request a mouse protocol. Full-screen children receive alternate-scroll input only while DECSET 1007 is enabled; otherwise `fleetcom` suppresses wheel events. Each task retains 2,000 lines of scrollback. For inline children, wheel-up or `Shift+PageUp` enters history; paging keys navigate it, while `Esc` or ordinary input returns to live output. [`commands.md`](commands.md) documents the exact routing rules.
Attached input follows the terminal modes reported by the child. Modified Enter becomes `ESC CR` when the terminal reports the modifier. Paste receives bracketed-paste markers only when the child enables them. Mouse events go to children that request a mouse protocol. Full-screen children receive alternate-scroll input only while DECSET 1007 is enabled; otherwise `fleetcom` suppresses wheel events. Each task retains 2,000 lines of scrollback by default; the supervisor can set another depth at startup. For inline children, wheel-up or `Shift+PageUp` enters history; paging keys navigate it, while `Esc` or ordinary input returns to live output. [`commands.md`](commands.md) documents the exact routing rules.

## Grouping follows one activity window

The dashboard groups tasks by state (In use / Running / Idle / Completed), working directory, or names assigned with `g`. One 10-second window drives both idle signals: after 10 s without output, the row glyph changes from `✻` to `∙` and the task moves to Idle in the same refresh. A tool such as `top`, which prints every 1–2 s, never crosses the window, so it stays `✻` under Running. The preview text holds separately: a status from a weaker source must persist for 600 ms before it replaces a stronger one, which absorbs repaint flicker without affecting grouping.
The dashboard groups tasks by state (In use / Running / Idle / Completed), working directory, or names assigned with `g`. One 10-second window drives both idle signals: after 10 s without output, the row glyph changes from `✻` to `∙` and, under state grouping, the task moves to the Idle section in the same refresh. Idle state does not affect row order within directory or custom sections. A tool such as `top`, which prints every 1–2 s, never crosses the window, so it stays `✻` under Running. Preview text is independent: a status from a weaker source must persist for 600 ms before it replaces a stronger one, which absorbs repaint flicker without affecting grouping.
4 changes: 2 additions & 2 deletions docs/sessions.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ The `version` field must be an integer from 1 through the newest format supporte

The shape, not the version, discriminates the schema. An object-valued `dirs` marks the wrapped form shown above. The loader also accepts a flat map whose top-level keys are directories and whose values are entry arrays. In that form, an array-valued key named `dirs` remains a directory entry, but a top-level `version` member is always the format version, never a directory. Flat-map files list by filename stem because they have no stored name. Saving one writes the wrapped form and permits overwriting it without a stored-name collision check.

Saves are atomic: `fleetcom` writes and syncs a private temporary file in the session directory, then renames it over the recipe. Recipes persist full command lines, which can embed secrets. New session directories use mode 0700, saves remove group and other permissions from existing session directories, and recipe files use mode 0600.
Saves are atomic: `fleetcom` writes and syncs a private temporary file in the session directory, then renames it over the recipe. Recipes persist full command lines, which can embed secrets. [Security](README.md#security) documents the directory and file permissions.

The file is plain JSON and practical to edit by hand. Editing the `name` field changes which session the file claims to be: collision checks compare it, so a save under the old name will be refused. On load, the daemon removes control characters, trims surrounding whitespace, and limits group and display names to 64 characters. `Unassigned` maps to no group but remains a legal display name. Invalid JSON fails the entire load. Within valid JSON, `fleetcom` drops any member that matches neither entry form, including a non-string scalar, an object without a string `cmd`, or an object with a non-string `group` or `name`.

Expand Down Expand Up @@ -79,7 +79,7 @@ A snapshot uses the session format above, with an `autosaved <timestamp>` UTC la

In the dashboard, `o` opens the [session picker](commands.md#the-o-session-picker) on the saved list; while snapshots exist, `Tab` flips it to the recovery list.

Recovery files carry the same caveat as saved recipes: they persist full command lines, which can embed secrets. New recovery directories use mode 0700, and snapshot files use mode 0600.
Recovery files carry the same caveat as saved recipes: they persist full command lines, which can embed secrets.

## Saving and loading

Expand Down
Loading