> _**CLI skill · `app` namespace** · ~2,905 tokens_

# `app` — resolve apps to shell commands

## Purpose

HTTP resolver across package sources (trusted-list, system-path, nixpkgs, pkgx, AppImage, OCI, manifests). Returns ranked candidates or `shell_command`. Command-only by default; delegates to `hoody-terminal` only when enabled.

## When to use

- Resolve `firefox`/`react`/`owner/repo` to a command.
- Cross-provider candidates with stable `set_id`.
- Preview via `dry_run` / `print_curl` / `POST /api/v1/run/preflight`.
- Batch via `POST /api/v1/run/batch`; persist profiles/recipes.

## When NOT to use

Not for: command known → `terminal`, long-lived process → `daemon`, one-shot remote exec → `exec`, arbitrary HTTP → `curl`.

## Prerequisites

- Kit slug `run`.
- Delegation: `HOODY_RUN_ENABLE_TERMINAL_EXECUTE=true` + `HOODY_TERMINAL_URL`.

## Capability URL

→ See `SKILL-CLI.md § Proxy URLs`.

**Reaching a service you host on a container port** (any port, any namespace):

- `https://{projectId}-{containerId}-http-<port>.{node}.containers.hoody.icu` — proxy speaks HTTP to `localhost:<port>`.
- `https://{projectId}-{containerId}-https-<port>.{node}.containers.hoody.icu` — proxy speaks HTTPS to `localhost:<port>` (target needs TLS).

Edge is always `https://`. No alias, firewall edit, or proxy registration needed; capability-token gates still apply.

## Common workflows

### 1. Search then pick

1. `GET /api/v1/run/search` → `{ set_id, candidates[] }`.
2. `POST /api/v1/run/run` → `shell_command`.

### 2. Preflight then delegate

1. `POST /api/v1/run/preflight` → `recommended_mode`, `terminal_request_preview`, `missing_requirements`, `effective_policy`.
2. `POST /api/v1/run/run`.

### 3. Cursor-paged search

`POST /api/v1/run/search/paged` → `{ set_id, total_count, items, next_cursor }` (note `items`, not `candidates`). The cursor-paged endpoint returns one page per call; drain manually by carrying `next_cursor` forward until it's null/absent.
### 4. Batch

`POST /api/v1/run/batch`. `mode:"run"` command-only.

### 5. Recipes

`POST /api/v1/run/recipes`; invoke via `POST /api/v1/run/recipes/{name}/run` — generated SDK takes `name` positional + a body, NOT a single options object.

## Quirks & gotchas

- Kit slug/URL/HTTP prefix all `run`.
- `limit` default 25, clamped 1..=100.
- 30s query cache; `HOODY_RUN_QUERY_CACHE_TTL_MS`.
- `set_id` expires 300s.
- Selector requires `app`. Aliases `q`/`name` are accepted ONLY by the urlencoded query-string parser (GET / form-style); the JSON `Selector` model has only `app`, so JSON POST / SDK calls must use `app:`.
- `dry_run=true` always command-only.
- No `HOODY_RUN_ENABLE_TERMINAL_EXECUTE=true` → silent fall-back to dry-run.
- `POST /api/v1/run/batch` only knows `mode: "search" | "run"` (no `"preflight"`); `"run"` is command-only.
- HTML redirect only on scheduled delegated runs. Requires `redirect_to` OR `HOODY_DISPLAY_URL_TEMPLATE` to compute the redirect host.
- `preflight.recommended_mode` enum is `"search-only" | "dry-run" | "delegated-execute" | "printed-curl"` — NOT `delegated`/`redirect`.
- App jobs use `status: "queued" | "running" | "done" | "error"` — NOT `completed`/`failed`.
- `POST /api/v1/run/recipes/{name}/run` / `POST /api/v1/run/recipes/{name}/search` reject disallowed overrides with `400 "recipe override not allowed: <field>"` — they are NOT silently dropped.
- `selected.run_plan` carries `command`/`env`/`cwd`; `argv` is on `selected.execution_plan`.
- `/go/...` routes are annotated `x-visibility.sdk: false` in kit source (alias routes for bookmarkable launch URLs), but the annotation is not honored by the current generator — the SDK still exposes `GET /api/v1/run/go/{rest}` / `GET /api/v1/run/t/{terminal_id}/go/{rest}`. Prefer `GET /api/v1/run/run`/`POST /api/v1/run/run`.
- Zero CLI commands.

