prompt-exporter 2.0.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,11 +7,14 @@ CLI to **sync AI conversation prompts** from multiple sources to your machine as
7
7
  **Sources**:
8
8
 
9
9
  - ✅ `chatgpt` — [chatgpt.com](https://chatgpt.com/) history via Chromium CDP
10
+ - ✅ `lumo` — [lumo.proton.me](https://lumo.proton.me/) (Proton) via Chromium CDP / in-app Redux
10
11
  - 🎯 `claude` — [claude.ai](https://claude.ai/) (planned)
11
12
  - 🎯 `gemini` — [gemini.google.com](https://gemini.google.com/) (planned)
12
- - 🎯 `copilot` — [copilot.microsoft.com](https://copilot.microsoft.com/) / Bing Copilot (planned)
13
+ - 🎯 `deepseek` — [chat.deepseek.com](https://chat.deepseek.com/) (planned)
13
14
  - 🎯 `perplexity` — [perplexity.ai](https://www.perplexity.ai/) (planned)
14
- - 🎯 `grok` — [grok.x.ai](https://grok.x.ai/) / xAI (planned)
15
+ - 🎯 `copilot` — Microsoft 365 Copilot ([m365.cloud.microsoft](https://m365.cloud.microsoft/chat/) / [copilot.microsoft.com](https://copilot.microsoft.com/)) (planned)
16
+ - 🎯 `grok` — [grok.com](https://grok.com/) / xAI (planned)
17
+ - 🎯 `kimi` — [kimi.com](https://www.kimi.com/) / Moonshot (planned)
15
18
 
16
19
  **Platforms**:
17
20
 
@@ -7,9 +7,11 @@ import {
7
7
  getCdpEndpointInfo,
8
8
  isCdpRunning,
9
9
  isChatGptUrl,
10
+ isLumoUrl,
10
11
  listCdpTargets,
11
- openCdpTab
12
- } from "./chunk-YHAS76TP.js";
12
+ openCdpTab,
13
+ withCdpSocket
14
+ } from "./chunk-O5E5XFJR.js";
13
15
  export {
14
16
  activateCdpTarget,
15
17
  cdpNavigate,
@@ -19,6 +21,8 @@ export {
19
21
  getCdpEndpointInfo,
20
22
  isCdpRunning,
21
23
  isChatGptUrl,
24
+ isLumoUrl,
22
25
  listCdpTargets,
23
- openCdpTab
26
+ openCdpTab,
27
+ withCdpSocket
24
28
  };
@@ -192,6 +192,14 @@ function isChatGptUrl(url) {
192
192
  return false;
193
193
  }
194
194
  }
195
+ function isLumoUrl(url) {
196
+ try {
197
+ const parsed = new URL(url);
198
+ return parsed.hostname === "lumo.proton.me" || parsed.hostname.endsWith(".lumo.proton.me");
199
+ } catch {
200
+ return false;
201
+ }
202
+ }
195
203
  function parseTarget(raw) {
196
204
  if (typeof raw.id !== "string") return null;
197
205
  return {
@@ -427,9 +435,11 @@ export {
427
435
  discoverCdpPort,
428
436
  getCdpEndpointInfo,
429
437
  isChatGptUrl,
438
+ isLumoUrl,
430
439
  listCdpTargets,
431
440
  openCdpTab,
432
441
  activateCdpTarget,
442
+ withCdpSocket,
433
443
  cdpNavigate,
434
444
  ensureChatGptSessionTab,
435
445
  extractAccessTokenViaCdp
package/dist/index.js CHANGED
@@ -13,10 +13,12 @@ import {
13
13
  getSourceTokenPath,
14
14
  isCdpRunning,
15
15
  isChatGptUrl,
16
+ isLumoUrl,
16
17
  listCdpTargets,
17
18
  openCdpTab,
18
- resolveChromiumUserDataDir
19
- } from "./chunk-YHAS76TP.js";
19
+ resolveChromiumUserDataDir,
20
+ withCdpSocket
21
+ } from "./chunk-O5E5XFJR.js";
20
22
 
21
23
  // src/cli/index.ts
22
24
  import { Command } from "commander";
@@ -285,7 +287,7 @@ var CdpHttpSession = class _CdpHttpSession {
285
287
  }
286
288
  static async connect(preferredPort = DEFAULT_CDP_PORT, options = {}) {
287
289
  let port = preferredPort;
288
- const { isCdpRunning: isCdpRunning2 } = await import("./cdp-PRMCSQGL.js");
290
+ const { isCdpRunning: isCdpRunning2 } = await import("./cdp-5CBMWFGT.js");
289
291
  if (!await isCdpRunning2(port)) {
290
292
  const found = await discoverCdpPort(port);
291
293
  if (found === null) {
@@ -1550,8 +1552,14 @@ async function convertDirectory(inputDir, filesDir) {
1550
1552
  await removeStaleMarkdown(conversationsDir, id, mdName);
1551
1553
  await fs4.writeFile(mdPath, markdown, "utf-8");
1552
1554
  converted++;
1553
- } catch {
1555
+ } catch (err) {
1554
1556
  errors++;
1557
+ if (process.env.PROMPT_EXPORTER_DEBUG_MD === "1") {
1558
+ console.error(
1559
+ `markdown convert failed for ${jsonPath}:`,
1560
+ err instanceof Error ? err.message : err
1561
+ );
1562
+ }
1555
1563
  }
1556
1564
  }
1557
1565
  for (const dir of touchedDirs) {
@@ -1998,11 +2006,452 @@ async function runFileDownloads(client, output, options) {
1998
2006
  }
1999
2007
  }
2000
2008
 
2009
+ // src/cli/commands/backup-lumo.ts
2010
+ import chalk3 from "chalk";
2011
+ import ora2 from "ora";
2012
+
2013
+ // src/sources/lumo/api/endpoints.ts
2014
+ var LUMO_ORIGIN = "https://lumo.proton.me";
2015
+ var LUMO_HOME = `${LUMO_ORIGIN}/`;
2016
+
2017
+ // src/sources/lumo/api/cdp-export.ts
2018
+ var EXPORT_EXPRESSION = `(() => {
2019
+ const PULL_TIMEOUT_MS = 45000;
2020
+ const PULL_POLL_MS = 250;
2021
+ const INIT_WAIT_MS = 90000;
2022
+
2023
+ function sleep(ms) {
2024
+ return new Promise((r) => setTimeout(r, ms));
2025
+ }
2026
+
2027
+ function findReduxStore() {
2028
+ if (window.lumoStore && typeof window.lumoStore.getState === 'function') {
2029
+ return window.lumoStore;
2030
+ }
2031
+
2032
+ const tryFiber = (fiber, depth) => {
2033
+ if (!fiber || depth > 80) return null;
2034
+ const props = fiber.memoizedProps || fiber.pendingProps;
2035
+ if (props && props.store && typeof props.store.getState === 'function') {
2036
+ return props.store;
2037
+ }
2038
+ if (props && props.value && props.value.store && typeof props.value.store.getState === 'function') {
2039
+ return props.value.store;
2040
+ }
2041
+ let hook = fiber.memoizedState;
2042
+ for (let i = 0; hook && i < 100; i++) {
2043
+ const ms = hook.memoizedState;
2044
+ if (ms && ms.store && typeof ms.store.getState === 'function') return ms.store;
2045
+ if (ms && typeof ms.getState === 'function' && typeof ms.dispatch === 'function') return ms;
2046
+ hook = hook.next;
2047
+ }
2048
+ return tryFiber(fiber.child, depth + 1) || tryFiber(fiber.sibling, depth + 1);
2049
+ };
2050
+
2051
+ // Lumo has no #root; React mounts on an inner DIV (__reactContainer$).
2052
+ const nodes = document.querySelectorAll('*');
2053
+ for (const el of nodes) {
2054
+ const key = Object.keys(el).find((k) => k.startsWith('__reactContainer'));
2055
+ if (!key) continue;
2056
+ let fiber = el[key];
2057
+ if (fiber && fiber.stateNode) fiber = fiber.stateNode;
2058
+ if (fiber && fiber.child) fiber = fiber.child;
2059
+ const store = tryFiber(fiber, 0);
2060
+ if (store) return store;
2061
+ }
2062
+ for (const el of nodes) {
2063
+ const key = Object.keys(el).find((k) => k.startsWith('__reactFiber'));
2064
+ if (!key) continue;
2065
+ const store = tryFiber(el[key], 0);
2066
+ if (store) return store;
2067
+ }
2068
+ return null;
2069
+ }
2070
+
2071
+ function messageText(m) {
2072
+ if (!m) return '';
2073
+ if (typeof m.content === 'string' && m.content.trim()) return m.content;
2074
+ if (Array.isArray(m.blocks)) {
2075
+ return m.blocks
2076
+ .filter((b) => b && b.type === 'text' && typeof b.content === 'string')
2077
+ .map((b) => b.content)
2078
+ .join('\\n')
2079
+ .trim();
2080
+ }
2081
+ return '';
2082
+ }
2083
+
2084
+ function roleOf(m) {
2085
+ const r = (m && m.role) || 'assistant';
2086
+ if (r === 'user' || r === 'assistant' || r === 'system') return r;
2087
+ if (r === 'tool_call' || r === 'tool_result') return 'assistant';
2088
+ return String(r);
2089
+ }
2090
+
2091
+ function toEpoch(iso) {
2092
+ if (!iso) return 0;
2093
+ const t = Date.parse(iso);
2094
+ return Number.isFinite(t) ? Math.floor(t / 1000) : 0;
2095
+ }
2096
+
2097
+ function buildDetail(conversation, messages) {
2098
+ const byId = {};
2099
+ for (const m of messages) {
2100
+ byId[m.id] = m;
2101
+ }
2102
+ const mapping = {};
2103
+ const roots = [];
2104
+ for (const m of messages) {
2105
+ const parent = m.parentId && byId[m.parentId] ? m.parentId : null;
2106
+ mapping[m.id] = {
2107
+ id: m.id,
2108
+ parent,
2109
+ children: [],
2110
+ message: {
2111
+ id: m.id,
2112
+ author: { role: roleOf(m) },
2113
+ content: { content_type: 'text', parts: [messageText(m)] },
2114
+ create_time: toEpoch(m.createdAt),
2115
+ },
2116
+ };
2117
+ if (!parent) roots.push(m.id);
2118
+ }
2119
+ for (const m of messages) {
2120
+ if (m.parentId && mapping[m.parentId] && mapping[m.id]) {
2121
+ mapping[m.parentId].children.push(m.id);
2122
+ }
2123
+ }
2124
+ // synthetic root if needed (ChatGPT-style)
2125
+ let current = null;
2126
+ if (messages.length) {
2127
+ const leaves = messages.filter((m) => !(mapping[m.id].children || []).length);
2128
+ const last = leaves.sort((a, b) => toEpoch(a.createdAt) - toEpoch(b.createdAt)).pop();
2129
+ current = last ? last.id : messages[messages.length - 1].id;
2130
+ }
2131
+ if (roots.length === 0 && messages.length) {
2132
+ const rootId = 'client-created-root';
2133
+ mapping[rootId] = {
2134
+ id: rootId,
2135
+ parent: null,
2136
+ children: messages.filter((m) => !m.parentId).map((m) => m.id),
2137
+ message: null,
2138
+ };
2139
+ for (const m of messages) {
2140
+ if (!m.parentId && mapping[m.id]) mapping[m.id].parent = rootId;
2141
+ }
2142
+ }
2143
+
2144
+ return {
2145
+ id: conversation.id,
2146
+ title: conversation.title || 'Untitled',
2147
+ create_time: toEpoch(conversation.createdAt),
2148
+ update_time: toEpoch(conversation.updatedAt || conversation.createdAt),
2149
+ space_id: conversation.spaceId,
2150
+ starred: !!conversation.starred,
2151
+ mapping,
2152
+ current_node: current,
2153
+ };
2154
+ }
2155
+
2156
+ async function waitForStore() {
2157
+ const start = Date.now();
2158
+ while (Date.now() - start < INIT_WAIT_MS) {
2159
+ const store = findReduxStore();
2160
+ if (store) {
2161
+ const state = store.getState();
2162
+ const cred = state.credentials && state.credentials.masterKeyState;
2163
+ const convs = state.conversations || {};
2164
+ const init = state.initialization;
2165
+ const readyCred = cred && (cred.status === 'ready' || cred.status === 'ineligible');
2166
+ const hasConvs = Object.keys(convs).length > 0;
2167
+ // Allow empty account once credentials ready / init done
2168
+ if (readyCred || hasConvs || (init && init.status === 'done')) {
2169
+ return store;
2170
+ }
2171
+ }
2172
+ await sleep(400);
2173
+ }
2174
+ return findReduxStore();
2175
+ }
2176
+
2177
+ async function pullConversation(store, id) {
2178
+ const before = Object.values(store.getState().messages || {}).filter(
2179
+ (m) => m.conversationId === id
2180
+ ).length;
2181
+ if (before > 0) return;
2182
+
2183
+ store.dispatch({ type: 'lumo/conversation/pullRequest', payload: { id } });
2184
+ const start = Date.now();
2185
+ while (Date.now() - start < PULL_TIMEOUT_MS) {
2186
+ await sleep(PULL_POLL_MS);
2187
+ const n = Object.values(store.getState().messages || {}).filter(
2188
+ (m) => m.conversationId === id
2189
+ ).length;
2190
+ if (n > before) return;
2191
+ }
2192
+ }
2193
+
2194
+ return (async () => {
2195
+ if (!location.hostname.includes('lumo.proton.me')) {
2196
+ return {
2197
+ ok: false,
2198
+ error: 'Not on lumo.proton.me \u2014 open Lumo in this CDP browser and log in.',
2199
+ };
2200
+ }
2201
+ const store = await waitForStore();
2202
+ if (!store) {
2203
+ return {
2204
+ ok: false,
2205
+ error:
2206
+ 'Could not find Lumo Redux store. Open https://lumo.proton.me/, log in, wait for chats to load, then retry.',
2207
+ };
2208
+ }
2209
+
2210
+ const warnings = [];
2211
+ const state0 = store.getState();
2212
+ const cred = state0.credentials && state0.credentials.masterKeyState;
2213
+ if (cred && cred.status === 'failed') {
2214
+ return { ok: false, error: 'Lumo master key failed: ' + (cred.message || 'unknown') };
2215
+ }
2216
+ if (cred && cred.status === 'ineligible') {
2217
+ warnings.push('User not eligible for Lumo cloud sync; exporting local Redux state only.');
2218
+ }
2219
+
2220
+ let conversations = Object.values(state0.conversations || {}).filter((c) => c && !c.deleted && !c.ghost);
2221
+ if (conversations.length === 0) {
2222
+ // give hydrate more time
2223
+ await sleep(2000);
2224
+ conversations = Object.values(store.getState().conversations || {}).filter(
2225
+ (c) => c && !c.deleted && !c.ghost
2226
+ );
2227
+ }
2228
+
2229
+ conversations.sort(
2230
+ (a, b) =>
2231
+ Date.parse(b.updatedAt || b.createdAt || 0) - Date.parse(a.updatedAt || a.createdAt || 0)
2232
+ );
2233
+
2234
+ for (const c of conversations) {
2235
+ try {
2236
+ await pullConversation(store, c.id);
2237
+ } catch (e) {
2238
+ warnings.push('pull failed for ' + c.id + ': ' + String(e && e.message ? e.message : e));
2239
+ }
2240
+ }
2241
+
2242
+ const messagesAll = Object.values(store.getState().messages || {});
2243
+ const items = [];
2244
+ const details = [];
2245
+ for (const c of conversations) {
2246
+ const msgs = messagesAll
2247
+ .filter((m) => m && m.conversationId === c.id && !m.deleted)
2248
+ .sort((a, b) => Date.parse(a.createdAt || 0) - Date.parse(b.createdAt || 0));
2249
+ if (msgs.length === 0) {
2250
+ warnings.push('no messages for conversation ' + c.id + ' (' + (c.title || '') + ')');
2251
+ }
2252
+ const detail = buildDetail(c, msgs);
2253
+ items.push({
2254
+ id: detail.id,
2255
+ title: detail.title,
2256
+ create_time: detail.create_time,
2257
+ update_time: detail.update_time,
2258
+ space_id: detail.space_id,
2259
+ starred: detail.starred,
2260
+ });
2261
+ details.push(detail);
2262
+ }
2263
+
2264
+ return {
2265
+ ok: true,
2266
+ conversations: items,
2267
+ details,
2268
+ warnings,
2269
+ conversationCount: items.length,
2270
+ messageCount: messagesAll.length,
2271
+ };
2272
+ })();
2273
+ })()`;
2274
+ async function ensureLumoTab(port) {
2275
+ const targets = await listCdpTargets(port);
2276
+ const pages = targets.filter((t) => t.type === "page" || t.type === "tab");
2277
+ let target = pages.find((t) => isLumoUrl(t.url));
2278
+ if (!target) {
2279
+ target = await openCdpTab(port, LUMO_HOME);
2280
+ } else {
2281
+ await activateCdpTarget(port, target.id);
2282
+ }
2283
+ let wsUrl = target.webSocketDebuggerUrl;
2284
+ if (!wsUrl) {
2285
+ const refreshed = (await listCdpTargets(port)).find((t) => t.id === target.id);
2286
+ wsUrl = refreshed?.webSocketDebuggerUrl;
2287
+ }
2288
+ if (!wsUrl) {
2289
+ throw new Error("No DevTools WebSocket for Lumo tab");
2290
+ }
2291
+ return { targetId: target.id, wsUrl };
2292
+ }
2293
+ async function exportLumoViaCdp(options = {}) {
2294
+ let port = options.cdpPort ?? DEFAULT_CDP_PORT;
2295
+ if (!await isCdpRunning(port)) {
2296
+ const found = await discoverCdpPort(port);
2297
+ if (found === null) {
2298
+ throw new Error(
2299
+ "No Chromium CDP endpoint found. Start with: prompt-exporter chromium start --url https://lumo.proton.me/"
2300
+ );
2301
+ }
2302
+ port = found;
2303
+ }
2304
+ const { wsUrl } = await ensureLumoTab(port);
2305
+ const result = await withCdpSocket(wsUrl, async (send) => {
2306
+ await send("Runtime.enable");
2307
+ await send("Page.enable").catch(() => void 0);
2308
+ const evaluated = await send("Runtime.evaluate", {
2309
+ expression: EXPORT_EXPRESSION,
2310
+ awaitPromise: true,
2311
+ returnByValue: true
2312
+ });
2313
+ if (evaluated.exceptionDetails) {
2314
+ throw new Error(
2315
+ `Lumo export evaluate failed: ${JSON.stringify(evaluated.exceptionDetails).slice(0, 400)}`
2316
+ );
2317
+ }
2318
+ const remote = evaluated.result;
2319
+ if (!remote) {
2320
+ throw new Error(
2321
+ `Lumo export returned no RemoteObject: ${JSON.stringify(evaluated).slice(0, 300)}`
2322
+ );
2323
+ }
2324
+ if (remote.type === "undefined" || remote.subtype === "error") {
2325
+ throw new Error(
2326
+ `Lumo export RemoteObject empty/error: ${JSON.stringify(remote).slice(0, 400)}`
2327
+ );
2328
+ }
2329
+ if ("value" in remote) {
2330
+ return remote.value;
2331
+ }
2332
+ throw new Error(
2333
+ `Lumo export missing value (type=${String(remote.type)}): ${JSON.stringify(remote).slice(0, 400)}`
2334
+ );
2335
+ });
2336
+ if (!result || typeof result !== "object") {
2337
+ throw new Error(
2338
+ `Lumo export returned empty result (${String(result)}). Is lumo.proton.me open and logged in on the CDP browser?`
2339
+ );
2340
+ }
2341
+ if (result.ok === false) {
2342
+ throw new Error(String(result.error || "Lumo export failed"));
2343
+ }
2344
+ const conversations = result.conversations ?? [];
2345
+ const details = result.details ?? [];
2346
+ const warnings = result.warnings ?? [];
2347
+ if (options.verbose) {
2348
+ for (const w of warnings) {
2349
+ console.error(`[lumo] ${w}`);
2350
+ }
2351
+ }
2352
+ return { conversations, details, warnings };
2353
+ }
2354
+
2355
+ // src/cli/commands/backup-lumo.ts
2356
+ function asConversationDetail(detail) {
2357
+ return detail;
2358
+ }
2359
+ async function lumoBackupCommand(options) {
2360
+ const spinner = ora2("Connecting to Lumo via Chromium CDP\u2026").start();
2361
+ try {
2362
+ const bundle = await exportLumoViaCdp({
2363
+ cdpPort: options.cdpPort,
2364
+ verbose: options.verbose
2365
+ });
2366
+ spinner.succeed(
2367
+ chalk3.green(
2368
+ `Lumo export: ${bundle.conversations.length} conversation(s) from Redux`
2369
+ )
2370
+ );
2371
+ if (bundle.warnings.length && options.verbose) {
2372
+ for (const w of bundle.warnings) {
2373
+ console.log(chalk3.yellow(` \u26A0 ${w}`));
2374
+ }
2375
+ }
2376
+ const storage = new StorageService(options.output);
2377
+ await storage.initialize();
2378
+ await storage.appendLog(
2379
+ `Lumo CDP export: ${bundle.conversations.length} conversations`
2380
+ );
2381
+ await storage.saveIndex(
2382
+ bundle.conversations
2383
+ );
2384
+ let downloaded = 0;
2385
+ let skipped = 0;
2386
+ for (const detail of bundle.details) {
2387
+ if (options.incremental) {
2388
+ const existing = await storage.getExistingConversationUpdateTime(detail.id);
2389
+ if (existing !== null && existing >= detail.update_time) {
2390
+ skipped++;
2391
+ continue;
2392
+ }
2393
+ }
2394
+ await storage.saveConversation(detail.id, asConversationDetail(detail));
2395
+ downloaded++;
2396
+ }
2397
+ await storage.saveMetadata({
2398
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2399
+ totalConversations: bundle.conversations.length,
2400
+ successfulDownloads: downloaded,
2401
+ failedDownloads: 0,
2402
+ errors: []
2403
+ });
2404
+ console.log();
2405
+ console.log(chalk3.bold("Lumo backup summary"));
2406
+ console.log(` Output: ${options.output}`);
2407
+ console.log(` Listed: ${bundle.conversations.length}`);
2408
+ console.log(` Downloaded: ${downloaded}`);
2409
+ console.log(` Skipped: ${skipped}`);
2410
+ if (bundle.warnings.length) {
2411
+ console.log(` Warnings: ${bundle.warnings.length}`);
2412
+ }
2413
+ const mdSpinner = ora2("Writing Markdown\u2026").start();
2414
+ const md = await convertDirectory(options.output);
2415
+ if (md.converted === 0 && md.errors > 0) {
2416
+ mdSpinner.fail(
2417
+ chalk3.red(`Markdown: 0 written, ${md.errors} errors`)
2418
+ );
2419
+ } else {
2420
+ mdSpinner.succeed(
2421
+ chalk3.green(`Markdown: ${md.converted} written, ${md.errors} errors`)
2422
+ );
2423
+ }
2424
+ } catch (error) {
2425
+ spinner.fail(chalk3.red("Lumo sync failed"));
2426
+ throw error;
2427
+ }
2428
+ }
2429
+ async function lumoListCommand(options) {
2430
+ const bundle = await exportLumoViaCdp({
2431
+ cdpPort: options.cdpPort,
2432
+ verbose: options.verbose
2433
+ });
2434
+ if (options.json) {
2435
+ console.log(JSON.stringify(bundle.conversations, null, 2));
2436
+ return;
2437
+ }
2438
+ console.log();
2439
+ console.log(chalk3.bold(`Lumo conversations (${bundle.conversations.length})`));
2440
+ for (const c of bundle.conversations) {
2441
+ const when = c.update_time ? new Date(c.update_time * 1e3).toISOString().slice(0, 10) : "????-??-??";
2442
+ console.log(` ${when} ${c.title} ${chalk3.dim(c.id)}`);
2443
+ }
2444
+ if (bundle.warnings.length && options.verbose) {
2445
+ console.log();
2446
+ for (const w of bundle.warnings) console.log(chalk3.yellow(w));
2447
+ }
2448
+ }
2449
+
2001
2450
  // src/cli/commands/chromium.ts
2002
2451
  import { spawn } from "child_process";
2003
2452
  import fs7 from "fs/promises";
2004
2453
  import path7 from "path";
2005
- import chalk3 from "chalk";
2454
+ import chalk4 from "chalk";
2006
2455
 
2007
2456
  // src/utils/browser.ts
2008
2457
  import { access } from "fs/promises";
@@ -2033,11 +2482,11 @@ async function resolveBrowser() {
2033
2482
  // src/cli/commands/chromium.ts
2034
2483
  function printNextSteps() {
2035
2484
  console.log("Then run:");
2036
- console.log(chalk3.cyan(" prompt-exporter token"));
2485
+ console.log(chalk4.cyan(" prompt-exporter token"));
2037
2486
  console.log("to read accessToken from this browser session.");
2038
2487
  console.log("");
2039
2488
  console.log("Then sync conversations:");
2040
- console.log(chalk3.cyan(" prompt-exporter sync"));
2489
+ console.log(chalk4.cyan(" prompt-exporter sync"));
2041
2490
  console.log("");
2042
2491
  }
2043
2492
  async function isProfileLocked(userDataDir) {
@@ -2055,13 +2504,13 @@ async function reuseExistingCdp(port, url, existing) {
2055
2504
  if (!info) {
2056
2505
  throw new Error(`No CDP endpoint on port ${port}`);
2057
2506
  }
2058
- console.log(chalk3.green(`Reusing Chromium CDP on port ${port}.`));
2059
- console.log(chalk3.dim(` CDP: http://127.0.0.1:${port}`));
2507
+ console.log(chalk4.green(`Reusing Chromium CDP on port ${port}.`));
2508
+ console.log(chalk4.dim(` CDP: http://127.0.0.1:${port}`));
2060
2509
  if (info.Browser) {
2061
- console.log(chalk3.dim(` Browser: ${info.Browser}`));
2510
+ console.log(chalk4.dim(` Browser: ${info.Browser}`));
2062
2511
  }
2063
2512
  if (info.webSocketDebuggerUrl) {
2064
- console.log(chalk3.dim(` WS: ${info.webSocketDebuggerUrl}`));
2513
+ console.log(chalk4.dim(` WS: ${info.webSocketDebuggerUrl}`));
2065
2514
  }
2066
2515
  console.log("");
2067
2516
  const targets = await listCdpTargets(port);
@@ -2070,13 +2519,13 @@ async function reuseExistingCdp(port, url, existing) {
2070
2519
  const chatgpt = exact ?? (isChatGptUrl(url) ? pages.find((t) => isChatGptUrl(t.url)) : void 0);
2071
2520
  if (chatgpt) {
2072
2521
  await activateCdpTarget(port, chatgpt.id);
2073
- console.log(chalk3.green(`Focused existing tab in that browser.`));
2074
- console.log(chalk3.dim(` ${chatgpt.title || chatgpt.id}`));
2075
- console.log(chalk3.dim(` ${chatgpt.url}`));
2522
+ console.log(chalk4.green(`Focused existing tab in that browser.`));
2523
+ console.log(chalk4.dim(` ${chatgpt.title || chatgpt.id}`));
2524
+ console.log(chalk4.dim(` ${chatgpt.url}`));
2076
2525
  } else {
2077
2526
  const created = await openCdpTab(port, url);
2078
- console.log(chalk3.green(`Opened a new tab in that browser (same session).`));
2079
- console.log(chalk3.dim(` ${created.url || url}`));
2527
+ console.log(chalk4.green(`Opened a new tab in that browser (same session).`));
2528
+ console.log(chalk4.dim(` ${created.url || url}`));
2080
2529
  }
2081
2530
  console.log("");
2082
2531
  printNextSteps();
@@ -2111,12 +2560,12 @@ async function chromiumStartCommand(options) {
2111
2560
  return;
2112
2561
  }
2113
2562
  console.log(
2114
- chalk3.yellow("Chromium profile is already in use (browser running).")
2563
+ chalk4.yellow("Chromium profile is already in use (browser running).")
2115
2564
  );
2116
- console.log(chalk3.dim(` Profile: ${userDataDir}`));
2565
+ console.log(chalk4.dim(` Profile: ${userDataDir}`));
2117
2566
  console.log("");
2118
2567
  console.log(
2119
- chalk3.yellow(
2568
+ chalk4.yellow(
2120
2569
  `No CDP answered on port ${port}, so this running browser cannot be controlled.`
2121
2570
  )
2122
2571
  );
@@ -2143,13 +2592,13 @@ async function chromiumStartCommand(options) {
2143
2592
  }
2144
2593
  args.push(url);
2145
2594
  if (options.print) {
2146
- console.log(chalk3.dim(`Browser: ${browser}`));
2147
- console.log(chalk3.dim(`Args: ${args.join(" ")}`));
2595
+ console.log(chalk4.dim(`Browser: ${browser}`));
2596
+ console.log(chalk4.dim(`Args: ${args.join(" ")}`));
2148
2597
  }
2149
- console.log(chalk3.bold("Starting Chromium with remote debugging\u2026"));
2150
- console.log(chalk3.dim(` CDP: http://127.0.0.1:${port}`));
2598
+ console.log(chalk4.bold("Starting Chromium with remote debugging\u2026"));
2599
+ console.log(chalk4.dim(` CDP: http://127.0.0.1:${port}`));
2151
2600
  console.log(
2152
- chalk3.dim(
2601
+ chalk4.dim(
2153
2602
  ` Profile: ${userDataDir}` + (mode === "system" ? " (system \u2014 your normal login)" : mode === "isolated" ? " (isolated)" : " (custom)")
2154
2603
  )
2155
2604
  );
@@ -2160,15 +2609,15 @@ async function chromiumStartCommand(options) {
2160
2609
  stdio: "ignore"
2161
2610
  });
2162
2611
  child.unref();
2163
- console.log(chalk3.green(`Chromium started (pid ${child.pid ?? "unknown"}).`));
2612
+ console.log(chalk4.green(`Chromium started (pid ${child.pid ?? "unknown"}).`));
2164
2613
  }
2165
2614
 
2166
2615
  // src/cli/commands/list.ts
2167
- import chalk4 from "chalk";
2168
- import ora2 from "ora";
2616
+ import chalk5 from "chalk";
2617
+ import ora3 from "ora";
2169
2618
  async function listCommand(options) {
2170
2619
  const { token, delay, verbose, json, project, cdpPort } = options;
2171
- const spinner = ora2("Connecting...").start();
2620
+ const spinner = ora3("Connecting...").start();
2172
2621
  const { client } = await createChatGptClient({ token, verbose, cdpPort });
2173
2622
  const storage = new StorageService(getDefaultDataDir());
2174
2623
  const service = new BackupService(client, storage);
@@ -2209,30 +2658,30 @@ async function listCommand(options) {
2209
2658
  } else {
2210
2659
  console.log();
2211
2660
  for (const conv of conversations) {
2212
- const title = conv.title ?? chalk4.dim("(untitled)");
2661
+ const title = conv.title ?? chalk5.dim("(untitled)");
2213
2662
  const date = conv.update_time ? new Date(
2214
2663
  typeof conv.update_time === "number" ? conv.update_time * 1e3 : conv.update_time
2215
2664
  ).toLocaleDateString() : "unknown date";
2216
- console.log(`${chalk4.cyan(conv.id)} ${title} ${chalk4.dim(`[${date}]`)}`);
2665
+ console.log(`${chalk5.cyan(conv.id)} ${title} ${chalk5.dim(`[${date}]`)}`);
2217
2666
  }
2218
2667
  console.log();
2219
- console.log(chalk4.green(`Total: ${conversations.length} conversations`));
2668
+ console.log(chalk5.green(`Total: ${conversations.length} conversations`));
2220
2669
  }
2221
2670
  } catch (error) {
2222
2671
  spinner.fail("Failed to list conversations");
2223
2672
  if (error instanceof Error) {
2224
2673
  if (error.name === "AuthenticationError") {
2225
- console.error(chalk4.red(`
2674
+ console.error(chalk5.red(`
2226
2675
  Authentication failed: ${error.message}`));
2227
2676
  console.error(
2228
- chalk4.yellow(
2677
+ chalk5.yellow(
2229
2678
  "\nKeep Chromium CDP logged in to chatgpt.com, then retry:"
2230
2679
  )
2231
2680
  );
2232
2681
  console.error(" prompt-exporter chromium start");
2233
2682
  console.error(" prompt-exporter list");
2234
2683
  } else {
2235
- console.error(chalk4.red(`
2684
+ console.error(chalk5.red(`
2236
2685
  Error: ${error.message}`));
2237
2686
  }
2238
2687
  }
@@ -2243,11 +2692,11 @@ Error: ${error.message}`));
2243
2692
  }
2244
2693
 
2245
2694
  // src/cli/commands/projects.ts
2246
- import chalk5 from "chalk";
2247
- import ora3 from "ora";
2695
+ import chalk6 from "chalk";
2696
+ import ora4 from "ora";
2248
2697
  async function projectsCommand(options) {
2249
2698
  const { token, verbose, json, cdpPort } = options;
2250
- const spinner = ora3("Connecting...").start();
2699
+ const spinner = ora4("Connecting...").start();
2251
2700
  const { client } = await createChatGptClient({ token, verbose, cdpPort });
2252
2701
  const storage = new StorageService(getDefaultDataDir());
2253
2702
  const service = new BackupService(client, storage);
@@ -2273,19 +2722,19 @@ async function projectsCommand(options) {
2273
2722
  const id = gizmo.id;
2274
2723
  const lastInteracted = gizmo.last_interacted_at ? new Date(gizmo.last_interacted_at).toLocaleDateString() : "never";
2275
2724
  console.log(
2276
- `${chalk5.cyan(id)} ${chalk5.bold(name)} ${chalk5.dim(`[last: ${lastInteracted}]`)}`
2725
+ `${chalk6.cyan(id)} ${chalk6.bold(name)} ${chalk6.dim(`[last: ${lastInteracted}]`)}`
2277
2726
  );
2278
2727
  }
2279
2728
  console.log();
2280
- console.log(chalk5.green(`Total: ${projects.length} projects`));
2729
+ console.log(chalk6.green(`Total: ${projects.length} projects`));
2281
2730
  }
2282
2731
  } catch (error) {
2283
2732
  spinner.fail("Failed to list projects");
2284
2733
  if (error instanceof Error) {
2285
- console.error(chalk5.red(`
2734
+ console.error(chalk6.red(`
2286
2735
  Error: ${error.message}`));
2287
2736
  if (error.name === "AuthenticationError") {
2288
- console.error(chalk5.yellow("\nKeep Chromium CDP logged in, then retry:"));
2737
+ console.error(chalk6.yellow("\nKeep Chromium CDP logged in, then retry:"));
2289
2738
  console.error(" prompt-exporter chromium start");
2290
2739
  console.error(" prompt-exporter projects");
2291
2740
  }
@@ -2298,7 +2747,7 @@ Error: ${error.message}`));
2298
2747
 
2299
2748
  // src/cli/commands/token.ts
2300
2749
  import { spawn as spawn2 } from "child_process";
2301
- import chalk6 from "chalk";
2750
+ import chalk7 from "chalk";
2302
2751
 
2303
2752
  // src/utils/cdp-session.ts
2304
2753
  var SESSION_URL = "https://chatgpt.com/api/auth/session";
@@ -2312,7 +2761,7 @@ function getWebSocket() {
2312
2761
  }
2313
2762
  return WS;
2314
2763
  }
2315
- async function withCdpSocket(webSocketDebuggerUrl, fn) {
2764
+ async function withCdpSocket2(webSocketDebuggerUrl, fn) {
2316
2765
  const WS = getWebSocket();
2317
2766
  const ws = new WS(webSocketDebuggerUrl);
2318
2767
  let nextId = 1;
@@ -2394,7 +2843,7 @@ function normalizeProbe(raw) {
2394
2843
  }
2395
2844
  async function probeSessionViaCdp(webSocketDebuggerUrl) {
2396
2845
  try {
2397
- return await withCdpSocket(webSocketDebuggerUrl, async (send) => {
2846
+ return await withCdpSocket2(webSocketDebuggerUrl, async (send) => {
2398
2847
  await send("Runtime.enable");
2399
2848
  const result = await send("Runtime.evaluate", {
2400
2849
  expression: `(() => {
@@ -2534,16 +2983,16 @@ async function waitForAccessToken(port, options = {}) {
2534
2983
  // src/cli/commands/token.ts
2535
2984
  async function persistAndAnnounce(token, show) {
2536
2985
  const file = await saveToken(token);
2537
- console.log(chalk6.green("Saved accessToken for sync."));
2538
- console.log(chalk6.dim(` ${file}`));
2539
- console.log(chalk6.dim(` preview: ${token.slice(0, 16)}\u2026 (${token.length} chars)`));
2986
+ console.log(chalk7.green("Saved accessToken for sync."));
2987
+ console.log(chalk7.dim(` ${file}`));
2988
+ console.log(chalk7.dim(` preview: ${token.slice(0, 16)}\u2026 (${token.length} chars)`));
2540
2989
  console.log("");
2541
2990
  if (show) {
2542
2991
  console.log(` export PROMPT_EXPORTER_CHATGPT_TOKEN="${token}"`);
2543
2992
  console.log("");
2544
2993
  }
2545
2994
  console.log("You can sync right away (no manual export needed):");
2546
- console.log(chalk6.cyan(" prompt-exporter sync"));
2995
+ console.log(chalk7.cyan(" prompt-exporter sync"));
2547
2996
  }
2548
2997
  function printManualSteps() {
2549
2998
  console.log("Steps:");
@@ -2564,11 +3013,11 @@ async function tokenCommand(options = {}) {
2564
3013
  }
2565
3014
  if (!await isCdpRunning(port)) {
2566
3015
  console.log(
2567
- chalk6.yellow(
3016
+ chalk7.yellow(
2568
3017
  `No CDP on port ${port}. Start one with: prompt-exporter chromium start`
2569
3018
  )
2570
3019
  );
2571
- console.log(chalk6.dim("Opening a separate Chromium window as fallback\u2026"));
3020
+ console.log(chalk7.dim("Opening a separate Chromium window as fallback\u2026"));
2572
3021
  console.log("");
2573
3022
  const browser = await resolveBrowser();
2574
3023
  console.log(`Open ${SESSION_URL}, then re-run prompt-exporter token`);
@@ -2583,20 +3032,20 @@ async function tokenCommand(options = {}) {
2583
3032
  return;
2584
3033
  }
2585
3034
  const info = await getCdpEndpointInfo(port);
2586
- console.log(chalk6.green(`Using existing Chromium CDP on port ${port}.`));
3035
+ console.log(chalk7.green(`Using existing Chromium CDP on port ${port}.`));
2587
3036
  if (info?.Browser) {
2588
- console.log(chalk6.dim(` Browser: ${info.Browser}`));
3037
+ console.log(chalk7.dim(` Browser: ${info.Browser}`));
2589
3038
  }
2590
- console.log(chalk6.dim(` Token file: ${getTokenPath()}`));
3039
+ console.log(chalk7.dim(` Token file: ${getTokenPath()}`));
2591
3040
  console.log("");
2592
3041
  try {
2593
3042
  const { target, reused, probe } = await prepareSessionTarget(port);
2594
3043
  console.log(
2595
- chalk6.dim(
3044
+ chalk7.dim(
2596
3045
  `${reused ? "Reused" : "Opened"} tab: ${target.title || target.id}`
2597
3046
  )
2598
3047
  );
2599
- console.log(chalk6.dim(` ${target.url || SESSION_URL}`));
3048
+ console.log(chalk7.dim(` ${target.url || SESSION_URL}`));
2600
3049
  console.log("");
2601
3050
  if (probe.loggedIn && probe.accessToken) {
2602
3051
  await persistAndAnnounce(probe.accessToken, show);
@@ -2604,31 +3053,31 @@ async function tokenCommand(options = {}) {
2604
3053
  }
2605
3054
  if (probe.anonymousBannerOnly || !probe.loggedIn) {
2606
3055
  console.log(
2607
- chalk6.yellow(
3056
+ chalk7.yellow(
2608
3057
  "No accessToken in the session tab yet (anonymous / not logged in)."
2609
3058
  )
2610
3059
  );
2611
3060
  if (probe.keys.length) {
2612
- console.log(chalk6.dim(` session keys: ${probe.keys.join(", ")}`));
3061
+ console.log(chalk7.dim(` session keys: ${probe.keys.join(", ")}`));
2613
3062
  }
2614
3063
  if (probe.error) {
2615
- console.log(chalk6.dim(` ${probe.error}`));
3064
+ console.log(chalk7.dim(` ${probe.error}`));
2616
3065
  }
2617
3066
  console.log("");
2618
3067
  const appTab = await ensureChatGptAppTab(port);
2619
- console.log(chalk6.cyan(`Focused ChatGPT for login: ${APP_URL}`));
2620
- console.log(chalk6.dim(` Tab: ${appTab.url || APP_URL}`));
3068
+ console.log(chalk7.cyan(`Focused ChatGPT for login: ${APP_URL}`));
3069
+ console.log(chalk7.dim(` Tab: ${appTab.url || APP_URL}`));
2621
3070
  console.log("");
2622
3071
  if (!wait) {
2623
3072
  printManualSteps();
2624
3073
  return;
2625
3074
  }
2626
3075
  console.log(
2627
- chalk6.bold(
3076
+ chalk7.bold(
2628
3077
  `Waiting up to ${Math.round(timeoutMs / 1e3)}s for login, then re-reading session\u2026`
2629
3078
  )
2630
3079
  );
2631
- console.log(chalk6.dim("Press Ctrl+C to cancel."));
3080
+ console.log(chalk7.dim("Press Ctrl+C to cancel."));
2632
3081
  console.log("");
2633
3082
  const finalProbe = await waitForAccessToken(port, {
2634
3083
  timeoutMs,
@@ -2636,7 +3085,7 @@ async function tokenCommand(options = {}) {
2636
3085
  onTick: (p) => {
2637
3086
  if (p.loggedIn) return;
2638
3087
  process.stdout.write(
2639
- chalk6.dim(
3088
+ chalk7.dim(
2640
3089
  ` waiting\u2026 (${p.anonymousBannerOnly ? "banner-only" : p.keys.join(",") || "no token"})\r`
2641
3090
  )
2642
3091
  );
@@ -2647,18 +3096,18 @@ async function tokenCommand(options = {}) {
2647
3096
  await persistAndAnnounce(finalProbe.accessToken, show);
2648
3097
  return;
2649
3098
  }
2650
- console.log(chalk6.red("Timed out \u2014 accessToken not found."));
3099
+ console.log(chalk7.red("Timed out \u2014 accessToken not found."));
2651
3100
  printManualSteps();
2652
3101
  process.exitCode = 1;
2653
3102
  return;
2654
3103
  }
2655
- console.log(chalk6.yellow("Could not read accessToken from the session page."));
2656
- if (probe.error) console.log(chalk6.dim(` ${probe.error}`));
3104
+ console.log(chalk7.yellow("Could not read accessToken from the session page."));
3105
+ if (probe.error) console.log(chalk7.dim(` ${probe.error}`));
2657
3106
  printManualSteps();
2658
3107
  process.exitCode = 1;
2659
3108
  } catch (error) {
2660
3109
  console.error(
2661
- chalk6.red(
3110
+ chalk7.red(
2662
3111
  `CDP session failed: ${error instanceof Error ? error.message : error}`
2663
3112
  )
2664
3113
  );
@@ -2677,8 +3126,18 @@ var chatgptSource = {
2677
3126
  }
2678
3127
  };
2679
3128
 
3129
+ // src/sources/lumo/index.ts
3130
+ var lumoSource = {
3131
+ id: "lumo",
3132
+ label: "Lumo",
3133
+ description: "lumo.proton.me (Proton) via Chromium CDP / in-app Redux",
3134
+ defaultDataDir() {
3135
+ return getSourceDataDir("lumo");
3136
+ }
3137
+ };
3138
+
2680
3139
  // src/sources/registry.ts
2681
- var sources = [chatgptSource];
3140
+ var sources = [chatgptSource, lumoSource];
2682
3141
  function listSources() {
2683
3142
  return sources.map(({ id, label, description }) => ({
2684
3143
  id,
@@ -2712,7 +3171,7 @@ function createCli() {
2712
3171
  const program2 = new Command();
2713
3172
  program2.name("prompt-exporter").description(
2714
3173
  "Export and sync AI conversation prompts from multiple sources to local JSON + Markdown"
2715
- ).version("2.0.0").hook("preAction", async () => {
3174
+ ).version("2.1.0").hook("preAction", async () => {
2716
3175
  await ensureHomeLayout();
2717
3176
  }).addHelpText(
2718
3177
  "after",
@@ -2728,6 +3187,8 @@ function createCli() {
2728
3187
  "Typical flow:",
2729
3188
  " prompt-exporter chromium start",
2730
3189
  " prompt-exporter sync --source chatgpt",
3190
+ " prompt-exporter chromium start --url https://lumo.proton.me/",
3191
+ " prompt-exporter sync --source lumo",
2731
3192
  "",
2732
3193
  "Data: ~/.prompt-exporter/{source}/conversations/"
2733
3194
  ].join("\n")
@@ -2793,6 +3254,7 @@ function createCli() {
2793
3254
  "Examples:",
2794
3255
  " prompt-exporter chromium start",
2795
3256
  " prompt-exporter chromium start --port 9222",
3257
+ " prompt-exporter chromium start --url https://lumo.proton.me/",
2796
3258
  " prompt-exporter chromium start --isolated"
2797
3259
  ].join("\n")
2798
3260
  ).action(async (options) => {
@@ -2837,6 +3299,15 @@ function createCli() {
2837
3299
  );
2838
3300
  const runBackup = async (options) => {
2839
3301
  const sourceId = resolveSourceId(options.source);
3302
+ if (sourceId === "lumo") {
3303
+ await lumoBackupCommand({
3304
+ output: resolveOutput(options),
3305
+ incremental: options.incremental,
3306
+ verbose: options.verbose,
3307
+ cdpPort: options.port
3308
+ });
3309
+ return;
3310
+ }
2840
3311
  if (sourceId !== "chatgpt") {
2841
3312
  throw new Error(
2842
3313
  `Source "${sourceId}" is registered but sync is not implemented yet`
@@ -2892,6 +3363,7 @@ function createCli() {
2892
3363
  "Examples:",
2893
3364
  " prompt-exporter sync",
2894
3365
  " prompt-exporter sync --source chatgpt",
3366
+ " prompt-exporter sync --source lumo",
2895
3367
  " prompt-exporter sync --download-files",
2896
3368
  " prompt-exporter sync --no-incremental"
2897
3369
  ].join("\n")
@@ -2919,6 +3391,14 @@ function createCli() {
2919
3391
  "List conversations from a specific project"
2920
3392
  ).option("-v, --verbose", "Verbose logging", false).option("--json", "Output as JSON", false).action(async (options) => {
2921
3393
  const sourceId = resolveSourceId(options.source);
3394
+ if (sourceId === "lumo") {
3395
+ await lumoListCommand({
3396
+ verbose: options.verbose,
3397
+ json: options.json,
3398
+ cdpPort: options.port
3399
+ });
3400
+ return;
3401
+ }
2922
3402
  if (sourceId !== "chatgpt") {
2923
3403
  throw new Error(`Source "${sourceId}" list not implemented yet`);
2924
3404
  }
package/docs/DEVELOPER.md CHANGED
@@ -17,6 +17,7 @@ src/
17
17
  types.ts # Source interface
18
18
  registry.ts
19
19
  chatgpt/ # chatgpt source (api/, index.ts)
20
+ lumo/ # lumo.proton.me (CDP Redux export)
20
21
  services/ # storage, markdown, backup (source-agnostic)
21
22
  utils/ # paths, credentials, cdp, browser
22
23
  aur/
@@ -8,7 +8,7 @@
8
8
  - Re-sync incrementally as chats change
9
9
  - Hand transcripts to another AI as context
10
10
 
11
- The first source is **chatgpt** (chatgpt.com). Planned next: Claude, Gemini, Copilot, Perplexity, and Grok.
11
+ The first implemented sources are **chatgpt** (chatgpt.com) and **lumo** ([lumo.proton.me](https://lumo.proton.me/)). Planned next: `claude`, `gemini`, `deepseek`, `perplexity`, `copilot` (Microsoft 365), `grok`, and `kimi`.
12
12
 
13
13
  **Platforms:** Linux is supported today; Windows and macOS are in focus for CDP/browser path parity.
14
14
 
@@ -89,6 +89,17 @@ prompt-exporter sync --no-incremental # force full re-download
89
89
  prompt-exporter sources
90
90
  ```
91
91
 
92
+ ### Lumo (Proton)
93
+
94
+ Lumo encrypts payloads on the server. Sync reads the **decrypted** in-app Redux store over CDP after login:
95
+
96
+ ```bash
97
+ prompt-exporter chromium start --url https://lumo.proton.me/
98
+ # log in; wait until chats appear in the sidebar
99
+ prompt-exporter sync --source lumo
100
+ prompt-exporter list --source lumo
101
+ ```
102
+
92
103
  `sync` is incremental by default (skips unchanged `update_time`).
93
104
 
94
105
  Auth prefers a logged-in Chromium CDP session. Node Bearer alone is often blocked by Cloudflare.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "prompt-exporter",
3
- "version": "2.0.1",
4
- "description": "CLI to sync AI conversation prompts from multiple sources (ChatGPT first) to local JSON + Markdown",
3
+ "version": "2.1.0",
4
+ "description": "CLI to sync AI conversation prompts from multiple sources (ChatGPT, Lumo, …) to local JSON + Markdown",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Alexandru Zbarcea <alexz@apache.org>",
7
7
  "type": "module",
@@ -17,16 +17,16 @@
17
17
  "README.md"
18
18
  ],
19
19
  "engines": {
20
- "node": ">=20"
20
+ "node": ">=24"
21
21
  },
22
22
  "scripts": {
23
- "dev": "tsx src/index.ts",
23
+ "dev": "node --disable-warning=DEP0205 --import tsx src/index.ts",
24
24
  "build": "tsup src/index.ts --format esm --dts --clean",
25
25
  "start": "node dist/index.js",
26
26
  "prepublishOnly": "npm run build",
27
27
  "typecheck": "tsc --noEmit",
28
- "test": "npm run build && tsx --test tests/*.test.ts",
29
- "test:unit": "tsx --test tests/*.test.ts",
28
+ "test": "npm run build && node --disable-warning=DEP0205 --import tsx --test tests/*.test.ts",
29
+ "test:unit": "node --disable-warning=DEP0205 --import tsx --test tests/*.test.ts",
30
30
  "ci": "npm run typecheck && npm run test"
31
31
  },
32
32
  "repository": {