oneurai-cli 1.0.2 → 1.0.4

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/bin/index.js CHANGED
@@ -8,29 +8,32 @@ import Conf from 'conf';
8
8
  import ora from 'ora';
9
9
  import fs from 'fs';
10
10
  import path from 'path';
11
- import archiver from 'archiver';
11
+ import archiver from 'archiver';
12
12
  import FormData from 'form-data';
13
- import { execSync } from 'child_process'; // 👈 ضف هذا السطر
14
- import { createRequire } from 'module'; // 👈 وهذا عشان نقرأ إصدارنا الحالي
15
- const require = createRequire(import.meta.url);
16
- const packageJson = require('../package.json'); // قراءة ملف package
13
+ import { exec, execSync } from 'child_process';
14
+ import { createRequire } from 'module';
17
15
  import AdmZip from 'adm-zip';
16
+ import chokidar from 'chokidar'; // 👈 للمراقبة
17
+
18
+ const require = createRequire(import.meta.url);
19
+ const packageJson = require('../package.json');
18
20
 
19
21
  const program = new Command();
20
22
  const config = new Conf({ projectName: 'one-cli' });
21
23
 
22
- const API_URL = 'https://oneurai.com/api';
24
+ const API_URL = 'https://oneurai.com/api';
25
+ const VERSION_URL = 'https://oneurai.com/cli/version.json';
26
+
27
+ // --- دوال مساعدة ---
23
28
 
