inbox-to-md 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +21 -0
- package/README.md +43 -0
- package/dist/ansi.d.ts +5 -0
- package/dist/ansi.js +13 -0
- package/dist/ansi.js.map +1 -0
- package/dist/archive.d.ts +2 -0
- package/dist/archive.js +175 -0
- package/dist/archive.js.map +1 -0
- package/dist/auth.d.ts +1 -0
- package/dist/auth.js +167 -0
- package/dist/auth.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +52 -0
- package/dist/cli.js.map +1 -0
- package/dist/compact.d.ts +3 -0
- package/dist/compact.js +211 -0
- package/dist/compact.js.map +1 -0
- package/dist/config.d.ts +19 -0
- package/dist/config.js +79 -0
- package/dist/config.js.map +1 -0
- package/dist/imap.d.ts +7 -0
- package/dist/imap.js +38 -0
- package/dist/imap.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +58 -0
- package/dist/index.js.map +1 -0
- package/dist/markdown.d.ts +18 -0
- package/dist/markdown.js +129 -0
- package/dist/markdown.js.map +1 -0
- package/dist/mime.d.ts +15 -0
- package/dist/mime.js +105 -0
- package/dist/mime.js.map +1 -0
- package/dist/prompts.d.ts +1 -0
- package/dist/prompts.js +11 -0
- package/dist/prompts.js.map +1 -0
- package/dist/ref-emails.d.ts +6 -0
- package/dist/ref-emails.js +16 -0
- package/dist/ref-emails.js.map +1 -0
- package/dist/sync.d.ts +10 -0
- package/dist/sync.js +296 -0
- package/dist/sync.js.map +1 -0
- package/dist/tui.d.ts +42 -0
- package/dist/tui.js +371 -0
- package/dist/tui.js.map +1 -0
- package/package.json +52 -0
package/dist/compact.js
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
// `npm run compact` — for each account, hierarchically compact its synced
|
|
2
|
+
// emails down to a single markdown digest under TARGET_TOKENS tokens
|
|
3
|
+
// (docs/algo-1.md), written to <syncPath>/compacted/final.md.
|
|
4
|
+
//
|
|
5
|
+
// Each layer: greedily pack the input strings into windows of at most
|
|
6
|
+
// TARGET_TOKENS tokens, have Claude compact each window in parallel, save
|
|
7
|
+
// each result as compacted/k_<layer>/<m>.md, then recurse on the compacted
|
|
8
|
+
// strings until everything fits in one window.
|
|
9
|
+
//
|
|
10
|
+
// Model calls go through the Claude Agent SDK, which runs on the local
|
|
11
|
+
// Claude Code installation and its login — usage bills to the Claude
|
|
12
|
+
// subscription, no ANTHROPIC_API_KEY or .env needed. The Agent SDK has no
|
|
13
|
+
// count_tokens endpoint, so token counts are a conservative local estimate;
|
|
14
|
+
// windows are soft bounds (the model's context is vastly larger than a
|
|
15
|
+
// window), so estimation error only shifts where windows split.
|
|
16
|
+
import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
17
|
+
import { createHash } from 'crypto';
|
|
18
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
19
|
+
import { join } from 'path';
|
|
20
|
+
import { loadConfig } from './config.js';
|
|
21
|
+
import { COMPACT_SYSTEM_PROMPT } from './prompts.js';
|
|
22
|
+
import { listSyncedEmails, readSyncedEmail } from './ref-emails.js';
|
|
23
|
+
const MODEL = 'claude-sonnet-5';
|
|
24
|
+
const TARGET_TOKENS = 100_000;
|
|
25
|
+
// A window must fit the model's context alongside the system prompt and
|
|
26
|
+
// leave room for the response (Sonnet 5: 1M context, 128K max output), so
|
|
27
|
+
// windows are capped independently of TARGET_TOKENS, with generous headroom
|
|
28
|
+
// for token-estimation error. The 0.98 keeps a window under the cap once
|
|
29
|
+
// parts are joined (concatenation boundaries can add ~1 token each).
|
|
30
|
+
const MODEL_WINDOW_CAP = 400_000;
|
|
31
|
+
const WINDOW_TOKEN_BUDGET = Math.floor(Math.min(TARGET_TOKENS, MODEL_WINDOW_CAP) * 0.98);
|
|
32
|
+
const CONCURRENCY = 8;
|
|
33
|
+
// Synced emails inline attachments as base64 data URIs — megabytes of
|
|
34
|
+
// encoded bytes that carry nothing a text digest can use, blow the token
|
|
35
|
+
// budget, and trip the API's usage-policy classifier (a prompt that is
|
|
36
|
+
// mostly opaque base64 looks like obfuscated content). Strip them before
|
|
37
|
+
// compaction, keeping the alt text as a trace that an attachment existed.
|
|
38
|
+
function stripInlineAttachments(email) {
|
|
39
|
+
return email.replace(/\(data:[\w.+-]+\/[\w.+-]+;base64,[A-Za-z0-9+/=\s]+\)/g, '(attachment omitted)');
|
|
40
|
+
}
|
|
41
|
+
// English prose averages ~4 chars/token; email archives (headers, URLs,
|
|
42
|
+
// encoded content) tokenize denser, so estimate conservatively at 3.5 —
|
|
43
|
+
// overestimating tokens only makes windows smaller, never over budget.
|
|
44
|
+
export function estimateTokens(str) {
|
|
45
|
+
if (str.trim() === '')
|
|
46
|
+
return 0;
|
|
47
|
+
return Math.ceil(str.length / 3.5);
|
|
48
|
+
}
|
|
49
|
+
// Saved window digests start with a checksum of the window's *input* text,
|
|
50
|
+
// so a re-run only reuses a digest when the underlying window content is
|
|
51
|
+
// unchanged (new/edited emails shift window boundaries and must recompact).
|
|
52
|
+
// The header is stripped before the digest feeds the next layer.
|
|
53
|
+
const CHECKSUM_RE = /^<!-- input-checksum: ([0-9a-f]{64}) -->\n/;
|
|
54
|
+
function checksum(text) {
|
|
55
|
+
return createHash('sha256').update(text).digest('hex');
|
|
56
|
+
}
|
|
57
|
+
// Returns the saved digest if it was produced from this exact window text,
|
|
58
|
+
// or null when missing, stale, checksum-less (pre-checksum run), or a
|
|
59
|
+
// failure placeholder — all of which mean the window must be recompacted.
|
|
60
|
+
function readReusableDigest(outPath, windowText) {
|
|
61
|
+
if (!existsSync(outPath))
|
|
62
|
+
return null;
|
|
63
|
+
const existing = readFileSync(outPath, 'utf8');
|
|
64
|
+
const match = CHECKSUM_RE.exec(existing);
|
|
65
|
+
if (match === null || match[1] !== checksum(windowText))
|
|
66
|
+
return null;
|
|
67
|
+
const digest = existing.slice(match[0].length);
|
|
68
|
+
if (digest.startsWith('> [!warning]'))
|
|
69
|
+
return null;
|
|
70
|
+
return digest;
|
|
71
|
+
}
|
|
72
|
+
// One model call: compact a single window's text. Returns the digest, or a
|
|
73
|
+
// failure reason string (never both) so the caller can record why a window
|
|
74
|
+
// was skipped without killing the run. Tools are disabled and the turn count
|
|
75
|
+
// capped: this is a pure text-in/text-out completion, not an agentic session.
|
|
76
|
+
async function compactWindow(windowText) {
|
|
77
|
+
for await (const message of query({
|
|
78
|
+
prompt: windowText,
|
|
79
|
+
options: {
|
|
80
|
+
model: MODEL,
|
|
81
|
+
systemPrompt: COMPACT_SYSTEM_PROMPT,
|
|
82
|
+
tools: [],
|
|
83
|
+
maxTurns: 1,
|
|
84
|
+
},
|
|
85
|
+
})) {
|
|
86
|
+
if (message.type !== 'result')
|
|
87
|
+
continue;
|
|
88
|
+
if (message.subtype !== 'success') {
|
|
89
|
+
return { failure: `${message.subtype}: ${message.errors.join('; ')}` };
|
|
90
|
+
}
|
|
91
|
+
if (message.is_error) {
|
|
92
|
+
return { failure: `api error (status ${message.api_error_status ?? 'unknown'}): ${message.result}` };
|
|
93
|
+
}
|
|
94
|
+
if (message.stop_reason === 'refusal')
|
|
95
|
+
return { failure: 'model refused' };
|
|
96
|
+
if (message.result === '')
|
|
97
|
+
return { failure: 'empty result' };
|
|
98
|
+
return { text: message.result };
|
|
99
|
+
}
|
|
100
|
+
return { failure: 'stream ended without a result message' };
|
|
101
|
+
}
|
|
102
|
+
// One layer of the recursion over one account's emails. `outputDir` is that
|
|
103
|
+
// account's compacted/ directory; `layer` is internal bookkeeping for its
|
|
104
|
+
// k_<layer>/ subpaths — callers pass only (strs, target, outputDir).
|
|
105
|
+
export async function compact(strs, targetNumberOfTokens, outputDir, layer = 0) {
|
|
106
|
+
const budget = Math.min(targetNumberOfTokens, WINDOW_TOKEN_BUDGET);
|
|
107
|
+
const counts = strs.map(estimateTokens);
|
|
108
|
+
const total = counts.reduce((sum, c) => sum + c, 0);
|
|
109
|
+
if (total <= targetNumberOfTokens) {
|
|
110
|
+
// Base case: everything already fits in one window.
|
|
111
|
+
return strs.join('');
|
|
112
|
+
}
|
|
113
|
+
// Greedy sliding window over the running token sum: extend j until adding
|
|
114
|
+
// the next string would exceed the budget, then start a new window at j.
|
|
115
|
+
// A single string over budget can't share a window and may not even fit
|
|
116
|
+
// the model's context (one 2M-token email exists in this archive), so it
|
|
117
|
+
// is sliced into budget-sized character chunks — chars per token derived
|
|
118
|
+
// from the estimator — and each chunk becomes a window.
|
|
119
|
+
const windows = [];
|
|
120
|
+
let windowParts = [];
|
|
121
|
+
let windowTokens = 0;
|
|
122
|
+
for (let k = 0; k < strs.length; k++) {
|
|
123
|
+
if (counts[k] > budget) {
|
|
124
|
+
if (windowParts.length > 0) {
|
|
125
|
+
windows.push(windowParts.join(''));
|
|
126
|
+
windowParts = [];
|
|
127
|
+
windowTokens = 0;
|
|
128
|
+
}
|
|
129
|
+
const chunkChars = Math.max(1, Math.floor((strs[k].length * budget) / counts[k]));
|
|
130
|
+
for (let pos = 0; pos < strs[k].length; pos += chunkChars) {
|
|
131
|
+
windows.push(strs[k].slice(pos, pos + chunkChars));
|
|
132
|
+
}
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (windowParts.length > 0 && windowTokens + counts[k] > budget) {
|
|
136
|
+
windows.push(windowParts.join(''));
|
|
137
|
+
windowParts = [];
|
|
138
|
+
windowTokens = 0;
|
|
139
|
+
}
|
|
140
|
+
windowParts.push(strs[k]);
|
|
141
|
+
windowTokens += counts[k];
|
|
142
|
+
}
|
|
143
|
+
if (windowParts.length > 0)
|
|
144
|
+
windows.push(windowParts.join(''));
|
|
145
|
+
console.log(`layer ${layer}: ${strs.length} inputs (~${total} tokens) → ${windows.length} windows`);
|
|
146
|
+
const layerDir = join(outputDir, `k_${layer}`);
|
|
147
|
+
mkdirSync(layerDir, { recursive: true });
|
|
148
|
+
// Compact each window with the model, in bounded-concurrency batches (each
|
|
149
|
+
// call spawns a Claude Code subprocess, so keep the fan-out modest).
|
|
150
|
+
const compacted = [];
|
|
151
|
+
for (let start = 0; start < windows.length; start += CONCURRENCY) {
|
|
152
|
+
const batch = windows.slice(start, start + CONCURRENCY);
|
|
153
|
+
const results = await Promise.all(batch.map(async (windowText, offset) => {
|
|
154
|
+
const m = start + offset;
|
|
155
|
+
// Resume support: a digest left by an earlier run is reused only if
|
|
156
|
+
// its recorded input checksum still matches this window's text.
|
|
157
|
+
// Failure placeholders don't count — a re-run retries those windows.
|
|
158
|
+
const outPath = join(layerDir, `${m}.md`);
|
|
159
|
+
const reusable = readReusableDigest(outPath, windowText);
|
|
160
|
+
if (reusable !== null) {
|
|
161
|
+
console.log(`layer ${layer}: reusing existing window ${m}`);
|
|
162
|
+
return reusable;
|
|
163
|
+
}
|
|
164
|
+
const checksumHeader = `<!-- input-checksum: ${checksum(windowText)} -->\n`;
|
|
165
|
+
const result = await compactWindow(windowText);
|
|
166
|
+
// A refusal on one window (e.g. a phishing email in the archive)
|
|
167
|
+
// must not kill the run: record it visibly and keep going.
|
|
168
|
+
if ('failure' in result) {
|
|
169
|
+
console.warn(`layer ${layer}: window ${m} NOT compacted (${result.failure}) — placeholder written`);
|
|
170
|
+
const placeholder = `> [!warning] Window ${m} of layer ${layer} was not compacted (${result.failure}); its content is omitted from this digest.\n`;
|
|
171
|
+
writeFileSync(outPath, checksumHeader + placeholder);
|
|
172
|
+
return placeholder;
|
|
173
|
+
}
|
|
174
|
+
writeFileSync(outPath, checksumHeader + result.text);
|
|
175
|
+
console.log(`layer ${layer}: compacted window ${m}`);
|
|
176
|
+
return result.text;
|
|
177
|
+
}));
|
|
178
|
+
compacted.push(...results);
|
|
179
|
+
}
|
|
180
|
+
return compact(compacted, targetNumberOfTokens, outputDir, layer + 1);
|
|
181
|
+
}
|
|
182
|
+
export async function main() {
|
|
183
|
+
let compactedAny = false;
|
|
184
|
+
for (const account of loadConfig().accounts) {
|
|
185
|
+
const synced = listSyncedEmails(account.syncPath);
|
|
186
|
+
if (synced.length === 0) {
|
|
187
|
+
console.warn(`${account.username}: no synced emails found — skipping`);
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
compactedAny = true;
|
|
191
|
+
console.log(`compacting ${synced.length} emails from ${account.username} to <= ${TARGET_TOKENS} tokens with ${MODEL}`);
|
|
192
|
+
// Each email is prefixed with its citation key so the model can attribute
|
|
193
|
+
// every digest fact to its source file (see COMPACT_SYSTEM_PROMPT).
|
|
194
|
+
const emails = synced.map((email) => `\n<!-- source: ${email.citationKey} -->\n${stripInlineAttachments(readSyncedEmail(email))}`);
|
|
195
|
+
const outputDir = join(account.syncPath, 'compacted');
|
|
196
|
+
const digest = await compact(emails, TARGET_TOKENS, outputDir);
|
|
197
|
+
mkdirSync(outputDir, { recursive: true });
|
|
198
|
+
const finalPath = join(outputDir, 'final.md');
|
|
199
|
+
writeFileSync(finalPath, digest);
|
|
200
|
+
console.log(`${account.username}: final digest written to ${finalPath}`);
|
|
201
|
+
}
|
|
202
|
+
if (!compactedAny) {
|
|
203
|
+
console.error('no synced emails found for any account — run `npm start` first');
|
|
204
|
+
process.exitCode = 1;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
main().catch((err) => {
|
|
208
|
+
console.error(err instanceof Error ? err.message : err);
|
|
209
|
+
process.exitCode = 1;
|
|
210
|
+
});
|
|
211
|
+
//# sourceMappingURL=compact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compact.js","sourceRoot":"","sources":["../src/compact.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare const CONFIG_VERSION = 1;
|
|
2
|
+
export interface ImapAccount {
|
|
3
|
+
id: string;
|
|
4
|
+
label: string;
|
|
5
|
+
host: string;
|
|
6
|
+
port: number;
|
|
7
|
+
tls: boolean;
|
|
8
|
+
username: string;
|
|
9
|
+
password: string;
|
|
10
|
+
syncPath: string;
|
|
11
|
+
}
|
|
12
|
+
export interface Config {
|
|
13
|
+
version: typeof CONFIG_VERSION;
|
|
14
|
+
accounts: ImapAccount[];
|
|
15
|
+
}
|
|
16
|
+
export declare const CONFIG_PATH: string;
|
|
17
|
+
export declare function expandTilde(p: string): string;
|
|
18
|
+
export declare function loadConfig(): Config;
|
|
19
|
+
export declare function saveConfig(config: Config): void;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Config load/save for ~/.config/inbox-to-md/config.json. The file holds IMAP
|
|
2
|
+
// passwords in plaintext (by explicit choice, over the macOS Keychain), so it
|
|
3
|
+
// is written 0600 inside a 0700 directory and never overwritten when
|
|
4
|
+
// unparseable — a typo while hand-editing must not destroy stored credentials.
|
|
5
|
+
import { mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from 'fs';
|
|
6
|
+
import { homedir } from 'os';
|
|
7
|
+
import { dirname, join } from 'path';
|
|
8
|
+
import { dim } from './ansi.js';
|
|
9
|
+
export const CONFIG_VERSION = 1;
|
|
10
|
+
export const CONFIG_PATH = join(homedir(), '.config', 'inbox-to-md', 'config.json');
|
|
11
|
+
export function expandTilde(p) {
|
|
12
|
+
if (p === '~')
|
|
13
|
+
return homedir();
|
|
14
|
+
if (p.startsWith('~/'))
|
|
15
|
+
return join(homedir(), p.slice(2));
|
|
16
|
+
return p;
|
|
17
|
+
}
|
|
18
|
+
function isImapAccount(v) {
|
|
19
|
+
if (typeof v !== 'object' || v === null)
|
|
20
|
+
return false;
|
|
21
|
+
const a = v;
|
|
22
|
+
return (typeof a.id === 'string' &&
|
|
23
|
+
typeof a.label === 'string' &&
|
|
24
|
+
typeof a.host === 'string' &&
|
|
25
|
+
typeof a.port === 'number' &&
|
|
26
|
+
typeof a.tls === 'boolean' &&
|
|
27
|
+
typeof a.username === 'string' &&
|
|
28
|
+
typeof a.password === 'string' &&
|
|
29
|
+
typeof a.syncPath === 'string');
|
|
30
|
+
}
|
|
31
|
+
function invalidConfigError(reason) {
|
|
32
|
+
return new Error(`Invalid config at ${CONFIG_PATH}: ${reason}\n` +
|
|
33
|
+
`Fix the file by hand, or delete it and re-run \`npm run auth\` (stored accounts will be lost).`);
|
|
34
|
+
}
|
|
35
|
+
export function loadConfig() {
|
|
36
|
+
let raw;
|
|
37
|
+
try {
|
|
38
|
+
raw = readFileSync(CONFIG_PATH, 'utf8');
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
if (err.code === 'ENOENT') {
|
|
42
|
+
return { version: CONFIG_VERSION, accounts: [] }; // valid first-run state
|
|
43
|
+
}
|
|
44
|
+
throw err;
|
|
45
|
+
}
|
|
46
|
+
const mode = statSync(CONFIG_PATH).mode;
|
|
47
|
+
if ((mode & 0o077) !== 0) {
|
|
48
|
+
process.stderr.write(dim(`Warning: ${CONFIG_PATH} is readable by other users — run: chmod 600 ${CONFIG_PATH}`) + '\n');
|
|
49
|
+
}
|
|
50
|
+
let parsed;
|
|
51
|
+
try {
|
|
52
|
+
parsed = JSON.parse(raw);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
throw invalidConfigError(`not valid JSON (${err.message})`);
|
|
56
|
+
}
|
|
57
|
+
if (typeof parsed !== 'object' || parsed === null)
|
|
58
|
+
throw invalidConfigError('not a JSON object');
|
|
59
|
+
const cfg = parsed;
|
|
60
|
+
if (cfg.version !== CONFIG_VERSION) {
|
|
61
|
+
throw invalidConfigError(`unsupported version ${JSON.stringify(cfg.version)} (expected ${CONFIG_VERSION})`);
|
|
62
|
+
}
|
|
63
|
+
if (!Array.isArray(cfg.accounts) || !cfg.accounts.every(isImapAccount)) {
|
|
64
|
+
throw invalidConfigError('malformed "accounts" entry');
|
|
65
|
+
}
|
|
66
|
+
return { version: CONFIG_VERSION, accounts: cfg.accounts };
|
|
67
|
+
}
|
|
68
|
+
export function saveConfig(config) {
|
|
69
|
+
mkdirSync(dirname(CONFIG_PATH), { recursive: true, mode: 0o700 });
|
|
70
|
+
const tmp = CONFIG_PATH + '.tmp';
|
|
71
|
+
// Remove any stale tmp first: writeFileSync only applies `mode` when it
|
|
72
|
+
// creates the file, so writing over a leftover tmp could keep loose perms.
|
|
73
|
+
rmSync(tmp, { force: true });
|
|
74
|
+
writeFileSync(tmp, JSON.stringify(config, null, 2) + '\n', { mode: 0o600 });
|
|
75
|
+
// Atomic swap — a crash mid-write must never corrupt the only copy of the
|
|
76
|
+
// stored passwords.
|
|
77
|
+
renameSync(tmp, CONFIG_PATH);
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/imap.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ImapFlow } from 'imapflow';
|
|
2
|
+
import type { ImapAccount } from './config.js';
|
|
3
|
+
type ImapConnection = Pick<ImapAccount, 'host' | 'port' | 'tls' | 'username' | 'password'>;
|
|
4
|
+
export declare function createImapClient(account: ImapConnection): ImapFlow;
|
|
5
|
+
export declare function closeImapClient(client: ImapFlow): Promise<void>;
|
|
6
|
+
export declare function describeImapError(err: unknown, host: string, port: number): string;
|
|
7
|
+
export {};
|
package/dist/imap.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Shared IMAP client construction and error phrasing, used by both the auth
|
|
2
|
+
// verify step and the sync engine so a given failure reads the same in both.
|
|
3
|
+
import { ImapFlow } from 'imapflow';
|
|
4
|
+
export function createImapClient(account) {
|
|
5
|
+
return new ImapFlow({
|
|
6
|
+
host: account.host,
|
|
7
|
+
port: account.port,
|
|
8
|
+
secure: account.tls,
|
|
9
|
+
auth: { user: account.username, pass: account.password },
|
|
10
|
+
// imapflow's default pino logger spews JSON to stdout; silence it so it
|
|
11
|
+
// can't wreck the TUI or the sync summary.
|
|
12
|
+
logger: false,
|
|
13
|
+
connectionTimeout: 10_000,
|
|
14
|
+
greetingTimeout: 10_000,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
// Closes a connected client, preferring a clean LOGOUT but never letting a
|
|
18
|
+
// failure during teardown mask the real result.
|
|
19
|
+
export async function closeImapClient(client) {
|
|
20
|
+
try {
|
|
21
|
+
await client.logout();
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
client.close();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function describeImapError(err, host, port) {
|
|
28
|
+
const e = err;
|
|
29
|
+
if (e?.authenticationFailed)
|
|
30
|
+
return 'Login failed — check username/password';
|
|
31
|
+
const message = e?.message ?? String(err);
|
|
32
|
+
const code = e?.code ?? '';
|
|
33
|
+
if (['ENOTFOUND', 'ECONNREFUSED', 'ECONNRESET', 'ETIMEDOUT'].includes(code) || /timed?\s?out/i.test(message)) {
|
|
34
|
+
return `Cannot reach ${host}:${port}: ${message}`;
|
|
35
|
+
}
|
|
36
|
+
return message;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=imap.js.map
|
package/dist/imap.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"imap.js","sourceRoot":"","sources":["../src/imap.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,6EAA6E;AAE7E,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAKpC,MAAM,UAAU,gBAAgB,CAAC,OAAuB;IACtD,OAAO,IAAI,QAAQ,CAAC;QAClB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,MAAM,EAAE,OAAO,CAAC,GAAG;QACnB,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC,QAAQ,EAAE;QACxD,wEAAwE;QACxE,2CAA2C;QAC3C,MAAM,EAAE,KAAK;QACb,iBAAiB,EAAE,MAAM;QACzB,eAAe,EAAE,MAAM;KACxB,CAAC,CAAC;AACL,CAAC;AAED,2EAA2E;AAC3E,gDAAgD;AAChD,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,MAAgB;IACpD,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC;IACxB,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,CAAC,KAAK,EAAE,CAAC;IACjB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,GAAY,EAAE,IAAY,EAAE,IAAY;IACxE,MAAM,CAAC,GAAG,GAAiF,CAAC;IAC5F,IAAI,CAAC,EAAE,oBAAoB;QAAE,OAAO,wCAAwC,CAAC;IAC7E,MAAM,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC;IAC1C,MAAM,IAAI,GAAG,CAAC,EAAE,IAAI,IAAI,EAAE,CAAC;IAC3B,IAAI,CAAC,WAAW,EAAE,cAAc,EAAE,YAAY,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAC7G,OAAO,gBAAgB,IAAI,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;IACpD,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// `npm start` — sync all configured inboxes to markdown.
|
|
2
|
+
//
|
|
3
|
+
// npm start sync since the 1st of last month
|
|
4
|
+
// npm start -- --since 2026-01-01 sync since a specific date
|
|
5
|
+
// npm start -- --force-rewrite re-download and overwrite already-synced files
|
|
6
|
+
import { red } from './ansi.js';
|
|
7
|
+
import { loadConfig } from './config.js';
|
|
8
|
+
import { runSync } from './sync.js';
|
|
9
|
+
const USAGE = 'Usage: inbox-to-md sync [--since YYYY-MM-DD] [--force-rewrite]';
|
|
10
|
+
// Strict YYYY-MM-DD, validated by round-trip so e.g. 2026-02-31 is rejected
|
|
11
|
+
// rather than silently rolling over into March.
|
|
12
|
+
function parseSinceValue(value) {
|
|
13
|
+
const match = value.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
|
14
|
+
if (!match)
|
|
15
|
+
throw new Error(`Invalid --since date "${value}" (expected YYYY-MM-DD)\n${USAGE}`);
|
|
16
|
+
const year = Number(match[1]);
|
|
17
|
+
const month = Number(match[2]);
|
|
18
|
+
const day = Number(match[3]);
|
|
19
|
+
const date = new Date(year, month - 1, day);
|
|
20
|
+
if (date.getFullYear() !== year || date.getMonth() !== month - 1 || date.getDate() !== day) {
|
|
21
|
+
throw new Error(`Invalid --since date "${value}" (no such day)\n${USAGE}`);
|
|
22
|
+
}
|
|
23
|
+
return date;
|
|
24
|
+
}
|
|
25
|
+
function parseArgs(argv) {
|
|
26
|
+
let since;
|
|
27
|
+
let forceRewrite = false;
|
|
28
|
+
for (let i = 0; i < argv.length; i++) {
|
|
29
|
+
const arg = argv[i];
|
|
30
|
+
if (arg === '--since') {
|
|
31
|
+
const value = argv[++i];
|
|
32
|
+
if (value === undefined)
|
|
33
|
+
throw new Error(`--since requires a value\n${USAGE}`);
|
|
34
|
+
since = parseSinceValue(value);
|
|
35
|
+
}
|
|
36
|
+
else if (arg.startsWith('--since=')) {
|
|
37
|
+
since = parseSinceValue(arg.slice('--since='.length));
|
|
38
|
+
}
|
|
39
|
+
else if (arg === '--force-rewrite') {
|
|
40
|
+
forceRewrite = true;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
throw new Error(`Unknown argument "${arg}"\n${USAGE}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return { since, forceRewrite };
|
|
47
|
+
}
|
|
48
|
+
async function main() {
|
|
49
|
+
const { since, forceRewrite } = parseArgs(process.argv.slice(2));
|
|
50
|
+
const config = loadConfig();
|
|
51
|
+
const ok = await runSync(config, since, forceRewrite);
|
|
52
|
+
process.exitCode = ok ? 0 : 1;
|
|
53
|
+
}
|
|
54
|
+
main().catch((err) => {
|
|
55
|
+
console.error(red(err instanceof Error ? err.message : String(err)));
|
|
56
|
+
process.exitCode = 1;
|
|
57
|
+
});
|
|
58
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { AttachmentInfo } from './mime.js';
|
|
2
|
+
export interface EmailContent {
|
|
3
|
+
from: string;
|
|
4
|
+
to: string;
|
|
5
|
+
subject: string;
|
|
6
|
+
date: Date | null;
|
|
7
|
+
messageId: string;
|
|
8
|
+
text: string;
|
|
9
|
+
html: string;
|
|
10
|
+
attachments: AttachmentInfo[];
|
|
11
|
+
}
|
|
12
|
+
export declare function slugify(s: string): string;
|
|
13
|
+
export declare function fallbackHash(from: string, dateIso: string, subject: string): string;
|
|
14
|
+
export declare function buildFilename(date: Date, subject: string, hash?: string): string;
|
|
15
|
+
export declare function renderEmail(email: EmailContent, fallbackDate: Date, mailbox: string): string;
|
|
16
|
+
export declare function extractFrontmatterValue(fileHead: string, key: string): string | null;
|
|
17
|
+
export declare function extractMessageId(fileHead: string): string | null;
|
|
18
|
+
export declare function readFrontmatterHead(path: string): string | null;
|
package/dist/markdown.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// Email → markdown rendering: frontmatter, filenames, and the scanner that
|
|
2
|
+
// reads frontmatter values back out of existing files. Writer and scanner
|
|
3
|
+
// live together so the frontmatter format has exactly one owner.
|
|
4
|
+
import { createHash } from 'crypto';
|
|
5
|
+
import { closeSync, openSync, readSync } from 'fs';
|
|
6
|
+
import TurndownService from 'turndown';
|
|
7
|
+
const turndown = new TurndownService();
|
|
8
|
+
// Inline images arrive as data: URIs — megabytes of base64 that a text
|
|
9
|
+
// digest can't use. Replace them with a marker instead of copying the
|
|
10
|
+
// payload into the markdown.
|
|
11
|
+
turndown.addRule('dataUriImage', {
|
|
12
|
+
filter: (node) => node.nodeName === 'IMG' && /^data:/i.test(node.getAttribute('src') ?? ''),
|
|
13
|
+
replacement: () => '*(inline image)*',
|
|
14
|
+
});
|
|
15
|
+
// Every frontmatter value goes through JSON.stringify: a JSON string is a
|
|
16
|
+
// valid YAML double-quoted scalar, and it escapes quotes, newlines, and
|
|
17
|
+
// control characters — so a hostile subject like `"\nauthor: x` cannot
|
|
18
|
+
// inject frontmatter keys.
|
|
19
|
+
function yamlValue(s) {
|
|
20
|
+
return JSON.stringify(s);
|
|
21
|
+
}
|
|
22
|
+
export function slugify(s) {
|
|
23
|
+
const slug = s
|
|
24
|
+
.toLowerCase()
|
|
25
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
26
|
+
.replace(/^-+|-+$/g, '')
|
|
27
|
+
.slice(0, 80)
|
|
28
|
+
.replace(/-+$/, '');
|
|
29
|
+
return slug || 'no-subject';
|
|
30
|
+
}
|
|
31
|
+
function localDateStamp(date) {
|
|
32
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
33
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
|
|
34
|
+
}
|
|
35
|
+
// Deterministic stand-in identity for emails with no Message-ID header: the
|
|
36
|
+
// filename itself becomes the dedupe key, so it must hash the same fields on
|
|
37
|
+
// every run.
|
|
38
|
+
export function fallbackHash(from, dateIso, subject) {
|
|
39
|
+
return createHash('sha256').update(`${from}\n${dateIso}\n${subject}`).digest('hex').slice(0, 8);
|
|
40
|
+
}
|
|
41
|
+
export function buildFilename(date, subject, hash) {
|
|
42
|
+
const suffix = hash ? `-${hash}` : '';
|
|
43
|
+
return `${localDateStamp(date)}-${slugify(subject)}${suffix}.md`;
|
|
44
|
+
}
|
|
45
|
+
function formatSize(bytes) {
|
|
46
|
+
if (bytes < 1024)
|
|
47
|
+
return `${bytes} B`;
|
|
48
|
+
if (bytes < 1024 * 1024)
|
|
49
|
+
return `${Math.round(bytes / 1024)} KB`;
|
|
50
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
51
|
+
}
|
|
52
|
+
export function renderEmail(email, fallbackDate, mailbox) {
|
|
53
|
+
const date = email.date ?? fallbackDate;
|
|
54
|
+
let body;
|
|
55
|
+
if (email.text.trim() !== '') {
|
|
56
|
+
body = email.text.trim();
|
|
57
|
+
}
|
|
58
|
+
else if (email.html !== '') {
|
|
59
|
+
body = turndown.turndown(email.html).trim() || '*(no body)*';
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
body = '*(no body)*';
|
|
63
|
+
}
|
|
64
|
+
let out = '---\n';
|
|
65
|
+
out += `from: ${yamlValue(email.from)}\n`;
|
|
66
|
+
out += `to: ${yamlValue(email.to)}\n`;
|
|
67
|
+
out += `subject: ${yamlValue(email.subject)}\n`;
|
|
68
|
+
out += `date: ${yamlValue(date.toISOString())}\n`;
|
|
69
|
+
out += `message-id: ${yamlValue(email.messageId)}\n`;
|
|
70
|
+
out += `mailbox: ${yamlValue(mailbox)}\n`;
|
|
71
|
+
out += '---\n\n';
|
|
72
|
+
out += body + '\n';
|
|
73
|
+
if (email.attachments.length > 0) {
|
|
74
|
+
out += '\n## Attachments\n\n';
|
|
75
|
+
for (const att of email.attachments) {
|
|
76
|
+
out += `- ${att.name} (${formatSize(att.size)}, ${att.contentType})\n`;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
// Reads a frontmatter value back out of a previously written file. Scans only
|
|
82
|
+
// the frontmatter block — an email body is arbitrary text and could contain a
|
|
83
|
+
// line that looks like frontmatter, so matching past the closing --- would
|
|
84
|
+
// let a hostile email poison the dedupe set. `key` must be a literal key we
|
|
85
|
+
// wrote ourselves (it is interpolated into a regex unescaped).
|
|
86
|
+
export function extractFrontmatterValue(fileHead, key) {
|
|
87
|
+
if (!fileHead.startsWith('---\n'))
|
|
88
|
+
return null;
|
|
89
|
+
const end = fileHead.indexOf('\n---', 4);
|
|
90
|
+
const frontmatter = end === -1 ? fileHead : fileHead.slice(0, end);
|
|
91
|
+
const match = frontmatter.match(new RegExp(`^${key}: (".*")$`, 'm'));
|
|
92
|
+
if (!match)
|
|
93
|
+
return null;
|
|
94
|
+
try {
|
|
95
|
+
const value = JSON.parse(match[1]);
|
|
96
|
+
return value === '' ? null : value;
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
export function extractMessageId(fileHead) {
|
|
103
|
+
return extractFrontmatterValue(fileHead, 'message-id');
|
|
104
|
+
}
|
|
105
|
+
// How much of each file to read when scanning for frontmatter values.
|
|
106
|
+
// Frontmatter is a handful of single-line quoted scalars, but from/to lists
|
|
107
|
+
// with many recipients can get long — 8 KB leaves a wide margin.
|
|
108
|
+
const FRONTMATTER_SCAN_BYTES = 8192;
|
|
109
|
+
// Reads just the head of a previously written file, enough to cover its
|
|
110
|
+
// frontmatter. Returns null when the file can't be opened (e.g. it vanished
|
|
111
|
+
// between readdir and open).
|
|
112
|
+
export function readFrontmatterHead(path) {
|
|
113
|
+
let fd;
|
|
114
|
+
try {
|
|
115
|
+
fd = openSync(path, 'r');
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
const buffer = Buffer.alloc(FRONTMATTER_SCAN_BYTES);
|
|
122
|
+
const bytes = readSync(fd, buffer, 0, FRONTMATTER_SCAN_BYTES, 0);
|
|
123
|
+
return buffer.toString('utf8', 0, bytes);
|
|
124
|
+
}
|
|
125
|
+
finally {
|
|
126
|
+
closeSync(fd);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=markdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.js","sourceRoot":"","sources":["../src/markdown.ts"],"names":[],"mappings":"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"}
|
package/dist/mime.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { MessageStructureObject } from 'imapflow';
|
|
2
|
+
export interface TextPartRef {
|
|
3
|
+
part: string;
|
|
4
|
+
encoding: string;
|
|
5
|
+
charset: string;
|
|
6
|
+
isHtml: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface AttachmentInfo {
|
|
9
|
+
name: string;
|
|
10
|
+
size: number;
|
|
11
|
+
contentType: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function findTextPart(root: MessageStructureObject): TextPartRef | null;
|
|
14
|
+
export declare function collectAttachments(root: MessageStructureObject): AttachmentInfo[];
|
|
15
|
+
export declare function decodeTextPart(raw: Buffer, encoding: string, charset: string): string;
|