@lotics/cli 0.6.0 → 0.8.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
@@ -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. Prints email and auto-generated password for web app login.
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,18 @@ 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
+ lotics workspace create "Sales" # create a new workspace (admin only)
60
+ ```
61
+
62
+ Single-workspace organizations auto-select on first use.
63
+
46
64
  ## CLI
47
65
 
48
66
  ```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,25 @@ 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 prints email and auto-generated password for web app login.
29
- Use lotics whoami to check the current account email.
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 tools List available tools by category
35
- 3. lotics tools <name> Show tool description and full input schema
36
- 4. lotics run <tool> '<json>' Execute a tool with JSON arguments
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 3) before calling a tool.
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
47
+ lotics workspace create <name> Create a new workspace (admin only)
43
48
  lotics tools List all available tools
44
49
  lotics tools <name> Show tool description and input schema
45
50
  lotics run <tool> '<json>' Execute a tool
@@ -204,12 +209,16 @@ async function handleSignup(flags) {
204
209
  process.exit(1);
205
210
  }
206
211
  const existing = loadConfig() ?? {};
207
- saveConfig({ ...existing, api_key: data.api_key, email: data.email });
212
+ saveConfig({
213
+ ...existing,
214
+ api_key: data.api_key,
215
+ email: data.email,
216
+ workspace_id: data.workspace_id,
217
+ });
208
218
  console.error(`Account created. You can now use the CLI.`);
209
- console.error(` Email: ${data.email}`);
210
- console.error(` Password: ${data.password}`);
211
- console.error(`\nUse these credentials to log into the Lotics web app.`);
212
- console.error(`Run "lotics whoami" to check the current account email.`);
219
+ console.error(` Email: ${data.email}`);
220
+ console.error(`\nCheck your email for a magic link to access the Lotics web app.`);
221
+ console.error(`Run "lotics login" to request a new link at any time.`);
213
222
  }
214
223
  async function handleSetup(providedKey) {
215
224
  const apiKey = providedKey ?? await prompt("Enter your API key: ");
@@ -229,7 +238,23 @@ async function handleSetup(providedKey) {
229
238
  process.exit(1);
230
239
  }
231
240
  const existing = loadConfig() ?? {};
232
- saveConfig({ ...existing, api_key: apiKey, email });
241
+ const newConfig = { ...existing, api_key: apiKey, email };
242
+ // Auto-resolve workspace
243
+ try {
244
+ const workspaces = await client.listWorkspaces();
245
+ if (workspaces.length === 1) {
246
+ newConfig.workspace_id = workspaces[0].id;
247
+ }
248
+ else if (workspaces.length > 1) {
249
+ console.error(`\nMultiple workspaces found. Run "lotics workspace select <id>" to choose one:`);
250
+ printWorkspaceList(workspaces);
251
+ }
252
+ }
253
+ catch (error) {
254
+ const msg = error instanceof Error ? error.message : String(error);
255
+ console.error(`Warning: could not resolve workspace: ${msg}`);
256
+ }
257
+ saveConfig(newConfig);
233
258
  console.error("Authenticated.");
234
259
  }
235
260
  function requireClient(flags) {
@@ -238,7 +263,44 @@ function requireClient(flags) {
238
263
  console.error('Not authenticated. Run "lotics setup" or set LOTICS_API_KEY.');
239
264
  process.exit(1);
240
265
  }
241
- return new LoticsClient({ apiKey: auth.apiKey });
266
+ const config = loadConfig();
267
+ return new LoticsClient({ apiKey: auth.apiKey, workspaceId: config?.workspace_id });
268
+ }
269
+ function printWorkspaceList(workspaces, currentId) {
270
+ for (const ws of workspaces) {
271
+ const marker = ws.id === currentId ? " (current)" : "";
272
+ console.error(` ${ws.id} ${ws.name} ${ws.timezone} ${ws.default_currency}${marker}`);
273
+ }
274
+ }
275
+ async function resolveWorkspace(client) {
276
+ const config = loadConfig();
277
+ if (config?.workspace_id)
278
+ return;
279
+ let workspaces;
280
+ try {
281
+ workspaces = await client.listWorkspaces();
282
+ }
283
+ catch (error) {
284
+ const message = error instanceof Error ? error.message : String(error);
285
+ console.error(`Failed to list workspaces: ${message}`);
286
+ process.exit(1);
287
+ }
288
+ if (workspaces.length === 0) {
289
+ console.error("No workspaces found for this organization.");
290
+ process.exit(1);
291
+ }
292
+ if (workspaces.length === 1) {
293
+ const existing = config ?? {};
294
+ saveConfig({ ...existing, workspace_id: workspaces[0].id });
295
+ client.setWorkspaceId(workspaces[0].id);
296
+ return;
297
+ }
298
+ // Multiple workspaces, none selected
299
+ console.error("Multiple workspaces available. Select one with:\n");
300
+ console.error(' lotics workspace select <id>\n');
301
+ printWorkspaceList(workspaces);
302
+ console.error("");
303
+ process.exit(1);
242
304
  }
