{"_id":"@diabolicallabs/slack","_rev":"2-f65bdace825029e19df929aa3c2176b2","name":"@diabolicallabs/slack","dist-tags":{"latest":"1.0.1"},"versions":{"1.0.0":{"name":"@diabolicallabs/slack","version":"1.0.0","author":{"url":"https://deeismail.com","name":"Diana Ismail","email":"diana@deeismail.com"},"license":"MIT","_id":"@diabolicallabs/slack@1.0.0","maintainers":[{"name":"shackled78","email":"shackled78@gmail.com"}],"homepage":"https://github.com/mannism/dlabs-toolkit#readme","bugs":{"url":"https://github.com/mannism/dlabs-toolkit/issues"},"dist":{"shasum":"1ed236da6ed0be759d33f909e218a80cad5e3744","tarball":"https://registry.npmjs.org/@diabolicallabs/slack/-/slack-1.0.0.tgz","fileCount":6,"integrity":"sha512-PpFHmU8U1QmD7t/8P/rip7GKK7V/D4fW1VUXnLLMz8eaS9s+HBWSkBQnt+q8EHvTd13UyDofuWGqyHnJTZ2y1Q==","signatures":[{"sig":"MEQCIE0XxUegG8fXsYpBzYtoWC5ZTa2t1Mn+dYQ4ilh//0e9AiBpn7wBWTRYFgucX0RtY7QwAWL9rf83AMJ7jYbh8L1B8A==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":55049},"main":"./dist/index.js","type":"module","_from":"file:diabolicallabs-slack-1.0.0.tgz","types":"./dist/index.d.ts","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"lint":"biome check ./src","test":"vitest run --coverage","build":"tsup","typecheck":"tsc --noEmit","test:watch":"vitest"},"_npmUser":{"name":"shackled78","email":"shackled78@gmail.com"},"_resolved":"/tmp/9d020439dc762e082b51bc0ff98e22d9/diabolicallabs-slack-1.0.0.tgz","publisher":"Diabolical Labs","_integrity":"sha512-PpFHmU8U1QmD7t/8P/rip7GKK7V/D4fW1VUXnLLMz8eaS9s+HBWSkBQnt+q8EHvTd13UyDofuWGqyHnJTZ2y1Q==","repository":{"url":"git+https://github.com/mannism/dlabs-toolkit.git","type":"git","directory":"packages/slack"},"_npmVersion":"10.8.2","description":"Send-only Slack notifier. chat.postMessage + incoming webhook via @slack/web-api. Named error taxonomy, retry, pluggable logger, optional rate-limiter peer-dep. © Diabolical Labs","directories":{},"_nodeVersion":"20.20.2","dependencies":{"@slack/types":"^2.21.0","@slack/web-api":"^7.16.0","@diabolicallabs/notifier-core":"1.0.0"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.5","vitest":"^4.1.6","@types/node":"^25.9.0","@vitest/coverage-v8":"^4.1.6","@diabolicallabs/rate-limiter":"1.0.0"},"peerDependencies":{"@diabolicallabs/rate-limiter":"^1.0.0"},"peerDependenciesMeta":{"@diabolicallabs/rate-limiter":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/slack_1.0.0_1779599988721_0.09149620154102323","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@diabolicallabs/slack","version":"1.0.1","description":"Send-only Slack notifier. chat.postMessage + incoming webhook via @slack/web-api. Named error taxonomy, retry, pluggable logger, optional rate-limiter peer-dep. © Diabolical Labs","author":{"name":"Diana Ismail","email":"diana@deeismail.com","url":"https://deeismail.com"},"publisher":"Diabolical Labs","license":"MIT","type":"module","exports":{".":{"import":"./dist/index.js","types":"./dist/index.d.ts"}},"main":"./dist/index.js","types":"./dist/index.d.ts","repository":{"type":"git","url":"git+https://github.com/mannism/dlabs-toolkit.git","directory":"packages/slack"},"homepage":"https://github.com/mannism/dlabs-toolkit#readme","bugs":{"url":"https://github.com/mannism/dlabs-toolkit/issues"},"engines":{"node":">=20"},"dependencies":{"@slack/types":"^3.0.0","@slack/web-api":"^8.0.0","@diabolicallabs/notifier-core":"1.0.0"},"peerDependencies":{"@diabolicallabs/rate-limiter":"^1.0.0"},"peerDependenciesMeta":{"@diabolicallabs/rate-limiter":{"optional":true}},"devDependencies":{"@types/node":"^26.2.0","@vitest/coverage-v8":"^4.1.10","tsup":"^8.3.5","vitest":"^4.1.10","@diabolicallabs/rate-limiter":"1.0.1"},"scripts":{"build":"tsup","typecheck":"tsc --noEmit","lint":"biome check ./src","test":"vitest run --coverage","test:watch":"vitest"},"_id":"@diabolicallabs/slack@1.0.1","_integrity":"sha512-Tawvv2UN9tYARYHO5KhnoXSDbooy1Hax4K/AOCAXNhh9oqse69O0ACQluGquvdSugKrNBjoI0T/jVYHveOggQw==","_resolved":"/tmp/0dbc856dd44404c2d4b1f08638dd6df2/diabolicallabs-slack-1.0.1.tgz","_from":"file:diabolicallabs-slack-1.0.1.tgz","_nodeVersion":"20.20.2","_npmVersion":"10.8.2","dist":{"integrity":"sha512-Tawvv2UN9tYARYHO5KhnoXSDbooy1Hax4K/AOCAXNhh9oqse69O0ACQluGquvdSugKrNBjoI0T/jVYHveOggQw==","shasum":"ca20a20b726c92911f0133763152e65a827702b5","tarball":"https://registry.npmjs.org/@diabolicallabs/slack/-/slack-1.0.1.tgz","fileCount":6,"unpackedSize":59757,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCJV5c/zHHk2glVyxkEKdLem4MOjuPYz/LFYdmLqZkoMgIhAL+ZXSe4YKu9posivn8tedpgq6Kcp5F9j+BHoRrqn8qI"}]},"_npmUser":{"name":"shackled78","email":"shackled78@gmail.com"},"directories":{},"maintainers":[{"name":"shackled78","email":"shackled78@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/slack_1.0.1_1786889630737_0.9817843953485932"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-24T05:19:48.597Z","modified":"2026-08-16T14:13:51.083Z","1.0.0":"2026-05-24T05:19:48.870Z","1.0.1":"2026-08-16T14:13:50.872Z"},"bugs":{"url":"https://github.com/mannism/dlabs-toolkit/issues"},"author":{"name":"Diana Ismail","email":"diana@deeismail.com","url":"https://deeismail.com"},"license":"MIT","homepage":"https://github.com/mannism/dlabs-toolkit#readme","repository":{"type":"git","url":"git+https://github.com/mannism/dlabs-toolkit.git","directory":"packages/slack"},"description":"Send-only Slack notifier. chat.postMessage + incoming webhook via @slack/web-api. Named error taxonomy, retry, pluggable logger, optional rate-limiter peer-dep. © Diabolical Labs","maintainers":[{"name":"shackled78","email":"shackled78@gmail.com"}],"readme":"# `@diabolicallabs/slack`\n\nSend-only Slack notifier built on `@slack/web-api` v8. Supports `chat.postMessage` (bot-token path) and incoming webhooks.\n\n## Install\n\n```bash\npnpm add @diabolicallabs/slack\n```\n\nFor proactive rate-limit gating (optional):\n\n```bash\npnpm add @diabolicallabs/rate-limiter\n```\n\n## Usage\n\n### From environment variables\n\n```ts\nimport { createSlackNotifierFromEnv } from '@diabolicallabs/slack';\n\n// Reads SLACK_BOT_TOKEN, SLACK_WEBHOOK_URL, SLACK_DEFAULT_CHANNEL\nconst slack = createSlackNotifierFromEnv();\nawait slack.postMessage({ channel: '#alerts', text: 'Deploy complete' });\n```\n\n### Explicit config\n\n```ts\nimport { createSlackNotifier } from '@diabolicallabs/slack';\n\nconst slack = createSlackNotifier({\n  botToken: process.env.SLACK_BOT_TOKEN,\n  defaultChannel: '#agent-fleet-health',\n  maxRetries: 3,\n  timeoutMs: 10_000,\n});\n\nawait slack.postMessage({\n  channel: '#alerts',\n  text: 'Fallback text for notifications',\n  blocks: [\n    {\n      type: 'section',\n      text: { type: 'mrkdwn', text: '*Deploy complete* — GEOAudit v2.1.0 is live.' },\n    },\n  ],\n});\n```\n\n### Incoming webhook\n\n```ts\nconst slack = createSlackNotifier({\n  webhookUrl: process.env.SLACK_WEBHOOK_URL,\n});\n\nawait slack.postWebhook({ text: 'Fleet health check passed' });\n```\n\n### Portable interface\n\n```ts\nimport type { Notifier } from '@diabolicallabs/notifier-core';\nimport { createSlackNotifierFromEnv } from '@diabolicallabs/slack';\n\nconst notifier: Notifier = createSlackNotifierFromEnv();\nawait notifier.send({ to: '#alerts', text: 'hello' });\n```\n\n### With rate-limiter (optional peer-dep)\n\n```ts\nimport { createSlackNotifier } from '@diabolicallabs/slack';\nimport { createRateLimiter } from '@diabolicallabs/rate-limiter';\nimport Redis from 'ioredis';\n\nconst redis = new Redis(process.env.REDIS_URL);\nconst rateLimiter = createRateLimiter({\n  redis,\n  windowMs: 1_000,\n  maxRequests: 1,\n});\n\nconst slack = createSlackNotifier({\n  botToken: process.env.SLACK_BOT_TOKEN,\n  rateLimiter,\n});\n```\n\n## Configuration\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `botToken` | `string` | — | Slack bot token (`xoxb-…`). Required for `postMessage`. |\n| `webhookUrl` | `string` | — | Incoming webhook URL. Required for `postWebhook`. |\n| `defaultChannel` | `string` | — | Default channel for `postMessage` when none specified. |\n| `maxRetries` | `number` | `3` | Max retry attempts on transient failures. |\n| `baseDelayMs` | `number` | `500` | Base delay for exponential backoff (ms). |\n| `capDelayMs` | `number` | `2000` | Maximum delay cap for backoff (ms). |\n| `timeoutMs` | `number` | `10000` | Per-request timeout (ms). |\n| `logger` | `Logger` | stdout JSON | Pluggable logger. |\n| `rateLimiter` | `RateLimiter` | — | Optional — proactive per-channel rate-limit gating. |\n\nAt least one of `botToken` or `webhookUrl` must be provided. `createSlackNotifierFromEnv()` throws `SlackValidationError` synchronously when both are absent.\n\n## Environment variables\n\n| Variable | Required | Description |\n|---|---|---|\n| `SLACK_BOT_TOKEN` | If using `postMessage` | Bot token from your Slack app. |\n| `SLACK_WEBHOOK_URL` | If using `postWebhook` | Incoming webhook URL. |\n| `SLACK_DEFAULT_CHANNEL` | No | Default channel for `postMessage`. |\n\n## Error taxonomy\n\n| Class | When thrown | Retryable |\n|---|---|---|\n| `SlackError` | Generic fallback | — |\n| `SlackAuthError` | `invalid_auth`, `not_authed`, `token_revoked`, `account_inactive` | No |\n| `SlackChannelNotFoundError` | `channel_not_found` | No |\n| `SlackRateLimitError` | 429 after retries exhausted | No |\n| `SlackValidationError` | `invalid_arguments`, missing payload, or missing credentials | No |\n| `SlackUnavailableError` | 5xx after retries exhausted | No (already retried) |\n\nAll errors extend `PlatformError` from `@diabolicallabs/notifier-core`.\n\n`SlackRateLimitError` has two additional fields:\n- `kind: 'exceeded' | 'unavailable'` — `'exceeded'` = real 429; `'unavailable'` = Redis broken in the rate-limiter peer-dep\n- `retryAfterMs: number | null` — milliseconds from the `Retry-After` header\n\n## Rate-limit handling\n\nTwo layers:\n\n1. **Proactive (optional)**: if `config.rateLimiter` is provided, calls `check('slack:channel:{channel}')` before each `postMessage`. Throws `SlackRateLimitError` immediately if over limit, without hitting Slack.\n\n2. **Reactive**: `Retry-After` header value from Slack 429 responses is respected and propagated to `SlackRateLimitError.retryAfterMs`.\n\nIf the rate-limiter peer-dep throws (Redis down), the notifier logs `SLACK_RATELIMITER_UNAVAILABLE` and sends anyway — Slack's own 429 protection is the safety net.\n\n## Integration test (living example)\n\nSee `src/__tests__/integration/slack.integration.test.ts`. Run with:\n\n```bash\nSLACK_BOT_TOKEN=xoxb-... SLACK_TEST_CHANNEL=#wave6-test pnpm test:integration\n```\n\nCI skips this suite when env vars are absent.\n\n## License\n\nMIT — © Diabolical Labs\n","readmeFilename":"README.md"}