@qverisai/cli 0.4.0 → 0.6.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
@@ -2,53 +2,81 @@
2
2
 
3
3
  Discover, inspect, and call 10,000+ API capabilities from your terminal.
4
4
 
5
+ New users can run the guided first-call wizard:
6
+
7
+ ```bash
8
+ qveris init
9
+ ```
10
+
5
11
  ```
6
12
  $ qveris discover "weather forecast API"
7
13
  Found 5 capabilities matching your query
8
14
 
9
- 1. gridpoint_forecast by Weather.gov
10
- weather_gov.gridpoints.forecast.retrieve.v1
11
- Returns a textual forecast for a 2.5km grid area
12
- relevance: 95% · success: 99.8% · latency: ~180ms · cost: 3 cr
15
+ 1. icons
16
+ weather_gov.icons.retrieve.v3.ad0b4d80
17
+ Returns a forecast icon. Icon services in API are deprecated.
18
+ relevance: 99% · success: 100.0% · latency: ~325ms · billing: Charged 5.75 credits per call
13
19
 
14
- 2. current_weather by OpenWeather
15
- openweathermap.weather.current.v1
16
- Get current weather data for any location
17
- relevance: 87% · success: 99.5% · latency: ~200ms · cost: 2 cr
20
+ 2. Weather Information Retrieve
21
+ amap_webservice.weather.weatherinfo.retrieve.v3
22
+ Query current (base) or future (all) weather information for a specified city using adcode.
23
+ relevance: 96% · success: 83.0% · latency: ~467ms · billing: Charged 5.75 credits per call
18
24
 
19
25
  $ qveris inspect 1
20
- gridpoint_forecast
21
- Returns a textual forecast for a 2.5km grid area
26
+ icons
27
+ weather_gov.icons.retrieve.v3.ad0b4d80
28
+ Returns a forecast icon. Icon services in API are deprecated.
22
29
 
23
- Provider: Weather.gov
24
- Latency: ~180ms
25
- Success: 99.8%
26
- Cost: 3 credits
30
+ Provider: weather.gov
31
+ Region: global
32
+ Latency: ~325ms
33
+ Success: 100.0%
34
+ Billing: Charged 5.75 credits per call
27
35
 
28
36
  Parameters:
29
- wfo string required
30
- Forecast office ID
31
- values: "LWX", "OKX", "LAX", ...
32
- x integer required
33
- Forecast grid X coordinate
34
- y integer required
35
- Forecast grid Y coordinate
37
+ set string required
38
+ .
39
+ timeOfDay string required The time of day for which to retrieve data (e.g., "morning", "afternoon")
40
+
41
+ first string required The first record to retrieve (index or ID)
42
+
43
+ size string optional
44
+ Font size
45
+ fontsize integer optional
46
+ Font size
36
47
 
37
48
  Example:
38
- {"wfo": "LWX", "x": 90, "y": 90}
49
+ {"set":"land","first":"sct","timeOfDay":"day"}
50
+
51
+ $ qveris call 1 --params '{"set":"land","first":"sct","timeOfDay":"day"}'
52
+ ✓ success · 311ms · 5.75 credits pre-settlement · (1078.15 remaining)
53
+ tool: amap_webservice.weather.weatherinfo.retrieve.v3 · id: d79cd15b-2f36-4ce1-bb3c-6ea28b5ecfa2
39
54
 
40
- $ qveris call 1 --params '{"wfo":"LWX","x":90,"y":90}'
41
- ✓ success · 523ms · 10 credits · (1368087.93 remaining)
42
- tool: weather_gov... · id: 7ebcaf9d-...
55
+ Billing:
56
+ Total estimated pre-settlement charge: 5.75 credits
57
+ Pre-settlement: 5.75 credits
58
+ Final charge status: qveris usage --mode search --execution-id d79cd15b-2f36-4ce1-bb3c-6ea28b5ecfa2
43
59
 
44
60
  {
45
- "type": "Feature",
46
- "properties": {
47
- "periods": [
48
- { "name": "Today", "temperature": 79, "shortForecast": "Partly Sunny" },
49
- ...
50
- ]
51
- }
61
+ "status": "1",
62
+ "count": "1",
63
+ "info": "OK",
64
+ "infocode": "10000",
65
+ "lives": [
66
+ {
67
+ "province": "北京",
68
+ "city": "东城区",
69
+ "adcode": "110101",
70
+ "weather": "多云",
71
+ "temperature": "31",
72
+ "winddirection": "西南",
73
+ "windpower": "≤3",
74
+ "humidity": "50",
75
+ "reporttime": "2026-05-13 13:33:58",
76
+ "temperature_float": "31.0",
77
+ "humidity_float": "50.0"
78
+ }
79
+ ]
52
80
  }
53
81
  ```
