@kybernesis/create 0.19.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/add.js +6 -0
- package/dist/buzz.d.ts +29 -0
- package/dist/buzz.js +154 -0
- package/dist/cli.js +1 -0
- package/dist/doctor.js +4 -1
- package/dist/templates.js +75 -46
- package/dist/upgrade.js +6 -13
- package/package.json +1 -1
- package/skills/kybernesis-packages/SKILL.md +4 -1
package/dist/add.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { basename, join, resolve } from "node:path";
|
|
3
3
|
import { bold, dim, green, red, run, yellow } from "./util.js";
|
|
4
|
+
import { addBuzz } from "./buzz.js";
|
|
4
5
|
import { CHANNEL_KINDS, arcanaMemorySlotTs, channelPlan } from "./templates.js";
|
|
5
6
|
/**
|
|
6
7
|
* `kyb add channel <kind>` — put another surface on an agent that already exists.
|
|
@@ -21,10 +22,15 @@ import { CHANNEL_KINDS, arcanaMemorySlotTs, channelPlan } from "./templates.js";
|
|
|
21
22
|
export async function add(what, rest) {
|
|
22
23
|
if (what === "memory")
|
|
23
24
|
return addMemory();
|
|
25
|
+
// Buzz is a bridge and a key, not a channel file; "add channel buzz" is what
|
|
26
|
+
// the provisioner and people naturally type, so both spellings land here.
|
|
27
|
+
if (what === "buzz" || (what === "channel" && rest.find((a) => !a.startsWith("-")) === "buzz"))
|
|
28
|
+
return addBuzz();
|
|
24
29
|
if (what !== "channel") {
|
|
25
30
|
console.error(red(`kyb add: don't know how to add "${what ?? ""}".`));
|
|
26
31
|
console.log(dim(` kyb add channel <${CHANNEL_KINDS.filter((k) => k !== "none").join("|")}>`));
|
|
27
32
|
console.log(dim(` kyb add memory ${dim("— an eve 0.49 memory slot backed by Arcana (recall before every turn)")}`));
|
|
33
|
+
console.log(dim(` kyb add buzz ${dim("— the agent's own Buzz identity, tools and bridge, ready for its owner's community")}`));
|
|
28
34
|
process.exitCode = 1;
|
|
29
35
|
return;
|
|
30
36
|
}
|
package/dist/buzz.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `kyb add buzz` — get an agent ready for its owner's Buzz community.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* Buzz is not a channel file. The agent is a member of the community with its
|
|
6
|
+
* own key, a separate bridge process hears mentions and answers as the person
|
|
7
|
+
* who spoke, and an extension gives the agent the rest of the workspace. Every
|
|
8
|
+
* one of those used to be a manual step on the host, so ticking Buzz on a build
|
|
9
|
+
* did nothing at all.
|
|
10
|
+
*
|
|
11
|
+
* This does everything that does not depend on the customer: the package, the
|
|
12
|
+
* extension mount, the agent's own key, the Buzz CLI and the bridge's unit. The
|
|
13
|
+
* unit is left switched off, because the one thing only the customer can give
|
|
14
|
+
* is which community it lives in. That arrives later through the owner's
|
|
15
|
+
* Channels page (manage's `buzz.join`), which writes the relay, publishes the
|
|
16
|
+
* agent's profile and starts the bridge.
|
|
17
|
+
*/
|
|
18
|
+
export declare const BUZZ_PACKAGE = "@kybernesis/buzz@^0.10.2";
|
|
19
|
+
export declare const BUZZ_EXTENSION_MOUNT: string;
|
|
20
|
+
/** Where the agent's Buzz key lives: inside the agent, so the bridge and the agent read the same file. */
|
|
21
|
+
export declare function buzzKeyPath(appDir: string): string;
|
|
22
|
+
export declare function buzzBridgeUnitName(agent: string): string;
|
|
23
|
+
/** The bridge's systemd unit. Pure, so the test pins what every host gets. */
|
|
24
|
+
export declare function buzzBridgeUnit(input: {
|
|
25
|
+
agent: string;
|
|
26
|
+
user: string;
|
|
27
|
+
appDir: string;
|
|
28
|
+
}): string;
|
|
29
|
+
export declare function addBuzz(cwd?: string): void;
|
package/dist/buzz.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir, userInfo } from "node:os";
|
|
3
|
+
import { basename, join, resolve } from "node:path";
|
|
4
|
+
import { upsertEnv } from "./envfile.js";
|
|
5
|
+
import { bold, capture, dim, green, red, run, yellow } from "./util.js";
|
|
6
|
+
/**
|
|
7
|
+
* `kyb add buzz` — get an agent ready for its owner's Buzz community.
|
|
8
|
+
*
|
|
9
|
+
* @remarks
|
|
10
|
+
* Buzz is not a channel file. The agent is a member of the community with its
|
|
11
|
+
* own key, a separate bridge process hears mentions and answers as the person
|
|
12
|
+
* who spoke, and an extension gives the agent the rest of the workspace. Every
|
|
13
|
+
* one of those used to be a manual step on the host, so ticking Buzz on a build
|
|
14
|
+
* did nothing at all.
|
|
15
|
+
*
|
|
16
|
+
* This does everything that does not depend on the customer: the package, the
|
|
17
|
+
* extension mount, the agent's own key, the Buzz CLI and the bridge's unit. The
|
|
18
|
+
* unit is left switched off, because the one thing only the customer can give
|
|
19
|
+
* is which community it lives in. That arrives later through the owner's
|
|
20
|
+
* Channels page (manage's `buzz.join`), which writes the relay, publishes the
|
|
21
|
+
* agent's profile and starts the bridge.
|
|
22
|
+
*/
|
|
23
|
+
export const BUZZ_PACKAGE = "@kybernesis/buzz@^0.10.2";
|
|
24
|
+
export const BUZZ_EXTENSION_MOUNT = `// The agent's hands in Buzz: projects, issues, pull requests, patches, repos,\n` +
|
|
25
|
+
`// long-form notes, channel canvases, workflows, the feed, media — everything the\n` +
|
|
26
|
+
`// workspace has beyond talking, which the bridge alone does not provide.\n` +
|
|
27
|
+
`//\n` +
|
|
28
|
+
`// Actions are signed with THIS AGENT's key and appear under its name.\n` +
|
|
29
|
+
`export { default } from "@kybernesis/buzz/extension";\n`;
|
|
30
|
+
/** Where the agent's Buzz key lives: inside the agent, so the bridge and the agent read the same file. */
|
|
31
|
+
export function buzzKeyPath(appDir) {
|
|
32
|
+
return join(appDir, ".buzz", "agent-key.json");
|
|
33
|
+
}
|
|
34
|
+
export function buzzBridgeUnitName(agent) {
|
|
35
|
+
return `${agent}-buzz-bridge`;
|
|
36
|
+
}
|
|
37
|
+
/** The bridge's systemd unit. Pure, so the test pins what every host gets. */
|
|
38
|
+
export function buzzBridgeUnit(input) {
|
|
39
|
+
const { agent, user, appDir } = input;
|
|
40
|
+
return `# Managed by @kybernesis/create (kyb add buzz). Switched on from the owner's Channels page.
|
|
41
|
+
[Unit]
|
|
42
|
+
Description=${agent} Buzz bridge
|
|
43
|
+
After=network-online.target ${agent}-agent.service
|
|
44
|
+
Wants=network-online.target
|
|
45
|
+
|
|
46
|
+
[Service]
|
|
47
|
+
Type=simple
|
|
48
|
+
User=${user}
|
|
49
|
+
WorkingDirectory=${appDir}
|
|
50
|
+
# The relay, the key and the agent credential all come from .env.local, the
|
|
51
|
+
# same file the agent reads, so they can never disagree.
|
|
52
|
+
ExecStart=/bin/bash -lc 'set -a && . ./.env.local && set +a && exec npx kybernesis-buzz run'
|
|
53
|
+
Restart=always
|
|
54
|
+
RestartSec=10
|
|
55
|
+
# The bridge publishes "offline" on the way out; this gives it time to.
|
|
56
|
+
KillSignal=SIGTERM
|
|
57
|
+
TimeoutStopSec=10
|
|
58
|
+
StartLimitBurst=5
|
|
59
|
+
StartLimitIntervalSec=300
|
|
60
|
+
StandardOutput=append:${appDir}/buzz-bridge.log
|
|
61
|
+
StandardError=append:${appDir}/buzz-bridge.log
|
|
62
|
+
|
|
63
|
+
[Install]
|
|
64
|
+
WantedBy=multi-user.target
|
|
65
|
+
`;
|
|
66
|
+
}
|
|
67
|
+
export function addBuzz(cwd = resolve(process.cwd())) {
|
|
68
|
+
if (!existsSync(join(cwd, "package.json")) || !existsSync(join(cwd, "agent"))) {
|
|
69
|
+
console.error(red("kyb add buzz: run this inside an agent directory."));
|
|
70
|
+
process.exitCode = 1;
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (!existsSync(join(cwd, "scripts/eve-server.sh"))) {
|
|
74
|
+
console.error(red("kyb add buzz: Buzz runs a bridge beside the agent, so it needs a host of its own (an exe VM or a server). This agent deploys to Vercel."));
|
|
75
|
+
process.exitCode = 1;
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const agent = agentNameOf(cwd);
|
|
79
|
+
console.log(bold("kyb add buzz"));
|
|
80
|
+
console.log(dim(` agent: ${agent}`));
|
|
81
|
+
const pkg = JSON.parse(readFileSync(join(cwd, "package.json"), "utf8"));
|
|
82
|
+
if (!pkg.dependencies?.["@kybernesis/buzz"]) {
|
|
83
|
+
run("npm", ["install", BUZZ_PACKAGE, "--no-audit", "--no-fund"], { cwd });
|
|
84
|
+
}
|
|
85
|
+
const mount = join(cwd, "agent/extensions/buzz.ts");
|
|
86
|
+
if (!existsSync(mount)) {
|
|
87
|
+
mkdirSync(join(cwd, "agent/extensions"), { recursive: true });
|
|
88
|
+
writeFileSync(mount, BUZZ_EXTENSION_MOUNT);
|
|
89
|
+
console.log(green(" + agent/extensions/buzz.ts"));
|
|
90
|
+
}
|
|
91
|
+
// One key for the bridge and the agent's tools. An existing BUZZ_KEYFILE is
|
|
92
|
+
// kept: replacing a key is a new member the community never invited.
|
|
93
|
+
const envPath = join(cwd, ".env.local");
|
|
94
|
+
const env = existsSync(envPath) ? readFileSync(envPath, "utf8") : "";
|
|
95
|
+
const existing = env.match(/^BUZZ_KEYFILE\s*=\s*"?([^"\n]+)"?/m)?.[1]?.trim();
|
|
96
|
+
const keyFile = existing || buzzKeyPath(cwd);
|
|
97
|
+
if (!existing)
|
|
98
|
+
upsertEnv(cwd, { BUZZ_KEYFILE: keyFile });
|
|
99
|
+
mkdirSync(join(keyFile, ".."), { recursive: true });
|
|
100
|
+
ignore(cwd, ".buzz/");
|
|
101
|
+
const init = capture("npx", ["kybernesis-buzz", "init"], cwd, { BUZZ_KEYFILE: keyFile });
|
|
102
|
+
const npub = init?.match(/npub1[02-9ac-hj-np-z]+/)?.[0];
|
|
103
|
+
if (!npub) {
|
|
104
|
+
console.error(red(" Could not create the agent's Buzz identity (npx kybernesis-buzz init)."));
|
|
105
|
+
process.exitCode = 1;
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
console.log(green(` + Buzz identity ${npub}`));
|
|
109
|
+
// The CLI is what the agent acts through; no binary is published, so it is
|
|
110
|
+
// built once in a container. Without docker the agent can still talk.
|
|
111
|
+
if (!existsSync(join(cwd, ".buzz/bin/buzz"))) {
|
|
112
|
+
const hasDocker = capture("sh", ["-c", "command -v docker >/dev/null && echo yes"])?.trim() === "yes";
|
|
113
|
+
if (hasDocker) {
|
|
114
|
+
console.log(dim(" building the Buzz CLI for this host (first time only, a few minutes) …"));
|
|
115
|
+
const ok = run("npx", ["kybernesis-buzz", "install-cli"], { cwd, allowFail: true, quiet: true, timeoutMs: 15 * 60_000 });
|
|
116
|
+
console.log(ok ? green(" + Buzz CLI (.buzz/bin/buzz)") : yellow(" ! the Buzz CLI did not build; run: npx kybernesis-buzz install-cli"));
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
console.log(yellow(" ! no docker here, so the Buzz CLI was not built; the agent can talk in Buzz but not act in it."));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const unitName = buzzBridgeUnitName(agent);
|
|
123
|
+
const dir = mkdtempSync(join(tmpdir(), "kyb-buzz-"));
|
|
124
|
+
try {
|
|
125
|
+
const file = join(dir, `${unitName}.service`);
|
|
126
|
+
writeFileSync(file, buzzBridgeUnit({ agent, user: userInfo().username, appDir: cwd }));
|
|
127
|
+
const installed = run("sudo", ["-n", "install", "-m", "644", file, `/etc/systemd/system/${unitName}.service`], { allowFail: true, quiet: true }) &&
|
|
128
|
+
run("sudo", ["-n", "systemctl", "daemon-reload"], { allowFail: true, quiet: true });
|
|
129
|
+
console.log(installed
|
|
130
|
+
? green(` + ${unitName}.service (off until the owner connects a community)`)
|
|
131
|
+
: yellow(` ! could not install ${unitName}.service without a password; the owner's Connect step will need it`));
|
|
132
|
+
}
|
|
133
|
+
finally {
|
|
134
|
+
rmSync(dir, { recursive: true, force: true });
|
|
135
|
+
}
|
|
136
|
+
console.log(bold("\n Next:"));
|
|
137
|
+
console.log(` · The owner connects it from the Channels page: their community's address, then an admin adds ${npub} as a member.`);
|
|
138
|
+
}
|
|
139
|
+
function agentNameOf(dir) {
|
|
140
|
+
const env = join(dir, ".env.local");
|
|
141
|
+
if (existsSync(env)) {
|
|
142
|
+
const hit = readFileSync(env, "utf8").match(/^KYBERNESIS_AGENT\s*=\s*"?([^"\n]+)"?/m);
|
|
143
|
+
if (hit?.[1])
|
|
144
|
+
return hit[1].trim();
|
|
145
|
+
}
|
|
146
|
+
return basename(dir);
|
|
147
|
+
}
|
|
148
|
+
function ignore(dir, entry) {
|
|
149
|
+
const path = join(dir, ".gitignore");
|
|
150
|
+
const current = existsSync(path) ? readFileSync(path, "utf8") : "";
|
|
151
|
+
if (current.split("\n").some((line) => line.trim() === entry))
|
|
152
|
+
return;
|
|
153
|
+
writeFileSync(path, `${current.replace(/\n*$/, "\n")}${entry}\n`);
|
|
154
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -233,6 +233,7 @@ ${dim(" npm i -g @kybernesis/create@latest")}
|
|
|
233
233
|
--yes ${dim("no prompts; take flags and defaults")}
|
|
234
234
|
${bold("kyb add channel <kind>")}
|
|
235
235
|
${dim("slack|imessage|telegram|discord|web — writes the channel, deps and env")}
|
|
236
|
+
${bold("kyb add buzz")} ${dim("the agent's own Buzz identity, tools and bridge (connected from the Channels page)")}
|
|
236
237
|
${bold("kyb add memory")} ${dim("eve 0.49 memory slot backed by Arcana: recall before every turn")}
|
|
237
238
|
${bold("kyb doctor")} preflight checks (keys, issuer, envs, discovery)
|
|
238
239
|
${bold("kyb skills")} install/refresh the FDE skill suite for Claude Code
|
package/dist/doctor.js
CHANGED
|
@@ -294,7 +294,10 @@ export async function doctor() {
|
|
|
294
294
|
// iMessage or Telegram should never be told to create a Slack connector.
|
|
295
295
|
const hasSlackChannel = existsSync(join(cwd, "agent/channels/slack.ts"));
|
|
296
296
|
if (hasSlackChannel) {
|
|
297
|
-
|
|
297
|
+
const slackSource = readFileSync(join(cwd, "agent/channels/slack.ts"), "utf8");
|
|
298
|
+
if (slackSource.includes("controlPlaneSlackCredentials"))
|
|
299
|
+
add("pass", "Slack via the control plane (this agent's own Slack app)", "the owner adds it from the Channels page");
|
|
300
|
+
else if (env.SLACK_CONNECTOR_UID)
|
|
298
301
|
add("pass", `Slack connector uid: ${env.SLACK_CONNECTOR_UID}`, "verify trigger path /eve/v1/slack (vercel connect list)");
|
|
299
302
|
else if (env.SLACK_BOT_TOKEN)
|
|
300
303
|
add("pass", "Slack via portable credentials (SLACK_BOT_TOKEN)");
|
package/dist/templates.js
CHANGED
|
@@ -260,19 +260,23 @@ export function channelPlan(kind, name, host) {
|
|
|
260
260
|
return {
|
|
261
261
|
file: "slack.ts",
|
|
262
262
|
content: onExe
|
|
263
|
-
? `import {
|
|
264
|
-
import {
|
|
265
|
-
|
|
266
|
-
// Slack
|
|
267
|
-
//
|
|
268
|
-
//
|
|
269
|
-
//
|
|
270
|
-
//
|
|
263
|
+
? `import { channelIdentity } from "@kybernesis/enterprise";
|
|
264
|
+
import { controlPlaneSlackCredentials, multiplayerSlackChannel } from "@kybernesis/multiplayer/slack";
|
|
265
|
+
|
|
266
|
+
// Slack as this agent's own Slack app: its own name, @handle and picture in the
|
|
267
|
+
// customer's workspace. The control plane makes the app and keeps its signing
|
|
268
|
+
// secret and bot token in the customer's org; this channel reads them with the
|
|
269
|
+
// agent credential at call time, so installing or removing the app from the
|
|
270
|
+
// Channels page needs no restart and nothing Slack-related lives on this host.
|
|
271
|
+
//
|
|
272
|
+
// Every sender is a control-plane person, never a bare Slack user: an unlinked
|
|
273
|
+
// one gets a private DM with a one-time claim link and nothing else.
|
|
274
|
+
const issuer = process.env.KYBERNESIS_ISSUER ?? "https://agent.kybernesis.ai";
|
|
275
|
+
const credential = process.env.KYBERNESIS_AGENT_CREDENTIAL;
|
|
276
|
+
|
|
271
277
|
export default multiplayerSlackChannel({
|
|
272
|
-
credentials: {
|
|
273
|
-
|
|
274
|
-
webhookVerifier: forwardedSocketVerifier(),
|
|
275
|
-
},
|
|
278
|
+
credentials: controlPlaneSlackCredentials({ issuer, credential }),
|
|
279
|
+
identity: credential ? channelIdentity({ issuer, credential }) : null,
|
|
276
280
|
});
|
|
277
281
|
`
|
|
278
282
|
: `import { connectSlackCredentials } from "@vercel/connect/eve";
|
|
@@ -284,20 +288,13 @@ export default multiplayerSlackChannel({
|
|
|
284
288
|
credentials: connectSlackCredentials(process.env.SLACK_CONNECTOR_UID!),
|
|
285
289
|
});
|
|
286
290
|
`,
|
|
287
|
-
deps: ["@kybernesis/multiplayer",
|
|
291
|
+
deps: onExe ? ["@kybernesis/multiplayer@^0.6.0", "@kybernesis/enterprise"] : ["@kybernesis/multiplayer", "@vercel/connect"],
|
|
288
292
|
registryItems: [],
|
|
289
|
-
env: onExe
|
|
290
|
-
? [
|
|
291
|
-
`SLACK_BOT_TOKEN="xoxb-..." # outbound; from Slack OAuth & Permissions`,
|
|
292
|
-
`SLACK_SOCKET_FORWARDING_SECRET="$(openssl rand -hex 24)"`,
|
|
293
|
-
]
|
|
294
|
-
: [`SLACK_CONNECTOR_UID="slack/${name}"`],
|
|
293
|
+
env: onExe ? [] : [`SLACK_CONNECTOR_UID="slack/${name}"`],
|
|
295
294
|
steps: onExe
|
|
296
295
|
? [
|
|
297
|
-
`
|
|
298
|
-
`
|
|
299
|
-
`Run the forwarder (scripts/slack-forwarder.py in @kybernesis/exe) with EXE_SLACK_GW set`,
|
|
300
|
-
`After ANY scope change, reinstall the app — a stale token silently stops receiving events`,
|
|
296
|
+
`Make the host public so Slack can reach /eve/v1/slack (ssh exe.dev share set-public <vm>)`,
|
|
297
|
+
`The control plane makes this agent's Slack app; the owner adds it to their workspace from the Channels page`,
|
|
301
298
|
]
|
|
302
299
|
: [
|
|
303
300
|
`vercel connect create slack --triggers --name ${name}`,
|
|
@@ -305,40 +302,72 @@ export default multiplayerSlackChannel({
|
|
|
305
302
|
],
|
|
306
303
|
};
|
|
307
304
|
case "imessage":
|
|
308
|
-
return
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
305
|
+
return onExe
|
|
306
|
+
? {
|
|
307
|
+
file: "linq.ts",
|
|
308
|
+
content: `import { channelIdentity } from "@kybernesis/enterprise";
|
|
309
|
+
import { linqEnvCredentials, linqSenderIdentity } from "@kybernesis/exe/linq";
|
|
310
|
+
import { linqChannel } from "eve/channels/linq";
|
|
311
|
+
|
|
312
|
+
// iMessage and SMS via the customer's own Linq account. Inbound is a signed
|
|
313
|
+
// webhook at /eve/v1/linq. The owner connects a line from the Channels page
|
|
314
|
+
// (the manage linq.signup and linq.verify actions), which writes LINQ_API_KEY
|
|
315
|
+
// and LINQ_WEBHOOK_SECRET and registers this host's webhook.
|
|
316
|
+
//
|
|
317
|
+
// A sender is a control-plane person, never a phone number: an unlinked number
|
|
318
|
+
// gets one text with a claim link and nothing else.
|
|
319
|
+
const issuer = process.env.KYBERNESIS_ISSUER ?? "https://agent.kybernesis.ai";
|
|
320
|
+
const credential = process.env.KYBERNESIS_AGENT_CREDENTIAL;
|
|
321
|
+
const agentName = process.env.KYBERNESIS_AGENT;
|
|
322
|
+
|
|
323
|
+
export default linqChannel({
|
|
324
|
+
credentials: linqEnvCredentials(),
|
|
325
|
+
...(agentName ? { userName: agentName } : {}),
|
|
326
|
+
onMessage: linqSenderIdentity({
|
|
327
|
+
issuer,
|
|
328
|
+
agentName,
|
|
329
|
+
identity: credential ? channelIdentity({ issuer, credential }) : null,
|
|
330
|
+
}),
|
|
331
|
+
});
|
|
332
|
+
`,
|
|
333
|
+
deps: ["@kybernesis/exe", "@kybernesis/enterprise"],
|
|
334
|
+
registryItems: [],
|
|
335
|
+
env: [],
|
|
336
|
+
steps: [
|
|
337
|
+
`Make the host public so Linq can reach /eve/v1/linq (ssh exe.dev share set-public <vm>)`,
|
|
338
|
+
`The owner connects a line from the Channels page; it signs up their own Linq account with their email`,
|
|
339
|
+
],
|
|
340
|
+
}
|
|
341
|
+
: {
|
|
342
|
+
file: "photon.ts",
|
|
343
|
+
content: `import { photonIMessageChannel } from "eve/channels/photon";
|
|
344
|
+
|
|
312
345
|
// iMessage via Photon. Inbound is a plain webhook at /eve/v1/photon; the Photon
|
|
313
346
|
// signing secret takes precedence over the default Vercel-OIDC verifier, so this
|
|
314
347
|
// works on any host with a public HTTPS URL.
|
|
315
348
|
export default photonIMessageChannel({
|
|
316
|
-
|
|
317
|
-
? ` credentials: photonEnvCredentials(),`
|
|
318
|
-
: ` async credentials() {
|
|
349
|
+
async credentials() {
|
|
319
350
|
const projectId = process.env.IMESSAGE_PROJECT_ID;
|
|
320
351
|
const projectSecret = process.env.IMESSAGE_PROJECT_SECRET;
|
|
321
352
|
if (!projectId || !projectSecret) throw new Error("Photon project credentials are required.");
|
|
322
353
|
return { projectId, projectSecret };
|
|
323
|
-
}
|
|
354
|
+
},
|
|
324
355
|
webhookSecret: process.env.IMESSAGE_WEBHOOK_SECRET,
|
|
325
356
|
});
|
|
326
357
|
`,
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
],
|
|
341
|
-
};
|
|
358
|
+
deps: [],
|
|
359
|
+
registryItems: [],
|
|
360
|
+
env: [
|
|
361
|
+
`IMESSAGE_PROJECT_ID="..."`,
|
|
362
|
+
`IMESSAGE_PROJECT_SECRET="..."`,
|
|
363
|
+
`IMESSAGE_WEBHOOK_SECRET="..." # Photon webhook signing secret`,
|
|
364
|
+
],
|
|
365
|
+
steps: [
|
|
366
|
+
`Create a Photon project and register the phone number (npx eve add channel/photon-imessage walks it)`,
|
|
367
|
+
`Deploy so the public URL exists`,
|
|
368
|
+
`Register a Photon webhook for https://<your-host>/eve/v1/photon and copy its signing secret to IMESSAGE_WEBHOOK_SECRET`,
|
|
369
|
+
],
|
|
370
|
+
};
|
|
342
371
|
case "telegram":
|
|
343
372
|
return {
|
|
344
373
|
file: "telegram.ts",
|
package/dist/upgrade.js
CHANGED
|
@@ -4,6 +4,7 @@ import { tmpdir } from "node:os";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { pathToFileURL } from "node:url";
|
|
6
6
|
import { upsertEnv } from "./envfile.js";
|
|
7
|
+
import { BUZZ_EXTENSION_MOUNT } from "./buzz.js";
|
|
7
8
|
import { reconcileHostArtifact } from "./host-artifacts.js";
|
|
8
9
|
import { assessPeerTree, staleRangeWarning } from "./peer-tree.js";
|
|
9
10
|
import { dockerPruneCronArtifact } from "./docker-prune-cron.js";
|
|
@@ -101,18 +102,7 @@ function repairBuzzSetup(cwd, deps) {
|
|
|
101
102
|
const mount = join(cwd, "agent/extensions/buzz.ts");
|
|
102
103
|
if (!existsSync(mount)) {
|
|
103
104
|
mkdirSync(join(cwd, "agent/extensions"), { recursive: true });
|
|
104
|
-
writeFileSync(mount,
|
|
105
|
-
` +
|
|
106
|
-
`// long-form notes, channel canvases, workflows, the feed, media — everything the
|
|
107
|
-
` +
|
|
108
|
-
`// workspace has beyond talking, which the bridge alone does not provide.
|
|
109
|
-
` +
|
|
110
|
-
`//
|
|
111
|
-
` +
|
|
112
|
-
`// Actions are signed with THIS AGENT's key and appear under its name.
|
|
113
|
-
` +
|
|
114
|
-
`export { default } from "@kybernesis/buzz/extension";
|
|
115
|
-
`);
|
|
105
|
+
writeFileSync(mount, BUZZ_EXTENSION_MOUNT);
|
|
116
106
|
done.push("mounted the Buzz extension (agent/extensions/buzz.ts)");
|
|
117
107
|
}
|
|
118
108
|
/**
|
|
@@ -124,7 +114,10 @@ function repairBuzzSetup(cwd, deps) {
|
|
|
124
114
|
*/
|
|
125
115
|
const envPath = join(cwd, ".env.local");
|
|
126
116
|
const env = existsSync(envPath) ? readFileSync(envPath, "utf8") : "";
|
|
127
|
-
|
|
117
|
+
// An agent set up by `kyb add buzz` has no relay until its owner connects a
|
|
118
|
+
// community from the Channels page; that is waiting, not broken.
|
|
119
|
+
const awaitingOwner = /^BUZZ_KEYFILE=/m.test(env) && capture("sh", ["-c", "grep -l 'kyb add buzz' /etc/systemd/system/*buzz-bridge.service 2>/dev/null"]);
|
|
120
|
+
if (!/^BUZZ_RELAY=/m.test(env) && !awaitingOwner) {
|
|
128
121
|
const unit = capture("sh", [
|
|
129
122
|
"-c",
|
|
130
123
|
"grep -ohE 'BUZZ_(RELAY|KEYFILE)=[^ \"]+' /etc/systemd/system/*buzz-bridge.service 2>/dev/null | head -2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kybernesis/create",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "The Kybernesis agent scaffolder and FDE toolkit: one command to a governed, remembering, multiplayer, self-testing eve agent \u2014 plus doctor and upgrade.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -82,7 +82,10 @@ then `eve add @kybernesis/<item>`). Each covers one axis:
|
|
|
82
82
|
- **manage** — the other direction: `manageChannel()` lets Studio install
|
|
83
83
|
capabilities and write schedules onto a running agent, and `routineTools()`
|
|
84
84
|
turns "every morning at 8, brief me" into a real schedule file. This is how a
|
|
85
|
-
routine gets created from chat without anyone touching the repo.
|
|
85
|
+
routine gets created from chat without anyone touching the repo. The registry
|
|
86
|
+
item writes `create_routine`, `list_routines` and `delete_routine` into
|
|
87
|
+
`agent/tools/` (sharing `agent/lib/routines.ts`, which reads the restart from
|
|
88
|
+
`agent/channels/kyb.ts`), so `kyb init --studio` agents have them. Since 0.11
|
|
86
89
|
it also carries the owner's setup routes (`GET /eve/v1/kyb/setup`,
|
|
87
90
|
`POST .../setup/env`, `POST .../setup/action`): a `manage` grant only;
|
|
88
91
|
allowlisted env (`EXE_MODEL`, `EXE_LLM_URL`, `ANTHROPIC_API_KEY`,
|