@kybernesis/create 0.1.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/LICENSE +202 -0
- package/NOTICE +2 -0
- package/README.md +66 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +47 -0
- package/dist/doctor.d.ts +1 -0
- package/dist/doctor.js +119 -0
- package/dist/init.d.ts +1 -0
- package/dist/init.js +81 -0
- package/dist/templates.d.ts +11 -0
- package/dist/templates.js +156 -0
- package/dist/upgrade.d.ts +1 -0
- package/dist/upgrade.js +52 -0
- package/dist/util.d.ts +19 -0
- package/dist/util.js +55 -0
- package/package.json +29 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
package/NOTICE
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# @kybernesis/create — the `kyb` CLI
|
|
2
|
+
|
|
3
|
+
One command from empty directory to a **governed, remembering, multiplayer,
|
|
4
|
+
self-testing** [eve](https://eve.dev) agent — plus the preflight and upgrade
|
|
5
|
+
tooling a forward-deployed engineer uses across an engagement's life.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm create @kybernesis acme-atlas
|
|
9
|
+
# or, installed globally / via npx:
|
|
10
|
+
npx @kybernesis/create init acme-atlas
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## `kyb init [name]`
|
|
14
|
+
|
|
15
|
+
Scaffolds the full Kybernesis stack:
|
|
16
|
+
|
|
17
|
+
1. `eve init` (pinned eve version) → base agent
|
|
18
|
+
2. Adds the Kybernesis registry + all four packages
|
|
19
|
+
(`enterprise`, `arcana`, `multiplayer`, `evals`)
|
|
20
|
+
3. Prompts for display name, **department subagents**, and issuer — then
|
|
21
|
+
generates each department: routing description, instructions, its own
|
|
22
|
+
Arcana connection (per-dept workspace + key envs, eval-key fallback), and
|
|
23
|
+
the three memory skills copied from the installed arcana package
|
|
24
|
+
4. Writes the identity instructions, the root memory mount
|
|
25
|
+
(company/DM split + hermetic-eval key switching), the configured
|
|
26
|
+
`evals/kybernesis.eval.ts`, `.env.example`, and the hermetic
|
|
27
|
+
`npm run eval` script
|
|
28
|
+
5. Verifies: typecheck + `eve info` discovery
|
|
29
|
+
6. Prints the human-steps checklist (Arcana workspaces/keys, `vercel link`,
|
|
30
|
+
the Slack connector browser flow, control-plane registration + grants,
|
|
31
|
+
eval → deploy → the revoke demo)
|
|
32
|
+
|
|
33
|
+
Non-interactive (CI / agents): pipe stdin from `/dev/null` and pass the name —
|
|
34
|
+
all prompts take defaults (departments: finance, marketing, engineering).
|
|
35
|
+
|
|
36
|
+
## `kyb doctor`
|
|
37
|
+
|
|
38
|
+
Run inside an agent project. Checks, with pass/warn/fail per line:
|
|
39
|
+
|
|
40
|
+
- all four `@kybernesis/*` packages installed
|
|
41
|
+
- **every Arcana key↔workspace pair validated read-only against the live API**
|
|
42
|
+
(200 / wrong-key 403 / missing-workspace 404, each with the fix)
|
|
43
|
+
- control-plane issuer JWKS reachable; `KYBERNESIS_AGENT` set
|
|
44
|
+
- Slack connector UID present
|
|
45
|
+
- `eve info` discovery clean
|
|
46
|
+
- port 2000 free (a running dev server makes `eve eval` exit early)
|
|
47
|
+
|
|
48
|
+
Exit code 1 on any failure — usable as a CI preflight.
|
|
49
|
+
|
|
50
|
+
## `kyb upgrade [--skip-eval]`
|
|
51
|
+
|
|
52
|
+
Compares installed `@kybernesis/*` versions against npm, installs what's
|
|
53
|
+
behind, typechecks, then **runs the eval suite as the gate** — prints
|
|
54
|
+
"deploy" only on green. This is the maintenance-retainer loop as a command:
|
|
55
|
+
package improvement → `kyb upgrade` per client → green → `npx eve deploy`.
|
|
56
|
+
|
|
57
|
+
## Design notes
|
|
58
|
+
|
|
59
|
+
- **Zero runtime dependencies** — node builtins only (`spawnSync`, `readline`,
|
|
60
|
+
global `fetch`). Nothing to audit, nothing to break.
|
|
61
|
+
- The scaffold pins the eve version the packages are developed against;
|
|
62
|
+
upgrading eve is a deliberate, eval-gated step — not something a scaffold
|
|
63
|
+
should do implicitly.
|
|
64
|
+
- Human steps stay human: Slack's browser flow, key creation, and admin grants
|
|
65
|
+
can't be automated away — the CLI sequences everything around them and hands
|
|
66
|
+
you the checklist. Don't promise zero-touch.
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* kyb — the Kybernesis agent scaffolder and FDE toolkit.
|
|
4
|
+
*
|
|
5
|
+
* kyb init [name] scaffold a governed, remembering, multiplayer,
|
|
6
|
+
* self-testing eve agent (also: npm create @kybernesis)
|
|
7
|
+
* kyb doctor preflight an agent project: keys, issuer, envs, discovery
|
|
8
|
+
* kyb upgrade bump @kybernesis/* to latest, gated on the eval suite
|
|
9
|
+
* --skip-eval skip the eval gate (not for production changes)
|
|
10
|
+
*/
|
|
11
|
+
import { bold, dim } from "./util.js";
|
|
12
|
+
import { init } from "./init.js";
|
|
13
|
+
import { doctor } from "./doctor.js";
|
|
14
|
+
import { upgrade } from "./upgrade.js";
|
|
15
|
+
const [, , command, ...rest] = process.argv;
|
|
16
|
+
switch (command) {
|
|
17
|
+
case "init":
|
|
18
|
+
await init(rest.find((a) => !a.startsWith("-")));
|
|
19
|
+
break;
|
|
20
|
+
case "doctor":
|
|
21
|
+
await doctor();
|
|
22
|
+
break;
|
|
23
|
+
case "upgrade":
|
|
24
|
+
await upgrade(rest.includes("--skip-eval"));
|
|
25
|
+
break;
|
|
26
|
+
case undefined:
|
|
27
|
+
// `npm create @kybernesis <name>` lands here with the name in rest? No —
|
|
28
|
+
// npm create passes extra args as argv; treat a bare arg as init's name.
|
|
29
|
+
await init(undefined);
|
|
30
|
+
break;
|
|
31
|
+
default:
|
|
32
|
+
if (!command.startsWith("-")) {
|
|
33
|
+
// `npm create @kybernesis acme-agent` → argv[2] is the name.
|
|
34
|
+
await init(command);
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
console.log(`
|
|
38
|
+
${bold("kyb")} — Kybernesis agent scaffolder & FDE toolkit
|
|
39
|
+
|
|
40
|
+
${bold("kyb init [name]")} scaffold a full Kybernesis eve agent
|
|
41
|
+
${bold("kyb doctor")} preflight checks (keys, issuer, envs, discovery)
|
|
42
|
+
${bold("kyb upgrade")} bump @kybernesis/* packages, gated on evals
|
|
43
|
+
--skip-eval ${dim("skip the eval gate")}
|
|
44
|
+
|
|
45
|
+
Registry: https://registry.kybernesis.ai · Docs: the FDE playbook
|
|
46
|
+
`);
|
|
47
|
+
}
|
package/dist/doctor.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function doctor(): Promise<void>;
|
package/dist/doctor.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { bold, capture, dim, green, parseEnv, red, yellow } from "./util.js";
|
|
4
|
+
const MARK = {
|
|
5
|
+
pass: green("✓"),
|
|
6
|
+
warn: yellow("!"),
|
|
7
|
+
fail: red("✗"),
|
|
8
|
+
};
|
|
9
|
+
async function head(url, headers) {
|
|
10
|
+
try {
|
|
11
|
+
const res = await fetch(url, { headers, signal: AbortSignal.timeout(6000) });
|
|
12
|
+
return res.status;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export async function doctor() {
|
|
19
|
+
const cwd = process.cwd();
|
|
20
|
+
const checks = [];
|
|
21
|
+
const add = (verdict, label, detail) => checks.push({ verdict, label, detail });
|
|
22
|
+
// ── project shape ──────────────────────────────────────────────────────
|
|
23
|
+
if (!existsSync(join(cwd, "agent"))) {
|
|
24
|
+
console.error(red("Not an eve agent project (no agent/ directory). Run inside the agent repo."));
|
|
25
|
+
process.exit(2);
|
|
26
|
+
}
|
|
27
|
+
const pkg = JSON.parse(readFileSync(join(cwd, "package.json"), "utf8"));
|
|
28
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
29
|
+
for (const p of ["@kybernesis/arcana", "@kybernesis/enterprise", "@kybernesis/multiplayer", "@kybernesis/evals"]) {
|
|
30
|
+
if (deps[p])
|
|
31
|
+
add("pass", `${p} ${deps[p]}`);
|
|
32
|
+
else
|
|
33
|
+
add("warn", `${p} not installed`, `eve add @kybernesis/${p.split("/")[1]}`);
|
|
34
|
+
}
|
|
35
|
+
// ── env ────────────────────────────────────────────────────────────────
|
|
36
|
+
const envPath = join(cwd, ".env.local");
|
|
37
|
+
const env = {
|
|
38
|
+
...(existsSync(envPath) ? parseEnv(readFileSync(envPath, "utf8")) : {}),
|
|
39
|
+
...process.env,
|
|
40
|
+
};
|
|
41
|
+
if (!existsSync(envPath))
|
|
42
|
+
add("warn", ".env.local missing", "copy .env.example and fill it");
|
|
43
|
+
// ── arcana keys: validate every key↔workspace pair read-only ───────────
|
|
44
|
+
const arcanaPairs = [];
|
|
45
|
+
if (env.ARCANA_API_KEY && env.ARCANA_COMPANY_WORKSPACE)
|
|
46
|
+
arcanaPairs.push({ key: env.ARCANA_API_KEY, ws: env.ARCANA_COMPANY_WORKSPACE, label: "company brain" });
|
|
47
|
+
if (env.ARCANA_EVAL_API_KEY) {
|
|
48
|
+
const evalWs = /ARCANA_COMPANY_WORKSPACE=([a-z0-9-]+)-eval|([a-z0-9-]+)-eval/.exec(pkg.scripts?.eval ?? "");
|
|
49
|
+
const ws = evalWs?.[1] ? `${evalWs[1]}-eval` : evalWs?.[2] ? `${evalWs[2]}-eval` : null;
|
|
50
|
+
if (ws)
|
|
51
|
+
arcanaPairs.push({ key: env.ARCANA_EVAL_API_KEY, ws, label: "eval workspace" });
|
|
52
|
+
}
|
|
53
|
+
for (const [k, v] of Object.entries(env)) {
|
|
54
|
+
const m = /^ARCANA_([A-Z0-9_]+)_API_KEY$/.exec(k);
|
|
55
|
+
if (m && m[1] !== "EVAL" && v) {
|
|
56
|
+
const ws = env[`ARCANA_${m[1]}_WORKSPACE`];
|
|
57
|
+
if (ws)
|
|
58
|
+
arcanaPairs.push({ key: v, ws, label: `${m[1].toLowerCase()} brain` });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (arcanaPairs.length === 0)
|
|
62
|
+
add("warn", "no Arcana key+workspace pairs configured");
|
|
63
|
+
for (const pair of arcanaPairs) {
|
|
64
|
+
const status = await head(`https://api.arcana.kybernesis.ai/brain/${pair.ws}/timeline?limit=1`, { Authorization: `Bearer ${pair.key}`, "X-Kyberagent-Agent": pair.ws });
|
|
65
|
+
if (status === 200)
|
|
66
|
+
add("pass", `Arcana ${pair.label} (${pair.ws})`);
|
|
67
|
+
else if (status === 403)
|
|
68
|
+
add("fail", `Arcana ${pair.label}: key not scoped to "${pair.ws}"`, "wrong key for this workspace");
|
|
69
|
+
else if (status === 404)
|
|
70
|
+
add("fail", `Arcana ${pair.label}: workspace "${pair.ws}" does not exist`, "create it in Arcana");
|
|
71
|
+
else
|
|
72
|
+
add("fail", `Arcana ${pair.label} (${pair.ws}): HTTP ${status ?? "unreachable"}`);
|
|
73
|
+
}
|
|
74
|
+
// ── control plane ──────────────────────────────────────────────────────
|
|
75
|
+
if (env.KYBERNESIS_ISSUER) {
|
|
76
|
+
const status = await head(`${env.KYBERNESIS_ISSUER.replace(/\/$/, "")}/api/jwks`);
|
|
77
|
+
if (status === 200)
|
|
78
|
+
add("pass", `issuer JWKS reachable (${env.KYBERNESIS_ISSUER})`);
|
|
79
|
+
else
|
|
80
|
+
add("fail", `issuer JWKS: HTTP ${status ?? "unreachable"} (${env.KYBERNESIS_ISSUER})`);
|
|
81
|
+
if (!env.KYBERNESIS_AGENT)
|
|
82
|
+
add("fail", "KYBERNESIS_AGENT not set", "must equal the control-plane agent name");
|
|
83
|
+
else
|
|
84
|
+
add("pass", `governed as "${env.KYBERNESIS_AGENT}"`, "confirm it's registered + granted in the admin");
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
add("warn", "KYBERNESIS_ISSUER not set", "agent is not control-plane governed");
|
|
88
|
+
}
|
|
89
|
+
// ── slack ──────────────────────────────────────────────────────────────
|
|
90
|
+
if (env.SLACK_CONNECTOR_UID)
|
|
91
|
+
add("pass", `Slack connector uid: ${env.SLACK_CONNECTOR_UID}`, "verify trigger path /eve/v1/slack (vercel connect list)");
|
|
92
|
+
else
|
|
93
|
+
add("warn", "SLACK_CONNECTOR_UID not set", "vercel connect create slack --triggers");
|
|
94
|
+
// ── eve discovery + local port ─────────────────────────────────────────
|
|
95
|
+
const info = capture("npx", ["eve", "info"], cwd);
|
|
96
|
+
if (info === null)
|
|
97
|
+
add("fail", "eve info failed", "run npx eve info for details");
|
|
98
|
+
else {
|
|
99
|
+
const diag = /Diagnostics\s+(\d+) errors?, (\d+) warnings?/.exec(info);
|
|
100
|
+
if (diag && diag[1] === "0")
|
|
101
|
+
add("pass", `eve discovery clean (${diag[2]} warnings)`);
|
|
102
|
+
else
|
|
103
|
+
add("fail", `eve discovery: ${diag ? `${diag[1]} errors` : "unparsed"}`, "npx eve info");
|
|
104
|
+
}
|
|
105
|
+
const portBusy = capture("lsof", ["-ti", ":2000"]);
|
|
106
|
+
if (portBusy && portBusy.trim())
|
|
107
|
+
add("warn", "port 2000 in use", "eve eval exits early while a dev server runs — kill it first");
|
|
108
|
+
else
|
|
109
|
+
add("pass", "port 2000 free (eve eval can boot its own server)");
|
|
110
|
+
// ── report ─────────────────────────────────────────────────────────────
|
|
111
|
+
console.log(bold("\nkyb doctor\n"));
|
|
112
|
+
for (const c of checks) {
|
|
113
|
+
console.log(` ${MARK[c.verdict]} ${c.label}${c.detail ? dim(` — ${c.detail}`) : ""}`);
|
|
114
|
+
}
|
|
115
|
+
const fails = checks.filter((c) => c.verdict === "fail").length;
|
|
116
|
+
const warns = checks.filter((c) => c.verdict === "warn").length;
|
|
117
|
+
console.log(`\n ${fails ? red(`${fails} failing`) : green("0 failing")}, ${warns ? yellow(`${warns} warnings`) : "0 warnings"}\n`);
|
|
118
|
+
process.exit(fails ? 1 : 0);
|
|
119
|
+
}
|
package/dist/init.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function init(rawName: string | undefined): Promise<void>;
|
package/dist/init.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { DEFAULT_ISSUER, EVE_VERSION, REGISTRY_URL, ask, bold, closePrompts, green, run, slug, yellow, } from "./util.js";
|
|
4
|
+
import { envExample, evalFileTs, evalScript, identityMd, rootArcanaTs, subagentAgentTs, subagentArcanaTs, subagentInstructionsMd, } from "./templates.js";
|
|
5
|
+
const ITEMS = ["enterprise", "arcana", "multiplayer", "evals"];
|
|
6
|
+
export async function init(rawName) {
|
|
7
|
+
const name = slug(rawName ?? (await ask("Agent name (kebab-case)?", "acme-agent")));
|
|
8
|
+
if (!name) {
|
|
9
|
+
console.error("An agent name is required.");
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
const displayName = await ask("Display name (what employees call it)?", name);
|
|
13
|
+
const deptsRaw = await ask("Department subagents (comma-separated, empty for none)?", "finance,marketing,engineering");
|
|
14
|
+
const depts = deptsRaw
|
|
15
|
+
.split(",")
|
|
16
|
+
.map((d) => slug(d))
|
|
17
|
+
.filter(Boolean);
|
|
18
|
+
const issuer = await ask("Control-plane issuer?", DEFAULT_ISSUER);
|
|
19
|
+
closePrompts();
|
|
20
|
+
const dir = resolve(process.cwd(), name);
|
|
21
|
+
if (existsSync(dir)) {
|
|
22
|
+
console.error(`Directory ${name}/ already exists.`);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
console.log(bold(`\n1/6 Scaffolding eve agent (eve@${EVE_VERSION}) …`));
|
|
26
|
+
run("npx", [`eve@${EVE_VERSION}`, "init", name]);
|
|
27
|
+
console.log(bold("\n2/6 Adding the Kybernesis registry + packages …"));
|
|
28
|
+
run("npx", ["eve", "registry", "add", `@kybernesis=${REGISTRY_URL}`], { cwd: dir });
|
|
29
|
+
for (const item of ITEMS) {
|
|
30
|
+
run("npx", ["eve", "add", `@kybernesis/${item}`, "--overwrite"], { cwd: dir });
|
|
31
|
+
}
|
|
32
|
+
console.log(bold("\n3/6 Writing agent identity, memory mount, and eval wiring …"));
|
|
33
|
+
mkdirSync(join(dir, "agent/instructions"), { recursive: true });
|
|
34
|
+
writeFileSync(join(dir, "agent/instructions/identity.md"), identityMd(displayName, depts));
|
|
35
|
+
// The scaffold ships a flat agent/instructions.md; the directory form wins,
|
|
36
|
+
// so remove the flat file to avoid ambiguity.
|
|
37
|
+
try {
|
|
38
|
+
unlinkSync(join(dir, "agent/instructions.md"));
|
|
39
|
+
}
|
|
40
|
+
catch { }
|
|
41
|
+
writeFileSync(join(dir, "agent/extensions/arcana.ts"), rootArcanaTs());
|
|
42
|
+
writeFileSync(join(dir, "evals/kybernesis.eval.ts"), evalFileTs(displayName, depts));
|
|
43
|
+
console.log(bold(`\n4/6 Generating ${depts.length} department subagent(s) …`));
|
|
44
|
+
const skillsSource = join(dir, "node_modules/@kybernesis/arcana/dist/extension/skills");
|
|
45
|
+
for (const dept of depts) {
|
|
46
|
+
const base = join(dir, "agent/subagents", dept);
|
|
47
|
+
mkdirSync(join(base, "connections"), { recursive: true });
|
|
48
|
+
writeFileSync(join(base, "agent.ts"), subagentAgentTs(dept));
|
|
49
|
+
writeFileSync(join(base, "instructions.md"), subagentInstructionsMd(dept));
|
|
50
|
+
writeFileSync(join(base, "connections/arcana.ts"), subagentArcanaTs(dept));
|
|
51
|
+
// Subagents inherit nothing: copy the memory skills from the installed
|
|
52
|
+
// arcana package so each specialist carries the playbooks.
|
|
53
|
+
if (existsSync(skillsSource)) {
|
|
54
|
+
cpSync(skillsSource, join(base, "skills"), { recursive: true });
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
console.log(yellow(` ! arcana skills not found at ${skillsSource} — copy them manually`));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
console.log(bold("\n5/6 Env template + hermetic eval script …"));
|
|
61
|
+
writeFileSync(join(dir, ".env.example"), envExample(name, depts, issuer));
|
|
62
|
+
const pkgPath = join(dir, "package.json");
|
|
63
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
64
|
+
pkg.scripts = { ...pkg.scripts, eval: evalScript(name, depts) };
|
|
65
|
+
writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
66
|
+
console.log(bold("\n6/6 Verifying (typecheck + eve discovery) …"));
|
|
67
|
+
const tsOk = run("npm", ["run", "typecheck"], { cwd: dir, allowFail: true });
|
|
68
|
+
const infoOk = run("npx", ["eve", "info"], { cwd: dir, allowFail: true, quiet: true });
|
|
69
|
+
console.log(tsOk && infoOk ? green(" ✓ typecheck + discovery clean") : yellow(" ! verify manually: npm run typecheck && npx eve info"));
|
|
70
|
+
console.log(`
|
|
71
|
+
${green("✓")} ${bold(name)} scaffolded: governed (enterprise) · remembering (arcana) · multiplayer (slack) · self-testing (evals)${depts.length ? ` · ${depts.length} dept subagent(s)` : ""}
|
|
72
|
+
|
|
73
|
+
${bold("Human steps (in order) — the FDE playbook covers each in detail:")}
|
|
74
|
+
1. Arcana: create workspaces (${name}-company, ${name}-eval${depts.map((d) => `, ${name}-${d}`).join("")}) + scoped kb_ keys; fill .env.local from .env.example
|
|
75
|
+
2. Vercel: \`vercel link\` in ${name}/ (client's team), add envs (prod/preview Sensitive)
|
|
76
|
+
3. Slack: \`vercel connect create slack --triggers --name ${name}\` → detach → re-attach with --trigger-path /eve/v1/slack
|
|
77
|
+
4. Control plane: register agent "${name}" (runtime: ▲ eve) at ${issuer}/agents + grant the pilot cohort
|
|
78
|
+
5. \`npm run eval\` → green → \`npx eve deploy\` → live Slack smoke + the revoke demo
|
|
79
|
+
Run ${bold("kyb doctor")} inside ${name}/ any time to check the wiring.
|
|
80
|
+
`);
|
|
81
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** File templates written by `kyb init`. Kept in sync with the proven Kyber patterns. */
|
|
2
|
+
export declare const DEPT_DESCRIPTIONS: Record<string, string>;
|
|
3
|
+
export declare function deptDescription(dept: string): string;
|
|
4
|
+
export declare function identityMd(displayName: string, depts: string[]): string;
|
|
5
|
+
export declare function subagentAgentTs(dept: string): string;
|
|
6
|
+
export declare function subagentInstructionsMd(dept: string): string;
|
|
7
|
+
export declare function subagentArcanaTs(dept: string): string;
|
|
8
|
+
export declare function rootArcanaTs(): string;
|
|
9
|
+
export declare function evalFileTs(displayName: string, depts: string[]): string;
|
|
10
|
+
export declare function envExample(name: string, depts: string[], issuer: string): string;
|
|
11
|
+
export declare function evalScript(name: string, depts: string[]): string;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/** File templates written by `kyb init`. Kept in sync with the proven Kyber patterns. */
|
|
2
|
+
export const DEPT_DESCRIPTIONS = {
|
|
3
|
+
finance: "Finance specialist: financials, budgets, spend, revenue, invoices, runway, and financial reporting. Keeps the finance team's own memory workspace. Delegate any finance-shaped task, question, or reporting request here.",
|
|
4
|
+
marketing: "Marketing specialist: campaigns, content, positioning, brand, launches, social, and ads. Keeps the marketing team's own memory workspace. Delegate any marketing-shaped task or question here.",
|
|
5
|
+
engineering: "Engineering specialist: production errors and incidents, architecture decisions, technical questions, and engineering documentation. Keeps the engineering team's own memory workspace. Delegate any engineering-shaped task or question here.",
|
|
6
|
+
};
|
|
7
|
+
export function deptDescription(dept) {
|
|
8
|
+
return (DEPT_DESCRIPTIONS[dept] ??
|
|
9
|
+
`${dept[0].toUpperCase()}${dept.slice(1)} specialist. Keeps the ${dept} team's own memory workspace. Delegate ${dept}-shaped tasks and questions here. TODO: sharpen this routing description for the client.`);
|
|
10
|
+
}
|
|
11
|
+
export function identityMd(displayName, depts) {
|
|
12
|
+
const delegation = depts.length
|
|
13
|
+
? `
|
|
14
|
+
# Delegation
|
|
15
|
+
|
|
16
|
+
You have department specialists available as tools: ${depts.map((d) => `\`${d}\``).join(", ")}.
|
|
17
|
+
Each has its own memory workspace and tool surface. When a request is clearly
|
|
18
|
+
departmental, delegate to the specialist instead of answering from the shared
|
|
19
|
+
brain.
|
|
20
|
+
|
|
21
|
+
Brief them fully: a specialist sees none of this conversation, so pack
|
|
22
|
+
everything it needs into the message (who is asking, the specifics, any
|
|
23
|
+
constraints, and the desired output shape). Synthesize the specialist's answer
|
|
24
|
+
for the person — don't paste it raw. General company knowledge stays with you
|
|
25
|
+
and the shared brain.
|
|
26
|
+
`
|
|
27
|
+
: "";
|
|
28
|
+
return `# Identity
|
|
29
|
+
|
|
30
|
+
You are ${displayName}. You live in the company's chat surfaces: people
|
|
31
|
+
@mention you in channels and message you directly in DMs. Be concise and
|
|
32
|
+
concrete — chat is not a document editor. Prefer short paragraphs and bullet
|
|
33
|
+
lists.
|
|
34
|
+
${delegation}`;
|
|
35
|
+
}
|
|
36
|
+
export function subagentAgentTs(dept) {
|
|
37
|
+
return `import { defineAgent } from "eve";
|
|
38
|
+
|
|
39
|
+
export default defineAgent({
|
|
40
|
+
description:
|
|
41
|
+
${JSON.stringify(deptDescription(dept))},
|
|
42
|
+
model: "anthropic/claude-sonnet-5",
|
|
43
|
+
});
|
|
44
|
+
`;
|
|
45
|
+
}
|
|
46
|
+
export function subagentInstructionsMd(dept) {
|
|
47
|
+
return `# Identity
|
|
48
|
+
|
|
49
|
+
You are the ${dept} specialist. You receive fully-briefed tasks from the root
|
|
50
|
+
agent — you never see the original conversation, so treat the incoming message
|
|
51
|
+
as the complete brief and return a complete, self-contained answer.
|
|
52
|
+
|
|
53
|
+
# Memory
|
|
54
|
+
|
|
55
|
+
You have the ${dept} team's own long-term memory workspace (Arcana) via the
|
|
56
|
+
\`arcana\` connection: recall, search, timeline, remember, and brain notes.
|
|
57
|
+
|
|
58
|
+
- Look up relevant context BEFORE answering. Never claim something isn't known
|
|
59
|
+
without searching first (empty entity recall alone is not enough — escalate
|
|
60
|
+
to search).
|
|
61
|
+
- Store new ${dept} facts and decisions proactively with \`arcana_remember\`
|
|
62
|
+
(called via its qualified name); use brain notes (\`arcana_brain_write\` AND
|
|
63
|
+
\`arcana_brain_add\`, always both) for long-form material.
|
|
64
|
+
- Load the \`recall\`, \`remember\`, or \`brain-note\` skill for the full
|
|
65
|
+
procedure.
|
|
66
|
+
`;
|
|
67
|
+
}
|
|
68
|
+
export function subagentArcanaTs(dept) {
|
|
69
|
+
const upper = dept.toUpperCase().replace(/[^A-Z0-9]+/g, "_");
|
|
70
|
+
return `import { defineMcpClientConnection } from "eve/connections";
|
|
71
|
+
|
|
72
|
+
const workspace = process.env.ARCANA_${upper}_WORKSPACE ?? "REPLACE-${dept}";
|
|
73
|
+
|
|
74
|
+
export default defineMcpClientConnection({
|
|
75
|
+
url: "https://mcp.arcana.kybernesis.ai/mcp",
|
|
76
|
+
description:
|
|
77
|
+
"The ${dept} team's long-term memory (Arcana): remember, recall, search, timeline, and brain notes.",
|
|
78
|
+
auth: {
|
|
79
|
+
getToken: async () => {
|
|
80
|
+
const token =
|
|
81
|
+
(workspace.endsWith("-eval")
|
|
82
|
+
? process.env.ARCANA_EVAL_API_KEY
|
|
83
|
+
: undefined) ??
|
|
84
|
+
process.env.ARCANA_${upper}_API_KEY ??
|
|
85
|
+
process.env.ARCANA_API_KEY;
|
|
86
|
+
if (!token) throw new Error("ARCANA_${upper}_API_KEY is not set.");
|
|
87
|
+
return { token };
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
headers: { "X-Kyberagent-Agent": async () => workspace },
|
|
91
|
+
});
|
|
92
|
+
`;
|
|
93
|
+
}
|
|
94
|
+
export function rootArcanaTs() {
|
|
95
|
+
return `import arcana from "@kybernesis/arcana";
|
|
96
|
+
|
|
97
|
+
// One brain for shared surfaces, optionally a second for DMs. Hermetic eval
|
|
98
|
+
// runs override ARCANA_COMPANY_WORKSPACE to "<client>-eval", which switches
|
|
99
|
+
// to the eval workspace's own key automatically.
|
|
100
|
+
const COMPANY = process.env.ARCANA_COMPANY_WORKSPACE ?? "REPLACE-company";
|
|
101
|
+
const DM = process.env.ARCANA_DM_WORKSPACE ?? COMPANY;
|
|
102
|
+
|
|
103
|
+
export default arcana({
|
|
104
|
+
apiKey:
|
|
105
|
+
(COMPANY.endsWith("-eval") ? process.env.ARCANA_EVAL_API_KEY : undefined) ??
|
|
106
|
+
process.env.ARCANA_API_KEY!,
|
|
107
|
+
workspace: COMPANY,
|
|
108
|
+
// DM sessions carry surface:"dm" via @kybernesis/multiplayer.
|
|
109
|
+
resolveWorkspace: (ctx) =>
|
|
110
|
+
ctx.session.auth.current?.attributes.surface === "dm" ? DM : undefined,
|
|
111
|
+
});
|
|
112
|
+
`;
|
|
113
|
+
}
|
|
114
|
+
export function evalFileTs(displayName, depts) {
|
|
115
|
+
const routing = depts.map((d) => ` { subagent: ${JSON.stringify(d)} },`).join("\n");
|
|
116
|
+
return `import { kybernesisBaseline } from "@kybernesis/evals";
|
|
117
|
+
|
|
118
|
+
export default kybernesisBaseline({
|
|
119
|
+
agentDisplayName: ${JSON.stringify(displayName)},
|
|
120
|
+
${depts.length ? ` routing: [\n${routing}\n ],\n` : ""}});
|
|
121
|
+
`;
|
|
122
|
+
}
|
|
123
|
+
export function envExample(name, depts, issuer) {
|
|
124
|
+
const deptVars = depts
|
|
125
|
+
.map((d) => {
|
|
126
|
+
const upper = d.toUpperCase().replace(/[^A-Z0-9]+/g, "_");
|
|
127
|
+
return `# ARCANA_${upper}_API_KEY="kb_..."\n# ARCANA_${upper}_WORKSPACE="${name}-${d}"`;
|
|
128
|
+
})
|
|
129
|
+
.join("\n");
|
|
130
|
+
return `# ── Kybernesis agent environment ─────────────────────────────────────
|
|
131
|
+
# Real values belong in Vercel envs (prod/preview Sensitive); \`eve deploy\`
|
|
132
|
+
# overwrites .env.local from the development environment on every deploy.
|
|
133
|
+
|
|
134
|
+
# Control-plane governance (@kybernesis/enterprise)
|
|
135
|
+
KYBERNESIS_ISSUER="${issuer}"
|
|
136
|
+
KYBERNESIS_AGENT="${name}"
|
|
137
|
+
|
|
138
|
+
# Slack (@kybernesis/multiplayer) — Vercel Connect connector UID
|
|
139
|
+
# SLACK_CONNECTOR_UID="slack/${name}"
|
|
140
|
+
|
|
141
|
+
# Arcana memory (@kybernesis/arcana) — one workspace + scoped kb_ key per brain
|
|
142
|
+
# ARCANA_API_KEY="kb_..."
|
|
143
|
+
# ARCANA_COMPANY_WORKSPACE="${name}-company"
|
|
144
|
+
# ARCANA_DM_WORKSPACE="${name}-company"
|
|
145
|
+
# ARCANA_EVAL_API_KEY="kb_..." # key for the ${name}-eval workspace (hermetic evals)
|
|
146
|
+
${deptVars}
|
|
147
|
+
`;
|
|
148
|
+
}
|
|
149
|
+
export function evalScript(name, depts) {
|
|
150
|
+
const overrides = [
|
|
151
|
+
`ARCANA_COMPANY_WORKSPACE=${name}-eval`,
|
|
152
|
+
`ARCANA_DM_WORKSPACE=${name}-eval`,
|
|
153
|
+
...depts.map((d) => `ARCANA_${d.toUpperCase().replace(/[^A-Z0-9]+/g, "_")}_WORKSPACE=${name}-eval`),
|
|
154
|
+
];
|
|
155
|
+
return `${overrides.join(" ")} eve eval`;
|
|
156
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function upgrade(skipEval: boolean): Promise<void>;
|
package/dist/upgrade.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { bold, capture, dim, green, red, run, yellow } from "./util.js";
|
|
4
|
+
const PACKAGES = [
|
|
5
|
+
"@kybernesis/arcana",
|
|
6
|
+
"@kybernesis/enterprise",
|
|
7
|
+
"@kybernesis/multiplayer",
|
|
8
|
+
"@kybernesis/evals",
|
|
9
|
+
];
|
|
10
|
+
export async function upgrade(skipEval) {
|
|
11
|
+
const cwd = process.cwd();
|
|
12
|
+
const pkg = JSON.parse(readFileSync(join(cwd, "package.json"), "utf8"));
|
|
13
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
14
|
+
console.log(bold("\nkyb upgrade — checking @kybernesis/* against npm\n"));
|
|
15
|
+
const toUpgrade = [];
|
|
16
|
+
for (const name of PACKAGES) {
|
|
17
|
+
if (!deps[name])
|
|
18
|
+
continue;
|
|
19
|
+
const installed = capture("node", ["-p", `require('${name}/package.json').version`], cwd)?.trim();
|
|
20
|
+
const latest = capture("npm", ["view", name, "version"])?.trim();
|
|
21
|
+
if (!installed || !latest) {
|
|
22
|
+
console.log(` ${yellow("!")} ${name}: could not resolve versions`);
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (installed === latest)
|
|
26
|
+
console.log(` ${green("✓")} ${name}@${installed} ${dim("(latest)")}`);
|
|
27
|
+
else {
|
|
28
|
+
console.log(` ${yellow("↑")} ${name}: ${installed} → ${latest}`);
|
|
29
|
+
toUpgrade.push(`${name}@${latest}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (toUpgrade.length === 0) {
|
|
33
|
+
console.log(`\n${green("Everything is at latest.")}\n`);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
console.log(bold(`\nInstalling: ${toUpgrade.join(", ")}\n`));
|
|
37
|
+
run("npm", ["install", ...toUpgrade], { cwd });
|
|
38
|
+
run("npm", ["run", "typecheck"], { cwd });
|
|
39
|
+
if (skipEval) {
|
|
40
|
+
console.log(yellow("\nEval gate SKIPPED (--skip-eval). Run `npm run eval` before deploying.\n"));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
console.log(bold("\nRunning the eval gate (npm run eval) — deploy only on green …\n"));
|
|
44
|
+
const ok = run("npm", ["run", "eval"], { cwd, allowFail: true });
|
|
45
|
+
if (ok) {
|
|
46
|
+
console.log(`\n${green("✓ Upgrade green.")} Deploy with: ${bold("npx eve deploy")}\n`);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
console.log(`\n${red("✗ Evals failed after upgrade.")} Do NOT deploy — inspect .eve/evals/ artifacts.\n`);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
}
|
package/dist/util.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare const EVE_VERSION = "0.29.5";
|
|
2
|
+
export declare const REGISTRY_URL = "https://registry.kybernesis.ai/r/{name}.json";
|
|
3
|
+
export declare const DEFAULT_ISSUER = "https://agent.kybernesis.ai";
|
|
4
|
+
export declare const green: (s: string) => string;
|
|
5
|
+
export declare const yellow: (s: string) => string;
|
|
6
|
+
export declare const red: (s: string) => string;
|
|
7
|
+
export declare const bold: (s: string) => string;
|
|
8
|
+
export declare const dim: (s: string) => string;
|
|
9
|
+
export declare function run(command: string, args: string[], options?: {
|
|
10
|
+
cwd?: string;
|
|
11
|
+
allowFail?: boolean;
|
|
12
|
+
quiet?: boolean;
|
|
13
|
+
}): boolean;
|
|
14
|
+
export declare function capture(command: string, args: string[], cwd?: string): string | null;
|
|
15
|
+
export declare function ask(question: string, fallback: string): Promise<string>;
|
|
16
|
+
export declare function closePrompts(): void;
|
|
17
|
+
/** Parse KEY="value" / KEY=value lines from an env file's contents. */
|
|
18
|
+
export declare function parseEnv(contents: string): Record<string, string>;
|
|
19
|
+
export declare function slug(name: string): string;
|
package/dist/util.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { createInterface } from "node:readline/promises";
|
|
3
|
+
export const EVE_VERSION = "0.29.5";
|
|
4
|
+
export const REGISTRY_URL = "https://registry.kybernesis.ai/r/{name}.json";
|
|
5
|
+
export const DEFAULT_ISSUER = "https://agent.kybernesis.ai";
|
|
6
|
+
const TTY = process.stdout.isTTY === true;
|
|
7
|
+
export const green = (s) => (TTY ? `\x1b[32m${s}\x1b[0m` : s);
|
|
8
|
+
export const yellow = (s) => (TTY ? `\x1b[33m${s}\x1b[0m` : s);
|
|
9
|
+
export const red = (s) => (TTY ? `\x1b[31m${s}\x1b[0m` : s);
|
|
10
|
+
export const bold = (s) => (TTY ? `\x1b[1m${s}\x1b[0m` : s);
|
|
11
|
+
export const dim = (s) => (TTY ? `\x1b[2m${s}\x1b[0m` : s);
|
|
12
|
+
export function run(command, args, options) {
|
|
13
|
+
if (!options?.quiet)
|
|
14
|
+
console.log(dim(` $ ${command} ${args.join(" ")}`));
|
|
15
|
+
const result = spawnSync(command, args, {
|
|
16
|
+
cwd: options?.cwd,
|
|
17
|
+
stdio: options?.quiet ? "pipe" : "inherit",
|
|
18
|
+
env: process.env,
|
|
19
|
+
});
|
|
20
|
+
const ok = result.status === 0;
|
|
21
|
+
if (!ok && !options?.allowFail) {
|
|
22
|
+
console.error(red(`Command failed: ${command} ${args.join(" ")}`));
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
return ok;
|
|
26
|
+
}
|
|
27
|
+
export function capture(command, args, cwd) {
|
|
28
|
+
const result = spawnSync(command, args, { cwd, encoding: "utf8", env: process.env });
|
|
29
|
+
return result.status === 0 ? result.stdout : null;
|
|
30
|
+
}
|
|
31
|
+
let rl = null;
|
|
32
|
+
export async function ask(question, fallback) {
|
|
33
|
+
if (!process.stdin.isTTY)
|
|
34
|
+
return fallback;
|
|
35
|
+
rl ??= createInterface({ input: process.stdin, output: process.stdout });
|
|
36
|
+
const answer = (await rl.question(`${question} ${dim(`(${fallback})`)} `)).trim();
|
|
37
|
+
return answer || fallback;
|
|
38
|
+
}
|
|
39
|
+
export function closePrompts() {
|
|
40
|
+
rl?.close();
|
|
41
|
+
rl = null;
|
|
42
|
+
}
|
|
43
|
+
/** Parse KEY="value" / KEY=value lines from an env file's contents. */
|
|
44
|
+
export function parseEnv(contents) {
|
|
45
|
+
const out = {};
|
|
46
|
+
for (const line of contents.split("\n")) {
|
|
47
|
+
const match = /^\s*([A-Z0-9_]+)\s*=\s*"?([^"\n]*)"?\s*$/.exec(line);
|
|
48
|
+
if (match && !line.trim().startsWith("#"))
|
|
49
|
+
out[match[1]] = match[2];
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
export function slug(name) {
|
|
54
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
55
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kybernesis/create",
|
|
3
|
+
"version": "0.1.0",
|
|
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
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"homepage": "https://kybernesis.ai",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/KybernesisAI/platform.git",
|
|
11
|
+
"directory": "packages/create"
|
|
12
|
+
},
|
|
13
|
+
"keywords": ["eve", "kybernesis", "cli", "scaffold", "agent", "create"],
|
|
14
|
+
"files": ["dist", "NOTICE"],
|
|
15
|
+
"bin": { "kyb": "./dist/cli.js" },
|
|
16
|
+
"exports": {
|
|
17
|
+
"./package.json": "./package.json"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc -p tsconfig.build.json",
|
|
21
|
+
"prepare": "tsc -p tsconfig.build.json",
|
|
22
|
+
"typecheck": "tsc --noEmit"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/node": "24.x",
|
|
26
|
+
"typescript": "7.0.2"
|
|
27
|
+
},
|
|
28
|
+
"engines": { "node": ">=20" }
|
|
29
|
+
}
|