@synaplink/orqlaude 0.5.1 → 0.5.5

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 (42) hide show
  1. package/README.md +49 -12
  2. package/dist/__tests__/desktop_config.test.d.ts +1 -0
  3. package/dist/__tests__/desktop_config.test.js +110 -0
  4. package/dist/__tests__/desktop_config.test.js.map +1 -0
  5. package/dist/__tests__/v05.test.js +6 -2
  6. package/dist/__tests__/v05.test.js.map +1 -1
  7. package/dist/__tests__/v052.test.d.ts +1 -0
  8. package/dist/__tests__/v052.test.js +53 -0
  9. package/dist/__tests__/v052.test.js.map +1 -0
  10. package/dist/__tests__/v053.test.d.ts +1 -0
  11. package/dist/__tests__/v053.test.js +89 -0
  12. package/dist/__tests__/v053.test.js.map +1 -0
  13. package/dist/cli.js +118 -0
  14. package/dist/cli.js.map +1 -1
  15. package/dist/lib/desktop_config.d.ts +66 -0
  16. package/dist/lib/desktop_config.js +126 -0
  17. package/dist/lib/desktop_config.js.map +1 -0
  18. package/dist/lib/spawn_cli.d.ts +79 -0
  19. package/dist/lib/spawn_cli.js +211 -0
  20. package/dist/lib/spawn_cli.js.map +1 -0
  21. package/dist/lib/state.d.ts +10 -6
  22. package/dist/lib/state.js.map +1 -1
  23. package/dist/lib/telegram_status.d.ts +24 -0
  24. package/dist/lib/telegram_status.js +87 -0
  25. package/dist/lib/telegram_status.js.map +1 -0
  26. package/dist/server.js +1 -1
  27. package/dist/telegram/api.d.ts +0 -20
  28. package/dist/telegram/api.js +0 -32
  29. package/dist/telegram/api.js.map +1 -1
  30. package/dist/telegram/bot.js +54 -6
  31. package/dist/telegram/bot.js.map +1 -1
  32. package/dist/telegram/notifier.js +10 -98
  33. package/dist/telegram/notifier.js.map +1 -1
  34. package/dist/tools/broker.js +47 -1
  35. package/dist/tools/broker.js.map +1 -1
  36. package/dist/tools/dispatch.js +163 -23
  37. package/dist/tools/dispatch.js.map +1 -1
  38. package/dist/tools/ping.js +16 -2
  39. package/dist/tools/ping.js.map +1 -1
  40. package/dist/tools/userio.js +40 -9
  41. package/dist/tools/userio.js.map +1 -1
  42. package/package.json +3 -2
package/README.md CHANGED
@@ -44,20 +44,55 @@ orqlaude is the thin layer that adds those things. It never spawns processes its
44
44
  npm install -g @synaplink/orqlaude # CLI + MCP server
45
45
  ```
46
46
 
47
- In your project, add to `.mcp.json` (or copy `.mcp.json.template`):
47
+ Then wire it into Claude Desktop's MCP config in one command:
48
+
49
+ ```sh
50
+ cd /path/to/your/project
51
+ orql setup
52
+ ```
53
+
54
+ `orql setup` **patches** Claude Desktop's `claude_desktop_config.json` in place — adds an `orqlaude` MCP server entry pointed at this project's state dir, and **preserves every other server you have configured** (lm-studio, etc.) plus the entire `preferences` block. Writes a timestamped `.bak` before changing anything. Re-runs are idempotent — if the entry is already correct, it reports `already correct; nothing to do` and exits without touching the file.
55
+
56
+ Flags:
57
+ - `--state-dir <path>` override the default (which walks up from cwd for a `.git`)
58
+ - `--config-path <path>` override Claude Desktop's config path
59
+ - `--yes` skip prompts
60
+
61
+ Fully quit and relaunch Claude Desktop after running. The `mcp__orqlaude__*` tools will then appear in your sessions.
62
+
63
+ If you'd rather edit the config yourself, the entry should look like:
48
64
 
49
65
  ```json
