@zenera/cli 1.1.36 → 1.1.37
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/archive.js +1 -1
- package/dist/batch.js +2 -3
- package/dist/cache.js +1 -1
- package/dist/candidates.js +1 -1
- package/dist/catalog.d.ts +1 -1
- package/dist/catalog.js +1 -1
- package/dist/commands/cache.js +1 -1
- package/dist/commands/check.js +1 -3
- package/dist/commands/export.js +1 -3
- package/dist/commands/import.js +1 -2
- package/dist/commands/init.js +1 -4
- package/dist/commands/inspect.d.ts +1 -1
- package/dist/commands/inspect.js +1 -4
- package/dist/commands/key.js +1 -2
- package/dist/commands/list.js +4 -5
- package/dist/commands/memory.js +1 -2
- package/dist/commands/meta.js +2 -4
- package/dist/commands/models.d.ts +1 -1
- package/dist/commands/models.js +1 -2
- package/dist/commands/run.js +2 -2
- package/dist/commands/sandbox.js +1 -2
- package/dist/engine.d.ts +3 -90
- package/dist/engine.js +16 -302
- package/dist/history.js +1 -1
- package/dist/lib.d.ts +1 -2
- package/dist/lib.js +1 -2
- package/dist/liveness.d.ts +1 -1
- package/dist/liveness.js +1 -1
- package/dist/meta/host.d.ts +1 -2
- package/dist/meta/host.js +1 -2
- package/dist/modelref.d.ts +1 -1
- package/dist/podman.d.ts +1 -1
- package/dist/podman.js +1 -1
- package/dist/resolve.d.ts +2 -2
- package/dist/resolve.js +2 -3
- package/dist/resources.js +1 -1
- package/dist/sandbox.d.ts +2 -60
- package/dist/sandbox.js +1 -200
- package/dist/term.d.ts +1 -22
- package/dist/term.js +3 -26
- package/dist/tui/app.js +1 -1
- package/dist/tui/models.d.ts +1 -1
- package/dist/validate.d.ts +1 -2
- package/dist/validate.js +1 -3
- package/package.json +4 -3
- package/dist/audit.d.ts +0 -58
- package/dist/audit.js +0 -148
- package/dist/env.d.ts +0 -20
- package/dist/env.js +0 -65
- package/dist/home.d.ts +0 -35
- package/dist/home.js +0 -112
- package/dist/ids.d.ts +0 -12
- package/dist/ids.js +0 -44
- package/dist/image.d.ts +0 -16
- package/dist/image.js +0 -85
- package/dist/keys.d.ts +0 -269
- package/dist/keys.js +0 -624
- package/dist/projects.d.ts +0 -91
- package/dist/projects.js +0 -303
- package/dist/session.d.ts +0 -112
- package/dist/session.js +0 -188
package/dist/home.d.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
export declare function home(): string;
|
|
2
|
-
export declare const paths: {
|
|
3
|
-
home: typeof home;
|
|
4
|
-
projects: () => string;
|
|
5
|
-
keys: () => string;
|
|
6
|
-
keyDir: () => string;
|
|
7
|
-
faker: () => string;
|
|
8
|
-
/** what the meta agent runs on, which is a choice about a tool, not a project */
|
|
9
|
-
meta: () => string;
|
|
10
|
-
/** work already done and worth keeping: vectors, parses, model listings */
|
|
11
|
-
cache: () => string;
|
|
12
|
-
};
|
|
13
|
-
/** Creates a directory owner-only, and leaves an existing one's mode alone. */
|
|
14
|
-
export declare function ensureDir(dir: string, mode?: number): string;
|
|
15
|
-
export declare function ensureHome(): string;
|
|
16
|
-
/**
|
|
17
|
-
* Refuses to read a credential file that anyone else can read, the way `ssh`
|
|
18
|
-
* does. A world-readable key is not a warning-level event: warnings are
|
|
19
|
-
* ignored, and the whole point of the store is that the secret is not lying
|
|
20
|
-
* around in the open.
|
|
21
|
-
*
|
|
22
|
-
* Windows reports a mode that means nothing here, so the check is POSIX-only.
|
|
23
|
-
*/
|
|
24
|
-
export declare function assertPrivate(path: string): void;
|
|
25
|
-
export declare function readJson<T>(path: string, fallback: T): Promise<T>;
|
|
26
|
-
/**
|
|
27
|
-
* Write to a sibling and rename. A crash then leaves either the old file or the
|
|
28
|
-
* new one, never a half-written registry — which matters more here than it
|
|
29
|
-
* looks, because the alternative is a corrupt `keys.json` locking someone out
|
|
30
|
-
* of every provider at once.
|
|
31
|
-
*
|
|
32
|
-
* Synchronous on purpose: this is also called from exit paths.
|
|
33
|
-
*/
|
|
34
|
-
export declare function writeJson(path: string, value: unknown, mode?: number): void;
|
|
35
|
-
//# sourceMappingURL=home.d.ts.map
|
package/dist/home.js
DELETED
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { chmodSync, mkdirSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
3
|
-
import { readFile } from 'node:fs/promises';
|
|
4
|
-
import { homedir } from 'node:os';
|
|
5
|
-
import { dirname, join } from 'node:path';
|
|
6
|
-
import { CliError, EXIT } from "./term.js";
|
|
7
|
-
// ---------------------------------------------------------------------------
|
|
8
|
-
// The home directory
|
|
9
|
-
//
|
|
10
|
-
// ~/.zenera/neo holds exactly two kinds of thing: credentials, which belong to
|
|
11
|
-
// the machine and never to a project, and an index of where projects are, which
|
|
12
|
-
// is a convenience and is allowed to be wrong.
|
|
13
|
-
//
|
|
14
|
-
// `ZENERA_HOME` moves the whole tree. That is what makes any of this testable
|
|
15
|
-
// and what lets CI start from an empty one.
|
|
16
|
-
// ---------------------------------------------------------------------------
|
|
17
|
-
const DIR_MODE = 0o700;
|
|
18
|
-
const FILE_MODE = 0o600;
|
|
19
|
-
export function home() {
|
|
20
|
-
const override = process.env.ZENERA_HOME?.trim();
|
|
21
|
-
return override ? override : join(homedir(), '.zenera', 'neo');
|
|
22
|
-
}
|
|
23
|
-
export const paths = {
|
|
24
|
-
home,
|
|
25
|
-
projects: () => join(home(), 'projects.json'),
|
|
26
|
-
keys: () => join(home(), 'keys.json'),
|
|
27
|
-
keyDir: () => join(home(), 'keys'),
|
|
28
|
-
faker: () => join(home(), 'faker'),
|
|
29
|
-
/** what the meta agent runs on, which is a choice about a tool, not a project */
|
|
30
|
-
meta: () => join(home(), 'meta.json'),
|
|
31
|
-
/** work already done and worth keeping: vectors, parses, model listings */
|
|
32
|
-
cache: () => join(home(), 'cache'),
|
|
33
|
-
};
|
|
34
|
-
/** Creates a directory owner-only, and leaves an existing one's mode alone. */
|
|
35
|
-
export function ensureDir(dir, mode = DIR_MODE) {
|
|
36
|
-
mkdirSync(dir, { recursive: true, mode });
|
|
37
|
-
return dir;
|
|
38
|
-
}
|
|
39
|
-
export function ensureHome() {
|
|
40
|
-
return ensureDir(home());
|
|
41
|
-
}
|
|
42
|
-
// ---------------------------------------------------------------------------
|
|
43
|
-
// Permissions
|
|
44
|
-
// ---------------------------------------------------------------------------
|
|
45
|
-
/**
|
|
46
|
-
* Refuses to read a credential file that anyone else can read, the way `ssh`
|
|
47
|
-
* does. A world-readable key is not a warning-level event: warnings are
|
|
48
|
-
* ignored, and the whole point of the store is that the secret is not lying
|
|
49
|
-
* around in the open.
|
|
50
|
-
*
|
|
51
|
-
* Windows reports a mode that means nothing here, so the check is POSIX-only.
|
|
52
|
-
*/
|
|
53
|
-
export function assertPrivate(path) {
|
|
54
|
-
if (process.platform === 'win32') {
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
let mode;
|
|
58
|
-
try {
|
|
59
|
-
mode = statSync(path).mode;
|
|
60
|
-
}
|
|
61
|
-
catch {
|
|
62
|
-
return; // absent is fine; it will be created with the right mode
|
|
63
|
-
}
|
|
64
|
-
const open = mode & 0o077;
|
|
65
|
-
if (open !== 0) {
|
|
66
|
-
const octal = (mode & 0o777).toString(8).padStart(3, '0');
|
|
67
|
-
throw new CliError(`permissions ${octal} on ${path} are too open`, EXIT.credentials, `run: chmod 600 ${path}`);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
// ---------------------------------------------------------------------------
|
|
71
|
-
// JSON files
|
|
72
|
-
// ---------------------------------------------------------------------------
|
|
73
|
-
export async function readJson(path, fallback) {
|
|
74
|
-
let text;
|
|
75
|
-
try {
|
|
76
|
-
text = await readFile(path, 'utf8');
|
|
77
|
-
}
|
|
78
|
-
catch (err) {
|
|
79
|
-
if (err.code === 'ENOENT') {
|
|
80
|
-
return fallback;
|
|
81
|
-
}
|
|
82
|
-
throw err;
|
|
83
|
-
}
|
|
84
|
-
try {
|
|
85
|
-
return JSON.parse(text);
|
|
86
|
-
}
|
|
87
|
-
catch {
|
|
88
|
-
throw new CliError(`${path} is not valid JSON`, EXIT.invalid, 'fix or delete the file');
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Write to a sibling and rename. A crash then leaves either the old file or the
|
|
93
|
-
* new one, never a half-written registry — which matters more here than it
|
|
94
|
-
* looks, because the alternative is a corrupt `keys.json` locking someone out
|
|
95
|
-
* of every provider at once.
|
|
96
|
-
*
|
|
97
|
-
* Synchronous on purpose: this is also called from exit paths.
|
|
98
|
-
*/
|
|
99
|
-
export function writeJson(path, value, mode = FILE_MODE) {
|
|
100
|
-
ensureDir(dirname(path));
|
|
101
|
-
const tmp = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
102
|
-
try {
|
|
103
|
-
writeFileSync(tmp, `${JSON.stringify(value, null, 2)}\n`, { mode });
|
|
104
|
-
chmodSync(tmp, mode);
|
|
105
|
-
renameSync(tmp, path);
|
|
106
|
-
}
|
|
107
|
-
catch (err) {
|
|
108
|
-
rmSync(tmp, { force: true });
|
|
109
|
-
throw err;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
//# sourceMappingURL=home.js.map
|
package/dist/ids.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export declare function stamp(now?: Date): string;
|
|
2
|
-
/**
|
|
3
|
-
* Ids arrive from `--session` and `--run` and become path segments, so they are
|
|
4
|
-
* checked rather than trusted. The shape has no `.` and no separator, which
|
|
5
|
-
* makes traversal unrepresentable rather than merely rejected.
|
|
6
|
-
*/
|
|
7
|
-
export declare function isStamp(value: string): boolean;
|
|
8
|
-
/** Human-readable form of a stamp: `2026-08-25 14:30:12`. */
|
|
9
|
-
export declare function stampDate(id: string): string;
|
|
10
|
-
/** ISO instant a stamp names, for `ago()`. Local time in, local time out. */
|
|
11
|
-
export declare function stampInstant(id: string): string | undefined;
|
|
12
|
-
//# sourceMappingURL=ids.d.ts.map
|
package/dist/ids.js
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { randomBytes } from 'node:crypto';
|
|
2
|
-
// ---------------------------------------------------------------------------
|
|
3
|
-
// Identifiers
|
|
4
|
-
//
|
|
5
|
-
// `YYYYMMDD-HHMMSS-xxxx`, local time, four hex characters of entropy.
|
|
6
|
-
//
|
|
7
|
-
// Sortable as a plain string, readable without a decoder, and collision-free
|
|
8
|
-
// when two runs start inside the same second. An epoch integer gives up the
|
|
9
|
-
// first two properties and a uuid gives up all three.
|
|
10
|
-
// ---------------------------------------------------------------------------
|
|
11
|
-
const STAMP = /^\d{8}-\d{6}-[0-9a-f]{4}$/;
|
|
12
|
-
export function stamp(now = new Date()) {
|
|
13
|
-
const p = (n, w = 2) => String(n).padStart(w, '0');
|
|
14
|
-
const date = `${now.getFullYear()}${p(now.getMonth() + 1)}${p(now.getDate())}`;
|
|
15
|
-
const time = `${p(now.getHours())}${p(now.getMinutes())}${p(now.getSeconds())}`;
|
|
16
|
-
return `${date}-${time}-${randomBytes(2).toString('hex')}`;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Ids arrive from `--session` and `--run` and become path segments, so they are
|
|
20
|
-
* checked rather than trusted. The shape has no `.` and no separator, which
|
|
21
|
-
* makes traversal unrepresentable rather than merely rejected.
|
|
22
|
-
*/
|
|
23
|
-
export function isStamp(value) {
|
|
24
|
-
return STAMP.test(value);
|
|
25
|
-
}
|
|
26
|
-
/** Human-readable form of a stamp: `2026-08-25 14:30:12`. */
|
|
27
|
-
export function stampDate(id) {
|
|
28
|
-
if (!isStamp(id)) {
|
|
29
|
-
return id;
|
|
30
|
-
}
|
|
31
|
-
const [d, t] = id.split('-');
|
|
32
|
-
return (`${d.slice(0, 4)}-${d.slice(4, 6)}-${d.slice(6, 8)} ` +
|
|
33
|
-
`${t.slice(0, 2)}:${t.slice(2, 4)}:${t.slice(4, 6)}`);
|
|
34
|
-
}
|
|
35
|
-
/** ISO instant a stamp names, for `ago()`. Local time in, local time out. */
|
|
36
|
-
export function stampInstant(id) {
|
|
37
|
-
if (!isStamp(id)) {
|
|
38
|
-
return undefined;
|
|
39
|
-
}
|
|
40
|
-
const [d, t] = id.split('-');
|
|
41
|
-
const at = new Date(Number(d.slice(0, 4)), Number(d.slice(4, 6)) - 1, Number(d.slice(6, 8)), Number(t.slice(0, 2)), Number(t.slice(2, 4)), Number(t.slice(4, 6)));
|
|
42
|
-
return Number.isNaN(at.getTime()) ? undefined : at.toISOString();
|
|
43
|
-
}
|
|
44
|
-
//# sourceMappingURL=ids.js.map
|
package/dist/image.d.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { type SandboxConfig } from '@zenera/neo';
|
|
2
|
-
export interface ResolvedBuild {
|
|
3
|
-
/** the image reference to run, and to build under */
|
|
4
|
-
tag: string;
|
|
5
|
-
/** absolute path to the Dockerfile */
|
|
6
|
-
dockerfile: string;
|
|
7
|
-
/** absolute path to the build context */
|
|
8
|
-
context: string;
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* What the config's `build:` block means on this machine, or nothing if it has
|
|
12
|
-
* none. Both paths are resolved against the project root and refused if they
|
|
13
|
-
* escape it — a project is data someone else may have written.
|
|
14
|
-
*/
|
|
15
|
-
export declare function resolveBuild(root: string, config?: SandboxConfig): ResolvedBuild | undefined;
|
|
16
|
-
//# sourceMappingURL=image.d.ts.map
|
package/dist/image.js
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { projectDir, projectFile } from '@zenera/neo';
|
|
2
|
-
import { createHash } from 'node:crypto';
|
|
3
|
-
import { readFileSync, readdirSync } from 'node:fs';
|
|
4
|
-
import { dirname, join, relative, sep } from 'node:path';
|
|
5
|
-
// ---------------------------------------------------------------------------
|
|
6
|
-
// Building the sandbox image
|
|
7
|
-
//
|
|
8
|
-
// `image:` names something to pull; `build:` names a Dockerfile to build. The
|
|
9
|
-
// library never learns the difference — it is handed a resolved reference and
|
|
10
|
-
// runs it — because building is a host concern with a container engine
|
|
11
|
-
// attached to it, and that is the line this CLI exists on the other side of.
|
|
12
|
-
//
|
|
13
|
-
// The tag is a function of what goes into the image, and it has to be: the
|
|
14
|
-
// container's name in @zenera/neo hashes `spec.image`, so a tag that stayed put
|
|
15
|
-
// while the Dockerfile changed would leave a `persist: true` container running
|
|
16
|
-
// last week's filesystem, with nothing anywhere saying so. Hashing the context
|
|
17
|
-
// as well as the Dockerfile means an edit to either yields a new tag, a new
|
|
18
|
-
// container name, and a build — which is the only honest answer.
|
|
19
|
-
// ---------------------------------------------------------------------------
|
|
20
|
-
/** Not scoped: a `/` or an `@` is not a legal image tag. */
|
|
21
|
-
const TAG = 'localhost/zenera-sandbox';
|
|
22
|
-
/**
|
|
23
|
-
* A build context is meant to be small — a Dockerfile and whatever it copies.
|
|
24
|
-
* Hashing a directory someone pointed at their whole home folder would hang
|
|
25
|
-
* before it was wrong, so it stops and says which key to narrow.
|
|
26
|
-
*/
|
|
27
|
-
const MAX_CONTEXT_FILES = 2_000;
|
|
28
|
-
/**
|
|
29
|
-
* What the config's `build:` block means on this machine, or nothing if it has
|
|
30
|
-
* none. Both paths are resolved against the project root and refused if they
|
|
31
|
-
* escape it — a project is data someone else may have written.
|
|
32
|
-
*/
|
|
33
|
-
export function resolveBuild(root, config) {
|
|
34
|
-
if (!config?.build) {
|
|
35
|
-
return undefined;
|
|
36
|
-
}
|
|
37
|
-
const dockerfile = projectFile(root, config.build.dockerfile, 'sandbox.build.dockerfile');
|
|
38
|
-
const context = config.build.context
|
|
39
|
-
? projectDir(root, config.build.context, 'sandbox.build.context')
|
|
40
|
-
: dirname(dockerfile);
|
|
41
|
-
return { tag: `${TAG}:${digest(dockerfile, context)}`, dockerfile, context };
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* The content address of a build: the Dockerfile, then every file the build can
|
|
45
|
-
* see, by path and by content.
|
|
46
|
-
*
|
|
47
|
-
* `.dockerignore` is not read. The engine honours it and we do not, so an
|
|
48
|
-
* ignored file that changes yields a new tag and a build that produces the same
|
|
49
|
-
* image — wasteful, never wrong, and the alternative is reimplementing a match
|
|
50
|
-
* syntax whose disagreements would be silent.
|
|
51
|
-
*/
|
|
52
|
-
function digest(dockerfile, context) {
|
|
53
|
-
const hash = createHash('sha256');
|
|
54
|
-
hash.update(readFileSync(dockerfile));
|
|
55
|
-
for (const rel of walk(context)) {
|
|
56
|
-
// The separator is hashed as posix so the same tree tags the same on
|
|
57
|
-
// any host.
|
|
58
|
-
hash.update(`\0${rel.split(sep).join('/')}\0`);
|
|
59
|
-
hash.update(readFileSync(join(context, rel)));
|
|
60
|
-
}
|
|
61
|
-
return hash.digest('hex').slice(0, 12);
|
|
62
|
-
}
|
|
63
|
-
/** Every file under `dir`, relative and sorted, so the digest is stable. */
|
|
64
|
-
function walk(dir) {
|
|
65
|
-
const found = [];
|
|
66
|
-
const pending = [dir];
|
|
67
|
-
while (pending.length > 0) {
|
|
68
|
-
const at = pending.pop();
|
|
69
|
-
for (const entry of readdirSync(at, { withFileTypes: true })) {
|
|
70
|
-
const path = join(at, entry.name);
|
|
71
|
-
if (entry.isDirectory()) {
|
|
72
|
-
pending.push(path);
|
|
73
|
-
}
|
|
74
|
-
else if (entry.isFile()) {
|
|
75
|
-
found.push(relative(dir, path));
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
if (found.length > MAX_CONTEXT_FILES) {
|
|
79
|
-
throw new Error(`sandbox.build.context: ${dir} holds more than ${MAX_CONTEXT_FILES} files — ` +
|
|
80
|
-
'point `context:` at the directory the build actually copies from');
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
return found.sort();
|
|
84
|
-
}
|
|
85
|
-
//# sourceMappingURL=image.js.map
|
package/dist/keys.d.ts
DELETED
|
@@ -1,269 +0,0 @@
|
|
|
1
|
-
/** The vendors a *model* can come from — the library's provider kinds, verbatim. */
|
|
2
|
-
export declare const PROVIDERS: readonly ["openai", "anthropic", "google", "vertex", "openrouter"];
|
|
3
|
-
export type Provider = (typeof PROVIDERS)[number];
|
|
4
|
-
/**
|
|
5
|
-
* Credentials that are not a way to reach a model.
|
|
6
|
-
*
|
|
7
|
-
* A tool can need a key as much as a model can, and the reasons a keyring
|
|
8
|
-
* exists — one place, 0600, materialised into the environment before a run —
|
|
9
|
-
* do not care which. What does care is everything that reasons about *models*:
|
|
10
|
-
* a machine holding nothing but an Exa key cannot run an agent, and saying it
|
|
11
|
-
* can would move the failure from `zen run`'s first line to its first turn.
|
|
12
|
-
* Hence two lists rather than one longer one, and `kind` on the shape so the
|
|
13
|
-
* few places that must tell them apart are made to say which they mean.
|
|
14
|
-
*/
|
|
15
|
-
export declare const SERVICES: readonly ["exa"];
|
|
16
|
-
export type Service = (typeof SERVICES)[number];
|
|
17
|
-
/**
|
|
18
|
-
* Somewhere every model can be reached through, with one key. Its own list for
|
|
19
|
-
* the same reason as `SERVICES`: it is neither a vendor a model comes from nor
|
|
20
|
-
* a tool, and a gateway entry only counts while the keyring is in its mode.
|
|
21
|
-
*/
|
|
22
|
-
export declare const GATEWAYS: readonly ["bifrost"];
|
|
23
|
-
export type GatewayOwner = (typeof GATEWAYS)[number];
|
|
24
|
-
/** `direct` calls each vendor with its own key; `bifrost` sends everything through the gateway. */
|
|
25
|
-
export type KeyMode = 'direct' | GatewayOwner;
|
|
26
|
-
export declare const MODES: readonly KeyMode[];
|
|
27
|
-
/** Anything the keyring can hold a credential for. */
|
|
28
|
-
export declare const OWNERS: readonly ["openai", "anthropic", "google", "vertex", "openrouter", "exa", "bifrost"];
|
|
29
|
-
export type KeyOwner = Provider | Service | GatewayOwner;
|
|
30
|
-
/**
|
|
31
|
-
* One way a credential can arrive.
|
|
32
|
-
*
|
|
33
|
-
* A provider that accepts two accepts two of these, and they agree about
|
|
34
|
-
* nothing: not the variable, not whether the value is the secret or a path to
|
|
35
|
-
* it, not where you go to get one. So the alternative is a whole form rather
|
|
36
|
-
* than a wider `holds`.
|
|
37
|
-
*/
|
|
38
|
-
export interface CredentialForm {
|
|
39
|
-
/** what the value is: a secret string, or a path to a credentials file */
|
|
40
|
-
holds: 'secret' | 'file';
|
|
41
|
-
/** environment variable the library reads */
|
|
42
|
-
env: string;
|
|
43
|
-
/** where to get one, printed when there is none */
|
|
44
|
-
where: string;
|
|
45
|
-
}
|
|
46
|
-
interface ProviderShape {
|
|
47
|
-
/** whether this is somewhere a model lives, something a tool calls, or a way to every model */
|
|
48
|
-
kind: 'model' | 'service' | 'gateway';
|
|
49
|
-
label: string;
|
|
50
|
-
/** the ways in, first being the one this provider is usually reached by */
|
|
51
|
-
forms: [CredentialForm, ...CredentialForm[]];
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Vertex is the odd one, and it is odd twice.
|
|
55
|
-
*
|
|
56
|
-
* Its usual credential is not a key at all: the GenAI SDK resolves Application
|
|
57
|
-
* Default Credentials itself, so what is stored is a service-account *file* and
|
|
58
|
-
* what is exported is a path. But it also accepts an express-mode api key,
|
|
59
|
-
* which is an ordinary secret under an entirely different variable. The two are
|
|
60
|
-
* alternatives — express mode addresses no project — so which form a credential
|
|
61
|
-
* is gets decided per entry rather than per provider.
|
|
62
|
-
*/
|
|
63
|
-
export declare const SHAPES: Record<KeyOwner, ProviderShape>;
|
|
64
|
-
/**
|
|
65
|
-
* The form a provider is usually reached by — for the questions that have to
|
|
66
|
-
* have one answer, like which variable to name when nothing is set yet.
|
|
67
|
-
*/
|
|
68
|
-
export declare function form(provider: KeyOwner): CredentialForm;
|
|
69
|
-
/** Every variable a provider's credential could arrive in, usual one first. */
|
|
70
|
-
export declare function envNames(provider: KeyOwner): string[];
|
|
71
|
-
/** Variables that name a credential *file* rather than carrying the secret. */
|
|
72
|
-
export declare const FILE_ENVS: string[];
|
|
73
|
-
/** The variable this particular credential occupies. */
|
|
74
|
-
export declare function envOf(entry: Pick<KeyEntry, 'provider' | 'holds' | 'env'>): string;
|
|
75
|
-
export declare function isProvider(name: string): name is Provider;
|
|
76
|
-
export declare function isOwner(name: string): name is KeyOwner;
|
|
77
|
-
export declare function isGateway(name: string): name is GatewayOwner;
|
|
78
|
-
export declare function assertOwner(name: string): KeyOwner;
|
|
79
|
-
/**
|
|
80
|
-
* `blocked` is the credential being fine and the *account* not: an api switched
|
|
81
|
-
* off in the project, a spent balance, a model this key was never granted. It
|
|
82
|
-
* is split out of `dead` because the two want opposite actions — rotating a key
|
|
83
|
-
* that authenticated perfectly is the wrong afternoon.
|
|
84
|
-
*/
|
|
85
|
-
export type Liveness = 'live' | 'dead' | 'blocked' | 'unknown';
|
|
86
|
-
export interface KeyCheck {
|
|
87
|
-
state: Liveness;
|
|
88
|
-
at: string;
|
|
89
|
-
/** the provider's own words when it said no, or ours when we could not ask */
|
|
90
|
-
detail?: string;
|
|
91
|
-
/** what to actually do about it, when the refusal implies something specific */
|
|
92
|
-
fix?: string;
|
|
93
|
-
}
|
|
94
|
-
export interface KeyEntry {
|
|
95
|
-
provider: KeyOwner;
|
|
96
|
-
/** unique within a provider; `default` unless the user says otherwise */
|
|
97
|
-
name: string;
|
|
98
|
-
holds: 'secret' | 'file';
|
|
99
|
-
/** the secret itself, or a path relative to the key directory */
|
|
100
|
-
value: string;
|
|
101
|
-
/** the variable it is exported as; absent on entries written before providers had two */
|
|
102
|
-
env?: string;
|
|
103
|
-
/** vertex, file-shaped only: what the library would otherwise have to be told twice */
|
|
104
|
-
project?: string;
|
|
105
|
-
location?: string;
|
|
106
|
-
/** gateway only: where it listens */
|
|
107
|
-
url?: string;
|
|
108
|
-
addedAt: string;
|
|
109
|
-
check?: KeyCheck;
|
|
110
|
-
}
|
|
111
|
-
export declare const keyId: (e: Pick<KeyEntry, "provider" | "name">) => string;
|
|
112
|
-
/**
|
|
113
|
-
* `provider` or `provider/name`. Returned separately rather than as a string so
|
|
114
|
-
* callers cannot accidentally re-split it, and so a name that is not a legal
|
|
115
|
-
* path segment is rejected here, once, before it can become a filename.
|
|
116
|
-
*/
|
|
117
|
-
export declare function parseRef(ref: string): {
|
|
118
|
-
provider: KeyOwner;
|
|
119
|
-
name?: string;
|
|
120
|
-
};
|
|
121
|
-
/** Which of a provider's forms a raw value is, decided by the value itself. */
|
|
122
|
-
export declare function formOf(provider: KeyOwner, raw: string): CredentialForm;
|
|
123
|
-
/**
|
|
124
|
-
* Throws on a name that is neither an endpoint nor shaped like a region — `usa`,
|
|
125
|
-
* or `us-central` with its digit missing — because those are 404s at the first
|
|
126
|
-
* call, a long way from the command that caused them. Returns false for a
|
|
127
|
-
* well-formed name that is merely unrecognised, which the caller reports and
|
|
128
|
-
* stores anyway: Google adds regions, and a list compiled here would be wrong
|
|
129
|
-
* before it was old.
|
|
130
|
-
*/
|
|
131
|
-
export declare function checkRegion(region: string): boolean;
|
|
132
|
-
/** What a credential cannot say about itself. */
|
|
133
|
-
export interface KeyMeta {
|
|
134
|
-
/** GCP project id, for a Vertex service account */
|
|
135
|
-
project?: string;
|
|
136
|
-
/** GCP region, or `global` */
|
|
137
|
-
location?: string;
|
|
138
|
-
/** gateway base url */
|
|
139
|
-
url?: string;
|
|
140
|
-
}
|
|
141
|
-
export declare class KeyStore {
|
|
142
|
-
#private;
|
|
143
|
-
private constructor();
|
|
144
|
-
static open(): Promise<KeyStore>;
|
|
145
|
-
get entries(): readonly KeyEntry[];
|
|
146
|
-
/** Every entry for a provider, the active one first. */
|
|
147
|
-
for(provider: KeyOwner): KeyEntry[];
|
|
148
|
-
find(provider: KeyOwner, name: string): KeyEntry | undefined;
|
|
149
|
-
/** The entry a run would use, or undefined when the provider has none. */
|
|
150
|
-
active(provider: KeyOwner): KeyEntry | undefined;
|
|
151
|
-
isActive(entry: KeyEntry): boolean;
|
|
152
|
-
/**
|
|
153
|
-
* Adds or replaces. A file-shaped credential is *copied* into the key
|
|
154
|
-
* directory: the point of a store is that the credential survives the
|
|
155
|
-
* original being moved, renamed or cleaned up, and a stored path that
|
|
156
|
-
* silently stops resolving is worse than no store at all.
|
|
157
|
-
*
|
|
158
|
-
* Which form a value is, when the provider accepts two, is read off the
|
|
159
|
-
* value: a path that is there is a credentials file, and anything else is a
|
|
160
|
-
* secret. Asking would be a flag to get wrong, and a service-account key
|
|
161
|
-
* and an api key are not mistakable for one another.
|
|
162
|
-
*/
|
|
163
|
-
add(provider: KeyOwner, name: string, raw: string, meta?: KeyMeta): KeyEntry;
|
|
164
|
-
remove(provider: KeyOwner, name: string): boolean;
|
|
165
|
-
use(provider: KeyOwner, name: string): KeyEntry;
|
|
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. */
|
|
168
|
-
get mode(): KeyMode;
|
|
169
|
-
get modeFrom(): 'env' | 'keyring';
|
|
170
|
-
/** Refuses a gateway mode with no gateway to send anything to. */
|
|
171
|
-
setMode(mode: KeyMode): void;
|
|
172
|
-
save(): void;
|
|
173
|
-
/**
|
|
174
|
-
* Absolute path behind a file-shaped entry. Stored entries name a file in
|
|
175
|
-
* the key directory; an ambient one already knows where it is.
|
|
176
|
-
*/
|
|
177
|
-
fileOf(entry: KeyEntry): string;
|
|
178
|
-
/** The plaintext an entry stands for — the only way out of the store. */
|
|
179
|
-
reveal(entry: KeyEntry): string;
|
|
180
|
-
/**
|
|
181
|
-
* Which GCP project a Vertex entry addresses, and how that was decided —
|
|
182
|
-
* the same order the library resolves it in, so what is shown is what will
|
|
183
|
-
* be called. A service-account file states its own project, and nothing was
|
|
184
|
-
* ever stored for it, so an entry with no `project` is not an entry with no
|
|
185
|
-
* project: it is one whose project is only knowable by reading the file.
|
|
186
|
-
*/
|
|
187
|
-
projectOf(entry: KeyEntry): {
|
|
188
|
-
id: string;
|
|
189
|
-
from: 'env' | 'stored' | 'file';
|
|
190
|
-
} | undefined;
|
|
191
|
-
/**
|
|
192
|
-
* What the library would see. Real environment variables win, so CI,
|
|
193
|
-
* `docker run -e` and a one-off `OPENAI_API_KEY=… zen run` all behave
|
|
194
|
-
* exactly as they did before the store existed.
|
|
195
|
-
*
|
|
196
|
-
* Services are included by default, because a tool reads its key from the
|
|
197
|
-
* environment for exactly the same reason a model adapter does.
|
|
198
|
-
*
|
|
199
|
-
* In a gateway mode the gateway's key replaces every vendor's, so those are
|
|
200
|
-
* withheld: nothing would read them, and the sandbox would be handed keys
|
|
201
|
-
* the run never needed. In `direct` mode it is the gateway that is withheld,
|
|
202
|
-
* since its url alone is what switches the library over.
|
|
203
|
-
*/
|
|
204
|
-
environment(only?: KeyOwner[]): Record<string, string>;
|
|
205
|
-
/**
|
|
206
|
-
* Copies the keyring's active credentials into `process.env`, the only
|
|
207
|
-
* place the library looks. Must run before anything that reads them —
|
|
208
|
-
* in `bifrost` mode this is what sets `BIFROST_BASE_URL`, so `gatewayOf()`
|
|
209
|
-
* returns undefined until it has. Returns what it set.
|
|
210
|
-
*/
|
|
211
|
-
materialize(only?: KeyOwner[]): Record<string, string>;
|
|
212
|
-
}
|
|
213
|
-
/**
|
|
214
|
-
* Enough of a secret to recognise it, never enough to use it. Short values are
|
|
215
|
-
* hidden outright rather than half-shown — a twelve-character secret with eight
|
|
216
|
-
* characters visible is not masked, it is inconvenienced.
|
|
217
|
-
*/
|
|
218
|
-
export declare function mask(secret: string): string;
|
|
219
|
-
export declare function describe(store: KeyStore, entry: KeyEntry): string;
|
|
220
|
-
/**
|
|
221
|
-
* A credential the keyring does not hold but the libraries will nonetheless
|
|
222
|
-
* find: a variable already in the environment, or the file `gcloud auth
|
|
223
|
-
* application-default login` writes.
|
|
224
|
-
*
|
|
225
|
-
* These have to be listed, because they are the reason a provider works when
|
|
226
|
-
* `zen key ls` says there is nothing for it — and the reason one keeps working
|
|
227
|
-
* after its entry is removed.
|
|
228
|
-
*/
|
|
229
|
-
export interface Ambient {
|
|
230
|
-
provider: KeyOwner;
|
|
231
|
-
/** the variable it arrived in; absent when it was found where the SDK looks */
|
|
232
|
-
env?: string;
|
|
233
|
-
holds: 'secret' | 'file';
|
|
234
|
-
value: string;
|
|
235
|
-
}
|
|
236
|
-
/** How an ambient credential is named on the command line: it has no key name. */
|
|
237
|
-
export declare function ambientId(cred: Ambient): string;
|
|
238
|
-
/**
|
|
239
|
-
* Where `gcloud auth application-default login` leaves its credentials. The
|
|
240
|
-
* GenAI SDK reads this without being told to, so it counts even though nothing
|
|
241
|
-
* in the environment mentions it.
|
|
242
|
-
*/
|
|
243
|
-
export declare function gcloudAdc(): string | undefined;
|
|
244
|
-
export declare function ambient(store: KeyStore, only?: KeyOwner[]): Ambient[];
|
|
245
|
-
/**
|
|
246
|
-
* Every credential variable this process is carrying, whatever put it there.
|
|
247
|
-
*
|
|
248
|
-
* Read off the environment rather than off the store, and deliberately: by the
|
|
249
|
-
* time anyone asks, `materialize()` has already run, so the environment is the
|
|
250
|
-
* union of the keyring and whatever the shell brought — which is exactly the
|
|
251
|
-
* set of credentials the run is actually using.
|
|
252
|
-
*/
|
|
253
|
-
export declare function credentials(): {
|
|
254
|
-
env: string;
|
|
255
|
-
holds: 'secret' | 'file';
|
|
256
|
-
value: string;
|
|
257
|
-
}[];
|
|
258
|
-
/**
|
|
259
|
-
* Called before a run: says plainly that there is no way to reach a model,
|
|
260
|
-
* rather than letting the SDK raise it three frames deeper as a 401.
|
|
261
|
-
*
|
|
262
|
-
* Only model providers count. A keyring holding nothing but an Exa key can
|
|
263
|
-
* search the web and cannot think, and reporting that as usable would trade
|
|
264
|
-
* one clear error here for an obscure one on the first turn.
|
|
265
|
-
*/
|
|
266
|
-
export declare function assertUsable(store: KeyStore): void;
|
|
267
|
-
export declare function assertNotEmpty(store: KeyStore): void;
|
|
268
|
-
export {};
|
|
269
|
-
//# sourceMappingURL=keys.d.ts.map
|