postgresai 0.17.0-rc.2 → 0.17.0-rc.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -271,6 +271,35 @@ postgresai mon check # System readiness check
271
271
  postgresai mon shell <service> # Open shell to monitoring service
272
272
  ```
273
273
 
274
+ ### PromQL queries (`promql`)
275
+
276
+ Ask a monitoring instance a PromQL question through the platform. The platform
277
+ never connects to the instance: it queues the query as a job, the instance picks
278
+ it up on its next poll, runs it against its own metric store, and posts the
279
+ answer back.
280
+
281
+ ```bash
282
+ # instant query
283
+ pgai promql 'up' --instance <instance-uuid>
284
+
285
+ # range query
286
+ pgai promql 'sum(rate(pgwatch_db_stats_xact_commit[5m]))' \
287
+ --instance <instance-uuid> \
288
+ --range --start 2026-09-21T00:00:00Z --end 2026-09-21T01:00:00Z --step 60
289
+
290
+ # on the box itself, --instance is read from .pgwatch-config
291
+ pgai promql 'up'
292
+ ```
293
+
294
+ `--at <time>` evaluates an instant query at a given moment (ignored with
295
+ `--range`). `--json` prints every point instead of the first and last per series.
296
+ The wait is sized from the instance's poll pacing; `--timeout` overrides it. A
297
+ result too large for the byte budget is trimmed and flagged `truncated`.
298
+
299
+ Requires platform-all !809
300
+ (https://gitlab.com/postgres-ai/platform-all/-/merge_requests/809); until it is
301
+ deployed the command returns a PGRST202 404.
302
+
274
303
  ### MCP server (`mcp` group)
275
304
 
276
305
  ```bash
@@ -299,7 +328,7 @@ required** — the server will not assume an organization. Call `orgs_list` (or
299
328
  run `pgai orgs`) to discover the available ids.
300
329
 
301
330
  Tools exposed:
302
- - `list_issues`: returns the same JSON as `postgresai issues list` (args: `{ org_id, status?, hidden_only?, limit?, offset?, debug? }`).
331
+ - `list_issues`: returns the same JSON as `postgresai issues list` (args: `{ org_id, status?, hidden_only?, limit?, offset?, debug? }`). Unlike the CLI, omitting `status` returns open and closed issues; pass `status: "open"` to match the CLI default.
303
332
  - `view_issue`: view a single issue with its comments (args: `{ issue_id, org_id, debug? }`).
304
333
  - `create_issue`: create a new issue (args: `{ title, description?, org_id, project_id?, labels?, attachments?, is_hidden?, debug? }`).
305
334
  - `update_issue`: update title/description/status/labels/is_hidden (args: `{ issue_id, org_id, title?, description?, status?, labels?, attachments?, is_hidden?, debug? }`).
@@ -350,7 +379,8 @@ sensitive.
350
379
  ### Issues management (`issues` group)
351
380
 
