pgserve 2.1.3 → 2.2.1

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 (235) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/README.md +105 -1
  3. package/bin/autopg-wrapper.cjs +16 -0
  4. package/bin/pgserve-wrapper.cjs +32 -6
  5. package/bin/postgres-server.js +56 -0
  6. package/console/README.md +131 -0
  7. package/console/api.js +173 -0
  8. package/console/app.jsx +483 -0
  9. package/console/colors_and_type.css +227 -0
  10. package/console/components.jsx +167 -0
  11. package/console/console.css +1666 -0
  12. package/console/data.jsx +350 -0
  13. package/console/index.html +31 -0
  14. package/console/screens/databases.jsx +5 -0
  15. package/console/screens/health.jsx +5 -0
  16. package/console/screens/ingress.jsx +5 -0
  17. package/console/screens/optimizer.jsx +5 -0
  18. package/console/screens/rlm-sim.jsx +5 -0
  19. package/console/screens/rlm-trace.jsx +5 -0
  20. package/console/screens/security.jsx +5 -0
  21. package/console/screens/settings.jsx +611 -0
  22. package/console/screens/sql.jsx +5 -0
  23. package/console/screens/sync.jsx +5 -0
  24. package/console/screens/tables.jsx +5 -0
  25. package/console/tweaks-panel.jsx +425 -0
  26. package/package.json +14 -2
  27. package/scripts/postinstall.cjs +60 -0
  28. package/src/cli-config.cjs +310 -0
  29. package/src/cli-install.cjs +112 -11
  30. package/src/cli-restart.cjs +228 -0
  31. package/src/cli-ui.cjs +580 -0
  32. package/src/cluster.js +43 -38
  33. package/src/postgres.js +141 -19
  34. package/src/settings-loader.cjs +235 -0
  35. package/src/settings-migrate.cjs +212 -0
  36. package/src/settings-pg-args.cjs +146 -0
  37. package/src/settings-schema.cjs +422 -0
  38. package/src/settings-validator.cjs +416 -0
  39. package/src/settings-writer.cjs +288 -0
  40. package/src/upgrade/index.js +65 -0
  41. package/src/upgrade/runner.js +23 -0
  42. package/src/upgrade/steps/binary-cache-flush.js +67 -0
  43. package/src/upgrade/steps/consumer-signal.js +40 -0
  44. package/src/upgrade/steps/env-refresh.js +89 -0
  45. package/src/upgrade/steps/health-validate.js +53 -0
  46. package/src/upgrade/steps/plpgsql-resolve.js +66 -0
  47. package/src/upgrade/steps/port-reconcile.js +52 -0
  48. package/.claude/context/windows-debug.md +0 -119
  49. package/.genie/AGENTS.md +0 -15
  50. package/.genie/agents/README.md +0 -110
  51. package/.genie/agents/analyze.md +0 -176
  52. package/.genie/agents/forge.md +0 -290
  53. package/.genie/agents/garbage-cleaner.md +0 -324
  54. package/.genie/agents/garbage-collector.md +0 -596
  55. package/.genie/agents/github-issue-gc.md +0 -618
  56. package/.genie/agents/review.md +0 -380
  57. package/.genie/agents/semantic-analyzer/find-duplicates.md +0 -90
  58. package/.genie/agents/semantic-analyzer/find-orphans.md +0 -99
  59. package/.genie/agents/semantic-analyzer.md +0 -101
  60. package/.genie/agents/update.md +0 -182
  61. package/.genie/agents/wish.md +0 -357
  62. package/.genie/brainstorms/pgserve-v2/DESIGN.md +0 -174
  63. package/.genie/code/AGENTS.md +0 -694
  64. package/.genie/code/agents/audit/risk.md +0 -173
  65. package/.genie/code/agents/audit/security.md +0 -189
  66. package/.genie/code/agents/audit.md +0 -145
  67. package/.genie/code/agents/challenge.md +0 -230
  68. package/.genie/code/agents/change-reviewer.md +0 -295
  69. package/.genie/code/agents/code-garbage-collector.md +0 -425
  70. package/.genie/code/agents/code-quality.md +0 -410
  71. package/.genie/code/agents/commit-suggester.md +0 -255
  72. package/.genie/code/agents/commit.md +0 -124
  73. package/.genie/code/agents/consensus.md +0 -204
  74. package/.genie/code/agents/daily-standup.md +0 -722
  75. package/.genie/code/agents/docgen.md +0 -48
  76. package/.genie/code/agents/explore.md +0 -79
  77. package/.genie/code/agents/fix.md +0 -100
  78. package/.genie/code/agents/git/commit-advisory.md +0 -219
  79. package/.genie/code/agents/git/workflows/issue.md +0 -244
  80. package/.genie/code/agents/git/workflows/pr.md +0 -179
  81. package/.genie/code/agents/git/workflows/release.md +0 -460
  82. package/.genie/code/agents/git/workflows/report.md +0 -342
  83. package/.genie/code/agents/git.md +0 -432
  84. package/.genie/code/agents/implementor.md +0 -161
  85. package/.genie/code/agents/install.md +0 -515
  86. package/.genie/code/agents/issue-creator.md +0 -344
  87. package/.genie/code/agents/polish.md +0 -116
  88. package/.genie/code/agents/qa.md +0 -653
  89. package/.genie/code/agents/refactor.md +0 -294
  90. package/.genie/code/agents/release.md +0 -1129
  91. package/.genie/code/agents/roadmap.md +0 -885
  92. package/.genie/code/agents/tests.md +0 -557
  93. package/.genie/code/agents/tracer.md +0 -50
  94. package/.genie/code/agents/update/upstream-update.md +0 -85
  95. package/.genie/code/agents/update/versions/generic-update.md +0 -305
  96. package/.genie/code/agents/vibe.md +0 -1317
  97. package/.genie/code/spells/agent-configuration.md +0 -58
  98. package/.genie/code/spells/automated-rc-publishing.md +0 -106
  99. package/.genie/code/spells/branch-tracker-guidance.md +0 -28
  100. package/.genie/code/spells/debug.md +0 -320
  101. package/.genie/code/spells/emoji-naming-convention.md +0 -303
  102. package/.genie/code/spells/evidence-storage.md +0 -26
  103. package/.genie/code/spells/file-naming-rules.md +0 -35
  104. package/.genie/code/spells/forge-code-blueprints.md +0 -195
  105. package/.genie/code/spells/genie-integration.md +0 -153
  106. package/.genie/code/spells/publishing-protocol.md +0 -61
  107. package/.genie/code/spells/team-consultation-protocol.md +0 -284
  108. package/.genie/code/spells/tool-requirements.md +0 -20
  109. package/.genie/code/spells/triad-maintenance-protocol.md +0 -154
  110. package/.genie/code/teams/tech-council/council.md +0 -328
  111. package/.genie/code/teams/tech-council/jt.md +0 -352
  112. package/.genie/code/teams/tech-council/nayr.md +0 -305
  113. package/.genie/code/teams/tech-council/oettam.md +0 -375
  114. package/.genie/neurons/README.md +0 -193
  115. package/.genie/neurons/forge.md +0 -106
  116. package/.genie/neurons/genie.md +0 -63
  117. package/.genie/neurons/review.md +0 -106
  118. package/.genie/neurons/wish.md +0 -104
  119. package/.genie/product/README.md +0 -20
  120. package/.genie/product/cli-automation.md +0 -359
  121. package/.genie/product/environment.md +0 -60
  122. package/.genie/product/mission.md +0 -60
  123. package/.genie/product/roadmap.md +0 -44
  124. package/.genie/product/tech-stack.md +0 -34
  125. package/.genie/product/templates/context-template.md +0 -218
  126. package/.genie/product/templates/qa-done-report-template.md +0 -68
  127. package/.genie/product/templates/review-report-template.md +0 -89
  128. package/.genie/product/templates/wish-template.md +0 -120
  129. package/.genie/scripts/helpers/analyze-commit.js +0 -195
  130. package/.genie/scripts/helpers/bullet-counter.js +0 -194
  131. package/.genie/scripts/helpers/bullet-find.js +0 -289
  132. package/.genie/scripts/helpers/bullet-id.js +0 -244
  133. package/.genie/scripts/helpers/check-secrets.js +0 -237
  134. package/.genie/scripts/helpers/count-tokens.js +0 -200
  135. package/.genie/scripts/helpers/create-frontmatter.js +0 -456
  136. package/.genie/scripts/helpers/detect-markers.js +0 -293
  137. package/.genie/scripts/helpers/detect-todos.js +0 -267
  138. package/.genie/scripts/helpers/detect-unlabeled-blocks.js +0 -135
  139. package/.genie/scripts/helpers/embeddings.js +0 -344
  140. package/.genie/scripts/helpers/find-empty-sections.js +0 -158
  141. package/.genie/scripts/helpers/index.js +0 -319
  142. package/.genie/scripts/helpers/validate-frontmatter.js +0 -578
  143. package/.genie/scripts/helpers/validate-links.js +0 -207
  144. package/.genie/scripts/helpers/validate-paths.js +0 -373
  145. package/.genie/spells/README.md +0 -9
  146. package/.genie/spells/ace-protocol.md +0 -118
  147. package/.genie/spells/ask-one-at-a-time.md +0 -175
  148. package/.genie/spells/backup-analyzer.md +0 -542
  149. package/.genie/spells/blocker.md +0 -12
  150. package/.genie/spells/break-things-move-fast.md +0 -56
  151. package/.genie/spells/context-candidates.md +0 -72
  152. package/.genie/spells/context-critic.md +0 -51
  153. package/.genie/spells/defer-to-expertise.md +0 -278
  154. package/.genie/spells/delegate-dont-do.md +0 -292
  155. package/.genie/spells/error-investigation-protocol.md +0 -328
  156. package/.genie/spells/evidence-based-completion.md +0 -273
  157. package/.genie/spells/experiment.md +0 -65
  158. package/.genie/spells/file-creation-protocol.md +0 -229
  159. package/.genie/spells/forge-integration.md +0 -281
  160. package/.genie/spells/forge-orchestration.md +0 -514
  161. package/.genie/spells/gather-context.md +0 -18
  162. package/.genie/spells/global-health-check.md +0 -34
  163. package/.genie/spells/global-noop-roundtrip.md +0 -25
  164. package/.genie/spells/install-genie.md +0 -1232
  165. package/.genie/spells/install.md +0 -82
  166. package/.genie/spells/investigate-before-commit.md +0 -112
  167. package/.genie/spells/know-yourself.md +0 -288
  168. package/.genie/spells/learn.md +0 -828
  169. package/.genie/spells/mcp-diagnostic-protocol.md +0 -246
  170. package/.genie/spells/mcp-first.md +0 -124
  171. package/.genie/spells/multi-step-execution.md +0 -67
  172. package/.genie/spells/orchestration-boundary-protocol.md +0 -256
  173. package/.genie/spells/orchestrator-not-implementor.md +0 -189
  174. package/.genie/spells/prompt.md +0 -746
  175. package/.genie/spells/reflect.md +0 -404
  176. package/.genie/spells/routing-decision-matrix.md +0 -368
  177. package/.genie/spells/run-in-parallel.md +0 -12
  178. package/.genie/spells/session-state-updater-example.md +0 -196
  179. package/.genie/spells/session-state-updater.md +0 -220
  180. package/.genie/spells/track-long-running-tasks.md +0 -133
  181. package/.genie/spells/troubleshoot-infrastructure.md +0 -176
  182. package/.genie/spells/upgrade-genie.md +0 -415
  183. package/.genie/spells/url-presentation-protocol.md +0 -301
  184. package/.genie/spells/wish-initiation.md +0 -158
  185. package/.genie/spells/wish-issue-linkage.md +0 -410
  186. package/.genie/spells/wish-lifecycle.md +0 -100
  187. package/.genie/state/provider-status.json +0 -3
  188. package/.genie/state/version.json +0 -16
  189. package/.genie/wishes/canonical-pgserve-pm2-supervision/WISH.md +0 -290
  190. package/.genie/wishes/pgserve-v2/BRIEF-from-genie-pgserve.md +0 -99
  191. package/.genie/wishes/pgserve-v2/WISH.md +0 -442
  192. package/.genie/wishes/release-system-genie-pattern/WISH.md +0 -268
  193. package/.genie/wishes/release-system-genie-pattern/validation.md +0 -205
  194. package/.gitguardian.yaml +0 -29
  195. package/.gitguardianignore +0 -16
  196. package/.github/workflows/ci.yml +0 -122
  197. package/.github/workflows/release.yml +0 -289
  198. package/.github/workflows/version.yml +0 -228
  199. package/.husky/pre-commit +0 -2
  200. package/AGENTS.md +0 -433
  201. package/CLAUDE.md +0 -1
  202. package/Makefile +0 -285
  203. package/assets/icon.ico +0 -0
  204. package/bun.lock +0 -435
  205. package/bunfig.toml +0 -28
  206. package/ecosystem.config.cjs +0 -23
  207. package/eslint.config.js +0 -63
  208. package/examples/multi-tenant-demo.js +0 -104
  209. package/install.sh +0 -123
  210. package/knip.json +0 -9
  211. package/tests/audit.test.js +0 -189
  212. package/tests/backpressure.test.js +0 -167
  213. package/tests/benchmarks/runner.js +0 -1197
  214. package/tests/benchmarks/vector-generator.js +0 -368
  215. package/tests/cli-install.test.js +0 -322
  216. package/tests/control-db.test.js +0 -285
  217. package/tests/daemon-args.test.js +0 -86
  218. package/tests/daemon-control.test.js +0 -171
  219. package/tests/daemon-fingerprint-integration.test.js +0 -111
  220. package/tests/daemon-pr24-regression.test.js +0 -198
  221. package/tests/fingerprint.test.js +0 -263
  222. package/tests/fixtures/240-orphan-seed.sql +0 -30
  223. package/tests/multi-tenant.test.js +0 -374
  224. package/tests/orphan-cleanup.test.js +0 -390
  225. package/tests/pg-version-regex.test.js +0 -129
  226. package/tests/quick-bench.js +0 -135
  227. package/tests/router-handshake-retry.test.js +0 -119
  228. package/tests/router-handshake-watchdog.test.js +0 -110
  229. package/tests/sdk.test.js +0 -71
  230. package/tests/stale-postmaster-pid.test.js +0 -85
  231. package/tests/stress-test.js +0 -439
  232. package/tests/sync-perf-test.js +0 -150
  233. package/tests/tcp-listen.test.js +0 -368
  234. package/tests/tenancy.test.js +0 -403
  235. package/tests/wrapper-supervision.test.js +0 -107
