@zenera/cli 1.1.24 → 1.1.25
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 +53 -0
- package/dist/ask.js +28 -5
- package/dist/banner.js +2 -2
- package/dist/batch.d.ts +6 -0
- package/dist/batch.js +76 -1
- package/dist/catalog.d.ts +11 -1
- package/dist/catalog.js +80 -7
- package/dist/commands/inspect.d.ts +47 -1
- package/dist/commands/inspect.js +167 -18
- package/dist/commands/memory.js +128 -29
- package/dist/commands/meta.js +311 -21
- package/dist/commands/models.js +96 -12
- package/dist/commands/open.js +4 -4
- package/dist/commands/run.js +38 -4
- package/dist/commands/sandbox.js +76 -14
- package/dist/engine.d.ts +5 -0
- package/dist/engine.js +1 -0
- package/dist/eventlog.d.ts +59 -0
- package/dist/eventlog.js +146 -0
- package/dist/liveness.d.ts +7 -0
- package/dist/liveness.js +15 -1
- package/dist/meta/editor.d.ts +9 -0
- package/dist/meta/editor.js +60 -0
- package/dist/meta/finetune/children.d.ts +8 -0
- package/dist/meta/finetune/children.js +76 -0
- package/dist/meta/finetune/command.d.ts +30 -0
- package/dist/meta/finetune/command.js +338 -0
- package/dist/meta/finetune/dataset/anchors.d.ts +41 -0
- package/dist/meta/finetune/dataset/anchors.js +239 -0
- package/dist/meta/finetune/dataset/changes.d.ts +36 -0
- package/dist/meta/finetune/dataset/changes.js +319 -0
- package/dist/meta/finetune/dataset/command.d.ts +17 -0
- package/dist/meta/finetune/dataset/command.js +1011 -0
- package/dist/meta/finetune/dataset/drift.d.ts +33 -0
- package/dist/meta/finetune/dataset/drift.js +71 -0
- package/dist/meta/finetune/dataset/export.d.ts +26 -0
- package/dist/meta/finetune/dataset/export.js +51 -0
- package/dist/meta/finetune/dataset/index.d.ts +2 -0
- package/dist/meta/finetune/dataset/index.js +2 -0
- package/dist/meta/finetune/dataset/sample.d.ts +50 -0
- package/dist/meta/finetune/dataset/sample.js +197 -0
- package/dist/meta/finetune/dataset/schema.d.ts +33 -0
- package/dist/meta/finetune/dataset/schema.js +216 -0
- package/dist/meta/finetune/dataset/store.d.ts +35 -0
- package/dist/meta/finetune/dataset/store.js +177 -0
- package/dist/meta/finetune/dataset/types.d.ts +125 -0
- package/dist/meta/finetune/dataset/types.js +39 -0
- package/dist/meta/finetune/failure.d.ts +15 -0
- package/dist/meta/finetune/failure.js +26 -0
- package/dist/meta/finetune/feedback.d.ts +45 -0
- package/dist/meta/finetune/feedback.js +85 -0
- package/dist/meta/finetune/improvements.d.ts +51 -0
- package/dist/meta/finetune/improvements.js +244 -0
- package/dist/meta/finetune/index.d.ts +2 -0
- package/dist/meta/finetune/index.js +2 -0
- package/dist/meta/finetune/journal.d.ts +35 -0
- package/dist/meta/finetune/journal.js +135 -0
- package/dist/meta/finetune/live.d.ts +74 -0
- package/dist/meta/finetune/live.js +226 -0
- package/dist/meta/finetune/loop.d.ts +3 -0
- package/dist/meta/finetune/loop.js +114 -0
- package/dist/meta/finetune/memories.d.ts +47 -0
- package/dist/meta/finetune/memories.js +154 -0
- package/dist/meta/finetune/report.d.ts +33 -0
- package/dist/meta/finetune/report.js +1284 -0
- package/dist/meta/finetune/retry.d.ts +24 -0
- package/dist/meta/finetune/retry.js +96 -0
- package/dist/meta/finetune/sampler.d.ts +18 -0
- package/dist/meta/finetune/sampler.js +41 -0
- package/dist/meta/finetune/system.d.ts +27 -0
- package/dist/meta/finetune/system.js +132 -0
- package/dist/meta/finetune/train.d.ts +39 -0
- package/dist/meta/finetune/train.js +391 -0
- package/dist/meta/finetune/tui.d.ts +16 -0
- package/dist/meta/finetune/tui.js +291 -0
- package/dist/meta/finetune/tuning.d.ts +167 -0
- package/dist/meta/finetune/tuning.js +289 -0
- package/dist/meta/finetune/usage.d.ts +62 -0
- package/dist/meta/finetune/usage.js +225 -0
- package/dist/meta/host.d.ts +14 -0
- package/dist/meta/host.js +20 -0
- package/dist/meta/index.d.ts +11 -0
- package/dist/meta/index.js +11 -0
- package/dist/meta/launch.d.ts +81 -0
- package/dist/meta/launch.js +286 -0
- package/dist/meta/model.d.ts +44 -0
- package/dist/meta/model.js +119 -0
- package/dist/meta/prompts.d.ts +18 -0
- package/dist/meta/prompts.js +61 -0
- package/dist/meta/relay.d.ts +20 -0
- package/dist/meta/relay.js +123 -0
- package/dist/meta/tokens.d.ts +52 -0
- package/dist/meta/tokens.js +226 -0
- package/dist/meta/window.d.ts +61 -0
- package/dist/meta/window.js +198 -0
- package/dist/meta/wire.d.ts +42 -0
- package/dist/meta/wire.js +129 -0
- package/dist/modelref.d.ts +16 -0
- package/dist/modelref.js +41 -0
- package/dist/narrate.js +5 -2
- package/dist/podman.d.ts +26 -0
- package/dist/podman.js +83 -3
- package/dist/projects.d.ts +0 -5
- package/dist/projects.js +2 -15
- package/dist/sandbox.d.ts +2 -0
- package/dist/sandbox.js +10 -3
- package/dist/scaffold.d.ts +15 -6
- package/dist/scaffold.js +5 -60
- package/dist/session.js +4 -29
- package/dist/term.d.ts +2 -0
- package/dist/term.js +11 -2
- package/dist/trace.js +53 -2
- package/dist/tui/models.d.ts +33 -0
- package/dist/tui/models.js +389 -0
- package/dist/tui/wrap.d.ts +5 -0
- package/dist/tui/wrap.js +12 -0
- package/dist/usage.d.ts +78 -0
- package/dist/usage.js +51 -0
- package/package.json +3 -2
- package/templates/editor/.github/copilot-instructions.md +31 -102
- package/templates/editor/.github/prompts/analyze.prompt.md +31 -0
- package/templates/editor/.github/prompts/dataset.prompt.md +108 -0
- package/templates/editor/.github/prompts/finetune-apply.prompt.md +91 -0
- package/templates/editor/.github/prompts/finetune.prompt.md +691 -198
- package/templates/editor/.github/prompts/new-agent.prompt.md +3 -1
- package/templates/editor/.github/prompts/new-skill.prompt.md +2 -0
- package/templates/editor/.github/prompts/project-review.prompt.md +4 -3
- package/templates/editor/.github/skills/zen-analyze-run/SKILL.md +483 -0
- package/templates/editor/.github/skills/zen-cli/SKILL.md +9 -1
- package/templates/editor/.github/skills/zen-cli/references/dataset.md +34 -0
- package/templates/editor/.github/skills/zen-cli/references/finetune.md +77 -0
- package/templates/editor/.github/skills/zen-cli/references/inspect.md +47 -9
- package/templates/editor/.github/skills/zen-cli/references/memory.md +33 -10
- package/templates/editor/.github/skills/zen-cli/references/meta.md +61 -0
- package/templates/editor/.github/skills/zen-cli/references/models.md +5 -1
- package/templates/editor/.github/skills/zen-cli/references/rag.md +24 -6
- package/templates/editor/.github/skills/zen-cli/references/sandbox.md +10 -1
- package/templates/editor/.github/skills/zen-code-python/SKILL.md +123 -0
- package/templates/editor/.github/skills/zen-code-python/references/code_python.md +48 -0
- package/templates/editor/.github/skills/zen-dataset/SKILL.md +253 -0
- package/templates/editor/.github/skills/zen-finetune/SKILL.md +1449 -658
- package/templates/editor/.github/skills/zen-finetune/references/readme-template.md +268 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/batch.mjs +390 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/difficult.mjs +243 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/memory.mjs +280 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/next.mjs +398 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/report.mjs +797 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/select.mjs +354 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/usage.mjs +722 -0
- package/templates/editor/.github/skills/zen-inspect/SKILL.md +60 -25
- package/templates/editor/.github/skills/zen-inspect-ask/SKILL.md +270 -0
- package/templates/editor/.github/skills/zen-memory/SKILL.md +18 -14
- package/templates/editor/.github/skills/zen-rag-docs/SKILL.md +20 -8
- package/templates/editor/.github/skills/zen-review/SKILL.md +5 -3
- package/templates/editor/.github/skills/zen-review/scripts/check-paths.sh +5 -4
- package/templates/editor/.github/skills/zen-sandbox/SKILL.md +7 -3
- package/templates/editor/.github/skills/zen-sandbox-capacity/SKILL.md +2 -2
- package/templates/editor/.github/skills/zen-topology/SKILL.md +283 -0
- package/templates/editor/.vscode/settings.json +1 -0
- package/templates/project/agents/files-instructions.md +29 -20
- package/templates/project/agents/memory-instructions.md +31 -32
- package/templates/project/agents/tools-instructions.md +7 -4
- package/templates/project/gitignore +2 -0
- package/dist/meta.d.ts +0 -221
- package/dist/meta.js +0 -684
- package/templates/editor/.github/skills/zen-finetune/scripts/collect.sh +0 -432
- package/templates/editor/.github/skills/zen-finetune/scripts/sample.sh +0 -199
package/dist/commands/models.js
CHANGED
|
@@ -11,14 +11,14 @@
|
|
|
11
11
|
// instead", and prints a ref on stdout that can be pasted straight back into
|
|
12
12
|
// agents.yaml — or read by an agent doing the pasting.
|
|
13
13
|
// ---------------------------------------------------------------------------
|
|
14
|
-
import { createEmbedder,
|
|
14
|
+
import { createEmbedder, ModelRegistry } from '@zenera/neo';
|
|
15
15
|
import { parse } from "../args.js";
|
|
16
|
-
import { loadCatalog, loadCatalogs, matches, PREFERRED, } from "../catalog.js";
|
|
16
|
+
import { byGroup, groupOf, isPartner, loadCatalog, loadCatalogs, matches, PREFERRED, } from "../catalog.js";
|
|
17
17
|
import { ensureHome } from "../home.js";
|
|
18
18
|
import { envNames, form, isProvider, KeyStore, PROVIDERS } from "../keys.js";
|
|
19
|
-
import { probeModel } from "../liveness.js";
|
|
20
|
-
import { ago, bold, credentialError, cyan, dim, green, json, note, progress, red, table, usageError, write, writeAll, yellow, } from "../term.js";
|
|
21
|
-
const USAGE = 'zen models <providers|ls|search|show|test|pick> [ref] [options]';
|
|
19
|
+
import { probeChat, probeModel } from "../liveness.js";
|
|
20
|
+
import { ago, bold, CliError, credentialError, cyan, dim, EXIT, green, isInteractive, json, note, progress, red, table, usageError, write, writeAll, yellow, } from "../term.js";
|
|
21
|
+
const USAGE = 'zen models <providers|ls|search|show|test|pick|browse> [ref] [options]';
|
|
22
22
|
/**
|
|
23
23
|
* The keyring is materialised here, and only here.
|
|
24
24
|
*
|
|
@@ -112,15 +112,32 @@ function freshness(cat) {
|
|
|
112
112
|
return cat.origin === 'live' ? green('fetched now') : dim(`cached ${ago(cat.fetchedAt)}`);
|
|
113
113
|
}
|
|
114
114
|
const roleMark = (row) => row.roles.join('+');
|
|
115
|
+
const maker = (row) => !row.publisher ? '' : isPartner(row) ? yellow(row.publisher) : green(row.publisher);
|
|
115
116
|
function row(entry) {
|
|
116
117
|
return [
|
|
117
118
|
cyan(entry.ref),
|
|
119
|
+
maker(entry),
|
|
118
120
|
dim(roleMark(entry)),
|
|
119
121
|
dim(tokens(entry.contextLength)),
|
|
120
122
|
dim(entry.pricing?.free ? 'free' : ''),
|
|
121
123
|
dim(entry.name ?? ''),
|
|
122
124
|
];
|
|
123
125
|
}
|
|
126
|
+
/** The table, with a heading wherever a reseller's rows change maker. */
|
|
127
|
+
function grouped(entries) {
|
|
128
|
+
const lines = table(entries.map(row));
|
|
129
|
+
const out = [];
|
|
130
|
+
let last;
|
|
131
|
+
entries.forEach((e, i) => {
|
|
132
|
+
const group = groupOf(e);
|
|
133
|
+
if (group && group !== last) {
|
|
134
|
+
out.push(...(out.length > 0 ? [''] : []), bold(group));
|
|
135
|
+
}
|
|
136
|
+
last = group;
|
|
137
|
+
out.push(lines[i]);
|
|
138
|
+
});
|
|
139
|
+
return out;
|
|
140
|
+
}
|
|
124
141
|
/**
|
|
125
142
|
* Says how a listing was obtained whenever it was not the vendor's own word —
|
|
126
143
|
* on stderr, because it is narration about the answer rather than the answer.
|
|
@@ -175,6 +192,7 @@ const providers = async (ctx, args) => {
|
|
|
175
192
|
})));
|
|
176
193
|
note('');
|
|
177
194
|
note(dim('zen models ls <provider> what one of them serves'));
|
|
195
|
+
note(dim('zen models browse walk them with the arrow keys'));
|
|
178
196
|
note(dim('zen models pick --chat the first one that answers'));
|
|
179
197
|
};
|
|
180
198
|
// ---------------------------------------------------------------------------
|
|
@@ -198,7 +216,7 @@ const ls = async (ctx, args) => {
|
|
|
198
216
|
const all = cats
|
|
199
217
|
.flatMap((c) => c.entries)
|
|
200
218
|
.filter((e) => matches(e, '', { roles }))
|
|
201
|
-
.sort(
|
|
219
|
+
.sort(byGroup);
|
|
202
220
|
if (ctx.json) {
|
|
203
221
|
json({
|
|
204
222
|
models: all,
|
|
@@ -215,10 +233,13 @@ const ls = async (ctx, args) => {
|
|
|
215
233
|
note(dim('nothing matched — try: zen models ls --refresh'));
|
|
216
234
|
return;
|
|
217
235
|
}
|
|
218
|
-
writeAll(
|
|
236
|
+
writeAll(grouped(all.slice(0, limit)));
|
|
219
237
|
if (all.length > limit) {
|
|
220
238
|
note(dim(`${all.length - limit} more — narrow with \`zen models search\`, or pass --all`));
|
|
221
239
|
}
|
|
240
|
+
if (all.some(isPartner)) {
|
|
241
|
+
note(dim('partner models are billed through Vertex and must be enabled in Model Garden first: https://console.cloud.google.com/vertex-ai/model-garden'));
|
|
242
|
+
}
|
|
222
243
|
};
|
|
223
244
|
// ---------------------------------------------------------------------------
|
|
224
245
|
// search
|
|
@@ -255,7 +276,7 @@ const search = async (ctx, args) => {
|
|
|
255
276
|
// Cheapest first when price is known: search is usually the step before
|
|
256
277
|
// picking one, and the free ones are what most people are looking for.
|
|
257
278
|
.sort((a, b) => Number(b.pricing?.free ?? false) - Number(a.pricing?.free ?? false) ||
|
|
258
|
-
a
|
|
279
|
+
byGroup(a, b));
|
|
259
280
|
if (ctx.json) {
|
|
260
281
|
json({ query, matched: hits.length, models: hits.slice(0, limit) });
|
|
261
282
|
return;
|
|
@@ -377,9 +398,7 @@ function target(ref, provider, id, role) {
|
|
|
377
398
|
if (role !== 'chat') {
|
|
378
399
|
throw usageError(`${ref} is ${role}-only, and there is no way to ask it a question from here`, 'zen models test only exercises chat and embedding models');
|
|
379
400
|
}
|
|
380
|
-
|
|
381
|
-
// a cap at all, so this is not merely thrift.
|
|
382
|
-
return { ref, kind: 'model', model: createModel({ provider, model: id, maxTokens: 16 }) };
|
|
401
|
+
return { ref, kind: 'model', model: probeChat(provider, id) };
|
|
383
402
|
}
|
|
384
403
|
function verdict(probe) {
|
|
385
404
|
switch (probe.check.state) {
|
|
@@ -536,6 +555,67 @@ const pick = async (ctx, args) => {
|
|
|
536
555
|
throw credentialError(`no ${role} model answered on this machine`, 'add a credential: zen key add openai');
|
|
537
556
|
};
|
|
538
557
|
// ---------------------------------------------------------------------------
|
|
558
|
+
// browse
|
|
559
|
+
// ---------------------------------------------------------------------------
|
|
560
|
+
/**
|
|
561
|
+
* The same catalog, walked with the arrow keys: providers, what one serves,
|
|
562
|
+
* one model in full, with a key to ask it a question and a key to take it.
|
|
563
|
+
*
|
|
564
|
+
* The interface is drawn on stderr, so a pick reaches stdout alone and
|
|
565
|
+
* `$(zen models browse)` is the ref, the same contract `pick` keeps.
|
|
566
|
+
*/
|
|
567
|
+
const browse = async (ctx, args) => {
|
|
568
|
+
const { values, positionals } = parse(args, { refresh: { type: 'boolean' }, theme: { type: 'string' } }, 'zen models browse [provider] [--refresh] [--theme dark|light|auto]');
|
|
569
|
+
const initial = positionals[0];
|
|
570
|
+
if (initial !== undefined && !isProvider(initial)) {
|
|
571
|
+
throw usageError(`unknown provider "${initial}"`, `known: ${PROVIDERS.join(', ')}`);
|
|
572
|
+
}
|
|
573
|
+
if (ctx.json || !isInteractive()) {
|
|
574
|
+
throw usageError('browsing needs a person at a terminal', 'from a script: zen models ls --json, or zen models pick --chat');
|
|
575
|
+
}
|
|
576
|
+
const where = await credentials();
|
|
577
|
+
// Offline, like `providers`: the overview should not cost five listings.
|
|
578
|
+
// A provider is fetched when it is opened.
|
|
579
|
+
const cats = await loadCatalogs(PROVIDERS, { offline: true });
|
|
580
|
+
const { browse: draw } = await import("../tui/models.js");
|
|
581
|
+
const chosen = await draw({
|
|
582
|
+
providers: cats.map((c) => ({
|
|
583
|
+
provider: c.provider,
|
|
584
|
+
credential: source(where, c.provider),
|
|
585
|
+
models: c.entries.length,
|
|
586
|
+
origin: c.origin,
|
|
587
|
+
fetchedAt: c.fetchedAt,
|
|
588
|
+
})),
|
|
589
|
+
initial,
|
|
590
|
+
theme: values.theme,
|
|
591
|
+
load: (provider, refresh) => loadCatalog(provider, { refresh: refresh || values.refresh }),
|
|
592
|
+
test: async (entry) => {
|
|
593
|
+
const role = entry.roles.includes('chat')
|
|
594
|
+
? 'chat'
|
|
595
|
+
: entry.roles.includes('embedding')
|
|
596
|
+
? 'embedding'
|
|
597
|
+
: (entry.roles[0] ?? 'chat');
|
|
598
|
+
const probe = await probeModel(target(entry.ref, entry.provider, entry.id, role));
|
|
599
|
+
return {
|
|
600
|
+
state: probe.check.state,
|
|
601
|
+
ms: probe.ms,
|
|
602
|
+
detail: probe.check.detail,
|
|
603
|
+
fix: probe.check.fix,
|
|
604
|
+
dimensions: probe.dimensions,
|
|
605
|
+
};
|
|
606
|
+
},
|
|
607
|
+
});
|
|
608
|
+
if (!chosen) {
|
|
609
|
+
// Leaving is a fine way to end a browse, but not an answer to a
|
|
610
|
+
// substitution that was waiting for one.
|
|
611
|
+
if (!process.stdout.isTTY) {
|
|
612
|
+
throw new CliError('nothing picked', EXIT.failed);
|
|
613
|
+
}
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
write(chosen.ref);
|
|
617
|
+
};
|
|
618
|
+
// ---------------------------------------------------------------------------
|
|
539
619
|
// Dispatch
|
|
540
620
|
// ---------------------------------------------------------------------------
|
|
541
621
|
const SUBS = {
|
|
@@ -548,6 +628,8 @@ const SUBS = {
|
|
|
548
628
|
test,
|
|
549
629
|
check: test,
|
|
550
630
|
pick,
|
|
631
|
+
browse,
|
|
632
|
+
ui: browse,
|
|
551
633
|
};
|
|
552
634
|
export const models = {
|
|
553
635
|
summary: 'What this machine can use: list, search, test and pick models.',
|
|
@@ -570,6 +652,8 @@ export const models = {
|
|
|
570
652
|
' zen models show <ref> One model, in full.',
|
|
571
653
|
' zen models test <ref> … Ask it one real question.',
|
|
572
654
|
' zen models pick --embedding The first ref that answers, on stdout.',
|
|
655
|
+
' zen models browse [provider] Walk providers and models with the',
|
|
656
|
+
' arrow keys; t asks one, p prints its ref.',
|
|
573
657
|
],
|
|
574
658
|
run: async (ctx) => {
|
|
575
659
|
const [name, ...rest] = ctx.args;
|
|
@@ -588,7 +672,7 @@ export const models = {
|
|
|
588
672
|
await ls(ctx, ctx.args);
|
|
589
673
|
return;
|
|
590
674
|
}
|
|
591
|
-
throw usageError(`unknown: zen models ${name}`, `try: ${cyan('providers, ls, search, show, test, pick')} — or a provider: ${dim(PROVIDERS.join(', '))}`);
|
|
675
|
+
throw usageError(`unknown: zen models ${name}`, `try: ${cyan('providers, ls, search, show, test, pick, browse')} — or a provider: ${dim(PROVIDERS.join(', '))}`);
|
|
592
676
|
},
|
|
593
677
|
};
|
|
594
678
|
//# sourceMappingURL=models.js.map
|
package/dist/commands/open.js
CHANGED
|
@@ -3,9 +3,9 @@ import { accessSync, constants, existsSync, readFileSync } from 'node:fs';
|
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
import { delimiter, join, resolve, sep } from 'node:path';
|
|
5
5
|
import { one, parse } from "../args.js";
|
|
6
|
+
import { editorFiles } from "../meta/editor.js";
|
|
6
7
|
import { project as resolveProject } from "../resolve.js";
|
|
7
|
-
import {
|
|
8
|
-
import { CliError, cyan, dim, EXIT, json, note, usageError } from "../term.js";
|
|
8
|
+
import { CliError, cyan, dim, EXIT, isInteractive, json, note, usageError } from "../term.js";
|
|
9
9
|
const USAGE = 'zen open [project] [--editor <cmd>] [--wait]';
|
|
10
10
|
/**
|
|
11
11
|
* A project is resolved by name or from the cwd, and the path is handed to an
|
|
@@ -247,8 +247,8 @@ function verify(editor) {
|
|
|
247
247
|
? 'name one with --editor, or set $ZENERA_EDITOR'
|
|
248
248
|
: `${editor.from} names a command that is not on PATH`);
|
|
249
249
|
}
|
|
250
|
-
if ((editor.from === '$VISUAL' || editor.from === '$EDITOR') && !
|
|
251
|
-
throw usageError(`${editor.from} names a terminal editor and there is no terminal here`, 'name a windowed one with --editor, or set $ZENERA_EDITOR');
|
|
250
|
+
if ((editor.from === '$VISUAL' || editor.from === '$EDITOR') && !isInteractive()) {
|
|
251
|
+
throw usageError(`${editor.from} names a terminal editor and there is no one at a terminal here`, 'name a windowed one with --editor, or set $ZENERA_EDITOR');
|
|
252
252
|
}
|
|
253
253
|
}
|
|
254
254
|
async function launch(editor, dir) {
|
package/dist/commands/run.js
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
|
+
import { usageByModel } from '@zenera/neo';
|
|
1
2
|
import { writeFile } from 'node:fs/promises';
|
|
2
3
|
import { resolve } from 'node:path';
|
|
3
4
|
import { parse } from "../args.js";
|
|
4
5
|
import { concurrency, runBatch } from "../batch.js";
|
|
5
6
|
import * as Engine from "../engine.js";
|
|
7
|
+
import { agentEventLog } from "../eventlog.js";
|
|
6
8
|
import { duration, Narrator, stopMark, summary } from "../narrate.js";
|
|
7
9
|
import * as Projects from "../projects.js";
|
|
8
10
|
import { readBatch, readRequest } from "../request.js";
|
|
9
11
|
import { target } from "../resolve.js";
|
|
10
12
|
import { display } from "../session.js";
|
|
11
|
-
import { bold, cyan, dim, json, jsonText, note, readStdin, usageError, write } from "../term.js";
|
|
13
|
+
import { bold, cyan, dim, drivenByAgent, json, jsonText, note, readStdin, usageError, write, } from "../term.js";
|
|
12
14
|
import { parseChoice } from "../tui/theme.js";
|
|
15
|
+
import { appendUsage } from "../usage.js";
|
|
13
16
|
const USAGE = 'zen run [project] [prompt] [options]';
|
|
14
17
|
export const run = {
|
|
15
18
|
summary: 'Run the project — the TUI on a terminal, one shot otherwise.',
|
|
@@ -40,6 +43,10 @@ export const run = {
|
|
|
40
43
|
' --theme <dark|light> Colours for the TUI. Default: auto.',
|
|
41
44
|
' --out <file> Put the answer in this file instead of on screen.',
|
|
42
45
|
' With --json, the file gets the whole JSON envelope.',
|
|
46
|
+
' --events <file> Append what the agent does to this file as it does it:',
|
|
47
|
+
' one JSON line per model call, tool call and message.',
|
|
48
|
+
' --disposable Remove its containers when it ends, whatever `persist:`',
|
|
49
|
+
' says: for a session that will never be continued.',
|
|
43
50
|
' --yes Answer yes to every question.',
|
|
44
51
|
' --json Print machine-readable JSON (session, run, mounts, output, etc...).',
|
|
45
52
|
'',
|
|
@@ -121,6 +128,8 @@ export const run = {
|
|
|
121
128
|
plain: { type: 'boolean' },
|
|
122
129
|
theme: { type: 'string' },
|
|
123
130
|
out: { type: 'string' },
|
|
131
|
+
events: { type: 'string' },
|
|
132
|
+
disposable: { type: 'boolean' },
|
|
124
133
|
'batch-dir': { type: 'string' },
|
|
125
134
|
concurrency: { type: 'string' },
|
|
126
135
|
}, USAGE);
|
|
@@ -158,6 +167,9 @@ export const run = {
|
|
|
158
167
|
// TUI, where there is someone to ask, still asks.
|
|
159
168
|
const input = request ? request.input : prompt;
|
|
160
169
|
const shot = Boolean(prompt) || Boolean(request);
|
|
170
|
+
if (values.events && !shot) {
|
|
171
|
+
throw usageError('--events records a one-shot run', 'give a prompt or --input');
|
|
172
|
+
}
|
|
161
173
|
const where = await target({
|
|
162
174
|
cwd: ctx.cwd,
|
|
163
175
|
project: request?.project ?? values.project ?? (named ? head : undefined),
|
|
@@ -179,6 +191,7 @@ export const run = {
|
|
|
179
191
|
memoryReadOnly: values['memory-read-only'],
|
|
180
192
|
keys: values['no-keys'] ? false : undefined,
|
|
181
193
|
yes: values.yes || ctx.json,
|
|
194
|
+
disposable: values.disposable,
|
|
182
195
|
});
|
|
183
196
|
try {
|
|
184
197
|
// The TUI is for the case it is actually good at: a person at a
|
|
@@ -188,7 +201,8 @@ export const run = {
|
|
|
188
201
|
const drawing = !shot &&
|
|
189
202
|
!values.plain &&
|
|
190
203
|
!ctx.json &&
|
|
191
|
-
Boolean(process.stdout.isTTY && process.stdin.isTTY)
|
|
204
|
+
Boolean(process.stdout.isTTY && process.stdin.isTTY) &&
|
|
205
|
+
!drivenByAgent();
|
|
192
206
|
if (drawing) {
|
|
193
207
|
const { start } = await import("../tui/app.js");
|
|
194
208
|
await start(engine, {
|
|
@@ -222,6 +236,8 @@ const NOT_IN_A_BATCH = [
|
|
|
222
236
|
['workspace', 'they cannot share one; each item gets its own, or names one in the file'],
|
|
223
237
|
['plain', 'a batch never draws the TUI'],
|
|
224
238
|
['theme', 'a batch never draws the TUI'],
|
|
239
|
+
['events', 'each item already has its own output.json'],
|
|
240
|
+
['disposable', 'a batch removes its containers already'],
|
|
225
241
|
];
|
|
226
242
|
async function batch(ctx, values, rest, piped) {
|
|
227
243
|
if (rest.length > 0) {
|
|
@@ -261,11 +277,18 @@ async function once(engine, input, values, asJson, cwd, where) {
|
|
|
261
277
|
quiet: asJson,
|
|
262
278
|
live: Boolean(process.stderr.isTTY),
|
|
263
279
|
});
|
|
280
|
+
const record = values.events ? agentEventLog(resolve(cwd, values.events)) : undefined;
|
|
281
|
+
const onEvent = (event) => {
|
|
282
|
+
narrator.handle(event);
|
|
283
|
+
record?.(event);
|
|
284
|
+
};
|
|
264
285
|
// Ctrl-C asks the run to stop rather than killing the process, so the turn
|
|
265
|
-
// still lands on disk and the session stays resumable.
|
|
286
|
+
// still lands on disk and the session stays resumable. SIGTERM too: it is
|
|
287
|
+
// how a tuning stops a run it left behind, and its containers go with it.
|
|
266
288
|
const stopping = new AbortController();
|
|
267
289
|
const onInterrupt = () => stopping.abort();
|
|
268
290
|
process.once('SIGINT', onInterrupt);
|
|
291
|
+
process.once('SIGTERM', onInterrupt);
|
|
269
292
|
if (!asJson) {
|
|
270
293
|
// Two questions were just answered, possibly without being asked. Say
|
|
271
294
|
// which way they went: a run that quietly resumed the wrong session, or
|
|
@@ -278,12 +301,23 @@ async function once(engine, input, values, asJson, cwd, where) {
|
|
|
278
301
|
}
|
|
279
302
|
let outcome;
|
|
280
303
|
try {
|
|
281
|
-
outcome = await Engine.run(engine, input,
|
|
304
|
+
outcome = await Engine.run(engine, input, onEvent, stopping.signal);
|
|
282
305
|
}
|
|
283
306
|
finally {
|
|
284
307
|
narrator.done();
|
|
285
308
|
process.off('SIGINT', onInterrupt);
|
|
309
|
+
process.off('SIGTERM', onInterrupt);
|
|
286
310
|
}
|
|
311
|
+
appendUsage(where.project.dir, {
|
|
312
|
+
kind: 'run',
|
|
313
|
+
ts: new Date().toISOString(),
|
|
314
|
+
runDir: outcome.run.dir,
|
|
315
|
+
session: engine.session.id,
|
|
316
|
+
ok: true,
|
|
317
|
+
stopReason: outcome.result.stopReason,
|
|
318
|
+
durationMs: outcome.durationMs,
|
|
319
|
+
models: usageByModel(outcome.result.state.trajectory),
|
|
320
|
+
});
|
|
287
321
|
if (asJson) {
|
|
288
322
|
// --out is a destination, not a copy, and with --json the answer *is*
|
|
289
323
|
// the envelope — so that is what lands in the file, not the prose.
|
package/dist/commands/sandbox.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { readProjectConfig, sandboxLimits, } from '@zenera/neo';
|
|
2
2
|
import { parse } from "../args.js";
|
|
3
3
|
import { resolveBuild } from "../image.js";
|
|
4
|
-
import { engineDisk, ensurePodmanReady, ownedContainers, podmanStatus, removeContainers, } from "../podman.js";
|
|
4
|
+
import { engineDisk, ensurePodmanReady, idleContainers, ownedContainers, podmanStatus, removeContainers, sessionOwners, } from "../podman.js";
|
|
5
5
|
import { current as currentProject, dirSize, isProjectDir, open as openProject, Registry, sessionIds, } from "../projects.js";
|
|
6
|
-
import { ago, bold, bytes, dim, green, json, note, red, table, usageError, write, writeAll, yellow, } from "../term.js";
|
|
6
|
+
import { ago, bold, bytes, count, dim, green, json, note, red, table, usageError, write, writeAll, yellow, } from "../term.js";
|
|
7
7
|
const USAGE = 'zen sandbox [status|up|pull|clean|disk] [options]';
|
|
8
8
|
/** Enough to see the pattern; the rest are a number. */
|
|
9
9
|
const LISTED = 6;
|
|
@@ -11,6 +11,8 @@ const LISTED = 6;
|
|
|
11
11
|
const INDENT = ' '.repeat(11);
|
|
12
12
|
/** `agents.yaml` states memory in MiB; `bytes` prints bytes. */
|
|
13
13
|
const MIB = 1024 * 1024;
|
|
14
|
+
/** Below this many free locks, `status` says so: a batch can spend that in minutes. */
|
|
15
|
+
const FEW_LOCKS = 256;
|
|
14
16
|
// ---------------------------------------------------------------------------
|
|
15
17
|
// The container engine, on its own
|
|
16
18
|
//
|
|
@@ -33,6 +35,8 @@ export const sandbox = {
|
|
|
33
35
|
'',
|
|
34
36
|
' --project <name|dir> Which project the image and containers belong to.',
|
|
35
37
|
' --image <ref> Use this image instead of the project\u2019s.',
|
|
38
|
+
' --idle With clean: only what no live run is using — stopped ones,',
|
|
39
|
+
' and running ones whose run was killed before it cleaned up.',
|
|
36
40
|
'',
|
|
37
41
|
'Without --project this is the project you are standing in, and standing',
|
|
38
42
|
'nowhere is an answer: `status` then reports the engine and every container',
|
|
@@ -45,6 +49,8 @@ export const sandbox = {
|
|
|
45
49
|
const { values, positionals } = parse(ctx.args, {
|
|
46
50
|
project: { type: 'string' },
|
|
47
51
|
image: { type: 'string' },
|
|
52
|
+
stopped: { type: 'boolean' },
|
|
53
|
+
idle: { type: 'boolean' },
|
|
48
54
|
}, USAGE);
|
|
49
55
|
const what = positionals[0] ?? 'status';
|
|
50
56
|
if (!['status', 'up', 'pull', 'clean', 'disk'].includes(what)) {
|
|
@@ -70,7 +76,7 @@ export const sandbox = {
|
|
|
70
76
|
}
|
|
71
77
|
return up(image, build, true, ctx.json, true);
|
|
72
78
|
case 'clean':
|
|
73
|
-
return clean(ctx.json);
|
|
79
|
+
return clean(ctx.json, Boolean(values.idle || values.stopped));
|
|
74
80
|
case 'disk':
|
|
75
81
|
return disk(ctx.json);
|
|
76
82
|
}
|
|
@@ -116,6 +122,7 @@ async function status(project, image, asJson) {
|
|
|
116
122
|
// a report about one project must not list another's — or the faker's,
|
|
117
123
|
// which wears the same label and belongs to no project at all.
|
|
118
124
|
const containers = project ? ofProject(project.dir, all) : all;
|
|
125
|
+
const idle = all.length > 0 ? idleContainers(all, await sessionOwners()) : [];
|
|
119
126
|
if (asJson) {
|
|
120
127
|
json({
|
|
121
128
|
...found,
|
|
@@ -124,6 +131,7 @@ async function status(project, image, asJson) {
|
|
|
124
131
|
limits: project?.limits ?? null,
|
|
125
132
|
fits: fits(found.capacity?.memory, project?.limits?.memory) ?? null,
|
|
126
133
|
containers,
|
|
134
|
+
idle: idle.map((c) => c.name),
|
|
127
135
|
});
|
|
128
136
|
return;
|
|
129
137
|
}
|
|
@@ -153,7 +161,8 @@ async function status(project, image, asJson) {
|
|
|
153
161
|
if (build) {
|
|
154
162
|
write(`${bold('dockerfile')} ${dim(build.dockerfile)}`);
|
|
155
163
|
}
|
|
156
|
-
writeAll(
|
|
164
|
+
writeAll(lockLines(found.freeLocks, idle.length));
|
|
165
|
+
writeAll(containerLines(containers, idle, project?.name, all.length));
|
|
157
166
|
if (!found.installed || !found.ready) {
|
|
158
167
|
note('');
|
|
159
168
|
note(dim('run `zen sandbox up` to fix what can be fixed.'));
|
|
@@ -225,6 +234,22 @@ function ofProject(dir, containers) {
|
|
|
225
234
|
const sessions = new Set(sessionIds(dir));
|
|
226
235
|
return containers.filter((c) => c.key !== undefined && sessions.has(c.key));
|
|
227
236
|
}
|
|
237
|
+
/**
|
|
238
|
+
* Every container the engine holds takes one of its locks, stopped or not, and
|
|
239
|
+
* at zero nothing can be created — for any project. Said only when it is close.
|
|
240
|
+
*/
|
|
241
|
+
function lockLines(free, idle) {
|
|
242
|
+
if (free === undefined || free >= FEW_LOCKS) {
|
|
243
|
+
return [];
|
|
244
|
+
}
|
|
245
|
+
const left = free === 0 ? red('none free') : yellow(`${free} free`);
|
|
246
|
+
return [
|
|
247
|
+
`${bold('locks')} ${left} ${dim('· one per container, stopped ones included')}`,
|
|
248
|
+
...(idle > 0
|
|
249
|
+
? [`${INDENT}${dim(`${idle} no live run is using — zen sandbox clean --idle`)}`]
|
|
250
|
+
: []),
|
|
251
|
+
];
|
|
252
|
+
}
|
|
228
253
|
/**
|
|
229
254
|
* One per line rather than one long line, because there is normally more than
|
|
230
255
|
* one and the interesting part — how old, and whether anything is still up —
|
|
@@ -235,21 +260,31 @@ function ofProject(dir, containers) {
|
|
|
235
260
|
* stopped ones behind. `scope` names the project these belong to, and saying
|
|
236
261
|
* so is half the answer — the other half is that there are more elsewhere.
|
|
237
262
|
*/
|
|
238
|
-
function containerLines(containers, scope) {
|
|
263
|
+
function containerLines(containers, idle, scope, total = containers.length) {
|
|
239
264
|
const tail = scope
|
|
240
265
|
? `one per session in ${scope}, kept by \`persist: true\` — all of them: zen sandbox disk`
|
|
241
266
|
: 'one per session, kept by `persist: true` — see: zen sandbox disk';
|
|
267
|
+
const elsewhere = total > containers.length ? ` · ${total} on the machine` : '';
|
|
242
268
|
if (containers.length === 0) {
|
|
243
|
-
return [`${bold('containers')} ${dim(scope ? `none in ${scope}` : 'none')}`];
|
|
269
|
+
return [`${bold('containers')} ${dim(scope ? `none in ${scope}${elsewhere}` : 'none')}`];
|
|
244
270
|
}
|
|
271
|
+
// Running, but the run that started it is gone: nothing will ever stop it.
|
|
272
|
+
const orphans = new Set(idle.filter((c) => c.state === 'running').map((c) => c.name));
|
|
245
273
|
const running = containers.filter((c) => c.state === 'running').length;
|
|
246
|
-
const
|
|
274
|
+
const lost = containers.filter((c) => orphans.has(c.name)).length;
|
|
275
|
+
const head = `${bold('containers')} ${containers.length} ${dim((running ? `· ${running} running` : '· none running') +
|
|
276
|
+
(lost ? `, ${lost} of them orphaned` : '') +
|
|
277
|
+
elsewhere)}`;
|
|
247
278
|
const rows = containers
|
|
248
279
|
.slice(0, LISTED)
|
|
249
280
|
.map((c) => [
|
|
250
281
|
INDENT.slice(2),
|
|
251
282
|
c.name,
|
|
252
|
-
|
|
283
|
+
orphans.has(c.name)
|
|
284
|
+
? yellow('orphaned')
|
|
285
|
+
: c.state === 'running'
|
|
286
|
+
? green('running')
|
|
287
|
+
: dim(c.state),
|
|
253
288
|
dim(ago(c.createdAt)),
|
|
254
289
|
]);
|
|
255
290
|
const rest = containers.length - LISTED;
|
|
@@ -258,6 +293,9 @@ function containerLines(containers, scope) {
|
|
|
258
293
|
...table(rows),
|
|
259
294
|
...(rest > 0 ? [`${INDENT}${dim(`+${rest} more`)}`] : []),
|
|
260
295
|
`${INDENT}${dim(tail)}`,
|
|
296
|
+
...(lost
|
|
297
|
+
? [`${INDENT}${dim('orphaned: its run was killed — zen sandbox clean --idle')}`]
|
|
298
|
+
: []),
|
|
261
299
|
];
|
|
262
300
|
}
|
|
263
301
|
async function up(image, build, yes, asJson, rebuild = false) {
|
|
@@ -268,8 +306,9 @@ async function up(image, build, yes, asJson, rebuild = false) {
|
|
|
268
306
|
}
|
|
269
307
|
write(`${green('ready')}${image ? ` ${dim(image)}` : ''}`);
|
|
270
308
|
}
|
|
271
|
-
async function clean(asJson) {
|
|
272
|
-
const
|
|
309
|
+
async function clean(asJson, idle) {
|
|
310
|
+
const owned = await ownedContainers(undefined, undefined, { sizes: true });
|
|
311
|
+
const containers = idle ? idleContainers(owned, await sessionOwners()) : owned;
|
|
273
312
|
const names = containers.map((c) => c.name);
|
|
274
313
|
const freed = containers.reduce((n, c) => n + (c.size ?? 0), 0);
|
|
275
314
|
await removeContainers(names);
|
|
@@ -307,10 +346,30 @@ async function disk(asJson) {
|
|
|
307
346
|
write('');
|
|
308
347
|
}
|
|
309
348
|
writeAll(projectRows(projects, loose));
|
|
310
|
-
|
|
349
|
+
const idle = idleContainers(containers, await sessionOwners());
|
|
350
|
+
const live = containers.length - idle.length;
|
|
351
|
+
const sessions = projects.reduce((n, p) => n + p.files, 0);
|
|
352
|
+
const hints = [
|
|
353
|
+
...(idle.length > 0
|
|
354
|
+
? [`zen sandbox clean --idle ${count(idle.length, 'container')} no live run is using`]
|
|
355
|
+
: []),
|
|
356
|
+
...(live > 0
|
|
357
|
+
? [`zen sandbox clean also the ${live} in use, cutting off whatever runs there`]
|
|
358
|
+
: []),
|
|
359
|
+
...(usage && usage.images.reclaimable > 0
|
|
360
|
+
? [`podman image prune -a ${bytes(usage.images.reclaimable)} of unused images`]
|
|
361
|
+
: []),
|
|
362
|
+
// The ON DISK column is the bulk of the report and no command above touches it.
|
|
363
|
+
...(sessions > 0
|
|
364
|
+
? [
|
|
365
|
+
`${bytes(sessions)} ON DISK is project directories on this host, not the engine: ` +
|
|
366
|
+
'removing containers frees none of it',
|
|
367
|
+
]
|
|
368
|
+
: []),
|
|
369
|
+
];
|
|
370
|
+
if (hints.length > 0) {
|
|
311
371
|
write('');
|
|
312
|
-
|
|
313
|
-
write(dim(`podman image prune -a ${bytes(usage.images.reclaimable)} of unused images`));
|
|
372
|
+
writeAll(hints.map((h) => dim(h)));
|
|
314
373
|
}
|
|
315
374
|
}
|
|
316
375
|
function engineRows(usage) {
|
|
@@ -382,13 +441,16 @@ function projectRows(projects, loose) {
|
|
|
382
441
|
// Containers whose session directory is gone, and faker's, which are
|
|
383
442
|
// labelled the same way and belong to no project at all.
|
|
384
443
|
const size = loose.reduce((n, c) => n + (c.size ?? 0), 0);
|
|
444
|
+
const fakers = loose.filter((c) => c.key?.startsWith('faker-')).length;
|
|
385
445
|
rows.push([
|
|
386
446
|
dim('(unclaimed)'),
|
|
387
447
|
dim('—'),
|
|
388
448
|
dim('—'),
|
|
389
449
|
dim(String(loose.length)),
|
|
390
450
|
dim(bytes(size)),
|
|
391
|
-
dim(
|
|
451
|
+
dim(fakers === loose.length
|
|
452
|
+
? `zen faker ${fakers === 1 ? 'server' : 'servers'}, no project`
|
|
453
|
+
: 'no session owns these'),
|
|
392
454
|
]);
|
|
393
455
|
}
|
|
394
456
|
const total = (pick) => projects.reduce((n, p) => n + pick(p), 0);
|
package/dist/engine.d.ts
CHANGED
|
@@ -24,6 +24,11 @@ export interface EngineOptions {
|
|
|
24
24
|
* complaint is a worse report than one.
|
|
25
25
|
*/
|
|
26
26
|
quiet?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* The session will never be resumed, so its containers are removed on close
|
|
29
|
+
* whatever `persist:` says. A stopped one would only hold an engine lock.
|
|
30
|
+
*/
|
|
31
|
+
disposable?: boolean;
|
|
27
32
|
}
|
|
28
33
|
export interface Engine {
|
|
29
34
|
project: AgentProject;
|
package/dist/engine.js
CHANGED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { type AgentEvent } from '@zenera/neo';
|
|
2
|
+
export type EventBody = {
|
|
3
|
+
type: 'llm';
|
|
4
|
+
model: string;
|
|
5
|
+
in: number;
|
|
6
|
+
cached: number;
|
|
7
|
+
out: number;
|
|
8
|
+
reasoning: number;
|
|
9
|
+
ms?: number;
|
|
10
|
+
} | {
|
|
11
|
+
type: 'tool';
|
|
12
|
+
phase: 'start';
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
subject: string;
|
|
16
|
+
} | {
|
|
17
|
+
type: 'tool';
|
|
18
|
+
phase: 'end';
|
|
19
|
+
id: string;
|
|
20
|
+
name: string;
|
|
21
|
+
ok: boolean;
|
|
22
|
+
ms?: number;
|
|
23
|
+
} | {
|
|
24
|
+
type: 'say';
|
|
25
|
+
text: string;
|
|
26
|
+
};
|
|
27
|
+
export type EventLine = EventBody & {
|
|
28
|
+
t: string;
|
|
29
|
+
};
|
|
30
|
+
/** A line that cannot be written is dropped: watching a run must not break it. */
|
|
31
|
+
export declare function eventLog(path: string): (body: EventBody) => void;
|
|
32
|
+
/** For `zen run`: the runtime's own events, folded to the shared lines. */
|
|
33
|
+
export declare function agentEventLog(path: string): (event: AgentEvent) => void;
|
|
34
|
+
/** One line of copilot's JSONL, as much of it as is read here. */
|
|
35
|
+
export interface CopilotEvent {
|
|
36
|
+
type: string;
|
|
37
|
+
data?: Record<string, unknown>;
|
|
38
|
+
}
|
|
39
|
+
/** A model call read off copilot's span file - see `meta/tokens.ts`. */
|
|
40
|
+
export interface CopilotCall {
|
|
41
|
+
model: string;
|
|
42
|
+
usage: {
|
|
43
|
+
inputTokens: number;
|
|
44
|
+
cachedInputTokens: number;
|
|
45
|
+
outputTokens: number;
|
|
46
|
+
reasoningTokens: number;
|
|
47
|
+
};
|
|
48
|
+
durationMs: number;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* For `zen meta run`: copilot's events and spans, folded to the same lines.
|
|
52
|
+
* Its JSONL has no token counts, so `llm` lines come from the spans instead,
|
|
53
|
+
* about a second after each call.
|
|
54
|
+
*/
|
|
55
|
+
export declare function copilotEventLog(path: string): {
|
|
56
|
+
event: (event: CopilotEvent) => void;
|
|
57
|
+
call: (call: CopilotCall) => void;
|
|
58
|
+
};
|
|
59
|
+
//# sourceMappingURL=eventlog.d.ts.map
|