@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 +3 -3
- package/src/cli.js +418 -127
- package/src/cli.test.js +81 -1
- package/src/commands/indexes.js +306 -0
- package/src/commands/indexes.test.js +298 -0
- package/CLAUDE.md +0 -330
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepsql/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "DeepSQL CLI and stdio MCP server for self-hosted deployments",
|
|
5
5
|
"bin": {
|
|
6
|
-
"deepsql": "
|
|
7
|
-
"deepsql-mcp": "
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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]
|
|
275
|
-
stdout.write(`${
|
|
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${
|
|
571
|
+
stderr.write(`Unknown command: ${command}\n\n${renderRootHelp(colorEnabled(stderr, rawArgv))}\n`);
|
|
288
572
|
return 2;
|
|
289
573
|
}
|
|
290
574
|
|
|
291
|
-
|
|
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 };
|