@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 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 a file
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.uploadFile("./report.pdf");
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> Upload a local 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 '{"file_id":"fil_..."}'
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> [--as <name>]');
204
- console.error('Uploads a local file and returns its file_id.');
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
- console.log(JSON.stringify(info, null, 2));
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
- for (const [category, { description, tools }] of Object.entries(categories)) {
222
- console.log(description ? `\n## ${category} — ${description}` : `\n## ${category}`);
223
- console.log(` ${tools.join(", ")}`);
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> [--as <name>]
267
+ // lotics upload <file|dir...> [--as <name>]
230
268
  if (command === "upload") {
231
- const upload = await client.uploadFile(subcommand, {
232
- filename: flags.as,
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[0].error}`);
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
- console.log(JSON.stringify(upload.files[0], null, 2));
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
- const result = await client.execute(toolName, args, { format, timeoutMs });
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 (format === "text" && result.model_output) {
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;
@@ -45,7 +45,7 @@ export declare class LoticsClient {
45
45
  path: string;
46
46
  filename: string;
47
47
  }>;
48
- uploadFile(filePath: string, options?: {
49
- filename?: string;
48
+ uploadFiles(filePaths: string[], options?: {
49
+ filenames?: string[];
50
50
  }): Promise<FileUploadResult>;
51
51
  }
@@ -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 uploadFile(filePath, options) {
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
- formData.append("file", blob, filename);
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/cli",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Lotics SDK and CLI for AI agents",
5
5
  "type": "module",
6
6
  "bin": {