channel-formatter 1.0.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/README.md ADDED
@@ -0,0 +1,42 @@
1
+ # channel-formatter
2
+
3
+ Format markdown-like AI output for messaging channels with different syntax and size limits.
4
+
5
+ ## Features
6
+
7
+ - Telegram MarkdownV2 escaping
8
+ - Slack-friendly markdown conversion
9
+ - Plain-text SMS fallback
10
+ - Message chunking by channel size limits
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ npm install channel-formatter
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ ```ts
21
+ import { formatForChannel } from "channel-formatter";
22
+
23
+ const chunks = formatForChannel(markdown, "telegram");
24
+
25
+ for (const chunk of chunks) {
26
+ await sendMessage(chunk);
27
+ }
28
+ ```
29
+
30
+ ## Supported Channels
31
+
32
+ - `telegram`
33
+ - `slack`
34
+ - `teams`
35
+ - `sms`
36
+
37
+ ## Exports
38
+
39
+ - `formatForChannel(text, channel)`
40
+ - `splitMessage(text, maxLen)`
41
+
42
+ Best for notification bots, chat assistants, and workflow tools that need channel-specific output formatting.
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "channel-formatter",
3
+ "version": "1.0.0",
4
+ "description": "Format AI response Markdown for Telegram, Slack, Teams, and SMS — zero dependencies",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "scripts": {
16
+ "build": "tsc",
17
+ "dev": "tsc --watch",
18
+ "typecheck": "tsc --noEmit --pretty false"
19
+ },
20
+ "keywords": ["formatter", "telegram", "slack", "teams", "sms", "markdown", "channel", "messaging"],
21
+ "devDependencies": {
22
+ "typescript": "^5"
23
+ }
24
+ }
package/src/index.ts ADDED
@@ -0,0 +1,113 @@
1
+ /**
2
+ * channel-formatter
3
+ *
4
+ * Format AI response Markdown for different messaging channels.
5
+ * Pure functions — zero runtime dependencies.
6
+ *
7
+ * Usage:
8
+ * import { formatForChannel } from "channel-formatter";
9
+ *
10
+ * const chunks = formatForChannel(markdownText, "telegram");
11
+ * for (const chunk of chunks) await sendMessage(chunk);
12
+ */
13
+
14
+ export type Channel = "telegram" | "slack" | "teams" | "sms";
15
+
16
+ const MAX_TELEGRAM_MSG = 4096;
17
+ const MAX_SLACK_MSG = 3000;
18
+ const MAX_SMS_MSG = 1600; // ~10 concatenated SMS segments
19
+ const MAX_TEAMS_MSG = 4000;
20
+
21
+ /**
22
+ * Convert Markdown to Telegram MarkdownV2 format.
23
+ * Telegram MarkdownV2 requires escaping many special characters.
24
+ */
25
+ function toTelegramMarkdown(text: string): string {
26
+ let out = text;
27
+
28
+ // Headers → bold line
29
+ out = out.replace(/^#{1,3}\s+(.+)$/gm, "*$1*");
30
+
31
+ // **bold** → *bold*
32
+ out = out.replace(/\*\*([^*]+)\*\*/g, "*$1*");
33
+
34
+ // Escape special chars outside code spans
35
+ const parts = out.split(/(```[\s\S]*?```|`[^`]+`)/g);
36
+ out = parts.map((part, i) => {
37
+ if (i % 2 === 1) return part; // code span — leave as-is
38
+ return part.replace(/([_\[\]()~>#+=|{}.!\-])/g, "\\$1");
39
+ }).join("");
40
+
41
+ return out;
42
+ }
43
+
44
+ /**
45
+ * Convert Markdown to Slack mrkdwn format.
46
+ */
47
+ function toSlackMarkdown(text: string): string {
48
+ let out = text;
49
+ // Headers → *bold*
50
+ out = out.replace(/^#{1,3}\s+(.+)$/gm, "*$1*");
51
+ // **bold** → *bold*
52
+ out = out.replace(/\*\*([^*]+)\*\*/g, "*$1*");
53
+ // `inline code` stays same; Slack renders - and * as bullets natively
54
+ return out;
55
+ }
56
+
57
+ /**
58
+ * Strip all Markdown for plain-text channels (SMS).
59
+ */
60
+ function toPlainText(text: string): string {
61
+ let out = text;
62
+ out = out.replace(/^#{1,3}\s+/gm, ""); // remove header markers
63
+ out = out.replace(/\*\*([^*]+)\*\*/g, "$1"); // **bold** → bold
64
+ out = out.replace(/`([^`]+)`/g, "$1"); // `code` → code
65
+ out = out.replace(/```[\s\S]*?```/g, "[code block]"); // code blocks
66
+ out = out.replace(/^[-*]\s/gm, "• "); // bullets
67
+ return out.trim();
68
+ }
69
+
70
+ /**
71
+ * Split a long message into chunks that fit within the channel limit.
72
+ * Tries to split on paragraph boundaries to preserve readability.
73
+ */
74
+ export function splitMessage(text: string, maxLen: number): string[] {
75
+ if (text.length <= maxLen) return [text];
76
+ const chunks: string[] = [];
77
+ let remaining = text;
78
+ while (remaining.length > 0) {
79
+ if (remaining.length <= maxLen) {
80
+ chunks.push(remaining);
81
+ break;
82
+ }
83
+ let splitAt = remaining.lastIndexOf("\n\n", maxLen);
84
+ if (splitAt < maxLen / 2) splitAt = remaining.lastIndexOf("\n", maxLen);
85
+ if (splitAt < maxLen / 2) splitAt = maxLen;
86
+ chunks.push(remaining.slice(0, splitAt).trim());
87
+ remaining = remaining.slice(splitAt).trim();
88
+ }
89
+ return chunks.filter(Boolean);
90
+ }
91
+
92
+ /**
93
+ * Format and split a Markdown message for the given channel.
94
+ * Returns an array of strings to send sequentially.
95
+ *
96
+ * @param text - Markdown text produced by your AI
97
+ * @param channel - Target channel
98
+ * @returns - Array of message chunks, each within the channel's size limit
99
+ */
100
+ export function formatForChannel(text: string, channel: Channel): string[] {
101
+ switch (channel) {
102
+ case "telegram":
103
+ return splitMessage(toTelegramMarkdown(text), MAX_TELEGRAM_MSG);
104
+ case "slack":
105
+ return splitMessage(toSlackMarkdown(text), MAX_SLACK_MSG);
106
+ case "sms":
107
+ return splitMessage(toPlainText(text), MAX_SMS_MSG);
108
+ case "teams":
109
+ default:
110
+ // Teams supports a Markdown subset similar to standard
111
+ return splitMessage(text, MAX_TEAMS_MSG);
112
+ }
113
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "outDir": "./dist",
7
+ "declaration": true,
8
+ "declarationMap": true,
9
+ "sourceMap": true,
10
+ "strict": true,
11
+ "esModuleInterop": true,
12
+ "skipLibCheck": true
13
+ },
14
+ "include": ["src"]
15
+ }