54
82
 
@@ -57,12 +85,16 @@ tool: weather_gov... · id: 7ebcaf9d-...
57
85
  **One-liner (recommended):**
58
86
 
59
87
  ```bash
88
+ # Linux / macOS
60
89
  curl -fsSL https://qveris.ai/cli/install | bash
90
+
91
+ # Windows (PowerShell)
92
+ irm https://qveris.ai/cli/install.ps1 | iex
61
93
  ```
62
94
 
63
95
  **Or via npm:**
64
96
 
65
- ```bash
97
+ ```sh
66
98
  npm install -g @qverisai/cli
67
99
  ```
68
100
 
@@ -77,6 +109,10 @@ Requires Node.js 18+.
77
109
  ## Quick Start
78
110
 
79
111
  ```bash
112
+ # Guided path: auth → discover → inspect → call → usage/ledger guidance
113
+ qveris init
114
+
115
+ # Manual path
80
116
  # 1. Authenticate
81
117
  qveris login
82
118
 
@@ -87,7 +123,7 @@ qveris discover "weather forecast"
87
123
  qveris inspect 1
88
124
 
89
125
  # 4. Call it
90
- qveris call 1 --params '{"wfo": "LWX", "x": 90, "y": 90}'
126
+ qveris call 1 --params '{"set":"land","first":"sct","timeOfDay":"day"}'
91
127
  ```
92
128
 
93
129
  ## Commands
@@ -96,9 +132,10 @@ qveris call 1 --params '{"wfo": "LWX", "x": 90, "y": 90}'
96
132
 
97
133
  | Command | Description |
98
134
  |---------|-------------|
99
- | `qveris discover <query>` | Find capabilities by natural language. Shows tool ID, provider, description, relevance, success rate, latency, and cost for each result. |
135
+ | `qveris init` | Guided first-call wizard: auth, discover, inspect, call, and usage/ledger reconciliation guidance. |
136
+ | `qveris discover <query>` | Find capabilities by natural language. Shows tool ID, description, relevance, success rate, latency, billing rule, provider, tags, and region when available. |
100
137
  | `qveris inspect <id\|index>` | View full tool details: parameters (type, required, description, enum values), example, provider info, execution history. |
101
- | `qveris call <id\|index>` | Execute a capability. Shows result data, execution time, cost, and remaining credits. |
138
+ | `qveris call <id\|index>` | Execute a capability. Shows result data, execution time, pre-settlement billing, and remaining credits. |
102
139
 
103
140
  ### Account
104
141
 
@@ -108,6 +145,8 @@ qveris call 1 --params '{"wfo": "LWX", "x": 90, "y": 90}'
108
145
  | `qveris logout` | Remove stored key |
109
146
  | `qveris whoami` | Show current auth status, key source, and region |
110
147
  | `qveris credits` | Check credit balance |
148
+ | `qveris usage` | Context-safe usage audit summary/search/export |
149
+ | `qveris ledger` | Context-safe credits ledger summary/search/export |
111
150
 
112
151
  ### Utilities
113
152
 
@@ -116,10 +155,26 @@ qveris call 1 --params '{"wfo": "LWX", "x": 90, "y": 90}'
116
155
  | `qveris interactive` | Launch REPL mode (discover/inspect/call/codegen in one session) |
117
156
  | `qveris doctor` | Self-check: Node.js version, API key, region, connectivity |
118
157
  | `qveris config <subcommand>` | Manage CLI settings (set, get, list, reset, path) |
158
+ | `qveris mcp configure` | Generate MCP client config for Cursor, Claude Desktop, OpenCode, OpenClaw, or generic stdio; generate a `claude mcp add` command for Claude Code |
159
+ | `qveris mcp validate` | Validate an MCP config file, with optional live stdio tool probing |
119
160
  | `qveris completions <shell>` | Generate shell completions (bash/zsh/fish) |
120
161
 
121
162
  ## Usage
122
163
 
164
+ ### Init
165
+
166
+ Run the guided first-call wizard:
167
+
168
+ ```bash
169
+ qveris init
170
+ qveris init --query "weather forecast API"
171
+ qveris init --dry-run
172
+ qveris init --resume --params '{"city": "London"}'
173
+ qveris init --json
174
+ ```
175
+
176
+ `init` discovers a capability, inspects the selected result, calls it with sample parameters when available, and ends with exact `usage` / `ledger` commands so you can reconcile final billing. Use `--resume` after recoverable parameter or provider failures to reuse the last discovery session.
177
+
123
178
  ### Discover
124
179
 
125
180
  Search for capabilities using natural language. Each result shows the tool name, provider, tool ID, description, and quality metrics to help you choose.
@@ -146,7 +201,7 @@ qveris inspect 1 2 3
146
201
 
147
202
  ### Call
148
203
 
149
- Execute a tool. Results are automatically truncated for terminal display (4KB). Large results get an OSS download link.
204
+ Execute a tool. Results are automatically truncated for terminal display (4KB). Large results get an OSS download link. Billing shown here is pre-settlement; use `qveris usage` or `qveris ledger` for final charge status.
150
205
 
151
206
  ```bash
