weread-export 0.1.2 → 0.1.4

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
@@ -37,7 +37,7 @@ Key 存于 `~/.dsh/weread-export.json`(权限 0600),同步快照存于 `~/
37
37
  | `weread_readdata` | 阅读统计(weekly/monthly/annually/overall) |
38
38
  | `weread_sync` | 拉取书架 + 笔记本概览到本地缓存 |
39
39
  | `weread_export` | 多目标导出:`dest=flomo`(默认)/`local`(本地 Markdown,`localDir` 必填)/`notion`(本插件独立 Token+目标页);可按 `exportPrompt`/`usePrompt` 用 LLM 按 prompt 整理后导出,`limit` 控制条数 |
40
- | `weread_flomo` | flomo 快捷导出:#标签可自定义;默认按配置导出条数(0=全部,超长自动拆多条 MEMO),`limit` 可临时覆盖 |
40
+ | `weread_flomo` | flomo 快捷导出:#标签可自定义;默认按配置导出条数(0=全部,超长自动拆多条 MEMO),`limit` 可临时覆盖。flomo 凭据可在本插件面板「flomo 导出」区直接配置(与 dsh-flomo 共享) |
41
41
 
42
42
  ## 常见问题
43
43
 
package/README.zh.md CHANGED
@@ -37,7 +37,7 @@ Key 存于 `~/.dsh/weread-export.json`(权限 0600),同步快照存于 `~/
37
37
  | `weread_readdata` | 阅读统计(weekly/monthly/annually/overall) |
38
38
  | `weread_sync` | 拉取书架 + 笔记本概览到本地缓存 |
39
39
  | `weread_export` | 多目标导出:`dest=flomo`(默认)/`local`(本地 Markdown,`localDir` 必填)/`notion`(本插件独立 Token+目标页);可按 `exportPrompt`/`usePrompt` 用 LLM 按 prompt 整理后导出,`limit` 控制条数 |
40
- | `weread_flomo` | flomo 快捷导出:#标签可自定义;默认按配置导出条数(0=全部,超长自动拆多条 MEMO),`limit` 可临时覆盖 |
40
+ | `weread_flomo` | flomo 快捷导出:#标签可自定义;默认按配置导出条数(0=全部,超长自动拆多条 MEMO),`limit` 可临时覆盖。flomo 凭据可在本插件面板「flomo 导出」区直接配置(与 dsh-flomo 共享) |
41
41
 
42
42
  ## 常见问题
43
43
 
package/lib/client.js CHANGED
@@ -113,11 +113,25 @@ window.__ModuleLoader__.load({
113
113
  fontSize: "13px",
114
114
  margin: 0
115
115
  },
116
- section: {
116
+ /** One visually separated group card. */
117
+ group: {
118
+ display: "flex",
119
+ flexDirection: "column",
120
+ gap: "8px",
121
+ marginTop: "6px",
122
+ padding: "12px 14px",
123
+ borderRadius: "10px",
124
+ border: "1px solid rgba(128,128,128,0.22)",
125
+ background: "rgba(128,128,128,0.05)",
126
+ minWidth: 0
127
+ },
128
+ groupTitle: {
117
129
  fontWeight: 600,
118
130
  fontSize: "12px",
119
- margin: "6px 0 0",
120
- opacity: .9
131
+ opacity: .95,
132
+ margin: 0,
133
+ paddingBottom: "6px",
134
+ borderBottom: "1px solid rgba(128,128,128,0.18)"
121
135
  },
122
136
  status: {
123
137
  fontSize: "12px",
@@ -133,7 +147,8 @@ window.__ModuleLoader__.load({
133
147
  display: "flex",
134
148
  gap: "6px",
135
149
  alignItems: "center",
136
- minWidth: 0
150
+ minWidth: 0,
151
+ flexWrap: "wrap"
137
152
  },
138
153
  col: {
139
154
  display: "flex",
@@ -212,7 +227,7 @@ window.__ModuleLoader__.load({
212
227
  if (view === null) return "加载中…";
213
228
  if (!view.configured) return "未配置 — 打开 https://weread.qq.com/r/weread-skills,用微信读书账号登录后点击「创建 Key」并复制(wrk- 开头),粘贴到上方输入框。";
214
229
  const destLabel = view.exportDest === "local" ? "本地文件" : view.exportDest === "notion" ? "Notion" : "flomo";
215
- return "已配置 · Key " + view.apiKeyMasked + "\n默认导出目标 " + destLabel + " · 导出条数 " + (view.exportLimit === 0 ? "全部" : view.exportLimit + " 条") + " · 默认标签 #" + view.defaultFlomoTag + "\nprompt 处理 " + (view.usePrompt ? "开(" + (view.llmConfigured ? view.llmModel + " @ " + view.llmBaseUrl : "LLM 未配置") + ")" : "关") + " · Notion " + (view.notionConfigured ? "已配置" + (view.notionTargetPageId !== "" ? " + 目标页" : "(未填目标页)") : "未配置") + " · flomo " + (view.flomoConfigured ? "已配置" : "未配置") + " · 缓存书架 " + view.cachedShelfBooks + " 本 / 有笔记 " + view.cachedNoteBooks + " 本" + (view.lastSyncAt !== "" ? " · 最近同步 " + view.lastSyncAt : "");
230
+ return "已配置 · Key " + view.apiKeyMasked + "\n默认导出目标 " + destLabel + " · 导出条数 " + (view.exportLimit === 0 ? "全部" : view.exportLimit + " 条") + " · 默认标签 #" + view.defaultFlomoTag + "\nprompt 处理 " + (view.usePrompt ? "开(" + (view.llmConfigured ? view.llmModel + " @ " + view.llmBaseUrl : "LLM 未配置") + ")" : "关") + " · Notion " + (view.notionConfigured ? "已配置" + (view.notionTargetPageId !== "" ? " + 目标页" : "(未填目标页)") : "未配置") + " · flomo " + (view.flomoConfigured ? "已配置(" + view.flomoSource + " " + view.flomoMasked + ")" : "未配置") + " · 缓存书架 " + view.cachedShelfBooks + " 本 / 有笔记 " + view.cachedNoteBooks + " 本" + (view.lastSyncAt !== "" ? " · 最近同步 " + view.lastSyncAt : "");
216
231
  }
217
232
  /** The WeRead settings panel component. */
218
233
  function WereadSettingsPanel() {
@@ -224,6 +239,8 @@ window.__ModuleLoader__.load({
224
239
  const [exportDest, setExportDest] = (0, react.useState)("flomo");
225
240
  const [localExportDir, setLocalExportDir] = (0, react.useState)("");
226
241
  const [notionToken, setNotionToken] = (0, react.useState)("");
242
+ const [flomoWebhookUrl, setFlomoWebhookUrl] = (0, react.useState)("");
243
+ const [flomoApiKey, setFlomoApiKey] = (0, react.useState)("");
227
244
  const [notionTargetPageId, setNotionTargetPageId] = (0, react.useState)("");
228
245
  const [usePrompt, setUsePrompt] = (0, react.useState)(false);
229
246
  const [llmBaseUrl, setLlmBaseUrl] = (0, react.useState)("");
@@ -307,29 +324,41 @@ window.__ModuleLoader__.load({
307
324
  if (localExportDir.trim() !== "") patch.localExportDir = localExportDir.trim();
308
325
  if (notionToken.trim() !== "") patch.notionToken = notionToken.trim();
309
326
  if (notionTargetPageId.trim() !== "") patch.notionTargetPageId = notionTargetPageId.trim();
327
+ if (flomoWebhookUrl.trim() !== "") patch.flomoWebhookUrl = flomoWebhookUrl.trim();
328
+ if (flomoApiKey.trim() !== "") patch.flomoApiKey = flomoApiKey.trim();
310
329
  patch.usePrompt = usePrompt;
311
330
  if (llmBaseUrl.trim() !== "") patch.llmBaseUrl = llmBaseUrl.trim();
312
331
  if (llmApiKey.trim() !== "") patch.llmApiKey = llmApiKey.trim();
313
332
  if (llmModel.trim() !== "") patch.llmModel = llmModel.trim();
314
333
  if (exportPrompt.trim() !== "") patch.exportPrompt = exportPrompt;
315
334
  const next = await api.setConfig(patch);
316
- setView({
317
- ...next,
318
- flomoConfigured: view?.flomoConfigured ?? false,
319
- cachedShelfBooks: view?.cachedShelfBooks ?? 0,
320
- cachedNoteBooks: view?.cachedNoteBooks ?? 0,
321
- cacheUpdatedAt: view?.cacheUpdatedAt ?? ""
322
- });
335
+ const status = await api.getStatus();
336
+ setView(status);
323
337
  setMessage(next.configured ? "配置已保存。" : "配置未保存完整:缺少 API Key。");
324
338
  setApiKey("");
325
339
  setNotionToken("");
326
340
  setLlmApiKey("");
341
+ setFlomoWebhookUrl("");
342
+ setFlomoApiKey("");
327
343
  } catch (error) {
328
344
  setMessage("保存失败: " + String(error instanceof Error ? error.message : error));
329
345
  } finally {
330
346
  setBusy(false);
331
347
  }
332
348
  };
349
+ const clearFlomoConfig = async () => {
350
+ setBusy(true);
351
+ setMessage("");
352
+ try {
353
+ await api.setConfig({ flomoReset: true });
354
+ setView(await api.getStatus());
355
+ setMessage("已清除 flomo 配置。");
356
+ } catch (error) {
357
+ setMessage("清除失败: " + String(error instanceof Error ? error.message : error));
358
+ } finally {
359
+ setBusy(false);
360
+ }
361
+ };
333
362
  const clearConfig = async () => {
334
363
  setBusy(true);
335
364
  setMessage("");
@@ -338,6 +367,9 @@ window.__ModuleLoader__.load({
338
367
  setView({
339
368
  ...next,
340
369
  flomoConfigured: view?.flomoConfigured ?? false,
370
+ flomoSource: view?.flomoSource ?? "",
371
+ flomoMasked: view?.flomoMasked ?? "",
372
+ flomoConfigPath: view?.flomoConfigPath ?? "",
341
373
  cachedShelfBooks: view?.cachedShelfBooks ?? 0,
342
374
  cachedNoteBooks: view?.cachedNoteBooks ?? 0,
343
375
  cacheUpdatedAt: view?.cacheUpdatedAt ?? ""
@@ -414,322 +446,394 @@ window.__ModuleLoader__.load({
414
446
  style: view?.configured === false ? s.statusWarn : s.status,
415
447
  children: statusText(view)
416
448
  }),
417
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
418
- style: s.section,
419
- children: "连接与导出偏好"
420
- }),
421
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
422
- style: s.row,
423
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
424
- style: s.input,
425
- placeholder: "Skills API Key(wrk- 开头)",
426
- value: apiKey,
427
- onChange: (e) => setApiKey(e.target.value)
428
- })
429
- }),
430
449
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
431
- style: s.row,
450
+ style: s.group,
432
451
  children: [
433
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
434
- style: s.label,
435
- children: "默认导出标签"
436
- }),
437
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
438
- style: {
439
- ...s.input,
440
- width: "130px"
441
- },
442
- placeholder: "如 读书笔记",
443
- value: defaultFlomoTag,
444
- onChange: (e) => setDefaultFlomoTag(e.target.value)
452
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
453
+ style: s.groupTitle,
454
+ children: "① 连接与导出偏好"
445
455
  }),
446
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
447
- style: s.label,
448
- children: "导出条数"
456
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
457
+ style: s.row,
458
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
459
+ style: s.input,
460
+ placeholder: "Skills API Key(wrk- 开头)",
461
+ value: apiKey,
462
+ onChange: (e) => setApiKey(e.target.value)
463
+ })
449
464
  }),
450
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
451
- style: {
452
- ...s.select,
453
- flex: 0,
454
- minWidth: "104px"
455
- },
456
- value: limitChoice,
457
- onChange: (e) => setLimitChoice(e.target.value),
465
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
466
+ style: s.row,
458
467
  children: [
459
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
460
- value: "0",
461
- children: "全部导出"
468
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
469
+ style: s.label,
470
+ children: "默认导出标签"
462
471
  }),
