@kybernesis/create 0.12.0 → 0.12.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/doctor.js CHANGED
@@ -367,9 +367,23 @@ export async function doctor() {
367
367
  }
368
368
  }
369
369
  // ── eve discovery + local port ─────────────────────────────────────────
370
- const info = capture("npx", ["eve", "info"], cwd);
371
- if (info === null)
372
- add("fail", "eve info failed", "run npx eve info for details");
370
+ /**
371
+ * Discovery, run with the environment the SERVER runs with.
372
+ *
373
+ * `eve info` compiles the agent, and an agent that reads a variable at module
374
+ * scope — a model id, a required credential — throws without it. eve does not
375
+ * load .env.local itself, so running this with a bare environment reported a
376
+ * healthy agent as a failed discovery, on a host where the service starts it
377
+ * correctly every time. That red is the one thing a person is told must be
378
+ * green before going further, so it stopped a deployment that was fine.
379
+ *
380
+ * `env` here is the same merged view every other check reads: .env.local
381
+ * underneath, the real environment on top.
382
+ */
383
+ const info = capture("npx", ["eve", "info"], cwd, env);
384
+ if (info === null) {
385
+ add("fail", "eve info failed", "run: set -a && . ./.env.local && set +a && npx eve info — the agent's own error is in that output");
386
+ }
373
387
  else {
374
388
  const diag = /Diagnostics\s+(\d+) errors?, (\d+) warnings?/.exec(info);
375
389
  if (diag && diag[1] === "0")
package/dist/upgrade.js CHANGED
@@ -1,4 +1,4 @@
1
- import { existsSync, readFileSync } from "node:fs";
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { upsertEnv } from "./envfile.js";
4
4
  import { EVE_VERSION, bold, capture, dim, green, red, run, yellow } from "./util.js";
@@ -87,6 +87,98 @@ function repairLocalQueueTimeouts(cwd, deps) {
87
87
  ` ${dim("slower than 30s was redelivered and its steps re-run — the agent answered twice.")}\n` +
88
88
  ` ${dim("Takes effect on the next server restart.")}\n`);
89
89
  }
