@kybernesis/create 0.13.4 → 0.14.1

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/upgrade.js CHANGED
@@ -1,6 +1,10 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { upsertEnv } from "./envfile.js";
4
+ import { reconcileHostArtifact } from "./host-artifacts.js";
5
+ import { repairManageRestart } from "./systemd.js";
6
+ import { repairTerminalSandboxCleanupHooks } from "./sandbox-cleanup.js";
7
+ import { repairRemovedDefaultTools as repairRemovedDefaultTools_ } from "./removed-default-tools.js";
4
8
  import { EVE_VERSION, bold, capture, dim, green, red, run, yellow } from "./util.js";
5
9
  /**
6
10
  * Which packages to upgrade: every `@kybernesis/*` this agent depends on.
@@ -179,54 +183,125 @@ function repairBuzzSetup(cwd, deps) {
179
183
  console.log(` ${dim("Takes effect after the next build and restart.")}\n`);
180
184
  }
181
185
  }
186
+ /** Quote one argument for the manual shell commands printed after a failed repair. */
187
+ function shellQuote(value) {
188
+ return `'${value.replaceAll("'", `'"'"'`)}'`;
189
+ }
182
190
  /**
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.
191
+ * eve 0.39 removed glob and grep from the default tool set; see
192
+ * removed-default-tools.ts. Runs before typecheck because a stale disable
193
+ * file is a compile error from 0.39 on, and an upgrade must never leave an
194
+ * agent that cannot build.
197
195
  */
198
- function repairDockerPrune(cwd, deps) {
196
+ function repairRemovedDefaultTools(cwd) {
197
+ const { removed, optedIn } = repairRemovedDefaultTools_(cwd);
198
+ for (const file of removed) {
199
+ console.log(` ${green("-")} removed ${file} ${dim("(disabled a tool eve no longer provides by default; a compile error from eve 0.39)")}`);
200
+ }
201
+ for (const file of optedIn) {
202
+ console.log(` ${green("+")} wrote ${file} ${dim("(keeps the tool this scope had on eve 0.38)")}`);
203
+ }
204
+ if (removed.length || optedIn.length)
205
+ console.log();
206
+ }
207
+ function repairSandboxCleanupHooks(cwd, deps) {
199
208
  if (!deps["@kybernesis/exe"])
200
209
  return;
201
- if (capture("sh", ["-c", "command -v docker >/dev/null && echo yes"])?.trim() !== "yes")
210
+ const added = repairTerminalSandboxCleanupHooks(cwd);
211
+ if (added.length === 0)
202
212
  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))
213
+ console.log(` ${green("+")} installed terminal sandbox cleanup for ${added.length} agent scope(s) ` +
214
+ `${dim("(completed and failed sessions only)")}\n`);
215
+ }
216
+ /**
217
+ * Keep package-owned host files synchronized with the package that installed them.
218
+ *
219
+ * Existing artifacts are compared even when the capability they support is
220
+ * temporarily unavailable. Missing cron installation remains Docker-gated, and
221
+ * a missing systemd unit remains an explicit operator choice.
222
+ */
223
+ function repairHostArtifacts(cwd, deps) {
224
+ if (!deps["@kybernesis/exe"])
208
225
  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`);
226
+ const scripts = join(cwd, "node_modules/@kybernesis/exe/scripts");
227
+ const pruneSource = join(scripts, "docker-prune.sh");
228
+ const pruneTarget = "/etc/cron.daily/kyb-docker-prune";
229
+ const hasDocker = capture("sh", ["-c", "command -v docker >/dev/null && echo yes"])?.trim() === "yes";
230
+ if (existsSync(pruneSource)) {
231
+ const pruneResult = reconcileHostArtifact({
232
+ targetPath: pruneTarget,
233
+ desiredContent: readFileSync(pruneSource),
234
+ expectedMode: 0o755,
235
+ installIfMissing: hasDocker,
236
+ owner: "@kybernesis/exe/scripts/docker-prune.sh",
237
+ update: () => run("sudo", ["-n", "install", "-m", "0755", pruneSource, pruneTarget], {
238
+ cwd,
239
+ allowFail: true,
240
+ quiet: true,
241
+ }),
242
+ manualCommand: `sudo install -m 0755 ${shellQuote(pruneSource)} ${pruneTarget}`,
243
+ });
244
+ if ((pruneResult === "current" || pruneResult === "updated") && existsSync("/etc/cron.weekly/docker-prune")) {
245
+ const removed = run("sudo", ["-n", "rm", "-f", "/etc/cron.weekly/docker-prune"], {
246
+ cwd,
247
+ allowFail: true,
248
+ quiet: true,
249
+ });
250
+ if (!removed) {
251
+ console.log(` ${yellow("!")} the daily docker reclaim is current, but the legacy weekly job remains. Run:\n` +
252
+ ` ${dim("sudo rm -f /etc/cron.weekly/docker-prune")}`);
253
+ }
254
+ }
224
255
  }
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")}`);
256
+ const serviceScript = join(scripts, "install-service.sh");
257
+ if (!existsSync(serviceScript))
258
+ return;
259
+ const unitTarget = capture("bash", [serviceScript, "--unit-path"], cwd)?.trim();
260
+ const unitContent = capture("bash", [serviceScript, "--print-unit"], cwd);
261
+ const marker = capture("bash", [serviceScript, "--managed-marker"], cwd)?.trim();
262
+ if (!unitTarget || unitContent === null || !marker)
263
+ return;
264
+ /**
265
+ * Only a unit this package wrote is this package's to rewrite.
266
+ *
267
+ * Every host from before the installer existed has a hand-written unit —
268
+ * some with Environment= lines the renderer knows nothing about. Reconciling
269
+ * those would report "drifted (content)" and replace them, and a warning
270
+ * that scrolls past in the middle of an upgrade is not consent. The marker
271
+ * is the first line of every generated unit; a unit without it is reported
272
+ * and left exactly as it is.
273
+ */
274
+ if (existsSync(unitTarget)) {
275
+ let installed;
276
+ try {
277
+ installed = readFileSync(unitTarget, "utf8");
278
+ }
279
+ catch (error) {
280
+ console.log(` ${yellow("!")} could not read ${unitTarget} to check whether it is package-managed ` +
281
+ `(${error.message}); leaving it alone.`);
282
+ return;
283
+ }
284
+ if (!installed.includes(marker)) {
285
+ console.log(` ${yellow("!")} ${unitTarget} was not written by @kybernesis/exe, so it was left as is.\n` +
286
+ ` To adopt the package-managed unit (the current file is kept at ${unitTarget}.bak):\n` +
287
+ ` ${dim(`bash ${shellQuote(serviceScript)} --refresh-unit`)}\n` +
288
+ ` Host-specific settings belong in a drop-in: ${dim(`sudo systemctl edit <name>-agent`)}`);
289
+ return;
290
+ }
229
291
  }
