← Back to Plugins
Voice

Jarvis Ui

vinodrajvtr By vinodrajvtr 👁 5 views ▲ 0 votes

JARVIS UI is a plugin-based terminal UI framework that gives any AI agent a common interface instead of each one reinventing the same plumbing. Every agent ends up solving the same four problems: discovering tools, registering UI components, managing a plugin lifecycle, and dispatching calls.

GitHub

Install

pip install .

README

# JARVIS UI

A plugin-based terminal UI framework that lets **any** AI agent plug into a common interface.

Write your agent's tools and UI components once; JARVIS handles discovery, lifecycle, and dispatch. The core is **standard library only** — it adds no dependencies to your agent's environment.

```
plugins discover -> load -> get_tools() / get_ui_components() -> dispatch
```

---

## Why

Every agent ends up reinventing the same plumbing: a way to discover tools, register UI components, manage a plugin lifecycle, and dispatch calls. This framework is that plumbing, and nothing else. Your agent supplies the domain logic.

## Status

Alpha. The core and all three bundled example plugins load and execute. The interface may still change.

---

## Install

```bash
git clone https://github.com/vinodrajvtr/jarvis-ui.git
cd jarvis-ui
python3 main.py --help
```

Or as a package:

```bash
pip install .
```

Requires Python 3.8+. No third-party dependencies.

---

## Quick start

Run the bundled examples:

```bash
# See what's available
python3 main.py tools list
python3 main.py ui list

# Load an agent plugin (note: --plugin-dir points at the PARENT folder)
python3 main.py --plugin-dir examples/hermes-plugin plugins load hermes
```

Expected output:

```
  Registered tool: hermes_chat (from hermes)
  Registered UI component: hermes_query_input (from hermes)
Plugin loaded: hermes v0.1.0
Successfully loaded: hermes v0.1.0
  Tools: 3
  UI Components: 4
```

---

## Add your own agent

### 1. Create the directory layout

The loader looks for `<plugin-dir>/<PLUGIN_NAME>/plugin.py`. The **folder name becomes the plugin name**, and `--plugin-dir` points at its **parent**.

```
my-agent-plugin/          <- point --plugin-dir here
  my-agent/               <- folder name = plugin name
    plugin.py             <- filename must be exactly this
```

### 2. Write the plugin

```python
import os
import sys

# core/ holds plugin_manager.py. Put it on sys.path so the flat import
# resolves -- there is no `jarvis_ui` package to import from.
sys.path.insert(
    0, os.path.join(os.path.dirname(__file__), "..", "..", "core")
)
from plugin_manager import PluginInterface


class MyAgentPlugin(PluginInterface):
    def __init__(self):
        self.name = "my-agent"
        self.version = "0.1.0"
        self.context = None

    def on_load(self, agent_context):
        """Called when the plugin loads. agent_context carries config, keys,
        model preferences, workspace path."""
        self.context = agent_context

    def on_unload(self):
        """Release connections, threads, handles."""
        self.context = None

    def get_tools(self):
        return [
            {
                "name": "my_agent_ask",
                "description": "Send a prompt to MyAgent",
                "parameters": {"prompt": "string"},
                "handler": self._ask,
            }
        ]

    def get_ui_components(self):
        return [
            {
                "name": "my_agent_input",
                "type": "input",
                "label": "MyAgent:",
                "placeholder": "Enter query...",
                "handler": self._ask,
            }
        ]

    def get_config(self):
        return {"setting": "default"}

    def validate_config(self, config):
        return isinstance(config, dict)

    def _ask(self, prompt=""):
        # Replace this with a real call into your agent.
        return {"success": True, "result": prompt}
```

### 3. Load it

```bash
python3 main.py --plugin-dir my-agent-plugin plugins load my-agent
```

```
  Registered tool: my_agent_ask (from my-agent)
  Registered UI component: my_agent_input (from my-agent)
Successfully loaded: my-agent v0.1.0
  Tools: 1
  UI Components: 1
```

That's it. No registration call, no manifest, no framework edits.

---

## Interface

| Method | Purpose |
|---|---|
| `on_load(agent_context)` | Initialize; receives config, keys, workspace |
| `on_unload()` | Clean up resources |
| `get_tools()` | Return tool definitions (`name`, `description`, `parameters`, `handler`) |
| `get_ui_components()` | Return component definitions (`name`, `type`, `label`, `handler`) |
| `get_config()` | Return default configuration |
| `validate_config(config)` | Return `True` if config is usable |

Component `type` is one of `input`, `output`, `button`, `selector`.

---

## CLI

```
python3 main.py [--plugin-dir DIR] <command>

  plugins list | discover | load <name> | unload <name> | reload [name]
  tools   list | execute <tool> [json-params]
  ui      list | create <type> [--label ...] [--placeholder ...]
```

Global `--plugin-dir` sets where plugins are discovered from. It defaults to
`core/plugins/`, which is usually not where yours live — pass it explicitly.

---

## Example plugins

| Plugin | Location | Shows |
|---|---|---|
| hermes | `examples/hermes-plugin/hermes/` | Talking to a Hermes agent over its API |
| openclaw | `examples/openclaw-plugin/openclaw/` | Wrapping a CLI-driven agent |
| claude-code | `examples/claude-code-plugin/claude-code/` | Subprocess integration with availability checks |

Each declares 3 tools and 4 UI components.

---

## Notes and gotchas

- **`--plugin-dir` points at the parent.** Passing the plugin folder itself finds nothing.
- **The import is flat** (`from plugin_manager import PluginInterface`), not `from jarvis_ui.core...`. `core/` goes on `sys.path`.
- **There is no `register_plugin()`.** Discovery is automatic: the loader imports `plugin.py` and instantiates the `PluginInterface` subclass it finds.
- **Name matching is normalized.** Non-alphanumerics are stripped and case is folded, so a `my-agent` folder matches a `MyAgentPlugin` class.
- **Subclass `PluginInterface`.** `load_plugin` validates with `isinstance()`, so a duck-typed class that doesn't subclass it will be rejected.
- **Set `name`/`version` in `__init__`.** They're read from the instance after construction.

---

## Project layout

```
jarvis-ui/
  core/                    plugin_manager, tool_integration, ui_components
  examples/                three working agent plugins
  docs/framework_docs.md   full API reference
  main.py                  CLI entry point
  jarvis_tui.py            curses front-end for the hermes plugin
  test_plugin.py           smoke test
```

---

## License

Apache-2.0. See [LICENSE](LICENSE).
voice

Comments

Sign in to leave a comment

Loading comments...