langtrain 0.1.14 → 0.1.16

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.
@@ -0,0 +1,154 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import path from 'path';
4
+ import { bgCyan, black, red, select, isCancel, outro, intro, gray } from './ui'; // Ensure clear is exported if added, otherwise use console.clear()
5
+ import { showBanner } from './ui';
6
+ import { ensureAuth, handleLogin, getSubscription } from './auth';
7
+ import { getMenu, MenuState } from './menu';
8
+ import { getConfig } from './config';
9
+
10
+ // Handlers
11
+ import { handleSubscriptionStatus } from './handlers/subscription';
12
+ import { handleTuneFinetune, handleTuneGenerate } from './handlers/tune';
13
+ import { handleVisionFinetune, handleVisionGenerate } from './handlers/vision';
14
+ import { handleAgentCreate, handleAgentDelete, handleAgentList } from './handlers/agent';
15
+
16
+ // Clients
17
+ import { SubscriptionInfo, Langvision, Langtune, AgentClient, ModelClient } from '../index';
18
+ import packageJson from '../../package.json';
19
+
20
+ export async function main() {
21
+ const program = new Command();
22
+ const version = packageJson.version;
23
+
24
+ program
25
+ .name('langtrain')
26
+ .description(packageJson.description || 'Langtrain CLI for AI Model Fine-tuning and Generation')
27
+ .version(version);
28
+
29
+ program.action(async () => {
30
+ showBanner(version);
31
+
32
+ // 1. Auth & Plan Check Force
33
+ // 1. Auth & Plan Check (Lazy)
34
+ // 0. First Run Check
35
+ const isFirstRun = process.argv.includes('--first-run');
36
+ if (isFirstRun) {
37
+ // Check if interactive
38
+ if (process.stdin.isTTY) {
39
+ intro('Welcome to Langtrain! Let\'s get you set up.');
40
+ await handleLogin();
41
+ // Reload config after login
42
+ } else {
43
+ console.log('Langtrain installed! Run "npx langtrain login" to authenticate.');
44
+ process.exit(0);
45
+ }
46
+ }
47
+
48
+ // 1. Auth & Plan Check (Lazy)
49
+ let config = getConfig();
50
+ let apiKey = config.apiKey || '';
51
+ let plan: SubscriptionInfo | null = null;
52
+
53
+ // Try to fetch plan if key exists?
54
+ if (apiKey) {
55
+ try { plan = await getSubscription(apiKey); } catch { }
56
+ }
57
+
58
+ // 2. Global Client Init
59
+ let clients = {
60
+ vision: new Langvision({ apiKey }),
61
+ tune: new Langtune({ apiKey }),
62
+ agent: new AgentClient({ apiKey, baseUrl: config.baseUrl }),
63
+ model: new ModelClient({ apiKey, baseUrl: config.baseUrl })
64
+ };
65
+
66
+ // 3. Navigation Loop
67
+ let currentState: MenuState = 'main';
68
+
69
+ while (true) {
70
+ // Clear screen for clean sub-menu navigation?
71
+ // Maybe not full clear to keep banner, but at least separate visual blocks.
72
+ // showBanner(version); // Re-showing banner might be too much flickering.
73
+ // console.log(''); // simple spacer
74
+
75
+ const operation = await select({
76
+ message: getMessageForState(currentState),
77
+ options: getMenu(currentState, plan, !!apiKey)
78
+ });
79
+
80
+ if (isCancel(operation)) {
81
+ if (currentState === 'main') {
82
+ outro('Goodbye!');
83
+ process.exit(0);
84
+ } else {
85
+ currentState = 'main';
86
+ continue;
87
+ }
88
+ }
89
+
90
+ const op = operation as string;
91
+
92
+ // Navigation Logic
93
+ if (op === 'exit') {
94
+ outro('Goodbye!');
95
+ process.exit(0);
96
+ }
97
+ if (op === 'back') {
98
+ currentState = 'main';
99
+ continue;
100
+ }
101
+ if (op.startsWith('nav-')) {
102
+ currentState = op.replace('nav-', '') as MenuState;
103
+ continue;
104
+ }
105
+
106
+ // Action Logic
107
+ try {
108
+ switch (op) {
109
+ case 'login':
110
+ await handleLogin();
111
+ config = getConfig();
112
+ apiKey = config.apiKey || '';
113
+ clients = {
114
+ vision: new Langvision({ apiKey }),
115
+ tune: new Langtune({ apiKey }),
116
+ agent: new AgentClient({ apiKey, baseUrl: config.baseUrl }),
117
+ model: new ModelClient({ apiKey, baseUrl: config.baseUrl })
118
+ };
119
+ try { plan = await getSubscription(apiKey); } catch { }
120
+ break;
121
+ case 'status': await handleSubscriptionStatus(); break;
122
+ case 'tune-finetune': await handleTuneFinetune(clients.tune, clients.model); break;
123
+ case 'tune-generate': await handleTuneGenerate(clients.tune); break;
124
+ case 'vision-finetune': await handleVisionFinetune(clients.vision, clients.model); break;
125
+ case 'vision-generate': await handleVisionGenerate(clients.vision); break;
126
+ case 'agent-list': await handleAgentList(clients.agent); break;
127
+ case 'agent-create': await handleAgentCreate(clients.agent, clients.model); break;
128
+ case 'agent-delete': await handleAgentDelete(clients.agent); break;
129
+ }
130
+
131
+ // After action, where do we go?
132
+ // Stay in current state (sub-menu) is usually preferred.
133
+
134
+ } catch (error: any) {
135
+ outro(red(`Error: ${error.message}`));
136
+ }
137
+ }
138
+ });
139
+
140
+ program.parse(process.argv);
141
+ }
142
+
143
+ main().catch(console.error);
144
+
145
+ function getMessageForState(state: MenuState): string {
146
+ switch (state) {
147
+ case 'main': return 'Main Menu:';
148
+ case 'agents': return 'Agents & Tools:';
149
+ case 'text': return 'Langtune (Text Operations):';
150
+ case 'vision': return 'Langvision (Vision Operations):';
151
+ case 'settings': return 'Settings:';
152
+ default: return 'Select an option:';
153
+ }
154
+ }
@@ -0,0 +1,70 @@
1
+ import { SubscriptionInfo } from '../index';
2
+
3
+ export interface MenuOption {
4
+ value: string;
5
+ label: string;
6
+ hint?: string;
7
+ }
8
+
9
+ export type MenuState = 'main' | 'agents' | 'text' | 'vision' | 'settings';
10
+
11
+ export function getMenu(state: MenuState, plan: SubscriptionInfo | null, isAuthenticated: boolean): MenuOption[] {
12
+ const isPro = plan?.plan === 'pro' || plan?.plan === 'enterprise';
13
+
14
+ // If not authenticated, force login or limited menu?
15
+ // User requested "lazy auth", so we should show menu but maybe highlight login or allow navigation and prompt later.
16
+ // Let's add a visual cue.
17
+
18
+ switch (state) {
19
+ case 'main':
20
+ const menu: MenuOption[] = [
21
+ { value: 'nav-agents', label: 'Agents', hint: 'Manage & Chat with AI Agents' },
22
+ { value: 'nav-text', label: 'Langtune (Text)', hint: 'Fine-tuning & Generation' },
23
+ { value: 'nav-vision', label: 'Langvision (Vision)', hint: 'Vision Analysis & Tuning' },
24
+ { value: 'nav-settings', label: 'Settings', hint: 'Subscription & Auth' }
25
+ ];
26
+
27
+ if (!isAuthenticated) {
28
+ // menu.unshift({ value: 'login', label: 'Login to Langtrain', hint: 'Required for most features' });
29
+ // Actually, let's make Login the first option if not authenticated
30
+ // But keep the others so user can see what's available (and get prompted)
31
+ menu.unshift({ value: 'login', label: 'Login to Langtrain', hint: 'Required for most features' });
32
+ }
33
+
34
+ // Always add Exit
35
+ menu.push({ value: 'exit', label: 'Exit' });
36
+ return menu;
37
+
38
+ case 'agents':
39
+ return [
40
+ { value: 'agent-list', label: 'List & Run Agents', hint: 'View active agents' },
41
+ { value: 'agent-create', label: 'Create New Agent', hint: 'Deploy a new agent' },
42
+ { value: 'agent-delete', label: 'Delete Agent', hint: 'Remove an agent' },
43
+ { value: 'back', label: '← Back to Main Menu' }
44
+ ];
45
+
46
+ case 'text':
47
+ return [
48
+ { value: 'tune-finetune', label: 'Fine-tune Text Model', hint: 'Create custom LLM' },
49
+ { value: 'tune-generate', label: 'Generate Text', hint: 'Test your models' },
50
+ { value: 'back', label: '← Back to Main Menu' }
51
+ ];
52
+
53
+ case 'vision':
54
+ return [
55
+ { value: 'vision-finetune', label: 'Fine-tune Vision Model', hint: 'Create custom VLM' },
56
+ { value: 'vision-generate', label: 'Generate Vision Response', hint: 'Test vision models' },
57
+ { value: 'back', label: '← Back to Main Menu' }
58
+ ];
59
+
60
+ case 'settings':
61
+ return [
62
+ { value: 'status', label: isAuthenticated ? `Subscription Status (${plan?.plan || 'Free'})` : 'Check Status (Login required)' },
63
+ { value: 'login', label: isAuthenticated ? 'Update API Key' : 'Login' },
64
+ { value: 'back', label: '← Back to Main Menu' }
65
+ ];
66
+
67
+ default:
68
+ return [];
69
+ }
70
+ }
package/src/cli/ui.ts ADDED
@@ -0,0 +1,50 @@
1
+ import { text, select, confirm, password, isCancel, cancel } from '@clack/prompts';
2
+ import { bgCyan, black, red, green, yellow, gray, cyan, bold } from 'kleur/colors';
3
+
4
+ // Gradient removed for cleaner look, or keep if user likes it but wants no emojis?
5
+ // User said "remove emojis", didn't explicitly say "remove colors/gradients", but "clean UI" usually implies less noise.
6
+ // I will keep the banner gradient as it is a brand element, but remove emojis from intro/outro.
7
+ import gradient from 'gradient-string';
8
+
9
+ export function showBanner(version: string) {
10
+ console.clear();
11
+ const banner = `
12
+ ██╗ █████╗ ███╗ ██╗ ██████╗████████╗██████╗ █████╗ ██╗███╗ ██╗
13
+ ██║ ██╔══██╗████╗ ██║██╔════╝╚══██╔══╝██╔══██╗██╔══██╗██║████╗ ██║
14
+ ██║ ███████║██╔██╗ ██║██║ ███╗ ██║ ██████╔╝███████║██║██╔██╗ ██║
15
+ ██║ ██╔══██║██║╚██╗██║██║ ██║ ██║ ██╔══██╗██╔══██║██║██║╚██╗██║
16
+ ███████╗██║ ██║██║ ╚████║╚██████╔╝ ██║ ██║ ██║██║ ██║██║██║ ╚████║
17
+ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝
18
+ `;
19
+ console.log(gradient(['#00DC82', '#36E4DA', '#0047E1'])(banner));
20
+ console.log(`${bgCyan(black(` Langtrain SDK v${version} `))}\n`);
21
+ }
22
+
23
+ export function intro(message: string) {
24
+ console.log(cyan(`◆ ${message}`));
25
+ }
26
+
27
+ export function outro(message: string) {
28
+ console.log(gray(`└ ${message}`));
29
+ }
30
+
31
+ export function spinner() {
32
+ return {
33
+ start: (msg: string) => process.stdout.write(`${cyan('●')} ${msg}\r`),
34
+ stop: (msg?: string) => {
35
+ if (msg) console.log(`${green('✔')} ${msg}`);
36
+ else console.log(''); // Newline
37
+ },
38
+ message: (msg: string) => process.stdout.write(`${cyan('●')} ${msg}\r`)
39
+ };
40
+ }
41
+
42
+ export function showError(message: string) {
43
+ console.log(red(`✖ Error: ${message}`));
44
+ }
45
+
46
+ export function showSuccess(message: string) {
47
+ console.log(green(`✔ ${message}`));
48
+ }
49
+
50
+ export { text, select, confirm, password, isCancel, cancel, bgCyan, black, red, green, yellow, gray, cyan, bold, gradient };
package/src/index.ts CHANGED
@@ -4,14 +4,16 @@ export { Langvision } from 'langvision';
4
4
  export { Langtune } from 'langtune';