90
+ /**
91
+ * Finish a Buzz install that a version bump alone leaves half-done.
92
+ *
93
+ * @remarks
94
+ * A capability that arrives in a package but needs four manual steps to switch
95
+ * on has not really shipped. That is what happened here: `kyb upgrade` moved an
96
+ * agent to a version whose whole point was acting in a workspace, and the agent
97
+ * went on being unable to, because the extension was not mounted, the process
98
+ * had none of the environment the bridge had, and the CLI was not on the host.
99
+ * Everything looked upgraded and nothing was different.
100
+ *
101
+ * The person who hit it was an engineer with a terminal, and it still took a
102
+ * hand-written prompt to sort out. A client would not have got there at all —
103
+ * they would have concluded the feature did not work.
104
+ *
105
+ * So each of those is repaired here, from what the host can already tell us:
106
+ * the bridge's own service file holds the relay and the identity, and the rest
107
+ * is a file and a binary.
108
+ */
109
+ function repairBuzzSetup(cwd, deps) {
110
+ if (!deps["@kybernesis/buzz"])
111
+ return;
112
+ const done = [];
113
+ // 1. The extension mount. Without it the tools do not exist, and nothing says so.
114
+ const mount = join(cwd, "agent/extensions/buzz.ts");
115
+ if (!existsSync(mount)) {
116
+ mkdirSync(join(cwd, "agent/extensions"), { recursive: true });
117
+ writeFileSync(mount, `// The agent's hands in Buzz: projects, issues, pull requests, patches, repos,
118
+ ` +
119
+ `// long-form notes, channel canvases, workflows, the feed, media — everything the
120
+ ` +
121
+ `// workspace has beyond talking, which the bridge alone does not provide.
122
+ ` +
123
+ `//
124
+ ` +
125
+ `// Actions are signed with THIS AGENT's key and appear under its name.
126
+ ` +
127
+ `export { default } from "@kybernesis/buzz/extension";
128
+ `);
129
+ done.push("mounted the Buzz extension (agent/extensions/buzz.ts)");
130
+ }
131
+ /**
132
+ * 2. The environment. The bridge runs with the relay and the key; the AGENT
133
+ * process runs with neither, because nothing ever needed it to until the
134
+ * tools existed. Read from the service file rather than asked for, because
135
+ * the answer is already on the host and a person retyping it will eventually
136
+ * mistype it.
137
+ */
138
+ const envPath = join(cwd, ".env.local");
139
+ const env = existsSync(envPath) ? readFileSync(envPath, "utf8") : "";
140
+ if (!/^BUZZ_RELAY=/m.test(env)) {
141
+ const unit = capture("sh", [
142
+ "-c",
143
+ "grep -ohE 'BUZZ_(RELAY|KEYFILE)=[^ \"]+' /etc/systemd/system/*buzz-bridge.service 2>/dev/null | head -2",
144
+ ]);
145
+ const values = {};
146
+ for (const line of (unit ?? "").split("\n")) {
147
+ const [name, ...rest] = line.trim().split("=");
148
+ if (name && rest.length)
149
+ values[name] = rest.join("=").replace(/^"|"$/g, "");
150
+ }
151
+ if (values.BUZZ_RELAY) {
152
+ upsertEnv(cwd, {
153
+ BUZZ_RELAY: values.BUZZ_RELAY,
154
+ ...(values.BUZZ_KEYFILE ? { BUZZ_KEYFILE: values.BUZZ_KEYFILE } : {}),
155
+ });
156
+ done.push("gave the agent process the relay and identity its bridge already had");
157
+ }
158
+ else {
159
+ console.log(` ${yellow("!")} Buzz tools need BUZZ_RELAY and BUZZ_KEYFILE in .env.local — the same ` +
160
+ `values the bridge service uses. Could not read them from this host.`);
161
+ }
162
+ }
163
+ // 3. The CLI itself. Built once, in a container, because no binary is published.
164
+ if (!existsSync(join(cwd, ".buzz/bin/buzz"))) {
165
+ const hasDocker = capture("sh", ["-c", "command -v docker >/dev/null && echo yes"])?.trim() === "yes";
166
+ if (hasDocker) {
167
+ console.log(dim(" building the Buzz CLI for this host (first time only, a few minutes) …"));
168
+ const ok = run("npx", ["kybernesis-buzz", "install-cli"], { cwd, allowFail: true, quiet: true });
169
+ done.push(ok ? "installed the Buzz CLI (.buzz/bin/buzz)" : "could NOT install the Buzz CLI — run: npx kybernesis-buzz install-cli");
170
+ }
171
+ else {
172
+ console.log(` ${yellow("!")} No docker here, so the Buzz CLI cannot be built. Without it the agent ` +
173
+ `can read the workspace but not act in it. Install docker, or set BUZZ_CLI_URL to a ` +
174
+ `binary built for this platform, then: npx kybernesis-buzz install-cli`);
175
+ }
176
+ }
177
+ if (done.length > 0) {
178
+ console.log(` ${green("+")} Buzz: ${done.join("; ")}`);
179
+ console.log(` ${dim("Takes effect after the next build and restart.")}\n`);
180
+ }
181
+ }
90
182
  export async function upgrade(skipEval) {
91
183
  const cwd = process.cwd();
92
184
  const pkg = JSON.parse(readFileSync(join(cwd, "package.json"), "utf8"));
@@ -94,6 +186,7 @@ export async function upgrade(skipEval) {
94
186
  console.log(bold("\nkyb upgrade — checking @kybernesis/* and eve against npm\n"));
95
187
  warnIfStale();
96
188
  repairLocalQueueTimeouts(cwd, deps);
189
+ repairBuzzSetup(cwd, deps);
97
190
  const toUpgrade = [];
98
191
  const unresolved = [];
99
192
  for (const name of kybernesisPackages(deps)) {
package/dist/util.d.ts CHANGED
@@ -11,7 +11,7 @@ export declare function run(command: string, args: string[], options?: {
11
11
  allowFail?: boolean;
12
12
  quiet?: boolean;
13
13
  }): boolean;
14
- export declare function capture(command: string, args: string[], cwd?: string): string | null;
14
+ export declare function capture(command: string, args: string[], cwd?: string, extraEnv?: Record<string, string>): string | null;
15
15
  export declare function ask(question: string, fallback: string): Promise<string>;
16
16
  export declare function closePrompts(): void;
17
17
  /** Parse KEY="value" / KEY=value lines from an env file's contents. */
package/dist/util.js CHANGED
@@ -24,8 +24,12 @@ export function run(command, args, options) {
24
24
  }
25
25
  return ok;
26
26
  }
27
- export function capture(command, args, cwd) {
28
- const result = spawnSync(command, args, { cwd, encoding: "utf8", env: process.env });
27
+ export function capture(command, args, cwd, extraEnv) {
28
+ const result = spawnSync(command, args, {
29
+ cwd,
30
+ encoding: "utf8",
31
+ env: extraEnv ? { ...process.env, ...extraEnv } : process.env,
32
+ });
29
33
  return result.status === 0 ? result.stdout : null;
30
34
  }
31
35
  let rl = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kybernesis/create",
3
- "version": "0.12.0",
3
+ "version": "0.12.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",
@@ -178,9 +178,12 @@ something. Chase them a week out — an unprovisioned Vercel team on Day 1 costs
178
178
  - [ ] **Confirm who at ACME is the Slack workspace admin.** Creating the Slack connector
179
179
  requires someone who can approve a Slack app install. If that person is on holiday
180
180
  your Day 2 is Slack-less.
181
- - [ ] **Read the reference implementation.** `~/kyber` is our own production agent and
182
- the canonical example of everything in Phase 4. Skim `agent/agent.ts`,
183
- `agent/instructions/`, `agent/subagents/finance/`, and `evals/`.
181
+ - [ ] **Know the shape you are aiming for** before writing any of it: one
182
+ `agent/agent.ts` that picks a model and mounts extensions, instructions
183
+ split into identity and per-surface, one subagent per department that owns
184
+ its own tools and skills, and an `evals/` suite that gates the deploy.
185
+ Every later phase adds to that skeleton; building it in a different shape
186
+ is what makes the eval suite and the routing evals fight you.
184
187
  - [ ] **Confirm package versions you will pin.** As of 2026-08-06:
185
188
  `@kybernesis/arcana@0.1.1`, `@kybernesis/enterprise@0.1.2`,
186
189
  `@kybernesis/multiplayer@0.1.0`, `@kybernesis/evals@0.2.1`,
@@ -436,9 +439,9 @@ What to know when choosing:
436
439
  principal). Prefer `session.started` over per-turn switching: prompt caches
437
440
  are per model, and every switch re-ingests the conversation at uncached
438
441
  prices. Resolver failures degrade to the fallback, never fail the turn.
439
- - Kyber runs `anthropic/claude-opus-4.8` via the gateway; our eval judges are
440
- configured separately in `evals/evals.config.ts` and must **never** be the
441
- model under test.
442
+ - Whatever the agent runs, eval judges are configured separately in
443
+ `evals/evals.config.ts` and must **never** be the model under test — a model
444
+ grading its own output measures agreement, not correctness.
442
445
 
443
446
  ### 4.1 Install `@kybernesis/enterprise` (governance) — do this FIRST
444
447
 
@@ -532,16 +535,11 @@ npx eve add @kybernesis/multiplayer
532
535
  This writes `agent/channels/slack.ts` and `agent/instructions/multiplayer.md`, and
533
536
  declares the `SLACK_CONNECTOR_UID` env var.
534
537
 
535
- > **Status as of 2026-08-05:** the registry item is **live** (`eve add` resolves and
536
- > writes both files), and the package is dogfooded in `~/kyber` but
537
- > `@kybernesis/multiplayer@0.1.0` is **not yet published to npm**, so the dependency
538
- > install step will fail until it is. Check first:
538
+ > Published and installable `eve add` resolves the registry item and writes both
539
+ > files. Confirm what you are pinning before you promise a version to a client:
539
540
  > ```bash
540
- > npm view @kybernesis/multiplayer version # E404 means the publish hasn't landed
541
+ > npm view @kybernesis/multiplayer version
541
542
  > ```
542
- > If it 404s, install from the workspace checkout at `~/kyber/packages/multiplayer` (or
543
- > vendor the two files by hand — they are shown below and in
544
- > `agent/instructions/multiplayer.md`) and revisit once the publish lands.
545
543
 
546
544
  The whole Slack integration is one file:
547
545
 
@@ -968,7 +966,7 @@ token minted per edge; the callee URL comes from the registry (discovery), env
968
966
  var still wins. THE DEMO: revoke the edge in the admin → the caller is refused
969
967
  (edge_not_granted) within 5 minutes, no redeploy; re-grant → restored. Run it
970
968
  for the client — it's the whole governance story in one minute. Full lifecycle
971
- proven live 2026-08-07 (kyber eve-gtm). Budget note: the deployed agent and
969
+ proven live 2026-08-07 between two governed agents. Budget note: the deployed agent and
972
970
  local eval runs share the project's AI Gateway budget — size it for both.
973
971
 
974
972
  ### 4.4 Author the agent's identity and instructions
@@ -982,8 +980,7 @@ surface.ts example below). Keep always-on instructions to identity, tone, and
982
980
  standing rules; procedures belong in skills (§4.3e) — the model loads those on
983
981
  demand instead of paying for them every turn.
984
982
 
985
- Crib the structure from `~/kyber/agent/instructions/identity.md`, which has
986
- three sections worth copying:
983
+ Give `agent/instructions/identity.md` three sections:
987
984
 
988
985
  1. **Identity** — who the agent is, and *how to write for Slack*: short paragraphs,
989
986
  bullets, no headings unless the answer is genuinely long. Slack is a chat surface, not
@@ -996,9 +993,9 @@ three sections worth copying:
996
993
  asks for something personal in a channel.
997
994
 
998
995
  For per-session context, `defineDynamic` on `session.started` lets you inject
999
- surface-specific instructions. `~/kyber/agent/instructions/surface.ts` is a working
1000
- example: it greets a DM session by the caller's verified name and reminds a channel
1001
- session that everything it posts is public.
996
+ surface-specific instructions greeting a DM session by the caller's verified
997
+ name, and reminding a channel session that everything it posts is public. The
998
+ same agent should not talk to a room the way it talks to one person.
1002
999
 
1003
1000
  **Author these with Claude Code (§4.0), and judge drafts by test, not by
1004
1001
  reading** — paste the discovery notes, have it draft identity.md and the
@@ -1077,7 +1074,7 @@ export default defineAgent({
1077
1074
  workspace })` mount with that department's scoped key) and only that
1078
1075
  subagent gets the connection + skills + instructions. This is the default
1079
1076
  pattern now. The plain-connection alternative below still works (it's what
1080
- pre-0.30 required, and what you'll find in older kyber subagents) when you
1077
+ pre-0.30 required, and what you'll find in subagents written before it) when you
1081
1078
  want the connection without the shipped skills:
1082
1079
 
1083
1080
  ```ts
@@ -1107,9 +1104,9 @@ export default defineAgent({
1107
1104
 
1108
1105
  `agent/schedules/*.ts` for anything recurring — a Monday pipeline summary, a Friday
1109
1106
  financial report. **Schedules live on the root agent only**; a scheduled root turn
1110
- delegates to the subagent that owns the work. `~/kyber/agent/schedules/friday-financials.ts`
1111
- is the working example: it fires Friday 02:00 UTC, delegates to `finance`, and DMs the
1112
- result to a configured Slack user. Note that DMing a user from a schedule needs the
1107
+ delegates to the subagent that owns the work. A worked example: fire at 02:00 UTC
1108
+ on Friday, delegate to a `finance` subagent, and DM the result to a configured
1109
+ Slack user. Note that DMing a user from a schedule needs the
1113
1110
  `im:write` scope on the Slack connector — add it during Phase 5 or the first run fails
1114
1111
  silently at the last step.
1115
1112
 
@@ -1147,8 +1144,8 @@ rate, delegation mix, p50/p95 duration.
1147
1144
  userId by default, so PostHog sees one anonymous actor named after the service. To
1148
1145
  attribute turns to the verified speaker, add a sibling hook that stamps the
1149
1146
  per-message-authenticated principal via evlog's `useLogger` — on `step.started`, not
1150
- `turn.started`, so evlog's turn state exists regardless of hook ordering (crib
1151
- `~/kyber/agent/hooks/attribution.ts`). Then a one-time $identify per person maps ids
1147
+ `turn.started`, so evlog's turn state exists regardless of hook ordering. Then a
1148
+ one-time $identify per person maps ids
1152
1149
  to names. Per-employee telemetry must be a deliberate, disclosed choice at a client.
1153
1150
 
1154
1151
  ### 4.7 Environment variables
@@ -1931,8 +1928,8 @@ conversation — it changes the shape of the deal.
1931
1928
 
1932
1929
  **Grok, on a SuperGrok or X Premium+ subscription.** Same arrangement, without
1933
1930
  the broker: xAI's Grok Build CLI does a device login and writes a credential
1934
- that is a valid bearer for `https://api.x.ai/v1`. Proven in production on Sid —
1935
- twelve evals, twenty-nine gates, green on the subscription.
1931
+ that is a valid bearer for `https://api.x.ai/v1`. Proven in production on a
1932
+ self-hosted agent — twelve evals, twenty-nine gates, green on the subscription.
1936
1933
 
1937
1934
  ```bash
1938
1935
  # on the host, as the unix user the agent runs as
@@ -1962,8 +1959,8 @@ Three things to know before you promise it to a client:
1962
1959
  host for a week, it is an open question, and it would present to the client as
1963
1960
  the agent breaking for no reason.
1964
1961
 
1965
- **The model will lie about which model it is.** Sid, running Grok, stated it was
1966
- "Claude Opus 4.6, Anthropic" and attributed it to an instruction that exists
1962
+ **The model will lie about which model it is.** A self-hosted agent running Grok
1963
+ stated it was "Claude Opus 4.6, Anthropic" and attributed it to an instruction that exists
1967
1964
  nowhere in its context. Verify from the host — the configured model id and the
1968
1965
  credential in use — never by asking the agent. Expect a client to ask it in a
1969
1966
  demo, and have the real answer ready.