> _**SDK skill · `app` namespace** · ~10,936 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` / `preflight`.
- Batch via `runBatch`; 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-SDK.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. `execution.searchCandidates({ app, os?, kind?, arch?, tags?, source? })` → `{ set_id, candidates[] }`.
2. `execution.runAppPost({ ...selector, set_id, pick:"index", pick_index:N, dry_run:true })` → `shell_command`.

### 2. Preflight then delegate

1. `execution.preflight(Selector)` → `recommended_mode`, `terminal_request_preview`, `missing_requirements`, `effective_policy`.
2. `execution.runAppPost({ ..., dry_run:false })`.

### 3. Cursor-paged search

`execution.searchCandidatesPaged` → `{ 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.
The SDK exposes `execution.searchCandidatesPagedAll` and `execution.searchCandidatesPagedIterator`, but they currently only return/yield the **first page** — the generated paginator can't thread the cursor back into a cursor-paged call, so it stops after one page. To collect all candidates reliably, carry `next_cursor` forward by hand until it's null/absent.
### 4. Batch

`execution.runBatch({ items: [{ request_id, mode, selector }] })`. `mode:"run"` command-only.

### 5. Recipes

`recipes.create({ name, selector_template, allowed_overrides })`; invoke via `recipes.run(name, data)` — 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.
- `runBatch` 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`.
- `recipes.run` / `recipes.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 `execution.runPathBased` / `execution.runTerminalAnchored`. Prefer `execution.runAppGet`/`execution.runAppPost`.
- 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 `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.

```typescript
const r = await client.app.execution.searchCandidates({ app: 'firefox', kind: 'any', limit: 5 });
const setId = (r.data as any).set_id;
const top = (r.data as any).candidates[0];
console.log(top.candidate_id, top.score);
```
**Step 2 — resolve to a command.** `pick: 'first'` + `dry_run: true` returns `shell_command` without delegating to `hoody-terminal`.

```typescript
const run = await client.app.execution.runAppPost({
  app: 'firefox', kind: 'any', pick: 'first', dry_run: true,
});
console.log((run.data as any).shell_command);
```
### 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).

```typescript
const dry = await client.app.execution.runAppPost({
  app: 'echo', kind: 'cli', pick: 'first', dry_run: true,
});
console.log('argv:', (dry.data as any).selected.execution_plan.argv);
```
**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`.

```typescript
const r = await client.app.execution.runAppPost({
  app: 'echo', kind: 'cli', pick: 'first', dry_run: false,
});
if ((r.data as any).status === 'dry-run' && !(r.data as any).terminal) {
  console.warn('Terminal delegation disabled on this kit; nothing actually launched');
}
```
### 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`.**

```typescript
const list = await client.app.execution.searchCandidates({ app: 'git', kind: 'cli', limit: 5 });
(list.data as any).candidates.forEach((c, i) =>
  console.log(i, c.candidate_id, c.provider, c.score));
const setId = (list.data as any).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}`).

```typescript
const r = await client.app.execution.runAppPost({
  app: 'git', kind: 'cli', set_id: setId, pick: 'index', pick_index: 2, dry_run: true,
});
console.log((r.data as any).shell_command);
```
**Pick by id alternative** — when you know the exact candidate, use `pick: 'id'` + `candidate_id`:

```typescript
await client.app.execution.runAppPost({
  app: 'git', kind: 'cli', pick: 'id',
  candidate_id: 'system-path:/usr/bin/git', dry_run: true,
});
```
### 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.

