langtrain 0.1.25 → 0.2.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/chunk-36QS5AXY.mjs +3 -0
- package/dist/chunk-36QS5AXY.mjs.map +1 -0
- package/dist/chunk-ZN3AO753.js +3 -0
- package/dist/chunk-ZN3AO753.js.map +1 -0
- package/dist/cli.js +17 -9
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +17 -9
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +273 -84
- package/dist/index.d.ts +273 -84
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
- package/src/cli/auth.ts +63 -15
- package/src/cli/handlers/telemetry.ts +127 -0
- package/src/cli/index.ts +171 -144
- package/src/cli/menu.ts +36 -34
- package/src/index.ts +13 -9
- package/src/lib/agent.ts +71 -48
- package/src/lib/base.ts +152 -0
- package/src/lib/files.ts +52 -37
- package/src/lib/guardrails.ts +55 -25
- package/src/lib/models.ts +32 -28
- package/src/lib/secrets.ts +35 -20
- package/src/lib/subscription.ts +50 -30
- package/src/lib/training.ts +71 -38
- package/src/lib/usage.ts +60 -0
- package/dist/chunk-Q5EF25B2.js +0 -3
- package/dist/chunk-Q5EF25B2.js.map +0 -1
- package/dist/chunk-XRT7LLNF.mjs +0 -3
- package/dist/chunk-XRT7LLNF.mjs.map +0 -1
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { green, dim, cyan, bold, yellow, gray, spinner } from '../ui';
|
|
2
|
+
import { getConfig } from '../config';
|
|
3
|
+
|
|
4
|
+
// Session-level telemetry tracker
|
|
5
|
+
let sessionStart = Date.now();
|
|
6
|
+
let apiCallCount = 0;
|
|
7
|
+
let totalLatencyMs = 0;
|
|
8
|
+
let errorCount = 0;
|
|
9
|
+
|
|
10
|
+
export function trackApiCall(latencyMs: number, isError: boolean = false) {
|
|
11
|
+
apiCallCount++;
|
|
12
|
+
totalLatencyMs += latencyMs;
|
|
13
|
+
if (isError) errorCount++;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function handleTokens() {
|
|
17
|
+
const config = getConfig();
|
|
18
|
+
const apiKey = config.apiKey;
|
|
19
|
+
|
|
20
|
+
console.log('');
|
|
21
|
+
console.log(bold(' ╔══════════════════════════════════════╗'));
|
|
22
|
+
console.log(bold(' ║ TOKEN USAGE ║'));
|
|
23
|
+
console.log(bold(' ╚══════════════════════════════════════╝'));
|
|
24
|
+
console.log('');
|
|
25
|
+
|
|
26
|
+
if (!apiKey) {
|
|
27
|
+
console.log(yellow(' Login required to view token usage.\n'));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const s = spinner();
|
|
32
|
+
s.start('Fetching token usage...');
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const axios = require('axios');
|
|
36
|
+
const baseUrl = config.baseUrl || 'https://api.langtrain.xyz';
|
|
37
|
+
const res = await axios.get(`${baseUrl}/v1/usage/tokens`, {
|
|
38
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const usage = res.data;
|
|
42
|
+
s.stop(green('Token usage retrieved'));
|
|
43
|
+
console.log('');
|
|
44
|
+
|
|
45
|
+
const used = usage.tokens_used || 0;
|
|
46
|
+
const limit = usage.token_limit || 10000;
|
|
47
|
+
const pct = Math.round((used / limit) * 100);
|
|
48
|
+
const remaining = Math.max(0, limit - used);
|
|
49
|
+
|
|
50
|
+
// Visual bar
|
|
51
|
+
const barWidth = 30;
|
|
52
|
+
const filled = Math.round((pct / 100) * barWidth);
|
|
53
|
+
const bar = '█'.repeat(filled) + '░'.repeat(barWidth - filled);
|
|
54
|
+
const barColor = pct > 90 ? '\x1b[31m' : pct > 70 ? '\x1b[33m' : '\x1b[32m';
|
|
55
|
+
|
|
56
|
+
console.log(` ${dim('Period:')} ${usage.period || 'Current Month'}`);
|
|
57
|
+
console.log(` ${dim('Used:')} ${used.toLocaleString()} tokens`);
|
|
58
|
+
console.log(` ${dim('Limit:')} ${limit.toLocaleString()} tokens`);
|
|
59
|
+
console.log(` ${dim('Remaining:')} ${remaining.toLocaleString()} tokens`);
|
|
60
|
+
console.log(` ${dim('Usage:')} ${barColor}${bar}\x1b[0m ${pct}%`);
|
|
61
|
+
console.log('');
|
|
62
|
+
|
|
63
|
+
if (usage.breakdown) {
|
|
64
|
+
console.log(dim(' ── Breakdown ────────────────────────'));
|
|
65
|
+
console.log(` ${dim('Training:')} ${(usage.breakdown.training || 0).toLocaleString()}`);
|
|
66
|
+
console.log(` ${dim('Inference:')} ${(usage.breakdown.inference || 0).toLocaleString()}`);
|
|
67
|
+
console.log(` ${dim('Agents:')} ${(usage.breakdown.agents || 0).toLocaleString()}`);
|
|
68
|
+
console.log('');
|
|
69
|
+
}
|
|
70
|
+
} catch (e: any) {
|
|
71
|
+
s.stop('');
|
|
72
|
+
// Show mock data if API not available
|
|
73
|
+
console.log(dim(' Token data not available from server.'));
|
|
74
|
+
console.log(dim(' Showing session estimates:\n'));
|
|
75
|
+
|
|
76
|
+
console.log(` ${dim('Session calls:')} ${apiCallCount}`);
|
|
77
|
+
console.log(` ${dim('Est. tokens:')} ~${apiCallCount * 150}`);
|
|
78
|
+
console.log('');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function handleTelemetry() {
|
|
83
|
+
const uptimeMs = Date.now() - sessionStart;
|
|
84
|
+
const uptimeSec = Math.round(uptimeMs / 1000);
|
|
85
|
+
const uptimeMin = Math.floor(uptimeSec / 60);
|
|
86
|
+
const uptimeStr = uptimeMin > 0 ? `${uptimeMin}m ${uptimeSec % 60}s` : `${uptimeSec}s`;
|
|
87
|
+
|
|
88
|
+
const avgLatency = apiCallCount > 0 ? Math.round(totalLatencyMs / apiCallCount) : 0;
|
|
89
|
+
const errorRate = apiCallCount > 0 ? Math.round((errorCount / apiCallCount) * 100) : 0;
|
|
90
|
+
|
|
91
|
+
console.log('');
|
|
92
|
+
console.log(bold(' ╔══════════════════════════════════════╗'));
|
|
93
|
+
console.log(bold(' ║ SESSION TELEMETRY ║'));
|
|
94
|
+
console.log(bold(' ╚══════════════════════════════════════╝'));
|
|
95
|
+
console.log('');
|
|
96
|
+
|
|
97
|
+
console.log(` ${dim('Session:')} ${uptimeStr}`);
|
|
98
|
+
console.log(` ${dim('API calls:')} ${apiCallCount}`);
|
|
99
|
+
console.log(` ${dim('Avg latency:')} ${avgLatency}ms`);
|
|
100
|
+
console.log(` ${dim('Errors:')} ${errorCount} (${errorRate}%)`);
|
|
101
|
+
console.log('');
|
|
102
|
+
|
|
103
|
+
console.log(dim(' ── Environment ──────────────────────'));
|
|
104
|
+
console.log(` ${dim('Node:')} ${process.version}`);
|
|
105
|
+
console.log(` ${dim('Platform:')} ${process.platform} ${process.arch}`);
|
|
106
|
+
console.log(` ${dim('Memory:')} ${Math.round(process.memoryUsage().heapUsed / 1024 / 1024)}MB heap`);
|
|
107
|
+
console.log(` ${dim('Config:')} ~/.langtrain/config.json`);
|
|
108
|
+
console.log('');
|
|
109
|
+
|
|
110
|
+
// API health check
|
|
111
|
+
const config = getConfig();
|
|
112
|
+
if (config.apiKey) {
|
|
113
|
+
const s = spinner();
|
|
114
|
+
s.start('Pinging API...');
|
|
115
|
+
try {
|
|
116
|
+
const axios = require('axios');
|
|
117
|
+
const baseUrl = config.baseUrl || 'https://api.langtrain.xyz';
|
|
118
|
+
const start = Date.now();
|
|
119
|
+
await axios.get(`${baseUrl}/health`, { timeout: 5000 });
|
|
120
|
+
const latency = Date.now() - start;
|
|
121
|
+
s.stop(green(`API healthy (${latency}ms)`));
|
|
122
|
+
} catch {
|
|
123
|
+
s.stop(yellow('API unreachable'));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
console.log('');
|
|
127
|
+
}
|
package/src/cli/index.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from 'commander';
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import { showBanner } from './ui';
|
|
6
|
-
import { ensureAuth, handleLogin, getSubscription } from './auth';
|
|
3
|
+
import { select, isCancel, outro, intro, colors, showBanner } from './ui';
|
|
4
|
+
import { ensureAuth, handleLogin, handleLogout, getSubscription, isAuthenticated } from './auth';
|
|
7
5
|
import { getMenu, MenuState } from './menu';
|
|
8
6
|
import { getConfig } from './config';
|
|
9
7
|
|
|
@@ -18,24 +16,66 @@ import { handleDataUpload, handleDataRefine } from './handlers/data';
|
|
|
18
16
|
import { handleDeploy } from './handlers/deploy';
|
|
19
17
|
import { handleDev } from './handlers/dev';
|
|
20
18
|
import { handleGuardrailList, handleGuardrailCreate } from './handlers/guardrails';
|
|
21
|
-
|
|
22
19
|
import { handleEnvMenu } from './handlers/env';
|
|
23
20
|
import { handleLogs } from './handlers/logs';
|
|
21
|
+
import { handleTokens, handleTelemetry } from './handlers/telemetry';
|
|
24
22
|
|
|
25
23
|
// Clients
|
|
26
24
|
import { SubscriptionInfo, Langvision, Langtune, AgentClient, ModelClient, FileClient, TrainingClient, SecretClient } from '../index';
|
|
27
25
|
import packageJson from '../../package.json';
|
|
28
26
|
|
|
27
|
+
function showStatusBar(plan: SubscriptionInfo | null) {
|
|
28
|
+
const { dim, green, yellow, cyan, bold, gray } = colors;
|
|
29
|
+
|
|
30
|
+
const planLabel = plan?.plan === 'pro'
|
|
31
|
+
? bold(green('PRO'))
|
|
32
|
+
: plan?.plan === 'enterprise'
|
|
33
|
+
? bold(green('ENTERPRISE'))
|
|
34
|
+
: dim('FREE');
|
|
35
|
+
|
|
36
|
+
const tokensUsed = plan?.usage?.tokensUsedThisMonth || 0;
|
|
37
|
+
const tokenLimit = plan?.usage?.tokenLimit || 10000;
|
|
38
|
+
const pct = Math.round((tokensUsed / tokenLimit) * 100);
|
|
39
|
+
const tokenBar = pct > 80 ? yellow(`${pct}%`) : green(`${pct}%`);
|
|
40
|
+
|
|
41
|
+
console.log(dim(' ─────────────────────────────────────────────'));
|
|
42
|
+
console.log(` ${dim('Plan:')} ${planLabel} ${dim('│')} ${dim('Tokens:')} ${tokensUsed.toLocaleString()}/${tokenLimit.toLocaleString()} ${tokenBar}`);
|
|
43
|
+
console.log(dim(' ─────────────────────────────────────────────\n'));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function buildClients(apiKey: string, baseUrl?: string) {
|
|
47
|
+
return {
|
|
48
|
+
vision: new Langvision({ apiKey }),
|
|
49
|
+
tune: new Langtune({ apiKey }),
|
|
50
|
+
agent: new AgentClient({ apiKey, baseUrl }),
|
|
51
|
+
model: new ModelClient({ apiKey, baseUrl }),
|
|
52
|
+
train: new TrainingClient({ apiKey, baseUrl }),
|
|
53
|
+
secret: new SecretClient({ apiKey, baseUrl }),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function getMessageForState(state: MenuState): string {
|
|
58
|
+
switch (state) {
|
|
59
|
+
case 'main': return 'What would you like to do?';
|
|
60
|
+
case 'agents': return 'Agents:';
|
|
61
|
+
case 'text': return 'Langtune (Text):';
|
|
62
|
+
case 'vision': return 'Langvision (Vision):';
|
|
63
|
+
case 'guard': return 'Guardrails:';
|
|
64
|
+
case 'settings': return 'Settings:';
|
|
65
|
+
default: return 'Select an option:';
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
29
69
|
export async function main() {
|
|
30
70
|
const program = new Command();
|
|
31
71
|
const version = packageJson.version;
|
|
32
72
|
|
|
33
73
|
program
|
|
34
74
|
.name('langtrain')
|
|
35
|
-
.description(
|
|
75
|
+
.description('Langtrain CLI — Fine-tuning, Agents, and AI Ops')
|
|
36
76
|
.version(version);
|
|
37
77
|
|
|
38
|
-
//
|
|
78
|
+
// ── Standalone commands (work without interactive mode) ─────────────
|
|
39
79
|
program.command('init')
|
|
40
80
|
.description('Initialize a new Langtrain project')
|
|
41
81
|
.action(handleInit);
|
|
@@ -50,7 +90,7 @@ export async function main() {
|
|
|
50
90
|
});
|
|
51
91
|
|
|
52
92
|
program.command('dev')
|
|
53
|
-
.description('Start local development server
|
|
93
|
+
.description('Start local development server')
|
|
54
94
|
.action(async () => {
|
|
55
95
|
const config = getConfig();
|
|
56
96
|
const apiKey = config.apiKey || '';
|
|
@@ -76,57 +116,91 @@ export async function main() {
|
|
|
76
116
|
await handleLogs(client, agent);
|
|
77
117
|
});
|
|
78
118
|
|
|
119
|
+
program.command('login')
|
|
120
|
+
.description('Authenticate with your API key')
|
|
121
|
+
.action(async () => {
|
|
122
|
+
await handleLogin();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
program.command('logout')
|
|
126
|
+
.description('Clear stored credentials')
|
|
127
|
+
.action(async () => {
|
|
128
|
+
await handleLogout();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
program.command('tokens')
|
|
132
|
+
.description('View token usage for current period')
|
|
133
|
+
.action(handleTokens);
|
|
134
|
+
|
|
135
|
+
// ── Data commands ──────────────────────────────────────────────────
|
|
136
|
+
const dataCommand = program.command('data').description('Manage datasets');
|
|
137
|
+
|
|
138
|
+
dataCommand.command('upload [file]')
|
|
139
|
+
.description('Upload a dataset')
|
|
140
|
+
.action(async () => {
|
|
141
|
+
const config = getConfig();
|
|
142
|
+
const client = new FileClient({ apiKey: config.apiKey || '', baseUrl: config.baseUrl });
|
|
143
|
+
await handleDataUpload(client);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
dataCommand.command('refine [fileId]')
|
|
147
|
+
.description('Refine a dataset using guardrails')
|
|
148
|
+
.action(async (fileId) => {
|
|
149
|
+
const config = getConfig();
|
|
150
|
+
const client = new FileClient({ apiKey: config.apiKey || '', baseUrl: config.baseUrl });
|
|
151
|
+
await handleDataRefine(client, fileId);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// ── Guardrail commands ─────────────────────────────────────────────
|
|
155
|
+
const guardCommand = program.command('guardrails').description('Manage data guardrails');
|
|
156
|
+
|
|
157
|
+
guardCommand.command('list')
|
|
158
|
+
.description('List available guardrails')
|
|
159
|
+
.action(async () => await handleGuardrailList(null));
|
|
160
|
+
|
|
161
|
+
guardCommand.command('create')
|
|
162
|
+
.description('Create a new guardrail')
|
|
163
|
+
.action(async () => await handleGuardrailCreate(null));
|
|
164
|
+
|
|
165
|
+
// ── Interactive mode (default action) ──────────────────────────────
|
|
79
166
|
program.action(async () => {
|
|
80
167
|
showBanner(version);
|
|
81
168
|
|
|
82
|
-
//
|
|
83
|
-
// 1. Auth & Plan Check (Lazy)
|
|
84
|
-
// 0. First Run Check
|
|
169
|
+
// First-run check
|
|
85
170
|
const isFirstRun = process.argv.includes('--first-run');
|
|
86
171
|
if (isFirstRun) {
|
|
87
|
-
// Check if interactive
|
|
88
172
|
if (process.stdin.isTTY) {
|
|
89
173
|
intro('Welcome to Langtrain! Let\'s get you set up.');
|
|
90
174
|
await handleLogin();
|
|
91
|
-
// Reload config after login
|
|
92
175
|
} else {
|
|
93
176
|
console.log('Langtrain installed! Run "npx langtrain login" to authenticate.');
|
|
94
177
|
process.exit(0);
|
|
95
178
|
}
|
|
96
179
|
}
|
|
97
180
|
|
|
98
|
-
//
|
|
181
|
+
// ── Auth-gated flow ───────────────────────────────────────────
|
|
99
182
|
let config = getConfig();
|
|
100
183
|
let apiKey = config.apiKey || '';
|
|
184
|
+
let authed = isAuthenticated();
|
|
101
185
|
let plan: SubscriptionInfo | null = null;
|
|
102
186
|
|
|
103
|
-
//
|
|
104
|
-
if (apiKey) {
|
|
187
|
+
// If authenticated, show status bar
|
|
188
|
+
if (authed && apiKey) {
|
|
105
189
|
try { plan = await getSubscription(apiKey); } catch { }
|
|
190
|
+
showStatusBar(plan);
|
|
191
|
+
} else {
|
|
192
|
+
console.log(colors.dim(' Not logged in. Only basic options available.\n'));
|
|
106
193
|
}
|
|
107
194
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
tune: new Langtune({ apiKey }),
|
|
112
|
-
agent: new AgentClient({ apiKey, baseUrl: config.baseUrl }),
|
|
113
|
-
model: new ModelClient({ apiKey, baseUrl: config.baseUrl }),
|
|
114
|
-
train: new TrainingClient({ apiKey, baseUrl: config.baseUrl }),
|
|
115
|
-
secret: new SecretClient({ apiKey, baseUrl: config.baseUrl })
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
// 3. Navigation Loop
|
|
195
|
+
let clients = authed ? buildClients(apiKey, config.baseUrl) : null;
|
|
196
|
+
|
|
197
|
+
// ── Navigation loop ──────────────────────────────────────────
|
|
119
198
|
let currentState: MenuState = 'main';
|
|
120
199
|
|
|
121
200
|
while (true) {
|
|
122
|
-
// Clear screen for clean sub-menu navigation?
|
|
123
|
-
// Maybe not full clear to keep banner, but at least separate visual blocks.
|
|
124
|
-
// showBanner(version); // Re-showing banner might be too much flickering.
|
|
125
|
-
// console.log(''); // simple spacer
|
|
126
|
-
|
|
127
201
|
const operation = await select({
|
|
128
202
|
message: getMessageForState(currentState),
|
|
129
|
-
options: getMenu(currentState, plan,
|
|
203
|
+
options: getMenu(currentState, plan, authed)
|
|
130
204
|
});
|
|
131
205
|
|
|
132
206
|
if (isCancel(operation)) {
|
|
@@ -141,135 +215,88 @@ export async function main() {
|
|
|
141
215
|
|
|
142
216
|
const op = operation as string;
|
|
143
217
|
|
|
144
|
-
// Navigation
|
|
145
|
-
if (op === 'exit') {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
149
|
-
if (op === 'back') {
|
|
150
|
-
currentState = 'main';
|
|
151
|
-
continue;
|
|
152
|
-
}
|
|
153
|
-
if (op.startsWith('nav-')) {
|
|
154
|
-
currentState = op.replace('nav-', '') as MenuState;
|
|
155
|
-
continue;
|
|
156
|
-
}
|
|
218
|
+
// Navigation
|
|
219
|
+
if (op === 'exit') { outro('Goodbye!'); process.exit(0); }
|
|
220
|
+
if (op === 'back') { currentState = 'main'; continue; }
|
|
221
|
+
if (op.startsWith('nav-')) { currentState = op.replace('nav-', '') as MenuState; continue; }
|
|
157
222
|
|
|
158
|
-
//
|
|
223
|
+
// Actions
|
|
159
224
|
try {
|
|
160
225
|
switch (op) {
|
|
226
|
+
// Auth
|
|
161
227
|
case 'login':
|
|
162
228
|
await handleLogin();
|
|
163
229
|
config = getConfig();
|
|
164
230
|
apiKey = config.apiKey || '';
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
173
|
-
|
|
231
|
+
authed = isAuthenticated();
|
|
232
|
+
if (authed) {
|
|
233
|
+
try { plan = await getSubscription(apiKey); } catch { }
|
|
234
|
+
clients = buildClients(apiKey, config.baseUrl);
|
|
235
|
+
console.clear();
|
|
236
|
+
showBanner(version);
|
|
237
|
+
showStatusBar(plan);
|
|
238
|
+
}
|
|
239
|
+
break;
|
|
240
|
+
|
|
241
|
+
case 'logout':
|
|
242
|
+
await handleLogout();
|
|
243
|
+
apiKey = '';
|
|
244
|
+
authed = false;
|
|
245
|
+
plan = null;
|
|
246
|
+
clients = null;
|
|
247
|
+
console.clear();
|
|
248
|
+
showBanner(version);
|
|
249
|
+
console.log(colors.dim(' Logged out. Only basic options available.\n'));
|
|
174
250
|
break;
|
|
251
|
+
|
|
252
|
+
case 'docs':
|
|
253
|
+
console.log(colors.cyan('\n 📖 https://docs.langtrain.ai\n'));
|
|
254
|
+
break;
|
|
255
|
+
|
|
256
|
+
// Status & info
|
|
175
257
|
case 'status': await handleSubscriptionStatus(); break;
|
|
176
|
-
case '
|
|
177
|
-
case '
|
|
178
|
-
case 'dev': await handleDev(clients.agent); break;
|
|
179
|
-
case 'env': await handleEnvMenu(clients.secret); break;
|
|
180
|
-
case 'logs': await handleLogs(clients.agent); break;
|
|
258
|
+
case 'tokens': await handleTokens(); break;
|
|
259
|
+
case 'telemetry': await handleTelemetry(); break;
|
|
181
260
|
case 'doctor': await handleDoctor(); break;
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
case '
|
|
185
|
-
case '
|
|
186
|
-
case '
|
|
187
|
-
case '
|
|
188
|
-
case '
|
|
189
|
-
|
|
190
|
-
|
|
261
|
+
|
|
262
|
+
// Project
|
|
263
|
+
case 'init': await handleInit(); break;
|
|
264
|
+
case 'deploy': if (clients) await handleDeploy(clients.agent); break;
|
|
265
|
+
case 'dev': if (clients) await handleDev(clients.agent); break;
|
|
266
|
+
case 'env': if (clients) await handleEnvMenu(clients.secret); break;
|
|
267
|
+
case 'logs': if (clients) await handleLogs(clients.agent); break;
|
|
268
|
+
|
|
269
|
+
// Tune
|
|
270
|
+
case 'tune-finetune': if (clients) await handleTuneFinetune(clients.tune, clients.model); break;
|
|
271
|
+
case 'tune-list': if (clients) await handleTuneList(clients.train); break;
|
|
272
|
+
case 'tune-generate': if (clients) await handleTuneGenerate(clients.tune); break;
|
|
273
|
+
|
|
274
|
+
// Vision
|
|
275
|
+
case 'vision-finetune': if (clients) await handleVisionFinetune(clients.vision, clients.model); break;
|
|
276
|
+
case 'vision-generate': if (clients) await handleVisionGenerate(clients.vision); break;
|
|
277
|
+
|
|
278
|
+
// Agents
|
|
279
|
+
case 'agent-list': if (clients) await handleAgentList(clients.agent); break;
|
|
280
|
+
case 'agent-create': if (clients) await handleAgentCreate(clients.agent, clients.model); break;
|
|
281
|
+
case 'agent-delete': if (clients) await handleAgentDelete(clients.agent); break;
|
|
282
|
+
|
|
283
|
+
// Data
|
|
284
|
+
case 'data-upload':
|
|
285
|
+
if (apiKey) await handleDataUpload(new FileClient({ apiKey }));
|
|
286
|
+
break;
|
|
287
|
+
case 'data-refine':
|
|
288
|
+
if (apiKey) await handleDataRefine(new FileClient({ apiKey }));
|
|
289
|
+
break;
|
|
290
|
+
|
|
291
|
+
// Guardrails
|
|
191
292
|
case 'guard-list': await handleGuardrailList(null); break;
|
|
192
293
|
case 'guard-create': await handleGuardrailCreate(null); break;
|
|
193
|
-
case 'data-refine': await handleDataRefine(new FileClient({ apiKey })); break;
|
|
194
294
|
}
|
|
195
|
-
|
|
196
|
-
// After action, where do we go?
|
|
197
|
-
// Stay in current state (sub-menu) is usually preferred.
|
|
198
|
-
|
|
199
295
|
} catch (error: any) {
|
|
200
296
|
outro(colors.red(`Error: ${error.message}`));
|
|
201
297
|
}
|
|
202
298
|
}
|
|
203
299
|
});
|
|
204
300
|
|
|
205
|
-
const dataCommand = program.command('data')
|
|
206
|
-
.description('Manage datasets');
|
|
207
|
-
|
|
208
|
-
dataCommand.command('upload [file]')
|
|
209
|
-
.description('Upload a dataset')
|
|
210
|
-
.action(async (file) => {
|
|
211
|
-
const config = getConfig();
|
|
212
|
-
const apiKey = config.apiKey || '';
|
|
213
|
-
const client = new FileClient({ apiKey, baseUrl: config.baseUrl });
|
|
214
|
-
// handleDataUpload only takes client, file is prompted inside or we need to update handleDataUpload signature
|
|
215
|
-
await handleDataUpload(client);
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
dataCommand.command('analyze')
|
|
219
|
-
.description('Analyze a dataset with AI')
|
|
220
|
-
.action(async () => {
|
|
221
|
-
const config = getConfig();
|
|
222
|
-
const apiKey = config.apiKey || '';
|
|
223
|
-
const client = new FileClient({ apiKey, baseUrl: config.baseUrl });
|
|
224
|
-
// handleDataAnalyze needs to be exported/imported
|
|
225
|
-
// Assuming I named it handleDataAnalyze in previous step (I did edit existing function, likely need to rename or export new one)
|
|
226
|
-
// Wait, I updated handleDataList in previous step to be the analyze function?
|
|
227
|
-
// No, I added code TO handleDataList or replaced it?
|
|
228
|
-
// Let me check previous tool call.
|
|
229
|
-
// I replaced the end of handleDataList (the mocked download part) with analyze logic?
|
|
230
|
-
// I should verify data.ts structure.
|
|
231
|
-
// Let's assume I need to properly export handleDataAnalyze.
|
|
232
|
-
// For now, I'll register it assuming export.
|
|
233
|
-
// For now, I'll register it assuming export.
|
|
234
|
-
const { handleDataList } = require('./handlers/data');
|
|
235
|
-
await handleDataList(client);
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
dataCommand.command('refine [fileId]')
|
|
239
|
-
.description('Refine a dataset using guardrails')
|
|
240
|
-
.action(async (fileId) => {
|
|
241
|
-
const config = getConfig();
|
|
242
|
-
const apiKey = config.apiKey || '';
|
|
243
|
-
const client = new FileClient({ apiKey, baseUrl: config.baseUrl });
|
|
244
|
-
await handleDataRefine(client, fileId);
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
const guardCommand = program.command('guardrails')
|
|
248
|
-
.description('Manage data guardrails');
|
|
249
|
-
|
|
250
|
-
guardCommand.command('list')
|
|
251
|
-
.description('List available guardrails')
|
|
252
|
-
.action(async () => {
|
|
253
|
-
await handleGuardrailList(null);
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
guardCommand.command('create')
|
|
257
|
-
.description('Create a new guardrail')
|
|
258
|
-
.action(async () => {
|
|
259
|
-
await handleGuardrailCreate(null);
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
|
|
263
301
|
main().catch(console.error);
|
|
264
|
-
|
|
265
|
-
function getMessageForState(state: MenuState): string {
|
|
266
|
-
switch (state) {
|
|
267
|
-
case 'main': return 'Main Menu:';
|
|
268
|
-
case 'agents': return 'Agents & Tools:';
|
|
269
|
-
case 'text': return 'Langtune (Text Operations):';
|
|
270
|
-
case 'vision': return 'Langvision (Vision Operations):';
|
|
271
|
-
case 'settings': return 'Settings:';
|
|
272
|
-
default: return 'Select an option:';
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
302
|
}
|
package/src/cli/menu.ts
CHANGED
|
@@ -9,45 +9,44 @@ export interface MenuOption {
|
|
|
9
9
|
export type MenuState = 'main' | 'agents' | 'text' | 'vision' | 'guard' | 'settings';
|
|
10
10
|
|
|
11
11
|
export function getMenu(state: MenuState, plan: SubscriptionInfo | null, isAuthenticated: boolean): MenuOption[] {
|
|
12
|
-
const isPro = plan?.plan === 'pro' || plan?.plan === 'enterprise';
|
|
13
12
|
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
// ── Unauthenticated: Claude-style minimal menu ─────────────────────────
|
|
14
|
+
if (!isAuthenticated) {
|
|
15
|
+
return [
|
|
16
|
+
{ value: 'login', label: '→ Login', hint: 'Authenticate with your API key' },
|
|
17
|
+
{ value: 'docs', label: ' Documentation', hint: 'https://docs.langtrain.ai' },
|
|
18
|
+
{ value: 'exit', label: ' Exit' }
|
|
19
|
+
];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ── Authenticated menus ────────────────────────────────────────────────
|
|
23
|
+
const planLabel = plan?.plan === 'pro' ? 'PRO' : plan?.plan === 'enterprise' ? 'ENTERPRISE' : 'FREE';
|
|
17
24
|
|
|
18
25
|
switch (state) {
|
|
19
26
|
case 'main':
|
|
20
|
-
|
|
21
|
-
{ value: 'nav-agents', label: 'Agents', hint: 'Manage &
|
|
22
|
-
{ value: 'nav-text', label: 'Langtune
|
|
23
|
-
{ value: 'nav-vision', label: 'Langvision
|
|
24
|
-
{ value: 'nav-guard', label: '
|
|
25
|
-
{ value: 'init', label: '
|
|
26
|
-
{ value: 'deploy', label: 'Deploy', hint: 'Push
|
|
27
|
-
{ value: 'dev', label: '
|
|
28
|
-
{ value: 'env', label: 'Secrets
|
|
29
|
-
{ value: 'logs', label: 'Logs', hint: '
|
|
30
|
-
{ value: '
|
|
31
|
-
{ value: '
|
|
27
|
+
return [
|
|
28
|
+
{ value: 'nav-agents', label: ' Agents', hint: 'Manage & deploy AI agents' },
|
|
29
|
+
{ value: 'nav-text', label: ' Langtune', hint: 'Text fine-tuning & generation' },
|
|
30
|
+
{ value: 'nav-vision', label: ' Langvision', hint: 'Vision fine-tuning & analysis' },
|
|
31
|
+
{ value: 'nav-guard', label: ' Guardrails', hint: 'Data quality & safety rules' },
|
|
32
|
+
{ value: 'init', label: ' Init Project', hint: 'Scaffold new Langtrain app' },
|
|
33
|
+
{ value: 'deploy', label: ' Deploy', hint: 'Push to Langtrain Cloud' },
|
|
34
|
+
{ value: 'dev', label: ' Dev Server', hint: 'Local watch mode' },
|
|
35
|
+
{ value: 'env', label: ' Secrets', hint: 'Manage environment variables' },
|
|
36
|
+
{ value: 'logs', label: ' Logs', hint: 'Stream agent logs' },
|
|
37
|
+
{ value: 'tokens', label: ' Token Usage', hint: 'View consumption this period' },
|
|
38
|
+
{ value: 'telemetry', label: ' Telemetry', hint: 'Session stats & API health' },
|
|
39
|
+
{ value: 'doctor', label: ' Doctor', hint: 'Check environment health' },
|
|
40
|
+
{ value: 'nav-settings', label: ' Settings', hint: `Plan: ${planLabel}` },
|
|
41
|
+
{ value: 'exit', label: ' Exit' }
|
|
32
42
|
];
|
|
33
43
|
|
|
34
|
-
if (!isAuthenticated) {
|
|
35
|
-
// menu.unshift({ value: 'login', label: 'Login to Langtrain', hint: 'Required for most features' });
|
|
36
|
-
// Actually, let's make Login the first option if not authenticated
|
|
37
|
-
// But keep the others so user can see what's available (and get prompted)
|
|
38
|
-
menu.unshift({ value: 'login', label: 'Login to Langtrain', hint: 'Required for most features' });
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Always add Exit
|
|
42
|
-
menu.push({ value: 'exit', label: 'Exit' });
|
|
43
|
-
return menu;
|
|
44
|
-
|
|
45
44
|
case 'agents':
|
|
46
45
|
return [
|
|
47
46
|
{ value: 'agent-list', label: 'List & Run Agents', hint: 'Chat with active agents' },
|
|
48
47
|
{ value: 'agent-create', label: 'Create New Agent', hint: 'Deploy a new agent' },
|
|
49
48
|
{ value: 'agent-delete', label: 'Delete Agent', hint: 'Remove an agent' },
|
|
50
|
-
{ value: 'back', label: '← Back
|
|
49
|
+
{ value: 'back', label: '← Back' }
|
|
51
50
|
];
|
|
52
51
|
|
|
53
52
|
case 'text':
|
|
@@ -56,7 +55,7 @@ export function getMenu(state: MenuState, plan: SubscriptionInfo | null, isAuthe
|
|
|
56
55
|
{ value: 'tune-list', label: 'List Jobs', hint: 'Check training status' },
|
|
57
56
|
{ value: 'tune-generate', label: 'Generate Text', hint: 'Test your models' },
|
|
58
57
|
{ value: 'data-upload', label: 'Upload Dataset', hint: 'Upload JSONL for training' },
|
|
59
|
-
{ value: 'back', label: '← Back
|
|
58
|
+
{ value: 'back', label: '← Back' }
|
|
60
59
|
];
|
|
61
60
|
|
|
62
61
|
case 'guard':
|
|
@@ -64,21 +63,24 @@ export function getMenu(state: MenuState, plan: SubscriptionInfo | null, isAuthe
|
|
|
64
63
|
{ value: 'guard-list', label: 'List Guardrails', hint: 'View active rules' },
|
|
65
64
|
{ value: 'guard-create', label: 'Create Guardrail', hint: 'Define new rules' },
|
|
66
65
|
{ value: 'data-refine', label: 'Refine Dataset', hint: 'Apply guardrail to data' },
|
|
67
|
-
{ value: 'back', label: '← Back
|
|
66
|
+
{ value: 'back', label: '← Back' }
|
|
68
67
|
];
|
|
69
68
|
|
|
70
69
|
case 'vision':
|
|
71
70
|
return [
|
|
72
71
|
{ value: 'vision-finetune', label: 'Fine-tune Vision Model', hint: 'Create custom VLM' },
|
|
73
72
|
{ value: 'vision-generate', label: 'Generate Vision Response', hint: 'Test vision models' },
|
|
74
|
-
{ value: 'back', label: '← Back
|
|
73
|
+
{ value: 'back', label: '← Back' }
|
|
75
74
|
];
|
|
76
75
|
|
|
77
76
|
case 'settings':
|
|
78
77
|
return [
|
|
79
|
-
{ value: 'status', label:
|
|
80
|
-
{ value: '
|
|
81
|
-
{ value: '
|
|
78
|
+
{ value: 'status', label: `Subscription (${planLabel})`, hint: 'View plan details' },
|
|
79
|
+
{ value: 'tokens', label: 'Token Usage', hint: 'View consumption' },
|
|
80
|
+
{ value: 'telemetry', label: 'Telemetry', hint: 'Session & API health' },
|
|
81
|
+
{ value: 'login', label: 'Update API Key', hint: 'Change credentials' },
|
|
82
|
+
{ value: 'logout', label: 'Logout', hint: 'Clear stored credentials' },
|
|
83
|
+
{ value: 'back', label: '← Back' }
|
|
82
84
|
];
|
|
83
85
|
|
|
84
86
|
default:
|