@cybernetyx1/atlasflow-slack 0.1.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/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ PROPRIETARY SOFTWARE LICENSE
2
+
3
+ Copyright (c) 2026 Cybernetyx. All rights reserved.
4
+
5
+ This software and its source code are the proprietary and confidential property
6
+ of the copyright holder. The software is original work authored independently.
7
+
8
+ No part of this software may be copied, reproduced, modified, published,
9
+ distributed, sublicensed, or sold in any form or by any means without the prior
10
+ written permission of the copyright holder, except as expressly permitted by a
11
+ separate written agreement.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15
+ FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT
16
+ HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION
17
+ OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE
18
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,38 @@
1
+ # @cybernetyx1/atlasflow-slack
2
+
3
+ Slack Web API tools and persona slot bindings for AtlasFlow agents — post messages, read channel history, inspect threads, and look up users.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ pnpm add @cybernetyx1/atlasflow-slack
9
+ ```
10
+
11
+ Part of the AtlasFlow monorepo. Proprietary.
12
+
13
+ ## Usage
14
+
15
+ `slackTools(options)` returns the raw Slack tools for an agent. `slackBinding(options)` returns a persona binding that resolves a workspace bot token from the environment and fills the `slack` manifest slot.
16
+
17
+ ```ts
18
+ import { slackTools } from "@cybernetyx1/atlasflow-slack";
19
+
20
+ // Direct: pass a bot token and a default channel.
21
+ const tools = slackTools({
22
+ token: process.env.SLACK_BOT_TOKEN,
23
+ channel: "C0123456789",
24
+ });
25
+ ```
26
+
27
+ ```ts
28
+ import { slackBinding } from "@cybernetyx1/atlasflow-slack";
29
+
30
+ // Persona slot binding: resolves the token from env at runtime.
31
+ const binding = slackBinding({ tokenEnv: "SLACK_BOT_TOKEN", slot: "slack" });
32
+ ```
33
+
34
+ Also exported: `slackToolBinding`, `createSlackClient`, the `SlackApiError` class, and the `SlackToolsOptions` / `SlackEnvBindingOptions` types.
35
+
36
+ ## License
37
+
38
+ Proprietary. © 2026 Cybernetyx. See LICENSE.
@@ -0,0 +1,41 @@
1
+ import { PersonaBindings, PersonaToolsBinding, RawTool } from '@cybernetyx1/atlasflow-runtime';
2
+
3
+ interface SlackToolsOptions {
4
+ /** Slack bot/user token, usually xoxb-... or xoxp-.... */
5
+ token?: string;
6
+ /** Default channel used when a tool call omits channel. */
7
+ channel?: string;
8
+ /** Override for compatible Slack API hosts. Defaults to https://slack.com/api. */
9
+ baseUrl?: string;
10
+ userAgent?: string;
11
+ fetch?: typeof fetch;
12
+ headers?: Record<string, string>;
13
+ /** Default and maximum history/replies page size. Defaults to 15 for 2025+ Slack limits. */
14
+ maxHistoryLimit?: number;
15
+ }
16
+ interface SlackEnvBindingOptions extends Omit<SlackToolsOptions, "token" | "channel"> {
17
+ tokenEnv?: string;
18
+ channel?: string;
19
+ channelEnv?: string;
20
+ /** Manifest slot satisfied by these tools. Defaults to "slack". */
21
+ slot?: string;
22
+ }
23
+ declare class SlackApiError extends Error {
24
+ status: number;
25
+ code: string;
26
+ details?: unknown | undefined;
27
+ retryAfter?: number | undefined;
28
+ constructor(status: number, code: string, message: string, details?: unknown | undefined, retryAfter?: number | undefined);
29
+ }
30
+ interface SlackRequestOptions {
31
+ body?: Record<string, unknown>;
32
+ query?: Record<string, unknown>;
33
+ }
34
+ declare function createSlackClient(options?: SlackToolsOptions): {
35
+ request: <T extends Record<string, unknown>>(method: string, requestOptions?: SlackRequestOptions) => Promise<T>;
36
+ };
37
+ declare function slackTools(options?: SlackToolsOptions): RawTool[];
38
+ declare function slackToolBinding(options?: SlackEnvBindingOptions): PersonaToolsBinding;
39
+ declare function slackBinding(options?: SlackEnvBindingOptions): PersonaBindings;
40
+
41
+ export { SlackApiError, type SlackEnvBindingOptions, type SlackRequestOptions, type SlackToolsOptions, createSlackClient, slackBinding, slackToolBinding, slackTools };
package/dist/index.js ADDED
@@ -0,0 +1,299 @@
1
+ // src/index.ts
2
+ import { rawTool } from "@cybernetyx1/atlasflow-runtime";
3
+ var SlackApiError = class extends Error {
4
+ constructor(status, code, message, details, retryAfter) {
5
+ super(message);
6
+ this.status = status;
7
+ this.code = code;
8
+ this.details = details;
9
+ this.retryAfter = retryAfter;
10
+ this.name = "SlackApiError";
11
+ }
12
+ status;
13
+ code;
14
+ details;
15
+ retryAfter;
16
+ };
17
+ var DEFAULT_BASE_URL = "https://slack.com/api";
18
+ var DEFAULT_HISTORY_LIMIT = 15;
19
+ function stringParam(value, name) {
20
+ if (typeof value !== "string" || value.trim() === "") throw new Error(`${name} is required`);
21
+ return value;
22
+ }
23
+ function optionalString(value) {
24
+ return typeof value === "string" && value.trim() !== "" ? value : void 0;
25
+ }
26
+ function booleanParam(value) {
27
+ return typeof value === "boolean" ? value : void 0;
28
+ }
29
+ function integerParam(value, fallback, name, max) {
30
+ if (value === void 0 || value === null) return fallback;
31
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 1) throw new Error(`${name} must be a positive integer`);
32
+ return max ? Math.min(value, max) : value;
33
+ }
34
+ function resolveChannel(options, args) {
35
+ return stringParam(args.channel ?? options.channel, "channel");
36
+ }
37
+ function stripUndefined(value) {
38
+ return Object.fromEntries(Object.entries(value).filter(([, v]) => v !== void 0 && v !== null && v !== ""));
39
+ }
40
+ function json(data) {
41
+ return JSON.stringify(data);
42
+ }
43
+ function qs(query) {
44
+ const params = new URLSearchParams();
45
+ for (const [key, value] of Object.entries(stripUndefined(query ?? {}))) params.set(key, String(value));
46
+ const text = params.toString();
47
+ return text ? `?${text}` : "";
48
+ }
49
+ function createSlackClient(options = {}) {
50
+ const doFetch = options.fetch ?? fetch;
51
+ const base = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
52
+ async function request(method, requestOptions = {}) {
53
+ const isPost = requestOptions.body !== void 0;
54
+ const res = await doFetch(`${base}/${method}${qs(requestOptions.query)}`, {
55
+ method: isPost ? "POST" : "GET",
56
+ headers: {
57
+ accept: "application/json",
58
+ ...isPost ? { "content-type": "application/json" } : {},
59
+ "user-agent": options.userAgent ?? "atlasflow-slack",
60
+ ...options.token ? { authorization: `Bearer ${options.token}` } : {},
61
+ ...options.headers
62
+ },
63
+ body: isPost ? JSON.stringify(stripUndefined(requestOptions.body ?? {})) : void 0
64
+ });
65
+ const retryAfterRaw = res.headers.get("retry-after");
66
+ const retryAfter = retryAfterRaw ? Number(retryAfterRaw) : void 0;
67
+ const data = await res.json().catch(() => ({}));
68
+ if (!res.ok) {
69
+ throw new SlackApiError(res.status, res.status === 429 ? "slack_rate_limited" : "slack_http_error", res.statusText, data, retryAfter);
70
+ }
71
+ if (data.ok === false) {
72
+ const code = typeof data.error === "string" ? data.error : "slack_error";
73
+ throw new SlackApiError(res.status, code, code, data);
74
+ }
75
+ return data;
76
+ }
77
+ return { request };
78
+ }
79
+ function slackTools(options = {}) {
80
+ const client = createSlackClient(options);
81
+ const maxHistoryLimit = options.maxHistoryLimit ?? DEFAULT_HISTORY_LIMIT;
82
+ return [
83
+ rawTool({
84
+ name: "slack_post_message",
85
+ description: "Post a message to a Slack channel, DM, or thread.",
86
+ parameters: schema(
87
+ {
88
+ channel: stringSchema("Channel, DM, or MPIM id. Optional when bound by the workspace."),
89
+ text: stringSchema("Message text."),
90
+ thread_ts: stringSchema("Thread timestamp for replies."),
91
+ mrkdwn: booleanSchema("Whether Slack should parse mrkdwn. Defaults to true."),
92
+ blocks: { type: "array", description: "Optional Slack Block Kit blocks." }
93
+ },
94
+ ["text"]
95
+ ),
96
+ execute: async (args) => {
97
+ const res = await client.request("chat.postMessage", {
98
+ body: {
99
+ channel: resolveChannel(options, args),
100
+ text: stringParam(args.text, "text"),
101
+ thread_ts: optionalString(args.thread_ts),
102
+ mrkdwn: booleanParam(args.mrkdwn),
103
+ blocks: Array.isArray(args.blocks) ? args.blocks : void 0
104
+ }
105
+ });
106
+ return json({ channel: res.channel, ts: res.ts, message: res.message, warning: res.warning });
107
+ }
108
+ }),
109
+ rawTool({
110
+ name: "slack_reply_to_thread",
111
+ description: "Reply to an existing Slack message thread.",
112
+ parameters: schema(
113
+ {
114
+ channel: stringSchema("Channel id. Optional when bound by the workspace."),
115
+ thread_ts: stringSchema("Parent message timestamp."),
116
+ text: stringSchema("Reply text."),
117
+ mrkdwn: booleanSchema("Whether Slack should parse mrkdwn. Defaults to true.")
118
+ },
119
+ ["thread_ts", "text"]
120
+ ),
121
+ execute: async (args) => {
122
+ const res = await client.request("chat.postMessage", {
123
+ body: {
124
+ channel: resolveChannel(options, args),
125
+ thread_ts: stringParam(args.thread_ts, "thread_ts"),
126
+ text: stringParam(args.text, "text"),
127
+ mrkdwn: booleanParam(args.mrkdwn)
128
+ }
129
+ });
130
+ return json({ channel: res.channel, ts: res.ts, message: res.message, warning: res.warning });
131
+ }
132
+ }),
133
+ rawTool({
134
+ name: "slack_list_channel_history",
135
+ description: "Read recent messages from a Slack conversation.",
136
+ parameters: schema(
137
+ {
138
+ channel: stringSchema("Channel id. Optional when bound by the workspace."),
139
+ limit: integerSchema("Maximum messages to return."),
140
+ cursor: stringSchema("Pagination cursor."),
141
+ oldest: stringSchema("Only messages after this timestamp."),
142
+ latest: stringSchema("Only messages before this timestamp."),
143
+ inclusive: booleanSchema("Include messages matching oldest/latest.")
144
+ },
145
+ []
146
+ ),
147
+ execute: async (args) => {
148
+ const limit = integerParam(args.limit, maxHistoryLimit, "limit", maxHistoryLimit);
149
+ const res = await client.request("conversations.history", {
150
+ query: {
151
+ channel: resolveChannel(options, args),
152
+ limit,
153
+ cursor: optionalString(args.cursor),
154
+ oldest: optionalString(args.oldest),
155
+ latest: optionalString(args.latest),
156
+ inclusive: booleanParam(args.inclusive)
157
+ }
158
+ });
159
+ return json({ messages: res.messages, has_more: res.has_more, response_metadata: res.response_metadata, warning: res.warning });
160
+ }
161
+ }),
162
+ rawTool({
163
+ name: "slack_get_thread_replies",
164
+ description: "Read replies in a Slack thread.",
165
+ parameters: schema(
166
+ {
167
+ channel: stringSchema("Channel id. Optional when bound by the workspace."),
168
+ thread_ts: stringSchema("Parent message timestamp."),
169
+ limit: integerSchema("Maximum messages to return."),
170
+ cursor: stringSchema("Pagination cursor.")
171
+ },
172
+ ["thread_ts"]
173
+ ),
174
+ execute: async (args) => {
175
+ const limit = integerParam(args.limit, maxHistoryLimit, "limit", maxHistoryLimit);
176
+ const res = await client.request("conversations.replies", {
177
+ query: {
178
+ channel: resolveChannel(options, args),
179
+ ts: stringParam(args.thread_ts, "thread_ts"),
180
+ limit,
181
+ cursor: optionalString(args.cursor)
182
+ }
183
+ });
184
+ return json({ messages: res.messages, has_more: res.has_more, response_metadata: res.response_metadata, warning: res.warning });
185
+ }
186
+ }),
187
+ rawTool({
188
+ name: "slack_get_channel_info",
189
+ description: "Fetch metadata for a Slack conversation.",
190
+ parameters: schema(
191
+ {
192
+ channel: stringSchema("Channel, DM, or MPIM id. Optional when bound by the workspace."),
193
+ include_num_members: booleanSchema("Include member count when available.")
194
+ },
195
+ []
196
+ ),
197
+ execute: async (args) => {
198
+ const res = await client.request("conversations.info", {
199
+ query: {
200
+ channel: resolveChannel(options, args),
201
+ include_num_members: booleanParam(args.include_num_members)
202
+ }
203
+ });
204
+ return json({ channel: res.channel, warning: res.warning });
205
+ }
206
+ }),
207
+ rawTool({
208
+ name: "slack_get_user_info",
209
+ description: "Fetch Slack user profile/account metadata.",
210
+ parameters: schema({ user: stringSchema("Slack user id.") }, ["user"]),
211
+ execute: async (args) => {
212
+ const res = await client.request("users.info", { query: { user: stringParam(args.user, "user") } });
213
+ return json({ user: res.user, warning: res.warning });
214
+ }
215
+ }),
216
+ rawTool({
217
+ name: "slack_open_dm",
218
+ description: "Open a direct-message or multi-person DM conversation.",
219
+ parameters: schema(
220
+ {
221
+ user: stringSchema("Single Slack user id."),
222
+ users: { type: "array", description: "Slack user ids for MPIM.", items: { type: "string" } },
223
+ return_im: booleanSchema("Return full IM object.")
224
+ },
225
+ []
226
+ ),
227
+ execute: async (args) => {
228
+ const users = Array.isArray(args.users) ? args.users.map((u) => stringParam(u, "users[]")).join(",") : optionalString(args.user);
229
+ const res = await client.request("conversations.open", {
230
+ body: { users: stringParam(users, "user or users"), return_im: booleanParam(args.return_im) }
231
+ });
232
+ return json({ channel: res.channel, no_op: res.no_op, already_open: res.already_open, warning: res.warning });
233
+ }
234
+ }),
235
+ rawTool({
236
+ name: "slack_add_reaction",
237
+ description: "Add an emoji reaction to a Slack message.",
238
+ parameters: schema(
239
+ {
240
+ channel: stringSchema("Channel id. Optional when bound by the workspace."),
241
+ timestamp: stringSchema("Message timestamp."),
242
+ name: stringSchema("Emoji name without surrounding colons.")
243
+ },
244
+ ["timestamp", "name"]
245
+ ),
246
+ execute: async (args) => {
247
+ const res = await client.request("reactions.add", {
248
+ body: {
249
+ channel: resolveChannel(options, args),
250
+ timestamp: stringParam(args.timestamp, "timestamp"),
251
+ name: stringParam(args.name, "name").replace(/^:|:$/g, "")
252
+ }
253
+ });
254
+ return json({ ok: res.ok, warning: res.warning });
255
+ }
256
+ })
257
+ ];
258
+ }
259
+ function slackToolBinding(options = {}) {
260
+ return ({ env }) => slackTools({
261
+ ...options,
262
+ token: readEnv(env, options.tokenEnv ?? "SLACK_BOT_TOKEN", false),
263
+ channel: options.channel ?? readEnv(env, options.channelEnv ?? "SLACK_CHANNEL_ID", false)
264
+ });
265
+ }
266
+ function slackBinding(options = {}) {
267
+ const slot = options.slot ?? "slack";
268
+ return { toolSlots: { [slot]: slackToolBinding(options) } };
269
+ }
270
+ function readEnv(env, name, required) {
271
+ const value = env[name];
272
+ if (typeof value === "string" && value) return value;
273
+ if (required) throw new Error(`${name} is required for Slack tools`);
274
+ return void 0;
275
+ }
276
+ function stringSchema(description) {
277
+ return { type: "string", description };
278
+ }
279
+ function booleanSchema(description) {
280
+ return { type: "boolean", description };
281
+ }
282
+ function integerSchema(description) {
283
+ return { type: "integer", minimum: 1, description };
284
+ }
285
+ function schema(properties, required) {
286
+ return {
287
+ type: "object",
288
+ properties,
289
+ required,
290
+ additionalProperties: false
291
+ };
292
+ }
293
+ export {
294
+ SlackApiError,
295
+ createSlackClient,
296
+ slackBinding,
297
+ slackToolBinding,
298
+ slackTools
299
+ };
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@cybernetyx1/atlasflow-slack",
3
+ "version": "0.1.0",
4
+ "description": "Slack Web API tools and bindings for AtlasFlow agents.",
5
+ "type": "module",
6
+ "license": "SEE LICENSE IN LICENSE",
7
+ "author": "Cybernetyx",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/Cybernetyx/atlasflow.git",
11
+ "directory": "packages/slack"
12
+ },
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/index.js"
17
+ }
18
+ },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "dependencies": {
23
+ "@cybernetyx1/atlasflow-runtime": "0.1.0"
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^22.10.0",
27
+ "tsup": "^8.3.5",
28
+ "typescript": "^5.7.2"
29
+ },
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "scripts": {
34
+ "build": "tsup",
35
+ "typecheck": "tsc --noEmit",
36
+ "test": "node --import tsx --test test/*.test.ts"
37
+ }
38
+ }