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.
Files changed (3) hide show
  1. package/README.md +448 -79
  2. package/dist/index.js +1 -1
  3. package/package.json +4 -3
package/README.md CHANGED
@@ -1,50 +1,132 @@
1
- # orc — Human + AI Orchestration Hub
1
+ <div align="center">
2
2
 
3
- > Persistent memory · Task management with HITL review · Generic job runner · MCP server for Claude Code, Cursor, Codex, and Gemini CLI.
4
- >
5
- > One SQLite file. Shared across every agent you use.
3
+ # orc
6
4
 
7
- ## Install
5
+ **Multi-agent orchestrator with human-in-the-loop review**
6
+
7
+ [![npm version](https://img.shields.io/npm/v/orc-ai?style=flat-square)](https://www.npmjs.com/package/orc-ai)
8
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue?style=flat-square)](LICENSE)
9
+ ![Bun](https://img.shields.io/badge/Bun-%3E%3D1.1-f472b6?style=flat-square&logo=bun&logoColor=white)
10
+ ![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178c6?style=flat-square&logo=typescript&logoColor=white)
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
- > **Requires [Bun](https://bun.sh) ≥ 1.1** to run. Bun is used as the runtime — install it once, then `orc` works everywhere.
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
- ## What it does
72
+ # Windows download orc-windows-x64.exe from the release page and add to PATH
73
+ ```
74
+
75
+ #### From source
18
76
 
19
- ORC is the shared brain between you and your AI agents. Every agent connects to the same store of tasks, memories, and jobs — so when you switch from Claude Code to Cursor, context doesn't evaporate.
77
+ ```bash
78
+ git clone https://github.com/niradler/orc
79
+ cd orc && bun install && bun build
80
+ ```
20
81
 
21
- - **Shared memory** — store decisions, rules, discoveries once; any agent can search them
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
- ## Quick Start
84
+ ### Quick start
29
85
 
30
86
  ```bash
31
- # Start the API (keeps the DB open and serves the REST + MCP endpoint)
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
- # In another terminal — try it
35
- orc status
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 --scope myproject
38
- orc job add nightly --command "echo hello" --trigger cron --cron "0 22 * * *"
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
- The database is created automatically at `~/.orc/orc.db` on first run.
104
+ > [!TIP]
105
+ > The database is created automatically at `~/.orc/orc.db` on first run. No setup needed.
42
106
 
43
- ## Agent Setup
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` in your project (or the global Cursor MCP config):
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
- ### Claude Code
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` from the [orc repo](https://github.com/niradler/orc) to `~/.codex/settings.json`.
146
+ **Codex** — Copy `hooks/codex/settings.json` to `~/.codex/settings.json` and update the path.
71
147
 
72
- ### Gemini CLI
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
- ## CLI Reference
162
+ </details>
163
+
164
+ ## Usage
165
+
166
+ ### How agents use ORC
90
167
 
91
168
  ```
92
- orc daemon start Start API + scheduler + file-watchers + gateway
93
- orc daemon stop Send SIGTERM to running daemon
94
- orc daemon status Show scheduler state + next run times per job
95
- orc home Show ~/.orc directory, daemon state, and config
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
- orc api Start the API server only (no scheduler)
98
- orc mcp Start the MCP server in stdio mode
99
- orc status Show API health, task count, memory count
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
- orc task list [--status] List tasks (default: active)
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
- orc mem list List recent memories (--limit)
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
- orc job list List all jobs with trigger type and run count
113
- orc job add <name> Create a job (--command, --trigger, --cron, --watch)
114
- orc job run <name> Trigger a job immediately
115
- orc job runs <name> Show run history (--logs, --sessions, --limit)
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
- orc session list List recent agent sessions
118
- orc session show <id> Show session detail
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
- ## Configuration
221
+ ### Projects
122
222
 
123
- Create `~/.orc/config.json`:
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
- "api": {
128
- "port": 7700,
129
- "host": "127.0.0.1",
130
- "secret": "optional-bearer-token"
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
- Key env vars: `ORC_DB_PATH`, `ORC_API_PORT` (default 7700), `ORC_API_SECRET`, `ORC_SESSION_ID`, `ORC_LOG_LEVEL`.
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
- ## Cross-agent collaboration
461
+ ### Logs
138
462
 
139
- All agents share one SQLite file intentionally.
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
- Claude Code ──┐
143
- Cursor ──┤──→ ~/.orc/orc.db ←── orc cli (you)
144
- Codex ──┘
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
- A task created by Claude Code appears in Cursor's context. A rule stored by Codex shows up in Claude Code's memory search.
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
- Set `ORC_SESSION_ID` per agent (e.g. `cursor`, `codex`, `claude-code`) so sessions don't collide.
515
+ See [AGENTS.md](./AGENTS.md) for the full development guide and coding conventions.
150
516
 
151
- ---
517
+ ## Learn more
152
518
 
153
- Full documentation: [github.com/niradler/orc](https://github.com/niradler/orc)
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.8",
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:*",