@kybernesis/create 0.7.13 → 0.8.0

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
@@ -23,6 +23,7 @@ import { installSkills } from "./skills.js";
23
23
  import { deploy } from "./deploy.js";
24
24
  import { register } from "./register.js";
25
25
  import { agentName, configureArcana } from "./arcana.js";
26
+ import { credential } from "./credential.js";
26
27
  /** This build's version, so a skew can name itself instead of being guessed at. */
27
28
  const VERSION = (() => {
28
29
  try {
@@ -79,6 +80,9 @@ switch (command) {
79
80
  suggest: flag(rest, "name") ?? agentName(process.cwd(), basename(process.cwd())),
80
81
  });
81
82
  break;
83
+ case "credential":
84
+ await credential({ name: flag(rest, "name"), host: flag(rest, "host"), local: rest.includes("--local") });
85
+ break;
82
86
  case "register":
83
87
  await register({ name: flag(rest, "name"), url: flag(rest, "url") });
84
88
  break;
@@ -137,6 +141,8 @@ ${dim(" npm i -g @kybernesis/create@latest")}
137
141
  ${bold("kyb skills")} install/refresh the FDE skill suite for Claude Code
138
142
  --global ${dim("install to ~/.claude/skills instead of this repo")}
139
143
  ${bold("kyb arcana")} set memory workspaces + keys, and verify each pair
144
+ ${bold("kyb credential")} mint this agent's control-plane credential and install it
145
+ --local ${dim("write ./.env.local instead of the host")}
140
146
  ${bold("kyb register")} register this agent with the control plane
141
147
  --name=<name> ${dim("defaults to KYBERNESIS_AGENT in .env.local")}
142
148
  --url=<url> ${dim("defaults to https://$EXE_VM_NAME.exe.xyz")}
@@ -0,0 +1,6 @@
1
+ export declare function credential(options: {
2
+ dir?: string;
3
+ name?: string;
4
+ host?: string;
5
+ local?: boolean;
6
+ }): Promise<void>;
@@ -0,0 +1,125 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { upsertEnv } from "./envfile.js";
5
+ import { signIn } from "./register.js";
6
+ import { bold, dim, green, red, yellow } from "./util.js";
7
+ /**
8
+ * `kyb credential` — mint this agent's control-plane credential and install it
9
+ * on its host.
10
+ *
11
+ * The credential is what an agent uses to prove it is itself: it is required to
12
+ * discover granted peers and to mint the short-lived tokens that reach them.
13
+ * Without one, `governedPeers()` finds nothing and says nothing — no tools, no
14
+ * error, an agent that simply never mentions the colleague you granted it.
15
+ *
16
+ * Until now the only way to install one was KYBER Studio's "Work on this
17
+ * computer" toggle. That is a good path and stays the default, but it is a
18
+ * desktop app doing something the CLI can do: sign in as the owner, mint, and
19
+ * write it where the agent reads it. Making a headless setup depend on a GUI
20
+ * toggle is the kind of gap that turns a ten-minute deployment into an evening.
21
+ *
22
+ * The value is never printed and never passed through a shell argument — it
23
+ * goes to the host over stdin and lands in a 0600 file.
24
+ */
25
+ function envOf(dir) {
26
+ const out = {};
27
+ const p = join(dir, ".env.local");
28
+ if (!existsSync(p))
29
+ return out;
30
+ for (const line of readFileSync(p, "utf8").split("\n")) {
31
+ const m = /^\s*([A-Z0-9_]+)\s*=\s*(.*)$/.exec(line);
32
+ if (m?.[1] && out[m[1]] === undefined)
33
+ out[m[1]] = (m[2] ?? "").trim().replace(/^["']|["']$/g, "");
34
+ }
35
+ return out;
36
+ }
37
+ export async function credential(options) {
38
+ const dir = options.dir ?? process.cwd();
39
+ const env = envOf(dir);
40
+ const issuer = (env.KYBERNESIS_ISSUER || "https://agent.kybernesis.ai").replace(/\/$/, "");
41
+ const agent = options.name ?? env.KYBERNESIS_AGENT;
42
+ console.log(bold("kyb credential"));
43
+ if (!agent) {
44
+ console.log(red(" No agent name. Set KYBERNESIS_AGENT in .env.local, or pass --name=<name>."));
45
+ process.exitCode = 1;
46
+ return;
47
+ }
48
+ console.log(dim(` agent: ${agent} issuer: ${issuer}`));
49
+ const token = await signIn(issuer);
50
+ if (!token) {
51
+ process.exitCode = 1;
52
+ return;
53
+ }
54
+ const res = await fetch(`${issuer}/api/agents/credential`, {
55
+ method: "POST",
56
+ headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
57
+ body: JSON.stringify({ agent }),
58
+ }).catch(() => null);
59
+ if (!res?.ok) {
60
+ const detail = ((await res?.json().catch(() => ({}))) ?? {});
61
+ console.log(red(`\n Could not mint a credential (${res?.status ?? "no response"}).`));
62
+ if (detail.error === 'unknown agent') {
63
+ console.log(dim(` No agent named "${agent}" in your org — register it first with \`kyb register\`.`));
64
+ }
65
+ else if (res?.status === 403) {
66
+ // Deliberately not the same as "may talk to it": holding the credential
67
+ // means BEING the agent, so only an owner or manage grant qualifies.
68
+ console.log(dim(" You need to own this agent, or hold a manage grant on it."));
69
+ }
70
+ else if (detail.error) {
71
+ console.log(dim(` ${detail.error}`));
72
+ }
73
+ process.exitCode = 1;
74
+ return;
75
+ }
76
+ const minted = (await res.json());
77
+ const value = minted.credential ?? minted.token;
78
+ if (!value) {
79
+ console.log(red(" The control plane returned no credential."));
80
+ process.exitCode = 1;
81
+ return;
82
+ }
83
+ // Where the agent will read it from. A deployed agent reads the HOST's copy;
84
+ // writing only the laptop's is the mistake that makes this look done and
85
+ // leaves discovery silent, because `kyb deploy` deliberately never overwrites
86
+ // a host .env.local that already exists.
87
+ const target = options.host ?? env.EVE_SSH_HOST ?? (env.EXE_VM_NAME ? `${env.EXE_VM_NAME}.exe.xyz` : null);
88
+ if (options.local || !target) {
89
+ upsertEnv(dir, { KYBERNESIS_AGENT_CREDENTIAL: value });
90
+ console.log(green("\n ✓ credential written to ./.env.local"));
91
+ if (!target)
92
+ console.log(dim(" No host known — deploy it, or re-run with --host=<ssh target>."));
93
+ return;
94
+ }
95
+ const name = JSON.parse(readFileSync(join(dir, "package.json"), "utf8")).name ?? "agent";
96
+ const remote = `~/${name}/.env.local`;
97
+ // stdin, never argv: a credential in a command line is visible in the host's
98
+ // process list and lands in shell history on both machines.
99
+ const script = `set -e; f="${remote}"; f="\${f/#\\~/$HOME}"; v=$(cat); touch "$f"; chmod 600 "$f"; ` +
100
+ `tmp=$(mktemp); grep -v '^KYBERNESIS_AGENT_CREDENTIAL=' "$f" > "$tmp" || true; ` +
101
+ `printf 'KYBERNESIS_AGENT_CREDENTIAL="%s"\\n' "$v" >> "$tmp"; mv "$tmp" "$f"; chmod 600 "$f"; ` +
102
+ `grep -c '^KYBERNESIS_AGENT_CREDENTIAL=' "$f"`;
103
+ try {
104
+ const wrote = execFileSync("ssh", [target, script], { input: value, encoding: "utf8" });
105
+ console.log(green(`\n ✓ installed on ${target} (${wrote.trim()} line)`));
106
+ }
107
+ catch {
108
+ console.log(red(`\n Could not write it on ${target}.`));
109
+ console.log(dim(" Re-run with --local to write ./.env.local instead."));
110
+ process.exitCode = 1;
111
+ return;
112
+ }
113
+ console.log(dim(" Restarting so the agent reads it …"));
114
+ try {
115
+ execFileSync("ssh", [target, `cd ~/${name} && bash scripts/eve-server.sh restart >/dev/null 2>&1 &`], {
116
+ encoding: "utf8",
117
+ });
118
+ console.log(green(" ✓ restart triggered"));
119
+ }
120
+ catch {
121
+ console.log(yellow(` ! could not restart — run: ssh ${target} 'cd ~/${name} && bash scripts/eve-server.sh restart'`));
122
+ }
123
+ console.log(dim(`\n Check what it can now reach:\n` +
124
+ ` ssh ${target} 'cd ~/${name} && CRED=$(grep -m1 "^KYBERNESIS_AGENT_CREDENTIAL=" .env.local | cut -d= -f2- | tr -d "\\"") && curl -s -H "authorization: Bearer $CRED" ${issuer}/api/agent/peers'\n`));
125
+ }
@@ -1,3 +1,5 @@
1
+ /** Sign in with the device flow and return an identity token. */
2
+ export declare function signIn(issuer: string): Promise<string | null>;
1
3
  export declare function register(options: {
2
4
  name?: string;
3
5
  url?: string;
package/dist/register.js CHANGED
@@ -16,7 +16,7 @@ function envOf(dir) {
16
16
  return out;
17
17
  }
18
18
  /** Sign in with the device flow and return an identity token. */
19
- async function signIn(issuer) {
19
+ export async function signIn(issuer) {
20
20
  const started = await fetch(`${issuer}/api/oauth/device`, {
21
21
  method: "POST",
22
22
  headers: { "content-type": "application/json" },
package/dist/util.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export declare const EVE_VERSION = "0.30.8";
1
+ export declare const EVE_VERSION = "0.38.3";
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.30.8";
3
+ export const EVE_VERSION = "0.38.3";
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.7.13",
3
+ "version": "0.8.0",
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",
@@ -185,7 +185,7 @@ something. Chase them a week out — an unprovisioned Vercel team on Day 1 costs
185
185
  `@kybernesis/arcana@0.1.1`, `@kybernesis/enterprise@0.1.2`,
186
186
  `@kybernesis/multiplayer@0.1.0`, `@kybernesis/evals@0.2.1`,
187
187
  `@kybernesis/create@0.1.4`, `@kybernesis/engineer@0.2.0`, and
188
- `eve@0.30.8` (the Kybernesis-certified version). All public on npm;
188
+ `eve@0.38.3` (the Kybernesis-certified version). All public on npm;
189
189
  `kyb doctor` checks the wiring.
190
190
 
191
191
  ---
@@ -349,8 +349,8 @@ npx eve registry view @kybernesis/arcana
349
349
  ### 3.4 Pin your versions
350
350
 
351
351
  Before you install anything else, decide and record the versions this engagement pins.
352
- Put them in the repo README. Pin `eve@0.30.8` — the **Kybernesis-certified** version
353
- (certification run 2026-08-06: full suite green, zero code changes). Never pin blind
352
+ Put them in the repo README. Pin `eve@0.38.3` — the **Kybernesis-certified** version
353
+ (certification run 2026-08-17: full suite green; one schedule API change, see HANDOFF). Never pin blind
354
354
  npm-latest; `kyb upgrade` carries a client to the certified pin behind their own eval
355
355
  gate, and that upgrade is a **deliberate, eval-gated step**, never something that
356
356
  happens by accident mid-pilot.
@@ -110,7 +110,7 @@ then `eve add @kybernesis/<item>`). Each covers one axis:
110
110
  asterisks onto the URL in Slack and breaks it. Coach agents in prose, not
111
111
  shell (WAFs eat shell-syntax Slack messages).
112
112
  - **ESM packaging**: relative imports need `.js` extensions (tsc doesn't
113
- rewrite); eve is a peer dep with an explicit range (`>=0.30.0 <0.31.0`),
113
+ rewrite); eve is a peer dep with an explicit range (`>=0.38.0 <0.39.0`),
114
114
  pinned exactly in devDeps.
115
115
  - **Eval fixtures are hardened on purpose** — in-test nonces (eve caches
116
116
  compiled eval modules), per-run unique keys (workspaces accumulate),