```typescript
const r = await client.app.execution.searchCandidates({
  app: 'jq', os: 'linux', arch: 'amd64', kind: 'cli', source: ['system'], limit: 5,
});
const providers = new Set((r.data as any).candidates.map(c => c.provider));
// providers = Set { 'system' }
```
**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. `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).

```typescript
const pf = await client.app.execution.preflight({
  app: 'xeyes', kind: 'gui', pick: 'first',
});
if ((pf.data as any).recommended_mode !== 'delegated-execute') {
  console.warn('Will fall back to dry-run; terminal_request_preview:', (pf.data as any).terminal_request_preview);
}
if ((pf.data as any).missing_requirements?.length) {
  console.error('Missing:', (pf.data as any).missing_requirements);
}
```
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 `runAppPost({ dry_run: false })`.

### 6. Pagination — walk a long candidate list with `searchCandidatesPaged`

**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.**

```typescript
const page1 = await client.app.execution.searchCandidatesPaged({
  selector: { app: 'git', kind: 'cli' }, page_size: 3,
});
console.log((page1.data as any).total_count, (page1.data as any).items.length);
const cursor = (page1.data as any).next_cursor;
```
**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.
⚠ The SDK's `execution.searchCandidatesPagedAll(...)` and `execution.searchCandidatesPagedIterator(...)` helpers currently return/yield only the **first page** (the generated paginator can't advance the cursor on a cursor-paged op) — relying on them silently drops later pages, so use the manual `next_cursor` loop above to collect all candidates.
```typescript
// ⚠ searchCandidatesPagedAll / searchCandidatesPagedIterator currently return
// only the FIRST page (the paginator can't advance the cursor). Drain by hand:
let cursor2 = (page1.data as any).next_cursor;
const all = [...((page1.data as any).items as any[])];
while (cursor2) {
  const p = await client.app.execution.searchCandidatesPaged({
    selector: { app: 'git', kind: 'cli' }, page_size: 3, cursor: cursor2,
  });
  all.push(...((p.data as any).items as any[]));
  cursor2 = (p.data as any).next_cursor;
}
console.log('drained:', all.length);
```
⚠ `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.**

```typescript
const sub = await client.app.jobs.createSearch({ app: 'firefox', kind: 'any' });
const jid = (sub.data as any).job_id;
```
**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.

```typescript
let status = '';
while (!['done', 'error'].includes(status)) {
  await new Promise(r => setTimeout(r, 1000));
  const s = await client.app.jobs.getStatus(jid);
  status = (s.data as any).status;
}
const final = await client.app.jobs.getStatus(jid); // read result inline
```
### 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.

`runBatch` 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`).

```typescript
const batch = await client.app.execution.runBatch({
  items: [
    { request_id: 'a', mode: 'run', selector: { app: 'ls', kind: 'cli', pick: 'first', dry_run: true } },
    { request_id: 'b', mode: 'run', selector: { app: 'echo', kind: 'cli', pick: 'first', dry_run: true } },
    { request_id: 'c', mode: 'search', selector: { app: 'git', kind: 'cli', limit: 3 } },
  ],
});
for (const it of (batch.data as any).items) {
  if (it.result === 'run')    console.log(it.request_id, it.run.shell_command);
  if (it.result === 'search') console.log(it.request_id, it.search.candidates.length, 'candidates');
}
```
⚠ `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 `runAppPost` 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 `recipes.run` — update the recipe to widen the allow-list.

```typescript
await client.app.recipes.create({
  name: 'team-js-runtime',
  description: 'Resolve a JS runtime; team-default = node CLI',
  selector_template: {
    app: 'node', kind: 'cli', os: 'linux', arch: 'amd64',
    pick: 'first', dry_run: true,
  },
  allowed_overrides: ['app', 'version', 'tags'],
});
```
**Step 2 — list / get / update / delete.**

```typescript
const list = await client.app.recipes.list();
const one  = await client.app.recipes.get('team-js-runtime');
await client.app.recipes.update('team-js-runtime', {
  description: 'Updated: now also resolves bun/deno via override',
});
await client.app.recipes.delete('team-js-runtime');
```
### 10. Invoke a recipe with overrides — `recipes.run(name, { overrides })`

**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 `runAppPost` (`{ status, shell_command, selected, ... }`).

```typescript
const r = await client.app.recipes.run('team-js-runtime', {
  overrides: { app: 'bun' },
});
console.log((r.data as any).shell_command, (r.data as any).selected.provider);
```
**Step 2 — search through the recipe** (same selector, but stop at candidate listing instead of resolving) via `recipes.search`:

```typescript
const s = await client.app.recipes.search('team-js-runtime', {
  overrides: { app: 'node' },
});
console.log((s.data as any).candidates.length, 'candidates across',
  new Set((s.data as any).candidates.map(c => c.provider)));
```
⚠ Overrides outside `allowed_overrides` are **rejected** with `400 "recipe override not allowed: <field>"` — they are NOT silently dropped. Use `recipes.update` to widen the allow-list.

## Reference

**Accessor:** `client.app`  |  **Import:** `import * as app from '@hoody-ai/hoody-sdk/app'`

### `client.app.configuration` (1) — APIs for retrieving consolidated runtime configuration state including active profile selection

#### `get` — Get full runtime configuration

```typescript
client.app.configuration.get()
```

**Returns:** `app_ConfigFile`  |  **HTTP:** `GET /api/v1/run/config`

