pi-meta-oauth 0.2.3 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -3
- package/extensions/media.ts +1446 -0
- package/extensions/meta.ts +43 -8
- package/extensions/voice/windows-audio.cs +281 -0
- package/extensions/voice/windows-audio.ps1 +195 -0
- package/extensions/voice.ts +153 -12
- package/package.json +2 -1
package/extensions/voice.ts
CHANGED
|
@@ -25,6 +25,10 @@ const HELPER_SOURCE = join(HELPER_DIR, "macos-audio.swift");
|
|
|
25
25
|
const HELPER_PLIST = join(HELPER_DIR, "Info.plist");
|
|
26
26
|
const HELPER_ENTITLEMENTS = join(HELPER_DIR, "Entitlements.plist");
|
|
27
27
|
const HELPER_BINARY = join(AGENT_DIR, "bin/pi-meta-oauth-voice-v1");
|
|
28
|
+
// Windows helpers
|
|
29
|
+
const WINDOWS_HELPER_SOURCE = join(HELPER_DIR, "windows-audio.cs");
|
|
30
|
+
const WINDOWS_HELPER_PS1 = join(HELPER_DIR, "windows-audio.ps1");
|
|
31
|
+
const WINDOWS_HELPER_BINARY = join(AGENT_DIR, "bin/pi-meta-oauth-voice-v1.exe");
|
|
28
32
|
|
|
29
33
|
const DEFAULT_ASR_ENDPOINT =
|
|
30
34
|
"wss://shortwave.facebook.com/voyager/v1/asr/duplex";
|
|
@@ -45,6 +49,24 @@ type AsrTranscript = {
|
|
|
45
49
|
final?: unknown;
|
|
46
50
|
};
|
|
47
51
|
|
|
52
|
+
function isMacOS(): boolean {
|
|
53
|
+
return platform() === "darwin";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function isWindows(): boolean {
|
|
57
|
+
return platform() === "win32";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function isSupportedPlatform(): boolean {
|
|
61
|
+
return isMacOS() || isWindows();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function supportedPlatformLabel(): string {
|
|
65
|
+
if (isMacOS()) return "macOS";
|
|
66
|
+
if (isWindows()) return "Windows";
|
|
67
|
+
return `${platform()}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
48
70
|
function loadSettings(): Settings {
|
|
49
71
|
if (existsSync(SETTINGS_FILE)) {
|
|
50
72
|
try {
|
|
@@ -56,7 +78,7 @@ function loadSettings(): Settings {
|
|
|
56
78
|
// Fall through to the platform default.
|
|
57
79
|
}
|
|
58
80
|
}
|
|
59
|
-
return { enabled:
|
|
81
|
+
return { enabled: isSupportedPlatform() };
|
|
60
82
|
}
|
|
61
83
|
|
|
62
84
|
function saveSettings(settings: Settings): void {
|
|
@@ -206,10 +228,17 @@ function helperNeedsBuild(): boolean {
|
|
|
206
228
|
);
|
|
207
229
|
}
|
|
208
230
|
|
|
231
|
+
function windowsHelperNeedsBuild(): boolean {
|
|
232
|
+
if (!existsSync(WINDOWS_HELPER_BINARY)) return true;
|
|
233
|
+
if (!existsSync(WINDOWS_HELPER_SOURCE)) return false;
|
|
234
|
+
const binaryTime = statSync(WINDOWS_HELPER_BINARY).mtimeMs;
|
|
235
|
+
return statSync(WINDOWS_HELPER_SOURCE).mtimeMs > binaryTime;
|
|
236
|
+
}
|
|
237
|
+
|
|
209
238
|
async function ensureMacOSHelper(): Promise<string> {
|
|
210
|
-
if (
|
|
239
|
+
if (!isMacOS()) {
|
|
211
240
|
throw new Error(
|
|
212
|
-
"Muse-style voice input is currently available only on macOS",
|
|
241
|
+
"Muse-style voice input is currently available only on macOS and Windows",
|
|
213
242
|
);
|
|
214
243
|
}
|
|
215
244
|
if (
|
|
@@ -249,6 +278,109 @@ async function ensureMacOSHelper(): Promise<string> {
|
|
|
249
278
|
return HELPER_BINARY;
|
|
250
279
|
}
|
|
251
280
|
|
|
281
|
+
async function tryCompileWindowsHelper(): Promise<boolean> {
|
|
282
|
+
if (!existsSync(WINDOWS_HELPER_SOURCE)) return false;
|
|
283
|
+
mkdirSync(join(AGENT_DIR, "bin"), { recursive: true });
|
|
284
|
+
|
|
285
|
+
const candidates: string[] = [
|
|
286
|
+
"csc",
|
|
287
|
+
"csc.exe",
|
|
288
|
+
"C:\\Windows\\Microsoft.NET\\Framework\\v4.0.30319\\csc.exe",
|
|
289
|
+
"C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\csc.exe",
|
|
290
|
+
];
|
|
291
|
+
// Also try where-found csc via PATH expansion if on Windows
|
|
292
|
+
for (const c of candidates) {
|
|
293
|
+
try {
|
|
294
|
+
await runProcess(c, [
|
|
295
|
+
"/nologo",
|
|
296
|
+
"/target:exe",
|
|
297
|
+
`/out:${WINDOWS_HELPER_BINARY}`,
|
|
298
|
+
WINDOWS_HELPER_SOURCE,
|
|
299
|
+
]);
|
|
300
|
+
if (existsSync(WINDOWS_HELPER_BINARY)) return true;
|
|
301
|
+
} catch {
|
|
302
|
+
// try next candidate
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function findPowerShellCommand(): string {
|
|
309
|
+
// Prefer pwsh (PowerShell 7) if present, otherwise Windows PowerShell
|
|
310
|
+
// We check filesystem first to avoid spawning during hot path
|
|
311
|
+
if (existsSync("C:\\Program Files\\PowerShell\\7\\pwsh.exe"))
|
|
312
|
+
return "C:\\Program Files\\PowerShell\\7\\pwsh.exe";
|
|
313
|
+
if (existsSync("C:\\Program Files\\PowerShell\\7\\pwsh")) return "pwsh";
|
|
314
|
+
// Windows PowerShell is inbox in System32
|
|
315
|
+
if (
|
|
316
|
+
existsSync("C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe")
|
|
317
|
+
)
|
|
318
|
+
return "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe";
|
|
319
|
+
return "powershell.exe";
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async function ensureWindowsHelper(): Promise<{
|
|
323
|
+
command: string;
|
|
324
|
+
args: string[];
|
|
325
|
+
}> {
|
|
326
|
+
if (!isWindows()) {
|
|
327
|
+
throw new Error(
|
|
328
|
+
"Muse-style voice input is currently available only on macOS and Windows",
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
if (!existsSync(WINDOWS_HELPER_SOURCE) && !existsSync(WINDOWS_HELPER_PS1)) {
|
|
332
|
+
throw new Error("The Windows microphone helper sources are incomplete");
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// If compiled exe exists and is fresh, use it
|
|
336
|
+
if (existsSync(WINDOWS_HELPER_BINARY) && !windowsHelperNeedsBuild()) {
|
|
337
|
+
return { command: WINDOWS_HELPER_BINARY, args: [] };
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Try to compile exe for better performance
|
|
341
|
+
if (existsSync(WINDOWS_HELPER_SOURCE)) {
|
|
342
|
+
if (!windowsHelperNeedsBuild() && existsSync(WINDOWS_HELPER_BINARY)) {
|
|
343
|
+
return { command: WINDOWS_HELPER_BINARY, args: [] };
|
|
344
|
+
}
|
|
345
|
+
const compiled = await tryCompileWindowsHelper();
|
|
346
|
+
if (compiled && existsSync(WINDOWS_HELPER_BINARY)) {
|
|
347
|
+
return { command: WINDOWS_HELPER_BINARY, args: [] };
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Fallback to PowerShell script (no compilation needed)
|
|
352
|
+
if (existsSync(WINDOWS_HELPER_PS1)) {
|
|
353
|
+
const ps = findPowerShellCommand();
|
|
354
|
+
return {
|
|
355
|
+
command: ps,
|
|
356
|
+
args: [
|
|
357
|
+
"-NoProfile",
|
|
358
|
+
"-ExecutionPolicy",
|
|
359
|
+
"Bypass",
|
|
360
|
+
"-File",
|
|
361
|
+
WINDOWS_HELPER_PS1,
|
|
362
|
+
],
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
throw new Error(
|
|
367
|
+
"Windows voice helper could not be prepared — no compiled binary and no PowerShell fallback found",
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
async function ensureHelper(): Promise<{ command: string; args: string[] }> {
|
|
372
|
+
if (isMacOS()) {
|
|
373
|
+
const bin = await ensureMacOSHelper();
|
|
374
|
+
return { command: bin, args: [] };
|
|
375
|
+
}
|
|
376
|
+
if (isWindows()) {
|
|
377
|
+
return ensureWindowsHelper();
|
|
378
|
+
}
|
|
379
|
+
throw new Error(
|
|
380
|
+
`Muse-style voice input is currently available only on macOS and Windows (current: ${supportedPlatformLabel()})`,
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
|
|
252
384
|
class MetaVoiceController {
|
|
253
385
|
private readonly pi: ExtensionAPI;
|
|
254
386
|
private readonly settings = loadSettings();
|
|
@@ -339,9 +471,9 @@ class MetaVoiceController {
|
|
|
339
471
|
}
|
|
340
472
|
|
|
341
473
|
private enable(ctx: ExtensionContext): void {
|
|
342
|
-
if (
|
|
474
|
+
if (!isSupportedPlatform()) {
|
|
343
475
|
ctx.ui.notify(
|
|
344
|
-
|
|
476
|
+
`Meta voice input is currently available only on macOS and Windows (current: ${supportedPlatformLabel()})`,
|
|
345
477
|
"error",
|
|
346
478
|
);
|
|
347
479
|
return;
|
|
@@ -407,8 +539,8 @@ class MetaVoiceController {
|
|
|
407
539
|
this.renderTranscript(ctx);
|
|
408
540
|
|
|
409
541
|
try {
|
|
410
|
-
const [
|
|
411
|
-
|
|
542
|
+
const [helperInfo, apiKey] = await Promise.all([
|
|
543
|
+
ensureHelper(),
|
|
412
544
|
ctx.modelRegistry.getApiKeyForProvider(META_PROVIDER_ID),
|
|
413
545
|
]);
|
|
414
546
|
if (currentGeneration !== this.generation) return;
|
|
@@ -421,7 +553,12 @@ class MetaVoiceController {
|
|
|
421
553
|
}
|
|
422
554
|
|
|
423
555
|
this.connectSocket(ctx, apiKey, currentGeneration);
|
|
424
|
-
this.spawnHelper(
|
|
556
|
+
this.spawnHelper(
|
|
557
|
+
ctx,
|
|
558
|
+
helperInfo.command,
|
|
559
|
+
helperInfo.args,
|
|
560
|
+
currentGeneration,
|
|
561
|
+
);
|
|
425
562
|
this.renderInterval = setInterval(() => {
|
|
426
563
|
this.renderTranscript(ctx);
|
|
427
564
|
this.renderRecordingStatus(ctx);
|
|
@@ -558,10 +695,14 @@ class MetaVoiceController {
|
|
|
558
695
|
|
|
559
696
|
private spawnHelper(
|
|
560
697
|
ctx: ExtensionContext,
|
|
561
|
-
|
|
698
|
+
command: string,
|
|
699
|
+
args: string[],
|
|
562
700
|
currentGeneration: number,
|
|
563
701
|
): void {
|
|
564
|
-
const helper = spawn(
|
|
702
|
+
const helper = spawn(command, args, {
|
|
703
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
704
|
+
windowsHide: true,
|
|
705
|
+
});
|
|
565
706
|
this.helper = helper;
|
|
566
707
|
helper.stdout?.on("data", (chunk: Buffer) =>
|
|
567
708
|
this.parseHelperOutput(ctx, chunk, currentGeneration),
|
|
@@ -603,7 +744,7 @@ class MetaVoiceController {
|
|
|
603
744
|
try {
|
|
604
745
|
this.handleHelperEvent(ctx, JSON.parse(line) as HelperEvent);
|
|
605
746
|
} catch {
|
|
606
|
-
// Ignore non-protocol output from
|
|
747
|
+
// Ignore non-protocol output from audio frameworks.
|
|
607
748
|
}
|
|
608
749
|
}
|
|
609
750
|
}
|
|
@@ -636,7 +777,7 @@ class MetaVoiceController {
|
|
|
636
777
|
new Error(
|
|
637
778
|
typeof event.message === "string"
|
|
638
779
|
? event.message
|
|
639
|
-
: "
|
|
780
|
+
: "microphone capture failed",
|
|
640
781
|
),
|
|
641
782
|
);
|
|
642
783
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-meta-oauth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Meta Model API OAuth provider and Muse-style voice dictation for pi",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"pi": {
|
|
30
30
|
"extensions": [
|
|
31
31
|
"./extensions/meta.ts",
|
|
32
|
+
"./extensions/media.ts",
|
|
32
33
|
"./extensions/voice.ts"
|
|
33
34
|
],
|
|
34
35
|
"image": "https://raw.githubusercontent.com/BlockedPath/pi-meta-oauth/main/preview.png"
|