broods 0.2.1 → 0.4.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/README.md +89 -0
- package/dist/account.d.ts +802 -0
- package/dist/account.js +290 -0
- package/dist/cli/index.js +414 -157
- package/dist/index.d.ts +530 -80
- package/dist/index.js +419 -25
- package/package.json +12 -8
package/dist/cli/index.js
CHANGED
|
@@ -2,18 +2,19 @@
|
|
|
2
2
|
// @bun
|
|
3
3
|
|
|
4
4
|
// src/cli/index.ts
|
|
5
|
-
import { mkdir as mkdir3, readFile as readFile3, readdir as readdir2, writeFile as
|
|
5
|
+
import { mkdir as mkdir3, readFile as readFile3, readdir as readdir2, writeFile as writeFile4 } from "fs/promises";
|
|
6
6
|
import { createHash as createHash2 } from "crypto";
|
|
7
7
|
import { basename as basename2, dirname as dirname2, join as join4, relative as relative2, resolve as resolve4 } from "path";
|
|
8
8
|
import { watch } from "fs";
|
|
9
9
|
import { spawn as spawn2 } from "child_process";
|
|
10
|
-
import { tmpdir } from "os";
|
|
10
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
11
11
|
import { performance } from "perf_hooks";
|
|
12
12
|
|
|
13
13
|
// src/manifest.ts
|
|
14
14
|
import { pathToFileURL } from "url";
|
|
15
15
|
import { createHash } from "crypto";
|
|
16
|
-
import { readdir, readFile as readFile2 } from "fs/promises";
|
|
16
|
+
import { mkdtemp, readdir, readFile as readFile2, rm, writeFile as writeFile2 } from "fs/promises";
|
|
17
|
+
import { tmpdir } from "os";
|
|
17
18
|
import { basename, isAbsolute, join as join3, relative, resolve as resolve2 } from "path";
|
|
18
19
|
|
|
19
20
|
// src/config.ts
|
|
@@ -23,6 +24,9 @@ import { dirname, join } from "path";
|
|
|
23
24
|
var PROJECT_DIR = "broods";
|
|
24
25
|
var GENERATED_DIR = "_generated";
|
|
25
26
|
var USER_CONFIG_PATH = join(homedir(), ".broods", "config.json");
|
|
27
|
+
var BROODS_APEX_DOMAIN = "broods.app";
|
|
28
|
+
var DASHBOARD_HOST_PREFIX = "dashboard";
|
|
29
|
+
var GATEWAY_HOST_PREFIX = "gateway";
|
|
26
30
|
async function readStoredAuth() {
|
|
27
31
|
const envToken = process.env.BROODS_TOKEN;
|
|
28
32
|
const envConvexUrl = process.env.BROODS_BASE_URL;
|
|
@@ -50,6 +54,20 @@ async function writeStoredAuth(config) {
|
|
|
50
54
|
function stripTrailingSlash(value) {
|
|
51
55
|
return value.replace(/\/+$/, "");
|
|
52
56
|
}
|
|
57
|
+
function gatewayUrlForDashboard(dashboardUrl) {
|
|
58
|
+
try {
|
|
59
|
+
const url = new URL(dashboardUrl);
|
|
60
|
+
if (!url.hostname.startsWith(`${DASHBOARD_HOST_PREFIX}.`))
|
|
61
|
+
return;
|
|
62
|
+
const rest = url.hostname.slice(DASHBOARD_HOST_PREFIX.length + 1);
|
|
63
|
+
if (rest !== BROODS_APEX_DOMAIN && !rest.endsWith(`.${BROODS_APEX_DOMAIN}`))
|
|
64
|
+
return;
|
|
65
|
+
url.hostname = `${GATEWAY_HOST_PREFIX}.${rest}`;
|
|
66
|
+
return stripTrailingSlash(url.origin);
|
|
67
|
+
} catch {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
53
71
|
|
|
54
72
|
// src/runtime-config.ts
|
|
55
73
|
import { existsSync, readFileSync, statSync } from "fs";
|
|
@@ -137,25 +155,27 @@ function readStoredAuthSync() {
|
|
|
137
155
|
var RESOURCE_MARKER = Symbol.for("broods.resource");
|
|
138
156
|
var CONFIG_MARKER = Symbol.for("broods.config");
|
|
139
157
|
var CHANNEL_MARKER = Symbol.for("broods.channel");
|
|
140
|
-
var
|
|
158
|
+
var ENV_NAME_PATTERN = /^[A-Z][A-Z0-9_]{0,63}$/;
|
|
159
|
+
var env = new Proxy((name) => {
|
|
160
|
+
if (!ENV_NAME_PATTERN.test(name)) {
|
|
161
|
+
throw new Error("env name must match /^[A-Z][A-Z0-9_]*$/ and be at most 64 characters.");
|
|
162
|
+
}
|
|
141
163
|
return { __beeblastEnv: true, name };
|
|
142
164
|
}, {
|
|
143
165
|
get(target, property, receiver) {
|
|
144
|
-
if (typeof property === "string")
|
|
145
|
-
|
|
166
|
+
if (typeof property === "string" && ENV_NAME_PATTERN.test(property)) {
|
|
167
|
+
throw new Error(`env.${property} is not supported; use env("${property}")`);
|
|
168
|
+
}
|
|
146
169
|
return Reflect.get(target, property, receiver);
|
|
147
170
|
}
|
|
148
171
|
});
|
|
149
|
-
function defineResource(kind,
|
|
150
|
-
if (input.config === undefined) {
|
|
151
|
-
throw new Error(`Resource "${input.name}" must include config`);
|
|
152
|
-
}
|
|
172
|
+
function defineResource(kind, name, description, config) {
|
|
153
173
|
return {
|
|
154
174
|
[RESOURCE_MARKER]: true,
|
|
155
175
|
kind,
|
|
156
|
-
name
|
|
157
|
-
...
|
|
158
|
-
config
|
|
176
|
+
name,
|
|
177
|
+
...description ? { description } : {},
|
|
178
|
+
config
|
|
159
179
|
};
|
|
160
180
|
}
|
|
161
181
|
function defineChannel(type, config) {
|
|
@@ -193,25 +213,32 @@ function defineBroods(config) {
|
|
|
193
213
|
return { [CONFIG_MARKER]: true, config };
|
|
194
214
|
}
|
|
195
215
|
function defineAgent(input) {
|
|
196
|
-
|
|
216
|
+
const { name, description, ...config } = input;
|
|
217
|
+
return defineResource("agent", name, description, config);
|
|
197
218
|
}
|
|
198
219
|
function defineWorkspace(input) {
|
|
199
|
-
|
|
220
|
+
const { name, description, ...config } = input;
|
|
221
|
+
return defineResource("workspace", name, description, config);
|
|
200
222
|
}
|
|
201
223
|
function defineSandbox(input) {
|
|
202
|
-
|
|
224
|
+
const { name, description, ...config } = input;
|
|
225
|
+
return defineResource("sandbox", name, description, config);
|
|
203
226
|
}
|
|
204
227
|
function defineSkill(input) {
|
|
205
|
-
|
|
228
|
+
const { name, description, ...config } = input;
|
|
229
|
+
return defineResource("skill", name, description, config);
|
|
206
230
|
}
|
|
207
231
|
function defineTool(input) {
|
|
208
|
-
|
|
232
|
+
const { name, ...config } = input;
|
|
233
|
+
return defineResource("tool", name, undefined, config);
|
|
209
234
|
}
|
|
210
235
|
function definePolicy(input) {
|
|
211
|
-
|
|
236
|
+
const { name, description, ...config } = input;
|
|
237
|
+
return defineResource("policy", name, description, config);
|
|
212
238
|
}
|
|
213
239
|
function defineCron(input) {
|
|
214
|
-
|
|
240
|
+
const { name, description, ...config } = input;
|
|
241
|
+
return defineResource("cron", name, description, config);
|
|
215
242
|
}
|
|
216
243
|
function isResource(value) {
|
|
217
244
|
return Boolean(value && typeof value === "object" && value[RESOURCE_MARKER]);
|
|
@@ -224,8 +251,20 @@ function isBroodsConfig(value) {
|
|
|
224
251
|
}
|
|
225
252
|
|
|
226
253
|
// src/manifest.ts
|
|
227
|
-
var
|
|
228
|
-
|
|
254
|
+
var SDK_STUB_SOURCE = `const passthrough = (input) => input;
|
|
255
|
+
export const defineTool = passthrough;
|
|
256
|
+
export const defineAgent = passthrough;
|
|
257
|
+
export const defineWorkspace = passthrough;
|
|
258
|
+
export const defineSandbox = passthrough;
|
|
259
|
+
export const defineSkill = passthrough;
|
|
260
|
+
export const definePolicy = passthrough;
|
|
261
|
+
export const defineCron = passthrough;
|
|
262
|
+
export const defineBroods = passthrough;
|
|
263
|
+
export const env = (name) => ({ __beeblastEnv: true, name });
|
|
264
|
+
export default {};
|
|
265
|
+
`;
|
|
266
|
+
var MAX_BUNDLE_FILE_BYTES = 1e7;
|
|
267
|
+
var MAX_BUNDLE_TOTAL_BYTES = 20000000;
|
|
229
268
|
var MAX_BUNDLE_FILES = 200;
|
|
230
269
|
var SKIPPED_BUNDLE_DIRECTORIES = new Set(["node_modules", ".git"]);
|
|
231
270
|
var UNSAFE_BUNDLE_FILE_NAMES = [
|
|
@@ -252,7 +291,11 @@ async function compileProject(options = {}) {
|
|
|
252
291
|
const files = await listTypeScriptFiles(root);
|
|
253
292
|
const exports = await loadExports(files);
|
|
254
293
|
const config = await findConfig(exports, cwd, options.project);
|
|
255
|
-
const resourceExports = exports.filter((entry) => isResource(entry.value)).map((entry) => ({
|
|
294
|
+
const resourceExports = exports.filter((entry) => isResource(entry.value)).map((entry) => ({
|
|
295
|
+
exportName: entry.exportName,
|
|
296
|
+
file: entry.file,
|
|
297
|
+
resource: entry.value
|
|
298
|
+
}));
|
|
256
299
|
const resources = resourceExports.map((entry) => entry.resource);
|
|
257
300
|
assertUniqueResources(resources);
|
|
258
301
|
const channels = compileChannels(resourceExports, exports);
|
|
@@ -266,7 +309,7 @@ async function compileProject(options = {}) {
|
|
|
266
309
|
assertSupportedWorkspaceSandboxMounts(resources);
|
|
267
310
|
const resourceAliases = aliasesForResources(resourceExports);
|
|
268
311
|
const environment = resolveEnvironment(config, options.environment ?? (options.useRuntimeEnvironment === false ? undefined : process.env.BROODS_ENVIRONMENT), options.command ?? "dev");
|
|
269
|
-
const manifestResources = (await Promise.all(
|
|
312
|
+
const manifestResources = (await Promise.all(resourceExports.map((entry) => toManifestResources(entry, root)))).flat().sort((a, b) => `${a.kind}:${a.name}`.localeCompare(`${b.kind}:${b.name}`));
|
|
270
313
|
return {
|
|
271
314
|
config,
|
|
272
315
|
resources,
|
|
@@ -282,25 +325,25 @@ async function compileProject(options = {}) {
|
|
|
282
325
|
}
|
|
283
326
|
function collectEnvRefNames(manifest) {
|
|
284
327
|
const names = new Set;
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
328
|
+
for (const resource of manifest.resources)
|
|
329
|
+
collectEnvRefNamesFromValue(resource.config, names);
|
|
330
|
+
return [...names].sort();
|
|
331
|
+
}
|
|
332
|
+
function collectEnvRefNamesFromValue(value, names) {
|
|
333
|
+
if (Array.isArray(value)) {
|
|
334
|
+
for (const entry of value)
|
|
335
|
+
collectEnvRefNamesFromValue(entry, names);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
if (value && typeof value === "object") {
|
|
339
|
+
const record = value;
|
|
340
|
+
if (record.__beeblastEnv === true && typeof record.name === "string") {
|
|
341
|
+
names.add(record.name);
|
|
289
342
|
return;
|
|
290
343
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
if (record.__beeblastEnv === true && typeof record.name === "string") {
|
|
294
|
-
names.add(record.name);
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
for (const entry of Object.values(record))
|
|
298
|
-
walk(entry);
|
|
299
|
-
}
|
|
344
|
+
for (const entry of Object.values(record))
|
|
345
|
+
collectEnvRefNamesFromValue(entry, names);
|
|
300
346
|
}
|
|
301
|
-
for (const resource of manifest.resources)
|
|
302
|
-
walk(resource.config);
|
|
303
|
-
return [...names].sort();
|
|
304
347
|
}
|
|
305
348
|
function resolveEnvironment(config, explicit, command) {
|
|
306
349
|
if (explicit)
|
|
@@ -333,7 +376,11 @@ async function loadExports(files) {
|
|
|
333
376
|
for (const file of files) {
|
|
334
377
|
const href = `${pathToFileURL(file).href}?t=${Date.now()}`;
|
|
335
378
|
const mod = await import(href);
|
|
336
|
-
values.push(...Object.entries(mod).map(([exportName, value]) => ({
|
|
379
|
+
values.push(...Object.entries(mod).map(([exportName, value]) => ({
|
|
380
|
+
exportName,
|
|
381
|
+
file,
|
|
382
|
+
value
|
|
383
|
+
})));
|
|
337
384
|
}
|
|
338
385
|
return values;
|
|
339
386
|
}
|
|
@@ -550,7 +597,7 @@ function supportsS3WorkspaceMount(sandbox) {
|
|
|
550
597
|
return Boolean(options && typeof options === "object" && !Array.isArray(options) && options.mountAwsS3Buckets === true);
|
|
551
598
|
}
|
|
552
599
|
function sandboxProvider(sandbox) {
|
|
553
|
-
return typeof sandbox.config.provider === "string" ? sandbox.config.provider : "
|
|
600
|
+
return typeof sandbox.config.provider === "string" ? sandbox.config.provider : "sandbox";
|
|
554
601
|
}
|
|
555
602
|
function assertUniqueResources(resources) {
|
|
556
603
|
const seen = new Set;
|
|
@@ -640,7 +687,8 @@ function capitalize(value) {
|
|
|
640
687
|
function isValidIdentifier(value) {
|
|
641
688
|
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(value);
|
|
642
689
|
}
|
|
643
|
-
async function toManifestResources(
|
|
690
|
+
async function toManifestResources(entry, projectRoot) {
|
|
691
|
+
const resource = entry.resource;
|
|
644
692
|
if (resource.kind === "agent") {
|
|
645
693
|
const normalized = normalizeAgentConfig(resource, projectRoot);
|
|
646
694
|
return [
|
|
@@ -653,19 +701,22 @@ async function toManifestResources(resource, projectRoot) {
|
|
|
653
701
|
}
|
|
654
702
|
];
|
|
655
703
|
}
|
|
656
|
-
return [
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
704
|
+
return [
|
|
705
|
+
{
|
|
706
|
+
kind: resource.kind,
|
|
707
|
+
name: resource.name,
|
|
708
|
+
...resource.description ? { description: resource.description } : {},
|
|
709
|
+
config: await normalizeConfig(entry, projectRoot)
|
|
710
|
+
}
|
|
711
|
+
];
|
|
662
712
|
}
|
|
663
|
-
async function normalizeConfig(
|
|
713
|
+
async function normalizeConfig(entry, projectRoot) {
|
|
714
|
+
const resource = entry.resource;
|
|
664
715
|
if (resource.kind === "skill") {
|
|
665
716
|
return await normalizeSkillConfig(resource.config, projectRoot);
|
|
666
717
|
}
|
|
667
718
|
if (resource.kind === "tool") {
|
|
668
|
-
return await normalizeToolConfig(resource.config, projectRoot);
|
|
719
|
+
return await normalizeToolConfig(entry, resource.config, projectRoot);
|
|
669
720
|
}
|
|
670
721
|
if (resource.kind === "policy") {
|
|
671
722
|
const config = { ...resource.config };
|
|
@@ -679,14 +730,24 @@ async function normalizeConfig(resource, projectRoot) {
|
|
|
679
730
|
config.name = config.name ?? resource.name;
|
|
680
731
|
delete config.agent;
|
|
681
732
|
if (typeof config.input === "string") {
|
|
682
|
-
config.events = [
|
|
733
|
+
config.events = [
|
|
734
|
+
{ role: "user", content: [{ type: "text", text: config.input }] }
|
|
735
|
+
];
|
|
683
736
|
delete config.input;
|
|
684
737
|
}
|
|
685
738
|
return rewriteValues(config);
|
|
686
739
|
}
|
|
687
740
|
return rewriteValues(resource.config);
|
|
688
741
|
}
|
|
689
|
-
var KNOWN_PROVIDER_NAMES = [
|
|
742
|
+
var KNOWN_PROVIDER_NAMES = [
|
|
743
|
+
"google",
|
|
744
|
+
"openai",
|
|
745
|
+
"anthropic",
|
|
746
|
+
"bedrock",
|
|
747
|
+
"gateway",
|
|
748
|
+
"minimax",
|
|
749
|
+
"custom"
|
|
750
|
+
];
|
|
690
751
|
var KNOWN_PROVIDER_SETTING_KEYS = new Set([
|
|
691
752
|
"apiKey",
|
|
692
753
|
"base_url",
|
|
@@ -786,11 +847,13 @@ function normalizeInlineAgentHooks(agentName, hooks) {
|
|
|
786
847
|
if (typeof handler !== "function") {
|
|
787
848
|
throw new Error(`Agent "${agentName}" config.hooks.${name} must be a function`);
|
|
788
849
|
}
|
|
789
|
-
return [
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
850
|
+
return [
|
|
851
|
+
{
|
|
852
|
+
name,
|
|
853
|
+
event: INLINE_AGENT_HOOK_EVENTS[name],
|
|
854
|
+
source: toHookFunctionExpression(handler.toString())
|
|
855
|
+
}
|
|
856
|
+
];
|
|
794
857
|
});
|
|
795
858
|
if (entries.length === 0)
|
|
796
859
|
return;
|
|
@@ -904,26 +967,43 @@ async function normalizeSkillConfig(config, projectRoot) {
|
|
|
904
967
|
files
|
|
905
968
|
};
|
|
906
969
|
}
|
|
907
|
-
async function normalizeToolConfig(config, projectRoot) {
|
|
908
|
-
const
|
|
970
|
+
async function normalizeToolConfig(entry, config, projectRoot) {
|
|
971
|
+
const defaultConfigEnvRefs = new Set;
|
|
972
|
+
collectEnvRefNamesFromValue(config.defaultConfig, defaultConfigEnvRefs);
|
|
973
|
+
if (defaultConfigEnvRefs.size > 0) {
|
|
974
|
+
throw new Error(`Tool "${entry.resource.name}" defaultConfig cannot contain env("NAME") references; put environment values in the agent's tools.<tool>.config so they stay encrypted and environment-scoped`);
|
|
975
|
+
}
|
|
976
|
+
const bundlePath = config.path ? resolveContainedResourcePath(projectRoot, config.path, "Tool") : entry.file;
|
|
909
977
|
const manifestPath = relative(projectRoot, bundlePath).split("\\").join("/");
|
|
910
978
|
assertSafeBundlePath(manifestPath, "Tool");
|
|
911
979
|
const sourceSize = Buffer.byteLength(await readFile2(bundlePath));
|
|
912
980
|
if (sourceSize > MAX_BUNDLE_FILE_BYTES) {
|
|
913
981
|
throw new Error(`Tool source ${manifestPath} is too large (${sourceSize} bytes, max ${MAX_BUNDLE_FILE_BYTES})`);
|
|
914
982
|
}
|
|
915
|
-
const
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
minify: false
|
|
921
|
-
});
|
|
922
|
-
if (!build.success || build.outputs.length !== 1) {
|
|
923
|
-
const details = build.logs.map((entry) => entry.message).filter(Boolean).join("; ");
|
|
924
|
-
throw new Error(`Tool bundle ${manifestPath} failed to build${details ? `: ${details}` : ""}`);
|
|
983
|
+
const shimDir = await mkdtemp(join3(tmpdir(), "broods-tool-"));
|
|
984
|
+
const shimPath = join3(shimDir, "tool-adapter.mjs");
|
|
985
|
+
await writeFile2(shimPath, toolShimSource(bundlePath, config.path, entry), "utf8");
|
|
986
|
+
if (!config.path) {
|
|
987
|
+
await writeFile2(join3(shimDir, "broods-stub.mjs"), SDK_STUB_SOURCE, "utf8");
|
|
925
988
|
}
|
|
926
|
-
|
|
989
|
+
let bundle;
|
|
990
|
+
try {
|
|
991
|
+
const build = await Bun.build({
|
|
992
|
+
entrypoints: [shimPath],
|
|
993
|
+
target: "node",
|
|
994
|
+
format: "esm",
|
|
995
|
+
minify: false,
|
|
996
|
+
plugins: config.path ? [] : [sdkStubPlugin(shimDir)]
|
|
997
|
+
});
|
|
998
|
+
if (!build.success || build.outputs.length !== 1) {
|
|
999
|
+
const details = build.logs.map((entry2) => entry2.message).filter(Boolean).join("; ");
|
|
1000
|
+
throw new Error(`Tool bundle ${manifestPath} failed to build${details ? `: ${details}` : ""}`);
|
|
1001
|
+
}
|
|
1002
|
+
bundle = await build.outputs[0].text();
|
|
1003
|
+
} finally {
|
|
1004
|
+
await rm(shimDir, { recursive: true, force: true });
|
|
1005
|
+
}
|
|
1006
|
+
bundle = normalizeBundleComments(bundle, bundlePath, manifestPath);
|
|
927
1007
|
const bundleSize = Buffer.byteLength(bundle);
|
|
928
1008
|
if (bundleSize > MAX_BUNDLE_FILE_BYTES) {
|
|
929
1009
|
throw new Error(`Tool bundle ${manifestPath} is too large (${bundleSize} bytes, max ${MAX_BUNDLE_FILE_BYTES})`);
|
|
@@ -948,7 +1028,10 @@ function normalizeWorkspaceRef(entry, agentName) {
|
|
|
948
1028
|
if (typeof name !== "string") {
|
|
949
1029
|
throw new Error(`Agent ${agentName} workspace ref must be a defineWorkspace(...) resource or its name`);
|
|
950
1030
|
}
|
|
951
|
-
const normalized = {
|
|
1031
|
+
const normalized = {
|
|
1032
|
+
name,
|
|
1033
|
+
workspaceId: name
|
|
1034
|
+
};
|
|
952
1035
|
if ("sandbox" in entry) {
|
|
953
1036
|
const sandbox = entry.sandbox;
|
|
954
1037
|
normalized.sandbox = sandbox === null ? null : isResource(sandbox) ? sandbox.name : sandbox;
|
|
@@ -1048,21 +1131,49 @@ function contentTypeForPath(path) {
|
|
|
1048
1131
|
return "text/plain; charset=utf-8";
|
|
1049
1132
|
return "application/octet-stream";
|
|
1050
1133
|
}
|
|
1134
|
+
function sdkStubPlugin(shimDir) {
|
|
1135
|
+
const stub = join3(shimDir, "broods-stub.mjs");
|
|
1136
|
+
return {
|
|
1137
|
+
name: "broods-sdk-stub",
|
|
1138
|
+
setup(build) {
|
|
1139
|
+
build.onResolve({ filter: /^broods(\/.*)?$/ }, () => ({ path: stub }));
|
|
1140
|
+
}
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
function toolShimSource(bundlePath, explicitPath, entry) {
|
|
1144
|
+
const from = JSON.stringify(bundlePath);
|
|
1145
|
+
if (explicitPath) {
|
|
1146
|
+
return `import __toolModule from ${from};
|
|
1147
|
+
const __impl = typeof __toolModule === "function" ? __toolModule() : __toolModule;
|
|
1148
|
+
export default { name: __impl.name, execute: (input, options) => __impl.execute(options.context, input) };
|
|
1149
|
+
`;
|
|
1150
|
+
}
|
|
1151
|
+
return `import { ${entry.exportName} as __tool } from ${from};
|
|
1152
|
+
export default { name: ${JSON.stringify(entry.resource.name)}, execute: (input, options) => __tool.execute(input, options) };
|
|
1153
|
+
`;
|
|
1154
|
+
}
|
|
1155
|
+
function escapeRegExp(value) {
|
|
1156
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1157
|
+
}
|
|
1158
|
+
function normalizeBundleComments(bundle, bundlePath, manifestPath) {
|
|
1159
|
+
const sourceComment = new RegExp(`^// .*${escapeRegExp(basename(bundlePath))}$`, "gm");
|
|
1160
|
+
return bundle.replace(/^\/\/ .*tool-adapter\.mjs$/gm, () => "// tool-adapter.mjs").replace(/^\/\/ .*broods-stub\.mjs$/gm, () => "// broods-stub.mjs").replace(sourceComment, () => `// ${manifestPath}`);
|
|
1161
|
+
}
|
|
1051
1162
|
function normalizeProjectName(name) {
|
|
1052
1163
|
return name.replace(/^@/, "").replace(/\//g, "-").replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1053
1164
|
}
|
|
1054
1165
|
|
|
1055
1166
|
// src/codegen.ts
|
|
1056
|
-
import { mkdir as mkdir2, writeFile as
|
|
1167
|
+
import { mkdir as mkdir2, writeFile as writeFile3 } from "fs/promises";
|
|
1057
1168
|
import { resolve as resolve3 } from "path";
|
|
1058
1169
|
async function writeGeneratedFiles(manifest, ids, cwd = process.cwd(), aliases = {}, endpoint = null, channels = []) {
|
|
1059
1170
|
const dir = resolve3(cwd, PROJECT_DIR, GENERATED_DIR);
|
|
1060
1171
|
const scopedIds = idsForManifest(ids, manifest);
|
|
1061
1172
|
await mkdir2(dir, { recursive: true });
|
|
1062
|
-
await
|
|
1063
|
-
await
|
|
1064
|
-
await
|
|
1065
|
-
await
|
|
1173
|
+
await writeFile3(resolve3(dir, "ids.ts"), idsFile(scopedIds), "utf8");
|
|
1174
|
+
await writeFile3(resolve3(dir, "api.ts"), apiFile(manifest, scopedIds, aliases, endpoint, channels), "utf8");
|
|
1175
|
+
await writeFile3(resolve3(dir, "resources.ts"), resourcesFile(manifest), "utf8");
|
|
1176
|
+
await writeFile3(resolve3(dir, "dataModel.ts"), dataModelFile(), "utf8");
|
|
1066
1177
|
}
|
|
1067
1178
|
function idsForManifest(ids, manifest) {
|
|
1068
1179
|
const namesByKind = {
|
|
@@ -1169,7 +1280,9 @@ class BroodsSyncClient {
|
|
|
1169
1280
|
this.fetchImpl = options.fetch ?? fetch;
|
|
1170
1281
|
}
|
|
1171
1282
|
async getManifest(project, environment) {
|
|
1172
|
-
const response = await this.request(project, environment, "/manifest", {
|
|
1283
|
+
const response = await this.request(project, environment, "/manifest", {
|
|
1284
|
+
method: "GET"
|
|
1285
|
+
});
|
|
1173
1286
|
if (response.status === 404)
|
|
1174
1287
|
return null;
|
|
1175
1288
|
await assertOk(response, "Fetch manifest failed");
|
|
@@ -1185,7 +1298,9 @@ class BroodsSyncClient {
|
|
|
1185
1298
|
return await response.json();
|
|
1186
1299
|
}
|
|
1187
1300
|
async getRuntimeKey(project, environment) {
|
|
1188
|
-
const response = await this.request(project, environment, "/runtime-key", {
|
|
1301
|
+
const response = await this.request(project, environment, "/runtime-key", {
|
|
1302
|
+
method: "GET"
|
|
1303
|
+
});
|
|
1189
1304
|
if (response.status === 404)
|
|
1190
1305
|
return null;
|
|
1191
1306
|
await assertOk(response, "Fetch runtime key failed");
|
|
@@ -1209,7 +1324,9 @@ class BroodsSyncClient {
|
|
|
1209
1324
|
await assertOk(response, "Set environment variable failed");
|
|
1210
1325
|
}
|
|
1211
1326
|
async listEnv(project, environment) {
|
|
1212
|
-
const response = await this.request(project, environment, "/env", {
|
|
1327
|
+
const response = await this.request(project, environment, "/env", {
|
|
1328
|
+
method: "GET"
|
|
1329
|
+
});
|
|
1213
1330
|
await assertOk(response, "List environment variables failed");
|
|
1214
1331
|
const payload = await response.json();
|
|
1215
1332
|
return payload.variables ?? [];
|
|
@@ -1274,7 +1391,10 @@ class BroodsSyncClient {
|
|
|
1274
1391
|
}
|
|
1275
1392
|
}
|
|
1276
1393
|
function diffManifests(local, remote) {
|
|
1277
|
-
const remoteResources = new Map((remote?.resources ?? []).map((entry) => [
|
|
1394
|
+
const remoteResources = new Map((remote?.resources ?? []).map((entry) => [
|
|
1395
|
+
`${entry.kind}:${entry.name}`,
|
|
1396
|
+
entry
|
|
1397
|
+
]));
|
|
1278
1398
|
const localResources = new Map(local.resources.map((entry) => [`${entry.kind}:${entry.name}`, entry]));
|
|
1279
1399
|
const entries = [];
|
|
1280
1400
|
const unmatchedLocal = [];
|
|
@@ -1284,7 +1404,11 @@ function diffManifests(local, remote) {
|
|
|
1284
1404
|
if (!remoteResource) {
|
|
1285
1405
|
unmatchedLocal.push({ key, resource });
|
|
1286
1406
|
} else if (stableJson(snapshotResource(remoteResource)) !== stableJson(snapshotResource(resource))) {
|
|
1287
|
-
entries.push({
|
|
1407
|
+
entries.push({
|
|
1408
|
+
operation: "update",
|
|
1409
|
+
kind: resource.kind,
|
|
1410
|
+
name: resource.name
|
|
1411
|
+
});
|
|
1288
1412
|
}
|
|
1289
1413
|
}
|
|
1290
1414
|
for (const [key, resource] of remoteResources) {
|
|
@@ -1309,12 +1433,20 @@ function diffManifests(local, remote) {
|
|
|
1309
1433
|
}
|
|
1310
1434
|
for (const { key, resource } of unmatchedLocal) {
|
|
1311
1435
|
if (!renamedLocalKeys.has(key)) {
|
|
1312
|
-
entries.push({
|
|
1436
|
+
entries.push({
|
|
1437
|
+
operation: "create",
|
|
1438
|
+
kind: resource.kind,
|
|
1439
|
+
name: resource.name
|
|
1440
|
+
});
|
|
1313
1441
|
}
|
|
1314
1442
|
}
|
|
1315
1443
|
for (const { key, resource } of unmatchedRemote) {
|
|
1316
1444
|
if (!renamedRemoteKeys.has(key)) {
|
|
1317
|
-
entries.push({
|
|
1445
|
+
entries.push({
|
|
1446
|
+
operation: "delete",
|
|
1447
|
+
kind: resource.kind,
|
|
1448
|
+
name: resource.name
|
|
1449
|
+
});
|
|
1318
1450
|
}
|
|
1319
1451
|
}
|
|
1320
1452
|
return entries.sort((a, b) => diffSortKey(a).localeCompare(diffSortKey(b)));
|
|
@@ -1353,7 +1485,10 @@ function normalizeEnvRefs(value) {
|
|
|
1353
1485
|
if (record.__beeblastEnv === true && typeof record.name === "string") {
|
|
1354
1486
|
return `\${${record.name}}`;
|
|
1355
1487
|
}
|
|
1356
|
-
return Object.fromEntries(Object.entries(record).map(([key, entry]) => [
|
|
1488
|
+
return Object.fromEntries(Object.entries(record).map(([key, entry]) => [
|
|
1489
|
+
key,
|
|
1490
|
+
normalizeEnvRefs(entry)
|
|
1491
|
+
]));
|
|
1357
1492
|
}
|
|
1358
1493
|
return value;
|
|
1359
1494
|
}
|
|
@@ -1426,6 +1561,15 @@ async function* readSseStream(body) {
|
|
|
1426
1561
|
// src/client.ts
|
|
1427
1562
|
var DEFAULT_CORE_BASE_URL = "https://gateway.broods.app";
|
|
1428
1563
|
|
|
1564
|
+
class IngressAcceptedError extends Error {
|
|
1565
|
+
accepted;
|
|
1566
|
+
constructor(accepted) {
|
|
1567
|
+
super(`Ingress ${accepted.eventId} was accepted asynchronously with status ${accepted.status ?? "queued"}`);
|
|
1568
|
+
this.accepted = accepted;
|
|
1569
|
+
this.name = "IngressAcceptedError";
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1429
1573
|
class BroodsClient {
|
|
1430
1574
|
baseUrl;
|
|
1431
1575
|
apiKey;
|
|
@@ -1484,6 +1628,9 @@ class BroodsClient {
|
|
|
1484
1628
|
const response = await this.openStream(body, targetUrl);
|
|
1485
1629
|
if (!response.ok)
|
|
1486
1630
|
throw new Error(`Run failed: ${response.status} ${await response.text()}`);
|
|
1631
|
+
if (response.status === 202) {
|
|
1632
|
+
throw new IngressAcceptedError(normalizeAsyncAccepted(await response.json(), body));
|
|
1633
|
+
}
|
|
1487
1634
|
if (!response.body)
|
|
1488
1635
|
throw new Error("Run response has no body");
|
|
1489
1636
|
for await (const data of readSseStream(response.body)) {
|
|
@@ -1541,7 +1688,7 @@ class BroodsClient {
|
|
|
1541
1688
|
if (options.signal?.aborted)
|
|
1542
1689
|
throw new Error("Async status polling aborted.");
|
|
1543
1690
|
const payload = await this.getAsyncStatus(status, options);
|
|
1544
|
-
if (payload.status === "awaiting_approval" || payload.status === "completed" || payload.status === "failed" || payload.status === "not_found") {
|
|
1691
|
+
if (payload.status === "awaiting_approval" || payload.status === "completed" || payload.status === "failed" || payload.status === "expired" || payload.status === "not_found") {
|
|
1545
1692
|
return payload;
|
|
1546
1693
|
}
|
|
1547
1694
|
await sleep(intervalMs, options.signal);
|
|
@@ -1688,6 +1835,8 @@ function directRunBody(input, prefix) {
|
|
|
1688
1835
|
eventId,
|
|
1689
1836
|
conversationKey: input.conversationKey ?? eventId,
|
|
1690
1837
|
events: resolveRunEvents(input),
|
|
1838
|
+
...input.mode !== undefined ? { mode: input.mode } : {},
|
|
1839
|
+
...input.idempotencyKey !== undefined ? { idempotencyKey: input.idempotencyKey } : {},
|
|
1691
1840
|
...input.system !== undefined ? { system: input.system } : {},
|
|
1692
1841
|
...input.model !== undefined ? { model: input.model } : {}
|
|
1693
1842
|
};
|
|
@@ -1703,11 +1852,16 @@ function normalizeAsyncAccepted(payload, requestBody) {
|
|
|
1703
1852
|
const status = parseStatusUrl(statusUrl);
|
|
1704
1853
|
if (!status.statusId)
|
|
1705
1854
|
throw new Error("Async response statusUrl missing status id");
|
|
1855
|
+
const eventId = typeof payload.eventId === "string" ? payload.eventId : status.statusId;
|
|
1856
|
+
const acceptedStatus = payload.status;
|
|
1857
|
+
const requestedMode = payload.requestedMode;
|
|
1706
1858
|
return {
|
|
1707
1859
|
statusUrl,
|
|
1708
1860
|
statusId: status.statusId,
|
|
1709
|
-
eventId
|
|
1710
|
-
agentId: status.agentId ?? requestBody.agentId
|
|
1861
|
+
eventId,
|
|
1862
|
+
agentId: status.agentId ?? requestBody.agentId,
|
|
1863
|
+
...acceptedStatus === "accepted" || acceptedStatus === "queued" || acceptedStatus === "applied" || acceptedStatus === "processing" ? { status: acceptedStatus } : {},
|
|
1864
|
+
...requestedMode === "reject" || requestedMode === "followup" || requestedMode === "collect" || requestedMode === "steer" ? { requestedMode } : {}
|
|
1711
1865
|
};
|
|
1712
1866
|
}
|
|
1713
1867
|
function parseStatusUrl(statusUrl) {
|
|
@@ -1841,6 +1995,8 @@ class BroodsWebSocketClient {
|
|
|
1841
1995
|
events: resolveRunEvents(input),
|
|
1842
1996
|
sessionId: input.sessionId,
|
|
1843
1997
|
...input.eventId !== undefined ? { eventId: input.eventId } : {},
|
|
1998
|
+
...input.mode !== undefined ? { mode: input.mode } : {},
|
|
1999
|
+
...input.idempotencyKey !== undefined ? { idempotencyKey: input.idempotencyKey } : {},
|
|
1844
2000
|
...input.system !== undefined ? { system: input.system } : {},
|
|
1845
2001
|
...input.model !== undefined ? { model: input.model } : {}
|
|
1846
2002
|
}));
|
|
@@ -1851,16 +2007,17 @@ class BroodsWebSocketClient {
|
|
|
1851
2007
|
const payload = parseServerMessage(event.data);
|
|
1852
2008
|
if (!payload)
|
|
1853
2009
|
return;
|
|
1854
|
-
|
|
1855
|
-
|
|
2010
|
+
const effective = unwrapServerMessage(payload, handlers);
|
|
2011
|
+
handlers.onMessage?.(effective);
|
|
2012
|
+
switch (effective.type) {
|
|
1856
2013
|
case "meta":
|
|
1857
|
-
handlers.onMeta?.(
|
|
2014
|
+
handlers.onMeta?.(effective);
|
|
1858
2015
|
break;
|
|
1859
2016
|
case "done":
|
|
1860
2017
|
finish();
|
|
1861
2018
|
break;
|
|
1862
2019
|
case "error":
|
|
1863
|
-
fail(new Error(formatWireError(
|
|
2020
|
+
fail(new Error(formatWireError(effective.error)));
|
|
1864
2021
|
break;
|
|
1865
2022
|
}
|
|
1866
2023
|
};
|
|
@@ -1876,6 +2033,57 @@ class BroodsWebSocketClient {
|
|
|
1876
2033
|
};
|
|
1877
2034
|
return {
|
|
1878
2035
|
url,
|
|
2036
|
+
sendControl(message) {
|
|
2037
|
+
if (socket.readyState !== WS_OPEN)
|
|
2038
|
+
throw new Error("WebSocket is not open");
|
|
2039
|
+
socket.send(JSON.stringify({ type: "control", ...message }));
|
|
2040
|
+
},
|
|
2041
|
+
close
|
|
2042
|
+
};
|
|
2043
|
+
}
|
|
2044
|
+
attach(input, handlers = {}) {
|
|
2045
|
+
const WebSocketImpl = this.resolveWebSocket();
|
|
2046
|
+
const url = this.buildUrl(input);
|
|
2047
|
+
const socket = new WebSocketImpl(url);
|
|
2048
|
+
let closed = false;
|
|
2049
|
+
const close = (code = 1000, reason = "client closed") => {
|
|
2050
|
+
if (closed)
|
|
2051
|
+
return;
|
|
2052
|
+
closed = true;
|
|
2053
|
+
if (socket.readyState === WS_OPEN || socket.readyState === WS_CONNECTING)
|
|
2054
|
+
socket.close(code, reason);
|
|
2055
|
+
};
|
|
2056
|
+
socket.onopen = () => socket.send(JSON.stringify({
|
|
2057
|
+
type: "attach",
|
|
2058
|
+
...input,
|
|
2059
|
+
signal: undefined,
|
|
2060
|
+
agent: undefined
|
|
2061
|
+
}));
|
|
2062
|
+
socket.onmessage = (event) => {
|
|
2063
|
+
if (typeof event.data !== "string")
|
|
2064
|
+
return;
|
|
2065
|
+
const payload = parseServerMessage(event.data);
|
|
2066
|
+
if (!payload)
|
|
2067
|
+
return;
|
|
2068
|
+
const effective = unwrapServerMessage(payload, handlers);
|
|
2069
|
+
handlers.onMessage?.(effective);
|
|
2070
|
+
if (effective.type === "done")
|
|
2071
|
+
handlers.onDone?.();
|
|
2072
|
+
if (effective.type === "error")
|
|
2073
|
+
handlers.onError?.(new Error(formatWireError(effective.error)));
|
|
2074
|
+
};
|
|
2075
|
+
socket.onerror = () => handlers.onError?.(webSocketAccessError(this.baseUrl));
|
|
2076
|
+
socket.onclose = (event) => {
|
|
2077
|
+
if (!closed && event.code !== 1000)
|
|
2078
|
+
handlers.onError?.(new Error(event.reason || "WebSocket connection closed."));
|
|
2079
|
+
};
|
|
2080
|
+
return {
|
|
2081
|
+
url,
|
|
2082
|
+
sendControl(message) {
|
|
2083
|
+
if (socket.readyState !== WS_OPEN)
|
|
2084
|
+
throw new Error("WebSocket is not open");
|
|
2085
|
+
socket.send(JSON.stringify({ type: "control", ...message }));
|
|
2086
|
+
},
|
|
1879
2087
|
close
|
|
1880
2088
|
};
|
|
1881
2089
|
}
|
|
@@ -1968,6 +2176,13 @@ function normalizeWebSocketServiceUrl(value) {
|
|
|
1968
2176
|
function webSocketAccessError(baseUrl) {
|
|
1969
2177
|
return new Error(`Cannot access the WebSocket service at ${baseUrl}.`);
|
|
1970
2178
|
}
|
|
2179
|
+
function unwrapServerMessage(payload, handlers) {
|
|
2180
|
+
if (payload.type !== "output")
|
|
2181
|
+
return payload;
|
|
2182
|
+
const output = payload;
|
|
2183
|
+
handlers.onOutput?.(output);
|
|
2184
|
+
return output.data;
|
|
2185
|
+
}
|
|
1971
2186
|
function parseServerMessage(data) {
|
|
1972
2187
|
try {
|
|
1973
2188
|
const value = JSON.parse(data);
|
|
@@ -2595,7 +2810,7 @@ function renderStreamPart(part, state) {
|
|
|
2595
2810
|
// package.json
|
|
2596
2811
|
var package_default = {
|
|
2597
2812
|
name: "broods",
|
|
2598
|
-
version: "0.
|
|
2813
|
+
version: "0.4.0",
|
|
2599
2814
|
description: "CLI + TypeScript SDK for the broods agent platform.",
|
|
2600
2815
|
type: "module",
|
|
2601
2816
|
license: "MIT",
|
|
@@ -2622,6 +2837,10 @@ var package_default = {
|
|
|
2622
2837
|
".": {
|
|
2623
2838
|
types: "./dist/index.d.ts",
|
|
2624
2839
|
import: "./dist/index.js"
|
|
2840
|
+
},
|
|
2841
|
+
"./account": {
|
|
2842
|
+
types: "./dist/account.d.ts",
|
|
2843
|
+
import: "./dist/account.js"
|
|
2625
2844
|
}
|
|
2626
2845
|
},
|
|
2627
2846
|
main: "./dist/index.js",
|
|
@@ -2641,17 +2860,17 @@ var package_default = {
|
|
|
2641
2860
|
node: ">=22"
|
|
2642
2861
|
},
|
|
2643
2862
|
dependencies: {
|
|
2644
|
-
"@chat-adapter/discord": "^4.
|
|
2645
|
-
"@chat-adapter/github": "^4.
|
|
2646
|
-
"@chat-adapter/slack": "^4.
|
|
2647
|
-
"@chat-adapter/telegram": "^4.
|
|
2648
|
-
ai: "^7.0.
|
|
2863
|
+
"@chat-adapter/discord": "^4.35.0",
|
|
2864
|
+
"@chat-adapter/github": "^4.35.0",
|
|
2865
|
+
"@chat-adapter/slack": "^4.35.0",
|
|
2866
|
+
"@chat-adapter/telegram": "^4.35.0",
|
|
2867
|
+
ai: "^7.0.37"
|
|
2649
2868
|
},
|
|
2650
2869
|
devDependencies: {
|
|
2651
2870
|
"@types/bun": "^1.3.14",
|
|
2652
|
-
rollup: "^4.
|
|
2871
|
+
rollup: "^4.62.2",
|
|
2653
2872
|
typescript: "^6.0.3",
|
|
2654
|
-
"rollup-plugin-dts": "^6.
|
|
2873
|
+
"rollup-plugin-dts": "^6.4.1"
|
|
2655
2874
|
}
|
|
2656
2875
|
};
|
|
2657
2876
|
|
|
@@ -2672,7 +2891,7 @@ Commands:
|
|
|
2672
2891
|
init Create a broods/ project shell
|
|
2673
2892
|
login Authenticate with WorkOS through the dashboard
|
|
2674
2893
|
dev Watch + sync Development AND live-tail agent logs (like \`convex dev\`);
|
|
2675
|
-
confirms before deleting; auto-pushes env
|
|
2894
|
+
confirms before deleting; auto-pushes env("NAME") values from .env.local
|
|
2676
2895
|
dev --once Sync Development a single time and exit (no watch, no log stream)
|
|
2677
2896
|
diff Show local desired state vs remote state
|
|
2678
2897
|
deploy Sync Production once; writes BROODS_API_KEY to .env.local
|
|
@@ -2682,7 +2901,7 @@ Commands:
|
|
|
2682
2901
|
env list List environment variable names (values stay hidden)
|
|
2683
2902
|
env rm <name> Remove an environment variable
|
|
2684
2903
|
stream Stream live logs for the whole project/environment (Ctrl+C to stop)
|
|
2685
|
-
logs Backfill recent logs then live-tail; default 100 lines
|
|
2904
|
+
logs Backfill recent logs then live-tail; all levels, default 100 lines
|
|
2686
2905
|
(--errors / --level warn filter to WARN+; -n/--limit <n> changes backfill size)
|
|
2687
2906
|
agent list List the agents in the current project/environment scope
|
|
2688
2907
|
agent get <name> Show an agent's resources (model, sandbox, workspaces, tools, channels)
|
|
@@ -2757,8 +2976,10 @@ async function init(args) {
|
|
|
2757
2976
|
await writeStarter(resolve4(root, ".gitignore"), `_generated
|
|
2758
2977
|
.cache
|
|
2759
2978
|
`, force);
|
|
2979
|
+
const dashboardUrl = optionValue(args, "--dashboard-url") ?? DEFAULT_DASHBOARD_URL;
|
|
2760
2980
|
await writeLocalEnvDefaults({
|
|
2761
|
-
dashboardUrl
|
|
2981
|
+
dashboardUrl,
|
|
2982
|
+
baseUrl: gatewayUrlForDashboard(dashboardUrl),
|
|
2762
2983
|
project: optionValue(args, "--project") ?? inferProjectName(process.cwd()),
|
|
2763
2984
|
environment: optionValue(args, "--env") ?? "development",
|
|
2764
2985
|
region: optionValue(args, "--region") ?? DEFAULT_SERVICE_REGION,
|
|
@@ -2778,6 +2999,7 @@ async function login(args) {
|
|
|
2778
2999
|
const environment = optionValue(args, "--env") ?? process.env.BROODS_ENVIRONMENT ?? "development";
|
|
2779
3000
|
await writeLocalEnvDefaults({
|
|
2780
3001
|
dashboardUrl: auth.dashboardUrl ?? dashboardUrl,
|
|
3002
|
+
baseUrl: auth.baseUrl,
|
|
2781
3003
|
project,
|
|
2782
3004
|
environment,
|
|
2783
3005
|
region: optionValue(args, "--region") ?? process.env.BROODS_REGION ?? DEFAULT_SERVICE_REGION,
|
|
@@ -2812,7 +3034,10 @@ async function diff(args) {
|
|
|
2812
3034
|
command: "dev"
|
|
2813
3035
|
});
|
|
2814
3036
|
const auth = await requireAuth(optionValue(args, "--base-url") ?? config.baseUrl);
|
|
2815
|
-
const client = new BroodsSyncClient({
|
|
3037
|
+
const client = new BroodsSyncClient({
|
|
3038
|
+
baseUrl: auth.baseUrl,
|
|
3039
|
+
token: auth.token
|
|
3040
|
+
});
|
|
2816
3041
|
const remote = await client.getManifest(manifest.project, manifest.environment);
|
|
2817
3042
|
printDiffEntries(diffManifests(manifest, remote?.manifest ?? null));
|
|
2818
3043
|
}
|
|
@@ -2824,7 +3049,10 @@ async function deploy(args) {
|
|
|
2824
3049
|
useRuntimeEnvironment: false
|
|
2825
3050
|
});
|
|
2826
3051
|
const auth = await requireAuth(optionValue(args, "--base-url") ?? config.baseUrl);
|
|
2827
|
-
const client = new BroodsSyncClient({
|
|
3052
|
+
const client = new BroodsSyncClient({
|
|
3053
|
+
baseUrl: auth.baseUrl,
|
|
3054
|
+
token: auth.token
|
|
3055
|
+
});
|
|
2828
3056
|
const result = await client.putManifest(manifest, hasFlag(args, "--prune"), hasFlag(args, "--rotate-key"));
|
|
2829
3057
|
await writeGeneratedFiles(manifest, result.ids, process.cwd(), resourceAliases, result.deployment, channels);
|
|
2830
3058
|
await ensureGitIgnore();
|
|
@@ -2865,7 +3093,7 @@ async function dev(args) {
|
|
|
2865
3093
|
printReadyLine(performance.now() - start);
|
|
2866
3094
|
return;
|
|
2867
3095
|
}
|
|
2868
|
-
const declinedFile = join4(
|
|
3096
|
+
const declinedFile = join4(tmpdir2(), `broods-declined-${process.pid}.txt`);
|
|
2869
3097
|
const childEnv = {
|
|
2870
3098
|
...process.env,
|
|
2871
3099
|
BROODS_DECLINED_FILE: declinedFile,
|
|
@@ -2928,7 +3156,12 @@ async function streamDevLogs(args, signal) {
|
|
|
2928
3156
|
}
|
|
2929
3157
|
const minLevel = resolveMinLevel(args);
|
|
2930
3158
|
try {
|
|
2931
|
-
for await (const entry of subscribeObservabilityLogs({
|
|
3159
|
+
for await (const entry of subscribeObservabilityLogs({
|
|
3160
|
+
baseUrl: creds.baseUrl,
|
|
3161
|
+
apiKey: creds.apiKey,
|
|
3162
|
+
project,
|
|
3163
|
+
environment
|
|
3164
|
+
}, { backfill: 0, minLevel, signal })) {
|
|
2932
3165
|
console.log(formatObservabilityEntry(entry));
|
|
2933
3166
|
}
|
|
2934
3167
|
} catch (error) {
|
|
@@ -2960,6 +3193,7 @@ async function ensureLocalDevDefaults(args) {
|
|
|
2960
3193
|
const values = parseEnv2(current);
|
|
2961
3194
|
const missing = [
|
|
2962
3195
|
"BROODS_DASHBOARD_URL",
|
|
3196
|
+
"BROODS_BASE_URL",
|
|
2963
3197
|
"BROODS_PROJECT",
|
|
2964
3198
|
"BROODS_ENVIRONMENT",
|
|
2965
3199
|
"BROODS_REGION"
|
|
@@ -2976,7 +3210,10 @@ async function ensureLocalDevDefaults(args) {
|
|
|
2976
3210
|
let region = optionValue(args, "--region") ?? process.env.BROODS_REGION ?? DEFAULT_SERVICE_REGION;
|
|
2977
3211
|
if (process.stdin.isTTY && needsProject) {
|
|
2978
3212
|
const auth = await requireAuthOrLogin(dashboardUrl);
|
|
2979
|
-
const client = new BroodsSyncClient({
|
|
3213
|
+
const client = new BroodsSyncClient({
|
|
3214
|
+
baseUrl: auth.baseUrl,
|
|
3215
|
+
token: auth.token
|
|
3216
|
+
});
|
|
2980
3217
|
const context = await getOnboardingContextOrFallback(client, auth);
|
|
2981
3218
|
const selectedContext = await selectOnboardingOrg(client, context);
|
|
2982
3219
|
project = await selectOnboardingProject(selectedContext, project);
|
|
@@ -2993,6 +3230,7 @@ async function ensureLocalDevDefaults(args) {
|
|
|
2993
3230
|
}
|
|
2994
3231
|
await writeLocalEnvDefaults({
|
|
2995
3232
|
dashboardUrl,
|
|
3233
|
+
baseUrl: runtime.baseUrl ?? gatewayUrlForDashboard(dashboardUrl),
|
|
2996
3234
|
project,
|
|
2997
3235
|
environment,
|
|
2998
3236
|
region,
|
|
@@ -3018,13 +3256,15 @@ async function getOnboardingContextOrFallback(client, auth) {
|
|
|
3018
3256
|
printWarning("CLI onboarding endpoint is not available yet; using the org from the current login.");
|
|
3019
3257
|
return {
|
|
3020
3258
|
currentOrgId: auth.org.id,
|
|
3021
|
-
orgs: [
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3259
|
+
orgs: [
|
|
3260
|
+
{
|
|
3261
|
+
id: auth.org.id,
|
|
3262
|
+
name: auth.org.name,
|
|
3263
|
+
slug: auth.org.slug,
|
|
3264
|
+
role: "admin",
|
|
3265
|
+
accountStatus: "active"
|
|
3266
|
+
}
|
|
3267
|
+
],
|
|
3028
3268
|
projects: []
|
|
3029
3269
|
};
|
|
3030
3270
|
}
|
|
@@ -3053,7 +3293,11 @@ async function selectOnboardingProject(context, inferred) {
|
|
|
3053
3293
|
if (context.projects.length === 0) {
|
|
3054
3294
|
return promptText("Project name", inferred);
|
|
3055
3295
|
}
|
|
3056
|
-
const createNew = {
|
|
3296
|
+
const createNew = {
|
|
3297
|
+
kind: "create",
|
|
3298
|
+
name: defaultProjectName(context, inferred),
|
|
3299
|
+
slug: ""
|
|
3300
|
+
};
|
|
3057
3301
|
const choices = [
|
|
3058
3302
|
...context.projects,
|
|
3059
3303
|
createNew
|
|
@@ -3064,12 +3308,12 @@ async function selectOnboardingProject(context, inferred) {
|
|
|
3064
3308
|
}
|
|
3065
3309
|
return selected.name;
|
|
3066
3310
|
}
|
|
3067
|
-
function runSyncChild(args,
|
|
3311
|
+
function runSyncChild(args, env2) {
|
|
3068
3312
|
return new Promise((resolvePromise, reject) => {
|
|
3069
3313
|
const entryPoint = process.argv[1] ?? "";
|
|
3070
3314
|
const child = spawn2(process.execPath, [entryPoint, "dev", "--once", ...args], {
|
|
3071
3315
|
stdio: "inherit",
|
|
3072
|
-
env:
|
|
3316
|
+
env: env2
|
|
3073
3317
|
});
|
|
3074
3318
|
child.on("error", reject);
|
|
3075
3319
|
child.on("exit", (code, signal) => {
|
|
@@ -3088,7 +3332,10 @@ async function syncDev(args) {
|
|
|
3088
3332
|
command: "dev"
|
|
3089
3333
|
});
|
|
3090
3334
|
const auth = await requireAuth(optionValue(args, "--base-url") ?? config.baseUrl);
|
|
3091
|
-
const client = new BroodsSyncClient({
|
|
3335
|
+
const client = new BroodsSyncClient({
|
|
3336
|
+
baseUrl: auth.baseUrl,
|
|
3337
|
+
token: auth.token
|
|
3338
|
+
});
|
|
3092
3339
|
const remote = await client.getManifest(manifest.project, manifest.environment);
|
|
3093
3340
|
const diff2 = diffManifests(manifest, remote?.manifest ?? null);
|
|
3094
3341
|
printDiffEntries(diff2.filter((entry) => entry.operation !== "delete"));
|
|
@@ -3188,7 +3435,7 @@ async function loadEnvSyncCache() {
|
|
|
3188
3435
|
async function saveEnvSyncCache(cache) {
|
|
3189
3436
|
const path = envSyncCachePath();
|
|
3190
3437
|
await mkdir3(resolve4(path, ".."), { recursive: true });
|
|
3191
|
-
await
|
|
3438
|
+
await writeFile4(path, `${JSON.stringify(cache, null, 2)}
|
|
3192
3439
|
`, "utf8");
|
|
3193
3440
|
}
|
|
3194
3441
|
async function rememberEnvSyncValue(scopeKey, project, environment, name, value) {
|
|
@@ -3229,7 +3476,7 @@ async function rememberDeclinedDeletes(keys) {
|
|
|
3229
3476
|
const path = process.env.BROODS_DECLINED_FILE;
|
|
3230
3477
|
if (!path || keys.length === 0)
|
|
3231
3478
|
return;
|
|
3232
|
-
await
|
|
3479
|
+
await writeFile4(path, `${keys.join(`
|
|
3233
3480
|
`)}
|
|
3234
3481
|
`, { flag: "a" });
|
|
3235
3482
|
}
|
|
@@ -3237,7 +3484,7 @@ async function clearDeclinedDeletes() {
|
|
|
3237
3484
|
const path = process.env.BROODS_DECLINED_FILE;
|
|
3238
3485
|
if (!path)
|
|
3239
3486
|
return;
|
|
3240
|
-
await
|
|
3487
|
+
await writeFile4(path, "", "utf8");
|
|
3241
3488
|
}
|
|
3242
3489
|
async function envCommand(args) {
|
|
3243
3490
|
const subcommand = args[0];
|
|
@@ -3255,7 +3502,10 @@ async function envCommand(args) {
|
|
|
3255
3502
|
command: "dev"
|
|
3256
3503
|
});
|
|
3257
3504
|
const auth = await requireAuth(optionValue(args, "--base-url") ?? config.baseUrl);
|
|
3258
|
-
const client = new BroodsSyncClient({
|
|
3505
|
+
const client = new BroodsSyncClient({
|
|
3506
|
+
baseUrl: auth.baseUrl,
|
|
3507
|
+
token: auth.token
|
|
3508
|
+
});
|
|
3259
3509
|
const scopeKey = envSyncScopeKey(auth.baseUrl, auth.token);
|
|
3260
3510
|
const target = `${manifest.project}/${manifest.environment}`;
|
|
3261
3511
|
if (isList) {
|
|
@@ -3304,7 +3554,7 @@ function resolveMinLevel(args) {
|
|
|
3304
3554
|
return "WARN";
|
|
3305
3555
|
const raw = optionValue(args, "--level");
|
|
3306
3556
|
if (!raw)
|
|
3307
|
-
return
|
|
3557
|
+
return;
|
|
3308
3558
|
const upper = raw.toUpperCase();
|
|
3309
3559
|
if (upper === "WARN" || upper === "WARNING")
|
|
3310
3560
|
return "WARN";
|
|
@@ -3398,7 +3648,10 @@ async function loadAgentsWithIds(args) {
|
|
|
3398
3648
|
command: "dev"
|
|
3399
3649
|
});
|
|
3400
3650
|
const auth = await requireAuth(optionValue(args, "--base-url") ?? config.baseUrl);
|
|
3401
|
-
const remote = await new BroodsSyncClient({
|
|
3651
|
+
const remote = await new BroodsSyncClient({
|
|
3652
|
+
baseUrl: auth.baseUrl,
|
|
3653
|
+
token: auth.token
|
|
3654
|
+
}).getManifest(manifest.project, manifest.environment);
|
|
3402
3655
|
const agents = manifest.resources.filter((resource) => resource.kind === "agent").map((resource) => ({
|
|
3403
3656
|
name: resource.name,
|
|
3404
3657
|
config: resource.config ?? {},
|
|
@@ -3475,18 +3728,27 @@ async function run(args) {
|
|
|
3475
3728
|
const agent = manifest.resources.find((resource) => resource.kind === "agent" && resource.name === agentName);
|
|
3476
3729
|
if (!agent)
|
|
3477
3730
|
throw new Error(`Unknown local agent: ${agentName}`);
|
|
3478
|
-
const remote = await new BroodsSyncClient({
|
|
3731
|
+
const remote = await new BroodsSyncClient({
|
|
3732
|
+
baseUrl: auth.baseUrl,
|
|
3733
|
+
token: auth.token
|
|
3734
|
+
}).getManifest(manifest.project, manifest.environment);
|
|
3479
3735
|
const agentId = remote?.ids.agents[agentName];
|
|
3480
3736
|
if (!agentId)
|
|
3481
3737
|
throw new Error(`Agent ${agentName} is not synced yet. Run broods dev --once or broods deploy first.`);
|
|
3482
|
-
const runtimeKey = await new BroodsSyncClient({
|
|
3738
|
+
const runtimeKey = await new BroodsSyncClient({
|
|
3739
|
+
baseUrl: auth.baseUrl,
|
|
3740
|
+
token: auth.token
|
|
3741
|
+
}).getRuntimeKey(manifest.project, manifest.environment).catch(() => null);
|
|
3483
3742
|
if (runtimeKey?.apiKey) {
|
|
3484
3743
|
await writeEnvValue("BROODS_API_KEY", runtimeKey.apiKey);
|
|
3485
3744
|
}
|
|
3486
3745
|
if (agent.config.publicAccess !== true) {
|
|
3487
3746
|
printWarning(`\u26A0 Agent "${agentName}" does not set publicAccess: true. The public endpoint is secured by default; ` + "if the deployed agent has not enabled it, this run will be refused.");
|
|
3488
3747
|
}
|
|
3489
|
-
const client = new BroodsClient(
|
|
3748
|
+
const client = new BroodsClient({
|
|
3749
|
+
baseUrl: optionValue(args, "--base-url") ?? process.env.BROODS_BASE_URL ?? process.env.BROODS_HOST ?? auth.baseUrl,
|
|
3750
|
+
...runtimeKey?.apiKey ? { apiKey: runtimeKey.apiKey } : {}
|
|
3751
|
+
});
|
|
3490
3752
|
const state = createRenderState();
|
|
3491
3753
|
try {
|
|
3492
3754
|
for await (const part of client.stream({
|
|
@@ -3513,7 +3775,7 @@ async function run(args) {
|
|
|
3513
3775
|
}
|
|
3514
3776
|
async function writeStarter(path, contents, force) {
|
|
3515
3777
|
try {
|
|
3516
|
-
await
|
|
3778
|
+
await writeFile4(path, contents, { flag: force ? "w" : "wx" });
|
|
3517
3779
|
} catch (error) {
|
|
3518
3780
|
if (error.code === "EEXIST")
|
|
3519
3781
|
return;
|
|
@@ -3533,7 +3795,7 @@ async function ensureGitIgnore() {
|
|
|
3533
3795
|
` : missing.join(`
|
|
3534
3796
|
`) + `
|
|
3535
3797
|
`;
|
|
3536
|
-
await
|
|
3798
|
+
await writeFile4(path, body, "utf8");
|
|
3537
3799
|
}
|
|
3538
3800
|
async function writeLocalEnvDefaults(options) {
|
|
3539
3801
|
const path = resolve4(process.cwd(), ".env.local");
|
|
@@ -3541,14 +3803,13 @@ async function writeLocalEnvDefaults(options) {
|
|
|
3541
3803
|
const values = parseEnv2(current);
|
|
3542
3804
|
const nextValues = {
|
|
3543
3805
|
BROODS_DASHBOARD_URL: options.dashboardUrl,
|
|
3806
|
+
...options.baseUrl ? { BROODS_BASE_URL: options.baseUrl } : {},
|
|
3544
3807
|
BROODS_PROJECT: options.project,
|
|
3545
3808
|
BROODS_ENVIRONMENT: options.environment,
|
|
3546
3809
|
BROODS_REGION: options.region
|
|
3547
3810
|
};
|
|
3548
3811
|
const lines = current ? current.replace(/\n?$/, `
|
|
3549
|
-
`).split(/\n/) : [
|
|
3550
|
-
"# Local broods CLI settings. Tokens are stored outside the repo."
|
|
3551
|
-
];
|
|
3812
|
+
`).split(/\n/) : ["# Local broods CLI settings. Tokens are stored outside the repo."];
|
|
3552
3813
|
let changed = false;
|
|
3553
3814
|
for (const [key, value] of Object.entries(nextValues)) {
|
|
3554
3815
|
if (values[key] !== undefined && !options.force)
|
|
@@ -3567,7 +3828,7 @@ async function writeLocalEnvDefaults(options) {
|
|
|
3567
3828
|
const body = `${lines.filter((line, index, all) => !(line === "" && index === all.length - 1)).join(`
|
|
3568
3829
|
`)}
|
|
3569
3830
|
`;
|
|
3570
|
-
await
|
|
3831
|
+
await writeFile4(path, body, "utf8");
|
|
3571
3832
|
}
|
|
3572
3833
|
async function writeEnvValue(key, value) {
|
|
3573
3834
|
const path = resolve4(process.cwd(), ".env.local");
|
|
@@ -3583,7 +3844,7 @@ async function writeEnvValue(key, value) {
|
|
|
3583
3844
|
const body = `${lines.filter((line, i, all) => !(line === "" && i === all.length - 1)).join(`
|
|
3584
3845
|
`)}
|
|
3585
3846
|
`;
|
|
3586
|
-
await
|
|
3847
|
+
await writeFile4(path, body, "utf8");
|
|
3587
3848
|
}
|
|
3588
3849
|
async function readTextIfExists(path) {
|
|
3589
3850
|
try {
|
|
@@ -3649,33 +3910,29 @@ function starterAgent() {
|
|
|
3649
3910
|
` + `// A Lambda sandbox: a fresh, ephemeral bash environment created per run.
|
|
3650
3911
|
` + `export const lambdaSandbox = defineSandbox({
|
|
3651
3912
|
` + ` name: "lambda-sandbox",
|
|
3652
|
-
` + `
|
|
3653
|
-
` + `
|
|
3654
|
-
` + `
|
|
3655
|
-
` + `
|
|
3656
|
-
` + ` timeout: 60,
|
|
3657
|
-
` + ` },
|
|
3913
|
+
` + ` provider: "lambda",
|
|
3914
|
+
` + ` network: { mode: "deny-all" },
|
|
3915
|
+
` + ` permissionMode: "bypass",
|
|
3916
|
+
` + ` timeout: 60,
|
|
3658
3917
|
` + `});
|
|
3659
3918
|
|
|
3660
3919
|
` + `export const myAgent = defineAgent({
|
|
3661
3920
|
` + ` name: "my-agent",
|
|
3662
|
-
` + `
|
|
3663
|
-
` + `
|
|
3664
|
-
` + `
|
|
3665
|
-
` + `
|
|
3666
|
-
` + `
|
|
3667
|
-
` + `
|
|
3668
|
-
` + `
|
|
3669
|
-
` + `
|
|
3670
|
-
` + `
|
|
3671
|
-
` + ` system: "You are a helpful assistant.",
|
|
3672
|
-
` + ` },
|
|
3673
|
-
` + ` sandbox: lambdaSandbox,
|
|
3674
|
-
` + ` // Expose the public runtime endpoint (SSE/WebSocket) so the API key and
|
|
3675
|
-
` + ` // \`broods run\` can reach this agent. Off by default \u2014 secured: a
|
|
3676
|
-
` + ` // private agent is only reachable via internal endpoints or channel webhooks.
|
|
3677
|
-
` + ` publicAccess: true,
|
|
3921
|
+
` + ` provider: {
|
|
3922
|
+
` + ` openai: { apiKey: env("OPENAI_API_KEY") },
|
|
3923
|
+
` + ` },
|
|
3924
|
+
` + ` model: {
|
|
3925
|
+
` + ` provider: "openai",
|
|
3926
|
+
` + ` modelId: "gpt-5.5",
|
|
3927
|
+
` + ` },
|
|
3928
|
+
` + ` agent: {
|
|
3929
|
+
` + ` system: "You are a helpful assistant.",
|
|
3678
3930
|
` + ` },
|
|
3931
|
+
` + ` sandbox: lambdaSandbox,
|
|
3932
|
+
` + ` // Expose the public runtime endpoint (SSE/WebSocket) so the API key and
|
|
3933
|
+
` + ` // \`broods run\` can reach this agent. Off by default \u2014 secured: a
|
|
3934
|
+
` + ` // private agent is only reachable via internal endpoints or channel webhooks.
|
|
3935
|
+
` + ` publicAccess: true,
|
|
3679
3936
|
` + `});
|
|
3680
3937
|
`;
|
|
3681
3938
|
}
|