---

### `client.app.docs` (2) — Self-documenting specification endpoints in JSON and YAML formats

#### `getJson` — OpenAPI specification (JSON)

```typescript
client.app.docs.getJson()
```

**Returns:** `any`  |  **HTTP:** `GET /api/v1/run/openapi.json`

---

#### `getYaml` — OpenAPI specification (YAML)

```typescript
client.app.docs.getYaml()
```

**Returns:** `any`  |  **HTTP:** `GET /api/v1/run/openapi.yaml`

---

### `client.app.execution` (10) — APIs for searching and running applications across multiple package sources with automatic candidate ranking and selection

#### `preflight` — Preflight a run request

```typescript
client.app.execution.preflight(data: app_Selector)
```

| Parameter | Type | In | Required | Description |
|-----------|------|------|----------|-------------|
| `data` | `app_Selector` | body | Yes |  |

**Returns:** `app_PreflightResponse`  |  **HTTP:** `POST /api/v1/run/preflight`

---

#### `runAppGet` — Resolve an application and return exact shell command

```typescript
client.app.execution.runAppGet(app: string, os?: string, source?: array, kind?: string, arch?: string, tags?: array, profile?: string, channel?: string, version?: string, variant?: string, publisher?: string, repo?: string, release?: string, asset?: string, pick?: string, pick_index?: integer, candidate_id?: string, set_id?: string, terminal_id?: integer, display?: string, origin?: string, defer_pid?: integer, defer_start_time_ticks?: string, defer_timeout_ms?: integer, defer_poll_ms?: integer, dry_run?: boolean, print_curl?: string, format?: string, redirect?: boolean, redirect_to?: string, limit?: integer)
```

| Parameter | Type | In | Required | Description |
|-----------|------|------|----------|-------------|
| `app` | `string` | query | Yes | Primary name query |
| `os` | `string` | query | No | Target OS filter |
| `source` | `array` | query | No | Source kind filter (repeatable) |
| `kind` | `string` | query | No | App kind filter |
| `arch` | `string` | query | No | Target CPU architecture filter |
| `tags` | `array` | query | No | Free-form tags for filtering and ranking (repeatable) |
| `profile` | `string` | query | No | Named profile for default preferences |
| `channel` | `string` | query | No | Release channel hint |
| `version` | `string` | query | No | Exact version or provider-defined version constraint |
| `variant` | `string` | query | No | Provider-specific variant hint |
| `publisher` | `string` | query | No | Publisher hint for curated registries |
| `repo` | `string` | query | No | Repository hint such as owner/name |
| `release` | `string` | query | No | Release hint such as a tag name |
| `asset` | `string` | query | No | Desired asset name or pattern |
| `pick` | `string` | query | No | Candidate selection mode (ask, first, index, id) |
| `pick_index` | `integer` | query | No | Candidate index (required when pick=index) |
| `candidate_id` | `string` | query | No | Specific candidate ID (required when pick=id) |
| `set_id` | `string` | query | No | Bind pick to a specific candidate set |
| `terminal_id` | `integer` | query | No | Terminal session ID (default 1) |
| `display` | `string` | query | No | X11 DISPLAY number |
| `origin` | `string` | query | No | Origin identifier for observability propagation |
| `defer_pid` | `integer` | query | No | Defer command injection until this PID exits |
| `defer_start_time_ticks` | `string` | query | No | Start-time ticks used to avoid PID reuse bugs |
| `defer_timeout_ms` | `integer` | query | No | Maximum defer wait time in milliseconds |
| `defer_poll_ms` | `integer` | query | No | Defer polling interval in milliseconds |
| `dry_run` | `boolean` | query | No | If true, force command-only response (no delegation) |
| `print_curl` | `string` | query | No | Generate curl command (hoody-run or hoody-terminal) |
| `format` | `string` | query | No | Output format (json or html) |
| `redirect` | `boolean` | query | No | Redirect to display page after scheduling |
| `redirect_to` | `string` | query | No | Override redirect target URL |
| `limit` | `integer` | query | No | Max candidates (default 25) |

**Returns:** `app_RunResponse`  |  **HTTP:** `GET /api/v1/run/run`

---

#### `runAppPost` — Resolve an application via JSON body

```typescript
client.app.execution.runAppPost(data: app_Selector)
```

| Parameter | Type | In | Required | Description |
|-----------|------|------|----------|-------------|
| `data` | `app_Selector` | body | Yes |  |

