aibroker 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 +21 -0
- package/README.md +40 -0
- package/dist/adapters/iterm/core.d.ts +28 -0
- package/dist/adapters/iterm/core.d.ts.map +1 -0
- package/dist/adapters/iterm/core.js +191 -0
- package/dist/adapters/iterm/core.js.map +1 -0
- package/dist/adapters/iterm/dictation.d.ts +26 -0
- package/dist/adapters/iterm/dictation.d.ts.map +1 -0
- package/dist/adapters/iterm/dictation.js +99 -0
- package/dist/adapters/iterm/dictation.js.map +1 -0
- package/dist/adapters/iterm/sessions.d.ts +31 -0
- package/dist/adapters/iterm/sessions.d.ts.map +1 -0
- package/dist/adapters/iterm/sessions.js +156 -0
- package/dist/adapters/iterm/sessions.js.map +1 -0
- package/dist/adapters/kokoro/media.d.ts +32 -0
- package/dist/adapters/kokoro/media.d.ts.map +1 -0
- package/dist/adapters/kokoro/media.js +157 -0
- package/dist/adapters/kokoro/media.js.map +1 -0
- package/dist/adapters/kokoro/tts.d.ts +19 -0
- package/dist/adapters/kokoro/tts.d.ts.map +1 -0
- package/dist/adapters/kokoro/tts.js +157 -0
- package/dist/adapters/kokoro/tts.js.map +1 -0
- package/dist/adapters/session/backend.d.ts +22 -0
- package/dist/adapters/session/backend.d.ts.map +1 -0
- package/dist/adapters/session/backend.js +43 -0
- package/dist/adapters/session/backend.js.map +1 -0
- package/dist/backend/api.d.ts +20 -0
- package/dist/backend/api.d.ts.map +1 -0
- package/dist/backend/api.js +26 -0
- package/dist/backend/api.js.map +1 -0
- package/dist/core/log.d.ts +7 -0
- package/dist/core/log.d.ts.map +1 -0
- package/dist/core/log.js +14 -0
- package/dist/core/log.js.map +1 -0
- package/dist/core/markdown.d.ts +32 -0
- package/dist/core/markdown.d.ts.map +1 -0
- package/dist/core/markdown.js +71 -0
- package/dist/core/markdown.js.map +1 -0
- package/dist/core/mime.d.ts +11 -0
- package/dist/core/mime.d.ts.map +1 -0
- package/dist/core/mime.js +46 -0
- package/dist/core/mime.js.map +1 -0
- package/dist/core/persistence.d.ts +20 -0
- package/dist/core/persistence.d.ts.map +1 -0
- package/dist/core/persistence.js +108 -0
- package/dist/core/persistence.js.map +1 -0
- package/dist/core/router.d.ts +24 -0
- package/dist/core/router.d.ts.map +1 -0
- package/dist/core/router.js +38 -0
- package/dist/core/router.js.map +1 -0
- package/dist/core/state.d.ts +45 -0
- package/dist/core/state.d.ts.map +1 -0
- package/dist/core/state.js +82 -0
- package/dist/core/state.js.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -0
- package/dist/ipc/client.d.ts +45 -0
- package/dist/ipc/client.d.ts.map +1 -0
- package/dist/ipc/client.js +199 -0
- package/dist/ipc/client.js.map +1 -0
- package/dist/ipc/server.d.ts +34 -0
- package/dist/ipc/server.d.ts.map +1 -0
- package/dist/ipc/server.js +123 -0
- package/dist/ipc/server.js.map +1 -0
- package/dist/types/backend.d.ts +25 -0
- package/dist/types/backend.d.ts.map +1 -0
- package/dist/types/backend.js +9 -0
- package/dist/types/backend.js.map +1 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +6 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/ipc.d.ts +18 -0
- package/dist/types/ipc.d.ts.map +1 -0
- package/dist/types/ipc.js +6 -0
- package/dist/types/ipc.js.map +1 -0
- package/dist/types/session.d.ts +29 -0
- package/dist/types/session.d.ts.map +1 -0
- package/dist/types/session.js +5 -0
- package/dist/types/session.js.map +1 -0
- package/dist/types/transport.d.ts +43 -0
- package/dist/types/transport.d.ts.map +1 -0
- package/dist/types/transport.js +8 -0
- package/dist/types/transport.js.map +1 -0
- package/dist/types/voice.d.ts +17 -0
- package/dist/types/voice.d.ts.map +1 -0
- package/dist/types/voice.js +5 -0
- package/dist/types/voice.js.map +1 -0
- package/package.json +35 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* adapters/kokoro/media.ts — Audio transcription via Whisper CLI.
|
|
3
|
+
*
|
|
4
|
+
* Transport-agnostic: only handles local audio files.
|
|
5
|
+
* Downloading media from WhatsApp/Telegram stays in per-project code.
|
|
6
|
+
*/
|
|
7
|
+
/** Absolute path to the Whisper CLI binary. */
|
|
8
|
+
export declare const WHISPER_BIN: string;
|
|
9
|
+
/** Whisper model (overridable via env). */
|
|
10
|
+
export declare const WHISPER_MODEL: string;
|
|
11
|
+
/**
|
|
12
|
+
* Map a MIME type to a sensible file extension for images.
|
|
13
|
+
*/
|
|
14
|
+
export declare function mimetypeToExt(mimetype: string | null | undefined): string;
|
|
15
|
+
/**
|
|
16
|
+
* Map a MIME type to a sensible file extension for documents.
|
|
17
|
+
*/
|
|
18
|
+
export declare function mimetypeToDocExt(mimetype: string | null | undefined): string;
|
|
19
|
+
/**
|
|
20
|
+
* Transcribe a local audio file using the Whisper CLI.
|
|
21
|
+
*
|
|
22
|
+
* @param audioPath - Absolute path to the audio file (WAV, OGG, MP3, etc.)
|
|
23
|
+
* @param label - Label prefix for the transcript (e.g. "[Voice note]" or "[Audio]")
|
|
24
|
+
* @returns Formatted transcript string, or null on failure.
|
|
25
|
+
*/
|
|
26
|
+
export declare function transcribeAudio(audioPath: string, label?: string): Promise<string | null>;
|
|
27
|
+
/**
|
|
28
|
+
* Split text into chunks suitable for sequential TTS voice notes.
|
|
29
|
+
* Splits at paragraph breaks, then sentence boundaries, targeting ~800 chars per chunk.
|
|
30
|
+
*/
|
|
31
|
+
export declare function splitIntoChunks(text: string, maxChars?: number): string[];
|
|
32
|
+
//# sourceMappingURL=media.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media.d.ts","sourceRoot":"","sources":["../../../src/adapters/kokoro/media.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAYH,+CAA+C;AAC/C,eAAO,MAAM,WAAW,QAGR,CAAC;AAEjB,2CAA2C;AAC3C,eAAO,MAAM,aAAa,QACgG,CAAC;AAE3H;;GAEG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAMzE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAiB5E;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,SAAS,EAAE,MAAM,EACjB,KAAK,SAAY,GAChB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA4CxB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,SAAM,GAAG,MAAM,EAAE,CA+BtE"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* adapters/kokoro/media.ts — Audio transcription via Whisper CLI.
|
|
3
|
+
*
|
|
4
|
+
* Transport-agnostic: only handles local audio files.
|
|
5
|
+
* Downloading media from WhatsApp/Telegram stays in per-project code.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, readFileSync, unlinkSync } from "node:fs";
|
|
8
|
+
import { promisify } from "node:util";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { execFile } from "node:child_process";
|
|
12
|
+
import { log } from "../../core/log.js";
|
|
13
|
+
const execFileAsync = promisify(execFile);
|
|
14
|
+
/** Absolute path to the Whisper CLI binary. */
|
|
15
|
+
export const WHISPER_BIN = ["/opt/homebrew/bin/whisper", "/usr/local/bin/whisper", "whisper"].find((p) => p === "whisper" || existsSync(p)) ?? "whisper";
|
|
16
|
+
/** Whisper model (overridable via env). */
|
|
17
|
+
export const WHISPER_MODEL = process.env.AIBROKER_WHISPER_MODEL ?? process.env.MSGBRIDGE_WHISPER_MODEL ?? process.env.WHAZAA_WHISPER_MODEL ?? "small";
|
|
18
|
+
/**
|
|
19
|
+
* Map a MIME type to a sensible file extension for images.
|
|
20
|
+
*/
|
|
21
|
+
export function mimetypeToExt(mimetype) {
|
|
22
|
+
if (!mimetype)
|
|
23
|
+
return "jpg";
|
|
24
|
+
if (mimetype.includes("png"))
|
|
25
|
+
return "png";
|
|
26
|
+
if (mimetype.includes("webp"))
|
|
27
|
+
return "webp";
|
|
28
|
+
if (mimetype.includes("gif"))
|
|
29
|
+
return "gif";
|
|
30
|
+
return "jpg";
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Map a MIME type to a sensible file extension for documents.
|
|
34
|
+
*/
|
|
35
|
+
export function mimetypeToDocExt(mimetype) {
|
|
36
|
+
if (!mimetype)
|
|
37
|
+
return "bin";
|
|
38
|
+
if (mimetype.includes("pdf"))
|
|
39
|
+
return "pdf";
|
|
40
|
+
if (mimetype.includes("word") || mimetype.includes("docx"))
|
|
41
|
+
return "docx";
|
|
42
|
+
if (mimetype.includes("msword"))
|
|
43
|
+
return "doc";
|
|
44
|
+
if (mimetype.includes("spreadsheet") || mimetype.includes("xlsx"))
|
|
45
|
+
return "xlsx";
|
|
46
|
+
if (mimetype.includes("ms-excel"))
|
|
47
|
+
return "xls";
|
|
48
|
+
if (mimetype.includes("presentation") || mimetype.includes("pptx"))
|
|
49
|
+
return "pptx";
|
|
50
|
+
if (mimetype.includes("ms-powerpoint"))
|
|
51
|
+
return "ppt";
|
|
52
|
+
if (mimetype.includes("zip"))
|
|
53
|
+
return "zip";
|
|
54
|
+
if (mimetype.includes("text/plain"))
|
|
55
|
+
return "txt";
|
|
56
|
+
if (mimetype.includes("text/csv"))
|
|
57
|
+
return "csv";
|
|
58
|
+
if (mimetype.includes("json"))
|
|
59
|
+
return "json";
|
|
60
|
+
if (mimetype.includes("mp4"))
|
|
61
|
+
return "mp4";
|
|
62
|
+
if (mimetype.includes("webm"))
|
|
63
|
+
return "webm";
|
|
64
|
+
if (mimetype.includes("3gpp"))
|
|
65
|
+
return "3gp";
|
|
66
|
+
return "bin";
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Transcribe a local audio file using the Whisper CLI.
|
|
70
|
+
*
|
|
71
|
+
* @param audioPath - Absolute path to the audio file (WAV, OGG, MP3, etc.)
|
|
72
|
+
* @param label - Label prefix for the transcript (e.g. "[Voice note]" or "[Audio]")
|
|
73
|
+
* @returns Formatted transcript string, or null on failure.
|
|
74
|
+
*/
|
|
75
|
+
export async function transcribeAudio(audioPath, label = "[Audio]") {
|
|
76
|
+
const base = audioPath.replace(/\.[^.]+$/, "");
|
|
77
|
+
const baseName = base.split("/").pop();
|
|
78
|
+
const filesToClean = [
|
|
79
|
+
join(tmpdir(), `${baseName}.txt`),
|
|
80
|
+
join(tmpdir(), `${baseName}.json`),
|
|
81
|
+
join(tmpdir(), `${baseName}.vtt`),
|
|
82
|
+
join(tmpdir(), `${baseName}.srt`),
|
|
83
|
+
join(tmpdir(), `${baseName}.tsv`),
|
|
84
|
+
];
|
|
85
|
+
try {
|
|
86
|
+
log(`Transcribing ${audioPath} (model=${WHISPER_MODEL})...`);
|
|
87
|
+
await execFileAsync(WHISPER_BIN, [audioPath, "--model", WHISPER_MODEL, "--output_format", "txt", "--output_dir", tmpdir(), "--verbose", "False"], {
|
|
88
|
+
timeout: 120_000,
|
|
89
|
+
env: {
|
|
90
|
+
...process.env,
|
|
91
|
+
PATH: `/opt/homebrew/bin:/usr/local/bin:${process.env.PATH || "/usr/bin:/bin"}`,
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
const txtPath = join(tmpdir(), `${baseName}.txt`);
|
|
95
|
+
if (!existsSync(txtPath)) {
|
|
96
|
+
log(`Whisper did not produce output at ${txtPath}`);
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
const transcript = readFileSync(txtPath, "utf-8").trim();
|
|
100
|
+
log(`Transcription: ${transcript.slice(0, 80)}`);
|
|
101
|
+
return `${label}: ${transcript}`;
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
log(`Audio transcription failed: ${err}`);
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
for (const f of filesToClean) {
|
|
109
|
+
try {
|
|
110
|
+
unlinkSync(f);
|
|
111
|
+
}
|
|
112
|
+
catch { /* ignore */ }
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Split text into chunks suitable for sequential TTS voice notes.
|
|
118
|
+
* Splits at paragraph breaks, then sentence boundaries, targeting ~800 chars per chunk.
|
|
119
|
+
*/
|
|
120
|
+
export function splitIntoChunks(text, maxChars = 800) {
|
|
121
|
+
if (text.length <= maxChars)
|
|
122
|
+
return [text];
|
|
123
|
+
const paragraphs = text.split(/\n\n+/);
|
|
124
|
+
const chunks = [];
|
|
125
|
+
let current = "";
|
|
126
|
+
for (const para of paragraphs) {
|
|
127
|
+
if (current.length + para.length + 2 <= maxChars) {
|
|
128
|
+
current += (current ? "\n\n" : "") + para;
|
|
129
|
+
}
|
|
130
|
+
else if (para.length <= maxChars) {
|
|
131
|
+
if (current)
|
|
132
|
+
chunks.push(current);
|
|
133
|
+
current = para;
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
if (current)
|
|
137
|
+
chunks.push(current);
|
|
138
|
+
// Split long paragraph at sentence boundaries
|
|
139
|
+
const sentences = para.match(/[^.!?]+[.!?]+\s*/g) ?? [para];
|
|
140
|
+
current = "";
|
|
141
|
+
for (const sentence of sentences) {
|
|
142
|
+
if (current.length + sentence.length <= maxChars) {
|
|
143
|
+
current += sentence;
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
if (current)
|
|
147
|
+
chunks.push(current.trim());
|
|
148
|
+
current = sentence;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (current.trim())
|
|
154
|
+
chunks.push(current.trim());
|
|
155
|
+
return chunks;
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=media.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media.js","sourceRoot":"","sources":["../../../src/adapters/kokoro/media.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* adapters/kokoro/tts.ts — Kokoro TTS synthesis pipeline.
|
|
3
|
+
*
|
|
4
|
+
* Converts text to OGG Opus audio buffers (for sending as voice notes)
|
|
5
|
+
* or plays locally via afplay. No transport SDK imports.
|
|
6
|
+
*
|
|
7
|
+
* Pipeline: Kokoro-js → Float32 PCM → WAV → ffmpeg → OGG Opus
|
|
8
|
+
*/
|
|
9
|
+
export type KokoroVoice = "af_heart" | "af_alloy" | "af_aoede" | "af_bella" | "af_jessica" | "af_kore" | "af_nicole" | "af_nova" | "af_river" | "af_sarah" | "af_sky" | "am_adam" | "am_echo" | "am_eric" | "am_fenrir" | "am_liam" | "am_michael" | "am_onyx" | "am_puck" | "am_santa" | "bf_alice" | "bf_emma" | "bf_isabella" | "bf_lily" | "bm_daniel" | "bm_fable" | "bm_george" | "bm_lewis";
|
|
10
|
+
/**
|
|
11
|
+
* Convert text to an OGG Opus voice note buffer.
|
|
12
|
+
*/
|
|
13
|
+
export declare function textToVoiceNote(text: string, voice?: string): Promise<Buffer>;
|
|
14
|
+
/**
|
|
15
|
+
* Synthesize text and play locally via afplay.
|
|
16
|
+
*/
|
|
17
|
+
export declare function speakLocally(text: string, voice?: string): Promise<void>;
|
|
18
|
+
export declare function listVoices(): string[];
|
|
19
|
+
//# sourceMappingURL=tts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tts.d.ts","sourceRoot":"","sources":["../../../src/adapters/kokoro/tts.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAcH,MAAM,MAAM,WAAW,GACnB,UAAU,GAAG,UAAU,GAAG,UAAU,GAAG,UAAU,GAAG,YAAY,GAChE,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,UAAU,GAAG,QAAQ,GACxE,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,GAC3D,YAAY,GAAG,SAAS,GAAG,SAAS,GAAG,UAAU,GACjD,UAAU,GAAG,SAAS,GAAG,aAAa,GAAG,SAAS,GAClD,WAAW,GAAG,UAAU,GAAG,WAAW,GAAG,UAAU,CAAC;AAqCxD;;GAEG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA4CnF;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAsB9E;AAED,wBAAgB,UAAU,IAAI,MAAM,EAAE,CAErC"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* adapters/kokoro/tts.ts — Kokoro TTS synthesis pipeline.
|
|
3
|
+
*
|
|
4
|
+
* Converts text to OGG Opus audio buffers (for sending as voice notes)
|
|
5
|
+
* or plays locally via afplay. No transport SDK imports.
|
|
6
|
+
*
|
|
7
|
+
* Pipeline: Kokoro-js → Float32 PCM → WAV → ffmpeg → OGG Opus
|
|
8
|
+
*/
|
|
9
|
+
import { execSync, spawn } from "node:child_process";
|
|
10
|
+
import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { tmpdir } from "node:os";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { log } from "../../core/log.js";
|
|
14
|
+
const FFMPEG = ["/opt/homebrew/bin/ffmpeg", "/usr/local/bin/ffmpeg", "ffmpeg"].find((p) => p === "ffmpeg" || existsSync(p)) ?? "ffmpeg";
|
|
15
|
+
const KNOWN_VOICES = [
|
|
16
|
+
"af_heart", "af_alloy", "af_aoede", "af_bella", "af_jessica",
|
|
17
|
+
"af_kore", "af_nicole", "af_nova", "af_river", "af_sarah", "af_sky",
|
|
18
|
+
"am_adam", "am_echo", "am_eric", "am_fenrir", "am_liam",
|
|
19
|
+
"am_michael", "am_onyx", "am_puck", "am_santa",
|
|
20
|
+
"bf_alice", "bf_emma", "bf_isabella", "bf_lily",
|
|
21
|
+
"bm_daniel", "bm_fable", "bm_george", "bm_lewis",
|
|
22
|
+
];
|
|
23
|
+
const DEFAULT_VOICE = process.env.AIBROKER_TTS_VOICE ??
|
|
24
|
+
process.env.MSGBRIDGE_TTS_VOICE ??
|
|
25
|
+
process.env.WHAZAA_TTS_VOICE ??
|
|
26
|
+
"bm_fable";
|
|
27
|
+
const MODEL_ID = "onnx-community/Kokoro-82M-v1.0-ONNX";
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
29
|
+
let ttsInstance = null;
|
|
30
|
+
let initPromise = null;
|
|
31
|
+
async function ensureInitialized() {
|
|
32
|
+
if (ttsInstance !== null)
|
|
33
|
+
return;
|
|
34
|
+
if (initPromise !== null) {
|
|
35
|
+
await initPromise;
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
initPromise = (async () => {
|
|
39
|
+
log(`Initializing Kokoro TTS (model: ${MODEL_ID}, dtype: q8)...`);
|
|
40
|
+
const { KokoroTTS } = await import("kokoro-js");
|
|
41
|
+
ttsInstance = await KokoroTTS.from_pretrained(MODEL_ID, { dtype: "q8", device: "cpu" });
|
|
42
|
+
log("Kokoro TTS ready.");
|
|
43
|
+
})();
|
|
44
|
+
await initPromise;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Convert text to an OGG Opus voice note buffer.
|
|
48
|
+
*/
|
|
49
|
+
export async function textToVoiceNote(text, voice) {
|
|
50
|
+
if (!text?.trim())
|
|
51
|
+
throw new Error("TTS: text must not be empty");
|
|
52
|
+
if (FFMPEG === "ffmpeg" && !existsSync("/usr/bin/ffmpeg")) {
|
|
53
|
+
log("Warning: ffmpeg not found at known Homebrew paths; falling back to bare 'ffmpeg'.");
|
|
54
|
+
}
|
|
55
|
+
const resolvedVoice = resolveVoice(voice ?? DEFAULT_VOICE);
|
|
56
|
+
await ensureInitialized();
|
|
57
|
+
log(`Generating audio: voice=${resolvedVoice}, text="${text.slice(0, 60)}${text.length > 60 ? "..." : ""}"`);
|
|
58
|
+
const result = await ttsInstance.generate(text, { voice: resolvedVoice });
|
|
59
|
+
const combined = result.audio;
|
|
60
|
+
const sampleRate = result.sampling_rate ?? 24_000;
|
|
61
|
+
if (combined.length === 0)
|
|
62
|
+
throw new Error("TTS: generate produced no audio");
|
|
63
|
+
log(`Generated ${combined.length} samples at ${sampleRate} Hz`);
|
|
64
|
+
const uid = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
65
|
+
const wavPath = join(tmpdir(), `aibroker-tts-${uid}.wav`);
|
|
66
|
+
const oggPath = join(tmpdir(), `aibroker-tts-${uid}.ogg`);
|
|
67
|
+
try {
|
|
68
|
+
writeFileSync(wavPath, float32ToWav(combined, sampleRate));
|
|
69
|
+
const ffmpegCmd = `"${FFMPEG}" -y -i "${wavPath}" -c:a libopus -b:a 64k -ar 24000 -ac 1 -application voip -vbr off "${oggPath}" 2>&1`;
|
|
70
|
+
try {
|
|
71
|
+
execSync(ffmpegCmd, { timeout: 30_000, stdio: "pipe" });
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
throw new Error(`ffmpeg conversion failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
75
|
+
}
|
|
76
|
+
if (!existsSync(oggPath))
|
|
77
|
+
throw new Error("ffmpeg did not produce output file");
|
|
78
|
+
const oggBuffer = readFileSync(oggPath);
|
|
79
|
+
log(`Converted to OGG Opus: ${oggBuffer.length} bytes`);
|
|
80
|
+
return oggBuffer;
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
for (const p of [wavPath, oggPath]) {
|
|
84
|
+
try {
|
|
85
|
+
if (existsSync(p))
|
|
86
|
+
unlinkSync(p);
|
|
87
|
+
}
|
|
88
|
+
catch { /* ignore */ }
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Synthesize text and play locally via afplay.
|
|
94
|
+
*/
|
|
95
|
+
export async function speakLocally(text, voice) {
|
|
96
|
+
if (!text?.trim())
|
|
97
|
+
throw new Error("TTS: text must not be empty");
|
|
98
|
+
const resolvedVoice = resolveVoice(voice ?? DEFAULT_VOICE);
|
|
99
|
+
await ensureInitialized();
|
|
100
|
+
log(`Speaking locally: voice=${resolvedVoice}, text="${text.slice(0, 60)}${text.length > 60 ? "..." : ""}"`);
|
|
101
|
+
const speakResult = await ttsInstance.generate(text, { voice: resolvedVoice });
|
|
102
|
+
const speakAudio = speakResult.audio;
|
|
103
|
+
const speakSampleRate = speakResult.sampling_rate ?? 24_000;
|
|
104
|
+
if (speakAudio.length === 0)
|
|
105
|
+
throw new Error("TTS: generate produced no audio");
|
|
106
|
+
const wavPath = join(tmpdir(), `aibroker-speak-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.wav`);
|
|
107
|
+
writeFileSync(wavPath, float32ToWav(speakAudio, speakSampleRate));
|
|
108
|
+
const child = spawn("afplay", [wavPath], { stdio: "ignore", detached: true });
|
|
109
|
+
child.on("close", () => {
|
|
110
|
+
try {
|
|
111
|
+
unlinkSync(wavPath);
|
|
112
|
+
}
|
|
113
|
+
catch { /* ignore */ }
|
|
114
|
+
});
|
|
115
|
+
child.unref();
|
|
116
|
+
}
|
|
117
|
+
export function listVoices() {
|
|
118
|
+
return [...KNOWN_VOICES];
|
|
119
|
+
}
|
|
120
|
+
// ── Internal helpers ──
|
|
121
|
+
function float32ToWav(samples, sampleRate) {
|
|
122
|
+
const numSamples = samples.length;
|
|
123
|
+
const byteRate = sampleRate * 2;
|
|
124
|
+
const dataSize = numSamples * 2;
|
|
125
|
+
const buf = Buffer.allocUnsafe(44 + dataSize);
|
|
126
|
+
buf.write("RIFF", 0, "ascii");
|
|
127
|
+
buf.writeUInt32LE(36 + dataSize, 4);
|
|
128
|
+
buf.write("WAVE", 8, "ascii");
|
|
129
|
+
buf.write("fmt ", 12, "ascii");
|
|
130
|
+
buf.writeUInt32LE(16, 16);
|
|
131
|
+
buf.writeUInt16LE(1, 20);
|
|
132
|
+
buf.writeUInt16LE(1, 22);
|
|
133
|
+
buf.writeUInt32LE(sampleRate, 24);
|
|
134
|
+
buf.writeUInt32LE(byteRate, 28);
|
|
135
|
+
buf.writeUInt16LE(2, 32);
|
|
136
|
+
buf.writeUInt16LE(16, 34);
|
|
137
|
+
buf.write("data", 36, "ascii");
|
|
138
|
+
buf.writeUInt32LE(dataSize, 40);
|
|
139
|
+
for (let i = 0; i < numSamples; i++) {
|
|
140
|
+
const clamped = Math.max(-1, Math.min(1, samples[i]));
|
|
141
|
+
buf.writeInt16LE(Math.round(clamped * 32767), 44 + i * 2);
|
|
142
|
+
}
|
|
143
|
+
return buf;
|
|
144
|
+
}
|
|
145
|
+
function resolveVoice(voice) {
|
|
146
|
+
const lower = voice.toLowerCase().trim();
|
|
147
|
+
if (lower === "true" || lower === "default" || lower === "")
|
|
148
|
+
return DEFAULT_VOICE;
|
|
149
|
+
if (KNOWN_VOICES.includes(lower))
|
|
150
|
+
return lower;
|
|
151
|
+
const match = KNOWN_VOICES.find((v) => v.endsWith(`_${lower}`) || v === lower);
|
|
152
|
+
if (match)
|
|
153
|
+
return match;
|
|
154
|
+
log(`Unknown voice "${voice}", falling back to "${DEFAULT_VOICE}".`);
|
|
155
|
+
return DEFAULT_VOICE;
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=tts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tts.js","sourceRoot":"","sources":["../../../src/adapters/kokoro/tts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* adapters/session/backend.ts — SessionBackend: deliver messages via iTerm2 typeIntoSession.
|
|
3
|
+
*
|
|
4
|
+
* This is the primary backend for CLI-based AI models (Claude, ChatGPT, ollama).
|
|
5
|
+
* Messages are typed into the terminal session and the AI responds async via
|
|
6
|
+
* the transport's incoming message handler.
|
|
7
|
+
*/
|
|
8
|
+
import type { Backend, SessionBackendConfig } from "../../types/backend.js";
|
|
9
|
+
export declare class SessionBackend implements Backend {
|
|
10
|
+
readonly name: string;
|
|
11
|
+
readonly type: "session";
|
|
12
|
+
readonly command: string;
|
|
13
|
+
constructor(config: SessionBackendConfig & {
|
|
14
|
+
name?: string;
|
|
15
|
+
});
|
|
16
|
+
/**
|
|
17
|
+
* Deliver a message by typing it into the target iTerm2 session.
|
|
18
|
+
* Returns undefined because the response comes back async via the transport.
|
|
19
|
+
*/
|
|
20
|
+
deliver(message: string, sessionId?: string): Promise<string | undefined>;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=backend.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../../../src/adapters/session/backend.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAK5E,qBAAa,cAAe,YAAW,OAAO;IAC5C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAG,SAAS,CAAU;IACnC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAEb,MAAM,EAAE,oBAAoB,GAAG;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE;IAK5D;;;OAGG;IACG,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;CAuBhF"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* adapters/session/backend.ts — SessionBackend: deliver messages via iTerm2 typeIntoSession.
|
|
3
|
+
*
|
|
4
|
+
* This is the primary backend for CLI-based AI models (Claude, ChatGPT, ollama).
|
|
5
|
+
* Messages are typed into the terminal session and the AI responds async via
|
|
6
|
+
* the transport's incoming message handler.
|
|
7
|
+
*/
|
|
8
|
+
import { typeIntoSession } from "../iterm/core.js";
|
|
9
|
+
import { log } from "../../core/log.js";
|
|
10
|
+
import { activeItermSessionId, sessionRegistry } from "../../core/state.js";
|
|
11
|
+
export class SessionBackend {
|
|
12
|
+
name;
|
|
13
|
+
type = "session";
|
|
14
|
+
command;
|
|
15
|
+
constructor(config) {
|
|
16
|
+
this.name = config.name ?? config.command;
|
|
17
|
+
this.command = config.command;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Deliver a message by typing it into the target iTerm2 session.
|
|
21
|
+
* Returns undefined because the response comes back async via the transport.
|
|
22
|
+
*/
|
|
23
|
+
async deliver(message, sessionId) {
|
|
24
|
+
// Resolve target iTerm2 session
|
|
25
|
+
let targetItermId = activeItermSessionId;
|
|
26
|
+
if (sessionId) {
|
|
27
|
+
const session = sessionRegistry.get(sessionId);
|
|
28
|
+
if (session?.itermSessionId) {
|
|
29
|
+
targetItermId = session.itermSessionId;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (!targetItermId) {
|
|
33
|
+
log(`SessionBackend: no iTerm session to deliver to`);
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
const ok = typeIntoSession(targetItermId, message);
|
|
37
|
+
if (!ok) {
|
|
38
|
+
log(`SessionBackend: failed to type into session ${targetItermId}`);
|
|
39
|
+
}
|
|
40
|
+
return undefined; // async response via transport
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=backend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backend.js","sourceRoot":"","sources":["../../../src/adapters/session/backend.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AACxC,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE5E,MAAM,OAAO,cAAc;IAChB,IAAI,CAAS;IACb,IAAI,GAAG,SAAkB,CAAC;IAC1B,OAAO,CAAS;IAEzB,YAAY,MAAgD;QAC1D,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,OAAO,CAAC;QAC1C,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;IAChC,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,OAAO,CAAC,OAAe,EAAE,SAAkB;QAC/C,gCAAgC;QAChC,IAAI,aAAa,GAAG,oBAAoB,CAAC;QAEzC,IAAI,SAAS,EAAE,CAAC;YACd,MAAM,OAAO,GAAG,eAAe,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YAC/C,IAAI,OAAO,EAAE,cAAc,EAAE,CAAC;gBAC5B,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC;YACzC,CAAC;QACH,CAAC;QAED,IAAI,CAAC,aAAa,EAAE,CAAC;YACnB,GAAG,CAAC,gDAAgD,CAAC,CAAC;YACtD,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,MAAM,EAAE,GAAG,eAAe,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;QACnD,IAAI,CAAC,EAAE,EAAE,CAAC;YACR,GAAG,CAAC,+CAA+C,aAAa,EAAE,CAAC,CAAC;QACtE,CAAC;QAED,OAAO,SAAS,CAAC,CAAC,+BAA+B;IACnD,CAAC;CACF"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* backend/api.ts — APIBackend: direct HTTP API calls to AI providers.
|
|
3
|
+
*
|
|
4
|
+
* Stub for Phase 9. Will implement:
|
|
5
|
+
* - Anthropic API (Claude)
|
|
6
|
+
* - OpenAI API (ChatGPT)
|
|
7
|
+
* - Ollama API (local models)
|
|
8
|
+
*/
|
|
9
|
+
import type { Backend, APIBackendConfig } from "../types/backend.js";
|
|
10
|
+
export declare class APIBackend implements Backend {
|
|
11
|
+
readonly name: string;
|
|
12
|
+
readonly type: "api";
|
|
13
|
+
readonly provider: APIBackendConfig["provider"];
|
|
14
|
+
readonly model: string;
|
|
15
|
+
constructor(config: APIBackendConfig & {
|
|
16
|
+
name?: string;
|
|
17
|
+
});
|
|
18
|
+
deliver(message: string, _sessionId?: string): Promise<string | undefined>;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/backend/api.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAGrE,qBAAa,UAAW,YAAW,OAAO;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAG,KAAK,CAAU;IAC/B,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAChD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;gBAEX,MAAM,EAAE,gBAAgB,GAAG;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE;IAMlD,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;CAOjF"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* backend/api.ts — APIBackend: direct HTTP API calls to AI providers.
|
|
3
|
+
*
|
|
4
|
+
* Stub for Phase 9. Will implement:
|
|
5
|
+
* - Anthropic API (Claude)
|
|
6
|
+
* - OpenAI API (ChatGPT)
|
|
7
|
+
* - Ollama API (local models)
|
|
8
|
+
*/
|
|
9
|
+
import { log } from "../core/log.js";
|
|
10
|
+
export class APIBackend {
|
|
11
|
+
name;
|
|
12
|
+
type = "api";
|
|
13
|
+
provider;
|
|
14
|
+
model;
|
|
15
|
+
constructor(config) {
|
|
16
|
+
this.name = config.name ?? `${config.provider}/${config.model}`;
|
|
17
|
+
this.provider = config.provider;
|
|
18
|
+
this.model = config.model;
|
|
19
|
+
}
|
|
20
|
+
async deliver(message, _sessionId) {
|
|
21
|
+
log(`APIBackend: not yet implemented (provider=${this.provider}, model=${this.model})`);
|
|
22
|
+
throw new Error(`APIBackend is not yet implemented. Use SessionBackend for now. ` +
|
|
23
|
+
`(provider=${this.provider}, model=${this.model})`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/backend/api.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AAErC,MAAM,OAAO,UAAU;IACZ,IAAI,CAAS;IACb,IAAI,GAAG,KAAc,CAAC;IACtB,QAAQ,CAA+B;IACvC,KAAK,CAAS;IAEvB,YAAY,MAA4C;QACtD,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,GAAG,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QAChE,IAAI,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;QAChC,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;IAC5B,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,OAAe,EAAE,UAAmB;QAChD,GAAG,CAAC,6CAA6C,IAAI,CAAC,QAAQ,WAAW,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC;QACxF,MAAM,IAAI,KAAK,CACb,iEAAiE;YACjE,aAAa,IAAI,CAAC,QAAQ,WAAW,IAAI,CAAC,KAAK,GAAG,CACnD,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Timestamped logger — all output goes to stderr (stdout is MCP JSON-RPC).
|
|
3
|
+
* The prefix is configurable so each consumer can identify itself.
|
|
4
|
+
*/
|
|
5
|
+
export declare function setLogPrefix(prefix: string): void;
|
|
6
|
+
export declare function log(...args: unknown[]): void;
|
|
7
|
+
//# sourceMappingURL=log.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../../src/core/log.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAEjD;AAED,wBAAgB,GAAG,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAM5C"}
|
package/dist/core/log.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Timestamped logger — all output goes to stderr (stdout is MCP JSON-RPC).
|
|
3
|
+
* The prefix is configurable so each consumer can identify itself.
|
|
4
|
+
*/
|
|
5
|
+
let _prefix = "aibroker";
|
|
6
|
+
export function setLogPrefix(prefix) {
|
|
7
|
+
_prefix = prefix;
|
|
8
|
+
}
|
|
9
|
+
export function log(...args) {
|
|
10
|
+
const timestamp = new Date().toISOString().slice(11, 23);
|
|
11
|
+
const parts = args.map((a) => typeof a === "string" ? a : JSON.stringify(a));
|
|
12
|
+
process.stderr.write(`[${_prefix} ${timestamp}] ${parts.join(" ")}\n`);
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=log.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log.js","sourceRoot":"","sources":["../../src/core/log.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,IAAI,OAAO,GAAG,UAAU,CAAC;AAEzB,MAAM,UAAU,YAAY,CAAC,MAAc;IACzC,OAAO,GAAG,MAAM,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,GAAG,CAAC,GAAG,IAAe;IACpC,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IACzD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAC3B,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAC9C,CAAC;IACF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,OAAO,IAAI,SAAS,KAAK,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACzE,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared markdown transform primitives.
|
|
3
|
+
*
|
|
4
|
+
* These transforms are the common subset used by both WhatsApp and Telegram
|
|
5
|
+
* formatters. Each transport applies additional platform-specific rules on top.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Apply the 8 shared block-level and inline markdown transforms.
|
|
9
|
+
*
|
|
10
|
+
* Transform order:
|
|
11
|
+
* 1. Headings → bold uppercase (using placeholder to protect from italic pass)
|
|
12
|
+
* 2. Horizontal rules → em dashes
|
|
13
|
+
* 3. Blockquotes → left bar
|
|
14
|
+
* 4. Checkboxes → unicode checkboxes
|
|
15
|
+
* 5. Unordered lists → bullet points
|
|
16
|
+
* 6. Bold **text** → placeholder-wrapped
|
|
17
|
+
* 7. Italic *text* → _text_
|
|
18
|
+
* 8. Replace bold placeholders with target bold marker
|
|
19
|
+
*
|
|
20
|
+
* @param text - Input markdown text
|
|
21
|
+
* @param boldMarker - The bold delimiter for the target platform ("*" for WhatsApp, "<b>" for Telegram)
|
|
22
|
+
* @param boldEndMarker - The closing bold delimiter ("*" for WhatsApp, "</b>" for Telegram)
|
|
23
|
+
* @param italicMarker - The italic delimiter ("_" for WhatsApp, "<i>" for Telegram)
|
|
24
|
+
* @param italicEndMarker - The closing italic delimiter ("_" for WhatsApp, "</i>" for Telegram)
|
|
25
|
+
*/
|
|
26
|
+
export declare function applySharedMarkdownTransforms(text: string, boldMarker: string, boldEndMarker: string, italicMarker: string, italicEndMarker: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* WhatsApp markdown formatter.
|
|
29
|
+
* Uses * for bold, _ for italic.
|
|
30
|
+
*/
|
|
31
|
+
export declare function markdownToWhatsApp(text: string): string;
|
|
32
|
+
//# sourceMappingURL=markdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/core/markdown.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,MAAM,EACrB,YAAY,EAAE,MAAM,EACpB,eAAe,EAAE,MAAM,GACtB,MAAM,CA0BR;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAavD"}
|