@l4yercak3/cli 1.2.21 → 1.3.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.
@@ -294,6 +294,155 @@ class BackendClient {
294
294
  async syncApplication(applicationId, syncData) {
295
295
  return await this.request('POST', `/api/v1/cli/applications/${applicationId}/sync`, syncData);
296
296
  }
297
+
298
+ // ============================================
299
+ // Activity Protocol / Page Detection API
300
+ // ============================================
301
+
302
+ /**
303
+ * Bulk register pages for an application
304
+ * @param {string} applicationId - The application ID
305
+ * @param {Array<{path: string, name: string, pageType?: string}>} pages - Pages to register
306
+ * @returns {Promise<{success: boolean, results: Array, total: number, created: number, updated: number}>}
307
+ */
308
+ async bulkRegisterPages(applicationId, pages) {
309
+ return await this.request('POST', '/api/v1/activity/pages/bulk', {
310
+ applicationId,
311
+ pages,
312
+ });
313
+ }
314
+
315
+ /**
316
+ * Register a single page
317
+ * @param {string} applicationId - The application ID
318
+ * @param {object} pageData - Page data
319
+ * @returns {Promise<{success: boolean, pageId: string, created: boolean}>}
320
+ */
321
+ async registerPage(applicationId, pageData) {
322
+ return await this.request('POST', '/api/v1/activity/pages', {
323
+ applicationId,
324
+ ...pageData,
325
+ });
326
+ }
327
+
328
+ /**
329
+ * Get all pages for an application
330
+ * @param {string} applicationId - The application ID
331
+ * @param {object} options - Query options (status)
332
+ * @returns {Promise<{success: boolean, pages: Array, total: number}>}
333
+ */
334
+ async getApplicationPages(applicationId, options = {}) {
335
+ let url = `/api/v1/activity/pages?applicationId=${applicationId}`;
336
+ if (options.status) {
337
+ url += `&status=${options.status}`;
338
+ }
339
+ return await this.request('GET', url);
340
+ }
341
+
342
+ /**
343
+ * Log an activity event
344
+ * @param {string} applicationId - The application ID
345
+ * @param {object} eventData - Event data
346
+ * @returns {Promise<{success: boolean, eventId: string, timestamp: number}>}
347
+ */
348
+ async logActivityEvent(applicationId, eventData) {
349
+ return await this.request('POST', '/api/v1/activity/events', {
350
+ applicationId,
351
+ ...eventData,
352
+ });
353
+ }
354
+
355
+ /**
356
+ * Get activity events for an application
357
+ * @param {string} applicationId - The application ID
358
+ * @param {object} options - Query options (severity, category, limit)
359
+ * @returns {Promise<{success: boolean, events: Array, hasMore: boolean, nextCursor: string|null}>}
360
+ */
361
+ async getActivityEvents(applicationId, options = {}) {
362
+ let url = `/api/v1/activity/events?applicationId=${applicationId}`;
363
+ if (options.severity) {
364
+ url += `&severity=${options.severity}`;
365
+ }
366
+ if (options.category) {
367
+ url += `&category=${options.category}`;
368
+ }
369
+ if (options.limit) {
370
+ url += `&limit=${options.limit}`;
371
+ }
372
+ return await this.request('GET', url);
373
+ }
374
+
375
+ /**
376
+ * Get activity statistics for an application
377
+ * @param {string} applicationId - The application ID
378
+ * @param {string} timeRange - Time range ('1h', '24h', '7d')
379
+ * @returns {Promise<{success: boolean, stats: object}>}
380
+ */
381
+ async getActivityStats(applicationId, timeRange = '24h') {
382
+ return await this.request(
383
+ 'GET',
384
+ `/api/v1/activity/stats?applicationId=${applicationId}&timeRange=${timeRange}`
385
+ );
386
+ }
387
+
388
+ /**
389
+ * Get activity settings for an application
390
+ * @param {string} applicationId - The application ID
391
+ * @returns {Promise<object>}
392
+ */
393
+ async getActivitySettings(applicationId) {
394
+ return await this.request('GET', `/api/v1/activity/settings?applicationId=${applicationId}`);
395
+ }
396
+
397
+ /**
398
+ * Update activity settings for an application
399
+ * @param {string} applicationId - The application ID
400
+ * @param {object} settings - Settings to update
401
+ * @returns {Promise<object>}
402
+ */
403
+ async updateActivitySettings(applicationId, settings) {
404
+ return await this.request('PATCH', '/api/v1/activity/settings', {
405
+ applicationId,
406
+ ...settings,
407
+ });
408
+ }
409
+
410
+ // ============================================
411
+ // Repository Connection API
412
+ // ============================================
413
+
414
+ /**
415
+ * Connect a GitHub repository to a connected application
416
+ * Triggers repo analysis on the platform side
417
+ * @param {string} applicationId - The application ID
418
+ * @param {object} repoData - Repository connection data
419
+ * @param {string} repoData.repoUrl - Full GitHub repo URL
420
+ * @param {string} repoData.branch - Branch to analyze (e.g., 'main')
421
+ * @param {string} [repoData.githubConnectionId] - Optional GitHub OAuth connection ID
422
+ * @returns {Promise<object>}
423
+ */
424
+ async connectRepo(applicationId, repoData) {
425
+ return await this.request(
426
+ 'POST',
427
+ `/api/v1/cli/applications/${applicationId}/connect-repo`,
428
+ repoData
429
+ );
430
+ }
431
+
432
+ /**
433
+ * Sync project manifest data to the platform
434
+ * Pushes the .l4yercak3.json content for platform-side analysis
435
+ * @param {string} applicationId - The application ID
436
+ * @param {object} manifest - The manifest object from .l4yercak3.json
437
+ * @returns {Promise<object>}
438
+ */
439
+ async syncManifest(applicationId, manifest) {
440
+ return await this.request(
441
+ 'POST',
442
+ `/api/v1/cli/applications/${applicationId}/sync-manifest`,
443
+ manifest
444
+ );
445
+ }
297
446
  }
