@kybernesis/create 0.14.5 → 0.14.7
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/README.md +40 -2
- package/dist/agent-limits.d.ts +2 -0
- package/dist/agent-limits.js +47 -9
- package/dist/cli.js +17 -1
- package/dist/doctor.d.ts +2 -0
- package/dist/doctor.js +28 -0
- package/dist/init.d.ts +2 -0
- package/dist/init.js +41 -9
- package/dist/model-reach.d.ts +44 -0
- package/dist/model-reach.js +52 -0
- package/dist/templates.d.ts +6 -5
- package/dist/templates.js +60 -49
- package/dist/upgrade.js +14 -4
- package/dist/util.d.ts +4 -0
- package/dist/util.js +5 -1
- package/package.json +2 -2
- package/skills/eve-building/SKILL.md +12 -0
package/README.md
CHANGED
|
@@ -30,8 +30,45 @@ Scaffolds the full Kybernesis stack:
|
|
|
30
30
|
the Slack connector browser flow, control-plane registration + grants,
|
|
31
31
|
eval → deploy → the revoke demo)
|
|
32
32
|
|
|
33
|
-
Non-interactive (CI / agents):
|
|
34
|
-
|
|
33
|
+
Non-interactive (CI / agents): pass the name and `--yes`; omitted options use
|
|
34
|
+
the documented defaults.
|
|
35
|
+
|
|
36
|
+
### Model reach
|
|
37
|
+
|
|
38
|
+
The default route is unchanged: Vercel scaffolds author an AI Gateway model id,
|
|
39
|
+
and exe scaffolds use `exeModel()` plus `EXE_MODEL` for the VM's attached LLM
|
|
40
|
+
integration.
|
|
41
|
+
|
|
42
|
+
To bill model calls to a Claude subscription instead of the exe gateway:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
kyb init acme-atlas --host=exe --model-reach=claude-sub --yes
|
|
46
|
+
# Factory/noninteractive equivalent:
|
|
47
|
+
KYB_MODEL_REACH=claude-sub kyb init acme-atlas --host=exe --yes
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
An explicit `--model-reach` takes precedence over `KYB_MODEL_REACH`. The
|
|
51
|
+
`claude-sub` reach is exe-only because its OAuth proxy must run on the same host.
|
|
52
|
+
It currently supports the bare Anthropic id `claude-opus-5` only (the gateway
|
|
53
|
+
form `anthropic/claude-opus-5` is normalized); other model ids are rejected
|
|
54
|
+
because `@kybernesis/exe` certifies its exported context-window constant for that
|
|
55
|
+
model only.
|
|
56
|
+
|
|
57
|
+
The generated root and all generated model-bearing subagents use
|
|
58
|
+
`createAnthropic` from `@ai-sdk/anthropic` with `claudeSubscription()` and
|
|
59
|
+
`CLAUDE_SUBSCRIPTION_CONTEXT_WINDOW` from `@kybernesis/exe`. This is Eve 0.49's
|
|
60
|
+
documented provider-authored AI SDK `LanguageModel` shape (`node_modules/eve/docs/agent-config.md`),
|
|
61
|
+
not a gateway string. Subscription scaffolds do not write or read `EXE_MODEL`.
|
|
62
|
+
On the host, stand up and authenticate the loopback-only proxy:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
bash scripts/claude-subscription.sh up
|
|
66
|
+
bash scripts/claude-subscription.sh login
|
|
67
|
+
bash scripts/claude-subscription.sh status
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The concrete provider and proxy contract is documented in
|
|
71
|
+
`packages/exe/src/claude.ts` and `packages/exe/README.md`.
|
|
35
72
|
|
|
36
73
|
## `kyb doctor`
|
|
37
74
|
|
|
@@ -42,6 +79,7 @@ Run inside an agent project. Checks, with pass/warn/fail per line:
|
|
|
42
79
|
(200 / wrong-key 403 / missing-workspace 404, each with the fix)
|
|
43
80
|
- control-plane issuer JWKS reachable; `KYBERNESIS_AGENT` set
|
|
44
81
|
- Slack connector UID present
|
|
82
|
+
- model reach (`claude-sub`, `exe`, gateway, direct provider, or unresolved)
|
|
45
83
|
- `eve info` discovery clean
|
|
46
84
|
- port 2000 free (a running dev server makes `eve eval` exit early)
|
|
47
85
|
|
package/dist/agent-limits.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CompiledModelRouting } from "./model-reach.js";
|
|
1
2
|
/** Eve 0.49's root default when the compiled config omits the policy. */
|
|
2
3
|
export declare const CERTIFIED_INHERITED_MAX_INPUT_TOKENS_PER_SESSION = 40000000;
|
|
3
4
|
export type AgentInputLimit = {
|
|
@@ -19,6 +20,7 @@ export interface EveInfoInspection {
|
|
|
19
20
|
} | null;
|
|
20
21
|
status: string | null;
|
|
21
22
|
limit: AgentInputLimit;
|
|
23
|
+
modelRouting: CompiledModelRouting;
|
|
22
24
|
}
|
|
23
25
|
/**
|
|
24
26
|
* Read the effective root policy from Eve's compiled artifact, never from
|
package/dist/agent-limits.js
CHANGED
|
@@ -21,7 +21,12 @@ export function parseEveInfoInspection(output, readText = (path) => readFileSync
|
|
|
21
21
|
? info.artifacts.compiledManifest
|
|
22
22
|
: null;
|
|
23
23
|
if (!manifestPath) {
|
|
24
|
-
return {
|
|
24
|
+
return {
|
|
25
|
+
diagnostics,
|
|
26
|
+
status,
|
|
27
|
+
limit: { kind: "unresolved", reason: "compiled manifest path was unavailable" },
|
|
28
|
+
modelRouting: { kind: "unresolved", reason: "compiled manifest path was unavailable" },
|
|
29
|
+
};
|
|
25
30
|
}
|
|
26
31
|
let manifest;
|
|
27
32
|
try {
|
|
@@ -32,31 +37,39 @@ export function parseEveInfoInspection(output, readText = (path) => readFileSync
|
|
|
32
37
|
diagnostics,
|
|
33
38
|
status,
|
|
34
39
|
limit: { kind: "unresolved", reason: `compiled manifest could not be read: ${error.message}` },
|
|
40
|
+
modelRouting: { kind: "unresolved", reason: `compiled manifest could not be read: ${error.message}` },
|
|
35
41
|
};
|
|
36
42
|
}
|
|
37
43
|
if (!isRecord(manifest) || !isRecord(manifest.config)) {
|
|
38
|
-
return {
|
|
44
|
+
return {
|
|
45
|
+
diagnostics,
|
|
46
|
+
status,
|
|
47
|
+
limit: { kind: "unresolved", reason: "compiled root config was unavailable" },
|
|
48
|
+
modelRouting: { kind: "unresolved", reason: "compiled root config was unavailable" },
|
|
49
|
+
};
|
|
39
50
|
}
|
|
51
|
+
const modelRouting = parseModelRouting(manifest.config);
|
|
40
52
|
const limits = manifest.config.limits;
|
|
41
53
|
if (limits === undefined) {
|
|
42
|
-
return inherited(diagnostics, status);
|
|
54
|
+
return inherited(diagnostics, status, modelRouting);
|
|
43
55
|
}
|
|
44
56
|
if (!isRecord(limits)) {
|
|
45
|
-
return { diagnostics, status, limit: { kind: "unresolved", reason: "compiled root limits were malformed" } };
|
|
57
|
+
return { diagnostics, status, limit: { kind: "unresolved", reason: "compiled root limits were malformed" }, modelRouting };
|
|
46
58
|
}
|
|
47
59
|
if (!Object.hasOwn(limits, "maxInputTokensPerSession")) {
|
|
48
|
-
return inherited(diagnostics, status);
|
|
60
|
+
return inherited(diagnostics, status, modelRouting);
|
|
49
61
|
}
|
|
50
62
|
const value = limits.maxInputTokensPerSession;
|
|
51
63
|
if (value === false)
|
|
52
|
-
return { diagnostics, status, limit: { kind: "explicit-uncapped" } };
|
|
64
|
+
return { diagnostics, status, limit: { kind: "explicit-uncapped" }, modelRouting };
|
|
53
65
|
if (typeof value === "number" && Number.isFinite(value) && value > 0) {
|
|
54
|
-
return { diagnostics, status, limit: { kind: "explicit-numeric", value } };
|
|
66
|
+
return { diagnostics, status, limit: { kind: "explicit-numeric", value }, modelRouting };
|
|
55
67
|
}
|
|
56
68
|
return {
|
|
57
69
|
diagnostics,
|
|
58
70
|
status,
|
|
59
71
|
limit: { kind: "unresolved", reason: "compiled maxInputTokensPerSession was neither a positive number nor false" },
|
|
72
|
+
modelRouting,
|
|
60
73
|
};
|
|
61
74
|
}
|
|
62
75
|
/** Compile once through Eve's structured info contract and inspect its artifact. */
|
|
@@ -71,15 +84,40 @@ function parseDiagnostics(value) {
|
|
|
71
84
|
? { errors: value.errors, warnings: value.warnings }
|
|
72
85
|
: null;
|
|
73
86
|
}
|
|
74
|
-
function inherited(diagnostics, status) {
|
|
87
|
+
function inherited(diagnostics, status, modelRouting) {
|
|
75
88
|
return {
|
|
76
89
|
diagnostics,
|
|
77
90
|
status,
|
|
78
91
|
limit: { kind: "inherited", value: CERTIFIED_INHERITED_MAX_INPUT_TOKENS_PER_SESSION },
|
|
92
|
+
modelRouting,
|
|
79
93
|
};
|
|
80
94
|
}
|
|
95
|
+
function parseModelRouting(config) {
|
|
96
|
+
if (config.dynamicModel !== undefined) {
|
|
97
|
+
return { kind: "unresolved", reason: "the root uses dynamic model selection" };
|
|
98
|
+
}
|
|
99
|
+
const model = config.model;
|
|
100
|
+
if (!isRecord(model) || !isRecord(model.routing)) {
|
|
101
|
+
return { kind: "unresolved", reason: "compiled model routing was unavailable" };
|
|
102
|
+
}
|
|
103
|
+
const routing = model.routing;
|
|
104
|
+
if (routing.kind === "gateway")
|
|
105
|
+
return { kind: "gateway" };
|
|
106
|
+
if (routing.kind === "external") {
|
|
107
|
+
return {
|
|
108
|
+
kind: "external",
|
|
109
|
+
...(typeof routing.provider === "string" ? { provider: routing.provider } : {}),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
return { kind: "unresolved", reason: "compiled model routing was unrecognized" };
|
|
113
|
+
}
|
|
81
114
|
function unresolved(reason) {
|
|
82
|
-
return {
|
|
115
|
+
return {
|
|
116
|
+
diagnostics: null,
|
|
117
|
+
status: null,
|
|
118
|
+
limit: { kind: "unresolved", reason },
|
|
119
|
+
modelRouting: { kind: "unresolved", reason },
|
|
120
|
+
};
|
|
83
121
|
}
|
|
84
122
|
function isRecord(value) {
|
|
85
123
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
package/dist/cli.js
CHANGED
|
@@ -60,6 +60,7 @@ function initOptions(rest) {
|
|
|
60
60
|
channel: flag(rest, "channel"),
|
|
61
61
|
host: flag(rest, "host"),
|
|
62
62
|
model: flag(rest, "model"),
|
|
63
|
+
modelReach: flag(rest, "model-reach"),
|
|
63
64
|
subagents: subs === undefined ? undefined : subs.split(',').map((s) => s.trim()).filter(Boolean),
|
|
64
65
|
yes: rest.includes('--yes') || rest.includes('-y'),
|
|
65
66
|
};
|
|
@@ -87,6 +88,20 @@ const COMMANDS = {
|
|
|
87
88
|
function usage(command) {
|
|
88
89
|
if (command && COMMANDS[command]) {
|
|
89
90
|
console.log(`\n ${bold(`kyb ${command}`)} — ${COMMANDS[command]}\n`);
|
|
91
|
+
if (command === "init") {
|
|
92
|
+
console.log(` ${bold("kyb init [name]")} [options]
|
|
93
|
+
--channel=<kind> ${dim("none|slack|imessage|telegram|discord|web")}
|
|
94
|
+
--host=<kind> ${dim("vercel|exe (default: vercel)")}
|
|
95
|
+
--model=<id> ${dim("gateway id; claude-sub requires bare claude-opus-5")}
|
|
96
|
+
--model-reach=<reach> ${dim("claude-sub (exe only; default: host route)")}
|
|
97
|
+
--subagents=a,b ${dim("department subagents")}
|
|
98
|
+
--engineer ${dim("add the engineer layer")}
|
|
99
|
+
--studio ${dim("wire KYBER Studio local/manage routes")}
|
|
100
|
+
--yes, -y ${dim("no prompts")}
|
|
101
|
+
|
|
102
|
+
${dim("Factory env: KYB_MODEL_REACH=claude-sub (the CLI flag takes precedence).")}
|
|
103
|
+
${dim("Claude subscription reach uses the loopback OAuth proxy; it does not read EXE_MODEL.")}\n`);
|
|
104
|
+
}
|
|
90
105
|
return;
|
|
91
106
|
}
|
|
92
107
|
console.log(`\n ${bold("kyb")} ${dim(VERSION)} — the Kybernesis agent CLI\n`);
|
|
@@ -192,7 +207,8 @@ ${dim(" npm i -g @kybernesis/create@latest")}
|
|
|
192
207
|
--channel=<kind> ${dim("none|slack|imessage|telegram|discord|web (default: none)")}
|
|
193
208
|
--host=<kind> ${dim("vercel|exe (default: vercel)")}
|
|
194
209
|
--subagents=a,b ${dim("department subagents (default: none)")}
|
|
195
|
-
--model=<id> ${dim("provider/model-id
|
|
210
|
+
--model=<id> ${dim("provider/model-id; bare claude-opus-5 for claude-sub")}
|
|
211
|
+
--model-reach=<r> ${dim("claude-sub (exe only); env: KYB_MODEL_REACH")}
|
|
196
212
|
--engineer ${dim("add the engineer layer: workshop sandbox + vision dev loop")}
|
|
197
213
|
--studio ${dim("wire for KYBER Studio: local execution + management routes")}
|
|
198
214
|
--yes ${dim("no prompts; take flags and defaults")}
|
package/dist/doctor.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type AgentInputLimit } from "./agent-limits.js";
|
|
2
|
+
import { type CompiledModelRouting } from "./model-reach.js";
|
|
2
3
|
type Verdict = "pass" | "warn" | "fail";
|
|
3
4
|
export interface Check {
|
|
4
5
|
verdict: Verdict;
|
|
@@ -17,6 +18,7 @@ export interface DockerTemplateInspection {
|
|
|
17
18
|
}[];
|
|
18
19
|
}
|
|
19
20
|
export declare function dockerTemplateDoctorChecks(result: DockerTemplateInspection): Check[];
|
|
21
|
+
export declare function modelReachDoctorCheck(authoredSource: string | null, compiledRouting: CompiledModelRouting): Check;
|
|
20
22
|
export declare function agentInputLimitDoctorCheck(limit: AgentInputLimit): Check;
|
|
21
23
|
/**
|
|
22
24
|
* The inspector lives in @kybernesis/exe, next to the runtime that owns the
|
package/dist/doctor.js
CHANGED
|
@@ -4,6 +4,7 @@ import { pathToFileURL } from "node:url";
|
|
|
4
4
|
import { bold, capture, dim, green, parseEnv, red, yellow } from "./util.js";
|
|
5
5
|
import { diagnoseManageRestart, findMatchingAgentServiceUnit } from "./systemd.js";
|
|
6
6
|
import { inspectEveAgent } from "./agent-limits.js";
|
|
7
|
+
import { classifyModelReach } from "./model-reach.js";
|
|
7
8
|
const MARK = {
|
|
8
9
|
pass: green("✓"),
|
|
9
10
|
warn: yellow("!"),
|
|
@@ -31,6 +32,24 @@ export function dockerTemplateDoctorChecks(result) {
|
|
|
31
32
|
detail: issue.detail,
|
|
32
33
|
}));
|
|
33
34
|
}
|
|
35
|
+
export function modelReachDoctorCheck(authoredSource, compiledRouting) {
|
|
36
|
+
const reach = classifyModelReach(authoredSource, compiledRouting);
|
|
37
|
+
switch (reach.kind) {
|
|
38
|
+
case "claude-sub":
|
|
39
|
+
return { verdict: "pass", label: "Model reach: claude-sub (Claude subscription)" };
|
|
40
|
+
case "exe":
|
|
41
|
+
return { verdict: "pass", label: "Model reach: exe (host LLM integration)" };
|
|
42
|
+
case "gateway":
|
|
43
|
+
return { verdict: "pass", label: "Model reach: gateway" };
|
|
44
|
+
case "direct-provider":
|
|
45
|
+
return {
|
|
46
|
+
verdict: "pass",
|
|
47
|
+
label: `Model reach: direct-provider${reach.provider ? ` (${reach.provider})` : ""}`,
|
|
48
|
+
};
|
|
49
|
+
case "unresolved":
|
|
50
|
+
return { verdict: "warn", label: "Model reach: unresolved", detail: reach.reason };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
34
53
|
export function agentInputLimitDoctorCheck(limit) {
|
|
35
54
|
switch (limit.kind) {
|
|
36
55
|
case "explicit-numeric":
|
|
@@ -502,9 +521,16 @@ export async function doctor() {
|
|
|
502
521
|
* `env` here is the same merged view every other check reads: .env.local
|
|
503
522
|
* underneath, the real environment on top.
|
|
504
523
|
*/
|
|
524
|
+
const rootAgentPath = join(cwd, "agent/agent.ts");
|
|
525
|
+
const rootAgentSource = existsSync(rootAgentPath) ? readFileSync(rootAgentPath, "utf8") : null;
|
|
505
526
|
const info = inspectEveAgent(cwd, env);
|
|
506
527
|
if (info === null) {
|
|
507
528
|
add("fail", "eve info failed", "run: set -a && . ./.env.local && set +a && npx eve info --json — the agent's own error is in that output");
|
|
529
|
+
const reachCheck = modelReachDoctorCheck(rootAgentSource, {
|
|
530
|
+
kind: "unresolved",
|
|
531
|
+
reason: "eve info failed before compiled model routing could be inspected",
|
|
532
|
+
});
|
|
533
|
+
add(reachCheck.verdict, reachCheck.label, reachCheck.detail);
|
|
508
534
|
add("warn", "Eve max input tokens/session: unresolved", "eve info failed before the compiled root policy could be inspected");
|
|
509
535
|
}
|
|
510
536
|
else {
|
|
@@ -513,6 +539,8 @@ export async function doctor() {
|
|
|
513
539
|
add("pass", `eve discovery clean (${diag.warnings} warnings)`);
|
|
514
540
|
else
|
|
515
541
|
add("fail", `eve discovery: ${diag ? `${diag.errors} errors` : "unparsed"}`, "npx eve info --json");
|
|
542
|
+
const reachCheck = modelReachDoctorCheck(rootAgentSource, info.modelRouting);
|
|
543
|
+
add(reachCheck.verdict, reachCheck.label, reachCheck.detail);
|
|
516
544
|
const limitCheck = agentInputLimitDoctorCheck(info.limit);
|
|
517
545
|
add(limitCheck.verdict, limitCheck.label, limitCheck.detail);
|
|
518
546
|
}
|
package/dist/init.d.ts
CHANGED
|
@@ -26,6 +26,8 @@ export interface InitOptions {
|
|
|
26
26
|
* actually non-interactive.
|
|
27
27
|
*/
|
|
28
28
|
model?: string;
|
|
29
|
+
/** One-shot route selector. `claude-sub` uses the host-local Claude OAuth proxy. */
|
|
30
|
+
modelReach?: string;
|
|
29
31
|
/** Department subagents. Default NONE. */
|
|
30
32
|
subagents?: string[];
|
|
31
33
|
/** Skip prompts and take the flags/defaults as given. */
|
package/dist/init.js
CHANGED
|
@@ -7,6 +7,7 @@ import { configureArcana } from "./arcana.js";
|
|
|
7
7
|
import { upsertEnv } from "./envfile.js";
|
|
8
8
|
import { systemdRestartCommand } from "./systemd.js";
|
|
9
9
|
import { sandboxCleanupScaffoldFiles } from "./sandbox-cleanup.js";
|
|
10
|
+
import { MODEL_REACH_ENV, resolveModelScaffold } from "./model-reach.js";
|
|
10
11
|
/**
|
|
11
12
|
* The always-installed core. Everything else — channels, subagents, engineer,
|
|
12
13
|
* host bindings — is opt-in, because assuming them means the FDE deletes files
|
|
@@ -18,7 +19,6 @@ const CORE_ITEMS = ["enterprise", "arcana", "evals"];
|
|
|
18
19
|
// self-hosted agent it cannot get an OIDC token and the agent fails to boot.
|
|
19
20
|
const ENGINEER_ITEMS_ALL = ["extension/agent-browser", "extension/github-tools"];
|
|
20
21
|
const ENGINEER_ITEMS_VERCEL = ["connection/vercel"];
|
|
21
|
-
const DEFAULT_MODEL = "anthropic/claude-sonnet-5";
|
|
22
22
|
export async function init(rawName, options = {}) {
|
|
23
23
|
const engineer = options.engineer === true;
|
|
24
24
|
const studio = options.studio === true;
|
|
@@ -59,8 +59,20 @@ export async function init(rawName, options = {}) {
|
|
|
59
59
|
console.error(`Directory ${name}/ already exists.`);
|
|
60
60
|
process.exit(1);
|
|
61
61
|
}
|
|
62
|
+
let model;
|
|
63
|
+
try {
|
|
64
|
+
model = resolveModelScaffold({
|
|
65
|
+
host,
|
|
66
|
+
cliReach: options.modelReach,
|
|
67
|
+
envReach: process.env[MODEL_REACH_ENV],
|
|
68
|
+
model: options.model,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
console.error(red(error.message));
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
62
75
|
const plan = channelPlan(channel, name, host);
|
|
63
|
-
const model = options.model ?? DEFAULT_MODEL;
|
|
64
76
|
console.log(bold(`\n1/6 Scaffolding eve agent (eve@${EVE_VERSION}) …`));
|
|
65
77
|
// eve's scaffold ends by opening its interactive model picker
|
|
66
78
|
// (`eve dev --input /model`), which exits non-zero wherever there is no
|
|
@@ -74,7 +86,19 @@ export async function init(rawName, options = {}) {
|
|
|
74
86
|
// refuses an id it cannot find in the AI Gateway catalog and then creates
|
|
75
87
|
// nothing at all, and an exe-hosted agent takes its model from EXE_MODEL,
|
|
76
88
|
// not from the gateway.
|
|
77
|
-
|
|
89
|
+
// Two guards, both learned on a real Factory build (KYB-521):
|
|
90
|
+
// - AI_AGENT: eve's init hands off to `eve dev --onboard` when it sees no
|
|
91
|
+
// interactive terminal, and that is a server that waits for a person, so a
|
|
92
|
+
// headless `kyb init` never returned. eve skips the handoff and prints its
|
|
93
|
+
// summary when it believes a coding agent launched it; the marker is
|
|
94
|
+
// stripped from eve's own child spawns, so nothing else sees it.
|
|
95
|
+
// - a timeout, because `--yes` cannot reach a choice eve makes internally,
|
|
96
|
+
// and a scaffold that cannot finish must fail, not wait forever.
|
|
97
|
+
run("npx", [`eve@${EVE_VERSION}`, "init", name], {
|
|
98
|
+
allowFail: true,
|
|
99
|
+
env: { AI_AGENT: process.env.AI_AGENT?.trim() || "kybernesis-create" },
|
|
100
|
+
timeoutMs: 20 * 60_000,
|
|
101
|
+
});
|
|
78
102
|
// The real test of that step, since its exit code cannot be trusted.
|
|
79
103
|
if (!existsSync(join(dir, "package.json"))) {
|
|
80
104
|
console.error(red(`\n eve did not create a project in ${dir}. Nothing else can run.`));
|
|
@@ -85,11 +109,17 @@ export async function init(rawName, options = {}) {
|
|
|
85
109
|
for (const item of CORE_ITEMS) {
|
|
86
110
|
run("npx", ["eve", "add", `@kybernesis/${item}`, "--overwrite"], { cwd: dir });
|
|
87
111
|
}
|
|
88
|
-
//
|
|
89
|
-
//
|
|
112
|
+
// Anthropic is always the exe eval judge and is also the claude-sub agent
|
|
113
|
+
// provider. OpenAI is needed only by the ordinary exeModel gateway route.
|
|
90
114
|
const extraDeps = [
|
|
91
115
|
...plan.deps,
|
|
92
|
-
...(host === "exe"
|
|
116
|
+
...(host === "exe"
|
|
117
|
+
? [
|
|
118
|
+
"@kybernesis/exe",
|
|
119
|
+
...(model.reach === "default" ? ["@ai-sdk/openai"] : []),
|
|
120
|
+
"@ai-sdk/anthropic",
|
|
121
|
+
]
|
|
122
|
+
: []),
|
|
93
123
|
];
|
|
94
124
|
if (extraDeps.length) {
|
|
95
125
|
console.log(bold(`\n2b Installing for ${channel}/${host}: ${extraDeps.join(", ")} …`));
|
|
@@ -185,7 +215,8 @@ export async function init(rawName, options = {}) {
|
|
|
185
215
|
* with the model. Empty fails honestly; the host lists its own with
|
|
186
216
|
* curl https://llm.int.exe.xyz/models.json
|
|
187
217
|
*/
|
|
188
|
-
|
|
218
|
+
if (model.reach === "default")
|
|
219
|
+
known.EXE_MODEL = "";
|
|
189
220
|
/**
|
|
190
221
|
* Longer than any turn, because the alternative is answering twice.
|
|
191
222
|
*
|
|
@@ -287,7 +318,7 @@ export async function init(rawName, options = {}) {
|
|
|
287
318
|
for (const dept of depts) {
|
|
288
319
|
const base = join(dir, "agent/subagents", dept);
|
|
289
320
|
mkdirSync(join(base, "connections"), { recursive: true });
|
|
290
|
-
writeFileSync(join(base, "agent.ts"), subagentAgentTs(dept));
|
|
321
|
+
writeFileSync(join(base, "agent.ts"), subagentAgentTs(dept, host, model));
|
|
291
322
|
writeFileSync(join(base, "instructions.md"), subagentInstructionsMd(dept));
|
|
292
323
|
writeFileSync(join(base, "connections/arcana.ts"), subagentArcanaTs(dept));
|
|
293
324
|
if (existsSync(skillsSource)) {
|
|
@@ -328,13 +359,14 @@ export async function init(rawName, options = {}) {
|
|
|
328
359
|
"self-testing (evals)",
|
|
329
360
|
channel === "none" ? null : `${channel} channel`,
|
|
330
361
|
host === "exe" ? "exe.dev host" : null,
|
|
362
|
+
model.reach === "claude-sub" ? "Claude subscription reach" : null,
|
|
331
363
|
studio ? "KYBER Studio (local execution + management routes)" : null,
|
|
332
364
|
engineer ? "engineer subagent (workshop + vision loop)" : null,
|
|
333
365
|
depts.length ? `${depts.length} dept subagent(s)` : null,
|
|
334
366
|
].filter(Boolean);
|
|
335
367
|
const steps = [
|
|
336
368
|
`Arcana: create workspaces (${name}-company, ${name}-eval${depts.map((d) => `, ${name}-${d}`).join("")}) + scoped kb_ keys; fill .env.local from .env.example`,
|
|
337
|
-
...hostSteps(host, name),
|
|
369
|
+
...hostSteps(host, name, model),
|
|
338
370
|
...plan.steps,
|
|
339
371
|
...(engPlan?.steps ?? []),
|
|
340
372
|
`Control plane: register agent "${name}" at ${issuer}/agents + grant the pilot cohort`,
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { HostKind } from "./templates.js";
|
|
2
|
+
export declare const MODEL_REACH_ENV = "KYB_MODEL_REACH";
|
|
3
|
+
export declare const CLAUDE_SUBSCRIPTION_MODEL = "claude-opus-5";
|
|
4
|
+
export type ScaffoldModelReach = "default" | "claude-sub";
|
|
5
|
+
export interface ModelScaffoldConfig {
|
|
6
|
+
reach: ScaffoldModelReach;
|
|
7
|
+
model: string;
|
|
8
|
+
}
|
|
9
|
+
export type CompiledModelRouting = {
|
|
10
|
+
kind: "gateway";
|
|
11
|
+
} | {
|
|
12
|
+
kind: "external";
|
|
13
|
+
provider?: string;
|
|
14
|
+
} | {
|
|
15
|
+
kind: "unresolved";
|
|
16
|
+
reason: string;
|
|
17
|
+
};
|
|
18
|
+
export type DoctorModelReach = {
|
|
19
|
+
kind: "claude-sub";
|
|
20
|
+
} | {
|
|
21
|
+
kind: "exe";
|
|
22
|
+
} | {
|
|
23
|
+
kind: "gateway";
|
|
24
|
+
} | {
|
|
25
|
+
kind: "direct-provider";
|
|
26
|
+
provider?: string;
|
|
27
|
+
} | {
|
|
28
|
+
kind: "unresolved";
|
|
29
|
+
reason: string;
|
|
30
|
+
};
|
|
31
|
+
/** Resolve the one-shot scaffold selector. An explicit CLI value wins over Factory env. */
|
|
32
|
+
export declare function resolveModelReach(cliValue?: string, envValue?: string): ScaffoldModelReach;
|
|
33
|
+
/** Validate host/model compatibility before eve creates any files. */
|
|
34
|
+
export declare function resolveModelScaffold(options: {
|
|
35
|
+
host: HostKind;
|
|
36
|
+
cliReach?: string;
|
|
37
|
+
envReach?: string;
|
|
38
|
+
model?: string;
|
|
39
|
+
}): ModelScaffoldConfig;
|
|
40
|
+
/**
|
|
41
|
+
* Classify the effective route from stable authored helpers first, then Eve's
|
|
42
|
+
* compiled routing. Provider=anthropic alone is not evidence of a subscription.
|
|
43
|
+
*/
|
|
44
|
+
export declare function classifyModelReach(authoredSource: string | null, compiledRouting: CompiledModelRouting): DoctorModelReach;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export const MODEL_REACH_ENV = "KYB_MODEL_REACH";
|
|
2
|
+
export const CLAUDE_SUBSCRIPTION_MODEL = "claude-opus-5";
|
|
3
|
+
/** Resolve the one-shot scaffold selector. An explicit CLI value wins over Factory env. */
|
|
4
|
+
export function resolveModelReach(cliValue, envValue) {
|
|
5
|
+
const selected = cliValue !== undefined ? cliValue : envValue;
|
|
6
|
+
if (selected === undefined)
|
|
7
|
+
return "default";
|
|
8
|
+
if (selected.trim() === "" && cliValue === undefined)
|
|
9
|
+
return "default";
|
|
10
|
+
if (selected.trim() === "claude-sub")
|
|
11
|
+
return "claude-sub";
|
|
12
|
+
throw new Error(`Unsupported model reach ${JSON.stringify(selected)}. Supported values: claude-sub (or omit it for the host default).`);
|
|
13
|
+
}
|
|
14
|
+
/** Validate host/model compatibility before eve creates any files. */
|
|
15
|
+
export function resolveModelScaffold(options) {
|
|
16
|
+
const reach = resolveModelReach(options.cliReach, options.envReach);
|
|
17
|
+
if (reach === "default") {
|
|
18
|
+
return { reach, model: options.model ?? "anthropic/claude-sonnet-5" };
|
|
19
|
+
}
|
|
20
|
+
if (options.host !== "exe") {
|
|
21
|
+
throw new Error("Model reach claude-sub requires --host=exe because its OAuth proxy runs on the same machine as the agent.");
|
|
22
|
+
}
|
|
23
|
+
const model = (options.model ?? CLAUDE_SUBSCRIPTION_MODEL).replace(/^anthropic\//, "");
|
|
24
|
+
if (model !== CLAUDE_SUBSCRIPTION_MODEL) {
|
|
25
|
+
throw new Error(`Model reach claude-sub currently supports only ${CLAUDE_SUBSCRIPTION_MODEL}. ` +
|
|
26
|
+
"That is the model covered by @kybernesis/exe's certified context-window constant; use a bare Anthropic id.");
|
|
27
|
+
}
|
|
28
|
+
return { reach, model };
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Classify the effective route from stable authored helpers first, then Eve's
|
|
32
|
+
* compiled routing. Provider=anthropic alone is not evidence of a subscription.
|
|
33
|
+
*/
|
|
34
|
+
export function classifyModelReach(authoredSource, compiledRouting) {
|
|
35
|
+
if (authoredSource !== null) {
|
|
36
|
+
const claudeSubscription = /\bclaudeSubscription\s*\(/.test(authoredSource);
|
|
37
|
+
const exeModel = /\bexeModel\s*\(/.test(authoredSource);
|
|
38
|
+
if (claudeSubscription && !exeModel)
|
|
39
|
+
return { kind: "claude-sub" };
|
|
40
|
+
if (exeModel && !claudeSubscription)
|
|
41
|
+
return { kind: "exe" };
|
|
42
|
+
if (claudeSubscription && exeModel) {
|
|
43
|
+
return { kind: "unresolved", reason: "root agent authors both claudeSubscription() and exeModel()" };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (compiledRouting.kind === "gateway")
|
|
47
|
+
return { kind: "gateway" };
|
|
48
|
+
if (compiledRouting.kind === "external") {
|
|
49
|
+
return { kind: "direct-provider", ...(compiledRouting.provider ? { provider: compiledRouting.provider } : {}) };
|
|
50
|
+
}
|
|
51
|
+
return { kind: "unresolved", reason: compiledRouting.reason };
|
|
52
|
+
}
|
package/dist/templates.d.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
/** File templates written by `kyb init`. Kept in sync with the proven Kyber patterns. */
|
|
2
|
+
import type { ModelScaffoldConfig } from "./model-reach.js";
|
|
2
3
|
export declare const DEPT_DESCRIPTIONS: Record<string, string>;
|
|
3
4
|
export declare function deptDescription(dept: string): string;
|
|
4
5
|
export declare function identityMd(displayName: string, depts: string[]): string;
|
|
5
|
-
export declare function subagentAgentTs(dept: string): string;
|
|
6
|
+
export declare function subagentAgentTs(dept: string, host: HostKind, model: ModelScaffoldConfig): string;
|
|
6
7
|
export declare function subagentInstructionsMd(dept: string): string;
|
|
7
8
|
export declare function subagentArcanaTs(dept: string): string;
|
|
8
9
|
export declare function rootArcanaTs(): string;
|
|
9
10
|
export declare function evalFileTs(displayName: string, depts: string[]): string;
|
|
10
|
-
export declare function envExample(name: string, depts: string[], issuer: string, channelEnv?: string[], host?: "vercel" | "exe", model?:
|
|
11
|
+
export declare function envExample(name: string, depts: string[], issuer: string, channelEnv?: string[], host?: "vercel" | "exe", model?: ModelScaffoldConfig): string;
|
|
11
12
|
export declare function evalScript(name: string, depts: string[]): string;
|
|
12
13
|
export type ChannelKind = "none" | "slack" | "imessage" | "telegram" | "discord" | "web";
|
|
13
14
|
export declare const CHANNEL_KINDS: ChannelKind[];
|
|
@@ -26,8 +27,8 @@ export interface ChannelPlan {
|
|
|
26
27
|
}
|
|
27
28
|
export declare function channelPlan(kind: ChannelKind, name: string, host: HostKind): ChannelPlan;
|
|
28
29
|
export type HostKind = "vercel" | "exe";
|
|
29
|
-
export declare function hostAgentTs(host: HostKind, model:
|
|
30
|
-
export declare function hostSteps(host: HostKind, name: string): string[];
|
|
30
|
+
export declare function hostAgentTs(host: HostKind, model: ModelScaffoldConfig): string;
|
|
31
|
+
export declare function hostSteps(host: HostKind, name: string, model: ModelScaffoldConfig): string[];
|
|
31
32
|
export interface EngineerPlan {
|
|
32
33
|
files: Array<{
|
|
33
34
|
path: string;
|
|
@@ -36,7 +37,7 @@ export interface EngineerPlan {
|
|
|
36
37
|
deps: string[];
|
|
37
38
|
steps: string[];
|
|
38
39
|
}
|
|
39
|
-
export declare function engineerPlan(host: HostKind, model:
|
|
40
|
+
export declare function engineerPlan(host: HostKind, model: ModelScaffoldConfig): EngineerPlan;
|
|
40
41
|
/**
|
|
41
42
|
* The eval judge, for an exe.dev host.
|
|
42
43
|
*
|
package/dist/templates.js
CHANGED
|
@@ -33,14 +33,14 @@ concrete — chat is not a document editor. Prefer short paragraphs and bullet
|
|
|
33
33
|
lists.
|
|
34
34
|
${delegation}`;
|
|
35
35
|
}
|
|
36
|
-
export function subagentAgentTs(dept) {
|
|
36
|
+
export function subagentAgentTs(dept, host, model) {
|
|
37
|
+
const rendered = agentModelTemplate(host, model);
|
|
37
38
|
return `import { defineAgent } from "eve";
|
|
38
|
-
|
|
39
|
+
${rendered.imports}
|
|
39
40
|
export default defineAgent({
|
|
40
41
|
description:
|
|
41
42
|
${JSON.stringify(deptDescription(dept))},
|
|
42
|
-
|
|
43
|
-
});
|
|
43
|
+
${rendered.fields}});
|
|
44
44
|
`;
|
|
45
45
|
}
|
|
46
46
|
export function subagentInstructionsMd(dept) {
|
|
@@ -164,17 +164,22 @@ ${depts.length ? ` routing: [\n${routing}\n ],\n` : ""}});
|
|
|
164
164
|
* agent's machine.
|
|
165
165
|
*/
|
|
166
166
|
function exeEnvBlock(name, model) {
|
|
167
|
-
|
|
168
|
-
|
|
167
|
+
const gatewayModel = model.reach === "default"
|
|
168
|
+
? `# EXE_MODEL must match the LLM integration you attached to the VM. A ChatGPT
|
|
169
169
|
# subscription serves OpenAI models; the code default will fail against one.
|
|
170
|
-
# Set it explicitly rather than relying on ${model}.
|
|
171
|
-
EXE_MODEL="${model}"
|
|
172
|
-
|
|
170
|
+
# Set it explicitly rather than relying on ${model.model}.
|
|
171
|
+
EXE_MODEL="${model.model}"
|
|
172
|
+
`
|
|
173
|
+
: `# Model reach: Claude subscription through the loopback OAuth proxy.
|
|
174
|
+
# EXE_MODEL is intentionally absent: this agent never calls the exe LLM gateway.
|
|
175
|
+
`;
|
|
176
|
+
return `# Self-hosted host + model (@kybernesis/exe)
|
|
177
|
+
${gatewayModel}# Preview URLs are built from this. There is no default on purpose.
|
|
173
178
|
EXE_VM_NAME="${name}"
|
|
174
179
|
|
|
175
180
|
`;
|
|
176
181
|
}
|
|
177
|
-
export function envExample(name, depts, issuer, channelEnv = [], host = "vercel", model = "") {
|
|
182
|
+
export function envExample(name, depts, issuer, channelEnv = [], host = "vercel", model = { reach: "default", model: "" }) {
|
|
178
183
|
const deptVars = depts
|
|
179
184
|
.map((d) => {
|
|
180
185
|
const upper = d.toUpperCase().replace(/[^A-Z0-9]+/g, "_");
|
|
@@ -354,31 +359,52 @@ export default discordChannel({ botToken: process.env.DISCORD_BOT_TOKEN! });
|
|
|
354
359
|
};
|
|
355
360
|
}
|
|
356
361
|
}
|
|
357
|
-
|
|
362
|
+
/** One renderer for every model-bearing generated agent, root or subagent. */
|
|
363
|
+
function agentModelTemplate(host, config) {
|
|
364
|
+
if (config.reach === "claude-sub") {
|
|
365
|
+
if (host !== "exe")
|
|
366
|
+
throw new Error("claude-sub model templates require an exe host");
|
|
367
|
+
return {
|
|
368
|
+
imports: `import { createAnthropic } from "@ai-sdk/anthropic";\n` +
|
|
369
|
+
`import { CLAUDE_SUBSCRIPTION_CONTEXT_WINDOW, claudeSubscription } from "@kybernesis/exe";\n\n`,
|
|
370
|
+
fields: ` model: claudeSubscription({ model: ${JSON.stringify(config.model)}, createAnthropic }),\n` +
|
|
371
|
+
` modelContextWindowTokens: CLAUDE_SUBSCRIPTION_CONTEXT_WINDOW,\n`,
|
|
372
|
+
};
|
|
373
|
+
}
|
|
358
374
|
if (host === "exe") {
|
|
359
|
-
return
|
|
360
|
-
import { createOpenAI } from "@ai-sdk/openai"
|
|
361
|
-
import { exeModel } from "@kybernesis/exe"
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
// ChatGPT subscription) server-side.
|
|
366
|
-
export default defineAgent({
|
|
367
|
-
model: exeModel({ model: process.env.EXE_MODEL ?? ${JSON.stringify(model)}, createOpenAI }),
|
|
368
|
-
modelContextWindowTokens: 200_000,
|
|
369
|
-
// This long-lived self-hosted process has no metered platform session ceiling.
|
|
370
|
-
limits: { maxInputTokensPerSession: false },
|
|
371
|
-
});
|
|
372
|
-
`;
|
|
375
|
+
return {
|
|
376
|
+
imports: `import { createOpenAI } from "@ai-sdk/openai";\n` +
|
|
377
|
+
`import { exeModel } from "@kybernesis/exe";\n\n`,
|
|
378
|
+
fields: ` model: exeModel({ model: process.env.EXE_MODEL ?? ${JSON.stringify(config.model)}, createOpenAI }),\n` +
|
|
379
|
+
` modelContextWindowTokens: 200_000,\n`,
|
|
380
|
+
};
|
|
373
381
|
}
|
|
382
|
+
return { imports: "", fields: ` model: ${JSON.stringify(config.model)},\n` };
|
|
383
|
+
}
|
|
384
|
+
export function hostAgentTs(host, model) {
|
|
385
|
+
const rendered = agentModelTemplate(host, model);
|
|
386
|
+
const comment = model.reach === "claude-sub"
|
|
387
|
+
? `// Claude subscription model through the loopback OAuth proxy — no API key or exe gateway.\n`
|
|
388
|
+
: host === "exe"
|
|
389
|
+
? `// 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`
|
|
390
|
+
: "";
|
|
374
391
|
return `import { defineAgent } from "eve";
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
model: ${JSON.stringify(model)},
|
|
378
|
-
});
|
|
392
|
+
${rendered.imports}${comment}export default defineAgent({
|
|
393
|
+
${rendered.fields}${host === "exe" ? ` // This long-lived self-hosted process has no metered platform session ceiling.\n limits: { maxInputTokensPerSession: false },\n` : ""}});
|
|
379
394
|
`;
|
|
380
395
|
}
|
|
381
|
-
export function hostSteps(host, name) {
|
|
396
|
+
export function hostSteps(host, name, model) {
|
|
397
|
+
if (host === "exe" && model.reach === "claude-sub") {
|
|
398
|
+
return [
|
|
399
|
+
`Create the VM: ssh exe.dev new --name ${name}`,
|
|
400
|
+
`Start the loopback Claude OAuth proxy: bash scripts/claude-subscription.sh up`,
|
|
401
|
+
`Sign in with the client's Claude subscription: bash scripts/claude-subscription.sh login`,
|
|
402
|
+
`Prove the proxy is signed in and not exposed: bash scripts/claude-subscription.sh status`,
|
|
403
|
+
`Install Node 24 + deps on the VM, fill .env.local, then install the production service:`,
|
|
404
|
+
` bash node_modules/@kybernesis/exe/scripts/install-service.sh`,
|
|
405
|
+
`The systemd unit exports .env.local, builds before every start, and is the single supervisor`,
|
|
406
|
+
];
|
|
407
|
+
}
|
|
382
408
|
if (host === "exe") {
|
|
383
409
|
return [
|
|
384
410
|
`Create the VM: ssh exe.dev new --name ${name}`,
|
|
@@ -507,32 +533,17 @@ export default defineSandbox({
|
|
|
507
533
|
}
|
|
508
534
|
export function engineerPlan(host, model) {
|
|
509
535
|
const onExe = host === "exe";
|
|
536
|
+
const renderedModel = agentModelTemplate(host, model);
|
|
510
537
|
const files = [
|
|
511
538
|
{
|
|
512
539
|
path: "agent/subagents/builder/agent.ts",
|
|
513
|
-
content:
|
|
514
|
-
|
|
515
|
-
import { createOpenAI } from "@ai-sdk/openai";
|
|
516
|
-
import { exeModel } from "@kybernesis/exe";
|
|
517
|
-
|
|
518
|
-
// The specialist the root agent delegates BUILDING to. \`description\` is what
|
|
540
|
+
content: `import { defineAgent } from "eve";
|
|
541
|
+
${renderedModel.imports}// The specialist the root agent delegates BUILDING to. \`description\` is what
|
|
519
542
|
// the root routes on — keep it about building, not answering.
|
|
520
543
|
export default defineAgent({
|
|
521
544
|
description:
|
|
522
545
|
"Builds and runs software: scaffolds projects, writes code, installs dependencies, runs builds and dev servers, and visually verifies rendered pages. Use when the user asks for something to be BUILT, prototyped, deployed, or fixed in code — not for questions, planning, or scheduling.",
|
|
523
|
-
|
|
524
|
-
modelContextWindowTokens: 200_000,
|
|
525
|
-
});
|
|
526
|
-
`
|
|
527
|
-
: `import { defineAgent } from "eve";
|
|
528
|
-
|
|
529
|
-
// The specialist the root agent delegates BUILDING to. \`description\` is what
|
|
530
|
-
// the root routes on — keep it about building, not answering.
|
|
531
|
-
export default defineAgent({
|
|
532
|
-
description:
|
|
533
|
-
"Builds and runs software: scaffolds projects, writes code, installs dependencies, runs builds and dev servers, and visually verifies rendered pages. Use when the user asks for something to be BUILT, prototyped, deployed, or fixed in code — not for questions, planning, or scheduling.",
|
|
534
|
-
model: ${JSON.stringify(model)},
|
|
535
|
-
});
|
|
546
|
+
${renderedModel.fields}});
|
|
536
547
|
`,
|
|
537
548
|
},
|
|
538
549
|
{
|
package/dist/upgrade.js
CHANGED
|
@@ -424,13 +424,16 @@ function dependencyEntries(pkg) {
|
|
|
424
424
|
function rewriteManifest(cwd, pkg, targets) {
|
|
425
425
|
for (const target of targets) {
|
|
426
426
|
const section = pkg[target.section];
|
|
427
|
-
|
|
427
|
+
// eve is pinned exactly: the pin IS the certification. A caret let a
|
|
428
|
+
// clean install resolve eve@0.49.1 against a certified 0.49.0 (blind
|
|
429
|
+
// latest, the one thing kyb upgrade exists to prevent).
|
|
430
|
+
section[target.name] = target.name === "eve" ? target.targetVersion : `^${target.targetVersion}`;
|
|
428
431
|
}
|
|
429
432
|
writeFileSync(join(cwd, "package.json"), `${JSON.stringify(pkg, null, 2)}\n`);
|
|
430
433
|
}
|
|
431
434
|
function recoveryGuidance(targets, bridgeUnit) {
|
|
432
435
|
console.log(`\n${yellow("The dependency tree was not verified. Complete the clean install before using it:")}\n` +
|
|
433
|
-
` 1. Ensure every root @kybernesis/*
|
|
436
|
+
` 1. Ensure every root @kybernesis/* range in package.json is the resolved ^version, and eve is pinned exactly to ${EVE_VERSION}.\n` +
|
|
434
437
|
` 2. ${dim("rm -rf node_modules")}\n` +
|
|
435
438
|
` 3. ${dim("rm -f package-lock.json")}\n` +
|
|
436
439
|
` 4. ${dim("npm install")}\n` +
|
|
@@ -592,8 +595,15 @@ export async function upgrade(options = {}) {
|
|
|
592
595
|
return;
|
|
593
596
|
}
|
|
594
597
|
}
|
|
595
|
-
|
|
596
|
-
|
|
598
|
+
// Outside a clean install, eve goes in with --save-exact for the same
|
|
599
|
+
// reason rewriteManifest pins it: npm would save a caret by default.
|
|
600
|
+
const eveChange = changed.find((spec) => spec.startsWith("eve@"));
|
|
601
|
+
const others = changed.filter((spec) => spec !== eveChange);
|
|
602
|
+
const installArgs = cleanInstall ? ["install"] : ["install", ...others];
|
|
603
|
+
let installed = cleanInstall || others.length > 0 ? commandResult("npm", installArgs, cwd) : { status: 0, stdout: "", stderr: "" };
|
|
604
|
+
if (installed.status === 0 && !cleanInstall && eveChange) {
|
|
605
|
+
installed = commandResult("npm", ["install", "--save-exact", eveChange], cwd);
|
|
606
|
+
}
|
|
597
607
|
if (installed.status !== 0) {
|
|
598
608
|
const output = `${installed.stdout}\n${installed.stderr}`;
|
|
599
609
|
if (/ERESOLVE/i.test(output)) {
|
package/dist/util.d.ts
CHANGED
|
@@ -10,6 +10,10 @@ export declare function run(command: string, args: string[], options?: {
|
|
|
10
10
|
cwd?: string;
|
|
11
11
|
allowFail?: boolean;
|
|
12
12
|
quiet?: boolean;
|
|
13
|
+
/** Extra environment for this one child; the parent's is inherited underneath. */
|
|
14
|
+
env?: Record<string, string>;
|
|
15
|
+
/** Kill the child after this many milliseconds. A command that cannot finish is a failure, not a wait. */
|
|
16
|
+
timeoutMs?: number;
|
|
13
17
|
}): boolean;
|
|
14
18
|
export declare function capture(command: string, args: string[], cwd?: string, extraEnv?: Record<string, string>): string | null;
|
|
15
19
|
export declare function ask(question: string, fallback: string): Promise<string>;
|
package/dist/util.js
CHANGED
|
@@ -15,8 +15,12 @@ export function run(command, args, options) {
|
|
|
15
15
|
const result = spawnSync(command, args, {
|
|
16
16
|
cwd: options?.cwd,
|
|
17
17
|
stdio: options?.quiet ? "pipe" : "inherit",
|
|
18
|
-
env: process.env,
|
|
18
|
+
env: { ...process.env, ...options?.env },
|
|
19
|
+
timeout: options?.timeoutMs,
|
|
19
20
|
});
|
|
21
|
+
if (result.error && result.error.code === "ETIMEDOUT") {
|
|
22
|
+
console.error(red(`Command did not finish within ${Math.round((options?.timeoutMs ?? 0) / 60_000)} min and was stopped: ${command} ${args.join(" ")}`));
|
|
23
|
+
}
|
|
20
24
|
const ok = result.status === 0;
|
|
21
25
|
if (!ok && !options?.allowFail) {
|
|
22
26
|
console.error(red(`Command failed: ${command} ${args.join(" ")}`));
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kybernesis/create",
|
|
3
|
-
"version": "0.14.
|
|
4
|
-
"description": "The Kybernesis agent scaffolder and FDE toolkit: one command to a governed, remembering, multiplayer, self-testing eve agent
|
|
3
|
+
"version": "0.14.7",
|
|
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",
|
|
7
7
|
"homepage": "https://kybernesis.ai",
|
|
@@ -25,6 +25,18 @@ Direct provider: install `@ai-sdk/<provider>`, pass `anthropic("claude-opus-4-8"
|
|
|
25
25
|
`defineDynamic({ fallback, events })` — prefer `session.started` scope (prompt
|
|
26
26
|
caches are per model). Docs: `agent-config.md`.
|
|
27
27
|
|
|
28
|
+
For an exe-hosted agent that must use a client's Claude subscription instead of
|
|
29
|
+
the exe LLM gateway, scaffold with `--host=exe --model-reach=claude-sub` (or set
|
|
30
|
+
`KYB_MODEL_REACH=claude-sub` for Factory invocation; the CLI flag wins). The
|
|
31
|
+
certified shape is `createAnthropic` from `@ai-sdk/anthropic` passed to
|
|
32
|
+
`claudeSubscription({ model: "claude-opus-5", createAnthropic })`, with
|
|
33
|
+
`CLAUDE_SUBSCRIPTION_CONTEXT_WINDOW`, both from `@kybernesis/exe`. Use the bare
|
|
34
|
+
Anthropic id, never `anthropic/...`; do not reuse that context-window constant
|
|
35
|
+
for another model. The generated agent does not use `exeModel` or `EXE_MODEL`.
|
|
36
|
+
Run `scripts/claude-subscription.sh up`, `login`, then `status` on the host; the
|
|
37
|
+
OAuth proxy must stay loopback-only. Provider details:
|
|
38
|
+
`packages/exe/src/claude.ts` and `packages/exe/README.md`.
|
|
39
|
+
|
|
28
40
|
## Channels (agent/channels/, one file per surface)
|
|
29
41
|
|
|
30
42
|
Filename = channel id. eve normalizes every surface into one runtime — tools/
|