@tacuchi/agent-workflow-cli 4.6.0 → 5.0.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.
Files changed (65) hide show
  1. package/dist/application/dev-graduate-service.d.ts +33 -4
  2. package/dist/application/dev-graduate-service.d.ts.map +1 -1
  3. package/dist/application/dev-graduate-service.js +225 -41
  4. package/dist/application/dev-graduate-service.js.map +1 -1
  5. package/dist/application/dsn-reader-service.d.ts +10 -0
  6. package/dist/application/dsn-reader-service.d.ts.map +1 -0
  7. package/dist/application/dsn-reader-service.js +26 -0
  8. package/dist/application/dsn-reader-service.js.map +1 -0
  9. package/dist/application/graduation-check-service.d.ts +19 -0
  10. package/dist/application/graduation-check-service.d.ts.map +1 -0
  11. package/dist/application/graduation-check-service.js +145 -0
  12. package/dist/application/graduation-check-service.js.map +1 -0
  13. package/dist/application/hub-init-service.d.ts +47 -0
  14. package/dist/application/hub-init-service.d.ts.map +1 -0
  15. package/dist/application/hub-init-service.js +92 -0
  16. package/dist/application/hub-init-service.js.map +1 -0
  17. package/dist/application/mcp-doctor-service.d.ts +23 -0
  18. package/dist/application/mcp-doctor-service.d.ts.map +1 -0
  19. package/dist/application/mcp-doctor-service.js +123 -0
  20. package/dist/application/mcp-doctor-service.js.map +1 -0
  21. package/dist/application/mcp-host-reader.d.ts +13 -0
  22. package/dist/application/mcp-host-reader.d.ts.map +1 -0
  23. package/dist/application/mcp-host-reader.js +89 -0
  24. package/dist/application/mcp-host-reader.js.map +1 -0
  25. package/dist/application/mcp-host-writer.d.ts +13 -0
  26. package/dist/application/mcp-host-writer.d.ts.map +1 -0
  27. package/dist/application/mcp-host-writer.js +196 -0
  28. package/dist/application/mcp-host-writer.js.map +1 -0
  29. package/dist/application/mcp-setup-service.d.ts +31 -0
  30. package/dist/application/mcp-setup-service.d.ts.map +1 -0
  31. package/dist/application/mcp-setup-service.js +69 -0
  32. package/dist/application/mcp-setup-service.js.map +1 -0
  33. package/dist/application/paths-service.d.ts +22 -0
  34. package/dist/application/paths-service.d.ts.map +1 -1
  35. package/dist/application/paths-service.js +33 -1
  36. package/dist/application/paths-service.js.map +1 -1
  37. package/dist/application/visibility-doctor-service.d.ts +34 -0
  38. package/dist/application/visibility-doctor-service.d.ts.map +1 -0
  39. package/dist/application/visibility-doctor-service.js +192 -0
  40. package/dist/application/visibility-doctor-service.js.map +1 -0
  41. package/dist/cli/commands/graduation-check.d.ts +3 -0
  42. package/dist/cli/commands/graduation-check.d.ts.map +1 -0
  43. package/dist/cli/commands/graduation-check.js +11 -0
  44. package/dist/cli/commands/graduation-check.js.map +1 -0
  45. package/dist/cli/commands/hub-init.d.ts +3 -0
  46. package/dist/cli/commands/hub-init.d.ts.map +1 -0
  47. package/dist/cli/commands/hub-init.js +95 -0
  48. package/dist/cli/commands/hub-init.js.map +1 -0
  49. package/dist/cli/commands/mcp.d.ts.map +1 -1
  50. package/dist/cli/commands/mcp.js +159 -37
  51. package/dist/cli/commands/mcp.js.map +1 -1
  52. package/dist/cli/commands/visibility.d.ts +3 -0
  53. package/dist/cli/commands/visibility.d.ts.map +1 -0
  54. package/dist/cli/commands/visibility.js +42 -0
  55. package/dist/cli/commands/visibility.js.map +1 -0
  56. package/dist/cli/commands/wave4d-simple.d.ts.map +1 -1
  57. package/dist/cli/commands/wave4d-simple.js +9 -4
  58. package/dist/cli/commands/wave4d-simple.js.map +1 -1
  59. package/dist/cli/main.js +6 -0
  60. package/dist/cli/main.js.map +1 -1
  61. package/dist/domain/mcp-entry.d.ts +44 -0
  62. package/dist/domain/mcp-entry.d.ts.map +1 -0
  63. package/dist/domain/mcp-entry.js +13 -0
  64. package/dist/domain/mcp-entry.js.map +1 -0
  65. package/package.json +3 -2
