@opencomputer/blue 0.0.4 → 0.0.5

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 CHANGED
@@ -14,7 +14,7 @@ control-plane contract:
14
14
  and hibernating WebSockets;
15
15
  - WorkOS device authentication for the hosted CLI;
16
16
  - a Blue model gateway that keeps the platform OpenRouter key server-side;
17
- - managed remote Slack connections through Composio Connect Links and triggers;
17
+ - native local and deployed Slack apps managed through the Slack CLI;
18
18
  - sub-second content-addressed agent artifacts on a generic Lambda MicroVM
19
19
  runtime image;
20
20
  - an AccountDO registry for agents, immutable deployments, and aliases;
@@ -132,61 +132,56 @@ package exposes the shorter `blue` binary.
132
132
 
133
133
  ### Local Slack
134
134
 
135
- Add the declarative Slack channel to an agent:
135
+ Install and authenticate the
136
+ [Slack CLI](https://docs.slack.dev/tools/slack-cli/), then add Slack to an
137
+ agent:
136
138
 
137
139
  ```bash
138
140
  blue channels add slack
141
+ blue channels connect slack --local
139
142
  ```
140
143
 
141
- For local development, Socket Mode is the shortest path:
142
-
143
- ```env
144
- SLACK_APP_TOKEN=xapp-...
145
- SLACK_BOT_TOKEN=xoxb-...
146
- ```
147
-
148
- Restart `blue dev`; it prints `slack: Socket Mode connected`. No request URL or
149
- tunnel is required. Subscribe the app to `app_mention` and grant
150
- `app_mentions:read` plus `chat:write`.
144
+ The first command checks in the Slack manifest and Slack CLI hooks. The second
145
+ creates and installs the agent's local Slack app. Start it with `blue dev`;
146
+ Slack CLI supplies its development app and bot tokens to Blue's start hook, and
147
+ Blue connects over Socket Mode. No tunnel or manually copied token is needed.
151
148
 
152
149
  Blue immediately shows Slack's native loading indicator, rotates startup
153
150
  messages, and updates the status with reasoning and tool activity. The final
154
151
  reply clears the indicator automatically. Each Slack thread maps to one
155
152
  persistent local Blue session, with the mapping stored under `.blue/dev/`.
156
153
 
157
- The HTTP Events API remains available as a local fallback. Set
158
- `SLACK_SIGNING_SECRET` and `SLACK_BOT_TOKEN`, expose the webhook printed by
159
- `blue dev`, and configure it as the Slack app's Events API request URL.
154
+ The non-secret app and workspace identity is recorded in
155
+ `.blue/channels/slack.local.json`. Slack CLI owns the local tokens in its
156
+ credential store; they are not written to the agent's `.env`.
160
157
 
161
158
  ### Remote Slack
162
159
 
163
- Remote agents use Composio's managed Slack app. Slack credentials stay in
164
- Composio; Blue stores only the connected-account and trigger IDs. First declare
165
- Slack in the agent and deploy it:
160
+ Deploy the same agent, then create its production Slack app:
166
161
 
167
162
  ```bash
168
163
  blue channels add slack
169
164
  blue deploy
165
+ blue channels connect slack --remote
170
166
  ```
171
167
 
172
- `blue deploy` prints the agent's hosted landing page. Connect one Slack channel
173
- using its Slack channel ID:
174
-
175
- ```bash
176
- blue channels connect slack --channel C0123456789
177
- ```
168
+ Blue first reserves a callback for the deployed agent. Slack CLI creates or
169
+ updates the production app from the checked-in manifest, verifies the callback,
170
+ and invokes Blue's deploy hook with the resulting bot token. Blue encrypts that
171
+ token with its platform channel key. It is never written to the repository or
172
+ sent to the agent MicroVM.
178
173
 
179
- The CLI opens a hosted Composio Connect Link. Complete Slack authorization,
180
- invite the managed app to the selected channel when Slack requires it, then
181
- send a message there. Each Slack thread maps to one persistent remote Blue
182
- session. Blue resumes that session's MicroVM for later turns, posts the answer
183
- in the same thread, and suspends the MicroVM after delivery.
174
+ Invite the resulting app to any channel where it should run, then mention it.
175
+ Direct messages also work. Each Slack thread maps to one persistent remote Blue
176
+ session. Blue resumes that session's MicroVM for later turns, replies as the
177
+ agent's Slack app, and suspends the MicroVM after delivery.
184
178
 
185
179
  Inspect and remove managed connections with:
186
180
 
187
181
  ```bash
188
182
  blue channels list
189
- blue channels disconnect slack <connection-id>
183
+ blue channels disconnect slack --local
184
+ blue channels disconnect slack --remote
190
185
  ```
191
186
 
192
187
  ## Hosted Flow
@@ -224,8 +219,8 @@ The hosted Worker requires:
224
219
  - `BLUE_WORKOS_CLIENT_ID` and optionally `BLUE_WORKOS_ISSUER` /
225
220
  `BLUE_WORKOS_JWKS_URL`;
226
221
  - `OPENROUTER_API_KEY` as a Worker secret;
227
- - `COMPOSIO_API_KEY`, `COMPOSIO_SLACK_AUTH_CONFIG_ID`, and
228
- `COMPOSIO_WEBHOOK_SECRET` as Worker secrets;
222
+ - `BLUE_CHANNEL_ENCRYPTION_KEY` as a Worker secret containing a base64-encoded
223
+ 32-byte random key;
229
224
  - `BLUE_RUNTIME_IMAGE_ARN` and `BLUE_RUNTIME_IMAGE_VERSION`;
230
225
  - a `DEPLOYMENTS` service binding that returns signed platform artifact
231
226
  uploads;
@@ -244,23 +239,14 @@ npx wrangler secret put BLUE_RUNTIME_IMAGE_ARN \
244
239
  --config wrangler.production.jsonc
245
240
  npx wrangler secret put BLUE_RUNTIME_IMAGE_VERSION \
246
241
  --config wrangler.production.jsonc
247
- npx wrangler secret put COMPOSIO_API_KEY \
242
+ npx wrangler secret put BLUE_CHANNEL_ENCRYPTION_KEY \
248
243
  --config wrangler.production.jsonc
249
- npx wrangler secret put COMPOSIO_SLACK_AUTH_CONFIG_ID \
250
- --config wrangler.production.jsonc
251
- npx wrangler secret put COMPOSIO_WEBHOOK_SECRET \
252
- --config wrangler.production.jsonc
253
- ```
254
-
255
- Create one Composio webhook subscription for the project:
256
-
257
- ```text
258
- https://blue.opencomputer.dev/v1/webhooks/composio
259
244
  ```
260
245
 
261
- Enable `composio.trigger.message` and use the subscription's signing secret as
262
- `COMPOSIO_WEBHOOK_SECRET`. Blue creates one
263
- `SLACK_CHANNEL_MESSAGE_RECEIVED` trigger per connected agent/channel.
246
+ Generate the channel encryption secret with `openssl rand -base64 32`. Every
247
+ remote Slack installation receives a unique callback credential, stored by
248
+ Blue only as a hash. Slack CLI configures that callback automatically from the
249
+ agent manifest.
264
250
 
265
251
  Configure the platform Worker's AWS secrets plus
266
252
  `BLUE_AWS_REGION`, `BLUE_ARTIFACT_BUCKET`, `BLUE_EXECUTION_ROLE_ARN`,
@@ -0,0 +1,24 @@
1
+ import type { SlackStatus } from "./slack-progress.js";
2
+ export interface SlackInstallation {
3
+ appId: string;
4
+ teamId: string;
5
+ teamName?: string;
6
+ botUserId: string;
7
+ botId: string;
8
+ }
9
+ export declare function resolveSlackInstallation(botToken: string): Promise<SlackInstallation>;
10
+ export declare function postSlackMessage(botToken: string, input: {
11
+ channelId: string;
12
+ threadTimestamp: string;
13
+ text: string;
14
+ }): Promise<string>;
15
+ export declare function updateSlackMessage(botToken: string, input: {
16
+ channelId: string;
17
+ messageTimestamp: string;
18
+ text: string;
19
+ }): Promise<void>;
20
+ export declare function setSlackThreadStatus(botToken: string, input: {
21
+ channelId: string;
22
+ threadTimestamp: string;
23
+ status: SlackStatus;
24
+ }): Promise<void>;
@@ -0,0 +1,69 @@
1
+ import { isRecord } from "../protocol.js";
2
+ const SLACK_API = "https://slack.com/api";
3
+ async function slackRequest(token, method, body) {
4
+ const response = await fetch(`${SLACK_API}/${method}`, {
5
+ method: "POST",
6
+ headers: {
7
+ authorization: `Bearer ${token}`,
8
+ "content-type": "application/json; charset=utf-8",
9
+ },
10
+ body: JSON.stringify(body),
11
+ });
12
+ const result = await response.json().catch(() => null);
13
+ if (!response.ok || !isRecord(result) || result.ok !== true) {
14
+ const message = isRecord(result) && typeof result.error === "string"
15
+ ? result.error
16
+ : `HTTP ${String(response.status)}`;
17
+ throw new Error(`Slack ${method} failed: ${message}`);
18
+ }
19
+ return result;
20
+ }
21
+ export async function resolveSlackInstallation(botToken) {
22
+ const identity = await slackRequest(botToken, "auth.test", {});
23
+ const teamId = typeof identity.team_id === "string" ? identity.team_id : null;
24
+ const botUserId = typeof identity.user_id === "string" ? identity.user_id : null;
25
+ const botId = typeof identity.bot_id === "string" ? identity.bot_id : null;
26
+ if (!teamId || !botUserId || !botId) {
27
+ throw new Error("Slack auth.test did not return a bot installation");
28
+ }
29
+ const bot = await slackRequest(botToken, "bots.info", { bot: botId });
30
+ const botInfo = isRecord(bot.bot) ? bot.bot : null;
31
+ const appId = botInfo && typeof botInfo.app_id === "string" ? botInfo.app_id : null;
32
+ if (!appId) {
33
+ throw new Error("Slack bots.info did not return an app ID");
34
+ }
35
+ return {
36
+ appId,
37
+ teamId,
38
+ teamName: typeof identity.team === "string" ? identity.team : undefined,
39
+ botUserId,
40
+ botId,
41
+ };
42
+ }
43
+ export async function postSlackMessage(botToken, input) {
44
+ const result = await slackRequest(botToken, "chat.postMessage", {
45
+ channel: input.channelId,
46
+ thread_ts: input.threadTimestamp,
47
+ text: input.text.slice(0, 35_000),
48
+ });
49
+ if (typeof result.ts !== "string") {
50
+ throw new Error("Slack chat.postMessage did not return a timestamp");
51
+ }
52
+ return result.ts;
53
+ }
54
+ export async function updateSlackMessage(botToken, input) {
55
+ await slackRequest(botToken, "chat.update", {
56
+ channel: input.channelId,
57
+ ts: input.messageTimestamp,
58
+ text: input.text.slice(0, 35_000),
59
+ });
60
+ }
61
+ export async function setSlackThreadStatus(botToken, input) {
62
+ await slackRequest(botToken, "assistant.threads.setStatus", {
63
+ channel_id: input.channelId,
64
+ thread_ts: input.threadTimestamp,
65
+ status: input.status.status,
66
+ loading_messages: input.status.loadingMessages,
67
+ });
68
+ }
69
+ //# sourceMappingURL=slack-api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slack-api.js","sourceRoot":"","sources":["../../src/channels/slack-api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAG1C,MAAM,SAAS,GAAG,uBAAuB,CAAC;AAU1C,KAAK,UAAU,YAAY,CACzB,KAAa,EACb,MAAc,EACd,IAA6B;IAE7B,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,SAAS,IAAI,MAAM,EAAE,EAAE;QACrD,MAAM,EAAE,MAAM;QACd,OAAO,EAAE;YACP,aAAa,EAAE,UAAU,KAAK,EAAE;YAChC,cAAc,EAAE,iCAAiC;SAClD;QACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;KAC3B,CAAC,CAAC;IACH,MAAM,MAAM,GAAY,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IAChE,IAAI,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,EAAE,KAAK,IAAI,EAAE,CAAC;QAC5D,MAAM,OAAO,GACX,QAAQ,CAAC,MAAM,CAAC,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ;YAClD,CAAC,CAAC,MAAM,CAAC,KAAK;YACd,CAAC,CAAC,QAAQ,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CAAC,SAAS,MAAM,YAAY,OAAO,EAAE,CAAC,CAAC;IACxD,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,QAAgB;IAEhB,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,QAAQ,EAAE,WAAW,EAAE,EAAE,CAAC,CAAC;IAC/D,MAAM,MAAM,GACV,OAAO,QAAQ,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;IACjE,MAAM,SAAS,GACb,OAAO,QAAQ,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;IACjE,MAAM,KAAK,GAAG,OAAO,QAAQ,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;IAC3E,IAAI,CAAC,MAAM,IAAI,CAAC,SAAS,IAAI,CAAC,KAAK,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;IACvE,CAAC;IACD,MAAM,GAAG,GAAG,MAAM,YAAY,CAAC,QAAQ,EAAE,WAAW,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC;IACtE,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;IACnD,MAAM,KAAK,GACT,OAAO,IAAI,OAAO,OAAO,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;IACxE,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO;QACL,KAAK;QACL,MAAM;QACN,QAAQ,EACN,OAAO,QAAQ,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;QAC/D,SAAS;QACT,KAAK;KACN,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,QAAgB,EAChB,KAIC;IAED,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,QAAQ,EAAE,kBAAkB,EAAE;QAC9D,OAAO,EAAE,KAAK,CAAC,SAAS;QACxB,SAAS,EAAE,KAAK,CAAC,eAAe;QAChC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC;KAClC,CAAC,CAAC;IACH,IAAI,OAAO,MAAM,CAAC,EAAE,KAAK,QAAQ,EAAE,CAAC;QAClC,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;IACvE,CAAC;IACD,OAAO,MAAM,CAAC,EAAE,CAAC;AACnB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,QAAgB,EAChB,KAIC;IAED,MAAM,YAAY,CAAC,QAAQ,EAAE,aAAa,EAAE;QAC1C,OAAO,EAAE,KAAK,CAAC,SAAS;QACxB,EAAE,EAAE,KAAK,CAAC,gBAAgB;QAC1B,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC;KAClC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,QAAgB,EAChB,KAIC;IAED,MAAM,YAAY,CAAC,QAAQ,EAAE,6BAA6B,EAAE;QAC1D,UAAU,EAAE,KAAK,CAAC,SAAS;QAC3B,SAAS,EAAE,KAAK,CAAC,eAAe;QAChC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM;QAC3B,gBAAgB,EAAE,KAAK,CAAC,MAAM,CAAC,eAAe;KAC/C,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,8 @@
1
+ export declare function verifySlackSignature(payload: string, headers: Headers, secret: string, now?: number): Promise<boolean>;
2
+ export interface SlackEventCallback {
3
+ id: string;
4
+ appId: string;
5
+ teamId: string;
6
+ data: Record<string, unknown>;
7
+ }
8
+ export declare function parseSlackEventCallback(value: unknown): SlackEventCallback | null;
@@ -0,0 +1,48 @@
1
+ import { isRecord } from "../protocol.js";
2
+ function bytesToHex(bytes) {
3
+ return [...bytes]
4
+ .map((byte) => byte.toString(16).padStart(2, "0"))
5
+ .join("");
6
+ }
7
+ function constantTimeEqual(left, right) {
8
+ const size = Math.max(left.length, right.length);
9
+ let difference = left.length ^ right.length;
10
+ for (let index = 0; index < size; index += 1) {
11
+ difference |=
12
+ (left.charCodeAt(index) || 0) ^ (right.charCodeAt(index) || 0);
13
+ }
14
+ return difference === 0;
15
+ }
16
+ export async function verifySlackSignature(payload, headers, secret, now = Date.now()) {
17
+ const timestamp = headers.get("x-slack-request-timestamp");
18
+ const signature = headers.get("x-slack-signature");
19
+ if (!timestamp || !signature || !signature.startsWith("v0="))
20
+ return false;
21
+ const seconds = Number(timestamp);
22
+ if (!Number.isFinite(seconds) || Math.abs(now - seconds * 1_000) > 300_000) {
23
+ return false;
24
+ }
25
+ const key = await crypto.subtle.importKey("raw", new TextEncoder().encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
26
+ const digest = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(`v0:${timestamp}:${payload}`));
27
+ return constantTimeEqual(signature.slice(3), bytesToHex(new Uint8Array(digest)));
28
+ }
29
+ export function parseSlackEventCallback(value) {
30
+ if (!isRecord(value) || value.type !== "event_callback")
31
+ return null;
32
+ if (typeof value.event_id !== "string" ||
33
+ typeof value.api_app_id !== "string" ||
34
+ typeof value.team_id !== "string" ||
35
+ !isRecord(value.event) ||
36
+ (value.event.type !== "message" &&
37
+ value.event.type !== "app_mention") ||
38
+ typeof value.event.channel !== "string") {
39
+ return null;
40
+ }
41
+ return {
42
+ id: value.event_id,
43
+ appId: value.api_app_id,
44
+ teamId: value.team_id,
45
+ data: value.event,
46
+ };
47
+ }
48
+ //# sourceMappingURL=slack-events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slack-events.js","sourceRoot":"","sources":["../../src/channels/slack-events.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ export interface LocalSlackConnectionState {
2
+ version: 1;
3
+ appId: string;
4
+ teamId: string;
5
+ teamName?: string;
6
+ }
7
+ export interface RemoteSlackConnectionState {
8
+ version: 1;
9
+ connectionId: string;
10
+ agentId: string;
11
+ webhookUrl: string;
12
+ apiUrl: string;
13
+ }
14
+ export declare function slackManifest(name: string, mode: "local" | "remote", webhookUrl?: string): Record<string, unknown>;
15
+ export declare function renderSlackProjectManifest(directory?: string, mode?: "local" | "remote"): Promise<Record<string, unknown>>;
16
+ export declare function localSlackStatePath(root: string): string;
17
+ export declare function remoteSlackStatePath(root: string): string;
18
+ export declare function readLocalSlackState(root: string): Promise<LocalSlackConnectionState | undefined>;
19
+ export declare function readRemoteSlackState(root: string): Promise<RemoteSlackConnectionState | undefined>;
20
+ export declare function writeRemoteSlackState(root: string, state: RemoteSlackConnectionState): Promise<void>;
21
+ export declare function captureLocalSlackState(root: string): Promise<LocalSlackConnectionState>;
@@ -0,0 +1,133 @@
1
+ import { access, mkdir, readFile, writeFile, } from "node:fs/promises";
2
+ import { basename, dirname, resolve } from "node:path";
3
+ import { findAgentRoot } from "../core/agent.js";
4
+ async function exists(path) {
5
+ try {
6
+ await access(path);
7
+ return true;
8
+ }
9
+ catch {
10
+ return false;
11
+ }
12
+ }
13
+ export function slackManifest(name, mode, webhookUrl) {
14
+ const eventSubscriptions = {
15
+ bot_events: [
16
+ "app_mention",
17
+ "app_context_changed",
18
+ "app_home_opened",
19
+ "message.im",
20
+ ],
21
+ };
22
+ if (mode === "remote") {
23
+ if (!webhookUrl) {
24
+ throw new Error("Run `blue channels connect slack --remote` before rendering the production manifest");
25
+ }
26
+ eventSubscriptions.request_url = webhookUrl;
27
+ }
28
+ return {
29
+ _metadata: { major_version: 1, minor_version: 1 },
30
+ display_information: {
31
+ name,
32
+ description: `Run the ${name} Blue agent from Slack.`,
33
+ background_color: "#0B1220",
34
+ },
35
+ features: {
36
+ agent_view: {
37
+ agent_description: `Work with the ${name} agent.`,
38
+ suggested_prompts: [
39
+ {
40
+ title: "Get started",
41
+ message: "Inspect the workspace and tell me what you can help with.",
42
+ },
43
+ ],
44
+ },
45
+ bot_user: {
46
+ display_name: name,
47
+ always_online: true,
48
+ },
49
+ },
50
+ oauth_config: {
51
+ scopes: {
52
+ bot: [
53
+ "app_mentions:read",
54
+ "assistant:write",
55
+ "chat:write",
56
+ "im:history",
57
+ ],
58
+ },
59
+ },
60
+ settings: {
61
+ event_subscriptions: eventSubscriptions,
62
+ org_deploy_enabled: false,
63
+ socket_mode_enabled: mode === "local",
64
+ token_rotation_enabled: false,
65
+ },
66
+ };
67
+ }
68
+ export async function renderSlackProjectManifest(directory = process.cwd(), mode = process.env.BLUE_SLACK_MODE === "remote" ? "remote" : "local") {
69
+ const root = await findAgentRoot(directory);
70
+ if (!root)
71
+ throw new Error("No Blue agent repository was found");
72
+ const remote = mode === "remote" ? await readRemoteSlackState(root) : undefined;
73
+ return slackManifest(basename(root), mode, remote?.webhookUrl);
74
+ }
75
+ export function localSlackStatePath(root) {
76
+ return resolve(root, ".blue", "channels", "slack.local.json");
77
+ }
78
+ export function remoteSlackStatePath(root) {
79
+ return resolve(root, ".blue", "channels", "slack.remote.json");
80
+ }
81
+ export async function readLocalSlackState(root) {
82
+ try {
83
+ return JSON.parse(await readFile(localSlackStatePath(root), "utf8"));
84
+ }
85
+ catch {
86
+ return undefined;
87
+ }
88
+ }
89
+ export async function readRemoteSlackState(root) {
90
+ try {
91
+ return JSON.parse(await readFile(remoteSlackStatePath(root), "utf8"));
92
+ }
93
+ catch {
94
+ return undefined;
95
+ }
96
+ }
97
+ export async function writeRemoteSlackState(root, state) {
98
+ const path = remoteSlackStatePath(root);
99
+ await mkdir(dirname(path), { recursive: true });
100
+ await writeFile(path, `${JSON.stringify(state, null, 2)}\n`, {
101
+ mode: 0o600,
102
+ });
103
+ }
104
+ export async function captureLocalSlackState(root) {
105
+ const candidates = [
106
+ resolve(root, ".slack", "apps.dev.json"),
107
+ resolve(root, ".slack", "apps.json"),
108
+ ];
109
+ for (const path of candidates) {
110
+ if (!(await exists(path)))
111
+ continue;
112
+ const parsed = JSON.parse(await readFile(path, "utf8"));
113
+ const entries = Object.values(parsed.apps ?? {});
114
+ const selected = entries.find((entry) => entry.team_domain === parsed.default) ??
115
+ entries[0];
116
+ if (selected?.app_id && selected.team_id) {
117
+ const state = {
118
+ version: 1,
119
+ appId: selected.app_id,
120
+ teamId: selected.team_id,
121
+ teamName: selected.team_domain,
122
+ };
123
+ const output = localSlackStatePath(root);
124
+ await mkdir(dirname(output), { recursive: true });
125
+ await writeFile(output, `${JSON.stringify(state, null, 2)}\n`, {
126
+ mode: 0o600,
127
+ });
128
+ return state;
129
+ }
130
+ }
131
+ throw new Error("Slack CLI did not persist the local app installation");
132
+ }
133
+ //# sourceMappingURL=slack-project.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slack-project.js","sourceRoot":"","sources":["../../src/channels/slack-project.ts"],"names":[],"mappings":"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"}