@kybernesis/create 0.18.0 → 0.19.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/doctor.d.ts CHANGED
@@ -20,7 +20,7 @@ export interface DockerTemplateInspection {
20
20
  }[];
21
21
  }
22
22
  export declare function dockerTemplateDoctorChecks(result: DockerTemplateInspection): Check[];
23
- export declare function modelReachDoctorCheck(authoredSource: string | null, compiledRouting: CompiledModelRouting): Check;
23
+ export declare function modelReachDoctorCheck(authoredSource: string | null, compiledRouting: CompiledModelRouting, env?: Record<string, string | undefined>): Check;
24
24
  export declare function agentInputLimitDoctorCheck(limit: AgentInputLimit): Check;
25
25
  export declare function diskUsageDoctorCheck(percent: number, dockerSummary: string | null): Check;
26
26
  /**
package/dist/doctor.js CHANGED
@@ -47,8 +47,13 @@ export function dockerTemplateDoctorChecks(result) {
47
47
  detail: issue.detail,
48
48
  }));
49
49
  }
50
- export function modelReachDoctorCheck(authoredSource, compiledRouting) {
51
- const reach = classifyModelReach(authoredSource, compiledRouting);
50
+ export function modelReachDoctorCheck(authoredSource, compiledRouting, env = {}) {
51
+ const check = modelReachCheck(classifyModelReach(authoredSource, compiledRouting, env));
52
+ return authoredSource !== null && /\bexeModelReach\s*\(/.test(authoredSource)
53
+ ? { ...check, label: `${check.label} [KYB_MODEL_REACH switch]` }
54
+ : check;
55
+ }
56
+ function modelReachCheck(reach) {
52
57
  switch (reach.kind) {
53
58
  case "claude-sub":
54
59
  return { verdict: "pass", label: "Model reach: claude-sub (Claude subscription)" };
@@ -653,7 +658,7 @@ export async function doctor() {
653
658
  const reachCheck = modelReachDoctorCheck(rootAgentSource, {
654
659
  kind: "unresolved",
655
660
  reason: "eve info failed before compiled model routing could be inspected",
656
- });
661
+ }, env);
657
662
  add(reachCheck.verdict, reachCheck.label, reachCheck.detail);
658
663
  add("warn", "Eve max input tokens/session: unresolved", "eve info failed before the compiled root policy could be inspected");
659
664
  }
@@ -663,7 +668,7 @@ export async function doctor() {
663
668
  add("pass", `eve discovery clean (${diag.warnings} warnings)`);
664
669
  else
665
670
  add("fail", `eve discovery: ${diag ? `${diag.errors} errors` : "unparsed"}`, "npx eve info --json");
666
- const reachCheck = modelReachDoctorCheck(rootAgentSource, info.modelRouting);
671
+ const reachCheck = modelReachDoctorCheck(rootAgentSource, info.modelRouting, env);
667
672
  add(reachCheck.verdict, reachCheck.label, reachCheck.detail);
668
673
  const limitCheck = agentInputLimitDoctorCheck(info.limit);
669
674
  add(limitCheck.verdict, limitCheck.label, limitCheck.detail);
package/dist/init.js CHANGED
@@ -132,17 +132,12 @@ export async function init(rawName, options = {}) {
132
132
  for (const item of CORE_ITEMS) {
133
133
  run("npx", ["eve", "add", `@kybernesis/${item}`, "--overwrite"], { cwd: dir });
134
134
  }
135
- // Anthropic is always the exe eval judge and is also the claude-sub agent
136
- // provider. OpenAI is needed only by the ordinary exeModel gateway route.
135
+ // Anthropic is always the exe eval judge. Both providers are installed on
136
+ // every exe agent: exeModelReach can be switched to any route at boot, and
137
+ // between them the routes call both.
137
138
  const extraDeps = [
138
139
  ...plan.deps,
139
- ...(host === "exe"
140
- ? [
141
- "@kybernesis/exe",
142
- ...(model.reach === "default" ? ["@ai-sdk/openai"] : []),
143
- "@ai-sdk/anthropic",
144
- ]
145
- : []),
140
+ ...(host === "exe" ? ["@kybernesis/exe", "@ai-sdk/openai", "@ai-sdk/anthropic"] : []),
146
141
  ];
147
142
  if (extraDeps.length) {
148
143
  console.log(bold(`\n2b Installing for ${channel}/${host}: ${extraDeps.join(", ")} …`));
@@ -39,8 +39,15 @@ export declare function resolveModelScaffold(options: {
39
39
  envReach?: string;
40
40
  model?: string;
41
41
  }): ModelScaffoldConfig;
42
+ /**
43
+ * The route an `exeModelReach` agent takes at boot: KYB_MODEL_REACH when it is
44
+ * one that can run, else the authored `reach`. The same rules as
45
+ * `resolveExeReach` in @kybernesis/exe (declared here so create keeps zero
46
+ * runtime deps); change both together.
47
+ */
48
+ export declare function switchedModelReach(source: string, env: Record<string, string | undefined>): DoctorModelReach | null;
42
49
  /**
43
50
  * Classify the effective route from stable authored helpers first, then Eve's
44
51
  * compiled routing. Provider=anthropic alone is not evidence of a subscription.
45
52
  */
