postgresai 0.14.0-dev.56 → 0.14.0-dev.57

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.
@@ -10,7 +10,7 @@ import * as os from "os";
10
10
  import * as crypto from "node:crypto";
11
11
  import { Client } from "pg";
12
12
  import { startMcpServer } from "../lib/mcp-server";
13
- import { fetchIssues, fetchIssueComments, createIssueComment, fetchIssue } from "../lib/issues";
13
+ import { fetchIssues, fetchIssueComments, createIssueComment, fetchIssue, createIssue, updateIssue, updateIssueComment } from "../lib/issues";
14
14
  import { resolveBaseUrls } from "../lib/util";
15
15
  import { applyInitPlan, buildInitPlan, connectWithSslFallback, DEFAULT_MONITORING_USER, redactPasswordsInSql, resolveAdminConnection, resolveMonitoringPassword, verifyInitSetup } from "../lib/init";
16
16
  import * as pkce from "../lib/pkce";
@@ -42,7 +42,7 @@ async function execPromise(command: string): Promise<{ stdout: string; stderr: s
42
42
  childProcess.exec(command, (error, stdout, stderr) => {
43
43
  if (error) {
44
44
  const err = error as Error & { code: number };
45
- err.code = error.code ?? 1;
45
+ err.code = typeof error.code === "number" ? error.code : 1;
46
46
  reject(err);
47
47
  } else {
48
48
  resolve({ stdout, stderr });
@@ -56,7 +56,7 @@ async function execFilePromise(file: string, args: string[]): Promise<{ stdout:
56
56
  childProcess.execFile(file, args, (error, stdout, stderr) => {
57
57
  if (error) {
58
58
  const err = error as Error & { code: number };
59
- err.code = error.code ?? 1;
59
+ err.code = typeof error.code === "number" ? error.code : 1;
60
60
  reject(err);
61
61
  } else {
62
62
  resolve({ stdout, stderr });
@@ -1174,6 +1174,11 @@ mon
1174
1174
  .option("--tag <tag>", "Docker image tag to use (e.g., 0.14.0, 0.14.0-dev.33)")
1175
1175
  .option("-y, --yes", "accept all defaults and skip interactive prompts", false)
1176
1176
  .action(async (opts: { demo: boolean; apiKey?: string; dbUrl?: string; tag?: string; yes: boolean }) => {
1177
+ // Get apiKey from global program options (--api-key is defined globally)
1178
+ // This is needed because Commander.js routes --api-key to the global option, not the subcommand's option
1179
+ const globalOpts = program.opts<CliOptions>();
1180
+ const apiKey = opts.apiKey || globalOpts.apiKey;
1181
+
1177
1182
  console.log("\n=================================");
1178
1183
  console.log(" PostgresAI monitoring local install");
1179
1184
  console.log("=================================\n");
@@ -1226,7 +1231,7 @@ mon
1226
1231
  opts.dbUrl = undefined;
1227
1232
  }
1228
1233
 
1229
- if (opts.demo && opts.apiKey) {
1234
+ if (opts.demo && apiKey) {
1230
1235
  console.error("✗ Cannot use --api-key with --demo mode");
1231
1236
  console.error("✗ Demo mode is for testing only and does not support API key integration");
1232
1237
  console.error("\nUse demo mode without API key: postgres-ai mon local-install --demo");
@@ -1248,11 +1253,11 @@ mon
1248
1253
  console.log("Step 1: Postgres AI API Configuration (Optional)");
1249
1254
  console.log("An API key enables automatic upload of PostgreSQL reports to Postgres AI\n");
1250
1255
 
1251
- if (opts.apiKey) {
1256
+ if (apiKey) {
1252
1257
  console.log("Using API key provided via --api-key parameter");
1253
- config.writeConfig({ apiKey: opts.apiKey });
1258
+ config.writeConfig({ apiKey });
1254
1259
  // Keep reporter compatibility (docker-compose mounts .pgwatch-config)
1255
- fs.writeFileSync(path.resolve(projectDir, ".pgwatch-config"), `api_key=${opts.apiKey}\n`, {
1260
+ fs.writeFileSync(path.resolve(projectDir, ".pgwatch-config"), `api_key=${apiKey}\n`, {
1256
1261
  encoding: "utf8",
1257
1262
  mode: 0o600
1258
1263
  });
@@ -2508,7 +2513,7 @@ issues
2508
2513
  });
2509
2514
 
2510
2515
  issues
2511
- .command("post_comment <issueId> <content>")
2516
+ .command("post-comment <issueId> <content>")
2512
2517
  .description("post a new comment to an issue")
2513
2518
  .option("--parent <uuid>", "parent comment id")
2514
2519
  .option("--debug", "enable debug output")
@@ -2553,6 +2558,194 @@ issues
2553
2558
  }
2554
2559
  });
2555
2560
 
2561
+ issues
2562
+ .command("create <title>")
2563
+ .description("create a new issue")
2564
+ .option("--org-id <id>", "organization id (defaults to config orgId)", (v) => parseInt(v, 10))
2565
+ .option("--project-id <id>", "project id", (v) => parseInt(v, 10))
2566
+ .option("--description <text>", "issue description (supports \\\\n)")
2567
+ .option(
2568
+ "--label <label>",
2569
+ "issue label (repeatable)",
2570
+ (value: string, previous: string[]) => {
2571
+ previous.push(value);
2572
+ return previous;
2573
+ },
2574
+ [] as string[]
2575
+ )
2576
+ .option("--debug", "enable debug output")
2577
+ .option("--json", "output raw JSON")
2578
+ .action(async (rawTitle: string, opts: { orgId?: number; projectId?: number; description?: string; label?: string[]; debug?: boolean; json?: boolean }) => {
2579
+ try {
2580
+ const rootOpts = program.opts<CliOptions>();
2581
+ const cfg = config.readConfig();
2582
+ const { apiKey } = getConfig(rootOpts);
2583
+ if (!apiKey) {
2584
+ console.error("API key is required. Run 'pgai auth' first or set --api-key.");
2585
+ process.exitCode = 1;
2586
+ return;
2587
+ }
2588
+
2589
+ const title = interpretEscapes(String(rawTitle || "").trim());
2590
+ if (!title) {
2591
+ console.error("title is required");
2592
+ process.exitCode = 1;
2593
+ return;
2594
+ }
2595
+
2596
+ const orgId = typeof opts.orgId === "number" && !Number.isNaN(opts.orgId) ? opts.orgId : cfg.orgId;
2597
+ if (typeof orgId !== "number") {
2598
+ console.error("org_id is required. Either pass --org-id or run 'pgai auth' to store it in config.");
2599
+ process.exitCode = 1;
2600
+ return;
2601
+ }
2602
+
2603
+ const description = opts.description !== undefined ? interpretEscapes(String(opts.description)) : undefined;
2604
+ const labels = Array.isArray(opts.label) && opts.label.length > 0 ? opts.label.map(String) : undefined;
2605
+ const projectId = typeof opts.projectId === "number" && !Number.isNaN(opts.projectId) ? opts.projectId : undefined;
2606
+
2607
+ const { apiBaseUrl } = resolveBaseUrls(rootOpts, cfg);
2608
+ const result = await createIssue({
2609
+ apiKey,
2610
+ apiBaseUrl,
2611
+ title,
2612
+ orgId,
2613
+ description,
2614
+ projectId,
2615
+ labels,
2616
+ debug: !!opts.debug,
2617
+ });
2618
+ printResult(result, opts.json);
2619
+ } catch (err) {
2620
+ const message = err instanceof Error ? err.message : String(err);
2621
+ console.error(message);
2622
+ process.exitCode = 1;
2623
+ }
2624
+ });
2625
+
2626
+ issues
2627
+ .command("update <issueId>")
2628
+ .description("update an existing issue (title/description/status/labels)")
2629
+ .option("--title <text>", "new title (supports \\\\n)")
2630
+ .option("--description <text>", "new description (supports \\\\n)")
2631
+ .option("--status <value>", "status: open|closed|0|1")
2632
+ .option(
2633
+ "--label <label>",
2634
+ "set labels (repeatable). If provided, replaces existing labels.",
2635
+ (value: string, previous: string[]) => {
2636
+ previous.push(value);
2637
+ return previous;
2638
+ },
2639
+ [] as string[]
2640
+ )
2641
+ .option("--clear-labels", "set labels to an empty list")
2642
+ .option("--debug", "enable debug output")
2643
+ .option("--json", "output raw JSON")
2644
+ .action(async (issueId: string, opts: { title?: string; description?: string; status?: string; label?: string[]; clearLabels?: boolean; debug?: boolean; json?: boolean }) => {
2645
+ try {
2646
+ const rootOpts = program.opts<CliOptions>();
2647
+ const cfg = config.readConfig();
2648
+ const { apiKey } = getConfig(rootOpts);
2649
+ if (!apiKey) {
2650
+ console.error("API key is required. Run 'pgai auth' first or set --api-key.");
2651
+ process.exitCode = 1;
2652
+ return;
2653
+ }
2654
+
2655
+ const { apiBaseUrl } = resolveBaseUrls(rootOpts, cfg);
2656
+
2657
+ const title = opts.title !== undefined ? interpretEscapes(String(opts.title)) : undefined;
2658
+ const description = opts.description !== undefined ? interpretEscapes(String(opts.description)) : undefined;
2659
+
2660
+ let status: number | undefined = undefined;
2661
+ if (opts.status !== undefined) {
2662
+ const raw = String(opts.status).trim().toLowerCase();
2663
+ if (raw === "open") status = 0;
2664
+ else if (raw === "closed") status = 1;
2665
+ else {
2666
+ const n = Number(raw);
2667
+ if (!Number.isFinite(n)) {
2668
+ console.error("status must be open|closed|0|1");
2669
+ process.exitCode = 1;
2670
+ return;
2671
+ }
2672
+ status = n;
2673
+ }
2674
+ if (status !== 0 && status !== 1) {
2675
+ console.error("status must be 0 (open) or 1 (closed)");
2676
+ process.exitCode = 1;
2677
+ return;
2678
+ }
2679
+ }
2680
+
2681
+ let labels: string[] | undefined = undefined;
2682
+ if (opts.clearLabels) {
2683
+ labels = [];
2684
+ } else if (Array.isArray(opts.label) && opts.label.length > 0) {
2685
+ labels = opts.label.map(String);
2686
+ }
2687
+
2688
+ const result = await updateIssue({
2689
+ apiKey,
2690
+ apiBaseUrl,
2691
+ issueId,
2692
+ title,
2693
+ description,
2694
+ status,
2695
+ labels,
2696
+ debug: !!opts.debug,
2697
+ });
2698
+ printResult(result, opts.json);
2699
+ } catch (err) {
2700
+ const message = err instanceof Error ? err.message : String(err);
2701
+ console.error(message);
2702
+ process.exitCode = 1;
2703
+ }
2704
+ });
2705
+
2706
+ issues
2707
+ .command("update-comment <commentId> <content>")
2708
+ .description("update an existing issue comment")
2709
+ .option("--debug", "enable debug output")
2710
+ .option("--json", "output raw JSON")
2711
+ .action(async (commentId: string, content: string, opts: { debug?: boolean; json?: boolean }) => {
2712
+ try {
2713
+ if (opts.debug) {
2714
+ // eslint-disable-next-line no-console
2715
+ console.log(`Debug: Original content: ${JSON.stringify(content)}`);
2716
+ }
2717
+ content = interpretEscapes(content);
2718
+ if (opts.debug) {
2719
+ // eslint-disable-next-line no-console
2720
+ console.log(`Debug: Interpreted content: ${JSON.stringify(content)}`);
2721
+ }
2722
+
2723
+ const rootOpts = program.opts<CliOptions>();
2724
+ const cfg = config.readConfig();
2725
+ const { apiKey } = getConfig(rootOpts);
2726
+ if (!apiKey) {
2727
+ console.error("API key is required. Run 'pgai auth' first or set --api-key.");
2728
+ process.exitCode = 1;
2729
+ return;
2730
+ }
2731
+
2732
+ const { apiBaseUrl } = resolveBaseUrls(rootOpts, cfg);
2733
+
2734
+ const result = await updateIssueComment({
2735
+ apiKey,
2736
+ apiBaseUrl,
2737
+ commentId,
2738
+ content,
2739
+ debug: !!opts.debug,
2740
+ });
2741
+ printResult(result, opts.json);
2742
+ } catch (err) {
2743
+ const message = err instanceof Error ? err.message : String(err);
2744
+ console.error(message);
2745
+ process.exitCode = 1;
2746
+ }
2747
+ });
2748
+
2556
2749
  // MCP server
2557
2750
  const mcp = program.command("mcp").description("MCP server integration");
2558
2751