@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 +32 -46
- package/dist/channels/slack-api.d.ts +24 -0
- package/dist/channels/slack-api.js +69 -0
- package/dist/channels/slack-api.js.map +1 -0
- package/dist/channels/slack-events.d.ts +8 -0
- package/dist/channels/slack-events.js +48 -0
- package/dist/channels/slack-events.js.map +1 -0
- package/dist/channels/slack-project.d.ts +21 -0
- package/dist/channels/slack-project.js +133 -0
- package/dist/channels/slack-project.js.map +1 -0
- package/dist/cli/index.js +160 -18
- package/dist/cli/index.js.map +1 -1
- package/dist/core/agent.js +63 -6
- package/dist/core/agent.js.map +1 -1
- package/dist/dev/manager.js +23 -44
- package/dist/dev/manager.js.map +1 -1
- package/dist/landing/agent.js +4 -4
- package/dist/landing/agent.js.map +1 -1
- package/dist/protocol.d.ts +9 -6
- package/dist/protocol.js.map +1 -1
- package/dist/sdk/client.d.ts +2 -1
- package/dist/sdk/client.js +7 -0
- package/dist/sdk/client.js.map +1 -1
- package/package.json +1 -1
- package/dist/channels/composio.d.ts +0 -32
- package/dist/channels/composio.js +0 -149
- package/dist/channels/composio.js.map +0 -1
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
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
|
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
|
-
- `
|
|
228
|
-
|
|
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
|
|
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
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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":"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"}
|
|
@@ -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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"}
|