@deepsql/mcp 0.10.1 → 0.11.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.
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@deepsql/mcp",
3
- "version": "0.10.1",
3
+ "version": "0.11.0",
4
4
  "description": "DeepSQL CLI and stdio MCP server for self-hosted deployments",
5
5
  "bin": {
6
- "deepsql": "./bin/deepsql.js",
7
- "deepsql-mcp": "./deepsql-phase1-server.js"
6
+ "deepsql": "bin/deepsql.js",
7
+ "deepsql-mcp": "deepsql-phase1-server.js"
8
8
  },
9
9
  "main": "./deepsql-phase1-server.js",
10
10
  "files": [
package/src/cli.js CHANGED
@@ -10,6 +10,7 @@
10
10
  * - value flags: --url <url>, --token <t>, --connection <name>, --limit 50
11
11
  * - subcommands: deepsql connections list, deepsql config show
12
12
  * - positional: deepsql brain-context "which tables hold orders?"
13
+ * - per-command -h/--help: prints that command's usage block.
13
14
  */
14
15
 
15
16
  const COMMANDS = {
@@ -29,6 +30,7 @@ const COMMANDS = {
29
30
  relationships: () => require("./commands/relationships"),
30
31
  "anti-patterns": () => require("./commands/anti-patterns"),
31
32
  digest: () => require("./commands/digest"),
33
+ indexes: () => require("./commands/indexes"),
32
34
  users: () => require("./commands/users"),
33
35
  access: () => require("./commands/access"),
34
36
  permissions: () => require("./commands/permissions"),
@@ -36,128 +38,400 @@ const COMMANDS = {
36
38
  setup: () => require("./commands/setup"),
37
39
  };
38
40
 
39
- const HELP = `deepsql ask the database what's wrong. It already knows.
40
-
41
- Self-hosted AI for database performance: profile slow queries, stream
42
- live optimization plans, audit access, and run day-to-day admin ops
43
- all from the terminal, without leaving your VPC.
44
-
45
- Usage:
46
- deepsql <command> [options]
47
-
48
- Commands:
49
- login [--url <url>] [--device|--browser|--password] [--no-browser] [--label <name>]
50
- [--email <email>] [--password-stdin]
51
- Authorize this CLI with a DeepSQL instance.
52
- Default: browser callback (PKCE), or device-code
53
- on headless boxes. Use --password for direct
54
- email+password login on a fresh self-host VM
55
- with no browser; pipe the password via stdin
56
- with --password-stdin for non-interactive use.
57
- logout [--url <url>] Revoke and forget the saved token.
58
- whoami Show the user behind the saved token.
59
- config show List saved profiles.
60
- config set-default <url> Set the default profile.
61
- config path Print the auth file path.
62
- mcp Run the stdio MCP server using the saved token.
63
- connections list [--json] List database connections (active default
64
- is marked with \`*\`).
65
- connections use <name> Pin <name> as the active default; commands
66
- drop --connection from then on.
67
- connections current Print the active default (exit 1 if none).
68
- connections unset Clear the active default for this profile.
69
- connections schema [--json] Print the JSON Schema for the connection
70
- config (the input format for \`add\`).
71
- connections add [--from-file <p>] [--from-stdin] [--upsert] [--no-test]
72
- [--wait] [--delete-after] [--cloud]
73
- [--allow-plaintext-secrets]
74
- Create a connection. Default is interactive
75
- prompts; use --from-file for AI-agent flows.
76
- connections update <name> --from-file <p>
77
- PATCH-style update; omitted secrets are
78
- preserved.
79
- connections remove <name> [--yes]
80
- Delete a connection (DELETE /connections).
81
- connections test [<name> | --from-file <p>]
82
- Validate a connection without saving.
83
- Prints the privilege report.
84
- connections show <name> [--json]
85
- Show a connection's config (secrets masked).
86
- connections init <name> [--force] [--wait]
87
- Trigger brain re-initialization.
88
- query "<sql>" --connection <name> [--limit <n>] [--timeout-seconds <n>] [--file <path>] [--json]
89
- Run a read-only SQL statement. Enforced
90
- read-only at the backend (parser-level) and
91
- ACL-checked per connection.
92
- explain "<sql>" --connection <name> [--file <path>] [--json]
93
- Get an EXPLAIN plan (no ANALYZE — also
94
- read-only enforced).
95
- schema [tables|objects] --connection <name>
96
- Dump connection schema or database objects
97
- as JSON.
98
- digest [N] [--connection <name>] [--json]
99
- Show the latest DeepSQL digest, or pass a
100
- number to list the last N (e.g. digest 5).
101
- digest list [--count N] [--connection <name>] [--json]
102
- Explicit list form (same as digest <N>).
103
- digest show <id> [--connection <name>] [--json]
104
- Show one digest by id.
105
-
106
- Brain commands (give a coding agent DeepSQL's retrieved context — agentless V1):
107
- brain-context "<question>" --connection <name> [--top-k <n>] [--json]
108
- Retrieve embedding-ranked tables/columns/FKs,
109
- training docs, and business rules for a
110
- question. With --top-k, returns ranked
111
- diagnostic snippets; otherwise returns the
112
- rich training-context payload.
113
- business-rules --connection <name> [--question "..."] [--json]
114
- List active business rules and SQL guardrails
115
- for a connection (optionally scoped by
116
- question).
117
- relationships --connection <name> [--json]
118
- Inferred and validated foreign-key
119
- relationships with confidence scores.
120
- anti-patterns --connection <name> [--kind table|query] [--limit <n>] [--json]
121
- Schema-level (table) or query-level
122
- anti-patterns detected by the brain.
123
-
124
- Admin commands (require ADMIN role on the calling token):
125
- users list | get <ref> | add [<email>] [--role <r>] [--name <n>] [--password-stdin]
126
- | set-role <ref> <role> | lock|unlock|disable <ref>
127
- | resend-invite <ref> | reset-password <ref> [--password-stdin]
128
- | delete <ref> [--yes]
129
- Manage workspace users.
130
- access list --user <ref> | --connection <name>
131
- | grant --user <ref> --connection <name> --level read|write|admin
132
- | revoke --user <ref> --connection <name>
133
- | policy <user> <connection> (opens $EDITOR)
134
- Per-connection access grants and chat policies.
135
- permissions list [--role <ROLE>] [--json]
136
- | override --role <ROLE> --permission <PERM> --grant|--revoke [--reason "..."]
137
- | reset --role <ROLE> --permission <PERM>
138
- Role-based permission overrides.
139
- slow-queries latest --connection <name>
140
- | history --connection <name> [N]
141
- | analyze --connection <name> [--time-range LAST_24_HOURS|LAST_HOUR]
142
- [--threshold-ms <n>] [--limit <n>]
143
- | optimize --connection <name> --query-id <id>
144
- (streams AI optimization steps to stderr; result to stdout)
145
- | delete (--history-id <id> | --connection <name>) [--yes]
146
- Read, trigger, and clean up slow-query analyses.
147
- setup [--skip-email] [--skip-slack] [--skip-complete]
148
- Post-install wizard: SMTP/email, Slack
149
- (digests + bot), then mark setup complete.
150
- Org and LLM config are set at install time
151
- and are NOT touched by this wizard.
152
-
153
- Global options:
154
- --url <url> Override the DeepSQL base URL.
155
- --token <tok> Override the auth token (also: DEEPSQL_AUTH_TOKEN).
156
- --connection <name> Override the active connection (also: DEEPSQL_CONNECTION,
157
- or pin one with \`deepsql connections use <name>\`).
158
- -h, --help Show help.
159
- -v, --version Show version.
160
- `;
41
+ // ─── Top-level command catalog (rendered into the root help) ────────────────
42
+ //
43
+ // `sub: true` marks commands that have nested subcommands. They get a `*`
44
+ // suffix in the listing and reveal their full usage via `<command> --help`.
45
+
46
+ const COMMAND_LIST = [
47
+ ["login", false, "Authorize this CLI with a DeepSQL instance"],
48
+ ["logout", false, "Revoke and forget the saved token"],
49
+ ["whoami", false, "Show the user behind the saved token"],
50
+ ["config", true, "Manage saved CLI profiles"],
51
+ ["mcp", false, "Run the stdio MCP server using the saved token"],
52
+ ["connections", true, "Manage database connections"],
53
+ ["query", false, "Run a read-only SQL statement"],
54
+ ["explain", false, "Get an EXPLAIN plan (no ANALYZE)"],
55
+ ["schema", false, "Dump connection schema or DB objects as JSON"],
56
+ ["digest", true, "Show DeepSQL daily digests"],
57
+ ["brain-context", false, "Retrieve embedding-ranked context for a question"],
58
+ ["business-rules", false, "List active business rules and SQL guardrails"],
59
+ ["relationships", false, "List inferred and validated FK relationships"],
60
+ ["anti-patterns", false, "List schema- or query-level anti-patterns"],
61
+ ["indexes", true, "Index suggestions, usage, and health (read-only)"],
62
+ ["users", true, "Manage workspace users (admin)"],
63
+ ["access", true, "Manage per-connection access grants (admin)"],
64
+ ["permissions", true, "Manage role-based permission overrides (admin)"],
65
+ ["slow-queries", true, "Read, trigger, and stream slow-query analyses (admin)"],
66
+ ["setup", false, "Post-install wizard for SMTP/email and Slack"],
67
+ ];
68
+
69
+ const GLOBAL_OPTIONS = [
70
+ ["--url <url>", "Override the DeepSQL base URL"],
71
+ ["--token <tok>", "Override the auth token (also: DEEPSQL_AUTH_TOKEN)"],
72
+ ["--connection <name>", "Override the active connection (also: DEEPSQL_CONNECTION)"],
73
+ ["--no-color", "Disable ANSI colors"],
74
+ ["-h, --help", "Display help for command"],
75
+ ["-v, --version", "Show version"],
76
+ ];
77
+
78
+ // ─── Per-command help blocks ────────────────────────────────────────────────
79
+ //
80
+ // Each entry is { description, usage, subcommands?, options?, notes? }.
81
+ // Subcommands and options render as two-column tables (name | description).
82
+ // Centralized here so individual command modules don't each carry a HELP
83
+ // constant; the dispatcher renders them on `<command> --help`.
84
+
85
+ const COMMAND_HELP = {
86
+ login: {
87
+ description: "Authorize this CLI with a DeepSQL instance.",
88
+ usage: "deepsql login [options]",
89
+ options: [
90
+ ["--url <url>", "DeepSQL base URL to authorize against"],
91
+ ["--browser", "Use browser callback (PKCE) default on desktops"],
92
+ ["--device", "Use device-code flow default on headless boxes"],
93
+ ["--password", "Direct email+password login (fresh self-host VMs)"],
94
+ ["--no-browser", "Force device-code even on a desktop"],
95
+ ["--email <email>", "Email for --password flow"],
96
+ ["--password-stdin", "Read password from stdin (non-interactive)"],
97
+ ["--label <name>", "Label the saved token for `whoami`"],
98
+ ],
99
+ },
100
+
101
+ logout: {
102
+ description: "Revoke and forget the saved token.",
103
+ usage: "deepsql logout [--url <url>]",
104
+ options: [
105
+ ["--url <url>", "Profile to log out of (default: current default profile)"],
106
+ ],
107
+ },
108
+
109
+ whoami: {
110
+ description: "Show the user behind the saved token.",
111
+ usage: "deepsql whoami [--url <url>]",
112
+ },
113
+
114
+ mcp: {
115
+ description: "Run the stdio MCP server using the saved token.",
116
+ usage: "deepsql mcp [--url <url>]",
117
+ notes: "Used by editor MCP configs (Cursor, Claude Desktop) so the config never has to embed a raw token.",
118
+ },
119
+
120
+ config: {
121
+ description: "Manage saved CLI profiles (one per DeepSQL URL).",
122
+ usage: "deepsql config <subcommand> [options]",
123
+ subcommands: [
124
+ ["show", "List saved profiles (default)"],
125
+ ["set-default <url>", "Set the default profile"],
126
+ ["path", "Print the auth file path"],
127
+ ],
128
+ },
129
+
130
+ connections: {
131
+ description: "Manage database connections — list, pin, full CRUD.",
132
+ usage: "deepsql connections <subcommand> [options]",
133
+ subcommands: [
134
+ ["list [--json]", "List database connections (active default marked with *)"],
135
+ ["use <name>", "Pin <name> as the active default for this profile"],
136
+ ["current", "Print the active default (exit 1 if none)"],
137
+ ["unset", "Clear the active default for this profile"],
138
+ ["schema [--json]", "Print the JSON Schema for the connection config"],
139
+ ["add [options]", "Create a connection (interactive or from JSON)"],
140
+ ["update <name> --from-file <p>", "PATCH-style update; omitted secrets preserved"],
141
+ ["remove <name> [--yes]", "Delete a connection (DELETE /connections)"],
142
+ ["test [<name> | --from-file <p>]", "Validate a connection without saving"],
143
+ ["show <name> [--json]", "Show a connection's config (secrets masked)"],
144
+ ["init <name> [--force] [--wait]", "Trigger brain re-initialization"],
145
+ ],
146
+ options: [
147
+ ["--from-file <p>", "Read connection JSON from file"],
148
+ ["--from-stdin", "Read connection JSON from stdin"],
149
+ ["--upsert", "PUT instead of POST on add if name collision"],
150
+ ["--no-test", "Skip pre-save POST /connections/test"],
151
+ ["--wait", "Poll brain init to COMPLETED/FAILED"],
152
+ ["--delete-after", "rm the --from-file path on success"],
153
+ ["--cloud", "Prompt for cloud/instance metadata"],
154
+ ["--allow-plaintext-secrets", "Allow plaintext secrets in JSON input"],
155
+ ["--json", "Print JSON output where supported"],
156
+ ["--yes", "Assume yes for confirmations"],
157
+ ],
158
+ },
159
+
160
+ query: {
161
+ description: "Run a read-only SQL statement (parser-enforced, ACL-checked).",
162
+ usage: 'deepsql query "<sql>" --connection <name> [options]',
163
+ options: [
164
+ ["--connection <name>", "Connection to run against"],
165
+ ["--limit <n>", "Row limit (1–1000, default 100)"],
166
+ ["--timeout-seconds <n>", "Statement timeout in seconds (1–60)"],
167
+ ["--file <path>", "Read SQL from a file instead of argv"],
168
+ ["--json", "Raw JSON output"],
169
+ ],
170
+ },
171
+
172
+ explain: {
173
+ description: "Get an EXPLAIN plan (no ANALYZE — read-only enforced).",
174
+ usage: 'deepsql explain "<sql>" --connection <name> [options]',
175
+ options: [
176
+ ["--connection <name>", "Connection to run against"],
177
+ ["--file <path>", "Read SQL from a file instead of argv"],
178
+ ["--json", "Raw JSON output"],
179
+ ],
180
+ },
181
+
182
+ schema: {
183
+ description: "Dump connection schema or database objects as JSON.",
184
+ usage: "deepsql schema [tables|objects] --connection <name>",
185
+ subcommands: [
186
+ ["tables", "Tables, columns, FKs (default)"],
187
+ ["objects", "Database objects (views, procedures, etc.)"],
188
+ ],
189
+ },
190
+
191
+ digest: {
192
+ description: "Surface the daily DeepSQL digest from the terminal.",
193
+ usage: "deepsql digest [N|<subcommand>] [options]",
194
+ subcommands: [
195
+ ["(no args)", "Show the most recent digest, full body"],
196
+ ["<N>", "List the last N digests, compact"],
197
+ ["list [--count N]", "Explicit list form (same as digest <N>)"],
198
+ ["show <id>", "Show one digest by id"],
199
+ ],
200
+ options: [
201
+ ["--connection <name>", "Filter to one connection"],
202
+ ["--json", "Raw JSON output"],
203
+ ],
204
+ },
205
+
206
+ "brain-context": {
207
+ description: "Retrieve embedding-ranked tables/columns/FKs, training docs, and business rules for a question.",
208
+ usage: 'deepsql brain-context "<question>" --connection <name> [options]',
209
+ options: [
210
+ ["--connection <name>", "Connection to retrieve from"],
211
+ ["--top-k <n>", "Return ranked diagnostic snippets (else: rich training payload)"],
212
+ ["--json", "Raw JSON output"],
213
+ ],
214
+ },
215
+
216
+ "business-rules": {
217
+ description: "List active business rules and SQL guardrails for a connection.",
218
+ usage: "deepsql business-rules --connection <name> [options]",
219
+ options: [
220
+ ["--connection <name>", "Connection to inspect"],
221
+ ['--question "..."', "Scope rules to a specific question"],
222
+ ["--json", "Raw JSON output"],
223
+ ],
224
+ },
225
+
226
+ relationships: {
227
+ description: "Inferred and validated foreign-key relationships with confidence scores.",
228
+ usage: "deepsql relationships --connection <name> [--json]",
229
+ options: [
230
+ ["--connection <name>", "Connection to inspect"],
231
+ ["--json", "Raw JSON output"],
232
+ ],
233
+ },
234
+
235
+ "anti-patterns": {
236
+ description: "Schema-level (table) or query-level anti-patterns detected by the brain.",
237
+ usage: "deepsql anti-patterns --connection <name> [options]",
238
+ options: [
239
+ ["--connection <name>", "Connection to inspect"],
240
+ ["--kind table|query", "Anti-pattern flavor (default: table)"],
241
+ ["--limit <n>", "Limit results (query mode)"],
242
+ ["--json", "Raw JSON output"],
243
+ ],
244
+ },
245
+
246
+ indexes: {
247
+ description: "Index suggestions, usage, and health — read-only in V1.",
248
+ usage: "deepsql indexes <subcommand> [options]",
249
+ subcommands: [
250
+ ["list [--all] [--status <s>]", "Index recommendations (defaults to PENDING)"],
251
+ ["missing", "Missing-index suggestions from the advisor"],
252
+ ["health", "Comprehensive index health report"],
253
+ ["unused", "Indexes the engine has not used"],
254
+ ["duplicates", "Duplicate or redundant indexes"],
255
+ ["usage <table>", "Per-table index usage statistics"],
256
+ ],
257
+ options: [
258
+ ["--connection <name>", "Connection to inspect"],
259
+ ["--all", "Include APPLIED and DISMISSED in `list`"],
260
+ ["--status PENDING|APPLIED|DISMISSED", "Filter `list` by status"],
261
+ ["--json", "Raw JSON output"],
262
+ ],
263
+ notes: "V1 is read-only. Apply, dismiss, delete, and generate are not exposed by the CLI yet.",
264
+ },
265
+
266
+ users: {
267
+ description: "Manage workspace users (admin).",
268
+ usage: "deepsql users <subcommand> [options]",
269
+ subcommands: [
270
+ ["list [--json]", "List all users"],
271
+ ["get <ref>", "Show one user by email or id"],
272
+ ["add [<email>] [options]", "Invite or create a user"],
273
+ ["set-role <ref> <role>", "Change a user's role"],
274
+ ["lock <ref>", "Lock a user account"],
275
+ ["unlock <ref>", "Unlock a user account"],
276
+ ["disable <ref>", "Disable a user account"],
277
+ ["resend-invite <ref>", "Resend an invitation email"],
278
+ ["reset-password <ref>", "Reset a user's password"],
279
+ ["delete <ref> [--yes]", "Permanently delete a user"],
280
+ ],
281
+ options: [
282
+ ["--role <r>", "Role for `add` / `set-role`"],
283
+ ["--name <n>", "Display name for `add`"],
284
+ ["--password-stdin", "Read password from stdin"],
285
+ ["--json", "Raw JSON output (list)"],
286
+ ["--yes", "Assume yes for confirmations"],
287
+ ],
288
+ notes: "All endpoints under /admin/users/** require ROLE_ADMIN.",
289
+ },
290
+
291
+ access: {
292
+ description: "Per-connection access grants and chat policies (admin).",
293
+ usage: "deepsql access <subcommand> [options]",
294
+ subcommands: [
295
+ ["list --user <ref>", "Connections this user can see"],
296
+ ["list --connection <name>", "Users who can see this connection"],
297
+ ["grant --user <ref> --connection <name> --level <lvl>", "Grant access (read|write|admin)"],
298
+ ["revoke --user <ref> --connection <name>", "Revoke access"],
299
+ ["policy <user> <connection>", "Edit chat policy in $EDITOR"],
300
+ ],
301
+ },
302
+
303
+ permissions: {
304
+ description: "Global role-based permission overrides (admin).",
305
+ usage: "deepsql permissions <subcommand> [options]",
306
+ subcommands: [
307
+ ["list [--role <r>] [--json]", "List permissions, optionally filtered by role"],
308
+ ["override --role <r> --permission <p> --grant|--revoke [--reason \"...\"]", "Override a role's permission"],
309
+ ["reset --role <r> --permission <p>", "Remove an override (revert to default)"],
310
+ ],
311
+ },
312
+
313
+ "slow-queries": {
314
+ description: "Read, trigger, and stream slow-query analyses (admin).",
315
+ usage: "deepsql slow-queries <subcommand> [options]",
316
+ subcommands: [
317
+ ["latest --connection <name>", "Most recent analysis for the connection"],
318
+ ["history --connection <name> [N]", "Last N analyses"],
319
+ ["analyze --connection <name> [options]", "Trigger a new analysis"],
320
+ ["optimize --connection <name> --query-id <id>", "Stream AI optimization steps live (SSE)"],
321
+ ["delete (--history-id <id> | --connection <name>) [--yes]", "Clean up history"],
322
+ ],
323
+ options: [
324
+ ["--time-range LAST_24_HOURS|LAST_HOUR", "Window for analyze"],
325
+ ["--threshold-ms <n>", "Min duration to consider"],
326
+ ["--limit <n>", "Cap results"],
327
+ ["--json", "Raw JSON output"],
328
+ ],
329
+ notes: "`optimize` follows the SSE protocol from /slow-queries/optimize/stream — step events go to stderr, the final result to stdout. Honors SIGINT.",
330
+ },
331
+
332
+ setup: {
333
+ description: "Post-install wizard: SMTP/email, Slack (digests + bot), then mark setup complete.",
334
+ usage: "deepsql setup [options]",
335
+ options: [
336
+ ["--skip-email", "Skip the SMTP/email step"],
337
+ ["--skip-slack", "Skip the Slack step"],
338
+ ["--skip-complete", "Don't mark setup complete at the end"],
339
+ ],
340
+ notes: "Org and LLM config are set at install time and are NOT touched by this wizard.",
341
+ },
342
+ };
343
+
344
+ // ─── Color helpers ──────────────────────────────────────────────────────────
345
+
346
+ function colorEnabled(stream, rawArgv) {
347
+ if (process.env.NO_COLOR) return false;
348
+ if (rawArgv && (rawArgv.includes("--no-color") || rawArgv.includes("--no-colors"))) return false;
349
+ if (process.env.FORCE_COLOR === "1") return true;
350
+ return !!(stream && stream.isTTY);
351
+ }
352
+
353
+ function paint(useColor) {
354
+ if (!useColor) {
355
+ const id = (s) => s;
356
+ return { brand: id, accent: id, bold: id, dim: id, reset: "" };
357
+ }
358
+ return {
359
+ brand: (s) => `\x1b[1;38;5;75m${s}\x1b[0m`, // bold sky-blue
360
+ accent: (s) => `\x1b[38;5;75m${s}\x1b[0m`, // sky-blue
361
+ bold: (s) => `\x1b[1m${s}\x1b[0m`,
362
+ dim: (s) => `\x1b[2m${s}\x1b[0m`,
363
+ reset: "\x1b[0m",
364
+ };
365
+ }
366
+
367
+ function pad(str, width) {
368
+ return str + " ".repeat(Math.max(0, width - str.length));
369
+ }
370
+
371
+ function renderTable(rows, c, { nameMin = 16, gap = 2 } = {}) {
372
+ const nameWidth = Math.max(nameMin, ...rows.map((r) => r[0].length));
373
+ return rows
374
+ .map(([name, desc]) => ` ${c.accent(pad(name, nameWidth))}${" ".repeat(gap)}${desc}`)
375
+ .join("\n");
376
+ }
377
+
378
+ // ─── Renderers ──────────────────────────────────────────────────────────────
379
+
380
+ function renderRootHelp(useColor) {
381
+ const c = paint(useColor);
382
+ const pkg = require("../package.json");
383
+
384
+ const header =
385
+ `${c.brand("🐬 DeepSQL")} ${c.accent(pkg.version)} ` +
386
+ c.dim("— ask the database what's wrong. It already knows.");
387
+
388
+ const usage = `${c.bold("Usage:")} deepsql [options] [command]`;
389
+
390
+ const optionsBlock =
391
+ `${c.bold("Options:")}\n` + renderTable(GLOBAL_OPTIONS, c, { nameMin: 22 });
392
+
393
+ const commandRows = COMMAND_LIST.map(([name, hasSub, desc]) => [
394
+ hasSub ? `${name} *` : name,
395
+ desc,
396
+ ]);
397
+ const commandsBlock =
398
+ `${c.bold("Commands:")}\n` +
399
+ ` ${c.dim("Hint: commands suffixed with * have subcommands. Run <command> --help for details.")}\n` +
400
+ renderTable(commandRows, c, { nameMin: 16 });
401
+
402
+ return [header, "", usage, "", optionsBlock, "", commandsBlock, ""].join("\n");
403
+ }
404
+
405
+ function renderCommandHelp(name, useColor) {
406
+ const c = paint(useColor);
407
+ const help = COMMAND_HELP[name];
408
+ if (!help) {
409
+ return `${c.bold("deepsql " + name)}\n No detailed help available — run \`deepsql --help\` for the command list.\n`;
410
+ }
411
+
412
+ const lines = [];
413
+ lines.push(`${c.brand("deepsql " + name)} ${c.dim("— " + help.description)}`);
414
+ lines.push("");
415
+ lines.push(`${c.bold("Usage:")} ${help.usage}`);
416
+ if (help.subcommands && help.subcommands.length > 0) {
417
+ lines.push("");
418
+ lines.push(`${c.bold("Subcommands:")}`);
419
+ lines.push(renderTable(help.subcommands, c, { nameMin: 24 }));
420
+ }
421
+ if (help.options && help.options.length > 0) {
422
+ lines.push("");
423
+ lines.push(`${c.bold("Options:")}`);
424
+ lines.push(renderTable(help.options, c, { nameMin: 24 }));
425
+ }
426
+ if (help.notes) {
427
+ lines.push("");
428
+ lines.push(c.dim(help.notes));
429
+ }
430
+ lines.push("");
431
+ return lines.join("\n");
432
+ }
433
+
434
+ // ─── Argv parser ────────────────────────────────────────────────────────────
161
435
 
162
436
  function parseArgs(argv) {
163
437
  const result = { positional: [], flags: {} };
@@ -248,6 +522,9 @@ function buildOpts(parsed) {
248
522
  queryText: f.queryText || null,
249
523
  sampleQuery: f.sampleQuery || null,
250
524
  historyId: f.historyId || null,
525
+ // Indexes
526
+ all: !!f.all,
527
+ status: f.status || null,
251
528
  // Setup wizard
252
529
  force: !!f.force,
253
530
  skipEmail: !!f.skipEmail,
@@ -264,15 +541,22 @@ function buildOpts(parsed) {
264
541
  deleteAfter: !!f.deleteAfter,
265
542
  cloud: !!f.cloud,
266
543
  allowPlaintextSecrets: !!f.allowPlaintextSecrets,
544
+ // Display
545
+ noColor: !!f.noColor,
267
546
  };
268
547
  }
269
548
 
549
+ function isHelpFlag(arg) {
550
+ return arg === "--help" || arg === "-h";
551
+ }
552
+
270
553
  async function main(rawArgv = process.argv.slice(2), io = {}) {
271
554
  const stderr = io.stderr || process.stderr;
272
555
  const stdout = io.stdout || process.stdout;
556
+ const useColor = colorEnabled(stdout, rawArgv);
273
557
 
274
- if (rawArgv.length === 0 || rawArgv[0] === "--help" || rawArgv[0] === "-h") {
275
- stdout.write(`${HELP}\n`);
558
+ if (rawArgv.length === 0 || isHelpFlag(rawArgv[0])) {
559
+ stdout.write(`${renderRootHelp(useColor)}\n`);
276
560
  return 0;
277
561
  }
278
562
  if (rawArgv[0] === "--version" || rawArgv[0] === "-v") {
@@ -284,11 +568,18 @@ async function main(rawArgv = process.argv.slice(2), io = {}) {
284
568
  const command = rawArgv[0];
285
569
  const loader = COMMANDS[command];
286
570
  if (!loader) {
287
- stderr.write(`Unknown command: ${command}\n\n${HELP}\n`);
571
+ stderr.write(`Unknown command: ${command}\n\n${renderRootHelp(colorEnabled(stderr, rawArgv))}\n`);
288
572
  return 2;
289
573
  }
290
574
 
291
- const parsed = parseArgs(rawArgv.slice(1));
575
+ // Per-command help: `deepsql <command> --help` / `-h` (anywhere in args).
576
+ const restArgs = rawArgv.slice(1);
577
+ if (restArgs.some(isHelpFlag)) {
578
+ stdout.write(`${renderCommandHelp(command, useColor)}\n`);
579
+ return 0;
580
+ }
581
+
582
+ const parsed = parseArgs(restArgs);
292
583
  const opts = buildOpts(parsed);
293
584
 
294
585
  try {
@@ -304,4 +595,4 @@ async function main(rawArgv = process.argv.slice(2), io = {}) {
304
595
  }
305
596
  }
306
597
 
307
- module.exports = { main, parseArgs, buildOpts };
598
+ module.exports = { main, parseArgs, buildOpts, renderRootHelp, renderCommandHelp, COMMAND_HELP };