@lidianai/cli 0.1.0 → 0.1.2
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 +26 -0
- package/README.md +16 -9
- package/bun.lock +43 -0
- package/dist/index.js +194 -43
- package/package.json +1 -1
- package/src/commands/{act.ts → consume.ts} +12 -11
- package/src/commands/discover.ts +59 -0
- package/src/commands/feedback.ts +44 -0
- package/src/index.ts +188 -29
- package/src/lib/output.ts +37 -10
- package/src/commands/query.ts +0 -45
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,43 @@ Bun CLI for Lidian core REST endpoints.
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
|
|
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
|
|
17
|
-
lidian
|
|
18
|
-
lidian
|
|
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 feedback --execution-id <uuid> --rank <0..10> [--feedback "<text>"] [--api-key <key>] [--env production|staging] [--api-base <url>] [--json]
|
|
17
|
+
lidian account [--api-key <key>] [--env production|staging] [--api-base <url>] [--json]
|
|
19
18
|
lidian login [--key ld_...] [--json]
|
|
20
19
|
lidian --help
|
|
21
20
|
```
|
|
22
21
|
|
|
23
|
-
`
|
|
22
|
+
`discover` returns API matches (`items[]`) from `/v1/discover` with metadata and confidence fields (`matchScore`, `matchPercent`).
|
|
24
23
|
|
|
25
24
|
## Auth
|
|
26
25
|
|
|
27
26
|
- Store key locally: `lidian login --key ld_...` (writes `~/.lidian/config.json`)
|
|
28
27
|
- Or run `lidian login` and paste your key after browser auth flow.
|
|
29
28
|
- Resolution order: `--api-key` -> `LIDIAN_API_KEY` -> `~/.lidian/config.json`.
|
|
30
|
-
-
|
|
29
|
+
- API base resolution order:
|
|
30
|
+
- `--api-base`
|
|
31
|
+
- `LIDIAN_API_BASE`
|
|
32
|
+
- `--env` (`production` or `staging`)
|
|
33
|
+
- `LIDIAN_ENV` (`production` or `staging`)
|
|
34
|
+
- default `https://api.lidian.ai`
|
|
31
35
|
|
|
32
36
|
## x402
|
|
33
37
|
|
|
34
38
|
When `--payment-rail x402` is used, CLI performs:
|
|
35
39
|
1. `POST /v1/payments/requirements`
|
|
36
40
|
2. `POST /v1/payments/verify`
|
|
37
|
-
3. `POST /v1/
|
|
41
|
+
3. `POST /v1/consume` (returns `executionId`)
|
|
42
|
+
|
|
43
|
+
Submit feedback later:
|
|
44
|
+
4. `POST /v1/consume/feedback` with `executionId`, `rank`, optional `feedback`
|
|
38
45
|
|
|
39
46
|
## Dev
|
|
40
47
|
|
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/
|
|
28
|
-
var
|
|
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/
|
|
35
|
+
const execution2 = await http.post("/v1/consume", input, apiKey);
|
|
36
36
|
return {
|
|
37
37
|
execution: execution2,
|
|
38
38
|
payment: {
|
|
@@ -41,13 +41,52 @@ var runActCommand = async (http, apiKey, input) => {
|
|
|
41
41
|
}
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
|
-
const execution = await http.post("/v1/
|
|
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
|
+
|
|
73
|
+
// src/commands/feedback.ts
|
|
74
|
+
var runFeedbackCommand = async (http, apiKey, input) => {
|
|
75
|
+
if (!isUuid2(input.executionId)) {
|
|
76
|
+
throw new CliError("executionId must be a valid UUID.");
|
|
77
|
+
}
|
|
78
|
+
if (!Number.isInteger(input.rank) || input.rank < 0 || input.rank > 10) {
|
|
79
|
+
throw new CliError("rank must be an integer between 0 and 10.");
|
|
80
|
+
}
|
|
81
|
+
if (typeof input.feedback === "string" && input.feedback.length > 1000) {
|
|
82
|
+
throw new CliError("feedback cannot exceed 1000 characters.");
|
|
83
|
+
}
|
|
84
|
+
return http.post("/v1/consume/feedback", input, apiKey);
|
|
85
|
+
};
|
|
86
|
+
var isUuid2 = (value) => {
|
|
87
|
+
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);
|
|
88
|
+
};
|
|
89
|
+
|
|
51
90
|
// src/commands/login.ts
|
|
52
91
|
import { stdin, stdout } from "process";
|
|
53
92
|
import { createInterface } from "readline/promises";
|
|
@@ -93,7 +132,7 @@ var printResult = (result, asJson) => {
|
|
|
93
132
|
}
|
|
94
133
|
print(formatForHuman(result));
|
|
95
134
|
};
|
|
96
|
-
var
|
|
135
|
+
var printDiscoverResult = (result, asJson) => {
|
|
97
136
|
if (asJson) {
|
|
98
137
|
printResult(result, true);
|
|
99
138
|
return;
|
|
@@ -105,10 +144,10 @@ var printQueryResult = (result, asJson) => {
|
|
|
105
144
|
print(`Found ${result.items.length} of ${result.total} APIs (page ${result.page}).`);
|
|
106
145
|
for (const item of result.items) {
|
|
107
146
|
const confidence = item.matchPercent ? ` confidence=${item.matchPercent.toFixed(1)}%` : "";
|
|
108
|
-
print(`- ${item.name} (${item.id}) auth=${item.authType} cost=${item.defaultCostPerUse}c${confidence}`);
|
|
147
|
+
print(`- ${item.name} (${item.id}) auth=${item.authType} cost=${item.defaultCostPerUse}c (${formatUsd(item.defaultCostPerUse)})${confidence}`);
|
|
109
148
|
}
|
|
110
149
|
};
|
|
111
|
-
var
|
|
150
|
+
var printConsumeResult = (result, asJson) => {
|
|
112
151
|
if (asJson) {
|
|
113
152
|
printResult(result.execution, true);
|
|
114
153
|
return;
|
|
@@ -124,10 +163,21 @@ var printAccountResult = (result, asJson) => {
|
|
|
124
163
|
return;
|
|
125
164
|
}
|
|
126
165
|
print(`Account: ${result.user.id}`);
|
|
127
|
-
print(`Balance: ${result.balance.balance}
|
|
166
|
+
print(`Balance: ${result.balance.balance} cents (${formatUsd(result.balance.balance)})`);
|
|
167
|
+
};
|
|
168
|
+
var printFeedbackResult = (result, asJson) => {
|
|
169
|
+
if (asJson) {
|
|
170
|
+
printResult(result, true);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const feedback = result.feedback ?? "<none>";
|
|
174
|
+
print(`Feedback saved for execution=${result.executionId} rank=${result.rank} submittedBy=${result.submittedBy}`);
|
|
175
|
+
print(`Updated at: ${result.updatedAt}`);
|
|
176
|
+
print(`Comment: ${feedback}`);
|
|
128
177
|
};
|
|
129
178
|
var printExecutionResult = (result) => {
|
|
130
|
-
print(`Execution
|
|
179
|
+
print(`Execution ID: ${result.executionId}`);
|
|
180
|
+
print(`Execution succeeded. Spent=${result.credits.spent} cents (${formatUsd(result.credits.spent)}) balance=${result.credits.balance} cents (${formatUsd(result.credits.balance)})`);
|
|
131
181
|
print(JSON.stringify(result.data, null, 2));
|
|
132
182
|
};
|
|
133
183
|
var formatForHuman = (value) => {
|
|
@@ -136,6 +186,9 @@ var formatForHuman = (value) => {
|
|
|
136
186
|
}
|
|
137
187
|
return JSON.stringify(value, null, 2);
|
|
138
188
|
};
|
|
189
|
+
var formatUsd = (cents) => {
|
|
190
|
+
return `$${(cents / 100).toFixed(2)}`;
|
|
191
|
+
};
|
|
139
192
|
var fail = (error) => {
|
|
140
193
|
if (error instanceof CliError) {
|
|
141
194
|
printError(`Error: ${error.message}`);
|
|
@@ -198,19 +251,6 @@ var openUrl = (url) => {
|
|
|
198
251
|
} catch {}
|
|
199
252
|
};
|
|
200
253
|
|
|
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
254
|
// src/lib/auth.ts
|
|
215
255
|
var resolveApiKey = async (argsApiKey) => {
|
|
216
256
|
const config = await readConfig();
|
|
@@ -264,10 +304,24 @@ var handleResponse = async (response) => {
|
|
|
264
304
|
|
|
265
305
|
// src/index.ts
|
|
266
306
|
var DEFAULT_API_BASE = "https://api.lidian.ai";
|
|
267
|
-
var
|
|
307
|
+
var API_BASE_BY_ENV = {
|
|
308
|
+
production: "https://api.lidian.ai",
|
|
309
|
+
staging: "https://staging-api.lidian.ai"
|
|
310
|
+
};
|
|
311
|
+
var GLOBAL_OPTIONS = new Set(["api-key", "api-base", "env", "json", "help"]);
|
|
312
|
+
var BOOLEAN_OPTIONS = new Set(["json", "help"]);
|
|
268
313
|
var COMMAND_OPTIONS = {
|
|
269
|
-
|
|
270
|
-
|
|
314
|
+
discover: new Set([
|
|
315
|
+
"q",
|
|
316
|
+
"page",
|
|
317
|
+
"pageSize",
|
|
318
|
+
"category",
|
|
319
|
+
"auth-type",
|
|
320
|
+
"min-price",
|
|
321
|
+
"max-price"
|
|
322
|
+
]),
|
|
323
|
+
consume: new Set(["endpoint-id", "params", "payment-rail", "network"]),
|
|
324
|
+
feedback: new Set(["execution-id", "rank", "feedback"]),
|
|
271
325
|
account: new Set([]),
|
|
272
326
|
login: new Set(["key"])
|
|
273
327
|
};
|
|
@@ -277,7 +331,7 @@ var main = async () => {
|
|
|
277
331
|
return;
|
|
278
332
|
}
|
|
279
333
|
const parsed = parseArgs(process.argv.slice(2));
|
|
280
|
-
const apiBase =
|
|
334
|
+
const apiBase = resolveApiBase(asString(parsed.options["api-base"]), asEnvironment(asString(parsed.options.env) ?? process.env.LIDIAN_ENV));
|
|
281
335
|
const asJson = Boolean(parsed.options.json);
|
|
282
336
|
const http = createHttpClient(apiBase);
|
|
283
337
|
switch (parsed.command) {
|
|
@@ -291,39 +345,46 @@ var main = async () => {
|
|
|
291
345
|
}
|
|
292
346
|
return;
|
|
293
347
|
}
|
|
294
|
-
case "
|
|
348
|
+
case "discover": {
|
|
295
349
|
const apiKey = await resolveApiKey(asString(parsed.options["api-key"]));
|
|
296
350
|
const qValue = asString(parsed.options.q);
|
|
297
351
|
if (!qValue) {
|
|
298
|
-
throw new CliError("Missing --q for
|
|
352
|
+
throw new CliError("Missing --q for discover command.");
|
|
299
353
|
}
|
|
300
354
|
const page = toInt(asString(parsed.options.page), 1);
|
|
301
355
|
const pageSize = toInt(asString(parsed.options.pageSize), 1);
|
|
302
|
-
const
|
|
356
|
+
const authType = asAuthType(asString(parsed.options["auth-type"]));
|
|
357
|
+
const minPrice = toNumber(asString(parsed.options["min-price"]), "min-price");
|
|
358
|
+
const maxPrice = toNumber(asString(parsed.options["max-price"]), "max-price");
|
|
359
|
+
const result = await runDiscoverCommand(http, apiKey, {
|
|
303
360
|
q: qValue,
|
|
304
361
|
page,
|
|
305
|
-
pageSize
|
|
362
|
+
pageSize,
|
|
363
|
+
category: asString(parsed.options.category),
|
|
364
|
+
...authType ? { authType } : {},
|
|
365
|
+
...typeof minPrice === "number" ? { minPrice } : {},
|
|
366
|
+
...typeof maxPrice === "number" ? { maxPrice } : {}
|
|
306
367
|
});
|
|
307
|
-
|
|
368
|
+
printDiscoverResult(result, asJson);
|
|
308
369
|
return;
|
|
309
370
|
}
|
|
310
|
-
case "
|
|
371
|
+
case "consume": {
|
|
311
372
|
const apiKey = await resolveApiKey(asString(parsed.options["api-key"]));
|
|
312
373
|
const endpointIdValue = asString(parsed.options["endpoint-id"]);
|
|
313
374
|
if (!endpointIdValue) {
|
|
314
|
-
throw new CliError("Missing --endpoint-id for
|
|
375
|
+
throw new CliError("Missing --endpoint-id for consume command.");
|
|
315
376
|
}
|
|
316
377
|
const paramsRaw = asString(parsed.options.params) ?? "{}";
|
|
317
378
|
const params = parseJsonObject(paramsRaw, "--params");
|
|
318
379
|
const paymentRail = asPaymentRail(asString(parsed.options["payment-rail"]) ?? "prepaid_credits");
|
|
319
|
-
const network = asString(parsed.options.network);
|
|
320
|
-
const result = await
|
|
380
|
+
const network = asNetwork(asString(parsed.options.network));
|
|
381
|
+
const result = await runConsumeCommand(http, apiKey, {
|
|
321
382
|
endpointId: endpointIdValue,
|
|
322
383
|
params,
|
|
323
384
|
paymentRail,
|
|
324
385
|
...network ? { network } : {}
|
|
325
386
|
});
|
|
326
|
-
|
|
387
|
+
printConsumeResult(result, asJson);
|
|
327
388
|
return;
|
|
328
389
|
}
|
|
329
390
|
case "account": {
|
|
@@ -332,15 +393,35 @@ var main = async () => {
|
|
|
332
393
|
printAccountResult(result, asJson);
|
|
333
394
|
return;
|
|
334
395
|
}
|
|
396
|
+
case "feedback": {
|
|
397
|
+
const apiKey = await resolveApiKey(asString(parsed.options["api-key"]));
|
|
398
|
+
const executionId = asString(parsed.options["execution-id"]);
|
|
399
|
+
if (!executionId) {
|
|
400
|
+
throw new CliError("Missing --execution-id for feedback command.");
|
|
401
|
+
}
|
|
402
|
+
const rankRaw = asString(parsed.options.rank);
|
|
403
|
+
if (!rankRaw) {
|
|
404
|
+
throw new CliError("Missing --rank for feedback command.");
|
|
405
|
+
}
|
|
406
|
+
const rank = toIntInRange(rankRaw, "rank", 0, 10);
|
|
407
|
+
const feedback = asString(parsed.options.feedback);
|
|
408
|
+
const result = await runFeedbackCommand(http, apiKey, {
|
|
409
|
+
executionId,
|
|
410
|
+
rank,
|
|
411
|
+
...feedback ? { feedback } : {}
|
|
412
|
+
});
|
|
413
|
+
printFeedbackResult(result, asJson);
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
335
416
|
default:
|
|
336
417
|
throw new CliError("Unknown command.", 1);
|
|
337
418
|
}
|
|
338
419
|
};
|
|
339
420
|
var parseArgs = (argv) => {
|
|
340
421
|
const command = argv[0];
|
|
341
|
-
if (command !== "
|
|
422
|
+
if (command !== "discover" && command !== "consume" && command !== "feedback" && command !== "account" && command !== "login") {
|
|
342
423
|
printUsage();
|
|
343
|
-
throw new CliError("Invalid command. Use one of: login,
|
|
424
|
+
throw new CliError("Invalid command. Use one of: login, discover, consume, feedback, account.", 1);
|
|
344
425
|
}
|
|
345
426
|
const options = {};
|
|
346
427
|
let index = 1;
|
|
@@ -356,6 +437,9 @@ var parseArgs = (argv) => {
|
|
|
356
437
|
}
|
|
357
438
|
const next = argv[index + 1];
|
|
358
439
|
if (!next || next.startsWith("--")) {
|
|
440
|
+
if (!BOOLEAN_OPTIONS.has(key)) {
|
|
441
|
+
throw new CliError(`Missing value for --${key}`);
|
|
442
|
+
}
|
|
359
443
|
options[key] = true;
|
|
360
444
|
index += 1;
|
|
361
445
|
continue;
|
|
@@ -371,10 +455,17 @@ var parseArgs = (argv) => {
|
|
|
371
455
|
var printUsage = () => {
|
|
372
456
|
print("Usage:");
|
|
373
457
|
print(" lidian login [--key ld_...] [--json]");
|
|
374
|
-
print(' lidian
|
|
375
|
-
print("
|
|
458
|
+
print(' lidian discover --q "<term>" [--page 1] [--pageSize 1..3] [--category <name>] [--auth-type none|api_key|bearer|basic|oauth2|custom]');
|
|
459
|
+
print(" [--min-price <cents>] [--max-price <cents>] [--api-key <key>] [--env production|staging] [--api-base <url>] [--json]");
|
|
460
|
+
print(" lidian consume --endpoint-id <uuid> --params '<json>' [--payment-rail prepaid_credits|x402] [--api-key <key>] [--env production|staging] [--api-base <url>] [--json]");
|
|
376
461
|
print(" [--network base|ethereum]");
|
|
377
|
-
print(
|
|
462
|
+
print(' lidian feedback --execution-id <uuid> --rank <0..10> [--feedback "<text>"] [--api-key <key>] [--env production|staging] [--api-base <url>] [--json]');
|
|
463
|
+
print(" lidian account [--api-key <key>] [--env production|staging] [--api-base <url>] [--json]");
|
|
464
|
+
print("");
|
|
465
|
+
print("Env resolution:");
|
|
466
|
+
print(" --api-base > LIDIAN_API_BASE > --env > LIDIAN_ENV > production");
|
|
467
|
+
print(` production=${API_BASE_BY_ENV.production}`);
|
|
468
|
+
print(` staging=${API_BASE_BY_ENV.staging}`);
|
|
378
469
|
};
|
|
379
470
|
var asString = (value) => {
|
|
380
471
|
if (typeof value === "string")
|
|
@@ -385,13 +476,34 @@ var toInt = (value, fallback) => {
|
|
|
385
476
|
if (!value)
|
|
386
477
|
return fallback;
|
|
387
478
|
const parsed = Number.parseInt(value, 10);
|
|
388
|
-
if (Number.isNaN(parsed)) {
|
|
479
|
+
if (Number.isNaN(parsed) || parsed < 1) {
|
|
389
480
|
throw new CliError(`Invalid integer value: ${value}`);
|
|
390
481
|
}
|
|
391
482
|
return parsed;
|
|
392
483
|
};
|
|
484
|
+
var toIntInRange = (value, flagName, min, max) => {
|
|
485
|
+
const parsed = Number.parseInt(value, 10);
|
|
486
|
+
if (Number.isNaN(parsed) || String(parsed) !== value || parsed < min || parsed > max) {
|
|
487
|
+
throw new CliError(`Invalid --${flagName} value: ${value}. Expected integer ${min}..${max}.`);
|
|
488
|
+
}
|
|
489
|
+
return parsed;
|
|
490
|
+
};
|
|
491
|
+
var toNumber = (value, flagName) => {
|
|
492
|
+
if (!value)
|
|
493
|
+
return;
|
|
494
|
+
const parsed = Number(value);
|
|
495
|
+
if (Number.isNaN(parsed)) {
|
|
496
|
+
throw new CliError(`Invalid --${flagName} value: ${value}`);
|
|
497
|
+
}
|
|
498
|
+
return parsed;
|
|
499
|
+
};
|
|
393
500
|
var parseJsonObject = (value, flagName) => {
|
|
394
|
-
|
|
501
|
+
let parsed;
|
|
502
|
+
try {
|
|
503
|
+
parsed = JSON.parse(value);
|
|
504
|
+
} catch {
|
|
505
|
+
throw new CliError(`${flagName} must be valid JSON.`);
|
|
506
|
+
}
|
|
395
507
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
396
508
|
throw new CliError(`${flagName} must be a JSON object.`);
|
|
397
509
|
}
|
|
@@ -402,4 +514,43 @@ var asPaymentRail = (value) => {
|
|
|
402
514
|
return value;
|
|
403
515
|
throw new CliError("Invalid --payment-rail. Use prepaid_credits or x402.");
|
|
404
516
|
};
|
|
517
|
+
var asAuthType = (value) => {
|
|
518
|
+
if (!value)
|
|
519
|
+
return;
|
|
520
|
+
const valid = new Set([
|
|
521
|
+
"none",
|
|
522
|
+
"api_key",
|
|
523
|
+
"bearer",
|
|
524
|
+
"basic",
|
|
525
|
+
"oauth2",
|
|
526
|
+
"custom"
|
|
527
|
+
]);
|
|
528
|
+
if (valid.has(value)) {
|
|
529
|
+
return value;
|
|
530
|
+
}
|
|
531
|
+
throw new CliError("Invalid --auth-type. Use none, api_key, bearer, basic, oauth2, or custom.");
|
|
532
|
+
};
|
|
533
|
+
var asNetwork = (value) => {
|
|
534
|
+
if (!value)
|
|
535
|
+
return;
|
|
536
|
+
if (value === "base" || value === "ethereum")
|
|
537
|
+
return value;
|
|
538
|
+
throw new CliError("Invalid --network. Use base or ethereum.");
|
|
539
|
+
};
|
|
540
|
+
var asEnvironment = (value) => {
|
|
541
|
+
if (!value)
|
|
542
|
+
return;
|
|
543
|
+
if (value === "production" || value === "staging")
|
|
544
|
+
return value;
|
|
545
|
+
throw new CliError("Invalid --env. Use production or staging.");
|
|
546
|
+
};
|
|
547
|
+
var resolveApiBase = (cliApiBase, environment) => {
|
|
548
|
+
if (cliApiBase)
|
|
549
|
+
return cliApiBase;
|
|
550
|
+
if (process.env.LIDIAN_API_BASE)
|
|
551
|
+
return process.env.LIDIAN_API_BASE;
|
|
552
|
+
if (environment)
|
|
553
|
+
return API_BASE_BY_ENV[environment];
|
|
554
|
+
return DEFAULT_API_BASE;
|
|
555
|
+
};
|
|
405
556
|
main().catch(fail);
|
package/package.json
CHANGED
|
@@ -8,14 +8,15 @@ import {
|
|
|
8
8
|
|
|
9
9
|
export type PaymentRail = "prepaid_credits" | "x402";
|
|
10
10
|
|
|
11
|
-
export interface
|
|
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
|
|
18
|
+
export interface ConsumeApiResponse {
|
|
19
|
+
executionId: string;
|
|
19
20
|
data: unknown;
|
|
20
21
|
credits: {
|
|
21
22
|
spent: number;
|
|
@@ -23,19 +24,19 @@ export interface ActApiResponse {
|
|
|
23
24
|
};
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
export interface
|
|
27
|
-
execution:
|
|
27
|
+
export interface ConsumeCommandResult {
|
|
28
|
+
execution: ConsumeApiResponse;
|
|
28
29
|
payment?: {
|
|
29
30
|
requirements: PaymentRequirementsResponse;
|
|
30
31
|
verified: boolean;
|
|
31
32
|
};
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
export const
|
|
35
|
+
export const runConsumeCommand = async (
|
|
35
36
|
http: HttpClient,
|
|
36
37
|
apiKey: string,
|
|
37
|
-
input:
|
|
38
|
-
): Promise<
|
|
38
|
+
input: ConsumeCommandInput,
|
|
39
|
+
): Promise<ConsumeCommandResult> => {
|
|
39
40
|
if (!isUuid(input.endpointId)) {
|
|
40
41
|
throw new CliError("endpointId must be a valid UUID.");
|
|
41
42
|
}
|
|
@@ -53,8 +54,8 @@ export const runActCommand = async (
|
|
|
53
54
|
requirements.payTo,
|
|
54
55
|
);
|
|
55
56
|
|
|
56
|
-
const execution = await http.post<
|
|
57
|
-
"/v1/
|
|
57
|
+
const execution = await http.post<ConsumeApiResponse, ConsumeCommandInput>(
|
|
58
|
+
"/v1/consume",
|
|
58
59
|
input,
|
|
59
60
|
apiKey,
|
|
60
61
|
);
|
|
@@ -68,8 +69,8 @@ export const runActCommand = async (
|
|
|
68
69
|
};
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
const execution = await http.post<
|
|
72
|
-
"/v1/
|
|
72
|
+
const execution = await http.post<ConsumeApiResponse, ConsumeCommandInput>(
|
|
73
|
+
"/v1/consume",
|
|
73
74
|
input,
|
|
74
75
|
apiKey,
|
|
75
76
|
);
|
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { CliError } from "@/lib/errors";
|
|
2
|
+
import type { HttpClient } from "@/lib/http";
|
|
3
|
+
|
|
4
|
+
export interface FeedbackCommandInput {
|
|
5
|
+
executionId: string;
|
|
6
|
+
rank: number;
|
|
7
|
+
feedback?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface FeedbackApiResponse {
|
|
11
|
+
executionId: string;
|
|
12
|
+
rank: number;
|
|
13
|
+
feedback: string | null;
|
|
14
|
+
submittedBy: "agent" | "human";
|
|
15
|
+
updatedAt: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const runFeedbackCommand = async (
|
|
19
|
+
http: HttpClient,
|
|
20
|
+
apiKey: string,
|
|
21
|
+
input: FeedbackCommandInput,
|
|
22
|
+
): Promise<FeedbackApiResponse> => {
|
|
23
|
+
if (!isUuid(input.executionId)) {
|
|
24
|
+
throw new CliError("executionId must be a valid UUID.");
|
|
25
|
+
}
|
|
26
|
+
if (!Number.isInteger(input.rank) || input.rank < 0 || input.rank > 10) {
|
|
27
|
+
throw new CliError("rank must be an integer between 0 and 10.");
|
|
28
|
+
}
|
|
29
|
+
if (typeof input.feedback === "string" && input.feedback.length > 1000) {
|
|
30
|
+
throw new CliError("feedback cannot exceed 1000 characters.");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return http.post<FeedbackApiResponse, FeedbackCommandInput>(
|
|
34
|
+
"/v1/consume/feedback",
|
|
35
|
+
input,
|
|
36
|
+
apiKey,
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const isUuid = (value: string): boolean => {
|
|
41
|
+
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(
|
|
42
|
+
value,
|
|
43
|
+
);
|
|
44
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
3
|
import { runAccountCommand } from "@/commands/account";
|
|
4
|
-
import { type PaymentRail,
|
|
4
|
+
import { type PaymentRail, runConsumeCommand } from "@/commands/consume";
|
|
5
|
+
import { runDiscoverCommand } from "@/commands/discover";
|
|
6
|
+
import { runFeedbackCommand } from "@/commands/feedback";
|
|
5
7
|
import { runLoginCommand } from "@/commands/login";
|
|
6
|
-
import { runQueryCommand } from "@/commands/query";
|
|
7
8
|
import { resolveApiKey } from "@/lib/auth";
|
|
8
9
|
import { CliError } from "@/lib/errors";
|
|
9
10
|
import { createHttpClient } from "@/lib/http";
|
|
@@ -11,20 +12,37 @@ import {
|
|
|
11
12
|
fail,
|
|
12
13
|
print,
|
|
13
14
|
printAccountResult,
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
printConsumeResult,
|
|
16
|
+
printDiscoverResult,
|
|
17
|
+
printFeedbackResult,
|
|
16
18
|
} from "@/lib/output";
|
|
17
19
|
|
|
18
20
|
interface ParsedArgs {
|
|
19
|
-
command: "
|
|
21
|
+
command: "discover" | "consume" | "feedback" | "account" | "login";
|
|
20
22
|
options: Record<string, string | boolean>;
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
const DEFAULT_API_BASE = "https://api.lidian.ai";
|
|
24
|
-
const
|
|
26
|
+
const API_BASE_BY_ENV = {
|
|
27
|
+
production: "https://api.lidian.ai",
|
|
28
|
+
staging: "https://staging-api.lidian.ai",
|
|
29
|
+
} as const;
|
|
30
|
+
type Environment = keyof typeof API_BASE_BY_ENV;
|
|
31
|
+
|
|
32
|
+
const GLOBAL_OPTIONS = new Set(["api-key", "api-base", "env", "json", "help"]);
|
|
33
|
+
const BOOLEAN_OPTIONS = new Set(["json", "help"]);
|
|
25
34
|
const COMMAND_OPTIONS: Record<ParsedArgs["command"], Set<string>> = {
|
|
26
|
-
|
|
27
|
-
|
|
35
|
+
discover: new Set([
|
|
36
|
+
"q",
|
|
37
|
+
"page",
|
|
38
|
+
"pageSize",
|
|
39
|
+
"category",
|
|
40
|
+
"auth-type",
|
|
41
|
+
"min-price",
|
|
42
|
+
"max-price",
|
|
43
|
+
]),
|
|
44
|
+
consume: new Set(["endpoint-id", "params", "payment-rail", "network"]),
|
|
45
|
+
feedback: new Set(["execution-id", "rank", "feedback"]),
|
|
28
46
|
account: new Set([]),
|
|
29
47
|
login: new Set(["key"]),
|
|
30
48
|
};
|
|
@@ -35,10 +53,9 @@ const main = async (): Promise<void> => {
|
|
|
35
53
|
return;
|
|
36
54
|
}
|
|
37
55
|
const parsed = parseArgs(process.argv.slice(2));
|
|
38
|
-
const apiBase =
|
|
39
|
-
parsed.options["api-base"]
|
|
40
|
-
|
|
41
|
-
DEFAULT_API_BASE,
|
|
56
|
+
const apiBase = resolveApiBase(
|
|
57
|
+
asString(parsed.options["api-base"]),
|
|
58
|
+
asEnvironment(asString(parsed.options.env) ?? process.env.LIDIAN_ENV),
|
|
42
59
|
);
|
|
43
60
|
const asJson = Boolean(parsed.options.json);
|
|
44
61
|
const http = createHttpClient(apiBase);
|
|
@@ -54,41 +71,54 @@ const main = async (): Promise<void> => {
|
|
|
54
71
|
}
|
|
55
72
|
return;
|
|
56
73
|
}
|
|
57
|
-
case "
|
|
74
|
+
case "discover": {
|
|
58
75
|
const apiKey = await resolveApiKey(asString(parsed.options["api-key"]));
|
|
59
76
|
const qValue = asString(parsed.options.q);
|
|
60
77
|
if (!qValue) {
|
|
61
|
-
throw new CliError("Missing --q for
|
|
78
|
+
throw new CliError("Missing --q for discover command.");
|
|
62
79
|
}
|
|
63
80
|
const page = toInt(asString(parsed.options.page), 1);
|
|
64
81
|
const pageSize = toInt(asString(parsed.options.pageSize), 1);
|
|
65
|
-
const
|
|
82
|
+
const authType = asAuthType(asString(parsed.options["auth-type"]));
|
|
83
|
+
const minPrice = toNumber(
|
|
84
|
+
asString(parsed.options["min-price"]),
|
|
85
|
+
"min-price",
|
|
86
|
+
);
|
|
87
|
+
const maxPrice = toNumber(
|
|
88
|
+
asString(parsed.options["max-price"]),
|
|
89
|
+
"max-price",
|
|
90
|
+
);
|
|
91
|
+
const result = await runDiscoverCommand(http, apiKey, {
|
|
66
92
|
q: qValue,
|
|
67
93
|
page,
|
|
68
94
|
pageSize,
|
|
95
|
+
category: asString(parsed.options.category),
|
|
96
|
+
...(authType ? { authType } : {}),
|
|
97
|
+
...(typeof minPrice === "number" ? { minPrice } : {}),
|
|
98
|
+
...(typeof maxPrice === "number" ? { maxPrice } : {}),
|
|
69
99
|
});
|
|
70
|
-
|
|
100
|
+
printDiscoverResult(result, asJson);
|
|
71
101
|
return;
|
|
72
102
|
}
|
|
73
|
-
case "
|
|
103
|
+
case "consume": {
|
|
74
104
|
const apiKey = await resolveApiKey(asString(parsed.options["api-key"]));
|
|
75
105
|
const endpointIdValue = asString(parsed.options["endpoint-id"]);
|
|
76
106
|
if (!endpointIdValue) {
|
|
77
|
-
throw new CliError("Missing --endpoint-id for
|
|
107
|
+
throw new CliError("Missing --endpoint-id for consume command.");
|
|
78
108
|
}
|
|
79
109
|
const paramsRaw = asString(parsed.options.params) ?? "{}";
|
|
80
110
|
const params = parseJsonObject(paramsRaw, "--params");
|
|
81
111
|
const paymentRail = asPaymentRail(
|
|
82
112
|
asString(parsed.options["payment-rail"]) ?? "prepaid_credits",
|
|
83
113
|
);
|
|
84
|
-
const network = asString(parsed.options.network);
|
|
85
|
-
const result = await
|
|
114
|
+
const network = asNetwork(asString(parsed.options.network));
|
|
115
|
+
const result = await runConsumeCommand(http, apiKey, {
|
|
86
116
|
endpointId: endpointIdValue,
|
|
87
117
|
params,
|
|
88
118
|
paymentRail,
|
|
89
119
|
...(network ? { network } : {}),
|
|
90
120
|
});
|
|
91
|
-
|
|
121
|
+
printConsumeResult(result, asJson);
|
|
92
122
|
return;
|
|
93
123
|
}
|
|
94
124
|
case "account": {
|
|
@@ -97,6 +127,26 @@ const main = async (): Promise<void> => {
|
|
|
97
127
|
printAccountResult(result, asJson);
|
|
98
128
|
return;
|
|
99
129
|
}
|
|
130
|
+
case "feedback": {
|
|
131
|
+
const apiKey = await resolveApiKey(asString(parsed.options["api-key"]));
|
|
132
|
+
const executionId = asString(parsed.options["execution-id"]);
|
|
133
|
+
if (!executionId) {
|
|
134
|
+
throw new CliError("Missing --execution-id for feedback command.");
|
|
135
|
+
}
|
|
136
|
+
const rankRaw = asString(parsed.options.rank);
|
|
137
|
+
if (!rankRaw) {
|
|
138
|
+
throw new CliError("Missing --rank for feedback command.");
|
|
139
|
+
}
|
|
140
|
+
const rank = toIntInRange(rankRaw, "rank", 0, 10);
|
|
141
|
+
const feedback = asString(parsed.options.feedback);
|
|
142
|
+
const result = await runFeedbackCommand(http, apiKey, {
|
|
143
|
+
executionId,
|
|
144
|
+
rank,
|
|
145
|
+
...(feedback ? { feedback } : {}),
|
|
146
|
+
});
|
|
147
|
+
printFeedbackResult(result, asJson);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
100
150
|
default:
|
|
101
151
|
throw new CliError("Unknown command.", 1);
|
|
102
152
|
}
|
|
@@ -105,14 +155,15 @@ const main = async (): Promise<void> => {
|
|
|
105
155
|
const parseArgs = (argv: string[]): ParsedArgs => {
|
|
106
156
|
const command = argv[0];
|
|
107
157
|
if (
|
|
108
|
-
command !== "
|
|
109
|
-
command !== "
|
|
158
|
+
command !== "discover" &&
|
|
159
|
+
command !== "consume" &&
|
|
160
|
+
command !== "feedback" &&
|
|
110
161
|
command !== "account" &&
|
|
111
162
|
command !== "login"
|
|
112
163
|
) {
|
|
113
164
|
printUsage();
|
|
114
165
|
throw new CliError(
|
|
115
|
-
"Invalid command. Use one of: login,
|
|
166
|
+
"Invalid command. Use one of: login, discover, consume, feedback, account.",
|
|
116
167
|
1,
|
|
117
168
|
);
|
|
118
169
|
}
|
|
@@ -132,6 +183,9 @@ const parseArgs = (argv: string[]): ParsedArgs => {
|
|
|
132
183
|
}
|
|
133
184
|
const next = argv[index + 1];
|
|
134
185
|
if (!next || next.startsWith("--")) {
|
|
186
|
+
if (!BOOLEAN_OPTIONS.has(key)) {
|
|
187
|
+
throw new CliError(`Missing value for --${key}`);
|
|
188
|
+
}
|
|
135
189
|
options[key] = true;
|
|
136
190
|
index += 1;
|
|
137
191
|
continue;
|
|
@@ -151,13 +205,26 @@ const printUsage = (): void => {
|
|
|
151
205
|
print("Usage:");
|
|
152
206
|
print(" lidian login [--key ld_...] [--json]");
|
|
153
207
|
print(
|
|
154
|
-
' lidian
|
|
208
|
+
' lidian discover --q "<term>" [--page 1] [--pageSize 1..3] [--category <name>] [--auth-type none|api_key|bearer|basic|oauth2|custom]',
|
|
209
|
+
);
|
|
210
|
+
print(
|
|
211
|
+
" [--min-price <cents>] [--max-price <cents>] [--api-key <key>] [--env production|staging] [--api-base <url>] [--json]",
|
|
155
212
|
);
|
|
156
213
|
print(
|
|
157
|
-
" lidian
|
|
214
|
+
" lidian consume --endpoint-id <uuid> --params '<json>' [--payment-rail prepaid_credits|x402] [--api-key <key>] [--env production|staging] [--api-base <url>] [--json]",
|
|
158
215
|
);
|
|
159
216
|
print(" [--network base|ethereum]");
|
|
160
|
-
print(
|
|
217
|
+
print(
|
|
218
|
+
' lidian feedback --execution-id <uuid> --rank <0..10> [--feedback "<text>"] [--api-key <key>] [--env production|staging] [--api-base <url>] [--json]',
|
|
219
|
+
);
|
|
220
|
+
print(
|
|
221
|
+
" lidian account [--api-key <key>] [--env production|staging] [--api-base <url>] [--json]",
|
|
222
|
+
);
|
|
223
|
+
print("");
|
|
224
|
+
print("Env resolution:");
|
|
225
|
+
print(" --api-base > LIDIAN_API_BASE > --env > LIDIAN_ENV > production");
|
|
226
|
+
print(` production=${API_BASE_BY_ENV.production}`);
|
|
227
|
+
print(` staging=${API_BASE_BY_ENV.staging}`);
|
|
161
228
|
};
|
|
162
229
|
|
|
163
230
|
const asString = (value: string | boolean | undefined): string | undefined => {
|
|
@@ -168,17 +235,54 @@ const asString = (value: string | boolean | undefined): string | undefined => {
|
|
|
168
235
|
const toInt = (value: string | undefined, fallback: number): number => {
|
|
169
236
|
if (!value) return fallback;
|
|
170
237
|
const parsed = Number.parseInt(value, 10);
|
|
171
|
-
if (Number.isNaN(parsed)) {
|
|
238
|
+
if (Number.isNaN(parsed) || parsed < 1) {
|
|
172
239
|
throw new CliError(`Invalid integer value: ${value}`);
|
|
173
240
|
}
|
|
174
241
|
return parsed;
|
|
175
242
|
};
|
|
176
243
|
|
|
244
|
+
const toIntInRange = (
|
|
245
|
+
value: string,
|
|
246
|
+
flagName: string,
|
|
247
|
+
min: number,
|
|
248
|
+
max: number,
|
|
249
|
+
): number => {
|
|
250
|
+
const parsed = Number.parseInt(value, 10);
|
|
251
|
+
if (
|
|
252
|
+
Number.isNaN(parsed) ||
|
|
253
|
+
String(parsed) !== value ||
|
|
254
|
+
parsed < min ||
|
|
255
|
+
parsed > max
|
|
256
|
+
) {
|
|
257
|
+
throw new CliError(
|
|
258
|
+
`Invalid --${flagName} value: ${value}. Expected integer ${min}..${max}.`,
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
return parsed;
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const toNumber = (
|
|
265
|
+
value: string | undefined,
|
|
266
|
+
flagName: string,
|
|
267
|
+
): number | undefined => {
|
|
268
|
+
if (!value) return undefined;
|
|
269
|
+
const parsed = Number(value);
|
|
270
|
+
if (Number.isNaN(parsed)) {
|
|
271
|
+
throw new CliError(`Invalid --${flagName} value: ${value}`);
|
|
272
|
+
}
|
|
273
|
+
return parsed;
|
|
274
|
+
};
|
|
275
|
+
|
|
177
276
|
const parseJsonObject = (
|
|
178
277
|
value: string,
|
|
179
278
|
flagName: string,
|
|
180
279
|
): Record<string, unknown> => {
|
|
181
|
-
|
|
280
|
+
let parsed: unknown;
|
|
281
|
+
try {
|
|
282
|
+
parsed = JSON.parse(value) as unknown;
|
|
283
|
+
} catch {
|
|
284
|
+
throw new CliError(`${flagName} must be valid JSON.`);
|
|
285
|
+
}
|
|
182
286
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
183
287
|
throw new CliError(`${flagName} must be a JSON object.`);
|
|
184
288
|
}
|
|
@@ -190,4 +294,59 @@ const asPaymentRail = (value: string): PaymentRail => {
|
|
|
190
294
|
throw new CliError("Invalid --payment-rail. Use prepaid_credits or x402.");
|
|
191
295
|
};
|
|
192
296
|
|
|
297
|
+
const asAuthType = (
|
|
298
|
+
value: string | undefined,
|
|
299
|
+
):
|
|
300
|
+
| "none"
|
|
301
|
+
| "api_key"
|
|
302
|
+
| "bearer"
|
|
303
|
+
| "basic"
|
|
304
|
+
| "oauth2"
|
|
305
|
+
| "custom"
|
|
306
|
+
| undefined => {
|
|
307
|
+
if (!value) return undefined;
|
|
308
|
+
const valid = new Set([
|
|
309
|
+
"none",
|
|
310
|
+
"api_key",
|
|
311
|
+
"bearer",
|
|
312
|
+
"basic",
|
|
313
|
+
"oauth2",
|
|
314
|
+
"custom",
|
|
315
|
+
]);
|
|
316
|
+
if (valid.has(value)) {
|
|
317
|
+
return value as
|
|
318
|
+
| "none"
|
|
319
|
+
| "api_key"
|
|
320
|
+
| "bearer"
|
|
321
|
+
| "basic"
|
|
322
|
+
| "oauth2"
|
|
323
|
+
| "custom";
|
|
324
|
+
}
|
|
325
|
+
throw new CliError(
|
|
326
|
+
"Invalid --auth-type. Use none, api_key, bearer, basic, oauth2, or custom.",
|
|
327
|
+
);
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
const asNetwork = (value: string | undefined): string | undefined => {
|
|
331
|
+
if (!value) return undefined;
|
|
332
|
+
if (value === "base" || value === "ethereum") return value;
|
|
333
|
+
throw new CliError("Invalid --network. Use base or ethereum.");
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
const asEnvironment = (value: string | undefined): Environment | undefined => {
|
|
337
|
+
if (!value) return undefined;
|
|
338
|
+
if (value === "production" || value === "staging") return value;
|
|
339
|
+
throw new CliError("Invalid --env. Use production or staging.");
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
const resolveApiBase = (
|
|
343
|
+
cliApiBase: string | undefined,
|
|
344
|
+
environment: Environment | undefined,
|
|
345
|
+
): string => {
|
|
346
|
+
if (cliApiBase) return cliApiBase;
|
|
347
|
+
if (process.env.LIDIAN_API_BASE) return process.env.LIDIAN_API_BASE;
|
|
348
|
+
if (environment) return API_BASE_BY_ENV[environment];
|
|
349
|
+
return DEFAULT_API_BASE;
|
|
350
|
+
};
|
|
351
|
+
|
|
193
352
|
main().catch(fail);
|
package/src/lib/output.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { AccountApiResponse } from "@/commands/account";
|
|
2
|
-
import type {
|
|
3
|
-
|
|
2
|
+
import type {
|
|
3
|
+
ConsumeApiResponse,
|
|
4
|
+
ConsumeCommandResult,
|
|
5
|
+
} from "@/commands/consume";
|
|
6
|
+
import type { DiscoverApiResponse } from "@/commands/discover";
|
|
7
|
+
import type { FeedbackApiResponse } from "@/commands/feedback";
|
|
4
8
|
import { CliError } from "@/lib/errors";
|
|
5
9
|
|
|
6
10
|
export const print = (message: string): void => {
|
|
@@ -19,8 +23,8 @@ export const printResult = (result: unknown, asJson: boolean): void => {
|
|
|
19
23
|
print(formatForHuman(result));
|
|
20
24
|
};
|
|
21
25
|
|
|
22
|
-
export const
|
|
23
|
-
result:
|
|
26
|
+
export const printDiscoverResult = (
|
|
27
|
+
result: DiscoverApiResponse,
|
|
24
28
|
asJson: boolean,
|
|
25
29
|
): void => {
|
|
26
30
|
if (asJson) {
|
|
@@ -39,13 +43,13 @@ export const printQueryResult = (
|
|
|
39
43
|
? ` confidence=${item.matchPercent.toFixed(1)}%`
|
|
40
44
|
: "";
|
|
41
45
|
print(
|
|
42
|
-
`- ${item.name} (${item.id}) auth=${item.authType} cost=${item.defaultCostPerUse}c${confidence}`,
|
|
46
|
+
`- ${item.name} (${item.id}) auth=${item.authType} cost=${item.defaultCostPerUse}c (${formatUsd(item.defaultCostPerUse)})${confidence}`,
|
|
43
47
|
);
|
|
44
48
|
}
|
|
45
49
|
};
|
|
46
50
|
|
|
47
|
-
export const
|
|
48
|
-
result:
|
|
51
|
+
export const printConsumeResult = (
|
|
52
|
+
result: ConsumeCommandResult,
|
|
49
53
|
asJson: boolean,
|
|
50
54
|
): void => {
|
|
51
55
|
if (asJson) {
|
|
@@ -69,12 +73,31 @@ export const printAccountResult = (
|
|
|
69
73
|
return;
|
|
70
74
|
}
|
|
71
75
|
print(`Account: ${result.user.id}`);
|
|
72
|
-
print(
|
|
76
|
+
print(
|
|
77
|
+
`Balance: ${result.balance.balance} cents (${formatUsd(result.balance.balance)})`,
|
|
78
|
+
);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const printFeedbackResult = (
|
|
82
|
+
result: FeedbackApiResponse,
|
|
83
|
+
asJson: boolean,
|
|
84
|
+
): void => {
|
|
85
|
+
if (asJson) {
|
|
86
|
+
printResult(result, true);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const feedback = result.feedback ?? "<none>";
|
|
90
|
+
print(
|
|
91
|
+
`Feedback saved for execution=${result.executionId} rank=${result.rank} submittedBy=${result.submittedBy}`,
|
|
92
|
+
);
|
|
93
|
+
print(`Updated at: ${result.updatedAt}`);
|
|
94
|
+
print(`Comment: ${feedback}`);
|
|
73
95
|
};
|
|
74
96
|
|
|
75
|
-
const printExecutionResult = (result:
|
|
97
|
+
const printExecutionResult = (result: ConsumeApiResponse): void => {
|
|
98
|
+
print(`Execution ID: ${result.executionId}`);
|
|
76
99
|
print(
|
|
77
|
-
`Execution succeeded. Spent=${result.credits.spent} balance=${result.credits.balance}`,
|
|
100
|
+
`Execution succeeded. Spent=${result.credits.spent} cents (${formatUsd(result.credits.spent)}) balance=${result.credits.balance} cents (${formatUsd(result.credits.balance)})`,
|
|
78
101
|
);
|
|
79
102
|
print(JSON.stringify(result.data, null, 2));
|
|
80
103
|
};
|
|
@@ -86,6 +109,10 @@ const formatForHuman = (value: unknown): string => {
|
|
|
86
109
|
return JSON.stringify(value, null, 2);
|
|
87
110
|
};
|
|
88
111
|
|
|
112
|
+
const formatUsd = (cents: number): string => {
|
|
113
|
+
return `$${(cents / 100).toFixed(2)}`;
|
|
114
|
+
};
|
|
115
|
+
|
|
89
116
|
export const fail = (error: unknown): never => {
|
|
90
117
|
if (error instanceof CliError) {
|
|
91
118
|
printError(`Error: ${error.message}`);
|
package/src/commands/query.ts
DELETED
|
@@ -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
|
-
};
|