@kybernesis/create 0.18.1 → 0.20.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)" };
@@ -289,7 +294,10 @@ export async function doctor() {
289
294
  // iMessage or Telegram should never be told to create a Slack connector.
290
295
  const hasSlackChannel = existsSync(join(cwd, "agent/channels/slack.ts"));
291
296
  if (hasSlackChannel) {
292
- if (env.SLACK_CONNECTOR_UID)
297
+ const slackSource = readFileSync(join(cwd, "agent/channels/slack.ts"), "utf8");
298
+ if (slackSource.includes("controlPlaneSlackCredentials"))
299
+ add("pass", "Slack via the control plane (this agent's own Slack app)", "the owner adds it from the Channels page");
300
+ else if (env.SLACK_CONNECTOR_UID)
293
301
  add("pass", `Slack connector uid: ${env.SLACK_CONNECTOR_UID}`, "verify trigger path /eve/v1/slack (vercel connect list)");
294
302
  else if (env.SLACK_BOT_TOKEN)
295
303
  add("pass", "Slack via portable credentials (SLACK_BOT_TOKEN)");
@@ -653,7 +661,7 @@ export async function doctor() {
653
661
  const reachCheck = modelReachDoctorCheck(rootAgentSource, {
654
662
  kind: "unresolved",
655
663
  reason: "eve info failed before compiled model routing could be inspected",
656
- });
664
+ }, env);
657
665
  add(reachCheck.verdict, reachCheck.label, reachCheck.detail);
658
666
  add("warn", "Eve max input tokens/session: unresolved", "eve info failed before the compiled root policy could be inspected");
659
667
  }
