orchid-cli 1.2.0__tar.gz → 1.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/CHANGELOG.md +26 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/PKG-INFO +98 -23
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/README.md +96 -21
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/orchid_cli/auth/token_store.py +5 -4
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/orchid_cli/commands/index.py +5 -4
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/pyproject.toml +2 -2
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/.editorconfig +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/.github/workflows/ci.yml +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/.gitignore +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/.gitlint +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/.pre-commit-config.yaml +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/AGENTS.md +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/CLAUDE.md +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/CONTRIBUTING.md +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/LICENSE +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/icon.svg +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/orchid_cli/__init__.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/orchid_cli/_output.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/orchid_cli/_typer_async.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/orchid_cli/auth/__init__.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/orchid_cli/auth/config.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/orchid_cli/auth/flow.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/orchid_cli/auth/middleware.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/orchid_cli/auth/oidc.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/orchid_cli/auth/pkce.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/orchid_cli/bootstrap.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/orchid_cli/commands/__init__.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/orchid_cli/commands/_chat_helpers.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/orchid_cli/commands/_chat_interactive.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/orchid_cli/commands/_chat_send.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/orchid_cli/commands/_session.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/orchid_cli/commands/_skill_guardrails.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/orchid_cli/commands/_skill_md.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/orchid_cli/commands/_skill_text.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/orchid_cli/commands/_skill_tools.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/orchid_cli/commands/_tool_metadata.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/orchid_cli/commands/auth.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/orchid_cli/commands/chat.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/orchid_cli/commands/config.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/orchid_cli/commands/mcp.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/orchid_cli/commands/skill.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/orchid_cli/main.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/orchid_cli/slash_commands.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/tests/conftest.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/tests/fixtures/__init__.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/tests/fixtures/fake_tools_dates.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/tests/fixtures/fake_tools_math.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/tests/test_auth.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/tests/test_bootstrap.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/tests/test_bootstrap_warm.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/tests/test_browser_opener.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/tests/test_chat_builtin_slash.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/tests/test_commands_chat.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/tests/test_commands_config.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/tests/test_commands_skill.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/tests/test_middleware_seams.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/tests/test_output.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/tests/test_pkce.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/tests/test_resolve_session.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/tests/test_slash_commands.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/tests/test_token_store_security.py +0 -0
- {orchid_cli-1.2.0 → orchid_cli-1.3.0}/tests/test_tool_metadata.py +0 -0
|
@@ -2,6 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- version list -->
|
|
4
4
|
|
|
5
|
+
## v1.3.0 (2026-05-05)
|
|
6
|
+
|
|
7
|
+
### Chores
|
|
8
|
+
|
|
9
|
+
- Bump `orchid-ai` dependency to >=1.6.0 in `orchid-cli` and `orchid-api`
|
|
10
|
+
([`2aaedaf`](https://github.com/gadz82/orchid-cli/commit/2aaedafb417294f1780d80464b469467656fcd5d))
|
|
11
|
+
|
|
12
|
+
### Documentation
|
|
13
|
+
|
|
14
|
+
- Update Orchid API and MCP gateway README
|
|
15
|
+
([`9ff4750`](https://github.com/gadz82/orchid-cli/commit/9ff475005b06857afbc215b68c573b1a8c11a750))
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
- **cli**: Enable RecursiveIngestion for RAG index workflows
|
|
20
|
+
([`14bf9d4`](https://github.com/gadz82/orchid-cli/commit/14bf9d49b1eb6e5bf613497881765a0554d84f26))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
## v1.2.1 (2026-05-04)
|
|
24
|
+
|
|
25
|
+
### Bug Fixes
|
|
26
|
+
|
|
27
|
+
- Broken unit tests by ensuring lock.
|
|
28
|
+
([`64f59fa`](https://github.com/gadz82/orchid-cli/commit/64f59fa71f6efe7f2a5fd51c35ea2fb48f6cf8c4))
|
|
29
|
+
|
|
30
|
+
|
|
5
31
|
## v1.2.0 (2026-04-29)
|
|
6
32
|
|
|
7
33
|
### Bug Fixes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: orchid-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Orchid CLI — command-line interface for the Orchid agent framework
|
|
5
5
|
Project-URL: Homepage, https://github.com/gadz82/orchid
|
|
6
6
|
Project-URL: Repository, https://github.com/gadz82/orchid
|
|
@@ -26,7 +26,7 @@ Classifier: Typing :: Typed
|
|
|
26
26
|
Requires-Python: >=3.11
|
|
27
27
|
Requires-Dist: httpx>=0.28.0
|
|
28
28
|
Requires-Dist: langchain-core>=0.3.0
|
|
29
|
-
Requires-Dist: orchid-ai>=1.
|
|
29
|
+
Requires-Dist: orchid-ai>=1.6.0
|
|
30
30
|
Requires-Dist: pydantic-settings>=2.7.0
|
|
31
31
|
Requires-Dist: pyyaml>=6.0
|
|
32
32
|
Requires-Dist: rich>=13.0
|
|
@@ -46,9 +46,17 @@ Description-Content-Type: text/markdown
|
|
|
46
46
|
|
|
47
47
|
<h1 align="center">Orchid CLI</h1>
|
|
48
48
|
|
|
49
|
-
Command-line interface for the [Orchid](
|
|
49
|
+
Command-line interface for the [Orchid](../orchid/) multi-agent AI framework.
|
|
50
50
|
|
|
51
|
-
Provides terminal access to all chat operations, configuration validation, RAG indexing, and Claude Code skill generation. Mirrors the full functionality of [orchid-api](../orchid-api) but runs locally with no server, Docker, or external database required (defaults to SQLite).
|
|
51
|
+
Provides terminal access to all chat operations, configuration validation, RAG indexing, MCP server authorisation, and Claude Code skill generation. Mirrors the full functionality of [orchid-api](../orchid-api/) but runs locally with no server, Docker, or external database required (defaults to SQLite).
|
|
52
|
+
|
|
53
|
+
## Why use the CLI
|
|
54
|
+
|
|
55
|
+
- **Embedded workflows** — the CLI runs the same Orchid runtime as the API but in-process. Useful for batch jobs, scripted pipelines, and offline environments.
|
|
56
|
+
- **Identical config surface** — point at any `orchid.yml` / `agents.yaml` pair (basketball, restaurant, helpdesk, your own) and the CLI behaves the way orchid-api does, minus the HTTP layer.
|
|
57
|
+
- **No infrastructure required** — defaults to SQLite chat storage and the in-process vector reader. Add Qdrant / Postgres only when you need them.
|
|
58
|
+
- **Plugin extensible** — register custom subcommands via Python entry points without forking.
|
|
59
|
+
- **Skill export** — turn an `agents.yaml` into a set of Claude Code skill folders so the same agents are usable from `claude` directly.
|
|
52
60
|
|
|
53
61
|
## Installation
|
|
54
62
|
|
|
@@ -75,6 +83,30 @@ orchid chat create -c orchid.yml -t "My Chat"
|
|
|
75
83
|
orchid chat send <chat_id> "Hello!" -c orchid.yml
|
|
76
84
|
```
|
|
77
85
|
|
|
86
|
+
## Common workflows
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
# 1) Try a built-in example with no setup
|
|
90
|
+
orchid chat interactive -c examples/basketball/orchid.yml
|
|
91
|
+
|
|
92
|
+
# 2) Validate every example (CI-friendly)
|
|
93
|
+
for f in examples/**/agents.yaml examples/**/config/agents.yaml; do
|
|
94
|
+
orchid config validate "$f"
|
|
95
|
+
done
|
|
96
|
+
|
|
97
|
+
# 3) Pre-seed RAG with internal documentation
|
|
98
|
+
orchid index dir ./docs/internal -n knowledge_base -c orchid.yml --pattern '*.md'
|
|
99
|
+
|
|
100
|
+
# 4) Export agents as Claude Code skills for ad-hoc usage
|
|
101
|
+
orchid skill generate examples/restaurant/config/agents.yaml -o ~/.claude/skills
|
|
102
|
+
|
|
103
|
+
# 5) Drive a non-interactive chat from a shell script
|
|
104
|
+
chat_id=$(orchid chat create -c orchid.yml -t "batch-$(date +%s)" --json | jq -r .id)
|
|
105
|
+
for q in "$@"; do
|
|
106
|
+
orchid chat send "$chat_id" "$q" -c orchid.yml
|
|
107
|
+
done
|
|
108
|
+
```
|
|
109
|
+
|
|
78
110
|
## Commands
|
|
79
111
|
|
|
80
112
|
### Authentication
|
|
@@ -116,7 +148,7 @@ orchid chat delete <chat_id> -c orchid.yml
|
|
|
116
148
|
orchid chat delete <chat_id> -c orchid.yml --force
|
|
117
149
|
```
|
|
118
150
|
|
|
119
|
-
Chat IDs support **prefix matching**
|
|
151
|
+
Chat IDs support **prefix matching** — type the first few characters of the UUID.
|
|
120
152
|
|
|
121
153
|
### Messaging
|
|
122
154
|
|
|
@@ -128,6 +160,13 @@ orchid chat send <chat_id> "What is LangGraph?" -c orchid.yml
|
|
|
128
160
|
orchid chat send <chat_id> "Explain RAG" -c orchid.yml -m ollama/llama3.2
|
|
129
161
|
```
|
|
130
162
|
|
|
163
|
+
The CLI honours every framework feature exposed by the YAML config:
|
|
164
|
+
|
|
165
|
+
- **Mini-agents** — when an agent has `mini_agent.enabled: true`, the CLI streams the same `mini_agent.{decomposed,started,finished,aggregated}` lifecycle markers the frontend renders, surfaced in the terminal as collapsible sections.
|
|
166
|
+
- **Parallel tools** — `parallel_tools: true` on an agent activates Phase A intra-round parallel dispatch; the CLI doesn't need any special flag.
|
|
167
|
+
- **Prompt customisation** — every `prompt_sections` and `transformer_prompts` override declared in YAML applies inside the CLI just like the API.
|
|
168
|
+
- **Sliding-window summarization** — `supervisor.history_summary_enabled: true` compresses older history before the LLM call. CLI users see the same effect on long chats.
|
|
169
|
+
|
|
131
170
|
### Interactive Mode
|
|
132
171
|
|
|
133
172
|
```bash
|
|
@@ -156,6 +195,8 @@ Slash commands available inside interactive mode:
|
|
|
156
195
|
orchid config validate path/to/agents.yaml
|
|
157
196
|
```
|
|
158
197
|
|
|
198
|
+
Validation runs the full Pydantic schema, the defaults-merger pass, and any custom validators registered via the `OrchidAgentsConfig` plugin hooks. Errors include the exact field path and the offending value.
|
|
199
|
+
|
|
159
200
|
### MCP Server Authorization
|
|
160
201
|
|
|
161
202
|
Manage per-server OAuth for MCP servers that declare `auth.mode: oauth` in `agents.yaml`.
|
|
@@ -257,6 +298,8 @@ orchid skill generate path/to/agents.yaml --zip
|
|
|
257
298
|
|
|
258
299
|
Each agent skill includes a `scripts/` folder with standalone Python scripts that Claude Code can execute directly. Tools from the same source module are grouped into a single script file with a CLI wrapper that accepts `--arg value` arguments.
|
|
259
300
|
|
|
301
|
+
The skill generator pulls parameter metadata from the YAML `tools:` block when present (per ADR-017's parameter declaration), and falls back to Python signature introspection when omitted.
|
|
302
|
+
|
|
260
303
|
## Configuration
|
|
261
304
|
|
|
262
305
|
The `--config` (`-c`) flag points to an `orchid.yml` file:
|
|
@@ -283,6 +326,10 @@ storage:
|
|
|
283
326
|
class: orchid_ai.persistence.sqlite.OrchidSQLiteChatStorage
|
|
284
327
|
dsn: ~/.orchid/chats.db
|
|
285
328
|
|
|
329
|
+
# Startup hook (e.g. seeds RAG, registers custom strategies / tools)
|
|
330
|
+
startup:
|
|
331
|
+
hook: examples.travel-agency.hooks.startup.bootstrap_travel
|
|
332
|
+
|
|
286
333
|
# LangGraph checkpointer (optional) — enables persistent graph state,
|
|
287
334
|
# required for Human-in-the-Loop tool approval
|
|
288
335
|
checkpointer:
|
|
@@ -305,7 +352,7 @@ Chat data is stored in SQLite at `~/.orchid/chats.db` by default. OAuth tokens a
|
|
|
305
352
|
|
|
306
353
|
### Checkpointing
|
|
307
354
|
|
|
308
|
-
The CLI supports LangGraph checkpointers for persistent graph state. This is **required** when any agent uses Human-in-the-Loop (`requires_approval: true` on tools).
|
|
355
|
+
The CLI supports LangGraph checkpointers for persistent graph state. This is **required** when any agent uses Human-in-the-Loop (`requires_approval: true` on tools) or relies on resume-after-interrupt flows.
|
|
309
356
|
|
|
310
357
|
```yaml
|
|
311
358
|
# In orchid.yml
|
|
@@ -328,16 +375,16 @@ The CLI supports **OAuth 2.0 Authorization Code + PKCE** for authenticating with
|
|
|
328
375
|
|
|
329
376
|
### How It Works
|
|
330
377
|
|
|
331
|
-
1. `orchid auth login` opens the system browser to the provider's authorization page
|
|
332
|
-
2. User authenticates and consents
|
|
333
|
-
3. Provider redirects to a temporary `localhost` callback server
|
|
334
|
-
4. CLI exchanges the authorization code for access + refresh tokens (with PKCE verification)
|
|
335
|
-
5. Tokens are stored at `~/.orchid/tokens.json`
|
|
336
|
-
6. All subsequent `orchid chat` commands use the stored token automatically
|
|
378
|
+
1. `orchid auth login` opens the system browser to the provider's authorization page.
|
|
379
|
+
2. User authenticates and consents.
|
|
380
|
+
3. Provider redirects to a temporary `localhost` callback server.
|
|
381
|
+
4. CLI exchanges the authorization code for access + refresh tokens (with PKCE verification).
|
|
382
|
+
5. Tokens are stored at `~/.orchid/tokens.json` with `0o600` permissions.
|
|
383
|
+
6. All subsequent `orchid chat` commands use the stored token automatically.
|
|
337
384
|
|
|
338
385
|
### OIDC Discovery
|
|
339
386
|
|
|
340
|
-
When `issuer` is set in the config, the CLI fetches `{issuer}/.well-known/openid-configuration` to auto-discover `authorization_endpoint` and `token_endpoint`. This is the recommended approach
|
|
387
|
+
When `issuer` is set in the config, the CLI fetches `{issuer}/.well-known/openid-configuration` to auto-discover `authorization_endpoint` and `token_endpoint`. This is the recommended approach — you only need the issuer URL.
|
|
341
388
|
|
|
342
389
|
### Token Refresh
|
|
343
390
|
|
|
@@ -345,22 +392,19 @@ When the access token expires and a refresh token is available, the CLI refreshe
|
|
|
345
392
|
|
|
346
393
|
### Identity Resolution
|
|
347
394
|
|
|
348
|
-
When `identity_resolver_class` is configured, the CLI calls the resolver after login to populate `tenant_key` and `user_id` from the OAuth token. These identity fields are cached in the token file so subsequent commands don't need the resolver. See the [
|
|
395
|
+
When `identity_resolver_class` is configured, the CLI calls the resolver after login to populate `tenant_key` and `user_id` from the OAuth token. These identity fields are cached in the token file so subsequent commands don't need the resolver. See the [`OrchidIdentityResolver` ABC](../orchid/orchid_ai/core/identity.py) for the interface.
|
|
349
396
|
|
|
350
397
|
> **Note:** the CLI is an **independent OAuth client** — it runs its own
|
|
351
398
|
> authorization-code + PKCE dance against the upstream IdP and calls the
|
|
352
399
|
> identity resolver locally. It does NOT use the
|
|
353
400
|
> centralised `/auth/exchange-code` / `/auth/resolve-identity` /
|
|
354
|
-
> `/auth/refresh-token` endpoints that the MCP gateway uses
|
|
355
|
-
> of the [auth-centralisation roadmap](../.knowledge/auth-centralisation.md)).
|
|
401
|
+
> `/auth/refresh-token` endpoints that the MCP gateway uses.
|
|
356
402
|
> The CLI ships with the upstream secret `client_id` baked into its config
|
|
357
|
-
> because it's a desktop app, not a network service.
|
|
358
|
-
> could route the CLI through the same endpoints to remove the
|
|
359
|
-
> CLI-side `client_secret` / userinfo coupling — out of scope today.
|
|
403
|
+
> because it's a desktop app, not a network service.
|
|
360
404
|
|
|
361
405
|
### Dev Fallback
|
|
362
406
|
|
|
363
|
-
When `auth.dev_bypass: true` or `auth.cli` is absent, the CLI uses a dummy token (`cli-token`, tenant=`cli`, user=`cli-user`). This is fully backward compatible
|
|
407
|
+
When `auth.dev_bypass: true` or `auth.cli` is absent, the CLI uses a dummy token (`cli-token`, tenant=`cli`, user=`cli-user`). This is fully backward compatible — existing configs without OAuth continue to work unchanged.
|
|
364
408
|
|
|
365
409
|
## Prerequisites
|
|
366
410
|
|
|
@@ -396,7 +440,7 @@ After `pip install mypackage`, the command is available as `orchid mycommand gre
|
|
|
396
440
|
orchid_cli/
|
|
397
441
|
main.py Typer entry point — registers built-in + plugin subcommands
|
|
398
442
|
bootstrap.py Shared startup: load config, build graph, init storage,
|
|
399
|
-
wire checkpointer (optional)
|
|
443
|
+
wire checkpointer (optional), invoke startup hook
|
|
400
444
|
auth/ OAuth 2.0 authentication (self-contained)
|
|
401
445
|
config.py Provider settings from orchid.yml
|
|
402
446
|
oidc.py Shared OIDC discovery utility
|
|
@@ -415,7 +459,38 @@ orchid_cli/
|
|
|
415
459
|
skill.py Generate Claude Code skills from agents.yaml
|
|
416
460
|
```
|
|
417
461
|
|
|
418
|
-
|
|
462
|
+
`bootstrap.py` mirrors the orchid-api lifespan: load and validate config, build the LangGraph runtime, initialise the storage backend, wire the checkpointer, fire the startup hook (if any). The chat commands then attach an `OrchidAuthContext` per call and enter the supervisor — same primitives orchid-api uses, just without the FastAPI shell.
|
|
463
|
+
|
|
464
|
+
## Embedded mode (using the SDK directly)
|
|
465
|
+
|
|
466
|
+
For applications that need Orchid as a library rather than a CLI:
|
|
467
|
+
|
|
468
|
+
```python
|
|
469
|
+
from orchid_ai import Orchid, OrchidRuntime
|
|
470
|
+
from orchid_ai.config.loader import load_config
|
|
471
|
+
|
|
472
|
+
config = load_config("orchid.yml")
|
|
473
|
+
runtime = OrchidRuntime.from_config(config)
|
|
474
|
+
async with runtime:
|
|
475
|
+
answer = await runtime.ask(
|
|
476
|
+
"Hello!",
|
|
477
|
+
tenant_id="default",
|
|
478
|
+
user_id="me",
|
|
479
|
+
chat_id="chat-1",
|
|
480
|
+
)
|
|
481
|
+
print(answer.text)
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
The CLI uses these primitives internally; embedded users get the same behaviour without the Typer shell. See `examples/embedded-python/` for end-to-end patterns.
|
|
485
|
+
|
|
486
|
+
## Troubleshooting
|
|
487
|
+
|
|
488
|
+
- **`Cannot resolve chat storage class '…'`** — the dotted import path in `storage.class` failed to import. Confirm the package is installed and the module path is reachable from `PYTHONPATH`.
|
|
489
|
+
- **`No module named 'aiosqlite'`** — install the SQLite extra: `pip install orchid-ai[checkpoint-sqlite]`.
|
|
490
|
+
- **OAuth `redirect_uri_mismatch`** — register `http://localhost:<port>/callback` (the port the CLI prints on `auth login`) with your IdP. Some IdPs accept the loopback wildcard `http://127.0.0.1`; others require the literal port.
|
|
491
|
+
- **Tokens stored but `auth status` shows expired** — refresh failed. Inspect `~/.orchid/tokens.json` (chmod 600) and re-run `orchid auth login`.
|
|
492
|
+
- **Slow startup with custom LLM provider** — `bootstrap.py` initialises the chat model lazily, but startup hooks run synchronously. Move heavy work behind `if reader and reader.supports_writes:` guards inside the hook.
|
|
493
|
+
- **`No agents loaded`** — likely missing `agents.config_path` in `orchid.yml`. Inline-config users should switch to `agents:` (see `examples/embedded-python/06_inline_config.py`).
|
|
419
494
|
|
|
420
495
|
## Development
|
|
421
496
|
|
|
@@ -434,4 +509,4 @@ ruff check orchid_cli/
|
|
|
434
509
|
|
|
435
510
|
## License
|
|
436
511
|
|
|
437
|
-
MIT
|
|
512
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -4,9 +4,17 @@
|
|
|
4
4
|
|
|
5
5
|
<h1 align="center">Orchid CLI</h1>
|
|
6
6
|
|
|
7
|
-
Command-line interface for the [Orchid](
|
|
7
|
+
Command-line interface for the [Orchid](../orchid/) multi-agent AI framework.
|
|
8
8
|
|
|
9
|
-
Provides terminal access to all chat operations, configuration validation, RAG indexing, and Claude Code skill generation. Mirrors the full functionality of [orchid-api](../orchid-api) but runs locally with no server, Docker, or external database required (defaults to SQLite).
|
|
9
|
+
Provides terminal access to all chat operations, configuration validation, RAG indexing, MCP server authorisation, and Claude Code skill generation. Mirrors the full functionality of [orchid-api](../orchid-api/) but runs locally with no server, Docker, or external database required (defaults to SQLite).
|
|
10
|
+
|
|
11
|
+
## Why use the CLI
|
|
12
|
+
|
|
13
|
+
- **Embedded workflows** — the CLI runs the same Orchid runtime as the API but in-process. Useful for batch jobs, scripted pipelines, and offline environments.
|
|
14
|
+
- **Identical config surface** — point at any `orchid.yml` / `agents.yaml` pair (basketball, restaurant, helpdesk, your own) and the CLI behaves the way orchid-api does, minus the HTTP layer.
|
|
15
|
+
- **No infrastructure required** — defaults to SQLite chat storage and the in-process vector reader. Add Qdrant / Postgres only when you need them.
|
|
16
|
+
- **Plugin extensible** — register custom subcommands via Python entry points without forking.
|
|
17
|
+
- **Skill export** — turn an `agents.yaml` into a set of Claude Code skill folders so the same agents are usable from `claude` directly.
|
|
10
18
|
|
|
11
19
|
## Installation
|
|
12
20
|
|
|
@@ -33,6 +41,30 @@ orchid chat create -c orchid.yml -t "My Chat"
|
|
|
33
41
|
orchid chat send <chat_id> "Hello!" -c orchid.yml
|
|
34
42
|
```
|
|
35
43
|
|
|
44
|
+
## Common workflows
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# 1) Try a built-in example with no setup
|
|
48
|
+
orchid chat interactive -c examples/basketball/orchid.yml
|
|
49
|
+
|
|
50
|
+
# 2) Validate every example (CI-friendly)
|
|
51
|
+
for f in examples/**/agents.yaml examples/**/config/agents.yaml; do
|
|
52
|
+
orchid config validate "$f"
|
|
53
|
+
done
|
|
54
|
+
|
|
55
|
+
# 3) Pre-seed RAG with internal documentation
|
|
56
|
+
orchid index dir ./docs/internal -n knowledge_base -c orchid.yml --pattern '*.md'
|
|
57
|
+
|
|
58
|
+
# 4) Export agents as Claude Code skills for ad-hoc usage
|
|
59
|
+
orchid skill generate examples/restaurant/config/agents.yaml -o ~/.claude/skills
|
|
60
|
+
|
|
61
|
+
# 5) Drive a non-interactive chat from a shell script
|
|
62
|
+
chat_id=$(orchid chat create -c orchid.yml -t "batch-$(date +%s)" --json | jq -r .id)
|
|
63
|
+
for q in "$@"; do
|
|
64
|
+
orchid chat send "$chat_id" "$q" -c orchid.yml
|
|
65
|
+
done
|
|
66
|
+
```
|
|
67
|
+
|
|
36
68
|
## Commands
|
|
37
69
|
|
|
38
70
|
### Authentication
|
|
@@ -74,7 +106,7 @@ orchid chat delete <chat_id> -c orchid.yml
|
|
|
74
106
|
orchid chat delete <chat_id> -c orchid.yml --force
|
|
75
107
|
```
|
|
76
108
|
|
|
77
|
-
Chat IDs support **prefix matching**
|
|
109
|
+
Chat IDs support **prefix matching** — type the first few characters of the UUID.
|
|
78
110
|
|
|
79
111
|
### Messaging
|
|
80
112
|
|
|
@@ -86,6 +118,13 @@ orchid chat send <chat_id> "What is LangGraph?" -c orchid.yml
|
|
|
86
118
|
orchid chat send <chat_id> "Explain RAG" -c orchid.yml -m ollama/llama3.2
|
|
87
119
|
```
|
|
88
120
|
|
|
121
|
+
The CLI honours every framework feature exposed by the YAML config:
|
|
122
|
+
|
|
123
|
+
- **Mini-agents** — when an agent has `mini_agent.enabled: true`, the CLI streams the same `mini_agent.{decomposed,started,finished,aggregated}` lifecycle markers the frontend renders, surfaced in the terminal as collapsible sections.
|
|
124
|
+
- **Parallel tools** — `parallel_tools: true` on an agent activates Phase A intra-round parallel dispatch; the CLI doesn't need any special flag.
|
|
125
|
+
- **Prompt customisation** — every `prompt_sections` and `transformer_prompts` override declared in YAML applies inside the CLI just like the API.
|
|
126
|
+
- **Sliding-window summarization** — `supervisor.history_summary_enabled: true` compresses older history before the LLM call. CLI users see the same effect on long chats.
|
|
127
|
+
|
|
89
128
|
### Interactive Mode
|
|
90
129
|
|
|
91
130
|
```bash
|
|
@@ -114,6 +153,8 @@ Slash commands available inside interactive mode:
|
|
|
114
153
|
orchid config validate path/to/agents.yaml
|
|
115
154
|
```
|
|
116
155
|
|
|
156
|
+
Validation runs the full Pydantic schema, the defaults-merger pass, and any custom validators registered via the `OrchidAgentsConfig` plugin hooks. Errors include the exact field path and the offending value.
|
|
157
|
+
|
|
117
158
|
### MCP Server Authorization
|
|
118
159
|
|
|
119
160
|
Manage per-server OAuth for MCP servers that declare `auth.mode: oauth` in `agents.yaml`.
|
|
@@ -215,6 +256,8 @@ orchid skill generate path/to/agents.yaml --zip
|
|
|
215
256
|
|
|
216
257
|
Each agent skill includes a `scripts/` folder with standalone Python scripts that Claude Code can execute directly. Tools from the same source module are grouped into a single script file with a CLI wrapper that accepts `--arg value` arguments.
|
|
217
258
|
|
|
259
|
+
The skill generator pulls parameter metadata from the YAML `tools:` block when present (per ADR-017's parameter declaration), and falls back to Python signature introspection when omitted.
|
|
260
|
+
|
|
218
261
|
## Configuration
|
|
219
262
|
|
|
220
263
|
The `--config` (`-c`) flag points to an `orchid.yml` file:
|
|
@@ -241,6 +284,10 @@ storage:
|
|
|
241
284
|
class: orchid_ai.persistence.sqlite.OrchidSQLiteChatStorage
|
|
242
285
|
dsn: ~/.orchid/chats.db
|
|
243
286
|
|
|
287
|
+
# Startup hook (e.g. seeds RAG, registers custom strategies / tools)
|
|
288
|
+
startup:
|
|
289
|
+
hook: examples.travel-agency.hooks.startup.bootstrap_travel
|
|
290
|
+
|
|
244
291
|
# LangGraph checkpointer (optional) — enables persistent graph state,
|
|
245
292
|
# required for Human-in-the-Loop tool approval
|
|
246
293
|
checkpointer:
|
|
@@ -263,7 +310,7 @@ Chat data is stored in SQLite at `~/.orchid/chats.db` by default. OAuth tokens a
|
|
|
263
310
|
|
|
264
311
|
### Checkpointing
|
|
265
312
|
|
|
266
|
-
The CLI supports LangGraph checkpointers for persistent graph state. This is **required** when any agent uses Human-in-the-Loop (`requires_approval: true` on tools).
|
|
313
|
+
The CLI supports LangGraph checkpointers for persistent graph state. This is **required** when any agent uses Human-in-the-Loop (`requires_approval: true` on tools) or relies on resume-after-interrupt flows.
|
|
267
314
|
|
|
268
315
|
```yaml
|
|
269
316
|
# In orchid.yml
|
|
@@ -286,16 +333,16 @@ The CLI supports **OAuth 2.0 Authorization Code + PKCE** for authenticating with
|
|
|
286
333
|
|
|
287
334
|
### How It Works
|
|
288
335
|
|
|
289
|
-
1. `orchid auth login` opens the system browser to the provider's authorization page
|
|
290
|
-
2. User authenticates and consents
|
|
291
|
-
3. Provider redirects to a temporary `localhost` callback server
|
|
292
|
-
4. CLI exchanges the authorization code for access + refresh tokens (with PKCE verification)
|
|
293
|
-
5. Tokens are stored at `~/.orchid/tokens.json`
|
|
294
|
-
6. All subsequent `orchid chat` commands use the stored token automatically
|
|
336
|
+
1. `orchid auth login` opens the system browser to the provider's authorization page.
|
|
337
|
+
2. User authenticates and consents.
|
|
338
|
+
3. Provider redirects to a temporary `localhost` callback server.
|
|
339
|
+
4. CLI exchanges the authorization code for access + refresh tokens (with PKCE verification).
|
|
340
|
+
5. Tokens are stored at `~/.orchid/tokens.json` with `0o600` permissions.
|
|
341
|
+
6. All subsequent `orchid chat` commands use the stored token automatically.
|
|
295
342
|
|
|
296
343
|
### OIDC Discovery
|
|
297
344
|
|
|
298
|
-
When `issuer` is set in the config, the CLI fetches `{issuer}/.well-known/openid-configuration` to auto-discover `authorization_endpoint` and `token_endpoint`. This is the recommended approach
|
|
345
|
+
When `issuer` is set in the config, the CLI fetches `{issuer}/.well-known/openid-configuration` to auto-discover `authorization_endpoint` and `token_endpoint`. This is the recommended approach — you only need the issuer URL.
|
|
299
346
|
|
|
300
347
|
### Token Refresh
|
|
301
348
|
|
|
@@ -303,22 +350,19 @@ When the access token expires and a refresh token is available, the CLI refreshe
|
|
|
303
350
|
|
|
304
351
|
### Identity Resolution
|
|
305
352
|
|
|
306
|
-
When `identity_resolver_class` is configured, the CLI calls the resolver after login to populate `tenant_key` and `user_id` from the OAuth token. These identity fields are cached in the token file so subsequent commands don't need the resolver. See the [
|
|
353
|
+
When `identity_resolver_class` is configured, the CLI calls the resolver after login to populate `tenant_key` and `user_id` from the OAuth token. These identity fields are cached in the token file so subsequent commands don't need the resolver. See the [`OrchidIdentityResolver` ABC](../orchid/orchid_ai/core/identity.py) for the interface.
|
|
307
354
|
|
|
308
355
|
> **Note:** the CLI is an **independent OAuth client** — it runs its own
|
|
309
356
|
> authorization-code + PKCE dance against the upstream IdP and calls the
|
|
310
357
|
> identity resolver locally. It does NOT use the
|
|
311
358
|
> centralised `/auth/exchange-code` / `/auth/resolve-identity` /
|
|
312
|
-
> `/auth/refresh-token` endpoints that the MCP gateway uses
|
|
313
|
-
> of the [auth-centralisation roadmap](../.knowledge/auth-centralisation.md)).
|
|
359
|
+
> `/auth/refresh-token` endpoints that the MCP gateway uses.
|
|
314
360
|
> The CLI ships with the upstream secret `client_id` baked into its config
|
|
315
|
-
> because it's a desktop app, not a network service.
|
|
316
|
-
> could route the CLI through the same endpoints to remove the
|
|
317
|
-
> CLI-side `client_secret` / userinfo coupling — out of scope today.
|
|
361
|
+
> because it's a desktop app, not a network service.
|
|
318
362
|
|
|
319
363
|
### Dev Fallback
|
|
320
364
|
|
|
321
|
-
When `auth.dev_bypass: true` or `auth.cli` is absent, the CLI uses a dummy token (`cli-token`, tenant=`cli`, user=`cli-user`). This is fully backward compatible
|
|
365
|
+
When `auth.dev_bypass: true` or `auth.cli` is absent, the CLI uses a dummy token (`cli-token`, tenant=`cli`, user=`cli-user`). This is fully backward compatible — existing configs without OAuth continue to work unchanged.
|
|
322
366
|
|
|
323
367
|
## Prerequisites
|
|
324
368
|
|
|
@@ -354,7 +398,7 @@ After `pip install mypackage`, the command is available as `orchid mycommand gre
|
|
|
354
398
|
orchid_cli/
|
|
355
399
|
main.py Typer entry point — registers built-in + plugin subcommands
|
|
356
400
|
bootstrap.py Shared startup: load config, build graph, init storage,
|
|
357
|
-
wire checkpointer (optional)
|
|
401
|
+
wire checkpointer (optional), invoke startup hook
|
|
358
402
|
auth/ OAuth 2.0 authentication (self-contained)
|
|
359
403
|
config.py Provider settings from orchid.yml
|
|
360
404
|
oidc.py Shared OIDC discovery utility
|
|
@@ -373,7 +417,38 @@ orchid_cli/
|
|
|
373
417
|
skill.py Generate Claude Code skills from agents.yaml
|
|
374
418
|
```
|
|
375
419
|
|
|
376
|
-
|
|
420
|
+
`bootstrap.py` mirrors the orchid-api lifespan: load and validate config, build the LangGraph runtime, initialise the storage backend, wire the checkpointer, fire the startup hook (if any). The chat commands then attach an `OrchidAuthContext` per call and enter the supervisor — same primitives orchid-api uses, just without the FastAPI shell.
|
|
421
|
+
|
|
422
|
+
## Embedded mode (using the SDK directly)
|
|
423
|
+
|
|
424
|
+
For applications that need Orchid as a library rather than a CLI:
|
|
425
|
+
|
|
426
|
+
```python
|
|
427
|
+
from orchid_ai import Orchid, OrchidRuntime
|
|
428
|
+
from orchid_ai.config.loader import load_config
|
|
429
|
+
|
|
430
|
+
config = load_config("orchid.yml")
|
|
431
|
+
runtime = OrchidRuntime.from_config(config)
|
|
432
|
+
async with runtime:
|
|
433
|
+
answer = await runtime.ask(
|
|
434
|
+
"Hello!",
|
|
435
|
+
tenant_id="default",
|
|
436
|
+
user_id="me",
|
|
437
|
+
chat_id="chat-1",
|
|
438
|
+
)
|
|
439
|
+
print(answer.text)
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
The CLI uses these primitives internally; embedded users get the same behaviour without the Typer shell. See `examples/embedded-python/` for end-to-end patterns.
|
|
443
|
+
|
|
444
|
+
## Troubleshooting
|
|
445
|
+
|
|
446
|
+
- **`Cannot resolve chat storage class '…'`** — the dotted import path in `storage.class` failed to import. Confirm the package is installed and the module path is reachable from `PYTHONPATH`.
|
|
447
|
+
- **`No module named 'aiosqlite'`** — install the SQLite extra: `pip install orchid-ai[checkpoint-sqlite]`.
|
|
448
|
+
- **OAuth `redirect_uri_mismatch`** — register `http://localhost:<port>/callback` (the port the CLI prints on `auth login`) with your IdP. Some IdPs accept the loopback wildcard `http://127.0.0.1`; others require the literal port.
|
|
449
|
+
- **Tokens stored but `auth status` shows expired** — refresh failed. Inspect `~/.orchid/tokens.json` (chmod 600) and re-run `orchid auth login`.
|
|
450
|
+
- **Slow startup with custom LLM provider** — `bootstrap.py` initialises the chat model lazily, but startup hooks run synchronously. Move heavy work behind `if reader and reader.supports_writes:` guards inside the hook.
|
|
451
|
+
- **`No agents loaded`** — likely missing `agents.config_path` in `orchid.yml`. Inline-config users should switch to `agents:` (see `examples/embedded-python/06_inline_config.py`).
|
|
377
452
|
|
|
378
453
|
## Development
|
|
379
454
|
|
|
@@ -392,4 +467,4 @@ ruff check orchid_cli/
|
|
|
392
467
|
|
|
393
468
|
## License
|
|
394
469
|
|
|
395
|
-
MIT
|
|
470
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -67,8 +67,7 @@ class StoredToken:
|
|
|
67
67
|
|
|
68
68
|
def save_token(client_id: str, token: StoredToken) -> None:
|
|
69
69
|
"""Persist a token for the given client_id."""
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
# Directory creation happens in _exclusive_lock() to respect monkeypatching
|
|
72
71
|
with _exclusive_lock():
|
|
73
72
|
all_tokens = _read_all()
|
|
74
73
|
all_tokens[client_id] = asdict(token)
|
|
@@ -159,8 +158,10 @@ def _exclusive_lock() -> Iterator[None]:
|
|
|
159
158
|
where ``fcntl`` is unavailable; concurrent CLI invocations there
|
|
160
159
|
accept a small risk of last-writer-wins.
|
|
161
160
|
"""
|
|
162
|
-
_ORCHID_DIR
|
|
163
|
-
|
|
161
|
+
# Read _ORCHID_DIR from globals() so monkeypatching in tests works
|
|
162
|
+
orchid_dir = globals()["_ORCHID_DIR"]
|
|
163
|
+
orchid_dir.mkdir(parents=True, exist_ok=True)
|
|
164
|
+
lock_file = orchid_dir / _LOCK_FILENAME
|
|
164
165
|
fd = os.open(str(lock_file), os.O_RDWR | os.O_CREAT, 0o600)
|
|
165
166
|
try:
|
|
166
167
|
if _HAS_FCNTL:
|
|
@@ -36,6 +36,7 @@ from rich.console import Console
|
|
|
36
36
|
from orchid_ai.core.repository import Document, OrchidVectorWriter
|
|
37
37
|
from orchid_ai.documents.chunker import ChunkConfig
|
|
38
38
|
from orchid_ai.documents.pipeline import ingest_document
|
|
39
|
+
from orchid_ai.documents.strategies import RecursiveIngestion
|
|
39
40
|
from orchid_ai.rag.scopes import SHARED_TENANT, OrchidRAGScope
|
|
40
41
|
|
|
41
42
|
from .._typer_async import async_command
|
|
@@ -143,7 +144,7 @@ async def _index_file(
|
|
|
143
144
|
async with cli_context(config_path) as ctx:
|
|
144
145
|
writer = await _require_writer(ctx)
|
|
145
146
|
scope_obj = OrchidRAGScope(tenant_id=tenant_id, user_id=user, chat_id="", agent_id="")
|
|
146
|
-
|
|
147
|
+
ingestion = RecursiveIngestion(ChunkConfig(chunk_size=chunk_size, chunk_overlap=chunk_overlap))
|
|
147
148
|
|
|
148
149
|
console.print(f"[dim]Reading {file_path}...[/dim]")
|
|
149
150
|
file_bytes = file_path.read_bytes()
|
|
@@ -154,7 +155,7 @@ async def _index_file(
|
|
|
154
155
|
scope=scope_obj,
|
|
155
156
|
namespace=namespace,
|
|
156
157
|
writer=writer,
|
|
157
|
-
|
|
158
|
+
ingestion=ingestion,
|
|
158
159
|
vision_model=vision_model,
|
|
159
160
|
)
|
|
160
161
|
|
|
@@ -233,7 +234,7 @@ async def _index_dir(
|
|
|
233
234
|
async with cli_context(config_path) as ctx:
|
|
234
235
|
writer = await _require_writer(ctx)
|
|
235
236
|
scope_obj = OrchidRAGScope(tenant_id=tenant_id, user_id=user, chat_id="", agent_id="")
|
|
236
|
-
|
|
237
|
+
ingestion = RecursiveIngestion(ChunkConfig(chunk_size=chunk_size, chunk_overlap=chunk_overlap))
|
|
237
238
|
|
|
238
239
|
total_chunks = 0
|
|
239
240
|
successes = 0
|
|
@@ -248,7 +249,7 @@ async def _index_dir(
|
|
|
248
249
|
scope=scope_obj,
|
|
249
250
|
namespace=namespace,
|
|
250
251
|
writer=writer,
|
|
251
|
-
|
|
252
|
+
ingestion=ingestion,
|
|
252
253
|
vision_model=vision_model,
|
|
253
254
|
)
|
|
254
255
|
if count > 0:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "orchid-cli"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.3.0"
|
|
4
4
|
description = "Orchid CLI — command-line interface for the Orchid agent framework"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -36,7 +36,7 @@ keywords = [
|
|
|
36
36
|
"orchestration",
|
|
37
37
|
]
|
|
38
38
|
dependencies = [
|
|
39
|
-
"orchid-ai>=1.
|
|
39
|
+
"orchid-ai>=1.6.0",
|
|
40
40
|
"typer>=0.12.0",
|
|
41
41
|
"rich>=13.0",
|
|
42
42
|
"pyyaml>=6.0",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|