@kybernesis/create 0.14.9 → 0.14.10

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.
@@ -0,0 +1,8 @@
1
+ export declare function registeredDockerPruneAppDirs(installedContent: string | null): string[];
2
+ export interface DockerPruneCronArtifact {
3
+ content: Buffer;
4
+ appDirs: string[];
5
+ manualCommand: string;
6
+ }
7
+ /** Render the package script with a managed, mergeable app-directory header. */
8
+ export declare function dockerPruneCronArtifact(packageSource: string, currentAppDir: string, installedContent: string | null, targetPath: string): DockerPruneCronArtifact;
@@ -0,0 +1,38 @@
1
+ const MANAGED_PREFIX = "# kyb-managed-eve-app-dirs: ";
2
+ function shellSingleQuote(value) {
3
+ return `'${value.replaceAll("'", `'"'"'`)}'`;
4
+ }
5
+ export function registeredDockerPruneAppDirs(installedContent) {
6
+ if (!installedContent)
7
+ return [];
8
+ const line = installedContent.split("\n").find((candidate) => candidate.startsWith(MANAGED_PREFIX));
9
+ if (!line)
10
+ return [];
11
+ try {
12
+ const parsed = JSON.parse(line.slice(MANAGED_PREFIX.length));
13
+ return Array.isArray(parsed) && parsed.every((value) => typeof value === "string") ? parsed : [];
14
+ }
15
+ catch {
16
+ return [];
17
+ }
18
+ }
19
+ /** Render the package script with a managed, mergeable app-directory header. */
20
+ export function dockerPruneCronArtifact(packageSource, currentAppDir, installedContent, targetPath) {
21
+ const appDirs = [...new Set([...registeredDockerPruneAppDirs(installedContent), currentAppDir])];
22
+ const newline = packageSource.indexOf("\n");
23
+ const shebang = newline === -1 ? packageSource : packageSource.slice(0, newline);
24
+ const body = newline === -1 ? "" : packageSource.slice(newline + 1);
25
+ const joined = appDirs.join(":");
26
+ const content = Buffer.from(`${shebang}\n` +
27
+ `${MANAGED_PREFIX}${JSON.stringify(appDirs)}\n` +
28
+ `if [ -z "\${EVE_APP_DIRS:-}" ]; then EVE_APP_DIRS=${shellSingleQuote(joined)}; fi\n` +
29
+ `export EVE_APP_DIRS\n` +
30
+ body);
31
+ const encoded = content.toString("base64");
32
+ return {
33
+ content,
34
+ appDirs,
35
+ manualCommand: `printf %s ${shellSingleQuote(encoded)} | base64 -d | ` +
36
+ `sudo install -m 0755 /dev/stdin ${shellSingleQuote(targetPath)}`,
37
+ };
38
+ }
package/dist/doctor.d.ts CHANGED
@@ -22,6 +22,7 @@ export interface DockerTemplateInspection {
22
22
  export declare function dockerTemplateDoctorChecks(result: DockerTemplateInspection): Check[];
23
23
  export declare function modelReachDoctorCheck(authoredSource: string | null, compiledRouting: CompiledModelRouting): Check;
24
24
  export declare function agentInputLimitDoctorCheck(limit: AgentInputLimit): Check;
25
+ export declare function diskUsageDoctorCheck(percent: number, dockerSummary: string | null): Check;
25
26
  /**
26
27
  * The inspector lives in @kybernesis/exe, next to the runtime that owns the
27
28
  * templates; create already resolves that package's scripts out of
package/dist/doctor.js CHANGED
@@ -84,6 +84,15 @@ export function agentInputLimitDoctorCheck(limit) {
84
84
  };
85
85
  }
86
86
  }
87
+ export function diskUsageDoctorCheck(percent, dockerSummary) {
88
+ if (percent < 85)
89
+ return { verdict: "pass", label: `disk ${percent}% used` };
90
+ const detail = `Docker: ${dockerSummary?.trim() || "docker system df unavailable"}. ` +
91
+ `Run: sudo /etc/cron.daily/kyb-docker-prune`;
92
+ if (percent < 95)
93
+ return { verdict: "warn", label: `disk ${percent}% used`, detail };
94
+ return { verdict: "fail", label: `disk ${percent}% used`, detail };
95
+ }
87
96
  /**
88
97
  * The inspector lives in @kybernesis/exe, next to the runtime that owns the
89
98
  * templates; create already resolves that package's scripts out of
@@ -406,14 +415,17 @@ export async function doctor() {
406
415
  const job = capture("sh", ["-c", "test -x /etc/cron.daily/kyb-docker-prune && echo yes"])?.trim();
407
416
  const percent = Number(capture("sh", ["-c", "df / | awk 'NR==2{print $5}' | tr -d '%'"])?.trim() ?? 0);
408
417
  if (job !== "yes") {
409
- add("warn", "no daily docker reclaim on this host", "sandbox templates, build cache, and generic stopped containers accumulate; durable sessions stay protected and terminal hooks remove closed sessions; `kyb upgrade` installs /etc/cron.daily/kyb-docker-prune");
410
- }
411
- else if (percent >= 80) {
412
- 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");
418
+ add("warn", "no daily docker reclaim on this host", "sandbox templates, build cache, and generic stopped containers accumulate; `kyb upgrade` installs /etc/cron.daily/kyb-docker-prune");
413
419
  }
414
420
  else {
415
- add("pass", `daily docker reclaim installed (disk ${percent}% used)`);
421
+ add("pass", "daily docker reclaim installed");
416
422
  }
423
+ const dockerSummary = capture("sh", [
424
+ "-c",
425
+ "docker system df 2>/dev/null | head -3 | paste -sd ';' -",
426
+ ]);
427
+ const diskCheck = diskUsageDoctorCheck(percent, dockerSummary);
428
+ add(diskCheck.verdict, diskCheck.label, diskCheck.detail);
417
429
  }
418
430
  /**
419
431
  * Instructions that name a tool the agent does not have.
package/dist/upgrade.js CHANGED
@@ -1,9 +1,11 @@
1
- import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
1
+ import { chmodSync, copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
2
2
  import { spawnSync } from "node:child_process";
3
+ import { tmpdir } from "node:os";
3
4
  import { join } from "node:path";
4
5
  import { pathToFileURL } from "node:url";
5
6
  import { upsertEnv } from "./envfile.js";
6
7
  import { reconcileHostArtifact } from "./host-artifacts.js";
8
+ import { dockerPruneCronArtifact } from "./docker-prune-cron.js";
7
9
  import { findMatchingAgentServiceUnit, repairManageRestart } from "./systemd.js";
8
10
  import { repairTerminalSandboxCleanupHooks } from "./sandbox-cleanup.js";
9
11
  import { repairRemovedDefaultTools as repairRemovedDefaultTools_ } from "./removed-default-tools.js";
@@ -246,7 +248,7 @@ function repairHostArtifacts(cwd, deps) {
246
248
  return;
247
249
  const scripts = join(cwd, "node_modules/@kybernesis/exe/scripts");
248
250
  const pruneSource = join(scripts, "docker-prune.sh");
249
- const pruneTarget = "/etc/cron.daily/kyb-docker-prune";
251
+ const pruneTarget = process.env.KYB_DOCKER_PRUNE_TARGET ?? "/etc/cron.daily/kyb-docker-prune";
250
252
  const hasDocker = capture("sh", ["-c", "command -v docker >/dev/null && echo yes"])?.trim() === "yes";
251
253
  // The host start script is package-owned too. It carried a 45-second
252
254
  // verdict clock that called a cold sandbox template build a failure, and no
@@ -276,18 +278,37 @@ function repairHostArtifacts(cwd, deps) {
276
278
  });
277
279
  }
278
280
  if (existsSync(pruneSource)) {
281
+ let installedPrune = null;
282
+ try {
283
+ if (existsSync(pruneTarget))
284
+ installedPrune = readFileSync(pruneTarget, "utf8");
285
+ }
286
+ catch {
287
+ // reconcileHostArtifact reports the unreadable target and refuses to replace it.
288
+ }
289
+ const pruneArtifact = dockerPruneCronArtifact(readFileSync(pruneSource, "utf8"), cwd, installedPrune, pruneTarget);
279
290
  const pruneResult = reconcileHostArtifact({
280
291
  targetPath: pruneTarget,
281
- desiredContent: readFileSync(pruneSource),
292
+ desiredContent: pruneArtifact.content,
282
293
  expectedMode: 0o755,
283
294
  installIfMissing: hasDocker,
284
- owner: "@kybernesis/exe/scripts/docker-prune.sh",
285
- update: () => run("sudo", ["-n", "install", "-m", "0755", pruneSource, pruneTarget], {
286
- cwd,
287
- allowFail: true,
288
- quiet: true,
289
- }),
290
- manualCommand: `sudo install -m 0755 ${shellQuote(pruneSource)} ${pruneTarget}`,
295
+ owner: "@kybernesis/exe/scripts/docker-prune.sh with registered app directories",
296
+ update: () => {
297
+ const stageDir = mkdtempSync(join(tmpdir(), "kyb-docker-prune-"));
298
+ const staged = join(stageDir, "docker-prune.sh");
299
+ try {
300
+ writeFileSync(staged, pruneArtifact.content, { mode: 0o755 });
301
+ return run("sudo", ["-n", "install", "-m", "0755", staged, pruneTarget], {
302
+ cwd,
303
+ allowFail: true,
304
+ quiet: true,
305
+ });
306
+ }
307
+ finally {
308
+ rmSync(stageDir, { recursive: true, force: true });
309
+ }
310
+ },
311
+ manualCommand: pruneArtifact.manualCommand,
291
312
  });
292
313
  if ((pruneResult === "current" || pruneResult === "updated") && existsSync("/etc/cron.weekly/docker-prune")) {
293
314
  const removed = run("sudo", ["-n", "rm", "-f", "/etc/cron.weekly/docker-prune"], {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kybernesis/create",
3
- "version": "0.14.9",
3
+ "version": "0.14.10",
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",