@mason-data/cli 0.7.0 → 0.9.0

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 (155) hide show
  1. package/README.md +101 -73
  2. package/dist/src/api/error.js +3 -0
  3. package/dist/src/api/postgres/catalog-worker.js +49 -2
  4. package/dist/src/api/postgres/catalog.js +368 -25
  5. package/dist/src/api/postgres/encode.js +3 -1
  6. package/dist/src/api/postgres/explain.js +3 -2
  7. package/dist/src/api/postgres/listener.js +11 -3
  8. package/dist/src/api/postgres/protocol.js +39 -23
  9. package/dist/src/api/postgres/proxy.js +283 -0
  10. package/dist/src/api/postgres/schema.js +6 -0
  11. package/dist/src/api/postgres/server.js +1617 -245
  12. package/dist/src/api/postgres/translate.js +127 -10
  13. package/dist/src/api/request-log.js +18 -5
  14. package/dist/src/cdc/poll.js +5 -5
  15. package/dist/src/cli/background.js +47 -25
  16. package/dist/src/cli/bridge-stdin.js +101 -0
  17. package/dist/src/cli/cancel.js +75 -0
  18. package/dist/src/cli/command.js +170 -11
  19. package/dist/src/cli/commands.js +804 -209
  20. package/dist/src/cli/control.js +59 -0
  21. package/dist/src/cli/credentials.js +182 -0
  22. package/dist/src/cli/demo.js +20 -11
  23. package/dist/src/cli/deploy.js +574 -0
  24. package/dist/src/cli/dev.js +491 -130
  25. package/dist/src/cli/doctor.js +131 -38
  26. package/dist/src/cli/generated-files.js +143 -0
  27. package/dist/src/cli/guides.js +175 -36
  28. package/dist/src/cli/hosted.js +850 -0
  29. package/dist/src/cli/init.js +162 -65
  30. package/dist/src/cli/login.js +289 -0
  31. package/dist/src/cli/machine.js +13 -12
  32. package/dist/src/cli/main.js +11 -5
  33. package/dist/src/cli/mason.cjs +17 -0
  34. package/dist/src/cli/mcp.js +31 -22
  35. package/dist/src/cli/metrics.js +79 -0
  36. package/dist/src/cli/output-gate.js +176 -0
  37. package/dist/src/cli/page-share.js +96 -0
  38. package/dist/src/cli/pg-tls.js +14 -4
  39. package/dist/src/cli/pg-url.js +4 -0
  40. package/dist/src/cli/pin.js +19 -14
  41. package/dist/src/cli/pool-size.cjs +85 -0
  42. package/dist/src/cli/ports.js +2 -0
  43. package/dist/src/cli/preflight.js +138 -46
  44. package/dist/src/cli/project.js +142 -4
  45. package/dist/src/cli/readiness.js +76 -6
  46. package/dist/src/cli/registry.js +65 -38
  47. package/dist/src/cli/replica-hosted.js +665 -0
  48. package/dist/src/cli/replica.js +76 -0
  49. package/dist/src/cli/replication-checks.js +7 -5
  50. package/dist/src/cli/replication-mode.js +105 -26
  51. package/dist/src/cli/request-filter.js +134 -0
  52. package/dist/src/cli/roles.js +585 -0
  53. package/dist/src/cli/run-server.js +637 -48
  54. package/dist/src/cli/runs.js +1 -1
  55. package/dist/src/cli/security.js +67 -18
  56. package/dist/src/cli/serve.js +204 -46
  57. package/dist/src/cli/share-run.js +329 -0
  58. package/dist/src/cli/share.js +286 -0
  59. package/dist/src/cli/source-edit.js +75 -17
  60. package/dist/src/cli/source-role.js +344 -0
  61. package/dist/src/cli/sources.js +306 -47
  62. package/dist/src/cli/style.js +2 -1
  63. package/dist/src/cli/supervise.js +27 -24
  64. package/dist/src/cli/sync.js +55 -25
  65. package/dist/src/cli/token-hosted.js +337 -0
  66. package/dist/src/cli/tunnel.js +200 -0
  67. package/dist/src/cli/ui-ssh.js +460 -0
  68. package/dist/src/cli/ui.js +124 -17
  69. package/dist/src/cli/usage.js +128 -0
  70. package/dist/src/cli/user-hosted.js +299 -0
  71. package/dist/src/cli/user.js +573 -65
  72. package/dist/src/cli/version.js +9 -7
  73. package/dist/src/demo/seed.js +29 -0
  74. package/dist/src/duck/analyze.js +36 -20
  75. package/dist/src/duck/cancel.js +10 -1
  76. package/dist/src/duck/collation.js +266 -0
  77. package/dist/src/duck/constant-key.js +94 -0
  78. package/dist/src/duck/distinct-key.js +283 -26
  79. package/dist/src/duck/events.js +165 -0
  80. package/dist/src/duck/executor-worker.js +20 -11
  81. package/dist/src/duck/executor.js +47 -33
  82. package/dist/src/duck/explain.js +60 -46
  83. package/dist/src/duck/extensions.js +160 -0
  84. package/dist/src/duck/functions.js +461 -63
  85. package/dist/src/duck/gaps.js +48 -21
  86. package/dist/src/duck/generated-plan.js +1070 -0
  87. package/dist/src/duck/generated.js +906 -0
  88. package/dist/src/duck/grants.js +176 -0
  89. package/dist/src/duck/held-types.js +64 -0
  90. package/dist/src/duck/history.js +127 -0
  91. package/dist/src/duck/jsonb-order.js +9 -12
  92. package/dist/src/duck/kept-route.js +6 -5
  93. package/dist/src/duck/kept.js +15 -13
  94. package/dist/src/duck/late.js +1469 -40
  95. package/dist/src/duck/libc-bound.js +341 -0
  96. package/dist/src/duck/libc-order.js +892 -126
  97. package/dist/src/duck/pg-eval-worker.js +7 -1
  98. package/dist/src/duck/pg-eval.js +6 -4
  99. package/dist/src/duck/pgtree.js +774 -23
  100. package/dist/src/duck/plan.js +369 -47
  101. package/dist/src/duck/publication.js +20 -6
  102. package/dist/src/duck/requests.js +269 -93
  103. package/dist/src/duck/resolved.js +117 -2
  104. package/dist/src/duck/rewrite.js +4584 -580
  105. package/dist/src/duck/row-filter.js +655 -0
  106. package/dist/src/duck/statement.js +19 -0
  107. package/dist/src/duck/store.js +439 -177
  108. package/dist/src/duck/stream.js +19 -10
  109. package/dist/src/duck/sync.js +1411 -368
  110. package/dist/src/duck/token.js +4 -1
  111. package/dist/src/duck/types.js +69 -12
  112. package/dist/src/duck/usage.js +326 -0
  113. package/dist/src/duck/users.js +69 -15
  114. package/dist/src/duck/verified.js +161 -1
  115. package/dist/src/duck/views.js +5 -5
  116. package/dist/src/lib/database-name.js +62 -0
  117. package/dist/src/lib/database-url.js +55 -10
  118. package/dist/src/lib/dotenv.js +17 -4
  119. package/dist/src/lib/instant.js +50 -0
  120. package/dist/src/lib/log-buffer.js +77 -0
  121. package/dist/src/lib/log-read.js +196 -0
  122. package/dist/src/lib/pg-numeric.js +390 -49
  123. package/dist/src/lib/project-version.js +23 -23
  124. package/dist/src/lib/secret-mask.js +142 -0
  125. package/dist/src/lib/table-sync.js +282 -0
  126. package/dist/src/web/app.css +706 -66
  127. package/dist/src/web/app.js +331 -409
  128. package/dist/src/web/charts.js +229 -0
  129. package/dist/src/web/index.html +82 -18
  130. package/dist/src/web/pages/queries.js +192 -0
  131. package/dist/src/web/pages/status.js +611 -0
  132. package/dist/src/web/pages/syncs.js +155 -0
  133. package/dist/src/web/pages/tables.js +439 -0
  134. package/dist/src/web/pages/usage.js +397 -0
  135. package/dist/src/web/pages/users.js +219 -0
  136. package/dist/src/web/shared.js +584 -0
  137. package/dist/src/web/vendor/README.md +25 -0
  138. package/dist/src/web/vendor/geist-1.7.2/Geist-Variable.woff2 +0 -0
  139. package/dist/src/web/vendor/geist-1.7.2/GeistMono-Variable.woff2 +0 -0
  140. package/dist/src/web/vendor/geist-1.7.2/LICENSE.txt +92 -0
  141. package/dist/src/web/vendor/lucide-1.56.0/LICENSE.txt +43 -0
  142. package/dist/src/web/vendor/lucide-1.56.0/chart-column.mjs +29 -0
  143. package/dist/src/web/vendor/lucide-1.56.0/layout-dashboard.mjs +29 -0
  144. package/dist/src/web/vendor/lucide-1.56.0/logs.mjs +34 -0
  145. package/dist/src/web/vendor/lucide-1.56.0/menu.mjs +28 -0
  146. package/dist/src/web/vendor/lucide-1.56.0/square-terminal.mjs +28 -0
  147. package/dist/src/web/vendor/lucide-1.56.0/table.mjs +29 -0
  148. package/dist/src/web/vendor/lucide-1.56.0/users.mjs +29 -0
  149. package/dist/src/web/vendor/lucide-1.56.0/x.mjs +27 -0
  150. package/dist/src/web/vendor/uplot-1.6.32/LICENSE.txt +21 -0
  151. package/dist/src/web/vendor/uplot-1.6.32/uPlot.esm.js +6140 -0
  152. package/dist/src/web/vendor/uplot-1.6.32/uPlot.min.css +1 -0
  153. package/dist/src/web/vendor/zilla-slab-5.3.0/LICENSE.txt +93 -0
  154. package/dist/src/web/vendor/zilla-slab-5.3.0/zilla-slab-latin-700-normal.woff2 +0 -0
  155. package/package.json +12 -4
