@tacuchi/agent-workflow-cli 5.8.0 → 5.9.1

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 (107) hide show
  1. package/README.md +2 -2
  2. package/dist/application/checkpoint/markdown.js +1 -1
  3. package/dist/application/checkpoint/markdown.js.map +1 -1
  4. package/dist/application/checkpoint-write-service.js +1 -1
  5. package/dist/application/checkpoint-write-service.js.map +1 -1
  6. package/dist/application/history-table.js +1 -1
  7. package/dist/application/history-table.js.map +1 -1
  8. package/dist/application/hook-branch-check.d.ts +1 -1
  9. package/dist/application/mcp-connections-service.d.ts +37 -0
  10. package/dist/application/mcp-connections-service.d.ts.map +1 -0
  11. package/dist/application/mcp-connections-service.js +97 -0
  12. package/dist/application/mcp-connections-service.js.map +1 -0
  13. package/dist/application/mcp-dbhub-launcher.d.ts +1 -0
  14. package/dist/application/mcp-dbhub-launcher.d.ts.map +1 -1
  15. package/dist/application/mcp-dbhub-launcher.js +15 -3
  16. package/dist/application/mcp-dbhub-launcher.js.map +1 -1
  17. package/dist/application/mcp-doctor-service.d.ts +1 -0
  18. package/dist/application/mcp-doctor-service.d.ts.map +1 -1
  19. package/dist/application/mcp-doctor-service.js +7 -7
  20. package/dist/application/mcp-doctor-service.js.map +1 -1
  21. package/dist/application/mcp-host-reader.d.ts +2 -1
  22. package/dist/application/mcp-host-reader.d.ts.map +1 -1
  23. package/dist/application/mcp-host-reader.js +4 -4
  24. package/dist/application/mcp-host-reader.js.map +1 -1
  25. package/dist/application/mcp-host-writer.d.ts +1 -0
  26. package/dist/application/mcp-host-writer.d.ts.map +1 -1
  27. package/dist/application/mcp-host-writer.js +60 -6
  28. package/dist/application/mcp-host-writer.js.map +1 -1
  29. package/dist/application/mcp-remove-service.js +4 -4
  30. package/dist/application/mcp-remove-service.js.map +1 -1
  31. package/dist/application/mcp-setup-service.d.ts +1 -0
  32. package/dist/application/mcp-setup-service.d.ts.map +1 -1
  33. package/dist/application/mcp-setup-service.js +6 -6
  34. package/dist/application/mcp-setup-service.js.map +1 -1
  35. package/dist/application/multiroot-service.js +2 -2
  36. package/dist/application/multiroot-service.js.map +1 -1
  37. package/dist/application/paths-service.d.ts +1 -0
  38. package/dist/application/paths-service.d.ts.map +1 -1
  39. package/dist/application/paths-service.js +3 -0
  40. package/dist/application/paths-service.js.map +1 -1
  41. package/dist/application/phase-next-service.js +1 -1
  42. package/dist/application/phase-next-service.js.map +1 -1
  43. package/dist/application/plugin-doctor/manifests.d.ts +1 -1
  44. package/dist/application/plugin-doctor/manifests.d.ts.map +1 -1
  45. package/dist/application/plugin-doctor/manifests.js +9 -9
  46. package/dist/application/plugin-doctor/manifests.js.map +1 -1
  47. package/dist/application/release-data/artifacts.js +3 -3
  48. package/dist/application/release-data/artifacts.js.map +1 -1
  49. package/dist/application/release-data/sessions.js +3 -3
  50. package/dist/application/release-data/sessions.js.map +1 -1
  51. package/dist/application/self/bootstrap.js +4 -4
  52. package/dist/application/self/bootstrap.js.map +1 -1
  53. package/dist/application/self/mcp-config.d.ts +23 -15
  54. package/dist/application/self/mcp-config.d.ts.map +1 -1
  55. package/dist/application/self/mcp-config.js +392 -148
  56. package/dist/application/self/mcp-config.js.map +1 -1
  57. package/dist/application/session-create-service.js +8 -8
  58. package/dist/application/session-create-service.js.map +1 -1
  59. package/dist/application/sessions-service.d.ts.map +1 -1
  60. package/dist/application/sessions-service.js +2 -2
  61. package/dist/application/sessions-service.js.map +1 -1
  62. package/dist/application/upgrade-hub-mode-service.js +1 -1
  63. package/dist/application/upgrade-hub-mode-service.js.map +1 -1
  64. package/dist/application/visibility-doctor-service.js +1 -1
  65. package/dist/application/visibility-doctor-service.js.map +1 -1
  66. package/dist/application/workspace-mode-service.js +1 -1
  67. package/dist/application/workspace-mode-service.js.map +1 -1
  68. package/dist/cli/commands/hub-init.js +1 -1
  69. package/dist/cli/commands/hub-init.js.map +1 -1
  70. package/dist/cli/commands/mcp.d.ts.map +1 -1
  71. package/dist/cli/commands/mcp.js +40 -2
  72. package/dist/cli/commands/mcp.js.map +1 -1
  73. package/dist/cli/commands/project-md-upsert.js +1 -1
  74. package/dist/cli/commands/project-md-upsert.js.map +1 -1
  75. package/dist/cli/commands/self.js +1 -1
  76. package/dist/cli/commands/self.js.map +1 -1
  77. package/dist/cli/commands/session-close.js +1 -1
  78. package/dist/cli/commands/session-close.js.map +1 -1
  79. package/dist/cli/commands/session-create.js +1 -1
  80. package/dist/cli/commands/session-create.js.map +1 -1
  81. package/dist/cli/commands/session-resume.js +1 -1
  82. package/dist/cli/commands/session-resume.js.map +1 -1
  83. package/dist/cli/commands/sources.js +1 -1
  84. package/dist/cli/commands/sources.js.map +1 -1
  85. package/dist/cli/interactive-menu.js +1 -1
  86. package/dist/cli/interactive-menu.js.map +1 -1
  87. package/dist/cli/main.js +3 -3
  88. package/dist/cli/main.js.map +1 -1
  89. package/dist/domain/mcp-entry.d.ts +10 -2
  90. package/dist/domain/mcp-entry.d.ts.map +1 -1
  91. package/dist/domain/mcp-entry.js +28 -9
  92. package/dist/domain/mcp-entry.js.map +1 -1
  93. package/dist/runtime/types.d.ts +1 -1
  94. package/dist/runtime/types.d.ts.map +1 -1
  95. package/package.json +2 -2
  96. package/skills/agent-workflow/MANUAL-FUNCIONAL.md +4 -4
  97. package/skills/agent-workflow/MANUAL-TECNICO.md +4 -4
  98. package/skills/agent-workflow/SKILL.md +3 -3
  99. package/skills/agent-workflow/docs/TEST-PLAN.md +16 -16
  100. package/skills/agent-workflow/references/dev-only.md +1 -1
  101. package/skills/agent-workflow/references/doctor.md +1 -1
  102. package/skills/agent-workflow/references/history.md +1 -1
  103. package/skills/agent-workflow/references/hooks.md +2 -2
  104. package/skills/agent-workflow/references/mcp.md +8 -5
  105. package/skills/agent-workflow/references/orchestration.md +2 -2
  106. package/skills/agent-workflow/references/self.md +1 -1
  107. package/skills/agent-workflow/references/session-mgmt.md +1 -1