50
66
  {
51
67
  "mcpServers": {
52
68
  "orqlaude": {
53
69
  "command": "npx",
54
- "args": ["-y", "@synaplink/orqlaude"]
70
+ "args": ["-y", "-p", "@synaplink/orqlaude", "orqlaude-mcp"],
71
+ "env": {
72
+ "ORQLAUDE_STATE_DIR": "/absolute/path/to/your/project/.orqlaude"
73
+ }
55
74
  }
56
75
  }
57
76
  }
58
77
  ```
59
78
 
60
- Restart your Claude Code session. The `mcp__orqlaude__*` tools will appear.
79
+ The `ORQLAUDE_STATE_DIR` env var is important — it pins state to a specific path so the MCP server (running with `cwd=/` on some hosts) and the Telegram bot (running in your project) share the same state file.
80
+
81
+ ## Spawning Agnets: which tool to use
82
+
83
+ orqlaude itself doesn't spawn processes — it returns prompts and lets the orchestrator pick a spawn tool. Use them in this priority order:
84
+
85
+ | Priority | Tool | Isolation | Visibility | When to use |
86
+ |---|---|---|---|---|
87
+ | **1** | `mcp__ccd_session__spawn_task` | git worktree per Agnet | Claude Desktop Code sidebar | **Default.** Worktree-isolated, sandbox-clean, the Agnet shows up as its own session you can switch into. |
88
+ | 2 | Host's `Agent` tool (Claude Code built-in) | none — shares your cwd | tool-use only, not a separate session | Faster, no chip-click. **Loses worktree isolation** — Agnets may race on shared files. `claim_files` from the broker is your only collision signal. |
89
+ | 3 | Shell out `claude -p --worktree …` | explicit `--worktree` flag | JSONL on disk — not in sidebar until Desktop restart | Headless / cron / non-Desktop hosts. |
90
+
91
+ `next_task` returns a `spawn_strategies[]` array with ready-to-call args for each option, so the orchestrator can pick deliberately. **Picking by habit is the most common way to bypass orqlaude's isolation guarantees** — check the returned strategies and make a conscious choice.
92
+
93
+ ### Orphan detection
94
+
95
+ If an Agnet is dispatched but doesn't call `mcp__orqlaude__checkin` within 60 s, `status()` flags it in `orphan_alerts[]`. Common cause: the orchestrator used a non-`ccd_session__spawn_task` path and the Agnet skipped (or never reached) the protocol footer that tells it to register.
61
96
 
62
97
  ## Tool reference
63
98
 
@@ -116,13 +151,13 @@ Without a running `orqlaude tg start`, `notify_user` queues silently, `request_u
116
151
 
117
152
  #### Streaming transport
118
153
 
119
- orqlaude streams via Telegram's `sendMessageDraft` endpoint (the native streaming preview API, intended for agent output). Drafts are ephemeral (~30s) and updates that share the same `draft_id` are animated client-side. When a stream ends, orqlaude follows up with a `sendMessage` to persist the final content as a normal chat message with a `✓` marker.
154
+ orqlaude streams by opening a single Telegram message with `sendMessage`, then calling `editMessageText` on each append. The final `stream_to_user_end` does one more edit appending a `✓` marker.
120
155
 
121
- If the bot's Telegram server doesn't yet expose `sendMessageDraft` (older deployments), orqlaude falls back to a single `sendMessage` + repeated `editMessageText` per stream. The fallback is transparent you don't need to do anything; the notifier flips a `transport: "edit"` flag on the stream and continues.
156
+ (v0.5.1 briefly used `sendMessageDraft` for animated, ephemeral previews reverted in v0.5.4 after that endpoint proved unreliable in the standard Bot API.)
122
157
 
123
158
  Limits to know:
124
159
  - A Telegram message tops out at 4096 chars. orqlaude caps stream content at 3800 to leave room for the title + completion marker; further appends are silently truncated.
