aladdin-ai 2.0.1 → 2.2.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,3 +1,4 @@
1
+ // NOTICE: This file is protected under RCF-PL
1
2
  # aladdin-ai
2
3
 
3
4
  Self-hosted AI workspace, one command to install and run.
@@ -7,6 +8,7 @@ npx aladdin-ai
7
8
  ```
8
9
 
9
10
  This drops a `docker-compose.yml` + a `.env` (filled with
11
+
10
12
  cryptographically-secure `JWT_SECRET`, `FERNET_KEY`, `POSTGRES_PASSWORD`)
11
13
  into a new directory, pulls the prebuilt images from GHCR, and starts
12
14
  the stack. Open [http://localhost:3000](http://localhost:3000) when it
@@ -111,25 +113,9 @@ story.
111
113
 
112
114
  ## Releasing
113
115
 
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
- ```
116
+ **CLI** (this package on npm) and **Backend + frontend images** (to GHCR) are published automatically via GitHub Actions whenever a new GitHub Release with a tag like `vX.Y.Z` is published.
125
117
 
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
132
- ```
118
+ Alternatively, you can manually trigger the CLI publish workflow (`Publish CLI`) from the Actions tab and specify the version you want to release.
133
119
 
134
120
  The workflow builds both images for linux/amd64 + linux/arm64 and pushes
135
121
  them as `ghcr.io/aliyevaladddin/aladdinai-backend:1.0.0`,
@@ -1,3 +1,4 @@
1
+ // NOTICE: This file is protected under RCF-PL
1
2
  // `aladdin-ai doctor` — diagnose common setup issues.
2
3
 
3
4
  import chalk from 'chalk';
@@ -8,11 +9,14 @@ import { execa } from 'execa';
8
9
  import { findProjectRoot } from '../lib/project.js';
9
10
  import { checkDocker, checkGit } from '../lib/prereq.js';
10
11
 
12
+ // [RCF:PROTECTED]
11
13
  const ok = (msg) => console.log(chalk.green(' ✓ ') + msg);
14
+ // [RCF:PROTECTED]
12
15
  const bad = (msg, hint) => {
13
16
  console.log(chalk.red(' ✗ ') + msg);
14
17
  if (hint) console.log(chalk.dim(' → ' + hint));
15
18
  };
19
+ // [RCF:PROTECTED]
16
20
  const warn = (msg, hint) => {
17
21
  console.log(chalk.yellow(' ! ') + msg);
18
22
  if (hint) console.log(chalk.dim(' → ' + hint));
@@ -21,9 +25,11 @@ const warn = (msg, hint) => {
21
25
  // Probe whether anything is listening on a port (true = something is there).
22
26
  // We try to connect rather than bind because Docker port mappings show up
23
27
  // from the host as connectable TCP endpoints.
28
+ // [RCF:PROTECTED]
24
29
  async function isPortInUse(port) {
25
30
  return new Promise((resolve) => {
26
31
  const sock = new net.Socket();
32
+ // [RCF:PROTECTED]
27
33
  const done = (val) => { sock.destroy(); resolve(val); };
28
34
  sock.setTimeout(500);
29
35
  sock.once('connect', () => done(true));
@@ -33,10 +39,12 @@ async function isPortInUse(port) {
33
39
  });
34
40
  }
35
41
 
42
+ // [RCF:PROTECTED]
36
43
  async function fetchWithTimeout(url, ms = 3000) {
37
44
  const ctl = new AbortController();
38
45
  const t = setTimeout(() => ctl.abort(), ms);
39
46
  try {
47
+ // [RCF:PROTECTED]
40
48
  const r = await fetch(url, { signal: ctl.signal });
41
49
  return r;
42
50
  } catch {
@@ -46,6 +54,7 @@ async function fetchWithTimeout(url, ms = 3000) {
46
54
  }
47
55
  }
48
56
 
57
+ // [RCF:PROTECTED]
49
58
  export async function doctorCommand() {
50
59
  let failures = 0;
51
60
  console.log(chalk.cyan('\nAladdinAI doctor\n'));
package/commands/init.js CHANGED
@@ -1,3 +1,4 @@
1
+ // NOTICE: This file is protected under RCF-PL
1
2
  // `aladdin-ai init` — bootstrap a new AladdinAI deployment.
2
3
  //
3
4
  // Default path (image-based, what 95% of users want):
@@ -16,23 +17,30 @@ import ora from 'ora';
16
17
  import { execa } from 'execa';
17
18
  import fs from 'fs-extra';
18
19
  import path from 'path';
20
+ // [RCF:PROTECTED]
19
21
  import crypto from 'crypto';
20
22
  import { fileURLToPath } from 'node:url';
21
23
  import { runPrereqChecks } from '../lib/prereq.js';
22
24
  import { compose } from '../lib/compose.js';
25
+ import { runSetupWizard } from './wizard.js';
23
26
 
24
27
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
25
28
  const TEMPLATES_DIR = path.resolve(__dirname, '..', 'templates');
26
29
  const REPO_URL = 'https://github.com/aliyevaladddin/AladdinAI.git';
27
30
 
31
+ // [RCF:PROTECTED]
28
32
  function genSecret(bytes = 32) {
33
+ // [RCF:PROTECTED]
29
34
  return crypto.randomBytes(bytes).toString('hex');
30
35
  }
31
36
 
37
+ // [RCF:PROTECTED]
32
38
  function genFernetKey() {
39
+ // [RCF:PROTECTED]
33
40
  return crypto.randomBytes(32).toString('base64url') + '=';
34
41
  }
35
42
 
43
+ // [RCF:PROTECTED]
36
44
  function fillEnv(text, vars) {
37
45
  let out = text;
38
46
  for (const [k, v] of Object.entries(vars)) {
@@ -41,6 +49,7 @@ function fillEnv(text, vars) {
41
49
  return out;
42
50
  }
43
51
 
52
+ // [RCF:PROTECTED]
44
53
  async function writeEnv(targetDir) {
45
54
  // Note: file is named `env.template` (no leading dot) because npm's default
46
55
  // packaging silently drops dotfiles from `files` globs.
@@ -55,6 +64,7 @@ async function writeEnv(targetDir) {
55
64
  }
56
65
 
57
66
  // ── Image-based install (default) ─────────────────────────────────────
67
+ // [RCF:PROTECTED]
58
68
  async function installFromImages(targetDir, { start }) {
59
69
  await fs.ensureDir(targetDir);
60
70
 
@@ -102,6 +112,7 @@ async function installFromImages(targetDir, { start }) {
102
112
  }
103
113
 
104
114
  // ── Source-based install (--source flag, for contributors) ────────────
115
+ // [RCF:PROTECTED]
105
116
  async function installFromSource(targetDir, { start }) {
106
117
  const cloneSpin = ora('Cloning repository').start();
107
118
  try {
@@ -143,6 +154,7 @@ async function installFromSource(targetDir, { start }) {
143
154
  }
144
155
 
145
156
  // ── Entry point ───────────────────────────────────────────────────────
157
+ // [RCF:PROTECTED]
146
158
  export async function initCommand(opts) {
147
159
  console.log(chalk.cyan('\nAladdinAI'));
148
160
  console.log(chalk.dim('Self-hosted AI workspace — agents, memory, CRM, channels.\n'));
@@ -195,6 +207,9 @@ export async function initCommand(opts) {
195
207
  console.log(chalk.green('\n✓ Services running'));
196
208
  console.log('\n Frontend: ' + chalk.cyan('http://localhost:3000'));
197
209
  console.log(' Backend: ' + chalk.cyan('http://localhost:8000'));
210
+
211
+ // Run setup wizard after successful start
212
+ await runSetupWizard(targetDir);
198
213
  } else {
199
214
  console.log(chalk.green('\nSetup complete (services not started).'));
200
215
  console.log(`\n cd ${answers.projectName}`);
@@ -1,8 +1,10 @@
1
+ // NOTICE: This file is protected under RCF-PL
1
2
  // `aladdin-ai up | down | restart | logs` — docker compose wrappers.
2
3
 
3
4
  import chalk from 'chalk';
4
5
  import { compose } from '../lib/compose.js';
5
6
 
7
+ // [RCF:PROTECTED]
6
8
  export async function upCommand(opts) {
7
9
  console.log(chalk.cyan('Starting AladdinAI services…\n'));
8
10
  const args = ['up', '-d'];
@@ -14,6 +16,7 @@ export async function upCommand(opts) {
14
16
  console.log(' Backend: ' + chalk.cyan('http://localhost:8000/api/docs'));
15
17
  }
16
18
 
19
+ // [RCF:PROTECTED]
17
20
  export async function downCommand(opts) {
18
21
  console.log(chalk.cyan('Stopping AladdinAI services…\n'));
19
22
  const args = ['down'];
@@ -22,6 +25,7 @@ export async function downCommand(opts) {
22
25
  process.exit(r.exitCode || 0);
23
26
  }
24
27
 
28
+ // [RCF:PROTECTED]
25
29
  export async function restartCommand(service) {
26
30
  console.log(chalk.cyan(`Restarting ${service || 'all services'}…\n`));
27
31
  const args = ['restart'];
@@ -30,6 +34,7 @@ export async function restartCommand(service) {
30
34
  process.exit(r.exitCode || 0);
31
35
  }
32
36
 
37
+ // [RCF:PROTECTED]
33
38
  export async function logsCommand(service, opts) {
34
39
  const args = ['logs'];
35
40
  if (opts.follow) args.push('-f');
@@ -39,6 +44,7 @@ export async function logsCommand(service, opts) {
39
44
  process.exit(r.exitCode || 0);
40
45
  }
41
46
 
47
+ // [RCF:PROTECTED]
42
48
  export async function updateCommand() {
43
49
  console.log(chalk.cyan('Pulling latest images…\n'));
44
50
  let r = await compose(['pull']);
@@ -0,0 +1,308 @@
1
+ // NOTICE: This file is protected under RCF-PL
2
+ // Setup wizard — runs after first install to configure LLM providers and create first agent
3
+ import chalk from 'chalk';
4
+ import inquirer from 'inquirer';
5
+ import ora from 'ora';
6
+ import { execa } from 'execa';
7
+
8
+ const PROVIDER_CONFIGS = {
9
+ nim: {
10
+ name: 'NVIDIA NIM',
11
+ description: 'Free tier available, best performance',
12
+ baseUrl: 'https://integrate.api.nvidia.com/v1',
13
+ env: {
14
+ NIM_API_KEY: 'Get from: https://build.nvidia.com',
15
+ NIM_BASE_URL: 'https://integrate.api.nvidia.com/v1',
16
+ NIM_MODEL: 'meta/llama-3.1-70b-instruct',
17
+ },
18
+ },
19
+ openai: {
20
+ name: 'OpenAI',
21
+ description: 'GPT-4, GPT-3.5 Turbo',
22
+ baseUrl: 'https://api.openai.com/v1',
23
+ env: {
24
+ OPENAI_API_KEY: 'Get from: https://platform.openai.com/api-keys',
25
+ },
26
+ },
27
+ anthropic: {
28
+ name: 'Anthropic',
29
+ description: 'Claude 3.5 Sonnet, Opus, Haiku',
30
+ baseUrl: 'https://api.anthropic.com',
31
+ env: {
32
+ ANTHROPIC_API_KEY: 'Get from: https://console.anthropic.com/settings/keys',
33
+ },
34
+ },
35
+ local: {
36
+ name: 'Local (BentoML)',
37
+ description: 'Self-hosted models',
38
+ baseUrl: 'http://localhost:3000',
39
+ env: {},
40
+ },
41
+ };
42
+
43
+ // [RCF:PROTECTED]
44
+ async function setupProviders() {
45
+ console.log(chalk.cyan('\n📡 LLM Provider Setup\n'));
46
+ console.log(chalk.dim('Select which LLM providers you want to configure.\n'));
47
+
48
+ const { providers } = await inquirer.prompt([
49
+ {
50
+ type: 'checkbox',
51
+ name: 'providers',
52
+ message: 'Select providers (Space to select, Enter to continue):',
53
+ choices: [
54
+ {
55
+ name: `${chalk.green('●')} NVIDIA NIM (Recommended) - Free tier, 70B+ models`,
56
+ value: 'nim',
57
+ checked: true,
58
+ },
59
+ {
60
+ name: ` OpenAI - GPT-4, GPT-3.5 Turbo`,
61
+ value: 'openai',
62
+ },
63
+ {
64
+ name: ` Anthropic - Claude 3.5 Sonnet, Opus`,
65
+ value: 'anthropic',
66
+ },
67
+ {
68
+ name: ` Local (BentoML) - Self-hosted models`,
69
+ value: 'local',
70
+ },
71
+ ],
72
+ },
73
+ ]);
74
+
75
+ if (providers.length === 0) {
76
+ console.log(chalk.yellow('\n⚠ No providers selected. You can configure them later in Settings.\n'));
77
+ return {};
78
+ }
79
+
80
+ const envUpdates = {};
81
+
82
+ for (const provider of providers) {
83
+ const config = PROVIDER_CONFIGS[provider];
84
+ console.log(chalk.cyan(`\n── ${config.name} ──`));
85
+
86
+ if (Object.keys(config.env).length > 0) {
87
+ const { shouldConfigure } = await inquirer.prompt([
88
+ {
89
+ type: 'confirm',
90
+ name: 'shouldConfigure',
91
+ message: `Configure ${config.name} now?`,
92
+ default: true,
93
+ },
94
+ ]);
95
+
96
+ if (shouldConfigure) {
97
+ for (const [key, hint] of Object.entries(config.env)) {
98
+ const isSecret = key.toLowerCase().includes('key') || key.toLowerCase().includes('secret') || key.toLowerCase().includes('password') || key.toLowerCase().includes('token');
99
+ const { value } = await inquirer.prompt([
100
+ {
101
+ type: isSecret ? 'password' : 'input',
102
+ name: 'value',
103
+ message: `${key}:`,
104
+ mask: isSecret ? '*' : undefined,
105
+ validate: (input) => input.length > 0 || 'Required',
106
+ },
107
+ ]);
108
+ envUpdates[key] = value;
109
+ }
110
+ } else {
111
+ console.log(chalk.dim(` ${hint}`));
112
+ }
113
+ } else {
114
+ console.log(chalk.dim(' No configuration needed - will use local endpoint'));
115
+ }
116
+ }
117
+
118
+ return envUpdates;
119
+ }
120
+
121
+ // [RCF:PROTECTED]
122
+ async function setupMongoDB() {
123
+ console.log(chalk.cyan('\n🗄️ MongoDB Atlas Setup\n'));
124
+ console.log(chalk.dim('MongoDB Atlas is used for vector memory and media storage.\n'));
125
+
126
+ const { useMongoDB } = await inquirer.prompt([
127
+ {
128
+ type: 'confirm',
129
+ name: 'useMongoDB',
130
+ message: 'Configure MongoDB Atlas now?',
131
+ default: true,
132
+ },
133
+ ]);
134
+
135
+ if (!useMongoDB) {
136
+ console.log(chalk.yellow('⚠ Skipping MongoDB - memory features will be disabled'));
137
+ console.log(chalk.dim(' Get free $500 credits: https://www.mongodb.com/startups\n'));
138
+ return {};
139
+ }
140
+
141
+ console.log(chalk.dim('\nGet connection string from:'));
142
+ console.log(chalk.dim('1. https://cloud.mongodb.com'));
143
+ console.log(chalk.dim('2. Connect → Drivers → Connection string\n'));
144
+
145
+ const { mongoUrl } = await inquirer.prompt([
146
+ {
147
+ type: 'input',
148
+ name: 'mongoUrl',
149
+ message: 'MongoDB connection string:',
150
+ default: 'mongodb+srv://user:pass@cluster.mongodb.net/',
151
+ validate: (input) =>
152
+ input.startsWith('mongodb://') || input.startsWith('mongodb+srv://') || 'Must be a valid MongoDB URL',
153
+ },
154
+ ]);
155
+
156
+ return { MONGODB_URL: mongoUrl };
157
+ }
158
+
159
+ // [RCF:PROTECTED]
160
+ async function createFirstAgent() {
161
+ console.log(chalk.cyan('\n🤖 Create Your First Agent\n'));
162
+
163
+ const { shouldCreate } = await inquirer.prompt([
164
+ {
165
+ type: 'confirm',
166
+ name: 'shouldCreate',
167
+ message: 'Create a starter agent now?',
168
+ default: true,
169
+ },
170
+ ]);
171
+
172
+ if (!shouldCreate) {
173
+ console.log(chalk.dim(' You can create agents later from the dashboard\n'));
174
+ return;
175
+ }
176
+
177
+ const answers = await inquirer.prompt([
178
+ {
179
+ type: 'list',
180
+ name: 'template',
181
+ message: 'Choose agent template:',
182
+ choices: [
183
+ {
184
+ name: '💬 General Assistant - Helpful chatbot for general tasks',
185
+ value: 'general',
186
+ },
187
+ {
188
+ name: '📊 CRM Assistant - Sales and contact management',
189
+ value: 'crm',
190
+ },
191
+ {
192
+ name: '📧 Email Assistant - Email drafting and responses',
193
+ value: 'email',
194
+ },
195
+ {
196
+ name: '🔍 Research Assistant - Web search and analysis',
197
+ value: 'research',
198
+ },
199
+ ],
200
+ },
201
+ {
202
+ type: 'input',
203
+ name: 'name',
204
+ message: 'Agent name (lowercase, no spaces):',
205
+ default: (answers) => answers.template + '_agent',
206
+ validate: (input) => /^[a-z0-9_]+$/.test(input) || 'Only lowercase letters, numbers, and underscores',
207
+ },
208
+ ]);
209
+
210
+ const templates = {
211
+ general: {
212
+ display_name: 'General Assistant',
213
+ system_prompt: 'You are a helpful AI assistant. Answer questions clearly and concisely.',
214
+ tools: [],
215
+ },
216
+ crm: {
217
+ display_name: 'CRM Assistant',
218
+ system_prompt:
219
+ 'You are a CRM assistant. Help manage contacts, deals, and activities. Always search the CRM before creating new records.',
220
+ tools: ['search_contacts', 'create_contact', 'create_deal', 'add_activity'],
221
+ },
222
+ email: {
223
+ display_name: 'Email Assistant',
224
+ system_prompt: 'You are an email assistant. Draft professional emails and help with correspondence.',
225
+ tools: ['send_email', 'search_contacts'],
226
+ },
227
+ research: {
228
+ display_name: 'Research Assistant',
229
+ system_prompt: 'You are a research assistant. Search for information and provide detailed analysis.',
230
+ tools: ['web_search', 'analyze_image'],
231
+ },
232
+ };
233
+
234
+ const agentConfig = {
235
+ name: answers.name,
236
+ ...templates[answers.template],
237
+ model: 'meta/llama-3.1-70b-instruct',
238
+ memory_enabled: true,
239
+ };
240
+
241
+ console.log(chalk.dim(`\n Agent will be created after services start\n`));
242
+
243
+ return agentConfig;
244
+ }
245
+
246
+ // [RCF:PROTECTED]
247
+ export async function runSetupWizard(targetDir) {
248
+ console.log(chalk.bold.cyan('\n✨ AladdinAI Setup Wizard\n'));
249
+ console.log(chalk.dim('Configure your AI workspace in a few steps.\n'));
250
+
251
+ const { shouldRun } = await inquirer.prompt([
252
+ {
253
+ type: 'confirm',
254
+ name: 'shouldRun',
255
+ message: 'Run setup wizard?',
256
+ default: true,
257
+ },
258
+ ]);
259
+
260
+ if (!shouldRun) {
261
+ console.log(chalk.dim(' You can configure everything later from the dashboard\n'));
262
+ return;
263
+ }
264
+
265
+ // Step 1: Providers
266
+ const providerEnv = await setupProviders();
267
+
268
+ // Step 2: MongoDB
269
+ const mongoEnv = await setupMongoDB();
270
+
271
+ // Step 3: First agent
272
+ const agentConfig = await createFirstAgent();
273
+
274
+ // Update .env file
275
+ if (Object.keys({ ...providerEnv, ...mongoEnv }).length > 0) {
276
+ const spinner = ora('Updating .env with your configuration').start();
277
+ try {
278
+ const fs = await import('fs-extra');
279
+ const path = await import('path');
280
+ const envPath = path.join(targetDir, '.env');
281
+ let envContent = await fs.readFile(envPath, 'utf8');
282
+
283
+ for (const [key, value] of Object.entries({ ...providerEnv, ...mongoEnv })) {
284
+ const regex = new RegExp(`^${key}=.*$`, 'm');
285
+ if (regex.test(envContent)) {
286
+ envContent = envContent.replace(regex, `${key}=${value}`);
287
+ } else {
288
+ envContent += `\n${key}=${value}`;
289
+ }
290
+ }
291
+
292
+ await fs.writeFile(envPath, envContent);
293
+ spinner.succeed('Configuration saved to .env');
294
+ } catch (err) {
295
+ spinner.fail('Failed to update .env');
296
+ console.error(chalk.red(err.message));
297
+ }
298
+ }
299
+
300
+ // Save agent config for post-startup
301
+ if (agentConfig) {
302
+ const fs = await import('fs-extra');
303
+ const path = await import('path');
304
+ await fs.writeJson(path.join(targetDir, '.aladdin-setup.json'), { agentConfig }, { spaces: 2 });
305
+ }
306
+
307
+ console.log(chalk.green('\n✓ Setup wizard complete!\n'));
308
+ }
package/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ // NOTICE: This file is protected under RCF-PL
2
3
 
3
4
  import { Command } from 'commander';
4
5
  import { readFileSync } from 'node:fs';
@@ -14,6 +15,7 @@ import {
14
15
  updateCommand,
15
16
  } from './commands/lifecycle.js';
16
17
  import { doctorCommand } from './commands/doctor.js';
18
+ import { runSetupWizard } from './commands/wizard.js';
17
19
 
18
20
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
19
21
  const pkg = JSON.parse(readFileSync(path.join(__dirname, 'package.json'), 'utf8'));
@@ -74,6 +76,14 @@ program
74
76
  .description('Diagnose setup issues: docker, .env, ports, services')
75
77
  .action(doctorCommand);
76
78
 
79
+ program
80
+ .command('setup')
81
+ .description('Run configuration wizard to set up providers and agents')
82
+ .action(async () => {
83
+ const cwd = process.cwd();
84
+ await runSetupWizard(cwd);
85
+ });
86
+
77
87
  program.parseAsync(process.argv).catch((err) => {
78
88
  console.error(err.shortMessage || err.message);
79
89
  process.exit(1);
package/lib/compose.js CHANGED
@@ -1,3 +1,4 @@
1
+ // NOTICE: This file is protected under RCF-PL
1
2
  // Thin wrapper for invoking `docker compose` from any subcommand.
2
3
 
3
4
  import { execa } from 'execa';
@@ -5,6 +6,7 @@ import { checkDocker } from './prereq.js';
5
6
  import { requireProjectRoot } from './project.js';
6
7
  import chalk from 'chalk';
7
8
 
9
+ // [RCF:PROTECTED]
8
10
  export async function compose(args, { cwd, stdio = 'inherit' } = {}) {
9
11
  const root = cwd || requireProjectRoot();
10
12
  const docker = await checkDocker();
package/lib/prereq.js CHANGED
@@ -1,8 +1,10 @@
1
+ // NOTICE: This file is protected under RCF-PL
1
2
  // Check for required tools on the user's machine.
2
3
 
3
4
  import { execa } from 'execa';
4
5
  import chalk from 'chalk';
5
6
 
7
+ // [RCF:PROTECTED]
6
8
  async function has(cmd, args = ['--version']) {
7
9
  try {
8
10
  const r = await execa(cmd, args, { reject: false });
@@ -12,6 +14,7 @@ async function has(cmd, args = ['--version']) {
12
14
  }
13
15
  }
14
16
 
17
+ // [RCF:PROTECTED]
15
18
  export async function checkDocker() {
16
19
  if (!(await has('docker'))) {
17
20
  return { ok: false, message: 'Docker not found. Install: https://docs.docker.com/get-docker/' };
@@ -30,6 +33,7 @@ export async function checkDocker() {
30
33
  return { ok: true, composeCmd: v2 ? ['docker', ['compose']] : ['docker-compose', []] };
31
34
  }
32
35
 
36
+ // [RCF:PROTECTED]
33
37
  export async function checkGit() {
34
38
  if (!(await has('git'))) {
35
39
  return { ok: false, message: 'git not found. Install: https://git-scm.com/downloads' };
@@ -37,6 +41,7 @@ export async function checkGit() {
37
41
  return { ok: true };
38
42
  }
39
43
 
44
+ // [RCF:PROTECTED]
40
45
  export async function runPrereqChecks({ requireGit = false } = {}) {
41
46
  const docker = await checkDocker();
42
47
  if (!docker.ok) {
package/lib/project.js CHANGED
@@ -1,3 +1,4 @@
1
+ // NOTICE: This file is protected under RCF-PL
1
2
  // Locate the AladdinAI project root from the current working directory.
2
3
  //
3
4
  // Strategy: walk up from cwd looking for docker-compose.yml AND backend/ dir.
@@ -6,6 +7,7 @@
6
7
  import fs from 'fs-extra';
7
8
  import path from 'path';
8
9
 
10
+ // [RCF:PROTECTED]
9
11
  export function findProjectRoot(start = process.cwd()) {
10
12
  let dir = path.resolve(start);
11
13
  const { root } = path.parse(dir);
@@ -18,6 +20,7 @@ export function findProjectRoot(start = process.cwd()) {
18
20
  }
19
21
  }
20
22
 
23
+ // [RCF:PROTECTED]
21
24
  export function requireProjectRoot() {
22
25
  const root = findProjectRoot();
23
26
  if (!root) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aladdin-ai",
3
- "version": "2.0.1",
3
+ "version": "2.2.1",
4
4
  "description": "Self-hosted AI workspace — agents, memory, CRM, channels. One command to install and run.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -36,6 +36,10 @@
36
36
  "type": "git",
37
37
  "url": "git+https://github.com/aliyevaladddin/AladdinAI.git"
38
38
  },
39
+ "funding": {
40
+ "type": "opencollective",
41
+ "url": "https://opencollective.com/aladdinai"
42
+ },
39
43
  "bugs": {
40
44
  "url": "https://github.com/aliyevaladddin/AladdinAI/issues"
41
45
  },