naiad-cli 0.2.38 → 0.2.40

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.
@@ -0,0 +1 @@
1
+ export declare function loginCommand(args: string[]): Promise<void>;
@@ -0,0 +1,182 @@
1
+ import { spawn } from "child_process";
2
+ import { platform } from "os";
3
+ import { readRawConfig, writeRawConfig } from "../config/config.js";
4
+ export async function loginCommand(args) {
5
+ // Parse --api-url flag
6
+ let flagApiUrl;
7
+ for (let i = 0; i < args.length; i++) {
8
+ if (args[i] === "--api-url" && args[i + 1]) {
9
+ flagApiUrl = args[++i];
10
+ }
11
+ }
12
+ // Resolve API URL: flag → env → config file → default
13
+ let apiUrl = flagApiUrl || process.env.NAIAD_API_URL;
14
+ if (!apiUrl) {
15
+ try {
16
+ const cfg = readRawConfig();
17
+ if (typeof cfg.api_url === "string" && cfg.api_url) {
18
+ apiUrl = cfg.api_url;
19
+ }
20
+ }
21
+ catch {
22
+ // Config may not exist yet
23
+ }
24
+ }
25
+ if (!apiUrl) {
26
+ apiUrl = "http://localhost:3000";
27
+ }
28
+ // Check if already logged in
29
+ let existingConfig;
30
+ try {
31
+ existingConfig = readRawConfig();
32
+ }
33
+ catch {
34
+ console.error("Error: ~/.naiad/config.json is corrupted. Please fix or delete it.");
35
+ process.exit(1);
36
+ }
37
+ if (existingConfig.api_key) {
38
+ console.log("Already logged in. Run `naiad logout` first to switch accounts.");
39
+ return;
40
+ }
41
+ // Start device flow
42
+ let loginResp;
43
+ try {
44
+ const res = await fetch(apiUrl + "/auth/cli/login", {
45
+ method: "POST",
46
+ headers: { "Content-Type": "application/json" },
47
+ });
48
+ if (!res.ok) {
49
+ let msg = "Failed to start login flow";
50
+ try {
51
+ const body = await res.json();
52
+ if (body.message)
53
+ msg = body.message;
54
+ }
55
+ catch {
56
+ // ignore
57
+ }
58
+ console.error(`Error: ${msg}`);
59
+ process.exit(1);
60
+ }
61
+ loginResp = await res.json();
62
+ }
63
+ catch (err) {
64
+ console.error(`Error: Failed to start login flow — ${err.message}`);
65
+ process.exit(1);
66
+ }
67
+ // Open browser (fire-and-forget)
68
+ const openCmd = platform() === "darwin" ? "open" : "xdg-open";
69
+ try {
70
+ const child = spawn(openCmd, [loginResp.login_url], {
71
+ detached: true,
72
+ stdio: "ignore",
73
+ });
74
+ child.unref();
75
+ }
76
+ catch {
77
+ // ignore — we'll show the URL
78
+ }
79
+ console.log(`Opening browser to log in...`);
80
+ console.log(`If the browser didn't open, visit: ${loginResp.login_url}`);
81
+ console.log(`\nWaiting for authorization... (code: ${loginResp.user_code})`);
82
+ // Poll loop
83
+ const intervalMs = (loginResp.poll_interval || 5) * 1000;
84
+ const deadline = Date.now() + loginResp.expires_in * 1000;
85
+ let consecutiveFailures = 0;
86
+ let currentInterval = intervalMs;
87
+ while (Date.now() < deadline) {
88
+ await sleep(currentInterval);
89
+ if (Date.now() >= deadline)
90
+ break;
91
+ let pollRes;
92
+ try {
93
+ pollRes = await fetch(apiUrl + "/auth/cli/poll", {
94
+ method: "POST",
95
+ headers: { "Content-Type": "application/json" },
96
+ body: JSON.stringify({ device_code: loginResp.device_code }),
97
+ });
98
+ }
99
+ catch {
100
+ consecutiveFailures++;
101
+ if (consecutiveFailures >= 3) {
102
+ console.error("Error: Lost connection to server. Please try again.");
103
+ process.exit(1);
104
+ }
105
+ currentInterval = Math.min(currentInterval * 2, 30000);
106
+ continue;
107
+ }
108
+ if (pollRes.status === 429) {
109
+ currentInterval = Math.min(currentInterval * 2, 30000);
110
+ consecutiveFailures = 0;
111
+ continue;
112
+ }
113
+ if (pollRes.status >= 500) {
114
+ consecutiveFailures++;
115
+ if (consecutiveFailures >= 3) {
116
+ console.error("Error: Server error. Please try again.");
117
+ process.exit(1);
118
+ }
119
+ currentInterval = Math.min(currentInterval * 2, 30000);
120
+ continue;
121
+ }
122
+ let body;
123
+ try {
124
+ body = await pollRes.json();
125
+ }
126
+ catch {
127
+ consecutiveFailures++;
128
+ if (consecutiveFailures >= 3) {
129
+ console.error("Error: Unexpected server response. Please try again.");
130
+ process.exit(1);
131
+ }
132
+ currentInterval = Math.min(currentInterval * 2, 30000);
133
+ continue;
134
+ }
135
+ consecutiveFailures = 0;
136
+ currentInterval = intervalMs;
137
+ if (pollRes.status === 400 && body.code === "EXPIRED") {
138
+ console.error("Login expired. Please try again.");
139
+ process.exit(1);
140
+ }
141
+ if (!pollRes.ok && pollRes.status !== 200) {
142
+ console.error(`Error: ${body.message || "Unexpected error"}`);
143
+ process.exit(1);
144
+ }
145
+ if (body.status === "approved" && body.api_key) {
146
+ // Success — save key
147
+ let config;
148
+ try {
149
+ config = readRawConfig();
150
+ }
151
+ catch {
152
+ console.error("Error: ~/.naiad/config.json is corrupted. Please fix or delete it.");
153
+ process.exit(1);
154
+ }
155
+ config.api_key = body.api_key;
156
+ if (flagApiUrl) {
157
+ config.api_url = flagApiUrl;
158
+ }
159
+ writeRawConfig(config);
160
+ console.log("\n✓ Logged in successfully!");
161
+ if (process.env.NAIAD_API_KEY) {
162
+ console.log("Note: NAIAD_API_KEY environment variable is set and will take precedence over the stored key.");
163
+ }
164
+ return;
165
+ }
166
+ if (body.status === "denied") {
167
+ console.error("Login denied.");
168
+ process.exit(1);
169
+ }
170
+ if (body.status === "used") {
171
+ console.error("This login session has already been used.");
172
+ process.exit(1);
173
+ }
174
+ // "pending" — continue polling
175
+ }
176
+ console.error("Login expired. Please try again.");
177
+ process.exit(1);
178
+ }
179
+ function sleep(ms) {
180
+ return new Promise((resolve) => setTimeout(resolve, ms));
181
+ }
182
+ //# sourceMappingURL=login.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export declare function logoutCommand(): Promise<void>;
@@ -0,0 +1,22 @@
1
+ import { readRawConfig, writeRawConfig } from "../config/config.js";
2
+ export async function logoutCommand() {
3
+ let config;
4
+ try {
5
+ config = readRawConfig();
6
+ }
7
+ catch {
8
+ console.error("Error: ~/.naiad/config.json is corrupted. Please fix or delete it.");
9
+ process.exit(1);
10
+ }
11
+ if (!config.api_key) {
12
+ console.log("Not logged in.");
13
+ return;
14
+ }
15
+ delete config.api_key;
16
+ writeRawConfig(config);
17
+ console.log("✓ Logged out");
18
+ if (process.env.NAIAD_API_KEY) {
19
+ console.log("Note: NAIAD_API_KEY environment variable is still set.");
20
+ }
21
+ }
22
+ //# sourceMappingURL=logout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logout.js","sourceRoot":"","sources":["../../src/commands/logout.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAEpE,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,IAAI,MAA+B,CAAC;IACpC,IAAI,CAAC;QACH,MAAM,GAAG,aAAa,EAAE,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,KAAK,CAAC,oEAAoE,CAAC,CAAC;QACpF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;QAC9B,OAAO;IACT,CAAC;IAED,OAAO,MAAM,CAAC,OAAO,CAAC;IACtB,cAAc,CAAC,MAAM,CAAC,CAAC;IAEvB,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAE5B,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC;QAC9B,OAAO,CAAC,GAAG,CAAC,wDAAwD,CAAC,CAAC;IACxE,CAAC;AACH,CAAC"}
@@ -4,3 +4,5 @@ export interface NaiadConfig {
4
4
  isGHA: boolean;
5
5
  }
