@kybernesis/create 0.8.0 → 0.8.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
@@ -60,7 +60,50 @@ function initOptions(rest) {
60
60
  yes: rest.includes('--yes') || rest.includes('-y'),
61
61
  };
62
62
  }
63
+ /** What each command is for, in one line, as a person would ask for it. */
64
+ const COMMANDS = {
65
+ init: "Scaffold a new agent: governed, remembering, multiplayer, self-testing.",
66
+ doctor: "Check this machine and this project before an engagement.",
67
+ arcana: "Set the memory workspaces and keys this agent uses.",
68
+ skills: "Install the FDE skill suite (--global for every project).",
69
+ credential: "Write the agent credential onto a host (--local to stay here).",
70
+ register: "Register this agent with the control plane (--name, --url).",
71
+ deploy: "Deploy this agent to its host (--no-env to leave the env file alone).",
72
+ upgrade: "Bring @kybernesis packages and eve to the certified versions (--skip-eval).",
73
+ version: "Print the version of this tool.",
74
+ };
75
+ /**
76
+ * Print help instead of doing anything.
77
+ *
78
+ * Given a command, describe that command; otherwise list them. Either way this
79
+ * function's only effect is output — which is the entire point of it existing.
80
+ */
81
+ function usage(command) {
82
+ if (command && COMMANDS[command]) {
83
+ console.log(`\n ${bold(`kyb ${command}`)} — ${COMMANDS[command]}\n`);
84
+ return;
85
+ }
86
+ console.log(`\n ${bold("kyb")} ${dim(VERSION)} — the Kybernesis agent CLI\n`);
87
+ for (const [name, description] of Object.entries(COMMANDS)) {
88
+ console.log(` ${bold(name.padEnd(12))}${description}`);
89
+ }
90
+ console.log(`\n ${dim("kyb <command> --help for one command.")}\n`);
91
+ }
63
92
  const [, , command, ...rest] = process.argv;
93
+ /**
94
+ * `--help` asks what a command does. It must never be the thing that does it.
95
+ *
96
+ * Checked before dispatch rather than inside each command, because "the flag
97
+ * was ignored" is not a failure anyone verifies per command — and the one time
98
+ * it mattered, `kyb upgrade --help` ran a real upgrade against a live agent's
99
+ * dependencies. Nothing broke, which is the wrong kind of luck: the same
100
+ * mistake on a command that writes credentials or deploys would not have been
101
+ * survivable.
102
+ */
103
+ if (rest.includes("--help") || rest.includes("-h")) {
104
+ usage(command);
105
+ process.exit(0);
106
+ }
64
107
  switch (command) {
65
108
  case "init":
66
109
  await init(rest.find((a) => !a.startsWith("-")), initOptions(rest));
@@ -1,5 +1,6 @@
1
1
  import { execFileSync } from "node:child_process";
2
2
  import { existsSync, readFileSync } from "node:fs";
3
+ import { hostname } from "node:os";
3
4
  import { join } from "node:path";
4
5
  import { upsertEnv } from "./envfile.js";
5
6
  import { signIn } from "./register.js";
@@ -34,6 +35,19 @@ function envOf(dir) {
34
35
  }
35
36
  return out;
36
37
  }