**Returns:** `app_RunResponse`  |  **HTTP:** `POST /api/v1/run/run`

---

#### `runBatch` — Execute a batch of search or run requests

```typescript
client.app.execution.runBatch(data: app_BatchRequest)
```

| Parameter | Type | In | Required | Description |
|-----------|------|------|----------|-------------|
| `data` | `app_BatchRequest` | body | Yes |  |

**Returns:** `app_BatchResponse`  |  **HTTP:** `POST /api/v1/run/batch`

---

#### `runPathBased` — Path-based resolve (positional or key-value)

```typescript
client.app.execution.runPathBased(rest: string, os?: string, source?: array, kind?: string, arch?: string, tags?: array, profile?: string, channel?: string, version?: string, variant?: string, publisher?: string, repo?: string, release?: string, asset?: string, pick?: string, pick_index?: integer, candidate_id?: string, set_id?: string, terminal_id?: integer, display?: string, origin?: string, defer_pid?: integer, defer_start_time_ticks?: string, defer_timeout_ms?: integer, defer_poll_ms?: integer, dry_run?: boolean, print_curl?: string, format?: string, redirect?: boolean, redirect_to?: string, limit?: integer)
```

| Parameter | Type | In | Required | Description |
|-----------|------|------|----------|-------------|
| `rest` | `string` | path | Yes | Path segments for positional or key-value app specification |
| `os` | `string` | query | No | Target OS filter when not supplied in the path |
| `source` | `array` | query | No | Source kind filter (repeatable) |
| `kind` | `string` | query | No | App kind filter when not supplied in the path |
| `arch` | `string` | query | No | Target CPU architecture filter |
| `tags` | `array` | query | No | Free-form tags for filtering and ranking (repeatable) |
| `profile` | `string` | query | No | Named profile for default preferences |
| `channel` | `string` | query | No | Release channel hint |
| `version` | `string` | query | No | Exact version or provider-defined version constraint |
| `variant` | `string` | query | No | Provider-specific variant hint |
| `publisher` | `string` | query | No | Publisher hint for curated registries |
| `repo` | `string` | query | No | Repository hint such as owner/name |
| `release` | `string` | query | No | Release hint such as a tag name |
| `asset` | `string` | query | No | Desired asset name or pattern |
| `pick` | `string` | query | No | Candidate selection mode (ask, first, index, id) |
| `pick_index` | `integer` | query | No | Candidate index (required when pick=index) |
| `candidate_id` | `string` | query | No | Specific candidate ID (required when pick=id) |
| `set_id` | `string` | query | No | Bind pick to a specific candidate set |
| `terminal_id` | `integer` | query | No | Terminal session ID when not supplied in the path |
| `display` | `string` | query | No | X11 DISPLAY number |
| `origin` | `string` | query | No | Origin identifier for observability propagation |
| `defer_pid` | `integer` | query | No | Defer command injection until this PID exits |
| `defer_start_time_ticks` | `string` | query | No | Start-time ticks used to avoid PID reuse bugs |
| `defer_timeout_ms` | `integer` | query | No | Maximum defer wait time in milliseconds |
| `defer_poll_ms` | `integer` | query | No | Defer polling interval in milliseconds |
| `dry_run` | `boolean` | query | No | If true, force command-only response (no delegation) |
| `print_curl` | `string` | query | No | Generate curl command (hoody-run or hoody-terminal) |
| `format` | `string` | query | No | Output format (json or html) |
| `redirect` | `boolean` | query | No | Redirect to display page after scheduling |
| `redirect_to` | `string` | query | No | Override redirect target URL |
| `limit` | `integer` | query | No | Max candidates (default 25) |

**Returns:** `app_RunResponse`  |  **HTTP:** `GET /api/v1/run/go/{rest}`

---

#### `runTerminalAnchored` — Terminal-anchored path-based resolve

```typescript
client.app.execution.runTerminalAnchored(terminal_id: integer, rest: string, os?: string, source?: array, kind?: string, arch?: string, tags?: array, profile?: string, channel?: string, version?: string, variant?: string, publisher?: string, repo?: string, release?: string, asset?: string, pick?: string, pick_index?: integer, candidate_id?: string, set_id?: string, display?: string, origin?: string, defer_pid?: integer, defer_start_time_ticks?: string, defer_timeout_ms?: integer, defer_poll_ms?: integer, dry_run?: boolean, print_curl?: string, format?: string, redirect?: boolean, redirect_to?: string, limit?: integer)
```

