@headless-lms/plugin-slack 0.1.0 → 0.1.2
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 +3 -3
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -5,10 +5,10 @@ entitlements) to a Slack channel, formatted per event type.
|
|
|
5
5
|
|
|
6
6
|
## Actions
|
|
7
7
|
|
|
8
|
-
| id
|
|
9
|
-
|
|
|
8
|
+
| id | What it does |
|
|
9
|
+
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
10
10
|
| `postToChannel` | Posts a domain event to a channel. Input: `{ channel?, body }` where `body` is the event (`{ type, ...metadata }`). `entitlement.*` events get rich Block Kit formatting; unknown types post generically. `channel` falls back to the connection's `defaultChannel`. |
|
|
11
|
-
| `listChannels`
|
|
11
|
+
| `listChannels` | Lists public channels (for channel pickers). Paginated via `cursor`/`limit`. |
|
|
12
12
|
|
|
13
13
|
Formatted entitlement events: `entitlement.created`, `entitlement.updated`,
|
|
14
14
|
`entitlement.deleted`, `entitlement.expired` — each carrying an `entitlement`
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["listChannels","fetchChannels"],"sources":["../src/schemas.ts","../src/client.ts","../src/notifications/schema.ts","../src/notifications/formatters.ts","../src/actions/post-to-channel.ts","../src/actions/list-channels.ts","../src/index.ts"],"sourcesContent":["// slack integration — connection secrets/config schemas. Descriptions flow\n// into the JSON Schema the admin UI renders, so they double as setup docs.\nimport { z } from \"zod\";\n\nexport const SlackSecrets = z.object({\n botToken: z\n .string()\n .min(1)\n .describe(\n \"Slack bot token (xoxb-…). Create a Slack app at api.slack.com/apps → \" +\n \"OAuth & Permissions → add the bot scopes chat:write (post messages) and \" +\n \"channels:read (list channels) → Install to Workspace → copy the Bot User OAuth Token.\",\n ),\n});\n\nexport const SlackConfig = z.object({\n defaultChannel: z\n .string()\n .min(1)\n .describe(\n 'Channel to post to when an action doesn\\'t name one (e.g. \"#general\"). ' +\n \"The bot must be a member of the channel — invite it with /invite.\",\n ),\n});\n","// slack integration — thin Slack Web API wrapper shared by all actions.\n// Owns transport concerns only (auth header, envelope parsing, error surfacing);\n// what to send lives with the actions/formatters.\n\nexport interface SlackClientCtx {\n botToken: string;\n}\n\ninterface SlackEnvelope {\n ok: boolean;\n error?: string;\n}\n\nasync function slackApi<T extends SlackEnvelope>(\n ctx: SlackClientCtx,\n method: string,\n init: RequestInit,\n query?: URLSearchParams,\n): Promise<T> {\n const url = `https://slack.com/api/${method}${query ? `?${query.toString()}` : \"\"}`;\n const res = await fetch(url, {\n ...init,\n headers: {\n authorization: `Bearer ${ctx.botToken}`,\n ...init.headers,\n },\n });\n const body = (await res.json()) as T;\n if (!res.ok || !body.ok) {\n throw new Error(`slack ${method} failed: ${body.error ?? res.status}`);\n }\n return body;\n}\n\nexport async function postMessage(\n ctx: SlackClientCtx,\n args: { channel: string; text: string; blocks?: unknown[] },\n): Promise<{ channel: string; ts: string }> {\n const body = await slackApi<SlackEnvelope & { ts?: string }>(ctx, \"chat.postMessage\", {\n method: \"POST\",\n headers: { \"content-type\": \"application/json; charset=utf-8\" },\n body: JSON.stringify({ channel: args.channel, text: args.text, blocks: args.blocks }),\n });\n return { channel: args.channel, ts: body.ts ?? \"\" };\n}\n\nexport interface SlackChannel {\n id: string;\n name: string;\n}\n\nexport async function listChannels(\n ctx: SlackClientCtx,\n args: { cursor?: string; limit?: number },\n): Promise<{ channels: SlackChannel[]; nextCursor?: string }> {\n // conversations.list does not accept a JSON body — arguments go in the query.\n const query = new URLSearchParams({\n types: \"public_channel\",\n exclude_archived: \"true\",\n limit: String(args.limit ?? 100),\n });\n if (args.cursor) {\n query.set(\"cursor\", args.cursor);\n }\n const body = await slackApi<\n SlackEnvelope & {\n channels?: Array<{ id: string; name: string }>;\n response_metadata?: { next_cursor?: string };\n }\n >(ctx, \"conversations.list\", { method: \"GET\" }, query);\n const nextCursor = body.response_metadata?.next_cursor;\n return {\n channels: (body.channels ?? []).map(({ id, name }) => ({ id, name })),\n ...(nextCursor ? { nextCursor } : {}),\n };\n}\n","// slack integration — input contract for posting domain events. The body is\n// any domain event: a required `type` plus whatever metadata the event\n// carries (unknown keys pass through). Known event types get rich formatting\n// (see formatters.ts); anything else falls back to a generic post, so new\n// domain events work immediately and earn a bespoke formatter later.\nimport { z } from \"zod\";\n\nexport const EventBody = z\n .looseObject({\n type: z.string().min(1).describe('Domain event type, e.g. \"entitlement.created\".'),\n })\n .describe(\"A domain event: its type plus the metadata the event carries.\");\nexport type EventBody = z.infer<typeof EventBody>;\n\n/** The content reference an entitlement carries: identity + display name. */\nexport const ContentRefPayload = z.object({\n id: z.string(),\n type: z.string().describe('Content type, e.g. \"course\".'),\n title: z.string(),\n});\nexport type ContentRefPayload = z.infer<typeof ContentRefPayload>;\n\n/** The metadata entitlement.* events carry (mirrors the domain event snapshot). */\nexport const EntitlementEventPayload = z.object({\n id: z.string().optional(),\n firstName: z.string(),\n lastName: z.string(),\n studentEmail: z.string(),\n content: ContentRefPayload,\n grantedAt: z.string().describe(\"ISO-8601 timestamp\"),\n expiresAt: z.string().nullable().optional().describe(\"ISO-8601 timestamp, if access expires\"),\n});\nexport type EntitlementEventPayload = z.infer<typeof EntitlementEventPayload>;\n\n/** Validates an entitlement.* body's metadata before rich formatting. */\nexport const EntitlementEventBody = z.looseObject({ entitlement: EntitlementEventPayload });\n","// slack integration — formats a domain event into a Slack message: switch on\n// the event type, format accordingly. `text` is the notification-fallback\n// string; `blocks` is the Block Kit layout. Unknown event types get a generic\n// post of the event's metadata rather than an error.\nimport { EntitlementEventBody, type EntitlementEventPayload, type EventBody } from \"./schema.js\";\n\nexport interface SlackMessage {\n text: string;\n blocks: unknown[];\n}\n\nexport function formatMessage(body: EventBody): SlackMessage {\n switch (body.type) {\n case \"entitlement.created\":\n return formatEntitlementCreated(entitlement(body));\n case \"entitlement.updated\":\n return formatEntitlementUpdated(entitlement(body));\n case \"entitlement.deleted\":\n return formatEntitlementDeleted(entitlement(body));\n case \"entitlement.expired\":\n return formatEntitlementExpired(entitlement(body));\n default:\n return formatGeneric(body);\n }\n}\n\n// Throws (zod) when an entitlement.* body is missing/malformed metadata.\nfunction entitlement(body: EventBody): EntitlementEventPayload {\n return EntitlementEventBody.parse(body).entitlement;\n}\n\n// Course grants keep the familiar \"enrolled\" wording; other content types get\n// neutral \"granted access\" copy.\nfunction grantVerb(e: EntitlementEventPayload): string {\n return e.content.type === \"course\" ? \"enrolled in\" : \"granted access to\";\n}\n\nfunction formatEntitlementCreated(e: EntitlementEventPayload): SlackMessage {\n return {\n text: `✅ ${studentName(e)} ${grantVerb(e)} ${e.content.title}`,\n blocks: [header(\"✅ New entitlement\"), entitlementFields(e), context(`Granted ${e.grantedAt}`)],\n };\n}\n\nfunction formatEntitlementUpdated(e: EntitlementEventPayload): SlackMessage {\n return {\n text: `🔄 ${studentName(e)}'s access to ${e.content.title} was updated`,\n blocks: [\n header(\"🔄 Entitlement updated\"),\n entitlementFields(e),\n context(`Originally granted ${e.grantedAt}`),\n ],\n };\n}\n\nfunction formatEntitlementDeleted(e: EntitlementEventPayload): SlackMessage {\n return {\n text: `🚫 ${studentName(e)}'s access to ${e.content.title} was revoked`,\n blocks: [\n header(\"🚫 Entitlement removed\"),\n entitlementFields(e),\n context(`Originally granted ${e.grantedAt}`),\n ],\n };\n}\n\nfunction formatEntitlementExpired(e: EntitlementEventPayload): SlackMessage {\n return {\n text: `⏳ ${studentName(e)}'s access to ${e.content.title} has expired`,\n blocks: [\n header(\"⏳ Entitlement expired\"),\n entitlementFields(e),\n context(`Originally granted ${e.grantedAt}`),\n ],\n };\n}\n\n// Fallback for event types without a bespoke formatter: post the type and the\n// event's metadata as a code block (truncated to stay inside Slack's 3000-char\n// section limit).\nfunction formatGeneric(body: EventBody): SlackMessage {\n const { type, ...data } = body;\n const json = JSON.stringify(data, null, 2) ?? \"{}\";\n const truncated = json.length > 2800 ? `${json.slice(0, 2800)}\\n…` : json;\n return {\n text: `📣 ${type}`,\n blocks: [\n header(`📣 ${type}`),\n { type: \"section\", text: { type: \"mrkdwn\", text: `\\`\\`\\`${truncated}\\`\\`\\`` } },\n ],\n };\n}\n\nfunction studentName(e: EntitlementEventPayload): string {\n return `${e.firstName} ${e.lastName}`;\n}\n\nfunction header(title: string): unknown {\n return { type: \"header\", text: { type: \"plain_text\", text: title, emoji: true } };\n}\n\nfunction field(label: string, value: string): { type: \"mrkdwn\"; text: string } {\n return { type: \"mrkdwn\", text: `*${label}*\\n${value}` };\n}\n\nfunction entitlementFields(e: EntitlementEventPayload): unknown {\n const fields = [\n field(\"Student\", studentName(e)),\n field(\"Email\", e.studentEmail),\n field(contentLabel(e), e.content.title),\n field(\"Granted at\", e.grantedAt),\n ];\n if (e.expiresAt) {\n fields.push(field(\"Expires\", e.expiresAt));\n }\n return { type: \"section\", fields };\n}\n\n// \"Course\" for course grants; capitalized content type otherwise.\nfunction contentLabel(e: EntitlementEventPayload): string {\n const t = e.content.type;\n return t.charAt(0).toUpperCase() + t.slice(1);\n}\n\nfunction context(text: string): unknown {\n return { type: \"context\", elements: [{ type: \"mrkdwn\", text }] };\n}\n","// slack integration — the posting action: takes a channel and a domain-event\n// body, formats the message by event type, posts it.\nimport { z } from \"zod\";\nimport { zodAction } from \"@headless-lms/core/integrations\";\nimport { postMessage } from \"../client.js\";\nimport { formatMessage } from \"../notifications/formatters.js\";\nimport { EventBody } from \"../notifications/schema.js\";\n\nexport const postToChannel = zodAction({\n id: \"postToChannel\",\n description:\n \"Post a message to a Slack channel.\",\n input: z.object({\n channel: z\n .string()\n .min(1)\n .optional()\n .meta({\n description: \"The channel to post to; the connection's default channel when omitted.\",\n \"x-options\": { action: \"listChannels\", items: \"channels\", value: \"id\", label: \"name\" },\n }),\n body: EventBody,\n }),\n output: z.object({\n channel: z.string(),\n ts: z.string().describe(\"Slack's message timestamp.\"),\n }),\n run(ctx, input) {\n const channel = input.channel ?? (ctx.config.defaultChannel as string);\n const message = formatMessage(input.body);\n return postMessage(\n { botToken: ctx.secrets.botToken as string },\n { channel, text: message.text, blocks: message.blocks },\n );\n },\n});\n","// slack integration — channel listing for pickers (select a channel to post to).\nimport { z } from \"zod\";\nimport { zodAction } from \"@headless-lms/core/integrations\";\nimport { listChannels as fetchChannels } from \"../client.js\";\n\nexport const listChannels = zodAction({\n id: \"listChannels\",\n description:\n \"List public Slack channels the connection can see (for channel pickers). Requires the channels:read scope.\",\n input: z.object({\n cursor: z.string().optional().describe(\"Pagination cursor from a previous call.\"),\n limit: z.number().int().min(1).max(200).default(100),\n }),\n output: z.object({\n channels: z.array(z.object({ id: z.string(), name: z.string() })),\n nextCursor: z.string().optional(),\n }),\n run(ctx, input) {\n return fetchChannels({ botToken: ctx.secrets.botToken as string }, input);\n },\n});\n","// slack integration — satisfies the Integration contract; declares the config\n// shape a Slack connection carries and the actions callers can invoke. The\n// credential (bot token) is opaque to the platform; actions receive it\n// revealed, at point of use, via the ActionContext.\n//\n// Assembly only — schemas live in schemas.ts, transport in client.ts, actions\n// under actions/, and event formatting under notifications/.\nimport type { Integration } from \"@headless-lms/core/types\";\nimport { zodConfig, zodSecrets } from \"@headless-lms/core/integrations\";\nimport { SlackConfig, SlackSecrets } from \"./schemas.js\";\nimport { postToChannel } from \"./actions/post-to-channel.js\";\nimport { listChannels } from \"./actions/list-channels.js\";\n\nconst slack: Integration = {\n id: \"slack\",\n ...zodConfig(SlackConfig),\n ...zodSecrets(SlackSecrets),\n actions: [postToChannel, listChannels],\n};\n\nexport default slack;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["listChannels","fetchChannels"],"sources":["../src/schemas.ts","../src/client.ts","../src/notifications/schema.ts","../src/notifications/formatters.ts","../src/actions/post-to-channel.ts","../src/actions/list-channels.ts","../src/index.ts"],"sourcesContent":["// slack integration — connection secrets/config schemas. Descriptions flow\n// into the JSON Schema the admin UI renders, so they double as setup docs.\nimport { z } from \"zod\";\n\nexport const SlackSecrets = z.object({\n botToken: z\n .string()\n .min(1)\n .describe(\n \"Slack bot token (xoxb-…). Create a Slack app at api.slack.com/apps → \" +\n \"OAuth & Permissions → add the bot scopes chat:write (post messages) and \" +\n \"channels:read (list channels) → Install to Workspace → copy the Bot User OAuth Token.\",\n ),\n});\n\nexport const SlackConfig = z.object({\n defaultChannel: z\n .string()\n .min(1)\n .describe(\n 'Channel to post to when an action doesn\\'t name one (e.g. \"#general\"). ' +\n \"The bot must be a member of the channel — invite it with /invite.\",\n ),\n});\n","// slack integration — thin Slack Web API wrapper shared by all actions.\n// Owns transport concerns only (auth header, envelope parsing, error surfacing);\n// what to send lives with the actions/formatters.\n\nexport interface SlackClientCtx {\n botToken: string;\n}\n\ninterface SlackEnvelope {\n ok: boolean;\n error?: string;\n}\n\nasync function slackApi<T extends SlackEnvelope>(\n ctx: SlackClientCtx,\n method: string,\n init: RequestInit,\n query?: URLSearchParams,\n): Promise<T> {\n const url = `https://slack.com/api/${method}${query ? `?${query.toString()}` : \"\"}`;\n const res = await fetch(url, {\n ...init,\n headers: {\n authorization: `Bearer ${ctx.botToken}`,\n ...init.headers,\n },\n });\n const body = (await res.json()) as T;\n if (!res.ok || !body.ok) {\n throw new Error(`slack ${method} failed: ${body.error ?? res.status}`);\n }\n return body;\n}\n\nexport async function postMessage(\n ctx: SlackClientCtx,\n args: { channel: string; text: string; blocks?: unknown[] },\n): Promise<{ channel: string; ts: string }> {\n const body = await slackApi<SlackEnvelope & { ts?: string }>(ctx, \"chat.postMessage\", {\n method: \"POST\",\n headers: { \"content-type\": \"application/json; charset=utf-8\" },\n body: JSON.stringify({ channel: args.channel, text: args.text, blocks: args.blocks }),\n });\n return { channel: args.channel, ts: body.ts ?? \"\" };\n}\n\nexport interface SlackChannel {\n id: string;\n name: string;\n}\n\nexport async function listChannels(\n ctx: SlackClientCtx,\n args: { cursor?: string; limit?: number },\n): Promise<{ channels: SlackChannel[]; nextCursor?: string }> {\n // conversations.list does not accept a JSON body — arguments go in the query.\n const query = new URLSearchParams({\n types: \"public_channel\",\n exclude_archived: \"true\",\n limit: String(args.limit ?? 100),\n });\n if (args.cursor) {\n query.set(\"cursor\", args.cursor);\n }\n const body = await slackApi<\n SlackEnvelope & {\n channels?: Array<{ id: string; name: string }>;\n response_metadata?: { next_cursor?: string };\n }\n >(ctx, \"conversations.list\", { method: \"GET\" }, query);\n const nextCursor = body.response_metadata?.next_cursor;\n return {\n channels: (body.channels ?? []).map(({ id, name }) => ({ id, name })),\n ...(nextCursor ? { nextCursor } : {}),\n };\n}\n","// slack integration — input contract for posting domain events. The body is\n// any domain event: a required `type` plus whatever metadata the event\n// carries (unknown keys pass through). Known event types get rich formatting\n// (see formatters.ts); anything else falls back to a generic post, so new\n// domain events work immediately and earn a bespoke formatter later.\nimport { z } from \"zod\";\n\nexport const EventBody = z\n .looseObject({\n type: z.string().min(1).describe('Domain event type, e.g. \"entitlement.created\".'),\n })\n .describe(\"A domain event: its type plus the metadata the event carries.\");\nexport type EventBody = z.infer<typeof EventBody>;\n\n/** The content reference an entitlement carries: identity + display name. */\nexport const ContentRefPayload = z.object({\n id: z.string(),\n type: z.string().describe('Content type, e.g. \"course\".'),\n title: z.string(),\n});\nexport type ContentRefPayload = z.infer<typeof ContentRefPayload>;\n\n/** The metadata entitlement.* events carry (mirrors the domain event snapshot). */\nexport const EntitlementEventPayload = z.object({\n id: z.string().optional(),\n firstName: z.string(),\n lastName: z.string(),\n studentEmail: z.string(),\n content: ContentRefPayload,\n grantedAt: z.string().describe(\"ISO-8601 timestamp\"),\n expiresAt: z.string().nullable().optional().describe(\"ISO-8601 timestamp, if access expires\"),\n});\nexport type EntitlementEventPayload = z.infer<typeof EntitlementEventPayload>;\n\n/** Validates an entitlement.* body's metadata before rich formatting. */\nexport const EntitlementEventBody = z.looseObject({ entitlement: EntitlementEventPayload });\n","// slack integration — formats a domain event into a Slack message: switch on\n// the event type, format accordingly. `text` is the notification-fallback\n// string; `blocks` is the Block Kit layout. Unknown event types get a generic\n// post of the event's metadata rather than an error.\nimport { EntitlementEventBody, type EntitlementEventPayload, type EventBody } from \"./schema.js\";\n\nexport interface SlackMessage {\n text: string;\n blocks: unknown[];\n}\n\nexport function formatMessage(body: EventBody): SlackMessage {\n switch (body.type) {\n case \"entitlement.created\":\n return formatEntitlementCreated(entitlement(body));\n case \"entitlement.updated\":\n return formatEntitlementUpdated(entitlement(body));\n case \"entitlement.deleted\":\n return formatEntitlementDeleted(entitlement(body));\n case \"entitlement.expired\":\n return formatEntitlementExpired(entitlement(body));\n default:\n return formatGeneric(body);\n }\n}\n\n// Throws (zod) when an entitlement.* body is missing/malformed metadata.\nfunction entitlement(body: EventBody): EntitlementEventPayload {\n return EntitlementEventBody.parse(body).entitlement;\n}\n\n// Course grants keep the familiar \"enrolled\" wording; other content types get\n// neutral \"granted access\" copy.\nfunction grantVerb(e: EntitlementEventPayload): string {\n return e.content.type === \"course\" ? \"enrolled in\" : \"granted access to\";\n}\n\nfunction formatEntitlementCreated(e: EntitlementEventPayload): SlackMessage {\n return {\n text: `✅ ${studentName(e)} ${grantVerb(e)} ${e.content.title}`,\n blocks: [header(\"✅ New entitlement\"), entitlementFields(e), context(`Granted ${e.grantedAt}`)],\n };\n}\n\nfunction formatEntitlementUpdated(e: EntitlementEventPayload): SlackMessage {\n return {\n text: `🔄 ${studentName(e)}'s access to ${e.content.title} was updated`,\n blocks: [\n header(\"🔄 Entitlement updated\"),\n entitlementFields(e),\n context(`Originally granted ${e.grantedAt}`),\n ],\n };\n}\n\nfunction formatEntitlementDeleted(e: EntitlementEventPayload): SlackMessage {\n return {\n text: `🚫 ${studentName(e)}'s access to ${e.content.title} was revoked`,\n blocks: [\n header(\"🚫 Entitlement removed\"),\n entitlementFields(e),\n context(`Originally granted ${e.grantedAt}`),\n ],\n };\n}\n\nfunction formatEntitlementExpired(e: EntitlementEventPayload): SlackMessage {\n return {\n text: `⏳ ${studentName(e)}'s access to ${e.content.title} has expired`,\n blocks: [\n header(\"⏳ Entitlement expired\"),\n entitlementFields(e),\n context(`Originally granted ${e.grantedAt}`),\n ],\n };\n}\n\n// Fallback for event types without a bespoke formatter: post the type and the\n// event's metadata as a code block (truncated to stay inside Slack's 3000-char\n// section limit).\nfunction formatGeneric(body: EventBody): SlackMessage {\n const { type, ...data } = body;\n const json = JSON.stringify(data, null, 2) ?? \"{}\";\n const truncated = json.length > 2800 ? `${json.slice(0, 2800)}\\n…` : json;\n return {\n text: `📣 ${type}`,\n blocks: [\n header(`📣 ${type}`),\n { type: \"section\", text: { type: \"mrkdwn\", text: `\\`\\`\\`${truncated}\\`\\`\\`` } },\n ],\n };\n}\n\nfunction studentName(e: EntitlementEventPayload): string {\n return `${e.firstName} ${e.lastName}`;\n}\n\nfunction header(title: string): unknown {\n return { type: \"header\", text: { type: \"plain_text\", text: title, emoji: true } };\n}\n\nfunction field(label: string, value: string): { type: \"mrkdwn\"; text: string } {\n return { type: \"mrkdwn\", text: `*${label}*\\n${value}` };\n}\n\nfunction entitlementFields(e: EntitlementEventPayload): unknown {\n const fields = [\n field(\"Student\", studentName(e)),\n field(\"Email\", e.studentEmail),\n field(contentLabel(e), e.content.title),\n field(\"Granted at\", e.grantedAt),\n ];\n if (e.expiresAt) {\n fields.push(field(\"Expires\", e.expiresAt));\n }\n return { type: \"section\", fields };\n}\n\n// \"Course\" for course grants; capitalized content type otherwise.\nfunction contentLabel(e: EntitlementEventPayload): string {\n const t = e.content.type;\n return t.charAt(0).toUpperCase() + t.slice(1);\n}\n\nfunction context(text: string): unknown {\n return { type: \"context\", elements: [{ type: \"mrkdwn\", text }] };\n}\n","// slack integration — the posting action: takes a channel and a domain-event\n// body, formats the message by event type, posts it.\nimport { z } from \"zod\";\nimport { zodAction } from \"@headless-lms/core/integrations\";\nimport { postMessage } from \"../client.js\";\nimport { formatMessage } from \"../notifications/formatters.js\";\nimport { EventBody } from \"../notifications/schema.js\";\n\nexport const postToChannel = zodAction({\n id: \"postToChannel\",\n description: \"Post a message to a Slack channel.\",\n input: z.object({\n channel: z\n .string()\n .min(1)\n .optional()\n .meta({\n description: \"The channel to post to; the connection's default channel when omitted.\",\n \"x-options\": { action: \"listChannels\", items: \"channels\", value: \"id\", label: \"name\" },\n }),\n body: EventBody,\n }),\n output: z.object({\n channel: z.string(),\n ts: z.string().describe(\"Slack's message timestamp.\"),\n }),\n run(ctx, input) {\n const channel = input.channel ?? (ctx.config.defaultChannel as string);\n const message = formatMessage(input.body);\n return postMessage(\n { botToken: ctx.secrets.botToken as string },\n { channel, text: message.text, blocks: message.blocks },\n );\n },\n});\n","// slack integration — channel listing for pickers (select a channel to post to).\nimport { z } from \"zod\";\nimport { zodAction } from \"@headless-lms/core/integrations\";\nimport { listChannels as fetchChannels } from \"../client.js\";\n\nexport const listChannels = zodAction({\n id: \"listChannels\",\n description:\n \"List public Slack channels the connection can see (for channel pickers). Requires the channels:read scope.\",\n input: z.object({\n cursor: z.string().optional().describe(\"Pagination cursor from a previous call.\"),\n limit: z.number().int().min(1).max(200).default(100),\n }),\n output: z.object({\n channels: z.array(z.object({ id: z.string(), name: z.string() })),\n nextCursor: z.string().optional(),\n }),\n run(ctx, input) {\n return fetchChannels({ botToken: ctx.secrets.botToken as string }, input);\n },\n});\n","// slack integration — satisfies the Integration contract; declares the config\n// shape a Slack connection carries and the actions callers can invoke. The\n// credential (bot token) is opaque to the platform; actions receive it\n// revealed, at point of use, via the ActionContext.\n//\n// Assembly only — schemas live in schemas.ts, transport in client.ts, actions\n// under actions/, and event formatting under notifications/.\nimport type { Integration } from \"@headless-lms/core/types\";\nimport { zodConfig, zodSecrets } from \"@headless-lms/core/integrations\";\nimport { SlackConfig, SlackSecrets } from \"./schemas.js\";\nimport { postToChannel } from \"./actions/post-to-channel.js\";\nimport { listChannels } from \"./actions/list-channels.js\";\n\nconst slack: Integration = {\n id: \"slack\",\n ...zodConfig(SlackConfig),\n ...zodSecrets(SlackSecrets),\n actions: [postToChannel, listChannels],\n};\n\nexport default slack;\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@headless-lms/plugin-slack",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
}
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"zod": "4.
|
|
15
|
-
"@headless-lms/core": "0.
|
|
14
|
+
"zod": "4.6.7",
|
|
15
|
+
"@headless-lms/core": "0.6.0"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
18
|
"@types/node": "^22.10.2",
|