> ## Documentation Index
> Fetch the complete documentation index at: https://praison.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# tools add

> Install a tool package from the CLI and verify its tools became available

One command installs a tool package and tells you exactly which tools became available — or exits non-zero if none did.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    Cmd["🖥️ praisonai tools add pkg"] --> Install["📦 pip / uv install"]
    Install --> Resolver["🔍 fresh ToolResolver"]
    Resolver --> Diff{"🆕 New tools?"}
    Diff -->|yes| Table["✅ Available table"]
    Diff -->|no| Fail["🛑 exit 1"]

    classDef input fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef process fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef output fill:#10B981,stroke:#7C90A0,color:#fff
    classDef error fill:#8B0000,stroke:#7C90A0,color:#fff

    class Cmd input
    class Install,Resolver,Diff process
    class Table output
    class Fail error
```

Previously a package could `pip install` cleanly yet expose no resolvable tools — a silent failure. `tools add` turns that into a surfaced non-zero exit.

## Quick Start

<Steps>
  <Step title="Install a tool package">
    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    praisonai tools add praisonai-my-tools
    ```

    <Note>`praisonai-my-tools` is a placeholder — replace it with a real pip requirement spec.</Note>
  </Step>

  <Step title="Upgrade an installed package">
    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    praisonai tools add praisonai-my-tools --upgrade
    ```
  </Step>

  <Step title="Verify without installing">
    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    praisonai tools add praisonai-my-tools --dry-run
    ```

    `--dry-run` skips installation and reports whether any **new** tool would resolve from what's already installed.
  </Step>
</Steps>

