postgresai 0.14.0-dev.55 → 0.14.0-dev.57
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/bin/postgres-ai.ts +201 -8
- package/dist/bin/postgres-ai.js +702 -89
- package/dist/sql/01.role.sql +16 -0
- package/dist/sql/02.permissions.sql +37 -0
- package/dist/sql/03.optional_rds.sql +6 -0
- package/dist/sql/04.optional_self_managed.sql +8 -0
- package/dist/sql/05.helpers.sql +439 -0
- package/dist/sql/sql/01.role.sql +16 -0
- package/dist/sql/sql/02.permissions.sql +37 -0
- package/dist/sql/sql/03.optional_rds.sql +6 -0
- package/dist/sql/sql/04.optional_self_managed.sql +8 -0
- package/dist/sql/sql/05.helpers.sql +439 -0
- package/lib/checkup.ts +3 -0
- package/lib/config.ts +4 -4
- package/lib/init.ts +9 -3
- package/lib/issues.ts +318 -0
- package/lib/mcp-server.ts +207 -73
- package/lib/metrics-embedded.ts +2 -2
- package/package.json +2 -2
- package/sql/05.helpers.sql +31 -7
- package/test/checkup.integration.test.ts +46 -0
- package/test/checkup.test.ts +3 -2
- package/test/init.integration.test.ts +98 -0
- package/test/init.test.ts +72 -0
- package/test/issues.cli.test.ts +314 -0
- package/test/issues.test.ts +456 -0
- package/test/mcp-server.test.ts +988 -0
- package/test/schema-validation.test.ts +1 -1
package/dist/bin/postgres-ai.js
CHANGED
|
@@ -13064,7 +13064,7 @@ var {
|
|
|
13064
13064
|
// package.json
|
|
13065
13065
|
var package_default = {
|
|
13066
13066
|
name: "postgresai",
|
|
13067
|
-
version: "0.14.0-dev.
|
|
13067
|
+
version: "0.14.0-dev.57",
|
|
13068
13068
|
description: "postgres_ai CLI",
|
|
13069
13069
|
license: "Apache-2.0",
|
|
13070
13070
|
private: false,
|
|
@@ -13090,7 +13090,7 @@ var package_default = {
|
|
|
13090
13090
|
},
|
|
13091
13091
|
scripts: {
|
|
13092
13092
|
"embed-metrics": "bun run scripts/embed-metrics.ts",
|
|
13093
|
-
build: `bun run embed-metrics && bun build ./bin/postgres-ai.ts --outdir ./dist/bin --target node && node -e "const fs=require('fs');const f='./dist/bin/postgres-ai.js';fs.writeFileSync(f,fs.readFileSync(f,'utf8').replace('#!/usr/bin/env bun','#!/usr/bin/env node'))"`,
|
|
13093
|
+
build: `bun run embed-metrics && bun build ./bin/postgres-ai.ts --outdir ./dist/bin --target node && node -e "const fs=require('fs');const f='./dist/bin/postgres-ai.js';fs.writeFileSync(f,fs.readFileSync(f,'utf8').replace('#!/usr/bin/env bun','#!/usr/bin/env node'))" && cp -r ./sql ./dist/sql`,
|
|
13094
13094
|
prepublishOnly: "npm run build",
|
|
13095
13095
|
start: "bun ./bin/postgres-ai.ts --help",
|
|
13096
13096
|
"start:node": "node ./dist/bin/postgres-ai.js --help",
|
|
@@ -13145,10 +13145,10 @@ function readConfig() {
|
|
|
13145
13145
|
try {
|
|
13146
13146
|
const content = fs.readFileSync(userConfigPath, "utf8");
|
|
13147
13147
|
const parsed = JSON.parse(content);
|
|
13148
|
-
config.apiKey = parsed.apiKey
|
|
13149
|
-
config.baseUrl = parsed.baseUrl
|
|
13150
|
-
config.orgId = parsed.orgId
|
|
13151
|
-
config.defaultProject = parsed.defaultProject
|
|
13148
|
+
config.apiKey = parsed.apiKey ?? null;
|
|
13149
|
+
config.baseUrl = parsed.baseUrl ?? null;
|
|
13150
|
+
config.orgId = parsed.orgId ?? null;
|
|
13151
|
+
config.defaultProject = parsed.defaultProject ?? null;
|
|
13152
13152
|
return config;
|
|
13153
13153
|
} catch (err) {
|
|
13154
13154
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -15887,7 +15887,7 @@ var Result = import_lib.default.Result;
|
|
|
15887
15887
|
var TypeOverrides = import_lib.default.TypeOverrides;
|
|
15888
15888
|
var defaults = import_lib.default.defaults;
|
|
15889
15889
|
// package.json
|
|
15890
|
-
var version = "0.14.0-dev.
|
|
15890
|
+
var version = "0.14.0-dev.57";
|
|
15891
15891
|
var package_default2 = {
|
|
15892
15892
|
name: "postgresai",
|
|
15893
15893
|
version,
|
|
@@ -15916,7 +15916,7 @@ var package_default2 = {
|
|
|
15916
15916
|
},
|
|
15917
15917
|
scripts: {
|
|
15918
15918
|
"embed-metrics": "bun run scripts/embed-metrics.ts",
|
|
15919
|
-
build: `bun run embed-metrics && bun build ./bin/postgres-ai.ts --outdir ./dist/bin --target node && node -e "const fs=require('fs');const f='./dist/bin/postgres-ai.js';fs.writeFileSync(f,fs.readFileSync(f,'utf8').replace('#!/usr/bin/env bun','#!/usr/bin/env node'))"`,
|
|
15919
|
+
build: `bun run embed-metrics && bun build ./bin/postgres-ai.ts --outdir ./dist/bin --target node && node -e "const fs=require('fs');const f='./dist/bin/postgres-ai.js';fs.writeFileSync(f,fs.readFileSync(f,'utf8').replace('#!/usr/bin/env bun','#!/usr/bin/env node'))" && cp -r ./sql ./dist/sql`,
|
|
15920
15920
|
prepublishOnly: "npm run build",
|
|
15921
15921
|
start: "bun ./bin/postgres-ai.ts --help",
|
|
15922
15922
|
"start:node": "node ./dist/bin/postgres-ai.js --help",
|
|
@@ -15971,10 +15971,10 @@ function readConfig2() {
|
|
|
15971
15971
|
try {
|
|
15972
15972
|
const content = fs2.readFileSync(userConfigPath, "utf8");
|
|
15973
15973
|
const parsed = JSON.parse(content);
|
|
15974
|
-
config.apiKey = parsed.apiKey
|
|
15975
|
-
config.baseUrl = parsed.baseUrl
|
|
15976
|
-
config.orgId = parsed.orgId
|
|
15977
|
-
config.defaultProject = parsed.defaultProject
|
|
15974
|
+
config.apiKey = parsed.apiKey ?? null;
|
|
15975
|
+
config.baseUrl = parsed.baseUrl ?? null;
|
|
15976
|
+
config.orgId = parsed.orgId ?? null;
|
|
15977
|
+
config.defaultProject = parsed.defaultProject ?? null;
|
|
15978
15978
|
return config;
|
|
15979
15979
|
} catch (err) {
|
|
15980
15980
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -16089,7 +16089,8 @@ async function fetchIssues(params) {
|
|
|
16089
16089
|
const headers = {
|
|
16090
16090
|
"access-token": apiKey,
|
|
16091
16091
|
Prefer: "return=representation",
|
|
16092
|
-
"Content-Type": "application/json"
|
|
16092
|
+
"Content-Type": "application/json",
|
|
16093
|
+
Connection: "close"
|
|
16093
16094
|
};
|
|
16094
16095
|
if (debug) {
|
|
16095
16096
|
const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
|
|
@@ -16130,7 +16131,8 @@ async function fetchIssueComments(params) {
|
|
|
16130
16131
|
const headers = {
|
|
16131
16132
|
"access-token": apiKey,
|
|
16132
16133
|
Prefer: "return=representation",
|
|
16133
|
-
"Content-Type": "application/json"
|
|
16134
|
+
"Content-Type": "application/json",
|
|
16135
|
+
Connection: "close"
|
|
16134
16136
|
};
|
|
16135
16137
|
if (debug) {
|
|
16136
16138
|
const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
|
|
@@ -16174,7 +16176,8 @@ async function fetchIssue(params) {
|
|
|
16174
16176
|
const headers = {
|
|
16175
16177
|
"access-token": apiKey,
|
|
16176
16178
|
Prefer: "return=representation",
|
|
16177
|
-
"Content-Type": "application/json"
|
|
16179
|
+
"Content-Type": "application/json",
|
|
16180
|
+
Connection: "close"
|
|
16178
16181
|
};
|
|
16179
16182
|
if (debug) {
|
|
16180
16183
|
const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
|
|
@@ -16207,6 +16210,67 @@ async function fetchIssue(params) {
|
|
|
16207
16210
|
throw new Error(formatHttpError("Failed to fetch issue", response.status, data));
|
|
16208
16211
|
}
|
|
16209
16212
|
}
|
|
16213
|
+
async function createIssue(params) {
|
|
16214
|
+
const { apiKey, apiBaseUrl, title, orgId, description, projectId, labels, debug } = params;
|
|
16215
|
+
if (!apiKey) {
|
|
16216
|
+
throw new Error("API key is required");
|
|
16217
|
+
}
|
|
16218
|
+
if (!title) {
|
|
16219
|
+
throw new Error("title is required");
|
|
16220
|
+
}
|
|
16221
|
+
if (typeof orgId !== "number") {
|
|
16222
|
+
throw new Error("orgId is required");
|
|
16223
|
+
}
|
|
16224
|
+
const base = normalizeBaseUrl(apiBaseUrl);
|
|
16225
|
+
const url = new URL(`${base}/rpc/issue_create`);
|
|
16226
|
+
const bodyObj = {
|
|
16227
|
+
title,
|
|
16228
|
+
org_id: orgId
|
|
16229
|
+
};
|
|
16230
|
+
if (description !== undefined) {
|
|
16231
|
+
bodyObj.description = description;
|
|
16232
|
+
}
|
|
16233
|
+
if (projectId !== undefined) {
|
|
16234
|
+
bodyObj.project_id = projectId;
|
|
16235
|
+
}
|
|
16236
|
+
if (labels && labels.length > 0) {
|
|
16237
|
+
bodyObj.labels = labels;
|
|
16238
|
+
}
|
|
16239
|
+
const body = JSON.stringify(bodyObj);
|
|
16240
|
+
const headers = {
|
|
16241
|
+
"access-token": apiKey,
|
|
16242
|
+
Prefer: "return=representation",
|
|
16243
|
+
"Content-Type": "application/json",
|
|
16244
|
+
Connection: "close"
|
|
16245
|
+
};
|
|
16246
|
+
if (debug) {
|
|
16247
|
+
const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
|
|
16248
|
+
console.log(`Debug: Resolved API base URL: ${base}`);
|
|
16249
|
+
console.log(`Debug: POST URL: ${url.toString()}`);
|
|
16250
|
+
console.log(`Debug: Auth scheme: access-token`);
|
|
16251
|
+
console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
|
|
16252
|
+
console.log(`Debug: Request body: ${body}`);
|
|
16253
|
+
}
|
|
16254
|
+
const response = await fetch(url.toString(), {
|
|
16255
|
+
method: "POST",
|
|
16256
|
+
headers,
|
|
16257
|
+
body
|
|
16258
|
+
});
|
|
16259
|
+
if (debug) {
|
|
16260
|
+
console.log(`Debug: Response status: ${response.status}`);
|
|
16261
|
+
console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
|
|
16262
|
+
}
|
|
16263
|
+
const data = await response.text();
|
|
16264
|
+
if (response.ok) {
|
|
16265
|
+
try {
|
|
16266
|
+
return JSON.parse(data);
|
|
16267
|
+
} catch {
|
|
16268
|
+
throw new Error(`Failed to parse create issue response: ${data}`);
|
|
16269
|
+
}
|
|
16270
|
+
} else {
|
|
16271
|
+
throw new Error(formatHttpError("Failed to create issue", response.status, data));
|
|
16272
|
+
}
|
|
16273
|
+
}
|
|
16210
16274
|
async function createIssueComment(params) {
|
|
16211
16275
|
const { apiKey, apiBaseUrl, issueId, content, parentCommentId, debug } = params;
|
|
16212
16276
|
if (!apiKey) {
|
|
@@ -16231,7 +16295,8 @@ async function createIssueComment(params) {
|
|
|
16231
16295
|
const headers = {
|
|
16232
16296
|
"access-token": apiKey,
|
|
16233
16297
|
Prefer: "return=representation",
|
|
16234
|
-
"Content-Type": "application/json"
|
|
16298
|
+
"Content-Type": "application/json",
|
|
16299
|
+
Connection: "close"
|
|
16235
16300
|
};
|
|
16236
16301
|
if (debug) {
|
|
16237
16302
|
const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
|
|
@@ -16261,6 +16326,121 @@ async function createIssueComment(params) {
|
|
|
16261
16326
|
throw new Error(formatHttpError("Failed to create issue comment", response.status, data));
|
|
16262
16327
|
}
|
|
16263
16328
|
}
|
|
16329
|
+
async function updateIssue(params) {
|
|
16330
|
+
const { apiKey, apiBaseUrl, issueId, title, description, status, labels, debug } = params;
|
|
16331
|
+
if (!apiKey) {
|
|
16332
|
+
throw new Error("API key is required");
|
|
16333
|
+
}
|
|
16334
|
+
if (!issueId) {
|
|
16335
|
+
throw new Error("issueId is required");
|
|
16336
|
+
}
|
|
16337
|
+
if (title === undefined && description === undefined && status === undefined && labels === undefined) {
|
|
16338
|
+
throw new Error("At least one field to update is required (title, description, status, or labels)");
|
|
16339
|
+
}
|
|
16340
|
+
const base = normalizeBaseUrl(apiBaseUrl);
|
|
16341
|
+
const url = new URL(`${base}/rpc/issue_update`);
|
|
16342
|
+
const bodyObj = {
|
|
16343
|
+
p_id: issueId
|
|
16344
|
+
};
|
|
16345
|
+
if (title !== undefined) {
|
|
16346
|
+
bodyObj.p_title = title;
|
|
16347
|
+
}
|
|
16348
|
+
if (description !== undefined) {
|
|
16349
|
+
bodyObj.p_description = description;
|
|
16350
|
+
}
|
|
16351
|
+
if (status !== undefined) {
|
|
16352
|
+
bodyObj.p_status = status;
|
|
16353
|
+
}
|
|
16354
|
+
if (labels !== undefined) {
|
|
16355
|
+
bodyObj.p_labels = labels;
|
|
16356
|
+
}
|
|
16357
|
+
const body = JSON.stringify(bodyObj);
|
|
16358
|
+
const headers = {
|
|
16359
|
+
"access-token": apiKey,
|
|
16360
|
+
Prefer: "return=representation",
|
|
16361
|
+
"Content-Type": "application/json",
|
|
16362
|
+
Connection: "close"
|
|
16363
|
+
};
|
|
16364
|
+
if (debug) {
|
|
16365
|
+
const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
|
|
16366
|
+
console.log(`Debug: Resolved API base URL: ${base}`);
|
|
16367
|
+
console.log(`Debug: POST URL: ${url.toString()}`);
|
|
16368
|
+
console.log(`Debug: Auth scheme: access-token`);
|
|
16369
|
+
console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
|
|
16370
|
+
console.log(`Debug: Request body: ${body}`);
|
|
16371
|
+
}
|
|
16372
|
+
const response = await fetch(url.toString(), {
|
|
16373
|
+
method: "POST",
|
|
16374
|
+
headers,
|
|
16375
|
+
body
|
|
16376
|
+
});
|
|
16377
|
+
if (debug) {
|
|
16378
|
+
console.log(`Debug: Response status: ${response.status}`);
|
|
16379
|
+
console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
|
|
16380
|
+
}
|
|
16381
|
+
const data = await response.text();
|
|
16382
|
+
if (response.ok) {
|
|
16383
|
+
try {
|
|
16384
|
+
return JSON.parse(data);
|
|
16385
|
+
} catch {
|
|
16386
|
+
throw new Error(`Failed to parse update issue response: ${data}`);
|
|
16387
|
+
}
|
|
16388
|
+
} else {
|
|
16389
|
+
throw new Error(formatHttpError("Failed to update issue", response.status, data));
|
|
16390
|
+
}
|
|
16391
|
+
}
|
|
16392
|
+
async function updateIssueComment(params) {
|
|
16393
|
+
const { apiKey, apiBaseUrl, commentId, content, debug } = params;
|
|
16394
|
+
if (!apiKey) {
|
|
16395
|
+
throw new Error("API key is required");
|
|
16396
|
+
}
|
|
16397
|
+
if (!commentId) {
|
|
16398
|
+
throw new Error("commentId is required");
|
|
16399
|
+
}
|
|
16400
|
+
if (!content) {
|
|
16401
|
+
throw new Error("content is required");
|
|
16402
|
+
}
|
|
16403
|
+
const base = normalizeBaseUrl(apiBaseUrl);
|
|
16404
|
+
const url = new URL(`${base}/rpc/issue_comment_update`);
|
|
16405
|
+
const bodyObj = {
|
|
16406
|
+
p_id: commentId,
|
|
16407
|
+
p_content: content
|
|
16408
|
+
};
|
|
16409
|
+
const body = JSON.stringify(bodyObj);
|
|
16410
|
+
const headers = {
|
|
16411
|
+
"access-token": apiKey,
|
|
16412
|
+
Prefer: "return=representation",
|
|
16413
|
+
"Content-Type": "application/json",
|
|
16414
|
+
Connection: "close"
|
|
16415
|
+
};
|
|
16416
|
+
if (debug) {
|
|
16417
|
+
const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
|
|
16418
|
+
console.log(`Debug: Resolved API base URL: ${base}`);
|
|
16419
|
+
console.log(`Debug: POST URL: ${url.toString()}`);
|
|
16420
|
+
console.log(`Debug: Auth scheme: access-token`);
|
|
16421
|
+
console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
|
|
16422
|
+
console.log(`Debug: Request body: ${body}`);
|
|
16423
|
+
}
|
|
16424
|
+
const response = await fetch(url.toString(), {
|
|
16425
|
+
method: "POST",
|
|
16426
|
+
headers,
|
|
16427
|
+
body
|
|
16428
|
+
});
|
|
16429
|
+
if (debug) {
|
|
16430
|
+
console.log(`Debug: Response status: ${response.status}`);
|
|
16431
|
+
console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
|
|
16432
|
+
}
|
|
16433
|
+
const data = await response.text();
|
|
16434
|
+
if (response.ok) {
|
|
16435
|
+
try {
|
|
16436
|
+
return JSON.parse(data);
|
|
16437
|
+
} catch {
|
|
16438
|
+
throw new Error(`Failed to parse update comment response: ${data}`);
|
|
16439
|
+
}
|
|
16440
|
+
} else {
|
|
16441
|
+
throw new Error(formatHttpError("Failed to update issue comment", response.status, data));
|
|
16442
|
+
}
|
|
16443
|
+
}
|
|
16264
16444
|
|
|
16265
16445
|
// node_modules/zod/v4/core/core.js
|
|
16266
16446
|
var NEVER = Object.freeze({
|
|
@@ -23148,10 +23328,116 @@ class StdioServerTransport {
|
|
|
23148
23328
|
}
|
|
23149
23329
|
|
|
23150
23330
|
// lib/mcp-server.ts
|
|
23331
|
+
var interpretEscapes = (str2) => (str2 || "").replace(/\\n/g, `
|
|
23332
|
+
`).replace(/\\t/g, "\t").replace(/\\r/g, "\r").replace(/\\"/g, '"').replace(/\\'/g, "'");
|
|
23333
|
+
async function handleToolCall(req, rootOpts, extra) {
|
|
23334
|
+
const toolName = req.params.name;
|
|
23335
|
+
const args = req.params.arguments || {};
|
|
23336
|
+
const cfg = readConfig2();
|
|
23337
|
+
const apiKey = (rootOpts?.apiKey || process.env.PGAI_API_KEY || cfg.apiKey || "").toString();
|
|
23338
|
+
const { apiBaseUrl } = resolveBaseUrls(rootOpts, cfg);
|
|
23339
|
+
const debug = Boolean(args.debug ?? extra?.debug);
|
|
23340
|
+
if (!apiKey) {
|
|
23341
|
+
return {
|
|
23342
|
+
content: [
|
|
23343
|
+
{
|
|
23344
|
+
type: "text",
|
|
23345
|
+
text: "API key is required. Run 'pgai auth' or set PGAI_API_KEY."
|
|
23346
|
+
}
|
|
23347
|
+
],
|
|
23348
|
+
isError: true
|
|
23349
|
+
};
|
|
23350
|
+
}
|
|
23351
|
+
try {
|
|
23352
|
+
if (toolName === "list_issues") {
|
|
23353
|
+
const issues = await fetchIssues({ apiKey, apiBaseUrl, debug });
|
|
23354
|
+
return { content: [{ type: "text", text: JSON.stringify(issues, null, 2) }] };
|
|
23355
|
+
}
|
|
23356
|
+
if (toolName === "view_issue") {
|
|
23357
|
+
const issueId = String(args.issue_id || "").trim();
|
|
23358
|
+
if (!issueId) {
|
|
23359
|
+
return { content: [{ type: "text", text: "issue_id is required" }], isError: true };
|
|
23360
|
+
}
|
|
23361
|
+
const issue2 = await fetchIssue({ apiKey, apiBaseUrl, issueId, debug });
|
|
23362
|
+
if (!issue2) {
|
|
23363
|
+
return { content: [{ type: "text", text: "Issue not found" }], isError: true };
|
|
23364
|
+
}
|
|
23365
|
+
const comments = await fetchIssueComments({ apiKey, apiBaseUrl, issueId, debug });
|
|
23366
|
+
const combined = { issue: issue2, comments };
|
|
23367
|
+
return { content: [{ type: "text", text: JSON.stringify(combined, null, 2) }] };
|
|
23368
|
+
}
|
|
23369
|
+
if (toolName === "post_issue_comment") {
|
|
23370
|
+
const issueId = String(args.issue_id || "").trim();
|
|
23371
|
+
const rawContent = String(args.content || "");
|
|
23372
|
+
const parentCommentId = args.parent_comment_id ? String(args.parent_comment_id) : undefined;
|
|
23373
|
+
if (!issueId) {
|
|
23374
|
+
return { content: [{ type: "text", text: "issue_id is required" }], isError: true };
|
|
23375
|
+
}
|
|
23376
|
+
if (!rawContent) {
|
|
23377
|
+
return { content: [{ type: "text", text: "content is required" }], isError: true };
|
|
23378
|
+
}
|
|
23379
|
+
const content = interpretEscapes(rawContent);
|
|
23380
|
+
const result = await createIssueComment({ apiKey, apiBaseUrl, issueId, content, parentCommentId, debug });
|
|
23381
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
23382
|
+
}
|
|
23383
|
+
if (toolName === "create_issue") {
|
|
23384
|
+
const rawTitle = String(args.title || "").trim();
|
|
23385
|
+
if (!rawTitle) {
|
|
23386
|
+
return { content: [{ type: "text", text: "title is required" }], isError: true };
|
|
23387
|
+
}
|
|
23388
|
+
const title = interpretEscapes(rawTitle);
|
|
23389
|
+
const rawDescription = args.description ? String(args.description) : undefined;
|
|
23390
|
+
const description = rawDescription ? interpretEscapes(rawDescription) : undefined;
|
|
23391
|
+
const projectId = args.project_id !== undefined ? Number(args.project_id) : undefined;
|
|
23392
|
+
const labels = Array.isArray(args.labels) ? args.labels.map(String) : undefined;
|
|
23393
|
+
const orgId = args.org_id !== undefined ? Number(args.org_id) : cfg.orgId;
|
|
23394
|
+
if (orgId === undefined || orgId === null || Number.isNaN(orgId)) {
|
|
23395
|
+
return { content: [{ type: "text", text: "org_id is required. Either provide it as a parameter or run 'pgai auth' to set it in config." }], isError: true };
|
|
23396
|
+
}
|
|
23397
|
+
const result = await createIssue({ apiKey, apiBaseUrl, title, orgId, description, projectId, labels, debug });
|
|
23398
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
23399
|
+
}
|
|
23400
|
+
if (toolName === "update_issue") {
|
|
23401
|
+
const issueId = String(args.issue_id || "").trim();
|
|
23402
|
+
if (!issueId) {
|
|
23403
|
+
return { content: [{ type: "text", text: "issue_id is required" }], isError: true };
|
|
23404
|
+
}
|
|
23405
|
+
const rawTitle = args.title !== undefined ? String(args.title) : undefined;
|
|
23406
|
+
const title = rawTitle !== undefined ? interpretEscapes(rawTitle) : undefined;
|
|
23407
|
+
const rawDescription = args.description !== undefined ? String(args.description) : undefined;
|
|
23408
|
+
const description = rawDescription !== undefined ? interpretEscapes(rawDescription) : undefined;
|
|
23409
|
+
const status = args.status !== undefined ? Number(args.status) : undefined;
|
|
23410
|
+
const labels = Array.isArray(args.labels) ? args.labels.map(String) : undefined;
|
|
23411
|
+
if (title === undefined && description === undefined && status === undefined && labels === undefined) {
|
|
23412
|
+
return { content: [{ type: "text", text: "At least one field to update is required (title, description, status, or labels)" }], isError: true };
|
|
23413
|
+
}
|
|
23414
|
+
if (status !== undefined && (Number.isNaN(status) || status !== 0 && status !== 1)) {
|
|
23415
|
+
return { content: [{ type: "text", text: "status must be 0 (open) or 1 (closed)" }], isError: true };
|
|
23416
|
+
}
|
|
23417
|
+
const result = await updateIssue({ apiKey, apiBaseUrl, issueId, title, description, status, labels, debug });
|
|
23418
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
23419
|
+
}
|
|
23420
|
+
if (toolName === "update_issue_comment") {
|
|
23421
|
+
const commentId = String(args.comment_id || "").trim();
|
|
23422
|
+
const rawContent = String(args.content || "");
|
|
23423
|
+
if (!commentId) {
|
|
23424
|
+
return { content: [{ type: "text", text: "comment_id is required" }], isError: true };
|
|
23425
|
+
}
|
|
23426
|
+
if (!rawContent.trim()) {
|
|
23427
|
+
return { content: [{ type: "text", text: "content is required" }], isError: true };
|
|
23428
|
+
}
|
|
23429
|
+
const content = interpretEscapes(rawContent);
|
|
23430
|
+
const result = await updateIssueComment({ apiKey, apiBaseUrl, commentId, content, debug });
|
|
23431
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
23432
|
+
}
|
|
23433
|
+
throw new Error(`Unknown tool: ${toolName}`);
|
|
23434
|
+
} catch (err) {
|
|
23435
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
23436
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
23437
|
+
}
|
|
23438
|
+
}
|
|
23151
23439
|
async function startMcpServer(rootOpts, extra) {
|
|
23152
23440
|
const server = new Server({ name: "postgresai-mcp", version: package_default2.version }, { capabilities: { tools: {} } });
|
|
23153
|
-
const interpretEscapes = (str2) => (str2 || "").replace(/\\n/g, `
|
|
23154
|
-
`).replace(/\\t/g, "\t").replace(/\\r/g, "\r").replace(/\\"/g, '"').replace(/\\'/g, "'");
|
|
23155
23441
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
23156
23442
|
return {
|
|
23157
23443
|
tools: [
|
|
@@ -23193,65 +23479,68 @@ async function startMcpServer(rootOpts, extra) {
|
|
|
23193
23479
|
required: ["issue_id", "content"],
|
|
23194
23480
|
additionalProperties: false
|
|
23195
23481
|
}
|
|
23482
|
+
},
|
|
23483
|
+
{
|
|
23484
|
+
name: "create_issue",
|
|
23485
|
+
description: "Create a new issue in PostgresAI",
|
|
23486
|
+
inputSchema: {
|
|
23487
|
+
type: "object",
|
|
23488
|
+
properties: {
|
|
23489
|
+
title: { type: "string", description: "Issue title (required)" },
|
|
23490
|
+
description: { type: "string", description: "Issue description (supports \\n as newline)" },
|
|
23491
|
+
org_id: { type: "number", description: "Organization ID (uses config value if not provided)" },
|
|
23492
|
+
project_id: { type: "number", description: "Project ID to associate the issue with" },
|
|
23493
|
+
labels: {
|
|
23494
|
+
type: "array",
|
|
23495
|
+
items: { type: "string" },
|
|
23496
|
+
description: "Labels to apply to the issue"
|
|
23497
|
+
},
|
|
23498
|
+
debug: { type: "boolean", description: "Enable verbose debug logs" }
|
|
23499
|
+
},
|
|
23500
|
+
required: ["title"],
|
|
23501
|
+
additionalProperties: false
|
|
23502
|
+
}
|
|
23503
|
+
},
|
|
23504
|
+
{
|
|
23505
|
+
name: "update_issue",
|
|
23506
|
+
description: "Update an existing issue (title, description, status, labels). Use status=1 to close, status=0 to reopen.",
|
|
23507
|
+
inputSchema: {
|
|
23508
|
+
type: "object",
|
|
23509
|
+
properties: {
|
|
23510
|
+
issue_id: { type: "string", description: "Issue ID (UUID)" },
|
|
23511
|
+
title: { type: "string", description: "New title (supports \\n as newline)" },
|
|
23512
|
+
description: { type: "string", description: "New description (supports \\n as newline)" },
|
|
23513
|
+
status: { type: "number", description: "Status: 0=open, 1=closed" },
|
|
23514
|
+
labels: {
|
|
23515
|
+
type: "array",
|
|
23516
|
+
items: { type: "string" },
|
|
23517
|
+
description: "Labels to set on the issue"
|
|
23518
|
+
},
|
|
23519
|
+
debug: { type: "boolean", description: "Enable verbose debug logs" }
|
|
23520
|
+
},
|
|
23521
|
+
required: ["issue_id"],
|
|
23522
|
+
additionalProperties: false
|
|
23523
|
+
}
|
|
23524
|
+
},
|
|
23525
|
+
{
|
|
23526
|
+
name: "update_issue_comment",
|
|
23527
|
+
description: "Update an existing issue comment",
|
|
23528
|
+
inputSchema: {
|
|
23529
|
+
type: "object",
|
|
23530
|
+
properties: {
|
|
23531
|
+
comment_id: { type: "string", description: "Comment ID (UUID)" },
|
|
23532
|
+
content: { type: "string", description: "New comment text (supports \\n as newline)" },
|
|
23533
|
+
debug: { type: "boolean", description: "Enable verbose debug logs" }
|
|
23534
|
+
},
|
|
23535
|
+
required: ["comment_id", "content"],
|
|
23536
|
+
additionalProperties: false
|
|
23537
|
+
}
|
|
23196
23538
|
}
|
|
23197
23539
|
]
|
|
23198
23540
|
};
|
|
23199
23541
|
});
|
|
23200
23542
|
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
23201
|
-
|
|
23202
|
-
const args = req.params.arguments || {};
|
|
23203
|
-
const cfg = readConfig2();
|
|
23204
|
-
const apiKey = (rootOpts?.apiKey || process.env.PGAI_API_KEY || cfg.apiKey || "").toString();
|
|
23205
|
-
const { apiBaseUrl } = resolveBaseUrls(rootOpts, cfg);
|
|
23206
|
-
const debug = Boolean(args.debug ?? extra?.debug);
|
|
23207
|
-
if (!apiKey) {
|
|
23208
|
-
return {
|
|
23209
|
-
content: [
|
|
23210
|
-
{
|
|
23211
|
-
type: "text",
|
|
23212
|
-
text: "API key is required. Run 'pgai auth' or set PGAI_API_KEY."
|
|
23213
|
-
}
|
|
23214
|
-
],
|
|
23215
|
-
isError: true
|
|
23216
|
-
};
|
|
23217
|
-
}
|
|
23218
|
-
try {
|
|
23219
|
-
if (toolName === "list_issues") {
|
|
23220
|
-
const issues = await fetchIssues({ apiKey, apiBaseUrl, debug });
|
|
23221
|
-
return { content: [{ type: "text", text: JSON.stringify(issues, null, 2) }] };
|
|
23222
|
-
}
|
|
23223
|
-
if (toolName === "view_issue") {
|
|
23224
|
-
const issueId = String(args.issue_id || "").trim();
|
|
23225
|
-
if (!issueId) {
|
|
23226
|
-
return { content: [{ type: "text", text: "issue_id is required" }], isError: true };
|
|
23227
|
-
}
|
|
23228
|
-
const issue2 = await fetchIssue({ apiKey, apiBaseUrl, issueId, debug });
|
|
23229
|
-
if (!issue2) {
|
|
23230
|
-
return { content: [{ type: "text", text: "Issue not found" }], isError: true };
|
|
23231
|
-
}
|
|
23232
|
-
const comments = await fetchIssueComments({ apiKey, apiBaseUrl, issueId, debug });
|
|
23233
|
-
const combined = { issue: issue2, comments };
|
|
23234
|
-
return { content: [{ type: "text", text: JSON.stringify(combined, null, 2) }] };
|
|
23235
|
-
}
|
|
23236
|
-
if (toolName === "post_issue_comment") {
|
|
23237
|
-
const issueId = String(args.issue_id || "").trim();
|
|
23238
|
-
const rawContent = String(args.content || "");
|
|
23239
|
-
const parentCommentId = args.parent_comment_id ? String(args.parent_comment_id) : undefined;
|
|
23240
|
-
if (!issueId) {
|
|
23241
|
-
return { content: [{ type: "text", text: "issue_id is required" }], isError: true };
|
|
23242
|
-
}
|
|
23243
|
-
if (!rawContent) {
|
|
23244
|
-
return { content: [{ type: "text", text: "content is required" }], isError: true };
|
|
23245
|
-
}
|
|
23246
|
-
const content = interpretEscapes(rawContent);
|
|
23247
|
-
const result = await createIssueComment({ apiKey, apiBaseUrl, issueId, content, parentCommentId, debug });
|
|
23248
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
23249
|
-
}
|
|
23250
|
-
throw new Error(`Unknown tool: ${toolName}`);
|
|
23251
|
-
} catch (err) {
|
|
23252
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
23253
|
-
return { content: [{ type: "text", text: message }], isError: true };
|
|
23254
|
-
}
|
|
23543
|
+
return handleToolCall(req, rootOpts, extra);
|
|
23255
23544
|
});
|
|
23256
23545
|
const transport = new StdioServerTransport;
|
|
23257
23546
|
await server.connect(transport);
|
|
@@ -23269,7 +23558,8 @@ async function fetchIssues2(params) {
|
|
|
23269
23558
|
const headers = {
|
|
23270
23559
|
"access-token": apiKey,
|
|
23271
23560
|
Prefer: "return=representation",
|
|
23272
|
-
"Content-Type": "application/json"
|
|
23561
|
+
"Content-Type": "application/json",
|
|
23562
|
+
Connection: "close"
|
|
23273
23563
|
};
|
|
23274
23564
|
if (debug) {
|
|
23275
23565
|
const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
|
|
@@ -23310,7 +23600,8 @@ async function fetchIssueComments2(params) {
|
|
|
23310
23600
|
const headers = {
|
|
23311
23601
|
"access-token": apiKey,
|
|
23312
23602
|
Prefer: "return=representation",
|
|
23313
|
-
"Content-Type": "application/json"
|
|
23603
|
+
"Content-Type": "application/json",
|
|
23604
|
+
Connection: "close"
|
|
23314
23605
|
};
|
|
23315
23606
|
if (debug) {
|
|
23316
23607
|
const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
|
|
@@ -23354,7 +23645,8 @@ async function fetchIssue2(params) {
|
|
|
23354
23645
|
const headers = {
|
|
23355
23646
|
"access-token": apiKey,
|
|
23356
23647
|
Prefer: "return=representation",
|
|
23357
|
-
"Content-Type": "application/json"
|
|
23648
|
+
"Content-Type": "application/json",
|
|
23649
|
+
Connection: "close"
|
|
23358
23650
|
};
|
|
23359
23651
|
if (debug) {
|
|
23360
23652
|
const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
|
|
@@ -23387,6 +23679,67 @@ async function fetchIssue2(params) {
|
|
|
23387
23679
|
throw new Error(formatHttpError("Failed to fetch issue", response.status, data));
|
|
23388
23680
|
}
|
|
23389
23681
|
}
|
|
23682
|
+
async function createIssue2(params) {
|
|
23683
|
+
const { apiKey, apiBaseUrl, title, orgId, description, projectId, labels, debug } = params;
|
|
23684
|
+
if (!apiKey) {
|
|
23685
|
+
throw new Error("API key is required");
|
|
23686
|
+
}
|
|
23687
|
+
if (!title) {
|
|
23688
|
+
throw new Error("title is required");
|
|
23689
|
+
}
|
|
23690
|
+
if (typeof orgId !== "number") {
|
|
23691
|
+
throw new Error("orgId is required");
|
|
23692
|
+
}
|
|
23693
|
+
const base = normalizeBaseUrl(apiBaseUrl);
|
|
23694
|
+
const url = new URL(`${base}/rpc/issue_create`);
|
|
23695
|
+
const bodyObj = {
|
|
23696
|
+
title,
|
|
23697
|
+
org_id: orgId
|
|
23698
|
+
};
|
|
23699
|
+
if (description !== undefined) {
|
|
23700
|
+
bodyObj.description = description;
|
|
23701
|
+
}
|
|
23702
|
+
if (projectId !== undefined) {
|
|
23703
|
+
bodyObj.project_id = projectId;
|
|
23704
|
+
}
|
|
23705
|
+
if (labels && labels.length > 0) {
|
|
23706
|
+
bodyObj.labels = labels;
|
|
23707
|
+
}
|
|
23708
|
+
const body = JSON.stringify(bodyObj);
|
|
23709
|
+
const headers = {
|
|
23710
|
+
"access-token": apiKey,
|
|
23711
|
+
Prefer: "return=representation",
|
|
23712
|
+
"Content-Type": "application/json",
|
|
23713
|
+
Connection: "close"
|
|
23714
|
+
};
|
|
23715
|
+
if (debug) {
|
|
23716
|
+
const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
|
|
23717
|
+
console.log(`Debug: Resolved API base URL: ${base}`);
|
|
23718
|
+
console.log(`Debug: POST URL: ${url.toString()}`);
|
|
23719
|
+
console.log(`Debug: Auth scheme: access-token`);
|
|
23720
|
+
console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
|
|
23721
|
+
console.log(`Debug: Request body: ${body}`);
|
|
23722
|
+
}
|
|
23723
|
+
const response = await fetch(url.toString(), {
|
|
23724
|
+
method: "POST",
|
|
23725
|
+
headers,
|
|
23726
|
+
body
|
|
23727
|
+
});
|
|
23728
|
+
if (debug) {
|
|
23729
|
+
console.log(`Debug: Response status: ${response.status}`);
|
|
23730
|
+
console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
|
|
23731
|
+
}
|
|
23732
|
+
const data = await response.text();
|
|
23733
|
+
if (response.ok) {
|
|
23734
|
+
try {
|
|
23735
|
+
return JSON.parse(data);
|
|
23736
|
+
} catch {
|
|
23737
|
+
throw new Error(`Failed to parse create issue response: ${data}`);
|
|
23738
|
+
}
|
|
23739
|
+
} else {
|
|
23740
|
+
throw new Error(formatHttpError("Failed to create issue", response.status, data));
|
|
23741
|
+
}
|
|
23742
|
+
}
|
|
23390
23743
|
async function createIssueComment2(params) {
|
|
23391
23744
|
const { apiKey, apiBaseUrl, issueId, content, parentCommentId, debug } = params;
|
|
23392
23745
|
if (!apiKey) {
|
|
@@ -23411,7 +23764,8 @@ async function createIssueComment2(params) {
|
|
|
23411
23764
|
const headers = {
|
|
23412
23765
|
"access-token": apiKey,
|
|
23413
23766
|
Prefer: "return=representation",
|
|
23414
|
-
"Content-Type": "application/json"
|
|
23767
|
+
"Content-Type": "application/json",
|
|
23768
|
+
Connection: "close"
|
|
23415
23769
|
};
|
|
23416
23770
|
if (debug) {
|
|
23417
23771
|
const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
|
|
@@ -23441,6 +23795,121 @@ async function createIssueComment2(params) {
|
|
|
23441
23795
|
throw new Error(formatHttpError("Failed to create issue comment", response.status, data));
|
|
23442
23796
|
}
|
|
23443
23797
|
}
|
|
23798
|
+
async function updateIssue2(params) {
|
|
23799
|
+
const { apiKey, apiBaseUrl, issueId, title, description, status, labels, debug } = params;
|
|
23800
|
+
if (!apiKey) {
|
|
23801
|
+
throw new Error("API key is required");
|
|
23802
|
+
}
|
|
23803
|
+
if (!issueId) {
|
|
23804
|
+
throw new Error("issueId is required");
|
|
23805
|
+
}
|
|
23806
|
+
if (title === undefined && description === undefined && status === undefined && labels === undefined) {
|
|
23807
|
+
throw new Error("At least one field to update is required (title, description, status, or labels)");
|
|
23808
|
+
}
|
|
23809
|
+
const base = normalizeBaseUrl(apiBaseUrl);
|
|
23810
|
+
const url = new URL(`${base}/rpc/issue_update`);
|
|
23811
|
+
const bodyObj = {
|
|
23812
|
+
p_id: issueId
|
|
23813
|
+
};
|
|
23814
|
+
if (title !== undefined) {
|
|
23815
|
+
bodyObj.p_title = title;
|
|
23816
|
+
}
|
|
23817
|
+
if (description !== undefined) {
|
|
23818
|
+
bodyObj.p_description = description;
|
|
23819
|
+
}
|
|
23820
|
+
if (status !== undefined) {
|
|
23821
|
+
bodyObj.p_status = status;
|
|
23822
|
+
}
|
|
23823
|
+
if (labels !== undefined) {
|
|
23824
|
+
bodyObj.p_labels = labels;
|
|
23825
|
+
}
|
|
23826
|
+
const body = JSON.stringify(bodyObj);
|
|
23827
|
+
const headers = {
|
|
23828
|
+
"access-token": apiKey,
|
|
23829
|
+
Prefer: "return=representation",
|
|
23830
|
+
"Content-Type": "application/json",
|
|
23831
|
+
Connection: "close"
|
|
23832
|
+
};
|
|
23833
|
+
if (debug) {
|
|
23834
|
+
const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
|
|
23835
|
+
console.log(`Debug: Resolved API base URL: ${base}`);
|
|
23836
|
+
console.log(`Debug: POST URL: ${url.toString()}`);
|
|
23837
|
+
console.log(`Debug: Auth scheme: access-token`);
|
|
23838
|
+
console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
|
|
23839
|
+
console.log(`Debug: Request body: ${body}`);
|
|
23840
|
+
}
|
|
23841
|
+
const response = await fetch(url.toString(), {
|
|
23842
|
+
method: "POST",
|
|
23843
|
+
headers,
|
|
23844
|
+
body
|
|
23845
|
+
});
|
|
23846
|
+
if (debug) {
|
|
23847
|
+
console.log(`Debug: Response status: ${response.status}`);
|
|
23848
|
+
console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
|
|
23849
|
+
}
|
|
23850
|
+
const data = await response.text();
|
|
23851
|
+
if (response.ok) {
|
|
23852
|
+
try {
|
|
23853
|
+
return JSON.parse(data);
|
|
23854
|
+
} catch {
|
|
23855
|
+
throw new Error(`Failed to parse update issue response: ${data}`);
|
|
23856
|
+
}
|
|
23857
|
+
} else {
|
|
23858
|
+
throw new Error(formatHttpError("Failed to update issue", response.status, data));
|
|
23859
|
+
}
|
|
23860
|
+
}
|
|
23861
|
+
async function updateIssueComment2(params) {
|
|
23862
|
+
const { apiKey, apiBaseUrl, commentId, content, debug } = params;
|
|
23863
|
+
if (!apiKey) {
|
|
23864
|
+
throw new Error("API key is required");
|
|
23865
|
+
}
|
|
23866
|
+
if (!commentId) {
|
|
23867
|
+
throw new Error("commentId is required");
|
|
23868
|
+
}
|
|
23869
|
+
if (!content) {
|
|
23870
|
+
throw new Error("content is required");
|
|
23871
|
+
}
|
|
23872
|
+
const base = normalizeBaseUrl(apiBaseUrl);
|
|
23873
|
+
const url = new URL(`${base}/rpc/issue_comment_update`);
|
|
23874
|
+
const bodyObj = {
|
|
23875
|
+
p_id: commentId,
|
|
23876
|
+
p_content: content
|
|
23877
|
+
};
|
|
23878
|
+
const body = JSON.stringify(bodyObj);
|
|
23879
|
+
const headers = {
|
|
23880
|
+
"access-token": apiKey,
|
|
23881
|
+
Prefer: "return=representation",
|
|
23882
|
+
"Content-Type": "application/json",
|
|
23883
|
+
Connection: "close"
|
|
23884
|
+
};
|
|
23885
|
+
if (debug) {
|
|
23886
|
+
const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
|
|
23887
|
+
console.log(`Debug: Resolved API base URL: ${base}`);
|
|
23888
|
+
console.log(`Debug: POST URL: ${url.toString()}`);
|
|
23889
|
+
console.log(`Debug: Auth scheme: access-token`);
|
|
23890
|
+
console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
|
|
23891
|
+
console.log(`Debug: Request body: ${body}`);
|
|
23892
|
+
}
|
|
23893
|
+
const response = await fetch(url.toString(), {
|
|
23894
|
+
method: "POST",
|
|
23895
|
+
headers,
|
|
23896
|
+
body
|
|
23897
|
+
});
|
|
23898
|
+
if (debug) {
|
|
23899
|
+
console.log(`Debug: Response status: ${response.status}`);
|
|
23900
|
+
console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
|
|
23901
|
+
}
|
|
23902
|
+
const data = await response.text();
|
|
23903
|
+
if (response.ok) {
|
|
23904
|
+
try {
|
|
23905
|
+
return JSON.parse(data);
|
|
23906
|
+
} catch {
|
|
23907
|
+
throw new Error(`Failed to parse update comment response: ${data}`);
|
|
23908
|
+
}
|
|
23909
|
+
} else {
|
|
23910
|
+
throw new Error(formatHttpError("Failed to update issue comment", response.status, data));
|
|
23911
|
+
}
|
|
23912
|
+
}
|
|
23444
23913
|
|
|
23445
23914
|
// lib/util.ts
|
|
23446
23915
|
function maskSecret2(secret) {
|
|
@@ -23474,10 +23943,9 @@ function resolveBaseUrls2(opts, cfg, defaults2 = {}) {
|
|
|
23474
23943
|
|
|
23475
23944
|
// lib/init.ts
|
|
23476
23945
|
import { randomBytes } from "crypto";
|
|
23477
|
-
import { URL as URL2 } from "url";
|
|
23946
|
+
import { URL as URL2, fileURLToPath } from "url";
|
|
23478
23947
|
import * as fs3 from "fs";
|
|
23479
23948
|
import * as path3 from "path";
|
|
23480
|
-
var __dirname = "/builds/postgres-ai/postgres_ai/cli/lib";
|
|
23481
23949
|
var DEFAULT_MONITORING_USER = "postgres_ai_mon";
|
|
23482
23950
|
function sslModeToConfig(mode) {
|
|
23483
23951
|
if (mode.toLowerCase() === "disable")
|
|
@@ -23558,9 +24026,11 @@ async function connectWithSslFallback(ClientClass, adminConn, verbose) {
|
|
|
23558
24026
|
}
|
|
23559
24027
|
}
|
|
23560
24028
|
function sqlDir() {
|
|
24029
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
24030
|
+
const currentDir = path3.dirname(currentFile);
|
|
23561
24031
|
const candidates = [
|
|
23562
|
-
path3.resolve(
|
|
23563
|
-
path3.resolve(
|
|
24032
|
+
path3.resolve(currentDir, "..", "sql"),
|
|
24033
|
+
path3.resolve(currentDir, "..", "..", "sql")
|
|
23564
24034
|
];
|
|
23565
24035
|
for (const candidate of candidates) {
|
|
23566
24036
|
if (fs3.existsSync(candidate)) {
|
|
@@ -24517,6 +24987,7 @@ where
|
|
|
24517
24987
|
quote_ident(pci.relname) as tag_index_name,
|
|
24518
24988
|
quote_ident(pct.relname) as tag_table_name,
|
|
24519
24989
|
coalesce(nullif(quote_ident(pn.nspname), 'public') || '.', '') || quote_ident(pct.relname) as tag_relation_name,
|
|
24990
|
+
pg_get_indexdef(pidx.indexrelid) as index_definition,
|
|
24520
24991
|
pg_relation_size(pidx.indexrelid) index_size_bytes,
|
|
24521
24992
|
((
|
|
24522
24993
|
select count(1)
|
|
@@ -25154,6 +25625,7 @@ async function getInvalidIndexes(client, pgMajorVersion = 16) {
|
|
|
25154
25625
|
relation_name: String(transformed.relation_name || ""),
|
|
25155
25626
|
index_size_bytes: indexSizeBytes,
|
|
25156
25627
|
index_size_pretty: formatBytes(indexSizeBytes),
|
|
25628
|
+
index_definition: String(transformed.index_definition || ""),
|
|
25157
25629
|
supports_fk: toBool(transformed.supports_fk)
|
|
25158
25630
|
};
|
|
25159
25631
|
});
|
|
@@ -25907,7 +26379,7 @@ async function execPromise(command) {
|
|
|
25907
26379
|
childProcess.exec(command, (error2, stdout, stderr) => {
|
|
25908
26380
|
if (error2) {
|
|
25909
26381
|
const err = error2;
|
|
25910
|
-
err.code = error2.code
|
|
26382
|
+
err.code = typeof error2.code === "number" ? error2.code : 1;
|
|
25911
26383
|
reject(err);
|
|
25912
26384
|
} else {
|
|
25913
26385
|
resolve6({ stdout, stderr });
|
|
@@ -25920,7 +26392,7 @@ async function execFilePromise(file, args) {
|
|
|
25920
26392
|
childProcess.execFile(file, args, (error2, stdout, stderr) => {
|
|
25921
26393
|
if (error2) {
|
|
25922
26394
|
const err = error2;
|
|
25923
|
-
err.code = error2.code
|
|
26395
|
+
err.code = typeof error2.code === "number" ? error2.code : 1;
|
|
25924
26396
|
reject(err);
|
|
25925
26397
|
} else {
|
|
25926
26398
|
resolve6({ stdout, stderr });
|
|
@@ -26716,6 +27188,8 @@ program2.command("help", { isDefault: true }).description("show help").action(()
|
|
|
26716
27188
|
});
|
|
26717
27189
|
var mon = program2.command("mon").description("monitoring services management");
|
|
26718
27190
|
mon.command("local-install").description("install local monitoring stack (generate config, start services)").option("--demo", "demo mode with sample database", false).option("--api-key <key>", "Postgres AI API key for automated report uploads").option("--db-url <url>", "PostgreSQL connection URL to monitor").option("--tag <tag>", "Docker image tag to use (e.g., 0.14.0, 0.14.0-dev.33)").option("-y, --yes", "accept all defaults and skip interactive prompts", false).action(async (opts) => {
|
|
27191
|
+
const globalOpts = program2.opts();
|
|
27192
|
+
const apiKey = opts.apiKey || globalOpts.apiKey;
|
|
26719
27193
|
console.log(`
|
|
26720
27194
|
=================================`);
|
|
26721
27195
|
console.log(" PostgresAI monitoring local install");
|
|
@@ -26763,7 +27237,7 @@ mon.command("local-install").description("install local monitoring stack (genera
|
|
|
26763
27237
|
`);
|
|
26764
27238
|
opts.dbUrl = undefined;
|
|
26765
27239
|
}
|
|
26766
|
-
if (opts.demo &&
|
|
27240
|
+
if (opts.demo && apiKey) {
|
|
26767
27241
|
console.error("\u2717 Cannot use --api-key with --demo mode");
|
|
26768
27242
|
console.error("\u2717 Demo mode is for testing only and does not support API key integration");
|
|
26769
27243
|
console.error(`
|
|
@@ -26783,10 +27257,10 @@ Use demo mode without API key: postgres-ai mon local-install --demo`);
|
|
|
26783
27257
|
console.log("Step 1: Postgres AI API Configuration (Optional)");
|
|
26784
27258
|
console.log(`An API key enables automatic upload of PostgreSQL reports to Postgres AI
|
|
26785
27259
|
`);
|
|
26786
|
-
if (
|
|
27260
|
+
if (apiKey) {
|
|
26787
27261
|
console.log("Using API key provided via --api-key parameter");
|
|
26788
|
-
writeConfig({ apiKey
|
|
26789
|
-
fs5.writeFileSync(path5.resolve(projectDir, ".pgwatch-config"), `api_key=${
|
|
27262
|
+
writeConfig({ apiKey });
|
|
27263
|
+
fs5.writeFileSync(path5.resolve(projectDir, ".pgwatch-config"), `api_key=${apiKey}
|
|
26790
27264
|
`, {
|
|
26791
27265
|
encoding: "utf8",
|
|
26792
27266
|
mode: 384
|
|
@@ -27798,7 +28272,7 @@ Grafana credentials:`);
|
|
|
27798
28272
|
console.log(` Password: ${password}`);
|
|
27799
28273
|
console.log("");
|
|
27800
28274
|
});
|
|
27801
|
-
function
|
|
28275
|
+
function interpretEscapes2(str2) {
|
|
27802
28276
|
return str2.replace(/\\\\/g, "\x00").replace(/\\n/g, `
|
|
27803
28277
|
`).replace(/\\t/g, "\t").replace(/\\r/g, "\r").replace(/\\"/g, '"').replace(/\\'/g, "'").replace(/\x00/g, "\\");
|
|
27804
28278
|
}
|
|
@@ -27854,12 +28328,12 @@ issues.command("view <issueId>").description("view issue details and comments").
|
|
|
27854
28328
|
process.exitCode = 1;
|
|
27855
28329
|
}
|
|
27856
28330
|
});
|
|
27857
|
-
issues.command("
|
|
28331
|
+
issues.command("post-comment <issueId> <content>").description("post a new comment to an issue").option("--parent <uuid>", "parent comment id").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (issueId, content, opts) => {
|
|
27858
28332
|
try {
|
|
27859
28333
|
if (opts.debug) {
|
|
27860
28334
|
console.log(`Debug: Original content: ${JSON.stringify(content)}`);
|
|
27861
28335
|
}
|
|
27862
|
-
content =
|
|
28336
|
+
content = interpretEscapes2(content);
|
|
27863
28337
|
if (opts.debug) {
|
|
27864
28338
|
console.log(`Debug: Interpreted content: ${JSON.stringify(content)}`);
|
|
27865
28339
|
}
|
|
@@ -27887,6 +28361,145 @@ issues.command("post_comment <issueId> <content>").description("post a new comme
|
|
|
27887
28361
|
process.exitCode = 1;
|
|
27888
28362
|
}
|
|
27889
28363
|
});
|
|
28364
|
+
issues.command("create <title>").description("create a new issue").option("--org-id <id>", "organization id (defaults to config orgId)", (v) => parseInt(v, 10)).option("--project-id <id>", "project id", (v) => parseInt(v, 10)).option("--description <text>", "issue description (supports \\\\n)").option("--label <label>", "issue label (repeatable)", (value, previous) => {
|
|
28365
|
+
previous.push(value);
|
|
28366
|
+
return previous;
|
|
28367
|
+
}, []).option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (rawTitle, opts) => {
|
|
28368
|
+
try {
|
|
28369
|
+
const rootOpts = program2.opts();
|
|
28370
|
+
const cfg = readConfig();
|
|
28371
|
+
const { apiKey } = getConfig(rootOpts);
|
|
28372
|
+
if (!apiKey) {
|
|
28373
|
+
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
28374
|
+
process.exitCode = 1;
|
|
28375
|
+
return;
|
|
28376
|
+
}
|
|
28377
|
+
const title = interpretEscapes2(String(rawTitle || "").trim());
|
|
28378
|
+
if (!title) {
|
|
28379
|
+
console.error("title is required");
|
|
28380
|
+
process.exitCode = 1;
|
|
28381
|
+
return;
|
|
28382
|
+
}
|
|
28383
|
+
const orgId = typeof opts.orgId === "number" && !Number.isNaN(opts.orgId) ? opts.orgId : cfg.orgId;
|
|
28384
|
+
if (typeof orgId !== "number") {
|
|
28385
|
+
console.error("org_id is required. Either pass --org-id or run 'pgai auth' to store it in config.");
|
|
28386
|
+
process.exitCode = 1;
|
|
28387
|
+
return;
|
|
28388
|
+
}
|
|
28389
|
+
const description = opts.description !== undefined ? interpretEscapes2(String(opts.description)) : undefined;
|
|
28390
|
+
const labels = Array.isArray(opts.label) && opts.label.length > 0 ? opts.label.map(String) : undefined;
|
|
28391
|
+
const projectId = typeof opts.projectId === "number" && !Number.isNaN(opts.projectId) ? opts.projectId : undefined;
|
|
28392
|
+
const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
|
|
28393
|
+
const result = await createIssue2({
|
|
28394
|
+
apiKey,
|
|
28395
|
+
apiBaseUrl,
|
|
28396
|
+
title,
|
|
28397
|
+
orgId,
|
|
28398
|
+
description,
|
|
28399
|
+
projectId,
|
|
28400
|
+
labels,
|
|
28401
|
+
debug: !!opts.debug
|
|
28402
|
+
});
|
|
28403
|
+
printResult(result, opts.json);
|
|
28404
|
+
} catch (err) {
|
|
28405
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
28406
|
+
console.error(message);
|
|
28407
|
+
process.exitCode = 1;
|
|
28408
|
+
}
|
|
28409
|
+
});
|
|
28410
|
+
issues.command("update <issueId>").description("update an existing issue (title/description/status/labels)").option("--title <text>", "new title (supports \\\\n)").option("--description <text>", "new description (supports \\\\n)").option("--status <value>", "status: open|closed|0|1").option("--label <label>", "set labels (repeatable). If provided, replaces existing labels.", (value, previous) => {
|
|
28411
|
+
previous.push(value);
|
|
28412
|
+
return previous;
|
|
28413
|
+
}, []).option("--clear-labels", "set labels to an empty list").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (issueId, opts) => {
|
|
28414
|
+
try {
|
|
28415
|
+
const rootOpts = program2.opts();
|
|
28416
|
+
const cfg = readConfig();
|
|
28417
|
+
const { apiKey } = getConfig(rootOpts);
|
|
28418
|
+
if (!apiKey) {
|
|
28419
|
+
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
28420
|
+
process.exitCode = 1;
|
|
28421
|
+
return;
|
|
28422
|
+
}
|
|
28423
|
+
const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
|
|
28424
|
+
const title = opts.title !== undefined ? interpretEscapes2(String(opts.title)) : undefined;
|
|
28425
|
+
const description = opts.description !== undefined ? interpretEscapes2(String(opts.description)) : undefined;
|
|
28426
|
+
let status = undefined;
|
|
28427
|
+
if (opts.status !== undefined) {
|
|
28428
|
+
const raw = String(opts.status).trim().toLowerCase();
|
|
28429
|
+
if (raw === "open")
|
|
28430
|
+
status = 0;
|
|
28431
|
+
else if (raw === "closed")
|
|
28432
|
+
status = 1;
|
|
28433
|
+
else {
|
|
28434
|
+
const n = Number(raw);
|
|
28435
|
+
if (!Number.isFinite(n)) {
|
|
28436
|
+
console.error("status must be open|closed|0|1");
|
|
28437
|
+
process.exitCode = 1;
|
|
28438
|
+
return;
|
|
28439
|
+
}
|
|
28440
|
+
status = n;
|
|
28441
|
+
}
|
|
28442
|
+
if (status !== 0 && status !== 1) {
|
|
28443
|
+
console.error("status must be 0 (open) or 1 (closed)");
|
|
28444
|
+
process.exitCode = 1;
|
|
28445
|
+
return;
|
|
28446
|
+
}
|
|
28447
|
+
}
|
|
28448
|
+
let labels = undefined;
|
|
28449
|
+
if (opts.clearLabels) {
|
|
28450
|
+
labels = [];
|
|
28451
|
+
} else if (Array.isArray(opts.label) && opts.label.length > 0) {
|
|
28452
|
+
labels = opts.label.map(String);
|
|
28453
|
+
}
|
|
28454
|
+
const result = await updateIssue2({
|
|
28455
|
+
apiKey,
|
|
28456
|
+
apiBaseUrl,
|
|
28457
|
+
issueId,
|
|
28458
|
+
title,
|
|
28459
|
+
description,
|
|
28460
|
+
status,
|
|
28461
|
+
labels,
|
|
28462
|
+
debug: !!opts.debug
|
|
28463
|
+
});
|
|
28464
|
+
printResult(result, opts.json);
|
|
28465
|
+
} catch (err) {
|
|
28466
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
28467
|
+
console.error(message);
|
|
28468
|
+
process.exitCode = 1;
|
|
28469
|
+
}
|
|
28470
|
+
});
|
|
28471
|
+
issues.command("update-comment <commentId> <content>").description("update an existing issue comment").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (commentId, content, opts) => {
|
|
28472
|
+
try {
|
|
28473
|
+
if (opts.debug) {
|
|
28474
|
+
console.log(`Debug: Original content: ${JSON.stringify(content)}`);
|
|
28475
|
+
}
|
|
28476
|
+
content = interpretEscapes2(content);
|
|
28477
|
+
if (opts.debug) {
|
|
28478
|
+
console.log(`Debug: Interpreted content: ${JSON.stringify(content)}`);
|
|
28479
|
+
}
|
|
28480
|
+
const rootOpts = program2.opts();
|
|
28481
|
+
const cfg = readConfig();
|
|
28482
|
+
const { apiKey } = getConfig(rootOpts);
|
|
28483
|
+
if (!apiKey) {
|
|
28484
|
+
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
28485
|
+
process.exitCode = 1;
|
|
28486
|
+
return;
|
|
28487
|
+
}
|
|
28488
|
+
const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
|
|
28489
|
+
const result = await updateIssueComment2({
|
|
28490
|
+
apiKey,
|
|
28491
|
+
apiBaseUrl,
|
|
28492
|
+
commentId,
|
|
28493
|
+
content,
|
|
28494
|
+
debug: !!opts.debug
|
|
28495
|
+
});
|
|
28496
|
+
printResult(result, opts.json);
|
|
28497
|
+
} catch (err) {
|
|
28498
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
28499
|
+
console.error(message);
|
|
28500
|
+
process.exitCode = 1;
|
|
28501
|
+
}
|
|
28502
|
+
});
|
|
27890
28503
|
var mcp = program2.command("mcp").description("MCP server integration");
|
|
27891
28504
|
mcp.command("start").description("start MCP stdio server").option("--debug", "enable debug output").action(async (opts) => {
|
|
27892
28505
|
const rootOpts = program2.opts();
|