@kybernesis/create 0.7.2 → 0.7.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.
@@ -0,0 +1,8 @@
1
+ export interface ArcanaSetup {
2
+ dir: string;
3
+ /** Proposed workspace prefix — the agent's name. Only a suggestion. */
4
+ suggest: string;
5
+ /** Department subagents, each of which gets its own brain. */
6
+ depts?: string[];
7
+ }
8
+ export declare function configureArcana({ dir, suggest, depts }: ArcanaSetup): Promise<void>;
package/dist/arcana.js ADDED
@@ -0,0 +1,99 @@
1
+ import { appendFileSync, existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { ask, bold, dim, green, red, yellow } from "./util.js";
4
+ /**
5
+ * Collect the memory workspaces and their keys, and check them before moving on.
6
+ *
7
+ * This exists because the registry item's `envVars` only APPENDS blank names to
8
+ * .env.local — nothing ever asked for a value. So a scaffold finished looking
9
+ * complete, with an agent whose memory could not work, and the first sign of it
10
+ * was an agent that had lost its mind at runtime.
11
+ *
12
+ * It also stops guessing the workspace names. `<agent>-company` is a
13
+ * convention, not a fact: a workspace with that name may already exist for
14
+ * something else, or the client may have named theirs differently years ago.
15
+ * The scaffolder proposes; the person deploying decides.
16
+ */
17
+ const API = "https://api.arcana.kybernesis.ai";
18
+ /** Read a key/workspace pair the way the agent will: read-only, one call. */
19
+ async function check(workspace, key) {
20
+ try {
21
+ const res = await fetch(`${API}/brain/${encodeURIComponent(workspace)}/timeline?limit=1`, {
22
+ headers: { authorization: `Bearer ${key}`, "X-Kyberagent-Agent": workspace },
23
+ signal: AbortSignal.timeout(15_000),
24
+ });
25
+ if (res.ok)
26
+ return "ok";
27
+ // 403 is the one that matters: keys are workspace-scoped, and a key for the
28
+ // wrong brain is the single most common way this is set up wrong.
29
+ return res.status === 401 || res.status === 403 ? "forbidden" : "unreachable";
30
+ }
31
+ catch {
32
+ return "unreachable";
33
+ }
34
+ }
35
+ function upsertEnv(dir, values) {
36
+ const p = join(dir, ".env.local");
37
+ let text = existsSync(p) ? readFileSync(p, "utf8") : "";
38
+ for (const [k, v] of Object.entries(values)) {
39
+ if (!v)
40
+ continue;
41
+ const line = `${k}="${v}"`;
42
+ const re = new RegExp(`^${k}=.*$`, "m");
43
+ if (re.test(text))
44
+ text = text.replace(re, line);
45
+ else
46
+ text += (text.endsWith("\n") || text === "" ? "" : "\n") + line + "\n";
47
+ }
48
+ if (existsSync(p))
49
+ writeFileSync(p, text);
50
+ else
51
+ appendFileSync(p, text);
52
+ }
53
+ export async function configureArcana({ dir, suggest, depts = [] }) {
54
+ console.log(bold("\n Memory (Arcana) — workspaces and keys"));
55
+ console.log(dim(" Create these at https://arcana.kybernesis.ai and mint a scoped kb_ key\n" +
56
+ " for each. Names are yours — the suggestions are only a convention."));
57
+ const values = {};
58
+ let anyBad = false;
59
+ const pair = async (label, workspaceVar, keyVar, proposed) => {
60
+ const workspace = await ask(` ${label} workspace?`, proposed);
61
+ const key = await ask(` ${label} key (kb_…, empty to skip)?`, "");
62
+ if (!key) {
63
+ console.log(yellow(` skipped — ${keyVar} left unset`));
64
+ values[workspaceVar] = workspace;
65
+ return;
66
+ }
67
+ const state = await check(workspace, key);
68
+ if (state === "ok")
69
+ console.log(green(` ✓ ${workspace} reachable`));
70
+ else if (state === "forbidden") {
71
+ anyBad = true;
72
+ console.log(red(` ✗ that key is not valid for "${workspace}" (keys are workspace-scoped)`));
73
+ }
74
+ else {
75
+ console.log(yellow(` ! could not reach Arcana to check — saved anyway`));
76
+ }
77
+ values[workspaceVar] = workspace;
78
+ values[keyVar] = key;
79
+ };
80
+ await pair("Company", "ARCANA_COMPANY_WORKSPACE", "ARCANA_API_KEY", `${suggest}-company`);
81
+ // The eval script the scaffolder writes REQUIRES this one; leaving it to a
82
+ // template comment is how `npm run eval` fails on a fresh, correct install.
83
+ await pair("Eval", "ARCANA_EVAL_API_KEY_WORKSPACE", "ARCANA_EVAL_API_KEY", `${suggest}-eval`);
84
+ // The eval workspace name itself is derived by the npm script, so only the
85
+ // key is stored; drop the placeholder we used to prompt with.
86
+ delete values.ARCANA_EVAL_API_KEY_WORKSPACE;
87
+ for (const dept of depts) {
88
+ await pair(`Subagent "${dept}"`, `ARCANA_${dept.toUpperCase()}_WORKSPACE`, `ARCANA_${dept.toUpperCase()}_API_KEY`, `${suggest}-${dept}`);
89
+ }
90
+ // DM sessions default to the company brain unless the deployment splits them.
91
+ if (values.ARCANA_COMPANY_WORKSPACE) {
92
+ values.ARCANA_DM_WORKSPACE ??= values.ARCANA_COMPANY_WORKSPACE;
93
+ }
94
+ upsertEnv(dir, values);
95
+ console.log(dim(" written to .env.local"));
96
+ if (anyBad) {
97
+ console.log(yellow(" Fix the mismatched pair and re-run `kyb arcana` — `kyb doctor` checks them too."));
98
+ }
99
+ }
package/dist/cli.js CHANGED
@@ -6,6 +6,7 @@
6
6
  * self-testing eve agent (also: npm create @kybernesis)
7
7
  * kyb doctor preflight an agent project: keys, issuer, envs, discovery
8
8
  * kyb skills [--global] install/refresh the FDE Claude Code skill suite
9
+ * kyb arcana set memory workspaces + keys, and verify them
9
10
  * kyb register register this agent with the control plane (device flow)
10
11
  * kyb deploy put this repo on its host and restart it, with proof
11
12
  * kyb upgrade bump @kybernesis/* to latest, gated on the eval suite
@@ -18,6 +19,7 @@ import { upgrade } from "./upgrade.js";
18
19
  import { installSkills } from "./skills.js";
19
20
  import { deploy } from "./deploy.js";
20
21
  import { register } from "./register.js";
22
+ import { configureArcana } from "./arcana.js";
21
23
  function flag(rest, key) {
22
24
  const hit = rest.find((a) => a.startsWith(`--${key}=`));
23
25
  return hit ? hit.slice(key.length + 3) : undefined;
@@ -44,6 +46,9 @@ switch (command) {
44
46
  case "skills":
45
47
  installSkills({ global: rest.includes("--global") });
46
48
  break;
49
+ case "arcana":
50
+ await configureArcana({ dir: process.cwd(), suggest: flag(rest, "name") ?? "agent" });
51
+ break;
47
52
  case "register":
48
53
  await register({ name: flag(rest, "name"), url: flag(rest, "url") });
49
54
  break;
@@ -75,6 +80,7 @@ ${bold("kyb")} — Kybernesis agent scaffolder & FDE toolkit
75
80
  ${bold("kyb doctor")} preflight checks (keys, issuer, envs, discovery)
76
81
  ${bold("kyb skills")} install/refresh the FDE skill suite for Claude Code
77
82
  --global ${dim("install to ~/.claude/skills instead of this repo")}
83
+ ${bold("kyb arcana")} set memory workspaces + keys, and verify each pair
78
84
  ${bold("kyb register")} register this agent with the control plane
79
85
  --name=<name> ${dim("defaults to KYBERNESIS_AGENT in .env.local")}
80
86
  --url=<url> ${dim("defaults to https://$EXE_VM_NAME.exe.xyz")}
package/dist/init.js CHANGED
@@ -3,6 +3,7 @@ import { join, resolve } from "node:path";
3
3
  import { DEFAULT_ISSUER, EVE_VERSION, REGISTRY_URL, ask, bold, closePrompts, dim, green, run, slug, yellow, } from "./util.js";
4
4
  import { CHANNEL_KINDS, channelPlan, engineerPlan, envExample, evalFileTs, evalScript, hostAgentTs, hostSteps, identityMd, rootArcanaTs, subagentAgentTs, subagentArcanaTs, subagentInstructionsMd, } from "./templates.js";
5
5
  import { suiteDir } from "./skills.js";
6
+ import { configureArcana } from "./arcana.js";
6
7
  /**
7
8
  * The always-installed core. Everything else — channels, subagents, engineer,
8
9
  * host bindings — is opt-in, because assuming them means the FDE deletes files
@@ -111,6 +112,11 @@ export async function init(rawName, options = {}) {
111
112
  writeFileSync(join(dir, "agent/agent.ts"), hostAgentTs(host, DEFAULT_MODEL));
112
113
  writeFileSync(join(dir, "agent/extensions/arcana.ts"), rootArcanaTs());
113
114
  writeFileSync(join(dir, "evals/kybernesis.eval.ts"), evalFileTs(displayName, depts));
115
+ // Ask for the memory keys HERE, while the person is still standing in the
116
+ // scaffold — not in a printed next-step they will read after the context has
117
+ // gone. Skipped with --yes, which is for CI and takes no input by design.
118
+ if (!options.yes)
119
+ await configureArcana({ dir, suggest: name, depts });
114
120
  /**
115
121
  * A self-hosted agent gets its restart script installed, not described.
116
122
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kybernesis/create",
3
- "version": "0.7.2",
3
+ "version": "0.7.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",