@remnic/connector-fireflies 9.69.26 → 9.69.28
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/dist/index.d.ts +5 -4
- package/dist/index.js +46 -98
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { WearableConversation, WearableConnectorFactoryOptions, WearableSourceConnector, WearableConnectorRegistration } from '@remnic/core';
|
|
2
|
+
import { ConnectorApiError } from '@remnic/core/http-retry';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Minimal Fireflies.ai GraphQL API client (raw fetch, no SDK).
|
|
@@ -19,6 +20,7 @@ import { WearableConversation, WearableConnectorFactoryOptions, WearableSourceCo
|
|
|
19
20
|
* failure; those are surfaced as FirefliesApiError (a backend failure), never
|
|
20
21
|
* as an empty result (AGENTS.md §22).
|
|
21
22
|
*/
|
|
23
|
+
|
|
22
24
|
declare const FIREFLIES_DEFAULT_ENDPOINT = "https://api.fireflies.ai/graphql";
|
|
23
25
|
/** Hard API maximum for `limit` on the transcripts query. */
|
|
24
26
|
declare const TRANSCRIPTS_MAX_PAGE_SIZE = 50;
|
|
@@ -68,15 +70,14 @@ interface FirefliesClientOptions {
|
|
|
68
70
|
/** Injectable sleep for tests. */
|
|
69
71
|
sleep?: (ms: number) => Promise<void>;
|
|
70
72
|
}
|
|
71
|
-
declare class FirefliesApiError extends
|
|
72
|
-
|
|
73
|
-
constructor(message: string, status?: number | undefined);
|
|
73
|
+
declare class FirefliesApiError extends ConnectorApiError {
|
|
74
|
+
constructor(message: string, status?: number);
|
|
74
75
|
}
|
|
75
76
|
declare class FirefliesClient {
|
|
76
77
|
private readonly apiKey;
|
|
77
78
|
private readonly endpoint;
|
|
78
|
-
private readonly fetchImpl;
|
|
79
79
|
private readonly timeoutMs;
|
|
80
|
+
private readonly fetchImpl;
|
|
80
81
|
private readonly sleep;
|
|
81
82
|
constructor(options: FirefliesClientOptions);
|
|
82
83
|
/** One page of transcripts created inside the [fromDate, toDate) window. */
|
package/dist/index.js
CHANGED
|
@@ -6,11 +6,15 @@ import {
|
|
|
6
6
|
} from "@remnic/core";
|
|
7
7
|
|
|
8
8
|
// src/client.ts
|
|
9
|
+
import {
|
|
10
|
+
ConnectorApiError,
|
|
11
|
+
describeNetworkError,
|
|
12
|
+
retryingFetch,
|
|
13
|
+
stripTrailingSlashes
|
|
14
|
+
} from "@remnic/core/http-retry";
|
|
9
15
|
var FIREFLIES_DEFAULT_ENDPOINT = "https://api.fireflies.ai/graphql";
|
|
10
16
|
var TRANSCRIPTS_MAX_PAGE_SIZE = 50;
|
|
11
17
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
12
|
-
var MAX_RETRIES = 3;
|
|
13
|
-
var MAX_RETRY_DELAY_MS = 3e4;
|
|
14
18
|
var TRANSCRIPTS_QUERY = `query RemnicTranscripts($fromDate: DateTime, $toDate: DateTime, $limit: Int, $skip: Int) {
|
|
15
19
|
transcripts(fromDate: $fromDate, toDate: $toDate, limit: $limit, skip: $skip) {
|
|
16
20
|
id
|
|
@@ -24,13 +28,11 @@ var TRANSCRIPTS_QUERY = `query RemnicTranscripts($fromDate: DateTime, $toDate: D
|
|
|
24
28
|
}
|
|
25
29
|
}`;
|
|
26
30
|
var AUTH_PROBE_QUERY = `query RemnicFirefliesAuthProbe { transcripts(limit: 1) { id } }`;
|
|
27
|
-
var FirefliesApiError = class extends
|
|
31
|
+
var FirefliesApiError = class extends ConnectorApiError {
|
|
28
32
|
constructor(message, status) {
|
|
29
|
-
super(message);
|
|
30
|
-
this.status = status;
|
|
33
|
+
super(message, status);
|
|
31
34
|
this.name = "FirefliesApiError";
|
|
32
35
|
}
|
|
33
|
-
status;
|
|
34
36
|
};
|
|
35
37
|
function isRecord(value) {
|
|
36
38
|
return typeof value === "object" && value !== null;
|
|
@@ -38,8 +40,8 @@ function isRecord(value) {
|
|
|
38
40
|
var FirefliesClient = class {
|
|
39
41
|
apiKey;
|
|
40
42
|
endpoint;
|
|
41
|
-
fetchImpl;
|
|
42
43
|
timeoutMs;
|
|
44
|
+
fetchImpl;
|
|
43
45
|
sleep;
|
|
44
46
|
constructor(options) {
|
|
45
47
|
if (typeof options.apiKey !== "string" || options.apiKey.trim().length === 0) {
|
|
@@ -51,7 +53,7 @@ var FirefliesClient = class {
|
|
|
51
53
|
this.endpoint = stripTrailingSlashes(options.baseUrl ?? FIREFLIES_DEFAULT_ENDPOINT);
|
|
52
54
|
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
53
55
|
this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
54
|
-
this.sleep = options.sleep
|
|
56
|
+
this.sleep = options.sleep;
|
|
55
57
|
}
|
|
56
58
|
/** One page of transcripts created inside the [fromDate, toDate) window. */
|
|
57
59
|
async listTranscripts(params) {
|
|
@@ -88,62 +90,43 @@ var FirefliesClient = class {
|
|
|
88
90
|
}
|
|
89
91
|
}
|
|
90
92
|
async graphql(query, variables, signal) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
if (response.status === 429 || response.status >= 500) {
|
|
120
|
-
lastError = new FirefliesApiError(`Fireflies API responded ${response.status}`, response.status);
|
|
121
|
-
if (attempt < MAX_RETRIES) {
|
|
122
|
-
await this.sleep(await retryDelayMs(response, attempt));
|
|
123
|
-
continue;
|
|
124
|
-
}
|
|
125
|
-
throw lastError;
|
|
126
|
-
}
|
|
127
|
-
if (response.status === 401 || response.status === 403) {
|
|
128
|
-
throw new FirefliesApiError(`Fireflies API responded ${response.status}`, response.status);
|
|
129
|
-
}
|
|
130
|
-
let body;
|
|
131
|
-
try {
|
|
132
|
-
body = await response.json();
|
|
133
|
-
} catch {
|
|
134
|
-
throw new FirefliesApiError("Fireflies API returned a non-JSON body");
|
|
135
|
-
}
|
|
136
|
-
const errors = isRecord(body) ? body.errors : void 0;
|
|
137
|
-
if (Array.isArray(errors) && errors.length > 0) {
|
|
138
|
-
throw graphqlError(errors[0]);
|
|
139
|
-
}
|
|
140
|
-
const data = isRecord(body) ? body.data : void 0;
|
|
141
|
-
if (data === void 0 || data === null) {
|
|
142
|
-
throw new FirefliesApiError("Fireflies API returned no data and no errors");
|
|
143
|
-
}
|
|
144
|
-
return data;
|
|
93
|
+
const response = await retryingFetch(this.endpoint, {
|
|
94
|
+
init: {
|
|
95
|
+
method: "POST",
|
|
96
|
+
headers: {
|
|
97
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
98
|
+
"Content-Type": "application/json",
|
|
99
|
+
Accept: "application/json"
|
|
100
|
+
},
|
|
101
|
+
body: JSON.stringify({ query, variables })
|
|
102
|
+
},
|
|
103
|
+
fetchImpl: this.fetchImpl,
|
|
104
|
+
sleep: this.sleep,
|
|
105
|
+
signal,
|
|
106
|
+
timeoutMs: this.timeoutMs,
|
|
107
|
+
networkError: (err, attempts) => new FirefliesApiError(
|
|
108
|
+
`Fireflies API request failed after ${attempts} attempts: ${describeNetworkError(err)}`
|
|
109
|
+
),
|
|
110
|
+
retryableError: (retryable) => new FirefliesApiError(`Fireflies API responded ${retryable.status}`, retryable.status)
|
|
111
|
+
});
|
|
112
|
+
if (response.status === 401 || response.status === 403) {
|
|
113
|
+
throw new FirefliesApiError(`Fireflies API responded ${response.status}`, response.status);
|
|
114
|
+
}
|
|
115
|
+
let body;
|
|
116
|
+
try {
|
|
117
|
+
body = await response.json();
|
|
118
|
+
} catch {
|
|
119
|
+
throw new FirefliesApiError("Fireflies API returned a non-JSON body");
|
|
145
120
|
}
|
|
146
|
-
|
|
121
|
+
const errors = isRecord(body) ? body.errors : void 0;
|
|
122
|
+
if (Array.isArray(errors) && errors.length > 0) {
|
|
123
|
+
throw graphqlError(errors[0]);
|
|
124
|
+
}
|
|
125
|
+
const data = isRecord(body) ? body.data : void 0;
|
|
126
|
+
if (data === void 0 || data === null) {
|
|
127
|
+
throw new FirefliesApiError("Fireflies API returned no data and no errors");
|
|
128
|
+
}
|
|
129
|
+
return data;
|
|
147
130
|
}
|
|
148
131
|
};
|
|
149
132
|
function graphqlError(first) {
|
|
@@ -154,41 +137,6 @@ function graphqlError(first) {
|
|
|
154
137
|
const authLike = code === "unauthenticated" || code === "forbidden" || /unauthor|forbidden|invalid.*(token|api key)|expired/i.test(message);
|
|
155
138
|
return new FirefliesApiError(`Fireflies GraphQL error: ${message}`, authLike ? 401 : void 0);
|
|
156
139
|
}
|
|
157
|
-
function defaultSleep(ms) {
|
|
158
|
-
const { promise, resolve } = Promise.withResolvers();
|
|
159
|
-
setTimeout(resolve, ms);
|
|
160
|
-
return promise;
|
|
161
|
-
}
|
|
162
|
-
function describeNetworkError(err) {
|
|
163
|
-
if (isRecord(err)) {
|
|
164
|
-
const name = typeof err.name === "string" ? err.name : "Error";
|
|
165
|
-
const code = err.code;
|
|
166
|
-
return typeof code === "string" ? `${name} (${code})` : name;
|
|
167
|
-
}
|
|
168
|
-
return "network error";
|
|
169
|
-
}
|
|
170
|
-
function stripTrailingSlashes(value) {
|
|
171
|
-
let end = value.length;
|
|
172
|
-
while (end > 0 && value.charCodeAt(end - 1) === 47) end--;
|
|
173
|
-
return value.slice(0, end);
|
|
174
|
-
}
|
|
175
|
-
function backoffMs(attempt) {
|
|
176
|
-
return Math.min(MAX_RETRY_DELAY_MS, 1e3 * 2 ** attempt);
|
|
177
|
-
}
|
|
178
|
-
async function retryDelayMs(response, attempt) {
|
|
179
|
-
const header = response.headers.get("retry-after");
|
|
180
|
-
if (header) {
|
|
181
|
-
const seconds = Number(header);
|
|
182
|
-
if (Number.isFinite(seconds) && seconds >= 0) {
|
|
183
|
-
return Math.min(MAX_RETRY_DELAY_MS, seconds * 1e3);
|
|
184
|
-
}
|
|
185
|
-
const when = Date.parse(header);
|
|
186
|
-
if (Number.isFinite(when)) {
|
|
187
|
-
return Math.min(MAX_RETRY_DELAY_MS, Math.max(0, when - Date.now()));
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
return backoffMs(attempt);
|
|
191
|
-
}
|
|
192
140
|
|
|
193
141
|
// src/normalize.ts
|
|
194
142
|
import {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/client.ts","../src/normalize.ts"],"sourcesContent":["/**\n * @remnic/connector-fireflies — Fireflies.ai meeting-transcript connector.\n *\n * À-la-carte optional companion of @remnic/core: installing core alone\n * never pulls this in; core discovers it at runtime via a\n * computed-specifier dynamic import (see wearables/registry.ts) or via\n * a direct import of this module, which self-registers idempotently.\n *\n * API key: `wearables.sources.fireflies.apiKey`, else the\n * `REMNIC_FIREFLIES_API_KEY` / `FIREFLIES_API_KEY` environment\n * variables (checked in that order). Create a key under\n * Settings → Developer settings in the Fireflies app.\n */\n\nimport {\n selfRegisterWearableConnector,\n type WearableConnectorFactoryOptions,\n type WearableConnectorRegistration,\n type WearableFetchOptions,\n type WearableFetchPage,\n type WearableSourceConnector,\n} from \"@remnic/core\";\n\nimport { FirefliesClient } from \"./client.js\";\nimport { FIREFLIES_SOURCE_ID, firefliesDayWindow, transcriptToConversation } from \"./normalize.js\";\n\nexport {\n FirefliesClient,\n FirefliesApiError,\n FIREFLIES_DEFAULT_ENDPOINT,\n TRANSCRIPTS_MAX_PAGE_SIZE,\n} from \"./client.js\";\nexport type {\n FirefliesTranscript,\n FirefliesSentence,\n FirefliesSummary,\n FirefliesSpeaker,\n TranscriptsPage,\n FirefliesClientOptions,\n} from \"./client.js\";\nexport {\n FIREFLIES_SOURCE_ID,\n firefliesDayWindow,\n transcriptToConversation,\n} from \"./normalize.js\";\n\nconst FIREFLIES_DISPLAY_NAME = \"Fireflies.ai\";\n\nexport function resolveFirefliesApiKey(\n configuredKey: string | undefined,\n env: NodeJS.ProcessEnv = process.env,\n): string | undefined {\n if (typeof configuredKey === \"string\" && configuredKey.trim().length > 0) {\n return configuredKey.trim();\n }\n // REMNIC_* first, then the provider-conventional name.\n for (const name of [\"REMNIC_FIREFLIES_API_KEY\", \"FIREFLIES_API_KEY\"]) {\n const value = env[name];\n if (typeof value === \"string\" && value.trim().length > 0) {\n return value.trim();\n }\n }\n return undefined;\n}\n\nexport function createFirefliesConnector(\n options: WearableConnectorFactoryOptions,\n): WearableSourceConnector {\n // Key resolution happens lazily at call time (not factory time) so\n // `wearables status` works without credentials and env changes take\n // effect without a restart; the client constructor throws the\n // actionable missing-key message.\n let client: FirefliesClient | null = null;\n const getClient = (): FirefliesClient => {\n if (!client) {\n client = new FirefliesClient({\n apiKey: resolveFirefliesApiKey(options.settings.apiKey) ?? \"\",\n baseUrl: options.settings.baseUrl,\n });\n }\n return client;\n };\n\n return {\n id: FIREFLIES_SOURCE_ID,\n displayName: FIREFLIES_DISPLAY_NAME,\n async verifyAuth(signal?: AbortSignal) {\n return getClient().verifyAuth(signal);\n },\n async fetchConversations(opts: WearableFetchOptions): Promise<WearableFetchPage> {\n const { fromDate, toDate } = firefliesDayWindow(opts.date, opts.timezone);\n const skip = parseCursor(opts.cursor);\n const page = await getClient().listTranscripts({\n fromDate,\n toDate,\n skip,\n signal: opts.signal,\n });\n return {\n conversations: page.transcripts\n .map(transcriptToConversation)\n // A transcript with no resolvable meeting date can't produce a\n // valid conversation start; drop it rather than emit an empty\n // startIso that would fail downstream parsing silently.\n .filter((conversation) => conversation.startIso !== \"\"),\n // Advance the keyset by the raw page size (not the id-filtered count)\n // while full pages keep coming, so dropped rows never truncate the day.\n nextCursor: page.hadFullPage ? String(skip + page.rawCount) : null,\n };\n },\n };\n}\n\n/** Opaque cursor is the numeric `skip` offset; anything invalid restarts at 0. */\nfunction parseCursor(cursor: string | null | undefined): number {\n if (typeof cursor !== \"string\") return 0;\n const parsed = Number(cursor);\n return Number.isInteger(parsed) && parsed > 0 ? parsed : 0;\n}\n\nexport const wearableConnectorRegistration: WearableConnectorRegistration = {\n id: FIREFLIES_SOURCE_ID,\n displayName: FIREFLIES_DISPLAY_NAME,\n factory: createFirefliesConnector,\n};\n\n/**\n * Idempotently register the connector with the core registry. Importing\n * this module registers it as a side effect; calling this again is safe\n * (returns false when already registered).\n */\nexport const ensureFirefliesConnectorRegistered =\n selfRegisterWearableConnector(wearableConnectorRegistration);\n","/**\n * Minimal Fireflies.ai GraphQL API client (raw fetch, no SDK).\n *\n * API verified against https://docs.fireflies.ai/graphql-api/query/transcripts\n * and https://docs.fireflies.ai/fundamentals/authorization (fetched\n * 2026-07-21):\n * - Endpoint: POST https://api.fireflies.ai/graphql\n * - Auth: `Authorization: Bearer <api key>`\n * - `transcripts(fromDate, toDate, limit, skip)` where fromDate/toDate are\n * ISO-8601 date-time strings and `limit` maxes at 50.\n * - Each transcript exposes `id`, `title`, `date` (epoch ms), `duration`\n * (minutes), `summary { overview short_summary }`, `participants`, and\n * `sentences { index speaker_name speaker_id text start_time end_time }`\n * where start_time/end_time are second-offsets from the meeting start.\n *\n * GraphQL transport returns HTTP 200 with an `errors` array on query/auth\n * failure; those are surfaced as FirefliesApiError (a backend failure), never\n * as an empty result (AGENTS.md §22).\n */\n\nexport const FIREFLIES_DEFAULT_ENDPOINT = \"https://api.fireflies.ai/graphql\";\n\n/** Hard API maximum for `limit` on the transcripts query. */\nexport const TRANSCRIPTS_MAX_PAGE_SIZE = 50;\n\nconst DEFAULT_TIMEOUT_MS = 30_000;\nconst MAX_RETRIES = 3;\n/** Backoff cap so a hostile retryAfter can't stall a sync for minutes. */\nconst MAX_RETRY_DELAY_MS = 30_000;\n\nconst TRANSCRIPTS_QUERY = `query RemnicTranscripts($fromDate: DateTime, $toDate: DateTime, $limit: Int, $skip: Int) {\n transcripts(fromDate: $fromDate, toDate: $toDate, limit: $limit, skip: $skip) {\n id\n title\n date\n duration\n participants\n summary { overview short_summary }\n speakers { id name }\n sentences { index speaker_name speaker_id text start_time end_time }\n }\n}`;\n\nconst AUTH_PROBE_QUERY = `query RemnicFirefliesAuthProbe { transcripts(limit: 1) { id } }`;\n\nexport interface FirefliesSentence {\n index?: number;\n speaker_name?: string | null;\n speaker_id?: number | string | null;\n text?: string | null;\n start_time?: number | null;\n end_time?: number | null;\n}\n\nexport interface FirefliesSummary {\n overview?: string | null;\n short_summary?: string | null;\n}\n\nexport interface FirefliesSpeaker {\n id?: number | string | null;\n name?: string | null;\n}\n\nexport interface FirefliesTranscript {\n id: string;\n title?: string | null;\n /** Meeting datetime — epoch milliseconds (Float) or an ISO string. */\n date?: number | string | null;\n /** Meeting duration in minutes (Fireflies reports minutes). */\n duration?: number | null;\n participants?: string[] | null;\n summary?: FirefliesSummary | null;\n speakers?: FirefliesSpeaker[] | null;\n sentences?: FirefliesSentence[] | null;\n}\n\nexport interface TranscriptsPage {\n transcripts: FirefliesTranscript[];\n /** True when a full page came back (more may exist beyond this skip). */\n hadFullPage: boolean;\n /** Raw row count Fireflies returned at this offset (drives the skip cursor). */\n rawCount: number;\n}\n\nexport interface FirefliesClientOptions {\n apiKey: string;\n /** Override the GraphQL endpoint (self-hosted / proxy setups). */\n baseUrl?: string;\n /** Injectable for tests. Defaults to global fetch. */\n fetchImpl?: typeof fetch;\n /** Per-request timeout (ms). */\n timeoutMs?: number;\n /** Injectable sleep for tests. */\n sleep?: (ms: number) => Promise<void>;\n}\n\nexport class FirefliesApiError extends Error {\n constructor(\n message: string,\n readonly status?: number,\n ) {\n super(message);\n this.name = \"FirefliesApiError\";\n }\n}\n\n/** Narrow unknown JSON to a plain object so field reads are actually checked. */\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\nexport class FirefliesClient {\n private readonly apiKey: string;\n private readonly endpoint: string;\n private readonly fetchImpl: typeof fetch;\n private readonly timeoutMs: number;\n private readonly sleep: (ms: number) => Promise<void>;\n\n constructor(options: FirefliesClientOptions) {\n if (typeof options.apiKey !== \"string\" || options.apiKey.trim().length === 0) {\n throw new FirefliesApiError(\n \"Fireflies API key is missing. Set wearables.sources.fireflies.apiKey \" +\n \"or the REMNIC_FIREFLIES_API_KEY / FIREFLIES_API_KEY environment variable \" +\n \"(create a key under Settings → Developer settings in the Fireflies app).\",\n );\n }\n this.apiKey = options.apiKey.trim();\n this.endpoint = stripTrailingSlashes(options.baseUrl ?? FIREFLIES_DEFAULT_ENDPOINT);\n this.fetchImpl = options.fetchImpl ?? fetch;\n this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this.sleep = options.sleep ?? defaultSleep;\n }\n\n /** One page of transcripts created inside the [fromDate, toDate) window. */\n async listTranscripts(params: {\n fromDate: string;\n toDate: string;\n skip?: number;\n signal?: AbortSignal;\n }): Promise<TranscriptsPage> {\n const skip = Number.isInteger(params.skip) && (params.skip as number) > 0 ? (params.skip as number) : 0;\n const data = await this.graphql(\n TRANSCRIPTS_QUERY,\n { fromDate: params.fromDate, toDate: params.toDate, limit: TRANSCRIPTS_MAX_PAGE_SIZE, skip },\n params.signal,\n );\n const raw = isRecord(data) ? data.transcripts : undefined;\n if (!Array.isArray(raw)) {\n throw new FirefliesApiError(\n \"Fireflies API returned an unexpected transcripts shape (missing data.transcripts array)\",\n );\n }\n const transcripts = raw.filter(\n (entry): entry is FirefliesTranscript => isRecord(entry) && typeof entry.id === \"string\",\n );\n return { transcripts, hadFullPage: raw.length >= TRANSCRIPTS_MAX_PAGE_SIZE, rawCount: raw.length };\n }\n\n /** Cheap auth probe. */\n async verifyAuth(signal?: AbortSignal): Promise<{ ok: boolean; detail?: string }> {\n try {\n await this.graphql(AUTH_PROBE_QUERY, {}, signal);\n return { ok: true };\n } catch (err) {\n if (err instanceof FirefliesApiError && (err.status === 401 || err.status === 403)) {\n return {\n ok: false,\n detail:\n \"Fireflies rejected the API key (401/403) — create a new key under Settings → Developer settings\",\n };\n }\n return { ok: false, detail: err instanceof Error ? err.message : String(err) };\n }\n }\n\n private async graphql(\n query: string,\n variables: Record<string, unknown>,\n signal?: AbortSignal,\n ): Promise<unknown> {\n let lastError: unknown;\n for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {\n signal?.throwIfAborted();\n const timeoutSignal = AbortSignal.timeout(this.timeoutMs);\n const combined = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;\n let response: Response;\n try {\n response = await this.fetchImpl(this.endpoint, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n },\n body: JSON.stringify({ query, variables }),\n signal: combined,\n });\n } catch (err) {\n if (signal?.aborted) throw err;\n lastError = err;\n if (attempt < MAX_RETRIES) {\n await this.sleep(backoffMs(attempt));\n continue;\n }\n throw new FirefliesApiError(\n `Fireflies API request failed after ${MAX_RETRIES + 1} attempts: ${describeNetworkError(err)}`,\n );\n }\n\n if (response.status === 429 || response.status >= 500) {\n lastError = new FirefliesApiError(`Fireflies API responded ${response.status}`, response.status);\n if (attempt < MAX_RETRIES) {\n await this.sleep(await retryDelayMs(response, attempt));\n continue;\n }\n throw lastError;\n }\n if (response.status === 401 || response.status === 403) {\n throw new FirefliesApiError(`Fireflies API responded ${response.status}`, response.status);\n }\n let body: unknown;\n try {\n body = await response.json();\n } catch {\n throw new FirefliesApiError(\"Fireflies API returned a non-JSON body\");\n }\n // GraphQL returns 200 with an `errors` array on failure. That is a\n // backend failure, not an empty result (AGENTS.md §22): surface it so\n // an expired token never reads as a quiet day.\n const errors = isRecord(body) ? body.errors : undefined;\n if (Array.isArray(errors) && errors.length > 0) {\n throw graphqlError(errors[0]);\n }\n const data = isRecord(body) ? body.data : undefined;\n if (data === undefined || data === null) {\n throw new FirefliesApiError(\"Fireflies API returned no data and no errors\");\n }\n return data;\n }\n throw lastError instanceof Error ? lastError : new FirefliesApiError(\"Fireflies API request failed\");\n }\n}\n\nfunction graphqlError(first: unknown): FirefliesApiError {\n const record = isRecord(first) ? first : undefined;\n const message = record && typeof record.message === \"string\" ? record.message : \"unknown GraphQL error\";\n const extensions = record && isRecord(record.extensions) ? record.extensions : undefined;\n const code = extensions && typeof extensions.code === \"string\" ? extensions.code : undefined;\n const authLike =\n code === \"unauthenticated\" ||\n code === \"forbidden\" ||\n /unauthor|forbidden|invalid.*(token|api key)|expired/i.test(message);\n return new FirefliesApiError(`Fireflies GraphQL error: ${message}`, authLike ? 401 : undefined);\n}\n\nfunction defaultSleep(ms: number): Promise<void> {\n const { promise, resolve } = Promise.withResolvers<void>();\n setTimeout(resolve, ms);\n return promise;\n}\n\n/**\n * Network/timeout failures wrap Node error text that can carry loader\n * paths or stack fragments; sync errors reach MCP clients verbatim, so\n * only the error name + code survive.\n */\nfunction describeNetworkError(err: unknown): string {\n if (isRecord(err)) {\n const name = typeof err.name === \"string\" ? err.name : \"Error\";\n const code = err.code;\n return typeof code === \"string\" ? `${name} (${code})` : name;\n }\n return \"network error\";\n}\n\n/** Loop instead of `/\\/+$/` — CodeQL js/polynomial-redos on user-set URLs. */\nfunction stripTrailingSlashes(value: string): string {\n let end = value.length;\n while (end > 0 && value.charCodeAt(end - 1) === 47) end--;\n return value.slice(0, end);\n}\n\nfunction backoffMs(attempt: number): number {\n return Math.min(MAX_RETRY_DELAY_MS, 1_000 * 2 ** attempt);\n}\n\nasync function retryDelayMs(response: Response, attempt: number): Promise<number> {\n const header = response.headers.get(\"retry-after\");\n if (header) {\n const seconds = Number(header);\n if (Number.isFinite(seconds) && seconds >= 0) {\n return Math.min(MAX_RETRY_DELAY_MS, seconds * 1_000);\n }\n const when = Date.parse(header);\n if (Number.isFinite(when)) {\n return Math.min(MAX_RETRY_DELAY_MS, Math.max(0, when - Date.now()));\n }\n }\n return backoffMs(attempt);\n}\n","/**\n * Normalize Fireflies.ai transcripts into Remnic's provider-agnostic\n * `WearableConversation` shape, plus the timezone-aware day-window helper\n * the Fireflies `transcripts(fromDate, toDate)` filter needs.\n *\n * Fireflies sentences carry `start_time`/`end_time` as second-offsets from the\n * meeting start (`date`, epoch ms); they are converted to absolute UTC here\n * (the same trap the Omi connector handles). Speaker turns come as\n * `speaker_name`/`speaker_id`; the wearables speaker registry owns final\n * naming, so we never guess `isWearer`. Meetings that expose a summary but no\n * transcript degrade to a single `note`-keyed segment — a note is still\n * day-anchored recall material.\n */\n\nimport {\n activityDayWindow,\n type WearableConversation,\n type WearableTranscriptSegment,\n} from \"@remnic/core\";\n\nimport type { FirefliesTranscript } from \"./client.js\";\n\nexport const FIREFLIES_SOURCE_ID = \"fireflies\";\n\n/**\n * Half-open [fromDate, toDate) UTC ISO bounds of a local day — the window the\n * Fireflies `transcripts` query filters on. Field names map to the Fireflies\n * API; the DST-aware window math is core's `activityDayWindow`.\n */\nexport function firefliesDayWindow(\n date: string,\n timezone: string,\n): { fromDate: string; toDate: string } {\n const { startUtc, endUtc } = activityDayWindow(date, timezone);\n return { fromDate: startUtc, toDate: endUtc };\n}\n\n/** Meeting start as epoch ms, from Fireflies' epoch-ms Float or ISO string. */\nfunction meetingStartMs(date: FirefliesTranscript[\"date\"]): number {\n if (typeof date === \"number\" && Number.isFinite(date)) return date;\n if (typeof date === \"string\") {\n const parsed = Date.parse(date);\n if (Number.isFinite(parsed)) return parsed;\n }\n return Number.NaN;\n}\n\nfunction offsetToIso(startMs: number, offsetSeconds: number | null | undefined): string | undefined {\n if (Number.isNaN(startMs)) return undefined;\n if (typeof offsetSeconds !== \"number\" || !Number.isFinite(offsetSeconds)) return undefined;\n return new Date(startMs + offsetSeconds * 1_000).toISOString();\n}\n\nexport function transcriptToConversation(transcript: FirefliesTranscript): WearableConversation {\n const startMs = meetingStartMs(transcript.date);\n const startIso = Number.isNaN(startMs) ? \"\" : new Date(startMs).toISOString();\n // Fireflies reports `duration` in minutes.\n const endIso =\n !Number.isNaN(startMs) && typeof transcript.duration === \"number\" && Number.isFinite(transcript.duration)\n ? new Date(startMs + transcript.duration * 60_000).toISOString()\n : undefined;\n\n const segments: WearableTranscriptSegment[] = [];\n for (const sentence of transcript.sentences ?? []) {\n const text = typeof sentence.text === \"string\" ? sentence.text.trim() : \"\";\n if (text.length === 0) continue;\n const speakerName =\n typeof sentence.speaker_name === \"string\" && sentence.speaker_name.trim().length > 0\n ? sentence.speaker_name.trim()\n : undefined;\n const speakerId =\n sentence.speaker_id !== null && sentence.speaker_id !== undefined\n ? String(sentence.speaker_id).trim()\n : undefined;\n const segStart = offsetToIso(startMs, sentence.start_time);\n const segEnd = offsetToIso(startMs, sentence.end_time);\n segments.push({\n text,\n speakerKey: speakerName ?? (speakerId && speakerId.length > 0 ? speakerId : \"unknown\"),\n ...(speakerName !== undefined ? { speakerName } : {}),\n ...(segStart !== undefined ? { startIso: segStart } : {}),\n ...(segEnd !== undefined ? { endIso: segEnd } : {}),\n });\n }\n\n const summaryText = firstNonEmpty(transcript.summary?.overview, transcript.summary?.short_summary);\n\n // Note-only fallback: a meeting with a summary but no transcript still\n // anchors recall for the day.\n if (segments.length === 0 && summaryText !== undefined) {\n segments.push({ text: summaryText, speakerKey: \"note\" });\n }\n\n const title = typeof transcript.title === \"string\" && transcript.title.trim().length > 0\n ? transcript.title.trim()\n : undefined;\n\n return {\n id: transcript.id,\n source: FIREFLIES_SOURCE_ID,\n ...(title !== undefined ? { title } : {}),\n ...(summaryText !== undefined ? { summary: summaryText } : {}),\n startIso,\n ...(endIso !== undefined ? { endIso } : {}),\n segments,\n };\n}\n\nfunction firstNonEmpty(...values: Array<string | null | undefined>): string | undefined {\n for (const value of values) {\n if (typeof value === \"string\" && value.trim().length > 0) return value.trim();\n }\n return undefined;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/client.ts","../src/normalize.ts"],"sourcesContent":["/**\n * @remnic/connector-fireflies — Fireflies.ai meeting-transcript connector.\n *\n * À-la-carte optional companion of @remnic/core: installing core alone\n * never pulls this in; core discovers it at runtime via a\n * computed-specifier dynamic import (see wearables/registry.ts) or via\n * a direct import of this module, which self-registers idempotently.\n *\n * API key: `wearables.sources.fireflies.apiKey`, else the\n * `REMNIC_FIREFLIES_API_KEY` / `FIREFLIES_API_KEY` environment\n * variables (checked in that order). Create a key under\n * Settings → Developer settings in the Fireflies app.\n */\n\nimport {\n selfRegisterWearableConnector,\n type WearableConnectorFactoryOptions,\n type WearableConnectorRegistration,\n type WearableFetchOptions,\n type WearableFetchPage,\n type WearableSourceConnector,\n} from \"@remnic/core\";\n\nimport { FirefliesClient } from \"./client.js\";\nimport { FIREFLIES_SOURCE_ID, firefliesDayWindow, transcriptToConversation } from \"./normalize.js\";\n\nexport {\n FirefliesClient,\n FirefliesApiError,\n FIREFLIES_DEFAULT_ENDPOINT,\n TRANSCRIPTS_MAX_PAGE_SIZE,\n} from \"./client.js\";\nexport type {\n FirefliesTranscript,\n FirefliesSentence,\n FirefliesSummary,\n FirefliesSpeaker,\n TranscriptsPage,\n FirefliesClientOptions,\n} from \"./client.js\";\nexport {\n FIREFLIES_SOURCE_ID,\n firefliesDayWindow,\n transcriptToConversation,\n} from \"./normalize.js\";\n\nconst FIREFLIES_DISPLAY_NAME = \"Fireflies.ai\";\n\nexport function resolveFirefliesApiKey(\n configuredKey: string | undefined,\n env: NodeJS.ProcessEnv = process.env,\n): string | undefined {\n if (typeof configuredKey === \"string\" && configuredKey.trim().length > 0) {\n return configuredKey.trim();\n }\n // REMNIC_* first, then the provider-conventional name.\n for (const name of [\"REMNIC_FIREFLIES_API_KEY\", \"FIREFLIES_API_KEY\"]) {\n const value = env[name];\n if (typeof value === \"string\" && value.trim().length > 0) {\n return value.trim();\n }\n }\n return undefined;\n}\n\nexport function createFirefliesConnector(\n options: WearableConnectorFactoryOptions,\n): WearableSourceConnector {\n // Key resolution happens lazily at call time (not factory time) so\n // `wearables status` works without credentials and env changes take\n // effect without a restart; the client constructor throws the\n // actionable missing-key message.\n let client: FirefliesClient | null = null;\n const getClient = (): FirefliesClient => {\n if (!client) {\n client = new FirefliesClient({\n apiKey: resolveFirefliesApiKey(options.settings.apiKey) ?? \"\",\n baseUrl: options.settings.baseUrl,\n });\n }\n return client;\n };\n\n return {\n id: FIREFLIES_SOURCE_ID,\n displayName: FIREFLIES_DISPLAY_NAME,\n async verifyAuth(signal?: AbortSignal) {\n return getClient().verifyAuth(signal);\n },\n async fetchConversations(opts: WearableFetchOptions): Promise<WearableFetchPage> {\n const { fromDate, toDate } = firefliesDayWindow(opts.date, opts.timezone);\n const skip = parseCursor(opts.cursor);\n const page = await getClient().listTranscripts({\n fromDate,\n toDate,\n skip,\n signal: opts.signal,\n });\n return {\n conversations: page.transcripts\n .map(transcriptToConversation)\n // A transcript with no resolvable meeting date can't produce a\n // valid conversation start; drop it rather than emit an empty\n // startIso that would fail downstream parsing silently.\n .filter((conversation) => conversation.startIso !== \"\"),\n // Advance the keyset by the raw page size (not the id-filtered count)\n // while full pages keep coming, so dropped rows never truncate the day.\n nextCursor: page.hadFullPage ? String(skip + page.rawCount) : null,\n };\n },\n };\n}\n\n/** Opaque cursor is the numeric `skip` offset; anything invalid restarts at 0. */\nfunction parseCursor(cursor: string | null | undefined): number {\n if (typeof cursor !== \"string\") return 0;\n const parsed = Number(cursor);\n return Number.isInteger(parsed) && parsed > 0 ? parsed : 0;\n}\n\nexport const wearableConnectorRegistration: WearableConnectorRegistration = {\n id: FIREFLIES_SOURCE_ID,\n displayName: FIREFLIES_DISPLAY_NAME,\n factory: createFirefliesConnector,\n};\n\n/**\n * Idempotently register the connector with the core registry. Importing\n * this module registers it as a side effect; calling this again is safe\n * (returns false when already registered).\n */\nexport const ensureFirefliesConnectorRegistered =\n selfRegisterWearableConnector(wearableConnectorRegistration);\n","/**\n * Minimal Fireflies.ai GraphQL API client (raw fetch, no SDK).\n *\n * API verified against https://docs.fireflies.ai/graphql-api/query/transcripts\n * and https://docs.fireflies.ai/fundamentals/authorization (fetched\n * 2026-07-21):\n * - Endpoint: POST https://api.fireflies.ai/graphql\n * - Auth: `Authorization: Bearer <api key>`\n * - `transcripts(fromDate, toDate, limit, skip)` where fromDate/toDate are\n * ISO-8601 date-time strings and `limit` maxes at 50.\n * - Each transcript exposes `id`, `title`, `date` (epoch ms), `duration`\n * (minutes), `summary { overview short_summary }`, `participants`, and\n * `sentences { index speaker_name speaker_id text start_time end_time }`\n * where start_time/end_time are second-offsets from the meeting start.\n *\n * GraphQL transport returns HTTP 200 with an `errors` array on query/auth\n * failure; those are surfaced as FirefliesApiError (a backend failure), never\n * as an empty result (AGENTS.md §22).\n */\nimport {\n ConnectorApiError,\n describeNetworkError,\n retryingFetch,\n stripTrailingSlashes,\n} from \"@remnic/core/http-retry\";\n\nexport const FIREFLIES_DEFAULT_ENDPOINT = \"https://api.fireflies.ai/graphql\";\n\n/** Hard API maximum for `limit` on the transcripts query. */\nexport const TRANSCRIPTS_MAX_PAGE_SIZE = 50;\n\nconst DEFAULT_TIMEOUT_MS = 30_000;\n\nconst TRANSCRIPTS_QUERY = `query RemnicTranscripts($fromDate: DateTime, $toDate: DateTime, $limit: Int, $skip: Int) {\n transcripts(fromDate: $fromDate, toDate: $toDate, limit: $limit, skip: $skip) {\n id\n title\n date\n duration\n participants\n summary { overview short_summary }\n speakers { id name }\n sentences { index speaker_name speaker_id text start_time end_time }\n }\n}`;\n\nconst AUTH_PROBE_QUERY = `query RemnicFirefliesAuthProbe { transcripts(limit: 1) { id } }`;\n\nexport interface FirefliesSentence {\n index?: number;\n speaker_name?: string | null;\n speaker_id?: number | string | null;\n text?: string | null;\n start_time?: number | null;\n end_time?: number | null;\n}\n\nexport interface FirefliesSummary {\n overview?: string | null;\n short_summary?: string | null;\n}\n\nexport interface FirefliesSpeaker {\n id?: number | string | null;\n name?: string | null;\n}\n\nexport interface FirefliesTranscript {\n id: string;\n title?: string | null;\n /** Meeting datetime — epoch milliseconds (Float) or an ISO string. */\n date?: number | string | null;\n /** Meeting duration in minutes (Fireflies reports minutes). */\n duration?: number | null;\n participants?: string[] | null;\n summary?: FirefliesSummary | null;\n speakers?: FirefliesSpeaker[] | null;\n sentences?: FirefliesSentence[] | null;\n}\n\nexport interface TranscriptsPage {\n transcripts: FirefliesTranscript[];\n /** True when a full page came back (more may exist beyond this skip). */\n hadFullPage: boolean;\n /** Raw row count Fireflies returned at this offset (drives the skip cursor). */\n rawCount: number;\n}\n\nexport interface FirefliesClientOptions {\n apiKey: string;\n /** Override the GraphQL endpoint (self-hosted / proxy setups). */\n baseUrl?: string;\n /** Injectable for tests. Defaults to global fetch. */\n fetchImpl?: typeof fetch;\n /** Per-request timeout (ms). */\n timeoutMs?: number;\n /** Injectable sleep for tests. */\n sleep?: (ms: number) => Promise<void>;\n}\n\nexport class FirefliesApiError extends ConnectorApiError {\n constructor(\n message: string,\n status?: number,\n ) {\n super(message, status);\n this.name = \"FirefliesApiError\";\n }\n}\n\n/** Narrow unknown JSON to a plain object so field reads are actually checked. */\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\nexport class FirefliesClient {\n private readonly apiKey: string;\n private readonly endpoint: string;\n private readonly timeoutMs: number;\n private readonly fetchImpl: typeof fetch;\n private readonly sleep: ((ms: number) => Promise<void>) | undefined;\n\n constructor(options: FirefliesClientOptions) {\n if (typeof options.apiKey !== \"string\" || options.apiKey.trim().length === 0) {\n throw new FirefliesApiError(\n \"Fireflies API key is missing. Set wearables.sources.fireflies.apiKey \" +\n \"or the REMNIC_FIREFLIES_API_KEY / FIREFLIES_API_KEY environment variable \" +\n \"(create a key under Settings → Developer settings in the Fireflies app).\",\n );\n }\n this.apiKey = options.apiKey.trim();\n this.endpoint = stripTrailingSlashes(options.baseUrl ?? FIREFLIES_DEFAULT_ENDPOINT);\n this.fetchImpl = options.fetchImpl ?? fetch;\n this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this.sleep = options.sleep;\n }\n\n /** One page of transcripts created inside the [fromDate, toDate) window. */\n async listTranscripts(params: {\n fromDate: string;\n toDate: string;\n skip?: number;\n signal?: AbortSignal;\n }): Promise<TranscriptsPage> {\n const skip = Number.isInteger(params.skip) && (params.skip as number) > 0 ? (params.skip as number) : 0;\n const data = await this.graphql(\n TRANSCRIPTS_QUERY,\n { fromDate: params.fromDate, toDate: params.toDate, limit: TRANSCRIPTS_MAX_PAGE_SIZE, skip },\n params.signal,\n );\n const raw = isRecord(data) ? data.transcripts : undefined;\n if (!Array.isArray(raw)) {\n throw new FirefliesApiError(\n \"Fireflies API returned an unexpected transcripts shape (missing data.transcripts array)\",\n );\n }\n const transcripts = raw.filter(\n (entry): entry is FirefliesTranscript => isRecord(entry) && typeof entry.id === \"string\",\n );\n return { transcripts, hadFullPage: raw.length >= TRANSCRIPTS_MAX_PAGE_SIZE, rawCount: raw.length };\n }\n\n /** Cheap auth probe. */\n async verifyAuth(signal?: AbortSignal): Promise<{ ok: boolean; detail?: string }> {\n try {\n await this.graphql(AUTH_PROBE_QUERY, {}, signal);\n return { ok: true };\n } catch (err) {\n if (err instanceof FirefliesApiError && (err.status === 401 || err.status === 403)) {\n return {\n ok: false,\n detail:\n \"Fireflies rejected the API key (401/403) — create a new key under Settings → Developer settings\",\n };\n }\n return { ok: false, detail: err instanceof Error ? err.message : String(err) };\n }\n }\n\n private async graphql(\n query: string,\n variables: Record<string, unknown>,\n signal?: AbortSignal,\n ): Promise<unknown> {\n const response = await retryingFetch(this.endpoint, {\n init: {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n },\n body: JSON.stringify({ query, variables }),\n },\n fetchImpl: this.fetchImpl,\n sleep: this.sleep,\n signal,\n timeoutMs: this.timeoutMs,\n networkError: (err, attempts) =>\n new FirefliesApiError(\n `Fireflies API request failed after ${attempts} attempts: ${describeNetworkError(err)}`,\n ),\n retryableError: (retryable) =>\n new FirefliesApiError(`Fireflies API responded ${retryable.status}`, retryable.status),\n });\n if (response.status === 401 || response.status === 403) {\n throw new FirefliesApiError(`Fireflies API responded ${response.status}`, response.status);\n }\n let body: unknown;\n try {\n body = await response.json();\n } catch {\n throw new FirefliesApiError(\"Fireflies API returned a non-JSON body\");\n }\n // GraphQL returns 200 with an `errors` array on failure. That is a\n // backend failure, not an empty result (AGENTS.md §22): surface it so\n // an expired token never reads as a quiet day.\n const errors = isRecord(body) ? body.errors : undefined;\n if (Array.isArray(errors) && errors.length > 0) {\n throw graphqlError(errors[0]);\n }\n const data = isRecord(body) ? body.data : undefined;\n if (data === undefined || data === null) {\n throw new FirefliesApiError(\"Fireflies API returned no data and no errors\");\n }\n return data;\n }\n}\n\nfunction graphqlError(first: unknown): FirefliesApiError {\n const record = isRecord(first) ? first : undefined;\n const message = record && typeof record.message === \"string\" ? record.message : \"unknown GraphQL error\";\n const extensions = record && isRecord(record.extensions) ? record.extensions : undefined;\n const code = extensions && typeof extensions.code === \"string\" ? extensions.code : undefined;\n const authLike =\n code === \"unauthenticated\" ||\n code === \"forbidden\" ||\n /unauthor|forbidden|invalid.*(token|api key)|expired/i.test(message);\n return new FirefliesApiError(`Fireflies GraphQL error: ${message}`, authLike ? 401 : undefined);\n}\n","/**\n * Normalize Fireflies.ai transcripts into Remnic's provider-agnostic\n * `WearableConversation` shape, plus the timezone-aware day-window helper\n * the Fireflies `transcripts(fromDate, toDate)` filter needs.\n *\n * Fireflies sentences carry `start_time`/`end_time` as second-offsets from the\n * meeting start (`date`, epoch ms); they are converted to absolute UTC here\n * (the same trap the Omi connector handles). Speaker turns come as\n * `speaker_name`/`speaker_id`; the wearables speaker registry owns final\n * naming, so we never guess `isWearer`. Meetings that expose a summary but no\n * transcript degrade to a single `note`-keyed segment — a note is still\n * day-anchored recall material.\n */\n\nimport {\n activityDayWindow,\n type WearableConversation,\n type WearableTranscriptSegment,\n} from \"@remnic/core\";\n\nimport type { FirefliesTranscript } from \"./client.js\";\n\nexport const FIREFLIES_SOURCE_ID = \"fireflies\";\n\n/**\n * Half-open [fromDate, toDate) UTC ISO bounds of a local day — the window the\n * Fireflies `transcripts` query filters on. Field names map to the Fireflies\n * API; the DST-aware window math is core's `activityDayWindow`.\n */\nexport function firefliesDayWindow(\n date: string,\n timezone: string,\n): { fromDate: string; toDate: string } {\n const { startUtc, endUtc } = activityDayWindow(date, timezone);\n return { fromDate: startUtc, toDate: endUtc };\n}\n\n/** Meeting start as epoch ms, from Fireflies' epoch-ms Float or ISO string. */\nfunction meetingStartMs(date: FirefliesTranscript[\"date\"]): number {\n if (typeof date === \"number\" && Number.isFinite(date)) return date;\n if (typeof date === \"string\") {\n const parsed = Date.parse(date);\n if (Number.isFinite(parsed)) return parsed;\n }\n return Number.NaN;\n}\n\nfunction offsetToIso(startMs: number, offsetSeconds: number | null | undefined): string | undefined {\n if (Number.isNaN(startMs)) return undefined;\n if (typeof offsetSeconds !== \"number\" || !Number.isFinite(offsetSeconds)) return undefined;\n return new Date(startMs + offsetSeconds * 1_000).toISOString();\n}\n\nexport function transcriptToConversation(transcript: FirefliesTranscript): WearableConversation {\n const startMs = meetingStartMs(transcript.date);\n const startIso = Number.isNaN(startMs) ? \"\" : new Date(startMs).toISOString();\n // Fireflies reports `duration` in minutes.\n const endIso =\n !Number.isNaN(startMs) && typeof transcript.duration === \"number\" && Number.isFinite(transcript.duration)\n ? new Date(startMs + transcript.duration * 60_000).toISOString()\n : undefined;\n\n const segments: WearableTranscriptSegment[] = [];\n for (const sentence of transcript.sentences ?? []) {\n const text = typeof sentence.text === \"string\" ? sentence.text.trim() : \"\";\n if (text.length === 0) continue;\n const speakerName =\n typeof sentence.speaker_name === \"string\" && sentence.speaker_name.trim().length > 0\n ? sentence.speaker_name.trim()\n : undefined;\n const speakerId =\n sentence.speaker_id !== null && sentence.speaker_id !== undefined\n ? String(sentence.speaker_id).trim()\n : undefined;\n const segStart = offsetToIso(startMs, sentence.start_time);\n const segEnd = offsetToIso(startMs, sentence.end_time);\n segments.push({\n text,\n speakerKey: speakerName ?? (speakerId && speakerId.length > 0 ? speakerId : \"unknown\"),\n ...(speakerName !== undefined ? { speakerName } : {}),\n ...(segStart !== undefined ? { startIso: segStart } : {}),\n ...(segEnd !== undefined ? { endIso: segEnd } : {}),\n });\n }\n\n const summaryText = firstNonEmpty(transcript.summary?.overview, transcript.summary?.short_summary);\n\n // Note-only fallback: a meeting with a summary but no transcript still\n // anchors recall for the day.\n if (segments.length === 0 && summaryText !== undefined) {\n segments.push({ text: summaryText, speakerKey: \"note\" });\n }\n\n const title = typeof transcript.title === \"string\" && transcript.title.trim().length > 0\n ? transcript.title.trim()\n : undefined;\n\n return {\n id: transcript.id,\n source: FIREFLIES_SOURCE_ID,\n ...(title !== undefined ? { title } : {}),\n ...(summaryText !== undefined ? { summary: summaryText } : {}),\n startIso,\n ...(endIso !== undefined ? { endIso } : {}),\n segments,\n };\n}\n\nfunction firstNonEmpty(...values: Array<string | null | undefined>): string | undefined {\n for (const value of values) {\n if (typeof value === \"string\" && value.trim().length > 0) return value.trim();\n }\n return undefined;\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remnic/connector-fireflies",
|
|
3
|
-
"version": "9.69.
|
|
3
|
+
"version": "9.69.28",
|
|
4
4
|
"description": "Fireflies.ai meeting-transcript connector for Remnic — pull, clean, and remember meeting transcripts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -19,13 +19,13 @@
|
|
|
19
19
|
"provenance": true
|
|
20
20
|
},
|
|
21
21
|
"peerDependencies": {
|
|
22
|
-
"@remnic/core": "^9.69.
|
|
22
|
+
"@remnic/core": "^9.69.28"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"tsup": "^8.0.0",
|
|
26
26
|
"typescript": "^5.7.0",
|
|
27
27
|
"tsx": "^4.0.0",
|
|
28
|
-
"@remnic/core": "9.69.
|
|
28
|
+
"@remnic/core": "9.69.28"
|
|
29
29
|
},
|
|
30
30
|
"license": "MIT",
|
|
31
31
|
"repository": {
|