orc-ai 0.1.8 → 0.1.10
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.
- package/README.md +448 -79
- package/dist/index.js +1 -1
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -1,50 +1,132 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
>
|
|
5
|
-
> One SQLite file. Shared across every agent you use.
|
|
3
|
+
# orc
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
**Multi-agent orchestrator with human-in-the-loop review**
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/orc-ai)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+

|
|
10
|
+

|
|
11
|
+
|
|
12
|
+
One SQLite file. One CLI. Any agent.<br>
|
|
13
|
+
ORC coordinates Claude Code, Cursor, Codex, Gemini, and remote A2A agents through a shared task board, persistent memory, and a review flow that keeps you in control.
|
|
14
|
+
|
|
15
|
+
<img src="assets/Architecture.gif" alt="ORC Architecture" width="700" />
|
|
16
|
+
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
## Overview
|
|
20
|
+
|
|
21
|
+
Every AI agent session is an island. Start a new session and it knows nothing about the last one. Run two in parallel and they can't coordinate. Switch agents and you start from zero.
|
|
22
|
+
|
|
23
|
+
ORC fixes this. Shared memory across every session. A task board where agents submit work and you approve it. A scheduler that runs agents on a cron. All backed by a single SQLite file — no cloud, no account, no subscription.
|
|
24
|
+
|
|
25
|
+
### Key features
|
|
26
|
+
|
|
27
|
+
| Feature | What it does |
|
|
28
|
+
|---|---|
|
|
29
|
+
| **Agent orchestration** | Task loop spawns workers across backends (Claude Code, Codex, Gemini, Copilot, A2A), manages concurrency and review cycles |
|
|
30
|
+
| **Human-in-the-loop** | Agents submit work for your approval via CLI, Telegram, or Slack before anything lands |
|
|
31
|
+
| **Shared memory** | Decisions, rules, and discoveries stored once, searchable by any session via ranked full-text search |
|
|
32
|
+
| **Task board** | `todo → queued → doing → review → done` with dependency tracking, priority, and automatic unblocking |
|
|
33
|
+
| **Multi-backend routing** | Route to Claude Code, ACPX (Agent Communication Protocol, 14+ agents), or remote A2A endpoints; unknown names fall through to ACPX |
|
|
34
|
+
| **Job runner** | Cron, file-watch, webhook, or manual triggers with full run history |
|
|
35
|
+
| **MCP server** | 20 tools connect any [Model Context Protocol](https://modelcontextprotocol.io) (MCP) compatible agent with one config line |
|
|
36
|
+
| **Session continuity** | Snapshots survive context compaction so agents resume where they left off |
|
|
37
|
+
| **Gateway** | Approve work, search memory, and chat with live agents from Telegram or Slack |
|
|
38
|
+
| **Skill library** | Discoverable workflow templates (coder, reviewer, planner, bugfix) that encode your standards |
|
|
39
|
+
|
|
40
|
+
## Getting started
|
|
41
|
+
|
|
42
|
+
### Prerequisites
|
|
43
|
+
|
|
44
|
+
- [Bun](https://bun.sh) >= 1.1
|
|
45
|
+
|
|
46
|
+
### Install
|
|
8
47
|
|
|
9
48
|
```bash
|
|
10
49
|
npm install -g orc-ai
|
|
11
|
-
# or
|
|
12
|
-
bun add -g orc-ai
|
|
13
50
|
```
|
|
14
51
|
|
|
15
|
-
>
|
|
52
|
+
<details>
|
|
53
|
+
<summary>Other installation methods</summary>
|
|
54
|
+
|
|
55
|
+
#### Pre-built binaries
|
|
56
|
+
|
|
57
|
+
Download from [GitHub Releases](https://github.com/niradler/orc/releases):
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# macOS (Apple Silicon)
|
|
61
|
+
curl -L https://github.com/niradler/orc/releases/latest/download/orc-mac-arm64 -o /usr/local/bin/orc && chmod +x /usr/local/bin/orc
|
|
62
|
+
|
|
63
|
+
# macOS (Intel)
|
|
64
|
+
curl -L https://github.com/niradler/orc/releases/latest/download/orc-mac-x64 -o /usr/local/bin/orc && chmod +x /usr/local/bin/orc
|
|
65
|
+
|
|
66
|
+
# Linux (x64)
|
|
67
|
+
curl -L https://github.com/niradler/orc/releases/latest/download/orc-linux-x64 -o /usr/local/bin/orc && chmod +x /usr/local/bin/orc
|
|
68
|
+
|
|
69
|
+
# Linux (ARM64)
|
|
70
|
+
curl -L https://github.com/niradler/orc/releases/latest/download/orc-linux-arm64 -o /usr/local/bin/orc && chmod +x /usr/local/bin/orc
|
|
16
71
|
|
|
17
|
-
|
|
72
|
+
# Windows — download orc-windows-x64.exe from the release page and add to PATH
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
#### From source
|
|
18
76
|
|
|
19
|
-
|
|
77
|
+
```bash
|
|
78
|
+
git clone https://github.com/niradler/orc
|
|
79
|
+
cd orc && bun install && bun build
|
|
80
|
+
```
|
|
20
81
|
|
|
21
|
-
|
|
22
|
-
- **Task board** — tasks move through `todo → doing → review → done`; agents submit for review, you approve
|
|
23
|
-
- **Job runner** — schedule any command (cron, file-watch, manual); logs every run
|
|
24
|
-
- **MCP server** — one config line connects any agent to all of the above
|
|
25
|
-
- **Session continuity** — hooks capture what happened; snapshots survive context compaction
|
|
26
|
-
- **Gateway** — approve agent work from Telegram or Slack; start live Claude/Codex sessions from your phone
|
|
82
|
+
</details>
|
|
27
83
|
|
|
28
|
-
|
|
84
|
+
### Quick start
|
|
29
85
|
|
|
30
86
|
```bash
|
|
31
|
-
# Start the
|
|
87
|
+
# 1. Start the daemon — runs the REST API on :7700, task loop, job scheduler, and gateway
|
|
32
88
|
orc daemon start
|
|
33
89
|
|
|
34
|
-
#
|
|
35
|
-
orc
|
|
90
|
+
# 2. Create a project
|
|
91
|
+
orc project add my-app -d "My application"
|
|
92
|
+
orc project use my-app
|
|
93
|
+
|
|
94
|
+
# 3. Add tasks and memories — everything auto-scopes to my-app
|
|
36
95
|
orc task add "Fix the auth bug" --priority high
|
|
37
|
-
orc mem add "Use RWMutex for token refresh" --type decision
|
|
38
|
-
orc job add nightly --command "
|
|
96
|
+
orc mem add "Use RWMutex for token refresh" --type decision
|
|
97
|
+
orc job add nightly --command "bun run test" --trigger cron --cron "0 22 * * *"
|
|
98
|
+
|
|
99
|
+
# 4. See everything
|
|
100
|
+
orc status
|
|
101
|
+
orc task list
|
|
39
102
|
```
|
|
40
103
|
|
|
41
|
-
|
|
104
|
+
> [!TIP]
|
|
105
|
+
> The database is created automatically at `~/.orc/orc.db` on first run. No setup needed.
|
|
42
106
|
|
|
43
|
-
##
|
|
107
|
+
## Connect your agent
|
|
108
|
+
|
|
109
|
+
### Claude Code
|
|
110
|
+
|
|
111
|
+
Add to `~/.claude/settings.json`:
|
|
112
|
+
|
|
113
|
+
```json
|
|
114
|
+
{
|
|
115
|
+
"hooks": {
|
|
116
|
+
"PostToolUse": [{ "matcher": "Write|Edit|MultiEdit|StrReplace|EditNotebook|Bash|Shell|Agent|EnterPlanMode|ExitPlanMode|mcp__orc__task_|mcp__orc__memory_store|mcp__orc__memory_delete|mcp__orc__job_run|mcp__orc__job_create|mcp__orc__job_update", "hooks": [{ "type": "command", "command": "bun /path/to/orc/hooks/post-tool-use.ts" }] }],
|
|
117
|
+
"PreCompact": [{ "matcher": "", "hooks": [{ "type": "command", "command": "bun /path/to/orc/hooks/pre-compact.ts" }] }],
|
|
118
|
+
"SessionStart":[{ "matcher": "", "hooks": [{ "type": "command", "command": "bun /path/to/orc/hooks/session-start.ts" }] }]
|
|
119
|
+
},
|
|
120
|
+
"env": { "ORC_API_BASE": "http://127.0.0.1:7700", "ORC_PROJECT": "" }
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
> [!NOTE]
|
|
125
|
+
> Replace `/path/to/orc/` with the path to your ORC clone or the installed package location (run `npm root -g` to find global installs). Hooks handle session events and snapshots automatically.
|
|
44
126
|
|
|
45
127
|
### Cursor
|
|
46
128
|
|
|
47
|
-
Add to `.cursor/mcp.json
|
|
129
|
+
Add to `.cursor/mcp.json`:
|
|
48
130
|
|
|
49
131
|
```json
|
|
50
132
|
{
|
|
@@ -52,24 +134,18 @@ Add to `.cursor/mcp.json` in your project (or the global Cursor MCP config):
|
|
|
52
134
|
"orc": {
|
|
53
135
|
"command": "orc",
|
|
54
136
|
"args": ["mcp"],
|
|
55
|
-
"env": {
|
|
56
|
-
"ORC_API_BASE": "http://127.0.0.1:7700",
|
|
57
|
-
"ORC_SESSION_ID": "cursor"
|
|
58
|
-
}
|
|
137
|
+
"env": { "ORC_API_BASE": "http://127.0.0.1:7700", "ORC_SESSION_ID": "cursor" }
|
|
59
138
|
}
|
|
60
139
|
}
|
|
61
140
|
}
|
|
62
141
|
```
|
|
63
142
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
Copy `hooks/claude-code/settings.json` from the [orc repo](https://github.com/niradler/orc) to `~/.claude/settings.json` and replace the path placeholder with your actual path.
|
|
67
|
-
|
|
68
|
-
### Codex
|
|
143
|
+
<details>
|
|
144
|
+
<summary>Codex, Gemini CLI, and other MCP agents</summary>
|
|
69
145
|
|
|
70
|
-
Copy `hooks/codex/settings.json`
|
|
146
|
+
**Codex** — Copy `hooks/codex/settings.json` to `~/.codex/settings.json` and update the path.
|
|
71
147
|
|
|
72
|
-
|
|
148
|
+
**Gemini CLI / any MCP agent** — Add to your MCP config:
|
|
73
149
|
|
|
74
150
|
```json
|
|
75
151
|
{
|
|
@@ -77,77 +153,370 @@ Copy `hooks/codex/settings.json` from the [orc repo](https://github.com/niradler
|
|
|
77
153
|
"orc": {
|
|
78
154
|
"command": "orc",
|
|
79
155
|
"args": ["mcp"],
|
|
80
|
-
"env": {
|
|
81
|
-
"ORC_API_BASE": "http://127.0.0.1:7700",
|
|
82
|
-
"ORC_SESSION_ID": "gemini"
|
|
83
|
-
}
|
|
156
|
+
"env": { "ORC_API_BASE": "http://127.0.0.1:7700", "ORC_SESSION_ID": "gemini" }
|
|
84
157
|
}
|
|
85
158
|
}
|
|
86
159
|
}
|
|
87
160
|
```
|
|
88
161
|
|
|
89
|
-
|
|
162
|
+
</details>
|
|
163
|
+
|
|
164
|
+
## Usage
|
|
165
|
+
|
|
166
|
+
### How agents use ORC
|
|
90
167
|
|
|
91
168
|
```
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
169
|
+
1. Agent starts → context() returns active tasks + key memories
|
|
170
|
+
2. Agent works → creates tasks, stores decisions, records events
|
|
171
|
+
3. Agent submits work → task_update(status: "review", comment: "summary")
|
|
172
|
+
4. You review → approve or request changes via CLI / Telegram / Slack
|
|
173
|
+
5. Agent continues → picks up next task or resumes with feedback
|
|
174
|
+
6. Session ends → session_log() records what happened
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
When a context window fills up, `session_snapshot` captures current state into a compact 2KB blob that `session_restore` injects back after compaction — the agent picks up where it left off.
|
|
96
178
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
179
|
+
### Agent orchestration
|
|
180
|
+
|
|
181
|
+
The task loop automatically picks up queued tasks and spawns worker agents:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
# Create a task with a workflow and backend
|
|
185
|
+
orc task add "Implement user auth" --skill orc-coder --backend claude --priority high
|
|
186
|
+
|
|
187
|
+
# Or batch-create with dependencies via MCP
|
|
188
|
+
task_batch_create({ tasks: [
|
|
189
|
+
{ title: "Design schema", skill_name: "orc-planner" },
|
|
190
|
+
{ title: "Implement API", skill_name: "orc-coder", blocked_by: [0] },
|
|
191
|
+
{ title: "Review code", skill_name: "orc-reviewer", blocked_by: [1] }
|
|
192
|
+
]})
|
|
193
|
+
```
|
|
100
194
|
|
|
101
|
-
|
|
102
|
-
orc task add <title> Create a task (--priority, --body, --project)
|
|
103
|
-
orc task done <id> Mark a task done
|
|
104
|
-
orc task review <id> Submit task for HITL review
|
|
105
|
-
orc task approve <id> Approve a review
|
|
106
|
-
orc task reject <id> Request changes
|
|
195
|
+
The loop handles concurrency, session resume on feedback, review round limits, stale claim cleanup, and backend routing.
|
|
107
196
|
|
|
108
|
-
|
|
109
|
-
orc mem add <content> Store a memory (--type, --scope, --title)
|
|
110
|
-
orc mem search <query> Search memories via BM25 + trigram
|
|
197
|
+
#### Agent backends
|
|
111
198
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
199
|
+
| Backend | Description |
|
|
200
|
+
|---|---|
|
|
201
|
+
| `claude` | Native Claude Code CLI. Falls back to ACPX on error. |
|
|
202
|
+
| `acpx` | 14+ agents via [ACP CLI](https://github.com/AgenTool/acpx) — Codex, Gemini, Copilot, Kiro, Cursor, and more. |
|
|
203
|
+
| `a2a` | Remote agents via [Google A2A protocol](https://github.com/google/A2A) (JSON-RPC over HTTP). |
|
|
204
|
+
| *anything else* | Routes through ACPX with the name as `--agent` flag. |
|
|
116
205
|
|
|
117
|
-
|
|
118
|
-
|
|
206
|
+
Enable the task loop in `~/.orc/config.json`:
|
|
207
|
+
|
|
208
|
+
```json
|
|
209
|
+
{
|
|
210
|
+
"agent_loop": {
|
|
211
|
+
"enabled": true,
|
|
212
|
+
"poll_interval_minutes": 5,
|
|
213
|
+
"max_workers": 1,
|
|
214
|
+
"default_backend": "claude",
|
|
215
|
+
"session_idle_timeout_minutes": 20,
|
|
216
|
+
"worker_auto_approve": true
|
|
217
|
+
}
|
|
218
|
+
}
|
|
119
219
|
```
|
|
120
220
|
|
|
121
|
-
|
|
221
|
+
### Projects
|
|
122
222
|
|
|
123
|
-
|
|
223
|
+
Projects group tasks, memories, and jobs. Set an active project and all commands auto-scope:
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
orc project add my-app -d "Main application"
|
|
227
|
+
orc project use my-app # set active
|
|
228
|
+
|
|
229
|
+
orc task list # scoped to my-app
|
|
230
|
+
orc mem search "auth" # scoped to my-app
|
|
231
|
+
orc task list -p infra # override to different project
|
|
232
|
+
orc task list --no-project # see everything
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
**Resolution order:** explicit `-p <name>` > `activeProject` from config > error
|
|
236
|
+
|
|
237
|
+
MCP tools follow the same logic — pass `project: "name"` to scope, or omit to use the active project.
|
|
238
|
+
|
|
239
|
+
### Memory
|
|
240
|
+
|
|
241
|
+
Store decisions, conventions, and discoveries that persist across all sessions:
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
orc mem add "All IDs are ULIDs" --type rule
|
|
245
|
+
orc mem add "Use PostgreSQL for concurrent writes" --type decision
|
|
246
|
+
orc mem search "authentication"
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
| Type | Weight | Use for |
|
|
250
|
+
|---|---|---|
|
|
251
|
+
| `rule` | High | Conventions: "all IDs are ULIDs" |
|
|
252
|
+
| `decision` | High | Choices: "use PostgreSQL for concurrent writes" |
|
|
253
|
+
| `discovery` | Medium | Findings: "token refresh has a race condition" |
|
|
254
|
+
| `event` | Low | Things that happened: "deployed v1.0" |
|
|
255
|
+
| `fact` | Low | General knowledge (default) |
|
|
256
|
+
|
|
257
|
+
### Task status flow
|
|
258
|
+
|
|
259
|
+
```
|
|
260
|
+
todo → queued → doing → review → done
|
|
261
|
+
│ │
|
|
262
|
+
v v
|
|
263
|
+
blocked changes_requested → doing
|
|
264
|
+
│
|
|
265
|
+
v
|
|
266
|
+
paused
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Tasks with `required_review: true` (default) need your approval before moving to `done`. Set `max_review_rounds` to auto-pause tasks that cycle through too many revision rounds.
|
|
270
|
+
|
|
271
|
+
<img src="assets/TaskFlow.gif" alt="Task Flow" width="600" />
|
|
272
|
+
|
|
273
|
+
### Jobs
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
orc job add deploy --command "bun run deploy" --trigger manual
|
|
277
|
+
orc job add nightly --command "bun run test" --trigger cron --cron "0 22 * * *"
|
|
278
|
+
orc job add on-change --command "bun run lint" --trigger watch --watch "./src"
|
|
279
|
+
orc job add on-push --command "bun run ci" --trigger webhook
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Gateway (Telegram / Slack)
|
|
283
|
+
|
|
284
|
+
Approve agent work from your phone, search memory, or start a live AI session.
|
|
124
285
|
|
|
125
286
|
```json
|
|
126
287
|
{
|
|
127
|
-
"
|
|
128
|
-
"
|
|
129
|
-
|
|
130
|
-
|
|
288
|
+
"gateway": {
|
|
289
|
+
"telegram": {
|
|
290
|
+
"enabled": true,
|
|
291
|
+
"token": "7123456789:AAF...",
|
|
292
|
+
"authorized_users": [123456789]
|
|
293
|
+
}
|
|
131
294
|
}
|
|
132
295
|
}
|
|
133
296
|
```
|
|
134
297
|
|
|
135
|
-
|
|
298
|
+
**Commands:** `/status`, `/tasks`, `/approve <id>`, `/reject <id>`, `/jobs`, `/run <name>`, `/mem <query>`, `/agent <claude|codex>`
|
|
299
|
+
|
|
300
|
+
> [!TIP]
|
|
301
|
+
> Create a Telegram bot via [@BotFather](https://t.me/BotFather) and find your user ID via [@userinfobot](https://t.me/userinfobot).
|
|
302
|
+
|
|
303
|
+
<details>
|
|
304
|
+
<summary>Slack setup</summary>
|
|
305
|
+
|
|
306
|
+
```json
|
|
307
|
+
{
|
|
308
|
+
"gateway": {
|
|
309
|
+
"slack": {
|
|
310
|
+
"enabled": true,
|
|
311
|
+
"bot_token": "xoxb-...",
|
|
312
|
+
"app_token": "xapp-...",
|
|
313
|
+
"authorized_users": ["U01ABCDEF"]
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Same commands as Telegram. Create a Slack app at [api.slack.com/apps](https://api.slack.com/apps) with Socket Mode enabled.
|
|
320
|
+
|
|
321
|
+
</details>
|
|
322
|
+
|
|
323
|
+
## Skills
|
|
324
|
+
|
|
325
|
+
ORC ships with agent workflow skills and built-in skill templates for the task loop.
|
|
326
|
+
|
|
327
|
+
### Install skills
|
|
328
|
+
|
|
329
|
+
```bash
|
|
330
|
+
# Install all ORC skills into your agent
|
|
331
|
+
npx skills add niradler/orc --all
|
|
332
|
+
|
|
333
|
+
# Or pick specific ones
|
|
334
|
+
npx skills add niradler/orc --skill orc-session orc-tasks
|
|
335
|
+
|
|
336
|
+
# Global install
|
|
337
|
+
npx skills add niradler/orc --all -g
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### Agent workflow skills
|
|
341
|
+
|
|
342
|
+
| Skill | Triggers on |
|
|
343
|
+
|---|---|
|
|
344
|
+
| `orc-session` | Session start, context compaction, resuming work |
|
|
345
|
+
| `orc-tasks` | Task creation, status updates, HITL review, task decomposition |
|
|
346
|
+
| `orc-knowledge` | Storing decisions, searching memory, "remember this", "what did we decide" |
|
|
347
|
+
| `orc-gateway` | Telegram/Slack setup, remote approval, live agent sessions |
|
|
348
|
+
|
|
349
|
+
### Built-in skill templates
|
|
350
|
+
|
|
351
|
+
Skill templates live in `skills/*/SKILL.md` (built-in) and `~/.orc/skills/` (user-defined). Agents discover them via `skill_list` and load with `skill_read`.
|
|
352
|
+
|
|
353
|
+
| Skill | Type | Purpose |
|
|
354
|
+
|---|---|---|
|
|
355
|
+
| `orc-worker-base` | Base | Default worker behavior — ORC awareness, status updates, deliverable format |
|
|
356
|
+
| `orc-main-base` | Base | Orchestration agent — planning, decomposition, monitoring |
|
|
357
|
+
| `orc-coder` | Workflow | Implementation — understand, plan, implement, verify, submit |
|
|
358
|
+
| `orc-planner` | Workflow | Task decomposition with dependencies and workflow assignment |
|
|
359
|
+
| `orc-reviewer` | Workflow | Structured evaluation — correctness, tests, security, conventions |
|
|
360
|
+
| `orc-bugfix` | Workflow | Bug investigation — reproduce, root-cause, fix, regression test |
|
|
361
|
+
| `orc-requirements` | Skill | Requirements interview — outcome, criteria, constraints, scope |
|
|
362
|
+
| `orc-report` | Skill | Project status report — health summary, blockers, active work |
|
|
363
|
+
|
|
364
|
+
Add custom skills by creating a `SKILL.md` in `~/.orc/skills/my-workflow/SKILL.md`.
|
|
365
|
+
|
|
366
|
+
## MCP tools
|
|
367
|
+
|
|
368
|
+
**20 tools** available to any connected agent. Start every session with `context`.
|
|
369
|
+
|
|
370
|
+
| Category | Tools |
|
|
371
|
+
|---|---|
|
|
372
|
+
| **Project** | `project_list` |
|
|
373
|
+
| **Memory** | `context`, `memory_search`, `memory_get`, `memory_store` |
|
|
374
|
+
| **Task** | `task_list`, `task_get`, `task_create`, `task_update`, `task_batch_create` |
|
|
375
|
+
| **Skill** | `skill_list`, `skill_read` |
|
|
376
|
+
| **Search** | `search` |
|
|
377
|
+
| **Job** | `job_list`, `job_run`, `job_status` |
|
|
378
|
+
| **Session** | `session_event`, `session_snapshot`, `session_restore`, `session_log` |
|
|
379
|
+
|
|
380
|
+
## REST API
|
|
381
|
+
|
|
382
|
+
Runs on port 7700 with auto-generated OpenAPI spec.
|
|
383
|
+
|
|
384
|
+
- **Swagger UI:** `GET /docs`
|
|
385
|
+
- **OpenAPI spec:** `GET /openapi.json`
|
|
386
|
+
|
|
387
|
+
<details>
|
|
388
|
+
<summary>Full endpoint list</summary>
|
|
389
|
+
|
|
390
|
+
| Method | Path | Description |
|
|
391
|
+
|---|---|---|
|
|
392
|
+
| `GET` | `/health` | Health check |
|
|
393
|
+
| `GET/POST/PATCH/DELETE` | `/projects` | CRUD projects |
|
|
394
|
+
| `GET` | `/projects/by-name/{name}` | Lookup by name |
|
|
395
|
+
| `GET` | `/projects/{id}/summary` | Task/memory/job counts |
|
|
396
|
+
| `GET/POST/PATCH/DELETE` | `/tasks` | CRUD tasks |
|
|
397
|
+
| `GET/POST` | `/tasks/{id}/notes` | Task notes |
|
|
398
|
+
| `GET/POST/DELETE` | `/tasks/{id}/links` | Task dependencies |
|
|
399
|
+
| `GET/POST/DELETE` | `/memories` | CRUD memories |
|
|
400
|
+
| `GET` | `/memories/search` | BM25 search |
|
|
401
|
+
| `GET/POST` | `/jobs` | CRUD jobs |
|
|
402
|
+
| `POST` | `/jobs/{id}/trigger` | Trigger a job |
|
|
403
|
+
| `GET` | `/jobs/{id}/runs` | Run history |
|
|
404
|
+
| `GET` | `/jobs/{id}/runs/{runId}/logs` | Run logs |
|
|
405
|
+
| `GET` | `/skills` | Skill templates |
|
|
406
|
+
| `GET` | `/sessions` | Agent session logs |
|
|
407
|
+
| `POST` | `/mcp/tool` | Execute any MCP tool via HTTP |
|
|
408
|
+
|
|
409
|
+
</details>
|
|
410
|
+
|
|
411
|
+
## CLI reference
|
|
412
|
+
|
|
413
|
+
```
|
|
414
|
+
orc daemon start|stop|status Manage the daemon (API + scheduler + gateway)
|
|
415
|
+
orc api Start the API server only
|
|
416
|
+
orc mcp Start the MCP server (stdio)
|
|
417
|
+
orc home Show ~/.orc directory and config
|
|
418
|
+
orc status Show API health and counts
|
|
419
|
+
|
|
420
|
+
orc project list|add|show|use|update|archive
|
|
421
|
+
orc task list|add|show|update|done|review|approve|reject|delete
|
|
422
|
+
orc mem list|add|search
|
|
423
|
+
orc job list|add|run|runs
|
|
424
|
+
orc session list|show|log
|
|
425
|
+
orc skill list|show
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
> [!NOTE]
|
|
429
|
+
> All task/mem/job commands default to the active project. Use `-p <name>` to override or `--no-project` to see everything. Add `--json` for machine-readable output.
|
|
430
|
+
|
|
431
|
+
## Configuration
|
|
432
|
+
|
|
433
|
+
ORC merges config in priority order (later wins):
|
|
434
|
+
|
|
435
|
+
1. `~/.orc/config.json` — user global
|
|
436
|
+
2. `./.orc/config.json` — project-local
|
|
437
|
+
3. Environment variables
|
|
438
|
+
|
|
439
|
+
<details>
|
|
440
|
+
<summary>Environment variables</summary>
|
|
441
|
+
|
|
442
|
+
| Variable | Default | Description |
|
|
443
|
+
|---|---|---|
|
|
444
|
+
| `ORC_DB_PATH` | `~/.orc/orc.db` | SQLite database path |
|
|
445
|
+
| `ORC_API_PORT` | `7700` | API listen port |
|
|
446
|
+
| `ORC_API_SECRET` | — | Bearer token for auth |
|
|
447
|
+
| `ORC_SESSION_ID` | `default` | Per-agent session identifier |
|
|
448
|
+
| `ORC_LOG_LEVEL` | `info` | `debug`, `info`, `warn`, `error` |
|
|
449
|
+
| `ORC_LOG_DIR` | `~/.orc/logs` | Log file directory |
|
|
450
|
+
| `ORC_LOG_FILE` | `1` | Set to `0` to disable file logging |
|
|
451
|
+
| `ORC_RUNNER_TIMEOUT` | `300` | Default job timeout (seconds) |
|
|
452
|
+
| `ORC_AGENT_LOOP_ENABLED` | `false` | Enable the agent task loop |
|
|
453
|
+
| `ORC_AGENT_LOOP_POLL_INTERVAL` | `5` | Task loop poll interval (minutes) |
|
|
454
|
+
| `ORC_AGENT_LOOP_MAX_WORKERS` | `1` | Max concurrent worker agents |
|
|
455
|
+
| `ORC_AGENT_LOOP_DEFAULT_BACKEND` | `claude` | Default agent backend |
|
|
456
|
+
| `ORC_AGENT_LOOP_IDLE_TIMEOUT` | `20` | Session idle timeout (minutes) |
|
|
457
|
+
| `ORC_AGENT_LOOP_AUTO_APPROVE` | `true` | Auto-approve worker tool permissions |
|
|
458
|
+
|
|
459
|
+
</details>
|
|
136
460
|
|
|
137
|
-
|
|
461
|
+
### Logs
|
|
138
462
|
|
|
139
|
-
All
|
|
463
|
+
All output goes to **stderr** (colored, human-readable) and **`~/.orc/logs/orc.log`** (JSON lines, machine-readable). Log files rotate at 10 MB with 3 rotated backups (30 MB total).
|
|
140
464
|
|
|
465
|
+
```bash
|
|
466
|
+
# Tail recent errors
|
|
467
|
+
grep '"level":"error"' ~/.orc/logs/orc.log | tail -20
|
|
468
|
+
|
|
469
|
+
# Watch live
|
|
470
|
+
tail -f ~/.orc/logs/orc.log | jq .
|
|
141
471
|
```
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
472
|
+
|
|
473
|
+
### Running as a service
|
|
474
|
+
|
|
475
|
+
```bash
|
|
476
|
+
# PM2 (any platform)
|
|
477
|
+
pm2 start "orc daemon start" --name orc
|
|
478
|
+
pm2 save && pm2 startup
|
|
145
479
|
```
|
|
146
480
|
|
|
147
|
-
|
|
481
|
+
**macOS (launchd):** `ProgramArguments: ["/usr/local/bin/orc", "daemon", "start"]` with `RunAtLoad` and `KeepAlive`.
|
|
482
|
+
|
|
483
|
+
**Linux (systemd):** `ExecStart=/usr/local/bin/orc daemon start` with `Restart=on-failure`.
|
|
484
|
+
|
|
485
|
+
## Architecture
|
|
486
|
+
|
|
487
|
+
```
|
|
488
|
+
packages/
|
|
489
|
+
core/ Config (Zod), types, logger, ULID IDs
|
|
490
|
+
db/ Drizzle ORM + SQLite (~/.orc/orc.db)
|
|
491
|
+
api/ Hono REST API + OpenAPI spec (:7700)
|
|
492
|
+
sdk/ Typed HTTP client from OpenAPI
|
|
493
|
+
cli/ Commander CLI (the `orc` binary)
|
|
494
|
+
mcp/ MCP server (stdio)
|
|
495
|
+
runner/ Job executor + cron/watch scheduler + task loop
|
|
496
|
+
gateway/ Telegram + Slack bridge + agent sessions
|
|
497
|
+
agent-runtime/ Agent backend registry (claude, acpx, a2a)
|
|
498
|
+
task-service/ Task status transitions + side-effects
|
|
499
|
+
tui/ Terminal UI
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
Data flow: `Agent → MCP → API → DB` / `CLI → SDK → API → DB`
|
|
503
|
+
|
|
504
|
+
## Development
|
|
505
|
+
|
|
506
|
+
```bash
|
|
507
|
+
bun install # install all workspace deps
|
|
508
|
+
bun dev # API + CLI in watch mode
|
|
509
|
+
bun typecheck # typecheck all packages
|
|
510
|
+
bun check # biome lint + format
|
|
511
|
+
bun test # run all tests
|
|
512
|
+
bun build # build all packages
|
|
513
|
+
```
|
|
148
514
|
|
|
149
|
-
|
|
515
|
+
See [AGENTS.md](./AGENTS.md) for the full development guide and coding conventions.
|
|
150
516
|
|
|
151
|
-
|
|
517
|
+
## Learn more
|
|
152
518
|
|
|
153
|
-
|
|
519
|
+
- [Usage Guide](./docs/usage-guide.md) — best practices for memory, tasks, multi-agent workflows, and configuration
|
|
520
|
+
- [Vision](./docs/vision.md) — why ORC exists and the problem it solves
|
|
521
|
+
- [Roadmap](./docs/roadmap.md) — what shipped and what's next
|
|
522
|
+
- [Agent Orchestration Design](./docs/agent-orchestration-design.md) — architecture spec for the task loop and multi-agent workflow
|
package/dist/index.js
CHANGED
|
@@ -593,5 +593,5 @@ Events (${W.length}/${J.events.length}):`);let U={file:"\uD83D\uDCC4",task:"\u27
|
|
|
593
593
|
Snapshot:`),console.log(J.snapshot);else if(X.snapshot&&!J.snapshot)console.log(`
|
|
594
594
|
No snapshot stored for this session.`)}),$.command("log <summary>").description("Log a session summary (calls session_log MCP tool)").option("-a, --agent <name>","Agent name","human").option("--agent-version <v>","Agent version string").action(async(w,X)=>{let G=s(),E=`http://${G.api.host}:${G.api.port}`,J={"Content-Type":"application/json"};if(G.api.secret)J.Authorization=`Bearer ${G.api.secret}`;let A=await fetch(`${E}/mcp/tool`,{method:"POST",headers:J,body:JSON.stringify({name:"session_log",args:{agent:X.agent,agent_version:X.agentVersion,summary:w}})}),W=await A.json();if(!A.ok)return console.error("Error:",W.error);if(l())return E0(W);console.log(W.result)}),$}import{readFileSync as pf}from"fs";function A7($,w){return`\x1B[${w}m${$}\x1B[0m`}function hQ(){let $=new G$("skill").description("Manage skills");return $.command("list").description("List installed skills").option("-q, --query <q>","Keyword search").option("--source <source>","Filter by source (builtin|user)").option("--reload","Force cache rebuild").action(async(w)=>{let X=G0(),{data:G,error:E}=await X.skills.list({q:w.query,source:w.source,reload:w.reload});if(E)return console.error("Error:",E);let J=G?.skills??[];if(l())return E0({skills:J});if(J.length===0)return console.log("No skills found.");for(let A of J){let W=A.source==="user"?A7(" [user]","36"):"",U=A.name.length>30?`${A.name.slice(0,29)}\u2026`:A.name,Y=A.description?` \u2014 ${A.description.slice(0,50)}`:"";console.log(` ${U.padEnd(32)}${W}${Y}`)}}),$.command("read <name>").description("Read a skill").option("--ref <filename>","Read a specific reference file").action(async(w,X)=>{let G=G0(),{data:E,error:J}=await G.skills.read(w,X.ref);if(J)return console.error("Error:",J);if(!E)return console.error("Skill not found.");if(l())return E0(E);if(X.ref){let A=E;console.log(A7(`# ${A.name}`,"1")),console.log(),console.log(A.content)}else{let A=E;if(console.log(A7(`# ${A.name}`,"1")),A.description)console.log(` ${A.description}`);if(console.log(` source: ${A.source}`),console.log(` path: ${A.path}`),A.references.length>0)console.log(` refs: ${A.references.map((W)=>W.name).join(", ")}`);console.log(),console.log(A.content)}}),$.command("create <name>").description("Create a new user skill").option("-c, --content <content>","SKILL.md content (or pipe via stdin)").option("-f, --file <path>","Read content from file").action(async(w,X)=>{let G;if(X.file)G=pf(X.file,"utf-8");else if(X.content)G=X.content;else{let W=[];for await(let U of process.stdin)W.push(U);G=Buffer.concat(W).toString("utf-8")}if(!G.trim())return console.error("Error: No content provided. Use --content, --file, or pipe via stdin.");let E=G0(),{data:J,error:A}=await E.skills.create({name:w,content:G});if(A)return console.error("Error:",A);if(!J)return console.error("Failed to create skill.");if(l())return E0(J);console.log(`Created skill: ${J.name} at ${J.path}`)}),$}function uQ(){return new G$("status").description("Show system status").action(async()=>{let $=G0(),{data:w,error:X}=await $.health.check();if(X){console.error("API unreachable \u2014 is `orc api` running?");return}console.log(`orc API \u25CF running v${w?.version} uptime: ${w?.uptime}s`);let{data:G}=await $.tasks.list({limit:100}),{data:E}=await $.jobs.list(),{data:J}=await $.memories.list();if(l())return E0({api:w,tasks:G?.tasks??[],jobs:E?.jobs??[],memories:J?.memories??[]});let A=rf(G?.tasks??[],"status");console.log(`
|
|
595
595
|
Tasks:`);for(let[W,U]of Object.entries(A))console.log(` ${W.padEnd(20)} ${U}`);console.log(`
|
|
596
|
-
Jobs: ${E?.jobs.length??0} defined`),console.log(`Memory: ${J?.memories.length??0} recent entries`)})}function rf($,w){return $.reduce((X,G)=>{let E=String(G[w]);return X[E]=(X[E]??0)+1,X},{})}U$();async function fX($,w){if(w.length===26)return w;let{data:X}=await $.tasks.list({limit:100}),G=(X?.tasks??[]).find((E)=>E.id.endsWith(w)||E.id===w);if(!G)return console.error(`Task not found: ${w}`),null;return G.id}function mQ(){let $=new G$("task").description("Manage tasks");return $.command("list").description("List tasks").option("-p, --project <name>","Filter by project name").option("--no-project","Show all tasks across projects").option("-s, --status <status>","Filter by status").option("-t, --tag <tag>","Filter by tag").option("-l, --limit <n>","Max results","20").option("--flat","Disable status grouping").action(async(w)=>{let X=G0(),G=w.project===!1,E=G?null:await g1(X,{project:w.project,noProject:G}),{data:J,error:A}=await X.tasks.list({...E?{project_id:E.id}:{},status:w.status,tag:w.tag,limit:Number(w.limit)});if(A){if(l())return B1(String(A));return console.error("Error:",A)}let W=J?.tasks??[];if(l())return E0({tasks:W,project:E?.name??null});if(W.length===0)return console.log("No tasks found.");let U=new Map;if(G){let{data:D}=await X.projects.list({limit:100});for(let N of D?.projects??[])U.set(N.id,N.name)}else if(E)console.log(xX(E.name,"1")),console.log();let Y=G;if(w.status||w.flat)for(let D of W){let N=cQ(D,Y?U:void 0);console.log(N)}else{let D=["review","changes_requested","doing","blocked","todo"],N=new Map;for(let H of W){let L=N.get(H.status)??[];L.push(H),N.set(H.status,L)}for(let H of D){let L=N.get(H);if(!L||L.length===0)continue;let F=`\u2500\u2500 ${H} (${L.length}) `;console.log(W7(F.padEnd(50,"\u2500"),H));for(let M of L)console.log(cQ(M,Y?U:void 0));console.log()}}}),$.command("add <title>").description("Create a new task").option("-p, --project <name>","Project name").option("--no-project","Create without project").option("--priority <p>","Priority (low/normal/high/critical)","normal").option("-b, --body <text>","Task body").action(async(w,X)=>{let G=G0(),E=X.project===!1,J=E?null:await g1(G,{project:X.project,noProject:E}),{data:A,error:W}=await G.tasks.create({title:w,body:X.body,...J?{project_id:J.id}:{},priority:X.priority});if(W){if(l())return B1(String(W));return console.error("Error:",W)}if(l())return E0(A);console.log(`Created: [${i(A?.id)}] ${A?.title}`)}),$.command("done <id>").description("Mark task as done (accepts full ULID or last-6 suffix)").action(async(w)=>{let X=G0(),G=await fX(X,w);if(!G)return;let{data:E,error:J}=await X.tasks.update(G,{status:"done"});if(J){if(l())return B1(String(J));return console.error("Error:",J)}if(l())return E0(E);console.log(`Done: [${i(E?.id)}] ${E?.title}`)}),$.command("review <id>").description("Submit task for review").action(async(w)=>{let X=G0(),G=await fX(X,w);if(!G)return;let{data:E,error:J}=await X.tasks.update(G,{status:"review"});if(J){if(l())return B1(String(J));return console.error("Error:",J)}if(l())return E0(E);console.log(`In review: [${i(E?.id)}] ${E?.title}`)}),$.command("approve <id>").description("Approve a task in review (HITL)").option("-c, --comment <text>","Optional comment to add").action(async(w,X)=>{let G=G0(),E=await fX(G,w);if(!E)return;let{data:J,error:A}=await G.tasks.get(E);if(A){if(l())return B1(String(A));return console.error("Error:",A)}if(J?.status!=="review")return console.error(`Task is not in review (current: ${J?.status})`);let{data:W,error:U}=await G.tasks.update(E,{status:"done"});if(U){if(l())return B1(String(U));return console.error("Error:",U)}if(X.comment)await G.tasks.addComment(E,X.comment,"human");if(l())return E0(W);console.log(`Approved: [${i(W?.id)}] ${W?.title}`)}),$.command("reject <id>").description("Reject a task in review, request changes (HITL)").option("-r, --reason <text>","Reason for rejection").action(async(w,X)=>{let G=G0(),E=await fX(G,w);if(!E)return;let{data:J,error:A}=await G.tasks.get(E);if(A){if(l())return B1(String(A));return console.error("Error:",A)}if(J?.status!=="review")return console.error(`Task is not in review (current: ${J?.status})`);let{data:W,error:U}=await G.tasks.update(E,{status:"changes_requested"});if(U){if(l())return B1(String(U));return console.error("Error:",U)}if(X.reason)await G.tasks.addComment(E,X.reason,"human");if(l())return E0(W);console.log(`Changes requested: [${i(W?.id)}] ${W?.title}`)}),$.command("show <id>").description("Show task details").action(async(w)=>{let X=G0(),G=await fX(X,w);if(!G)return;let{data:E,error:J}=await X.tasks.get(G);if(J){if(l())return B1(String(J));return console.error("Error:",J)}if(!E)return console.error("Task not found");if(l()){let{data:Y}=await X.tasks.listLinks(G),D=Y?.links??[];return E0({...E,links:D})}let A=(Y,D)=>{if(D!=null)console.log(` ${xX(Y.padEnd(14),"2")} ${D}`)};if(console.log(),console.log(` ${W7(U7(E.status),E.status)} ${xX(E.title,"1")}`),console.log(),A("ID",E.id),A("Status",`${U7(E.status)} ${E.status}`),A("Priority",`${nQ(E.priority)} ${E.priority}`),A("Progress",`${E.progress}%`),A("Project",E.project_id??void 0),A("Tags",E.tags?.join(", ")??void 0),A("Due",E.due_at??void 0),A("Author",E.author),A("Claimed by",E.claimed_by??void 0),A("Created",E.created_at),A("Updated",E.updated_at),E.body)console.log(),console.log(` ${xX("Body","2")}`),console.log(` ${E.body}`);let{data:W}=await X.tasks.listLinks(G),U=W?.links??[];if(U.length>0){console.log(),console.log(` ${xX("Links","2")}`);for(let Y of U){let D=Y.from_task_id===G?Y.to_task_id:Y.from_task_id;console.log(` ${Y.link_type} \u2192 [${i(D)}]`)}}console.log()}),$.command("update <id>").description("Update a task").option("--title <t>","New title").option("--body <text>","New body").option("--status <s>","New status (todo/doing/review/changes_requested/blocked/done/cancelled)").option("--priority <p>","Priority (low/normal/high/critical)").option("--progress <n>","Progress (0-100)").option("--tags <csv>","Comma-separated tags").option("-p, --project <name>","Move to project").option("--no-project","Remove from project").action(async(w,X)=>{let G=G0(),E=await fX(G,w);if(!E)return;let J={};if(X.title)J.title=X.title;if(X.body)J.body=X.body;if(X.status)J.status=X.status;if(X.priority)J.priority=X.priority;if(X.progress!==void 0&&X.progress!==!0)J.progress=Number(X.progress);if(X.tags)J.tags=X.tags.split(",").map((Y)=>Y.trim());if(X.project===!1)J.project_id=null;else if(typeof X.project==="string"){let Y=await g1(G,{project:X.project,noProject:!1});if(Y)J.project_id=Y.id}if(Object.keys(J).length===0)return console.error("No updates specified. Use --help to see options.");if(D1())return O1("update",`task [${i(E)}]`,J);let{data:W,error:U}=await G.tasks.update(E,J);if(U){if(l())return B1(String(U));return console.error("Error:",U)}if(l())return E0(W);console.log(`Updated: [${i(W?.id)}] ${W?.title}`)}),$.command("delete <id>").description("Delete a task").action(async(w)=>{let X=G0(),G=await fX(X,w);if(!G)return;if(D1())return O1("delete",`task [${i(G)}]`);let{error:E}=await X.tasks.delete(G);if(E){if(l())return B1(String(E));return console.error("Error:",E)}if(l())return E0({deleted:G});console.log(`Deleted: [${i(G)}]`)}),$}function xX($,w){return process.stdout.isTTY?`\x1B[${w}m${$}\x1B[0m`:$}var of={review:"33",changes_requested:"33",doing:"36",blocked:"31",todo:"2",done:"32",cancelled:"2"};function W7($,w){return xX($,of[w]??"0")}function U7($){return{todo:"\u25CB",doing:"\u25D0",review:"\u25C9",changes_requested:"\u21A9",blocked:"\u2298",done:"\u25CF",cancelled:"\u2715"}[$]??"?"}function nQ($){return{low:"\u2193",normal:"\u2192",high:"\u2191",critical:"\u203C"}[$]??" "}function cQ($,w){let X=U7($.status),G=nQ($.priority),E=i($.id),J=$.title.length>40?`${$.title.slice(0,39)}\u2026`:$.title,A=` ${W7(X,$.status)} ${G} [${E}] ${J.padEnd(40)}`;if(w&&$.project_id)A+=` ${xX(w.get($.project_id)??i($.project_id),"2")}`;return A}var i$=new G$().name("orc").description("Human + AI Orchestration Hub").version(WG).option("--db <path>","DB file path (overrides ORC_DB_PATH / config.json)").option("--port <n>","API port (overrides ORC_API_PORT / config.json)").option("--host <host>","API host (overrides ORC_API_HOST / config.json)").option("--secret <secret>","API bearer secret (overrides ORC_API_SECRET / config.json)").option("--log-level <level>","Log level: debug|info|warn|error (overrides ORC_LOG_LEVEL)").option("--runner-timeout <secs>","Default job timeout in seconds (overrides ORC_RUNNER_TIMEOUT)").option("--runner-max-jobs <n>","Max concurrent jobs (overrides ORC_RUNNER_MAX_JOBS)").option("--snapshot-max-bytes <n>","Session snapshot budget bytes (overrides ORC_SNAPSHOT_MAX_BYTES)").option("--json","Machine-readable JSON output").option("--dry-run","Preview changes without executing").hook("preSubcommand",($,w)=>{let X=i$.opts();if(X.db)process.env.ORC_DB_PATH=X.db;if(X.port)process.env.ORC_API_PORT=X.port;if(X.host)process.env.ORC_API_HOST=X.host;if(X.secret)process.env.ORC_API_SECRET=X.secret;if(X.logLevel)process.env.ORC_LOG_LEVEL=X.logLevel;if(X.runnerTimeout)process.env.ORC_RUNNER_TIMEOUT=X.runnerTimeout;if(X.runnerMaxJobs)process.env.ORC_RUNNER_MAX_JOBS=X.runnerMaxJobs;if(X.snapshotMaxBytes)process.env.ORC_SNAPSHOT_MAX_BYTES=X.snapshotMaxBytes});i$.addCommand(mQ());i$.addCommand(bQ());i$.addCommand(vQ());i$.addCommand(kQ());i$.addCommand(gQ());i$.addCommand(qQ());i$.addCommand(SQ());i$.addCommand(hQ());i$.addCommand(uQ());i$.addCommand(xQ());i$.command("api").description("Start the API server (use --port / --host / --db / --secret to configure)").action(async()=>{await Promise.resolve().then(() => (X7(),w7)),await new Promise(()=>{})});i$.command("home").description("Show ~/.orc directory contents and daemon state").action(()=>{let $=s();console.log(`ORC home: ${ew}
|
|
596
|
+
Jobs: ${E?.jobs.length??0} defined`),console.log(`Memory: ${J?.memories.length??0} recent entries`)})}function rf($,w){return $.reduce((X,G)=>{let E=String(G[w]);return X[E]=(X[E]??0)+1,X},{})}U$();async function fX($,w){if(w.length===26)return w;let{data:X}=await $.tasks.list({limit:100}),G=(X?.tasks??[]).find((E)=>E.id.endsWith(w)||E.id===w);if(!G)return console.error(`Task not found: ${w}`),null;return G.id}function mQ(){let $=new G$("task").description("Manage tasks");return $.command("list").description("List tasks").option("-p, --project <name>","Filter by project name").option("--no-project","Show all tasks across projects").option("-s, --status <status>","Filter by status").option("-t, --tag <tag>","Filter by tag").option("-l, --limit <n>","Max results","20").option("--flat","Disable status grouping").action(async(w)=>{let X=G0(),G=w.project===!1,E=G?null:await g1(X,{project:w.project,noProject:G}),{data:J,error:A}=await X.tasks.list({...E?{project_id:E.id}:{},status:w.status,tag:w.tag,limit:Number(w.limit)});if(A){if(l())return B1(String(A));return console.error("Error:",A)}let W=J?.tasks??[];if(l())return E0({tasks:W,project:E?.name??null});if(W.length===0)return console.log("No tasks found.");let U=new Map;if(G){let{data:D}=await X.projects.list({limit:100});for(let N of D?.projects??[])U.set(N.id,N.name)}else if(E)console.log(xX(E.name,"1")),console.log();let Y=G;if(w.status||w.flat)for(let D of W){let N=cQ(D,Y?U:void 0);console.log(N)}else{let D=["review","changes_requested","doing","blocked","todo"],N=new Map;for(let H of W){let L=N.get(H.status)??[];L.push(H),N.set(H.status,L)}for(let H of D){let L=N.get(H);if(!L||L.length===0)continue;let F=`\u2500\u2500 ${H} (${L.length}) `;console.log(W7(F.padEnd(50,"\u2500"),H));for(let M of L)console.log(cQ(M,Y?U:void 0));console.log()}}}),$.command("add <title>").description("Create a new task").option("-p, --project <name>","Project name").option("--no-project","Create without project").option("--priority <p>","Priority (low/normal/high/critical)","normal").option("-b, --body <text>","Task body").option("--skill <name>","Skill for agent execution (e.g. orc-coder)").option("--backend <backend>","Agent backend (claude|codex|cursor)").action(async(w,X)=>{let G=G0(),E=X.project===!1,J=E?null:await g1(G,{project:X.project,noProject:E}),{data:A,error:W}=await G.tasks.create({title:w,body:X.body,...J?{project_id:J.id}:{},priority:X.priority,skill_name:X.skill});if(W){if(l())return B1(String(W));return console.error("Error:",W)}if(l())return E0(A);console.log(`Created: [${i(A?.id)}] ${A?.title}`)}),$.command("done <id>").description("Mark task as done (accepts full ULID or last-6 suffix)").action(async(w)=>{let X=G0(),G=await fX(X,w);if(!G)return;let{data:E,error:J}=await X.tasks.update(G,{status:"done"});if(J){if(l())return B1(String(J));return console.error("Error:",J)}if(l())return E0(E);console.log(`Done: [${i(E?.id)}] ${E?.title}`)}),$.command("review <id>").description("Submit task for review").action(async(w)=>{let X=G0(),G=await fX(X,w);if(!G)return;let{data:E,error:J}=await X.tasks.update(G,{status:"review"});if(J){if(l())return B1(String(J));return console.error("Error:",J)}if(l())return E0(E);console.log(`In review: [${i(E?.id)}] ${E?.title}`)}),$.command("approve <id>").description("Approve a task in review (HITL)").option("-c, --comment <text>","Optional comment to add").action(async(w,X)=>{let G=G0(),E=await fX(G,w);if(!E)return;let{data:J,error:A}=await G.tasks.get(E);if(A){if(l())return B1(String(A));return console.error("Error:",A)}if(J?.status!=="review")return console.error(`Task is not in review (current: ${J?.status})`);let{data:W,error:U}=await G.tasks.update(E,{status:"done"});if(U){if(l())return B1(String(U));return console.error("Error:",U)}if(X.comment)await G.tasks.addComment(E,X.comment,"human");if(l())return E0(W);console.log(`Approved: [${i(W?.id)}] ${W?.title}`)}),$.command("reject <id>").description("Reject a task in review, request changes (HITL)").option("-r, --reason <text>","Reason for rejection").action(async(w,X)=>{let G=G0(),E=await fX(G,w);if(!E)return;let{data:J,error:A}=await G.tasks.get(E);if(A){if(l())return B1(String(A));return console.error("Error:",A)}if(J?.status!=="review")return console.error(`Task is not in review (current: ${J?.status})`);let{data:W,error:U}=await G.tasks.update(E,{status:"changes_requested"});if(U){if(l())return B1(String(U));return console.error("Error:",U)}if(X.reason)await G.tasks.addComment(E,X.reason,"human");if(l())return E0(W);console.log(`Changes requested: [${i(W?.id)}] ${W?.title}`)}),$.command("show <id>").description("Show task details").action(async(w)=>{let X=G0(),G=await fX(X,w);if(!G)return;let{data:E,error:J}=await X.tasks.get(G);if(J){if(l())return B1(String(J));return console.error("Error:",J)}if(!E)return console.error("Task not found");if(l()){let{data:Y}=await X.tasks.listLinks(G),D=Y?.links??[];return E0({...E,links:D})}let A=(Y,D)=>{if(D!=null)console.log(` ${xX(Y.padEnd(14),"2")} ${D}`)};if(console.log(),console.log(` ${W7(U7(E.status),E.status)} ${xX(E.title,"1")}`),console.log(),A("ID",E.id),A("Status",`${U7(E.status)} ${E.status}`),A("Priority",`${nQ(E.priority)} ${E.priority}`),A("Progress",`${E.progress}%`),A("Project",E.project_id??void 0),A("Tags",E.tags?.join(", ")??void 0),A("Due",E.due_at??void 0),A("Author",E.author),A("Claimed by",E.claimed_by??void 0),A("Created",E.created_at),A("Updated",E.updated_at),E.body)console.log(),console.log(` ${xX("Body","2")}`),console.log(` ${E.body}`);let{data:W}=await X.tasks.listLinks(G),U=W?.links??[];if(U.length>0){console.log(),console.log(` ${xX("Links","2")}`);for(let Y of U){let D=Y.from_task_id===G?Y.to_task_id:Y.from_task_id;console.log(` ${Y.link_type} \u2192 [${i(D)}]`)}}console.log()}),$.command("update <id>").description("Update a task").option("--title <t>","New title").option("--body <text>","New body").option("--status <s>","New status (todo/doing/review/changes_requested/blocked/done/cancelled)").option("--priority <p>","Priority (low/normal/high/critical)").option("--progress <n>","Progress (0-100)").option("--tags <csv>","Comma-separated tags").option("-p, --project <name>","Move to project").option("--no-project","Remove from project").action(async(w,X)=>{let G=G0(),E=await fX(G,w);if(!E)return;let J={};if(X.title)J.title=X.title;if(X.body)J.body=X.body;if(X.status)J.status=X.status;if(X.priority)J.priority=X.priority;if(X.progress!==void 0&&X.progress!==!0)J.progress=Number(X.progress);if(X.tags)J.tags=X.tags.split(",").map((Y)=>Y.trim());if(X.project===!1)J.project_id=null;else if(typeof X.project==="string"){let Y=await g1(G,{project:X.project,noProject:!1});if(Y)J.project_id=Y.id}if(Object.keys(J).length===0)return console.error("No updates specified. Use --help to see options.");if(D1())return O1("update",`task [${i(E)}]`,J);let{data:W,error:U}=await G.tasks.update(E,J);if(U){if(l())return B1(String(U));return console.error("Error:",U)}if(l())return E0(W);console.log(`Updated: [${i(W?.id)}] ${W?.title}`)}),$.command("delete <id>").description("Delete a task").action(async(w)=>{let X=G0(),G=await fX(X,w);if(!G)return;if(D1())return O1("delete",`task [${i(G)}]`);let{error:E}=await X.tasks.delete(G);if(E){if(l())return B1(String(E));return console.error("Error:",E)}if(l())return E0({deleted:G});console.log(`Deleted: [${i(G)}]`)}),$}function xX($,w){return process.stdout.isTTY?`\x1B[${w}m${$}\x1B[0m`:$}var of={review:"33",changes_requested:"33",doing:"36",blocked:"31",todo:"2",done:"32",cancelled:"2"};function W7($,w){return xX($,of[w]??"0")}function U7($){return{todo:"\u25CB",doing:"\u25D0",review:"\u25C9",changes_requested:"\u21A9",blocked:"\u2298",done:"\u25CF",cancelled:"\u2715"}[$]??"?"}function nQ($){return{low:"\u2193",normal:"\u2192",high:"\u2191",critical:"\u203C"}[$]??" "}function cQ($,w){let X=U7($.status),G=nQ($.priority),E=i($.id),J=$.title.length>40?`${$.title.slice(0,39)}\u2026`:$.title,A=` ${W7(X,$.status)} ${G} [${E}] ${J.padEnd(40)}`;if(w&&$.project_id)A+=` ${xX(w.get($.project_id)??i($.project_id),"2")}`;return A}var i$=new G$().name("orc").description("Human + AI Orchestration Hub").version(WG).option("--db <path>","DB file path (overrides ORC_DB_PATH / config.json)").option("--port <n>","API port (overrides ORC_API_PORT / config.json)").option("--host <host>","API host (overrides ORC_API_HOST / config.json)").option("--secret <secret>","API bearer secret (overrides ORC_API_SECRET / config.json)").option("--log-level <level>","Log level: debug|info|warn|error (overrides ORC_LOG_LEVEL)").option("--runner-timeout <secs>","Default job timeout in seconds (overrides ORC_RUNNER_TIMEOUT)").option("--runner-max-jobs <n>","Max concurrent jobs (overrides ORC_RUNNER_MAX_JOBS)").option("--snapshot-max-bytes <n>","Session snapshot budget bytes (overrides ORC_SNAPSHOT_MAX_BYTES)").option("--json","Machine-readable JSON output").option("--dry-run","Preview changes without executing").hook("preSubcommand",($,w)=>{let X=i$.opts();if(X.db)process.env.ORC_DB_PATH=X.db;if(X.port)process.env.ORC_API_PORT=X.port;if(X.host)process.env.ORC_API_HOST=X.host;if(X.secret)process.env.ORC_API_SECRET=X.secret;if(X.logLevel)process.env.ORC_LOG_LEVEL=X.logLevel;if(X.runnerTimeout)process.env.ORC_RUNNER_TIMEOUT=X.runnerTimeout;if(X.runnerMaxJobs)process.env.ORC_RUNNER_MAX_JOBS=X.runnerMaxJobs;if(X.snapshotMaxBytes)process.env.ORC_SNAPSHOT_MAX_BYTES=X.snapshotMaxBytes});i$.addCommand(mQ());i$.addCommand(bQ());i$.addCommand(vQ());i$.addCommand(kQ());i$.addCommand(gQ());i$.addCommand(qQ());i$.addCommand(SQ());i$.addCommand(hQ());i$.addCommand(uQ());i$.addCommand(xQ());i$.command("api").description("Start the API server (use --port / --host / --db / --secret to configure)").action(async()=>{await Promise.resolve().then(() => (X7(),w7)),await new Promise(()=>{})});i$.command("home").description("Show ~/.orc directory contents and daemon state").action(()=>{let $=s();console.log(`ORC home: ${ew}
|
|
597
597
|
`);let w=[{label:"DB",path:$.db.path},{label:"config",path:`${ew}/config.json`},{label:"pid",path:hG},{label:"daemon log",path:`${ew}/daemon.log`}];for(let G of w)if(Ki(G.path)){let E=Math.round(ji(G.path).size/1024);console.log(` \u2713 ${G.label.padEnd(12)} ${G.path} (${E}KB)`)}else console.log(` \xB7 ${G.label.padEnd(12)} ${G.path} (not found)`);let X=MJ();if(console.log(),X)try{process.kill(X,0),console.log(` daemon \u25CF running pid:${X}`)}catch{console.log(` daemon \u25CB stopped (stale pid: ${X})`)}else console.log(" daemon \u25CB not running");console.log(),console.log(` config port:${$.api.port} host:${$.api.host} secret:${$.api.secret?"***":"(none)"}`)});i$.command("mcp").description("Start the MCP server (stdio)").action(async()=>{let{startStdioServer:$}=await q2().then(() => P2);await $()});i$.parseAsync(process.argv).catch(($)=>{console.error($ instanceof Error?$.message:$),process.exit(1)});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "orc-ai",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "Human + AI Orchestration Hub — persistent memory, task management, session continuity and multi-agent collaboration for Cursor, Claude Code, and Codex",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -12,12 +12,13 @@
|
|
|
12
12
|
"typecheck": "tsc --noEmit",
|
|
13
13
|
"build": "bun build src/index.ts --outfile dist/index.js --target bun --minify",
|
|
14
14
|
"build:bin": "bun run build.ts",
|
|
15
|
-
"prepublishOnly": "bun run build",
|
|
15
|
+
"prepublishOnly": "cp ../../README.md . && bun run build",
|
|
16
16
|
"test": "bun test",
|
|
17
17
|
"clean": "rm -rf dist"
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
|
-
"dist/index.js"
|
|
20
|
+
"dist/index.js",
|
|
21
|
+
"README.md"
|
|
21
22
|
],
|
|
22
23
|
"devDependencies": {
|
|
23
24
|
"@orc/api": "workspace:*",
|