152
207
  # Inline params
@@ -178,13 +233,14 @@ For terminal use, results larger than 4KB are automatically truncated. The CLI s
178
233
  - The response schema so you know the data structure
179
234
 
180
235
  ```
181
- ✓ success · 1200ms · 5 credits
236
+ ✓ success · 1200ms · 5 credits pre-settlement
182
237
 
183
238
  Response truncated (32KB → 4KB preview)
184
239
 
185
240
  Full content (valid 120 min):
186
241
  https://qveris-tool-results-cache-bj.oss-cn-beijing...
187
- Download: curl -o result.json '<url>'
242
+ Download: curl -o result.json '<url>' (Linux/macOS)
243
+ Download: Invoke-WebRequest -Uri '<url>' -OutFile result.json (Windows PowerShell)
188
244
 
189
245
  Schema:
190
246
  query: string
@@ -199,6 +255,76 @@ Preview:
199
255
 
200
256
  For agent/script use (`--json` or piped output), the default increases to 20KB (matching the MCP server). Use `--max-size -1` for unlimited.
201
257
 
258
+ ### MCP Configuration
259
+
260
+ Generate MCP client config without hand-editing JSON. Print mode is the default and uses `YOUR_QVERIS_API_KEY` placeholders so the output is safe to share. Placeholder output intentionally fails API key validation until you replace it or use `--include-key`. For Claude Code, the command prints a `claude mcp add` command instead of writing a config file.
261
+
262
+ ```bash
263
+ # Print safe config for Cursor
264
+ qveris mcp configure --target cursor
265
+
266
+ # Write a working Cursor config using your resolved API key
267
+ qveris mcp configure --target cursor --write --include-key
268
+
269
+ # Other supported targets
270
+ qveris mcp configure --target claude-desktop --write --include-key
271
+ qveris mcp configure --target opencode --write --include-key
272
+ qveris mcp configure --target openclaw --write --include-key
273
+ qveris mcp configure --target claude-code
274
+ qveris mcp configure --target generic --json
275
+ ```
276
+
277
+ Config file locations and destinations:
278
+ - **Cursor**: `~/.cursor/mcp.json` (Linux/macOS) or `%USERPROFILE%\.cursor\mcp.json` (Windows)
279
+ - **Claude Desktop**: `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows)
280
+ - **Claude Code**: no config file is written; `qveris mcp configure --target claude-code` outputs a `claude mcp add` command
281
+
282
+ Validate an existing config:
283
+
284
+ ```bash
285
+ # Static config validation
286
+ qveris mcp validate --target cursor
287
+
288
+ # Live stdio probe: starts the MCP server and confirms discover/inspect/call are visible
289
+ qveris mcp validate --target cursor --probe
290
+ ```
291
+
292
+ Supported targets: `cursor`, `claude-desktop`, `claude-code`, `opencode`, `openclaw`, `generic`.
293
+
294
+ ### Usage Audit
295
+
296
+ Use `qveris usage` to answer whether recent calls succeeded, failed, or charged credits. It defaults to `summary` mode to protect Agent context.
297
+ Summary mode requests service-side `summary=true` aggregates when available and falls back to bounded client-side aggregation for older deployments.
298
+
299
+ ```bash
300
+ # Aggregate recent usage by hour/day/week
301
+ qveris usage --mode summary --bucket hour
302
+
303
+ # Check one execution precisely
304
+ qveris usage --mode search --execution-id <execution_id> --json
305
+
306
+ # Find high-cost or suspicious records
307
+ qveris usage --mode search --min-credits 30 --max-credits 100 --json
308
+ qveris usage --mode search --charge-outcome failed_charged_review --json
309
+
310
+ # Export raw matching rows to a local JSONL file instead of stdout
311
+ qveris usage --mode export-file --start-date 2026-05-01 --end-date 2026-05-04
312
+ ```
313
+
314
+ ### Credits Ledger
315
+
316
+ Use `qveris ledger` to explain final credit balance movements. It also defaults to `summary` mode.
317
+ Summary mode requests service-side `summary=true` aggregates when available and falls back to bounded client-side aggregation for older deployments.
318
+
319
+ ```bash
320
+ qveris ledger --mode summary --bucket day
321
+ qveris ledger --mode search --direction consume --min-credits 50 --json
322
+ qveris ledger --mode search --entry-type consume_tool_execute --json
323
+ qveris ledger --mode export-file --start-date 2026-05-01 --end-date 2026-05-04
324
+ ```
325
+
326
+ `summary` and `search` never print full history. Large record sets are written to `.qveris/exports/*.jsonl` with `--mode export-file`.
327
+
202
328
  ### Interactive Mode