@@ -663,7 +671,7 @@ export async function doctor() {
663
671
  add("pass", `eve discovery clean (${diag.warnings} warnings)`);
664
672
  else
665
673
  add("fail", `eve discovery: ${diag ? `${diag.errors} errors` : "unparsed"}`, "npx eve info --json");
666
- const reachCheck = modelReachDoctorCheck(rootAgentSource, info.modelRouting);
674
+ const reachCheck = modelReachDoctorCheck(rootAgentSource, info.modelRouting, env);
667
675
  add(reachCheck.verdict, reachCheck.label, reachCheck.detail);
668
676
  const limitCheck = agentInputLimitDoctorCheck(info.limit);
669
677
  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
@@ -260,19 +260,23 @@ export function channelPlan(kind, name, host) {
260
260
  return {
261
261
  file: "slack.ts",
262
262
  content: onExe
263
- ? `import { multiplayerSlackChannel } from "@kybernesis/multiplayer/slack";
264
- import { forwardedSocketVerifier } from "@kybernesis/exe/slack";
265
-
266
- // Slack on a self-hosted (exe.dev) host.
267
- // Inbound: a forwarder holds the exe-brokered Socket Mode connection and POSTs
268
- // events here; the verifier authenticates them on SLACK_SOCKET_FORWARDING_SECRET.
269
- // Outbound: SLACK_BOT_TOKEN must be on the host — eve's SlackChannelCredentials
270
- // has no apiUrl, so calls can't route through the exe integration yet.
263
+ ? `import { channelIdentity } from "@kybernesis/enterprise";
264
+ import { controlPlaneSlackCredentials, multiplayerSlackChannel } from "@kybernesis/multiplayer/slack";
265
+
266
+ // Slack as this agent's own Slack app: its own name, @handle and picture in the
267
+ // customer's workspace. The control plane makes the app and keeps its signing
268
+ // secret and bot token in the customer's org; this channel reads them with the
269
+ // agent credential at call time, so installing or removing the app from the
270
+ // Channels page needs no restart and nothing Slack-related lives on this host.
271
+ //
272
+ // Every sender is a control-plane person, never a bare Slack user: an unlinked
273
+ // one gets a private DM with a one-time claim link and nothing else.
274
+ const issuer = process.env.KYBERNESIS_ISSUER ?? "https://agent.kybernesis.ai";
275
+ const credential = process.env.KYBERNESIS_AGENT_CREDENTIAL;
276
+
271
277
  export default multiplayerSlackChannel({
272
- credentials: {
273
- botToken: process.env.SLACK_BOT_TOKEN!,
274
- webhookVerifier: forwardedSocketVerifier(),
275
- },
278
+ credentials: controlPlaneSlackCredentials({ issuer, credential }),
279
+ identity: credential ? channelIdentity({ issuer, credential }) : null,
276
280
  });
277
281
  `
278
282
  : `import { connectSlackCredentials } from "@vercel/connect/eve";
@@ -284,20 +288,13 @@ export default multiplayerSlackChannel({
284
288
  credentials: connectSlackCredentials(process.env.SLACK_CONNECTOR_UID!),
285
289
  });
286
290
  `,
287
- deps: ["@kybernesis/multiplayer", ...(onExe ? ["@kybernesis/exe"] : ["@vercel/connect"])],
291
+ deps: onExe ? ["@kybernesis/multiplayer@^0.6.0", "@kybernesis/enterprise"] : ["@kybernesis/multiplayer", "@vercel/connect"],
288
292
  registryItems: [],
289
- env: onExe
290
- ? [
291
- `SLACK_BOT_TOKEN="xoxb-..." # outbound; from Slack OAuth & Permissions`,
292
- `SLACK_SOCKET_FORWARDING_SECRET="$(openssl rand -hex 24)"`,
293
- ]
294
- : [`SLACK_CONNECTOR_UID="slack/${name}"`],
293
+ env: onExe ? [] : [`SLACK_CONNECTOR_UID="slack/${name}"`],
295
294
  steps: onExe
296
295
  ? [
297
- `Create a Slack app (Socket Mode on; scopes: app_mentions:read, chat:write, channels:history, groups:history, im:history, users:read)`,
298
- `Hold its tokens off-host: ssh exe.dev integrations add slack --name ${name} --bot-token=- --app-token=-`,
299
- `Run the forwarder (scripts/slack-forwarder.py in @kybernesis/exe) with EXE_SLACK_GW set`,
300
- `After ANY scope change, reinstall the app — a stale token silently stops receiving events`,
296
+ `Make the host public so Slack can reach /eve/v1/slack (ssh exe.dev share set-public <vm>)`,
297
+ `The control plane makes this agent's Slack app; the owner adds it to their workspace from the Channels page`,
301
298
  ]
302
299
  : [
303
300
  `vercel connect create slack --triggers --name ${name}`,
@@ -305,40 +302,72 @@ export default multiplayerSlackChannel({
305
302
  ],
306
303
  };
307
304
  case "imessage":
308
- return {
309
- file: "photon.ts",
310
- content: `import { photonIMessageChannel } from "eve/channels/photon";
311
- ${onExe ? `import { photonEnvCredentials } from "@kybernesis/exe/photon";\n` : ""}
305
+ return onExe
306
+ ? {
307
+ file: "linq.ts",
308
+ content: `import { channelIdentity } from "@kybernesis/enterprise";
309
+ import { linqEnvCredentials, linqSenderIdentity } from "@kybernesis/exe/linq";
310
+ import { linqChannel } from "eve/channels/linq";
311
+
312
+ // iMessage and SMS via the customer's own Linq account. Inbound is a signed
313
+ // webhook at /eve/v1/linq. The owner connects a line from the Channels page
314
+ // (the manage linq.signup and linq.verify actions), which writes LINQ_API_KEY
315
+ // and LINQ_WEBHOOK_SECRET and registers this host's webhook.
316
+ //
317
+ // A sender is a control-plane person, never a phone number: an unlinked number
318
+ // gets one text with a claim link and nothing else.
319
+ const issuer = process.env.KYBERNESIS_ISSUER ?? "https://agent.kybernesis.ai";
320
+ const credential = process.env.KYBERNESIS_AGENT_CREDENTIAL;
321
+ const agentName = process.env.KYBERNESIS_AGENT;
322
+
323
+ export default linqChannel({
324
+ credentials: linqEnvCredentials(),
325
+ ...(agentName ? { userName: agentName } : {}),
326
+ onMessage: linqSenderIdentity({
327
+ issuer,
328
+ agentName,
329
+ identity: credential ? channelIdentity({ issuer, credential }) : null,
330
+ }),
331
+ });
332
+ `,
333
+ deps: ["@kybernesis/exe", "@kybernesis/enterprise"],
334
+ registryItems: [],
335
+ env: [],
336
+ steps: [
337
+ `Make the host public so Linq can reach /eve/v1/linq (ssh exe.dev share set-public <vm>)`,
338
+ `The owner connects a line from the Channels page; it signs up their own Linq account with their email`,
339
+ ],
340
+ }
341
+ : {
342
+ file: "photon.ts",
343
+ content: `import { photonIMessageChannel } from "eve/channels/photon";
344
+
312
345
  // iMessage via Photon. Inbound is a plain webhook at /eve/v1/photon; the Photon
313
346
  // signing secret takes precedence over the default Vercel-OIDC verifier, so this
314
347
  // works on any host with a public HTTPS URL.
315
348
  export default photonIMessageChannel({
316
- ${onExe
317
- ? ` credentials: photonEnvCredentials(),`
318
- : ` async credentials() {
349
+ async credentials() {
319
350
  const projectId = process.env.IMESSAGE_PROJECT_ID;
320
351
  const projectSecret = process.env.IMESSAGE_PROJECT_SECRET;
321
352
  if (!projectId || !projectSecret) throw new Error("Photon project credentials are required.");
322
353
  return { projectId, projectSecret };
323
- },`}
354
+ },
324
355
  webhookSecret: process.env.IMESSAGE_WEBHOOK_SECRET,
325
356
  });
326
357
  `,
327
- deps: onExe ? ["@kybernesis/exe"] : [],
328
- registryItems: [],
329
- env: [
330
- `IMESSAGE_PROJECT_ID="..."`,
331
- `IMESSAGE_PROJECT_SECRET="..."`,
332
- `IMESSAGE_WEBHOOK_SECRET="..." # Photon webhook signing secret`,
333
- ],
334
- steps: [
335
- `Create a Photon project and register the phone number (npx eve add channel/photon-imessage walks it)`,
336
- onExe
337
- ? `Make the host public FIRST — webhooks need anonymous access:\n ssh exe.dev share port <vm> 8000 && ssh exe.dev share set-public <vm>`
338
- : `Deploy so the public URL exists`,
339
- `Register a Photon webhook for https://<your-host>/eve/v1/photon and copy its signing secret to IMESSAGE_WEBHOOK_SECRET`,
340
- ],
341
- };
358
+ deps: [],
359
+ registryItems: [],
360
+ env: [
361
+ `IMESSAGE_PROJECT_ID="..."`,
362
+ `IMESSAGE_PROJECT_SECRET="..."`,
363
+ `IMESSAGE_WEBHOOK_SECRET="..." # Photon webhook signing secret`,
364
+ ],
365
+ steps: [
366
+ `Create a Photon project and register the phone number (npx eve add channel/photon-imessage walks it)`,
367
+ `Deploy so the public URL exists`,
368
+ `Register a Photon webhook for https://<your-host>/eve/v1/photon and copy its signing secret to IMESSAGE_WEBHOOK_SECRET`,
369
+ ],
370
+ };
342
371
  case "telegram":
