ozali 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -3
- package/cli/bin/ozali.mjs +16 -4
- package/cli/lib/commands.mjs +598 -28
- package/cli/lib/detect.mjs +9 -1
- package/cli/lib/util.mjs +23 -0
- package/cli/test/smoke.test.mjs +64 -0
- package/docs/deploy-cloud-gcloud.md +149 -0
- package/docs/deploy-cloud-vps.md +174 -0
- package/docs/guia-de-uso.md +6 -1
- package/docs/ideas/postgres-vs-mysql-engram-cloud.md +137 -0
- package/docs/team-history.md +52 -0
- package/package.json +1 -1
- package/skill/references/engram-convention.md +78 -0
package/cli/lib/detect.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// detect.mjs — detección read-only del entorno del proyecto destino.
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { exists, which, gitInfo, nodeMajor, HOME } from "./util.mjs";
|
|
4
|
+
import { exists, which, gitInfo, nodeMajor, HOME, readJSON } from "./util.mjs";
|
|
5
5
|
|
|
6
6
|
/** Variante de fuente de verdad: {found, doc, dir, variant} */
|
|
7
7
|
export function detectSourceOfTruth(cwd) {
|
|
@@ -47,6 +47,13 @@ export function detectEngram() {
|
|
|
47
47
|
return { available: !!bin, bin: bin || null };
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
/** Metadatos compartibles de Engram Cloud del proyecto (sin secretos). */
|
|
51
|
+
export function detectCloud(cwd) {
|
|
52
|
+
const metaPath = path.join(cwd, ".ozali", "cloud.json");
|
|
53
|
+
const meta = readJSON(metaPath);
|
|
54
|
+
return { present: !!meta, path: metaPath, meta: meta || null };
|
|
55
|
+
}
|
|
56
|
+
|
|
50
57
|
/**
|
|
51
58
|
* Capacidades de testing (heurística read-only). Devuelve runner(s), comando y
|
|
52
59
|
* un conteo aproximado de archivos de prueba. NO resuelve strict_tdd (eso lo
|
|
@@ -98,6 +105,7 @@ export function detectAll(cwd) {
|
|
|
98
105
|
agents: detectAgents(cwd),
|
|
99
106
|
skill: detectInstalledSkill(cwd),
|
|
100
107
|
engram: detectEngram(),
|
|
108
|
+
cloud: detectCloud(cwd),
|
|
101
109
|
testing: detectTesting(cwd),
|
|
102
110
|
};
|
|
103
111
|
}
|
package/cli/lib/util.mjs
CHANGED
|
@@ -21,6 +21,21 @@ export function pkgVersion() {
|
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Nombre del asset de release de Engram para un SO/arch/versión dados.
|
|
26
|
+
* Convención oficial: engram_<version>_<os>_<arch>.<ext> (.tar.gz en linux/darwin, .zip en windows).
|
|
27
|
+
* Devuelve null si el SO o la arquitectura no están soportados. Función pura (sin red).
|
|
28
|
+
*/
|
|
29
|
+
export function engramAssetName(platform, arch, version) {
|
|
30
|
+
const osMap = { darwin: "darwin", linux: "linux", win32: "windows" };
|
|
31
|
+
const archMap = { x64: "amd64", arm64: "arm64" };
|
|
32
|
+
const os = osMap[platform];
|
|
33
|
+
const a = archMap[arch];
|
|
34
|
+
if (!os || !a) return null;
|
|
35
|
+
const ext = platform === "win32" ? "zip" : "tar.gz";
|
|
36
|
+
return `engram_${version}_${os}_${a}.${ext}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
24
39
|
// ---- colores (sin deps; respeta NO_COLOR y no-TTY) --------------------------
|
|
25
40
|
const useColor = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
26
41
|
const wrap = (code) => (s) => (useColor ? `\x1b[${code}m${s}\x1b[0m` : String(s));
|
|
@@ -70,6 +85,14 @@ export function writeJSON(p, obj) {
|
|
|
70
85
|
fs.writeFileSync(p, JSON.stringify(obj, null, 2) + "\n");
|
|
71
86
|
}
|
|
72
87
|
|
|
88
|
+
/** Abre una URL en el navegador por defecto. Devuelve true si pudo lanzar el comando. */
|
|
89
|
+
export function openURL(url) {
|
|
90
|
+
if (!url) return false;
|
|
91
|
+
if (process.platform === "darwin") return spawnCmd("open", [url]) === 0;
|
|
92
|
+
if (process.platform === "win32") return spawnCmd("cmd", ["/c", "start", "", url]) === 0;
|
|
93
|
+
return spawnCmd("xdg-open", [url]) === 0;
|
|
94
|
+
}
|
|
95
|
+
|
|
73
96
|
// ---- ejecución de comandos (read-only / git) --------------------------------
|
|
74
97
|
/** Ejecuta un binario y devuelve stdout recortado, o null si falla. */
|
|
75
98
|
export function tryExec(cmd, args = [], opts = {}) {
|
package/cli/test/smoke.test.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import fs from "node:fs";
|
|
|
6
6
|
import os from "node:os";
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { engramAssetName } from "../lib/util.mjs";
|
|
9
10
|
|
|
10
11
|
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
11
12
|
const BIN = path.resolve(HERE, "..", "bin", "ozali.mjs");
|
|
@@ -118,6 +119,20 @@ test("init opencode crea jarvis (AGENTS.md + agente + plugin)", () => {
|
|
|
118
119
|
}
|
|
119
120
|
});
|
|
120
121
|
|
|
122
|
+
test("update agrega ozali-jarvis en un repo que no lo tenía", () => {
|
|
123
|
+
const dir = tmpProject();
|
|
124
|
+
try {
|
|
125
|
+
run(["init", "--yes", "--no-engram", "--no-trust", "--no-jarvis", "--agent", "claude-code", "--scope", "project", "--knowledge-repo", path.join(dir, ".k")], dir);
|
|
126
|
+
assert.ok(!fs.existsSync(path.join(dir, ".claude", "agents", "ozali-jarvis.md")), "precondición: sin jarvis");
|
|
127
|
+
run(["update"], dir);
|
|
128
|
+
assert.ok(fs.existsSync(path.join(dir, ".claude", "agents", "ozali-jarvis.md")), "update crea subagente jarvis");
|
|
129
|
+
assert.match(fs.readFileSync(path.join(dir, "CLAUDE.md"), "utf8"), /ozali-jarvis:start/, "update crea bloque jarvis");
|
|
130
|
+
assert.ok(fs.existsSync(path.join(dir, ".engram", "config.json")), "update fija .engram/config.json");
|
|
131
|
+
} finally {
|
|
132
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
121
136
|
test("init --dry-run no escribe nada", () => {
|
|
122
137
|
const dir = tmpProject();
|
|
123
138
|
try {
|
|
@@ -136,3 +151,52 @@ test("el paquete NO tiene dependencias ni lifecycle scripts (seguridad)", () =>
|
|
|
136
151
|
assert.ok(!(pkg.scripts && pkg.scripts[s]), `sin script de ciclo de vida: ${s}`);
|
|
137
152
|
}
|
|
138
153
|
});
|
|
154
|
+
|
|
155
|
+
test("--help menciona el comando cloud", () => {
|
|
156
|
+
const { stdout } = run(["--help"]);
|
|
157
|
+
assert.match(stdout, /cloud/, "help debe mencionar cloud");
|
|
158
|
+
assert.match(stdout, /--dashboard/, "help debe mencionar --dashboard");
|
|
159
|
+
assert.match(stdout, /--conflicts/, "help debe mencionar --conflicts");
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("cloud status imprime cabecera y no rompe", () => {
|
|
163
|
+
const dir = tmpProject();
|
|
164
|
+
try {
|
|
165
|
+
const { code, stdout } = run(["cloud", "status"], dir, true);
|
|
166
|
+
assert.match(stdout, /ozali cloud status/, "debe imprimir cabecera de cloud status");
|
|
167
|
+
} finally {
|
|
168
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("cloud con subcomando desconocido sale con código 1", () => {
|
|
173
|
+
const dir = tmpProject();
|
|
174
|
+
try {
|
|
175
|
+
const { code, stdout } = run(["cloud", "frobnicate"], dir, true);
|
|
176
|
+
assert.equal(code, 1, "debe salir con código 1");
|
|
177
|
+
assert.match(stdout, /Subcomando desconocido/, "debe indicar subcomando desconocido");
|
|
178
|
+
} finally {
|
|
179
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test(".gitignore del repo destino permite .ozali/cloud.json (commiteable)", () => {
|
|
184
|
+
const dir = tmpProject();
|
|
185
|
+
try {
|
|
186
|
+
run(["init", "--yes", "--no-engram", "--no-trust", "--no-jarvis", "--agent", "claude-code", "--scope", "project", "--knowledge-repo", path.join(dir, ".k")], dir);
|
|
187
|
+
const gi = fs.readFileSync(path.join(dir, ".gitignore"), "utf8");
|
|
188
|
+
assert.match(gi, /\.ozali\/\*/, "debe ignorar .ozali/*");
|
|
189
|
+
assert.match(gi, /!\.ozali\/cloud\.json/, "debe permitir .ozali/cloud.json");
|
|
190
|
+
} finally {
|
|
191
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("engramAssetName respeta la convención de release por SO/arch", () => {
|
|
196
|
+
assert.equal(engramAssetName("linux", "x64", "1.17.0"), "engram_1.17.0_linux_amd64.tar.gz");
|
|
197
|
+
assert.equal(engramAssetName("linux", "arm64", "1.17.0"), "engram_1.17.0_linux_arm64.tar.gz");
|
|
198
|
+
assert.equal(engramAssetName("darwin", "arm64", "1.17.0"), "engram_1.17.0_darwin_arm64.tar.gz");
|
|
199
|
+
assert.equal(engramAssetName("win32", "x64", "1.17.0"), "engram_1.17.0_windows_amd64.zip");
|
|
200
|
+
assert.equal(engramAssetName("linux", "ia32", "1.17.0"), null, "arch no soportada → null");
|
|
201
|
+
assert.equal(engramAssetName("freebsd", "x64", "1.17.0"), null, "SO no soportado → null");
|
|
202
|
+
});
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# Desplegar Engram Cloud en Google Cloud
|
|
2
|
+
|
|
3
|
+
← [README](../README.md)
|
|
4
|
+
|
|
5
|
+
Dos opciones para desplegar el **servidor de Engram Cloud** en Google Cloud Platform, según el
|
|
6
|
+
perfil de carga y mantenimiento que prefieras.
|
|
7
|
+
|
|
8
|
+
## Opción A: Cloud Run + Cloud SQL (serverless, managed)
|
|
9
|
+
|
|
10
|
+
La opción más **hands-off**: Postgres gestionado por Google, Engram en Cloud Run (serverless,
|
|
11
|
+
escala a cero). Ideal si no quieres mantener VMs.
|
|
12
|
+
|
|
13
|
+
### Componentes
|
|
14
|
+
|
|
15
|
+
| Servicio | Qué hace | Coste aprox. |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| Cloud SQL (Postgres 16) | Base de datos gestionada | ~$15-30/mes (db-f1-micro) |
|
|
18
|
+
| Cloud Run | Ejecuta el servidor Engram | $0 por inactivo + por uso |
|
|
19
|
+
| Secret Manager | Guarda el token y DB URL | gratis hasta 6 secretos |
|
|
20
|
+
| Artifact Registry | Imagen Docker de Engram | gratis hasta 0.5 GB |
|
|
21
|
+
|
|
22
|
+
### Pasos
|
|
23
|
+
|
|
24
|
+
1. **Crear instancia de Cloud SQL:**
|
|
25
|
+
```bash
|
|
26
|
+
gcloud sql instances create engram-db \
|
|
27
|
+
--database-version=POSTGRES_16 \
|
|
28
|
+
--tier=db-f1-micro \
|
|
29
|
+
--region=us-central1 \
|
|
30
|
+
--root-password=$(openssl rand -hex 32)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
2. **Crear base de datos y usuario:**
|
|
34
|
+
```bash
|
|
35
|
+
gcloud sql databases create engram --instance=engram-db
|
|
36
|
+
gcloud sql users create engram --instance=engram-db --password=$(openssl rand -hex 16)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
3. **Subir la imagen a Artifact Registry:**
|
|
40
|
+
```bash
|
|
41
|
+
gcloud artifacts repositories create engram --repository-format=docker --location=us-central1
|
|
42
|
+
docker pull ghcr.io/gentleman-programming/engram:latest
|
|
43
|
+
docker tag ghcr.io/gentleman-programming/engram:latest \
|
|
44
|
+
us-central1-docker.pkg.dev/$PROJECT_ID/engram/cloud:latest
|
|
45
|
+
docker push us-central1-docker.pkg.dev/$PROJECT_ID/engram/cloud:latest
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
4. **Crear secretos en Secret Manager:**
|
|
49
|
+
```bash
|
|
50
|
+
echo -n "postgres://engram:...@/engram?cloudsql=/cloudsql/$PROJECT_ID:us-central1:engram-db" \
|
|
51
|
+
| gcloud secrets create ENGRAM_CLOUD_DATABASE_URL --data-file=-
|
|
52
|
+
echo -n "$(openssl rand -hex 32)" \
|
|
53
|
+
| gcloud secrets create ENGRAM_CLOUD_TOKEN --data-file=-
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
5. **Desplegar en Cloud Run:**
|
|
57
|
+
```bash
|
|
58
|
+
gcloud run deploy engram-cloud \
|
|
59
|
+
--image=us-central1-docker.pkg.dev/$PROJECT_ID/engram/cloud:latest \
|
|
60
|
+
--region=us-central1 \
|
|
61
|
+
--port=18080 \
|
|
62
|
+
--no-allow-unauthenticated \
|
|
63
|
+
--add-cloudsql-instances=$PROJECT_ID:us-central1:engram-db \
|
|
64
|
+
--set-secrets="ENGRAM_CLOUD_DATABASE_URL=ENGRAM_CLOUD_DATABASE_URL:latest" \
|
|
65
|
+
--set-secrets="ENGRAM_CLOUD_TOKEN=ENGRAM_CLOUD_TOKEN:latest" \
|
|
66
|
+
--set-env-vars="ENGRAM_CLOUD_DASHBOARD=true"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
6. **IAM — quién puede acceder:**
|
|
70
|
+
- Los devs no acceden a Cloud Run directamente; lo hace el CLI de Engram.
|
|
71
|
+
- Si Cloud Run está `--no-allow-unauthenticated`, configura un endpoint público con
|
|
72
|
+
Cloud Armor (IP allowlist) o un Identity-Aware Proxy (IAP).
|
|
73
|
+
|
|
74
|
+
7. **VPC connector para Cloud SQL:**
|
|
75
|
+
Si Cloud SQL tiene IP privada (recomendado), crea un Serverless VPC connector:
|
|
76
|
+
```bash
|
|
77
|
+
gcloud compute networks vpc-access connectors create engram-conn \
|
|
78
|
+
--network=default --region=us-central1 --range=10.8.0.0/28
|
|
79
|
+
```
|
|
80
|
+
Y en el deploy de Cloud Run añade `--vpc-connector=engram-conn`.
|
|
81
|
+
|
|
82
|
+
### Verificar
|
|
83
|
+
```bash
|
|
84
|
+
gcloud run services describe engram-cloud --region=us-central1 --format='value(status.url)'
|
|
85
|
+
curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" \
|
|
86
|
+
https://engram-cloud-xxxx-uc.a.run.app/health
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Opción B: GCE VM + docker-compose (similar al VPS)
|
|
90
|
+
|
|
91
|
+
Si prefieres control total (equivalente al [despliegue VPS](deploy-cloud-vps.md) pero en GCE):
|
|
92
|
+
|
|
93
|
+
1. **Crear VM:**
|
|
94
|
+
```bash
|
|
95
|
+
gcloud compute instances create engram-cloud \
|
|
96
|
+
--machine-type=e2-small \
|
|
97
|
+
--zone=us-central1-a \
|
|
98
|
+
--image-family=ubuntu-2204-lts \
|
|
99
|
+
--image-project=ubuntu-os-cloud \
|
|
100
|
+
--boot-disk-size=30GB \
|
|
101
|
+
--tags=engram-cloud
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
2. **Firewall (solo HTTP/HTTPS + SSH):**
|
|
105
|
+
```bash
|
|
106
|
+
gcloud compute firewall-rules create engram-allow-http \
|
|
107
|
+
--allow=tcp:80,tcp:443 --target-tags=engram-cloud
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
3. **SSH e instalar Docker:**
|
|
111
|
+
```bash
|
|
112
|
+
gcloud compute ssh engram-cloud --zone=us-central1-a
|
|
113
|
+
# dentro de la VM:
|
|
114
|
+
sudo apt update && sudo apt install -y docker.io docker-compose-v2 nginx certbot python3-certbot-nginx
|
|
115
|
+
sudo usermod -aG docker $USER
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
4. **Desplegar igual que el VPS:**
|
|
119
|
+
- Copia `docker-compose.yml` y `.env` (ver [deploy-cloud-vps.md](deploy-cloud-vps.md))
|
|
120
|
+
- Configura nginx + Let's Encrypt
|
|
121
|
+
- Configura systemd para auto-start
|
|
122
|
+
|
|
123
|
+
### Ventajas de GCE vs VPS genérico
|
|
124
|
+
- **Secret Manager:** los secretos viven en GCP, no en un `.env` en disco
|
|
125
|
+
- **Snapshots programados** del disco para backups
|
|
126
|
+
- **Cloud Logging** centralizado
|
|
127
|
+
- **MIG (Managed Instance Group)** si necesitas HA
|
|
128
|
+
|
|
129
|
+
## Comparación rápida
|
|
130
|
+
|
|
131
|
+
| | Opción A (Cloud Run) | Opción B (GCE VM) |
|
|
132
|
+
|---|---|---|
|
|
133
|
+
| Mantenimiento | Mínimo (serverless) | Medio (gestionas la VM) |
|
|
134
|
+
| Coste en reposo | ~$0 (Cloud Run escala a 0) | ~$13/mes (e2-small) |
|
|
135
|
+
| Cold start | Sí (1-3s tras inactividad) | No |
|
|
136
|
+
| Control | Limitado | Total |
|
|
137
|
+
| Backups | Automáticos (Cloud SQL) | Manuales (snapshots) |
|
|
138
|
+
| Recomendado para | Equipos pequeños/medianos | Equipos con carga constante o compliance estricto |
|
|
139
|
+
|
|
140
|
+
## Conectar los devs
|
|
141
|
+
|
|
142
|
+
Independiente de la opción elegida, cada dev conecta con:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
ozali init
|
|
146
|
+
# → "¿Habilitar Engram Cloud?" → sí
|
|
147
|
+
# → URL: https://engram-cloud-xxxx.a.run.app (A) o https://engram.mi-empresa.com (B)
|
|
148
|
+
# → Token: <ENGRAM_CLOUD_TOKEN>
|
|
149
|
+
```
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# Desplegar Engram Cloud en un VPS
|
|
2
|
+
|
|
3
|
+
← [README](../README.md)
|
|
4
|
+
|
|
5
|
+
Guía para desplegar el **servidor de Engram Cloud** en un VPS genérico (DigitalOcean, Hetzner,
|
|
6
|
+
Linode, AWS EC2, etc.). El servidor centraliza la memoria de equipo: los devs replican a él
|
|
7
|
+
con `ozali sync --cloud` (o automáticamente con autosync).
|
|
8
|
+
|
|
9
|
+
## Requisitos
|
|
10
|
+
|
|
11
|
+
- VPS con Linux (Ubuntu 22.04+ recomendado) y acceso SSH
|
|
12
|
+
- Docker Engine + Docker Compose v2
|
|
13
|
+
- Un dominio (o subdominio) apuntando al VPS
|
|
14
|
+
- Puertos: 80/443 (nginx) abiertos al público; 18080 solo localhost
|
|
15
|
+
|
|
16
|
+
## 1. docker-compose.yml
|
|
17
|
+
|
|
18
|
+
```yaml
|
|
19
|
+
services:
|
|
20
|
+
postgres:
|
|
21
|
+
image: postgres:16-alpine
|
|
22
|
+
restart: unless-stopped
|
|
23
|
+
env_file: .env
|
|
24
|
+
volumes:
|
|
25
|
+
- pgdata:/var/lib/postgresql/data
|
|
26
|
+
healthcheck:
|
|
27
|
+
test: ["CMD-SHELL", "pg_isready -U engram"]
|
|
28
|
+
interval: 5s
|
|
29
|
+
timeout: 3s
|
|
30
|
+
retries: 5
|
|
31
|
+
|
|
32
|
+
cloud:
|
|
33
|
+
image: ghcr.io/gentleman-programming/engram:latest
|
|
34
|
+
restart: unless-stopped
|
|
35
|
+
depends_on:
|
|
36
|
+
postgres:
|
|
37
|
+
condition: service_healthy
|
|
38
|
+
env_file: .env
|
|
39
|
+
ports:
|
|
40
|
+
- "127.0.0.1:18080:18080" # solo localhost; nginx expone al exterior
|
|
41
|
+
|
|
42
|
+
volumes:
|
|
43
|
+
pgdata:
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
> El `cloud` escucha en `127.0.0.1:18080` (no `0.0.0.0`): nginx hace de reverse proxy con TLS
|
|
47
|
+
> y es el único expuesto al público.
|
|
48
|
+
|
|
49
|
+
## 2. .env (secretos fuertes)
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Postgres
|
|
53
|
+
POSTGRES_USER=engram
|
|
54
|
+
POSTGRES_PASSWORD=<genera un secreto fuerte: openssl rand -hex 32>
|
|
55
|
+
POSTGRES_DB=engram
|
|
56
|
+
|
|
57
|
+
# Engram Cloud
|
|
58
|
+
ENGRAM_CLOUD_DATABASE_URL=postgres://engram:<PASSWORD>@postgres:5432/engram?sslmode=disable
|
|
59
|
+
ENGRAM_CLOUD_TOKEN=<genera otro secreto fuerte: openssl rand -hex 32>
|
|
60
|
+
ENGRAM_CLOUD_DASHBOARD=true
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
> **El `ENGRAM_CLOUD_TOKEN` es lo que pide `ozali init` a cada dev.** Compártelo por canal
|
|
64
|
+
> seguro (1Password, Bitwarden, Signal), nunca por email/chat del repo.
|
|
65
|
+
|
|
66
|
+
## 3. Hardening
|
|
67
|
+
|
|
68
|
+
### 3.1 Firewall (ufw)
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
sudo ufw default deny incoming
|
|
72
|
+
sudo ufw default allow outgoing
|
|
73
|
+
sudo ufw allow 22/tcp # SSH (considera cambiar el puerto)
|
|
74
|
+
sudo ufw allow 80/tcp # HTTP (nginx → redirige a HTTPS)
|
|
75
|
+
sudo ufw allow 443/tcp # HTTPS (nginx)
|
|
76
|
+
sudo ufw enable
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 3.2 nginx reverse proxy con SSL (Let's Encrypt)
|
|
80
|
+
|
|
81
|
+
```nginx
|
|
82
|
+
server {
|
|
83
|
+
listen 80;
|
|
84
|
+
server_name engram.mi-empresa.com;
|
|
85
|
+
return 301 https://$host$request_uri;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
server {
|
|
89
|
+
listen 443 ssl http2;
|
|
90
|
+
server_name engram.mi-empresa.com;
|
|
91
|
+
|
|
92
|
+
ssl_certificate /etc/letsencrypt/live/engram.mi-empresa.com/fullchain.pem;
|
|
93
|
+
ssl_certificate_key /etc/letsencrypt/live/engram.mi-empresa.com/privkey.pem;
|
|
94
|
+
ssl_protocols TLSv1.2 TLSv1.3;
|
|
95
|
+
ssl_ciphers HIGH:!aNULL:!MD5;
|
|
96
|
+
|
|
97
|
+
location / {
|
|
98
|
+
proxy_pass http://127.0.0.1:18080;
|
|
99
|
+
proxy_set_header Host $host;
|
|
100
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
101
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
102
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Certbot para el certificado:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
sudo certbot --nginx -d engram.mi-empresa.com
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### 3.3 systemd para auto-start
|
|
114
|
+
|
|
115
|
+
```ini
|
|
116
|
+
# /etc/systemd/system/engram-cloud.service
|
|
117
|
+
[Unit]
|
|
118
|
+
Description=Engram Cloud (Docker Compose)
|
|
119
|
+
After=docker.service
|
|
120
|
+
Requires=docker.service
|
|
121
|
+
|
|
122
|
+
[Service]
|
|
123
|
+
Type=oneshot
|
|
124
|
+
RemainAfterExit=yes
|
|
125
|
+
WorkingDirectory=/opt/engram-cloud
|
|
126
|
+
ExecStart=/usr/bin/docker compose up -d
|
|
127
|
+
ExecStop=/usr/bin/docker compose down
|
|
128
|
+
TimeoutStartSec=0
|
|
129
|
+
|
|
130
|
+
[Install]
|
|
131
|
+
WantedBy=multi-user.target
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
sudo systemctl enable --now engram-cloud
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## 4. Verificar
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
# En el VPS:
|
|
142
|
+
docker compose ps # ambos servicios "healthy"
|
|
143
|
+
curl http://127.0.0.1:18080/health # debería responder OK
|
|
144
|
+
|
|
145
|
+
# Desde fuera:
|
|
146
|
+
curl https://engram.mi-empresa.com/health
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## 5. Conectar los devs
|
|
150
|
+
|
|
151
|
+
Cada dev en su repo:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
ozali init
|
|
155
|
+
# → "¿Habilitar Engram Cloud?" → sí
|
|
156
|
+
# → URL: https://engram.mi-empresa.com
|
|
157
|
+
# → Token: <el ENGRAM_CLOUD_TOKEN del .env>
|
|
158
|
+
# → autosync activo
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Si el servidor requiere upgrade de esquema:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
ozali cloud upgrade # doctor → repair → bootstrap
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Backups
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
# Dump de Postgres (cron diario):
|
|
171
|
+
docker exec engram-cloud-postgres-1 pg_dump -U engram engram | gzip > /backups/engram-$(date +%F).sql.gz
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Mantén los backups fuera del VPS (S3, GCS, almacenamiento externo).
|
package/docs/guia-de-uso.md
CHANGED
|
@@ -82,7 +82,7 @@ Lleva el histórico y la memoria al repo de conocimiento del equipo. Tus compañ
|
|
|
82
82
|
|---|---|
|
|
83
83
|
| `ozali init` | Prepara el proyecto (lo corres una vez por repo). |
|
|
84
84
|
| `ozali doctor` | Revisa que todo esté bien. Es solo lectura, no cambia nada. |
|
|
85
|
-
| `ozali update` | Actualiza
|
|
85
|
+
| `ozali update` | Actualiza skill ozali + ozali-jarvis + permisos (y guía cómo regenerar cdk). |
|
|
86
86
|
| `ozali sync` | Sube el histórico/memoria al repo de equipo. |
|
|
87
87
|
| `ozali sync --import` | Baja lo que el equipo ya guardó. |
|
|
88
88
|
| `ozali audit` | Navega/audita la memoria de Engram (qué se ha hecho). |
|
|
@@ -190,6 +190,11 @@ Es el aviso de seguridad de Claude Code: ignora los permisos del proyecto hasta
|
|
|
190
190
|
Deja que `init` lo marque como confiable, o abre Claude Code en la carpeta y acepta el diálogo una
|
|
191
191
|
vez. Tras eso, el aviso desaparece y los permisos surten efecto.
|
|
192
192
|
|
|
193
|
+
**Actualicé ozali pero no veo ozali-jarvis (o cdk quedó viejo).**
|
|
194
|
+
Corre `ozali update` en el repo: refresca la skill ozali, los permisos y **crea/actualiza
|
|
195
|
+
ozali-jarvis**. La skill `cdk` la regenera tu agente: tras `ozali update`, abre el agente y vuelve a
|
|
196
|
+
correr la skill `ozali` para regenerar `cdk` con lo nuevo (tus docs por hito y el plan se conservan).
|
|
197
|
+
|
|
193
198
|
**¿Funciona en Windows?**
|
|
194
199
|
Sí. Los permisos base incluyen variantes PowerShell, y Engram se instala con `go install` o un
|
|
195
200
|
binario descargable.
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# PostgreSQL 16 vs MySQL 8.x para Engram Cloud
|
|
2
|
+
|
|
3
|
+
← [README](../README.md)
|
|
4
|
+
|
|
5
|
+
Guía comparativa para adaptar el despliegue de Engram Cloud de PostgreSQL a MySQL 8.x.
|
|
6
|
+
|
|
7
|
+
## Cambios necesarios en `docker-compose.yml`
|
|
8
|
+
|
|
9
|
+
### PostgreSQL 16 (actual — ver [deploy-cloud-vps.md](deploy-cloud-vps.md))
|
|
10
|
+
|
|
11
|
+
```yaml
|
|
12
|
+
services:
|
|
13
|
+
postgres:
|
|
14
|
+
image: postgres:16-alpine
|
|
15
|
+
restart: unless-stopped
|
|
16
|
+
env_file: .env
|
|
17
|
+
volumes:
|
|
18
|
+
- pgdata:/var/lib/postgresql/data
|
|
19
|
+
healthcheck:
|
|
20
|
+
test: ["CMD-SHELL", "pg_isready -U engram"]
|
|
21
|
+
interval: 5s
|
|
22
|
+
timeout: 3s
|
|
23
|
+
retries: 5
|
|
24
|
+
|
|
25
|
+
cloud:
|
|
26
|
+
image: ghcr.io/gentleman-programming/engram:latest
|
|
27
|
+
restart: unless-stopped
|
|
28
|
+
depends_on:
|
|
29
|
+
postgres:
|
|
30
|
+
condition: service_healthy
|
|
31
|
+
env_file: .env
|
|
32
|
+
ports:
|
|
33
|
+
- "127.0.0.1:18080:18080"
|
|
34
|
+
|
|
35
|
+
volumes:
|
|
36
|
+
pgdata:
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### MySQL 8.x (propuesto)
|
|
40
|
+
|
|
41
|
+
```yaml
|
|
42
|
+
services:
|
|
43
|
+
mysql:
|
|
44
|
+
image: mysql:8.0
|
|
45
|
+
restart: unless-stopped
|
|
46
|
+
env_file: .env
|
|
47
|
+
volumes:
|
|
48
|
+
- mysqldata:/var/lib/mysql
|
|
49
|
+
healthcheck:
|
|
50
|
+
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
|
|
51
|
+
interval: 5s
|
|
52
|
+
timeout: 3s
|
|
53
|
+
retries: 5
|
|
54
|
+
|
|
55
|
+
cloud:
|
|
56
|
+
image: ghcr.io/gentleman-programming/engram:latest
|
|
57
|
+
restart: unless-stopped
|
|
58
|
+
depends_on:
|
|
59
|
+
mysql:
|
|
60
|
+
condition: service_healthy
|
|
61
|
+
env_file: .env
|
|
62
|
+
ports:
|
|
63
|
+
- "127.0.0.1:18080:18080"
|
|
64
|
+
|
|
65
|
+
volumes:
|
|
66
|
+
mysqldata:
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Cambios en `.env`
|
|
70
|
+
|
|
71
|
+
| Variable | PostgreSQL | MySQL 8.x |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| DB user/pass | `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB` | `MYSQL_ROOT_PASSWORD`, `MYSQL_DATABASE`, `MYSQL_USER`, `MYSQL_PASSWORD` |
|
|
74
|
+
| Connection string | `postgres://engram:pass@postgres:5432/engram?sslmode=disable` | `engram:pass@tcp(mysql:3306)/engram?charset=utf8mb4&parseTime=True&loc=Local` |
|
|
75
|
+
|
|
76
|
+
### `.env` para MySQL 8.x
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# MySQL
|
|
80
|
+
MYSQL_ROOT_PASSWORD=<genera un secreto fuerte: openssl rand -hex 32>
|
|
81
|
+
MYSQL_DATABASE=engram
|
|
82
|
+
MYSQL_USER=engram
|
|
83
|
+
MYSQL_PASSWORD=<genera otro secreto fuerte>
|
|
84
|
+
|
|
85
|
+
# Engram Cloud
|
|
86
|
+
ENGRAM_CLOUD_DATABASE_URL=engram:<PASSWORD>@tcp(mysql:3306)/engram?charset=utf8mb4&parseTime=True&loc=Local
|
|
87
|
+
ENGRAM_CLOUD_TOKEN=<genera otro secreto fuerte: openssl rand -hex 32>
|
|
88
|
+
ENGRAM_CLOUD_DASHBOARD=true
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Tabla comparativa
|
|
92
|
+
|
|
93
|
+
| Aspecto | PostgreSQL 16 | MySQL 8.x |
|
|
94
|
+
|---|---|---|
|
|
95
|
+
| Imagen Docker | `postgres:16-alpine` (~80MB) | `mysql:8.0` (~450MB) |
|
|
96
|
+
| Healthcheck | `pg_isready -U engram` | `mysqladmin ping -h localhost` |
|
|
97
|
+
| Volúmenes | `/var/lib/postgresql/data` | `/var/lib/mysql` |
|
|
98
|
+
| Driver Go | `lib/pq` o `pgx` | `go-sql-driver/mysql` |
|
|
99
|
+
| Connection string | `postgres://` | `user:pass@tcp(host:port)/db?...` |
|
|
100
|
+
| JSON nativo | `JSONB` (índices GIN) | `JSON` + funciones |
|
|
101
|
+
| Arrays nativos | Sí (`text[]`, `int[]`) | No (usa tablas relacionales) |
|
|
102
|
+
| Full-text search | `tsvector`/`tsquery` | `FULLTEXT` indexes |
|
|
103
|
+
| Concurrency | MVCC sin locks de lectura | MVCC con read locks en algunos casos |
|
|
104
|
+
| Backup | `pg_dump` | `mysqldump` |
|
|
105
|
+
|
|
106
|
+
## Bloqueador: ¿Engram soporta MySQL?
|
|
107
|
+
|
|
108
|
+
Depende del código fuente de [github.com/Gentleman-Programming/engram](https://github.com/Gentleman-Programming/engram):
|
|
109
|
+
|
|
110
|
+
- **Si usa GORM**: con cambiar el driver (`postgres` → `mysql`) y el DSN, funcionaría.
|
|
111
|
+
- **Si usa SQL nativo con funciones Postgres** (`JSONB`, `NOW()`, `ARRAY`, `ILIKE`, `RETURNING`): requiere tocar el código de Engram.
|
|
112
|
+
|
|
113
|
+
### Para verificar rápido
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
# 1. Desplegar con MySQL (docker-compose arriba)
|
|
117
|
+
docker compose up -d
|
|
118
|
+
|
|
119
|
+
# 2. Ver logs del contenedor cloud:
|
|
120
|
+
docker logs engram-cloud-cloud-1
|
|
121
|
+
# Si falla con "unsupported driver" o "syntax error", no hay soporte MySQL.
|
|
122
|
+
|
|
123
|
+
# 3. Si conecta, verificar health:
|
|
124
|
+
curl http://127.0.0.1:18080/health
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Backup con MySQL
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
# Dump de MySQL (cron diario):
|
|
131
|
+
docker exec engram-cloud-mysql-1 mysqldump -u engram -p<PASSWORD> engram | gzip > /backups/engram-$(date +%F).sql.gz
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Recomendación
|
|
135
|
+
|
|
136
|
+
- **Mantener PostgreSQL** si no hay requisito específico de MySQL. Es lo que Engram espera.
|
|
137
|
+
- **Migrar a MySQL** solo si el equipo ya tiene infra MySQL operativa (DBA, backups, monitoreo) y se confirma que Engram tiene driver MySQL compilado.
|
package/docs/team-history.md
CHANGED
|
@@ -64,3 +64,55 @@ histórico apunta a un commit exacto.
|
|
|
64
64
|
|
|
65
65
|
El contrato concreto de qué se espeja a Engram, cuándo, y el algoritmo de `ozali sync` está en
|
|
66
66
|
[skill/references/engram-convention.md](../skill/references/engram-convention.md) (§3 y §6).
|
|
67
|
+
|
|
68
|
+
## Engram Cloud: réplica en tiempo real (opt-in)
|
|
69
|
+
|
|
70
|
+
Además del git-sync, ozali soporta **Engram Cloud** — un servidor central que replica la memoria
|
|
71
|
+
del equipo **en tiempo real** (autosync), sin necesidad de commit/push manual.
|
|
72
|
+
|
|
73
|
+
### Modelo: cloud-first, git-sync como backup
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
dev A guarda memoria → autosync → cloud → dev B (autosync recibe)
|
|
77
|
+
↕
|
|
78
|
+
git-sync (backup/secondary, repos de conocimiento)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
- **Cloud-first**: la réplica es automática e invisible. El repo de conocimiento sigue siendo
|
|
82
|
+
el backup offline y el canal para docs (que no van a cloud).
|
|
83
|
+
- **Sin cloud**: todo funciona igual con git-sync manual (`ozali sync` + `--push`/`--import`).
|
|
84
|
+
|
|
85
|
+
### Onboarding de equipo (un dev nuevo en 1 paso)
|
|
86
|
+
|
|
87
|
+
Cuando un dev nuevo hace `ozali init` en un repo que ya tiene `.ozali/cloud.json` (commiteable,
|
|
88
|
+
sin secretos), el CLI detecta la cloud del equipo y ofrece conectarse automáticamente:
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
$ ozali init
|
|
92
|
+
✓ Engram Cloud del equipo detectado (servidor: https://engram.mi-empresa.com)
|
|
93
|
+
→ Proyecto: "ozali"
|
|
94
|
+
→ ¿Conectarte a la memoria del equipo? [sí/no]
|
|
95
|
+
→ token: ****
|
|
96
|
+
→ Recibiendo memoria del equipo…
|
|
97
|
+
→ autosync activo (invisible)
|
|
98
|
+
→ Listo.
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Despliegue del servidor
|
|
102
|
+
|
|
103
|
+
Ver [docs/deploy-cloud-vps.md](deploy-cloud-vps.md) (VPS genérico con docker-compose) y
|
|
104
|
+
[docs/deploy-cloud-gcloud.md](deploy-cloud-gcloud.md) (Google Cloud: Cloud Run o GCE VM).
|
|
105
|
+
|
|
106
|
+
### Comandos cloud
|
|
107
|
+
|
|
108
|
+
| Comando | Qué hace |
|
|
109
|
+
|---|---|
|
|
110
|
+
| `ozali sync --cloud` | Push manual a cloud |
|
|
111
|
+
| `ozali sync --cloud --import` | Pull desde cloud (onboarding inverso) |
|
|
112
|
+
| `ozali cloud status` | Estado + último sync + upgrade |
|
|
113
|
+
| `ozali cloud upgrade` | doctor → repair → bootstrap |
|
|
114
|
+
| `ozali cloud dashboard` | Abre el dashboard web |
|
|
115
|
+
| `ozali cloud config` | Re-configura servidor/token |
|
|
116
|
+
|
|
117
|
+
Detalle del contrato de memoria cloud (scope, idioma, conflictos, troubleshooting) en
|
|
118
|
+
[skill/references/engram-convention.md](../skill/references/engram-convention.md) §8.
|