203
329
 
204
330
  ```bash
@@ -230,12 +356,19 @@ qveris> exit
230
356
  ### API Key
231
357
 
232
358
  ```bash
233
- # Option 1: Login (saves to ~/.config/qveris/config.json)
359
+ # Option 1: Login (saves to the qveris config file)
234
360
  qveris login
235
361
 
236
362
  # Option 2: Environment variable
363
+ # Linux / macOS
237
364
  export QVERIS_API_KEY="sk-1_..."
238
365
 
366
+ # Windows (PowerShell)
367
+ $env:QVERIS_API_KEY="sk-1_..."
368
+
369
+ # Windows (CMD)
370
+ set QVERIS_API_KEY=sk-1_...
371
+
239
372
  # Option 3: Per-command flag
240
373
  qveris discover "weather" --api-key "sk-1_..."
241
374
  ```
@@ -260,18 +393,34 @@ No extra configuration needed. `qveris login` prompts for region selection inter
260
393
  qveris login --token "sk-cn-xxx"
261
394
 
262
395
  # Or environment variable
396
+ # Linux / macOS
263
397
  export QVERIS_REGION=cn
264
398
 
399
+ # Windows (PowerShell)
400
+ $env:QVERIS_REGION="cn"
401
+
402
+ # Windows (CMD)
403
+ set QVERIS_REGION=cn
404
+
265
405
  # Or custom base URL
406
+ # Linux / macOS
266
407
  export QVERIS_BASE_URL=https://custom.endpoint/api/v1
267
408
 
409
+ # Windows (PowerShell)
410
+ $env:QVERIS_BASE_URL="https://custom.endpoint/api/v1"
411
+
412
+ # Windows (CMD)
413
+ set QVERIS_BASE_URL=https://custom.endpoint/api/v1
414
+
268
415
  # Or per-command
269
416
  qveris discover "weather" --base-url https://qveris.cn/api/v1
270
417
  ```
271
418
 
272
419
  ### Config File
273
420
 
274
- Located at `~/.config/qveris/config.json` (respects `XDG_CONFIG_HOME`).
421
+ Located at:
422
+ - **All platforms**: `$XDG_CONFIG_HOME/qveris/config.json` when `XDG_CONFIG_HOME` is set
423
+ - **Default**: `~/.config/qveris/config.json` (for example, `C:\Users\<user>\.config\qveris\config.json` on Windows)
275
424
 
276
425
  ```bash
277
426
  qveris config list # View all settings with sources
@@ -302,6 +451,11 @@ qveris completions fish | source
302
451
  | `--api-key <key>` | | Override API key |
303
452
  | `--timeout <seconds>` | | Request timeout |
304
453
  | `--max-size <bytes>` | | Response size limit (-1 = unlimited) |
454
+ | `--target <target>` | | MCP target |
455
+ | `--output <path>` | | MCP config output path |
456
+ | `--write` | | Write MCP config to disk |
457
+ | `--include-key` | | Include resolved API key instead of a placeholder |
458
+ | `--probe` | | Live-probe MCP tools during validation |
305
459
  | `--no-color` | | Disable colors |
306
460
  | `--version` | `-V` | Print version |
307
461
  | `--help` | `-h` | Show help |
@@ -319,8 +473,22 @@ When used by agents or in scripts, the CLI auto-detects non-TTY environments:
319
473
 
320
474
  ```bash
