gufi-cli 0.1.27 → 0.1.31
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/CLAUDE.md +33 -35
- package/README.md +2 -4
- package/dist/commands/claude.d.ts +17 -0
- package/dist/commands/claude.js +422 -0
- package/dist/commands/context.js +1 -2
- package/dist/commands/doctor.js +92 -25
- package/dist/commands/install.d.ts +21 -0
- package/dist/commands/packages.js +28 -70
- package/dist/commands/push.d.ts +3 -13
- package/dist/commands/push.js +29 -130
- package/dist/commands/setup-claude.d.ts +6 -0
- package/dist/commands/setup-claude.js +137 -0
- package/dist/index.d.ts +2 -5
- package/dist/index.js +17 -39
- package/dist/lib/api.d.ts +15 -2
- package/dist/lib/api.js +20 -11
- package/dist/lib/security.js +10 -2
- package/dist/lib/sync.d.ts +6 -10
- package/dist/lib/sync.js +16 -43
- package/dist/mcp.js +418 -1876
- package/package.json +2 -1
package/CLAUDE.md
CHANGED
|
@@ -10,12 +10,11 @@
|
|
|
10
10
|
tools/cli/
|
|
11
11
|
├── src/
|
|
12
12
|
│ ├── index.ts # Entry point CLI
|
|
13
|
-
│ ├── mcp.ts # MCP Server (
|
|
13
|
+
│ ├── mcp.ts # MCP Server (12 tools)
|
|
14
14
|
│ ├── commands/ # Comandos CLI
|
|
15
15
|
│ │ ├── context.ts # gufi context
|
|
16
16
|
│ │ ├── pull.ts # gufi view:pull
|
|
17
17
|
│ │ ├── push.ts # gufi view:push
|
|
18
|
-
│ │ ├── watch.ts # gufi view:watch
|
|
19
18
|
│ │ └── ...
|
|
20
19
|
│ └── lib/
|
|
21
20
|
│ ├── api.ts # Cliente API
|
|
@@ -42,7 +41,7 @@ gufi config:prod
|
|
|
42
41
|
|
|
43
42
|
**IMPORTANTE**:
|
|
44
43
|
- Todos los tools MCP (`gufi_*`) respetan el entorno configurado
|
|
45
|
-
- `gufi_whoami()` muestra el entorno actual
|
|
44
|
+
- `gufi_whoami()` muestra el entorno actual y empresas disponibles
|
|
46
45
|
- Antes de hacer cambios, verifica que estás en el entorno correcto
|
|
47
46
|
|
|
48
47
|
### Config file (~/.gufi/config.json)
|
|
@@ -56,27 +55,30 @@ gufi config:prod
|
|
|
56
55
|
}
|
|
57
56
|
```
|
|
58
57
|
|
|
59
|
-
## MCP Server
|
|
58
|
+
## MCP Server - 12 Tools
|
|
60
59
|
|
|
61
|
-
El servidor MCP está en `src/mcp.ts` y expone
|
|
60
|
+
El servidor MCP está en `src/mcp.ts` y expone **12 tools** para que Claude interactúe con Gufi.
|
|
62
61
|
|
|
63
|
-
### Tools
|
|
62
|
+
### Tools organizados por categoría
|
|
64
63
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
64
|
+
| Categoría | Tool | Descripción |
|
|
65
|
+
|-----------|------|-------------|
|
|
66
|
+
| **Context** | `gufi_context` | Schema completo (company, view, package) |
|
|
67
|
+
| | `gufi_whoami` | Usuario, entorno, empresas |
|
|
68
|
+
| | `gufi_docs` | Documentación (`docs/mcp/`) |
|
|
69
|
+
| **Schema** | `gufi_schema_modify` | Operaciones atomicas (add/update/remove) |
|
|
70
|
+
| **Automations** | `gufi_automation` | Scripts: list, get, create |
|
|
71
|
+
| | `gufi_triggers` | Ver/configurar triggers |
|
|
72
|
+
| | `gufi_executions` | Historial de ejecuciones |
|
|
73
|
+
| **Data** | `gufi_data` | CRUD: list, get, create, update, delete |
|
|
74
|
+
| **Env** | `gufi_env` | Variables: list, set, delete |
|
|
75
|
+
| **Views** | `gufi_view_pull` | Descargar vista a local |
|
|
76
|
+
| | `gufi_view_push` | Subir cambios a draft |
|
|
77
|
+
| **Packages** | `gufi_package` | list, get, create, delete, add_module, remove_module, publish |
|
|
76
78
|
|
|
77
79
|
### Añadir un nuevo tool MCP
|
|
78
80
|
|
|
79
|
-
1. **Definir tool** en array `TOOLS` (~línea
|
|
81
|
+
1. **Definir tool** en array `TOOLS` (~línea 340):
|
|
80
82
|
```typescript
|
|
81
83
|
{
|
|
82
84
|
name: "gufi_nuevo_tool",
|
|
@@ -91,7 +93,7 @@ El servidor MCP está en `src/mcp.ts` y expone 44 tools para que Claude interact
|
|
|
91
93
|
},
|
|
92
94
|
```
|
|
93
95
|
|
|
94
|
-
2. **Añadir handler** en `toolHandlers` (~línea
|
|
96
|
+
2. **Añadir handler** en `toolHandlers` (~línea 700):
|
|
95
97
|
```typescript
|
|
96
98
|
async gufi_nuevo_tool(params: { param1: string }) {
|
|
97
99
|
// Implementación
|
|
@@ -117,16 +119,16 @@ Los endpoints del backend están montados así:
|
|
|
117
119
|
/api/marketplace/* - Marketplace público
|
|
118
120
|
/api/automation-scripts - Scripts de automatización
|
|
119
121
|
/api/cli/* - Endpoints específicos del CLI
|
|
122
|
+
/api/schema/operations - Operaciones de schema
|
|
120
123
|
```
|
|
121
124
|
|
|
122
|
-
**CRÍTICO**: Los módulos usan `/api/company/modules/`, NO `/api/modules/`
|
|
123
|
-
|
|
124
125
|
### Funciones helper importantes
|
|
125
126
|
|
|
126
127
|
- `detectIfInGufiCodebase()` - Detecta si está en el repo gogufi
|
|
127
128
|
- `loadViewMetaFromCwd()` - Carga `.gufi-view.json` del directorio actual
|
|
128
129
|
- `generateViewContextMcp()` - Genera contexto de una vista
|
|
129
130
|
- `generatePackageContextMcp()` - Genera contexto de un package
|
|
131
|
+
- `getClaudeOptimizedContext()` - Exporta schema en formato optimizado para Claude
|
|
130
132
|
- `apiRequest(endpoint, options, companyId)` - Peticiones a API con auth
|
|
131
133
|
- `developerRequest(path, options)` - Peticiones a /api/developer/
|
|
132
134
|
|
|
@@ -153,24 +155,20 @@ export async function miComando(args: string[], flags: Flags) {
|
|
|
153
155
|
| `gufi config:prod` | Cambiar a producción |
|
|
154
156
|
| `gufi context` | Generar contexto para Claude |
|
|
155
157
|
| `gufi view:pull <id>` | Descargar vista a local |
|
|
156
|
-
| `gufi view:push` |
|
|
157
|
-
| `gufi
|
|
158
|
-
| `gufi view:errors <id>` | Ver errores de compilación |
|
|
159
|
-
| `gufi package:sync <id>` | Publicar draft → producción |
|
|
158
|
+
| `gufi view:push` | Sync completo (sube todo, servidor borra lo que no esté) |
|
|
159
|
+
| `gufi package:publish <id>` | Publicar draft → producción |
|
|
160
160
|
| `gufi docs <topic>` | Ver documentación |
|
|
161
161
|
|
|
162
|
-
###
|
|
163
|
-
|
|
164
|
-
Las vistas marketplace tienen **dos tablas**:
|
|
162
|
+
### Views: Solo Pull/Push
|
|
165
163
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
164
|
+
```
|
|
165
|
+
gufi view:pull <id> → ~/gufi-dev/view_<id>/
|
|
166
|
+
# Editar archivos locales
|
|
167
|
+
gufi view:push → Sync completo
|
|
168
|
+
gufi package:publish <id> → Publicar
|
|
169
|
+
```
|
|
172
170
|
|
|
173
|
-
**
|
|
171
|
+
**Push = sync completo**: Sube TODOS los archivos, servidor borra los que no vengan.
|
|
174
172
|
|
|
175
173
|
## Config (~/.gufi/)
|
|
176
174
|
|
package/README.md
CHANGED
|
@@ -301,8 +301,7 @@ gufi package <id> # Ver detalles
|
|
|
301
301
|
gufi package:create "Nombre"
|
|
302
302
|
gufi package:add-module <packageId>
|
|
303
303
|
gufi package:add-view <packageId> <viewId>
|
|
304
|
-
gufi package:publish <id>
|
|
305
|
-
gufi package:sync <id> # Sincronizar versión
|
|
304
|
+
gufi package:publish <id> # Publicar cambios a producción
|
|
306
305
|
```
|
|
307
306
|
|
|
308
307
|
### Migraciones de Package
|
|
@@ -498,9 +497,8 @@ El MCP expone todas las funcionalidades del CLI como tools que Claude puede usar
|
|
|
498
497
|
| Tool | Descripción |
|
|
499
498
|
|------|-------------|
|
|
500
499
|
| **Contexto** | |
|
|
501
|
-
| `gufi_context` |
|
|
500
|
+
| `gufi_context` | THE ONE TOOL - Contexto unificado (schema, módulos, vistas, packages) |
|
|
502
501
|
| `gufi_whoami` | Ver usuario y entorno actual |
|
|
503
|
-
| `gufi_schema` | Ver estructura de tablas |
|
|
504
502
|
| **Companies** | |
|
|
505
503
|
| `gufi_companies` | Listar companies del usuario |
|
|
506
504
|
| `gufi_company_create` | Crear nueva company |
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gufi claude - Setup completo de Claude Code para Gufi
|
|
3
|
+
*
|
|
4
|
+
* 1. Configura workspace y MCP
|
|
5
|
+
* 2. Instala como servicio del sistema (arranca automáticamente)
|
|
6
|
+
* 3. Inicia el bridge
|
|
7
|
+
*
|
|
8
|
+
* Funciona igual en Mac, Linux y Windows.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* 💜 gufi claude - Setup + Install + Run
|
|
12
|
+
*/
|
|
13
|
+
export declare function claudeCommand(options: {
|
|
14
|
+
port?: number;
|
|
15
|
+
service?: boolean;
|
|
16
|
+
uninstall?: boolean;
|
|
17
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gufi claude - Setup completo de Claude Code para Gufi
|
|
3
|
+
*
|
|
4
|
+
* 1. Configura workspace y MCP
|
|
5
|
+
* 2. Instala como servicio del sistema (arranca automáticamente)
|
|
6
|
+
* 3. Inicia el bridge
|
|
7
|
+
*
|
|
8
|
+
* Funciona igual en Mac, Linux y Windows.
|
|
9
|
+
*/
|
|
10
|
+
import chalk from "chalk";
|
|
11
|
+
import fs from "fs";
|
|
12
|
+
import path from "path";
|
|
13
|
+
import os from "os";
|
|
14
|
+
import { spawn, execSync } from "child_process";
|
|
15
|
+
import { WebSocketServer, WebSocket } from "ws";
|
|
16
|
+
import { isLoggedIn } from "../lib/config.js";
|
|
17
|
+
const DEFAULT_PORT = 9876;
|
|
18
|
+
const WORKSPACE_DIR = path.join(os.homedir(), "gufi-workspace");
|
|
19
|
+
const GLOBAL_CLAUDE_JSON = path.join(os.homedir(), ".claude.json");
|
|
20
|
+
const GUFI_DIR = path.join(os.homedir(), ".gufi");
|
|
21
|
+
const SERVICE_NAME = "com.gufi.claude";
|
|
22
|
+
const WINDOWS_TASK_NAME = "GufiClaude";
|
|
23
|
+
/**
|
|
24
|
+
* Check if service is already installed
|
|
25
|
+
*/
|
|
26
|
+
function isServiceInstalled() {
|
|
27
|
+
const platform = os.platform();
|
|
28
|
+
if (platform === "darwin") {
|
|
29
|
+
const plistPath = path.join(os.homedir(), "Library/LaunchAgents", `${SERVICE_NAME}.plist`);
|
|
30
|
+
return fs.existsSync(plistPath);
|
|
31
|
+
}
|
|
32
|
+
else if (platform === "linux") {
|
|
33
|
+
const servicePath = path.join(os.homedir(), ".config/systemd/user/gufi-claude.service");
|
|
34
|
+
return fs.existsSync(servicePath);
|
|
35
|
+
}
|
|
36
|
+
else if (platform === "win32") {
|
|
37
|
+
try {
|
|
38
|
+
execSync(`schtasks /query /tn "${WINDOWS_TASK_NAME}"`, { stdio: "ignore" });
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Check if bridge is already running on port
|
|
49
|
+
*/
|
|
50
|
+
function isPortInUse(port) {
|
|
51
|
+
try {
|
|
52
|
+
if (os.platform() === "win32") {
|
|
53
|
+
const result = execSync(`netstat -ano | findstr :${port}`, { encoding: "utf8" });
|
|
54
|
+
return result.includes("LISTENING");
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
const result = execSync(`lsof -i:${port} -t`, { encoding: "utf8" });
|
|
58
|
+
return result.trim().length > 0;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Ensure workspace exists and MCP is configured
|
|
67
|
+
*/
|
|
68
|
+
function ensureWorkspace() {
|
|
69
|
+
// Create workspace if needed
|
|
70
|
+
if (!fs.existsSync(WORKSPACE_DIR)) {
|
|
71
|
+
fs.mkdirSync(WORKSPACE_DIR, { recursive: true });
|
|
72
|
+
}
|
|
73
|
+
// Create .gufi dir if needed
|
|
74
|
+
if (!fs.existsSync(GUFI_DIR)) {
|
|
75
|
+
fs.mkdirSync(GUFI_DIR, { recursive: true });
|
|
76
|
+
}
|
|
77
|
+
// Create CLAUDE.md if needed
|
|
78
|
+
const claudeMdPath = path.join(WORKSPACE_DIR, "CLAUDE.md");
|
|
79
|
+
if (!fs.existsSync(claudeMdPath)) {
|
|
80
|
+
fs.writeFileSync(claudeMdPath, `# Gufi Workspace
|
|
81
|
+
|
|
82
|
+
Este es tu espacio de trabajo para Gufi ERP.
|
|
83
|
+
|
|
84
|
+
## MCP Tools Disponibles
|
|
85
|
+
|
|
86
|
+
Usa las herramientas de Gufi:
|
|
87
|
+
- \`gufi_context\` - Contexto de empresa/módulos
|
|
88
|
+
- \`gufi_companies\` - Ver empresas
|
|
89
|
+
- \`gufi_rows\` - Consultar datos
|
|
90
|
+
- \`gufi_docs\` - Documentación
|
|
91
|
+
|
|
92
|
+
## Quick Start
|
|
93
|
+
|
|
94
|
+
Pregúntame sobre tus empresas, módulos o datos.
|
|
95
|
+
Tengo acceso completo a Gufi via MCP.
|
|
96
|
+
`);
|
|
97
|
+
}
|
|
98
|
+
// Configure MCP in global ~/.claude.json
|
|
99
|
+
if (fs.existsSync(GLOBAL_CLAUDE_JSON)) {
|
|
100
|
+
try {
|
|
101
|
+
const claudeConfig = JSON.parse(fs.readFileSync(GLOBAL_CLAUDE_JSON, "utf8"));
|
|
102
|
+
if (!claudeConfig.projects)
|
|
103
|
+
claudeConfig.projects = {};
|
|
104
|
+
const workspaceProject = claudeConfig.projects[WORKSPACE_DIR];
|
|
105
|
+
if (!workspaceProject?.mcpServers?.gufi) {
|
|
106
|
+
claudeConfig.projects[WORKSPACE_DIR] = {
|
|
107
|
+
...workspaceProject,
|
|
108
|
+
mcpServers: {
|
|
109
|
+
...(workspaceProject?.mcpServers || {}),
|
|
110
|
+
gufi: { type: "stdio", command: "gufi", args: ["mcp"], env: {} },
|
|
111
|
+
},
|
|
112
|
+
hasTrustDialogAccepted: true,
|
|
113
|
+
};
|
|
114
|
+
fs.writeFileSync(GLOBAL_CLAUDE_JSON, JSON.stringify(claudeConfig, null, 2));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch { }
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Find gufi executable path
|
|
122
|
+
*/
|
|
123
|
+
async function findGufiPath() {
|
|
124
|
+
return new Promise((resolve) => {
|
|
125
|
+
const cmd = os.platform() === "win32" ? "where" : "which";
|
|
126
|
+
const proc = spawn(cmd, ["gufi"], { shell: true });
|
|
127
|
+
let output = "";
|
|
128
|
+
proc.stdout.on("data", (data) => { output += data.toString(); });
|
|
129
|
+
proc.on("close", (code) => {
|
|
130
|
+
if (code === 0 && output.trim()) {
|
|
131
|
+
resolve(output.trim().split("\n")[0]); // First result
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
resolve("gufi"); // Fallback
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Install as system service
|
|
141
|
+
*/
|
|
142
|
+
async function installService(gufiPath, port) {
|
|
143
|
+
const platform = os.platform();
|
|
144
|
+
if (platform === "darwin") {
|
|
145
|
+
await installMac(gufiPath, port);
|
|
146
|
+
}
|
|
147
|
+
else if (platform === "linux") {
|
|
148
|
+
await installLinux(gufiPath, port);
|
|
149
|
+
}
|
|
150
|
+
else if (platform === "win32") {
|
|
151
|
+
await installWindows(gufiPath, port);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
async function installMac(gufiPath, port) {
|
|
155
|
+
const plistPath = path.join(os.homedir(), "Library/LaunchAgents", `${SERVICE_NAME}.plist`);
|
|
156
|
+
const plistDir = path.dirname(plistPath);
|
|
157
|
+
if (!fs.existsSync(plistDir)) {
|
|
158
|
+
fs.mkdirSync(plistDir, { recursive: true });
|
|
159
|
+
}
|
|
160
|
+
// Stop existing if running
|
|
161
|
+
try {
|
|
162
|
+
execSync(`launchctl unload "${plistPath}"`, { stdio: "ignore" });
|
|
163
|
+
}
|
|
164
|
+
catch { }
|
|
165
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
166
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
167
|
+
<plist version="1.0">
|
|
168
|
+
<dict>
|
|
169
|
+
<key>Label</key>
|
|
170
|
+
<string>${SERVICE_NAME}</string>
|
|
171
|
+
<key>ProgramArguments</key>
|
|
172
|
+
<array>
|
|
173
|
+
<string>${gufiPath}</string>
|
|
174
|
+
<string>claude</string>
|
|
175
|
+
<string>--service</string>
|
|
176
|
+
<string>--port</string>
|
|
177
|
+
<string>${port}</string>
|
|
178
|
+
</array>
|
|
179
|
+
<key>RunAtLoad</key>
|
|
180
|
+
<true/>
|
|
181
|
+
<key>KeepAlive</key>
|
|
182
|
+
<true/>
|
|
183
|
+
<key>StandardOutPath</key>
|
|
184
|
+
<string>${GUFI_DIR}/claude.log</string>
|
|
185
|
+
<key>StandardErrorPath</key>
|
|
186
|
+
<string>${GUFI_DIR}/claude.error.log</string>
|
|
187
|
+
<key>EnvironmentVariables</key>
|
|
188
|
+
<dict>
|
|
189
|
+
<key>PATH</key>
|
|
190
|
+
<string>/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin:${process.env.PATH}</string>
|
|
191
|
+
</dict>
|
|
192
|
+
</dict>
|
|
193
|
+
</plist>`;
|
|
194
|
+
fs.writeFileSync(plistPath, plist);
|
|
195
|
+
execSync(`launchctl load "${plistPath}"`, { stdio: "ignore" });
|
|
196
|
+
console.log(chalk.green(" ✓ Servicio instalado (arranca con el sistema)"));
|
|
197
|
+
}
|
|
198
|
+
async function installLinux(gufiPath, port) {
|
|
199
|
+
const serviceDir = path.join(os.homedir(), ".config/systemd/user");
|
|
200
|
+
const servicePath = path.join(serviceDir, "gufi-claude.service");
|
|
201
|
+
if (!fs.existsSync(serviceDir)) {
|
|
202
|
+
fs.mkdirSync(serviceDir, { recursive: true });
|
|
203
|
+
}
|
|
204
|
+
const service = `[Unit]
|
|
205
|
+
Description=Gufi Claude Code Bridge
|
|
206
|
+
After=network.target
|
|
207
|
+
|
|
208
|
+
[Service]
|
|
209
|
+
Type=simple
|
|
210
|
+
ExecStart=${gufiPath} claude --service --port ${port}
|
|
211
|
+
Restart=always
|
|
212
|
+
RestartSec=10
|
|
213
|
+
|
|
214
|
+
[Install]
|
|
215
|
+
WantedBy=default.target
|
|
216
|
+
`;
|
|
217
|
+
fs.writeFileSync(servicePath, service);
|
|
218
|
+
execSync("systemctl --user daemon-reload", { stdio: "ignore" });
|
|
219
|
+
execSync("systemctl --user enable gufi-claude", { stdio: "ignore" });
|
|
220
|
+
execSync("systemctl --user start gufi-claude", { stdio: "ignore" });
|
|
221
|
+
console.log(chalk.green(" ✓ Servicio instalado (arranca con el sistema)"));
|
|
222
|
+
}
|
|
223
|
+
async function installWindows(gufiPath, port) {
|
|
224
|
+
// VBS script to run hidden
|
|
225
|
+
const vbsPath = path.join(GUFI_DIR, "gufi-claude.vbs");
|
|
226
|
+
const vbs = `Set WshShell = CreateObject("WScript.Shell")
|
|
227
|
+
WshShell.Run """${gufiPath}"" claude --service --port ${port}", 0, False`;
|
|
228
|
+
fs.writeFileSync(vbsPath, vbs);
|
|
229
|
+
// Delete existing task
|
|
230
|
+
try {
|
|
231
|
+
execSync(`schtasks /delete /tn "${WINDOWS_TASK_NAME}" /f`, { stdio: "ignore" });
|
|
232
|
+
}
|
|
233
|
+
catch { }
|
|
234
|
+
// Create task
|
|
235
|
+
try {
|
|
236
|
+
execSync(`schtasks /create /tn "${WINDOWS_TASK_NAME}" /tr "wscript.exe \\"${vbsPath}\\"" /sc onlogon /rl highest /f`, { stdio: "ignore" });
|
|
237
|
+
// Start now
|
|
238
|
+
spawn("wscript.exe", [vbsPath], { detached: true, stdio: "ignore" }).unref();
|
|
239
|
+
console.log(chalk.green(" ✓ Servicio instalado (arranca con Windows)"));
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
console.log(chalk.yellow(" ⚠️ Ejecuta como administrador para instalar servicio"));
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* 💜 gufi claude - Setup + Install + Run
|
|
247
|
+
*/
|
|
248
|
+
export async function claudeCommand(options) {
|
|
249
|
+
const port = options.port || DEFAULT_PORT;
|
|
250
|
+
// Handle uninstall
|
|
251
|
+
if (options.uninstall) {
|
|
252
|
+
await uninstallService();
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
// If running as service (called by launchd/systemd), just start server
|
|
256
|
+
if (options.service) {
|
|
257
|
+
await startBridgeServer(port);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
// Interactive mode - show UI
|
|
261
|
+
console.log(chalk.magenta(`
|
|
262
|
+
╭──────────────────────────────────────────────────────╮
|
|
263
|
+
│ 🐿️ Gufi Claude Code │
|
|
264
|
+
╰──────────────────────────────────────────────────────╯
|
|
265
|
+
`));
|
|
266
|
+
// Check login
|
|
267
|
+
if (!isLoggedIn()) {
|
|
268
|
+
console.log(chalk.yellow(" ⚠️ No estás logueado. Ejecuta: gufi login\n"));
|
|
269
|
+
}
|
|
270
|
+
// Check Claude Code installed
|
|
271
|
+
const claudeOk = await checkClaudeInstalled();
|
|
272
|
+
if (!claudeOk) {
|
|
273
|
+
console.log(chalk.red(" ✗ Claude Code no encontrado."));
|
|
274
|
+
console.log(chalk.gray(" Instala: npm install -g @anthropic-ai/claude-code\n"));
|
|
275
|
+
process.exit(1);
|
|
276
|
+
}
|
|
277
|
+
console.log(chalk.green(" ✓ Claude Code detectado"));
|
|
278
|
+
// Setup workspace
|
|
279
|
+
ensureWorkspace();
|
|
280
|
+
console.log(chalk.green(" ✓ Workspace configurado"));
|
|
281
|
+
console.log(chalk.gray(` 📁 ${WORKSPACE_DIR}`));
|
|
282
|
+
// Check if already running
|
|
283
|
+
if (isPortInUse(port)) {
|
|
284
|
+
console.log(chalk.green(` ✓ Bridge ya está corriendo en puerto ${port}`));
|
|
285
|
+
console.log(chalk.cyan(`
|
|
286
|
+
¡Todo listo! Abre Gufi en tu navegador.
|
|
287
|
+
El botón de Claude Code conectará automáticamente.
|
|
288
|
+
`));
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
// Install service if not installed
|
|
292
|
+
if (!isServiceInstalled()) {
|
|
293
|
+
console.log(chalk.gray(" 📦 Instalando servicio..."));
|
|
294
|
+
const gufiPath = await findGufiPath();
|
|
295
|
+
await installService(gufiPath, port);
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
console.log(chalk.green(" ✓ Servicio instalado"));
|
|
299
|
+
}
|
|
300
|
+
// Wait a moment for service to start
|
|
301
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
302
|
+
if (isPortInUse(port)) {
|
|
303
|
+
console.log(chalk.green(` ✓ Bridge corriendo en puerto ${port}`));
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
console.log(chalk.yellow(` ⚠️ Iniciando bridge manualmente...`));
|
|
307
|
+
await startBridgeServer(port);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
console.log(chalk.cyan(`
|
|
311
|
+
¡Todo listo!
|
|
312
|
+
|
|
313
|
+
El bridge de Claude Code:
|
|
314
|
+
• Arranca automáticamente con tu sistema
|
|
315
|
+
• Corre en segundo plano en puerto ${port}
|
|
316
|
+
|
|
317
|
+
Abre Gufi → Click en el botón Claude Code
|
|
318
|
+
|
|
319
|
+
Comandos:
|
|
320
|
+
• gufi claude --uninstall - Desinstalar servicio
|
|
321
|
+
• cat ~/.gufi/claude.log - Ver logs
|
|
322
|
+
`));
|
|
323
|
+
}
|
|
324
|
+
async function uninstallService() {
|
|
325
|
+
const platform = os.platform();
|
|
326
|
+
console.log(chalk.magenta(`
|
|
327
|
+
╭──────────────────────────────────────────────────────╮
|
|
328
|
+
│ 🐿️ Desinstalando Gufi Claude │
|
|
329
|
+
╰──────────────────────────────────────────────────────╯
|
|
330
|
+
`));
|
|
331
|
+
if (platform === "darwin") {
|
|
332
|
+
const plistPath = path.join(os.homedir(), "Library/LaunchAgents", `${SERVICE_NAME}.plist`);
|
|
333
|
+
try {
|
|
334
|
+
execSync(`launchctl unload "${plistPath}"`, { stdio: "ignore" });
|
|
335
|
+
}
|
|
336
|
+
catch { }
|
|
337
|
+
if (fs.existsSync(plistPath))
|
|
338
|
+
fs.unlinkSync(plistPath);
|
|
339
|
+
}
|
|
340
|
+
else if (platform === "linux") {
|
|
341
|
+
try {
|
|
342
|
+
execSync("systemctl --user stop gufi-claude", { stdio: "ignore" });
|
|
343
|
+
execSync("systemctl --user disable gufi-claude", { stdio: "ignore" });
|
|
344
|
+
}
|
|
345
|
+
catch { }
|
|
346
|
+
const servicePath = path.join(os.homedir(), ".config/systemd/user/gufi-claude.service");
|
|
347
|
+
if (fs.existsSync(servicePath))
|
|
348
|
+
fs.unlinkSync(servicePath);
|
|
349
|
+
}
|
|
350
|
+
else if (platform === "win32") {
|
|
351
|
+
try {
|
|
352
|
+
execSync(`schtasks /delete /tn "${WINDOWS_TASK_NAME}" /f`, { stdio: "ignore" });
|
|
353
|
+
}
|
|
354
|
+
catch { }
|
|
355
|
+
const vbsPath = path.join(GUFI_DIR, "gufi-claude.vbs");
|
|
356
|
+
if (fs.existsSync(vbsPath))
|
|
357
|
+
fs.unlinkSync(vbsPath);
|
|
358
|
+
}
|
|
359
|
+
console.log(chalk.green(" ✓ Servicio desinstalado\n"));
|
|
360
|
+
}
|
|
361
|
+
async function startBridgeServer(port) {
|
|
362
|
+
// Dynamically import PTY
|
|
363
|
+
let pty;
|
|
364
|
+
try {
|
|
365
|
+
pty = await import("@homebridge/node-pty-prebuilt-multiarch");
|
|
366
|
+
}
|
|
367
|
+
catch {
|
|
368
|
+
console.error("Error loading PTY");
|
|
369
|
+
process.exit(1);
|
|
370
|
+
}
|
|
371
|
+
const wss = new WebSocketServer({ port });
|
|
372
|
+
console.log(`Servidor en ws://localhost:${port}`);
|
|
373
|
+
wss.on("connection", (ws) => {
|
|
374
|
+
const shellCmd = os.platform() === "win32" ? "cmd.exe" : "/bin/zsh";
|
|
375
|
+
const shellArgs = os.platform() === "win32" ? ["/c", "claude"] : ["-l", "-c", "claude"];
|
|
376
|
+
const shell = pty.spawn(shellCmd, shellArgs, {
|
|
377
|
+
name: "xterm-256color",
|
|
378
|
+
cols: 120,
|
|
379
|
+
rows: 40,
|
|
380
|
+
cwd: WORKSPACE_DIR,
|
|
381
|
+
env: { ...process.env, TERM: "xterm-256color" },
|
|
382
|
+
});
|
|
383
|
+
shell.onData((data) => {
|
|
384
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
385
|
+
ws.send(JSON.stringify({ type: "output", content: data }));
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
shell.onExit(({ exitCode }) => {
|
|
389
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
390
|
+
ws.send(JSON.stringify({ type: "exit", code: exitCode }));
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
ws.on("message", (data) => {
|
|
394
|
+
try {
|
|
395
|
+
const msg = JSON.parse(data.toString());
|
|
396
|
+
if (msg.type === "input")
|
|
397
|
+
shell.write(msg.content);
|
|
398
|
+
else if (msg.type === "resize")
|
|
399
|
+
shell.resize(msg.cols || 120, msg.rows || 40);
|
|
400
|
+
}
|
|
401
|
+
catch { }
|
|
402
|
+
});
|
|
403
|
+
ws.on("close", () => shell.kill());
|
|
404
|
+
ws.on("error", () => shell.kill());
|
|
405
|
+
});
|
|
406
|
+
wss.on("error", (err) => {
|
|
407
|
+
if (err.code === "EADDRINUSE") {
|
|
408
|
+
console.error(`Puerto ${port} en uso`);
|
|
409
|
+
process.exit(1);
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
process.on("SIGINT", () => { wss.close(); process.exit(0); });
|
|
413
|
+
process.on("SIGTERM", () => { wss.close(); process.exit(0); });
|
|
414
|
+
}
|
|
415
|
+
async function checkClaudeInstalled() {
|
|
416
|
+
return new Promise((resolve) => {
|
|
417
|
+
const proc = spawn("claude", ["--version"], { stdio: ["ignore", "pipe", "pipe"], shell: true });
|
|
418
|
+
proc.on("close", (code) => resolve(code === 0));
|
|
419
|
+
proc.on("error", () => resolve(false));
|
|
420
|
+
setTimeout(() => { proc.kill(); resolve(false); }, 5000);
|
|
421
|
+
});
|
|
422
|
+
}
|
package/dist/commands/context.js
CHANGED
|
@@ -265,8 +265,7 @@ async function generatePackageContext(packageId, includeConcepts) {
|
|
|
265
265
|
md += `## Comandos\n`;
|
|
266
266
|
md += "```bash\n";
|
|
267
267
|
md += `gufi package ${packageId} # Ver detalles\n`;
|
|
268
|
-
md += `gufi package:
|
|
269
|
-
md += `gufi package:publish ${packageId} # Publicar\n`;
|
|
268
|
+
md += `gufi package:publish ${packageId} # Publicar cambios\n`;
|
|
270
269
|
if (pkg.views?.[0]) {
|
|
271
270
|
md += `gufi view:pull ${pkg.views[0].view_id || pkg.views[0].pk_id} # Descargar primera vista\n`;
|
|
272
271
|
}
|