@@ -0,0 +1,310 @@
1
+ /**
2
+ * `autopg config` subcommand router (also reachable via `pgserve config`).
3
+ *
4
+ * Surface:
5
+ * autopg config list - print every leaf as key|value|source
6
+ * autopg config get <key> - print the resolved value (machine-friendly)
7
+ * autopg config set <key> <value> - validate + atomic write, round-trips through get
8
+ * autopg config edit - open $EDITOR on settings.json
9
+ * autopg config path - print absolute path to settings.json
10
+ * autopg config init [--force] - write schema defaults; refuses to clobber
11
+ *
12
+ * Exit codes:
13
+ * 0 - success
14
+ * 1 - unknown subcommand / IO error / EDITOR not set / settings file unreadable
15
+ * 2 - validation error (stable shape: `error: <field> — <CODE>: <detail>`)
16
+ *
17
+ * The CLI is single-process and skips the etag round-trip — each `set` is its
18
+ * own transaction. Concurrency control is the UI helper's responsibility.
19
+ */
20
+
21
+ 'use strict';
22
+
23
+ const { spawnSync } = require('node:child_process');
24
+ const fs = require('node:fs');
25
+
26
+ const { loadEffectiveConfig, getSettingsPath } = require('./settings-loader.cjs');
27
+ const {
28
+ setLeaf,
29
+ initSettings,
30
+ ensureConfigDir,
31
+ } = require('./settings-writer.cjs');
32
+ const {
33
+ ValidationError,
34
+ validateSetting,
35
+ resolveKey,
36
+ } = require('./settings-validator.cjs');
37
+ const { SCHEMA, flattenSchema } = require('./settings-schema.cjs');
38
+
39
+ const EXIT_OK = 0;
40
+ const EXIT_UNKNOWN = 1;
41
+ const EXIT_VALIDATION = 2;
42
+
43
+ function emitError(field, code, detail) {
44
+ process.stderr.write(`error: ${field} — ${code}: ${detail}\n`);
45
+ }
46
+
47
+ function emitErrorFromValidation(err) {
48
+ emitError(err.field ?? '_root', err.code ?? 'INVALID', err.detail ?? err.message);
49
+ }
50
+
51
+ /**
52
+ * Resolve the current value of `key` from the merged effective config tree.
53
+ * Supports curated leaves (`section.field`) and `_extra` entries
54
+ * (`postgres._extra.<gucName>`). Returns `{ value }` or `null` when missing.
55
+ */
56
+ function readValue(tree, key) {
57
+ if (key.startsWith('postgres._extra.')) {
58
+ const guc = key.slice('postgres._extra.'.length);
59
+ const map = tree?.postgres?._extra;
60
+ if (map && Object.prototype.hasOwnProperty.call(map, guc)) {
61
+ return { value: map[guc] };
62
+ }
63
+ return null;
64
+ }
65
+ const [section, field] = key.split('.');
66
+ if (!section || !field) return null;
67
+ const node = tree?.[section];
68
+ if (!node || !Object.prototype.hasOwnProperty.call(node, field)) return null;
69
+ return { value: node[field] };
70
+ }
71
+
72
+ /**
73
+ * Serialize a leaf value for human consumption. Objects (the `_extra` map
74
+ * descriptor) round-trip through JSON; primitives stringify directly.
75
+ * `null` / `undefined` render as the empty string so `autopg config get`
76
+ * stays scriptable.
77
+ */
78
+ function formatValue(value) {
79
+ if (value === null || value === undefined) return '';
80
+ if (typeof value === 'object') return JSON.stringify(value);
81
+ return String(value);
82
+ }
83
+
84
+ /**
85
+ * Assemble the full set of keys to display in `config list`. Curated
86
+ * leaves come from the schema; `_extra` entries are expanded from the
87
+ * effective tree so user-added GUCs surface.
88
+ */
89
+ function enumerateKeys(tree) {
90
+ const out = [];
91
+ for (const [section, fields] of Object.entries(SCHEMA)) {
92
+ for (const field of Object.keys(fields)) {
93
+ out.push(`${section}.${field}`);
94
+ }
95
+ }
96
+ const extras = tree?.postgres?._extra;
97
+ if (extras && typeof extras === 'object') {
98
+ for (const guc of Object.keys(extras)) {
99
+ out.push(`postgres._extra.${guc}`);
100
+ }
101
+ }
102
+ return out;
103
+ }
104
+
105
+ function pad(s, n) {
106
+ s = String(s);
107
+ if (s.length >= n) return s;
108
+ return s + ' '.repeat(n - s.length);
109
+ }
110
+
111
+ function cmdList() {
112
+ const { settings, sources } = loadEffectiveConfig();
113
+ const keys = enumerateKeys(settings);
114
+
115
+ // Source for `_extra` entries inherits the parent map's source. The
116
+ // loader doesn't break the map per-entry because env precedence
117
+ // applies wholesale, so we surface each row's source as the parent.
118
+ const rows = keys.map((key) => {
119
+ const valueResolved = readValue(settings, key);
120
+ const value = valueResolved ? formatValue(valueResolved.value) : '';
121
+ let source;
122
+ if (key.startsWith('postgres._extra.')) {
123
+ source = sources['postgres._extra'] || 'default';
124
+ } else {
125
+ source = sources[key] || 'default';
126
+ }
127
+ return { key, value, source };
128
+ });
129
+
130
+ const widths = {
131
+ key: Math.max(3, ...rows.map((r) => r.key.length)),
132
+ value: Math.max(5, ...rows.map((r) => r.value.length)),
133
+ source: Math.max(6, ...rows.map((r) => r.source.length)),
134
+ };
135
+
136
+ process.stdout.write(
137
+ `${pad('KEY', widths.key)} ${pad('VALUE', widths.value)} ${pad('SOURCE', widths.source)}\n`,
138
+ );
139
+ for (const row of rows) {
140
+ process.stdout.write(
141
+ `${pad(row.key, widths.key)} ${pad(row.value, widths.value)} ${pad(row.source, widths.source)}\n`,
142
+ );
143
+ }
144
+ return EXIT_OK;
145
+ }
146
+
147
+ function cmdGet(args) {
148
+ const key = args[0];
149
+ if (!key) {
150
+ emitError('_args', 'INVALID_KEY', 'config get requires a key');
151
+ return EXIT_VALIDATION;
152
+ }
153
+ // Validate key shape early so typos surface as INVALID_KEY rather than
154
+ // an empty value print.
155
+ try {
156
+ resolveKey(key);
157
+ } catch (err) {
158
+ if (err instanceof ValidationError) {
159
+ emitErrorFromValidation(err);
160
+ return EXIT_VALIDATION;
161
+ }
162
+ throw err;
163
+ }
164
+
165
+ const { settings } = loadEffectiveConfig();
166
+ const resolved = readValue(settings, key);
167
+ if (!resolved) {
168
+ process.stdout.write('\n');
169
+ return EXIT_OK;
170
+ }
171
+ process.stdout.write(`${formatValue(resolved.value)}\n`);
172
+ return EXIT_OK;
173
+ }
174
+
175
+ function cmdSet(args) {
176
+ if (args.length < 2) {
177
+ emitError('_args', 'INVALID_KEY', 'config set requires <key> <value>');
178
+ return EXIT_VALIDATION;
179
+ }
180
+ const [key, ...rest] = args;
181
+ // Allow values that contain spaces by joining the remainder. Operators
182
+ // can still quote the value as a single argv slot; this is the safe
183
+ // fallback.
184
+ const value = rest.join(' ');
185
+
186
+ try {
187
+ setLeaf(key, value);
188
+ } catch (err) {
189
+ if (err instanceof ValidationError) {
190
+ emitErrorFromValidation(err);
191
+ return EXIT_VALIDATION;
192
+ }
193
+ throw err;
194
+ }
195
+ return EXIT_OK;
196
+ }
197
+
198
+ function cmdPath() {
199
+ process.stdout.write(`${getSettingsPath()}\n`);
200
+ return EXIT_OK;
201
+ }
202
+
203
+ function cmdInit(args) {
204
+ const force = args.includes('--force');
205
+ ensureConfigDir();
206
+ try {
207
+ initSettings({ force });
208
+ } catch (err) {
209
+ if (err.code === 'EEXIST') {
210
+ emitError(
211
+ getSettingsPath(),
212
+ 'EEXIST',
213
+ 'settings.json already exists; pass --force to overwrite',
214
+ );
215
+ return EXIT_VALIDATION;
216
+ }
217
+ if (err instanceof ValidationError) {
218
+ emitErrorFromValidation(err);
219
+ return EXIT_VALIDATION;
220
+ }
221
+ throw err;
222
+ }
223
+ process.stdout.write(`autopg: wrote defaults to ${getSettingsPath()}\n`);
224
+ return EXIT_OK;
225
+ }
226
+
227
+ /**
228
+ * `autopg config edit` — open the configured editor on `settings.json`,
229
+ * creating the file with defaults if it doesn't exist yet (so the
230
+ * operator gets a useful template instead of an empty buffer).
231
+ *
232
+ * Editor resolution: $VISUAL, $EDITOR, then `vi` (POSIX) / `notepad` (Windows).
233
+ */
234
+ function cmdEdit() {
235
+ const settingsPath = getSettingsPath();
236
+ if (!fs.existsSync(settingsPath)) {
237
+ ensureConfigDir();
238
+ initSettings({});
239
+ }
240
+
241
+ const editor =
242
+ process.env.VISUAL ||
243
+ process.env.EDITOR ||
244
+ (process.platform === 'win32' ? 'notepad' : 'vi');
245
+
246
+ // Editors are interactive — inherit stdio so the operator gets the TUI.
247
+ const result = spawnSync(editor, [settingsPath], { stdio: 'inherit' });
248
+ if (result.error) {
249
+ emitError(
250
+ 'editor',
251
+ 'EEDITOR',
252
+ `failed to launch editor "${editor}": ${result.error.message}`,
253
+ );
254
+ return EXIT_UNKNOWN;
255
+ }
256
+ return result.status ?? EXIT_OK;
257
+ }
258
+
259
+ /**
260
+ * Subcommand dispatch. Returns the exit code; the parent dispatcher
261
+ * uses the return value as `process.exit(code)` directly.
262
+ */
263
+ function dispatch(subcommand, args = []) {
264
+ switch (subcommand) {
265
+ case 'list':
266
+ return cmdList();
267
+ case 'get':
268
+ return cmdGet(args);
269
+ case 'set':
270
+ return cmdSet(args);
271
+ case 'path':
272
+ return cmdPath();
273
+ case 'init':
274
+ return cmdInit(args);
275
+ case 'edit':
276
+ return cmdEdit();
277
+ case undefined:
278
+ case '': {
279
+ // Bare `autopg config` → list (mirrors `git config --list` ergonomics).
280
+ return cmdList();
281
+ }
282
+ default:
283
+ emitError(subcommand, 'INVALID_KEY', `unknown config subcommand "${subcommand}"`);
284
+ process.stderr.write(
285
+ 'usage: autopg config <list|get|set|edit|path|init> [args]\n',
286
+ );
287
+ return EXIT_UNKNOWN;
288
+ }
289
+ }
290
+
291
+ module.exports = {
292
+ dispatch,
293
+ EXIT_OK,
294
+ EXIT_UNKNOWN,
295
+ EXIT_VALIDATION,
296
+ // Test surface
297
+ _internals: {
298
+ cmdList,
299
+ cmdGet,
300
+ cmdSet,
301
+ cmdPath,
302
+ cmdInit,
303
+ cmdEdit,
304
+ enumerateKeys,
305
+ formatValue,
306
+ readValue,
307
+ flattenSchema,
308
+ validateSetting,
309
+ },
310
+ };
@@ -77,8 +77,22 @@ const HARDENED_DEFAULTS = {
77
77
  logDateFormat: 'YYYY-MM-DD HH:mm:ss.SSS',
78
78
  };