463
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
464
- value: "20",
465
- children: "20 条(默认)"
472
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
473
+ style: {
474
+ ...s.input,
475
+ width: "130px"
476
+ },
477
+ placeholder: "如 读书笔记",
478
+ value: defaultFlomoTag,
479
+ onChange: (e) => setDefaultFlomoTag(e.target.value)
466
480
  }),
467
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
468
- value: "50",
469
- children: "50 条"
481
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
482
+ style: s.label,
483
+ children: "导出条数"
470
484
  }),
471
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
472
- value: "100",
473
- children: "100 条"
485
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
486
+ style: {
487
+ ...s.select,
488
+ flex: 0,
489
+ minWidth: "104px"
490
+ },
491
+ value: limitChoice,
492
+ onChange: (e) => setLimitChoice(e.target.value),
493
+ children: [
494
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
495
+ value: "0",
496
+ children: "全部导出"
497
+ }),
498
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
499
+ value: "20",
500
+ children: "20 条(默认)"
501
+ }),
502
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
503
+ value: "50",
504
+ children: "50 条"
505
+ }),
506
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
507
+ value: "100",
508
+ children: "100 条"
509
+ }),
510
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
511
+ value: "custom",
512
+ children: "自定义…"
513
+ })
514
+ ]
474
515
  }),
475
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
476
- value: "custom",
477
- children: "自定义…"
516
+ limitChoice === "custom" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
517
+ style: {
518
+ ...s.input,
519
+ width: "64px"
520
+ },
521
+ placeholder: "条数",
522
+ value: customLimit,
523
+ onChange: (e) => setCustomLimit(e.target.value)
478
524
  })
479
525
  ]
480
526
  }),
481
- limitChoice === "custom" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
482
- style: {
483
- ...s.input,
484
- width: "64px"
485
- },
486
- placeholder: "条数",
487
- value: customLimit,
488
- onChange: (e) => setCustomLimit(e.target.value)
489
- }),
490
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: s.flex }),
491
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
492
- style: s.button,
493
- onClick: () => void saveConfig(),
494
- disabled: busy,
495
- children: "保存配置"
496
- }),
497
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
498
- style: s.button,
499
- onClick: () => void runTest(),
500
- disabled: busy || !view?.configured,
501
- children: "测试连接"
527
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
528
+ style: s.row,
529
+ children: [
530
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
531
+ style: s.label,
532
+ children: "默认目标"
533
+ }),
534
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
535
+ style: {
536
+ ...s.select,
537
+ flex: 0,
538
+ minWidth: "110px"
539
+ },
540
+ value: exportDest,
541
+ onChange: (e) => setExportDest(e.target.value),
542
+ children: [
543
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
544
+ value: "flomo",
545
+ children: "flomo"
546
+ }),
547
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
548
+ value: "local",
549
+ children: "本地文件"
550
+ }),
551
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
552
+ value: "notion",
553
+ children: "Notion"
554
+ })
555
+ ]
556
+ }),
557
+ exportDest === "local" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
558
+ style: s.input,
559
+ placeholder: "本地导出目录(绝对路径,可留空导出时填)",
560
+ value: localExportDir,
561
+ onChange: (e) => setLocalExportDir(e.target.value)
562
+ })
563
+ ]
502
564
  }),
503
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
504
- style: s.button,
505
- onClick: () => void clearConfig(),
506
- disabled: busy,
507
- children: "清除"
565
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
566
+ style: s.row,
567
+ children: [
568
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: s.flex }),
569
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
570
+ style: s.button,
571
+ onClick: () => void saveConfig(),
572
+ disabled: busy,
573
+ children: "保存配置"
574
+ }),
575
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
576
+ style: s.button,
577
+ onClick: () => void runTest(),
578
+ disabled: busy || !view?.configured,
579
+ children: "测试连接"
580
+ }),
581
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
582
+ style: s.button,
583
+ onClick: () => void clearConfig(),
584
+ disabled: busy,
585
+ children: "清除"
586
+ })
587
+ ]
508
588
  })
509
589
  ]
510
590
  }),
511
591
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
512
- style: s.row,
592
+ style: s.group,
513
593
  children: [
514
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
515
- style: s.label,
516
- children: "默认目标"
594
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
595
+ style: s.groupTitle,
596
+ children: "② flomo 导出"
517
597
  }),
518
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
519
- style: {
520
- ...s.select,
521
- flex: 0,
522
- minWidth: "110px"
523
- },
524
- value: exportDest,
525
- onChange: (e) => setExportDest(e.target.value),
598
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
599
+ style: view?.flomoConfigured === false ? s.statusWarn : s.status,
600
+ children: view === null ? "加载中…" : view.flomoConfigured ? "已配置:" + view.flomoSource + " " + view.flomoMasked + "(与「Flomo」面板共享,文件 " + view.flomoConfigPath + ")" : "未配置 — 在 flomo 设置页(flomoapp.com/mine?source=incoming_webhook)获取 API URL,粘贴到下方。"
601
+ }),
602
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
603
+ style: s.row,
604
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
605
+ style: s.input,
606
+ type: "password",
607
+ placeholder: "flomo API URL(https://flomoapp.com/iwh/xxxx)",
608
+ value: flomoWebhookUrl,
609
+ onChange: (e) => setFlomoWebhookUrl(e.target.value)
610
+ })
611
+ }),
612
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
613
+ style: s.row,
526
614
  children: [
527
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
528
- value: "flomo",
529
- children: "flomo"
615
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
616
+ style: s.input,
617
+ type: "password",
618
+ placeholder: "或 flomo API Key(新版,与 URL 二选一)",
619
+ value: flomoApiKey,
620
+ onChange: (e) => setFlomoApiKey(e.target.value)
530
621
  }),
531
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
532
- value: "local",
533
- children: "本地文件"
622
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
623
+ style: s.button,
624
+ onClick: () => void saveConfig(),
625
+ disabled: busy,
626
+ children: "保存 flomo"
534
627
  }),
535
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
536
- value: "notion",
537
- children: "Notion"
628
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
629
+ style: s.button,
630
+ onClick: () => void clearFlomoConfig(),
631
+ disabled: busy,
632
+ children: "清除"
538
633
  })
539
634
  ]
540
- }),
541
- exportDest === "local" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
542
- style: s.input,
543
- placeholder: "本地导出目录(绝对路径,可留空导出时填)",
544
- value: localExportDir,
545
- onChange: (e) => setLocalExportDir(e.target.value)
546
635
  })
547
636
  ]
548
637
  }),
549
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
550
- style: s.section,
551
- children: "Notion(本插件独立配置)"
552
- }),
553
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
554
- style: s.row,
555
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
556
- style: s.input,
557
- type: "password",
558
- placeholder: "Notion Integration Token(notion.so/my-integrations 创建)",
559
- value: notionToken,
560
- onChange: (e) => setNotionToken(e.target.value)
561
- })
562
- }),
563
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
564
- style: s.row,
565
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
566
- style: s.input,
567
- placeholder: "目标父页面 URL 或 32 位 ID(页面需分享给该 Integration)",
568
- value: notionTargetPageId,
569
- onChange: (e) => setNotionTargetPageId(e.target.value)
570
- })
571
- }),
572
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
573
- style: s.section,
574
- children: "AI · prompt 处理(导出前用 LLM 按模板整理)"
575
- }),
576
638
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
577
- style: s.row,
639
+ style: s.group,
578
640
  children: [
579
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
580
- style: {
581
- ...s.label,
582
- display: "flex",
583
- alignItems: "center",
584
- gap: "4px",
585
- cursor: "pointer"
586
- },
587
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
588
- type: "checkbox",
589
- checked: usePrompt,
590
- onChange: (e) => setUsePrompt(e.target.checked)
591
- }), "启用"]
641
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
642
+ style: s.groupTitle,
643
+ children: "③ Notion 导出"
592
644
  }),
593
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
594
- style: s.input,
595
- placeholder: "Base URL(默认 https://api.deepseek.com/v1)",
596
- value: llmBaseUrl,
597
- onChange: (e) => setLlmBaseUrl(e.target.value)
645
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
646
+ style: s.row,
647
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
648
+ style: s.input,
649
+ type: "password",
650
+ placeholder: "Notion Integration Token(notion.so/my-integrations 创建)",
651
+ value: notionToken,
652
+ onChange: (e) => setNotionToken(e.target.value)
653
+ })
598
654
  }),
599
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
600
- style: {
601
- ...s.input,
602
- width: "130px"
603
- },
604
- placeholder: "模型",
605
- value: llmModel,
606
- onChange: (e) => setLlmModel(e.target.value)
607
- }),
608
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
609
- style: s.input,
610
- type: "password",
611
- placeholder: "LLM API Key(自定义)",
612
- value: llmApiKey,
613
- onChange: (e) => setLlmApiKey(e.target.value)
655
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
656
+ style: s.row,
657
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
658
+ style: s.input,
659
+ placeholder: "目标父页面 URL 或 32 位 ID(页面需分享给该 Integration)",
660
+ value: notionTargetPageId,
661
+ onChange: (e) => setNotionTargetPageId(e.target.value)
662
+ })
614
663
  })
615
664
  ]
616
665
  }),
617
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
618
- style: s.col,
619
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
620
- style: s.textarea,
621
- placeholder: "导出 prompt 模板,可用 {title} {author} {highlights} {thoughts} 占位符",
622
- value: exportPrompt,
623
- onChange: (e) => setExportPrompt(e.target.value)
624
- })
625
- }),
626
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
627
- style: s.row,
628
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
629
- style: s.button,
630
- onClick: () => void runSync(),
631
- disabled: busy || !view?.configured,
632
- children: "同步书架/笔记"
633
- })
634
- }),
635
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
636
- style: s.section,
637
- children: "快捷导出"
638
- }),
639
666
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
640
- style: s.row,
641
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
642
- style: s.label,
643
- children: "选择书籍"
644
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
645
- style: s.select,
646
- value: bookId,
647
- onChange: (e) => setBookId(e.target.value),
648
- disabled: books.length === 0,
649
- children: books.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
650
- value: "",
651
- children: "(缓存无书籍,先同步)"
652
- }) : books.map((b) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
653
- value: b.bookId,
654
- children: [
655
- "《",
656
- b.title,
657
- "》",
658
- b.author !== "" ? " · " + b.author : ""
659
- ]
660
- }, b.bookId))
661
- })]
662
- }),
663
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
664
- style: s.row,
667
+ style: s.group,
665
668
  children: [
666
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
667
- style: s.label,
668
- children: "本次目标"
669
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
670
+ style: s.groupTitle,
671
+ children: "④ AI · prompt 处理(导出前用 LLM 按模板整理)"
669
672
  }),
670
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
671
- style: {
672
- ...s.select,
673
- flex: 0,
674
- minWidth: "110px"
675
- },
676
- value: destNow,
677
- onChange: (e) => setDestNow(e.target.value),
673
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
674
+ style: s.row,
678
675
  children: [
679
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
680
- value: "flomo",
681
- children: "flomo"
676
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
677
+ style: {
678
+ ...s.label,
679
+ display: "flex",
680
+ alignItems: "center",
681
+ gap: "4px",
682
+ cursor: "pointer"
683
+ },
684
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
685
+ type: "checkbox",
686
+ checked: usePrompt,
687
+ onChange: (e) => setUsePrompt(e.target.checked)
688
+ }), "启用"]
689
+ }),
690
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
691
+ style: s.input,
692
+ placeholder: "Base URL(默认 https://api.deepseek.com/v1)",
693
+ value: llmBaseUrl,
694
+ onChange: (e) => setLlmBaseUrl(e.target.value)
682
695
  }),