6
6
  export declare function loadConfig(): NaiadConfig;
7
+ export declare function readRawConfig(): Record<string, unknown>;
8
+ export declare function writeRawConfig(data: Record<string, unknown>): void;
@@ -1,6 +1,7 @@
1
- import { readFileSync, existsSync } from "fs";
1
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, renameSync } from "fs";
2
2
  import { join } from "path";
3
3
  import { homedir } from "os";
4
+ import { randomUUID } from "crypto";
4
5
  export function loadConfig() {
5
6
  const apiUrl = process.env.NAIAD_API_URL || loadFromFile("api_url") || "http://localhost:3000";
6
7
  const apiKey = process.env.NAIAD_API_KEY || loadFromFile("api_key") || "";
@@ -19,4 +20,23 @@ function loadFromFile(key) {
19
20
  return undefined;
20
21
  }
21
22
  }
23
+ export function readRawConfig() {
24
+ const configPath = join(homedir(), ".naiad", "config.json");
25
+ if (!existsSync(configPath))
26
+ return {};
27
+ const raw = readFileSync(configPath, "utf-8");
28
+ const parsed = JSON.parse(raw);
29
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
30
+ throw new Error("config file is not a JSON object");
31
+ }
32
+ return parsed;
33
+ }
34
+ export function writeRawConfig(data) {
35
+ const dir = join(homedir(), ".naiad");
36
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
37
+ const configPath = join(dir, "config.json");
38
+ const tempPath = join(dir, `.config.tmp.${randomUUID()}`);
39
+ writeFileSync(tempPath, JSON.stringify(data, null, 2) + "\n", { mode: 0o600 });
40
+ renameSync(tempPath, configPath);
41
+ }
22
42
  //# sourceMappingURL=config.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAC9C,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAQ7B,MAAM,UAAU,UAAU;IACxB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,YAAY,CAAC,SAAS,CAAC,IAAI,uBAAuB,CAAC;IAC/F,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,YAAY,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;IAC1E,MAAM,KAAK,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC;IAEzD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;AACnC,CAAC;AAED,SAAS,YAAY,CAAC,GAAW;IAC/B,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC;IAC5D,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,OAAO,SAAS,CAAC;IAC9C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC;QAC7D,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;IACrB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AACpF,OAAO,EAAE,IAAI,EAAW,MAAM,MAAM,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAQpC,MAAM,UAAU,UAAU;IACxB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,YAAY,CAAC,SAAS,CAAC,IAAI,uBAAuB,CAAC;IAC/F,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,YAAY,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;IAC1E,MAAM,KAAK,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC;IAEzD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;AACnC,CAAC;AAED,SAAS,YAAY,CAAC,GAAW;IAC/B,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC;IAC5D,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,OAAO,SAAS,CAAC;IAC9C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC;QAC7D,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;IACrB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC;IAC5D,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,OAAO,EAAE,CAAC;IACvC,MAAM,GAAG,GAAG,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IAC9C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC/B,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3E,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;IACtD,CAAC;IACD,OAAO,MAAiC,CAAC;AAC3C,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,IAA6B;IAC1D,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,CAAC;IACtC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACjD,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC;IAC5C,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,eAAe,UAAU,EAAE,EAAE,CAAC,CAAC;IAC1D,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC/E,UAAU,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;AACnC,CAAC"}