@@ -3,19 +3,29 @@
3
3
  * command's summary, in this order — so a command cannot exist without being listed, and tests/cli-help.test.ts holds
4
4
  * each one to src/cli/command.ts's rules (its own --help, an example, every option documented).
5
5
  */
6
- import { failed, parseCommand, reportText } from "./command.js";
6
+ import { failed, parseCommand, reportText, wantsJson } from "./command.js";
7
+ import { removedStartFlags } from "../lib/table-sync.js";
8
+ import { sourcesTop } from "./sources.js";
7
9
  import { dev } from "./dev.js";
8
10
  import { devInBackground } from "./background.js";
9
11
  import { ADD, LOGS, REMOVE, STATUS, STOP, TABLES, WAIT, add, logs, remove, status, stop, tables, wait } from "./commands.js";
10
12
  import { USER, user } from "./user.js";
11
13
  import { MCP, mcp } from "./mcp.js";
14
+ import { SHARE, share } from "./share.js";
15
+ import { DEFAULT_UI_PORT } from "./ports.js";
12
16
  import { UI, ui } from "./ui.js";
13
17
  import { GUIDES, guideText } from "./guides.js";
14
18
  import { DOCTOR, NODE_MIN, doctor } from "./doctor.js";
15
19
  import { DESTROY, destroy } from "./machine.js";