352
381
  ```bash
353
- postgresai issues list # List issues (shows: id, title, status, created_at; is_hidden only when set)
382
+ postgresai issues list # List OPEN issues (shows: id, title, status, created_at; is_hidden only when set)
383
+ postgresai issues list --status closed # Only closed issues (--status all for both)
354
384
  postgresai issues list --hidden-only # Only hidden issues (PostgresAI staff)
355
385
  postgresai issues view <issueId> # View issue details and comments
356
386
  postgresai issues create --org-id <id> <title> # Create a new issue
@@ -13430,6 +13430,11 @@ var require_dist2 = __commonJS(function(exports, module) {
13430
13430
  });
13431
13431
 
13432
13432
  // lib/util.ts
13433
+ function requestTimeoutSignal2(timeoutMs) {
13434
+ const requested = typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : DEFAULT_HTTP_REQUEST_TIMEOUT_MS2;
13435
+ const bounded = Math.max(1, Math.min(DEFAULT_HTTP_REQUEST_TIMEOUT_MS2, Math.floor(requested)));
13436
+ return { signal: AbortSignal.timeout(bounded), timeoutMs: bounded };
13437
+ }
13433
13438
  function maskSecret2(secret) {
13434
13439
  if (!secret)
13435
13440
  return "";
@@ -13461,6 +13466,7 @@ function resolveBaseUrls2(opts, cfg, defaults = {}) {
13461
13466
  storageBaseUrl: normalizeBaseUrl2(storageCandidate)
13462
13467
  };
13463
13468
  }
13469
+ var DEFAULT_HTTP_REQUEST_TIMEOUT_MS2 = 25000;
13464
13470
  var init_util = () => {};
13465
13471
 
13466
13472
  // node_modules/commander/index.js
@@ -13524,7 +13530,7 @@ var {
13524
13530
  // package.json
13525
13531
  var package_default = {
13526
13532
  name: "postgresai",
13527
- version: "0.17.0-rc.2",
13533
+ version: "0.17.0-rc.3",
13528
13534
  description: "postgres_ai CLI",
13529
13535
  license: "Apache-2.0",
13530
13536
  private: false,
@@ -16884,7 +16890,7 @@ var Result = import_lib.default.Result;
16884
16890
  var TypeOverrides = import_lib.default.TypeOverrides;
16885
16891
  var defaults = import_lib.default.defaults;
16886
16892
  // package.json
16887
- var version = "0.17.0-rc.2";
16893
+ var version = "0.17.0-rc.3";
16888
16894
  var package_default2 = {
16889
16895
  name: "postgresai",
16890
16896
  version,
@@ -17315,6 +17321,10 @@ function resolveMcpOrgScope(args, apiKey, cfg) {
17315
17321
  }
17316
17322
 
17317
17323
  // lib/issues.ts
17324
+ var IssueStatus = {
17325
+ OPEN: 0,
17326
+ CLOSED: 1
17327
+ };
17318
17328
  function issueRequestHeaders(apiKey, orgScope) {
17319
17329
  return buildAuthHeaders(apiKey, orgScope, {
17320
17330
  Prefer: "return=representation",
@@ -17331,6 +17341,17 @@ function withVisibleHiddenFlag(issue) {
17331
17341
  const { is_hidden: _dropped, ...rest } = issue;
17332
17342
  return rest;
17333
17343
  }
17344
+ function presentIssue(issue) {
17345
+ const visible = withVisibleHiddenFlag(issue);
17346
+ return { ...visible, status: issueStatusLabel(issue.status) };
17347
+ }
17348
+ function issueStatusLabel(status) {
17349
+ if (status === IssueStatus.OPEN)
17350
+ return "open";
17351
+ if (status === IssueStatus.CLOSED)
17352
+ return "closed";
17353
+ return status;
17354
+ }
17334
17355
  async function fetchIssues(params) {
17335
17356
  const { apiKey, apiBaseUrl, orgScope, orgId, status, limit = 20, offset = 0, hiddenOnly, debug } = params;
17336
17357
  if (!apiKey) {
@@ -24994,7 +25015,7 @@ async function handleToolCall(req, rootOpts, extra) {
24994
25015
  const offset = args.offset !== undefined ? Number(args.offset) : undefined;
24995
25016
  const hiddenOnly = Boolean(args.hidden_only);
24996
25017
  const issues = await fetchIssues({ apiKey, apiBaseUrl, orgId, orgScope: scope.orgScope, status, limit, offset, hiddenOnly, debug });
24997
- const trimmed = issues.map((issue) => withVisibleHiddenFlag(issue));
25018
+ const trimmed = issues.map((issue) => presentIssue(issue));
24998
25019
  return { content: [{ type: "text", text: JSON.stringify(trimmed, null, 2) }] };
24999
25020
  }
25000
25021
  if (toolName === "view_issue") {
@@ -25010,7 +25031,7 @@ async function handleToolCall(req, rootOpts, extra) {
25010
25031
  return { content: [{ type: "text", text: "Issue not found" }], isError: true };
25011
25032
  }
25012
25033
  const comments = await fetchIssueComments({ apiKey, apiBaseUrl, issueId, orgScope: scope.orgScope, debug });
25013
- const combined = { issue: withVisibleHiddenFlag(issue), comments };
25034
+ const combined = { issue: presentIssue(issue), comments };
25014
25035
  return { content: [{ type: "text", text: JSON.stringify(combined, null, 2) }] };
25015
25036
  }
25016
25037
  if (toolName === "post_issue_comment") {
@@ -25314,7 +25335,7 @@ async function startMcpServer(rootOpts, extra) {
25314
25335
  type: "object",
25315
25336
  properties: {
25316
25337
  org_id: { type: "number", description: "Organization ID. Optional with a per-organization token (falls back to config); REQUIRED with a global token, which can reach every organization the user belongs to and will not assume one. Use orgs_list / `pgai orgs` to discover valid values." },
25317
- status: { type: "string", description: "Filter by status: 'open', 'closed', or omit for all" },
25338
+ status: { type: "string", description: "Filter by status: 'open', 'closed', or omit for all (note: the CLI defaults to open). Rows carry status as 'open'/'closed'." },
25318
25339
  limit: { type: "number", description: "Max number of issues to return (default: 20)" },
25319
25340
  offset: { type: "number", description: "Number of issues to skip (default: 0)" },
25320
25341
  hidden_only: { type: "boolean", description: "List only hidden issues (PostgresAI staff; same as CLI --hidden-only)" },
@@ -25596,6 +25617,10 @@ async function startMcpServer(rootOpts, extra) {
25596
25617
  }
25597
25618
 
25598
25619
  // lib/issues.ts
25620
+ var IssueStatus2 = {
25621
+ OPEN: 0,
25622
+ CLOSED: 1
25623
+ };
25599
25624
  function issueRequestHeaders2(apiKey, orgScope) {
25600
25625
  return buildAuthHeaders(apiKey, orgScope, {
25601
25626
  Prefer: "return=representation",
@@ -25612,6 +25637,17 @@ function withVisibleHiddenFlag2(issue) {
25612
25637
  const { is_hidden: _dropped, ...rest } = issue;
25613
25638
  return rest;
25614
25639
  }
25640
+ function presentIssue2(issue) {
25641
+ const visible = withVisibleHiddenFlag2(issue);
25642
+ return { ...visible, status: issueStatusLabel2(issue.status) };
25643
+ }
25644
+ function issueStatusLabel2(status) {
25645
+ if (status === IssueStatus2.OPEN)
25646
+ return "open";
25647
+ if (status === IssueStatus2.CLOSED)
25648
+ return "closed";
25649
+ return status;
25650
+ }
25615
25651
  async function fetchIssues2(params) {
25616
25652
  const { apiKey, apiBaseUrl, orgScope, orgId, status, limit = 20, offset = 0, hiddenOnly, debug } = params;
25617
25653
  if (!apiKey) {
@@ -27138,6 +27174,134 @@ async function destroySnapshot(params) {
27138
27174
  // bin/postgres-ai.ts
27139
27175
  init_util();
27140
27176
 
27177
+ // lib/promql.ts
27178
+ function describeRpcError(status, body) {
27179
+ try {
27180
+ const parsed = JSON.parse(body);
27181
+ const parts = [parsed.message, parsed.details, parsed.hint].filter(Boolean);
27182
+ if (parts.length)
27183
+ return parts.join(" — ");
27184
+ } catch {}
27185
+ return body.trim() || `HTTP ${status}`;
27186
+ }
27187
+ async function callRpc3(name, apiKey, body, opts) {
27188
+ const { apiBaseUrl } = resolveBaseUrls(opts);
27189
+ const res = await fetch(`${apiBaseUrl}/rpc/${name}`, {
27190
+ method: "POST",
27191
+ headers: {
27192
+ "Content-Type": "application/json",
27193
+ "access-token": apiKey,
27194
+ ...orgScopeHeaders(getActiveOrgScope())
27195
+ },
27196
+ body: JSON.stringify(body),
27197
+ signal: AbortSignal.timeout(opts?.timeoutMs ?? 30000)
27198
+ });
27199
+ const text = await res.text().catch(() => "");
27200
+ if (!res.ok) {
27201
+ throw new Error(describeRpcError(res.status, text));
27202
+ }
27203
+ try {
27204
+ return text ? JSON.parse(text) : null;
27205
+ } catch {
27206
+ throw new Error(`${name} returned a body that is not JSON`);
27207
+ }
27208
+ }
27209
+ async function enqueueQuery(apiKey, args, opts) {
27210
+ const body = {
27211
+ p_instance_id: args.instanceId,
27212
+ p_kind: args.kind,
27213
+ p_query: args.query
27214
+ };
27215
+ if (args.kind === "promql_range") {
27216
+ body.p_start = args.start;
27217
+ body.p_end = args.end;
27218
+ body.p_step_s = args.stepS;
27219
+ } else if (args.at) {
27220
+ body.p_at = args.at;
27221
+ }
27222
+ const reply = await callRpc3("instance_query_enqueue", apiKey, body, opts);
27223
+ const jobId = reply?.job_id;
27224
+ if (!jobId) {
27225
+ throw new Error("instance_query_enqueue returned no job_id");
27226
+ }
27227
+ return { jobId, firstAnswerEstimateS: positiveSeconds(reply?.first_answer_estimate_s) };
27228
+ }
27229
+ function positiveSeconds(v) {
27230
+ return typeof v === "number" && Number.isFinite(v) && v > 0 ? v : null;
27231
+ }
27232
+ async function readQueryResult(apiKey, jobId, opts) {
27233
+ const reply = await callRpc3("instance_query_result", apiKey, { p_job_id: jobId }, opts);
27234
+ if (!reply || typeof reply.status !== "string") {
27235
+ throw new Error("instance_query_result returned an unexpected reply");
27236
+ }
27237
+ return reply;
27238
+ }
27239
+ var TERMINAL = new Set(["done", "failed", "expired"]);
27240
+ function isTerminal2(status) {
27241
+ return TERMINAL.has(status);
27242
+ }
27243
+ function pollDelay(attempt) {
27244
+ return Math.min(1000 * 2 ** attempt, 15000);
27245
+ }
27246
+ async function awaitQueryResult(apiKey, jobId, opts) {
27247
+ const now = opts.now ?? (() => Date.now());
27248
+ const sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
27249
+ const deadline = now() + opts.timeoutMs;
27250
+ for (let attempt = 0;; attempt++) {
27251
+ const res = await readQueryResult(apiKey, jobId, opts);
27252
+ if (isTerminal2(res.status))
27253
+ return res;
27254
+ const delay = pollDelay(attempt);
27255
+ if (now() + delay >= deadline)
27256
+ return res;
27257
+ await sleep(delay);
27258
+ }
27259
+ }
27260
+ function formatMetric(metric) {
27261
+ const name = metric.__name__ ?? "";
27262
+ const labels = Object.entries(metric).filter(([k]) => k !== "__name__").sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${k}="${v}"`);
27263
+ if (!labels.length)
27264
+ return name || "{}";
27265
+ return `${name}{${labels.join(", ")}}`;
27266
+ }
27267
+ function pad(s, width) {
27268
+ return s.length >= width ? s : s + " ".repeat(width - s.length);
27269
+ }
27270
+ function renderPromQL(payload) {
27271
+ const lines = [];
27272
+ if (!payload.result.length) {
27273
+ lines.push("Empty result set.");
27274
+ } else if (payload.resultType === "vector") {
27275
+ const rows = payload.result.map((s) => [formatMetric(s.metric), s.value ? s.value[1] : ""]);
27276
+ const w = Math.max(6, ...rows.map((r) => r[0].length));
27277
+ lines.push(`${pad("SERIES", w)} VALUE`);
27278
+ for (const [m, v] of rows)
27279
+ lines.push(`${pad(m, w)} ${v}`);
27280
+ } else {
27281
+ const rows = payload.result.map((s) => {
27282
+ const vs = s.values ?? [];
27283
+ const first = vs.length ? vs[0][1] : "";
27284
+ const last = vs.length ? vs[vs.length - 1][1] : "";
27285
+ return [formatMetric(s.metric), String(vs.length), first, last];
27286
+ });
27287
+ const w = Math.max(6, ...rows.map((r) => r[0].length));
27288
+ lines.push(`${pad("SERIES", w)} POINTS FIRST LAST`);
27289
+ for (const [m, n, f, l] of rows)
27290
+ lines.push(`${pad(m, w)} ${pad(n, 6)} ${f} ${l}`);
27291
+ if (rows.length)
27292
+ lines.push("(first and last sample per series; use --json for every point)");
27293
+ }
27294
+ const series = payload.result.length;
27295
+ const points = payload.result.reduce((n, s) => n + (s.values?.length ?? 0) + (s.value ? 1 : 0), 0);
27296
+ lines.push(`
27297
+ ${series} series, ${points} points`);
27298
+ if (payload.stats.truncated) {
27299
+ lines.push("⚠ TRUNCATED: too large to return whole. Raise --step, narrow the time range, " + "or select fewer series.");
27300
+ }
27301
+ return lines.join(`
27302
+ `);
27303
+ }
27304
+
27141
27305
  // lib/instances.ts