298
447
 
299
448
  module.exports = new BackendClient();
@@ -0,0 +1,243 @@
1
+ /**
2
+ * Connect Command
3
+ * Connects the project to the L4YERCAK3 platform by:
4
+ * 1. Loading the .l4yercak3.json manifest
5
+ * 2. Registering the app (if not already registered)
6
+ * 3. Connecting the GitHub repository for platform-side analysis
7
+ * 4. Writing env vars for platform integration
8
+ */
9
+
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+ const configManager = require('../config/config-manager');
13
+ const backendClient = require('../api/backend-client');
14
+ const manifestGenerator = require('../generators/manifest-generator');
15
+ const githubDetector = require('../detectors/github-detector');
16
+ const { generateProjectPathHash } = require('../utils/file-utils');
17
+ const {
18
+ requireAuth,
19
+ selectOrganization,
20
+ generateNewApiKey,
21
+ } = require('../utils/init-helpers');
22
+ const inquirer = require('inquirer');
23
+ const chalk = require('chalk');
24
+ const pkg = require('../../package.json');
25
+
26
+ async function handleConnect() {
27
+ requireAuth(configManager);
28
+
29
+ console.log(chalk.cyan(' 🔗 Connecting project to L4YERCAK3...\n'));
30
+
31
+ try {
32
+ const projectPath = process.cwd();
33
+
34
+ // Step 1: Load manifest
35
+ const manifest = manifestGenerator.loadManifest(projectPath);
36
+ if (!manifest) {
37
+ console.log(chalk.yellow(' ⚠️ No .l4yercak3.json manifest found.'));
38
+ console.log(chalk.gray(' Run "l4yercak3 init" first to scan your project.\n'));
39
+ process.exit(1);
40
+ }
41
+
42
+ console.log(chalk.green(` ✅ Loaded manifest (${manifest.framework}, ${manifest.detectedModels?.length || 0} models, ${manifest.detectedRoutes?.length || 0} routes)`));
43
+
44
+ // Step 2: Organization selection
45
+ console.log(chalk.cyan('\n 📦 Organization Setup\n'));
46
+ const existingConfig = configManager.getProjectConfig(projectPath);
47
+ let organizationId, organizationName;
48
+
49
+ if (existingConfig && existingConfig.organizationId) {
50
+ console.log(chalk.green(` ✅ Using organization: ${existingConfig.organizationName || existingConfig.organizationId}`));
51
+ organizationId = existingConfig.organizationId;
52
+ organizationName = existingConfig.organizationName;
53
+ } else {
54
+ const defaultName = manifest.framework || 'My Organization';
55
+ const result = await selectOrganization(defaultName);
56
+ organizationId = result.organizationId;
57
+ organizationName = result.organizationName;
58
+ }
59
+
60
+ // Step 3: Register application if not already registered
61
+ let applicationId = existingConfig?.applicationId || null;
62
+
63
+ if (!applicationId) {
64
+ console.log(chalk.cyan('\n 📱 Registering application...\n'));
65
+ const projectPathHash = generateProjectPathHash(projectPath);
66
+
67
+ try {
68
+ const existingApp = await backendClient.checkExistingApplication(organizationId, projectPathHash);
69
+
70
+ if (existingApp.found && existingApp.application) {
71
+ applicationId = existingApp.application.id;
72
+ console.log(chalk.green(` ✅ Found existing registration: ${existingApp.application.name}\n`));
73
+ } else {
74
+ const folderName = path.basename(projectPath);
75
+ const registrationData = {
76
+ organizationId,
77
+ name: folderName,
78
+ description: `Connected via CLI from ${manifest.framework} project`,
79
+ source: {
80
+ type: 'cli',
81
+ projectPathHash,
82
+ cliVersion: pkg.version,
83
+ framework: manifest.framework,
84
+ routerType: manifest.routerType,
85
+ hasTypeScript: manifest.typescript || false,
86
+ },
87
+ connection: {
88
+ features: [],
89
+ },
90
+ deployment: {},
91
+ };
92
+
93
+ const result = await backendClient.registerApplication(registrationData);
94
+ applicationId = result.applicationId;
95
+ console.log(chalk.green(` ✅ Application registered with L4YERCAK3\n`));
96
+ }
97
+ } catch (regError) {
98
+ console.log(chalk.yellow(` ⚠️ Could not register application: ${regError.message}`));
99
+ console.log(chalk.gray(' Continuing with GitHub connection...\n'));
100
+ }
101
+ } else {
102
+ console.log(chalk.green(` ✅ Application already registered (${applicationId})`));
103
+ }
104
+
105
+ // Step 4: API key setup
106
+ let apiKey = null;
107
+ const envPath = path.join(projectPath, '.env.local');
108
+
109
+ if (fs.existsSync(envPath)) {
110
+ try {
111
+ const envContent = fs.readFileSync(envPath, 'utf8');
112
+ const match = envContent.match(/^L4YERCAK3_API_KEY=(.+)$/m);
113
+ if (match && match[1] && match[1].startsWith('sk_')) {
114
+ console.log(chalk.green(` ✅ API key found in .env.local`));
115
+ } else {
116
+ apiKey = await generateNewApiKey(organizationId);
117
+ }
118
+ } catch {
119
+ apiKey = await generateNewApiKey(organizationId);
120
+ }
121
+ } else {
122
+ apiKey = await generateNewApiKey(organizationId);
123
+ }
124
+
125
+ // Step 5: Detect and connect GitHub repository
126
+ console.log(chalk.cyan('\n 🐙 GitHub Repository Connection\n'));
127
+ const githubInfo = githubDetector.detect(projectPath);
128
+
129
+ if (githubInfo.isGitHub) {
130
+ console.log(chalk.green(` ✅ Detected: ${githubInfo.owner}/${githubInfo.repo}`));
131
+ console.log(chalk.gray(` Branch: ${githubInfo.branch || 'main'}`));
132
+ console.log(chalk.gray(` URL: ${githubInfo.url}\n`));
133
+
134
+ const { connectRepo } = await inquirer.prompt([
135
+ {
136
+ type: 'confirm',
137
+ name: 'connectRepo',
138
+ message: 'Connect this repository to L4YERCAK3 for platform analysis?',
139
+ default: true,
140
+ },
141
+ ]);
142
+
143
+ if (connectRepo && applicationId) {
144
+ try {
145
+ await backendClient.connectRepo(applicationId, {
146
+ repoUrl: githubInfo.url,
147
+ branch: githubInfo.branch || 'main',
148
+ });
149
+ console.log(chalk.green('\n ✅ Repository connected! The platform will analyze your codebase.'));
150
+ console.log(chalk.gray(' Your app will appear in the builder once analysis completes.\n'));
151
+ } catch (repoError) {
152
+ console.log(chalk.yellow(`\n ⚠️ Could not connect repository: ${repoError.message}`));
153
+ console.log(chalk.gray(' This feature may not be available yet on the platform.\n'));
154
+ }
155
+ } else if (connectRepo && !applicationId) {
156
+ console.log(chalk.yellow('\n ⚠️ Cannot connect repo without a registered application.'));
157
+ console.log(chalk.gray(' Try running "l4yercak3 connect" again after resolving registration.\n'));
158
+ }
159
+ } else if (githubInfo.hasGit) {
160
+ console.log(chalk.yellow(' ⚠️ Git repository detected but not hosted on GitHub.'));
161
+ console.log(chalk.gray(' GitHub connection is required for platform analysis.\n'));
162
+ } else {
163
+ console.log(chalk.yellow(' ⚠️ No git repository detected.'));
164
+ console.log(chalk.gray(' Initialize a git repo and push to GitHub for platform analysis.\n'));
165
+ }
166
+
167
+ // Step 6: Sync manifest to platform
168
+ if (applicationId) {
169
+ try {
170
+ await backendClient.syncManifest(applicationId, manifest);
171
+ console.log(chalk.green(' ✅ Manifest synced to platform'));
172
+ } catch (syncError) {
173
+ console.log(chalk.yellow(` ⚠️ Could not sync manifest: ${syncError.message}`));
174
+ console.log(chalk.gray(' This feature may not be available yet.\n'));
175
+ }
176
+ }
177
+
178
+ // Step 7: Update .env.local
179
+ if (apiKey || applicationId || organizationId) {
180
+ const envVars = {};
181
+ if (apiKey) envVars.L4YERCAK3_API_KEY = apiKey;
182
+ if (organizationId) envVars.L4YERCAK3_ORG_ID = organizationId;
183
+ if (applicationId) envVars.L4YERCAK3_APP_ID = applicationId;
184
+
185
+ updateEnvFile(envPath, envVars);
186
+ console.log(chalk.green(' ✅ Environment variables written to .env.local\n'));
187
+ }
188
+
189
+ // Step 8: Save project config
190
+ const projectConfig = {
191
+ ...(existingConfig || {}),
192
+ organizationId,
193
+ organizationName,
194
+ applicationId,
195
+ projectPathHash: generateProjectPathHash(projectPath),
196
+ connectedAt: Date.now(),
197
+ githubRepo: githubInfo.isGitHub ? `${githubInfo.owner}/${githubInfo.repo}` : undefined,
198
+ };
199
+
200
+ configManager.saveProjectConfig(projectPath, projectConfig);
201
+
202
+ console.log(chalk.cyan(' 🎉 Connection complete!\n'));
203
+ console.log(chalk.gray(' Next steps:'));
204
+ console.log(chalk.gray(' • Run "l4yercak3 scaffold" to generate integration files'));
205
+ console.log(chalk.gray(' • Run "l4yercak3 sync" after changing your project structure\n'));
206
+
207
+ } catch (error) {
208
+ console.error(chalk.red(`\n ❌ Error: ${error.message}\n`));
209
+ if (process.env.L4YERCAK3_DEBUG && error.stack) {
210
+ console.error(chalk.gray(error.stack));
211
+ }
212
+ process.exit(1);
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Update .env.local file, merging new vars with existing content
218
+ * @param {string} envPath - Path to .env.local
219
+ * @param {Object<string, string>} vars - Key-value pairs to set
220
+ */
221
+ function updateEnvFile(envPath, vars) {
222
+ let content = '';
223
+ if (fs.existsSync(envPath)) {
224
+ content = fs.readFileSync(envPath, 'utf8');
225
+ }
226
+
227
+ for (const [key, value] of Object.entries(vars)) {
228
+ const regex = new RegExp(`^${key}=.*$`, 'm');
229
+ if (regex.test(content)) {
230
+ content = content.replace(regex, `${key}=${value}`);
231
+ } else {
232
+ content = `${content.trimEnd()}${content ? '\n' : ''}${key}=${value}\n`;
233
+ }
234
+ }
235
+
236
+ fs.writeFileSync(envPath, content, 'utf8');
237
+ }
238
+
239
+ module.exports = {
240
+ command: 'connect',
241
+ description: 'Connect your project to the L4YERCAK3 platform',
242
+ handler: handleConnect,
243
+ };