@lotics/cli 0.2.0 → 0.3.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/README.md +3 -3
- package/dist/src/cli.js +83 -21
- package/dist/src/client.d.ts +2 -2
- package/dist/src/client.js +9 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -40,8 +40,8 @@ lotics run query_records '{"table_id":"tbl_...","field_keys":["name"]}'
|
|
|
40
40
|
# Pipe args via stdin
|
|
41
41
|
echo '{"table_id":"tbl_..."}' | lotics run query_records
|
|
42
42
|
|
|
43
|
-
# Upload
|
|
44
|
-
lotics upload ./report.pdf
|
|
43
|
+
# Upload files (multiple files and directories supported)
|
|
44
|
+
lotics upload ./report.pdf ./data.csv ./documents/
|
|
45
45
|
|
|
46
46
|
# Generate a file, then download it
|
|
47
47
|
lotics run generate_excel_from_template '{"..."}'
|
|
@@ -59,7 +59,7 @@ import { LoticsClient } from "@lotics/cli";
|
|
|
59
59
|
const client = new LoticsClient({ apiKey: "ltk_..." });
|
|
60
60
|
|
|
61
61
|
const { result } = await client.execute("query_tables", {});
|
|
62
|
-
const upload = await client.
|
|
62
|
+
const upload = await client.uploadFiles(["./report.pdf", "./data.csv"]);
|
|
63
63
|
await client.downloadFile(url, "./output.xlsx");
|
|
64
64
|
const { tools, categories } = await client.listTools();
|
|
65
65
|
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";
|
|
@@ -28,7 +30,7 @@ COMMANDS
|
|
|
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) => {
|
|
@@ -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;
|
|
@@ -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",
|