@kybernesis/create 0.15.6 → 0.15.7
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/doctor.js +35 -0
- package/dist/on-line-version.d.ts +51 -0
- package/dist/on-line-version.js +87 -0
- package/dist/stale-sidecar.d.ts +51 -0
- package/dist/stale-sidecar.js +56 -0
- package/dist/upgrade.js +53 -3
- package/package.json +1 -1
package/dist/doctor.js
CHANGED
|
@@ -7,6 +7,7 @@ import { diagnoseManageRestart, findMatchingAgentServiceUnit } from "./systemd.j
|
|
|
7
7
|
import { inspectEveAgent } from "./agent-limits.js";
|
|
8
8
|
import { classifyModelReach } from "./model-reach.js";
|
|
9
9
|
import { countAbandonedRuns } from "./run-store.js";
|
|
10
|
+
import { describeStaleSidecar, newestPackageChange, staleSidecars } from "./stale-sidecar.js";
|
|
10
11
|
import { checkSelfVersion } from "./self-version.js";
|
|
11
12
|
const MARK = {
|
|
12
13
|
pass: green("✓"),
|
|
@@ -117,6 +118,17 @@ async function head(url, headers) {
|
|
|
117
118
|
return null;
|
|
118
119
|
}
|
|
119
120
|
}
|
|
121
|
+
/** Running units that belong to a Kybernesis agent on this host. */
|
|
122
|
+
function listAgentUnits() {
|
|
123
|
+
const out = capture("systemctl", ["list-units", "--type=service", "--state=running", "--no-legend", "--plain"]);
|
|
124
|
+
if (!out)
|
|
125
|
+
return [];
|
|
126
|
+
return out
|
|
127
|
+
.split("\n")
|
|
128
|
+
.map((line) => line.trim().split(/\s+/)[0] ?? "")
|
|
129
|
+
.filter((unit) => /-(agent|buzz-bridge)\.service$/.test(unit))
|
|
130
|
+
.map((unit) => unit.replace(/\.service$/, ""));
|
|
131
|
+
}
|
|
120
132
|
export async function doctor() {
|
|
121
133
|
const cwd = process.cwd();
|
|
122
134
|
const installedAgentService = findMatchingAgentServiceUnit(cwd);
|
|
@@ -132,6 +144,29 @@ export async function doctor() {
|
|
|
132
144
|
* them. Reported rather than fixed: the remedy restarts the agent, which is
|
|
133
145
|
* not something a diagnostic should do behind someone's back.
|
|
134
146
|
*/
|
|
147
|
+
/**
|
|
148
|
+
* Services still running the code they loaded at start.
|
|
149
|
+
*
|
|
150
|
+
* An agent host has more than one: deploy.sh restarts the agent, and the Buzz
|
|
151
|
+
* bridge is a separate unit it never touches. Both bridges on the reference
|
|
152
|
+
* fleet ran four days on a superseded package while the fix sat on disk, so
|
|
153
|
+
* two changes signed off as deployed were live nowhere. Nothing looked
|
|
154
|
+
* broken — the old code simply kept doing what it did.
|
|
155
|
+
*/
|
|
156
|
+
const packageChange = newestPackageChange(cwd);
|
|
157
|
+
const started = new Map();
|
|
158
|
+
for (const unit of listAgentUnits()) {
|
|
159
|
+
const stamp = capture("systemctl", ["show", "-p", "ActiveEnterTimestamp", "--value", unit])?.trim();
|
|
160
|
+
const at = stamp ? new Date(stamp) : null;
|
|
161
|
+
if (at && !Number.isNaN(at.getTime()))
|
|
162
|
+
started.set(unit, at);
|
|
163
|
+
}
|
|
164
|
+
for (const stale of staleSidecars({ started, packageChange })) {
|
|
165
|
+
add("fail", `${stale.service} is running superseded code`, describeStaleSidecar(stale));
|
|
166
|
+
}
|
|
167
|
+
if (started.size > 0 && staleSidecars({ started, packageChange }).length === 0) {
|
|
168
|
+
add("pass", `every service is running the code currently installed (${started.size} checked)`);
|
|
169
|
+
}
|
|
135
170
|
const runs = countAbandonedRuns(cwd);
|
|
136
171
|
if (runs) {
|
|
137
172
|
if (runs.abandoned === 0) {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Picking the right build of a per-eve-line package.
|
|
3
|
+
*
|
|
4
|
+
* `@kybernesis/*` packages ship one build per eve line, and the version numbers
|
|
5
|
+
* are NOT ordered by line: `@kybernesis/voice` 0.1.2 is the eve 0.49 build and
|
|
6
|
+
* was published AFTER 0.1.1, which is the 0.51 build. So `npm view <pkg>
|
|
7
|
+
* version` — npm's `latest` — is routinely the wrong package for a given host,
|
|
8
|
+
* and installing it puts an agent on a build compiled against an eve API it is
|
|
9
|
+
* not running.
|
|
10
|
+
*
|
|
11
|
+
* That failure is silent. The package installs, the agent builds, the service
|
|
12
|
+
* starts, and one code path breaks later at runtime. It is how
|
|
13
|
+
* `@kybernesis/notify` took an agent down and how two agents ran a 0.51 build of
|
|
14
|
+
* `@kybernesis/manage` on eve 0.49 for days without anyone noticing.
|
|
15
|
+
*
|
|
16
|
+
* So an upgrade must never ask for "latest". It must ask for the newest version
|
|
17
|
+
* whose declared eve peer range contains the eve this host actually runs.
|
|
18
|
+
*/
|
|
19
|
+
/** A published version and the eve range it declares. */
|
|
20
|
+
export interface Candidate {
|
|
21
|
+
version: string;
|
|
22
|
+
/** `peerDependencies.eve`, or undefined when the package declares none. */
|
|
23
|
+
peer?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Compare semver-ish versions numerically.
|
|
27
|
+
*
|
|
28
|
+
* Numerically, because these are picked by sorting and string order puts 0.8.9
|
|
29
|
+
* above 0.8.10 — which would hand a host the wrong build. A prerelease sorts
|
|
30
|
+
* BELOW its release (0.1.2-rc < 0.1.2), so a release candidate is never chosen
|
|
31
|
+
* over the release it precedes.
|
|
32
|
+
*/
|
|
33
|
+
export declare function versionCompare(a: string, b: string): number;
|
|
34
|
+
/**
|
|
35
|
+
* Whether an eve version falls inside a declared peer range.
|
|
36
|
+
*
|
|
37
|
+
* Deliberately narrow: it understands the two forms these packages actually
|
|
38
|
+
* publish — `>=X <Y` and a caret — and returns false for anything it does not
|
|
39
|
+
* recognise. Guessing at a range it cannot parse is how a wrong build gets
|
|
40
|
+
* installed, and "I do not know" must not read as "yes".
|
|
41
|
+
*/
|
|
42
|
+
export declare function rangeContains(range: string | undefined, eve: string): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* The newest published build that supports this host's eve.
|
|
45
|
+
*
|
|
46
|
+
* Returns undefined when no published version declares support — which is a
|
|
47
|
+
* real answer and must be surfaced, not smoothed over by falling back to
|
|
48
|
+
* latest. `@kybernesis/voice` had NO eve 0.49 build for weeks while two agents
|
|
49
|
+
* on 0.49 ran its 0.51 one.
|
|
50
|
+
*/
|
|
51
|
+
export declare function newestOnLine(candidates: readonly Candidate[], eve: string): string | undefined;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Picking the right build of a per-eve-line package.
|
|
3
|
+
*
|
|
4
|
+
* `@kybernesis/*` packages ship one build per eve line, and the version numbers
|
|
5
|
+
* are NOT ordered by line: `@kybernesis/voice` 0.1.2 is the eve 0.49 build and
|
|
6
|
+
* was published AFTER 0.1.1, which is the 0.51 build. So `npm view <pkg>
|
|
7
|
+
* version` — npm's `latest` — is routinely the wrong package for a given host,
|
|
8
|
+
* and installing it puts an agent on a build compiled against an eve API it is
|
|
9
|
+
* not running.
|
|
10
|
+
*
|
|
11
|
+
* That failure is silent. The package installs, the agent builds, the service
|
|
12
|
+
* starts, and one code path breaks later at runtime. It is how
|
|
13
|
+
* `@kybernesis/notify` took an agent down and how two agents ran a 0.51 build of
|
|
14
|
+
* `@kybernesis/manage` on eve 0.49 for days without anyone noticing.
|
|
15
|
+
*
|
|
16
|
+
* So an upgrade must never ask for "latest". It must ask for the newest version
|
|
17
|
+
* whose declared eve peer range contains the eve this host actually runs.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Compare semver-ish versions numerically.
|
|
21
|
+
*
|
|
22
|
+
* Numerically, because these are picked by sorting and string order puts 0.8.9
|
|
23
|
+
* above 0.8.10 — which would hand a host the wrong build. A prerelease sorts
|
|
24
|
+
* BELOW its release (0.1.2-rc < 0.1.2), so a release candidate is never chosen
|
|
25
|
+
* over the release it precedes.
|
|
26
|
+
*/
|
|
27
|
+
export function versionCompare(a, b) {
|
|
28
|
+
const split = (v) => {
|
|
29
|
+
const dash = v.indexOf("-");
|
|
30
|
+
const core = (dash === -1 ? v : v.slice(0, dash)).split(".").map((p) => Number(p) || 0);
|
|
31
|
+
return { core, pre: dash !== -1 };
|
|
32
|
+
};
|
|
33
|
+
const pa = split(a);
|
|
34
|
+
const pb = split(b);
|
|
35
|
+
for (let i = 0; i < Math.max(pa.core.length, pb.core.length); i += 1) {
|
|
36
|
+
const x = pa.core[i] ?? 0;
|
|
37
|
+
const y = pb.core[i] ?? 0;
|
|
38
|
+
if (x !== y)
|
|
39
|
+
return x < y ? -1 : 1;
|
|
40
|
+
}
|
|
41
|
+
if (pa.pre === pb.pre)
|
|
42
|
+
return 0;
|
|
43
|
+
return pa.pre ? -1 : 1;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Whether an eve version falls inside a declared peer range.
|
|
47
|
+
*
|
|
48
|
+
* Deliberately narrow: it understands the two forms these packages actually
|
|
49
|
+
* publish — `>=X <Y` and a caret — and returns false for anything it does not
|
|
50
|
+
* recognise. Guessing at a range it cannot parse is how a wrong build gets
|
|
51
|
+
* installed, and "I do not know" must not read as "yes".
|
|
52
|
+
*/
|
|
53
|
+
export function rangeContains(range, eve) {
|
|
54
|
+
if (!range)
|
|
55
|
+
return false;
|
|
56
|
+
const bounded = /^>=\s*([0-9][^\s]*)\s*<\s*([0-9][^\s]*)$/.exec(range.trim());
|
|
57
|
+
if (bounded) {
|
|
58
|
+
return versionCompare(eve, bounded[1]) >= 0 && versionCompare(eve, bounded[2]) < 0;
|
|
59
|
+
}
|
|
60
|
+
const caret = /^\^([0-9][^\s]*)$/.exec(range.trim());
|
|
61
|
+
if (caret) {
|
|
62
|
+
const base = caret[1].split(".");
|
|
63
|
+
const major = Number(base[0] ?? 0);
|
|
64
|
+
const minor = Number(base[1] ?? 0);
|
|
65
|
+
// ^0.x is minor-locked, as npm treats it.
|
|
66
|
+
const upper = major === 0 ? `0.${minor + 1}.0` : `${major + 1}.0.0`;
|
|
67
|
+
return versionCompare(eve, caret[1]) >= 0 && versionCompare(eve, upper) < 0;
|
|
68
|
+
}
|
|
69
|
+
const atLeast = /^>=\s*([0-9][^\s]*)$/.exec(range.trim());
|
|
70
|
+
if (atLeast)
|
|
71
|
+
return versionCompare(eve, atLeast[1]) >= 0;
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* The newest published build that supports this host's eve.
|
|
76
|
+
*
|
|
77
|
+
* Returns undefined when no published version declares support — which is a
|
|
78
|
+
* real answer and must be surfaced, not smoothed over by falling back to
|
|
79
|
+
* latest. `@kybernesis/voice` had NO eve 0.49 build for weeks while two agents
|
|
80
|
+
* on 0.49 ran its 0.51 one.
|
|
81
|
+
*/
|
|
82
|
+
export function newestOnLine(candidates, eve) {
|
|
83
|
+
const supported = candidates.filter((c) => rangeContains(c.peer, eve));
|
|
84
|
+
if (supported.length === 0)
|
|
85
|
+
return undefined;
|
|
86
|
+
return supported.sort((a, b) => versionCompare(a.version, b.version)).at(-1)?.version;
|
|
87
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Services that are still running the code they loaded at start, after the
|
|
3
|
+
* package on disk has moved underneath them.
|
|
4
|
+
*
|
|
5
|
+
* Node reads a module once, at process start. Upgrading a package therefore
|
|
6
|
+
* changes nothing in a service that was already running — it keeps executing
|
|
7
|
+
* the old code from memory while the new version sits on disk, indefinitely.
|
|
8
|
+
*
|
|
9
|
+
* An agent host has more than one service. `deploy.sh` restarts the agent, and
|
|
10
|
+
* the Buzz bridge is a separate long-lived unit it does not touch. On the
|
|
11
|
+
* reference fleet both bridges ran for four days on `@kybernesis/buzz` 0.9.2
|
|
12
|
+
* while 0.9.9 was installed, so two fixes that had been signed off as deployed
|
|
13
|
+
* were live nowhere. Nothing was broken enough to notice; the bridges simply
|
|
14
|
+
* kept doing what the old code did.
|
|
15
|
+
*
|
|
16
|
+
* Checking the FILES is what hid it. The only honest question is whether the
|
|
17
|
+
* process started after the package changed.
|
|
18
|
+
*/
|
|
19
|
+
export interface StaleSidecar {
|
|
20
|
+
service: string;
|
|
21
|
+
/** When the unit last started. */
|
|
22
|
+
startedAt: Date;
|
|
23
|
+
/** The newest package change it has not picked up. */
|
|
24
|
+
packageChangedAt: Date;
|
|
25
|
+
/** The package whose change is newest, for the message. */
|
|
26
|
+
packageName: string;
|
|
27
|
+
}
|
|
28
|
+
/** The newest mtime across installed @kybernesis packages, and which one it was. */
|
|
29
|
+
export declare function newestPackageChange(cwd: string): {
|
|
30
|
+
at: Date;
|
|
31
|
+
name: string;
|
|
32
|
+
} | null;
|
|
33
|
+
/**
|
|
34
|
+
* Compare each running unit's start time against the newest package change.
|
|
35
|
+
*
|
|
36
|
+
* `started` maps a unit name to when it started — supplied by the caller so
|
|
37
|
+
* this stays testable and does not shell out.
|
|
38
|
+
*
|
|
39
|
+
* A grace margin avoids flagging the restart that an install performs itself:
|
|
40
|
+
* a service restarted within a minute of the package landing has the new code.
|
|
41
|
+
*/
|
|
42
|
+
export declare function staleSidecars(input: {
|
|
43
|
+
started: ReadonlyMap<string, Date>;
|
|
44
|
+
packageChange: {
|
|
45
|
+
at: Date;
|
|
46
|
+
name: string;
|
|
47
|
+
} | null;
|
|
48
|
+
graceMs?: number;
|
|
49
|
+
}): StaleSidecar[];
|
|
50
|
+
/** One line per stale service, naming the fix. */
|
|
51
|
+
export declare function describeStaleSidecar(stale: StaleSidecar): string;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { readdirSync, statSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
/** The newest mtime across installed @kybernesis packages, and which one it was. */
|
|
4
|
+
export function newestPackageChange(cwd) {
|
|
5
|
+
const scope = join(cwd, "node_modules", "@kybernesis");
|
|
6
|
+
let newest = null;
|
|
7
|
+
let entries;
|
|
8
|
+
try {
|
|
9
|
+
entries = readdirSync(scope);
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
for (const name of entries) {
|
|
15
|
+
try {
|
|
16
|
+
// The manifest is rewritten on every install of that package, so its
|
|
17
|
+
// mtime is when the package last changed on this host.
|
|
18
|
+
const at = statSync(join(scope, name, "package.json")).mtime;
|
|
19
|
+
if (!newest || at > newest.at)
|
|
20
|
+
newest = { at, name: `@kybernesis/${name}` };
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
// A half-removed package tells us nothing; skip it.
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return newest;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Compare each running unit's start time against the newest package change.
|
|
30
|
+
*
|
|
31
|
+
* `started` maps a unit name to when it started — supplied by the caller so
|
|
32
|
+
* this stays testable and does not shell out.
|
|
33
|
+
*
|
|
34
|
+
* A grace margin avoids flagging the restart that an install performs itself:
|
|
35
|
+
* a service restarted within a minute of the package landing has the new code.
|
|
36
|
+
*/
|
|
37
|
+
export function staleSidecars(input) {
|
|
38
|
+
const change = input.packageChange;
|
|
39
|
+
if (!change)
|
|
40
|
+
return [];
|
|
41
|
+
const grace = input.graceMs ?? 60_000;
|
|
42
|
+
const out = [];
|
|
43
|
+
for (const [service, startedAt] of input.started) {
|
|
44
|
+
if (startedAt.getTime() >= change.at.getTime() - grace)
|
|
45
|
+
continue;
|
|
46
|
+
out.push({ service, startedAt, packageChangedAt: change.at, packageName: change.name });
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
/** One line per stale service, naming the fix. */
|
|
51
|
+
export function describeStaleSidecar(stale) {
|
|
52
|
+
const started = stale.startedAt.toISOString().slice(0, 16).replace("T", " ");
|
|
53
|
+
const changed = stale.packageChangedAt.toISOString().slice(0, 16).replace("T", " ");
|
|
54
|
+
return (`${stale.service} started ${started} but ${stale.packageName} changed ${changed} — ` +
|
|
55
|
+
`it is still running the code it loaded at start. Restart it: sudo systemctl restart ${stale.service}`);
|
|
56
|
+
}
|
package/dist/upgrade.js
CHANGED
|
@@ -7,6 +7,7 @@ import { upsertEnv } from "./envfile.js";
|
|
|
7
7
|
import { reconcileHostArtifact } from "./host-artifacts.js";
|
|
8
8
|
import { assessPeerTree, staleRangeWarning } from "./peer-tree.js";
|
|
9
9
|
import { dockerPruneCronArtifact } from "./docker-prune-cron.js";
|
|
10
|
+
import { newestOnLine } from "./on-line-version.js";
|
|
10
11
|
import { findMatchingAgentServiceUnit, repairManageRestart } from "./systemd.js";
|
|
11
12
|
import { repairTerminalSandboxCleanupHooks } from "./sandbox-cleanup.js";
|
|
12
13
|
import { repairRemovedDefaultTools as repairRemovedDefaultTools_ } from "./removed-default-tools.js";
|
|
@@ -753,6 +754,44 @@ function installedPackageValue(cwd, name, expression) {
|
|
|
753
754
|
function targetEvePeer(name, version) {
|
|
754
755
|
return capture("npm", ["view", `${name}@${version}`, "peerDependencies.eve"])?.trim() || undefined;
|
|
755
756
|
}
|
|
757
|
+
/**
|
|
758
|
+
* The newest published build of a @kybernesis package that supports a given eve.
|
|
759
|
+
*
|
|
760
|
+
* NOT `npm view <name> version`. These packages ship one build per eve line and
|
|
761
|
+
* the versions are not ordered by line — voice 0.1.2 is the eve 0.49 build and
|
|
762
|
+
* was published after 0.1.1, the 0.51 build — so npm's `latest` is routinely the
|
|
763
|
+
* wrong package. Installing it puts the agent on a build compiled against an eve
|
|
764
|
+
* API it is not running, which fails at neither install nor build, only later
|
|
765
|
+
* and only on one path. That is how an 0.51 build of @kybernesis/manage ran on
|
|
766
|
+
* two eve 0.49 agents for days, and how @kybernesis/notify took one down.
|
|
767
|
+
*
|
|
768
|
+
* Returns undefined when nothing published supports this eve — a real answer,
|
|
769
|
+
* and one the caller must report rather than paper over.
|
|
770
|
+
*/
|
|
771
|
+
function newestBuildForEve(name, eve) {
|
|
772
|
+
const raw = capture("npm", ["view", name, "versions", "--json"]);
|
|
773
|
+
if (!raw)
|
|
774
|
+
return undefined;
|
|
775
|
+
let versions;
|
|
776
|
+
try {
|
|
777
|
+
const parsed = JSON.parse(raw);
|
|
778
|
+
versions = Array.isArray(parsed) ? parsed : [String(parsed)];
|
|
779
|
+
}
|
|
780
|
+
catch {
|
|
781
|
+
return undefined;
|
|
782
|
+
}
|
|
783
|
+
// Newest first, so the usual case stops after one lookup.
|
|
784
|
+
const ordered = [...versions].reverse();
|
|
785
|
+
const candidates = [];
|
|
786
|
+
for (const version of ordered) {
|
|
787
|
+
const peer = targetEvePeer(name, version);
|
|
788
|
+
candidates.push({ version, ...(peer ? { peer } : {}) });
|
|
789
|
+
const hit = newestOnLine(candidates, eve);
|
|
790
|
+
if (hit)
|
|
791
|
+
return hit;
|
|
792
|
+
}
|
|
793
|
+
return undefined;
|
|
794
|
+
}
|
|
756
795
|
function dependencyEntries(pkg) {
|
|
757
796
|
const entries = [];
|
|
758
797
|
for (const section of ["dependencies", "devDependencies"]) {
|
|
@@ -879,9 +918,20 @@ export async function upgrade(options = {}) {
|
|
|
879
918
|
continue;
|
|
880
919
|
}
|
|
881
920
|
installedVersions.set(name, installed);
|
|
882
|
-
|
|
921
|
+
// eve goes to the certified pin. Everything else goes to the newest build
|
|
922
|
+
// that supports the eve this host will be running — NOT npm's `latest`,
|
|
923
|
+
// which for a per-eve-line package is routinely a build for a different
|
|
924
|
+
// line and installs without complaint.
|
|
925
|
+
const target = name === "eve"
|
|
926
|
+
? EVE_VERSION
|
|
927
|
+
: name.startsWith("@kybernesis/")
|
|
928
|
+
? newestBuildForEve(name, EVE_VERSION)
|
|
929
|
+
: capture("npm", ["view", name, "version"])?.trim();
|
|
883
930
|
if (!target) {
|
|
884
|
-
console.log(` ${yellow("!")} ${name}:
|
|
931
|
+
console.log(` ${yellow("!")} ${name}: ` +
|
|
932
|
+
(name.startsWith("@kybernesis/")
|
|
933
|
+
? `no published build declares support for eve ${EVE_VERSION} — leaving it alone`
|
|
934
|
+
: "could not resolve target version"));
|
|
885
935
|
unresolved.add(name);
|
|
886
936
|
continue;
|
|
887
937
|
}
|
|
@@ -891,7 +941,7 @@ export async function upgrade(options = {}) {
|
|
|
891
941
|
targetPeers.set(name, targetEvePeer(name, target));
|
|
892
942
|
}
|
|
893
943
|
if (installed === target) {
|
|
894
|
-
console.log(` ${green("✓")} ${name}@${installed} ${dim(name === "eve" ? "(certified)" : "(latest)")}`);
|
|
944
|
+
console.log(` ${green("✓")} ${name}@${installed} ${dim(name === "eve" ? "(certified)" : name.startsWith("@kybernesis/") ? `(newest for eve ${EVE_VERSION})` : "(latest)")}`);
|
|
895
945
|
}
|
|
896
946
|
else if (name === "eve" && versionLt(EVE_VERSION, installed)) {
|
|
897
947
|
console.log(` ${yellow("!")} eve@${installed} is AHEAD of the certified ${EVE_VERSION} ${dim("— unsupported territory")}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kybernesis/create",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.7",
|
|
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",
|