@zenera/cli 1.1.0 → 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 +79 -19
- 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/commands/sandbox.js +226 -22
- package/dist/engine.d.ts +3 -1
- package/dist/engine.js +10 -2
- package/dist/image.d.ts +16 -0
- package/dist/image.js +85 -0
- package/dist/keys.d.ts +95 -12
- package/dist/keys.js +175 -34
- package/dist/lib.d.ts +2 -2
- package/dist/lib.js +2 -2
- package/dist/liveness.d.ts +16 -6
- package/dist/liveness.js +74 -23
- package/dist/main.js +0 -0
- package/dist/podman.d.ts +57 -1
- package/dist/podman.js +177 -12
- package/dist/projects.d.ts +18 -0
- package/dist/projects.js +60 -1
- package/dist/sandbox.d.ts +14 -1
- package/dist/sandbox.js +88 -8
- package/dist/scaffold.d.ts +21 -15
- package/dist/scaffold.js +133 -167
- package/dist/term.d.ts +2 -0
- package/dist/term.js +14 -0
- package/dist/validate.d.ts +20 -3
- package/dist/validate.js +309 -14
- package/package.json +2 -18
- package/templates/{.github → editor/.github}/copilot-instructions.md +161 -48
- package/templates/{.github → editor/.github}/prompts/new-skill.prompt.md +13 -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/project/sandbox/Dockerfile +21 -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/review-project.prompt.md +0 -0
package/dist/scaffold.js
CHANGED
|
@@ -1,21 +1,64 @@
|
|
|
1
|
-
import { mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
// ---------------------------------------------------------------------------
|
|
5
5
|
// Scaffolding
|
|
6
6
|
//
|
|
7
|
-
// What `zen init` writes
|
|
7
|
+
// What `zen init` writes and `zen open` refreshes — none of which is in this
|
|
8
|
+
// file. `templates/` holds the real thing, laid out the way it lands, so
|
|
9
|
+
// changing what a project starts life as is editing a file rather than a string
|
|
10
|
+
// literal escaping every backtick and `${...}` it contains:
|
|
11
|
+
//
|
|
12
|
+
// templates/project/ the project's own files. Written once and edited from
|
|
13
|
+
// then on, so anything already there is left alone.
|
|
14
|
+
// templates/editor/ ours: `.vscode/settings.json` and the `.github/` tree,
|
|
15
|
+
// which describe this version of `zen` to the editor and
|
|
16
|
+
// are replaced every time.
|
|
17
|
+
// templates/parts/ fragments spliced into a template above.
|
|
18
|
+
//
|
|
19
|
+
// The trees are copied whole and nothing enumerates them, so adding a file to
|
|
20
|
+
// a new project is adding a file to `templates/project/` and nothing else.
|
|
21
|
+
//
|
|
22
|
+
// What is there is deliberately close to empty: a template full of
|
|
8
23
|
// commented-out options is a template nobody reads and everybody deletes. The
|
|
9
|
-
// one agent
|
|
24
|
+
// one agent works as written, and every other knob is in `docs/`.
|
|
10
25
|
// ---------------------------------------------------------------------------
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
26
|
+
const TEMPLATES = fileURLToPath(new URL('../templates', import.meta.url));
|
|
27
|
+
/** The suffix on a file with `{{...}}` in it, dropped when the file lands. */
|
|
28
|
+
const TEMPLATE = '.tmpl';
|
|
29
|
+
/**
|
|
30
|
+
* Fills the `{{name}}` in a template, in the two shapes templates use.
|
|
31
|
+
*
|
|
32
|
+
* A placeholder alone on a line takes a whole fragment: its own indentation is
|
|
33
|
+
* applied to every line of the value, and an empty value takes the line with
|
|
34
|
+
* it — which is how an optional block leaves nothing behind. Anywhere else it
|
|
35
|
+
* takes a word. A name nothing supplies throws, so a typo in a template is a
|
|
36
|
+
* failing test rather than a `{{provider}}` sitting in somebody's agents.yaml.
|
|
37
|
+
*/
|
|
38
|
+
function render(text, vars) {
|
|
39
|
+
const value = (name) => {
|
|
40
|
+
const found = vars[name];
|
|
41
|
+
if (found === undefined) {
|
|
42
|
+
throw new Error(`template asks for {{${name}}}, which nothing supplies`);
|
|
43
|
+
}
|
|
44
|
+
return found;
|
|
45
|
+
};
|
|
46
|
+
return text
|
|
47
|
+
.replace(/^([ \t]*)\{\{(\w+)\}\}[ \t]*\r?\n/gm, (_, indent, name) => {
|
|
48
|
+
const body = value(name).trimEnd();
|
|
49
|
+
if (!body) {
|
|
50
|
+
return '';
|
|
51
|
+
}
|
|
52
|
+
const lines = body.split('\n').map((line) => (line ? indent + line : ''));
|
|
53
|
+
return `${lines.join('\n')}\n`;
|
|
54
|
+
})
|
|
55
|
+
.replace(/\{\{(\w+)\}\}/g, (_, name) => value(name));
|
|
56
|
+
}
|
|
57
|
+
/** Reads one fragment from `templates/parts/`, without its trailing newline. */
|
|
58
|
+
function part(name, vars = {}) {
|
|
59
|
+
const text = readFileSync(join(TEMPLATES, 'parts', `${name}${TEMPLATE}`), 'utf8');
|
|
60
|
+
return render(text, vars).trimEnd();
|
|
61
|
+
}
|
|
19
62
|
/**
|
|
20
63
|
* The `model:` section, which is one line until it has to say more.
|
|
21
64
|
*
|
|
@@ -24,93 +67,58 @@ Replace this with yours.
|
|
|
24
67
|
* reasoning means splitting the ref back into the two fields and giving the
|
|
25
68
|
* configuration a name to be referred to by.
|
|
26
69
|
*/
|
|
27
|
-
|
|
70
|
+
function modelSection(ref, options) {
|
|
28
71
|
const colon = ref.indexOf(':');
|
|
29
72
|
if (!options || colon < 0) {
|
|
30
|
-
return ('
|
|
31
|
-
'# whole project moves. The prefix is the *provider* name, not the vendor — drop\n' +
|
|
32
|
-
'# it and the id goes to the default provider, whatever the id looks like.\n' +
|
|
33
|
-
`model: ${ref}`);
|
|
73
|
+
return part('model.yaml', { ref });
|
|
34
74
|
}
|
|
35
|
-
|
|
36
|
-
.
|
|
37
|
-
.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
'# whole project moves. A named configuration is what gives the knobs below\n' +
|
|
42
|
-
'# somewhere to live; a bare `model: <provider>:<id>` works when there are none.\n' +
|
|
43
|
-
'models:\n' +
|
|
44
|
-
' main:\n' +
|
|
45
|
-
` provider: ${ref.slice(0, colon)}\n` +
|
|
46
|
-
` model: ${ref.slice(colon + 1)}\n` +
|
|
47
|
-
`${indented}\n` +
|
|
48
|
-
'\n' +
|
|
49
|
-
'model: main');
|
|
50
|
-
};
|
|
75
|
+
return part('models.yaml', {
|
|
76
|
+
provider: ref.slice(0, colon),
|
|
77
|
+
id: ref.slice(colon + 1),
|
|
78
|
+
options,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
51
81
|
/**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
82
|
+
* The name a template file lands under.
|
|
83
|
+
*
|
|
84
|
+
* `gitignore` gains its dot here because it cannot have one in the repository:
|
|
85
|
+
* npm strips a `.gitignore` out of a published tarball, and git would read this
|
|
86
|
+
* one as rules about `packages/cli/templates/` rather than as content.
|
|
55
87
|
*/
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
tools:
|
|
91
|
-
- workspace:*
|
|
92
|
-
- sandbox:*${web ? '\n - exa:*' : ''}
|
|
93
|
-
`;
|
|
94
|
-
const PROMPT = `You are a helpful assistant working inside a project workspace.
|
|
95
|
-
|
|
96
|
-
You have tools to read, search and edit files. The workspace is the only
|
|
97
|
-
place you can see; paths are relative to its root.
|
|
98
|
-
|
|
99
|
-
You can also run shell commands. They run in a container over the same
|
|
100
|
-
workspace, not on the user's machine, so a command that fails there has cost
|
|
101
|
-
them nothing — but it is still their work in the directory, so read before you
|
|
102
|
-
overwrite and say what you ran.
|
|
103
|
-
|
|
104
|
-
Read a file before you change it: \`apply_patch\` matches the surrounding text
|
|
105
|
-
exactly, so a patch written from memory will not apply. Use \`apply_patch\` to
|
|
106
|
-
change part of a file and \`write_file\` only for a new one.
|
|
107
|
-
|
|
108
|
-
Say what you changed.
|
|
109
|
-
`;
|
|
110
|
-
const GITIGNORE = `# Sessions hold run state, memory, blobs and whatever the agent wrote.
|
|
111
|
-
# None of it is source.
|
|
112
|
-
sessions/
|
|
113
|
-
`;
|
|
88
|
+
function target(name) {
|
|
89
|
+
if (name.endsWith(TEMPLATE)) {
|
|
90
|
+
return name.slice(0, -TEMPLATE.length);
|
|
91
|
+
}
|
|
92
|
+
return name === 'gitignore' ? '.gitignore' : name;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Copies one template directory into `dir` at `rel`, depth first, sorted so the
|
|
96
|
+
* list it returns is the same on every machine. Only a `.tmpl` is read as text;
|
|
97
|
+
* everything else is copied byte for byte.
|
|
98
|
+
*/
|
|
99
|
+
function copyTree(from, dir, rel, opts) {
|
|
100
|
+
const written = [];
|
|
101
|
+
mkdirSync(join(dir, rel), { recursive: true });
|
|
102
|
+
const entries = readdirSync(from, { withFileTypes: true });
|
|
103
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
104
|
+
for (const entry of entries) {
|
|
105
|
+
const source = join(from, entry.name);
|
|
106
|
+
if (entry.isDirectory()) {
|
|
107
|
+
written.push(...copyTree(source, dir, join(rel, entry.name), opts));
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const child = join(rel, target(entry.name));
|
|
111
|
+
if (opts.keep && existsSync(join(dir, child))) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const body = entry.name.endsWith(TEMPLATE)
|
|
115
|
+
? render(readFileSync(source, 'utf8'), opts.vars ?? {})
|
|
116
|
+
: readFileSync(source);
|
|
117
|
+
writeFileSync(join(dir, child), body);
|
|
118
|
+
written.push(child);
|
|
119
|
+
}
|
|
120
|
+
return written;
|
|
121
|
+
}
|
|
114
122
|
// ---------------------------------------------------------------------------
|
|
115
123
|
// Telling the editor which instructions are not for it
|
|
116
124
|
//
|
|
@@ -127,94 +135,52 @@ sessions/
|
|
|
127
135
|
// up whatever `AGENTS.md` a run happened to leave behind. It is a *restricted*
|
|
128
136
|
// setting, so it applies only in a trusted workspace; that is the right way
|
|
129
137
|
// round, since an untrusted folder is not one to run agents in either.
|
|
130
|
-
// ---------------------------------------------------------------------------
|
|
131
|
-
const VSCODE_SETTINGS = `{
|
|
132
|
-
"chat.useNestedAgentsMdFiles": false,
|
|
133
|
-
"chat.tools.terminal.autoApprove": {
|
|
134
|
-
"zen": true
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
`;
|
|
138
|
-
/**
|
|
139
|
-
* Writes `.vscode/settings.json` under `dir`, replacing what is there. The file
|
|
140
|
-
* is ours: it says how the editor is to treat a directory the agents write
|
|
141
|
-
* into, and a stale copy of that answer is worse than none. Returns the
|
|
142
|
-
* relative path.
|
|
143
|
-
*/
|
|
144
|
-
export function editorSettings(dir) {
|
|
145
|
-
const rel = join('.vscode', 'settings.json');
|
|
146
|
-
mkdirSync(join(dir, '.vscode'), { recursive: true });
|
|
147
|
-
writeFileSync(join(dir, rel), VSCODE_SETTINGS);
|
|
148
|
-
return rel;
|
|
149
|
-
}
|
|
150
|
-
// ---------------------------------------------------------------------------
|
|
151
|
-
// The other half of the editor story
|
|
152
138
|
//
|
|
153
139
|
// `INSTRUCTIONS.md` addresses the *project's* agents. The editor's assistant
|
|
154
140
|
// still needs a brief of its own, and what it needs to know is how this kind
|
|
155
141
|
// of project is put together — the file formats, how a prompt is written, when
|
|
156
|
-
// to add a skill rather than an agent. That is
|
|
157
|
-
// standing brief, plus the prompt files and skills the editor picks up from
|
|
158
|
-
//
|
|
159
|
-
// are full of backticks and `${...}` examples, which a TS template literal
|
|
160
|
-
// cannot hold without escaping every one of them into illegibility.
|
|
161
|
-
//
|
|
162
|
-
// `templates/.github/` mirrors what lands in the project one for one, so
|
|
163
|
-
// adding a skill or a prompt file is adding a file there and nothing else.
|
|
142
|
+
// to add a skill rather than an agent. That is what the `.github/` tree is: the
|
|
143
|
+
// standing brief, plus the prompt files and skills the editor picks up from the
|
|
144
|
+
// same place.
|
|
164
145
|
// ---------------------------------------------------------------------------
|
|
165
|
-
const GITHUB_TEMPLATE = fileURLToPath(new URL('../templates/.github', import.meta.url));
|
|
166
|
-
/**
|
|
167
|
-
* Writes the `.github/` tree under `dir`, replacing what is there — it
|
|
168
|
-
* describes the file formats of the version of `zen` in hand, so the current
|
|
169
|
-
* one is the only one worth having. Returns the relative paths written.
|
|
170
|
-
*/
|
|
171
|
-
export function copilotInstructions(dir) {
|
|
172
|
-
return copyTree(GITHUB_TEMPLATE, dir, '.github');
|
|
173
|
-
}
|
|
174
146
|
/**
|
|
175
|
-
*
|
|
176
|
-
*
|
|
147
|
+
* Writes the editor's files under `dir`, replacing what is there. They are
|
|
148
|
+
* ours: they say how the editor is to treat a directory the agents write into,
|
|
149
|
+
* and they describe the file formats of the version of `zen` in hand, so the
|
|
150
|
+
* current answer is the only one worth having and a stale one is worse than
|
|
151
|
+
* none. Returns the relative paths written.
|
|
177
152
|
*/
|
|
178
|
-
function
|
|
179
|
-
|
|
180
|
-
mkdirSync(join(dir, rel), { recursive: true });
|
|
181
|
-
const entries = readdirSync(from, { withFileTypes: true });
|
|
182
|
-
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
183
|
-
for (const entry of entries) {
|
|
184
|
-
const child = join(rel, entry.name);
|
|
185
|
-
if (entry.isDirectory()) {
|
|
186
|
-
written.push(...copyTree(join(from, entry.name), dir, child));
|
|
187
|
-
}
|
|
188
|
-
else {
|
|
189
|
-
writeFileSync(join(dir, child), readFileSync(join(from, entry.name)));
|
|
190
|
-
written.push(child);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
return written;
|
|
153
|
+
export function editorFiles(dir) {
|
|
154
|
+
return copyTree(join(TEMPLATES, 'editor'), dir, '', {});
|
|
194
155
|
}
|
|
195
156
|
/**
|
|
196
|
-
* Writes a project. Never overwrites the project's own files
|
|
197
|
-
*
|
|
198
|
-
* and are replaced.
|
|
157
|
+
* Writes a project. Never overwrites the project's own files — a second `init`
|
|
158
|
+
* over a directory fills in what is missing and leaves the rest alone — but the
|
|
159
|
+
* editor files are ours, and are replaced.
|
|
160
|
+
*
|
|
161
|
+
* The two are returned apart because they are read differently: the project's
|
|
162
|
+
* files are the thing that was just made, and worth listing; the editor's are
|
|
163
|
+
* plumbing for a tool that may not even be installed, and listing them buries
|
|
164
|
+
* the first set under twice as many lines about the second.
|
|
199
165
|
*/
|
|
200
166
|
export function scaffold(opts) {
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
};
|
|
208
|
-
|
|
167
|
+
const files = copyTree(join(TEMPLATES, 'project'), opts.dir, '', {
|
|
168
|
+
keep: true,
|
|
169
|
+
vars: {
|
|
170
|
+
model: modelSection(opts.model, opts.modelOptions),
|
|
171
|
+
exa: opts.web ? part('exa.yaml') : '',
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
// The directories with no file to put in them: a skill is a folder someone
|
|
175
|
+
// adds, sessions is written into on the first run, and `.tmp` is scratch —
|
|
176
|
+
// there so an agent has somewhere inside the workspace to put a working
|
|
177
|
+
// file, which is somewhere the sandbox can still reach after the container
|
|
178
|
+
// it was written from is gone.
|
|
209
179
|
mkdirSync(join(opts.dir, 'agents', 'skills'), { recursive: true });
|
|
210
180
|
mkdirSync(join(opts.dir, 'sessions'), { recursive: true });
|
|
211
|
-
|
|
212
|
-
put('agents.yaml', AGENTS_YAML(opts.model, opts.modelOptions, opts.web));
|
|
213
|
-
put(join('agents', 'prompts', 'default.md'), PROMPT);
|
|
214
|
-
put('.gitignore', GITIGNORE);
|
|
181
|
+
mkdirSync(join(opts.dir, '.tmp'), { recursive: true });
|
|
215
182
|
// The project directory is what `zen open` opens, so this is where the
|
|
216
183
|
// editor actually reads them.
|
|
217
|
-
|
|
218
|
-
return written;
|
|
184
|
+
return { files, editor: editorFiles(opts.dir) };
|
|
219
185
|
}
|
|
220
186
|
//# sourceMappingURL=scaffold.js.map
|
package/dist/term.d.ts
CHANGED
|
@@ -66,4 +66,6 @@ export declare function choose<T>(title: string, choices: readonly Choice<T>[]):
|
|
|
66
66
|
export declare function readStdin(): Promise<string | undefined>;
|
|
67
67
|
export declare function ago(iso: string | undefined): string;
|
|
68
68
|
export declare function count(n: number, singular: string, plural?: string): string;
|
|
69
|
+
/** Powers of 1000, as the container engine prints them, so the two agree. */
|
|
70
|
+
export declare function bytes(n: number): string;
|
|
69
71
|
//# sourceMappingURL=term.d.ts.map
|
package/dist/term.js
CHANGED
|
@@ -239,4 +239,18 @@ export function ago(iso) {
|
|
|
239
239
|
export function count(n, singular, plural = `${singular}s`) {
|
|
240
240
|
return `${n} ${n === 1 ? singular : plural}`;
|
|
241
241
|
}
|
|
242
|
+
/** Powers of 1000, as the container engine prints them, so the two agree. */
|
|
243
|
+
export function bytes(n) {
|
|
244
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
245
|
+
return '0 B';
|
|
246
|
+
}
|
|
247
|
+
const units = ['B', 'kB', 'MB', 'GB', 'TB'];
|
|
248
|
+
let value = n;
|
|
249
|
+
let unit = 0;
|
|
250
|
+
while (value >= 1000 && unit < units.length - 1) {
|
|
251
|
+
value /= 1000;
|
|
252
|
+
unit++;
|
|
253
|
+
}
|
|
254
|
+
return `${unit > 0 && value < 100 ? value.toFixed(1) : Math.round(value)} ${units[unit]}`;
|
|
255
|
+
}
|
|
242
256
|
//# sourceMappingURL=term.js.map
|
package/dist/validate.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AnyTool, type ProjectConfig } from '@zenera/neo';
|
|
1
|
+
import { type AnyTool, type ProjectConfig, type Runner } from '@zenera/neo';
|
|
2
2
|
import { type DeclaredRole } from './audit.ts';
|
|
3
3
|
import { type KeyStore } from './keys.ts';
|
|
4
4
|
/**
|
|
@@ -65,8 +65,8 @@ export interface SkillReport {
|
|
|
65
65
|
/** the SKILL.md, relative to the project root */
|
|
66
66
|
path: string;
|
|
67
67
|
tools?: string[];
|
|
68
|
-
/** other files in the skill folder, which the agent
|
|
69
|
-
|
|
68
|
+
/** other files in the skill folder, which the agent can reach under /skills */
|
|
69
|
+
files?: string[];
|
|
70
70
|
/** agents whose binding can see it */
|
|
71
71
|
usedBy: string[];
|
|
72
72
|
}
|
|
@@ -111,8 +111,12 @@ export interface Report {
|
|
|
111
111
|
models: ModelReport[];
|
|
112
112
|
sandbox: {
|
|
113
113
|
image: string | null;
|
|
114
|
+
/** the Dockerfile that image is built from, when it is built rather than pulled */
|
|
115
|
+
dockerfile: string | null;
|
|
114
116
|
declared: boolean;
|
|
115
117
|
used: boolean;
|
|
118
|
+
/** whether the image was actually built and a container started */
|
|
119
|
+
probed: boolean;
|
|
116
120
|
};
|
|
117
121
|
findings: Finding[];
|
|
118
122
|
counts: {
|
|
@@ -139,6 +143,19 @@ export interface ValidateOptions {
|
|
|
139
143
|
* pay for it.
|
|
140
144
|
*/
|
|
141
145
|
keys?: KeyStore;
|
|
146
|
+
/**
|
|
147
|
+
* Whether to build the image and start a container. Everything else here
|
|
148
|
+
* is a reading of files; this is the one part that runs something, so it
|
|
149
|
+
* is named separately and can be turned off.
|
|
150
|
+
*/
|
|
151
|
+
sandbox?: {
|
|
152
|
+
enabled: boolean;
|
|
153
|
+
engine?: string;
|
|
154
|
+
/** the test seam, as elsewhere — a check can be watched without an engine */
|
|
155
|
+
exec?: Runner;
|
|
156
|
+
/** called with each slow step, so the caller can narrate one */
|
|
157
|
+
onProgress?: (what: string) => void;
|
|
158
|
+
};
|
|
142
159
|
}
|
|
143
160
|
export declare function validateProject(opts: ValidateOptions): Promise<Report>;
|
|
144
161
|
export declare function availableTools(root: string, config: ProjectConfig): AnyTool<unknown>[];
|