postgresai 0.14.0-dev.71 → 0.14.0-dev.73

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13064,7 +13064,7 @@ var {
13064
13064
  // package.json
13065
13065
  var package_default = {
13066
13066
  name: "postgresai",
13067
- version: "0.14.0-dev.71",
13067
+ version: "0.14.0-dev.73",
13068
13068
  description: "postgres_ai CLI",
13069
13069
  license: "Apache-2.0",
13070
13070
  private: false,
@@ -15887,7 +15887,7 @@ var Result = import_lib.default.Result;
15887
15887
  var TypeOverrides = import_lib.default.TypeOverrides;
15888
15888
  var defaults = import_lib.default.defaults;
15889
15889
  // package.json
15890
- var version = "0.14.0-dev.71";
15890
+ var version = "0.14.0-dev.73";
15891
15891
  var package_default2 = {
15892
15892
  name: "postgresai",
15893
15893
  version,
@@ -16079,13 +16079,24 @@ function resolveBaseUrls(opts, cfg, defaults2 = {}) {
16079
16079
 
16080
16080
  // lib/issues.ts
16081
16081
  async function fetchIssues(params) {
16082
- const { apiKey, apiBaseUrl, debug } = params;
16082
+ const { apiKey, apiBaseUrl, orgId, status, limit = 20, offset = 0, debug } = params;
16083
16083
  if (!apiKey) {
16084
16084
  throw new Error("API key is required");
16085
16085
  }
16086
16086
  const base = normalizeBaseUrl(apiBaseUrl);
16087
16087
  const url = new URL(`${base}/issues`);
16088
16088
  url.searchParams.set("select", "id,title,status,created_at");
16089
+ url.searchParams.set("order", "id.desc");
16090
+ url.searchParams.set("limit", String(limit));
16091
+ url.searchParams.set("offset", String(offset));
16092
+ if (typeof orgId === "number") {
16093
+ url.searchParams.set("org_id", `eq.${orgId}`);
16094
+ }
16095
+ if (status === "open") {
16096
+ url.searchParams.set("status", "eq.0");
16097
+ } else if (status === "closed") {
16098
+ url.searchParams.set("status", "eq.1");
16099
+ }
16089
16100
  const headers = {
16090
16101
  "access-token": apiKey,
16091
16102
  Prefer: "return=representation",
@@ -16170,7 +16181,7 @@ async function fetchIssue(params) {
16170
16181
  }
16171
16182
  const base = normalizeBaseUrl(apiBaseUrl);
16172
16183
  const url = new URL(`${base}/issues`);
16173
- url.searchParams.set("select", "id,title,description,status,created_at,author_display_name");
16184
+ url.searchParams.set("select", "id,title,description,status,created_at,author_display_name,action_items");
16174
16185
  url.searchParams.set("id", `eq.${issueId}`);
16175
16186
  url.searchParams.set("limit", "1");
16176
16187
  const headers = {
@@ -16198,11 +16209,20 @@ async function fetchIssue(params) {
16198
16209
  if (response.ok) {
16199
16210
  try {
16200
16211
  const parsed = JSON.parse(data);
16201
- 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) {
@@ -24645,6 +25312,39 @@ class SupabaseClient {
24645
25312
  return err;
24646
25313
  }
24647
25314
  }
25315
+ async function fetchPoolerDatabaseUrl(config2, username) {
25316
+ const url = `${SUPABASE_API_BASE}/v1/projects/${encodeURIComponent(config2.projectRef)}/config/database/pooler`;
25317
+ try {
25318
+ const response = await fetch(url, {
25319
+ method: "GET",
25320
+ headers: {
25321
+ Authorization: `Bearer ${config2.accessToken}`
25322
+ }
25323
+ });
25324
+ if (!response.ok) {
25325
+ return null;
25326
+ }
25327
+ const data = await response.json();
25328
+ if (Array.isArray(data) && data.length > 0) {
25329
+ const pooler = data[0];
25330
+ if (pooler.db_host && pooler.db_port && pooler.db_name) {
25331
+ return `postgresql://${username}@${pooler.db_host}:${pooler.db_port}/${pooler.db_name}`;
25332
+ }
25333
+ if (typeof pooler.connection_string === "string") {
25334
+ try {
25335
+ const connUrl = new URL(pooler.connection_string);
25336
+ const portPart = connUrl.port ? `:${connUrl.port}` : "";
25337
+ return `postgresql://${username}@${connUrl.hostname}${portPart}${connUrl.pathname}`;
25338
+ } catch {
25339
+ return null;
25340
+ }
25341
+ }
25342
+ }
25343
+ return null;
25344
+ } catch {
25345
+ return null;
25346
+ }
25347
+ }
24648
25348
  function resolveSupabaseConfig(opts) {
24649
25349
  const accessToken = opts.accessToken?.trim() || process.env.SUPABASE_ACCESS_TOKEN?.trim() || "";
24650
25350
  const projectRef = opts.projectRef?.trim() || process.env.SUPABASE_PROJECT_REF?.trim() || "";
@@ -27303,6 +28003,10 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
27303
28003
  console.log(`Optional permissions: ${includeOptionalPermissions2 ? "enabled" : "skipped"}`);
27304
28004
  }
27305
28005
  const supabaseClient = new SupabaseClient(supabaseConfig);
28006
+ let databaseUrl = null;
28007
+ if (jsonOutput) {
28008
+ databaseUrl = await fetchPoolerDatabaseUrl(supabaseConfig, opts.monitoringUser);
28009
+ }
27306
28010
  try {
27307
28011
  const database = await supabaseClient.getCurrentDatabase();
27308
28012
  if (!database) {
@@ -27320,7 +28024,7 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
27320
28024
  });
27321
28025
  if (v.ok) {
27322
28026
  if (jsonOutput) {
27323
- outputJson({
28027
+ const result = {
27324
28028
  success: true,
27325
28029
  mode: "supabase",
27326
28030
  action: "verify",
@@ -27328,7 +28032,11 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
27328
28032
  monitoringUser: opts.monitoringUser,
27329
28033
  verified: true,
27330
28034
  missingOptional: v.missingOptional
27331
- });
28035
+ };
28036
+ if (databaseUrl) {
28037
+ result.databaseUrl = databaseUrl;
28038
+ }
28039
+ outputJson(result);
27332
28040
  } else {
27333
28041
  console.log("\u2713 prepare-db verify: OK");
27334
28042
  if (v.missingOptional.length > 0) {
@@ -27340,7 +28048,7 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
27340
28048
  return;
27341
28049
  }
27342
28050
  if (jsonOutput) {
27343
- outputJson({
28051
+ const result = {
27344
28052
  success: false,
27345
28053
  mode: "supabase",
27346
28054
  action: "verify",
@@ -27349,7 +28057,11 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
27349
28057
  verified: false,
27350
28058
  missingRequired: v.missingRequired,
27351
28059
  missingOptional: v.missingOptional
27352
- });
28060
+ };
28061
+ if (databaseUrl) {
28062
+ result.databaseUrl = databaseUrl;
28063
+ }
28064
+ outputJson(result);
27353
28065
  } else {
27354
28066
  console.error("\u2717 prepare-db verify failed: missing required items");
27355
28067
  for (const m of v.missingRequired)
@@ -27444,6 +28156,9 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
27444
28156
  if (passwordGenerated) {
27445
28157
  result.generatedPassword = monPassword;
27446
28158
  }
28159
+ if (databaseUrl) {
28160
+ result.databaseUrl = databaseUrl;
28161
+ }
27447
28162
  outputJson(result);
27448
28163
  } else {
27449
28164
  console.log(opts.resetPassword ? "\u2713 prepare-db password reset completed" : "\u2713 prepare-db completed");
@@ -29175,18 +29890,36 @@ function interpretEscapes2(str2) {
29175
29890
  `).replace(/\\t/g, "\t").replace(/\\r/g, "\r").replace(/\\"/g, '"').replace(/\\'/g, "'").replace(/\x00/g, "\\");
29176
29891
  }
29177
29892
  var issues = program2.command("issues").description("issues management");
29178
- issues.command("list").description("list issues").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (opts) => {
29893
+ issues.command("list").description("list issues").option("--status <status>", "filter by status: open, closed, or all (default: all)").option("--limit <n>", "max number of issues to return (default: 20)", parseInt).option("--offset <n>", "number of issues to skip (default: 0)", parseInt).option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (opts) => {
29894
+ const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Fetching issues...");
29179
29895
  try {
29180
29896
  const rootOpts = program2.opts();
29181
29897
  const cfg = readConfig();
29182
29898
  const { apiKey } = getConfig(rootOpts);
29183
29899
  if (!apiKey) {
29900
+ spinner.stop();
29184
29901
  console.error("API key is required. Run 'pgai auth' first or set --api-key.");
29185
29902
  process.exitCode = 1;
29186
29903
  return;
29187
29904
  }
29905
+ const orgId = cfg.orgId ?? undefined;
29188
29906
  const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
29189
- const result = await fetchIssues2({ apiKey, apiBaseUrl, debug: !!opts.debug });
29907
+ let statusFilter;
29908
+ if (opts.status === "open") {
29909
+ statusFilter = "open";
29910
+ } else if (opts.status === "closed") {
29911
+ statusFilter = "closed";
29912
+ }
29913
+ const result = await fetchIssues2({
29914
+ apiKey,
29915
+ apiBaseUrl,
29916
+ orgId,
29917
+ status: statusFilter,
29918
+ limit: opts.limit,
29919
+ offset: opts.offset,
29920
+ debug: !!opts.debug
29921
+ });
29922
+ spinner.stop();
29190
29923
  const trimmed = Array.isArray(result) ? result.map((r) => ({
29191
29924
  id: r.id,
29192
29925
  title: r.title,
@@ -29195,17 +29928,20 @@ issues.command("list").description("list issues").option("--debug", "enable debu
29195
29928
  })) : result;
29196
29929
  printResult(trimmed, opts.json);
29197
29930
  } catch (err) {
29931
+ spinner.stop();
29198
29932
  const message = err instanceof Error ? err.message : String(err);
29199
29933
  console.error(message);
29200
29934
  process.exitCode = 1;
29201
29935
  }
29202
29936
  });
29203
29937
  issues.command("view <issueId>").description("view issue details and comments").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (issueId, opts) => {
29938
+ const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Fetching issue...");
29204
29939
  try {
29205
29940
  const rootOpts = program2.opts();
29206
29941
  const cfg = readConfig();
29207
29942
  const { apiKey } = getConfig(rootOpts);
29208
29943
  if (!apiKey) {
29944
+ spinner.stop();
29209
29945
  console.error("API key is required. Run 'pgai auth' first or set --api-key.");
29210
29946
  process.exitCode = 1;
29211
29947
  return;
@@ -29213,32 +29949,38 @@ issues.command("view <issueId>").description("view issue details and comments").
29213
29949
  const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
29214
29950
  const issue2 = await fetchIssue2({ apiKey, apiBaseUrl, issueId, debug: !!opts.debug });
29215
29951
  if (!issue2) {
29952
+ spinner.stop();
29216
29953
  console.error("Issue not found");
29217
29954
  process.exitCode = 1;
29218
29955
  return;
29219
29956
  }
29957
+ spinner.update("Fetching comments...");
29220
29958
  const comments = await fetchIssueComments2({ apiKey, apiBaseUrl, issueId, debug: !!opts.debug });
29959
+ spinner.stop();
29221
29960
  const combined = { issue: issue2, comments };
29222
29961
  printResult(combined, opts.json);
29223
29962
  } catch (err) {
29963
+ spinner.stop();
29224
29964
  const message = err instanceof Error ? err.message : String(err);
29225
29965
  console.error(message);
29226
29966
  process.exitCode = 1;
29227
29967
  }
29228
29968
  });
29229
29969
  issues.command("post-comment <issueId> <content>").description("post a new comment to an issue").option("--parent <uuid>", "parent comment id").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (issueId, content, opts) => {
29970
+ if (opts.debug) {
29971
+ console.log(`Debug: Original content: ${JSON.stringify(content)}`);
29972
+ }
29973
+ content = interpretEscapes2(content);
29974
+ if (opts.debug) {
29975
+ console.log(`Debug: Interpreted content: ${JSON.stringify(content)}`);
29976
+ }
29977
+ const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Posting comment...");
29230
29978
  try {
29231
- if (opts.debug) {
29232
- console.log(`Debug: Original content: ${JSON.stringify(content)}`);
29233
- }
29234
- content = interpretEscapes2(content);
29235
- if (opts.debug) {
29236
- console.log(`Debug: Interpreted content: ${JSON.stringify(content)}`);
29237
- }
29238
29979
  const rootOpts = program2.opts();
29239
29980
  const cfg = readConfig();
29240
29981
  const { apiKey } = getConfig(rootOpts);
29241
29982
  if (!apiKey) {
29983
+ spinner.stop();
29242
29984
  console.error("API key is required. Run 'pgai auth' first or set --api-key.");
29243
29985
  process.exitCode = 1;
29244
29986
  return;
@@ -29252,41 +29994,44 @@ issues.command("post-comment <issueId> <content>").description("post a new comme
29252
29994
  parentCommentId: opts.parent,
29253
29995
  debug: !!opts.debug
29254
29996
  });
29997
+ spinner.stop();
29255
29998
  printResult(result, opts.json);
29256
29999
  } catch (err) {
30000
+ spinner.stop();
29257
30001
  const message = err instanceof Error ? err.message : String(err);
29258
30002
  console.error(message);
29259
30003
  process.exitCode = 1;
29260
30004
  }
29261
30005
  });
29262
- issues.command("create <title>").description("create a new issue").option("--org-id <id>", "organization id (defaults to config orgId)", (v) => parseInt(v, 10)).option("--project-id <id>", "project id", (v) => parseInt(v, 10)).option("--description <text>", "issue description (supports \\\\n)").option("--label <label>", "issue label (repeatable)", (value, previous) => {
30006
+ issues.command("create <title>").description("create a new issue").option("--org-id <id>", "organization id (defaults to config orgId)", (v) => parseInt(v, 10)).option("--project-id <id>", "project id", (v) => parseInt(v, 10)).option("--description <text>", "issue description (use \\n for newlines)").option("--label <label>", "issue label (repeatable)", (value, previous) => {
29263
30007
  previous.push(value);
29264
30008
  return previous;
29265
30009
  }, []).option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (rawTitle, opts) => {
30010
+ const rootOpts = program2.opts();
30011
+ const cfg = readConfig();
30012
+ const { apiKey } = getConfig(rootOpts);
30013
+ if (!apiKey) {
30014
+ console.error("API key is required. Run 'pgai auth' first or set --api-key.");
30015
+ process.exitCode = 1;
30016
+ return;
30017
+ }
30018
+ const title = interpretEscapes2(String(rawTitle || "").trim());
30019
+ if (!title) {
30020
+ console.error("title is required");
30021
+ process.exitCode = 1;
30022
+ return;
30023
+ }
30024
+ const orgId = typeof opts.orgId === "number" && !Number.isNaN(opts.orgId) ? opts.orgId : cfg.orgId;
30025
+ if (typeof orgId !== "number") {
30026
+ console.error("org_id is required. Either pass --org-id or run 'pgai auth' to store it in config.");
30027
+ process.exitCode = 1;
30028
+ return;
30029
+ }
30030
+ const description = opts.description !== undefined ? interpretEscapes2(String(opts.description)) : undefined;
30031
+ const labels = Array.isArray(opts.label) && opts.label.length > 0 ? opts.label.map(String) : undefined;
30032
+ const projectId = typeof opts.projectId === "number" && !Number.isNaN(opts.projectId) ? opts.projectId : undefined;
30033
+ const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Creating issue...");
29266
30034
  try {
29267
- const rootOpts = program2.opts();
29268
- const cfg = readConfig();
29269
- const { apiKey } = getConfig(rootOpts);
29270
- if (!apiKey) {
29271
- console.error("API key is required. Run 'pgai auth' first or set --api-key.");
29272
- process.exitCode = 1;
29273
- return;
29274
- }
29275
- const title = interpretEscapes2(String(rawTitle || "").trim());
29276
- if (!title) {
29277
- console.error("title is required");
29278
- process.exitCode = 1;
29279
- return;
29280
- }
29281
- const orgId = typeof opts.orgId === "number" && !Number.isNaN(opts.orgId) ? opts.orgId : cfg.orgId;
29282
- if (typeof orgId !== "number") {
29283
- console.error("org_id is required. Either pass --org-id or run 'pgai auth' to store it in config.");
29284
- process.exitCode = 1;
29285
- return;
29286
- }
29287
- const description = opts.description !== undefined ? interpretEscapes2(String(opts.description)) : undefined;
29288
- const labels = Array.isArray(opts.label) && opts.label.length > 0 ? opts.label.map(String) : undefined;
29289
- const projectId = typeof opts.projectId === "number" && !Number.isNaN(opts.projectId) ? opts.projectId : undefined;
29290
30035
  const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
29291
30036
  const result = await createIssue2({
29292
30037
  apiKey,
@@ -29298,57 +30043,60 @@ issues.command("create <title>").description("create a new issue").option("--org
29298
30043
  labels,
29299
30044
  debug: !!opts.debug
29300
30045
  });
30046
+ spinner.stop();
29301
30047
  printResult(result, opts.json);
29302
30048
  } catch (err) {
30049
+ spinner.stop();
29303
30050
  const message = err instanceof Error ? err.message : String(err);
29304
30051
  console.error(message);
29305
30052
  process.exitCode = 1;
29306
30053
  }
29307
30054
  });
29308
- issues.command("update <issueId>").description("update an existing issue (title/description/status/labels)").option("--title <text>", "new title (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) => {
30055
+ issues.command("update <issueId>").description("update an existing issue (title/description/status/labels)").option("--title <text>", "new title (use \\n for newlines)").option("--description <text>", "new description (use \\n for newlines)").option("--status <value>", "status: open|closed|0|1").option("--label <label>", "set labels (repeatable). If provided, replaces existing labels.", (value, previous) => {
29309
30056
  previous.push(value);
29310
30057
  return previous;
29311
30058
  }, []).option("--clear-labels", "set labels to an empty list").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (issueId, opts) => {
29312
- try {
29313
- const rootOpts = program2.opts();
29314
- const cfg = readConfig();
29315
- const { apiKey } = getConfig(rootOpts);
29316
- if (!apiKey) {
29317
- console.error("API key is required. Run 'pgai auth' first or set --api-key.");
29318
- process.exitCode = 1;
29319
- return;
29320
- }
29321
- const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
29322
- const title = opts.title !== undefined ? interpretEscapes2(String(opts.title)) : undefined;
29323
- const description = opts.description !== undefined ? interpretEscapes2(String(opts.description)) : undefined;
29324
- let status = undefined;
29325
- if (opts.status !== undefined) {
29326
- const raw = String(opts.status).trim().toLowerCase();
29327
- if (raw === "open")
29328
- status = 0;
29329
- else if (raw === "closed")
29330
- status = 1;
29331
- else {
29332
- const n = Number(raw);
29333
- if (!Number.isFinite(n)) {
29334
- console.error("status must be open|closed|0|1");
29335
- process.exitCode = 1;
29336
- return;
29337
- }
29338
- status = n;
29339
- }
29340
- if (status !== 0 && status !== 1) {
29341
- console.error("status must be 0 (open) or 1 (closed)");
30059
+ const rootOpts = program2.opts();
30060
+ const cfg = readConfig();
30061
+ const { apiKey } = getConfig(rootOpts);
30062
+ if (!apiKey) {
30063
+ console.error("API key is required. Run 'pgai auth' first or set --api-key.");
30064
+ process.exitCode = 1;
30065
+ return;
30066
+ }
30067
+ const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
30068
+ const title = opts.title !== undefined ? interpretEscapes2(String(opts.title)) : undefined;
30069
+ const description = opts.description !== undefined ? interpretEscapes2(String(opts.description)) : undefined;
30070
+ let status = undefined;
30071
+ if (opts.status !== undefined) {
30072
+ const raw = String(opts.status).trim().toLowerCase();
30073
+ if (raw === "open")
30074
+ status = 0;
30075
+ else if (raw === "closed")
30076
+ status = 1;
30077
+ else {
30078
+ const n = Number(raw);
30079
+ if (!Number.isFinite(n)) {
30080
+ console.error("status must be open|closed|0|1");
29342
30081
  process.exitCode = 1;
29343
30082
  return;
29344
30083
  }
30084
+ status = n;
29345
30085
  }
29346
- let labels = undefined;
29347
- if (opts.clearLabels) {
29348
- labels = [];
29349
- } else if (Array.isArray(opts.label) && opts.label.length > 0) {
29350
- labels = opts.label.map(String);
30086
+ if (status !== 0 && status !== 1) {
30087
+ console.error("status must be 0 (open) or 1 (closed)");
30088
+ process.exitCode = 1;
30089
+ return;
29351
30090
  }
30091
+ }
30092
+ let labels = undefined;
30093
+ if (opts.clearLabels) {
30094
+ labels = [];
30095
+ } else if (Array.isArray(opts.label) && opts.label.length > 0) {
30096
+ labels = opts.label.map(String);
30097
+ }
30098
+ const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Updating issue...");
30099
+ try {
29352
30100
  const result = await updateIssue2({
29353
30101
  apiKey,
29354
30102
  apiBaseUrl,
@@ -29359,40 +30107,217 @@ issues.command("update <issueId>").description("update an existing issue (title/
29359
30107
  labels,
29360
30108
  debug: !!opts.debug
29361
30109
  });
30110
+ spinner.stop();
29362
30111
  printResult(result, opts.json);
29363
30112
  } catch (err) {
30113
+ spinner.stop();
29364
30114
  const message = err instanceof Error ? err.message : String(err);
29365
30115
  console.error(message);
29366
30116
  process.exitCode = 1;
29367
30117
  }
29368
30118
  });
29369
30119
  issues.command("update-comment <commentId> <content>").description("update an existing issue comment").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (commentId, content, opts) => {
30120
+ if (opts.debug) {
30121
+ console.log(`Debug: Original content: ${JSON.stringify(content)}`);
30122
+ }
30123
+ content = interpretEscapes2(content);
30124
+ if (opts.debug) {
30125
+ console.log(`Debug: Interpreted content: ${JSON.stringify(content)}`);
30126
+ }
30127
+ const rootOpts = program2.opts();
30128
+ const cfg = readConfig();
30129
+ const { apiKey } = getConfig(rootOpts);
30130
+ if (!apiKey) {
30131
+ console.error("API key is required. Run 'pgai auth' first or set --api-key.");
30132
+ process.exitCode = 1;
30133
+ return;
30134
+ }
30135
+ const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Updating comment...");
29370
30136
  try {
29371
- if (opts.debug) {
29372
- console.log(`Debug: Original content: ${JSON.stringify(content)}`);
29373
- }
29374
- content = interpretEscapes2(content);
29375
- if (opts.debug) {
29376
- console.log(`Debug: Interpreted content: ${JSON.stringify(content)}`);
30137
+ const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
30138
+ const result = await updateIssueComment2({
30139
+ apiKey,
30140
+ apiBaseUrl,
30141
+ commentId,
30142
+ content,
30143
+ debug: !!opts.debug
30144
+ });
30145
+ spinner.stop();
30146
+ printResult(result, opts.json);
30147
+ } catch (err) {
30148
+ spinner.stop();
30149
+ const message = err instanceof Error ? err.message : String(err);
30150
+ console.error(message);
30151
+ process.exitCode = 1;
30152
+ }
30153
+ });
30154
+ issues.command("action-items <issueId>").description("list action items for an issue").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (issueId, opts) => {
30155
+ const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Fetching action items...");
30156
+ try {
30157
+ const rootOpts = program2.opts();
30158
+ const cfg = readConfig();
30159
+ const { apiKey } = getConfig(rootOpts);
30160
+ if (!apiKey) {
30161
+ spinner.stop();
30162
+ console.error("API key is required. Run 'pgai auth' first or set --api-key.");
30163
+ process.exitCode = 1;
30164
+ return;
29377
30165
  }
30166
+ const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
30167
+ const result = await fetchActionItems2({ apiKey, apiBaseUrl, issueId, debug: !!opts.debug });
30168
+ spinner.stop();
30169
+ printResult(result, opts.json);
30170
+ } catch (err) {
30171
+ spinner.stop();
30172
+ const message = err instanceof Error ? err.message : String(err);
30173
+ console.error(message);
30174
+ process.exitCode = 1;
30175
+ }
30176
+ });
30177
+ issues.command("view-action-item <actionItemIds...>").description("view action item(s) with all details (supports multiple IDs)").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (actionItemIds, opts) => {
30178
+ const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Fetching action item(s)...");
30179
+ try {
29378
30180
  const rootOpts = program2.opts();
29379
30181
  const cfg = readConfig();
29380
30182
  const { apiKey } = getConfig(rootOpts);
29381
30183
  if (!apiKey) {
30184
+ spinner.stop();
29382
30185
  console.error("API key is required. Run 'pgai auth' first or set --api-key.");
29383
30186
  process.exitCode = 1;
29384
30187
  return;
29385
30188
  }
29386
30189
  const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
29387
- const result = await updateIssueComment2({
30190
+ const result = await fetchActionItem2({ apiKey, apiBaseUrl, actionItemIds, debug: !!opts.debug });
30191
+ if (result.length === 0) {
30192
+ spinner.stop();
30193
+ console.error("Action item(s) not found");
30194
+ process.exitCode = 1;
30195
+ return;
30196
+ }
30197
+ spinner.stop();
30198
+ printResult(result, opts.json);
30199
+ } catch (err) {
30200
+ spinner.stop();
30201
+ const message = err instanceof Error ? err.message : String(err);
30202
+ console.error(message);
30203
+ process.exitCode = 1;
30204
+ }
30205
+ });
30206
+ issues.command("create-action-item <issueId> <title>").description("create a new action item for an issue").option("--description <text>", "detailed description (use \\n for newlines)").option("--sql-action <sql>", "SQL command to execute").option("--config <json>", 'config change as JSON: {"parameter":"...","value":"..."} (repeatable)', (value, previous) => {
30207
+ try {
30208
+ previous.push(JSON.parse(value));
30209
+ } catch {
30210
+ console.error(`Invalid JSON for --config: ${value}`);
30211
+ process.exit(1);
30212
+ }
30213
+ return previous;
30214
+ }, []).option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (issueId, rawTitle, opts) => {
30215
+ const rootOpts = program2.opts();
30216
+ const cfg = readConfig();
30217
+ const { apiKey } = getConfig(rootOpts);
30218
+ if (!apiKey) {
30219
+ console.error("API key is required. Run 'pgai auth' first or set --api-key.");
30220
+ process.exitCode = 1;
30221
+ return;
30222
+ }
30223
+ const title = interpretEscapes2(String(rawTitle || "").trim());
30224
+ if (!title) {
30225
+ console.error("title is required");
30226
+ process.exitCode = 1;
30227
+ return;
30228
+ }
30229
+ const description = opts.description !== undefined ? interpretEscapes2(String(opts.description)) : undefined;
30230
+ const sqlAction = opts.sqlAction;
30231
+ const configs = Array.isArray(opts.config) && opts.config.length > 0 ? opts.config : undefined;
30232
+ const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Creating action item...");
30233
+ try {
30234
+ const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
30235
+ const result = await createActionItem2({
29388
30236
  apiKey,
29389
30237
  apiBaseUrl,
29390
- commentId,
29391
- content,
30238
+ issueId,
30239
+ title,
30240
+ description,
30241
+ sqlAction,
30242
+ configs,
29392
30243
  debug: !!opts.debug
29393
30244
  });
29394
- printResult(result, opts.json);
30245
+ spinner.stop();
30246
+ printResult({ id: result }, opts.json);
30247
+ } catch (err) {
30248
+ spinner.stop();
30249
+ const message = err instanceof Error ? err.message : String(err);
30250
+ console.error(message);
30251
+ process.exitCode = 1;
30252
+ }
30253
+ });
30254
+ issues.command("update-action-item <actionItemId>").description("update an action item (title, description, status, sql_action, configs)").option("--title <text>", "new title (use \\n for newlines)").option("--description <text>", "new description (use \\n for newlines)").option("--done", "mark as done").option("--not-done", "mark as not done").option("--status <value>", "status: waiting_for_approval|approved|rejected").option("--status-reason <text>", "reason for status change").option("--sql-action <sql>", "SQL command (use empty string to clear)").option("--config <json>", "config change as JSON (repeatable, replaces all configs)", (value, previous) => {
30255
+ try {
30256
+ previous.push(JSON.parse(value));
30257
+ } catch {
30258
+ console.error(`Invalid JSON for --config: ${value}`);
30259
+ process.exit(1);
30260
+ }
30261
+ return previous;
30262
+ }, []).option("--clear-configs", "clear all config changes").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (actionItemId, opts) => {
30263
+ const rootOpts = program2.opts();
30264
+ const cfg = readConfig();
30265
+ const { apiKey } = getConfig(rootOpts);
30266
+ if (!apiKey) {
30267
+ console.error("API key is required. Run 'pgai auth' first or set --api-key.");
30268
+ process.exitCode = 1;
30269
+ return;
30270
+ }
30271
+ const title = opts.title !== undefined ? interpretEscapes2(String(opts.title)) : undefined;
30272
+ const description = opts.description !== undefined ? interpretEscapes2(String(opts.description)) : undefined;
30273
+ let isDone = undefined;
30274
+ if (opts.done)
30275
+ isDone = true;
30276
+ else if (opts.notDone)
30277
+ isDone = false;
30278
+ let status = undefined;
30279
+ if (opts.status !== undefined) {
30280
+ const validStatuses = ["waiting_for_approval", "approved", "rejected"];
30281
+ if (!validStatuses.includes(opts.status)) {
30282
+ console.error(`status must be one of: ${validStatuses.join(", ")}`);
30283
+ process.exitCode = 1;
30284
+ return;
30285
+ }
30286
+ status = opts.status;
30287
+ }
30288
+ const statusReason = opts.statusReason;
30289
+ const sqlAction = opts.sqlAction;
30290
+ let configs = undefined;
30291
+ if (opts.clearConfigs) {
30292
+ configs = [];
30293
+ } else if (Array.isArray(opts.config) && opts.config.length > 0) {
30294
+ configs = opts.config;
30295
+ }
30296
+ if (title === undefined && description === undefined && isDone === undefined && status === undefined && statusReason === undefined && sqlAction === undefined && configs === undefined) {
30297
+ console.error("At least one update option is required");
30298
+ process.exitCode = 1;
30299
+ return;
30300
+ }
30301
+ const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Updating action item...");
30302
+ try {
30303
+ const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
30304
+ await updateActionItem2({
30305
+ apiKey,
30306
+ apiBaseUrl,
30307
+ actionItemId,
30308
+ title,
30309
+ description,
30310
+ isDone,
30311
+ status,
30312
+ statusReason,
30313
+ sqlAction,
30314
+ configs,
30315
+ debug: !!opts.debug
30316
+ });
30317
+ spinner.stop();
30318
+ printResult({ success: true }, opts.json);
29395
30319
  } catch (err) {
30320
+ spinner.stop();
29396
30321
  const message = err instanceof Error ? err.message : String(err);
29397
30322
  console.error(message);
29398
30323
  process.exitCode = 1;