@@ -0,0 +1,47 @@
1
+ import type { EnvPort } from "../ports/env.js";
2
+ import type { FileSystemPort } from "../ports/file-system.js";
3
+ import { type MultirootError, type MultirootResult } from "./multiroot-service.js";
4
+ import type { PathsService } from "./paths-service.js";
5
+ import { type ProjectMdUpsertError, type ProjectMdUpsertOutput } from "./project-md-upsert-service.js";
6
+ export interface HubInitFuente {
7
+ alias: string;
8
+ path: string;
9
+ }
10
+ export interface HubInitInput {
11
+ proyecto: string;
12
+ fuentes: HubInitFuente[];
13
+ workingBranches: Record<string, string>;
14
+ mainBranch?: string;
15
+ workspace?: string;
16
+ skipAttach?: boolean;
17
+ dryRun?: boolean;
18
+ }
19
+ export interface HubInitProjectMdPreview {
20
+ dry_run_preview: {
21
+ fuentes: number;
22
+ mode: "hub";
23
+ };
24
+ }
25
+ export interface HubInitAttachSkipped {
26
+ skipped: true;
27
+ reason: string;
28
+ }
29
+ export interface HubInitAttachPreview {
30
+ dry_run_preview: {
31
+ paths: string[];
32
+ workspace: string;
33
+ };
34
+ }
35
+ export interface HubInitResult {
36
+ ok: boolean;
37
+ dry_run: boolean;
38
+ workspace: string;
39
+ project_md: ProjectMdUpsertOutput | ProjectMdUpsertError | HubInitProjectMdPreview;
40
+ attach_multiroot: MultirootResult | MultirootError | HubInitAttachSkipped | HubInitAttachPreview;
41
+ }
42
+ export interface HubInitInputError {
43
+ error: string;
44
+ hint?: string;
45
+ }
46
+ export declare function runHubInit(fs: FileSystemPort, env: EnvPort, paths: PathsService, input: HubInitInput): Promise<HubInitResult | HubInitInputError>;
47
+ //# sourceMappingURL=hub-init-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hub-init-service.d.ts","sourceRoot":"","sources":["../../src/application/hub-init-service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,eAAe,EAAgB,MAAM,wBAAwB,CAAC;AACjG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAE3B,MAAM,gCAAgC,CAAC;AAExC,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,uBAAuB;IACtC,eAAe,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,KAAK,CAAA;KAAE,CAAC;CACnD;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,IAAI,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,oBAAoB;IACnC,eAAe,EAAE;QAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;CACzD;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,qBAAqB,GAAG,oBAAoB,GAAG,uBAAuB,CAAC;IACnF,gBAAgB,EAAE,eAAe,GAAG,cAAc,GAAG,oBAAoB,GAAG,oBAAoB,CAAC;CAClG;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,UAAU,CAC9B,EAAE,EAAE,cAAc,EAClB,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,YAAY,EACnB,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,aAAa,GAAG,iBAAiB,CAAC,CAgE5C"}
@@ -0,0 +1,92 @@
1
+ import { resolve } from "node:path";
2
+ import { runMultiroot } from "./multiroot-service.js";
3
+ import { runProjectMdUpsertWrite, } from "./project-md-upsert-service.js";
4
+ export async function runHubInit(fs, env, paths, input) {
5
+ const validation = validateInput(input);
6
+ if (validation)
7
+ return validation;
8
+ const workspace = input.workspace ? resolve(input.workspace) : resolve(env.cwd());
9
+ if (input.dryRun) {
10
+ return {
11
+ ok: true,
12
+ dry_run: true,
13
+ workspace,
14
+ project_md: { dry_run_preview: { fuentes: input.fuentes.length, mode: "hub" } },
15
+ attach_multiroot: input.skipAttach
16
+ ? { skipped: true, reason: "skip-attach flag" }
17
+ : {
18
+ dry_run_preview: { paths: input.fuentes.map((f) => f.path), workspace },
19
+ },
20
+ };
21
+ }
22
+ const targetEnv = workspace !== resolve(env.cwd()) ? overrideCwd(env, workspace) : env;
23
+ const projectMd = await runProjectMdUpsertWrite(fs, targetEnv, paths, {
24
+ op: "init",
25
+ mode: "hub",
26
+ proyecto: input.proyecto,
27
+ fuentes: input.fuentes.map((f) => ({ alias: f.alias, path: f.path })),
28
+ workingBranches: input.workingBranches,
29
+ ...(input.mainBranch !== undefined ? { mainBranch: input.mainBranch } : {}),
30
+ verbose: true,
31
+ });
32
+ if ("error" in projectMd) {
33
+ return {
34
+ ok: false,
35
+ dry_run: false,
36
+ workspace,
37
+ project_md: projectMd,
38
+ attach_multiroot: { skipped: true, reason: "project_md_failed" },
39
+ };
40
+ }
41
+ if (input.skipAttach) {
42
+ return {
43
+ ok: projectMd.ok,
44
+ dry_run: false,
45
+ workspace,
46
+ project_md: projectMd,
47
+ attach_multiroot: { skipped: true, reason: "skip-attach flag" },
48
+ };
49
+ }
50
+ const attach = await runMultiroot(fs, targetEnv, paths, "attach", {
51
+ fromSources: true,
52
+ workspace,
53
+ });
54
+ const attachOk = !("error" in attach);
55
+ return {
56
+ ok: projectMd.ok && attachOk,
57
+ dry_run: false,
58
+ workspace,
59
+ project_md: projectMd,
60
+ attach_multiroot: attach,
61
+ };
62
+ }
63
+ function overrideCwd(env, cwd) {
64
+ return {
65
+ get: (k) => env.get(k),
66
+ homeDir: () => env.homeDir(),
67
+ cwd: () => cwd,
68
+ };
69
+ }
70
+ function validateInput(input) {
71
+ if (!input.proyecto || input.proyecto.trim().length === 0) {
72
+ return { error: "missing_proyecto", hint: "--proyecto es obligatorio" };
73
+ }
74
+ if (!input.fuentes || input.fuentes.length < 2) {
75
+ return {
76
+ error: "insufficient_fuentes",
77
+ hint: "hub-init requiere mínimo 2 fuentes (--fuente alias:path repetible)",
78
+ };
79
+ }
80
+ const aliases = new Set();
81
+ for (const f of input.fuentes) {
82
+ if (!f.alias || !f.path) {
83
+ return { error: "invalid_fuente", hint: `fuente sin alias o path: ${JSON.stringify(f)}` };
84
+ }
85
+ if (aliases.has(f.alias)) {
86
+ return { error: "duplicate_alias", hint: `alias duplicado: ${f.alias}` };
87
+ }
88
+ aliases.add(f.alias);
89
+ }
90
+ return null;
91
+ }
92
+ //# sourceMappingURL=hub-init-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hub-init-service.js","sourceRoot":"","sources":["../../src/application/hub-init-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import { type McpDriftReport, type McpHost, type McpInstance } from "../domain/mcp-entry.js";
2
+ import type { EnvPort } from "../ports/env.js";
3
+ import type { PathsService } from "./paths-service.js";
4
+ export interface McpDoctorInput {
5
+ hosts: McpHost[];
6
+ instances: McpInstance[];
7
+ scope: "workspace" | "global";
8
+ workspace?: string;
9
+ }
10
+ export interface McpDoctorResult {
11
+ scope: "workspace" | "global";
12
+ scope_dir: string;
13
+ reports: McpDriftReport[];
14
+ summary: {
15
+ ok: number;
16
+ missing_mcp: number;
17
+ dsn_mismatch: number;
18
+ missing_dsn: number;
19
+ extra: number;
20
+ };
21
+ }
22
+ export declare function runMcpDoctor(env: EnvPort, paths: PathsService, input: McpDoctorInput): McpDoctorResult;
23
+ //# sourceMappingURL=mcp-doctor-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-doctor-service.d.ts","sourceRoot":"","sources":["../../src/application/mcp-doctor-service.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,OAAO,EACZ,KAAK,WAAW,EAEjB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAG/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,SAAS,EAAE,WAAW,EAAE,CAAC;IACzB,KAAK,EAAE,WAAW,GAAG,QAAQ,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,WAAW,GAAG,QAAQ,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,OAAO,EAAE;QACP,EAAE,EAAE,MAAM,CAAC;QACX,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;QACrB,WAAW,EAAE,MAAM,CAAC;QACpB,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;CACH;AAED,wBAAgB,YAAY,CAC1B,GAAG,EAAE,OAAO,EACZ,KAAK,EAAE,YAAY,EACnB,KAAK,EAAE,cAAc,GACpB,eAAe,CAoBjB"}
@@ -0,0 +1,123 @@
1
+ import { homedir } from "node:os";
2
+ import { resolve } from "node:path";
3
+ import { buildMcpEntry, } from "../domain/mcp-entry.js";
4
+ import { dsnKeyForInstance, readBootstrapDsn } from "./dsn-reader-service.js";
5
+ import { readMcpEntry } from "./mcp-host-reader.js";
6
+ export function runMcpDoctor(env, paths, input) {
7
+ const scopeDir = resolveScopeDir(env, input);
8
+ const dsn = readBootstrapDsn(paths);
9
+ const reports = [];
10
+ for (const host of input.hosts) {
11
+ for (const instance of input.instances) {
12
+ reports.push(buildReport(host, instance, scopeDir, dsn, input.scope));
13
+ }
14
+ }
15
+ const summary = {
16
+ ok: reports.filter((r) => r.status === "ok").length,
17
+ missing_mcp: reports.filter((r) => r.status === "missing-mcp").length,
18
+ dsn_mismatch: reports.filter((r) => r.status === "dsn-mismatch").length,
19
+ missing_dsn: reports.filter((r) => r.status === "missing-dsn").length,
20
+ extra: reports.filter((r) => r.status === "extra-entry").length,
21
+ };
22
+ return { scope: input.scope, scope_dir: scopeDir, reports, summary };
23
+ }
24
+ function buildReport(host, instance, scopeDir, dsn, scope) {
25
+ const entry = buildMcpEntry(instance);
26
+ const snapshot = readMcpEntry(host, scopeDir, entry.name);
27
+ const dsnKey = dsnKeyForInstance(instance);
28
+ const dsnPresent = dsn.exists && Boolean(dsn.values[dsnKey]);
29
+ const dsnInfo = {
30
+ path: dsn.path,
31
+ exists: dsn.exists,
32
+ key: dsnKey,
33
+ present: dsnPresent,
34
+ };
35
+ if (!snapshot.exists) {
36
+ return {
37
+ host,
38
+ instance,
39
+ scope,
40
+ target: snapshot.target,
41
+ dsn: dsnInfo,
42
+ mcp: { name: entry.name, present: false, matches: false },
43
+ status: dsnPresent ? "missing-mcp" : "missing-dsn",
44
+ detail: dsnPresent
45
+ ? `Falta entrada MCP '${entry.name}' en ${snapshot.target}`
46
+ : `Ni DSN ni MCP registrados para ${instance}`,
47
+ };
48
+ }
49
+ const matches = matchesEntry(snapshot, entry);
50
+ if (!dsnPresent) {
51
+ return {
52
+ host,
53
+ instance,
54
+ scope,
55
+ target: snapshot.target,
56
+ dsn: dsnInfo,
57
+ mcp: { name: entry.name, present: true, matches },
58
+ status: "dsn-mismatch",
59
+ detail: `MCP '${entry.name}' registrado pero ${dsnKey} no está en ${dsn.path}`,
60
+ };
61
+ }
62
+ if (!matches) {
63
+ return {
64
+ host,
65
+ instance,
66
+ scope,
67
+ target: snapshot.target,
68
+ dsn: dsnInfo,
69
+ mcp: { name: entry.name, present: true, matches: false },
70
+ status: "extra-entry",
71
+ detail: `Entrada '${entry.name}' difiere del shape esperado (command/args/env)`,
72
+ };
73
+ }
74
+ return {
75
+ host,
76
+ instance,
77
+ scope,
78
+ target: snapshot.target,
79
+ dsn: dsnInfo,
80
+ mcp: { name: entry.name, present: true, matches: true },
81
+ status: "ok",
82
+ };
83
+ }
84
+ function matchesEntry(snapshot, entry) {
85
+ if (snapshot.command !== entry.command)
86
+ return false;
87
+ if (!arraysEqual(snapshot.args ?? [], entry.args))
88
+ return false;
89
+ if (!recordsEqual(snapshot.env ?? {}, entry.env))
90
+ return false;
91
+ return true;
92
+ }
93
+ function arraysEqual(a, b) {
94
+ if (a.length !== b.length)
95
+ return false;
96
+ for (let i = 0; i < a.length; i += 1) {
97
+ if (a[i] !== b[i])
98
+ return false;
99
+ }
100
+ return true;
101
+ }
102
+ function recordsEqual(a, b) {
103
+ const keysA = Object.keys(a).sort();
104
+ const keysB = Object.keys(b).sort();
105
+ if (keysA.length !== keysB.length)
106
+ return false;
107
+ for (let i = 0; i < keysA.length; i += 1) {
108
+ if (keysA[i] !== keysB[i])
109
+ return false;
110
+ const k = keysA[i] ?? "";
111
+ if (a[k] !== b[k])
112
+ return false;
113
+ }
114
+ return true;
115
+ }
116
+ function resolveScopeDir(env, input) {
117
+ if (input.scope === "global")
118
+ return homedir();
119
+ if (input.workspace)
120
+ return resolve(input.workspace);
121
+ return resolve(env.cwd());
122
+ }
123
+ //# sourceMappingURL=mcp-doctor-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-doctor-service.js","sourceRoot":"","sources":["../../src/application/mcp-doctor-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { McpHost } from "../domain/mcp-entry.js";
2
+ export interface McpEntrySnapshot {
3
+ host: McpHost;
4
+ target: string;
5
+ name: string;
6
+ exists: boolean;
7
+ command?: string;
8
+ args?: string[];
9
+ env?: Record<string, string>;
10
+ raw?: unknown;
11
+ }
12
+ export declare function readMcpEntry(host: McpHost, scopeDir: string, name: string): McpEntrySnapshot;
13
+ //# sourceMappingURL=mcp-host-reader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-host-reader.d.ts","sourceRoot":"","sources":["../../src/application/mcp-host-reader.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAEtD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,gBAAgB,CAG5F"}
@@ -0,0 +1,89 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { parse as parseToml } from "smol-toml";
4
+ export function readMcpEntry(host, scopeDir, name) {
5
+ if (host === "claude")
6
+ return readClaudeMcpEntry(scopeDir, name);
7
+ return readCodexMcpEntry(scopeDir, name);
8
+ }
9
+ function readClaudeMcpEntry(scopeDir, name) {
10
+ const target = join(scopeDir, ".claude", "settings.json");
11
+ if (!existsSync(target)) {
12
+ return { host: "claude", target, name, exists: false };
13
+ }
14
+ const text = readFileSync(target, "utf-8");
15
+ if (text.trim().length === 0) {
16
+ return { host: "claude", target, name, exists: false };
17
+ }
18
+ let data;
19
+ try {
20
+ data = JSON.parse(text);
21
+ }
22
+ catch {
23
+ return { host: "claude", target, name, exists: false };
24
+ }
25
+ const mcpServers = (data.mcpServers ?? {});
26
+ const entry = mcpServers[name];
27
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
28
+ return { host: "claude", target, name, exists: false };
29
+ }
30
+ const e = entry;
31
+ return {
32
+ host: "claude",
33
+ target,
34
+ name,
35
+ exists: true,
36
+ ...(typeof e.command === "string" ? { command: e.command } : {}),
37
+ ...(Array.isArray(e.args)
38
+ ? { args: e.args.filter((x) => typeof x === "string") }
39
+ : {}),
40
+ ...(typeof e.env === "object" && e.env !== null ? { env: toStringRecord(e.env) } : {}),
41
+ raw: e,
42
+ };
43
+ }
44
+ function readCodexMcpEntry(scopeDir, name) {
45
+ const target = join(scopeDir, ".codex", "config.toml");
46
+ if (!existsSync(target)) {
47
+ return { host: "codex", target, name, exists: false };
48
+ }
49
+ const text = readFileSync(target, "utf-8");
50
+ if (text.trim().length === 0) {
51
+ return { host: "codex", target, name, exists: false };
52
+ }
53
+ let data;
54
+ try {
55
+ data = parseToml(text);
56
+ }
57
+ catch {
58
+ return { host: "codex", target, name, exists: false };
59
+ }
60
+ const mcpServers = (data.mcp_servers ?? {});
61
+ const entry = mcpServers[name];
62
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
63
+ return { host: "codex", target, name, exists: false };
64
+ }
65
+ const e = entry;
66
+ return {
67
+ host: "codex",
68
+ target,
69
+ name,
70
+ exists: true,
71
+ ...(typeof e.command === "string" ? { command: e.command } : {}),
72
+ ...(Array.isArray(e.args)
73
+ ? { args: e.args.filter((x) => typeof x === "string") }
74
+ : {}),
75
+ ...(typeof e.env === "object" && e.env !== null ? { env: toStringRecord(e.env) } : {}),
76
+ raw: e,
77
+ };
78
+ }
79
+ function toStringRecord(obj) {
80
+ const out = {};
81
+ if (!obj || typeof obj !== "object")
82
+ return out;
83
+ for (const [k, v] of Object.entries(obj)) {
84
+ if (typeof v === "string")
85
+ out[k] = v;
86
+ }
87
+ return out;
88
+ }
89
+ //# sourceMappingURL=mcp-host-reader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-host-reader.js","sourceRoot":"","sources":["../../src/application/mcp-host-reader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { McpEntry, McpHost, McpWriteOpts, McpWriteResult } from "../domain/mcp-entry.js";
2
+ export interface ScopeInput {
3
+ scopeDir: string;
4
+ }
5
+ export declare class McpWriterError extends Error {
6
+ readonly target: string;
7
+ readonly cause?: string | undefined;
8
+ constructor(message: string, target: string, cause?: string | undefined);
9
+ }
10
+ export declare function writeMcpEntry(host: McpHost, entry: McpEntry, scope: ScopeInput, opts?: McpWriteOpts): McpWriteResult;
11
+ export declare function removeCodexMcpBlocks(text: string, name: string): string;
12
+ export declare function appendCodexMcpBlocks(text: string, entry: McpEntry): string;
13
+ //# sourceMappingURL=mcp-host-writer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-host-writer.d.ts","sourceRoot":"","sources":["../../src/application/mcp-host-writer.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,QAAQ,EACR,OAAO,EAEP,YAAY,EACZ,cAAc,EACf,MAAM,wBAAwB,CAAC;AAEhC,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,cAAe,SAAQ,KAAK;aAGrB,MAAM,EAAE,MAAM;aACL,KAAK,CAAC,EAAE,MAAM;gBAFvC,OAAO,EAAE,MAAM,EACC,MAAM,EAAE,MAAM,EACL,KAAK,CAAC,EAAE,MAAM,YAAA;CAK1C;AAED,wBAAgB,aAAa,CAC3B,IAAI,EAAE,OAAO,EACb,KAAK,EAAE,QAAQ,EACf,KAAK,EAAE,UAAU,EACjB,IAAI,GAAE,YAAiB,GACtB,cAAc,CAGhB;AAuJD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAMvE;AAeD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,GAAG,MAAM,CAe1E"}
@@ -0,0 +1,196 @@
1
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { parse as parseToml } from "smol-toml";
4
+ export class McpWriterError extends Error {
5
+ target;
6
+ cause;
7
+ constructor(message, target, cause) {
8
+ super(message);
9
+ this.target = target;
10
+ this.cause = cause;
11
+ this.name = "McpWriterError";
12
+ }
13
+ }
14
+ export function writeMcpEntry(host, entry, scope, opts = {}) {
15
+ if (host === "claude")
16
+ return writeClaudeMcpEntry(entry, scope, opts);
17
+ return writeCodexMcpEntry(entry, scope, opts);
18
+ }
19
+ function writeClaudeMcpEntry(entry, scope, opts) {
20
+ const settingsFile = join(scope.scopeDir, ".claude", "settings.json");
21
+ const data = readClaudeSettings(settingsFile);
22
+ const mcpServers = ensureRecord(data, "mcpServers");
23
+ const existing = mcpServers[entry.name];
24
+ const expected = expectedClaudeShape(entry);
25
+ if (deepEqual(existing, expected)) {
26
+ return resultSkipped("claude", settingsFile, entry.name);
27
+ }
28
+ mcpServers[entry.name] = expected;
29
+ data.mcpServers = mcpServers;
30
+ const newJson = `${JSON.stringify(data, null, 2)}\n`;
31
+ const oldJson = existsSync(settingsFile) ? readFileSync(settingsFile, "utf-8") : "";
32
+ if (newJson === oldJson) {
33
+ return resultSkipped("claude", settingsFile, entry.name);
34
+ }
35
+ if (opts.dryRun) {
36
+ return resultDryRun("claude", settingsFile, entry.name, [
37
+ `mcpServers.${entry.name}: ${existing ? "update" : "add"}`,
38
+ ]);
39
+ }
40
+ mkdirSync(dirname(settingsFile), { recursive: true });
41
+ const backup = backupFile(settingsFile);
42
+ writeFileSync(settingsFile, newJson, "utf-8");
43
+ return resultWritten("claude", settingsFile, entry.name, backup);
44
+ }
45
+ function writeCodexMcpEntry(entry, scope, opts) {
46
+ const configFile = join(scope.scopeDir, ".codex", "config.toml");
47
+ const oldContent = existsSync(configFile) ? readFileSync(configFile, "utf-8") : "";
48
+ let parsed;
49
+ try {
50
+ parsed = oldContent.length > 0 ? parseToml(oldContent) : {};
51
+ }
52
+ catch (err) {
53
+ throw new McpWriterError(`config.toml inválido en ${configFile}`, configFile, err.message);
54
+ }
55
+ const mcpServers = (parsed.mcp_servers ?? {});
56
+ const existing = mcpServers[entry.name];
57
+ const expected = expectedCodexShape(entry);
58
+ if (deepEqual(existing, expected)) {
59
+ return resultSkipped("codex", configFile, entry.name);
60
+ }
61
+ const cleaned = removeCodexMcpBlocks(oldContent, entry.name);
62
+ const newContent = appendCodexMcpBlocks(cleaned, entry);
63
+ if (newContent === oldContent) {
64
+ return resultSkipped("codex", configFile, entry.name);
65
+ }
66
+ if (opts.dryRun) {
67
+ return resultDryRun("codex", configFile, entry.name, [
68
+ `[mcp_servers.${entry.name}]: ${existing ? "update" : "add"}`,
69
+ `[mcp_servers.${entry.name}.env]: ${existing ? "update" : "add"}`,
70
+ ]);
71
+ }
72
+ mkdirSync(dirname(configFile), { recursive: true });
73
+ const backup = backupFile(configFile);
74
+ writeFileSync(configFile, newContent, "utf-8");
75
+ return resultWritten("codex", configFile, entry.name, backup);
76
+ }
77
+ function readClaudeSettings(file) {
78
+ if (!existsSync(file))
79
+ return {};
80
+ const text = readFileSync(file, "utf-8");
81
+ if (text.trim().length === 0)
82
+ return {};
83
+ try {
84
+ const parsed = JSON.parse(text);
85
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
86
+ return parsed;
87
+ }
88
+ throw new Error("settings.json no es un objeto JSON");
89
+ }
90
+ catch (err) {
91
+ throw new McpWriterError(`settings.json inválido en ${file}`, file, err.message);
92
+ }
93
+ }
94
+ function ensureRecord(parent, key) {
95
+ const current = parent[key];
96
+ if (current && typeof current === "object" && !Array.isArray(current)) {
97
+ return current;
98
+ }
99
+ const fresh = {};
100
+ parent[key] = fresh;
101
+ return fresh;
102
+ }
103
+ function expectedClaudeShape(entry) {
104
+ return {
105
+ command: entry.command,
106
+ args: [...entry.args],
107
+ env: { ...entry.env },
108
+ };
109
+ }
110
+ function expectedCodexShape(entry) {
111
+ return {
112
+ command: entry.command,
113
+ args: [...entry.args],
114
+ env: { ...entry.env },
115
+ };
116
+ }
117
+ function deepEqual(a, b) {
118
+ return canonicalJson(a) === canonicalJson(b);
119
+ }
120
+ function canonicalJson(v) {
121
+ if (v === null || v === undefined)
122
+ return JSON.stringify(v ?? null);
123
+ if (typeof v !== "object")
124
+ return JSON.stringify(v);
125
+ if (Array.isArray(v))
126
+ return `[${v.map(canonicalJson).join(",")}]`;
127
+ const obj = v;
128
+ const keys = Object.keys(obj).sort();
129
+ return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalJson(obj[k])}`).join(",")}}`;
130
+ }
131
+ function backupFile(path) {
132
+ if (!existsSync(path))
133
+ return null;
134
+ const ts = Math.floor(Date.now() / 1000);
135
+ const backupPath = `${path}.bak.${ts}`;
136
+ copyFileSync(path, backupPath);
137
+ return backupPath;
138
+ }
139
+ function escapeRegex(s) {
140
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
141
+ }
142
+ export function removeCodexMcpBlocks(text, name) {
143
+ let out = text;
144
+ for (const header of [`[mcp_servers.${name}.env]`, `[mcp_servers.${name}]`]) {
145
+ out = removeBlock(out, header);
146
+ }
147
+ return out.replace(/\n{3,}/g, "\n\n");
148
+ }
149
+ function removeBlock(text, sectionHeader) {
150
+ const re = new RegExp(`^${escapeRegex(sectionHeader)}[ \\t]*$`, "m");
151
+ const match = re.exec(text);
152
+ if (!match || match.index === undefined)
153
+ return text;
154
+ const start = match.index;
155
+ const headerEnd = start + match[0].length;
156
+ const after = text.slice(headerEnd);
157
+ const nextRe = /\n\[/;
158
+ const nextMatch = nextRe.exec(after);
159
+ const end = nextMatch ? headerEnd + (nextMatch.index ?? 0) + 1 : text.length;
160
+ return text.slice(0, start) + text.slice(end);
161
+ }
162
+ export function appendCodexMcpBlocks(text, entry) {
163
+ const buffer = [];
164
+ let prefix = text;
165
+ if (prefix.length > 0 && !prefix.endsWith("\n"))
166
+ prefix += "\n";
167
+ if (prefix.length > 0 && !prefix.endsWith("\n\n"))
168
+ prefix += "\n";
169
+ buffer.push(`[mcp_servers.${entry.name}]`);
170
+ buffer.push(`command = ${tomlString(entry.command)}`);
171
+ buffer.push(`args = [${entry.args.map(tomlString).join(", ")}]`);
172
+ buffer.push("");
173
+ buffer.push(`[mcp_servers.${entry.name}.env]`);
174
+ for (const [k, v] of Object.entries(entry.env)) {
175
+ buffer.push(`${k} = ${tomlString(v)}`);
176
+ }
177
+ buffer.push("");
178
+ return prefix + buffer.join("\n");
179
+ }
180
+ function tomlString(value) {
181
+ // TOML basic strings share JSON escaping conventions for ASCII with \n/\t/\"/\\.
182
+ return JSON.stringify(value);
183
+ }
184
+ function resultWritten(host, target, name, backup) {
185
+ return action(host, target, name, "written", backup);
186
+ }
187
+ function resultSkipped(host, target, name) {
188
+ return action(host, target, name, "skipped-idempotent", null);
189
+ }
190
+ function resultDryRun(host, target, name, diff) {
191
+ return { ...action(host, target, name, "dry-run", null), diff };
192
+ }
193
+ function action(host, target, name, status, backup) {
194
+ return { host, target, name, action: status, backup };
195
+ }
196
+ //# sourceMappingURL=mcp-host-writer.js.map