gufi-cli 0.1.37 โ 0.1.39
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/commands/assistant.d.ts +22 -0
- package/dist/commands/assistant.js +377 -0
- package/dist/index.js +4 -1
- package/dist/mcp.js +63 -40
- package/package.json +1 -1
- package/dist/commands/install.d.ts +0 -21
- package/dist/commands/setup-claude.d.ts +0 -6
- package/dist/commands/setup-claude.js +0 -137
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gufi assistant - WebSocket bridge between frontend UI and Claude Code
|
|
3
|
+
*
|
|
4
|
+
* ๐ Permite editar vistas desde el frontend usando Claude Code local + MCP
|
|
5
|
+
*
|
|
6
|
+
* Architecture:
|
|
7
|
+
* โโโโโโโโโโโโโโโ WebSocket โโโโโโโโโโโโโโโโโโโ
|
|
8
|
+
* โ Frontend โโโโโโโโโโโโโโโโโโโโโบโ gufi assistant โ
|
|
9
|
+
* โ (UI/Chat) โ localhost:3005 โ โ โ
|
|
10
|
+
* โโโโโโโโโโโโโโโ โ Claude Code โ
|
|
11
|
+
* โ + MCP Gufi โ
|
|
12
|
+
* โโโโโโโโโโโโโโโโโโโ
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* gufi assistant # Start on default port 3005
|
|
16
|
+
* gufi assistant --port 3010 # Custom port
|
|
17
|
+
*/
|
|
18
|
+
interface AssistantFlags {
|
|
19
|
+
port?: string | number;
|
|
20
|
+
}
|
|
21
|
+
export declare function assistantCommand(flags?: AssistantFlags): Promise<void>;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gufi assistant - WebSocket bridge between frontend UI and Claude Code
|
|
3
|
+
*
|
|
4
|
+
* ๐ Permite editar vistas desde el frontend usando Claude Code local + MCP
|
|
5
|
+
*
|
|
6
|
+
* Architecture:
|
|
7
|
+
* โโโโโโโโโโโโโโโ WebSocket โโโโโโโโโโโโโโโโโโโ
|
|
8
|
+
* โ Frontend โโโโโโโโโโโโโโโโโโโโโบโ gufi assistant โ
|
|
9
|
+
* โ (UI/Chat) โ localhost:3005 โ โ โ
|
|
10
|
+
* โโโโโโโโโโโโโโโ โ Claude Code โ
|
|
11
|
+
* โ + MCP Gufi โ
|
|
12
|
+
* โโโโโโโโโโโโโโโโโโโ
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* gufi assistant # Start on default port 3005
|
|
16
|
+
* gufi assistant --port 3010 # Custom port
|
|
17
|
+
*/
|
|
18
|
+
import chalk from "chalk";
|
|
19
|
+
import { WebSocketServer, WebSocket } from "ws";
|
|
20
|
+
import { spawn } from "child_process";
|
|
21
|
+
import { isLoggedIn, getCurrentEnv } from "../lib/config.js";
|
|
22
|
+
const DEFAULT_PORT = 4005;
|
|
23
|
+
const clients = new Map();
|
|
24
|
+
/**
|
|
25
|
+
* Parse JSON output - extract final result and session_id for memory
|
|
26
|
+
*/
|
|
27
|
+
function parseClaudeJsonLine(line, _requestId, _sendResponse, state) {
|
|
28
|
+
try {
|
|
29
|
+
const data = JSON.parse(line);
|
|
30
|
+
// Return the final result with session_id (check FIRST before generic session_id)
|
|
31
|
+
if (data.type === "result" && data.result) {
|
|
32
|
+
return { finalText: data.result, sessionId: data.session_id };
|
|
33
|
+
}
|
|
34
|
+
// Capture session_id from init message (not result)
|
|
35
|
+
if (data.type === "system" && data.session_id) {
|
|
36
|
+
return { sessionId: data.session_id };
|
|
37
|
+
}
|
|
38
|
+
// Log tool usage for terminal visibility
|
|
39
|
+
if (data.type === "assistant" && data.message?.content) {
|
|
40
|
+
for (const block of data.message.content) {
|
|
41
|
+
if (block.type === "tool_use") {
|
|
42
|
+
state.lastToolName = block.name;
|
|
43
|
+
console.log(chalk.blue(` ๐ง ${block.name}`));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (data.type === "user" && data.message?.content) {
|
|
48
|
+
for (const block of data.message.content) {
|
|
49
|
+
if (block.type === "tool_result") {
|
|
50
|
+
console.log(chalk.green(` โ completado`));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return {};
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return {};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Execute Claude Code with a prompt and stream the response
|
|
62
|
+
* Returns the session_id for memory/resume
|
|
63
|
+
*/
|
|
64
|
+
async function executeClaudeCode(prompt, viewId, viewName, requestId, sendResponse, existingSessionId = null) {
|
|
65
|
+
// Build the full prompt with view context
|
|
66
|
+
const fullPrompt = `
|
|
67
|
+
[Contexto: Estรกs editando la vista "${viewName}" de Gufi ERP]
|
|
68
|
+
|
|
69
|
+
REGLAS CRรTICAS:
|
|
70
|
+
1. SIEMPRE usa los tools MCP para hacer cambios reales. NUNCA digas que hiciste algo sin llamar al tool.
|
|
71
|
+
2. Para modificar archivos: gufi_view_file_update({ view_id, file_path, content })
|
|
72
|
+
3. Para leer archivos: gufi_view_files({ view_id })
|
|
73
|
+
4. El view_id es ${viewId} - รบsalo directamente
|
|
74
|
+
5. Responde en espaรฑol y sรฉ conciso
|
|
75
|
+
|
|
76
|
+
PROHIBIDO:
|
|
77
|
+
- Decir "He aรฑadido..." sin haber llamado a gufi_view_file_update
|
|
78
|
+
- Inventar que hiciste cambios
|
|
79
|
+
- Responder sin ejecutar los tools necesarios
|
|
80
|
+
|
|
81
|
+
Instrucciรณn del usuario: ${prompt}
|
|
82
|
+
`.trim();
|
|
83
|
+
console.log(chalk.cyan(`\n ๐ Request: "${prompt.substring(0, 50)}..."`));
|
|
84
|
+
console.log(chalk.gray(` View: "${viewName}" (ID hint: ${viewId})`));
|
|
85
|
+
return new Promise((resolve, reject) => {
|
|
86
|
+
// Build args - use --resume if we have a session
|
|
87
|
+
const args = [
|
|
88
|
+
"--dangerously-skip-permissions",
|
|
89
|
+
"-p", // print mode
|
|
90
|
+
"--output-format", "stream-json",
|
|
91
|
+
"--verbose",
|
|
92
|
+
];
|
|
93
|
+
if (existingSessionId) {
|
|
94
|
+
args.push("--resume", existingSessionId);
|
|
95
|
+
console.log(chalk.gray(` ๐ง Resumiendo sesiรณn ${existingSessionId.substring(0, 8)}...`));
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
console.log(chalk.gray(` ๐ง Nueva sesiรณn de Claude...`));
|
|
99
|
+
}
|
|
100
|
+
const claudeProcess = spawn("claude", args, {
|
|
101
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
102
|
+
env: { ...process.env },
|
|
103
|
+
});
|
|
104
|
+
// Write prompt to stdin and close it
|
|
105
|
+
claudeProcess.stdin?.write(fullPrompt);
|
|
106
|
+
claudeProcess.stdin?.end();
|
|
107
|
+
console.log(chalk.yellow(" โณ Claude Code ejecutando (PID: " + claudeProcess.pid + ")..."));
|
|
108
|
+
let lineBuffer = "";
|
|
109
|
+
let lastFinalText = "";
|
|
110
|
+
let capturedSessionId = existingSessionId;
|
|
111
|
+
const streamState = {
|
|
112
|
+
currentText: "",
|
|
113
|
+
currentToolName: null,
|
|
114
|
+
currentToolInput: "",
|
|
115
|
+
lastToolName: null,
|
|
116
|
+
};
|
|
117
|
+
claudeProcess.stdout?.on("data", (data) => {
|
|
118
|
+
const chunk = data.toString();
|
|
119
|
+
lineBuffer += chunk;
|
|
120
|
+
// Process complete lines (each JSON object is on its own line)
|
|
121
|
+
const lines = lineBuffer.split("\n");
|
|
122
|
+
lineBuffer = lines.pop() || ""; // Keep incomplete line in buffer
|
|
123
|
+
for (const line of lines) {
|
|
124
|
+
if (line.trim()) {
|
|
125
|
+
const result = parseClaudeJsonLine(line, requestId, sendResponse, streamState);
|
|
126
|
+
if (result.finalText) {
|
|
127
|
+
lastFinalText = result.finalText;
|
|
128
|
+
}
|
|
129
|
+
if (result.sessionId) {
|
|
130
|
+
capturedSessionId = result.sessionId;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
claudeProcess.stderr?.on("data", (data) => {
|
|
136
|
+
const text = data.toString();
|
|
137
|
+
// Log all stderr for debugging
|
|
138
|
+
console.log(chalk.gray(` ๐ stderr: ${text.substring(0, 100)}`));
|
|
139
|
+
if (text.toLowerCase().includes("error:") && !text.includes("is_error")) {
|
|
140
|
+
sendResponse({
|
|
141
|
+
type: "error",
|
|
142
|
+
id: requestId,
|
|
143
|
+
error: text,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
claudeProcess.on("close", (code) => {
|
|
148
|
+
// Process any remaining buffer
|
|
149
|
+
if (lineBuffer.trim()) {
|
|
150
|
+
const result = parseClaudeJsonLine(lineBuffer, requestId, sendResponse, streamState);
|
|
151
|
+
if (result.finalText) {
|
|
152
|
+
lastFinalText = result.finalText;
|
|
153
|
+
}
|
|
154
|
+
if (result.sessionId) {
|
|
155
|
+
capturedSessionId = result.sessionId;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
console.log(chalk.gray(` ๐ Claude terminรณ con cรณdigo: ${code}`));
|
|
159
|
+
if (capturedSessionId) {
|
|
160
|
+
console.log(chalk.gray(` ๐ Session: ${capturedSessionId.substring(0, 8)}...`));
|
|
161
|
+
}
|
|
162
|
+
if (code !== 0) {
|
|
163
|
+
console.log(chalk.red(` โ Error: cรณdigo ${code}`));
|
|
164
|
+
sendResponse({
|
|
165
|
+
type: "error",
|
|
166
|
+
id: requestId,
|
|
167
|
+
error: `Claude process exited with code ${code}`,
|
|
168
|
+
});
|
|
169
|
+
reject(new Error(`Claude exited with code ${code}`));
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
console.log(chalk.green(` โ Completado`));
|
|
173
|
+
sendResponse({
|
|
174
|
+
type: "response",
|
|
175
|
+
id: requestId,
|
|
176
|
+
content: lastFinalText,
|
|
177
|
+
done: true,
|
|
178
|
+
});
|
|
179
|
+
resolve(capturedSessionId);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
claudeProcess.on("error", (err) => {
|
|
183
|
+
console.log(chalk.red(` โ Error spawn: ${err.message}`));
|
|
184
|
+
sendResponse({
|
|
185
|
+
type: "error",
|
|
186
|
+
id: requestId,
|
|
187
|
+
error: `Failed to start Claude: ${err.message}`,
|
|
188
|
+
});
|
|
189
|
+
reject(err);
|
|
190
|
+
});
|
|
191
|
+
// Store the process for potential cancellation
|
|
192
|
+
const clientState = [...clients.values()].find((c) => c.currentRequestId === requestId);
|
|
193
|
+
if (clientState) {
|
|
194
|
+
clientState.claudeProcess = claudeProcess;
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Handle incoming WebSocket messages
|
|
200
|
+
*/
|
|
201
|
+
async function handleMessage(ws, rawMessage) {
|
|
202
|
+
const state = clients.get(ws);
|
|
203
|
+
if (!state)
|
|
204
|
+
return;
|
|
205
|
+
const sendResponse = (response) => {
|
|
206
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
207
|
+
ws.send(JSON.stringify(response));
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
try {
|
|
211
|
+
const message = JSON.parse(rawMessage);
|
|
212
|
+
switch (message.type) {
|
|
213
|
+
case "ping":
|
|
214
|
+
sendResponse({ type: "pong", id: message.id });
|
|
215
|
+
break;
|
|
216
|
+
case "cancel":
|
|
217
|
+
if (state.claudeProcess) {
|
|
218
|
+
state.claudeProcess.kill("SIGTERM");
|
|
219
|
+
state.claudeProcess = null;
|
|
220
|
+
state.currentRequestId = null;
|
|
221
|
+
sendResponse({
|
|
222
|
+
type: "response",
|
|
223
|
+
id: message.id,
|
|
224
|
+
content: "Cancelado",
|
|
225
|
+
done: true,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
break;
|
|
229
|
+
case "clear":
|
|
230
|
+
// Reset session for new conversation
|
|
231
|
+
state.sessionId = null;
|
|
232
|
+
console.log(chalk.yellow(" ๐๏ธ Sesiรณn limpiada"));
|
|
233
|
+
sendResponse({
|
|
234
|
+
type: "status",
|
|
235
|
+
id: message.id,
|
|
236
|
+
content: "cleared",
|
|
237
|
+
});
|
|
238
|
+
break;
|
|
239
|
+
case "chat":
|
|
240
|
+
if (!message.viewId || !message.message) {
|
|
241
|
+
sendResponse({
|
|
242
|
+
type: "error",
|
|
243
|
+
id: message.id,
|
|
244
|
+
error: "viewId y message son requeridos",
|
|
245
|
+
});
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
state.currentRequestId = message.id;
|
|
249
|
+
try {
|
|
250
|
+
// Pass existing sessionId for memory, get back new/updated sessionId
|
|
251
|
+
const newSessionId = await executeClaudeCode(message.message, message.viewId, message.viewName || "Unknown", message.id, sendResponse, state.sessionId);
|
|
252
|
+
// Save session for next message
|
|
253
|
+
if (newSessionId) {
|
|
254
|
+
state.sessionId = newSessionId;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
sendResponse({
|
|
259
|
+
type: "error",
|
|
260
|
+
id: message.id,
|
|
261
|
+
error: error.message || "Error desconocido",
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
finally {
|
|
265
|
+
state.claudeProcess = null;
|
|
266
|
+
state.currentRequestId = null;
|
|
267
|
+
}
|
|
268
|
+
break;
|
|
269
|
+
default:
|
|
270
|
+
sendResponse({
|
|
271
|
+
type: "error",
|
|
272
|
+
id: message.id || "unknown",
|
|
273
|
+
error: `Tipo de mensaje desconocido: ${message.type}`,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
catch (error) {
|
|
278
|
+
sendResponse({
|
|
279
|
+
type: "error",
|
|
280
|
+
id: "parse-error",
|
|
281
|
+
error: `Error parseando mensaje: ${error.message}`,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
export async function assistantCommand(flags = {}) {
|
|
286
|
+
// Check login
|
|
287
|
+
if (!isLoggedIn()) {
|
|
288
|
+
console.log(chalk.red("\n โ No estรกs logueado. Usa: gufi login\n"));
|
|
289
|
+
process.exit(1);
|
|
290
|
+
}
|
|
291
|
+
// Check Claude Code is installed
|
|
292
|
+
try {
|
|
293
|
+
const { execSync } = await import("child_process");
|
|
294
|
+
execSync("claude --version", { stdio: "ignore" });
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
console.log(chalk.red("\n โ Claude Code no encontrado."));
|
|
298
|
+
console.log(chalk.gray(" Instala con: npm install -g @anthropic-ai/claude-code\n"));
|
|
299
|
+
process.exit(1);
|
|
300
|
+
}
|
|
301
|
+
const port = typeof flags.port === "string" ? parseInt(flags.port, 10) : flags.port || DEFAULT_PORT;
|
|
302
|
+
const env = getCurrentEnv();
|
|
303
|
+
console.log(chalk.magenta("\n ๐ฃ Gufi Assistant\n"));
|
|
304
|
+
console.log(chalk.gray(` Entorno: ${env === "prod" ? "Producciรณn" : "Local"}`));
|
|
305
|
+
console.log(chalk.gray(` Puerto WebSocket: ${port}`));
|
|
306
|
+
console.log();
|
|
307
|
+
// Create WebSocket server
|
|
308
|
+
const wss = new WebSocketServer({ port });
|
|
309
|
+
wss.on("listening", () => {
|
|
310
|
+
console.log(chalk.green(` โ WebSocket server escuchando en ws://localhost:${port}`));
|
|
311
|
+
console.log();
|
|
312
|
+
console.log(chalk.cyan(" Conecta desde el frontend:"));
|
|
313
|
+
console.log(chalk.gray(` 1. Abre /developer/views/<id> en Gufi`));
|
|
314
|
+
console.log(chalk.gray(` 2. Click en "Edit with AI"`));
|
|
315
|
+
console.log(chalk.gray(` 3. El chat se conecta automรกticamente aquรญ`));
|
|
316
|
+
console.log();
|
|
317
|
+
console.log(chalk.yellow(" Ctrl+C para detener\n"));
|
|
318
|
+
});
|
|
319
|
+
wss.on("connection", (ws) => {
|
|
320
|
+
console.log(chalk.green(" โ Cliente conectado"));
|
|
321
|
+
// Initialize client state
|
|
322
|
+
clients.set(ws, {
|
|
323
|
+
ws,
|
|
324
|
+
claudeProcess: null,
|
|
325
|
+
currentRequestId: null,
|
|
326
|
+
sessionId: null,
|
|
327
|
+
});
|
|
328
|
+
// Send status message
|
|
329
|
+
ws.send(JSON.stringify({
|
|
330
|
+
type: "status",
|
|
331
|
+
id: "init",
|
|
332
|
+
content: "connected",
|
|
333
|
+
}));
|
|
334
|
+
ws.on("message", (data) => {
|
|
335
|
+
handleMessage(ws, data.toString());
|
|
336
|
+
});
|
|
337
|
+
ws.on("close", () => {
|
|
338
|
+
console.log(chalk.yellow(" โ Cliente desconectado"));
|
|
339
|
+
// Don't kill Claude process on disconnect - let it finish
|
|
340
|
+
// The client might reconnect (hot reload, etc.)
|
|
341
|
+
clients.delete(ws);
|
|
342
|
+
});
|
|
343
|
+
ws.on("error", (error) => {
|
|
344
|
+
console.log(chalk.red(` โ Error WebSocket: ${error.message}`));
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
wss.on("error", (error) => {
|
|
348
|
+
if (error.code === "EADDRINUSE") {
|
|
349
|
+
console.log(chalk.red(`\n โ Puerto ${port} ya estรก en uso.`));
|
|
350
|
+
console.log(chalk.gray(` Usa: gufi assistant --port <otro-puerto>\n`));
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
console.log(chalk.red(`\n โ Error: ${error.message}\n`));
|
|
354
|
+
}
|
|
355
|
+
process.exit(1);
|
|
356
|
+
});
|
|
357
|
+
// Handle graceful shutdown
|
|
358
|
+
process.on("SIGINT", () => {
|
|
359
|
+
console.log(chalk.yellow("\n Cerrando servidor..."));
|
|
360
|
+
// Kill all Claude processes with SIGKILL (force)
|
|
361
|
+
for (const state of clients.values()) {
|
|
362
|
+
if (state.claudeProcess) {
|
|
363
|
+
state.claudeProcess.kill("SIGKILL");
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
// Close all WebSocket connections
|
|
367
|
+
for (const client of wss.clients) {
|
|
368
|
+
client.terminate();
|
|
369
|
+
}
|
|
370
|
+
// Force exit after 1 second max
|
|
371
|
+
setTimeout(() => {
|
|
372
|
+
console.log(chalk.green(" โ Servidor cerrado\n"));
|
|
373
|
+
process.exit(0);
|
|
374
|
+
}, 500);
|
|
375
|
+
wss.close();
|
|
376
|
+
});
|
|
377
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -82,11 +82,14 @@ import { doctorCommand } from "./commands/doctor.js";
|
|
|
82
82
|
import { docsCommand } from "./commands/docs.js";
|
|
83
83
|
import { startMcpServer } from "./mcp.js";
|
|
84
84
|
import { claudeCommand } from "./commands/claude.js";
|
|
85
|
+
import { createRequire } from "module";
|
|
86
|
+
const require = createRequire(import.meta.url);
|
|
87
|
+
const pkg = require("../package.json");
|
|
85
88
|
const program = new Command();
|
|
86
89
|
program
|
|
87
90
|
.name("gufi")
|
|
88
91
|
.description("๐ฃ Gufi CLI - Desarrolla mรณdulos, vistas y automations")
|
|
89
|
-
.version(
|
|
92
|
+
.version(pkg.version);
|
|
90
93
|
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
91
94
|
// ๐ Auth
|
|
92
95
|
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
package/dist/mcp.js
CHANGED
|
@@ -37,19 +37,36 @@ const DEFAULT_ENV = "prod";
|
|
|
37
37
|
/**
|
|
38
38
|
* Get API URL for the specified environment.
|
|
39
39
|
* @param env - 'prod' (default) or 'dev' (localhost:3000 โ Cloud SQL Dev)
|
|
40
|
+
*
|
|
41
|
+
* Uses resolveEnv() which is STRICT - invalid values throw errors.
|
|
40
42
|
*/
|
|
41
43
|
function getApiUrl(env) {
|
|
42
|
-
const resolvedEnv = (env
|
|
44
|
+
const resolvedEnv = resolveEnv(env);
|
|
43
45
|
return ENV_URLS[resolvedEnv];
|
|
44
46
|
}
|
|
45
47
|
/**
|
|
46
48
|
* Resolve env parameter to canonical form ('prod' or 'dev')
|
|
47
49
|
* 'local' is treated as alias for 'dev'
|
|
50
|
+
*
|
|
51
|
+
* IMPORTANT: This function is STRICT to prevent accidental prod modifications.
|
|
52
|
+
* - undefined/null โ defaults to 'prod' (backwards compatible)
|
|
53
|
+
* - 'prod', 'dev', 'local' โ valid values
|
|
54
|
+
* - Any other value โ THROWS ERROR (prevents typos like 'dve' from hitting prod)
|
|
48
55
|
*/
|
|
49
56
|
function resolveEnv(env) {
|
|
57
|
+
// Undefined/null โ default to prod (backwards compatible)
|
|
58
|
+
if (env === undefined || env === null || env === "") {
|
|
59
|
+
return "prod";
|
|
60
|
+
}
|
|
61
|
+
// Valid values
|
|
50
62
|
if (env === "dev" || env === "local")
|
|
51
63
|
return "dev";
|
|
52
|
-
|
|
64
|
+
if (env === "prod")
|
|
65
|
+
return "prod";
|
|
66
|
+
// STRICT: Unknown value = ERROR, not silent fallback to prod
|
|
67
|
+
// This prevents typos like 'dve' or 'Dev' from accidentally modifying prod
|
|
68
|
+
throw new Error(`Invalid environment: '${env}'. Valid values are 'prod', 'dev', or 'local'. ` +
|
|
69
|
+
`This error prevents accidental production modifications.`);
|
|
53
70
|
}
|
|
54
71
|
// Keep for backwards compatibility (some internal functions may use this)
|
|
55
72
|
function getSessionApiUrl() {
|
|
@@ -871,23 +888,24 @@ const toolHandlers = {
|
|
|
871
888
|
async gufi_context(params) {
|
|
872
889
|
// Generate intelligent context based on what's requested
|
|
873
890
|
// Always returns FULL context (no more "detail" parameter)
|
|
891
|
+
const env = params.env;
|
|
874
892
|
// Module-specific context
|
|
875
893
|
if (params.module_id && params.company_id) {
|
|
876
|
-
return getClaudeOptimizedContext(params.company_id, params.module_id, undefined, true);
|
|
894
|
+
return getClaudeOptimizedContext(params.company_id, params.module_id, undefined, true, env);
|
|
877
895
|
}
|
|
878
896
|
// Entity-specific context
|
|
879
897
|
if (params.entity_id && params.company_id) {
|
|
880
|
-
return getClaudeOptimizedContext(params.company_id, undefined, params.entity_id, true);
|
|
898
|
+
return getClaudeOptimizedContext(params.company_id, undefined, params.entity_id, true, env);
|
|
881
899
|
}
|
|
882
900
|
if (params.view_id) {
|
|
883
|
-
return generateViewContextMcp(parseInt(params.view_id), true);
|
|
901
|
+
return generateViewContextMcp(parseInt(params.view_id), true, env);
|
|
884
902
|
}
|
|
885
903
|
else if (params.package_id) {
|
|
886
|
-
return generatePackageContextMcp(parseInt(params.package_id), true);
|
|
904
|
+
return generatePackageContextMcp(parseInt(params.package_id), true, env);
|
|
887
905
|
}
|
|
888
906
|
else if (params.company_id) {
|
|
889
907
|
// Company context - always full
|
|
890
|
-
return getClaudeOptimizedContext(params.company_id, undefined, undefined, true);
|
|
908
|
+
return getClaudeOptimizedContext(params.company_id, undefined, undefined, true, env);
|
|
891
909
|
}
|
|
892
910
|
else {
|
|
893
911
|
// Auto-detect context from current directory
|
|
@@ -922,10 +940,10 @@ const toolHandlers = {
|
|
|
922
940
|
// 2. Check if we're in a view directory (.gufi-view.json)
|
|
923
941
|
const viewMeta = loadViewMetaFromCwd();
|
|
924
942
|
if (viewMeta) {
|
|
925
|
-
return generateViewContextMcp(viewMeta.viewId, true);
|
|
943
|
+
return generateViewContextMcp(viewMeta.viewId, true, env);
|
|
926
944
|
}
|
|
927
945
|
// 3. Fallback: Return packages overview
|
|
928
|
-
const response = await developerRequest("/my-packages");
|
|
946
|
+
const response = await developerRequest("/my-packages", {}, true, env);
|
|
929
947
|
const packages = response.data || [];
|
|
930
948
|
return {
|
|
931
949
|
type: "packages_overview",
|
|
@@ -983,7 +1001,7 @@ const toolHandlers = {
|
|
|
983
1001
|
preview: params.preview || false,
|
|
984
1002
|
skip_existing: params.skip_existing || false,
|
|
985
1003
|
}),
|
|
986
|
-
}, params.company_id);
|
|
1004
|
+
}, params.company_id, true, params.env);
|
|
987
1005
|
if (params.preview) {
|
|
988
1006
|
return {
|
|
989
1007
|
preview: true,
|
|
@@ -1261,7 +1279,7 @@ const toolHandlers = {
|
|
|
1261
1279
|
}
|
|
1262
1280
|
const data = await apiRequest(endpoint, {
|
|
1263
1281
|
headers: { "X-Company-ID": params.company_id },
|
|
1264
|
-
});
|
|
1282
|
+
}, params.company_id, true, params.env);
|
|
1265
1283
|
return { executions: data.data || data || [] };
|
|
1266
1284
|
},
|
|
1267
1285
|
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
@@ -1387,10 +1405,10 @@ const toolHandlers = {
|
|
|
1387
1405
|
// Environment Variables
|
|
1388
1406
|
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
1389
1407
|
async gufi_env(params) {
|
|
1390
|
-
const { action, company_id, key, value } = params;
|
|
1408
|
+
const { action, company_id, key, value, env } = params;
|
|
1391
1409
|
switch (action) {
|
|
1392
1410
|
case "list": {
|
|
1393
|
-
const data = await apiRequest("/api/cli/env", {}, company_id);
|
|
1411
|
+
const data = await apiRequest("/api/cli/env", {}, company_id, true, env);
|
|
1394
1412
|
return { variables: data || [] };
|
|
1395
1413
|
}
|
|
1396
1414
|
case "set": {
|
|
@@ -1399,7 +1417,7 @@ const toolHandlers = {
|
|
|
1399
1417
|
await apiRequest("/api/cli/env", {
|
|
1400
1418
|
method: "POST",
|
|
1401
1419
|
body: JSON.stringify({ key, value }),
|
|
1402
|
-
}, company_id);
|
|
1420
|
+
}, company_id, true, env);
|
|
1403
1421
|
return { success: true, key };
|
|
1404
1422
|
}
|
|
1405
1423
|
case "delete": {
|
|
@@ -1407,7 +1425,7 @@ const toolHandlers = {
|
|
|
1407
1425
|
throw new Error("key required for action 'delete'");
|
|
1408
1426
|
await apiRequest(`/api/cli/env/${encodeURIComponent(key)}`, {
|
|
1409
1427
|
method: "DELETE",
|
|
1410
|
-
}, company_id);
|
|
1428
|
+
}, company_id, true, env);
|
|
1411
1429
|
return { success: true, key };
|
|
1412
1430
|
}
|
|
1413
1431
|
default:
|
|
@@ -1420,8 +1438,9 @@ const toolHandlers = {
|
|
|
1420
1438
|
async gufi_view_pull(params) {
|
|
1421
1439
|
const viewId = params.view_id;
|
|
1422
1440
|
const companyId = params.company_id;
|
|
1441
|
+
const env = params.env;
|
|
1423
1442
|
// Get view info for package_id (pass company_id for access check)
|
|
1424
|
-
const viewResponse = await apiRequest(`/api/marketplace/views/${viewId}`, {}, companyId);
|
|
1443
|
+
const viewResponse = await apiRequest(`/api/marketplace/views/${viewId}`, {}, companyId, true, env);
|
|
1425
1444
|
const view = viewResponse.data || viewResponse;
|
|
1426
1445
|
// ๐ Backend returns pk_id, not id
|
|
1427
1446
|
if (!view || !view.pk_id) {
|
|
@@ -1444,6 +1463,7 @@ const toolHandlers = {
|
|
|
1444
1463
|
async gufi_view_push(params) {
|
|
1445
1464
|
let viewDir;
|
|
1446
1465
|
let viewId = params.view_id;
|
|
1466
|
+
const env = params.env;
|
|
1447
1467
|
// If view_id provided, get dir from it
|
|
1448
1468
|
if (viewId) {
|
|
1449
1469
|
viewDir = getViewDir(`view_${viewId}`);
|
|
@@ -1459,7 +1479,7 @@ const toolHandlers = {
|
|
|
1459
1479
|
// Get view info for package
|
|
1460
1480
|
let packageInfo = null;
|
|
1461
1481
|
if (viewId) {
|
|
1462
|
-
const viewResponse = await apiRequest(`/api/marketplace/views/${viewId}
|
|
1482
|
+
const viewResponse = await apiRequest(`/api/marketplace/views/${viewId}`, {}, undefined, true, env);
|
|
1463
1483
|
const view = viewResponse.data || viewResponse;
|
|
1464
1484
|
if (view.package_id) {
|
|
1465
1485
|
packageInfo = { id: view.package_id, publish_cmd: `gufi package:publish ${view.package_id}` };
|
|
@@ -1483,10 +1503,10 @@ const toolHandlers = {
|
|
|
1483
1503
|
// Packages
|
|
1484
1504
|
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
1485
1505
|
async gufi_package(params) {
|
|
1486
|
-
const { action, id, name, description, module_id, company_id } = params;
|
|
1506
|
+
const { action, id, name, description, module_id, company_id, env } = params;
|
|
1487
1507
|
switch (action) {
|
|
1488
1508
|
case "list": {
|
|
1489
|
-
const response = await developerRequest("/my-packages");
|
|
1509
|
+
const response = await developerRequest("/my-packages", {}, true, env);
|
|
1490
1510
|
const packages = response.data || [];
|
|
1491
1511
|
return {
|
|
1492
1512
|
packages: packages.map((p) => ({
|
|
@@ -1502,11 +1522,11 @@ const toolHandlers = {
|
|
|
1502
1522
|
case "get": {
|
|
1503
1523
|
if (!id)
|
|
1504
1524
|
throw new Error("id required for action 'get'");
|
|
1505
|
-
const response = await developerRequest(`/packages/${id}
|
|
1525
|
+
const response = await developerRequest(`/packages/${id}`, {}, true, env);
|
|
1506
1526
|
const pkg = response.data;
|
|
1507
1527
|
let views = [];
|
|
1508
1528
|
try {
|
|
1509
|
-
const viewsResponse = await developerRequest(`/packages/${id}/views
|
|
1529
|
+
const viewsResponse = await developerRequest(`/packages/${id}/views`, {}, true, env);
|
|
1510
1530
|
views = viewsResponse.data || [];
|
|
1511
1531
|
}
|
|
1512
1532
|
catch { }
|
|
@@ -1543,13 +1563,13 @@ const toolHandlers = {
|
|
|
1543
1563
|
version: "1.0.0",
|
|
1544
1564
|
status: "draft",
|
|
1545
1565
|
}),
|
|
1546
|
-
});
|
|
1566
|
+
}, true, env);
|
|
1547
1567
|
return { success: true, package: response.data };
|
|
1548
1568
|
}
|
|
1549
1569
|
case "delete": {
|
|
1550
1570
|
if (!id)
|
|
1551
1571
|
throw new Error("id required for action 'delete'");
|
|
1552
|
-
await developerRequest(`/packages/${id}`, { method: "DELETE" });
|
|
1572
|
+
await developerRequest(`/packages/${id}`, { method: "DELETE" }, true, env);
|
|
1553
1573
|
return { success: true };
|
|
1554
1574
|
}
|
|
1555
1575
|
case "add_module": {
|
|
@@ -1561,7 +1581,7 @@ const toolHandlers = {
|
|
|
1561
1581
|
companyId: parseInt(company_id),
|
|
1562
1582
|
moduleId: parseInt(module_id),
|
|
1563
1583
|
}),
|
|
1564
|
-
});
|
|
1584
|
+
}, true, env);
|
|
1565
1585
|
return { success: true, module: response.data };
|
|
1566
1586
|
}
|
|
1567
1587
|
case "remove_module": {
|
|
@@ -1569,7 +1589,7 @@ const toolHandlers = {
|
|
|
1569
1589
|
throw new Error("id and module_id required for action 'remove_module'");
|
|
1570
1590
|
await developerRequest(`/packages/${id}/modules/${module_id}`, {
|
|
1571
1591
|
method: "DELETE",
|
|
1572
|
-
});
|
|
1592
|
+
}, true, env);
|
|
1573
1593
|
return { success: true };
|
|
1574
1594
|
}
|
|
1575
1595
|
case "publish": {
|
|
@@ -1577,7 +1597,7 @@ const toolHandlers = {
|
|
|
1577
1597
|
throw new Error("id required for action 'publish'");
|
|
1578
1598
|
const response = await developerRequest(`/packages/${id}/publish`, {
|
|
1579
1599
|
method: "POST",
|
|
1580
|
-
});
|
|
1600
|
+
}, true, env);
|
|
1581
1601
|
return { success: true, status: response.data?.status, version: response.data?.version };
|
|
1582
1602
|
}
|
|
1583
1603
|
default:
|
|
@@ -1596,8 +1616,9 @@ const toolHandlers = {
|
|
|
1596
1616
|
* @param moduleId - Optional module ID to filter by
|
|
1597
1617
|
* @param entityId - Optional entity ID to filter by
|
|
1598
1618
|
* @param fullText - If true, return as plain text. Otherwise return structured.
|
|
1619
|
+
* @param env - Environment: 'prod' (default) or 'dev'
|
|
1599
1620
|
*/
|
|
1600
|
-
async function getClaudeOptimizedContext(companyId, moduleId, entityId, fullText = true) {
|
|
1621
|
+
async function getClaudeOptimizedContext(companyId, moduleId, entityId, fullText = true, env) {
|
|
1601
1622
|
// Build query params
|
|
1602
1623
|
const params = new URLSearchParams();
|
|
1603
1624
|
if (moduleId)
|
|
@@ -1605,12 +1626,14 @@ async function getClaudeOptimizedContext(companyId, moduleId, entityId, fullText
|
|
|
1605
1626
|
if (entityId)
|
|
1606
1627
|
params.set("entity_id", entityId);
|
|
1607
1628
|
const queryString = params.toString();
|
|
1608
|
-
const
|
|
1609
|
-
|
|
1629
|
+
const apiUrl = getApiUrl(env);
|
|
1630
|
+
const url = `${apiUrl}/api/schema/export-claude${queryString ? "?" + queryString : ""}`;
|
|
1631
|
+
const resolvedEnv = resolveEnv(env);
|
|
1632
|
+
let token = getTokenForEnv(resolvedEnv);
|
|
1610
1633
|
if (!token) {
|
|
1611
|
-
token = await
|
|
1634
|
+
token = await autoLoginWithEnv(env);
|
|
1612
1635
|
if (!token)
|
|
1613
|
-
throw new Error(
|
|
1636
|
+
throw new Error(`Not logged in for env '${resolvedEnv}'. Run: gufi login`);
|
|
1614
1637
|
}
|
|
1615
1638
|
const headers = {
|
|
1616
1639
|
Authorization: `Bearer ${token}`,
|
|
@@ -1645,15 +1668,15 @@ async function getClaudeOptimizedContext(companyId, moduleId, entityId, fullText
|
|
|
1645
1668
|
schema,
|
|
1646
1669
|
};
|
|
1647
1670
|
}
|
|
1648
|
-
async function generateViewContextMcp(viewId, includeConcepts) {
|
|
1671
|
+
async function generateViewContextMcp(viewId, includeConcepts, env) {
|
|
1649
1672
|
// Use apiRequest for view details
|
|
1650
|
-
const viewResponse = await apiRequest(`/api/marketplace/views/${viewId}
|
|
1673
|
+
const viewResponse = await apiRequest(`/api/marketplace/views/${viewId}`, {}, undefined, true, env);
|
|
1651
1674
|
const view = viewResponse.data || viewResponse;
|
|
1652
1675
|
// Get package info if exists
|
|
1653
1676
|
let pkg = null;
|
|
1654
1677
|
if (view.package_id) {
|
|
1655
1678
|
try {
|
|
1656
|
-
const pkgResponse = await developerRequest(`/packages/${view.package_id}
|
|
1679
|
+
const pkgResponse = await developerRequest(`/packages/${view.package_id}`, {}, true, env);
|
|
1657
1680
|
pkg = pkgResponse.data || pkgResponse;
|
|
1658
1681
|
}
|
|
1659
1682
|
catch { }
|
|
@@ -1681,17 +1704,17 @@ async function generateViewContextMcp(viewId, includeConcepts) {
|
|
|
1681
1704
|
try {
|
|
1682
1705
|
const modulesResponse = await apiRequest(`/api/company/schema`, {
|
|
1683
1706
|
headers: { "X-Company-ID": String(viewCompanyId) },
|
|
1684
|
-
}, String(viewCompanyId));
|
|
1707
|
+
}, String(viewCompanyId), true, env);
|
|
1685
1708
|
modules = modulesResponse.modules || modulesResponse.data?.modules || [];
|
|
1686
1709
|
}
|
|
1687
1710
|
catch { }
|
|
1688
1711
|
try {
|
|
1689
|
-
const automationsResponse = await apiRequest(`/api/automation-scripts`, {}, String(viewCompanyId));
|
|
1712
|
+
const automationsResponse = await apiRequest(`/api/automation-scripts`, {}, String(viewCompanyId), true, env);
|
|
1690
1713
|
automations = Array.isArray(automationsResponse) ? automationsResponse : automationsResponse.data || [];
|
|
1691
1714
|
}
|
|
1692
1715
|
catch { }
|
|
1693
1716
|
try {
|
|
1694
|
-
const envResponse = await apiRequest(`/api/cli/env`, {}, String(viewCompanyId));
|
|
1717
|
+
const envResponse = await apiRequest(`/api/cli/env`, {}, String(viewCompanyId), true, env);
|
|
1695
1718
|
envVars = Array.isArray(envResponse) ? envResponse : envResponse.data || [];
|
|
1696
1719
|
}
|
|
1697
1720
|
catch { }
|
|
@@ -1756,14 +1779,14 @@ async function generateViewContextMcp(viewId, includeConcepts) {
|
|
|
1756
1779
|
}
|
|
1757
1780
|
return result;
|
|
1758
1781
|
}
|
|
1759
|
-
async function generatePackageContextMcp(packageId, includeConcepts) {
|
|
1782
|
+
async function generatePackageContextMcp(packageId, includeConcepts, env) {
|
|
1760
1783
|
// Use developerRequest which has correct path /api/developer/...
|
|
1761
|
-
const response = await developerRequest(`/packages/${packageId}
|
|
1784
|
+
const response = await developerRequest(`/packages/${packageId}`, {}, true, env);
|
|
1762
1785
|
const pkg = response.data || response;
|
|
1763
1786
|
// Get views
|
|
1764
1787
|
let views = [];
|
|
1765
1788
|
try {
|
|
1766
|
-
const viewsResponse = await developerRequest(`/packages/${packageId}/views
|
|
1789
|
+
const viewsResponse = await developerRequest(`/packages/${packageId}/views`, {}, true, env);
|
|
1767
1790
|
views = viewsResponse.data || [];
|
|
1768
1791
|
}
|
|
1769
1792
|
catch { }
|
package/package.json
CHANGED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* gufi install - Instala Gufi Claude como servicio de sistema
|
|
3
|
-
*
|
|
4
|
-
* Mac: LaunchAgent que arranca con el sistema
|
|
5
|
-
* Linux: systemd user service
|
|
6
|
-
* Windows: Task Scheduler que arranca con login
|
|
7
|
-
*/
|
|
8
|
-
/**
|
|
9
|
-
* ๐ gufi install - Install as system service
|
|
10
|
-
*/
|
|
11
|
-
export declare function installCommand(options: {
|
|
12
|
-
port?: number;
|
|
13
|
-
}): Promise<void>;
|
|
14
|
-
/**
|
|
15
|
-
* ๐ gufi uninstall - Remove system service
|
|
16
|
-
*/
|
|
17
|
-
export declare function uninstallCommand(): Promise<void>;
|
|
18
|
-
/**
|
|
19
|
-
* ๐ gufi service:status - Check service status
|
|
20
|
-
*/
|
|
21
|
-
export declare function serviceStatusCommand(): Promise<void>;
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* gufi setup-claude - Configure Claude Code with Gufi MCP
|
|
3
|
-
*
|
|
4
|
-
* Creates workspace and configures MCP server
|
|
5
|
-
*/
|
|
6
|
-
import chalk from "chalk";
|
|
7
|
-
import ora from "ora";
|
|
8
|
-
import fs from "fs";
|
|
9
|
-
import path from "path";
|
|
10
|
-
import os from "os";
|
|
11
|
-
import { isLoggedIn, loadConfig } from "../lib/config.js";
|
|
12
|
-
const WORKSPACE_DIR = path.join(os.homedir(), "gufi-workspace");
|
|
13
|
-
const CLAUDE_CONFIG_DIR = path.join(os.homedir(), ".claude");
|
|
14
|
-
// Claude Code CLI uses settings.json, NOT claude_desktop_config.json
|
|
15
|
-
const CLAUDE_SETTINGS_FILE = path.join(CLAUDE_CONFIG_DIR, "settings.json");
|
|
16
|
-
const CLAUDE_MD_CONTENT = `# Gufi Workspace
|
|
17
|
-
|
|
18
|
-
Este es tu espacio de trabajo para Gufi ERP.
|
|
19
|
-
|
|
20
|
-
## MCP Tools Disponibles
|
|
21
|
-
|
|
22
|
-
Tienes acceso a todas las herramientas de Gufi:
|
|
23
|
-
|
|
24
|
-
- \`gufi_context\` - Obtener contexto de empresa, mรณdulos, entidades
|
|
25
|
-
- \`gufi_rows\` / \`gufi_row\` - Leer datos
|
|
26
|
-
- \`gufi_row_create\` / \`gufi_row_update\` - Crear/actualizar datos
|
|
27
|
-
- \`gufi_schema_modify\` - Modificar schema
|
|
28
|
-
- \`gufi_automations\` - Ver automatizaciones
|
|
29
|
-
- \`gufi_docs\` - Leer documentaciรณn
|
|
30
|
-
|
|
31
|
-
## Quick Start
|
|
32
|
-
|
|
33
|
-
1. Ver tus empresas:
|
|
34
|
-
\`\`\`
|
|
35
|
-
Usa gufi_companies para ver las empresas disponibles
|
|
36
|
-
\`\`\`
|
|
37
|
-
|
|
38
|
-
2. Obtener contexto de una empresa:
|
|
39
|
-
\`\`\`
|
|
40
|
-
Usa gufi_context con company_id para ver mรณdulos y entidades
|
|
41
|
-
\`\`\`
|
|
42
|
-
|
|
43
|
-
3. Consultar datos:
|
|
44
|
-
\`\`\`
|
|
45
|
-
Usa gufi_rows con el nombre de tabla (ej: m308_t4136)
|
|
46
|
-
\`\`\`
|
|
47
|
-
|
|
48
|
-
## Documentaciรณn
|
|
49
|
-
|
|
50
|
-
Para mรกs informaciรณn, usa:
|
|
51
|
-
\`\`\`
|
|
52
|
-
gufi_docs({ topic: "overview" })
|
|
53
|
-
\`\`\`
|
|
54
|
-
|
|
55
|
-
---
|
|
56
|
-
Configurado con: gufi setup-claude
|
|
57
|
-
`;
|
|
58
|
-
export async function setupClaudeCommand() {
|
|
59
|
-
console.log(chalk.magenta(`
|
|
60
|
-
โญโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
|
|
61
|
-
โ ๐ฟ๏ธ Gufi Claude Code Setup โ
|
|
62
|
-
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
|
|
63
|
-
`));
|
|
64
|
-
// Check login
|
|
65
|
-
if (!isLoggedIn()) {
|
|
66
|
-
console.log(chalk.yellow(" โ ๏ธ Primero debes iniciar sesiรณn."));
|
|
67
|
-
console.log(chalk.gray(" Ejecuta: gufi login\n"));
|
|
68
|
-
process.exit(1);
|
|
69
|
-
}
|
|
70
|
-
const config = loadConfig();
|
|
71
|
-
console.log(chalk.gray(` Usuario: ${config.email}\n`));
|
|
72
|
-
// Step 1: Create workspace directory
|
|
73
|
-
const spinnerWorkspace = ora("Creando workspace...").start();
|
|
74
|
-
try {
|
|
75
|
-
if (!fs.existsSync(WORKSPACE_DIR)) {
|
|
76
|
-
fs.mkdirSync(WORKSPACE_DIR, { recursive: true });
|
|
77
|
-
}
|
|
78
|
-
// Create CLAUDE.md
|
|
79
|
-
const claudeMdPath = path.join(WORKSPACE_DIR, "CLAUDE.md");
|
|
80
|
-
fs.writeFileSync(claudeMdPath, CLAUDE_MD_CONTENT);
|
|
81
|
-
spinnerWorkspace.succeed(chalk.green(`Workspace creado: ${WORKSPACE_DIR}`));
|
|
82
|
-
}
|
|
83
|
-
catch (err) {
|
|
84
|
-
spinnerWorkspace.fail(chalk.red(`Error creando workspace: ${err.message}`));
|
|
85
|
-
process.exit(1);
|
|
86
|
-
}
|
|
87
|
-
// Step 2: Configure MCP in Claude Code
|
|
88
|
-
const spinnerMcp = ora("Configurando MCP en Claude Code...").start();
|
|
89
|
-
try {
|
|
90
|
-
// Ensure .claude directory exists
|
|
91
|
-
if (!fs.existsSync(CLAUDE_CONFIG_DIR)) {
|
|
92
|
-
fs.mkdirSync(CLAUDE_CONFIG_DIR, { recursive: true });
|
|
93
|
-
}
|
|
94
|
-
// Read existing config or create new
|
|
95
|
-
let claudeConfig = { mcpServers: {} };
|
|
96
|
-
if (fs.existsSync(CLAUDE_SETTINGS_FILE)) {
|
|
97
|
-
try {
|
|
98
|
-
const existing = fs.readFileSync(CLAUDE_SETTINGS_FILE, "utf8");
|
|
99
|
-
claudeConfig = JSON.parse(existing);
|
|
100
|
-
if (!claudeConfig.mcpServers) {
|
|
101
|
-
claudeConfig.mcpServers = {};
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
catch {
|
|
105
|
-
// If parse fails, start fresh
|
|
106
|
-
claudeConfig = { mcpServers: {} };
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
// Add/update Gufi MCP server
|
|
110
|
-
claudeConfig.mcpServers.gufi = {
|
|
111
|
-
command: "gufi",
|
|
112
|
-
args: ["mcp"],
|
|
113
|
-
};
|
|
114
|
-
// Write config
|
|
115
|
-
fs.writeFileSync(CLAUDE_SETTINGS_FILE, JSON.stringify(claudeConfig, null, 2));
|
|
116
|
-
spinnerMcp.succeed(chalk.green("MCP de Gufi configurado en Claude Code"));
|
|
117
|
-
}
|
|
118
|
-
catch (err) {
|
|
119
|
-
spinnerMcp.fail(chalk.red(`Error configurando MCP: ${err.message}`));
|
|
120
|
-
process.exit(1);
|
|
121
|
-
}
|
|
122
|
-
// Success message
|
|
123
|
-
console.log(chalk.cyan(`
|
|
124
|
-
โ
Setup completado!
|
|
125
|
-
|
|
126
|
-
Tu workspace estรก en:
|
|
127
|
-
${chalk.white(WORKSPACE_DIR)}
|
|
128
|
-
|
|
129
|
-
Para usar Claude Code con Gufi:
|
|
130
|
-
${chalk.white("gufi claude")}
|
|
131
|
-
|
|
132
|
-
Esto abrirรก Claude Code con acceso a:
|
|
133
|
-
โข Todas las herramientas MCP de Gufi
|
|
134
|
-
โข Tu sesiรณn y empresas
|
|
135
|
-
โข Documentaciรณn integrada
|
|
136
|
-
`));
|
|
137
|
-
}
|