pi-meta-oauth 0.2.0 → 0.2.2

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 CHANGED
@@ -2,6 +2,9 @@
2
2
 
3
3
  # pi-meta-oauth
4
4
 
5
+ <!-- markdownlint-disable-next-line MD013 -->
6
+ [![npm version](https://img.shields.io/npm/v/pi-meta-oauth)](https://www.npmjs.com/package/pi-meta-oauth) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![GitHub stars](https://img.shields.io/github/stars/BlockedPath/pi-meta-oauth?style=social)](https://github.com/BlockedPath/pi-meta-oauth/stargazers) [![Last Commit](https://img.shields.io/github/last-commit/BlockedPath/pi-meta-oauth)](https://github.com/BlockedPath/pi-meta-oauth/commits/main) [![Issues](https://img.shields.io/github/issues/BlockedPath/pi-meta-oauth)](https://github.com/BlockedPath/pi-meta-oauth/issues) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/BlockedPath/pi-meta-oauth/pulls) [![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg)](https://www.conventionalcommits.org/en/v1.0.0/) [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue)](https://www.typescriptlang.org/) [![CI](https://github.com/BlockedPath/pi-meta-oauth/actions/workflows/ci.yml/badge.svg)](https://github.com/BlockedPath/pi-meta-oauth/actions/workflows/ci.yml) [![Pi compatible](https://img.shields.io/badge/pi-Compatible-blueviolet)](https://pi.dev)
7
+
5
8
  Meta Model API OAuth and Muse-style voice input for [pi](https://pi.dev).
6
9
 
7
10
  - Use Muse Spark models through Pi's `openai-responses` provider
@@ -83,13 +86,19 @@ bun run typecheck
83
86
  bun test
84
87
  ```
85
88
 
86
- ## Publish to pi.dev
89
+ ## Publish to npm and pi.dev
87
90
 
88
- The `pi-package` keyword makes the package discoverable at <https://pi.dev/packages>.
91
+ The `pi-package` keyword makes the package discoverable at <https://pi.dev/packages>. Publishing is handled by `.github/workflows/publish.yml` when a `v*` tag is pushed.
89
92
 
90
- ```bash
91
- npm publish --access public
92
- ```
93
+ Before the first automated release, configure npm trusted publishing for `pi-meta-oauth` with:
94
+
95
+ - GitHub owner: `BlockedPath`
96
+ - Repository: `pi-meta-oauth`
97
+ - Workflow filename: `publish.yml`
98
+ - Environment: leave blank
99
+ - Allowed action: `npm publish`
100
+
101
+ Bump `package.json`, commit the change, create a `v<version>` tag, and push the commit and tag. The workflow verifies that the tag matches the package version, runs typechecking and tests, publishes to npm with provenance, and creates the GitHub Release. Re-running a partially completed workflow is safe when npm reports that the same commit was already published.
93
102
 
94
103
  Users can then update with:
95
104
 
@@ -3,7 +3,10 @@ import type {
3
3
  OAuthLoginCallbacks,
4
4
  RefreshModelsContext,
5
5
  } from "@earendil-works/pi-ai";
6
- import type { ExtensionAPI, ProviderConfig } from "@earendil-works/pi-coding-agent";
6
+ import type {
7
+ ExtensionAPI,
8
+ ProviderConfig,
9
+ } from "@earendil-works/pi-coding-agent";
7
10
 
8
11
  export const META_PROVIDER_ID = "meta";
9
12
  export const META_API_BASE_URL = "https://api.meta.ai/v1";
@@ -60,7 +63,11 @@ interface MetaCatalogModel {
60
63
  modalities?: { input?: string[] };
61
64
  limit?: { context?: number; output?: number };
62
65
  variants?: Record<string, { reasoningEffort?: string }>;
63
- cost?: { input?: string | number; output?: string | number; cached?: string | number };
66
+ cost?: {
67
+ input?: string | number;
68
+ output?: string | number;
69
+ cached?: string | number;
70
+ };
64
71
  };
65
72
  };
66
73
  }
@@ -83,7 +90,7 @@ const FALLBACK_MODELS: MetaProviderModel[] = [
83
90
  cost: { input: 1.25, output: 4.25, cacheRead: 0.15, cacheWrite: 0 },
84
91
  contextWindow: 1_048_576,
85
92
  maxTokens: 256_000,
86
- compat: { supportsReasoningEffort: true },
93
+ compat: { supportsReasoningEffort: true, supportsToolSearch: true },
87
94
  },
88
95
  {
89
96
  id: "muse-spark-1.2-contributor",
@@ -102,7 +109,7 @@ const FALLBACK_MODELS: MetaProviderModel[] = [
102
109
  cost: { input: 0.1, output: 0.2, cacheRead: 0.002, cacheWrite: 0 },
103
110
  contextWindow: 1_048_576,
104
111
  maxTokens: 256_000,
105
- compat: { supportsReasoningEffort: true },
112
+ compat: { supportsReasoningEffort: true, supportsToolSearch: true },
106
113
  },
107
114
  {
108
115
  id: "muse-spark-1.1",
@@ -121,7 +128,7 @@ const FALLBACK_MODELS: MetaProviderModel[] = [
121
128
  cost: { input: 1.25, output: 4.25, cacheRead: 0.15, cacheWrite: 0 },
122
129
  contextWindow: 1_048_576,
123
130
  maxTokens: 256_000,
124
- compat: { supportsReasoningEffort: true },
131
+ compat: { supportsReasoningEffort: true, supportsToolSearch: true },
125
132
  },
126
133
  ];
127
134
 
@@ -129,7 +136,9 @@ function delay(milliseconds: number): Promise<void> {
129
136
  return new Promise((resolve) => setTimeout(resolve, milliseconds));
130
137
  }
131
138
 
132
- async function responseBody(response: Response): Promise<Record<string, unknown>> {
139
+ async function responseBody(
140
+ response: Response,
141
+ ): Promise<Record<string, unknown>> {
133
142
  const text = await response.text();
134
143
  if (!text) return {};
135
144
  try {
@@ -157,14 +166,23 @@ async function postForm<T>(
157
166
  ): Promise<{ response: Response; body: T & Record<string, unknown> }> {
158
167
  const response = await fetchImpl(url, {
159
168
  method: "POST",
160
- headers: { Accept: "application/json", "Content-Type": "application/x-www-form-urlencoded" },
169
+ headers: {
170
+ Accept: "application/json",
171
+ "Content-Type": "application/x-www-form-urlencoded",
172
+ },
161
173
  body: new URLSearchParams(fields),
162
174
  redirect: "manual",
163
175
  });
164
- return { response, body: (await responseBody(response)) as T & Record<string, unknown> };
176
+ return {
177
+ response,
178
+ body: (await responseBody(response)) as T & Record<string, unknown>,
179
+ };
165
180
  }
166
181
 
167
- export async function mintMetaApiKey(identityToken: string, fetchImpl: Fetch = fetch): Promise<string> {
182
+ export async function mintMetaApiKey(
183
+ identityToken: string,
184
+ fetchImpl: Fetch = fetch,
185
+ ): Promise<string> {
168
186
  const response = await fetchImpl(API_KEY_MINT_URL, {
169
187
  method: "POST",
170
188
  headers: {
@@ -175,12 +193,18 @@ export async function mintMetaApiKey(identityToken: string, fetchImpl: Fetch = f
175
193
  },
176
194
  body: "{}",
177
195
  });
178
- const body = (await responseBody(response)) as MintResponse & Record<string, unknown>;
196
+ const body = (await responseBody(response)) as MintResponse &
197
+ Record<string, unknown>;
179
198
  if (!response.ok) {
180
- throw new Error(`Meta API-key mint failed (HTTP ${response.status})${errorDetail(body) ? `: ${errorDetail(body)}` : ""}`);
199
+ throw new Error(
200
+ `Meta API-key mint failed (HTTP ${response.status})${errorDetail(body) ? `: ${errorDetail(body)}` : ""}`,
201
+ );
181
202
  }
182
203
  if (typeof body.api_key !== "string" || !body.api_key) {
183
- const setup = typeof body.action_url === "string" && body.action_url ? ` Complete setup at ${body.action_url}.` : "";
204
+ const setup =
205
+ typeof body.action_url === "string" && body.action_url
206
+ ? ` Complete setup at ${body.action_url}.`
207
+ : "";
184
208
  throw new Error(`Meta did not issue an API key.${setup}`);
185
209
  }
186
210
  return body.api_key;
@@ -204,17 +228,24 @@ export async function loginMeta(
204
228
  }
205
229
  const device = authorization.body;
206
230
  if (!device.device_code || !device.user_code || !device.verification_uri) {
207
- throw new Error("Meta device authorization returned an incomplete response");
231
+ throw new Error(
232
+ "Meta device authorization returned an incomplete response",
233
+ );
208
234
  }
209
235
 
210
- let intervalSeconds = Number.isFinite(device.interval) && Number(device.interval) > 0 ? Number(device.interval) : 5;
211
- const expiresInSeconds = Number.isFinite(device.expires_in) && Number(device.expires_in) > 0
212
- ? Number(device.expires_in)
213
- : 900;
236
+ let intervalSeconds =
237
+ Number.isFinite(device.interval) && Number(device.interval) > 0
238
+ ? Number(device.interval)
239
+ : 5;
240
+ const expiresInSeconds =
241
+ Number.isFinite(device.expires_in) && Number(device.expires_in) > 0
242
+ ? Number(device.expires_in)
243
+ : 900;
214
244
  const deadline = Date.now() + expiresInSeconds * 1000;
215
245
  callbacks.onDeviceCode({
216
246
  userCode: device.user_code,
217
- verificationUri: device.verification_uri_complete || device.verification_uri,
247
+ verificationUri:
248
+ device.verification_uri_complete || device.verification_uri,
218
249
  intervalSeconds,
219
250
  expiresInSeconds,
220
251
  });
@@ -267,7 +298,10 @@ export async function refreshMetaToken(
267
298
  credentials: OAuthCredentials,
268
299
  fetchImpl: Fetch = fetch,
269
300
  ): Promise<OAuthCredentials> {
270
- if (!credentials.refresh) throw new Error("Meta login is missing its identity token; run /login meta again");
301
+ if (!credentials.refresh)
302
+ throw new Error(
303
+ "Meta login is missing its identity token; run /login meta again",
304
+ );
271
305
  return {
272
306
  ...credentials,
273
307
  access: await mintMetaApiKey(credentials.refresh, fetchImpl),
@@ -276,56 +310,83 @@ export async function refreshMetaToken(
276
310
  }
277
311
 
278
312
  function finitePositive(value: unknown, fallback: number): number {
279
- return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
313
+ return typeof value === "number" && Number.isFinite(value) && value > 0
314
+ ? value
315
+ : fallback;
280
316
  }
281
317
 
282
318
  function numericCost(value: unknown, fallback: number): number {
283
- const number = typeof value === "number" ? value : typeof value === "string" ? Number(value) : Number.NaN;
319
+ const number =
320
+ typeof value === "number"
321
+ ? value
322
+ : typeof value === "string"
323
+ ? Number(value)
324
+ : Number.NaN;
284
325
  return Number.isFinite(number) && number >= 0 ? number : fallback;
285
326
  }
286
327
 
287
328
  function displayName(id: string): string {
288
329
  return id
289
330
  .split("-")
290
- .map((part) => part ? part[0].toUpperCase() + part.slice(1) : part)
331
+ .map((part) => (part ? part[0].toUpperCase() + part.slice(1) : part))
291
332
  .join(" ");
292
333
  }
293
334
 
294
- export function toProviderModels(catalog: CatalogResponse): MetaProviderModel[] {
335
+ export function toProviderModels(
336
+ catalog: CatalogResponse,
337
+ ): MetaProviderModel[] {
295
338
  return (catalog.data ?? []).flatMap((entry) => {
296
339
  if (typeof entry.id !== "string" || !entry.id) return [];
297
340
  const metadata = entry.metadata?.["muse-code"];
298
341
  if (metadata?.is_hidden) return [];
299
342
  const fallback = FALLBACK_MODELS.find((model) => model.id === entry.id);
300
- const catalogName = metadata?.name === entry.id ? undefined : metadata?.name;
343
+ const catalogName =
344
+ metadata?.name === entry.id ? undefined : metadata?.name;
301
345
  const variants = metadata?.variants ?? {};
302
- const thinkingLevelMap: NonNullable<MetaProviderModel["thinkingLevelMap"]> = {
303
- off: null,
304
- minimal: variants.minimal?.reasoningEffort ?? "minimal",
305
- low: variants.low?.reasoningEffort ?? "low",
306
- medium: variants.medium?.reasoningEffort ?? "medium",
307
- high: variants.high?.reasoningEffort ?? "high",
308
- xhigh: variants.xhigh?.reasoningEffort ?? "xhigh",
309
- max: null,
310
- };
311
- return [{
312
- id: entry.id,
313
- name: catalogName || fallback?.name || displayName(entry.id),
314
- reasoning: metadata?.reasoning ?? fallback?.reasoning ?? true,
315
- thinkingLevelMap,
316
- input: metadata
317
- ? (metadata.modalities?.input?.includes("image") ? ["text", "image"] : ["text"])
318
- : (fallback?.input ?? ["text"]),
319
- cost: {
320
- input: numericCost(metadata?.cost?.input, fallback?.cost.input ?? 0),
321
- output: numericCost(metadata?.cost?.output, fallback?.cost.output ?? 0),
322
- cacheRead: numericCost(metadata?.cost?.cached, fallback?.cost.cacheRead ?? 0),
323
- cacheWrite: 0,
324
- },
325
- contextWindow: finitePositive(metadata?.limit?.context, fallback?.contextWindow ?? 1_048_576),
326
- maxTokens: finitePositive(metadata?.limit?.output, fallback?.maxTokens ?? 256_000),
327
- compat: { supportsReasoningEffort: true },
328
- } satisfies MetaProviderModel];
346
+ const thinkingLevelMap: NonNullable<MetaProviderModel["thinkingLevelMap"]> =
347
+ {
348
+ off: null,
349
+ minimal: variants.minimal?.reasoningEffort ?? "minimal",
350
+ low: variants.low?.reasoningEffort ?? "low",
351
+ medium: variants.medium?.reasoningEffort ?? "medium",
352
+ high: variants.high?.reasoningEffort ?? "high",
353
+ xhigh: variants.xhigh?.reasoningEffort ?? "xhigh",
354
+ max: null,
355
+ };
356
+ return [
357
+ {
358
+ id: entry.id,
359
+ name: catalogName || fallback?.name || displayName(entry.id),
360
+ reasoning: metadata?.reasoning ?? fallback?.reasoning ?? true,
361
+ thinkingLevelMap,
362
+ input: metadata
363
+ ? metadata.modalities?.input?.includes("image")
364
+ ? ["text", "image"]
365
+ : ["text"]
366
+ : (fallback?.input ?? ["text"]),
367
+ cost: {
368
+ input: numericCost(metadata?.cost?.input, fallback?.cost.input ?? 0),
369
+ output: numericCost(
370
+ metadata?.cost?.output,
371
+ fallback?.cost.output ?? 0,
372
+ ),
373
+ cacheRead: numericCost(
374
+ metadata?.cost?.cached,
375
+ fallback?.cost.cacheRead ?? 0,
376
+ ),
377
+ cacheWrite: 0,
378
+ },
379
+ contextWindow: finitePositive(
380
+ metadata?.limit?.context,
381
+ fallback?.contextWindow ?? 1_048_576,
382
+ ),
383
+ maxTokens: finitePositive(
384
+ metadata?.limit?.output,
385
+ fallback?.maxTokens ?? 256_000,
386
+ ),
387
+ compat: { supportsReasoningEffort: true, supportsToolSearch: true },
388
+ } satisfies MetaProviderModel,
389
+ ];
329
390
  });
330
391
  }
331
392
 
@@ -333,21 +394,30 @@ export async function refreshMetaModels(
333
394
  context: RefreshModelsContext,
334
395
  fetchImpl: Fetch = fetch,
335
396
  ): Promise<MetaProviderModel[]> {
336
- if (!context.allowNetwork || context.signal?.aborted) return [...FALLBACK_MODELS];
337
- const apiKey = context.credential?.type === "oauth"
338
- ? context.credential.access
339
- : context.credential?.type === "api_key"
340
- ? context.credential.key
341
- : undefined;
397
+ if (!context.allowNetwork || context.signal?.aborted)
398
+ return [...FALLBACK_MODELS];
399
+ const apiKey =
400
+ context.credential?.type === "oauth"
401
+ ? context.credential.access
402
+ : context.credential?.type === "api_key"
403
+ ? context.credential.key
404
+ : undefined;
342
405
  if (!apiKey) return [...FALLBACK_MODELS];
343
406
 
344
407
  const response = await fetchImpl(META_MODEL_CATALOG_URL, {
345
- headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}`, "x-api-version": "1.0.0" },
408
+ headers: {
409
+ Accept: "application/json",
410
+ Authorization: `Bearer ${apiKey}`,
411
+ "x-api-version": "1.0.0",
412
+ },
346
413
  signal: context.signal,
347
414
  });
