postgresai 0.14.0-dev.75 → 0.14.0-dev.77

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.
Files changed (34) hide show
  1. package/bin/postgres-ai.ts +312 -6
  2. package/dist/bin/postgres-ai.js +916 -40
  3. package/dist/sql/02.extensions.sql +8 -0
  4. package/dist/sql/{02.permissions.sql → 03.permissions.sql} +1 -0
  5. package/dist/sql/sql/02.extensions.sql +8 -0
  6. package/dist/sql/sql/{02.permissions.sql → 03.permissions.sql} +1 -0
  7. package/dist/sql/sql/uninit/01.helpers.sql +5 -0
  8. package/dist/sql/sql/uninit/02.permissions.sql +30 -0
  9. package/dist/sql/sql/uninit/03.role.sql +27 -0
  10. package/dist/sql/uninit/01.helpers.sql +5 -0
  11. package/dist/sql/uninit/02.permissions.sql +30 -0
  12. package/dist/sql/uninit/03.role.sql +27 -0
  13. package/lib/checkup-dictionary.ts +113 -0
  14. package/lib/checkup.ts +21 -14
  15. package/lib/init.ts +109 -8
  16. package/package.json +9 -7
  17. package/scripts/embed-checkup-dictionary.ts +106 -0
  18. package/sql/02.extensions.sql +8 -0
  19. package/sql/{02.permissions.sql → 03.permissions.sql} +1 -0
  20. package/sql/uninit/01.helpers.sql +5 -0
  21. package/sql/uninit/02.permissions.sql +30 -0
  22. package/sql/uninit/03.role.sql +27 -0
  23. package/test/checkup.test.ts +17 -18
  24. package/test/init.test.ts +245 -11
  25. package/lib/metrics-embedded.ts +0 -79
  26. /package/dist/sql/{03.optional_rds.sql → 04.optional_rds.sql} +0 -0
  27. /package/dist/sql/{04.optional_self_managed.sql → 05.optional_self_managed.sql} +0 -0
  28. /package/dist/sql/{05.helpers.sql → 06.helpers.sql} +0 -0
  29. /package/dist/sql/sql/{03.optional_rds.sql → 04.optional_rds.sql} +0 -0
  30. /package/dist/sql/sql/{04.optional_self_managed.sql → 05.optional_self_managed.sql} +0 -0
  31. /package/dist/sql/sql/{05.helpers.sql → 06.helpers.sql} +0 -0
  32. /package/sql/{03.optional_rds.sql → 04.optional_rds.sql} +0 -0
  33. /package/sql/{04.optional_self_managed.sql → 05.optional_self_managed.sql} +0 -0
  34. /package/sql/{05.helpers.sql → 06.helpers.sql} +0 -0