683
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
684
- value: "local",
685
- children: "本地文件"
696
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
697
+ style: {
698
+ ...s.input,
699
+ width: "130px"
700
+ },
701
+ placeholder: "模型",
702
+ value: llmModel,
703
+ onChange: (e) => setLlmModel(e.target.value)
686
704
  }),
687
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
688
- value: "notion",
689
- children: "Notion"
705
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
706
+ style: s.input,
707
+ type: "password",
708
+ placeholder: "LLM API Key(自定义)",
709
+ value: llmApiKey,
710
+ onChange: (e) => setLlmApiKey(e.target.value)
690
711
  })
691
712
  ]
692
713
  }),
693
- destNow === "local" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
694
- style: s.input,
695
- placeholder: "导出路径(必填)",
696
- value: localDirNow,
697
- onChange: (e) => setLocalDirNow(e.target.value)
698
- }),
699
- destNow === "flomo" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
700
- style: {
701
- ...s.input,
702
- width: "150px"
703
- },
704
- placeholder: "本次标签(#)",
705
- value: flomoTagNow,
706
- onChange: (e) => setFlomoTagNow(e.target.value)
714
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
715
+ style: s.col,
716
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
717
+ style: s.textarea,
718
+ placeholder: "导出 prompt 模板,可用 {title} {author} {highlights} {thoughts} 占位符",
719
+ value: exportPrompt,
720
+ onChange: (e) => setExportPrompt(e.target.value)
721
+ })
707
722
  })
708
723
  ]
709
724
  }),
710
725
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
711
- style: s.row,
726
+ style: s.group,
712
727
  children: [
713
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
714
- style: {
715
- ...s.label,
716
- display: "flex",
717
- alignItems: "center",
718
- gap: "4px",
719
- cursor: "pointer"
720
- },
721
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
722
- type: "checkbox",
723
- checked: usePromptNow === null ? view?.usePrompt ?? false : usePromptNow,
724
- onChange: (e) => setUsePromptNow(e.target.checked)
725
- }), "本次用 prompt 处理(默认跟随配置)"]
728
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
729
+ style: s.groupTitle,
730
+ children: "⑤ 快捷导出"
731
+ }),
732
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
733
+ style: s.row,
734
+ children: [
735
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
736
+ style: s.label,
737
+ children: "选择书籍"
738
+ }),
739
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
740
+ style: s.select,
741
+ value: bookId,
742
+ onChange: (e) => setBookId(e.target.value),
743
+ disabled: books.length === 0,
744
+ children: books.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
745
+ value: "",
746
+ children: "(缓存无书籍,先同步)"
747
+ }) : books.map((b) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
748
+ value: b.bookId,
749
+ children: [
750
+ "《",
751
+ b.title,
752
+ "》",
753
+ b.author !== "" ? " · " + b.author : ""
754
+ ]
755
+ }, b.bookId))
756
+ }),
757
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
758
+ style: s.button,
759
+ onClick: () => void runSync(),
760
+ disabled: busy || !view?.configured,
761
+ children: "同步书架/笔记"
762
+ })
763
+ ]
764
+ }),
765
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
766
+ style: s.row,
767
+ children: [
768
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
769
+ style: s.label,
770
+ children: "本次目标"
771
+ }),
772
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
773
+ style: {
774
+ ...s.select,
775
+ flex: 0,
776
+ minWidth: "110px"
777
+ },
778
+ value: destNow,
779
+ onChange: (e) => setDestNow(e.target.value),
780
+ children: [
781
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
782
+ value: "flomo",
783
+ children: "flomo"
784
+ }),
785
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
786
+ value: "local",
787
+ children: "本地文件"
788
+ }),
789
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
790
+ value: "notion",
791
+ children: "Notion"
792
+ })
793
+ ]
794
+ }),
795
+ destNow === "local" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
796
+ style: s.input,
797
+ placeholder: "导出路径(必填)",
798
+ value: localDirNow,
799
+ onChange: (e) => setLocalDirNow(e.target.value)
800
+ }),
801
+ destNow === "flomo" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
802
+ style: {
803
+ ...s.input,
804
+ width: "150px"
805
+ },
806
+ placeholder: "本次标签(#)",
807
+ value: flomoTagNow,
808
+ onChange: (e) => setFlomoTagNow(e.target.value)
809
+ })
810
+ ]
726
811
  }),
727
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: s.flex }),
728
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
729
- style: s.button,
730
- onClick: () => void runExportNow(),
731
- disabled: busy || !view?.configured,
732
- children: ["导出到 ", destNow === "local" ? "本地" : destNow === "notion" ? "Notion" : "flomo"]
812
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
813
+ style: s.row,
814
+ children: [
815
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
816
+ style: {
817
+ ...s.label,
818
+ display: "flex",
819
+ alignItems: "center",
820
+ gap: "4px",
821
+ cursor: "pointer"
822
+ },
823
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
824
+ type: "checkbox",
825
+ checked: usePromptNow === null ? view?.usePrompt ?? false : usePromptNow,
826
+ onChange: (e) => setUsePromptNow(e.target.checked)
827
+ }), "本次用 prompt 处理(默认跟随配置)"]
828
+ }),
829
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: s.flex }),
830
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
831
+ style: s.button,
832
+ onClick: () => void runExportNow(),
833
+ disabled: busy || !view?.configured,
834
+ children: ["导出到 ", destNow === "local" ? "本地" : destNow === "notion" ? "Notion" : "flomo"]
835
+ })
836
+ ]
733
837
  })
734
838
  ]
735
839
  }),
