langtrain 0.1.18 → 0.1.19

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,84 @@
1
+ import { text, select, confirm, isCancel, cancel, spinner, intro, red, green, yellow, gray } from '../ui';
2
+ import { getConfig } from '../config';
3
+ import { FileClient } from '../../index';
4
+ import path from 'path';
5
+ import fs from 'fs';
6
+
7
+ export async function handleDataUpload(client: FileClient) {
8
+ const config = getConfig();
9
+ let workspaceId = config.workspace_id;
10
+
11
+ if (!workspaceId) {
12
+ // Optional: ask for workspace ID or try to infer?
13
+ // For upload, workspace_id is often optional (inferred from API key's default workspace)
14
+ // But let's ask if user wants to specify.
15
+ }
16
+
17
+ const filePath = await text({
18
+ message: 'Path to file:',
19
+ placeholder: './dataset.jsonl',
20
+ validate(value) {
21
+ if (!value) return 'Required';
22
+ if (!fs.existsSync(value)) return 'File not found';
23
+ }
24
+ });
25
+
26
+ if (isCancel(filePath)) return;
27
+
28
+ const purpose = await select({
29
+ message: 'File Purpose:',
30
+ options: [
31
+ { value: 'fine-tune', label: 'Fine-tuning (JSONL)' },
32
+ { value: 'vision-tune', label: 'Vision Tuning (Image/Zip)' },
33
+ { value: 'agent-knowledge', label: 'Agent Knowledge' }
34
+ ]
35
+ });
36
+
37
+ if (isCancel(purpose)) return;
38
+
39
+ const s = spinner();
40
+ s.start('Uploading file...');
41
+
42
+ try {
43
+ const result = await client.upload(filePath as string, workspaceId, purpose as string);
44
+ s.stop(green('File uploaded successfully!'));
45
+ console.log(gray(`ID: ${result.id}`));
46
+ console.log(gray(`Name: ${result.filename}`));
47
+ console.log(gray(`Bytes: ${result.bytes}`));
48
+ } catch (e: any) {
49
+ s.stop(red(`Upload failed: ${e.message}`));
50
+ }
51
+ }
52
+
53
+ export async function handleDataList(client: FileClient) {
54
+ const config = getConfig();
55
+ let workspaceId = config.workspace_id;
56
+
57
+ if (!workspaceId) {
58
+ // Try without workspace ID (some APIs return user's files)
59
+ // or ask
60
+ workspaceId = await text({ message: 'Enter Workspace ID (optional):', initialValue: '' });
61
+ if (isCancel(workspaceId)) return;
62
+ }
63
+
64
+ const s = spinner();
65
+ s.start('Fetching files...');
66
+
67
+ try {
68
+ const files = await client.list(workspaceId as string);
69
+ s.stop(`Found ${files.length} files`);
70
+
71
+ if (files.length === 0) {
72
+ console.log(yellow('No files found.'));
73
+ return;
74
+ }
75
+
76
+ // Just list them for now, maybe select to delete later?
77
+ files.forEach(f => {
78
+ console.log(`${f.id.padEnd(30)} ${f.filename.padEnd(20)} ${f.purpose} (${f.bytes}b)`);
79
+ });
80
+
81
+ } catch (e: any) {
82
+ s.stop(red(`Failed to list files: ${e.message}`));
83
+ }
84
+ }
@@ -0,0 +1,62 @@
1
+ import { intro, outro, spinner, green, red, yellow, showSuccess } from '../ui';
2
+ import { getConfig } from '../config';
3
+ import { AgentClient, AgentCreate } from '../../index';
4
+
5
+ export async function handleDeploy(client: AgentClient) {
6
+ intro('Deploying configuration to Langtrain Cloud...');
7
+
8
+ const config = getConfig();
9
+ const agents = config.agents || [];
10
+
11
+ if (agents.length === 0) {
12
+ intro(yellow('No agents found in langtrain.config.json'));
13
+ return;
14
+ }
15
+
16
+ // Iterate and deploy
17
+ for (const agentConfig of agents) {
18
+ const s = spinner();
19
+ s.start(`Deploying agent: ${agentConfig.name}...`);
20
+
21
+ try {
22
+ // Check if agent exists (by name? logic needed)
23
+ // Ideally we store ID in config after create, but for now let's just create new or try to find by name.
24
+ // Listing all agents is expensive if many, but safe for now.
25
+ const existingAgents = await client.list();
26
+ const existing = existingAgents.find(a => a.name === agentConfig.name);
27
+
28
+ if (existing) {
29
+ // Update (Note: SDK didn't expose update in my view_file, assuming create or need to add update)
30
+ // If update not available, we skip or warn.
31
+ // Let's assume we can't update yet as per SDK view.
32
+ // So we just skip if exists.
33
+ s.stop(yellow(`Agent ${agentConfig.name} already exists (ID: ${existing.id}). Skipping update (not supported yet).`));
34
+ } else {
35
+ // Create
36
+ const payload: AgentCreate = {
37
+ workspace_id: config.workspace_id || (existingAgents[0]?.workspace_id) || '', // Need a way to get workspace!
38
+ name: agentConfig.name,
39
+ description: agentConfig.description,
40
+ config: agentConfig.config
41
+ };
42
+
43
+ // Fallback for workspace_id
44
+ if (!payload.workspace_id) {
45
+ // Try to get from first agent or error
46
+ // Realistically, user needs to set workspace_id in config or we infer from API key scope.
47
+ // Let's warn.
48
+ s.stop(red(`Failed: Workspace ID missing in config for ${agentConfig.name}`));
49
+ continue;
50
+ }
51
+
52
+ await client.create(payload);
53
+ s.stop(green(`Agent ${agentConfig.name} deployed successfully!`));
54
+ }
55
+
56
+ } catch (e: any) {
57
+ s.stop(red(`Failed to deploy ${agentConfig.name}: ${e.message}`));
58
+ }
59
+ }
60
+
61
+ showSuccess('Deployment complete.');
62
+ }
@@ -0,0 +1,42 @@
1
+ import { intro, outro, spinner, green, red, yellow, showInfo, gray } from '../ui';
2
+ import { AgentClient } from '../../index';
3
+ import { handleDeploy } from './deploy';
4
+ import fs from 'fs';
5
+ import path from 'path';
6
+
7
+ export async function handleDev(client: AgentClient) {
8
+ intro('Starting Langtrain Development Server...');
9
+
10
+ const configPath = path.join(process.cwd(), 'langtrain.config.json');
11
+ if (!fs.existsSync(configPath)) {
12
+ intro(red('langtrain.config.json not found. Run "lt init" first.'));
13
+ return;
14
+ }
15
+
16
+ console.log(gray(`Watching ${configPath} for changes...`));
17
+
18
+ let isDeploying = false;
19
+
20
+ // Initial Deploy
21
+ await handleDeploy(client);
22
+
23
+ fs.watch(configPath, async (eventType) => {
24
+ if (eventType === 'change' && !isDeploying) {
25
+ isDeploying = true;
26
+ console.log(yellow('Configuration changed. Redeploying...'));
27
+ // Wait a bit for file write to complete
28
+ await new Promise(r => setTimeout(r, 500));
29
+ try {
30
+ await handleDeploy(client);
31
+ } catch (e: any) {
32
+ console.error(red(`Deploy failed: ${e.message}`));
33
+ } finally {
34
+ isDeploying = false;
35
+ console.log(gray(`Watching ${configPath}...`));
36
+ }
37
+ }
38
+ });
39
+
40
+ // Keep process alive
41
+ await new Promise(() => { });
42
+ }
@@ -0,0 +1,54 @@
1
+ import { intro, outro, showSuccess, showError, showWarning, showInfo, spinner, colors } from '../ui';
2
+ import { getConfig } from '../config';
3
+ import { getSubscription } from '../auth';
4
+ import os from 'os';
5
+
6
+ export async function handleDoctor() {
7
+ intro('Running Langtrain Doctor...');
8
+
9
+ const s = spinner();
10
+ let issues = 0;
11
+
12
+ // 1. Check Node Environment
13
+ s.start('Checking Node.js environment...');
14
+ const nodeVersion = process.version;
15
+ const platform = os.platform();
16
+ const arch = os.arch();
17
+
18
+ if (parseInt(nodeVersion.replace('v', '').split('.')[0]) < 18) {
19
+ s.stop(colors.red(`Node.js version ${nodeVersion} is outdated. Please upgrade to v18+.`));
20
+ issues++;
21
+ } else {
22
+ s.stop(`Node.js ${nodeVersion} (${platform} ${arch})`);
23
+ }
24
+
25
+ // 2. Check Configuration
26
+ s.start('Checking configuration...');
27
+ const config = getConfig();
28
+ if (!config.apiKey) {
29
+ s.stop(colors.yellow('API Key is missing. Run `langtrain login` or set LANGTRAIN_API_KEY.'));
30
+ issues++;
31
+ } else {
32
+ s.stop('Configuration found.');
33
+
34
+ // 3. Check API Connectivity
35
+ s.start('Checking API connectivity...');
36
+ try {
37
+ const plan = await getSubscription(config.apiKey);
38
+ s.stop(`Connected to Langtrain Cloud (Plan: ${colors.green(plan?.plan || 'unknown')})`);
39
+ } catch (e: any) {
40
+ s.stop(colors.red(`Failed to connect to Langtrain Cloud: ${e.message}`));
41
+ issues++;
42
+ }
43
+ }
44
+
45
+ console.log(''); // Spacer
46
+
47
+ if (issues === 0) {
48
+ showSuccess('Your Langtrain environment is healthy! Ready to build.');
49
+ } else {
50
+ showWarning(`Found ${issues} issue(s). Please resolve them for the best experience.`);
51
+ }
52
+
53
+ outro('Doctor check complete.');
54
+ }
@@ -0,0 +1,104 @@
1
+ import path from 'path';
2
+ import fs from 'fs';
3
+ import { text, confirm, select, isCancel, cancel, intro, outro, showSuccess, showInfo, spinner, colors } from '../ui';
4
+ import { getConfig } from '../config';
5
+ import { handleLogin } from '../auth';
6
+
7
+ export async function handleInit() {
8
+ intro('Initializing new Langtrain project...');
9
+
10
+ const cwd = process.cwd();
11
+
12
+ // 1. Check if already initialized
13
+ if (fs.existsSync(path.join(cwd, 'langtrain.config.json'))) {
14
+ showInfo('langtrain.config.json already exists in this directory.');
15
+ const overwrite = await confirm({
16
+ message: 'Do you want to re-initialize and overwrite the config?',
17
+ initialValue: false
18
+ });
19
+
20
+ if (isCancel(overwrite) || !overwrite) {
21
+ outro('Initialization cancelled.');
22
+ return;
23
+ }
24
+ }
25
+
26
+ // 2. Ask for Project Details
27
+ const projectName = await text({
28
+ message: 'What is the name of your project?',
29
+ placeholder: 'my-ai-app',
30
+ initialValue: path.basename(cwd),
31
+ validate(value) {
32
+ if (!value || value.length === 0) return 'Project name is required!';
33
+ }
34
+ });
35
+
36
+ if (isCancel(projectName)) {
37
+ cancel('Operation cancelled.');
38
+ return;
39
+ }
40
+
41
+ let config = getConfig();
42
+ let apiKey = config.apiKey;
43
+
44
+ if (apiKey) {
45
+ showSuccess('Found existing Langtrain credentials.');
46
+ } else {
47
+ const shouldLogin = await confirm({
48
+ message: 'You are not logged in. Do you want to log in now?',
49
+ initialValue: true
50
+ });
51
+
52
+ if (isCancel(shouldLogin)) {
53
+ cancel('Operation cancelled.');
54
+ return;
55
+ }
56
+
57
+ if (shouldLogin) {
58
+ await handleLogin();
59
+ config = getConfig(); // Reload config
60
+ apiKey = config.apiKey;
61
+ } else {
62
+ apiKey = await text({
63
+ message: 'Enter your Langtrain API Key (optional for local dev):',
64
+ placeholder: 'lt_sk_...',
65
+ initialValue: ''
66
+ }) as string;
67
+
68
+ if (isCancel(apiKey)) {
69
+ cancel('Operation cancelled.');
70
+ return;
71
+ }
72
+ }
73
+ }
74
+
75
+ // 3. Create Config File
76
+ const s = spinner();
77
+ s.start('Creating configuration...');
78
+
79
+ const configContent = {
80
+ name: projectName,
81
+ apiKey: apiKey || undefined,
82
+ environment: 'development',
83
+ agents: [
84
+ {
85
+ name: 'support-bot',
86
+ description: 'A helpful customer support assistant',
87
+ config: {
88
+ model: 'llama-3-8b',
89
+ system_prompt: 'You are a helpful customer support assistant.',
90
+ temperature: 0.7
91
+ }
92
+ }
93
+ ]
94
+ };
95
+
96
+ fs.writeFileSync(path.join(cwd, 'langtrain.config.json'), JSON.stringify(configContent, null, 2));
97
+
98
+ showSuccess('Project initialized successfully!');
99
+ console.log(colors.dim('\nNext steps:'));
100
+ console.log(` 1. Run ${colors.cyan('lt deploy')} to push your agent to the cloud.`);
101
+ console.log(` 2. Run ${colors.cyan('lt dev')} to start the local development loop.`);
102
+
103
+ outro('Happy coding!');
104
+ }
@@ -1,4 +1,4 @@
1
- import { text, select, confirm, isCancel, cancel, spinner, intro, red, green, yellow, bgCyan, black, gradient } from '../ui';
1
+ import { text, select, confirm, isCancel, cancel, spinner, intro, red, green, yellow, bgCyan, black, gradient, gray } from '../ui';
2
2
  import { getConfig } from '../config';