321
475
  # Agent workflow: discover → select → call → parse
476
+
477
+ # Linux / macOS
322
478
  TOOL=$(qveris discover "weather" --json | jq -r '.results[0].tool_id')
323
479
  qveris call "$TOOL" --params '{"city":"London"}' --json | jq '.result.data'
480
+
481
+ # Windows (PowerShell)
482
+ $TOOL = qveris discover "weather" --json | ConvertFrom-Json | Select-Object -ExpandProperty results | Select-Object -First 1 | Select-Object -ExpandProperty tool_id
483
+ qveris call $TOOL --params '{"city":"London"}' --json | ConvertFrom-Json | Select-Object -ExpandProperty result | Select-Object -ExpandProperty data
484
+
485
+ # Windows (CMD, interactive) - requires jq for Windows
486
+ for /f "tokens=*" %i in ('qveris discover "weather" --json ^| jq -r ".results[0].tool_id"') do set TOOL=%i
487
+ qveris call %TOOL% --params "{\"city\":\"London\"}" --json | jq ".result.data"
488
+
489
+ # Windows (.bat/.cmd script) - use %%i instead of %i
490
+ for /f "tokens=*" %%i in ('qveris discover "weather" --json ^| jq -r ".results[0].tool_id"') do set TOOL=%%i
491
+ qveris call %TOOL% --params "{\"city\":\"London\"}" --json | jq ".result.data"
324
492
  ```
325
493
 
326
494
  ## Exit Codes
@@ -343,7 +511,7 @@ QVeris CLI uses only Node.js built-in APIs. No `chalk`, no `commander`, no `yarg
343
511
  - Website: https://qveris.ai (global) / https://qveris.cn (China)
344
512
  - API Docs: https://qveris.ai/docs
345
513
  - Get API Key: https://qveris.ai/account?page=api-keys (global) / https://qveris.cn/account?page=api-keys (China)
346
- - GitHub: https://github.com/QVerisAI/QVerisAI
514
+ - GitHub: https://github.com/QVerisAI/qveris-agent-toolkit
347
515
 
348
516
  ## License
349
517
 
package/package.json CHANGED
@@ -1,11 +1,14 @@
1
1
  {
2
2
  "name": "@qverisai/cli",
3
- "version": "0.4.0",
4
- "description": "QVeris CLI -- discover, inspect, and call 10,000+ capabilities",
3
+ "version": "0.6.0",
4
+ "description": "QVeris CLI for agent capability discovery, inspection, calling, and audit",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "qveris": "./bin/qveris.mjs"
8
8
  },
9
+ "scripts": {
10
+ "test": "node --test"
11
+ },
9
12
  "engines": {
10
13
  "node": ">=18"
11
14
  },
@@ -24,7 +27,7 @@
24
27
  ],
25
28
  "repository": {
26
29
  "type": "git",
27
- "url": "https://github.com/QVerisAI/QVerisAI",
30
+ "url": "https://github.com/QVerisAI/qveris-agent-toolkit",
28
31
  "directory": "packages/cli"
29
32
  },
30
33
  "homepage": "https://qveris.ai",
