@remnic/connector-limitless 9.69.22 → 9.69.24

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 CHANGED
@@ -111,6 +111,6 @@ declare const wearableConnectorRegistration: WearableConnectorRegistration;
111
111
  * this module registers it as a side effect; calling this again is safe
112
112
  * (returns false when already registered).
113
113
  */
114
- declare function ensureLimitlessConnectorRegistered(): boolean;
114
+ declare const ensureLimitlessConnectorRegistered: () => boolean;
115
115
 
116
116
  export { LIFELOGS_MAX_PAGE_SIZE, LIMITLESS_DEFAULT_BASE_URL, LIMITLESS_SOURCE_ID, type LifelogsPage, LimitlessApiError, LimitlessClient, type LimitlessClientOptions, type LimitlessContentNode, type LimitlessLifelog, createLimitlessConnector, ensureLimitlessConnectorRegistered, lifelogToConversation, resolveLimitlessApiKey, wearableConnectorRegistration };
package/dist/index.js CHANGED
@@ -2,8 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import {
5
- registerWearableConnector,
6
- getWearableConnector
5
+ selfRegisterWearableConnector
7
6
  } from "@remnic/core";
8
7
 
9
8
  // src/client.ts
@@ -276,14 +275,7 @@ var wearableConnectorRegistration = {
276
275
  displayName: "Limitless Pendant",
277
276
  factory: createLimitlessConnector
278
277
  };
