# AGENTS.md — nest

This file is the **single source of truth** for the `nest` project. It defines what the
tool does, how it is implemented, and the code style to follow. Any LLM or contributor
working on this repository MUST read this file in full before writing code, and MUST
keep it updated whenever behavior, architecture, or conventions change.

---

## 1. Project Overview

**nest** is the official CLI companion of [Parrot Security Linux](https://parrotsec.org).
Its purpose is to help users — and LLM agents — answer two questions:

1. **Which security tools does Parrot provide, and which of them are installed here?**
2. **How do I launch a tool?** — locally on this machine, or inside a container
   (podman/docker).

### Core principles

- **Discoverability**: a curated, versioned catalog of every tool the Parrot project
  ships, queryable from the command line.
- **Run anything, anywhere**: any cataloged tool can be launched even if it is not
  installed locally, by running it from a Parrot container image.
- **Machine-friendly**: nest is designed to be used by LLM agents and scripts. Every
  read command supports `--json`; the schemas in §4.8 are stable, documented contracts.
- **Minimal trust surface**: nest is a security-distribution tool. Dependencies are kept
  to the absolute bare minimum (see §5.3). The catalog is trusted like code: it can
  inject container runtime flags, so it is curated, validated, and fetched only over a
  pinned HTTPS URL (§2.3, §4.6).

### Operating modes

nest behaves differently depending on where it runs:

| Mode | How it is detected | Behavior |
|---|---|---|
| **Host mode** | default | Full functionality: list, search, info, run locally or via containers, cleanup, refresh. |
| **Container mode** | running inside a container (see §5.4) | nest comes pre-installed in every Parrot docker image. In this mode it **only** lists the tools available *inside that image*, `run` executes locally only (no container-in-container launching), `cleanup` is unavailable (no runtime access), and `refresh` works but only affects the container's ephemeral filesystem. |

---

## 2. Tool Catalog

The catalog is a YAML file describing every tool provided by the Parrot project.

### 2.1 Location and resolution

Catalog copies, in resolution order:

1. `--catalog <path>` flag or `NEST_CATALOG` environment variable (development and
   testing; replaces everything below).
2. User copy: `$XDG_DATA_HOME/nest/tools.yaml` (default `~/.local/share/nest/tools.yaml`),
   written by `nest refresh` (§4.6).
3. System copy: `/usr/share/nest/tools.yaml`, shipped by the `nest` package, read-only.

The effective catalog is the valid copy with the highest `version`; the user copy wins
ties. A malformed user copy is ignored with a warning on stderr (fall back to the
system copy). A malformed system copy — or no catalog at all — is a fatal startup
error (exit `1`) naming the file and the problem, never a panic.

The catalog source lives at the repository root (`tools.yaml`); packaging installs it
to the system path. Tests must load and validate the shipped file (§7).

### 2.2 Schema

```yaml
# tools.yaml
version: 1

tools:
  - name: nmap                      # required: unique tool identifier (lowercase)
    description: >                  # required: one-line description, used by `search`
      Network exploration tool and security/port scanner
    package: nmap                   # required: name of the .deb package that ships it
    executables:                    # optional: absolute paths of the binaries it provides
      - /usr/bin/nmap               #   (default: ["/usr/bin/<name>"])
    images:                         # optional: container images that ship this tool;
      - docker.io/parrotsec/nmap    #   images[0] is the default (§2.3); absent = no
      - docker.io/parrotsec/security#   container available for this tool
    container:                      # optional: extra options for container runs
      network: host                 #   passed to --network
      cap_add:                      #   each entry passed to --cap-add
        - NET_RAW
        - NET_ADMIN
      extra_args: []                #   escape hatch: appended verbatim, last
    tags:                           # optional: categories for search/filtering
      - recon
      - network

  - name: metasploit
    description: Penetration testing framework
    package: metasploit-framework
    executables:
      - /usr/bin/msfconsole
      - /usr/bin/msfvenom
    images:
      - docker.io/parrotsec/metasploit
      - docker.io/parrotsec/security
    tags:
      - exploitation
```

`nmap` is the reference entry: it is the first tool implemented end to end and the
model for future catalog additions.

### 2.3 Rules

- `name` is the primary key; it must be unique across the catalog. Duplicates are a
  validation error. By convention it equals the package name; flagship tools may
  use a friendlier name (e.g. `metasploit` for `metasploit-framework`).
- When `executables` is omitted it defaults to `["/usr/bin/<name>"]`; list it
  explicitly only when the package's binary differs (e.g. `netexec` → `nxc`).
- `executables[0]` is the command `nest run` executes in **every** mode. If *any* of
  the listed paths exists and is executable, the tool is considered installed.
- When `images` is omitted or empty, the tool has **no container available**:
  `nest run --container` fails (exit `3`) with an apt install hint, unless
  `--image` is passed explicitly. Otherwise `images[0]` is the **default image**;
  untagged image references imply `:latest`.
- `container` options apply only to container runs. They are appended **after**
  nest's own runtime flags (§4.4), so tool-specific settings win on conflict. Local
  runs ignore them.
- `version` is an integer compared by `nest refresh`; an unsupported value is a
  validation error (`unsupported catalog version`).
- Unknown fields are ignored (forward compatibility). Missing required fields, wrong
  types, and duplicate names are validation errors with a clear message.
- The catalog is data, but it is **trusted like code**: `container` options translate
  directly into runtime privileges. Ship only curated entries; fetch updates only per
  §4.6.

---

## 3. User Configuration

Optional layered YAML configuration. Later files override earlier ones:

1. `/etc/nest/nest.yaml` (system-wide)
2. `~/.config/nest/nest.yaml` (per-user; honor `$XDG_CONFIG_HOME` if set)

A malformed config file is a fatal error (exit `1`) naming the offending file.

```yaml
# ~/.config/nest/nest.yaml
runtime: auto                     # auto | podman | docker   (default: auto)
default_mode: local               # local | container (default: local)
persistence_dir: ~/nest/persistence  # host directory bind-mounted into containers
catalog_url: ""                   # override the pinned refresh URL (testing only)
```

### 3.1 Resolution order — all settings

First non-empty value wins, left to right:

| Setting | Flag | Environment | Config key | Default |
|---|---|---|---|---|
| Runtime | `--runtime` | — | `runtime` | `auto` |
| Run mode | `--local` / `--container` | — | `default_mode` | `local` |
| Persistence dir | — | `NEST_PERSISTENCE_DIR` | `persistence_dir` | `~/nest/persistence` |
| Catalog source | `--catalog` | `NEST_CATALOG` | — | per §2.1 |
| Refresh URL | — | `NEST_CATALOG_URL` | `catalog_url` | pinned HTTPS URL (§4.6) |

In container mode the run mode is forced to `local`.

---

## 4. Command Specification

Global conventions:

- Results go to **stdout**; diagnostics, warnings, and progress go to **stderr**.
- Read commands (`list`, `search`, `info`, `version`) and `refresh`/`cleanup` support
  `--json`; schemas are documented in §4.8.
- Exit codes: `0` success, `1` generic error, `2` usage error, `3` tool not found or
  not installed, `4` runtime error (container backend unavailable, …).
- Running `nest` with no command prints the help text (same content as `nest --help`)
  and exits `0`.
- `--help` / `-h` on every command and subcommand.
- `--runtime` is accepted by every command that needs a container runtime; an unknown
  value is a usage error (exit `2`).
- Empty result sets are not errors: exit `0`, headers-only table, or empty JSON array.

### 4.1 `nest list`

List cataloged tools.

```
nest list [--all | --installed | --available] [--json]
```

- `--installed` (default in container mode): only tools detected on this system.
- `--available`: only tools *not* installed locally **and** with a container
  available (candidates for a container run).
- `--all`: everything. Default in host mode.

Every output mode shows both status columns — `STATUS` (`installed` /
`not installed`) and `CONTAINER` (`yes` / `no`) — so the two availability
dimensions are immediately readable by users and machines alike.

Example (host mode, plain output):

```
NAME         STATUS         CONTAINER  PACKAGE              DESCRIPTION
nmap         installed      yes        nmap                 Network exploration tool and port scanner
metasploit   not installed  yes        metasploit-framework Penetration testing framework
wireshark    not installed  no         wireshark            Network protocol analyzer
```

### 4.2 `nest search <term>`

Case-insensitive substring match over `name`, `description`, `package`, and `tags`,
always over the **full catalog** in both modes. Output columns identical to
`nest list` (both status columns always shown). No matches: exit `0` with empty
output. Supports `--json`.

### 4.3 `nest info <tool>`

Show full details of one catalog entry: description, package, executables (marking
which exist), images, container options, tags, install status, and the suggested run
command. Supports `--json`. Exits `3` if the tool is not in the catalog.

Example (plain output, tool not installed):

```
Name:        nmap
Description: Network exploration tool and security/port scanner
Package:     nmap
Status:      not installed
Executables: /usr/bin/nmap (missing)
Images:      docker.io/parrotsec/security (default)
Container:   network=host, cap_add=NET_RAW,NET_ADMIN
Tags:        recon, network
Run:         nest run nmap --container -- <args>
```

When installed locally, the last line is `Run: nest run nmap -- <args>`. For a
tool with no container available, it is `Run: sudo apt install <package>`.

### 4.4 `nest run`

Launch a tool.

```
nest run <tool> [--local | --container] [--runtime podman|docker]
         [--image <ref>] [-- <args for the tool>...]
```

- Mode resolution per §3.1; container mode forces `local`.
- The command executed is always `executables[0]`; everything after `--` is passed
  verbatim to the tool.
- **local**: replace the nest process with the tool (`syscall.Exec`) so signals and
  exit codes propagate naturally. If not installed, fail with a hint (exit `3`):
  `tool 'X' is not installed; try: nest run X --container` — or, when the tool
  has no container available, `tool 'X' is not installed; try: sudo apt install <package>`.
- **container** invocation contract:
  1. Image: `--image` or `images[0]`. If neither exists (no `--image` and the
     catalog lists no images), fail with an apt install hint (exit `3`).
     Pull if missing (progress to stderr).
  2. Create the host persistence directory (`mkdir -p`) *before* invoking the
     runtime — never let the runtime create it, or it becomes root-owned.
  3. Runtime flags, in order — nest defaults first:
     `run --rm -i` (`-t` only when stdin is a terminal)
     `--label io.parrot.nest.managed=true --label io.parrot.nest.tool=<name>`
     `-v <persistence_dir>:/persistence --workdir /persistence`
     — then the tool's `container` options (`network`, `cap_add`, `extra_args`), so
     they win on conflict.
  4. Then: `<image> <executables[0]> <args...>`.
  5. Containers are never given a name (parallel runs stay safe); `--rm` is the
     default and is not overridable in v1.
  6. nest forwards signals to the runtime child process and exits with the tool's
     exit code.
- Interactive tools must work: stdin/stdout/stderr are attached as described above.
- In **container mode**: only `--local` is valid; `--container` fails with exit `4`
  and a clear message.

### 4.5 `nest cleanup`

Remove nest-managed container resources. It only ever touches resources carrying the
`io.parrot.nest.managed=true` label (plus, with `--images`, images referenced by the
catalog), so the defaults are safe on shared docker hosts.

```
nest cleanup [--running] [--images] [--dry-run] [--yes] [--runtime podman|docker] [--json]
```

- Default: remove **stopped** nest-managed containers.
- `--running`: also stop and remove **running** nest-managed containers. Requires
  confirmation: an interactive prompt on a TTY, or `--yes`. Without a TTY and without
  `--yes`, refuse with exit `2`.
- `--images`: also remove catalog-referenced images present locally (deduplicated —
  many tools share the same Parrot image, so each unique image is handled once). Runs
  **after** container removal, since images cannot be removed while containers
  reference them.
  **TODO: `--images` currently asks for no confirmation; add an interactive/`--yes`
  confirmation before image removal. This reminder lives here until the code exists,
  then moves into the code as a TODO comment.**
- `--dry-run`: print what would be removed and change nothing; never asks for
  confirmation. With `--json`, emits the §4.8 schema; the removal lists then name
  what *would* be removed.
- A failure to remove an individual resource is reported on stderr and does **not**
  stop the run; cleanup continues with the remaining resources.
- Volumes are never pruned (see §8). Local host only. In container mode it fails
  with exit `4`.
- Exit codes: `0` success, `4` runtime unavailable.

### 4.6 `nest refresh`

Update the tool catalog and update already-local container images. Refresh never
installs new images: an image that is not present locally is only ever pulled on
demand by `nest run` (§4.4).

```
nest refresh [--runtime podman|docker] [--json]
```

Catalog update:

1. Fetch the catalog from the pinned HTTPS URL (overridable per §3.1, for testing).
2. Validate the download with the **same validator** used at startup; an invalid
   download changes nothing.
3. Install only if the downloaded `version` is strictly newer than the effective
   catalog's (§2.1). Write atomically: temp file in the target directory, then
   rename. **Never** writes to `/usr/share/nest` (package-managed).
4. Report `catalog updated vX → vY` or `catalog already up to date`.

Image update:

- Candidates: the **unique** images referenced by the catalog that are already
  present locally. Many tools share the same Parrot image; deduplicate so each
  unique image is handled exactly once per run.
- For each candidate, run `<runtime> pull <image>` — the runtime itself does the
  work: it downloads newer layers or reports the image is already at the latest
  version. No digest comparison is implemented in nest.
- Status per image: `up-to-date` | `updated` | `not-local` (skipped, never pulled)
  | `failed` (pull error — reported on stderr, does not stop the run).
- Without a usable runtime, the catalog update still succeeds and the image section
  is skipped with a stderr note (JSON: `"images": []`).

### 4.7 `nest version`

Print nest version, catalog version and source path, detected runtime, and
container-mode status. Supports `--json`.

### 4.8 JSON schemas

These are stable contracts: fields may be added, but never removed or renamed.
Keys are always present — empty collections serialize as `[]`, never omitted.
`info.container` is `null` when the tool declares no container options.

`list` / `search` — an array of tool summaries:

```json
[
  {
    "name": "nmap",
    "description": "Network exploration tool and security/port scanner",
    "package": "nmap",
    "installed": true,
    "container_image": "docker.io/parrotsec/nmap",
    "tags": ["recon", "network"]
  }
]
```

(`container_image` is the default image for container runs, or `null` when the
tool has no container available.)

`info` — one tool object:

```json
{
  "name": "nmap",
  "description": "Network exploration tool and security/port scanner",
  "package": "nmap",
  "installed": false,
  "executables": [{"path": "/usr/bin/nmap", "present": false}],
  "images": ["docker.io/parrotsec/security"],
  "tags": ["recon", "network"],
  "container": {"network": "host", "cap_add": ["NET_RAW", "NET_ADMIN"], "extra_args": []},
  "suggested_run": "nest run nmap --container -- <args>"
}
```

`version`:

```json
{
  "nest": "1.0.0",
  "catalog_version": 3,
  "catalog_source": "/home/user/.local/share/nest/tools.yaml",
  "runtime": "podman",
  "container_mode": false
}
```

(`runtime` is `null` when no runtime is detected.)

`refresh`:

```json
{
  "catalog": {"updated": true, "version": 4},
  "images": [
    {"image": "docker.io/parrotsec/security", "status": "updated"}
  ]
}
```

`cleanup`:

```json
{
  "dry_run": false,
  "containers_removed": ["abc123def456"],
  "images_removed": ["docker.io/parrotsec/security"]
}
```

---

## 5. Architecture

### 5.1 Layout

Single Go module (`module nest`). All code lives under `src/`, with packages
flat beside `main.go` — no unnecessary nesting:

```
tools.yaml                 # catalog source; packaging installs it to /usr/share/nest/tools.yaml
src/
  main.go     # thin entrypoint: parse, dispatch, exit-code mapping
  cmd/        # one file per command (list.go, search.go, info.go, run.go,
              #   cleanup.go, refresh.go, version.go)
  catalog/    # catalog loading, copy resolution (§2.1), validation, querying
  config/     # user config loading & merging, env resolution (§3.1)
  detect/     # install detection, container-mode detection, runtime detection
  runtime/    # podman/docker invocation: run args, labels, mounts, exec
  output/     # plain & JSON rendering helpers (§4.8)
```

Keep packages small and focused. Packages never import each other in cycles;
dependencies point inward toward `catalog`/`config`/`detect`/`runtime`.
`refresh` reuses the `catalog` validator; `cleanup` reuses `runtime`'s label filters.

### 5.2 Language & toolchain

- Go 1.25+ (see `go.mod`).
- No code generation. No build tags. No cgo.

### 5.3 Dependencies — hard rule

- **Only external dependency allowed: `gopkg.in/yaml.v3`.** Nothing else may be added
  without updating this file and a written justification.
- Argument parsing: stdlib `flag` with manual subcommand dispatch — **no cobra**,
  no cli frameworks.
- Container execution: **shell out to the `podman` / `docker` binaries** via
  `os/exec`. Do NOT use the Docker/Podman Go SDKs or client libraries — they pull in
  enormous dependency trees, which is exactly the supply-chain risk this project
  rejects.
- Everything else: standard library only.

### 5.4 Detection logic

- **Installed tool**: any of its `executables` exists and is executable
  (use `os.Stat`, check mode bits; do not rely on `exec.LookPath` alone since catalog
  paths are absolute).
- **Container mode**: true if any of the following holds: `/.dockerenv` exists,
  `/run/.containerenv` exists, or the `container` environment variable is non-empty.
- **Runtime detection** (`runtime: auto`): prefer `podman` — daemonless, rootless by
  default, and the Parrot OS default — else `docker`. If neither is found, container
  runs and `cleanup` fail with exit `4` and an actionable message, and `refresh`
  skips its image section with a stderr note (§4.6).
- **macOS**: nest runs on macOS as a client of VM-backed runtimes (podman machine,
  Docker Desktop). Detection is the same binary probe, but the VM must already be
  running: if the runtime binary exists but is unreachable, exit `4` with the exact
  remedy (`podman machine start` / start Docker Desktop). nest never starts VMs
  itself. Note: host networking is bound to the VM, not the macOS host.

### 5.5 Error handling

- Library code returns wrapped errors (`fmt.Errorf("...: %w", err)`); only `main`
  decides exit codes and prints user-facing messages.
- Never panic outside of unrecoverable programmer error during startup.
- Every user-facing failure must say **what** failed and **what to try next**.

---

## 6. Code Style

- Idiomatic, boring Go. Readability beats cleverness, always.
- `gofmt`-clean; `go vet ./...` must pass.
- Exported identifiers get doc comments starting with the identifier's name.
- Small functions (< ~40 lines as a guideline); early returns over deep nesting.
- No global mutable state; pass dependencies explicitly (config, catalog, runtime)
  as values/interfaces through constructors.
- Interfaces are small and defined where they are used (e.g. `output` may define a
  minimal `Renderer`).
- Table-driven tests with stdlib `testing`. Use `t.TempDir()` and fixture YAML files
  for catalog/config tests. No test frameworks, no mocking libraries.
- Commit messages: imperative, concise, explain the *why*.

---

## 7. Build, Test, Release

```sh
go build ./...        # build
go test ./...         # tests
go vet ./...          # static checks

# release build (version injected at link time)
go build -ldflags "-X main.version=$(git describe --tags --always)" -o nest ./src
```

- `main.version` defaults to `dev` when not injected.
- Tests must load and validate the shipped `tools.yaml` (§2.1).
- Test `run`/`cleanup`/`refresh` against a **fake `podman`/`docker` shell script**
  placed in a `t.TempDir()`-based `PATH`; no SDKs, no mocking frameworks.
- Cross-compilation must stay trivial (`GOOS`/`GOARCH` only, no cgo) — do not break it.

### 7.1 Debian packaging

Debian packaging lives in `debian/` (debhelper compat 13, golang buildsystem,
**native package** — source format `3.0 (native)`, version without a Debian
revision, e.g. `0.1+parrot1` in the `echo` suite). The package ships the `nest`
binary, `tools.yaml` at `/usr/share/nest/tools.yaml` (§2.1), and the `nest(1)`
man page. Build:

```sh
dpkg-buildpackage -us -uc -b
```

- Build-Depends: `golang-any (>= 1.25~)` and `golang-gopkg-yaml.v3-dev` — Go
  dependencies come from Debian packages, never vendored (§5.3).
- `debian/rules` injects `dpkg-parsechangelog -S Version` into
  `-X main.version=`, so bumping the changelog is enough for a new release.
- Catalog updates are delivered by package updates (`apt upgrade nest`); no
  apt hook or separate update channel is implemented for the system copy.
- nest is licensed GPL-3.0-or-later (see `LICENSE`); `debian/copyright` is
  machine-readable (DEP-5).

---

## 8. Out of Scope / Open Questions

Explicitly **out of scope** for now (do not implement without updating this file):

- Installing tools (`nest install` via apt) — future consideration.
- Implicit installation of tool images: `refresh` only updates images already
  present locally; new images are pulled on demand by `nest run` (§4.4).
- TUI/interactive picker.
- Windows support. (macOS **is** supported as a runtime client — §5.4.)
- Volume pruning: nest creates no volumes (bind mounts only), and dangling volumes
  may belong to other workloads — `cleanup` never touches them.
- Named containers: runs are anonymous and `--rm` by design (§4.4).

Open design questions, to be decided as the project evolves:

- Catalog authenticity: signature/checksum verification for `refresh` (today the only
  guarantee is the pinned HTTPS URL).
- Support for Apple's `container` CLI on macOS.
- Remote runs (designed, not implemented in v1): a `--remote` mode with a
  `remote_host` config key and `CONTAINER_HOST`/`DOCKER_HOST` wiring. When added,
  both variables are implemented together, each wired to its own runtime
  (podman → `CONTAINER_HOST`, docker → `DOCKER_HOST`), with a warning when the
  mismatched variable is set. The persistence mount's behavior must also be defined,
  since the bind-mount source would live on the remote host.
- **Known issue — image entrypoints**: the official Parrot tool images set
  `ENTRYPOINT` (e.g. `ENTRYPOINT ["nmap"]`), so §4.4's `<image> <executables[0]>`
  contract would run `nmap /usr/bin/nmap ...`. Planned fix: nest passes
  `--entrypoint <executables[0]>` explicitly (trade-off: the metasploit/beef
  init wrappers are skipped; their tools still run).
- Selecting a non-default executable (e.g. `msfvenom` instead of `msfconsole`).
- Should `nest list` support filtering by `tags` (`--tag recon`)?
- Should user config be able to extend/override catalog entries (custom tools)?

---

*Last updated: optional `images` (imageless tools) and default `executables`,
dual STATUS/CONTAINER availability columns, full Parrot package set in the
catalog (tools.parrotsec.org feed), `--available` = not installed + container
available, apt hints for imageless tools.*
