@mulmoclaude/core 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.
Files changed (122) hide show
  1. package/assets/helps/billing-clients-worklog.md +215 -0
  2. package/assets/helps/billing-invoice.md +458 -0
  3. package/assets/helps/business.md +104 -0
  4. package/assets/helps/collection-skills.md +810 -0
  5. package/assets/helps/custom-view.md +433 -0
  6. package/assets/helps/feeds.md +114 -0
  7. package/assets/helps/gemini.md +57 -0
  8. package/assets/helps/github.md +23 -0
  9. package/assets/helps/guide.md +61 -0
  10. package/assets/helps/index.md +89 -0
  11. package/assets/helps/lessons-collection.md +400 -0
  12. package/assets/helps/mulmoscript.md +249 -0
  13. package/assets/helps/portfolio-tracker.md +211 -0
  14. package/assets/helps/presentation-deck.md +828 -0
  15. package/assets/helps/presenthtml.md +89 -0
  16. package/assets/helps/sandbox.md +97 -0
  17. package/assets/helps/spreadsheet.md +43 -0
  18. package/assets/helps/storyteller.md +101 -0
  19. package/assets/helps/telegram.md +136 -0
  20. package/assets/helps/todo-collection.md +140 -0
  21. package/assets/helps/vocabulary.md +109 -0
  22. package/assets/helps/wiki.md +168 -0
  23. package/assets/skills-preset/mc-cooking-coach/SKILL.md +217 -0
  24. package/assets/skills-preset/mc-library/SKILL.md +188 -0
  25. package/assets/skills-preset/mc-manage-automations/SKILL.md +119 -0
  26. package/assets/skills-preset/mc-manage-skills/SKILL.md +141 -0
  27. package/assets/skills-preset/mc-wiki-deep-lint/SKILL.md +108 -0
  28. package/assets/skills-preset/mc-wiki-health-check/SKILL.md +61 -0
  29. package/assets/skills-preset/mc-wiki-ingest/SKILL.md +182 -0
  30. package/assets/skills-preset/mc-wiki-promote/SKILL.md +175 -0
  31. package/assets/skills-preset/mc-zenn/SKILL.md +136 -0
  32. package/dist/chunk-CKQMccvm.cjs +28 -0
  33. package/dist/collection/core/actionVisible.d.ts +34 -0
  34. package/dist/collection/core/calendarGrid.d.ts +120 -0
  35. package/dist/collection/core/deriveAll.d.ts +38 -0
  36. package/dist/collection/core/derivedFormula.d.ts +18 -0
  37. package/dist/collection/core/draft.d.ts +18 -0
  38. package/dist/collection/core/enumColors.d.ts +33 -0
  39. package/dist/collection/core/errorMessage.d.ts +4 -0
  40. package/dist/collection/core/itemLabel.d.ts +12 -0
  41. package/dist/collection/core/presentCollection.d.ts +13 -0
  42. package/dist/collection/core/promptSafety.d.ts +1 -0
  43. package/dist/collection/core/schema.d.ts +355 -0
  44. package/dist/collection/core/shortHexId.d.ts +8 -0
  45. package/dist/collection/core/sortItems.d.ts +29 -0
  46. package/dist/collection/core/uiTypes.d.ts +106 -0
  47. package/dist/collection/index.cjs +793 -0
  48. package/dist/collection/index.cjs.map +1 -0
  49. package/dist/collection/index.d.ts +14 -0
  50. package/dist/collection/index.js +740 -0
  51. package/dist/collection/index.js.map +1 -0
  52. package/dist/collection/paths.cjs +44 -0
  53. package/dist/collection/paths.cjs.map +1 -0
  54. package/dist/collection/paths.js +41 -0
  55. package/dist/collection/paths.js.map +1 -0
  56. package/dist/collection/server/atomic.d.ts +1 -0
  57. package/dist/collection/server/delete.d.ts +38 -0
  58. package/dist/collection/server/derive.d.ts +8 -0
  59. package/dist/collection/server/discoveredCollection.d.ts +18 -0
  60. package/dist/collection/server/discovery.d.ts +227 -0
  61. package/dist/collection/server/host.d.ts +77 -0
  62. package/dist/collection/server/index.cjs +1721 -0
  63. package/dist/collection/server/index.cjs.map +1 -0
  64. package/dist/collection/server/index.d.ts +11 -0
  65. package/dist/collection/server/index.js +1671 -0
  66. package/dist/collection/server/index.js.map +1 -0
  67. package/dist/collection/server/io.d.ts +114 -0
  68. package/dist/collection/server/paths.d.ts +52 -0
  69. package/dist/collection/server/spawn.d.ts +55 -0
  70. package/dist/collection/server/templatePath.d.ts +25 -0
  71. package/dist/collection/server/util.d.ts +3 -0
  72. package/dist/collection/server/validate.d.ts +19 -0
  73. package/dist/collection/server/views.d.ts +20 -0
  74. package/dist/deriveAll-C15OpM3K.cjs +399 -0
  75. package/dist/deriveAll-C15OpM3K.cjs.map +1 -0
  76. package/dist/deriveAll-C6BYnpBL.js +364 -0
  77. package/dist/deriveAll-C6BYnpBL.js.map +1 -0
  78. package/dist/file-change/index.cjs +72 -0
  79. package/dist/file-change/index.cjs.map +1 -0
  80. package/dist/file-change/index.d.ts +43 -0
  81. package/dist/file-change/index.js +66 -0
  82. package/dist/file-change/index.js.map +1 -0
  83. package/dist/notifier/engine.d.ts +72 -0
  84. package/dist/notifier/index.cjs +484 -0
  85. package/dist/notifier/index.cjs.map +1 -0
  86. package/dist/notifier/index.d.ts +3 -0
  87. package/dist/notifier/index.js +464 -0
  88. package/dist/notifier/index.js.map +1 -0
  89. package/dist/notifier/store.d.ts +18 -0
  90. package/dist/notifier/types.d.ts +118 -0
  91. package/dist/notifier/validate.d.ts +17 -0
  92. package/dist/scheduler/adapter.d.ts +48 -0
  93. package/dist/scheduler/index.cjs +352 -0
  94. package/dist/scheduler/index.cjs.map +1 -0
  95. package/dist/scheduler/index.d.ts +2 -0
  96. package/dist/scheduler/index.js +343 -0
  97. package/dist/scheduler/index.js.map +1 -0
  98. package/dist/scheduler/task-manager.d.ts +51 -0
  99. package/dist/whisper/client.cjs +241 -0
  100. package/dist/whisper/client.cjs.map +1 -0
  101. package/dist/whisper/client.d.ts +35 -0
  102. package/dist/whisper/client.js +239 -0
  103. package/dist/whisper/client.js.map +1 -0
  104. package/dist/whisper/ffmpeg.d.ts +6 -0
  105. package/dist/whisper/index.cjs +433 -0
  106. package/dist/whisper/index.cjs.map +1 -0
  107. package/dist/whisper/index.d.ts +5 -0
  108. package/dist/whisper/index.js +425 -0
  109. package/dist/whisper/index.js.map +1 -0
  110. package/dist/whisper/internal.d.ts +11 -0
  111. package/dist/whisper/models.d.ts +49 -0
  112. package/dist/whisper/sidecar.d.ts +8 -0
  113. package/dist/whisper/whisper.d.ts +28 -0
  114. package/dist/workspace-setup/assets.d.ts +10 -0
  115. package/dist/workspace-setup/index.d.ts +3 -0
  116. package/dist/workspace-setup/index.js +556 -0
  117. package/dist/workspace-setup/index.js.map +1 -0
  118. package/dist/workspace-setup/slug.d.ts +6 -0
  119. package/dist/workspace-setup/slug.js +13 -0
  120. package/dist/workspace-setup/slug.js.map +1 -0
  121. package/dist/workspace-setup/sync.d.ts +94 -0
  122. package/package.json +95 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.cjs","names":[],"sources":["../../src/whisper/client.ts"],"sourcesContent":["// @mulmoclaude/core/whisper/client — framework-neutral browser capture controller.\n// Records one utterance at a time with MediaRecorder, segments on pauses via a\n// Web Audio VAD, and sends each segment through an injected transport. State is\n// pushed via `onState`; the host wraps this into its own reactivity (Vue refs,\n// React state, …). No framework dependency. See plans/feat-extract-whisper-package.md.\n\n// Map a UI locale to a Whisper language code. UI language is a strong prior for\n// the spoken language; \"auto\" lets Whisper detect it from the audio.\nconst LOCALE_TO_WHISPER: Record<string, string> = {\n en: \"en\",\n ja: \"ja\",\n zh: \"zh\",\n ko: \"ko\",\n es: \"es\",\n \"pt-BR\": \"pt\",\n fr: \"fr\",\n de: \"de\",\n};\n\nexport function localeToWhisperLanguage(locale: string): string {\n return LOCALE_TO_WHISPER[locale] ?? \"auto\";\n}\n\n// VAD tuning. RMS over [-1,1] float samples; a pause is SILENCE_MS of\n// sub-threshold level after speech. MAX_SEGMENT_MS force-cuts a long unbroken\n// utterance so no clip exceeds Whisper's 30s window or the server's size cap.\nconst SPEECH_RMS = 0.015;\nconst SILENCE_MS = 800;\nconst MAX_SEGMENT_MS = 20_000;\nconst MONITOR_INTERVAL_MS = 100;\nconst AVAILABILITY_POLL_MS = 2_000;\n\nfunction pickRecorderMime(): string | undefined {\n const candidates = [\"audio/webm;codecs=opus\", \"audio/webm\", \"audio/mp4\"];\n if (typeof MediaRecorder === \"undefined\") return undefined;\n return candidates.find((type) => MediaRecorder.isTypeSupported(type));\n}\n\nfunction blobToDataUrl(blob: Blob): Promise<string> {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = () => resolve(String(reader.result));\n reader.onerror = () => reject(reader.error ?? new Error(\"FileReader failed\"));\n reader.readAsDataURL(blob);\n });\n}\n\nfunction computeRms(buffer: Float32Array): number {\n let sum = 0;\n for (const sample of buffer) sum += sample * sample;\n return Math.sqrt(sum / buffer.length);\n}\n\nfunction toMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\nexport interface VoiceCaptureTransport {\n /** Transcribe one segment. Throws on failure. */\n transcribe: (dataUrl: string, language: string) => Promise<{ text: string }>;\n /** Current readiness. `downloading` true keeps the controller polling so it\n * flips to ready as soon as a model download finishes. */\n getStatus: () => Promise<{ ready: boolean; downloading: boolean }>;\n}\n\nexport interface VoiceCaptureState {\n available: boolean;\n listening: boolean;\n transcribing: boolean;\n}\n\nexport interface VoiceCaptureCallbacks {\n /** A recognized (non-empty) segment transcript. */\n onTranscript: (text: string) => void;\n /** A segment produced no speech. */\n onEmpty?: () => void;\n /** A recoverable error message (transport failure, permission denied, etc.). */\n onError?: (message: string) => void;\n /** Pushed whenever available/listening/transcribing changes. */\n onState?: (state: VoiceCaptureState) => void;\n}\n\nexport interface VoiceCapture {\n refreshAvailability: () => Promise<void>;\n start: () => Promise<boolean>;\n stop: () => void;\n dispose: () => void;\n}\n\nexport function createVoiceCapture(transport: VoiceCaptureTransport, language: () => string, callbacks: VoiceCaptureCallbacks): VoiceCapture {\n let available = false;\n let listening = false;\n let transcribing = false;\n\n function emit(): void {\n callbacks.onState?.({ available, listening, transcribing });\n }\n function setAvailable(value: boolean): void {\n if (available !== value) {\n available = value;\n emit();\n }\n }\n function setListening(value: boolean): void {\n if (listening !== value) {\n listening = value;\n emit();\n }\n }\n\n let stream: MediaStream | null = null;\n let audioCtx: AudioContext | null = null;\n let analyser: AnalyserNode | null = null;\n let vadBuffer = new Float32Array(0);\n let monitorHandle: number | null = null;\n let recorder: MediaRecorder | null = null;\n let chunks: Blob[] = [];\n let mimeType = \"\";\n let segmentHasSpeech = false;\n let silenceStart: number | null = null;\n let segmentStart = 0;\n let pending = 0;\n let queue: Promise<void> = Promise.resolve();\n let availabilityPollHandle: number | null = null;\n // Bumped on stop(). Segments captured / sends resolved under an older\n // generation are dropped, so a late transcript never leaks across sessions.\n let generation = 0;\n let segmentGeneration = 0;\n // Single-flight guard for start(): true between entry and the moment capture\n // is set up (or the attempt aborts), so a second start can't race the first.\n let startInFlight = false;\n\n function setPending(delta: number): void {\n pending += delta;\n const next = pending > 0;\n if (transcribing !== next) {\n transcribing = next;\n emit();\n }\n }\n\n function stopAvailabilityPoll(): void {\n if (availabilityPollHandle !== null) {\n window.clearInterval(availabilityPollHandle);\n availabilityPollHandle = null;\n }\n }\n\n async function refreshAvailability(): Promise<void> {\n let status: { ready: boolean; downloading: boolean };\n try {\n status = await transport.getStatus();\n } catch {\n setAvailable(false);\n stopAvailabilityPoll();\n return;\n }\n setAvailable(status.ready);\n if (status.downloading) {\n if (availabilityPollHandle === null) {\n availabilityPollHandle = window.setInterval(() => {\n void refreshAvailability();\n }, AVAILABILITY_POLL_MS);\n }\n } else {\n stopAvailabilityPoll();\n }\n }\n\n async function sendSegment(blob: Blob, gen: number): Promise<void> {\n if (gen !== generation) return;\n try {\n const dataUrl = await blobToDataUrl(blob);\n const result = await transport.transcribe(dataUrl, language());\n if (gen !== generation) return;\n const text = result.text.trim();\n if (text.length === 0) callbacks.onEmpty?.();\n else callbacks.onTranscript(text);\n } catch (err) {\n // Generation-guard the failure path too: a send rejected after stop()/\n // session change belongs to a session the user already left.\n if (gen === generation) callbacks.onError?.(toMessage(err));\n }\n }\n\n // Serialize sends so transcripts append in capture order; `pending` keeps\n // `transcribing` true from enqueue until the send resolves.\n function enqueue(blob: Blob, gen: number): void {\n setPending(1);\n queue = queue\n .then(() => sendSegment(blob, gen))\n .catch(() => undefined)\n .finally(() => setPending(-1));\n }\n\n function containerType(): string {\n return mimeType.split(\";\")[0] || \"audio/webm\";\n }\n\n function onSegmentStop(): void {\n const hadSpeech = segmentHasSpeech;\n const gen = segmentGeneration;\n const blob = new Blob(chunks, { type: containerType() });\n if (listening) startRecorder();\n if (hadSpeech && blob.size > 0 && gen === generation) enqueue(blob, gen);\n }\n\n function startRecorder(): void {\n if (!stream) return;\n chunks = [];\n segmentHasSpeech = false;\n silenceStart = null;\n segmentStart = Date.now();\n segmentGeneration = generation;\n recorder = new MediaRecorder(stream, { mimeType });\n recorder.ondataavailable = (event) => {\n if (event.data.size > 0) chunks.push(event.data);\n };\n recorder.onstop = onSegmentStop;\n recorder.start();\n }\n\n function cutSegment(): void {\n if (recorder && recorder.state === \"recording\") recorder.stop();\n }\n\n function monitorTick(): void {\n if (!analyser) return;\n analyser.getFloatTimeDomainData(vadBuffer);\n const rms = computeRms(vadBuffer);\n const now = Date.now();\n if (rms > SPEECH_RMS) {\n segmentHasSpeech = true;\n silenceStart = null;\n } else if (segmentHasSpeech) {\n if (silenceStart === null) silenceStart = now;\n else if (now - silenceStart > SILENCE_MS) cutSegment();\n }\n if (segmentHasSpeech && now - segmentStart > MAX_SEGMENT_MS) cutSegment();\n }\n\n async function start(): Promise<boolean> {\n // Single-flight: `listening` only flips true AFTER getUserMedia resolves, so\n // a benign skip returns true (a start is already active / in progress).\n if (startInFlight || listening) return true;\n startInFlight = true;\n const startGen = generation;\n try {\n mimeType = pickRecorderMime() ?? \"\";\n if (!mimeType || !navigator.mediaDevices?.getUserMedia) {\n callbacks.onError?.(\"unsupported\");\n return false;\n }\n let acquired: MediaStream;\n try {\n acquired = await navigator.mediaDevices.getUserMedia({ audio: true });\n } catch {\n callbacks.onError?.(\"permission-denied\");\n return false;\n }\n // stop() bumps the generation; if it fired while we awaited permission\n // this start is stale — release the mic and abort.\n if (startGen !== generation) {\n acquired.getTracks().forEach((track) => track.stop());\n return false;\n }\n stream = acquired;\n audioCtx = new AudioContext();\n analyser = audioCtx.createAnalyser();\n analyser.fftSize = 2048;\n vadBuffer = new Float32Array(analyser.fftSize);\n audioCtx.createMediaStreamSource(stream).connect(analyser);\n setListening(true);\n startRecorder();\n monitorHandle = window.setInterval(monitorTick, MONITOR_INTERVAL_MS);\n return true;\n } finally {\n startInFlight = false;\n }\n }\n\n function stop(): void {\n // Bump the generation so any in-flight/queued segment is dropped rather than\n // applied after the user stops or switches sessions.\n generation += 1;\n setListening(false);\n if (monitorHandle !== null) {\n window.clearInterval(monitorHandle);\n monitorHandle = null;\n }\n if (recorder && recorder.state === \"recording\") recorder.stop();\n recorder = null;\n if (audioCtx) {\n audioCtx.close().catch(() => undefined);\n audioCtx = null;\n }\n analyser = null;\n stream?.getTracks().forEach((track) => track.stop());\n stream = null;\n }\n\n function dispose(): void {\n stopAvailabilityPoll();\n stop();\n }\n\n return { refreshAvailability, start, stop, dispose };\n}\n"],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ export declare function localeToWhisperLanguage(locale: string): string;
