postgresai 0.12.0-beta.6 → 0.14.0-dev.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +91 -2
- package/bin/postgres-ai.ts +552 -37
- package/dist/bin/postgres-ai.js +503 -26
- package/dist/bin/postgres-ai.js.map +1 -1
- package/dist/lib/init.d.ts +61 -0
- package/dist/lib/init.d.ts.map +1 -0
- package/dist/lib/init.js +359 -0
- package/dist/lib/init.js.map +1 -0
- package/dist/lib/issues.d.ts +69 -1
- package/dist/lib/issues.d.ts.map +1 -1
- package/dist/lib/issues.js +232 -1
- package/dist/lib/issues.js.map +1 -1
- package/dist/lib/mcp-server.d.ts.map +1 -1
- package/dist/lib/mcp-server.js +69 -15
- package/dist/lib/mcp-server.js.map +1 -1
- package/dist/package.json +3 -2
- package/lib/init.ts +404 -0
- package/lib/issues.ts +325 -3
- package/lib/mcp-server.ts +75 -17
- package/package.json +3 -2
- package/test/init.integration.test.cjs +269 -0
- package/test/init.test.cjs +69 -0
package/lib/issues.ts
CHANGED
|
@@ -2,13 +2,62 @@ import * as https from "https";
|
|
|
2
2
|
import { URL } from "url";
|
|
3
3
|
import { maskSecret, normalizeBaseUrl } from "./util";
|
|
4
4
|
|
|
5
|
+
export interface IssueActionItem {
|
|
6
|
+
id: string;
|
|
7
|
+
issue_id: string;
|
|
8
|
+
title: string;
|
|
9
|
+
description: string | null;
|
|
10
|
+
severity: number;
|
|
11
|
+
is_done: boolean;
|
|
12
|
+
done_by: number | null;
|
|
13
|
+
done_at: string | null;
|
|
14
|
+
created_at: string;
|
|
15
|
+
updated_at: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface Issue {
|
|
19
|
+
id: string;
|
|
20
|
+
title: string;
|
|
21
|
+
description: string | null;
|
|
22
|
+
created_at: string;
|
|
23
|
+
updated_at: string;
|
|
24
|
+
status: number;
|
|
25
|
+
url_main: string | null;
|
|
26
|
+
urls_extra: string[] | null;
|
|
27
|
+
data: unknown | null;
|
|
28
|
+
author_id: number;
|
|
29
|
+
org_id: number;
|
|
30
|
+
project_id: number | null;
|
|
31
|
+
is_ai_generated: boolean;
|
|
32
|
+
assigned_to: number[] | null;
|
|
33
|
+
labels: string[] | null;
|
|
34
|
+
is_edited: boolean;
|
|
35
|
+
author_display_name: string;
|
|
36
|
+
comment_count: number;
|
|
37
|
+
action_items: IssueActionItem[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface IssueComment {
|
|
41
|
+
id: string;
|
|
42
|
+
issue_id: string;
|
|
43
|
+
author_id: number;
|
|
44
|
+
parent_comment_id: string | null;
|
|
45
|
+
content: string;
|
|
46
|
+
created_at: string;
|
|
47
|
+
updated_at: string;
|
|
48
|
+
data: unknown | null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type IssueListItem = Pick<Issue, "id" | "title" | "status" | "created_at">;
|
|
52
|
+
|
|
53
|
+
export type IssueDetail = Pick<Issue, "id" | "title" | "description" | "status" | "created_at" | "author_display_name">;
|
|
5
54
|
export interface FetchIssuesParams {
|
|
6
55
|
apiKey: string;
|
|
7
56
|
apiBaseUrl: string;
|
|
8
57
|
debug?: boolean;
|
|
9
58
|
}
|
|
10
59
|
|
|
11
|
-
export async function fetchIssues(params: FetchIssuesParams): Promise<
|
|
60
|
+
export async function fetchIssues(params: FetchIssuesParams): Promise<IssueListItem[]> {
|
|
12
61
|
const { apiKey, apiBaseUrl, debug } = params;
|
|
13
62
|
if (!apiKey) {
|
|
14
63
|
throw new Error("API key is required");
|
|
@@ -16,6 +65,7 @@ export async function fetchIssues(params: FetchIssuesParams): Promise<unknown> {
|
|
|
16
65
|
|
|
17
66
|
const base = normalizeBaseUrl(apiBaseUrl);
|
|
18
67
|
const url = new URL(`${base}/issues`);
|
|
68
|
+
url.searchParams.set("select", "id,title,status,created_at");
|
|
19
69
|
|
|
20
70
|
const headers: Record<string, string> = {
|
|
21
71
|
"access-token": apiKey,
|
|
@@ -54,10 +104,10 @@ export async function fetchIssues(params: FetchIssuesParams): Promise<unknown> {
|
|
|
54
104
|
}
|
|
55
105
|
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
|
56
106
|
try {
|
|
57
|
-
const parsed = JSON.parse(data);
|
|
107
|
+
const parsed = JSON.parse(data) as IssueListItem[];
|
|
58
108
|
resolve(parsed);
|
|
59
109
|
} catch {
|
|
60
|
-
|
|
110
|
+
reject(new Error(`Failed to parse issues response: ${data}`));
|
|
61
111
|
}
|
|
62
112
|
} else {
|
|
63
113
|
let errMsg = `Failed to fetch issues: HTTP ${res.statusCode}`;
|
|
@@ -81,3 +131,275 @@ export async function fetchIssues(params: FetchIssuesParams): Promise<unknown> {
|
|
|
81
131
|
}
|
|
82
132
|
|
|
83
133
|
|
|
134
|
+
export interface FetchIssueCommentsParams {
|
|
135
|
+
apiKey: string;
|
|
136
|
+
apiBaseUrl: string;
|
|
137
|
+
issueId: string;
|
|
138
|
+
debug?: boolean;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export async function fetchIssueComments(params: FetchIssueCommentsParams): Promise<IssueComment[]> {
|
|
142
|
+
const { apiKey, apiBaseUrl, issueId, debug } = params;
|
|
143
|
+
if (!apiKey) {
|
|
144
|
+
throw new Error("API key is required");
|
|
145
|
+
}
|
|
146
|
+
if (!issueId) {
|
|
147
|
+
throw new Error("issueId is required");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const base = normalizeBaseUrl(apiBaseUrl);
|
|
151
|
+
const url = new URL(`${base}/issue_comments?issue_id=eq.${encodeURIComponent(issueId)}`);
|
|
152
|
+
|
|
153
|
+
const headers: Record<string, string> = {
|
|
154
|
+
"access-token": apiKey,
|
|
155
|
+
"Prefer": "return=representation",
|
|
156
|
+
"Content-Type": "application/json",
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
if (debug) {
|
|
160
|
+
const debugHeaders: Record<string, string> = { ...headers, "access-token": maskSecret(apiKey) };
|
|
161
|
+
// eslint-disable-next-line no-console
|
|
162
|
+
console.log(`Debug: Resolved API base URL: ${base}`);
|
|
163
|
+
// eslint-disable-next-line no-console
|
|
164
|
+
console.log(`Debug: GET URL: ${url.toString()}`);
|
|
165
|
+
// eslint-disable-next-line no-console
|
|
166
|
+
console.log(`Debug: Auth scheme: access-token`);
|
|
167
|
+
// eslint-disable-next-line no-console
|
|
168
|
+
console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return new Promise((resolve, reject) => {
|
|
172
|
+
const req = https.request(
|
|
173
|
+
url,
|
|
174
|
+
{
|
|
175
|
+
method: "GET",
|
|
176
|
+
headers,
|
|
177
|
+
},
|
|
178
|
+
(res) => {
|
|
179
|
+
let data = "";
|
|
180
|
+
res.on("data", (chunk) => (data += chunk));
|
|
181
|
+
res.on("end", () => {
|
|
182
|
+
if (debug) {
|
|
183
|
+
// eslint-disable-next-line no-console
|
|
184
|
+
console.log(`Debug: Response status: ${res.statusCode}`);
|
|
185
|
+
// eslint-disable-next-line no-console
|
|
186
|
+
console.log(`Debug: Response headers: ${JSON.stringify(res.headers)}`);
|
|
187
|
+
}
|
|
188
|
+
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
|
189
|
+
try {
|
|
190
|
+
const parsed = JSON.parse(data) as IssueComment[];
|
|
191
|
+
resolve(parsed);
|
|
192
|
+
} catch {
|
|
193
|
+
reject(new Error(`Failed to parse issue comments response: ${data}`));
|
|
194
|
+
}
|
|
195
|
+
} else {
|
|
196
|
+
let errMsg = `Failed to fetch issue comments: HTTP ${res.statusCode}`;
|
|
197
|
+
if (data) {
|
|
198
|
+
try {
|
|
199
|
+
const errObj = JSON.parse(data);
|
|
200
|
+
errMsg += `\n${JSON.stringify(errObj, null, 2)}`;
|
|
201
|
+
} catch {
|
|
202
|
+
errMsg += `\n${data}`;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
reject(new Error(errMsg));
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
req.on("error", (err: Error) => reject(err));
|
|
212
|
+
req.end();
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export interface FetchIssueParams {
|
|
217
|
+
apiKey: string;
|
|
218
|
+
apiBaseUrl: string;
|
|
219
|
+
issueId: string;
|
|
220
|
+
debug?: boolean;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export async function fetchIssue(params: FetchIssueParams): Promise<IssueDetail | null> {
|
|
224
|
+
const { apiKey, apiBaseUrl, issueId, debug } = params;
|
|
225
|
+
if (!apiKey) {
|
|
226
|
+
throw new Error("API key is required");
|
|
227
|
+
}
|
|
228
|
+
if (!issueId) {
|
|
229
|
+
throw new Error("issueId is required");
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const base = normalizeBaseUrl(apiBaseUrl);
|
|
233
|
+
const url = new URL(`${base}/issues`);
|
|
234
|
+
url.searchParams.set("select", "id,title,description,status,created_at,author_display_name");
|
|
235
|
+
url.searchParams.set("id", `eq.${issueId}`);
|
|
236
|
+
url.searchParams.set("limit", "1");
|
|
237
|
+
|
|
238
|
+
const headers: Record<string, string> = {
|
|
239
|
+
"access-token": apiKey,
|
|
240
|
+
"Prefer": "return=representation",
|
|
241
|
+
"Content-Type": "application/json",
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
if (debug) {
|
|
245
|
+
const debugHeaders: Record<string, string> = { ...headers, "access-token": maskSecret(apiKey) };
|
|
246
|
+
// eslint-disable-next-line no-console
|
|
247
|
+
console.log(`Debug: Resolved API base URL: ${base}`);
|
|
248
|
+
// eslint-disable-next-line no-console
|
|
249
|
+
console.log(`Debug: GET URL: ${url.toString()}`);
|
|
250
|
+
// eslint-disable-next-line no-console
|
|
251
|
+
console.log(`Debug: Auth scheme: access-token`);
|
|
252
|
+
// eslint-disable-next-line no-console
|
|
253
|
+
console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return new Promise((resolve, reject) => {
|
|
257
|
+
const req = https.request(
|
|
258
|
+
url,
|
|
259
|
+
{
|
|
260
|
+
method: "GET",
|
|
261
|
+
headers,
|
|
262
|
+
},
|
|
263
|
+
(res) => {
|
|
264
|
+
let data = "";
|
|
265
|
+
res.on("data", (chunk) => (data += chunk));
|
|
266
|
+
res.on("end", () => {
|
|
267
|
+
if (debug) {
|
|
268
|
+
// eslint-disable-next-line no-console
|
|
269
|
+
console.log(`Debug: Response status: ${res.statusCode}`);
|
|
270
|
+
// eslint-disable-next-line no-console
|
|
271
|
+
console.log(`Debug: Response headers: ${JSON.stringify(res.headers)}`);
|
|
272
|
+
}
|
|
273
|
+
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
|
274
|
+
try {
|
|
275
|
+
const parsed = JSON.parse(data);
|
|
276
|
+
if (Array.isArray(parsed)) {
|
|
277
|
+
resolve((parsed[0] as IssueDetail) ?? null);
|
|
278
|
+
} else {
|
|
279
|
+
resolve(parsed as IssueDetail);
|
|
280
|
+
}
|
|
281
|
+
} catch {
|
|
282
|
+
reject(new Error(`Failed to parse issue response: ${data}`));
|
|
283
|
+
}
|
|
284
|
+
} else {
|
|
285
|
+
let errMsg = `Failed to fetch issue: HTTP ${res.statusCode}`;
|
|
286
|
+
if (data) {
|
|
287
|
+
try {
|
|
288
|
+
const errObj = JSON.parse(data);
|
|
289
|
+
errMsg += `\n${JSON.stringify(errObj, null, 2)}`;
|
|
290
|
+
} catch {
|
|
291
|
+
errMsg += `\n${data}`;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
reject(new Error(errMsg));
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
req.on("error", (err: Error) => reject(err));
|
|
301
|
+
req.end();
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export interface CreateIssueCommentParams {
|
|
306
|
+
apiKey: string;
|
|
307
|
+
apiBaseUrl: string;
|
|
308
|
+
issueId: string;
|
|
309
|
+
content: string;
|
|
310
|
+
parentCommentId?: string;
|
|
311
|
+
debug?: boolean;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export async function createIssueComment(params: CreateIssueCommentParams): Promise<IssueComment> {
|
|
315
|
+
const { apiKey, apiBaseUrl, issueId, content, parentCommentId, debug } = params;
|
|
316
|
+
if (!apiKey) {
|
|
317
|
+
throw new Error("API key is required");
|
|
318
|
+
}
|
|
319
|
+
if (!issueId) {
|
|
320
|
+
throw new Error("issueId is required");
|
|
321
|
+
}
|
|
322
|
+
if (!content) {
|
|
323
|
+
throw new Error("content is required");
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const base = normalizeBaseUrl(apiBaseUrl);
|
|
327
|
+
const url = new URL(`${base}/rpc/issue_comment_create`);
|
|
328
|
+
|
|
329
|
+
const bodyObj: Record<string, unknown> = {
|
|
330
|
+
issue_id: issueId,
|
|
331
|
+
content: content,
|
|
332
|
+
};
|
|
333
|
+
if (parentCommentId) {
|
|
334
|
+
bodyObj.parent_comment_id = parentCommentId;
|
|
335
|
+
}
|
|
336
|
+
const body = JSON.stringify(bodyObj);
|
|
337
|
+
|
|
338
|
+
const headers: Record<string, string> = {
|
|
339
|
+
"access-token": apiKey,
|
|
340
|
+
"Prefer": "return=representation",
|
|
341
|
+
"Content-Type": "application/json",
|
|
342
|
+
"Content-Length": Buffer.byteLength(body).toString(),
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
if (debug) {
|
|
346
|
+
const debugHeaders: Record<string, string> = { ...headers, "access-token": maskSecret(apiKey) };
|
|
347
|
+
// eslint-disable-next-line no-console
|
|
348
|
+
console.log(`Debug: Resolved API base URL: ${base}`);
|
|
349
|
+
// eslint-disable-next-line no-console
|
|
350
|
+
console.log(`Debug: POST URL: ${url.toString()}`);
|
|
351
|
+
// eslint-disable-next-line no-console
|
|
352
|
+
console.log(`Debug: Auth scheme: access-token`);
|
|
353
|
+
// eslint-disable-next-line no-console
|
|
354
|
+
console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
|
|
355
|
+
// eslint-disable-next-line no-console
|
|
356
|
+
console.log(`Debug: Request body: ${body}`);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return new Promise((resolve, reject) => {
|
|
360
|
+
const req = https.request(
|
|
361
|
+
url,
|
|
362
|
+
{
|
|
363
|
+
method: "POST",
|
|
364
|
+
headers,
|
|
365
|
+
},
|
|
366
|
+
(res) => {
|
|
367
|
+
let data = "";
|
|
368
|
+
res.on("data", (chunk) => (data += chunk));
|
|
369
|
+
res.on("end", () => {
|
|
370
|
+
if (debug) {
|
|
371
|
+
// eslint-disable-next-line no-console
|
|
372
|
+
console.log(`Debug: Response status: ${res.statusCode}`);
|
|
373
|
+
// eslint-disable-next-line no-console
|
|
374
|
+
console.log(`Debug: Response headers: ${JSON.stringify(res.headers)}`);
|
|
375
|
+
}
|
|
376
|
+
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
|
377
|
+
try {
|
|
378
|
+
const parsed = JSON.parse(data) as IssueComment;
|
|
379
|
+
resolve(parsed);
|
|
380
|
+
} catch {
|
|
381
|
+
reject(new Error(`Failed to parse create comment response: ${data}`));
|
|
382
|
+
}
|
|
383
|
+
} else {
|
|
384
|
+
let errMsg = `Failed to create issue comment: HTTP ${res.statusCode}`;
|
|
385
|
+
if (data) {
|
|
386
|
+
try {
|
|
387
|
+
const errObj = JSON.parse(data);
|
|
388
|
+
errMsg += `\n${JSON.stringify(errObj, null, 2)}`;
|
|
389
|
+
} catch {
|
|
390
|
+
errMsg += `\n${data}`;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
reject(new Error(errMsg));
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
req.on("error", (err: Error) => reject(err));
|
|
400
|
+
req.write(body);
|
|
401
|
+
req.end();
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
|
package/lib/mcp-server.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as pkg from "../package.json";
|
|
2
2
|
import * as config from "./config";
|
|
3
|
-
import { fetchIssues } from "./issues";
|
|
3
|
+
import { fetchIssues, fetchIssueComments, createIssueComment, fetchIssue } from "./issues";
|
|
4
4
|
import { resolveBaseUrls } from "./util";
|
|
5
5
|
|
|
6
6
|
// MCP SDK imports
|
|
@@ -29,6 +29,16 @@ export async function startMcpServer(rootOpts?: RootOptsLike, extra?: { debug?:
|
|
|
29
29
|
{ capabilities: { tools: {} } }
|
|
30
30
|
);
|
|
31
31
|
|
|
32
|
+
// Interpret escape sequences (e.g., \n -> newline). Input comes from JSON, but
|
|
33
|
+
// we still normalize common escapes for consistency.
|
|
34
|
+
const interpretEscapes = (str: string): string =>
|
|
35
|
+
(str || "")
|
|
36
|
+
.replace(/\\n/g, "\n")
|
|
37
|
+
.replace(/\\t/g, "\t")
|
|
38
|
+
.replace(/\\r/g, "\r")
|
|
39
|
+
.replace(/\\"/g, '"')
|
|
40
|
+
.replace(/\\'/g, "'");
|
|
41
|
+
|
|
32
42
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
33
43
|
return {
|
|
34
44
|
tools: [
|
|
@@ -43,6 +53,34 @@ export async function startMcpServer(rootOpts?: RootOptsLike, extra?: { debug?:
|
|
|
43
53
|
additionalProperties: false,
|
|
44
54
|
},
|
|
45
55
|
},
|
|
56
|
+
{
|
|
57
|
+
name: "view_issue",
|
|
58
|
+
description: "View a specific issue with its comments",
|
|
59
|
+
inputSchema: {
|
|
60
|
+
type: "object",
|
|
61
|
+
properties: {
|
|
62
|
+
issue_id: { type: "string", description: "Issue ID (UUID)" },
|
|
63
|
+
debug: { type: "boolean", description: "Enable verbose debug logs" },
|
|
64
|
+
},
|
|
65
|
+
required: ["issue_id"],
|
|
66
|
+
additionalProperties: false,
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: "post_issue_comment",
|
|
71
|
+
description: "Post a new comment to an issue (optionally as a reply)",
|
|
72
|
+
inputSchema: {
|
|
73
|
+
type: "object",
|
|
74
|
+
properties: {
|
|
75
|
+
issue_id: { type: "string", description: "Issue ID (UUID)" },
|
|
76
|
+
content: { type: "string", description: "Comment text (supports \\n as newline)" },
|
|
77
|
+
parent_comment_id: { type: "string", description: "Parent comment ID (UUID) for replies" },
|
|
78
|
+
debug: { type: "boolean", description: "Enable verbose debug logs" },
|
|
79
|
+
},
|
|
80
|
+
required: ["issue_id", "content"],
|
|
81
|
+
additionalProperties: false,
|
|
82
|
+
},
|
|
83
|
+
},
|
|
46
84
|
],
|
|
47
85
|
};
|
|
48
86
|
});
|
|
@@ -51,10 +89,6 @@ export async function startMcpServer(rootOpts?: RootOptsLike, extra?: { debug?:
|
|
|
51
89
|
const toolName = req.params.name;
|
|
52
90
|
const args = (req.params.arguments as Record<string, unknown>) || {};
|
|
53
91
|
|
|
54
|
-
if (toolName !== "list_issues") {
|
|
55
|
-
throw new Error(`Unknown tool: ${toolName}`);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
92
|
const cfg = config.readConfig();
|
|
59
93
|
const apiKey = (rootOpts?.apiKey || process.env.PGAI_API_KEY || cfg.apiKey || "").toString();
|
|
60
94
|
const { apiBaseUrl } = resolveBaseUrls(rootOpts, cfg);
|
|
@@ -74,20 +108,44 @@ export async function startMcpServer(rootOpts?: RootOptsLike, extra?: { debug?:
|
|
|
74
108
|
}
|
|
75
109
|
|
|
76
110
|
try {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
content: [
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
111
|
+
if (toolName === "list_issues") {
|
|
112
|
+
const issues = await fetchIssues({ apiKey, apiBaseUrl, debug });
|
|
113
|
+
return { content: [{ type: "text", text: JSON.stringify(issues, null, 2) }] };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (toolName === "view_issue") {
|
|
117
|
+
const issueId = String(args.issue_id || "").trim();
|
|
118
|
+
if (!issueId) {
|
|
119
|
+
return { content: [{ type: "text", text: "issue_id is required" }], isError: true };
|
|
120
|
+
}
|
|
121
|
+
const issue = await fetchIssue({ apiKey, apiBaseUrl, issueId, debug });
|
|
122
|
+
if (!issue) {
|
|
123
|
+
return { content: [{ type: "text", text: "Issue not found" }], isError: true };
|
|
124
|
+
}
|
|
125
|
+
const comments = await fetchIssueComments({ apiKey, apiBaseUrl, issueId, debug });
|
|
126
|
+
const combined = { issue, comments };
|
|
127
|
+
return { content: [{ type: "text", text: JSON.stringify(combined, null, 2) }] };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (toolName === "post_issue_comment") {
|
|
131
|
+
const issueId = String(args.issue_id || "").trim();
|
|
132
|
+
const rawContent = String(args.content || "");
|
|
133
|
+
const parentCommentId = args.parent_comment_id ? String(args.parent_comment_id) : undefined;
|
|
134
|
+
if (!issueId) {
|
|
135
|
+
return { content: [{ type: "text", text: "issue_id is required" }], isError: true };
|
|
136
|
+
}
|
|
137
|
+
if (!rawContent) {
|
|
138
|
+
return { content: [{ type: "text", text: "content is required" }], isError: true };
|
|
139
|
+
}
|
|
140
|
+
const content = interpretEscapes(rawContent);
|
|
141
|
+
const result = await createIssueComment({ apiKey, apiBaseUrl, issueId, content, parentCommentId, debug });
|
|
142
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
throw new Error(`Unknown tool: ${toolName}`);
|
|
83
146
|
} catch (err) {
|
|
84
147
|
const message = err instanceof Error ? err.message : String(err);
|
|
85
|
-
return {
|
|
86
|
-
content: [
|
|
87
|
-
{ type: "text", text: message },
|
|
88
|
-
],
|
|
89
|
-
isError: true,
|
|
90
|
-
};
|
|
148
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
91
149
|
}
|
|
92
150
|
});
|
|
93
151
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "postgresai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0-dev.7",
|
|
4
4
|
"description": "postgres_ai CLI (Node.js)",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"private": false,
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
"build": "tsc",
|
|
26
26
|
"prepare": "npm run build",
|
|
27
27
|
"start": "node ./dist/bin/postgres-ai.js --help",
|
|
28
|
-
"dev": "tsc --watch"
|
|
28
|
+
"dev": "tsc --watch",
|
|
29
|
+
"test": "npm run build && node --test test/*.test.cjs"
|
|
29
30
|
},
|
|
30
31
|
"dependencies": {
|
|
31
32
|
"@modelcontextprotocol/sdk": "^1.20.2",
|