3
3
  import { Langtune, ModelClient, SubscriptionClient, FileClient, TrainingClient } from '../../index';
4
4
 
@@ -153,3 +153,105 @@ export async function handleTuneGenerate(tune: Langtune) {
153
153
  throw e;
154
154
  }
155
155
  }
156
+
157
+ export async function handleTuneList(trainingClient: TrainingClient) {
158
+ const s = spinner();
159
+ s.start('Fetching fine-tuning jobs...');
160
+
161
+ // We need workspace ID, usually from config or first agent?
162
+ // For now, let's just ask or list from all available if API supports it (it requires workspace_id)
163
+ // Let's assume user knows it or we can find it.
164
+ // Simplified: Just ask for Workspace ID if not in config (we don't save it yet)
165
+ // BETTER: Get it from an existing agent or config.
166
+ const config = getConfig();
167
+ let workspaceId = config.workspace_id;
168
+
169
+ if (!workspaceId) {
170
+ s.stop(yellow('Workspace ID required to list jobs.'));
171
+ workspaceId = await text({ message: 'Enter Workspace ID:' });
172
+ if (isCancel(workspaceId)) return;
173
+ }
174
+
175
+ try {
176
+ const jobs = await trainingClient.listJobs(workspaceId as string);
177
+ s.stop(`Found ${jobs.data.length} jobs`);
178
+
179
+ if (jobs.data.length === 0) {
180
+ console.log(yellow('No jobs found.'));
181
+ return;
182
+ }
183
+
184
+ const selectedJob = await select({
185
+ message: 'Select a job to view details:',
186
+ options: jobs.data.map(j => ({
187
+ value: j.id,
188
+ label: `${j.name || j.id} (${j.status})`,
189
+ hint: `Created: ${new Date(j.created_at).toLocaleDateString()}`
190
+ }))
191
+ });
192
+
193
+ if (isCancel(selectedJob)) return;
194
+
195
+ await handleTuneStatus(trainingClient, selectedJob as string);
196
+
197
+ } catch (e: any) {
198
+ s.stop(red(`Failed to list jobs: ${e.message}`));
199
+ }
200
+ }
201
+
202
+ export async function handleTuneStatus(trainingClient: TrainingClient, jobId?: string) {
203
+ let id = jobId;
204
+ if (!id) {
205
+ id = await text({ message: 'Enter Job ID:' }) as string;
206
+ if (isCancel(id)) return;
207
+ }
208
+
209
+ const s = spinner();
210
+ s.start(`Fetching status for ${id}...`);
211
+
212
+ try {
213
+ const job = await trainingClient.getJob(id);
214
+ s.stop(`Job Status: ${job.status.toUpperCase()}`);
215
+
216
+ console.log(gray('------------------------------------------------'));
217
+ console.log(`${bgCyan(black(' Job Details '))}`);
218
+ console.log(`ID: ${job.id}`);
219
+ console.log(`Name: ${job.name}`);
220
+ console.log(`Status: ${job.status === 'succeeded' ? green(job.status) : job.status}`);
221
+ console.log(`Model: ${job.base_model}`);
222
+ console.log(`Progress: ${job.progress || 0}%`);
223
+ if (job.error_message) console.log(red(`Error: ${job.error_message}`));
224
+ console.log(gray('------------------------------------------------'));
225
+
226
+ if (job.status === 'running' || job.status === 'queued') {
227
+ const action = await select({
228
+ message: 'Action:',
229
+ options: [
230
+ { value: 'refresh', label: 'Refresh Status' },
231
+ { value: 'cancel', label: 'Cancel Job' },
232
+ { value: 'back', label: 'Back' }
233
+ ]
234
+ });
235
+
236
+ if (action === 'refresh') await handleTuneStatus(trainingClient, id);
237
+ if (action === 'cancel') await handleTuneCancel(trainingClient, id);
238
+ }
239
+
240
+ } catch (e: any) {
241
+ s.stop(red(`Failed to get job status: ${e.message}`));
242
+ }
243
+ }
244
+
245
+ export async function handleTuneCancel(trainingClient: TrainingClient, jobId: string) {
246
+ const confirmCancel = await confirm({ message: 'Are you sure you want to cancel this job?' });
247
+ if (!confirmCancel || isCancel(confirmCancel)) return;
248
+
249
+ const s = spinner();
250
+ s.start('Canceling job...');
251
+ try {
252
+ await trainingClient.cancelJob(jobId);
253
+ s.stop(green('Job canceled successfully.'));
254
+ } catch (e: any) {
255
+ s.stop(red(`Failed to cancel job: ${e.message}`));
256
+ }
257
+ }
package/src/cli/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from 'commander';
3
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()
4
+ import { select, isCancel, outro, intro, colors } from './ui'; // Ensure clear is exported if added, otherwise use console.clear()
5
5
  import { showBanner } from './ui';
