@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.
- package/README.md +48 -8
- package/package.json +1 -1
- package/src/client/api.mjs +63 -1
- package/src/commands/completions.mjs +1 -1
- package/src/commands/credits.mjs +18 -4
- package/src/commands/discover.mjs +2 -2
- package/src/commands/doctor.mjs +1 -1
- package/src/commands/interactive.mjs +4 -5
- package/src/commands/ledger.mjs +186 -0
- package/src/commands/usage.mjs +190 -0
- package/src/compat/aliases.mjs +4 -0
- package/src/main.mjs +52 -0
- package/src/output/audit.mjs +536 -0
- package/src/output/formatter.mjs +59 -6
|
@@ -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
|
+
}
|
package/src/compat/aliases.mjs
CHANGED
|
@@ -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)")}
|