38
+ /**
39
+ * Is this machine the SSH target we were about to dial?
40
+ *
41
+ * Compared on the short name as well as the full one, because a host that
42
+ * answers to `box.example.xyz` usually reports itself as `box` — and the env
43
+ * file names whichever the person happened to type.
44
+ */
45
+ function isThisHost(target) {
46
+ const self = hostname().toLowerCase();
47
+ const short = self.split(".")[0] ?? self;
48
+ const wanted = target.toLowerCase();
49
+ return wanted === self || wanted === short || wanted.split(".")[0] === short;
50
+ }
37
51
  export async function credential(options) {
38
52
  const dir = options.dir ?? process.cwd();
39
53
  const env = envOf(dir);
@@ -85,9 +99,19 @@ export async function credential(options) {
85
99
  // leaves discovery silent, because `kyb deploy` deliberately never overwrites
86
100
  // a host .env.local that already exists.
87
101
  const target = options.host ?? env.EVE_SSH_HOST ?? (env.EXE_VM_NAME ? `${env.EXE_VM_NAME}.exe.xyz` : null);
88
- if (options.local || !target) {
102
+ // Already ON the host it would otherwise SSH to.
103
+ //
104
+ // Naming a host is how a laptop installs the credential where the agent will
105
+ // read it. Run the same command on that host — the natural thing to do while
106
+ // setting one up over SSH — and it dialled itself, needed a key the host does
107
+ // not hold for itself, and failed at the last step of an otherwise complete
108
+ // sign-in. The file it wanted to write was already under the cursor.
109
+ const onTargetHost = target !== null && isThisHost(target);
110
+ if (options.local || !target || onTargetHost) {
89
111
  upsertEnv(dir, { KYBERNESIS_AGENT_CREDENTIAL: value });
90
112
  console.log(green("\n ✓ credential written to ./.env.local"));
113
+ if (onTargetHost)
114
+ console.log(dim(` This IS ${target}, so there was nothing to copy.`));
91
115
  if (!target)
92
116
  console.log(dim(" No host known — deploy it, or re-run with --host=<ssh target>."));
93
117
  return;
package/dist/upgrade.js CHANGED
@@ -1,14 +1,25 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { EVE_VERSION, bold, capture, dim, green, red, run, yellow } from "./util.js";
4
- const PACKAGES = [
5
- "@kybernesis/arcana",
6
- "@kybernesis/enterprise",
7
- "@kybernesis/multiplayer",
8
- "@kybernesis/engineer",
9
- "@kybernesis/dispatch",
10
- "@kybernesis/evals",
11
- ];
4
+ /**
5
+ * Which packages to upgrade: every `@kybernesis/*` this agent depends on.
6
+ *
7
+ * @remarks
8
+ * This was a fixed list of six, written when there were six. Four more shipped
9
+ * afterwards — connectors, local, manage and exe — and an agent using them was
10
+ * told "everything is at latest certified versions" while holding versions from
11
+ * months earlier. A hardcoded list does not fail loudly when it falls behind;
12
+ * it just quietly stops covering things, and the command that reports it is the
13
+ * same one that is wrong.
14
+ *
15
+ * Reading the manifest cannot fall behind. A package added tomorrow is covered
16
+ * by an upgrade run today.
17
+ */
18
+ function kybernesisPackages(deps) {
19
+ return Object.keys(deps)
20
+ .filter((name) => name.startsWith("@kybernesis/"))
21
+ .sort();
22
+ }
12
23
  function versionLt(a, b) {
13
24
  const pa = a.split(".").map(Number);
14
25
  const pb = b.split(".").map(Number);
@@ -20,19 +31,47 @@ function versionLt(a, b) {
20
31
  }
21
32
  return false;
22
33
  }
34
+ /**
35
+ * Warn when this CLI is itself out of date.
36
+ *
37
+ * @remarks
38
+ * The certified eve version is a constant compiled INTO this tool, so an old
39
+ * kyb reports an old pin as though it were current — and does it with total
40
+ * confidence, in the one command whose entire job is telling you what current
41
+ * means. That failure runs the wrong way round: it tells a healthy agent it is
42
+ * ahead of certified and in "unsupported territory", which invites someone to
43
+ * downgrade a fleet that was fine.
44
+ *
45
+ * Checked here rather than at install because this is the command where being
46
+ * stale changes the answer.
47
+ */
48
+ function warnIfStale() {
49
+ const installed = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
50
+ const latest = capture("npm", ["view", "@kybernesis/create", "version"])?.trim();
51
+ if (!latest || latest === installed)
52
+ return;
53
+ if (!versionLt(installed, latest))
54
+ return;
55
+ console.log(` ${yellow("!")} kyb ${installed} is behind ${latest}. The certified eve version is ` +
56
+ `compiled into this tool, so an old kyb reports an old pin as current.`);
57
+ console.log(` ${dim("npm install -g @kybernesis/create@latest")}\n`);
58
+ }
23
59
  export async function upgrade(skipEval) {
24
60
  const cwd = process.cwd();
25
61
  const pkg = JSON.parse(readFileSync(join(cwd, "package.json"), "utf8"));
26
62
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
27
63
  console.log(bold("\nkyb upgrade — checking @kybernesis/* and eve against npm\n"));
64
+ warnIfStale();
28
65
  const toUpgrade = [];
29
- for (const name of PACKAGES) {
66
+ const unresolved = [];
67
+ for (const name of kybernesisPackages(deps)) {
30
68
  if (!deps[name])
31
69
  continue;
32
70
  const installed = capture("node", ["-p", `require('${name}/package.json').version`], cwd)?.trim();
33
71
  const latest = capture("npm", ["view", name, "version"])?.trim();
34
72
  if (!installed || !latest) {
35
73
  console.log(` ${yellow("!")} ${name}: could not resolve versions`);
74
+ unresolved.push(name);
36
75
  continue;
37
76
  }
38
77
  if (installed === latest)
@@ -65,6 +104,16 @@ export async function upgrade(skipEval) {
65
104
  console.log(dim(` note: eve@${eveLatest} exists upstream; ${EVE_VERSION} is the newest Kybernesis-certified version.`));
66
105
  }
67
106
  }
107
+ if (toUpgrade.length === 0 && unresolved.length > 0) {
108
+ // Saying everything is current, having just failed to check several
109
+ // packages, is the worst available answer: it is the sentence someone
110
+ // repeats to a client. Usually the dependencies are simply not installed
111
+ // here, which is worth naming rather than hiding behind a green tick.
112
+ console.log(`\n${yellow(`Checked what could be read. ${unresolved.length} package(s) could not be ` +
113
+ `resolved, so this is not a clean bill of health.`)}\n` +
114
+ ` ${dim("Usually: dependencies are not installed here. Run npm install, then kyb upgrade.")}\n`);
115
+ return;
116
+ }
68
117
  if (toUpgrade.length === 0) {
69
118
  console.log(`\n${green("Everything is at latest certified versions.")}\n`);
70
119
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kybernesis/create",
3
- "version": "0.8.0",
3
+ "version": "0.8.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",
@@ -31,7 +31,7 @@
31
31
  },
32
32
  "scripts": {
33
33
  "build": "tsc -p tsconfig.build.json",
34
- "prepare": "tsc -p tsconfig.build.json",
34
+ "prepack": "tsc -p tsconfig.build.json",
35
35
  "typecheck": "tsc --noEmit",
36
36
  "prepublishOnly": "node ../../scripts/prepublish.mjs"
37
37
  },
@@ -82,9 +82,10 @@ then `eve add @kybernesis/<item>`). Each covers one axis:
82
82
  same shape as eve's `experimental_chatgpt()`), `hostPreflight()`, Photon
83
83
  iMessage credentials, and a `/preview` tool. Subpaths: `/slack`, `/photon`,
84
84
  `/sandbox`, `/preview`. See the `self-hosting` skill.
85
- - **evals** — QA. `kybernesisBaseline({ agentDisplayName, routing,
86
- engineer? })` = smoke + 5 memory + routing per dept + optional vision-loop
87
- eval. Judge model model under test. Hermetic runs force all workspaces to
85
+ - **evals** — QA. `kybernesisBaseline({ agentDisplayName, routing, engineer?,
86
+ safety? })` = smoke + 5 memory + 1 safety (quoted content is data, on by
87
+ default) + routing per dept + optional engineer pair (vision loop, push-to-main
88
+ refusal). Judge model ≠ model under test. Hermetic runs force all workspaces to
88
89
  `<name>-eval` via the npm script.
89
90
  - **create** — the `kyb` CLI, and the whole lifecycle of an agent:
90
91
  `init` (scaffold; `--host=exe` also installs the hardened restart script),