Metadata-Version: 2.3
Name: uqadm
Version: 2026.40.0
Summary: Unique admin CLI (space list, export, upsert, migrate, delete): separate from unique-cli
Requires-Dist: unique-sdk>=2026.40.0
Requires-Dist: typer>=0.12,<1
Requires-Dist: python-dotenv>=1.0.0,<2
Requires-Dist: pyyaml>=6.0.1,<7
Requires-Dist: rich>=13.3.5
Requires-Dist: tomli-w>=1.0,<2
Requires-Python: >=3.11
Description-Content-Type: text/markdown

# uqadm

Admin CLI for the Unique platform. It groups these command families:

- **`space`** — list, export, diff, migrate, upsert, model replacement, access grants, ingestion settings, and delete assistant spaces.
- **`kb`** — knowledge-base folders: create paths, sync/download/remove files and folders, grant group access, read/set folder ingestion config, replace models in ingestion config.
- **`chat`** — send messages to an assistant and inspect chat history.
- **`env`** — manage named credential slots stored in `~/.uqadm/envs/`.
- **`install`** — one-time bootstrap: create directories, install shell completion, set up your first slot.

It is separate from `unique-cli` (the agent-oriented file explorer) and shares the same `UNIQUE_*` environment variable conventions as `unique_sdk`.

---

## Installation

From the AI monorepo root:

```bash
cd /path/to/ai
uv sync --package uqadm
uv run uqadm --help
```

Or install the `uqadm` package into any environment that already has `unique-sdk`:

```bash
pip install -e uqadm/
uqadm --help
```

---

## `uqadm install`

One-time bootstrap for a new machine or user. Safe to re-run (idempotent).

```bash
uqadm install                           # interactive
uqadm install --dry-run                 # preview without changes
uqadm install --no-rc                   # skip rc file patching
uqadm install --shell bash              # force bash (auto-detected by default)
uqadm install --rc-file ~/.zshrc        # explicit rc file path
```

What it does:

1. Creates `~/.uqadm/` (mode `0700`) and `~/.uqadm/envs/`.
2. Installs shell completion for `uqadm`.
3. Offers to create your first credential slot interactively (skipped if slots already exist).
4. Appends an idempotent `export UQADM_HOME=...` block to your shell rc file.

| Option | Description |
|--------|-------------|
| `--dry-run` | Print what would be done without making any changes. |
| `--no-rc` | Skip patching the shell rc file. |
| `--shell SHELL` | Shell to configure: `zsh` or `bash` (auto-detected from `$SHELL`). |
| `--rc-file PATH` | Explicit path to the rc file to patch. |

---

## Quick start

```bash
# 1. Bootstrap directories, shell completion, and first credential slot
uqadm install

# 2. (Optional) add more slots or set a different default
uqadm env create prod --set-default

# 3. List spaces — uses the configured default slot
uqadm space list

# 4. Send a message to an assistant
uqadm chat send asst_abc123 --text "Hello!"

# 5. Continue the same thread using the chat_id printed after step 4
uqadm chat send asst_abc123 --text "Tell me more" --chat-id chat_xyz789
```

---

## Credential slots

A **slot** is a short name (e.g. `qa`, `prod`, `1`) that maps to a `.env` file holding `UNIQUE_*` credentials.

### File locations (resolution order)

1. `~/.uqadm/envs/.{slot}.env` — managed by `uqadm env create`; this is the primary location.
2. If not found there, falls back to the **current working directory** (or `--cwd` if set):
   - `.{slot}.env` (hidden file wins)
   - `{slot}.env`

If no file is found, `uqadm` prints a short guide to stderr and exits with code **2** — no Python traceback.

### Default slot

Running `uqadm env set-default qa` writes the default slot to `~/.uqadm/config.toml`. All commands that accept `--slot` will use this default when the option is omitted.

### Env file format

```bash
# ~/.uqadm/envs/.qa.env
UNIQUE_USER_ID=user_...
UNIQUE_COMPANY_ID=company_...
UNIQUE_API_KEY=ukey_...
UNIQUE_APP_ID=app_...
UNIQUE_API_BASE=https://unique_api_base_url
```

