postgresai 0.14.0-beta.4 → 0.14.0-beta.6

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.
@@ -13064,7 +13064,7 @@ var {
13064
13064
  // package.json
13065
13065
  var package_default = {
13066
13066
  name: "postgresai",
13067
- version: "0.14.0-beta.4",
13067
+ version: "0.14.0-beta.6",
13068
13068
  description: "postgres_ai CLI",
13069
13069
  license: "Apache-2.0",
13070
13070
  private: false,
@@ -13090,12 +13090,14 @@ 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",
13097
13097
  dev: "bun run embed-metrics && bun --watch ./bin/postgres-ai.ts",
13098
13098
  test: "bun run embed-metrics && bun test",
13099
+ "test:fast": "bun run embed-metrics && bun test --coverage=false",
13100
+ "test:coverage": "bun run embed-metrics && bun test --coverage && echo 'Coverage report: cli/coverage/lcov-report/index.html'",
13099
13101
  typecheck: "bun run embed-metrics && bunx tsc --noEmit"
13100
13102
  },
13101
13103
  dependencies: {
@@ -13143,10 +13145,10 @@ function readConfig() {
13143
13145
  try {
13144
13146
  const content = fs.readFileSync(userConfigPath, "utf8");
13145
13147
  const parsed = JSON.parse(content);
13146
- config.apiKey = parsed.apiKey || null;
13147
- config.baseUrl = parsed.baseUrl || null;
13148
- config.orgId = parsed.orgId || null;
13149
- config.defaultProject = parsed.defaultProject || null;
13148
+ config.apiKey = parsed.apiKey ?? null;
13149
+ config.baseUrl = parsed.baseUrl ?? null;
13150
+ config.orgId = parsed.orgId ?? null;
13151
+ config.defaultProject = parsed.defaultProject ?? null;
13150
13152
  return config;
13151
13153
  } catch (err) {
13152
13154
  const message = err instanceof Error ? err.message : String(err);
@@ -15885,7 +15887,7 @@ var Result = import_lib.default.Result;
15885
15887
  var TypeOverrides = import_lib.default.TypeOverrides;
15886
15888
  var defaults = import_lib.default.defaults;
15887
15889
  // package.json
15888
- var version = "0.14.0-beta.4";
15890
+ var version = "0.14.0-beta.6";
15889
15891
  var package_default2 = {
15890
15892
  name: "postgresai",
15891
15893
  version,
@@ -15914,12 +15916,14 @@ var package_default2 = {
15914
15916
  },
15915
15917
  scripts: {
15916
15918
  "embed-metrics": "bun run scripts/embed-metrics.ts",
15917
- 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`,
15918
15920
  prepublishOnly: "npm run build",
15919
15921
  start: "bun ./bin/postgres-ai.ts --help",
15920
15922
  "start:node": "node ./dist/bin/postgres-ai.js --help",
15921
15923
  dev: "bun run embed-metrics && bun --watch ./bin/postgres-ai.ts",
15922
15924
  test: "bun run embed-metrics && bun test",
15925
+ "test:fast": "bun run embed-metrics && bun test --coverage=false",
15926
+ "test:coverage": "bun run embed-metrics && bun test --coverage && echo 'Coverage report: cli/coverage/lcov-report/index.html'",
15923
15927
  typecheck: "bun run embed-metrics && bunx tsc --noEmit"
15924
15928
  },
15925
15929
  dependencies: {
@@ -15967,10 +15971,10 @@ function readConfig2() {
15967
15971
  try {
15968
15972
  const content = fs2.readFileSync(userConfigPath, "utf8");
15969
15973
  const parsed = JSON.parse(content);
15970
- config.apiKey = parsed.apiKey || null;
15971
- config.baseUrl = parsed.baseUrl || null;
15972
- config.orgId = parsed.orgId || null;
15973
- config.defaultProject = parsed.defaultProject || null;
15974
+ config.apiKey = parsed.apiKey ?? null;
15975
+ config.baseUrl = parsed.baseUrl ?? null;
15976
+ config.orgId = parsed.orgId ?? null;
15977
+ config.defaultProject = parsed.defaultProject ?? null;
15974
15978
  return config;
15975
15979
  } catch (err) {
15976
15980
  const message = err instanceof Error ? err.message : String(err);
@@ -16085,7 +16089,8 @@ async function fetchIssues(params) {
16085
16089
  const headers = {
16086
16090
  "access-token": apiKey,
16087
16091
  Prefer: "return=representation",
16088
- "Content-Type": "application/json"
16092
+ "Content-Type": "application/json",
16093
+ Connection: "close"
16089
16094
  };
16090
16095
  if (debug) {
16091
16096
  const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
@@ -16126,7 +16131,8 @@ async function fetchIssueComments(params) {
16126
16131
  const headers = {
16127
16132
  "access-token": apiKey,
16128
16133
  Prefer: "return=representation",
16129
- "Content-Type": "application/json"
16134
+ "Content-Type": "application/json",
16135
+ Connection: "close"
16130
16136
  };
16131
16137
  if (debug) {
16132
16138
  const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
@@ -16170,7 +16176,8 @@ async function fetchIssue(params) {
16170
16176
  const headers = {
16171
16177
  "access-token": apiKey,
16172
16178
  Prefer: "return=representation",
16173
- "Content-Type": "application/json"
16179
+ "Content-Type": "application/json",
16180
+ Connection: "close"
16174
16181
  };
16175
16182
  if (debug) {
16176
16183
  const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
@@ -16203,6 +16210,67 @@ async function fetchIssue(params) {
16203
16210
  throw new Error(formatHttpError("Failed to fetch issue", response.status, data));
16204
16211
  }
16205
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
+ }
16206
16274
  async function createIssueComment(params) {
16207
16275
  const { apiKey, apiBaseUrl, issueId, content, parentCommentId, debug } = params;
16208
16276
  if (!apiKey) {
@@ -16227,7 +16295,8 @@ async function createIssueComment(params) {
16227
16295
  const headers = {
16228
16296
  "access-token": apiKey,
16229
16297
  Prefer: "return=representation",
16230
- "Content-Type": "application/json"
16298
+ "Content-Type": "application/json",
16299
+ Connection: "close"
16231
16300
  };
16232
16301
  if (debug) {
16233
16302
  const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
@@ -16257,6 +16326,121 @@ async function createIssueComment(params) {
16257
16326
  throw new Error(formatHttpError("Failed to create issue comment", response.status, data));
16258
16327
  }
16259
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
+ }
16260
16444
 
16261
16445
  // node_modules/zod/v4/core/core.js
16262
16446
  var NEVER = Object.freeze({
@@ -23144,10 +23328,116 @@ class StdioServerTransport {
23144
23328
  }
23145
23329
 
23146
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
+ }
23147
23439
  async function startMcpServer(rootOpts, extra) {
23148
23440
  const server = new Server({ name: "postgresai-mcp", version: package_default2.version }, { capabilities: { tools: {} } });
23149
- const interpretEscapes = (str2) => (str2 || "").replace(/\\n/g, `
23150
- `).replace(/\\t/g, "\t").replace(/\\r/g, "\r").replace(/\\"/g, '"').replace(/\\'/g, "'");
23151
23441
  server.setRequestHandler(ListToolsRequestSchema, async () => {
23152
23442
  return {
23153
23443
  tools: [
@@ -23189,65 +23479,68 @@ async function startMcpServer(rootOpts, extra) {
23189
23479
  required: ["issue_id", "content"],
23190
23480
  additionalProperties: false
23191
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
+ }
23192
23538
  }
23193
23539
  ]
23194
23540
  };
23195
23541
  });
23196
23542
  server.setRequestHandler(CallToolRequestSchema, async (req) => {
23197
- const toolName = req.params.name;
23198
- const args = req.params.arguments || {};
23199
- const cfg = readConfig2();
23200
- const apiKey = (rootOpts?.apiKey || process.env.PGAI_API_KEY || cfg.apiKey || "").toString();
23201
- const { apiBaseUrl } = resolveBaseUrls(rootOpts, cfg);
23202
- const debug = Boolean(args.debug ?? extra?.debug);
23203
- if (!apiKey) {
23204
- return {
23205
- content: [
23206
- {
23207
- type: "text",
23208
- text: "API key is required. Run 'pgai auth' or set PGAI_API_KEY."
23209
- }
23210
- ],
23211
- isError: true
23212
- };
23213
- }
23214
- try {
23215
- if (toolName === "list_issues") {
23216
- const issues = await fetchIssues({ apiKey, apiBaseUrl, debug });
23217
- return { content: [{ type: "text", text: JSON.stringify(issues, null, 2) }] };
23218
- }
23219
- if (toolName === "view_issue") {
23220
- const issueId = String(args.issue_id || "").trim();
23221
- if (!issueId) {
23222
- return { content: [{ type: "text", text: "issue_id is required" }], isError: true };
23223
- }
23224
- const issue2 = await fetchIssue({ apiKey, apiBaseUrl, issueId, debug });
23225
- if (!issue2) {
23226
- return { content: [{ type: "text", text: "Issue not found" }], isError: true };
23227
- }
23228
- const comments = await fetchIssueComments({ apiKey, apiBaseUrl, issueId, debug });
23229
- const combined = { issue: issue2, comments };
23230
- return { content: [{ type: "text", text: JSON.stringify(combined, null, 2) }] };
23231
- }
23232
- if (toolName === "post_issue_comment") {
23233
- const issueId = String(args.issue_id || "").trim();
23234
- const rawContent = String(args.content || "");
23235
- const parentCommentId = args.parent_comment_id ? String(args.parent_comment_id) : undefined;
23236
- if (!issueId) {
23237
- return { content: [{ type: "text", text: "issue_id is required" }], isError: true };
23238
- }
23239
- if (!rawContent) {
23240
- return { content: [{ type: "text", text: "content is required" }], isError: true };
23241
- }
23242
- const content = interpretEscapes(rawContent);
23243
- const result = await createIssueComment({ apiKey, apiBaseUrl, issueId, content, parentCommentId, debug });
23244
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
23245
- }
23246
- throw new Error(`Unknown tool: ${toolName}`);
23247
- } catch (err) {
23248
- const message = err instanceof Error ? err.message : String(err);
23249
- return { content: [{ type: "text", text: message }], isError: true };
23250
- }
23543
+ return handleToolCall(req, rootOpts, extra);
23251
23544
  });
23252
23545
  const transport = new StdioServerTransport;
23253
23546
  await server.connect(transport);
@@ -23265,7 +23558,8 @@ async function fetchIssues2(params) {
23265
23558
  const headers = {
23266
23559
  "access-token": apiKey,
23267
23560
  Prefer: "return=representation",
23268
- "Content-Type": "application/json"
23561
+ "Content-Type": "application/json",
23562
+ Connection: "close"
23269
23563
  };
23270
23564
  if (debug) {
23271
23565
  const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
@@ -23306,7 +23600,8 @@ async function fetchIssueComments2(params) {
23306
23600
  const headers = {
23307
23601
  "access-token": apiKey,
23308
23602
  Prefer: "return=representation",
23309
- "Content-Type": "application/json"
23603
+ "Content-Type": "application/json",
23604
+ Connection: "close"
23310
23605
  };
23311
23606
  if (debug) {
23312
23607
  const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
@@ -23350,7 +23645,8 @@ async function fetchIssue2(params) {
23350
23645
  const headers = {
23351
23646
  "access-token": apiKey,
23352
23647
  Prefer: "return=representation",
23353
- "Content-Type": "application/json"
23648
+ "Content-Type": "application/json",
23649
+ Connection: "close"
23354
23650
  };
23355
23651
  if (debug) {
23356
23652
  const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
@@ -23383,6 +23679,67 @@ async function fetchIssue2(params) {
23383
23679
  throw new Error(formatHttpError("Failed to fetch issue", response.status, data));
23384
23680
  }
23385
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
+ }
23386
23743
  async function createIssueComment2(params) {
23387
23744
  const { apiKey, apiBaseUrl, issueId, content, parentCommentId, debug } = params;
23388
23745
  if (!apiKey) {
@@ -23407,7 +23764,8 @@ async function createIssueComment2(params) {
23407
23764
  const headers = {
23408
23765
  "access-token": apiKey,
23409
23766
  Prefer: "return=representation",
23410
- "Content-Type": "application/json"
23767
+ "Content-Type": "application/json",
23768
+ Connection: "close"
23411
23769
  };
23412
23770
  if (debug) {
23413
23771
  const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
@@ -23437,6 +23795,121 @@ async function createIssueComment2(params) {
23437
23795
  throw new Error(formatHttpError("Failed to create issue comment", response.status, data));
23438
23796
  }
23439
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
+ }
23440
23913
 
23441
23914
  // lib/util.ts
23442
23915
  function maskSecret2(secret) {
@@ -23470,10 +23943,9 @@ function resolveBaseUrls2(opts, cfg, defaults2 = {}) {
23470
23943
 
23471
23944
  // lib/init.ts
23472
23945
  import { randomBytes } from "crypto";
23473
- import { URL as URL2 } from "url";
23946
+ import { URL as URL2, fileURLToPath } from "url";
23474
23947
  import * as fs3 from "fs";
23475
23948
  import * as path3 from "path";
23476
- var __dirname = "/builds/postgres-ai/postgres_ai/cli/lib";
23477
23949
  var DEFAULT_MONITORING_USER = "postgres_ai_mon";
23478
23950
  function sslModeToConfig(mode) {
23479
23951
  if (mode.toLowerCase() === "disable")
@@ -23554,9 +24026,11 @@ async function connectWithSslFallback(ClientClass, adminConn, verbose) {
23554
24026
  }
23555
24027
  }
23556
24028
  function sqlDir() {
24029
+ const currentFile = fileURLToPath(import.meta.url);
24030
+ const currentDir = path3.dirname(currentFile);
23557
24031
  const candidates = [
23558
- path3.resolve(__dirname, "..", "sql"),
23559
- path3.resolve(__dirname, "..", "..", "sql")
24032
+ path3.resolve(currentDir, "..", "sql"),
24033
+ path3.resolve(currentDir, "..", "..", "sql")
23560
24034
  ];
23561
24035
  for (const candidate of candidates) {
23562
24036
  if (fs3.existsSync(candidate)) {
@@ -24513,6 +24987,7 @@ where
24513
24987
  quote_ident(pci.relname) as tag_index_name,
24514
24988
  quote_ident(pct.relname) as tag_table_name,
24515
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,
24516
24991
  pg_relation_size(pidx.indexrelid) index_size_bytes,
24517
24992
  ((
24518
24993
  select count(1)
@@ -25150,6 +25625,7 @@ async function getInvalidIndexes(client, pgMajorVersion = 16) {
25150
25625
  relation_name: String(transformed.relation_name || ""),
25151
25626
  index_size_bytes: indexSizeBytes,
25152
25627
  index_size_pretty: formatBytes(indexSizeBytes),
25628
+ index_definition: String(transformed.index_definition || ""),
25153
25629
  supports_fk: toBool(transformed.supports_fk)
25154
25630
  };
25155
25631
  });
@@ -25903,7 +26379,7 @@ async function execPromise(command) {
25903
26379
  childProcess.exec(command, (error2, stdout, stderr) => {
25904
26380
  if (error2) {
25905
26381
  const err = error2;
25906
- err.code = error2.code ?? 1;
26382
+ err.code = typeof error2.code === "number" ? error2.code : 1;
25907
26383
  reject(err);
25908
26384
  } else {
25909
26385
  resolve6({ stdout, stderr });
@@ -25916,7 +26392,7 @@ async function execFilePromise(file, args) {
25916
26392
  childProcess.execFile(file, args, (error2, stdout, stderr) => {
25917
26393
  if (error2) {
25918
26394
  const err = error2;
25919
- err.code = error2.code ?? 1;
26395
+ err.code = typeof error2.code === "number" ? error2.code : 1;
25920
26396
  reject(err);
25921
26397
  } else {
25922
26398
  resolve6({ stdout, stderr });
@@ -26712,6 +27188,8 @@ program2.command("help", { isDefault: true }).description("show help").action(()
26712
27188
  });
26713
27189
  var mon = program2.command("mon").description("monitoring services management");
26714
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;
26715
27193
  console.log(`
26716
27194
  =================================`);
26717
27195
  console.log(" PostgresAI monitoring local install");
@@ -26723,14 +27201,28 @@ mon.command("local-install").description("install local monitoring stack (genera
26723
27201
  console.log(`Project directory: ${projectDir}
26724
27202
  `);
26725
27203
  const envFile = path5.resolve(projectDir, ".env");
26726
- const imageTag = opts.tag || package_default.version;
26727
- const envLines = [`PGAI_TAG=${imageTag}`];
27204
+ let existingTag = null;
27205
+ let existingRegistry = null;
27206
+ let existingPassword = null;
26728
27207
  if (fs5.existsSync(envFile)) {
26729
27208
  const existingEnv = fs5.readFileSync(envFile, "utf8");
27209
+ const tagMatch = existingEnv.match(/^PGAI_TAG=(.+)$/m);
27210
+ if (tagMatch)
27211
+ existingTag = tagMatch[1].trim();
27212
+ const registryMatch = existingEnv.match(/^PGAI_REGISTRY=(.+)$/m);
27213
+ if (registryMatch)
27214
+ existingRegistry = registryMatch[1].trim();
26730
27215
  const pwdMatch = existingEnv.match(/^GF_SECURITY_ADMIN_PASSWORD=(.+)$/m);
26731
- if (pwdMatch) {
26732
- envLines.push(`GF_SECURITY_ADMIN_PASSWORD=${pwdMatch[1]}`);
26733
- }
27216
+ if (pwdMatch)
27217
+ existingPassword = pwdMatch[1].trim();
27218
+ }
27219
+ const imageTag = opts.tag || existingTag || package_default.version;
27220
+ const envLines = [`PGAI_TAG=${imageTag}`];
27221
+ if (existingRegistry) {
27222
+ envLines.push(`PGAI_REGISTRY=${existingRegistry}`);
27223
+ }
27224
+ if (existingPassword) {
27225
+ envLines.push(`GF_SECURITY_ADMIN_PASSWORD=${existingPassword}`);
26734
27226
  }
26735
27227
  fs5.writeFileSync(envFile, envLines.join(`
26736
27228
  `) + `
@@ -26745,7 +27237,7 @@ mon.command("local-install").description("install local monitoring stack (genera
26745
27237
  `);
26746
27238
  opts.dbUrl = undefined;
26747
27239
  }
26748
- if (opts.demo && opts.apiKey) {
27240
+ if (opts.demo && apiKey) {
26749
27241
  console.error("\u2717 Cannot use --api-key with --demo mode");
26750
27242
  console.error("\u2717 Demo mode is for testing only and does not support API key integration");
26751
27243
  console.error(`
@@ -26765,10 +27257,10 @@ Use demo mode without API key: postgres-ai mon local-install --demo`);
26765
27257
  console.log("Step 1: Postgres AI API Configuration (Optional)");
26766
27258
  console.log(`An API key enables automatic upload of PostgreSQL reports to Postgres AI
26767
27259
  `);
26768
- if (opts.apiKey) {
27260
+ if (apiKey) {
26769
27261
  console.log("Using API key provided via --api-key parameter");
26770
- writeConfig({ apiKey: opts.apiKey });
26771
- fs5.writeFileSync(path5.resolve(projectDir, ".pgwatch-config"), `api_key=${opts.apiKey}
27262
+ writeConfig({ apiKey });
27263
+ fs5.writeFileSync(path5.resolve(projectDir, ".pgwatch-config"), `api_key=${apiKey}
26772
27264
  `, {
26773
27265
  encoding: "utf8",
26774
27266
  mode: 384
@@ -27535,7 +28027,7 @@ Please verify the --api-base-url parameter.`);
27535
28027
  process.exit(1);
27536
28028
  return;
27537
28029
  }
27538
- const authUrl = `${uiBaseUrl}/cli/auth?state=${encodeURIComponent(params.state)}&code_challenge=${encodeURIComponent(params.codeChallenge)}&code_challenge_method=S256&redirect_uri=${encodeURIComponent(redirectUri)}`;
28030
+ const authUrl = `${uiBaseUrl}/cli/auth?state=${encodeURIComponent(params.state)}&code_challenge=${encodeURIComponent(params.codeChallenge)}&code_challenge_method=S256&redirect_uri=${encodeURIComponent(redirectUri)}&api_url=${encodeURIComponent(apiBaseUrl)}`;
27539
28031
  if (opts.debug) {
27540
28032
  console.log(`Debug: Auth URL: ${authUrl}`);
27541
28033
  }
@@ -27780,7 +28272,7 @@ Grafana credentials:`);
27780
28272
  console.log(` Password: ${password}`);
27781
28273
  console.log("");
27782
28274
  });
27783
- function interpretEscapes(str2) {
28275
+ function interpretEscapes2(str2) {
27784
28276
  return str2.replace(/\\\\/g, "\x00").replace(/\\n/g, `
27785
28277
  `).replace(/\\t/g, "\t").replace(/\\r/g, "\r").replace(/\\"/g, '"').replace(/\\'/g, "'").replace(/\x00/g, "\\");
27786
28278
  }
@@ -27836,12 +28328,12 @@ issues.command("view <issueId>").description("view issue details and comments").
27836
28328
  process.exitCode = 1;
27837
28329
  }
27838
28330
  });
27839
- 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) => {
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) => {
27840
28332
  try {
27841
28333
  if (opts.debug) {
27842
28334
  console.log(`Debug: Original content: ${JSON.stringify(content)}`);
27843
28335
  }
27844
- content = interpretEscapes(content);
28336
+ content = interpretEscapes2(content);
27845
28337
  if (opts.debug) {
27846
28338
  console.log(`Debug: Interpreted content: ${JSON.stringify(content)}`);
27847
28339
  }
@@ -27869,6 +28361,145 @@ issues.command("post_comment <issueId> <content>").description("post a new comme
27869
28361
  process.exitCode = 1;
27870
28362
  }
27871
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
+ });
27872
28503
  var mcp = program2.command("mcp").description("MCP server integration");
27873
28504
  mcp.command("start").description("start MCP stdio server").option("--debug", "enable debug output").action(async (opts) => {
27874
28505
  const rootOpts = program2.opts();