postgresai 0.14.0-dev.71 → 0.14.0-dev.72

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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.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.71";
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) {
@@ -24645,6 +25312,38 @@ class SupabaseClient {
24645
25312
  return err;
24646
25313
  }
24647
25314
  }
25315
+ async function fetchPoolerDatabaseUrl(config2) {
25316
+ const url = `${SUPABASE_API_BASE}/v1/projects/${encodeURIComponent(config2.projectRef)}/config/database/pooler`;
25317
+ try {
25318
+ const response = await fetch(url, {
25319
+ method: "GET",
25320
+ headers: {
25321
+ Authorization: `Bearer ${config2.accessToken}`
25322
+ }
25323
+ });
25324
+ if (!response.ok) {
25325
+ return null;
25326
+ }
25327
+ const data = await response.json();
25328
+ if (Array.isArray(data) && data.length > 0) {
25329
+ const pooler = data[0];
25330
+ if (pooler.db_host && pooler.db_port && pooler.db_name && pooler.db_user) {
25331
+ return `postgresql://${pooler.db_user}@${pooler.db_host}:${pooler.db_port}/${pooler.db_name}`;
25332
+ }
25333
+ if (typeof pooler.connection_string === "string") {
25334
+ try {
25335
+ const connUrl = new URL(pooler.connection_string);
25336
+ return `postgresql://${connUrl.username}@${connUrl.hostname}:${connUrl.port}${connUrl.pathname}`;
25337
+ } catch {
25338
+ return null;
25339
+ }
25340
+ }
25341
+ }
25342
+ return null;
25343
+ } catch {
25344
+ return null;
25345
+ }
25346
+ }
24648
25347
  function resolveSupabaseConfig(opts) {
24649
25348
  const accessToken = opts.accessToken?.trim() || process.env.SUPABASE_ACCESS_TOKEN?.trim() || "";
24650
25349
  const projectRef = opts.projectRef?.trim() || process.env.SUPABASE_PROJECT_REF?.trim() || "";
@@ -27303,6 +28002,10 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
27303
28002
  console.log(`Optional permissions: ${includeOptionalPermissions2 ? "enabled" : "skipped"}`);
27304
28003
  }
27305
28004
  const supabaseClient = new SupabaseClient(supabaseConfig);
28005
+ let databaseUrl = null;
28006
+ if (jsonOutput) {
28007
+ databaseUrl = await fetchPoolerDatabaseUrl(supabaseConfig);
28008
+ }
27306
28009
  try {
27307
28010
  const database = await supabaseClient.getCurrentDatabase();
27308
28011
  if (!database) {
@@ -27320,7 +28023,7 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
27320
28023
  });