Toolkit-style names are also accepted (lowercase `unique_auth_user_id`, `unique_app_key`, etc.). If both `UNIQUE_*` and its alias are present, `UNIQUE_*` wins.

### Supported variables

| Variable | Required | Notes |
|----------|----------|-------|
| `UNIQUE_USER_ID` | Yes | Also: `unique_auth_user_id` / `UNIQUE_AUTH_USER_ID` |
| `UNIQUE_COMPANY_ID` | Yes | Also: `unique_auth_company_id` / `UNIQUE_AUTH_COMPANY_ID` |
| `UNIQUE_API_KEY` | No | Also: `unique_app_key` / `UNIQUE_APP_KEY` |
| `UNIQUE_APP_ID` | No | Also: `unique_app_id` |
| `UNIQUE_API_BASE` | No | Also: `unique_api_base_url` / `UNIQUE_API_BASE_URL`. Host-only URLs get `/public/chat` appended automatically. |

### Authentication debug output

If an API call fails with HTTP 401 or an authentication error, `uqadm` prints a redacted credential snapshot to stderr (user id, company id, app id, base URL, and a masked description of the API key — never the full key).

---

## Global options

These must appear **before** the subcommand:

| Option | Description |
|--------|-------------|
| `--help` / `-h` | Show help and exit. |
| `--version` | Print `uqadm` version and exit. |
| `--cwd DIRECTORY` | Override the directory used for local env file lookup. |

```bash
uqadm --version
uqadm --cwd /path/to/secrets space list --slot qa
```

---

## `uqadm env`

Manage credential slots in `~/.uqadm/envs/`.

```bash
uqadm env --help
```

### `env create SLOT`

Interactively (or non-interactively) create a credential slot file at `~/.uqadm/envs/.{SLOT}.env`.

```bash
uqadm env create qa
uqadm env create prod --set-default
uqadm env create staging --force               # overwrite if already exists
uqadm env create ci --non-interactive \
  --user-id user_abc \
  --company-id company_xyz \
  --api-key ukey_... \
  --api-base https://gateway.unique.app/public/chat-gen2
```

| Option | Description |
|--------|-------------|
| `--set-default` | Mark this slot as the default after creation. |
| `--force` | Overwrite an existing slot file without prompting. |
| `--non-interactive` | Skip prompts; supply values via flags below. |
| `--user-id TEXT` | `UNIQUE_USER_ID` value. |
| `--company-id TEXT` | `UNIQUE_COMPANY_ID` value. |
| `--api-key TEXT` | `UNIQUE_API_KEY` value (optional). |
| `--app-id TEXT` | `UNIQUE_APP_ID` value (optional). |
| `--api-base TEXT` | `UNIQUE_API_BASE` value (optional). |

### `env list`

List all available slots; the default slot is marked with `*`.

```bash
uqadm env list
# * qa
#   prod
#   staging
```

### `env show [SLOT]`

Print the resolved credential values for a slot (API key is redacted). Omit `SLOT` to use the default.

```bash
uqadm env show
uqadm env show prod
```

### `env set-default SLOT`

Set the default slot written to `~/.uqadm/config.toml`.

```bash
uqadm env set-default prod
```

### `env delete SLOT`

Remove the env file for a slot (prompts for confirmation unless `-y`).

```bash
uqadm env delete staging
uqadm env delete staging -y
```

---

## `uqadm space`

Space administration commands.

```bash
uqadm space --help
```

### `space list`

List all spaces visible to the resolved slot credentials.

```bash
uqadm space list                        # uses default slot
uqadm space list --slot qa
uqadm space list --slot prod --name Report
uqadm space list --slot prod --json
```

| Option | Description |
|--------|-------------|
| `--slot SLOT` | Credential slot (default: configured default slot). |
| `--name TEXT` | Case-insensitive partial filter on space name. |
| `--json` | Print full result as JSON instead of a table. |

### `space export SPACE_ID`

Export a space snapshot to stdout (JSON) or a file.

```bash
uqadm space export space_abc123                              # JSON to stdout
uqadm space export space_abc123 --slot prod
uqadm space export space_abc123 -o backup.yaml              # YAML file
uqadm space export space_abc123 -o backup.json
```