279
- function ensureLimitlessConnectorRegistered() {
280
- if (getWearableConnector(LIMITLESS_SOURCE_ID) !== void 0) {
281
- return false;
282
- }
283
- registerWearableConnector(wearableConnectorRegistration);
284
- return true;
285
- }
286
- ensureLimitlessConnectorRegistered();
278
+ var ensureLimitlessConnectorRegistered = selfRegisterWearableConnector(wearableConnectorRegistration);
287
279
  export {
288
280
  LIFELOGS_MAX_PAGE_SIZE,
289
281
  LIMITLESS_DEFAULT_BASE_URL,
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-limitless — Limitless.ai Pendant 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.limitless.apiKey`, else the\n * `REMNIC_LIMITLESS_API_KEY` / `LIMITLESS_API_KEY` environment\n * variables (checked in that order).\n */\n\nimport {\n registerWearableConnector,\n getWearableConnector,\n type WearableConnectorFactoryOptions,\n type WearableConnectorRegistration,\n type WearableFetchOptions,\n type WearableFetchPage,\n type WearableSourceConnector,\n} from \"@remnic/core\";\n\nimport { LimitlessClient } from \"./client.js\";\nimport { lifelogToConversation, LIMITLESS_SOURCE_ID } from \"./normalize.js\";\n\nexport { LimitlessClient, LimitlessApiError, LIFELOGS_MAX_PAGE_SIZE, LIMITLESS_DEFAULT_BASE_URL } from \"./client.js\";\nexport type {\n LifelogsPage,\n LimitlessClientOptions,\n LimitlessContentNode,\n LimitlessLifelog,\n} from \"./client.js\";\nexport { lifelogToConversation, LIMITLESS_SOURCE_ID } from \"./normalize.js\";\n\nexport function resolveLimitlessApiKey(\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_LIMITLESS_API_KEY\", \"LIMITLESS_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 createLimitlessConnector(\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: LimitlessClient | null = null;\n const getClient = (): LimitlessClient => {\n if (!client) {\n client = new LimitlessClient({\n apiKey: resolveLimitlessApiKey(options.settings.apiKey) ?? \"\",\n baseUrl: options.settings.baseUrl,\n });\n }\n return client;\n };\n\n return {\n id: LIMITLESS_SOURCE_ID,\n displayName: \"Limitless Pendant\",\n async verifyAuth(signal?: AbortSignal) {\n return getClient().verifyAuth(signal);\n },\n async fetchConversations(\n opts: WearableFetchOptions,\n ): Promise<WearableFetchPage> {\n const page = await getClient().listLifelogs({\n date: opts.date,\n timezone: opts.timezone,\n cursor: opts.cursor,\n signal: opts.signal,\n });\n return {\n conversations: page.lifelogs.map(lifelogToConversation),\n nextCursor: page.nextCursor,\n };\n },\n };\n}\n\nexport const wearableConnectorRegistration: WearableConnectorRegistration = {\n id: LIMITLESS_SOURCE_ID,\n displayName: \"Limitless Pendant\",\n factory: createLimitlessConnector,\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 function ensureLimitlessConnectorRegistered(): boolean {\n if (getWearableConnector(LIMITLESS_SOURCE_ID) !== undefined) {\n return false;\n }\n registerWearableConnector(wearableConnectorRegistration);\n return true;\n}\n\nensureLimitlessConnectorRegistered();\n","/**\n * Minimal Limitless Developer API client (raw fetch, no SDK).\n *\n * Contract verified against https://www.limitless.ai/developers/docs/api\n * (2026-06): base https://api.limitless.ai, header `X-API-Key`,\n * `GET /v1/lifelogs` with cursor pagination at `meta.lifelogs.nextCursor`\n * and a hard per-page max of 10. Rate limit is 180 req/min; 429 bodies\n * carry `retryAfter` (a string, in seconds).\n *\n * The API key is never logged and never included in thrown error\n * messages.\n */\n\nexport const LIMITLESS_DEFAULT_BASE_URL = \"https://api.limitless.ai\";\n\n/** Hard API maximum for `limit` on /v1/lifelogs. */\nexport const LIFELOGS_MAX_PAGE_SIZE = 10;\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\nexport interface LimitlessContentNode {\n type: string;\n content?: string;\n startTime?: string;\n endTime?: string;\n startOffsetMs?: number;\n endOffsetMs?: number;\n children?: LimitlessContentNode[];\n speakerName?: string | null;\n speakerIdentifier?: \"user\" | null;\n}\n\nexport interface LimitlessLifelog {\n id: string;\n title?: string;\n markdown?: string | null;\n startTime?: string;\n endTime?: string;\n isStarred?: boolean;\n updatedAt?: string;\n contents?: LimitlessContentNode[];\n}\n\nexport interface LifelogsPage {\n lifelogs: LimitlessLifelog[];\n nextCursor: string | null;\n}\n\nexport interface LimitlessClientOptions {\n apiKey: string;\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 LimitlessApiError extends Error {\n constructor(\n message: string,\n readonly status?: number,\n ) {\n super(message);\n this.name = \"LimitlessApiError\";\n }\n}\n\nexport class LimitlessClient {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly fetchImpl: typeof fetch;\n private readonly timeoutMs: number;\n private readonly sleep: (ms: number) => Promise<void>;\n\n constructor(options: LimitlessClientOptions) {\n if (typeof options.apiKey !== \"string\" || options.apiKey.trim().length === 0) {\n throw new LimitlessApiError(\n \"Limitless API key is missing. Set wearables.sources.limitless.apiKey \" +\n \"or the LIMITLESS_API_KEY environment variable (create a key under \" +\n \"Developer settings in the Limitless app).\",\n );\n }\n this.apiKey = options.apiKey.trim();\n this.baseUrl = stripTrailingSlashes(options.baseUrl ?? LIMITLESS_DEFAULT_BASE_URL);\n this.fetchImpl = options.fetchImpl ?? fetch;\n this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this.sleep =\n options.sleep ?? ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms)));\n }\n\n /** One page of lifelogs for a single day. */\n async listLifelogs(params: {\n date: string;\n timezone: string;\n cursor?: string | null;\n signal?: AbortSignal;\n }): Promise<LifelogsPage> {\n const search = new URLSearchParams({\n date: params.date,\n timezone: params.timezone,\n limit: String(LIFELOGS_MAX_PAGE_SIZE),\n direction: \"asc\",\n // The markdown rendering duplicates what `contents` carries and\n // inflates payloads; segments come from `contents`.\n includeMarkdown: \"false\",\n includeHeadings: \"false\",\n includeContents: \"true\",\n });\n if (typeof params.cursor === \"string\" && params.cursor.length > 0) {\n search.set(\"cursor\", params.cursor);\n }\n const payload = await this.requestJson(\n `/v1/lifelogs?${search.toString()}`,\n params.signal,\n );\n const data = (payload as { data?: { lifelogs?: unknown } }).data;\n const lifelogsRaw = data?.lifelogs;\n if (!Array.isArray(lifelogsRaw)) {\n throw new LimitlessApiError(\n \"Limitless API returned an unexpected /v1/lifelogs shape (missing data.lifelogs array)\",\n );\n }\n const meta = (payload as { meta?: { lifelogs?: { nextCursor?: unknown } } }).meta;\n const nextCursorRaw = meta?.lifelogs?.nextCursor;\n return {\n lifelogs: lifelogsRaw.filter(\n (entry): entry is LimitlessLifelog =>\n entry !== null && typeof entry === \"object\" && typeof (entry as { id?: unknown }).id === \"string\",\n ),\n nextCursor:\n typeof nextCursorRaw === \"string\" && nextCursorRaw.length > 0\n ? nextCursorRaw\n : null,\n };\n }\n\n /** Cheap auth probe. */\n async verifyAuth(signal?: AbortSignal): Promise<{ ok: boolean; detail?: string }> {\n try {\n const search = new URLSearchParams({\n limit: \"1\",\n includeMarkdown: \"false\",\n includeHeadings: \"false\",\n });\n await this.requestJson(`/v1/lifelogs?${search.toString()}`, signal);\n return { ok: true };\n } catch (err) {\n if (err instanceof LimitlessApiError && (err.status === 401 || err.status === 403)) {\n return {\n ok: false,\n detail: \"Limitless rejected the API key (401/403) — create a new key under Developer settings\",\n };\n }\n return {\n ok: false,\n detail: err instanceof Error ? err.message : String(err),\n };\n }\n }\n\n private async requestJson(pathAndQuery: string, signal?: AbortSignal): 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.baseUrl}${pathAndQuery}`, {\n method: \"GET\",\n headers: {\n \"X-API-Key\": this.apiKey,\n Accept: \"application/json\",\n },\n signal: combined,\n });\n } catch (err) {\n // Network failure / timeout: retry unless the caller aborted.\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 LimitlessApiError(\n `Limitless API request failed after ${MAX_RETRIES + 1} attempts: ${describeNetworkError(err)}`,\n );\n }\n\n if (response.status === 429 || response.status >= 500) {\n lastError = new LimitlessApiError(\n `Limitless API responded ${response.status}`,\n response.status,\n );\n if (attempt < MAX_RETRIES) {\n await this.sleep(await retryDelayMs(response, attempt));\n continue;\n }\n throw lastError;\n }\n if (!response.ok) {\n throw new LimitlessApiError(\n `Limitless API responded ${response.status} for ${pathAndQuery.split(\"?\")[0]}`,\n response.status,\n );\n }\n try {\n return await response.json();\n } catch {\n throw new LimitlessApiError(\"Limitless API returned a non-JSON body\");\n }\n }\n // Unreachable: every loop path returns or throws. Keep the throw for\n // exhaustiveness.\n throw lastError instanceof Error\n ? lastError\n : new LimitlessApiError(\"Limitless API request failed\");\n }\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 (Cursor review on PR #1458).\n */\nfunction describeNetworkError(err: unknown): string {\n if (!(err instanceof Error)) return \"unexpected non-Error failure\";\n const code = (err as NodeJS.ErrnoException).code;\n return typeof code === \"string\" && code.length > 0 ? `${err.name} (${code})` : err.name;\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) === 0x2f) 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 // 429 bodies carry retryAfter as a STRING of seconds (per docs); a\n // Retry-After header may also appear. Either is honored, capped.\n const headerValue = response.headers.get(\"retry-after\");\n let seconds: number | undefined;\n if (headerValue !== null) {\n const parsed = Number(headerValue);\n if (Number.isFinite(parsed) && parsed > 0) seconds = parsed;\n }\n if (seconds === undefined) {\n try {\n const body = (await response.clone().json()) as { retryAfter?: unknown };\n const parsed = Number(body?.retryAfter);\n if (Number.isFinite(parsed) && parsed > 0) seconds = parsed;\n } catch {\n // Body unavailable or non-JSON — fall through to exponential backoff.\n }\n }\n if (seconds !== undefined) {\n return Math.min(MAX_RETRY_DELAY_MS, Math.ceil(seconds * 1_000));\n }\n return backoffMs(attempt);\n}\n","/**\n * Normalize Limitless lifelogs into Remnic's provider-agnostic\n * `WearableConversation` shape.\n *\n * Spoken dialogue lives in `blockquote` content nodes (possibly nested\n * under headings via `children`); heading nodes structure the lifelog\n * and are not utterances. The wearer is marked by\n * `speakerIdentifier === \"user\"`; everyone else carries a\n * `speakerName` (\"Speaker 2\" or a saved name).\n */\n\nimport type {\n WearableConversation,\n WearableTranscriptSegment,\n} from \"@remnic/core\";\n\nimport type { LimitlessContentNode, LimitlessLifelog } from \"./client.js\";\n\nexport const LIMITLESS_SOURCE_ID = \"limitless\";\n\n/** Guard against pathological nesting in provider payloads. */\nconst MAX_NODE_DEPTH = 16;\n\nexport function lifelogToConversation(\n lifelog: LimitlessLifelog,\n): WearableConversation {\n const segments: WearableTranscriptSegment[] = [];\n collectSegments(lifelog.contents ?? [], segments, 0);\n return {\n id: lifelog.id,\n source: LIMITLESS_SOURCE_ID,\n title: typeof lifelog.title === \"string\" ? lifelog.title : undefined,\n startIso: lifelog.startTime ?? \"\",\n endIso: lifelog.endTime,\n segments,\n };\n}\n\nfunction collectSegments(\n nodes: LimitlessContentNode[],\n out: WearableTranscriptSegment[],\n depth: number,\n): void {\n if (depth > MAX_NODE_DEPTH) return;\n for (const node of nodes) {\n if (node === null || typeof node !== \"object\") continue;\n if (node.type === \"blockquote\" && typeof node.content === \"string\") {\n const text = node.content.trim();\n if (text.length > 0) {\n const isWearer = node.speakerIdentifier === \"user\";\n const speakerName =\n typeof node.speakerName === \"string\" && node.speakerName.trim().length > 0\n ? node.speakerName.trim()\n : undefined;\n out.push({\n text,\n // The wearer's stable key is \"user\"; other speakers key by the\n // provider name so saved names survive across lifelogs and the\n // registry can re-label \"Speaker 2\" everywhere at once.\n speakerKey: isWearer ? \"user\" : (speakerName ?? \"unknown\"),\n ...(speakerName !== undefined ? { speakerName } : {}),\n ...(isWearer ? { isWearer: true } : {}),\n ...(typeof node.startTime === \"string\" ? { startIso: node.startTime } : {}),\n ...(typeof node.endTime === \"string\" ? { endIso: node.endTime } : {}),\n });\n }\n }\n if (Array.isArray(node.children) && node.children.length > 0) {\n collectSegments(node.children, out, depth + 1);\n }\n }\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-limitless — Limitless.ai Pendant 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.limitless.apiKey`, else the\n * `REMNIC_LIMITLESS_API_KEY` / `LIMITLESS_API_KEY` environment\n * variables (checked in that order).\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 { LimitlessClient } from \"./client.js\";\nimport { lifelogToConversation, LIMITLESS_SOURCE_ID } from \"./normalize.js\";\n\nexport { LimitlessClient, LimitlessApiError, LIFELOGS_MAX_PAGE_SIZE, LIMITLESS_DEFAULT_BASE_URL } from \"./client.js\";\nexport type {\n LifelogsPage,\n LimitlessClientOptions,\n LimitlessContentNode,\n LimitlessLifelog,\n} from \"./client.js\";\nexport { lifelogToConversation, LIMITLESS_SOURCE_ID } from \"./normalize.js\";\n\nexport function resolveLimitlessApiKey(\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_LIMITLESS_API_KEY\", \"LIMITLESS_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 createLimitlessConnector(\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: LimitlessClient | null = null;\n const getClient = (): LimitlessClient => {\n if (!client) {\n client = new LimitlessClient({\n apiKey: resolveLimitlessApiKey(options.settings.apiKey) ?? \"\",\n baseUrl: options.settings.baseUrl,\n });\n }\n return client;\n };\n\n return {\n id: LIMITLESS_SOURCE_ID,\n displayName: \"Limitless Pendant\",\n async verifyAuth(signal?: AbortSignal) {\n return getClient().verifyAuth(signal);\n },\n async fetchConversations(\n opts: WearableFetchOptions,\n ): Promise<WearableFetchPage> {\n const page = await getClient().listLifelogs({\n date: opts.date,\n timezone: opts.timezone,\n cursor: opts.cursor,\n signal: opts.signal,\n });\n return {\n conversations: page.lifelogs.map(lifelogToConversation),\n nextCursor: page.nextCursor,\n };\n },\n };\n}\n\nexport const wearableConnectorRegistration: WearableConnectorRegistration = {\n id: LIMITLESS_SOURCE_ID,\n displayName: \"Limitless Pendant\",\n factory: createLimitlessConnector,\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 ensureLimitlessConnectorRegistered =\n selfRegisterWearableConnector(wearableConnectorRegistration);\n","/**\n * Minimal Limitless Developer API client (raw fetch, no SDK).\n *\n * Contract verified against https://www.limitless.ai/developers/docs/api\n * (2026-06): base https://api.limitless.ai, header `X-API-Key`,\n * `GET /v1/lifelogs` with cursor pagination at `meta.lifelogs.nextCursor`\n * and a hard per-page max of 10. Rate limit is 180 req/min; 429 bodies\n * carry `retryAfter` (a string, in seconds).\n *\n * The API key is never logged and never included in thrown error\n * messages.\n */\n\nexport const LIMITLESS_DEFAULT_BASE_URL = \"https://api.limitless.ai\";\n\n/** Hard API maximum for `limit` on /v1/lifelogs. */\nexport const LIFELOGS_MAX_PAGE_SIZE = 10;\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\nexport interface LimitlessContentNode {\n type: string;\n content?: string;\n startTime?: string;\n endTime?: string;\n startOffsetMs?: number;\n endOffsetMs?: number;\n children?: LimitlessContentNode[];\n speakerName?: string | null;\n speakerIdentifier?: \"user\" | null;\n}\n\nexport interface LimitlessLifelog {\n id: string;\n title?: string;\n markdown?: string | null;\n startTime?: string;\n endTime?: string;\n isStarred?: boolean;\n updatedAt?: string;\n contents?: LimitlessContentNode[];\n}\n\nexport interface LifelogsPage {\n lifelogs: LimitlessLifelog[];\n nextCursor: string | null;\n}\n\nexport interface LimitlessClientOptions {\n apiKey: string;\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 LimitlessApiError extends Error {\n constructor(\n message: string,\n readonly status?: number,\n ) {\n super(message);\n this.name = \"LimitlessApiError\";\n }\n}\n\nexport class LimitlessClient {\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly fetchImpl: typeof fetch;\n private readonly timeoutMs: number;\n private readonly sleep: (ms: number) => Promise<void>;\n\n constructor(options: LimitlessClientOptions) {\n if (typeof options.apiKey !== \"string\" || options.apiKey.trim().length === 0) {\n throw new LimitlessApiError(\n \"Limitless API key is missing. Set wearables.sources.limitless.apiKey \" +\n \"or the LIMITLESS_API_KEY environment variable (create a key under \" +\n \"Developer settings in the Limitless app).\",\n );\n }\n this.apiKey = options.apiKey.trim();\n this.baseUrl = stripTrailingSlashes(options.baseUrl ?? LIMITLESS_DEFAULT_BASE_URL);\n this.fetchImpl = options.fetchImpl ?? fetch;\n this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this.sleep =\n options.sleep ?? ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms)));\n }\n\n /** One page of lifelogs for a single day. */\n async listLifelogs(params: {\n date: string;\n timezone: string;\n cursor?: string | null;\n signal?: AbortSignal;\n }): Promise<LifelogsPage> {\n const search = new URLSearchParams({\n date: params.date,\n timezone: params.timezone,\n limit: String(LIFELOGS_MAX_PAGE_SIZE),\n direction: \"asc\",\n // The markdown rendering duplicates what `contents` carries and\n // inflates payloads; segments come from `contents`.\n includeMarkdown: \"false\",\n includeHeadings: \"false\",\n includeContents: \"true\",\n });\n if (typeof params.cursor === \"string\" && params.cursor.length > 0) {\n search.set(\"cursor\", params.cursor);\n }\n const payload = await this.requestJson(\n `/v1/lifelogs?${search.toString()}`,\n params.signal,\n );\n const data = (payload as { data?: { lifelogs?: unknown } }).data;\n const lifelogsRaw = data?.lifelogs;\n if (!Array.isArray(lifelogsRaw)) {\n throw new LimitlessApiError(\n \"Limitless API returned an unexpected /v1/lifelogs shape (missing data.lifelogs array)\",\n );\n }\n const meta = (payload as { meta?: { lifelogs?: { nextCursor?: unknown } } }).meta;\n const nextCursorRaw = meta?.lifelogs?.nextCursor;\n return {\n lifelogs: lifelogsRaw.filter(\n (entry): entry is LimitlessLifelog =>\n entry !== null && typeof entry === \"object\" && typeof (entry as { id?: unknown }).id === \"string\",\n ),\n nextCursor:\n typeof nextCursorRaw === \"string\" && nextCursorRaw.length > 0\n ? nextCursorRaw\n : null,\n };\n }\n\n /** Cheap auth probe. */\n async verifyAuth(signal?: AbortSignal): Promise<{ ok: boolean; detail?: string }> {\n try {\n const search = new URLSearchParams({\n limit: \"1\",\n includeMarkdown: \"false\",\n includeHeadings: \"false\",\n });\n await this.requestJson(`/v1/lifelogs?${search.toString()}`, signal);\n return { ok: true };\n } catch (err) {\n if (err instanceof LimitlessApiError && (err.status === 401 || err.status === 403)) {\n return {\n ok: false,\n detail: \"Limitless rejected the API key (401/403) — create a new key under Developer settings\",\n };\n }\n return {\n ok: false,\n detail: err instanceof Error ? err.message : String(err),\n };\n }\n }\n\n private async requestJson(pathAndQuery: string, signal?: AbortSignal): 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.baseUrl}${pathAndQuery}`, {\n method: \"GET\",\n headers: {\n \"X-API-Key\": this.apiKey,\n Accept: \"application/json\",\n },\n signal: combined,\n });\n } catch (err) {\n // Network failure / timeout: retry unless the caller aborted.\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 LimitlessApiError(\n `Limitless API request failed after ${MAX_RETRIES + 1} attempts: ${describeNetworkError(err)}`,\n );\n }\n\n if (response.status === 429 || response.status >= 500) {\n lastError = new LimitlessApiError(\n `Limitless API responded ${response.status}`,\n response.status,\n );\n if (attempt < MAX_RETRIES) {\n await this.sleep(await retryDelayMs(response, attempt));\n continue;\n }\n throw lastError;\n }\n if (!response.ok) {\n throw new LimitlessApiError(\n `Limitless API responded ${response.status} for ${pathAndQuery.split(\"?\")[0]}`,\n response.status,\n );\n }\n try {\n return await response.json();\n } catch {\n throw new LimitlessApiError(\"Limitless API returned a non-JSON body\");\n }\n }\n // Unreachable: every loop path returns or throws. Keep the throw for\n // exhaustiveness.\n throw lastError instanceof Error\n ? lastError\n : new LimitlessApiError(\"Limitless API request failed\");\n }\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 (Cursor review on PR #1458).\n */\nfunction describeNetworkError(err: unknown): string {\n if (!(err instanceof Error)) return \"unexpected non-Error failure\";\n const code = (err as NodeJS.ErrnoException).code;\n return typeof code === \"string\" && code.length > 0 ? `${err.name} (${code})` : err.name;\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) === 0x2f) 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 // 429 bodies carry retryAfter as a STRING of seconds (per docs); a\n // Retry-After header may also appear. Either is honored, capped.\n const headerValue = response.headers.get(\"retry-after\");\n let seconds: number | undefined;\n if (headerValue !== null) {\n const parsed = Number(headerValue);\n if (Number.isFinite(parsed) && parsed > 0) seconds = parsed;\n }\n if (seconds === undefined) {\n try {\n const body = (await response.clone().json()) as { retryAfter?: unknown };\n const parsed = Number(body?.retryAfter);\n if (Number.isFinite(parsed) && parsed > 0) seconds = parsed;\n } catch {\n // Body unavailable or non-JSON — fall through to exponential backoff.\n }\n }\n if (seconds !== undefined) {\n return Math.min(MAX_RETRY_DELAY_MS, Math.ceil(seconds * 1_000));\n }\n return backoffMs(attempt);\n}\n","/**\n * Normalize Limitless lifelogs into Remnic's provider-agnostic\n * `WearableConversation` shape.\n *\n * Spoken dialogue lives in `blockquote` content nodes (possibly nested\n * under headings via `children`); heading nodes structure the lifelog\n * and are not utterances. The wearer is marked by\n * `speakerIdentifier === \"user\"`; everyone else carries a\n * `speakerName` (\"Speaker 2\" or a saved name).\n */\n\nimport type {\n WearableConversation,\n WearableTranscriptSegment,\n} from \"@remnic/core\";\n\nimport type { LimitlessContentNode, LimitlessLifelog } from \"./client.js\";\n\nexport const LIMITLESS_SOURCE_ID = \"limitless\";\n\n/** Guard against pathological nesting in provider payloads. */\nconst MAX_NODE_DEPTH = 16;\n\nexport function lifelogToConversation(\n lifelog: LimitlessLifelog,\n): WearableConversation {\n const segments: WearableTranscriptSegment[] = [];\n collectSegments(lifelog.contents ?? [], segments, 0);\n return {\n id: lifelog.id,\n source: LIMITLESS_SOURCE_ID,\n title: typeof lifelog.title === \"string\" ? lifelog.title : undefined,\n startIso: lifelog.startTime ?? \"\",\n endIso: lifelog.endTime,\n segments,\n };\n}\n\nfunction collectSegments(\n nodes: LimitlessContentNode[],\n out: WearableTranscriptSegment[],\n depth: number,\n): void {\n if (depth > MAX_NODE_DEPTH) return;\n for (const node of nodes) {\n if (node === null || typeof node !== \"object\") continue;\n if (node.type === \"blockquote\" && typeof node.content === \"string\") {\n const text = node.content.trim();\n if (text.length > 0) {\n const isWearer = node.speakerIdentifier === \"user\";\n const speakerName =\n typeof node.speakerName === \"string\" && node.speakerName.trim().length > 0\n ? node.speakerName.trim()\n : undefined;\n out.push({\n text,\n // The wearer's stable key is \"user\"; other speakers key by the\n // provider name so saved names survive across lifelogs and the\n // registry can re-label \"Speaker 2\" everywhere at once.\n speakerKey: isWearer ? \"user\" : (speakerName ?? \"unknown\"),\n ...(speakerName !== undefined ? { speakerName } : {}),\n ...(isWearer ? { isWearer: true } : {}),\n ...(typeof node.startTime === \"string\" ? { startIso: node.startTime } : {}),\n ...(typeof node.endTime === \"string\" ? { endIso: node.endTime } : {}),\n });\n }\n }\n if (Array.isArray(node.children) && node.children.length > 0) {\n collectSegments(node.children, out, depth + 1);\n }\n }\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remnic/connector-limitless",
3
- "version": "9.69.22",
3
+ "version": "9.69.24",
4
4
  "description": "Limitless.ai Pendant connector for Remnic — pull, clean, and remember wearable 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"
22
+ "@remnic/core": "^9.69.24"
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.22"
28
+ "@remnic/core": "9.69.24"
29
29
  },
30
30
  "license": "MIT",
31
31
  "repository": {