79
79
 
80
+ /**
81
+ * Resolve the config directory. AUTOPG_CONFIG_DIR (the new var) wins,
82
+ * PGSERVE_CONFIG_DIR (the legacy var) is honored as a fall-through, and
83
+ * `~/.autopg/` is the new default. The legacy default `~/.pgserve/` is
84
+ * NOT consulted here — `settings-migrate.js` handles the one-shot copy.
85
+ *
86
+ * Soft-rename rule: AUTOPG_<X> beats PGSERVE_<X>. When only the legacy
87
+ * env is set we still honor it but the loader emits a one-time
88
+ * deprecation log via logger.warn (see settings-loader.js).
89
+ */
80
90
  function getConfigDir() {
81
- return process.env.PGSERVE_CONFIG_DIR || path.join(os.homedir(), '.pgserve');
91
+ return (
92
+ process.env.AUTOPG_CONFIG_DIR ||
93
+ process.env.PGSERVE_CONFIG_DIR ||
94
+ path.join(os.homedir(), '.autopg')
95
+ );
82
96
  }
83
97
 
84
98
  function getConfigPath() {
@@ -139,11 +153,40 @@ function pm2IsAvailable() {
139
153
  }
140
154
  }
141
155
 
156
+ /**
157
+ * Resolve the effective supervision config — start from HARDENED_DEFAULTS,
158
+ * overlay any values found in `~/.autopg/settings.json` `supervision`
159
+ * section. Failures fall through to defaults silently so `pgserve install`
160
+ * still works on a fresh machine before `autopg config init` has run.
161
+ *
162
+ * Precedence: defaults < settings.json < env (env wins via loadEffectiveConfig).
163
+ */
164
+ function getEffectiveSupervision() {
165
+ try {
166
+ const { loadEffectiveConfig } = require('./settings-loader.cjs');
167
+ const { settings } = loadEffectiveConfig();
168
+ const sup = settings?.supervision || {};
169
+ return {
170
+ maxRestarts: sup.maxRestarts ?? HARDENED_DEFAULTS.maxRestarts,
171
+ minUptimeMs: sup.minUptimeMs ?? HARDENED_DEFAULTS.minUptimeMs,
172
+ restartDelayMs: sup.restartDelayMs ?? HARDENED_DEFAULTS.restartDelayMs,
173
+ expBackoffRestartDelayMs: sup.expBackoffRestartDelayMs ?? HARDENED_DEFAULTS.expBackoffRestartDelayMs,
174
+ expBackoffMaxMs: sup.expBackoffMaxMs ?? HARDENED_DEFAULTS.expBackoffMaxMs,
175
+ maxMemory: sup.maxMemory ?? HARDENED_DEFAULTS.maxMemory,
176
+ killTimeoutMs: sup.killTimeoutMs ?? HARDENED_DEFAULTS.killTimeoutMs,
177
+ logDateFormat: sup.logDateFormat ?? HARDENED_DEFAULTS.logDateFormat,
178
+ };
179
+ } catch {
180
+ return { ...HARDENED_DEFAULTS };
181
+ }
182
+ }
183
+
142
184
  function buildPm2StartArgs({ scriptPath, port, dataDir }) {
143
185
  const logs = {
144
186
  out: path.join(getLogsDir(), `${PM2_PROCESS_NAME}-out.log`),
145
187
  error: path.join(getLogsDir(), `${PM2_PROCESS_NAME}-error.log`),
146
188
  };
189
+ const supervision = getEffectiveSupervision();
147
190
  return [
148
191
  'start',
149
192
  scriptPath,
@@ -152,7 +195,7 @@ function buildPm2StartArgs({ scriptPath, port, dataDir }) {
152
195
  '--interpreter',
153
196
  'none',
154
197
  '--max-restarts',
155
- String(HARDENED_DEFAULTS.maxRestarts),
198
+ String(supervision.maxRestarts),
156
199
  // NOTE: pm2 ≥ 6.0 dropped `--min-uptime` from the CLI surface — passing
157
200
  // it produces `error: unknown option --min-uptime` and aborts the
158
201
  // install. The flag still works inside an ecosystem file, but per the
@@ -162,18 +205,18 @@ function buildPm2StartArgs({ scriptPath, port, dataDir }) {
162
205
  // than only sub-`min_uptime` ones; the budget of 50 above is sized
163
206
  // accordingly.
164
207
  '--restart-delay',
165
- String(HARDENED_DEFAULTS.restartDelayMs),
208
+ String(supervision.restartDelayMs),
166
209
  // Exponential backoff between successive failures: starts at 100ms,
167
210
  // doubles each crash, ramps to ~60s. Avoids hammering pm2 + the host
168
211
  // when the underlying issue is persistent.
169
212
  '--exp-backoff-restart-delay',
170
- String(HARDENED_DEFAULTS.expBackoffRestartDelayMs),
213
+ String(supervision.expBackoffRestartDelayMs),
171
214
  '--max-memory-restart',
172
- HARDENED_DEFAULTS.maxMemory,
215
+ supervision.maxMemory,
173
216
  '--kill-timeout',
174
- String(HARDENED_DEFAULTS.killTimeoutMs),
217
+ String(supervision.killTimeoutMs),
175
218
  '--log-date-format',
176
- HARDENED_DEFAULTS.logDateFormat,
219
+ supervision.logDateFormat,
177
220
  '--output',
178
221
  logs.out,
179
222
  '--error',
@@ -393,12 +436,42 @@ function parseDataDir(args) {
393
436
  }
394
437
 
395
438
  /**
396
- * Entry point invoked by the wrapper. Returns the exit code. Throws on
397
- * unknown subcommand so the wrapper's normal flow can take over (the
398
- * router treats any non-recognized subcommand as "pass through to the
399
- * postgres-server.js dispatcher").
439
+ * One-shot migration check from `~/.pgserve/` `~/.autopg/`. Runs once
440
+ * per process at the top of dispatch() so every CLI entry point gets
441
+ * the cutover. Fully best-effort: any failure is swallowed (we never
442
+ * want migration to block an `autopg status` invocation).
443
+ */
444
+ let _migrationChecked = false;
445
+ function ensureMigrationOnce() {
446
+ if (_migrationChecked) return;
447
+ _migrationChecked = true;
448
+ try {
449
+ const { migrateIfNeeded } = require('./settings-migrate.cjs');
450
+ const result = migrateIfNeeded();
451
+ if (result.migrated) {
452
+ process.stderr.write(
453
+ `autopg: migrated ${result.legacy} → ${result.fresh} (one-time)\n`,
454
+ );
455
+ }
456
+ } catch {
457
+ // Swallow — operator can re-run migration manually if needed.
458
+ }
459
+ }
460
+
461
+ /**
462
+ * Entry point invoked by the wrapper. Returns the exit code (or a Promise
463
+ * for async subcommands such as `ui`). Throws on unknown subcommand so
464
+ * the wrapper's normal flow can take over (the router treats any
465
+ * non-recognized subcommand as "pass through to the postgres-server.js
466
+ * dispatcher").
467
+ *
468
+ * `ctx.scriptPath` is the path to `bin/postgres-server.js` (used by
469
+ * install for the pm2 entry point). For `restart` and `ui` we need the
470
+ * wrapper script path instead — `ctx.wrapperPath`. The wrapper provides
471
+ * both before calling dispatch.
400
472
  */
401
473
  function dispatch(subcommand, args, ctx) {
474
+ ensureMigrationOnce();
402
475
  switch (subcommand) {
403
476
  case 'install':
404
477
  return cmdInstall(args, ctx);
@@ -410,6 +483,33 @@ function dispatch(subcommand, args, ctx) {
410
483
  return cmdUrl();
411
484
  case 'port':
412
485
  return cmdPort();
486
+ case 'upgrade': {
487
+ const opts = {
488
+ quiet: args.includes('--quiet'),
489
+ dryRun: args.includes('--dry-run'),
490
+ skipSteps: (() => {
491
+ const idx = args.indexOf('--skip-steps');
492
+ if (idx === -1) return [];
493
+ return (args[idx + 1] || '').split(',').filter(Boolean);
494
+ })(),
495
+ };
496
+ return import(require('node:path').join(__dirname, 'upgrade', 'index.js'))
497
+ .then((mod) => mod.upgrade(opts))
498
+ .then((r) => process.exit(r.ok ? 0 : 1));
499
+ }
500
+ case 'config': {
501
+ const cfg = require('./cli-config.cjs');
502
+ const [sub, ...rest] = args;
503
+ return cfg.dispatch(sub, rest);
504
+ }
505
+ case 'restart': {
506
+ const restart = require('./cli-restart.cjs');
507
+ return restart.dispatch(args, { scriptPath: ctx.wrapperPath });
508
+ }
509
+ case 'ui': {
510
+ const ui = require('./cli-ui.cjs');
511
+ return ui.dispatch(args, { scriptPath: ctx.wrapperPath });
512
+ }
413
513
  default:
414
514
  throw new Error(`pgserve: dispatch called with unknown subcommand "${subcommand}"`);
415
515
  }
@@ -430,6 +530,7 @@ module.exports = {
430
530
  readConfig,
431
531
  writeConfig,
432
532
  buildPm2StartArgs,
533
+ getEffectiveSupervision,
433
534
  parsePort,
434
535
  parseDataDir,
435
536
  },