## Common errors

- `400 INVALID_PICK` — bad `pick_index`/`candidate_id`.
- `409 cursor set expired` — re-search.
- `502 SOURCE_RESOLUTION_FAILED`
- `503 LOCAL_TOOLS_UNAVAILABLE` — `nix`/`pkgx` missing.
- `500 INVALID_TERMINAL_URL`

## Related namespaces

- `terminal` — delegation backend. `exec` — known command. `daemon` — supervised. `display` — GUI X11.

## Examples

Every step in every example was live-tested against a real `run-1` kit. Each step has a copy-pasteable code block in the mode you're reading (curl for HTTP, TypeScript for SDK; CLI is unsupported — the `app` namespace exposes zero `hoody` CLI commands, so CLI blocks are intentionally absent). Set `P`, `C`, `N` (project id, container id, server name) from `hoody containers get` first.

### 1. Resolve `firefox` to a shell command — search, then pick the top hit

**Goal:** turn the user's typed `firefox` into a runnable shell command without firing it. The default `pick` mode is `ask` (returns candidates, no selection); use `first` to auto-pick the highest-ranked one.

**Step 1 — search.** Returns a `set_id` (binds your follow-up `pick` against this exact candidate list — `set_id` expires after ~300s) and `candidates[]` ranked by score.

**Step 2 — resolve to a command.** `pick: 'first'` + `dry_run: true` returns `shell_command` without delegating to `hoody-terminal`.

### 2. Inspect before firing — `dry_run: true` first, then `dry_run: false`

**Goal:** preview the exact command, only then actually launch. Lightweight CLI app (`echo`) used so we don't leak GUI state.

**Step 1 — dry-run preview.** Live-verified — response carries `shell_command` plus the full selected entry: `run_plan.{command,env,cwd}` (the resolved shell-form) and `execution_plan.{argv,env,cwd}` (the argv-form for delegated launchers).

**Step 2 — actually launch** (drop `dry_run` — flips delegation to `hoody-terminal` if `HOODY_RUN_ENABLE_TERMINAL_EXECUTE=true`). ⚠ When delegation is disabled, the kit silently falls back to dry-run regardless of `dry_run: false` (live-verified — `status` stayed `"dry-run"` and `terminal` field was `null`). The reliable way to detect a real launch is the `status` field plus a non-null `terminal`: scheduled delegated runs return `status: "scheduled"` with the `terminal` request preview populated; everything else (dry-run / printed-curl) leaves `terminal: null`.

### 3. Pick a non-default candidate by index when multiple match

**Goal:** `git` resolves to `system-path:/usr/bin/git` (score 200), but you specifically want the `pkgx` candidate at index 2. Bind the pick to a `set_id` to avoid the candidate list shifting under you.

**Step 1 — list candidates with `set_id`.**

**Step 2 — pick by index against the captured `set_id`.** Out-of-range raises `400 pick_index out of range: <N>` (live-verified — `pick_index: 999` returned `{"error":"pick_index out of range: 999","code":400}`).

**Pick by id alternative** — when you know the exact candidate, use `pick: 'id'` + `candidate_id`:

### 4. Filter by os / arch / kind / source / tags

**Goal:** narrow candidates to Linux x86_64 CLI tools sourced only from the system PATH (skip `nix`/`pkgx`/`appimage`). Useful when you don't want long resolver tails.

`source` is repeatable on `GET` (`source=system&source=registry`); it's an array on the JSON body. Empty / absent → no filter.

**Tags** are free-form ranking hints (e.g. `tags: ['portable']` boosts AppImage / pkgx candidates). Combine with `kind: 'gui'` for X11 apps, or `os: 'windows'` to filter the catalog to Wine-runnable variants.

### 5. Preflight before delegated run — check requirements + policy