16
20
  import { INIT, init } from "./init.js";
17
21
  import { SYNC, sync } from "./sync.js";
22
+ import { USAGE, usage } from "./usage.js";
23
+ import { CANCEL, cancel } from "./cancel.js";
18
24
  import { SERVE, serve } from "./serve.js";
25
+ import { LOGIN, LOGOUT, login, logout } from "./login.js";
26
+ import { DEPLOY, deploy } from "./deploy.js";
27
+ import { REPLICA, replica } from "./replica-hosted.js";
28
+ import { TOKEN, token } from "./token-hosted.js";
19
29
  import { PROJECT_LINE } from "./dev.js";
20
30
  import { version } from "./version.js";
21
31
  import { didYouMean } from "../lib/did-you-mean.js";
@@ -27,21 +37,31 @@ export const DEV = {
27
37
  needs: "project",
28
38
  name: "start",
29
39
  summary: "start Mason for this folder's project, in the background (mason init makes the project)",
30
- usage: `usage: mason start [--attach] [--replication [--publication <name>] | --poll] [--self-signed-tls] [--port <n>] [--json]
40
+ usage: `usage: mason start [--attach] [--self-signed-tls] [--port <n>] [--json]
31
41
 
32
- Starts Mason for the project of this folder (mason init made it): one process, which copies the tables you add
33
- (mason add) from the project's database into its own file, keeps them current, and answers the Postgres protocol
34
- for BI tools and your app. It only reads the database (SELECT): no wal_level, replication slot or publication is
35
- needed or made. It returns once Mason is up; run it again any time (it says so when Mason is already running). The
36
- copy and Mason's state stay between runs: the next start resumes.
42
+ Starts Mason for the project of this folder (mason init made it): one process, which syncs the tables you add
43
+ (mason add) from the project's database into its own file, keeps them current as the sources file says, and answers
44
+ the Postgres protocol for BI tools and your app. It only reads the database (SELECT, and a table's change log when
45
+ its sync: says replication). It returns once Mason is up; run it again any time (it says so when Mason is
46
+ already running). The synced data and Mason's state stay between runs: the next start resumes.
37
47
 
38
- With --replication it reads the database's change log instead: it needs wal_level = logical and a user that may
39
- replicate, and makes its own publication and replication slot on the database (mason destroy, or mason start --poll,
40
- drops them). The project keeps the mode until --poll; a switch keeps the copies and catches each table up by one
41
- poll. --publication <name> (else PG_PUBLICATION) names a publication that is the database owner's: it is read as it
42
- is, verified to carry every added table and column, and never created, altered or dropped — Mason makes only its
43
- slot. A read replica needs one (it takes no CREATE PUBLICATION). The name is kept with the project, so the next
44
- mason start --replication reads it again.
48
+ After its first sync, each table is kept current as the sources file's sync: says — at the top for every table, or
49
+ on a table's entry: off (the default, which mason init writes) reads it again only when you run mason sync <table>;
50
+ poll <interval> (poll 5s, poll 5m, poll 1h) polls it with SELECTs at that interval; replication reads its changes
51
+ from the database's change log, as they are made. Each poll scans its table whole
52
+ on your database, and the next waits at least four times as long as that poll took, so the polls never take more
53
+ than a fifth of your database's time. The start says the file's default once; mason status says how each table is
54
+ kept current and when it was last synced. A table whose sync: changed keeps its synced data and is caught up by one poll.
55
+
56
+ A table under sync: replication needs wal_level = logical, a user that may replicate, and a primary key (a key:
57
+ table is polled): Mason makes one replication slot for the project and its own publication, carrying the replicated
58
+ tables alone, and drops them, verified gone, when no table's sync is replication any more (and at mason destroy).
59
+ publication: <name> at the top of the sources file (\${PG_PUBLICATION} reads it from .env) names a publication that
60
+ is the database owner's: it is read as it is, verified to carry every replicated table and column, and never
61
+ created, altered or dropped — Mason makes only its slot. A read replica needs one (it takes no CREATE PUBLICATION).
62
+
63
+ --replication, --poll and --publication are gone: each answers with the line to write at the top of the sources
64
+ file (sync: replication, sync: poll 5m, publication: <name>) and exit 2, and starts nothing.
45
65
 
46
66
  The Postgres API's port is the project's: the first start takes 5433, or the next port free on every address when a
47
67
  second Postgres holds it, and records it; every later start listens on the same port, so a BI tool's saved connection
@@ -53,53 +73,56 @@ export const DEV = {
53
73
  MASON_PG_TLS_KEY (PEM files), or --self-signed-tls, which makes a certificate for the project in .mason/ and serves
54
74
  it again at every later start with the flag. Every client then connects with sslmode=require. A team whose own proxy
55
75
  or load balancer terminates TLS keeps it on 127.0.0.1, or sets MASON_PG_PLAIN=1 for a private network.
76
+ Behind a TCP proxy that sends the PROXY protocol v2, MASON_PG_PROXY_FROM=<cidr>[,<cidr>…] names the proxy's own
77
+ addresses: a connection from one must begin with the header, whose client the request log names, and a header from
78
+ any other address is refused.
56
79
 
57
80
  ${PROJECT_LINE}
58
81
 
59
82
  mason start
60
83
  mason start --attach in the foreground: Ctrl-C stops it
61
- mason start --replication read the change log, as production does
62
- mason start --replication --publication app_pub through the database owner's publication
63
84
  MASON_PG_HOST=0.0.0.0 mason start --self-signed-tls the Postgres API for other machines, over TLS
64
85
 
65
86
  --attach stay in the foreground (Ctrl-C stops Mason)
66
- --replication read the database's changes from its change log (logical replication), as production does
67
- --publication <name> with --replication: the publication to read, the owner's, never altered (PG_PUBLICATION;
68
- default: Mason's own, mason_dev_<project>_<source>)
69
- --poll read them with SELECTs (the default; takes back --replication)
70
87
  --self-signed-tls the Postgres API requires TLS, from a certificate made for this project in .mason/ (kept and
71
88
  served again by every later start with the flag); not with MASON_PG_TLS_CERT / _KEY
72
- --port <n> the port the mason commands reach it on (default 4039, or the next free one)
89
+ --port <n> the port the mason commands reach it on (default ${DEFAULT_UI_PORT}, or the next free one)
73
90
  --json as data, once it is up: { running, pid, postgres: { url, host, port, database, sslmode }, next }
74
91
  `,
75
92
  options: {
76
93
  port: { type: "string" }, attach: { type: "boolean", default: false }, json: { type: "boolean", default: false },
77
- replication: { type: "boolean", default: false }, poll: { type: "boolean", default: false }, publication: { type: "string" },
78
94
  "self-signed-tls": { type: "boolean", default: false },
79
95
  },
80
96
  json: ["running", "pid", "postgres", "next"],
81
97
  };
82
98
  async function runDev(argv) {
99
+ // --replication, --poll and --publication are gone (issue 33): each answered with the edit to make in the sources
100
+ // file, exit 2, before the options are parsed (it would be an unknown option there) and before anything starts. The
101
+ // file is read only when one is given. A decision: `mason start --replication --help` answers the refusal, not the
102
+ // usage — the flag is wrong whatever else the line asks
103
+ const removed = removedStartFlags(argv, () => startSourcesTop(process.cwd()));
104
+ if (removed !== null)
105
+ return failed("start", { code: "usage", ...removed, next: [["mason start", "each table kept current as its sync: says"], ["mason help production", "sync: and publication:, and what each needs"]] }, { json: wantsJson(argv, DEV) });
83
106
  const f = parseCommand(DEV, argv);
84
107
  if (typeof f === "number")
85
108
  return f;
86
109
  const v = f.values;
87
- if (v.replication && v.poll)
88
- return failed("start", { message: "--replication or --poll, not both", code: "usage", usage: DEV.usage }, { json: v.json });
89
- if (v.publication !== undefined && v.poll)
90
- return failed("start", { message: "--publication goes with --replication, not --poll", code: "usage", usage: DEV.usage }, { json: v.json });
91
- if (v.publication !== undefined && !/^\S+$/.test(v.publication))
92
- return failed("start", { message: `--publication must name a publication, not ${JSON.stringify(v.publication)}`, code: "usage", usage: DEV.usage }, { json: v.json });
93
110
  if (f.positionals.length) {
94
111
  // the database is the project's now: a URL here is the old habit, answered with the new way
95
112
  return failed("start", { message: "it takes no database URL: the project has one", code: "usage", why: "mason init <url> made it; .mason/config.json holds it", usage: DEV.usage }, { json: v.json });
96
113
  }
97
- const port = v.port === undefined ? 4039 : Number(v.port);
114
+ const port = v.port === undefined ? DEFAULT_UI_PORT : Number(v.port);
98
115
  if (!Number.isInteger(port) || port < 1 || port > 65_535)
99
116
  return failed("start", { message: `--port must be a port number (1-65535), not ${JSON.stringify(v.port)}`, code: "usage" }, { json: v.json });
100
117
  if (!v.attach)
101
- return devInBackground(argv, { json: v.json, selfSignedTls: v["self-signed-tls"], changes: v.replication ? "replication" : v.poll ? "poll" : null });
102
- return dev({ port, version: version(), changes: v.replication ? "replication" : v.poll ? "poll" : null, publication: v.publication ?? null, selfSignedTls: v["self-signed-tls"] });
118
+ return devInBackground(argv, { json: v.json, selfSignedTls: v["self-signed-tls"] });
119
+ return dev({ port, version: version(), selfSignedTls: v["self-signed-tls"] });
120
+ }
121
+ /** The project's sources file as a person names it (`mason/sources/app.yml`), for a removed flag's answer: the one file
122
+ * of the project of this folder, else the name every project's has. */
123
+ function startSourcesTop(cwd) {
124
+ const t = theProject(cwd);
125
+ return "problem" in t ? { shown: "mason/sources/<name>.yml", sync: null, publication: null } : sourcesTop(t.project.root, t.project.folder);
103
126
  }
104
127
  export const HELP = {
105
128
  name: "help",
@@ -152,9 +175,9 @@ export const STEPS = [
152
175
  ["Learn", [{ ...HELP, run: runHelp }]],
153
176
  ["Set up", [{ ...DOCTOR, run: (argv) => doctor(argv) }, { ...INIT, run: (argv) => init(argv) }, { ...DEV, run: runDev }]],
154
177
  ["Add your tables", [{ ...TABLES, run: tables }, { ...ADD, run: add }, { ...REMOVE, run: remove }, { ...WAIT, run: wait }]],
155
- ["Connect", [{ ...USER, run: user }, { ...MCP, run: mcp }]],
156
- ["Run", [{ ...STATUS, run: status }, { ...UI, run: ui }, { ...SYNC, run: sync }, { ...LOGS, run: logs }, { ...STOP, run: stop }, { ...DESTROY, run: (argv) => destroy(argv) }]],
157
- ["Go to production", [{ ...SERVE, run: serve }]],
178
+ ["Connect", [{ ...USER, run: user }, { ...MCP, run: mcp }, { ...SHARE, run: share }]],
179
+ ["Run", [{ ...STATUS, run: status }, { ...UI, run: ui }, { ...SYNC, run: sync }, { ...LOGS, run: logs }, { ...CANCEL, run: cancel }, { ...USAGE, run: (argv) => usage(argv) }, { ...STOP, run: stop }, { ...DESTROY, run: (argv) => destroy(argv) }]],
180
+ ["Go to production", [{ ...SERVE, run: serve }, { ...LOGIN, run: login }, { ...LOGOUT, run: logout }, { ...REPLICA, run: replica }, { ...TOKEN, run: token }, { ...DEPLOY, run: deploy }]],
158
181
  ];
159
182
  /** Every command, in the order `mason --help` lists them. */
160
183
  export const COMMANDS = STEPS.flatMap(([, commands]) => commands);
@@ -164,6 +187,9 @@ export const COMMANDS = STEPS.flatMap(([, commands]) => commands);
164
187
  * it, then the next command for that moment — and mason help, every command. `mason help` and `mason --help` are the
165
188
  * whole list (topUsage).
166
189
  */
190
+ /** WHAT MASON IS, in one line: the first `mason` alone and `mason --help` print, in the words its reader was told it
191
+ * in ("a drop-in read replica for analytics"). */
192
+ export const TAGLINE = "Mason: a drop-in read replica of your Postgres, for analytics — the tables you add, synced, kept current and served to psql, BI tools and your app.";
167
193
  export async function orientation(cwd, s = styleOut()) {
168
194
  const t = theProject(cwd);
169
195
  const project = "project" in t ? t.project : null;
@@ -172,12 +198,12 @@ export async function orientation(cwd, s = styleOut()) {
172
198
  ? [["Project", `${project.config.name}, files in ${project.folder}`], ["Mason", run ? `running (pid ${run.pid})` : "not running"]]
173
199
  : [["Project", "none here: mason init makes this folder one"]];
174
200
  const next = !project
175
- ? [["mason init", "make this folder a project (DATABASE_URL from .env, or mason init postgres://…)"],
201
+ ? [["mason init", "make this folder a project (MASON_DATABASE_URL or DATABASE_URL from .env, or mason init postgres://…)"],
176
202
  ["mason init demo", "or try it on a demo database Mason makes itself: nothing to install"],
177
203
  ["mason doctor", "what this machine needs for Mason, and whether it has it"]]
178
204
  : !run ? [["mason start", "start Mason for this project, in the background"], ["mason status", "the project and its tables"]]
179
205
  : [["mason status", "what Mason is doing: its tables, and the last day's slow and refused statements"], ["mason user add <name>", "a user for a BI tool or your app: its password and URL, printed once"]];
180
- return `${reportText({ headline: "Mason: your Postgres tables, copied and served to BI tools and your app.", details: rows,
206
+ return `${reportText({ headline: TAGLINE, details: rows,
181
207
  next: [...next, ["mason help", "every command, and the guides"]] }, s)}\n`;
182
208
  }
183
209
  /** `mason --help`: what Mason is, every command with its summary, and where each one's help is. On a terminal the steps are
@@ -204,8 +230,9 @@ export function topUsage(s = styleOut()) {
204
230
  return [
205
231
  `${s.bold("usage:")} mason <command> [options]`,
206
232
  "",
207
- para("Mason: your Postgres tables, copied and served to BI tools and your app. Make your app's folder a project (mason init), " +
233
+ para(`${TAGLINE} Make your app's folder a project (mason init), ` +
208
234
  `add tables (mason add, before or after the start), start Mason (mason start), connect a tool (mason user add). It needs Node ${NODE_MIN} or later; mason doctor checks.`),
235
+ para("Try it first with nothing to install: mason init demo, then mason start and mason ui (a demo shop database, hosted by Mason itself)."),
209
236
  para(PROJECT_LINE),
210
237
  "",
211
238
  listed.map(([step, es]) => ` ${s.bold(step)}\n${block(es)}`).join("\n\n"),
@@ -214,7 +241,7 @@ export function topUsage(s = styleOut()) {
214
241
  para("A command means the project of the folder it runs in (or above it)."),
215
242
  para("Every command but help and mcp (whose stdout is the MCP protocol's) takes --json: one JSON object for a program, always with next (what to run " +
216
243
  "next); a failure is { error: { code, message, why, fix }, next }. The exit code says how it went: 0 done, 1 failed " +
217
- "with the reason, 2 a wrong command line, 124 mason wait timed out. Driving Mason from a coding agent: mason help agent."),
244
+ "with the reason, 2 a wrong command line, 124 a wait timed out (mason wait, mason deploy, mason replica create). Driving Mason from a coding agent: mason help agent."),
218
245
  "",
219
246
  ].join("\n");
220
247
  }