@@ -36,7 +36,12 @@ async function requestJson(path, { method = "POST", query = {}, body, timeoutMs
36
36
  errorDetail = jsonBody.error_message || jsonBody.message || null;
37
37
  } catch { /* not JSON */ }
38
38
  if (status === 401 || status === 403) throw new CliError("AUTH_INVALID_KEY", errorDetail);
39
- if (status === 402) throw new CliError("CREDITS_INSUFFICIENT", errorDetail);
39
+ if (status === 402) {
40
+ const pricingHost = baseUrl.includes("qveris.cn") ? "https://qveris.cn" : "https://qveris.ai";
41
+ const err = new CliError("CREDITS_INSUFFICIENT", errorDetail);
42
+ err.hint = `Purchase credits at ${pricingHost}/pricing`;
43
+ throw err;
44
+ }
40
45
  if (status === 429) throw new CliError("RATE_LIMITED", errorDetail);
41
46
  const err = new CliError("API_ERROR", `HTTP ${status}: ${errorDetail || rawText}`);
42
47
  if (jsonBody) err.responseData = jsonBody;
@@ -57,6 +62,21 @@ async function requestJson(path, { method = "POST", query = {}, body, timeoutMs
57
62
  }
58
63
  }
59
64
 
65
+ export function unwrapApiResponse(response) {
66
+ if (
67
+ response &&
68
+ typeof response === "object" &&
69
+ Object.prototype.hasOwnProperty.call(response, "status") &&
70
+ Object.prototype.hasOwnProperty.call(response, "data")
71
+ ) {
72
+ if (response.status === "failure") {
73
+ throw new CliError("API_ERROR", response.message || "API request failed");
74
+ }
75
+ return response.data;
76
+ }
77
+ return response;
78
+ }
79
+
60
80
  export async function discoverTools({ apiKey, baseUrl: baseUrlFlag, query, limit = 5, timeoutMs = 30000 }) {
61
81
  const baseUrl = getBaseUrl(baseUrlFlag, apiKey);
62
82
  return requestJson("/search", { apiKey, baseUrl, body: { query, limit }, timeoutMs });
@@ -91,3 +111,45 @@ export async function callTool({
91
111
  timeoutMs,
92
112
  });
93
113
  }
114
+
115
+ export async function getCredits({ apiKey, baseUrl: baseUrlFlag, timeoutMs = 30000 }) {
116
+ const baseUrl = getBaseUrl(baseUrlFlag, apiKey);
117
+ return requestJson("/auth/credits", {
118
+ method: "GET",
119
+ apiKey,
120
+ baseUrl,
121
+ timeoutMs,
122
+ });
123
+ }
124
+
125
+ export async function getUsageHistory({
126
+ apiKey,
127
+ baseUrl: baseUrlFlag,
128
+ query = {},
129
+ timeoutMs = 30000,
130
+ }) {
131
+ const baseUrl = getBaseUrl(baseUrlFlag, apiKey);
132
+ return requestJson("/auth/usage/history/v2", {
133
+ method: "GET",
134
+ apiKey,
135
+ baseUrl,
136
+ query,
137
+ timeoutMs,
138
+ });
139
+ }
140
+
141
+ export async function getCreditsLedger({
142
+ apiKey,
143
+ baseUrl: baseUrlFlag,
144
+ query = {},
145
+ timeoutMs = 30000,
146
+ }) {
147
+ const baseUrl = getBaseUrl(baseUrlFlag, apiKey);
148
+ return requestJson("/auth/credits/ledger", {
149
+ method: "GET",
150
+ apiKey,
151
+ baseUrl,
152
+ query,
153
+ timeoutMs,
154
+ });
155
+ }
@@ -16,11 +16,15 @@ export function resolveApiKey(flagValue) {
16
16
  }
17
17
 
18
18
  const trimmed = value.trim();
19
- for (const pattern of PLACEHOLDER_PATTERNS) {
20
- if (pattern.test(trimmed)) {
21
- throw new CliError("AUTH_MISSING_KEY", "API key appears to be a placeholder. Set a real key.");
22
- }
19
+ if (isPlaceholderApiKey(trimmed)) {
20
+ throw new CliError("AUTH_MISSING_KEY", "API key appears to be a placeholder. Set a real key.");
23
21
  }
24
22
 
25
23
  return trimmed;
26
24
  }
25
+
26
+ export function isPlaceholderApiKey(value) {
27
+ if (typeof value !== "string") return true;
28
+ const trimmed = value.trim();
29
+ return PLACEHOLDER_PATTERNS.some((pattern) => pattern.test(trimmed));
30
+ }
@@ -1,6 +1,6 @@
1
1
  const COMMANDS = [
2
2
  "discover", "inspect", "call",
3
- "login", "logout", "whoami", "credits",
3
+ "login", "logout", "whoami", "credits", "usage", "ledger",
4
4
  "config", "interactive", "history", "doctor", "completions",
5
5
  ];
6
6
 
@@ -1,5 +1,5 @@
1
1
  import { resolveApiKey } from "../client/auth.mjs";
2
- import { discoverTools } from "../client/api.mjs";
2
+ import { getCredits, unwrapApiResponse } from "../client/api.mjs";
3
3
  import { resolveBaseUrl, getSiteUrl } from "../config/region.mjs";
4
4
  import { bold, dim, cyan, yellow, green } from "../output/colors.mjs";
5
5
  import { outputJson } from "../output/json.mjs";
@@ -13,16 +13,19 @@ export async function runCredits(flags) {
13
13
  const spinner = flags.json ? { stop() {} } : createSpinner("Checking credits...");
14
14
 
15
15
  try {
16
- // The backend returns remaining_credits in every /search response
17
- const result = await discoverTools({ apiKey, baseUrl: flags.baseUrl, query: "credit balance", limit: 1, timeoutMs: 10000 });
16
+ const result = unwrapApiResponse(await getCredits({ apiKey, baseUrl, timeoutMs: 10000 }));
18
17
  spinner.stop();
19
18
 
20
19
  const credits = result.remaining_credits;
21
20
 
22
21
  if (flags.json) {
23
- outputJson({ remaining_credits: credits ?? null });
22
+ outputJson(result);
24
23
  } else if (credits !== undefined && credits !== null) {
25
24
  console.log(`\n ${green("\u2713")} Credits remaining: ${bold(yellow(String(credits)))}`);
25
+ printBucket("Daily free", result.daily_free, "remaining", "total");
26
+ printBucket("Invite reward", result.invite_reward, "remaining", "total");
27
+ printBucket("Welcome bonus", result.welcome_bonus, "remaining", "initial");
28
+ printBucket("Purchased", result.purchased, "remaining", "total");
26
29
  console.log(` ${dim("Manage at:")} ${cyan(accountUrl)}\n`);
27
30
  } else {
28
31
  console.log(`\n ${dim("Credit balance not available in API response.")}`);
@@ -33,3 +36,14 @@ export async function runCredits(flags) {
33
36
  throw err;
34
37
  }
35
38
  }
39
+
40
+ function printBucket(label, bucket, remainingKey, totalKey) {
41
+ if (!bucket || typeof bucket !== "object") return;
42
+ const remaining = bucket[remainingKey];
43
+ const total = bucket[totalKey];
44
+ if (remaining === undefined && total === undefined) return;
45
+ const parts = [];
46
+ if (remaining !== undefined) parts.push(`remaining ${yellow(String(remaining))}`);
47
+ if (total !== undefined) parts.push(`${dim("of")} ${String(total)}`);
48
+ console.log(` ${dim(label + ":")} ${parts.join(" ")}`);
49
+ }
@@ -8,6 +8,7 @@ import { createSpinner } from "../output/spinner.mjs";
8
8
 
9
9
  export async function runDiscover(query, flags) {
10
10
  const apiKey = resolveApiKey(flags.apiKey);
11
+ const { region, baseUrl: resolvedBaseUrl } = resolveBaseUrl({ baseUrlFlag: flags.baseUrl, apiKey });
11
12
  const limit = parseInt(flags.limit, 10) || 5;
12
13
  const timeoutMs = (parseInt(flags.timeout, 10) || 30) * 1000;
13
14
 
@@ -16,7 +17,7 @@ export async function runDiscover(query, flags) {
16
17
  try {
17
18
  const result = await discoverTools({
18
19
  apiKey,
19
- baseUrl: flags.baseUrl,
20
+ baseUrl: resolvedBaseUrl,
20
21
  query,
21
22
  limit,
22
23
  timeoutMs,
@@ -26,7 +27,6 @@ export async function runDiscover(query, flags) {
26
27
 
27
28
  // Store richer session data for index resolution
28
29
  const tools = result.results ?? [];
29
- const { region, baseUrl: resolvedBaseUrl } = resolveBaseUrl({ baseUrlFlag: flags.baseUrl, apiKey });
30
30
  writeSession({
31
31
  discoveryId: result.search_id,
32
32
  query,
@@ -30,7 +30,7 @@ export async function runDoctor(flags) {
30
30
  // Test connectivity
31
31
  process.stderr.write(` \u2026 Testing API connectivity...\r`);
32
32
  try {
33
- await discoverTools({ apiKey, baseUrl: flags.baseUrl, query: "test", limit: 1, timeoutMs: 10000 });
33
+ await discoverTools({ apiKey, baseUrl, query: "test", limit: 1, timeoutMs: 10000 });
34
34
  console.log(` ${green("\u2713")} API connectivity OK `);
35
35
  } catch (err) {
36
36
  console.log(` ${red("\u2718")} API connectivity failed: ${err.message} `);