@kybernesis/create 0.13.0 → 0.13.2

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/cli.js CHANGED
@@ -57,6 +57,7 @@ function initOptions(rest) {
57
57
  studio: rest.includes('--studio'),
58
58
  channel: flag(rest, "channel"),
59
59
  host: flag(rest, "host"),
60
+ model: flag(rest, "model"),
60
61
  subagents: subs === undefined ? undefined : subs.split(',').map((s) => s.trim()).filter(Boolean),
61
62
  yes: rest.includes('--yes') || rest.includes('-y'),
62
63
  };
@@ -182,6 +183,7 @@ ${dim(" npm i -g @kybernesis/create@latest")}
182
183
  --channel=<kind> ${dim("none|slack|imessage|telegram|discord|web (default: none)")}
183
184
  --host=<kind> ${dim("vercel|exe (default: vercel)")}
184
185
  --subagents=a,b ${dim("department subagents (default: none)")}
186
+ --model=<id> ${dim("provider/model-id (default: sonnet 5)")}
185
187
  --engineer ${dim("add the engineer layer: workshop sandbox + vision dev loop")}
186
188
  --studio ${dim("wire for KYBER Studio: local execution + management routes")}
187
189
  --yes ${dim("no prompts; take flags and defaults")}
package/dist/doctor.js CHANGED
@@ -275,6 +275,27 @@ export async function doctor() {
275
275
  else {
276
276
  add("fail", `self-hosted: ${shortQueueTimeouts.join(" and ")} left at the 30s default`, "one queue delivery holds a connection open for the entire turn, so any turn slower than the timeout is redelivered and its steps re-run — the agent answers the same question twice, with two different answers, and nothing reports an error. Set both to 900000 in .env.local and restart the server");
277
277
  }
278
+ /**
279
+ * Disk, on a host whose runtime does not clean up after itself.
280
+ *
281
+ * eve builds a sandbox template per session configuration and keeps every
282
+ * one, and leaves session containers running long after their turn ended.
283
+ * The result is gigabytes a day on a working agent, and the failure it
284
+ * eventually produces looks like anything except a full disk.
285
+ */
286
+ if (capture("sh", ["-c", "command -v docker >/dev/null && echo yes"])?.trim() === "yes") {
287
+ const job = capture("sh", ["-c", "test -x /etc/cron.daily/kyb-docker-prune && echo yes"])?.trim();
288
+ const percent = Number(capture("sh", ["-c", "df / | awk 'NR==2{print $5}' | tr -d '%'"])?.trim() ?? 0);
289
+ if (job !== "yes") {
290
+ add("warn", "no daily docker reclaim on this host", "sandbox images and abandoned session containers accumulate by the gigabyte; `kyb upgrade` installs /etc/cron.daily/kyb-docker-prune");
291
+ }
292
+ else if (percent >= 80) {
293
+ add("fail", `disk ${percent}% full despite the reclaim job`, "run it now: sudo /etc/cron.daily/kyb-docker-prune, and check what else is on this host");
294
+ }
295
+ else {
296
+ add("pass", `daily docker reclaim installed (disk ${percent}% used)`);
297
+ }
298
+ }
278
299
  /**
279
300
  * Instructions that name a tool the agent does not have.
280
301
  *
package/dist/init.d.ts CHANGED
@@ -14,6 +14,18 @@ export interface InitOptions {
14
14
  channel?: ChannelKind;
15
15
  /** Where the agent runs. Default "vercel". */
16
16
  host?: HostKind;
17
+ /**
18
+ * The model, as provider/model-id.
19
+ *
20
+ * @remarks
21
+ * Passed straight through to `eve init`, and that is why it exists: without
22
+ * it eve ends its scaffold by opening its interactive model picker, which
23
+ * needs a terminal UI. On a laptop that is a prompt; on a headless machine
24
+ * it is `--input requires the interactive UI` and a scaffold that stops
25
+ * after step one having already written the project — so `--yes` was never
26
+ * actually non-interactive.
27
+ */
28
+ model?: string;
17
29
  /** Department subagents. Default NONE. */
18
30
  subagents?: string[];
19
31
  /** Skip prompts and take the flags/defaults as given. */
package/dist/init.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { chmodSync, copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
2
2
  import { join, resolve } from "node:path";
3
- import { DEFAULT_ISSUER, EVE_VERSION, REGISTRY_URL, ask, bold, closePrompts, dim, green, run, slug, yellow, } from "./util.js";
3
+ import { DEFAULT_ISSUER, EVE_VERSION, REGISTRY_URL, ask, bold, closePrompts, dim, green, run, slug, red, yellow, } from "./util.js";
4
4
  import { CHANNEL_KINDS, channelPlan, engineerPlan, envExample, evalFileTs, exeEvalConfigTs, evalScript, hostAgentTs, hostSteps, identityMd, rootArcanaTs, subagentAgentTs, subagentArcanaTs, subagentInstructionsMd, } from "./templates.js";
5
5
  import { suiteDir } from "./skills.js";
6
6
  import { configureArcana } from "./arcana.js";
@@ -58,8 +58,26 @@ export async function init(rawName, options = {}) {
58
58
  process.exit(1);
59
59
  }
60
60
  const plan = channelPlan(channel, name, host);
61
+ const model = options.model ?? DEFAULT_MODEL;
61
62
  console.log(bold(`\n1/6 Scaffolding eve agent (eve@${EVE_VERSION}) …`));
62
- run("npx", [`eve@${EVE_VERSION}`, "init", name]);
63
+ // eve's scaffold ends by opening its interactive model picker
64
+ // (`eve dev --input /model`), which exits non-zero wherever there is no
65
+ // terminal UI — AFTER the project is fully created and its dependencies
66
+ // installed. Treating that as a failure makes headless scaffolding
67
+ // impossible, which is what a machine building itself has to do.
68
+ //
69
+ // Nothing is lost by ignoring it: agent.ts is overwritten below with our own
70
+ // template carrying the chosen model, so eve's pick would not have survived
71
+ // this function either. `--model` is deliberately NOT passed through — eve
72
+ // refuses an id it cannot find in the AI Gateway catalog and then creates
73
+ // nothing at all, and an exe-hosted agent takes its model from EXE_MODEL,
74
+ // not from the gateway.
75
+ run("npx", [`eve@${EVE_VERSION}`, "init", name], { allowFail: true });
76
+ // The real test of that step, since its exit code cannot be trusted.
77
+ if (!existsSync(join(dir, "package.json"))) {
78
+ console.error(red(`\n eve did not create a project in ${dir}. Nothing else can run.`));
79
+ process.exit(1);
80
+ }
63
81
  console.log(bold("\n2/6 Adding the Kybernesis registry + core packages …"));
64
82
  run("npx", ["eve", "registry", "add", `@kybernesis=${REGISTRY_URL}`], { cwd: dir });
65
83
  for (const item of CORE_ITEMS) {
@@ -108,7 +126,7 @@ export async function init(rawName, options = {}) {
108
126
  ` This agent cannot be connected to a desktop until they install.`));
109
127
  }
110
128
  }
