langtrain 0.2.1 → 0.2.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "langtrain",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Unified JavaScript SDK for Langtrain Ecosystem",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -45,7 +45,6 @@
45
45
  "form-data": "^4.0.5",
46
46
  "gradient-string": "^3.0.0",
47
47
  "kleur": "^4.1.5",
48
- "langtrain": "^0.1.15",
49
48
  "langtune": "^0.1.1",
50
49
  "langvision": "^0.1.1"
51
50
  }
package/src/cli/auth.ts CHANGED
@@ -1,6 +1,25 @@
1
- import { password, isCancel, cancel, intro, green, yellow, red, bgMagenta, black, spinner, gray, cyan, dim, bold } from './ui';
1
+ import { password, isCancel, cancel, intro, green, yellow, red, bgMagenta, black, spinner, gray, cyan, dim, bold, note } from './ui';
2
2
  import { getConfig, saveConfig } from './config';
3
3
  import { SubscriptionClient, SubscriptionInfo } from '../index';
4
+ import axios from 'axios';
5
+ import { exec } from 'child_process';
6
+ import { promisify } from 'util';
7
+
8
+ const execAsync = promisify(exec);
9
+ const API_BASE = 'https://api.langtrain.ai/api/v1';
10
+
11
+ async function openBrowser(url: string) {
12
+ try {
13
+ const command = process.platform === 'win32'
14
+ ? `start ${url}`
15
+ : process.platform === 'darwin'
16
+ ? `open "${url}"`
17
+ : `xdg-open "${url}"`;
18
+ await execAsync(command);
19
+ } catch {
20
+ // Silently fail if browser can't open
21
+ }
22
+ }
4
23
 
5
24
  /**
6
25
  * Quick check if API key is stored (no network call).
@@ -28,9 +47,86 @@ export async function ensureAuth(): Promise<string> {
28
47
  }
29
48
 
30
49
  /**
31
- * Interactive login — Claude-style API key entry with immediate verification.
50
+ * Interactive login — browser-based OAuth flow with API key fallback.
32
51
  */
33
52
  export async function handleLogin() {
53
+ const s = spinner();
54
+ s.start('Connecting to Langtrain...');
55
+
56
+ try {
57
+ // 1. Request device code
58
+ const { data: codeData } = await axios.post(`${API_BASE}/auth/device/code`);
59
+ const { device_code, user_code, verification_url, expires_in, interval } = codeData;
60
+
61
+ s.stop(green('Connected.'));
62
+
63
+ console.log('\n ' + bgMagenta(black(' AUTHENTICATION ')) + '\n');
64
+ console.log(` To log in, please open your browser to:\n ${cyan(verification_url + '?code=' + user_code)}\n`);
65
+
66
+ note(
67
+ `Confirmation Code:\n${bold(user_code)}`,
68
+ 'Verify in Browser'
69
+ );
70
+
71
+ console.log(gray(' Opening browser automatically...'));
72
+ await openBrowser(`${verification_url}?code=${user_code}`);
73
+
74
+ const pollSpinner = spinner();
75
+ pollSpinner.start('Waiting for approval in browser...');
76
+
77
+ // 2. Poll for token
78
+ const startTime = Date.now();
79
+ const timeout = expires_in * 1000;
80
+
81
+ while (Date.now() - startTime < timeout) {
82
+ try {
83
+ const { data: tokenData } = await axios.post(`${API_BASE}/auth/device/token?device_code=${device_code}`);
84
+
85
+ if (tokenData.status === 'approved') {
86
+ const apiKey = tokenData.api_key;
87
+ pollSpinner.stop(green('Approved!'));
88
+
89
+ const verifySpinner = spinner();
90
+ verifySpinner.start('Verifying credentials...');
91
+
92
+ const client = new SubscriptionClient({ apiKey });
93
+ const info = await client.getStatus();
94
+
95
+ const planBadge = info.plan === 'pro'
96
+ ? bgMagenta(black(' PRO '))
97
+ : info.plan === 'enterprise'
98
+ ? bgMagenta(black(' ENTERPRISE '))
99
+ : ' FREE ';
100
+
101
+ verifySpinner.stop(green(`Authenticated ${planBadge}`));
102
+
103
+ const config = getConfig();
104
+ saveConfig({ ...config, apiKey });
105
+ console.log(green(' ✔ Credentials saved to ~/.langtrain/config.json\n'));
106
+ return;
107
+ }
108
+
109
+ if (tokenData.status === 'expired') {
110
+ pollSpinner.stop(red('Device code expired.'));
111
+ break;
112
+ }
113
+
114
+ // Wait for the requested interval
115
+ await new Promise(r => setTimeout(r, interval * 1000));
116
+ } catch (err) {
117
+ // Ignore network errors during polling
118
+ await new Promise(r => setTimeout(r, interval * 1000));
119
+ }
120
+ }
121
+
122
+ pollSpinner.stop(yellow('Login timed out.'));
123
+ } catch (err: any) {
124
+ s.stop(red('Could not reach Langtrain server.'));
125
+ }
126
+
127
+ // 3. Fallback to manual entry if browser flow fails
128
+ console.log(gray('\n Browser login failed or timed out. Falling back to manual entry.'));
129
+
34
130
  while (true) {
35
131
  console.log(dim(' ─────────────────────────────────────'));
36
132
  console.log(gray(' Get your API Key at: ') + cyan('https://app.langtrain.xyz/home/api'));
@@ -49,8 +145,8 @@ export async function handleLogin() {
49
145
  process.exit(0);
50
146
  }
51
147
 
52
- const s = spinner();
53
- s.start('Verifying API Key...');
148
+ const verifySpinner = spinner();
149
+ verifySpinner.start('Verifying API Key...');
54
150
 
55
151
  try {
56
152
  const client = new SubscriptionClient({ apiKey: apiKey as string });
@@ -62,22 +158,14 @@ export async function handleLogin() {
62
158
  ? bgMagenta(black(' ENTERPRISE '))
63
159
  : ' FREE ';
64
160
 
65
- s.stop(green(`Authenticated ${planBadge}`));
66
-
67
- // Show initial token info if available
68
- if (info.usage) {
69
- const used = info.usage.tokensUsedThisMonth || 0;
70
- const limit = info.usage.tokenLimit || 10000;
71
- const pct = Math.round((used / limit) * 100);
72
- console.log(dim(` Tokens: ${used.toLocaleString()} / ${limit.toLocaleString()} (${pct}% used)`));
73
- }
161
+ verifySpinner.stop(green(`Authenticated ${planBadge}`));
74
162
 
75
163
  const config = getConfig();
76
164
  saveConfig({ ...config, apiKey: apiKey as string });
77
165
  console.log(green(' ✔ Credentials saved to ~/.langtrain/config.json\n'));
78
166
  return;
79
167
  } catch (e: any) {
80
- s.stop(red('Invalid API Key. Please try again.'));
168
+ verifySpinner.stop(red('Invalid API Key. Please try again.'));
81
169
  }
82
170
  }
83
171
  }
package/src/cli/index.ts CHANGED
@@ -298,5 +298,7 @@ export async function main() {
298
298
  }
299
299
  });
300
300
 
301
- main().catch(console.error);
301
+ program.parse(process.argv);
302
302
  }
303
+
304
+ main().catch(console.error);