243
305
  /**
244
306
  * Resolve upload paths: files pass through, directories expand to their immediate files.
@@ -313,8 +375,14 @@ async function main() {
313
375
  console.error("Logged out. Credentials removed.");
314
376
  return;
315
377
  }
378
+ if (command === "login") {
379
+ const client = requireClient(flags);
380
+ const { email } = await client.login();
381
+ console.error(`Magic link sent to ${email}. Check your email to access the Lotics web app.`);
382
+ return;
383
+ }
316
384
  // --- Validate command before auth ---
317
- if (command !== "tools" && command !== "upload" && command !== "run" && command !== "download") {
385
+ if (command !== "tools" && command !== "upload" && command !== "run" && command !== "download" && command !== "workspace") {
318
386
  console.error(`Unknown command: ${command}`);
319
387
  console.error('Run "lotics --help" for usage.');
320
388
  process.exit(1);
@@ -335,6 +403,71 @@ async function main() {
335
403
  process.exit(1);
336
404
  }
337
405
  const client = requireClient(flags);
406
+ // lotics workspace / lotics workspace list / lotics workspace select <id>
407
+ if (command === "workspace") {
408
+ const workspaces = await client.listWorkspaces();
409
+ const config = loadConfig();
410
+ if (subcommand === "select") {
411
+ const targetId = toolArgs;
412
+ if (!targetId) {
413
+ console.error('Usage: lotics workspace select <workspace_id>');
414
+ process.exit(1);
415
+ }
416
+ const target = workspaces.find((ws) => ws.id === targetId);
417
+ if (!target) {
418
+ console.error(`Workspace not found: ${targetId}\n\nAvailable workspaces:`);
419
+ printWorkspaceList(workspaces, config?.workspace_id);
420
+ process.exit(1);
421
+ }
422
+ const existing = config ?? {};
423
+ saveConfig({ ...existing, workspace_id: target.id });
424
+ console.error(`Switched to workspace: ${target.name} (${target.id})`);
425
+ return;
426
+ }
427
+ if (subcommand === "create") {
428
+ const name = toolArgs;
429
+ if (!name) {
430
+ console.error('Usage: lotics workspace create <name> [--timezone <tz>]');
431
+ process.exit(1);
432
+ }
433
+ const timezone = flags.timezone;
434
+ const created = await client.createWorkspace({ name, timezone });
435
+ const existing = config ?? {};
436
+ saveConfig({ ...existing, workspace_id: created.id });
437
+ client.setWorkspaceId(created.id);
438
+ if (flags.json) {
439
+ console.log(JSON.stringify(created, null, 2));
440
+ }
441
+ else {
442
+ console.error(`Created workspace: ${created.name} (${created.id})`);
443
+ console.error(`Switched to ${created.id}`);
444
+ }
445
+ return;
446
+ }
447
+ // Default: list workspaces
448
+ if (subcommand && subcommand !== "list") {
449
+ console.error(`Unknown workspace subcommand: ${subcommand}`);
450
+ console.error('Usage: lotics workspace [list | select <id> | create <name>]');
451
+ process.exit(1);
452
+ }
453
+ if (flags.json) {
454
+ console.log(JSON.stringify(workspaces, null, 2));
455
+ }
456
+ else {
457
+ if (workspaces.length === 0) {
458
+ console.error("No workspaces found.");
459
+ }
460
+ else {
461
+ for (const ws of workspaces) {
462
+ const marker = ws.id === config?.workspace_id ? " (current)" : "";
463
+ console.log(`${ws.id} ${ws.name} ${ws.timezone} ${ws.default_currency}${marker}`);
464
+ }
465
+ }
466
+ }
467
+ return;
468
+ }
469
+ // Ensure workspace is resolved for all remaining commands
470
+ await resolveWorkspace(client);
338
471
  // lotics tools / lotics tools <name>
339
472
  if (command === "tools") {
340
473
  if (subcommand) {
@@ -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,26 @@ 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
+ createWorkspace(body: {
51
+ name: string;
52
+ timezone?: string;
53
+ }): Promise<WorkspaceInfo>;
54
+ login(): Promise<{
55
+ email: string;
56
+ }>;
37
57
  listTools(): Promise<{
38
58
  tools: string[];
39
59
  categories: Record<string, {
@@ -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
- async request(method, path, body) {
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,18 @@ 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 createWorkspace(body) {
78
+ return this.request("POST", "/v1/workspaces", body);
79
+ }
80
+ async login() {
81
+ return this.request("POST", "/v1/cli/login");
82
+ }
62
83
  async listTools() {
63
84
  return this.request("GET", "/v1/tools");
64
85
  }
@@ -74,7 +95,7 @@ export class LoticsClient {
74
95
  const url = `${this.baseUrl}/v1/tools/execute`;
75
96
  const response = await fetch(url, {
76
97
  method: "POST",
77
- headers: { "x-api-key": this.apiKey, "Content-Type": "application/json" },
98
+ headers: { ...this.buildHeaders(), "Content-Type": "application/json" },
78
99
  body: JSON.stringify(body),
79
100
  signal: controller.signal,
80
101
  });
@@ -107,7 +128,7 @@ export class LoticsClient {
107
128
  async downloadFileById(fileId, outputDir) {
108
129
  const url = `${this.baseUrl}/v1/files/${encodeURIComponent(fileId)}/download`;
109
130
  const response = await fetch(url, {
110
- headers: { "x-api-key": this.apiKey },
131
+ headers: this.buildHeaders(),
111
132
  });
112
133
  if (!response.ok)
113
134
  await this.throwResponseError(response);
@@ -133,7 +154,7 @@ export class LoticsClient {
133
154
  const url = `${this.baseUrl}/v1/files`;
134
155
  const response = await fetch(url, {
135
156
  method: "POST",
136
- headers: { "x-api-key": this.apiKey },
157
+ headers: this.buildHeaders(),
137
158
  body: formData,
138
159
  });
139
160
  if (!response.ok)
@@ -1,6 +1,7 @@
1
1
  export interface LoticsConfig {
2
2
  api_key?: string;
3
3
  email?: string;
4
+ workspace_id?: string;
4
5
  last_update_check?: number;
5
6
  latest_version?: string;
6
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/cli",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "Lotics SDK and CLI for AI agents",
5
5
  "type": "module",
6
6
  "bin": {