| Parameter | Type | In | Required | Description |
|-----------|------|------|----------|-------------|
| `terminal_id` | `integer` | path | Yes | Terminal session ID (1-65535) |
| `rest` | `string` | path | Yes | Path segments for app specification |
| `os` | `string` | query | No | Target OS filter when not supplied in the path |
| `source` | `array` | query | No | Source kind filter (repeatable) |
| `kind` | `string` | query | No | App kind filter when not supplied in the path |
| `arch` | `string` | query | No | Target CPU architecture filter |
| `tags` | `array` | query | No | Free-form tags for filtering and ranking (repeatable) |
| `profile` | `string` | query | No | Named profile for default preferences |
| `channel` | `string` | query | No | Release channel hint |
| `version` | `string` | query | No | Exact version or provider-defined version constraint |
| `variant` | `string` | query | No | Provider-specific variant hint |
| `publisher` | `string` | query | No | Publisher hint for curated registries |
| `repo` | `string` | query | No | Repository hint such as owner/name |
| `release` | `string` | query | No | Release hint such as a tag name |
| `asset` | `string` | query | No | Desired asset name or pattern |
| `pick` | `string` | query | No | Candidate selection mode (ask, first, index, id) |
| `pick_index` | `integer` | query | No | Candidate index (required when pick=index) |
| `candidate_id` | `string` | query | No | Specific candidate ID (required when pick=id) |
| `set_id` | `string` | query | No | Bind pick to a specific candidate set |
| `display` | `string` | query | No | X11 DISPLAY number |
| `origin` | `string` | query | No | Origin identifier for observability propagation |
| `defer_pid` | `integer` | query | No | Defer command injection until this PID exits |
| `defer_start_time_ticks` | `string` | query | No | Start-time ticks used to avoid PID reuse bugs |
| `defer_timeout_ms` | `integer` | query | No | Maximum defer wait time in milliseconds |
| `defer_poll_ms` | `integer` | query | No | Defer polling interval in milliseconds |
| `dry_run` | `boolean` | query | No | If true, force command-only response (no delegation) |
| `print_curl` | `string` | query | No | Generate curl command (hoody-run or hoody-terminal) |
| `format` | `string` | query | No | Output format (json or html) |
| `redirect` | `boolean` | query | No | Redirect to display page after scheduling |
| `redirect_to` | `string` | query | No | Override redirect target URL |
| `limit` | `integer` | query | No | Max candidates (default 25) |

**Returns:** `app_RunResponse`  |  **HTTP:** `GET /api/v1/run/t/{terminal_id}/go/{rest}`

---

#### `searchCandidates` — Search for app candidates

```typescript
client.app.execution.searchCandidates(app: string, os?: string, source?: array, kind?: string, arch?: string, tags?: array, profile?: string, channel?: string, version?: string, variant?: string, publisher?: string, repo?: string, release?: string, asset?: string, limit?: integer)
```

| Parameter | Type | In | Required | Description |
|-----------|------|------|----------|-------------|
| `app` | `string` | query | Yes | Primary name query (aliases q, name) |
| `os` | `string` | query | No | Target OS filter |
| `source` | `array` | query | No | Source kind filter (repeatable) |
| `kind` | `string` | query | No | App kind filter (gui, cli, any) |
| `arch` | `string` | query | No | Target CPU architecture filter |
| `tags` | `array` | query | No | Free-form tags for filtering and ranking (repeatable) |
| `profile` | `string` | query | No | Named profile for default preferences |
| `channel` | `string` | query | No | Release channel hint (for example stable or beta) |
| `version` | `string` | query | No | Exact version or provider-defined version constraint |
| `variant` | `string` | query | No | Provider-specific variant hint (for example portable or headless) |
| `publisher` | `string` | query | No | Publisher hint for curated registries |
| `repo` | `string` | query | No | Repository hint such as owner/name |
| `release` | `string` | query | No | Release hint such as a tag name |
| `asset` | `string` | query | No | Desired asset name or pattern |
| `limit` | `integer` | query | No | Max candidates to return (default 25) |

**Returns:** `app_SearchResponse`  |  **HTTP:** `GET /api/v1/run/search`

---

#### `searchCandidatesPaged` — Search for app candidates with cursor pagination

```typescript
client.app.execution.searchCandidatesPaged(data: app_PagedSearchRequest)
```

