postgresai 0.17.0-rc.1 → 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.
@@ -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.1",
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.1";
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) {
@@ -17473,7 +17494,7 @@ async function fetchIssue(params) {
17473
17494
  }
17474
17495
  }
17475
17496
  async function createIssue(params) {
17476
- const { apiKey, apiBaseUrl, orgScope, title, orgId, description, projectId, labels, debug } = params;
17497
+ const { apiKey, apiBaseUrl, orgScope, title, orgId, description, projectId, labels, hidden, debug } = params;
17477
17498
  if (!apiKey) {
17478
17499
  throw new Error("API key is required");
17479
17500
  }
@@ -17498,6 +17519,9 @@ async function createIssue(params) {
17498
17519
  if (labels && labels.length > 0) {
17499
17520
  bodyObj.labels = labels;
17500
17521
  }
17522
+ if (hidden === true) {
17523
+ bodyObj.is_hidden = true;
17524
+ }
17501
17525
  const body = JSON.stringify(bodyObj);
17502
17526
  const headers = issueRequestHeaders(apiKey, orgScope);
17503
17527
  if (debug) {
@@ -17579,15 +17603,15 @@ async function createIssueComment(params) {
17579
17603
  }
17580
17604
  }
17581
17605
  async function updateIssue(params) {
17582
- const { apiKey, apiBaseUrl, orgScope, issueId, title, description, status, labels, debug } = params;
17606
+ const { apiKey, apiBaseUrl, orgScope, issueId, title, description, status, labels, hidden, debug } = params;
17583
17607
  if (!apiKey) {
17584
17608
  throw new Error("API key is required");
17585
17609
  }
17586
17610
  if (!issueId) {
17587
17611
  throw new Error("issueId is required");
17588
17612
  }
17589
- if (title === undefined && description === undefined && status === undefined && labels === undefined) {
17590
- throw new Error("At least one field to update is required (title, description, status, or labels)");
17613
+ if (title === undefined && description === undefined && status === undefined && labels === undefined && hidden === undefined) {
17614
+ throw new Error("At least one field to update is required (title, description, status, labels, or hidden)");
17591
17615
  }
17592
17616
  const base = normalizeBaseUrl(apiBaseUrl);
17593
17617
  const url = new URL(`${base}/rpc/issue_update`);
@@ -17606,6 +17630,9 @@ async function updateIssue(params) {
17606
17630
  if (labels !== undefined) {
17607
17631
  bodyObj.p_labels = labels;
17608
17632
  }
17633
+ if (hidden !== undefined) {
17634
+ bodyObj.p_is_hidden = hidden;
17635
+ }
17609
17636
  const body = JSON.stringify(bodyObj);
17610
17637
  const headers = issueRequestHeaders(apiKey, orgScope);
17611
17638
  if (debug) {
@@ -24947,6 +24974,13 @@ var ORG_ID_TOOL_PROPERTY = {
24947
24974
  description: "Organization ID. Optional with a per-organization token (uses the stored org); 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."
24948
24975
  }
24949
24976
  };
24977
+ function parseHiddenArg(raw) {
24978
+ if (raw === undefined || raw === null)
24979
+ return {};
24980
+ if (typeof raw === "boolean")
24981
+ return { value: raw };
24982
+ return { error: { content: [{ type: "text", text: "is_hidden must be a boolean" }], isError: true } };
24983
+ }
24950
24984
  async function handleToolCall(req, rootOpts, extra) {
24951
24985
  const toolName = req.params.name;
24952
24986
  const args = req.params.arguments || {};
@@ -24981,7 +25015,7 @@ async function handleToolCall(req, rootOpts, extra) {
24981
25015
  const offset = args.offset !== undefined ? Number(args.offset) : undefined;
24982
25016
  const hiddenOnly = Boolean(args.hidden_only);
24983
25017
  const issues = await fetchIssues({ apiKey, apiBaseUrl, orgId, orgScope: scope.orgScope, status, limit, offset, hiddenOnly, debug });
24984
- const trimmed = issues.map((issue) => withVisibleHiddenFlag(issue));
25018
+ const trimmed = issues.map((issue) => presentIssue(issue));
24985
25019
  return { content: [{ type: "text", text: JSON.stringify(trimmed, null, 2) }] };
24986
25020
  }
24987
25021
  if (toolName === "view_issue") {
@@ -24997,7 +25031,7 @@ async function handleToolCall(req, rootOpts, extra) {
24997
25031
  return { content: [{ type: "text", text: "Issue not found" }], isError: true };
24998
25032
  }
24999
25033
  const comments = await fetchIssueComments({ apiKey, apiBaseUrl, issueId, orgScope: scope.orgScope, debug });
25000
- const combined = { issue: withVisibleHiddenFlag(issue), comments };
25034
+ const combined = { issue: presentIssue(issue), comments };
25001
25035
  return { content: [{ type: "text", text: JSON.stringify(combined, null, 2) }] };
25002
25036
  }
25003
25037
  if (toolName === "post_issue_comment") {
@@ -25033,6 +25067,10 @@ async function handleToolCall(req, rootOpts, extra) {
25033
25067
  let description = rawDescription ? interpretEscapes(rawDescription) : undefined;
25034
25068
  const projectId = args.project_id !== undefined ? Number(args.project_id) : undefined;
25035
25069
  const labels = Array.isArray(args.labels) ? args.labels.map(String) : undefined;
25070
+ const hiddenArg = parseHiddenArg(args.is_hidden);
25071
+ if (hiddenArg.error)
25072
+ return hiddenArg.error;
25073
+ const hidden = hiddenArg.value === true;
25036
25074
  const attachments = Array.isArray(args.attachments) ? args.attachments.map(String).filter((p) => p.length > 0) : [];
25037
25075
  const scope = resolveMcpOrgScope(args, apiKey, cfg);
25038
25076
  if (scope.error)
@@ -25046,7 +25084,7 @@ async function handleToolCall(req, rootOpts, extra) {
25046
25084
  const uploaded = await uploadAttachments({ apiKey, orgScope: scope.orgScope, storageBaseUrl, attachmentPaths: attachments, debug });
25047
25085
  description = appendAttachmentsToContent(description ?? "", uploaded);
25048
25086
  }
25049
- const result = await createIssue({ apiKey, apiBaseUrl, title, orgId, orgScope: scope.orgScope, description, projectId, labels, debug });
25087
+ const result = await createIssue({ apiKey, apiBaseUrl, title, orgId, orgScope: scope.orgScope, description, projectId, labels, hidden, debug });
25050
25088
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
25051
25089
  }
25052
25090
  if (toolName === "update_issue") {
@@ -25060,9 +25098,13 @@ async function handleToolCall(req, rootOpts, extra) {
25060
25098
  let description = rawDescription !== undefined ? interpretEscapes(rawDescription) : undefined;
25061
25099
  const status = args.status !== undefined ? Number(args.status) : undefined;
25062
25100
  const labels = Array.isArray(args.labels) ? args.labels.map(String) : undefined;
25101
+ const hiddenArg = parseHiddenArg(args.is_hidden);
25102
+ if (hiddenArg.error)
25103
+ return hiddenArg.error;
25104
+ const hidden = hiddenArg.value;
25063
25105
  const attachments = Array.isArray(args.attachments) ? args.attachments.map(String).filter((p) => p.length > 0) : [];
25064
- if (title === undefined && description === undefined && status === undefined && labels === undefined && attachments.length === 0) {
25065
- return { content: [{ type: "text", text: "At least one field to update is required (title, description, status, labels, or attachments)" }], isError: true };
25106
+ if (title === undefined && description === undefined && status === undefined && labels === undefined && hidden === undefined && attachments.length === 0) {
25107
+ return { content: [{ type: "text", text: "At least one field to update is required (title, description, status, labels, is_hidden, or attachments)" }], isError: true };
25066
25108
  }
25067
25109
  if (status !== undefined && (Number.isNaN(status) || status !== 0 && status !== 1)) {
25068
25110
  return { content: [{ type: "text", text: "status must be 0 (open) or 1 (closed)" }], isError: true };
@@ -25082,7 +25124,7 @@ async function handleToolCall(req, rootOpts, extra) {
25082
25124
  const uploaded = await uploadAttachments({ apiKey, orgScope: scope.orgScope, storageBaseUrl, attachmentPaths: attachments, debug });
25083
25125
  description = appendAttachmentsToContent(description, uploaded);
25084
25126
  }
25085
- const result = await updateIssue({ apiKey, apiBaseUrl, issueId, title, description, status, labels, orgScope: scope.orgScope, debug });
25127
+ const result = await updateIssue({ apiKey, apiBaseUrl, issueId, title, description, status, labels, hidden, orgScope: scope.orgScope, debug });
25086
25128
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
25087
25129
  }
25088
25130
  if (toolName === "update_issue_comment") {
@@ -25293,7 +25335,7 @@ async function startMcpServer(rootOpts, extra) {
25293
25335
  type: "object",
25294
25336
  properties: {
25295
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." },
25296
- 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'." },
25297
25339
  limit: { type: "number", description: "Max number of issues to return (default: 20)" },
25298
25340
  offset: { type: "number", description: "Number of issues to skip (default: 0)" },
25299
25341
  hidden_only: { type: "boolean", description: "List only hidden issues (PostgresAI staff; same as CLI --hidden-only)" },
@@ -25357,6 +25399,7 @@ async function startMcpServer(rootOpts, extra) {
25357
25399
  items: { type: "string" },
25358
25400
  description: "Local file paths to upload and append as markdown links to the description (images render inline)"
25359
25401
  },
25402
+ is_hidden: { type: "boolean", description: "Create as a hidden, staff-internal issue (PostgresAI staff only; same as CLI --hidden)" },
25360
25403
  debug: { type: "boolean", description: "Enable verbose debug logs" }
25361
25404
  },
25362
25405
  required: ["title"],
@@ -25365,7 +25408,7 @@ async function startMcpServer(rootOpts, extra) {
25365
25408
  },
25366
25409
  {
25367
25410
  name: "update_issue",
25368
- description: "Update an existing issue (title, description, status, labels). Use status=1 to close, status=0 to reopen. Local files passed via 'attachments' are uploaded and appended to 'description'; if 'description' is omitted, the existing description is fetched first and appended to.",
25411
+ description: "Update an existing issue (title, description, status, labels, is_hidden). Use status=1 to close, status=0 to reopen. Local files passed via 'attachments' are uploaded and appended to 'description'; if 'description' is omitted, the existing description is fetched first and appended to.",
25369
25412
  inputSchema: {
25370
25413
  type: "object",
25371
25414
  properties: {
@@ -25384,6 +25427,7 @@ async function startMcpServer(rootOpts, extra) {
25384
25427
  items: { type: "string" },
25385
25428
  description: "Local file paths to upload and append as markdown links (images render inline). When provided without 'description', the existing description is fetched and appended to."
25386
25429
  },
25430
+ is_hidden: { type: "boolean", description: "true hides the issue, false unhides it (PostgresAI staff only; same as CLI --hidden / --no-hidden). Omit (or pass null) to leave unchanged." },
25387
25431
  debug: { type: "boolean", description: "Enable verbose debug logs" }
25388
25432
  },
25389
25433
  required: ["issue_id"],
@@ -25573,6 +25617,10 @@ async function startMcpServer(rootOpts, extra) {
25573
25617
  }
25574
25618
 
25575
25619
  // lib/issues.ts
25620
+ var IssueStatus2 = {
25621
+ OPEN: 0,
25622
+ CLOSED: 1
25623
+ };
25576
25624
  function issueRequestHeaders2(apiKey, orgScope) {
25577
25625
  return buildAuthHeaders(apiKey, orgScope, {
25578
25626
  Prefer: "return=representation",
@@ -25589,6 +25637,17 @@ function withVisibleHiddenFlag2(issue) {
25589
25637
  const { is_hidden: _dropped, ...rest } = issue;
25590
25638
  return rest;
25591
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
+ }
25592
25651
  async function fetchIssues2(params) {
25593
25652
  const { apiKey, apiBaseUrl, orgScope, orgId, status, limit = 20, offset = 0, hiddenOnly, debug } = params;
25594
25653
  if (!apiKey) {
@@ -25731,7 +25790,7 @@ async function fetchIssue2(params) {
25731
25790
  }
25732
25791
  }
25733
25792
  async function createIssue2(params) {
25734
- const { apiKey, apiBaseUrl, orgScope, title, orgId, description, projectId, labels, debug } = params;
25793
+ const { apiKey, apiBaseUrl, orgScope, title, orgId, description, projectId, labels, hidden, debug } = params;
25735
25794
  if (!apiKey) {
25736
25795
  throw new Error("API key is required");
25737
25796
  }
@@ -25756,6 +25815,9 @@ async function createIssue2(params) {
25756
25815
  if (labels && labels.length > 0) {
25757
25816
  bodyObj.labels = labels;
25758
25817
  }
25818
+ if (hidden === true) {
25819
+ bodyObj.is_hidden = true;
25820
+ }
25759
25821
  const body = JSON.stringify(bodyObj);
25760
25822
  const headers = issueRequestHeaders2(apiKey, orgScope);
25761
25823
  if (debug) {
@@ -25837,15 +25899,15 @@ async function createIssueComment2(params) {
25837
25899
  }
25838
25900
  }
25839
25901
  async function updateIssue2(params) {
25840
- const { apiKey, apiBaseUrl, orgScope, issueId, title, description, status, labels, debug } = params;
25902
+ const { apiKey, apiBaseUrl, orgScope, issueId, title, description, status, labels, hidden, debug } = params;
25841
25903
  if (!apiKey) {
25842
25904
  throw new Error("API key is required");
25843
25905
  }
25844
25906
  if (!issueId) {
25845
25907
  throw new Error("issueId is required");
25846
25908
  }
25847
- if (title === undefined && description === undefined && status === undefined && labels === undefined) {
25848
- throw new Error("At least one field to update is required (title, description, status, or labels)");
25909
+ if (title === undefined && description === undefined && status === undefined && labels === undefined && hidden === undefined) {
25910
+ throw new Error("At least one field to update is required (title, description, status, labels, or hidden)");
25849
25911
  }
25850
25912
  const base = normalizeBaseUrl(apiBaseUrl);
25851
25913
  const url = new URL(`${base}/rpc/issue_update`);
@@ -25864,6 +25926,9 @@ async function updateIssue2(params) {
25864
25926
  if (labels !== undefined) {
25865
25927
  bodyObj.p_labels = labels;
25866
25928
  }
25929
+ if (hidden !== undefined) {
25930
+ bodyObj.p_is_hidden = hidden;
25931
+ }
25867
25932
  const body = JSON.stringify(bodyObj);
25868
25933
  const headers = issueRequestHeaders2(apiKey, orgScope);
25869
25934
  if (debug) {
@@ -27109,6 +27174,134 @@ async function destroySnapshot(params) {
27109
27174
  // bin/postgres-ai.ts
27110
27175
  init_util();
27111
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
+
27112
27305
  // lib/instances.ts
27113
27306
  import * as fs4 from "fs";
27114
27307
 
@@ -38672,47 +38865,121 @@ function stripMatchingQuotes(value) {
38672
38865
  }
38673
38866
  return trimmed;
38674
38867
  }
38868
+ var VM_ADMIN_AUTH_KEYS = [
38869
+ "VM_DELETE_AUTH_KEY",
38870
+ "VM_SNAPSHOT_AUTH_KEY",
38871
+ "VM_FORCE_MERGE_AUTH_KEY",
38872
+ "VM_PPROF_AUTH_KEY"
38873
+ ];
38675
38874
  var REQUIRED_ENV_KEYS = [
38676
38875
  { key: "REPLICATOR_PASSWORD", defaultValue: () => crypto2.randomBytes(32).toString("hex"), introducedIn: "0.13" },
38677
38876
  { key: "VM_AUTH_USERNAME", defaultValue: () => "vmauth", introducedIn: "0.15" },
38678
- { key: "VM_AUTH_PASSWORD", defaultValue: () => crypto2.randomBytes(18).toString("base64"), introducedIn: "0.15" }
38877
+ { key: "VM_AUTH_PASSWORD", defaultValue: () => crypto2.randomBytes(18).toString("base64"), introducedIn: "0.15" },
38878
+ { key: "VM_DELETE_AUTH_KEY", defaultValue: () => crypto2.randomBytes(32).toString("hex"), introducedIn: "0.17" },
38879
+ { key: "VM_SNAPSHOT_AUTH_KEY", defaultValue: () => crypto2.randomBytes(32).toString("hex"), introducedIn: "0.17" },
38880
+ { key: "VM_FORCE_MERGE_AUTH_KEY", defaultValue: () => crypto2.randomBytes(32).toString("hex"), introducedIn: "0.17" },
38881
+ { key: "VM_PPROF_AUTH_KEY", defaultValue: () => crypto2.randomBytes(32).toString("hex"), introducedIn: "0.17" }
38679
38882
  ];
38680
38883
  function ensureRequiredEnvVars(projectDir) {
38681
38884
  const envFile = path7.resolve(projectDir, ".env");
38682
38885
  const existing = fs9.existsSync(envFile) ? fs9.readFileSync(envFile, "utf8") : "";
38683
38886
  const added = [];
38684
38887
  const appendLines = [];
38888
+ let content = existing;
38685
38889
  for (const spec of REQUIRED_ENV_KEYS) {
38686
- const re = new RegExp(`^${spec.key}=`, "m");
38687
- if (!re.test(existing)) {
38890
+ const present = new RegExp(`^[ ]*(?:export[ ]+)?${spec.key}=`, "m");
38891
+ if (!present.test(content)) {
38688
38892
  appendLines.push(`${spec.key}=${spec.defaultValue()}`);
38689
38893
  added.push(spec.key);
38894
+ continue;
38895
+ }
38896
+ if (VM_ADMIN_AUTH_KEYS.includes(spec.key)) {
38897
+ const blank = new RegExp(`^([ ]*(?:export[ ]+)?${spec.key}=)[ ]*(?:""|'')?[ ]*\r?$`, "gm");
38898
+ if (blank.test(content)) {
38899
+ const value = spec.defaultValue();
38900
+ content = content.replace(blank, (_match, prefix) => `${prefix}${value}`);
38901
+ added.push(spec.key);
38902
+ }
38690
38903
  }
38691
38904
  }
38692
- if (appendLines.length === 0) {
38905
+ if (appendLines.length === 0 && content === existing) {
38693
38906
  return added;
38694
38907
  }
38695
- const needsTrailingNewline = existing.length > 0 && !existing.endsWith(`
38908
+ const needsTrailingNewline = content.length > 0 && !content.endsWith(`
38696
38909
  `);
38697
- const newContent = existing + (needsTrailingNewline ? `
38698
- ` : "") + appendLines.join(`
38910
+ const newContent = content + (needsTrailingNewline ? `
38911
+ ` : "") + (appendLines.length > 0 ? appendLines.join(`
38699
38912
  `) + `
38700
- `;
38701
- fs9.writeFileSync(envFile, newContent, { encoding: "utf8", mode: 384 });
38913
+ ` : "");
38914
+ writeEnvFile(envFile, newContent);
38702
38915
  return added;
38703
38916
  }
38917
+ function writeEnvFile(envFile, content) {
38918
+ let targetFile = envFile;
38919
+ try {
38920
+ if (fs9.existsSync(envFile))
38921
+ targetFile = fs9.realpathSync(envFile);
38922
+ } catch {}
38923
+ const tmpFile = `${targetFile}.tmp`;
38924
+ fs9.writeFileSync(tmpFile, content, { encoding: "utf8", mode: 384 });
38925
+ try {
38926
+ fs9.chmodSync(tmpFile, 384);
38927
+ if (fs9.existsSync(targetFile)) {
38928
+ const st = fs9.statSync(targetFile);
38929
+ fs9.chownSync(tmpFile, st.uid, st.gid);
38930
+ }
38931
+ } catch {}
38932
+ try {
38933
+ fs9.renameSync(tmpFile, targetFile);
38934
+ } catch (error) {
38935
+ try {
38936
+ fs9.unlinkSync(tmpFile);
38937
+ } catch {}
38938
+ throw error;
38939
+ }
38940
+ }
38704
38941
  var LOCAL_INSTALL_MANAGED_ENV_KEYS = [
38705
38942
  "PGAI_TAG",
38706
38943
  "PGAI_REGISTRY",
38707
38944
  "GF_SECURITY_ADMIN_PASSWORD",
38708
38945
  "REPLICATOR_PASSWORD",
38709
38946
  "VM_AUTH_USERNAME",
38710
- "VM_AUTH_PASSWORD"
38947
+ "VM_AUTH_PASSWORD",
38948
+ "VM_DELETE_AUTH_KEY",
38949
+ "VM_SNAPSHOT_AUTH_KEY",
38950
+ "VM_FORCE_MERGE_AUTH_KEY",
38951
+ "VM_PPROF_AUTH_KEY",
38952
+ "PGAI_INSTANCE_ID",
38953
+ "INSTANCE_JOBS_USER"
38711
38954
  ];
38712
- 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) {
38713
38974
  const readRaw = (key) => {
38714
- const m = existingEnv.match(new RegExp(`^${key}=(.+)$`, "m"));
38715
- return m ? m[1].trim() : null;
38975
+ const re = new RegExp(`^[ ]*(?:export[ ]+)?${key}=(.*)$`, "gm");
38976
+ let value = null;
38977
+ for (const m of existingEnv.matchAll(re)) {
38978
+ value = m[1].trim();
38979
+ }
38980
+ if (value === null)
38981
+ return null;
38982
+ return stripMatchingQuotes(value) === "" ? null : value;
38716
38983
  };
38717
38984
  const envLines = [`PGAI_TAG=${imageTag}`];
38718
38985
  const registry = readRaw("PGAI_REGISTRY");
@@ -38727,11 +38994,23 @@ function buildLocalInstallEnv(existingEnv, imageTag) {
38727
38994
  envLines.push(`VM_AUTH_USERNAME=${vmAuthUsername ? stripMatchingQuotes(vmAuthUsername) : "vmauth"}`);
38728
38995
  const vmAuthPassword = readRaw("VM_AUTH_PASSWORD");
38729
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}`);
39005
+ for (const key of VM_ADMIN_AUTH_KEYS) {
39006
+ const existing = readRaw(key);
39007
+ envLines.push(`${key}=${existing ? stripMatchingQuotes(existing) : crypto2.randomBytes(32).toString("hex")}`);
39008
+ }
38730
39009
  const preservedKeys = [];
38731
39010
  for (const line of existingEnv.split(/\r?\n/)) {
38732
39011
  if (!line.trim())
38733
39012
  continue;
38734
- const m = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)=/);
39013
+ const m = line.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)=/);
38735
39014
  if (m) {
38736
39015
  if (LOCAL_INSTALL_MANAGED_ENV_KEYS.includes(m[1]))
38737
39016
  continue;
@@ -38743,9 +39022,9 @@ function buildLocalInstallEnv(existingEnv, imageTag) {
38743
39022
  `) + `
38744
39023
  `, preservedKeys };
38745
39024
  }
38746
- async function execFilePromise(file, args) {
39025
+ async function execFilePromise(file, args, options = {}) {
38747
39026
  return new Promise((resolve, reject) => {
38748
- childProcess.execFile(file, args, (error, stdout, stderr) => {
39027
+ childProcess.execFile(file, args, options, (error, stdout, stderr) => {
38749
39028
  if (error) {
38750
39029
  const err = error;
38751
39030
  err.code = typeof error.code === "number" ? error.code : 1;
@@ -40484,6 +40763,12 @@ function isDockerRunning() {
40484
40763
  return false;
40485
40764
  }
40486
40765
  }
40766
+ function shouldScaleOutNodeExporter(args) {
40767
+ const upIndex = args.indexOf("up");
40768
+ if (upIndex === -1)
40769
+ return false;
40770
+ return !args.slice(upIndex + 1).some((a) => !a.startsWith("-"));
40771
+ }
40487
40772
  function getComposeCmd() {
40488
40773
  const tryCmd = (cmd, args) => spawnSync2(cmd, args, { stdio: "ignore", timeout: 5000 }).status === 0;
40489
40774
  if (tryCmd("docker", ["compose", "version"]))
@@ -40505,16 +40790,6 @@ function checkRunningContainers() {
40505
40790
  return { running: false, containers: [] };
40506
40791
  }
40507
40792
  }
40508
- function planMonitoringRegistration(args) {
40509
- const projectName = args.project?.trim() || undefined;
40510
- if (args.instanceId) {
40511
- return { kind: "adopt", projectName };
40512
- }
40513
- if (!projectName) {
40514
- return { kind: "error-missing-project", projectName: undefined };
40515
- }
40516
- return { kind: "self-register", projectName };
40517
- }
40518
40793
  async function registerMonitoringInstance(apiKey, projectName, opts) {
40519
40794
  const { apiBaseUrl } = resolveBaseUrls2(opts);
40520
40795
  const url = `${apiBaseUrl}/rpc/monitoring_instance_register`;
@@ -40549,7 +40824,8 @@ Debug: Registering monitoring instance...`);
40549
40824
  "Content-Type": "application/json",
40550
40825
  ...orgScopeHeaders2(opts?.orgScope)
40551
40826
  },
40552
- body: JSON.stringify(requestBody)
40827
+ body: JSON.stringify(requestBody),
40828
+ signal: requestTimeoutSignal2(opts?.timeoutMs).signal
40553
40829
  });
40554
40830
  const body = await res.text().catch(() => "");
40555
40831
  if (!res.ok) {
@@ -40584,6 +40860,25 @@ Debug: Registering monitoring instance...`);
40584
40860
  }
40585
40861
  return null;
40586
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
+ }
40587
40882
  var PROJECT_NAME_RE = /^[A-Za-z0-9._-]+$/;
40588
40883
  function resolveAdoptedProject(reg) {
40589
40884
  if (!reg)
@@ -40600,8 +40895,11 @@ function updatePgwatchConfig(configPath, updates) {
40600
40895
  let lines = [];
40601
40896
  if (fs9.existsSync(configPath)) {
40602
40897
  const stats = fs9.statSync(configPath);
40603
- if (!stats.isDirectory()) {
40604
- 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+/, "");
40605
40903
  lines = content.split(/\r?\n/).filter((l) => l.trim() !== "");
40606
40904
  }
40607
40905
  }
@@ -40609,6 +40907,7 @@ function updatePgwatchConfig(configPath, updates) {
40609
40907
  const existingIndex = lines.findIndex((l) => l.startsWith(key + "="));
40610
40908
  if (existingIndex >= 0) {
40611
40909
  lines[existingIndex] = `${key}=${value}`;
40910
+ lines = lines.filter((l, i) => i === existingIndex || !l.startsWith(key + "="));
40612
40911
  } else {
40613
40912
  lines.push(`${key}=${value}`);
40614
40913
  }
@@ -40623,6 +40922,129 @@ function updatePgwatchConfig(configPath, updates) {
40623
40922
  console.error(`Warning: could not restrict permissions on ${configPath}: ${message}`);
40624
40923
  }
40625
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
+ }
40626
41048
  async function applyMonitoringTargetsConfig() {
40627
41049
  console.log("Applying monitoring target configuration...");
40628
41050
  const generateCode = await runCompose(["run", "--rm", "sources-generator"]);
@@ -40696,7 +41118,7 @@ async function runCompose(args, grafanaPassword) {
40696
41118
  }
40697
41119
  }
40698
41120
  const finalArgs = [...args];
40699
- if (process.platform === "darwin" && args.includes("up")) {
41121
+ if (process.platform === "darwin" && shouldScaleOutNodeExporter(args)) {
40700
41122
  finalArgs.push("--scale", "self-node-exporter=0");
40701
41123
  }
40702
41124
  return new Promise((resolve) => {
@@ -40708,6 +41130,51 @@ async function runCompose(args, grafanaPassword) {
40708
41130
  child.on("close", (code) => resolve(code || 0));
40709
41131
  });
40710
41132
  }
41133
+ async function applySinkPrometheusConfig(projectDir) {
41134
+ const composeCmd = getComposeCmd();
41135
+ if (!composeCmd) {
41136
+ console.error(`
41137
+ \u2717 docker compose not found, so the sink-prometheus config was not applied.`);
41138
+ return false;
41139
+ }
41140
+ let running;
41141
+ try {
41142
+ const { stdout } = await execFilePromise(composeCmd[0], [
41143
+ ...composeCmd.slice(1),
41144
+ "-f",
41145
+ path7.resolve(projectDir, "docker-compose.yml"),
41146
+ "ps",
41147
+ "-q",
41148
+ "sink-prometheus"
41149
+ ], { cwd: projectDir });
41150
+ running = stdout.trim().length > 0;
41151
+ } catch (error) {
41152
+ console.error(`
41153
+ \u2717 Could not determine whether sink-prometheus is running.`);
41154
+ console.error(" The admin-endpoint keys may NOT be in effect. To apply them:");
41155
+ console.error(" docker compose up -d --no-deps sink-prometheus");
41156
+ if (process.env.DEBUG) {
41157
+ console.error(` ${error instanceof Error ? error.message : String(error)}`);
41158
+ }
41159
+ return false;
41160
+ }
41161
+ if (!running) {
41162
+ console.log(`
41163
+ (sink-prometheus is not running - the new config applies when you start the stack)`);
41164
+ return true;
41165
+ }
41166
+ console.log(`
41167
+ Applying sink-prometheus config (VictoriaMetrics admin keys, #359)...`);
41168
+ const code = await runCompose(["up", "-d", "--no-deps", "sink-prometheus"]);
41169
+ if (code !== 0) {
41170
+ console.error("\u2717 Could not apply the sink-prometheus config.");
41171
+ console.error(" The admin-endpoint keys are NOT in effect until you run:");
41172
+ console.error(" docker compose up -d --no-deps sink-prometheus");
41173
+ return false;
41174
+ }
41175
+ console.log("\u2713 sink-prometheus is running the current config");
41176
+ return true;
41177
+ }
40711
41178
  program2.command("help").description("show help").action(() => {
40712
41179
  program2.outputHelp();
40713
41180
  });
@@ -40734,19 +41201,17 @@ mon.command("local-install").description("install local monitoring stack (genera
40734
41201
  const { projectDir, instancesFile: instancesPath } = await resolveOrInitPaths();
40735
41202
  console.log(`Project directory: ${projectDir}
40736
41203
  `);
40737
- if (opts.project) {
40738
- const cfgPath = path7.resolve(projectDir, ".pgwatch-config");
40739
- updatePgwatchConfig(cfgPath, { project_name: opts.project });
40740
- console.log(`Using project name: ${opts.project}
40741
- `);
41204
+ if (opts.project && !applyProjectName(projectDir, opts.project)) {
41205
+ process.exitCode = 1;
41206
+ return;
40742
41207
  }
40743
41208
  const envFile = path7.resolve(projectDir, ".env");
40744
41209
  const existingEnv = fs9.existsSync(envFile) ? fs9.readFileSync(envFile, "utf8") : "";
40745
41210
  const previousTagMatch = existingEnv.match(/^PGAI_TAG=(.+)$/m);
40746
41211
  const previousTag = previousTagMatch ? previousTagMatch[1].trim() : null;
40747
41212
  const imageTag = opts.tag || package_default.version;
40748
- const { content: envContent, preservedKeys } = buildLocalInstallEnv(existingEnv, imageTag);
40749
- fs9.writeFileSync(envFile, envContent, { encoding: "utf8", mode: 384 });
41213
+ const { content: envContent, preservedKeys } = buildLocalInstallEnv(existingEnv, imageTag, opts.instanceId || process.env.PGAI_INSTANCE_ID || null, path7.resolve(projectDir, ".pgwatch-config"));
41214
+ writeEnvFile(envFile, envContent);
40750
41215
  if (preservedKeys.length > 0) {
40751
41216
  console.log(`Preserved existing .env settings: ${preservedKeys.join(", ")}
40752
41217
  `);
@@ -40785,9 +41250,10 @@ Use demo mode without API key: postgres-ai mon local-install --demo`);
40785
41250
  if (apiKey) {
40786
41251
  console.log("Using API key provided via --api-key parameter");
40787
41252
  writeConfig({ apiKey });
40788
- updatePgwatchConfig(path7.resolve(projectDir, ".pgwatch-config"), { api_key: apiKey });
40789
- console.log(`\u2713 API key saved
40790
- `);
41253
+ if (!applyApiKey(projectDir, apiKey)) {
41254
+ process.exitCode = 1;
41255
+ return;
41256
+ }
40791
41257
  } else if (opts.yes) {
40792
41258
  console.log("Auto-yes mode: no API key provided, skipping API key setup");
40793
41259
  console.error("\u26A0 Reports will be generated locally only");
@@ -40802,10 +41268,11 @@ Use demo mode without API key: postgres-ai mon local-install --demo`);
40802
41268
  const trimmedKey = inputApiKey.trim();
40803
41269
  if (trimmedKey) {
40804
41270
  writeConfig({ apiKey: trimmedKey });
40805
- updatePgwatchConfig(path7.resolve(projectDir, ".pgwatch-config"), { api_key: trimmedKey });
41271
+ if (!applyApiKey(projectDir, trimmedKey)) {
41272
+ process.exitCode = 1;
41273
+ return;
41274
+ }
40806
41275
  apiKey = trimmedKey;
40807
- console.log(`\u2713 API key saved
40808
- `);
40809
41276
  break;
40810
41277
  }
40811
41278
  console.error("\u26A0 API key cannot be empty");
@@ -41042,9 +41509,9 @@ Searched: ${demoCandidates.join(", ")}
41042
41509
  const envLines = envContent.split(/\r?\n/).filter((l) => !/^VM_AUTH_USERNAME=/.test(l) && !/^VM_AUTH_PASSWORD=/.test(l)).filter((l, i, arr) => !(i === arr.length - 1 && l === ""));
41043
41510
  envLines.push(`VM_AUTH_USERNAME=${vmAuthUsername}`);
41044
41511
  envLines.push(`VM_AUTH_PASSWORD=${vmAuthPassword}`);
41045
- fs9.writeFileSync(envFile, envLines.join(`
41512
+ writeEnvFile(envFile, envLines.join(`
41046
41513
  `) + `
41047
- `, { encoding: "utf8", mode: 384 });
41514
+ `);
41048
41515
  }
41049
41516
  console.log(`\u2713 VictoriaMetrics auth configured
41050
41517
  `);
@@ -41086,25 +41553,11 @@ Searched: ${demoCandidates.join(", ")}
41086
41553
  const plan = planMonitoringRegistration({ project: opts.project, instanceId });
41087
41554
  const projectName = plan.projectName;
41088
41555
  if (instanceId) {
41089
- const reg = await registerMonitoringInstance(apiKey, projectName, {
41556
+ const reg = await adoptAndPersist(projectDir, apiKey, projectName, instanceId, {
41090
41557
  orgScope: regOrgScope,
41091
41558
  apiBaseUrl: globalOpts.apiBaseUrl,
41092
- debug: !!process.env.DEBUG,
41093
- instanceId
41559
+ debug: !!process.env.DEBUG
41094
41560
  });
41095
- const adoptedProject = resolveAdoptedProject(reg);
41096
- if (adoptedProject != null) {
41097
- updatePgwatchConfig(path7.resolve(projectDir, ".pgwatch-config"), {
41098
- project_name: adoptedProject
41099
- });
41100
- const verb = reg?.created ? "Registered" : "Adopted";
41101
- console.log(`\u2713 ${verb} monitoring instance (project: ${adoptedProject})
41102
- `);
41103
- } else if (reg) {
41104
- 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>`));
41105
- } else {
41106
- 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>`));
41107
- }
41108
41561
  if (reg === null) {
41109
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'
41110
41563
  `);
@@ -41129,7 +41582,7 @@ Searched: ${demoCandidates.join(", ")}
41129
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>.");
41130
41583
  process.exitCode = 1;
41131
41584
  } else {
41132
- registerMonitoringInstance(apiKey, projectName, {
41585
+ await selfRegisterAndPersist(projectDir, apiKey, projectName, {
41133
41586
  apiBaseUrl: globalOpts.apiBaseUrl,
41134
41587
  debug: !!process.env.DEBUG,
41135
41588
  orgScope: regOrgScope
@@ -41198,7 +41651,8 @@ var MONITORING_CONTAINERS = [
41198
41651
  "self-postgres-exporter",
41199
41652
  "flask-pgss-api",
41200
41653
  "sources-generator",
41201
- "postgres-reports"
41654
+ "postgres-reports",
41655
+ "instance-jobs"
41202
41656
  ];
41203
41657
  var COMPOSE_PROJECT_NAME = "postgres_ai";
41204
41658
  var DOCKER_NETWORK_NAME = `${COMPOSE_PROJECT_NAME}_default`;
@@ -41251,15 +41705,31 @@ mon.command("logs [service]").option("-f, --follow", "follow logs", false).optio
41251
41705
  if (code !== 0)
41252
41706
  process.exitCode = code;
41253
41707
  });
41254
- 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) => {
41255
- 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 [
41256
41722
  { name: "Grafana", container: "grafana-with-datasources" },
41257
41723
  { name: "Prometheus", container: "sink-prometheus" },
41258
41724
  { name: "PGWatch (Postgres)", container: "pgwatch-postgres" },
41259
41725
  { name: "PGWatch (Prometheus)", container: "pgwatch-prometheus" },
41260
41726
  { name: "Target DB", container: "target-db" },
41261
- { name: "Sink Postgres", container: "sink-postgres" }
41727
+ { name: "Sink Postgres", container: "sink-postgres" },
41728
+ { name: "Instance jobs", container: "instance-jobs", optional: true, readHealth: true }
41262
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();
41263
41733
  const waitTime = opts.wait || 0;
41264
41734
  const maxAttempts = waitTime > 0 ? Math.ceil(waitTime / 5) : 1;
41265
41735
  console.log(`Checking service health...
@@ -41275,16 +41745,12 @@ mon.command("health").description("health check for monitoring services").option
41275
41745
  for (const service of services) {
41276
41746
  try {
41277
41747
  const result = spawnSync2("docker", ["inspect", "-f", "{{.State.Status}}", service.container], { stdio: "pipe" });
41278
- const status = result.stdout.trim();
41279
- if (result.status === 0 && status === "running") {
41280
- console.log(`\u2713 ${service.name}: healthy`);
41281
- } else if (result.status === 0) {
41282
- console.log(`\u2717 ${service.name}: unhealthy (status: ${status})`);
41283
- allHealthy = false;
41284
- } else {
41285
- console.log(`\u2717 ${service.name}: unreachable`);
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)
41286
41753
  allHealthy = false;
41287
- }
41288
41754
  } catch (error) {
41289
41755
  console.log(`\u2717 ${service.name}: unreachable`);
41290
41756
  allHealthy = false;
@@ -41345,8 +41811,13 @@ mon.command("update-config").description("apply monitoring services configuratio
41345
41811
  }
41346
41812
  await refreshBundledComposeIfStale(projectDir);
41347
41813
  const code = await runCompose(["run", "--rm", "sources-generator"]);
41348
- if (code !== 0)
41814
+ if (code !== 0) {
41349
41815
  process.exitCode = code;
41816
+ return;
41817
+ }
41818
+ if (!await applySinkPrometheusConfig(projectDir)) {
41819
+ process.exitCode = 1;
41820
+ }
41350
41821
  });
41351
41822
  mon.command("update").description("update monitoring stack (migrate .env, pull images)").action(async () => {
41352
41823
  console.log(`Updating PostgresAI monitoring stack...
@@ -41388,6 +41859,10 @@ mon.command("update").description("update monitoring stack (migrate .env, pull i
41388
41859
  Updating Docker images...`);
41389
41860
  const code = await runCompose(["pull"]);
41390
41861
  if (code === 0) {
41862
+ if (!await applySinkPrometheusConfig(projectDir)) {
41863
+ process.exitCode = 1;
41864
+ return;
41865
+ }
41391
41866
  console.log(`
41392
41867
  \u2713 Update completed successfully`);
41393
41868
  console.log(`
@@ -42089,8 +42564,87 @@ program2.command("orgs").description("list organizations this credential can rea
42089
42564
  process.exitCode = 1;
42090
42565
  }
42091
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
+ });
42092
42646
  var issues = program2.command("issues").description("issues management");
42093
- 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) => {
42094
42648
  const spinner = createTtySpinner(process.stdout.isTTY ?? false, "Fetching issues...");
42095
42649
  try {
42096
42650
  const rootOpts = program2.opts();
@@ -42105,11 +42659,15 @@ withOrgOptions(issues.command("list")).description("list issues").option("--stat
42105
42659
  const orgScope = getActiveOrgScope2() ?? { source: "token" };
42106
42660
  const orgId = configOrgIdForBody(apiKey, cfg.orgId);
42107
42661
  const { apiBaseUrl } = resolveBaseUrls2(rootOpts, cfg);
42662
+ const statusArg = (opts.status ?? "open").trim().toLowerCase();
42108
42663
  let statusFilter;
42109
- if (opts.status === "open") {
42110
- statusFilter = "open";
42111
- } else if (opts.status === "closed") {
42112
- 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;
42113
42671
  }
42114
42672
  const result = await fetchIssues2({
42115
42673
  apiKey,
@@ -42123,7 +42681,7 @@ withOrgOptions(issues.command("list")).description("list issues").option("--stat
42123
42681
  debug: !!opts.debug
42124
42682
  });
42125
42683
  spinner.stop();
42126
- const trimmed = Array.isArray(result) ? result.map((r) => withVisibleHiddenFlag2({
42684
+ const trimmed = Array.isArray(result) ? result.map((r) => presentIssue2({
42127
42685
  id: r.id,
42128
42686
  title: r.title,
42129
42687
  status: r.status,
@@ -42161,7 +42719,7 @@ withOrgOptions(issues.command("view <issueId>")).description("view issue details
42161
42719
  spinner.update("Fetching comments...");
42162
42720
  const comments = await fetchIssueComments2({ apiKey, apiBaseUrl, issueId, debug: !!opts.debug });
42163
42721
  spinner.stop();
42164
- const combined = { issue: withVisibleHiddenFlag2(issue), comments };
42722
+ const combined = { issue: presentIssue2(issue), comments };
42165
42723
  printResult(combined, opts.json);
42166
42724
  } catch (err) {
42167
42725
  spinner.stop();
@@ -42228,7 +42786,7 @@ withOrgOptions(issues.command("create <title>")).description("create a new issue
42228
42786
  }, []).option("--attach <path>", "attach a file (uploads to storage and appends a markdown link to the description; repeatable)", (value, previous) => {
42229
42787
  previous.push(value);
42230
42788
  return previous;
42231
- }, []).option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (rawTitle, opts) => {
42789
+ }, []).option("--hidden", "create as a hidden issue (PostgresAI staff)").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (rawTitle, opts) => {
42232
42790
  const rootOpts = program2.opts();
42233
42791
  const cfg = readConfig();
42234
42792
  const { apiKey } = getConfig(rootOpts);
@@ -42289,6 +42847,7 @@ withOrgOptions(issues.command("create <title>")).description("create a new issue
42289
42847
  description: augmentedDescription,
42290
42848
  projectId,
42291
42849
  labels,
42850
+ hidden: opts.hidden === true,
42292
42851
  debug: !!opts.debug
42293
42852
  });
42294
42853
  spinner.stop();
@@ -42306,7 +42865,7 @@ withOrgOptions(issues.command("update <issueId>")).description("update an existi
42306
42865
  }, []).option("--clear-labels", "set labels to an empty list").option("--attach <path>", "attach a file (uploads and appends a markdown link to --description; if --description is omitted the existing description is fetched and appended to; repeatable)", (value, previous) => {
42307
42866
  previous.push(value);
42308
42867
  return previous;
42309
- }, []).option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (issueId, opts) => {
42868
+ }, []).option("--hidden", "hide the issue (PostgresAI staff)").option("--no-hidden", "unhide the issue (PostgresAI staff)").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (issueId, opts) => {
42310
42869
  const rootOpts = program2.opts();
42311
42870
  const cfg = readConfig();
42312
42871
  const { apiKey } = getConfig(rootOpts);
@@ -42377,6 +42936,7 @@ withOrgOptions(issues.command("update <issueId>")).description("update an existi
42377
42936
  description,
42378
42937
  status,
42379
42938
  labels,
42939
+ hidden: typeof opts.hidden === "boolean" ? opts.hidden : undefined,
42380
42940
  debug: !!opts.debug
42381
42941
  });
42382
42942
  spinner.stop();
@@ -43363,13 +43923,23 @@ if (__require.main == __require.module) {
43363
43923
  export {
43364
43924
  LOCAL_INSTALL_MANAGED_ENV_KEYS,
43365
43925
  ORG_SCOPED_COMMANDS,
43926
+ adoptAndPersist,
43927
+ applyApiKey,
43928
+ applyProjectName,
43366
43929
  buildLocalInstallEnv,
43930
+ instanceIdToPersist,
43367
43931
  isValidComposeYaml,
43932
+ judgeServiceHealth,
43933
+ monitoringHealthServices,
43934
+ persistInstanceId,
43368
43935
  planMonitoringRegistration,
43369
43936
  program2 as program,
43370
43937
  readDeployedTag,
43371
43938
  refreshBundledComposeIfStale,
43372
43939
  registerMonitoringInstance,
43373
43940
  resolveAdoptedProject,
43941
+ selfRegisterAndPersist,
43942
+ shouldScaleOutNodeExporter,
43943
+ statOwner,
43374
43944
  updatePgwatchConfig
43375
43945
  };