**Goal:** before firing a GUI app via `hoody-terminal`, learn whether the kit thinks it'll succeed. `POST /api/v1/run/preflight` returns `recommended_mode` (`"search-only"` / `"dry-run"` / `"delegated-execute"` / `"printed-curl"`), `missing_requirements`, `terminal_request_preview`, and the `effective_policy` (verify, integrity, allow-delegated, deny-lists).

When `recommended_mode === 'dry-run'` and `terminal_request_preview` is `null`, the kit lacks `HOODY_RUN_ENABLE_TERMINAL_EXECUTE=true` + `HOODY_TERMINAL_URL` — fix container env (or accept dry-run) before calling `POST /api/v1/run/run`.

### 6. Pagination — walk a long candidate list with `POST /api/v1/run/search/paged`

**Goal:** the `git` query returns 21 candidates across providers. Fetch them in pages of 3 without re-running expensive nix/pkgx queries.

`POST /api/v1/run/search/paged` returns `{ set_id, total_count, items, next_cursor }`. (Note: response field is `items`, not `candidates`.) Pass `next_cursor` back to get the next page; bound to the original `set_id` so the candidate set is stable.

**Step 1 — first page.**

**Step 2 — fetch all pages.** ⚠ The `search/paged` endpoint returns one page per call — there is no built-in drain-all behavior, so walk it manually by carrying `next_cursor` between calls until it's null/absent.
⚠ `409 cursor set expired` after ~300s — re-run the initial search with `selector` (no cursor) to get a fresh `set_id`.

### 7. Async search via job queue — for slow nix/pkgx queries

**Goal:** searching `firefox` across nixpkgs can take 15+s synchronously. Submit as a job, do other work, fetch result later.

⚠ `POST /api/v1/run/search/jobs` body is a **flat Selector** (NOT `{selector: ...}` like `search/paged`) — live-verified, the wrapped form returns `Failed to deserialize ... missing field 'app'`.

**Step 1 — submit.**

**Step 2 — poll.** Status transitions `queued → running → done` (or `error`). Search-resolve jobs have a 10-minute TTL refreshed on every status/result read, so polling keeps them alive — but plan to read the result the moment status flips to `done`/`error` rather than relying on the TTL.

### 8. Batch — resolve N apps in a single round-trip

**Goal:** the agent decided on three apps at once (`ls`, `echo`, `git`); resolve all to commands without three separate HTTP hits.

`POST /api/v1/run/batch` accepts items with `mode: 'search' | 'run'` (NOT `'preflight'` — live-verified `Failed to deserialize ... unknown variant 'preflight'`). Each item has its own `request_id` for correlation; results come back in the same order with one of `result: 'search'` (full search response) or `result: 'run'` (with `selected` + `shell_command`).

⚠ `mode: 'run'` items are **always command-only**, even if the kit has terminal delegation enabled. To actually launch via `hoody-terminal`, fire a separate per-item `POST /api/v1/run/run` after.

### 9. Save a recipe — reusable selector template with override allow-list

**Goal:** the team often resolves "give me a JS runtime" with a fixed set of filters. Save it once as a recipe; teammates run it by name and only override approved fields.

**Step 1 — create.** `allowed_overrides` is a whitelist; any `overrides.*` outside it is rejected with `400 "recipe override not allowed: <field>"` on `POST /api/v1/run/recipes/{name}/run` — update the recipe to widen the allow-list.

**Step 2 — list / get / update / delete.**

### 10. Invoke a recipe with overrides — `POST /api/v1/run/recipes/{name}/run`

**Goal:** teammate uses the `team-js-runtime` recipe but wants `bun` instead of the default `node`. They override only the allow-listed `app` field; other selector fields stay locked.

**Step 1 — run with overrides.** Returns the same envelope as `POST /api/v1/run/run` (`{ status, shell_command, selected, ... }`).

**Step 2 — search through the recipe** (same selector, but stop at candidate listing instead of resolving) via `POST /api/v1/run/recipes/{name}/search`:

⚠ Overrides outside `allowed_overrides` are **rejected** with `400 "recipe override not allowed: <field>"` — they are NOT silently dropped. Use `PATCH /api/v1/run/recipes/{name}` to widen the allow-list.

## Reference

_(no CLI commands registered for this namespace)_