111
- const engPlan = engineer ? engineerPlan(host, DEFAULT_MODEL) : null;
129
+ const engPlan = engineer ? engineerPlan(host, model) : null;
112
130
  if (engPlan) {
113
131
  console.log(bold("\n2c Engineer subagent: workshop sandbox + vision dev loop …"));
114
132
  run("npm", ["install", ...engPlan.deps, "--no-audit", "--no-fund"], { cwd: dir, allowFail: true });
@@ -133,7 +151,7 @@ export async function init(rawName, options = {}) {
133
151
  unlinkSync(join(dir, "agent/instructions.md"));
134
152
  }
135
153
  catch { }
136
- writeFileSync(join(dir, "agent/agent.ts"), hostAgentTs(host, DEFAULT_MODEL));
154
+ writeFileSync(join(dir, "agent/agent.ts"), hostAgentTs(host, model));
137
155
  writeFileSync(join(dir, "agent/extensions/arcana.ts"), rootArcanaTs());
138
156
  writeFileSync(join(dir, "evals/kybernesis.eval.ts"), evalFileTs(displayName, depts));
139
157
  // A self-hosted agent judges through its own integration; the default
@@ -287,7 +305,7 @@ export async function init(rawName, options = {}) {
287
305
  }
288
306
  }
289
307
  console.log(bold("\n5/6 Env template + hermetic eval script …"));
290
- writeFileSync(join(dir, ".env.example"), envExample(name, depts, issuer, plan.env, host, DEFAULT_MODEL));
308
+ writeFileSync(join(dir, ".env.example"), envExample(name, depts, issuer, plan.env, host, model));
291
309
  const pkgPath = join(dir, "package.json");
292
310
  const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
293
311
  pkg.scripts = { ...pkg.scripts, eval: evalScript(name, depts) };
package/dist/upgrade.js CHANGED
@@ -179,14 +179,63 @@ function repairBuzzSetup(cwd, deps) {
179
179
  console.log(` ${dim("Takes effect after the next build and restart.")}\n`);
180
180
  }
181
181
  }