package/dist/index.js CHANGED
@@ -6,6 +6,8 @@ import { loadConfig } from "./config/config.js";
6
6
  import { execCommand } from "./commands/exec.js";
7
7
  import { interactiveCommand } from "./commands/interactive.js";
8
8
  import { continueCommand } from "./commands/continue.js";
9
+ import { loginCommand } from "./commands/login.js";
10
+ import { logoutCommand } from "./commands/logout.js";
9
11
  import { findPackageRoot } from "./utils/package-root.js";
10
12
  const args = process.argv.slice(2);
11
13
  if (args.includes("--version") || args.includes("-v")) {
@@ -18,9 +20,11 @@ if (args.includes("--help") || args.includes("-h")) {
18
20
  console.log(`naiad - Naiad 2.0 CLI
19
21
 
20
22
  Usage:
21
- naiad [options] Interactive mode (default)
22
- naiad exec [options] "<prompt>" Headless mode
23
- naiad continue <thread-id> Resume a thread locally
23
+ naiad login [--api-url <url>] Log in via browser
24
+ naiad logout Log out and remove stored credentials
25
+ naiad [options] Interactive mode (default)
26
+ naiad exec [options] "<prompt>" Headless mode
27
+ naiad continue <thread-id> Resume a thread locally
24
28
 
25
29
  Interactive options:
26
30
  --model <model_id> Model to use (default: first active model from server)
@@ -46,12 +50,20 @@ Environment:
46
50
  NAIAD_API_KEY API key for authentication`);
47
51
  process.exit(0);
48
52
  }
53
+ const command = args[0];
54
+ if (command === "login") {
55
+ await loginCommand(args.slice(1));
56
+ process.exit(0);
57
+ }
58
+ if (command === "logout") {
59
+ await logoutCommand();
60
+ process.exit(0);
61
+ }
49
62
  const config = loadConfig();
50
63
  if (!config.apiKey && !config.isGHA) {
51
- console.error("Error: NAIAD_API_KEY is required. Set it via environment variable or ~/.naiad/config.json");
64
+ console.error("Error: Not authenticated. Run `naiad login` or set NAIAD_API_KEY.");
52
65
  process.exit(1);
53
66
  }
54
- const command = args[0];
55
67
  if (command === "continue") {
56
68
  const continueArgs = args.slice(1);
57
69
  const threadIdArg = continueArgs[0];
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -11,7 +11,7 @@ import {
11
11
  calculateCost,
12
12
  } from "@mariozechner/pi-ai";
13
13
  import { execSync, execFileSync, spawn } from "child_process";
14
- import * as readline from "readline";
14
+ // Note: readline removed - using manual LF buffering for JSONL framing (v0.57.0+ compatibility)
15
15
  import * as fs from "fs";
16
16
  import * as path from "path";
17
17
  import * as os from "os";
@@ -637,10 +637,11 @@ When you're done, provide a clear, actionable summary the caller can act on.`;
637
637
  let closed = false;
638
638
  let terminationReason: "timeout" | "abort" | null = null;
639
639
  let statusLine = "";
640
+ let stdoutBuffer = "";
641
+ const stdoutDecoder = new TextDecoder("utf-8", { fatal: false });
642
+ const stderrDecoder = new TextDecoder("utf-8", { fatal: false });
640
643
 
641
- const rl = readline.createInterface({ input: child.stdout!, crlfDelay: Infinity });
642
-
643
- rl.on("line", (line: string) => {
644
+ function processLine(line: string) {
644
645
  if (!line.trim()) return;
645
646
  let event: any;
646
647
  try {
@@ -690,10 +691,40 @@ When you're done, provide a clear, actionable summary the caller can act on.`;
690
691
  }
691
692
  }
692
693
  }
694
+ }
695
+
696
+ function flushStdoutBuffer(final = false) {
697
+ if (final && stdoutBuffer.trim()) {
698
+ processLine(stdoutBuffer.trim());
699
+ stdoutBuffer = "";
700
+ }
701
+ }
702
+
703
+ // Use LF-only buffering for JSONL (v0.57.0+ compatibility)
704
+ // readline is avoided because it splits on Unicode line separators (U+2028/U+2029)
705
+ child.stdout!.on("data", (data: Buffer) => {
706
+ stdoutBuffer += stdoutDecoder.decode(data, { stream: true });
707
+ const lines = stdoutBuffer.split("\n");
708
+ stdoutBuffer = lines.pop() ?? "";
709
+
710
+ for (const line of lines) {
711
+ processLine(line);
712
+ }
713
+ });
714
+
715
+ child.stdout!.on("end", () => {
716
+ // Flush decoder and process any remaining partial line
717
+ stdoutBuffer += stdoutDecoder.decode(undefined, { stream: false });
718
+ flushStdoutBuffer(true);
693
719
  });
694
720
 
695
721
  child.stderr!.on("data", (data: Buffer) => {
696
- stderr += data.toString();
722
+ stderr += stderrDecoder.decode(data, { stream: true });
723
+ });
724
+
725
+ child.stderr!.on("end", () => {
726
+ // Flush any remaining stderr bytes
727
+ stderr += stderrDecoder.decode(undefined, { stream: false });
697
728
  });
698
729
 
699
730
  child.on("error", (err) => {
@@ -719,6 +750,9 @@ When you're done, provide a clear, actionable summary the caller can act on.`;
719
750
  signal.removeEventListener("abort", abortHandler);
720
751
  }
721
752
 
753
+ // Flush any remaining output in the stdout buffer
754
+ flushStdoutBuffer(true);
755
+
722
756
  if (terminationReason === "timeout") {
723
757
  const timeoutSec = Math.round(timeoutMs / 1000);
724
758
  reject(new Error(`Seer timed out after ${timeoutSec} seconds`));
@@ -1204,17 +1238,125 @@ When you're done, provide a clear, actionable summary the caller can act on.`;
1204
1238
  const isGHA = process.env.GITHUB_ACTIONS === "true";
1205
1239
  const apiBaseUrl = inferenceUrl.replace(/\/api\/v1\/inference$/, "");
1206
1240
 
1207
- async function callToolEndpoint(method: string, endpoint: string, body?: unknown): Promise<any> {
1208
- const url = `${apiBaseUrl}/api/v1/threads/${threadId}/github/${endpoint}`;
1209
- const res = await fetch(url, {
1210
- method,
1211
- headers: {
1212
- "Content-Type": "application/json",
1213
- Authorization: `Bearer ${apiKey}`,
1214
- },
1215
- body: body ? JSON.stringify(body) : undefined,
1241
+ const GITHUB_TOOL_TIMEOUT_MS = 30_000;
1242
+
1243
+ function createAbortError(message = "Aborted"): Error {
1244
+ const err = new Error(message);
1245
+ err.name = "AbortError";
1246
+ return err;
1247
+ }
1248
+
1249
+ function throwIfAborted(signal?: AbortSignal): void {
1250
+ if (signal?.aborted) {
1251
+ throw createAbortError();
1252
+ }
1253
+ }
1254
+
1255
+ function waitForTurn(previous: Promise<unknown>, signal?: AbortSignal): Promise<void> {
1256
+ if (!signal) {
1257
+ return previous.then(() => undefined);
1258
+ }
1259
+ if (signal.aborted) {
1260
+ return Promise.reject(createAbortError());
1261
+ }
1262
+
1263
+ return new Promise<void>((resolve, reject) => {
1264
+ const onAbort = () => {
1265
+ signal.removeEventListener("abort", onAbort);
1266
+ reject(createAbortError());
1267
+ };
1268
+
1269
+ previous.then(
1270
+ () => {
1271
+ signal.removeEventListener("abort", onAbort);
1272
+ resolve();
1273
+ },
1274
+ (err) => {
1275
+ signal.removeEventListener("abort", onAbort);
1276
+ reject(err);
1277
+ },
1278
+ );
1279
+
1280
+ signal.addEventListener("abort", onAbort, { once: true });
1216
1281
  });
1217
- const data = await res.json();
1282
+ }
1283
+
1284
+ // Abort-aware mutex for serializing mutating GitHub operations.
1285
+ // If a caller aborts while waiting, its queued operation is skipped and
1286
+ // later operations can continue once earlier ones finish.
1287
+ class AbortAwareMutex {
1288
+ private tail: Promise<void> = Promise.resolve();
1289
+
1290
+ async acquire<T>(fn: () => Promise<T>, signal?: AbortSignal): Promise<T> {
1291
+ throwIfAborted(signal);
1292
+
1293
+ const previous = this.tail;
1294
+ let release!: () => void;
1295
+ const current = new Promise<void>((resolve) => {
1296
+ release = resolve;
1297
+ });
1298
+
1299
+ // Successors still wait for `previous`, but this slot can be released
1300
+ // early if the waiting caller aborts.
1301
+ this.tail = previous.then(() => current, () => current);
1302
+
1303
+ try {
1304
+ await waitForTurn(previous, signal);
1305
+ throwIfAborted(signal);
1306
+ return await fn();
1307
+ } finally {
1308
+ release();
1309
+ }
1310
+ }
1311
+ }
1312
+ const githubMutex = new AbortAwareMutex();
1313
+
1314
+ async function callToolEndpoint(
1315
+ method: string,
1316
+ endpoint: string,
1317
+ body?: unknown,
1318
+ signal?: AbortSignal,
1319
+ ): Promise<any> {
1320
+ const url = `${apiBaseUrl}/api/v1/threads/${threadId}/github/${endpoint}`;
1321
+ const timeoutSignal = AbortSignal.timeout(GITHUB_TOOL_TIMEOUT_MS);
1322
+ const combinedSignal = signal
1323
+ ? AbortSignal.any([signal, timeoutSignal])
1324
+ : timeoutSignal;
1325
+
1326
+ let res: Response;
1327
+ let raw = "";
1328
+
1329
+ try {
1330
+ res = await fetch(url, {
1331
+ method,
1332
+ headers: {
1333
+ "Content-Type": "application/json",
1334
+ Authorization: `Bearer ${apiKey}`,
1335
+ },
1336
+ body: body ? JSON.stringify(body) : undefined,
1337
+ signal: combinedSignal,
1338
+ });
1339
+
1340
+ raw = await res.text();
1341
+ } catch (err) {
1342
+ if (signal?.aborted) throw err;
1343
+ if (timeoutSignal.aborted) {
1344
+ throw new Error(
1345
+ `GitHub tool request timed out after ${Math.round(GITHUB_TOOL_TIMEOUT_MS / 1000)} seconds`,
1346
+ );
1347
+ }
1348
+ throw err;
1349
+ }
1350
+
1351
+ let data: any = {};
1352
+ if (raw) {
1353
+ try {
1354
+ data = JSON.parse(raw);
1355
+ } catch {
1356
+ data = { raw };
1357
+ }
1358
+ }
1359
+
1218
1360
  if (res.status === 409) {
1219
1361
  return data;
1220
1362
  }
@@ -1237,7 +1379,7 @@ When you're done, provide a clear, actionable summary the caller can act on.`;
1237
1379
  parameters: Type.Object({}),
1238
1380
  async execute(toolCallId, params, signal, onUpdate, ctx) {
1239
1381
  if (isGHA) {
1240
- const data = await callToolEndpoint("GET", "context");
1382
+ const data = await callToolEndpoint("GET", "context", undefined, signal);
1241
1383
  return { content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }] };
1242
1384
  }
1243
1385
 
@@ -1300,122 +1442,125 @@ When you're done, provide a clear, actionable summary the caller can act on.`;
1300
1442
  ),
1301
1443
  }),
1302
1444
  async execute(toolCallId, params, signal, onUpdate, ctx) {
1303
- // Stage files
1304
- if (params.files && params.files.length > 0) {
1305
- execFileSync("git", ["add", ...params.files]);
1306
- } else {
1307
- execFileSync("git", ["add", "-A"]);
1308
- }
1309
-
1310
- // Check if there are changes to commit
1311
- try {
1312
- execFileSync("git", ["diff", "--cached", "--quiet"]);
1313
- // exit 0 means no changes
1314
- return { content: [{ type: "text" as const, text: JSON.stringify({ pushed: false, reason: "no_changes" }) }] };
1315
- } catch {
1316
- // exit non-zero means there are staged changes — continue
1317
- }
1445
+ // Serialize mutating GitHub operations to prevent race conditions (v0.58.0+ parallel tool execution)
1446
+ return githubMutex.acquire(async () => {
1447
+ // Stage files
1448
+ if (params.files && params.files.length > 0) {
1449
+ execFileSync("git", ["add", ...params.files]);
1450
+ } else {
1451
+ execFileSync("git", ["add", "-A"]);
1452
+ }
1318
1453
 
1319
- // Set git identity and commit
1320
- execFileSync("git", ["config", "user.name", "naiad-bot"]);
1321
- execFileSync("git", ["config", "user.email", "266131081+naiad-bot@users.noreply.github.com"]);
1322
- execFileSync("git", ["commit", "-m", params.message]);
1454
+ // Check if there are changes to commit
1455
+ try {
1456
+ execFileSync("git", ["diff", "--cached", "--quiet"]);
1457
+ // exit 0 means no changes
1458
+ return { content: [{ type: "text" as const, text: JSON.stringify({ pushed: false, reason: "no_changes" }) }] };
1459
+ } catch {
1460
+ // exit non-zero means there are staged changes — continue
1461
+ }
1323
1462
 
1324
- const headSha = execFileSync("git", ["rev-parse", "HEAD"]).toString().trim();
1463
+ // Set git identity and commit
1464
+ execFileSync("git", ["config", "user.name", "naiad-bot"]);
1465
+ execFileSync("git", ["config", "user.email", "266131081+naiad-bot@users.noreply.github.com"]);
1466
+ execFileSync("git", ["commit", "-m", params.message]);
1325
1467
 
1326
- if (isGHA) {
1327
- // Step 1: Preflight — validate push safety and get expectedRef WITHOUT minting a push token
1328
- const preflight = await callToolEndpoint("POST", "push-preflight", {
1329
- message: params.message,
1330
- commit_sha: headSha,
1331
- });
1468
+ const headSha = execFileSync("git", ["rev-parse", "HEAD"]).toString().trim();
1332
1469
 
1333
- if (preflight.error) {
1334
- return { content: [{ type: "text" as const, text: JSON.stringify(preflight) }] };
1335
- }
1470
+ if (isGHA) {
1471
+ // Step 1: Preflight validate push safety and get expectedRef WITHOUT minting a push token
1472
+ const preflight = await callToolEndpoint("POST", "push-preflight", {
1473
+ message: params.message,
1474
+ commit_sha: headSha,
1475
+ }, signal);
1336
1476
 
1337
- const { expectedRef, branch: preflightBranch } = preflight;
1477
+ if (preflight.error) {
1478
+ return { content: [{ type: "text" as const, text: JSON.stringify(preflight) }] };
1479
+ }
1338
1480
 
1339
- // Step 2: Check for merge commits (pre-push safety) using server-provided expectedRef
1340
- const merges = execFileSync("git", ["rev-list", "--merges", `${expectedRef}..HEAD`]).toString().trim();
1341
- if (merges) {
1342
- return {
1343
- content: [{ type: "text" as const, text: JSON.stringify({ error: "policy_denied", message: "Merge commits are not allowed", retryable: false }) }],
1344
- };
1345
- }
1481
+ const { expectedRef, branch: preflightBranch } = preflight;
1346
1482
 
1347
- // Step 3: Mint push token only after merge check passes
1348
- const pushData = await callToolEndpoint("POST", "commit-and-push", {
1349
- message: params.message,
1350
- commit_sha: headSha,
1351
- });
1483
+ // Step 2: Check for merge commits (pre-push safety) using server-provided expectedRef
1484
+ const merges = execFileSync("git", ["rev-list", "--merges", `${expectedRef}..HEAD`]).toString().trim();
1485
+ if (merges) {
1486
+ return {
1487
+ content: [{ type: "text" as const, text: JSON.stringify({ error: "policy_denied", message: "Merge commits are not allowed", retryable: false }) }],
1488
+ };
1489
+ }
1352
1490
 
1353
- if (pushData.error) {
1354
- return { content: [{ type: "text" as const, text: JSON.stringify(pushData) }] };
1355
- }
1491
+ // Step 3: Mint push token — only after merge check passes
1492
+ const pushData = await callToolEndpoint("POST", "commit-and-push", {
1493
+ message: params.message,
1494
+ commit_sha: headSha,
1495
+ }, signal);
1356
1496
 
1357
- const { pushToken, pushUrl, branch } = pushData;
1497
+ if (pushData.error) {
1498
+ return { content: [{ type: "text" as const, text: JSON.stringify(pushData) }] };
1499
+ }
1358
1500
 
1359
- // Validate pushUrl matches the expected repo (not just any GitHub URL)
1360
- const rawRemote = execFileSync("git", ["remote", "get-url", "origin"]).toString().trim();
1361
- const expectedRepo = rawRemote
1362
- .replace(/^https?:\/\/github\.com\//, "")
1363
- .replace(/^git@github\.com:/, "")
1364
- .replace(/\.git$/, "");
1365
- const expectedPushUrl = `https://github.com/${expectedRepo}.git`;
1366
- if (!pushUrl || pushUrl !== expectedPushUrl) {
1367
- return {
1368
- content: [{ type: "text" as const, text: JSON.stringify({ error: "policy_denied", message: "Push URL does not match expected repository", retryable: false }) }],
1369
- };
1370
- }
1501
+ const { pushToken, pushUrl, branch } = pushData;
1502
+
1503
+ // Validate pushUrl matches the expected repo (not just any GitHub URL)
1504
+ const rawRemote = execFileSync("git", ["remote", "get-url", "origin"]).toString().trim();
1505
+ const expectedRepo = rawRemote
1506
+ .replace(/^https?:\/\/github\.com\//, "")
1507
+ .replace(/^git@github\.com:/, "")
1508
+ .replace(/\.git$/, "");
1509
+ const expectedPushUrl = `https://github.com/${expectedRepo}.git`;
1510
+ if (!pushUrl || pushUrl !== expectedPushUrl) {
1511
+ return {
1512
+ content: [{ type: "text" as const, text: JSON.stringify({ error: "policy_denied", message: "Push URL does not match expected repository", retryable: false }) }],
1513
+ };
1514
+ }
1371
1515
 
1372
- const basicAuth = Buffer.from(`x-access-token:${pushToken}`).toString("base64");
1516
+ const basicAuth = Buffer.from(`x-access-token:${pushToken}`).toString("base64");
1373
1517
 
1374
- // Clean-room push — env vars (GIT_CONFIG_NOSYSTEM, GIT_CONFIG_GLOBAL,
1375
- // GIT_CONFIG) already neutralize system/global/local config includes.
1376
- try {
1377
- execFileSync(
1378
- "git",
1379
- [
1380
- "-c", "core.hooksPath=/dev/null",
1381
- "-c", "credential.helper=",
1382
- `-c`, `http.extraheader=Authorization: Basic ${basicAuth}`,
1383
- "push", pushUrl, `HEAD:refs/heads/${branch}`, "--no-force",
1384
- ],
1385
- {
1386
- env: {
1387
- PATH: process.env.PATH,
1388
- HOME: process.env.HOME,
1389
- GIT_CONFIG_NOSYSTEM: "1",
1390
- GIT_CONFIG_GLOBAL: "/dev/null",
1391
- GIT_CONFIG: "/dev/null",
1392
- GIT_TERMINAL_PROMPT: "0",
1518
+ // Clean-room push — env vars (GIT_CONFIG_NOSYSTEM, GIT_CONFIG_GLOBAL,
1519
+ // GIT_CONFIG) already neutralize system/global/local config includes.
1520
+ try {
1521
+ execFileSync(
1522
+ "git",
1523
+ [
1524
+ "-c", "core.hooksPath=/dev/null",
1525
+ "-c", "credential.helper=",
1526
+ `-c`, `http.extraheader=Authorization: Basic ${basicAuth}`,
1527
+ "push", pushUrl, `HEAD:refs/heads/${branch}`, "--no-force",
1528
+ ],
1529
+ {
1530
+ env: {
1531
+ PATH: process.env.PATH,
1532
+ HOME: process.env.HOME,
1533
+ GIT_CONFIG_NOSYSTEM: "1",
1534
+ GIT_CONFIG_GLOBAL: "/dev/null",
1535
+ GIT_CONFIG: "/dev/null",
1536
+ GIT_TERMINAL_PROMPT: "0",
1537
+ },
1393
1538
  },
1394
- },
1395
- );
1396
- } catch (pushErr: any) {
1397
- // Sanitize error execFileSync includes the full command with credentials
1398
- const stderr = pushErr.stderr?.toString() || "";
1399
- throw new Error(`git push failed: ${stderr.replace(/Authorization:[^\s]*/g, "Authorization: [REDACTED]")}`);
1400
- }
1539
+ );
1540
+ } catch (pushErr: any) {
1541
+ // Sanitize error execFileSync includes the full command with credentials
1542
+ const stderr = pushErr.stderr?.toString() || "";
1543
+ throw new Error(`git push failed: ${stderr.replace(/Authorization:[^\s]*/g, "Authorization: [REDACTED]")}`);
1544
+ }
1401
1545
 
1402
- // Post-push verification (defense-in-depth: server checks for merge commits)
1403
- const verifyResult = await callToolEndpoint("POST", "post-push-verify", {
1404
- commit_sha: headSha,
1405
- expected_ref: expectedRef,
1406
- branch,
1407
- });
1408
- if (verifyResult.error) {
1409
- return { content: [{ type: "text" as const, text: JSON.stringify(verifyResult) }] };
1546
+ // Post-push verification (defense-in-depth: server checks for merge commits)
1547
+ const verifyResult = await callToolEndpoint("POST", "post-push-verify", {
1548
+ commit_sha: headSha,
1549
+ expected_ref: expectedRef,
1550
+ branch,
1551
+ }, signal);
1552
+ if (verifyResult.error) {
1553
+ return { content: [{ type: "text" as const, text: JSON.stringify(verifyResult) }] };
1554
+ }
1555
+
1556
+ return { content: [{ type: "text" as const, text: JSON.stringify({ pushed: true, headSha, branch }) }] };
1410
1557
  }
1411
1558
 
1559
+ // Local fallback: just push
1560
+ execFileSync("git", ["push"]);
1561
+ const branch = execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"]).toString().trim();
1412
1562
  return { content: [{ type: "text" as const, text: JSON.stringify({ pushed: true, headSha, branch }) }] };
1413
- }
1414
-
1415
- // Local fallback: just push
1416
- execFileSync("git", ["push"]);
1417
- const branch = execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"]).toString().trim();
1418
- return { content: [{ type: "text" as const, text: JSON.stringify({ pushed: true, headSha, branch }) }] };
1563
+ }, signal);
1419
1564
  },
1420
1565
  });
1421
1566
 
@@ -1437,35 +1582,38 @@ When you're done, provide a clear, actionable summary the caller can act on.`;
1437
1582
  draft: Type.Optional(Type.Boolean({ description: "Create as draft PR. Defaults to false." })),
1438
1583
  }),
1439
1584
  async execute(toolCallId, params, signal, onUpdate, ctx) {
1440
- if (isGHA) {
1441
- const data = await callToolEndpoint("POST", "pr", {
1442
- title: params.title,
1443
- body: params.body,
1444
- draft: params.draft,
1445
- });
1446
- return { content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }] };
1447
- }
1585
+ // Serialize mutating GitHub operations to prevent race conditions (v0.58.0+ parallel tool execution)
1586
+ return githubMutex.acquire(async () => {
1587
+ if (isGHA) {
1588
+ const data = await callToolEndpoint("POST", "pr", {
1589
+ title: params.title,
1590
+ body: params.body,
1591
+ draft: params.draft,
1592
+ }, signal);
1593
+ return { content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }] };
1594
+ }
1448
1595
 
1449
- // Local fallback: gh CLI
1450
- try {
1451
- // Try to update existing PR first
1452
- execFileSync("gh", ["pr", "edit", "--title", params.title, "--body", params.body]);
1453
- const prJson = execFileSync("gh", ["pr", "view", "--json", "number,url"]).toString().trim();
1454
- const pr = JSON.parse(prJson);
1455
- return { content: [{ type: "text" as const, text: JSON.stringify({ prNumber: pr.number, prUrl: pr.url, created: false }) }] };
1456
- } catch {
1457
- // No existing PR — create one
1458
- const createArgs = ["pr", "create", "--title", params.title, "--body", params.body];
1459
- if (params.draft) createArgs.push("--draft");
1460
- const prUrl = execFileSync("gh", createArgs).toString().trim();
1461
- // Fetch PR number from the newly created PR
1462
- let prNumber: number | undefined;
1596
+ // Local fallback: gh CLI
1463
1597
  try {
1464
- const newPrJson = execFileSync("gh", ["pr", "view", "--json", "number"]).toString().trim();
1465
- prNumber = JSON.parse(newPrJson).number;
1466
- } catch {}
1467
- return { content: [{ type: "text" as const, text: JSON.stringify({ prNumber, prUrl, created: true }) }] };
1468
- }
1598
+ // Try to update existing PR first
1599
+ execFileSync("gh", ["pr", "edit", "--title", params.title, "--body", params.body]);
1600
+ const prJson = execFileSync("gh", ["pr", "view", "--json", "number,url"]).toString().trim();
1601
+ const pr = JSON.parse(prJson);
1602
+ return { content: [{ type: "text" as const, text: JSON.stringify({ prNumber: pr.number, prUrl: pr.url, created: false }) }] };
1603
+ } catch {
1604
+ // No existing PR — create one
1605
+ const createArgs = ["pr", "create", "--title", params.title, "--body", params.body];
1606
+ if (params.draft) createArgs.push("--draft");
1607
+ const prUrl = execFileSync("gh", createArgs).toString().trim();
1608
+ // Fetch PR number from the newly created PR
1609
+ let prNumber: number | undefined;
1610
+ try {
1611
+ const newPrJson = execFileSync("gh", ["pr", "view", "--json", "number"]).toString().trim();
1612
+ prNumber = JSON.parse(newPrJson).number;
1613
+ } catch {}
1614
+ return { content: [{ type: "text" as const, text: JSON.stringify({ prNumber, prUrl, created: true }) }] };
1615
+ }
1616
+ }, signal);
1469
1617
  },
