broods 0.44.1 → 0.45.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/account.d.ts CHANGED
@@ -178,10 +178,11 @@ interface McpOauth {
178
178
  }
179
179
 
180
180
  /**
181
- * Agent configuration: types for the per-agent settings object, the runtime
182
- * projection of a stored config, and encryption helpers. The validation rules
183
- * are the config plane's (`@broods/convex/model/agentRules`), so a config is
184
- * judged the same on write and on every run.
181
+ * Agent configuration: types for the per-agent settings object and the
182
+ * runtime projection of a stored config. The validation rules are the config
183
+ * plane's (`@broods/convex/model/agentRules`), so a config is judged the same
184
+ * on write and on every run. Decrypting the stored blob is
185
+ * `../convex/account-keys.ts`.
185
186
  * Account types and auth live in `./accounts.ts` and `../auth.ts`.
186
187
  */
187
188
 
@@ -715,17 +716,24 @@ type UpdateCronInput = {
715
716
  input?: never;
716
717
  });
717
718
 
719
+ /**
720
+ * The sandbox provider list: the one list the schema validator, the config
721
+ * rules, core's executor registry and the SDK derive from. A leaf on purpose,
722
+ * so `schema.ts` can read it without pulling the rules' import graph.
723
+ */
724
+ declare const SANDBOX_PROVIDERS: readonly ["sandbox", "lambda", "e2b", "daytona", "vercel", "machine", "custom"];
725
+ type SandboxProvider = (typeof SANDBOX_PROVIDERS)[number];
726
+
718
727
  /**
719
728
  * Sandbox-config validation for the Convex config plane. Ports core's former
720
729
  * storage/sandbox-config.ts normalizer so the public /v1/sandboxes contract
721
730
  * is unchanged. The public projection lives in ./responses.ts.
722
731
  */
723
- declare const SANDBOX_PROVIDERS: readonly ["sandbox", "lambda", "e2b", "daytona", "vercel", "machine"];
732
+
724
733
  declare const SANDBOX_RUNTIMES: readonly ["bash", "python", "node"];
725
734
  declare const SANDBOX_PERMISSION_MODES: readonly ["edit", "ask", "bypass"];
726
735
  declare const SANDBOX_NETWORK_MODES: readonly ["allow-all", "deny-all", "restricted"];
727
736
  declare const SANDBOX_SIZE_NAMES: readonly ["tiny", "xsmall", "small", "medium", "large"];
728
- type SandboxProvider = (typeof SANDBOX_PROVIDERS)[number];
729
737
  type RuntimeName = (typeof SANDBOX_RUNTIMES)[number];
730
738
  type PermissionMode = (typeof SANDBOX_PERMISSION_MODES)[number];
731
739
  type NetworkMode = (typeof SANDBOX_NETWORK_MODES)[number];
@@ -764,6 +772,29 @@ interface SandboxConfig {
764
772
  options?: Record<string, unknown>;
765
773
  }
766
774
 
775
+ /**
776
+ * Workspace isolation levels, the one list the config plane, core, the
777
+ * dashboard and the CLI read. No imports on purpose: the CLI bundles this file
778
+ * without the Convex runtime behind workspaceRules (like modelProviders.ts).
779
+ */
780
+ declare const WORKSPACE_ISOLATION_LEVELS: readonly ["conversation", "agent"];
781
+ /**
782
+ * How a workspace splits its files. "conversation" mounts a folder per channel
783
+ * partition, "agent" gives every attached agent its own folder. Unset shares
784
+ * one root between every agent and conversation.
785
+ */
786
+ type WorkspaceIsolation = (typeof WORKSPACE_ISOLATION_LEVELS)[number];
787
+
788
+ /**
789
+ * Workspace-config validation for the Convex config plane. Ports core's
790
+ * former storage/workspace-config.ts normalizer so the public /v1/workspaces
791
+ * contract is unchanged. Workspace config holds no secrets (a roleArn is not
792
+ * a secret), so it is stored and returned in plaintext. Pure module, safe
793
+ * for the default Convex runtime. The public projection lives in
794
+ * ./responses.ts. Core runs the storage access rule too, so it reads the env
795
+ * names of both sides.
796
+ */
797
+
767
798
  declare const WORKSPACE_STORAGE_PROVIDERS: readonly ["s3"];
768
799
  type WorkspaceStorageProvider = (typeof WORKSPACE_STORAGE_PROVIDERS)[number];
