@lidianai/cli 0.1.0 → 0.1.1

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/AGENTS.md ADDED
@@ -0,0 +1,26 @@
1
+ # AGENTS.md - Lidian CLI
2
+
3
+ Guidelines for agents working in the `cli/` package.
4
+
5
+ ## Core Standards
6
+
7
+ - Keep implementations simple, clean, and easy to read.
8
+ - Prefer direct logic over clever abstractions and indirection.
9
+ - Avoid obscure decisions and surprising behavior.
10
+
11
+ ## Required Simplification Pass
12
+
13
+ After any feature or code change, always review your own diff and simplify before finishing:
14
+
15
+ - remove dead code and temporary scaffolding
16
+ - collapse duplicated logic
17
+ - simplify branching/flow where possible
18
+ - improve naming for clarity
19
+
20
+ If complexity is unavoidable, add a short comment explaining why it is necessary.
21
+
22
+ ## Tooling Rules
23
+
24
+ - Use `bun`/`bunx` only.
25
+ - Run `bun run lint` and `bun run typecheck` before completion.
26
+ - Use explicit types and avoid `any`.
package/README.md CHANGED
@@ -5,36 +5,39 @@ Bun CLI for Lidian core REST endpoints.
5
5
  ## Install
6
6
 
7
7
  ```bash
8
- npm install -g @lidian/cli
9
- # or
10
- bunx @lidian/cli --help
8
+ bunx @lidianai/cli --help
11
9
  ```
12
10
 
13
11
  ## Commands
14
12
 
15
13
  ```bash
16
- lidian query --q "<term>" [--page 1] [--pageSize 1..3] [--api-key <key>] [--json]
17
- lidian act --endpoint-id <uuid> --params '<json>' [--payment-rail prepaid_credits|x402] [--network base|ethereum] [--api-key <key>] [--json]
18
- lidian account [--api-key <key>] [--json]
14
+ lidian discover --q "<term>" [--page 1] [--pageSize 1..3] [--category <name>] [--auth-type none|api_key|bearer|basic|oauth2|custom] [--min-price <cents>] [--max-price <cents>] [--api-key <key>] [--env production|staging] [--api-base <url>] [--json]
15
+ lidian consume --endpoint-id <uuid> --params '<json>' [--payment-rail prepaid_credits|x402] [--network base|ethereum] [--api-key <key>] [--env production|staging] [--api-base <url>] [--json]
16
+ lidian account [--api-key <key>] [--env production|staging] [--api-base <url>] [--json]
19
17
  lidian login [--key ld_...] [--json]
20
18
  lidian --help
21
19
  ```
22
20
 
23
- `query` returns API matches (`items[]`) from `/v1/query` with metadata and confidence fields (`matchScore`, `matchPercent`).
21
+ `discover` returns API matches (`items[]`) from `/v1/discover` with metadata and confidence fields (`matchScore`, `matchPercent`).
24
22
 
25
23
  ## Auth
26
24
 
27
25
  - Store key locally: `lidian login --key ld_...` (writes `~/.lidian/config.json`)
28
26
  - Or run `lidian login` and paste your key after browser auth flow.
29
27
  - Resolution order: `--api-key` -> `LIDIAN_API_KEY` -> `~/.lidian/config.json`.
30
- - Optional: set `LIDIAN_API_BASE` (default `https://api.lidian.ai`).
28
+ - API base resolution order:
29
+ - `--api-base`
30
+ - `LIDIAN_API_BASE`
31
+ - `--env` (`production` or `staging`)
32
+ - `LIDIAN_ENV` (`production` or `staging`)
33
+ - default `https://api.lidian.ai`
31
34
 
32
35
  ## x402
33
36
 
34
37
  When `--payment-rail x402` is used, CLI performs:
35
38
  1. `POST /v1/payments/requirements`
36
39
  2. `POST /v1/payments/verify`
37
- 3. `POST /v1/act`
40
+ 3. `POST /v1/consume`
38
41
 
39
42
  ## Dev
40
43
 
