@trazum/cli 1.50.2 → 1.50.4
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/dist/gateway-server.d.ts +91 -0
- package/dist/gateway-server.d.ts.map +1 -0
- package/dist/gateway-server.js +290 -0
- package/dist/gateway-server.js.map +1 -0
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +53 -0
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/es.d.ts.map +1 -1
- package/dist/i18n/es.js +55 -0
- package/dist/i18n/es.js.map +1 -1
- package/dist/i18n/types.d.ts +33 -0
- package/dist/i18n/types.d.ts.map +1 -1
- package/dist/index.js +188 -7
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/gateway-server.ts +350 -0
- package/src/i18n/en.ts +69 -0
- package/src/i18n/es.ts +71 -0
- package/src/i18n/types.ts +30 -0
- package/src/index.ts +256 -5
package/src/index.ts
CHANGED
|
@@ -39,6 +39,8 @@ import {
|
|
|
39
39
|
DEFAULT_USAGE,
|
|
40
40
|
budgetPositions,
|
|
41
41
|
conform,
|
|
42
|
+
outcomeReport,
|
|
43
|
+
FAILURE_POLICIES,
|
|
42
44
|
detectFromSource,
|
|
43
45
|
matchLocale,
|
|
44
46
|
parsePlanDocument,
|
|
@@ -133,6 +135,8 @@ import type {
|
|
|
133
135
|
import type {
|
|
134
136
|
BudgetReport,
|
|
135
137
|
ContractName,
|
|
138
|
+
FailurePolicy,
|
|
139
|
+
GatewayStanding,
|
|
136
140
|
UsageProfileReport,
|
|
137
141
|
WaiverUse,
|
|
138
142
|
InitDecline,
|
|
@@ -179,6 +183,12 @@ import { fetchProviderUsage, findCredential } from './connect.js';
|
|
|
179
183
|
import { STORE_DIR, appendRecords, readStore, rewriteStore } from './store-fs.js';
|
|
180
184
|
import { WAIVER_LOG, appendWaiverUse, readWaiverLog } from './waiver-log.js';
|
|
181
185
|
import { DEFAULT_PORT, buildServer, listen } from './serve.js';
|
|
186
|
+
import {
|
|
187
|
+
DEFAULT_GATEWAY_PORT,
|
|
188
|
+
UPSTREAMS,
|
|
189
|
+
buildGateway,
|
|
190
|
+
listenGateway,
|
|
191
|
+
} from './gateway-server.js';
|
|
182
192
|
import {
|
|
183
193
|
WATCH_STATE_VERSION,
|
|
184
194
|
checkWebhook,
|
|
@@ -233,6 +243,7 @@ interface Args {
|
|
|
233
243
|
const VALUE_FLAGS = new Set([
|
|
234
244
|
'against',
|
|
235
245
|
'contract',
|
|
246
|
+
'on-cannot-tell',
|
|
236
247
|
'from-log',
|
|
237
248
|
'min-usd',
|
|
238
249
|
'payload',
|
|
@@ -557,6 +568,7 @@ const COMMAND_FLAGS: Record<string, string[]> = {
|
|
|
557
568
|
init: ['dry-run', 'yes', 'json', 'pricing', 'pricing-live'],
|
|
558
569
|
conform: ['contract', 'json'],
|
|
559
570
|
feedback: [],
|
|
571
|
+
gateway: ['on-cannot-tell', 'port', 'socket', 'pricing', 'pricing-live'],
|
|
560
572
|
where: [],
|
|
561
573
|
rules: [],
|
|
562
574
|
blame: ['limit', 'model', 'calls', 'output-tokens', 'batch', 'prompt', 'markdown-out'],
|
|
@@ -2064,6 +2076,119 @@ function commandFeedback(t: CliMessages): void {
|
|
|
2064
2076
|
console.log();
|
|
2065
2077
|
}
|
|
2066
2078
|
|
|
2079
|
+
/**
|
|
2080
|
+
* `trazum gateway <provider>` — in the path, and refusing rather than advising.
|
|
2081
|
+
*
|
|
2082
|
+
* The last thing this product could not do. `serve` answers a question an
|
|
2083
|
+
* implementation may ignore; a connector reports the runaway after it ran.
|
|
2084
|
+
* Standing between the caller and the provider fixes both — usage is measured
|
|
2085
|
+
* from the provider's own response as it comes back, and a refusal is a
|
|
2086
|
+
* refusal.
|
|
2087
|
+
*
|
|
2088
|
+
* **The failure policy is required.** `--on-cannot-tell fail-open` keeps the
|
|
2089
|
+
* product working and lets the bill run; `fail-closed` stops the bill and takes
|
|
2090
|
+
* the product down with it. Both are defensible and there is deliberately no
|
|
2091
|
+
* default: a proxy that picks silently has made the most consequential decision
|
|
2092
|
+
* in somebody's architecture on their behalf, at install time, without saying
|
|
2093
|
+
* so.
|
|
2094
|
+
*
|
|
2095
|
+
* **Substitution is off unless it is written down.** `spend.substitute` in the
|
|
2096
|
+
* config, with the operator's own reason, and every substituted call is marked
|
|
2097
|
+
* so no later report treats it as the call the caller made.
|
|
2098
|
+
*/
|
|
2099
|
+
async function commandGateway(
|
|
2100
|
+
args: Args,
|
|
2101
|
+
config: TrazumConfig,
|
|
2102
|
+
configDir: string,
|
|
2103
|
+
pricing: PricingCatalogue,
|
|
2104
|
+
t: CliMessages,
|
|
2105
|
+
): Promise<void> {
|
|
2106
|
+
const provider = args.positional[0];
|
|
2107
|
+
if (provider === undefined || UPSTREAMS[provider] === undefined) {
|
|
2108
|
+
throw new Error(t.gateway.badProvider(provider ?? '', Object.keys(UPSTREAMS).join(', ')));
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
/**
|
|
2112
|
+
* No default, and the error says why rather than just what.
|
|
2113
|
+
*
|
|
2114
|
+
* The one flag in this product that refuses to guess on the reader's behalf,
|
|
2115
|
+
* because the two answers differ in which failure they accept and nobody but
|
|
2116
|
+
* the operator knows which their product can survive.
|
|
2117
|
+
*/
|
|
2118
|
+
const policyFlag = stringFlag(args, 'on-cannot-tell');
|
|
2119
|
+
if (policyFlag === undefined || !FAILURE_POLICIES.includes(policyFlag as FailurePolicy)) {
|
|
2120
|
+
throw new Error(t.gateway.needsPolicy(FAILURE_POLICIES.join(', ')));
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
const { resolved } = await readStore(configDir);
|
|
2124
|
+
const budget = budgetPositions(resolved.records, config.spend, { catalogue: pricing });
|
|
2125
|
+
const position = budget.positions[0] ?? null;
|
|
2126
|
+
|
|
2127
|
+
/**
|
|
2128
|
+
* Read once at start, like `serve`'s.
|
|
2129
|
+
*
|
|
2130
|
+
* A file read in the request path would put Trazum's own latency between a
|
|
2131
|
+
* caller and their provider on every call, which is a cost this product
|
|
2132
|
+
* would otherwise be reporting on somebody else. The staleness is real, so a
|
|
2133
|
+
* refusal carries `asOfMs` and says what it rested on.
|
|
2134
|
+
*/
|
|
2135
|
+
const standing: GatewayStanding | null =
|
|
2136
|
+
position === null || position.coverage === 'none'
|
|
2137
|
+
? null
|
|
2138
|
+
: {
|
|
2139
|
+
limitUsd: position.limitUsd,
|
|
2140
|
+
consumedUsd: position.consumedUsd,
|
|
2141
|
+
provenance: 'measured',
|
|
2142
|
+
asOfMs: Date.now(),
|
|
2143
|
+
};
|
|
2144
|
+
|
|
2145
|
+
const measured: { calls: number; usd: number } = { calls: 0, usd: 0 };
|
|
2146
|
+
const server = buildGateway({
|
|
2147
|
+
provider,
|
|
2148
|
+
catalogue: pricing,
|
|
2149
|
+
policy: {
|
|
2150
|
+
onCannotTell: policyFlag as FailurePolicy,
|
|
2151
|
+
...(config.spend?.substitute === undefined ? {} : { substitute: config.spend.substitute }),
|
|
2152
|
+
},
|
|
2153
|
+
standing: () => standing,
|
|
2154
|
+
record: (call) => {
|
|
2155
|
+
measured.calls += 1;
|
|
2156
|
+
console.error(
|
|
2157
|
+
c.dim(
|
|
2158
|
+
t.gateway.measured(
|
|
2159
|
+
call.model,
|
|
2160
|
+
call.label,
|
|
2161
|
+
call.inputTokens,
|
|
2162
|
+
call.outputTokens,
|
|
2163
|
+
call.substituted,
|
|
2164
|
+
),
|
|
2165
|
+
),
|
|
2166
|
+
);
|
|
2167
|
+
},
|
|
2168
|
+
note: (line) => {
|
|
2169
|
+
console.error(c.yellow(` ${line}`));
|
|
2170
|
+
},
|
|
2171
|
+
});
|
|
2172
|
+
|
|
2173
|
+
const socket = stringFlag(args, 'socket');
|
|
2174
|
+
const portRaw = stringFlag(args, 'port');
|
|
2175
|
+
const port = portRaw === undefined ? DEFAULT_GATEWAY_PORT : Number(portRaw);
|
|
2176
|
+
if (socket === undefined && (!Number.isInteger(port) || port < 0 || port > 65_535)) {
|
|
2177
|
+
throw new Error(t.serve.badPort(String(portRaw)));
|
|
2178
|
+
}
|
|
2179
|
+
|
|
2180
|
+
const where = await listenGateway(server, socket !== undefined ? { socket } : { port });
|
|
2181
|
+
console.log(c.bold(t.gateway.listening(where, provider)));
|
|
2182
|
+
console.log(` ${c.dim(wrap(t.gateway.pointYourSdk(where), 74, ' '))}`);
|
|
2183
|
+
console.log(` ${c.dim(wrap(t.gateway.credential(), 74, ' '))}`);
|
|
2184
|
+
console.log(` ${c.dim(wrap(t.gateway.neverSubstitutes(), 74, ' '))}`);
|
|
2185
|
+
console.log(
|
|
2186
|
+
` ${c.dim(wrap(standing === null ? t.gateway.noStanding() : t.gateway.standing(formatUsd(standing.consumedUsd), formatUsd(standing.limitUsd)), 74, ' '))}`,
|
|
2187
|
+
);
|
|
2188
|
+
console.log(` ${c.dim(wrap(t.gateway.policy(policyFlag), 74, ' '))}`);
|
|
2189
|
+
console.log();
|
|
2190
|
+
}
|
|
2191
|
+
|
|
2067
2192
|
function commandModels(t: CliMessages, pricing: PricingCatalogue): void {
|
|
2068
2193
|
const n = (value: number): string => value.toLocaleString(t.numberLocale);
|
|
2069
2194
|
const col = t.models.columns;
|
|
@@ -3640,6 +3765,7 @@ async function commandConnect(
|
|
|
3640
3765
|
async function commandHistory(
|
|
3641
3766
|
args: Args,
|
|
3642
3767
|
config: TrazumConfig,
|
|
3768
|
+
configDir: string,
|
|
3643
3769
|
pricing: PricingCatalogue,
|
|
3644
3770
|
t: CliMessages,
|
|
3645
3771
|
): Promise<void> {
|
|
@@ -3759,7 +3885,7 @@ async function commandHistory(
|
|
|
3759
3885
|
* the waiver record belongs to the repository whose gates fired, and the
|
|
3760
3886
|
* stored reports may have come from anywhere.
|
|
3761
3887
|
*/
|
|
3762
|
-
const waivers = await readWaiverLog(
|
|
3888
|
+
const waivers = await readWaiverLog(configDir);
|
|
3763
3889
|
const waiverReport = waiverHistory(waivers.uses, config.waive ?? []);
|
|
3764
3890
|
|
|
3765
3891
|
const stamped = { ...history, unrecognizedFiles: unrecognized, waivers: waiverReport };
|
|
@@ -4161,7 +4287,13 @@ async function commandPlan(
|
|
|
4161
4287
|
if (outPath !== undefined) console.log(c.dim(wrap(t.plan.wrote(outPath), 74, '')));
|
|
4162
4288
|
}
|
|
4163
4289
|
|
|
4164
|
-
async function commandProfile(
|
|
4290
|
+
async function commandProfile(
|
|
4291
|
+
args: Args,
|
|
4292
|
+
config: TrazumConfig,
|
|
4293
|
+
configDir: string,
|
|
4294
|
+
pricing: PricingCatalogue,
|
|
4295
|
+
t: CliMessages,
|
|
4296
|
+
): Promise<void> {
|
|
4165
4297
|
const path = args.positional[0];
|
|
4166
4298
|
if (path === undefined) {
|
|
4167
4299
|
console.log();
|
|
@@ -4454,6 +4586,7 @@ async function commandProfile(args: Args, config: TrazumConfig, pricing: Pricing
|
|
|
4454
4586
|
can(cov.label > 0, t.profile.dryRunLabels(share(cov.label)));
|
|
4455
4587
|
can(cov.ts > 0, t.profile.dryRunClock(share(cov.ts)));
|
|
4456
4588
|
can(cov.session > 0, t.profile.dryRunSessions(share(cov.session)));
|
|
4589
|
+
can(cov.outcome > 0, t.profile.dryRunOutcomes(share(cov.outcome)));
|
|
4457
4590
|
can(cov.stopReason > 0, t.profile.dryRunStopReason(share(cov.stopReason)));
|
|
4458
4591
|
// "No cache traffic" is not a missing field: the split can only exist on
|
|
4459
4592
|
// records that wrote, and a log that never wrote has nothing to record.
|
|
@@ -4985,7 +5118,7 @@ async function commandProfile(args: Args, config: TrazumConfig, pricing: Pricing
|
|
|
4985
5118
|
const recordWaiverUses = async (): Promise<void> => {
|
|
4986
5119
|
if (waiverUses.length === 0) return;
|
|
4987
5120
|
for (const use of waiverUses) {
|
|
4988
|
-
const failed = await appendWaiverUse(
|
|
5121
|
+
const failed = await appendWaiverUse(configDir, use);
|
|
4989
5122
|
if (failed !== null) {
|
|
4990
5123
|
console.error(c.dim(t.profile.waiveNotRecorded(WAIVER_LOG, failed)));
|
|
4991
5124
|
return;
|
|
@@ -6285,6 +6418,97 @@ async function commandProfile(args: Args, config: TrazumConfig, pricing: Pricing
|
|
|
6285
6418
|
* section at all, because a paragraph of things that are fine is the
|
|
6286
6419
|
* paragraph readers learn to skip.
|
|
6287
6420
|
*/
|
|
6421
|
+
/**
|
|
6422
|
+
* Outcomes — the counterpart, where somebody recorded one.
|
|
6423
|
+
*
|
|
6424
|
+
* Printed above the coverage section rather than below it, because when this
|
|
6425
|
+
* section is present it is the most valuable thing on the page: every other
|
|
6426
|
+
* figure in this report is a cost, and this is the only one that says what
|
|
6427
|
+
* the money bought.
|
|
6428
|
+
*
|
|
6429
|
+
* Silent when nothing recorded an outcome. The coverage section below
|
|
6430
|
+
* already names the missing field and what it would unlock, and printing an
|
|
6431
|
+
* empty Outcomes heading above it would be the same sentence twice.
|
|
6432
|
+
*/
|
|
6433
|
+
{
|
|
6434
|
+
const outcomes = outcomeReport(report.outcomeTally, config.outcomes ?? null);
|
|
6435
|
+
if (outcomes.coverage.recorded > 0) {
|
|
6436
|
+
console.log();
|
|
6437
|
+
console.log(c.bold(t.profile.outcomeHeading()));
|
|
6438
|
+
|
|
6439
|
+
const col = t.profile.outcomeColumns;
|
|
6440
|
+
const rows = [...outcomes.slices, ...outcomes.undeclared].map((slice) => ({
|
|
6441
|
+
value: slice.value,
|
|
6442
|
+
verdict:
|
|
6443
|
+
slice.verdict === 'success'
|
|
6444
|
+
? t.profile.verdictSuccess()
|
|
6445
|
+
: slice.verdict === 'undeclared'
|
|
6446
|
+
? t.profile.verdictUndeclared()
|
|
6447
|
+
: t.profile.verdictOther(),
|
|
6448
|
+
calls: n(slice.calls),
|
|
6449
|
+
spend: formatUsd(slice.usd),
|
|
6450
|
+
}));
|
|
6451
|
+
const w = {
|
|
6452
|
+
value: Math.max(...rows.map((r) => r.value.length), col.outcome.length),
|
|
6453
|
+
verdict: Math.max(...rows.map((r) => r.verdict.length), 0),
|
|
6454
|
+
calls: Math.max(...rows.map((r) => r.calls.length), col.calls.length),
|
|
6455
|
+
spend: Math.max(...rows.map((r) => r.spend.length), col.spend.length),
|
|
6456
|
+
};
|
|
6457
|
+
console.log(
|
|
6458
|
+
c.dim(
|
|
6459
|
+
` ${col.outcome.padEnd(w.value)} ${''.padEnd(w.verdict)} ` +
|
|
6460
|
+
`${col.calls.padStart(w.calls)} ${col.spend.padStart(w.spend)}`,
|
|
6461
|
+
),
|
|
6462
|
+
);
|
|
6463
|
+
for (const row of rows) {
|
|
6464
|
+
const tint =
|
|
6465
|
+
row.verdict === t.profile.verdictUndeclared()
|
|
6466
|
+
? c.yellow
|
|
6467
|
+
: row.verdict === t.profile.verdictSuccess()
|
|
6468
|
+
? c.green
|
|
6469
|
+
: c.dim;
|
|
6470
|
+
console.log(
|
|
6471
|
+
` ${row.value.padEnd(w.value)} ${tint(row.verdict.padEnd(w.verdict))} ` +
|
|
6472
|
+
`${row.calls.padStart(w.calls)} ${row.spend.padStart(w.spend)}`,
|
|
6473
|
+
);
|
|
6474
|
+
}
|
|
6475
|
+
|
|
6476
|
+
console.log();
|
|
6477
|
+
if (outcomes.successShareOfRecordedUsd !== null) {
|
|
6478
|
+
const declaredUsd = outcomes.slices.reduce((sum, slice) => sum + slice.usd, 0);
|
|
6479
|
+
console.log(
|
|
6480
|
+
` ${wrap(t.profile.outcomeRate(pct(outcomes.successShareOfRecordedUsd), formatUsd(declaredUsd)), 74, ' ')}`,
|
|
6481
|
+
);
|
|
6482
|
+
} else if (outcomes.noRate !== null) {
|
|
6483
|
+
console.log(` ${c.dim(wrap(t.profile.outcomeNoRate(outcomes.noRate), 74, ' '))}`);
|
|
6484
|
+
}
|
|
6485
|
+
|
|
6486
|
+
// What the rate does not cover, every time it is printed. A rate over a
|
|
6487
|
+
// twelfth of the bill is a rate about a twelfth of the bill.
|
|
6488
|
+
if (outcomes.coverage.unrecordedUsd > 0 && report.total.totalUsd > 0) {
|
|
6489
|
+
console.log(
|
|
6490
|
+
` ${c.yellow('!')} ${wrap(
|
|
6491
|
+
t.profile.outcomeUnrecorded(
|
|
6492
|
+
pct(outcomes.coverage.unrecordedUsd / report.total.totalUsd),
|
|
6493
|
+
formatUsd(outcomes.coverage.unrecordedUsd),
|
|
6494
|
+
),
|
|
6495
|
+
74,
|
|
6496
|
+
' ',
|
|
6497
|
+
)}`,
|
|
6498
|
+
);
|
|
6499
|
+
}
|
|
6500
|
+
if (outcomes.undeclared.length > 0) {
|
|
6501
|
+
console.log(
|
|
6502
|
+
` ${c.yellow('!')} ${wrap(
|
|
6503
|
+
t.profile.outcomeUndeclared(outcomes.undeclared.map((s) => s.value).join(', ')),
|
|
6504
|
+
74,
|
|
6505
|
+
' ',
|
|
6506
|
+
)}`,
|
|
6507
|
+
);
|
|
6508
|
+
}
|
|
6509
|
+
}
|
|
6510
|
+
}
|
|
6511
|
+
|
|
6288
6512
|
const coverage = report.fieldCoverage;
|
|
6289
6513
|
if (coverage.parsed > 0) {
|
|
6290
6514
|
const missing: string[] = [];
|
|
@@ -6295,6 +6519,14 @@ async function commandProfile(args: Args, config: TrazumConfig, pricing: Pricing
|
|
|
6295
6519
|
if (coverage.session < coverage.parsed) {
|
|
6296
6520
|
missing.push(t.profile.needsSession(partial(coverage.session)));
|
|
6297
6521
|
}
|
|
6522
|
+
/**
|
|
6523
|
+
* Listed first among the missing when it is missing entirely, because it
|
|
6524
|
+
* is the one field that changes what every other figure here *means*. The
|
|
6525
|
+
* rest sharpen a cost; this one gives it a counterpart.
|
|
6526
|
+
*/
|
|
6527
|
+
if (coverage.outcome < coverage.parsed) {
|
|
6528
|
+
missing.push(t.profile.needsOutcome(partial(coverage.outcome)));
|
|
6529
|
+
}
|
|
6298
6530
|
if (coverage.ts < coverage.parsed) {
|
|
6299
6531
|
missing.push(t.profile.needsTs(partial(coverage.ts)));
|
|
6300
6532
|
}
|
|
@@ -8014,6 +8246,22 @@ async function main(): Promise<void> {
|
|
|
8014
8246
|
};
|
|
8015
8247
|
}
|
|
8016
8248
|
const { config } = loaded;
|
|
8249
|
+
/**
|
|
8250
|
+
* Where the waiver record lives: **beside the config that declared it**.
|
|
8251
|
+
*
|
|
8252
|
+
* It used to be the process's working directory, which is a different place
|
|
8253
|
+
* whenever somebody runs `trazum profile ../logs/x.jsonl --config ../repo/
|
|
8254
|
+
* trazum.config.json` — and that is not hypothetical. This repository's own
|
|
8255
|
+
* test suite did exactly that from `packages/cli`, so sixty records of a
|
|
8256
|
+
* fixture's decisions accumulated in a package directory and one of them was
|
|
8257
|
+
* committed to `main`, where it sat for two releases.
|
|
8258
|
+
*
|
|
8259
|
+
* A waiver is a decision a *repository* made. The record of using it belongs
|
|
8260
|
+
* with the file that made it, not with wherever the terminal happened to be.
|
|
8261
|
+
* No config means no waivers, so there is nothing to write and `.` is never
|
|
8262
|
+
* reached.
|
|
8263
|
+
*/
|
|
8264
|
+
const configDir = loaded.path === null ? '.' : dirname(loaded.path);
|
|
8017
8265
|
const pricing = await pricingFor(args, loaded, t);
|
|
8018
8266
|
|
|
8019
8267
|
// The config only gets to choose the locale when nothing more explicit did.
|
|
@@ -8033,7 +8281,7 @@ async function main(): Promise<void> {
|
|
|
8033
8281
|
await commandBaseline(args, config, pricing, t, locale);
|
|
8034
8282
|
break;
|
|
8035
8283
|
case 'profile':
|
|
8036
|
-
await commandProfile(args, config, pricing, t);
|
|
8284
|
+
await commandProfile(args, config, configDir, pricing, t);
|
|
8037
8285
|
break;
|
|
8038
8286
|
case 'plan':
|
|
8039
8287
|
await commandPlan(args, pricing, t);
|
|
@@ -8042,7 +8290,7 @@ async function main(): Promise<void> {
|
|
|
8042
8290
|
await commandVerify(args, pricing, t);
|
|
8043
8291
|
break;
|
|
8044
8292
|
case 'history':
|
|
8045
|
-
await commandHistory(args, config, pricing, t);
|
|
8293
|
+
await commandHistory(args, config, configDir, pricing, t);
|
|
8046
8294
|
break;
|
|
8047
8295
|
case 'connect':
|
|
8048
8296
|
await commandConnect(args, pricing, t);
|
|
@@ -8071,6 +8319,9 @@ async function main(): Promise<void> {
|
|
|
8071
8319
|
case 'models':
|
|
8072
8320
|
commandModels(t, pricing);
|
|
8073
8321
|
break;
|
|
8322
|
+
case 'gateway':
|
|
8323
|
+
await commandGateway(args, config, configDir, pricing, t);
|
|
8324
|
+
break;
|
|
8074
8325
|
case 'feedback':
|
|
8075
8326
|
commandFeedback(t);
|
|
8076
8327
|
break;
|