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.
@@ -13064,7 +13064,7 @@ var {
13064
13064
  // package.json
13065
13065
  var package_default = {
13066
13066
  name: "postgresai",
13067
- version: "0.14.0-dev.70",
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.70";
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
- if (Array.isArray(parsed)) {
16202
- return parsed[0] ?? null;
16203
- } else {
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 issues = await fetchIssues({ apiKey, apiBaseUrl, debug });
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({ name: "postgresai-mcp", version: package_default2.version }, { capabilities: { tools: {} } });
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
- server.setRequestHandler(CallToolRequestSchema, async (req) => {
23543
- return handleToolCall(req, rootOpts, extra);
23544
- });
23545
- const transport = new StdioServerTransport;
23546
- await server.connect(transport);
23547
- }
23548
-
23549
- // lib/issues.ts
23550
- async function fetchIssues2(params) {
23551
- const { apiKey, apiBaseUrl, debug } = params;
23552
- if (!apiKey) {
23553
- throw new Error("API key is required");
23554
- }
23555
- const base = normalizeBaseUrl(apiBaseUrl);
23556
- const url = new URL(`${base}/issues`);
23557
- url.searchParams.set("select", "id,title,status,created_at");
23558
- const headers = {
23559
- "access-token": apiKey,
23560
- Prefer: "return=representation",
23561
- "Content-Type": "application/json",
23562
- Connection: "close"
23563
- };
23564
- if (debug) {
23565
- const debugHeaders = { ...headers, "access-token": maskSecret(apiKey) };
23566
- console.log(`Debug: Resolved API base URL: ${base}`);
23567
- console.log(`Debug: GET URL: ${url.toString()}`);
23568
- console.log(`Debug: Auth scheme: access-token`);
23569
- console.log(`Debug: Request headers: ${JSON.stringify(debugHeaders)}`);
23570
- }
23571
- const response = await fetch(url.toString(), {
23572
- method: "GET",
23573
- headers
23574
- });
23575
- if (debug) {
23576
- console.log(`Debug: Response status: ${response.status}`);
23577
- console.log(`Debug: Response headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()))}`);
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
- if (Array.isArray(parsed)) {
23671
- return parsed[0] ?? null;
23672
- } else {
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
- const roleStmt = `do $$ begin
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
- const roleSql = applyTemplate(loadSqlTemplate("01.role.sql"), { ...vars, ROLE_STMT: roleStmt });
24284
- steps.push({ name: "01.role", sql: roleSql });
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: applyTemplate(loadSqlTemplate("02.permissions.sql"), vars)
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
- const rolcfgRes = await params.client.query("select rolconfig from pg_catalog.pg_roles where rolname = $1", [role]);
24411
- const rolconfig = rolcfgRes.rows?.[0]?.rolconfig;
24412
- const spLine = Array.isArray(rolconfig) ? rolconfig.find((v) => String(v).startsWith("search_path=")) : undefined;
24413
- if (typeof spLine !== "string" || !spLine) {
24414
- missingRequired.push("role search_path is set");
24415
- } else {
24416
- const sp = spLine.toLowerCase();
24417
- if (!sp.includes("postgres_ai") || !sp.includes("public") || !sp.includes("pg_catalog")) {
24418
- missingRequired.push("role search_path includes postgres_ai, public and pg_catalog");
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
- outputJson({
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
- outputJson({
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("\u2713 prepare-db verify: OK");
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
- const result = await fetchIssues2({ apiKey, apiBaseUrl, debug: !!opts.debug });
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 (supports \\\\n)").option("--label <label>", "issue label (repeatable)", (value, previous) => {
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 (supports \\\\n)").option("--description <text>", "new description (supports \\\\n)").option("--status <value>", "status: open|closed|0|1").option("--label <label>", "set labels (repeatable). If provided, replaces existing labels.", (value, previous) => {
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
- try {
29269
- const rootOpts = program2.opts();
29270
- const cfg = readConfig();
29271
- const { apiKey } = getConfig(rootOpts);
29272
- if (!apiKey) {
29273
- console.error("API key is required. Run 'pgai auth' first or set --api-key.");
29274
- process.exitCode = 1;
29275
- return;
29276
- }
29277
- const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
29278
- const title = opts.title !== undefined ? interpretEscapes2(String(opts.title)) : undefined;
29279
- const description = opts.description !== undefined ? interpretEscapes2(String(opts.description)) : undefined;
29280
- let status = undefined;
29281
- if (opts.status !== undefined) {
29282
- const raw = String(opts.status).trim().toLowerCase();
29283
- if (raw === "open")
29284
- status = 0;
29285
- else if (raw === "closed")
29286
- status = 1;
29287
- else {
29288
- const n = Number(raw);
29289
- if (!Number.isFinite(n)) {
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
- let labels = undefined;
29303
- if (opts.clearLabels) {
29304
- labels = [];
29305
- } else if (Array.isArray(opts.label) && opts.label.length > 0) {
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
- if (opts.debug) {
29328
- console.log(`Debug: Original content: ${JSON.stringify(content)}`);
29329
- }
29330
- content = interpretEscapes2(content);
29331
- if (opts.debug) {
29332
- console.log(`Debug: Interpreted content: ${JSON.stringify(content)}`);
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 updateIssueComment2({
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
- commentId,
29347
- content,
30237
+ issueId,
30238
+ title,
30239
+ description,
30240
+ sqlAction,
30241
+ configs,
29348
30242
  debug: !!opts.debug
29349
30243
  });
29350
- printResult(result, opts.json);
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;