@lotics/cli 0.2.0 → 0.3.1
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/README.md +5 -4
- package/dist/src/cli.js +88 -26
- package/dist/src/client.d.ts +2 -2
- package/dist/src/client.js +9 -7
- package/dist/src/config.js +11 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,7 +27,8 @@ The CLI checks for updates once per day and prompts when a new version is availa
|
|
|
27
27
|
|
|
28
28
|
```bash
|
|
29
29
|
# 1. Authenticate
|
|
30
|
-
lotics auth
|
|
30
|
+
lotics auth # interactive prompt
|
|
31
|
+
lotics auth ltk_... # non-interactive (for agents/CI)
|
|
31
32
|
|
|
32
33
|
# 2. Discover tools
|
|
33
34
|
lotics tools # list tools by category with descriptions
|
|
@@ -40,8 +41,8 @@ lotics run query_records '{"table_id":"tbl_...","field_keys":["name"]}'
|
|
|
40
41
|
# Pipe args via stdin
|
|
41
42
|
echo '{"table_id":"tbl_..."}' | lotics run query_records
|
|
42
43
|
|
|
43
|
-
# Upload
|
|
44
|
-
lotics upload ./report.pdf
|
|
44
|
+
# Upload files (multiple files and directories supported)
|
|
45
|
+
lotics upload ./report.pdf ./data.csv ./documents/
|
|
45
46
|
|
|
46
47
|
# Generate a file, then download it
|
|
47
48
|
lotics run generate_excel_from_template '{"..."}'
|
|
@@ -59,7 +60,7 @@ import { LoticsClient } from "@lotics/cli";
|
|
|
59
60
|
const client = new LoticsClient({ apiKey: "ltk_..." });
|
|
60
61
|
|
|
61
62
|
const { result } = await client.execute("query_tables", {});
|
|
62
|
-
const upload = await client.
|
|
63
|
+
const upload = await client.uploadFiles(["./report.pdf", "./data.csv"]);
|
|
63
64
|
await client.downloadFile(url, "./output.xlsx");
|
|
64
65
|
const { tools, categories } = await client.listTools();
|
|
65
66
|
const info = await client.getTool("query_records");
|
package/dist/src/cli.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
2
4
|
import readline from "node:readline";
|
|
3
5
|
import { LoticsClient } from "./client.js";
|
|
4
6
|
import { resolveAuth, loadConfig, saveConfig, deleteConfig, getConfigPath, checkForUpdate } from "./config.js";
|
|
@@ -13,7 +15,7 @@ Lotics is an AI-powered operations platform. Through this CLI you can:
|
|
|
13
15
|
- Create and manage apps, knowledge docs, and files
|
|
14
16
|
|
|
15
17
|
USAGE
|
|
16
|
-
1. lotics auth
|
|
18
|
+
1. lotics auth [api_key] Authenticate (saves key to ~/.lotics/config.json)
|
|
17
19
|
2. lotics tools List available tools by category
|
|
18
20
|
3. lotics tools <name> Show tool description and full input schema
|
|
19
21
|
4. lotics run <tool> '<json>' Execute a tool with JSON arguments
|
|
@@ -23,12 +25,12 @@ USAGE
|
|
|
23
25
|
Query tools return IDs used as arguments to other tools.
|
|
24
26
|
|
|
25
27
|
COMMANDS
|
|
26
|
-
lotics auth
|
|
28
|
+
lotics auth [api_key] Save API key (interactive prompt if omitted)
|
|
27
29
|
lotics logout Remove saved credentials
|
|
28
30
|
lotics tools List all available tools
|
|
29
31
|
lotics tools <name> Show tool description and input schema
|
|
30
32
|
lotics run <tool> '<json>' Execute a tool
|
|
31
|
-
lotics upload <file
|
|
33
|
+
lotics upload <file|dir...> Upload files (directories expand to their immediate files)
|
|
32
34
|
lotics download <file_id> Download a file by ID
|
|
33
35
|
|
|
34
36
|
FLAGS
|
|
@@ -56,8 +58,8 @@ FILES
|
|
|
56
58
|
|
|
57
59
|
Upload files before referencing them in tool args:
|
|
58
60
|
|
|
59
|
-
lotics upload ./data.csv
|
|
60
|
-
lotics run create_records '{"
|
|
61
|
+
lotics upload ./data.csv ./report.pdf ./documents/
|
|
62
|
+
lotics run create_records '{"table_id":"tbl_...","records":[{"fld_file":["fil_..."]}]}'
|
|
61
63
|
|
|
62
64
|
STDIN
|
|
63
65
|
Pipe JSON arguments via stdin instead of inline:
|
|
@@ -77,6 +79,7 @@ function parseArgs(argv) {
|
|
|
77
79
|
let command;
|
|
78
80
|
let subcommand;
|
|
79
81
|
let toolArgs;
|
|
82
|
+
const restArgs = [];
|
|
80
83
|
let i = 0;
|
|
81
84
|
while (i < argv.length) {
|
|
82
85
|
const arg = argv[i];
|
|
@@ -115,11 +118,14 @@ function parseArgs(argv) {
|
|
|
115
118
|
else if (!toolArgs) {
|
|
116
119
|
toolArgs = arg;
|
|
117
120
|
}
|
|
121
|
+
else {
|
|
122
|
+
restArgs.push(arg);
|
|
123
|
+
}
|
|
118
124
|
break;
|
|
119
125
|
}
|
|
120
126
|
i++;
|
|
121
127
|
}
|
|
122
|
-
return { command, subcommand, toolArgs, flags };
|
|
128
|
+
return { command, subcommand, toolArgs, restArgs, flags };
|
|
123
129
|
}
|
|
124
130
|
function readStdin() {
|
|
125
131
|
return new Promise((resolve, reject) => {
|
|
@@ -141,8 +147,8 @@ function prompt(question) {
|
|
|
141
147
|
});
|
|
142
148
|
});
|
|
143
149
|
}
|
|
144
|
-
async function handleAuth() {
|
|
145
|
-
const apiKey = await prompt("Enter your API key: ");
|
|
150
|
+
async function handleAuth(providedKey) {
|
|
151
|
+
const apiKey = providedKey ?? await prompt("Enter your API key: ");
|
|
146
152
|
if (!apiKey) {
|
|
147
153
|
console.error("No API key provided.");
|
|
148
154
|
process.exit(1);
|
|
@@ -168,8 +174,30 @@ function requireClient(flags) {
|
|
|
168
174
|
}
|
|
169
175
|
return new LoticsClient({ apiKey: auth.apiKey });
|
|
170
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* Resolve upload paths: files pass through, directories expand to their immediate files.
|
|
179
|
+
*/
|
|
180
|
+
function resolveUploadPaths(rawPaths) {
|
|
181
|
+
const result = [];
|
|
182
|
+
for (const p of rawPaths) {
|
|
183
|
+
const resolved = path.resolve(p);
|
|
184
|
+
const stat = fs.statSync(resolved);
|
|
185
|
+
if (stat.isDirectory()) {
|
|
186
|
+
const entries = fs.readdirSync(resolved, { withFileTypes: true });
|
|
187
|
+
for (const entry of entries) {
|
|
188
|
+
if (entry.isFile()) {
|
|
189
|
+
result.push(path.join(resolved, entry.name));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
result.push(resolved);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return result;
|
|
198
|
+
}
|
|
171
199
|
async function main() {
|
|
172
|
-
const { command, subcommand, toolArgs, flags } = parseArgs(process.argv.slice(2));
|
|
200
|
+
const { command, subcommand, toolArgs, restArgs, flags } = parseArgs(process.argv.slice(2));
|
|
173
201
|
if (flags.help || (!command && !flags.version)) {
|
|
174
202
|
printHelp();
|
|
175
203
|
return;
|
|
@@ -180,7 +208,7 @@ async function main() {
|
|
|
180
208
|
}
|
|
181
209
|
// --- Commands that don't require auth ---
|
|
182
210
|
if (command === "auth") {
|
|
183
|
-
await handleAuth();
|
|
211
|
+
await handleAuth(subcommand ?? flags.apiKey);
|
|
184
212
|
return;
|
|
185
213
|
}
|
|
186
214
|
if (command === "logout") {
|
|
@@ -200,8 +228,8 @@ async function main() {
|
|
|
200
228
|
process.exit(1);
|
|
201
229
|
}
|
|
202
230
|
if (command === "upload" && !subcommand) {
|
|
203
|
-
console.error('Usage: lotics upload <file
|
|
204
|
-
console.error('Uploads
|
|
231
|
+
console.error('Usage: lotics upload <file|dir...> [--as <name>]');
|
|
232
|
+
console.error('Uploads files and returns their file_ids. Directories expand to their immediate files.');
|
|
205
233
|
process.exit(1);
|
|
206
234
|
}
|
|
207
235
|
if (command === "download" && !subcommand) {
|
|
@@ -214,28 +242,58 @@ async function main() {
|
|
|
214
242
|
if (command === "tools") {
|
|
215
243
|
if (subcommand) {
|
|
216
244
|
const info = await client.getTool(subcommand);
|
|
217
|
-
|
|
245
|
+
if (flags.json) {
|
|
246
|
+
console.log(JSON.stringify(info, null, 2));
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
console.log(`${info.name}\n\n${info.description}\n\nInput schema:\n${JSON.stringify(info.input_schema, null, 2)}`);
|
|
250
|
+
}
|
|
218
251
|
}
|
|
219
252
|
else {
|
|
220
253
|
const { categories } = await client.listTools();
|
|
221
|
-
|
|
222
|
-
console.log(
|
|
223
|
-
|
|
254
|
+
if (flags.json) {
|
|
255
|
+
console.log(JSON.stringify(categories, null, 2));
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
for (const [category, { description, tools }] of Object.entries(categories)) {
|
|
259
|
+
console.log(description ? `\n## ${category} — ${description}` : `\n## ${category}`);
|
|
260
|
+
console.log(` ${tools.join(", ")}`);
|
|
261
|
+
}
|
|
262
|
+
console.log("");
|
|
224
263
|
}
|
|
225
|
-
console.log("");
|
|
226
264
|
}
|
|
227
265
|
return;
|
|
228
266
|
}
|
|
229
|
-
// lotics upload <file
|
|
267
|
+
// lotics upload <file|dir...> [--as <name>]
|
|
230
268
|
if (command === "upload") {
|
|
231
|
-
const
|
|
232
|
-
|
|
269
|
+
const rawPaths = [subcommand, ...(toolArgs ? [toolArgs] : []), ...restArgs];
|
|
270
|
+
const filePaths = resolveUploadPaths(rawPaths);
|
|
271
|
+
if (filePaths.length === 0) {
|
|
272
|
+
console.error("No files found in the specified paths.");
|
|
273
|
+
process.exit(1);
|
|
274
|
+
}
|
|
275
|
+
if (flags.as && filePaths.length > 1) {
|
|
276
|
+
console.error("Cannot use --as with multiple files.");
|
|
277
|
+
process.exit(1);
|
|
278
|
+
}
|
|
279
|
+
const upload = await client.uploadFiles(filePaths, {
|
|
280
|
+
filenames: flags.as ? [flags.as] : undefined,
|
|
233
281
|
});
|
|
234
|
-
if (upload.errors.length > 0) {
|
|
235
|
-
console.error(`Upload failed: ${upload.errors
|
|
282
|
+
if (upload.files.length === 0 && upload.errors.length > 0) {
|
|
283
|
+
console.error(`Upload failed: ${upload.errors.map((e) => `${e.filename}: ${e.error}`).join(", ")}`);
|
|
236
284
|
process.exit(1);
|
|
237
285
|
}
|
|
238
|
-
|
|
286
|
+
if (flags.json) {
|
|
287
|
+
console.log(JSON.stringify({ files: upload.files, errors: upload.errors }, null, 2));
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
for (const file of upload.files) {
|
|
291
|
+
console.log(`${file.id} ${file.filename} ${file.mime_type}`);
|
|
292
|
+
}
|
|
293
|
+
for (const error of upload.errors) {
|
|
294
|
+
console.error(`FAILED ${error.filename} ${error.error}`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
239
297
|
return;
|
|
240
298
|
}
|
|
241
299
|
// lotics download <file_id> [-o <path>]
|
|
@@ -261,18 +319,22 @@ async function main() {
|
|
|
261
319
|
process.exit(1);
|
|
262
320
|
}
|
|
263
321
|
}
|
|
264
|
-
const format = flags.json ? "json" : "text";
|
|
265
322
|
const timeoutMs = flags.timeout ?? 60000;
|
|
266
|
-
|
|
323
|
+
// Always request text format so model_output is available; --json only affects CLI output
|
|
324
|
+
const result = await client.execute(toolName, args, { format: "text", timeoutMs });
|
|
267
325
|
if (result.error) {
|
|
268
326
|
console.error(result.error);
|
|
269
327
|
console.error(`\nHint: run "lotics tools ${toolName}" to see the expected input schema.`);
|
|
270
328
|
process.exit(1);
|
|
271
329
|
}
|
|
272
|
-
if (
|
|
330
|
+
if (flags.json) {
|
|
331
|
+
console.log(JSON.stringify(result.result, null, 2));
|
|
332
|
+
}
|
|
333
|
+
else if (result.model_output) {
|
|
273
334
|
console.log(result.model_output);
|
|
274
335
|
}
|
|
275
336
|
else {
|
|
337
|
+
// Fallback: compact JSON when no text output is available
|
|
276
338
|
console.log(JSON.stringify(result.result, null, 2));
|
|
277
339
|
}
|
|
278
340
|
return;
|
package/dist/src/client.d.ts
CHANGED
package/dist/src/client.js
CHANGED
|
@@ -116,14 +116,16 @@ export class LoticsClient {
|
|
|
116
116
|
await fs.promises.writeFile(absolutePath, buffer);
|
|
117
117
|
return { path: absolutePath, filename };
|
|
118
118
|
}
|
|
119
|
-
async
|
|
120
|
-
const absolutePath = path.resolve(filePath);
|
|
121
|
-
const buffer = await fs.promises.readFile(absolutePath);
|
|
122
|
-
const filename = options?.filename ?? path.basename(absolutePath);
|
|
123
|
-
const mimeType = getMimeType(filename);
|
|
124
|
-
const blob = new Blob([buffer], { type: mimeType });
|
|
119
|
+
async uploadFiles(filePaths, options) {
|
|
125
120
|
const formData = new FormData();
|
|
126
|
-
|
|
121
|
+
for (let i = 0; i < filePaths.length; i++) {
|
|
122
|
+
const absolutePath = path.resolve(filePaths[i]);
|
|
123
|
+
const buffer = await fs.promises.readFile(absolutePath);
|
|
124
|
+
const filename = options?.filenames?.[i] ?? path.basename(absolutePath);
|
|
125
|
+
const mimeType = getMimeType(filename);
|
|
126
|
+
const blob = new Blob([buffer], { type: mimeType });
|
|
127
|
+
formData.append("file", blob, filename);
|
|
128
|
+
}
|
|
127
129
|
const url = `${this.baseUrl}/v1/files`;
|
|
128
130
|
const response = await fetch(url, {
|
|
129
131
|
method: "POST",
|
package/dist/src/config.js
CHANGED
|
@@ -70,7 +70,18 @@ async function fetchLatestVersion() {
|
|
|
70
70
|
clearTimeout(timeout);
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
|
+
function isNewerVersion(latest, current) {
|
|
74
|
+
const [lMaj, lMin, lPat] = latest.split(".").map(Number);
|
|
75
|
+
const [cMaj, cMin, cPat] = current.split(".").map(Number);
|
|
76
|
+
if (lMaj !== cMaj)
|
|
77
|
+
return lMaj > cMaj;
|
|
78
|
+
if (lMin !== cMin)
|
|
79
|
+
return lMin > cMin;
|
|
80
|
+
return lPat > cPat;
|
|
81
|
+
}
|
|
73
82
|
function printUpdateWarning(current, latest) {
|
|
83
|
+
if (!isNewerVersion(latest, current))
|
|
84
|
+
return;
|
|
74
85
|
console.error(`\nUpdate available: ${current} → ${latest}`);
|
|
75
86
|
console.error(`Run: npm i -g @lotics/cli\n`);
|
|
76
87
|
}
|