| Option | Description |
|--------|-------------|
| `SPACE_ID` | Space id or `https://` URL containing `/space/<id>`. |
| `--slot SLOT` | Credential slot (default: configured default slot). |
| `-o`, `--output PATH` | Write to file; suffix must be `.json`, `.yaml`, or `.yml`. Default: stdout. |

### `space upsert`

Create or update a space from a local snapshot file. Omit `--target` to create a new space; provide `--target` to update an existing one.

```bash
uqadm space upsert -f backup.yaml                            # create on default slot
uqadm space upsert -f backup.yaml --slot qa                  # create on specific slot
uqadm space upsert -f edited.json --target space_dst456      # update existing space
uqadm space upsert -f backup.yaml --slot prod --target space_dst456
uqadm space upsert -f backup.yaml --dry-run
```

| Option | Description |
|--------|-------------|
| `-f`, `--file FILE` | Local snapshot (`.json`, `.yaml`, or `.yml`). Required. |
| `--slot SLOT` | Credential slot (default: configured default slot). |
| `--target SPACE_ID` | Space id or URL to update. Omit to create a new space. |
| `--dry-run` | Print actions without calling create/update APIs. |

### `space diff`

Compare two spaces. Exits **0** if identical, **1** if differences exist.

```bash
uqadm space diff --source "qa:space_a" --destination "qa:space_b"
uqadm space diff --source "qa:space_x" --destination "prod:space_y" --format side-by-side
uqadm space diff --source "qa:x" --destination "prod:y" --strict
```

| Option | Description |
|--------|-------------|
| `--source SPEC` | First space (`slot:space_id` or `slot:URL`). |
| `--destination SPEC` | Second space (same format). |
| `--strict` | Compare raw payloads (skip normalization). |
| `--format` | `unified` (default) or `side-by-side`. |

By default, ephemeral keys (`id`, `createdAt`, `updatedAt`, etc.) are stripped before comparison so you see meaningful config drift.

### `space migrate`

Copy assistant configuration from a source space to a destination (new or existing).

```bash
uqadm space migrate --source "qa:space_src123" --destination "prod:"          # create new
uqadm space migrate --source "qa:space_src123" --destination "prod:space_dst" # update existing
uqadm space migrate --source "qa:space_src123" --destination "prod:" --dry-run
```

| Option | Description |
|--------|-------------|
| `--source SPEC` | Source space (`slot:space_id` or `slot:URL`). Space id required. |
| `--destination SPEC` | `slot` / `slot:` to create; `slot:space_id` or `slot:URL` to update. |
| `--dry-run` | Print actions without making API write calls. |
| `--with-knowledge` | Reserved; currently informational only. |

**Endpoint spec format** (`slot:space_id` or `slot:URL`):

| Spec | Slot | Space id |
|------|------|----------|
| `qa` | `qa` | *(none — create)* |
| `qa:` | `qa` | *(none — create)* |
| `qa:space_abc123` | `qa` | `space_abc123` |
| `prod:https://host/app/space/space_xyz` | `prod` | `space_xyz` |

Supported URL path markers: `/space/<id>`, `/custom-space/<id>`, `/swappable-intelligence-space/<id>`.

Migrates top-level space fields including `languageModel`, `allowModelSwitching`, `switchableLanguageModels` (user model selection toggle and allowed model list), `settings`, `assistantPrompts`, and modules matched by name. Scope rules, MCP bindings, and briefings are not migrated.

### `space model-replace`

Replace one language model with another across a space configuration. Rewrites
every **model-bearing key** whose value equals ``--from-model`` — top-level
``languageModel``, ``switchableLanguageModels`` entries, and nested module/tool
configuration keys (``languageModel``, ``fallbackLanguageModel``,
``hallucinationModel``, ``model``, ``modelName``, …) at any depth. Prompt text
and non-model fields (``languageModelMaxInputTokens``, ``allowModelSwitching``)
are never touched.

``--to-model`` accepts either a **model name** or a **path to a JSON/YAML file**
holding language-model info (which must include ``name``). A name is written at
each matched site as a plain string; a file is written as the full mapping it
contains. A value that looks like a path but does not resolve to a file is
rejected, so a mistyped path can never be written into your configs as a model
name.