2
+ export interface VoiceCaptureTransport {
3
+ /** Transcribe one segment. Throws on failure. */
4
+ transcribe: (dataUrl: string, language: string) => Promise<{
5
+ text: string;
6
+ }>;
7
+ /** Current readiness. `downloading` true keeps the controller polling so it
8
+ * flips to ready as soon as a model download finishes. */
9
+ getStatus: () => Promise<{
10
+ ready: boolean;
11
+ downloading: boolean;
12
+ }>;
13
+ }
14
+ export interface VoiceCaptureState {
15
+ available: boolean;
16
+ listening: boolean;
17
+ transcribing: boolean;
18
+ }
19
+ export interface VoiceCaptureCallbacks {
20
+ /** A recognized (non-empty) segment transcript. */
21
+ onTranscript: (text: string) => void;
22
+ /** A segment produced no speech. */
23
+ onEmpty?: () => void;
24
+ /** A recoverable error message (transport failure, permission denied, etc.). */
25
+ onError?: (message: string) => void;
26
+ /** Pushed whenever available/listening/transcribing changes. */
27
+ onState?: (state: VoiceCaptureState) => void;
28
+ }
29
+ export interface VoiceCapture {
30
+ refreshAvailability: () => Promise<void>;
31
+ start: () => Promise<boolean>;
32
+ stop: () => void;
33
+ dispose: () => void;
34
+ }
35
+ export declare function createVoiceCapture(transport: VoiceCaptureTransport, language: () => string, callbacks: VoiceCaptureCallbacks): VoiceCapture;
@@ -0,0 +1,239 @@
1
+ //#region src/whisper/client.ts
2
+ var LOCALE_TO_WHISPER = {
3
+ en: "en",
4
+ ja: "ja",
5
+ zh: "zh",
6
+ ko: "ko",
7
+ es: "es",
8
+ "pt-BR": "pt",
9
+ fr: "fr",
10
+ de: "de"
11
+ };
12
+ function localeToWhisperLanguage(locale) {
13
+ return LOCALE_TO_WHISPER[locale] ?? "auto";
14
+ }
15
+ var SPEECH_RMS = .015;
16
+ var SILENCE_MS = 800;
17
+ var MAX_SEGMENT_MS = 2e4;
18
+ var MONITOR_INTERVAL_MS = 100;
19
+ var AVAILABILITY_POLL_MS = 2e3;
20
+ function pickRecorderMime() {
21
+ const candidates = [
22
+ "audio/webm;codecs=opus",
23
+ "audio/webm",
24
+ "audio/mp4"
25
+ ];
26
+ if (typeof MediaRecorder === "undefined") return void 0;
27
+ return candidates.find((type) => MediaRecorder.isTypeSupported(type));
28
+ }
29
+ function blobToDataUrl(blob) {
30
+ return new Promise((resolve, reject) => {
31
+ const reader = new FileReader();
32
+ reader.onload = () => resolve(String(reader.result));
33
+ reader.onerror = () => reject(reader.error ?? /* @__PURE__ */ new Error("FileReader failed"));
34
+ reader.readAsDataURL(blob);
35
+ });
36
+ }
37
+ function computeRms(buffer) {
38
+ let sum = 0;
39
+ for (const sample of buffer) sum += sample * sample;
40
+ return Math.sqrt(sum / buffer.length);
41
+ }
42
+ function toMessage(err) {
43
+ return err instanceof Error ? err.message : String(err);
44
+ }
45
+ function createVoiceCapture(transport, language, callbacks) {
46
+ let available = false;
47
+ let listening = false;
48
+ let transcribing = false;
49
+ function emit() {
50
+ callbacks.onState?.({
51
+ available,
52
+ listening,
53
+ transcribing
54
+ });
55
+ }
56
+ function setAvailable(value) {
57
+ if (available !== value) {
58
+ available = value;
59
+ emit();
60
+ }
61
+ }
62
+ function setListening(value) {
63
+ if (listening !== value) {
64
+ listening = value;
65
+ emit();
66
+ }
67
+ }
68
+ let stream = null;
69
+ let audioCtx = null;
70
+ let analyser = null;
71
+ let vadBuffer = new Float32Array(0);
72
+ let monitorHandle = null;
73
+ let recorder = null;
74
+ let chunks = [];
75
+ let mimeType = "";
76
+ let segmentHasSpeech = false;
77
+ let silenceStart = null;
78
+ let segmentStart = 0;
79
+ let pending = 0;
80
+ let queue = Promise.resolve();
81
+ let availabilityPollHandle = null;
82
+ let generation = 0;
83
+ let segmentGeneration = 0;
84
+ let startInFlight = false;
85
+ function setPending(delta) {
86
+ pending += delta;
87
+ const next = pending > 0;
88
+ if (transcribing !== next) {
89
+ transcribing = next;
90
+ emit();
91
+ }
92
+ }
93
+ function stopAvailabilityPoll() {
94
+ if (availabilityPollHandle !== null) {
95
+ window.clearInterval(availabilityPollHandle);
96
+ availabilityPollHandle = null;
97
+ }
98
+ }
99
+ async function refreshAvailability() {
100
+ let status;
101
+ try {
102
+ status = await transport.getStatus();
103
+ } catch {
104
+ setAvailable(false);
105
+ stopAvailabilityPoll();
106
+ return;
107
+ }
108
+ setAvailable(status.ready);
109
+ if (status.downloading) {
110
+ if (availabilityPollHandle === null) availabilityPollHandle = window.setInterval(() => {
111
+ refreshAvailability();
112
+ }, AVAILABILITY_POLL_MS);
113
+ } else stopAvailabilityPoll();
114
+ }
115
+ async function sendSegment(blob, gen) {
116
+ if (gen !== generation) return;
117
+ try {
118
+ const dataUrl = await blobToDataUrl(blob);
119
+ const result = await transport.transcribe(dataUrl, language());
120
+ if (gen !== generation) return;
121
+ const text = result.text.trim();
122
+ if (text.length === 0) callbacks.onEmpty?.();
123
+ else callbacks.onTranscript(text);
124
+ } catch (err) {
125
+ if (gen === generation) callbacks.onError?.(toMessage(err));
126
+ }
127
+ }
128
+ function enqueue(blob, gen) {
129
+ setPending(1);
130
+ queue = queue.then(() => sendSegment(blob, gen)).catch(() => void 0).finally(() => setPending(-1));
131
+ }
132
+ function containerType() {
133
+ return mimeType.split(";")[0] || "audio/webm";
134
+ }
135
+ function onSegmentStop() {
136
+ const hadSpeech = segmentHasSpeech;
137
+ const gen = segmentGeneration;
138
+ const blob = new Blob(chunks, { type: containerType() });
139
+ if (listening) startRecorder();
140
+ if (hadSpeech && blob.size > 0 && gen === generation) enqueue(blob, gen);
141
+ }
142
+ function startRecorder() {
143
+ if (!stream) return;
144
+ chunks = [];
145
+ segmentHasSpeech = false;
146
+ silenceStart = null;
147
+ segmentStart = Date.now();
148
+ segmentGeneration = generation;
149
+ recorder = new MediaRecorder(stream, { mimeType });
150
+ recorder.ondataavailable = (event) => {
151
+ if (event.data.size > 0) chunks.push(event.data);
152
+ };
153
+ recorder.onstop = onSegmentStop;
154
+ recorder.start();
155
+ }
156
+ function cutSegment() {
157
+ if (recorder && recorder.state === "recording") recorder.stop();
158
+ }
159
+ function monitorTick() {
160
+ if (!analyser) return;
161
+ analyser.getFloatTimeDomainData(vadBuffer);
162
+ const rms = computeRms(vadBuffer);
163
+ const now = Date.now();
164
+ if (rms > SPEECH_RMS) {
165
+ segmentHasSpeech = true;
166
+ silenceStart = null;
167
+ } else if (segmentHasSpeech) {
168
+ if (silenceStart === null) silenceStart = now;
169
+ else if (now - silenceStart > SILENCE_MS) cutSegment();
170
+ }
171
+ if (segmentHasSpeech && now - segmentStart > MAX_SEGMENT_MS) cutSegment();
172
+ }
173
+ async function start() {
174
+ if (startInFlight || listening) return true;
175
+ startInFlight = true;
176
+ const startGen = generation;
177
+ try {
178
+ mimeType = pickRecorderMime() ?? "";
179
+ if (!mimeType || !navigator.mediaDevices?.getUserMedia) {
180
+ callbacks.onError?.("unsupported");
181
+ return false;
182
+ }
183
+ let acquired;
184
+ try {
185
+ acquired = await navigator.mediaDevices.getUserMedia({ audio: true });
186
+ } catch {
187
+ callbacks.onError?.("permission-denied");
188
+ return false;
189
+ }
190
+ if (startGen !== generation) {
191
+ acquired.getTracks().forEach((track) => track.stop());
192
+ return false;
193
+ }
194
+ stream = acquired;
195
+ audioCtx = new AudioContext();
196
+ analyser = audioCtx.createAnalyser();
197
+ analyser.fftSize = 2048;
198
+ vadBuffer = new Float32Array(analyser.fftSize);
199
+ audioCtx.createMediaStreamSource(stream).connect(analyser);
200
+ setListening(true);
201
+ startRecorder();
202
+ monitorHandle = window.setInterval(monitorTick, MONITOR_INTERVAL_MS);
203
+ return true;
204
+ } finally {
205
+ startInFlight = false;
206
+ }
207
+ }
208
+ function stop() {
209
+ generation += 1;
210
+ setListening(false);
211
+ if (monitorHandle !== null) {
212
+ window.clearInterval(monitorHandle);
213
+ monitorHandle = null;
214
+ }
215
+ if (recorder && recorder.state === "recording") recorder.stop();
216
+ recorder = null;
217
+ if (audioCtx) {
218
+ audioCtx.close().catch(() => void 0);
219
+ audioCtx = null;
220
+ }
221
+ analyser = null;
222
+ stream?.getTracks().forEach((track) => track.stop());
223
+ stream = null;
224
+ }
225
+ function dispose() {
226
+ stopAvailabilityPoll();
227
+ stop();
228
+ }
229
+ return {
230
+ refreshAvailability,
231
+ start,
232
+ stop,
233
+ dispose
234
+ };
235
+ }
236
+ //#endregion
237
+ export { createVoiceCapture, localeToWhisperLanguage };
238
+
239
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","names":[],"sources":["../../src/whisper/client.ts"],"sourcesContent":["// @mulmoclaude/core/whisper/client — framework-neutral browser capture controller.\n// Records one utterance at a time with MediaRecorder, segments on pauses via a\n// Web Audio VAD, and sends each segment through an injected transport. State is\n// pushed via `onState`; the host wraps this into its own reactivity (Vue refs,\n// React state, …). No framework dependency. See plans/feat-extract-whisper-package.md.\n\n// Map a UI locale to a Whisper language code. UI language is a strong prior for\n// the spoken language; \"auto\" lets Whisper detect it from the audio.\nconst LOCALE_TO_WHISPER: Record<string, string> = {\n en: \"en\",\n ja: \"ja\",\n zh: \"zh\",\n ko: \"ko\",\n es: \"es\",\n \"pt-BR\": \"pt\",\n fr: \"fr\",\n de: \"de\",\n};\n\nexport function localeToWhisperLanguage(locale: string): string {\n return LOCALE_TO_WHISPER[locale] ?? \"auto\";\n}\n\n// VAD tuning. RMS over [-1,1] float samples; a pause is SILENCE_MS of\n// sub-threshold level after speech. MAX_SEGMENT_MS force-cuts a long unbroken\n// utterance so no clip exceeds Whisper's 30s window or the server's size cap.\nconst SPEECH_RMS = 0.015;\nconst SILENCE_MS = 800;\nconst MAX_SEGMENT_MS = 20_000;\nconst MONITOR_INTERVAL_MS = 100;\nconst AVAILABILITY_POLL_MS = 2_000;\n\nfunction pickRecorderMime(): string | undefined {\n const candidates = [\"audio/webm;codecs=opus\", \"audio/webm\", \"audio/mp4\"];\n if (typeof MediaRecorder === \"undefined\") return undefined;\n return candidates.find((type) => MediaRecorder.isTypeSupported(type));\n}\n\nfunction blobToDataUrl(blob: Blob): Promise<string> {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = () => resolve(String(reader.result));\n reader.onerror = () => reject(reader.error ?? new Error(\"FileReader failed\"));\n reader.readAsDataURL(blob);\n });\n}\n\nfunction computeRms(buffer: Float32Array): number {\n let sum = 0;\n for (const sample of buffer) sum += sample * sample;\n return Math.sqrt(sum / buffer.length);\n}\n\nfunction toMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\nexport interface VoiceCaptureTransport {\n /** Transcribe one segment. Throws on failure. */\n transcribe: (dataUrl: string, language: string) => Promise<{ text: string }>;\n /** Current readiness. `downloading` true keeps the controller polling so it\n * flips to ready as soon as a model download finishes. */\n getStatus: () => Promise<{ ready: boolean; downloading: boolean }>;\n}\n\nexport interface VoiceCaptureState {\n available: boolean;\n listening: boolean;\n transcribing: boolean;\n}\n\nexport interface VoiceCaptureCallbacks {\n /** A recognized (non-empty) segment transcript. */\n onTranscript: (text: string) => void;\n /** A segment produced no speech. */\n onEmpty?: () => void;\n /** A recoverable error message (transport failure, permission denied, etc.). */\n onError?: (message: string) => void;\n /** Pushed whenever available/listening/transcribing changes. */\n onState?: (state: VoiceCaptureState) => void;\n}\n\nexport interface VoiceCapture {\n refreshAvailability: () => Promise<void>;\n start: () => Promise<boolean>;\n stop: () => void;\n dispose: () => void;\n}\n\nexport function createVoiceCapture(transport: VoiceCaptureTransport, language: () => string, callbacks: VoiceCaptureCallbacks): VoiceCapture {\n let available = false;\n let listening = false;\n let transcribing = false;\n\n function emit(): void {\n callbacks.onState?.({ available, listening, transcribing });\n }\n function setAvailable(value: boolean): void {\n if (available !== value) {\n available = value;\n emit();\n }\n }\n function setListening(value: boolean): void {\n if (listening !== value) {\n listening = value;\n emit();\n }\n }\n\n let stream: MediaStream | null = null;\n let audioCtx: AudioContext | null = null;\n let analyser: AnalyserNode | null = null;\n let vadBuffer = new Float32Array(0);\n let monitorHandle: number | null = null;\n let recorder: MediaRecorder | null = null;\n let chunks: Blob[] = [];\n let mimeType = \"\";\n let segmentHasSpeech = false;\n let silenceStart: number | null = null;\n let segmentStart = 0;\n let pending = 0;\n let queue: Promise<void> = Promise.resolve();\n let availabilityPollHandle: number | null = null;\n // Bumped on stop(). Segments captured / sends resolved under an older\n // generation are dropped, so a late transcript never leaks across sessions.\n let generation = 0;\n let segmentGeneration = 0;\n // Single-flight guard for start(): true between entry and the moment capture\n // is set up (or the attempt aborts), so a second start can't race the first.\n let startInFlight = false;\n\n function setPending(delta: number): void {\n pending += delta;\n const next = pending > 0;\n if (transcribing !== next) {\n transcribing = next;\n emit();\n }\n }\n\n function stopAvailabilityPoll(): void {\n if (availabilityPollHandle !== null) {\n window.clearInterval(availabilityPollHandle);\n availabilityPollHandle = null;\n }\n }\n\n async function refreshAvailability(): Promise<void> {\n let status: { ready: boolean; downloading: boolean };\n try {\n status = await transport.getStatus();\n } catch {\n setAvailable(false);\n stopAvailabilityPoll();\n return;\n }\n setAvailable(status.ready);\n if (status.downloading) {\n if (availabilityPollHandle === null) {\n availabilityPollHandle = window.setInterval(() => {\n void refreshAvailability();\n }, AVAILABILITY_POLL_MS);\n }\n } else {\n stopAvailabilityPoll();\n }\n }\n\n async function sendSegment(blob: Blob, gen: number): Promise<void> {\n if (gen !== generation) return;\n try {\n const dataUrl = await blobToDataUrl(blob);\n const result = await transport.transcribe(dataUrl, language());\n if (gen !== generation) return;\n const text = result.text.trim();\n if (text.length === 0) callbacks.onEmpty?.();\n else callbacks.onTranscript(text);\n } catch (err) {\n // Generation-guard the failure path too: a send rejected after stop()/\n // session change belongs to a session the user already left.\n if (gen === generation) callbacks.onError?.(toMessage(err));\n }\n }\n\n // Serialize sends so transcripts append in capture order; `pending` keeps\n // `transcribing` true from enqueue until the send resolves.\n function enqueue(blob: Blob, gen: number): void {\n setPending(1);\n queue = queue\n .then(() => sendSegment(blob, gen))\n .catch(() => undefined)\n .finally(() => setPending(-1));\n }\n\n function containerType(): string {\n return mimeType.split(\";\")[0] || \"audio/webm\";\n }\n\n function onSegmentStop(): void {\n const hadSpeech = segmentHasSpeech;\n const gen = segmentGeneration;\n const blob = new Blob(chunks, { type: containerType() });\n if (listening) startRecorder();\n if (hadSpeech && blob.size > 0 && gen === generation) enqueue(blob, gen);\n }\n\n function startRecorder(): void {\n if (!stream) return;\n chunks = [];\n segmentHasSpeech = false;\n silenceStart = null;\n segmentStart = Date.now();\n segmentGeneration = generation;\n recorder = new MediaRecorder(stream, { mimeType });\n recorder.ondataavailable = (event) => {\n if (event.data.size > 0) chunks.push(event.data);\n };\n recorder.onstop = onSegmentStop;\n recorder.start();\n }\n\n function cutSegment(): void {\n if (recorder && recorder.state === \"recording\") recorder.stop();\n }\n\n function monitorTick(): void {\n if (!analyser) return;\n analyser.getFloatTimeDomainData(vadBuffer);\n const rms = computeRms(vadBuffer);\n const now = Date.now();\n if (rms > SPEECH_RMS) {\n segmentHasSpeech = true;\n silenceStart = null;\n } else if (segmentHasSpeech) {\n if (silenceStart === null) silenceStart = now;\n else if (now - silenceStart > SILENCE_MS) cutSegment();\n }\n if (segmentHasSpeech && now - segmentStart > MAX_SEGMENT_MS) cutSegment();\n }\n\n async function start(): Promise<boolean> {\n // Single-flight: `listening` only flips true AFTER getUserMedia resolves, so\n // a benign skip returns true (a start is already active / in progress).\n if (startInFlight || listening) return true;\n startInFlight = true;\n const startGen = generation;\n try {\n mimeType = pickRecorderMime() ?? \"\";\n if (!mimeType || !navigator.mediaDevices?.getUserMedia) {\n callbacks.onError?.(\"unsupported\");\n return false;\n }\n let acquired: MediaStream;\n try {\n acquired = await navigator.mediaDevices.getUserMedia({ audio: true });\n } catch {\n callbacks.onError?.(\"permission-denied\");\n return false;\n }\n // stop() bumps the generation; if it fired while we awaited permission\n // this start is stale — release the mic and abort.\n if (startGen !== generation) {\n acquired.getTracks().forEach((track) => track.stop());\n return false;\n }\n stream = acquired;\n audioCtx = new AudioContext();\n analyser = audioCtx.createAnalyser();\n analyser.fftSize = 2048;\n vadBuffer = new Float32Array(analyser.fftSize);\n audioCtx.createMediaStreamSource(stream).connect(analyser);\n setListening(true);\n startRecorder();\n monitorHandle = window.setInterval(monitorTick, MONITOR_INTERVAL_MS);\n return true;\n } finally {\n startInFlight = false;\n }\n }\n\n function stop(): void {\n // Bump the generation so any in-flight/queued segment is dropped rather than\n // applied after the user stops or switches sessions.\n generation += 1;\n setListening(false);\n if (monitorHandle !== null) {\n window.clearInterval(monitorHandle);\n monitorHandle = null;\n }\n if (recorder && recorder.state === \"recording\") recorder.stop();\n recorder = null;\n if (audioCtx) {\n audioCtx.close().catch(() => undefined);\n audioCtx = null;\n }\n analyser = null;\n stream?.getTracks().forEach((track) => track.stop());\n stream = null;\n }\n\n function dispose(): void {\n stopAvailabilityPoll();\n stop();\n }\n\n return { refreshAvailability, start, stop, dispose };\n}\n"],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /** ffmpeg args to decode any input to 16 kHz mono signed-16-bit WAV. Pure +
2
+ * exported for unit tests. */
3
+ export declare function buildWav16kArgs(inputPath: string, outputPath: string): string[];
4
+ /** Convert `inputPath` to a 16 kHz mono WAV at `outputPath`. Throws on ffmpeg
5
+ * failure or timeout. */
6
+ export declare function convertToWav16k(inputPath: string, outputPath: string, ffmpegBinary?: string): Promise<void>;