@zenera/cli 1.1.2 → 1.1.3
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 +88 -11
- package/dist/audit.d.ts +8 -6
- package/dist/audit.js +14 -22
- package/dist/commands/check.js +34 -7
- package/dist/commands/init.js +71 -11
- package/dist/commands/key.js +126 -36
- package/dist/commands/models.js +3 -3
- package/dist/commands/open.js +2 -2
- package/dist/commands/run.js +3 -0
- package/dist/engine.d.ts +2 -0
- package/dist/engine.js +1 -0
- package/dist/keys.d.ts +95 -12
- package/dist/keys.js +175 -34
- package/dist/lib.d.ts +1 -1
- package/dist/lib.js +1 -1
- package/dist/liveness.d.ts +16 -6
- package/dist/liveness.js +74 -23
- package/dist/sandbox.d.ts +2 -0
- package/dist/sandbox.js +58 -7
- package/dist/scaffold.d.ts +21 -21
- package/dist/scaffold.js +132 -204
- package/dist/validate.js +3 -3
- package/package.json +2 -18
- package/templates/{.github → editor/.github}/copilot-instructions.md +7 -6
- package/templates/editor/.github/skills/api-schema-index/SKILL.md +292 -0
- package/templates/editor/.github/skills/zen-cli/SKILL.md +74 -0
- package/templates/editor/.github/skills/zen-cli/references/check.md +92 -0
- package/templates/editor/.github/skills/zen-cli/references/faker.md +111 -0
- package/templates/editor/.github/skills/zen-cli/references/frame.md +119 -0
- package/templates/editor/.github/skills/zen-cli/references/inspect.md +61 -0
- package/templates/editor/.github/skills/zen-cli/references/keys.md +114 -0
- package/templates/editor/.github/skills/zen-cli/references/projects.md +99 -0
- package/templates/editor/.github/skills/zen-cli/references/rag.md +159 -0
- package/templates/editor/.github/skills/zen-cli/references/run.md +104 -0
- package/templates/editor/.github/skills/zen-cli/references/sandbox.md +91 -0
- package/templates/editor/.vscode/settings.json +6 -0
- package/templates/parts/exa.yaml.tmpl +5 -0
- package/templates/parts/model.yaml.tmpl +4 -0
- package/templates/parts/models.yaml.tmpl +10 -0
- package/templates/project/INSTRUCTIONS.md +7 -0
- package/templates/project/SPECIFICATION.md +6 -0
- package/templates/project/agents/prompts/default.md +15 -0
- package/templates/project/agents.yaml.tmpl +44 -0
- package/templates/project/assets/README.md +12 -0
- package/templates/project/gitignore +9 -0
- package/templates/{sandbox → project/sandbox}/Dockerfile +2 -0
- package/templates/.github/skills/zen-cli/SKILL.md +0 -110
- /package/templates/{.github → editor/.github}/prompts/new-agent.prompt.md +0 -0
- /package/templates/{.github → editor/.github}/prompts/new-skill.prompt.md +0 -0
- /package/templates/{.github → editor/.github}/prompts/review-project.prompt.md +0 -0
package/dist/commands/key.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { parse } from "../args.js";
|
|
3
3
|
import { ensureHome } from "../home.js";
|
|
4
|
-
import { assertNotEmpty, describe, keyId, KeyStore, mask, OWNERS, parseRef, SHAPES, } from "../keys.js";
|
|
4
|
+
import { ambient, ambientId, assertNotEmpty, describe, envNames, envOf, keyId, KeyStore, mask, OWNERS, parseRef, SHAPES, } from "../keys.js";
|
|
5
5
|
import { probe, probeAll } from "../liveness.js";
|
|
6
6
|
import { ago, ask, askSecret, bold, confirm, credentialError, cyan, dim, green, isInteractive, json, note, progress, readStdin, red, table, usageError, write, writeAll, yellow, } from "../term.js";
|
|
7
7
|
const USAGE = 'zen key <ls|add|use|check|rm|show|env> [ref] [options]';
|
|
@@ -16,35 +16,59 @@ const MARK = {
|
|
|
16
16
|
function state(entry) {
|
|
17
17
|
return entry.check ? MARK[entry.check.state] : dim('unchecked');
|
|
18
18
|
}
|
|
19
|
-
|
|
19
|
+
/**
|
|
20
|
+
* An ambient credential borrowed into the shape the rest of this file works
|
|
21
|
+
* in. It is not in the store and never will be: `store.find` misses it, so
|
|
22
|
+
* `store.record` drops its check and nothing is written to disk. That is the
|
|
23
|
+
* intent — it can be listed and probed, not chosen or forgotten.
|
|
24
|
+
*/
|
|
25
|
+
function asEntry(cred) {
|
|
26
|
+
return {
|
|
27
|
+
provider: cred.provider,
|
|
28
|
+
name: cred.env ? `$${cred.env}` : 'adc',
|
|
29
|
+
holds: cred.holds,
|
|
30
|
+
value: cred.value,
|
|
31
|
+
env: cred.env,
|
|
32
|
+
addedAt: new Date().toISOString(),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function rows(store, borrowed) {
|
|
20
36
|
const out = [
|
|
21
37
|
[bold(''), bold('KEY'), bold('VALUE'), bold('STATE'), bold('CHECKED')],
|
|
22
38
|
];
|
|
23
39
|
for (const provider of OWNERS) {
|
|
24
40
|
for (const entry of store.for(provider)) {
|
|
25
|
-
const
|
|
41
|
+
const shadow = envNames(provider).find((name) => process.env[name]);
|
|
26
42
|
out.push([
|
|
27
43
|
store.isActive(entry) ? green('*') : ' ',
|
|
28
44
|
keyId(entry),
|
|
29
45
|
dim(describe(store, entry)),
|
|
30
46
|
state(entry),
|
|
31
47
|
dim(entry.check ? ago(entry.check.at) : '—') +
|
|
32
|
-
(
|
|
33
|
-
|
|
34
|
-
|
|
48
|
+
(shadow && store.isActive(entry) ? yellow(` shadowed by $${shadow}`) : ''),
|
|
49
|
+
]);
|
|
50
|
+
}
|
|
51
|
+
for (const [cred, entry] of borrowed.filter(([c]) => c.provider === provider)) {
|
|
52
|
+
out.push([
|
|
53
|
+
dim('~'),
|
|
54
|
+
dim(ambientId(cred)),
|
|
55
|
+
dim(describe(store, entry)),
|
|
56
|
+
state(entry),
|
|
57
|
+
dim(cred.env ? 'from the environment' : 'from gcloud'),
|
|
35
58
|
]);
|
|
36
59
|
}
|
|
37
60
|
}
|
|
38
61
|
return table(out);
|
|
39
62
|
}
|
|
40
63
|
/**
|
|
41
|
-
* Checking is a network round trip per key, so it says which
|
|
42
|
-
*
|
|
64
|
+
* Checking is a network round trip per key, so it says which ones have come
|
|
65
|
+
* back. Silence for ten seconds is indistinguishable from a hang.
|
|
43
66
|
*/
|
|
44
67
|
async function checkAll(ctx, store, targets) {
|
|
45
68
|
const bar = ctx.json ? undefined : progress();
|
|
69
|
+
bar?.update(dim(`checking ${targets.length} key${targets.length === 1 ? '' : 's'} …`));
|
|
46
70
|
try {
|
|
47
|
-
return await probeAll(store, targets, (entry,
|
|
71
|
+
return await probeAll(store, targets, (entry, done, total) => bar?.update(dim(`checked ${keyId(entry)} … ${done}/${total}`)));
|
|
48
72
|
}
|
|
49
73
|
finally {
|
|
50
74
|
bar?.done();
|
|
@@ -53,29 +77,47 @@ async function checkAll(ctx, store, targets) {
|
|
|
53
77
|
const ls = async (ctx, args) => {
|
|
54
78
|
const { values } = parse(args, { check: { type: 'boolean' } }, USAGE);
|
|
55
79
|
const store = await KeyStore.open();
|
|
80
|
+
const borrowed = ambient(store).map((cred) => [cred, asEntry(cred)]);
|
|
56
81
|
if (values.check) {
|
|
57
|
-
|
|
82
|
+
// Ambient credentials are checked alongside the stored ones and, unlike
|
|
83
|
+
// them, forgotten again: there is nowhere to write the result, and a
|
|
84
|
+
// variable that changes between runs would make a cached one a lie.
|
|
85
|
+
const checks = await checkAll(ctx, store, [
|
|
86
|
+
...store.entries,
|
|
87
|
+
...borrowed.map(([, entry]) => entry),
|
|
88
|
+
]);
|
|
58
89
|
for (const [entry, check] of checks) {
|
|
59
90
|
store.record(entry, check);
|
|
91
|
+
entry.check = check;
|
|
60
92
|
}
|
|
61
93
|
store.save();
|
|
62
94
|
}
|
|
63
95
|
if (ctx.json) {
|
|
64
|
-
json(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
96
|
+
json({
|
|
97
|
+
keys: store.entries.map((e) => ({
|
|
98
|
+
...e,
|
|
99
|
+
value: e.holds === 'file' ? store.fileOf(e) : mask(e.value),
|
|
100
|
+
active: store.isActive(e),
|
|
101
|
+
env: envOf(e),
|
|
102
|
+
})),
|
|
103
|
+
ambient: borrowed.map(([cred, entry]) => ({
|
|
104
|
+
provider: cred.provider,
|
|
105
|
+
id: ambientId(cred),
|
|
106
|
+
env: cred.env ?? null,
|
|
107
|
+
holds: cred.holds,
|
|
108
|
+
value: cred.holds === 'file' ? cred.value : mask(cred.value),
|
|
109
|
+
check: entry.check,
|
|
110
|
+
})),
|
|
111
|
+
});
|
|
70
112
|
return;
|
|
71
113
|
}
|
|
72
|
-
if (store.entries.length === 0) {
|
|
114
|
+
if (store.entries.length === 0 && borrowed.length === 0) {
|
|
73
115
|
note('the keyring is empty');
|
|
74
116
|
note(dim('add one: zen key add openai'));
|
|
75
117
|
return;
|
|
76
118
|
}
|
|
77
|
-
writeAll(rows(store));
|
|
78
|
-
const missing = OWNERS.filter((p) => !store.active(p) && !
|
|
119
|
+
writeAll(rows(store, borrowed));
|
|
120
|
+
const missing = OWNERS.filter((p) => !store.active(p) && !borrowed.some(([c]) => c.provider === p));
|
|
79
121
|
if (missing.length) {
|
|
80
122
|
note('');
|
|
81
123
|
note(dim(`no key for: ${missing.join(', ')}`));
|
|
@@ -87,7 +129,12 @@ const ls = async (ctx, args) => {
|
|
|
87
129
|
* Piped stdin or an echo-off prompt are the only two ways in.
|
|
88
130
|
*/
|
|
89
131
|
const add = async (ctx, args) => {
|
|
90
|
-
const { values, positionals } = parse(args, {
|
|
132
|
+
const { values, positionals } = parse(args, {
|
|
133
|
+
name: { type: 'string' },
|
|
134
|
+
'no-check': { type: 'boolean' },
|
|
135
|
+
project: { type: 'string' },
|
|
136
|
+
location: { type: 'string' },
|
|
137
|
+
}, 'zen key add <provider>[/name] [--name <name>] [--project <id>] [--location <region>] [--no-check]');
|
|
91
138
|
const ref = positionals[0];
|
|
92
139
|
if (!ref) {
|
|
93
140
|
throw usageError('which provider?', `one of: ${OWNERS.join(', ')}`);
|
|
@@ -96,6 +143,9 @@ const add = async (ctx, args) => {
|
|
|
96
143
|
const provider = parsed.provider;
|
|
97
144
|
const name = values.name ?? parsed.name ?? 'default';
|
|
98
145
|
const shape = SHAPES[provider];
|
|
146
|
+
if ((values.project || values.location) && provider !== 'vertex') {
|
|
147
|
+
throw usageError(`--project and --location mean nothing to ${shape.label}`, 'they configure a Vertex service account');
|
|
148
|
+
}
|
|
99
149
|
ensureHome();
|
|
100
150
|
const store = await KeyStore.open();
|
|
101
151
|
if (store.find(provider, name) && isInteractive()) {
|
|
@@ -103,14 +153,20 @@ const add = async (ctx, args) => {
|
|
|
103
153
|
throw usageError('cancelled');
|
|
104
154
|
}
|
|
105
155
|
}
|
|
106
|
-
const raw = (await readStdin()) ?? (await promptFor(shape
|
|
156
|
+
const raw = (await readStdin()) ?? (await promptFor(shape));
|
|
107
157
|
if (!raw) {
|
|
108
158
|
throw usageError('no value given');
|
|
109
159
|
}
|
|
110
|
-
|
|
160
|
+
// A provider with one form can say up front that a path is wrong. One with
|
|
161
|
+
// two cannot: a value that is not a path is not a mistake there, it is the
|
|
162
|
+
// other kind of credential, and `add` decides which by looking.
|
|
163
|
+
if (shape.forms.length === 1 && shape.forms[0].holds === 'file' && !existsSync(raw)) {
|
|
111
164
|
throw usageError(`no such file: ${raw}`);
|
|
112
165
|
}
|
|
113
|
-
const entry = store.add(provider, name, raw
|
|
166
|
+
const entry = store.add(provider, name, raw, {
|
|
167
|
+
project: values.project,
|
|
168
|
+
location: values.location,
|
|
169
|
+
});
|
|
114
170
|
// Verified before it is trusted, but stored either way: a key that cannot
|
|
115
171
|
// be checked right now — offline, behind a proxy — is not a key that is
|
|
116
172
|
// wrong, and refusing to save it would make `zen key add` fail on a plane.
|
|
@@ -128,20 +184,37 @@ const add = async (ctx, args) => {
|
|
|
128
184
|
if (ctx.json) {
|
|
129
185
|
json({
|
|
130
186
|
key: keyId(entry),
|
|
131
|
-
env:
|
|
187
|
+
env: envOf(entry),
|
|
132
188
|
active: store.isActive(entry),
|
|
133
189
|
check: entry.check,
|
|
134
190
|
});
|
|
135
191
|
return;
|
|
136
192
|
}
|
|
137
|
-
note(`${green('added')} ${bold(keyId(entry))} ${dim(describe(store, entry))}
|
|
138
|
-
|
|
139
|
-
|
|
193
|
+
note(`${green('added')} ${bold(keyId(entry))} ${dim(describe(store, entry))} ` +
|
|
194
|
+
`${dim(`→ $${envOf(entry)}`)} ${state(entry)}`);
|
|
195
|
+
if (process.env[envOf(entry)]) {
|
|
196
|
+
note(yellow(`$${envOf(entry)} is set and will win over this`));
|
|
197
|
+
}
|
|
198
|
+
if (provider === 'vertex' && entry.holds === 'file' && !entry.project) {
|
|
199
|
+
note(dim('no --project given; the project_id inside the file will be used'));
|
|
140
200
|
}
|
|
141
201
|
};
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
202
|
+
/**
|
|
203
|
+
* A provider with two forms cannot ask for one of them. It asks with the echo
|
|
204
|
+
* off, because one of the two answers is a secret and there is no way to know
|
|
205
|
+
* which is coming until it arrives.
|
|
206
|
+
*/
|
|
207
|
+
async function promptFor(shape) {
|
|
208
|
+
const [first, ...rest] = shape.forms;
|
|
209
|
+
if (rest.length) {
|
|
210
|
+
note(dim(`${shape.label} — ${first.where},`));
|
|
211
|
+
for (const form of rest) {
|
|
212
|
+
note(dim(` or ${form.where}`));
|
|
213
|
+
}
|
|
214
|
+
return askSecret('Paste the key, or a path to the file:');
|
|
215
|
+
}
|
|
216
|
+
note(dim(`${shape.label} — ${first.where}`));
|
|
217
|
+
return first.holds === 'file'
|
|
145
218
|
? ask('Path to the credentials file:')
|
|
146
219
|
: askSecret('Paste the key (it will not be shown):');
|
|
147
220
|
}
|
|
@@ -159,7 +232,7 @@ const use = async (ctx, args) => {
|
|
|
159
232
|
const entry = store.use(provider, name);
|
|
160
233
|
store.save();
|
|
161
234
|
if (ctx.json) {
|
|
162
|
-
json({ key: keyId(entry), env:
|
|
235
|
+
json({ key: keyId(entry), env: envOf(entry) });
|
|
163
236
|
return;
|
|
164
237
|
}
|
|
165
238
|
note(`${green('using')} ${bold(keyId(entry))} for ${SHAPES[provider].label}`);
|
|
@@ -242,7 +315,7 @@ const show = async (ctx, args) => {
|
|
|
242
315
|
if (ctx.json) {
|
|
243
316
|
json({
|
|
244
317
|
key: keyId(entry),
|
|
245
|
-
env:
|
|
318
|
+
env: envOf(entry),
|
|
246
319
|
value,
|
|
247
320
|
revealed: Boolean(values.reveal),
|
|
248
321
|
});
|
|
@@ -256,8 +329,10 @@ const show = async (ctx, args) => {
|
|
|
256
329
|
}
|
|
257
330
|
writeAll(table([
|
|
258
331
|
[dim('key'), keyId(entry)],
|
|
259
|
-
[dim('env'),
|
|
332
|
+
[dim('env'), envOf(entry)],
|
|
260
333
|
[dim('value'), value],
|
|
334
|
+
...(entry.project ? [[dim('project'), entry.project]] : []),
|
|
335
|
+
...(entry.location ? [[dim('location'), entry.location]] : []),
|
|
261
336
|
[dim('state'), state(entry)],
|
|
262
337
|
[dim('added'), ago(entry.addedAt)],
|
|
263
338
|
]));
|
|
@@ -278,8 +353,18 @@ const env = async (ctx, args) => {
|
|
|
278
353
|
const vars = {};
|
|
279
354
|
for (const provider of only ?? OWNERS) {
|
|
280
355
|
const entry = store.active(provider);
|
|
281
|
-
if (entry) {
|
|
282
|
-
|
|
356
|
+
if (!entry) {
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
vars[envOf(entry)] = store.reveal(entry);
|
|
360
|
+
// A service account says which project it belongs to but not which
|
|
361
|
+
// region to call, and a shell that has the file and not the region is
|
|
362
|
+
// still a shell that cannot reach a model.
|
|
363
|
+
if (entry.project) {
|
|
364
|
+
vars.GOOGLE_CLOUD_PROJECT = entry.project;
|
|
365
|
+
}
|
|
366
|
+
if (entry.location) {
|
|
367
|
+
vars.GOOGLE_CLOUD_LOCATION = entry.location;
|
|
283
368
|
}
|
|
284
369
|
}
|
|
285
370
|
if (ctx.json) {
|
|
@@ -306,11 +391,16 @@ export const key = {
|
|
|
306
391
|
usage: USAGE,
|
|
307
392
|
details: [
|
|
308
393
|
'Keys live in ~/.zenera/neo/keys.json (0600) and are materialised into',
|
|
309
|
-
'the environment before a run. A real environment variable always wins
|
|
394
|
+
'the environment before a run. A real environment variable always wins,',
|
|
395
|
+
'and is listed as ~ so it is clear where a working provider comes from.',
|
|
310
396
|
'',
|
|
311
397
|
'Model providers: openai, anthropic, google, vertex, openrouter.',
|
|
312
398
|
'Services the tools call: exa.',
|
|
313
399
|
'',
|
|
400
|
+
'Vertex takes either shape: a service-account JSON file, which wants',
|
|
401
|
+
'--project and --location too, or an express-mode API key, which wants',
|
|
402
|
+
'neither. Which one you gave is read off the value.',
|
|
403
|
+
'',
|
|
314
404
|
' zen key ls [--check] Everything stored, and its state.',
|
|
315
405
|
' zen key add <provider>[/name] Read a key from stdin, or ask for it.',
|
|
316
406
|
' zen key use <provider>/<name> Choose which one a run uses.',
|
package/dist/commands/models.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { loadProject, readProjectConfig } from '@zenera/neo';
|
|
2
2
|
import { parse } from "../args.js";
|
|
3
|
-
import { KeyStore, PROVIDERS
|
|
3
|
+
import { envNames, form, KeyStore, PROVIDERS } from "../keys.js";
|
|
4
4
|
import { project as resolveProject } from "../resolve.js";
|
|
5
5
|
import { bold, credentialError, cyan, dim, green, invalidError, json, note, red, table, writeAll, yellow, } from "../term.js";
|
|
6
6
|
import { availableTools } from "../validate.js";
|
|
@@ -22,7 +22,7 @@ export const models = {
|
|
|
22
22
|
// Asked before materialising, because materialising is exactly what
|
|
23
23
|
// erases the difference between "the environment had it" and "the
|
|
24
24
|
// keyring supplied it".
|
|
25
|
-
const fromEnv = new Set(PROVIDERS.filter((p) => process.env[
|
|
25
|
+
const fromEnv = new Set(PROVIDERS.filter((p) => envNames(p).some((name) => process.env[name])));
|
|
26
26
|
store.materialize();
|
|
27
27
|
let project;
|
|
28
28
|
try {
|
|
@@ -64,7 +64,7 @@ export const models = {
|
|
|
64
64
|
}));
|
|
65
65
|
const credentials = PROVIDERS.map((p) => ({
|
|
66
66
|
provider: p,
|
|
67
|
-
env:
|
|
67
|
+
env: envNames(p).find((name) => process.env[name]) ?? form(p).env,
|
|
68
68
|
source: fromEnv.has(p)
|
|
69
69
|
? 'environment'
|
|
70
70
|
: store.active(p)
|
package/dist/commands/open.js
CHANGED
|
@@ -4,7 +4,7 @@ import { homedir } from 'node:os';
|
|
|
4
4
|
import { delimiter, join, resolve, sep } from 'node:path';
|
|
5
5
|
import { one, parse } from "../args.js";
|
|
6
6
|
import { project as resolveProject } from "../resolve.js";
|
|
7
|
-
import {
|
|
7
|
+
import { editorFiles } from "../scaffold.js";
|
|
8
8
|
import { CliError, cyan, dim, EXIT, json, note, usageError } from "../term.js";
|
|
9
9
|
const USAGE = 'zen open [project] [--editor <cmd>] [--wait]';
|
|
10
10
|
/**
|
|
@@ -42,7 +42,7 @@ export const open = {
|
|
|
42
42
|
// opened on, and a project may predate either of them — or the version
|
|
43
43
|
// this `zen` writes. They are ours, so they are written fresh before
|
|
44
44
|
// the window is there to read them.
|
|
45
|
-
const written =
|
|
45
|
+
const written = editorFiles(dir);
|
|
46
46
|
if (ctx.json) {
|
|
47
47
|
json({
|
|
48
48
|
name: found.name,
|
package/dist/commands/run.js
CHANGED
|
@@ -19,6 +19,7 @@ export const run = {
|
|
|
19
19
|
' --model <ref> Override the default model.',
|
|
20
20
|
' --image <ref> Override the sandbox image commands run in.',
|
|
21
21
|
' --read-only Give the agent no way to write.',
|
|
22
|
+
' --no-keys Keep the API keys out of the sandbox.',
|
|
22
23
|
' --quiet Answer only; no narration.',
|
|
23
24
|
' --plain One shot, even on a terminal.',
|
|
24
25
|
' --theme <dark|light> Force the palette. Detected otherwise; $ZENERA_THEME.',
|
|
@@ -43,6 +44,7 @@ export const run = {
|
|
|
43
44
|
workspace: { type: 'string' },
|
|
44
45
|
model: { type: 'string' },
|
|
45
46
|
image: { type: 'string' },
|
|
47
|
+
'no-keys': { type: 'boolean' },
|
|
46
48
|
'read-only': { type: 'boolean' },
|
|
47
49
|
yes: { type: 'boolean' },
|
|
48
50
|
quiet: { type: 'boolean' },
|
|
@@ -84,6 +86,7 @@ export const run = {
|
|
|
84
86
|
readOnly: values['read-only'],
|
|
85
87
|
model: values.model,
|
|
86
88
|
image: values.image,
|
|
89
|
+
keys: values['no-keys'] ? false : undefined,
|
|
87
90
|
yes: values.yes || ctx.json,
|
|
88
91
|
});
|
|
89
92
|
try {
|
package/dist/engine.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ export interface EngineOptions {
|
|
|
10
10
|
model?: string;
|
|
11
11
|
/** sandbox image override — `--image` */
|
|
12
12
|
image?: string;
|
|
13
|
+
/** whether credentials reach the sandbox — `--no-keys` sets this false */
|
|
14
|
+
keys?: boolean;
|
|
13
15
|
/** answer the sandbox's install question without asking — `--yes` */
|
|
14
16
|
yes?: boolean;
|
|
15
17
|
}
|
package/dist/engine.js
CHANGED
package/dist/keys.d.ts
CHANGED
|
@@ -17,24 +17,49 @@ export type Service = (typeof SERVICES)[number];
|
|
|
17
17
|
/** Anything the keyring can hold a credential for. */
|
|
18
18
|
export declare const OWNERS: readonly ["openai", "anthropic", "google", "vertex", "openrouter", "exa"];
|
|
19
19
|
export type KeyOwner = Provider | Service;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
/**
|
|
21
|
+
* One way a credential can arrive.
|
|
22
|
+
*
|
|
23
|
+
* A provider that accepts two accepts two of these, and they agree about
|
|
24
|
+
* nothing: not the variable, not whether the value is the secret or a path to
|
|
25
|
+
* it, not where you go to get one. So the alternative is a whole form rather
|
|
26
|
+
* than a wider `holds`.
|
|
27
|
+
*/
|
|
28
|
+
export interface CredentialForm {
|
|
25
29
|
/** what the value is: a secret string, or a path to a credentials file */
|
|
26
30
|
holds: 'secret' | 'file';
|
|
27
|
-
|
|
31
|
+
/** environment variable the library reads */
|
|
32
|
+
env: string;
|
|
28
33
|
/** where to get one, printed when there is none */
|
|
29
34
|
where: string;
|
|
30
35
|
}
|
|
36
|
+
interface ProviderShape {
|
|
37
|
+
/** whether this is somewhere a model lives, or something a tool calls */
|
|
38
|
+
kind: 'model' | 'service';
|
|
39
|
+
label: string;
|
|
40
|
+
/** the ways in, first being the one this provider is usually reached by */
|
|
41
|
+
forms: [CredentialForm, ...CredentialForm[]];
|
|
42
|
+
}
|
|
31
43
|
/**
|
|
32
|
-
* Vertex is the odd one
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
44
|
+
* Vertex is the odd one, and it is odd twice.
|
|
45
|
+
*
|
|
46
|
+
* Its usual credential is not a key at all: the GenAI SDK resolves Application
|
|
47
|
+
* Default Credentials itself, so what is stored is a service-account *file* and
|
|
48
|
+
* what is exported is a path. But it also accepts an express-mode api key,
|
|
49
|
+
* which is an ordinary secret under an entirely different variable. The two are
|
|
50
|
+
* alternatives — express mode addresses no project — so which form a credential
|
|
51
|
+
* is gets decided per entry rather than per provider.
|
|
36
52
|
*/
|
|
37
53
|
export declare const SHAPES: Record<KeyOwner, ProviderShape>;
|
|
54
|
+
/**
|
|
55
|
+
* The form a provider is usually reached by — for the questions that have to
|
|
56
|
+
* have one answer, like which variable to name when nothing is set yet.
|
|
57
|
+
*/
|
|
58
|
+
export declare function form(provider: KeyOwner): CredentialForm;
|
|
59
|
+
/** Every variable a provider's credential could arrive in, usual one first. */
|
|
60
|
+
export declare function envNames(provider: KeyOwner): string[];
|
|
61
|
+
/** The variable this particular credential occupies. */
|
|
62
|
+
export declare function envOf(entry: Pick<KeyEntry, 'provider' | 'holds' | 'env'>): string;
|
|
38
63
|
export declare function isProvider(name: string): name is Provider;
|
|
39
64
|
export declare function isOwner(name: string): name is KeyOwner;
|
|
40
65
|
export declare function assertOwner(name: string): KeyOwner;
|
|
@@ -52,6 +77,11 @@ export interface KeyEntry {
|
|
|
52
77
|
holds: 'secret' | 'file';
|
|
53
78
|
/** the secret itself, or a path relative to the key directory */
|
|
54
79
|
value: string;
|
|
80
|
+
/** the variable it is exported as; absent on entries written before providers had two */
|
|
81
|
+
env?: string;
|
|
82
|
+
/** vertex, file-shaped only: what the library would otherwise have to be told twice */
|
|
83
|
+
project?: string;
|
|
84
|
+
location?: string;
|
|
55
85
|
addedAt: string;
|
|
56
86
|
check?: KeyCheck;
|
|
57
87
|
}
|
|
@@ -65,6 +95,13 @@ export declare function parseRef(ref: string): {
|
|
|
65
95
|
provider: KeyOwner;
|
|
66
96
|
name?: string;
|
|
67
97
|
};
|
|
98
|
+
/** What a credential cannot say about itself. */
|
|
99
|
+
export interface KeyMeta {
|
|
100
|
+
/** GCP project id, for a Vertex service account */
|
|
101
|
+
project?: string;
|
|
102
|
+
/** GCP region, or `global` */
|
|
103
|
+
location?: string;
|
|
104
|
+
}
|
|
68
105
|
export declare class KeyStore {
|
|
69
106
|
#private;
|
|
70
107
|
private constructor();
|
|
@@ -81,13 +118,21 @@ export declare class KeyStore {
|
|
|
81
118
|
* directory: the point of a store is that the credential survives the
|
|
82
119
|
* original being moved, renamed or cleaned up, and a stored path that
|
|
83
120
|
* silently stops resolving is worse than no store at all.
|
|
121
|
+
*
|
|
122
|
+
* Which form a value is, when the provider accepts two, is read off the
|
|
123
|
+
* value: a path that is there is a credentials file, and anything else is a
|
|
124
|
+
* secret. Asking would be a flag to get wrong, and a service-account key
|
|
125
|
+
* and an api key are not mistakable for one another.
|
|
84
126
|
*/
|
|
85
|
-
add(provider: KeyOwner, name: string, raw: string): KeyEntry;
|
|
127
|
+
add(provider: KeyOwner, name: string, raw: string, meta?: KeyMeta): KeyEntry;
|
|
86
128
|
remove(provider: KeyOwner, name: string): boolean;
|
|
87
129
|
use(provider: KeyOwner, name: string): KeyEntry;
|
|
88
130
|
record(entry: KeyEntry, check: KeyCheck): void;
|
|
89
131
|
save(): void;
|
|
90
|
-
/**
|
|
132
|
+
/**
|
|
133
|
+
* Absolute path behind a file-shaped entry. Stored entries name a file in
|
|
134
|
+
* the key directory; an ambient one already knows where it is.
|
|
135
|
+
*/
|
|
91
136
|
fileOf(entry: KeyEntry): string;
|
|
92
137
|
/** The plaintext an entry stands for — the only way out of the store. */
|
|
93
138
|
reveal(entry: KeyEntry): string;
|
|
@@ -110,6 +155,44 @@ export declare class KeyStore {
|
|
|
110
155
|
*/
|
|
111
156
|
export declare function mask(secret: string): string;
|
|
112
157
|
export declare function describe(store: KeyStore, entry: KeyEntry): string;
|
|
158
|
+
/**
|
|
159
|
+
* A credential the keyring does not hold but the libraries will nonetheless
|
|
160
|
+
* find: a variable already in the environment, or the file `gcloud auth
|
|
161
|
+
* application-default login` writes.
|
|
162
|
+
*
|
|
163
|
+
* These have to be listed, because they are the reason a provider works when
|
|
164
|
+
* `zen key ls` says there is nothing for it — and the reason one keeps working
|
|
165
|
+
* after its entry is removed.
|
|
166
|
+
*/
|
|
167
|
+
export interface Ambient {
|
|
168
|
+
provider: KeyOwner;
|
|
169
|
+
/** the variable it arrived in; absent when it was found where the SDK looks */
|
|
170
|
+
env?: string;
|
|
171
|
+
holds: 'secret' | 'file';
|
|
172
|
+
value: string;
|
|
173
|
+
}
|
|
174
|
+
/** How an ambient credential is named on the command line: it has no key name. */
|
|
175
|
+
export declare function ambientId(cred: Ambient): string;
|
|
176
|
+
/**
|
|
177
|
+
* Where `gcloud auth application-default login` leaves its credentials. The
|
|
178
|
+
* GenAI SDK reads this without being told to, so it counts even though nothing
|
|
179
|
+
* in the environment mentions it.
|
|
180
|
+
*/
|
|
181
|
+
export declare function gcloudAdc(): string | undefined;
|
|
182
|
+
export declare function ambient(store: KeyStore, only?: KeyOwner[]): Ambient[];
|
|
183
|
+
/**
|
|
184
|
+
* Every credential variable this process is carrying, whatever put it there.
|
|
185
|
+
*
|
|
186
|
+
* Read off the environment rather than off the store, and deliberately: by the
|
|
187
|
+
* time anyone asks, `materialize()` has already run, so the environment is the
|
|
188
|
+
* union of the keyring and whatever the shell brought — which is exactly the
|
|
189
|
+
* set of credentials the run is actually using.
|
|
190
|
+
*/
|
|
191
|
+
export declare function credentials(): {
|
|
192
|
+
env: string;
|
|
193
|
+
holds: 'secret' | 'file';
|
|
194
|
+
value: string;
|
|
195
|
+
}[];
|
|
113
196
|
/**
|
|
114
197
|
* Called before a run: says plainly that there is no way to reach a model,
|
|
115
198
|
* rather than letting the SDK raise it three frames deeper as a 401.
|