343
372
  return {
344
373
  file: "telegram.ts",
@@ -392,36 +421,38 @@ export default discordChannel({ botToken: process.env.DISCORD_BOT_TOKEN! });
392
421
  }
393
422
  /** One renderer for every model-bearing generated agent, root or subagent. */
394
423
  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
- }
424
+ if (config.reach === "claude-sub" && host !== "exe")
425
+ throw new Error("claude-sub model templates require an exe host");
405
426
  if (host === "exe") {
427
+ /**
428
+ * Every exe agent takes the runtime switch: KYB_MODEL_REACH (gateway, byok
429
+ * or claude-sub) picks the route at boot, so the setup route can change it
430
+ * by writing env. `reach` here is what runs while it is unset, which is
431
+ * exactly what this scaffold used to hard-code.
432
+ *
433
+ * The subscription scaffold still names a gateway id, used only if the
434
+ * agent is switched to the gateway or BYOK with no EXE_MODEL set.
435
+ */
436
+ const defaults = config.reach === "claude-sub"
437
+ ? `reach: "claude-sub", model: ${JSON.stringify(`anthropic/${config.model}`)}`
438
+ : `reach: "gateway", model: ${JSON.stringify(config.model)}`;
406
439
  return {
407
- // createAnthropic: the gateway serves Claude on Messages only, so an
408
- // anthropic/* EXE_MODEL through the OpenAI client answers 404.
409
440
  imports: `import { createAnthropic } from "@ai-sdk/anthropic";\n` +
410
441
  `import { createOpenAI } from "@ai-sdk/openai";\n` +
411
- `import { exeModel } from "@kybernesis/exe";\n\n`,
412
- fields: ` model: exeModel({ model: process.env.EXE_MODEL ?? ${JSON.stringify(config.model)}, createOpenAI, createAnthropic }),\n` +
413
- ` modelContextWindowTokens: 200_000,\n`,
442
+ `import { exeModelReach } from "@kybernesis/exe";\n\n`,
443
+ fields: ` ...exeModelReach({ ${defaults}, createOpenAI, createAnthropic }),\n`,
414
444
  };
415
445
  }
