aladdin-ai 1.2.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,25 +1,140 @@
1
- # 🪄 AladdinAI CLI Installer
1
+ # aladdin-ai
2
2
 
3
- The official bootstrap tool for the **AladdinAI Platform**.
3
+ Self-hosted AI workspace, one command to install and run.
4
4
 
5
- This tool allows you to deploy the full AladdinAI ecosystem, including the AI Orchestrator, CRM integration, and the Desktop Application, with a single command.
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
- ## 🚀 Installation
8
-
9
- You don't need to install this package globally. Just run:
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
- ## ✨ What's included?
16
- - **AI Orchestrator**: Support for MiniCPM3 and other LLMs.
17
- - **RCF Protocol**: Built-in security and correlation framework.
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
- ## 🛡️ Security
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
+ }
@@ -0,0 +1,210 @@
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
+ // Note: file is named `env.template` (no leading dot) because npm's default
46
+ // packaging silently drops dotfiles from `files` globs.
47
+ const tmplPath = path.join(TEMPLATES_DIR, 'env.template');
48
+ const tmpl = await fs.readFile(tmplPath, 'utf8');
49
+ const filled = fillEnv(tmpl, {
50
+ POSTGRES_PASSWORD: genSecret(16),
51
+ JWT_SECRET: genSecret(32),
52
+ FERNET_KEY: genFernetKey(),
53
+ });
54
+ await fs.writeFile(path.join(targetDir, '.env'), filled);
55
+ }
56
+
57
+ // ── Image-based install (default) ─────────────────────────────────────
58
+ async function installFromImages(targetDir, { start }) {
59
+ await fs.ensureDir(targetDir);
60
+
61
+ // 1. Copy compose template
62
+ const composeSpin = ora('Writing docker-compose.yml').start();
63
+ try {
64
+ await fs.copy(
65
+ path.join(TEMPLATES_DIR, 'docker-compose.yml'),
66
+ path.join(targetDir, 'docker-compose.yml'),
67
+ );
68
+ composeSpin.succeed('docker-compose.yml written');
69
+ } catch (err) {
70
+ composeSpin.fail('Failed to write docker-compose.yml');
71
+ throw err;
72
+ }
73
+
74
+ // 2. Generate .env
75
+ const envSpin = ora('Generating .env with secure secrets').start();
76
+ try {
77
+ await writeEnv(targetDir);
78
+ envSpin.succeed('.env created');
79
+ } catch (err) {
80
+ envSpin.fail('Failed to write .env');
81
+ throw err;
82
+ }
83
+
84
+ if (!start) return;
85
+
86
+ // 3. Pull images
87
+ console.log(chalk.cyan('\nPulling images from ghcr.io (first run can take a few minutes)…\n'));
88
+ const pullRes = await compose(['pull'], { cwd: targetDir });
89
+ if (pullRes.exitCode !== 0) {
90
+ console.error(chalk.red('\ndocker compose pull failed. Images may not be published yet.'));
91
+ console.error(chalk.dim('You can build from source instead: npx aladdin-ai init --source\n'));
92
+ process.exit(1);
93
+ }
94
+
95
+ // 4. Start
96
+ console.log(chalk.cyan('\nStarting services…\n'));
97
+ const upRes = await compose(['up', '-d'], { cwd: targetDir });
98
+ if (upRes.exitCode !== 0) {
99
+ console.error(chalk.red('\ndocker compose up failed. Inspect logs with `npx aladdin-ai logs`.'));
100
+ process.exit(1);
101
+ }
102
+ }
103
+
104
+ // ── Source-based install (--source flag, for contributors) ────────────
105
+ async function installFromSource(targetDir, { start }) {
106
+ const cloneSpin = ora('Cloning repository').start();
107
+ try {
108
+ await execa('git', ['clone', '--depth', '1', REPO_URL, targetDir]);
109
+ await fs.remove(path.join(targetDir, '.git'));
110
+ cloneSpin.succeed('Repository cloned');
111
+ } catch (err) {
112
+ cloneSpin.fail('git clone failed');
113
+ throw err;
114
+ }
115
+
116
+ const envSpin = ora('Generating .env with secure secrets').start();
117
+ try {
118
+ const tmpl = await fs.readFile(path.join(targetDir, '.env.example'), 'utf8');
119
+ const dbPassword = genSecret(16);
120
+ const filled = tmpl
121
+ .replace(/^POSTGRES_PASSWORD=.*$/m, `POSTGRES_PASSWORD=${dbPassword}`)
122
+ .replace(/^JWT_SECRET=.*$/m, `JWT_SECRET=${genSecret(32)}`)
123
+ .replace(/^FERNET_KEY=.*$/m, `FERNET_KEY=${genFernetKey()}`)
124
+ .replace(
125
+ /^# DATABASE_URL=.*$/m,
126
+ `DATABASE_URL=postgresql+asyncpg://aladdin_user:${dbPassword}@postgres:5432/aladdinai`,
127
+ );
128
+ await fs.writeFile(path.join(targetDir, '.env'), filled);
129
+ envSpin.succeed('.env created');
130
+ } catch (err) {
131
+ envSpin.fail('Failed to create .env');
132
+ throw err;
133
+ }
134
+
135
+ if (!start) return;
136
+
137
+ console.log(chalk.cyan('\nBuilding images locally and starting…\n'));
138
+ const r = await compose(['up', '-d', '--build'], { cwd: targetDir });
139
+ if (r.exitCode !== 0) {
140
+ console.error(chalk.red('\ndocker compose up failed. Inspect logs with `npx aladdin-ai logs`.'));
141
+ process.exit(1);
142
+ }
143
+ }
144
+
145
+ // ── Entry point ───────────────────────────────────────────────────────
146
+ export async function initCommand(opts) {
147
+ console.log(chalk.cyan('\nAladdinAI'));
148
+ console.log(chalk.dim('Self-hosted AI workspace — agents, memory, CRM, channels.\n'));
149
+
150
+ // Source mode needs git as well; image mode only needs docker.
151
+ await runPrereqChecks({ requireGit: !!opts.source });
152
+
153
+ const defaults = {
154
+ projectName: opts.name || 'aladdin-ai',
155
+ start: !opts.skipStart,
156
+ };
157
+
158
+ const answers = opts.yes
159
+ ? defaults
160
+ : await inquirer.prompt([
161
+ {
162
+ type: 'input',
163
+ name: 'projectName',
164
+ message: 'Project directory:',
165
+ default: defaults.projectName,
166
+ },
167
+ {
168
+ type: 'confirm',
169
+ name: 'start',
170
+ message: opts.source
171
+ ? 'Build images and start services after setup?'
172
+ : 'Pull images and start services after setup?',
173
+ default: defaults.start,
174
+ },
175
+ ]);
176
+
177
+ const targetDir = path.resolve(process.cwd(), answers.projectName);
178
+ if (fs.existsSync(targetDir) && fs.readdirSync(targetDir).length > 0) {
179
+ console.log(chalk.red(`\nDirectory "${answers.projectName}" already exists and is not empty.`));
180
+ process.exit(1);
181
+ }
182
+
183
+ try {
184
+ if (opts.source) {
185
+ await installFromSource(targetDir, answers);
186
+ } else {
187
+ await installFromImages(targetDir, answers);
188
+ }
189
+ } catch (err) {
190
+ console.error(chalk.red('\n' + (err.shortMessage || err.message)));
191
+ process.exit(1);
192
+ }
193
+
194
+ if (answers.start) {
195
+ console.log(chalk.green('\n✓ Services running'));
196
+ console.log('\n Frontend: ' + chalk.cyan('http://localhost:3000'));
197
+ console.log(' Backend: ' + chalk.cyan('http://localhost:8000'));
198
+ } else {
199
+ console.log(chalk.green('\nSetup complete (services not started).'));
200
+ console.log(`\n cd ${answers.projectName}`);
201
+ console.log(' npx aladdin-ai up');
202
+ }
203
+
204
+ console.log(chalk.dim('\nHealth check: npx aladdin-ai doctor'));
205
+ if (!opts.source) {
206
+ console.log(chalk.dim('Update: cd ' + answers.projectName + ' && docker compose pull && npx aladdin-ai restart\n'));
207
+ } else {
208
+ console.log(chalk.dim('Source tree is in ' + answers.projectName + ' — edit and rebuild with `npx aladdin-ai up --build`\n'));
209
+ }
210
+ }
@@ -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 chalk from 'chalk';
8
- import inquirer from 'inquirer';
9
- import ora from 'ora';
10
- import { execa } from 'execa';
11
- import fs from 'fs-extra';
12
- import path from 'path';
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('Bootstrap your own AladdinAI instance')
30
- .version('1.2.0')
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
- const answers = await inquirer.prompt([
103
- {
104
- type: 'input',
105
- name: 'projectName',
106
- message: 'What is your project name?',
107
- default: 'my-aladdin-ai'
108
- },
109
- {
110
- type: 'confirm',
111
- name: 'installDeps',
112
- message: 'Would you like to install dependencies automatically?',
113
- default: true
114
- }
115
- ]);
116
-
117
- const targetDir = path.join(process.cwd(), answers.projectName);
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
- try {
127
- await execa('git', ['clone', REPO_URL, targetDir]);
128
- spinner.succeed(chalk.green('Repository cloned successfully!'));
129
- await fs.remove(path.join(targetDir, '.git'));
48
+ program
49
+ .command('down')
50
+ .description('Stop services')
51
+ .option('-v, --volumes', 'also remove volumes (DESTROYS data)')
52
+ .action(downCommand);
130
53
 
131
- if (answers.installDeps) {
132
- const feSpinner = ora('Installing Frontend dependencies...').start();
133
- await execa('npm', ['install'], { cwd: path.join(targetDir, 'frontend') });
134
- feSpinner.succeed(chalk.green('Frontend dependencies installed!'));
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
- console.log(chalk.blue('\n🛡️ RCF Protocol: Your project is pre-marked for RCF compliance.'));
138
- console.log(chalk.green(`\n🚀 Success! Your AladdinAI platform is ready in ${answers.projectName}`));
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
- } catch (error) {
141
- spinner.fail(chalk.red('Failed to bootstrap project.'));
142
- console.error(error);
143
- }
144
- });
66
+ program
67
+ .command('update')
68
+ .description('Pull the latest images and recreate services')
69
+ .action(updateCommand);
145
70
 