292
+ reconcileHostArtifact({
293
+ targetPath: unitTarget,
294
+ desiredContent: Buffer.from(unitContent),
295
+ expectedMode: 0o644,
296
+ installIfMissing: false,
297
+ owner: "@kybernesis/exe/scripts/install-service.sh",
298
+ update: () => run("env", ["KYB_NONINTERACTIVE=1", "bash", serviceScript, "--refresh-unit"], {
299
+ cwd,
300
+ allowFail: true,
301
+ quiet: true,
302
+ }),
303
+ manualCommand: `bash ${shellQuote(serviceScript)} --refresh-unit`,
304
+ });
230
305
  }
231
306
  export async function upgrade(skipEval) {
232
307
  const cwd = process.cwd();
@@ -286,6 +361,7 @@ export async function upgrade(skipEval) {
286
361
  console.log(`\n${yellow(`Checked what could be read. ${unresolved.length} package(s) could not be ` +
287
362
  `resolved, so this is not a clean bill of health.`)}\n` +
288
363
  ` ${dim("Usually: dependencies are not installed here. Run npm install, then kyb upgrade.")}\n`);
364
+ repairManageRestart(cwd, deps);
289
365
  return;
290
366
  }
291
367
  if (toUpgrade.length === 0) {
@@ -293,11 +369,24 @@ export async function upgrade(skipEval) {
293
369
  // Still repair: being on the right versions is not the same as being set
294
370
  // up. An agent can sit at latest for weeks with a capability switched off.
295
371
  repairBuzzSetup(cwd, deps);
296
- repairDockerPrune(cwd, deps);
372
+ repairRemovedDefaultTools(cwd);
373
+ repairSandboxCleanupHooks(cwd, deps);
374
+ repairHostArtifacts(cwd, deps);
375
+ repairManageRestart(cwd, deps);
297
376
  return;
298
377
  }
299
378
  console.log(bold(`\nInstalling: ${toUpgrade.join(", ")}\n`));
300
- run("npm", ["install", ...toUpgrade], { cwd });
379
+ /**
380
+ * One install, with peers relaxed. Every @kybernesis package peers on a
381
+ * narrow eve range, and eve moves with them: the installed set demands the
382
+ * old eve, the new set demands the new one, and npm's resolver refuses
383
+ * either order — eve first, packages first, or all at once — under strict
384
+ * peers (seen on Kyber moving 0.38 → 0.49: ERESOLVE all three ways). The
385
+ * set being installed is exactly the certified combination, so relaxing
386
+ * peer resolution for this one command changes nothing about what ends up
387
+ * on disk; the version check above is what guards correctness.
388
+ */
389
+ run("npm", ["install", ...toUpgrade, "--legacy-peer-deps"], { cwd });
301
390
  /**
302
391
  * Repairs run AFTER the install, not before.
303
392
  *
@@ -308,7 +397,10 @@ export async function upgrade(skipEval) {
308
397
  * bug that hides itself, because by then it looks like it always worked.
309
398
  */
310
399
  repairBuzzSetup(cwd, deps);
311
- repairDockerPrune(cwd, deps);
400
+ repairRemovedDefaultTools(cwd);
401
+ repairSandboxCleanupHooks(cwd, deps);
402
+ repairHostArtifacts(cwd, deps);
403
+ repairManageRestart(cwd, deps);
312
404
  run("npm", ["run", "typecheck"], { cwd });
313
405
  if (eveChanged) {
314
406
  // A framework bump must also pass discovery/compile, not just types.
package/dist/util.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export declare const EVE_VERSION = "0.38.3";
1
+ export declare const EVE_VERSION = "0.49.0";
2
2
  export declare const REGISTRY_URL = "https://registry.kybernesis.ai/r/{name}.json";
3
3
  export declare const DEFAULT_ISSUER = "https://agent.kybernesis.ai";
4
4
  export declare const green: (s: string) => string;
package/dist/util.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { spawnSync } from "node:child_process";
2
2
  import { createInterface } from "node:readline/promises";
3
- export const EVE_VERSION = "0.38.3";
3
+ export const EVE_VERSION = "0.49.0";
4
4
  export const REGISTRY_URL = "https://registry.kybernesis.ai/r/{name}.json";
5
5
  export const DEFAULT_ISSUER = "https://agent.kybernesis.ai";
6
6
  const TTY = process.stdout.isTTY === true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kybernesis/create",
3
- "version": "0.13.4",
3
+ "version": "0.14.1",
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",
@@ -33,7 +33,8 @@
33
33
  "build": "tsc -p tsconfig.build.json",
34
34
  "prepack": "tsc -p tsconfig.build.json",
35
35
  "typecheck": "tsc --noEmit",
36
- "prepublishOnly": "node ../../scripts/prepublish.mjs"
36
+ "prepublishOnly": "node ../../scripts/prepublish.mjs",
37
+ "test": "node --test \"test/**/*.test.mjs\""
37
38
  },
38
39
  "devDependencies": {
39
40
  "@types/node": "24.x",
@@ -188,7 +188,7 @@ something. Chase them a week out — an unprovisioned Vercel team on Day 1 costs
188
188
  `@kybernesis/arcana@0.1.1`, `@kybernesis/enterprise@0.1.2`,
189
189
  `@kybernesis/multiplayer@0.1.0`, `@kybernesis/evals@0.2.1`,
190
190
  `@kybernesis/create@0.1.4`, `@kybernesis/engineer@0.2.0`, and
191
- `eve@0.38.3` (the Kybernesis-certified version). All public on npm;
191
+ `eve@0.49.0` (the Kybernesis-certified version). All public on npm;
192
192
  `kyb doctor` checks the wiring.
193
193
 
194
194
  ---
@@ -354,8 +354,8 @@ npx eve registry view @kybernesis/arcana
354
354
  ### 3.4 Pin your versions
355
355
 
356
356
  Before you install anything else, decide and record the versions this engagement pins.
357
- Put them in the repo README. Pin `eve@0.38.3` — the **Kybernesis-certified** version
358
- (certification run 2026-08-17: full suite green; one schedule API change, see HANDOFF). Never pin blind
357
+ Put them in the repo README. Pin `eve@0.49.0` — the **Kybernesis-certified** version
358
+ (certification run 2026-09-01: full suite green; one schedule API change, see HANDOFF). Never pin blind
359
359
  npm-latest; `kyb upgrade` carries a client to the certified pin behind their own eval
360
360
  gate, and that upgrade is a **deliberate, eval-gated step**, never something that
361
361
  happens by accident mid-pilot.
@@ -10,7 +10,10 @@ Twelve packages, npm-public under `@kybernesis`, Apache-2.0, monorepo
10
10
  then `eve add @kybernesis/<item>`). Each covers one axis:
11
11
 
12
12
  - **arcana** — memory. Extension mount `arcana({ apiKey, workspace,
13
- resolveWorkspace? })`. Workspace-scoped `kb_` keys (403 outside their
13
+ resolveWorkspace? })`; from eve 0.49 also a memory PROVIDER:
14
+ `arcanaMemory()` from `@kybernesis/arcana/memory` in an `agent/memory/*.ts`
15
+ slot (`kyb add memory` writes it) — recall before every turn, skipped
16
+ under 4 words, capture off by default, tools left to the extension. Workspace-scoped `kb_` keys (403 outside their
14
17
  workspace — one key per brain). `resolveWorkspace` only from VERIFIED
15
18
  session context, never model output, and only key-reachable workspaces.
16
19
  Subagents: local extension mount (`subagents/<id>/extensions/arcana.ts`,
@@ -127,7 +130,7 @@ then `eve add @kybernesis/<item>`). Each covers one axis:
127
130
  asterisks onto the URL in Slack and breaks it. Coach agents in prose, not
128
131
  shell (WAFs eat shell-syntax Slack messages).
129
132
  - **ESM packaging**: relative imports need `.js` extensions (tsc doesn't
130
- rewrite); eve is a peer dep with an explicit range (`>=0.38.0 <0.39.0`),
133
+ rewrite); eve is a peer dep with an explicit range (`>=0.49.0 <0.50.0`),
131
134
  pinned exactly in devDeps.
132
135
  - **Eval fixtures are hardened on purpose** — in-test nonces (eve caches
133
136
  compiled eval modules), per-run unique keys (workspaces accumulate),