@@ -13064,7 +13064,7 @@ var {
13064
13064
  // package.json
13065
13065
  var package_default = {
13066
13066
  name: "postgresai",
13067
- version: "0.14.0-dev.75",
13067
+ version: "0.14.0-dev.77",
13068
13068
  description: "postgres_ai CLI",
13069
13069
  license: "Apache-2.0",
13070
13070
  private: false,
@@ -13090,15 +13090,17 @@ var package_default = {
13090
13090
  },
13091
13091
  scripts: {
13092
13092
  "embed-metrics": "bun run scripts/embed-metrics.ts",
13093
- build: `bun run embed-metrics && bun build ./bin/postgres-ai.ts --outdir ./dist/bin --target node && node -e "const fs=require('fs');const f='./dist/bin/postgres-ai.js';fs.writeFileSync(f,fs.readFileSync(f,'utf8').replace('#!/usr/bin/env bun','#!/usr/bin/env node'))" && cp -r ./sql ./dist/sql`,
13093
+ "embed-checkup-dictionary": "bun run scripts/embed-checkup-dictionary.ts",
13094
+ "embed-all": "bun run embed-metrics && bun run embed-checkup-dictionary",
13095
+ build: `bun run embed-all && bun build ./bin/postgres-ai.ts --outdir ./dist/bin --target node && node -e "const fs=require('fs');const f='./dist/bin/postgres-ai.js';fs.writeFileSync(f,fs.readFileSync(f,'utf8').replace('#!/usr/bin/env bun','#!/usr/bin/env node'))" && cp -r ./sql ./dist/sql`,
13094
13096
  prepublishOnly: "npm run build",
13095
13097
  start: "bun ./bin/postgres-ai.ts --help",
13096
13098
  "start:node": "node ./dist/bin/postgres-ai.js --help",
13097
- dev: "bun run embed-metrics && bun --watch ./bin/postgres-ai.ts",
13098
- test: "bun run embed-metrics && bun test",
13099
- "test:fast": "bun run embed-metrics && bun test --coverage=false",
13100
- "test:coverage": "bun run embed-metrics && bun test --coverage && echo 'Coverage report: cli/coverage/lcov-report/index.html'",
13101
- typecheck: "bun run embed-metrics && bunx tsc --noEmit"
13099
+ dev: "bun run embed-all && bun --watch ./bin/postgres-ai.ts",
13100
+ test: "bun run embed-all && bun test",
13101
+ "test:fast": "bun run embed-all && bun test",
13102
+ "test:coverage": "bun run embed-all && bun test --coverage && echo 'Coverage report: cli/coverage/lcov-report/index.html'",
13103
+ typecheck: "bun run embed-all && bunx tsc --noEmit"
13102
13104
  },
13103
13105
  dependencies: {
13104
13106
  "@modelcontextprotocol/sdk": "^1.20.2",
@@ -15887,7 +15889,7 @@ var Result = import_lib.default.Result;
15887
15889
  var TypeOverrides = import_lib.default.TypeOverrides;
15888
15890
  var defaults = import_lib.default.defaults;
15889
15891
  // package.json
15890
- var version = "0.14.0-dev.75";
15892
+ var version = "0.14.0-dev.77";
15891
15893
  var package_default2 = {
15892
15894
  name: "postgresai",
15893
15895
  version,
@@ -15916,15 +15918,17 @@ var package_default2 = {
15916
15918
  },
15917
15919
  scripts: {
15918
15920
  "embed-metrics": "bun run scripts/embed-metrics.ts",
15919
- build: `bun run embed-metrics && bun build ./bin/postgres-ai.ts --outdir ./dist/bin --target node && node -e "const fs=require('fs');const f='./dist/bin/postgres-ai.js';fs.writeFileSync(f,fs.readFileSync(f,'utf8').replace('#!/usr/bin/env bun','#!/usr/bin/env node'))" && cp -r ./sql ./dist/sql`,
15921
+ "embed-checkup-dictionary": "bun run scripts/embed-checkup-dictionary.ts",
15922
+ "embed-all": "bun run embed-metrics && bun run embed-checkup-dictionary",
15923
+ build: `bun run embed-all && bun build ./bin/postgres-ai.ts --outdir ./dist/bin --target node && node -e "const fs=require('fs');const f='./dist/bin/postgres-ai.js';fs.writeFileSync(f,fs.readFileSync(f,'utf8').replace('#!/usr/bin/env bun','#!/usr/bin/env node'))" && cp -r ./sql ./dist/sql`,
15920
15924
  prepublishOnly: "npm run build",
15921
15925
  start: "bun ./bin/postgres-ai.ts --help",
15922
15926
  "start:node": "node ./dist/bin/postgres-ai.js --help",
15923
- dev: "bun run embed-metrics && bun --watch ./bin/postgres-ai.ts",
15924
- test: "bun run embed-metrics && bun test",
15925
- "test:fast": "bun run embed-metrics && bun test --coverage=false",
15926
- "test:coverage": "bun run embed-metrics && bun test --coverage && echo 'Coverage report: cli/coverage/lcov-report/index.html'",
15927
- typecheck: "bun run embed-metrics && bunx tsc --noEmit"
15927
+ dev: "bun run embed-all && bun --watch ./bin/postgres-ai.ts",
15928
+ test: "bun run embed-all && bun test",
15929
+ "test:fast": "bun run embed-all && bun test",
15930
+ "test:coverage": "bun run embed-all && bun test --coverage && echo 'Coverage report: cli/coverage/lcov-report/index.html'",
15931
+ typecheck: "bun run embed-all && bunx tsc --noEmit"
15928
15932
  },
15929
15933
  dependencies: {
15930
15934
  "@modelcontextprotocol/sdk": "^1.20.2",
@@ -24961,7 +24965,11 @@ end $$;`;
24961
24965
  const roleSql = applyTemplate(loadSqlTemplate("01.role.sql"), { ...vars, ROLE_STMT: roleStmt });
24962
24966
  steps.push({ name: "01.role", sql: roleSql });
24963
24967
  }
24964
- let permissionsSql = applyTemplate(loadSqlTemplate("02.permissions.sql"), vars);
24968
+ steps.push({
24969
+ name: "02.extensions",
24970
+ sql: loadSqlTemplate("02.extensions.sql")
24971
+ });
24972
+ let permissionsSql = applyTemplate(loadSqlTemplate("03.permissions.sql"), vars);
24965
24973
  if (SKIP_ALTER_USER_PROVIDERS.includes(provider)) {
24966
24974
  permissionsSql = permissionsSql.split(`
24967
24975
  `).filter((line) => {
@@ -24973,21 +24981,21 @@ end $$;`;
24973
24981
  `);
24974
24982
  }
24975
24983
  steps.push({
24976
- name: "02.permissions",
24984
+ name: "03.permissions",
24977
24985
  sql: permissionsSql
24978
24986
  });
24979
24987
  steps.push({
24980
- name: "05.helpers",
24981
- sql: applyTemplate(loadSqlTemplate("05.helpers.sql"), vars)
24988
+ name: "06.helpers",
24989
+ sql: applyTemplate(loadSqlTemplate("06.helpers.sql"), vars)
24982
24990
  });
24983
24991
  if (params.includeOptionalPermissions) {
24984
24992
  steps.push({
24985
- name: "03.optional_rds",
24986
- sql: applyTemplate(loadSqlTemplate("03.optional_rds.sql"), vars),
24993
+ name: "04.optional_rds",
24994
+ sql: applyTemplate(loadSqlTemplate("04.optional_rds.sql"), vars),
24987
24995
  optional: true
24988
24996
  }, {
24989
- name: "04.optional_self_managed",
24990
- sql: applyTemplate(loadSqlTemplate("04.optional_self_managed.sql"), vars),
24997
+ name: "05.optional_self_managed",
24998
+ sql: applyTemplate(loadSqlTemplate("05.optional_self_managed.sql"), vars),
24991
24999
  optional: true
24992
25000
  });
24993
25001
  }
@@ -25055,6 +25063,62 @@ async function applyInitPlan(params) {
25055
25063
  }
25056
25064
  return { applied, skippedOptional };
25057
25065
  }
25066
+ async function buildUninitPlan(params) {
25067
+ const monitoringUser = params.monitoringUser || DEFAULT_MONITORING_USER;
25068
+ const database = params.database;
25069
+ const provider = params.provider ?? "self-managed";
25070
+ const dropRole = params.dropRole ?? true;
25071
+ const qRole = quoteIdent(monitoringUser);
25072
+ const qDb = quoteIdent(database);
25073
+ const qRoleLiteral = quoteLiteral(monitoringUser);
25074
+ const steps = [];
25075
+ const vars = {
25076
+ ROLE_IDENT: qRole,
25077
+ DB_IDENT: qDb,
25078
+ ROLE_LITERAL: qRoleLiteral
25079
+ };
25080
+ steps.push({
25081
+ name: "01.drop_helpers",
25082
+ sql: applyTemplate(loadSqlTemplate("uninit/01.helpers.sql"), vars)
25083
+ });
25084
+ steps.push({
25085
+ name: "02.revoke_permissions",
25086
+ sql: applyTemplate(loadSqlTemplate("uninit/02.permissions.sql"), vars)
25087
+ });
25088
+ if (dropRole && !SKIP_ROLE_CREATION_PROVIDERS.includes(provider)) {
25089
+ steps.push({
25090
+ name: "03.drop_role",
25091
+ sql: applyTemplate(loadSqlTemplate("uninit/03.role.sql"), vars)
25092
+ });
25093
+ }
25094
+ return { monitoringUser, database, steps, dropRole };
25095
+ }
25096
+ async function applyUninitPlan(params) {
25097
+ const applied = [];
25098
+ const errors3 = [];
25099
+ const executeStep = async (step) => {
25100
+ await params.client.query("begin;");
25101
+ try {
25102
+ await params.client.query(step.sql, step.params);
25103
+ await params.client.query("commit;");
25104
+ } catch (e) {
25105
+ try {
25106
+ await params.client.query("rollback;");
25107
+ } catch {}
25108
+ throw e;
25109
+ }
25110
+ };
25111
+ for (const step of params.plan.steps) {
25112
+ try {
25113
+ await executeStep(step);
25114
+ applied.push(step.name);
25115
+ } catch (e) {
25116
+ const msg = e instanceof Error ? e.message : String(e);
25117
+ errors3.push(`${step.name}: ${msg}`);
25118
+ }
25119
+ }
25120
+ return { applied, errors: errors3 };
25121
+ }
25058
25122
  async function verifyInitSetup(params) {
25059
25123
  await params.client.query("begin isolation level repeatable read;");
25060
25124
  try {
@@ -26482,6 +26546,572 @@ function transformMetricRow(row) {
26482
26546
  return result;
26483
26547
  }
26484
26548
 
26549
+ // lib/checkup-dictionary-embedded.ts
26550
+ var CHECKUP_DICTIONARY_DATA = [
26551
+ {
26552
+ code: "A001",
26553
+ title: "System information",
26554
+ description: "OS, kernel, hardware details",
26555
+ category: "system",
26556
+ sort_order: null,
26557
+ is_system_report: false
26558
+ },
26559
+ {
26560
+ code: "A002",
26561
+ title: "Postgres major version",
26562
+ description: "Major version and end-of-life status",
26563
+ category: "system",
26564
+ sort_order: null,
26565
+ is_system_report: false
26566
+ },
26567
+ {
26568
+ code: "A003",
26569
+ title: "Postgres settings",
26570
+ description: "Full list of current settings",
26571
+ category: "system",
26572
+ sort_order: null,
26573
+ is_system_report: false
26574
+ },
26575
+ {
26576
+ code: "A004",
26577
+ title: "Cluster information",
26578
+ description: "Data directory, cluster name, system identifier",
26579
+ category: "system",
26580
+ sort_order: null,
26581
+ is_system_report: false
26582
+ },
26583
+ {
26584
+ code: "A005",
26585
+ title: "Extensions",
26586
+ description: "Installed and available extensions",
26587
+ category: "system",
26588
+ sort_order: null,
26589
+ is_system_report: false
26590
+ },
26591
+ {
26592
+ code: "A006",
26593
+ title: "Postgres setting deviations",
26594
+ description: "Settings that differ from defaults",
26595
+ category: "system",
26596
+ sort_order: null,
26597
+ is_system_report: false
26598
+ },
26599
+ {
26600
+ code: "A007",
26601
+ title: "Altered settings",
26602
+ description: "Settings changed via ALTER SYSTEM or per-user/database",
26603
+ category: "system",
26604
+ sort_order: null,
26605
+ is_system_report: false
26606
+ },
26607
+ {
26608
+ code: "A008",
26609
+ title: "Disk usage and file system type",
26610
+ description: "Disk space, mount points, filesystem types",
26611
+ category: "system",
26612
+ sort_order: null,
26613
+ is_system_report: false
26614
+ },
26615
+ {
26616
+ code: "A009",
26617
+ title: "Tablespaces",
26618
+ description: "Tablespace locations and usage",
26619
+ category: "system",
26620
+ sort_order: null,
26621
+ is_system_report: false
26622
+ },
26623
+ {
26624
+ code: "A010",
26625
+ title: "Corruption control",
26626
+ description: "Checksums, wal_log_hints, backup safety",
26627
+ category: "system",
26628
+ sort_order: null,
26629
+ is_system_report: false
26630
+ },
26631
+ {
26632
+ code: "A011",
26633
+ title: "Connection management and pooling",
26634
+ description: "Connection limits and pooler detection",
26635
+ category: "system",
26636
+ sort_order: null,
26637
+ is_system_report: false
26638
+ },
26639
+ {
26640
+ code: "A012",
26641
+ title: "Anti-crash checks",
26642
+ description: "fsync, full_page_writes, synchronous_commit",
26643
+ category: "system",
26644
+ sort_order: null,
26645
+ is_system_report: false
26646
+ },
26647
+ {
26648
+ code: "A013",
26649
+ title: "Postgres minor version",
26650
+ description: "Minor version and available updates",
26651
+ category: "system",
26652
+ sort_order: null,
26653
+ is_system_report: false
26654
+ },
26655
+ {
26656
+ code: "B001",
26657
+ title: "SLO/SLA, RPO, RTO",
26658
+ description: "Recovery objectives and current capabilities",
26659
+ category: "backup",
26660
+ sort_order: null,
26661
+ is_system_report: false
26662
+ },
26663
+ {
26664
+ code: "B002",
26665
+ title: "File system and mount flags",
26666
+ description: "Mount options affecting durability",
26667
+ category: "backup",
26668
+ sort_order: null,
26669
+ is_system_report: false
26670
+ },
26671
+ {
26672
+ code: "B003",
26673
+ title: "Full and incremental backups",
26674
+ description: "Backup tooling and schedule",
26675
+ category: "backup",
26676
+ sort_order: null,
26677
+ is_system_report: false
26678
+ },
26679
+ {
26680
+ code: "B004",
26681
+ title: "WAL archiving",
26682
+ description: "Archive configuration and status",
26683
+ category: "backup",
26684
+ sort_order: null,
26685
+ is_system_report: false
26686
+ },
26687
+ {
26688
+ code: "B005",
26689
+ title: "Backup restore testing and monitoring",
26690
+ description: "Restore testing practices and alerting",
26691
+ category: "backup",
26692
+ sort_order: null,
26693
+ is_system_report: false
26694
+ },
26695
+ {
26696
+ code: "C001",
26697
+ title: "SLO/SLA",
26698
+ description: "Availability objectives",
26699
+ category: "replication",
26700
+ sort_order: null,
26701
+ is_system_report: false
26702
+ },
26703
+ {
26704
+ code: "C002",
26705
+ title: "Replication mode and method",
26706
+ description: "Sync/async, streaming/WAL shipping",
26707
+ category: "replication",
26708
+ sort_order: null,
26709
+ is_system_report: false
26710
+ },
26711
+ {
26712
+ code: "C003",
26713
+ title: "Single points of failure",
26714
+ description: "SPOF analysis",
26715
+ category: "replication",
26716
+ sort_order: null,
26717
+ is_system_report: false
26718
+ },
26719
+ {
26720
+ code: "C004",
26721
+ title: "Failover",
26722
+ description: "Failover mechanism and automation",
26723
+ category: "replication",
26724
+ sort_order: null,
26725
+ is_system_report: false
26726
+ },
26727
+ {
26728
+ code: "C005",
26729
+ title: "Switchover",
26730
+ description: "Planned switchover procedures",
26731
+ category: "replication",
26732
+ sort_order: null,
26733
+ is_system_report: false
26734
+ },
26735
+ {
26736
+ code: "C006",
26737
+ title: "Delayed replica",
26738
+ description: "Delayed standby configuration",
26739
+ category: "replication",
26740
+ sort_order: null,
26741
+ is_system_report: false
26742
+ },
26743
+ {
26744
+ code: "C007",
26745
+ title: "Replication slots and lag",
26746
+ description: "Slot status, lag, standby feedback",
26747
+ category: "replication",
26748
+ sort_order: null,
26749
+ is_system_report: false
26750
+ },
26751
+ {
26752
+ code: "D001",
26753
+ title: "Logging settings",
26754
+ description: "Log destination, verbosity, rotation",
26755
+ category: "monitoring",
26756
+ sort_order: null,
26757
+ is_system_report: false
26758
+ },
26759
+ {
26760
+ code: "D002",
26761
+ title: "Useful Linux tools",
26762
+ description: "Recommended OS-level tooling",
26763
+ category: "monitoring",
26764
+ sort_order: null,
26765
+ is_system_report: false
26766
+ },
26767
+ {
26768
+ code: "D003",
26769
+ title: "Monitoring metrics",
26770
+ description: "Key metrics to track",
26771
+ category: "monitoring",
26772
+ sort_order: null,
26773
+ is_system_report: false
26774
+ },
26775
+ {
26776
+ code: "D004",
26777
+ title: "pg_stat_statements and pg_stat_kcache",
26778
+ description: "Query stats extension settings",
26779
+ category: "monitoring",
26780
+ sort_order: null,
26781
+ is_system_report: false
26782
+ },
26783
+ {
26784
+ code: "D005",
26785
+ title: "track_io_timing and auto_explain",
26786
+ description: "I/O timing and plan logging",
26787
+ category: "monitoring",
26788
+ sort_order: null,
26789
+ is_system_report: false
26790
+ },
26791
+ {
26792
+ code: "D006",
26793
+ title: "Recommended DBA toolsets",
26794
+ description: "Useful third-party tools",
26795
+ category: "monitoring",
26796
+ sort_order: null,
26797
+ is_system_report: false
26798
+ },
26799
+ {
26800
+ code: "D007",
26801
+ title: "Postgres troubleshooting tools",
26802
+ description: "Built-in and ecosystem diagnostic tools",
26803
+ category: "monitoring",
26804
+ sort_order: null,
26805
+ is_system_report: false
26806
+ },
26807
+ {
26808
+ code: "E001",
26809
+ title: "WAL and checkpoint settings",
26810
+ description: "WAL size, checkpoint timing, I/O limits",
26811
+ category: "wal",
26812
+ sort_order: null,
26813
+ is_system_report: false
26814
+ },
26815
+ {
26816
+ code: "E002",
26817
+ title: "Checkpoint and bgwriter activity",
26818
+ description: "Checkpoint frequency, bgwriter stats",
26819
+ category: "wal",
26820
+ sort_order: null,
26821
+ is_system_report: false
26822
+ },
26823
+ {
26824
+ code: "F001",
26825
+ title: "Autovacuum: current settings",
26826
+ description: "Autovacuum configuration",
26827
+ category: "vacuum",
26828
+ sort_order: null,
26829
+ is_system_report: false
26830
+ },
26831
+ {
26832
+ code: "F002",
26833
+ title: "Autovacuum: transaction ID wraparound",
26834
+ description: "XID age and wraparound risk",
26835
+ category: "vacuum",
26836
+ sort_order: null,
26837
+ is_system_report: false
26838
+ },
26839
+ {
26840
+ code: "F003",
26841
+ title: "Autovacuum: dead tuples",
26842
+ description: "Dead tuple counts and cleanup status",
26843
+ category: "vacuum",
26844
+ sort_order: null,
26845
+ is_system_report: false
26846
+ },
26847
+ {
26848
+ code: "F004",
26849
+ title: "Autovacuum: heap bloat estimate",
26850
+ description: "Estimated table bloat",
26851
+ category: "vacuum",
26852
+ sort_order: null,
26853
+ is_system_report: false
26854
+ },
26855
+ {
26856
+ code: "F005",
26857
+ title: "Autovacuum: index bloat estimate",
26858
+ description: "Estimated index bloat",
26859
+ category: "vacuum",
26860
+ sort_order: null,
26861
+ is_system_report: false
26862
+ },
26863
+ {
26864
+ code: "F006",
26865
+ title: "Precise heap bloat analysis",
26866
+ description: "Detailed table bloat measurement",
26867
+ category: "vacuum",
26868
+ sort_order: null,
26869
+ is_system_report: false
26870
+ },
26871
+ {
26872
+ code: "F007",
26873
+ title: "Precise index bloat analysis",
26874
+ description: "Detailed index bloat measurement",
26875
+ category: "vacuum",
26876
+ sort_order: null,
26877
+ is_system_report: false
26878
+ },
26879
+ {
26880
+ code: "F008",
26881
+ title: "Autovacuum: resource usage",
26882
+ description: "Autovacuum I/O and CPU impact",
26883
+ category: "vacuum",
26884
+ sort_order: null,
26885
+ is_system_report: false
26886
+ },
26887
+ {
26888
+ code: "G001",
26889
+ title: "Memory-related settings",
26890
+ description: "shared_buffers, work_mem, maintenance_work_mem",
26891
+ category: "tuning",
26892
+ sort_order: null,
26893
+ is_system_report: false
26894
+ },
26895
+ {
26896
+ code: "G002",
26897
+ title: "Connections and current activity",
26898
+ description: "Connection usage and active sessions",
26899
+ category: "tuning",
26900
+ sort_order: null,
26901
+ is_system_report: false
26902
+ },
26903
+ {
26904
+ code: "G003",
26905
+ title: "Timeouts, locks, deadlocks",
26906
+ description: "Lock settings and deadlock stats",
26907
+ category: "tuning",
26908
+ sort_order: null,
26909
+ is_system_report: false
26910
+ },
26911
+ {
26912
+ code: "G004",
26913
+ title: "Query planner settings",
26914
+ description: "Planner cost and behavior settings",
26915
+ category: "tuning",
26916
+ sort_order: null,
26917
+ is_system_report: false
26918
+ },
26919
+ {
26920
+ code: "G005",
26921
+ title: "I/O settings",
26922
+ description: "effective_io_concurrency, random_page_cost",
26923
+ category: "tuning",
26924
+ sort_order: null,
26925
+ is_system_report: false
26926
+ },
26927
+ {
26928
+ code: "G006",
26929
+ title: "Statistics target settings",
26930
+ description: "default_statistics_target and per-column targets",
26931
+ category: "tuning",
26932
+ sort_order: null,
26933
+ is_system_report: false
26934
+ },
26935
+ {
26936
+ code: "H001",
26937
+ title: "Invalid indexes",
26938
+ description: "Indexes that failed to build",
26939
+ category: "indexes",
26940
+ sort_order: null,
26941
+ is_system_report: false
26942
+ },
26943
+ {
26944
+ code: "H002",
26945
+ title: "Unused indexes",
26946
+ description: "Indexes with no scans",
26947
+ category: "indexes",
26948
+ sort_order: null,
26949
+ is_system_report: false
26950
+ },
26951
+ {
26952
+ code: "H003",
26953
+ title: "Non-indexed foreign keys",
26954
+ description: "FKs missing supporting indexes",
26955
+ category: "indexes",
26956
+ sort_order: null,
26957
+ is_system_report: false
26958
+ },
26959
+ {
26960
+ code: "H004",
26961
+ title: "Redundant indexes",
26962
+ description: "Indexes covered by other indexes",
26963
+ category: "indexes",
26964
+ sort_order: null,
26965
+ is_system_report: false
26966
+ },
26967
+ {
26968
+ code: "J001",
26969
+ title: "Capacity planning",
26970
+ description: "Growth trends and resource projections",
26971
+ category: "capacity",
26972
+ sort_order: null,
26973
+ is_system_report: false
26974
+ },
26975
+ {
26976
+ code: "K001",
26977
+ title: "Globally aggregated query metrics",
26978
+ description: "Overall query stats summary",
26979
+ category: "queries",
26980
+ sort_order: null,
26981
+ is_system_report: false
26982
+ },
26983
+ {
26984
+ code: "K002",
26985
+ title: "Workload type",
26986
+ description: "Read/write/mixed workload classification",
26987
+ category: "queries",
26988
+ sort_order: null,
26989
+ is_system_report: false
26990
+ },
26991
+ {
26992
+ code: "K003",
26993
+ title: "Top queries by total time",
26994
+ description: "Highest total execution + planning time",
26995
+ category: "queries",
26996
+ sort_order: null,
26997
+ is_system_report: false
26998
+ },
26999
+ {
27000
+ code: "K004",
27001
+ title: "Top queries by temp bytes written",
27002
+ description: "Queries spilling to disk",
27003
+ category: "queries",
27004
+ sort_order: null,
27005
+ is_system_report: false
27006
+ },
27007
+ {
27008
+ code: "K005",
27009
+ title: "Top queries by WAL generation",
27010
+ description: "Queries generating most WAL",
27011
+ category: "queries",
27012
+ sort_order: null,
27013
+ is_system_report: false
27014
+ },
27015
+ {
27016
+ code: "K006",
27017
+ title: "Top queries by shared blocks read",
27018
+ description: "Queries with most disk reads",
27019
+ category: "queries",
27020
+ sort_order: null,
27021
+ is_system_report: false
27022
+ },
27023
+ {
27024
+ code: "K007",
27025
+ title: "Top queries by shared blocks hit",
27026
+ description: "Queries with most buffer hits",
27027
+ category: "queries",
27028
+ sort_order: null,
27029
+ is_system_report: false
27030
+ },
27031
+ {
27032
+ code: "K008",
27033
+ title: "Top queries by shared blocks accessed",
27034
+ description: "Queries touching most data",
27035
+ category: "queries",
27036
+ sort_order: null,
27037
+ is_system_report: false
27038
+ },
27039
+ {
27040
+ code: "L001",
27041
+ title: "Table sizes",
27042
+ description: "Table and toast sizes",
27043
+ category: "schema",
27044
+ sort_order: null,
27045
+ is_system_report: false
27046
+ },
27047
+ {
27048
+ code: "L002",
27049
+ title: "Data types being used",
27050
+ description: "Column type distribution",
27051
+ category: "schema",
27052
+ sort_order: null,
27053
+ is_system_report: false
27054
+ },
27055
+ {
27056
+ code: "L003",
27057
+ title: "Integer overflow risks in primary keys",
27058
+ description: "PKs approaching integer limits",
27059
+ category: "schema",
27060
+ sort_order: null,
27061
+ is_system_report: false
27062
+ },
27063
+ {
27064
+ code: "L004",
27065
+ title: "Tables without primary key",
27066
+ description: "Tables missing PK or unique constraint",
27067
+ category: "schema",
27068
+ sort_order: null,
27069
+ is_system_report: false
27070
+ },
27071
+ {
27072
+ code: "M001",
27073
+ title: "Top queries by mean execution time",
27074
+ description: "Slowest queries on average",
27075
+ category: "queries",
27076
+ sort_order: null,
27077
+ is_system_report: false
27078
+ },
27079
+ {
27080
+ code: "M002",
27081
+ title: "Top queries by rows processed",
27082
+ description: "Queries touching most rows",
27083
+ category: "queries",
27084
+ sort_order: null,
27085
+ is_system_report: false
27086
+ },
27087
+ {
27088
+ code: "M003",
27089
+ title: "Top queries by I/O time",
27090
+ description: "Queries with highest I/O wait",
27091
+ category: "queries",
27092
+ sort_order: null,
27093
+ is_system_report: false
27094
+ },
27095
+ {
27096
+ code: "N001",
27097
+ title: "Wait events by type and query",
27098
+ description: "Wait event analysis",
27099
+ category: "waits",
27100
+ sort_order: null,
27101
+ is_system_report: false
27102
+ }
27103
+ ];
27104
+
27105
+ // lib/checkup-dictionary.ts
27106
+ var dictionaryByCode = new Map(CHECKUP_DICTIONARY_DATA.map((entry) => [entry.code, entry]));
27107
+ function buildCheckInfoMap() {
27108
+ const result = {};
27109
+ for (const entry of CHECKUP_DICTIONARY_DATA) {
27110
+ result[entry.code] = entry.title;
27111
+ }
27112
+ return result;
27113
+ }
27114
+
26485
27115
  // lib/checkup.ts
26486
27116
  var __dirname = "/builds/postgres-ai/postgres_ai/cli/lib";
26487
27117
  var SECONDS_PER_DAY = 86400;
@@ -27238,19 +27868,15 @@ var REPORT_GENERATORS = {
27238
27868
  H002: generateH002,
27239
27869
  H004: generateH004
27240
27870
  };
27241
- var CHECK_INFO = {
27242
- A002: "Postgres major version",
27243
- A003: "Postgres settings",
27244
- A004: "Cluster information",
27245
- A007: "Altered settings",
27246
- A013: "Postgres minor version",
27247
- D004: "pg_stat_statements and pg_stat_kcache settings",
27248
- F001: "Autovacuum: current settings",
27249
- G001: "Memory-related settings",
27250
- H001: "Invalid indexes",
27251
- H002: "Unused indexes",
27252
- H004: "Redundant indexes"
27253
- };
27871
+ var CHECK_INFO = (() => {
27872
+ const fullMap = buildCheckInfoMap();
27873
+ const expressCheckIds = Object.keys(REPORT_GENERATORS);
27874
+ const filtered = {};
27875
+ for (const checkId of expressCheckIds) {
27876
+ filtered[checkId] = fullMap[checkId] || checkId;
27877
+ }
27878
+ return filtered;
27879
+ })();
27254
27880
  async function generateAllReports(client, nodeName = "node-01", onProgress) {
27255
27881
  const reports = {};
27256
27882
  const entries = Object.entries(REPORT_GENERATORS);
@@ -28229,12 +28855,16 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
28229
28855
  if (errAny.code === "42501") {
28230
28856
  if (failedStep === "01.role") {
28231
28857
  console.error(" Context: role creation/update requires CREATEROLE or superuser");
28232
- } else if (failedStep === "02.permissions") {
28858
+ } else if (failedStep === "03.permissions") {
28233
28859
  console.error(" Context: grants/view/search_path require sufficient GRANT/DDL privileges");
28234
28860
  }
28235
28861
  console.error(" Fix: ensure your Supabase access token has sufficient permissions");
28236
28862
  console.error(" Tip: run with --print-sql to review the exact SQL plan");
28237
28863
  }
28864
+ if (errAny.code === "42P06" || message.includes("already exists") && failedStep === "03.permissions") {
28865
+ console.error(" Hint: postgres_ai schema or objects already exist from a previous setup.");
28866
+ console.error(" Fix: run 'postgresai unprepare-db <connection>' first to clean up, then retry prepare-db.");
28867
+ }
28238
28868
  }
28239
28869
  if (errAny && typeof errAny === "object" && typeof errAny.httpStatus === "number") {
28240
28870
  if (errAny.httpStatus === 401) {
@@ -28498,13 +29128,258 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
28498
29128
  if (errAny.code === "42501") {
28499
29129
  if (failedStep === "01.role") {
28500
29130
  console.error(" Context: role creation/update requires CREATEROLE or superuser");
28501
- } else if (failedStep === "02.permissions") {
29131
+ } else if (failedStep === "03.permissions") {
28502
29132
  console.error(" Context: grants/view/search_path require sufficient GRANT/DDL privileges");
28503
29133
  }
28504
29134
  console.error(" Fix: connect as a superuser (or a role with CREATEROLE and sufficient GRANT privileges)");
28505
29135
  console.error(" Fix: on managed Postgres, use the provider's admin/master user");
28506
29136
  console.error(" Tip: run with --print-sql to review the exact SQL plan");
28507
29137
  }
29138
+ if (errAny.code === "42P06" || message.includes("already exists") && failedStep === "03.permissions") {
29139
+ console.error(" Hint: postgres_ai schema or objects already exist from a previous setup.");
29140
+ console.error(" Fix: run 'postgresai unprepare-db <connection>' first to clean up, then retry prepare-db.");
29141
+ }
29142
+ if (errAny.code === "ECONNREFUSED") {
29143
+ console.error(" Hint: check host/port and ensure Postgres is reachable from this machine");
29144
+ }
29145
+ if (errAny.code === "ENOTFOUND") {
29146
+ console.error(" Hint: DNS resolution failed; double-check the host name");
29147
+ }
29148
+ if (errAny.code === "ETIMEDOUT") {
29149
+ console.error(" Hint: connection timed out; check network/firewall rules");
29150
+ }
29151
+ }
29152
+ process.exitCode = 1;
29153
+ }
29154
+ } finally {
29155
+ if (client) {
29156
+ try {
29157
+ await client.end();
29158
+ } catch {}
29159
+ }
29160
+ }
29161
+ });
29162
+ program2.command("unprepare-db [conn]").description("remove monitoring setup: drop monitoring user, views, schema, and revoke permissions").option("--db-url <url>", "PostgreSQL connection URL (admin) (deprecated; pass it as positional arg)").option("-h, --host <host>", "PostgreSQL host (psql-like)").option("-p, --port <port>", "PostgreSQL port (psql-like)").option("-U, --username <username>", "PostgreSQL user (psql-like)").option("-d, --dbname <dbname>", "PostgreSQL database name (psql-like)").option("--admin-password <password>", "Admin connection password (otherwise uses PGPASSWORD if set)").option("--monitoring-user <name>", "Monitoring role name to remove", DEFAULT_MONITORING_USER).option("--keep-role", "Keep the monitoring role (only revoke permissions and drop objects)", false).option("--provider <provider>", "Database provider (e.g., supabase). Affects which steps are executed.").option("--print-sql", "Print SQL plan and exit (no changes applied)", false).option("--force", "Skip confirmation prompt", false).option("--json", "Output result as JSON (machine-readable)", false).addHelpText("after", [
29163
+ "",
29164
+ "Examples:",
29165
+ " postgresai unprepare-db postgresql://admin@host:5432/dbname",
29166
+ ' postgresai unprepare-db "dbname=dbname host=host user=admin"',
29167
+ " postgresai unprepare-db -h host -p 5432 -U admin -d dbname",
29168
+ "",
29169
+ "Admin password:",
29170
+ " --admin-password <password> or PGPASSWORD=... (libpq standard)",
29171
+ "",
29172
+ "Keep role but remove objects/permissions:",
29173
+ " postgresai unprepare-db <conn> --keep-role",
29174
+ "",
29175
+ "Inspect SQL without applying changes:",
29176
+ " postgresai unprepare-db <conn> --print-sql",
29177
+ "",
29178
+ "Offline SQL plan (no DB connection):",
29179
+ " postgresai unprepare-db --print-sql",
29180
+ "",
29181
+ "Skip confirmation prompt:",
29182
+ " postgresai unprepare-db <conn> --force"
29183
+ ].join(`
29184
+ `)).action(async (conn, opts, cmd) => {
29185
+ const jsonOutput = opts.json;
29186
+ const outputJson = (data) => {
29187
+ console.log(JSON.stringify(data, null, 2));
29188
+ };
29189
+ const outputError = (error2) => {
29190
+ if (jsonOutput) {
29191
+ outputJson({
29192
+ success: false,
29193
+ error: error2
29194
+ });
29195
+ } else {
29196
+ console.error(`Error: unprepare-db: ${error2.message}`);
29197
+ if (error2.step)
29198
+ console.error(` Step: ${error2.step}`);
29199
+ if (error2.code)
29200
+ console.error(` Code: ${error2.code}`);
29201
+ if (error2.detail)
29202
+ console.error(` Detail: ${error2.detail}`);
29203
+ if (error2.hint)
29204
+ console.error(` Hint: ${error2.hint}`);
29205
+ }
29206
+ process.exitCode = 1;
29207
+ };
29208
+ const shouldPrintSql = !!opts.printSql;
29209
+ const dropRole = !opts.keepRole;
29210
+ const providerWarning = validateProvider(opts.provider);
29211
+ if (providerWarning) {
29212
+ console.warn(`\u26A0 ${providerWarning}`);
29213
+ }
29214
+ if (!conn && !opts.dbUrl && !opts.host && !opts.port && !opts.username && !opts.adminPassword) {
29215
+ if (shouldPrintSql) {
29216
+ const database = (opts.dbname ?? process.env.PGDATABASE ?? "postgres").trim();
29217
+ const plan = await buildUninitPlan({
29218
+ database,
29219
+ monitoringUser: opts.monitoringUser,
29220
+ dropRole,
29221
+ provider: opts.provider
29222
+ });
29223
+ console.log(`
29224
+ --- SQL plan (offline; not connected) ---`);
29225
+ console.log(`-- database: ${database}`);
29226
+ console.log(`-- monitoring user: ${opts.monitoringUser}`);
29227
+ console.log(`-- provider: ${opts.provider ?? "self-managed"}`);
29228
+ console.log(`-- drop role: ${dropRole}`);
29229
+ for (const step of plan.steps) {
29230
+ console.log(`
29231
+ -- ${step.name}`);
29232
+ console.log(step.sql);
29233
+ }
29234
+ console.log(`
29235
+ --- end SQL plan ---
29236
+ `);
29237
+ return;
29238
+ }
29239
+ }
29240
+ let adminConn;
29241
+ try {
29242
+ adminConn = resolveAdminConnection({
29243
+ conn,
29244
+ dbUrlFlag: opts.dbUrl,
29245
+ host: opts.host ?? process.env.PGHOST,
29246
+ port: opts.port ?? process.env.PGPORT,
29247
+ username: opts.username ?? process.env.PGUSER,
29248
+ dbname: opts.dbname ?? process.env.PGDATABASE,
29249
+ adminPassword: opts.adminPassword,
29250
+ envPassword: process.env.PGPASSWORD
29251
+ });
29252
+ } catch (e) {
29253
+ const msg = e instanceof Error ? e.message : String(e);
29254
+ if (jsonOutput) {
29255
+ outputError({ message: msg });
29256
+ } else {
29257
+ console.error(`Error: unprepare-db: ${msg}`);
29258
+ if (typeof msg === "string" && msg.startsWith("Connection is required.")) {
29259
+ console.error("");
29260
+ cmd.outputHelp({ error: true });
29261
+ }
29262
+ process.exitCode = 1;
29263
+ }
29264
+ return;
29265
+ }
29266
+ if (!jsonOutput) {
29267
+ console.log(`Connecting to: ${adminConn.display}`);
29268
+ console.log(`Monitoring user: ${opts.monitoringUser}`);
29269
+ console.log(`Drop role: ${dropRole}`);
29270
+ }
29271
+ if (!opts.force && !jsonOutput && !shouldPrintSql) {
29272
+ const answer = await new Promise((resolve6) => {
29273
+ const readline = getReadline();
29274
+ readline.question(`This will remove the monitoring setup for user "${opts.monitoringUser}"${dropRole ? " and drop the role" : ""}. Continue? [y/N] `, (ans) => resolve6(ans.trim().toLowerCase()));
29275
+ });
29276
+ if (answer !== "y" && answer !== "yes") {
29277
+ console.log("Aborted.");
29278
+ return;
29279
+ }
29280
+ }
29281
+ let client;
29282
+ try {
29283
+ const connResult = await connectWithSslFallback(Client, adminConn);
29284
+ client = connResult.client;
29285
+ const dbRes = await client.query("select current_database() as db");
29286
+ const database = dbRes.rows?.[0]?.db;
29287
+ if (typeof database !== "string" || !database) {
29288
+ throw new Error("Failed to resolve current database name");
29289
+ }
29290
+ const plan = await buildUninitPlan({
29291
+ database,
29292
+ monitoringUser: opts.monitoringUser,
29293
+ dropRole,
29294
+ provider: opts.provider
29295
+ });
29296
+ if (shouldPrintSql) {
29297
+ console.log(`
29298
+ --- SQL plan ---`);
29299
+ for (const step of plan.steps) {
29300
+ console.log(`
29301
+ -- ${step.name}`);
29302
+ console.log(step.sql);
29303
+ }
29304
+ console.log(`
29305
+ --- end SQL plan ---
29306
+ `);
29307
+ return;
29308
+ }
29309
+ const { applied, errors: errors3 } = await applyUninitPlan({ client, plan });
29310
+ if (jsonOutput) {
29311
+ outputJson({
29312
+ success: errors3.length === 0,
29313
+ action: "unprepare",
29314
+ database,
29315
+ monitoringUser: opts.monitoringUser,
29316
+ dropRole,
29317
+ applied,
29318
+ errors: errors3
29319
+ });
29320
+ if (errors3.length > 0) {
29321
+ process.exitCode = 1;
29322
+ }
29323
+ } else {
29324
+ if (errors3.length === 0) {
29325
+ console.log("\u2713 unprepare-db completed");
29326
+ console.log(`Applied ${applied.length} steps`);
29327
+ } else {
29328
+ console.log("\u26A0 unprepare-db completed with errors");
29329
+ console.log(`Applied ${applied.length} steps`);
29330
+ console.log("Errors:");
29331
+ for (const err of errors3) {
29332
+ console.log(` - ${err}`);
29333
+ }
29334
+ process.exitCode = 1;
29335
+ }
29336
+ }
29337
+ } catch (error2) {
29338
+ const errAny = error2;
29339
+ let message = "";
29340
+ if (error2 instanceof Error && error2.message) {
29341
+ message = error2.message;
29342
+ } else if (errAny && typeof errAny === "object" && typeof errAny.message === "string" && errAny.message) {
29343
+ message = errAny.message;
29344
+ } else {
29345
+ message = String(error2);
29346
+ }
29347
+ if (!message || message === "[object Object]") {
29348
+ message = "Unknown error";
29349
+ }
29350
+ const errorObj = { message };
29351
+ if (errAny && typeof errAny === "object") {
29352
+ if (typeof errAny.code === "string" && errAny.code)
29353
+ errorObj.code = errAny.code;
29354
+ if (typeof errAny.detail === "string" && errAny.detail)
29355
+ errorObj.detail = errAny.detail;
29356
+ if (typeof errAny.hint === "string" && errAny.hint)
29357
+ errorObj.hint = errAny.hint;
29358
+ }
29359
+ if (jsonOutput) {
29360
+ outputJson({
29361
+ success: false,
29362
+ error: errorObj
29363
+ });
29364
+ process.exitCode = 1;
29365
+ } else {
29366
+ console.error(`Error: unprepare-db: ${message}`);
29367
+ if (errAny && typeof errAny === "object") {
29368
+ if (typeof errAny.code === "string" && errAny.code) {
29369
+ console.error(` Code: ${errAny.code}`);
29370
+ }
29371
+ if (typeof errAny.detail === "string" && errAny.detail) {
29372
+ console.error(` Detail: ${errAny.detail}`);
29373
+ }
29374
+ if (typeof errAny.hint === "string" && errAny.hint) {
29375
+ console.error(` Hint: ${errAny.hint}`);
29376
+ }
29377
+ }
29378
+ if (errAny && typeof errAny === "object" && typeof errAny.code === "string") {
29379
+ if (errAny.code === "42501") {
29380
+ console.error(" Context: dropping roles/objects requires sufficient privileges");
29381
+ console.error(" Fix: connect as a superuser (or a role with appropriate DROP privileges)");
29382
+ }
28508
29383
  if (errAny.code === "ECONNREFUSED") {
28509
29384
  console.error(" Hint: check host/port and ensure Postgres is reachable from this machine");
28510
29385
  }
@@ -28523,6 +29398,7 @@ program2.command("prepare-db [conn]").description("prepare database for monitori
28523
29398
  await client.end();
28524
29399
  } catch {}
28525
29400
  }
29401
+ closeReadline();
28526
29402
  }
28527
29403
  });
28528
29404
  program2.command("checkup [conn]").description("generate health check reports directly from PostgreSQL (express mode)").option("--check-id <id>", `specific check to run: ${Object.keys(CHECK_INFO).join(", ")}, or ALL`, "ALL").option("--node-name <name>", "node name for reports", "node-01").option("--output <path>", "output directory for JSON files").option("--[no-]upload", "upload JSON results to PostgresAI (default: enabled; requires API key)", undefined).option("--project <project>", "project name or ID for remote upload (used with --upload; defaults to config defaultProject; auto-generated on first run)").option("--json", "output JSON to stdout (implies --no-upload)").addHelpText("after", [
@@ -28651,14 +29527,14 @@ async function resolveOrInitPaths() {
28651
29527
  }
28652
29528
  function isDockerRunning() {
28653
29529
  try {
28654
- const result = spawnSync2("docker", ["info"], { stdio: "pipe" });
29530
+ const result = spawnSync2("docker", ["info"], { stdio: "pipe", timeout: 5000 });
28655
29531
  return result.status === 0;
28656
29532
  } catch {
28657
29533
  return false;
28658
29534
  }
28659
29535
  }
28660
29536
  function getComposeCmd() {
28661
- const tryCmd = (cmd, args) => spawnSync2(cmd, args, { stdio: "ignore" }).status === 0;
29537
+ const tryCmd = (cmd, args) => spawnSync2(cmd, args, { stdio: "ignore", timeout: 5000 }).status === 0;
28662
29538
  if (tryCmd("docker-compose", ["version"]))
28663
29539
  return ["docker-compose"];
28664
29540
  if (tryCmd("docker", ["compose", "version"]))
@@ -28667,7 +29543,7 @@ function getComposeCmd() {
28667
29543
  }
28668
29544
  function checkRunningContainers() {
28669
29545
  try {
28670
- const result = spawnSync2("docker", ["ps", "--filter", "name=grafana-with-datasources", "--filter", "name=pgwatch", "--format", "{{.Names}}"], { stdio: "pipe", encoding: "utf8" });
29546
+ const result = spawnSync2("docker", ["ps", "--filter", "name=grafana-with-datasources", "--filter", "name=pgwatch", "--format", "{{.Names}}"], { stdio: "pipe", encoding: "utf8", timeout: 5000 });
28671
29547
  if (result.status === 0 && result.stdout) {
28672
29548
  const containers = result.stdout.trim().split(`
28673
29549
  `).filter(Boolean);