46
- export declare function classifyModelReach(authoredSource: string | null, compiledRouting: CompiledModelRouting): DoctorModelReach;
53
+ export declare function classifyModelReach(authoredSource: string | null, compiledRouting: CompiledModelRouting, env?: Record<string, string | undefined>): DoctorModelReach;
@@ -27,12 +27,46 @@ export function resolveModelScaffold(options) {
27
27
  }
28
28
  return { reach, model };
29
29
  }
30
+ /**
31
+ * The route an `exeModelReach` agent takes at boot: KYB_MODEL_REACH when it is
32
+ * one that can run, else the authored `reach`. The same rules as
33
+ * `resolveExeReach` in @kybernesis/exe (declared here so create keeps zero
34
+ * runtime deps); change both together.
35
+ */
36
+ export function switchedModelReach(source, env) {
37
+ if (!/\bexeModelReach\s*\(/.test(source))
38
+ return null;
39
+ const authored = /\breach\s*:\s*["'](gateway|byok|claude-sub)["']/.exec(source)?.[1];
40
+ if (!authored)
41
+ return { kind: "unresolved", reason: "exeModelReach has no literal default reach to read" };
42
+ const model = env.EXE_MODEL ?? /\bmodel\s*:\s*["']([^"']+)["']/.exec(source)?.[1] ?? "";
43
+ const route = (reach) => {
44
+ if (reach === "gateway")
45
+ return { kind: "exe" };
46
+ if (reach === "claude-sub")
47
+ return { kind: "claude-sub" };
48
+ if (reach !== "byok")
49
+ return null;
50
+ const provider = /^(anthropic|openai)\//.exec(model)?.[1];
51
+ if (!provider)
52
+ return null;
53
+ return env[provider === "anthropic" ? "ANTHROPIC_API_KEY" : "OPENAI_API_KEY"]?.trim()
54
+ ? { kind: "direct-provider", provider }
55
+ : null;
56
+ };
57
+ const requested = env[MODEL_REACH_ENV]?.trim();
58
+ const chosen = requested ? route(requested) : null;
59
+ return chosen ?? route(authored) ?? { kind: "direct-provider", ...(/^(anthropic|openai)\//.exec(model) ? { provider: model.split("/")[0] } : {}) };
60
+ }
30
61
  /**
31
62
  * Classify the effective route from stable authored helpers first, then Eve's
32
63
  * compiled routing. Provider=anthropic alone is not evidence of a subscription.
33
64
  */
34
- export function classifyModelReach(authoredSource, compiledRouting) {
65
+ export function classifyModelReach(authoredSource, compiledRouting, env = {}) {
35
66
  if (authoredSource !== null) {
67
+ const switched = switchedModelReach(authoredSource, env);
68
+ if (switched)
69
+ return switched;
36
70
  const claudeSubscription = /\bclaudeSubscription\s*\(/.test(authoredSource);
37
71
  const exeModel = /\bexeModel\s*\(/.test(authoredSource);
38
72
  const grokSubscription = /\bgrokSubscription\s*\(/.test(authoredSource);
package/dist/templates.js CHANGED
@@ -392,33 +392,38 @@ export default discordChannel({ botToken: process.env.DISCORD_BOT_TOKEN! });
392
392
  }
393
393
  /** One renderer for every model-bearing generated agent, root or subagent. */
394
394
  function agentModelTemplate(host, config) {
395
- if (config.reach === "claude-sub") {
396
- if (host !== "exe")
397
- throw new Error("claude-sub model templates require an exe host");
398
- return {
399
- imports: `import { createAnthropic } from "@ai-sdk/anthropic";\n` +
400
- `import { CLAUDE_SUBSCRIPTION_CONTEXT_WINDOW, claudeSubscription } from "@kybernesis/exe";\n\n`,
401
- fields: ` model: claudeSubscription({ model: ${JSON.stringify(config.model)}, createAnthropic }),\n` +
402
- ` modelContextWindowTokens: CLAUDE_SUBSCRIPTION_CONTEXT_WINDOW,\n`,
403
- };
404
- }
395
+ if (config.reach === "claude-sub" && host !== "exe")
396
+ throw new Error("claude-sub model templates require an exe host");
405
397
  if (host === "exe") {
398
+ /**
399
+ * Every exe agent takes the runtime switch: KYB_MODEL_REACH (gateway, byok
400
+ * or claude-sub) picks the route at boot, so the setup route can change it
401
+ * by writing env. `reach` here is what runs while it is unset, which is
402
+ * exactly what this scaffold used to hard-code.
403
+ *
404
+ * The subscription scaffold still names a gateway id, used only if the
405
+ * agent is switched to the gateway or BYOK with no EXE_MODEL set.
406
+ */
407
+ const defaults = config.reach === "claude-sub"
408
+ ? `reach: "claude-sub", model: ${JSON.stringify(`anthropic/${config.model}`)}`
409
+ : `reach: "gateway", model: ${JSON.stringify(config.model)}`;
406
410
  return {
407
- imports: `import { createOpenAI } from "@ai-sdk/openai";\n` +
408
- `import { exeModel } from "@kybernesis/exe";\n\n`,
409
- fields: ` model: exeModel({ model: process.env.EXE_MODEL ?? ${JSON.stringify(config.model)}, createOpenAI }),\n` +
410
- ` modelContextWindowTokens: 200_000,\n`,
411
+ imports: `import { createAnthropic } from "@ai-sdk/anthropic";\n` +
412
+ `import { createOpenAI } from "@ai-sdk/openai";\n` +
413
+ `import { exeModelReach } from "@kybernesis/exe";\n\n`,
414
+ fields: ` ...exeModelReach({ ${defaults}, createOpenAI, createAnthropic }),\n`,
411
415
  };
412
416
  }
413
417
  return { imports: "", fields: ` model: ${JSON.stringify(config.model)},\n` };
414
418
  }
415
419
  export function hostAgentTs(host, model) {
416
420
  const rendered = agentModelTemplate(host, model);
417
- const comment = model.reach === "claude-sub"
418
- ? `// Claude subscription model through the loopback OAuth proxy — no API key or exe gateway.\n`
419
- : host === "exe"
420
- ? `// Model served by the exe.dev LLM integration — no provider key on the host.\n// exe injects the credential (managed gateway, your API key, or a connected\n// ChatGPT subscription) server-side.\n`
421
- : "";
421
+ const comment = host !== "exe"
422
+ ? ""
423
+ : (model.reach === "claude-sub"
424
+ ? `// By default a Claude subscription through the loopback OAuth proxy — no API key or exe gateway.\n`
425
+ : `// By default the exe.dev LLM integration — no provider key on the host; exe\n// injects the credential (managed gateway, your API key, or a connected\n// ChatGPT subscription) server-side.\n`) +
426
+ `// KYB_MODEL_REACH (gateway | byok | claude-sub) switches the route at boot\n// without editing this file; EXE_MODEL names the gateway or BYOK model.\n`;
422
427
  return `import { defineAgent } from "eve";
423
428
  ${rendered.imports}${comment}export default defineAgent({
424
429
  ${rendered.fields}${host === "exe" ? ` // This long-lived self-hosted process has no metered platform session ceiling.\n limits: { maxInputTokensPerSession: false },\n` : ""}});
package/dist/upgrade.js CHANGED
@@ -607,7 +607,7 @@ export async function preflightEvalGate(cwd) {
607
607
  const reach = classifyModelReach(authoredSource, inspection?.modelRouting ?? {
608
608
  kind: "unresolved",
609
609
  reason: "eve info failed before compiled model routing could be inspected",
610
- });
610
+ }, env);
611
611
  const reasons = [];
612
612
  switch (reach.kind) {
613
613
  case "claude-sub":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kybernesis/create",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "description": "The Kybernesis agent scaffolder and FDE toolkit: one command to a governed, remembering, multiplayer, self-testing eve agent \u2014 plus doctor and upgrade.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -82,8 +82,22 @@ then `eve add @kybernesis/<item>`). Each covers one axis:
82
82
  - **manage** — the other direction: `manageChannel()` lets Studio install
83
83
  capabilities and write schedules onto a running agent, and `routineTools()`
84
84
  turns "every morning at 8, brief me" into a real schedule file. This is how a
85
- routine gets created from chat without anyone touching the repo.
86
- - **exe** — running off Vercel. `exeModel()` for exe.dev's LLM integration,
85
+ routine gets created from chat without anyone touching the repo. Since 0.11
86
+ it also carries the owner's setup routes (`GET /eve/v1/kyb/setup`,
87
+ `POST .../setup/env`, `POST .../setup/action`): a `manage` grant only;
88
+ allowlisted env (`EXE_MODEL`, `EXE_LLM_URL`, `ANTHROPIC_API_KEY`,
89
+ `OPENAI_API_KEY`, `SLACK_BOT_TOKEN`, `SLACK_SIGNING_SECRET`) and the actions
90
+ `claude.login` (link, then the code on stdin), `linq.signup`/`linq.verify`,
91
+ `link.login` and `vnc.password`. Every change applies at one restart that
92
+ waits until no conversation is in flight (deploy.sh's count). A BYOK key only
93
+ takes effect when `agent/agent.ts` uses that provider; status says so.
94
+ Since 0.12 it also takes `KYB_MODEL_REACH` (`gateway`, `byok`,
95
+ `claude-sub`) for agents authored with exe's `exeModelReach()`, and status
96
+ reports the route that will boot (with `fallback` when the request can't run).
97
+ - **exe** — running off Vercel. `exeModelReach()` (0.16+, what `kyb init
98
+ --host=exe` writes from create 0.19) picks gateway, BYOK or the Claude
99
+ subscription from `KYB_MODEL_REACH` at boot and falls back to the authored
100
+ default rather than fail to start. `exeModel()` for exe.dev's LLM integration,
87
101
  `grokSubscription()` / `readGrokCredential()` for a SuperGrok or X Premium+
88
102
  login (`grok login` → `~/.grok/auth.json`, a valid bearer for api.x.ai —
89
103
  same shape as eve's `experimental_chatgpt()`), `hostPreflight()`, Photon