broods 0.1.1 → 0.2.1

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/index.js CHANGED
@@ -47,6 +47,8 @@ function isObservabilityClientMessage(v) {
47
47
  const backfill = msg["backfill"];
48
48
  if (backfill !== undefined && (typeof backfill !== "number" || !Number.isSafeInteger(backfill) || backfill < 0 || backfill > MAX_OBSERVABILITY_BACKFILL))
49
49
  return false;
50
+ if (msg["liveOnly"] !== undefined && typeof msg["liveOnly"] !== "boolean")
51
+ return false;
50
52
  const minLevel = msg["minLevel"];
51
53
  if (minLevel !== undefined && minLevel !== "INFO" && minLevel !== "WARN" && minLevel !== "ERROR")
52
54
  return false;
@@ -59,7 +61,7 @@ function isObservabilityClientMessage(v) {
59
61
  return false;
60
62
  }
61
63
  // src/runtime-config.ts
62
- import { existsSync, readFileSync } from "fs";
64
+ import { existsSync, readFileSync, statSync } from "fs";
63
65
  import { join as join2, resolve } from "path";
64
66
 
65
67
  // src/config.ts
@@ -71,16 +73,19 @@ var GENERATED_DIR = "_generated";
71
73
  var USER_CONFIG_PATH = join(homedir(), ".broods", "config.json");
72
74
  async function readStoredAuth() {
73
75
  const envToken = process.env.BROODS_TOKEN;
74
- const envUrl = process.env.BROODS_DASHBOARD_URL;
75
- if (envToken && envUrl) {
76
+ const envConvexUrl = process.env.BROODS_BASE_URL;
77
+ if (envToken && envConvexUrl) {
76
78
  return {
77
- dashboardUrl: envUrl,
79
+ baseUrl: stripTrailingSlash(envConvexUrl),
78
80
  token: envToken,
79
81
  createdAt: new Date().toISOString()
80
82
  };
81
83
  }
82
84
  try {
83
- return JSON.parse(await readFile(USER_CONFIG_PATH, "utf8"));
85
+ const stored = JSON.parse(await readFile(USER_CONFIG_PATH, "utf8"));
86
+ if (typeof stored.baseUrl !== "string" || !stored.baseUrl)
87
+ return null;
88
+ return stored;
84
89
  } catch {
85
90
  return null;
86
91
  }
@@ -95,12 +100,15 @@ function stripTrailingSlash(value) {
95
100
  }
96
101
 
97
102
  // src/runtime-config.ts
98
- var loadedEnvForCwd = null;
103
+ var envFileCache = new Map;
104
+ var realEnvSnapshot = new Set(Object.keys(process.env));
105
+ var loadedFromFiles = new Set;
99
106
  function loadBroodsRuntimeConfig(cwd = process.cwd()) {
100
107
  loadEnvFiles(cwd);
101
108
  const stored = readStoredAuthSync();
102
109
  return {
103
110
  dashboardUrl: process.env.BROODS_DASHBOARD_URL ?? stored?.dashboardUrl,
111
+ baseUrl: process.env.BROODS_BASE_URL ?? stored?.baseUrl,
104
112
  token: process.env.BROODS_TOKEN ?? stored?.token,
105
113
  project: process.env.BROODS_PROJECT,
106
114
  environment: process.env.BROODS_ENVIRONMENT
@@ -108,17 +116,23 @@ function loadBroodsRuntimeConfig(cwd = process.cwd()) {
108
116
  }
109
117
  function loadEnvFiles(cwd) {
110
118
  const root = resolve(cwd);
111
- if (loadedEnvForCwd === root)
112
- return;
113
- loadedEnvForCwd = root;
114
- const originallySet = new Set(Object.keys(process.env));
119
+ const forceReload = process.env.BROODS_RELOAD_ENV === "1";
115
120
  for (const file of [".env", ".env.local"]) {
116
121
  const path = join2(root, file);
117
- if (!existsSync(path))
122
+ if (!existsSync(path)) {
123
+ envFileCache.delete(path);
118
124
  continue;
125
+ }
126
+ const mtimeMs = statSync(path).mtimeMs;
127
+ const cached = envFileCache.get(path);
128
+ if (cached && cached.mtimeMs === mtimeMs)
129
+ continue;
130
+ envFileCache.set(path, { mtimeMs });
119
131
  for (const [key, value] of Object.entries(parseEnv(readFileSync(path, "utf8")))) {
120
- if (!originallySet.has(key))
132
+ if (forceReload || !realEnvSnapshot.has(key) || loadedFromFiles.has(key)) {
121
133
  process.env[key] = value;
134
+ loadedFromFiles.add(key);
135
+ }
122
136
  }
123
137
  }
124
138
  }
@@ -153,10 +167,11 @@ function unquoteEnvValue(value) {
153
167
  function readStoredAuthSync() {
154
168
  try {
155
169
  const value = JSON.parse(readFileSync(USER_CONFIG_PATH, "utf8"));
156
- if (typeof value.dashboardUrl !== "string" || typeof value.token !== "string")
170
+ if (typeof value.baseUrl !== "string" || typeof value.token !== "string")
157
171
  return null;
158
172
  return {
159
- dashboardUrl: stripTrailingSlash(value.dashboardUrl),
173
+ baseUrl: stripTrailingSlash(value.baseUrl),
174
+ ...typeof value.dashboardUrl === "string" ? { dashboardUrl: stripTrailingSlash(value.dashboardUrl) } : {},
160
175
  token: value.token
161
176
  };
162
177
  } catch {
@@ -185,7 +200,7 @@ class BroodsClient {
185
200
  const name = refOrName;
186
201
  const id = agentId ?? "";
187
202
  if (!id)
188
- throw new Error(`Agent ${name} is missing a generated id. Run broods deploy first.`);
203
+ throw new Error(`Agent ${name} is missing a generated id. Run broods dev --once or broods deploy to sync it first.`);
189
204
  return {
190
205
  id,
191
206
  run: (input) => this.run({ ...input, agentName: name, agentId: id }),
@@ -195,7 +210,7 @@ class BroodsClient {
195
210
  }
196
211
  const ref = refOrName;
197
212
  if (!ref.id)
198
- throw new Error(`Agent ${ref.name} is missing a generated id. Run broods deploy first.`);
213
+ throw new Error(`Agent ${ref.name} is missing a generated id. Run broods dev --once or broods deploy to sync it first.`);
199
214
  return {
200
215
  id: ref.id,
201
216
  run: (input) => this.run(ref, input),
@@ -246,7 +261,8 @@ class BroodsClient {
246
261
  agentName: refOrInput.name
247
262
  } : refOrInput;
248
263
  const body = directRunBody(input, "async");
249
- const response = await this.fetchJson(`${this.baseUrl}/async`, {
264
+ const targetUrl = maybeInput ? this.scopedUrl(refOrInput, "/async") : `${this.baseUrl}/async`;
265
+ const response = await this.fetchJson(targetUrl, {
250
266
  method: "POST",
251
267
  headers: this.apiKeyHeaders(),
252
268
  body: JSON.stringify(body)
@@ -289,7 +305,7 @@ class BroodsClient {
289
305
  throw new Error("Polling timeout");
290
306
  }
291
307
  async createCron(input) {
292
- const response = await this.fetchJson(`${this.baseUrl}/accounts/me/crons`, {
308
+ const response = await this.fetchJson(`${this.baseUrl}/v1/crons`, {
293
309
  method: "POST",
294
310
  headers: this.apiKeyHeaders(),
295
311
  body: JSON.stringify(resolveCronInput(input))
@@ -299,7 +315,7 @@ class BroodsClient {
299
315
  return await response.json();
300
316
  }
301
317
  async listCrons() {
302
- const response = await this.fetchJson(`${this.baseUrl}/accounts/me/crons`, {
318
+ const response = await this.fetchJson(`${this.baseUrl}/v1/crons`, {
303
319
  method: "GET",
304
320
  headers: this.apiKeyHeaders()
305
321
  });
@@ -309,7 +325,7 @@ class BroodsClient {
309
325
  return payload.crons;
310
326
  }
311
327
  async getCron(cronId) {
312
- const response = await this.fetchJson(`${this.baseUrl}/accounts/me/crons/${encodeURIComponent(cronId)}`, {
328
+ const response = await this.fetchJson(`${this.baseUrl}/v1/crons/${encodeURIComponent(cronId)}`, {
313
329
  method: "GET",
314
330
  headers: this.apiKeyHeaders()
315
331
  });
@@ -324,7 +340,7 @@ class BroodsClient {
324
340
  if (options.limit !== undefined)
325
341
  params.set("limit", String(options.limit));
326
342
  const suffix = params.size > 0 ? `?${params}` : "";
327
- const response = await this.fetchJson(`${this.baseUrl}/accounts/me/crons/${encodeURIComponent(cronId)}/runs${suffix}`, {
343
+ const response = await this.fetchJson(`${this.baseUrl}/v1/crons/${encodeURIComponent(cronId)}/runs${suffix}`, {
328
344
  method: "GET",
329
345
  headers: this.apiKeyHeaders()
330
346
  });
@@ -334,7 +350,7 @@ class BroodsClient {
334
350
  return payload.runs;
335
351
  }
336
352
  async updateCron(cronId, patch) {
337
- const response = await this.fetchJson(`${this.baseUrl}/accounts/me/crons/${encodeURIComponent(cronId)}`, {
353
+ const response = await this.fetchJson(`${this.baseUrl}/v1/crons/${encodeURIComponent(cronId)}`, {
338
354
  method: "PATCH",
339
355
  headers: this.apiKeyHeaders(),
340
356
  body: JSON.stringify(patch)
@@ -344,7 +360,7 @@ class BroodsClient {
344
360
  return await response.json();
345
361
  }
346
362
  async deleteCron(cronId) {
347
- const response = await this.fetchJson(`${this.baseUrl}/accounts/me/crons/${encodeURIComponent(cronId)}`, {
363
+ const response = await this.fetchJson(`${this.baseUrl}/v1/crons/${encodeURIComponent(cronId)}`, {
348
364
  method: "DELETE",
349
365
  headers: this.apiKeyHeaders()
350
366
  });
@@ -355,11 +371,11 @@ class BroodsClient {
355
371
  const payload = await response.json();
356
372
  return payload.deleted;
357
373
  }
358
- scopedUrl(ref) {
374
+ scopedUrl(ref, suffix = "") {
359
375
  if (ref.projectSlug && ref.environmentSlug && ref.endpointId) {
360
- return `${this.baseUrl}/v1/${encodeURIComponent(ref.projectSlug)}` + `/agents/${encodeURIComponent(ref.environmentSlug)}/${encodeURIComponent(ref.endpointId)}`;
376
+ return `${this.baseUrl}/v1/${encodeURIComponent(ref.projectSlug)}` + `/agents/${encodeURIComponent(ref.environmentSlug)}/${encodeURIComponent(ref.endpointId)}${suffix}`;
361
377
  }
362
- return this.baseUrl;
378
+ return `${this.baseUrl}${suffix}`;
363
379
  }
364
380
  async openStream(body, targetUrl) {
365
381
  if (this.apiKey) {
@@ -515,7 +531,6 @@ function formatStreamError(error) {
515
531
  var DEFAULT_CONNECT_TIMEOUT_MS = 2000;
516
532
  var WS_CONNECTING = 0;
517
533
  var WS_OPEN = 1;
518
- var LAMBDA_FUNCTION_URL_HOST_RE = /\.lambda-url\.[a-z0-9-]+\.on\.aws$/i;
519
534
 
520
535
  class BroodsWebSocketClient {
521
536
  baseUrl;
@@ -524,7 +539,7 @@ class BroodsWebSocketClient {
524
539
  connectTimeoutMs;
525
540
  constructor(options = {}) {
526
541
  loadBroodsRuntimeConfig();
527
- this.baseUrl = normalizeWebSocketServiceUrl(options.baseUrl || process.env.BROODS_WEBSOCKET_URL || options.host || process.env.BROODS_BASE_URL || process.env.BROODS_HOST || DEFAULT_CORE_BASE_URL);
542
+ this.baseUrl = normalizeWebSocketServiceUrl(options.baseUrl || options.host || process.env.BROODS_BASE_URL || process.env.BROODS_HOST || DEFAULT_CORE_BASE_URL);
528
543
  this.apiKey = options.apiKey || process.env.BROODS_API_KEY || "";
529
544
  if (!this.apiKey)
530
545
  throw new Error("BroodsWebSocketClient requires apiKey or BROODS_API_KEY.");
@@ -706,10 +721,6 @@ function normalizeWebSocketServiceUrl(value) {
706
721
  return normalizeHttpServiceUrl(trimmed);
707
722
  }
708
723
  function webSocketAccessError(baseUrl) {
709
- const host = new URL(baseUrl).hostname;
710
- if (LAMBDA_FUNCTION_URL_HOST_RE.test(host)) {
711
- return new Error(`Cannot access the WebSocket service at ${baseUrl}. AWS Lambda Function URLs do not support WebSocket upgrades; ` + "set BROODS_WEBSOCKET_URL or BROODS_HOST to the deployed WebSocket gateway URL instead.");
712
- }
713
724
  return new Error(`Cannot access the WebSocket service at ${baseUrl}.`);
714
725
  }
715
726
  function parseServerMessage(data) {
@@ -754,11 +765,16 @@ function defineResource(kind, input) {
754
765
  };
755
766
  }
756
767
  function defineChannel(type, config) {
768
+ const { workspaceScope, ...channelConfig } = config;
757
769
  return {
758
770
  [CHANNEL_MARKER]: true,
759
771
  kind: "channel",
760
772
  type,
761
- config
773
+ ...workspaceScope ? { workspaceScope } : {},
774
+ config: {
775
+ ...channelConfig,
776
+ ...workspaceScope ? { workspaceScope } : {}
777
+ }
762
778
  };
763
779
  }
764
780
  function defineTelegramChannel(config) {
@@ -774,11 +790,7 @@ function defineDiscordChannel(config) {
774
790
  return defineChannel("discord", config);
775
791
  }
776
792
  function definePancakeChannel(config) {
777
- const { ignoreTagIds, ...channelConfig } = config;
778
- return defineChannel("pancake", {
779
- ...channelConfig,
780
- ...ignoreTagIds === undefined ? {} : { options: { ignoreTagIds } }
781
- });
793
+ return defineChannel("pancake", config);
782
794
  }
783
795
  function defineZaloChannel(config) {
784
796
  return defineChannel("zalo", config);
@@ -801,6 +813,9 @@ function defineSkill(input) {
801
813
  function defineTool(input) {
802
814
  return defineResource("tool", input);
803
815
  }
816
+ function definePolicy(input) {
817
+ return defineResource("policy", input);
818
+ }
804
819
  function defineCron(input) {
805
820
  return defineResource("cron", input);
806
821
  }
@@ -830,6 +845,7 @@ export {
830
845
  defineSlackChannel,
831
846
  defineSkill,
832
847
  defineSandbox,
848
+ definePolicy,
833
849
  definePancakeChannel,
834
850
  defineGitHubChannel,
835
851
  defineDiscordChannel,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "broods",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "description": "CLI + TypeScript SDK for the broods agent platform.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -42,12 +42,20 @@
42
42
  "pack:check": "bun run build && npm pack --dry-run",
43
43
  "test": "bun test"
44
44
  },
45
+ "engines": {
46
+ "node": ">=22"
47
+ },
45
48
  "dependencies": {
46
- "ai": "^6.0.195"
49
+ "@chat-adapter/discord": "^4.31.0",
50
+ "@chat-adapter/github": "^4.31.0",
51
+ "@chat-adapter/slack": "^4.31.0",
52
+ "@chat-adapter/telegram": "^4.31.0",
53
+ "ai": "^7.0.11"
47
54
  },
48
55
  "devDependencies": {
49
56
  "@types/bun": "^1.3.14",
50
57
  "rollup": "^4.53.3",
58
+ "typescript": "^6.0.3",
51
59
  "rollup-plugin-dts": "^6.3.0"
52
60
  }
53
61
  }