1470
1618
  });
1471
1619
 
@@ -1492,25 +1640,28 @@ When you're done, provide a clear, actionable summary the caller can act on.`;
1492
1640
  ),
1493
1641
  }),
1494
1642
  async execute(toolCallId, params, signal, onUpdate, ctx) {
1495
- if (isGHA) {
1496
- const data = await callToolEndpoint("POST", "comment", {
1497
- body: params.body,
1498
- target: params.target,
1499
- reviewCommentId: params.reviewCommentId,
1500
- });
1501
- return { content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }] };
1502
- }
1643
+ // Serialize mutating GitHub operations to prevent race conditions (v0.58.0+ parallel tool execution)
1644
+ return githubMutex.acquire(async () => {
1645
+ if (isGHA) {
1646
+ const data = await callToolEndpoint("POST", "comment", {
1647
+ body: params.body,
1648
+ target: params.target,
1649
+ reviewCommentId: params.reviewCommentId,
1650
+ }, signal);
1651
+ return { content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }] };
1652
+ }
1503
1653
 
1504
- // Local fallback: gh CLI
1505
- if (params.target === "pr_review_thread") {
1506
- throw new Error("pr_review_thread target is not supported in local CLI mode — use GHA or reply manually");
1507
- }
1508
- if (params.target === "issue") {
1509
- execFileSync("gh", ["issue", "comment", "--body", params.body]);
1510
- } else {
1511
- execFileSync("gh", ["pr", "comment", "--body", params.body]);
1512
- }
1513
- return { content: [{ type: "text" as const, text: JSON.stringify({ commentUrl: "comment posted locally" }) }] };
1654
+ // Local fallback: gh CLI
1655
+ if (params.target === "pr_review_thread") {
1656
+ throw new Error("pr_review_thread target is not supported in local CLI mode — use GHA or reply manually");
1657
+ }
1658
+ if (params.target === "issue") {
1659
+ execFileSync("gh", ["issue", "comment", "--body", params.body]);
1660
+ } else {
1661
+ execFileSync("gh", ["pr", "comment", "--body", params.body]);
1662
+ }
1663
+ return { content: [{ type: "text" as const, text: JSON.stringify({ commentUrl: "comment posted locally" }) }] };
1664
+ }, signal);
1514
1665
  },