6
6
  import { ensureAuth, handleLogin, getSubscription } from './auth';
7
7
  import { getMenu, MenuState } from './menu';
@@ -9,12 +9,17 @@ import { getConfig } from './config';
9
9
 
10
10
  // Handlers
11
11
  import { handleSubscriptionStatus } from './handlers/subscription';
12
- import { handleTuneFinetune, handleTuneGenerate } from './handlers/tune';
12
+ import { handleTuneFinetune, handleTuneGenerate, handleTuneList } from './handlers/tune';
13
13
  import { handleVisionFinetune, handleVisionGenerate } from './handlers/vision';
14
14
  import { handleAgentCreate, handleAgentDelete, handleAgentList } from './handlers/agent';
15
+ import { handleInit } from './handlers/init';
16
+ import { handleDoctor } from './handlers/doctor';
17
+ import { handleDataUpload } from './handlers/data';
18
+ import { handleDeploy } from './handlers/deploy';
19
+ import { handleDev } from './handlers/dev';
15
20
 
16
21
  // Clients
17
- import { SubscriptionInfo, Langvision, Langtune, AgentClient, ModelClient } from '../index';
22
+ import { SubscriptionInfo, Langvision, Langtune, AgentClient, ModelClient, FileClient, TrainingClient } from '../index';
18
23
  import packageJson from '../../package.json';