125
- - The draft path throttles at 400 ms between updates per stream; the edit fallback throttles at 1.5 s (matching the Bot API rate limit for `editMessageText`).
160
+ - Edits are rate-limited (~1/sec per message); orqlaude throttles to 1.5 s between edits per stream.
126
161
  - If you need to stream more than 4 kb of output, start a new stream when you're approaching the cap.
127
162
 
128
163
  ### Health
@@ -191,13 +226,15 @@ False positives are acceptable here — we surface concerns, we don't auto-kill.
191
226
 
192
227
  ## CLI
193
228
 
229
+ Two binaries are installed: `orqlaude` and the short alias `orql`. Use whichever feels right.
230
+
194
231
  ```sh
195
- orqlaude list # every plan in this project
196
- orqlaude status <plan_id> # refreshed status of one plan
197
- orqlaude show <plan_id> # raw plan JSON
198
- orqlaude history --limit 50 # tail audit log
199
- orqlaude where # show resolved state dir
200
- orqlaude help
232
+ orql list # every plan in this project
233
+ orql status <plan_id> # refreshed status of one plan
234
+ orql show <plan_id> # raw plan JSON
235
+ orql history --limit 50 # tail audit log
236
+ orql where # show resolved state dir
237
+ orql help
201
238
  ```
202
239
 
203
240
  Read-only. For active orchestration, use the MCP from inside Claude Code.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,110 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { promises as fs } from "node:fs";