package/bun.lock ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "lockfileVersion": 1,
3
+ "configVersion": 1,
4
+ "workspaces": {
5
+ "": {
6
+ "name": "@lidian/cli",
7
+ "devDependencies": {
8
+ "@biomejs/biome": "^2.4.4",
9
+ "@types/bun": "^1.3.9",
10
+ "typescript": "^5.9.3",
11
+ },
12
+ },
13
+ },
14
+ "packages": {
15
+ "@biomejs/biome": ["@biomejs/biome@2.4.4", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.4.4", "@biomejs/cli-darwin-x64": "2.4.4", "@biomejs/cli-linux-arm64": "2.4.4", "@biomejs/cli-linux-arm64-musl": "2.4.4", "@biomejs/cli-linux-x64": "2.4.4", "@biomejs/cli-linux-x64-musl": "2.4.4", "@biomejs/cli-win32-arm64": "2.4.4", "@biomejs/cli-win32-x64": "2.4.4" }, "bin": { "biome": "bin/biome" } }, "sha512-tigwWS5KfJf0cABVd52NVaXyAVv4qpUXOWJ1rxFL8xF1RVoeS2q/LK+FHgYoKMclJCuRoCWAPy1IXaN9/mS61Q=="],
16
+
17
+ "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.4.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-jZ+Xc6qvD6tTH5jM6eKX44dcbyNqJHssfl2nnwT6vma6B1sj7ZLTGIk6N5QwVBs5xGN52r3trk5fgd3sQ9We9A=="],
18
+
19
+ "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.4.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-Dh1a/+W+SUCXhEdL7TiX3ArPTFCQKJTI1mGncZNWfO+6suk+gYA4lNyJcBB+pwvF49uw0pEbUS49BgYOY4hzUg=="],
20
+
21
+ "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.4.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-V/NFfbWhsUU6w+m5WYbBenlEAz8eYnSqRMDMAW3K+3v0tYVkNyZn8VU0XPxk/lOqNXLSCCrV7FmV/u3SjCBShg=="],
22
+
23
+ "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.4.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-+sPAXq3bxmFwhVFJnSwkSF5Rw2ZAJMH3MF6C9IveAEOdSpgajPhoQhbbAK12SehN9j2QrHpk4J/cHsa/HqWaYQ=="],
24
+
25
+ "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.4.4", "", { "os": "linux", "cpu": "x64" }, "sha512-R4+ZCDtG9kHArasyBO+UBD6jr/FcFCTH8QkNTOCu0pRJzCWyWC4EtZa2AmUZB5h3e0jD7bRV2KvrENcf8rndBg=="],
26
+
27
+ "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.4.4", "", { "os": "linux", "cpu": "x64" }, "sha512-gGvFTGpOIQDb5CQ2VC0n9Z2UEqlP46c4aNgHmAMytYieTGEcfqhfCFnhs6xjt0S3igE6q5GLuIXtdQt3Izok+g=="],
28
+
29
+ "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.4.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-trzCqM7x+Gn832zZHgr28JoYagQNX4CZkUZhMUac2YxvvyDRLJDrb5m9IA7CaZLlX6lTQmADVfLEKP1et1Ma4Q=="],
30
+
31
+ "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.4.4", "", { "os": "win32", "cpu": "x64" }, "sha512-gnOHKVPFAAPrpoPt2t+Q6FZ7RPry/FDV3GcpU53P3PtLNnQjBmKyN2Vh/JtqXet+H4pme8CC76rScwdjDcT1/A=="],
32
+
33
+ "@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="],
34
+
35
+ "@types/node": ["@types/node@25.3.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A=="],
36
+
37
+ "bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="],
38
+
39
+ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
40
+
41
+ "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
42
+ }
43
+ }
package/dist/index.js CHANGED
@@ -24,15 +24,15 @@ var verifyPaymentAddress = async (http, apiKey, payTo) => {
24
24
  return http.post("/v1/payments/verify", { payTo }, apiKey);
25
25
  };
26
26
 