146
- program.parse(process.argv);
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": "1.2.0",
4
- "description": "The official CLI for AladdinAI Platform. Powered by RCF Protocol.",
3
+ "version": "2.0.1",
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": "echo \"Error: no test specified\" && exit 1"
23
+ "test": "node -e \"import('./index.js')\""
16
24
  },
17
25
  "keywords": [
18
26
  "ai",
19
- "orchestrator",
20
- "rcf-protocol",
27
+ "agents",
21
28
  "crm",
22
- "aladdin-ai",
23
- "automation"
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:
@@ -0,0 +1,25 @@
1
+ # AladdinAI environment configuration.
2
+ # The `npx aladdin-ai init` wizard fills in secure random values for the
3
+ # secret-looking variables below. Edit as needed.
4
+
5
+ # ── Database ─────────────────────────────────────────────────────────
6
+ POSTGRES_USER=aladdin_user
7
+ POSTGRES_PASSWORD=__GENERATED__
8
+ POSTGRES_DB=aladdinai
9
+
10
+ # ── Security (regenerate before exposing to the internet) ────────────
11
+ # JWT_SECRET: openssl rand -hex 32
12
+ JWT_SECRET=__GENERATED__
13
+ JWT_ALGORITHM=HS256
14
+ JWT_ACCESS_TOKEN_EXPIRE_MINUTES=30
15
+ JWT_REFRESH_TOKEN_EXPIRE_DAYS=7
16
+
17
+ # FERNET_KEY: 32 random bytes base64-urlsafe encoded
18
+ FERNET_KEY=__GENERATED__
19
+
20
+ # ── Networking ───────────────────────────────────────────────────────
21
+ NEXT_PUBLIC_API_URL=http://localhost:8000/api
22
+
23
+ # ── Image version (advanced) ─────────────────────────────────────────
24
+ # Pin to a specific release if "latest" causes you issues.
25
+ # ALADDINAI_VERSION=1.0.0