@deepsql/mcp 0.20.0 → 0.21.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/README.md +8 -3
- package/package.json +4 -2
- package/scripts/postinstall.js +57 -0
- package/src/agent/bootstrap.js +144 -56
- package/src/commands/agent.js +6 -3
- /package/agent-profile/tui/{entry.js → dist/entry.js} +0 -0
package/README.md
CHANGED
|
@@ -53,9 +53,14 @@ DBA persona, read-only-by-default tool scoping, and DeepSQL skins.
|
|
|
53
53
|
- **No LLM key needed** — the DeepSQL backend proxies the model
|
|
54
54
|
(`/api/llm/v1`), authenticated by your `deepsql login` token. The agent
|
|
55
55
|
never sees a provider key.
|
|
56
|
-
- **
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
- **Runtime install** — by default `npm install -g @deepsql/mcp` pre-installs
|
|
57
|
+
the agent runtime (a Hermes build pinned to a known-good commit) so the first
|
|
58
|
+
`deepsql` is instant. It's best-effort and never fails the npm install, and
|
|
59
|
+
skips automatically in CI and non-global installs. Opt out with
|
|
60
|
+
`DEEPSQL_SKIP_AGENT_SETUP=1` — pure-MCP users (Claude Code / Cursor) then
|
|
61
|
+
don't pull the runtime, and it installs lazily on first `deepsql agent`
|
|
62
|
+
instead. The per-user `deepsql` profile is provisioned on first launch after
|
|
63
|
+
login (it needs your token).
|
|
59
64
|
- **Read-only by default** — only the DeepSQL tools, memory, and skills are
|
|
60
65
|
enabled; host-affecting toolsets (terminal/file/code-exec/browser) are
|
|
61
66
|
disabled. `apply_index_recommendation` stays server-side confirm-gated.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepsql/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "DeepSQL CLI, DBA Agent TUI, and stdio MCP server for self-hosted deployments",
|
|
5
5
|
"bin": {
|
|
6
6
|
"deepsql": "bin/deepsql.js",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"bin",
|
|
15
15
|
"skills",
|
|
16
16
|
"src",
|
|
17
|
+
"scripts",
|
|
17
18
|
"agent-profile",
|
|
18
19
|
"deepsql-phase1-server.js",
|
|
19
20
|
"deepsql-phase1-lib.js",
|
|
@@ -21,7 +22,8 @@
|
|
|
21
22
|
"codex_config.customer.example.toml"
|
|
22
23
|
],
|
|
23
24
|
"scripts": {
|
|
24
|
-
"test": "node --test deepsql-phase1-lib.test.js src/*.test.js src/**/*.test.js"
|
|
25
|
+
"test": "node --test deepsql-phase1-lib.test.js src/*.test.js src/**/*.test.js",
|
|
26
|
+
"postinstall": "node scripts/postinstall.js"
|
|
25
27
|
},
|
|
26
28
|
"engines": {
|
|
27
29
|
"node": ">=20"
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
// Postinstall: pre-install the DeepSQL Agent (Hermes) runtime at `npm install -g`
|
|
5
|
+
// time so the first `deepsql` launch is instant instead of triggering a heavy
|
|
6
|
+
// one-time download. Best-effort and NON-FATAL — it never fails the npm install.
|
|
7
|
+
//
|
|
8
|
+
// Opt out (e.g. pure-MCP users who only want the editor tools, not the agent):
|
|
9
|
+
// DEEPSQL_SKIP_AGENT_SETUP=1 npm install -g @deepsql/mcp
|
|
10
|
+
//
|
|
11
|
+
// It auto-skips when there's nothing/no-one to set up for:
|
|
12
|
+
// - CI environments (process.env.CI)
|
|
13
|
+
// - non-global installs (local dev `npm install`, dependency installs)
|
|
14
|
+
// The profile itself is provisioned later, on first `deepsql` after login,
|
|
15
|
+
// because it needs the user's token.
|
|
16
|
+
|
|
17
|
+
function log(msg) {
|
|
18
|
+
process.stdout.write(`${msg}\n`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function skip(reason) {
|
|
22
|
+
// Silent for the common "not global / CI" cases; only note an explicit opt-out.
|
|
23
|
+
if (reason) log(`DeepSQL Agent: ${reason}`);
|
|
24
|
+
process.exit(0);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
if (process.env.DEEPSQL_SKIP_AGENT_SETUP) {
|
|
29
|
+
skip("agent runtime setup skipped (DEEPSQL_SKIP_AGENT_SETUP set).");
|
|
30
|
+
}
|
|
31
|
+
if (process.env.CI) skip();
|
|
32
|
+
// Only pre-install on an explicit global install (or a forced run). Local /
|
|
33
|
+
// dependency installs shouldn't pull a Python+Node runtime.
|
|
34
|
+
const isGlobal = String(process.env.npm_config_global || "") === "true";
|
|
35
|
+
if (!isGlobal && !process.env.DEEPSQL_FORCE_AGENT_SETUP) skip();
|
|
36
|
+
|
|
37
|
+
const { ensureHermes, hermesInstalled } = require("../src/agent/bootstrap");
|
|
38
|
+
|
|
39
|
+
if (hermesInstalled()) {
|
|
40
|
+
log("✓ DeepSQL Agent runtime already present.");
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
log("DeepSQL Agent: pre-installing the agent runtime so `deepsql` starts instantly.");
|
|
45
|
+
log(" (Opt out next time with DEEPSQL_SKIP_AGENT_SETUP=1.)");
|
|
46
|
+
const ok = ensureHermes({ stderr: process.stdout });
|
|
47
|
+
if (ok) {
|
|
48
|
+
log("✓ DeepSQL Agent runtime ready. Run `deepsql login`, then `deepsql`.");
|
|
49
|
+
} else {
|
|
50
|
+
// Non-fatal: the lazy installer will retry on first `deepsql agent`.
|
|
51
|
+
log("DeepSQL Agent: runtime not pre-installed; it'll be set up on first `deepsql` run.");
|
|
52
|
+
}
|
|
53
|
+
} catch (e) {
|
|
54
|
+
// Never break the npm install over agent pre-setup.
|
|
55
|
+
log(`DeepSQL Agent: skipped runtime pre-install (${e && e.message ? e.message : e}).`);
|
|
56
|
+
}
|
|
57
|
+
process.exit(0);
|
package/src/agent/bootstrap.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
// config rewrite keeps the token/connection fresh.
|
|
8
8
|
|
|
9
9
|
const fs = require("node:fs");
|
|
10
|
+
const os = require("node:os");
|
|
10
11
|
const path = require("node:path");
|
|
11
12
|
const { spawnSync } = require("node:child_process");
|
|
12
13
|
const { userHome } = require("../user-home");
|
|
@@ -17,15 +18,65 @@ const AGENT_DIR = path.join(HERMES_HOME, "hermes-agent");
|
|
|
17
18
|
const PROFILE = "deepsql";
|
|
18
19
|
const PROFILE_HOME = path.join(HERMES_HOME, "profiles", PROFILE);
|
|
19
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
|
+
|
|
20
74
|
function hermesBin() {
|
|
21
|
-
|
|
22
|
-
return fs.existsSync(venv) ? venv : "hermes";
|
|
75
|
+
return findHermes() || path.join(AGENT_DIR, ".venv", "bin", "hermes");
|
|
23
76
|
}
|
|
24
77
|
|
|
25
78
|
function hermesInstalled() {
|
|
26
|
-
|
|
27
|
-
const r = spawnSync("hermes", ["--version"], { stdio: "ignore" });
|
|
28
|
-
return r.status === 0;
|
|
79
|
+
return findHermes() !== null;
|
|
29
80
|
}
|
|
30
81
|
|
|
31
82
|
function mcpServerPath() {
|
|
@@ -42,16 +93,37 @@ function distSource() {
|
|
|
42
93
|
return null;
|
|
43
94
|
}
|
|
44
95
|
|
|
45
|
-
|
|
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 } = {}) {
|
|
46
100
|
const err = (io && io.stderr) || process.stderr;
|
|
47
101
|
if (hermesInstalled()) return true;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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("✓ DeepSQL Agent runtime installed. (Ignore any \"run hermes setup\" note above — DeepSQL configures it for you.)\n");
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
err.write(
|
|
120
|
+
"DeepSQL Agent: the runtime install did not complete.\n" +
|
|
121
|
+
` Install log: ${logFile}\n` +
|
|
122
|
+
` Exit code: ${r.status == null ? "n/a" : r.status}\n` +
|
|
123
|
+
" You can retry, or install Hermes manually: " +
|
|
124
|
+
`curl -fsSL ${INSTALL_URL} | bash -s -- --commit ${HERMES_COMMIT}\n`
|
|
53
125
|
);
|
|
54
|
-
return
|
|
126
|
+
return false;
|
|
55
127
|
}
|
|
56
128
|
|
|
57
129
|
function q(s) {
|
|
@@ -92,16 +164,39 @@ display:
|
|
|
92
164
|
`;
|
|
93
165
|
fs.writeFileSync(path.join(PROFILE_HOME, "config.yaml"), config);
|
|
94
166
|
|
|
167
|
+
// OPENAI_BASE_URL/OPENAI_API_KEY are belt-and-suspenders: Hermes' first-run
|
|
168
|
+
// guard (_has_any_provider_configured) treats either as "a provider is
|
|
169
|
+
// configured", so the "run hermes setup" gate can never fire on a fresh
|
|
170
|
+
// machine even if config-load timing differs. They point at the SAME backend
|
|
171
|
+
// proxy as the model block, authed by the user's DeepSQL token.
|
|
95
172
|
const env =
|
|
96
173
|
`DEEPSQL_API_BASE_URL=${apiBase}
|
|
97
174
|
DEEPSQL_AUTH_TOKEN=${token}
|
|
98
175
|
DEEPSQL_MCP_SERVER=${mcp}
|
|
99
176
|
DEEPSQL_DEFAULT_CONNECTION_ID=${conn}
|
|
177
|
+
OPENAI_BASE_URL=${proxy}
|
|
178
|
+
OPENAI_API_KEY=${token}
|
|
100
179
|
HERMES_REVISION=deepsql-cli
|
|
101
180
|
`;
|
|
102
181
|
fs.writeFileSync(path.join(PROFILE_HOME, ".env"), env, { mode: 0o600 });
|
|
103
182
|
}
|
|
104
183
|
|
|
184
|
+
// Env for spawning the TUI: augmented PATH (so the TUI's node + the MCP
|
|
185
|
+
// server's node resolve), the provider vars that defeat Hermes' first-run
|
|
186
|
+
// guard regardless of config state, and HERMES_TUI_DIR pointing at our prebuilt
|
|
187
|
+
// branded TUI (when the Hermes version matches) so Hermes runs it directly with
|
|
188
|
+
// no esbuild — branded on the first launch.
|
|
189
|
+
function launchEnv(session) {
|
|
190
|
+
const base = session.baseUrl.replace(/\/?$/, "/");
|
|
191
|
+
const extra = {
|
|
192
|
+
OPENAI_BASE_URL: base + "api/llm/v1",
|
|
193
|
+
OPENAI_API_KEY: session.token,
|
|
194
|
+
};
|
|
195
|
+
const tui = brandedTuiDir();
|
|
196
|
+
if (tui) extra.HERMES_TUI_DIR = tui;
|
|
197
|
+
return hermesEnv(extra);
|
|
198
|
+
}
|
|
199
|
+
|
|
105
200
|
function installProfileAssets(io) {
|
|
106
201
|
const err = (io && io.stderr) || process.stderr;
|
|
107
202
|
const src = distSource();
|
|
@@ -110,8 +205,19 @@ function installProfileAssets(io) {
|
|
|
110
205
|
return false;
|
|
111
206
|
}
|
|
112
207
|
const bin = hermesBin();
|
|
113
|
-
const baseEnv =
|
|
114
|
-
|
|
208
|
+
const baseEnv = hermesEnv();
|
|
209
|
+
const inst = spawnSync(
|
|
210
|
+
bin,
|
|
211
|
+
["profile", "install", src, "--name", PROFILE, "--force", "--yes"],
|
|
212
|
+
{ encoding: "utf8", env: baseEnv }
|
|
213
|
+
);
|
|
214
|
+
if (inst.status !== 0) {
|
|
215
|
+
err.write(
|
|
216
|
+
"DeepSQL Agent: provisioning the profile failed.\n" +
|
|
217
|
+
` ${(inst.stderr || inst.stdout || "(no output)").trim().split("\n").slice(-6).join("\n ")}\n`
|
|
218
|
+
);
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
115
221
|
// Skin loads from <profile>/skins/.
|
|
116
222
|
const skin = path.join(src, "skins", "deepsql.yaml");
|
|
117
223
|
if (fs.existsSync(skin)) {
|
|
@@ -119,12 +225,13 @@ function installProfileAssets(io) {
|
|
|
119
225
|
fs.mkdirSync(dst, { recursive: true });
|
|
120
226
|
fs.copyFileSync(skin, path.join(dst, "deepsql.yaml"));
|
|
121
227
|
}
|
|
122
|
-
// Read-only sandbox: only deepsql + skills/memory remain.
|
|
228
|
+
// Read-only sandbox: only deepsql + skills/memory remain. Non-fatal — the
|
|
229
|
+
// shipped distribution already scopes tools; this is defense in depth.
|
|
123
230
|
spawnSync(
|
|
124
231
|
bin,
|
|
125
232
|
["tools", "disable", "terminal", "file", "code_execution", "browser", "computer_use",
|
|
126
233
|
"image_gen", "tts", "vision", "web", "delegation", "cronjob"],
|
|
127
|
-
{ stdio: "ignore", env: {
|
|
234
|
+
{ stdio: "ignore", env: hermesEnv({ HERMES_HOME: PROFILE_HOME }) }
|
|
128
235
|
);
|
|
129
236
|
return fs.existsSync(PROFILE_HOME);
|
|
130
237
|
}
|
|
@@ -138,8 +245,7 @@ function ensureProfile(session, io) {
|
|
|
138
245
|
}
|
|
139
246
|
|
|
140
247
|
// Installed Hermes version (parsed from the runtime's __init__.py). Null if the
|
|
141
|
-
// runtime layout isn't what we expect (e.g. PATH-only install)
|
|
142
|
-
// skipped and the user gets the stock TUI with our skin.
|
|
248
|
+
// runtime layout isn't what we expect (e.g. PATH-only install).
|
|
143
249
|
function installedHermesVersion() {
|
|
144
250
|
try {
|
|
145
251
|
const init = path.join(AGENT_DIR, "hermes_cli", "__init__.py");
|
|
@@ -150,56 +256,38 @@ function installedHermesVersion() {
|
|
|
150
256
|
}
|
|
151
257
|
}
|
|
152
258
|
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
//
|
|
259
|
+
// The DeepSQL-branded prebuilt TUI bundle shipped in the package, as a directory
|
|
260
|
+
// laid out the way Hermes' `HERMES_TUI_DIR` mechanism expects: `<dir>/dist/entry.js`.
|
|
261
|
+
// When that env var points here, Hermes runs our entry.js DIRECTLY — no npm
|
|
262
|
+
// install, no esbuild — so the custom DEEPSQL wordmark / welcome panel / DBA
|
|
263
|
+
// placeholders show on the very FIRST launch. (The earlier copy-into-the-clone
|
|
264
|
+
// approach lost every launch because Hermes "always esbuild"s the normal path.)
|
|
157
265
|
//
|
|
158
|
-
// Guarded by the Hermes version our bundle was built against: a prebuilt
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
//
|
|
162
|
-
//
|
|
163
|
-
|
|
164
|
-
// We refresh the overlay on every launch and bump its mtime past the TUI build
|
|
165
|
-
// inputs so Hermes' freshness check (`_tui_need_rebuild`, an mtime compare of
|
|
166
|
-
// dist/entry.js vs src/) never rebuilds over it after a `hermes update`.
|
|
167
|
-
function ensureBrandedTui(io) {
|
|
168
|
-
const err = (io && io.stderr) || process.stderr;
|
|
266
|
+
// Guarded by the Hermes version our bundle was built against: a prebuilt entry.js
|
|
267
|
+
// speaks one release's TUI↔CLI protocol, so we only point at it on an exact
|
|
268
|
+
// version match (the runtime is pinned to HERMES_COMMIT, so it matches on our
|
|
269
|
+
// installs). On any mismatch / missing bundle we return null and let Hermes use
|
|
270
|
+
// its own (stock) TUI, which still renders our skin (DeepSQL name + maroon).
|
|
271
|
+
function brandedTuiDir() {
|
|
169
272
|
try {
|
|
170
273
|
const tuiDir = path.resolve(__dirname, "..", "..", "agent-profile", "tui");
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const builtFor = (() => {
|
|
174
|
-
try { return fs.readFileSync(path.join(tuiDir, "HERMES_VERSION"), "utf8").trim(); }
|
|
175
|
-
catch { return null; }
|
|
176
|
-
})();
|
|
274
|
+
if (!fs.existsSync(path.join(tuiDir, "dist", "entry.js"))) return null;
|
|
275
|
+
const builtFor = fs.readFileSync(path.join(tuiDir, "HERMES_VERSION"), "utf8").trim();
|
|
177
276
|
const installed = installedHermesVersion();
|
|
178
|
-
if (!builtFor || !installed || builtFor !== installed) return
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
fs.mkdirSync(distDir, { recursive: true });
|
|
183
|
-
const same =
|
|
184
|
-
fs.existsSync(target) && fs.statSync(target).size === fs.statSync(bundledEntry).size;
|
|
185
|
-
if (!same) fs.copyFileSync(bundledEntry, target);
|
|
186
|
-
// Bump mtime past every TUI build input so the freshness check never
|
|
187
|
-
// rebuilds the stock TUI over our branded bundle.
|
|
188
|
-
const future = new Date(Date.now() + 60_000);
|
|
189
|
-
fs.utimesSync(target, future, future);
|
|
190
|
-
return true;
|
|
191
|
-
} catch (e) {
|
|
192
|
-
err.write(`DeepSQL Agent: branded TUI overlay skipped (${e.message}); using stock TUI.\n`);
|
|
193
|
-
return false;
|
|
277
|
+
if (!builtFor || !installed || builtFor !== installed) return null;
|
|
278
|
+
return tuiDir;
|
|
279
|
+
} catch {
|
|
280
|
+
return null;
|
|
194
281
|
}
|
|
195
282
|
}
|
|
196
283
|
|
|
197
284
|
module.exports = {
|
|
198
285
|
ensureHermes,
|
|
199
286
|
ensureProfile,
|
|
200
|
-
|
|
287
|
+
brandedTuiDir,
|
|
201
288
|
hermesBin,
|
|
202
289
|
hermesInstalled,
|
|
290
|
+
launchEnv,
|
|
203
291
|
PROFILE,
|
|
204
292
|
PROFILE_HOME,
|
|
205
293
|
};
|
package/src/commands/agent.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
const { spawn } = require("node:child_process");
|
|
9
9
|
const { resolveSession } = require("./_session");
|
|
10
|
-
const { ensureHermes, ensureProfile,
|
|
10
|
+
const { ensureHermes, ensureProfile, hermesBin, launchEnv, PROFILE } = require("../agent/bootstrap");
|
|
11
11
|
|
|
12
12
|
async function run(opts, io = {}) {
|
|
13
13
|
const stderr = io.stderr || process.stderr;
|
|
@@ -28,10 +28,13 @@ async function run(opts, io = {}) {
|
|
|
28
28
|
stderr.write("Could not provision the DeepSQL Agent profile.\n");
|
|
29
29
|
return 1;
|
|
30
30
|
}
|
|
31
|
-
|
|
31
|
+
// Branding is delivered via HERMES_TUI_DIR in launchEnv (Hermes runs our
|
|
32
|
+
// prebuilt branded TUI directly — no esbuild), set just below.
|
|
32
33
|
|
|
33
34
|
// Hand the terminal to the branded TUI (profile config sets interface=tui).
|
|
34
|
-
|
|
35
|
+
// launchEnv augments PATH (node/uv for the TUI + MCP server) and injects the
|
|
36
|
+
// provider vars that keep Hermes' first-run guard from ever firing.
|
|
37
|
+
const child = spawn(hermesBin(), ["-p", PROFILE, "--tui"], { stdio: "inherit", env: launchEnv(session) });
|
|
35
38
|
return await new Promise((resolve) => {
|
|
36
39
|
child.on("exit", (code) => resolve(code ?? 0));
|
|
37
40
|
child.on("error", (err) => {
|
|
File without changes
|