@lotics/cli 0.5.0 → 0.7.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 +21 -4
- package/dist/src/cli.js +164 -27
- package/dist/src/client.d.ts +21 -0
- package/dist/src/client.js +26 -5
- package/dist/src/config.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -25,24 +25,41 @@ The CLI checks for updates once per day and prompts when a new version is availa
|
|
|
25
25
|
|
|
26
26
|
## Authentication
|
|
27
27
|
|
|
28
|
-
**`lotics signup`** — Creates a new Lotics account, organization, workspace, and API key in one step.
|
|
28
|
+
**`lotics signup`** — Creates a new Lotics account, organization, workspace, and API key in one step. Sends a magic link email so you can access the web app.
|
|
29
29
|
|
|
30
30
|
```bash
|
|
31
31
|
lotics signup # interactive prompts
|
|
32
32
|
lotics signup --email a@b.com --name "Agent" # non-interactive
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
**`lotics
|
|
35
|
+
**`lotics login`** — Send a magic link email to access the web app (requires prior signup or setup).
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
|
-
lotics
|
|
39
|
-
|
|
38
|
+
lotics login
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**`lotics setup`** — Saves an existing API key directly (e.g. one created in the Lotics web app).
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
lotics setup # interactive prompt
|
|
45
|
+
lotics setup ltk_... # non-interactive
|
|
40
46
|
```
|
|
41
47
|
|
|
42
48
|
API key is saved to `~/.lotics/config.json`. Run `lotics logout` to remove saved credentials.
|
|
43
49
|
|
|
44
50
|
Auth priority: `--api-key` flag > `LOTICS_API_KEY` env > `~/.lotics/config.json`.
|
|
45
51
|
|
|
52
|
+
## Workspaces
|
|
53
|
+
|
|
54
|
+
If your organization has multiple workspaces, select one before running tools:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
lotics workspace # list workspaces (marks current)
|
|
58
|
+
lotics workspace select wks_... # switch to a workspace
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Single-workspace organizations auto-select on first use.
|
|
62
|
+
|
|
46
63
|
## CLI
|
|
47
64
|
|
|
48
65
|
```bash
|
package/dist/src/cli.js
CHANGED
|
@@ -16,29 +16,34 @@ Lotics is an AI-powered operations platform. Through this CLI you can:
|
|
|
16
16
|
|
|
17
17
|
AUTHENTICATION
|
|
18
18
|
lotics signup Create account, org, workspace, and API key
|
|
19
|
-
lotics
|
|
19
|
+
lotics login Send a magic link email to access the web app
|
|
20
|
+
lotics setup [api_key] Save an existing API key (e.g. from the web app)
|
|
20
21
|
lotics whoami Show the email of the current account
|
|
21
22
|
lotics logout Remove saved credentials
|
|
22
23
|
|
|
23
24
|
Signup flags:
|
|
24
|
-
--email <email>
|
|
25
|
-
--name <name>
|
|
25
|
+
--email <email> Email (required; prompted if omitted)
|
|
26
|
+
--name <name> Display name (defaults to email prefix)
|
|
27
|
+
--timezone <timezone> Workspace timezone (defaults to UTC, e.g. Asia/Ho_Chi_Minh)
|
|
26
28
|
|
|
27
|
-
Signup
|
|
28
|
-
Use lotics
|
|
29
|
-
|
|
29
|
+
Signup sends a magic link email so you can access the Lotics web app.
|
|
30
|
+
Use lotics login to request a new magic link at any time.
|
|
31
|
+
Setup priority: --api-key flag > LOTICS_API_KEY env > saved config.
|
|
30
32
|
|
|
31
33
|
USAGE
|
|
32
|
-
1. lotics signup
|
|
33
|
-
2. lotics
|
|
34
|
-
3. lotics tools
|
|
35
|
-
4. lotics
|
|
34
|
+
1. lotics signup (or lotics setup) Create account or set up API key
|
|
35
|
+
2. lotics workspace Check current workspace (auto-selects if only one)
|
|
36
|
+
3. lotics tools List available tools by category
|
|
37
|
+
4. lotics tools <name> Show tool description and full input schema
|
|
38
|
+
5. lotics run <tool> '<json>' Execute a tool with JSON arguments
|
|
36
39
|
|
|
37
|
-
Always inspect the schema (step
|
|
40
|
+
Always inspect the schema (step 4) before calling a tool.
|
|
38
41
|
Tools are grouped by category (tables, records, views, etc.).
|
|
39
42
|
Query tools return IDs used as arguments to other tools.
|
|
40
43
|
|
|
41
44
|
COMMANDS
|
|
45
|
+
lotics workspace List workspaces (marks current)
|
|
46
|
+
lotics workspace select <id> Switch to a different workspace
|
|
42
47
|
lotics tools List all available tools
|
|
43
48
|
lotics tools <name> Show tool description and input schema
|
|
44
49
|
lotics run <tool> '<json>' Execute a tool
|
|
@@ -84,6 +89,7 @@ function parseArgs(argv) {
|
|
|
84
89
|
apiKey: undefined,
|
|
85
90
|
email: undefined,
|
|
86
91
|
name: undefined,
|
|
92
|
+
timezone: undefined,
|
|
87
93
|
version: false,
|
|
88
94
|
help: false,
|
|
89
95
|
};
|
|
@@ -117,6 +123,9 @@ function parseArgs(argv) {
|
|
|
117
123
|
case "--name":
|
|
118
124
|
flags.name = argv[++i];
|
|
119
125
|
break;
|
|
126
|
+
case "--timezone":
|
|
127
|
+
flags.timezone = argv[++i];
|
|
128
|
+
break;
|
|
120
129
|
case "--version":
|
|
121
130
|
case "-v":
|
|
122
131
|
flags.version = true;
|
|
@@ -183,10 +192,12 @@ async function handleSignup(flags) {
|
|
|
183
192
|
const body = { email };
|
|
184
193
|
if (name)
|
|
185
194
|
body.name = name;
|
|
195
|
+
if (flags.timezone)
|
|
196
|
+
body.timezone = flags.timezone;
|
|
186
197
|
const { ok, status, data } = await publicPost("/v1/cli/signup", body);
|
|
187
198
|
if (!ok) {
|
|
188
199
|
if (status === 409) {
|
|
189
|
-
console.error("An account with this email already exists. Use: lotics
|
|
200
|
+
console.error("An account with this email already exists. Use: lotics setup");
|
|
190
201
|
}
|
|
191
202
|
else if (status === 429) {
|
|
192
203
|
console.error("Too many signup attempts. Try again later.");
|
|
@@ -197,22 +208,28 @@ async function handleSignup(flags) {
|
|
|
197
208
|
process.exit(1);
|
|
198
209
|
}
|
|
199
210
|
const existing = loadConfig() ?? {};
|
|
200
|
-
saveConfig({
|
|
211
|
+
saveConfig({
|
|
212
|
+
...existing,
|
|
213
|
+
api_key: data.api_key,
|
|
214
|
+
email: data.email,
|
|
215
|
+
workspace_id: data.workspace_id,
|
|
216
|
+
});
|
|
201
217
|
console.error(`Account created. You can now use the CLI.`);
|
|
202
|
-
console.error(` Email:
|
|
203
|
-
console.error(
|
|
204
|
-
console.error(
|
|
205
|
-
console.error(`Run "lotics whoami" to check the current account email.`);
|
|
218
|
+
console.error(` Email: ${data.email}`);
|
|
219
|
+
console.error(`\nCheck your email for a magic link to access the Lotics web app.`);
|
|
220
|
+
console.error(`Run "lotics login" to request a new link at any time.`);
|
|
206
221
|
}
|
|
207
|
-
async function
|
|
222
|
+
async function handleSetup(providedKey) {
|
|
208
223
|
const apiKey = providedKey ?? await prompt("Enter your API key: ");
|
|
209
224
|
if (!apiKey) {
|
|
210
225
|
console.error("No API key provided.");
|
|
211
226
|
process.exit(1);
|
|
212
227
|
}
|
|
213
228
|
const client = new LoticsClient({ apiKey });
|
|
229
|
+
let email;
|
|
214
230
|
try {
|
|
215
|
-
await client.
|
|
231
|
+
const info = await client.whoami();
|
|
232
|
+
email = info.email;
|
|
216
233
|
}
|
|
217
234
|
catch (error) {
|
|
218
235
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -220,16 +237,69 @@ async function handleAuth(providedKey) {
|
|
|
220
237
|
process.exit(1);
|
|
221
238
|
}
|
|
222
239
|
const existing = loadConfig() ?? {};
|
|
223
|
-
|
|
240
|
+
const newConfig = { ...existing, api_key: apiKey, email };
|
|
241
|
+
// Auto-resolve workspace
|
|
242
|
+
try {
|
|
243
|
+
const workspaces = await client.listWorkspaces();
|
|
244
|
+
if (workspaces.length === 1) {
|
|
245
|
+
newConfig.workspace_id = workspaces[0].id;
|
|
246
|
+
}
|
|
247
|
+
else if (workspaces.length > 1) {
|
|
248
|
+
console.error(`\nMultiple workspaces found. Run "lotics workspace select <id>" to choose one:`);
|
|
249
|
+
printWorkspaceList(workspaces);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
254
|
+
console.error(`Warning: could not resolve workspace: ${msg}`);
|
|
255
|
+
}
|
|
256
|
+
saveConfig(newConfig);
|
|
224
257
|
console.error("Authenticated.");
|
|
225
258
|
}
|
|
226
259
|
function requireClient(flags) {
|
|
227
260
|
const auth = resolveAuth(flags);
|
|
228
261
|
if (!auth) {
|
|
229
|
-
console.error('Not authenticated. Run "lotics
|
|
262
|
+
console.error('Not authenticated. Run "lotics setup" or set LOTICS_API_KEY.');
|
|
263
|
+
process.exit(1);
|
|
264
|
+
}
|
|
265
|
+
const config = loadConfig();
|
|
266
|
+
return new LoticsClient({ apiKey: auth.apiKey, workspaceId: config?.workspace_id });
|
|
267
|
+
}
|
|
268
|
+
function printWorkspaceList(workspaces, currentId) {
|
|
269
|
+
for (const ws of workspaces) {
|
|
270
|
+
const marker = ws.id === currentId ? " (current)" : "";
|
|
271
|
+
console.error(` ${ws.id} ${ws.name} ${ws.timezone} ${ws.default_currency}${marker}`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
async function resolveWorkspace(client) {
|
|
275
|
+
const config = loadConfig();
|
|
276
|
+
if (config?.workspace_id)
|
|
277
|
+
return;
|
|
278
|
+
let workspaces;
|
|
279
|
+
try {
|
|
280
|
+
workspaces = await client.listWorkspaces();
|
|
281
|
+
}
|
|
282
|
+
catch (error) {
|
|
283
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
284
|
+
console.error(`Failed to list workspaces: ${message}`);
|
|
230
285
|
process.exit(1);
|
|
231
286
|
}
|
|
232
|
-
|
|
287
|
+
if (workspaces.length === 0) {
|
|
288
|
+
console.error("No workspaces found for this organization.");
|
|
289
|
+
process.exit(1);
|
|
290
|
+
}
|
|
291
|
+
if (workspaces.length === 1) {
|
|
292
|
+
const existing = config ?? {};
|
|
293
|
+
saveConfig({ ...existing, workspace_id: workspaces[0].id });
|
|
294
|
+
client.setWorkspaceId(workspaces[0].id);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
// Multiple workspaces, none selected
|
|
298
|
+
console.error("Multiple workspaces available. Select one with:\n");
|
|
299
|
+
console.error(' lotics workspace select <id>\n');
|
|
300
|
+
printWorkspaceList(workspaces);
|
|
301
|
+
console.error("");
|
|
302
|
+
process.exit(1);
|
|
233
303
|
}
|
|
234
304
|
/**
|
|
235
305
|
* Resolve upload paths: files pass through, directories expand to their immediate files.
|
|
@@ -268,17 +338,33 @@ async function main() {
|
|
|
268
338
|
await handleSignup(flags);
|
|
269
339
|
return;
|
|
270
340
|
}
|
|
271
|
-
if (command === "
|
|
272
|
-
await
|
|
341
|
+
if (command === "setup") {
|
|
342
|
+
await handleSetup(subcommand ?? flags.apiKey);
|
|
273
343
|
return;
|
|
274
344
|
}
|
|
275
345
|
if (command === "whoami") {
|
|
276
346
|
const config = loadConfig();
|
|
277
347
|
if (config?.email) {
|
|
278
348
|
console.log(config.email);
|
|
349
|
+
return;
|
|
279
350
|
}
|
|
280
|
-
|
|
281
|
-
|
|
351
|
+
// No email cached — try fetching from API
|
|
352
|
+
const auth = resolveAuth(flags);
|
|
353
|
+
if (!auth) {
|
|
354
|
+
console.error('Not authenticated. Run "lotics setup" or set LOTICS_API_KEY.');
|
|
355
|
+
process.exit(1);
|
|
356
|
+
}
|
|
357
|
+
const client = new LoticsClient({ apiKey: auth.apiKey });
|
|
358
|
+
try {
|
|
359
|
+
const info = await client.whoami();
|
|
360
|
+
// Cache for next time
|
|
361
|
+
const existing = loadConfig() ?? {};
|
|
362
|
+
saveConfig({ ...existing, email: info.email });
|
|
363
|
+
console.log(info.email);
|
|
364
|
+
}
|
|
365
|
+
catch (error) {
|
|
366
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
367
|
+
console.error(`Failed to fetch account info: ${message}`);
|
|
282
368
|
process.exit(1);
|
|
283
369
|
}
|
|
284
370
|
return;
|
|
@@ -288,8 +374,14 @@ async function main() {
|
|
|
288
374
|
console.error("Logged out. Credentials removed.");
|
|
289
375
|
return;
|
|
290
376
|
}
|
|
377
|
+
if (command === "login") {
|
|
378
|
+
const client = requireClient(flags);
|
|
379
|
+
const { email } = await client.login();
|
|
380
|
+
console.error(`Magic link sent to ${email}. Check your email to access the Lotics web app.`);
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
291
383
|
// --- Validate command before auth ---
|
|
292
|
-
if (command !== "tools" && command !== "upload" && command !== "run" && command !== "download") {
|
|
384
|
+
if (command !== "tools" && command !== "upload" && command !== "run" && command !== "download" && command !== "workspace") {
|
|
293
385
|
console.error(`Unknown command: ${command}`);
|
|
294
386
|
console.error('Run "lotics --help" for usage.');
|
|
295
387
|
process.exit(1);
|
|
@@ -310,6 +402,51 @@ async function main() {
|
|
|
310
402
|
process.exit(1);
|
|
311
403
|
}
|
|
312
404
|
const client = requireClient(flags);
|
|
405
|
+
// lotics workspace / lotics workspace list / lotics workspace select <id>
|
|
406
|
+
if (command === "workspace") {
|
|
407
|
+
const workspaces = await client.listWorkspaces();
|
|
408
|
+
const config = loadConfig();
|
|
409
|
+
if (subcommand === "select") {
|
|
410
|
+
const targetId = toolArgs;
|
|
411
|
+
if (!targetId) {
|
|
412
|
+
console.error('Usage: lotics workspace select <workspace_id>');
|
|
413
|
+
process.exit(1);
|
|
414
|
+
}
|
|
415
|
+
const target = workspaces.find((ws) => ws.id === targetId);
|
|
416
|
+
if (!target) {
|
|
417
|
+
console.error(`Workspace not found: ${targetId}\n\nAvailable workspaces:`);
|
|
418
|
+
printWorkspaceList(workspaces, config?.workspace_id);
|
|
419
|
+
process.exit(1);
|
|
420
|
+
}
|
|
421
|
+
const existing = config ?? {};
|
|
422
|
+
saveConfig({ ...existing, workspace_id: target.id });
|
|
423
|
+
console.error(`Switched to workspace: ${target.name} (${target.id})`);
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
// Default: list workspaces
|
|
427
|
+
if (subcommand && subcommand !== "list") {
|
|
428
|
+
console.error(`Unknown workspace subcommand: ${subcommand}`);
|
|
429
|
+
console.error('Usage: lotics workspace [list | select <id>]');
|
|
430
|
+
process.exit(1);
|
|
431
|
+
}
|
|
432
|
+
if (flags.json) {
|
|
433
|
+
console.log(JSON.stringify(workspaces, null, 2));
|
|
434
|
+
}
|
|
435
|
+
else {
|
|
436
|
+
if (workspaces.length === 0) {
|
|
437
|
+
console.error("No workspaces found.");
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
for (const ws of workspaces) {
|
|
441
|
+
const marker = ws.id === config?.workspace_id ? " (current)" : "";
|
|
442
|
+
console.log(`${ws.id} ${ws.name} ${ws.timezone} ${ws.default_currency}${marker}`);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
// Ensure workspace is resolved for all remaining commands
|
|
449
|
+
await resolveWorkspace(client);
|
|
313
450
|
// lotics tools / lotics tools <name>
|
|
314
451
|
if (command === "tools") {
|
|
315
452
|
if (subcommand) {
|
package/dist/src/client.d.ts
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
export interface LoticsClientOptions {
|
|
2
2
|
apiKey: string;
|
|
3
|
+
workspaceId?: string;
|
|
4
|
+
}
|
|
5
|
+
export interface WorkspaceInfo {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
timezone: string;
|
|
9
|
+
default_currency: string;
|
|
10
|
+
organization_id: string;
|
|
11
|
+
created_at: string;
|
|
3
12
|
}
|
|
4
13
|
export interface ToolExecuteResult {
|
|
5
14
|
result: unknown;
|
|
@@ -25,10 +34,22 @@ export interface FileUploadResult {
|
|
|
25
34
|
export declare const API_BASE_URL: string;
|
|
26
35
|
export declare class LoticsClient {
|
|
27
36
|
private apiKey;
|
|
37
|
+
private workspaceId;
|
|
28
38
|
private baseUrl;
|
|
29
39
|
constructor(options: LoticsClientOptions);
|
|
30
40
|
private throwResponseError;
|
|
41
|
+
private buildHeaders;
|
|
31
42
|
private request;
|
|
43
|
+
whoami(): Promise<{
|
|
44
|
+
email: string;
|
|
45
|
+
name: string;
|
|
46
|
+
organization_id: string;
|
|
47
|
+
}>;
|
|
48
|
+
setWorkspaceId(id: string): void;
|
|
49
|
+
listWorkspaces(): Promise<WorkspaceInfo[]>;
|
|
50
|
+
login(): Promise<{
|
|
51
|
+
email: string;
|
|
52
|
+
}>;
|
|
32
53
|
listTools(): Promise<{
|
|
33
54
|
tools: string[];
|
|
34
55
|
categories: Record<string, {
|
package/dist/src/client.js
CHANGED
|
@@ -24,9 +24,11 @@ function getMimeType(filename) {
|
|
|
24
24
|
export const API_BASE_URL = process.env.LOTICS_API_URL ?? "https://api.lotics.ai";
|
|
25
25
|
export class LoticsClient {
|
|
26
26
|
apiKey;
|
|
27
|
+
workspaceId;
|
|
27
28
|
baseUrl;
|
|
28
29
|
constructor(options) {
|
|
29
30
|
this.apiKey = options.apiKey;
|
|
31
|
+
this.workspaceId = options.workspaceId;
|
|
30
32
|
this.baseUrl = API_BASE_URL;
|
|
31
33
|
}
|
|
32
34
|
async throwResponseError(response) {
|
|
@@ -41,11 +43,18 @@ export class LoticsClient {
|
|
|
41
43
|
}
|
|
42
44
|
throw new Error(`${response.status}: ${message}`);
|
|
43
45
|
}
|
|
44
|
-
|
|
45
|
-
const url = `${this.baseUrl}${path}`;
|
|
46
|
+
buildHeaders() {
|
|
46
47
|
const headers = {
|
|
47
48
|
"x-api-key": this.apiKey,
|
|
48
49
|
};
|
|
50
|
+
if (this.workspaceId) {
|
|
51
|
+
headers["x-workspace-id"] = this.workspaceId;
|
|
52
|
+
}
|
|
53
|
+
return headers;
|
|
54
|
+
}
|
|
55
|
+
async request(method, path, body) {
|
|
56
|
+
const url = `${this.baseUrl}${path}`;
|
|
57
|
+
const headers = this.buildHeaders();
|
|
49
58
|
const init = { method, headers };
|
|
50
59
|
if (body !== undefined) {
|
|
51
60
|
headers["Content-Type"] = "application/json";
|
|
@@ -56,6 +65,18 @@ export class LoticsClient {
|
|
|
56
65
|
await this.throwResponseError(response);
|
|
57
66
|
return response.json();
|
|
58
67
|
}
|
|
68
|
+
async whoami() {
|
|
69
|
+
return this.request("GET", "/v1/cli/whoami");
|
|
70
|
+
}
|
|
71
|
+
setWorkspaceId(id) {
|
|
72
|
+
this.workspaceId = id;
|
|
73
|
+
}
|
|
74
|
+
async listWorkspaces() {
|
|
75
|
+
return this.request("GET", "/v1/workspaces");
|
|
76
|
+
}
|
|
77
|
+
async login() {
|
|
78
|
+
return this.request("POST", "/v1/cli/login");
|
|
79
|
+
}
|
|
59
80
|
async listTools() {
|
|
60
81
|
return this.request("GET", "/v1/tools");
|
|
61
82
|
}
|
|
@@ -71,7 +92,7 @@ export class LoticsClient {
|
|
|
71
92
|
const url = `${this.baseUrl}/v1/tools/execute`;
|
|
72
93
|
const response = await fetch(url, {
|
|
73
94
|
method: "POST",
|
|
74
|
-
headers: {
|
|
95
|
+
headers: { ...this.buildHeaders(), "Content-Type": "application/json" },
|
|
75
96
|
body: JSON.stringify(body),
|
|
76
97
|
signal: controller.signal,
|
|
77
98
|
});
|
|
@@ -104,7 +125,7 @@ export class LoticsClient {
|
|
|
104
125
|
async downloadFileById(fileId, outputDir) {
|
|
105
126
|
const url = `${this.baseUrl}/v1/files/${encodeURIComponent(fileId)}/download`;
|
|
106
127
|
const response = await fetch(url, {
|
|
107
|
-
headers:
|
|
128
|
+
headers: this.buildHeaders(),
|
|
108
129
|
});
|
|
109
130
|
if (!response.ok)
|
|
110
131
|
await this.throwResponseError(response);
|
|
@@ -130,7 +151,7 @@ export class LoticsClient {
|
|
|
130
151
|
const url = `${this.baseUrl}/v1/files`;
|
|
131
152
|
const response = await fetch(url, {
|
|
132
153
|
method: "POST",
|
|
133
|
-
headers:
|
|
154
|
+
headers: this.buildHeaders(),
|
|
134
155
|
body: formData,
|
|
135
156
|
});
|
|
136
157
|
if (!response.ok)
|
package/dist/src/config.d.ts
CHANGED