27321
28024
  if (v.ok) {
27322
28025
  if (jsonOutput) {
27323
- outputJson({
28026
+ const result = {
27324
28027
  success: true,
27325
28028
  mode: "supabase",
27326
28029
  action: "verify",
@@ -27328,7 +28031,11 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
27328
28031
  monitoringUser: opts.monitoringUser,
27329
28032
  verified: true,
27330
28033
  missingOptional: v.missingOptional
27331
- });
28034
+ };
28035
+ if (databaseUrl) {
28036
+ result.databaseUrl = databaseUrl;
28037
+ }
28038
+ outputJson(result);
27332
28039
  } else {
27333
28040
  console.log("\u2713 prepare-db verify: OK");
27334
28041
  if (v.missingOptional.length > 0) {
@@ -27340,7 +28047,7 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
27340
28047
  return;
27341
28048
  }
27342
28049
  if (jsonOutput) {
27343
- outputJson({
28050
+ const result = {
27344
28051
  success: false,
27345
28052
  mode: "supabase",
27346
28053
  action: "verify",
@@ -27349,7 +28056,11 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
27349
28056
  verified: false,
27350
28057
  missingRequired: v.missingRequired,
27351
28058
  missingOptional: v.missingOptional
27352
- });
28059
+ };
28060
+ if (databaseUrl) {
28061
+ result.databaseUrl = databaseUrl;
28062
+ }
28063
+ outputJson(result);
27353
28064
  } else {
27354
28065
  console.error("\u2717 prepare-db verify failed: missing required items");
27355
28066
  for (const m of v.missingRequired)
@@ -27444,6 +28155,9 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
27444
28155
  if (passwordGenerated) {
27445
28156
  result.generatedPassword = monPassword;
27446
28157
  }
28158
+ if (databaseUrl) {
28159
+ result.databaseUrl = databaseUrl;
28160
+ }
27447
28161
  outputJson(result);
27448
28162
  } else {
27449
28163
  console.log(opts.resetPassword ? "\u2713 prepare-db password reset completed" : "\u2713 prepare-db completed");
@@ -29175,18 +29889,36 @@ function interpretEscapes2(str2) {
29175
29889
  `).replace(/\\t/g, "\t").replace(/\\r/g, "\r").replace(/\\"/g, '"').replace(/\\'/g, "'").replace(/\x00/g, "\\");
29176
29890
  }
29177
29891
  var issues = program2.command("issues").description("issues management");
29178
- issues.command("list").description("list issues").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (opts) => {
29892
+ issues.command("list").description("list issues").option("--status <status>", "filter by status: open, closed, or all (default: all)").option("--limit <n>", "max number of issues to return (default: 20)", parseInt).option("--offset <n>", "number of issues to skip (default: 0)", parseInt).option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (opts) => {
29893
+ const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Fetching issues...");
29179
29894
  try {
29180
29895
  const rootOpts = program2.opts();
29181
29896
  const cfg = readConfig();
29182
29897
  const { apiKey } = getConfig(rootOpts);
29183
29898
  if (!apiKey) {
29899
+ spinner.stop();
29184
29900
  console.error("API key is required. Run 'pgai auth' first or set --api-key.");
29185
29901
  process.exitCode = 1;
29186
29902
  return;
29187
29903
  }
29904
+ const orgId = cfg.orgId ?? undefined;
29188
29905
  const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
29189
- 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();
29190
29922
  const trimmed = Array.isArray(result) ? result.map((r) => ({
29191
29923
  id: r.id,
29192
29924
  title: r.title,
@@ -29195,17 +29927,20 @@ issues.command("list").description("list issues").option("--debug", "enable debu
29195
29927
  })) : result;
29196
29928
  printResult(trimmed, opts.json);
29197
29929
  } catch (err) {
29930
+ spinner.stop();
29198
29931
  const message = err instanceof Error ? err.message : String(err);
29199
29932
  console.error(message);
29200
29933
  process.exitCode = 1;
29201
29934
  }
29202
29935
  });
29203
29936
  issues.command("view <issueId>").description("view issue details and comments").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (issueId, opts) => {
29937
+ const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Fetching issue...");
29204
29938
  try {
29205
29939
  const rootOpts = program2.opts();
29206
29940
  const cfg = readConfig();
29207
29941
  const { apiKey } = getConfig(rootOpts);
29208
29942
  if (!apiKey) {
29943
+ spinner.stop();
29209
29944
  console.error("API key is required. Run 'pgai auth' first or set --api-key.");
29210
29945
  process.exitCode = 1;
29211
29946
  return;
@@ -29213,32 +29948,38 @@ issues.command("view <issueId>").description("view issue details and comments").
29213
29948
  const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
29214
29949
  const issue2 = await fetchIssue2({ apiKey, apiBaseUrl, issueId, debug: !!opts.debug });
29215
29950
  if (!issue2) {
29951
+ spinner.stop();
29216
29952
  console.error("Issue not found");
29217
29953
  process.exitCode = 1;
29218
29954
  return;
29219
29955
  }
29956
+ spinner.update("Fetching comments...");
29220
29957
  const comments = await fetchIssueComments2({ apiKey, apiBaseUrl, issueId, debug: !!opts.debug });
29958
+ spinner.stop();
29221
29959
  const combined = { issue: issue2, comments };
29222
29960
  printResult(combined, opts.json);
29223
29961
  } catch (err) {
29962
+ spinner.stop();
29224
29963
  const message = err instanceof Error ? err.message : String(err);
29225
29964
  console.error(message);
29226
29965
  process.exitCode = 1;
29227
29966
  }
29228
29967
  });
29229
29968
  issues.command("post-comment <issueId> <content>").description("post a new comment to an issue").option("--parent <uuid>", "parent comment id").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (issueId, content, opts) => {
29969
+ if (opts.debug) {
29970
+ console.log(`Debug: Original content: ${JSON.stringify(content)}`);
29971
+ }
29972
+ content = interpretEscapes2(content);
29973
+ if (opts.debug) {
29974
+ console.log(`Debug: Interpreted content: ${JSON.stringify(content)}`);
29975
+ }
29976
+ const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Posting comment...");
29230
29977
  try {
29231
- if (opts.debug) {
29232
- console.log(`Debug: Original content: ${JSON.stringify(content)}`);
29233
- }
29234
- content = interpretEscapes2(content);
29235
- if (opts.debug) {
29236
- console.log(`Debug: Interpreted content: ${JSON.stringify(content)}`);
29237
- }
29238
29978
  const rootOpts = program2.opts();
29239
29979
  const cfg = readConfig();
29240
29980
  const { apiKey } = getConfig(rootOpts);
29241
29981
  if (!apiKey) {
29982
+ spinner.stop();
29242
29983
  console.error("API key is required. Run 'pgai auth' first or set --api-key.");
29243
29984
  process.exitCode = 1;
29244
29985
  return;
@@ -29252,41 +29993,44 @@ issues.command("post-comment <issueId> <content>").description("post a new comme
29252
29993
  parentCommentId: opts.parent,
29253
29994
  debug: !!opts.debug
29254
29995
  });
29996
+ spinner.stop();
29255
29997
  printResult(result, opts.json);
29256
29998
  } catch (err) {
29999
+ spinner.stop();
29257
30000
  const message = err instanceof Error ? err.message : String(err);
29258
30001
  console.error(message);
29259
30002
  process.exitCode = 1;
29260
30003
  }
29261
30004
  });
29262
- issues.command("create <title>").description("create a new issue").option("--org-id <id>", "organization id (defaults to config orgId)", (v) => parseInt(v, 10)).option("--project-id <id>", "project id", (v) => parseInt(v, 10)).option("--description <text>", "issue description (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) => {
29263
30006
  previous.push(value);
29264
30007
  return previous;
29265
30008
  }, []).option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (rawTitle, opts) => {
30009
+ const rootOpts = program2.opts();
30010
+ const cfg = readConfig();
30011
+ const { apiKey } = getConfig(rootOpts);
30012
+ if (!apiKey) {
30013
+ console.error("API key is required. Run 'pgai auth' first or set --api-key.");
30014
+ process.exitCode = 1;
30015
+ return;
30016
+ }
30017
+ const title = interpretEscapes2(String(rawTitle || "").trim());
30018
+ if (!title) {
30019
+ console.error("title is required");
30020
+ process.exitCode = 1;
30021
+ return;
30022
+ }
30023
+ const orgId = typeof opts.orgId === "number" && !Number.isNaN(opts.orgId) ? opts.orgId : cfg.orgId;
30024
+ if (typeof orgId !== "number") {
30025
+ console.error("org_id is required. Either pass --org-id or run 'pgai auth' to store it in config.");
30026
+ process.exitCode = 1;
30027
+ return;
30028
+ }
30029
+ const description = opts.description !== undefined ? interpretEscapes2(String(opts.description)) : undefined;
30030
+ const labels = Array.isArray(opts.label) && opts.label.length > 0 ? opts.label.map(String) : undefined;
30031
+ const projectId = typeof opts.projectId === "number" && !Number.isNaN(opts.projectId) ? opts.projectId : undefined;
30032
+ const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Creating issue...");
29266
30033
  try {
29267
- const rootOpts = program2.opts();
29268
- const cfg = readConfig();
29269
- const { apiKey } = getConfig(rootOpts);
29270
- if (!apiKey) {
29271
- console.error("API key is required. Run 'pgai auth' first or set --api-key.");
29272
- process.exitCode = 1;
29273
- return;
29274
- }
29275
- const title = interpretEscapes2(String(rawTitle || "").trim());
29276
- if (!title) {
29277
- console.error("title is required");
29278
- process.exitCode = 1;
29279
- return;
29280
- }
29281
- const orgId = typeof opts.orgId === "number" && !Number.isNaN(opts.orgId) ? opts.orgId : cfg.orgId;
29282
- if (typeof orgId !== "number") {
29283
- console.error("org_id is required. Either pass --org-id or run 'pgai auth' to store it in config.");
29284
- process.exitCode = 1;
29285
- return;
29286
- }
29287
- const description = opts.description !== undefined ? interpretEscapes2(String(opts.description)) : undefined;
29288
- const labels = Array.isArray(opts.label) && opts.label.length > 0 ? opts.label.map(String) : undefined;
29289
- const projectId = typeof opts.projectId === "number" && !Number.isNaN(opts.projectId) ? opts.projectId : undefined;
29290
30034
  const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
29291
30035
  const result = await createIssue2({
29292
30036
  apiKey,
@@ -29298,57 +30042,60 @@ issues.command("create <title>").description("create a new issue").option("--org
29298
30042
  labels,
29299
30043
  debug: !!opts.debug
29300
30044
  });
30045
+ spinner.stop();
29301
30046
  printResult(result, opts.json);
29302
30047
  } catch (err) {
30048
+ spinner.stop();
29303
30049
  const message = err instanceof Error ? err.message : String(err);
29304
30050
  console.error(message);
29305
30051
  process.exitCode = 1;
29306
30052
  }
29307
30053
  });
29308
- issues.command("update <issueId>").description("update an existing issue (title/description/status/labels)").option("--title <text>", "new title (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) => {
29309
30055
  previous.push(value);
29310
30056
  return previous;
29311
30057
  }, []).option("--clear-labels", "set labels to an empty list").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (issueId, opts) => {
29312
- 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)");
30058
+ const rootOpts = program2.opts();
30059
+ const cfg = readConfig();
30060
+ const { apiKey } = getConfig(rootOpts);
30061
+ if (!apiKey) {
30062
+ console.error("API key is required. Run 'pgai auth' first or set --api-key.");
30063
+ process.exitCode = 1;
30064
+ return;
30065
+ }
30066
+ const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
30067
+ const title = opts.title !== undefined ? interpretEscapes2(String(opts.title)) : undefined;
30068
+ const description = opts.description !== undefined ? interpretEscapes2(String(opts.description)) : undefined;
30069
+ let status = undefined;
30070
+ if (opts.status !== undefined) {
30071
+ const raw = String(opts.status).trim().toLowerCase();
30072
+ if (raw === "open")
30073
+ status = 0;
30074
+ else if (raw === "closed")
30075
+ status = 1;
30076
+ else {
30077
+ const n = Number(raw);
30078
+ if (!Number.isFinite(n)) {
30079
+ console.error("status must be open|closed|0|1");
29342
30080
  process.exitCode = 1;
29343
30081
  return;
29344
30082
  }
30083
+ status = n;
29345
30084
  }
29346
- 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);
30085
+ if (status !== 0 && status !== 1) {
30086
+ console.error("status must be 0 (open) or 1 (closed)");
30087
+ process.exitCode = 1;
30088
+ return;
29351
30089
  }
30090
+ }
30091
+ let labels = undefined;
30092
+ if (opts.clearLabels) {
30093
+ labels = [];
30094
+ } else if (Array.isArray(opts.label) && opts.label.length > 0) {
30095
+ labels = opts.label.map(String);
30096
+ }
30097
+ const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Updating issue...");
30098
+ try {
29352
30099
  const result = await updateIssue2({
29353
30100
  apiKey,
29354
30101
  apiBaseUrl,
@@ -29359,40 +30106,217 @@ issues.command("update <issueId>").description("update an existing issue (title/
29359
30106
  labels,
29360
30107
  debug: !!opts.debug
29361
30108
  });
30109
+ spinner.stop();
29362
30110
  printResult(result, opts.json);
29363
30111
  } catch (err) {
30112
+ spinner.stop();
29364
30113
  const message = err instanceof Error ? err.message : String(err);
29365
30114
  console.error(message);
29366
30115
  process.exitCode = 1;
29367
30116
  }
29368
30117
  });
29369
30118
  issues.command("update-comment <commentId> <content>").description("update an existing issue comment").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (commentId, content, opts) => {
30119
+ if (opts.debug) {
30120
+ console.log(`Debug: Original content: ${JSON.stringify(content)}`);
30121
+ }
30122
+ content = interpretEscapes2(content);
30123
+ if (opts.debug) {
30124
+ console.log(`Debug: Interpreted content: ${JSON.stringify(content)}`);
30125
+ }
30126
+ const rootOpts = program2.opts();
30127
+ const cfg = readConfig();
30128
+ const { apiKey } = getConfig(rootOpts);
30129
+ if (!apiKey) {
30130
+ console.error("API key is required. Run 'pgai auth' first or set --api-key.");
30131
+ process.exitCode = 1;
30132
+ return;
30133
+ }
30134
+ const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Updating comment...");
29370
30135
  try {
29371
- 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)}`);
30136
+ const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
30137
+ const result = await updateIssueComment2({
30138
+ apiKey,
30139
+ apiBaseUrl,
30140
+ commentId,
30141
+ content,
30142
+ debug: !!opts.debug
30143
+ });
30144
+ spinner.stop();
30145
+ printResult(result, opts.json);
30146
+ } catch (err) {
30147
+ spinner.stop();
30148
+ const message = err instanceof Error ? err.message : String(err);
30149
+ console.error(message);
30150
+ process.exitCode = 1;
30151
+ }
30152
+ });
30153
+ issues.command("action-items <issueId>").description("list action items for an issue").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (issueId, opts) => {
30154
+ const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Fetching action items...");
30155
+ try {
30156
+ const rootOpts = program2.opts();
30157
+ const cfg = readConfig();
30158
+ const { apiKey } = getConfig(rootOpts);
30159
+ if (!apiKey) {
30160
+ spinner.stop();
30161
+ console.error("API key is required. Run 'pgai auth' first or set --api-key.");
30162
+ process.exitCode = 1;
30163
+ return;
29377
30164
  }
30165
+ const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
30166
+ const result = await fetchActionItems2({ apiKey, apiBaseUrl, issueId, debug: !!opts.debug });
30167
+ spinner.stop();
30168
+ printResult(result, opts.json);
30169
+ } catch (err) {
30170
+ spinner.stop();
30171
+ const message = err instanceof Error ? err.message : String(err);
30172
+ console.error(message);
30173
+ process.exitCode = 1;
30174
+ }
30175
+ });
30176
+ issues.command("view-action-item <actionItemIds...>").description("view action item(s) with all details (supports multiple IDs)").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (actionItemIds, opts) => {
30177
+ const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Fetching action item(s)...");
30178
+ try {
29378
30179
  const rootOpts = program2.opts();
29379
30180
  const cfg = readConfig();
29380
30181
  const { apiKey } = getConfig(rootOpts);
29381
30182
  if (!apiKey) {
30183
+ spinner.stop();
29382
30184
  console.error("API key is required. Run 'pgai auth' first or set --api-key.");
29383
30185
  process.exitCode = 1;
29384
30186
  return;
29385
30187
  }
29386
30188
  const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
29387
- const result = await 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({
29388
30235
  apiKey,
29389
30236
  apiBaseUrl,
29390
- commentId,
29391
- content,
30237
+ issueId,
30238
+ title,
30239
+ description,
30240
+ sqlAction,
30241
+ configs,
29392
30242
  debug: !!opts.debug
29393
30243
  });
29394
- 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);
29395
30318
  } catch (err) {
30319
+ spinner.stop();
29396
30320
  const message = err instanceof Error ? err.message : String(err);
29397
30321
  console.error(message);
29398
30322
  process.exitCode = 1;