@iaforged/context-code 1.2.12 → 1.3.5

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.
@@ -6,6 +6,7 @@ import { createOrchestrationMessage, createOrchestrationRun, createOrchestration
6
6
  import { activityManager } from '../../utils/activityManager.js';
7
7
  import { manuallyExtractSessionMemory } from '../../services/sessionMemory/sessionMemory.js';
8
8
  import { createUserMessage, createAssistantMessage } from '../../utils/messages.js';
9
+ import { logForDebugging } from '../../utils/debug.js';
9
10
  function parseArgs(args) {
10
11
  const trimmed = args.trim();
11
12
  if (!trimmed) {
@@ -5,6 +5,8 @@ import { getTerminalFocusState, subscribeTerminalFocus, } from '../ink/terminal-
5
5
  import { getFeatureValue_CACHED_MAY_BE_STALE } from '../services/analytics/growthbook.js';
6
6
  import { generateAwaySummary } from '../services/awaySummary.js';
7
7
  import { createAwaySummaryMessage } from '../utils/messages.js';
8
+ import { logForDebugging } from '../utils/debug.js';
9
+ import { getGlobalConfig } from '../utils/config.js';
8
10
  function hasSummarySinceLastUserTurn(messages) {
9
11
  for (let i = messages.length - 1; i >= 0; i--) {
10
12
  const m = messages[i];
@@ -17,7 +17,7 @@ export function useDoublePress(setPending, onDoublePress, onFirstPress) {
17
17
  clearTimeoutSafe();
18
18
  };
19
19
  }, [clearTimeoutSafe]);
20
- return useCallback(() => {
20
+ return useCallback((...args) => {
21
21
  const now = Date.now();
22
22
  const timeSinceLastPress = now - lastPressRef.current;
23
23
  const isDoublePress = timeSinceLastPress <= DOUBLE_PRESS_TIMEOUT_MS &&
@@ -25,19 +25,19 @@ export function useDoublePress(setPending, onDoublePress, onFirstPress) {
25
25
  if (isDoublePress) {
26
26
  // Double press detected
27
27
  clearTimeoutSafe();
28
- setPending(false);
29
- onDoublePress();
28
+ setPending(false, ...args);
29
+ onDoublePress(...args);
30
30
  }
31
31
  else {
32
32
  // First press
33
- onFirstPress?.();
34
- setPending(true);
33
+ onFirstPress?.(...args);
34
+ setPending(true, ...args);
35
35
  // Clear any existing timeout and set new one
36
36
  clearTimeoutSafe();
37
- timeoutRef.current = setTimeout((setPending, timeoutRef) => {
38
- setPending(false);
37
+ timeoutRef.current = setTimeout((setPending, timeoutRef, ...args) => {
38
+ setPending(false, ...args);
39
39
  timeoutRef.current = undefined;
40
- }, DOUBLE_PRESS_TIMEOUT_MS, setPending, timeoutRef);
40
+ }, DOUBLE_PRESS_TIMEOUT_MS, setPending, timeoutRef, ...args);
41
41
  }
42
42
  lastPressRef.current = now;
43
43
  }, [setPending, onDoublePress, onFirstPress, clearTimeoutSafe]);
@@ -1,3 +1,4 @@
1
+ import { useRef, useState } from 'react';
1
2
  import { isInputModeCharacter } from '../components/PromptInput/inputModes.js';
2
3
  import { useNotifications } from '../context/notifications.js';
3
4
  import stripAnsi from 'strip-ansi';
@@ -8,7 +9,7 @@ import { env } from '../utils/env.js';
8
9
  import { isFullscreenEnvEnabled } from '../utils/fullscreen.js';
9
10
  import { isModifierPressed, prewarmModifiers } from '../utils/modifiers.js';
10
11
  import { useDoublePress } from './useDoublePress.js';
11
- const NOOP_HANDLER = () => { };
12
+ const NOOP_HANDLER = (_c, _i) => { };
12
13
  function insertPlainText(cursor, input) {
13
14
  const text = stripAnsi(input)
14
15
  // eslint-disable-next-line custom-rules/no-lookbehind-regex -- .replace(re, str) on 1-2 char keystrokes: no-match returns same string (Object.is), regex never runs
@@ -21,8 +22,8 @@ function insertPlainText(cursor, input) {
21
22
  }
22
23
  function mapInput(input_map) {
23
24
  const map = new Map(input_map);
24
- return function (input) {
25
- return (map.get(input) ?? NOOP_HANDLER)(input);
25
+ return function (cursor, input) {
26
+ return (map.get(input) ?? NOOP_HANDLER)(cursor, input);
26
27
  };
27
28
  }
28
29
  export function useTextInput({ value: originalValue, onChange, onSubmit, onExit, onExitMessage, onHistoryUp, onHistoryDown, onHistoryReset, onClearInput, mask = '', multiline = false, cursorChar, invert, columns, onImagePaste: _onImagePaste, disableCursorMovementForUpDownKeys = false, disableEscapeDoublePress = false, maxVisibleLines, externalOffset, onOffsetChange, inputFilter, inlineGhostText, dim, }) {
@@ -32,12 +33,25 @@ export function useTextInput({ value: originalValue, onChange, onSubmit, onExit,
32
33
  }
33
34
  const offset = externalOffset;
34
35
  const setOffset = onOffsetChange;
36
+ // Maintain synchronous internal state to prevent character loss during rapid typing.
37
+ // React re-renders can be slower than keystrokes in a complex TUI.
38
+ const lastProcessedValueRef = useRef(originalValue);
39
+ const lastProcessedOffsetRef = useRef(externalOffset);
40
+ // Sync internal state with props when they change externally (history, undo, etc.)
41
+ const [lastPropValue, setLastPropValue] = useState(originalValue);
42
+ const [lastPropOffset, setLastPropOffset] = useState(externalOffset);
43
+ if (originalValue !== lastPropValue || externalOffset !== lastPropOffset) {
44
+ lastProcessedValueRef.current = originalValue;
45
+ lastProcessedOffsetRef.current = externalOffset;
46
+ setLastPropValue(originalValue);
47
+ setLastPropOffset(externalOffset);
48
+ }
35
49
  const cursor = Cursor.fromText(originalValue, columns, offset);
36
50
  const { addNotification, removeNotification } = useNotifications();
37
- const handleCtrlC = useDoublePress(show => {
51
+ const handleCtrlC = useDoublePress((show, _cursor) => {
38
52
  onExitMessage?.(show, 'Ctrl-C');
39
- }, () => onExit?.(), () => {
40
- if (originalValue) {
53
+ }, (_cursor) => onExit?.(), (cursor) => {
54
+ if (cursor.text) {
41
55
  onChange('');
42
56
  setOffset(0);
43
57
  onHistoryReset?.();
@@ -47,8 +61,8 @@ export function useTextInput({ value: originalValue, onChange, onSubmit, onExit,
47
61
  // It's a text-level double-press escape for clearing input, not an action-level keybinding.
48
62
  // Double-press Esc clears the input and saves to history - this is text editing behavior,
49
63
  // not dialog dismissal, and needs the double-press safety mechanism.
50
- const handleEscape = useDoublePress((show) => {
51
- if (!originalValue || !show) {
64
+ const handleEscape = useDoublePress((show, cursor) => {
65
+ if (!cursor.text || !show) {
52
66
  return;
53
67
  }
54
68
  addNotification({
@@ -57,57 +71,57 @@ export function useTextInput({ value: originalValue, onChange, onSubmit, onExit,
57
71
  priority: 'immediate',
58
72
  timeoutMs: 1000,
59
73
  });
60
- }, () => {
74
+ }, (_show, cursor) => {
61
75
  // Remove the "Esc again to clear" notification immediately
62
76
  removeNotification('escape-again-to-clear');
63
77
  onClearInput?.();
64
- if (originalValue) {
78
+ if (cursor.text) {
65
79
  // Track double-escape usage for feature discovery
66
80
  // Save to history before clearing
67
- if (originalValue.trim() !== '') {
68
- addToHistory(originalValue);
81
+ if (cursor.text.trim() !== '') {
82
+ addToHistory(cursor.text);
69
83
  }
70
84
  onChange('');
71
85
  setOffset(0);
72
86
  onHistoryReset?.();
73
87
  }
74
88
  });
75
- const handleEmptyCtrlD = useDoublePress(show => {
76
- if (originalValue !== '') {
89
+ const handleEmptyCtrlD = useDoublePress((show, cursor) => {
90
+ if (cursor.text !== '') {
77
91
  return;
78
92
  }
79
93
  onExitMessage?.(show, 'Ctrl-D');
80
- }, () => {
81
- if (originalValue !== '') {
94
+ }, (_show, cursor) => {
95
+ if (cursor.text !== '') {
82
96
  return;
83
97
  }
84
98
  onExit?.();
85
99
  });
86
- function handleCtrlD() {
100
+ function handleCtrlD(cursor) {
87
101
  if (cursor.text === '') {
88
102
  // When input is empty, handle double-press
89
- handleEmptyCtrlD();
103
+ handleEmptyCtrlD(cursor);
90
104
  return cursor;
91
105
  }
92
106
  // When input is not empty, delete forward like iPython
93
107
  return cursor.del();
94
108
  }
95
- function killToLineEnd() {
109
+ function killToLineEnd(cursor) {
96
110
  const { cursor: newCursor, killed } = cursor.deleteToLineEnd();
97
111
  pushToKillRing(killed, 'append');
98
112
  return newCursor;
99
113
  }
100
- function killToLineStart() {
114
+ function killToLineStart(cursor) {
101
115
  const { cursor: newCursor, killed } = cursor.deleteToLineStart();
102
116
  pushToKillRing(killed, 'prepend');
103
117
  return newCursor;
104
118
  }
105
- function killWordBefore() {
119
+ function killWordBefore(cursor) {
106
120
  const { cursor: newCursor, killed } = cursor.deleteWordBefore();
107
121
  pushToKillRing(killed, 'prepend');
108
122
  return newCursor;
109
123
  }
110
- function yank() {
124
+ function yank(cursor) {
111
125
  const text = getLastKill();
112
126
  if (text.length > 0) {
113
127
  const startOffset = cursor.offset;
@@ -117,7 +131,7 @@ export function useTextInput({ value: originalValue, onChange, onSubmit, onExit,
117
131
  }
118
132
  return cursor;
119
133
  }
120
- function handleYankPop() {
134
+ function handleYankPop(cursor) {
121
135
  const popResult = yankPop();
122
136
  if (!popResult) {
123
137
  return cursor;
@@ -132,27 +146,27 @@ export function useTextInput({ value: originalValue, onChange, onSubmit, onExit,
132
146
  return Cursor.fromText(newText, columns, newOffset);
133
147
  }
134
148
  const handleCtrl = mapInput([
135
- ['a', () => cursor.startOfLine()],
136
- ['b', () => cursor.left()],
137
- ['c', handleCtrlC],
138
- ['d', handleCtrlD],
139
- ['e', () => cursor.endOfLine()],
140
- ['f', () => cursor.right()],
141
- ['h', () => cursor.deleteTokenBefore() ?? cursor.backspace()],
142
- ['k', killToLineEnd],
143
- ['n', () => downOrHistoryDown()],
144
- ['p', () => upOrHistoryUp()],
145
- ['u', killToLineStart],
146
- ['w', killWordBefore],
147
- ['y', yank],
149
+ ['a', cursor => cursor.startOfLine()],
150
+ ['b', cursor => cursor.left()],
151
+ ['c', (cursor, _input) => handleCtrlC(cursor)],
152
+ ['d', (cursor, _input) => handleCtrlD(cursor)],
153
+ ['e', cursor => cursor.endOfLine()],
154
+ ['f', cursor => cursor.right()],
155
+ ['h', cursor => cursor.deleteTokenBefore() ?? cursor.backspace()],
156
+ ['k', (cursor, _input) => killToLineEnd(cursor)],
157
+ ['n', (cursor, _input) => downOrHistoryDown(cursor)],
158
+ ['p', (cursor, _input) => upOrHistoryUp(cursor)],
159
+ ['u', (cursor, _input) => killToLineStart(cursor)],
160
+ ['w', (cursor, _input) => killWordBefore(cursor)],
161
+ ['y', (cursor, _input) => yank(cursor)],
148
162
  ]);
149
163
  const handleMeta = mapInput([
150
- ['b', () => cursor.prevWord()],
151
- ['f', () => cursor.nextWord()],
152
- ['d', () => cursor.deleteWordAfter()],
153
- ['y', handleYankPop],
164
+ ['b', cursor => cursor.prevWord()],
165
+ ['f', cursor => cursor.nextWord()],
166
+ ['d', cursor => cursor.deleteWordAfter()],
167
+ ['y', (cursor, _input) => handleYankPop(cursor)],
154
168
  ]);
155
- function handleEnter(key) {
169
+ function handleEnter(cursor, key) {
156
170
  if (multiline &&
157
171
  cursor.offset > 0 &&
158
172
  cursor.text[cursor.offset - 1] === '\\') {
@@ -169,9 +183,9 @@ export function useTextInput({ value: originalValue, onChange, onSubmit, onExit,
169
183
  if (env.terminal === 'Apple_Terminal' && isModifierPressed('shift')) {
170
184
  return cursor.insert('\n');
171
185
  }
172
- onSubmit?.(originalValue);
186
+ onSubmit?.(cursor.text);
173
187
  }
174
- function upOrHistoryUp() {
188
+ function upOrHistoryUp(cursor) {
175
189
  if (disableCursorMovementForUpDownKeys) {
176
190
  onHistoryUp?.();
177
191
  return cursor;
@@ -193,7 +207,7 @@ export function useTextInput({ value: originalValue, onChange, onSubmit, onExit,
193
207
  onHistoryUp?.();
194
208
  return cursor;
195
209
  }
196
- function downOrHistoryDown() {
210
+ function downOrHistoryDown(cursor) {
197
211
  if (disableCursorMovementForUpDownKeys) {
198
212
  onHistoryDown?.();
199
213
  return cursor;
@@ -218,7 +232,7 @@ export function useTextInput({ value: originalValue, onChange, onSubmit, onExit,
218
232
  function mapKey(key) {
219
233
  switch (true) {
220
234
  case key.escape:
221
- return () => {
235
+ return (cursor) => {
222
236
  // Skip when a keybinding context (e.g. Autocomplete) owns escape.
223
237
  // useKeybindings can't shield us via stopImmediatePropagation —
224
238
  // BaseTextInput's useInput registers first (child effects fire
@@ -226,38 +240,40 @@ export function useTextInput({ value: originalValue, onChange, onSubmit, onExit,
226
240
  // time the keybinding's handler stops propagation.
227
241
  if (disableEscapeDoublePress)
228
242
  return cursor;
229
- handleEscape();
243
+ handleEscape(cursor);
230
244
  // Return the current cursor unchanged - handleEscape manages state internally
231
245
  return cursor;
232
246
  };
233
247
  case key.leftArrow && (key.ctrl || key.meta || key.fn):
234
- return () => cursor.prevWord();
248
+ return (cursor) => cursor.prevWord();
235
249
  case key.rightArrow && (key.ctrl || key.meta || key.fn):
236
- return () => cursor.nextWord();
250
+ return (cursor) => cursor.nextWord();
237
251
  case key.backspace:
238
252
  return key.meta || key.ctrl
239
- ? killWordBefore
240
- : () => cursor.deleteTokenBefore() ?? cursor.backspace();
253
+ ? (cursor) => killWordBefore(cursor)
254
+ : (cursor) => cursor.deleteTokenBefore() ?? cursor.backspace();
241
255
  case key.delete:
242
- return key.meta ? killToLineEnd : () => cursor.del();
256
+ return key.meta
257
+ ? (cursor) => killToLineEnd(cursor)
258
+ : (cursor) => cursor.del();
243
259
  case key.ctrl:
244
260
  return handleCtrl;
245
261
  case key.home:
246
- return () => cursor.startOfLine();
262
+ return (cursor) => cursor.startOfLine();
247
263
  case key.end:
248
- return () => cursor.endOfLine();
264
+ return (cursor) => cursor.endOfLine();
249
265
  case key.pageDown:
250
266
  // In fullscreen mode, PgUp/PgDn scroll the message viewport instead
251
267
  // of moving the cursor — no-op here, ScrollKeybindingHandler handles it.
252
268
  if (isFullscreenEnvEnabled()) {
253
269
  return NOOP_HANDLER;
254
270
  }
255
- return () => cursor.endOfLine();
271
+ return (cursor) => cursor.endOfLine();
256
272
  case key.pageUp:
257
273
  if (isFullscreenEnvEnabled()) {
258
274
  return NOOP_HANDLER;
259
275
  }
260
- return () => cursor.startOfLine();
276
+ return (cursor) => cursor.startOfLine();
261
277
  case key.wheelUp:
262
278
  case key.wheelDown:
263
279
  // Mouse wheel events only exist when fullscreen mouse tracking is on.
@@ -266,21 +282,21 @@ export function useTextInput({ value: originalValue, onChange, onSubmit, onExit,
266
282
  return NOOP_HANDLER;
267
283
  case key.return:
268
284
  // Must come before key.meta so Option+Return inserts newline
269
- return () => handleEnter(key);
285
+ return (cursor) => handleEnter(cursor, key);
270
286
  case key.meta:
271
287
  return handleMeta;
272
288
  case key.tab:
273
- return () => cursor;
289
+ return (cursor) => cursor;
274
290
  case key.upArrow && !key.shift:
275
- return upOrHistoryUp;
291
+ return (cursor) => upOrHistoryUp(cursor);
276
292
  case key.downArrow && !key.shift:
277
- return downOrHistoryDown;
293
+ return (cursor) => downOrHistoryDown(cursor);
278
294
  case key.leftArrow:
279
- return () => cursor.left();
295
+ return (cursor) => cursor.left();
280
296
  case key.rightArrow:
281
- return () => cursor.right();
297
+ return (cursor) => cursor.right();
282
298
  default: {
283
- return function (input) {
299
+ return function (cursor, input) {
284
300
  switch (true) {
285
301
  // Home key
286
302
  case input === '\x1b[H' || input === '\x1b[1~':
@@ -327,30 +343,34 @@ export function useTextInput({ value: originalValue, onChange, onSubmit, onExit,
327
343
  if (filteredInput === '' && input !== '') {
328
344
  return;
329
345
  }
346
+ // Get the current synchronous state
347
+ const currentCursor = Cursor.fromText(lastProcessedValueRef.current, columns, lastProcessedOffsetRef.current);
330
348
  // In SSH/tmux or slow terminals, raw DEL characters can be coalesced with
331
349
  // printable input. Process the chunk sequentially so DEL only removes the
332
350
  // preceding character and the rest of the packet still gets inserted.
333
351
  if (!key.backspace && !key.delete && filteredInput.includes('\x7f')) {
334
- let currentCursor = cursor;
352
+ let activeCursor = currentCursor;
335
353
  let segmentStart = 0;
336
354
  for (let i = 0; i < filteredInput.length; i++) {
337
355
  if (filteredInput[i] !== '\x7f') {
338
356
  continue;
339
357
  }
340
358
  if (segmentStart < i) {
341
- currentCursor = insertPlainText(currentCursor, filteredInput.slice(segmentStart, i));
359
+ activeCursor = insertPlainText(activeCursor, filteredInput.slice(segmentStart, i));
342
360
  }
343
- currentCursor = currentCursor.backspace();
361
+ activeCursor = activeCursor.backspace();
344
362
  segmentStart = i + 1;
345
363
  }
346
364
  if (segmentStart < filteredInput.length) {
347
- currentCursor = insertPlainText(currentCursor, filteredInput.slice(segmentStart));
365
+ activeCursor = insertPlainText(activeCursor, filteredInput.slice(segmentStart));
348
366
  }
349
- if (!cursor.equals(currentCursor)) {
350
- if (cursor.text !== currentCursor.text) {
351
- onChange(currentCursor.text);
367
+ if (!currentCursor.equals(activeCursor)) {
368
+ if (currentCursor.text !== activeCursor.text) {
369
+ lastProcessedValueRef.current = activeCursor.text;
370
+ onChange(activeCursor.text);
352
371
  }
353
- setOffset(currentCursor.offset);
372
+ lastProcessedOffsetRef.current = activeCursor.offset;
373
+ setOffset(activeCursor.offset);
354
374
  }
355
375
  resetKillAccumulation();
356
376
  resetYankState();
@@ -364,12 +384,14 @@ export function useTextInput({ value: originalValue, onChange, onSubmit, onExit,
364
384
  if (!isYankKey(key, filteredInput)) {
365
385
  resetYankState();
366
386
  }
367
- const nextCursor = mapKey(key)(filteredInput);
387
+ const nextCursor = mapKey(key)(currentCursor, filteredInput);
368
388
  if (nextCursor) {
369
- if (!cursor.equals(nextCursor)) {
370
- if (cursor.text !== nextCursor.text) {
389
+ if (!currentCursor.equals(nextCursor)) {
390
+ if (currentCursor.text !== nextCursor.text) {
391
+ lastProcessedValueRef.current = nextCursor.text;
371
392
  onChange(nextCursor.text);
372
393
  }
394
+ lastProcessedOffsetRef.current = nextCursor.offset;
373
395
  setOffset(nextCursor.offset);
374
396
  }
375
397
  // SSH-coalesced Enter: on slow links, "o" + Enter can arrive as one
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iaforged/context-code",
3
- "version": "1.2.12",
3
+ "version": "1.3.5",
4
4
  "description": "Context Code es un asistente de desarrollo para la terminal. Puede revisar tu proyecto, editar archivos, ejecutar comandos y apoyarte en tareas reales de programacion.",
5
5
  "author": "Context AI",
6
6
  "license": "MIT",
@@ -1,155 +0,0 @@
1
-
2
- import { createRequire } from 'module'
3
-
4
- const require = createRequire(import.meta.url)
5
-
6
- type AudioCaptureNapi = {
7
- startRecording(
8
- onData: (data: Buffer) => void,
9
- onEnd: () => void,
10
- ): boolean
11
- stopRecording(): void
12
- isRecording(): boolean
13
- startPlayback(sampleRate: number, channels: number): boolean
14
- writePlaybackData(data: Buffer): void
15
- stopPlayback(): void
16
- isPlaying(): boolean
17
- // TCC microphone authorization status (macOS only):
18
- // 0 = notDetermined, 1 = restricted, 2 = denied, 3 = authorized.
19
- // Linux: always returns 3 (authorized) — no system-level microphone permission API.
20
- // Windows: returns 3 (authorized) if registry key absent or allowed,
21
- // 2 (denied) if microphone access is explicitly denied.
22
- microphoneAuthorizationStatus?(): number
23
- }
24
-
25
- let cachedModule: AudioCaptureNapi | null = null
26
- let loadAttempted = false
27
-
28
- function loadModule(): AudioCaptureNapi | null {
29
- if (loadAttempted) {
30
- return cachedModule
31
- }
32
- loadAttempted = true
33
-
34
- // Supported platforms: macOS (darwin), Linux, Windows (win32)
35
- const platform = process.platform
36
- if (platform !== 'darwin' && platform !== 'linux' && platform !== 'win32') {
37
- return null
38
- }
39
-
40
- // Candidate 1: native-embed path (bun compile). AUDIO_CAPTURE_NODE_PATH is
41
- // defined at build time in build-with-plugins.ts for native builds only — the
42
- // define resolves it to the static literal "../../audio-capture.node" so bun
43
- // compile can rewrite it to /$bunfs/root/audio-capture.node. MUST stay a
44
- // direct require(env var) — bun cannot analyze require(variable) from a loop.
45
- if (process.env.AUDIO_CAPTURE_NODE_PATH) {
46
- try {
47
- // eslint-disable-next-line @typescript-eslint/no-require-imports
48
- cachedModule = require(
49
- process.env.AUDIO_CAPTURE_NODE_PATH,
50
- ) as AudioCaptureNapi
51
- return cachedModule
52
- } catch {
53
- // fall through to runtime fallbacks below
54
- }
55
- }
56
-
57
- // Candidates 2/3: npm-install and dev/source layouts. Dynamic require is
58
- // fine here — in bundled output (node --target build) require() resolves at
59
- // runtime relative to cli.js at the package root; in dev it resolves
60
- // relative to this file (vendor/audio-capture-src/index.ts).
61
- const platformDir = `${process.arch}-${platform}`
62
- const fallbacks = [
63
- `./vendor/audio-capture/${platformDir}/audio-capture.node`,
64
- `../audio-capture/${platformDir}/audio-capture.node`,
65
- ]
66
- for (const p of fallbacks) {
67
- try {
68
- // eslint-disable-next-line @typescript-eslint/no-require-imports
69
- cachedModule = require(p) as AudioCaptureNapi
70
- return cachedModule
71
- } catch {
72
- // try next
73
- }
74
- }
75
- return null
76
- }
77
-
78
- export function isNativeAudioAvailable(): boolean {
79
- return loadModule() !== null
80
- }
81
-
82
- export function startNativeRecording(
83
- onData: (data: Buffer) => void,
84
- onEnd: () => void,
85
- ): boolean {
86
- const mod = loadModule()
87
- if (!mod) {
88
- return false
89
- }
90
- return mod.startRecording(onData, onEnd)
91
- }
92
-
93
- export function stopNativeRecording(): void {
94
- const mod = loadModule()
95
- if (!mod) {
96
- return
97
- }
98
- mod.stopRecording()
99
- }
100
-
101
- export function isNativeRecordingActive(): boolean {
102
- const mod = loadModule()
103
- if (!mod) {
104
- return false
105
- }
106
- return mod.isRecording()
107
- }
108
-
109
- export function startNativePlayback(
110
- sampleRate: number,
111
- channels: number,
112
- ): boolean {
113
- const mod = loadModule()
114
- if (!mod) {
115
- return false
116
- }
117
- return mod.startPlayback(sampleRate, channels)
118
- }
119
-
120
- export function writeNativePlaybackData(data: Buffer): void {
121
- const mod = loadModule()
122
- if (!mod) {
123
- return
124
- }
125
- mod.writePlaybackData(data)
126
- }
127
-
128
- export function stopNativePlayback(): void {
129
- const mod = loadModule()
130
- if (!mod) {
131
- return
132
- }
133
- mod.stopPlayback()
134
- }
135
-
136
- export function isNativePlaying(): boolean {
137
- const mod = loadModule()
138
- if (!mod) {
139
- return false
140
- }
141
- return mod.isPlaying()
142
- }
143
-
144
- // Returns the microphone authorization status.
145
- // On macOS, returns the TCC status: 0=notDetermined, 1=restricted, 2=denied, 3=authorized.
146
- // On Linux, always returns 3 (authorized) — no system-level mic permission API.
147
- // On Windows, returns 3 (authorized) if registry key absent or allowed, 2 (denied) if explicitly denied.
148
- // Returns 0 (notDetermined) if the native module is unavailable.
149
- export function microphoneAuthorizationStatus(): number {
150
- const mod = loadModule()
151
- if (!mod || !mod.microphoneAuthorizationStatus) {
152
- return 0
153
- }
154
- return mod.microphoneAuthorizationStatus()
155
- }