aerocoding 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1034 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+
6
+ // src/commands/login.ts
7
+ import chalk4 from "chalk";
8
+
9
+ // src/auth/device-flow.ts
10
+ import axios from "axios";
11
+ import chalk from "chalk";
12
+ import ora from "ora";
13
+ import open from "open";
14
+ var API_URL = process.env.API_URL || "http://localhost:3000";
15
+ var MAX_POLL_TIME = 15 * 60 * 1e3;
16
+ var DeviceFlow = class {
17
+ /**
18
+ * Initiate the complete device authorization flow
19
+ */
20
+ async initiateAuth() {
21
+ const deviceAuth = await this.requestDeviceCode();
22
+ this.displayUserCode(deviceAuth);
23
+ await this.openBrowser(deviceAuth.verification_uri_complete);
24
+ const tokens = await this.pollForToken(deviceAuth);
25
+ return tokens;
26
+ }
27
+ /**
28
+ * Step 1: Request device code from server
29
+ */
30
+ async requestDeviceCode() {
31
+ try {
32
+ const response = await axios.post(`${API_URL}/api/device/authorize`, {
33
+ client_id: "aerocoding-cli",
34
+ scope: "generate:code projects:read"
35
+ });
36
+ return response.data;
37
+ } catch (error) {
38
+ console.error(chalk.red("Failed to initiate device authorization"));
39
+ if (error.response) {
40
+ console.error(
41
+ chalk.red(`Error: ${error.response.data.error_description}`)
42
+ );
43
+ }
44
+ throw new Error("Failed to initiate device authorization");
45
+ }
46
+ }
47
+ /**
48
+ * Step 2: Display user code and instructions
49
+ */
50
+ displayUserCode(auth) {
51
+ console.log("\n");
52
+ console.log(chalk.cyan("\u2501".repeat(60)));
53
+ console.log(chalk.bold.white(" \u{1F680} AeroCoding CLI - Device Activation"));
54
+ console.log(chalk.cyan("\u2501".repeat(60)));
55
+ console.log("");
56
+ console.log(chalk.white(" 1. Open this URL in your browser:"));
57
+ console.log(chalk.cyan.bold(` ${auth.verification_uri}`));
58
+ console.log("");
59
+ console.log(chalk.white(" 2. Enter this code:"));
60
+ console.log(chalk.green.bold(` ${auth.user_code}`));
61
+ console.log("");
62
+ console.log(chalk.white(" 3. Confirm you see this code on the website:"));
63
+ console.log(chalk.yellow.bold(` ${auth.confirmation_code}`));
64
+ console.log("");
65
+ console.log(
66
+ chalk.gray(` Code expires in ${auth.expires_in / 60} minutes`)
67
+ );
68
+ console.log(chalk.cyan("\u2501".repeat(60)));
69
+ console.log("");
70
+ }
71
+ /**
72
+ * Step 2.5: Open browser automatically
73
+ */
74
+ async openBrowser(url) {
75
+ try {
76
+ await open(url);
77
+ console.log(chalk.gray(" \u2713 Opening browser..."));
78
+ } catch {
79
+ console.log(chalk.yellow(" \u26A0 Could not open browser automatically"));
80
+ console.log(chalk.gray(" Please open the URL manually\n"));
81
+ }
82
+ }
83
+ /**
84
+ * Step 3: Poll for token
85
+ */
86
+ async pollForToken(auth) {
87
+ const spinner2 = ora({
88
+ text: "Waiting for authorization...",
89
+ color: "cyan"
90
+ }).start();
91
+ const startTime = Date.now();
92
+ let currentInterval = auth.interval * 1e3;
93
+ while (true) {
94
+ if (Date.now() - startTime > MAX_POLL_TIME) {
95
+ spinner2.fail(chalk.red("Authorization timeout"));
96
+ throw new Error("Device authorization timed out");
97
+ }
98
+ try {
99
+ const response = await axios.post(`${API_URL}/api/device/token`, {
100
+ device_code: auth.device_code,
101
+ client_id: "aerocoding-cli"
102
+ });
103
+ spinner2.succeed(chalk.green("\u2713 Successfully authenticated!"));
104
+ return response.data;
105
+ } catch (error) {
106
+ const errorCode = error.response?.data?.error;
107
+ const errorDescription = error.response?.data?.error_description;
108
+ if (errorCode === "authorization_pending") {
109
+ await this.sleep(currentInterval);
110
+ continue;
111
+ }
112
+ if (errorCode === "slow_down") {
113
+ currentInterval += 5e3;
114
+ spinner2.text = "Polling... (slowed down to avoid spam)";
115
+ await this.sleep(currentInterval);
116
+ continue;
117
+ }
118
+ if (errorCode === "expired_token") {
119
+ spinner2.fail(chalk.red("Authorization code expired"));
120
+ throw new Error("Device code expired. Please try again.");
121
+ }
122
+ if (errorCode === "access_denied") {
123
+ spinner2.fail(chalk.red("Authorization denied"));
124
+ throw new Error("You denied the authorization request");
125
+ }
126
+ spinner2.fail(chalk.red("Authorization failed"));
127
+ console.error(
128
+ chalk.red(`Error: ${errorDescription || "Unknown error"}`)
129
+ );
130
+ throw new Error(errorDescription || "Unknown error");
131
+ }
132
+ }
133
+ }
134
+ /**
135
+ * Helper: Sleep for specified milliseconds
136
+ */
137
+ sleep(ms) {
138
+ return new Promise((resolve) => setTimeout(resolve, ms));
139
+ }
140
+ };
141
+
142
+ // src/auth/token-manager.ts
143
+ import { Entry } from "@napi-rs/keyring";
144
+ import { createClient } from "@supabase/supabase-js";
145
+ import chalk2 from "chalk";
146
+ var SERVICE_NAME = "aerocoding-cli";
147
+ var SUPABASE_URL = process.env.SUPABASE_URL || "https://your-project.supabase.co";
148
+ var SUPABASE_ANON_KEY = process.env.SUPABASE_ANON_KEY || "";
149
+ function getPassword(service, account) {
150
+ try {
151
+ const entry = new Entry(service, account);
152
+ return entry.getPassword();
153
+ } catch {
154
+ return null;
155
+ }
156
+ }
157
+ function setPassword(service, account, password) {
158
+ const entry = new Entry(service, account);
159
+ entry.setPassword(password);
160
+ }
161
+ function deletePassword(service, account) {
162
+ try {
163
+ const entry = new Entry(service, account);
164
+ entry.deletePassword();
165
+ } catch {
166
+ }
167
+ }
168
+ var TokenManager = class {
169
+ supabase;
170
+ constructor() {
171
+ this.supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
172
+ auth: {
173
+ autoRefreshToken: false,
174
+ persistSession: false
175
+ }
176
+ });
177
+ }
178
+ /**
179
+ * Get access token, automatically refreshing if expired
180
+ */
181
+ async getAccessToken() {
182
+ try {
183
+ let accessToken = getPassword(SERVICE_NAME, "access_token");
184
+ if (!accessToken) {
185
+ return null;
186
+ }
187
+ if (this.isTokenExpired(accessToken)) {
188
+ console.log(chalk2.yellow("\u23F1\uFE0F Token expired, refreshing..."));
189
+ accessToken = await this.refreshTokens();
190
+ }
191
+ return accessToken;
192
+ } catch (error) {
193
+ console.error(chalk2.red("Failed to get access token:"), error);
194
+ return null;
195
+ }
196
+ }
197
+ /**
198
+ * Refresh tokens using stored refresh token
199
+ */
200
+ async refreshTokens() {
201
+ try {
202
+ const refreshToken = getPassword(SERVICE_NAME, "refresh_token");
203
+ if (!refreshToken) {
204
+ throw new Error("No refresh token available");
205
+ }
206
+ const { data, error } = await this.supabase.auth.refreshSession({
207
+ refresh_token: refreshToken
208
+ });
209
+ if (error || !data.session) {
210
+ throw new Error("Failed to refresh session");
211
+ }
212
+ setPassword(SERVICE_NAME, "access_token", data.session.access_token);
213
+ setPassword(SERVICE_NAME, "refresh_token", data.session.refresh_token);
214
+ console.log(chalk2.green("\u2713 Token refreshed successfully"));
215
+ return data.session.access_token;
216
+ } catch (error) {
217
+ console.error(chalk2.red("Token refresh failed. Please login again."));
218
+ await this.logout();
219
+ throw error;
220
+ }
221
+ }
222
+ /**
223
+ * Check if JWT token is expired (or expires in < 5 min)
224
+ */
225
+ isTokenExpired(token) {
226
+ try {
227
+ const payload = JSON.parse(
228
+ Buffer.from(token.split(".")[1], "base64").toString()
229
+ );
230
+ const expiresAt = payload.exp * 1e3;
231
+ const now = Date.now();
232
+ return expiresAt - now < 5 * 60 * 1e3;
233
+ } catch {
234
+ return true;
235
+ }
236
+ }
237
+ /**
238
+ * Save tokens and user metadata after successful login
239
+ */
240
+ async saveTokens(accessToken, refreshToken, user) {
241
+ try {
242
+ setPassword(SERVICE_NAME, "access_token", accessToken);
243
+ setPassword(SERVICE_NAME, "refresh_token", refreshToken);
244
+ setPassword(SERVICE_NAME, "user_metadata", JSON.stringify(user));
245
+ } catch (error) {
246
+ console.error(chalk2.red("Failed to save tokens:"), error);
247
+ throw error;
248
+ }
249
+ }
250
+ /**
251
+ * Get stored user metadata
252
+ */
253
+ async getUserMetadata() {
254
+ try {
255
+ const metadata = getPassword(SERVICE_NAME, "user_metadata");
256
+ return metadata ? JSON.parse(metadata) : null;
257
+ } catch {
258
+ return null;
259
+ }
260
+ }
261
+ /**
262
+ * Logout - clear all stored credentials
263
+ */
264
+ async logout() {
265
+ try {
266
+ deletePassword(SERVICE_NAME, "access_token");
267
+ deletePassword(SERVICE_NAME, "refresh_token");
268
+ deletePassword(SERVICE_NAME, "user_metadata");
269
+ } catch (error) {
270
+ }
271
+ }
272
+ /**
273
+ * Check if user is currently authenticated
274
+ */
275
+ async isAuthenticated() {
276
+ const token = await this.getAccessToken();
277
+ return token !== null;
278
+ }
279
+ };
280
+
281
+ // src/api/client.ts
282
+ import axios2 from "axios";
283
+ var API_URL2 = process.env.API_URL || "http://localhost:3000";
284
+ var ApiClient = class {
285
+ client;
286
+ constructor(accessToken, options) {
287
+ this.client = axios2.create({
288
+ baseURL: API_URL2,
289
+ headers: {
290
+ Authorization: `Bearer ${accessToken}`,
291
+ "Content-Type": "application/json"
292
+ },
293
+ timeout: 3e4
294
+ // 30 seconds
295
+ });
296
+ this.client.interceptors.response.use(
297
+ (response) => response,
298
+ async (error) => {
299
+ if (error?.response?.status === 401) {
300
+ await options?.onUnauthorized?.();
301
+ }
302
+ return Promise.reject(error);
303
+ }
304
+ );
305
+ }
306
+ /**
307
+ * Get current authenticated user
308
+ */
309
+ async getCurrentUser() {
310
+ const response = await this.client.get("/api/user/me");
311
+ return response.data;
312
+ }
313
+ /**
314
+ * List user's projects
315
+ */
316
+ async listProjects(organizationId) {
317
+ const response = await this.client.get("/api/projects", {
318
+ params: { organizationId }
319
+ });
320
+ return response.data;
321
+ }
322
+ /**
323
+ * Get project by ID
324
+ */
325
+ async getProject(projectId) {
326
+ const response = await this.client.get(`/api/projects/${projectId}`);
327
+ return response.data;
328
+ }
329
+ /**
330
+ * Generate code from project schema
331
+ */
332
+ async generateCode(payload) {
333
+ const response = await this.client.post("/api/generate", payload);
334
+ return response.data.data;
335
+ }
336
+ /**
337
+ * Get credit usage for organization
338
+ */
339
+ async getCreditUsage(organizationId) {
340
+ const response = await this.client.get("/api/credits/usage", {
341
+ params: { organizationId }
342
+ });
343
+ return response.data;
344
+ }
345
+ /**
346
+ * Get available architectures for a framework
347
+ */
348
+ async getArchitectures(framework) {
349
+ const response = await this.client.get("/api/architectures", {
350
+ params: { framework }
351
+ });
352
+ return response.data;
353
+ }
354
+ };
355
+
356
+ // src/commands/_shared/create-api-client.ts
357
+ function createApiClientWithAutoLogout(accessToken, tokenManager) {
358
+ return new ApiClient(accessToken, {
359
+ onUnauthorized: async () => {
360
+ await tokenManager.logout();
361
+ }
362
+ });
363
+ }
364
+
365
+ // src/commands/_shared/unauthorized.ts
366
+ import chalk3 from "chalk";
367
+ async function handleUnauthorized(tokenManager) {
368
+ await tokenManager.logout();
369
+ console.error(
370
+ chalk3.yellow(
371
+ " Your session is invalid or expired. You have been logged out."
372
+ )
373
+ );
374
+ console.error(chalk3.gray(" Run 'aerocoding login' to authenticate."));
375
+ process.exit(1);
376
+ }
377
+
378
+ // src/commands/login.ts
379
+ async function loginCommand() {
380
+ console.log(chalk4.bold("\n\u{1F680} AeroCoding CLI Login\n"));
381
+ const tokenManager = new TokenManager();
382
+ const existingToken = await tokenManager.getAccessToken();
383
+ if (existingToken) {
384
+ const metadata = await tokenManager.getUserMetadata();
385
+ console.log(
386
+ chalk4.yellow("\u26A0\uFE0F Already logged in as:"),
387
+ chalk4.white(metadata?.email || "unknown")
388
+ );
389
+ console.log(
390
+ chalk4.gray(
391
+ " Run 'aerocoding logout' to login with a different account\n"
392
+ )
393
+ );
394
+ return;
395
+ }
396
+ try {
397
+ const deviceFlow = new DeviceFlow();
398
+ const tokens = await deviceFlow.initiateAuth();
399
+ const apiClient = createApiClientWithAutoLogout(
400
+ tokens.access_token,
401
+ tokenManager
402
+ );
403
+ let user;
404
+ try {
405
+ user = await apiClient.getCurrentUser();
406
+ } catch (error) {
407
+ if (error.response?.status === 401) {
408
+ await handleUnauthorized(tokenManager);
409
+ }
410
+ throw error;
411
+ }
412
+ await tokenManager.saveTokens(tokens.access_token, tokens.refresh_token, {
413
+ id: user.id,
414
+ email: user.email,
415
+ name: user.name || void 0,
416
+ tier: user.tier
417
+ });
418
+ console.log("");
419
+ console.log(
420
+ chalk4.green("\u2713 Successfully logged in as:"),
421
+ chalk4.white(user.email)
422
+ );
423
+ console.log(chalk4.gray(" Plan:"), chalk4.cyan(user.tier.toUpperCase()));
424
+ console.log(chalk4.gray(" Run 'aerocoding whoami' to verify\n"));
425
+ } catch (error) {
426
+ console.error(
427
+ chalk4.red("\n\u2717 Login failed:"),
428
+ error.message || "Unknown error"
429
+ );
430
+ process.exit(1);
431
+ }
432
+ }
433
+
434
+ // src/commands/logout.ts
435
+ import chalk5 from "chalk";
436
+ async function logoutCommand() {
437
+ const tokenManager = new TokenManager();
438
+ const metadata = await tokenManager.getUserMetadata();
439
+ if (!metadata) {
440
+ console.log(chalk5.yellow("\u26A0\uFE0F Not logged in"));
441
+ return;
442
+ }
443
+ const email = metadata.email;
444
+ await tokenManager.logout();
445
+ console.log(chalk5.green("\u2713 Logged out successfully"));
446
+ console.log(chalk5.gray(` Cleared credentials for ${email}
447
+ `));
448
+ }
449
+
450
+ // src/commands/whoami.ts
451
+ import chalk6 from "chalk";
452
+ async function whoamiCommand() {
453
+ const tokenManager = new TokenManager();
454
+ const token = await tokenManager.getAccessToken();
455
+ if (!token) {
456
+ console.log(chalk6.red("\u2717 Not logged in"));
457
+ console.log(chalk6.gray(" Run 'aerocoding login' to authenticate\n"));
458
+ return;
459
+ }
460
+ try {
461
+ const apiClient = createApiClientWithAutoLogout(token, tokenManager);
462
+ const user = await apiClient.getCurrentUser();
463
+ console.log("");
464
+ console.log(chalk6.white("Logged in as:"), chalk6.cyan.bold(user.email));
465
+ console.log(chalk6.gray("User ID:"), user.id);
466
+ console.log(chalk6.gray("Plan:"), chalk6.cyan(user.tier.toUpperCase()));
467
+ if (user.name) {
468
+ console.log(chalk6.gray("Name:"), user.name);
469
+ }
470
+ console.log("");
471
+ } catch (error) {
472
+ console.error(chalk6.red("\u2717 Failed to get user info"));
473
+ if (error.response?.status === 401) {
474
+ await handleUnauthorized(tokenManager);
475
+ }
476
+ process.exit(1);
477
+ }
478
+ }
479
+
480
+ // src/commands/generate.ts
481
+ import chalk9 from "chalk";
482
+ import ora2 from "ora";
483
+
484
+ // src/utils/file-writer.ts
485
+ import fs from "fs/promises";
486
+ import path from "path";
487
+ import chalk7 from "chalk";
488
+ function isPathSafe(outputDir, filePath) {
489
+ const resolvedOutput = path.resolve(outputDir);
490
+ const resolvedFile = path.resolve(outputDir, filePath);
491
+ return resolvedFile.startsWith(resolvedOutput + path.sep) || resolvedFile === resolvedOutput;
492
+ }
493
+ async function writeGeneratedFiles(files, outputDir) {
494
+ for (const file of files) {
495
+ if (!isPathSafe(outputDir, file.path)) {
496
+ console.error(
497
+ chalk7.red(` \u2717 Skipping unsafe path: ${file.path}`)
498
+ );
499
+ console.error(
500
+ chalk7.gray(` Path traversal detected - file path must be within output directory`)
501
+ );
502
+ continue;
503
+ }
504
+ const fullPath = path.resolve(outputDir, file.path);
505
+ const dir = path.dirname(fullPath);
506
+ try {
507
+ await fs.mkdir(dir, { recursive: true });
508
+ await fs.writeFile(fullPath, file.content, "utf-8");
509
+ console.log(chalk7.gray(` \u2713 ${file.path}`));
510
+ } catch (error) {
511
+ console.error(chalk7.red(` \u2717 Failed to write ${file.path}`));
512
+ console.error(chalk7.gray(` ${error.message}`));
513
+ }
514
+ }
515
+ }
516
+
517
+ // src/utils/prompt.ts
518
+ import readline from "readline";
519
+ import chalk8 from "chalk";
520
+ function promptConfirm(message) {
521
+ return new Promise((resolve) => {
522
+ const rl = readline.createInterface({
523
+ input: process.stdin,
524
+ output: process.stdout
525
+ });
526
+ rl.question(chalk8.yellow(`${message} [Y/n] `), (answer) => {
527
+ rl.close();
528
+ const normalized = answer.trim().toLowerCase();
529
+ resolve(normalized !== "n" && normalized !== "no");
530
+ });
531
+ });
532
+ }
533
+
534
+ // src/config/loader.ts
535
+ import fs2 from "fs/promises";
536
+ import path2 from "path";
537
+
538
+ // src/config/schema.ts
539
+ import { z } from "zod";
540
+ var configSchema = z.object({
541
+ $schema: z.string().optional(),
542
+ project: z.string().uuid(),
543
+ output: z.string().default("./generated"),
544
+ backend: z.object({
545
+ preset: z.string(),
546
+ layers: z.array(z.string())
547
+ }).optional(),
548
+ frontend: z.object({
549
+ preset: z.string(),
550
+ layers: z.array(z.string())
551
+ }).optional(),
552
+ codeStyle: z.object({
553
+ includeValidations: z.boolean().default(true),
554
+ includeComments: z.boolean().default(true),
555
+ includeAnnotations: z.boolean().default(true),
556
+ includeLogging: z.boolean().default(true),
557
+ includeTesting: z.boolean().default(true)
558
+ }).default({}),
559
+ libraries: z.object({
560
+ validation: z.string().optional(),
561
+ logging: z.string().optional()
562
+ }).optional(),
563
+ excludePatterns: z.array(z.string()).optional()
564
+ });
565
+ var CONFIG_FILENAME = ".aerocodingrc.json";
566
+
567
+ // src/config/loader.ts
568
+ async function loadConfig(dir = process.cwd()) {
569
+ const configPath = path2.join(dir, CONFIG_FILENAME);
570
+ try {
571
+ const content = await fs2.readFile(configPath, "utf-8");
572
+ const parsed = JSON.parse(content);
573
+ return configSchema.parse(parsed);
574
+ } catch (error) {
575
+ if (error.code === "ENOENT") {
576
+ return null;
577
+ }
578
+ throw error;
579
+ }
580
+ }
581
+ async function saveConfig(config, dir = process.cwd()) {
582
+ const configPath = path2.join(dir, CONFIG_FILENAME);
583
+ const content = JSON.stringify(
584
+ {
585
+ $schema: "https://aerocoding.app/schemas/aerocodingrc.json",
586
+ ...config
587
+ },
588
+ null,
589
+ 2
590
+ );
591
+ await fs2.writeFile(configPath, content, "utf-8");
592
+ }
593
+ async function configExists(dir = process.cwd()) {
594
+ const configPath = path2.join(dir, CONFIG_FILENAME);
595
+ try {
596
+ await fs2.access(configPath);
597
+ return true;
598
+ } catch {
599
+ return false;
600
+ }
601
+ }
602
+
603
+ // src/commands/generate.ts
604
+ function buildTargetsFromConfig(config) {
605
+ if (!config) return [];
606
+ const targets = [];
607
+ if (config.backend?.preset) {
608
+ targets.push("backend");
609
+ }
610
+ if (config.frontend?.preset) {
611
+ targets.push("frontend");
612
+ }
613
+ return targets;
614
+ }
615
+ async function generateCommand(options) {
616
+ const tokenManager = new TokenManager();
617
+ const token = await tokenManager.getAccessToken();
618
+ if (!token) {
619
+ console.log(chalk9.red("\n Not authenticated"));
620
+ console.log(chalk9.gray(" Run 'aerocoding login' to get started\n"));
621
+ process.exit(1);
622
+ }
623
+ const config = await loadConfig();
624
+ const projectId = options.project || config?.project;
625
+ if (!projectId) {
626
+ console.log(chalk9.red("\n Project ID required"));
627
+ console.log(chalk9.gray(" Run 'aerocoding init' to create a config file"));
628
+ console.log(chalk9.gray(" Or use --project <id> to specify a project\n"));
629
+ process.exit(1);
630
+ }
631
+ const targets = options.targets || buildTargetsFromConfig(config);
632
+ const output = options.output || config?.output || "./generated";
633
+ const backendPreset = options.backendPreset || config?.backend?.preset;
634
+ const frontendPreset = options.frontendPreset || config?.frontend?.preset;
635
+ const backendLayers = options.backendLayers || config?.backend?.layers;
636
+ const frontendLayers = options.frontendLayers || config?.frontend?.layers;
637
+ const validationLib = options.validationLib || config?.libraries?.validation;
638
+ const includeValidations = options.validations ?? config?.codeStyle?.includeValidations ?? true;
639
+ const includeComments = options.comments ?? config?.codeStyle?.includeComments ?? true;
640
+ const includeAnnotations = options.annotations ?? config?.codeStyle?.includeAnnotations ?? true;
641
+ const includeLogging = options.logging ?? config?.codeStyle?.includeLogging ?? true;
642
+ const includeTesting = options.testing ?? config?.codeStyle?.includeTesting ?? true;
643
+ const apiClient = createApiClientWithAutoLogout(token, tokenManager);
644
+ let spinner2 = ora2({ text: "Fetching project...", color: "cyan" }).start();
645
+ try {
646
+ const project = await apiClient.getProject(projectId);
647
+ spinner2.succeed(chalk9.gray(`Project: ${project.name}`));
648
+ spinner2 = ora2({ text: "Checking credits...", color: "cyan" }).start();
649
+ const credits = await apiClient.getCreditUsage(project.organizationId);
650
+ spinner2.succeed(chalk9.gray(`Credits: ${credits.remaining} / ${credits.limit} available`));
651
+ console.log("");
652
+ console.log(chalk9.bold(" Generation Summary"));
653
+ console.log(chalk9.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
654
+ console.log(chalk9.gray(" Project:"), chalk9.white(project.name));
655
+ if (config) {
656
+ console.log(chalk9.gray(" Config:"), chalk9.cyan(".aerocodingrc.json"));
657
+ }
658
+ console.log(
659
+ chalk9.gray(" Targets:"),
660
+ chalk9.cyan(targets.length > 0 ? targets.join(", ") : "default")
661
+ );
662
+ if (backendPreset) {
663
+ console.log(chalk9.gray(" Backend Preset:"), chalk9.cyan(backendPreset));
664
+ }
665
+ if (frontendPreset) {
666
+ console.log(chalk9.gray(" Frontend Preset:"), chalk9.cyan(frontendPreset));
667
+ }
668
+ const disabledOptions = [];
669
+ if (!includeValidations) disabledOptions.push("validations");
670
+ if (!includeComments) disabledOptions.push("comments");
671
+ if (!includeAnnotations) disabledOptions.push("annotations");
672
+ if (!includeLogging) disabledOptions.push("logging");
673
+ if (!includeTesting) disabledOptions.push("testing");
674
+ if (disabledOptions.length > 0) {
675
+ console.log(chalk9.gray(" Disabled:"), chalk9.yellow(disabledOptions.join(", ")));
676
+ }
677
+ if (validationLib) {
678
+ console.log(chalk9.gray(" Validation Lib:"), chalk9.cyan(validationLib));
679
+ }
680
+ console.log(
681
+ chalk9.gray(" Credits:"),
682
+ credits.remaining > 50 ? chalk9.green(`${credits.remaining} available`) : chalk9.yellow(`${credits.remaining} available (low)`)
683
+ );
684
+ console.log(chalk9.gray(" Output:"), chalk9.cyan(output));
685
+ console.log("");
686
+ if (!options.yes) {
687
+ const confirmed = await promptConfirm(" Proceed with generation?");
688
+ if (!confirmed) {
689
+ console.log(chalk9.yellow("\n Generation cancelled\n"));
690
+ process.exit(0);
691
+ }
692
+ }
693
+ console.log("");
694
+ spinner2 = ora2({ text: "Generating code...", color: "cyan" }).start();
695
+ const result = await apiClient.generateCode({
696
+ projectId,
697
+ targets,
698
+ options: {
699
+ includeValidations,
700
+ includeComments,
701
+ includeAnnotations,
702
+ includeLogging,
703
+ includeTesting,
704
+ outputDir: output,
705
+ backendPreset,
706
+ frontendPreset,
707
+ backendLayers,
708
+ frontendLayers,
709
+ validationLib
710
+ }
711
+ });
712
+ spinner2.succeed(chalk9.green("Code generated successfully!"));
713
+ console.log("");
714
+ console.log(chalk9.bold(" Results"));
715
+ console.log(chalk9.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
716
+ console.log(chalk9.gray(" Files:"), chalk9.cyan(result.stats.totalFiles));
717
+ console.log(chalk9.gray(" Entities:"), chalk9.cyan(result.stats.totalEntities));
718
+ console.log(
719
+ chalk9.gray(" Languages:"),
720
+ chalk9.cyan(result.stats.languages.join(", "))
721
+ );
722
+ if (result.creditsUsed !== void 0) {
723
+ console.log("");
724
+ console.log(chalk9.bold(" Credits"));
725
+ console.log(chalk9.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
726
+ console.log(chalk9.gray(" Used:"), chalk9.yellow(result.creditsUsed));
727
+ console.log(
728
+ chalk9.gray(" Remaining:"),
729
+ result.creditsRemaining !== void 0 && result.creditsRemaining > 50 ? chalk9.green(result.creditsRemaining) : chalk9.yellow(result.creditsRemaining)
730
+ );
731
+ }
732
+ if (result.warnings && result.warnings.length > 0) {
733
+ console.log("");
734
+ console.log(chalk9.yellow(" Warnings:"));
735
+ for (const warning of result.warnings) {
736
+ console.log(chalk9.yellow(` - ${warning}`));
737
+ }
738
+ }
739
+ console.log("");
740
+ await writeGeneratedFiles(result.files, output);
741
+ console.log(chalk9.green(` Files written to ${chalk9.white(output)}`));
742
+ console.log("");
743
+ } catch (error) {
744
+ spinner2.fail(chalk9.red("Generation failed"));
745
+ if (error.response?.status === 401) {
746
+ await handleUnauthorized(tokenManager);
747
+ } else if (error.response?.status === 403) {
748
+ console.log(chalk9.yellow("\n You don't have permission to access this project."));
749
+ console.log(chalk9.gray(" Check if you're part of the organization.\n"));
750
+ } else if (error.response?.status === 404) {
751
+ console.log(chalk9.yellow("\n Project not found."));
752
+ console.log(chalk9.gray(" Check if the project ID is correct.\n"));
753
+ } else if (error.response?.status === 429) {
754
+ const data = error.response.data;
755
+ console.log(chalk9.red("\n Insufficient credits"));
756
+ if (data.requiredCredits) {
757
+ console.log(chalk9.yellow(` Required: ${data.requiredCredits} credits`));
758
+ }
759
+ console.log(chalk9.yellow(` Available: ${data.remaining ?? 0} credits`));
760
+ console.log(chalk9.gray("\n Upgrade your plan or wait for credit reset.\n"));
761
+ } else if (error.response?.data?.message) {
762
+ console.log(chalk9.red(`
763
+ ${error.response.data.message}
764
+ `));
765
+ } else {
766
+ console.log(chalk9.red(`
767
+ ${error.message}
768
+ `));
769
+ }
770
+ process.exit(1);
771
+ }
772
+ }
773
+
774
+ // src/commands/init.ts
775
+ import * as p from "@clack/prompts";
776
+ import chalk10 from "chalk";
777
+ async function initCommand(options) {
778
+ p.intro(chalk10.bgCyan.black(" AeroCoding CLI "));
779
+ if (!options.force && await configExists()) {
780
+ const overwrite = await p.confirm({
781
+ message: "Config file already exists. Overwrite?",
782
+ initialValue: false
783
+ });
784
+ if (p.isCancel(overwrite) || !overwrite) {
785
+ p.cancel("Operation cancelled.");
786
+ process.exit(0);
787
+ }
788
+ }
789
+ const tokenManager = new TokenManager();
790
+ const token = await tokenManager.getAccessToken();
791
+ if (!token) {
792
+ p.cancel("Not logged in. Run 'aerocoding login' first.");
793
+ process.exit(1);
794
+ }
795
+ const apiClient = createApiClientWithAutoLogout(token, tokenManager);
796
+ try {
797
+ let projectId = options.project;
798
+ if (!projectId) {
799
+ const spinner3 = p.spinner();
800
+ spinner3.start("Loading projects...");
801
+ const projects = await apiClient.listProjects();
802
+ spinner3.stop("Projects loaded");
803
+ if (projects.length === 0) {
804
+ p.cancel("No projects found. Create a project on aerocoding.app first.");
805
+ process.exit(1);
806
+ }
807
+ const selectedProject = await p.select({
808
+ message: "Select project",
809
+ options: projects.map((proj) => ({
810
+ value: proj.id,
811
+ label: proj.name,
812
+ hint: [proj.backendFramework, proj.frontendFramework].filter(Boolean).join(" + ")
813
+ }))
814
+ });
815
+ if (p.isCancel(selectedProject)) {
816
+ p.cancel("Operation cancelled.");
817
+ process.exit(0);
818
+ }
819
+ projectId = selectedProject;
820
+ }
821
+ const spinner2 = p.spinner();
822
+ spinner2.start("Fetching project details...");
823
+ const project = await apiClient.getProject(projectId);
824
+ spinner2.stop(`Project: ${project.name}`);
825
+ const targetOptions = [];
826
+ if (project.backendFramework) {
827
+ targetOptions.push({
828
+ value: "backend",
829
+ label: `Backend (${project.backendFramework})`
830
+ });
831
+ }
832
+ if (project.frontendFramework) {
833
+ targetOptions.push({
834
+ value: "frontend",
835
+ label: `Frontend (${project.frontendFramework})`
836
+ });
837
+ }
838
+ if (targetOptions.length === 0) {
839
+ p.cancel("Project has no frameworks configured. Update it on aerocoding.app first.");
840
+ process.exit(1);
841
+ }
842
+ const targets = await p.multiselect({
843
+ message: "What do you want to generate?",
844
+ options: targetOptions,
845
+ required: true
846
+ });
847
+ if (p.isCancel(targets)) {
848
+ p.cancel("Operation cancelled.");
849
+ process.exit(0);
850
+ }
851
+ const selectedTargets = targets;
852
+ const config = {
853
+ project: projectId,
854
+ output: "./generated",
855
+ codeStyle: {
856
+ includeValidations: true,
857
+ includeComments: true,
858
+ includeAnnotations: true,
859
+ includeLogging: true,
860
+ includeTesting: true
861
+ }
862
+ };
863
+ if (selectedTargets.includes("backend") && project.backendFramework) {
864
+ const archSpinner = p.spinner();
865
+ archSpinner.start("Loading backend architectures...");
866
+ const architectures = await apiClient.getArchitectures(project.backendFramework);
867
+ archSpinner.stop("Architectures loaded");
868
+ if (architectures.length > 0) {
869
+ const preset = await p.select({
870
+ message: "Backend architecture",
871
+ options: architectures.map((arch) => ({
872
+ value: arch.id,
873
+ label: arch.name,
874
+ hint: arch.description
875
+ }))
876
+ });
877
+ if (p.isCancel(preset)) {
878
+ p.cancel("Operation cancelled.");
879
+ process.exit(0);
880
+ }
881
+ const selectedArch = architectures.find((a) => a.id === preset);
882
+ if (selectedArch && selectedArch.layers.length > 0) {
883
+ const layers = await p.multiselect({
884
+ message: "Select backend layers to include",
885
+ options: selectedArch.layers.map((layer) => ({
886
+ value: layer.id,
887
+ label: layer.name,
888
+ hint: layer.category
889
+ })),
890
+ initialValues: selectedArch.layers.map((l) => l.id)
891
+ });
892
+ if (p.isCancel(layers)) {
893
+ p.cancel("Operation cancelled.");
894
+ process.exit(0);
895
+ }
896
+ config.backend = {
897
+ preset,
898
+ layers
899
+ };
900
+ } else {
901
+ config.backend = {
902
+ preset,
903
+ layers: []
904
+ };
905
+ }
906
+ }
907
+ }
908
+ if (selectedTargets.includes("frontend") && project.frontendFramework) {
909
+ const archSpinner = p.spinner();
910
+ archSpinner.start("Loading frontend architectures...");
911
+ const architectures = await apiClient.getArchitectures(project.frontendFramework);
912
+ archSpinner.stop("Architectures loaded");
913
+ if (architectures.length > 0) {
914
+ const preset = await p.select({
915
+ message: "Frontend architecture",
916
+ options: architectures.map((arch) => ({
917
+ value: arch.id,
918
+ label: arch.name,
919
+ hint: arch.description
920
+ }))
921
+ });
922
+ if (p.isCancel(preset)) {
923
+ p.cancel("Operation cancelled.");
924
+ process.exit(0);
925
+ }
926
+ const selectedArch = architectures.find((a) => a.id === preset);
927
+ if (selectedArch && selectedArch.layers.length > 0) {
928
+ const layers = await p.multiselect({
929
+ message: "Select frontend layers to include",
930
+ options: selectedArch.layers.map((layer) => ({
931
+ value: layer.id,
932
+ label: layer.name,
933
+ hint: layer.category
934
+ })),
935
+ initialValues: selectedArch.layers.map((l) => l.id)
936
+ });
937
+ if (p.isCancel(layers)) {
938
+ p.cancel("Operation cancelled.");
939
+ process.exit(0);
940
+ }
941
+ config.frontend = {
942
+ preset,
943
+ layers
944
+ };
945
+ } else {
946
+ config.frontend = {
947
+ preset,
948
+ layers: []
949
+ };
950
+ }
951
+ }
952
+ }
953
+ const codeStyleOptions = await p.multiselect({
954
+ message: "Code style options",
955
+ options: [
956
+ { value: "validations", label: "Include validations", hint: "Add validation rules" },
957
+ { value: "comments", label: "Include comments", hint: "Add code documentation" },
958
+ { value: "annotations", label: "Include annotations", hint: "Add decorators/attributes" },
959
+ { value: "logging", label: "Include logging", hint: "Add log statements" },
960
+ { value: "testing", label: "Include tests", hint: "Generate test files" }
961
+ ],
962
+ initialValues: ["validations", "comments", "annotations", "logging", "testing"]
963
+ });
964
+ if (p.isCancel(codeStyleOptions)) {
965
+ p.cancel("Operation cancelled.");
966
+ process.exit(0);
967
+ }
968
+ const selectedStyles = codeStyleOptions;
969
+ config.codeStyle = {
970
+ includeValidations: selectedStyles.includes("validations"),
971
+ includeComments: selectedStyles.includes("comments"),
972
+ includeAnnotations: selectedStyles.includes("annotations"),
973
+ includeLogging: selectedStyles.includes("logging"),
974
+ includeTesting: selectedStyles.includes("testing")
975
+ };
976
+ const output = await p.text({
977
+ message: "Output directory",
978
+ initialValue: "./generated",
979
+ validate: (value) => {
980
+ if (!value) return "Output directory is required";
981
+ return void 0;
982
+ }
983
+ });
984
+ if (p.isCancel(output)) {
985
+ p.cancel("Operation cancelled.");
986
+ process.exit(0);
987
+ }
988
+ config.output = output;
989
+ await saveConfig(config);
990
+ p.outro(
991
+ chalk10.green("Config saved to .aerocodingrc.json") + "\n\n" + chalk10.gray(" Run ") + chalk10.cyan("aerocoding generate") + chalk10.gray(" to generate code!")
992
+ );
993
+ } catch (error) {
994
+ if (error.response?.status === 401) {
995
+ await handleUnauthorized(tokenManager);
996
+ } else if (error.response?.data?.message) {
997
+ p.cancel(error.response.data.message);
998
+ } else {
999
+ p.cancel(error.message || "An unexpected error occurred");
1000
+ }
1001
+ process.exit(1);
1002
+ }
1003
+ }
1004
+
1005
+ // src/commands/pull.ts
1006
+ import chalk11 from "chalk";
1007
+ async function pullCommand(options) {
1008
+ console.log(chalk11.yellow("\u26A0\uFE0F Command not yet implemented"));
1009
+ if (options.project) {
1010
+ console.log(chalk11.gray(` Requested project: ${options.project}`));
1011
+ }
1012
+ console.log(chalk11.gray(" Coming soon in v0.2.0\n"));
1013
+ }
1014
+
1015
+ // src/commands/status.ts
1016
+ import chalk12 from "chalk";
1017
+ async function statusCommand() {
1018
+ console.log(chalk12.yellow("\u26A0\uFE0F Command not yet implemented"));
1019
+ console.log(chalk12.gray(" Coming soon in v0.2.0\n"));
1020
+ }
1021
+
1022
+ // src/index.ts
1023
+ import "dotenv/config";
1024
+ var program = new Command();
1025
+ program.name("aerocoding").description("AeroCoding CLI - Generate production-ready code from UML diagrams").version("0.1.0");
1026
+ program.command("login").description("Authenticate with AeroCoding").action(loginCommand);
1027
+ program.command("logout").description("Logout and clear stored credentials").action(logoutCommand);
1028
+ program.command("whoami").description("Show current authenticated user").action(whoamiCommand);
1029
+ program.command("init").description("Initialize AeroCoding in current directory").option("-p, --project <id>", "Project ID (skip selection)").option("-f, --force", "Overwrite existing config without asking").action(initCommand);
1030
+ program.command("pull").description("Pull schema from cloud").option("-p, --project <id>", "Project ID").action(pullCommand);
1031
+ program.command("status").description("Show local schema status").action(statusCommand);
1032
+ program.command("generate").alias("gen").description("Generate code from schema").option("-p, --project <id>", "Project ID").option("-t, --targets <targets...>", "Generation targets (e.g., dotnet-entity)").option("-e, --entities <entities...>", "Filter by entity names").option("-o, --output <dir>", "Output directory", "./generated").option("--backend-preset <preset>", "Backend architecture preset").option("--frontend-preset <preset>", "Frontend architecture preset").option("--backend-layers <layers...>", "Backend layers to generate").option("--frontend-layers <layers...>", "Frontend layers to generate").option("--no-validations", "Exclude validations").option("--no-comments", "Exclude comments").option("--no-annotations", "Exclude annotations").option("--no-logging", "Exclude logging statements").option("--no-testing", "Exclude test files").option("--validation-lib <framework>", "Validation library (e.g., fluentvalidation, zod, formz)").option("-y, --yes", "Skip confirmation prompt").action(generateCommand);
1033
+ program.parse();
1034
+ //# sourceMappingURL=index.js.map