@insforge/cli 0.1.31 → 0.1.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3084,6 +3084,594 @@ function formatSize2(gb) {
3084
3084
  return `${gb.toFixed(2)} GB`;
3085
3085
  }
3086
3086
 
3087
+ // src/commands/diagnose/metrics.ts
3088
+ var METRIC_LABELS = {
3089
+ cpu_usage: "CPU Usage",
3090
+ memory_usage: "Memory Usage",
3091
+ disk_usage: "Disk Usage",
3092
+ network_in: "Network In",
3093
+ network_out: "Network Out"
3094
+ };
3095
+ var NETWORK_METRICS = /* @__PURE__ */ new Set(["network_in", "network_out"]);
3096
+ function formatValue(metric, value) {
3097
+ if (NETWORK_METRICS.has(metric)) {
3098
+ return formatBytes(value) + "/s";
3099
+ }
3100
+ return `${value.toFixed(1)}%`;
3101
+ }
3102
+ function formatBytes(bytes) {
3103
+ if (bytes < 1024) return `${bytes.toFixed(1)} B`;
3104
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
3105
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
3106
+ }
3107
+ function computeStats(data) {
3108
+ if (data.length === 0) return { latest: 0, avg: 0, max: 0 };
3109
+ const latest = data[data.length - 1].value;
3110
+ let sum = 0;
3111
+ let max = -Infinity;
3112
+ for (const d of data) {
3113
+ sum += d.value;
3114
+ if (d.value > max) max = d.value;
3115
+ }
3116
+ return { latest, avg: sum / data.length, max };
3117
+ }
3118
+ function aggregateByMetric(series) {
3119
+ const grouped = /* @__PURE__ */ new Map();
3120
+ for (const s of series) {
3121
+ const existing = grouped.get(s.metric);
3122
+ if (existing) existing.push(s);
3123
+ else grouped.set(s.metric, [s]);
3124
+ }
3125
+ const result = [];
3126
+ for (const [metric, group] of grouped) {
3127
+ if (group.length === 1 || !NETWORK_METRICS.has(metric)) {
3128
+ result.push(group[0]);
3129
+ continue;
3130
+ }
3131
+ const tsMap = /* @__PURE__ */ new Map();
3132
+ for (const s of group) {
3133
+ for (const d of s.data) {
3134
+ tsMap.set(d.timestamp, (tsMap.get(d.timestamp) ?? 0) + d.value);
3135
+ }
3136
+ }
3137
+ const merged = [...tsMap.entries()].sort((a, b) => a[0] - b[0]).map(([timestamp, value]) => ({ timestamp, value }));
3138
+ result.push({ metric, instance_id: "aggregate", data: merged });
3139
+ }
3140
+ return result;
3141
+ }
3142
+ async function fetchMetricsSummary(projectId, apiUrl) {
3143
+ const res = await platformFetch(`/projects/v1/${projectId}/metrics?range=1h`, {}, apiUrl);
3144
+ return await res.json();
3145
+ }
3146
+ function registerDiagnoseMetricsCommand(diagnoseCmd2) {
3147
+ diagnoseCmd2.command("metrics").description("Display EC2 instance metrics (CPU, memory, disk, network)").option("--range <range>", "Time range: 1h, 6h, 24h, 7d", "1h").option("--metrics <list>", "Comma-separated metrics to query").action(async (opts, cmd) => {
3148
+ const { json, apiUrl } = getRootOpts(cmd);
3149
+ try {
3150
+ await requireAuth(apiUrl);
3151
+ const config = getProjectConfig();
3152
+ if (!config) throw new ProjectNotLinkedError();
3153
+ if (config.project_id === "oss-project") {
3154
+ throw new CLIError(
3155
+ "Metrics requires InsForge Platform login. Not available when linked via --api-key."
3156
+ );
3157
+ }
3158
+ const params = new URLSearchParams({ range: opts.range });
3159
+ if (opts.metrics) params.set("metrics", opts.metrics);
3160
+ const res = await platformFetch(
3161
+ `/projects/v1/${config.project_id}/metrics?${params.toString()}`,
3162
+ {},
3163
+ apiUrl
3164
+ );
3165
+ const data = await res.json();
3166
+ const aggregated = aggregateByMetric(data.metrics);
3167
+ if (json) {
3168
+ const enriched = {
3169
+ ...data,
3170
+ metrics: aggregated.map((m) => {
3171
+ const stats = computeStats(m.data);
3172
+ return { ...m, latest: stats.latest, avg: stats.avg, max: stats.max };
3173
+ })
3174
+ };
3175
+ outputJson(enriched);
3176
+ } else {
3177
+ if (!aggregated.length) {
3178
+ console.log("No metrics data available.");
3179
+ return;
3180
+ }
3181
+ const headers = ["Metric", "Latest", "Avg", "Max", "Range"];
3182
+ const rows = aggregated.map((m) => {
3183
+ const stats = computeStats(m.data);
3184
+ return [
3185
+ METRIC_LABELS[m.metric] ?? m.metric,
3186
+ formatValue(m.metric, stats.latest),
3187
+ formatValue(m.metric, stats.avg),
3188
+ formatValue(m.metric, stats.max),
3189
+ data.range
3190
+ ];
3191
+ });
3192
+ outputTable(headers, rows);
3193
+ }
3194
+ await reportCliUsage("cli.diagnose.metrics", true);
3195
+ } catch (err) {
3196
+ await reportCliUsage("cli.diagnose.metrics", false);
3197
+ handleError(err, json);
3198
+ }
3199
+ });
3200
+ }
3201
+
3202
+ // src/commands/diagnose/advisor.ts
3203
+ async function fetchAdvisorSummary(projectId, apiUrl) {
3204
+ const res = await platformFetch(`/projects/v1/${projectId}/advisor/latest`, {}, apiUrl);
3205
+ return await res.json();
3206
+ }
3207
+ function registerDiagnoseAdvisorCommand(diagnoseCmd2) {
3208
+ diagnoseCmd2.command("advisor").description("Display latest advisor scan results and issues").option("--severity <level>", "Filter by severity: critical, warning, info").option("--category <cat>", "Filter by category: security, performance, health").option("--limit <n>", "Maximum number of issues to return", "50").action(async (opts, cmd) => {
3209
+ const { json, apiUrl } = getRootOpts(cmd);
3210
+ try {
3211
+ await requireAuth(apiUrl);
3212
+ const config = getProjectConfig();
3213
+ if (!config) throw new ProjectNotLinkedError();
3214
+ if (config.project_id === "oss-project") {
3215
+ throw new CLIError(
3216
+ "Advisor requires InsForge Platform login. Not available when linked via --api-key."
3217
+ );
3218
+ }
3219
+ const projectId = config.project_id;
3220
+ const scanRes = await platformFetch(
3221
+ `/projects/v1/${projectId}/advisor/latest`,
3222
+ {},
3223
+ apiUrl
3224
+ );
3225
+ const scan = await scanRes.json();
3226
+ const issueParams = new URLSearchParams();
3227
+ if (opts.severity) issueParams.set("severity", opts.severity);
3228
+ if (opts.category) issueParams.set("category", opts.category);
3229
+ issueParams.set("limit", opts.limit);
3230
+ const issuesRes = await platformFetch(
3231
+ `/projects/v1/${projectId}/advisor/latest/issues?${issueParams.toString()}`,
3232
+ {},
3233
+ apiUrl
3234
+ );
3235
+ const issuesData = await issuesRes.json();
3236
+ if (json) {
3237
+ outputJson({ scan, issues: issuesData.issues });
3238
+ } else {
3239
+ const date = new Date(scan.scannedAt).toLocaleDateString();
3240
+ const s = scan.summary;
3241
+ console.log(
3242
+ `Scan: ${date} (${scan.status}) \u2014 ${s.critical} critical, ${s.warning} warning, ${s.info} info
3243
+ `
3244
+ );
3245
+ if (!issuesData.issues || issuesData.issues.length === 0) {
3246
+ console.log("No issues found.");
3247
+ return;
3248
+ }
3249
+ const headers = ["Severity", "Category", "Affected Object", "Title"];
3250
+ const rows = issuesData.issues.map((issue) => [
3251
+ issue.severity,
3252
+ issue.category,
3253
+ issue.affectedObject,
3254
+ issue.title
3255
+ ]);
3256
+ outputTable(headers, rows);
3257
+ }
3258
+ await reportCliUsage("cli.diagnose.advisor", true);
3259
+ } catch (err) {
3260
+ await reportCliUsage("cli.diagnose.advisor", false);
3261
+ handleError(err, json);
3262
+ }
3263
+ });
3264
+ }
3265
+
3266
+ // src/commands/diagnose/db.ts
3267
+ var DB_CHECKS = {
3268
+ connections: {
3269
+ label: "Connections",
3270
+ sql: `SELECT
3271
+ (SELECT count(*) FROM pg_stat_activity WHERE state IS NOT NULL) AS active,
3272
+ (SELECT setting::int FROM pg_settings WHERE name = 'max_connections') AS max`,
3273
+ format(rows) {
3274
+ const r = rows[0] ?? {};
3275
+ console.log(` Active: ${r.active} / ${r.max}`);
3276
+ }
3277
+ },
3278
+ "slow-queries": {
3279
+ label: "Slow Queries (>5s)",
3280
+ sql: `SELECT pid, now() - query_start AS duration, substring(query for 80) AS query
3281
+ FROM pg_stat_activity
3282
+ WHERE state = 'active' AND now() - query_start > interval '5 seconds'
3283
+ ORDER BY query_start ASC`,
3284
+ format(rows) {
3285
+ if (rows.length === 0) {
3286
+ console.log(" None");
3287
+ return;
3288
+ }
3289
+ const headers = ["PID", "Duration", "Query"];
3290
+ const tableRows = rows.map((r) => [
3291
+ String(r.pid ?? ""),
3292
+ String(r.duration ?? ""),
3293
+ String(r.query ?? "")
3294
+ ]);
3295
+ outputTable(headers, tableRows);
3296
+ }
3297
+ },
3298
+ bloat: {
3299
+ label: "Table Bloat (top 10)",
3300
+ sql: `SELECT schemaname || '.' || relname AS table, n_dead_tup AS dead_tuples
3301
+ FROM pg_stat_user_tables
3302
+ ORDER BY n_dead_tup DESC
3303
+ LIMIT 10`,
3304
+ format(rows) {
3305
+ if (rows.length === 0) {
3306
+ console.log(" No user tables found.");
3307
+ return;
3308
+ }
3309
+ const headers = ["Table", "Dead Tuples"];
3310
+ const tableRows = rows.map((r) => [
3311
+ String(r.table ?? ""),
3312
+ String(r.dead_tuples ?? 0)
3313
+ ]);
3314
+ outputTable(headers, tableRows);
3315
+ }
3316
+ },
3317
+ size: {
3318
+ label: "Table Sizes (top 10)",
3319
+ sql: `SELECT schemaname || '.' || relname AS table,
3320
+ pg_size_pretty(pg_total_relation_size(relid)) AS size
3321
+ FROM pg_stat_user_tables
3322
+ ORDER BY pg_total_relation_size(relid) DESC
3323
+ LIMIT 10`,
3324
+ format(rows) {
3325
+ if (rows.length === 0) {
3326
+ console.log(" No user tables found.");
3327
+ return;
3328
+ }
3329
+ const headers = ["Table", "Size"];
3330
+ const tableRows = rows.map((r) => [
3331
+ String(r.table ?? ""),
3332
+ String(r.size ?? "")
3333
+ ]);
3334
+ outputTable(headers, tableRows);
3335
+ }
3336
+ },
3337
+ "index-usage": {
3338
+ label: "Index Usage (worst 10)",
3339
+ sql: `SELECT schemaname || '.' || relname AS table, idx_scan, seq_scan,
3340
+ CASE WHEN (idx_scan + seq_scan) > 0
3341
+ THEN round(100.0 * idx_scan / (idx_scan + seq_scan), 1)
3342
+ ELSE 0 END AS idx_ratio
3343
+ FROM pg_stat_user_tables
3344
+ WHERE (idx_scan + seq_scan) > 0
3345
+ ORDER BY idx_ratio ASC
3346
+ LIMIT 10`,
3347
+ format(rows) {
3348
+ if (rows.length === 0) {
3349
+ console.log(" No scan data available.");
3350
+ return;
3351
+ }
3352
+ const headers = ["Table", "Index Scans", "Seq Scans", "Index Ratio"];
3353
+ const tableRows = rows.map((r) => [
3354
+ String(r.table ?? ""),
3355
+ String(r.idx_scan ?? 0),
3356
+ String(r.seq_scan ?? 0),
3357
+ `${r.idx_ratio ?? 0}%`
3358
+ ]);
3359
+ outputTable(headers, tableRows);
3360
+ }
3361
+ },
3362
+ locks: {
3363
+ label: "Waiting Locks",
3364
+ sql: `SELECT pid, mode, relation::regclass AS relation, granted
3365
+ FROM pg_locks
3366
+ WHERE NOT granted`,
3367
+ format(rows) {
3368
+ if (rows.length === 0) {
3369
+ console.log(" None");
3370
+ return;
3371
+ }
3372
+ const headers = ["PID", "Mode", "Relation", "Granted"];
3373
+ const tableRows = rows.map((r) => [
3374
+ String(r.pid ?? ""),
3375
+ String(r.mode ?? ""),
3376
+ String(r.relation ?? ""),
3377
+ String(r.granted ?? "")
3378
+ ]);
3379
+ outputTable(headers, tableRows);
3380
+ }
3381
+ },
3382
+ "cache-hit": {
3383
+ label: "Cache Hit Ratio",
3384
+ sql: `SELECT CASE WHEN sum(heap_blks_hit + heap_blks_read) > 0
3385
+ THEN round(100.0 * sum(heap_blks_hit) / sum(heap_blks_hit + heap_blks_read), 1)
3386
+ ELSE 0 END AS ratio
3387
+ FROM pg_statio_user_tables`,
3388
+ format(rows) {
3389
+ const ratio = rows[0]?.ratio ?? 0;
3390
+ console.log(` ${ratio}%`);
3391
+ }
3392
+ }
3393
+ };
3394
+ var ALL_CHECKS = Object.keys(DB_CHECKS);
3395
+ async function runDbChecks() {
3396
+ const results = {};
3397
+ for (const key of ALL_CHECKS) {
3398
+ try {
3399
+ const { rows } = await runRawSql(DB_CHECKS[key].sql, true);
3400
+ results[key] = rows;
3401
+ } catch {
3402
+ results[key] = [];
3403
+ }
3404
+ }
3405
+ return results;
3406
+ }
3407
+ function registerDiagnoseDbCommand(diagnoseCmd2) {
3408
+ diagnoseCmd2.command("db").description("Run database health checks (connections, bloat, index usage, etc.)").option("--check <checks>", "Comma-separated checks: " + ALL_CHECKS.join(", "), "all").action(async (opts, cmd) => {
3409
+ const { json } = getRootOpts(cmd);
3410
+ try {
3411
+ await requireAuth();
3412
+ if (!getProjectConfig()) throw new ProjectNotLinkedError();
3413
+ const checkNames = opts.check === "all" ? ALL_CHECKS : opts.check.split(",").map((s) => s.trim());
3414
+ const results = {};
3415
+ for (const name of checkNames) {
3416
+ const check = DB_CHECKS[name];
3417
+ if (!check) {
3418
+ console.error(`Unknown check: ${name}. Available: ${ALL_CHECKS.join(", ")}`);
3419
+ continue;
3420
+ }
3421
+ try {
3422
+ const { rows } = await runRawSql(check.sql, true);
3423
+ results[name] = rows;
3424
+ } catch (err) {
3425
+ results[name] = [];
3426
+ if (!json) {
3427
+ console.error(` Failed to run ${name}: ${err instanceof Error ? err.message : err}`);
3428
+ }
3429
+ }
3430
+ }
3431
+ if (json) {
3432
+ outputJson(results);
3433
+ } else {
3434
+ for (const name of checkNames) {
3435
+ const check = DB_CHECKS[name];
3436
+ if (!check) continue;
3437
+ console.log(`
3438
+ \u2500\u2500 ${check.label} ${"\u2500".repeat(Math.max(0, 40 - check.label.length))}`);
3439
+ check.format(results[name] ?? []);
3440
+ }
3441
+ console.log("");
3442
+ }
3443
+ await reportCliUsage("cli.diagnose.db", true);
3444
+ } catch (err) {
3445
+ await reportCliUsage("cli.diagnose.db", false);
3446
+ handleError(err, json);
3447
+ }
3448
+ });
3449
+ }
3450
+
3451
+ // src/commands/diagnose/logs.ts
3452
+ var LOG_SOURCES = ["insforge.logs", "postgREST.logs", "postgres.logs", "function.logs"];
3453
+ var ERROR_PATTERN = /\b(error|fatal|panic)\b/i;
3454
+ function parseLogEntry(entry) {
3455
+ if (typeof entry === "string") {
3456
+ return { ts: "", msg: entry };
3457
+ }
3458
+ const e = entry;
3459
+ const ts = String(e.timestamp ?? e.time ?? "");
3460
+ const msg = String(e.message ?? e.msg ?? e.log ?? JSON.stringify(e));
3461
+ return { ts, msg };
3462
+ }
3463
+ async function fetchSourceLogs(source, limit) {
3464
+ const res = await ossFetch(`/api/logs/${encodeURIComponent(source)}?limit=${limit}`);
3465
+ const data = await res.json();
3466
+ const logs = Array.isArray(data) ? data : data.logs ?? [];
3467
+ const errors = [];
3468
+ for (const entry of logs) {
3469
+ const { ts, msg } = parseLogEntry(entry);
3470
+ if (ERROR_PATTERN.test(msg)) {
3471
+ errors.push({ timestamp: ts, message: msg, source });
3472
+ }
3473
+ }
3474
+ return { source, total: logs.length, errors };
3475
+ }
3476
+ async function fetchLogsSummary(limit = 100) {
3477
+ const results = [];
3478
+ for (const source of LOG_SOURCES) {
3479
+ try {
3480
+ results.push(await fetchSourceLogs(source, limit));
3481
+ } catch {
3482
+ results.push({ source, total: 0, errors: [] });
3483
+ }
3484
+ }
3485
+ return results;
3486
+ }
3487
+ function registerDiagnoseLogsCommand(diagnoseCmd2) {
3488
+ diagnoseCmd2.command("logs").description("Aggregate error-level logs from all backend sources").option("--source <name>", "Specific log source to check").option("--limit <n>", "Number of log entries per source", "100").action(async (opts, cmd) => {
3489
+ const { json } = getRootOpts(cmd);
3490
+ try {
3491
+ await requireAuth();
3492
+ if (!getProjectConfig()) throw new ProjectNotLinkedError();
3493
+ const limit = parseInt(opts.limit, 10) || 100;
3494
+ const sources = opts.source ? [opts.source] : [...LOG_SOURCES];
3495
+ const summaries = [];
3496
+ for (const source of sources) {
3497
+ try {
3498
+ summaries.push(await fetchSourceLogs(source, limit));
3499
+ } catch {
3500
+ summaries.push({ source, total: 0, errors: [] });
3501
+ }
3502
+ }
3503
+ if (json) {
3504
+ outputJson({ sources: summaries });
3505
+ } else {
3506
+ const headers = ["Source", "Total", "Errors"];
3507
+ const rows = summaries.map((s) => [s.source, String(s.total), String(s.errors.length)]);
3508
+ outputTable(headers, rows);
3509
+ const allErrors = summaries.flatMap((s) => s.errors);
3510
+ if (allErrors.length > 0) {
3511
+ console.log("\n\u2500\u2500 Error Details " + "\u2500".repeat(30));
3512
+ for (const err of allErrors) {
3513
+ const prefix = err.timestamp ? `[${err.source}] ${err.timestamp}` : `[${err.source}]`;
3514
+ console.log(`
3515
+ ${prefix}`);
3516
+ console.log(` ${err.message}`);
3517
+ }
3518
+ console.log("");
3519
+ }
3520
+ }
3521
+ await reportCliUsage("cli.diagnose.logs", true);
3522
+ } catch (err) {
3523
+ await reportCliUsage("cli.diagnose.logs", false);
3524
+ handleError(err, json);
3525
+ }
3526
+ });
3527
+ }
3528
+
3529
+ // src/commands/diagnose/index.ts
3530
+ function sectionHeader(title) {
3531
+ return `\u2500\u2500 ${title} ${"\u2500".repeat(Math.max(0, 44 - title.length))}`;
3532
+ }
3533
+ function registerDiagnoseCommands(diagnoseCmd2) {
3534
+ diagnoseCmd2.description("Backend diagnostics \u2014 run with no subcommand for a full health report").action(async (_opts, cmd) => {
3535
+ const { json, apiUrl } = getRootOpts(cmd);
3536
+ try {
3537
+ await requireAuth(apiUrl);
3538
+ const config = getProjectConfig();
3539
+ if (!config) throw new ProjectNotLinkedError();
3540
+ const projectId = config.project_id;
3541
+ const projectName = config.project_name;
3542
+ const ossMode = config.project_id === "oss-project";
3543
+ const metricsPromise = ossMode ? Promise.reject(new Error("Platform login required (linked via --api-key)")) : fetchMetricsSummary(projectId, apiUrl);
3544
+ const advisorPromise = ossMode ? Promise.reject(new Error("Platform login required (linked via --api-key)")) : fetchAdvisorSummary(projectId, apiUrl);
3545
+ const [metricsResult, advisorResult, dbResult, logsResult] = await Promise.allSettled([
3546
+ metricsPromise,
3547
+ advisorPromise,
3548
+ runDbChecks(),
3549
+ fetchLogsSummary(100)
3550
+ ]);
3551
+ if (json) {
3552
+ const report = { project: projectName, errors: [] };
3553
+ const errors = [];
3554
+ if (metricsResult.status === "fulfilled") {
3555
+ const data = metricsResult.value;
3556
+ report.metrics = data.metrics.map((m) => {
3557
+ if (m.data.length === 0) return { metric: m.metric, latest: null, avg: null, max: null };
3558
+ let sum = 0;
3559
+ let max = -Infinity;
3560
+ for (const d of m.data) {
3561
+ sum += d.value;
3562
+ if (d.value > max) max = d.value;
3563
+ }
3564
+ return {
3565
+ metric: m.metric,
3566
+ latest: m.data[m.data.length - 1].value,
3567
+ avg: sum / m.data.length,
3568
+ max
3569
+ };
3570
+ });
3571
+ } else {
3572
+ report.metrics = null;
3573
+ errors.push(metricsResult.reason?.message ?? "Metrics unavailable");
3574
+ }
3575
+ if (advisorResult.status === "fulfilled") {
3576
+ report.advisor = advisorResult.value;
3577
+ } else {
3578
+ report.advisor = null;
3579
+ errors.push(advisorResult.reason?.message ?? "Advisor unavailable");
3580
+ }
3581
+ if (dbResult.status === "fulfilled") {
3582
+ report.db = dbResult.value;
3583
+ } else {
3584
+ report.db = null;
3585
+ errors.push(dbResult.reason?.message ?? "DB checks unavailable");
3586
+ }
3587
+ if (logsResult.status === "fulfilled") {
3588
+ report.logs = logsResult.value;
3589
+ } else {
3590
+ report.logs = null;
3591
+ errors.push(logsResult.reason?.message ?? "Logs unavailable");
3592
+ }
3593
+ report.errors = errors;
3594
+ outputJson(report);
3595
+ } else {
3596
+ console.log(`
3597
+ InsForge Health Report \u2014 ${projectName}
3598
+ `);
3599
+ console.log(sectionHeader("System Metrics (last 1h)"));
3600
+ if (metricsResult.status === "fulfilled") {
3601
+ const metrics = metricsResult.value.metrics;
3602
+ if (metrics.length === 0) {
3603
+ console.log(" No metrics data available.");
3604
+ } else {
3605
+ const vals = {};
3606
+ for (const m of metrics) {
3607
+ if (m.data.length > 0) vals[m.metric] = m.data[m.data.length - 1].value;
3608
+ }
3609
+ const cpu = vals.cpu_usage !== void 0 ? `${vals.cpu_usage.toFixed(1)}%` : "N/A";
3610
+ const mem = vals.memory_usage !== void 0 ? `${vals.memory_usage.toFixed(1)}%` : "N/A";
3611
+ const disk = vals.disk_usage !== void 0 ? `${vals.disk_usage.toFixed(1)}%` : "N/A";
3612
+ const netIn = vals.network_in !== void 0 ? formatBytesCompact(vals.network_in) + "/s" : "N/A";
3613
+ const netOut = vals.network_out !== void 0 ? formatBytesCompact(vals.network_out) + "/s" : "N/A";
3614
+ console.log(` CPU: ${cpu} Memory: ${mem}`);
3615
+ console.log(` Disk: ${disk} Network: \u2193${netIn} \u2191${netOut}`);
3616
+ }
3617
+ } else {
3618
+ console.log(` N/A \u2014 ${metricsResult.reason?.message ?? "unavailable"}`);
3619
+ }
3620
+ console.log("\n" + sectionHeader("Advisor Scan"));
3621
+ if (advisorResult.status === "fulfilled") {
3622
+ const scan = advisorResult.value;
3623
+ const s = scan.summary;
3624
+ const date = new Date(scan.scannedAt).toLocaleDateString();
3625
+ console.log(` ${date} (${scan.status}) \u2014 ${s.critical} critical \xB7 ${s.warning} warning \xB7 ${s.info} info`);
3626
+ } else {
3627
+ console.log(` N/A \u2014 ${advisorResult.reason?.message ?? "unavailable"}`);
3628
+ }
3629
+ console.log("\n" + sectionHeader("Database"));
3630
+ if (dbResult.status === "fulfilled") {
3631
+ const db = dbResult.value;
3632
+ const conn = db.connections?.[0];
3633
+ const cache = db["cache-hit"]?.[0];
3634
+ const deadTuples = (db.bloat ?? []).reduce(
3635
+ (sum, r) => sum + (Number(r.dead_tuples) || 0),
3636
+ 0
3637
+ );
3638
+ const lockCount = (db.locks ?? []).length;
3639
+ console.log(
3640
+ ` Connections: ${conn?.active ?? "?"}/${conn?.max ?? "?"} Cache Hit: ${cache?.ratio ?? "?"}%`
3641
+ );
3642
+ console.log(
3643
+ ` Dead tuples: ${deadTuples.toLocaleString()} Locks waiting: ${lockCount}`
3644
+ );
3645
+ } else {
3646
+ console.log(` N/A \u2014 ${dbResult.reason?.message ?? "unavailable"}`);
3647
+ }
3648
+ console.log("\n" + sectionHeader("Recent Errors (last 100 logs/source)"));
3649
+ if (logsResult.status === "fulfilled") {
3650
+ const summaries = logsResult.value;
3651
+ const parts = summaries.map((s) => `${s.source}: ${s.errors.length}`);
3652
+ console.log(` ${parts.join(" ")}`);
3653
+ } else {
3654
+ console.log(` N/A \u2014 ${logsResult.reason?.message ?? "unavailable"}`);
3655
+ }
3656
+ console.log("");
3657
+ }
3658
+ await reportCliUsage("cli.diagnose", true);
3659
+ } catch (err) {
3660
+ await reportCliUsage("cli.diagnose", false);
3661
+ handleError(err, json);
3662
+ }
3663
+ });
3664
+ registerDiagnoseMetricsCommand(diagnoseCmd2);
3665
+ registerDiagnoseAdvisorCommand(diagnoseCmd2);
3666
+ registerDiagnoseDbCommand(diagnoseCmd2);
3667
+ registerDiagnoseLogsCommand(diagnoseCmd2);
3668
+ }
3669
+ function formatBytesCompact(bytes) {
3670
+ if (bytes < 1024) return `${bytes.toFixed(0)}B`;
3671
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
3672
+ return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
3673
+ }
3674
+
3087
3675
  // src/index.ts
3088
3676
  var __dirname = dirname(fileURLToPath(import.meta.url));
3089
3677
  var pkg = JSON.parse(readFileSync6(join7(__dirname, "../package.json"), "utf-8"));
@@ -3151,6 +3739,8 @@ registerSecretsUpdateCommand(secretsCmd);
3151
3739
  registerSecretsDeleteCommand(secretsCmd);
3152
3740
  registerLogsCommand(program);
3153
3741
  registerMetadataCommand(program);
3742
+ var diagnoseCmd = program.command("diagnose");
3743
+ registerDiagnoseCommands(diagnoseCmd);
3154
3744
  var schedulesCmd = program.command("schedules").description("Manage scheduled tasks (cron jobs)");
3155
3745
  registerSchedulesListCommand(schedulesCmd);
3156
3746
  registerSchedulesGetCommand(schedulesCmd);