postgresai 0.17.0-rc.3 → 0.17.0-rc.4

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.
@@ -13530,7 +13530,7 @@ var {
13530
13530
  // package.json
13531
13531
  var package_default = {
13532
13532
  name: "postgresai",
13533
- version: "0.17.0-rc.3",
13533
+ version: "0.17.0-rc.4",
13534
13534
  description: "postgres_ai CLI",
13535
13535
  license: "Apache-2.0",
13536
13536
  private: false,
@@ -16890,7 +16890,7 @@ var Result = import_lib.default.Result;
16890
16890
  var TypeOverrides = import_lib.default.TypeOverrides;
16891
16891
  var defaults = import_lib.default.defaults;
16892
16892
  // package.json
16893
- var version = "0.17.0-rc.3";
16893
+ var version = "0.17.0-rc.4";
16894
16894
  var package_default2 = {
16895
16895
  name: "postgresai",
16896
16896
  version,
@@ -17223,6 +17223,22 @@ var TEXT_SENSITIVE_PAIR = /((?:password|passwd|db[-_]?pass|connstr|secret|token|
17223
17223
  function redactTextSecrets(text) {
17224
17224
  return text.replace(TEXT_URL_USERINFO, "$1:[REDACTED]@").replace(TEXT_SENSITIVE_PAIR, "$1[REDACTED]");
17225
17225
  }
17226
+ function isControlCodePoint(cp) {
17227
+ return cp < 32 || cp === 127 || cp >= 128 && cp <= 159 || cp === 8232 || cp === 8233;
17228
+ }
17229
+ function stripControlsToSpace(text) {
17230
+ let out = "";
17231
+ for (const ch of text) {
17232
+ const cp = ch.codePointAt(0);
17233
+ out += cp !== undefined && isControlCodePoint(cp) ? " " : ch;
17234
+ }
17235
+ return out;
17236
+ }
17237
+ function whitespaceTolerantSecretPattern(secret) {
17238
+ const chars = Array.from(secret.replace(/\s+/g, ""));
17239
+ const body = chars.map((c) => c.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("\\s*");
17240
+ return new RegExp(body, "g");
17241
+ }
17226
17242
  function normalizeBaseUrl(value) {
17227
17243
  const trimmed = (value || "").replace(/\/$/, "");
17228
17244
  try {
@@ -30566,6 +30582,38 @@ async function resolveDatasourceId(adminPassword, debug = false) {
30566
30582
  return null;
30567
30583
  }
30568
30584
  }
30585
+ function aasSuccessMessage(r) {
30586
+ const armed = r.armedFor === "pull" ? " (armed for pull: the platform will query this instance's Grafana)" : r.armedFor === "jobs" ? " (armed for the job channel: this instance answers its own queries)" : "";
30587
+ const base = `AAS auto-collection registered${armed}`;
30588
+ if (r.vcpus === 0) {
30589
+ return `${base} — collection stays OFF until a source-DB vCPU count is known (the platform stamps it at provision time; --vcpus <n> sets it for a manual install)`;
30590
+ }
30591
+ if (r.urlPending) {
30592
+ return `${base} — collection starts once this deploy reports the Grafana url to the platform`;
30593
+ }
30594
+ return base;
30595
+ }
30596
+ var PLATFORM_ERROR_MAX = 300;
30597
+ function formatPlatformError(body, secrets = []) {
30598
+ if (!body || typeof body !== "object")
30599
+ return "";
30600
+ const b = body;
30601
+ const str = (v) => typeof v === "string" && v.trim() !== "" ? v.trim() : "";
30602
+ const text = str(b.details) || str(b.message);
30603
+ if (!text)
30604
+ return "";
30605
+ const code = str(b.code);
30606
+ let out = stripControlsToSpace(code ? `${code}: ${text}` : text).replace(/\s+/g, " ").trim();
30607
+ for (const secret of secrets) {
30608
+ const compact = secret.replace(/\s+/g, "");
30609
+ if (compact.length < 8)
30610
+ continue;
30611
+ out = out.replace(whitespaceTolerantSecretPattern(compact), "[redacted]");
30612
+ }
30613
+ out = redactTextSecrets(out);
30614
+ out = out.replace(/glsa_[A-Za-z0-9_-]+/g, "[redacted]");
30615
+ return out.length > PLATFORM_ERROR_MAX ? `${out.slice(0, PLATFORM_ERROR_MAX - 1).trimEnd()}…` : out;
30616
+ }
30569
30617
  async function registerAasCollection(apiKey, instanceId, opts) {
30570
30618
  const debug = !!opts.debug;
30571
30619
  try {
@@ -30615,11 +30663,19 @@ async function registerAasCollection(apiKey, instanceId, opts) {
30615
30663
  })
30616
30664
  });
30617
30665
  if (!res.ok) {
30666
+ const detail = formatPlatformError(await res.json().catch(() => null), [apiKey, saToken]);
30667
+ const suffix = detail ? ` — ${detail}` : "";
30618
30668
  if (debug)
30619
- console.error(`Debug: AAS register failed: HTTP ${res.status}`);
30620
- return { ok: false, reason: `platform returned HTTP ${res.status}` };
30669
+ console.error(`Debug: AAS register failed: HTTP ${res.status}${suffix}`);
30670
+ return { ok: false, reason: `platform returned HTTP ${res.status}${suffix}` };
30621
30671
  }
30622
- return { ok: true };
30672
+ const reply = await res.json().catch(() => null);
30673
+ return {
30674
+ ok: true,
30675
+ vcpus: typeof reply?.vcpus === "number" ? reply.vcpus : undefined,
30676
+ urlPending: typeof reply?.url_pending === "boolean" ? reply.url_pending : undefined,
30677
+ armedFor: typeof reply?.armed_for === "string" ? reply.armed_for : undefined
30678
+ };
30623
30679
  } catch (err) {
30624
30680
  if (debug)
30625
30681
  console.error(`Debug: AAS register error: ${err.message}`);
@@ -38950,8 +39006,41 @@ var LOCAL_INSTALL_MANAGED_ENV_KEYS = [
38950
39006
  "VM_FORCE_MERGE_AUTH_KEY",
38951
39007
  "VM_PPROF_AUTH_KEY",
38952
39008
  "PGAI_INSTANCE_ID",
38953
- "INSTANCE_JOBS_USER"
39009
+ "INSTANCE_JOBS_USER",
39010
+ "COMPOSE_PROFILES"
38954
39011
  ];
39012
+ var INSTANCE_JOBS_PROFILE = "instance-jobs";
39013
+ function composeProfilesValue(existing, want) {
39014
+ const profiles = [];
39015
+ for (const raw of (existing ?? "").split(",")) {
39016
+ const token = raw.trim();
39017
+ if (token && !profiles.includes(token))
39018
+ profiles.push(token);
39019
+ }
39020
+ const at = profiles.indexOf(INSTANCE_JOBS_PROFILE);
39021
+ if (want === true && at === -1)
39022
+ profiles.push(INSTANCE_JOBS_PROFILE);
39023
+ if (want === false && at !== -1)
39024
+ profiles.splice(at, 1);
39025
+ return profiles.length > 0 ? profiles.join(",") : null;
39026
+ }
39027
+ function instanceJobsProfileEnabled(composeProfiles) {
39028
+ return (composeProfiles ?? "").split(",").map((p) => p.trim()).includes(INSTANCE_JOBS_PROFILE);
39029
+ }
39030
+ function resolveInstanceJobsRequest(flag, envValue) {
39031
+ if (flag !== undefined)
39032
+ return { enabled: flag };
39033
+ const value = (envValue ?? "").trim().toLowerCase();
39034
+ if (value === "")
39035
+ return {};
39036
+ if (["1", "true", "yes", "on"].includes(value))
39037
+ return { enabled: true };
39038
+ if (["0", "false", "no", "off"].includes(value))
39039
+ return { enabled: false };
39040
+ return {
39041
+ error: `PGAI_INSTANCE_JOBS=${envValue} is not a boolean ` + "(use true/false, 1/0, yes/no, on/off, or leave it empty to keep the current setting)"
39042
+ };
39043
+ }
38955
39044
  function statOwner(file) {
38956
39045
  if (!file)
38957
39046
  return;
@@ -38970,7 +39059,7 @@ function processOwner() {
38970
39059
  return null;
38971
39060
  return `${process.getuid()}:${process.getgid()}`;
38972
39061
  }
38973
- function buildLocalInstallEnv(existingEnv, imageTag, instanceId, credentialFile) {
39062
+ function buildLocalInstallEnv(existingEnv, imageTag, instanceId, credentialFile, instanceJobs) {
38974
39063
  const readRaw = (key) => {
38975
39064
  const re = new RegExp(`^[ ]*(?:export[ ]+)?${key}=(.*)$`, "gm");
38976
39065
  let value = null;
@@ -39002,6 +39091,10 @@ function buildLocalInstallEnv(existingEnv, imageTag, instanceId, credentialFile)
39002
39091
  const credentialOwner = owner === undefined ? processOwner() : owner;
39003
39092
  if (credentialOwner)
39004
39093
  envLines.push(`INSTANCE_JOBS_USER=${credentialOwner}`);
39094
+ const existingProfiles = readRaw("COMPOSE_PROFILES");
39095
+ const composeProfiles = composeProfilesValue(existingProfiles === null ? null : stripMatchingQuotes(existingProfiles), instanceJobs);
39096
+ if (composeProfiles)
39097
+ envLines.push(`COMPOSE_PROFILES=${composeProfiles}`);
39005
39098
  for (const key of VM_ADMIN_AUTH_KEYS) {
39006
39099
  const existing = readRaw(key);
39007
39100
  envLines.push(`${key}=${existing ? stripMatchingQuotes(existing) : crypto2.randomBytes(32).toString("hex")}`);
@@ -41053,7 +41146,7 @@ async function applyMonitoringTargetsConfig() {
41053
41146
  console.log("Restarting pgwatch collectors to pick up target changes...");
41054
41147
  return runCompose(["up", "-d", "--force-recreate", "pgwatch-prometheus", "pgwatch-postgres"]);
41055
41148
  }
41056
- async function runCompose(args, grafanaPassword) {
41149
+ async function runCompose(args, grafanaPassword, extraEnv) {
41057
41150
  let composeFile;
41058
41151
  let projectDir;
41059
41152
  try {
@@ -41117,6 +41210,8 @@ async function runCompose(args, grafanaPassword) {
41117
41210
  }
41118
41211
  }
41119
41212
  }
41213
+ if (extraEnv)
41214
+ Object.assign(env, extraEnv);
41120
41215
  const finalArgs = [...args];
41121
41216
  if (process.platform === "darwin" && shouldScaleOutNodeExporter(args)) {
41122
41217
  finalArgs.push("--scale", "self-node-exporter=0");
@@ -41175,6 +41270,127 @@ Applying sink-prometheus config (VictoriaMetrics admin keys, #359)...`);
41175
41270
  console.log("\u2713 sink-prometheus is running the current config");
41176
41271
  return true;
41177
41272
  }
41273
+ function readComposeProfiles(knownProjectDir) {
41274
+ const fromProcess = process.env.COMPOSE_PROFILES;
41275
+ if (fromProcess !== undefined && fromProcess.trim() !== "")
41276
+ return fromProcess;
41277
+ let projectDir;
41278
+ if (knownProjectDir) {
41279
+ projectDir = knownProjectDir;
41280
+ } else {
41281
+ try {
41282
+ ({ projectDir } = resolvePaths());
41283
+ } catch {
41284
+ projectDir = getDefaultMonitoringProjectDir();
41285
+ }
41286
+ }
41287
+ const envFile = path7.resolve(projectDir, ".env");
41288
+ let content;
41289
+ try {
41290
+ content = fs9.readFileSync(envFile, "utf8");
41291
+ } catch (err) {
41292
+ if (err?.code !== "ENOENT") {
41293
+ console.error(`\u26A0 Could not read ${envFile}: ${err instanceof Error ? err.message : String(err)}`);
41294
+ console.error(" Treating the instance-jobs profile as disabled; if it is in fact enabled,");
41295
+ console.error(" an absent container will be reported as '- not enabled' rather than a fault.");
41296
+ }
41297
+ return null;
41298
+ }
41299
+ let value = null;
41300
+ for (const m of content.matchAll(/^[ \t]*(?:export[ \t]+)?COMPOSE_PROFILES=(.*)$/gm)) {
41301
+ value = stripMatchingQuotes(m[1].trim());
41302
+ }
41303
+ return value;
41304
+ }
41305
+ function instanceJobsRemovalOutcome(before, after, removeExitCode) {
41306
+ if (before === "unknown" || after === "unknown") {
41307
+ return {
41308
+ ok: false,
41309
+ message: `\u2717 Could not determine whether the instance-jobs container is present.
41310
+ ` + " The profile is off in .env, but a container left behind keeps polling."
41311
+ };
41312
+ }
41313
+ if (before === "absent") {
41314
+ return { ok: true, message: "\u2713 instance-jobs is disabled (no container was present)" };
41315
+ }
41316
+ if (removeExitCode !== 0 || after !== "absent") {
41317
+ return {
41318
+ ok: false,
41319
+ message: `\u2717 Could not remove the instance-jobs container.
41320
+ ` + ` The profile is off in .env, but the container is still there and still polling. To retry:
41321
+ ` + ` COMPOSE_PROFILES=${INSTANCE_JOBS_PROFILE} docker compose rm -sf ${INSTANCE_JOBS_PROFILE}`
41322
+ };
41323
+ }
41324
+ return before === "running" ? {
41325
+ ok: true,
41326
+ message: "\u26A0 instance-jobs was RUNNING and has been removed \u2014 outbound collection on this machine is now OFF"
41327
+ } : { ok: true, message: "\u2713 instance-jobs container removed (it was not running)" };
41328
+ }
41329
+ function instanceJobsStartSkipReason(storeRunning) {
41330
+ if (storeRunning === true)
41331
+ return null;
41332
+ return storeRunning === false ? "the stack is not running, so instance-jobs was not started - it comes up with the rest on `mon start`" : "could not tell whether the stack is running, so instance-jobs was not started - run `mon start`, then `mon health`";
41333
+ }
41334
+ async function composeServiceRunning(projectDir, service) {
41335
+ const composeCmd = getComposeCmd();
41336
+ if (!composeCmd)
41337
+ return null;
41338
+ try {
41339
+ const { stdout } = await execFilePromise(composeCmd[0], [...composeCmd.slice(1), "-f", path7.resolve(projectDir, "docker-compose.yml"), "ps", "-q", service], { cwd: projectDir });
41340
+ return stdout.trim().length > 0;
41341
+ } catch {
41342
+ return null;
41343
+ }
41344
+ }
41345
+ async function applyInstanceJobsProfile(projectDir, enabled) {
41346
+ if (enabled === undefined)
41347
+ return true;
41348
+ if (enabled) {
41349
+ const skip = instanceJobsStartSkipReason(await composeServiceRunning(projectDir, "sink-prometheus"));
41350
+ if (skip) {
41351
+ console.log(`
41352
+ (${skip})`);
41353
+ return true;
41354
+ }
41355
+ console.log(`
41356
+ Starting the instance-jobs container (compose profile enabled)...`);
41357
+ const code = await runCompose(["up", "-d", "--no-deps", INSTANCE_JOBS_PROFILE]);
41358
+ if (code !== 0) {
41359
+ console.error("\u2717 Could not start the instance-jobs container.");
41360
+ console.error(" The channel is enabled in .env but nothing is polling. To retry:");
41361
+ console.error(` docker compose up -d --no-deps ${INSTANCE_JOBS_PROFILE}`);
41362
+ return false;
41363
+ }
41364
+ console.log("\u2713 instance-jobs is running");
41365
+ return true;
41366
+ }
41367
+ const before = await inspectInstanceJobsState();
41368
+ if (before === "absent" || before === "unknown") {
41369
+ const verdict = instanceJobsRemovalOutcome(before, before, 0);
41370
+ (verdict.ok ? console.log : console.error)(`
41371
+ ${verdict.message}`);
41372
+ return verdict.ok;
41373
+ }
41374
+ const code = await runCompose(["rm", "-sf", INSTANCE_JOBS_PROFILE], undefined, { COMPOSE_PROFILES: INSTANCE_JOBS_PROFILE });
41375
+ const verdict = instanceJobsRemovalOutcome(before, await inspectInstanceJobsState(), code);
41376
+ (verdict.ok ? console.log : console.error)(`
41377
+ ${verdict.message}`);
41378
+ return verdict.ok;
41379
+ }
41380
+ async function inspectInstanceJobsState() {
41381
+ try {
41382
+ const { stdout } = await execFilePromise("docker", [
41383
+ "inspect",
41384
+ "-f",
41385
+ "{{.State.Running}}",
41386
+ INSTANCE_JOBS_PROFILE
41387
+ ]);
41388
+ return stdout.trim() === "true" ? "running" : "stopped";
41389
+ } catch (error) {
41390
+ const text = error instanceof Error ? error.message : String(error);
41391
+ return /no such (object|container)/i.test(text) ? "absent" : "unknown";
41392
+ }
41393
+ }
41178
41394
  program2.command("help").description("show help").action(() => {
41179
41395
  program2.outputHelp();
41180
41396
  });
@@ -41188,7 +41404,7 @@ mon.command("local-install").description("install local monitoring stack (genera
41188
41404
  " PGAI_ENABLE_IPV6=false (accepted: true|false|yes|no, lowercase)",
41189
41405
  ""
41190
41406
  ].join(`
41191
- `)).option("--demo", "demo mode with sample database", false).option("--api-key <key>", "Postgres AI API key for automated report uploads").option("--org <alias>", `organization alias (or ${ORG_ENV}); required to register with a global token`).option("--org-id <id>", `organization id (or ${ORG_ID_ENV}); alternative to --org`).option("--db-url <url>", "PostgreSQL connection URL to monitor").option("--tag <tag>", "Docker image tag to use (e.g., 0.14.0, 0.14.0-dev.33)").option("--project <name>", "Docker Compose project name (default: postgres_ai)").option("--instance-id <uuid>", "adopt a console-provisioned monitoring instance instead of self-registering a new one (set automatically by the provisioning flow; PGAI_INSTANCE_ID env also works)").option("--vcpus <n>", "source DB vCPU count used for AAS zone thresholds (PGAI_VCPUS env also works). Omit or 0 = unknown: the platform keeps the value it stamped at provision time. An explicit non-zero value overwrites the stored one; changing it re-arms the AAS backfill.").option("-y, --yes", "accept all defaults and skip interactive prompts", false).action(async (opts) => {
41407
+ `)).option("--demo", "demo mode with sample database", false).option("--api-key <key>", "Postgres AI API key for automated report uploads").option("--org <alias>", `organization alias (or ${ORG_ENV}); required to register with a global token`).option("--org-id <id>", `organization id (or ${ORG_ID_ENV}); alternative to --org`).option("--db-url <url>", "PostgreSQL connection URL to monitor").option("--tag <tag>", "Docker image tag to use (e.g., 0.14.0, 0.14.0-dev.33)").option("--project <name>", "Docker Compose project name (default: postgres_ai)").option("--instance-id <uuid>", "adopt a console-provisioned monitoring instance instead of self-registering a new one (set automatically by the provisioning flow; PGAI_INSTANCE_ID env also works)").option("--vcpus <n>", "source DB vCPU count used for AAS zone thresholds (PGAI_VCPUS env also works). Omit or 0 = unknown: the platform keeps the value it stamped at provision time. An explicit non-zero value overwrites the stored one; changing it re-arms the AAS backfill.").option("--instance-jobs", "enable the instance-jobs compose profile (outbound collection channel; PGAI_INSTANCE_JOBS env also works). Writes COMPOSE_PROFILES into the stack .env, so every later mon command covers the container").option("--no-instance-jobs", "disable the instance-jobs compose profile and remove its container").option("-y, --yes", "accept all defaults and skip interactive prompts", false).action(async (opts) => {
41192
41408
  const globalOpts = program2.opts();
41193
41409
  let apiKey = opts.apiKey || globalOpts.apiKey;
41194
41410
  console.log(`
@@ -41210,7 +41426,14 @@ mon.command("local-install").description("install local monitoring stack (genera
41210
41426
  const previousTagMatch = existingEnv.match(/^PGAI_TAG=(.+)$/m);
41211
41427
  const previousTag = previousTagMatch ? previousTagMatch[1].trim() : null;
41212
41428
  const imageTag = opts.tag || package_default.version;
41213
- const { content: envContent, preservedKeys } = buildLocalInstallEnv(existingEnv, imageTag, opts.instanceId || process.env.PGAI_INSTANCE_ID || null, path7.resolve(projectDir, ".pgwatch-config"));
41429
+ const instanceJobsRequest = resolveInstanceJobsRequest(opts.instanceJobs, process.env.PGAI_INSTANCE_JOBS);
41430
+ if (instanceJobsRequest.error) {
41431
+ console.error(`\u2717 ${instanceJobsRequest.error}`);
41432
+ process.exitCode = 1;
41433
+ return;
41434
+ }
41435
+ const instanceJobs = instanceJobsRequest.enabled;
41436
+ const { content: envContent, preservedKeys } = buildLocalInstallEnv(existingEnv, imageTag, opts.instanceId || process.env.PGAI_INSTANCE_ID || null, path7.resolve(projectDir, ".pgwatch-config"), instanceJobs);
41214
41437
  writeEnvFile(envFile, envContent);
41215
41438
  if (preservedKeys.length > 0) {
41216
41439
  console.log(`Preserved existing .env settings: ${preservedKeys.join(", ")}
@@ -41241,6 +41464,8 @@ Use demo mode without API key: postgres-ai mon local-install --demo`);
41241
41464
  console.error(`\u26A0 Monitoring services are already running: ${containers.join(", ")}`);
41242
41465
  console.log(`Use 'postgres-ai mon restart' to restart them
41243
41466
  `);
41467
+ if (!await applyInstanceJobsProfile(projectDir, instanceJobs))
41468
+ process.exitCode = 1;
41244
41469
  return;
41245
41470
  }
41246
41471
  if (!opts.demo) {
@@ -41530,6 +41755,9 @@ Searched: ${demoCandidates.join(", ")}
41530
41755
  }
41531
41756
  console.log(`\u2713 Services started
41532
41757
  `);
41758
+ const instanceJobsApplied = await applyInstanceJobsProfile(projectDir, instanceJobs);
41759
+ if (!instanceJobsApplied)
41760
+ process.exitCode = 1;
41533
41761
  if (apiKey && !opts.demo) {
41534
41762
  let regOrgScope;
41535
41763
  if (isGlobalTokenValue(apiKey)) {
@@ -41571,7 +41799,7 @@ Searched: ${demoCandidates.join(", ")}
41571
41799
  debug: !!process.env.DEBUG
41572
41800
  });
41573
41801
  if (aas.ok) {
41574
- console.log(`\u2713 AAS auto-collection registered
41802
+ console.log(`\u2713 ${aasSuccessMessage(aas)}
41575
41803
  `);
41576
41804
  } else {
41577
41805
  console.error(`\u26A0 AAS auto-collection not registered (${aas.reason}); it can be enabled later by re-running 'postgresai mon local-install'
@@ -41590,9 +41818,15 @@ Searched: ${demoCandidates.join(", ")}
41590
41818
  }
41591
41819
  }
41592
41820
  console.log("=================================");
41593
- console.log(" Local install completed!");
41821
+ console.log(instanceJobsApplied ? " Local install completed!" : " Local install completed WITH ONE FAILURE");
41594
41822
  console.log(`=================================
41595
41823
  `);
41824
+ if (!instanceJobsApplied) {
41825
+ console.log(instanceJobs ? `\u2717 The instance-jobs container did not start. Everything below is running; the outbound
41826
+ ` + " collection channel is not. See the error above, then `postgres-ai mon health`.\n" : `\u2717 The instance-jobs container could not be removed. Everything below is running, and so
41827
+ ` + ` is a container that keeps polling. See the error above.
41828
+ `);
41829
+ }
41596
41830
  console.log("What's running:");
41597
41831
  if (opts.demo) {
41598
41832
  console.log(" \u2705 Demo PostgreSQL database (monitoring target)");
@@ -41707,7 +41941,7 @@ mon.command("logs [service]").option("-f, --follow", "follow logs", false).optio
41707
41941
  });
41708
41942
  function judgeServiceHealth(service, present, status, health) {
41709
41943
  if (!present) {
41710
- return service.optional ? { ok: true, mark: "-", label: "not enabled" } : { ok: false, mark: "\u2717", label: "unreachable" };
41944
+ return service.optional ? { ok: true, mark: "-", label: "not enabled" } : { ok: false, mark: "\u2717", label: service.absentLabel ?? "unreachable" };
41711
41945
  }
41712
41946
  if (status !== "running") {
41713
41947
  return { ok: false, mark: "\u2717", label: `unhealthy (status: ${status})` };
@@ -41717,7 +41951,7 @@ function judgeServiceHealth(service, present, status, health) {
41717
41951
  }
41718
41952
  return { ok: true, mark: "\u2713", label: "healthy" };
41719
41953
  }
41720
- function monitoringHealthServices() {
41954
+ function monitoringHealthServices(instanceJobsEnabled = false) {
41721
41955
  return [
41722
41956
  { name: "Grafana", container: "grafana-with-datasources" },
41723
41957
  { name: "Prometheus", container: "sink-prometheus" },
@@ -41725,11 +41959,17 @@ function monitoringHealthServices() {
41725
41959
  { name: "PGWatch (Prometheus)", container: "pgwatch-prometheus" },
41726
41960
  { name: "Target DB", container: "target-db" },
41727
41961
  { name: "Sink Postgres", container: "sink-postgres" },
41728
- { name: "Instance jobs", container: "instance-jobs", optional: true, readHealth: true }
41962
+ {
41963
+ name: "Instance jobs",
41964
+ container: "instance-jobs",
41965
+ optional: !instanceJobsEnabled,
41966
+ readHealth: true,
41967
+ absentLabel: "enabled but not running"
41968
+ }
41729
41969
  ];
41730
41970
  }
41731
41971
  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();
41972
+ const services = monitoringHealthServices(instanceJobsProfileEnabled(readComposeProfiles()));
41733
41973
  const waitTime = opts.wait || 0;
41734
41974
  const maxAttempts = waitTime > 0 ? Math.ceil(waitTime / 5) : 1;
41735
41975
  console.log(`Checking service health...
@@ -41863,6 +42103,12 @@ Updating Docker images...`);
41863
42103
  process.exitCode = 1;
41864
42104
  return;
41865
42105
  }
42106
+ if (instanceJobsProfileEnabled(readComposeProfiles(projectDir))) {
42107
+ if (!await applyInstanceJobsProfile(projectDir, true)) {
42108
+ process.exitCode = 1;
42109
+ return;
42110
+ }
42111
+ }
41866
42112
  console.log(`
41867
42113
  \u2713 Update completed successfully`);
41868
42114
  console.log(`
@@ -42617,7 +42863,7 @@ withOrgOptions(program2.command("promql")).description("run a PromQL query on a
42617
42863
  });
42618
42864
  if (!isTerminal2(res.status)) {
42619
42865
  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.");
42866
+ console.error(res.status === "queued" ? "Nothing has claimed it: check the instance-jobs container is running on that instance " + "(`postgresai mon health`; it is behind a compose profile, enabled with " + "`mon local-install --instance-jobs`), 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
42867
  process.exitCode = 1;
42622
42868
  return;
42623
42869
  }
@@ -43927,17 +44173,23 @@ export {
43927
44173
  applyApiKey,
43928
44174
  applyProjectName,
43929
44175
  buildLocalInstallEnv,
44176
+ composeProfilesValue,
43930
44177
  instanceIdToPersist,
44178
+ instanceJobsProfileEnabled,
44179
+ instanceJobsRemovalOutcome,
44180
+ instanceJobsStartSkipReason,
43931
44181
  isValidComposeYaml,
43932
44182
  judgeServiceHealth,
43933
44183
  monitoringHealthServices,
43934
44184
  persistInstanceId,
43935
44185
  planMonitoringRegistration,
43936
44186
  program2 as program,
44187
+ readComposeProfiles,
43937
44188
  readDeployedTag,
43938
44189
  refreshBundledComposeIfStale,
43939
44190
  registerMonitoringInstance,
43940
44191
  resolveAdoptedProject,
44192
+ resolveInstanceJobsRequest,
43941
44193
  selfRegisterAndPersist,
43942
44194
  shouldScaleOutNodeExporter,
43943
44195
  statOwner,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postgresai",
3
- "version": "0.17.0-rc.3",
3
+ "version": "0.17.0-rc.4",
4
4
  "description": "postgres_ai CLI",
5
5
  "license": "Apache-2.0",
6
6
  "private": false,