416
446
  return { imports: "", fields: ` model: ${JSON.stringify(config.model)},\n` };
417
447
  }
418
448
  export function hostAgentTs(host, model) {
419
449
  const rendered = agentModelTemplate(host, model);
420
- const comment = model.reach === "claude-sub"
421
- ? `// Claude subscription model through the loopback OAuth proxy — no API key or exe gateway.\n`
422
- : host === "exe"
423
- ? `// 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`
424
- : "";
450
+ const comment = host !== "exe"
451
+ ? ""
452
+ : (model.reach === "claude-sub"
453
+ ? `// By default a Claude subscription through the loopback OAuth proxy — no API key or exe gateway.\n`
454
+ : `// 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`) +
455
+ `// 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`;
425
456
  return `import { defineAgent } from "eve";
426
457
  ${rendered.imports}${comment}export default defineAgent({
427
458
  ${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.1",
3
+ "version": "0.20.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,25 @@ 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. The registry
86
+ item writes `create_routine`, `list_routines` and `delete_routine` into
87
+ `agent/tools/` (sharing `agent/lib/routines.ts`, which reads the restart from
88
+ `agent/channels/kyb.ts`), so `kyb init --studio` agents have them. Since 0.11
89
+ it also carries the owner's setup routes (`GET /eve/v1/kyb/setup`,
90
+ `POST .../setup/env`, `POST .../setup/action`): a `manage` grant only;
91
+ allowlisted env (`EXE_MODEL`, `EXE_LLM_URL`, `ANTHROPIC_API_KEY`,
92
+ `OPENAI_API_KEY`, `SLACK_BOT_TOKEN`, `SLACK_SIGNING_SECRET`) and the actions
93
+ `claude.login` (link, then the code on stdin), `linq.signup`/`linq.verify`,
94
+ `link.login` and `vnc.password`. Every change applies at one restart that
95
+ waits until no conversation is in flight (deploy.sh's count). A BYOK key only
96
+ takes effect when `agent/agent.ts` uses that provider; status says so.
97
+ Since 0.12 it also takes `KYB_MODEL_REACH` (`gateway`, `byok`,
98
+ `claude-sub`) for agents authored with exe's `exeModelReach()`, and status
99
+ reports the route that will boot (with `fallback` when the request can't run).
100
+ - **exe** — running off Vercel. `exeModelReach()` (0.16+, what `kyb init
101
+ --host=exe` writes from create 0.19) picks gateway, BYOK or the Claude
102
+ subscription from `KYB_MODEL_REACH` at boot and falls back to the authored
103
+ default rather than fail to start. `exeModel()` for exe.dev's LLM integration,
87
104
  `grokSubscription()` / `readGrokCredential()` for a SuperGrok or X Premium+
88
105
  login (`grok login` → `~/.grok/auth.json`, a valid bearer for api.x.ai —
89
106
  same shape as eve's `experimental_chatgpt()`), `hostPreflight()`, Photon