@kybernesis/create 0.7.13 → 0.7.14
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 +6 -0
- package/dist/credential.d.ts +6 -0
- package/dist/credential.js +125 -0
- package/dist/register.d.ts +2 -0
- package/dist/register.js +1 -1
- package/package.json +1 -1
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,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
|
+
}
|
package/dist/register.d.ts
CHANGED
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kybernesis/create",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.14",
|
|
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",
|