postgresai 0.14.0-dev.71 → 0.14.0-dev.73
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 +1064 -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 +55 -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.73",
|
|
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.73";
|
|
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,39 @@ class SupabaseClient {
|
|
|
24645
25312
|
return err;
|
|
24646
25313
|
}
|
|
24647
25314
|
}
|
|
25315
|
+
async function fetchPoolerDatabaseUrl(config2, username) {
|
|
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) {
|
|
25331
|
+
return `postgresql://${username}@${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
|
+
const portPart = connUrl.port ? `:${connUrl.port}` : "";
|
|
25337
|
+
return `postgresql://${username}@${connUrl.hostname}${portPart}${connUrl.pathname}`;
|
|
25338
|
+
} catch {
|
|
25339
|
+
return null;
|
|
25340
|
+
}
|
|
25341
|
+
}
|
|
25342
|
+
}
|
|
25343
|
+
return null;
|
|
25344
|
+
} catch {
|
|
25345
|
+
return null;
|
|
25346
|
+
}
|
|
25347
|
+
}
|
|
24648
25348
|
function resolveSupabaseConfig(opts) {
|
|
24649
25349
|
const accessToken = opts.accessToken?.trim() || process.env.SUPABASE_ACCESS_TOKEN?.trim() || "";
|
|
24650
25350
|
const projectRef = opts.projectRef?.trim() || process.env.SUPABASE_PROJECT_REF?.trim() || "";
|
|
@@ -27303,6 +28003,10 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
|
|
|
27303
28003
|
console.log(`Optional permissions: ${includeOptionalPermissions2 ? "enabled" : "skipped"}`);
|
|
27304
28004
|
}
|
|
27305
28005
|
const supabaseClient = new SupabaseClient(supabaseConfig);
|
|
28006
|
+
let databaseUrl = null;
|
|
28007
|
+
if (jsonOutput) {
|
|
28008
|
+
databaseUrl = await fetchPoolerDatabaseUrl(supabaseConfig, opts.monitoringUser);
|
|
28009
|
+
}
|
|
27306
28010
|
try {
|
|
27307
28011
|
const database = await supabaseClient.getCurrentDatabase();
|
|
27308
28012
|
if (!database) {
|
|
@@ -27320,7 +28024,7 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
|
|
|
27320
28024
|
});
|
|
27321
28025
|
if (v.ok) {
|
|
27322
28026
|
if (jsonOutput) {
|
|
27323
|
-
|
|
28027
|
+
const result = {
|
|
27324
28028
|
success: true,
|
|
27325
28029
|
mode: "supabase",
|
|
27326
28030
|
action: "verify",
|
|
@@ -27328,7 +28032,11 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
|
|
|
27328
28032
|
monitoringUser: opts.monitoringUser,
|
|
27329
28033
|
verified: true,
|
|
27330
28034
|
missingOptional: v.missingOptional
|
|
27331
|
-
}
|
|
28035
|
+
};
|
|
28036
|
+
if (databaseUrl) {
|
|
28037
|
+
result.databaseUrl = databaseUrl;
|
|
28038
|
+
}
|
|
28039
|
+
outputJson(result);
|
|
27332
28040
|
} else {
|
|
27333
28041
|
console.log("\u2713 prepare-db verify: OK");
|
|
27334
28042
|
if (v.missingOptional.length > 0) {
|
|
@@ -27340,7 +28048,7 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
|
|
|
27340
28048
|
return;
|
|
27341
28049
|
}
|
|
27342
28050
|
if (jsonOutput) {
|
|
27343
|
-
|
|
28051
|
+
const result = {
|
|
27344
28052
|
success: false,
|
|
27345
28053
|
mode: "supabase",
|
|
27346
28054
|
action: "verify",
|
|
@@ -27349,7 +28057,11 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
|
|
|
27349
28057
|
verified: false,
|
|
27350
28058
|
missingRequired: v.missingRequired,
|
|
27351
28059
|
missingOptional: v.missingOptional
|
|
27352
|
-
}
|
|
28060
|
+
};
|
|
28061
|
+
if (databaseUrl) {
|
|
28062
|
+
result.databaseUrl = databaseUrl;
|
|
28063
|
+
}
|
|
28064
|
+
outputJson(result);
|
|
27353
28065
|
} else {
|
|
27354
28066
|
console.error("\u2717 prepare-db verify failed: missing required items");
|
|
27355
28067
|
for (const m of v.missingRequired)
|
|
@@ -27444,6 +28156,9 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
|
|
|
27444
28156
|
if (passwordGenerated) {
|
|
27445
28157
|
result.generatedPassword = monPassword;
|
|
27446
28158
|
}
|
|
28159
|
+
if (databaseUrl) {
|
|
28160
|
+
result.databaseUrl = databaseUrl;
|
|
28161
|
+
}
|
|
27447
28162
|
outputJson(result);
|
|
27448
28163
|
} else {
|
|
27449
28164
|
console.log(opts.resetPassword ? "\u2713 prepare-db password reset completed" : "\u2713 prepare-db completed");
|
|
@@ -29175,18 +29890,36 @@ function interpretEscapes2(str2) {
|
|
|
29175
29890
|
`).replace(/\\t/g, "\t").replace(/\\r/g, "\r").replace(/\\"/g, '"').replace(/\\'/g, "'").replace(/\x00/g, "\\");
|
|
29176
29891
|
}
|
|
29177
29892
|
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) => {
|
|
29893
|
+
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) => {
|
|
29894
|
+
const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Fetching issues...");
|
|
29179
29895
|
try {
|
|
29180
29896
|
const rootOpts = program2.opts();
|
|
29181
29897
|
const cfg = readConfig();
|
|
29182
29898
|
const { apiKey } = getConfig(rootOpts);
|
|
29183
29899
|
if (!apiKey) {
|
|
29900
|
+
spinner.stop();
|
|
29184
29901
|
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
29185
29902
|
process.exitCode = 1;
|
|
29186
29903
|
return;
|
|
29187
29904
|
}
|
|
29905
|
+
const orgId = cfg.orgId ?? undefined;
|
|
29188
29906
|
const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
|
|
29189
|
-
|
|
29907
|
+
let statusFilter;
|
|
29908
|
+
if (opts.status === "open") {
|
|
29909
|
+
statusFilter = "open";
|
|
29910
|
+
} else if (opts.status === "closed") {
|
|
29911
|
+
statusFilter = "closed";
|
|
29912
|
+
}
|
|
29913
|
+
const result = await fetchIssues2({
|
|
29914
|
+
apiKey,
|
|
29915
|
+
apiBaseUrl,
|
|
29916
|
+
orgId,
|
|
29917
|
+
status: statusFilter,
|
|
29918
|
+
limit: opts.limit,
|
|
29919
|
+
offset: opts.offset,
|
|
29920
|
+
debug: !!opts.debug
|
|
29921
|
+
});
|
|
29922
|
+
spinner.stop();
|
|
29190
29923
|
const trimmed = Array.isArray(result) ? result.map((r) => ({
|
|
29191
29924
|
id: r.id,
|
|
29192
29925
|
title: r.title,
|
|
@@ -29195,17 +29928,20 @@ issues.command("list").description("list issues").option("--debug", "enable debu
|
|
|
29195
29928
|
})) : result;
|
|
29196
29929
|
printResult(trimmed, opts.json);
|
|
29197
29930
|
} catch (err) {
|
|
29931
|
+
spinner.stop();
|
|
29198
29932
|
const message = err instanceof Error ? err.message : String(err);
|
|
29199
29933
|
console.error(message);
|
|
29200
29934
|
process.exitCode = 1;
|
|
29201
29935
|
}
|
|
29202
29936
|
});
|
|
29203
29937
|
issues.command("view <issueId>").description("view issue details and comments").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (issueId, opts) => {
|
|
29938
|
+
const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Fetching issue...");
|
|
29204
29939
|
try {
|
|
29205
29940
|
const rootOpts = program2.opts();
|
|
29206
29941
|
const cfg = readConfig();
|
|
29207
29942
|
const { apiKey } = getConfig(rootOpts);
|
|
29208
29943
|
if (!apiKey) {
|
|
29944
|
+
spinner.stop();
|
|
29209
29945
|
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
29210
29946
|
process.exitCode = 1;
|
|
29211
29947
|
return;
|
|
@@ -29213,32 +29949,38 @@ issues.command("view <issueId>").description("view issue details and comments").
|
|
|
29213
29949
|
const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
|
|
29214
29950
|
const issue2 = await fetchIssue2({ apiKey, apiBaseUrl, issueId, debug: !!opts.debug });
|
|
29215
29951
|
if (!issue2) {
|
|
29952
|
+
spinner.stop();
|
|
29216
29953
|
console.error("Issue not found");
|
|
29217
29954
|
process.exitCode = 1;
|
|
29218
29955
|
return;
|
|
29219
29956
|
}
|
|
29957
|
+
spinner.update("Fetching comments...");
|
|
29220
29958
|
const comments = await fetchIssueComments2({ apiKey, apiBaseUrl, issueId, debug: !!opts.debug });
|
|
29959
|
+
spinner.stop();
|
|
29221
29960
|
const combined = { issue: issue2, comments };
|
|
29222
29961
|
printResult(combined, opts.json);
|
|
29223
29962
|
} catch (err) {
|
|
29963
|
+
spinner.stop();
|
|
29224
29964
|
const message = err instanceof Error ? err.message : String(err);
|
|
29225
29965
|
console.error(message);
|
|
29226
29966
|
process.exitCode = 1;
|
|
29227
29967
|
}
|
|
29228
29968
|
});
|
|
29229
29969
|
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) => {
|
|
29970
|
+
if (opts.debug) {
|
|
29971
|
+
console.log(`Debug: Original content: ${JSON.stringify(content)}`);
|
|
29972
|
+
}
|
|
29973
|
+
content = interpretEscapes2(content);
|
|
29974
|
+
if (opts.debug) {
|
|
29975
|
+
console.log(`Debug: Interpreted content: ${JSON.stringify(content)}`);
|
|
29976
|
+
}
|
|
29977
|
+
const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Posting comment...");
|
|
29230
29978
|
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
29979
|
const rootOpts = program2.opts();
|
|
29239
29980
|
const cfg = readConfig();
|
|
29240
29981
|
const { apiKey } = getConfig(rootOpts);
|
|
29241
29982
|
if (!apiKey) {
|
|
29983
|
+
spinner.stop();
|
|
29242
29984
|
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
29243
29985
|
process.exitCode = 1;
|
|
29244
29986
|
return;
|
|
@@ -29252,41 +29994,44 @@ issues.command("post-comment <issueId> <content>").description("post a new comme
|
|
|
29252
29994
|
parentCommentId: opts.parent,
|
|
29253
29995
|
debug: !!opts.debug
|
|
29254
29996
|
});
|
|
29997
|
+
spinner.stop();
|
|
29255
29998
|
printResult(result, opts.json);
|
|
29256
29999
|
} catch (err) {
|
|
30000
|
+
spinner.stop();
|
|
29257
30001
|
const message = err instanceof Error ? err.message : String(err);
|
|
29258
30002
|
console.error(message);
|
|
29259
30003
|
process.exitCode = 1;
|
|
29260
30004
|
}
|
|
29261
30005
|
});
|
|
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 (
|
|
30006
|
+
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
30007
|
previous.push(value);
|
|
29264
30008
|
return previous;
|
|
29265
30009
|
}, []).option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (rawTitle, opts) => {
|
|
30010
|
+
const rootOpts = program2.opts();
|
|
30011
|
+
const cfg = readConfig();
|
|
30012
|
+
const { apiKey } = getConfig(rootOpts);
|
|
30013
|
+
if (!apiKey) {
|
|
30014
|
+
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
30015
|
+
process.exitCode = 1;
|
|
30016
|
+
return;
|
|
30017
|
+
}
|
|
30018
|
+
const title = interpretEscapes2(String(rawTitle || "").trim());
|
|
30019
|
+
if (!title) {
|
|
30020
|
+
console.error("title is required");
|
|
30021
|
+
process.exitCode = 1;
|
|
30022
|
+
return;
|
|
30023
|
+
}
|
|
30024
|
+
const orgId = typeof opts.orgId === "number" && !Number.isNaN(opts.orgId) ? opts.orgId : cfg.orgId;
|
|
30025
|
+
if (typeof orgId !== "number") {
|
|
30026
|
+
console.error("org_id is required. Either pass --org-id or run 'pgai auth' to store it in config.");
|
|
30027
|
+
process.exitCode = 1;
|
|
30028
|
+
return;
|
|
30029
|
+
}
|
|
30030
|
+
const description = opts.description !== undefined ? interpretEscapes2(String(opts.description)) : undefined;
|
|
30031
|
+
const labels = Array.isArray(opts.label) && opts.label.length > 0 ? opts.label.map(String) : undefined;
|
|
30032
|
+
const projectId = typeof opts.projectId === "number" && !Number.isNaN(opts.projectId) ? opts.projectId : undefined;
|
|
30033
|
+
const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Creating issue...");
|
|
29266
30034
|
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
30035
|
const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
|
|
29291
30036
|
const result = await createIssue2({
|
|
29292
30037
|
apiKey,
|
|
@@ -29298,57 +30043,60 @@ issues.command("create <title>").description("create a new issue").option("--org
|
|
|
29298
30043
|
labels,
|
|
29299
30044
|
debug: !!opts.debug
|
|
29300
30045
|
});
|
|
30046
|
+
spinner.stop();
|
|
29301
30047
|
printResult(result, opts.json);
|
|
29302
30048
|
} catch (err) {
|
|
30049
|
+
spinner.stop();
|
|
29303
30050
|
const message = err instanceof Error ? err.message : String(err);
|
|
29304
30051
|
console.error(message);
|
|
29305
30052
|
process.exitCode = 1;
|
|
29306
30053
|
}
|
|
29307
30054
|
});
|
|
29308
|
-
issues.command("update <issueId>").description("update an existing issue (title/description/status/labels)").option("--title <text>", "new title (
|
|
30055
|
+
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
30056
|
previous.push(value);
|
|
29310
30057
|
return previous;
|
|
29311
30058
|
}, []).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)");
|
|
30059
|
+
const rootOpts = program2.opts();
|
|
30060
|
+
const cfg = readConfig();
|
|
30061
|
+
const { apiKey } = getConfig(rootOpts);
|
|
30062
|
+
if (!apiKey) {
|
|
30063
|
+
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
30064
|
+
process.exitCode = 1;
|
|
30065
|
+
return;
|
|
30066
|
+
}
|
|
30067
|
+
const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
|
|
30068
|
+
const title = opts.title !== undefined ? interpretEscapes2(String(opts.title)) : undefined;
|
|
30069
|
+
const description = opts.description !== undefined ? interpretEscapes2(String(opts.description)) : undefined;
|
|
30070
|
+
let status = undefined;
|
|
30071
|
+
if (opts.status !== undefined) {
|
|
30072
|
+
const raw = String(opts.status).trim().toLowerCase();
|
|
30073
|
+
if (raw === "open")
|
|
30074
|
+
status = 0;
|
|
30075
|
+
else if (raw === "closed")
|
|
30076
|
+
status = 1;
|
|
30077
|
+
else {
|
|
30078
|
+
const n = Number(raw);
|
|
30079
|
+
if (!Number.isFinite(n)) {
|
|
30080
|
+
console.error("status must be open|closed|0|1");
|
|
29342
30081
|
process.exitCode = 1;
|
|
29343
30082
|
return;
|
|
29344
30083
|
}
|
|
30084
|
+
status = n;
|
|
29345
30085
|
}
|
|
29346
|
-
|
|
29347
|
-
|
|
29348
|
-
|
|
29349
|
-
|
|
29350
|
-
labels = opts.label.map(String);
|
|
30086
|
+
if (status !== 0 && status !== 1) {
|
|
30087
|
+
console.error("status must be 0 (open) or 1 (closed)");
|
|
30088
|
+
process.exitCode = 1;
|
|
30089
|
+
return;
|
|
29351
30090
|
}
|
|
30091
|
+
}
|
|
30092
|
+
let labels = undefined;
|
|
30093
|
+
if (opts.clearLabels) {
|
|
30094
|
+
labels = [];
|
|
30095
|
+
} else if (Array.isArray(opts.label) && opts.label.length > 0) {
|
|
30096
|
+
labels = opts.label.map(String);
|
|
30097
|
+
}
|
|
30098
|
+
const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Updating issue...");
|
|
30099
|
+
try {
|
|
29352
30100
|
const result = await updateIssue2({
|
|
29353
30101
|
apiKey,
|
|
29354
30102
|
apiBaseUrl,
|
|
@@ -29359,40 +30107,217 @@ issues.command("update <issueId>").description("update an existing issue (title/
|
|
|
29359
30107
|
labels,
|
|
29360
30108
|
debug: !!opts.debug
|
|
29361
30109
|
});
|
|
30110
|
+
spinner.stop();
|
|
29362
30111
|
printResult(result, opts.json);
|
|
29363
30112
|
} catch (err) {
|
|
30113
|
+
spinner.stop();
|
|
29364
30114
|
const message = err instanceof Error ? err.message : String(err);
|
|
29365
30115
|
console.error(message);
|
|
29366
30116
|
process.exitCode = 1;
|
|
29367
30117
|
}
|
|
29368
30118
|
});
|
|
29369
30119
|
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) => {
|
|
30120
|
+
if (opts.debug) {
|
|
30121
|
+
console.log(`Debug: Original content: ${JSON.stringify(content)}`);
|
|
30122
|
+
}
|
|
30123
|
+
content = interpretEscapes2(content);
|
|
30124
|
+
if (opts.debug) {
|
|
30125
|
+
console.log(`Debug: Interpreted content: ${JSON.stringify(content)}`);
|
|
30126
|
+
}
|
|
30127
|
+
const rootOpts = program2.opts();
|
|
30128
|
+
const cfg = readConfig();
|
|
30129
|
+
const { apiKey } = getConfig(rootOpts);
|
|
30130
|
+
if (!apiKey) {
|
|
30131
|
+
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
30132
|
+
process.exitCode = 1;
|
|
30133
|
+
return;
|
|
30134
|
+
}
|
|
30135
|
+
const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Updating comment...");
|
|
29370
30136
|
try {
|
|
29371
|
-
|
|
29372
|
-
|
|
29373
|
-
|
|
29374
|
-
|
|
29375
|
-
|
|
29376
|
-
|
|
30137
|
+
const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
|
|
30138
|
+
const result = await updateIssueComment2({
|
|
30139
|
+
apiKey,
|
|
30140
|
+
apiBaseUrl,
|
|
30141
|
+
commentId,
|
|
30142
|
+
content,
|
|
30143
|
+
debug: !!opts.debug
|
|
30144
|
+
});
|
|
30145
|
+
spinner.stop();
|
|
30146
|
+
printResult(result, opts.json);
|
|
30147
|
+
} catch (err) {
|
|
30148
|
+
spinner.stop();
|
|
30149
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
30150
|
+
console.error(message);
|
|
30151
|
+
process.exitCode = 1;
|
|
30152
|
+
}
|
|
30153
|
+
});
|
|
30154
|
+
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) => {
|
|
30155
|
+
const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Fetching action items...");
|
|
30156
|
+
try {
|
|
30157
|
+
const rootOpts = program2.opts();
|
|
30158
|
+
const cfg = readConfig();
|
|
30159
|
+
const { apiKey } = getConfig(rootOpts);
|
|
30160
|
+
if (!apiKey) {
|
|
30161
|
+
spinner.stop();
|
|
30162
|
+
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
30163
|
+
process.exitCode = 1;
|
|
30164
|
+
return;
|
|
29377
30165
|
}
|
|
30166
|
+
const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
|
|
30167
|
+
const result = await fetchActionItems2({ apiKey, apiBaseUrl, issueId, debug: !!opts.debug });
|
|
30168
|
+
spinner.stop();
|
|
30169
|
+
printResult(result, opts.json);
|
|
30170
|
+
} catch (err) {
|
|
30171
|
+
spinner.stop();
|
|
30172
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
30173
|
+
console.error(message);
|
|
30174
|
+
process.exitCode = 1;
|
|
30175
|
+
}
|
|
30176
|
+
});
|
|
30177
|
+
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) => {
|
|
30178
|
+
const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Fetching action item(s)...");
|
|
30179
|
+
try {
|
|
29378
30180
|
const rootOpts = program2.opts();
|
|
29379
30181
|
const cfg = readConfig();
|
|
29380
30182
|
const { apiKey } = getConfig(rootOpts);
|
|
29381
30183
|
if (!apiKey) {
|
|
30184
|
+
spinner.stop();
|
|
29382
30185
|
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
29383
30186
|
process.exitCode = 1;
|
|
29384
30187
|
return;
|
|
29385
30188
|
}
|
|
29386
30189
|
const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
|
|
29387
|
-
const result = await
|
|
30190
|
+
const result = await fetchActionItem2({ apiKey, apiBaseUrl, actionItemIds, debug: !!opts.debug });
|
|
30191
|
+
if (result.length === 0) {
|
|
30192
|
+
spinner.stop();
|
|
30193
|
+
console.error("Action item(s) not found");
|
|
30194
|
+
process.exitCode = 1;
|
|
30195
|
+
return;
|
|
30196
|
+
}
|
|
30197
|
+
spinner.stop();
|
|
30198
|
+
printResult(result, opts.json);
|
|
30199
|
+
} catch (err) {
|
|
30200
|
+
spinner.stop();
|
|
30201
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
30202
|
+
console.error(message);
|
|
30203
|
+
process.exitCode = 1;
|
|
30204
|
+
}
|
|
30205
|
+
});
|
|
30206
|
+
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) => {
|
|
30207
|
+
try {
|
|
30208
|
+
previous.push(JSON.parse(value));
|
|
30209
|
+
} catch {
|
|
30210
|
+
console.error(`Invalid JSON for --config: ${value}`);
|
|
30211
|
+
process.exit(1);
|
|
30212
|
+
}
|
|
30213
|
+
return previous;
|
|
30214
|
+
}, []).option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (issueId, rawTitle, opts) => {
|
|
30215
|
+
const rootOpts = program2.opts();
|
|
30216
|
+
const cfg = readConfig();
|
|
30217
|
+
const { apiKey } = getConfig(rootOpts);
|
|
30218
|
+
if (!apiKey) {
|
|
30219
|
+
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
30220
|
+
process.exitCode = 1;
|
|
30221
|
+
return;
|
|
30222
|
+
}
|
|
30223
|
+
const title = interpretEscapes2(String(rawTitle || "").trim());
|
|
30224
|
+
if (!title) {
|
|
30225
|
+
console.error("title is required");
|
|
30226
|
+
process.exitCode = 1;
|
|
30227
|
+
return;
|
|
30228
|
+
}
|
|
30229
|
+
const description = opts.description !== undefined ? interpretEscapes2(String(opts.description)) : undefined;
|
|
30230
|
+
const sqlAction = opts.sqlAction;
|
|
30231
|
+
const configs = Array.isArray(opts.config) && opts.config.length > 0 ? opts.config : undefined;
|
|
30232
|
+
const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Creating action item...");
|
|
30233
|
+
try {
|
|
30234
|
+
const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
|
|
30235
|
+
const result = await createActionItem2({
|
|
29388
30236
|
apiKey,
|
|
29389
30237
|
apiBaseUrl,
|
|
29390
|
-
|
|
29391
|
-
|
|
30238
|
+
issueId,
|
|
30239
|
+
title,
|
|
30240
|
+
description,
|
|
30241
|
+
sqlAction,
|
|
30242
|
+
configs,
|
|
29392
30243
|
debug: !!opts.debug
|
|
29393
30244
|
});
|
|
29394
|
-
|
|
30245
|
+
spinner.stop();
|
|
30246
|
+
printResult({ id: result }, opts.json);
|
|
30247
|
+
} catch (err) {
|
|
30248
|
+
spinner.stop();
|
|
30249
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
30250
|
+
console.error(message);
|
|
30251
|
+
process.exitCode = 1;
|
|
30252
|
+
}
|
|
30253
|
+
});
|
|
30254
|
+
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) => {
|
|
30255
|
+
try {
|
|
30256
|
+
previous.push(JSON.parse(value));
|
|
30257
|
+
} catch {
|
|
30258
|
+
console.error(`Invalid JSON for --config: ${value}`);
|
|
30259
|
+
process.exit(1);
|
|
30260
|
+
}
|
|
30261
|
+
return previous;
|
|
30262
|
+
}, []).option("--clear-configs", "clear all config changes").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (actionItemId, opts) => {
|
|
30263
|
+
const rootOpts = program2.opts();
|
|
30264
|
+
const cfg = readConfig();
|
|
30265
|
+
const { apiKey } = getConfig(rootOpts);
|
|
30266
|
+
if (!apiKey) {
|
|
30267
|
+
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
30268
|
+
process.exitCode = 1;
|
|
30269
|
+
return;
|
|
30270
|
+
}
|
|
30271
|
+
const title = opts.title !== undefined ? interpretEscapes2(String(opts.title)) : undefined;
|
|
30272
|
+
const description = opts.description !== undefined ? interpretEscapes2(String(opts.description)) : undefined;
|
|
30273
|
+
let isDone = undefined;
|
|
30274
|
+
if (opts.done)
|
|
30275
|
+
isDone = true;
|
|
30276
|
+
else if (opts.notDone)
|
|
30277
|
+
isDone = false;
|
|
30278
|
+
let status = undefined;
|
|
30279
|
+
if (opts.status !== undefined) {
|
|
30280
|
+
const validStatuses = ["waiting_for_approval", "approved", "rejected"];
|
|
30281
|
+
if (!validStatuses.includes(opts.status)) {
|
|
30282
|
+
console.error(`status must be one of: ${validStatuses.join(", ")}`);
|
|
30283
|
+
process.exitCode = 1;
|
|
30284
|
+
return;
|
|
30285
|
+
}
|
|
30286
|
+
status = opts.status;
|
|
30287
|
+
}
|
|
30288
|
+
const statusReason = opts.statusReason;
|
|
30289
|
+
const sqlAction = opts.sqlAction;
|
|
30290
|
+
let configs = undefined;
|
|
30291
|
+
if (opts.clearConfigs) {
|
|
30292
|
+
configs = [];
|
|
30293
|
+
} else if (Array.isArray(opts.config) && opts.config.length > 0) {
|
|
30294
|
+
configs = opts.config;
|
|
30295
|
+
}
|
|
30296
|
+
if (title === undefined && description === undefined && isDone === undefined && status === undefined && statusReason === undefined && sqlAction === undefined && configs === undefined) {
|
|
30297
|
+
console.error("At least one update option is required");
|
|
30298
|
+
process.exitCode = 1;
|
|
30299
|
+
return;
|
|
30300
|
+
}
|
|
30301
|
+
const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Updating action item...");
|
|
30302
|
+
try {
|
|
30303
|
+
const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
|
|
30304
|
+
await updateActionItem2({
|
|
30305
|
+
apiKey,
|
|
30306
|
+
apiBaseUrl,
|
|
30307
|
+
actionItemId,
|
|
30308
|
+
title,
|
|
30309
|
+
description,
|
|
30310
|
+
isDone,
|
|
30311
|
+
status,
|
|
30312
|
+
statusReason,
|
|
30313
|
+
sqlAction,
|
|
30314
|
+
configs,
|
|
30315
|
+
debug: !!opts.debug
|
|
30316
|
+
});
|
|
30317
|
+
spinner.stop();
|
|
30318
|
+
printResult({ success: true }, opts.json);
|
|
29395
30319
|
} catch (err) {
|
|
30320
|
+
spinner.stop();
|
|
29396
30321
|
const message = err instanceof Error ? err.message : String(err);
|
|
29397
30322
|
console.error(message);
|
|
29398
30323
|
process.exitCode = 1;
|