| Parameter | Type | In | Required | Description |
|-----------|------|------|----------|-------------|
| `data` | `app_PagedSearchRequest` | body | Yes |  |

**Returns:** `app_PagedSearchResponse`  |  **HTTP:** `POST /api/v1/run/search/paged`

---

#### `searchCandidatesPagedAll` — Search for app candidates with cursor pagination (collect all pages)

```typescript
client.app.execution.searchCandidatesPagedAll(data: app_PagedSearchRequest)
```

| Parameter | Type | In | Required | Description |
|-----------|------|------|----------|-------------|
| `data` | `app_PagedSearchRequest` | body | Yes |  |

**Returns:** `app_PagedSearchResponse[]`  |  **HTTP:** `POST /api/v1/run/search/paged`

---

#### `searchCandidatesPagedIterator` — Search for app candidates with cursor pagination (async iterator)

```typescript
client.app.execution.searchCandidatesPagedIterator(data: app_PagedSearchRequest)
```

| Parameter | Type | In | Required | Description |
|-----------|------|------|----------|-------------|
| `data` | `app_PagedSearchRequest` | body | Yes |  |

**Returns:** `AsyncIterableIterator<app_PagedSearchResponse>`  |  **HTTP:** `POST /api/v1/run/search/paged`

---

### `client.app.health` (1) — APIs for searching and running applications across multiple package sources with automatic candidate ranking and selection

#### `check` — Service health check

```typescript
client.app.health.check()
```

**Returns:** `app_HealthResponse`  |  **HTTP:** `GET /api/v1/run/health`

---

### `client.app.jobs` (2) — APIs for tracking async job status with optional long-polling support for sync and background operations

#### `createSearch` — Start an async search job

```typescript
client.app.jobs.createSearch(data: app_Selector)
```

| Parameter | Type | In | Required | Description |
|-----------|------|------|----------|-------------|
| `data` | `app_Selector` | body | Yes |  |

**Returns:** `app_Job`  |  **HTTP:** `POST /api/v1/run/search/jobs`

---

#### `getStatus` — Get job status

```typescript
client.app.jobs.getStatus(job_id: string, wait?: string, timeout_ms?: integer)
```

| Parameter | Type | In | Required | Description |
|-----------|------|------|----------|-------------|
| `job_id` | `string` | path | Yes | Job identifier (UUID) |
| `wait` | `string` | query | No | Set to 'done' to long-poll until job completes |
| `timeout_ms` | `integer` | query | No | Long-poll timeout in milliseconds (default 0, max 120000) |

**Returns:** `app_Job`  |  **HTTP:** `GET /api/v1/run/jobs/{job_id}`

---

### `client.app.profiles` (5) — APIs for managing user profiles and defaults including source overrides, pick mode, and display preferences

#### `create` — Create a new profile

```typescript
client.app.profiles.create(data: app_ProfileConfig)
```

| Parameter | Type | In | Required | Description |
|-----------|------|------|----------|-------------|
| `data` | `app_ProfileConfig` | body | Yes |  |

**Returns:** `any`  |  **HTTP:** `POST /api/v1/run/profiles`

---

#### `delete` — Delete a profile

```typescript
client.app.profiles.delete(profile: string)
```

| Parameter | Type | In | Required | Description |
|-----------|------|------|----------|-------------|
| `profile` | `string` | path | Yes | Profile name |

**Returns:** `void`  |  **HTTP:** `DELETE /api/v1/run/profiles/{profile}`

---

#### `list` — List all profiles

```typescript
client.app.profiles.list()
```

**Returns:** `any`  |  **HTTP:** `GET /api/v1/run/profiles`

---

#### `select` — Select the active profile

```typescript
client.app.profiles.select(profile: string)
```

| Parameter | Type | In | Required | Description |
|-----------|------|------|----------|-------------|
| `profile` | `string` | path | Yes | Profile name to select |

**Returns:** `app_SelectedProfileResponse`  |  **HTTP:** `POST /api/v1/run/profiles/{profile}/select`

---

#### `update` — Update a profile

```typescript
client.app.profiles.update(profile: string, data: object)
```

| Parameter | Type | In | Required | Description |
|-----------|------|------|----------|-------------|
| `profile` | `string` | path | Yes | Profile name |
| `data` | `object` | body | Yes |  |

**Returns:** `app_ProfileConfig`  |  **HTTP:** `PATCH /api/v1/run/profiles/{profile}`

---