5
5
 
6
6
  // Export Agent Client
7
- export { AgentClient, Agent, AgentRun, AgentCreate } from './agent';
8
- export { FileClient, FileResponse } from './files';
9
- export { TrainingClient, FineTuneJobCreate, FineTuneJobResponse } from './training';
10
- export { SubscriptionClient, SubscriptionInfo, FeatureCheck } from './subscription';
7
+ export { AgentClient, Agent, AgentRun, AgentCreate } from './lib/agent';
8
+ export { FileClient, FileResponse } from './lib/files';
9
+ export { TrainingClient, FineTuneJobCreate, FineTuneJobResponse } from './lib/training';
10
+ export { SubscriptionClient, SubscriptionInfo, FeatureCheck } from './lib/subscription';
11
+ export { ModelClient, Model } from './lib/models';
11
12
 
12
13
  // Export Types with Namespaces to avoid collisions
13
14
  import * as Vision from 'langvision';
14
15
  import * as Text from 'langtune';
15
- import * as AgentTypes from './agent';
16
+ import * as AgentTypes from './lib/agent';
17
+ import * as ModelTypes from './lib/models';
16
18
 
17
- export { Vision, Text, AgentTypes };
19
+ export { Vision, Text, AgentTypes, ModelTypes };
@@ -6,7 +6,7 @@ export interface Agent {
6
6
  name: string;
7
7
  description?: string;
8
8
  model_id?: string;
9
- config: any;
9
+ config: Record<string, unknown>;
10
10
  is_active: boolean;
11
11
  created_at: string;
12
12
  updated_at: string;
@@ -16,7 +16,7 @@ export interface AgentRun {
16
16
  id: string;
17
17
  conversation_id: string;
18
18
  success: boolean;
19
- output?: any;
19
+ output?: unknown;
20
20
  error?: string;
21
21
  latency_ms: number;
22
22
  tokens_used: number;
@@ -0,0 +1,61 @@
1
+ import axios, { AxiosInstance } from 'axios';
2
+
3
+ export interface Permission {
4
+ id: string;
5
+ object: string;
6
+ created: number;
7
+ allow_create_engine: boolean;
8
+ allow_sampling: boolean;
9
+ allow_logprobs: boolean;
10
+ allow_search_indices: boolean;
11
+ allow_view: boolean;
12
+ allow_fine_tuning: boolean;
13
+ organization: string;
14
+ group: any;
15
+ is_blocking: boolean;
16
+ }
17
+
18
+ export interface Model {
19
+ id: string;
20
+ object: string; // 'model'
21
+ created: number;
22
+ owned_by: string;
23
+ permission: Permission[];
24
+ root: string;
25
+ parent: string | null;
26
+ task?: 'text' | 'vision' | 'agent';
27
+ }
28
+
29
+ export class ModelClient {
30
+ private client: AxiosInstance;
31
+
32
+ constructor(config: { apiKey: string, baseUrl?: string }) {
33
+ this.client = axios.create({
34
+ baseURL: config.baseUrl || 'https://api.langtrain.ai/api/v1',
35
+ headers: {
36
+ 'X-API-Key': config.apiKey,
37
+ 'Content-Type': 'application/json'
38
+ }
39
+ });
40
+ }
41
+
42
+ async list(task?: string): Promise<Model[]> {
43
+ const params: any = {};
44
+ if (task) params.task = task;
45
+
46
+ try {
47
+ const response = await this.client.get<{ data: Model[] }>('/models', { params });
48
+ return response.data.data;
49
+ } catch (error) {
50
+ // Fallback if endpoint doesn't support filtering or fails
51
+ // Verify if /models exists, otherwise return empty or throw
52
+ // customized error handling could go here
53
+ throw error;
54
+ }
55
+ }
56
+
57
+ async get(modelId: string): Promise<Model> {
58
+ const response = await this.client.get<Model>(`/models/${modelId}`);
59
+ return response.data;
60
+ }
61
+ }
package/tsup.config.ts CHANGED
@@ -1,11 +1,16 @@
1
1
  import { defineConfig } from 'tsup';
2
2
 
3
3
  export default defineConfig({
4
- entry: ['src/index.ts', 'src/cli.ts'],
4
+ entry: {
5
+ index: 'src/index.ts',
6
+ cli: 'src/cli/index.ts',
7
+ },
5
8
  format: ['cjs', 'esm'],
6
9
  dts: true,
7
- splitting: false,
10
+ splitting: true,
8
11
  sourcemap: true,
9
12
  clean: true,
13
+ minify: true,
14
+ treeshake: true,
10
15
  noExternal: ['langvision', 'langtune'],
11
16
  });