24
- // --- دالة مساعدة لجلب بيانات المستخدم ---
25
29
  async function ensureUsername(token) {
26
30
  let username = config.get('username');
27
31
  if (username) return username;
28
-
29
32
  try {
30
33
  const response = await axios.get(`${API_URL}/user`, {
31
34
  headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' }
32
35
  });
33
- username = response.data.username || response.data.name;
36
+ username = response.data.username || response.data.name;
34
37
  config.set('username', username);
35
38
  return username;
36
39
  } catch (error) {
@@ -38,317 +41,355 @@ async function ensureUsername(token) {
38
41
  }
39
42
  }
40
43
 
41
- program
42
- .name('one')
43
- .description('Oneurai CLI - Official Tool')
44
- .version('1.0.0');
45
-
46
- // --- HELLO ---
47
- program
48
- .command('hello')
49
- .description('Check connection')
50
- .action(async () => {
51
- console.log(chalk.green.bold('Welcome to Oneurai CLI! 🚀'));
52
- const token = config.get('auth_token');
53
-
54
- if (!token) {
55
- console.log(chalk.yellow('Not logged in. Run: one login'));
56
- return;
57
- }
44
+ // دالة الرفع (Deploy) - فصلناها عشان نستخدمها في push وفي watch
45
+ async function deployProject(token, projectConfig, spinner = null) {
46
+ if (!spinner) spinner = ora('Preparing deploy...').start();
58
47
 
59
- const spinner = ora('Checking connection...').start();
60
- try {
61
- config.delete('username');
62
- const username = await ensureUsername(token);
63
- spinner.succeed(chalk.cyan(`Connected as: ${chalk.bold(username)} ✅`));
64
- } catch (error) {
65
- spinner.fail(chalk.red('Connection failed.'));
66
- }
67
- });
68
-
69
- // --- LOGIN ---
70
- program
71
- .command('login')
72
- .description('Authenticate')
73
- .action(async () => {
74
- console.log(chalk.blue.bold('🔐 Authenticate with Oneurai.com'));
75
-
76
- const answers = await inquirer.prompt([
77
- {
78
- type: 'password',
79
- name: 'token',
80
- message: 'Paste your Access Token:',
81
- validate: input => input.length > 5 || 'Invalid token.'
82
- }
83
- ]);
48
+ const output = fs.createWriteStream('project.zip');
49
+ const archive = archiver('zip', { zlib: { level: 9 } });
84
50
 
85
- const spinner = ora('Verifying...').start();
51
+ return new Promise((resolve, reject) => {
52
+ output.on('close', async () => {
53
+ spinner.text = 'Uploading to Oneurai...';
54
+ try {
55
+ const form = new FormData();
56
+ form.append('project_name', projectConfig.name);
57
+ form.append('file', fs.createReadStream('project.zip'));
58
+
59
+ await axios.post(`${API_URL}/deploy`, form, {
60
+ headers: { ...form.getHeaders(), 'Authorization': `Bearer ${token}` },
61
+ maxContentLength: Infinity,
62
+ maxBodyLength: Infinity
63
+ });
64
+
65
+ fs.unlinkSync('project.zip');
66
+ spinner.succeed(chalk.green(`Deployed: ${projectConfig.name} at ${new Date().toLocaleTimeString()} 🚀`));
67
+ resolve(true);
68
+
69
+ } catch (error) {
70
+ if (fs.existsSync('project.zip')) fs.unlinkSync('project.zip');
71
+ spinner.fail(chalk.red('Deployment failed.'));
72
+ if (error.response) console.error(chalk.red(error.response.data.message));
73
+ resolve(false); // Resolve false instead of reject to keep watch alive
74
+ }
75
+ });
76
+
77
+ archive.on('error', (err) => {
78
+ spinner.fail('Archiving failed');
79
+ reject(err);
80
+ });
81
+
82
+ archive.pipe(output);
86
83
 
84
+ const ignoreList = projectConfig.ignore || ['node_modules/**', '.git/**'];
85
+ if (!ignoreList.includes('oneurai.json')) ignoreList.push('oneurai.json');
86
+
87
+ archive.glob('**/*', { cwd: process.cwd(), ignore: ignoreList });
88
+ archive.finalize();
89
+ });
90
+ }
91
+
92
+ // --- إعدادات البرنامج ---
93
+ program.name('one').description('Oneurai CLI - Official Tool').version(packageJson.version);
94
+
95
+ // --- Middleware: Check Update ---
96
+ program.hook('preAction', async (thisCommand, actionCommand) => {
97
+ if (['update', 'help'].includes(actionCommand.name())) return;
87
98
  try {
88
- const response = await axios.get(`${API_URL}/user`, {
89
- headers: {
90
- 'Authorization': `Bearer ${answers.token}`,
91
- 'Accept': 'application/json'
99
+ const response = await axios.get(VERSION_URL, { timeout: 1500 });
100
+ if (response.data.version !== packageJson.version) {
101
+ console.log(chalk.bgRed.white.bold('\n 🚨 CRITICAL UPDATE REQUIRED '));
102
+ console.log(chalk.yellow(`Current: ${packageJson.version} | Latest: ${response.data.version}`));
103
+ console.log(chalk.cyan('👉 Run: one update\n'));
104
+ process.exit(1);
92
105
  }
93
- });
106
+ } catch (e) { }
107
+ });
94
108
 
95
- config.set('auth_token', answers.token);
96
-
97
- const username = response.data.username || response.data.name;
98
- config.set('username', username);
99
-
100
- spinner.succeed(chalk.green(`Success! Logged in as ${chalk.bold(username)}`));
109
+ // ================= أوااااامر =================
101
110
 
102
- } catch (error) {
103
- spinner.fail(chalk.red('Login failed.'));
104
- }
105
- });
111
+ // 1. HELLO
112
+ program.command('hello').description('Check connection').action(async () => {
113
+ const token = config.get('auth_token');
114
+ if (!token) return console.log(chalk.yellow('Not logged in.'));
115
+ const spinner = ora('Connecting...').start();
116
+ try {
117
+ config.delete('username');
118
+ const username = await ensureUsername(token);
119
+ spinner.succeed(chalk.cyan(`Connected as: ${chalk.bold(username)} ✅`));
120
+ } catch (error) { spinner.fail(chalk.red('Connection failed.')); }
121
+ });
122
+
123
+ // 2. LOGIN
124
+ program.command('login').description('Authenticate').action(async () => {
125
+ const answers = await inquirer.prompt([{ type: 'password', name: 'token', message: 'Access Token:' }]);
126
+ const spinner = ora('Verifying...').start();
127
+ try {
128
+ const res = await axios.get(`${API_URL}/user`, { headers: { Authorization: `Bearer ${answers.token}` } });
129
+ config.set('auth_token', answers.token);
130
+ config.set('username', res.data.username || res.data.name);
131
+ spinner.succeed(chalk.green(`Logged in as ${res.data.username}`));
132
+ } catch (error) { spinner.fail(chalk.red('Invalid token.')); }
133
+ });
106
134
 
107
- // --- LOGOUT ---
135
+ // 3. LOGOUT
108
136
  program.command('logout').action(() => {
109
- config.delete('auth_token');
110
- config.delete('username');
137
+ config.clear();
111
138
  console.log(chalk.green('Logged out.'));
112
139
  });
113
140
 
114
- // --- INIT (المعدل والمضمون) ---
115
- program
116
- .command('init')
117
- .description('Create project')
118
- .action(async () => {
141
+ // 4. INIT
142
+ program.command('init').description('Create project').action(async () => {
119
143
  const token = config.get('auth_token');
120
- if (!token) return console.log(chalk.red('Please login first.'));
121
-
122
- if (fs.existsSync('./oneurai.json')) {
123
- return console.log(chalk.yellow('⚠️ oneurai.json already exists.'));
124
- }
125
-
126
- const username = await ensureUsername(token);
127
-
128
- console.log(chalk.blue.bold('⚡ Create New Oneurai Project'));
144
+ if (!token) return console.log(chalk.red('Login first.'));
145
+ if (fs.existsSync('./oneurai.json')) return console.log(chalk.yellow('⚠️ Project already initialized.'));
129
146
 
130
147
  const answers = await inquirer.prompt([
131
- {
132
- type: 'input',
133
- name: 'title',
134
- message: 'Project Title:',
135
- validate: input => input.length > 2 || 'Min 2 chars.'
136
- },
137
- {
138
- type: 'input',
139
- name: 'slug',
140
- message: 'Project Slug:',
141
- default: (answers) => answers.title.toLowerCase().replace(/ /g, '-'),
142
- validate: input => /^[a-z0-9-_]+$/.test(input) || 'Use letters, numbers, dashes.'
143
- },
144
- {
145
- type: 'confirm',
146
- name: 'is_public',
147
- message: 'Is this project Public (Open Source)?',
148
- default: true
149
- }
148
+ { name: 'title', message: 'Project Title:' },
149
+ { name: 'slug', message: 'Project Slug:', default: a => a.title.toLowerCase().replace(/ /g, '-') },
150
+ { type: 'confirm', name: 'is_public', message: 'Is Public?', default: true }
150
151
  ]);
151
152
 
152
153
  const spinner = ora('Creating...').start();
153
-
154
154
  try {
155
- const response = await axios.post(`${API_URL}/projects/create`, {
156
- title: answers.title,
157
- slug: answers.slug,
158
- is_public: answers.is_public
159
- }, {
160
- headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' }
161
- });
162
-
163
- const projectConfig = {
164
- name: response.data.project.slug,
165
- id: response.data.project.id,
155
+ const res = await axios.post(`${API_URL}/projects/create`, answers, { headers: { Authorization: `Bearer ${token}` } });
156
+ fs.writeFileSync('./oneurai.json', JSON.stringify({
157
+ name: res.data.project.slug,
158
+ id: res.data.project.id,
166
159
  created_at: new Date().toISOString(),
167
- // تم إضافة oneurai.json للقائمة الافتراضية هنا
168
- ignore: ['node_modules', '.git', '.env', 'storage', 'vendor', '*.zip', 'oneurai.json']
169
- };
170
-
171
- fs.writeFileSync('./oneurai.json', JSON.stringify(projectConfig, null, 2));
172
-
173
- spinner.succeed(chalk.green(`Created: ${response.data.project.title}`));
174
-
175
- console.log(chalk.cyan(`🔗 Link: https://oneurai.com/project/${username}/${response.data.project.slug}`));
176
- console.log(chalk.gray('Run "one push" to deploy.'));
177
-
160
+ ignore: ['node_modules', '.git', '.env', 'storage', '*.zip', 'oneurai.json']
161
+ }, null, 2));
162
+ spinner.succeed(chalk.green(`Created: ${res.data.project.title}`));
178
163
  } catch (error) {
179
164
  spinner.fail(chalk.red('Failed.'));
180
- if (error.response && error.response.data.errors) {
181
- console.error(chalk.red(JSON.stringify(error.response.data.errors)));
182
- } else if (error.response) {
183
- console.error(chalk.red(JSON.stringify(error.response.data)));
184
- } else {
185
- console.error(chalk.red(error.message));
186
- }
165
+ if (error.response) console.error(error.response.data);
187
166
  }
188
- });
167
+ });
189
168
 
190
- // --- PUSH ---
191
- program
192
- .command('push')
193
- .description('Deploy files')
194
- .action(async () => {
169
+ // 5. PUSH
170
+ program.command('push').description('Deploy files').action(async () => {
195
171
  const token = config.get('auth_token');
196
- if (!token) return console.log(chalk.red('Please login first.'));
197
-
172
+ if (!token) return console.log(chalk.red('Login first.'));
198
173
  if (!fs.existsSync('./oneurai.json')) return console.log(chalk.red('Run: one init'));
199
-
200
- const username = await ensureUsername(token);
201
174
  const projectConfig = JSON.parse(fs.readFileSync('./oneurai.json', 'utf-8'));
202
- console.log(chalk.blue.bold(`🚀 Deploying ${projectConfig.name}...`));
203
-
204
- const spinner = ora('Compressing...').start();
175
+ await deployProject(token, projectConfig);
176
+ });
205
177
 
206
- const output = fs.createWriteStream('project.zip');
207
- const archive = archiver('zip', { zlib: { level: 9 } });
178
+ // 6. WATCH (Magic ✨)
179
+ // 6. WATCH (Magic ✨) - Fixed Logic
180
+ program.command('watch').description('Auto-deploy on save').action(async () => {
181
+ const token = config.get('auth_token');
182
+ if (!token) return console.log(chalk.red('Login first.'));
183
+ if (!fs.existsSync('./oneurai.json')) return console.log(chalk.red('Run: one init'));
208
184
 
209
- output.on('close', async () => {
210
- spinner.text = 'Uploading...';
185
+ const projectConfig = JSON.parse(fs.readFileSync('./oneurai.json', 'utf-8'));
186
+ console.clear();
187
+ console.log(chalk.blue.bold(`👀 Watching ${projectConfig.name} for changes...`));
188
+ console.log(chalk.gray('Press Ctrl+C to stop.'));
189
+
190
+ let debounceTimer;
191
+
192
+ // 1. تحديد قائمة التجاهل بدقة
193
+ // ندمج قائمة المستخدم مع القائمة الإجبارية
194
+ const userIgnores = projectConfig.ignore || [];
195
+ const systemIgnores = [
196
+ 'node_modules',
197
+ 'node_modules/**',
198
+ '.git',
199
+ '.git/**',
200
+ 'project.zip',
201
+ '*.zip',
202
+ 'oneurai.json',
203
+ '*.log',
204
+ '.DS_Store',
211
205
 
212
- try {
213
- const form = new FormData();
214
- form.append('project_name', projectConfig.name);
215
- form.append('file', fs.createReadStream('project.zip'));
216
-
217
- await axios.post(`${API_URL}/deploy`, form, {
218
- headers: { ...form.getHeaders(), 'Authorization': `Bearer ${token}` },
219
- maxContentLength: Infinity,
220
- maxBodyLength: Infinity
221
- });
222
-
223
- fs.unlinkSync('project.zip');
224
- spinner.succeed(chalk.green('Deployed successfully! ☁️'));
225
-
226
- console.log(chalk.cyan(`\n🔗 View project: https://oneurai.com/project/${username}/${projectConfig.name}`));
227
-
228
- } catch (error) {
229
- if (fs.existsSync('project.zip')) fs.unlinkSync('project.zip');
230
- spinner.fail(chalk.red('Deployment failed ❌'));
231
- if(error.response) console.error(chalk.red(error.response.data.message));
206
+ // 🔥🔥🔥 القائمة السوداء لأسماء ويندوز المؤقتة 🔥🔥🔥
207
+ '**/New Text Document.txt', // الاسم الافتراضي
208
+ '**/New Text Document*.txt', // لو فيه أرقام (2)
209
+ '**/New folder', // المجلدات الجديدة
210
+ ];
211
+
212
+ const combinedIgnores = [...new Set([...userIgnores, ...systemIgnores])];
213
+
214
+ const watcher = chokidar.watch('.', {
215
+ ignored: combinedIgnores,
216
+ persistent: true,
217
+ ignoreInitial: true,
218
+ awaitWriteFinish: {
219
+ stabilityThreshold: 2000, // زدنا وقت الاستقرار لثانيتين (عشان يمديك تخلص كتابة)
220
+ pollInterval: 100
232
221
  }
233
222
  });
234
223
 
235
- archive.on('error', (err) => { throw err; });
236
- archive.pipe(output);
224
+ watcher.on('all', (event, filePath) => {
225
+ // حماية إضافية
226
+ if (filePath.includes('project.zip') || filePath.includes('oneurai.json')) return;
237
227
 
238
- // التعديل هنا: دمج قائمة التجاهل مع oneurai.json وإجبار استبعاده
239
- const ignoreList = projectConfig.ignore || ['node_modules/**', '.git/**'];
240
-
241
- // نضيف oneurai.json للقائمة إذا لم يكن موجوداً
242
- if (!ignoreList.includes('oneurai.json')) {
243
- ignoreList.push('oneurai.json');
244
- }
228
+ // تجاهل أي ملف اسمه New Text Document حتى لو عدى الفلتر الأول
229
+ if (filePath.includes('New Text Document')) return;
245
230
 
246
- archive.glob('**/*', {
247
- cwd: process.cwd(),
248
- ignore: ignoreList
231
+ clearTimeout(debounceTimer);
232
+ process.stdout.write(chalk.yellow(`\r📝 Change detected in ${filePath}... waiting`));
233
+
234
+ // 🔥 زدنا الوقت إلى 3 ثواني (3000ms)
235
+ // هذا يعطيك وقت كافي تكتب الاسم وتضغط Enter قبل ما يقرر يرفع
236
+ debounceTimer = setTimeout(async () => {
237
+ console.log();
238
+ await deployProject(token, projectConfig);
239
+ console.log(chalk.blue.bold(`👀 Watching...`));
240
+ }, 3000);
249
241
  });
242
+ });
250
243
 
251
- archive.finalize();
252
- });
253
-
254
- program
255
- .command('update')
256
- .description('Update CLI to the latest version')
257
- .action(async () => {
258
- console.log(chalk.blue.bold('🔄 Checking for updates...'));
259
- const spinner = ora('Contacting server...').start();
244
+ // 7. STATUS 📊
245
+ program.command('status').description('Show project details').action(async () => {
246
+ const token = config.get('auth_token');
247
+ if (!token) return console.log(chalk.red('Login first.'));
248
+ if (!fs.existsSync('./oneurai.json')) return console.log(chalk.red('Run: one init'));
260
249
 
250
+ const spinner = ora('Fetching status...').start();
261
251
  try {
262
- // 1. جلب رقم الإصدار الأحدث من السيرفر
263
- // تأكد أنك أنشأت الملف في سيرفرك في المسار public/cli/version.json
264
- const response = await axios.get('https://oneurai.com/cli/version.json');
265
- const latestVersion = response.data.version;
266
- const currentVersion = packageJson.version;
267
-
268
- if (latestVersion === currentVersion) {
269
- spinner.succeed(chalk.green('You are already on the latest version! ✅'));
270
- return;
271
- }
272
-
273
- spinner.info(chalk.yellow(`New version found: ${latestVersion} (Current: ${currentVersion})`));
274
- spinner.start('Installing update...');
275
-
276
- // 2. تنفيذ أمر التحديث السحري
277
- // هذا الأمر يخلي npm يحمل الملف من سيرفرك ويثبته فوق النسخة الحالية
278
- execSync('npm install -g https://oneurai.com/cli/one-latest.tgz', { stdio: 'inherit' });
252
+ const projectConfig = JSON.parse(fs.readFileSync('./oneurai.json', 'utf-8'));
253
+ const res = await axios.get(`${API_URL}/projects/${projectConfig.name}/status`, {
254
+ headers: { Authorization: `Bearer ${token}` }
255
+ });
279
256
 
280
- spinner.succeed(chalk.green(`Successfully updated to v${latestVersion} 🚀`));
281
- console.log(chalk.gray('Please restart your terminal to apply changes.'));
257
+ spinner.stop();
258
+ const data = res.data;
259
+ console.log(chalk.bold('\n📊 Project Status:\n'));
260
+ console.log(` ${chalk.blue('Name:')} ${data.title}`);
261
+ console.log(` ${chalk.blue('Slug:')} ${data.slug}`);
262
+ console.log(` ${chalk.blue('Visiblity:')} ${data.visibility === 'Public' ? chalk.green('Public') : chalk.red('Private')}`);
263
+ console.log(` ${chalk.blue('Size:')} ${data.total_size}`);
264
+ console.log(` ${chalk.blue('Files:')} ${data.files_count}`);
265
+ console.log(` ${chalk.blue('Updated:')} ${data.last_deployed}`);
266
+ console.log(`\n 🔗 ${chalk.gray(`https://oneurai.com/project/${await ensureUsername(token)}/${data.slug}`)}\n`);
282
267
 
283
268
  } catch (error) {
284
- spinner.fail(chalk.red('Update failed.'));
285
- if (error.response && error.response.status === 404) {
286
- console.error(chalk.red('Update server not found (404). Check files on server.'));
287
- } else {
288
- console.error(chalk.red(error.message));
289
- }
269
+ spinner.fail(chalk.red('Failed to get status.'));
290
270
  }
291
- });
271
+ });
292
272
 
293
- program
294
- .command('get <slug>')
295
- .description('Download (Clone) a project from Oneurai')
296
- .action(async (slug) => {
273
+ // 8. LIST 📑
274
+ program.command('list').alias('ls').description('List all your projects').action(async () => {
297
275
  const token = config.get('auth_token');
298
- if (!token) return console.log(chalk.red('Please login first.'));
299
-
300
- // التحقق هل المجلد موجود مسبقاً؟
301
- if (fs.existsSync(slug)) {
302
- return console.log(chalk.red(`❌ Directory "${slug}" already exists. Please delete it or choose another location.`));
303
- }
304
-
305
- const spinner = ora(`Fetching project "${slug}"...`).start();
276
+ if (!token) return console.log(chalk.red('Login first.'));
306
277
 
278
+ const spinner = ora('Fetching projects...').start();
307
279
  try {
308
- // 1. طلب الملف المضغوط
309
- const response = await axios({
310
- method: 'get',
311
- url: `${API_URL}/projects/${slug}/download`,
312
- headers: { Authorization: `Bearer ${token}` },
313
- responseType: 'arraybuffer'
280
+ const res = await axios.get(`${API_URL}/projects`, {
281
+ headers: { Authorization: `Bearer ${token}` }
314
282
  });
283
+ spinner.stop();
284
+
285
+ if (res.data.length === 0) return console.log(chalk.yellow('No projects found. Run "one init" to start!'));
286
+
287
+ // عرض تفاعلي
288
+ const answer = await inquirer.prompt([
289
+ {
290
+ type: 'list',
291
+ name: 'project',
292
+ message: 'Select a project to clone (or Ctrl+C to exit):',
293
+ choices: res.data,
294
+ pageSize: 10
295
+ }
296
+ ]);
297
+
298
+ // إذا اختار مشروع، ننفذ أمر get عليه
299
+ // نستدعي دالة get برمجياً (أو نطلب منه يكتب الأمر)
300
+ console.log(chalk.cyan(`\nTo clone this project, run:\n`));
301
+ console.log(chalk.white.bgBlack(` one get ${answer.project} `));
302
+ console.log();
315
303
 
316
- spinner.text = 'Unzipping files...';
304
+ } catch (error) {
305
+ spinner.fail(chalk.red('Failed to list projects.'));
306
+ }
307
+ });
317
308
 
318
- // 2. إنشاء المجلد
319
- fs.mkdirSync(slug);
309
+ // 9. DOCTOR 👨‍⚕️
310
+ program.command('doctor').description('Diagnose CLI issues').action(async () => {
311
+ console.log(chalk.bold('🩺 Oneurai CLI Doctor\n'));
320
312
 
321
- // 3. حفظ الملف وفك الضغط
322
- const zipPath = path.join(slug, 'temp_project.zip');
323
- fs.writeFileSync(zipPath, response.data);
313
+ // Check 1: CLI Version
314
+ console.log(`${chalk.green('✔')} CLI Version: ${packageJson.version}`);
324
315
 
325
- const zip = new AdmZip(zipPath);
326
- zip.extractAllTo(slug, true);
316
+ // Check 2: Config File
317
+ if (fs.existsSync('./oneurai.json')) {
318
+ try {
319
+ JSON.parse(fs.readFileSync('./oneurai.json', 'utf-8'));
320
+ console.log(`${chalk.green('✔')} oneurai.json: Valid`);
321
+ } catch (e) { console.log(`${chalk.red('✘')} oneurai.json: Corrupted JSON`); }
322
+ } else {
323
+ console.log(`${chalk.yellow('⚠')} oneurai.json: Not found (Run inside project folder)`);
324
+ }
327
325
 
328
- // 4. تنظيف
329
- fs.unlinkSync(zipPath);
326
+ // Check 3: API Connection
327
+ const spinner = ora('Checking API Connection...').start();
328
+ try {
329
+ await axios.get(`${API_URL.replace('/api', '')}`); // Ping Home
330
+ spinner.succeed(`${chalk.green('✔')} API Status: Online`);
331
+ } catch (e) {
332
+ spinner.fail(`${chalk.red('✘')} API Status: Unreachable`);
333
+ }
330
334
 
331
- // حذفنا كود إنشاء oneurai.json من هنا
335
+ // Check 4: Auth Status
336
+ const token = config.get('auth_token');
337
+ if (token) {
338
+ try {
339
+ await axios.get(`${API_URL}/user`, { headers: { Authorization: `Bearer ${token}` } });
340
+ console.log(`${chalk.green('✔')} Authentication: Valid Token`);
341
+ } catch (e) {
342
+ console.log(`${chalk.red('✘')} Authentication: Invalid/Expired Token (Run 'one login')`);
343
+ }
344
+ } else {
345
+ console.log(`${chalk.yellow('⚠')} Authentication: Not Logged In`);
346
+ }
332
347
 
333
- spinner.succeed(chalk.green(`Successfully downloaded "${slug}"! 📦`));
334
-
335
- // تعليمات واضحة للمستخدم
336
- console.log(chalk.cyan(`\nNext steps:`));
337
- console.log(chalk.white(` 1. cd ${slug}`));
338
- console.log(chalk.white(` 2. one init (to link this folder)`)); // 👈 تذكير بالـ init
348
+ console.log(chalk.gray('\nDiagnosis complete.'));
349
+ });
339
350
 
340
- } catch (error) {
341
- if (fs.existsSync(slug)) {
342
- fs.rmSync(slug, { recursive: true, force: true });
343
- }
351
+ // 10. UPDATE (القديم)
352
+ program.command('update').description('Update CLI').action(() => {
353
+ try {
354
+ console.log(chalk.blue('Updating...'));
355
+ execSync('npm install -g https://oneurai.com/cli/one-latest.tgz', { stdio: 'inherit' });
356
+ } catch (e) { console.error(e.message); }
357
+ });
344
358
 
345
- spinner.fail(chalk.red('Download failed.'));
346
-
347
- if (error.response && error.response.status === 404) {
348
- console.error(chalk.red('❌ Project not found or access denied.'));
349
- } else {
350
- console.error(chalk.red(error.message));
351
- }
359
+ // 11. GET (القديم)
360
+ program.command('get <slug>').description('Clone project').action(async (slug) => {
361
+ // ... (نفس كود get السابق بالضبط - عشان المساحة ما كررته هنا بس هو موجود ضمنياً)
362
+ // إذا نسخته انسخه من الكود السابق، أو تبيني أعيد كتابته كامل؟
363
+ // سأضيفه باختصار هنا لكي يكون الملف كاملاً
364
+ const token = config.get('auth_token');
365
+ if (!token) return console.log(chalk.red('Login first.'));
366
+ if (fs.existsSync(slug)) return console.log(chalk.red(`Directory "${slug}" exists.`));
367
+
368
+ const spinner = ora('Downloading...').start();
369
+ try {
370
+ const res = await axios({ url: `${API_URL}/projects/${slug}/download`, headers: { Authorization: `Bearer ${token}` }, responseType: 'arraybuffer' });
371
+ fs.mkdirSync(slug);
372
+ fs.writeFileSync(path.join(slug, 'temp.zip'), res.data);
373
+ const zip = new AdmZip(path.join(slug, 'temp.zip'));
374
+ zip.extractAllTo(slug, true);
375
+ fs.unlinkSync(path.join(slug, 'temp.zip'));
376
+ spinner.succeed(chalk.green('Cloned successfully!'));
377
+ console.log(`Run: cd ${slug} && one init`);
378
+ } catch (e) {
379
+ if (fs.existsSync(slug)) fs.rmSync(slug, { recursive: true });
380
+ spinner.fail('Failed.');
352
381
  }
353
- });
382
+ });
383
+
384
+ // 12. OPEN (القديم)
385
+ program.command('open').description('Open in browser').action(async () => {
386
+ if (!fs.existsSync('./oneurai.json')) return console.log(chalk.red('Run inside project.'));
387
+ const token = config.get('auth_token');
388
+ const slug = JSON.parse(fs.readFileSync('./oneurai.json')).name;
389
+ const url = `https://oneurai.com/project/${await ensureUsername(token)}/${slug}`;
390
+ console.log(chalk.green(`Opening ${url}`));
391
+ const cmd = process.platform === 'win32' ? `start "" "${url}"` : (process.platform === 'darwin' ? `open "${url}"` : `xdg-open "${url}"`);
392
+ exec(cmd);
393
+ });
394
+
354
395
  program.parse(process.argv);
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oneurai-cli",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Oneurai CLI Tool",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -18,6 +18,7 @@
18
18
  "archiver": "^7.0.1",
19
19
  "axios": "^1.13.2",
20
20
  "chalk": "^5.6.2",
21
+ "chokidar": "^5.0.0",
21
22
  "commander": "^14.0.2",
22
23
  "conf": "^15.0.2",
23
24
  "form-data": "^4.0.5",