**Input** is exactly one of: a ``SPACE_ID`` (live space), ``-f FILE`` (local
snapshot), or ``--all`` (interactive sweep over every space in the slot,
prompting ``y/n/a/q`` per matching space).

Only ``--all`` prompts. Naming a single ``SPACE_ID`` applies the update
immediately — use ``--dry-run`` (or ``-o``) first if you want to see the matched
paths before anything is written.

**Output**: with ``-o FILE`` the rewritten snapshot is written to disk
(``.json``/``.yaml``/``.yml``) and **no API write happens**; with ``-f`` and no
``-o`` it prints to stdout. Otherwise the space is updated **in place** with a
minimal payload (only the changed top-level fields and changed module
configurations are sent).

Every live update is **verified**: the space is read back and each rewritten
path checked against the value that was sent, so a write the API accepts but
does not store fails the command instead of reporting success.

``update_space`` writes a module through its ``configuration`` only, and a
handful of top-level fields. A match anywhere else — say under a module's
``toolDefinition`` — therefore cannot be sent, and the command **refuses the
update** and exits non-zero rather than applying a partial rewrite. Use ``-o``
to export the fully rewritten snapshot in that case.

```bash
# Preview which paths would change
uqadm space model-replace asst_abc --from-model AZURE_GPT_4o_2024_0806 \
  --to-model AZURE_GPT_5_2025_0807 --dry-run

# Update the live space in place
uqadm space model-replace asst_abc --from-model AZURE_GPT_4o_2024_0806 \
  --to-model AZURE_GPT_5_2025_0807

# Produce a rewritten snapshot file instead of writing to the platform
uqadm space model-replace asst_abc --from-model OLD --to-model NEW -o migrated.yaml

# Rewrite a local snapshot, replacement model described by a YAML/JSON file
uqadm space model-replace -f backup.yaml --from-model OLD \
  --to-model ./new-model.yaml -o backup.migrated.yaml

# Interactively sweep every space in a slot
uqadm space model-replace --all --slot prod --from-model OLD --to-model NEW
```

| Option | Description |
|--------|-------------|
| `SPACE_ID` | Space id or URL (mutually exclusive with `--file` / `--all`). |
| `-f`, `--file FILE` | Local snapshot (`.json`/`.yaml`/`.yml`) to rewrite instead of a live space. |
| `--all` | Iterate every space in the slot, prompting per matching space (`y`/`n`/`a`/`q`). |
| `--name TEXT` | With `--all`: case-insensitive partial filter on space name. |
| `--from-model NAME` | Model name currently in the configuration to replace. Required. |
| `--to-model NAME\|FILE` | Replacement model: a model name, or a path to a `.json`/`.yaml`/`.yml` file with language-model info (must include `name`). Required. |
| `-o`, `--output PATH` | Write the rewritten snapshot to a file instead of updating the platform. |
| `--dry-run` | Print matched paths and planned updates without writing anything — no API call and no output file, even with `-o`. |
| `-y`, `--yes` | With `--all`: apply without prompting. |
| `--slot SLOT` | Credential slot (default: configured default). |

Note: a single-space run does not follow links into sub-agent spaces (they are
separate configuration trees); ``--all`` covers them because it iterates every
space in the slot.

### `space access-grant SPACE_ID`

Add **user or group** entries to a space ACL via ``Space.add_space_access``. The API **merges** new entries with existing access; it does not replace the full ACL.

```bash
uqadm space access-grant asst_abc --group grp_1 --group grp_2
uqadm space access-grant asst_abc --user user_1 --type MANAGE --slot qa
```

| Option | Description |
|--------|-------------|
| `SPACE_ID` | Space id or URL (same rules as ``space export``). |
| `--group ID` | Repeat for each group. |
| `--user ID` | Repeat for each user. |
| `--type` | `USE` (default), `MANAGE`, or `UPLOAD`. |
| `--slot SLOT` | Credential slot (default: configured default). |

### `space ingestion-set SPACE_ID CONFIG_FILE`

Load a JSON/YAML **mapping** from disk and assign it to **``settings.ingestionConfig``** on the assistant. Other top-level ``settings`` keys are preserved (shallow merge); the file content **replaces** the previous ``ingestionConfig`` object.

