postgresai 0.14.0-dev.70 → 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 +403 -95
- package/dist/bin/postgres-ai.js +1126 -158
- package/lib/init.ts +76 -19
- 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/config-consistency.test.ts +36 -0
- package/test/init.integration.test.ts +78 -70
- package/test/init.test.ts +155 -0
- 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) {
|
|
@@ -23947,6 +24614,15 @@ import { URL as URL2, fileURLToPath } from "url";
|
|
|
23947
24614
|
import * as fs3 from "fs";
|
|
23948
24615
|
import * as path3 from "path";
|
|
23949
24616
|
var DEFAULT_MONITORING_USER = "postgres_ai_mon";
|
|
24617
|
+
var KNOWN_PROVIDERS = ["self-managed", "supabase"];
|
|
24618
|
+
var SKIP_ROLE_CREATION_PROVIDERS = ["supabase"];
|
|
24619
|
+
var SKIP_ALTER_USER_PROVIDERS = ["supabase"];
|
|
24620
|
+
var SKIP_SEARCH_PATH_CHECK_PROVIDERS = ["supabase"];
|
|
24621
|
+
function validateProvider(provider) {
|
|
24622
|
+
if (!provider || KNOWN_PROVIDERS.includes(provider))
|
|
24623
|
+
return null;
|
|
24624
|
+
return `Unknown provider "${provider}". Known providers: ${KNOWN_PROVIDERS.join(", ")}. Treating as self-managed.`;
|
|
24625
|
+
}
|
|
23950
24626
|
function sslModeToConfig(mode) {
|
|
23951
24627
|
if (mode.toLowerCase() === "disable")
|
|
23952
24628
|
return false;
|
|
@@ -24261,6 +24937,7 @@ async function resolveMonitoringPassword(opts) {
|
|
|
24261
24937
|
async function buildInitPlan(params) {
|
|
24262
24938
|
const monitoringUser = params.monitoringUser || DEFAULT_MONITORING_USER;
|
|
24263
24939
|
const database = params.database;
|
|
24940
|
+
const provider = params.provider ?? "self-managed";
|
|
24264
24941
|
const qRole = quoteIdent(monitoringUser);
|
|
24265
24942
|
const qDb = quoteIdent(database);
|
|
24266
24943
|
const qPw = quoteLiteral(params.monitoringPassword);
|
|
@@ -24270,7 +24947,8 @@ async function buildInitPlan(params) {
|
|
|
24270
24947
|
ROLE_IDENT: qRole,
|
|
24271
24948
|
DB_IDENT: qDb
|
|
24272
24949
|
};
|
|
24273
|
-
|
|
24950
|
+
if (!SKIP_ROLE_CREATION_PROVIDERS.includes(provider)) {
|
|
24951
|
+
const roleStmt = `do $$ begin
|
|
24274
24952
|
if not exists (select 1 from pg_catalog.pg_roles where rolname = ${qRoleNameLit}) then
|
|
24275
24953
|
begin
|
|
24276
24954
|
create user ${qRole} with password ${qPw};
|
|
@@ -24280,11 +24958,23 @@ async function buildInitPlan(params) {
|
|
|
24280
24958
|
end if;
|
|
24281
24959
|
alter user ${qRole} with password ${qPw};
|
|
24282
24960
|
end $$;`;
|
|
24283
|
-
|
|
24284
|
-
|
|
24961
|
+
const roleSql = applyTemplate(loadSqlTemplate("01.role.sql"), { ...vars, ROLE_STMT: roleStmt });
|
|
24962
|
+
steps.push({ name: "01.role", sql: roleSql });
|
|
24963
|
+
}
|
|
24964
|
+
let permissionsSql = applyTemplate(loadSqlTemplate("02.permissions.sql"), vars);
|
|
24965
|
+
if (SKIP_ALTER_USER_PROVIDERS.includes(provider)) {
|
|
24966
|
+
permissionsSql = permissionsSql.split(`
|
|
24967
|
+
`).filter((line) => {
|
|
24968
|
+
const trimmed = line.trim();
|
|
24969
|
+
if (trimmed.startsWith("--") || trimmed === "")
|
|
24970
|
+
return true;
|
|
24971
|
+
return !/^\s*alter\s+user\s+/i.test(line);
|
|
24972
|
+
}).join(`
|
|
24973
|
+
`);
|
|
24974
|
+
}
|
|
24285
24975
|
steps.push({
|
|
24286
24976
|
name: "02.permissions",
|
|
24287
|
-
sql:
|
|
24977
|
+
sql: permissionsSql
|
|
24288
24978
|
});
|
|
24289
24979
|
steps.push({
|
|
24290
24980
|
name: "05.helpers",
|
|
@@ -24372,6 +25062,7 @@ async function verifyInitSetup(params) {
|
|
|
24372
25062
|
const missingOptional = [];
|
|
24373
25063
|
const role = params.monitoringUser;
|
|
24374
25064
|
const db = params.database;
|
|
25065
|
+
const provider = params.provider ?? "self-managed";
|
|
24375
25066
|
const roleRes = await params.client.query("select 1 from pg_catalog.pg_roles where rolname = $1", [role]);
|
|
24376
25067
|
const roleExists = (roleRes.rowCount ?? 0) > 0;
|
|
24377
25068
|
if (!roleExists) {
|
|
@@ -24407,15 +25098,17 @@ async function verifyInitSetup(params) {
|
|
|
24407
25098
|
if (!schemaUsageRes.rows?.[0]?.ok) {
|
|
24408
25099
|
missingRequired.push("USAGE on schema public");
|
|
24409
25100
|
}
|
|
24410
|
-
|
|
24411
|
-
|
|
24412
|
-
|
|
24413
|
-
|
|
24414
|
-
|
|
24415
|
-
|
|
24416
|
-
|
|
24417
|
-
|
|
24418
|
-
|
|
25101
|
+
if (!SKIP_SEARCH_PATH_CHECK_PROVIDERS.includes(provider)) {
|
|
25102
|
+
const rolcfgRes = await params.client.query("select rolconfig from pg_catalog.pg_roles where rolname = $1", [role]);
|
|
25103
|
+
const rolconfig = rolcfgRes.rows?.[0]?.rolconfig;
|
|
25104
|
+
const spLine = Array.isArray(rolconfig) ? rolconfig.find((v) => String(v).startsWith("search_path=")) : undefined;
|
|
25105
|
+
if (typeof spLine !== "string" || !spLine) {
|
|
25106
|
+
missingRequired.push("role search_path is set");
|
|
25107
|
+
} else {
|
|
25108
|
+
const sp = spLine.toLowerCase();
|
|
25109
|
+
if (!sp.includes("postgres_ai") || !sp.includes("public") || !sp.includes("pg_catalog")) {
|
|
25110
|
+
missingRequired.push("role search_path includes postgres_ai, public and pg_catalog");
|
|
25111
|
+
}
|
|
24419
25112
|
}
|
|
24420
25113
|
}
|
|
24421
25114
|
const explainFnRes = await params.client.query("select has_function_privilege($1, 'postgres_ai.explain_generic(text, text, text)', 'EXECUTE') as ok", [role]);
|
|
@@ -24619,6 +25312,38 @@ class SupabaseClient {
|
|
|
24619
25312
|
return err;
|
|
24620
25313
|
}
|
|
24621
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
|
+
}
|
|
24622
25347
|
function resolveSupabaseConfig(opts) {
|
|
24623
25348
|
const accessToken = opts.accessToken?.trim() || process.env.SUPABASE_ACCESS_TOKEN?.trim() || "";
|
|
24624
25349
|
const projectRef = opts.projectRef?.trim() || process.env.SUPABASE_PROJECT_REF?.trim() || "";
|
|
@@ -27134,7 +27859,7 @@ program2.command("set-default-project <project>").description("store default pro
|
|
|
27134
27859
|
writeConfig({ defaultProject: value });
|
|
27135
27860
|
console.log(`Default project saved: ${value}`);
|
|
27136
27861
|
});
|
|
27137
|
-
program2.command("prepare-db [conn]").description("prepare database for monitoring: create monitoring user, required view(s), and grant permissions (idempotent)").option("--db-url <url>", "PostgreSQL connection URL (admin) to run the setup against (deprecated; pass it as positional arg)").option("-h, --host <host>", "PostgreSQL host (psql-like)").option("-p, --port <port>", "PostgreSQL port (psql-like)").option("-U, --username <username>", "PostgreSQL user (psql-like)").option("-d, --dbname <dbname>", "PostgreSQL database name (psql-like)").option("--admin-password <password>", "Admin connection password (otherwise uses PGPASSWORD if set)").option("--monitoring-user <name>", "Monitoring role name to create/update", DEFAULT_MONITORING_USER).option("--password <password>", "Monitoring role password (overrides PGAI_MON_PASSWORD)").option("--skip-optional-permissions", "Skip optional permissions (RDS/self-managed extras)", false).option("--verify", "Verify that monitoring role/permissions are in place (no changes)", false).option("--reset-password", "Reset monitoring role password only (no other changes)", false).option("--print-sql", "Print SQL plan and exit (no changes applied)", false).option("--print-password", "Print generated monitoring password (DANGEROUS in CI logs)", false).option("--supabase", "Use Supabase Management API instead of direct PostgreSQL connection", false).option("--supabase-access-token <token>", "Supabase Management API access token (or SUPABASE_ACCESS_TOKEN env)").option("--supabase-project-ref <ref>", "Supabase project reference (or SUPABASE_PROJECT_REF env)").option("--json", "Output result as JSON (machine-readable)", false).addHelpText("after", [
|
|
27862
|
+
program2.command("prepare-db [conn]").description("prepare database for monitoring: create monitoring user, required view(s), and grant permissions (idempotent)").option("--db-url <url>", "PostgreSQL connection URL (admin) to run the setup against (deprecated; pass it as positional arg)").option("-h, --host <host>", "PostgreSQL host (psql-like)").option("-p, --port <port>", "PostgreSQL port (psql-like)").option("-U, --username <username>", "PostgreSQL user (psql-like)").option("-d, --dbname <dbname>", "PostgreSQL database name (psql-like)").option("--admin-password <password>", "Admin connection password (otherwise uses PGPASSWORD if set)").option("--monitoring-user <name>", "Monitoring role name to create/update", DEFAULT_MONITORING_USER).option("--password <password>", "Monitoring role password (overrides PGAI_MON_PASSWORD)").option("--skip-optional-permissions", "Skip optional permissions (RDS/self-managed extras)", false).option("--provider <provider>", "Database provider (e.g., supabase). Affects which steps are executed.").option("--verify", "Verify that monitoring role/permissions are in place (no changes)", false).option("--reset-password", "Reset monitoring role password only (no other changes)", false).option("--print-sql", "Print SQL plan and exit (no changes applied)", false).option("--print-password", "Print generated monitoring password (DANGEROUS in CI logs)", false).option("--supabase", "Use Supabase Management API instead of direct PostgreSQL connection", false).option("--supabase-access-token <token>", "Supabase Management API access token (or SUPABASE_ACCESS_TOKEN env)").option("--supabase-project-ref <ref>", "Supabase project reference (or SUPABASE_PROJECT_REF env)").option("--json", "Output result as JSON (machine-readable)", false).addHelpText("after", [
|
|
27138
27863
|
"",
|
|
27139
27864
|
"Examples:",
|
|
27140
27865
|
" postgresai prepare-db postgresql://admin@host:5432/dbname",
|
|
@@ -27177,7 +27902,11 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
|
|
|
27177
27902
|
" SUPABASE_ACCESS_TOKEN=... postgresai prepare-db --supabase --supabase-project-ref <ref>",
|
|
27178
27903
|
"",
|
|
27179
27904
|
" Generate a token at: https://supabase.com/dashboard/account/tokens",
|
|
27180
|
-
" Find your project ref in: https://supabase.com/dashboard/project/<ref>"
|
|
27905
|
+
" Find your project ref in: https://supabase.com/dashboard/project/<ref>",
|
|
27906
|
+
"",
|
|
27907
|
+
"Provider-specific behavior (for direct connections):",
|
|
27908
|
+
" --provider supabase Skip role creation (create user in Supabase dashboard)",
|
|
27909
|
+
" Skip ALTER USER (restricted by Supabase)"
|
|
27181
27910
|
].join(`
|
|
27182
27911
|
`)).action(async (conn, opts, cmd) => {
|
|
27183
27912
|
const jsonOutput = opts.json;
|
|
@@ -27216,6 +27945,10 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
|
|
|
27216
27945
|
}
|
|
27217
27946
|
const shouldPrintSql = !!opts.printSql;
|
|
27218
27947
|
const redactPasswords = (sql) => redactPasswordsInSql(sql);
|
|
27948
|
+
const providerWarning = validateProvider(opts.provider);
|
|
27949
|
+
if (providerWarning) {
|
|
27950
|
+
console.warn(`\u26A0 ${providerWarning}`);
|
|
27951
|
+
}
|
|
27219
27952
|
if (!conn && !opts.dbUrl && !opts.host && !opts.port && !opts.username && !opts.adminPassword) {
|
|
27220
27953
|
if (shouldPrintSql) {
|
|
27221
27954
|
const database = (opts.dbname ?? process.env.PGDATABASE ?? "postgres").trim();
|
|
@@ -27225,12 +27958,14 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
|
|
|
27225
27958
|
database,
|
|
27226
27959
|
monitoringUser: opts.monitoringUser,
|
|
27227
27960
|
monitoringPassword: monPassword,
|
|
27228
|
-
includeOptionalPermissions: includeOptionalPermissions2
|
|
27961
|
+
includeOptionalPermissions: includeOptionalPermissions2,
|
|
27962
|
+
provider: opts.provider
|
|
27229
27963
|
});
|
|
27230
27964
|
console.log(`
|
|
27231
27965
|
--- SQL plan (offline; not connected) ---`);
|
|
27232
27966
|
console.log(`-- database: ${database}`);
|
|
27233
27967
|
console.log(`-- monitoring user: ${opts.monitoringUser}`);
|
|
27968
|
+
console.log(`-- provider: ${opts.provider ?? "self-managed"}`);
|
|
27234
27969
|
console.log(`-- optional permissions: ${includeOptionalPermissions2 ? "enabled" : "skipped"}`);
|
|
27235
27970
|
for (const step of plan.steps) {
|
|
27236
27971
|
console.log(`
|
|
@@ -27267,6 +28002,10 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
|
|
|
27267
28002
|
console.log(`Optional permissions: ${includeOptionalPermissions2 ? "enabled" : "skipped"}`);
|
|
27268
28003
|
}
|
|
27269
28004
|
const supabaseClient = new SupabaseClient(supabaseConfig);
|
|
28005
|
+
let databaseUrl = null;
|
|
28006
|
+
if (jsonOutput) {
|
|
28007
|
+
databaseUrl = await fetchPoolerDatabaseUrl(supabaseConfig);
|
|
28008
|
+
}
|
|
27270
28009
|
try {
|
|
27271
28010
|
const database = await supabaseClient.getCurrentDatabase();
|
|
27272
28011
|
if (!database) {
|
|
@@ -27284,7 +28023,7 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
|
|
|
27284
28023
|
});
|
|
27285
28024
|
if (v.ok) {
|
|
27286
28025
|
if (jsonOutput) {
|
|
27287
|
-
|
|
28026
|
+
const result = {
|
|
27288
28027
|
success: true,
|
|
27289
28028
|
mode: "supabase",
|
|
27290
28029
|
action: "verify",
|
|
@@ -27292,7 +28031,11 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
|
|
|
27292
28031
|
monitoringUser: opts.monitoringUser,
|
|
27293
28032
|
verified: true,
|
|
27294
28033
|
missingOptional: v.missingOptional
|
|
27295
|
-
}
|
|
28034
|
+
};
|
|
28035
|
+
if (databaseUrl) {
|
|
28036
|
+
result.databaseUrl = databaseUrl;
|
|
28037
|
+
}
|
|
28038
|
+
outputJson(result);
|
|
27296
28039
|
} else {
|
|
27297
28040
|
console.log("\u2713 prepare-db verify: OK");
|
|
27298
28041
|
if (v.missingOptional.length > 0) {
|
|
@@ -27304,7 +28047,7 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
|
|
|
27304
28047
|
return;
|
|
27305
28048
|
}
|
|
27306
28049
|
if (jsonOutput) {
|
|
27307
|
-
|
|
28050
|
+
const result = {
|
|
27308
28051
|
success: false,
|
|
27309
28052
|
mode: "supabase",
|
|
27310
28053
|
action: "verify",
|
|
@@ -27313,7 +28056,11 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
|
|
|
27313
28056
|
verified: false,
|
|
27314
28057
|
missingRequired: v.missingRequired,
|
|
27315
28058
|
missingOptional: v.missingOptional
|
|
27316
|
-
}
|
|
28059
|
+
};
|
|
28060
|
+
if (databaseUrl) {
|
|
28061
|
+
result.databaseUrl = databaseUrl;
|
|
28062
|
+
}
|
|
28063
|
+
outputJson(result);
|
|
27317
28064
|
} else {
|
|
27318
28065
|
console.error("\u2717 prepare-db verify failed: missing required items");
|
|
27319
28066
|
for (const m of v.missingRequired)
|
|
@@ -27408,6 +28155,9 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
|
|
|
27408
28155
|
if (passwordGenerated) {
|
|
27409
28156
|
result.generatedPassword = monPassword;
|
|
27410
28157
|
}
|
|
28158
|
+
if (databaseUrl) {
|
|
28159
|
+
result.databaseUrl = databaseUrl;
|
|
28160
|
+
}
|
|
27411
28161
|
outputJson(result);
|
|
27412
28162
|
} else {
|
|
27413
28163
|
console.log(opts.resetPassword ? "\u2713 prepare-db password reset completed" : "\u2713 prepare-db completed");
|
|
@@ -27550,7 +28300,8 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
|
|
|
27550
28300
|
client,
|
|
27551
28301
|
database,
|
|
27552
28302
|
monitoringUser: opts.monitoringUser,
|
|
27553
|
-
includeOptionalPermissions
|
|
28303
|
+
includeOptionalPermissions,
|
|
28304
|
+
provider: opts.provider
|
|
27554
28305
|
});
|
|
27555
28306
|
if (v.ok) {
|
|
27556
28307
|
if (jsonOutput) {
|
|
@@ -27560,11 +28311,12 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
|
|
|
27560
28311
|
action: "verify",
|
|
27561
28312
|
database,
|
|
27562
28313
|
monitoringUser: opts.monitoringUser,
|
|
28314
|
+
provider: opts.provider,
|
|
27563
28315
|
verified: true,
|
|
27564
28316
|
missingOptional: v.missingOptional
|
|
27565
28317
|
});
|
|
27566
28318
|
} else {
|
|
27567
|
-
console.log(
|
|
28319
|
+
console.log(`\u2713 prepare-db verify: OK${opts.provider ? ` (provider: ${opts.provider})` : ""}`);
|
|
27568
28320
|
if (v.missingOptional.length > 0) {
|
|
27569
28321
|
console.log("\u26A0 Optional items missing:");
|
|
27570
28322
|
for (const m of v.missingOptional)
|
|
@@ -27642,9 +28394,15 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
|
|
|
27642
28394
|
database,
|
|
27643
28395
|
monitoringUser: opts.monitoringUser,
|
|
27644
28396
|
monitoringPassword: monPassword,
|
|
27645
|
-
includeOptionalPermissions
|
|
28397
|
+
includeOptionalPermissions,
|
|
28398
|
+
provider: opts.provider
|
|
27646
28399
|
});
|
|
27647
28400
|
const effectivePlan = opts.resetPassword ? { ...plan, steps: plan.steps.filter((s) => s.name === "01.role") } : plan;
|
|
28401
|
+
if (opts.resetPassword && effectivePlan.steps.length === 0) {
|
|
28402
|
+
console.error(`\u2717 --reset-password not supported for provider "${opts.provider}" (role creation is skipped)`);
|
|
28403
|
+
process.exitCode = 1;
|
|
28404
|
+
return;
|
|
28405
|
+
}
|
|
27648
28406
|
if (shouldPrintSql) {
|
|
27649
28407
|
console.log(`
|
|
27650
28408
|
--- SQL plan ---`);
|
|
@@ -29131,18 +29889,36 @@ function interpretEscapes2(str2) {
|
|
|
29131
29889
|
`).replace(/\\t/g, "\t").replace(/\\r/g, "\r").replace(/\\"/g, '"').replace(/\\'/g, "'").replace(/\x00/g, "\\");
|
|
29132
29890
|
}
|
|
29133
29891
|
var issues = program2.command("issues").description("issues management");
|
|
29134
|
-
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...");
|
|
29135
29894
|
try {
|
|
29136
29895
|
const rootOpts = program2.opts();
|
|
29137
29896
|
const cfg = readConfig();
|
|
29138
29897
|
const { apiKey } = getConfig(rootOpts);
|
|
29139
29898
|
if (!apiKey) {
|
|
29899
|
+
spinner.stop();
|
|
29140
29900
|
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
29141
29901
|
process.exitCode = 1;
|
|
29142
29902
|
return;
|
|
29143
29903
|
}
|
|
29904
|
+
const orgId = cfg.orgId ?? undefined;
|
|
29144
29905
|
const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
|
|
29145
|
-
|
|
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();
|
|
29146
29922
|
const trimmed = Array.isArray(result) ? result.map((r) => ({
|
|
29147
29923
|
id: r.id,
|
|
29148
29924
|
title: r.title,
|
|
@@ -29151,17 +29927,20 @@ issues.command("list").description("list issues").option("--debug", "enable debu
|
|
|
29151
29927
|
})) : result;
|
|
29152
29928
|
printResult(trimmed, opts.json);
|
|
29153
29929
|
} catch (err) {
|
|
29930
|
+
spinner.stop();
|
|
29154
29931
|
const message = err instanceof Error ? err.message : String(err);
|
|
29155
29932
|
console.error(message);
|
|
29156
29933
|
process.exitCode = 1;
|
|
29157
29934
|
}
|
|
29158
29935
|
});
|
|
29159
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...");
|
|
29160
29938
|
try {
|
|
29161
29939
|
const rootOpts = program2.opts();
|
|
29162
29940
|
const cfg = readConfig();
|
|
29163
29941
|
const { apiKey } = getConfig(rootOpts);
|
|
29164
29942
|
if (!apiKey) {
|
|
29943
|
+
spinner.stop();
|
|
29165
29944
|
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
29166
29945
|
process.exitCode = 1;
|
|
29167
29946
|
return;
|
|
@@ -29169,32 +29948,38 @@ issues.command("view <issueId>").description("view issue details and comments").
|
|
|
29169
29948
|
const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
|
|
29170
29949
|
const issue2 = await fetchIssue2({ apiKey, apiBaseUrl, issueId, debug: !!opts.debug });
|
|
29171
29950
|
if (!issue2) {
|
|
29951
|
+
spinner.stop();
|
|
29172
29952
|
console.error("Issue not found");
|
|
29173
29953
|
process.exitCode = 1;
|
|
29174
29954
|
return;
|
|
29175
29955
|
}
|
|
29956
|
+
spinner.update("Fetching comments...");
|
|
29176
29957
|
const comments = await fetchIssueComments2({ apiKey, apiBaseUrl, issueId, debug: !!opts.debug });
|
|
29958
|
+
spinner.stop();
|
|
29177
29959
|
const combined = { issue: issue2, comments };
|
|
29178
29960
|
printResult(combined, opts.json);
|
|
29179
29961
|
} catch (err) {
|
|
29962
|
+
spinner.stop();
|
|
29180
29963
|
const message = err instanceof Error ? err.message : String(err);
|
|
29181
29964
|
console.error(message);
|
|
29182
29965
|
process.exitCode = 1;
|
|
29183
29966
|
}
|
|
29184
29967
|
});
|
|
29185
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...");
|
|
29186
29977
|
try {
|
|
29187
|
-
if (opts.debug) {
|
|
29188
|
-
console.log(`Debug: Original content: ${JSON.stringify(content)}`);
|
|
29189
|
-
}
|
|
29190
|
-
content = interpretEscapes2(content);
|
|
29191
|
-
if (opts.debug) {
|
|
29192
|
-
console.log(`Debug: Interpreted content: ${JSON.stringify(content)}`);
|
|
29193
|
-
}
|
|
29194
29978
|
const rootOpts = program2.opts();
|
|
29195
29979
|
const cfg = readConfig();
|
|
29196
29980
|
const { apiKey } = getConfig(rootOpts);
|
|
29197
29981
|
if (!apiKey) {
|
|
29982
|
+
spinner.stop();
|
|
29198
29983
|
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
29199
29984
|
process.exitCode = 1;
|
|
29200
29985
|
return;
|
|
@@ -29208,41 +29993,44 @@ issues.command("post-comment <issueId> <content>").description("post a new comme
|
|
|
29208
29993
|
parentCommentId: opts.parent,
|
|
29209
29994
|
debug: !!opts.debug
|
|
29210
29995
|
});
|
|
29996
|
+
spinner.stop();
|
|
29211
29997
|
printResult(result, opts.json);
|
|
29212
29998
|
} catch (err) {
|
|
29999
|
+
spinner.stop();
|
|
29213
30000
|
const message = err instanceof Error ? err.message : String(err);
|
|
29214
30001
|
console.error(message);
|
|
29215
30002
|
process.exitCode = 1;
|
|
29216
30003
|
}
|
|
29217
30004
|
});
|
|
29218
|
-
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) => {
|
|
29219
30006
|
previous.push(value);
|
|
29220
30007
|
return previous;
|
|
29221
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...");
|
|
29222
30033
|
try {
|
|
29223
|
-
const rootOpts = program2.opts();
|
|
29224
|
-
const cfg = readConfig();
|
|
29225
|
-
const { apiKey } = getConfig(rootOpts);
|
|
29226
|
-
if (!apiKey) {
|
|
29227
|
-
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
29228
|
-
process.exitCode = 1;
|
|
29229
|
-
return;
|
|
29230
|
-
}
|
|
29231
|
-
const title = interpretEscapes2(String(rawTitle || "").trim());
|
|
29232
|
-
if (!title) {
|
|
29233
|
-
console.error("title is required");
|
|
29234
|
-
process.exitCode = 1;
|
|
29235
|
-
return;
|
|
29236
|
-
}
|
|
29237
|
-
const orgId = typeof opts.orgId === "number" && !Number.isNaN(opts.orgId) ? opts.orgId : cfg.orgId;
|
|
29238
|
-
if (typeof orgId !== "number") {
|
|
29239
|
-
console.error("org_id is required. Either pass --org-id or run 'pgai auth' to store it in config.");
|
|
29240
|
-
process.exitCode = 1;
|
|
29241
|
-
return;
|
|
29242
|
-
}
|
|
29243
|
-
const description = opts.description !== undefined ? interpretEscapes2(String(opts.description)) : undefined;
|
|
29244
|
-
const labels = Array.isArray(opts.label) && opts.label.length > 0 ? opts.label.map(String) : undefined;
|
|
29245
|
-
const projectId = typeof opts.projectId === "number" && !Number.isNaN(opts.projectId) ? opts.projectId : undefined;
|
|
29246
30034
|
const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
|
|
29247
30035
|
const result = await createIssue2({
|
|
29248
30036
|
apiKey,
|
|
@@ -29254,57 +30042,60 @@ issues.command("create <title>").description("create a new issue").option("--org
|
|
|
29254
30042
|
labels,
|
|
29255
30043
|
debug: !!opts.debug
|
|
29256
30044
|
});
|
|
30045
|
+
spinner.stop();
|
|
29257
30046
|
printResult(result, opts.json);
|
|
29258
30047
|
} catch (err) {
|
|
30048
|
+
spinner.stop();
|
|
29259
30049
|
const message = err instanceof Error ? err.message : String(err);
|
|
29260
30050
|
console.error(message);
|
|
29261
30051
|
process.exitCode = 1;
|
|
29262
30052
|
}
|
|
29263
30053
|
});
|
|
29264
|
-
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) => {
|
|
29265
30055
|
previous.push(value);
|
|
29266
30056
|
return previous;
|
|
29267
30057
|
}, []).option("--clear-labels", "set labels to an empty list").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (issueId, opts) => {
|
|
29268
|
-
|
|
29269
|
-
|
|
29270
|
-
|
|
29271
|
-
|
|
29272
|
-
|
|
29273
|
-
|
|
29274
|
-
|
|
29275
|
-
|
|
29276
|
-
|
|
29277
|
-
|
|
29278
|
-
|
|
29279
|
-
|
|
29280
|
-
|
|
29281
|
-
|
|
29282
|
-
|
|
29283
|
-
|
|
29284
|
-
|
|
29285
|
-
|
|
29286
|
-
|
|
29287
|
-
|
|
29288
|
-
|
|
29289
|
-
|
|
29290
|
-
console.error("status must be open|closed|0|1");
|
|
29291
|
-
process.exitCode = 1;
|
|
29292
|
-
return;
|
|
29293
|
-
}
|
|
29294
|
-
status = n;
|
|
29295
|
-
}
|
|
29296
|
-
if (status !== 0 && status !== 1) {
|
|
29297
|
-
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");
|
|
29298
30080
|
process.exitCode = 1;
|
|
29299
30081
|
return;
|
|
29300
30082
|
}
|
|
30083
|
+
status = n;
|
|
29301
30084
|
}
|
|
29302
|
-
|
|
29303
|
-
|
|
29304
|
-
|
|
29305
|
-
|
|
29306
|
-
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;
|
|
29307
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 {
|
|
29308
30099
|
const result = await updateIssue2({
|
|
29309
30100
|
apiKey,
|
|
29310
30101
|
apiBaseUrl,
|
|
@@ -29315,40 +30106,217 @@ issues.command("update <issueId>").description("update an existing issue (title/
|
|
|
29315
30106
|
labels,
|
|
29316
30107
|
debug: !!opts.debug
|
|
29317
30108
|
});
|
|
30109
|
+
spinner.stop();
|
|
29318
30110
|
printResult(result, opts.json);
|
|
29319
30111
|
} catch (err) {
|
|
30112
|
+
spinner.stop();
|
|
29320
30113
|
const message = err instanceof Error ? err.message : String(err);
|
|
29321
30114
|
console.error(message);
|
|
29322
30115
|
process.exitCode = 1;
|
|
29323
30116
|
}
|
|
29324
30117
|
});
|
|
29325
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...");
|
|
29326
30135
|
try {
|
|
29327
|
-
|
|
29328
|
-
|
|
29329
|
-
|
|
29330
|
-
|
|
29331
|
-
|
|
29332
|
-
|
|
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;
|
|
29333
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 {
|
|
29334
30179
|
const rootOpts = program2.opts();
|
|
29335
30180
|
const cfg = readConfig();
|
|
29336
30181
|
const { apiKey } = getConfig(rootOpts);
|
|
29337
30182
|
if (!apiKey) {
|
|
30183
|
+
spinner.stop();
|
|
29338
30184
|
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
29339
30185
|
process.exitCode = 1;
|
|
29340
30186
|
return;
|
|
29341
30187
|
}
|
|
29342
30188
|
const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
|
|
29343
|
-
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({
|
|
29344
30235
|
apiKey,
|
|
29345
30236
|
apiBaseUrl,
|
|
29346
|
-
|
|
29347
|
-
|
|
30237
|
+
issueId,
|
|
30238
|
+
title,
|
|
30239
|
+
description,
|
|
30240
|
+
sqlAction,
|
|
30241
|
+
configs,
|
|
29348
30242
|
debug: !!opts.debug
|
|
29349
30243
|
});
|
|
29350
|
-
|
|
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);
|
|
29351
30318
|
} catch (err) {
|
|
30319
|
+
spinner.stop();
|
|
29352
30320
|
const message = err instanceof Error ? err.message : String(err);
|
|
29353
30321
|
console.error(message);
|
|
29354
30322
|
process.exitCode = 1;
|