### `client.app.recipes` (7) — APIs for managing saved selector templates and invoking them with controlled overrides

#### `create` — Create a saved recipe

```typescript
client.app.recipes.create(data: app_RecipeConfig)
```

| Parameter | Type | In | Required | Description |
|-----------|------|------|----------|-------------|
| `data` | `app_RecipeConfig` | body | Yes |  |

**Returns:** `any`  |  **HTTP:** `POST /api/v1/run/recipes`

---

#### `delete` — Delete a saved recipe

```typescript
client.app.recipes.delete(name: string)
```

| Parameter | Type | In | Required | Description |
|-----------|------|------|----------|-------------|
| `name` | `string` | path | Yes | Recipe name |

**Returns:** `void`  |  **HTTP:** `DELETE /api/v1/run/recipes/{name}`

---

#### `get` — Get a saved recipe

```typescript
client.app.recipes.get(name: string)
```

| Parameter | Type | In | Required | Description |
|-----------|------|------|----------|-------------|
| `name` | `string` | path | Yes | Recipe name |

**Returns:** `app_RecipeConfig`  |  **HTTP:** `GET /api/v1/run/recipes/{name}`

---

#### `list` — List saved launch recipes

```typescript
client.app.recipes.list()
```

**Returns:** `any`  |  **HTTP:** `GET /api/v1/run/recipes`

---

#### `run` — Run using a saved recipe

```typescript
client.app.recipes.run(name: string, data: app_RecipeExecutionRequest)
```

| Parameter | Type | In | Required | Description |
|-----------|------|------|----------|-------------|
| `name` | `string` | path | Yes | Recipe name |
| `data` | `app_RecipeExecutionRequest` | body | Yes |  |

**Returns:** `app_RunResponse`  |  **HTTP:** `POST /api/v1/run/recipes/{name}/run`

---

#### `search` — Search using a saved recipe

```typescript
client.app.recipes.search(name: string, data: app_RecipeExecutionRequest)
```

| Parameter | Type | In | Required | Description |
|-----------|------|------|----------|-------------|
| `name` | `string` | path | Yes | Recipe name |
| `data` | `app_RecipeExecutionRequest` | body | Yes |  |

**Returns:** `app_SearchResponse`  |  **HTTP:** `POST /api/v1/run/recipes/{name}/search`

---

#### `update` — Update a saved recipe

```typescript
client.app.recipes.update(name: string, data: object)
```

| Parameter | Type | In | Required | Description |
|-----------|------|------|----------|-------------|
| `name` | `string` | path | Yes | Recipe name |
| `data` | `object` | body | Yes |  |

**Returns:** `app_RecipeConfig`  |  **HTTP:** `PATCH /api/v1/run/recipes/{name}`

---

### `client.app.sources` (7) — APIs for managing package sources including CRUD operations, enable/disable, priority control, and sync triggers

#### `create` — Create a new package source

```typescript
client.app.sources.create(data: app_SourceConfig)
```

| Parameter | Type | In | Required | Description |
|-----------|------|------|----------|-------------|
| `data` | `app_SourceConfig` | body | Yes |  |

**Returns:** `any`  |  **HTTP:** `POST /api/v1/run/sources`

---

#### `delete` — Delete a package source

```typescript
client.app.sources.delete(source_id: string)
```

| Parameter | Type | In | Required | Description |
|-----------|------|------|----------|-------------|
| `source_id` | `string` | path | Yes | Source identifier |

**Returns:** `void`  |  **HTTP:** `DELETE /api/v1/run/sources/{source_id}`

---

#### `getDiagnostics` — Get runtime diagnostics for a source

```typescript
client.app.sources.getDiagnostics(source_id: string)
```

| Parameter | Type | In | Required | Description |
|-----------|------|------|----------|-------------|
| `source_id` | `string` | path | Yes | Source identifier |

**Returns:** `app_SourceDiagnostics`  |  **HTTP:** `GET /api/v1/run/sources/{source_id}/diagnostics`

---

#### `list` — List all package sources

```typescript
client.app.sources.list()
```

**Returns:** `any`  |  **HTTP:** `GET /api/v1/run/sources`

---

#### `sync` — Sync a single source

```typescript
client.app.sources.sync(source_id: string)
```

| Parameter | Type | In | Required | Description |
|-----------|------|------|----------|-------------|
| `source_id` | `string` | path | Yes | Source identifier |

**Returns:** `app_Job`  |  **HTTP:** `POST /api/v1/run/sources/{source_id}/sync`