Folder-level ingestion (knowledge base) uses a different shape; use ``uqadm kb ingestion set`` for scopes/folders.

```bash
uqadm space ingestion-set asst_abc ./ingestion.json
uqadm space ingestion-set asst_abc ./ingestion.yaml --slot prod --dry-run
```

| Option | Description |
|--------|-------------|
| `CONFIG_FILE` | ``.json``, ``.yaml``, or ``.yml``; root must be a mapping. |
| `--dry-run` | Print which ``settings`` keys would be sent without PATCHing. |
| `--slot SLOT` | Credential slot (default: configured default). |

### `space delete SPACE_ID`

Delete a space (prompts for confirmation unless `-y`).

```bash
uqadm space delete space_old123
uqadm space delete space_old123 --slot prod -y
uqadm space delete space_old123 --dry-run
```

| Option | Description |
|--------|-------------|
| `SPACE_ID` | Space id or `https://` URL containing `/space/<id>`. |
| `--slot SLOT` | Credential slot (default: configured default slot). |
| `-y`, `--yes` | Skip the confirmation prompt. |
| `--dry-run` | Fetch and describe what would be deleted, without deleting. |

---

## `uqadm kb`

Manage **knowledge-base folders**: create paths, sync/download/remove files and
folders, grant group access, and read or set folder ingestion config (via
``unique_sdk.Folder`` and ``Content.delete`` for targeted file removal).

```bash
uqadm kb --help
```

### `kb mkdir`