4
+ import path from "node:path";
5
+ import os from "node:os";
6
+ import { planPatch, buildOrqlaudeEntry, readDesktopConfig, writeDesktopConfigAtomic, findDesktopConfigPath, } from "../lib/desktop_config.js";
7
+ /** v0.5.5: regression tests for the desktop_config patcher. */
8
+ const STATE_DIR = "/Users/matthew/Documents/orqlaude/.orqlaude";
9
+ test("planPatch with no existing config creates one + adds orqlaude entry", () => {
10
+ const result = planPatch(null, STATE_DIR);
11
+ assert.equal(result.action, "create-config");
12
+ assert.equal(result.before, null);
13
+ assert.equal(result.after.command, "npx");
14
+ assert.deepEqual(result.after.args, ["-y", "-p", "@synaplink/orqlaude", "orqlaude-mcp"]);
15
+ assert.equal(result.after.env?.ORQLAUDE_STATE_DIR, STATE_DIR);
16
+ });
17
+ test("planPatch with existing config but no orqlaude key adds it without touching others", () => {
18
+ const existing = {
19
+ mcpServers: {
20
+ "lm-studio": {
21
+ command: "npx",
22
+ args: ["-y", "@mzxrai/mcp-openai"],
23
+ env: { OPENAI_API_KEY: "lm-studio" },
24
+ },
25
+ },
26
+ preferences: { someFlag: true },
27
+ };
28
+ const result = planPatch(existing, STATE_DIR);
29
+ assert.equal(result.action, "create-server");
30
+ // Original is preserved
31
+ assert.deepEqual(result.config.mcpServers["lm-studio"], existing.mcpServers["lm-studio"]);
32
+ assert.deepEqual(result.config.preferences, { someFlag: true });
33
+ // New entry present
34
+ assert.equal(result.config.mcpServers["orqlaude"].env?.ORQLAUDE_STATE_DIR, STATE_DIR);
35
+ });
36
+ test("planPatch with correct orqlaude entry already present → noop", () => {
37
+ const existing = {
38
+ mcpServers: { orqlaude: buildOrqlaudeEntry(STATE_DIR) },
39
+ };
40
+ const result = planPatch(existing, STATE_DIR);
41
+ assert.equal(result.action, "noop");
42
+ });
43
+ test("planPatch with orqlaude entry at wrong state dir → update; preserve other env keys", () => {
44
+ const existing = {
45
+ mcpServers: {
46
+ orqlaude: {
47
+ command: "npx",
48
+ args: ["-y", "-p", "@synaplink/orqlaude", "orqlaude-mcp"],
49
+ env: { ORQLAUDE_STATE_DIR: "/old/path", CUSTOM_DEBUG_FLAG: "1" },
50
+ },
51
+ },
52
+ };
53
+ const result = planPatch(existing, STATE_DIR);
54
+ assert.equal(result.action, "update-server");
55
+ // New state dir applied
56
+ assert.equal(result.after.env?.ORQLAUDE_STATE_DIR, STATE_DIR);
57
+ // Custom env key preserved
58
+ assert.equal(result.after.env?.CUSTOM_DEBUG_FLAG, "1");
59
+ });
60
+ test("planPatch preserves top-level keys we don't recognize", () => {
61
+ const existing = {
62
+ mcpServers: {},
63
+ preferences: { x: 1 },
64
+ futureField: { whatever: true },
65
+ };
66
+ const result = planPatch(existing, STATE_DIR);
67
+ assert.equal(result.action, "create-server");
68
+ assert.deepEqual(result.config.preferences, { x: 1 });
69
+ assert.deepEqual(result.config.futureField, { whatever: true });
70
+ });
71
+ test("writeDesktopConfigAtomic creates a timestamped backup when the file exists", async () => {
72
+ const dir = await fs.mkdtemp(path.join(os.tmpdir(), "orqlaude-desktop-"));
73
+ const filePath = path.join(dir, "config.json");
74
+ await fs.writeFile(filePath, JSON.stringify({ original: true }, null, 2));
75
+ const { backupPath } = await writeDesktopConfigAtomic(filePath, { updated: true });
76
+ assert.ok(backupPath, "expected a backup path");
77
+ const backed = JSON.parse(await fs.readFile(backupPath, "utf8"));
78
+ assert.deepEqual(backed, { original: true });
79
+ const updated = JSON.parse(await fs.readFile(filePath, "utf8"));
80
+ assert.deepEqual(updated, { updated: true });
81
+ });
82
+ test("writeDesktopConfigAtomic creates parent dirs + no backup on fresh file", async () => {
83
+ const dir = await fs.mkdtemp(path.join(os.tmpdir(), "orqlaude-desktop-"));
84
+ const filePath = path.join(dir, "nested", "deep", "config.json");
85
+ const { backupPath } = await writeDesktopConfigAtomic(filePath, { fresh: true });
86
+ assert.equal(backupPath, null);
87
+ const got = JSON.parse(await fs.readFile(filePath, "utf8"));
88
+ assert.deepEqual(got, { fresh: true });
89
+ });
90
+ test("readDesktopConfig returns null for missing files; throws on malformed JSON", async () => {
91
+ const dir = await fs.mkdtemp(path.join(os.tmpdir(), "orqlaude-desktop-"));
92
+ const filePath = path.join(dir, "config.json");
93
+ assert.equal(await readDesktopConfig(filePath), null);
94
+ await fs.writeFile(filePath, "{ not valid json,,,");
95
+ await assert.rejects(() => readDesktopConfig(filePath), /Failed to parse/);
96
+ });
97
+ test("findDesktopConfigPath returns a platform-appropriate path", () => {
98
+ const p = findDesktopConfigPath();
99
+ assert.ok(p.endsWith("claude_desktop_config.json"), `got: ${p}`);
100
+ if (process.platform === "darwin") {
101
+ assert.ok(p.includes("Library/Application Support/Claude"), `got: ${p}`);
102
+ }
103
+ else if (process.platform === "win32") {
104
+ assert.ok(p.includes("Claude"), `got: ${p}`);
105
+ }
106
+ else {
107
+ assert.ok(p.includes(".config/Claude"), `got: ${p}`);
108
+ }
109
+ });
110
+ //# sourceMappingURL=desktop_config.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"desktop_config.test.js","sourceRoot":"","sources":["../../src/__tests__/desktop_config.test.ts"],"names":[],"mappings":"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"}
@@ -72,9 +72,13 @@ test("v0.5 schema: newPlan initializes userStreams empty", async () => {
72
72
  });
73
73
  assert.deepEqual(plan.userStreams, []);
74
74
  });