package/lib/client.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":[],"sources":["../src/client/api.ts","../src/client/WereadSettingsPanel.tsx","../src/client/index.ts"],"sourcesContent":["/**\n * Browser-side API client for the /api/weread-export route family. The only\n * data access path the settings panel uses — plain fetch, same origin.\n */\n\n/** Public config view (mirrors the host contract). */\nexport interface WereadConfigView {\n configured: boolean\n apiKeyMasked: string\n defaultFlomoTag: string\n /** 0 = export ALL highlights; N > 0 = cap at N. */\n exportLimit: number\n exportDest: string\n localExportDir: string\n notionConfigured: boolean\n notionTargetPageId: string\n usePrompt: boolean\n llmConfigured: boolean\n llmBaseUrl: string\n llmModel: string\n lastSyncAt: string\n configPath: string\n}\n\n/** Status view with cache + flomo stats. */\nexport interface WereadStatusView extends WereadConfigView {\n flomoConfigured: boolean\n cachedShelfBooks: number\n cachedNoteBooks: number\n cacheUpdatedAt: string\n}\n\n/** Sync result. */\nexport interface WereadSyncResult {\n ok: boolean\n message: string\n shelfBooks: number\n notebooks: number\n}\n\n/** One cached book for the export picker. */\nexport interface WereadBook {\n bookId: string\n title: string\n author: string\n}\n\n/** Flomo export result. */\nexport interface WereadFlomoResult {\n ok: boolean\n message: string\n sent: number\n memoCount?: number\n bookId?: string\n}\n\n/** Error carrying the route's JSON error message. */\nexport class WereadApiError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'WereadApiError'\n }\n}\n\n/** Parse a JSON response or throw a WereadApiError. */\nasync function readJson<T>(response: Response): Promise<T> {\n let body: unknown\n try {\n body = await response.json()\n } catch {\n throw new WereadApiError(`HTTP ${response.status}: invalid JSON response`)\n }\n if (!response.ok) {\n const message = typeof body === 'object' && body !== null && typeof (body as { error?: unknown }).error === 'string'\n ? (body as { error: string }).error\n : `HTTP ${response.status}`\n throw new WereadApiError(message)\n }\n return body as T\n}\n\n/** Plain fetch helper with an error wrapper. */\nasync function request<T>(path: string, init?: RequestInit): Promise<T> {\n let response: Response\n try {\n response = await fetch(path, init)\n } catch (error) {\n throw new WereadApiError('网络请求失败: ' + String(error instanceof Error ? error.message : error))\n }\n return readJson<T>(response)\n}\n\n/** The weread panel API. */\nexport class WereadApi {\n async getConfig(): Promise<WereadConfigView> {\n return request<WereadConfigView>('/api/weread-export/config')\n }\n\n async setConfig(patch: Record<string, unknown>): Promise<WereadConfigView> {\n return request<WereadConfigView>('/api/weread-export/config', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(patch),\n })\n }\n\n async getStatus(): Promise<WereadStatusView> {\n return request<WereadStatusView>('/api/weread-export/status')\n }\n\n async test(): Promise<{ ok: boolean; message: string }> {\n return request<{ ok: boolean; message: string }>('/api/weread-export/test', { method: 'POST' })\n }\n\n async sync(): Promise<WereadSyncResult> {\n return request<WereadSyncResult>('/api/weread-export/sync', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({}),\n })\n }\n\n async books(): Promise<WereadBook[]> {\n const data = await request<{ books: WereadBook[] }>('/api/weread-export/books')\n return data.books ?? []\n }\n\n async exportFlomo(bookId: string, tag: string, limit = 20): Promise<WereadFlomoResult> {\n return request<WereadFlomoResult>('/api/weread-export/flomo', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ bookId, tag, limit }),\n })\n }\n\n /** Multi-target export: flomo / local / notion with optional prompt. */\n async exportData(body: Record<string, unknown>): Promise<WereadFlomoResult> {\n return request<WereadFlomoResult>('/api/weread-export/export', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n })\n }\n}\n","/**\n * WeRead settings panel — rendered inside the web settings page\n * (settings.section entry). Connection setup (Skills API key), export\n * preferences (destination flomo/local/Notion, per-export limit, local dir,\n * Notion token + target page, LLM prompt processing with a user-editable\n * template), manual sync, and a quick multi-target export. Plain React,\n * inline styles only.\n */\nimport { useCallback, useEffect, useState } from 'react'\nimport {\n WereadApi, type WereadConfigView, type WereadStatusView, type WereadSyncResult, type WereadBook, type WereadFlomoResult,\n} from './api.ts'\n\n/** Module-level API client (stateless; the component closes over it). */\nconst api = new WereadApi()\n\n/** One shared style sheet (kept tiny and theme-agnostic). */\nconst s = {\n card: {\n display: 'flex',\n flexDirection: 'column',\n gap: '10px',\n maxWidth: '680px',\n padding: '14px 16px',\n borderRadius: '10px',\n border: '1px solid rgba(128,128,128,0.3)',\n fontSize: '13px',\n color: 'inherit',\n } as const,\n title: { fontWeight: 600, fontSize: '13px', margin: 0 } as const,\n section: { fontWeight: 600, fontSize: '12px', margin: '6px 0 0', opacity: 0.9 } as const,\n status: { fontSize: '12px', opacity: 0.85, whiteSpace: 'pre-wrap' } as const,\n statusWarn: { fontSize: '12px', opacity: 0.9, color: '#c9763a' } as const,\n row: { display: 'flex', gap: '6px', alignItems: 'center', minWidth: 0 } as const,\n col: { display: 'flex', flexDirection: 'column', gap: '4px', minWidth: 0 } as const,\n label: { fontSize: '12px', opacity: 0.8, whiteSpace: 'nowrap' } as const,\n input: {\n flex: 1,\n minWidth: 0,\n boxSizing: 'border-box',\n padding: '5px 8px',\n borderRadius: '6px',\n border: '1px solid rgba(128,128,128,0.35)',\n background: 'rgba(128,128,128,0.08)',\n color: 'inherit',\n fontSize: '12px',\n } as const,\n textarea: {\n width: '100%',\n minHeight: '96px',\n boxSizing: 'border-box',\n padding: '6px 8px',\n borderRadius: '6px',\n border: '1px solid rgba(128,128,128,0.35)',\n background: 'rgba(128,128,128,0.08)',\n color: 'inherit',\n fontSize: '12px',\n fontFamily: 'inherit',\n lineHeight: 1.5,\n resize: 'vertical',\n } as const,\n select: {\n flex: 1,\n minWidth: 0,\n maxWidth: '100%',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n padding: '4px 6px',\n borderRadius: '6px',\n border: '1px solid rgba(128,128,128,0.35)',\n background: 'rgba(128,128,128,0.08)',\n color: 'inherit',\n fontSize: '12px',\n } as const,\n flex: { flex: 1 } as const,\n button: {\n padding: '4px 10px',\n borderRadius: '6px',\n cursor: 'pointer',\n border: '1px solid rgba(128,128,128,0.4)',\n background: 'rgba(128,128,128,0.14)',\n color: 'inherit',\n fontSize: '12px',\n whiteSpace: 'nowrap',\n } as const,\n msg: { fontSize: '12px', whiteSpace: 'pre-wrap', wordBreak: 'break-all', opacity: 0.9 } as const,\n hint: { fontSize: '11px', opacity: 0.75, lineHeight: 1.6 } as const,\n}\n\n/** Status line for the current config view. */\nfunction statusText(view: WereadStatusView | null): string {\n if (view === null) return '加载中…'\n if (!view.configured) {\n return '未配置 — 打开 https://weread.qq.com/r/weread-skills,用微信读书账号登录后点击「创建 Key」并复制(wrk- 开头),粘贴到上方输入框。'\n }\n const destLabel = view.exportDest === 'local' ? '本地文件' : (view.exportDest === 'notion' ? 'Notion' : 'flomo')\n return (\n '已配置 · Key ' + view.apiKeyMasked +\n '\\n默认导出目标 ' + destLabel + ' · 导出条数 ' + (view.exportLimit === 0 ? '全部' : view.exportLimit + ' 条') +\n ' · 默认标签 #' + view.defaultFlomoTag +\n '\\nprompt 处理 ' + (view.usePrompt ? '开(' + (view.llmConfigured ? view.llmModel + ' @ ' + view.llmBaseUrl : 'LLM 未配置') + ')' : '关') +\n ' · Notion ' + (view.notionConfigured ? '已配置' + (view.notionTargetPageId !== '' ? ' + 目标页' : '(未填目标页)') : '未配置') +\n ' · flomo ' + (view.flomoConfigured ? '已配置' : '未配置') +\n ' · 缓存书架 ' + view.cachedShelfBooks + ' 本 / 有笔记 ' + view.cachedNoteBooks + ' 本' +\n (view.lastSyncAt !== '' ? ' · 最近同步 ' + view.lastSyncAt : '')\n )\n}\n\n/** The WeRead settings panel component. */\nexport function WereadSettingsPanel(): JSX.Element {\n const [view, setView] = useState<WereadStatusView | null>(null)\n const [apiKey, setApiKey] = useState('')\n const [defaultFlomoTag, setDefaultFlomoTag] = useState('')\n const [limitChoice, setLimitChoice] = useState('20')\n const [customLimit, setCustomLimit] = useState('')\n const [exportDest, setExportDest] = useState('flomo')\n const [localExportDir, setLocalExportDir] = useState('')\n const [notionToken, setNotionToken] = useState('')\n const [notionTargetPageId, setNotionTargetPageId] = useState('')\n const [usePrompt, setUsePrompt] = useState(false)\n const [llmBaseUrl, setLlmBaseUrl] = useState('')\n const [llmApiKey, setLlmApiKey] = useState('')\n const [llmModel, setLlmModel] = useState('')\n const [exportPrompt, setExportPrompt] = useState('')\n const [busy, setBusy] = useState(false)\n const [message, setMessage] = useState('')\n const [books, setBooks] = useState<WereadBook[]>([])\n const [bookId, setBookId] = useState('')\n // Per-export (this run) controls.\n const [destNow, setDestNow] = useState('')\n const [localDirNow, setLocalDirNow] = useState('')\n const [flomoTagNow, setFlomoTagNow] = useState('')\n const [usePromptNow, setUsePromptNow] = useState<boolean | null>(null)\n\n /** Map the persisted exportLimit to the choice selector. */\n const limitFromView = (value: number): string => {\n if (value === 0) return '0'\n if ([20, 50, 100].includes(value)) return String(value)\n return 'custom'\n }\n\n const refresh = useCallback(async () => {\n try {\n const next = await api.getStatus()\n setView(next)\n setDefaultFlomoTag((prev) => prev === '' ? next.defaultFlomoTag : prev)\n setFlomoTagNow((prev) => prev === '' ? next.defaultFlomoTag : prev)\n setLimitChoice(limitFromView(next.exportLimit))\n if (![0, 20, 50, 100].includes(next.exportLimit)) setCustomLimit(String(next.exportLimit))\n setExportDest(next.exportDest)\n setLocalExportDir(next.localExportDir)\n setNotionTargetPageId(next.notionTargetPageId)\n setUsePrompt(next.usePrompt)\n setLlmBaseUrl(next.llmBaseUrl)\n setLlmModel(next.llmModel)\n if (destNow === '') setDestNow(next.exportDest)\n } catch (error) {\n setMessage('状态读取失败: ' + String(error instanceof Error ? error.message : error))\n }\n }, [destNow])\n\n useEffect(() => {\n void refresh()\n }, [refresh])\n\n const reloadBooks = useCallback(async () => {\n try {\n const list = await api.books()\n setBooks(list)\n if (list.length > 0) setBookId((prev) => prev !== '' && list.some((b) => b.bookId === prev) ? prev : (list[0]?.bookId ?? ''))\n } catch {\n setBooks([])\n }\n }, [])\n\n useEffect(() => {\n void reloadBooks()\n }, [reloadBooks])\n\n /** Resolve the chosen export limit to a number (0 = all). */\n const resolveExportLimit = (): number => {\n if (limitChoice === 'custom') {\n const parsed = Number(customLimit)\n return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : 0\n }\n return Number(limitChoice) || 0\n }\n\n const saveConfig = async (): Promise<void> => {\n setBusy(true)\n setMessage('')\n try {\n const patch: Record<string, unknown> = {}\n if (apiKey.trim() !== '') patch.apiKey = apiKey.trim()\n if (defaultFlomoTag.trim() !== '') patch.defaultFlomoTag = defaultFlomoTag.trim()\n patch.exportLimit = resolveExportLimit()\n patch.exportDest = exportDest\n if (localExportDir.trim() !== '') patch.localExportDir = localExportDir.trim()\n if (notionToken.trim() !== '') patch.notionToken = notionToken.trim()\n if (notionTargetPageId.trim() !== '') patch.notionTargetPageId = notionTargetPageId.trim()\n patch.usePrompt = usePrompt\n if (llmBaseUrl.trim() !== '') patch.llmBaseUrl = llmBaseUrl.trim()\n if (llmApiKey.trim() !== '') patch.llmApiKey = llmApiKey.trim()\n if (llmModel.trim() !== '') patch.llmModel = llmModel.trim()\n if (exportPrompt.trim() !== '') patch.exportPrompt = exportPrompt\n const next: WereadConfigView = await api.setConfig(patch)\n setView({ ...next, flomoConfigured: view?.flomoConfigured ?? false, cachedShelfBooks: view?.cachedShelfBooks ?? 0, cachedNoteBooks: view?.cachedNoteBooks ?? 0, cacheUpdatedAt: view?.cacheUpdatedAt ?? '' })\n setMessage(next.configured ? '配置已保存。' : '配置未保存完整:缺少 API Key。')\n setApiKey('')\n setNotionToken('')\n setLlmApiKey('')\n } catch (error) {\n setMessage('保存失败: ' + String(error instanceof Error ? error.message : error))\n } finally {\n setBusy(false)\n }\n }\n\n const clearConfig = async (): Promise<void> => {\n setBusy(true)\n setMessage('')\n try {\n const next: WereadConfigView = await api.setConfig({ reset: true })\n setView({ ...next, flomoConfigured: view?.flomoConfigured ?? false, cachedShelfBooks: view?.cachedShelfBooks ?? 0, cachedNoteBooks: view?.cachedNoteBooks ?? 0, cacheUpdatedAt: view?.cacheUpdatedAt ?? '' })\n setMessage('已清除配置。')\n } catch (error) {\n setMessage('清除失败: ' + String(error instanceof Error ? error.message : error))\n } finally {\n setBusy(false)\n }\n }\n\n const runTest = async (): Promise<void> => {\n setBusy(true)\n setMessage('测试中…')\n try {\n const result = await api.test()\n setMessage(result.ok ? result.message : '测试失败: ' + result.message)\n } catch (error) {\n setMessage('测试失败: ' + String(error instanceof Error ? error.message : error))\n } finally {\n setBusy(false)\n }\n }\n\n const runSync = async (): Promise<void> => {\n setBusy(true)\n setMessage('同步中…')\n try {\n const result: WereadSyncResult = await api.sync()\n setMessage(result.ok ? result.message : '同步失败: ' + result.message)\n await refresh()\n await reloadBooks()\n } catch (error) {\n setMessage('同步失败: ' + String(error instanceof Error ? error.message : error))\n } finally {\n setBusy(false)\n }\n }\n\n const runExportNow = async (): Promise<void> => {\n if (bookId === '') {\n setMessage('请先同步以加载书籍列表。')\n return\n }\n const dest = destNow || view?.exportDest || 'flomo'\n if (dest === 'local' && localDirNow.trim() === '') {\n setMessage('本地导出需要填写导出路径(每次必填)。')\n return\n }\n setBusy(true)\n setMessage('导出中…')\n try {\n const body: Record<string, unknown> = { bookId, dest }\n if (dest === 'local') body.localDir = localDirNow.trim()\n if (dest === 'flomo' && flomoTagNow.trim() !== '') body.tag = flomoTagNow.trim()\n if (usePromptNow !== null) body.usePrompt = usePromptNow\n const result: WereadFlomoResult = await api.exportData(body)\n setMessage(result.ok ? result.message : '导出失败: ' + result.message)\n } catch (error) {\n setMessage('导出失败: ' + String(error instanceof Error ? error.message : error))\n } finally {\n setBusy(false)\n }\n }\n\n return (\n <div style={s.card}>\n <h3 style={s.title}>微信读书</h3>\n <div style={view?.configured === false ? s.statusWarn : s.status}>{statusText(view)}</div>\n\n <div style={s.section}>连接与导出偏好</div>\n <div style={s.row}>\n <input\n style={s.input}\n placeholder=\"Skills API Key(wrk- 开头)\"\n value={apiKey}\n onChange={(e) => setApiKey(e.target.value)}\n />\n </div>\n <div style={s.row}>\n <span style={s.label}>默认导出标签</span>\n <input\n style={{ ...s.input, width: '130px' }}\n placeholder=\"如 读书笔记\"\n value={defaultFlomoTag}\n onChange={(e) => setDefaultFlomoTag(e.target.value)}\n />\n <span style={s.label}>导出条数</span>\n <select style={{ ...s.select, flex: 0, minWidth: '104px' }} value={limitChoice} onChange={(e) => setLimitChoice(e.target.value)}>\n <option value=\"0\">全部导出</option>\n <option value=\"20\">20 条(默认)</option>\n <option value=\"50\">50 条</option>\n <option value=\"100\">100 条</option>\n <option value=\"custom\">自定义…</option>\n </select>\n {limitChoice === 'custom' && (\n <input style={{ ...s.input, width: '64px' }} placeholder=\"条数\" value={customLimit} onChange={(e) => setCustomLimit(e.target.value)} />\n )}\n <div style={s.flex} />\n <button style={s.button} onClick={() => void saveConfig()} disabled={busy}>保存配置</button>\n <button style={s.button} onClick={() => void runTest()} disabled={busy || !view?.configured}>测试连接</button>\n <button style={s.button} onClick={() => void clearConfig()} disabled={busy}>清除</button>\n </div>\n <div style={s.row}>\n <span style={s.label}>默认目标</span>\n <select style={{ ...s.select, flex: 0, minWidth: '110px' }} value={exportDest} onChange={(e) => setExportDest(e.target.value)}>\n <option value=\"flomo\">flomo</option>\n <option value=\"local\">本地文件</option>\n <option value=\"notion\">Notion</option>\n </select>\n {exportDest === 'local' && (\n <input style={s.input} placeholder=\"本地导出目录(绝对路径,可留空导出时填)\" value={localExportDir} onChange={(e) => setLocalExportDir(e.target.value)} />\n )}\n </div>\n\n <div style={s.section}>Notion(本插件独立配置)</div>\n <div style={s.row}>\n <input style={s.input} type=\"password\" placeholder=\"Notion Integration Token(notion.so/my-integrations 创建)\" value={notionToken} onChange={(e) => setNotionToken(e.target.value)} />\n </div>\n <div style={s.row}>\n <input style={s.input} placeholder=\"目标父页面 URL 或 32 位 ID(页面需分享给该 Integration)\" value={notionTargetPageId} onChange={(e) => setNotionTargetPageId(e.target.value)} />\n </div>\n\n <div style={s.section}>AI · prompt 处理(导出前用 LLM 按模板整理)</div>\n <div style={s.row}>\n <label style={{ ...s.label, display: 'flex', alignItems: 'center', gap: '4px', cursor: 'pointer' }}>\n <input type=\"checkbox\" checked={usePrompt} onChange={(e) => setUsePrompt(e.target.checked)} />\n 启用\n </label>\n <input style={s.input} placeholder=\"Base URL(默认 https://api.deepseek.com/v1)\" value={llmBaseUrl} onChange={(e) => setLlmBaseUrl(e.target.value)} />\n <input style={{ ...s.input, width: '130px' }} placeholder=\"模型\" value={llmModel} onChange={(e) => setLlmModel(e.target.value)} />\n <input style={s.input} type=\"password\" placeholder=\"LLM API Key(自定义)\" value={llmApiKey} onChange={(e) => setLlmApiKey(e.target.value)} />\n </div>\n <div style={s.col}>\n <textarea\n style={s.textarea}\n placeholder=\"导出 prompt 模板,可用 {title} {author} {highlights} {thoughts} 占位符\"\n value={exportPrompt}\n onChange={(e) => setExportPrompt(e.target.value)}\n />\n </div>\n\n <div style={s.row}>\n <button style={s.button} onClick={() => void runSync()} disabled={busy || !view?.configured}>同步书架/笔记</button>\n </div>\n\n <div style={s.section}>快捷导出</div>\n <div style={s.row}>\n <span style={s.label}>选择书籍</span>\n <select style={s.select} value={bookId} onChange={(e) => setBookId(e.target.value)} disabled={books.length === 0}>\n {books.length === 0 ? <option value=\"\">(缓存无书籍,先同步)</option> : books.map((b) => (\n <option key={b.bookId} value={b.bookId}>《{b.title}》{b.author !== '' ? ' · ' + b.author : ''}</option>\n ))}\n </select>\n </div>\n <div style={s.row}>\n <span style={s.label}>本次目标</span>\n <select style={{ ...s.select, flex: 0, minWidth: '110px' }} value={destNow} onChange={(e) => setDestNow(e.target.value)}>\n <option value=\"flomo\">flomo</option>\n <option value=\"local\">本地文件</option>\n <option value=\"notion\">Notion</option>\n </select>\n {destNow === 'local' && (\n <input style={s.input} placeholder=\"导出路径(必填)\" value={localDirNow} onChange={(e) => setLocalDirNow(e.target.value)} />\n )}\n {destNow === 'flomo' && (\n <input style={{ ...s.input, width: '150px' }} placeholder=\"本次标签(#)\" value={flomoTagNow} onChange={(e) => setFlomoTagNow(e.target.value)} />\n )}\n </div>\n <div style={s.row}>\n <label style={{ ...s.label, display: 'flex', alignItems: 'center', gap: '4px', cursor: 'pointer' }}>\n <input\n type=\"checkbox\"\n checked={usePromptNow === null ? (view?.usePrompt ?? false) : usePromptNow}\n onChange={(e) => setUsePromptNow(e.target.checked)}\n />\n 本次用 prompt 处理(默认跟随配置)\n </label>\n <div style={s.flex} />\n <button style={s.button} onClick={() => void runExportNow()} disabled={busy || !view?.configured}>\n 导出到 {destNow === 'local' ? '本地' : (destNow === 'notion' ? 'Notion' : 'flomo')}\n </button>\n </div>\n\n {message !== '' && <div style={s.msg}>{message}</div>}\n <div style={s.hint}>\n 【API Key】打开 https://weread.qq.com/r/weread-skills → 微信读书账号登录 → 「创建 Key」→ 复制(wrk- 开头)。Key 绑定你的账号身份,请勿泄露。存于 ~/.dsh/weread-export.json(权限 0600)。\n </div>\n <div style={s.hint}>\n 【导出目标】flomo = 发到浮墨(复用「Flomo」面板凭据,超长自动拆多条 MEMO);本地 = 导出 Markdown 文件到指定目录(每次导出需填路径);Notion = 用本插件自己的 Integration Token 在目标父页面下创建子页面写入(目标页需分享给该 Integration)。\n </div>\n <div style={s.hint}>\n 【AI prompt】启用后导出前会调用 LLM(OpenAI 兼容,默认 DeepSeek)按模板整理划线内容再导出。模板支持 {'{title}'} {'{author}'} {'{highlights}'} {'{thoughts}'} 占位符,可随意编辑;本次导出也可临时开关。\n </div>\n <div style={s.hint}>\n 【导出条数】「全部导出」= 全部划线都导出(flomo 超长自动拆多条);「20/50/100/自定义」= 最多导出的条数。\n 同步后可用 weread_shelf / weread_notes / weread_readdata / weread_search / weread_book / weread_export 等工具。\n </div>\n </div>\n )\n}\n","/**\n * weread-export — browser half. Registers the WeRead settings panel into the\n * web settings page (settings.section entry). The panel configures the\n * Skills API key, default flomo tag, drives manual syncs, and quick-exports\n * highlights to flomo. Failure policy: registration problems are logged,\n * never thrown — the web shell fails the whole boot when a plugin apply\n * throws, and an external plugin must not take the GUI down.\n */\n// Type-only: pulls the settings-surface SlotMap merge (the 'settings.section'\n// entry) and the client runtime Context merge.\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\nimport type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'\nimport { WereadSettingsPanel } from './WereadSettingsPanel.tsx'\n\n/** Required services. */\nexport const inject = ['slots']\n\n/**\n * Register the WeRead settings page.\n * @param ctx - client root context.\n */\nexport function apply(ctx: ClientContext): void {\n try {\n ctx.slots.inject('settings.section', () => ctx.slots.register({\n name: 'settings.section',\n id: 'weread',\n order: 316,\n label: () => '微信读书',\n }, WereadSettingsPanel))\n } catch (error) {\n console.warn('[weread-export] settings panel registration failed:', error)\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":[],"sources":["../src/client/api.ts","../src/client/WereadSettingsPanel.tsx","../src/client/index.ts"],"sourcesContent":["/**\n * Browser-side API client for the /api/weread-export route family. The only\n * data access path the settings panel uses — plain fetch, same origin.\n */\n\n/** Public config view (mirrors the host contract). */\nexport interface WereadConfigView {\n configured: boolean\n apiKeyMasked: string\n defaultFlomoTag: string\n /** 0 = export ALL highlights; N > 0 = cap at N. */\n exportLimit: number\n exportDest: string\n localExportDir: string\n notionConfigured: boolean\n notionTargetPageId: string\n usePrompt: boolean\n llmConfigured: boolean\n llmBaseUrl: string\n llmModel: string\n lastSyncAt: string\n configPath: string\n}\n\n/** Status view with cache + flomo stats. */\nexport interface WereadStatusView extends WereadConfigView {\n flomoConfigured: boolean\n flomoSource: string\n flomoMasked: string\n flomoConfigPath: string\n cachedShelfBooks: number\n cachedNoteBooks: number\n cacheUpdatedAt: string\n}\n\n/** Sync result. */\nexport interface WereadSyncResult {\n ok: boolean\n message: string\n shelfBooks: number\n notebooks: number\n}\n\n/** One cached book for the export picker. */\nexport interface WereadBook {\n bookId: string\n title: string\n author: string\n}\n\n/** Flomo export result. */\nexport interface WereadFlomoResult {\n ok: boolean\n message: string\n sent: number\n memoCount?: number\n bookId?: string\n}\n\n/** Error carrying the route's JSON error message. */\nexport class WereadApiError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'WereadApiError'\n }\n}\n\n/** Parse a JSON response or throw a WereadApiError. */\nasync function readJson<T>(response: Response): Promise<T> {\n let body: unknown\n try {\n body = await response.json()\n } catch {\n throw new WereadApiError(`HTTP ${response.status}: invalid JSON response`)\n }\n if (!response.ok) {\n const message = typeof body === 'object' && body !== null && typeof (body as { error?: unknown }).error === 'string'\n ? (body as { error: string }).error\n : `HTTP ${response.status}`\n throw new WereadApiError(message)\n }\n return body as T\n}\n\n/** Plain fetch helper with an error wrapper. */\nasync function request<T>(path: string, init?: RequestInit): Promise<T> {\n let response: Response\n try {\n response = await fetch(path, init)\n } catch (error) {\n throw new WereadApiError('网络请求失败: ' + String(error instanceof Error ? error.message : error))\n }\n return readJson<T>(response)\n}\n\n/** The weread panel API. */\nexport class WereadApi {\n async getConfig(): Promise<WereadConfigView> {\n return request<WereadConfigView>('/api/weread-export/config')\n }\n\n async setConfig(patch: Record<string, unknown>): Promise<WereadConfigView> {\n return request<WereadConfigView>('/api/weread-export/config', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(patch),\n })\n }\n\n async getStatus(): Promise<WereadStatusView> {\n return request<WereadStatusView>('/api/weread-export/status')\n }\n\n async test(): Promise<{ ok: boolean; message: string }> {\n return request<{ ok: boolean; message: string }>('/api/weread-export/test', { method: 'POST' })\n }\n\n async sync(): Promise<WereadSyncResult> {\n return request<WereadSyncResult>('/api/weread-export/sync', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({}),\n })\n }\n\n async books(): Promise<WereadBook[]> {\n const data = await request<{ books: WereadBook[] }>('/api/weread-export/books')\n return data.books ?? []\n }\n\n async exportFlomo(bookId: string, tag: string, limit = 20): Promise<WereadFlomoResult> {\n return request<WereadFlomoResult>('/api/weread-export/flomo', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ bookId, tag, limit }),\n })\n }\n\n /** Multi-target export: flomo / local / notion with optional prompt. */\n async exportData(body: Record<string, unknown>): Promise<WereadFlomoResult> {\n return request<WereadFlomoResult>('/api/weread-export/export', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n })\n }\n}\n","/**\n * WeRead settings panel — rendered inside the web settings page\n * (settings.section entry). Connection setup (Skills API key), export\n * preferences (destination flomo/local/Notion, per-export limit, local dir,\n * Notion token + target page, LLM prompt processing with a user-editable\n * template), manual sync, and a quick multi-target export. Plain React,\n * inline styles only.\n */\nimport { useCallback, useEffect, useState } from 'react'\nimport {\n WereadApi, type WereadConfigView, type WereadStatusView, type WereadSyncResult, type WereadBook, type WereadFlomoResult,\n} from './api.ts'\n\n/** Module-level API client (stateless; the component closes over it). */\nconst api = new WereadApi()\n\n/** One shared style sheet (kept tiny and theme-agnostic). */\nconst s = {\n card: {\n display: 'flex',\n flexDirection: 'column',\n gap: '10px',\n maxWidth: '680px',\n padding: '14px 16px',\n borderRadius: '10px',\n border: '1px solid rgba(128,128,128,0.3)',\n fontSize: '13px',\n color: 'inherit',\n } as const,\n title: { fontWeight: 600, fontSize: '13px', margin: 0 } as const,\n /** One visually separated group card. */\n group: {\n display: 'flex',\n flexDirection: 'column',\n gap: '8px',\n marginTop: '6px',\n padding: '12px 14px',\n borderRadius: '10px',\n border: '1px solid rgba(128,128,128,0.22)',\n background: 'rgba(128,128,128,0.05)',\n minWidth: 0,\n } as const,\n groupTitle: {\n fontWeight: 600,\n fontSize: '12px',\n opacity: 0.95,\n margin: 0,\n paddingBottom: '6px',\n borderBottom: '1px solid rgba(128,128,128,0.18)',\n } as const,\n status: { fontSize: '12px', opacity: 0.85, whiteSpace: 'pre-wrap' } as const,\n statusWarn: { fontSize: '12px', opacity: 0.9, color: '#c9763a' } as const,\n row: { display: 'flex', gap: '6px', alignItems: 'center', minWidth: 0, flexWrap: 'wrap' } as const,\n col: { display: 'flex', flexDirection: 'column', gap: '4px', minWidth: 0 } as const,\n label: { fontSize: '12px', opacity: 0.8, whiteSpace: 'nowrap' } as const,\n input: {\n flex: 1,\n minWidth: 0,\n boxSizing: 'border-box',\n padding: '5px 8px',\n borderRadius: '6px',\n border: '1px solid rgba(128,128,128,0.35)',\n background: 'rgba(128,128,128,0.08)',\n color: 'inherit',\n fontSize: '12px',\n } as const,\n textarea: {\n width: '100%',\n minHeight: '96px',\n boxSizing: 'border-box',\n padding: '6px 8px',\n borderRadius: '6px',\n border: '1px solid rgba(128,128,128,0.35)',\n background: 'rgba(128,128,128,0.08)',\n color: 'inherit',\n fontSize: '12px',\n fontFamily: 'inherit',\n lineHeight: 1.5,\n resize: 'vertical',\n } as const,\n select: {\n flex: 1,\n minWidth: 0,\n maxWidth: '100%',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n padding: '4px 6px',\n borderRadius: '6px',\n border: '1px solid rgba(128,128,128,0.35)',\n background: 'rgba(128,128,128,0.08)',\n color: 'inherit',\n fontSize: '12px',\n } as const,\n flex: { flex: 1 } as const,\n button: {\n padding: '4px 10px',\n borderRadius: '6px',\n cursor: 'pointer',\n border: '1px solid rgba(128,128,128,0.4)',\n background: 'rgba(128,128,128,0.14)',\n color: 'inherit',\n fontSize: '12px',\n whiteSpace: 'nowrap',\n } as const,\n msg: { fontSize: '12px', whiteSpace: 'pre-wrap', wordBreak: 'break-all', opacity: 0.9 } as const,\n hint: { fontSize: '11px', opacity: 0.75, lineHeight: 1.6 } as const,\n}\n\n/** Status line for the current config view. */\nfunction statusText(view: WereadStatusView | null): string {\n if (view === null) return '加载中…'\n if (!view.configured) {\n return '未配置 — 打开 https://weread.qq.com/r/weread-skills,用微信读书账号登录后点击「创建 Key」并复制(wrk- 开头),粘贴到上方输入框。'\n }\n const destLabel = view.exportDest === 'local' ? '本地文件' : (view.exportDest === 'notion' ? 'Notion' : 'flomo')\n return (\n '已配置 · Key ' + view.apiKeyMasked +\n '\\n默认导出目标 ' + destLabel + ' · 导出条数 ' + (view.exportLimit === 0 ? '全部' : view.exportLimit + ' 条') +\n ' · 默认标签 #' + view.defaultFlomoTag +\n '\\nprompt 处理 ' + (view.usePrompt ? '开(' + (view.llmConfigured ? view.llmModel + ' @ ' + view.llmBaseUrl : 'LLM 未配置') + ')' : '关') +\n ' · Notion ' + (view.notionConfigured ? '已配置' + (view.notionTargetPageId !== '' ? ' + 目标页' : '(未填目标页)') : '未配置') +\n ' · flomo ' + (view.flomoConfigured ? '已配置(' + view.flomoSource + ' ' + view.flomoMasked + ')' : '未配置') +\n ' · 缓存书架 ' + view.cachedShelfBooks + ' 本 / 有笔记 ' + view.cachedNoteBooks + ' 本' +\n (view.lastSyncAt !== '' ? ' · 最近同步 ' + view.lastSyncAt : '')\n )\n}\n\n/** The WeRead settings panel component. */\nexport function WereadSettingsPanel(): JSX.Element {\n const [view, setView] = useState<WereadStatusView | null>(null)\n const [apiKey, setApiKey] = useState('')\n const [defaultFlomoTag, setDefaultFlomoTag] = useState('')\n const [limitChoice, setLimitChoice] = useState('20')\n const [customLimit, setCustomLimit] = useState('')\n const [exportDest, setExportDest] = useState('flomo')\n const [localExportDir, setLocalExportDir] = useState('')\n const [notionToken, setNotionToken] = useState('')\n const [flomoWebhookUrl, setFlomoWebhookUrl] = useState('')\n const [flomoApiKey, setFlomoApiKey] = useState('')\n const [notionTargetPageId, setNotionTargetPageId] = useState('')\n const [usePrompt, setUsePrompt] = useState(false)\n const [llmBaseUrl, setLlmBaseUrl] = useState('')\n const [llmApiKey, setLlmApiKey] = useState('')\n const [llmModel, setLlmModel] = useState('')\n const [exportPrompt, setExportPrompt] = useState('')\n const [busy, setBusy] = useState(false)\n const [message, setMessage] = useState('')\n const [books, setBooks] = useState<WereadBook[]>([])\n const [bookId, setBookId] = useState('')\n // Per-export (this run) controls.\n const [destNow, setDestNow] = useState('')\n const [localDirNow, setLocalDirNow] = useState('')\n const [flomoTagNow, setFlomoTagNow] = useState('')\n const [usePromptNow, setUsePromptNow] = useState<boolean | null>(null)\n\n /** Map the persisted exportLimit to the choice selector. */\n const limitFromView = (value: number): string => {\n if (value === 0) return '0'\n if ([20, 50, 100].includes(value)) return String(value)\n return 'custom'\n }\n\n const refresh = useCallback(async () => {\n try {\n const next = await api.getStatus()\n setView(next)\n setDefaultFlomoTag((prev) => prev === '' ? next.defaultFlomoTag : prev)\n setFlomoTagNow((prev) => prev === '' ? next.defaultFlomoTag : prev)\n setLimitChoice(limitFromView(next.exportLimit))\n if (![0, 20, 50, 100].includes(next.exportLimit)) setCustomLimit(String(next.exportLimit))\n setExportDest(next.exportDest)\n setLocalExportDir(next.localExportDir)\n setNotionTargetPageId(next.notionTargetPageId)\n setUsePrompt(next.usePrompt)\n setLlmBaseUrl(next.llmBaseUrl)\n setLlmModel(next.llmModel)\n if (destNow === '') setDestNow(next.exportDest)\n } catch (error) {\n setMessage('状态读取失败: ' + String(error instanceof Error ? error.message : error))\n }\n }, [destNow])\n\n useEffect(() => {\n void refresh()\n }, [refresh])\n\n const reloadBooks = useCallback(async () => {\n try {\n const list = await api.books()\n setBooks(list)\n if (list.length > 0) setBookId((prev) => prev !== '' && list.some((b) => b.bookId === prev) ? prev : (list[0]?.bookId ?? ''))\n } catch {\n setBooks([])\n }\n }, [])\n\n useEffect(() => {\n void reloadBooks()\n }, [reloadBooks])\n\n /** Resolve the chosen export limit to a number (0 = all). */\n const resolveExportLimit = (): number => {\n if (limitChoice === 'custom') {\n const parsed = Number(customLimit)\n return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : 0\n }\n return Number(limitChoice) || 0\n }\n\n const saveConfig = async (): Promise<void> => {\n setBusy(true)\n setMessage('')\n try {\n const patch: Record<string, unknown> = {}\n if (apiKey.trim() !== '') patch.apiKey = apiKey.trim()\n if (defaultFlomoTag.trim() !== '') patch.defaultFlomoTag = defaultFlomoTag.trim()\n patch.exportLimit = resolveExportLimit()\n patch.exportDest = exportDest\n if (localExportDir.trim() !== '') patch.localExportDir = localExportDir.trim()\n if (notionToken.trim() !== '') patch.notionToken = notionToken.trim()\n if (notionTargetPageId.trim() !== '') patch.notionTargetPageId = notionTargetPageId.trim()\n if (flomoWebhookUrl.trim() !== '') patch.flomoWebhookUrl = flomoWebhookUrl.trim()\n if (flomoApiKey.trim() !== '') patch.flomoApiKey = flomoApiKey.trim()\n patch.usePrompt = usePrompt\n if (llmBaseUrl.trim() !== '') patch.llmBaseUrl = llmBaseUrl.trim()\n if (llmApiKey.trim() !== '') patch.llmApiKey = llmApiKey.trim()\n if (llmModel.trim() !== '') patch.llmModel = llmModel.trim()\n if (exportPrompt.trim() !== '') patch.exportPrompt = exportPrompt\n const next: WereadConfigView = await api.setConfig(patch)\n const status = await api.getStatus()\n setView(status)\n setMessage(next.configured ? '配置已保存。' : '配置未保存完整:缺少 API Key。')\n setApiKey('')\n setNotionToken('')\n setLlmApiKey('')\n setFlomoWebhookUrl('')\n setFlomoApiKey('')\n } catch (error) {\n setMessage('保存失败: ' + String(error instanceof Error ? error.message : error))\n } finally {\n setBusy(false)\n }\n }\n\n const clearFlomoConfig = async (): Promise<void> => {\n setBusy(true)\n setMessage('')\n try {\n await api.setConfig({ flomoReset: true })\n setView(await api.getStatus())\n setMessage('已清除 flomo 配置。')\n } catch (error) {\n setMessage('清除失败: ' + String(error instanceof Error ? error.message : error))\n } finally {\n setBusy(false)\n }\n }\n\n const clearConfig = async (): Promise<void> => {\n setBusy(true)\n setMessage('')\n try {\n const next: WereadConfigView = await api.setConfig({ reset: true })\n setView({ ...next, flomoConfigured: view?.flomoConfigured ?? false, flomoSource: view?.flomoSource ?? '', flomoMasked: view?.flomoMasked ?? '', flomoConfigPath: view?.flomoConfigPath ?? '', cachedShelfBooks: view?.cachedShelfBooks ?? 0, cachedNoteBooks: view?.cachedNoteBooks ?? 0, cacheUpdatedAt: view?.cacheUpdatedAt ?? '' })\n setMessage('已清除配置。')\n } catch (error) {\n setMessage('清除失败: ' + String(error instanceof Error ? error.message : error))\n } finally {\n setBusy(false)\n }\n }\n\n const runTest = async (): Promise<void> => {\n setBusy(true)\n setMessage('测试中…')\n try {\n const result = await api.test()\n setMessage(result.ok ? result.message : '测试失败: ' + result.message)\n } catch (error) {\n setMessage('测试失败: ' + String(error instanceof Error ? error.message : error))\n } finally {\n setBusy(false)\n }\n }\n\n const runSync = async (): Promise<void> => {\n setBusy(true)\n setMessage('同步中…')\n try {\n const result: WereadSyncResult = await api.sync()\n setMessage(result.ok ? result.message : '同步失败: ' + result.message)\n await refresh()\n await reloadBooks()\n } catch (error) {\n setMessage('同步失败: ' + String(error instanceof Error ? error.message : error))\n } finally {\n setBusy(false)\n }\n }\n\n const runExportNow = async (): Promise<void> => {\n if (bookId === '') {\n setMessage('请先同步以加载书籍列表。')\n return\n }\n const dest = destNow || view?.exportDest || 'flomo'\n if (dest === 'local' && localDirNow.trim() === '') {\n setMessage('本地导出需要填写导出路径(每次必填)。')\n return\n }\n setBusy(true)\n setMessage('导出中…')\n try {\n const body: Record<string, unknown> = { bookId, dest }\n if (dest === 'local') body.localDir = localDirNow.trim()\n if (dest === 'flomo' && flomoTagNow.trim() !== '') body.tag = flomoTagNow.trim()\n if (usePromptNow !== null) body.usePrompt = usePromptNow\n const result: WereadFlomoResult = await api.exportData(body)\n setMessage(result.ok ? result.message : '导出失败: ' + result.message)\n } catch (error) {\n setMessage('导出失败: ' + String(error instanceof Error ? error.message : error))\n } finally {\n setBusy(false)\n }\n }\n\n return (\n <div style={s.card}>\n <h3 style={s.title}>微信读书</h3>\n <div style={view?.configured === false ? s.statusWarn : s.status}>{statusText(view)}</div>\n\n <div style={s.group}>\n <div style={s.groupTitle}>① 连接与导出偏好</div>\n <div style={s.row}>\n <input\n style={s.input}\n placeholder=\"Skills API Key(wrk- 开头)\"\n value={apiKey}\n onChange={(e) => setApiKey(e.target.value)}\n />\n </div>\n <div style={s.row}>\n <span style={s.label}>默认导出标签</span>\n <input\n style={{ ...s.input, width: '130px' }}\n placeholder=\"如 读书笔记\"\n value={defaultFlomoTag}\n onChange={(e) => setDefaultFlomoTag(e.target.value)}\n />\n <span style={s.label}>导出条数</span>\n <select style={{ ...s.select, flex: 0, minWidth: '104px' }} value={limitChoice} onChange={(e) => setLimitChoice(e.target.value)}>\n <option value=\"0\">全部导出</option>\n <option value=\"20\">20 条(默认)</option>\n <option value=\"50\">50 条</option>\n <option value=\"100\">100 条</option>\n <option value=\"custom\">自定义…</option>\n </select>\n {limitChoice === 'custom' && (\n <input style={{ ...s.input, width: '64px' }} placeholder=\"条数\" value={customLimit} onChange={(e) => setCustomLimit(e.target.value)} />\n )}\n </div>\n <div style={s.row}>\n <span style={s.label}>默认目标</span>\n <select style={{ ...s.select, flex: 0, minWidth: '110px' }} value={exportDest} onChange={(e) => setExportDest(e.target.value)}>\n <option value=\"flomo\">flomo</option>\n <option value=\"local\">本地文件</option>\n <option value=\"notion\">Notion</option>\n </select>\n {exportDest === 'local' && (\n <input style={s.input} placeholder=\"本地导出目录(绝对路径,可留空导出时填)\" value={localExportDir} onChange={(e) => setLocalExportDir(e.target.value)} />\n )}\n </div>\n <div style={s.row}>\n <div style={s.flex} />\n <button style={s.button} onClick={() => void saveConfig()} disabled={busy}>保存配置</button>\n <button style={s.button} onClick={() => void runTest()} disabled={busy || !view?.configured}>测试连接</button>\n <button style={s.button} onClick={() => void clearConfig()} disabled={busy}>清除</button>\n </div>\n </div>\n\n <div style={s.group}>\n <div style={s.groupTitle}>② flomo 导出</div>\n <div style={view?.flomoConfigured === false ? s.statusWarn : s.status}>\n {view === null\n ? '加载中…'\n : (view.flomoConfigured\n ? '已配置:' + view.flomoSource + ' ' + view.flomoMasked + '(与「Flomo」面板共享,文件 ' + view.flomoConfigPath + ')'\n : '未配置 — 在 flomo 设置页(flomoapp.com/mine?source=incoming_webhook)获取 API URL,粘贴到下方。')}\n </div>\n <div style={s.row}>\n <input style={s.input} type=\"password\" placeholder=\"flomo API URL(https://flomoapp.com/iwh/xxxx)\" value={flomoWebhookUrl} onChange={(e) => setFlomoWebhookUrl(e.target.value)} />\n </div>\n <div style={s.row}>\n <input style={s.input} type=\"password\" placeholder=\"或 flomo API Key(新版,与 URL 二选一)\" value={flomoApiKey} onChange={(e) => setFlomoApiKey(e.target.value)} />\n <button style={s.button} onClick={() => void saveConfig()} disabled={busy}>保存 flomo</button>\n <button style={s.button} onClick={() => void clearFlomoConfig()} disabled={busy}>清除</button>\n </div>\n </div>\n\n <div style={s.group}>\n <div style={s.groupTitle}>③ Notion 导出</div>\n <div style={s.row}>\n <input style={s.input} type=\"password\" placeholder=\"Notion Integration Token(notion.so/my-integrations 创建)\" value={notionToken} onChange={(e) => setNotionToken(e.target.value)} />\n </div>\n <div style={s.row}>\n <input style={s.input} placeholder=\"目标父页面 URL 或 32 位 ID(页面需分享给该 Integration)\" value={notionTargetPageId} onChange={(e) => setNotionTargetPageId(e.target.value)} />\n </div>\n </div>\n\n <div style={s.group}>\n <div style={s.groupTitle}>④ AI · prompt 处理(导出前用 LLM 按模板整理)</div>\n <div style={s.row}>\n <label style={{ ...s.label, display: 'flex', alignItems: 'center', gap: '4px', cursor: 'pointer' }}>\n <input type=\"checkbox\" checked={usePrompt} onChange={(e) => setUsePrompt(e.target.checked)} />\n 启用\n </label>\n <input style={s.input} placeholder=\"Base URL(默认 https://api.deepseek.com/v1)\" value={llmBaseUrl} onChange={(e) => setLlmBaseUrl(e.target.value)} />\n <input style={{ ...s.input, width: '130px' }} placeholder=\"模型\" value={llmModel} onChange={(e) => setLlmModel(e.target.value)} />\n <input style={s.input} type=\"password\" placeholder=\"LLM API Key(自定义)\" value={llmApiKey} onChange={(e) => setLlmApiKey(e.target.value)} />\n </div>\n <div style={s.col}>\n <textarea\n style={s.textarea}\n placeholder=\"导出 prompt 模板,可用 {title} {author} {highlights} {thoughts} 占位符\"\n value={exportPrompt}\n onChange={(e) => setExportPrompt(e.target.value)}\n />\n </div>\n </div>\n\n <div style={s.group}>\n <div style={s.groupTitle}>⑤ 快捷导出</div>\n <div style={s.row}>\n <span style={s.label}>选择书籍</span>\n <select style={s.select} value={bookId} onChange={(e) => setBookId(e.target.value)} disabled={books.length === 0}>\n {books.length === 0 ? <option value=\"\">(缓存无书籍,先同步)</option> : books.map((b) => (\n <option key={b.bookId} value={b.bookId}>《{b.title}》{b.author !== '' ? ' · ' + b.author : ''}</option>\n ))}\n </select>\n <button style={s.button} onClick={() => void runSync()} disabled={busy || !view?.configured}>同步书架/笔记</button>\n </div>\n <div style={s.row}>\n <span style={s.label}>本次目标</span>\n <select style={{ ...s.select, flex: 0, minWidth: '110px' }} value={destNow} onChange={(e) => setDestNow(e.target.value)}>\n <option value=\"flomo\">flomo</option>\n <option value=\"local\">本地文件</option>\n <option value=\"notion\">Notion</option>\n </select>\n {destNow === 'local' && (\n <input style={s.input} placeholder=\"导出路径(必填)\" value={localDirNow} onChange={(e) => setLocalDirNow(e.target.value)} />\n )}\n {destNow === 'flomo' && (\n <input style={{ ...s.input, width: '150px' }} placeholder=\"本次标签(#)\" value={flomoTagNow} onChange={(e) => setFlomoTagNow(e.target.value)} />\n )}\n </div>\n <div style={s.row}>\n <label style={{ ...s.label, display: 'flex', alignItems: 'center', gap: '4px', cursor: 'pointer' }}>\n <input\n type=\"checkbox\"\n checked={usePromptNow === null ? (view?.usePrompt ?? false) : usePromptNow}\n onChange={(e) => setUsePromptNow(e.target.checked)}\n />\n 本次用 prompt 处理(默认跟随配置)\n </label>\n <div style={s.flex} />\n <button style={s.button} onClick={() => void runExportNow()} disabled={busy || !view?.configured}>\n 导出到 {destNow === 'local' ? '本地' : (destNow === 'notion' ? 'Notion' : 'flomo')}\n </button>\n </div>\n </div>\n\n {message !== '' && <div style={s.msg}>{message}</div>}\n <div style={s.hint}>\n 【API Key】打开 https://weread.qq.com/r/weread-skills → 微信读书账号登录 → 「创建 Key」→ 复制(wrk- 开头)。Key 绑定你的账号身份,请勿泄露。存于 ~/.dsh/weread-export.json(权限 0600)。\n </div>\n <div style={s.hint}>\n 【导出目标】flomo = 发到浮墨(复用「Flomo」面板凭据,超长自动拆多条 MEMO);本地 = 导出 Markdown 文件到指定目录(每次导出需填路径);Notion = 用本插件自己的 Integration Token 在目标父页面下创建子页面写入(目标页需分享给该 Integration)。\n </div>\n <div style={s.hint}>\n 【AI prompt】启用后导出前会调用 LLM(OpenAI 兼容,默认 DeepSeek)按模板整理划线内容再导出。模板支持 {'{title}'} {'{author}'} {'{highlights}'} {'{thoughts}'} 占位符,可随意编辑;本次导出也可临时开关。\n </div>\n <div style={s.hint}>\n 【导出条数】「全部导出」= 全部划线都导出(flomo 超长自动拆多条);「20/50/100/自定义」= 最多导出的条数。\n 同步后可用 weread_shelf / weread_notes / weread_readdata / weread_search / weread_book / weread_export 等工具。\n </div>\n </div>\n )\n}\n","/**\n * weread-export — browser half. Registers the WeRead settings panel into the\n * web settings page (settings.section entry). The panel configures the\n * Skills API key, default flomo tag, drives manual syncs, and quick-exports\n * highlights to flomo. Failure policy: registration problems are logged,\n * never thrown — the web shell fails the whole boot when a plugin apply\n * throws, and an external plugin must not take the GUI down.\n */\n// Type-only: pulls the settings-surface SlotMap merge (the 'settings.section'\n// entry) and the client runtime Context merge.\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\nimport type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'\nimport { WereadSettingsPanel } from './WereadSettingsPanel.tsx'\n\n/** Required services. */\nexport const inject = ['slots']\n\n/**\n * Register the WeRead settings page.\n * @param ctx - client root context.\n */\nexport function apply(ctx: ClientContext): void {\n try {\n ctx.slots.inject('settings.section', () => ctx.slots.register({\n name: 'settings.section',\n id: 'weread',\n order: 316,\n label: () => '微信读书',\n }, WereadSettingsPanel))\n } catch (error) {\n console.warn('[weread-export] settings panel registration failed:', error)\n }\n}\n"],"mappings":"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"}
package/lib/index.js CHANGED
@@ -302,25 +302,58 @@ var WereadApi = class {
302
302
  const FLOMO_CONFIG_FILE = path.join(homedir(), ".dsh", "dsh-flomo.json");
303
303
  /** Request timeout for a flomo POST. */
304
304
  const REQUEST_TIMEOUT_MS$1 = 2e4;
305
+ /** Mask a credential for display, keeping only the head and tail. */
306
+ function mask$1(value) {
307
+ if (!value) return "";
308
+ if (value.length <= 8) return value.slice(0, 2) + "****";
309
+ return value.slice(0, 4) + "****" + value.slice(-4);
310
+ }
305
311
  /** Whether flomo credentials exist on this machine. */
306
312
  async function flomoConfigured() {
307
313
  return (await loadFlomoCredentials()).resolved !== null;
308
314
  }
309
- /** Load and resolve the flomo send URL (null when not configured). */
310
- async function resolveFlomoUrl() {
311
- return (await loadFlomoCredentials()).resolved;
315
+ /** Public flomo status: source + masked credential. */
316
+ async function flomoStatus() {
317
+ const creds = await readFlomoCredentials();
318
+ const source = creds.webhookUrl !== "" ? "webhookUrl" : creds.apiKey !== "" ? "apiKey" : "";
319
+ return {
320
+ configured: source !== "",
321
+ source,
322
+ masked: source === "webhookUrl" ? mask$1(creds.webhookUrl) : source === "apiKey" ? mask$1(creds.apiKey) : "",
323
+ configPath: FLOMO_CONFIG_FILE
324
+ };
312
325
  }
313
- /** Read ~/.dsh/dsh-flomo.json and resolve the request URL. */
314
- async function loadFlomoCredentials() {
326
+ /** Read the persisted flomo credentials (never throws). */
327
+ async function readFlomoCredentials() {
315
328
  let record = {};
316
329
  try {
317
330
  const raw = await readFile(FLOMO_CONFIG_FILE, "utf8");
318
331
  const parsed = JSON.parse(raw);
319
332
  if (typeof parsed === "object" && parsed !== null) record = parsed;
320
333
  } catch {}
321
- const webhook = typeof record.webhookUrl === "string" ? record.webhookUrl.trim() : "";
334
+ return {
335
+ apiKey: typeof record.apiKey === "string" ? record.apiKey : "",
336
+ webhookUrl: typeof record.webhookUrl === "string" ? record.webhookUrl : ""
337
+ };
338
+ }
339
+ /**
340
+ * Write flomo credentials to the shared ~/.dsh/dsh-flomo.json (mode 0600).
341
+ * Shared with the dsh-flomo plugin — one place, both plugins use it.
342
+ */
343
+ async function writeFlomoCredentials(next) {
344
+ await mkdir(path.dirname(FLOMO_CONFIG_FILE), { recursive: true });
345
+ await writeFile(FLOMO_CONFIG_FILE, JSON.stringify(next, null, 2), { mode: 384 });
346
+ }
347
+ /** Load and resolve the flomo send URL (null when not configured). */
348
+ async function resolveFlomoUrl() {
349
+ return (await loadFlomoCredentials()).resolved;
350
+ }
351
+ /** Read ~/.dsh/dsh-flomo.json and resolve the request URL. */
352
+ async function loadFlomoCredentials() {
353
+ const creds = await readFlomoCredentials();
354
+ const webhook = creds.webhookUrl.trim();
322
355
  if (webhook) return { resolved: webhook };
323
- const key = typeof record.apiKey === "string" ? record.apiKey.trim() : "";
356
+ const key = creds.apiKey.trim();
324
357
  if (key) return { resolved: "https://flomoapp.com/api/prod/apis/webhook/v1/?apiKey=" + encodeURIComponent(key) };
325
358
  return { resolved: null };
326
359
  }
@@ -1956,7 +1989,23 @@ function makeRoutes(deps) {
1956
1989
  writeJson(res, 400, { error: "invalid JSON body" });
1957
1990
  return;
1958
1991
  }
1959
- writeJson(res, 200, await store.patch(body));
1992
+ if (body.flomoWebhookUrl !== void 0 || body.flomoApiKey !== void 0 || body.flomoReset === true) {
1993
+ const next = { ...await readFlomoCredentials() };
1994
+ if (body.flomoReset === true) {
1995
+ next.webhookUrl = "";
1996
+ next.apiKey = "";
1997
+ } else {
1998
+ if (typeof body.flomoWebhookUrl === "string") next.webhookUrl = body.flomoWebhookUrl.trim();
1999
+ if (typeof body.flomoApiKey === "string") next.apiKey = body.flomoApiKey.trim();
2000
+ }
2001
+ await writeFlomoCredentials(next);
2002
+ const rest = { ...body };
2003
+ delete rest.flomoWebhookUrl;
2004
+ delete rest.flomoApiKey;
2005
+ delete rest.flomoReset;
2006
+ await store.patch(rest);
2007
+ } else await store.patch(body);
2008
+ writeJson(res, 200, await store.view());
1960
2009
  return;
1961
2010
  }
1962
2011
  writeJson(res, 405, { error: `method not allowed: ${method}` });
@@ -1969,11 +2018,14 @@ function makeRoutes(deps) {
1969
2018
  if (!guard(req, res, "GET")) return;
1970
2019
  const view = await store.view();
1971
2020
  const cache = await readCache();
1972
- const flomoOk = await resolveFlomoUrl().then((url) => url !== null).catch(() => false);
2021
+ const flomo = await flomoStatus();
1973
2022
  const noteBooks = cache.notebooks.filter((b) => (b.reviewCount ?? 0) + (b.noteCount ?? 0) + (b.bookmarkCount ?? 0) > 0).length;
1974
2023
  writeJson(res, 200, {
1975
2024
  ...view,
1976
- flomoConfigured: flomoOk,
2025
+ flomoConfigured: flomo.configured,
2026
+ flomoSource: flomo.source,
2027
+ flomoMasked: flomo.masked,
2028
+ flomoConfigPath: flomo.configPath,
1977
2029
  cachedShelfBooks: cache.shelfBooks.length,
1978
2030
  cachedNoteBooks: noteBooks,
1979
2031
  cacheUpdatedAt: cache.updatedAt
@@ -2161,4 +2213,4 @@ function apply(ctx, config) {
2161
2213
  sync();
2162
2214
  }
2163
2215
  //#endregion
2164
- export { DEFAULT_EXPORT_PROMPT, FLOMO_CONFIG_FILE, FLOMO_MAX_CHARS, NOTION_API, NOTION_VERSION, SKILL_VERSION, WEREAD_API, WEREAD_GATEWAY, WEREAD_GUIDANCE, WereadApi, WereadApiError, WereadStore, apply, buildExportMarkdown, buildFlomoMemo, buildFlomoMemos, buildNotesMarkdown, buildTaggedContent, buildTools, cachePath, chatComplete, chunkText, configPath, dateLabel, deepLink, defineTool, doSync, emptyCache, exportToFlomo, exportToLocal, exportToNotion, flomoConfigured, formatDate, formatDuration, formatRating, inject, llmConfigured, makeRoutes, mask, name, normalizeNotionPageId, notebookLines, postMemo, processWithPrompt, readCache, renderPrompt, resolveFlomoUrl, runExport, shelfLine, toNotionBlocks, wereadBookTool, wereadConfigTool, wereadExportTool, wereadFlomoTool, wereadNotesTool, wereadReaddataTool, wereadSearchTool, wereadShelfTool, wereadStatusTool, wereadSyncTool, writeCache };
2216
+ export { DEFAULT_EXPORT_PROMPT, FLOMO_CONFIG_FILE, FLOMO_MAX_CHARS, NOTION_API, NOTION_VERSION, SKILL_VERSION, WEREAD_API, WEREAD_GATEWAY, WEREAD_GUIDANCE, WereadApi, WereadApiError, WereadStore, apply, buildExportMarkdown, buildFlomoMemo, buildFlomoMemos, buildNotesMarkdown, buildTaggedContent, buildTools, cachePath, chatComplete, chunkText, configPath, dateLabel, deepLink, defineTool, doSync, emptyCache, exportToFlomo, exportToLocal, exportToNotion, flomoConfigured, flomoStatus, formatDate, formatDuration, formatRating, inject, llmConfigured, makeRoutes, mask, name, normalizeNotionPageId, notebookLines, postMemo, processWithPrompt, readCache, readFlomoCredentials, renderPrompt, resolveFlomoUrl, runExport, shelfLine, toNotionBlocks, wereadBookTool, wereadConfigTool, wereadExportTool, wereadFlomoTool, wereadNotesTool, wereadReaddataTool, wereadSearchTool, wereadShelfTool, wereadStatusTool, wereadSyncTool, writeCache, writeFlomoCredentials };
@@ -23,6 +23,9 @@ export interface WereadConfigView {
23
23
  /** Status view with cache + flomo stats. */
24
24
  export interface WereadStatusView extends WereadConfigView {
25
25
  flomoConfigured: boolean;
26
+ flomoSource: string;
27
+ flomoMasked: string;
28
+ flomoConfigPath: string;
26
29
  cachedShelfBooks: number;
27
30
  cachedNoteBooks: number;
28
31
  cacheUpdatedAt: string;
@@ -14,8 +14,25 @@ export interface FlomoCredentials {
14
14
  apiKey: string;
15
15
  webhookUrl: string;
16
16
  }
17
+ /** Public flomo status view (never the full credential). */
18
+ export interface FlomoStatusView {
19
+ configured: boolean;
20
+ /** 'webhookUrl' | 'apiKey' | '' */
21
+ source: string;
22
+ masked: string;
23
+ configPath: string;
24
+ }
17
25
  /** Whether flomo credentials exist on this machine. */
18
26
  export declare function flomoConfigured(): Promise<boolean>;
27
+ /** Public flomo status: source + masked credential. */
28
+ export declare function flomoStatus(): Promise<FlomoStatusView>;
29
+ /** Read the persisted flomo credentials (never throws). */
30
+ export declare function readFlomoCredentials(): Promise<FlomoCredentials>;
31
+ /**
32
+ * Write flomo credentials to the shared ~/.dsh/dsh-flomo.json (mode 0600).
33
+ * Shared with the dsh-flomo plugin — one place, both plugins use it.
34
+ */
35
+ export declare function writeFlomoCredentials(next: FlomoCredentials): Promise<void>;
19
36
  /** Load and resolve the flomo send URL (null when not configured). */
20
37
  export declare function resolveFlomoUrl(): Promise<string | null>;
21
38
  /** One send outcome (never throws for HTTP/parse outcomes). */
@@ -37,7 +37,7 @@ export { WereadStore, mask, configPath, cachePath, DEFAULT_EXPORT_PROMPT, type W
37
37
  export { WereadApi, WereadApiError, WEREAD_GATEWAY, SKILL_VERSION, type BookInfo, type ShelfBook, type NotebookEntry, type Highlight, type MineReviewEntry } from './api.ts';
38
38
  export { wereadStatusTool, wereadConfigTool, wereadSearchTool, wereadBookTool, wereadShelfTool, wereadNotesTool, wereadReaddataTool, wereadSyncTool, wereadExportTool, wereadFlomoTool, runExport, buildTools, type ToolContext, type ExportRequest, type ExportResult } from './tools.ts';
39
39
  export { doSync, readCache, writeCache, emptyCache, buildNotesMarkdown, buildFlomoMemo, buildFlomoMemos, FLOMO_MAX_CHARS, formatDate, formatDuration, formatRating, dateLabel, deepLink, shelfLine, notebookLines, type WereadCache } from './cache.ts';
40
- export { resolveFlomoUrl, flomoConfigured, postMemo, buildTaggedContent, FLOMO_CONFIG_FILE } from './flomo.ts';
40
+ export { resolveFlomoUrl, flomoConfigured, flomoStatus, readFlomoCredentials, writeFlomoCredentials, postMemo, buildTaggedContent, FLOMO_CONFIG_FILE, type FlomoStatusView, type FlomoCredentials } from './flomo.ts';
41
41
  export { chatComplete, renderPrompt, llmConfigured, type LlmConfig } from './llm.ts';
42
42
  export { buildExportMarkdown, processWithPrompt, exportToLocal, exportToNotion, exportToFlomo, chunkText, toNotionBlocks, normalizeNotionPageId, NOTION_API, NOTION_VERSION } from './export.ts';
43
43
  export { makeRoutes, WEREAD_API } from './routes.ts';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "weread-export",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "微信读书 (WeChat Reading) integration for DeepSeek Harness: connect the official WeRead Skills Agent Gateway with a wrk- API key, then browse your bookshelf, export highlights & thoughts, query reading stats, and send highlights to flomo — agent tools plus a web settings panel",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",