@@ -1,132 +1,216 @@
1
- import { mcpEntryNameFor, validateMcpInstance, } from "../../domain/mcp-entry.js";
2
- import { writeDsnValue } from "../dev-bootstrap-dsn-service.js";
3
- import { dsnKeyForInstance } from "../dsn-reader-service.js";
1
+ import { resolve } from "node:path";
2
+ import { buildMcpEntry, mcpEntryNameFor, normalizeDsnVarName, validateDsnVarName, validateMcpInstance, } from "../../domain/mcp-entry.js";
3
+ import { readBootstrapDsn } from "../dsn-reader-service.js";
4
+ import { deleteMcpConnection, readMcpConnections, upsertMcpConnection, } from "../mcp-connections-service.js";
4
5
  import { runMcpDoctor } from "../mcp-doctor-service.js";
6
+ import { readMcpEntry } from "../mcp-host-reader.js";
5
7
  import { runMcpRemove } from "../mcp-remove-service.js";
6
8
  import { runMcpSetup } from "../mcp-setup-service.js";
7
9
  export async function selfMcpConfig(args, ctx, prompts) {
8
10
  const prompt = prompts ?? (await loadPrompts());
9
- const action = await resolveAction(args, prompt);
10
- if (action === "cancel") {
11
+ const resolved = await resolveAction(args, prompt);
12
+ switch (resolved.action) {
13
+ case "list":
14
+ return resolved.fromArgs ? listConnections(ctx) : listConnectionsMenu(args, ctx, prompt);
15
+ case "use-env":
16
+ return useExistingDsnVar(args, ctx, prompt);
17
+ case "create-env":
18
+ return createDsnEnvHelp(args, prompt);
19
+ case "install-claude":
20
+ return runConnectionAction(args, ctx, prompt, resolved.action);
21
+ case "install-codex":
22
+ return runConnectionAction(args, ctx, prompt, resolved.action);
23
+ case "doctor":
24
+ return runConnectionAction(args, ctx, prompt, resolved.action);
25
+ case "remove":
26
+ return runConnectionAction(args, ctx, prompt, resolved.action);
27
+ case "cancel":
28
+ return {
29
+ ok: true,
30
+ data: { action: "cancel", connection: null, summary: "Operación cancelada." },
31
+ exitCode: 0,
32
+ };
33
+ }
34
+ }
35
+ function listConnections(ctx) {
36
+ const connections = connectionViews(ctx);
37
+ return {
38
+ ok: true,
39
+ data: {
40
+ action: "list",
41
+ connection: null,
42
+ connections,
43
+ table: formatConnectionsTable(connections),
44
+ summary: connections.length > 0
45
+ ? `${connections.length} conexión(es) MCP registradas.`
46
+ : "No hay conexiones MCP registradas.",
47
+ },
48
+ exitCode: 0,
49
+ };
50
+ }
51
+ async function listConnectionsMenu(args, ctx, prompts) {
52
+ const connections = connectionViews(ctx);
53
+ if (connections.length === 0) {
11
54
  return {
12
55
  ok: true,
13
- data: { action, connection: null, summary: "Operación cancelada." },
56
+ data: {
57
+ action: "list",
58
+ connection: null,
59
+ connections,
60
+ table: formatConnectionsTable(connections),
61
+ summary: "No hay conexiones MCP registradas. Primero usa una DSN env var existente.",
62
+ },
14
63
  exitCode: 0,
15
64
  };
16
65
  }
17
- const instance = await resolveInstance(args, prompt);
18
- const hosts = await resolveHosts(args, prompt);
19
- const request = buildRequest(action, instance, hosts, args.flags.has("--dry-run"), ctx);
20
- switch (request.action) {
21
- case "configure":
22
- return runConfigure(request, ctx, prompt);
23
- case "install":
24
- return runInstall(request, ctx);
25
- case "uninstall":
26
- return runUninstall(request, ctx);
27
- case "doctor":
28
- return runDoctorAction(request, ctx);
66
+ const action = await prompts.select({
67
+ message: formatConnectionsTable(connections),
68
+ default: "install-claude",
69
+ choices: [
70
+ { name: "Instalar/Actualizar en Claude Code", value: "install-claude" },
71
+ { name: "Instalar/Actualizar en Codex", value: "install-codex" },
72
+ { name: "Diagnosticar", value: "doctor" },
73
+ { name: "Eliminar", value: "remove" },
74
+ { name: "Cancelar", value: "cancel" },
75
+ ],
76
+ });
77
+ if (action === "cancel") {
78
+ return {
79
+ ok: true,
80
+ data: {
81
+ action,
82
+ connection: null,
83
+ connections,
84
+ table: formatConnectionsTable(connections),
85
+ summary: "Operación cancelada.",
86
+ },
87
+ exitCode: 0,
88
+ };
29
89
  }
90
+ return runConnectionAction(args, ctx, prompts, action);
30
91
  }
31
- function buildRequest(action, instance, hosts, dryRun, ctx) {
32
- const connection = {
33
- instance,
34
- server_name: mcpEntryNameFor(instance),
35
- dsn_key: dsnKeyForInstance(instance),
36
- dsn_path: ctx.paths.userDsnFile(),
37
- };
38
- return { action, instance, hosts, dryRun, connection };
39
- }
40
- async function runConfigure(request, ctx, prompts) {
41
- const dsn = await prompts.password({
42
- message: `DSN para ${request.connection.server_name} (${request.connection.dsn_key})`,
43
- mask: "*",
44
- validate: (value) => (value.trim().length > 0 ? true : "El DSN no puede estar vacío"),
45
- });
46
- const dsnWrite = request.dryRun
47
- ? { path: request.connection.dsn_path, key: request.connection.dsn_key, dry_run: true }
48
- : {
49
- ...writeDsnValue(ctx.paths, { key: request.connection.dsn_key, value: dsn }),
50
- dry_run: false,
92
+ async function useExistingDsnVar(args, ctx, prompts) {
93
+ const name = await resolveConnectionName(args, prompts);
94
+ const dsnVar = await resolveDsnVar(args, prompts, name);
95
+ if (!isDsnVisible(ctx, dsnVar)) {
96
+ return {
97
+ ok: false,
98
+ error: {
99
+ code: "DSN_VAR_NOT_VISIBLE",
100
+ message: `${dsnVar} no está visible en el entorno actual ni en ${ctx.paths.userDsnFile()}.`,
101
+ },
102
+ data: {
103
+ action: "use-env",
104
+ connection: null,
105
+ env_help: buildEnvHelp(dsnVar, name),
106
+ summary: `Exporta ${dsnVar} y vuelve a registrar la conexión.`,
107
+ },
108
+ exitCode: 1,
51
109
  };
52
- const setup = setupConnection(request, ctx);
53
- if ("ok" in setup) {
54
- return refusal(request.action, request.connection, setup.hint, setup.exitCode);
55
110
  }
56
- const doctor = doctorConnection(request, ctx);
111
+ const write = upsertMcpConnection(ctx.paths, { name, dsnVar });
112
+ const connection = connectionView(ctx, {
113
+ name: write.connection.name,
114
+ dsnVar: write.connection.dsnVar,
115
+ dsnPresent: true,
116
+ });
57
117
  return {
58
- ok: setup.errors.length === 0,
118
+ ok: true,
59
119
  data: {
60
- action: request.action,
61
- connection: request.connection,
62
- dsn_write: dsnWrite,
63
- setup,
64
- doctor,
65
- summary: `Configuración MCP preparada para ${request.connection.server_name}.`,
120
+ action: "use-env",
121
+ connection,
122
+ connections: connectionViews(ctx),
123
+ table: formatConnectionsTable(connectionViews(ctx)),
124
+ registry: { path: write.path, changed: true },
125
+ summary: `Conexión '${connection.nombre}' registrada con ${connection.dsn_var}.`,
66
126
  },
67
- ...(setup.errors.length > 0
68
- ? {
69
- error: {
70
- code: "MCP_CONFIG_PARTIAL",
71
- message: `${setup.errors.length} error(es) durante setup; ver data.setup.errors`,
72
- },
73
- }
74
- : {}),
75
- exitCode: setup.errors.length > 0 ? 1 : 0,
127
+ exitCode: 0,
76
128
  };
77
129
  }
78
- function runInstall(request, ctx) {
79
- const setup = setupConnection(request, ctx);
80
- if ("ok" in setup) {
81
- return refusal(request.action, request.connection, setup.hint, setup.exitCode);
82
- }
83
- const doctor = doctorConnection(request, ctx);
130
+ async function createDsnEnvHelp(args, prompts) {
131
+ const name = await resolveConnectionName(args, prompts);
132
+ const dsnVar = await resolveDsnVar(args, prompts, name);
84
133
  return {
85
- ok: setup.errors.length === 0,
134
+ ok: true,
86
135
  data: {
87
- action: request.action,
88
- connection: request.connection,
89
- setup,
90
- doctor,
91
- summary: `Instalación MCP aplicada para ${request.connection.server_name}.`,
136
+ action: "create-env",
137
+ connection: null,
138
+ env_help: buildEnvHelp(dsnVar, name),
139
+ summary: `Comandos sugeridos para crear ${dsnVar}. No se instaló ningún host.`,
92
140
  },
93
- exitCode: setup.errors.length > 0 ? 1 : 0,
141
+ exitCode: 0,
94
142
  };
95
143
  }
96
- function runUninstall(request, ctx) {
97
- const remove = runMcpRemove(ctx.env, {
98
- hosts: request.hosts,
99
- instances: [request.instance],
144
+ async function runConnectionAction(args, ctx, prompts, action) {
145
+ const connection = await resolveRegisteredConnection(args, ctx, prompts);
146
+ if (connection === null) {
147
+ return {
148
+ ok: false,
149
+ error: {
150
+ code: "NO_MCP_CONNECTIONS",
151
+ message: "No hay conexiones MCP registradas. Usa primero 'self mcp use-env'.",
152
+ },
153
+ data: { action, connection: null, summary: "No hay conexiones MCP registradas." },
154
+ exitCode: 1,
155
+ };
156
+ }
157
+ switch (action) {
158
+ case "install-claude":
159
+ return installConnection(args, ctx, connection, "claude");
160
+ case "install-codex":
161
+ return installConnection(args, ctx, connection, "codex");
162
+ case "doctor":
163
+ return doctorConnection(ctx, connection);
164
+ case "remove":
165
+ return removeConnection(args, ctx, connection);
166
+ }
167
+ }
168
+ function installConnection(args, ctx, connection, host) {
169
+ const setup = runMcpSetup(ctx.env, {
170
+ hosts: [host],
171
+ instances: [connection.name],
100
172
  scope: "workspace",
101
- dryRun: request.dryRun,
173
+ dsnVars: { [connection.name]: connection.dsnVar },
174
+ dryRun: args.flags.has("--dry-run"),
102
175
  });
103
- if ("ok" in remove) {
104
- return refusal(request.action, request.connection, remove.hint, remove.exitCode);
105
- }
106
- const doctor = doctorConnection(request, ctx);
176
+ if ("ok" in setup)
177
+ return refusal(hostAction(host), connectionView(ctx, connection), setup.hint);
178
+ const doctor = runDoctor(ctx, connection, [host]);
179
+ const hasErrors = setup.errors.length > 0;
107
180
  return {
108
- ok: remove.errors.length === 0,
181
+ ok: !hasErrors,
109
182
  data: {
110
- action: request.action,
111
- connection: request.connection,
112
- remove,
183
+ action: hostAction(host),
184
+ connection: connectionView(ctx, connection),
185
+ connections: connectionViews(ctx),
186
+ table: formatConnectionsTable(connectionViews(ctx)),
187
+ setup,
113
188
  doctor,
114
- summary: `Desinstalación MCP aplicada para ${request.connection.server_name}.`,
189
+ summary: `Conexión '${connection.name}' instalada/actualizada en ${hostLabel(host)}.`,
115
190
  },
116
- exitCode: remove.errors.length > 0 ? 1 : 0,
191
+ ...(hasErrors
192
+ ? {
193
+ error: {
194
+ code: "MCP_SETUP_PARTIAL",
195
+ message: `${setup.errors.length} error(es) durante setup; ver data.setup.errors`,
196
+ },
197
+ }
198
+ : {}),
199
+ exitCode: hasErrors ? 1 : 0,
117
200
  };
118
201
  }
119
- function runDoctorAction(request, ctx) {
120
- const doctor = doctorConnection(request, ctx);
202
+ function doctorConnection(ctx, connection) {
203
+ const doctor = runDoctor(ctx, connection, ["claude", "codex"]);
204
+ const allOk = doctor.summary.ok === doctor.reports.length;
121
205
  return {
122
- ok: doctor.summary.ok === doctor.reports.length,
206
+ ok: allOk,
123
207
  data: {
124
- action: request.action,
125
- connection: request.connection,
208
+ action: "doctor",
209
+ connection: connectionView(ctx, connection),
126
210
  doctor,
127
- summary: `Diagnóstico MCP ejecutado para ${request.connection.server_name}.`,
211
+ summary: `Diagnóstico MCP ejecutado para '${connection.name}'.`,
128
212
  },
129
- ...(doctor.summary.ok === doctor.reports.length
213
+ ...(allOk
130
214
  ? {}
131
215
  : {
132
216
  error: {
@@ -134,50 +218,122 @@ function runDoctorAction(request, ctx) {
134
218
  message: `${doctor.reports.length - doctor.summary.ok}/${doctor.reports.length} entradas con drift`,
135
219
  },
136
220
  }),
137
- exitCode: doctor.summary.ok === doctor.reports.length ? 0 : 1,
221
+ exitCode: allOk ? 0 : 1,
138
222
  };
139
223
  }
140
- function setupConnection(request, ctx) {
141
- return runMcpSetup(ctx.env, {
142
- hosts: request.hosts,
143
- instances: [request.instance],
224
+ function removeConnection(args, ctx, connection) {
225
+ const dryRun = args.flags.has("--dry-run");
226
+ const remove = runMcpRemove(ctx.env, {
227
+ hosts: ["claude", "codex"],
228
+ instances: [connection.name],
144
229
  scope: "workspace",
145
- dryRun: request.dryRun,
230
+ dryRun,
146
231
  });
232
+ if ("ok" in remove)
233
+ return refusal("remove", connectionView(ctx, connection), remove.hint);
234
+ const hasErrors = remove.errors.length > 0;
235
+ const deleted = !dryRun && !hasErrors ? deleteMcpConnection(ctx.paths, connection) : null;
236
+ return {
237
+ ok: !hasErrors,
238
+ data: {
239
+ action: "remove",
240
+ connection: connectionView(ctx, connection),
241
+ connections: connectionViews(ctx),
242
+ table: formatConnectionsTable(connectionViews(ctx)),
243
+ remove,
244
+ ...(deleted ? { registry: { path: deleted.path, changed: deleted.removed } } : {}),
245
+ summary: dryRun
246
+ ? `Previsualización de eliminación para '${connection.name}'.`
247
+ : `Conexión '${connection.name}' eliminada de Claude Code, Codex y del registro local.`,
248
+ },
249
+ ...(hasErrors
250
+ ? {
251
+ error: {
252
+ code: "MCP_REMOVE_PARTIAL",
253
+ message: `${remove.errors.length} error(es) durante remove; ver data.remove.errors`,
254
+ },
255
+ }
256
+ : {}),
257
+ exitCode: hasErrors ? 1 : 0,
258
+ };
147
259
  }
148
- function doctorConnection(request, ctx) {
260
+ function runDoctor(ctx, connection, hosts) {
149
261
  return runMcpDoctor(ctx.env, ctx.paths, {
150
- hosts: request.hosts,
151
- instances: [request.instance],
262
+ hosts,
263
+ instances: [connection.name],
152
264
  scope: "workspace",
265
+ dsnVars: { [connection.name]: connection.dsnVar },
153
266
  });
154
267
  }
155
- async function loadPrompts() {
156
- const prompts = await import("@inquirer/prompts");
268
+ function connectionViews(ctx) {
269
+ return readMcpConnections(ctx.paths, ctx.env).map((connection) => connectionView(ctx, connection));
270
+ }
271
+ function connectionView(ctx, connection) {
157
272
  return {
158
- select: prompts.select,
159
- input: prompts.input,
160
- password: prompts.password,
273
+ nombre: connection.name,
274
+ server_name: mcpEntryNameFor(connection.name),
275
+ dsn_var: connection.dsnVar,
276
+ dsn_visible: isDsnVisible(ctx, connection.dsnVar),
277
+ instalado: {
278
+ claude_code: installStatus(ctx, connection, "claude"),
279
+ codex: installStatus(ctx, connection, "codex"),
280
+ },
161
281
  };
162
282
  }
163
- async function resolveAction(args, prompts) {
164
- const raw = args.values.get("action") ?? args.rest[1];
165
- if (isAction(raw))
166
- return raw;
167
- return prompts.select({
168
- message: "Configurar MCP BD",
169
- default: "configure",
170
- choices: [
171
- { name: "Configurar DSN + instalar host", value: "configure" },
172
- { name: "Instalar host con DSN existente", value: "install" },
173
- { name: "Desinstalar host", value: "uninstall" },
174
- { name: "Diagnosticar", value: "doctor" },
175
- { name: "Cancelar", value: "cancel" },
176
- ],
283
+ function installStatus(ctx, connection, host) {
284
+ const entry = buildMcpEntry(connection.name, connection.dsnVar);
285
+ const snapshot = readMcpEntry(host, resolve(ctx.env.cwd()), entry.name);
286
+ if (!snapshot.exists)
287
+ return "no";
288
+ if (snapshot.command !== entry.command)
289
+ return "drift";
290
+ if (!arraysEqual(snapshot.args ?? [], entry.args))
291
+ return "drift";
292
+ if (!recordsEqual(snapshot.env ?? {}, entry.env))
293
+ return "drift";
294
+ return "si";
295
+ }
296
+ function isDsnVisible(ctx, dsnVar) {
297
+ if (ctx.env.get(dsnVar))
298
+ return true;
299
+ const dsn = readBootstrapDsn(ctx.paths);
300
+ return Boolean(dsn.values[dsnVar]);
301
+ }
302
+ function formatConnectionsTable(connections) {
303
+ const header = "| nombre | DSN var (nombre) | Instalado en Claude Code | Instalado en Codex |";
304
+ const separator = "|---|---|---|---|";
305
+ if (connections.length === 0)
306
+ return `${header}\n${separator}`;
307
+ const rows = connections.map((item) => `| ${item.nombre} | ${item.dsn_var} | ${item.instalado.claude_code} | ${item.instalado.codex} |`);
308
+ return [header, separator, ...rows].join("\n");
309
+ }
310
+ async function resolveRegisteredConnection(args, ctx, prompts) {
311
+ const connections = readMcpConnections(ctx.paths, ctx.env);
312
+ if (connections.length === 0)
313
+ return null;
314
+ const raw = args.values.get("name") ?? args.values.get("instance");
315
+ if (raw !== undefined) {
316
+ const validation = validateMcpInstance(raw);
317
+ if (!validation.ok)
318
+ throw new Error(validation.error);
319
+ const found = connections.find((item) => item.name === validation.value);
320
+ if (found === undefined) {
321
+ throw new Error(`conexión MCP no registrada: '${validation.value}'`);
322
+ }
323
+ return found;
324
+ }
325
+ const selected = await prompts.select({
326
+ message: "Conexión",
327
+ default: connections[0]?.name ?? "cert",
328
+ choices: connections.map((item) => ({
329
+ name: `${item.name} (${item.dsnVar})`,
330
+ value: item.name,
331
+ })),
177
332
  });
333
+ return connections.find((item) => item.name === selected) ?? null;
178
334
  }
179
- async function resolveInstance(args, prompts) {
180
- const raw = args.values.get("instance") ?? args.values.get("name");
335
+ async function resolveConnectionName(args, prompts) {
336
+ const raw = args.values.get("name") ?? args.values.get("instance");
181
337
  if (raw !== undefined) {
182
338
  const validation = validateMcpInstance(raw);
183
339
  if (!validation.ok)
@@ -185,7 +341,7 @@ async function resolveInstance(args, prompts) {
185
341
  return validation.value;
186
342
  }
187
343
  const value = await prompts.input({
188
- message: "Nombre de conexión (cert, prod o custom; se registrará como qtc-<nombre>)",
344
+ message: "Nombre de conexión",
189
345
  default: "cert",
190
346
  validate: (input) => {
191
347
  const validation = validateMcpInstance(input);
@@ -197,41 +353,129 @@ async function resolveInstance(args, prompts) {
197
353
  throw new Error(validation.error);
198
354
  return validation.value;
199
355
  }
200
- async function resolveHosts(args, prompts) {
201
- const raw = args.values.get("host");
202
- if (raw !== undefined)
203
- return expandHost(raw);
204
- const value = await prompts.select({
205
- message: "Host destino",
206
- default: "both",
207
- choices: [
208
- { name: "Claude Code", value: "claude" },
209
- { name: "Codex", value: "codex" },
210
- { name: "Ambos", value: "both" },
211
- ],
356
+ async function resolveDsnVar(args, prompts, name) {
357
+ const raw = args.values.get("dsn-var") ?? args.values.get("var");
358
+ if (raw !== undefined) {
359
+ const validation = validateDsnVarName(raw);
360
+ if (!validation.ok)
361
+ throw new Error(validation.error);
362
+ return validation.value;
363
+ }
364
+ const value = await prompts.input({
365
+ message: "Nombre de variable DSN",
366
+ default: defaultDsnVar(name),
367
+ validate: (input) => {
368
+ const validation = validateDsnVarName(input);
369
+ return validation.ok ? true : validation.error;
370
+ },
212
371
  });
213
- return expandHost(value);
372
+ const validation = validateDsnVarName(value);
373
+ if (!validation.ok)
374
+ throw new Error(validation.error);
375
+ return validation.value;
376
+ }
377
+ function defaultDsnVar(name) {
378
+ const normalized = name.toUpperCase().replace(/-/g, "_");
379
+ if (normalized === "CERT")
380
+ return "DB_CERT_DSN";
381
+ if (normalized === "PROD")
382
+ return "DB_PROD_DSN";
383
+ return `DB_${normalized}_DSN`;
384
+ }
385
+ function buildEnvHelp(dsnVar, name) {
386
+ const variable = normalizeDsnVarName(dsnVar);
387
+ const platform = process.platform === "win32" ? "windows" : process.platform;
388
+ const commands = process.platform === "win32"
389
+ ? [
390
+ `$env:${variable} = "<DSN>"`,
391
+ `[Environment]::SetEnvironmentVariable("${variable}", "<DSN>", "User")`,
392
+ ]
393
+ : [
394
+ `export ${variable}='<DSN>'`,
395
+ `printf '%s\\n' "export ${variable}='<DSN>'" >> ${shellStartupFile()}`,
396
+ ];
397
+ return {
398
+ platform,
399
+ variable,
400
+ commands,
401
+ next_step: `agent-workflow self mcp use-env --name ${name} --dsn-var ${variable}`,
402
+ };
403
+ }
404
+ function shellStartupFile() {
405
+ const shell = process.env.SHELL ?? "";
406
+ if (shell.endsWith("/zsh"))
407
+ return "~/.zshenv";
408
+ if (shell.endsWith("/bash"))
409
+ return "~/.bashrc";
410
+ return "~/.profile";
411
+ }
412
+ async function loadPrompts() {
413
+ const prompts = await import("@inquirer/prompts");
414
+ return {
415
+ select: prompts.select,
416
+ input: prompts.input,
417
+ };
214
418
  }
215
- function expandHost(value) {
216
- if (value === "both")
217
- return ["claude", "codex"];
218
- if (value === "claude" || value === "codex")
219
- return [value];
220
- throw new Error(`host inválido: '${value}'. Valores válidos: claude | codex | both`);
419
+ async function resolveAction(args, prompts) {
420
+ const raw = args.values.get("action") ?? args.rest[1];
421
+ if (isAction(raw))
422
+ return { action: raw, fromArgs: true };
423
+ return {
424
+ action: await prompts.select({
425
+ message: "Configurar MCP database (dbhub)",
426
+ default: "list",
427
+ choices: [
428
+ { name: "Listar conexiones existentes", value: "list" },
429
+ { name: "Utilizar DSN env var existente", value: "use-env" },
430
+ { name: "Crear DSN env var", value: "create-env" },
431
+ { name: "Cancelar", value: "cancel" },
432
+ ],
433
+ }),
434
+ fromArgs: false,
435
+ };
221
436
  }
222
437
  function isAction(value) {
223
- return (value === "configure" ||
224
- value === "install" ||
225
- value === "uninstall" ||
438
+ return (value === "list" ||
439
+ value === "use-env" ||
440
+ value === "create-env" ||
441
+ value === "install-claude" ||
442
+ value === "install-codex" ||
226
443
  value === "doctor" ||
444
+ value === "remove" ||
227
445
  value === "cancel");
228
446
  }
229
- function refusal(action, connection, message, exitCode) {
447
+ function hostAction(host) {
448
+ return host === "claude" ? "install-claude" : "install-codex";
449
+ }
450
+ function hostLabel(host) {
451
+ return host === "claude" ? "Claude Code" : "Codex";
452
+ }
453
+ function refusal(action, connection, message) {
230
454
  return {
231
455
  ok: false,
232
456
  error: { code: "GLOBAL_REQUIRES_FORCE", message },
233
457
  data: { action, connection, summary: message },
234
- exitCode,
458
+ exitCode: 2,
235
459
  };
236
460
  }
461
+ function arraysEqual(a, b) {
462
+ if (a.length !== b.length)
463
+ return false;
464
+ for (let i = 0; i < a.length; i += 1) {
465
+ if (a[i] !== b[i])
466
+ return false;
467
+ }
468
+ return true;
469
+ }
470
+ function recordsEqual(a, b) {
471
+ const keysA = Object.keys(a).sort();
472
+ const keysB = Object.keys(b).sort();
473
+ if (!arraysEqual(keysA, keysB))
474
+ return false;
475
+ for (const key of keysA) {
476
+ if (a[key] !== b[key])
477
+ return false;
478
+ }
479
+ return true;
480
+ }
237
481
  //# sourceMappingURL=mcp-config.js.map