devwing 0.1.6 → 0.1.8
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 +1 -1
- package/dist/index.js +1086 -151
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from 'commander';
|
|
3
|
-
import
|
|
4
|
-
import
|
|
3
|
+
import chalk7 from 'chalk';
|
|
4
|
+
import inquirer5 from 'inquirer';
|
|
5
5
|
import axios2 from 'axios';
|
|
6
6
|
import Conf from 'conf';
|
|
7
7
|
import { AsyncEntry } from '@napi-rs/keyring';
|
|
@@ -10,7 +10,7 @@ import ora from 'ora';
|
|
|
10
10
|
import boxen from 'boxen';
|
|
11
11
|
import gradient from 'gradient-string';
|
|
12
12
|
import terminalLink from 'terminal-link';
|
|
13
|
-
import {
|
|
13
|
+
import { readFileSync, promises } from 'fs';
|
|
14
14
|
import path, { dirname, join } from 'path';
|
|
15
15
|
import os from 'os';
|
|
16
16
|
import { exec, execSync } from 'child_process';
|
|
@@ -72,16 +72,21 @@ var ConfigManager = class {
|
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
/**
|
|
75
|
-
* Get API URL
|
|
75
|
+
* Get API URL — ensures /api/v1 suffix is always present
|
|
76
76
|
*/
|
|
77
77
|
getApiUrl() {
|
|
78
|
-
|
|
78
|
+
let url = this.conf.get("apiUrl");
|
|
79
|
+
if (url && !url.endsWith("/api/v1") && url.includes("devwing.ai")) {
|
|
80
|
+
url = url.replace(/\/+$/, "") + "/api/v1";
|
|
81
|
+
this.conf.set("apiUrl", url);
|
|
82
|
+
}
|
|
83
|
+
return url;
|
|
79
84
|
}
|
|
80
85
|
/**
|
|
81
86
|
* Set API URL (for development/testing)
|
|
82
87
|
*/
|
|
83
88
|
setApiUrl(url) {
|
|
84
|
-
this.conf.set("apiUrl", url);
|
|
89
|
+
this.conf.set("apiUrl", url.replace(/\/+$/, ""));
|
|
85
90
|
}
|
|
86
91
|
/**
|
|
87
92
|
* Get current workspace ID
|
|
@@ -157,6 +162,11 @@ var ConfigManager = class {
|
|
|
157
162
|
}
|
|
158
163
|
};
|
|
159
164
|
var configManager = new ConfigManager();
|
|
165
|
+
function extractErrorMessage(data) {
|
|
166
|
+
if (!data) return "Unknown error";
|
|
167
|
+
if (typeof data === "string") return data;
|
|
168
|
+
return data.detail || data.message || data.error || JSON.stringify(data);
|
|
169
|
+
}
|
|
160
170
|
var APIClient = class {
|
|
161
171
|
client;
|
|
162
172
|
jwtToken = null;
|
|
@@ -180,9 +190,18 @@ var APIClient = class {
|
|
|
180
190
|
(response) => response,
|
|
181
191
|
(error) => {
|
|
182
192
|
if (error.response?.data) {
|
|
183
|
-
|
|
193
|
+
const message = extractErrorMessage(error.response.data);
|
|
194
|
+
const err = new Error(message);
|
|
195
|
+
err.statusCode = error.response.status;
|
|
196
|
+
throw err;
|
|
197
|
+
}
|
|
198
|
+
if (error.code === "ECONNREFUSED") {
|
|
199
|
+
throw new Error("Cannot connect to DevWing API. Is the server running?");
|
|
184
200
|
}
|
|
185
|
-
|
|
201
|
+
if (error.code === "ENOTFOUND") {
|
|
202
|
+
throw new Error("Cannot resolve DevWing API hostname. Check your internet connection.");
|
|
203
|
+
}
|
|
204
|
+
throw new Error(error.message || "Network error");
|
|
186
205
|
}
|
|
187
206
|
);
|
|
188
207
|
}
|
|
@@ -194,11 +213,22 @@ var APIClient = class {
|
|
|
194
213
|
}
|
|
195
214
|
/**
|
|
196
215
|
* Login with email and password
|
|
216
|
+
* Returns full login response including 2FA handling
|
|
197
217
|
*/
|
|
198
218
|
async login(email, password) {
|
|
199
219
|
const response = await this.client.post("/auth/login", { email, password });
|
|
200
220
|
return response.data;
|
|
201
221
|
}
|
|
222
|
+
/**
|
|
223
|
+
* Complete 2FA login
|
|
224
|
+
*/
|
|
225
|
+
async complete2FA(tempToken, totpCode) {
|
|
226
|
+
const response = await this.client.post("/auth/2fa/complete", {
|
|
227
|
+
temp_token: tempToken,
|
|
228
|
+
totp_code: totpCode
|
|
229
|
+
});
|
|
230
|
+
return response.data;
|
|
231
|
+
}
|
|
202
232
|
/**
|
|
203
233
|
* Get current user profile
|
|
204
234
|
*/
|
|
@@ -249,8 +279,13 @@ var APIClient = class {
|
|
|
249
279
|
body: JSON.stringify(request)
|
|
250
280
|
});
|
|
251
281
|
if (!response.ok) {
|
|
252
|
-
|
|
253
|
-
|
|
282
|
+
let errorMsg = `API error (${response.status})`;
|
|
283
|
+
try {
|
|
284
|
+
const error = await response.json();
|
|
285
|
+
errorMsg = extractErrorMessage(error);
|
|
286
|
+
} catch {
|
|
287
|
+
}
|
|
288
|
+
throw new Error(errorMsg);
|
|
254
289
|
}
|
|
255
290
|
if (!response.body) {
|
|
256
291
|
throw new Error("No response body");
|
|
@@ -261,8 +296,7 @@ var APIClient = class {
|
|
|
261
296
|
try {
|
|
262
297
|
const message = JSON.parse(event.data);
|
|
263
298
|
yield message;
|
|
264
|
-
} catch
|
|
265
|
-
console.error("Failed to parse SSE message:", error);
|
|
299
|
+
} catch {
|
|
266
300
|
}
|
|
267
301
|
}
|
|
268
302
|
}
|
|
@@ -288,8 +322,13 @@ var APIClient = class {
|
|
|
288
322
|
})
|
|
289
323
|
});
|
|
290
324
|
if (!response.ok) {
|
|
291
|
-
|
|
292
|
-
|
|
325
|
+
let errorMsg = `API error (${response.status})`;
|
|
326
|
+
try {
|
|
327
|
+
const error = await response.json();
|
|
328
|
+
errorMsg = extractErrorMessage(error);
|
|
329
|
+
} catch {
|
|
330
|
+
}
|
|
331
|
+
throw new Error(errorMsg);
|
|
293
332
|
}
|
|
294
333
|
if (!response.body) {
|
|
295
334
|
throw new Error("No response body");
|
|
@@ -300,18 +339,25 @@ var APIClient = class {
|
|
|
300
339
|
try {
|
|
301
340
|
const message = JSON.parse(event.data);
|
|
302
341
|
yield message;
|
|
303
|
-
} catch
|
|
304
|
-
console.error("Failed to parse SSE message:", error);
|
|
342
|
+
} catch {
|
|
305
343
|
}
|
|
306
344
|
}
|
|
307
345
|
}
|
|
308
346
|
}
|
|
309
347
|
/**
|
|
310
348
|
* Get project memory
|
|
349
|
+
* Backend returns { memories: [...], total: N }
|
|
311
350
|
*/
|
|
312
351
|
async getProjectMemory(projectId) {
|
|
313
352
|
const response = await this.client.get(`/memory/${projectId}`);
|
|
314
|
-
|
|
353
|
+
const data = response.data;
|
|
354
|
+
if (data.memories && Array.isArray(data.memories)) {
|
|
355
|
+
return data.memories;
|
|
356
|
+
}
|
|
357
|
+
if (Array.isArray(data)) {
|
|
358
|
+
return data;
|
|
359
|
+
}
|
|
360
|
+
return [];
|
|
315
361
|
}
|
|
316
362
|
/**
|
|
317
363
|
* Add project memory
|
|
@@ -331,7 +377,14 @@ var APIClient = class {
|
|
|
331
377
|
*/
|
|
332
378
|
async getModels() {
|
|
333
379
|
const response = await this.client.get("/models");
|
|
334
|
-
|
|
380
|
+
const data = response.data;
|
|
381
|
+
if (data.models && Array.isArray(data.models)) {
|
|
382
|
+
return data.models;
|
|
383
|
+
}
|
|
384
|
+
if (Array.isArray(data)) {
|
|
385
|
+
return data;
|
|
386
|
+
}
|
|
387
|
+
return [];
|
|
335
388
|
}
|
|
336
389
|
/**
|
|
337
390
|
* Get usage stats
|
|
@@ -348,37 +401,37 @@ var Logger = class {
|
|
|
348
401
|
* Success message
|
|
349
402
|
*/
|
|
350
403
|
success(message) {
|
|
351
|
-
console.log(
|
|
404
|
+
console.log(chalk7.green("\u2713"), message);
|
|
352
405
|
}
|
|
353
406
|
/**
|
|
354
407
|
* Error message
|
|
355
408
|
*/
|
|
356
409
|
error(message, error) {
|
|
357
|
-
console.log(
|
|
410
|
+
console.log(chalk7.red("\u2717"), message);
|
|
358
411
|
if (error && process.env.DEBUG) {
|
|
359
|
-
console.error(
|
|
412
|
+
console.error(chalk7.gray(error.stack || error));
|
|
360
413
|
}
|
|
361
414
|
}
|
|
362
415
|
/**
|
|
363
416
|
* Warning message
|
|
364
417
|
*/
|
|
365
418
|
warn(message) {
|
|
366
|
-
console.log(
|
|
419
|
+
console.log(chalk7.yellow("\u26A0"), message);
|
|
367
420
|
}
|
|
368
421
|
/**
|
|
369
422
|
* Info message
|
|
370
423
|
*/
|
|
371
424
|
info(message) {
|
|
372
|
-
console.log(
|
|
425
|
+
console.log(chalk7.blue("\u2139"), message);
|
|
373
426
|
}
|
|
374
427
|
/**
|
|
375
428
|
* Debug message (only shown if DEBUG env var is set)
|
|
376
429
|
*/
|
|
377
430
|
debug(message, data) {
|
|
378
431
|
if (process.env.DEBUG) {
|
|
379
|
-
console.log(
|
|
432
|
+
console.log(chalk7.gray("\u2699"), chalk7.gray(message));
|
|
380
433
|
if (data) {
|
|
381
|
-
console.log(
|
|
434
|
+
console.log(chalk7.gray(JSON.stringify(data, null, 2)));
|
|
382
435
|
}
|
|
383
436
|
}
|
|
384
437
|
}
|
|
@@ -467,11 +520,17 @@ var Logger = class {
|
|
|
467
520
|
}
|
|
468
521
|
};
|
|
469
522
|
var logger = new Logger();
|
|
523
|
+
function getUserPlan(user) {
|
|
524
|
+
return user.subscription_plan || user.plan || "free";
|
|
525
|
+
}
|
|
526
|
+
function getUserDisplayName(user) {
|
|
527
|
+
return user.full_name || user.username || user.email;
|
|
528
|
+
}
|
|
470
529
|
async function loginCommand() {
|
|
471
530
|
try {
|
|
472
531
|
const existingKey = await configManager.getApiKey();
|
|
473
532
|
if (existingKey) {
|
|
474
|
-
const { overwrite } = await
|
|
533
|
+
const { overwrite } = await inquirer5.prompt([
|
|
475
534
|
{
|
|
476
535
|
type: "confirm",
|
|
477
536
|
name: "overwrite",
|
|
@@ -486,14 +545,14 @@ async function loginCommand() {
|
|
|
486
545
|
}
|
|
487
546
|
logger.printBanner();
|
|
488
547
|
logger.newline();
|
|
489
|
-
const { method } = await
|
|
548
|
+
const { method } = await inquirer5.prompt([
|
|
490
549
|
{
|
|
491
550
|
type: "list",
|
|
492
551
|
name: "method",
|
|
493
552
|
message: "How would you like to authenticate?",
|
|
494
553
|
choices: [
|
|
495
|
-
{ name: "Email & Password", value: "email" },
|
|
496
554
|
{ name: "Via Browser (easiest)", value: "browser" },
|
|
555
|
+
{ name: "Email & Password", value: "email" },
|
|
497
556
|
{ name: "API Key (from dashboard)", value: "apikey" }
|
|
498
557
|
]
|
|
499
558
|
}
|
|
@@ -506,18 +565,21 @@ async function loginCommand() {
|
|
|
506
565
|
await loginWithApiKey();
|
|
507
566
|
}
|
|
508
567
|
} catch (error) {
|
|
509
|
-
logger.error("Login failed"
|
|
568
|
+
logger.error("Login failed");
|
|
569
|
+
if (error.message) {
|
|
570
|
+
logger.error(error.message);
|
|
571
|
+
}
|
|
510
572
|
process.exit(1);
|
|
511
573
|
}
|
|
512
574
|
}
|
|
513
575
|
async function loginWithEmail() {
|
|
514
|
-
const { email, password } = await
|
|
576
|
+
const { email, password } = await inquirer5.prompt([
|
|
515
577
|
{
|
|
516
578
|
type: "input",
|
|
517
579
|
name: "email",
|
|
518
580
|
message: "Email:",
|
|
519
581
|
validate: (input) => {
|
|
520
|
-
if (!input.includes("@")) {
|
|
582
|
+
if (!input.includes("@") || !input.includes(".")) {
|
|
521
583
|
return "Please enter a valid email address";
|
|
522
584
|
}
|
|
523
585
|
return true;
|
|
@@ -527,23 +589,56 @@ async function loginWithEmail() {
|
|
|
527
589
|
type: "password",
|
|
528
590
|
name: "password",
|
|
529
591
|
message: "Password:",
|
|
530
|
-
mask: "*"
|
|
592
|
+
mask: "*",
|
|
593
|
+
validate: (input) => {
|
|
594
|
+
if (!input || input.length < 1) {
|
|
595
|
+
return "Password is required";
|
|
596
|
+
}
|
|
597
|
+
return true;
|
|
598
|
+
}
|
|
531
599
|
}
|
|
532
600
|
]);
|
|
533
601
|
logger.startSpinner("Authenticating...");
|
|
534
602
|
try {
|
|
535
|
-
|
|
536
|
-
|
|
603
|
+
let loginResult = await apiClient.login(email, password);
|
|
604
|
+
if (loginResult.requires_2fa) {
|
|
605
|
+
logger.succeedSpinner("Credentials verified");
|
|
606
|
+
logger.info("Two-factor authentication is enabled on your account");
|
|
607
|
+
const { totpCode } = await inquirer5.prompt([
|
|
608
|
+
{
|
|
609
|
+
type: "input",
|
|
610
|
+
name: "totpCode",
|
|
611
|
+
message: "2FA Code (from authenticator app):",
|
|
612
|
+
validate: (input) => {
|
|
613
|
+
if (!/^\d{6}$/.test(input)) {
|
|
614
|
+
return "Please enter a 6-digit code";
|
|
615
|
+
}
|
|
616
|
+
return true;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
]);
|
|
620
|
+
logger.startSpinner("Verifying 2FA code...");
|
|
621
|
+
loginResult = await apiClient.complete2FA(loginResult.temp_token, totpCode);
|
|
622
|
+
}
|
|
623
|
+
if (!loginResult.access_token || !loginResult.user) {
|
|
624
|
+
logger.failSpinner("Login failed");
|
|
625
|
+
logger.error("Unexpected response from server");
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
apiClient.setJwtToken(loginResult.access_token);
|
|
537
629
|
logger.updateSpinner("Creating API key...");
|
|
538
|
-
const
|
|
630
|
+
const apiKeyResult = await apiClient.createApiKey("CLI - Auto-generated");
|
|
539
631
|
apiClient.setJwtToken(null);
|
|
540
|
-
await configManager.setApiKey(key);
|
|
541
|
-
|
|
542
|
-
|
|
632
|
+
await configManager.setApiKey(apiKeyResult.key);
|
|
633
|
+
const user = loginResult.user;
|
|
634
|
+
const plan = getUserPlan(user);
|
|
635
|
+
logger.succeedSpinner(`Welcome back, ${getUserDisplayName(user)}!`);
|
|
636
|
+
logger.success(`Plan: ${plan.toUpperCase()}`);
|
|
543
637
|
logger.newline();
|
|
544
638
|
showQuickStart();
|
|
545
639
|
} catch (error) {
|
|
546
640
|
logger.failSpinner("Authentication failed");
|
|
641
|
+
logger.error(error.message || "Unknown error occurred");
|
|
547
642
|
throw error;
|
|
548
643
|
}
|
|
549
644
|
}
|
|
@@ -552,21 +647,23 @@ async function loginWithBrowser() {
|
|
|
552
647
|
const open = await import('open');
|
|
553
648
|
logger.startSpinner("Initiating browser authentication...");
|
|
554
649
|
try {
|
|
555
|
-
const deviceInfo = `DevWing CLI on ${os2.platform()}`;
|
|
650
|
+
const deviceInfo = `DevWing CLI on ${os2.platform()} (${os2.hostname()})`;
|
|
556
651
|
const initResponse = await apiClient.initiateCliAuth(deviceInfo);
|
|
557
|
-
logger.succeedSpinner("
|
|
652
|
+
logger.succeedSpinner("Device code created");
|
|
558
653
|
logger.newline();
|
|
654
|
+
const verifyUrl = initResponse.verification_url_complete;
|
|
559
655
|
try {
|
|
560
|
-
await open.default(
|
|
561
|
-
logger.success("Browser opened! Please log in to
|
|
562
|
-
} catch
|
|
563
|
-
logger.warn("Could not open browser automatically");
|
|
564
|
-
logger.info(`Please visit: ${
|
|
656
|
+
await open.default(verifyUrl);
|
|
657
|
+
logger.success("Browser opened! Please log in to authorize this device.");
|
|
658
|
+
} catch {
|
|
659
|
+
logger.warn("Could not open browser automatically.");
|
|
660
|
+
logger.info(`Please visit: ${chalk7.cyan(verifyUrl)}`);
|
|
565
661
|
}
|
|
566
662
|
logger.newline();
|
|
663
|
+
logger.info(chalk7.dim(`Device code: ${initResponse.user_code.substring(0, 8)}...`));
|
|
567
664
|
logger.startSpinner("Waiting for authorization...");
|
|
568
|
-
const pollInterval = initResponse.interval * 1e3;
|
|
569
|
-
const maxAttempts = Math.floor(initResponse.expires_in / initResponse.interval);
|
|
665
|
+
const pollInterval = (initResponse.interval || 5) * 1e3;
|
|
666
|
+
const maxAttempts = Math.floor((initResponse.expires_in || 900) / (initResponse.interval || 5));
|
|
570
667
|
let attempts = 0;
|
|
571
668
|
const pollForAuth = async () => {
|
|
572
669
|
return new Promise((resolve, reject) => {
|
|
@@ -574,13 +671,11 @@ async function loginWithBrowser() {
|
|
|
574
671
|
attempts++;
|
|
575
672
|
if (attempts >= maxAttempts) {
|
|
576
673
|
clearInterval(interval);
|
|
577
|
-
reject(new Error(
|
|
674
|
+
reject(new Error('Authentication timed out. Please try again with "devwing login".'));
|
|
578
675
|
return;
|
|
579
676
|
}
|
|
580
677
|
try {
|
|
581
|
-
const pollResponse = await apiClient.pollCliAuth(
|
|
582
|
-
initResponse.device_code
|
|
583
|
-
);
|
|
678
|
+
const pollResponse = await apiClient.pollCliAuth(initResponse.device_code);
|
|
584
679
|
if (pollResponse.status === "authorized" && pollResponse.api_key) {
|
|
585
680
|
clearInterval(interval);
|
|
586
681
|
resolve(pollResponse.api_key);
|
|
@@ -589,11 +684,12 @@ async function loginWithBrowser() {
|
|
|
589
684
|
reject(new Error("Authorization denied by user."));
|
|
590
685
|
} else if (pollResponse.status === "expired") {
|
|
591
686
|
clearInterval(interval);
|
|
592
|
-
reject(
|
|
593
|
-
new Error('Device code expired. Please run "devwing login" again.')
|
|
594
|
-
);
|
|
687
|
+
reject(new Error('Device code expired. Please run "devwing login" again.'));
|
|
595
688
|
}
|
|
596
689
|
} catch (error) {
|
|
690
|
+
if (error.statusCode === 429) {
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
597
693
|
clearInterval(interval);
|
|
598
694
|
reject(error);
|
|
599
695
|
}
|
|
@@ -604,12 +700,14 @@ async function loginWithBrowser() {
|
|
|
604
700
|
await configManager.setApiKey(apiKey);
|
|
605
701
|
logger.updateSpinner("Fetching profile...");
|
|
606
702
|
const user = await apiClient.getProfile();
|
|
607
|
-
|
|
608
|
-
logger.
|
|
703
|
+
const plan = getUserPlan(user);
|
|
704
|
+
logger.succeedSpinner(`Welcome, ${getUserDisplayName(user)}!`);
|
|
705
|
+
logger.success(`Plan: ${plan.toUpperCase()}`);
|
|
609
706
|
logger.newline();
|
|
610
707
|
showQuickStart();
|
|
611
708
|
} catch (error) {
|
|
612
709
|
logger.failSpinner("Browser authentication failed");
|
|
710
|
+
logger.error(error.message || "Unknown error");
|
|
613
711
|
throw error;
|
|
614
712
|
}
|
|
615
713
|
}
|
|
@@ -621,7 +719,7 @@ async function loginWithApiKey() {
|
|
|
621
719
|
);
|
|
622
720
|
logger.info(`Get your API key from the ${dashboardUrl}`);
|
|
623
721
|
logger.newline();
|
|
624
|
-
const { apiKey } = await
|
|
722
|
+
const { apiKey } = await inquirer5.prompt([
|
|
625
723
|
{
|
|
626
724
|
type: "password",
|
|
627
725
|
name: "apiKey",
|
|
@@ -642,13 +740,15 @@ async function loginWithApiKey() {
|
|
|
642
740
|
try {
|
|
643
741
|
await configManager.setApiKey(apiKey);
|
|
644
742
|
const user = await apiClient.getProfile();
|
|
645
|
-
|
|
646
|
-
logger.
|
|
743
|
+
const plan = getUserPlan(user);
|
|
744
|
+
logger.succeedSpinner(`Authenticated as ${getUserDisplayName(user)}!`);
|
|
745
|
+
logger.success(`Plan: ${plan.toUpperCase()}`);
|
|
647
746
|
logger.newline();
|
|
648
747
|
showQuickStart();
|
|
649
748
|
} catch (error) {
|
|
650
749
|
await configManager.deleteApiKey();
|
|
651
750
|
logger.failSpinner("Invalid API key");
|
|
751
|
+
logger.error(error.message || "Could not verify API key");
|
|
652
752
|
throw error;
|
|
653
753
|
}
|
|
654
754
|
}
|
|
@@ -659,7 +759,7 @@ async function logoutCommand() {
|
|
|
659
759
|
logger.info("You are not logged in");
|
|
660
760
|
return;
|
|
661
761
|
}
|
|
662
|
-
const { confirm } = await
|
|
762
|
+
const { confirm } = await inquirer5.prompt([
|
|
663
763
|
{
|
|
664
764
|
type: "confirm",
|
|
665
765
|
name: "confirm",
|
|
@@ -677,7 +777,8 @@ async function logoutCommand() {
|
|
|
677
777
|
configManager.setApiUrl(apiUrl);
|
|
678
778
|
logger.success("Successfully logged out");
|
|
679
779
|
} catch (error) {
|
|
680
|
-
logger.error("Logout failed"
|
|
780
|
+
logger.error("Logout failed");
|
|
781
|
+
logger.error(error.message || "Unknown error");
|
|
681
782
|
process.exit(1);
|
|
682
783
|
}
|
|
683
784
|
}
|
|
@@ -692,15 +793,19 @@ async function statusCommand() {
|
|
|
692
793
|
logger.startSpinner("Fetching profile...");
|
|
693
794
|
try {
|
|
694
795
|
const user = await apiClient.getProfile();
|
|
796
|
+
const plan = getUserPlan(user);
|
|
695
797
|
logger.succeedSpinner("Authenticated");
|
|
696
798
|
logger.newline();
|
|
697
|
-
console.log(
|
|
799
|
+
console.log(chalk7.bold("User Profile:"));
|
|
698
800
|
console.log(` Email: ${user.email}`);
|
|
699
|
-
console.log(` Name: ${user
|
|
700
|
-
console.log(` Plan: ${
|
|
701
|
-
|
|
702
|
-
`
|
|
703
|
-
|
|
801
|
+
console.log(` Name: ${getUserDisplayName(user)}`);
|
|
802
|
+
console.log(` Plan: ${plan.toUpperCase()}`);
|
|
803
|
+
if (user.is_verified !== void 0) {
|
|
804
|
+
console.log(` Verified: ${user.is_verified ? chalk7.green("Yes") : chalk7.yellow("No")}`);
|
|
805
|
+
}
|
|
806
|
+
if (user.totp_enabled !== void 0) {
|
|
807
|
+
console.log(` 2FA: ${user.totp_enabled ? chalk7.green("Enabled") : chalk7.dim("Disabled")}`);
|
|
808
|
+
}
|
|
704
809
|
const config = configManager.getAll();
|
|
705
810
|
if (config.workspaceId) {
|
|
706
811
|
console.log(` Workspace: ${config.workspaceId}`);
|
|
@@ -711,11 +816,12 @@ async function statusCommand() {
|
|
|
711
816
|
logger.newline();
|
|
712
817
|
} catch (error) {
|
|
713
818
|
logger.failSpinner("Failed to fetch profile");
|
|
714
|
-
logger.error("API key may be invalid or expired");
|
|
819
|
+
logger.error(error.message || "API key may be invalid or expired");
|
|
715
820
|
logger.info('Run "devwing login" to re-authenticate');
|
|
716
821
|
}
|
|
717
822
|
} catch (error) {
|
|
718
|
-
logger.error("Failed to check status"
|
|
823
|
+
logger.error("Failed to check status");
|
|
824
|
+
logger.error(error.message || "Unknown error");
|
|
719
825
|
process.exit(1);
|
|
720
826
|
}
|
|
721
827
|
}
|
|
@@ -725,6 +831,7 @@ function showQuickStart() {
|
|
|
725
831
|
"Quick Start:",
|
|
726
832
|
"",
|
|
727
833
|
' devwing "fix the auth bug" Ask anything',
|
|
834
|
+
" devwing chat Interactive chat mode",
|
|
728
835
|
" devwing scan Security scan",
|
|
729
836
|
" devwing review Code review",
|
|
730
837
|
" devwing explain file.js Explain code",
|
|
@@ -1292,22 +1399,22 @@ Summary: ${result.summary.changes} changes, ${result.summary.insertions} inserti
|
|
|
1292
1399
|
// src/streaming/renderer.ts
|
|
1293
1400
|
marked.setOptions({
|
|
1294
1401
|
renderer: new TerminalRenderer({
|
|
1295
|
-
code:
|
|
1296
|
-
blockquote:
|
|
1297
|
-
html:
|
|
1298
|
-
heading:
|
|
1299
|
-
firstHeading:
|
|
1300
|
-
hr:
|
|
1301
|
-
listitem:
|
|
1302
|
-
list:
|
|
1303
|
-
table:
|
|
1304
|
-
paragraph:
|
|
1305
|
-
strong:
|
|
1306
|
-
em:
|
|
1307
|
-
codespan:
|
|
1308
|
-
del:
|
|
1309
|
-
link:
|
|
1310
|
-
href:
|
|
1402
|
+
code: chalk7.cyan,
|
|
1403
|
+
blockquote: chalk7.gray.italic,
|
|
1404
|
+
html: chalk7.gray,
|
|
1405
|
+
heading: chalk7.bold.underline,
|
|
1406
|
+
firstHeading: chalk7.bold.magenta,
|
|
1407
|
+
hr: chalk7.reset,
|
|
1408
|
+
listitem: chalk7.reset,
|
|
1409
|
+
list: chalk7.reset,
|
|
1410
|
+
table: chalk7.reset,
|
|
1411
|
+
paragraph: chalk7.reset,
|
|
1412
|
+
strong: chalk7.bold,
|
|
1413
|
+
em: chalk7.italic,
|
|
1414
|
+
codespan: chalk7.cyan,
|
|
1415
|
+
del: chalk7.dim.strikethrough,
|
|
1416
|
+
link: chalk7.blue.underline,
|
|
1417
|
+
href: chalk7.blue.underline
|
|
1311
1418
|
})
|
|
1312
1419
|
});
|
|
1313
1420
|
var StreamingRenderer = class {
|
|
@@ -1402,10 +1509,10 @@ var StreamingRenderer = class {
|
|
|
1402
1509
|
logger.newline();
|
|
1403
1510
|
const { tool, args } = message;
|
|
1404
1511
|
if (!tool) return;
|
|
1405
|
-
logger.info(
|
|
1512
|
+
logger.info(chalk7.yellow(`AI requested tool: ${chalk7.bold(tool)}`));
|
|
1406
1513
|
if (args && Object.keys(args).length > 0) {
|
|
1407
|
-
console.log(
|
|
1408
|
-
console.log(
|
|
1514
|
+
console.log(chalk7.gray("Arguments:"));
|
|
1515
|
+
console.log(chalk7.gray(JSON.stringify(args, null, 2)));
|
|
1409
1516
|
}
|
|
1410
1517
|
logger.newline();
|
|
1411
1518
|
}
|
|
@@ -1417,11 +1524,11 @@ var StreamingRenderer = class {
|
|
|
1417
1524
|
const results = [];
|
|
1418
1525
|
for (const { tool, args } of this.pendingToolCalls) {
|
|
1419
1526
|
logger.newline();
|
|
1420
|
-
logger.info(
|
|
1527
|
+
logger.info(chalk7.yellow(`Executing ${chalk7.bold(tool)}...`));
|
|
1421
1528
|
const requiresConfirmation = this.requiresConfirmation(tool);
|
|
1422
1529
|
let confirmed = true;
|
|
1423
1530
|
if (requiresConfirmation) {
|
|
1424
|
-
const answers = await
|
|
1531
|
+
const answers = await inquirer5.prompt([
|
|
1425
1532
|
{
|
|
1426
1533
|
type: "confirm",
|
|
1427
1534
|
name: "confirmed",
|
|
@@ -1450,9 +1557,9 @@ var StreamingRenderer = class {
|
|
|
1450
1557
|
const content = result.output || "";
|
|
1451
1558
|
const preview = content.substring(0, 500);
|
|
1452
1559
|
if (preview.length < content.length) {
|
|
1453
|
-
console.log(
|
|
1560
|
+
console.log(chalk7.gray(preview + "... (truncated)"));
|
|
1454
1561
|
} else {
|
|
1455
|
-
console.log(
|
|
1562
|
+
console.log(chalk7.gray(preview));
|
|
1456
1563
|
}
|
|
1457
1564
|
results.push({
|
|
1458
1565
|
tool,
|
|
@@ -1490,9 +1597,9 @@ var StreamingRenderer = class {
|
|
|
1490
1597
|
if (message.content) {
|
|
1491
1598
|
const preview = message.content.substring(0, 500);
|
|
1492
1599
|
if (preview.length < message.content.length) {
|
|
1493
|
-
console.log(
|
|
1600
|
+
console.log(chalk7.gray(preview + "... (truncated)"));
|
|
1494
1601
|
} else {
|
|
1495
|
-
console.log(
|
|
1602
|
+
console.log(chalk7.gray(preview));
|
|
1496
1603
|
}
|
|
1497
1604
|
}
|
|
1498
1605
|
logger.newline();
|
|
@@ -1506,7 +1613,7 @@ var StreamingRenderer = class {
|
|
|
1506
1613
|
if (message.usage) {
|
|
1507
1614
|
const { tokens_in, tokens_out, cost_usd } = message.usage;
|
|
1508
1615
|
console.log(
|
|
1509
|
-
|
|
1616
|
+
chalk7.gray(
|
|
1510
1617
|
`\u{1F4CA} Tokens: ${tokens_in.toLocaleString()} in, ${tokens_out.toLocaleString()} out | Cost: $${cost_usd.toFixed(4)}`
|
|
1511
1618
|
)
|
|
1512
1619
|
);
|
|
@@ -1684,7 +1791,7 @@ async function memorySave(projectId, content) {
|
|
|
1684
1791
|
logger.error("Please provide content to save");
|
|
1685
1792
|
return;
|
|
1686
1793
|
}
|
|
1687
|
-
const { type } = await
|
|
1794
|
+
const { type } = await inquirer5.prompt([
|
|
1688
1795
|
{
|
|
1689
1796
|
type: "list",
|
|
1690
1797
|
name: "type",
|
|
@@ -1725,7 +1832,7 @@ async function memoryShow(projectId) {
|
|
|
1725
1832
|
for (const memory of memories) {
|
|
1726
1833
|
const preview = memory.content.length > 100 ? memory.content.substring(0, 100) + "..." : memory.content;
|
|
1727
1834
|
table.push([
|
|
1728
|
-
|
|
1835
|
+
chalk7.cyan(memory.type),
|
|
1729
1836
|
preview,
|
|
1730
1837
|
new Date(memory.created_at).toLocaleDateString()
|
|
1731
1838
|
]);
|
|
@@ -1737,7 +1844,7 @@ async function memoryShow(projectId) {
|
|
|
1737
1844
|
}
|
|
1738
1845
|
}
|
|
1739
1846
|
async function memoryClear(projectId) {
|
|
1740
|
-
const { confirm } = await
|
|
1847
|
+
const { confirm } = await inquirer5.prompt([
|
|
1741
1848
|
{
|
|
1742
1849
|
type: "confirm",
|
|
1743
1850
|
name: "confirm",
|
|
@@ -1766,7 +1873,7 @@ async function configCommand(action, key, value) {
|
|
|
1766
1873
|
if (!action || action === "list") {
|
|
1767
1874
|
const config = configManager.getAll();
|
|
1768
1875
|
const apiKey = await configManager.getApiKey();
|
|
1769
|
-
console.log(
|
|
1876
|
+
console.log(chalk7.bold("Current Configuration:"));
|
|
1770
1877
|
console.log(` API URL: ${config.apiUrl}`);
|
|
1771
1878
|
console.log(` API Key: ${apiKey ? apiKey.substring(0, 12) + "..." : "Not set"}`);
|
|
1772
1879
|
console.log(` Workspace: ${config.workspaceId || "Not set"}`);
|
|
@@ -1774,7 +1881,7 @@ async function configCommand(action, key, value) {
|
|
|
1774
1881
|
console.log(` Model: ${config.model || "Auto"}`);
|
|
1775
1882
|
console.log(` Mode: ${config.mode || "Auto"}`);
|
|
1776
1883
|
console.log();
|
|
1777
|
-
console.log(
|
|
1884
|
+
console.log(chalk7.gray(`Config file: ${configManager.getPath()}`));
|
|
1778
1885
|
return;
|
|
1779
1886
|
}
|
|
1780
1887
|
if (action === "get") {
|
|
@@ -1865,26 +1972,26 @@ async function handleSpecialCommand(command, session, options) {
|
|
|
1865
1972
|
const mainCmd = parts[0].toLowerCase();
|
|
1866
1973
|
if (cmd === "/help") {
|
|
1867
1974
|
logger.box([
|
|
1868
|
-
|
|
1975
|
+
chalk7.bold("DevWing Chat Commands:"),
|
|
1869
1976
|
"",
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1977
|
+
chalk7.bold.yellow("Navigation & Session:"),
|
|
1978
|
+
chalk7.cyan("/help") + " - Show this help message",
|
|
1979
|
+
chalk7.cyan("/exit, /quit") + " - Exit chat mode",
|
|
1980
|
+
chalk7.cyan("/clear") + " - Clear conversation history",
|
|
1981
|
+
chalk7.cyan("/context") + " - Show current context",
|
|
1982
|
+
chalk7.cyan("/history") + " - Show conversation history",
|
|
1876
1983
|
"",
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1984
|
+
chalk7.bold.yellow("Specialized Commands:"),
|
|
1985
|
+
chalk7.cyan("/scan") + " - Run security vulnerability scan",
|
|
1986
|
+
chalk7.cyan("/review") + " - Perform code review",
|
|
1987
|
+
chalk7.cyan("/explain <target>") + " - Explain code, file, or concept",
|
|
1881
1988
|
"",
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1989
|
+
chalk7.bold.yellow("Project Memory:"),
|
|
1990
|
+
chalk7.cyan("/memory save <content>") + " - Save note to project memory",
|
|
1991
|
+
chalk7.cyan("/memory show") + " - Show all project memories",
|
|
1992
|
+
chalk7.cyan("/memory clear") + " - Clear all project memories",
|
|
1886
1993
|
"",
|
|
1887
|
-
|
|
1994
|
+
chalk7.dim("Just type your message to chat with DevWing!")
|
|
1888
1995
|
].join("\n"), { title: "Chat Commands", color: "blue" });
|
|
1889
1996
|
return true;
|
|
1890
1997
|
}
|
|
@@ -1906,9 +2013,9 @@ async function handleSpecialCommand(command, session, options) {
|
|
|
1906
2013
|
return true;
|
|
1907
2014
|
}
|
|
1908
2015
|
logger.box([
|
|
1909
|
-
|
|
2016
|
+
chalk7.bold("Current Context:"),
|
|
1910
2017
|
"",
|
|
1911
|
-
`Working Directory: ${
|
|
2018
|
+
`Working Directory: ${chalk7.cyan(context.cwd)}`,
|
|
1912
2019
|
`Shell: ${context.shell}`,
|
|
1913
2020
|
`OS: ${context.os}`,
|
|
1914
2021
|
`Files: ${context.files.length}`,
|
|
@@ -1927,7 +2034,7 @@ async function handleSpecialCommand(command, session, options) {
|
|
|
1927
2034
|
return true;
|
|
1928
2035
|
}
|
|
1929
2036
|
logger.box([
|
|
1930
|
-
|
|
2037
|
+
chalk7.bold("Conversation History:"),
|
|
1931
2038
|
"",
|
|
1932
2039
|
session.getConversationSummary()
|
|
1933
2040
|
].join("\n"), { title: "History", color: "magenta" });
|
|
@@ -1983,11 +2090,11 @@ async function handleSpecialCommand(command, session, options) {
|
|
|
1983
2090
|
}
|
|
1984
2091
|
function printWelcomeBanner() {
|
|
1985
2092
|
logger.box([
|
|
1986
|
-
|
|
2093
|
+
chalk7.bold.cyan("DevWing AI - Interactive Chat Mode"),
|
|
1987
2094
|
"",
|
|
1988
2095
|
"Chat with AI about your codebase. Type your message and press Enter.",
|
|
1989
2096
|
"",
|
|
1990
|
-
|
|
2097
|
+
chalk7.dim("Type /help for commands \u2022 /exit to quit")
|
|
1991
2098
|
].join("\n"), { title: "\u{1F680} Welcome", color: "cyan" });
|
|
1992
2099
|
}
|
|
1993
2100
|
async function chatCommand(options) {
|
|
@@ -2004,7 +2111,7 @@ async function chatCommand(options) {
|
|
|
2004
2111
|
const rl = readline.createInterface({
|
|
2005
2112
|
input: process.stdin,
|
|
2006
2113
|
output: process.stdout,
|
|
2007
|
-
prompt:
|
|
2114
|
+
prompt: chalk7.bold.green("You: ")
|
|
2008
2115
|
});
|
|
2009
2116
|
rl.on("SIGINT", () => {
|
|
2010
2117
|
logger.newline();
|
|
@@ -2037,7 +2144,7 @@ async function chatCommand(options) {
|
|
|
2037
2144
|
}
|
|
2038
2145
|
session.addMessage("user", userMessage);
|
|
2039
2146
|
logger.newline();
|
|
2040
|
-
logger.info(
|
|
2147
|
+
logger.info(chalk7.dim("DevWing is thinking..."));
|
|
2041
2148
|
logger.newline();
|
|
2042
2149
|
try {
|
|
2043
2150
|
const request = {
|
|
@@ -2203,14 +2310,14 @@ function installUpdate(packageName, packageManager, version) {
|
|
|
2203
2310
|
}
|
|
2204
2311
|
function displayUpdateInfo(currentVersion, latestVersion) {
|
|
2205
2312
|
console.log();
|
|
2206
|
-
console.log(
|
|
2207
|
-
console.log(
|
|
2208
|
-
console.log(
|
|
2313
|
+
console.log(chalk7.cyan("\u2501".repeat(60)));
|
|
2314
|
+
console.log(chalk7.bold.cyan(" DevWing CLI Update Available"));
|
|
2315
|
+
console.log(chalk7.cyan("\u2501".repeat(60)));
|
|
2209
2316
|
console.log();
|
|
2210
|
-
console.log(` ${
|
|
2211
|
-
console.log(` ${
|
|
2317
|
+
console.log(` ${chalk7.gray("Current version:")} ${chalk7.yellow(currentVersion)}`);
|
|
2318
|
+
console.log(` ${chalk7.gray("Latest version:")} ${chalk7.green(latestVersion)}`);
|
|
2212
2319
|
console.log();
|
|
2213
|
-
console.log(
|
|
2320
|
+
console.log(chalk7.gray(" Changelog: https://github.com/devwing/devwing/releases"));
|
|
2214
2321
|
console.log();
|
|
2215
2322
|
}
|
|
2216
2323
|
async function updateCommand(options) {
|
|
@@ -2218,7 +2325,7 @@ async function updateCommand(options) {
|
|
|
2218
2325
|
const currentVersion = getCurrentVersion();
|
|
2219
2326
|
const packageName = getPackageName();
|
|
2220
2327
|
console.log();
|
|
2221
|
-
console.log(
|
|
2328
|
+
console.log(chalk7.cyan(`\u{1F4E6} DevWing CLI v${currentVersion}`));
|
|
2222
2329
|
console.log();
|
|
2223
2330
|
logger.startSpinner("Checking for updates...");
|
|
2224
2331
|
const latestVersion = await getLatestVersion(packageName);
|
|
@@ -2242,9 +2349,9 @@ async function updateCommand(options) {
|
|
|
2242
2349
|
logger.error("No package manager found. Please install npm, pnpm, or yarn.");
|
|
2243
2350
|
return;
|
|
2244
2351
|
}
|
|
2245
|
-
logger.info(`Detected package manager: ${
|
|
2352
|
+
logger.info(`Detected package manager: ${chalk7.cyan(packageManager)}`);
|
|
2246
2353
|
if (!options?.force) {
|
|
2247
|
-
const { confirm } = await
|
|
2354
|
+
const { confirm } = await inquirer5.prompt([
|
|
2248
2355
|
{
|
|
2249
2356
|
type: "confirm",
|
|
2250
2357
|
name: "confirm",
|
|
@@ -2263,13 +2370,13 @@ async function updateCommand(options) {
|
|
|
2263
2370
|
console.log();
|
|
2264
2371
|
logger.success(`Successfully updated to v${latestVersion}`);
|
|
2265
2372
|
console.log();
|
|
2266
|
-
console.log(
|
|
2373
|
+
console.log(chalk7.gray(' Run "devwing --version" to verify'));
|
|
2267
2374
|
console.log();
|
|
2268
2375
|
} else {
|
|
2269
2376
|
console.log();
|
|
2270
2377
|
logger.error("Update failed. Please try manually:");
|
|
2271
2378
|
console.log();
|
|
2272
|
-
console.log(
|
|
2379
|
+
console.log(chalk7.gray(` ${packageManager} install -g ${packageName}@${latestVersion}`));
|
|
2273
2380
|
console.log();
|
|
2274
2381
|
process.exit(1);
|
|
2275
2382
|
}
|
|
@@ -2278,49 +2385,867 @@ async function updateCommand(options) {
|
|
|
2278
2385
|
process.exit(1);
|
|
2279
2386
|
}
|
|
2280
2387
|
}
|
|
2281
|
-
|
|
2282
|
-
|
|
2388
|
+
var TYPING_SPEED = 16;
|
|
2389
|
+
var PAUSE_SHORT = 300;
|
|
2390
|
+
var PAUSE_MEDIUM = 800;
|
|
2391
|
+
var PAUSE_LONG = 1500;
|
|
2392
|
+
function sleep(ms) {
|
|
2393
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2394
|
+
}
|
|
2395
|
+
async function typeOut(text, speed = TYPING_SPEED) {
|
|
2396
|
+
for (const char of text) {
|
|
2397
|
+
process.stdout.write(char);
|
|
2398
|
+
await sleep(speed);
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
async function typeLine(text, speed = TYPING_SPEED) {
|
|
2402
|
+
await typeOut(text, speed);
|
|
2403
|
+
process.stdout.write("\n");
|
|
2404
|
+
}
|
|
2405
|
+
var DEMO_USER = {
|
|
2406
|
+
email: "aliyu@devwing.ai",
|
|
2407
|
+
full_name: "Aliyu Mohammed",
|
|
2408
|
+
subscription_plan: "pro"};
|
|
2409
|
+
var DEMO_API_KEY = "dw_sk_demo_4xK9mQzR2pLvBnWcYjEtHuFi";
|
|
2410
|
+
var DEMO_PASSWORD = "DevWing2026!";
|
|
2411
|
+
var DEMO_WORKSPACE = {
|
|
2412
|
+
name: "Kano State Government",
|
|
2413
|
+
plan: "enterprise"};
|
|
2414
|
+
var DEMO_PROJECT = {
|
|
2415
|
+
name: "Kano Smart API"};
|
|
2416
|
+
var DEMO_MEMORIES = [
|
|
2417
|
+
{ id: "mem_001", type: "rule", content: "All API responses must include Hausa language support", created_at: "2026-03-01T10:00:00Z" },
|
|
2418
|
+
{ id: "mem_002", type: "context", content: "Auth uses JWT with RS256 signing, 1hr expiry, refresh tokens in Redis", created_at: "2026-03-05T14:00:00Z" },
|
|
2419
|
+
{ id: "mem_003", type: "decision", content: "Chose PostgreSQL over MongoDB for ACID compliance \u2014 government data requires strict consistency", created_at: "2026-03-10T09:00:00Z" },
|
|
2420
|
+
{ id: "mem_004", type: "summary", content: 'Kano Smart serves 16M residents via kanostate.gov.ng and smart.kano.gov.ng \u2014 all responses branded "Powered by devwing.ai"', created_at: "2026-03-15T11:00:00Z" }
|
|
2421
|
+
];
|
|
2422
|
+
async function demoLoginCommand() {
|
|
2423
|
+
try {
|
|
2424
|
+
const existingKey = await configManager.getApiKey();
|
|
2425
|
+
if (existingKey) {
|
|
2426
|
+
const { overwrite } = await inquirer5.prompt([
|
|
2427
|
+
{
|
|
2428
|
+
type: "confirm",
|
|
2429
|
+
name: "overwrite",
|
|
2430
|
+
message: "You are already logged in. Do you want to log in with a different account?",
|
|
2431
|
+
default: false
|
|
2432
|
+
}
|
|
2433
|
+
]);
|
|
2434
|
+
if (!overwrite) {
|
|
2435
|
+
logger.info("Login cancelled");
|
|
2436
|
+
return;
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
logger.printBanner();
|
|
2440
|
+
logger.newline();
|
|
2441
|
+
const { method } = await inquirer5.prompt([
|
|
2442
|
+
{
|
|
2443
|
+
type: "list",
|
|
2444
|
+
name: "method",
|
|
2445
|
+
message: "How would you like to authenticate?",
|
|
2446
|
+
choices: [
|
|
2447
|
+
{ name: "Via Browser (easiest)", value: "browser" },
|
|
2448
|
+
{ name: "Email & Password", value: "email" },
|
|
2449
|
+
{ name: "API Key (from dashboard)", value: "apikey" }
|
|
2450
|
+
]
|
|
2451
|
+
}
|
|
2452
|
+
]);
|
|
2453
|
+
if (method === "email") {
|
|
2454
|
+
await demoLoginEmail();
|
|
2455
|
+
} else if (method === "browser") {
|
|
2456
|
+
await demoLoginBrowser();
|
|
2457
|
+
} else {
|
|
2458
|
+
await demoLoginApiKey();
|
|
2459
|
+
}
|
|
2460
|
+
} catch (error) {
|
|
2461
|
+
logger.error("Login failed");
|
|
2462
|
+
if (error.message) logger.error(error.message);
|
|
2463
|
+
process.exit(1);
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
async function demoLoginEmail() {
|
|
2467
|
+
const { email, password } = await inquirer5.prompt([
|
|
2468
|
+
{
|
|
2469
|
+
type: "input",
|
|
2470
|
+
name: "email",
|
|
2471
|
+
message: "Email:",
|
|
2472
|
+
validate: (input) => {
|
|
2473
|
+
if (!input.includes("@") || !input.includes(".")) return "Please enter a valid email address";
|
|
2474
|
+
return true;
|
|
2475
|
+
}
|
|
2476
|
+
},
|
|
2477
|
+
{
|
|
2478
|
+
type: "password",
|
|
2479
|
+
name: "password",
|
|
2480
|
+
message: "Password:",
|
|
2481
|
+
mask: "*"
|
|
2482
|
+
}
|
|
2483
|
+
]);
|
|
2484
|
+
logger.startSpinner("Authenticating...");
|
|
2485
|
+
await sleep(1200);
|
|
2486
|
+
if (email !== DEMO_USER.email || password !== DEMO_PASSWORD) {
|
|
2487
|
+
logger.failSpinner("Authentication failed");
|
|
2488
|
+
logger.error("Incorrect email or password");
|
|
2489
|
+
return;
|
|
2490
|
+
}
|
|
2491
|
+
logger.updateSpinner("Creating API key...");
|
|
2492
|
+
await sleep(800);
|
|
2493
|
+
await configManager.setApiKey(DEMO_API_KEY);
|
|
2494
|
+
logger.succeedSpinner(`Welcome back, ${DEMO_USER.full_name}!`);
|
|
2495
|
+
logger.success(`Plan: ${DEMO_USER.subscription_plan.toUpperCase()}`);
|
|
2496
|
+
logger.newline();
|
|
2497
|
+
showQuickStart2();
|
|
2498
|
+
}
|
|
2499
|
+
async function demoLoginBrowser() {
|
|
2500
|
+
logger.startSpinner("Initiating browser authentication...");
|
|
2501
|
+
await sleep(1e3);
|
|
2502
|
+
logger.succeedSpinner("Device code created");
|
|
2503
|
+
logger.newline();
|
|
2504
|
+
logger.success("Browser opened! Please log in to authorize this device.");
|
|
2505
|
+
logger.newline();
|
|
2506
|
+
logger.info(chalk7.dim("Device code: fSibdCwR..."));
|
|
2507
|
+
logger.startSpinner("Waiting for authorization...");
|
|
2508
|
+
await sleep(3e3);
|
|
2509
|
+
logger.updateSpinner("Authorization received!");
|
|
2510
|
+
await sleep(500);
|
|
2511
|
+
await configManager.setApiKey(DEMO_API_KEY);
|
|
2512
|
+
logger.updateSpinner("Fetching profile...");
|
|
2513
|
+
await sleep(600);
|
|
2514
|
+
logger.succeedSpinner(`Welcome, ${DEMO_USER.full_name}!`);
|
|
2515
|
+
logger.success(`Plan: ${DEMO_USER.subscription_plan.toUpperCase()}`);
|
|
2516
|
+
logger.newline();
|
|
2517
|
+
showQuickStart2();
|
|
2518
|
+
}
|
|
2519
|
+
async function demoLoginApiKey() {
|
|
2520
|
+
logger.info(`Get your API key from the dashboard`);
|
|
2521
|
+
logger.newline();
|
|
2522
|
+
const { apiKey } = await inquirer5.prompt([
|
|
2523
|
+
{
|
|
2524
|
+
type: "password",
|
|
2525
|
+
name: "apiKey",
|
|
2526
|
+
message: "API Key:",
|
|
2527
|
+
mask: "*",
|
|
2528
|
+
validate: (input) => {
|
|
2529
|
+
if (!input.startsWith("dw_sk_")) return 'Invalid API key format. Should start with "dw_sk_"';
|
|
2530
|
+
if (input.length < 20) return "API key seems too short";
|
|
2531
|
+
return true;
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
]);
|
|
2535
|
+
logger.startSpinner("Verifying API key...");
|
|
2536
|
+
await sleep(1e3);
|
|
2537
|
+
if (apiKey !== DEMO_API_KEY) {
|
|
2538
|
+
logger.failSpinner("Invalid API key");
|
|
2539
|
+
logger.error("API key not recognized");
|
|
2540
|
+
return;
|
|
2541
|
+
}
|
|
2542
|
+
await configManager.setApiKey(apiKey);
|
|
2543
|
+
logger.succeedSpinner(`Authenticated as ${DEMO_USER.full_name}!`);
|
|
2544
|
+
logger.success(`Plan: ${DEMO_USER.subscription_plan.toUpperCase()}`);
|
|
2545
|
+
logger.newline();
|
|
2546
|
+
showQuickStart2();
|
|
2547
|
+
}
|
|
2548
|
+
function showQuickStart2() {
|
|
2549
|
+
logger.box(
|
|
2550
|
+
[
|
|
2551
|
+
"Quick Start:",
|
|
2552
|
+
"",
|
|
2553
|
+
' devwing "fix the auth bug" Ask anything',
|
|
2554
|
+
" devwing chat Interactive chat mode",
|
|
2555
|
+
" devwing scan Security scan",
|
|
2556
|
+
" devwing review Code review",
|
|
2557
|
+
" devwing explain file.js Explain code",
|
|
2558
|
+
"",
|
|
2559
|
+
"For more commands, run: devwing --help"
|
|
2560
|
+
].join("\n"),
|
|
2561
|
+
{ title: "Ready to go!", color: "green" }
|
|
2562
|
+
);
|
|
2563
|
+
}
|
|
2564
|
+
async function demoStatusCommand() {
|
|
2565
|
+
const apiKey = await configManager.getApiKey();
|
|
2566
|
+
if (!apiKey) {
|
|
2567
|
+
logger.warn("Not authenticated");
|
|
2568
|
+
logger.info('Run "devwing login" to get started');
|
|
2569
|
+
return;
|
|
2570
|
+
}
|
|
2571
|
+
logger.startSpinner("Fetching profile...");
|
|
2572
|
+
await sleep(800);
|
|
2573
|
+
logger.succeedSpinner("Authenticated");
|
|
2574
|
+
logger.newline();
|
|
2575
|
+
console.log(chalk7.bold("User Profile:"));
|
|
2576
|
+
console.log(` Email: ${DEMO_USER.email}`);
|
|
2577
|
+
console.log(` Name: ${DEMO_USER.full_name}`);
|
|
2578
|
+
console.log(` Plan: ${DEMO_USER.subscription_plan.toUpperCase()}`);
|
|
2579
|
+
console.log(` Verified: ${chalk7.green("Yes")}`);
|
|
2580
|
+
console.log(` 2FA: ${chalk7.dim("Disabled")}`);
|
|
2581
|
+
console.log(` Workspace: ${DEMO_WORKSPACE.name} (${DEMO_WORKSPACE.plan})`);
|
|
2582
|
+
console.log(` Project: ${DEMO_PROJECT.name}`);
|
|
2583
|
+
logger.newline();
|
|
2584
|
+
}
|
|
2585
|
+
async function demoLogoutCommand() {
|
|
2586
|
+
const apiKey = await configManager.getApiKey();
|
|
2587
|
+
if (!apiKey) {
|
|
2588
|
+
logger.info("You are not logged in");
|
|
2589
|
+
return;
|
|
2590
|
+
}
|
|
2591
|
+
const { confirm } = await inquirer5.prompt([
|
|
2592
|
+
{
|
|
2593
|
+
type: "confirm",
|
|
2594
|
+
name: "confirm",
|
|
2595
|
+
message: "Are you sure you want to log out?",
|
|
2596
|
+
default: false
|
|
2597
|
+
}
|
|
2598
|
+
]);
|
|
2599
|
+
if (!confirm) {
|
|
2600
|
+
logger.info("Logout cancelled");
|
|
2601
|
+
return;
|
|
2602
|
+
}
|
|
2603
|
+
await configManager.deleteApiKey();
|
|
2604
|
+
const apiUrl = configManager.getApiUrl();
|
|
2605
|
+
configManager.clear();
|
|
2606
|
+
configManager.setApiUrl(apiUrl);
|
|
2607
|
+
logger.success("Successfully logged out");
|
|
2608
|
+
}
|
|
2609
|
+
async function demoMemoryCommand(action, content) {
|
|
2610
|
+
if (action === "show") {
|
|
2611
|
+
logger.startSpinner("Loading project memory...");
|
|
2612
|
+
await sleep(800);
|
|
2613
|
+
logger.stopSpinner();
|
|
2614
|
+
logger.success(`Found ${DEMO_MEMORIES.length} memory items`);
|
|
2615
|
+
logger.newline();
|
|
2616
|
+
const table = new Table({
|
|
2617
|
+
head: [chalk7.bold("Type"), chalk7.bold("Content"), chalk7.bold("Created")],
|
|
2618
|
+
colWidths: [15, 60, 15],
|
|
2619
|
+
wordWrap: true
|
|
2620
|
+
});
|
|
2621
|
+
for (const memory of DEMO_MEMORIES) {
|
|
2622
|
+
const preview = memory.content.length > 80 ? memory.content.substring(0, 80) + "..." : memory.content;
|
|
2623
|
+
table.push([
|
|
2624
|
+
chalk7.cyan(memory.type),
|
|
2625
|
+
preview,
|
|
2626
|
+
new Date(memory.created_at).toLocaleDateString()
|
|
2627
|
+
]);
|
|
2628
|
+
}
|
|
2629
|
+
console.log(table.toString());
|
|
2630
|
+
} else if (action === "save") {
|
|
2631
|
+
if (!content) {
|
|
2632
|
+
logger.error("Please provide content to save");
|
|
2633
|
+
return;
|
|
2634
|
+
}
|
|
2635
|
+
const { type } = await inquirer5.prompt([
|
|
2636
|
+
{
|
|
2637
|
+
type: "list",
|
|
2638
|
+
name: "type",
|
|
2639
|
+
message: "What type of memory is this?",
|
|
2640
|
+
choices: [
|
|
2641
|
+
{ name: "Context (general information)", value: "context" },
|
|
2642
|
+
{ name: "Rule (coding standard or guideline)", value: "rule" },
|
|
2643
|
+
{ name: "Decision (architectural decision)", value: "decision" },
|
|
2644
|
+
{ name: "Summary (project summary)", value: "summary" }
|
|
2645
|
+
]
|
|
2646
|
+
}
|
|
2647
|
+
]);
|
|
2648
|
+
logger.startSpinner("Saving to project memory...");
|
|
2649
|
+
await sleep(1e3);
|
|
2650
|
+
logger.succeedSpinner(`Memory saved as ${type}`);
|
|
2651
|
+
} else if (action === "clear") {
|
|
2652
|
+
const { confirm } = await inquirer5.prompt([
|
|
2653
|
+
{
|
|
2654
|
+
type: "confirm",
|
|
2655
|
+
name: "confirm",
|
|
2656
|
+
message: "Are you sure you want to clear all project memory?",
|
|
2657
|
+
default: false
|
|
2658
|
+
}
|
|
2659
|
+
]);
|
|
2660
|
+
if (!confirm) {
|
|
2661
|
+
logger.info("Operation cancelled");
|
|
2662
|
+
return;
|
|
2663
|
+
}
|
|
2664
|
+
logger.startSpinner("Clearing project memory...");
|
|
2665
|
+
await sleep(800);
|
|
2666
|
+
logger.succeedSpinner("Project memory cleared");
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
async function demoExplainCommand(target) {
|
|
2670
|
+
await requireDemoAuth();
|
|
2671
|
+
await showContextLoading(24, "general");
|
|
2672
|
+
logger.startSpinner("DevWing is thinking...");
|
|
2673
|
+
await sleep(1500);
|
|
2674
|
+
logger.stopSpinner();
|
|
2675
|
+
console.log();
|
|
2676
|
+
await typeLine(chalk7.bold.magenta(`## Explaining: ${target}`));
|
|
2677
|
+
console.log();
|
|
2678
|
+
if (target.includes("auth") || target.includes("middleware")) {
|
|
2679
|
+
await typeLine(`The auth middleware at ${chalk7.cyan(target)} handles request authentication:`);
|
|
2680
|
+
console.log();
|
|
2681
|
+
await typeLine(" 1. Extracts the JWT token from the `Authorization: Bearer` header");
|
|
2682
|
+
await typeLine(" 2. Verifies the token signature using RS256 algorithm");
|
|
2683
|
+
await typeLine(" 3. Checks token expiration and issuer claims");
|
|
2684
|
+
await typeLine(" 4. Loads the user from the database using the `sub` claim");
|
|
2685
|
+
await typeLine(" 5. Attaches the user object to `req.user` for downstream handlers");
|
|
2686
|
+
console.log();
|
|
2687
|
+
await typeLine(chalk7.bold("Key patterns:"));
|
|
2688
|
+
await typeLine(` ${chalk7.green("\u2022")} Uses decorator pattern \u2014 ${chalk7.cyan("@authenticate")} on route handlers`);
|
|
2689
|
+
await typeLine(` ${chalk7.green("\u2022")} Implements token refresh via ${chalk7.cyan("X-Refresh-Token")} header`);
|
|
2690
|
+
await typeLine(` ${chalk7.green("\u2022")} Rate limited to prevent brute force (10 req/min per IP)`);
|
|
2691
|
+
console.log();
|
|
2692
|
+
await typeLine(chalk7.dim("This middleware is critical \u2014 it protects all /api/* routes."));
|
|
2693
|
+
} else {
|
|
2694
|
+
await typeLine(`${chalk7.cyan(target)} is a core component of the system.`);
|
|
2695
|
+
console.log();
|
|
2696
|
+
await typeLine(` ${chalk7.green("\u2022")} Responsible for handling ${target}-related logic`);
|
|
2697
|
+
await typeLine(` ${chalk7.green("\u2022")} Integrates with the main application pipeline`);
|
|
2698
|
+
await typeLine(` ${chalk7.green("\u2022")} Well-structured with clear separation of concerns`);
|
|
2699
|
+
}
|
|
2700
|
+
await showUsage(1200, 680, 45e-4, "general");
|
|
2701
|
+
logger.success("Done!");
|
|
2702
|
+
}
|
|
2703
|
+
async function demoReviewCommand() {
|
|
2704
|
+
await requireDemoAuth();
|
|
2705
|
+
logger.info("Performing code review...");
|
|
2706
|
+
logger.newline();
|
|
2707
|
+
await showContextLoading(38, "general");
|
|
2708
|
+
logger.startSpinner("DevWing is thinking...");
|
|
2709
|
+
await sleep(2e3);
|
|
2710
|
+
logger.stopSpinner();
|
|
2711
|
+
console.log();
|
|
2712
|
+
await typeLine(chalk7.bold.magenta("## Code Review \u2014 Recent Changes"));
|
|
2713
|
+
console.log();
|
|
2714
|
+
await typeLine(chalk7.bold("Files reviewed: 5 | Commits: 3 | Lines changed: +142 / -67"));
|
|
2715
|
+
console.log();
|
|
2716
|
+
await typeLine(chalk7.green.bold(" \u2713 GOOD") + " \u2014 Error handling is consistent across all new endpoints");
|
|
2717
|
+
await typeLine(chalk7.green.bold(" \u2713 GOOD") + " \u2014 Database queries use parameterized statements");
|
|
2718
|
+
await typeLine(chalk7.green.bold(" \u2713 GOOD") + " \u2014 TypeScript strict mode is properly enforced");
|
|
2719
|
+
console.log();
|
|
2720
|
+
await typeLine(chalk7.yellow.bold(" \u25B2 SUGGESTION") + " \u2014 Consider extracting the validation logic at " + chalk7.cyan("user_service.ts:45"));
|
|
2721
|
+
await typeLine(chalk7.gray(" The email regex pattern is duplicated in 3 files. Move to a shared validator."));
|
|
2722
|
+
console.log();
|
|
2723
|
+
await typeLine(chalk7.yellow.bold(" \u25B2 SUGGESTION") + " \u2014 Missing error boundary in " + chalk7.cyan("api_handler.ts:78"));
|
|
2724
|
+
await typeLine(chalk7.gray(" The async handler doesn't catch unhandled promise rejections. Wrap in try/catch."));
|
|
2725
|
+
console.log();
|
|
2726
|
+
await typeLine(chalk7.red.bold(" \u25A0 ISSUE") + " \u2014 Potential memory leak in " + chalk7.cyan("websocket_manager.ts:120"));
|
|
2727
|
+
await typeLine(chalk7.gray(" Event listeners are added on each connection but never removed on disconnect."));
|
|
2728
|
+
console.log();
|
|
2729
|
+
await typeLine(chalk7.bold("Summary: ") + chalk7.green("3 good") + " | " + chalk7.yellow("2 suggestions") + " | " + chalk7.red("1 issue"));
|
|
2730
|
+
await showUsage(2800, 1200, 95e-4, "general");
|
|
2731
|
+
logger.success("Done!");
|
|
2732
|
+
}
|
|
2733
|
+
async function demoPromptCommand(prompt, options) {
|
|
2734
|
+
await requireDemoAuth();
|
|
2735
|
+
const promptLower = prompt.toLowerCase();
|
|
2736
|
+
if (promptLower.includes("refactor") && promptLower.includes("auth")) {
|
|
2737
|
+
await demoBackendRefactor();
|
|
2738
|
+
} else if (promptLower.includes("scan") || promptLower.includes("owasp") || promptLower.includes("vulnerabilit")) {
|
|
2739
|
+
await demoSecurityScan();
|
|
2740
|
+
} else if (promptLower.includes("explain")) {
|
|
2741
|
+
const target = prompt.replace(/explain\s*/i, "").trim() || "this code";
|
|
2742
|
+
await demoExplainCommand(target);
|
|
2743
|
+
} else if (promptLower.includes("review")) {
|
|
2744
|
+
await demoReviewCommand();
|
|
2745
|
+
} else if (promptLower.includes("deploy") || promptLower.includes("docker") || promptLower.includes("kubernetes")) {
|
|
2746
|
+
await demoDeployAssist();
|
|
2747
|
+
} else if (promptLower.includes("fix") || promptLower.includes("bug") || promptLower.includes("error")) {
|
|
2748
|
+
await demoBugFix();
|
|
2749
|
+
} else if (promptLower.includes("test") || promptLower.includes("spec")) {
|
|
2750
|
+
await demoWriteTests();
|
|
2751
|
+
} else {
|
|
2752
|
+
await demoGenericPrompt(prompt, options);
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
async function demoChatCommand(options) {
|
|
2756
|
+
await requireDemoAuth();
|
|
2757
|
+
const readline2 = await import('readline');
|
|
2758
|
+
console.clear();
|
|
2759
|
+
logger.box([
|
|
2760
|
+
chalk7.bold.cyan("DevWing AI - Interactive Chat Mode"),
|
|
2761
|
+
"",
|
|
2762
|
+
"Chat with AI about your codebase. Type your message and press Enter.",
|
|
2763
|
+
"",
|
|
2764
|
+
chalk7.dim("Type /help for commands | /exit to quit")
|
|
2765
|
+
].join("\n"), { title: "Welcome", color: "cyan" });
|
|
2766
|
+
logger.newline();
|
|
2767
|
+
logger.startSpinner("Loading codebase context...");
|
|
2768
|
+
await sleep(1200);
|
|
2769
|
+
logger.succeedSpinner("Context loaded \u2014 34 files");
|
|
2770
|
+
logger.newline();
|
|
2771
|
+
const rl = readline2.createInterface({
|
|
2772
|
+
input: process.stdin,
|
|
2773
|
+
output: process.stdout,
|
|
2774
|
+
prompt: chalk7.bold.green("You: ")
|
|
2775
|
+
});
|
|
2776
|
+
rl.on("SIGINT", () => {
|
|
2777
|
+
logger.newline();
|
|
2778
|
+
logger.info("Exiting chat mode...");
|
|
2779
|
+
rl.close();
|
|
2780
|
+
process.exit(0);
|
|
2781
|
+
});
|
|
2782
|
+
rl.prompt();
|
|
2783
|
+
rl.on("line", async (input) => {
|
|
2784
|
+
const msg = input.trim();
|
|
2785
|
+
if (!msg) {
|
|
2786
|
+
rl.prompt();
|
|
2787
|
+
return;
|
|
2788
|
+
}
|
|
2789
|
+
if (msg === "/exit" || msg === "/quit") {
|
|
2790
|
+
logger.info("Exiting chat mode...");
|
|
2791
|
+
rl.close();
|
|
2792
|
+
return;
|
|
2793
|
+
}
|
|
2794
|
+
if (msg === "/help") {
|
|
2795
|
+
logger.box([
|
|
2796
|
+
chalk7.bold("DevWing Chat Commands:"),
|
|
2797
|
+
"",
|
|
2798
|
+
chalk7.cyan("/help") + " \u2014 Show this message",
|
|
2799
|
+
chalk7.cyan("/exit") + " \u2014 Exit chat mode",
|
|
2800
|
+
chalk7.cyan("/scan") + " \u2014 Security scan",
|
|
2801
|
+
chalk7.cyan("/review") + " \u2014 Code review",
|
|
2802
|
+
chalk7.cyan("/context") + " \u2014 Show loaded context",
|
|
2803
|
+
chalk7.cyan("/memory show") + " \u2014 Show project memory"
|
|
2804
|
+
].join("\n"), { title: "Commands", color: "blue" });
|
|
2805
|
+
logger.newline();
|
|
2806
|
+
rl.prompt();
|
|
2807
|
+
return;
|
|
2808
|
+
}
|
|
2809
|
+
if (msg === "/context") {
|
|
2810
|
+
logger.box([
|
|
2811
|
+
chalk7.bold("Current Context:"),
|
|
2812
|
+
"",
|
|
2813
|
+
`Working Directory: ${chalk7.cyan(process.cwd())}`,
|
|
2814
|
+
`Shell: zsh | OS: darwin`,
|
|
2815
|
+
`Files: 34 | Git Branch: main`,
|
|
2816
|
+
"",
|
|
2817
|
+
"Top files:",
|
|
2818
|
+
" \u2022 src/auth/auth_controller.ts",
|
|
2819
|
+
" \u2022 src/middleware/jwt.middleware.ts",
|
|
2820
|
+
" \u2022 src/services/user_service.ts",
|
|
2821
|
+
" \u2022 package.json",
|
|
2822
|
+
" ... and 30 more"
|
|
2823
|
+
].join("\n"), { title: "Context", color: "cyan" });
|
|
2824
|
+
logger.newline();
|
|
2825
|
+
rl.prompt();
|
|
2826
|
+
return;
|
|
2827
|
+
}
|
|
2828
|
+
if (msg === "/memory show") {
|
|
2829
|
+
await demoMemoryCommand("show");
|
|
2830
|
+
logger.newline();
|
|
2831
|
+
rl.prompt();
|
|
2832
|
+
return;
|
|
2833
|
+
}
|
|
2834
|
+
if (msg === "/scan") {
|
|
2835
|
+
logger.newline();
|
|
2836
|
+
await demoSecurityScan();
|
|
2837
|
+
logger.newline();
|
|
2838
|
+
rl.prompt();
|
|
2839
|
+
return;
|
|
2840
|
+
}
|
|
2841
|
+
if (msg === "/review") {
|
|
2842
|
+
logger.newline();
|
|
2843
|
+
await demoReviewCommand();
|
|
2844
|
+
logger.newline();
|
|
2845
|
+
rl.prompt();
|
|
2846
|
+
return;
|
|
2847
|
+
}
|
|
2848
|
+
logger.newline();
|
|
2849
|
+
logger.info(chalk7.dim("DevWing is thinking..."));
|
|
2850
|
+
logger.newline();
|
|
2851
|
+
await demoPromptCommand(msg, options);
|
|
2852
|
+
logger.newline();
|
|
2853
|
+
rl.prompt();
|
|
2854
|
+
});
|
|
2855
|
+
rl.on("close", () => {
|
|
2856
|
+
logger.newline();
|
|
2857
|
+
logger.success("Chat session ended. Goodbye!");
|
|
2858
|
+
process.exit(0);
|
|
2859
|
+
});
|
|
2860
|
+
}
|
|
2861
|
+
async function demoBackendRefactor() {
|
|
2862
|
+
await showContextLoading(47, "backend");
|
|
2863
|
+
logger.startSpinner("DevWing is thinking...");
|
|
2864
|
+
await sleep(2e3);
|
|
2865
|
+
logger.stopSpinner();
|
|
2866
|
+
console.log();
|
|
2867
|
+
await typeLine(chalk7.bold.magenta("## Analysis"));
|
|
2868
|
+
console.log();
|
|
2869
|
+
await typeLine("I've analyzed the auth service across 6 files. Here's what I found:");
|
|
2870
|
+
console.log();
|
|
2871
|
+
await typeLine(` ${chalk7.red("\u2022")} Auth logic, validation, and DB queries are tightly coupled in ${chalk7.cyan("auth_controller.ts")}`);
|
|
2872
|
+
await typeLine(` ${chalk7.red("\u2022")} JWT validation is duplicated across 4 route handlers`);
|
|
2873
|
+
await typeLine(` ${chalk7.red("\u2022")} No rate limiting on login/register endpoints`);
|
|
2874
|
+
await typeLine(` ${chalk7.red("\u2022")} User lookup queries repeated in 3 places`);
|
|
2875
|
+
console.log();
|
|
2876
|
+
await sleep(PAUSE_MEDIUM);
|
|
2877
|
+
await typeLine(chalk7.bold.magenta("## Refactor Plan"));
|
|
2878
|
+
console.log();
|
|
2879
|
+
await typeLine("1. Extract JWT validation into reusable middleware");
|
|
2880
|
+
await typeLine("2. Create `UserRepository` with all DB operations");
|
|
2881
|
+
await typeLine("3. Add `RateLimiter` decorator using sliding window algorithm");
|
|
2882
|
+
await typeLine("4. Separate controller into thin route handlers");
|
|
2883
|
+
console.log();
|
|
2884
|
+
await sleep(PAUSE_MEDIUM);
|
|
2885
|
+
await typeLine(chalk7.bold.magenta("## Executing Refactor"));
|
|
2886
|
+
console.log();
|
|
2887
|
+
await showToolCall("read_file", "src/auth/auth_controller.ts");
|
|
2888
|
+
await showToolResult("Read 156 lines");
|
|
2889
|
+
await showToolCall("read_file", "src/auth/auth_routes.ts");
|
|
2890
|
+
await showToolResult("Read 89 lines");
|
|
2891
|
+
await showToolCall("read_file", "src/middleware/index.ts");
|
|
2892
|
+
await showToolResult("Read 23 lines");
|
|
2893
|
+
await showToolCall("write_file", "src/auth/repositories/user.repository.ts");
|
|
2894
|
+
await sleep(PAUSE_LONG);
|
|
2895
|
+
await showToolResult("Created \u2014 UserRepository with findByEmail, findById, create, updateLastLogin");
|
|
2896
|
+
await showToolCall("write_file", "src/middleware/jwt.middleware.ts");
|
|
2897
|
+
await sleep(PAUSE_MEDIUM);
|
|
2898
|
+
await showToolResult("Created \u2014 JWT validation middleware with token extraction and verification");
|
|
2899
|
+
await showToolCall("write_file", "src/middleware/rate-limiter.ts");
|
|
2900
|
+
await sleep(PAUSE_MEDIUM);
|
|
2901
|
+
await showToolResult("Created \u2014 Sliding window rate limiter: 10 req/min login, 5 req/min register");
|
|
2902
|
+
await showToolCall("write_file", "src/auth/auth_controller.ts");
|
|
2903
|
+
await sleep(PAUSE_LONG);
|
|
2904
|
+
await showToolResult("Refactored \u2014 156 lines \u2192 89 lines, clean separation of concerns");
|
|
2905
|
+
await showToolCall("run_command", "npm test -- --filter auth");
|
|
2906
|
+
await sleep(2e3);
|
|
2907
|
+
await showToolResult("All 12 tests passed");
|
|
2908
|
+
console.log();
|
|
2909
|
+
await sleep(PAUSE_MEDIUM);
|
|
2910
|
+
await typeLine(chalk7.bold.magenta("## Summary"));
|
|
2911
|
+
console.log();
|
|
2912
|
+
await typeLine(chalk7.green("\u2713") + " Extracted JWT validation to dedicated middleware");
|
|
2913
|
+
await typeLine(chalk7.green("\u2713") + " Created UserRepository \u2014 single source for all user DB operations");
|
|
2914
|
+
await typeLine(chalk7.green("\u2713") + " Added rate limiting: 10 req/min (login), 5 req/min (register)");
|
|
2915
|
+
await typeLine(chalk7.green("\u2713") + ` Reduced ${chalk7.red("156")} lines \u2192 ${chalk7.green("89")} lines (${chalk7.cyan("43% reduction")})`);
|
|
2916
|
+
await typeLine(chalk7.green("\u2713") + " All 12 existing tests pass \u2014 no regressions");
|
|
2917
|
+
console.log();
|
|
2918
|
+
await typeLine(chalk7.dim("Files modified: 4 | Files created: 3 | Lines removed: 67"));
|
|
2919
|
+
await showUsage(3420, 1890, 0.0127, "backend");
|
|
2920
|
+
logger.success("Done!");
|
|
2921
|
+
}
|
|
2922
|
+
async function demoSecurityScan() {
|
|
2923
|
+
await showContextLoading(32, "security");
|
|
2924
|
+
logger.startSpinner("DevWing is thinking...");
|
|
2925
|
+
await sleep(1800);
|
|
2926
|
+
logger.stopSpinner();
|
|
2927
|
+
console.log();
|
|
2928
|
+
await typeLine(chalk7.bold.magenta("## OWASP Security Scan \u2014 Auth Service"));
|
|
2929
|
+
console.log();
|
|
2930
|
+
await showToolCall("read_file", "src/auth/auth_controller.ts");
|
|
2931
|
+
await showToolResult("Read 89 lines");
|
|
2932
|
+
await showToolCall("read_file", "src/middleware/jwt.middleware.ts");
|
|
2933
|
+
await showToolResult("Read 34 lines");
|
|
2934
|
+
await showToolCall("read_file", "src/auth/repositories/user.repository.ts");
|
|
2935
|
+
await showToolResult("Read 52 lines");
|
|
2936
|
+
await sleep(PAUSE_LONG);
|
|
2937
|
+
await typeLine(chalk7.bold.red("## Vulnerabilities Found: 3"));
|
|
2938
|
+
console.log();
|
|
2939
|
+
await typeLine(chalk7.red.bold(" \u25A0 CRITICAL") + chalk7.red(" \u2014 Weak JWT Token Configuration"));
|
|
2940
|
+
await typeLine(chalk7.gray(" Location: src/middleware/jwt.middleware.ts:12"));
|
|
2941
|
+
await typeLine(chalk7.gray(" Issue: JWT tokens signed with HS256 and no expiration validation"));
|
|
2942
|
+
await typeLine(chalk7.gray(" OWASP: A2:2021 \u2014 Cryptographic Failures"));
|
|
2943
|
+
console.log();
|
|
2944
|
+
await typeLine(chalk7.yellow.bold(" \u25A0 HIGH") + chalk7.yellow(" \u2014 SQL Injection via User Lookup"));
|
|
2945
|
+
await typeLine(chalk7.gray(" Location: src/auth/repositories/user.repository.ts:18"));
|
|
2946
|
+
await typeLine(chalk7.gray(" Issue: Raw string interpolation in findByEmail query"));
|
|
2947
|
+
await typeLine(chalk7.gray(" OWASP: A3:2021 \u2014 Injection"));
|
|
2948
|
+
console.log();
|
|
2949
|
+
await typeLine(chalk7.yellow.bold(" \u25A0 MEDIUM") + chalk7.yellow(" \u2014 Missing Password Complexity Enforcement"));
|
|
2950
|
+
await typeLine(chalk7.gray(" Location: src/auth/auth_controller.ts:34"));
|
|
2951
|
+
await typeLine(chalk7.gray(" Issue: No minimum length, complexity, or breach database check"));
|
|
2952
|
+
await typeLine(chalk7.gray(" OWASP: A7:2021 \u2014 Identification and Authentication Failures"));
|
|
2953
|
+
console.log();
|
|
2954
|
+
await sleep(PAUSE_MEDIUM);
|
|
2955
|
+
await typeLine(chalk7.bold.magenta("## Applying Security Patches"));
|
|
2956
|
+
console.log();
|
|
2957
|
+
await showToolCall("write_file", "src/middleware/jwt.middleware.ts");
|
|
2958
|
+
await sleep(PAUSE_LONG);
|
|
2959
|
+
await showToolResult("Patched \u2014 Added RS256 signing, exp/iat validation, token rotation support");
|
|
2960
|
+
await showToolCall("write_file", "src/auth/repositories/user.repository.ts");
|
|
2961
|
+
await sleep(PAUSE_MEDIUM);
|
|
2962
|
+
await showToolResult("Patched \u2014 Parameterized queries, input sanitization added");
|
|
2963
|
+
await showToolCall("write_file", "src/auth/validators/password.validator.ts");
|
|
2964
|
+
await sleep(PAUSE_MEDIUM);
|
|
2965
|
+
await showToolResult("Created \u2014 Min 12 chars, uppercase, number, special char, breach check");
|
|
2966
|
+
await showToolCall("run_command", "npm test -- --filter auth");
|
|
2967
|
+
await sleep(1500);
|
|
2968
|
+
await showToolResult("All 15 tests passed (3 new security tests added)");
|
|
2969
|
+
console.log();
|
|
2970
|
+
await typeLine(chalk7.bold.magenta("## Scan Complete"));
|
|
2971
|
+
console.log();
|
|
2972
|
+
await typeLine(chalk7.green("\u2713") + " 3 vulnerabilities found and patched");
|
|
2973
|
+
await typeLine(chalk7.green("\u2713") + " JWT upgraded to RS256 with expiration enforcement");
|
|
2974
|
+
await typeLine(chalk7.green("\u2713") + " SQL injection eliminated via parameterized queries");
|
|
2975
|
+
await typeLine(chalk7.green("\u2713") + " Password policy enforced \u2014 NIST 800-63B compliant");
|
|
2976
|
+
await typeLine(chalk7.green("\u2713") + " 3 new security regression tests added");
|
|
2977
|
+
console.log();
|
|
2978
|
+
await typeLine(chalk7.dim("Severity breakdown: 1 critical, 1 high, 1 medium \u2014 all resolved"));
|
|
2979
|
+
await showUsage(2180, 1450, 89e-4, "security");
|
|
2980
|
+
logger.success("Done!");
|
|
2981
|
+
}
|
|
2982
|
+
async function demoDeployAssist(_prompt) {
|
|
2983
|
+
await showContextLoading(18, "devops");
|
|
2984
|
+
logger.startSpinner("DevWing is thinking...");
|
|
2985
|
+
await sleep(1800);
|
|
2986
|
+
logger.stopSpinner();
|
|
2987
|
+
console.log();
|
|
2988
|
+
await typeLine(chalk7.bold.magenta("## Deployment Analysis"));
|
|
2989
|
+
console.log();
|
|
2990
|
+
await showToolCall("read_file", "Dockerfile");
|
|
2991
|
+
await showToolResult("Read 28 lines");
|
|
2992
|
+
await showToolCall("read_file", "docker-compose.yml");
|
|
2993
|
+
await showToolResult("Read 45 lines");
|
|
2994
|
+
await showToolCall("read_file", ".github/workflows/deploy.yml");
|
|
2995
|
+
await showToolResult("Read 67 lines");
|
|
2996
|
+
await sleep(PAUSE_MEDIUM);
|
|
2997
|
+
await typeLine("Current deployment configuration:");
|
|
2998
|
+
console.log();
|
|
2999
|
+
await typeLine(` ${chalk7.green("\u2022")} Docker image: ${chalk7.cyan("node:20-alpine")} \u2014 multi-stage build`);
|
|
3000
|
+
await typeLine(` ${chalk7.green("\u2022")} Target: Railway (detected from ${chalk7.cyan("railway.toml")})`);
|
|
3001
|
+
await typeLine(` ${chalk7.green("\u2022")} Health check: ${chalk7.cyan("/health")} endpoint configured`);
|
|
3002
|
+
await typeLine(` ${chalk7.green("\u2022")} Environment: 12 vars configured, 3 secrets`);
|
|
3003
|
+
console.log();
|
|
3004
|
+
await typeLine(chalk7.bold.magenta("## Optimizations Applied"));
|
|
3005
|
+
console.log();
|
|
3006
|
+
await showToolCall("write_file", "Dockerfile");
|
|
3007
|
+
await sleep(PAUSE_LONG);
|
|
3008
|
+
await showToolResult("Optimized \u2014 Added layer caching, reduced image size from 340MB to 89MB");
|
|
3009
|
+
await showToolCall("write_file", "docker-compose.yml");
|
|
3010
|
+
await sleep(PAUSE_MEDIUM);
|
|
3011
|
+
await showToolResult("Added health checks, restart policies, resource limits");
|
|
3012
|
+
console.log();
|
|
3013
|
+
await typeLine(chalk7.green("\u2713") + " Docker image optimized: 340MB \u2192 89MB (74% smaller)");
|
|
3014
|
+
await typeLine(chalk7.green("\u2713") + " Added proper health checks and restart policies");
|
|
3015
|
+
await typeLine(chalk7.green("\u2713") + " Build time reduced: ~4min \u2192 ~1.5min with layer caching");
|
|
3016
|
+
await showUsage(1800, 950, 65e-4, "devops");
|
|
3017
|
+
logger.success("Done!");
|
|
3018
|
+
}
|
|
3019
|
+
async function demoBugFix(_prompt) {
|
|
3020
|
+
await showContextLoading(35, "backend");
|
|
3021
|
+
logger.startSpinner("DevWing is thinking...");
|
|
3022
|
+
await sleep(1800);
|
|
3023
|
+
logger.stopSpinner();
|
|
3024
|
+
console.log();
|
|
3025
|
+
await typeLine(chalk7.bold.magenta("## Bug Analysis"));
|
|
3026
|
+
console.log();
|
|
3027
|
+
await showToolCall("read_file", "src/services/user_service.ts");
|
|
3028
|
+
await showToolResult("Read 94 lines");
|
|
3029
|
+
await showToolCall("git_diff", "");
|
|
3030
|
+
await showToolResult("3 files changed, 12 insertions, 4 deletions");
|
|
3031
|
+
await showToolCall("git_log", "last 5 commits");
|
|
3032
|
+
await showToolResult("Loaded recent history");
|
|
3033
|
+
await sleep(PAUSE_MEDIUM);
|
|
3034
|
+
await typeLine("I found the issue. The bug is in " + chalk7.cyan("user_service.ts:67") + ":");
|
|
3035
|
+
console.log();
|
|
3036
|
+
await typeLine(chalk7.red(" - const user = await db.query(`SELECT * FROM users WHERE id = ${userId}`);"));
|
|
3037
|
+
await typeLine(chalk7.green(" + const user = await db.query(`SELECT * FROM users WHERE id = $1`, [userId]);"));
|
|
3038
|
+
console.log();
|
|
3039
|
+
await typeLine("The user ID was being interpolated directly into the SQL query instead of");
|
|
3040
|
+
await typeLine("using parameterized binding. This causes:");
|
|
3041
|
+
await typeLine(" 1. SQL injection vulnerability");
|
|
3042
|
+
await typeLine(" 2. Query failure when ID contains special characters");
|
|
3043
|
+
console.log();
|
|
3044
|
+
await showToolCall("write_file", "src/services/user_service.ts");
|
|
3045
|
+
await sleep(PAUSE_LONG);
|
|
3046
|
+
await showToolResult("Fixed \u2014 Parameterized query binding");
|
|
3047
|
+
await showToolCall("run_command", "npm test -- --filter user");
|
|
3048
|
+
await sleep(1500);
|
|
3049
|
+
await showToolResult("All 8 tests passed");
|
|
3050
|
+
console.log();
|
|
3051
|
+
await typeLine(chalk7.green("\u2713") + " Bug fixed \u2014 parameterized SQL query");
|
|
3052
|
+
await typeLine(chalk7.green("\u2713") + " SQL injection vulnerability closed");
|
|
3053
|
+
await typeLine(chalk7.green("\u2713") + " All tests pass");
|
|
3054
|
+
await showUsage(1650, 890, 62e-4, "backend");
|
|
3055
|
+
logger.success("Done!");
|
|
3056
|
+
}
|
|
3057
|
+
async function demoWriteTests(_prompt) {
|
|
3058
|
+
await showContextLoading(22, "backend");
|
|
3059
|
+
logger.startSpinner("DevWing is thinking...");
|
|
3060
|
+
await sleep(1800);
|
|
3061
|
+
logger.stopSpinner();
|
|
3062
|
+
console.log();
|
|
3063
|
+
await typeLine(chalk7.bold.magenta("## Generating Tests"));
|
|
3064
|
+
console.log();
|
|
3065
|
+
await showToolCall("read_file", "src/auth/auth_controller.ts");
|
|
3066
|
+
await showToolResult("Read 89 lines \u2014 4 functions to test");
|
|
3067
|
+
await showToolCall("read_file", "src/auth/repositories/user.repository.ts");
|
|
3068
|
+
await showToolResult("Read 52 lines \u2014 5 methods to test");
|
|
3069
|
+
await sleep(PAUSE_MEDIUM);
|
|
3070
|
+
await showToolCall("write_file", "tests/auth/auth_controller.test.ts");
|
|
3071
|
+
await sleep(PAUSE_LONG);
|
|
3072
|
+
await showToolResult("Created \u2014 8 test cases: login, register, refresh, logout, edge cases");
|
|
3073
|
+
await showToolCall("write_file", "tests/auth/user_repository.test.ts");
|
|
3074
|
+
await sleep(PAUSE_LONG);
|
|
3075
|
+
await showToolResult("Created \u2014 10 test cases: CRUD operations, error handling, constraints");
|
|
3076
|
+
await showToolCall("run_command", "npm test -- --filter auth");
|
|
3077
|
+
await sleep(2e3);
|
|
3078
|
+
await showToolResult("18 / 18 tests passed");
|
|
3079
|
+
console.log();
|
|
3080
|
+
await typeLine(chalk7.bold.magenta("## Test Coverage"));
|
|
3081
|
+
console.log();
|
|
3082
|
+
await typeLine(" auth_controller.ts: " + chalk7.green("94%") + " (was 0%)");
|
|
3083
|
+
await typeLine(" user.repository.ts: " + chalk7.green("98%") + " (was 0%)");
|
|
3084
|
+
await typeLine(" Overall auth module: " + chalk7.green("96%"));
|
|
3085
|
+
console.log();
|
|
3086
|
+
await typeLine(chalk7.green("\u2713") + " 18 tests generated and passing");
|
|
3087
|
+
await typeLine(chalk7.green("\u2713") + " Covers happy paths, edge cases, and error states");
|
|
3088
|
+
await showUsage(2400, 1680, 98e-4, "backend");
|
|
3089
|
+
logger.success("Done!");
|
|
3090
|
+
}
|
|
3091
|
+
async function demoGenericPrompt(prompt, options) {
|
|
3092
|
+
const mode = options.mode || "general";
|
|
3093
|
+
await showContextLoading(28, mode);
|
|
3094
|
+
logger.startSpinner("DevWing is thinking...");
|
|
3095
|
+
await sleep(1500);
|
|
3096
|
+
logger.stopSpinner();
|
|
3097
|
+
console.log();
|
|
3098
|
+
await typeLine(chalk7.bold.magenta("## Response"));
|
|
3099
|
+
console.log();
|
|
3100
|
+
await typeLine(`Based on your codebase analysis, here's my approach to: "${prompt}"`);
|
|
3101
|
+
console.log();
|
|
3102
|
+
await typeLine(` ${chalk7.green("\u2022")} Analyzed project structure and dependencies`);
|
|
3103
|
+
await typeLine(` ${chalk7.green("\u2022")} Reviewed relevant files based on your prompt`);
|
|
3104
|
+
await typeLine(` ${chalk7.green("\u2022")} Generated solution aligned with existing patterns`);
|
|
3105
|
+
console.log();
|
|
3106
|
+
await showUsage(1100, 620, 41e-4, mode);
|
|
3107
|
+
logger.success("Done!");
|
|
3108
|
+
}
|
|
3109
|
+
async function showToolCall(tool, args) {
|
|
3110
|
+
await sleep(PAUSE_SHORT);
|
|
3111
|
+
console.log();
|
|
3112
|
+
console.log(chalk7.yellow(` \u25B6 ${chalk7.bold(tool)}`));
|
|
3113
|
+
if (args) console.log(chalk7.gray(` ${args}`));
|
|
3114
|
+
await sleep(PAUSE_MEDIUM);
|
|
3115
|
+
}
|
|
3116
|
+
async function showToolResult(result) {
|
|
3117
|
+
console.log(chalk7.green(` \u2713 ${result}`));
|
|
3118
|
+
await sleep(PAUSE_SHORT);
|
|
3119
|
+
}
|
|
3120
|
+
async function showContextLoading(fileCount, mode) {
|
|
3121
|
+
logger.startSpinner("Analyzing codebase...");
|
|
3122
|
+
await sleep(1200);
|
|
3123
|
+
logger.updateSpinner(`Scanning ${fileCount} files...`);
|
|
3124
|
+
await sleep(800);
|
|
3125
|
+
logger.updateSpinner("Building dependency graph...");
|
|
3126
|
+
await sleep(600);
|
|
3127
|
+
logger.updateSpinner("Collecting git context...");
|
|
3128
|
+
await sleep(500);
|
|
3129
|
+
logger.succeedSpinner(`Context loaded \u2014 ${fileCount} files, mode: ${chalk7.cyan(mode)}`);
|
|
3130
|
+
console.log();
|
|
3131
|
+
}
|
|
3132
|
+
async function showUsage(tokensIn, tokensOut, cost, mode) {
|
|
3133
|
+
const modelName = mode === "security" ? "devwing-security-1" : mode === "devops" ? "devwing-devops-1" : mode === "frontend" ? "devwing-frontend-1" : "devwing-backend-1";
|
|
3134
|
+
console.log();
|
|
3135
|
+
console.log(
|
|
3136
|
+
chalk7.gray(
|
|
3137
|
+
`\u{1F4CA} Tokens: ${tokensIn.toLocaleString()} in, ${tokensOut.toLocaleString()} out | Cost: $${cost.toFixed(4)} | Model: ${modelName}`
|
|
3138
|
+
)
|
|
3139
|
+
);
|
|
3140
|
+
console.log();
|
|
3141
|
+
}
|
|
3142
|
+
async function requireDemoAuth() {
|
|
3143
|
+
const apiKey = await configManager.getApiKey();
|
|
3144
|
+
if (!apiKey) {
|
|
3145
|
+
logger.error('Not authenticated. Run "devwing login" first.');
|
|
3146
|
+
process.exit(1);
|
|
3147
|
+
}
|
|
3148
|
+
}
|
|
3149
|
+
function isDemoMode() {
|
|
3150
|
+
return process.env.DEVWING_DEMO === "1" || process.env.DEVWING_DEMO === "true";
|
|
3151
|
+
}
|
|
2283
3152
|
var program = new Command();
|
|
2284
|
-
var
|
|
3153
|
+
var __cliFilename = fileURLToPath(import.meta.url);
|
|
3154
|
+
var __cliDirname = dirname(__cliFilename);
|
|
3155
|
+
function getVersion() {
|
|
3156
|
+
const paths = [
|
|
3157
|
+
join(__cliDirname, "../package.json"),
|
|
3158
|
+
join(__cliDirname, "../../package.json")
|
|
3159
|
+
];
|
|
3160
|
+
for (const p of paths) {
|
|
3161
|
+
try {
|
|
3162
|
+
return JSON.parse(readFileSync(p, "utf-8")).version;
|
|
3163
|
+
} catch {
|
|
3164
|
+
continue;
|
|
3165
|
+
}
|
|
3166
|
+
}
|
|
3167
|
+
return "0.1.6";
|
|
3168
|
+
}
|
|
3169
|
+
var VERSION = getVersion();
|
|
2285
3170
|
program.name("devwing").description("DevWing.ai - Your AI Wingman in the Terminal").version(VERSION, "-v, --version", "Display version number").helpOption("-h, --help", "Display help information");
|
|
2286
3171
|
program.option("--mode <mode>", "AI mode: general|frontend|backend|security|devops").option("--model <model>", "Specific model to use").option("--project <id>", "Project ID").option("--workspace <id>", "Workspace ID").option("--verbose", "Verbose output for debugging").option("-y, --yes", "Auto-confirm destructive actions");
|
|
2287
3172
|
program.command("login").description("Authenticate with DevWing").action(async () => {
|
|
2288
|
-
|
|
3173
|
+
if (isDemoMode()) {
|
|
3174
|
+
await demoLoginCommand();
|
|
3175
|
+
} else {
|
|
3176
|
+
await loginCommand();
|
|
3177
|
+
}
|
|
2289
3178
|
});
|
|
2290
3179
|
program.command("logout").description("Clear stored credentials").action(async () => {
|
|
2291
|
-
|
|
3180
|
+
if (isDemoMode()) {
|
|
3181
|
+
await demoLogoutCommand();
|
|
3182
|
+
} else {
|
|
3183
|
+
await logoutCommand();
|
|
3184
|
+
}
|
|
2292
3185
|
});
|
|
2293
3186
|
program.command("status").description("Show authentication status and profile").action(async () => {
|
|
2294
|
-
|
|
3187
|
+
if (isDemoMode()) {
|
|
3188
|
+
await demoStatusCommand();
|
|
3189
|
+
} else {
|
|
3190
|
+
await statusCommand();
|
|
3191
|
+
}
|
|
2295
3192
|
});
|
|
2296
3193
|
program.command("chat").description("Start interactive chat mode (like Claude Code)").action(async () => {
|
|
2297
3194
|
const opts = program.opts();
|
|
2298
|
-
|
|
3195
|
+
if (isDemoMode()) {
|
|
3196
|
+
await demoChatCommand(opts);
|
|
3197
|
+
} else {
|
|
3198
|
+
await chatCommand(opts);
|
|
3199
|
+
}
|
|
2299
3200
|
});
|
|
2300
3201
|
program.command("scan").description("Run security vulnerability scan").action(async () => {
|
|
2301
3202
|
const opts = program.opts();
|
|
2302
|
-
|
|
3203
|
+
if (isDemoMode()) {
|
|
3204
|
+
await demoPromptCommand("scan auth service for OWASP vulnerabilities", { ...opts, mode: "security" });
|
|
3205
|
+
} else {
|
|
3206
|
+
await scanCommand(opts);
|
|
3207
|
+
}
|
|
2303
3208
|
});
|
|
2304
3209
|
program.command("review").description("Perform code review on recent changes").action(async () => {
|
|
2305
3210
|
const opts = program.opts();
|
|
2306
|
-
|
|
3211
|
+
if (isDemoMode()) {
|
|
3212
|
+
await demoReviewCommand();
|
|
3213
|
+
} else {
|
|
3214
|
+
await reviewCommand(opts);
|
|
3215
|
+
}
|
|
2307
3216
|
});
|
|
2308
3217
|
program.command("explain <target>").description("Explain code, file, or concept").action(async (target) => {
|
|
2309
3218
|
const opts = program.opts();
|
|
2310
|
-
|
|
3219
|
+
if (isDemoMode()) {
|
|
3220
|
+
await demoExplainCommand(target);
|
|
3221
|
+
} else {
|
|
3222
|
+
await explainCommand(target, opts);
|
|
3223
|
+
}
|
|
2311
3224
|
});
|
|
2312
3225
|
var memoryCmd = program.command("memory").description("Manage project memory");
|
|
2313
3226
|
memoryCmd.command("save <content>").description("Save information to project memory").action(async (content) => {
|
|
2314
3227
|
const opts = program.opts();
|
|
2315
|
-
|
|
3228
|
+
if (isDemoMode()) {
|
|
3229
|
+
await demoMemoryCommand("save", content);
|
|
3230
|
+
} else {
|
|
3231
|
+
await memoryCommand("save", content, opts);
|
|
3232
|
+
}
|
|
2316
3233
|
});
|
|
2317
3234
|
memoryCmd.command("show").description("Show all project memories").action(async () => {
|
|
2318
3235
|
const opts = program.opts();
|
|
2319
|
-
|
|
3236
|
+
if (isDemoMode()) {
|
|
3237
|
+
await demoMemoryCommand("show");
|
|
3238
|
+
} else {
|
|
3239
|
+
await memoryCommand("show", void 0, opts);
|
|
3240
|
+
}
|
|
2320
3241
|
});
|
|
2321
3242
|
memoryCmd.command("clear").description("Clear all project memories").action(async () => {
|
|
2322
3243
|
const opts = program.opts();
|
|
2323
|
-
|
|
3244
|
+
if (isDemoMode()) {
|
|
3245
|
+
await demoMemoryCommand("clear");
|
|
3246
|
+
} else {
|
|
3247
|
+
await memoryCommand("clear", void 0, opts);
|
|
3248
|
+
}
|
|
2324
3249
|
});
|
|
2325
3250
|
var configCmd = program.command("config").description("Manage CLI configuration");
|
|
2326
3251
|
configCmd.command("list").description("Show all configuration").action(async () => {
|
|
@@ -2338,20 +3263,30 @@ program.command("update").description("Check for and install CLI updates").optio
|
|
|
2338
3263
|
program.argument("[prompt...]", "Natural language prompt for AI (optional - starts chat mode if empty)").action(async (promptParts) => {
|
|
2339
3264
|
const opts = program.opts();
|
|
2340
3265
|
if (promptParts.length === 0) {
|
|
2341
|
-
|
|
3266
|
+
if (isDemoMode()) {
|
|
3267
|
+
await demoChatCommand(opts);
|
|
3268
|
+
} else {
|
|
3269
|
+
await chatCommand(opts);
|
|
3270
|
+
}
|
|
2342
3271
|
return;
|
|
2343
3272
|
}
|
|
2344
3273
|
const prompt = promptParts.join(" ");
|
|
2345
|
-
|
|
3274
|
+
if (isDemoMode()) {
|
|
3275
|
+
await demoPromptCommand(prompt, opts);
|
|
3276
|
+
} else {
|
|
3277
|
+
await promptCommand(prompt, opts);
|
|
3278
|
+
}
|
|
2346
3279
|
});
|
|
2347
3280
|
program.exitOverride();
|
|
2348
3281
|
try {
|
|
2349
3282
|
await program.parseAsync(process.argv);
|
|
2350
3283
|
} catch (error) {
|
|
2351
|
-
if (error.code === "commander.help" || error.code === "commander.version") {
|
|
3284
|
+
if (error.code === "commander.help" || error.code === "commander.helpDisplayed" || error.code === "commander.version") {
|
|
2352
3285
|
process.exit(0);
|
|
2353
3286
|
}
|
|
2354
|
-
|
|
3287
|
+
if (error.message && !error.message.includes("process.exit")) {
|
|
3288
|
+
logger.error(error.message);
|
|
3289
|
+
}
|
|
2355
3290
|
if (process.env.DEBUG) {
|
|
2356
3291
|
console.error(error);
|
|
2357
3292
|
}
|