348
- const body = (await responseBody(response)) as CatalogResponse & Record<string, unknown>;
415
+ const body = (await responseBody(response)) as CatalogResponse &
416
+ Record<string, unknown>;
349
417
  if (!response.ok) {
350
- throw new Error(`Meta model catalog failed (HTTP ${response.status})${errorDetail(body) ? `: ${errorDetail(body)}` : ""}`);
418
+ throw new Error(
419
+ `Meta model catalog failed (HTTP ${response.status})${errorDetail(body) ? `: ${errorDetail(body)}` : ""}`,
420
+ );
351
421
  }
352
422
  return toProviderModels(body);
353
423
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-meta-oauth",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Meta Model API OAuth provider and Muse-style voice dictation for pi",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -13,6 +13,11 @@
13
13
  ],
14
14
  "license": "MIT",
15
15
  "type": "module",
16
+ "files": [
17
+ "LICENSE",
18
+ "README.md",
19
+ "extensions/"
20
+ ],
16
21
  "repository": {
17
22
  "type": "git",
18
23
  "url": "https://github.com/BlockedPath/pi-meta-oauth"
package/preview.png DELETED
Binary file
@@ -1,118 +0,0 @@
1
- /// <reference types="bun-types" />
2
- import { describe, expect, test } from "bun:test";
3
- import {
4
- loginMeta,
5
- mintMetaApiKey,
6
- toProviderModels,
7
- } from "../extensions/meta.ts";
8
-
9
- function jsonResponse(body: unknown, status = 200): Response {
10
- return new Response(JSON.stringify(body), {
11
- status,
12
- headers: { "Content-Type": "application/json" },
13
- });
14
- }
15
-
16
- describe("Meta OAuth provider", () => {
17
- test("maps the Muse catalog into Pi model metadata", () => {
18
- const models = toProviderModels({
19
- data: [
20
- {
21
- id: "muse-spark-test",
22
- metadata: {
23
- "muse-code": {
24
- name: "Muse Spark Test",
25
- reasoning: true,
26
- modalities: { input: ["text", "image"] },
27
- limit: { context: 123_000, output: 45_000 },
28
- variants: { high: { reasoningEffort: "deep" } },
29
- cost: { input: "1.25", output: "4.25", cached: "0.15" },
30
- },
31
- },
32
- },
33
- { id: "hidden", metadata: { "muse-code": { is_hidden: true } } },
34
- ],
35
- });
36
-
37
- expect(models).toHaveLength(1);
38
- expect(models[0]).toMatchObject({
39
- id: "muse-spark-test",
40
- name: "Muse Spark Test",
41
- input: ["text", "image"],
42
- contextWindow: 123_000,
43
- maxTokens: 45_000,
44
- cost: { input: 1.25, output: 4.25, cacheRead: 0.15, cacheWrite: 0 },
45
- thinkingLevelMap: { off: null, high: "deep", max: null },
46
- });
47
- });
48
-
49
- test("runs device login, polls, and mints a Model API key", async () => {
50
- const requests: Array<{ url: string; authorization?: string }> = [];
51
- const responses = [
52
- jsonResponse({
53
- device_code: "device-token",
54
- user_code: "ABCD-1234",
55
- verification_uri: "https://auth.meta.com/device",
56
- verification_uri_complete:
57
- "https://auth.meta.com/device?code=ABCD-1234",
58
- expires_in: 900,
59
- interval: 1,
60
- }),
61
- jsonResponse({ error: "authorization_pending" }, 400),
62
- jsonResponse({ access_token: "identity-token" }),
63
- jsonResponse({
64
- api_key: "model-api-key",
65
- base_url: "https://api.meta.ai/v1",
66
- }),
67
- ];
68
- const fetchMock = (async (
69
- input: string | URL | Request,
70
- init?: RequestInit,
71
- ) => {
72
- const headers = new Headers(init?.headers);
73
- requests.push({
74
- url: String(input),
75
- authorization: headers.get("Authorization") ?? undefined,
76
- });
77
- const response = responses.shift();
78
- if (!response) throw new Error("Unexpected request");
79
- return response;
80
- }) as unknown as typeof fetch;
81
- const deviceCodes: unknown[] = [];
82
- const credentials = await loginMeta(
83
- {
84
- onAuth() {},
85
- onDeviceCode(value: unknown) {
86
- deviceCodes.push(value);
87
- },
88
- onPrompt: async () => "",
89
- onSelect: async () => undefined,
90
- },
91
- fetchMock,
92
- async () => {},
93
- );
94
-
95
- expect(deviceCodes).toEqual([
96
- {
97
- userCode: "ABCD-1234",
98
- verificationUri: "https://auth.meta.com/device?code=ABCD-1234",
99
- intervalSeconds: 1,
100
- expiresInSeconds: 900,
101
- },
102
- ]);
103
- expect(credentials.refresh).toBe("identity-token");
104
- expect(credentials.access).toBe("model-api-key");
105
- expect(requests.at(-1)?.authorization).toBe("Bearer identity-token");
106
- });
107
-
108
- test("reports account setup when minting yields no API key", async () => {
109
- const fetchMock = (async () =>
110
- jsonResponse({
111
- require_payment: true,
112
- action_url: "https://dev.meta.ai/billing",
113
- })) as unknown as typeof fetch;
114
- await expect(mintMetaApiKey("identity-token", fetchMock)).rejects.toThrow(
115
- "Complete setup at https://dev.meta.ai/billing",
116
- );
117
- });
118
- });
@@ -1,55 +0,0 @@
1
- /// <reference types="bun-types" />
2
- import { describe, expect, test } from "bun:test";
3
- import { existsSync } from "node:fs";
4
- import { dirname, join } from "node:path";
5
- import { fileURLToPath } from "node:url";
6
- import {
7
- asrEndpoint,
8
- formatAuthorization,
9
- pcmAudioLevel,
10
- } from "../extensions/voice.ts";
11
-
12
- describe("Meta voice mode", () => {
13
- test("maps silent and audible PCM into meter levels", () => {
14
- const silent = Buffer.alloc(3_200);
15
- const audible = Buffer.alloc(3_200);
16
- for (let offset = 0; offset < audible.length; offset += 2) {
17
- audible.writeInt16LE(offset % 4 === 0 ? 8_000 : -8_000, offset);
18
- }
19
-
20
- expect(pcmAudioLevel(silent)).toBe(0);
21
- expect(pcmAudioLevel(audible)).toBeGreaterThan(0.5);
22
- expect(pcmAudioLevel(audible)).toBeLessThanOrEqual(1);
23
- });
24
-
25
- test("formats the Meta ASR authorization value exactly once", () => {
26
- expect(formatAuthorization("LLM|example")).toBe("OAuth LLM|example");
27
- expect(formatAuthorization("OAuth LLM|example")).toBe("OAuth LLM|example");
28
- });
29
-
30
- test("builds a secure ASR URL with a session id", () => {
31
- let url: URL;
32
- try {
33
- url = new URL(asrEndpoint("session-123"));
34
- } catch (error) {
35
- throw new Error("ASR endpoint was not a valid URL", { cause: error });
36
- }
37
- expect(url.protocol).toBe("wss:");
38
- expect(url.hostname).toBe("shortwave.facebook.com");
39
- expect(url.searchParams.get("sessionId")).toBe("session-123");
40
- });
41
-
42
- test("ships the macOS helper resources", () => {
43
- const root = join(
44
- dirname(fileURLToPath(import.meta.url)),
45
- "../extensions/voice",
46
- );
47
- for (const file of [
48
- "macos-audio.swift",
49
- "Info.plist",
50
- "Entitlements.plist",
51
- ]) {
52
- expect(existsSync(join(root, file))).toBe(true);
53
- }
54
- });
55
- });
package/tsconfig.json DELETED
@@ -1,12 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "ESNext",
5
- "moduleResolution": "bundler",
6
- "strict": true,
7
- "allowImportingTsExtensions": true,
8
- "skipLibCheck": true,
9
- "types": ["bun-types"]
10
- },
11
- "include": ["extensions", "tests"]
12
- }