@deepsql/mcp 0.22.0 → 0.26.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/CLAUDE.md +7 -2
- package/README.md +5 -2
- package/deepsql-phase1-lib.js +138 -1
- package/package.json +2 -3
- package/skills/SKILL_BODY.md +6 -2
- package/src/cli.js +25 -1
- package/src/cli.test.js +2 -1
- package/src/commands/_agent_intro.js +227 -0
- package/src/commands/_agent_status.js +93 -0
- package/src/commands/_connections.js +41 -17
- package/src/commands/agent.js +54 -5
- package/src/commands/brain.js +144 -0
- package/src/commands/connections.js +8 -2
- package/src/connections/cache.js +52 -0
- package/agent-profile/SOUL.md +0 -28
- package/agent-profile/distribution.yaml +0 -41
- package/agent-profile/skills/bi-query/SKILL.md +0 -40
- package/agent-profile/skills/index-advisor/SKILL.md +0 -34
- package/agent-profile/skills/schema-exploration/SKILL.md +0 -32
- package/agent-profile/skills/slow-query-optimize/SKILL.md +0 -31
- package/agent-profile/skills/workload-analysis/SKILL.md +0 -36
- package/agent-profile/skins/deepsql.yaml +0 -46
- package/agent-profile/tui/HERMES_VERSION +0 -1
- package/agent-profile/tui/dist/entry.js +0 -92903
- package/src/agent/bootstrap.js +0 -295
package/src/agent/bootstrap.js
DELETED
|
@@ -1,295 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
// Lazy bootstrap for the DeepSQL Agent (Hermes-based TUI): detect/install the
|
|
4
|
-
// Hermes runtime and provision the `deepsql` profile (model → the backend LLM
|
|
5
|
-
// proxy with the user's token; DeepSQL MCP tools; DBA persona/skills; subtle
|
|
6
|
-
// DeepSQL skin; read-only sandbox). Provisioning is idempotent; the per-launch
|
|
7
|
-
// config rewrite keeps the token/connection fresh.
|
|
8
|
-
|
|
9
|
-
const fs = require("node:fs");
|
|
10
|
-
const os = require("node:os");
|
|
11
|
-
const path = require("node:path");
|
|
12
|
-
const { spawnSync } = require("node:child_process");
|
|
13
|
-
const { userHome } = require("../user-home");
|
|
14
|
-
|
|
15
|
-
const HOME = userHome();
|
|
16
|
-
const HERMES_HOME = path.join(HOME, ".hermes");
|
|
17
|
-
const AGENT_DIR = path.join(HERMES_HOME, "hermes-agent");
|
|
18
|
-
const PROFILE = "deepsql";
|
|
19
|
-
const PROFILE_HOME = path.join(HERMES_HOME, "profiles", PROFILE);
|
|
20
|
-
|
|
21
|
-
const INSTALL_URL = "https://hermes-agent.nousresearch.com/install.sh";
|
|
22
|
-
// Pin the Hermes runtime to the exact commit our bundled TUI (agent-profile/
|
|
23
|
-
// tui/entry.js, HERMES_VERSION 0.17.0) was built against. This makes installs
|
|
24
|
-
// reproducible AND guarantees the branded-TUI overlay's version guard matches,
|
|
25
|
-
// so end users get the DeepSQL wordmark/welcome — not a "latest main" build
|
|
26
|
-
// that drifts in behavior and skips our overlay. Bump alongside the bundle.
|
|
27
|
-
const HERMES_COMMIT = "92d40c2553961243991376bf889d833e8326caf7";
|
|
28
|
-
|
|
29
|
-
// Extra dirs the Hermes runtime + its installer rely on but a bare GUI/login
|
|
30
|
-
// shell PATH (as seen by a spawned Node process) often lacks: the venv, the
|
|
31
|
-
// installer's `hermes` symlink targets, and Homebrew (uv/node/git).
|
|
32
|
-
function extraPathDirs() {
|
|
33
|
-
return [
|
|
34
|
-
path.join(AGENT_DIR, ".venv", "bin"),
|
|
35
|
-
path.join(HOME, ".local", "bin"),
|
|
36
|
-
"/usr/local/bin",
|
|
37
|
-
"/opt/homebrew/bin",
|
|
38
|
-
"/usr/local/lib/hermes-agent/.venv/bin",
|
|
39
|
-
];
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Env for any spawn that runs (or installs) Hermes: augmented PATH so node/uv/
|
|
43
|
-
// git resolve, UV_NO_CONFIG so uv ignores stray project configs, and a pinned
|
|
44
|
-
// HERMES_HOME so the install/profile lands where we expect.
|
|
45
|
-
function hermesEnv(extra = {}) {
|
|
46
|
-
const sep = path.delimiter;
|
|
47
|
-
const have = (process.env.PATH || "").split(sep);
|
|
48
|
-
const merged = [...extraPathDirs().filter((d) => !have.includes(d)), ...have].join(sep);
|
|
49
|
-
return { ...process.env, PATH: merged, UV_NO_CONFIG: "1", HERMES_HOME, ...extra };
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// All the places the official installer may drop a runnable `hermes` for a
|
|
53
|
-
// non-root install (venv) or an FHS/root install (/usr/local), plus the PATH.
|
|
54
|
-
function findHermes() {
|
|
55
|
-
const candidates = [];
|
|
56
|
-
if (process.env.HERMES_INSTALL_DIR) {
|
|
57
|
-
candidates.push(path.join(process.env.HERMES_INSTALL_DIR, ".venv", "bin", "hermes"));
|
|
58
|
-
}
|
|
59
|
-
candidates.push(
|
|
60
|
-
path.join(AGENT_DIR, ".venv", "bin", "hermes"),
|
|
61
|
-
"/usr/local/lib/hermes-agent/.venv/bin/hermes",
|
|
62
|
-
path.join(HOME, ".local", "bin", "hermes"),
|
|
63
|
-
"/usr/local/bin/hermes"
|
|
64
|
-
);
|
|
65
|
-
for (const bin of candidates) {
|
|
66
|
-
if (!fs.existsSync(bin)) continue;
|
|
67
|
-
if (spawnSync(bin, ["--version"], { stdio: "ignore", env: hermesEnv() }).status === 0) return bin;
|
|
68
|
-
}
|
|
69
|
-
// Last resort: a `hermes` already on the (augmented) PATH.
|
|
70
|
-
if (spawnSync("hermes", ["--version"], { stdio: "ignore", env: hermesEnv() }).status === 0) return "hermes";
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function hermesBin() {
|
|
75
|
-
return findHermes() || path.join(AGENT_DIR, ".venv", "bin", "hermes");
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function hermesInstalled() {
|
|
79
|
-
return findHermes() !== null;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function mcpServerPath() {
|
|
83
|
-
return path.resolve(__dirname, "..", "..", "deepsql-phase1-server.js");
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Profile distribution (SOUL.md + skills/ + skins/): the bundled copy shipped in
|
|
87
|
-
// the package, else the repo `hermes/` dir during local development.
|
|
88
|
-
function distSource() {
|
|
89
|
-
const bundled = path.resolve(__dirname, "..", "..", "agent-profile");
|
|
90
|
-
if (fs.existsSync(path.join(bundled, "distribution.yaml"))) return bundled;
|
|
91
|
-
const repoHermes = path.resolve(__dirname, "..", "..", "..", "hermes");
|
|
92
|
-
if (fs.existsSync(path.join(repoHermes, "distribution.yaml"))) return repoHermes;
|
|
93
|
-
return null;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Install the Hermes runtime, pinned + non-interactive, with the output teed to
|
|
97
|
-
// a log so a failure surfaces a real cause instead of a vague "can't set up".
|
|
98
|
-
// `quiet` (postinstall) hides the live stream; interactive first-run shows it.
|
|
99
|
-
function ensureHermes(io, { quiet = false } = {}) {
|
|
100
|
-
const err = (io && io.stderr) || process.stderr;
|
|
101
|
-
if (hermesInstalled()) return true;
|
|
102
|
-
|
|
103
|
-
err.write("DeepSQL Agent: installing the agent runtime (first run; ~1–2 min)…\n");
|
|
104
|
-
const logFile = path.join(os.tmpdir(), `deepsql-agent-install-${process.pid}.log`);
|
|
105
|
-
// --commit pins the checkout; --skip-setup/--non-interactive avoid the
|
|
106
|
-
// wizard + any TTY prompt. tee keeps a persisted log for diagnostics.
|
|
107
|
-
const script =
|
|
108
|
-
`curl -fsSL ${INSTALL_URL} | bash -s -- ` +
|
|
109
|
-
`--skip-setup --non-interactive --commit ${HERMES_COMMIT} 2>&1 | tee ${JSON.stringify(logFile)}`;
|
|
110
|
-
const r = spawnSync("bash", ["-lc", script], {
|
|
111
|
-
stdio: quiet ? ["ignore", "ignore", "ignore"] : "inherit",
|
|
112
|
-
env: hermesEnv(),
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
if (hermesInstalled()) {
|
|
116
|
-
err.write(quiet
|
|
117
|
-
? "✓ DeepSQL Agent runtime installed.\n"
|
|
118
|
-
: "✓ DeepSQL Agent runtime installed. (Ignore any \"run hermes setup\" note above — DeepSQL configures it for you.)\n");
|
|
119
|
-
return true;
|
|
120
|
-
}
|
|
121
|
-
err.write(
|
|
122
|
-
"DeepSQL Agent: the runtime install did not complete.\n" +
|
|
123
|
-
` Install log: ${logFile}\n` +
|
|
124
|
-
` Exit code: ${r.status == null ? "n/a" : r.status}\n` +
|
|
125
|
-
" You can retry, or install Hermes manually: " +
|
|
126
|
-
`curl -fsSL ${INSTALL_URL} | bash -s -- --commit ${HERMES_COMMIT}\n`
|
|
127
|
-
);
|
|
128
|
-
return false;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function q(s) {
|
|
132
|
-
return `"${String(s).replace(/"/g, '\\"')}"`;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function writeRuntimeConfig(session) {
|
|
136
|
-
fs.mkdirSync(PROFILE_HOME, { recursive: true });
|
|
137
|
-
const base = session.baseUrl.replace(/\/?$/, "/"); // ensure single trailing /
|
|
138
|
-
const proxy = base + "api/llm/v1";
|
|
139
|
-
const apiBase = base + "api/";
|
|
140
|
-
const token = session.token;
|
|
141
|
-
const mcp = mcpServerPath();
|
|
142
|
-
const conn = session.defaultConnection || "";
|
|
143
|
-
|
|
144
|
-
const config =
|
|
145
|
-
`model:
|
|
146
|
-
default: gpt-5.4
|
|
147
|
-
provider: custom
|
|
148
|
-
base_url: ${q(proxy)}
|
|
149
|
-
api_key: ${q(token)}
|
|
150
|
-
api_mode: chat_completions
|
|
151
|
-
context_length: 272000
|
|
152
|
-
mcp_servers:
|
|
153
|
-
deepsql:
|
|
154
|
-
command: node
|
|
155
|
-
args:
|
|
156
|
-
- ${q(mcp)}
|
|
157
|
-
env:
|
|
158
|
-
DEEPSQL_API_BASE_URL: ${q(apiBase)}
|
|
159
|
-
DEEPSQL_MCP_USER_ID: deepsql-cli
|
|
160
|
-
DEEPSQL_AUTH_TOKEN: ${q(token)}
|
|
161
|
-
approvals:
|
|
162
|
-
mode: smart
|
|
163
|
-
display:
|
|
164
|
-
skin: deepsql
|
|
165
|
-
interface: tui
|
|
166
|
-
`;
|
|
167
|
-
fs.writeFileSync(path.join(PROFILE_HOME, "config.yaml"), config);
|
|
168
|
-
|
|
169
|
-
// OPENAI_BASE_URL/OPENAI_API_KEY are belt-and-suspenders: Hermes' first-run
|
|
170
|
-
// guard (_has_any_provider_configured) treats either as "a provider is
|
|
171
|
-
// configured", so the "run hermes setup" gate can never fire on a fresh
|
|
172
|
-
// machine even if config-load timing differs. They point at the SAME backend
|
|
173
|
-
// proxy as the model block, authed by the user's DeepSQL token.
|
|
174
|
-
const env =
|
|
175
|
-
`DEEPSQL_API_BASE_URL=${apiBase}
|
|
176
|
-
DEEPSQL_AUTH_TOKEN=${token}
|
|
177
|
-
DEEPSQL_MCP_SERVER=${mcp}
|
|
178
|
-
DEEPSQL_DEFAULT_CONNECTION_ID=${conn}
|
|
179
|
-
OPENAI_BASE_URL=${proxy}
|
|
180
|
-
OPENAI_API_KEY=${token}
|
|
181
|
-
HERMES_REVISION=deepsql-cli
|
|
182
|
-
`;
|
|
183
|
-
fs.writeFileSync(path.join(PROFILE_HOME, ".env"), env, { mode: 0o600 });
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Env for spawning the TUI: augmented PATH (so the TUI's node + the MCP
|
|
187
|
-
// server's node resolve), the provider vars that defeat Hermes' first-run
|
|
188
|
-
// guard regardless of config state, and HERMES_TUI_DIR pointing at our prebuilt
|
|
189
|
-
// branded TUI (when the Hermes version matches) so Hermes runs it directly with
|
|
190
|
-
// no esbuild — branded on the first launch.
|
|
191
|
-
function launchEnv(session) {
|
|
192
|
-
const base = session.baseUrl.replace(/\/?$/, "/");
|
|
193
|
-
const extra = {
|
|
194
|
-
OPENAI_BASE_URL: base + "api/llm/v1",
|
|
195
|
-
OPENAI_API_KEY: session.token,
|
|
196
|
-
};
|
|
197
|
-
const tui = brandedTuiDir();
|
|
198
|
-
if (tui) extra.HERMES_TUI_DIR = tui;
|
|
199
|
-
return hermesEnv(extra);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function installProfileAssets(io) {
|
|
203
|
-
const err = (io && io.stderr) || process.stderr;
|
|
204
|
-
const src = distSource();
|
|
205
|
-
if (!src) {
|
|
206
|
-
err.write("DeepSQL agent profile assets not found in this install.\n");
|
|
207
|
-
return false;
|
|
208
|
-
}
|
|
209
|
-
const bin = hermesBin();
|
|
210
|
-
const baseEnv = hermesEnv();
|
|
211
|
-
const inst = spawnSync(
|
|
212
|
-
bin,
|
|
213
|
-
["profile", "install", src, "--name", PROFILE, "--force", "--yes"],
|
|
214
|
-
{ encoding: "utf8", env: baseEnv }
|
|
215
|
-
);
|
|
216
|
-
if (inst.status !== 0) {
|
|
217
|
-
err.write(
|
|
218
|
-
"DeepSQL Agent: provisioning the profile failed.\n" +
|
|
219
|
-
` ${(inst.stderr || inst.stdout || "(no output)").trim().split("\n").slice(-6).join("\n ")}\n`
|
|
220
|
-
);
|
|
221
|
-
return false;
|
|
222
|
-
}
|
|
223
|
-
// Skin loads from <profile>/skins/.
|
|
224
|
-
const skin = path.join(src, "skins", "deepsql.yaml");
|
|
225
|
-
if (fs.existsSync(skin)) {
|
|
226
|
-
const dst = path.join(PROFILE_HOME, "skins");
|
|
227
|
-
fs.mkdirSync(dst, { recursive: true });
|
|
228
|
-
fs.copyFileSync(skin, path.join(dst, "deepsql.yaml"));
|
|
229
|
-
}
|
|
230
|
-
// Read-only sandbox: only deepsql + skills/memory remain. Non-fatal — the
|
|
231
|
-
// shipped distribution already scopes tools; this is defense in depth.
|
|
232
|
-
spawnSync(
|
|
233
|
-
bin,
|
|
234
|
-
["tools", "disable", "terminal", "file", "code_execution", "browser", "computer_use",
|
|
235
|
-
"image_gen", "tts", "vision", "web", "delegation", "cronjob"],
|
|
236
|
-
{ stdio: "ignore", env: hermesEnv({ HERMES_HOME: PROFILE_HOME }) }
|
|
237
|
-
);
|
|
238
|
-
return fs.existsSync(PROFILE_HOME);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
function ensureProfile(session, io) {
|
|
242
|
-
if (!fs.existsSync(path.join(PROFILE_HOME, "config.yaml"))) {
|
|
243
|
-
if (!installProfileAssets(io)) return false;
|
|
244
|
-
}
|
|
245
|
-
writeRuntimeConfig(session); // always refresh the token/connection for this launch
|
|
246
|
-
return true;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// Installed Hermes version (parsed from the runtime's __init__.py). Null if the
|
|
250
|
-
// runtime layout isn't what we expect (e.g. PATH-only install).
|
|
251
|
-
function installedHermesVersion() {
|
|
252
|
-
try {
|
|
253
|
-
const init = path.join(AGENT_DIR, "hermes_cli", "__init__.py");
|
|
254
|
-
const m = fs.readFileSync(init, "utf8").match(/__version__\s*=\s*["']([^"']+)["']/);
|
|
255
|
-
return m ? m[1] : null;
|
|
256
|
-
} catch {
|
|
257
|
-
return null;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// The DeepSQL-branded prebuilt TUI bundle shipped in the package, as a directory
|
|
262
|
-
// laid out the way Hermes' `HERMES_TUI_DIR` mechanism expects: `<dir>/dist/entry.js`.
|
|
263
|
-
// When that env var points here, Hermes runs our entry.js DIRECTLY — no npm
|
|
264
|
-
// install, no esbuild — so the custom DEEPSQL wordmark / welcome panel / DBA
|
|
265
|
-
// placeholders show on the very FIRST launch. (The earlier copy-into-the-clone
|
|
266
|
-
// approach lost every launch because Hermes "always esbuild"s the normal path.)
|
|
267
|
-
//
|
|
268
|
-
// Guarded by the Hermes version our bundle was built against: a prebuilt entry.js
|
|
269
|
-
// speaks one release's TUI↔CLI protocol, so we only point at it on an exact
|
|
270
|
-
// version match (the runtime is pinned to HERMES_COMMIT, so it matches on our
|
|
271
|
-
// installs). On any mismatch / missing bundle we return null and let Hermes use
|
|
272
|
-
// its own (stock) TUI, which still renders our skin (DeepSQL name + maroon).
|
|
273
|
-
function brandedTuiDir() {
|
|
274
|
-
try {
|
|
275
|
-
const tuiDir = path.resolve(__dirname, "..", "..", "agent-profile", "tui");
|
|
276
|
-
if (!fs.existsSync(path.join(tuiDir, "dist", "entry.js"))) return null;
|
|
277
|
-
const builtFor = fs.readFileSync(path.join(tuiDir, "HERMES_VERSION"), "utf8").trim();
|
|
278
|
-
const installed = installedHermesVersion();
|
|
279
|
-
if (!builtFor || !installed || builtFor !== installed) return null;
|
|
280
|
-
return tuiDir;
|
|
281
|
-
} catch {
|
|
282
|
-
return null;
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
module.exports = {
|
|
287
|
-
ensureHermes,
|
|
288
|
-
ensureProfile,
|
|
289
|
-
brandedTuiDir,
|
|
290
|
-
hermesBin,
|
|
291
|
-
hermesInstalled,
|
|
292
|
-
launchEnv,
|
|
293
|
-
PROFILE,
|
|
294
|
-
PROFILE_HOME,
|
|
295
|
-
};
|