aladdin-ai 1.2.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +131 -16
- package/commands/doctor.js +172 -0
- package/commands/init.js +208 -0
- package/commands/lifecycle.js +50 -0
- package/index.js +66 -132
- package/lib/compose.js +17 -0
- package/lib/prereq.js +54 -0
- package/lib/project.js +31 -0
- package/package.json +23 -9
- package/templates/docker-compose.yml +53 -0
package/README.md
CHANGED
|
@@ -1,25 +1,140 @@
|
|
|
1
|
-
#
|
|
1
|
+
# aladdin-ai
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Self-hosted AI workspace, one command to install and run.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
```bash
|
|
6
|
+
npx aladdin-ai
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
This drops a `docker-compose.yml` + a `.env` (filled with
|
|
10
|
+
cryptographically-secure `JWT_SECRET`, `FERNET_KEY`, `POSTGRES_PASSWORD`)
|
|
11
|
+
into a new directory, pulls the prebuilt images from GHCR, and starts
|
|
12
|
+
the stack. Open [http://localhost:3000](http://localhost:3000) when it
|
|
13
|
+
finishes.
|
|
14
|
+
|
|
15
|
+
No `git`, no source code on your machine, no Python or Node toolchain.
|
|
16
|
+
Just Docker.
|
|
17
|
+
|
|
18
|
+
## Install modes
|
|
6
19
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
20
|
+
| Mode | Command | What you get | Who it's for |
|
|
21
|
+
|---|---|---|---|
|
|
22
|
+
| Image (default) | `npx aladdin-ai` | compose + `.env` (~2 files), images pulled from GHCR | Anyone who just wants to use AladdinAI |
|
|
23
|
+
| Source | `npx aladdin-ai init --source` | Full git clone, images built locally | Contributors who want to modify the code |
|
|
24
|
+
|
|
25
|
+
Updates in image mode are one command: `npx aladdin-ai update`.
|
|
26
|
+
|
|
27
|
+
## Commands
|
|
10
28
|
|
|
11
29
|
```bash
|
|
12
|
-
npx aladdin-ai
|
|
30
|
+
npx aladdin-ai # alias for `init` (image mode)
|
|
31
|
+
npx aladdin-ai init # bootstrap a new project here
|
|
32
|
+
npx aladdin-ai init --source # bootstrap from git clone (for contributors)
|
|
33
|
+
|
|
34
|
+
npx aladdin-ai up # start services (docker compose up -d)
|
|
35
|
+
npx aladdin-ai up --build # rebuild images first (source mode)
|
|
36
|
+
npx aladdin-ai down # stop services
|
|
37
|
+
npx aladdin-ai down -v # also wipe volumes (DESTROYS data)
|
|
38
|
+
npx aladdin-ai restart # restart everything
|
|
39
|
+
npx aladdin-ai restart backend # restart one service
|
|
40
|
+
|
|
41
|
+
npx aladdin-ai logs # tail all service logs
|
|
42
|
+
npx aladdin-ai logs backend -f # follow one service
|
|
43
|
+
|
|
44
|
+
npx aladdin-ai update # pull latest images and recreate services
|
|
45
|
+
npx aladdin-ai doctor # diagnose setup issues
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
All commands except `init` find the nearest `docker-compose.yml` above
|
|
49
|
+
the current working directory, so you can run them from anywhere inside
|
|
50
|
+
a project.
|
|
51
|
+
|
|
52
|
+
## `doctor`
|
|
53
|
+
|
|
54
|
+
Quick health check when something feels off:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
$ npx aladdin-ai doctor
|
|
58
|
+
|
|
59
|
+
Tooling
|
|
60
|
+
✓ git installed
|
|
61
|
+
✓ docker + compose available, daemon running
|
|
62
|
+
|
|
63
|
+
Project
|
|
64
|
+
✓ project root: /home/me/aladdin-ai
|
|
65
|
+
✓ .env has required keys
|
|
66
|
+
|
|
67
|
+
Ports
|
|
68
|
+
✓ port 3000 (frontend) listening
|
|
69
|
+
✓ port 8000 (backend) listening
|
|
70
|
+
✓ port 5432 (postgres) listening
|
|
71
|
+
|
|
72
|
+
Services
|
|
73
|
+
✓ backend: running
|
|
74
|
+
✓ frontend: running
|
|
75
|
+
✓ postgres: running
|
|
76
|
+
|
|
77
|
+
Reachability
|
|
78
|
+
✓ backend responding on http://localhost:8000 (HTTP 200)
|
|
79
|
+
✓ frontend responding on http://localhost:3000 (HTTP 200)
|
|
80
|
+
|
|
81
|
+
All critical checks passed.
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
It also flags `.env` files that still hold the example placeholders, so
|
|
85
|
+
you don't accidentally expose an instance with `JWT_SECRET=change-me`.
|
|
86
|
+
|
|
87
|
+
## Pinning to a specific version
|
|
88
|
+
|
|
89
|
+
By default the compose file pulls the `:latest` tag. To pin a release,
|
|
90
|
+
set `ALADDINAI_VERSION` in your `.env`:
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
ALADDINAI_VERSION=1.0.0
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Then `npx aladdin-ai update` will only ever pull that version.
|
|
97
|
+
|
|
98
|
+
## Requirements
|
|
99
|
+
|
|
100
|
+
- Node.js 18+
|
|
101
|
+
- Docker 20+ with the Compose plugin (`docker compose version` works)
|
|
102
|
+
- `git` — only for `init --source`
|
|
103
|
+
|
|
104
|
+
## What is AladdinAI?
|
|
105
|
+
|
|
106
|
+
A self-hosted AI workspace: agents with private + shared memory, CRM,
|
|
107
|
+
multi-channel messaging (Telegram / WhatsApp / SMS / email), cron
|
|
108
|
+
triggers, and provider-agnostic LLM routing. See the
|
|
109
|
+
[main repo](https://github.com/aliyevaladddin/AladdinAI) for the full
|
|
110
|
+
story.
|
|
111
|
+
|
|
112
|
+
## Releasing
|
|
113
|
+
|
|
114
|
+
**CLI** (this package on npm) — publish via GitHub Actions
|
|
115
|
+
(`.github/workflows/cli-publish.yml`):
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
cd cli
|
|
119
|
+
npm version patch # or minor / major
|
|
120
|
+
cd ..
|
|
121
|
+
git commit -am "cli: bump to $(node -p "require('./cli/package.json').version")"
|
|
122
|
+
git tag "cli-v$(node -p "require('./cli/package.json').version")"
|
|
123
|
+
git push origin main --tags
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Backend + frontend images** — publish to GHCR via
|
|
127
|
+
`.github/workflows/docker-publish.yml`:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
git tag v1.0.0
|
|
131
|
+
git push origin v1.0.0
|
|
13
132
|
```
|
|
14
133
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
- **Desktop App**: Native installers for Mac, Windows, and Linux.
|
|
19
|
-
- **Full CRM**: Complete deals and contacts management system.
|
|
134
|
+
The workflow builds both images for linux/amd64 + linux/arm64 and pushes
|
|
135
|
+
them as `ghcr.io/aliyevaladddin/aladdinai-backend:1.0.0`,
|
|
136
|
+
`ghcr.io/aliyevaladddin/aladdinai-frontend:1.0.0`, and `:latest`.
|
|
20
137
|
|
|
21
|
-
##
|
|
22
|
-
Protected by the **Restricted Correlation Framework (RCF v2.0.3)**.
|
|
138
|
+
## License
|
|
23
139
|
|
|
24
|
-
|
|
25
|
-
Developed by [Aladdin Aliyev](https://github.com/aliyevaladddin)
|
|
140
|
+
Apache-2.0
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// `aladdin-ai doctor` — diagnose common setup issues.
|
|
2
|
+
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import fs from 'fs-extra';
|
|
5
|
+
import net from 'node:net';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { execa } from 'execa';
|
|
8
|
+
import { findProjectRoot } from '../lib/project.js';
|
|
9
|
+
import { checkDocker, checkGit } from '../lib/prereq.js';
|
|
10
|
+
|
|
11
|
+
const ok = (msg) => console.log(chalk.green(' ✓ ') + msg);
|
|
12
|
+
const bad = (msg, hint) => {
|
|
13
|
+
console.log(chalk.red(' ✗ ') + msg);
|
|
14
|
+
if (hint) console.log(chalk.dim(' → ' + hint));
|
|
15
|
+
};
|
|
16
|
+
const warn = (msg, hint) => {
|
|
17
|
+
console.log(chalk.yellow(' ! ') + msg);
|
|
18
|
+
if (hint) console.log(chalk.dim(' → ' + hint));
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Probe whether anything is listening on a port (true = something is there).
|
|
22
|
+
// We try to connect rather than bind because Docker port mappings show up
|
|
23
|
+
// from the host as connectable TCP endpoints.
|
|
24
|
+
async function isPortInUse(port) {
|
|
25
|
+
return new Promise((resolve) => {
|
|
26
|
+
const sock = new net.Socket();
|
|
27
|
+
const done = (val) => { sock.destroy(); resolve(val); };
|
|
28
|
+
sock.setTimeout(500);
|
|
29
|
+
sock.once('connect', () => done(true));
|
|
30
|
+
sock.once('timeout', () => done(false));
|
|
31
|
+
sock.once('error', () => done(false));
|
|
32
|
+
sock.connect(port, '127.0.0.1');
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function fetchWithTimeout(url, ms = 3000) {
|
|
37
|
+
const ctl = new AbortController();
|
|
38
|
+
const t = setTimeout(() => ctl.abort(), ms);
|
|
39
|
+
try {
|
|
40
|
+
const r = await fetch(url, { signal: ctl.signal });
|
|
41
|
+
return r;
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
} finally {
|
|
45
|
+
clearTimeout(t);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function doctorCommand() {
|
|
50
|
+
let failures = 0;
|
|
51
|
+
console.log(chalk.cyan('\nAladdinAI doctor\n'));
|
|
52
|
+
|
|
53
|
+
// ── Tooling ──────────────────────────────────────────────────────
|
|
54
|
+
console.log(chalk.bold('Tooling'));
|
|
55
|
+
const git = await checkGit();
|
|
56
|
+
git.ok ? ok('git installed') : (bad('git missing', 'install: https://git-scm.com/downloads'), failures++);
|
|
57
|
+
|
|
58
|
+
const docker = await checkDocker();
|
|
59
|
+
if (docker.ok) {
|
|
60
|
+
ok('docker + compose available, daemon running');
|
|
61
|
+
} else {
|
|
62
|
+
bad('docker not ready', docker.message);
|
|
63
|
+
failures++;
|
|
64
|
+
// Without docker we can't go further on services
|
|
65
|
+
console.log(chalk.red('\nFix Docker first; re-run `npx aladdin-ai doctor`.'));
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ── Project ──────────────────────────────────────────────────────
|
|
70
|
+
console.log(chalk.bold('\nProject'));
|
|
71
|
+
const root = findProjectRoot();
|
|
72
|
+
if (!root) {
|
|
73
|
+
warn('not inside an AladdinAI project',
|
|
74
|
+
'run `npx aladdin-ai init` or cd into the project directory');
|
|
75
|
+
console.log(chalk.dim('\nSkipping project-level checks.\n'));
|
|
76
|
+
process.exit(failures > 0 ? 1 : 0);
|
|
77
|
+
}
|
|
78
|
+
ok(`project root: ${root}`);
|
|
79
|
+
|
|
80
|
+
const envPath = path.join(root, '.env');
|
|
81
|
+
if (!fs.existsSync(envPath)) {
|
|
82
|
+
bad('.env missing', 'copy .env.example to .env and fill in secrets');
|
|
83
|
+
failures++;
|
|
84
|
+
} else {
|
|
85
|
+
const env = await fs.readFile(envPath, 'utf8');
|
|
86
|
+
const need = ['JWT_SECRET', 'FERNET_KEY', 'POSTGRES_PASSWORD'];
|
|
87
|
+
const placeholders = [
|
|
88
|
+
['JWT_SECRET', 'change-me-in-production'],
|
|
89
|
+
['POSTGRES_PASSWORD', 'replace_with_secure_password'],
|
|
90
|
+
['FERNET_KEY', 'your-secret-fernet-key-here'],
|
|
91
|
+
];
|
|
92
|
+
let envOk = true;
|
|
93
|
+
for (const k of need) {
|
|
94
|
+
if (!new RegExp(`^${k}=\\S`, 'm').test(env)) {
|
|
95
|
+
bad(`.env missing ${k}`);
|
|
96
|
+
envOk = false;
|
|
97
|
+
failures++;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
for (const [k, bad_val] of placeholders) {
|
|
101
|
+
if (new RegExp(`^${k}=${bad_val.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')}\\s*$`, 'm').test(env)) {
|
|
102
|
+
warn(`.env: ${k} still uses the default placeholder`,
|
|
103
|
+
'generate a real value before exposing this instance');
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (envOk) ok('.env has required keys');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ── Ports ────────────────────────────────────────────────────────
|
|
110
|
+
console.log(chalk.bold('\nPorts'));
|
|
111
|
+
for (const [port, label] of [[3000, 'frontend'], [8000, 'backend'], [5432, 'postgres']]) {
|
|
112
|
+
const inUse = await isPortInUse(port);
|
|
113
|
+
if (inUse) {
|
|
114
|
+
ok(`port ${port} (${label}) listening`);
|
|
115
|
+
} else {
|
|
116
|
+
warn(`port ${port} (${label}) nothing listening`,
|
|
117
|
+
'service may not be running — try `npx aladdin-ai up`');
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ── Compose services ─────────────────────────────────────────────
|
|
122
|
+
console.log(chalk.bold('\nServices'));
|
|
123
|
+
const [bin, prefix] = docker.composeCmd;
|
|
124
|
+
const psRes = await execa(bin, [...prefix, 'ps', '--format', 'json'], {
|
|
125
|
+
cwd: root,
|
|
126
|
+
reject: false,
|
|
127
|
+
});
|
|
128
|
+
if (psRes.exitCode !== 0) {
|
|
129
|
+
bad('docker compose ps failed', psRes.stderr?.slice(0, 200));
|
|
130
|
+
failures++;
|
|
131
|
+
} else {
|
|
132
|
+
const lines = psRes.stdout.split('\n').filter(Boolean);
|
|
133
|
+
if (lines.length === 0) {
|
|
134
|
+
warn('no compose services running', 'run `npx aladdin-ai up`');
|
|
135
|
+
} else {
|
|
136
|
+
for (const line of lines) {
|
|
137
|
+
try {
|
|
138
|
+
const svc = JSON.parse(line);
|
|
139
|
+
const name = svc.Service || svc.Name;
|
|
140
|
+
const state = svc.State || svc.Status;
|
|
141
|
+
if (state === 'running' || /Up/.test(state || '')) ok(`${name}: ${state}`);
|
|
142
|
+
else { bad(`${name}: ${state}`); failures++; }
|
|
143
|
+
} catch {
|
|
144
|
+
// ignore malformed line
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ── HTTP reachability ────────────────────────────────────────────
|
|
151
|
+
console.log(chalk.bold('\nReachability'));
|
|
152
|
+
const beRes = await fetchWithTimeout('http://localhost:8000/');
|
|
153
|
+
if (beRes && beRes.status < 500) ok(`backend responding on http://localhost:8000 (HTTP ${beRes.status})`);
|
|
154
|
+
else { warn('backend not responding on :8000', 'check `npx aladdin-ai logs backend`'); }
|
|
155
|
+
|
|
156
|
+
const feRes = await fetchWithTimeout('http://localhost:3000');
|
|
157
|
+
if (feRes && feRes.status < 500) {
|
|
158
|
+
ok(`frontend responding on http://localhost:3000 (HTTP ${feRes.status})`);
|
|
159
|
+
} else {
|
|
160
|
+
warn('frontend not responding on :3000', 'check `npx aladdin-ai logs frontend`');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ── Summary ──────────────────────────────────────────────────────
|
|
164
|
+
console.log();
|
|
165
|
+
if (failures === 0) {
|
|
166
|
+
console.log(chalk.green('All critical checks passed.\n'));
|
|
167
|
+
process.exit(0);
|
|
168
|
+
} else {
|
|
169
|
+
console.log(chalk.red(`${failures} issue${failures === 1 ? '' : 's'} found.\n`));
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
}
|
package/commands/init.js
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
// `aladdin-ai init` — bootstrap a new AladdinAI deployment.
|
|
2
|
+
//
|
|
3
|
+
// Default path (image-based, what 95% of users want):
|
|
4
|
+
// 1. Create target dir
|
|
5
|
+
// 2. Copy bundled docker-compose.yml + .env template
|
|
6
|
+
// 3. Fill .env with secure random secrets
|
|
7
|
+
// 4. `docker compose pull` + `docker compose up -d`
|
|
8
|
+
// No git, no source code on the user's machine. Updates via `docker pull`.
|
|
9
|
+
//
|
|
10
|
+
// Source path (--source flag, for contributors):
|
|
11
|
+
// git clone the repo + build images locally with docker-compose.dev.yml.
|
|
12
|
+
|
|
13
|
+
import chalk from 'chalk';
|
|
14
|
+
import inquirer from 'inquirer';
|
|
15
|
+
import ora from 'ora';
|
|
16
|
+
import { execa } from 'execa';
|
|
17
|
+
import fs from 'fs-extra';
|
|
18
|
+
import path from 'path';
|
|
19
|
+
import crypto from 'crypto';
|
|
20
|
+
import { fileURLToPath } from 'node:url';
|
|
21
|
+
import { runPrereqChecks } from '../lib/prereq.js';
|
|
22
|
+
import { compose } from '../lib/compose.js';
|
|
23
|
+
|
|
24
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
25
|
+
const TEMPLATES_DIR = path.resolve(__dirname, '..', 'templates');
|
|
26
|
+
const REPO_URL = 'https://github.com/aliyevaladddin/AladdinAI.git';
|
|
27
|
+
|
|
28
|
+
function genSecret(bytes = 32) {
|
|
29
|
+
return crypto.randomBytes(bytes).toString('hex');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function genFernetKey() {
|
|
33
|
+
return crypto.randomBytes(32).toString('base64url') + '=';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function fillEnv(text, vars) {
|
|
37
|
+
let out = text;
|
|
38
|
+
for (const [k, v] of Object.entries(vars)) {
|
|
39
|
+
out = out.replace(new RegExp(`^${k}=__GENERATED__$`, 'm'), `${k}=${v}`);
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function writeEnv(targetDir) {
|
|
45
|
+
const tmplPath = path.join(TEMPLATES_DIR, '.env.template');
|
|
46
|
+
const tmpl = await fs.readFile(tmplPath, 'utf8');
|
|
47
|
+
const filled = fillEnv(tmpl, {
|
|
48
|
+
POSTGRES_PASSWORD: genSecret(16),
|
|
49
|
+
JWT_SECRET: genSecret(32),
|
|
50
|
+
FERNET_KEY: genFernetKey(),
|
|
51
|
+
});
|
|
52
|
+
await fs.writeFile(path.join(targetDir, '.env'), filled);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ── Image-based install (default) ─────────────────────────────────────
|
|
56
|
+
async function installFromImages(targetDir, { start }) {
|
|
57
|
+
await fs.ensureDir(targetDir);
|
|
58
|
+
|
|
59
|
+
// 1. Copy compose template
|
|
60
|
+
const composeSpin = ora('Writing docker-compose.yml').start();
|
|
61
|
+
try {
|
|
62
|
+
await fs.copy(
|
|
63
|
+
path.join(TEMPLATES_DIR, 'docker-compose.yml'),
|
|
64
|
+
path.join(targetDir, 'docker-compose.yml'),
|
|
65
|
+
);
|
|
66
|
+
composeSpin.succeed('docker-compose.yml written');
|
|
67
|
+
} catch (err) {
|
|
68
|
+
composeSpin.fail('Failed to write docker-compose.yml');
|
|
69
|
+
throw err;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 2. Generate .env
|
|
73
|
+
const envSpin = ora('Generating .env with secure secrets').start();
|
|
74
|
+
try {
|
|
75
|
+
await writeEnv(targetDir);
|
|
76
|
+
envSpin.succeed('.env created');
|
|
77
|
+
} catch (err) {
|
|
78
|
+
envSpin.fail('Failed to write .env');
|
|
79
|
+
throw err;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!start) return;
|
|
83
|
+
|
|
84
|
+
// 3. Pull images
|
|
85
|
+
console.log(chalk.cyan('\nPulling images from ghcr.io (first run can take a few minutes)…\n'));
|
|
86
|
+
const pullRes = await compose(['pull'], { cwd: targetDir });
|
|
87
|
+
if (pullRes.exitCode !== 0) {
|
|
88
|
+
console.error(chalk.red('\ndocker compose pull failed. Images may not be published yet.'));
|
|
89
|
+
console.error(chalk.dim('You can build from source instead: npx aladdin-ai init --source\n'));
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 4. Start
|
|
94
|
+
console.log(chalk.cyan('\nStarting services…\n'));
|
|
95
|
+
const upRes = await compose(['up', '-d'], { cwd: targetDir });
|
|
96
|
+
if (upRes.exitCode !== 0) {
|
|
97
|
+
console.error(chalk.red('\ndocker compose up failed. Inspect logs with `npx aladdin-ai logs`.'));
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ── Source-based install (--source flag, for contributors) ────────────
|
|
103
|
+
async function installFromSource(targetDir, { start }) {
|
|
104
|
+
const cloneSpin = ora('Cloning repository').start();
|
|
105
|
+
try {
|
|
106
|
+
await execa('git', ['clone', '--depth', '1', REPO_URL, targetDir]);
|
|
107
|
+
await fs.remove(path.join(targetDir, '.git'));
|
|
108
|
+
cloneSpin.succeed('Repository cloned');
|
|
109
|
+
} catch (err) {
|
|
110
|
+
cloneSpin.fail('git clone failed');
|
|
111
|
+
throw err;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const envSpin = ora('Generating .env with secure secrets').start();
|
|
115
|
+
try {
|
|
116
|
+
const tmpl = await fs.readFile(path.join(targetDir, '.env.example'), 'utf8');
|
|
117
|
+
const dbPassword = genSecret(16);
|
|
118
|
+
const filled = tmpl
|
|
119
|
+
.replace(/^POSTGRES_PASSWORD=.*$/m, `POSTGRES_PASSWORD=${dbPassword}`)
|
|
120
|
+
.replace(/^JWT_SECRET=.*$/m, `JWT_SECRET=${genSecret(32)}`)
|
|
121
|
+
.replace(/^FERNET_KEY=.*$/m, `FERNET_KEY=${genFernetKey()}`)
|
|
122
|
+
.replace(
|
|
123
|
+
/^# DATABASE_URL=.*$/m,
|
|
124
|
+
`DATABASE_URL=postgresql+asyncpg://aladdin_user:${dbPassword}@postgres:5432/aladdinai`,
|
|
125
|
+
);
|
|
126
|
+
await fs.writeFile(path.join(targetDir, '.env'), filled);
|
|
127
|
+
envSpin.succeed('.env created');
|
|
128
|
+
} catch (err) {
|
|
129
|
+
envSpin.fail('Failed to create .env');
|
|
130
|
+
throw err;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (!start) return;
|
|
134
|
+
|
|
135
|
+
console.log(chalk.cyan('\nBuilding images locally and starting…\n'));
|
|
136
|
+
const r = await compose(['up', '-d', '--build'], { cwd: targetDir });
|
|
137
|
+
if (r.exitCode !== 0) {
|
|
138
|
+
console.error(chalk.red('\ndocker compose up failed. Inspect logs with `npx aladdin-ai logs`.'));
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ── Entry point ───────────────────────────────────────────────────────
|
|
144
|
+
export async function initCommand(opts) {
|
|
145
|
+
console.log(chalk.cyan('\nAladdinAI'));
|
|
146
|
+
console.log(chalk.dim('Self-hosted AI workspace — agents, memory, CRM, channels.\n'));
|
|
147
|
+
|
|
148
|
+
// Source mode needs git as well; image mode only needs docker.
|
|
149
|
+
await runPrereqChecks({ requireGit: !!opts.source });
|
|
150
|
+
|
|
151
|
+
const defaults = {
|
|
152
|
+
projectName: opts.name || 'aladdin-ai',
|
|
153
|
+
start: !opts.skipStart,
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const answers = opts.yes
|
|
157
|
+
? defaults
|
|
158
|
+
: await inquirer.prompt([
|
|
159
|
+
{
|
|
160
|
+
type: 'input',
|
|
161
|
+
name: 'projectName',
|
|
162
|
+
message: 'Project directory:',
|
|
163
|
+
default: defaults.projectName,
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
type: 'confirm',
|
|
167
|
+
name: 'start',
|
|
168
|
+
message: opts.source
|
|
169
|
+
? 'Build images and start services after setup?'
|
|
170
|
+
: 'Pull images and start services after setup?',
|
|
171
|
+
default: defaults.start,
|
|
172
|
+
},
|
|
173
|
+
]);
|
|
174
|
+
|
|
175
|
+
const targetDir = path.resolve(process.cwd(), answers.projectName);
|
|
176
|
+
if (fs.existsSync(targetDir) && fs.readdirSync(targetDir).length > 0) {
|
|
177
|
+
console.log(chalk.red(`\nDirectory "${answers.projectName}" already exists and is not empty.`));
|
|
178
|
+
process.exit(1);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
if (opts.source) {
|
|
183
|
+
await installFromSource(targetDir, answers);
|
|
184
|
+
} else {
|
|
185
|
+
await installFromImages(targetDir, answers);
|
|
186
|
+
}
|
|
187
|
+
} catch (err) {
|
|
188
|
+
console.error(chalk.red('\n' + (err.shortMessage || err.message)));
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (answers.start) {
|
|
193
|
+
console.log(chalk.green('\n✓ Services running'));
|
|
194
|
+
console.log('\n Frontend: ' + chalk.cyan('http://localhost:3000'));
|
|
195
|
+
console.log(' Backend: ' + chalk.cyan('http://localhost:8000'));
|
|
196
|
+
} else {
|
|
197
|
+
console.log(chalk.green('\nSetup complete (services not started).'));
|
|
198
|
+
console.log(`\n cd ${answers.projectName}`);
|
|
199
|
+
console.log(' npx aladdin-ai up');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
console.log(chalk.dim('\nHealth check: npx aladdin-ai doctor'));
|
|
203
|
+
if (!opts.source) {
|
|
204
|
+
console.log(chalk.dim('Update: cd ' + answers.projectName + ' && docker compose pull && npx aladdin-ai restart\n'));
|
|
205
|
+
} else {
|
|
206
|
+
console.log(chalk.dim('Source tree is in ' + answers.projectName + ' — edit and rebuild with `npx aladdin-ai up --build`\n'));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// `aladdin-ai up | down | restart | logs` — docker compose wrappers.
|
|
2
|
+
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { compose } from '../lib/compose.js';
|
|
5
|
+
|
|
6
|
+
export async function upCommand(opts) {
|
|
7
|
+
console.log(chalk.cyan('Starting AladdinAI services…\n'));
|
|
8
|
+
const args = ['up', '-d'];
|
|
9
|
+
if (opts.build) args.push('--build');
|
|
10
|
+
const r = await compose(args);
|
|
11
|
+
if (r.exitCode !== 0) process.exit(r.exitCode || 1);
|
|
12
|
+
console.log(chalk.green('\n✓ Services started'));
|
|
13
|
+
console.log(' Frontend: ' + chalk.cyan('http://localhost:3000'));
|
|
14
|
+
console.log(' Backend: ' + chalk.cyan('http://localhost:8000/api/docs'));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function downCommand(opts) {
|
|
18
|
+
console.log(chalk.cyan('Stopping AladdinAI services…\n'));
|
|
19
|
+
const args = ['down'];
|
|
20
|
+
if (opts.volumes) args.push('-v');
|
|
21
|
+
const r = await compose(args);
|
|
22
|
+
process.exit(r.exitCode || 0);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function restartCommand(service) {
|
|
26
|
+
console.log(chalk.cyan(`Restarting ${service || 'all services'}…\n`));
|
|
27
|
+
const args = ['restart'];
|
|
28
|
+
if (service) args.push(service);
|
|
29
|
+
const r = await compose(args);
|
|
30
|
+
process.exit(r.exitCode || 0);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function logsCommand(service, opts) {
|
|
34
|
+
const args = ['logs'];
|
|
35
|
+
if (opts.follow) args.push('-f');
|
|
36
|
+
if (opts.tail) args.push('--tail', String(opts.tail));
|
|
37
|
+
if (service) args.push(service);
|
|
38
|
+
const r = await compose(args);
|
|
39
|
+
process.exit(r.exitCode || 0);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function updateCommand() {
|
|
43
|
+
console.log(chalk.cyan('Pulling latest images…\n'));
|
|
44
|
+
let r = await compose(['pull']);
|
|
45
|
+
if (r.exitCode !== 0) process.exit(r.exitCode || 1);
|
|
46
|
+
console.log(chalk.cyan('\nRecreating services with new images…\n'));
|
|
47
|
+
r = await compose(['up', '-d']);
|
|
48
|
+
if (r.exitCode !== 0) process.exit(r.exitCode || 1);
|
|
49
|
+
console.log(chalk.green('\n✓ Updated and running'));
|
|
50
|
+
}
|
package/index.js
CHANGED
|
@@ -1,146 +1,80 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// NOTICE: This file is protected under RCF-PL v1.2.8
|
|
4
|
-
// [RCF:PROTECTED]
|
|
5
|
-
|
|
6
3
|
import { Command } from 'commander';
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import
|
|
4
|
+
import { readFileSync } from 'node:fs';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import updateNotifier from 'update-notifier';
|
|
8
|
+
import { initCommand } from './commands/init.js';
|
|
9
|
+
import {
|
|
10
|
+
upCommand,
|
|
11
|
+
downCommand,
|
|
12
|
+
restartCommand,
|
|
13
|
+
logsCommand,
|
|
14
|
+
updateCommand,
|
|
15
|
+
} from './commands/lifecycle.js';
|
|
16
|
+
import { doctorCommand } from './commands/doctor.js';
|
|
17
|
+
|
|
18
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
const pkg = JSON.parse(readFileSync(path.join(__dirname, 'package.json'), 'utf8'));
|
|
20
|
+
|
|
21
|
+
// Check for updates once per day, show a banner on next run
|
|
22
|
+
updateNotifier({ pkg, updateCheckInterval: 1000 * 60 * 60 * 24 }).notify({ defer: false });
|
|
13
23
|
|
|
14
24
|
const program = new Command();
|
|
15
25
|
|
|
16
|
-
const REPO_URL = 'https://github.com/aliyevaladddin/AladdinAI.git';
|
|
17
|
-
|
|
18
|
-
console.log(chalk.cyan(`
|
|
19
|
-
╔══════════════════════════════════════════════════════════╗
|
|
20
|
-
║ ║
|
|
21
|
-
║ ✨ WELCOME TO ALADDIN AI PLATFORM INITIALIZER ✨ ║
|
|
22
|
-
║ Powered by RCF Protocol v2.0.3 ║
|
|
23
|
-
║ ║
|
|
24
|
-
╚══════════════════════════════════════════════════════════╝
|
|
25
|
-
`));
|
|
26
|
-
|
|
27
26
|
program
|
|
28
27
|
.name('aladdin-ai')
|
|
29
|
-
.description('
|
|
30
|
-
.version(
|
|
31
|
-
.action(async () => {
|
|
32
|
-
const { mode } = await inquirer.prompt([
|
|
33
|
-
{
|
|
34
|
-
type: 'list',
|
|
35
|
-
name: 'mode',
|
|
36
|
-
message: 'What would you like to do?',
|
|
37
|
-
choices: [
|
|
38
|
-
{ name: '🚀 Install Desktop App (Recommended)', value: 'desktop' },
|
|
39
|
-
{ name: '💻 Clone Source Code (For Developers)', value: 'source' }
|
|
40
|
-
]
|
|
41
|
-
}
|
|
42
|
-
]);
|
|
43
|
-
|
|
44
|
-
if (mode === 'desktop') {
|
|
45
|
-
const spinner = ora('Preparing Desktop App download...').start();
|
|
46
|
-
try {
|
|
47
|
-
const platform = process.platform;
|
|
48
|
-
let downloadUrl = '';
|
|
49
|
-
let fileName = '';
|
|
50
|
-
|
|
51
|
-
// GitHub Release assets usually follow this pattern
|
|
52
|
-
if (platform === 'darwin') {
|
|
53
|
-
downloadUrl = 'https://github.com/aliyevaladddin/AladdinAI/releases/latest/download/AladdinAI.dmg';
|
|
54
|
-
fileName = 'AladdinAI.dmg';
|
|
55
|
-
} else if (platform === 'win32') {
|
|
56
|
-
// Changed to match electron-builder default naming or redirect
|
|
57
|
-
downloadUrl = 'https://github.com/aliyevaladddin/AladdinAI/releases/latest/download/AladdinAI.exe';
|
|
58
|
-
fileName = 'AladdinAI.exe';
|
|
59
|
-
} else if (platform === 'linux') {
|
|
60
|
-
downloadUrl = 'https://github.com/aliyevaladddin/AladdinAI/releases/latest/download/AladdinAI.AppImage';
|
|
61
|
-
fileName = 'AladdinAI.AppImage';
|
|
62
|
-
} else {
|
|
63
|
-
spinner.fail(chalk.red('Platform not supported for desktop direct install.'));
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
spinner.text = `Downloading ${fileName} for ${platform}...`;
|
|
68
|
-
const downloadPath = path.join(process.cwd(), fileName);
|
|
69
|
-
|
|
70
|
-
// Use -f to fail on 404, -L to follow redirects
|
|
71
|
-
try {
|
|
72
|
-
await execa('curl', ['-f', '-L', '-o', downloadPath, downloadUrl]);
|
|
73
|
-
} catch (downloadErr) {
|
|
74
|
-
spinner.fail(chalk.red(`\n❌ Failed to download ${fileName}. Possibly the version is not yet available on GitHub Releases.`));
|
|
75
|
-
console.log(chalk.yellow(`\nTry downloading manually from: https://github.com/aliyevaladddin/AladdinAI/releases`));
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
spinner.succeed(chalk.green(`\n✅ Download complete: ${fileName}`));
|
|
80
|
-
console.log(chalk.cyan(`\n👉 Opening ${fileName} for installation...`));
|
|
81
|
-
|
|
82
|
-
try {
|
|
83
|
-
if (platform === 'darwin') {
|
|
84
|
-
await execa('open', [downloadPath]);
|
|
85
|
-
} else if (platform === 'win32') {
|
|
86
|
-
// More robust Windows open command using shell
|
|
87
|
-
await execa('powershell', ['-Command', `Start-Process "${downloadPath}"`], { shell: true });
|
|
88
|
-
} else {
|
|
89
|
-
await execa('xdg-open', [downloadPath]);
|
|
90
|
-
}
|
|
91
|
-
} catch (openErr) {
|
|
92
|
-
console.log(chalk.yellow(`\n⚠️ Could not open automatically. Please run ${fileName} manually from this folder.`));
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
} catch (err) {
|
|
96
|
-
spinner.fail(chalk.red('An unexpected error occurred during installation.'));
|
|
97
|
-
console.error(err);
|
|
98
|
-
}
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
28
|
+
.description('Self-hosted AI workspace — agents, memory, CRM, channels.')
|
|
29
|
+
.version(pkg.version);
|
|
101
30
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
if (fs.existsSync(targetDir)) {
|
|
120
|
-
console.log(chalk.red(`\n❌ Error: Directory ${answers.projectName} already exists.`));
|
|
121
|
-
process.exit(1);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const spinner = ora('Cloning AladdinAI repository...').start();
|
|
31
|
+
// ── init (default) ────────────────────────────────────────────────────
|
|
32
|
+
program
|
|
33
|
+
.command('init', { isDefault: true })
|
|
34
|
+
.description('Set up a new AladdinAI deployment using prebuilt Docker images')
|
|
35
|
+
.option('-n, --name <name>', 'project directory name')
|
|
36
|
+
.option('-y, --yes', 'accept defaults, skip prompts')
|
|
37
|
+
.option('--skip-start', 'do not run docker compose up after setup')
|
|
38
|
+
.option('--source', 'install from source (git clone + local build, for contributors)')
|
|
39
|
+
.action(initCommand);
|
|
40
|
+
|
|
41
|
+
// ── lifecycle ─────────────────────────────────────────────────────────
|
|
42
|
+
program
|
|
43
|
+
.command('up')
|
|
44
|
+
.description('Start services (docker compose up -d)')
|
|
45
|
+
.option('--build', 'rebuild images before starting')
|
|
46
|
+
.action(upCommand);
|
|
125
47
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
48
|
+
program
|
|
49
|
+
.command('down')
|
|
50
|
+
.description('Stop services')
|
|
51
|
+
.option('-v, --volumes', 'also remove volumes (DESTROYS data)')
|
|
52
|
+
.action(downCommand);
|
|
130
53
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
54
|
+
program
|
|
55
|
+
.command('restart [service]')
|
|
56
|
+
.description('Restart all services or a specific one (backend, frontend, postgres)')
|
|
57
|
+
.action(restartCommand);
|
|
136
58
|
|
|
137
|
-
|
|
138
|
-
|
|
59
|
+
program
|
|
60
|
+
.command('logs [service]')
|
|
61
|
+
.description('Tail service logs (defaults to all)')
|
|
62
|
+
.option('-f, --follow', 'follow log output')
|
|
63
|
+
.option('-t, --tail <lines>', 'number of lines to show from the end', '100')
|
|
64
|
+
.action(logsCommand);
|
|
139
65
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
});
|
|
66
|
+
program
|
|
67
|
+
.command('update')
|
|
68
|
+
.description('Pull the latest images and recreate services')
|
|
69
|
+
.action(updateCommand);
|
|
145
70
|
|
|
146
|
-
|
|
71
|
+
// ── diagnostics ───────────────────────────────────────────────────────
|
|
72
|
+
program
|
|
73
|
+
.command('doctor')
|
|
74
|
+
.description('Diagnose setup issues: docker, .env, ports, services')
|
|
75
|
+
.action(doctorCommand);
|
|
76
|
+
|
|
77
|
+
program.parseAsync(process.argv).catch((err) => {
|
|
78
|
+
console.error(err.shortMessage || err.message);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
});
|
package/lib/compose.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Thin wrapper for invoking `docker compose` from any subcommand.
|
|
2
|
+
|
|
3
|
+
import { execa } from 'execa';
|
|
4
|
+
import { checkDocker } from './prereq.js';
|
|
5
|
+
import { requireProjectRoot } from './project.js';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
|
|
8
|
+
export async function compose(args, { cwd, stdio = 'inherit' } = {}) {
|
|
9
|
+
const root = cwd || requireProjectRoot();
|
|
10
|
+
const docker = await checkDocker();
|
|
11
|
+
if (!docker.ok) {
|
|
12
|
+
console.error(chalk.red('✗ ') + docker.message);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
const [bin, prefix] = docker.composeCmd;
|
|
16
|
+
return execa(bin, [...prefix, ...args], { cwd: root, stdio, reject: false });
|
|
17
|
+
}
|
package/lib/prereq.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Check for required tools on the user's machine.
|
|
2
|
+
|
|
3
|
+
import { execa } from 'execa';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
|
|
6
|
+
async function has(cmd, args = ['--version']) {
|
|
7
|
+
try {
|
|
8
|
+
const r = await execa(cmd, args, { reject: false });
|
|
9
|
+
return r.exitCode === 0;
|
|
10
|
+
} catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function checkDocker() {
|
|
16
|
+
if (!(await has('docker'))) {
|
|
17
|
+
return { ok: false, message: 'Docker not found. Install: https://docs.docker.com/get-docker/' };
|
|
18
|
+
}
|
|
19
|
+
// `docker compose` (v2) or legacy `docker-compose`
|
|
20
|
+
const v2 = await has('docker', ['compose', 'version']);
|
|
21
|
+
const v1 = await has('docker-compose');
|
|
22
|
+
if (!v2 && !v1) {
|
|
23
|
+
return { ok: false, message: 'docker compose plugin not found. Upgrade Docker Desktop to v20+.' };
|
|
24
|
+
}
|
|
25
|
+
// Daemon running?
|
|
26
|
+
const info = await execa('docker', ['info'], { reject: false });
|
|
27
|
+
if (info.exitCode !== 0) {
|
|
28
|
+
return { ok: false, message: 'Docker daemon is not running. Start Docker Desktop.' };
|
|
29
|
+
}
|
|
30
|
+
return { ok: true, composeCmd: v2 ? ['docker', ['compose']] : ['docker-compose', []] };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function checkGit() {
|
|
34
|
+
if (!(await has('git'))) {
|
|
35
|
+
return { ok: false, message: 'git not found. Install: https://git-scm.com/downloads' };
|
|
36
|
+
}
|
|
37
|
+
return { ok: true };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function runPrereqChecks({ requireGit = false } = {}) {
|
|
41
|
+
const docker = await checkDocker();
|
|
42
|
+
if (!docker.ok) {
|
|
43
|
+
console.error(chalk.red('✗ ') + docker.message);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
if (requireGit) {
|
|
47
|
+
const git = await checkGit();
|
|
48
|
+
if (!git.ok) {
|
|
49
|
+
console.error(chalk.red('✗ ') + git.message);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return docker; // exposes composeCmd
|
|
54
|
+
}
|
package/lib/project.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Locate the AladdinAI project root from the current working directory.
|
|
2
|
+
//
|
|
3
|
+
// Strategy: walk up from cwd looking for docker-compose.yml AND backend/ dir.
|
|
4
|
+
// If not found, return null — caller decides whether to error out.
|
|
5
|
+
|
|
6
|
+
import fs from 'fs-extra';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
|
|
9
|
+
export function findProjectRoot(start = process.cwd()) {
|
|
10
|
+
let dir = path.resolve(start);
|
|
11
|
+
const { root } = path.parse(dir);
|
|
12
|
+
while (true) {
|
|
13
|
+
const compose = path.join(dir, 'docker-compose.yml');
|
|
14
|
+
const backend = path.join(dir, 'backend');
|
|
15
|
+
if (fs.existsSync(compose) && fs.existsSync(backend)) return dir;
|
|
16
|
+
if (dir === root) return null;
|
|
17
|
+
dir = path.dirname(dir);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function requireProjectRoot() {
|
|
22
|
+
const root = findProjectRoot();
|
|
23
|
+
if (!root) {
|
|
24
|
+
console.error(
|
|
25
|
+
'\nNot inside an AladdinAI project (no docker-compose.yml found above cwd).\n' +
|
|
26
|
+
'Run `npx aladdin-ai init` first, then cd into the created directory.\n'
|
|
27
|
+
);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
return root;
|
|
31
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aladdin-ai",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Self-hosted AI workspace — agents, memory, CRM, channels. One command to install and run.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"aladdin-ai": "index.js"
|
|
@@ -11,16 +11,26 @@
|
|
|
11
11
|
"registry": "https://registry.npmjs.org/"
|
|
12
12
|
},
|
|
13
13
|
"type": "module",
|
|
14
|
+
"files": [
|
|
15
|
+
"index.js",
|
|
16
|
+
"commands",
|
|
17
|
+
"lib",
|
|
18
|
+
"templates",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
14
22
|
"scripts": {
|
|
15
|
-
"test": "
|
|
23
|
+
"test": "node -e \"import('./index.js')\""
|
|
16
24
|
},
|
|
17
25
|
"keywords": [
|
|
18
26
|
"ai",
|
|
19
|
-
"
|
|
20
|
-
"rcf-protocol",
|
|
27
|
+
"agents",
|
|
21
28
|
"crm",
|
|
22
|
-
"
|
|
23
|
-
"
|
|
29
|
+
"fastapi",
|
|
30
|
+
"nextjs",
|
|
31
|
+
"self-hosted",
|
|
32
|
+
"docker",
|
|
33
|
+
"aladdin-ai"
|
|
24
34
|
],
|
|
25
35
|
"repository": {
|
|
26
36
|
"type": "git",
|
|
@@ -32,12 +42,16 @@
|
|
|
32
42
|
"homepage": "https://github.com/aliyevaladddin/AladdinAI#readme",
|
|
33
43
|
"author": "Aladdin Aliyev <aladdin@aliyev.site>",
|
|
34
44
|
"license": "Apache-2.0",
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=18"
|
|
47
|
+
},
|
|
35
48
|
"dependencies": {
|
|
36
49
|
"chalk": "^5.3.0",
|
|
37
50
|
"commander": "^12.0.3",
|
|
38
51
|
"execa": "^9.1.0",
|
|
39
52
|
"fs-extra": "^11.2.0",
|
|
40
53
|
"inquirer": "^9.2.22",
|
|
41
|
-
"ora": "^8.0.1"
|
|
54
|
+
"ora": "^8.0.1",
|
|
55
|
+
"update-notifier": "^7.0.0"
|
|
42
56
|
}
|
|
43
|
-
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# AladdinAI — production compose using prebuilt images from GHCR.
|
|
2
|
+
#
|
|
3
|
+
# Pull updates: docker compose pull
|
|
4
|
+
# Pin a version: set ALADDINAI_VERSION in your .env (defaults to "latest").
|
|
5
|
+
|
|
6
|
+
services:
|
|
7
|
+
postgres:
|
|
8
|
+
image: postgres:16-alpine
|
|
9
|
+
environment:
|
|
10
|
+
POSTGRES_USER: ${POSTGRES_USER}
|
|
11
|
+
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
|
12
|
+
POSTGRES_DB: ${POSTGRES_DB}
|
|
13
|
+
volumes:
|
|
14
|
+
- pgdata:/var/lib/postgresql/data
|
|
15
|
+
healthcheck:
|
|
16
|
+
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
|
|
17
|
+
interval: 5s
|
|
18
|
+
timeout: 3s
|
|
19
|
+
retries: 5
|
|
20
|
+
restart: unless-stopped
|
|
21
|
+
|
|
22
|
+
backend:
|
|
23
|
+
image: ghcr.io/aliyevaladddin/aladdinai-backend:${ALADDINAI_VERSION:-latest}
|
|
24
|
+
ports:
|
|
25
|
+
- "8000:8000"
|
|
26
|
+
env_file:
|
|
27
|
+
- .env
|
|
28
|
+
environment:
|
|
29
|
+
# Override to point at the docker-compose Postgres service, not localhost.
|
|
30
|
+
DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}
|
|
31
|
+
volumes:
|
|
32
|
+
- media:/app/media
|
|
33
|
+
depends_on:
|
|
34
|
+
postgres:
|
|
35
|
+
condition: service_healthy
|
|
36
|
+
restart: unless-stopped
|
|
37
|
+
|
|
38
|
+
frontend:
|
|
39
|
+
image: ghcr.io/aliyevaladddin/aladdinai-frontend:${ALADDINAI_VERSION:-latest}
|
|
40
|
+
ports:
|
|
41
|
+
- "3000:3000"
|
|
42
|
+
env_file:
|
|
43
|
+
- .env
|
|
44
|
+
environment:
|
|
45
|
+
# Server-side Next.js rewrites need to reach backend via the compose network
|
|
46
|
+
BACKEND_INTERNAL_URL: http://backend:8000
|
|
47
|
+
depends_on:
|
|
48
|
+
- backend
|
|
49
|
+
restart: unless-stopped
|
|
50
|
+
|
|
51
|
+
volumes:
|
|
52
|
+
pgdata:
|
|
53
|
+
media:
|