devwing 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,2066 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import chalk3 from 'chalk';
4
+ import inquirer from 'inquirer';
5
+ import axios2 from 'axios';
6
+ import Conf from 'conf';
7
+ import { AsyncEntry } from '@napi-rs/keyring';
8
+ import { EventSourceParserStream } from 'eventsource-parser/stream';
9
+ import ora from 'ora';
10
+ import boxen from 'boxen';
11
+ import gradient from 'gradient-string';
12
+ import terminalLink from 'terminal-link';
13
+ import { readFileSync, promises } from 'fs';
14
+ import path, { dirname, join } from 'path';
15
+ import os from 'os';
16
+ import { exec, execSync } from 'child_process';
17
+ import { simpleGit } from 'simple-git';
18
+ import { marked } from 'marked';
19
+ import TerminalRenderer from 'marked-terminal';
20
+ import fs2 from 'fs/promises';
21
+ import util from 'util';
22
+ import Table from 'cli-table3';
23
+ import { fileURLToPath } from 'url';
24
+ import semver from 'semver';
25
+
26
+ var SERVICE_NAME = "DevWing CLI";
27
+ var API_KEY_ACCOUNT = "api-key";
28
+ var ConfigManager = class {
29
+ conf;
30
+ constructor() {
31
+ this.conf = new Conf({
32
+ projectName: "devwing",
33
+ defaults: {
34
+ apiUrl: process.env.DEVWING_API_URL || "https://api.devwing.ai"
35
+ }
36
+ });
37
+ }
38
+ /**
39
+ * Get API key from OS keychain
40
+ */
41
+ async getApiKey() {
42
+ try {
43
+ const entry = new AsyncEntry(SERVICE_NAME, API_KEY_ACCOUNT);
44
+ const password = await entry.getPassword();
45
+ return password || null;
46
+ } catch (error) {
47
+ console.error("Failed to retrieve API key from keychain:", error);
48
+ return null;
49
+ }
50
+ }
51
+ /**
52
+ * Store API key in OS keychain
53
+ */
54
+ async setApiKey(apiKey) {
55
+ try {
56
+ const entry = new AsyncEntry(SERVICE_NAME, API_KEY_ACCOUNT);
57
+ await entry.setPassword(apiKey);
58
+ } catch (error) {
59
+ throw new Error(`Failed to store API key in keychain: ${error}`);
60
+ }
61
+ }
62
+ /**
63
+ * Delete API key from OS keychain
64
+ */
65
+ async deleteApiKey() {
66
+ try {
67
+ const entry = new AsyncEntry(SERVICE_NAME, API_KEY_ACCOUNT);
68
+ await entry.deletePassword();
69
+ } catch (error) {
70
+ console.error("Failed to delete API key from keychain:", error);
71
+ }
72
+ }
73
+ /**
74
+ * Get API URL
75
+ */
76
+ getApiUrl() {
77
+ return this.conf.get("apiUrl");
78
+ }
79
+ /**
80
+ * Set API URL (for development/testing)
81
+ */
82
+ setApiUrl(url) {
83
+ this.conf.set("apiUrl", url);
84
+ }
85
+ /**
86
+ * Get current workspace ID
87
+ */
88
+ getWorkspaceId() {
89
+ return this.conf.get("workspaceId");
90
+ }
91
+ /**
92
+ * Set current workspace ID
93
+ */
94
+ setWorkspaceId(workspaceId) {
95
+ this.conf.set("workspaceId", workspaceId);
96
+ }
97
+ /**
98
+ * Get current project ID
99
+ */
100
+ getProjectId() {
101
+ return this.conf.get("projectId");
102
+ }
103
+ /**
104
+ * Set current project ID
105
+ */
106
+ setProjectId(projectId) {
107
+ this.conf.set("projectId", projectId);
108
+ }
109
+ /**
110
+ * Get preferred model
111
+ */
112
+ getModel() {
113
+ return this.conf.get("model");
114
+ }
115
+ /**
116
+ * Set preferred model
117
+ */
118
+ setModel(model) {
119
+ this.conf.set("model", model);
120
+ }
121
+ /**
122
+ * Get preferred AI mode
123
+ */
124
+ getMode() {
125
+ return this.conf.get("mode");
126
+ }
127
+ /**
128
+ * Set preferred AI mode
129
+ */
130
+ setMode(mode) {
131
+ this.conf.set("mode", mode);
132
+ }
133
+ /**
134
+ * Get all config
135
+ */
136
+ getAll() {
137
+ return {
138
+ apiUrl: this.getApiUrl(),
139
+ workspaceId: this.getWorkspaceId(),
140
+ projectId: this.getProjectId(),
141
+ model: this.getModel(),
142
+ mode: this.getMode()
143
+ };
144
+ }
145
+ /**
146
+ * Clear all config (except API key)
147
+ */
148
+ clear() {
149
+ this.conf.clear();
150
+ }
151
+ /**
152
+ * Get config file path (for debugging)
153
+ */
154
+ getPath() {
155
+ return this.conf.path;
156
+ }
157
+ };
158
+ var configManager = new ConfigManager();
159
+ var APIClient = class {
160
+ client;
161
+ jwtToken = null;
162
+ constructor() {
163
+ this.client = axios2.create({
164
+ timeout: 12e4
165
+ // 2 minutes
166
+ });
167
+ this.client.interceptors.request.use(async (config) => {
168
+ const apiKey = await configManager.getApiKey();
169
+ const apiUrl = configManager.getApiUrl();
170
+ config.baseURL = apiUrl;
171
+ if (this.jwtToken) {
172
+ config.headers.Authorization = `Bearer ${this.jwtToken}`;
173
+ } else if (apiKey) {
174
+ config.headers.Authorization = `Bearer ${apiKey}`;
175
+ }
176
+ return config;
177
+ });
178
+ this.client.interceptors.response.use(
179
+ (response) => response,
180
+ (error) => {
181
+ if (error.response?.data) {
182
+ throw new Error(error.response.data.message || error.response.data.error);
183
+ }
184
+ throw error;
185
+ }
186
+ );
187
+ }
188
+ /**
189
+ * Set JWT token for temporary authentication (login flow only)
190
+ */
191
+ setJwtToken(token) {
192
+ this.jwtToken = token;
193
+ }
194
+ /**
195
+ * Login with email and password
196
+ */
197
+ async login(email, password) {
198
+ const response = await this.client.post("/auth/login", { email, password });
199
+ return response.data;
200
+ }
201
+ /**
202
+ * Get current user profile
203
+ */
204
+ async getProfile() {
205
+ const response = await this.client.get("/users/me");
206
+ return response.data;
207
+ }
208
+ /**
209
+ * Create a new API key
210
+ */
211
+ async createApiKey(name) {
212
+ const response = await this.client.post("/api-keys", { name });
213
+ return response.data;
214
+ }
215
+ /**
216
+ * Initiate CLI browser authentication (device flow)
217
+ */
218
+ async initiateCliAuth(deviceInfo) {
219
+ const response = await this.client.post("/auth/cli/initiate", {
220
+ device_info: deviceInfo
221
+ });
222
+ return response.data;
223
+ }
224
+ /**
225
+ * Poll for CLI authentication status
226
+ */
227
+ async pollCliAuth(deviceCode) {
228
+ const response = await this.client.post("/auth/cli/poll", {
229
+ device_code: deviceCode
230
+ });
231
+ return response.data;
232
+ }
233
+ /**
234
+ * Stream AI completion
235
+ */
236
+ async *streamCompletion(request) {
237
+ const apiKey = await configManager.getApiKey();
238
+ const apiUrl = configManager.getApiUrl();
239
+ if (!apiKey) {
240
+ throw new Error('Not authenticated. Run "devwing login" first.');
241
+ }
242
+ const response = await fetch(`${apiUrl}/completions`, {
243
+ method: "POST",
244
+ headers: {
245
+ "Content-Type": "application/json",
246
+ Authorization: `Bearer ${apiKey}`
247
+ },
248
+ body: JSON.stringify(request)
249
+ });
250
+ if (!response.ok) {
251
+ const error = await response.json();
252
+ throw new Error(error.message || "Failed to get completion");
253
+ }
254
+ if (!response.body) {
255
+ throw new Error("No response body");
256
+ }
257
+ const stream = response.body.pipeThrough(new TextDecoderStream()).pipeThrough(new EventSourceParserStream());
258
+ for await (const event of stream) {
259
+ if (event.data) {
260
+ try {
261
+ const message = JSON.parse(event.data);
262
+ yield message;
263
+ } catch (error) {
264
+ console.error("Failed to parse SSE message:", error);
265
+ }
266
+ }
267
+ }
268
+ }
269
+ /**
270
+ * Continue completion with tool results
271
+ */
272
+ async *continueCompletion(sessionId, toolResults) {
273
+ const apiKey = await configManager.getApiKey();
274
+ const apiUrl = configManager.getApiUrl();
275
+ if (!apiKey) {
276
+ throw new Error('Not authenticated. Run "devwing login" first.');
277
+ }
278
+ const response = await fetch(`${apiUrl}/completions/continue`, {
279
+ method: "POST",
280
+ headers: {
281
+ "Content-Type": "application/json",
282
+ Authorization: `Bearer ${apiKey}`
283
+ },
284
+ body: JSON.stringify({
285
+ session_id: sessionId,
286
+ tool_results: toolResults
287
+ })
288
+ });
289
+ if (!response.ok) {
290
+ const error = await response.json();
291
+ throw new Error(error.message || "Failed to continue completion");
292
+ }
293
+ if (!response.body) {
294
+ throw new Error("No response body");
295
+ }
296
+ const stream = response.body.pipeThrough(new TextDecoderStream()).pipeThrough(new EventSourceParserStream());
297
+ for await (const event of stream) {
298
+ if (event.data) {
299
+ try {
300
+ const message = JSON.parse(event.data);
301
+ yield message;
302
+ } catch (error) {
303
+ console.error("Failed to parse SSE message:", error);
304
+ }
305
+ }
306
+ }
307
+ }
308
+ /**
309
+ * Get project memory
310
+ */
311
+ async getProjectMemory(projectId) {
312
+ const response = await this.client.get(`/memory/${projectId}`);
313
+ return response.data;
314
+ }
315
+ /**
316
+ * Add project memory
317
+ */
318
+ async addProjectMemory(projectId, content, type) {
319
+ const response = await this.client.post(`/memory/${projectId}`, { content, type });
320
+ return response.data;
321
+ }
322
+ /**
323
+ * Delete project memory
324
+ */
325
+ async deleteProjectMemory(projectId, memoryId) {
326
+ await this.client.delete(`/memory/${projectId}/${memoryId}`);
327
+ }
328
+ /**
329
+ * Get available models
330
+ */
331
+ async getModels() {
332
+ const response = await this.client.get("/models");
333
+ return response.data;
334
+ }
335
+ /**
336
+ * Get usage stats
337
+ */
338
+ async getUsage(params) {
339
+ const response = await this.client.get("/analytics/overview", { params });
340
+ return response.data;
341
+ }
342
+ };
343
+ var apiClient = new APIClient();
344
+ var Logger = class {
345
+ spinner = null;
346
+ /**
347
+ * Success message
348
+ */
349
+ success(message) {
350
+ console.log(chalk3.green("\u2713"), message);
351
+ }
352
+ /**
353
+ * Error message
354
+ */
355
+ error(message, error) {
356
+ console.log(chalk3.red("\u2717"), message);
357
+ if (error && process.env.DEBUG) {
358
+ console.error(chalk3.gray(error.stack || error));
359
+ }
360
+ }
361
+ /**
362
+ * Warning message
363
+ */
364
+ warn(message) {
365
+ console.log(chalk3.yellow("\u26A0"), message);
366
+ }
367
+ /**
368
+ * Info message
369
+ */
370
+ info(message) {
371
+ console.log(chalk3.blue("\u2139"), message);
372
+ }
373
+ /**
374
+ * Debug message (only shown if DEBUG env var is set)
375
+ */
376
+ debug(message, data) {
377
+ if (process.env.DEBUG) {
378
+ console.log(chalk3.gray("\u2699"), chalk3.gray(message));
379
+ if (data) {
380
+ console.log(chalk3.gray(JSON.stringify(data, null, 2)));
381
+ }
382
+ }
383
+ }
384
+ /**
385
+ * Start a spinner
386
+ */
387
+ startSpinner(text) {
388
+ this.spinner = ora({
389
+ text,
390
+ color: "cyan"
391
+ }).start();
392
+ }
393
+ /**
394
+ * Update spinner text
395
+ */
396
+ updateSpinner(text) {
397
+ if (this.spinner) {
398
+ this.spinner.text = text;
399
+ }
400
+ }
401
+ /**
402
+ * Stop spinner with success
403
+ */
404
+ succeedSpinner(text) {
405
+ if (this.spinner) {
406
+ this.spinner.succeed(text);
407
+ this.spinner = null;
408
+ }
409
+ }
410
+ /**
411
+ * Stop spinner with failure
412
+ */
413
+ failSpinner(text) {
414
+ if (this.spinner) {
415
+ this.spinner.fail(text);
416
+ this.spinner = null;
417
+ }
418
+ }
419
+ /**
420
+ * Stop spinner
421
+ */
422
+ stopSpinner() {
423
+ if (this.spinner) {
424
+ this.spinner.stop();
425
+ this.spinner = null;
426
+ }
427
+ }
428
+ /**
429
+ * Print DevWing banner
430
+ */
431
+ printBanner() {
432
+ const banner = gradient.pastel.multiline([
433
+ "",
434
+ " \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 ",
435
+ " \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D ",
436
+ " \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2557",
437
+ " \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u255A\u2588\u2588\u2557 \u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551",
438
+ " \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u255A\u2588\u2588\u2588\u2588\u2554\u255D \u255A\u2588\u2588\u2588\u2554\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D",
439
+ " \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u255D\u255A\u2550\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D ",
440
+ "",
441
+ " Your AI Wingman in the Terminal",
442
+ ""
443
+ ].join("\n"));
444
+ console.log(banner);
445
+ }
446
+ /**
447
+ * Print a box message
448
+ */
449
+ box(message, options) {
450
+ console.log(
451
+ boxen(message, {
452
+ padding: 1,
453
+ margin: 1,
454
+ borderStyle: "round",
455
+ borderColor: options?.color || "cyan",
456
+ title: options?.title,
457
+ titleAlignment: "center"
458
+ })
459
+ );
460
+ }
461
+ /**
462
+ * Print a newline
463
+ */
464
+ newline() {
465
+ console.log();
466
+ }
467
+ };
468
+ var logger = new Logger();
469
+ async function loginCommand() {
470
+ try {
471
+ const existingKey = await configManager.getApiKey();
472
+ if (existingKey) {
473
+ const { overwrite } = await inquirer.prompt([
474
+ {
475
+ type: "confirm",
476
+ name: "overwrite",
477
+ message: "You are already logged in. Do you want to log in with a different account?",
478
+ default: false
479
+ }
480
+ ]);
481
+ if (!overwrite) {
482
+ logger.info("Login cancelled");
483
+ return;
484
+ }
485
+ }
486
+ logger.printBanner();
487
+ logger.newline();
488
+ const { method } = await inquirer.prompt([
489
+ {
490
+ type: "list",
491
+ name: "method",
492
+ message: "How would you like to authenticate?",
493
+ choices: [
494
+ { name: "Email & Password", value: "email" },
495
+ { name: "Via Browser (easiest)", value: "browser" },
496
+ { name: "API Key (from dashboard)", value: "apikey" }
497
+ ]
498
+ }
499
+ ]);
500
+ if (method === "email") {
501
+ await loginWithEmail();
502
+ } else if (method === "browser") {
503
+ await loginWithBrowser();
504
+ } else {
505
+ await loginWithApiKey();
506
+ }
507
+ } catch (error) {
508
+ logger.error("Login failed", error);
509
+ process.exit(1);
510
+ }
511
+ }
512
+ async function loginWithEmail() {
513
+ const { email, password } = await inquirer.prompt([
514
+ {
515
+ type: "input",
516
+ name: "email",
517
+ message: "Email:",
518
+ validate: (input) => {
519
+ if (!input.includes("@")) {
520
+ return "Please enter a valid email address";
521
+ }
522
+ return true;
523
+ }
524
+ },
525
+ {
526
+ type: "password",
527
+ name: "password",
528
+ message: "Password:",
529
+ mask: "*"
530
+ }
531
+ ]);
532
+ logger.startSpinner("Authenticating...");
533
+ try {
534
+ const { access_token, user } = await apiClient.login(email, password);
535
+ apiClient.setJwtToken(access_token);
536
+ logger.updateSpinner("Creating API key...");
537
+ const { key } = await apiClient.createApiKey("CLI - Auto-generated");
538
+ apiClient.setJwtToken(null);
539
+ await configManager.setApiKey(key);
540
+ logger.succeedSpinner(`Welcome back, ${user.full_name || user.email}!`);
541
+ logger.success(`Plan: ${user.plan.toUpperCase()}`);
542
+ logger.newline();
543
+ showQuickStart();
544
+ } catch (error) {
545
+ logger.failSpinner("Authentication failed");
546
+ throw error;
547
+ }
548
+ }
549
+ async function loginWithBrowser() {
550
+ const os2 = await import('os');
551
+ const open = await import('open');
552
+ logger.startSpinner("Initiating browser authentication...");
553
+ try {
554
+ const deviceInfo = `DevWing CLI on ${os2.platform()}`;
555
+ const initResponse = await apiClient.initiateCliAuth(deviceInfo);
556
+ logger.succeedSpinner("Ready to authenticate!");
557
+ logger.newline();
558
+ logger.box(
559
+ [
560
+ "Browser Authentication",
561
+ "",
562
+ `Code: ${chalk3.bold.yellow(initResponse.user_code)}`,
563
+ "",
564
+ `Opening browser to ${chalk3.cyan(initResponse.verification_url)}`,
565
+ "",
566
+ `Enter the code above when prompted.`,
567
+ "",
568
+ `Expires in ${Math.floor(initResponse.expires_in / 60)} minutes.`
569
+ ].join("\n"),
570
+ { title: "Verify Device", color: "blue" }
571
+ );
572
+ logger.newline();
573
+ logger.info("Opening browser...");
574
+ try {
575
+ await open.default(initResponse.verification_url_complete);
576
+ logger.success("Browser opened! Please complete authentication.");
577
+ } catch (err) {
578
+ logger.warn("Could not open browser automatically");
579
+ logger.info(
580
+ `Please visit: ${chalk3.cyan(initResponse.verification_url_complete)}`
581
+ );
582
+ }
583
+ logger.newline();
584
+ logger.startSpinner("Waiting for authorization...");
585
+ const pollInterval = initResponse.interval * 1e3;
586
+ const maxAttempts = Math.floor(initResponse.expires_in / initResponse.interval);
587
+ let attempts = 0;
588
+ const pollForAuth = async () => {
589
+ return new Promise((resolve, reject) => {
590
+ const interval = setInterval(async () => {
591
+ attempts++;
592
+ if (attempts >= maxAttempts) {
593
+ clearInterval(interval);
594
+ reject(new Error("Authentication timed out. Please try again."));
595
+ return;
596
+ }
597
+ try {
598
+ const pollResponse = await apiClient.pollCliAuth(
599
+ initResponse.device_code
600
+ );
601
+ if (pollResponse.status === "authorized" && pollResponse.api_key) {
602
+ clearInterval(interval);
603
+ resolve(pollResponse.api_key);
604
+ } else if (pollResponse.status === "denied") {
605
+ clearInterval(interval);
606
+ reject(new Error("Authorization denied by user."));
607
+ } else if (pollResponse.status === "expired") {
608
+ clearInterval(interval);
609
+ reject(
610
+ new Error('Device code expired. Please run "devwing login" again.')
611
+ );
612
+ }
613
+ } catch (error) {
614
+ clearInterval(interval);
615
+ reject(error);
616
+ }
617
+ }, pollInterval);
618
+ });
619
+ };
620
+ const apiKey = await pollForAuth();
621
+ await configManager.setApiKey(apiKey);
622
+ logger.updateSpinner("Fetching profile...");
623
+ const user = await apiClient.getProfile();
624
+ logger.succeedSpinner(`Welcome, ${user.full_name || user.email}!`);
625
+ logger.success(`Plan: ${user.plan.toUpperCase()}`);
626
+ logger.newline();
627
+ showQuickStart();
628
+ } catch (error) {
629
+ logger.failSpinner("Browser authentication failed");
630
+ throw error;
631
+ }
632
+ }
633
+ async function loginWithApiKey() {
634
+ const dashboardUrl = terminalLink(
635
+ "dashboard",
636
+ "https://devwing.ai/dashboard/api-keys",
637
+ { fallback: () => "https://devwing.ai/dashboard/api-keys" }
638
+ );
639
+ logger.info(`Get your API key from the ${dashboardUrl}`);
640
+ logger.newline();
641
+ const { apiKey } = await inquirer.prompt([
642
+ {
643
+ type: "password",
644
+ name: "apiKey",
645
+ message: "API Key:",
646
+ mask: "*",
647
+ validate: (input) => {
648
+ if (!input.startsWith("dw_sk_")) {
649
+ return 'Invalid API key format. Should start with "dw_sk_"';
650
+ }
651
+ if (input.length < 20) {
652
+ return "API key seems too short";
653
+ }
654
+ return true;
655
+ }
656
+ }
657
+ ]);
658
+ logger.startSpinner("Verifying API key...");
659
+ try {
660
+ await configManager.setApiKey(apiKey);
661
+ const user = await apiClient.getProfile();
662
+ logger.succeedSpinner(`Successfully authenticated as ${user.full_name || user.email}!`);
663
+ logger.success(`Plan: ${user.plan.toUpperCase()}`);
664
+ logger.newline();
665
+ showQuickStart();
666
+ } catch (error) {
667
+ await configManager.deleteApiKey();
668
+ logger.failSpinner("Invalid API key");
669
+ throw error;
670
+ }
671
+ }
672
+ async function logoutCommand() {
673
+ try {
674
+ const apiKey = await configManager.getApiKey();
675
+ if (!apiKey) {
676
+ logger.info("You are not logged in");
677
+ return;
678
+ }
679
+ const { confirm } = await inquirer.prompt([
680
+ {
681
+ type: "confirm",
682
+ name: "confirm",
683
+ message: "Are you sure you want to log out?",
684
+ default: false
685
+ }
686
+ ]);
687
+ if (!confirm) {
688
+ logger.info("Logout cancelled");
689
+ return;
690
+ }
691
+ await configManager.deleteApiKey();
692
+ const apiUrl = configManager.getApiUrl();
693
+ configManager.clear();
694
+ configManager.setApiUrl(apiUrl);
695
+ logger.success("Successfully logged out");
696
+ } catch (error) {
697
+ logger.error("Logout failed", error);
698
+ process.exit(1);
699
+ }
700
+ }
701
+ async function statusCommand() {
702
+ try {
703
+ const apiKey = await configManager.getApiKey();
704
+ if (!apiKey) {
705
+ logger.warn("Not authenticated");
706
+ logger.info('Run "devwing login" to get started');
707
+ return;
708
+ }
709
+ logger.startSpinner("Fetching profile...");
710
+ try {
711
+ const user = await apiClient.getProfile();
712
+ logger.succeedSpinner("Authenticated");
713
+ logger.newline();
714
+ console.log(chalk3.bold("User Profile:"));
715
+ console.log(` Email: ${user.email}`);
716
+ console.log(` Name: ${user.full_name || "Not set"}`);
717
+ console.log(` Plan: ${user.plan.toUpperCase()}`);
718
+ console.log(
719
+ ` Tokens used today: ${user.tokens_used_today.toLocaleString()}`
720
+ );
721
+ const config = configManager.getAll();
722
+ if (config.workspaceId) {
723
+ console.log(` Workspace: ${config.workspaceId}`);
724
+ }
725
+ if (config.projectId) {
726
+ console.log(` Project: ${config.projectId}`);
727
+ }
728
+ logger.newline();
729
+ } catch (error) {
730
+ logger.failSpinner("Failed to fetch profile");
731
+ logger.error("API key may be invalid or expired");
732
+ logger.info('Run "devwing login" to re-authenticate');
733
+ }
734
+ } catch (error) {
735
+ logger.error("Failed to check status", error);
736
+ process.exit(1);
737
+ }
738
+ }
739
+ function showQuickStart() {
740
+ logger.box(
741
+ [
742
+ "Quick Start:",
743
+ "",
744
+ ' devwing "fix the auth bug" Ask anything',
745
+ " devwing scan Security scan",
746
+ " devwing review Code review",
747
+ " devwing explain file.js Explain code",
748
+ "",
749
+ "For more commands, run: devwing --help"
750
+ ].join("\n"),
751
+ { title: "Ready to go!", color: "green" }
752
+ );
753
+ }
754
+ var DEFAULT_OPTIONS = {
755
+ maxFiles: 50,
756
+ maxFileSize: 100 * 1024,
757
+ // 100KB
758
+ maxTotalTokens: 1e5,
759
+ excludePatterns: [
760
+ "**/node_modules/**",
761
+ "**/dist/**",
762
+ "**/build/**",
763
+ "**/.git/**",
764
+ "**/coverage/**",
765
+ "**/.next/**",
766
+ "**/.vscode/**",
767
+ "**/.idea/**",
768
+ "**/*.log",
769
+ "**/*.lock",
770
+ "**/package-lock.json",
771
+ "**/yarn.lock",
772
+ "**/pnpm-lock.yaml"
773
+ ]
774
+ };
775
+ var ContextCollector = class {
776
+ git;
777
+ cwd;
778
+ options;
779
+ constructor(cwd = process.cwd(), options = {}) {
780
+ this.cwd = cwd;
781
+ this.git = simpleGit(cwd);
782
+ this.options = { ...DEFAULT_OPTIONS, ...options };
783
+ }
784
+ /**
785
+ * Collect all context for AI completion
786
+ */
787
+ async collect(prompt) {
788
+ logger.debug("Collecting context from:", this.cwd);
789
+ const context = {
790
+ files: [],
791
+ cwd: this.cwd,
792
+ shell: this.detectShell(),
793
+ os: os.platform()
794
+ };
795
+ try {
796
+ const isRepo = await this.git.checkIsRepo();
797
+ if (isRepo) {
798
+ context.git_branch = await this.getCurrentBranch();
799
+ context.git_diff = await this.getGitDiff();
800
+ context.git_log = await this.getRecentCommits();
801
+ }
802
+ } catch (error) {
803
+ logger.debug("Not a git repository or git not available");
804
+ }
805
+ context.node_version = this.getNodeVersion();
806
+ context.python_version = this.getPythonVersion();
807
+ context.framework = await this.detectFramework();
808
+ context.files = await this.collectFiles(prompt);
809
+ logger.debug(`Collected ${context.files.length} files`);
810
+ return context;
811
+ }
812
+ /**
813
+ * Collect relevant files based on prompt and project state
814
+ */
815
+ async collectFiles(prompt) {
816
+ const files = [];
817
+ const modifiedFiles = await this.getModifiedFiles();
818
+ const mentionedFiles = prompt ? this.extractFilePaths(prompt) : [];
819
+ const configFiles = await this.getConfigFiles();
820
+ const filePaths = Array.from(
821
+ /* @__PURE__ */ new Set([...modifiedFiles, ...mentionedFiles, ...configFiles])
822
+ );
823
+ for (const filePath of filePaths.slice(0, this.options.maxFiles)) {
824
+ try {
825
+ const fullPath = path.resolve(this.cwd, filePath);
826
+ const stat = await promises.stat(fullPath);
827
+ if (stat.size > this.options.maxFileSize) {
828
+ logger.debug(`Skipping large file: ${filePath} (${stat.size} bytes)`);
829
+ continue;
830
+ }
831
+ const content = await promises.readFile(fullPath, "utf-8");
832
+ const language = this.detectLanguage(filePath);
833
+ files.push({
834
+ path: filePath,
835
+ content,
836
+ language,
837
+ size: stat.size
838
+ });
839
+ } catch (error) {
840
+ logger.debug(`Failed to read file ${filePath}:`, error);
841
+ }
842
+ }
843
+ return files;
844
+ }
845
+ /**
846
+ * Get modified files from git
847
+ */
848
+ async getModifiedFiles() {
849
+ try {
850
+ const status = await this.git.status();
851
+ return [
852
+ ...status.modified,
853
+ ...status.created,
854
+ ...status.staged
855
+ ];
856
+ } catch (error) {
857
+ return [];
858
+ }
859
+ }
860
+ /**
861
+ * Extract file paths mentioned in prompt
862
+ */
863
+ extractFilePaths(prompt) {
864
+ const paths = [];
865
+ const patterns = [
866
+ /[\w-]+\/[\w-/.]+\.\w+/g,
867
+ // path/to/file.ext
868
+ /[\w-]+\.\w+/g
869
+ // file.ext
870
+ ];
871
+ for (const pattern of patterns) {
872
+ const matches = prompt.match(pattern);
873
+ if (matches) {
874
+ paths.push(...matches);
875
+ }
876
+ }
877
+ return paths;
878
+ }
879
+ /**
880
+ * Get important config files
881
+ */
882
+ async getConfigFiles() {
883
+ const configPatterns = [
884
+ "package.json",
885
+ "tsconfig.json",
886
+ "requirements.txt",
887
+ "Pipfile",
888
+ "Dockerfile",
889
+ "docker-compose.yml",
890
+ ".env.example",
891
+ "README.md"
892
+ ];
893
+ const files = [];
894
+ for (const pattern of configPatterns) {
895
+ try {
896
+ const fullPath = path.join(this.cwd, pattern);
897
+ await promises.access(fullPath);
898
+ files.push(pattern);
899
+ } catch {
900
+ }
901
+ }
902
+ return files;
903
+ }
904
+ /**
905
+ * Get current git branch
906
+ */
907
+ async getCurrentBranch() {
908
+ try {
909
+ const branch = await this.git.revparse(["--abbrev-ref", "HEAD"]);
910
+ return branch.trim();
911
+ } catch {
912
+ return "unknown";
913
+ }
914
+ }
915
+ /**
916
+ * Get git diff
917
+ */
918
+ async getGitDiff() {
919
+ try {
920
+ const diff = await this.git.diff();
921
+ return diff.substring(0, 1e4);
922
+ } catch {
923
+ return "";
924
+ }
925
+ }
926
+ /**
927
+ * Get recent commits
928
+ */
929
+ async getRecentCommits() {
930
+ try {
931
+ const log = await this.git.log({ maxCount: 5 });
932
+ return log.all.map((commit) => `${commit.hash.substring(0, 7)} - ${commit.message}`).join("\n");
933
+ } catch {
934
+ return "";
935
+ }
936
+ }
937
+ /**
938
+ * Detect shell type
939
+ */
940
+ detectShell() {
941
+ return path.basename(process.env.SHELL || "sh");
942
+ }
943
+ /**
944
+ * Get Node.js version
945
+ */
946
+ getNodeVersion() {
947
+ try {
948
+ return process.version;
949
+ } catch {
950
+ return void 0;
951
+ }
952
+ }
953
+ /**
954
+ * Get Python version
955
+ */
956
+ getPythonVersion() {
957
+ try {
958
+ const version = execSync("python --version 2>&1", { encoding: "utf-8" });
959
+ return version.trim();
960
+ } catch {
961
+ return void 0;
962
+ }
963
+ }
964
+ /**
965
+ * Detect framework from package.json or requirements.txt
966
+ */
967
+ async detectFramework() {
968
+ try {
969
+ const packageJsonPath = path.join(this.cwd, "package.json");
970
+ try {
971
+ const packageJson = JSON.parse(await promises.readFile(packageJsonPath, "utf-8"));
972
+ const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
973
+ if (deps.next) return "Next.js";
974
+ if (deps.react) return "React";
975
+ if (deps.vue) return "Vue";
976
+ if (deps["@angular/core"]) return "Angular";
977
+ if (deps.express) return "Express";
978
+ if (deps.fastify) return "Fastify";
979
+ } catch {
980
+ }
981
+ const requirementsPath = path.join(this.cwd, "requirements.txt");
982
+ try {
983
+ const requirements = await promises.readFile(requirementsPath, "utf-8");
984
+ if (requirements.includes("django")) return "Django";
985
+ if (requirements.includes("flask")) return "Flask";
986
+ if (requirements.includes("fastapi")) return "FastAPI";
987
+ } catch {
988
+ }
989
+ } catch (error) {
990
+ logger.debug("Failed to detect framework:", error);
991
+ }
992
+ return void 0;
993
+ }
994
+ /**
995
+ * Detect programming language from file extension
996
+ */
997
+ detectLanguage(filePath) {
998
+ const ext = path.extname(filePath).toLowerCase();
999
+ const languageMap = {
1000
+ ".js": "javascript",
1001
+ ".jsx": "javascript",
1002
+ ".ts": "typescript",
1003
+ ".tsx": "typescript",
1004
+ ".py": "python",
1005
+ ".rb": "ruby",
1006
+ ".go": "go",
1007
+ ".rs": "rust",
1008
+ ".java": "java",
1009
+ ".cpp": "cpp",
1010
+ ".c": "c",
1011
+ ".h": "c",
1012
+ ".hpp": "cpp",
1013
+ ".cs": "csharp",
1014
+ ".php": "php",
1015
+ ".swift": "swift",
1016
+ ".kt": "kotlin",
1017
+ ".dart": "dart",
1018
+ ".sh": "bash",
1019
+ ".yml": "yaml",
1020
+ ".yaml": "yaml",
1021
+ ".json": "json",
1022
+ ".xml": "xml",
1023
+ ".html": "html",
1024
+ ".css": "css",
1025
+ ".scss": "scss",
1026
+ ".md": "markdown",
1027
+ ".sql": "sql"
1028
+ };
1029
+ return languageMap[ext] || "text";
1030
+ }
1031
+ };
1032
+ var execAsync = util.promisify(exec);
1033
+ var ToolEngine = class {
1034
+ allowedProjectPath;
1035
+ commandTimeout = 3e4;
1036
+ // 30 seconds
1037
+ maxFileSize = 10 * 1024 * 1024;
1038
+ // 10MB
1039
+ constructor(allowedProjectPath = process.cwd()) {
1040
+ this.allowedProjectPath = path.resolve(allowedProjectPath);
1041
+ }
1042
+ /**
1043
+ * Validate and resolve file path to prevent directory traversal
1044
+ */
1045
+ validatePath(filePath) {
1046
+ const resolvedPath = path.resolve(this.allowedProjectPath, filePath);
1047
+ if (!resolvedPath.startsWith(this.allowedProjectPath)) {
1048
+ throw new Error(`Security Error: Path '${filePath}' is outside allowed project directory`);
1049
+ }
1050
+ return resolvedPath;
1051
+ }
1052
+ // =================================================================
1053
+ // FILE OPERATIONS
1054
+ // =================================================================
1055
+ async readFile(filePath) {
1056
+ try {
1057
+ const validatedPath = this.validatePath(filePath);
1058
+ try {
1059
+ const stat = await fs2.stat(validatedPath);
1060
+ if (!stat.isFile()) {
1061
+ return { success: false, output: "", error: `Path is not a file: ${filePath}` };
1062
+ }
1063
+ if (stat.size > this.maxFileSize) {
1064
+ return { success: false, output: "", error: `File too large: ${stat.size} bytes (max ${this.maxFileSize})` };
1065
+ }
1066
+ const content = await fs2.readFile(validatedPath, "utf-8");
1067
+ return {
1068
+ success: true,
1069
+ output: content,
1070
+ metadata: { size: stat.size, lines: content.split("\n").length }
1071
+ };
1072
+ } catch (e) {
1073
+ if (e.code === "ENOENT") {
1074
+ return { success: false, output: "", error: `File not found: ${filePath}` };
1075
+ }
1076
+ throw e;
1077
+ }
1078
+ } catch (e) {
1079
+ return { success: false, output: "", error: e.message };
1080
+ }
1081
+ }
1082
+ async writeFile(filePath, content, userConfirmed = false) {
1083
+ if (!userConfirmed) {
1084
+ return { success: false, output: "", error: "User confirmation required for write_file operation" };
1085
+ }
1086
+ try {
1087
+ const validatedPath = this.validatePath(filePath);
1088
+ await fs2.mkdir(path.dirname(validatedPath), { recursive: true });
1089
+ await fs2.writeFile(validatedPath, content, "utf-8");
1090
+ return {
1091
+ success: true,
1092
+ output: `File written: ${filePath}`,
1093
+ metadata: { path: validatedPath, size: Buffer.byteLength(content, "utf8") }
1094
+ };
1095
+ } catch (e) {
1096
+ return { success: false, output: "", error: e.message };
1097
+ }
1098
+ }
1099
+ async listDirectory(dirPath = ".") {
1100
+ try {
1101
+ const validatedPath = this.validatePath(dirPath);
1102
+ try {
1103
+ const stat = await fs2.stat(validatedPath);
1104
+ if (!stat.isDirectory()) {
1105
+ return { success: false, output: "", error: `Path is not a directory: ${dirPath}` };
1106
+ }
1107
+ const items = await fs2.readdir(validatedPath, { withFileTypes: true });
1108
+ const formattedItems = await Promise.all(items.map(async (item) => {
1109
+ const itemType = item.isDirectory() ? "dir" : "file";
1110
+ let size = 0;
1111
+ if (item.isFile()) {
1112
+ try {
1113
+ const itemStat = await fs2.stat(path.join(validatedPath, item.name));
1114
+ size = itemStat.size;
1115
+ } catch (e) {
1116
+ }
1117
+ }
1118
+ return { name: item.name, type: itemType, size };
1119
+ }));
1120
+ formattedItems.sort((a, b) => {
1121
+ if (a.type === b.type) return a.name.localeCompare(b.name);
1122
+ return a.type === "dir" ? -1 : 1;
1123
+ });
1124
+ const outputLines = formattedItems.map((item) => {
1125
+ return item.type === "dir" ? `\u{1F4C1} ${item.name}/` : `\u{1F4C4} ${item.name} (${item.size} bytes)`;
1126
+ });
1127
+ return {
1128
+ success: true,
1129
+ output: outputLines.join("\n"),
1130
+ metadata: { item_count: formattedItems.length }
1131
+ };
1132
+ } catch (e) {
1133
+ if (e.code === "ENOENT") {
1134
+ return { success: false, output: "", error: `Directory not found: ${dirPath}` };
1135
+ }
1136
+ throw e;
1137
+ }
1138
+ } catch (e) {
1139
+ return { success: false, output: "", error: e.message };
1140
+ }
1141
+ }
1142
+ // =================================================================
1143
+ // SHELL EXECUTION
1144
+ // =================================================================
1145
+ async runCommand(command, cwd, userConfirmed = false) {
1146
+ if (!userConfirmed) {
1147
+ return { success: false, output: "", error: "User confirmation required for run_command operation" };
1148
+ }
1149
+ try {
1150
+ let execCwd = this.allowedProjectPath;
1151
+ if (cwd) {
1152
+ execCwd = this.validatePath(cwd);
1153
+ const stat = await fs2.stat(execCwd);
1154
+ if (!stat.isDirectory()) {
1155
+ return { success: false, output: "", error: `Working directory not found: ${cwd}` };
1156
+ }
1157
+ }
1158
+ try {
1159
+ const { stdout, stderr } = await execAsync(command, {
1160
+ cwd: execCwd,
1161
+ timeout: this.commandTimeout,
1162
+ env: { ...process.env, TERM: "dumb" }
1163
+ });
1164
+ return {
1165
+ success: true,
1166
+ output: stdout || stderr,
1167
+ // Some commands write to stderr even on success
1168
+ metadata: { command }
1169
+ };
1170
+ } catch (e) {
1171
+ return {
1172
+ success: false,
1173
+ output: e.stdout || "",
1174
+ error: e.stderr || e.message,
1175
+ metadata: { command, exit_code: e.code }
1176
+ };
1177
+ }
1178
+ } catch (e) {
1179
+ return { success: false, output: "", error: e.message };
1180
+ }
1181
+ }
1182
+ // =================================================================
1183
+ // GIT OPERATIONS
1184
+ // =================================================================
1185
+ async gitDiff(cwd) {
1186
+ try {
1187
+ let gitCwd = this.allowedProjectPath;
1188
+ if (cwd) gitCwd = this.validatePath(cwd);
1189
+ const git = simpleGit(gitCwd);
1190
+ const diff = await git.diff();
1191
+ return { success: true, output: diff };
1192
+ } catch (e) {
1193
+ return { success: false, output: "", error: e.message };
1194
+ }
1195
+ }
1196
+ async gitLog(limit = 5, cwd) {
1197
+ try {
1198
+ let gitCwd = this.allowedProjectPath;
1199
+ if (cwd) gitCwd = this.validatePath(cwd);
1200
+ const git = simpleGit(gitCwd);
1201
+ const log = await git.log({ maxCount: limit });
1202
+ const output = log.all.map((commit) => `${commit.hash.substring(0, 7)} - ${commit.message}`).join("\n");
1203
+ return { success: true, output };
1204
+ } catch (e) {
1205
+ return { success: false, output: "", error: e.message };
1206
+ }
1207
+ }
1208
+ async gitCommit(message, cwd, userConfirmed = false) {
1209
+ if (!userConfirmed) {
1210
+ return { success: false, output: "", error: "User confirmation required for git_commit operation" };
1211
+ }
1212
+ try {
1213
+ let gitCwd = this.allowedProjectPath;
1214
+ if (cwd) gitCwd = this.validatePath(cwd);
1215
+ const git = simpleGit(gitCwd);
1216
+ const result = await git.commit(message);
1217
+ return {
1218
+ success: true,
1219
+ output: `Committed: ${result.commit}
1220
+ Branch: ${result.branch}
1221
+ Summary: ${result.summary.changes} changes, ${result.summary.insertions} insertions, ${result.summary.deletions} deletions`
1222
+ };
1223
+ } catch (e) {
1224
+ return { success: false, output: "", error: e.message };
1225
+ }
1226
+ }
1227
+ // =================================================================
1228
+ // EXTERNAL API OPERATIONS
1229
+ // =================================================================
1230
+ async searchDocs(query, source = "mdn") {
1231
+ try {
1232
+ if (source === "mdn") {
1233
+ const url = `https://developer.mozilla.org/api/v1/search?q=${encodeURIComponent(query)}`;
1234
+ const response = await axios2.get(url, { timeout: 1e4 });
1235
+ return { success: true, output: JSON.stringify(response.data, null, 2), metadata: { source } };
1236
+ } else if (source === "stackoverflow") {
1237
+ const url = `https://api.stackexchange.com/2.3/search?order=desc&sort=activity&intitle=${encodeURIComponent(query)}&site=stackoverflow`;
1238
+ const response = await axios2.get(url, { timeout: 1e4 });
1239
+ return { success: true, output: JSON.stringify(response.data, null, 2), metadata: { source } };
1240
+ } else {
1241
+ return { success: false, output: "", error: `Unsupported documentation source: ${source}` };
1242
+ }
1243
+ } catch (e) {
1244
+ return { success: false, output: "", error: e.message };
1245
+ }
1246
+ }
1247
+ async httpRequest(url, method = "GET", headers, body) {
1248
+ try {
1249
+ const response = await axios2({
1250
+ url,
1251
+ method,
1252
+ headers,
1253
+ data: body,
1254
+ timeout: 1e4
1255
+ });
1256
+ return {
1257
+ success: true,
1258
+ output: typeof response.data === "string" ? response.data : JSON.stringify(response.data, null, 2),
1259
+ metadata: { status_code: response.status }
1260
+ };
1261
+ } catch (e) {
1262
+ return {
1263
+ success: false,
1264
+ output: "",
1265
+ error: e.response ? `HTTP ${e.response.status}: ${e.message}` : e.message
1266
+ };
1267
+ }
1268
+ }
1269
+ async cveLookup(cveId) {
1270
+ try {
1271
+ const url = `https://services.nvd.nist.gov/rest/json/cves/2.0?cveId=${encodeURIComponent(cveId)}`;
1272
+ const response = await axios2.get(url, { timeout: 1e4 });
1273
+ return { success: true, output: JSON.stringify(response.data, null, 2), metadata: { cve_id: cveId } };
1274
+ } catch (e) {
1275
+ return { success: false, output: "", error: e.message };
1276
+ }
1277
+ }
1278
+ // =================================================================
1279
+ // TOOL EXECUTION DISPATCHER
1280
+ // =================================================================
1281
+ async executeTool(toolName, args, userConfirmed = false) {
1282
+ switch (toolName) {
1283
+ case "read_file":
1284
+ return this.readFile(args.file_path || "");
1285
+ case "write_file":
1286
+ return this.writeFile(args.file_path || "", args.content || "", userConfirmed);
1287
+ case "list_directory":
1288
+ return this.listDirectory(args.dir_path || ".");
1289
+ case "run_command":
1290
+ return this.runCommand(args.command || "", args.cwd, userConfirmed);
1291
+ case "git_diff":
1292
+ return this.gitDiff(args.cwd);
1293
+ case "git_log":
1294
+ return this.gitLog(args.limit || 5, args.cwd);
1295
+ case "git_commit":
1296
+ return this.gitCommit(args.message || "", args.cwd, userConfirmed);
1297
+ case "search_docs":
1298
+ return this.searchDocs(args.query || "", args.source || "mdn");
1299
+ case "http_request":
1300
+ return this.httpRequest(args.url || "", args.method || "GET", args.headers, args.body);
1301
+ case "cve_lookup":
1302
+ return this.cveLookup(args.cve_id || "");
1303
+ default:
1304
+ return { success: false, output: "", error: `Tool not implemented: ${toolName}` };
1305
+ }
1306
+ }
1307
+ };
1308
+
1309
+ // src/streaming/renderer.ts
1310
+ marked.setOptions({
1311
+ renderer: new TerminalRenderer({
1312
+ code: chalk3.cyan,
1313
+ blockquote: chalk3.gray.italic,
1314
+ html: chalk3.gray,
1315
+ heading: chalk3.bold.underline,
1316
+ firstHeading: chalk3.bold.magenta,
1317
+ hr: chalk3.reset,
1318
+ listitem: chalk3.reset,
1319
+ list: chalk3.reset,
1320
+ table: chalk3.reset,
1321
+ paragraph: chalk3.reset,
1322
+ strong: chalk3.bold,
1323
+ em: chalk3.italic,
1324
+ codespan: chalk3.cyan,
1325
+ del: chalk3.dim.strikethrough,
1326
+ link: chalk3.blue.underline,
1327
+ href: chalk3.blue.underline
1328
+ })
1329
+ });
1330
+ var StreamingRenderer = class {
1331
+ buffer = "";
1332
+ inCodeBlock = false;
1333
+ toolEngine;
1334
+ pendingToolCalls = [];
1335
+ sessionId = null;
1336
+ constructor() {
1337
+ this.toolEngine = new ToolEngine(process.cwd());
1338
+ }
1339
+ getPendingToolCalls() {
1340
+ return this.pendingToolCalls;
1341
+ }
1342
+ getSessionId() {
1343
+ return this.sessionId;
1344
+ }
1345
+ clearPendingTools() {
1346
+ this.pendingToolCalls = [];
1347
+ this.sessionId = null;
1348
+ }
1349
+ /**
1350
+ * Process stream message
1351
+ */
1352
+ async processMessage(message) {
1353
+ switch (message.type) {
1354
+ case "token":
1355
+ await this.handleToken(message.content || "");
1356
+ break;
1357
+ case "tool_call":
1358
+ if (message.tool) {
1359
+ this.pendingToolCalls.push({
1360
+ tool: message.tool,
1361
+ args: message.args || {}
1362
+ });
1363
+ await this.handleToolCall(message);
1364
+ }
1365
+ break;
1366
+ case "tools_pending":
1367
+ if (message.session_id) {
1368
+ this.sessionId = message.session_id;
1369
+ }
1370
+ await this.flushBuffer();
1371
+ break;
1372
+ case "tool_result":
1373
+ await this.handleToolResult(message);
1374
+ break;
1375
+ case "done":
1376
+ await this.handleDone(message);
1377
+ break;
1378
+ case "error":
1379
+ await this.handleError(message);
1380
+ break;
1381
+ }
1382
+ }
1383
+ /**
1384
+ * Handle token (word/character from AI)
1385
+ */
1386
+ async handleToken(token) {
1387
+ this.buffer += token;
1388
+ if (token.includes("```")) {
1389
+ this.inCodeBlock = !this.inCodeBlock;
1390
+ }
1391
+ if (token.includes("\n") || this.buffer.length > 1e3) {
1392
+ await this.flushBuffer();
1393
+ } else {
1394
+ process.stdout.write(token);
1395
+ }
1396
+ }
1397
+ /**
1398
+ * Flush accumulated buffer
1399
+ */
1400
+ async flushBuffer() {
1401
+ if (this.buffer.length === 0) return;
1402
+ if (this.inCodeBlock) {
1403
+ process.stdout.write(this.buffer);
1404
+ } else {
1405
+ try {
1406
+ const rendered = await marked(this.buffer);
1407
+ process.stdout.write(rendered);
1408
+ } catch (error) {
1409
+ process.stdout.write(this.buffer);
1410
+ }
1411
+ }
1412
+ this.buffer = "";
1413
+ }
1414
+ /**
1415
+ * Handle tool call request from AI - just display it, execution happens later
1416
+ */
1417
+ async handleToolCall(message) {
1418
+ await this.flushBuffer();
1419
+ logger.newline();
1420
+ const { tool, args } = message;
1421
+ if (!tool) return;
1422
+ logger.info(chalk3.yellow(`AI requested tool: ${chalk3.bold(tool)}`));
1423
+ if (args && Object.keys(args).length > 0) {
1424
+ console.log(chalk3.gray("Arguments:"));
1425
+ console.log(chalk3.gray(JSON.stringify(args, null, 2)));
1426
+ }
1427
+ logger.newline();
1428
+ }
1429
+ /**
1430
+ * Execute all pending tool calls
1431
+ * This is called from prompt.ts after stream ends
1432
+ */
1433
+ async executePendingTools() {
1434
+ const results = [];
1435
+ for (const { tool, args } of this.pendingToolCalls) {
1436
+ logger.newline();
1437
+ logger.info(chalk3.yellow(`Executing ${chalk3.bold(tool)}...`));
1438
+ const requiresConfirmation = this.requiresConfirmation(tool);
1439
+ let confirmed = true;
1440
+ if (requiresConfirmation) {
1441
+ const answers = await inquirer.prompt([
1442
+ {
1443
+ type: "confirm",
1444
+ name: "confirmed",
1445
+ message: `Allow ${tool} to execute?`,
1446
+ default: false
1447
+ }
1448
+ ]);
1449
+ confirmed = answers.confirmed;
1450
+ if (!confirmed) {
1451
+ logger.warn("Tool execution cancelled by user");
1452
+ results.push({
1453
+ tool,
1454
+ args,
1455
+ result: "User denied permission",
1456
+ success: false
1457
+ });
1458
+ continue;
1459
+ }
1460
+ }
1461
+ logger.startSpinner(`Executing ${tool}...`);
1462
+ try {
1463
+ const result = await this.toolEngine.executeTool(tool, args || {}, confirmed);
1464
+ logger.stopSpinner();
1465
+ if (result.success) {
1466
+ logger.success(`${tool} completed successfully`);
1467
+ const content = result.output || "";
1468
+ const preview = content.substring(0, 500);
1469
+ if (preview.length < content.length) {
1470
+ console.log(chalk3.gray(preview + "... (truncated)"));
1471
+ } else {
1472
+ console.log(chalk3.gray(preview));
1473
+ }
1474
+ results.push({
1475
+ tool,
1476
+ args,
1477
+ result: result.output || "",
1478
+ success: true
1479
+ });
1480
+ } else {
1481
+ logger.error(`${tool} failed: ${result.error}`);
1482
+ results.push({
1483
+ tool,
1484
+ args,
1485
+ result: result.error || "Unknown error",
1486
+ success: false
1487
+ });
1488
+ }
1489
+ } catch (error) {
1490
+ logger.stopSpinner();
1491
+ logger.error(`${tool} threw error: ${error.message}`);
1492
+ results.push({
1493
+ tool,
1494
+ args,
1495
+ result: error.message,
1496
+ success: false
1497
+ });
1498
+ }
1499
+ }
1500
+ return results;
1501
+ }
1502
+ /**
1503
+ * Handle tool execution result (from backend)
1504
+ */
1505
+ async handleToolResult(message) {
1506
+ logger.stopSpinner();
1507
+ if (message.content) {
1508
+ const preview = message.content.substring(0, 500);
1509
+ if (preview.length < message.content.length) {
1510
+ console.log(chalk3.gray(preview + "... (truncated)"));
1511
+ } else {
1512
+ console.log(chalk3.gray(preview));
1513
+ }
1514
+ }
1515
+ logger.newline();
1516
+ }
1517
+ /**
1518
+ * Handle completion done
1519
+ */
1520
+ async handleDone(message) {
1521
+ await this.flushBuffer();
1522
+ logger.newline();
1523
+ if (message.usage) {
1524
+ const { tokens_in, tokens_out, cost_usd } = message.usage;
1525
+ console.log(
1526
+ chalk3.gray(
1527
+ `\u{1F4CA} Tokens: ${tokens_in.toLocaleString()} in, ${tokens_out.toLocaleString()} out | Cost: $${cost_usd.toFixed(4)}`
1528
+ )
1529
+ );
1530
+ }
1531
+ logger.newline();
1532
+ }
1533
+ /**
1534
+ * Handle error
1535
+ */
1536
+ async handleError(message) {
1537
+ await this.flushBuffer();
1538
+ logger.newline();
1539
+ logger.error(message.error || "An error occurred");
1540
+ }
1541
+ /**
1542
+ * Check if tool requires user confirmation
1543
+ */
1544
+ requiresConfirmation(tool) {
1545
+ const destructiveTools = [
1546
+ "write_file",
1547
+ "run_command",
1548
+ "git_commit",
1549
+ "delete_file",
1550
+ "modify_file"
1551
+ ];
1552
+ return destructiveTools.includes(tool);
1553
+ }
1554
+ /**
1555
+ * Reset renderer state
1556
+ */
1557
+ reset() {
1558
+ this.buffer = "";
1559
+ this.inCodeBlock = false;
1560
+ }
1561
+ };
1562
+ var streamingRenderer = new StreamingRenderer();
1563
+
1564
+ // src/commands/prompt.ts
1565
+ async function promptCommand(prompt, options) {
1566
+ try {
1567
+ const apiKey = await configManager.getApiKey();
1568
+ if (!apiKey) {
1569
+ logger.error('Not authenticated. Run "devwing login" first.');
1570
+ process.exit(1);
1571
+ }
1572
+ logger.startSpinner("Analyzing codebase...");
1573
+ const collector = new ContextCollector(process.cwd());
1574
+ const context = await collector.collect(prompt);
1575
+ logger.succeedSpinner("Context collected");
1576
+ logger.newline();
1577
+ logger.info("DevWing is thinking...");
1578
+ logger.newline();
1579
+ const request = {
1580
+ prompt,
1581
+ mode: options.mode,
1582
+ model: options.model,
1583
+ project_id: options.project || configManager.getProjectId(),
1584
+ context,
1585
+ stream: true,
1586
+ max_tokens: 4096
1587
+ };
1588
+ let continueLoop = true;
1589
+ while (continueLoop) {
1590
+ streamingRenderer.reset();
1591
+ streamingRenderer.clearPendingTools();
1592
+ try {
1593
+ const sessionId = streamingRenderer.getSessionId();
1594
+ if (sessionId) {
1595
+ const toolResults = await streamingRenderer.executePendingTools();
1596
+ logger.newline();
1597
+ logger.info("Sending tool results back to AI...");
1598
+ logger.newline();
1599
+ for await (const message of apiClient.continueCompletion(sessionId, toolResults)) {
1600
+ await streamingRenderer.processMessage(message);
1601
+ }
1602
+ } else {
1603
+ for await (const message of apiClient.streamCompletion(request)) {
1604
+ await streamingRenderer.processMessage(message);
1605
+ }
1606
+ }
1607
+ const pendingTools = streamingRenderer.getPendingToolCalls();
1608
+ const newSessionId = streamingRenderer.getSessionId();
1609
+ if (pendingTools.length > 0 && newSessionId) {
1610
+ continueLoop = true;
1611
+ } else {
1612
+ continueLoop = false;
1613
+ }
1614
+ } catch (error) {
1615
+ logger.newline();
1616
+ logger.error("Request failed:", error);
1617
+ if (error.message.includes("429")) {
1618
+ logger.warn("Rate limit exceeded. Please wait a moment and try again.");
1619
+ } else if (error.message.includes("quota")) {
1620
+ logger.warn("Token quota exceeded. Consider upgrading your plan.");
1621
+ } else if (error.message.includes("unauthorized")) {
1622
+ logger.warn('Authentication failed. Try running "devwing login" again.');
1623
+ }
1624
+ process.exit(1);
1625
+ }
1626
+ }
1627
+ logger.success("Done!");
1628
+ } catch (error) {
1629
+ logger.error("Command failed", error);
1630
+ process.exit(1);
1631
+ }
1632
+ }
1633
+ async function scanCommand(options) {
1634
+ logger.info("Running security vulnerability scan...");
1635
+ logger.newline();
1636
+ const prompt = `Perform a comprehensive security scan of this codebase. Check for:
1637
+ - Common vulnerabilities (OWASP Top 10)
1638
+ - Dependency vulnerabilities
1639
+ - Hardcoded secrets or credentials
1640
+ - SQL injection risks
1641
+ - XSS vulnerabilities
1642
+ - Insecure authentication/authorization
1643
+ - API security issues
1644
+
1645
+ Provide a detailed report with severity levels and remediation steps.`;
1646
+ await promptCommand(prompt, { ...options, mode: "security" });
1647
+ }
1648
+ async function reviewCommand(options) {
1649
+ logger.info("Performing code review...");
1650
+ logger.newline();
1651
+ const prompt = `Conduct a thorough code review of the recent changes. Focus on:
1652
+ - Code quality and best practices
1653
+ - Potential bugs or edge cases
1654
+ - Performance issues
1655
+ - Readability and maintainability
1656
+ - Documentation completeness
1657
+ - Test coverage
1658
+ - Security concerns
1659
+
1660
+ Provide specific suggestions for improvement.`;
1661
+ await promptCommand(prompt, { ...options, mode: options.mode || "general" });
1662
+ }
1663
+ async function explainCommand(target, options) {
1664
+ logger.info(`Explaining: ${target}`);
1665
+ logger.newline();
1666
+ const prompt = `Please explain ${target} in detail. Include:
1667
+ - What it does and how it works
1668
+ - Key components and their relationships
1669
+ - Any important patterns or techniques used
1670
+ - Potential gotchas or edge cases
1671
+ - How it fits into the larger system
1672
+
1673
+ Make the explanation clear and accessible.`;
1674
+ await promptCommand(prompt, { ...options });
1675
+ }
1676
+ async function memoryCommand(action, content, options = {}) {
1677
+ const projectId = options.project || configManager.getProjectId();
1678
+ if (!projectId) {
1679
+ logger.error('No project configured. Use "devwing config set project <project-id>"');
1680
+ return;
1681
+ }
1682
+ try {
1683
+ switch (action) {
1684
+ case "save":
1685
+ await memorySave(projectId, content);
1686
+ break;
1687
+ case "show":
1688
+ await memoryShow(projectId);
1689
+ break;
1690
+ case "clear":
1691
+ await memoryClear(projectId);
1692
+ break;
1693
+ }
1694
+ } catch (error) {
1695
+ logger.error("Memory command failed", error);
1696
+ process.exit(1);
1697
+ }
1698
+ }
1699
+ async function memorySave(projectId, content) {
1700
+ if (!content) {
1701
+ logger.error("Please provide content to save");
1702
+ return;
1703
+ }
1704
+ const { type } = await inquirer.prompt([
1705
+ {
1706
+ type: "list",
1707
+ name: "type",
1708
+ message: "What type of memory is this?",
1709
+ choices: [
1710
+ { name: "Context (general information)", value: "context" },
1711
+ { name: "Rule (coding standard or guideline)", value: "rule" },
1712
+ { name: "Decision (architectural decision)", value: "decision" },
1713
+ { name: "Summary (project summary)", value: "summary" }
1714
+ ]
1715
+ }
1716
+ ]);
1717
+ logger.startSpinner("Saving to project memory...");
1718
+ try {
1719
+ await apiClient.addProjectMemory(projectId, content, type);
1720
+ logger.succeedSpinner("Memory saved successfully");
1721
+ } catch (error) {
1722
+ logger.failSpinner("Failed to save memory");
1723
+ throw error;
1724
+ }
1725
+ }
1726
+ async function memoryShow(projectId) {
1727
+ logger.startSpinner("Loading project memory...");
1728
+ try {
1729
+ const memories = await apiClient.getProjectMemory(projectId);
1730
+ logger.stopSpinner();
1731
+ if (memories.length === 0) {
1732
+ logger.info("No memories stored for this project yet");
1733
+ return;
1734
+ }
1735
+ logger.success(`Found ${memories.length} memory items`);
1736
+ logger.newline();
1737
+ const table = new Table({
1738
+ head: ["Type", "Content", "Created"],
1739
+ colWidths: [15, 60, 20],
1740
+ wordWrap: true
1741
+ });
1742
+ for (const memory of memories) {
1743
+ const preview = memory.content.length > 100 ? memory.content.substring(0, 100) + "..." : memory.content;
1744
+ table.push([
1745
+ chalk3.cyan(memory.type),
1746
+ preview,
1747
+ new Date(memory.created_at).toLocaleDateString()
1748
+ ]);
1749
+ }
1750
+ console.log(table.toString());
1751
+ } catch (error) {
1752
+ logger.failSpinner("Failed to load memory");
1753
+ throw error;
1754
+ }
1755
+ }
1756
+ async function memoryClear(projectId) {
1757
+ const { confirm } = await inquirer.prompt([
1758
+ {
1759
+ type: "confirm",
1760
+ name: "confirm",
1761
+ message: "Are you sure you want to clear all project memory?",
1762
+ default: false
1763
+ }
1764
+ ]);
1765
+ if (!confirm) {
1766
+ logger.info("Operation cancelled");
1767
+ return;
1768
+ }
1769
+ logger.startSpinner("Clearing project memory...");
1770
+ try {
1771
+ const memories = await apiClient.getProjectMemory(projectId);
1772
+ for (const memory of memories) {
1773
+ await apiClient.deleteProjectMemory(projectId, memory.id);
1774
+ }
1775
+ logger.succeedSpinner("Project memory cleared");
1776
+ } catch (error) {
1777
+ logger.failSpinner("Failed to clear memory");
1778
+ throw error;
1779
+ }
1780
+ }
1781
+ async function configCommand(action, key, value) {
1782
+ try {
1783
+ if (!action || action === "list") {
1784
+ const config = configManager.getAll();
1785
+ const apiKey = await configManager.getApiKey();
1786
+ console.log(chalk3.bold("Current Configuration:"));
1787
+ console.log(` API URL: ${config.apiUrl}`);
1788
+ console.log(` API Key: ${apiKey ? apiKey.substring(0, 12) + "..." : "Not set"}`);
1789
+ console.log(` Workspace: ${config.workspaceId || "Not set"}`);
1790
+ console.log(` Project: ${config.projectId || "Not set"}`);
1791
+ console.log(` Model: ${config.model || "Auto"}`);
1792
+ console.log(` Mode: ${config.mode || "Auto"}`);
1793
+ console.log();
1794
+ console.log(chalk3.gray(`Config file: ${configManager.getPath()}`));
1795
+ return;
1796
+ }
1797
+ if (action === "get") {
1798
+ if (!key) {
1799
+ logger.error("Please specify a key to get");
1800
+ return;
1801
+ }
1802
+ const config = configManager.getAll();
1803
+ const val = config[key];
1804
+ console.log(val || "Not set");
1805
+ return;
1806
+ }
1807
+ if (action === "set") {
1808
+ if (!key || !value) {
1809
+ logger.error("Please specify both key and value");
1810
+ return;
1811
+ }
1812
+ switch (key) {
1813
+ case "project":
1814
+ configManager.setProjectId(value);
1815
+ logger.success(`project set to: ${value}`);
1816
+ break;
1817
+ case "workspace":
1818
+ configManager.setWorkspaceId(value);
1819
+ logger.success(`workspace set to: ${value}`);
1820
+ break;
1821
+ case "model":
1822
+ configManager.setModel(value);
1823
+ logger.success(`model set to: ${value}`);
1824
+ break;
1825
+ case "mode":
1826
+ const validModes = ["general", "frontend", "backend", "security", "devops"];
1827
+ if (validModes.includes(value)) {
1828
+ configManager.setMode(value);
1829
+ logger.success(`mode set to: ${value}`);
1830
+ } else {
1831
+ logger.error(`Invalid mode. Must be one of: ${validModes.join(", ")}`);
1832
+ }
1833
+ break;
1834
+ case "apiUrl":
1835
+ configManager.setApiUrl(value);
1836
+ logger.success(`apiUrl set to: ${value}`);
1837
+ break;
1838
+ default:
1839
+ logger.error(`Unknown config key: ${key}`);
1840
+ }
1841
+ }
1842
+ } catch (error) {
1843
+ logger.error("Config command failed", error);
1844
+ process.exit(1);
1845
+ }
1846
+ }
1847
+ var __filename2 = fileURLToPath(import.meta.url);
1848
+ var __dirname2 = dirname(__filename2);
1849
+ function getCurrentVersion() {
1850
+ try {
1851
+ const packageJsonPath = join(__dirname2, "../../package.json");
1852
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
1853
+ return packageJson.version;
1854
+ } catch (error) {
1855
+ logger.error("Failed to read package version", error);
1856
+ return "0.1.0";
1857
+ }
1858
+ }
1859
+ function getPackageName() {
1860
+ try {
1861
+ const packageJsonPath = join(__dirname2, "../../package.json");
1862
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
1863
+ return packageJson.name;
1864
+ } catch (error) {
1865
+ logger.error("Failed to read package name", error);
1866
+ return "@devwing/cli";
1867
+ }
1868
+ }
1869
+ async function getLatestVersion(packageName) {
1870
+ try {
1871
+ const response = await fetch(`https://registry.npmjs.org/${packageName}`);
1872
+ if (!response.ok) {
1873
+ throw new Error(`Registry responded with ${response.status}`);
1874
+ }
1875
+ const data = await response.json();
1876
+ return data["dist-tags"].latest;
1877
+ } catch (error) {
1878
+ logger.error("Failed to fetch latest version from npm registry", error);
1879
+ return null;
1880
+ }
1881
+ }
1882
+ function detectPackageManager() {
1883
+ try {
1884
+ execSync("pnpm --version", { stdio: "ignore" });
1885
+ return "pnpm";
1886
+ } catch {
1887
+ }
1888
+ try {
1889
+ execSync("yarn --version", { stdio: "ignore" });
1890
+ return "yarn";
1891
+ } catch {
1892
+ }
1893
+ try {
1894
+ execSync("npm --version", { stdio: "ignore" });
1895
+ return "npm";
1896
+ } catch {
1897
+ }
1898
+ return null;
1899
+ }
1900
+ function installUpdate(packageName, packageManager, version) {
1901
+ try {
1902
+ logger.startSpinner(`Installing ${packageName}@${version}...`);
1903
+ const command = packageManager === "yarn" ? `yarn global add ${packageName}@${version}` : `${packageManager} install -g ${packageName}@${version}`;
1904
+ execSync(command, { stdio: "inherit" });
1905
+ logger.stopSpinner();
1906
+ return true;
1907
+ } catch (error) {
1908
+ logger.stopSpinner();
1909
+ logger.error("Failed to install update", error);
1910
+ return false;
1911
+ }
1912
+ }
1913
+ function displayUpdateInfo(currentVersion, latestVersion) {
1914
+ console.log();
1915
+ console.log(chalk3.cyan("\u2501".repeat(60)));
1916
+ console.log(chalk3.bold.cyan(" DevWing CLI Update Available"));
1917
+ console.log(chalk3.cyan("\u2501".repeat(60)));
1918
+ console.log();
1919
+ console.log(` ${chalk3.gray("Current version:")} ${chalk3.yellow(currentVersion)}`);
1920
+ console.log(` ${chalk3.gray("Latest version:")} ${chalk3.green(latestVersion)}`);
1921
+ console.log();
1922
+ console.log(chalk3.gray(" Changelog: https://github.com/devwing/devwing/releases"));
1923
+ console.log();
1924
+ }
1925
+ async function updateCommand(options) {
1926
+ try {
1927
+ const currentVersion = getCurrentVersion();
1928
+ const packageName = getPackageName();
1929
+ console.log();
1930
+ console.log(chalk3.cyan(`\u{1F4E6} DevWing CLI v${currentVersion}`));
1931
+ console.log();
1932
+ logger.startSpinner("Checking for updates...");
1933
+ const latestVersion = await getLatestVersion(packageName);
1934
+ logger.stopSpinner();
1935
+ if (!latestVersion) {
1936
+ logger.warn("Unable to check for updates. Please check your internet connection.");
1937
+ return;
1938
+ }
1939
+ const isNewer = semver.gt(latestVersion, currentVersion);
1940
+ if (!isNewer) {
1941
+ logger.success(`You're running the latest version (v${currentVersion})`);
1942
+ return;
1943
+ }
1944
+ displayUpdateInfo(currentVersion, latestVersion);
1945
+ if (options?.check) {
1946
+ logger.info('Run "devwing update" to install the latest version');
1947
+ return;
1948
+ }
1949
+ const packageManager = detectPackageManager();
1950
+ if (!packageManager) {
1951
+ logger.error("No package manager found. Please install npm, pnpm, or yarn.");
1952
+ return;
1953
+ }
1954
+ logger.info(`Detected package manager: ${chalk3.cyan(packageManager)}`);
1955
+ if (!options?.force) {
1956
+ const { confirm } = await inquirer.prompt([
1957
+ {
1958
+ type: "confirm",
1959
+ name: "confirm",
1960
+ message: `Update to v${latestVersion}?`,
1961
+ default: true
1962
+ }
1963
+ ]);
1964
+ if (!confirm) {
1965
+ logger.info("Update cancelled");
1966
+ return;
1967
+ }
1968
+ }
1969
+ console.log();
1970
+ const success = installUpdate(packageName, packageManager, latestVersion);
1971
+ if (success) {
1972
+ console.log();
1973
+ logger.success(`Successfully updated to v${latestVersion}`);
1974
+ console.log();
1975
+ console.log(chalk3.gray(' Run "devwing --version" to verify'));
1976
+ console.log();
1977
+ } else {
1978
+ console.log();
1979
+ logger.error("Update failed. Please try manually:");
1980
+ console.log();
1981
+ console.log(chalk3.gray(` ${packageManager} install -g ${packageName}@${latestVersion}`));
1982
+ console.log();
1983
+ process.exit(1);
1984
+ }
1985
+ } catch (error) {
1986
+ logger.error("Update check failed", error);
1987
+ process.exit(1);
1988
+ }
1989
+ }
1990
+
1991
+ // src/index.ts
1992
+ var program = new Command();
1993
+ var VERSION = "0.1.0";
1994
+ 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");
1995
+ 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");
1996
+ program.command("login").description("Authenticate with DevWing").action(async () => {
1997
+ await loginCommand();
1998
+ });
1999
+ program.command("logout").description("Clear stored credentials").action(async () => {
2000
+ await logoutCommand();
2001
+ });
2002
+ program.command("status").description("Show authentication status and profile").action(async () => {
2003
+ await statusCommand();
2004
+ });
2005
+ program.command("scan").description("Run security vulnerability scan").action(async () => {
2006
+ const opts = program.opts();
2007
+ await scanCommand(opts);
2008
+ });
2009
+ program.command("review").description("Perform code review on recent changes").action(async () => {
2010
+ const opts = program.opts();
2011
+ await reviewCommand(opts);
2012
+ });
2013
+ program.command("explain <target>").description("Explain code, file, or concept").action(async (target) => {
2014
+ const opts = program.opts();
2015
+ await explainCommand(target, opts);
2016
+ });
2017
+ var memoryCmd = program.command("memory").description("Manage project memory");
2018
+ memoryCmd.command("save <content>").description("Save information to project memory").action(async (content) => {
2019
+ const opts = program.opts();
2020
+ await memoryCommand("save", content, opts);
2021
+ });
2022
+ memoryCmd.command("show").description("Show all project memories").action(async () => {
2023
+ const opts = program.opts();
2024
+ await memoryCommand("show", void 0, opts);
2025
+ });
2026
+ memoryCmd.command("clear").description("Clear all project memories").action(async () => {
2027
+ const opts = program.opts();
2028
+ await memoryCommand("clear", void 0, opts);
2029
+ });
2030
+ var configCmd = program.command("config").description("Manage CLI configuration");
2031
+ configCmd.command("list").description("Show all configuration").action(async () => {
2032
+ await configCommand("list");
2033
+ });
2034
+ configCmd.command("get <key>").description("Get configuration value").action(async (key) => {
2035
+ await configCommand("get", key);
2036
+ });
2037
+ configCmd.command("set <key> <value>").description("Set configuration value").action(async (key, value) => {
2038
+ await configCommand("set", key, value);
2039
+ });
2040
+ program.command("update").description("Check for and install CLI updates").option("--check", "Only check for updates, do not install").option("--force", "Skip confirmation prompt").action(async (options) => {
2041
+ await updateCommand(options);
2042
+ });
2043
+ program.argument("[prompt...]", "Natural language prompt for AI").action(async (promptParts) => {
2044
+ if (promptParts.length === 0) {
2045
+ program.help();
2046
+ return;
2047
+ }
2048
+ const prompt = promptParts.join(" ");
2049
+ const opts = program.opts();
2050
+ await promptCommand(prompt, opts);
2051
+ });
2052
+ program.exitOverride();
2053
+ try {
2054
+ await program.parseAsync(process.argv);
2055
+ } catch (error) {
2056
+ if (error.code === "commander.help" || error.code === "commander.version") {
2057
+ process.exit(0);
2058
+ }
2059
+ logger.error("An error occurred", error);
2060
+ if (process.env.DEBUG) {
2061
+ console.error(error);
2062
+ }
2063
+ process.exit(1);
2064
+ }
2065
+ //# sourceMappingURL=index.js.map
2066
+ //# sourceMappingURL=index.js.map