@deepsql/mcp 0.6.0 → 0.10.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.
@@ -66,29 +66,104 @@ const TOOL_DEFINITIONS = [
66
66
  },
67
67
  },
68
68
  {
69
- name: "answer_question",
70
- description: "Ask DeepSQL a natural-language database question using the full chat pipeline.",
69
+ name: "get_brain_context",
70
+ description:
71
+ "Retrieve DeepSQL's brain context for a question: relevant tables, columns, FKs, training docs, business rules, anti-patterns, and embedding-ranked snippets. Use this to give your own coding agent the same retrieval context the DeepSQL agent uses, then have your agent generate the SQL/answer.",
71
72
  inputSchema: {
72
73
  type: "object",
73
74
  properties: {
74
- connectionId: {
75
+ connectionId: { type: "string", description: "DeepSQL connection ID." },
76
+ question: {
75
77
  type: "string",
76
- description: "DeepSQL connection ID.",
78
+ description: "Natural-language question used for retrieval ranking.",
79
+ },
80
+ topK: {
81
+ type: "integer",
82
+ minimum: 1,
83
+ maximum: 100,
84
+ description:
85
+ "Optional retrieval breadth. When provided, returns ranked diagnostic results from /training/retrieve; otherwise returns the rich /training/context payload.",
77
86
  },
87
+ },
88
+ required: ["connectionId", "question"],
89
+ additionalProperties: false,
90
+ },
91
+ },
92
+ {
93
+ name: "list_business_rules",
94
+ description:
95
+ "List active business rules and SQL guardrails for a connection. Optional `question` filters to rules applicable to that question.",
96
+ inputSchema: {
97
+ type: "object",
98
+ properties: {
99
+ connectionId: { type: "string", description: "DeepSQL connection ID." },
78
100
  question: {
79
101
  type: "string",
80
- description: "Natural-language database question.",
102
+ description: "Optional natural-language question to scope rules.",
81
103
  },
82
- chatId: {
104
+ },
105
+ required: ["connectionId"],
106
+ additionalProperties: false,
107
+ },
108
+ },
109
+ {
110
+ name: "get_relationships",
111
+ description:
112
+ "Get inferred and validated foreign-key relationships for a connection (source/target table+column with confidence and inference method).",
113
+ inputSchema: {
114
+ type: "object",
115
+ properties: {
116
+ connectionId: { type: "string", description: "DeepSQL connection ID." },
117
+ },
118
+ required: ["connectionId"],
119
+ additionalProperties: false,
120
+ },
121
+ },
122
+ {
123
+ name: "get_anti_patterns",
124
+ description:
125
+ "Get DeepSQL-detected anti-patterns. `kind=table` returns table/schema-level anti-patterns; `kind=query` returns query-level anti-patterns (with optional limit).",
126
+ inputSchema: {
127
+ type: "object",
128
+ properties: {
129
+ connectionId: { type: "string", description: "DeepSQL connection ID." },
130
+ kind: {
83
131
  type: "string",
84
- description: "Optional DeepSQL chat ID for conversation continuity.",
132
+ enum: ["table", "query"],
133
+ description: "Which anti-pattern catalog to fetch. Defaults to 'table'.",
85
134
  },
86
- userId: {
87
- type: "string",
88
- description: "Optional user ID for attribution in feedback/history.",
135
+ limit: {
136
+ type: "integer",
137
+ minimum: 1,
138
+ maximum: 500,
139
+ description: "Optional row limit (only used for kind='query').",
89
140
  },
90
141
  },
91
- required: ["connectionId", "question"],
142
+ required: ["connectionId"],
143
+ additionalProperties: false,
144
+ },
145
+ },
146
+ {
147
+ name: "analyze_slow_queries",
148
+ description:
149
+ "Analyze recent slow queries for a connection over the last 24 hours, returning fingerprints, durations, and example statements.",
150
+ inputSchema: {
151
+ type: "object",
152
+ properties: {
153
+ connectionId: { type: "string", description: "DeepSQL connection ID." },
154
+ thresholdMs: {
155
+ type: "number",
156
+ minimum: 1,
157
+ description: "Minimum query duration in milliseconds. Defaults to 100.",
158
+ },
159
+ limit: {
160
+ type: "integer",
161
+ minimum: 1,
162
+ maximum: 500,
163
+ description: "Maximum queries to return. Defaults to 10.",
164
+ },
165
+ },
166
+ required: ["connectionId"],
92
167
  additionalProperties: false,
93
168
  },
94
169
  },
@@ -374,18 +449,64 @@ function summarizeObjects(payload) {
374
449
  : "No database objects were returned.";
375
450
  }
376
451
 
377
- function summarizeChat(payload) {
378
- const lines = [];
379
- if (payload?.chatId) {
380
- lines.push(`chatId: ${payload.chatId}`);
452
+ function summarizeBrainContext(payload) {
453
+ if (!payload || typeof payload !== "object") {
454
+ return "Brain context unavailable.";
381
455
  }
382
- if (payload?.sql) {
383
- lines.push(`sql: ${compactWhitespace(payload.sql)}`);
456
+ // /training/retrieve diagnostic shape
457
+ if (Array.isArray(payload?.results) || payload?.totalResults != null) {
458
+ const total =
459
+ payload.totalResults ?? (Array.isArray(payload.results) ? payload.results.length : 0);
460
+ const tableCount = Array.isArray(payload.tablesCovered) ? payload.tablesCovered.length : 0;
461
+ return `Retrieved ${total} ranked snippet(s) covering ${tableCount} table(s).`;
384
462
  }
385
- if (payload?.message) {
386
- lines.push(`answer: ${payload.message}`);
463
+ // /training/context rich shape (RetrievedContextResult)
464
+ const tables = Array.isArray(payload.ragTableNames)
465
+ ? payload.ragTableNames.length
466
+ : payload.ragTableNames
467
+ ? Object.keys(payload.ragTableNames).length
468
+ : 0;
469
+ const types = payload.typeCounts
470
+ ? Object.entries(payload.typeCounts).map(([k, v]) => `${k}=${v}`).join(", ")
471
+ : "";
472
+ const intent = payload.retrievalIntent || "n/a";
473
+ const skipped = payload.skipped ? ` (skipped: ${payload.skipReason || "?"})` : "";
474
+ return `Brain context: intent=${intent}, topK=${payload.retrievalTopK ?? "?"}, results=${payload.resultCount ?? 0}, tables=${tables}${types ? `, types[${types}]` : ""}${skipped}.`;
475
+ }
476
+
477
+ function summarizeBusinessRules(payload) {
478
+ const active = payload?.activeRuleCount ?? (payload?.activeRules?.length ?? 0);
479
+ const guards = payload?.applicableGuardrailCount ?? (payload?.applicableGuardrails?.length ?? 0);
480
+ return `Business rules: ${active} active, ${guards} applicable guardrail(s).`;
481
+ }
482
+
483
+ function summarizeRelationships(payload) {
484
+ const list = Array.isArray(payload) ? payload : payload?.relationships || [];
485
+ const high = list.filter((r) => (r.confidence ?? 0) >= 0.8).length;
486
+ return `${list.length} relationship(s) (${high} high-confidence).`;
487
+ }
488
+
489
+ function summarizeAntiPatterns(payload, kind) {
490
+ if (kind === "table") {
491
+ const tables = payload && typeof payload === "object" ? Object.keys(payload).length : 0;
492
+ return `Anti-patterns across ${tables} table(s).`;
387
493
  }
388
- return lines.join("\n");
494
+ const list = Array.isArray(payload) ? payload : payload?.patterns || [];
495
+ const sev = list.reduce((acc, p) => {
496
+ const s = p.severity || "UNKNOWN";
497
+ acc[s] = (acc[s] || 0) + 1;
498
+ return acc;
499
+ }, {});
500
+ const sevStr = Object.entries(sev).map(([k, v]) => `${k}=${v}`).join(", ");
501
+ return `${list.length} query anti-pattern(s)${sevStr ? ` (${sevStr})` : ""}.`;
502
+ }
503
+
504
+ function summarizeSlowQueries(payload) {
505
+ const list = Array.isArray(payload?.queries) ? payload.queries : [];
506
+ const total = payload?.totalCount ?? list.length;
507
+ const avg = payload?.avgDurationMs;
508
+ const max = payload?.maxDurationMs;
509
+ return `${total} slow query/queries${avg != null ? `, avg=${avg}ms` : ""}${max != null ? `, max=${max}ms` : ""}.`;
389
510
  }
390
511
 
391
512
  function summarizeQueryResult(payload) {
@@ -405,7 +526,7 @@ function summarizeExplain(payload) {
405
526
  return `EXPLAIN completed for ${planType}.`;
406
527
  }
407
528
 
408
- function buildToolResult(name, payload) {
529
+ function buildToolResult(name, payload, extra = {}) {
409
530
  let summary;
410
531
 
411
532
  switch (name) {
@@ -418,8 +539,20 @@ function buildToolResult(name, payload) {
418
539
  case "get_database_objects":
419
540
  summary = summarizeObjects(payload);
420
541
  break;
421
- case "answer_question":
422
- summary = summarizeChat(payload);
542
+ case "get_brain_context":
543
+ summary = summarizeBrainContext(payload);
544
+ break;
545
+ case "list_business_rules":
546
+ summary = summarizeBusinessRules(payload);
547
+ break;
548
+ case "get_relationships":
549
+ summary = summarizeRelationships(payload);
550
+ break;
551
+ case "get_anti_patterns":
552
+ summary = summarizeAntiPatterns(payload, extra.kind || "table");
553
+ break;
554
+ case "analyze_slow_queries":
555
+ summary = summarizeSlowQueries(payload);
423
556
  break;
424
557
  case "execute_readonly_sql":
425
558
  summary = summarizeQueryResult(payload);
@@ -484,23 +617,84 @@ async function handleToolCall(config, name, args = {}) {
484
617
  return buildToolResult(name, payload);
485
618
  }
486
619
 
487
- case "answer_question": {
620
+ case "get_brain_context": {
488
621
  const connectionId = String(args.connectionId || "").trim();
489
622
  const question = String(args.question || "").trim();
490
- const chatId = args.chatId ? String(args.chatId) : null;
491
- const userId = args.userId ? String(args.userId) : config.defaultUserId;
623
+ if (!connectionId) return buildToolError("connectionId is required.");
624
+ if (!question) return buildToolError("question is required.");
625
+
626
+ // Route based on whether the caller wants ranked diagnostics (topK) or
627
+ // the rich training-context payload.
628
+ let payload;
629
+ if (args.topK != null) {
630
+ const topK = clampInteger(args.topK, 1, 100, 20);
631
+ const path =
632
+ `/training/retrieve/${encodeURIComponent(connectionId)}` +
633
+ `?q=${encodeURIComponent(question)}&topK=${topK}`;
634
+ payload = await callDeepSqlApi(config, path);
635
+ } else {
636
+ payload = await callDeepSqlApi(
637
+ config,
638
+ `/training/context/${encodeURIComponent(connectionId)}`,
639
+ { method: "POST", json: { question } },
640
+ );
641
+ }
642
+ return buildToolResult(name, payload);
643
+ }
492
644
 
493
- const payload = await callDeepSqlApi(config, "/chat", {
494
- method: "POST",
495
- json: {
496
- connectionId,
497
- message: question,
498
- chatId,
499
- userId,
500
- projectId: config.defaultProjectId,
501
- },
502
- });
645
+ case "list_business_rules": {
646
+ const connectionId = String(args.connectionId || "").trim();
647
+ if (!connectionId) return buildToolError("connectionId is required.");
648
+ let path = `/business-rules/connection/${encodeURIComponent(connectionId)}`;
649
+ if (args.question) {
650
+ path += `?question=${encodeURIComponent(String(args.question))}`;
651
+ }
652
+ const payload = await callDeepSqlApi(config, path);
653
+ return buildToolResult(name, payload);
654
+ }
503
655
 
656
+ case "get_relationships": {
657
+ const connectionId = String(args.connectionId || "").trim();
658
+ if (!connectionId) return buildToolError("connectionId is required.");
659
+ const payload = await callDeepSqlApi(
660
+ config,
661
+ `/brain/inferred-relationships/${encodeURIComponent(connectionId)}`,
662
+ );
663
+ return buildToolResult(name, payload);
664
+ }
665
+
666
+ case "get_anti_patterns": {
667
+ const connectionId = String(args.connectionId || "").trim();
668
+ if (!connectionId) return buildToolError("connectionId is required.");
669
+ const kind = args.kind === "query" ? "query" : "table";
670
+ let path;
671
+ if (kind === "query") {
672
+ path = `/brain/query-anti-patterns/${encodeURIComponent(connectionId)}`;
673
+ if (args.limit != null) {
674
+ path += `?limit=${clampInteger(args.limit, 1, 500, 50)}`;
675
+ }
676
+ } else {
677
+ path = `/brain/table-anti-patterns/${encodeURIComponent(connectionId)}`;
678
+ }
679
+ const payload = await callDeepSqlApi(config, path);
680
+ return buildToolResult(name, payload, { kind });
681
+ }
682
+
683
+ case "analyze_slow_queries": {
684
+ const connectionId = String(args.connectionId || "").trim();
685
+ if (!connectionId) return buildToolError("connectionId is required.");
686
+ const params = [];
687
+ if (args.thresholdMs != null) {
688
+ params.push(`threshold=${Number(args.thresholdMs)}`);
689
+ }
690
+ if (args.limit != null) {
691
+ params.push(`limit=${clampInteger(args.limit, 1, 500, 10)}`);
692
+ }
693
+ const qs = params.length ? `?${params.join("&")}` : "";
694
+ const payload = await callDeepSqlApi(
695
+ config,
696
+ `/slow-queries/analyze/${encodeURIComponent(connectionId)}${qs}`,
697
+ );
504
698
  return buildToolResult(name, payload);
505
699
  }
506
700
 
@@ -193,7 +193,7 @@ class DeepSqlPhase1McpServer {
193
193
  },
194
194
  serverInfo: SERVER_INFO,
195
195
  instructions:
196
- "DeepSQL phase 1 MCP exposes read-only database access through DeepSQL. Prefer answer_question for high-level tasks. execute_readonly_sql and explain_readonly_sql reject mutating SQL.",
196
+ "DeepSQL phase 1 MCP exposes read-only database access plus DeepSQL's brain (retrieved context, business rules, inferred relationships, anti-patterns, slow-query analysis). Prefer get_brain_context to ground SQL/answer generation in retrieved schema knowledge, then call execute_readonly_sql / explain_readonly_sql to validate. Mutating SQL is rejected by both client and backend.",
197
197
  });
198
198
  return;
199
199
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepsql/mcp",
3
- "version": "0.6.0",
3
+ "version": "0.10.0",
4
4
  "description": "DeepSQL CLI and stdio MCP server for self-hosted deployments",
5
5
  "bin": {
6
6
  "deepsql": "./bin/deepsql.js",
@@ -9,6 +9,8 @@
9
9
  "main": "./deepsql-phase1-server.js",
10
10
  "files": [
11
11
  "README.md",
12
+ "CLAUDE.md",
13
+ "AGENT-SETUP.md",
12
14
  "bin",
13
15
  "src",
14
16
  "deepsql-phase1-server.js",
package/src/api/client.js CHANGED
@@ -8,7 +8,7 @@
8
8
  * needs:
9
9
  * - profile-based base URL resolution (not env-only)
10
10
  * - to talk to the unauthenticated /auth/cli endpoints
11
- * - to stream responses for `ask`
11
+ * - per-call query string composition for brain endpoints
12
12
  */
13
13
 
14
14
  class ApiError extends Error {
package/src/auth/store.js CHANGED
@@ -11,10 +11,18 @@
11
11
  * {
12
12
  * "default": "http://localhost:8080",
13
13
  * "profiles": {
14
- * "<base-url>": { token, username, tokenId, createdAt }
14
+ * "<base-url>": {
15
+ * token, username, tokenId, createdAt,
16
+ * defaultConnection?: "<connection-name-or-uuid>"
17
+ * }
15
18
  * }
16
19
  * }
17
20
  *
21
+ * `defaultConnection` is the active connection for commands that need one
22
+ * (query/explain/schema/digest/slow-queries/brain-*). Set via
23
+ * `deepsql connections use <name>`. Resolution order in commands:
24
+ * --connection flag → DEEPSQL_CONNECTION env → profile.defaultConnection
25
+ *
18
26
  * The file is written with mode 0600 and the parent dir with 0700. We refuse to
19
27
  * read a file with looser perms unless DEEPSQL_INSECURE_AUTH=1 is set, since
20
28
  * tokens grant access to the user's databases.
@@ -133,6 +141,27 @@ function defaultBaseUrl() {
133
141
  return state.default || null;
134
142
  }
135
143
 
144
+ function getDefaultConnection(baseUrl) {
145
+ const profile = getProfile(baseUrl);
146
+ return profile && profile.defaultConnection ? profile.defaultConnection : null;
147
+ }
148
+
149
+ function setDefaultConnection(baseUrl, connectionName) {
150
+ const state = load();
151
+ const key = normalizeBaseUrl(baseUrl);
152
+ if (!state.profiles[key]) {
153
+ throw new Error(
154
+ `No profile saved for ${key}. Run \`deepsql login --url ${key}\` first.`,
155
+ );
156
+ }
157
+ if (connectionName == null || connectionName === "") {
158
+ delete state.profiles[key].defaultConnection;
159
+ } else {
160
+ state.profiles[key].defaultConnection = connectionName;
161
+ }
162
+ save(state);
163
+ }
164
+
136
165
  function listProfiles() {
137
166
  return load();
138
167
  }
@@ -141,6 +170,7 @@ module.exports = {
141
170
  authFilePath,
142
171
  configDir,
143
172
  defaultBaseUrl,
173
+ getDefaultConnection,
144
174
  getProfile,
145
175
  listProfiles,
146
176
  load,
@@ -148,5 +178,6 @@ module.exports = {
148
178
  removeProfile,
149
179
  save,
150
180
  setDefault,
181
+ setDefaultConnection,
151
182
  setProfile,
152
183
  };
@@ -63,6 +63,28 @@ test("auth file is written with mode 0600", { skip: process.platform === "win32"
63
63
  });
64
64
  });
65
65
 
66
+ test("setDefaultConnection round-trips and clearing it removes the field", () => {
67
+ withTempStore((store) => {
68
+ store.setProfile("http://x", { token: "t", username: "a" });
69
+ assert.equal(store.getDefaultConnection("http://x"), null);
70
+
71
+ store.setDefaultConnection("http://x", "prod-replica");
72
+ assert.equal(store.getDefaultConnection("http://x"), "prod-replica");
73
+
74
+ store.setDefaultConnection("http://x", null);
75
+ assert.equal(store.getDefaultConnection("http://x"), null);
76
+ });
77
+ });
78
+
79
+ test("setDefaultConnection refuses to write when the profile doesn't exist", () => {
80
+ withTempStore((store) => {
81
+ assert.throws(
82
+ () => store.setDefaultConnection("http://no-such-profile", "x"),
83
+ /No profile saved/,
84
+ );
85
+ });
86
+ });
87
+
66
88
  test("rejects loose perms unless DEEPSQL_INSECURE_AUTH=1", { skip: process.platform === "win32" }, () => {
67
89
  withTempStore((store, dir) => {
68
90
  store.setProfile("http://localhost:8080", { token: "t", username: "a" });
package/src/cli.js CHANGED
@@ -9,7 +9,7 @@
9
9
  * - boolean flags: --json, --device, --browser, --no-browser
10
10
  * - value flags: --url <url>, --token <t>, --connection <name>, --limit 50
11
11
  * - subcommands: deepsql connections list, deepsql config show
12
- * - positional: deepsql ask "what tables exist?"
12
+ * - positional: deepsql brain-context "which tables hold orders?"
13
13
  */
14
14
 
15
15
  const COMMANDS = {
@@ -19,10 +19,15 @@ const COMMANDS = {
19
19
  config: () => require("./commands/config"),
20
20
  mcp: () => require("./commands/mcp"),
21
21
  connections: () => require("./commands/connections"),
22
- ask: () => require("./commands/ask"),
23
22
  query: () => require("./commands/query"),
24
23
  explain: () => require("./commands/explain"),
25
24
  schema: () => require("./commands/schema"),
25
+ // Brain tools — give a coding agent the same retrieval context the chat
26
+ // pipeline uses, then let the agent generate SQL/answers itself.
27
+ "brain-context": () => require("./commands/brain-context"),
28
+ "business-rules": () => require("./commands/business-rules"),
29
+ relationships: () => require("./commands/relationships"),
30
+ "anti-patterns": () => require("./commands/anti-patterns"),
26
31
  digest: () => require("./commands/digest"),
27
32
  users: () => require("./commands/users"),
28
33
  access: () => require("./commands/access"),
@@ -31,7 +36,11 @@ const COMMANDS = {
31
36
  setup: () => require("./commands/setup"),
32
37
  };
33
38
 
34
- const HELP = `deepsql — DeepSQL CLI
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.
35
44
 
36
45
  Usage:
37
46
  deepsql <command> [options]
@@ -51,15 +60,41 @@ Commands:
51
60
  config set-default <url> Set the default profile.
52
61
  config path Print the auth file path.
53
62
  mcp Run the stdio MCP server using the saved token.
54
- connections list [--json] List database connections.
55
- ask "<question>" --connection <name> [--chat <id>] [--json]
56
- Ask DeepSQL a question.
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.
57
88
  query "<sql>" --connection <name> [--limit <n>] [--timeout-seconds <n>] [--file <path>] [--json]
58
- Run a read-only SQL query.
89
+ Run a read-only SQL statement. Enforced
90
+ read-only at the backend (parser-level) and
91
+ ACL-checked per connection.
59
92
  explain "<sql>" --connection <name> [--file <path>] [--json]
60
- Get an EXPLAIN plan.
93
+ Get an EXPLAIN plan (no ANALYZE — also
94
+ read-only enforced).
61
95
  schema [tables|objects] --connection <name>
62
- Dump schema or database objects as JSON.
96
+ Dump connection schema or database objects
97
+ as JSON.
63
98
  digest [N] [--connection <name>] [--json]
64
99
  Show the latest DeepSQL digest, or pass a
65
100
  number to list the last N (e.g. digest 5).
@@ -68,6 +103,24 @@ Commands:
68
103
  digest show <id> [--connection <name>] [--json]
69
104
  Show one digest by id.
70
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
+
71
124
  Admin commands (require ADMIN role on the calling token):
72
125
  users list | get <ref> | add [<email>] [--role <r>] [--name <n>] [--password-stdin]
73
126
  | set-role <ref> <role> | lock|unlock|disable <ref>
@@ -98,10 +151,12 @@ Admin commands (require ADMIN role on the calling token):
98
151
  and are NOT touched by this wizard.
99
152
 
100
153
  Global options:
101
- --url <url> Override the DeepSQL base URL.
102
- --token <tok> Override the auth token (also: DEEPSQL_AUTH_TOKEN).
103
- -h, --help Show help.
104
- -v, --version Show version.
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.
105
160
  `;
106
161
 
107
162
  function parseArgs(argv) {
@@ -182,6 +237,10 @@ function buildOpts(parsed) {
182
237
  grant: !!f.grant,
183
238
  revoke: !!f.revoke,
184
239
  reason: f.reason || null,
240
+ // Brain tools
241
+ topK: f.topK ?? null,
242
+ kind: f.kind || null,
243
+ question: f.question || null,
185
244
  // Slow queries
186
245
  timeRange: f.timeRange || null,
187
246
  thresholdMs: f.thresholdMs || null,
@@ -196,6 +255,15 @@ function buildOpts(parsed) {
196
255
  skipComplete: !!f.skipComplete,
197
256
  // Confirmations
198
257
  yes: !!f.yes || !!f.y,
258
+ // Connection management
259
+ fromFile: f.fromFile || null,
260
+ fromStdin: !!f.fromStdin,
261
+ upsert: !!f.upsert,
262
+ noTest: !!f.noTest,
263
+ wait: !!f.wait,
264
+ deleteAfter: !!f.deleteAfter,
265
+ cloud: !!f.cloud,
266
+ allowPlaintextSecrets: !!f.allowPlaintextSecrets,
199
267
  };
200
268
  }
201
269
 
@@ -29,11 +29,34 @@ async function listConnections(session) {
29
29
  return cachedList;
30
30
  }
31
31
 
32
+ /**
33
+ * Resolution chain for the connection a command should hit:
34
+ *
35
+ * 1. explicit `input` argument (i.e. opts.connection from --connection flag)
36
+ * 2. DEEPSQL_CONNECTION env var
37
+ * 3. session.defaultConnection (set via `deepsql connections use <name>`)
38
+ *
39
+ * If none of those produce a value, throw a friendly message that points the
40
+ * user at all three escape hatches.
41
+ */
32
42
  async function resolveConnectionId(session, input) {
33
- if (!input || typeof input !== "string") {
34
- throw new Error("Pass a connection name or id with --connection.");
43
+ let raw = input;
44
+ let source = "--connection";
45
+ if (raw == null || raw === "") {
46
+ raw = process.env.DEEPSQL_CONNECTION || null;
47
+ source = "DEEPSQL_CONNECTION";
48
+ }
49
+ if (raw == null || raw === "") {
50
+ raw = session && session.defaultConnection ? session.defaultConnection : null;
51
+ source = "saved default";
52
+ }
53
+ if (!raw || typeof raw !== "string") {
54
+ throw new Error(
55
+ "No connection specified. Pass --connection <name>, set DEEPSQL_CONNECTION, " +
56
+ "or run `deepsql connections use <name>` to pin a default.",
57
+ );
35
58
  }
36
- const trimmed = input.trim();
59
+ const trimmed = raw.trim();
37
60
  if (UUID_RE.test(trimmed)) return trimmed;
38
61
 
39
62
  const connections = await listConnections(session);