@tacuchi/agent-workflow-cli 21.10.0 → 21.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/dist/application/dev-bootstrap-dsn-service.js +45 -35
- package/dist/application/dev-bootstrap-dsn-service.js.map +1 -1
- package/dist/application/docs-canon-service.js +68 -0
- package/dist/application/docs-canon-service.js.map +1 -0
- package/dist/application/dsn-reader-service.js +53 -2
- package/dist/application/dsn-reader-service.js.map +1 -1
- package/dist/application/mcp-dbhub-launcher.js +193 -34
- package/dist/application/mcp-dbhub-launcher.js.map +1 -1
- package/dist/application/mcp-doctor-service.js +13 -4
- package/dist/application/mcp-doctor-service.js.map +1 -1
- package/dist/application/mcp-test-connection-service.js +5 -11
- package/dist/application/mcp-test-connection-service.js.map +1 -1
- package/dist/application/visibility-doctor-service.js +89 -49
- package/dist/application/visibility-doctor-service.js.map +1 -1
- package/dist/application/workspace-migrate/apply.js +57 -0
- package/dist/application/workspace-migrate/apply.js.map +1 -0
- package/dist/application/workspace-migrate/markers.js +177 -0
- package/dist/application/workspace-migrate/markers.js.map +1 -0
- package/dist/application/workspace-migrate/plan.js +171 -0
- package/dist/application/workspace-migrate/plan.js.map +1 -0
- package/dist/application/workspace-migrate/preview.js +111 -0
- package/dist/application/workspace-migrate/preview.js.map +1 -0
- package/dist/cli/commands/unknown-flags.js +51 -0
- package/dist/cli/commands/unknown-flags.js.map +1 -0
- package/dist/cli/commands/visibility.js +105 -16
- package/dist/cli/commands/visibility.js.map +1 -1
- package/dist/cli/commands/workspace-migrate.js +70 -0
- package/dist/cli/commands/workspace-migrate.js.map +1 -0
- package/package.json +1 -1
- package/skills/w/commands/fix-git.md +17 -23
|
@@ -1,64 +1,74 @@
|
|
|
1
1
|
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
3
|
export function runBootstrapDsn(paths, input) {
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
if (input.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
11
|
-
if (input.prodDsn) {
|
|
12
|
-
lines.push(`DB_PROD_DSN=${input.prodDsn}`);
|
|
13
|
-
written.push("DB_PROD_DSN");
|
|
14
|
-
}
|
|
15
|
-
if (lines.length === 0) {
|
|
4
|
+
const entries = [];
|
|
5
|
+
if (input.certDsn)
|
|
6
|
+
entries.push({ key: "DB_CERT_DSN", value: input.certDsn });
|
|
7
|
+
if (input.prodDsn)
|
|
8
|
+
entries.push({ key: "DB_PROD_DSN", value: input.prodDsn });
|
|
9
|
+
if (entries.length === 0) {
|
|
16
10
|
return {
|
|
17
11
|
error: "Ni DB_CERT_DSN ni DB_PROD_DSN visibles en el entorno actual. Exportalas en ~/.zshenv (macOS/Linux) o System Environment (Windows) y reabrí Claude Code desde una terminal donde 'echo $DB_CERT_DSN' devuelva valor.",
|
|
18
12
|
exitCode: 2,
|
|
19
13
|
};
|
|
20
14
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
try {
|
|
24
|
-
chmodSync(target, 0o600);
|
|
25
|
-
}
|
|
26
|
-
catch {
|
|
27
|
-
// ignore chmod failures
|
|
28
|
-
}
|
|
29
|
-
return { ok: true, path: target, wrote: written };
|
|
15
|
+
const path = writeDsnValues(paths, entries);
|
|
16
|
+
return { ok: true, path, wrote: entries.map((entry) => entry.key) };
|
|
30
17
|
}
|
|
31
18
|
export function writeDsnValue(paths, input) {
|
|
19
|
+
const path = writeDsnValues(paths, [input]);
|
|
20
|
+
return { ok: true, path, key: input.key };
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Upsert of every entry into dsn.env, preserving the lines this call does not
|
|
24
|
+
* own: the file is shared by every connection the user ever bootstrapped, so a
|
|
25
|
+
* whole-file rewrite would drop the DSNs of the other ones.
|
|
26
|
+
*/
|
|
27
|
+
function writeDsnValues(paths, entries) {
|
|
32
28
|
const target = paths.userDsnFile();
|
|
33
|
-
|
|
34
|
-
const
|
|
29
|
+
let lines = readDsnLines(target);
|
|
30
|
+
for (const entry of entries) {
|
|
31
|
+
lines = upsertDsnLine(lines, entry.key, entry.value);
|
|
32
|
+
}
|
|
35
33
|
mkdirSync(dirname(target), { recursive: true });
|
|
36
|
-
writeFileSync(target, `${
|
|
34
|
+
writeFileSync(target, `${lines.join("\n")}\n`, "utf-8");
|
|
37
35
|
try {
|
|
38
36
|
chmodSync(target, 0o600);
|
|
39
37
|
}
|
|
40
38
|
catch {
|
|
41
|
-
//
|
|
39
|
+
// Best effort: filesystems without POSIX modes (Windows, FAT) must not fail
|
|
40
|
+
// a write that already landed. The DSN is still written.
|
|
42
41
|
}
|
|
43
|
-
return
|
|
42
|
+
return target;
|
|
44
43
|
}
|
|
45
44
|
function readDsnLines(path) {
|
|
46
45
|
if (!existsSync(path))
|
|
47
46
|
return [];
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
const lines = readFileSync(path, "utf-8").split(/\r?\n/);
|
|
48
|
+
// Only the empty tail the final newline produces is dropped. Blank lines
|
|
49
|
+
// inside the file are the user's own grouping of a file this tool shares with
|
|
50
|
+
// them, and an upsert has no business collapsing it.
|
|
51
|
+
if (lines[lines.length - 1] === "")
|
|
52
|
+
lines.pop();
|
|
53
|
+
return lines;
|
|
51
54
|
}
|
|
52
55
|
function upsertDsnLine(lines, key, value) {
|
|
53
56
|
const prefix = `${key}=`;
|
|
57
|
+
const next = [];
|
|
54
58
|
let replaced = false;
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
for (const line of lines) {
|
|
60
|
+
// Compare the line trimmed: an indented assignment still assigns, and
|
|
61
|
+
// leaving it behind would keep a stale credential alive in a file the user
|
|
62
|
+
// believes updated. Duplicates of the same key collapse into the first.
|
|
63
|
+
if (!line.trimStart().startsWith(prefix)) {
|
|
64
|
+
next.push(line);
|
|
65
|
+
continue;
|
|
59
66
|
}
|
|
60
|
-
|
|
61
|
-
|
|
67
|
+
if (replaced)
|
|
68
|
+
continue;
|
|
69
|
+
replaced = true;
|
|
70
|
+
next.push(`${key}=${value}`);
|
|
71
|
+
}
|
|
62
72
|
if (!replaced)
|
|
63
73
|
next.push(`${key}=${value}`);
|
|
64
74
|
return next;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev-bootstrap-dsn-service.js","sourceRoot":"","sources":["../../src/application/dev-bootstrap-dsn-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACxF,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"dev-bootstrap-dsn-service.js","sourceRoot":"","sources":["../../src/application/dev-bootstrap-dsn-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { parse as parseToml } from "smol-toml";
|
|
2
|
+
import { checkSafeRelativePath } from "../domain/safe-path.js";
|
|
3
|
+
const TABLE = "docs";
|
|
4
|
+
export async function resolveDocsCanon(fs, paths, categories) {
|
|
5
|
+
const canon = {};
|
|
6
|
+
// Workspace last: it overrides the user-global default, same order as skills.
|
|
7
|
+
for (const path of [paths.userSkillsToml(), paths.cwdSkillsToml()]) {
|
|
8
|
+
if (!(await fs.exists(path)))
|
|
9
|
+
continue;
|
|
10
|
+
const level = await readDocsTable(fs, path);
|
|
11
|
+
if (!level.ok)
|
|
12
|
+
return level;
|
|
13
|
+
for (const [category, dir] of Object.entries(level.table)) {
|
|
14
|
+
const checked = checkDestination(path, category, dir, categories);
|
|
15
|
+
if (!checked.ok)
|
|
16
|
+
return checked;
|
|
17
|
+
canon[category] = checked.dir;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return { ok: true, canon };
|
|
21
|
+
}
|
|
22
|
+
async function readDocsTable(fs, path) {
|
|
23
|
+
let parsed;
|
|
24
|
+
try {
|
|
25
|
+
parsed = parseToml(await fs.readText(path));
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
return { ok: false, error: `${path} no se puede leer como TOML: ${String(error)}` };
|
|
29
|
+
}
|
|
30
|
+
const table = parsed[TABLE];
|
|
31
|
+
if (table === undefined || table === null)
|
|
32
|
+
return { ok: true, table: {} };
|
|
33
|
+
if (typeof table !== "object" || Array.isArray(table)) {
|
|
34
|
+
return { ok: false, error: `${path}: [${TABLE}] no es una tabla` };
|
|
35
|
+
}
|
|
36
|
+
return { ok: true, table: table };
|
|
37
|
+
}
|
|
38
|
+
function checkDestination(path, category, dir, categories) {
|
|
39
|
+
if (!categories.includes(category)) {
|
|
40
|
+
return {
|
|
41
|
+
ok: false,
|
|
42
|
+
error: `${path}: [${TABLE}] no reconoce '${category}'; las categorías son: ${categories.join(", ")}`,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (typeof dir !== "string") {
|
|
46
|
+
return { ok: false, error: `${path}: [${TABLE}].${category} tiene que ser una ruta de texto` };
|
|
47
|
+
}
|
|
48
|
+
// Same guard every write boundary uses, so a canon can move a category inside
|
|
49
|
+
// the workspace and never outside it.
|
|
50
|
+
const safe = checkSafeRelativePath(dir.replace(/\/+$/, ""));
|
|
51
|
+
if (!safe.ok) {
|
|
52
|
+
return { ok: false, error: `${path}: [${TABLE}].${category} = '${dir}' ${safe.why}` };
|
|
53
|
+
}
|
|
54
|
+
// Inside the workspace is not enough: it has to be DOCUMENTAL. A canon
|
|
55
|
+
// pointing at a dot-directory would publish dossiers into the CLI's own
|
|
56
|
+
// runtime — `.workflow/sessions` is the sharp case, because a published unit
|
|
57
|
+
// named `NNN-export-…` is then enumerated as a session and the workspace grows
|
|
58
|
+
// a phantom open line that shows up in `status` and in the next export's
|
|
59
|
+
// corpus. The tool's own state is not a place a document may be published to.
|
|
60
|
+
if (safe.path.split("/")[0]?.startsWith(".") === true) {
|
|
61
|
+
return {
|
|
62
|
+
ok: false,
|
|
63
|
+
error: `${path}: [${TABLE}].${category} = '${dir}' apunta a un directorio oculto; el canon documental publica documentos, no estado interno de la herramienta`,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return { ok: true, dir: safe.path };
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=docs-canon-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docs-canon-service.js","sourceRoot":"","sources":["../../src/application/docs-canon-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -22,7 +22,58 @@ export function readBootstrapDsn(paths) {
|
|
|
22
22
|
return { path, exists: true, values };
|
|
23
23
|
}
|
|
24
24
|
export function dsnKeyForInstance(instance) {
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
return dsnKeyFromSegments(instanceSegments(instance));
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The variable names that may hold the DSN of `instance`, most specific first:
|
|
29
|
+
* the canonical key, and the one without its leading segment. An alias usually
|
|
30
|
+
* carries an organisation prefix (`qtc-cert`) that the variable the user really
|
|
31
|
+
* exported does not (`DB_CERT_DSN`).
|
|
32
|
+
*
|
|
33
|
+
* Exactly ONE segment is ever dropped. `qtc-cert-ro` may fall back to
|
|
34
|
+
* DB_CERT_RO_DSN but never to DB_RO_DSN, because collapsing an alias to its last
|
|
35
|
+
* segment makes different organisations share one generic name — `acme-prod` and
|
|
36
|
+
* `qtc-prod` would both reach DB_PROD_DSN, and starting a server against another
|
|
37
|
+
* environment's credential is a worse outcome than refusing to start.
|
|
38
|
+
*/
|
|
39
|
+
export function dsnKeyCandidates(instance) {
|
|
40
|
+
const segments = instanceSegments(instance);
|
|
41
|
+
// A single-segment or degenerate alias still yields its canonical key: callers
|
|
42
|
+
// must always have at least one name to probe and to name in errors.
|
|
43
|
+
if (segments.length <= 1)
|
|
44
|
+
return [dsnKeyFromSegments(segments)];
|
|
45
|
+
return [dsnKeyFromSegments(segments), dsnKeyFromSegments(segments.slice(1))];
|
|
46
|
+
}
|
|
47
|
+
function instanceSegments(instance) {
|
|
48
|
+
return normalizeMcpInstance(instance)
|
|
49
|
+
.split("-")
|
|
50
|
+
.filter((segment) => segment.length > 0);
|
|
51
|
+
}
|
|
52
|
+
function dsnKeyFromSegments(segments) {
|
|
53
|
+
return `DB_${segments.join("_").toUpperCase()}_DSN`;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Single resolution path shared by the dbhub launcher and the connection
|
|
57
|
+
* tester. Precedence: the whole candidate list in `env` first, then the whole
|
|
58
|
+
* list in the dsn.env file — an exported variable beats a persisted one, and
|
|
59
|
+
* among exported variables the most specific name wins.
|
|
60
|
+
*
|
|
61
|
+
* Throws whatever `readBootstrapDsn` throws (unreadable dsn.env): each caller
|
|
62
|
+
* decides how that surfaces instead of it being swallowed here.
|
|
63
|
+
*/
|
|
64
|
+
export function resolveDsnFromCandidates(candidates, env, paths) {
|
|
65
|
+
for (const variable of candidates) {
|
|
66
|
+
const fromEnv = env[variable];
|
|
67
|
+
if (fromEnv && fromEnv.length > 0)
|
|
68
|
+
return { dsn: fromEnv, variable, source: "env" };
|
|
69
|
+
}
|
|
70
|
+
// Read once, and only after every candidate missed in the environment.
|
|
71
|
+
const values = readBootstrapDsn(paths).values;
|
|
72
|
+
for (const variable of candidates) {
|
|
73
|
+
const fromFile = values[variable];
|
|
74
|
+
if (fromFile && fromFile.length > 0)
|
|
75
|
+
return { dsn: fromFile, variable, source: "dsn.env" };
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
27
78
|
}
|
|
28
79
|
//# sourceMappingURL=dsn-reader-service.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dsn-reader-service.js","sourceRoot":"","sources":["../../src/application/dsn-reader-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAoB,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAShF,MAAM,UAAU,gBAAgB,CAAC,KAAmB;IAClD,MAAM,IAAI,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;IACjC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACtB,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IAC7C,CAAC;IACD,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACzC,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACvC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QACxD,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,EAAE,IAAI,CAAC;YAAE,SAAS;QACtB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACtC,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC;YAAE,MAAM,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;IACxC,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AACxC,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,QAAqB;IACrD,MAAM,UAAU,GAAG,oBAAoB,CAAC,QAAQ,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"dsn-reader-service.js","sourceRoot":"","sources":["../../src/application/dsn-reader-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
// Launcher that resolves the DSN and spawns `npx -y @bytebase/dbhub
|
|
1
|
+
// Launcher that resolves the DSN and spawns `npx -y @bytebase/dbhub`.
|
|
2
|
+
// stdin/stderr are inherited; stdout is piped so the dbhub startup banner never
|
|
3
|
+
// reaches the MCP JSON-RPC channel (see DbhubBannerFilter).
|
|
2
4
|
import { spawn } from "node:child_process";
|
|
3
5
|
import { normalizeDsnVarName, validateDsnVarName, validateMcpInstance, } from "../domain/mcp-entry.js";
|
|
4
|
-
import {
|
|
6
|
+
import { dsnKeyCandidates, resolveDsnFromCandidates, } from "./dsn-reader-service.js";
|
|
5
7
|
export const DBHUB_DSN_VAR_ENV = "DBHUB_DSN_VAR";
|
|
6
8
|
export class DbhubLauncherError extends Error {
|
|
7
9
|
constructor(message) {
|
|
@@ -10,38 +12,154 @@ export class DbhubLauncherError extends Error {
|
|
|
10
12
|
}
|
|
11
13
|
}
|
|
12
14
|
export function resolveDsn(instance, deps) {
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return { dsn: fromEnv, source: "env" };
|
|
17
|
-
}
|
|
18
|
-
// An unreadable dsn.env (e.g. it is a directory, or a permission/race error)
|
|
19
|
-
// falls through to the DbhubLauncherError below, not a raw fs throw.
|
|
20
|
-
let fromFile;
|
|
15
|
+
const candidates = dsnVarCandidates(instance, deps);
|
|
16
|
+
let resolved = null;
|
|
17
|
+
let fileError = null;
|
|
21
18
|
try {
|
|
22
|
-
|
|
19
|
+
resolved = resolveDsnFromCandidates(candidates, deps.env, deps.paths);
|
|
23
20
|
}
|
|
24
|
-
catch {
|
|
25
|
-
|
|
21
|
+
catch (err) {
|
|
22
|
+
// An unreadable dsn.env (a directory in its place, a permission or race
|
|
23
|
+
// error) surfaces as this launcher's own diagnostic naming the cause,
|
|
24
|
+
// never as a raw fs throw and never as a silent "not found".
|
|
25
|
+
fileError = err instanceof Error ? err.message : String(err);
|
|
26
26
|
}
|
|
27
|
-
if (
|
|
28
|
-
|
|
27
|
+
if (resolved === null) {
|
|
28
|
+
throw new DbhubLauncherError(buildUnresolvedDsnMessage(instance, candidates, deps, fileError));
|
|
29
29
|
}
|
|
30
|
-
|
|
30
|
+
reportNonCanonicalVariable(resolved, candidates, deps);
|
|
31
|
+
return resolved;
|
|
31
32
|
}
|
|
32
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Variable names to probe, most specific first. An explicit DBHUB_DSN_VAR names
|
|
35
|
+
* the variable exactly — no prefix-dropping candidates are derived from it.
|
|
36
|
+
*/
|
|
37
|
+
function dsnVarCandidates(instance, deps) {
|
|
33
38
|
const configured = deps.env[DBHUB_DSN_VAR_ENV];
|
|
34
|
-
if (configured
|
|
35
|
-
|
|
39
|
+
if (configured !== undefined && configured.trim().length > 0) {
|
|
40
|
+
const validation = validateDsnVarName(configured);
|
|
41
|
+
if (!validation.ok) {
|
|
42
|
+
throw new DbhubLauncherError(`[dbhub-mcp-runner] ${DBHUB_DSN_VAR_ENV} inválida '${configured}': ${validation.error}`);
|
|
43
|
+
}
|
|
44
|
+
return [normalizeDsnVarName(validation.value)];
|
|
36
45
|
}
|
|
37
|
-
const
|
|
38
|
-
if (
|
|
39
|
-
throw new DbhubLauncherError(`[dbhub-mcp-runner]
|
|
46
|
+
const [canonical, ...rest] = dsnKeyCandidates(instance);
|
|
47
|
+
if (canonical === undefined) {
|
|
48
|
+
throw new DbhubLauncherError(`[dbhub-mcp-runner] la conexión '${instance}' no deriva ninguna variable DSN`);
|
|
49
|
+
}
|
|
50
|
+
return [canonical, ...rest];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* A DSN found under a non-canonical name is a resolution the user did not
|
|
54
|
+
* spell out: say which variable was used and which one was missing, so the
|
|
55
|
+
* mismatch never passes as a silent default.
|
|
56
|
+
*/
|
|
57
|
+
function reportNonCanonicalVariable(resolved, candidates, deps) {
|
|
58
|
+
const canonical = candidates[0];
|
|
59
|
+
if (resolved.variable === canonical)
|
|
60
|
+
return;
|
|
61
|
+
const origin = resolved.source === "env" ? "process.env" : deps.paths.userDsnFile();
|
|
62
|
+
stderrWriter(deps)(`[dbhub-mcp-runner] usando ${resolved.variable} (desde ${origin}) porque ${canonical} no está definida.\n`);
|
|
63
|
+
}
|
|
64
|
+
function buildUnresolvedDsnMessage(instance, candidates, deps, fileError) {
|
|
65
|
+
const dsnFile = deps.paths.userDsnFile();
|
|
66
|
+
const probed = candidates.length === 1 ? "la variable" : "estas variables, en orden";
|
|
67
|
+
const where = fileError === null
|
|
68
|
+
? `primero en process.env y después en ${dsnFile}`
|
|
69
|
+
: `primero en process.env; ${dsnFile} no se pudo leer (${fileError})`;
|
|
70
|
+
return [
|
|
71
|
+
`[dbhub-mcp-runner] no encontré el DSN de la conexión '${instance}'.`,
|
|
72
|
+
`Probé ${probed}: ${candidates.join(", ")} — ${where}.`,
|
|
73
|
+
`Salidas: exportá ${candidates[0]} en ~/.zshenv (macOS/Linux) o System Environment (Windows)`,
|
|
74
|
+
`y reabrí el host desde una terminal donde 'echo $${candidates[0]}' devuelva valor;`,
|
|
75
|
+
`o nombrá la variable exacta con ${DBHUB_DSN_VAR_ENV} en el entorno del servidor MCP;`,
|
|
76
|
+
`o registrá la conexión con 'aw mcp setup --instance ${instance} --dsn-var <NOMBRE>'.`,
|
|
77
|
+
].join(" ");
|
|
78
|
+
}
|
|
79
|
+
function stderrWriter(deps) {
|
|
80
|
+
return (deps.stderr ??
|
|
81
|
+
((chunk) => {
|
|
82
|
+
process.stderr.write(chunk);
|
|
83
|
+
}));
|
|
84
|
+
}
|
|
85
|
+
const NEWLINE = 0x0a;
|
|
86
|
+
const OPEN_BRACE = 0x7b;
|
|
87
|
+
const EMPTY_CHUNK = Buffer.alloc(0);
|
|
88
|
+
/** Narrow enough that a banner containing it verbatim is not a realistic risk. */
|
|
89
|
+
const JSONRPC_MARKER = Buffer.from('{"jsonrpc"');
|
|
90
|
+
/**
|
|
91
|
+
* Splits dbhub's stdout into banner (stderr) and protocol (stdout).
|
|
92
|
+
*
|
|
93
|
+
* `@bytebase/dbhub` prints an ASCII banner on stdout before speaking JSON-RPC,
|
|
94
|
+
* which corrupts the stdio channel every MCP client reads. Complete lines are
|
|
95
|
+
* routed to stderr until the first one whose first non-blank byte is `{`; from
|
|
96
|
+
* that line on the filter is `started` and the caller must hand the pipe over
|
|
97
|
+
* raw, so the protocol is never re-sliced. Pure on purpose: testable without
|
|
98
|
+
* spawning a process.
|
|
99
|
+
*/
|
|
100
|
+
export class DbhubBannerFilter {
|
|
101
|
+
pending = EMPTY_CHUNK;
|
|
102
|
+
jsonStarted = false;
|
|
103
|
+
/** True once the JSON-RPC stream began: the caller switches to raw passthrough. */
|
|
104
|
+
get started() {
|
|
105
|
+
return this.jsonStarted;
|
|
106
|
+
}
|
|
107
|
+
push(chunk) {
|
|
108
|
+
if (this.jsonStarted)
|
|
109
|
+
return { stdout: chunk, stderr: EMPTY_CHUNK };
|
|
110
|
+
this.pending = this.pending.length === 0 ? chunk : Buffer.concat([this.pending, chunk]);
|
|
111
|
+
// A banner that does not end in a newline would otherwise swallow the first
|
|
112
|
+
// message glued to its tail, so the protocol marker is a second and narrower
|
|
113
|
+
// cut point: bytes before it are banner, bytes from it on are protocol.
|
|
114
|
+
const marker = this.pending.indexOf(JSONRPC_MARKER);
|
|
115
|
+
const banner = [];
|
|
116
|
+
let consumed = 0;
|
|
117
|
+
let newline = this.pending.indexOf(NEWLINE, consumed);
|
|
118
|
+
while (newline !== -1) {
|
|
119
|
+
const line = this.pending.subarray(consumed, newline + 1);
|
|
120
|
+
// A line that already opens the protocol is handed over from its first
|
|
121
|
+
// byte, blanks included: the marker cut is for a banner glued to the
|
|
122
|
+
// message, never an excuse to re-slice bytes that are already protocol.
|
|
123
|
+
if (startsJsonRpc(line))
|
|
124
|
+
return this.startAt(consumed, consumed, banner);
|
|
125
|
+
if (marker !== -1 && marker < newline)
|
|
126
|
+
return this.startAt(marker, consumed, banner);
|
|
127
|
+
banner.push(line);
|
|
128
|
+
consumed = newline + 1;
|
|
129
|
+
newline = this.pending.indexOf(NEWLINE, consumed);
|
|
130
|
+
}
|
|
131
|
+
if (marker !== -1)
|
|
132
|
+
return this.startAt(marker, consumed, banner);
|
|
133
|
+
this.pending = Buffer.from(this.pending.subarray(consumed));
|
|
134
|
+
return { stdout: EMPTY_CHUNK, stderr: Buffer.concat(banner) };
|
|
135
|
+
}
|
|
136
|
+
/** Protocol starts at `cut`: [consumed, cut) is banner, [cut, …) goes out verbatim. */
|
|
137
|
+
startAt(cut, consumed, banner) {
|
|
138
|
+
this.jsonStarted = true;
|
|
139
|
+
if (cut > consumed)
|
|
140
|
+
banner.push(this.pending.subarray(consumed, cut));
|
|
141
|
+
const stdout = Buffer.from(this.pending.subarray(cut));
|
|
142
|
+
this.pending = EMPTY_CHUNK;
|
|
143
|
+
return { stdout, stderr: Buffer.concat(banner) };
|
|
144
|
+
}
|
|
145
|
+
/** Stream ended: anything still buffered never became a JSON line, so it is banner. */
|
|
146
|
+
end() {
|
|
147
|
+
const stderr = this.pending;
|
|
148
|
+
this.pending = EMPTY_CHUNK;
|
|
149
|
+
return { stdout: EMPTY_CHUNK, stderr };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function startsJsonRpc(line) {
|
|
153
|
+
for (const byte of line) {
|
|
154
|
+
// Skip leading blanks (space, tab, CR, LF); a fully blank line is banner.
|
|
155
|
+
if (byte === 0x20 || byte === 0x09 || byte === 0x0d || byte === NEWLINE)
|
|
156
|
+
continue;
|
|
157
|
+
return byte === OPEN_BRACE;
|
|
40
158
|
}
|
|
41
|
-
return
|
|
159
|
+
return false;
|
|
42
160
|
}
|
|
43
161
|
/**
|
|
44
|
-
* Resolves DSN and spawns `npx -y @bytebase/dbhub
|
|
162
|
+
* Resolves the DSN and spawns `npx -y @bytebase/dbhub`.
|
|
45
163
|
* Resolves only when the spawned child exits.
|
|
46
164
|
*/
|
|
47
165
|
export async function runDbhubLauncher(input) {
|
|
@@ -53,25 +171,66 @@ export async function runDbhubLauncher(input) {
|
|
|
53
171
|
const { dsn } = resolveDsn(instance, input.deps);
|
|
54
172
|
const isWin = input.deps.platform === "win32";
|
|
55
173
|
const cmd = isWin ? "npx.cmd" : "npx";
|
|
174
|
+
const protocol = input.deps.stdout ?? process.stdout;
|
|
56
175
|
return new Promise((resolve, reject) => {
|
|
57
176
|
const child = spawn(cmd, ["-y", "@bytebase/dbhub"], {
|
|
58
|
-
stdio: "inherit",
|
|
177
|
+
stdio: ["inherit", "pipe", "inherit"],
|
|
59
178
|
env: { ...input.deps.env, DSN: dsn },
|
|
60
179
|
shell: isWin,
|
|
61
180
|
});
|
|
181
|
+
if (child.stdout === null) {
|
|
182
|
+
reject(new DbhubLauncherError("[dbhub-mcp-runner] no pude capturar el stdout de dbhub"));
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
forwardDbhubStdout(child.stdout, {
|
|
186
|
+
protocol,
|
|
187
|
+
diagnostics: stderrWriter(input.deps),
|
|
188
|
+
});
|
|
62
189
|
child.on("error", (err) => reject(new DbhubLauncherError(`[dbhub-mcp-runner] ${err.message}`)));
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
190
|
+
// `close`, not `exit`: `exit` fires before the child's stdout is drained, so
|
|
191
|
+
// the tail of the protocol would still be in flight. And piping made a second
|
|
192
|
+
// loss possible that `stdio: "inherit"` ruled out structurally — the child
|
|
193
|
+
// used to write fd 1 itself, whereas now every byte is re-written through OUR
|
|
194
|
+
// stdout, and the CLI ends with process.exit(), which drops whatever is still
|
|
195
|
+
// queued on a pipe. The zero-length write is a drain barrier: its callback
|
|
196
|
+
// runs once everything before it reached the OS.
|
|
197
|
+
child.on("close", (code, signal) => {
|
|
198
|
+
// Honor signal: emulate `process.kill(process.pid, signal)` from JS launcher
|
|
199
|
+
// by mapping to a non-zero exit. The CLI command will exit with that code.
|
|
200
|
+
const exitCode = signal ? 128 + (signalToNumber(signal) ?? 0) : (code ?? 0);
|
|
201
|
+
protocol.write(EMPTY_CHUNK, () => resolve({ exitCode }));
|
|
72
202
|
});
|
|
73
203
|
});
|
|
74
204
|
}
|
|
205
|
+
/**
|
|
206
|
+
* Wires a dbhub stdout stream to this process's channels: banner lines to
|
|
207
|
+
* diagnostics, protocol bytes to the protocol channel. Takes its channels as
|
|
208
|
+
* parameters so the handover can be exercised without spawning anything.
|
|
209
|
+
*/
|
|
210
|
+
export function forwardDbhubStdout(source, channels) {
|
|
211
|
+
const filter = new DbhubBannerFilter();
|
|
212
|
+
const onData = (chunk) => {
|
|
213
|
+
const routed = filter.push(chunk);
|
|
214
|
+
if (routed.stderr.length > 0)
|
|
215
|
+
channels.diagnostics(routed.stderr.toString("utf-8"));
|
|
216
|
+
if (routed.stdout.length > 0)
|
|
217
|
+
channels.protocol.write(routed.stdout);
|
|
218
|
+
if (!filter.started)
|
|
219
|
+
return;
|
|
220
|
+
// Protocol started: pause before detaching, so no chunk can be emitted
|
|
221
|
+
// between removing the listener and attaching the pipe. `end: false` keeps
|
|
222
|
+
// the child's EOF from closing our own stdout.
|
|
223
|
+
source.pause();
|
|
224
|
+
source.removeListener("data", onData);
|
|
225
|
+
source.pipe(channels.protocol, { end: false });
|
|
226
|
+
};
|
|
227
|
+
source.on("data", onData);
|
|
228
|
+
source.on("end", () => {
|
|
229
|
+
const rest = filter.end();
|
|
230
|
+
if (rest.stderr.length > 0)
|
|
231
|
+
channels.diagnostics(rest.stderr.toString("utf-8"));
|
|
232
|
+
});
|
|
233
|
+
}
|
|
75
234
|
function signalToNumber(signal) {
|
|
76
235
|
// Common signals; fallback to null (will produce exitCode 128 + 0 = 128).
|
|
77
236
|
const map = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-dbhub-launcher.js","sourceRoot":"","sources":["../../src/application/mcp-dbhub-launcher.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"mcp-dbhub-launcher.js","sourceRoot":"","sources":["../../src/application/mcp-dbhub-launcher.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isDeepStrictEqual } from "node:util";
|
|
2
2
|
import { buildMcpEntry, normalizeMcpInstance, } from "../domain/mcp-entry.js";
|
|
3
|
-
import { dsnKeyForInstance, readBootstrapDsn } from "./dsn-reader-service.js";
|
|
3
|
+
import { dsnKeyCandidates, dsnKeyForInstance, readBootstrapDsn } from "./dsn-reader-service.js";
|
|
4
4
|
import { readMcpEntry } from "./mcp-host-reader.js";
|
|
5
5
|
import { resolveScopeDir } from "./mcp-scope-common.js";
|
|
6
6
|
export function runMcpDoctor(env, paths, input) {
|
|
@@ -22,10 +22,19 @@ export function runMcpDoctor(env, paths, input) {
|
|
|
22
22
|
return { scope: input.scope, scope_dir: scopeDir, reports, summary };
|
|
23
23
|
}
|
|
24
24
|
function buildReport(env, host, instance, scopeDir, dsn, scope, dsnVars) {
|
|
25
|
-
const
|
|
26
|
-
const entry = buildMcpEntry(instance,
|
|
25
|
+
const declaredVar = dsnVars?.[normalizeMcpInstance(instance)];
|
|
26
|
+
const entry = buildMcpEntry(instance, declaredVar);
|
|
27
27
|
const snapshot = readMcpEntry(host, scopeDir, entry.name, scope);
|
|
28
|
-
|
|
28
|
+
// Probe the same chain the launcher probes, in the same order. A doctor that
|
|
29
|
+
// only knows the canonical name reports `missing-dsn` for a connection the
|
|
30
|
+
// launcher would start happily — a diagnosis contradicting the runtime is
|
|
31
|
+
// worse than none. What gets reported is the variable that carries the value,
|
|
32
|
+
// never the canonical name assumed.
|
|
33
|
+
const candidates = declaredVar !== undefined ? [declaredVar] : dsnKeyCandidates(instance);
|
|
34
|
+
const resolvedKey = candidates.find((key) => Boolean(env.get(key))) ??
|
|
35
|
+
(dsn.exists ? candidates.find((key) => Boolean(dsn.values[key])) : undefined);
|
|
36
|
+
const dsnKey = resolvedKey ?? candidates[0] ?? dsnKeyForInstance(instance);
|
|
37
|
+
const dsnPresent = resolvedKey !== undefined;
|
|
29
38
|
const dsnInfo = {
|
|
30
39
|
path: dsn.path,
|
|
31
40
|
exists: dsn.exists,
|