@qverisai/cli 0.4.0 → 0.5.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]];
package/src/main.mjs CHANGED
@@ -72,6 +72,16 @@ export async function main(argv) {
72
72
  await runCredits(flags);
73
73
  break;
74
74
  }
75
+ case "usage": {
76
+ const { runUsage } = await import("./commands/usage.mjs");
77
+ await runUsage(flags);
78
+ break;
79
+ }
80
+ case "ledger": {
81
+ const { runLedger } = await import("./commands/ledger.mjs");
82
+ await runLedger(flags);
83
+ break;
84
+ }
75
85
  case "config": {
76
86
  const subcommand = rest[0];
77
87
  const subArgs = rest.slice(1);
@@ -119,6 +129,11 @@ const VALUE_FLAGS = {
119
129
  "api-key": "apiKey", "base-url": "baseUrl", timeout: "timeout",
120
130
  limit: "limit", "discovery-id": "discoveryId", params: "params",
121
131
  "max-size": "maxSize", codegen: "codegen", token: "token",
132
+ mode: "mode", "start-date": "startDate", "end-date": "endDate",
133
+ bucket: "bucket", "execution-id": "executionId", "search-id": "searchId",
134
+ "event-type": "eventType", kind: "kind", success: "success",
135
+ "charge-outcome": "chargeOutcome", "entry-type": "entryType",
136
+ direction: "direction", "min-credits": "minCredits", "max-credits": "maxCredits",
122
137
  };
123
138
 
124
139
  function takeNext(args, i, flag) {
@@ -199,6 +214,34 @@ function extractGlobalFlags(args) {
199
214
  flags.codegen = takeNext(args, i++, arg); break;
200
215
  case "--token":
201
216
  flags.token = takeNext(args, i++, arg); break;
217
+ case "--mode":
218
+ flags.mode = takeNext(args, i++, arg); break;
219
+ case "--start-date":
220
+ flags.startDate = takeNext(args, i++, arg); break;
221
+ case "--end-date":
222
+ flags.endDate = takeNext(args, i++, arg); break;
223
+ case "--bucket":
224
+ flags.bucket = takeNext(args, i++, arg); break;
225
+ case "--execution-id":
226
+ flags.executionId = takeNext(args, i++, arg); break;
227
+ case "--search-id":
228
+ flags.searchId = takeNext(args, i++, arg); break;
229
+ case "--event-type":
230
+ flags.eventType = takeNext(args, i++, arg); break;
231
+ case "--kind":
232
+ flags.kind = takeNext(args, i++, arg); break;
233
+ case "--success":
234
+ flags.success = takeNext(args, i++, arg); break;
235
+ case "--charge-outcome":
236
+ flags.chargeOutcome = takeNext(args, i++, arg); break;
237
+ case "--entry-type":
238
+ flags.entryType = takeNext(args, i++, arg); break;
239
+ case "--direction":
240
+ flags.direction = takeNext(args, i++, arg); break;
241
+ case "--min-credits":
242
+ flags.minCredits = takeNext(args, i++, arg); break;
243
+ case "--max-credits":
244
+ flags.maxCredits = takeNext(args, i++, arg); break;
202
245
  default:
203
246
  flags._positional.push(arg);
204
247
  }
@@ -228,6 +271,8 @@ function printUsage(flags = {}) {
228
271
  ${cyan("logout")} Remove stored API key
229
272
  ${cyan("whoami")} Show current auth status
230
273
  ${cyan("credits")} Show credit balance
274
+ ${cyan("usage")} Summarize or search usage audit history
275
+ ${cyan("ledger")} Summarize or search credit ledger entries
231
276
 
232
277
  ${bold("Configuration:")}
233
278
  ${cyan("config")} set|get|list|reset Manage CLI settings
@@ -243,6 +288,11 @@ function printUsage(flags = {}) {
243
288
  --api-key <key> Override API key
244
289
  --base-url <url> Override API base URL
245
290
  --timeout <seconds> Request timeout
291
+ --mode <mode> summary | search | export-file for usage/ledger
292
+ --start-date <date> Usage/ledger range start (YYYY-MM-DD)
293
+ --end-date <date> Usage/ledger range end (YYYY-MM-DD)
294
+ --min-credits <n> Usage/ledger amount lower bound
295
+ --max-credits <n> Usage/ledger amount upper bound
246
296
  --no-color Disable colors
247
297
  --verbose, -v Show request details
248
298
  --version, -V Print version
@@ -258,6 +308,8 @@ function printUsage(flags = {}) {
258
308
  qveris inspect 1
259
309
  qveris call 1 --params '{"city": "London"}'
260
310
  qveris call 1 --params @params.json --codegen curl
311
+ qveris usage --mode search --execution-id <id>
312
+ qveris ledger --mode search --min-credits 50 --direction consume
261
313
  qveris interactive
262
314
 
263
315
  ${dim("https://qveris.ai (global) / https://qveris.cn (China)")}