769
800
  type WorkspaceStorageAuth = {
@@ -781,9 +812,13 @@ interface WorkspaceStorageConfig {
781
812
  prefix?: string;
782
813
  auth?: WorkspaceStorageAuth;
783
814
  }
815
+ /** What the API accepts: the boolean spelling of isolation is stored as its level. */
816
+ type WorkspaceConfigInput = Omit<WorkspaceConfig, "isolation"> & {
817
+ isolation?: WorkspaceIsolation | boolean;
818
+ };
784
819
  interface WorkspaceConfig {
785
820
  storage: WorkspaceStorageConfig;
786
- isolation?: boolean;
821
+ isolation?: WorkspaceIsolation;
787
822
  harness?: {
788
823
  workspace?: {
789
824
  enabled?: boolean;
@@ -805,7 +840,7 @@ declare const AGENT_POLICY_ACTIONS: readonly ["agent.invoke", "tool.call", "work
805
840
  * resource route. Roles carry the same PolicyDocument shape as agent policies;
806
841
  * each caller passes its action set to `normalizePolicyDocument`.
807
842
  */
808
- declare const API_POLICY_ACTIONS: readonly ["account:read", "account:write", "agents:read", "agents:write", "channels:read", "channels:write", "crons:read", "crons:write", "env:read", "env:write", "hooks:read", "hooks:write", "mcp:read", "mcp:write", "policies:read", "policies:write", "sandboxes:read", "sandboxes:write", "skills:read", "skills:write", "workspaces:read", "workspaces:write"];
843
+ declare const API_POLICY_ACTIONS: readonly ["account:read", "account:write", "agents:read", "agents:write", "audit:read", "audit:write", "channels:read", "channels:write", "crons:read", "crons:write", "env:read", "env:write", "hooks:read", "hooks:write", "mcp:read", "mcp:write", "policies:read", "policies:write", "sandboxes:read", "sandboxes:write", "skills:read", "skills:write", "workspaces:read", "workspaces:write"];
809
844
  type AgentPolicyAction = (typeof AGENT_POLICY_ACTIONS)[number];
810
845
  type ApiPolicyAction = (typeof API_POLICY_ACTIONS)[number];
811
846
  type PolicyAction = AgentPolicyAction | ApiPolicyAction;
@@ -980,7 +1015,7 @@ interface Connection {
980
1015
  * server runtimes, as well as Node and Bun.
981
1016
  *
982
1017
  * Auth: every call sends `Authorization: Bearer {accountSecret}` to
983
- * `{baseUrl}/v1/...`, or a short-lived `fp_sts_` role session token from
1018
+ * `{baseUrl}/v1/...`, or a short-lived `bsts_` role session token from
984
1019
  * `assumeRole()`, limited to what the role's policy allows. Secrets inside
985
1020
  * agent configs are encrypted at rest by the platform and come back redacted
986
1021
  * (`********`) on reads.
@@ -996,7 +1031,7 @@ interface BroodsAccountClientOptions {
996
1031
  /** Account key used as the Bearer token. Falls back to `BROODS_ACCOUNT_SECRET`. */
997
1032
  accountSecret?: string;
998
1033
  /**
999
- * Short-lived `fp_sts_` role session token (from {@link BroodsAccountClient.assumeRole})
1034
+ * Short-lived `bsts_` role session token (from {@link BroodsAccountClient.assumeRole})
1000
1035
  * used as the Bearer instead of the account key. The session can only do
1001
1036
  * what its role's policy allows. Falls back to `BROODS_SESSION_TOKEN`.
1002
1037
  */
@@ -1008,6 +1043,8 @@ interface BroodsAccount {
1008
1043
  accountId: string;
1009
1044
  username: string;
1010
1045
  status: string;
1046
+ /** Days an audit row is kept; absent means the 90 day default. */
1047
+ auditRetentionDays?: number;
1011
1048
  [key: string]: unknown;
1012
1049
  }
1013
1050
  /** Public agent record; `config` comes back with secret values redacted. */
@@ -1096,9 +1133,82 @@ interface AccountRole {
1096
1133
  createdAt: string;
1097
1134
  updatedAt: string;
1098
1135
  }
1136
+ /** One link of an agent actor's delegation chain: who asked, or an agent that delegated. */
1137
+ type AuditPrincipalLink = {
1138
+ kind: "user";
1139
+ id: string;
1140
+ name?: string;
1141
+ channel?: string;
1142
+ } | {
1143
+ kind: "api";
1144
+ keyKind: "account" | "deployment" | "cron";
1145
+ } | {
1146
+ kind: "agent";
1147
+ agentId: string;
1148
+ };
1149
+ /**
1150
+ * One row of the account's hash-chained audit ledger, as `GET /v1/audit`
1151
+ * serves it. `hash` is sha256 over the canonical JSON of every field but
1152
+ * `hash` itself (an absent optional field is left out, not null), with
1153
+ * `prevHash` linking it to the row before.
1154
+ */
1155
+ interface AuditEvent {
1156
+ accountId: string;
1157
+ seq: number;
1158
+ prevHash: string;
1159
+ hash: string;
1160
+ /** Unix ms. */
1161
+ at: number;
1162
+ actor: {
1163
+ kind: string;
1164
+ id?: string;
1165
+ email?: string;
1166
+ name?: string;
1167
+ agentId?: string;
1168
+ /** On an agent actor: who asked, then each agent that delegated, oldest first. */
1169
+ chain?: AuditPrincipalLink[];
1170
+ };
1171
+ action: string;
1172
+ resource: {
1173
+ kind: string;
1174
+ id?: string;
1175
+ name?: string;
1176
+ };
1177
+ summary: string;
1178
+ detailsJson?: string;
1179
+ projectId?: string;
1180
+ stageId?: string;
1181
+ traceId?: string;
1182
+ }
1183
+ /** A page of ledger rows plus the chain head to compare the last row against. */
1184
+ interface AuditPage {
1185
+ events: AuditEvent[];
1186
+ /** Pass as `since` to read the rows after this page. */
1187
+ nextSince: number;
1188
+ head: {
1189
+ seq: number;
1190
+ hash: string;
1191
+ } | null;
1192
+ }
1193
+ /** Result of `GET /v1/audit/verify`: the chain recomputed over the checked range. */
1194
+ interface AuditVerification {
1195
+ ok: boolean;
1196
+ brokenAtSeq?: number;
1197
+ checkedFrom?: number;
1198
+ checkedTo?: number;
1199
+ }
1200
+ /** The account's audit export target. The signing secret never comes back. */
1201
+ interface AuditSink {
1202
+ kind: "webhook";
1203
+ url: string;
1204
+ /** Highest seq the receiver acknowledged. */
1205
+ exportedSeq: number;
1206
+ lastError?: string;
1207
+ updatedAt: string;
1208
+ }
1099
1209
  /** Short-lived role session minted by `POST /v1/account/assume-role`. */
1100
1210
  interface AssumeRoleResult {
1101
- /** `fp_sts_` bearer token; pass it as `sessionToken` to a new client. */
1211
+ /** `bsts_` bearer token; pass it as `sessionToken` to a new client. */
1102
1212
  token: string;
1103
1213
  /** ISO timestamp when the session stops working. */
1104
1214
  expiresAt: string;
@@ -1303,9 +1413,11 @@ declare class BroodsAccountClient {
1303
1413
  updateAccount(patch: {
1304
1414
  username?: string;
1305
1415
  description?: string | null;
1416
+ /** 1 to 3650 days, or null to return to the 90 day default. */
1417
+ auditRetentionDays?: number | null;
1306
1418
  }): Promise<BroodsAccount | null>;
1307
1419
  /**
1308
- * Exchange a role for a short-lived `fp_sts_` session token. Callable with
1420
+ * Exchange a role for a short-lived `bsts_` session token. Callable with
1309
1421
  * the account key, a CLI login token, or a runtime key (the latter
1310
1422
  * only into roles scoped to the key's own project/stage). Construct a new
1311
1423
  * client with `{ sessionToken: result.token }` to act as the role.
@@ -1364,13 +1476,13 @@ declare class BroodsAccountClient {
1364
1476
  createWorkspace(input: {
1365
1477
  name: string;
1366
1478
  description?: string;
1367
- config?: WorkspaceConfig;
1479
+ config?: WorkspaceConfigInput;
1368
1480
  }): Promise<AccountWorkspace>;
1369
1481
  getWorkspace(workspaceId: string): Promise<AccountWorkspace | null>;
1370
1482
  updateWorkspace(workspaceId: string, patch: {
1371
1483
  name?: string;
1372
1484
  description?: string | null;
1373
- config?: ConfigPatch<WorkspaceConfig>;
1485
+ config?: ConfigPatch<WorkspaceConfigInput>;
1374
1486
  }): Promise<AccountWorkspace | null>;
1375
1487
  deleteWorkspace(workspaceId: string): Promise<boolean>;
1376
1488
  /** Flat listing of every file in the workspace's S3-backed filesystem. Returns [] when the workspace is gone. */
@@ -1457,6 +1569,32 @@ declare class BroodsAccountClient {
1457
1569
  status?: "active" | "disabled";
1458
1570
  }): Promise<AccountRole | null>;
1459
1571
  deleteRole(roleId: string): Promise<boolean>;
1572
+ /** The audit ledger: rows since a seq, chain verification, and the webhook sink. */
1573
+ readonly audit: {
1574
+ /** Rows with `seq > since`, oldest first, at most `limit` (default 100, max 500). */
1575
+ list: (options?: {
1576
+ since?: number;
1577
+ limit?: number;
1578
+ }) => Promise<AuditPage>;
1579
+ /**
1580
+ * Recompute the chain over `[fromSeq, toSeq]`, from the oldest kept row by
1581
+ * default, at most 1000 rows per call. `ok` covers only `checkedFrom` to
1582
+ * `checkedTo`; on a longer ledger call again with `fromSeq: checkedTo + 1`.
1583
+ */
1584
+ verify: (options?: {
1585
+ fromSeq?: number;
1586
+ toSeq?: number;
1587
+ }) => Promise<AuditVerification>;
1588
+ /** The account's sink, or null when none is set. */
1589
+ getSink: () => Promise<AuditSink | null>;
1590
+ /** Set the one webhook sink. `url` must be public https; `secret` signs each batch. */
1591
+ setSink: (input: {
1592
+ url: string;
1593
+ secret: string;
1594
+ }) => Promise<AuditSink>;
1595
+ /** Remove the sink; exports stop and rows age out on retention alone. Returns whether one existed. */
1596
+ deleteSink: () => Promise<boolean>;
1597
+ };
1460
1598
  listChannels(): Promise<AccountChannel[]>;
1461
1599
  /** Bind one real chat channel to an agent. One active record per place. */
1462
1600
  createChannel(input: {
@@ -1491,4 +1629,4 @@ declare class BroodsAccountClient {
1491
1629
  }
1492
1630
 
1493
1631
  export { BroodsAccountApiError, BroodsAccountClient, envPlaceholder, resolveEnvCredential };
1494
- export type { AccountAgent, AccountChannel, AccountEnvVar, AccountMcp, AccountPolicy, AccountRole, AccountSandbox, AccountWorkspace, AssumeRoleResult, BroodsAccount, BroodsAccountClientOptions, ChannelRecordConfig, ConfigPatch, Connection, ConnectionCode, ConnectionStart, ConnectionStartResult, ConnectionType, CreateAgentResult, CreateMcpInput, DeleteAccountResult, McpOauthInput, RotateSecretResult, SandboxLifecycleResult, SandboxSnapshotResult, SandboxTerminalTicket, SkillUploadInput, StageScope, UpdateAgentInput, UpdateMcpInput, WorkspaceFileEntry };
1632
+ export type { AccountAgent, AccountChannel, AccountEnvVar, AccountMcp, AccountPolicy, AccountRole, AccountSandbox, AccountWorkspace, AssumeRoleResult, AuditEvent, AuditPage, AuditPrincipalLink, AuditSink, AuditVerification, BroodsAccount, BroodsAccountClientOptions, ChannelRecordConfig, ConfigPatch, Connection, ConnectionCode, ConnectionStart, ConnectionStartResult, ConnectionType, CreateAgentResult, CreateMcpInput, DeleteAccountResult, McpOauthInput, RotateSecretResult, SandboxLifecycleResult, SandboxSnapshotResult, SandboxTerminalTicket, SkillUploadInput, StageScope, UpdateAgentInput, UpdateMcpInput, WorkspaceFileEntry };
package/dist/account.js CHANGED
@@ -1 +1 @@
1
- import{m,Ae,Z,R}from"./chunk-a3f2xzbr.js";export{m as BroodsAccountApiError,R as BroodsAccountClient,Ae as envPlaceholder,Z as resolveEnvCredential};
1
+ import{m,Ae,Z,R}from"./chunk-bb3jfhv3.js";export{m as BroodsAccountApiError,R as BroodsAccountClient,Ae as envPlaceholder,Z as resolveEnvCredential};
@@ -1,2 +1,2 @@
1
- var u=/^[A-Z][A-Z0-9_]*$/;class m extends Error{status;body;constructor(e,t,n,s){super(`${e} ${t} failed with ${n}: ${s}`);this.name="BroodsAccountApiError",this.status=n,this.body=s}}function Ae(e){if(!u.test(e)||e.length>64)throw Error("envPlaceholder name must match /^[A-Z][A-Z0-9_]*$/ and be at most 64 characters.");return`\${${e}}`}function Z(){return o("BROODS_SESSION_TOKEN")??o("BROODS_ACCOUNT_SECRET")}class R{baseUrl;bearerToken;fetchImpl;constructor(e={}){let t=e.baseUrl??o("BROODS_BASE_URL")??"https://gateway.broods.app",n=e.sessionToken??e.accountSecret??Z();if(!n)throw Error("BroodsAccountClient requires an accountSecret or sessionToken (or BROODS_ACCOUNT_SECRET / BROODS_SESSION_TOKEN).");let s=t.length;while(s>0&&t.charCodeAt(s-1)===47)s-=1;this.baseUrl=t.slice(0,s),this.bearerToken=n,this.fetchImpl=e.fetch??fetch}async getAccount(){let e=await this.request("GET","/v1/account");if(!e)throw new m("GET","/v1/account",404,"Account not found");return e.account}async updateAccount(e){return(await this.request("PATCH","/v1/account",e))?.account??null}async assumeRole(e,t={}){let n=await this.request("POST","/v1/account/assume-role",{roleId:e,...t.ttlSeconds!==void 0?{ttlSeconds:t.ttlSeconds}:{}});if(!n)throw new m("POST","/v1/account/assume-role",404,"Role not found");return n}async rotateSecret(){let e=await this.request("POST","/v1/account/rotate-secret");if(!e)throw new m("POST","/v1/account/rotate-secret",404,"Account not found");return e}async deleteAccount(){return await this.request("DELETE","/v1/account")??{deleted:!1}}webhookUrl(e,t){let n=[e,t].map(encodeURIComponent);return`${this.baseUrl}/v1/webhooks/${n.join("/")}`}async listAgents(){return(await this.request("GET","/v1/agents"))?.agents??[]}async createAgent(e){let t=await this.request("POST","/v1/agents",e);if(!t)throw new m("POST","/v1/agents",404,"Not found");return t}async getAgent(e){return await this.request("GET",`/v1/agents/${encodeURIComponent(e)}`)}async updateAgent(e,t){return await this.request("PATCH",`/v1/agents/${encodeURIComponent(e)}`,t)}async deleteAgent(e){return(await this.request("DELETE",`/v1/agents/${encodeURIComponent(e)}`))?.deleted??!1}async listEnvVars(){return(await this.request("GET","/v1/env"))?.env??[]}async setEnvVar(e,t){await this.request("PUT",`/v1/env/${encodeURIComponent(e)}`,{value:t})}async deleteEnvVar(e){return(await this.request("DELETE",`/v1/env/${encodeURIComponent(e)}`))?.deleted??!1}async listConnections(){return(await this.request("GET","/v1/account/connections"))?.connections??[]}async getConnection(e){return await this.request("GET",`/v1/account/connections/${e}`)}async startConnection(e,t){let n=await this.request("POST",`/v1/account/connections/${e}/start`,t);if(!n)throw new m("POST",`/v1/account/connections/${e}/start`,404,"This deployment has no /v1/account/connections yet");return n}async connect(e,t){let n=await this.request("PUT",`/v1/account/connections/${e}`,t);if(!n)throw new m("PUT",`/v1/account/connections/${e}`,404,"This deployment has no /v1/account/connections yet");return n}async disconnect(e){return(await this.request("DELETE",`/v1/account/connections/${e}`))?.deleted??!1}async listCrons(){return(await this.request("GET","/v1/crons"))?.crons??[]}async createCron(e){let t=await this.request("POST","/v1/crons",e);if(!t)throw new m("POST","/v1/crons",404,"Not found");return t}async getCron(e){return await this.request("GET",`/v1/crons/${encodeURIComponent(e)}`)}async updateCron(e,t){return await this.request("PATCH",`/v1/crons/${encodeURIComponent(e)}`,t)}async deleteCron(e){return(await this.request("DELETE",`/v1/crons/${encodeURIComponent(e)}`))?.deleted??!1}async listCronRuns(e,t={}){let n=t.limit!==void 0?`?limit=${t.limit}`:"";return(await this.request("GET",`/v1/crons/${encodeURIComponent(e)}/runs${n}`))?.runs??[]}async listWorkspaces(){return(await this.request("GET","/v1/workspaces"))?.workspaces??[]}async createWorkspace(e){let t=await this.request("POST","/v1/workspaces",e);if(!t)throw new m("POST","/v1/workspaces",404,"Not found");return t}async getWorkspace(e){return await this.request("GET",`/v1/workspaces/${encodeURIComponent(e)}`)}async updateWorkspace(e,t){return await this.request("PATCH",`/v1/workspaces/${encodeURIComponent(e)}`,t)}async deleteWorkspace(e){return(await this.request("DELETE",`/v1/workspaces/${encodeURIComponent(e)}`))?.deleted??!1}async listWorkspaceFiles(e){return(await this.request("GET",`/v1/workspaces/${encodeURIComponent(e)}/files`))?.files??[]}async getWorkspaceFileUrl(e,t){let n=`?path=${encodeURIComponent(t)}`;return(await this.request("GET",`/v1/workspaces/${encodeURIComponent(e)}/files${n}`))?.url??null}async uploadWorkspaceFile(e,t){let n=`/v1/workspaces/${encodeURIComponent(e)}/files`,s=await this.request("POST",n,t);if(!s)throw new m("POST",n,404,"Workspace not found");return s.file}async renameWorkspaceFile(e,t,n){let s=`/v1/workspaces/${encodeURIComponent(e)}/files`,r=await this.request("PATCH",s,{path:t,newPath:n});if(!r)throw new m("PATCH",s,404,"Workspace not found");return r.renamed}async deleteWorkspaceFile(e,t){return(await this.request("DELETE",`/v1/workspaces/${encodeURIComponent(e)}/files`,{path:t}))?.deleted??0}async listSandboxes(){return(await this.request("GET","/v1/sandboxes"))?.sandboxes??[]}async createSandbox(e){let t=await this.request("POST","/v1/sandboxes",e);if(!t)throw new m("POST","/v1/sandboxes",404,"Not found");return t}async getSandbox(e){return await this.request("GET",`/v1/sandboxes/${encodeURIComponent(e)}`)}async updateSandbox(e,t){return await this.request("PATCH",`/v1/sandboxes/${encodeURIComponent(e)}`,t)}async deleteSandbox(e){return(await this.request("DELETE",`/v1/sandboxes/${encodeURIComponent(e)}`))?.deleted??!1}async suspendSandbox(e,t){return await this.sandboxAction(e,"suspend",{reservationKey:t})}async resumeSandbox(e,t){return await this.sandboxAction(e,"resume",{reservationKey:t})}async terminateSandbox(e,t){return await this.sandboxAction(e,"terminate",{reservationKey:t})}async snapshotSandbox(e,t,n){return await this.sandboxAction(e,"snapshot",{reservationKey:t,name:n})}async openSandboxTerminal(e,t){return await this.sandboxAction(e,"terminal",{reservationKey:t})}async listMcp(e){let t=`/v1/mcp${i(e)}`,n=await this.request("GET",t);if(!n)throw new m("GET",t,404,"Not found");return n.servers??[]}async createMcp(e,t){let n=`/v1/mcp${i(e)}`,s=await this.request("POST",n,t);if(!s)throw new m("POST",n,404,"Not found");return s}async uploadMcpBundle(e){let n=await this.request("POST","/v1/mcp/uploads");if(!n?.uploadUrl)throw new m("POST","/v1/mcp/uploads",502,"response omitted uploadUrl");let s=await this.fetchImpl(n.uploadUrl,{method:"POST",headers:{"Content-Type":"application/javascript"},body:e});if(!s.ok)throw new m("POST","/v1/mcp/uploads",s.status,await s.text());let{storageId:r}=await s.json();if(!r)throw new m("POST","/v1/mcp/uploads",502,"upload response omitted storageId");let a=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(e)),c=Array.from(new Uint8Array(a)).map((l)=>l.toString(16).padStart(2,"0")).join("");return{bundleStorageId:r,sha256:c}}async getMcp(e){return await this.request("GET",`/v1/mcp/${encodeURIComponent(e)}`)}async updateMcp(e,t){return await this.request("PATCH",`/v1/mcp/${encodeURIComponent(e)}`,t)}async deleteMcp(e){return(await this.request("DELETE",`/v1/mcp/${encodeURIComponent(e)}`))?.deleted??!1}async listPolicies(){return(await this.request("GET","/v1/policies"))?.policies??[]}async createPolicy(e){let t=await this.request("POST","/v1/policies",e);if(!t)throw new m("POST","/v1/policies",404,"Not found");return t}async getPolicy(e){return await this.request("GET",`/v1/policies/${encodeURIComponent(e)}`)}async updatePolicy(e,t){return await this.request("PATCH",`/v1/policies/${encodeURIComponent(e)}`,t)}async deletePolicy(e){return(await this.request("DELETE",`/v1/policies/${encodeURIComponent(e)}`))?.deleted??!1}async listRoles(){return(await this.request("GET","/v1/roles"))?.roles??[]}async createRole(e){let t=await this.request("POST","/v1/roles",e);if(!t)throw new m("POST","/v1/roles",404,"Not found");return t}async getRole(e){return await this.request("GET",`/v1/roles/${encodeURIComponent(e)}`)}async updateRole(e,t){return await this.request("PATCH",`/v1/roles/${encodeURIComponent(e)}`,t)}async deleteRole(e){return(await this.request("DELETE",`/v1/roles/${encodeURIComponent(e)}`))?.deleted??!1}async listChannels(){return(await this.request("GET","/v1/channels"))?.channels??[]}async createChannel(e){let t=await this.request("POST","/v1/channels",e);if(!t)throw new m("POST","/v1/channels",404,"Not found");return t}async getChannel(e){return await this.request("GET",`/v1/channels/${encodeURIComponent(e)}`)}async updateChannel(e,t){return await this.request("PATCH",`/v1/channels/${encodeURIComponent(e)}`,t)}async deleteChannel(e){return(await this.request("DELETE",`/v1/channels/${encodeURIComponent(e)}`))?.deleted??!1}async listSkills(){return(await this.request("GET","/v1/skills"))?.skills??[]}async createSkill(e){let t=await this.request("POST","/v1/skills",e);if(!t)throw new m("POST","/v1/skills",404,"Not found");return t}async getSkill(e){return await this.request("GET",`/v1/skills/${encodeURIComponent(e)}`)}async uploadSkill(e,t){let n=`/v1/skills/${encodeURIComponent(e)}`,s=await this.request("PUT",n,t);if(!s)throw new m("PUT",n,404,"Skill not found");return s}async deleteSkill(e){return(await this.request("DELETE",`/v1/skills/${encodeURIComponent(e)}`))?.deleted??!1}async sandboxAction(e,t,n){let s=`/v1/sandboxes/${encodeURIComponent(e)}/${t}`,r=await this.request("POST",s,n);if(!r)throw new m("POST",s,404,"Sandbox not found");return r}async request(e,t,n){let s=await this.fetchImpl(`${this.baseUrl}${t}`,{method:e,headers:{Authorization:`Bearer ${this.bearerToken}`,...n!==void 0?{"Content-Type":"application/json"}:{}},...n!==void 0?{body:JSON.stringify(n)}:{}});if(s.status===404)return null;if(!s.ok)throw new m(e,t,s.status,await s.text());return await s.json()}}function o(e){return typeof process<"u"?process?.env?.[e]:void 0}function i(e){return`?${new URLSearchParams({project:e.project,stage:e.stage}).toString()}`}
1
+ var d=/^[A-Z][A-Z0-9_]*$/;class m extends Error{status;body;constructor(e,t,n,s){super(`${e} ${t} failed with ${n}: ${s}`);this.name="BroodsAccountApiError",this.status=n,this.body=s}}function Ae(e){if(!d.test(e)||e.length>64)throw Error("envPlaceholder name must match /^[A-Z][A-Z0-9_]*$/ and be at most 64 characters.");return`\${${e}}`}function Z(){return o("BROODS_SESSION_TOKEN")??o("BROODS_ACCOUNT_SECRET")}class R{baseUrl;bearerToken;fetchImpl;constructor(e={}){let t=e.baseUrl??o("BROODS_BASE_URL")??"https://gateway.broods.app",n=e.sessionToken??e.accountSecret??Z();if(!n)throw Error("BroodsAccountClient requires an accountSecret or sessionToken (or BROODS_ACCOUNT_SECRET / BROODS_SESSION_TOKEN).");let s=t.length;while(s>0&&t.charCodeAt(s-1)===47)s-=1;this.baseUrl=t.slice(0,s),this.bearerToken=n,this.fetchImpl=e.fetch??fetch}async getAccount(){let e=await this.request("GET","/v1/account");if(!e)throw new m("GET","/v1/account",404,"Account not found");return e.account}async updateAccount(e){return(await this.request("PATCH","/v1/account",e))?.account??null}async assumeRole(e,t={}){let n=await this.request("POST","/v1/account/assume-role",{roleId:e,...t.ttlSeconds!==void 0?{ttlSeconds:t.ttlSeconds}:{}});if(!n)throw new m("POST","/v1/account/assume-role",404,"Role not found");return n}async rotateSecret(){let e=await this.request("POST","/v1/account/rotate-secret");if(!e)throw new m("POST","/v1/account/rotate-secret",404,"Account not found");return e}async deleteAccount(){return await this.request("DELETE","/v1/account")??{deleted:!1}}webhookUrl(e,t){let n=[e,t].map(encodeURIComponent);return`${this.baseUrl}/v1/webhooks/${n.join("/")}`}async listAgents(){return(await this.request("GET","/v1/agents"))?.agents??[]}async createAgent(e){let t=await this.request("POST","/v1/agents",e);if(!t)throw new m("POST","/v1/agents",404,"Not found");return t}async getAgent(e){return await this.request("GET",`/v1/agents/${encodeURIComponent(e)}`)}async updateAgent(e,t){return await this.request("PATCH",`/v1/agents/${encodeURIComponent(e)}`,t)}async deleteAgent(e){return(await this.request("DELETE",`/v1/agents/${encodeURIComponent(e)}`))?.deleted??!1}async listEnvVars(){return(await this.request("GET","/v1/env"))?.env??[]}async setEnvVar(e,t){await this.request("PUT",`/v1/env/${encodeURIComponent(e)}`,{value:t})}async deleteEnvVar(e){return(await this.request("DELETE",`/v1/env/${encodeURIComponent(e)}`))?.deleted??!1}async listConnections(){return(await this.request("GET","/v1/account/connections"))?.connections??[]}async getConnection(e){return await this.request("GET",`/v1/account/connections/${e}`)}async startConnection(e,t){let n=await this.request("POST",`/v1/account/connections/${e}/start`,t);if(!n)throw new m("POST",`/v1/account/connections/${e}/start`,404,"This deployment has no /v1/account/connections yet");return n}async connect(e,t){let n=await this.request("PUT",`/v1/account/connections/${e}`,t);if(!n)throw new m("PUT",`/v1/account/connections/${e}`,404,"This deployment has no /v1/account/connections yet");return n}async disconnect(e){return(await this.request("DELETE",`/v1/account/connections/${e}`))?.deleted??!1}async listCrons(){return(await this.request("GET","/v1/crons"))?.crons??[]}async createCron(e){let t=await this.request("POST","/v1/crons",e);if(!t)throw new m("POST","/v1/crons",404,"Not found");return t}async getCron(e){return await this.request("GET",`/v1/crons/${encodeURIComponent(e)}`)}async updateCron(e,t){return await this.request("PATCH",`/v1/crons/${encodeURIComponent(e)}`,t)}async deleteCron(e){return(await this.request("DELETE",`/v1/crons/${encodeURIComponent(e)}`))?.deleted??!1}async listCronRuns(e,t={}){let n=t.limit!==void 0?`?limit=${t.limit}`:"";return(await this.request("GET",`/v1/crons/${encodeURIComponent(e)}/runs${n}`))?.runs??[]}async listWorkspaces(){return(await this.request("GET","/v1/workspaces"))?.workspaces??[]}async createWorkspace(e){let t=await this.request("POST","/v1/workspaces",e);if(!t)throw new m("POST","/v1/workspaces",404,"Not found");return t}async getWorkspace(e){return await this.request("GET",`/v1/workspaces/${encodeURIComponent(e)}`)}async updateWorkspace(e,t){return await this.request("PATCH",`/v1/workspaces/${encodeURIComponent(e)}`,t)}async deleteWorkspace(e){return(await this.request("DELETE",`/v1/workspaces/${encodeURIComponent(e)}`))?.deleted??!1}async listWorkspaceFiles(e){return(await this.request("GET",`/v1/workspaces/${encodeURIComponent(e)}/files`))?.files??[]}async getWorkspaceFileUrl(e,t){let n=`?path=${encodeURIComponent(t)}`;return(await this.request("GET",`/v1/workspaces/${encodeURIComponent(e)}/files${n}`))?.url??null}async uploadWorkspaceFile(e,t){let n=`/v1/workspaces/${encodeURIComponent(e)}/files`,s=await this.request("POST",n,t);if(!s)throw new m("POST",n,404,"Workspace not found");return s.file}async renameWorkspaceFile(e,t,n){let s=`/v1/workspaces/${encodeURIComponent(e)}/files`,r=await this.request("PATCH",s,{path:t,newPath:n});if(!r)throw new m("PATCH",s,404,"Workspace not found");return r.renamed}async deleteWorkspaceFile(e,t){return(await this.request("DELETE",`/v1/workspaces/${encodeURIComponent(e)}/files`,{path:t}))?.deleted??0}async listSandboxes(){return(await this.request("GET","/v1/sandboxes"))?.sandboxes??[]}async createSandbox(e){let t=await this.request("POST","/v1/sandboxes",e);if(!t)throw new m("POST","/v1/sandboxes",404,"Not found");return t}async getSandbox(e){return await this.request("GET",`/v1/sandboxes/${encodeURIComponent(e)}`)}async updateSandbox(e,t){return await this.request("PATCH",`/v1/sandboxes/${encodeURIComponent(e)}`,t)}async deleteSandbox(e){return(await this.request("DELETE",`/v1/sandboxes/${encodeURIComponent(e)}`))?.deleted??!1}async suspendSandbox(e,t){return await this.sandboxAction(e,"suspend",{reservationKey:t})}async resumeSandbox(e,t){return await this.sandboxAction(e,"resume",{reservationKey:t})}async terminateSandbox(e,t){return await this.sandboxAction(e,"terminate",{reservationKey:t})}async snapshotSandbox(e,t,n){return await this.sandboxAction(e,"snapshot",{reservationKey:t,name:n})}async openSandboxTerminal(e,t){return await this.sandboxAction(e,"terminal",{reservationKey:t})}async listMcp(e){let t=`/v1/mcp${a(e)}`,n=await this.request("GET",t);if(!n)throw new m("GET",t,404,"Not found");return n.servers??[]}async createMcp(e,t){let n=`/v1/mcp${a(e)}`,s=await this.request("POST",n,t);if(!s)throw new m("POST",n,404,"Not found");return s}async uploadMcpBundle(e){let n=await this.request("POST","/v1/mcp/uploads");if(!n?.uploadUrl)throw new m("POST","/v1/mcp/uploads",502,"response omitted uploadUrl");let s=await this.fetchImpl(n.uploadUrl,{method:"POST",headers:{"Content-Type":"application/javascript"},body:e});if(!s.ok)throw new m("POST","/v1/mcp/uploads",s.status,await s.text());let{storageId:r}=await s.json();if(!r)throw new m("POST","/v1/mcp/uploads",502,"upload response omitted storageId");let c=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(e)),u=Array.from(new Uint8Array(c)).map((l)=>l.toString(16).padStart(2,"0")).join("");return{bundleStorageId:r,sha256:u}}async getMcp(e){return await this.request("GET",`/v1/mcp/${encodeURIComponent(e)}`)}async updateMcp(e,t){return await this.request("PATCH",`/v1/mcp/${encodeURIComponent(e)}`,t)}async deleteMcp(e){return(await this.request("DELETE",`/v1/mcp/${encodeURIComponent(e)}`))?.deleted??!1}async listPolicies(){return(await this.request("GET","/v1/policies"))?.policies??[]}async createPolicy(e){let t=await this.request("POST","/v1/policies",e);if(!t)throw new m("POST","/v1/policies",404,"Not found");return t}async getPolicy(e){return await this.request("GET",`/v1/policies/${encodeURIComponent(e)}`)}async updatePolicy(e,t){return await this.request("PATCH",`/v1/policies/${encodeURIComponent(e)}`,t)}async deletePolicy(e){return(await this.request("DELETE",`/v1/policies/${encodeURIComponent(e)}`))?.deleted??!1}async listRoles(){return(await this.request("GET","/v1/roles"))?.roles??[]}async createRole(e){let t=await this.request("POST","/v1/roles",e);if(!t)throw new m("POST","/v1/roles",404,"Not found");return t}async getRole(e){return await this.request("GET",`/v1/roles/${encodeURIComponent(e)}`)}async updateRole(e,t){return await this.request("PATCH",`/v1/roles/${encodeURIComponent(e)}`,t)}async deleteRole(e){return(await this.request("DELETE",`/v1/roles/${encodeURIComponent(e)}`))?.deleted??!1}audit={list:async(e={})=>{let t=await this.request("GET",`/v1/audit${i(e)}`);if(!t)throw new m("GET","/v1/audit",404,"Not found");return t},verify:async(e={})=>{let t=await this.request("GET",`/v1/audit/verify${i(e)}`);if(!t)throw new m("GET","/v1/audit/verify",404,"Not found");return t},getSink:async()=>await this.request("GET","/v1/audit/sink"),setSink:async(e)=>{let t=await this.request("PUT","/v1/audit/sink",e);if(!t)throw new m("PUT","/v1/audit/sink",404,"Not found");return t},deleteSink:async()=>(await this.request("DELETE","/v1/audit/sink"))?.deleted??!1};async listChannels(){return(await this.request("GET","/v1/channels"))?.channels??[]}async createChannel(e){let t=await this.request("POST","/v1/channels",e);if(!t)throw new m("POST","/v1/channels",404,"Not found");return t}async getChannel(e){return await this.request("GET",`/v1/channels/${encodeURIComponent(e)}`)}async updateChannel(e,t){return await this.request("PATCH",`/v1/channels/${encodeURIComponent(e)}`,t)}async deleteChannel(e){return(await this.request("DELETE",`/v1/channels/${encodeURIComponent(e)}`))?.deleted??!1}async listSkills(){return(await this.request("GET","/v1/skills"))?.skills??[]}async createSkill(e){let t=await this.request("POST","/v1/skills",e);if(!t)throw new m("POST","/v1/skills",404,"Not found");return t}async getSkill(e){return await this.request("GET",`/v1/skills/${encodeURIComponent(e)}`)}async uploadSkill(e,t){let n=`/v1/skills/${encodeURIComponent(e)}`,s=await this.request("PUT",n,t);if(!s)throw new m("PUT",n,404,"Skill not found");return s}async deleteSkill(e){return(await this.request("DELETE",`/v1/skills/${encodeURIComponent(e)}`))?.deleted??!1}async sandboxAction(e,t,n){let s=`/v1/sandboxes/${encodeURIComponent(e)}/${t}`,r=await this.request("POST",s,n);if(!r)throw new m("POST",s,404,"Sandbox not found");return r}async request(e,t,n){let s=await this.fetchImpl(`${this.baseUrl}${t}`,{method:e,headers:{Authorization:`Bearer ${this.bearerToken}`,...n!==void 0?{"Content-Type":"application/json"}:{}},...n!==void 0?{body:JSON.stringify(n)}:{}});if(s.status===404)return null;if(!s.ok)throw new m(e,t,s.status,await s.text());return await s.json()}}function o(e){return typeof process<"u"?process?.env?.[e]:void 0}function i(e){let t=new URLSearchParams;for(let[n,s]of Object.entries(e))if(s!==void 0)t.set(n,String(s));return t.size>0?`?${t.toString()}`:""}function a(e){return`?${new URLSearchParams({project:e.project,stage:e.stage}).toString()}`}
2
2
  export{m,Ae,Z,R};
@@ -1 +1 @@
1
- import{j}from"./chunk-r86n05ec.js";import{Z,R}from"./chunk-a3f2xzbr.js";import{e,o,a,l,s}from"./chunk-n890pdvc.js";var E={project:e().optional(),stage:e().optional()},M={sandboxId:e().min(1),reservationKey:e().min(1)},v=a().describe("Must be true, after the owner has agreed to this delete."),b="'********' keeps a stored secret and null deletes a field: never send a placeholder you did not read from a get.",A=[{singular:"agent",plural:"agents",key:"agentId",createHint:"Needs name and config; a 409 returns the existing agentId.",deleteHint:"Deleting an agent also drops its runtime rows.",updateHint:b,list:(t)=>t.listAgents(),get:(t,r)=>t.getAgent(r),create:(t,r)=>t.createAgent(r),update:(t,r,n)=>t.updateAgent(r,n),remove:(t,r)=>t.deleteAgent(r)},{singular:"cron",plural:"crons",key:"cronId",createHint:"Needs name, agentId, scheduleExpression (rate/cron/at) and input or events.",list:(t)=>t.listCrons(),get:(t,r)=>t.getCron(r),create:(t,r)=>t.createCron(r),update:(t,r,n)=>t.updateCron(r,n),remove:(t,r)=>t.deleteCron(r)},{singular:"sandbox",plural:"sandboxes",key:"sandboxId",updateHint:b,list:(t)=>t.listSandboxes(),get:(t,r)=>t.getSandbox(r),create:(t,r)=>t.createSandbox(r),update:(t,r,n)=>t.updateSandbox(r,n),remove:(t,r)=>t.deleteSandbox(r)},{singular:"workspace",plural:"workspaces",key:"workspaceId",list:(t)=>t.listWorkspaces(),get:(t,r)=>t.getWorkspace(r),create:(t,r)=>t.createWorkspace(r),update:(t,r,n)=>t.updateWorkspace(r,n),remove:(t,r)=>t.deleteWorkspace(r)},{singular:"policy",plural:"policies",key:"policyId",createHint:"Agent-runtime policy document (tool.call, skill.load), not API credential scoping.",list:(t)=>t.listPolicies(),get:(t,r)=>t.getPolicy(r),create:(t,r)=>t.createPolicy(r),update:(t,r,n)=>t.updatePolicy(r,n),remove:(t,r)=>t.deletePolicy(r)},{singular:"role",plural:"roles",key:"roleId",createHint:"Needs name and a version-1 policy over the API namespace, e.g. agents:read. Account key only.",list:(t)=>t.listRoles(),get:(t,r)=>t.getRole(r),create:(t,r)=>t.createRole(r),update:(t,r,n)=>t.updateRole(r,n),remove:(t,r)=>t.deleteRole(r)},{singular:"channel",plural:"channels",key:"channelId",list:(t)=>t.listChannels(),get:(t,r)=>t.getChannel(r),create:(t,r)=>t.createChannel(r),update:(t,r,n)=>t.updateChannel(r,n),remove:(t,r)=>t.deleteChannel(r)},{singular:"skill",plural:"skills",key:"skillName",createHint:'source is "files" (base64), "json" or "github". The stored name comes from the SKILL.md frontmatter.',list:(t)=>t.listSkills(),get:(t,r)=>t.getSkill(r),create:(t,r)=>t.createSkill(r),remove:(t,r)=>t.deleteSkill(r)},{singular:"mcp",plural:"mcp",key:"serverId",scoped:!0,list:(t,r)=>t.listMcp(r),get:(t,r)=>t.getMcp(r),create:(t,r,n)=>t.createMcp(n,r),update:(t,r,n)=>t.updateMcp(r,n),remove:(t,r)=>t.deleteMcp(r)}];function Pt(t={}){let r=Z()?new R({}):null;if(!r&&!t.cli)throw Error("broods mcp needs a credential: set BROODS_SESSION_TOKEN or BROODS_ACCOUNT_SECRET, or run `broods login`.");let n=new j({name:"broods",version:"1"},{capabilities:{tools:{}}});if(r){for(let i of A)x(n,r,i,t.scope??{});N(n,r)}if(t.cli)O(n,t.cli);if(t.allowDestructive)q(n,r,t.cli??null);return n}function y(t,r){if(!t)throw Error(`Refusing without confirm:true. ${r}`)}async function d(t){try{return P(await t())}catch(r){return k(r)}}function k(t){return{content:[{type:"text",text:t instanceof Error?t.message:String(t)}],isError:!0}}function P(t){return{content:[{type:"text",text:JSON.stringify(t??null,null,2)}]}}function T(t,r,n,i){if(!t.scoped)return;let c=n??r.project,p=i??r.stage;if(!c||!p)throw Error(`${t.plural} live in one stage, so this call needs both 'project' and 'stage'`);return{project:c,stage:p}}function O(t,r){t.registerTool("list-orgs",{description:"Every organization this login can reach, with the current one marked.",inputSchema:{}},async()=>await d(async()=>{let n=await r.getOnboarding();return{currentOrgId:n.currentOrgId,orgs:n.orgs}})),t.registerTool("create-org",{description:"Create an organization and switch this login to it. Later calls act in the new org.",inputSchema:{name:e().min(1)}},async({name:n})=>await d(async()=>await r.createOnboardingOrg(n))),t.registerTool("select-org",{description:"Switch this login to another organization, by an orgId from list-orgs.",inputSchema:{orgId:e().min(1)}},async({orgId:n})=>await d(async()=>await r.selectOnboardingOrg(n))),t.registerTool("list-projects",{description:"Every project in the current org, empty ones last. Project names are not unique, so take the id from here.",inputSchema:{}},async()=>await d(async()=>await r.listProjects())),t.registerTool("list-stages",{description:"Every stage of one project, by project name.",inputSchema:{project:e().min(1)}},async({project:n})=>await d(async()=>await r.listStages(n))),t.registerTool("create-stage",{description:"Create a stage in a project, optionally cloning another stage's architecture and env vars. A project name that does not exist yet is created with it, which is the only way to make a project without deploying a manifest.",inputSchema:{project:e().min(1),name:e().min(1),from:e().optional().describe("Stage to clone architecture and env vars from.")}},async({project:n,name:i,from:c})=>await d(async()=>await r.createStage(n,i,c)))}function q(t,r,n){if(r)t.registerTool("rotate-secret",{description:"Rotate the account key. The current key stops working immediately and the new one is shown once, so this breaks every deployment and CI job still holding the old one. Requires confirm:true.",inputSchema:{confirm:v}},async({confirm:i})=>await d(async()=>(y(i,"Everything holding the current secret breaks the moment this runs."),await r.rotateSecret())));if(n)t.registerTool("delete-project",{description:"Delete a project and everything under it: stages, agents, canvas, env vars, crons and workspace files. Takes a projectId from list-projects and requires confirm:true.",inputSchema:{projectId:e().min(1),confirm:v}},async({projectId:i,confirm:c})=>await d(async()=>(y(c,`Project '${i}' and everything under it goes away: name it, get the owner's agreement, then retry.`),await n.deleteProject(i))))}function N(t,r){t.registerTool("list-cron-runs",{description:"Run history for one cron, newest first. Check this before telling anyone a cron fired.",inputSchema:{cronId:e().min(1),limit:o().int().positive().max(100).optional()}},async({cronId:i,limit:c})=>await d(async()=>await r.listCronRuns(i,c===void 0?{}:{limit:c})));let n=[["suspend-sandbox","Suspend a persistent sandbox reservation.",(i,c)=>r.suspendSandbox(i,c)],["resume-sandbox","Resume a suspended sandbox reservation.",(i,c)=>r.resumeSandbox(i,c)],["terminate-sandbox","Terminate a sandbox reservation and drop its live-instance row. Terminate only a sandbox you created, and never one holding state you did not put there.",(i,c)=>r.terminateSandbox(i,c)],["open-sandbox-terminal","Mint a short-lived sealed ticket for an interactive PTY session on a persistent sandbox.",(i,c)=>r.openSandboxTerminal(i,c)]];for(let[i,c,p]of n)t.registerTool(i,{description:c,inputSchema:M},async({sandboxId:f,reservationKey:g})=>await d(async()=>await p(f,g)));t.registerTool("snapshot-sandbox",{description:"Snapshot a sandbox reservation into a reusable image (self-hosted provider).",inputSchema:{sandboxId:e().min(1),reservationKey:e().min(1),name:e().min(1)}},async({sandboxId:i,reservationKey:c,name:p})=>await d(async()=>await r.snapshotSandbox(i,c,p))),t.registerTool("upload-skill",{description:"Replace one skill's bundle wholesale (PUT, not a merge) by skillName. 'body' is the same source shape create-skill takes.",inputSchema:{id:e().min(1).describe("The skillName."),body:s(e(),l())}},async({id:i,body:c})=>await d(async()=>await r.uploadSkill(i,c))),t.registerTool("list-env-vars",{description:"List environment variable names. Values are write-only: the plane never returns them, so there is no read.",inputSchema:{}},async()=>await d(async()=>await r.listEnvVars())),t.registerTool("set-env-var",{description:"Create or replace one write-only environment variable. Reference it from a config as ${NAME} rather than inlining the value.",inputSchema:{name:e().min(1),value:e()}},async({name:i,value:c})=>await d(async()=>(await r.setEnvVar(i,c),{name:i,stored:!0}))),t.registerTool("delete-env-var",{description:"Delete one environment variable.",inputSchema:{name:e().min(1),confirm:v}},async({name:i,confirm:c})=>await d(async()=>(y(c,`Anything referencing \${${i}} breaks the moment this env var goes.`),{deleted:await r.deleteEnvVar(i),name:i}))),t.registerTool("assume-role",{description:"Exchange a role for a short-lived session token (default 1h, max 12h). Hand the returned token to whatever tool needs narrow access, as BROODS_SESSION_TOKEN. It is shown once and is not stored here.",inputSchema:{roleId:e().min(1),ttlSeconds:o().int().positive().max(43200).optional()}},async({roleId:i,ttlSeconds:c})=>await d(async()=>await r.assumeRole(i,c===void 0?{}:{ttlSeconds:c}))),t.registerTool("update-account",{description:"Rename this account or change its description. Everything else about an account is derived, not settable.",inputSchema:{username:e().optional(),description:e().nullable().optional()}},async({username:i,description:c})=>await d(async()=>await r.updateAccount({...i===void 0?{}:{username:i},...c===void 0?{}:{description:c}}))),t.registerTool("get-account",{description:"The account this server's credential resolves to. Run it first to confirm which tenant you are about to change.",inputSchema:{}},async()=>await d(async()=>await r.getAccount()))}function x(t,r,n,i){let c=e().min(1).describe(`The ${n.key}.`),p=n.scoped?` ${n.plural} live in one stage, so this also takes 'project' and 'stage'.`:"",{list:f,get:g,create:w,update:S,remove:_}=n;if(f){let u=`List every ${n.singular} on the account.${p}`;if(n.scoped)t.registerTool(`list-${n.plural}`,{description:u,inputSchema:E},async({project:h,stage:m})=>await d(async()=>await f(r,T(n,i,h,m))));else t.registerTool(`list-${n.plural}`,{description:u,inputSchema:{}},async()=>await d(async()=>await f(r)))}if(g)t.registerTool(`get-${n.singular}`,{description:`Read one ${n.singular} by ${n.key}.`,inputSchema:{id:c}},async({id:u})=>await d(async()=>await g(r,u)));if(w){let u=`Create one ${n.singular}. 'body' is the request body the config plane documents.`+(n.createHint?` ${n.createHint}`:"")+p+" Prefer changing the broods/ manifest and deploying for anything the project already declares.",h=s(e(),l());if(n.scoped)t.registerTool(`create-${n.singular}`,{description:u,inputSchema:{body:h,...E}},async({body:m,project:I,stage:C})=>await d(async()=>await w(r,m,T(n,i,I,C))));else t.registerTool(`create-${n.singular}`,{description:u,inputSchema:{body:h}},async({body:m})=>await d(async()=>await w(r,m)))}if(S)t.registerTool(`update-${n.singular}`,{description:`Deep-merge a patch into one ${n.singular} and return the updated record, so there is no need to read it back.`+(n.updateHint?` ${n.updateHint}`:""),inputSchema:{id:c,body:s(e(),l())}},async({id:u,body:h})=>await d(async()=>await S(r,u,h)));if(_)t.registerTool(`delete-${n.singular}`,{description:`Delete one ${n.singular}. Requires confirm:true, and takes one ${n.key} per call: never loop this over a list.`+(n.deleteHint?` ${n.deleteHint}`:""),inputSchema:{id:c,confirm:v}},async({id:u,confirm:h})=>await d(async()=>(y(h,`Deleting ${n.singular} '${u}' goes away for good: name it, get the owner's agreement, then retry.`),{deleted:await _(r,u),id:u})))}export{Pt as createBroodsMcpServer};
1
+ import{j}from"./chunk-r86n05ec.js";import{Z,R}from"./chunk-bb3jfhv3.js";import{e,o,a,l,s}from"./chunk-n890pdvc.js";var E={project:e().optional(),stage:e().optional()},M={sandboxId:e().min(1),reservationKey:e().min(1)},v=a().describe("Must be true, after the owner has agreed to this delete."),b="'********' keeps a stored secret and null deletes a field: never send a placeholder you did not read from a get.",A=[{singular:"agent",plural:"agents",key:"agentId",createHint:"Needs name and config; a 409 returns the existing agentId.",deleteHint:"Deleting an agent also drops its runtime rows.",updateHint:b,list:(t)=>t.listAgents(),get:(t,r)=>t.getAgent(r),create:(t,r)=>t.createAgent(r),update:(t,r,n)=>t.updateAgent(r,n),remove:(t,r)=>t.deleteAgent(r)},{singular:"cron",plural:"crons",key:"cronId",createHint:"Needs name, agentId, scheduleExpression (rate/cron/at) and input or events.",list:(t)=>t.listCrons(),get:(t,r)=>t.getCron(r),create:(t,r)=>t.createCron(r),update:(t,r,n)=>t.updateCron(r,n),remove:(t,r)=>t.deleteCron(r)},{singular:"sandbox",plural:"sandboxes",key:"sandboxId",updateHint:b,list:(t)=>t.listSandboxes(),get:(t,r)=>t.getSandbox(r),create:(t,r)=>t.createSandbox(r),update:(t,r,n)=>t.updateSandbox(r,n),remove:(t,r)=>t.deleteSandbox(r)},{singular:"workspace",plural:"workspaces",key:"workspaceId",list:(t)=>t.listWorkspaces(),get:(t,r)=>t.getWorkspace(r),create:(t,r)=>t.createWorkspace(r),update:(t,r,n)=>t.updateWorkspace(r,n),remove:(t,r)=>t.deleteWorkspace(r)},{singular:"policy",plural:"policies",key:"policyId",createHint:"Agent-runtime policy document (tool.call, skill.load), not API credential scoping.",list:(t)=>t.listPolicies(),get:(t,r)=>t.getPolicy(r),create:(t,r)=>t.createPolicy(r),update:(t,r,n)=>t.updatePolicy(r,n),remove:(t,r)=>t.deletePolicy(r)},{singular:"role",plural:"roles",key:"roleId",createHint:"Needs name and a version-1 policy over the API namespace, e.g. agents:read. Account key only.",list:(t)=>t.listRoles(),get:(t,r)=>t.getRole(r),create:(t,r)=>t.createRole(r),update:(t,r,n)=>t.updateRole(r,n),remove:(t,r)=>t.deleteRole(r)},{singular:"channel",plural:"channels",key:"channelId",list:(t)=>t.listChannels(),get:(t,r)=>t.getChannel(r),create:(t,r)=>t.createChannel(r),update:(t,r,n)=>t.updateChannel(r,n),remove:(t,r)=>t.deleteChannel(r)},{singular:"skill",plural:"skills",key:"skillName",createHint:'source is "files" (base64), "json" or "github". The stored name comes from the SKILL.md frontmatter.',list:(t)=>t.listSkills(),get:(t,r)=>t.getSkill(r),create:(t,r)=>t.createSkill(r),remove:(t,r)=>t.deleteSkill(r)},{singular:"mcp",plural:"mcp",key:"serverId",scoped:!0,list:(t,r)=>t.listMcp(r),get:(t,r)=>t.getMcp(r),create:(t,r,n)=>t.createMcp(n,r),update:(t,r,n)=>t.updateMcp(r,n),remove:(t,r)=>t.deleteMcp(r)}];function Pt(t={}){let r=Z()?new R({}):null;if(!r&&!t.cli)throw Error("broods mcp needs a credential: set BROODS_SESSION_TOKEN or BROODS_ACCOUNT_SECRET, or run `broods login`.");let n=new j({name:"broods",version:"1"},{capabilities:{tools:{}}});if(r){for(let i of A)x(n,r,i,t.scope??{});N(n,r)}if(t.cli)O(n,t.cli);if(t.allowDestructive)q(n,r,t.cli??null);return n}function y(t,r){if(!t)throw Error(`Refusing without confirm:true. ${r}`)}async function d(t){try{return P(await t())}catch(r){return k(r)}}function k(t){return{content:[{type:"text",text:t instanceof Error?t.message:String(t)}],isError:!0}}function P(t){return{content:[{type:"text",text:JSON.stringify(t??null,null,2)}]}}function T(t,r,n,i){if(!t.scoped)return;let c=n??r.project,p=i??r.stage;if(!c||!p)throw Error(`${t.plural} live in one stage, so this call needs both 'project' and 'stage'`);return{project:c,stage:p}}function O(t,r){t.registerTool("list-orgs",{description:"Every organization this login can reach, with the current one marked.",inputSchema:{}},async()=>await d(async()=>{let n=await r.getOnboarding();return{currentOrgId:n.currentOrgId,orgs:n.orgs}})),t.registerTool("create-org",{description:"Create an organization and switch this login to it. Later calls act in the new org.",inputSchema:{name:e().min(1)}},async({name:n})=>await d(async()=>await r.createOnboardingOrg(n))),t.registerTool("select-org",{description:"Switch this login to another organization, by an orgId from list-orgs.",inputSchema:{orgId:e().min(1)}},async({orgId:n})=>await d(async()=>await r.selectOnboardingOrg(n))),t.registerTool("list-projects",{description:"Every project in the current org, empty ones last. Project names are not unique, so take the id from here.",inputSchema:{}},async()=>await d(async()=>await r.listProjects())),t.registerTool("list-stages",{description:"Every stage of one project, by project name.",inputSchema:{project:e().min(1)}},async({project:n})=>await d(async()=>await r.listStages(n))),t.registerTool("create-stage",{description:"Create a stage in a project, optionally cloning another stage's architecture and env vars. A project name that does not exist yet is created with it, which is the only way to make a project without deploying a manifest.",inputSchema:{project:e().min(1),name:e().min(1),from:e().optional().describe("Stage to clone architecture and env vars from.")}},async({project:n,name:i,from:c})=>await d(async()=>await r.createStage(n,i,c)))}function q(t,r,n){if(r)t.registerTool("rotate-secret",{description:"Rotate the account key. The current key stops working immediately and the new one is shown once, so this breaks every deployment and CI job still holding the old one. Requires confirm:true.",inputSchema:{confirm:v}},async({confirm:i})=>await d(async()=>(y(i,"Everything holding the current secret breaks the moment this runs."),await r.rotateSecret())));if(n)t.registerTool("delete-project",{description:"Delete a project and everything under it: stages, agents, canvas, env vars, crons and workspace files. Takes a projectId from list-projects and requires confirm:true.",inputSchema:{projectId:e().min(1),confirm:v}},async({projectId:i,confirm:c})=>await d(async()=>(y(c,`Project '${i}' and everything under it goes away: name it, get the owner's agreement, then retry.`),await n.deleteProject(i))))}function N(t,r){t.registerTool("list-cron-runs",{description:"Run history for one cron, newest first. Check this before telling anyone a cron fired.",inputSchema:{cronId:e().min(1),limit:o().int().positive().max(100).optional()}},async({cronId:i,limit:c})=>await d(async()=>await r.listCronRuns(i,c===void 0?{}:{limit:c})));let n=[["suspend-sandbox","Suspend a persistent sandbox reservation.",(i,c)=>r.suspendSandbox(i,c)],["resume-sandbox","Resume a suspended sandbox reservation.",(i,c)=>r.resumeSandbox(i,c)],["terminate-sandbox","Terminate a sandbox reservation and drop its live-instance row. Terminate only a sandbox you created, and never one holding state you did not put there.",(i,c)=>r.terminateSandbox(i,c)],["open-sandbox-terminal","Mint a short-lived sealed ticket for an interactive PTY session on a persistent sandbox.",(i,c)=>r.openSandboxTerminal(i,c)]];for(let[i,c,p]of n)t.registerTool(i,{description:c,inputSchema:M},async({sandboxId:f,reservationKey:g})=>await d(async()=>await p(f,g)));t.registerTool("snapshot-sandbox",{description:"Snapshot a sandbox reservation into a reusable image (self-hosted provider).",inputSchema:{sandboxId:e().min(1),reservationKey:e().min(1),name:e().min(1)}},async({sandboxId:i,reservationKey:c,name:p})=>await d(async()=>await r.snapshotSandbox(i,c,p))),t.registerTool("upload-skill",{description:"Replace one skill's bundle wholesale (PUT, not a merge) by skillName. 'body' is the same source shape create-skill takes.",inputSchema:{id:e().min(1).describe("The skillName."),body:s(e(),l())}},async({id:i,body:c})=>await d(async()=>await r.uploadSkill(i,c))),t.registerTool("list-env-vars",{description:"List environment variable names. Values are write-only: the plane never returns them, so there is no read.",inputSchema:{}},async()=>await d(async()=>await r.listEnvVars())),t.registerTool("set-env-var",{description:"Create or replace one write-only environment variable. Reference it from a config as ${NAME} rather than inlining the value.",inputSchema:{name:e().min(1),value:e()}},async({name:i,value:c})=>await d(async()=>(await r.setEnvVar(i,c),{name:i,stored:!0}))),t.registerTool("delete-env-var",{description:"Delete one environment variable.",inputSchema:{name:e().min(1),confirm:v}},async({name:i,confirm:c})=>await d(async()=>(y(c,`Anything referencing \${${i}} breaks the moment this env var goes.`),{deleted:await r.deleteEnvVar(i),name:i}))),t.registerTool("assume-role",{description:"Exchange a role for a short-lived session token (default 1h, max 12h). Hand the returned token to whatever tool needs narrow access, as BROODS_SESSION_TOKEN. It is shown once and is not stored here.",inputSchema:{roleId:e().min(1),ttlSeconds:o().int().positive().max(43200).optional()}},async({roleId:i,ttlSeconds:c})=>await d(async()=>await r.assumeRole(i,c===void 0?{}:{ttlSeconds:c}))),t.registerTool("update-account",{description:"Rename this account or change its description. Everything else about an account is derived, not settable.",inputSchema:{username:e().optional(),description:e().nullable().optional()}},async({username:i,description:c})=>await d(async()=>await r.updateAccount({...i===void 0?{}:{username:i},...c===void 0?{}:{description:c}}))),t.registerTool("get-account",{description:"The account this server's credential resolves to. Run it first to confirm which tenant you are about to change.",inputSchema:{}},async()=>await d(async()=>await r.getAccount()))}function x(t,r,n,i){let c=e().min(1).describe(`The ${n.key}.`),p=n.scoped?` ${n.plural} live in one stage, so this also takes 'project' and 'stage'.`:"",{list:f,get:g,create:w,update:S,remove:_}=n;if(f){let u=`List every ${n.singular} on the account.${p}`;if(n.scoped)t.registerTool(`list-${n.plural}`,{description:u,inputSchema:E},async({project:h,stage:m})=>await d(async()=>await f(r,T(n,i,h,m))));else t.registerTool(`list-${n.plural}`,{description:u,inputSchema:{}},async()=>await d(async()=>await f(r)))}if(g)t.registerTool(`get-${n.singular}`,{description:`Read one ${n.singular} by ${n.key}.`,inputSchema:{id:c}},async({id:u})=>await d(async()=>await g(r,u)));if(w){let u=`Create one ${n.singular}. 'body' is the request body the config plane documents.`+(n.createHint?` ${n.createHint}`:"")+p+" Prefer changing the broods/ manifest and deploying for anything the project already declares.",h=s(e(),l());if(n.scoped)t.registerTool(`create-${n.singular}`,{description:u,inputSchema:{body:h,...E}},async({body:m,project:I,stage:C})=>await d(async()=>await w(r,m,T(n,i,I,C))));else t.registerTool(`create-${n.singular}`,{description:u,inputSchema:{body:h}},async({body:m})=>await d(async()=>await w(r,m)))}if(S)t.registerTool(`update-${n.singular}`,{description:`Deep-merge a patch into one ${n.singular} and return the updated record, so there is no need to read it back.`+(n.updateHint?` ${n.updateHint}`:""),inputSchema:{id:c,body:s(e(),l())}},async({id:u,body:h})=>await d(async()=>await S(r,u,h)));if(_)t.registerTool(`delete-${n.singular}`,{description:`Delete one ${n.singular}. Requires confirm:true, and takes one ${n.key} per call: never loop this over a list.`+(n.deleteHint?` ${n.deleteHint}`:""),inputSchema:{id:c,confirm:v}},async({id:u,confirm:h})=>await d(async()=>(y(h,`Deleting ${n.singular} '${u}' goes away for good: name it, get the owner's agreement, then retry.`),{deleted:await _(r,u),id:u})))}export{Pt as createBroodsMcpServer};