langtrain 0.1.18 → 0.1.20
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/.agent/workflows/gsd.md +27 -0
- package/.agent/workflows/ralph.md +28 -0
- package/README.md +8 -4
- package/assets/cli-demo.png +0 -0
- package/dist/chunk-D3O435OM.mjs +30 -0
- package/dist/chunk-D3O435OM.mjs.map +1 -0
- package/dist/chunk-Z7FYIYKL.js +30 -0
- package/dist/chunk-Z7FYIYKL.js.map +1 -0
- package/dist/cli.js +8 -4
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +8 -4
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +28 -1
- package/dist/index.d.ts +28 -1
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +3 -2
- package/src/cli/auth.ts +1 -0
- package/src/cli/handlers/agent.ts +22 -3
- package/src/cli/handlers/data.ts +207 -0
- package/src/cli/handlers/deploy.ts +62 -0
- package/src/cli/handlers/dev.ts +42 -0
- package/src/cli/handlers/doctor.ts +54 -0
- package/src/cli/handlers/env.ts +89 -0
- package/src/cli/handlers/guardrails.ts +100 -0
- package/src/cli/handlers/init.ts +104 -0
- package/src/cli/handlers/logs.ts +68 -0
- package/src/cli/handlers/tune.ts +103 -1
- package/src/cli/index.ts +138 -17
- package/src/cli/menu.ts +19 -2
- package/src/cli/ui.ts +32 -12
- package/src/index.ts +4 -1
- package/src/lib/agent.ts +7 -0
- package/src/lib/guardrails.ts +72 -0
- package/src/lib/secrets.ts +39 -0
- package/dist/chunk-PAHGEWDE.js +0 -30
- package/dist/chunk-PAHGEWDE.js.map +0 -1
- package/dist/chunk-Q46V6ODQ.mjs +0 -30
- package/dist/chunk-Q46V6ODQ.mjs.map +0 -1
|
@@ -146,12 +146,31 @@ export async function handleAgentList(client: AgentClient) {
|
|
|
146
146
|
await handleAgentRun(client, agentId as string, agents.find(a => a.id === agentId)?.name || 'Agent');
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
async function handleAgentRun(client: AgentClient, agentId: string, agentName: string) {
|
|
149
|
+
export async function handleAgentRun(client: AgentClient, agentId: string, agentName: string, initialMessage?: string) {
|
|
150
150
|
intro(bgCyan(black(` Chatting with ${agentName} `)));
|
|
151
151
|
console.log(gray('Type "exit" to quit conversation.'));
|
|
152
152
|
|
|
153
153
|
let conversationId: string | undefined = undefined;
|
|
154
154
|
|
|
155
|
+
// Send initial message if provided
|
|
156
|
+
if (initialMessage) {
|
|
157
|
+
const s = spinner();
|
|
158
|
+
s.start('Agent is analyzing...');
|
|
159
|
+
try {
|
|
160
|
+
const result = await client.execute(agentId, { prompt: initialMessage }, [], conversationId);
|
|
161
|
+
s.stop();
|
|
162
|
+
if ((result.output as any)?.response) {
|
|
163
|
+
console.log(gradient.pastel(`Agent: ${(result.output as any).response}`));
|
|
164
|
+
} else {
|
|
165
|
+
console.log(gradient.pastel(`Agent: ${JSON.stringify(result.output)}`));
|
|
166
|
+
}
|
|
167
|
+
conversationId = result.conversation_id;
|
|
168
|
+
} catch (e: any) {
|
|
169
|
+
s.stop(red('Error running agent.'));
|
|
170
|
+
console.error(e);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
155
174
|
while (true) {
|
|
156
175
|
const input = await text({
|
|
157
176
|
message: 'You:',
|
|
@@ -168,8 +187,8 @@ async function handleAgentRun(client: AgentClient, agentId: string, agentName: s
|
|
|
168
187
|
const result = await client.execute(agentId, { prompt: input }, [], conversationId);
|
|
169
188
|
s.stop();
|
|
170
189
|
|
|
171
|
-
if (result.output
|
|
172
|
-
console.log(gradient.pastel(`Agent: ${result.output.response}`));
|
|
190
|
+
if ((result.output as any)?.response) {
|
|
191
|
+
console.log(gradient.pastel(`Agent: ${(result.output as any).response}`));
|
|
173
192
|
} else {
|
|
174
193
|
console.log(gradient.pastel(`Agent: ${JSON.stringify(result.output)}`));
|
|
175
194
|
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { text, select, confirm, isCancel, cancel, spinner, intro, red, green, yellow, gray } from '../ui';
|
|
2
|
+
import { getConfig } from '../config';
|
|
3
|
+
import { FileClient, AgentClient, GuardrailClient } from '../../index';
|
|
4
|
+
import { handleAgentRun } from './agent';
|
|
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
|
+
const file = await select({
|
|
77
|
+
message: 'Select file to analyze (or cancel to exit):',
|
|
78
|
+
options: files.map(f => ({ value: f.id, label: `${f.filename} (${formatBytes(f.bytes)})` }))
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (isCancel(file)) return;
|
|
82
|
+
|
|
83
|
+
await handleDataAnalyze(client, file as string);
|
|
84
|
+
|
|
85
|
+
} catch (e: any) {
|
|
86
|
+
s.stop(red(`Failed to list files: ${e.message}`));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function handleDataAnalyze(client: FileClient, fileId: string) {
|
|
91
|
+
const config = getConfig();
|
|
92
|
+
const s2 = spinner();
|
|
93
|
+
s2.start('Connecting to Data Analyst...');
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const agentClient = new AgentClient({ apiKey: config.apiKey || '', baseUrl: config.baseUrl });
|
|
97
|
+
const agents = await agentClient.list();
|
|
98
|
+
// Check name (name is optional in type but we know it exists for system agent)
|
|
99
|
+
let analyst = agents.find(a => a.name && a.name === "Langtrain Data Analyst");
|
|
100
|
+
|
|
101
|
+
if (!analyst) {
|
|
102
|
+
s2.stop(yellow('Data Analyst agent (System) not found. Please contact admin to provision it.'));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
s2.stop(green('Connected to Data Analyst.'));
|
|
107
|
+
|
|
108
|
+
console.log(gray(`\nAnalyzing dataset ${fileId}...\n`));
|
|
109
|
+
|
|
110
|
+
await handleAgentRun(agentClient, analyst.id, analyst.name, `Please analyze the dataset with ID: ${fileId}`);
|
|
111
|
+
|
|
112
|
+
} catch (e: any) {
|
|
113
|
+
s2.stop(red(`Failed to connect: ${e.message}`));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export async function handleDataRefine(client: FileClient, fileId?: string) {
|
|
118
|
+
const config = getConfig();
|
|
119
|
+
const gClient = new GuardrailClient({ apiKey: config.apiKey || '', baseUrl: config.baseUrl });
|
|
120
|
+
|
|
121
|
+
// 0. Select File if not provided
|
|
122
|
+
if (!fileId) {
|
|
123
|
+
const s = spinner();
|
|
124
|
+
s.start('Fetching files...');
|
|
125
|
+
try {
|
|
126
|
+
// Need workspace ID logic similar to List?
|
|
127
|
+
// client.list takes workspaceId.
|
|
128
|
+
const wId = config.workspace_id || '';
|
|
129
|
+
const files = await client.list(wId);
|
|
130
|
+
s.stop(`Found ${files.length} files`);
|
|
131
|
+
|
|
132
|
+
if (files.length === 0) {
|
|
133
|
+
console.log(yellow('No files found. Upload one first.'));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const selection = await select({
|
|
138
|
+
message: 'Select file to refine:',
|
|
139
|
+
options: files.map(f => ({ value: f.id, label: `${f.filename} (${formatBytes(f.bytes)})` }))
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
if (isCancel(selection)) return;
|
|
143
|
+
fileId = selection as string;
|
|
144
|
+
|
|
145
|
+
} catch (e: any) {
|
|
146
|
+
s.stop(red(`Failed to fetch files: ${e.message}`));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// 1. Select Guardrail
|
|
152
|
+
const s = spinner();
|
|
153
|
+
s.start('Fetching guardrails...');
|
|
154
|
+
let guardId = "";
|
|
155
|
+
try {
|
|
156
|
+
const guards = await gClient.list();
|
|
157
|
+
s.stop(`Found ${guards.length} guardrails`);
|
|
158
|
+
|
|
159
|
+
if (guards.length === 0) {
|
|
160
|
+
console.log(yellow('No guardrails found. Please create one first using "lt guardrails create".'));
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const selection = await select({
|
|
165
|
+
message: 'Select a Guardrail to apply:',
|
|
166
|
+
options: guards.map((g: any) => ({
|
|
167
|
+
value: g.id,
|
|
168
|
+
label: g.name,
|
|
169
|
+
hint: g.description
|
|
170
|
+
}))
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
if (isCancel(selection)) return;
|
|
174
|
+
guardId = selection as string;
|
|
175
|
+
|
|
176
|
+
} catch (e: any) {
|
|
177
|
+
s.stop(red(`Failed to fetch guardrails: ${e.message}`));
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// 2. Apply
|
|
182
|
+
const s2 = spinner();
|
|
183
|
+
s2.start('Applying guardrail (filtering dataset)...');
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
const result = await gClient.apply(fileId, guardId);
|
|
187
|
+
s2.stop(green('Dataset refined successfully!'));
|
|
188
|
+
|
|
189
|
+
console.log(gray('Stats:'));
|
|
190
|
+
console.log(`Original Rows: ${result.original_rows}`);
|
|
191
|
+
console.log(`Filtered Rows: ${result.filtered_rows}`);
|
|
192
|
+
console.log(red(`Removed: ${result.removed_rows} rows`));
|
|
193
|
+
console.log(green(`New Dataset ID: ${result.new_dataset_id}`));
|
|
194
|
+
|
|
195
|
+
} catch (e: any) {
|
|
196
|
+
s2.stop(red(`Failed to refine dataset: ${e.message}`));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function formatBytes(bytes: number, decimals = 2) {
|
|
201
|
+
if (!+bytes) return '0 Bytes';
|
|
202
|
+
const k = 1024;
|
|
203
|
+
const dm = decimals < 0 ? 0 : decimals;
|
|
204
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
|
205
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
206
|
+
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
|
|
207
|
+
}
|
|
@@ -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,89 @@
|
|
|
1
|
+
import { intro, outro, spinner, green, red, yellow, showInfo, gray, showSuccess } from '../ui';
|
|
2
|
+
import { SecretClient } from '../../index';
|
|
3
|
+
import { text, confirm, select, isCancel, cancel } from '../ui';
|
|
4
|
+
import { getConfig } from '../config';
|
|
5
|
+
|
|
6
|
+
export async function handleEnvList(client: SecretClient) {
|
|
7
|
+
const s = spinner();
|
|
8
|
+
s.start('Fetching secrets...');
|
|
9
|
+
const config = getConfig();
|
|
10
|
+
try {
|
|
11
|
+
const secrets = await client.list(config.workspace_id);
|
|
12
|
+
s.stop(`Found ${secrets.length} secrets`);
|
|
13
|
+
|
|
14
|
+
if (secrets.length === 0) {
|
|
15
|
+
console.log(gray('No secrets found. Use "lt env set" to add one.'));
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
console.log(gray('------------------------------------------------'));
|
|
20
|
+
secrets.forEach(sec => {
|
|
21
|
+
console.log(`${sec.key.padEnd(30)} ${gray('******')}`);
|
|
22
|
+
});
|
|
23
|
+
console.log(gray('------------------------------------------------'));
|
|
24
|
+
|
|
25
|
+
} catch (e: any) {
|
|
26
|
+
s.stop(red(`Failed to list secrets: ${e.message}`));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function handleEnvSet(client: SecretClient, keyVal?: string) {
|
|
31
|
+
let key = '';
|
|
32
|
+
let value = '';
|
|
33
|
+
|
|
34
|
+
if (keyVal && keyVal.includes('=')) {
|
|
35
|
+
const parts = keyVal.split('=');
|
|
36
|
+
key = parts[0];
|
|
37
|
+
value = parts.slice(1).join('=');
|
|
38
|
+
} else {
|
|
39
|
+
key = await text({ message: 'Secret Key:', placeholder: 'OPENAI_API_KEY' }) as string;
|
|
40
|
+
if (isCancel(key)) return;
|
|
41
|
+
|
|
42
|
+
value = await text({ message: 'Secret Value:', placeholder: 'sk-...' }) as string;
|
|
43
|
+
if (isCancel(value)) return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const s = spinner();
|
|
47
|
+
s.start(`Setting ${key}...`);
|
|
48
|
+
const config = getConfig();
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
await client.set(key, value, config.workspace_id);
|
|
52
|
+
s.stop(green(`Secret ${key} set successfully.`));
|
|
53
|
+
} catch (e: any) {
|
|
54
|
+
s.stop(red(`Failed to set secret: ${e.message}`));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Interactive menu for env
|
|
59
|
+
export async function handleEnvMenu(client: SecretClient) {
|
|
60
|
+
const action = await select({
|
|
61
|
+
message: 'Manage Secrets',
|
|
62
|
+
options: [
|
|
63
|
+
{ value: 'list', label: 'List Secrets' },
|
|
64
|
+
{ value: 'set', label: 'Set Secret' },
|
|
65
|
+
{ value: 'remove', label: 'Remove Secret' },
|
|
66
|
+
{ value: 'back', label: 'Back' }
|
|
67
|
+
]
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
if (isCancel(action) || action === 'back') return;
|
|
71
|
+
|
|
72
|
+
if (action === 'list') await handleEnvList(client);
|
|
73
|
+
if (action === 'set') await handleEnvSet(client);
|
|
74
|
+
if (action === 'remove') {
|
|
75
|
+
const key = await text({ message: 'Key to remove:' });
|
|
76
|
+
if (!isCancel(key)) {
|
|
77
|
+
const s = spinner();
|
|
78
|
+
s.start('Removing...');
|
|
79
|
+
try {
|
|
80
|
+
// need workspace_id
|
|
81
|
+
const config = getConfig();
|
|
82
|
+
await client.delete(key as string, config.workspace_id);
|
|
83
|
+
s.stop(green('Removed.'));
|
|
84
|
+
} catch (e: any) {
|
|
85
|
+
s.stop(red(`Failed: ${e.message}`));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { text, select, confirm, isCancel, cancel, spinner, intro, red, green, yellow, gray } from '../ui';
|
|
2
|
+
import { getConfig } from '../config';
|
|
3
|
+
import { GuardrailClient } from '../../index';
|
|
4
|
+
|
|
5
|
+
export async function handleGuardrailList(client: any) { // using any to match index signature, but we instantiate specific client inside
|
|
6
|
+
const config = getConfig();
|
|
7
|
+
const gClient = new GuardrailClient({ apiKey: config.apiKey || '', baseUrl: config.baseUrl });
|
|
8
|
+
|
|
9
|
+
const s = spinner();
|
|
10
|
+
s.start('Fetching guardrails...');
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const guards = await gClient.list();
|
|
14
|
+
s.stop(`Found ${guards.length} guardrails`);
|
|
15
|
+
|
|
16
|
+
if (guards.length === 0) {
|
|
17
|
+
console.log(yellow('No guardrails found. Create one with "lt guardrails create".'));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
guards.forEach((g: any) => {
|
|
22
|
+
console.log(green(`• ${g.name}`) + gray(` (ID: ${g.id})`));
|
|
23
|
+
if (g.description) console.log(gray(` ${g.description}`));
|
|
24
|
+
console.log(gray(` Config: PII=${g.config.pii_enabled}, MinLen=${g.config.min_length}`));
|
|
25
|
+
console.log('');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
} catch (e: any) {
|
|
29
|
+
s.stop(red(`Failed to list guardrails: ${e.message}`));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function handleGuardrailCreate(client: any) {
|
|
34
|
+
const config = getConfig();
|
|
35
|
+
const gClient = new GuardrailClient({ apiKey: config.apiKey || '', baseUrl: config.baseUrl });
|
|
36
|
+
|
|
37
|
+
intro('Create a new Data Guardrail');
|
|
38
|
+
|
|
39
|
+
const name = await text({
|
|
40
|
+
message: 'Guardrail Name:',
|
|
41
|
+
placeholder: 'e.g. Strict Safety Policy',
|
|
42
|
+
validate(value) {
|
|
43
|
+
if (!value) return 'Name is required';
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
if (isCancel(name)) return;
|
|
47
|
+
|
|
48
|
+
const description = await text({
|
|
49
|
+
message: 'Description (optional):',
|
|
50
|
+
placeholder: 'Filters PII and short text',
|
|
51
|
+
});
|
|
52
|
+
if (isCancel(description)) return;
|
|
53
|
+
|
|
54
|
+
// Interactive Config
|
|
55
|
+
const minLen = await text({
|
|
56
|
+
message: 'Minimum Text Length (0 for no limit):',
|
|
57
|
+
initialValue: '0',
|
|
58
|
+
validate(value) {
|
|
59
|
+
if (isNaN(Number(value))) return 'Must be a number';
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
if (isCancel(minLen)) return;
|
|
63
|
+
|
|
64
|
+
const enablePii = await confirm({
|
|
65
|
+
message: 'Enable PII Filtering (Email/Phone)?',
|
|
66
|
+
initialValue: false
|
|
67
|
+
});
|
|
68
|
+
if (isCancel(enablePii)) return;
|
|
69
|
+
|
|
70
|
+
const patterns = await text({
|
|
71
|
+
message: 'Regex Patterns to Block (comma separated, optional):',
|
|
72
|
+
placeholder: 'e.g. bad_word, another_one',
|
|
73
|
+
});
|
|
74
|
+
if (isCancel(patterns)) return;
|
|
75
|
+
|
|
76
|
+
const s = spinner();
|
|
77
|
+
s.start('Creating guardrail...');
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const regexList = (patterns as string).split(',').map(p => p.trim()).filter(p => p.length > 0);
|
|
81
|
+
|
|
82
|
+
const payload = {
|
|
83
|
+
name,
|
|
84
|
+
description,
|
|
85
|
+
config: {
|
|
86
|
+
min_length: Number(minLen),
|
|
87
|
+
pii_enabled: enablePii,
|
|
88
|
+
regex_patterns: regexList,
|
|
89
|
+
profanity_enabled: false
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const result = await gClient.create(payload);
|
|
94
|
+
s.stop(green(`Guardrail "${result.name}" created successfully!`));
|
|
95
|
+
console.log(gray(`ID: ${result.id}`));
|
|
96
|
+
|
|
97
|
+
} catch (e: any) {
|
|
98
|
+
s.stop(red(`Failed to create guardrail: ${e.message}`));
|
|
99
|
+
}
|
|
100
|
+
}
|