75
- test("TelegramApi.sendMessageDraft rejects draft_id=0 (v0.5.1)", async () => {
75
+ test("TelegramApi has the expected post-v0.5.4 surface", async () => {
76
+ // v0.5.4 removed sendMessageDraft after sendMessage proved unreliable in
77
+ // the standard Bot API. Confirm it's no longer exported.
76
78
  const { TelegramApi } = await import("../telegram/api.js");
77
79
  const api = new TelegramApi("test:token");
78
- await assert.rejects(() => api.sendMessageDraft(123, 0, "hi"), /must be a non-zero integer/);
80
+ assert.equal(typeof api.sendMessage, "function");
81
+ assert.equal(typeof api.editMessageText, "function");
82
+ assert.equal(api.sendMessageDraft, undefined);
79
83
  });
80
84
  //# sourceMappingURL=v05.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"v05.test.js","sourceRoot":"","sources":["../../src/__tests__/v05.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"v05.test.js","sourceRoot":"","sources":["../../src/__tests__/v05.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,53 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { promises as fs } from "node:fs";
4
+ import path from "node:path";
5
+ import os from "node:os";
6
+ import { StateStore, newPlan, findPlan } from "../lib/state.js";
7
+ /** v0.5.2: orphan detection on dispatched-but-unregistered Agnets. */
8
+ async function tmpStore() {
9
+ const dir = await fs.mkdtemp(path.join(os.tmpdir(), "orqlaude-v052-"));
10
+ return { store: new StateStore(dir), dir };
11
+ }
12
+ test("orphan: task dispatched > 60s ago with no spawnedSessionId is detectable", async () => {
13
+ const { store } = await tmpStore();
14
+ let planId = "";
15
+ await store.update((s) => {
16
+ const p = newPlan("root", 100_000, [
17
+ { title: "T1", prompt: "p", tldr: "tl" },
18
+ { title: "T2", prompt: "p", tldr: "tl" },
19
+ ]);
20
+ p.tasks[0].status = "dispatched";
21
+ p.tasks[0].startedAt = Date.now() - 120_000; // 2 min ago
22
+ // Task 1 has no spawnedSessionId — orphan.
23
+ p.tasks[1].status = "dispatched";
24
+ p.tasks[1].startedAt = Date.now() - 30_000; // 30s ago, NOT orphan yet
25
+ p.tasks[1].spawnedSessionId = "registered";
26
+ planId = p.id;
27
+ s.plans[p.id] = p;
28
+ });
29
+ const plan = await store.read((s) => findPlan(s, planId));
30
+ const ORPHAN_MS = 60_000;
31
+ const orphans = plan.tasks.filter((t) => t.status === "dispatched" &&
32
+ !t.spawnedSessionId &&
33
+ t.startedAt &&
34
+ Date.now() - t.startedAt > ORPHAN_MS);
35
+ assert.equal(orphans.length, 1);
36
+ assert.equal(orphans[0].title, "T1");
37
+ });
38
+ test("orphan: dispatched < 60s ago is NOT yet an orphan", async () => {
39
+ const { store } = await tmpStore();
40
+ await store.update((s) => {
41
+ const p = newPlan("root", 100_000, [{ title: "T", prompt: "p", tldr: "tl" }]);
42
+ p.tasks[0].status = "dispatched";
43
+ p.tasks[0].startedAt = Date.now() - 10_000; // 10s ago
44
+ s.plans[p.id] = p;
45
+ });
46
+ const plan = await store.read((s) => Object.values(s.plans)[0]);
47
+ const orphans = plan.tasks.filter((t) => t.status === "dispatched" &&
48
+ !t.spawnedSessionId &&
49
+ t.startedAt &&
50
+ Date.now() - t.startedAt > 60_000);
51
+ assert.equal(orphans.length, 0);
52
+ });
53
+ //# sourceMappingURL=v052.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v052.test.js","sourceRoot":"","sources":["../../src/__tests__/v052.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,89 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { promises as fs } from "node:fs";
4
+ import path from "node:path";
5
+ import os from "node:os";
6
+ import { StateStore, newPlan, planForSession, unclaimedTaskById } from "../lib/state.js";
7
+ /**
8
+ * v0.5.3: regression tests for checkin conflict detection. We test the
9
+ * underlying state lookup primitives rather than the MCP tool itself —
10
+ * the conflict-detection branches in broker.ts use exactly these helpers.
11
+ */
12
+ async function tmpStore() {
13
+ const dir = await fs.mkdtemp(path.join(os.tmpdir(), "orqlaude-v053-"));
14
+ return new StateStore(dir);
15
+ }
16
+ test("v0.5.3: session bound to task A cannot be retargeted by passing task B's id", async () => {
17
+ const store = await tmpStore();
18
+ let planId = "";
19
+ let taskAId = "";
20
+ let taskBId = "";
21
+ await store.update((s) => {
22
+ const p = newPlan("root", 100_000, [
23
+ { title: "A", prompt: "a", tldr: "a" },
24
+ { title: "B", prompt: "b", tldr: "b" },
25
+ ]);
26
+ p.tasks[0].status = "dispatched";
27
+ p.tasks[0].spawnedSessionId = "session-X";
28
+ p.tasks[1].status = "dispatched";
29
+ planId = p.id;
30
+ taskAId = p.tasks[0].id;
31
+ taskBId = p.tasks[1].id;
32
+ s.plans[p.id] = p;
33
+ });
34
+ // Caller session X is already bound to task A. If they pass task_id=B,
35
+ // planForSession should return A — not silently switch them to B.
36
+ const result = await store.read((state) => {
37
+ const found = planForSession(state, "session-X");
38
+ return found ? { taskId: found.task.id, title: found.task.title } : null;
39
+ });
40
+ assert.ok(result, "session should be found");
41
+ assert.equal(result.taskId, taskAId);
42
+ assert.notEqual(result.taskId, taskBId);
43
+ });
44
+ test("v0.5.3: task already claimed by another session cannot be re-claimed via task_id", async () => {
45
+ const store = await tmpStore();
46
+ let taskId = "";
47
+ await store.update((s) => {
48
+ const p = newPlan("root", 100_000, [{ title: "T", prompt: "p", tldr: "tl" }]);
49
+ p.tasks[0].status = "running";
50
+ p.tasks[0].spawnedSessionId = "first-session";
51
+ taskId = p.tasks[0].id;
52
+ s.plans[p.id] = p;
53
+ });
54
+ // A different session calls checkin with that task_id.
55
+ // unclaimedTaskById should return undefined because the task IS claimed.
56
+ const target = await store.read((state) => unclaimedTaskById(state, taskId));
57
+ assert.equal(target, undefined);
58
+ });
59
+ test("v0.5.3: spawn_cli util discovers git root via .git directory", async () => {
60
+ const dir = await fs.mkdtemp(path.join(os.tmpdir(), "orqlaude-git-root-"));
61
+ await fs.mkdir(path.join(dir, ".git"));
62
+ await fs.mkdir(path.join(dir, "nested", "deeper"), { recursive: true });
63
+ const { findGitRoot } = await import("../lib/spawn_cli.js");
64
+ const real = await fs.realpath(dir);
65
+ assert.equal(findGitRoot(path.join(real, "nested", "deeper")), real);
66
+ });
67
+ test("v0.5.3: telegram_status returns 'unconfigured' when no config file exists", async () => {
68
+ const stateDir = await fs.mkdtemp(path.join(os.tmpdir(), "orqlaude-tg-status-"));
69
+ const { probeTelegramStatus } = await import("../lib/telegram_status.js");
70
+ // Override HOME so it can't find a real config.
71
+ const realHome = process.env.HOME;
72
+ const fakeHome = await fs.mkdtemp(path.join(os.tmpdir(), "orqlaude-fakehome-"));
73
+ process.env.HOME = fakeHome;
74
+ try {
75
+ const status = await probeTelegramStatus(stateDir);
76
+ // We can't be certain the user has no real config — but the path under
77
+ // a freshly-minted fake HOME doesn't exist, so status should be
78
+ // unconfigured.
79
+ assert.equal(status.status, "unconfigured");
80
+ assert.equal(status.hasToken, false);
81
+ }
82
+ finally {
83
+ if (realHome)
84
+ process.env.HOME = realHome;
85
+ else
86
+ delete process.env.HOME;
87
+ }
88
+ });
89
+ //# sourceMappingURL=v053.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v053.test.js","sourceRoot":"","sources":["../../src/__tests__/v053.test.ts"],"names":[],"mappings":"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"}
package/dist/cli.js CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import path from "node:path";
2
3
  import readline from "node:readline/promises";
3
4
  import { stdin, stdout } from "node:process";
4
5
  import { StateStore } from "./lib/state.js";
@@ -10,6 +11,8 @@ import { runBot } from "./telegram/bot.js";
10
11
  import { resolveStateDir } from "./lib/state_dir.js";
11
12
  import { style, styleStatus, banner } from "./lib/style.js";
12
13
  import { agnetLabel } from "./lib/agnet.js";
14
+ import { findDesktopConfigPath, readDesktopConfig, planPatch, writeDesktopConfigAtomic, } from "./lib/desktop_config.js";
15
+ import { existsSync } from "node:fs";
13
16
  /**
14
17
  * orqlaude CLI — read-only inspection of state + audit log + Telegram setup
15
18
  * and run.
@@ -40,6 +43,8 @@ async function main() {
40
43
  return await cmdHistory(parseLimit(rest));
41
44
  case "where":
42
45
  return cmdWhere();
46
+ case "setup":
47
+ return await cmdSetup(rest);
43
48
  case "tg":
44
49
  return await cmdTg(rest);
45
50
  default:
@@ -51,6 +56,10 @@ async function main() {
51
56
  function printHelp() {
52
57
  console.log(banner());
53
58
  console.log("");
59
+ console.log(style.bold(style.cream("Setup")));
60
+ console.log(` ${style.coral("orql setup")} ${style.sand("[--state-dir PATH] [--yes]")}`);
61
+ console.log(` Wire orqlaude into Claude Desktop's MCP config (idempotent, preserves other servers)`);
62
+ console.log("");
54
63
  console.log(style.bold(style.cream("Inspection")));
55
64
  console.log(` ${style.coral("orqlaude list")} List every plan in this project`);
56
65
  console.log(` ${style.coral("orqlaude status")} ${style.sand("<plan_id>")} Refreshed status of one plan`);
@@ -154,6 +163,115 @@ async function cmdShow(planId) {
154
163
  return 1;
155
164
  }
156
165
  }
166
+ // ---- setup -----------------------------------------------------------------
167
+ /**
168
+ * Detect a sensible default state dir for the current shell. Walks up from
169
+ * cwd for a `.git`; falls back to cwd.
170
+ */
171
+ function defaultStateDir() {
172
+ let dir = process.cwd();
173
+ for (let i = 0; i < 10; i++) {
174
+ if (existsSync(path.join(dir, ".git"))) {
175
+ return path.join(dir, ".orqlaude");
176
+ }
177
+ const parent = path.dirname(dir);
178
+ if (parent === dir)
179
+ break;
180
+ dir = parent;
181
+ }
182
+ return path.join(process.cwd(), ".orqlaude");
183
+ }
184
+ async function cmdSetup(args) {
185
+ console.log(banner());
186
+ console.log("");
187
+ console.log(style.bold(style.cream("Setup")));
188
+ console.log("Wires orqlaude into Claude Desktop's MCP config.");
189
+ console.log("");
190
+ // Parse flags
191
+ const stateDirIdx = args.indexOf("--state-dir");
192
+ const overrideStateDir = stateDirIdx !== -1 ? args[stateDirIdx + 1] : null;
193
+ const yes = args.includes("--yes") || args.includes("-y");
194
+ const configPathIdx = args.indexOf("--config-path");
195
+ const configPath = configPathIdx !== -1 ? args[configPathIdx + 1] : findDesktopConfigPath();
196
+ console.log(` ${style.sand("config:")} ${configPath}`);
197
+ // 1. Read existing config (preserve everything we don't own).
198
+ let existing;
199
+ try {
200
+ existing = await readDesktopConfig(configPath);
201
+ }
202
+ catch (err) {
203
+ console.error("");
204
+ console.error(style.crimson(`✗ ${err.message}`));
205
+ return 1;
206
+ }
207
+ if (!existing) {
208
+ console.log(` ${style.sand("status:")} ${style.crimson("config file does not exist — will create")}`);
209
+ }
210
+ else {
211
+ const otherServerCount = Object.keys(existing.mcpServers ?? {}).filter((k) => k !== "orqlaude").length;
212
+ const orqEntry = existing.mcpServers?.orqlaude;
213
+ const has = orqEntry ? style.coral("orqlaude entry present") : style.sand("no orqlaude entry yet");
214
+ console.log(` ${style.sand("status:")} ${has}, ${otherServerCount} other server(s) preserved`);
215
+ }
216
+ // 2. Pick state dir.
217
+ let stateDir = overrideStateDir;
218
+ if (!stateDir) {
219
+ const suggested = defaultStateDir();
220
+ if (yes) {
221
+ stateDir = suggested;
222
+ }
223
+ else {
224
+ const rl = readline.createInterface({ input: stdin, output: stdout });
225
+ const answer = (await rl.question(`\n ${style.coral("?")} state dir (where plans/audit live) [${suggested}]: `)).trim();
226
+ rl.close();
227
+ stateDir = answer || suggested;
228
+ }
229
+ }
230
+ stateDir = path.resolve(stateDir);
231
+ // 3. Compute patch.
232
+ const plan = planPatch(existing, stateDir);
233
+ console.log("");
234
+ console.log(style.bold(style.cream("Plan")));
235
+ switch (plan.action) {
236
+ case "noop":
237
+ console.log(` ${style.coral("✓")} already correct; nothing to do`);
238
+ return 0;
239
+ case "create-config":
240
+ console.log(` ${style.coral("→")} create ${configPath} with one server (orqlaude → ${stateDir})`);
241
+ break;
242
+ case "create-server":
243
+ console.log(` ${style.coral("→")} add orqlaude entry (→ ${stateDir}); preserve existing servers + preferences`);
244
+ break;
245
+ case "update-server":
246
+ console.log(` ${style.coral("→")} update orqlaude entry`);
247
+ console.log(` ${style.sand("before:")} ${JSON.stringify(plan.before)}`);
248
+ console.log(` ${style.sand("after:")} ${JSON.stringify(plan.after)}`);
249
+ break;
250
+ }
251
+ // 4. Confirm + write.
252
+ if (!yes) {
253
+ const rl2 = readline.createInterface({ input: stdin, output: stdout });
254
+ const ans = (await rl2.question(`\n ${style.coral("?")} apply? [Y/n] `)).trim().toLowerCase();
255
+ rl2.close();
256
+ if (ans && ans !== "y" && ans !== "yes") {
257
+ console.log(style.crimson(" ✗ cancelled"));
258
+ return 1;
259
+ }
260
+ }
261
+ const { backupPath } = await writeDesktopConfigAtomic(configPath, plan.config);
262
+ console.log("");
263
+ console.log(style.bold(style.coral("Done.")));
264
+ console.log(` ${style.sand("config:")} ${configPath}`);
265
+ if (backupPath)
266
+ console.log(` ${style.sand("backup:")} ${backupPath}`);
267
+ console.log(` ${style.sand("state dir:")} ${stateDir}`);
268
+ console.log("");
269
+ console.log(style.dim("Next steps:"));
270
+ console.log(style.dim(" 1. Fully quit Claude Desktop (Cmd-Q on macOS) and relaunch."));
271
+ console.log(style.dim(" 2. Open a session in this project. Call `mcp__orqlaude__ping` — it should report state_dir_source:'env'."));
272
+ console.log(style.dim(" 3. (Optional) Set up the Telegram bot: `orql tg setup`."));
273
+ return 0;
274
+ }
157
275
  function cmdWhere() {
158
276
  console.log(banner());
159
277
  console.log("");