<Warning>
  **Enable code execution during install first.** `praisonai tools add` refuses to install a tool package when the source is a local file path, a `github:` reference, **or a bare package name** unless `PRAISONAI_ALLOW_LOCAL_TOOLS=true` is set. All three branches run user code — a local file executes on load, a `github:` fetch downloads then loads, and a bare package name is resolved on `sys.path` and its `__init__` runs at import time. The gate is the same one that governs `tools.py` autoload (see [Security Environment Variables](/docs/features/security-environment-variables#praisonai_allow_local_tools)). A published `pip install`-style spec that is already installed and is only being resolved by name goes through the same `import`, so the gate applies there too.
</Warning>

On refusal of a bare package name, the CLI prints:

```text theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
Refusing to add package tools: set PRAISONAI_ALLOW_LOCAL_TOOLS=true to enable
(importing a package runs its code; same opt-in the runtime loader requires).
```

and returns `{"success": False, "error": "PRAISONAI_ALLOW_LOCAL_TOOLS not set"}`.

***

## How It Works

The command snapshots resolvable tools, installs, builds a **fresh** resolver, then reports the diff.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant User
    participant CLI as tools add
    participant Installer as pip / uv
    participant Resolver as ToolResolver

    User->>CLI: praisonai tools add pkg
    CLI->>Resolver: snapshot list_available() (before)
    alt not --dry-run
        CLI->>Installer: install pkg
        Installer-->>CLI: exit code
    end
    CLI->>Resolver: construct fresh ToolResolver()
    Resolver-->>CLI: list_available() (after)
    CLI->>User: table of new tools OR exit 1
```

| Step | What happens |
| - | - |
| **Snapshot** | Records `set(ToolResolver().list_available().keys())` |
| **Install** | Skipped on `--dry-run`; otherwise runs the resolved installer command |
| **Refresh** | Constructs a **fresh** `ToolResolver()` — see the note below |
| **Diff** | `sorted(after − before)` — new names become table rows |

<Warning>
  Discovery constructs a **fresh** `ToolResolver()`. Calling `resolver.invalidate()` alone is **not** enough — it only clears the per-name resolution cache, not instance-level availability caches (like whether `praisonai_tools` is importable). A new instance re-evaluates those against the now-updated environment.
</Warning>

***

## Security

* **No code execution for the `github:` inspection path.** The `github:` download inspects a candidate `tools.py` with `ast.parse` — it does **not** execute that downloaded file. But the fetch itself and the load run under the same opt-in.
* **Package-name branch executes `__init__` on resolve.** `praisonai tools add <name>` when `<name>` is neither a local path nor a `github:` reference calls `importlib.import_module(<name>)` to inspect the package. That runs its top-level code — which is exactly why it is now gated behind `PRAISONAI_ALLOW_LOCAL_TOOLS=true`.
* **`github:` downloads are locked down**: HTTPS raw URLs only (`raw.githubusercontent.com` / `raw.github.com`), no redirect follow, a **1 MiB** size cap, and the on-disk filename is derived from the URL's last path segment as a safe single basename (no directory traversal).
* **No `--sha256` flag** — the PR that hardened this path deliberately left it out to avoid a new CLI knob. Rely on PyPI signing or your own out-of-band verification if you need a pinned artefact.

***

## Options

| Argument / flag | Type | Default | Description |
| - | - | - | - |
| `package` | positional `str` | *(required)* | pip requirement spec — anything `pip install` / `uv pip install` accepts (name, `name==1.2.3`, VCS URL, path, extras). Any source (local file, `github:` reference, **or bare package name resolved on `sys.path`**) requires `PRAISONAI_ALLOW_LOCAL_TOOLS=true` — the package-name branch runs `importlib.import_module(name)`, which executes the package's `__init__`. `github:` downloads are HTTPS raw URLs only, capped at 1 MiB. |
| `--dry-run` | flag | `False` | Skip installation; run discovery only. Exits non-zero if nothing new is discovered |
| `--upgrade`, `-U` | flag | `False` | Append `--upgrade` so an already-installed package is upgraded |
| `--global` | flag | `False` | Install into the ambient/system environment instead of the CLI interpreter |

***

## How the Installer Is Chosen

Installation is pinned to the interpreter running the CLI so the package lands where discovery will look. See [plugins add → How the Installer Is Chosen](/docs/features/plugins-add#how-the-installer-is-chosen) for the shared decision diagram — both commands use the same `_resolve_installer()` logic.

***

## Exit Codes

| Situation | Exit code |
| - | - |
| At least one new tool became available | `0` |
| Installer command returned non-zero | `1` |
| Install succeeded but nothing new discovered | `1` |
| `--dry-run` and nothing new discovered | `1` |

***

## Output Shape

A successful run prints a Rich table titled `Registered N tool(s) from <package>`, with the tool's resolution source:

```text theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
        Registered 2 tools from praisonai-my-tools
┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Tool Name      ┃ Source     ┃
┡━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ fetch_report   │ external   │
│ send_invoice   │ registered │
└────────────────┴────────────┘
```

Source values come from `ToolResolver.list_available_sources()` and are one of:

| Source | Origin |
| - | - |
| `local` | Project `tools.py` |
| `builtin` | `praisonaiagents.tools` |
| `external` | `praisonai-tools` package |
| `registered` | Wrapper registry / entry-point plugins |

On `--dry-run` the title verb is `Discovered` instead of `Registered`.

***

## Common Patterns

<Tabs>
  <Tab title="Default (pin to CLI interpreter)">
    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    praisonai tools add praisonai-my-tools
    ```

    When `uv` is present, the package is pinned to the CLI interpreter so a fresh resolver sees it.
  </Tab>

  <Tab title="Global install">
    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    praisonai tools add praisonai-my-tools --global
    ```

    Installs into the ambient/system environment (`uv pip install --system`, or plain `pip install`).
  </Tab>

  <Tab title="CI verification">
    ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
    praisonai tools add praisonai-my-tools --dry-run
    ```

    Fails the job (exit 1) if the package no longer exposes resolvable tools after another change.
  </Tab>
</Tabs>

***

## Best Practices

<AccordionGroup>
  <Accordion title="Construct a fresh resolver in wrapper scripts">
    If you script installs yourself, build a new `ToolResolver()` after installing — `invalidate()` clears only the per-name cache, not instance-level availability caches.
  </Accordion>

  <Accordion title="Use --dry-run in CI to catch silent breakage">
    A package can install cleanly yet expose no tools. `--dry-run` exits non-zero when discovery finds nothing new.
  </Accordion>

  <Accordion title="Prefer the default over --global">
    The default pins to the CLI interpreter so the tools are resolvable by the same process. Reach for `--global` only when you want the system environment.
  </Accordion>

  <Accordion title="Inspect after installing">
    Run `praisonai tools list` to see every resolvable tool and its source, not just what this command added.
  </Accordion>
</AccordionGroup>

***

## Related

<CardGroup cols={2}>
  <Card title="plugins add" icon="download" href="/docs/features/plugins-add">
    Install a plugin package and verify it registered
  </Card>

  <Card title="Tool Source Registry" icon="puzzle-piece" href="/docs/features/tool-source-registry">
    Plug third-party tool sources via entry points
  </Card>

  <Card title="Tool Resolver" icon="wrench" href="/docs/features/tool-resolver">
    Single source of truth for loading tools
  </Card>

  <Card title="Tool Discovery Order" icon="list-tree" href="/docs/features/tool-discovery-order">
    How Agent resolves tool names at runtime
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.