@kybernesis/create 0.13.1 → 0.13.3
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/add.d.ts +17 -0
- package/dist/add.js +110 -0
- package/dist/cli.js +9 -0
- package/dist/init.d.ts +12 -0
- package/dist/init.js +23 -5
- package/dist/templates.js +21 -4
- package/package.json +1 -1
package/dist/add.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `kyb add channel <kind>` — put another surface on an agent that already exists.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* The scaffold has told people to run this command since the day it shipped,
|
|
6
|
+
* in its own closing summary and in the host-specific steps, and it did not
|
|
7
|
+
* exist. Anyone following those instructions got "no such command" and a
|
|
8
|
+
* suggestion to upgrade, which is a particularly bad failure: the advice is
|
|
9
|
+
* wrong, the fix it proposes does nothing, and the person concludes their
|
|
10
|
+
* install is broken.
|
|
11
|
+
*
|
|
12
|
+
* Everything it needs was already here. `channelPlan` describes each surface —
|
|
13
|
+
* its file, dependencies, registry items, env and human steps — and `init` has
|
|
14
|
+
* always applied that plan. This applies the same plan to a directory that is
|
|
15
|
+
* already an agent, which is all "add" ever meant.
|
|
16
|
+
*/
|
|
17
|
+
export declare function add(what: string | undefined, rest: string[]): Promise<void>;
|
package/dist/add.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { basename, join, resolve } from "node:path";
|
|
3
|
+
import { bold, dim, green, red, run, yellow } from "./util.js";
|
|
4
|
+
import { CHANNEL_KINDS, channelPlan } from "./templates.js";
|
|
5
|
+
/**
|
|
6
|
+
* `kyb add channel <kind>` — put another surface on an agent that already exists.
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
* The scaffold has told people to run this command since the day it shipped,
|
|
10
|
+
* in its own closing summary and in the host-specific steps, and it did not
|
|
11
|
+
* exist. Anyone following those instructions got "no such command" and a
|
|
12
|
+
* suggestion to upgrade, which is a particularly bad failure: the advice is
|
|
13
|
+
* wrong, the fix it proposes does nothing, and the person concludes their
|
|
14
|
+
* install is broken.
|
|
15
|
+
*
|
|
16
|
+
* Everything it needs was already here. `channelPlan` describes each surface —
|
|
17
|
+
* its file, dependencies, registry items, env and human steps — and `init` has
|
|
18
|
+
* always applied that plan. This applies the same plan to a directory that is
|
|
19
|
+
* already an agent, which is all "add" ever meant.
|
|
20
|
+
*/
|
|
21
|
+
export async function add(what, rest) {
|
|
22
|
+
if (what !== "channel") {
|
|
23
|
+
console.error(red(`kyb add: don't know how to add "${what ?? ""}".`));
|
|
24
|
+
console.log(dim(` kyb add channel <${CHANNEL_KINDS.filter((k) => k !== "none").join("|")}>`));
|
|
25
|
+
process.exitCode = 1;
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const kind = rest.find((a) => !a.startsWith("-"));
|
|
29
|
+
if (!kind || !CHANNEL_KINDS.includes(kind) || kind === "none") {
|
|
30
|
+
console.error(red(`kyb add channel: pick one of ${CHANNEL_KINDS.filter((k) => k !== "none").join(", ")}.`));
|
|
31
|
+
process.exitCode = 1;
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const dir = resolve(process.cwd());
|
|
35
|
+
if (!existsSync(join(dir, "package.json")) || !existsSync(join(dir, "agent"))) {
|
|
36
|
+
console.error(red("kyb add channel: run this inside an agent directory."));
|
|
37
|
+
process.exitCode = 1;
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const name = agentNameOf(dir);
|
|
41
|
+
const host = existsSync(join(dir, "scripts/eve-server.sh")) ? "exe" : "vercel";
|
|
42
|
+
const plan = channelPlan(kind, name, host);
|
|
43
|
+
console.log(bold(`kyb add channel ${kind}`));
|
|
44
|
+
console.log(dim(` agent: ${name}\n host: ${host}`));
|
|
45
|
+
if (!plan.file) {
|
|
46
|
+
console.error(red(` ${kind} has no channel file to add.`));
|
|
47
|
+
process.exitCode = 1;
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const target = join(dir, "agent/channels", plan.file);
|
|
51
|
+
// Refuse rather than overwrite. A channel file is somewhere people put real
|
|
52
|
+
// logic — routing, filters, a greeting — and silently replacing it with the
|
|
53
|
+
// template is not something an "add" command should ever do.
|
|
54
|
+
if (existsSync(target)) {
|
|
55
|
+
console.log(yellow(` agent/channels/${plan.file} already exists — leaving it alone.`));
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
mkdirSync(join(dir, "agent/channels"), { recursive: true });
|
|
59
|
+
writeFileSync(target, plan.content);
|
|
60
|
+
console.log(green(` + agent/channels/${plan.file}`));
|
|
61
|
+
}
|
|
62
|
+
if (plan.deps.length) {
|
|
63
|
+
console.log(bold(`\n Installing ${plan.deps.join(", ")} …`));
|
|
64
|
+
run("npm", ["install", ...plan.deps, "--no-audit", "--no-fund"], { cwd: dir, allowFail: true });
|
|
65
|
+
}
|
|
66
|
+
for (const item of plan.registryItems) {
|
|
67
|
+
run("npx", ["eve", "add", item, "--overwrite"], { cwd: dir, allowFail: true });
|
|
68
|
+
}
|
|
69
|
+
// Report what was actually written, not what the plan contained. Run twice,
|
|
70
|
+
// the second run adds nothing and saying otherwise sends someone looking in
|
|
71
|
+
// .env.example for variables that were already there.
|
|
72
|
+
const added = appendEnvExample(dir, kind, plan.env);
|
|
73
|
+
if (added)
|
|
74
|
+
console.log(green(` + ${added} line(s) in .env.example`));
|
|
75
|
+
console.log(bold("\n Still to do:"));
|
|
76
|
+
for (const step of plan.steps)
|
|
77
|
+
console.log(` · ${step}`);
|
|
78
|
+
console.log(dim(`\n Then fill the new values into .env.local and restart:\n` +
|
|
79
|
+
` ${host === "exe" ? "bash scripts/eve-server.sh" : "kyb deploy"}`));
|
|
80
|
+
}
|
|
81
|
+
/** The agent's own name, as the rest of the toolchain reads it. */
|
|
82
|
+
function agentNameOf(dir) {
|
|
83
|
+
const env = join(dir, ".env.local");
|
|
84
|
+
if (existsSync(env)) {
|
|
85
|
+
const hit = readFileSync(env, "utf8").match(/^KYBERNESIS_AGENT\s*=\s*"?([^"\n]+)"?/m);
|
|
86
|
+
if (hit?.[1])
|
|
87
|
+
return hit[1].trim();
|
|
88
|
+
}
|
|
89
|
+
return basename(dir);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Append the channel's variables, once.
|
|
93
|
+
*
|
|
94
|
+
* @remarks
|
|
95
|
+
* Appended rather than rewritten so a hand-edited example file survives, and
|
|
96
|
+
* skipped when the key is already present so running the command twice does not
|
|
97
|
+
* leave two copies of every variable for someone to reconcile later.
|
|
98
|
+
*/
|
|
99
|
+
function appendEnvExample(dir, kind, lines) {
|
|
100
|
+
const path = join(dir, ".env.example");
|
|
101
|
+
const existing = existsSync(path) ? readFileSync(path, "utf8") : "";
|
|
102
|
+
const fresh = lines.filter((line) => {
|
|
103
|
+
const key = line.split("=")[0]?.trim();
|
|
104
|
+
return key ? !new RegExp(`^${key}\\s*=`, "m").test(existing) : true;
|
|
105
|
+
});
|
|
106
|
+
if (!fresh.length)
|
|
107
|
+
return 0;
|
|
108
|
+
writeFileSync(path, `${existing.replace(/\n*$/, "\n")}\n# ${kind}\n${fresh.join("\n")}\n`);
|
|
109
|
+
return fresh.length;
|
|
110
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -16,6 +16,7 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
16
16
|
import { basename, dirname, join } from "node:path";
|
|
17
17
|
import { fileURLToPath } from "node:url";
|
|
18
18
|
import { bold, dim, red } from "./util.js";
|
|
19
|
+
import { add } from "./add.js";
|
|
19
20
|
import { init } from "./init.js";
|
|
20
21
|
import { doctor } from "./doctor.js";
|
|
21
22
|
import { upgrade } from "./upgrade.js";
|
|
@@ -57,6 +58,7 @@ function initOptions(rest) {
|
|
|
57
58
|
studio: rest.includes('--studio'),
|
|
58
59
|
channel: flag(rest, "channel"),
|
|
59
60
|
host: flag(rest, "host"),
|
|
61
|
+
model: flag(rest, "model"),
|
|
60
62
|
subagents: subs === undefined ? undefined : subs.split(',').map((s) => s.trim()).filter(Boolean),
|
|
61
63
|
yes: rest.includes('--yes') || rest.includes('-y'),
|
|
62
64
|
};
|
|
@@ -64,6 +66,7 @@ function initOptions(rest) {
|
|
|
64
66
|
/** What each command is for, in one line, as a person would ask for it. */
|
|
65
67
|
const COMMANDS = {
|
|
66
68
|
init: "Scaffold a new agent: governed, remembering, multiplayer, self-testing.",
|
|
69
|
+
add: "Add a chat surface to an agent that already exists.",
|
|
67
70
|
doctor: "Check this machine and this project before an engagement.",
|
|
68
71
|
arcana: "Set the memory workspaces and keys this agent uses.",
|
|
69
72
|
skills: "Install the FDE skill suite (--global for every project).",
|
|
@@ -110,6 +113,9 @@ switch (command) {
|
|
|
110
113
|
case "init":
|
|
111
114
|
await init(rest.find((a) => !a.startsWith("-")), initOptions(rest));
|
|
112
115
|
break;
|
|
116
|
+
case "add":
|
|
117
|
+
await add(rest.find((a) => !a.startsWith("-")), rest.slice(1));
|
|
118
|
+
break;
|
|
113
119
|
case "doctor":
|
|
114
120
|
await doctor();
|
|
115
121
|
break;
|
|
@@ -182,9 +188,12 @@ ${dim(" npm i -g @kybernesis/create@latest")}
|
|
|
182
188
|
--channel=<kind> ${dim("none|slack|imessage|telegram|discord|web (default: none)")}
|
|
183
189
|
--host=<kind> ${dim("vercel|exe (default: vercel)")}
|
|
184
190
|
--subagents=a,b ${dim("department subagents (default: none)")}
|
|
191
|
+
--model=<id> ${dim("provider/model-id (default: sonnet 5)")}
|
|
185
192
|
--engineer ${dim("add the engineer layer: workshop sandbox + vision dev loop")}
|
|
186
193
|
--studio ${dim("wire for KYBER Studio: local execution + management routes")}
|
|
187
194
|
--yes ${dim("no prompts; take flags and defaults")}
|
|
195
|
+
${bold("kyb add channel <kind>")}
|
|
196
|
+
${dim("slack|imessage|telegram|discord|web — writes the channel, deps and env")}
|
|
188
197
|
${bold("kyb doctor")} preflight checks (keys, issuer, envs, discovery)
|
|
189
198
|
${bold("kyb skills")} install/refresh the FDE skill suite for Claude Code
|
|
190
199
|
--global ${dim("install to ~/.claude/skills instead of this repo")}
|
package/dist/init.d.ts
CHANGED
|
@@ -14,6 +14,18 @@ export interface InitOptions {
|
|
|
14
14
|
channel?: ChannelKind;
|
|
15
15
|
/** Where the agent runs. Default "vercel". */
|
|
16
16
|
host?: HostKind;
|
|
17
|
+
/**
|
|
18
|
+
* The model, as provider/model-id.
|
|
19
|
+
*
|
|
20
|
+
* @remarks
|
|
21
|
+
* Passed straight through to `eve init`, and that is why it exists: without
|
|
22
|
+
* it eve ends its scaffold by opening its interactive model picker, which
|
|
23
|
+
* needs a terminal UI. On a laptop that is a prompt; on a headless machine
|
|
24
|
+
* it is `--input requires the interactive UI` and a scaffold that stops
|
|
25
|
+
* after step one having already written the project — so `--yes` was never
|
|
26
|
+
* actually non-interactive.
|
|
27
|
+
*/
|
|
28
|
+
model?: string;
|
|
17
29
|
/** Department subagents. Default NONE. */
|
|
18
30
|
subagents?: string[];
|
|
19
31
|
/** Skip prompts and take the flags/defaults as given. */
|
package/dist/init.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { chmodSync, copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
|
-
import { DEFAULT_ISSUER, EVE_VERSION, REGISTRY_URL, ask, bold, closePrompts, dim, green, run, slug, yellow, } from "./util.js";
|
|
3
|
+
import { DEFAULT_ISSUER, EVE_VERSION, REGISTRY_URL, ask, bold, closePrompts, dim, green, run, slug, red, yellow, } from "./util.js";
|
|
4
4
|
import { CHANNEL_KINDS, channelPlan, engineerPlan, envExample, evalFileTs, exeEvalConfigTs, evalScript, hostAgentTs, hostSteps, identityMd, rootArcanaTs, subagentAgentTs, subagentArcanaTs, subagentInstructionsMd, } from "./templates.js";
|
|
5
5
|
import { suiteDir } from "./skills.js";
|
|
6
6
|
import { configureArcana } from "./arcana.js";
|
|
@@ -58,8 +58,26 @@ export async function init(rawName, options = {}) {
|
|
|
58
58
|
process.exit(1);
|
|
59
59
|
}
|
|
60
60
|
const plan = channelPlan(channel, name, host);
|
|
61
|
+
const model = options.model ?? DEFAULT_MODEL;
|
|
61
62
|
console.log(bold(`\n1/6 Scaffolding eve agent (eve@${EVE_VERSION}) …`));
|
|
62
|
-
|
|
63
|
+
// eve's scaffold ends by opening its interactive model picker
|
|
64
|
+
// (`eve dev --input /model`), which exits non-zero wherever there is no
|
|
65
|
+
// terminal UI — AFTER the project is fully created and its dependencies
|
|
66
|
+
// installed. Treating that as a failure makes headless scaffolding
|
|
67
|
+
// impossible, which is what a machine building itself has to do.
|
|
68
|
+
//
|
|
69
|
+
// Nothing is lost by ignoring it: agent.ts is overwritten below with our own
|
|
70
|
+
// template carrying the chosen model, so eve's pick would not have survived
|
|
71
|
+
// this function either. `--model` is deliberately NOT passed through — eve
|
|
72
|
+
// refuses an id it cannot find in the AI Gateway catalog and then creates
|
|
73
|
+
// nothing at all, and an exe-hosted agent takes its model from EXE_MODEL,
|
|
74
|
+
// not from the gateway.
|
|
75
|
+
run("npx", [`eve@${EVE_VERSION}`, "init", name], { allowFail: true });
|
|
76
|
+
// The real test of that step, since its exit code cannot be trusted.
|
|
77
|
+
if (!existsSync(join(dir, "package.json"))) {
|
|
78
|
+
console.error(red(`\n eve did not create a project in ${dir}. Nothing else can run.`));
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
63
81
|
console.log(bold("\n2/6 Adding the Kybernesis registry + core packages …"));
|
|
64
82
|
run("npx", ["eve", "registry", "add", `@kybernesis=${REGISTRY_URL}`], { cwd: dir });
|
|
65
83
|
for (const item of CORE_ITEMS) {
|
|
@@ -108,7 +126,7 @@ export async function init(rawName, options = {}) {
|
|
|
108
126
|
` This agent cannot be connected to a desktop until they install.`));
|
|
109
127
|
}
|
|
110
128
|
}
|
|
111
|
-
const engPlan = engineer ? engineerPlan(host,
|
|
129
|
+
const engPlan = engineer ? engineerPlan(host, model) : null;
|
|
112
130
|
if (engPlan) {
|
|
113
131
|
console.log(bold("\n2c Engineer subagent: workshop sandbox + vision dev loop …"));
|
|
114
132
|
run("npm", ["install", ...engPlan.deps, "--no-audit", "--no-fund"], { cwd: dir, allowFail: true });
|
|
@@ -133,7 +151,7 @@ export async function init(rawName, options = {}) {
|
|
|
133
151
|
unlinkSync(join(dir, "agent/instructions.md"));
|
|
134
152
|
}
|
|
135
153
|
catch { }
|
|
136
|
-
writeFileSync(join(dir, "agent/agent.ts"), hostAgentTs(host,
|
|
154
|
+
writeFileSync(join(dir, "agent/agent.ts"), hostAgentTs(host, model));
|
|
137
155
|
writeFileSync(join(dir, "agent/extensions/arcana.ts"), rootArcanaTs());
|
|
138
156
|
writeFileSync(join(dir, "evals/kybernesis.eval.ts"), evalFileTs(displayName, depts));
|
|
139
157
|
// A self-hosted agent judges through its own integration; the default
|
|
@@ -287,7 +305,7 @@ export async function init(rawName, options = {}) {
|
|
|
287
305
|
}
|
|
288
306
|
}
|
|
289
307
|
console.log(bold("\n5/6 Env template + hermetic eval script …"));
|
|
290
|
-
writeFileSync(join(dir, ".env.example"), envExample(name, depts, issuer, plan.env, host,
|
|
308
|
+
writeFileSync(join(dir, ".env.example"), envExample(name, depts, issuer, plan.env, host, model));
|
|
291
309
|
const pkgPath = join(dir, "package.json");
|
|
292
310
|
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
293
311
|
pkg.scripts = { ...pkg.scripts, eval: evalScript(name, depts) };
|
package/dist/templates.js
CHANGED
|
@@ -85,10 +85,20 @@ export default defineMcpClientConnection({
|
|
|
85
85
|
"The ${dept} team's long-term memory (Arcana): remember, recall, search, timeline, and brain notes.",
|
|
86
86
|
auth: {
|
|
87
87
|
getToken: async () => {
|
|
88
|
+
// The eval brain is a DIFFERENT workspace, so it needs its own key —
|
|
89
|
+
// Arcana keys answer 403 outside the workspace they were minted for.
|
|
90
|
+
// Recognised by name rather than by suffix: an eval workspace does not
|
|
91
|
+
// have to be called "<agent>-eval", and matching on the suffix sends a
|
|
92
|
+
// custom one through the company key, where every memory eval fails with
|
|
93
|
+
// a 403 that reads as the agent's memory being broken.
|
|
94
|
+
// Falls back to the old suffix rule when nothing names the eval brain, so
|
|
95
|
+
// an agent written before this variable existed keeps working untouched.
|
|
96
|
+
const evalWorkspace = process.env.ARCANA_EVAL_WORKSPACE;
|
|
97
|
+
const isEval = evalWorkspace
|
|
98
|
+
? workspace === evalWorkspace
|
|
99
|
+
: workspace.endsWith("-eval");
|
|
88
100
|
const token =
|
|
89
|
-
(
|
|
90
|
-
? process.env.ARCANA_EVAL_API_KEY
|
|
91
|
-
: undefined) ??
|
|
101
|
+
(isEval ? process.env.ARCANA_EVAL_API_KEY : undefined) ??
|
|
92
102
|
process.env.ARCANA_${upper}_API_KEY ??
|
|
93
103
|
process.env.ARCANA_API_KEY;
|
|
94
104
|
if (!token) throw new Error("ARCANA_${upper}_API_KEY is not set.");
|
|
@@ -117,9 +127,16 @@ if (!COMPANY) {
|
|
|
117
127
|
}
|
|
118
128
|
const DM = process.env.ARCANA_DM_WORKSPACE ?? COMPANY;
|
|
119
129
|
|
|
130
|
+
// Same rule as the department connections: the eval brain is named, not
|
|
131
|
+
// guessed from a suffix.
|
|
132
|
+
// Falls back to the old suffix rule when nothing names the eval brain, so an
|
|
133
|
+
// agent written before this variable existed keeps working untouched.
|
|
134
|
+
const EVAL = process.env.ARCANA_EVAL_WORKSPACE;
|
|
135
|
+
const IS_EVAL = EVAL ? COMPANY === EVAL : COMPANY.endsWith("-eval");
|
|
136
|
+
|
|
120
137
|
export default arcana({
|
|
121
138
|
apiKey:
|
|
122
|
-
(
|
|
139
|
+
(IS_EVAL ? process.env.ARCANA_EVAL_API_KEY : undefined) ??
|
|
123
140
|
process.env.ARCANA_API_KEY!,
|
|
124
141
|
workspace: COMPANY,
|
|
125
142
|
// DM sessions carry surface:"dm" via @kybernesis/multiplayer.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kybernesis/create",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.3",
|
|
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",
|