1515
1666
  });
1516
1667
 
@@ -1547,27 +1698,30 @@ When you're done, provide a clear, actionable summary the caller can act on.`;
1547
1698
  ),
1548
1699
  }),
1549
1700
  async execute(toolCallId, params, signal, onUpdate, ctx) {
1550
- if (isGHA) {
1551
- const data = await callToolEndpoint("POST", "review", {
1552
- body: params.body,
1553
- event: params.event,
1554
- comments: params.comments,
1555
- });
1556
- return { content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }] };
1557
- }
1701
+ // Serialize mutating GitHub operations to prevent race conditions (v0.58.0+ parallel tool execution)
1702
+ return githubMutex.acquire(async () => {
1703
+ if (isGHA) {
1704
+ const data = await callToolEndpoint("POST", "review", {
1705
+ body: params.body,
1706
+ event: params.event,
1707
+ comments: params.comments,
1708
+ }, signal);
1709
+ return { content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }] };
1710
+ }
1558
1711
 
1559
- // Local fallback: gh pr review
1560
- if (params.comments && params.comments.length > 0) {
1561
- throw new Error("Inline review comments are not supported in local CLI mode — use GHA for inline comments");
1562
- }
1563
- const reviewArgs = ["pr", "review", "--body", params.body];
1564
- if (params.event === "REQUEST_CHANGES") {
1565
- reviewArgs.push("--request-changes");
1566
- } else {
1567
- reviewArgs.push("--comment");
1568
- }
1569
- execFileSync("gh", reviewArgs);
1570
- return { content: [{ type: "text" as const, text: JSON.stringify({ reviewUrl: "review posted locally" }) }] };
1712
+ // Local fallback: gh pr review
1713
+ if (params.comments && params.comments.length > 0) {
1714
+ throw new Error("Inline review comments are not supported in local CLI mode — use GHA for inline comments");
1715
+ }
1716
+ const reviewArgs = ["pr", "review", "--body", params.body];
1717
+ if (params.event === "REQUEST_CHANGES") {
1718
+ reviewArgs.push("--request-changes");
1719
+ } else {
1720
+ reviewArgs.push("--comment");
1721
+ }
1722
+ execFileSync("gh", reviewArgs);
1723
+ return { content: [{ type: "text" as const, text: JSON.stringify({ reviewUrl: "review posted locally" }) }] };
1724
+ }, signal);
1571
1725
  },
1572
1726
  });
1573
1727
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "naiad-cli",
3
- "version": "0.2.38",
3
+ "version": "0.2.40",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "naiad": "./dist/index.js"
@@ -21,7 +21,7 @@
21
21
  "test": "tsx --test src/callback/server.test.ts"
22
22
  },
23
23
  "dependencies": {
24
- "@mariozechner/pi-coding-agent": "^0.56.0"
24
+ "@mariozechner/pi-coding-agent": "^0.58.3"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@types/node": "^22.0.0",