Create one or more folder paths. Pass paths as arguments, with ``--path`` (repeatable), and/or ``--paths-file`` (one path per line; ``#`` starts a comment). Use ``--parent-scope-id`` with relative path segments instead of absolute ``paths``.

```bash
uqadm kb mkdir /Dept/HR /Dept/Legal
uqadm kb mkdir --paths-file folders.txt --slot qa
uqadm kb mkdir rel/sub --parent-scope-id scope_parent123
uqadm kb mkdir /Private --no-inherit-access
```

| Option | Description |
|--------|-------------|
| `--paths-file` | Text file of paths (one per line). |
| `--path` | Single path (repeatable). |
| `--parent-scope-id` | Use ``relativePaths`` under this scope. |
| `--inherit-access / --no-inherit-access` | Passed to ``Folder.create_paths`` (default: inherit). |
| `--slot SLOT` | Credential slot. |

### `kb sync`

Upload the contents of a local folder into a KB folder scope. Files matched by
filename are **replaced**; new files are **created**. This is an **upsert, not a
destructive mirror**: files that exist in the upstream KB scope but are missing
locally are **left untouched — `kb sync` never deletes remote files**. Requires
exactly one of ``--folder-path`` or ``--scope-id``. Without ``--recursive`` only
top-level files are synced; with it, subdirectories are recreated as child folders.

By default, replaced files **archive prior blobs** (restorable via
``unique-cli versions`` / ``restore-version``). Content ids are unchanged on
replace (upsert by filename key). Pass ``--no-version`` to skip archiving
(legacy overwrite behavior). Once a content row has been versioned, the platform
treats versioning as sticky — later uploads keep archiving even with
``--no-version``.

```bash
uqadm kb sync ./docs --folder-path /Dept/HR
uqadm kb sync ./docs --folder-path /Dept/HR -r --dry-run
uqadm kb sync ./docs --scope-id scope_abc -r --slot qa
uqadm kb sync ./docs --scope-id scope_abc --no-version
```

| Option | Description |
|--------|-------------|
| `--folder-path` | Target KB folder path (mutually exclusive with ``--scope-id``). |
| `--scope-id` | Target folder scope id (mutually exclusive with ``--folder-path``). |
| `-r`, `--recursive` | Recurse into subdirectories, mirroring them as child KB folders. |
| `--dry-run` | Show planned uploads without writing anything. |
| `--no-version` | Upload without archiving prior blobs. |
| `--slot SLOT` | Credential slot. |

Extensions that the OS `mimetypes` database cannot resolve (common on macOS for
``.md``, ``.xsd``, etc.) are registered with curated text/doc MIME types so they
sync correctly. Any file whose MIME type still cannot be determined is reported
as failed and skipped (it is never uploaded), and the command exits non-zero.

### `kb download`

Download files from a KB folder scope into a local directory. Requires exactly
one of ``--folder-path`` or ``--scope-id``. Without ``--recursive`` only
top-level files are downloaded; with it, subfolders are recreated as child
directories under ``LOCAL_DIR``.

```bash
uqadm kb download ./out --folder-path /Dept/HR
uqadm kb download ./out --folder-path /Dept/HR -r --dry-run
uqadm kb download ./out --scope-id scope_abc -r --slot qa
```

| Option | Description |
|--------|-------------|
| `--folder-path` | Source KB folder path (mutually exclusive with ``--scope-id``). |
| `--scope-id` | Source folder scope id (mutually exclusive with ``--folder-path``). |
| `-r`, `--recursive` | Recurse into subfolders, mirroring them as local subdirectories. |
| `--dry-run` | Show planned downloads without writing anything. |
| `--slot SLOT` | Credential slot. |

### `kb rm`

Delete a KB folder or specific files within it, using ``Folder.delete`` /
``Content.delete``. Requires exactly one of ``--folder-path`` or ``--scope-id``.
With one or more ``--file`` options (matched by key, repeatable) only those
files are deleted; otherwise the whole folder is removed. Deleting a **non-empty
folder requires ``--recursive``** (it refuses otherwise). Unless ``--yes`` is
given you are **prompted to confirm**; ``--dry-run`` prints the plan without
deleting anything.

```bash
uqadm kb rm --folder-path /Dept/HR --file old.pdf
uqadm kb rm --scope-id scope_abc -r --dry-run
uqadm kb rm --scope-id scope_abc -r --slot qa -y
```

| Option | Description |
|--------|-------------|
| `--folder-path` | Target KB folder path (mutually exclusive with ``--scope-id``). |
| `--scope-id` | Target folder scope id (mutually exclusive with ``--folder-path``). |
| `--file` | Delete only this file (matched by key) in the scope; repeatable. |
| `-r`, `--recursive` | Delete a non-empty folder and everything under it. |
| `--dry-run` | Show what would be deleted without deleting anything. |
| `-y`, `--yes` | Skip the interactive confirmation prompt. |
| `--slot SLOT` | Credential slot. |

### `kb access grant`

Grant **group** ``READ`` or ``WRITE`` on a folder. By default the change **applies to subfolders** (``applyToSubScopes``); pass ``--no-subfolders`` for this folder only.

```bash
uqadm kb access grant --folder-path /Dept/HR --group grp_1 --permission READ
uqadm kb access grant --scope-id scope_abc --group grp_1 --group grp_2 --permission WRITE --no-subfolders
```

### `kb ingestion get`

Print the ingestion config currently set on a folder scope (``Folder.get_info``).
The platform returns the stored config verbatim — including nested settings such
as ``pdfConfig.imageContentExtraction``, ``metadataExtractionConfig`` and
``chunkingConfiguration`` — and the emitted mapping is exactly what
``kb ingestion set`` consumes, so the two commands round-trip losslessly.
Requires exactly one of ``--folder-path`` or ``--scope-id``.

Without ``-o`` the config goes to **stdout as JSON** and all messages go to
stderr, so the output pipes cleanly (e.g. into ``jq``); with ``-o`` the format
follows the file suffix. A folder with no ingestion config emits ``{}`` plus a
note on stderr.

```bash
uqadm kb ingestion get --folder-path /Dept/HR
uqadm kb ingestion get --scope-id scope_abc --slot qa
uqadm kb ingestion get --folder-path /Dept/HR -o ./ingest.yaml

# Inspect a single key
uqadm kb ingestion get --folder-path /Dept/HR | jq .chunkingConfiguration

# Copy one folder's config onto another
uqadm kb ingestion get --folder-path /Dept/HR -o /tmp/hr.json
uqadm kb ingestion set /tmp/hr.json --folder-path /Dept/Legal --no-subfolders
```

| Option | Description |
|--------|-------------|
| `--folder-path` | Folder path (mutually exclusive with `--scope-id`). |
| `--scope-id` | Folder scope id (mutually exclusive with `--folder-path`). |
| `-o`, `--output PATH` | Write the config to this `.json`/`.yaml`/`.yml` file instead of stdout. |
| `--slot SLOT` | Credential slot (default: configured default). |

### `kb ingestion set CONFIG_FILE`

Apply **folder** ingestion settings from a JSON/YAML file (mapping root) using ``Folder.update_ingestion_config``. Default applies to **subfolders**; use ``--no-subfolders`` for this folder only.

The file **replaces** the stored config rather than merging into it: a top-level
key missing from the file is deleted from the folder, and the same applies key
by key to ``metadata``. To change one value, start from ``kb ingestion get``
output instead of writing a partial file.

```bash
uqadm kb ingestion set ./folder-ingest.json --folder-path /Dept/HR
uqadm kb ingestion set ./ingest.yaml --scope-id scope_abc --slot qa
```

### `kb ingestion model-replace`

Replace one language model with another in **folder ingestion configs**. Reads
the folder's current ``ingestionConfig`` (``Folder.get_info``), rewrites every
model-bearing key whose value equals ``--from-model``
(``vttConfig.languageModel``, ``metadataExtractionConfig.languageModel``,
``pdfConfig``/``htmlConfig`` ``imageContentExtraction.languageModel``,
``chunkingConfiguration.model``), and writes it back via
``Folder.update_ingestion_config``.

After each write the config is **re-read and verified**, so a key the platform
rejected or did not store fails loudly with a non-zero exit instead of passing
silently.

``--to-model`` accepts either a **model name** or a **path to a JSON/YAML file**
holding language-model info, exactly as in ``space model-replace``.

**Input** is exactly one of: ``--folder-path`` / ``--scope-id`` (live folder),
``-f FILE`` (local config file), or ``--all`` (interactive walk over every KB
folder in the slot, prompting ``y/n/a/q`` per matching folder). With ``-o`` the
rewritten config is written to a file and no API write happens.

Only ``--all`` prompts. Naming a single ``--folder-path`` / ``--scope-id``
applies the update immediately — use ``--dry-run`` (or ``-o``) first to review
the matched paths.

Unlike ``ingestion set``, the update applies to **this folder only** unless
``--subfolders`` is passed — pushing one folder's rewritten config down would
clobber subfolder-specific settings. ``--all`` rewrites each folder from its
own current config instead.

```bash
# Single folder, preview
uqadm kb ingestion model-replace --folder-path /Dept/HR \
  --from-model AZURE_GPT_4o_2024_0806 --to-model AZURE_GPT_5_2025_0807 --dry-run

# Single folder, update in place (verified after write)
uqadm kb ingestion model-replace --scope-id scope_abc --from-model OLD --to-model NEW

# Rewrite a local ingestion config file
uqadm kb ingestion model-replace -f ./ingest.yaml --from-model OLD --to-model NEW \
  -o ingest.migrated.yaml

# Interactively walk every KB folder in a slot
uqadm kb ingestion model-replace --all --slot prod --from-model OLD --to-model NEW
```

| Option | Description |
|--------|-------------|
| `--folder-path` | Folder path (mutually exclusive with `--scope-id`, `--file`, `--all`). |
| `--scope-id` | Folder scope id (mutually exclusive with `--folder-path`, `--file`, `--all`). |
| `-f`, `--file FILE` | Local ingestion config (`.json`/`.yaml`/`.yml`) to rewrite instead of a live folder. |
| `--all` | Walk every KB folder in the slot, prompting per matching folder (`y`/`n`/`a`/`q`). |
| `--from-model NAME` | Model name currently in the ingestion config to replace. Required. |
| `--to-model NAME\|FILE` | Replacement model: a model name, or a path to a `.json`/`.yaml`/`.yml` file with language-model info (must include `name`). Required. |
| `-o`, `--output PATH` | Write the rewritten config to a file instead of updating the platform. |
| `--subfolders` | Push the rewritten config to descendant folders too (`applyToSubScopes`). Default: this folder only. |
| `--dry-run` | Print matched paths and planned updates without writing anything — no API call and no output file, even with `-o`. |
| `-y`, `--yes` | With `--all`: apply without prompting. |
| `--slot SLOT` | Credential slot (default: configured default). |

---

## `uqadm chat`

Send messages to an assistant and inspect conversation history.

```bash
uqadm chat --help
```

### `chat send ASSISTANT_ID`

Send a message and print the reply. The `chat_id` of the thread is always shown in the framed header so you can copy it for follow-up messages.

**Message input** (pick one):

| Method | Flag / Usage |
|--------|-------------|
| Inline text | `--text "your message"` |
| File | `--file ./prompt.txt` |
| stdin | `echo "message" \| uqadm chat send ASSISTANT_ID` |

**Output format:**

```
────────────────────────────────────────────────────────────
chat_id: chat_xyz789
────────────────────────────────────────────────────────────
Here are the latest F1 headlines...
────────────────────────────────────────────────────────────
References
  [1] Formula 1 Official  https://www.formula1.com/...
────────────────────────────────────────────────────────────
Evaluation
  APPROVED · accurate
  The answer correctly summarizes recent race results.
────────────────────────────────────────────────────────────
```

References and Evaluation sections only appear when the response includes them. Use `--json` to get the full raw `Space.Message` object instead.

**Examples:**

```bash
# First message — starts a new thread
uqadm chat send asst_abc123 --text "What are the latest F1 news?"

# Follow-up in the same thread
uqadm chat send asst_abc123 --text "Tell me more about the race" --chat-id chat_xyz789

# Specific slot
uqadm chat send asst_abc123 --text "Hello" --slot prod

# Force a tool
uqadm chat send asst_abc123 --text "Search the web" --tool web_search

# Force multiple tools
uqadm chat send asst_abc123 --text "Run and explain" --tool code_interpreter --tool web_search

# Message from a file
uqadm chat send asst_abc123 --file ./prompt.txt

# Piped from stdin
echo "Summarize this" | uqadm chat send asst_abc123

# Increase timeout (default: 300 s)
uqadm chat send asst_abc123 --text "Complex question" --max-wait 600

# Raw JSON output
uqadm chat send asst_abc123 --text "Hello" --json
```

**All options:**

| Option | Default | Description |
|--------|---------|-------------|
| `ASSISTANT_ID` | — | The assistant to message. |
| `--slot SLOT` | default slot | Credential slot. |
| `--text TEXT` | — | Inline message text. |
| `--file PATH` | — | Read message from file. |
| `--chat-id ID` | — | Continue an existing chat thread. |
| `--tool NAME` | — | Force a tool (repeatable). |
| `--max-wait SECS` | `300` | Timeout waiting for a response. |
| `--poll-interval SECS` | `1.0` | Polling interval between status checks. |
| `--stop-on` | `stoppedStreamingAt` | Stop condition: `stoppedStreamingAt` or `completedAt`. |
| `--json` | off | Print raw `Space.Message` JSON. |

### `chat history CHAT_ID`

Fetch and display conversation history.

By default, shows the **selected token window** (last N messages within a token budget) — useful for reviewing context. Use `--full` to see every message in the thread exactly as stored.

```bash
uqadm chat history chat_xyz789
uqadm chat history chat_xyz789 --full
uqadm chat history chat_xyz789 --full --json
uqadm chat history chat_xyz789 --slot prod
```

Output uses the same framed style as `chat send`, with each message in its own block labeled `You` or `Assistant`.

| Option | Default | Description |
|--------|---------|-------------|
| `CHAT_ID` | — | Chat thread to fetch. |
| `--slot SLOT` | default slot | Credential slot. |
| `--full` | off | Show all messages (bypasses token-window selection). |
| `--json` | off | Print raw message list as JSON. |
| `--max-tokens INT` | `8000` | Token budget for the windowed view. |
| `--percent FLOAT` | `0.15` | Fraction of `max-tokens` allocated to history. |
| `--max-messages INT` | `4` | Maximum number of messages in the windowed view. |

---

## Python module entry

```bash
python -m uqadm --help
```

---

## Related

- **`unique-cli`** — SDK file-explorer CLI (`unique_sdk`); unchanged by `uqadm`.
- **`unique_sdk.cli.config`** — `load_config()` and env semantics shared with `unique-cli`.
