weread-export 0.1.2

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.
@@ -0,0 +1,238 @@
1
+ /**
2
+ * weread-export — WeRead Skills Agent Gateway client.
3
+ *
4
+ * Official interface: POST https://i.weread.qq.com/api/agent/gateway with
5
+ * `Authorization: Bearer <wrk-...>`; the body carries `api_name`,
6
+ * `skill_version` and business parameters flattened at the top level.
7
+ * Responses are field-trimmed by the service; `errcode !== 0` means an
8
+ * error with a Chinese message, and an `upgrade_info` field means the
9
+ * client's skill_version is stale and must be bumped.
10
+ *
11
+ * API key acquisition: open https://weread.qq.com/r/weread-skills, log in
12
+ * with your WeRead account, click 创建 Key, copy the wrk- key.
13
+ */
14
+ /** Gateway endpoint. */
15
+ export declare const WEREAD_GATEWAY = "https://i.weread.qq.com/api/agent/gateway";
16
+ /** Skill version reported on every request (mirrors the weread-skills pack). */
17
+ export declare const SKILL_VERSION = "1.0.4";
18
+ /** Error surfaced from the gateway (carries an optional errcode). */
19
+ export declare class WereadApiError extends Error {
20
+ code?: number;
21
+ constructor(message: string, code?: number);
22
+ }
23
+ /**
24
+ * WeRead Skills gateway client. All methods resolve parsed payloads and
25
+ * throw WereadApiError for API-level failures.
26
+ */
27
+ export declare class WereadApi {
28
+ private readonly apiKey;
29
+ constructor(apiKey: string);
30
+ /**
31
+ * Call one gateway endpoint.
32
+ * @param apiName - interface name, e.g. '/store/search' or '/_list'.
33
+ * @param params - business parameters, flattened at the top level.
34
+ */
35
+ gateway<T = Record<string, unknown>>(apiName: string, params?: Record<string, unknown>): Promise<T>;
36
+ /** List every available endpoint and its parameter definition. */
37
+ list(): Promise<Record<string, unknown>>;
38
+ /** Search the book store. */
39
+ search(keyword: string, count?: number, scope?: number): Promise<SearchResponse>;
40
+ /** Book metadata. */
41
+ bookInfo(bookId: string): Promise<BookInfo>;
42
+ /** Official chapter catalog (metadata only). */
43
+ chapterInfo(bookId: string): Promise<ChapterInfoResponse>;
44
+ /** Reading progress for one book. */
45
+ getProgress(bookId: string): Promise<ProgressResponse>;
46
+ /** The current bookshelf (books + audiobook albums + mp). */
47
+ shelf(): Promise<ShelfResponse>;
48
+ /** Notebook overview: every book with note/review/bookmark counts. */
49
+ notebooks(count?: number, lastSort?: number): Promise<NotebooksResponse>;
50
+ /** Underlines (highlights) for one book. */
51
+ bookmarklist(bookId: string): Promise<BookmarkListResponse>;
52
+ /** Personal thoughts/reviews for one book. */
53
+ reviewListMine(bookid: string, count?: number, synckey?: number): Promise<ReviewListMineResponse>;
54
+ /** Reading statistics. mode: weekly | monthly | annually | overall. */
55
+ readdata(mode: string, baseTime?: number): Promise<ReadDataResponse>;
56
+ }
57
+ export interface SearchResponse {
58
+ sid?: string;
59
+ /** 1=有更多, 0=无. */
60
+ hasMore?: number | boolean;
61
+ results?: SearchResult[];
62
+ }
63
+ export interface SearchResult {
64
+ title?: string;
65
+ scope?: number;
66
+ books?: SearchBookEntry[];
67
+ }
68
+ export interface SearchBookEntry {
69
+ searchIdx?: number;
70
+ bookInfo?: BookInfo;
71
+ newRating?: number;
72
+ newRatingCount?: number;
73
+ readingCount?: number;
74
+ deepLink?: string;
75
+ }
76
+ export interface BookInfo {
77
+ bookId?: string;
78
+ title?: string;
79
+ author?: string;
80
+ translator?: string;
81
+ cover?: string;
82
+ intro?: string;
83
+ category?: string;
84
+ publisher?: string;
85
+ publishTime?: string;
86
+ isbn?: string;
87
+ wordCount?: number;
88
+ newRating?: number;
89
+ newRatingCount?: number;
90
+ deepLink?: string;
91
+ }
92
+ export interface ChapterInfoResponse {
93
+ bookId?: string;
94
+ synckey?: number;
95
+ chapterUpdateTime?: number;
96
+ chapters?: Chapter[];
97
+ }
98
+ export interface Chapter {
99
+ chapterUid?: number;
100
+ chapterIdx?: number;
101
+ title?: string;
102
+ wordCount?: number;
103
+ level?: number;
104
+ updateTime?: number;
105
+ price?: number;
106
+ paid?: boolean;
107
+ isMPChapter?: boolean;
108
+ anchors?: unknown;
109
+ }
110
+ export interface ProgressResponse {
111
+ bookId?: string;
112
+ book?: ProgressBook;
113
+ timestamp?: number;
114
+ }
115
+ export interface ProgressBook {
116
+ chapterUid?: number;
117
+ chapterOffset?: number;
118
+ /** 0–100 integer (1 means 1%, not complete). */
119
+ progress?: number;
120
+ updateTime?: number;
121
+ recordReadingTime?: number;
122
+ finishTime?: number;
123
+ isStartReading?: boolean;
124
+ }
125
+ export interface ShelfResponse {
126
+ books?: ShelfBook[];
127
+ albums?: ShelfAlbum[];
128
+ mp?: unknown;
129
+ archive?: unknown[];
130
+ bookCount?: number;
131
+ }
132
+ export interface ShelfBook {
133
+ bookId?: string;
134
+ title?: string;
135
+ author?: string;
136
+ cover?: string;
137
+ category?: string;
138
+ readUpdateTime?: number;
139
+ /** 1=读完 (the gateway returns 1/0). */
140
+ finishReading?: boolean | number;
141
+ secret?: boolean | number;
142
+ deepLink?: string;
143
+ }
144
+ export interface ShelfAlbum {
145
+ albumInfo?: {
146
+ albumId?: string;
147
+ name?: string;
148
+ authorName?: string;
149
+ cover?: string;
150
+ trackCount?: number;
151
+ };
152
+ albumInfoExtra?: {
153
+ secret?: boolean;
154
+ };
155
+ }
156
+ export interface NotebooksResponse {
157
+ totalBookCount?: number;
158
+ totalNoteCount?: number;
159
+ /** 1=有更多, 0=无. */
160
+ hasMore?: number | boolean;
161
+ books?: NotebookEntry[];
162
+ }
163
+ export interface NotebookEntry {
164
+ bookId?: string;
165
+ book?: {
166
+ title?: string;
167
+ author?: string;
168
+ cover?: string;
169
+ };
170
+ /** 想法/点评数(划线想法、章节点评、书评等) */
171
+ reviewCount?: number;
172
+ /** 划线数(高亮原文条数) */
173
+ noteCount?: number;
174
+ /** 书签数(仅统计,不导出内容) */
175
+ bookmarkCount?: number;
176
+ readingProgress?: number;
177
+ markedStatus?: number;
178
+ sort?: number;
179
+ }
180
+ export interface BookmarkListResponse {
181
+ updated?: Highlight[];
182
+ chapters?: Chapter[];
183
+ book?: unknown;
184
+ }
185
+ export interface Highlight {
186
+ bookmarkId?: string;
187
+ bookId?: string;
188
+ chapterUid?: number;
189
+ markText?: string;
190
+ createTime?: number;
191
+ type?: number;
192
+ range?: string;
193
+ colorStyle?: number;
194
+ }
195
+ export interface ReviewListMineResponse {
196
+ reviews?: MineReviewEntry[];
197
+ totalCount?: number;
198
+ /** 1=有更多, 0=无. */
199
+ hasMore?: number | boolean;
200
+ synckey?: number;
201
+ }
202
+ export interface MineReviewEntry {
203
+ review?: {
204
+ reviewId?: string;
205
+ content?: string;
206
+ /** 想法对应的划线原文(划线想法时有值) */
207
+ abstract?: string;
208
+ /** 划线原文位置范围,如 "2959-3007" */
209
+ range?: string;
210
+ chapterUid?: number;
211
+ createTime?: number;
212
+ star?: number;
213
+ chapterName?: string;
214
+ isFinish?: boolean;
215
+ };
216
+ }
217
+ export interface ReadDataResponse {
218
+ baseTime?: number;
219
+ readTimes?: unknown;
220
+ dailyReadTimes?: unknown;
221
+ readDays?: number;
222
+ totalReadTime?: number;
223
+ dayAverageReadTime?: number;
224
+ compare?: unknown;
225
+ readLongest?: unknown[];
226
+ readStat?: unknown[];
227
+ preferCategory?: unknown[];
228
+ preferTime?: unknown[];
229
+ preferTimeWord?: string;
230
+ preferAuthor?: unknown[];
231
+ /** 文字阅读占比(%),约 wrReadTime/(wrReadTime+wrListenTime)*100 */
232
+ readRate?: number;
233
+ wrReadTime?: number;
234
+ wrListenTime?: number;
235
+ /** 好友排行:{ text, scheme } */
236
+ rank?: unknown;
237
+ yearReport?: unknown;
238
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * weread-export — local sync snapshot & render helpers.
3
+ *
4
+ * weread_sync pulls the bookshelf and the notebook overview into
5
+ * ~/.dsh/weread-export-cache.json (mode 0600) so the settings panel and
6
+ * quick actions can render without hammering the gateway. Markdown
7
+ * builders here are shared by the tools and the panel routes.
8
+ */
9
+ import type { WereadApi, ShelfBook, NotebookEntry, Highlight, Chapter, MineReviewEntry } from './api.ts';
10
+ /** Persistent snapshot shape. */
11
+ export interface WereadCache {
12
+ updatedAt: string;
13
+ shelfBooks: ShelfBook[];
14
+ albumsCount: number;
15
+ mpCount: number;
16
+ notebooks: NotebookEntry[];
17
+ }
18
+ /** Empty snapshot. */
19
+ export declare function emptyCache(): WereadCache;
20
+ /** Read the snapshot (never throws). */
21
+ export declare function readCache(): Promise<WereadCache>;
22
+ /** Persist the snapshot (mode 0600). */
23
+ export declare function writeCache(next: WereadCache): Promise<void>;
24
+ /** Pull shelf + notebooks into the snapshot. */
25
+ export declare function doSync(api: WereadApi): Promise<{
26
+ ok: boolean;
27
+ message: string;
28
+ cache: WereadCache;
29
+ }>;
30
+ /** Unix seconds → 'YYYY-MM-DD' (local); '' for missing values. */
31
+ export declare function formatDate(ts?: number): string;
32
+ /** Local date label 'YYYY-MM-DD(周X)'. */
33
+ export declare function dateLabel(date: Date): string;
34
+ /** Seconds → 'X小时Y分钟' / 'N分钟' / 'N秒'. */
35
+ export declare function formatDuration(seconds?: number): string;
36
+ /** Rating display: the gateway returns a 0-100 score; show as 0-10. */
37
+ export declare function formatRating(n?: number): string;
38
+ /** Book detail deep link, preferring the service-provided one. */
39
+ export declare function deepLink(bookId?: string, provided?: string): string;
40
+ /** ChapterUid → title map for note rendering. */
41
+ export declare function chapterTitleMap(chapters: Chapter[] | undefined): Map<number, string>;
42
+ /** One-line shelf entry. */
43
+ export declare function shelfLine(book: ShelfBook, progressByBookId: Map<string, number>): string;
44
+ /** Notebook overview lines (笔记数 = 划线 + 想法 + 书签). */
45
+ export declare function notebookLines(entries: NotebookEntry[]): string[];
46
+ /** Per-book notes markdown: highlights + thoughts. */
47
+ export declare function buildNotesMarkdown(title: string, author: string, highlights: Highlight[], thoughts: MineReviewEntry[], chapters: Chapter[] | undefined): string;
48
+ /** One flomo memo body for a book's highlights (truncated at `limit`). */
49
+ export declare function buildFlomoMemo(title: string, highlights: Highlight[], chapters: Chapter[] | undefined, total: number, limit: number): string;
50
+ /** Safe per-memo size cap (flomo does not document a hard limit; stay conservative). */
51
+ export declare const FLOMO_MAX_CHARS = 1800;
52
+ /**
53
+ * Split a book's highlights into one or more flomo memo bodies so that
54
+ * ALL highlights are exported — long lists are chunked by character count,
55
+ * never truncated. A single over-long highlight becomes its own memo.
56
+ */
57
+ export declare function buildFlomoMemos(title: string, highlights: Highlight[], chapters: Chapter[] | undefined, maxChars?: number): string[];
@@ -0,0 +1,2 @@
1
+ /** The WeRead settings panel component. */
2
+ export declare function WereadSettingsPanel(): JSX.Element;
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Browser-side API client for the /api/weread-export route family. The only
3
+ * data access path the settings panel uses — plain fetch, same origin.
4
+ */
5
+ /** Public config view (mirrors the host contract). */
6
+ export interface WereadConfigView {
7
+ configured: boolean;
8
+ apiKeyMasked: string;
9
+ defaultFlomoTag: string;
10
+ /** 0 = export ALL highlights; N > 0 = cap at N. */
11
+ exportLimit: number;
12
+ exportDest: string;
13
+ localExportDir: string;
14
+ notionConfigured: boolean;
15
+ notionTargetPageId: string;
16
+ usePrompt: boolean;
17
+ llmConfigured: boolean;
18
+ llmBaseUrl: string;
19
+ llmModel: string;
20
+ lastSyncAt: string;
21
+ configPath: string;
22
+ }
23
+ /** Status view with cache + flomo stats. */
24
+ export interface WereadStatusView extends WereadConfigView {
25
+ flomoConfigured: boolean;
26
+ cachedShelfBooks: number;
27
+ cachedNoteBooks: number;
28
+ cacheUpdatedAt: string;
29
+ }
30
+ /** Sync result. */
31
+ export interface WereadSyncResult {
32
+ ok: boolean;
33
+ message: string;
34
+ shelfBooks: number;
35
+ notebooks: number;
36
+ }
37
+ /** One cached book for the export picker. */
38
+ export interface WereadBook {
39
+ bookId: string;
40
+ title: string;
41
+ author: string;
42
+ }
43
+ /** Flomo export result. */
44
+ export interface WereadFlomoResult {
45
+ ok: boolean;
46
+ message: string;
47
+ sent: number;
48
+ memoCount?: number;
49
+ bookId?: string;
50
+ }
51
+ /** Error carrying the route's JSON error message. */
52
+ export declare class WereadApiError extends Error {
53
+ constructor(message: string);
54
+ }
55
+ /** The weread panel API. */
56
+ export declare class WereadApi {
57
+ getConfig(): Promise<WereadConfigView>;
58
+ setConfig(patch: Record<string, unknown>): Promise<WereadConfigView>;
59
+ getStatus(): Promise<WereadStatusView>;
60
+ test(): Promise<{
61
+ ok: boolean;
62
+ message: string;
63
+ }>;
64
+ sync(): Promise<WereadSyncResult>;
65
+ books(): Promise<WereadBook[]>;
66
+ exportFlomo(bookId: string, tag: string, limit?: number): Promise<WereadFlomoResult>;
67
+ /** Multi-target export: flomo / local / notion with optional prompt. */
68
+ exportData(body: Record<string, unknown>): Promise<WereadFlomoResult>;
69
+ }
@@ -0,0 +1,8 @@
1
+ import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client';
2
+ /** Required services. */
3
+ export declare const inject: string[];
4
+ /**
5
+ * Register the WeRead settings page.
6
+ * @param ctx - client root context.
7
+ */
8
+ export declare function apply(ctx: ClientContext): void;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * weread-export — unified export targets.
3
+ *
4
+ * One pipeline, three destinations: flomo, local file, Notion page.
5
+ * Highlights (+ thoughts) are rendered to markdown, optionally processed by
6
+ * the configured LLM prompt, then delivered to the selected target. The
7
+ * flomo path reuses the dsh-flomo credentials file; the Notion path uses
8
+ * this plugin's own token + parent page; the local path writes a .md file
9
+ * to a user-supplied directory (no default — the caller must provide it).
10
+ */
11
+ import type { Highlight, MineReviewEntry, Chapter } from './api.ts';
12
+ import type { LlmConfig } from './llm.ts';
13
+ /** Render full export markdown: highlights + thoughts with chapter/time. */
14
+ export declare function buildExportMarkdown(title: string, author: string, highlights: Highlight[], thoughts: MineReviewEntry[], chapters: Chapter[] | undefined): string;
15
+ /** Run export text through the configured LLM prompt. */
16
+ export declare function processWithPrompt(llm: LlmConfig, promptTemplate: string, vars: {
17
+ title: string;
18
+ author: string;
19
+ highlights: string;
20
+ thoughts: string;
21
+ }): Promise<string>;
22
+ /** Write content to <dir>/<title>.md; creates the directory. */
23
+ export declare function exportToLocal(dir: string, title: string, content: string): Promise<string>;
24
+ /** Notion REST base URL. */
25
+ export declare const NOTION_API = "https://api.notion.com";
26
+ /** API version header (covers every endpoint used here). */
27
+ export declare const NOTION_VERSION = "2022-06-28";
28
+ /** Normalize a Notion page URL / id to the 32-char page id. */
29
+ export declare function normalizeNotionPageId(input: string): string;
30
+ /** Split markdown text into Notion paragraph blocks. */
31
+ export declare function toNotionBlocks(content: string): Array<Record<string, unknown>>;
32
+ /**
33
+ * Create a child page under the target parent page with the export content,
34
+ * appending extra blocks in batches if needed.
35
+ */
36
+ export declare function exportToNotion(token: string, parentId: string, title: string, content: string): Promise<string>;
37
+ /** Send text to flomo, chunking by size (never truncates). */
38
+ export declare function exportToFlomo(flomoUrl: string, title: string, content: string, tag: string): Promise<{
39
+ sent: number;
40
+ memoCount: number;
41
+ failed: number;
42
+ message: string;
43
+ }>;
44
+ /** Split arbitrary text into size-capped chunks with a small header. */
45
+ export declare function chunkText(text: string, maxChars: number, title: string): string[];
@@ -0,0 +1,33 @@
1
+ /**
2
+ * weread-export — flomo export integration.
3
+ *
4
+ * weread_flomo sends a book's highlights/thoughts to flomo (浮墨笔记).
5
+ * It reuses the credentials already configured for the dsh-flomo plugin
6
+ * (~/.dsh/dsh-flomo.json, mode 0600): webhookUrl wins over apiKey. The
7
+ * flomo tag is fully customizable — the tool's `tag` parameter, or the
8
+ * store's defaultFlomoTag (defaults to 微信读书).
9
+ */
10
+ /** Config file location shared with dsh-flomo (machine-wide, mode 0600). */
11
+ export declare const FLOMO_CONFIG_FILE: string;
12
+ /** Persisted flomo credential shape (read-only from weread's side). */
13
+ export interface FlomoCredentials {
14
+ apiKey: string;
15
+ webhookUrl: string;
16
+ }
17
+ /** Whether flomo credentials exist on this machine. */
18
+ export declare function flomoConfigured(): Promise<boolean>;
19
+ /** Load and resolve the flomo send URL (null when not configured). */
20
+ export declare function resolveFlomoUrl(): Promise<string | null>;
21
+ /** One send outcome (never throws for HTTP/parse outcomes). */
22
+ export interface FlomoSendResult {
23
+ ok: boolean;
24
+ message: string;
25
+ code?: number;
26
+ }
27
+ /**
28
+ * POST one memo to the flomo logging API. Resolves { ok, message, code? } —
29
+ * rejects only for transport-level failures.
30
+ */
31
+ export declare function postMemo(url: string, content: string): Promise<FlomoSendResult>;
32
+ /** Append normalized #tags to a memo body. */
33
+ export declare function buildTaggedContent(content: string, tags: string): string;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * weread-export — 微信读书 (WeChat Reading) integration for DeepSeek Harness.
3
+ * Host half.
4
+ *
5
+ * Mounts the weread tools (status / config / search / book / shelf / notes /
6
+ * readdata / sync / flomo), the /api/weread-export route family the settings
7
+ * panel talks to, and a system-prompt announcement. Data rides the official
8
+ * WeRead Skills Agent Gateway (i.weread.qq.com/api/agent/gateway) with a
9
+ * user-bound wrk- API key created at https://weread.qq.com/r/weread-skills.
10
+ * The key lives in ~/.dsh/weread-export.json (mode 0600) and the sync snapshot
11
+ * in ~/.dsh/weread-export-cache.json. Tools and routes build the API client
12
+ * lazily from the store, so a key configured later takes effect immediately.
13
+ */
14
+ import type { Context } from '@deepseek-ai/cordis';
15
+ import { defineTool } from '@deepseek-ai/dsh-tools';
16
+ /** Stable cordis plugin name. */
17
+ export declare const name = "weread";
18
+ /** Services required before the weread surfaces can mount. */
19
+ export declare const inject: string[];
20
+ /** Model-facing announcement: plugin presence, capabilities, and limits. */
21
+ export declare const WEREAD_GUIDANCE: string;
22
+ /** Plugin config, read from the composition row. */
23
+ export interface Config {
24
+ /** When true (default), a system-prompt section announces the plugin. */
25
+ announceToAgent?: boolean;
26
+ /** Master switch for the plugin (routes, tools, prompt section). */
27
+ enabled?: boolean;
28
+ }
29
+ /**
30
+ * Mount the weread tools, routes, and announcement.
31
+ * @param ctx - host plugin context carrying tools/systemPrompt/webServer.
32
+ * @param config - plugin config from the composition row.
33
+ */
34
+ export declare function apply(ctx: Context, config?: Config): void;
35
+ /** Re-exports for host consumers and smoke tests. */
36
+ export { WereadStore, mask, configPath, cachePath, DEFAULT_EXPORT_PROMPT, type WereadConfigView, type WereadCredentials, type ExportDest } from './store.ts';
37
+ export { WereadApi, WereadApiError, WEREAD_GATEWAY, SKILL_VERSION, type BookInfo, type ShelfBook, type NotebookEntry, type Highlight, type MineReviewEntry } from './api.ts';
38
+ export { wereadStatusTool, wereadConfigTool, wereadSearchTool, wereadBookTool, wereadShelfTool, wereadNotesTool, wereadReaddataTool, wereadSyncTool, wereadExportTool, wereadFlomoTool, runExport, buildTools, type ToolContext, type ExportRequest, type ExportResult } from './tools.ts';
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';
41
+ export { chatComplete, renderPrompt, llmConfigured, type LlmConfig } from './llm.ts';
42
+ export { buildExportMarkdown, processWithPrompt, exportToLocal, exportToNotion, exportToFlomo, chunkText, toNotionBlocks, normalizeNotionPageId, NOTION_API, NOTION_VERSION } from './export.ts';
43
+ export { makeRoutes, WEREAD_API } from './routes.ts';
44
+ export { defineTool };
@@ -0,0 +1,31 @@
1
+ /**
2
+ * weread-export — LLM prompt processing.
3
+ *
4
+ * A minimal OpenAI-compatible chat-completions client (DeepSeek-style). The
5
+ * base URL, API key, and model are configured in the settings panel (the AI
6
+ * section of this plugin) — no dependency on the host model registry. Used
7
+ * to run highlights through a user-editable prompt before export.
8
+ */
9
+ /** LLM endpoint configuration. */
10
+ export interface LlmConfig {
11
+ baseUrl: string;
12
+ apiKey: string;
13
+ model: string;
14
+ }
15
+ /** Is the LLM configured (key + base url + model present)? */
16
+ export declare function llmConfigured(config: LlmConfig): boolean;
17
+ /**
18
+ * One chat completion. Resolves the assistant text; rejects with a readable
19
+ * error on transport or API failures.
20
+ */
21
+ export declare function chatComplete(config: LlmConfig, system: string, user: string): Promise<string>;
22
+ /**
23
+ * Fill {title} / {author} / {highlights} / {thoughts} placeholders in a
24
+ * prompt template.
25
+ */
26
+ export declare function renderPrompt(template: string, vars: {
27
+ title: string;
28
+ author: string;
29
+ highlights: string;
30
+ thoughts: string;
31
+ }): string;
@@ -0,0 +1,57 @@
1
+ /**
2
+ * weread-export — loopback HTTP routes for the web settings panel.
3
+ *
4
+ * Route family: /api/weread-export/*. All routes are loopback-only
5
+ * (127.0.0.1/localhost, same-origin) — the settings panel is the only
6
+ * consumer.
7
+ */
8
+ import type { IncomingMessage, ServerResponse } from 'node:http';
9
+ import type { WereadStore } from './store.ts';
10
+ /** Route paths. */
11
+ export declare const WEREAD_API: {
12
+ readonly config: "/api/weread-export/config";
13
+ readonly status: "/api/weread-export/status";
14
+ readonly test: "/api/weread-export/test";
15
+ readonly sync: "/api/weread-export/sync";
16
+ readonly export: "/api/weread-export/export";
17
+ readonly flomo: "/api/weread-export/flomo";
18
+ readonly books: "/api/weread-export/books";
19
+ };
20
+ /** Route handler context. */
21
+ export interface RouteContext {
22
+ store: WereadStore;
23
+ }
24
+ /**
25
+ * Build every /api/weread-export route (exact paths).
26
+ * @param deps - store (the API client is built lazily per request).
27
+ * @returns the route list.
28
+ */
29
+ export declare function makeRoutes(deps: RouteContext): ({
30
+ kind: "exact";
31
+ path: "/api/weread-export/config";
32
+ handler: (req: IncomingMessage, res: ServerResponse) => Promise<void>;
33
+ } | {
34
+ kind: "exact";
35
+ path: "/api/weread-export/status";
36
+ handler: (req: IncomingMessage, res: ServerResponse) => Promise<void>;
37
+ } | {
38
+ kind: "exact";
39
+ path: "/api/weread-export/test";
40
+ handler: (req: IncomingMessage, res: ServerResponse) => Promise<void>;
41
+ } | {
42
+ kind: "exact";
43
+ path: "/api/weread-export/sync";
44
+ handler: (req: IncomingMessage, res: ServerResponse) => Promise<void>;
45
+ } | {
46
+ kind: "exact";
47
+ path: "/api/weread-export/books";
48
+ handler: (req: IncomingMessage, res: ServerResponse) => Promise<void>;
49
+ } | {
50
+ kind: "exact";
51
+ path: "/api/weread-export/export";
52
+ handler: (req: IncomingMessage, res: ServerResponse) => Promise<void>;
53
+ } | {
54
+ kind: "exact";
55
+ path: "/api/weread-export/flomo";
56
+ handler: (req: IncomingMessage, res: ServerResponse) => Promise<void>;
57
+ })[];
@@ -0,0 +1,87 @@
1
+ /**
2
+ * weread-export — credential/cache store.
3
+ *
4
+ * Persists the WeRead Skills API key (wrk-...) to ~/.dsh/weread-export.json
5
+ * (mode 0600) and the latest sync snapshot (bookshelf + notebook overview)
6
+ * to ~/.dsh/weread-export-cache.json. The config file holds the API key plus
7
+ * the default flomo tag used by weread_flomo. Reads are lazy and cached;
8
+ * the public view() never exposes secrets. Config paths can be overridden
9
+ * with DSH_WEREAD_CONFIG / DSH_WEREAD_CACHE (used by tests).
10
+ */
11
+ /** Default machine-wide config location (mode 0600). */
12
+ export declare const DEFAULT_CONFIG_FILE: string;
13
+ /** Default sync cache location (mode 0600). */
14
+ export declare const DEFAULT_CACHE_FILE: string;
15
+ /** Test override for the config location. */
16
+ export declare function configPath(): string;
17
+ /** Test override for the cache location. */
18
+ export declare function cachePath(): string;
19
+ /** Persisted credential shape. Secrets never leave this module. */
20
+ export interface WereadCredentials {
21
+ /** WeRead Skills API key (wrk-...), user-bound. */
22
+ apiKey: string;
23
+ /** Default flomo tag for weread_flomo exports (without leading #). */
24
+ defaultFlomoTag: string;
25
+ /** Highlights per export: 0 = export ALL, N > 0 = cap at N. */
26
+ exportLimit: number;
27
+ /** Default export destination: flomo | local | notion. */
28
+ exportDest: ExportDest;
29
+ /** Local export directory (required when dest=local; no default). */
30
+ localExportDir: string;
31
+ /** Notion integration token (plugin-owned, independent of dsh-notion). */
32
+ notionToken: string;
33
+ /** Notion target parent page: id or URL (page must share with the token). */
34
+ notionTargetPageId: string;
35
+ /** Whether to run highlights through the LLM prompt before export. */
36
+ usePrompt: boolean;
37
+ /** LLM prompt template ({title}/{author}/{highlights}/{thoughts} placeholders). */
38
+ exportPrompt: string;
39
+ /** OpenAI-compatible chat completions base URL. */
40
+ llmBaseUrl: string;
41
+ /** LLM API key (custom, panel-configured). */
42
+ llmApiKey: string;
43
+ /** LLM model name. */
44
+ llmModel: string;
45
+ /** ISO timestamp of the last successful sync. */
46
+ lastSyncAt: string;
47
+ }
48
+ /** Export destination. */
49
+ export type ExportDest = 'flomo' | 'local' | 'notion';
50
+ /** Public, secret-free status view. */
51
+ export interface WereadConfigView {
52
+ configured: boolean;
53
+ apiKeyMasked: string;
54
+ defaultFlomoTag: string;
55
+ exportLimit: number;
56
+ exportDest: ExportDest;
57
+ localExportDir: string;
58
+ notionConfigured: boolean;
59
+ notionTargetPageId: string;
60
+ usePrompt: boolean;
61
+ llmConfigured: boolean;
62
+ llmBaseUrl: string;
63
+ llmModel: string;
64
+ lastSyncAt: string;
65
+ configPath: string;
66
+ }
67
+ /** Mask a credential for display, keeping only the head and tail. */
68
+ export declare function mask(value: string): string;
69
+ /** Default export prompt template. */
70
+ export declare const DEFAULT_EXPORT_PROMPT: string;
71
+ /**
72
+ * Small credential store backed by ~/.dsh/weread-export.json.
73
+ * Reads are lazy and cached; writes use mode 0600 so the API key never
74
+ * leaks to other local users.
75
+ */
76
+ export declare class WereadStore {
77
+ config: WereadCredentials | null;
78
+ load(): Promise<WereadCredentials>;
79
+ save(next: WereadCredentials): Promise<void>;
80
+ /** Public, secret-free view. */
81
+ view(): Promise<WereadConfigView>;
82
+ /**
83
+ * Apply a config patch: any supported field replaces, reset clears.
84
+ * Returns the public view.
85
+ */
86
+ patch(args: Record<string, unknown> | undefined): Promise<WereadConfigView>;
87
+ }