@remnic/connector-limitless 10.0.7 → 10.0.9

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
@@ -42,6 +42,12 @@ interface LifelogsPage {
42
42
  lifelogs: LimitlessLifelog[];
43
43
  nextCursor: string | null;
44
44
  }
45
+ interface LifelogsQuery {
46
+ date: string;
47
+ timezone: string;
48
+ cursor?: string | null;
49
+ signal?: AbortSignal;
50
+ }
45
51
  interface LimitlessClientOptions {
46
52
  apiKey: string;
47
53
  baseUrl?: string;
@@ -61,14 +67,11 @@ declare class LimitlessClient {
61
67
  private readonly fetchImpl;
62
68
  private readonly timeoutMs;
63
69
  private readonly sleep;
70
+ private pageSize;
64
71
  constructor(options: LimitlessClientOptions);
65
- /** One page of lifelogs for a single day. */
66
- listLifelogs(params: {
67
- date: string;
68
- timezone: string;
69
- cursor?: string | null;
70
- signal?: AbortSignal;
71
- }): Promise<LifelogsPage>;
72
+ /** Preserve the logical ten-item page even when provider requests get smaller. */
73
+ listLifelogs(params: LifelogsQuery): Promise<LifelogsPage>;
74
+ private fetchLifelogsPage;
72
75
  /** Cheap auth probe. */
73
76
  verifyAuth(signal?: AbortSignal): Promise<{
74
77
  ok: boolean;
package/dist/index.js CHANGED
@@ -29,6 +29,7 @@ var LimitlessClient = class {
29
29
  fetchImpl;
30
30
  timeoutMs;
31
31
  sleep;
32
+ pageSize = LIFELOGS_MAX_PAGE_SIZE;
32
33
  constructor(options) {
33
34
  if (typeof options.apiKey !== "string" || options.apiKey.trim().length === 0) {
34
35
  throw new LimitlessApiError(
@@ -41,12 +42,31 @@ var LimitlessClient = class {
41
42
  this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
42
43
  this.sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
43
44
  }
44
- /** One page of lifelogs for a single day. */
45
+ /** Preserve the logical ten-item page even when provider requests get smaller. */
45
46
  async listLifelogs(params) {
47
+ const lifelogs = [];
48
+ const seenCursors = /* @__PURE__ */ new Set();
49
+ let cursor = params.cursor;
50
+ if (cursor) seenCursors.add(cursor);
51
+ for (; ; ) {
52
+ const page = await this.fetchLifelogsPage(
53
+ { ...params, cursor },
54
+ LIFELOGS_MAX_PAGE_SIZE - lifelogs.length
55
+ );
56
+ lifelogs.push(...page.lifelogs);
57
+ cursor = page.nextCursor;
58
+ if (!cursor || page.lifelogs.length === 0 || this.pageSize === LIFELOGS_MAX_PAGE_SIZE || lifelogs.length >= LIFELOGS_MAX_PAGE_SIZE || seenCursors.has(cursor)) {
59
+ return { lifelogs, nextCursor: cursor };
60
+ }
61
+ seenCursors.add(cursor);
62
+ }
63
+ }
64
+ async fetchLifelogsPage(params, remaining) {
65
+ let pageSize = Math.min(this.pageSize, remaining);
46
66
  const search = new URLSearchParams({
47
67
  date: params.date,
48
68
  timezone: params.timezone,
49
- limit: String(LIFELOGS_MAX_PAGE_SIZE),
69
+ limit: String(pageSize),
50
70
  direction: "asc",
51
71
  // The markdown rendering duplicates what `contents` carries and
52
72
  // inflates payloads; segments come from `contents`.
@@ -57,10 +77,20 @@ var LimitlessClient = class {
57
77
  if (typeof params.cursor === "string" && params.cursor.length > 0) {
58
78
  search.set("cursor", params.cursor);
59
79
  }
60
- const payload = await this.requestJson(
61
- `/v1/lifelogs?${search.toString()}`,
62
- params.signal
63
- );
80
+ let payload;
81
+ for (; ; ) {
82
+ params.signal?.throwIfAborted();
83
+ search.set("limit", String(pageSize));
84
+ try {
85
+ payload = await this.requestJson(`/v1/lifelogs?${search.toString()}`, params.signal);
86
+ break;
87
+ } catch (err) {
88
+ if (params.signal?.aborted || !(err instanceof LimitlessApiError) || err.status !== 408 && err.status !== 504 || pageSize === 1) throw err;
89
+ pageSize = Math.max(1, Math.floor(pageSize / 2));
90
+ this.pageSize = Math.min(this.pageSize, pageSize);
91
+ await this.sleep(1e3);
92
+ }
93
+ }
64
94
  const data = payload.data;
65
95
  const lifelogsRaw = data?.lifelogs;
66
96
  if (!Array.isArray(lifelogsRaw)) {
@@ -128,8 +158,9 @@ var LimitlessClient = class {
128
158
  }
129
159
  return null;
130
160
  },
131
- networkError: (err, attempts) => new LimitlessApiError(
132
- `Limitless API request failed after ${attempts} attempts: ${describeNetworkError(err)}`
161
+ networkError: (err, attempts, { timedOut }) => new LimitlessApiError(
162
+ `Limitless API request failed after ${attempts} attempts: ${describeNetworkError(err)}`,
163
+ timedOut ? 408 : void 0
133
164
  ),
134
165
  retryableError: (retryable) => new LimitlessApiError(`Limitless API responded ${retryable.status}`, retryable.status)
135
166
  });
@@ -141,7 +172,11 @@ var LimitlessClient = class {
141
172
  }
142
173
  try {
143
174
  return await response.json();
144
- } catch {
175
+ } catch (err) {
176
+ if (signal?.aborted) throw err;
177
+ if (err instanceof Error && (err.name === "TimeoutError" || err.name === "AbortError")) {
178
+ throw new LimitlessApiError("Limitless API response body timed out", 408);
179
+ }
145
180
  throw new LimitlessApiError("Limitless API returned a non-JSON body");
146
181
  }
147
182
  }
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 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\nimport {\n ConnectorApiError,\n describeNetworkError,\n retryAfterHeaderMs,\n retryingFetch,\n stripTrailingSlashes,\n} from \"@remnic/core/http-retry\";\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;\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 ConnectorApiError {\n constructor(\n message: string,\n status?: number,\n ) {\n super(message, status);\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 const response = await retryingFetch(`${this.baseUrl}${pathAndQuery}`, {\n init: {\n method: \"GET\",\n headers: {\n \"X-API-Key\": this.apiKey,\n Accept: \"application/json\",\n },\n },\n fetchImpl: this.fetchImpl,\n sleep: this.sleep,\n signal,\n timeoutMs: this.timeoutMs,\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 retryAfterMs: async (retryable) => {\n const fromHeader = retryAfterHeaderMs(retryable, MAX_RETRY_DELAY_MS);\n if (fromHeader !== undefined) return fromHeader;\n try {\n const body = (await retryable.clone().json()) as { retryAfter?: unknown };\n const parsed = Number(body?.retryAfter);\n if (Number.isFinite(parsed) && parsed > 0) {\n return Math.min(MAX_RETRY_DELAY_MS, Math.ceil(parsed * 1_000));\n }\n } catch {\n // Body unavailable or non-JSON — fall through to exponential backoff.\n }\n return null;\n },\n networkError: (err, attempts) =>\n new LimitlessApiError(\n `Limitless API request failed after ${attempts} attempts: ${describeNetworkError(err)}`,\n ),\n retryableError: (retryable) =>\n new LimitlessApiError(`Limitless API responded ${retryable.status}`, retryable.status),\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}\n\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\nimport {\n ConnectorApiError,\n describeNetworkError,\n retryAfterHeaderMs,\n retryingFetch,\n stripTrailingSlashes,\n} from \"@remnic/core/http-retry\";\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;\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\ninterface LifelogsQuery {\n date: string;\n timezone: string;\n cursor?: string | null;\n signal?: AbortSignal;\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 ConnectorApiError {\n constructor(\n message: string,\n status?: number,\n ) {\n super(message, status);\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 private pageSize = LIFELOGS_MAX_PAGE_SIZE;\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 /** Preserve the logical ten-item page even when provider requests get smaller. */\n async listLifelogs(params: LifelogsQuery): Promise<LifelogsPage> {\n const lifelogs: LimitlessLifelog[] = [];\n const seenCursors = new Set<string>();\n let cursor = params.cursor;\n if (cursor) seenCursors.add(cursor);\n for (;;) {\n const page = await this.fetchLifelogsPage(\n { ...params, cursor }, LIFELOGS_MAX_PAGE_SIZE - lifelogs.length,\n );\n lifelogs.push(...page.lifelogs);\n cursor = page.nextCursor;\n // Keep the existing outer pagination safety ceiling's candidate capacity.\n if (!cursor || page.lifelogs.length === 0 || this.pageSize === LIFELOGS_MAX_PAGE_SIZE ||\n lifelogs.length >= LIFELOGS_MAX_PAGE_SIZE || seenCursors.has(cursor)) {\n return { lifelogs, nextCursor: cursor };\n }\n seenCursors.add(cursor);\n }\n }\n\n private async fetchLifelogsPage(params: LifelogsQuery, remaining: number): Promise<LifelogsPage> {\n let pageSize = Math.min(this.pageSize, remaining);\n const search = new URLSearchParams({\n date: params.date,\n timezone: params.timezone,\n limit: String(pageSize),\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 let payload: unknown;\n for (;;) {\n params.signal?.throwIfAborted();\n search.set(\"limit\", String(pageSize));\n try {\n payload = await this.requestJson(`/v1/lifelogs?${search.toString()}`, params.signal);\n break;\n } catch (err) {\n if (params.signal?.aborted || !(err instanceof LimitlessApiError) ||\n (err.status !== 408 && err.status !== 504) || pageSize === 1) throw err;\n // Retry the SAME cursor with a lighter payload; the caller still drains\n // every nextCursor. Keep the learned size for subsequent pages.\n pageSize = Math.max(1, Math.floor(pageSize / 2));\n this.pageSize = Math.min(this.pageSize, pageSize);\n await this.sleep(1_000);\n }\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 const response = await retryingFetch(`${this.baseUrl}${pathAndQuery}`, {\n init: {\n method: \"GET\",\n headers: {\n \"X-API-Key\": this.apiKey,\n Accept: \"application/json\",\n },\n },\n fetchImpl: this.fetchImpl,\n sleep: this.sleep,\n signal,\n timeoutMs: this.timeoutMs,\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 retryAfterMs: async (retryable) => {\n const fromHeader = retryAfterHeaderMs(retryable, MAX_RETRY_DELAY_MS);\n if (fromHeader !== undefined) return fromHeader;\n try {\n const body = (await retryable.clone().json()) as { retryAfter?: unknown };\n const parsed = Number(body?.retryAfter);\n if (Number.isFinite(parsed) && parsed > 0) {\n return Math.min(MAX_RETRY_DELAY_MS, Math.ceil(parsed * 1_000));\n }\n } catch {\n // Body unavailable or non-JSON — fall through to exponential backoff.\n }\n return null;\n },\n networkError: (err, attempts, { timedOut }) =>\n new LimitlessApiError(\n `Limitless API request failed after ${attempts} attempts: ${describeNetworkError(err)}`,\n timedOut ? 408 : undefined,\n ),\n retryableError: (retryable) =>\n new LimitlessApiError(`Limitless API responded ${retryable.status}`, retryable.status),\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 (err) {\n if (signal?.aborted) throw err;\n if (err instanceof Error && (err.name === \"TimeoutError\" || err.name === \"AbortError\")) {\n throw new LimitlessApiError(\"Limitless API response body timed out\", 408);\n }\n throw new LimitlessApiError(\"Limitless API returned a non-JSON body\");\n }\n }\n}\n\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": "10.0.7",
3
+ "version": "10.0.9",
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": "^10.0.7"
22
+ "@remnic/core": "^10.0.9"
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": "10.0.7"
28
+ "@remnic/core": "10.0.9"
29
29
  },
30
30
  "license": "MIT",
31
31
  "repository": {