postgresai 0.11.0-alpha.7 → 0.11.0-alpha.9

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.
@@ -1,703 +0,0 @@
1
- #!/usr/bin/env node
2
- "use strict";
3
-
4
- const { Command } = require("commander");
5
- const pkg = require("../package.json");
6
-
7
- function getConfig(opts) {
8
- const apiKey = opts.apiKey || process.env.PGAI_API_KEY || "";
9
- const baseUrl =
10
- opts.baseUrl || process.env.PGAI_BASE_URL || "https://postgres.ai/api/general/";
11
- return { apiKey, baseUrl };
12
- }
13
-
14
- const program = new Command();
15
-
16
- program
17
- .name("postgres-ai")
18
- .description("PostgresAI CLI")
19
- .version(pkg.version)
20
- .option("--api-key <key>", "API key (overrides PGAI_API_KEY)")
21
- .option(
22
- "--base-url <url>",
23
- "API base URL (overrides PGAI_BASE_URL)",
24
- "https://postgres.ai/api/general/"
25
- );
26
-
27
- const stub = (name) => async () => {
28
- // Temporary stubs until Node parity is implemented
29
- console.error(`${name}: not implemented in Node CLI yet; use bash CLI for now`);
30
- process.exitCode = 2;
31
- };
32
-
33
- function resolvePaths() {
34
- const path = require("path");
35
- const fs = require("fs");
36
- const projectDir = process.cwd();
37
- const composeFile = path.resolve(projectDir, "docker-compose.yml");
38
- const instancesFile = path.resolve(projectDir, "instances.yml");
39
- return { fs, path, projectDir, composeFile, instancesFile };
40
- }
41
-
42
- function getComposeCmd() {
43
- const { spawnSync } = require("child_process");
44
- const tryCmd = (cmd, args) => spawnSync(cmd, args, { stdio: "ignore" }).status === 0;
45
- if (tryCmd("docker-compose", ["version"])) return ["docker-compose"];
46
- if (tryCmd("docker", ["compose", "version"])) return ["docker", "compose"];
47
- return null;
48
- }
49
-
50
- async function runCompose(args) {
51
- const { composeFile } = resolvePaths();
52
- const cmd = getComposeCmd();
53
- if (!cmd) {
54
- console.error("docker compose not found (need docker-compose or docker compose)");
55
- process.exitCode = 1;
56
- return 1;
57
- }
58
- const { spawn } = require("child_process");
59
- return new Promise((resolve) => {
60
- const child = spawn(cmd[0], [...cmd.slice(1), "-f", composeFile, ...args], { stdio: "inherit" });
61
- child.on("close", (code) => resolve(code));
62
- });
63
- }
64
-
65
- program.command("help", { isDefault: true }).description("show help").action(() => {
66
- program.outputHelp();
67
- });
68
-
69
- // Service lifecycle
70
- program
71
- .command("quickstart")
72
- .description("complete setup (generate config, start services)")
73
- .option("--demo", "demo mode", false)
74
- .action(async () => {
75
- const code1 = await runCompose(["run", "--rm", "sources-generator"]);
76
- if (code1 !== 0) {
77
- process.exitCode = code1;
78
- return;
79
- }
80
- const code2 = await runCompose(["up", "-d"]);
81
- if (code2 !== 0) process.exitCode = code2;
82
- });
83
- program
84
- .command("install")
85
- .description("prepare project (no-op in repo checkout)")
86
- .action(async () => {
87
- console.log("Project files present; nothing to install.");
88
- });
89
- program
90
- .command("start")
91
- .description("start services")
92
- .action(async () => {
93
- const code = await runCompose(["up", "-d"]);
94
- if (code !== 0) process.exitCode = code;
95
- });
96
- program
97
- .command("stop")
98
- .description("stop services")
99
- .action(async () => {
100
- const code = await runCompose(["down"]);
101
- if (code !== 0) process.exitCode = code;
102
- });
103
- program
104
- .command("restart")
105
- .description("restart services")
106
- .action(async () => {
107
- const code = await runCompose(["restart"]);
108
- if (code !== 0) process.exitCode = code;
109
- });
110
- program
111
- .command("status")
112
- .description("show service status")
113
- .action(async () => {
114
- const code = await runCompose(["ps"]);
115
- if (code !== 0) process.exitCode = code;
116
- });
117
- program
118
- .command("logs [service]")
119
- .option("-f, --follow", "follow logs", false)
120
- .description("show logs for all or specific service")
121
- .action(async (service, opts) => {
122
- const args = ["logs"]; if (opts.follow) args.push("-f"); if (service) args.push(service);
123
- const code = await runCompose(args);
124
- if (code !== 0) process.exitCode = code;
125
- });
126
- program
127
- .command("health")
128
- .description("health check")
129
- .action(async () => {
130
- const { exec } = require("child_process");
131
- const util = require("util");
132
- const execPromise = util.promisify(exec);
133
-
134
- console.log("Checking service health...\n");
135
-
136
- const services = [
137
- { name: "Grafana", url: "http://localhost:3000/api/health" },
138
- { name: "Prometheus", url: "http://localhost:59090/-/healthy" },
139
- { name: "PGWatch (Postgres)", url: "http://localhost:58080/health" },
140
- { name: "PGWatch (Prometheus)", url: "http://localhost:58089/health" },
141
- ];
142
-
143
- let allHealthy = true;
144
-
145
- for (const service of services) {
146
- try {
147
- const { stdout, stderr } = await execPromise(
148
- `curl -sf -o /dev/null -w "%{http_code}" ${service.url}`,
149
- { timeout: 5000 }
150
- );
151
- const code = stdout.trim();
152
- if (code === "200") {
153
- console.log(`✓ ${service.name}: healthy`);
154
- } else {
155
- console.log(`✗ ${service.name}: unhealthy (HTTP ${code})`);
156
- allHealthy = false;
157
- }
158
- } catch (error) {
159
- console.log(`✗ ${service.name}: unreachable`);
160
- allHealthy = false;
161
- }
162
- }
163
-
164
- console.log("");
165
- if (allHealthy) {
166
- console.log("All services are healthy");
167
- } else {
168
- console.log("Some services are unhealthy");
169
- process.exitCode = 1;
170
- }
171
- });
172
- program
173
- .command("config")
174
- .description("show configuration")
175
- .action(async () => {
176
- const { fs, projectDir, composeFile, instancesFile } = resolvePaths();
177
- console.log(`Project Directory: ${projectDir}`);
178
- console.log(`Docker Compose File: ${composeFile}`);
179
- console.log(`Instances File: ${instancesFile}`);
180
- if (fs.existsSync(instancesFile)) {
181
- console.log("\nInstances configuration:\n");
182
- const text = fs.readFileSync(instancesFile, "utf8");
183
- process.stdout.write(text);
184
- if (!/\n$/.test(text)) console.log();
185
- }
186
- });
187
- program
188
- .command("update-config")
189
- .description("apply configuration (generate sources)")
190
- .action(async () => {
191
- const code = await runCompose(["run", "--rm", "sources-generator"]);
192
- if (code !== 0) process.exitCode = code;
193
- });
194
- program
195
- .command("update")
196
- .description("update project")
197
- .action(async () => {
198
- const { exec } = require("child_process");
199
- const util = require("util");
200
- const execPromise = util.promisify(exec);
201
- const fs = require("fs");
202
- const path = require("path");
203
-
204
- console.log("Updating PostgresAI monitoring stack...\n");
205
-
206
- try {
207
- // Check if we're in a git repo
208
- const gitDir = path.resolve(process.cwd(), ".git");
209
- if (!fs.existsSync(gitDir)) {
210
- console.error("Not a git repository. Cannot update.");
211
- process.exitCode = 1;
212
- return;
213
- }
214
-
215
- // Fetch latest changes
216
- console.log("Fetching latest changes...");
217
- await execPromise("git fetch origin");
218
-
219
- // Check current branch
220
- const { stdout: branch } = await execPromise("git rev-parse --abbrev-ref HEAD");
221
- const currentBranch = branch.trim();
222
- console.log(`Current branch: ${currentBranch}`);
223
-
224
- // Pull latest changes
225
- console.log("Pulling latest changes...");
226
- const { stdout: pullOut } = await execPromise("git pull origin " + currentBranch);
227
- console.log(pullOut);
228
-
229
- // Update Docker images
230
- console.log("\nUpdating Docker images...");
231
- const code = await runCompose(["pull"]);
232
-
233
- if (code === 0) {
234
- console.log("\n✓ Update completed successfully");
235
- console.log("\nTo apply updates, restart services:");
236
- console.log(" postgres-ai restart");
237
- } else {
238
- console.error("\n✗ Docker image update failed");
239
- process.exitCode = 1;
240
- }
241
- } catch (error) {
242
- console.error(`Update failed: ${error.message}`);
243
- process.exitCode = 1;
244
- }
245
- });
246
- program
247
- .command("reset [service]")
248
- .description("reset all or specific service")
249
- .action(async (service) => {
250
- const readline = require("readline");
251
- const rl = readline.createInterface({
252
- input: process.stdin,
253
- output: process.stdout,
254
- });
255
-
256
- const question = (prompt) => new Promise((resolve) => rl.question(prompt, resolve));
257
-
258
- try {
259
- if (service) {
260
- // Reset specific service
261
- console.log(`\nThis will stop '${service}', remove its volume, and restart it.`);
262
- console.log("All data for this service will be lost!\n");
263
-
264
- const answer = await question("Continue? (y/N): ");
265
- if (answer.toLowerCase() !== "y") {
266
- console.log("Cancelled");
267
- rl.close();
268
- return;
269
- }
270
-
271
- console.log(`\nStopping ${service}...`);
272
- await runCompose(["stop", service]);
273
-
274
- console.log(`Removing volume for ${service}...`);
275
- await runCompose(["rm", "-f", "-v", service]);
276
-
277
- console.log(`Restarting ${service}...`);
278
- const code = await runCompose(["up", "-d", service]);
279
-
280
- if (code === 0) {
281
- console.log(`\n✓ Service '${service}' has been reset`);
282
- } else {
283
- console.error(`\n✗ Failed to restart '${service}'`);
284
- process.exitCode = 1;
285
- }
286
- } else {
287
- // Reset all services
288
- console.log("\nThis will stop all services and remove all data!");
289
- console.log("Volumes, networks, and containers will be deleted.\n");
290
-
291
- const answer = await question("Continue? (y/N): ");
292
- if (answer.toLowerCase() !== "y") {
293
- console.log("Cancelled");
294
- rl.close();
295
- return;
296
- }
297
-
298
- console.log("\nStopping services and removing data...");
299
- const downCode = await runCompose(["down", "-v"]);
300
-
301
- if (downCode === 0) {
302
- console.log("✓ Environment reset completed - all containers and data removed");
303
- } else {
304
- console.error("✗ Reset failed");
305
- process.exitCode = 1;
306
- }
307
- }
308
-
309
- rl.close();
310
- } catch (error) {
311
- rl.close();
312
- console.error(`Reset failed: ${error.message}`);
313
- process.exitCode = 1;
314
- }
315
- });
316
- program
317
- .command("clean")
318
- .description("cleanup artifacts")
319
- .action(async () => {
320
- const { exec } = require("child_process");
321
- const util = require("util");
322
- const execPromise = util.promisify(exec);
323
-
324
- console.log("Cleaning up Docker resources...\n");
325
-
326
- try {
327
- // Remove stopped containers
328
- const { stdout: containers } = await execPromise("docker ps -aq --filter 'status=exited'");
329
- if (containers.trim()) {
330
- await execPromise(`docker rm ${containers.trim().split('\n').join(' ')}`);
331
- console.log("✓ Removed stopped containers");
332
- } else {
333
- console.log("✓ No stopped containers to remove");
334
- }
335
-
336
- // Remove unused volumes
337
- const { stdout: volumeOut } = await execPromise("docker volume prune -f");
338
- console.log("✓ Removed unused volumes");
339
-
340
- // Remove unused networks
341
- const { stdout: networkOut } = await execPromise("docker network prune -f");
342
- console.log("✓ Removed unused networks");
343
-
344
- // Remove dangling images
345
- const { stdout: imageOut } = await execPromise("docker image prune -f");
346
- console.log("✓ Removed dangling images");
347
-
348
- console.log("\nCleanup completed");
349
- } catch (error) {
350
- console.error(`Error during cleanup: ${error.message}`);
351
- process.exitCode = 1;
352
- }
353
- });
354
- program
355
- .command("shell <service>")
356
- .description("open service shell")
357
- .action(async (service) => {
358
- const code = await runCompose(["exec", "-T", service, "/bin/sh"]);
359
- if (code !== 0) process.exitCode = code;
360
- });
361
- program
362
- .command("check")
363
- .description("system readiness check")
364
- .action(async () => {
365
- const code = await runCompose(["ps"]);
366
- if (code !== 0) process.exitCode = code;
367
- });
368
-
369
- // Instance management
370
- program
371
- .command("list-instances")
372
- .description("list instances")
373
- .action(async () => {
374
- const fs = require("fs");
375
- const path = require("path");
376
- const instancesPath = path.resolve(process.cwd(), "instances.yml");
377
- if (!fs.existsSync(instancesPath)) {
378
- console.error(`instances.yml not found in ${process.cwd()}`);
379
- process.exitCode = 1;
380
- return;
381
- }
382
- const content = fs.readFileSync(instancesPath, "utf8");
383
- const lines = content.split(/\r?\n/);
384
- let currentName = "";
385
- let printed = false;
386
- const collected = [];
387
- for (const line of lines) {
388
- const m = line.match(/^-[\t ]*name:[\t ]*(.+)$/);
389
- if (m) {
390
- currentName = m[1].trim();
391
- collected.push(currentName);
392
- printed = true;
393
- }
394
- }
395
- // Hide demo placeholder if that's the only entry
396
- if (printed) {
397
- const filtered = collected.filter((n) => n !== "target-database");
398
- const list = filtered.length > 0 ? filtered : [];
399
- if (list.length === 0) {
400
- console.log("No instances configured");
401
- console.log("");
402
- console.log("To add an instance:");
403
- console.log(" postgres-ai add-instance <connection-string> <name>");
404
- console.log("");
405
- console.log("Example:");
406
- console.log(" postgres-ai add-instance 'postgresql://user:pass@host:5432/db' my-db");
407
- return;
408
- }
409
- for (const n of list) console.log(`Instance: ${n}`);
410
- } else {
411
- console.log("No instances found");
412
- }
413
- });
414
- program
415
- .command("add-instance [connStr] [name]")
416
- .description("add instance")
417
- .action(async (connStr, name) => {
418
- const fs = require("fs");
419
- const path = require("path");
420
- const file = path.resolve(process.cwd(), "instances.yml");
421
- if (!connStr) {
422
- console.error("Connection string required: postgresql://user:pass@host:port/db");
423
- process.exitCode = 1;
424
- return;
425
- }
426
- const m = connStr.match(/^postgresql:\/\/([^:]+):([^@]+)@([^:\/]+)(?::(\d+))?\/(.+)$/);
427
- if (!m) {
428
- console.error("Invalid connection string format");
429
- process.exitCode = 1;
430
- return;
431
- }
432
- const host = m[3];
433
- const db = m[5];
434
- const instanceName = name && name.trim() ? name.trim() : `${host}-${db}`.replace(/[^a-zA-Z0-9-]/g, "-");
435
- const lineStart = `- name: ${instanceName}`;
436
- const body = `- name: ${instanceName}\n conn_str: ${connStr}\n preset_metrics: full\n custom_metrics:\n is_enabled: true\n group: default\n custom_tags:\n env: production\n cluster: default\n node_name: ${instanceName}\n sink_type: ~sink_type~\n`;
437
- const content = fs.existsSync(file) ? fs.readFileSync(file, "utf8") : "";
438
- if (new RegExp(`^${lineStart}$`, "m").test(content)) {
439
- console.error(`Instance '${instanceName}' already exists`);
440
- process.exitCode = 1;
441
- return;
442
- }
443
- fs.appendFileSync(file, (content && !/\n$/.test(content) ? "\n" : "") + body, "utf8");
444
- console.log(`Instance '${instanceName}' added`);
445
- });
446
- program
447
- .command("remove-instance <name>")
448
- .description("remove instance")
449
- .action(async (name) => {
450
- const fs = require("fs");
451
- const path = require("path");
452
- const file = path.resolve(process.cwd(), "instances.yml");
453
- if (!fs.existsSync(file)) {
454
- console.error("instances.yml not found");
455
- process.exitCode = 1;
456
- return;
457
- }
458
- const text = fs.readFileSync(file, "utf8");
459
- const lines = text.split(/\r?\n/);
460
- const out = [];
461
- let skip = false;
462
- for (let i = 0; i < lines.length; i++) {
463
- const line = lines[i];
464
- const isStart = /^-[\t ]*name:[\t ]*(.+)$/.test(line);
465
- if (isStart) {
466
- const n = line.replace(/^-[\t ]*name:[\t ]*/, "").trim();
467
- if (n === name) {
468
- skip = true;
469
- continue;
470
- } else if (skip) {
471
- skip = false;
472
- }
473
- }
474
- if (!skip) out.push(line);
475
- }
476
- if (out.join("\n") === text) {
477
- console.error(`Instance '${name}' not found`);
478
- process.exitCode = 1;
479
- return;
480
- }
481
- fs.writeFileSync(file, out.join("\n"), "utf8");
482
- console.log(`Instance '${name}' removed`);
483
- });
484
- program
485
- .command("test-instance <name>")
486
- .description("test instance connectivity")
487
- .action(async (name) => {
488
- const fs = require("fs");
489
- const path = require("path");
490
- const { exec } = require("child_process");
491
- const util = require("util");
492
- const execPromise = util.promisify(exec);
493
-
494
- const instancesPath = path.resolve(process.cwd(), "instances.yml");
495
- if (!fs.existsSync(instancesPath)) {
496
- console.error("instances.yml not found");
497
- process.exitCode = 1;
498
- return;
499
- }
500
-
501
- const content = fs.readFileSync(instancesPath, "utf8");
502
- const lines = content.split(/\r?\n/);
503
- let connStr = "";
504
- let foundInstance = false;
505
-
506
- for (let i = 0; i < lines.length; i++) {
507
- const nameLine = lines[i].match(/^-[\t ]*name:[\t ]*(.+)$/);
508
- if (nameLine && nameLine[1].trim() === name) {
509
- foundInstance = true;
510
- // Look for conn_str in next lines
511
- for (let j = i + 1; j < lines.length && j < i + 15; j++) {
512
- const connLine = lines[j].match(/^[\t ]*conn_str:[\t ]*(.+)$/);
513
- if (connLine) {
514
- connStr = connLine[1].trim();
515
- break;
516
- }
517
- // Stop at next instance
518
- if (lines[j].match(/^-[\t ]*name:/)) break;
519
- }
520
- break;
521
- }
522
- }
523
-
524
- if (!foundInstance) {
525
- console.error(`Instance '${name}' not found`);
526
- process.exitCode = 1;
527
- return;
528
- }
529
-
530
- if (!connStr) {
531
- console.error(`Connection string not found for instance '${name}'`);
532
- process.exitCode = 1;
533
- return;
534
- }
535
-
536
- console.log(`Testing connection to '${name}'...`);
537
-
538
- try {
539
- const { stdout, stderr } = await execPromise(
540
- `psql "${connStr}" -c "SELECT version();" --no-psqlrc`,
541
- { timeout: 10000, env: { ...process.env, PAGER: 'cat' } }
542
- );
543
- console.log(`✓ Connection successful`);
544
- console.log(stdout.trim());
545
- } catch (error) {
546
- console.error(`✗ Connection failed: ${error.message}`);
547
- process.exitCode = 1;
548
- }
549
- });
550
-
551
- // API key and grafana
552
- program
553
- .command("add-key <apiKey>")
554
- .description("store API key")
555
- .action(async (apiKey) => {
556
- const fs = require("fs");
557
- const path = require("path");
558
- const cfgPath = path.resolve(process.cwd(), ".pgwatch-config");
559
- const existing = fs.existsSync(cfgPath) ? fs.readFileSync(cfgPath, "utf8") : "";
560
- const filtered = existing
561
- .split(/\r?\n/)
562
- .filter((l) => !/^api_key=/.test(l))
563
- .join("\n")
564
- .replace(/\n+$/g, "");
565
- const next = filtered.length ? `${filtered}\napi_key=${apiKey}\n` : `api_key=${apiKey}\n`;
566
- fs.writeFileSync(cfgPath, next, "utf8");
567
- console.log("API key saved to .pgwatch-config");
568
- });
569
-
570
- program
571
- .command("show-key")
572
- .description("show API key (masked)")
573
- .action(async () => {
574
- const fs = require("fs");
575
- const path = require("path");
576
- const cfgPath = path.resolve(process.cwd(), ".pgwatch-config");
577
- if (!fs.existsSync(cfgPath)) {
578
- console.log("No API key configured");
579
- return;
580
- }
581
- const content = fs.readFileSync(cfgPath, "utf8");
582
- const m = content.match(/^api_key=(.+)$/m);
583
- if (!m) {
584
- console.log("No API key configured");
585
- return;
586
- }
587
- const key = m[1].trim();
588
- if (!key) {
589
- console.log("No API key configured");
590
- return;
591
- }
592
- const mask = (k) => (k.length <= 8 ? "****" : `${k.slice(0, 4)}${"*".repeat(k.length - 8)}${k.slice(-4)}`);
593
- console.log(`Current API key: ${mask(key)}`);
594
- });
595
-
596
- program
597
- .command("remove-key")
598
- .description("remove API key")
599
- .action(async () => {
600
- const fs = require("fs");
601
- const path = require("path");
602
- const cfgPath = path.resolve(process.cwd(), ".pgwatch-config");
603
- if (!fs.existsSync(cfgPath)) {
604
- console.log("No API key configured");
605
- return;
606
- }
607
- const content = fs.readFileSync(cfgPath, "utf8");
608
- const filtered = content
609
- .split(/\r?\n/)
610
- .filter((l) => !/^api_key=/.test(l))
611
- .join("\n")
612
- .replace(/\n+$/g, "\n");
613
- fs.writeFileSync(cfgPath, filtered, "utf8");
614
- console.log("API key removed");
615
- });
616
- program
617
- .command("generate-grafana-password")
618
- .description("generate Grafana password")
619
- .action(async () => {
620
- const fs = require("fs");
621
- const path = require("path");
622
- const { exec } = require("child_process");
623
- const util = require("util");
624
- const execPromise = util.promisify(exec);
625
-
626
- const cfgPath = path.resolve(process.cwd(), ".pgwatch-config");
627
-
628
- try {
629
- // Generate secure password using openssl
630
- const { stdout: password } = await execPromise(
631
- "openssl rand -base64 12 | tr -d '\n'"
632
- );
633
- const newPassword = password.trim();
634
-
635
- if (!newPassword) {
636
- console.error("Failed to generate password");
637
- process.exitCode = 1;
638
- return;
639
- }
640
-
641
- // Read existing config
642
- let config = "";
643
- if (fs.existsSync(cfgPath)) {
644
- config = fs.readFileSync(cfgPath, "utf8");
645
- }
646
-
647
- // Update or add grafana_password
648
- const lines = config.split(/\r?\n/).filter((l) => !/^grafana_password=/.test(l));
649
- lines.push(`grafana_password=${newPassword}`);
650
-
651
- // Write back
652
- fs.writeFileSync(cfgPath, lines.filter(Boolean).join("\n") + "\n", "utf8");
653
-
654
- console.log("✓ New Grafana password generated and saved");
655
- console.log("\nNew credentials:");
656
- console.log(" URL: http://localhost:3000");
657
- console.log(" Username: monitor");
658
- console.log(` Password: ${newPassword}`);
659
- console.log("\nRestart Grafana to apply:");
660
- console.log(" postgres-ai restart grafana");
661
- } catch (error) {
662
- console.error(`Failed to generate password: ${error.message}`);
663
- console.error("\nNote: This command requires 'openssl' to be installed");
664
- process.exitCode = 1;
665
- }
666
- });
667
- program
668
- .command("show-grafana-credentials")
669
- .description("show Grafana credentials")
670
- .action(async () => {
671
- const fs = require("fs");
672
- const path = require("path");
673
- const cfgPath = path.resolve(process.cwd(), ".pgwatch-config");
674
- if (!fs.existsSync(cfgPath)) {
675
- console.error("Configuration file not found. Run 'quickstart' first.");
676
- process.exitCode = 1;
677
- return;
678
- }
679
- const content = fs.readFileSync(cfgPath, "utf8");
680
- const lines = content.split(/\r?\n/);
681
- let password = "";
682
- for (const line of lines) {
683
- const m = line.match(/^grafana_password=(.+)$/);
684
- if (m) {
685
- password = m[1].trim();
686
- break;
687
- }
688
- }
689
- if (!password) {
690
- console.error("Grafana password not found in configuration");
691
- process.exitCode = 1;
692
- return;
693
- }
694
- console.log("\nGrafana credentials:");
695
- console.log(" URL: http://localhost:3000");
696
- console.log(" Username: monitor");
697
- console.log(` Password: ${password}`);
698
- console.log("");
699
- });
700
-
701
- program.parseAsync(process.argv);
702
-
703
-