---

#### `syncAll` — Sync all sources

```typescript
client.app.sources.syncAll()
```

**Returns:** `app_Job`  |  **HTTP:** `POST /api/v1/run/sources/sync`

---

#### `update` — Update a package source

```typescript
client.app.sources.update(source_id: string, data: object)
```

| Parameter | Type | In | Required | Description |
|-----------|------|------|----------|-------------|
| `source_id` | `string` | path | Yes | Source identifier |
| `data` | `object` | body | Yes |  |

**Returns:** `app_SourceConfig`  |  **HTTP:** `PATCH /api/v1/run/sources/{source_id}`


### Body schemas

- `app_PagedSearchRequest` — `{ selector*: app_Selector, cursor: string, page_size: int }`
- `app_Selector` — `{ app*: string, os: app_Os, kind: app_AppKind, source: app_SourceKind[], arch: app_Arch, tags: string[], profile: string, channel: string, version: string, variant: string, publisher: string, repo: string, release: string, asset: string, pick: app_PickMode, pick_index: int, candidate_id: string, set_id: string, terminal_id: int, display: string, origin: string, defer_pid: int, defer_start_time_ticks: string, defer_timeout_ms: int, defer_poll_ms: int, format: app_OutputFormat, redirect: bool, redirect_to: string, dry_run: bool, print_curl: app_PrintCurlMode, limit: int }`
- `app_BatchRequest` — `{ items: app_BatchItemRequest[] }`
- `app_SourceConfig` — `{ source_id*: string, enabled*: bool, priority*: int, provider*: app_SourceKind, source_type*: app_SourceType, pin: app_SourcePin, config: object }`
- `app_ProfileConfig` — `{ name*: string, description: string, defaults: app_ProfileDefaults, sources_mode: app_ProfileSourceMode, sources: app_ProfileSourceOverride[], policy: app_PolicyConfig }`
- `app_RecipeConfig` — `{ name*: string, description: string, selector_template: app_SelectorTemplate, allowed_overrides: string[] }`
- `app_RecipeExecutionRequest` — `{ overrides: app_SelectorTemplate }`
- `app_Os` — `"linux" | "windows" | "any"`
- `app_AppKind` — `"gui" | "cli" | "any"`
- `app_SourceKind` — `"nix" | "pkgx" | "appimage" | "oci" | "registry" | "system" | "any"`
- `app_Arch` — `"amd64" | "arm64" | "any"`
- `app_PickMode` — `"ask" | "first" | "index" | "id"`
- `app_OutputFormat` — `"json" | "html"`
- `app_PrintCurlMode` — `"hoody-run" | "hoody-terminal"`
- `app_BatchItemRequest` — `{ request_id*: string, mode*: app_BatchMode, selector*: app_Selector }`
- `app_SourceType` — `"nix-pkgs" | "nix-flake" | "pkgx" | "app-image-pinned" | "app-image-git-hub-releases" | "app-image-catalog" | "oci-local-images" | "manifest-registry" | …(11 values)`
- `app_SourcePin` — `{ url*: string, sha256: string, author_pubkey_ed25519: string, sig_ed25519: string }`
- `app_ProfileDefaults` — `{ os: app_Os, kind: app_AppKind, source: app_SourceKind[], pick: app_PickMode, terminal_id: int, display: string, redirect: bool, limit: int }`
- `app_ProfileSourceMode` — `"inherit" | "allowlist"`
- `app_ProfileSourceOverride` — `{ source_id*: string, enabled: bool, priority: int }`
- `app_PolicyConfig` — `{ require_verified: bool, require_integrity: bool, allow_delegated_execution: bool, allow_redirect: bool, deny_providers: app_SourceKind[], deny_source_ids: string[] }`
- `app_SelectorTemplate` — `{ app: string, os: app_Os, kind: app_AppKind, source: app_SourceKind[], arch: app_Arch, tags: string[], profile: string, channel: string, version: string, variant: string, publisher: string, repo: string, release: string, asset: string, pick: app_PickMode, pick_index: int, candidate_id: string, set_id: string, terminal_id: int, display: string, origin: string, defer_pid: int, defer_start_time_ticks: string, defer_timeout_ms: int, defer_poll_ms: int, format: app_OutputFormat, redirect: bool, redirect_to: string, dry_run: bool, print_curl: app_PrintCurlMode, limit: int }`
- `app_BatchMode` — `"search" | "run"`

