@zenera/cli 1.1.29 → 1.1.30
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/commands/key.js
CHANGED
|
@@ -4,7 +4,7 @@ import { parse } from "../args.js";
|
|
|
4
4
|
import { ensureHome } from "../home.js";
|
|
5
5
|
import { ambient, ambientId, assertNotEmpty, checkRegion, describe, envNames, envOf, formOf, GATEWAYS, isGateway, keyId, KeyStore, mask, MODES, OWNERS, parseRef, SHAPES, } from "../keys.js";
|
|
6
6
|
import { probe, probeAll } from "../liveness.js";
|
|
7
|
-
import { ago, ask, askSecret, badge, bold, confirm, credentialError, cyan, dim, green, isInteractive, json, note, progress, readStdin, red, table, usageError, write, writeAll, yellow, } from "../term.js";
|
|
7
|
+
import { ago, ask, askSecret, badge, bold, confirm, credentialError, cyan, dim, green, isInteractive, json, note, progress, readStdin, red, table, usageError, warn, write, writeAll, yellow, } from "../term.js";
|
|
8
8
|
const USAGE = 'zen key <ls|add|use|mode|check|rm|show|env> [ref] [options]';
|
|
9
9
|
// ---------------------------------------------------------------------------
|
|
10
10
|
// Display
|
|
@@ -54,15 +54,28 @@ function placed(store, entry) {
|
|
|
54
54
|
* intent — it can be listed and probed, not chosen or forgotten.
|
|
55
55
|
*/
|
|
56
56
|
function asEntry(cred) {
|
|
57
|
+
const url = isGateway(cred.provider) ? envUrl() : undefined;
|
|
57
58
|
return {
|
|
58
59
|
provider: cred.provider,
|
|
59
60
|
name: cred.env ? `$${cred.env}` : 'adc',
|
|
60
61
|
holds: cred.holds,
|
|
61
62
|
value: cred.value,
|
|
62
63
|
env: cred.env,
|
|
64
|
+
...(url ? { url } : {}),
|
|
63
65
|
addedAt: new Date().toISOString(),
|
|
64
66
|
};
|
|
65
67
|
}
|
|
68
|
+
const envUrl = () => process.env[GATEWAY_URL_ENV]?.trim() || undefined;
|
|
69
|
+
/** Where a gateway mode sends runs, and with which key: the environment beats the keyring. */
|
|
70
|
+
function route(store, mode) {
|
|
71
|
+
const entry = store.active(mode);
|
|
72
|
+
const shell = envNames(mode).find((name) => process.env[name]);
|
|
73
|
+
return {
|
|
74
|
+
url: envUrl() ?? entry?.url,
|
|
75
|
+
key: shell ? `$${shell}` : entry ? keyId(entry) : undefined,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const FROM_ENV = `set by $${GATEWAY_URL_ENV} in the environment`;
|
|
66
79
|
function rows(store, borrowed) {
|
|
67
80
|
const out = [
|
|
68
81
|
[bold(''), bold('KEY'), bold('VALUE'), bold('WHERE'), bold('STATE'), bold('CHECKED')],
|
|
@@ -131,10 +144,12 @@ function modeLine(store) {
|
|
|
131
144
|
}
|
|
132
145
|
return;
|
|
133
146
|
}
|
|
134
|
-
const
|
|
135
|
-
note(`${cyan(badge(`⇄ ${mode.toUpperCase()} MODE`))} ${bold(
|
|
136
|
-
note(dim(`every model goes through the gateway with ${
|
|
137
|
-
|
|
147
|
+
const { url, key } = route(store, mode);
|
|
148
|
+
note(`${cyan(badge(`⇄ ${mode.toUpperCase()} MODE`))} ${bold(url ?? 'no url stored')}`);
|
|
149
|
+
note(dim(`every model goes through the gateway with ${key ?? 'no key'}; vendor keys are not used — ` +
|
|
150
|
+
(store.modeFrom === 'env'
|
|
151
|
+
? `${FROM_ENV}, unset it to stop`
|
|
152
|
+
: 'zen key mode direct to stop')));
|
|
138
153
|
note('');
|
|
139
154
|
}
|
|
140
155
|
/**
|
|
@@ -189,7 +204,7 @@ const ls = async (ctx, args) => {
|
|
|
189
204
|
});
|
|
190
205
|
return;
|
|
191
206
|
}
|
|
192
|
-
if (store.entries.length === 0 && borrowed.length === 0) {
|
|
207
|
+
if (store.entries.length === 0 && borrowed.length === 0 && store.mode === 'direct') {
|
|
193
208
|
note('the keyring is empty');
|
|
194
209
|
note(dim('add one: zen key add openai'));
|
|
195
210
|
return;
|
|
@@ -392,27 +407,44 @@ const mode = async (ctx, args) => {
|
|
|
392
407
|
if (!MODES.includes(wanted)) {
|
|
393
408
|
throw usageError(`unknown mode "${wanted}"`, `one of: ${MODES.join(', ')}`);
|
|
394
409
|
}
|
|
395
|
-
|
|
396
|
-
store.
|
|
410
|
+
// Already on through the environment: storing it as well needs a gateway key of its own.
|
|
411
|
+
const viaEnv = store.modeFrom === 'env' && wanted === store.mode;
|
|
412
|
+
if (!viaEnv || store.active(store.mode)?.url) {
|
|
413
|
+
store.setMode(wanted);
|
|
414
|
+
store.save();
|
|
415
|
+
}
|
|
416
|
+
if (store.modeFrom === 'env' && wanted !== store.mode) {
|
|
417
|
+
warn(`saved, but ${FROM_ENV} wins: unset it to call vendors directly`);
|
|
418
|
+
}
|
|
397
419
|
}
|
|
398
420
|
const now = store.mode;
|
|
399
|
-
const
|
|
421
|
+
const from = store.modeFrom;
|
|
422
|
+
const { url, key } = now === 'direct' ? {} : route(store, now);
|
|
400
423
|
if (ctx.json) {
|
|
401
|
-
json({ mode: now, ...(
|
|
424
|
+
json({ mode: now, from, ...(key ? { key } : {}), ...(url ? { url } : {}) });
|
|
402
425
|
return;
|
|
403
426
|
}
|
|
404
|
-
if (
|
|
427
|
+
if (now === 'direct') {
|
|
405
428
|
note(`${cyan('direct')} ${dim('— each provider is called with its own key')}`);
|
|
406
429
|
return;
|
|
407
430
|
}
|
|
408
|
-
note(`${cyan(now)} ${dim('— every model goes through')} ${
|
|
409
|
-
dim(`with ${
|
|
431
|
+
note(`${cyan(now)} ${dim('— every model goes through')} ${url ?? 'no url stored'} ` +
|
|
432
|
+
dim(`with ${key ?? 'no key'}`));
|
|
433
|
+
if (from === 'env') {
|
|
434
|
+
note(dim(FROM_ENV));
|
|
435
|
+
}
|
|
410
436
|
};
|
|
411
437
|
const check = async (ctx, args) => {
|
|
412
438
|
const { positionals } = parse(args, {}, 'zen key check [provider[/name]]');
|
|
413
439
|
const store = await KeyStore.open();
|
|
414
|
-
|
|
415
|
-
const
|
|
440
|
+
// Checked like stored keys, never recorded: `store.record` finds no entry for them.
|
|
441
|
+
const borrowed = ambient(store).map(asEntry);
|
|
442
|
+
const targets = positionals[0]
|
|
443
|
+
? select(store, positionals[0], borrowed)
|
|
444
|
+
: [...store.entries, ...borrowed];
|
|
445
|
+
if (targets.length === 0) {
|
|
446
|
+
assertNotEmpty(store);
|
|
447
|
+
}
|
|
416
448
|
const checks = await checkAll(ctx, store, targets);
|
|
417
449
|
for (const [entry, result] of checks) {
|
|
418
450
|
store.record(entry, result);
|
|
@@ -441,12 +473,13 @@ const check = async (ctx, args) => {
|
|
|
441
473
|
throw credentialError('at least one key authenticated but cannot be used');
|
|
442
474
|
}
|
|
443
475
|
};
|
|
444
|
-
function select(store, ref) {
|
|
476
|
+
function select(store, ref, borrowed = []) {
|
|
445
477
|
const { provider, name } = parseRef(ref);
|
|
446
478
|
if (!name) {
|
|
447
|
-
return store.for(provider);
|
|
479
|
+
return [...store.for(provider), ...borrowed.filter((e) => e.provider === provider)];
|
|
448
480
|
}
|
|
449
|
-
const entry = store.find(provider, name)
|
|
481
|
+
const entry = store.find(provider, name) ??
|
|
482
|
+
borrowed.find((e) => e.provider === provider && e.name === name);
|
|
450
483
|
if (!entry) {
|
|
451
484
|
throw usageError(`no key ${ref}`, 'see: zen key ls');
|
|
452
485
|
}
|
package/dist/keys.d.ts
CHANGED
|
@@ -164,7 +164,9 @@ export declare class KeyStore {
|
|
|
164
164
|
remove(provider: KeyOwner, name: string): boolean;
|
|
165
165
|
use(provider: KeyOwner, name: string): KeyEntry;
|
|
166
166
|
record(entry: KeyEntry, check: KeyCheck): void;
|
|
167
|
+
/** A `BIFROST_BASE_URL` in the environment wins over the keyring, as it does for the runtime. */
|
|
167
168
|
get mode(): KeyMode;
|
|
169
|
+
get modeFrom(): 'env' | 'keyring';
|
|
168
170
|
/** Refuses a gateway mode with no gateway to send anything to. */
|
|
169
171
|
setMode(mode: KeyMode): void;
|
|
170
172
|
save(): void;
|
package/dist/keys.js
CHANGED
|
@@ -380,8 +380,12 @@ export class KeyStore {
|
|
|
380
380
|
hit.check = check;
|
|
381
381
|
}
|
|
382
382
|
}
|
|
383
|
+
/** A `BIFROST_BASE_URL` in the environment wins over the keyring, as it does for the runtime. */
|
|
383
384
|
get mode() {
|
|
384
|
-
return this.#file.mode ?? 'direct';
|
|
385
|
+
return this.modeFrom === 'env' ? 'bifrost' : (this.#file.mode ?? 'direct');
|
|
386
|
+
}
|
|
387
|
+
get modeFrom() {
|
|
388
|
+
return process.env[GATEWAY_URL_ENV]?.trim() ? 'env' : 'keyring';
|
|
385
389
|
}
|
|
386
390
|
/** Refuses a gateway mode with no gateway to send anything to. */
|
|
387
391
|
setMode(mode) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zenera/cli",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.30",
|
|
4
4
|
"description": "Command-line front end for @zenera/neo: agentic projects you can run, share and commit.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agents",
|
|
@@ -54,8 +54,8 @@
|
|
|
54
54
|
"@google/genai": "^2.30.0",
|
|
55
55
|
"@inkjs/ui": "^2.0.0",
|
|
56
56
|
"@openrouter/sdk": "^1.4.25",
|
|
57
|
-
"@zenera/neo": "^1.1.
|
|
58
|
-
"@zenera/view": "^1.1.
|
|
57
|
+
"@zenera/neo": "^1.1.30",
|
|
58
|
+
"@zenera/view": "^1.1.30",
|
|
59
59
|
"google-auth-library": "^10.0.0",
|
|
60
60
|
"ink": "^7.1.1",
|
|
61
61
|
"openai": "^6.0.0",
|
|
@@ -73,7 +73,9 @@ Declared providers are rerouted too. `zen check` then reports
|
|
|
73
73
|
`BIFROST_API_KEY`, not the vendor variables, as the credential in use.
|
|
74
74
|
|
|
75
75
|
The switch is `BIFROST_BASE_URL` itself, so exporting it in a shell or a `.env`
|
|
76
|
-
turns gateway mode on without the keyring
|
|
76
|
+
turns gateway mode on without the keyring - and `zen key mode`/`ls`/`check`
|
|
77
|
+
report and probe it (`bifrost/$BIFROST_API_KEY`), with nothing stored. In this
|
|
78
|
+
mode `zen models` lists only
|
|
77
79
|
what the gateway serves - see `models.md`. The full guide is
|
|
78
80
|
[docs/gateway.md](https://github.com/andreyryabov/ZeneraNeo/blob/main/docs/gateway.md).
|
|
79
81
|
|