@lotics/cli 0.6.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 +18 -1
- package/dist/src/cli.js +126 -14
- package/dist/src/client.d.ts +16 -0
- package/dist/src/client.js +23 -5
- package/dist/src/config.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -25,13 +25,19 @@ 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 login`** — Send a magic link email to access the web app (requires prior signup or setup).
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
lotics login
|
|
39
|
+
```
|
|
40
|
+
|
|
35
41
|
**`lotics setup`** — Saves an existing API key directly (e.g. one created in the Lotics web app).
|
|
36
42
|
|
|
37
43
|
```bash
|
|
@@ -43,6 +49,17 @@ API key is saved to `~/.lotics/config.json`. Run `lotics logout` to remove saved
|
|
|
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,6 +16,7 @@ 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 login Send a magic link email to access the web app
|
|
19
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
|
|
@@ -25,21 +26,24 @@ AUTHENTICATION
|
|
|
25
26
|
--name <name> Display name (defaults to email prefix)
|
|
26
27
|
--timezone <timezone> Workspace timezone (defaults to UTC, e.g. Asia/Ho_Chi_Minh)
|
|
27
28
|
|
|
28
|
-
Signup
|
|
29
|
-
Use lotics
|
|
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.
|
|
30
31
|
Setup priority: --api-key flag > LOTICS_API_KEY env > saved config.
|
|
31
32
|
|
|
32
33
|
USAGE
|
|
33
34
|
1. lotics signup (or lotics setup) Create account or set up API key
|
|
34
|
-
2. lotics
|
|
35
|
-
3. lotics tools
|
|
36
|
-
4. lotics
|
|
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
|
|
37
39
|
|
|
38
|
-
Always inspect the schema (step
|
|
40
|
+
Always inspect the schema (step 4) before calling a tool.
|
|
39
41
|
Tools are grouped by category (tables, records, views, etc.).
|
|
40
42
|
Query tools return IDs used as arguments to other tools.
|
|
41
43
|
|
|
42
44
|
COMMANDS
|
|
45
|
+
lotics workspace List workspaces (marks current)
|
|
46
|
+
lotics workspace select <id> Switch to a different workspace
|
|
43
47
|
lotics tools List all available tools
|
|
44
48
|
lotics tools <name> Show tool description and input schema
|
|
45
49
|
lotics run <tool> '<json>' Execute a tool
|
|
@@ -204,12 +208,16 @@ async function handleSignup(flags) {
|
|
|
204
208
|
process.exit(1);
|
|
205
209
|
}
|
|
206
210
|
const existing = loadConfig() ?? {};
|
|
207
|
-
saveConfig({
|
|
211
|
+
saveConfig({
|
|
212
|
+
...existing,
|
|
213
|
+
api_key: data.api_key,
|
|
214
|
+
email: data.email,
|
|
215
|
+
workspace_id: data.workspace_id,
|
|
216
|
+
});
|
|
208
217
|
console.error(`Account created. You can now use the CLI.`);
|
|
209
|
-
console.error(` Email:
|
|
210
|
-
console.error(
|
|
211
|
-
console.error(
|
|
212
|
-
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.`);
|
|
213
221
|
}
|
|
214
222
|
async function handleSetup(providedKey) {
|
|
215
223
|
const apiKey = providedKey ?? await prompt("Enter your API key: ");
|
|
@@ -229,7 +237,23 @@ async function handleSetup(providedKey) {
|
|
|
229
237
|
process.exit(1);
|
|
230
238
|
}
|
|
231
239
|
const existing = loadConfig() ?? {};
|
|
232
|
-
|
|
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);
|
|
233
257
|
console.error("Authenticated.");
|
|
234
258
|
}
|
|
235
259
|
function requireClient(flags) {
|
|
@@ -238,7 +262,44 @@ function requireClient(flags) {
|
|
|
238
262
|
console.error('Not authenticated. Run "lotics setup" or set LOTICS_API_KEY.');
|
|
239
263
|
process.exit(1);
|
|
240
264
|
}
|
|
241
|
-
|
|
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}`);
|
|
285
|
+
process.exit(1);
|
|
286
|
+
}
|
|
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);
|
|
242
303
|
}
|
|
243
304
|
/**
|
|
244
305
|
* Resolve upload paths: files pass through, directories expand to their immediate files.
|
|
@@ -313,8 +374,14 @@ async function main() {
|
|
|
313
374
|
console.error("Logged out. Credentials removed.");
|
|
314
375
|
return;
|
|
315
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
|
+
}
|
|
316
383
|
// --- Validate command before auth ---
|
|
317
|
-
if (command !== "tools" && command !== "upload" && command !== "run" && command !== "download") {
|
|
384
|
+
if (command !== "tools" && command !== "upload" && command !== "run" && command !== "download" && command !== "workspace") {
|
|
318
385
|
console.error(`Unknown command: ${command}`);
|
|
319
386
|
console.error('Run "lotics --help" for usage.');
|
|
320
387
|
process.exit(1);
|
|
@@ -335,6 +402,51 @@ async function main() {
|
|
|
335
402
|
process.exit(1);
|
|
336
403
|
}
|
|
337
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);
|
|
338
450
|
// lotics tools / lotics tools <name>
|
|
339
451
|
if (command === "tools") {
|
|
340
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,15 +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;
|
|
32
43
|
whoami(): Promise<{
|
|
33
44
|
email: string;
|
|
34
45
|
name: string;
|
|
35
46
|
organization_id: string;
|
|
36
47
|
}>;
|
|
48
|
+
setWorkspaceId(id: string): void;
|
|
49
|
+
listWorkspaces(): Promise<WorkspaceInfo[]>;
|
|
50
|
+
login(): Promise<{
|
|
51
|
+
email: string;
|
|
52
|
+
}>;
|
|
37
53
|
listTools(): Promise<{
|
|
38
54
|
tools: string[];
|
|
39
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";
|
|
@@ -59,6 +68,15 @@ export class LoticsClient {
|
|
|
59
68
|
async whoami() {
|
|
60
69
|
return this.request("GET", "/v1/cli/whoami");
|
|
61
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
|
+
}
|
|
62
80
|
async listTools() {
|
|
63
81
|
return this.request("GET", "/v1/tools");
|
|
64
82
|
}
|
|
@@ -74,7 +92,7 @@ export class LoticsClient {
|
|
|
74
92
|
const url = `${this.baseUrl}/v1/tools/execute`;
|
|
75
93
|
const response = await fetch(url, {
|
|
76
94
|
method: "POST",
|
|
77
|
-
headers: {
|
|
95
|
+
headers: { ...this.buildHeaders(), "Content-Type": "application/json" },
|
|
78
96
|
body: JSON.stringify(body),
|
|
79
97
|
signal: controller.signal,
|
|
80
98
|
});
|
|
@@ -107,7 +125,7 @@ export class LoticsClient {
|
|
|
107
125
|
async downloadFileById(fileId, outputDir) {
|
|
108
126
|
const url = `${this.baseUrl}/v1/files/${encodeURIComponent(fileId)}/download`;
|
|
109
127
|
const response = await fetch(url, {
|
|
110
|
-
headers:
|
|
128
|
+
headers: this.buildHeaders(),
|
|
111
129
|
});
|
|
112
130
|
if (!response.ok)
|
|
113
131
|
await this.throwResponseError(response);
|
|
@@ -133,7 +151,7 @@ export class LoticsClient {
|
|
|
133
151
|
const url = `${this.baseUrl}/v1/files`;
|
|
134
152
|
const response = await fetch(url, {
|
|
135
153
|
method: "POST",
|
|
136
|
-
headers:
|
|
154
|
+
headers: this.buildHeaders(),
|
|
137
155
|
body: formData,
|
|
138
156
|
});
|
|
139
157
|
if (!response.ok)
|
package/dist/src/config.d.ts
CHANGED