postgresai 0.14.0-dev.71 → 0.14.0-dev.72
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 +376 -93
- package/dist/bin/postgres-ai.js +1063 -139
- package/lib/issues.ts +453 -7
- package/lib/mcp-server.ts +180 -3
- package/lib/metrics-embedded.ts +1 -1
- package/lib/supabase.ts +52 -0
- package/package.json +1 -1
- package/test/init.integration.test.ts +78 -70
- package/test/issues.cli.test.ts +224 -0
- package/test/mcp-server.test.ts +551 -12
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.72",
|
|
13068
13068
|
description: "postgres_ai CLI",
|
|
13069
13069
|
license: "Apache-2.0",
|
|
13070
13070
|
private: false,
|
|
@@ -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.72";
|
|
15891
15891
|
var package_default2 = {
|
|
15892
15892
|
name: "postgresai",
|
|
15893
15893
|
version,
|
|
@@ -16079,13 +16079,24 @@ function resolveBaseUrls(opts, cfg, defaults2 = {}) {
|
|
|
16079
16079
|
|
|
16080
16080
|
// lib/issues.ts
|
|
16081
16081
|
async function fetchIssues(params) {
|
|
16082
|
-
const { apiKey, apiBaseUrl, debug } = params;
|
|
16082
|
+
const { apiKey, apiBaseUrl, orgId, status, limit = 20, offset = 0, debug } = params;
|
|
16083
16083
|
if (!apiKey) {
|
|
16084
16084
|
throw new Error("API key is required");
|
|
16085
16085
|
}
|
|
16086
16086
|
const base = normalizeBaseUrl(apiBaseUrl);
|
|
16087
16087
|
const url = new URL(`${base}/issues`);
|
|
16088
16088
|
url.searchParams.set("select", "id,title,status,created_at");
|
|
16089
|
+
url.searchParams.set("order", "id.desc");
|
|
16090
|
+
url.searchParams.set("limit", String(limit));
|
|
16091
|
+
url.searchParams.set("offset", String(offset));
|
|
16092
|
+
if (typeof orgId === "number") {
|
|
16093
|
+
url.searchParams.set("org_id", `eq.${orgId}`);
|
|
16094
|
+
}
|
|
16095
|
+
if (status === "open") {
|
|
16096
|
+
url.searchParams.set("status", "eq.0");
|
|
16097
|
+
} else if (status === "closed") {
|
|
16098
|
+
url.searchParams.set("status", "eq.1");
|
|
16099
|
+
}
|
|
16089
16100
|
const headers = {
|
|
16090
16101
|
"access-token": apiKey,
|
|
16091
16102
|
Prefer: "return=representation",
|
|
@@ -16170,7 +16181,7 @@ async function fetchIssue(params) {
|
|
|
16170
16181
|
}
|
|
16171
16182
|
const base = normalizeBaseUrl(apiBaseUrl);
|
|
16172
16183
|
const url = new URL(`${base}/issues`);
|
|
16173
|
-
url.searchParams.set("select", "id,title,description,status,created_at,author_display_name");
|
|
16184
|
+
url.searchParams.set("select", "id,title,description,status,created_at,author_display_name,action_items");
|
|
16174
16185
|
url.searchParams.set("id", `eq.${issueId}`);
|
|
16175
16186
|
url.searchParams.set("limit", "1");
|
|
16176
16187
|
const headers = {
|
|
@@ -16198,11 +16209,20 @@ async function fetchIssue(params) {
|
|
|
16198
16209
|
if (response.ok) {
|
|
16199
16210
|
try {
|
|
16200
16211
|
const parsed = JSON.parse(data);
|
|
16201
|
-
|
|
16202
|
-
|
|
16203
|
-
|
|
16204
|
-
return parsed;
|
|
16212
|
+
const rawIssue = Array.isArray(parsed) ? parsed[0] : parsed;
|
|
16213
|
+
if (!rawIssue) {
|
|
16214
|
+
return null;
|
|
16205
16215
|
}
|
|
16216
|
+
const actionItemsSummary = Array.isArray(rawIssue.action_items) ? rawIssue.action_items.map((item) => ({ id: item.id, title: item.title })) : [];
|
|
16217
|
+
return {
|
|
16218
|
+
id: rawIssue.id,
|
|
16219
|
+
title: rawIssue.title,
|
|
16220
|
+
description: rawIssue.description,
|
|
16221
|
+
status: rawIssue.status,
|
|
16222
|
+
created_at: rawIssue.created_at,
|
|
16223
|
+
author_display_name: rawIssue.author_display_name,
|
|
16224
|
+
action_items: actionItemsSummary
|
|
16225
|
+
};
|
|
16206
16226
|
} catch {
|
|
16207
16227
|
throw new Error(`Failed to parse issue response: ${data}`);
|
|
16208
16228
|
}
|
|
@@ -16441,6 +16461,243 @@ async function updateIssueComment(params) {
|
|
|
16441
16461
|
throw new Error(formatHttpError("Failed to update issue comment", response.status, data));
|
|
16442
16462
|
}
|
|
16443
16463
|
}
|
|
16464
|
+
async function fetchActionItem(params) {
|
|
16465
|
+
const { apiKey, apiBaseUrl, actionItemIds, debug } = params;
|
|
16466
|
+
if (!apiKey) {
|
|
16467
|
+
throw new Error("API key is required");
|
|
16468
|
+
}
|
|
16469
|
+
const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
16470
|
+
const rawIds = Array.isArray(actionItemIds) ? actionItemIds : [actionItemIds];
|
|
16471
|
+
const validIds = rawIds.filter((id) => id != null && typeof id === "string").map((id) => id.trim()).filter((id) => id.length > 0 && uuidPattern.test(id));
|
|
16472
|
+
if (validIds.length === 0) {
|
|
16473
|
+
throw new Error("actionItemId is required and must be a valid UUID");
|
|
16474
|
+
}
|
|
16475
|
+
const base = normalizeBaseUrl(apiBaseUrl);
|
|
16476
|
+
const url = new URL(`${base}/issue_action_items`);
|
|
16477
|
+
if (validIds.length === 1) {
|
|
16478
|
+
url.searchParams.set("id", `eq.${validIds[0]}`);
|
|
16479
|
+
} else {
|
|
16480
|
+
url.searchParams.set("id", `in.(${validIds.join(",")})`);
|
|
16481
|
+
}
|
|
16482
|
+
const headers = {
|
|
16483
|
+
"access-token": apiKey,
|
|
16484
|
+
Prefer: "return=representation",
|
|
16485
|
+
"Content-Type": "application/json",
|
|
16486
|
+
Connection: "close"
|
|
16487
|
+
};
|
|
16488
|
+
if (debug) {
|
|
16489
|
+
const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
|
|
16490
|
+
console.log(`Debug: Resolved API base URL: ${base}`);
|
|
16491
|
+
console.log(`Debug: GET URL: ${url.toString()}`);
|
|
16492
|
+
console.log(`Debug: Auth scheme: access-token`);
|
|
16493
|
+
console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
|
|
16494
|
+
}
|
|
16495
|
+
const response = await fetch(url.toString(), {
|
|
16496
|
+
method: "GET",
|
|
16497
|
+
headers
|
|
16498
|
+
});
|
|
16499
|
+
if (debug) {
|
|
16500
|
+
console.log(`Debug: Response status: ${response.status}`);
|
|
16501
|
+
console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
|
|
16502
|
+
}
|
|
16503
|
+
const data = await response.text();
|
|
16504
|
+
if (response.ok) {
|
|
16505
|
+
try {
|
|
16506
|
+
const parsed = JSON.parse(data);
|
|
16507
|
+
if (Array.isArray(parsed)) {
|
|
16508
|
+
return parsed;
|
|
16509
|
+
}
|
|
16510
|
+
return parsed ? [parsed] : [];
|
|
16511
|
+
} catch {
|
|
16512
|
+
throw new Error(`Failed to parse action item response: ${data}`);
|
|
16513
|
+
}
|
|
16514
|
+
} else {
|
|
16515
|
+
throw new Error(formatHttpError("Failed to fetch action item", response.status, data));
|
|
16516
|
+
}
|
|
16517
|
+
}
|
|
16518
|
+
async function fetchActionItems(params) {
|
|
16519
|
+
const { apiKey, apiBaseUrl, issueId, debug } = params;
|
|
16520
|
+
if (!apiKey) {
|
|
16521
|
+
throw new Error("API key is required");
|
|
16522
|
+
}
|
|
16523
|
+
if (!issueId) {
|
|
16524
|
+
throw new Error("issueId is required");
|
|
16525
|
+
}
|
|
16526
|
+
const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
16527
|
+
if (!uuidPattern.test(issueId.trim())) {
|
|
16528
|
+
throw new Error("issueId must be a valid UUID");
|
|
16529
|
+
}
|
|
16530
|
+
const base = normalizeBaseUrl(apiBaseUrl);
|
|
16531
|
+
const url = new URL(`${base}/issue_action_items`);
|
|
16532
|
+
url.searchParams.set("issue_id", `eq.${issueId.trim()}`);
|
|
16533
|
+
const headers = {
|
|
16534
|
+
"access-token": apiKey,
|
|
16535
|
+
Prefer: "return=representation",
|
|
16536
|
+
"Content-Type": "application/json",
|
|
16537
|
+
Connection: "close"
|
|
16538
|
+
};
|
|
16539
|
+
if (debug) {
|
|
16540
|
+
const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
|
|
16541
|
+
console.log(`Debug: Resolved API base URL: ${base}`);
|
|
16542
|
+
console.log(`Debug: GET URL: ${url.toString()}`);
|
|
16543
|
+
console.log(`Debug: Auth scheme: access-token`);
|
|
16544
|
+
console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
|
|
16545
|
+
}
|
|
16546
|
+
const response = await fetch(url.toString(), {
|
|
16547
|
+
method: "GET",
|
|
16548
|
+
headers
|
|
16549
|
+
});
|
|
16550
|
+
if (debug) {
|
|
16551
|
+
console.log(`Debug: Response status: ${response.status}`);
|
|
16552
|
+
console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
|
|
16553
|
+
}
|
|
16554
|
+
const data = await response.text();
|
|
16555
|
+
if (response.ok) {
|
|
16556
|
+
try {
|
|
16557
|
+
return JSON.parse(data);
|
|
16558
|
+
} catch {
|
|
16559
|
+
throw new Error(`Failed to parse action items response: ${data}`);
|
|
16560
|
+
}
|
|
16561
|
+
} else {
|
|
16562
|
+
throw new Error(formatHttpError("Failed to fetch action items", response.status, data));
|
|
16563
|
+
}
|
|
16564
|
+
}
|
|
16565
|
+
async function createActionItem(params) {
|
|
16566
|
+
const { apiKey, apiBaseUrl, issueId, title, description, sqlAction, configs, debug } = params;
|
|
16567
|
+
if (!apiKey) {
|
|
16568
|
+
throw new Error("API key is required");
|
|
16569
|
+
}
|
|
16570
|
+
if (!issueId) {
|
|
16571
|
+
throw new Error("issueId is required");
|
|
16572
|
+
}
|
|
16573
|
+
const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
16574
|
+
if (!uuidPattern.test(issueId.trim())) {
|
|
16575
|
+
throw new Error("issueId must be a valid UUID");
|
|
16576
|
+
}
|
|
16577
|
+
if (!title) {
|
|
16578
|
+
throw new Error("title is required");
|
|
16579
|
+
}
|
|
16580
|
+
const base = normalizeBaseUrl(apiBaseUrl);
|
|
16581
|
+
const url = new URL(`${base}/rpc/issue_action_item_create`);
|
|
16582
|
+
const bodyObj = {
|
|
16583
|
+
issue_id: issueId,
|
|
16584
|
+
title
|
|
16585
|
+
};
|
|
16586
|
+
if (description !== undefined) {
|
|
16587
|
+
bodyObj.description = description;
|
|
16588
|
+
}
|
|
16589
|
+
if (sqlAction !== undefined) {
|
|
16590
|
+
bodyObj.sql_action = sqlAction;
|
|
16591
|
+
}
|
|
16592
|
+
if (configs !== undefined) {
|
|
16593
|
+
bodyObj.configs = configs;
|
|
16594
|
+
}
|
|
16595
|
+
const body = JSON.stringify(bodyObj);
|
|
16596
|
+
const headers = {
|
|
16597
|
+
"access-token": apiKey,
|
|
16598
|
+
Prefer: "return=representation",
|
|
16599
|
+
"Content-Type": "application/json",
|
|
16600
|
+
Connection: "close"
|
|
16601
|
+
};
|
|
16602
|
+
if (debug) {
|
|
16603
|
+
const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
|
|
16604
|
+
console.log(`Debug: Resolved API base URL: ${base}`);
|
|
16605
|
+
console.log(`Debug: POST URL: ${url.toString()}`);
|
|
16606
|
+
console.log(`Debug: Auth scheme: access-token`);
|
|
16607
|
+
console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
|
|
16608
|
+
console.log(`Debug: Request body: ${body}`);
|
|
16609
|
+
}
|
|
16610
|
+
const response = await fetch(url.toString(), {
|
|
16611
|
+
method: "POST",
|
|
16612
|
+
headers,
|
|
16613
|
+
body
|
|
16614
|
+
});
|
|
16615
|
+
if (debug) {
|
|
16616
|
+
console.log(`Debug: Response status: ${response.status}`);
|
|
16617
|
+
console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
|
|
16618
|
+
}
|
|
16619
|
+
const data = await response.text();
|
|
16620
|
+
if (response.ok) {
|
|
16621
|
+
try {
|
|
16622
|
+
return JSON.parse(data);
|
|
16623
|
+
} catch {
|
|
16624
|
+
throw new Error(`Failed to parse create action item response: ${data}`);
|
|
16625
|
+
}
|
|
16626
|
+
} else {
|
|
16627
|
+
throw new Error(formatHttpError("Failed to create action item", response.status, data));
|
|
16628
|
+
}
|
|
16629
|
+
}
|
|
16630
|
+
async function updateActionItem(params) {
|
|
16631
|
+
const { apiKey, apiBaseUrl, actionItemId, title, description, isDone, status, statusReason, sqlAction, configs, debug } = params;
|
|
16632
|
+
if (!apiKey) {
|
|
16633
|
+
throw new Error("API key is required");
|
|
16634
|
+
}
|
|
16635
|
+
if (!actionItemId) {
|
|
16636
|
+
throw new Error("actionItemId is required");
|
|
16637
|
+
}
|
|
16638
|
+
const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
16639
|
+
if (!uuidPattern.test(actionItemId.trim())) {
|
|
16640
|
+
throw new Error("actionItemId must be a valid UUID");
|
|
16641
|
+
}
|
|
16642
|
+
const hasUpdateField = title !== undefined || description !== undefined || isDone !== undefined || status !== undefined || statusReason !== undefined || sqlAction !== undefined || configs !== undefined;
|
|
16643
|
+
if (!hasUpdateField) {
|
|
16644
|
+
throw new Error("At least one field to update is required");
|
|
16645
|
+
}
|
|
16646
|
+
const base = normalizeBaseUrl(apiBaseUrl);
|
|
16647
|
+
const url = new URL(`${base}/rpc/issue_action_item_update`);
|
|
16648
|
+
const bodyObj = {
|
|
16649
|
+
action_item_id: actionItemId
|
|
16650
|
+
};
|
|
16651
|
+
if (title !== undefined) {
|
|
16652
|
+
bodyObj.title = title;
|
|
16653
|
+
}
|
|
16654
|
+
if (description !== undefined) {
|
|
16655
|
+
bodyObj.description = description;
|
|
16656
|
+
}
|
|
16657
|
+
if (isDone !== undefined) {
|
|
16658
|
+
bodyObj.is_done = isDone;
|
|
16659
|
+
}
|
|
16660
|
+
if (status !== undefined) {
|
|
16661
|
+
bodyObj.status = status;
|
|
16662
|
+
}
|
|
16663
|
+
if (statusReason !== undefined) {
|
|
16664
|
+
bodyObj.status_reason = statusReason;
|
|
16665
|
+
}
|
|
16666
|
+
if (sqlAction !== undefined) {
|
|
16667
|
+
bodyObj.sql_action = sqlAction;
|
|
16668
|
+
}
|
|
16669
|
+
if (configs !== undefined) {
|
|
16670
|
+
bodyObj.configs = configs;
|
|
16671
|
+
}
|
|
16672
|
+
const body = JSON.stringify(bodyObj);
|
|
16673
|
+
const headers = {
|
|
16674
|
+
"access-token": apiKey,
|
|
16675
|
+
Prefer: "return=representation",
|
|
16676
|
+
"Content-Type": "application/json",
|
|
16677
|
+
Connection: "close"
|
|
16678
|
+
};
|
|
16679
|
+
if (debug) {
|
|
16680
|
+
const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
|
|
16681
|
+
console.log(`Debug: Resolved API base URL: ${base}`);
|
|
16682
|
+
console.log(`Debug: POST URL: ${url.toString()}`);
|
|
16683
|
+
console.log(`Debug: Auth scheme: access-token`);
|
|
16684
|
+
console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
|
|
16685
|
+
console.log(`Debug: Request body: ${body}`);
|
|
16686
|
+
}
|
|
16687
|
+
const response = await fetch(url.toString(), {
|
|
16688
|
+
method: "POST",
|
|
16689
|
+
headers,
|
|
16690
|
+
body
|
|
16691
|
+
});
|
|
16692
|
+
if (debug) {
|
|
16693
|
+
console.log(`Debug: Response status: ${response.status}`);
|
|
16694
|
+
console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
|
|
16695
|
+
}
|
|
16696
|
+
if (!response.ok) {
|
|
16697
|
+
const data = await response.text();
|
|
16698
|
+
throw new Error(formatHttpError("Failed to update action item", response.status, data));
|
|
16699
|
+
}
|
|
16700
|
+
}
|
|
16444
16701
|
|
|
16445
16702
|
// node_modules/zod/v4/core/core.js
|
|
16446
16703
|
var NEVER = Object.freeze({
|
|
@@ -23350,7 +23607,16 @@ async function handleToolCall(req, rootOpts, extra) {
|
|
|
23350
23607
|
}
|
|
23351
23608
|
try {
|
|
23352
23609
|
if (toolName === "list_issues") {
|
|
23353
|
-
const
|
|
23610
|
+
const orgId = args.org_id !== undefined ? Number(args.org_id) : cfg.orgId ?? undefined;
|
|
23611
|
+
const statusArg = args.status ? String(args.status) : undefined;
|
|
23612
|
+
let status;
|
|
23613
|
+
if (statusArg === "open")
|
|
23614
|
+
status = "open";
|
|
23615
|
+
else if (statusArg === "closed")
|
|
23616
|
+
status = "closed";
|
|
23617
|
+
const limit = args.limit !== undefined ? Number(args.limit) : undefined;
|
|
23618
|
+
const offset = args.offset !== undefined ? Number(args.offset) : undefined;
|
|
23619
|
+
const issues = await fetchIssues({ apiKey, apiBaseUrl, orgId, status, limit, offset, debug });
|
|
23354
23620
|
return { content: [{ type: "text", text: JSON.stringify(issues, null, 2) }] };
|
|
23355
23621
|
}
|
|
23356
23622
|
if (toolName === "view_issue") {
|
|
@@ -23430,6 +23696,70 @@ async function handleToolCall(req, rootOpts, extra) {
|
|
|
23430
23696
|
const result = await updateIssueComment({ apiKey, apiBaseUrl, commentId, content, debug });
|
|
23431
23697
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
23432
23698
|
}
|
|
23699
|
+
if (toolName === "view_action_item") {
|
|
23700
|
+
let actionItemIds;
|
|
23701
|
+
if (Array.isArray(args.action_item_ids)) {
|
|
23702
|
+
actionItemIds = args.action_item_ids.map((id) => String(id).trim()).filter((id) => id);
|
|
23703
|
+
} else if (args.action_item_id) {
|
|
23704
|
+
actionItemIds = [String(args.action_item_id).trim()];
|
|
23705
|
+
} else {
|
|
23706
|
+
actionItemIds = [];
|
|
23707
|
+
}
|
|
23708
|
+
if (actionItemIds.length === 0) {
|
|
23709
|
+
return { content: [{ type: "text", text: "action_item_id or action_item_ids is required" }], isError: true };
|
|
23710
|
+
}
|
|
23711
|
+
const actionItems = await fetchActionItem({ apiKey, apiBaseUrl, actionItemIds, debug });
|
|
23712
|
+
if (actionItems.length === 0) {
|
|
23713
|
+
return { content: [{ type: "text", text: "Action item(s) not found" }], isError: true };
|
|
23714
|
+
}
|
|
23715
|
+
return { content: [{ type: "text", text: JSON.stringify(actionItems, null, 2) }] };
|
|
23716
|
+
}
|
|
23717
|
+
if (toolName === "list_action_items") {
|
|
23718
|
+
const issueId = String(args.issue_id || "").trim();
|
|
23719
|
+
if (!issueId) {
|
|
23720
|
+
return { content: [{ type: "text", text: "issue_id is required" }], isError: true };
|
|
23721
|
+
}
|
|
23722
|
+
const actionItems = await fetchActionItems({ apiKey, apiBaseUrl, issueId, debug });
|
|
23723
|
+
return { content: [{ type: "text", text: JSON.stringify(actionItems, null, 2) }] };
|
|
23724
|
+
}
|
|
23725
|
+
if (toolName === "create_action_item") {
|
|
23726
|
+
const issueId = String(args.issue_id || "").trim();
|
|
23727
|
+
const rawTitle = String(args.title || "").trim();
|
|
23728
|
+
if (!issueId) {
|
|
23729
|
+
return { content: [{ type: "text", text: "issue_id is required" }], isError: true };
|
|
23730
|
+
}
|
|
23731
|
+
if (!rawTitle) {
|
|
23732
|
+
return { content: [{ type: "text", text: "title is required" }], isError: true };
|
|
23733
|
+
}
|
|
23734
|
+
const title = interpretEscapes(rawTitle);
|
|
23735
|
+
const rawDescription = args.description ? String(args.description) : undefined;
|
|
23736
|
+
const description = rawDescription ? interpretEscapes(rawDescription) : undefined;
|
|
23737
|
+
const sqlAction = args.sql_action !== undefined ? String(args.sql_action) : undefined;
|
|
23738
|
+
const configs = Array.isArray(args.configs) ? args.configs : undefined;
|
|
23739
|
+
const result = await createActionItem({ apiKey, apiBaseUrl, issueId, title, description, sqlAction, configs, debug });
|
|
23740
|
+
return { content: [{ type: "text", text: JSON.stringify({ id: result }, null, 2) }] };
|
|
23741
|
+
}
|
|
23742
|
+
if (toolName === "update_action_item") {
|
|
23743
|
+
const actionItemId = String(args.action_item_id || "").trim();
|
|
23744
|
+
if (!actionItemId) {
|
|
23745
|
+
return { content: [{ type: "text", text: "action_item_id is required" }], isError: true };
|
|
23746
|
+
}
|
|
23747
|
+
const rawTitle = args.title !== undefined ? String(args.title) : undefined;
|
|
23748
|
+
const title = rawTitle !== undefined ? interpretEscapes(rawTitle) : undefined;
|
|
23749
|
+
const rawDescription = args.description !== undefined ? String(args.description) : undefined;
|
|
23750
|
+
const description = rawDescription !== undefined ? interpretEscapes(rawDescription) : undefined;
|
|
23751
|
+
const isDone = args.is_done !== undefined ? Boolean(args.is_done) : undefined;
|
|
23752
|
+
const status = args.status !== undefined ? String(args.status) : undefined;
|
|
23753
|
+
const statusReason = args.status_reason !== undefined ? String(args.status_reason) : undefined;
|
|
23754
|
+
if (title === undefined && description === undefined && isDone === undefined && status === undefined && statusReason === undefined) {
|
|
23755
|
+
return { content: [{ type: "text", text: "At least one field to update is required (title, description, is_done, status, or status_reason)" }], isError: true };
|
|
23756
|
+
}
|
|
23757
|
+
if (status !== undefined && !["waiting_for_approval", "approved", "rejected"].includes(status)) {
|
|
23758
|
+
return { content: [{ type: "text", text: "status must be 'waiting_for_approval', 'approved', or 'rejected'" }], isError: true };
|
|
23759
|
+
}
|
|
23760
|
+
await updateActionItem({ apiKey, apiBaseUrl, actionItemId, title, description, isDone, status, statusReason, debug });
|
|
23761
|
+
return { content: [{ type: "text", text: JSON.stringify({ success: true }, null, 2) }] };
|
|
23762
|
+
}
|
|
23433
23763
|
throw new Error(`Unknown tool: ${toolName}`);
|
|
23434
23764
|
} catch (err) {
|
|
23435
23765
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -23437,7 +23767,11 @@ async function handleToolCall(req, rootOpts, extra) {
|
|
|
23437
23767
|
}
|
|
23438
23768
|
}
|
|
23439
23769
|
async function startMcpServer(rootOpts, extra) {
|
|
23440
|
-
const server = new Server({
|
|
23770
|
+
const server = new Server({
|
|
23771
|
+
name: "postgresai-mcp",
|
|
23772
|
+
version: package_default2.version,
|
|
23773
|
+
title: "PostgresAI MCP Server"
|
|
23774
|
+
}, { capabilities: { tools: {} } });
|
|
23441
23775
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
23442
23776
|
return {
|
|
23443
23777
|
tools: [
|
|
@@ -23447,6 +23781,10 @@ async function startMcpServer(rootOpts, extra) {
|
|
|
23447
23781
|
inputSchema: {
|
|
23448
23782
|
type: "object",
|
|
23449
23783
|
properties: {
|
|
23784
|
+
org_id: { type: "number", description: "Organization ID (optional, falls back to config)" },
|
|
23785
|
+
status: { type: "string", description: "Filter by status: 'open', 'closed', or omit for all" },
|
|
23786
|
+
limit: { type: "number", description: "Max number of issues to return (default: 20)" },
|
|
23787
|
+
offset: { type: "number", description: "Number of issues to skip (default: 0)" },
|
|
23450
23788
|
debug: { type: "boolean", description: "Enable verbose debug logs" }
|
|
23451
23789
|
},
|
|
23452
23790
|
additionalProperties: false
|
|
@@ -23535,47 +23873,130 @@ async function startMcpServer(rootOpts, extra) {
|
|
|
23535
23873
|
required: ["comment_id", "content"],
|
|
23536
23874
|
additionalProperties: false
|
|
23537
23875
|
}
|
|
23538
|
-
}
|
|
23539
|
-
|
|
23540
|
-
|
|
23541
|
-
|
|
23542
|
-
|
|
23543
|
-
|
|
23544
|
-
|
|
23545
|
-
|
|
23546
|
-
|
|
23547
|
-
}
|
|
23548
|
-
|
|
23549
|
-
|
|
23550
|
-
|
|
23551
|
-
|
|
23552
|
-
|
|
23553
|
-
|
|
23554
|
-
|
|
23555
|
-
|
|
23556
|
-
|
|
23557
|
-
|
|
23558
|
-
|
|
23559
|
-
|
|
23560
|
-
|
|
23561
|
-
|
|
23562
|
-
|
|
23563
|
-
|
|
23564
|
-
|
|
23565
|
-
|
|
23566
|
-
|
|
23567
|
-
|
|
23568
|
-
|
|
23569
|
-
|
|
23570
|
-
|
|
23571
|
-
|
|
23572
|
-
|
|
23573
|
-
|
|
23574
|
-
|
|
23575
|
-
|
|
23576
|
-
|
|
23577
|
-
|
|
23578
|
-
|
|
23876
|
+
},
|
|
23877
|
+
{
|
|
23878
|
+
name: "view_action_item",
|
|
23879
|
+
description: "View action item(s) with all details. Supports single ID or multiple IDs.",
|
|
23880
|
+
inputSchema: {
|
|
23881
|
+
type: "object",
|
|
23882
|
+
properties: {
|
|
23883
|
+
action_item_id: { type: "string", description: "Single action item ID (UUID)" },
|
|
23884
|
+
action_item_ids: { type: "array", items: { type: "string" }, description: "Multiple action item IDs (UUIDs)" },
|
|
23885
|
+
debug: { type: "boolean", description: "Enable verbose debug logs" }
|
|
23886
|
+
},
|
|
23887
|
+
additionalProperties: false
|
|
23888
|
+
}
|
|
23889
|
+
},
|
|
23890
|
+
{
|
|
23891
|
+
name: "list_action_items",
|
|
23892
|
+
description: "List action items for an issue",
|
|
23893
|
+
inputSchema: {
|
|
23894
|
+
type: "object",
|
|
23895
|
+
properties: {
|
|
23896
|
+
issue_id: { type: "string", description: "Issue ID (UUID)" },
|
|
23897
|
+
debug: { type: "boolean", description: "Enable verbose debug logs" }
|
|
23898
|
+
},
|
|
23899
|
+
required: ["issue_id"],
|
|
23900
|
+
additionalProperties: false
|
|
23901
|
+
}
|
|
23902
|
+
},
|
|
23903
|
+
{
|
|
23904
|
+
name: "create_action_item",
|
|
23905
|
+
description: "Create a new action item for an issue",
|
|
23906
|
+
inputSchema: {
|
|
23907
|
+
type: "object",
|
|
23908
|
+
properties: {
|
|
23909
|
+
issue_id: { type: "string", description: "Issue ID (UUID)" },
|
|
23910
|
+
title: { type: "string", description: "Action item title" },
|
|
23911
|
+
description: { type: "string", description: "Detailed description" },
|
|
23912
|
+
sql_action: { type: "string", description: "SQL command to execute, e.g. 'DROP INDEX CONCURRENTLY idx_unused;'" },
|
|
23913
|
+
configs: {
|
|
23914
|
+
type: "array",
|
|
23915
|
+
items: {
|
|
23916
|
+
type: "object",
|
|
23917
|
+
properties: {
|
|
23918
|
+
parameter: { type: "string" },
|
|
23919
|
+
value: { type: "string" }
|
|
23920
|
+
},
|
|
23921
|
+
required: ["parameter", "value"]
|
|
23922
|
+
},
|
|
23923
|
+
description: "Configuration parameter changes"
|
|
23924
|
+
},
|
|
23925
|
+
debug: { type: "boolean", description: "Enable verbose debug logs" }
|
|
23926
|
+
},
|
|
23927
|
+
required: ["issue_id", "title"],
|
|
23928
|
+
additionalProperties: false
|
|
23929
|
+
}
|
|
23930
|
+
},
|
|
23931
|
+
{
|
|
23932
|
+
name: "update_action_item",
|
|
23933
|
+
description: "Update an action item: mark as done/not done, approve/reject, or edit title/description",
|
|
23934
|
+
inputSchema: {
|
|
23935
|
+
type: "object",
|
|
23936
|
+
properties: {
|
|
23937
|
+
action_item_id: { type: "string", description: "Action item ID (UUID)" },
|
|
23938
|
+
title: { type: "string", description: "New title" },
|
|
23939
|
+
description: { type: "string", description: "New description" },
|
|
23940
|
+
is_done: { type: "boolean", description: "Mark as done (true) or not done (false)" },
|
|
23941
|
+
status: { type: "string", description: "Approval status: 'waiting_for_approval', 'approved', or 'rejected'" },
|
|
23942
|
+
status_reason: { type: "string", description: "Reason for approval/rejection" },
|
|
23943
|
+
debug: { type: "boolean", description: "Enable verbose debug logs" }
|
|
23944
|
+
},
|
|
23945
|
+
required: ["action_item_id"],
|
|
23946
|
+
additionalProperties: false
|
|
23947
|
+
}
|
|
23948
|
+
}
|
|
23949
|
+
]
|
|
23950
|
+
};
|
|
23951
|
+
});
|
|
23952
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
23953
|
+
return handleToolCall(req, rootOpts, extra);
|
|
23954
|
+
});
|
|
23955
|
+
const transport = new StdioServerTransport;
|
|
23956
|
+
await server.connect(transport);
|
|
23957
|
+
}
|
|
23958
|
+
|
|
23959
|
+
// lib/issues.ts
|
|
23960
|
+
async function fetchIssues2(params) {
|
|
23961
|
+
const { apiKey, apiBaseUrl, orgId, status, limit = 20, offset = 0, debug } = params;
|
|
23962
|
+
if (!apiKey) {
|
|
23963
|
+
throw new Error("API key is required");
|
|
23964
|
+
}
|
|
23965
|
+
const base = normalizeBaseUrl(apiBaseUrl);
|
|
23966
|
+
const url = new URL(`${base}/issues`);
|
|
23967
|
+
url.searchParams.set("select", "id,title,status,created_at");
|
|
23968
|
+
url.searchParams.set("order", "id.desc");
|
|
23969
|
+
url.searchParams.set("limit", String(limit));
|
|
23970
|
+
url.searchParams.set("offset", String(offset));
|
|
23971
|
+
if (typeof orgId === "number") {
|
|
23972
|
+
url.searchParams.set("org_id", `eq.${orgId}`);
|
|
23973
|
+
}
|
|
23974
|
+
if (status === "open") {
|
|
23975
|
+
url.searchParams.set("status", "eq.0");
|
|
23976
|
+
} else if (status === "closed") {
|
|
23977
|
+
url.searchParams.set("status", "eq.1");
|
|
23978
|
+
}
|
|
23979
|
+
const headers = {
|
|
23980
|
+
"access-token": apiKey,
|
|
23981
|
+
Prefer: "return=representation",
|
|
23982
|
+
"Content-Type": "application/json",
|
|
23983
|
+
Connection: "close"
|
|
23984
|
+
};
|
|
23985
|
+
if (debug) {
|
|
23986
|
+
const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
|
|
23987
|
+
console.log(`Debug: Resolved API base URL: ${base}`);
|
|
23988
|
+
console.log(`Debug: GET URL: ${url.toString()}`);
|
|
23989
|
+
console.log(`Debug: Auth scheme: access-token`);
|
|
23990
|
+
console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
|
|
23991
|
+
}
|
|
23992
|
+
const response = await fetch(url.toString(), {
|
|
23993
|
+
method: "GET",
|
|
23994
|
+
headers
|
|
23995
|
+
});
|
|
23996
|
+
if (debug) {
|
|
23997
|
+
console.log(`Debug: Response status: ${response.status}`);
|
|
23998
|
+
console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
|
|
23999
|
+
}
|
|
23579
24000
|
const data = await response.text();
|
|
23580
24001
|
if (response.ok) {
|
|
23581
24002
|
try {
|
|
@@ -23639,7 +24060,7 @@ async function fetchIssue2(params) {
|
|
|
23639
24060
|
}
|
|
23640
24061
|
const base = normalizeBaseUrl(apiBaseUrl);
|
|
23641
24062
|
const url = new URL(`${base}/issues`);
|
|
23642
|
-
url.searchParams.set("select", "id,title,description,status,created_at,author_display_name");
|
|
24063
|
+
url.searchParams.set("select", "id,title,description,status,created_at,author_display_name,action_items");
|
|
23643
24064
|
url.searchParams.set("id", `eq.${issueId}`);
|
|
23644
24065
|
url.searchParams.set("limit", "1");
|
|
23645
24066
|
const headers = {
|
|
@@ -23667,11 +24088,20 @@ async function fetchIssue2(params) {
|
|
|
23667
24088
|
if (response.ok) {
|
|
23668
24089
|
try {
|
|
23669
24090
|
const parsed = JSON.parse(data);
|
|
23670
|
-
|
|
23671
|
-
|
|
23672
|
-
|
|
23673
|
-
return parsed;
|
|
24091
|
+
const rawIssue = Array.isArray(parsed) ? parsed[0] : parsed;
|
|
24092
|
+
if (!rawIssue) {
|
|
24093
|
+
return null;
|
|
23674
24094
|
}
|
|
24095
|
+
const actionItemsSummary = Array.isArray(rawIssue.action_items) ? rawIssue.action_items.map((item) => ({ id: item.id, title: item.title })) : [];
|
|
24096
|
+
return {
|
|
24097
|
+
id: rawIssue.id,
|
|
24098
|
+
title: rawIssue.title,
|
|
24099
|
+
description: rawIssue.description,
|
|
24100
|
+
status: rawIssue.status,
|
|
24101
|
+
created_at: rawIssue.created_at,
|
|
24102
|
+
author_display_name: rawIssue.author_display_name,
|
|
24103
|
+
action_items: actionItemsSummary
|
|
24104
|
+
};
|
|
23675
24105
|
} catch {
|
|
23676
24106
|
throw new Error(`Failed to parse issue response: ${data}`);
|
|
23677
24107
|
}
|
|
@@ -23910,6 +24340,243 @@ async function updateIssueComment2(params) {
|
|
|
23910
24340
|
throw new Error(formatHttpError("Failed to update issue comment", response.status, data));
|
|
23911
24341
|
}
|
|
23912
24342
|
}
|
|
24343
|
+
async function fetchActionItem2(params) {
|
|
24344
|
+
const { apiKey, apiBaseUrl, actionItemIds, debug } = params;
|
|
24345
|
+
if (!apiKey) {
|
|
24346
|
+
throw new Error("API key is required");
|
|
24347
|
+
}
|
|
24348
|
+
const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
24349
|
+
const rawIds = Array.isArray(actionItemIds) ? actionItemIds : [actionItemIds];
|
|
24350
|
+
const validIds = rawIds.filter((id) => id != null && typeof id === "string").map((id) => id.trim()).filter((id) => id.length > 0 && uuidPattern.test(id));
|
|
24351
|
+
if (validIds.length === 0) {
|
|
24352
|
+
throw new Error("actionItemId is required and must be a valid UUID");
|
|
24353
|
+
}
|
|
24354
|
+
const base = normalizeBaseUrl(apiBaseUrl);
|
|
24355
|
+
const url = new URL(`${base}/issue_action_items`);
|
|
24356
|
+
if (validIds.length === 1) {
|
|
24357
|
+
url.searchParams.set("id", `eq.${validIds[0]}`);
|
|
24358
|
+
} else {
|
|
24359
|
+
url.searchParams.set("id", `in.(${validIds.join(",")})`);
|
|
24360
|
+
}
|
|
24361
|
+
const headers = {
|
|
24362
|
+
"access-token": apiKey,
|
|
24363
|
+
Prefer: "return=representation",
|
|
24364
|
+
"Content-Type": "application/json",
|
|
24365
|
+
Connection: "close"
|
|
24366
|
+
};
|
|
24367
|
+
if (debug) {
|
|
24368
|
+
const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
|
|
24369
|
+
console.log(`Debug: Resolved API base URL: ${base}`);
|
|
24370
|
+
console.log(`Debug: GET URL: ${url.toString()}`);
|
|
24371
|
+
console.log(`Debug: Auth scheme: access-token`);
|
|
24372
|
+
console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
|
|
24373
|
+
}
|
|
24374
|
+
const response = await fetch(url.toString(), {
|
|
24375
|
+
method: "GET",
|
|
24376
|
+
headers
|
|
24377
|
+
});
|
|
24378
|
+
if (debug) {
|
|
24379
|
+
console.log(`Debug: Response status: ${response.status}`);
|
|
24380
|
+
console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
|
|
24381
|
+
}
|
|
24382
|
+
const data = await response.text();
|
|
24383
|
+
if (response.ok) {
|
|
24384
|
+
try {
|
|
24385
|
+
const parsed = JSON.parse(data);
|
|
24386
|
+
if (Array.isArray(parsed)) {
|
|
24387
|
+
return parsed;
|
|
24388
|
+
}
|
|
24389
|
+
return parsed ? [parsed] : [];
|
|
24390
|
+
} catch {
|
|
24391
|
+
throw new Error(`Failed to parse action item response: ${data}`);
|
|
24392
|
+
}
|
|
24393
|
+
} else {
|
|
24394
|
+
throw new Error(formatHttpError("Failed to fetch action item", response.status, data));
|
|
24395
|
+
}
|
|
24396
|
+
}
|
|
24397
|
+
async function fetchActionItems2(params) {
|
|
24398
|
+
const { apiKey, apiBaseUrl, issueId, debug } = params;
|
|
24399
|
+
if (!apiKey) {
|
|
24400
|
+
throw new Error("API key is required");
|
|
24401
|
+
}
|
|
24402
|
+
if (!issueId) {
|
|
24403
|
+
throw new Error("issueId is required");
|
|
24404
|
+
}
|
|
24405
|
+
const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
24406
|
+
if (!uuidPattern.test(issueId.trim())) {
|
|
24407
|
+
throw new Error("issueId must be a valid UUID");
|
|
24408
|
+
}
|
|
24409
|
+
const base = normalizeBaseUrl(apiBaseUrl);
|
|
24410
|
+
const url = new URL(`${base}/issue_action_items`);
|
|
24411
|
+
url.searchParams.set("issue_id", `eq.${issueId.trim()}`);
|
|
24412
|
+
const headers = {
|
|
24413
|
+
"access-token": apiKey,
|
|
24414
|
+
Prefer: "return=representation",
|
|
24415
|
+
"Content-Type": "application/json",
|
|
24416
|
+
Connection: "close"
|
|
24417
|
+
};
|
|
24418
|
+
if (debug) {
|
|
24419
|
+
const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
|
|
24420
|
+
console.log(`Debug: Resolved API base URL: ${base}`);
|
|
24421
|
+
console.log(`Debug: GET URL: ${url.toString()}`);
|
|
24422
|
+
console.log(`Debug: Auth scheme: access-token`);
|
|
24423
|
+
console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
|
|
24424
|
+
}
|
|
24425
|
+
const response = await fetch(url.toString(), {
|
|
24426
|
+
method: "GET",
|
|
24427
|
+
headers
|
|
24428
|
+
});
|
|
24429
|
+
if (debug) {
|
|
24430
|
+
console.log(`Debug: Response status: ${response.status}`);
|
|
24431
|
+
console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
|
|
24432
|
+
}
|
|
24433
|
+
const data = await response.text();
|
|
24434
|
+
if (response.ok) {
|
|
24435
|
+
try {
|
|
24436
|
+
return JSON.parse(data);
|
|
24437
|
+
} catch {
|
|
24438
|
+
throw new Error(`Failed to parse action items response: ${data}`);
|
|
24439
|
+
}
|
|
24440
|
+
} else {
|
|
24441
|
+
throw new Error(formatHttpError("Failed to fetch action items", response.status, data));
|
|
24442
|
+
}
|
|
24443
|
+
}
|
|
24444
|
+
async function createActionItem2(params) {
|
|
24445
|
+
const { apiKey, apiBaseUrl, issueId, title, description, sqlAction, configs, debug } = params;
|
|
24446
|
+
if (!apiKey) {
|
|
24447
|
+
throw new Error("API key is required");
|
|
24448
|
+
}
|
|
24449
|
+
if (!issueId) {
|
|
24450
|
+
throw new Error("issueId is required");
|
|
24451
|
+
}
|
|
24452
|
+
const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
24453
|
+
if (!uuidPattern.test(issueId.trim())) {
|
|
24454
|
+
throw new Error("issueId must be a valid UUID");
|
|
24455
|
+
}
|
|
24456
|
+
if (!title) {
|
|
24457
|
+
throw new Error("title is required");
|
|
24458
|
+
}
|
|
24459
|
+
const base = normalizeBaseUrl(apiBaseUrl);
|
|
24460
|
+
const url = new URL(`${base}/rpc/issue_action_item_create`);
|
|
24461
|
+
const bodyObj = {
|
|
24462
|
+
issue_id: issueId,
|
|
24463
|
+
title
|
|
24464
|
+
};
|
|
24465
|
+
if (description !== undefined) {
|
|
24466
|
+
bodyObj.description = description;
|
|
24467
|
+
}
|
|
24468
|
+
if (sqlAction !== undefined) {
|
|
24469
|
+
bodyObj.sql_action = sqlAction;
|
|
24470
|
+
}
|
|
24471
|
+
if (configs !== undefined) {
|
|
24472
|
+
bodyObj.configs = configs;
|
|
24473
|
+
}
|
|
24474
|
+
const body = JSON.stringify(bodyObj);
|
|
24475
|
+
const headers = {
|
|
24476
|
+
"access-token": apiKey,
|
|
24477
|
+
Prefer: "return=representation",
|
|
24478
|
+
"Content-Type": "application/json",
|
|
24479
|
+
Connection: "close"
|
|
24480
|
+
};
|
|
24481
|
+
if (debug) {
|
|
24482
|
+
const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
|
|
24483
|
+
console.log(`Debug: Resolved API base URL: ${base}`);
|
|
24484
|
+
console.log(`Debug: POST URL: ${url.toString()}`);
|
|
24485
|
+
console.log(`Debug: Auth scheme: access-token`);
|
|
24486
|
+
console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
|
|
24487
|
+
console.log(`Debug: Request body: ${body}`);
|
|
24488
|
+
}
|
|
24489
|
+
const response = await fetch(url.toString(), {
|
|
24490
|
+
method: "POST",
|
|
24491
|
+
headers,
|
|
24492
|
+
body
|
|
24493
|
+
});
|
|
24494
|
+
if (debug) {
|
|
24495
|
+
console.log(`Debug: Response status: ${response.status}`);
|
|
24496
|
+
console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
|
|
24497
|
+
}
|
|
24498
|
+
const data = await response.text();
|
|
24499
|
+
if (response.ok) {
|
|
24500
|
+
try {
|
|
24501
|
+
return JSON.parse(data);
|
|
24502
|
+
} catch {
|
|
24503
|
+
throw new Error(`Failed to parse create action item response: ${data}`);
|
|
24504
|
+
}
|
|
24505
|
+
} else {
|
|
24506
|
+
throw new Error(formatHttpError("Failed to create action item", response.status, data));
|
|
24507
|
+
}
|
|
24508
|
+
}
|
|
24509
|
+
async function updateActionItem2(params) {
|
|
24510
|
+
const { apiKey, apiBaseUrl, actionItemId, title, description, isDone, status, statusReason, sqlAction, configs, debug } = params;
|
|
24511
|
+
if (!apiKey) {
|
|
24512
|
+
throw new Error("API key is required");
|
|
24513
|
+
}
|
|
24514
|
+
if (!actionItemId) {
|
|
24515
|
+
throw new Error("actionItemId is required");
|
|
24516
|
+
}
|
|
24517
|
+
const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
24518
|
+
if (!uuidPattern.test(actionItemId.trim())) {
|
|
24519
|
+
throw new Error("actionItemId must be a valid UUID");
|
|
24520
|
+
}
|
|
24521
|
+
const hasUpdateField = title !== undefined || description !== undefined || isDone !== undefined || status !== undefined || statusReason !== undefined || sqlAction !== undefined || configs !== undefined;
|
|
24522
|
+
if (!hasUpdateField) {
|
|
24523
|
+
throw new Error("At least one field to update is required");
|
|
24524
|
+
}
|
|
24525
|
+
const base = normalizeBaseUrl(apiBaseUrl);
|
|
24526
|
+
const url = new URL(`${base}/rpc/issue_action_item_update`);
|
|
24527
|
+
const bodyObj = {
|
|
24528
|
+
action_item_id: actionItemId
|
|
24529
|
+
};
|
|
24530
|
+
if (title !== undefined) {
|
|
24531
|
+
bodyObj.title = title;
|
|
24532
|
+
}
|
|
24533
|
+
if (description !== undefined) {
|
|
24534
|
+
bodyObj.description = description;
|
|
24535
|
+
}
|
|
24536
|
+
if (isDone !== undefined) {
|
|
24537
|
+
bodyObj.is_done = isDone;
|
|
24538
|
+
}
|
|
24539
|
+
if (status !== undefined) {
|
|
24540
|
+
bodyObj.status = status;
|
|
24541
|
+
}
|
|
24542
|
+
if (statusReason !== undefined) {
|
|
24543
|
+
bodyObj.status_reason = statusReason;
|
|
24544
|
+
}
|
|
24545
|
+
if (sqlAction !== undefined) {
|
|
24546
|
+
bodyObj.sql_action = sqlAction;
|
|
24547
|
+
}
|
|
24548
|
+
if (configs !== undefined) {
|
|
24549
|
+
bodyObj.configs = configs;
|
|
24550
|
+
}
|
|
24551
|
+
const body = JSON.stringify(bodyObj);
|
|
24552
|
+
const headers = {
|
|
24553
|
+
"access-token": apiKey,
|
|
24554
|
+
Prefer: "return=representation",
|
|
24555
|
+
"Content-Type": "application/json",
|
|
24556
|
+
Connection: "close"
|
|
24557
|
+
};
|
|
24558
|
+
if (debug) {
|
|
24559
|
+
const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
|
|
24560
|
+
console.log(`Debug: Resolved API base URL: ${base}`);
|
|
24561
|
+
console.log(`Debug: POST URL: ${url.toString()}`);
|
|
24562
|
+
console.log(`Debug: Auth scheme: access-token`);
|
|
24563
|
+
console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
|
|
24564
|
+
console.log(`Debug: Request body: ${body}`);
|
|
24565
|
+
}
|
|
24566
|
+
const response = await fetch(url.toString(), {
|
|
24567
|
+
method: "POST",
|
|
24568
|
+
headers,
|
|
24569
|
+
body
|
|
24570
|
+
});
|
|
24571
|
+
if (debug) {
|
|
24572
|
+
console.log(`Debug: Response status: ${response.status}`);
|
|
24573
|
+
console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
|
|
24574
|
+
}
|
|
24575
|
+
if (!response.ok) {
|
|
24576
|
+
const data = await response.text();
|
|
24577
|
+
throw new Error(formatHttpError("Failed to update action item", response.status, data));
|
|
24578
|
+
}
|
|
24579
|
+
}
|
|
23913
24580
|
|
|
23914
24581
|
// lib/util.ts
|
|
23915
24582
|
function maskSecret2(secret) {
|
|
@@ -24645,6 +25312,38 @@ class SupabaseClient {
|
|
|
24645
25312
|
return err;
|
|
24646
25313
|
}
|
|
24647
25314
|
}
|
|
25315
|
+
async function fetchPoolerDatabaseUrl(config2) {
|
|
25316
|
+
const url = `${SUPABASE_API_BASE}/v1/projects/${encodeURIComponent(config2.projectRef)}/config/database/pooler`;
|
|
25317
|
+
try {
|
|
25318
|
+
const response = await fetch(url, {
|
|
25319
|
+
method: "GET",
|
|
25320
|
+
headers: {
|
|
25321
|
+
Authorization: `Bearer ${config2.accessToken}`
|
|
25322
|
+
}
|
|
25323
|
+
});
|
|
25324
|
+
if (!response.ok) {
|
|
25325
|
+
return null;
|
|
25326
|
+
}
|
|
25327
|
+
const data = await response.json();
|
|
25328
|
+
if (Array.isArray(data) && data.length > 0) {
|
|
25329
|
+
const pooler = data[0];
|
|
25330
|
+
if (pooler.db_host && pooler.db_port && pooler.db_name && pooler.db_user) {
|
|
25331
|
+
return `postgresql://${pooler.db_user}@${pooler.db_host}:${pooler.db_port}/${pooler.db_name}`;
|
|
25332
|
+
}
|
|
25333
|
+
if (typeof pooler.connection_string === "string") {
|
|
25334
|
+
try {
|
|
25335
|
+
const connUrl = new URL(pooler.connection_string);
|
|
25336
|
+
return `postgresql://${connUrl.username}@${connUrl.hostname}:${connUrl.port}${connUrl.pathname}`;
|
|
25337
|
+
} catch {
|
|
25338
|
+
return null;
|
|
25339
|
+
}
|
|
25340
|
+
}
|
|
25341
|
+
}
|
|
25342
|
+
return null;
|
|
25343
|
+
} catch {
|
|
25344
|
+
return null;
|
|
25345
|
+
}
|
|
25346
|
+
}
|
|
24648
25347
|
function resolveSupabaseConfig(opts) {
|
|
24649
25348
|
const accessToken = opts.accessToken?.trim() || process.env.SUPABASE_ACCESS_TOKEN?.trim() || "";
|
|
24650
25349
|
const projectRef = opts.projectRef?.trim() || process.env.SUPABASE_PROJECT_REF?.trim() || "";
|
|
@@ -27303,6 +28002,10 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
|
|
|
27303
28002
|
console.log(`Optional permissions: ${includeOptionalPermissions2 ? "enabled" : "skipped"}`);
|
|
27304
28003
|
}
|
|
27305
28004
|
const supabaseClient = new SupabaseClient(supabaseConfig);
|
|
28005
|
+
let databaseUrl = null;
|
|
28006
|
+
if (jsonOutput) {
|
|
28007
|
+
databaseUrl = await fetchPoolerDatabaseUrl(supabaseConfig);
|
|
28008
|
+
}
|
|
27306
28009
|
try {
|
|
27307
28010
|
const database = await supabaseClient.getCurrentDatabase();
|
|
27308
28011
|
if (!database) {
|
|
@@ -27320,7 +28023,7 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
|
|
|
27320
28023
|
});
|
|
27321
28024
|
if (v.ok) {
|
|
27322
28025
|
if (jsonOutput) {
|
|
27323
|
-
|
|
28026
|
+
const result = {
|
|
27324
28027
|
success: true,
|
|
27325
28028
|
mode: "supabase",
|
|
27326
28029
|
action: "verify",
|
|
@@ -27328,7 +28031,11 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
|
|
|
27328
28031
|
monitoringUser: opts.monitoringUser,
|
|
27329
28032
|
verified: true,
|
|
27330
28033
|
missingOptional: v.missingOptional
|
|
27331
|
-
}
|
|
28034
|
+
};
|
|
28035
|
+
if (databaseUrl) {
|
|
28036
|
+
result.databaseUrl = databaseUrl;
|
|
28037
|
+
}
|
|
28038
|
+
outputJson(result);
|
|
27332
28039
|
} else {
|
|
27333
28040
|
console.log("\u2713 prepare-db verify: OK");
|
|
27334
28041
|
if (v.missingOptional.length > 0) {
|
|
@@ -27340,7 +28047,7 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
|
|
|
27340
28047
|
return;
|
|
27341
28048
|
}
|
|
27342
28049
|
if (jsonOutput) {
|
|
27343
|
-
|
|
28050
|
+
const result = {
|
|
27344
28051
|
success: false,
|
|
27345
28052
|
mode: "supabase",
|
|
27346
28053
|
action: "verify",
|
|
@@ -27349,7 +28056,11 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
|
|
|
27349
28056
|
verified: false,
|
|
27350
28057
|
missingRequired: v.missingRequired,
|
|
27351
28058
|
missingOptional: v.missingOptional
|
|
27352
|
-
}
|
|
28059
|
+
};
|
|
28060
|
+
if (databaseUrl) {
|
|
28061
|
+
result.databaseUrl = databaseUrl;
|
|
28062
|
+
}
|
|
28063
|
+
outputJson(result);
|
|
27353
28064
|
} else {
|
|
27354
28065
|
console.error("\u2717 prepare-db verify failed: missing required items");
|
|
27355
28066
|
for (const m of v.missingRequired)
|
|
@@ -27444,6 +28155,9 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
|
|
|
27444
28155
|
if (passwordGenerated) {
|
|
27445
28156
|
result.generatedPassword = monPassword;
|
|
27446
28157
|
}
|
|
28158
|
+
if (databaseUrl) {
|
|
28159
|
+
result.databaseUrl = databaseUrl;
|
|
28160
|
+
}
|
|
27447
28161
|
outputJson(result);
|
|
27448
28162
|
} else {
|
|
27449
28163
|
console.log(opts.resetPassword ? "\u2713 prepare-db password reset completed" : "\u2713 prepare-db completed");
|
|
@@ -29175,18 +29889,36 @@ function interpretEscapes2(str2) {
|
|
|
29175
29889
|
`).replace(/\\t/g, "\t").replace(/\\r/g, "\r").replace(/\\"/g, '"').replace(/\\'/g, "'").replace(/\x00/g, "\\");
|
|
29176
29890
|
}
|
|
29177
29891
|
var issues = program2.command("issues").description("issues management");
|
|
29178
|
-
issues.command("list").description("list issues").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (opts) => {
|
|
29892
|
+
issues.command("list").description("list issues").option("--status <status>", "filter by status: open, closed, or all (default: all)").option("--limit <n>", "max number of issues to return (default: 20)", parseInt).option("--offset <n>", "number of issues to skip (default: 0)", parseInt).option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (opts) => {
|
|
29893
|
+
const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Fetching issues...");
|
|
29179
29894
|
try {
|
|
29180
29895
|
const rootOpts = program2.opts();
|
|
29181
29896
|
const cfg = readConfig();
|
|
29182
29897
|
const { apiKey } = getConfig(rootOpts);
|
|
29183
29898
|
if (!apiKey) {
|
|
29899
|
+
spinner.stop();
|
|
29184
29900
|
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
29185
29901
|
process.exitCode = 1;
|
|
29186
29902
|
return;
|
|
29187
29903
|
}
|
|
29904
|
+
const orgId = cfg.orgId ?? undefined;
|
|
29188
29905
|
const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
|
|
29189
|
-
|
|
29906
|
+
let statusFilter;
|
|
29907
|
+
if (opts.status === "open") {
|
|
29908
|
+
statusFilter = "open";
|
|
29909
|
+
} else if (opts.status === "closed") {
|
|
29910
|
+
statusFilter = "closed";
|
|
29911
|
+
}
|
|
29912
|
+
const result = await fetchIssues2({
|
|
29913
|
+
apiKey,
|
|
29914
|
+
apiBaseUrl,
|
|
29915
|
+
orgId,
|
|
29916
|
+
status: statusFilter,
|
|
29917
|
+
limit: opts.limit,
|
|
29918
|
+
offset: opts.offset,
|
|
29919
|
+
debug: !!opts.debug
|
|
29920
|
+
});
|
|
29921
|
+
spinner.stop();
|
|
29190
29922
|
const trimmed = Array.isArray(result) ? result.map((r) => ({
|
|
29191
29923
|
id: r.id,
|
|
29192
29924
|
title: r.title,
|
|
@@ -29195,17 +29927,20 @@ issues.command("list").description("list issues").option("--debug", "enable debu
|
|
|
29195
29927
|
})) : result;
|
|
29196
29928
|
printResult(trimmed, opts.json);
|
|
29197
29929
|
} catch (err) {
|
|
29930
|
+
spinner.stop();
|
|
29198
29931
|
const message = err instanceof Error ? err.message : String(err);
|
|
29199
29932
|
console.error(message);
|
|
29200
29933
|
process.exitCode = 1;
|
|
29201
29934
|
}
|
|
29202
29935
|
});
|
|
29203
29936
|
issues.command("view <issueId>").description("view issue details and comments").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (issueId, opts) => {
|
|
29937
|
+
const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Fetching issue...");
|
|
29204
29938
|
try {
|
|
29205
29939
|
const rootOpts = program2.opts();
|
|
29206
29940
|
const cfg = readConfig();
|
|
29207
29941
|
const { apiKey } = getConfig(rootOpts);
|
|
29208
29942
|
if (!apiKey) {
|
|
29943
|
+
spinner.stop();
|
|
29209
29944
|
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
29210
29945
|
process.exitCode = 1;
|
|
29211
29946
|
return;
|
|
@@ -29213,32 +29948,38 @@ issues.command("view <issueId>").description("view issue details and comments").
|
|
|
29213
29948
|
const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
|
|
29214
29949
|
const issue2 = await fetchIssue2({ apiKey, apiBaseUrl, issueId, debug: !!opts.debug });
|
|
29215
29950
|
if (!issue2) {
|
|
29951
|
+
spinner.stop();
|
|
29216
29952
|
console.error("Issue not found");
|
|
29217
29953
|
process.exitCode = 1;
|
|
29218
29954
|
return;
|
|
29219
29955
|
}
|
|
29956
|
+
spinner.update("Fetching comments...");
|
|
29220
29957
|
const comments = await fetchIssueComments2({ apiKey, apiBaseUrl, issueId, debug: !!opts.debug });
|
|
29958
|
+
spinner.stop();
|
|
29221
29959
|
const combined = { issue: issue2, comments };
|
|
29222
29960
|
printResult(combined, opts.json);
|
|
29223
29961
|
} catch (err) {
|
|
29962
|
+
spinner.stop();
|
|
29224
29963
|
const message = err instanceof Error ? err.message : String(err);
|
|
29225
29964
|
console.error(message);
|
|
29226
29965
|
process.exitCode = 1;
|
|
29227
29966
|
}
|
|
29228
29967
|
});
|
|
29229
29968
|
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) => {
|
|
29969
|
+
if (opts.debug) {
|
|
29970
|
+
console.log(`Debug: Original content: ${JSON.stringify(content)}`);
|
|
29971
|
+
}
|
|
29972
|
+
content = interpretEscapes2(content);
|
|
29973
|
+
if (opts.debug) {
|
|
29974
|
+
console.log(`Debug: Interpreted content: ${JSON.stringify(content)}`);
|
|
29975
|
+
}
|
|
29976
|
+
const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Posting comment...");
|
|
29230
29977
|
try {
|
|
29231
|
-
if (opts.debug) {
|
|
29232
|
-
console.log(`Debug: Original content: ${JSON.stringify(content)}`);
|
|
29233
|
-
}
|
|
29234
|
-
content = interpretEscapes2(content);
|
|
29235
|
-
if (opts.debug) {
|
|
29236
|
-
console.log(`Debug: Interpreted content: ${JSON.stringify(content)}`);
|
|
29237
|
-
}
|
|
29238
29978
|
const rootOpts = program2.opts();
|
|
29239
29979
|
const cfg = readConfig();
|
|
29240
29980
|
const { apiKey } = getConfig(rootOpts);
|
|
29241
29981
|
if (!apiKey) {
|
|
29982
|
+
spinner.stop();
|
|
29242
29983
|
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
29243
29984
|
process.exitCode = 1;
|
|
29244
29985
|
return;
|
|
@@ -29252,41 +29993,44 @@ issues.command("post-comment <issueId> <content>").description("post a new comme
|
|
|
29252
29993
|
parentCommentId: opts.parent,
|
|
29253
29994
|
debug: !!opts.debug
|
|
29254
29995
|
});
|
|
29996
|
+
spinner.stop();
|
|
29255
29997
|
printResult(result, opts.json);
|
|
29256
29998
|
} catch (err) {
|
|
29999
|
+
spinner.stop();
|
|
29257
30000
|
const message = err instanceof Error ? err.message : String(err);
|
|
29258
30001
|
console.error(message);
|
|
29259
30002
|
process.exitCode = 1;
|
|
29260
30003
|
}
|
|
29261
30004
|
});
|
|
29262
|
-
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 (
|
|
30005
|
+
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 (use \\n for newlines)").option("--label <label>", "issue label (repeatable)", (value, previous) => {
|
|
29263
30006
|
previous.push(value);
|
|
29264
30007
|
return previous;
|
|
29265
30008
|
}, []).option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (rawTitle, opts) => {
|
|
30009
|
+
const rootOpts = program2.opts();
|
|
30010
|
+
const cfg = readConfig();
|
|
30011
|
+
const { apiKey } = getConfig(rootOpts);
|
|
30012
|
+
if (!apiKey) {
|
|
30013
|
+
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
30014
|
+
process.exitCode = 1;
|
|
30015
|
+
return;
|
|
30016
|
+
}
|
|
30017
|
+
const title = interpretEscapes2(String(rawTitle || "").trim());
|
|
30018
|
+
if (!title) {
|
|
30019
|
+
console.error("title is required");
|
|
30020
|
+
process.exitCode = 1;
|
|
30021
|
+
return;
|
|
30022
|
+
}
|
|
30023
|
+
const orgId = typeof opts.orgId === "number" && !Number.isNaN(opts.orgId) ? opts.orgId : cfg.orgId;
|
|
30024
|
+
if (typeof orgId !== "number") {
|
|
30025
|
+
console.error("org_id is required. Either pass --org-id or run 'pgai auth' to store it in config.");
|
|
30026
|
+
process.exitCode = 1;
|
|
30027
|
+
return;
|
|
30028
|
+
}
|
|
30029
|
+
const description = opts.description !== undefined ? interpretEscapes2(String(opts.description)) : undefined;
|
|
30030
|
+
const labels = Array.isArray(opts.label) && opts.label.length > 0 ? opts.label.map(String) : undefined;
|
|
30031
|
+
const projectId = typeof opts.projectId === "number" && !Number.isNaN(opts.projectId) ? opts.projectId : undefined;
|
|
30032
|
+
const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Creating issue...");
|
|
29266
30033
|
try {
|
|
29267
|
-
const rootOpts = program2.opts();
|
|
29268
|
-
const cfg = readConfig();
|
|
29269
|
-
const { apiKey } = getConfig(rootOpts);
|
|
29270
|
-
if (!apiKey) {
|
|
29271
|
-
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
29272
|
-
process.exitCode = 1;
|
|
29273
|
-
return;
|
|
29274
|
-
}
|
|
29275
|
-
const title = interpretEscapes2(String(rawTitle || "").trim());
|
|
29276
|
-
if (!title) {
|
|
29277
|
-
console.error("title is required");
|
|
29278
|
-
process.exitCode = 1;
|
|
29279
|
-
return;
|
|
29280
|
-
}
|
|
29281
|
-
const orgId = typeof opts.orgId === "number" && !Number.isNaN(opts.orgId) ? opts.orgId : cfg.orgId;
|
|
29282
|
-
if (typeof orgId !== "number") {
|
|
29283
|
-
console.error("org_id is required. Either pass --org-id or run 'pgai auth' to store it in config.");
|
|
29284
|
-
process.exitCode = 1;
|
|
29285
|
-
return;
|
|
29286
|
-
}
|
|
29287
|
-
const description = opts.description !== undefined ? interpretEscapes2(String(opts.description)) : undefined;
|
|
29288
|
-
const labels = Array.isArray(opts.label) && opts.label.length > 0 ? opts.label.map(String) : undefined;
|
|
29289
|
-
const projectId = typeof opts.projectId === "number" && !Number.isNaN(opts.projectId) ? opts.projectId : undefined;
|
|
29290
30034
|
const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
|
|
29291
30035
|
const result = await createIssue2({
|
|
29292
30036
|
apiKey,
|
|
@@ -29298,57 +30042,60 @@ issues.command("create <title>").description("create a new issue").option("--org
|
|
|
29298
30042
|
labels,
|
|
29299
30043
|
debug: !!opts.debug
|
|
29300
30044
|
});
|
|
30045
|
+
spinner.stop();
|
|
29301
30046
|
printResult(result, opts.json);
|
|
29302
30047
|
} catch (err) {
|
|
30048
|
+
spinner.stop();
|
|
29303
30049
|
const message = err instanceof Error ? err.message : String(err);
|
|
29304
30050
|
console.error(message);
|
|
29305
30051
|
process.exitCode = 1;
|
|
29306
30052
|
}
|
|
29307
30053
|
});
|
|
29308
|
-
issues.command("update <issueId>").description("update an existing issue (title/description/status/labels)").option("--title <text>", "new title (
|
|
30054
|
+
issues.command("update <issueId>").description("update an existing issue (title/description/status/labels)").option("--title <text>", "new title (use \\n for newlines)").option("--description <text>", "new description (use \\n for newlines)").option("--status <value>", "status: open|closed|0|1").option("--label <label>", "set labels (repeatable). If provided, replaces existing labels.", (value, previous) => {
|
|
29309
30055
|
previous.push(value);
|
|
29310
30056
|
return previous;
|
|
29311
30057
|
}, []).option("--clear-labels", "set labels to an empty list").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (issueId, opts) => {
|
|
29312
|
-
|
|
29313
|
-
|
|
29314
|
-
|
|
29315
|
-
|
|
29316
|
-
|
|
29317
|
-
|
|
29318
|
-
|
|
29319
|
-
|
|
29320
|
-
|
|
29321
|
-
|
|
29322
|
-
|
|
29323
|
-
|
|
29324
|
-
|
|
29325
|
-
|
|
29326
|
-
|
|
29327
|
-
|
|
29328
|
-
|
|
29329
|
-
|
|
29330
|
-
|
|
29331
|
-
|
|
29332
|
-
|
|
29333
|
-
|
|
29334
|
-
console.error("status must be open|closed|0|1");
|
|
29335
|
-
process.exitCode = 1;
|
|
29336
|
-
return;
|
|
29337
|
-
}
|
|
29338
|
-
status = n;
|
|
29339
|
-
}
|
|
29340
|
-
if (status !== 0 && status !== 1) {
|
|
29341
|
-
console.error("status must be 0 (open) or 1 (closed)");
|
|
30058
|
+
const rootOpts = program2.opts();
|
|
30059
|
+
const cfg = readConfig();
|
|
30060
|
+
const { apiKey } = getConfig(rootOpts);
|
|
30061
|
+
if (!apiKey) {
|
|
30062
|
+
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
30063
|
+
process.exitCode = 1;
|
|
30064
|
+
return;
|
|
30065
|
+
}
|
|
30066
|
+
const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
|
|
30067
|
+
const title = opts.title !== undefined ? interpretEscapes2(String(opts.title)) : undefined;
|
|
30068
|
+
const description = opts.description !== undefined ? interpretEscapes2(String(opts.description)) : undefined;
|
|
30069
|
+
let status = undefined;
|
|
30070
|
+
if (opts.status !== undefined) {
|
|
30071
|
+
const raw = String(opts.status).trim().toLowerCase();
|
|
30072
|
+
if (raw === "open")
|
|
30073
|
+
status = 0;
|
|
30074
|
+
else if (raw === "closed")
|
|
30075
|
+
status = 1;
|
|
30076
|
+
else {
|
|
30077
|
+
const n = Number(raw);
|
|
30078
|
+
if (!Number.isFinite(n)) {
|
|
30079
|
+
console.error("status must be open|closed|0|1");
|
|
29342
30080
|
process.exitCode = 1;
|
|
29343
30081
|
return;
|
|
29344
30082
|
}
|
|
30083
|
+
status = n;
|
|
29345
30084
|
}
|
|
29346
|
-
|
|
29347
|
-
|
|
29348
|
-
|
|
29349
|
-
|
|
29350
|
-
labels = opts.label.map(String);
|
|
30085
|
+
if (status !== 0 && status !== 1) {
|
|
30086
|
+
console.error("status must be 0 (open) or 1 (closed)");
|
|
30087
|
+
process.exitCode = 1;
|
|
30088
|
+
return;
|
|
29351
30089
|
}
|
|
30090
|
+
}
|
|
30091
|
+
let labels = undefined;
|
|
30092
|
+
if (opts.clearLabels) {
|
|
30093
|
+
labels = [];
|
|
30094
|
+
} else if (Array.isArray(opts.label) && opts.label.length > 0) {
|
|
30095
|
+
labels = opts.label.map(String);
|
|
30096
|
+
}
|
|
30097
|
+
const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Updating issue...");
|
|
30098
|
+
try {
|
|
29352
30099
|
const result = await updateIssue2({
|
|
29353
30100
|
apiKey,
|
|
29354
30101
|
apiBaseUrl,
|
|
@@ -29359,40 +30106,217 @@ issues.command("update <issueId>").description("update an existing issue (title/
|
|
|
29359
30106
|
labels,
|
|
29360
30107
|
debug: !!opts.debug
|
|
29361
30108
|
});
|
|
30109
|
+
spinner.stop();
|
|
29362
30110
|
printResult(result, opts.json);
|
|
29363
30111
|
} catch (err) {
|
|
30112
|
+
spinner.stop();
|
|
29364
30113
|
const message = err instanceof Error ? err.message : String(err);
|
|
29365
30114
|
console.error(message);
|
|
29366
30115
|
process.exitCode = 1;
|
|
29367
30116
|
}
|
|
29368
30117
|
});
|
|
29369
30118
|
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) => {
|
|
30119
|
+
if (opts.debug) {
|
|
30120
|
+
console.log(`Debug: Original content: ${JSON.stringify(content)}`);
|
|
30121
|
+
}
|
|
30122
|
+
content = interpretEscapes2(content);
|
|
30123
|
+
if (opts.debug) {
|
|
30124
|
+
console.log(`Debug: Interpreted content: ${JSON.stringify(content)}`);
|
|
30125
|
+
}
|
|
30126
|
+
const rootOpts = program2.opts();
|
|
30127
|
+
const cfg = readConfig();
|
|
30128
|
+
const { apiKey } = getConfig(rootOpts);
|
|
30129
|
+
if (!apiKey) {
|
|
30130
|
+
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
30131
|
+
process.exitCode = 1;
|
|
30132
|
+
return;
|
|
30133
|
+
}
|
|
30134
|
+
const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Updating comment...");
|
|
29370
30135
|
try {
|
|
29371
|
-
|
|
29372
|
-
|
|
29373
|
-
|
|
29374
|
-
|
|
29375
|
-
|
|
29376
|
-
|
|
30136
|
+
const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
|
|
30137
|
+
const result = await updateIssueComment2({
|
|
30138
|
+
apiKey,
|
|
30139
|
+
apiBaseUrl,
|
|
30140
|
+
commentId,
|
|
30141
|
+
content,
|
|
30142
|
+
debug: !!opts.debug
|
|
30143
|
+
});
|
|
30144
|
+
spinner.stop();
|
|
30145
|
+
printResult(result, opts.json);
|
|
30146
|
+
} catch (err) {
|
|
30147
|
+
spinner.stop();
|
|
30148
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
30149
|
+
console.error(message);
|
|
30150
|
+
process.exitCode = 1;
|
|
30151
|
+
}
|
|
30152
|
+
});
|
|
30153
|
+
issues.command("action-items <issueId>").description("list action items for an issue").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (issueId, opts) => {
|
|
30154
|
+
const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Fetching action items...");
|
|
30155
|
+
try {
|
|
30156
|
+
const rootOpts = program2.opts();
|
|
30157
|
+
const cfg = readConfig();
|
|
30158
|
+
const { apiKey } = getConfig(rootOpts);
|
|
30159
|
+
if (!apiKey) {
|
|
30160
|
+
spinner.stop();
|
|
30161
|
+
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
30162
|
+
process.exitCode = 1;
|
|
30163
|
+
return;
|
|
29377
30164
|
}
|
|
30165
|
+
const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
|
|
30166
|
+
const result = await fetchActionItems2({ apiKey, apiBaseUrl, issueId, debug: !!opts.debug });
|
|
30167
|
+
spinner.stop();
|
|
30168
|
+
printResult(result, opts.json);
|
|
30169
|
+
} catch (err) {
|
|
30170
|
+
spinner.stop();
|
|
30171
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
30172
|
+
console.error(message);
|
|
30173
|
+
process.exitCode = 1;
|
|
30174
|
+
}
|
|
30175
|
+
});
|
|
30176
|
+
issues.command("view-action-item <actionItemIds...>").description("view action item(s) with all details (supports multiple IDs)").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (actionItemIds, opts) => {
|
|
30177
|
+
const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Fetching action item(s)...");
|
|
30178
|
+
try {
|
|
29378
30179
|
const rootOpts = program2.opts();
|
|
29379
30180
|
const cfg = readConfig();
|
|
29380
30181
|
const { apiKey } = getConfig(rootOpts);
|
|
29381
30182
|
if (!apiKey) {
|
|
30183
|
+
spinner.stop();
|
|
29382
30184
|
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
29383
30185
|
process.exitCode = 1;
|
|
29384
30186
|
return;
|
|
29385
30187
|
}
|
|
29386
30188
|
const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
|
|
29387
|
-
const result = await
|
|
30189
|
+
const result = await fetchActionItem2({ apiKey, apiBaseUrl, actionItemIds, debug: !!opts.debug });
|
|
30190
|
+
if (result.length === 0) {
|
|
30191
|
+
spinner.stop();
|
|
30192
|
+
console.error("Action item(s) not found");
|
|
30193
|
+
process.exitCode = 1;
|
|
30194
|
+
return;
|
|
30195
|
+
}
|
|
30196
|
+
spinner.stop();
|
|
30197
|
+
printResult(result, opts.json);
|
|
30198
|
+
} catch (err) {
|
|
30199
|
+
spinner.stop();
|
|
30200
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
30201
|
+
console.error(message);
|
|
30202
|
+
process.exitCode = 1;
|
|
30203
|
+
}
|
|
30204
|
+
});
|
|
30205
|
+
issues.command("create-action-item <issueId> <title>").description("create a new action item for an issue").option("--description <text>", "detailed description (use \\n for newlines)").option("--sql-action <sql>", "SQL command to execute").option("--config <json>", 'config change as JSON: {"parameter":"...","value":"..."} (repeatable)', (value, previous) => {
|
|
30206
|
+
try {
|
|
30207
|
+
previous.push(JSON.parse(value));
|
|
30208
|
+
} catch {
|
|
30209
|
+
console.error(`Invalid JSON for --config: ${value}`);
|
|
30210
|
+
process.exit(1);
|
|
30211
|
+
}
|
|
30212
|
+
return previous;
|
|
30213
|
+
}, []).option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (issueId, rawTitle, opts) => {
|
|
30214
|
+
const rootOpts = program2.opts();
|
|
30215
|
+
const cfg = readConfig();
|
|
30216
|
+
const { apiKey } = getConfig(rootOpts);
|
|
30217
|
+
if (!apiKey) {
|
|
30218
|
+
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
30219
|
+
process.exitCode = 1;
|
|
30220
|
+
return;
|
|
30221
|
+
}
|
|
30222
|
+
const title = interpretEscapes2(String(rawTitle || "").trim());
|
|
30223
|
+
if (!title) {
|
|
30224
|
+
console.error("title is required");
|
|
30225
|
+
process.exitCode = 1;
|
|
30226
|
+
return;
|
|
30227
|
+
}
|
|
30228
|
+
const description = opts.description !== undefined ? interpretEscapes2(String(opts.description)) : undefined;
|
|
30229
|
+
const sqlAction = opts.sqlAction;
|
|
30230
|
+
const configs = Array.isArray(opts.config) && opts.config.length > 0 ? opts.config : undefined;
|
|
30231
|
+
const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Creating action item...");
|
|
30232
|
+
try {
|
|
30233
|
+
const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
|
|
30234
|
+
const result = await createActionItem2({
|
|
29388
30235
|
apiKey,
|
|
29389
30236
|
apiBaseUrl,
|
|
29390
|
-
|
|
29391
|
-
|
|
30237
|
+
issueId,
|
|
30238
|
+
title,
|
|
30239
|
+
description,
|
|
30240
|
+
sqlAction,
|
|
30241
|
+
configs,
|
|
29392
30242
|
debug: !!opts.debug
|
|
29393
30243
|
});
|
|
29394
|
-
|
|
30244
|
+
spinner.stop();
|
|
30245
|
+
printResult({ id: result }, opts.json);
|
|
30246
|
+
} catch (err) {
|
|
30247
|
+
spinner.stop();
|
|
30248
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
30249
|
+
console.error(message);
|
|
30250
|
+
process.exitCode = 1;
|
|
30251
|
+
}
|
|
30252
|
+
});
|
|
30253
|
+
issues.command("update-action-item <actionItemId>").description("update an action item (title, description, status, sql_action, configs)").option("--title <text>", "new title (use \\n for newlines)").option("--description <text>", "new description (use \\n for newlines)").option("--done", "mark as done").option("--not-done", "mark as not done").option("--status <value>", "status: waiting_for_approval|approved|rejected").option("--status-reason <text>", "reason for status change").option("--sql-action <sql>", "SQL command (use empty string to clear)").option("--config <json>", "config change as JSON (repeatable, replaces all configs)", (value, previous) => {
|
|
30254
|
+
try {
|
|
30255
|
+
previous.push(JSON.parse(value));
|
|
30256
|
+
} catch {
|
|
30257
|
+
console.error(`Invalid JSON for --config: ${value}`);
|
|
30258
|
+
process.exit(1);
|
|
30259
|
+
}
|
|
30260
|
+
return previous;
|
|
30261
|
+
}, []).option("--clear-configs", "clear all config changes").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (actionItemId, opts) => {
|
|
30262
|
+
const rootOpts = program2.opts();
|
|
30263
|
+
const cfg = readConfig();
|
|
30264
|
+
const { apiKey } = getConfig(rootOpts);
|
|
30265
|
+
if (!apiKey) {
|
|
30266
|
+
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
30267
|
+
process.exitCode = 1;
|
|
30268
|
+
return;
|
|
30269
|
+
}
|
|
30270
|
+
const title = opts.title !== undefined ? interpretEscapes2(String(opts.title)) : undefined;
|
|
30271
|
+
const description = opts.description !== undefined ? interpretEscapes2(String(opts.description)) : undefined;
|
|
30272
|
+
let isDone = undefined;
|
|
30273
|
+
if (opts.done)
|
|
30274
|
+
isDone = true;
|
|
30275
|
+
else if (opts.notDone)
|
|
30276
|
+
isDone = false;
|
|
30277
|
+
let status = undefined;
|
|
30278
|
+
if (opts.status !== undefined) {
|
|
30279
|
+
const validStatuses = ["waiting_for_approval", "approved", "rejected"];
|
|
30280
|
+
if (!validStatuses.includes(opts.status)) {
|
|
30281
|
+
console.error(`status must be one of: ${validStatuses.join(", ")}`);
|
|
30282
|
+
process.exitCode = 1;
|
|
30283
|
+
return;
|
|
30284
|
+
}
|
|
30285
|
+
status = opts.status;
|
|
30286
|
+
}
|
|
30287
|
+
const statusReason = opts.statusReason;
|
|
30288
|
+
const sqlAction = opts.sqlAction;
|
|
30289
|
+
let configs = undefined;
|
|
30290
|
+
if (opts.clearConfigs) {
|
|
30291
|
+
configs = [];
|
|
30292
|
+
} else if (Array.isArray(opts.config) && opts.config.length > 0) {
|
|
30293
|
+
configs = opts.config;
|
|
30294
|
+
}
|
|
30295
|
+
if (title === undefined && description === undefined && isDone === undefined && status === undefined && statusReason === undefined && sqlAction === undefined && configs === undefined) {
|
|
30296
|
+
console.error("At least one update option is required");
|
|
30297
|
+
process.exitCode = 1;
|
|
30298
|
+
return;
|
|
30299
|
+
}
|
|
30300
|
+
const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Updating action item...");
|
|
30301
|
+
try {
|
|
30302
|
+
const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
|
|
30303
|
+
await updateActionItem2({
|
|
30304
|
+
apiKey,
|
|
30305
|
+
apiBaseUrl,
|
|
30306
|
+
actionItemId,
|
|
30307
|
+
title,
|
|
30308
|
+
description,
|
|
30309
|
+
isDone,
|
|
30310
|
+
status,
|
|
30311
|
+
statusReason,
|
|
30312
|
+
sqlAction,
|
|
30313
|
+
configs,
|
|
30314
|
+
debug: !!opts.debug
|
|
30315
|
+
});
|
|
30316
|
+
spinner.stop();
|
|
30317
|
+
printResult({ success: true }, opts.json);
|
|
29395
30318
|
} catch (err) {
|
|
30319
|
+
spinner.stop();
|
|
29396
30320
|
const message = err instanceof Error ? err.message : String(err);
|
|
29397
30321
|
console.error(message);
|
|
29398
30322
|
process.exitCode = 1;
|