devwing 0.1.5 → 0.1.7

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 CHANGED
@@ -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 { promises, readFileSync } from 'fs';
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
- return this.conf.get("apiUrl");
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
- throw new Error(error.response.data.message || error.response.data.error);
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?");
200
+ }
201
+ if (error.code === "ENOTFOUND") {
202
+ throw new Error("Cannot resolve DevWing API hostname. Check your internet connection.");
184
203
  }
185
- throw error;
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
- const error = await response.json();
253
- throw new Error(error.message || "Failed to get completion");
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 (error) {
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
- const error = await response.json();
292
- throw new Error(error.message || "Failed to continue completion");
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 (error) {
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
- return response.data;
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
- return response.data;
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
@@ -467,6 +520,12 @@ 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();
@@ -492,8 +551,8 @@ async function loginCommand() {
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,7 +565,10 @@ async function loginCommand() {
506
565
  await loginWithApiKey();
507
566
  }
508
567
  } catch (error) {
509
- logger.error("Login failed", error);
568
+ logger.error("Login failed");
569
+ if (error.message) {
570
+ logger.error(error.message);
571
+ }
510
572
  process.exit(1);
511
573
  }
512
574
  }
@@ -517,7 +579,7 @@ async function loginWithEmail() {
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
- const { access_token, user } = await apiClient.login(email, password);
536
- apiClient.setJwtToken(access_token);
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 inquirer.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 { key } = await apiClient.createApiKey("CLI - Auto-generated");
630
+ const apiKeyResult = await apiClient.createApiKey("CLI - Auto-generated");
539
631
  apiClient.setJwtToken(null);
540
- await configManager.setApiKey(key);
541
- logger.succeedSpinner(`Welcome back, ${user.full_name || user.email}!`);
542
- logger.success(`Plan: ${user.plan.toUpperCase()}`);
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("Opening browser...");
652
+ logger.succeedSpinner("Device code created");
558
653
  logger.newline();
654
+ const verifyUrl = initResponse.verification_url_complete;
559
655
  try {
560
- await open.default(initResponse.verification_url_complete);
561
- logger.success("Browser opened! Please log in to complete authentication.");
562
- } catch (err) {
563
- logger.warn("Could not open browser automatically");
564
- logger.info(`Please visit: ${chalk3.cyan(initResponse.verification_url_complete)}`);
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: ${chalk3.cyan(verifyUrl)}`);
565
661
  }
566
662
  logger.newline();
663
+ logger.info(chalk3.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("Authentication timed out. Please try again."));
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
- logger.succeedSpinner(`Welcome, ${user.full_name || user.email}!`);
608
- logger.success(`Plan: ${user.plan.toUpperCase()}`);
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
  }
@@ -642,13 +740,15 @@ async function loginWithApiKey() {
642
740
  try {
643
741
  await configManager.setApiKey(apiKey);
644
742
  const user = await apiClient.getProfile();
645
- logger.succeedSpinner(`Successfully authenticated as ${user.full_name || user.email}!`);
646
- logger.success(`Plan: ${user.plan.toUpperCase()}`);
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
  }
@@ -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", error);
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
799
  console.log(chalk3.bold("User Profile:"));
698
800
  console.log(` Email: ${user.email}`);
699
- console.log(` Name: ${user.full_name || "Not set"}`);
700
- console.log(` Plan: ${user.plan.toUpperCase()}`);
701
- console.log(
702
- ` Tokens used today: ${user.tokens_used_today.toLocaleString()}`
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 ? chalk3.green("Yes") : chalk3.yellow("No")}`);
805
+ }
806
+ if (user.totp_enabled !== void 0) {
807
+ console.log(` 2FA: ${user.totp_enabled ? chalk3.green("Enabled") : chalk3.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", error);
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",
@@ -2115,9 +2222,21 @@ var __filename2 = fileURLToPath(import.meta.url);
2115
2222
  var __dirname2 = dirname(__filename2);
2116
2223
  function getCurrentVersion() {
2117
2224
  try {
2118
- const packageJsonPath = join(__dirname2, "../../package.json");
2119
- const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
2120
- return packageJson.version;
2225
+ const possiblePaths = [
2226
+ join(__dirname2, "../package.json"),
2227
+ // Production: dist/ -> package.json
2228
+ join(__dirname2, "../../package.json")
2229
+ // Development: src/commands/ -> package.json
2230
+ ];
2231
+ for (const packageJsonPath of possiblePaths) {
2232
+ try {
2233
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
2234
+ return packageJson.version;
2235
+ } catch {
2236
+ continue;
2237
+ }
2238
+ }
2239
+ throw new Error("package.json not found in any expected location");
2121
2240
  } catch (error) {
2122
2241
  logger.error("Failed to read package version", error);
2123
2242
  return "0.1.0";
@@ -2125,12 +2244,24 @@ function getCurrentVersion() {
2125
2244
  }
2126
2245
  function getPackageName() {
2127
2246
  try {
2128
- const packageJsonPath = join(__dirname2, "../../package.json");
2129
- const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
2130
- return packageJson.name;
2247
+ const possiblePaths = [
2248
+ join(__dirname2, "../package.json"),
2249
+ // Production: dist/ -> package.json
2250
+ join(__dirname2, "../../package.json")
2251
+ // Development: src/commands/ -> package.json
2252
+ ];
2253
+ for (const packageJsonPath of possiblePaths) {
2254
+ try {
2255
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
2256
+ return packageJson.name;
2257
+ } catch {
2258
+ continue;
2259
+ }
2260
+ }
2261
+ throw new Error("package.json not found in any expected location");
2131
2262
  } catch (error) {
2132
2263
  logger.error("Failed to read package name", error);
2133
- return "@devwing/cli";
2264
+ return "devwing";
2134
2265
  }
2135
2266
  }
2136
2267
  async function getLatestVersion(packageName) {
@@ -2254,10 +2385,24 @@ async function updateCommand(options) {
2254
2385
  process.exit(1);
2255
2386
  }
2256
2387
  }
2257
-
2258
- // src/index.ts
2259
2388
  var program = new Command();
2260
- var VERSION = "0.1.4";
2389
+ var __cliFilename = fileURLToPath(import.meta.url);
2390
+ var __cliDirname = dirname(__cliFilename);
2391
+ function getVersion() {
2392
+ const paths = [
2393
+ join(__cliDirname, "../package.json"),
2394
+ join(__cliDirname, "../../package.json")
2395
+ ];
2396
+ for (const p of paths) {
2397
+ try {
2398
+ return JSON.parse(readFileSync(p, "utf-8")).version;
2399
+ } catch {
2400
+ continue;
2401
+ }
2402
+ }
2403
+ return "0.1.6";
2404
+ }
2405
+ var VERSION = getVersion();
2261
2406
  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");
2262
2407
  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");
2263
2408
  program.command("login").description("Authenticate with DevWing").action(async () => {
@@ -2324,10 +2469,12 @@ program.exitOverride();
2324
2469
  try {
2325
2470
  await program.parseAsync(process.argv);
2326
2471
  } catch (error) {
2327
- if (error.code === "commander.help" || error.code === "commander.version") {
2472
+ if (error.code === "commander.help" || error.code === "commander.helpDisplayed" || error.code === "commander.version") {
2328
2473
  process.exit(0);
2329
2474
  }
2330
- logger.error("An error occurred", error);
2475
+ if (error.message && !error.message.includes("process.exit")) {
2476
+ logger.error(error.message);
2477
+ }
2331
2478
  if (process.env.DEBUG) {
2332
2479
  console.error(error);
2333
2480
  }