27142
27306
  import * as fs4 from "fs";
27143
27307
 
@@ -38784,9 +38948,29 @@ var LOCAL_INSTALL_MANAGED_ENV_KEYS = [
38784
38948
  "VM_DELETE_AUTH_KEY",
38785
38949
  "VM_SNAPSHOT_AUTH_KEY",
38786
38950
  "VM_FORCE_MERGE_AUTH_KEY",
38787
- "VM_PPROF_AUTH_KEY"
38951
+ "VM_PPROF_AUTH_KEY",
38952
+ "PGAI_INSTANCE_ID",
38953
+ "INSTANCE_JOBS_USER"
38788
38954
  ];
38789
- function buildLocalInstallEnv(existingEnv, imageTag) {
38955
+ function statOwner(file) {
38956
+ if (!file)
38957
+ return;
38958
+ let st;
38959
+ try {
38960
+ st = fs9.statSync(file);
38961
+ } catch {
38962
+ return;
38963
+ }
38964
+ if (!st.isFile())
38965
+ return null;
38966
+ return `${st.uid}:${st.gid}`;
38967
+ }
38968
+ function processOwner() {
38969
+ if (typeof process.getuid !== "function" || typeof process.getgid !== "function")
38970
+ return null;
38971
+ return `${process.getuid()}:${process.getgid()}`;
38972
+ }
38973
+ function buildLocalInstallEnv(existingEnv, imageTag, instanceId, credentialFile) {
38790
38974
  const readRaw = (key) => {
38791
38975
  const re = new RegExp(`^[ ]*(?:export[ ]+)?${key}=(.*)$`, "gm");
38792
38976
  let value = null;
@@ -38810,6 +38994,14 @@ function buildLocalInstallEnv(existingEnv, imageTag) {
38810
38994
  envLines.push(`VM_AUTH_USERNAME=${vmAuthUsername ? stripMatchingQuotes(vmAuthUsername) : "vmauth"}`);
38811
38995
  const vmAuthPassword = readRaw("VM_AUTH_PASSWORD");
38812
38996
  envLines.push(`VM_AUTH_PASSWORD=${vmAuthPassword ? stripMatchingQuotes(vmAuthPassword) : crypto2.randomBytes(18).toString("base64")}`);
38997
+ const instanceIdValue = instanceId?.trim() || readRaw("PGAI_INSTANCE_ID");
38998
+ if (instanceIdValue) {
38999
+ envLines.push(`PGAI_INSTANCE_ID=${stripMatchingQuotes(instanceIdValue)}`);
39000
+ }
39001
+ const owner = statOwner(credentialFile);
39002
+ const credentialOwner = owner === undefined ? processOwner() : owner;
39003
+ if (credentialOwner)
39004
+ envLines.push(`INSTANCE_JOBS_USER=${credentialOwner}`);
38813
39005
  for (const key of VM_ADMIN_AUTH_KEYS) {
38814
39006
  const existing = readRaw(key);
38815
39007
  envLines.push(`${key}=${existing ? stripMatchingQuotes(existing) : crypto2.randomBytes(32).toString("hex")}`);
@@ -40598,16 +40790,6 @@ function checkRunningContainers() {
40598
40790
  return { running: false, containers: [] };
40599
40791
  }
40600
40792
  }
40601
- function planMonitoringRegistration(args) {
40602
- const projectName = args.project?.trim() || undefined;
40603
- if (args.instanceId) {
40604
- return { kind: "adopt", projectName };
40605
- }
40606
- if (!projectName) {
40607
- return { kind: "error-missing-project", projectName: undefined };
40608
- }
40609
- return { kind: "self-register", projectName };
40610
- }
40611
40793
  async function registerMonitoringInstance(apiKey, projectName, opts) {
40612
40794
  const { apiBaseUrl } = resolveBaseUrls2(opts);
40613
40795
  const url = `${apiBaseUrl}/rpc/monitoring_instance_register`;
@@ -40642,7 +40824,8 @@ Debug: Registering monitoring instance...`);
40642
40824
  "Content-Type": "application/json",
40643
40825
  ...orgScopeHeaders2(opts?.orgScope)
40644
40826
  },
40645
- body: JSON.stringify(requestBody)
40827
+ body: JSON.stringify(requestBody),
40828
+ signal: requestTimeoutSignal2(opts?.timeoutMs).signal
40646
40829
  });
40647
40830
  const body = await res.text().catch(() => "");
40648
40831
  if (!res.ok) {
@@ -40677,6 +40860,25 @@ Debug: Registering monitoring instance...`);
40677
40860
  }
40678
40861
  return null;
40679
40862
  }
40863
+ var INSTANCE_ID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
40864
+ function instanceIdToPersist(reg, requestedId) {
40865
+ for (const candidate of [reg?.instanceId, requestedId]) {
40866
+ const value = candidate?.trim();
40867
+ if (value && INSTANCE_ID_RE.test(value))
40868
+ return value;
40869
+ }
40870
+ return null;
40871
+ }
40872
+ function planMonitoringRegistration(args) {
40873
+ const projectName = args.project?.trim() || undefined;
40874
+ if (args.instanceId) {
40875
+ return { kind: "adopt", projectName };
40876
+ }
40877
+ if (!projectName) {
40878
+ return { kind: "error-missing-project", projectName: undefined };
40879
+ }
40880
+ return { kind: "self-register", projectName };
40881
+ }
40680
40882
  var PROJECT_NAME_RE = /^[A-Za-z0-9._-]+$/;
40681
40883
  function resolveAdoptedProject(reg) {
40682
40884
  if (!reg)
@@ -40693,8 +40895,11 @@ function updatePgwatchConfig(configPath, updates) {
40693
40895
  let lines = [];
40694
40896
  if (fs9.existsSync(configPath)) {
40695
40897
  const stats = fs9.statSync(configPath);
40696
- if (!stats.isDirectory()) {
40697
- const content = fs9.readFileSync(configPath, "utf8");
40898
+ if (!stats.isFile()) {
40899
+ throw new Error(`${configPath} exists but is not a regular file; refusing to write through it`);
40900
+ }
40901
+ {
40902
+ const content = fs9.readFileSync(configPath, "utf8").replace(/^\uFEFF+/, "");
40698
40903
  lines = content.split(/\r?\n/).filter((l) => l.trim() !== "");
40699
40904
  }
40700
40905
  }
@@ -40702,6 +40907,7 @@ function updatePgwatchConfig(configPath, updates) {
40702
40907
  const existingIndex = lines.findIndex((l) => l.startsWith(key + "="));
40703
40908
  if (existingIndex >= 0) {
40704
40909
  lines[existingIndex] = `${key}=${value}`;
40910
+ lines = lines.filter((l, i) => i === existingIndex || !l.startsWith(key + "="));
40705
40911
  } else {
40706
40912
  lines.push(`${key}=${value}`);
40707
40913
  }
@@ -40716,6 +40922,129 @@ function updatePgwatchConfig(configPath, updates) {
40716
40922
  console.error(`Warning: could not restrict permissions on ${configPath}: ${message}`);
40717
40923
  }
40718
40924
  }
40925
+ function applyApiKey(projectDir, apiKey) {
40926
+ const configPath = path7.resolve(projectDir, ".pgwatch-config");
40927
+ try {
40928
+ updatePgwatchConfig(configPath, { api_key: apiKey });
40929
+ } catch (err) {
40930
+ console.error(`\u2717 ${err instanceof Error ? err.message : String(err)}`);
40931
+ return false;
40932
+ }
40933
+ console.log(`\u2713 API key saved
40934
+ `);
40935
+ return true;
40936
+ }
40937
+ var DEFAULT_PROMQL_TIMEOUT_S = 11 * 60;
40938
+ function formatSeconds(total) {
40939
+ const s = Math.max(0, Math.round(total));
40940
+ if (s < 90)
40941
+ return `${s}s`;
40942
+ const m = Math.round(s / 60);
40943
+ if (m < 60)
40944
+ return `${m}m`;
40945
+ const h = Math.floor(m / 60);
40946
+ const rem = m % 60;
40947
+ return rem ? `${h}h${rem}m` : `${h}h`;
40948
+ }
40949
+ function parseDurationSeconds(v) {
40950
+ const m = /^(\d+)(s|m|h)?$/.exec(v.trim());
40951
+ if (!m)
40952
+ return null;
40953
+ const n = parseInt(m[1], 10);
40954
+ if (!Number.isFinite(n))
40955
+ return null;
40956
+ return n * (m[2] === "h" ? 3600 : m[2] === "m" ? 60 : 1);
40957
+ }
40958
+ function readInstanceIdFromProject() {
40959
+ try {
40960
+ const { projectDir } = resolvePaths();
40961
+ const configPath = path7.resolve(projectDir, ".pgwatch-config");
40962
+ if (!fs9.existsSync(configPath))
40963
+ return null;
40964
+ const line = fs9.readFileSync(configPath, "utf8").replace(/^\uFEFF+/, "").split(/\r?\n/).find((l) => l.startsWith("instance_id="));
40965
+ const value = line?.slice("instance_id=".length).trim();
40966
+ return value && INSTANCE_ID_RE.test(value) ? value : null;
40967
+ } catch {
40968
+ return null;
40969
+ }
40970
+ }
40971
+ function applyProjectName(projectDir, project) {
40972
+ const configPath = path7.resolve(projectDir, ".pgwatch-config");
40973
+ try {
40974
+ updatePgwatchConfig(configPath, { project_name: project });
40975
+ } catch (err) {
40976
+ console.error(`\u2717 ${err instanceof Error ? err.message : String(err)}`);
40977
+ return false;
40978
+ }
40979
+ console.log(`Using project name: ${project}
40980
+ `);
40981
+ return true;
40982
+ }
40983
+ function persistInstanceId(projectDir, reg, requestedId, alsoWrite = {}) {
40984
+ const instanceId = instanceIdToPersist(reg, requestedId);
40985
+ const updates = { ...alsoWrite, ...instanceId ? { instance_id: instanceId } : {} };
40986
+ if (Object.keys(updates).length === 0)
40987
+ return false;
40988
+ const configPath = path7.resolve(projectDir, ".pgwatch-config");
40989
+ try {
40990
+ updatePgwatchConfig(configPath, updates);
40991
+ return instanceId != null;
40992
+ } catch (err) {
40993
+ const message = err instanceof Error ? err.message : String(err);
40994
+ console.error(`Warning: could not record the monitoring instance id in ${configPath}: ${message}`);
40995
+ return false;
40996
+ }
40997
+ }
40998
+ async function adoptAndPersist(projectDir, apiKey, projectName, instanceId, opts, register = registerMonitoringInstance) {
40999
+ const reg = await register(apiKey, projectName, {
41000
+ orgScope: opts.orgScope,
41001
+ apiBaseUrl: opts.apiBaseUrl,
41002
+ debug: opts.debug,
41003
+ instanceId
41004
+ });
41005
+ const adoptedProject = resolveAdoptedProject(reg);
41006
+ const persisted = persistInstanceId(projectDir, reg, instanceId, {
41007
+ ...adoptedProject != null ? { project_name: adoptedProject } : {}
41008
+ });
41009
+ if (!persisted) {
41010
+ console.error(`\u26A0 Adopted instance ${instanceId} but could not record it in .pgwatch-config \u2014 ` + `instance jobs will idle until that file is writable and local-install is re-run
41011
+ `);
41012
+ }
41013
+ if (adoptedProject != null) {
41014
+ const verb = reg?.created ? "Registered" : "Adopted";
41015
+ console.log(`\u2713 ${verb} monitoring instance (project: ${adoptedProject})
41016
+ `);
41017
+ } else if (reg) {
41018
+ console.error(`\u26A0 Adopted provisioned instance ${instanceId} but the platform returned no project` + (projectName ? ` \u2014 reports will use project '${projectName}'` : ` \u2014 reports will have no project until 'postgresai mon local-install' is re-run with --project <name>`));
41019
+ } else {
41020
+ console.error(`\u26A0 Could not adopt provisioned instance ${instanceId}` + (projectName ? ` \u2014 reports will use project '${projectName}' until 'postgresai mon local-install' is re-run` : ` \u2014 reports will have no project until 'postgresai mon local-install' is re-run with --project <name>`));
41021
+ }
41022
+ return reg;
41023
+ }
41024
+ async function selfRegisterAndPersist(projectDir, apiKey, projectName, opts, register = registerMonitoringInstance) {
41025
+ const reg = await register(apiKey, projectName, {
41026
+ apiBaseUrl: opts.apiBaseUrl,
41027
+ debug: opts.debug,
41028
+ orgScope: opts.orgScope
41029
+ });
41030
+ const persisted = persistInstanceId(projectDir, reg, undefined);
41031
+ if (persisted) {
41032
+ console.log(`\u2713 Registered monitoring instance (project: ${projectName})
41033
+ `);
41034
+ return reg;
41035
+ }
41036
+ const minted = reg?.instanceId?.trim();
41037
+ if (minted) {
41038
+ console.error(`\u26A0 Registered monitoring instance ${minted} but could not record it in .pgwatch-config \u2014 ` + `instance jobs will idle. Add 'instance_id=${minted}' to that file, or re-run with ` + `--instance-id ${minted} (which adopts it rather than registering a second one)
41039
+ `);
41040
+ } else {
41041
+ const reRun = projectName ? `'postgresai mon local-install --project ${projectName}'` : "'postgresai mon local-install --project <name>'";
41042
+ console.error(`\u26A0 Could not register this monitoring instance \u2014 instance jobs will idle until ` + `${reRun} is re-run
41043
+ `);
41044
+ }
41045
+ process.exitCode = 1;
41046
+ return reg;
41047
+ }
40719
41048
  async function applyMonitoringTargetsConfig() {
40720
41049
  console.log("Applying monitoring target configuration...");
40721
41050
  const generateCode = await runCompose(["run", "--rm", "sources-generator"]);
@@ -40872,18 +41201,16 @@ mon.command("local-install").description("install local monitoring stack (genera
40872
41201
  const { projectDir, instancesFile: instancesPath } = await resolveOrInitPaths();
40873
41202
  console.log(`Project directory: ${projectDir}
40874
41203
  `);
40875
- if (opts.project) {
40876
- const cfgPath = path7.resolve(projectDir, ".pgwatch-config");
40877
- updatePgwatchConfig(cfgPath, { project_name: opts.project });
40878
- console.log(`Using project name: ${opts.project}
40879
- `);
41204
+ if (opts.project && !applyProjectName(projectDir, opts.project)) {
41205
+ process.exitCode = 1;
41206
+ return;
40880
41207
  }
40881
41208
  const envFile = path7.resolve(projectDir, ".env");
40882
41209
  const existingEnv = fs9.existsSync(envFile) ? fs9.readFileSync(envFile, "utf8") : "";
40883
41210
  const previousTagMatch = existingEnv.match(/^PGAI_TAG=(.+)$/m);
40884
41211
  const previousTag = previousTagMatch ? previousTagMatch[1].trim() : null;
40885
41212
  const imageTag = opts.tag || package_default.version;
40886
- const { content: envContent, preservedKeys } = buildLocalInstallEnv(existingEnv, imageTag);
41213
+ const { content: envContent, preservedKeys } = buildLocalInstallEnv(existingEnv, imageTag, opts.instanceId || process.env.PGAI_INSTANCE_ID || null, path7.resolve(projectDir, ".pgwatch-config"));
40887
41214
  writeEnvFile(envFile, envContent);
40888
41215
  if (preservedKeys.length > 0) {
40889
41216
  console.log(`Preserved existing .env settings: ${preservedKeys.join(", ")}
@@ -40923,9 +41250,10 @@ Use demo mode without API key: postgres-ai mon local-install --demo`);
40923
41250
  if (apiKey) {
40924
41251
  console.log("Using API key provided via --api-key parameter");
40925
41252
  writeConfig({ apiKey });
40926
- updatePgwatchConfig(path7.resolve(projectDir, ".pgwatch-config"), { api_key: apiKey });
40927
- console.log(`\u2713 API key saved
40928
- `);
41253
+ if (!applyApiKey(projectDir, apiKey)) {
41254
+ process.exitCode = 1;
41255
+ return;
41256
+ }
40929
41257
  } else if (opts.yes) {
40930
41258
  console.log("Auto-yes mode: no API key provided, skipping API key setup");
40931
41259
  console.error("\u26A0 Reports will be generated locally only");
@@ -40940,10 +41268,11 @@ Use demo mode without API key: postgres-ai mon local-install --demo`);
40940
41268
  const trimmedKey = inputApiKey.trim();
40941
41269
  if (trimmedKey) {
40942
41270
  writeConfig({ apiKey: trimmedKey });
40943
- updatePgwatchConfig(path7.resolve(projectDir, ".pgwatch-config"), { api_key: trimmedKey });
41271
+ if (!applyApiKey(projectDir, trimmedKey)) {
41272
+ process.exitCode = 1;
41273
+ return;
41274
+ }
40944
41275
  apiKey = trimmedKey;
40945
- console.log(`\u2713 API key saved
40946
- `);
40947
41276
  break;
40948
41277
  }
40949
41278
  console.error("\u26A0 API key cannot be empty");
@@ -41224,25 +41553,11 @@ Searched: ${demoCandidates.join(", ")}
41224
41553
  const plan = planMonitoringRegistration({ project: opts.project, instanceId });
41225
41554
  const projectName = plan.projectName;
41226
41555
  if (instanceId) {
41227
- const reg = await registerMonitoringInstance(apiKey, projectName, {
41556
+ const reg = await adoptAndPersist(projectDir, apiKey, projectName, instanceId, {
41228
41557
  orgScope: regOrgScope,
41229
41558
  apiBaseUrl: globalOpts.apiBaseUrl,
41230
- debug: !!process.env.DEBUG,
41231
- instanceId
41559
+ debug: !!process.env.DEBUG
41232
41560
  });
41233
- const adoptedProject = resolveAdoptedProject(reg);
41234
- if (adoptedProject != null) {
41235
- updatePgwatchConfig(path7.resolve(projectDir, ".pgwatch-config"), {
41236
- project_name: adoptedProject
41237
- });
41238
- const verb = reg?.created ? "Registered" : "Adopted";
41239
- console.log(`\u2713 ${verb} monitoring instance (project: ${adoptedProject})
41240
- `);
41241
- } else if (reg) {
41242
- console.error(`\u26A0 Adopted provisioned instance ${instanceId} but the platform returned no project` + (projectName ? ` \u2014 reports will use project '${projectName}'` : ` \u2014 reports will have no project until 'postgresai mon local-install' is re-run with --project <name>`));
41243
- } else {
41244
- console.error(`\u26A0 Could not adopt provisioned instance ${instanceId}` + (projectName ? ` \u2014 reports will use project '${projectName}' until 'postgresai mon local-install' is re-run` : ` \u2014 reports will have no project until 'postgresai mon local-install' is re-run with --project <name>`));
41245
- }
41246
41561
  if (reg === null) {
41247
41562
  console.error(`\u26A0 Skipping AAS auto-collection arming because instance adoption failed; it can be enabled later by re-running 'postgresai mon local-install'
41248
41563
  `);
@@ -41267,7 +41582,7 @@ Searched: ${demoCandidates.join(", ")}
41267
41582
  console.error("\u2717 A project name is required for self-registration (the 'postgres-ai-monitoring' default was removed). " + "Re-run with --project <name>, or adopt a console-provisioned instance with --instance-id <uuid>.");
41268
41583
  process.exitCode = 1;
41269
41584
  } else {
41270
- registerMonitoringInstance(apiKey, projectName, {
41585
+ await selfRegisterAndPersist(projectDir, apiKey, projectName, {
41271
41586
  apiBaseUrl: globalOpts.apiBaseUrl,
41272
41587
  debug: !!process.env.DEBUG,
41273
41588
  orgScope: regOrgScope
@@ -41336,7 +41651,8 @@ var MONITORING_CONTAINERS = [
41336
41651
  "self-postgres-exporter",
41337
41652
  "flask-pgss-api",
41338
41653
  "sources-generator",
41339
- "postgres-reports"
41654
+ "postgres-reports",
41655
+ "instance-jobs"
41340
41656
  ];
41341
41657
  var COMPOSE_PROJECT_NAME = "postgres_ai";
41342
41658
  var DOCKER_NETWORK_NAME = `${COMPOSE_PROJECT_NAME}_default`;
@@ -41389,15 +41705,31 @@ mon.command("logs [service]").option("-f, --follow", "follow logs", false).optio
41389
41705
  if (code !== 0)
41390
41706
  process.exitCode = code;
41391
41707
  });
41392
- mon.command("health").description("health check for monitoring services").option("--wait <seconds>", "wait time in seconds for services to become healthy", parseInt, 0).action(async (opts) => {
41393
- const services = [
41708
+ function judgeServiceHealth(service, present, status, health) {
41709
+ if (!present) {
41710
+ return service.optional ? { ok: true, mark: "-", label: "not enabled" } : { ok: false, mark: "\u2717", label: "unreachable" };
41711
+ }
41712
+ if (status !== "running") {
41713
+ return { ok: false, mark: "\u2717", label: `unhealthy (status: ${status})` };
41714
+ }
41715
+ if (service.readHealth && health && health !== "healthy" && health !== "starting") {
41716
+ return { ok: false, mark: "\u2717", label: `unhealthy (${health})` };
41717
+ }
41718
+ return { ok: true, mark: "\u2713", label: "healthy" };
41719
+ }
41720
+ function monitoringHealthServices() {
41721
+ return [
41394
41722
  { name: "Grafana", container: "grafana-with-datasources" },
41395
41723
  { name: "Prometheus", container: "sink-prometheus" },
41396
41724
  { name: "PGWatch (Postgres)", container: "pgwatch-postgres" },
41397
41725
  { name: "PGWatch (Prometheus)", container: "pgwatch-prometheus" },
41398
41726
  { name: "Target DB", container: "target-db" },
41399
- { name: "Sink Postgres", container: "sink-postgres" }
41727
+ { name: "Sink Postgres", container: "sink-postgres" },
41728
+ { name: "Instance jobs", container: "instance-jobs", optional: true, readHealth: true }
41400
41729
  ];
41730
+ }
41731
+ mon.command("health").description("health check for monitoring services").option("--wait <seconds>", "wait time in seconds for services to become healthy", parseInt, 0).action(async (opts) => {
41732
+ const services = monitoringHealthServices();
41401
41733
  const waitTime = opts.wait || 0;
41402
41734
  const maxAttempts = waitTime > 0 ? Math.ceil(waitTime / 5) : 1;
41403
41735
  console.log(`Checking service health...
@@ -41413,16 +41745,12 @@ mon.command("health").description("health check for monitoring services").option
41413
41745
  for (const service of services) {
41414
41746
  try {
41415
41747
  const result = spawnSync2("docker", ["inspect", "-f", "{{.State.Status}}", service.container], { stdio: "pipe" });
41416
- const status = result.stdout.trim();
41417
- if (result.status === 0 && status === "running") {
41418
- console.log(`\u2713 ${service.name}: healthy`);
41419
- } else if (result.status === 0) {
41420
- console.log(`\u2717 ${service.name}: unhealthy (status: ${status})`);
41748
+ const present = result.status === 0;
41749
+ const health = present && service.readHealth ? (spawnSync2("docker", ["inspect", "-f", "{{if .State.Health}}{{.State.Health.Status}}{{end}}", service.container], { stdio: "pipe" }).stdout || "").trim() : "";
41750
+ const verdict = judgeServiceHealth(service, present, (result.stdout || "").trim(), health);
41751
+ console.log(`${verdict.mark} ${service.name}: ${verdict.label}`);
41752
+ if (!verdict.ok)
41421
41753
  allHealthy = false;
41422
- } else {
41423
- console.log(`\u2717 ${service.name}: unreachable`);
41424
- allHealthy = false;
41425
- }
41426
41754
  } catch (error) {
41427
41755
  console.log(`\u2717 ${service.name}: unreachable`);
41428
41756
  allHealthy = false;
@@ -42236,8 +42564,87 @@ program2.command("orgs").description("list organizations this credential can rea
42236
42564
  process.exitCode = 1;
42237
42565
  }
42238
42566
  });
42567
+ withOrgOptions(program2.command("promql")).description("run a PromQL query on a monitoring instance's own metric store").argument("<expr>", "PromQL expression").option("--instance <uuid>", "monitoring instance id (default: this box's .pgwatch-config)").option("--at <rfc3339>", "evaluate an instant query at this time (default: now; ignored with --range)").option("--range", "run a range query (requires --start, --end, --step)").option("--start <rfc3339>", "range start").option("--end <rfc3339>", "range end").option("--step <duration>", "range step, e.g. 60s or 5m").option("--timeout <duration>", "how long to wait for an answer (default: sized from the instance's poll pacing; 11m if the platform does not report it)").option("--json", "output the raw result payload").action(async (expr, opts) => {
42568
+ try {
42569
+ const rootOpts = program2.opts();
42570
+ const cfg = readConfig();
42571
+ const { apiKey } = getConfig(rootOpts);
42572
+ if (!apiKey) {
42573
+ console.error("API key is required. Run 'pgai auth' first or set --api-key.");
42574
+ process.exitCode = 1;
42575
+ return;
42576
+ }
42577
+ const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
42578
+ const instanceId = opts.instance || readInstanceIdFromProject();
42579
+ if (!instanceId) {
42580
+ console.error("No monitoring instance. Pass --instance <uuid>, or run this on a box whose");
42581
+ console.error(".pgwatch-config has an instance_id (written by 'pgai mon local-install').");
42582
+ process.exitCode = 1;
42583
+ return;
42584
+ }
42585
+ const kind = opts.range ? "promql_range" : "promql_instant";
42586
+ const enqueue = { instanceId, kind, query: expr };
42587
+ if (opts.range) {
42588
+ if (!opts.start || !opts.end || !opts.step) {
42589
+ console.error("--range needs --start, --end and --step.");
42590
+ process.exitCode = 1;
42591
+ return;
42592
+ }
42593
+ const stepS = parseDurationSeconds(opts.step);
42594
+ if (stepS === null || stepS < 1) {
42595
+ console.error(`Invalid --step ${opts.step}: expected a duration like 60s, 5m or 1h.`);
42596
+ process.exitCode = 1;
42597
+ return;
42598
+ }
42599
+ enqueue.start = opts.start;
42600
+ enqueue.end = opts.end;
42601
+ enqueue.stepS = stepS;
42602
+ } else if (opts.at) {
42603
+ enqueue.at = opts.at;
42604
+ }
42605
+ const explicitTimeoutS = opts.timeout ? parseDurationSeconds(opts.timeout) : null;
42606
+ if (opts.timeout && (explicitTimeoutS === null || explicitTimeoutS < 1)) {
42607
+ console.error(`Invalid --timeout ${opts.timeout}: expected a duration like 60s or 2m.`);
42608
+ process.exitCode = 1;
42609
+ return;
42610
+ }
42611
+ const { jobId, firstAnswerEstimateS } = await enqueueQuery(apiKey, enqueue, { apiBaseUrl });
42612
+ const timeoutS = explicitTimeoutS ?? (firstAnswerEstimateS === null ? DEFAULT_PROMQL_TIMEOUT_S : Math.min(Math.max(Math.ceil(firstAnswerEstimateS * 1.5) + 30, 60), 1800));
42613
+ console.error(firstAnswerEstimateS === null ? `Queued as job ${jobId}; waiting up to ${formatSeconds(timeoutS)} for the instance's next poll.` : `Queued as job ${jobId}; this instance polls roughly every ${formatSeconds(firstAnswerEstimateS)} ` + `(jittered, so sometimes longer). Waiting up to ${formatSeconds(timeoutS)}.`);
42614
+ const res = await awaitQueryResult(apiKey, jobId, {
42615
+ apiBaseUrl,
42616
+ timeoutMs: timeoutS * 1000
42617
+ });
42618
+ if (!isTerminal2(res.status)) {
42619
+ console.error(`Timed out after ${timeoutS}s. Job ${jobId} is still ${res.status}.`);
42620
+ console.error(res.status === "queued" ? "Nothing has claimed it: check the instance-jobs container is running (it is behind " + "a compose profile), or wait \u2014 a first collection can queue ~20 periods ahead of it." : "The instance is working on it \u2014 wait, or re-run with a longer --timeout.");
42621
+ process.exitCode = 1;
42622
+ return;
42623
+ }
42624
+ if (res.status !== "done" || res.outcome !== "ok") {
42625
+ console.error(`Query ${res.status}${res.failure_class ? ` (${res.failure_class})` : ""}.`);
42626
+ if (res.error)
42627
+ console.error(res.error);
42628
+ process.exitCode = 1;
42629
+ return;
42630
+ }
42631
+ if (!res.result) {
42632
+ console.error("The job finished but carried no result.");
42633
+ process.exitCode = 1;
42634
+ return;
42635
+ }
42636
+ if (opts.json) {
42637
+ console.log(JSON.stringify(res.result, null, 2));
42638
+ return;
42639
+ }
42640
+ console.log(renderPromQL(res.result));
42641
+ } catch (err) {
42642
+ console.error(err instanceof Error ? err.message : String(err));
42643
+ process.exitCode = 1;
42644
+ }
42645
+ });
42239
42646
  var issues = program2.command("issues").description("issues management");
42240
- withOrgOptions(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("--hidden-only", "list only hidden issues (PostgresAI staff)").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (opts) => {
42647
+ withOrgOptions(issues.command("list")).description("list issues").option("--status <status>", "filter by status: open (default), closed, or 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("--hidden-only", "list only hidden issues (PostgresAI staff)").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (opts) => {
42241
42648
  const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Fetching issues...");
42242
42649
  try {
42243
42650
  const rootOpts = program2.opts();
@@ -42252,11 +42659,15 @@ withOrgOptions(issues.command("list")).description("list issues").option("--stat
42252
42659
  const orgScope = getActiveOrgScope2() ?? { source: "token" };
42253
42660
  const orgId = configOrgIdForBody(apiKey, cfg.orgId);
42254
42661
  const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
42662
+ const statusArg = (opts.status ?? "open").trim().toLowerCase();
42255
42663
  let statusFilter;
42256
- if (opts.status === "open") {
42257
- statusFilter = "open";
42258
- } else if (opts.status === "closed") {
42259
- statusFilter = "closed";
42664
+ if (statusArg === "open" || statusArg === "closed") {
42665
+ statusFilter = statusArg;
42666
+ } else if (statusArg !== "all") {
42667
+ spinner.stop();
42668
+ console.error("--status must be open, closed, or all");
42669
+ process.exitCode = 1;
42670
+ return;
42260
42671
  }
42261
42672
  const result = await fetchIssues2({
42262
42673
  apiKey,
@@ -42270,7 +42681,7 @@ withOrgOptions(issues.command("list")).description("list issues").option("--stat
42270
42681
  debug: !!opts.debug
42271
42682
  });
42272
42683
  spinner.stop();
42273
- const trimmed = Array.isArray(result) ? result.map((r) => withVisibleHiddenFlag2({
42684
+ const trimmed = Array.isArray(result) ? result.map((r) => presentIssue2({
42274
42685
  id: r.id,
42275
42686
  title: r.title,
42276
42687
  status: r.status,
@@ -42308,7 +42719,7 @@ withOrgOptions(issues.command("view <issueId>")).description("view issue details
42308
42719
  spinner.update("Fetching comments...");
42309
42720
  const comments = await fetchIssueComments2({ apiKey, apiBaseUrl, issueId, debug: !!opts.debug });
42310
42721
  spinner.stop();
42311
- const combined = { issue: withVisibleHiddenFlag2(issue), comments };
42722
+ const combined = { issue: presentIssue2(issue), comments };
42312
42723
  printResult(combined, opts.json);
42313
42724
  } catch (err) {
42314
42725
  spinner.stop();
@@ -43512,14 +43923,23 @@ if (__require.main == __require.module) {
43512
43923
  export {
43513
43924
  LOCAL_INSTALL_MANAGED_ENV_KEYS,
43514
43925
  ORG_SCOPED_COMMANDS,
43926
+ adoptAndPersist,
43927
+ applyApiKey,
43928
+ applyProjectName,
43515
43929
  buildLocalInstallEnv,
43930
+ instanceIdToPersist,
43516
43931
  isValidComposeYaml,
43932
+ judgeServiceHealth,
43933
+ monitoringHealthServices,
43934
+ persistInstanceId,
43517
43935
  planMonitoringRegistration,
43518
43936
  program2 as program,
43519
43937
  readDeployedTag,
43520
43938
  refreshBundledComposeIfStale,
43521
43939
  registerMonitoringInstance,
43522
43940
  resolveAdoptedProject,
43941
+ selfRegisterAndPersist,
43523
43942
  shouldScaleOutNodeExporter,
43943
+ statOwner,
43524
43944
  updatePgwatchConfig
43525
43945
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postgresai",
3
- "version": "0.17.0-rc.2",
3
+ "version": "0.17.0-rc.3",
4
4
  "description": "postgres_ai CLI",
5
5
  "license": "Apache-2.0",
6
6
  "private": false,