27
- // src/commands/act.ts
28
- var runActCommand = async (http, apiKey, input) => {
27
+ // src/commands/consume.ts
28
+ var runConsumeCommand = async (http, apiKey, input) => {
29
29
  if (!isUuid(input.endpointId)) {
30
30
  throw new CliError("endpointId must be a valid UUID.");
31
31
  }
32
32
  if (input.paymentRail === "x402") {
33
33
  const requirements = await requestPaymentRequirements(http, apiKey, input.endpointId, input.network);
34
34
  const verification = await verifyPaymentAddress(http, apiKey, requirements.payTo);
35
- const execution2 = await http.post("/v1/act", input, apiKey);
35
+ const execution2 = await http.post("/v1/consume", input, apiKey);
36
36
  return {
37
37
  execution: execution2,
38
38
  payment: {
@@ -41,13 +41,35 @@ var runActCommand = async (http, apiKey, input) => {
41
41
  }
42
42
  };
43
43
  }
44
- const execution = await http.post("/v1/act", input, apiKey);
44
+ const execution = await http.post("/v1/consume", input, apiKey);
45
45
  return { execution };
46
46
  };
47
47
  var isUuid = (value) => {
48
48
  return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
49
49
  };
50
50
 
51
+ // src/commands/discover.ts
52
+ var runDiscoverCommand = async (http, apiKey, input) => {
53
+ if (input.pageSize < 1 || input.pageSize > 3) {
54
+ throw new CliError("pageSize must be between 1 and 3.");
55
+ }
56
+ const params = new URLSearchParams;
57
+ params.set("q", input.q);
58
+ params.set("page", String(input.page));
59
+ params.set("pageSize", String(input.pageSize));
60
+ if (input.category)
61
+ params.set("category", input.category);
62
+ if (input.authType)
63
+ params.set("authType", input.authType);
64
+ if (typeof input.minPrice === "number") {
65
+ params.set("minPrice", String(input.minPrice));
66
+ }
67
+ if (typeof input.maxPrice === "number") {
68
+ params.set("maxPrice", String(input.maxPrice));
69
+ }
70
+ return http.get(`/v1/discover?${params.toString()}`, apiKey);
71
+ };
72
+
51
73
  // src/commands/login.ts
52
74
  import { stdin, stdout } from "process";
53
75
  import { createInterface } from "readline/promises";
@@ -93,7 +115,7 @@ var printResult = (result, asJson) => {
93
115
  }
94
116
  print(formatForHuman(result));
95
117
  };
96
- var printQueryResult = (result, asJson) => {
118
+ var printDiscoverResult = (result, asJson) => {
97
119
  if (asJson) {
98
120
  printResult(result, true);
99
121
  return;
@@ -105,10 +127,10 @@ var printQueryResult = (result, asJson) => {
105
127
  print(`Found ${result.items.length} of ${result.total} APIs (page ${result.page}).`);
106
128
  for (const item of result.items) {
107
129
  const confidence = item.matchPercent ? ` confidence=${item.matchPercent.toFixed(1)}%` : "";
108
- print(`- ${item.name} (${item.id}) auth=${item.authType} cost=${item.defaultCostPerUse}c${confidence}`);
130
+ print(`- ${item.name} (${item.id}) auth=${item.authType} cost=${item.defaultCostPerUse}c (${formatUsd(item.defaultCostPerUse)})${confidence}`);
109
131
  }
110
132
  };
111
- var printActResult = (result, asJson) => {
133
+ var printConsumeResult = (result, asJson) => {
112
134
  if (asJson) {
113
135
  printResult(result.execution, true);
114
136
  return;
@@ -124,10 +146,10 @@ var printAccountResult = (result, asJson) => {
124
146
  return;
125
147
  }
126
148
  print(`Account: ${result.user.id}`);
127
- print(`Balance: ${result.balance.balance} credits`);
149
+ print(`Balance: ${result.balance.balance} cents (${formatUsd(result.balance.balance)})`);
128
150
  };
129
151
  var printExecutionResult = (result) => {
130
- print(`Execution succeeded. Spent=${result.credits.spent} balance=${result.credits.balance}`);
152
+ print(`Execution succeeded. Spent=${result.credits.spent} cents (${formatUsd(result.credits.spent)}) balance=${result.credits.balance} cents (${formatUsd(result.credits.balance)})`);
131
153
  print(JSON.stringify(result.data, null, 2));
132
154
  };
133
155
  var formatForHuman = (value) => {
@@ -136,6 +158,9 @@ var formatForHuman = (value) => {
136
158
  }
137
159
  return JSON.stringify(value, null, 2);
138
160
  };
161
+ var formatUsd = (cents) => {
162
+ return `$${(cents / 100).toFixed(2)}`;
163
+ };
139
164
  var fail = (error) => {
140
165
  if (error instanceof CliError) {
141
166
  printError(`Error: ${error.message}`);
@@ -198,19 +223,6 @@ var openUrl = (url) => {
198
223
  } catch {}
199
224
  };
200
225
 
201
- // src/commands/query.ts
202
- var runQueryCommand = async (http, apiKey, input) => {
203
- if (input.pageSize < 1 || input.pageSize > 3) {
204
- throw new CliError("pageSize must be between 1 and 3.");
205
- }
206
- const params = new URLSearchParams({
207
- q: input.q,
208
- page: String(input.page),
209
- pageSize: String(input.pageSize)
210
- });
211
- return http.get(`/v1/query?${params.toString()}`, apiKey);
212
- };
213
-
214
226
  // src/lib/auth.ts
215
227
  var resolveApiKey = async (argsApiKey) => {
216
228
  const config = await readConfig();
@@ -264,10 +276,23 @@ var handleResponse = async (response) => {
264
276
 
265
277
  // src/index.ts
266
278
  var DEFAULT_API_BASE = "https://api.lidian.ai";
267
- var GLOBAL_OPTIONS = new Set(["api-key", "api-base", "json", "help"]);
279
+ var API_BASE_BY_ENV = {
280
+ production: "https://api.lidian.ai",
281
+ staging: "https://staging-api.lidian.ai"
282
+ };
283
+ var GLOBAL_OPTIONS = new Set(["api-key", "api-base", "env", "json", "help"]);
284
+ var BOOLEAN_OPTIONS = new Set(["json", "help"]);
268
285
  var COMMAND_OPTIONS = {
269
- query: new Set(["q", "page", "pageSize"]),
270
- act: new Set(["endpoint-id", "params", "payment-rail", "network"]),
286
+ discover: new Set([
287
+ "q",
288
+ "page",
289
+ "pageSize",
290
+ "category",
291
+ "auth-type",
292
+ "min-price",
293
+ "max-price"
294
+ ]),
295
+ consume: new Set(["endpoint-id", "params", "payment-rail", "network"]),
271
296
  account: new Set([]),
272
297
  login: new Set(["key"])
273
298
  };
@@ -277,7 +302,7 @@ var main = async () => {
277
302
  return;
278
303
  }
279
304
  const parsed = parseArgs(process.argv.slice(2));
280
- const apiBase = String(parsed.options["api-base"] ?? process.env.LIDIAN_API_BASE ?? DEFAULT_API_BASE);
305
+ const apiBase = resolveApiBase(asString(parsed.options["api-base"]), asEnvironment(asString(parsed.options.env) ?? process.env.LIDIAN_ENV));
281
306
  const asJson = Boolean(parsed.options.json);
282
307
  const http = createHttpClient(apiBase);
283
308
  switch (parsed.command) {
@@ -291,39 +316,46 @@ var main = async () => {
291
316
  }
292
317
  return;
293
318
  }
294
- case "query": {
319
+ case "discover": {
295
320
  const apiKey = await resolveApiKey(asString(parsed.options["api-key"]));
296
321
  const qValue = asString(parsed.options.q);
297
322
  if (!qValue) {
298
- throw new CliError("Missing --q for query command.");
323
+ throw new CliError("Missing --q for discover command.");
299
324
  }
300
325
  const page = toInt(asString(parsed.options.page), 1);
301
326
  const pageSize = toInt(asString(parsed.options.pageSize), 1);
302
- const result = await runQueryCommand(http, apiKey, {
327
+ const authType = asAuthType(asString(parsed.options["auth-type"]));
328
+ const minPrice = toNumber(asString(parsed.options["min-price"]), "min-price");
329
+ const maxPrice = toNumber(asString(parsed.options["max-price"]), "max-price");
330
+ const result = await runDiscoverCommand(http, apiKey, {
303
331
  q: qValue,
304
332
  page,
305
- pageSize
333
+ pageSize,
334
+ category: asString(parsed.options.category),
335
+ ...authType ? { authType } : {},
336
+ ...typeof minPrice === "number" ? { minPrice } : {},
337
+ ...typeof maxPrice === "number" ? { maxPrice } : {}
306
338
  });
307
- printQueryResult(result, asJson);
339
+ printDiscoverResult(result, asJson);
308
340
  return;
309
341
  }
310
- case "act": {
342
+ case "consume": {
311
343
  const apiKey = await resolveApiKey(asString(parsed.options["api-key"]));
312
344
  const endpointIdValue = asString(parsed.options["endpoint-id"]);
313
345
  if (!endpointIdValue) {
314
- throw new CliError("Missing --endpoint-id for act command.");
346
+ throw new CliError("Missing --endpoint-id for consume command.");
315
347
  }
316
348
  const paramsRaw = asString(parsed.options.params) ?? "{}";
317
349
  const params = parseJsonObject(paramsRaw, "--params");
318
350
  const paymentRail = asPaymentRail(asString(parsed.options["payment-rail"]) ?? "prepaid_credits");
319
- const network = asString(parsed.options.network);
320
- const result = await runActCommand(http, apiKey, {
351
+ const network = asNetwork(asString(parsed.options.network));
352
+ const result = await runConsumeCommand(http, apiKey, {
321
353
  endpointId: endpointIdValue,
322
354
  params,
323
355
  paymentRail,
324
356
  ...network ? { network } : {}
325
357
  });
326
- printActResult(result, asJson);
358
+ printConsumeResult(result, asJson);
327
359
  return;
328
360
  }
329
361
  case "account": {
@@ -338,9 +370,9 @@ var main = async () => {
338
370
  };
339
371
  var parseArgs = (argv) => {
340
372
  const command = argv[0];
341
- if (command !== "query" && command !== "act" && command !== "account" && command !== "login") {
373
+ if (command !== "discover" && command !== "consume" && command !== "account" && command !== "login") {
342
374
  printUsage();
343
- throw new CliError("Invalid command. Use one of: login, query, act, account.", 1);
375
+ throw new CliError("Invalid command. Use one of: login, discover, consume, account.", 1);
344
376
  }
345
377
  const options = {};
346
378
  let index = 1;
@@ -356,6 +388,9 @@ var parseArgs = (argv) => {
356
388
  }
357
389
  const next = argv[index + 1];
358
390
  if (!next || next.startsWith("--")) {
391
+ if (!BOOLEAN_OPTIONS.has(key)) {
392
+ throw new CliError(`Missing value for --${key}`);
393
+ }
359
394
  options[key] = true;
360
395
  index += 1;
361
396
  continue;
@@ -371,10 +406,16 @@ var parseArgs = (argv) => {
371
406
  var printUsage = () => {
372
407
  print("Usage:");
373
408
  print(" lidian login [--key ld_...] [--json]");
374
- print(' lidian query --q "<term>" [--page 1] [--pageSize 1..3] [--api-key <key>] [--json]');
375
- print(" lidian act --endpoint-id <uuid> --params '<json>' [--payment-rail prepaid_credits|x402] [--api-key <key>] [--json]");
409
+ print(' lidian discover --q "<term>" [--page 1] [--pageSize 1..3] [--category <name>] [--auth-type none|api_key|bearer|basic|oauth2|custom]');
410
+ print(" [--min-price <cents>] [--max-price <cents>] [--api-key <key>] [--env production|staging] [--api-base <url>] [--json]");
411
+ print(" lidian consume --endpoint-id <uuid> --params '<json>' [--payment-rail prepaid_credits|x402] [--api-key <key>] [--env production|staging] [--api-base <url>] [--json]");
376
412
  print(" [--network base|ethereum]");
377
- print(" lidian account [--api-key <key>] [--json]");
413
+ print(" lidian account [--api-key <key>] [--env production|staging] [--api-base <url>] [--json]");
414
+ print("");
415
+ print("Env resolution:");
416
+ print(" --api-base > LIDIAN_API_BASE > --env > LIDIAN_ENV > production");
417
+ print(` production=${API_BASE_BY_ENV.production}`);
418
+ print(` staging=${API_BASE_BY_ENV.staging}`);
378
419
  };
379
420
  var asString = (value) => {
380
421
  if (typeof value === "string")
@@ -385,13 +426,27 @@ var toInt = (value, fallback) => {
385
426
  if (!value)
386
427
  return fallback;
387
428
  const parsed = Number.parseInt(value, 10);
388
- if (Number.isNaN(parsed)) {
429
+ if (Number.isNaN(parsed) || parsed < 1) {
389
430
  throw new CliError(`Invalid integer value: ${value}`);
390
431
  }
391
432
  return parsed;
392
433
  };
434
+ var toNumber = (value, flagName) => {
435
+ if (!value)
436
+ return;
437
+ const parsed = Number(value);
438
+ if (Number.isNaN(parsed)) {
439
+ throw new CliError(`Invalid --${flagName} value: ${value}`);
440
+ }
441
+ return parsed;
442
+ };
393
443
  var parseJsonObject = (value, flagName) => {
394
- const parsed = JSON.parse(value);
444
+ let parsed;
445
+ try {
446
+ parsed = JSON.parse(value);
447
+ } catch {
448
+ throw new CliError(`${flagName} must be valid JSON.`);
449
+ }
395
450
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
396
451
  throw new CliError(`${flagName} must be a JSON object.`);
397
452
  }
@@ -402,4 +457,43 @@ var asPaymentRail = (value) => {
402
457
  return value;
403
458
  throw new CliError("Invalid --payment-rail. Use prepaid_credits or x402.");
404
459
  };
460
+ var asAuthType = (value) => {
461
+ if (!value)
462
+ return;
463
+ const valid = new Set([
464
+ "none",
465
+ "api_key",
466
+ "bearer",
467
+ "basic",
468
+ "oauth2",
469
+ "custom"
470
+ ]);
471
+ if (valid.has(value)) {
472
+ return value;
473
+ }
474
+ throw new CliError("Invalid --auth-type. Use none, api_key, bearer, basic, oauth2, or custom.");
475
+ };
476
+ var asNetwork = (value) => {
477
+ if (!value)
478
+ return;
479
+ if (value === "base" || value === "ethereum")
480
+ return value;
481
+ throw new CliError("Invalid --network. Use base or ethereum.");
482
+ };
483
+ var asEnvironment = (value) => {
484
+ if (!value)
485
+ return;
486
+ if (value === "production" || value === "staging")
487
+ return value;
488
+ throw new CliError("Invalid --env. Use production or staging.");
489
+ };
490
+ var resolveApiBase = (cliApiBase, environment) => {
491
+ if (cliApiBase)
492
+ return cliApiBase;
493
+ if (process.env.LIDIAN_API_BASE)
494
+ return process.env.LIDIAN_API_BASE;
495
+ if (environment)
496
+ return API_BASE_BY_ENV[environment];
497
+ return DEFAULT_API_BASE;
498
+ };
405
499
  main().catch(fail);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lidianai/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -8,14 +8,14 @@ import {
8
8
 
9
9
  export type PaymentRail = "prepaid_credits" | "x402";
10
10
 
11
- export interface ActCommandInput {
11
+ export interface ConsumeCommandInput {
12
12
  endpointId: string;
13
13
  params: Record<string, unknown>;
14
14
  paymentRail: PaymentRail;
15
15
  network?: string;
16
16
  }
17
17
 
18
- export interface ActApiResponse {
18
+ export interface ConsumeApiResponse {
19
19
  data: unknown;
20
20
  credits: {
21
21
  spent: number;
@@ -23,19 +23,19 @@ export interface ActApiResponse {
23
23
  };
24
24
  }
25
25
 
26
- export interface ActCommandResult {
27
- execution: ActApiResponse;
26
+ export interface ConsumeCommandResult {
27
+ execution: ConsumeApiResponse;
28
28
  payment?: {
29
29
  requirements: PaymentRequirementsResponse;
30
30
  verified: boolean;
31
31
  };
32
32
  }
33
33
 
34
- export const runActCommand = async (
34
+ export const runConsumeCommand = async (
35
35
  http: HttpClient,
36
36
  apiKey: string,
37
- input: ActCommandInput,
38
- ): Promise<ActCommandResult> => {
37
+ input: ConsumeCommandInput,
38
+ ): Promise<ConsumeCommandResult> => {
39
39
  if (!isUuid(input.endpointId)) {
40
40
  throw new CliError("endpointId must be a valid UUID.");
41
41
  }
@@ -53,8 +53,8 @@ export const runActCommand = async (
53
53
  requirements.payTo,
54
54
  );
55
55
 
56
- const execution = await http.post<ActApiResponse, ActCommandInput>(
57
- "/v1/act",
56
+ const execution = await http.post<ConsumeApiResponse, ConsumeCommandInput>(
57
+ "/v1/consume",
58
58
  input,
59
59
  apiKey,
60
60
  );
@@ -68,8 +68,8 @@ export const runActCommand = async (
68
68
  };
69
69
  }
70
70
 
71
- const execution = await http.post<ActApiResponse, ActCommandInput>(
72
- "/v1/act",
71
+ const execution = await http.post<ConsumeApiResponse, ConsumeCommandInput>(
72
+ "/v1/consume",
73
73
  input,
74
74
  apiKey,
75
75
  );
@@ -0,0 +1,59 @@
1
+ import { CliError } from "@/lib/errors";
2
+ import type { HttpClient } from "@/lib/http";
3
+
4
+ export interface DiscoverCommandInput {
5
+ q: string;
6
+ page: number;
7
+ pageSize: number;
8
+ category?: string;
9
+ authType?: "none" | "api_key" | "bearer" | "basic" | "oauth2" | "custom";
10
+ minPrice?: number;
11
+ maxPrice?: number;
12
+ }
13
+
14
+ export interface DiscoverApiResponse {
15
+ items: Array<{
16
+ id: string;
17
+ merchantId: string | null;
18
+ name: string;
19
+ description: string | null;
20
+ endpointBase: string;
21
+ authType: "none" | "api_key" | "bearer" | "basic" | "oauth2" | "custom";
22
+ defaultCostPerUse: number;
23
+ isActive: boolean;
24
+ openapiSpecUrl: string | null;
25
+ createdAt: string;
26
+ updatedAt: string;
27
+ matchScore?: number;
28
+ matchPercent?: number;
29
+ }>;
30
+ total: number;
31
+ page: number;
32
+ pageSize: number;
33
+ }
34
+
35
+ export const runDiscoverCommand = async (
36
+ http: HttpClient,
37
+ apiKey: string,
38
+ input: DiscoverCommandInput,
39
+ ): Promise<DiscoverApiResponse> => {
40
+ if (input.pageSize < 1 || input.pageSize > 3) {
41
+ throw new CliError("pageSize must be between 1 and 3.");
42
+ }
43
+ const params = new URLSearchParams();
44
+ params.set("q", input.q);
45
+ params.set("page", String(input.page));
46
+ params.set("pageSize", String(input.pageSize));
47
+ if (input.category) params.set("category", input.category);
48
+ if (input.authType) params.set("authType", input.authType);
49
+ if (typeof input.minPrice === "number") {
50
+ params.set("minPrice", String(input.minPrice));
51
+ }
52
+ if (typeof input.maxPrice === "number") {
53
+ params.set("maxPrice", String(input.maxPrice));
54
+ }
55
+ return http.get<DiscoverApiResponse>(
56
+ `/v1/discover?${params.toString()}`,
57
+ apiKey,
58
+ );
59
+ };
package/src/index.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
3
  import { runAccountCommand } from "@/commands/account";
4
- import { type PaymentRail, runActCommand } from "@/commands/act";
4
+ import { type PaymentRail, runConsumeCommand } from "@/commands/consume";
5
+ import { runDiscoverCommand } from "@/commands/discover";
5
6
  import { runLoginCommand } from "@/commands/login";
6
- import { runQueryCommand } from "@/commands/query";
7
7
  import { resolveApiKey } from "@/lib/auth";
8
8
  import { CliError } from "@/lib/errors";
9
9
  import { createHttpClient } from "@/lib/http";
@@ -11,20 +11,35 @@ import {
11
11
  fail,
12
12
  print,
13
13
  printAccountResult,
14
- printActResult,
15
- printQueryResult,
14
+ printConsumeResult,
15
+ printDiscoverResult,
16
16
  } from "@/lib/output";
17
17
 
18
18
  interface ParsedArgs {
19
- command: "query" | "act" | "account" | "login";
19
+ command: "discover" | "consume" | "account" | "login";
20
20
  options: Record<string, string | boolean>;
21
21
  }
22
22
 
23
23
  const DEFAULT_API_BASE = "https://api.lidian.ai";
24
- const GLOBAL_OPTIONS = new Set(["api-key", "api-base", "json", "help"]);
24
+ const API_BASE_BY_ENV = {
25
+ production: "https://api.lidian.ai",
26
+ staging: "https://staging-api.lidian.ai",
27
+ } as const;
28
+ type Environment = keyof typeof API_BASE_BY_ENV;
29
+
30
+ const GLOBAL_OPTIONS = new Set(["api-key", "api-base", "env", "json", "help"]);
31
+ const BOOLEAN_OPTIONS = new Set(["json", "help"]);
25
32
  const COMMAND_OPTIONS: Record<ParsedArgs["command"], Set<string>> = {
26
- query: new Set(["q", "page", "pageSize"]),
27
- act: new Set(["endpoint-id", "params", "payment-rail", "network"]),
33
+ discover: new Set([
34
+ "q",
35
+ "page",
36
+ "pageSize",
37
+ "category",
38
+ "auth-type",
39
+ "min-price",
40
+ "max-price",
41
+ ]),
42
+ consume: new Set(["endpoint-id", "params", "payment-rail", "network"]),
28
43
  account: new Set([]),
29
44
  login: new Set(["key"]),
30
45
  };
@@ -35,10 +50,9 @@ const main = async (): Promise<void> => {
35
50
  return;
36
51
  }
37
52
  const parsed = parseArgs(process.argv.slice(2));
38
- const apiBase = String(
39
- parsed.options["api-base"] ??
40
- process.env.LIDIAN_API_BASE ??
41
- DEFAULT_API_BASE,
53
+ const apiBase = resolveApiBase(
54
+ asString(parsed.options["api-base"]),
55
+ asEnvironment(asString(parsed.options.env) ?? process.env.LIDIAN_ENV),
42
56
  );
43
57
  const asJson = Boolean(parsed.options.json);
44
58
  const http = createHttpClient(apiBase);
@@ -54,41 +68,54 @@ const main = async (): Promise<void> => {
54
68
  }
55
69
  return;
56
70
  }
57
- case "query": {
71
+ case "discover": {
58
72
  const apiKey = await resolveApiKey(asString(parsed.options["api-key"]));
59
73
  const qValue = asString(parsed.options.q);
60
74
  if (!qValue) {
61
- throw new CliError("Missing --q for query command.");
75
+ throw new CliError("Missing --q for discover command.");
62
76
  }
63
77
  const page = toInt(asString(parsed.options.page), 1);
64
78
  const pageSize = toInt(asString(parsed.options.pageSize), 1);
65
- const result = await runQueryCommand(http, apiKey, {
79
+ const authType = asAuthType(asString(parsed.options["auth-type"]));
80
+ const minPrice = toNumber(
81
+ asString(parsed.options["min-price"]),
82
+ "min-price",
83
+ );
84
+ const maxPrice = toNumber(
85
+ asString(parsed.options["max-price"]),
86
+ "max-price",
87
+ );
88
+ const result = await runDiscoverCommand(http, apiKey, {
66
89
  q: qValue,
67
90
  page,
68
91
  pageSize,
92
+ category: asString(parsed.options.category),
93
+ ...(authType ? { authType } : {}),
94
+ ...(typeof minPrice === "number" ? { minPrice } : {}),
95
+ ...(typeof maxPrice === "number" ? { maxPrice } : {}),
69
96
  });
70
- printQueryResult(result, asJson);
97
+ printDiscoverResult(result, asJson);
71
98
  return;
72
99
  }
73
- case "act": {
100
+ case "consume": {
74
101
  const apiKey = await resolveApiKey(asString(parsed.options["api-key"]));
75
102
  const endpointIdValue = asString(parsed.options["endpoint-id"]);
76
103
  if (!endpointIdValue) {
77
- throw new CliError("Missing --endpoint-id for act command.");
104
+ throw new CliError("Missing --endpoint-id for consume command.");
78
105
  }
79
106
  const paramsRaw = asString(parsed.options.params) ?? "{}";
80
107
  const params = parseJsonObject(paramsRaw, "--params");
81
108
  const paymentRail = asPaymentRail(
82
109
  asString(parsed.options["payment-rail"]) ?? "prepaid_credits",
83
110
  );
84
- const network = asString(parsed.options.network);
85
- const result = await runActCommand(http, apiKey, {
111
+ const network = asNetwork(asString(parsed.options.network));
112
+ const result = await runConsumeCommand(http, apiKey, {
86
113
  endpointId: endpointIdValue,
87
114
  params,
88
115
  paymentRail,
89
116
  ...(network ? { network } : {}),
90
117
  });
91
- printActResult(result, asJson);
118
+ printConsumeResult(result, asJson);
92
119
  return;
93
120
  }
94
121
  case "account": {
@@ -105,14 +132,14 @@ const main = async (): Promise<void> => {
105
132
  const parseArgs = (argv: string[]): ParsedArgs => {
106
133
  const command = argv[0];
107
134
  if (
108
- command !== "query" &&
109
- command !== "act" &&
135
+ command !== "discover" &&
136
+ command !== "consume" &&
110
137
  command !== "account" &&
111
138
  command !== "login"
112
139
  ) {
113
140
  printUsage();
114
141
  throw new CliError(
115
- "Invalid command. Use one of: login, query, act, account.",
142
+ "Invalid command. Use one of: login, discover, consume, account.",
116
143
  1,
117
144
  );
118
145
  }
@@ -132,6 +159,9 @@ const parseArgs = (argv: string[]): ParsedArgs => {
132
159
  }
133
160
  const next = argv[index + 1];
134
161
  if (!next || next.startsWith("--")) {
162
+ if (!BOOLEAN_OPTIONS.has(key)) {
163
+ throw new CliError(`Missing value for --${key}`);
164
+ }
135
165
  options[key] = true;
136
166
  index += 1;
137
167
  continue;
@@ -151,13 +181,23 @@ const printUsage = (): void => {
151
181
  print("Usage:");
152
182
  print(" lidian login [--key ld_...] [--json]");
153
183
  print(
154
- ' lidian query --q "<term>" [--page 1] [--pageSize 1..3] [--api-key <key>] [--json]',
184
+ ' lidian discover --q "<term>" [--page 1] [--pageSize 1..3] [--category <name>] [--auth-type none|api_key|bearer|basic|oauth2|custom]',
155
185
  );
156
186
  print(
157
- " lidian act --endpoint-id <uuid> --params '<json>' [--payment-rail prepaid_credits|x402] [--api-key <key>] [--json]",
187
+ " [--min-price <cents>] [--max-price <cents>] [--api-key <key>] [--env production|staging] [--api-base <url>] [--json]",
188
+ );
189
+ print(
190
+ " lidian consume --endpoint-id <uuid> --params '<json>' [--payment-rail prepaid_credits|x402] [--api-key <key>] [--env production|staging] [--api-base <url>] [--json]",
158
191
  );
159
192
  print(" [--network base|ethereum]");
160
- print(" lidian account [--api-key <key>] [--json]");
193
+ print(
194
+ " lidian account [--api-key <key>] [--env production|staging] [--api-base <url>] [--json]",
195
+ );
196
+ print("");
197
+ print("Env resolution:");
198
+ print(" --api-base > LIDIAN_API_BASE > --env > LIDIAN_ENV > production");
199
+ print(` production=${API_BASE_BY_ENV.production}`);
200
+ print(` staging=${API_BASE_BY_ENV.staging}`);
161
201
  };
162
202
 
163
203
  const asString = (value: string | boolean | undefined): string | undefined => {
@@ -168,17 +208,34 @@ const asString = (value: string | boolean | undefined): string | undefined => {
168
208
  const toInt = (value: string | undefined, fallback: number): number => {
169
209
  if (!value) return fallback;
170
210
  const parsed = Number.parseInt(value, 10);
171
- if (Number.isNaN(parsed)) {
211
+ if (Number.isNaN(parsed) || parsed < 1) {
172
212
  throw new CliError(`Invalid integer value: ${value}`);
173
213
  }
174
214
  return parsed;
175
215
  };
176
216
 
217
+ const toNumber = (
218
+ value: string | undefined,
219
+ flagName: string,
220
+ ): number | undefined => {
221
+ if (!value) return undefined;
222
+ const parsed = Number(value);
223
+ if (Number.isNaN(parsed)) {
224
+ throw new CliError(`Invalid --${flagName} value: ${value}`);
225
+ }
226
+ return parsed;
227
+ };
228
+
177
229
  const parseJsonObject = (
178
230
  value: string,
179
231
  flagName: string,
180
232
  ): Record<string, unknown> => {
181
- const parsed = JSON.parse(value) as unknown;
233
+ let parsed: unknown;
234
+ try {
235
+ parsed = JSON.parse(value) as unknown;
236
+ } catch {
237
+ throw new CliError(`${flagName} must be valid JSON.`);
238
+ }
182
239
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
183
240
  throw new CliError(`${flagName} must be a JSON object.`);
184
241
  }
@@ -190,4 +247,59 @@ const asPaymentRail = (value: string): PaymentRail => {
190
247
  throw new CliError("Invalid --payment-rail. Use prepaid_credits or x402.");
191
248
  };
192
249
 
250
+ const asAuthType = (
251
+ value: string | undefined,
252
+ ):
253
+ | "none"
254
+ | "api_key"
255
+ | "bearer"
256
+ | "basic"
257
+ | "oauth2"
258
+ | "custom"
259
+ | undefined => {
260
+ if (!value) return undefined;
261
+ const valid = new Set([
262
+ "none",
263
+ "api_key",
264
+ "bearer",
265
+ "basic",
266
+ "oauth2",
267
+ "custom",
268
+ ]);
269
+ if (valid.has(value)) {
270
+ return value as
271
+ | "none"
272
+ | "api_key"
273
+ | "bearer"
274
+ | "basic"
275
+ | "oauth2"
276
+ | "custom";
277
+ }
278
+ throw new CliError(
279
+ "Invalid --auth-type. Use none, api_key, bearer, basic, oauth2, or custom.",
280
+ );
281
+ };
282
+
283
+ const asNetwork = (value: string | undefined): string | undefined => {
284
+ if (!value) return undefined;
285
+ if (value === "base" || value === "ethereum") return value;
286
+ throw new CliError("Invalid --network. Use base or ethereum.");
287
+ };
288
+
289
+ const asEnvironment = (value: string | undefined): Environment | undefined => {
290
+ if (!value) return undefined;
291
+ if (value === "production" || value === "staging") return value;
292
+ throw new CliError("Invalid --env. Use production or staging.");
293
+ };
294
+
295
+ const resolveApiBase = (
296
+ cliApiBase: string | undefined,
297
+ environment: Environment | undefined,
298
+ ): string => {
299
+ if (cliApiBase) return cliApiBase;
300
+ if (process.env.LIDIAN_API_BASE) return process.env.LIDIAN_API_BASE;
301
+ if (environment) return API_BASE_BY_ENV[environment];
302
+ return DEFAULT_API_BASE;
303
+ };
304
+
193
305
  main().catch(fail);
package/src/lib/output.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  import type { AccountApiResponse } from "@/commands/account";
2
- import type { ActApiResponse, ActCommandResult } from "@/commands/act";
3
- import type { QueryApiResponse } from "@/commands/query";
2
+ import type {
3
+ ConsumeApiResponse,
4
+ ConsumeCommandResult,
5
+ } from "@/commands/consume";
6
+ import type { DiscoverApiResponse } from "@/commands/discover";
4
7
  import { CliError } from "@/lib/errors";
5
8
 
6
9
  export const print = (message: string): void => {
@@ -19,8 +22,8 @@ export const printResult = (result: unknown, asJson: boolean): void => {
19
22
  print(formatForHuman(result));
20
23
  };
21
24
 
22
- export const printQueryResult = (
23
- result: QueryApiResponse,
25
+ export const printDiscoverResult = (
26
+ result: DiscoverApiResponse,
24
27
  asJson: boolean,
25
28
  ): void => {
26
29
  if (asJson) {
@@ -39,13 +42,13 @@ export const printQueryResult = (
39
42
  ? ` confidence=${item.matchPercent.toFixed(1)}%`
40
43
  : "";
41
44
  print(
42
- `- ${item.name} (${item.id}) auth=${item.authType} cost=${item.defaultCostPerUse}c${confidence}`,
45
+ `- ${item.name} (${item.id}) auth=${item.authType} cost=${item.defaultCostPerUse}c (${formatUsd(item.defaultCostPerUse)})${confidence}`,
43
46
  );
44
47
  }
45
48
  };
46
49
 
47
- export const printActResult = (
48
- result: ActCommandResult,
50
+ export const printConsumeResult = (
51
+ result: ConsumeCommandResult,
49
52
  asJson: boolean,
50
53
  ): void => {
51
54
  if (asJson) {
@@ -69,12 +72,14 @@ export const printAccountResult = (
69
72
  return;
70
73
  }
71
74
  print(`Account: ${result.user.id}`);
72
- print(`Balance: ${result.balance.balance} credits`);
75
+ print(
76
+ `Balance: ${result.balance.balance} cents (${formatUsd(result.balance.balance)})`,
77
+ );
73
78
  };
74
79
 
75
- const printExecutionResult = (result: ActApiResponse): void => {
80
+ const printExecutionResult = (result: ConsumeApiResponse): void => {
76
81
  print(
77
- `Execution succeeded. Spent=${result.credits.spent} balance=${result.credits.balance}`,
82
+ `Execution succeeded. Spent=${result.credits.spent} cents (${formatUsd(result.credits.spent)}) balance=${result.credits.balance} cents (${formatUsd(result.credits.balance)})`,
78
83
  );
79
84
  print(JSON.stringify(result.data, null, 2));
80
85
  };
@@ -86,6 +91,10 @@ const formatForHuman = (value: unknown): string => {
86
91
  return JSON.stringify(value, null, 2);
87
92
  };
88
93
 
94
+ const formatUsd = (cents: number): string => {
95
+ return `$${(cents / 100).toFixed(2)}`;
96
+ };
97
+
89
98
  export const fail = (error: unknown): never => {
90
99
  if (error instanceof CliError) {
91
100
  printError(`Error: ${error.message}`);
@@ -1,45 +0,0 @@
1
- import { CliError } from "@/lib/errors";
2
- import type { HttpClient } from "@/lib/http";
3
-
4
- export interface QueryCommandInput {
5
- q: string;
6
- page: number;
7
- pageSize: number;
8
- }
9
-
10
- export interface QueryApiResponse {
11
- items: Array<{
12
- id: string;
13
- merchantId: string | null;
14
- name: string;
15
- description: string | null;
16
- endpointBase: string;
17
- authType: "none" | "api_key" | "bearer" | "basic" | "oauth2" | "custom";
18
- defaultCostPerUse: number;
19
- isActive: boolean;
20
- openapiSpecUrl: string | null;
21
- createdAt: string;
22
- updatedAt: string;
23
- matchScore?: number;
24
- matchPercent?: number;
25
- }>;
26
- total: number;
27
- page: number;
28
- pageSize: number;
29
- }
30
-
31
- export const runQueryCommand = async (
32
- http: HttpClient,
33
- apiKey: string,
34
- input: QueryCommandInput,
35
- ): Promise<QueryApiResponse> => {
36
- if (input.pageSize < 1 || input.pageSize > 3) {
37
- throw new CliError("pageSize must be between 1 and 3.");
38
- }
39
- const params = new URLSearchParams({
40
- q: input.q,
41
- page: String(input.page),
42
- pageSize: String(input.pageSize),
43
- });
44
- return http.get<QueryApiResponse>(`/v1/query?${params.toString()}`, apiKey);
45
- };