19
24
 
20
25
  export async function main() {
@@ -26,6 +31,29 @@ export async function main() {
26
31
  .description(packageJson.description || 'Langtrain CLI for AI Model Fine-tuning and Generation')
27
32
  .version(version);
28
33
 
34
+ // Register standalone commands
35
+ program.command('init')
36
+ .description('Initialize a new Langtrain project')
37
+ .action(handleInit);
38
+
39
+ program.command('deploy')
40
+ .description('Deploy configuration to Langtrain Cloud')
41
+ .action(async () => {
42
+ const config = getConfig();
43
+ const apiKey = config.apiKey || '';
44
+ const client = new AgentClient({ apiKey, baseUrl: config.baseUrl });
45
+ await handleDeploy(client);
46
+ });
47
+
48
+ program.command('dev')
49
+ .description('Start local development server (Watch Mode)')
50
+ .action(async () => {
51
+ const config = getConfig();
52
+ const apiKey = config.apiKey || '';
53
+ const client = new AgentClient({ apiKey, baseUrl: config.baseUrl });
54
+ await handleDev(client);
55
+ });
56
+
29
57
  program.action(async () => {
30
58
  showBanner(version);
31
59
 
@@ -60,7 +88,8 @@ export async function main() {
60
88
  vision: new Langvision({ apiKey }),
61
89
  tune: new Langtune({ apiKey }),
62
90
  agent: new AgentClient({ apiKey, baseUrl: config.baseUrl }),
63
- model: new ModelClient({ apiKey, baseUrl: config.baseUrl })
91
+ model: new ModelClient({ apiKey, baseUrl: config.baseUrl }),
92
+ train: new TrainingClient({ apiKey, baseUrl: config.baseUrl })
64
93
  };
65
94
 
66
95
  // 3. Navigation Loop
@@ -114,25 +143,32 @@ export async function main() {
114
143
  vision: new Langvision({ apiKey }),
115
144
  tune: new Langtune({ apiKey }),
116
145
  agent: new AgentClient({ apiKey, baseUrl: config.baseUrl }),
117
- model: new ModelClient({ apiKey, baseUrl: config.baseUrl })
146
+ model: new ModelClient({ apiKey, baseUrl: config.baseUrl }),
147
+ train: new TrainingClient({ apiKey, baseUrl: config.baseUrl })
118
148
  };
119
149
  try { plan = await getSubscription(apiKey); } catch { }
120
150
  break;
121
151
  case 'status': await handleSubscriptionStatus(); break;
152
+ case 'init': await handleInit(); break;
153
+ case 'deploy': await handleDeploy(clients.agent); break;
154
+ case 'dev': await handleDev(clients.agent); break;
155
+ case 'doctor': await handleDoctor(); break;
122
156
  case 'tune-finetune': await handleTuneFinetune(clients.tune, clients.model); break;
157
+ case 'tune-list': await handleTuneList(clients.train); break;
123
158
  case 'tune-generate': await handleTuneGenerate(clients.tune); break;
124
159
  case 'vision-finetune': await handleVisionFinetune(clients.vision, clients.model); break;
125
160
  case 'vision-generate': await handleVisionGenerate(clients.vision); break;
126
161
  case 'agent-list': await handleAgentList(clients.agent); break;
127
162
  case 'agent-create': await handleAgentCreate(clients.agent, clients.model); break;
128
163
  case 'agent-delete': await handleAgentDelete(clients.agent); break;
164
+ case 'data-upload': await handleDataUpload(new FileClient({ apiKey })); break;
129
165
  }
130
166
 
131
167
  // After action, where do we go?
132
168
  // Stay in current state (sub-menu) is usually preferred.
133
169
 
134
170
  } catch (error: any) {
135
- outro(red(`Error: ${error.message}`));
171
+ outro(colors.red(`Error: ${error.message}`));
136
172
  }
137
173
  }
138
174
  });