182
+ /**
183
+ * Keep the agent host from filling up with what the runtime leaves behind.
184
+ *
185
+ * @remarks
186
+ * eve builds a sandbox template image per session configuration and never
187
+ * collects the old ones, and it leaves session CONTAINERS running — a turn that
188
+ * took four minutes can still own a container, and its writable layer, eight
189
+ * days later. Forty stale images accumulated on one agent in a week; another
190
+ * reached 94% full and began failing in ways that looked like anything but a
191
+ * disk problem. Every self-hosted agent hits this; it is a property of the
192
+ * runtime, not of any one deployment.
193
+ *
194
+ * Installed rather than documented, and DAILY rather than weekly, because the
195
+ * accumulation is measured in gigabytes per day on an agent doing real work.
196
+ * The first version of this ran weekly and was already too slow.
197
+ */
198
+ function repairDockerPrune(cwd, deps) {
199
+ if (!deps["@kybernesis/exe"])
200
+ return;
201
+ if (capture("sh", ["-c", "command -v docker >/dev/null && echo yes"])?.trim() !== "yes")
202
+ return;
203
+ const installed = capture("sh", ["-c", "test -x /etc/cron.daily/kyb-docker-prune && echo yes"]);
204
+ if (installed?.trim() === "yes")
205
+ return;
206
+ const source = join(cwd, "node_modules/@kybernesis/exe/scripts/docker-prune.sh");
207
+ if (!existsSync(source))
208
+ return;
209
+ // `sudo -n`: this runs inside an upgrade, and an upgrade that stops to ask
210
+ // for a password in the middle of an unattended run is worse than one that
211
+ // says what it could not do.
212
+ const ok = run("sh", [
213
+ "-c",
214
+ // The weekly predecessor is removed in the same breath: two jobs pruning
215
+ // the same host is not twice as safe, it is one more thing to reason
216
+ // about when something unexpected disappears.
217
+ `sudo -n cp ${JSON.stringify(source)} /etc/cron.daily/kyb-docker-prune && ` +
218
+ `sudo -n chmod 755 /etc/cron.daily/kyb-docker-prune && ` +
219
+ `sudo -n rm -f /etc/cron.weekly/docker-prune`,
220
+ ], { cwd, allowFail: true, quiet: true });
221
+ if (ok) {
222
+ console.log(` ${green("+")} installed the daily docker reclaim (/etc/cron.daily/kyb-docker-prune)`);
223
+ console.log(` ${dim("eve leaves sandbox images and running session containers behind; this collects them.")}\n`);
224
+ }
225
+ else {
226
+ console.log(` ${yellow("!")} could not install the docker reclaim job (needs sudo). Without it this host ` +
227
+ `fills with stale sandbox images. Run:\n` +
228
+ ` ${dim("sudo cp node_modules/@kybernesis/exe/scripts/docker-prune.sh /etc/cron.daily/kyb-docker-prune && sudo chmod 755 /etc/cron.daily/kyb-docker-prune")}`);
229
+ }
230
+ }
182
231
  export async function upgrade(skipEval) {
183
232
  const cwd = process.cwd();
184
233
  const pkg = JSON.parse(readFileSync(join(cwd, "package.json"), "utf8"));
185
234
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
186
235
  console.log(bold("\nkyb upgrade — checking @kybernesis/* and eve against npm\n"));
187
236
  warnIfStale();
237
+ // Env-only, so it is safe before anything is installed.
188
238
  repairLocalQueueTimeouts(cwd, deps);
189
- repairBuzzSetup(cwd, deps);
190
239
  const toUpgrade = [];
191
240
  const unresolved = [];
192
241
  for (const name of kybernesisPackages(deps)) {
@@ -241,10 +290,25 @@ export async function upgrade(skipEval) {
241
290
  }
242
291
  if (toUpgrade.length === 0) {
243
292
  console.log(`\n${green("Everything is at latest certified versions.")}\n`);
293
+ // Still repair: being on the right versions is not the same as being set
294
+ // up. An agent can sit at latest for weeks with a capability switched off.
295
+ repairBuzzSetup(cwd, deps);
296
+ repairDockerPrune(cwd, deps);
244
297
  return;
245
298
  }
246
299
  console.log(bold(`\nInstalling: ${toUpgrade.join(", ")}\n`));
247
300
  run("npm", ["install", ...toUpgrade], { cwd });
301
+ /**
302
+ * Repairs run AFTER the install, not before.
303
+ *
304
+ * Both of these copy files out of packages that the install has just put
305
+ * there — a proxy script, a cron job, a CLI. Running them first meant looking
306
+ * for a file the older installed version did not ship: the repair found
307
+ * nothing, said nothing, and only worked on the NEXT upgrade. Which is a
308
+ * bug that hides itself, because by then it looks like it always worked.
309
+ */
310
+ repairBuzzSetup(cwd, deps);
311
+ repairDockerPrune(cwd, deps);
248
312
  run("npm", ["run", "typecheck"], { cwd });
249
313
  if (eveChanged) {
250
314
  // A framework bump must also pass discovery/compile, not just types.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kybernesis/create",
3
- "version": "0.13.0",
3
+ "version": "0.13.2",
4
4
  "description": "The Kybernesis agent scaffolder and FDE toolkit: one command to a governed, remembering, multiplayer, self-testing eve agent — plus doctor and upgrade.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",