@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.
@@ -0,0 +1,190 @@
1
+ import { resolveApiKey } from "../client/auth.mjs";
2
+ import { getUsageHistory, unwrapApiResponse } from "../client/api.mjs";
3
+ import { outputJson } from "../output/json.mjs";
4
+ import { createSpinner } from "../output/spinner.mjs";
5
+ import {
6
+ DEFAULT_PAGE_SIZE,
7
+ MAX_EXPORT_ROWS,
8
+ MAX_SEARCH_SCAN_ROWS,
9
+ MAX_SUMMARY_ROWS,
10
+ buildUsageQuery,
11
+ buildUsageSummary,
12
+ buildUsageSummaryFromServer,
13
+ chooseBucket,
14
+ clampLimit,
15
+ extractItems,
16
+ extractTotal,
17
+ formatExportMetadata,
18
+ formatUsageRows,
19
+ formatUsageSummary,
20
+ matchesUsageFilters,
21
+ pickUsageRow,
22
+ resolveDateRange,
23
+ resolveMode,
24
+ writeJsonlExport,
25
+ } from "../output/audit.mjs";
26
+
27
+ export async function runUsage(flags) {
28
+ const apiKey = resolveApiKey(flags.apiKey);
29
+ const mode = resolveMode(flags.mode);
30
+ const timeoutMs = (parseInt(flags.timeout, 10) || 30) * 1000;
31
+ const limit = clampLimit(flags.limit);
32
+ const { startDate, endDate } = resolveDateRange(flags);
33
+ const bucket = chooseBucket(startDate, endDate, flags.bucket);
34
+
35
+ const spinner = flags.json ? { stop() {} } : createSpinner("Querying usage history...");
36
+
37
+ try {
38
+ if (mode === "search") {
39
+ const { rows, total, partial } = await collectUsageRows({
40
+ apiKey,
41
+ flags,
42
+ timeoutMs,
43
+ limit,
44
+ maxRows: MAX_SEARCH_SCAN_ROWS,
45
+ stopWhenLimitReached: true,
46
+ });
47
+ spinner.stop();
48
+ const payload = {
49
+ mode,
50
+ start_date: startDate,
51
+ end_date: endDate,
52
+ shown_records: rows.length,
53
+ matched_records: total,
54
+ truncated: partial,
55
+ items: rows.map(pickUsageRow),
56
+ };
57
+ if (flags.json) outputJson(payload);
58
+ else console.log(formatUsageRows(rows, { total, partial }));
59
+ return;
60
+ }
61
+
62
+ if (mode === "export_file") {
63
+ const { rows, total, partial } = await collectUsageRows({
64
+ apiKey,
65
+ flags,
66
+ timeoutMs,
67
+ limit: MAX_EXPORT_ROWS,
68
+ maxRows: MAX_EXPORT_ROWS,
69
+ stopWhenLimitReached: false,
70
+ });
71
+ spinner.stop();
72
+ const metadata = writeJsonlExport("usage_history", rows, {
73
+ start_date: startDate,
74
+ end_date: endDate,
75
+ matched_records: total,
76
+ truncated: partial,
77
+ filters: usageFilters(flags),
78
+ });
79
+ if (flags.json) outputJson(metadata);
80
+ else console.log(formatExportMetadata(metadata));
81
+ return;
82
+ }
83
+
84
+ const summaryResponse = unwrapApiResponse(await getUsageHistory({
85
+ apiKey,
86
+ baseUrl: flags.baseUrl,
87
+ query: buildUsageQuery(flags, {
88
+ page: 1,
89
+ pageSize: limit,
90
+ mode: "summary",
91
+ }),
92
+ timeoutMs,
93
+ }));
94
+ if (summaryResponse?.summary) {
95
+ spinner.stop();
96
+ const rows = extractItems(summaryResponse);
97
+ const total = extractTotal(summaryResponse, rows.length);
98
+ const summary = buildUsageSummaryFromServer(summaryResponse.summary, { startDate, endDate, bucket });
99
+ if (flags.json) {
100
+ outputJson({
101
+ mode,
102
+ summary,
103
+ scanned_records: rows.length,
104
+ matched_records: total,
105
+ truncated: false,
106
+ });
107
+ } else {
108
+ console.log(formatUsageSummary(summary, { scannedRows: rows.length, total, partial: false }));
109
+ }
110
+ return;
111
+ }
112
+
113
+ const { rows, total, partial, scannedRows } = await collectUsageRows({
114
+ apiKey,
115
+ flags,
116
+ timeoutMs,
117
+ limit: MAX_SUMMARY_ROWS,
118
+ maxRows: MAX_SUMMARY_ROWS,
119
+ stopWhenLimitReached: false,
120
+ });
121
+ spinner.stop();
122
+ const summary = buildUsageSummary(rows, { startDate, endDate, bucket });
123
+ if (flags.json) {
124
+ outputJson({
125
+ mode,
126
+ summary,
127
+ scanned_records: scannedRows,
128
+ matched_records: total,
129
+ truncated: partial,
130
+ });
131
+ } else {
132
+ console.log(formatUsageSummary(summary, { scannedRows, total, partial }));
133
+ }
134
+ } catch (err) {
135
+ spinner.stop();
136
+ throw err;
137
+ }
138
+ }
139
+
140
+ async function collectUsageRows({ apiKey, flags, timeoutMs, limit, maxRows, stopWhenLimitReached }) {
141
+ const rows = [];
142
+ let page = 1;
143
+ let total;
144
+ let scannedRows = 0;
145
+ let partial = false;
146
+
147
+ while (rows.length < limit && scannedRows < maxRows) {
148
+ const query = buildUsageQuery(flags, { page, pageSize: Math.min(DEFAULT_PAGE_SIZE, maxRows - scannedRows) });
149
+ const response = unwrapApiResponse(await getUsageHistory({
150
+ apiKey,
151
+ baseUrl: flags.baseUrl,
152
+ query,
153
+ timeoutMs,
154
+ }));
155
+ const items = extractItems(response);
156
+ if (total === undefined) total = extractTotal(response, undefined);
157
+ if (items.length === 0) break;
158
+ scannedRows += items.length;
159
+ for (const item of items) {
160
+ if (!matchesUsageFilters(item, flags)) continue;
161
+ rows.push(item);
162
+ if (stopWhenLimitReached && rows.length >= limit) break;
163
+ }
164
+ if (stopWhenLimitReached && rows.length >= limit) {
165
+ partial = Boolean(total && total > scannedRows);
166
+ break;
167
+ }
168
+ if (total !== undefined && scannedRows >= total) break;
169
+ if (items.length < DEFAULT_PAGE_SIZE) break;
170
+ page += 1;
171
+ }
172
+
173
+ if (total !== undefined && scannedRows < total) partial = true;
174
+ if (scannedRows >= maxRows && (total === undefined || scannedRows < total)) partial = true;
175
+
176
+ return { rows: rows.slice(0, limit), total, partial, scannedRows };
177
+ }
178
+
179
+ function usageFilters(flags) {
180
+ return {
181
+ execution_id: flags.executionId,
182
+ search_id: flags.searchId,
183
+ event_type: flags.eventType,
184
+ kind: flags.kind,
185
+ success: flags.success,
186
+ charge_outcome: flags.chargeOutcome,
187
+ min_credits: flags.minCredits,
188
+ max_credits: flags.maxCredits,
189
+ };
190
+ }
@@ -13,6 +13,7 @@ const FLAG_ALIASES = {
13
13
  export function normalizeLegacyArgs(args) {
14
14
  const result = [...args];
15
15
  const warnings = [];
16
+ const originalCommand = result[0];
16
17
 
17
18
  if (result.length > 0 && COMMAND_ALIASES[result[0]]) {
18
19
  warnings.push(`'${result[0]}' is deprecated; use '${COMMAND_ALIASES[result[0]]}' instead.`);
@@ -20,6 +21,9 @@ export function normalizeLegacyArgs(args) {
20
21
  }
21
22
 
22
23
  for (let i = 0; i < result.length; i++) {
24
+ if (originalCommand === "usage" && result[i] === "--search-id") {
25
+ continue;
26
+ }
23
27
  if (FLAG_ALIASES[result[i]]) {
24
28
  warnings.push(`'${result[i]}' is deprecated; use '${FLAG_ALIASES[result[i]]}' instead.`);
25
29
  result[i] = FLAG_ALIASES[result[i]];
@@ -29,9 +29,24 @@ export const ERROR_CODES = {
29
29
  },
30
30
  PARAMS_INVALID_JSON: {
31
31
  message: "Invalid JSON in --params",
32
- hint: "Check JSON syntax in --params value",
32
+ hint: "Check JSON syntax in --params value, or pass a file with --params @params.json",
33
33
  exit: EX_USAGE,
34
34
  },
35
+ INIT_PARAMS_REQUIRED: {
36
+ message: "Init could not infer safe parameters for the selected capability",
37
+ hint: "Run 'qveris inspect 1' to review required params, then rerun 'qveris init --resume --params <json>'",
38
+ exit: EX_USAGE,
39
+ },
40
+ TOOL_CALL_FAILED: {
41
+ message: "Capability call failed",
42
+ hint: "Review the error, adjust --params, then rerun 'qveris init --resume --params <json>'",
43
+ exit: EX_UNAVAILABLE,
44
+ },
45
+ PROVIDER_FAILURE: {
46
+ message: "Remote provider failed",
47
+ hint: "Try another discovered capability with 'qveris inspect 2' and 'qveris call 2', or rerun discovery with a broader query",
48
+ exit: EX_UNAVAILABLE,
49
+ },
35
50
  TOOL_NOT_FOUND: {
36
51
  message: "Tool not found",
37
52
  hint: "Run 'qveris discover' to find available tools",
@@ -44,12 +59,12 @@ export const ERROR_CODES = {
44
59
  },
45
60
  CREDITS_INSUFFICIENT: {
46
61
  message: "Insufficient credits",
47
- hint: "Purchase credits at https://qveris.ai/pricing",
62
+ hint: "Purchase credits at https://qveris.ai/pricing, then confirm balance with 'qveris credits'",
48
63
  exit: EX_NOPERM,
49
64
  },
50
65
  SESSION_EXPIRED: {
51
66
  message: "Session expired",
52
- hint: "Run 'qveris discover' to start a new session",
67
+ hint: "Run 'qveris discover' or 'qveris init' to start a new session",
53
68
  exit: EX_USAGE,
54
69
  },
55
70
  };
package/src/main.mjs CHANGED
@@ -52,6 +52,11 @@ export async function main(argv) {
52
52
  await runCall(rest[0], flags);
53
53
  break;
54
54
  }
55
+ case "init": {
56
+ const { runInit } = await import("./commands/init.mjs");
57
+ await runInit(rest.join(" "), flags);
58
+ break;
59
+ }
55
60
  case "login": {
56
61
  const { runLogin } = await import("./commands/login.mjs");
57
62
  await runLogin(flags);
@@ -72,6 +77,16 @@ export async function main(argv) {
72
77
  await runCredits(flags);
73
78
  break;
74
79
  }
80
+ case "usage": {
81
+ const { runUsage } = await import("./commands/usage.mjs");
82
+ await runUsage(flags);
83
+ break;
84
+ }
85
+ case "ledger": {
86
+ const { runLedger } = await import("./commands/ledger.mjs");
87
+ await runLedger(flags);
88
+ break;
89
+ }
75
90
  case "config": {
76
91
  const subcommand = rest[0];
77
92
  const subArgs = rest.slice(1);
@@ -80,6 +95,14 @@ export async function main(argv) {
80
95
  await runConfig(subcommand, subArgs, flags);
81
96
  break;
82
97
  }
98
+ case "mcp": {
99
+ const subcommand = rest[0];
100
+ const subArgs = rest.slice(1);
101
+ if (!subcommand) { console.error(" Usage: qveris mcp <configure|validate> [target]"); process.exitCode = 2; return; }
102
+ const { runMcp } = await import("./commands/mcp.mjs");
103
+ await runMcp(subcommand, subArgs, flags);
104
+ break;
105
+ }
83
106
  case "interactive":
84
107
  case "repl": {
85
108
  const { runInteractive } = await import("./commands/interactive.mjs");
@@ -119,6 +142,12 @@ const VALUE_FLAGS = {
119
142
  "api-key": "apiKey", "base-url": "baseUrl", timeout: "timeout",
120
143
  limit: "limit", "discovery-id": "discoveryId", params: "params",
121
144
  "max-size": "maxSize", codegen: "codegen", token: "token",
145
+ query: "query", "tool-id": "toolId", target: "target", output: "output",
146
+ mode: "mode", "start-date": "startDate", "end-date": "endDate",
147
+ bucket: "bucket", "execution-id": "executionId", "search-id": "searchId",
148
+ "event-type": "eventType", kind: "kind", success: "success",
149
+ "charge-outcome": "chargeOutcome", "entry-type": "entryType",
150
+ direction: "direction", "min-credits": "minCredits", "max-credits": "maxCredits",
122
151
  };
123
152
 
124
153
  function takeNext(args, i, flag) {
@@ -177,6 +206,18 @@ function extractGlobalFlags(args) {
177
206
  flags.version = true; break;
178
207
  case "--dry-run":
179
208
  flags.dryRun = true; break;
209
+ case "--write":
210
+ flags.write = true; break;
211
+ case "--print":
212
+ flags.print = true; break;
213
+ case "--include-key":
214
+ flags.includeKey = true; break;
215
+ case "--validate":
216
+ flags.validate = true; break;
217
+ case "--probe":
218
+ flags.probe = true; break;
219
+ case "--resume":
220
+ flags.resume = true; break;
180
221
  case "--no-browser":
181
222
  flags.noBrowser = true; break;
182
223
  case "--clear":
@@ -199,6 +240,42 @@ function extractGlobalFlags(args) {
199
240
  flags.codegen = takeNext(args, i++, arg); break;
200
241
  case "--token":
201
242
  flags.token = takeNext(args, i++, arg); break;
243
+ case "--target":
244
+ flags.target = takeNext(args, i++, arg); break;
245
+ case "--output":
246
+ flags.output = takeNext(args, i++, arg); break;
247
+ case "--query":
248
+ flags.query = takeNext(args, i++, arg); break;
249
+ case "--tool-id":
250
+ flags.toolId = takeNext(args, i++, arg); break;
251
+ case "--mode":
252
+ flags.mode = takeNext(args, i++, arg); break;
253
+ case "--start-date":
254
+ flags.startDate = takeNext(args, i++, arg); break;
255
+ case "--end-date":
256
+ flags.endDate = takeNext(args, i++, arg); break;
257
+ case "--bucket":
258
+ flags.bucket = takeNext(args, i++, arg); break;
259
+ case "--execution-id":
260
+ flags.executionId = takeNext(args, i++, arg); break;
261
+ case "--search-id":
262
+ flags.searchId = takeNext(args, i++, arg); break;
263
+ case "--event-type":
264
+ flags.eventType = takeNext(args, i++, arg); break;
265
+ case "--kind":
266
+ flags.kind = takeNext(args, i++, arg); break;
267
+ case "--success":
268
+ flags.success = takeNext(args, i++, arg); break;
269
+ case "--charge-outcome":
270
+ flags.chargeOutcome = takeNext(args, i++, arg); break;
271
+ case "--entry-type":
272
+ flags.entryType = takeNext(args, i++, arg); break;
273
+ case "--direction":
274
+ flags.direction = takeNext(args, i++, arg); break;
275
+ case "--min-credits":
276
+ flags.minCredits = takeNext(args, i++, arg); break;
277
+ case "--max-credits":
278
+ flags.maxCredits = takeNext(args, i++, arg); break;
202
279
  default:
203
280
  flags._positional.push(arg);
204
281
  }
@@ -219,6 +296,7 @@ function printUsage(flags = {}) {
219
296
  qveris <command> [args] [flags]
220
297
 
221
298
  ${bold("Core Commands:")}
299
+ ${cyan("init")} Guided first-call wizard
222
300
  ${cyan("discover")} <query> Find capabilities by natural language
223
301
  ${cyan("inspect")} <tool_id|index> View tool details, parameters, and stats
224
302
  ${cyan("call")} <tool_id|index> Execute a capability
@@ -228,9 +306,12 @@ function printUsage(flags = {}) {
228
306
  ${cyan("logout")} Remove stored API key
229
307
  ${cyan("whoami")} Show current auth status
230
308
  ${cyan("credits")} Show credit balance
309
+ ${cyan("usage")} Summarize or search usage audit history
310
+ ${cyan("ledger")} Summarize or search credit ledger entries
231
311
 
232
312
  ${bold("Configuration:")}
233
313
  ${cyan("config")} set|get|list|reset Manage CLI settings
314
+ ${cyan("mcp")} configure|validate Generate and validate MCP client config
234
315
 
235
316
  ${bold("Utilities:")}
236
317
  ${cyan("interactive")} Launch interactive REPL mode
@@ -243,6 +324,19 @@ function printUsage(flags = {}) {
243
324
  --api-key <key> Override API key
244
325
  --base-url <url> Override API base URL
245
326
  --timeout <seconds> Request timeout
327
+ --target <target> MCP target: cursor | claude-desktop | claude-code | opencode | openclaw | generic
328
+ --output <path> MCP config output path
329
+ --write Write MCP config to disk
330
+ --include-key Include resolved API key instead of placeholder
331
+ --probe Start MCP server and verify visible tools during validation
332
+ --query <query> Init discovery query override
333
+ --tool-id <id> Init selected capability override
334
+ --resume Resume init from the last discovery session
335
+ --mode <mode> summary | search | export-file for usage/ledger
336
+ --start-date <date> Usage/ledger range start (YYYY-MM-DD)
337
+ --end-date <date> Usage/ledger range end (YYYY-MM-DD)
338
+ --min-credits <n> Usage/ledger amount lower bound
339
+ --max-credits <n> Usage/ledger amount upper bound
246
340
  --no-color Disable colors
247
341
  --verbose, -v Show request details
248
342
  --version, -V Print version
@@ -254,10 +348,17 @@ function printUsage(flags = {}) {
254
348
  QVERIS_BASE_URL Custom API base URL
255
349
 
256
350
  ${bold("Examples:")}
351
+ qveris init
352
+ qveris init --query "weather forecast API"
353
+ qveris init --resume --params '{"city": "London"}'
257
354
  qveris discover "weather forecast API"
258
355
  qveris inspect 1
259
356
  qveris call 1 --params '{"city": "London"}'
260
357
  qveris call 1 --params @params.json --codegen curl
358
+ qveris mcp configure --target cursor --write --include-key
359
+ qveris mcp validate --target cursor
360
+ qveris usage --mode search --execution-id <id>
361
+ qveris ledger --mode search --min-credits 50 --direction consume
261
362
  qveris interactive
262
363
 
263
364
  ${dim("https://qveris.ai (global) / https://qveris.cn (China)")}