package/src/cli/menu.ts CHANGED
@@ -21,6 +21,10 @@ export function getMenu(state: MenuState, plan: SubscriptionInfo | null, isAuthe
21
21
  { value: 'nav-agents', label: 'Agents', hint: 'Manage & Chat with AI Agents' },
22
22
  { value: 'nav-text', label: 'Langtune (Text)', hint: 'Fine-tuning & Generation' },
23
23
  { value: 'nav-vision', label: 'Langvision (Vision)', hint: 'Vision Analysis & Tuning' },
24
+ { value: 'init', label: 'Initialize Project', hint: 'Scaffold new Langtrain app' },
25
+ { value: 'deploy', label: 'Deploy', hint: 'Push config to Cloud' },
26
+ { value: 'dev', label: 'Start Dev Server', hint: 'Watch mode' },
27
+ { value: 'doctor', label: 'Doctor', hint: 'Check environment health' },
24
28
  { value: 'nav-settings', label: 'Settings', hint: 'Subscription & Auth' }
25
29
  ];
26
30
 
@@ -37,7 +41,7 @@ export function getMenu(state: MenuState, plan: SubscriptionInfo | null, isAuthe
37
41
 
38
42
  case 'agents':
39
43
  return [
40
- { value: 'agent-list', label: 'List & Run Agents', hint: 'View active agents' },
44
+ { value: 'agent-list', label: 'List & Run Agents', hint: 'Chat with active agents' },
41
45
  { value: 'agent-create', label: 'Create New Agent', hint: 'Deploy a new agent' },
42
46
  { value: 'agent-delete', label: 'Delete Agent', hint: 'Remove an agent' },
43
47
  { value: 'back', label: '← Back to Main Menu' }
@@ -46,7 +50,9 @@ export function getMenu(state: MenuState, plan: SubscriptionInfo | null, isAuthe
46
50
  case 'text':
47
51
  return [
48
52
  { value: 'tune-finetune', label: 'Fine-tune Text Model', hint: 'Create custom LLM' },
53
+ { value: 'tune-list', label: 'List Jobs', hint: 'Check training status' },
49
54
  { value: 'tune-generate', label: 'Generate Text', hint: 'Test your models' },
55
+ { value: 'data-upload', label: 'Upload Dataset', hint: 'Upload JSONL for training' },
50
56
  { value: 'back', label: '← Back to Main Menu' }
51
57
  ];
52
58
 
package/src/cli/ui.ts CHANGED
@@ -1,11 +1,10 @@
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.
1
+ import { text, select, confirm, password, isCancel, cancel, note } from '@clack/prompts';
2
+ import { bgCyan, black, red, green, yellow, gray, cyan, bold, dim, blue } from 'kleur/colors';
7
3
  import gradient from 'gradient-string';
8
4
 
5
+ // Re-export specific prompts to keep imports clean in other files
6
+ export { text, select, confirm, password, isCancel, cancel, note };
7
+
9
8
  export function showBanner(version: string) {
10
9
  console.clear();
11
10
  const banner = `
@@ -47,4 +46,22 @@ export function showSuccess(message: string) {
47
46
  console.log(green(`✔ ${message}`));
48
47
  }
49
48
 
50
- export { text, select, confirm, password, isCancel, cancel, bgCyan, black, red, green, yellow, gray, cyan, bold, gradient };
49
+ export function showWarning(message: string) {
50
+ console.log(yellow(`⚠ Warning: ${message}`));
51
+ }
52
+
53
+ export function showInfo(message: string) {
54
+ console.log(blue(`ℹ ${message}`));
55
+ }
56
+
57
+ export function showDim(message: string) {
58
+ console.log(dim(message));
59
+ }
60
+
61
+ // Re-export for backward compatibility
62
+ export { bgCyan, black, red, green, yellow, gray, cyan, bold, dim, blue, gradient };
63
+
64
+ export const colors = {
65
+ bgCyan, black, red, green, yellow, gray, cyan, bold, dim, blue
66
+ };
67
+