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/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Chaidhat Chaimongkol
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# inbox-to-md
|
|
2
|
+
|
|
3
|
+
Sync IMAP inboxes to Markdown files — one file per email, with YAML frontmatter.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install -g inbox-to-md
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires Node.js 20 or newer.
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
inbox-to-md auth # interactive TUI: add/edit/delete IMAP accounts and their sync paths
|
|
17
|
+
inbox-to-md sync # sync INBOX + Sent mail from this month and last month
|
|
18
|
+
inbox-to-md compact # compact all messages into one md
|
|
19
|
+
inbox-to-md archive <md dir> # archive the emails behind the .md files in a directory
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Flags for `inbox-to-md sync`:
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
inbox-to-md sync --since 2026-01-01 # sync since a specific date instead of the default window
|
|
26
|
+
inbox-to-md sync --force-rewrite # re-download and overwrite already-synced files
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Development
|
|
30
|
+
|
|
31
|
+
Run from a checkout without building:
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
npm install
|
|
35
|
+
npm run auth # = inbox-to-md auth
|
|
36
|
+
npm start # = inbox-to-md sync (flags go after --)
|
|
37
|
+
npm run compact # = inbox-to-md compact
|
|
38
|
+
npm run archive <md dir> # = inbox-to-md archive
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## License
|
|
42
|
+
|
|
43
|
+
[MIT](LICENSE.md)
|
package/dist/ansi.d.ts
ADDED
package/dist/ansi.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Minimal ANSI helpers — keeps the TUI dep-free. Functions return the input
|
|
2
|
+
// unchanged when stdout isn't a TTY so piping to a file or another command
|
|
3
|
+
// produces clean text.
|
|
4
|
+
const tty = process.stdout.isTTY;
|
|
5
|
+
function wrap(open, close) {
|
|
6
|
+
return (s) => (tty ? `\x1b[${open}m${s}\x1b[${close}m` : s);
|
|
7
|
+
}
|
|
8
|
+
export const bold = wrap(1, 22);
|
|
9
|
+
export const dim = wrap(2, 22);
|
|
10
|
+
export const red = wrap(31, 39);
|
|
11
|
+
export const green = wrap(32, 39);
|
|
12
|
+
export const cyan = wrap(36, 39);
|
|
13
|
+
//# sourceMappingURL=ansi.js.map
|
package/dist/ansi.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ansi.js","sourceRoot":"","sources":["../src/ansi.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,2EAA2E;AAC3E,uBAAuB;AAEvB,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC;AAEjC,SAAS,IAAI,CAAC,IAAY,EAAE,KAAa;IACvC,OAAO,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,IAAI,IAAI,CAAC,QAAQ,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACtE,CAAC;AAED,MAAM,CAAC,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAChC,MAAM,CAAC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAC/B,MAAM,CAAC,MAAM,GAAG,GAAG,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;AAChC,MAAM,CAAC,MAAM,KAAK,GAAG,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;AAClC,MAAM,CAAC,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC"}
|
package/dist/archive.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// `npm run archive <md dir>` — for every INBOX email markdown file in the
|
|
2
|
+
// given directory, finds the email on the server (by the message-id in the
|
|
3
|
+
// file's frontmatter, searched in the mailbox it was synced from), moves it to the
|
|
4
|
+
// account's Archive mailbox, and deletes the file. The file is only deleted
|
|
5
|
+
// after the server confirms the move — a failed or not-found archive keeps
|
|
6
|
+
// the file so nothing is lost silently.
|
|
7
|
+
import { readdirSync, rmSync, statSync } from 'fs';
|
|
8
|
+
import { join, resolve } from 'path';
|
|
9
|
+
import { bold, dim, green, red } from './ansi.js';
|
|
10
|
+
import { loadConfig } from './config.js';
|
|
11
|
+
import { closeImapClient, createImapClient, describeImapError } from './imap.js';
|
|
12
|
+
import { extractFrontmatterValue, extractMessageId, readFrontmatterHead } from './markdown.js';
|
|
13
|
+
const USAGE = 'Usage: inbox-to-md archive <md dir>';
|
|
14
|
+
// Files without a message-id (fallback-hash filenames) or without a mailbox
|
|
15
|
+
// can't be located on any server, so they are reported and left alone rather
|
|
16
|
+
// than guessed at. Only INBOX mail is archivable: archiving anything else
|
|
17
|
+
// (e.g. Sent) just strips its label server-side for no benefit, so non-INBOX
|
|
18
|
+
// files are rejected.
|
|
19
|
+
function collectTargets(dir, counts) {
|
|
20
|
+
const targets = [];
|
|
21
|
+
for (const name of readdirSync(dir).sort()) {
|
|
22
|
+
if (!name.endsWith('.md'))
|
|
23
|
+
continue;
|
|
24
|
+
const path = join(dir, name);
|
|
25
|
+
const head = readFrontmatterHead(path);
|
|
26
|
+
const messageId = head === null ? null : extractMessageId(head);
|
|
27
|
+
const mailbox = head === null ? null : extractFrontmatterValue(head, 'mailbox');
|
|
28
|
+
if (!messageId || !mailbox) {
|
|
29
|
+
counts.skipped++;
|
|
30
|
+
console.error(dim(`skipping ${name}: no message-id/mailbox in frontmatter`));
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (mailbox !== 'INBOX') {
|
|
34
|
+
counts.skipped++;
|
|
35
|
+
console.error(red(`skipping ${name}: mailbox is "${mailbox}" — only INBOX mail can be archived`));
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
targets.push({ path, name, messageId, mailbox, archived: false });
|
|
39
|
+
}
|
|
40
|
+
return targets;
|
|
41
|
+
}
|
|
42
|
+
// Locates the account's Archive mailbox: the RFC 6154 special-use flag when
|
|
43
|
+
// the server provides it, Gmail's All Mail (\All — moving there is how Gmail
|
|
44
|
+
// archives), or the usual names. Returns null when nothing matches; we never
|
|
45
|
+
// create a mailbox on the user's server on our own.
|
|
46
|
+
async function findArchiveMailbox(client) {
|
|
47
|
+
const boxes = await client.list();
|
|
48
|
+
const bySpecialUse = boxes.find((b) => b.specialUse === '\\Archive' || b.specialUse === '\\All');
|
|
49
|
+
if (bySpecialUse)
|
|
50
|
+
return bySpecialUse.path;
|
|
51
|
+
const byName = boxes.find((b) => /^archives?$/i.test(b.name));
|
|
52
|
+
return byName ? byName.path : null;
|
|
53
|
+
}
|
|
54
|
+
// Tries to archive each pending target on this account. A message-id that
|
|
55
|
+
// isn't found here is left for the other accounts (or reported as not found
|
|
56
|
+
// at the end) — searching the wrong account just returns nothing.
|
|
57
|
+
async function archiveOnAccount(account, targets, counts) {
|
|
58
|
+
const pending = targets.filter((t) => !t.archived);
|
|
59
|
+
if (pending.length === 0)
|
|
60
|
+
return;
|
|
61
|
+
// One mailbox open per distinct source mailbox, not per file.
|
|
62
|
+
const byMailbox = new Map();
|
|
63
|
+
for (const t of pending) {
|
|
64
|
+
const group = byMailbox.get(t.mailbox);
|
|
65
|
+
if (group)
|
|
66
|
+
group.push(t);
|
|
67
|
+
else
|
|
68
|
+
byMailbox.set(t.mailbox, [t]);
|
|
69
|
+
}
|
|
70
|
+
const client = createImapClient(account);
|
|
71
|
+
await client.connect();
|
|
72
|
+
try {
|
|
73
|
+
const archiveBox = await findArchiveMailbox(client);
|
|
74
|
+
if (archiveBox === null) {
|
|
75
|
+
console.error(`${bold(account.label)}: ${red('no Archive mailbox found — skipping this account')}`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
for (const [mailbox, files] of byMailbox) {
|
|
79
|
+
let lock;
|
|
80
|
+
try {
|
|
81
|
+
lock = await client.getMailboxLock(mailbox);
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
continue; // this account has no such mailbox; another account may
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
for (const target of files) {
|
|
88
|
+
try {
|
|
89
|
+
const uids = await client.search({ header: { 'message-id': target.messageId } }, { uid: true });
|
|
90
|
+
if (!uids || uids.length === 0)
|
|
91
|
+
continue;
|
|
92
|
+
// A source already inside the Archive mailbox needs no move —
|
|
93
|
+
// the email is archived; just clean up the file.
|
|
94
|
+
if (mailbox !== archiveBox) {
|
|
95
|
+
await client.messageMove(uids, archiveBox, { uid: true });
|
|
96
|
+
}
|
|
97
|
+
rmSync(target.path);
|
|
98
|
+
target.archived = true;
|
|
99
|
+
counts.archived++;
|
|
100
|
+
console.log(`${green('archived')} ${target.name} ${dim(`(${account.label}: ${mailbox} → ${archiveBox})`)}`);
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
counts.errors++;
|
|
104
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
105
|
+
console.error(red(`error archiving ${target.name} on ${account.label}: ${detail}`));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
finally {
|
|
110
|
+
lock.release();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
finally {
|
|
115
|
+
await closeImapClient(client);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Archives across every account, isolating failures like the sync engine: one
|
|
119
|
+
// broken account logs loudly and the rest still run. Returns true only when
|
|
120
|
+
// every file was archived cleanly.
|
|
121
|
+
export async function runArchive(config, dir) {
|
|
122
|
+
if (config.accounts.length === 0) {
|
|
123
|
+
console.error('No accounts configured. Run `npm run auth` to add one.');
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
const counts = { archived: 0, skipped: 0, errors: 0 };
|
|
127
|
+
const targets = collectTargets(dir, counts);
|
|
128
|
+
if (targets.length === 0 && counts.skipped === 0) {
|
|
129
|
+
console.log('No email markdown files found — nothing to archive.');
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
let allOk = counts.skipped === 0;
|
|
133
|
+
for (const account of config.accounts) {
|
|
134
|
+
try {
|
|
135
|
+
await archiveOnAccount(account, targets, counts);
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
allOk = false;
|
|
139
|
+
console.error(`${bold(account.label)}: ${red(`FAILED — ${describeImapError(err, account.host, account.port)}`)}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const notFound = targets.filter((t) => !t.archived);
|
|
143
|
+
for (const target of notFound) {
|
|
144
|
+
console.error(red(`not found on any account: ${target.name} (${target.messageId})`));
|
|
145
|
+
}
|
|
146
|
+
console.log(dim(`Total: ${counts.archived} archived · ${counts.skipped} skipped · ${notFound.length} not found · ${counts.errors} errors`));
|
|
147
|
+
return allOk && counts.errors === 0 && notFound.length === 0;
|
|
148
|
+
}
|
|
149
|
+
function parseDirArg(argv) {
|
|
150
|
+
if (argv.length !== 1 || argv[0].startsWith('-')) {
|
|
151
|
+
throw new Error(USAGE);
|
|
152
|
+
}
|
|
153
|
+
const dir = resolve(argv[0]);
|
|
154
|
+
let stat;
|
|
155
|
+
try {
|
|
156
|
+
stat = statSync(dir);
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
throw new Error(`No such directory: ${dir}\n${USAGE}`);
|
|
160
|
+
}
|
|
161
|
+
if (!stat.isDirectory())
|
|
162
|
+
throw new Error(`Not a directory: ${dir}\n${USAGE}`);
|
|
163
|
+
return dir;
|
|
164
|
+
}
|
|
165
|
+
async function main() {
|
|
166
|
+
const dir = parseDirArg(process.argv.slice(2));
|
|
167
|
+
const config = loadConfig();
|
|
168
|
+
const ok = await runArchive(config, dir);
|
|
169
|
+
process.exitCode = ok ? 0 : 1;
|
|
170
|
+
}
|
|
171
|
+
main().catch((err) => {
|
|
172
|
+
console.error(red(err instanceof Error ? err.message : String(err)));
|
|
173
|
+
process.exitCode = 1;
|
|
174
|
+
});
|
|
175
|
+
//# sourceMappingURL=archive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archive.js","sourceRoot":"","sources":["../src/archive.ts"],"names":[],"mappings":"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"}
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// `npm run auth` — interactive account management. A menu loop lists the
|
|
2
|
+
// configured accounts; Enter edits (or adds), Ctrl+X deletes with a
|
|
3
|
+
// confirmation, Esc quits. The add/edit form verifies by actually logging in
|
|
4
|
+
// over IMAP before anything is saved.
|
|
5
|
+
import { randomUUID } from 'crypto';
|
|
6
|
+
import { mkdirSync } from 'fs';
|
|
7
|
+
import { isAbsolute } from 'path';
|
|
8
|
+
import { dim, red } from './ansi.js';
|
|
9
|
+
import { CONFIG_PATH, expandTilde, loadConfig, saveConfig } from './config.js';
|
|
10
|
+
import { closeImapClient, createImapClient, describeImapError } from './imap.js';
|
|
11
|
+
import { enterTui, exitTui, pickFromMenu, runForm } from './tui.js';
|
|
12
|
+
const MENU_FOOTER = '[↑/↓] select · [enter] edit/add · [ctrl+x] delete · [esc] quit';
|
|
13
|
+
const FORM_FOOTER = '[↑/↓] move · [enter] verify & save · [esc] back without saving';
|
|
14
|
+
function errorMessage(err) {
|
|
15
|
+
return err instanceof Error ? err.message : String(err);
|
|
16
|
+
}
|
|
17
|
+
// Verifies form values: local validation first (cheap, specific messages),
|
|
18
|
+
// then a real IMAP login — imapflow authenticates during connect(), so
|
|
19
|
+
// success proves host, port, TLS mode, and credentials in one shot.
|
|
20
|
+
// Returns null on success or the error string to show in red.
|
|
21
|
+
async function verifyAccount(values) {
|
|
22
|
+
if (values.label.trim() === '')
|
|
23
|
+
return 'Label is required';
|
|
24
|
+
if (values.host.trim() === '')
|
|
25
|
+
return 'IMAP host is required';
|
|
26
|
+
const port = parseInt(values.port, 10);
|
|
27
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535)
|
|
28
|
+
return 'Port must be between 1 and 65535';
|
|
29
|
+
if (values.username.trim() === '')
|
|
30
|
+
return 'Username is required';
|
|
31
|
+
if (values.password === '')
|
|
32
|
+
return 'Password is required';
|
|
33
|
+
const syncPath = expandTilde(values.syncPath.trim());
|
|
34
|
+
if (syncPath === '')
|
|
35
|
+
return 'Sync path is required';
|
|
36
|
+
if (!isAbsolute(syncPath))
|
|
37
|
+
return 'Sync path must be absolute (or start with ~)';
|
|
38
|
+
// Create the directory now so a bad path fails here, visibly, instead of
|
|
39
|
+
// at sync time.
|
|
40
|
+
try {
|
|
41
|
+
mkdirSync(syncPath, { recursive: true });
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
return `Cannot create ${syncPath}: ${errorMessage(err)}`;
|
|
45
|
+
}
|
|
46
|
+
const client = createImapClient({
|
|
47
|
+
host: values.host.trim(),
|
|
48
|
+
port,
|
|
49
|
+
tls: values.tls === 'yes',
|
|
50
|
+
username: values.username.trim(),
|
|
51
|
+
password: values.password,
|
|
52
|
+
});
|
|
53
|
+
try {
|
|
54
|
+
await client.connect();
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
return describeImapError(err, values.host.trim(), port);
|
|
58
|
+
}
|
|
59
|
+
await closeImapClient(client);
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
function accountFields(existing) {
|
|
63
|
+
return [
|
|
64
|
+
{ key: 'label', label: 'Label', kind: 'text', value: existing?.label ?? '' },
|
|
65
|
+
{ key: 'host', label: 'IMAP host', kind: 'text', value: existing?.host ?? '', hint: 'e.g. imap.gmail.com' },
|
|
66
|
+
{ key: 'port', label: 'Port', kind: 'number', value: String(existing?.port ?? 993) },
|
|
67
|
+
{ key: 'tls', label: 'TLS', kind: 'select', value: existing ? (existing.tls ? 'yes' : 'no') : 'yes', options: ['yes', 'no'] },
|
|
68
|
+
{ key: 'username', label: 'Username', kind: 'text', value: existing?.username ?? '' },
|
|
69
|
+
{ key: 'password', label: 'Password', kind: 'secret', value: existing?.password ?? '' },
|
|
70
|
+
{ key: 'syncPath', label: 'Sync path', kind: 'text', value: existing?.syncPath ?? '', hint: '~ is expanded; the directory is created if missing' },
|
|
71
|
+
];
|
|
72
|
+
}
|
|
73
|
+
// Runs the add/edit form; on verified submit, upserts the account and saves
|
|
74
|
+
// to disk immediately so a later crash can't lose it.
|
|
75
|
+
async function editAccount(config, index) {
|
|
76
|
+
const existing = index === null ? null : config.accounts[index];
|
|
77
|
+
const result = await runForm({
|
|
78
|
+
title: existing ? `Edit account: ${existing.label}` : 'Add account',
|
|
79
|
+
hint: FORM_FOOTER,
|
|
80
|
+
fields: accountFields(existing),
|
|
81
|
+
submitLabel: '',
|
|
82
|
+
verify: verifyAccount,
|
|
83
|
+
});
|
|
84
|
+
if (result === 'back')
|
|
85
|
+
return;
|
|
86
|
+
const account = {
|
|
87
|
+
id: existing?.id ?? randomUUID(),
|
|
88
|
+
label: result.label.trim(),
|
|
89
|
+
host: result.host.trim(),
|
|
90
|
+
port: parseInt(result.port, 10),
|
|
91
|
+
tls: result.tls === 'yes',
|
|
92
|
+
username: result.username.trim(),
|
|
93
|
+
password: result.password,
|
|
94
|
+
syncPath: expandTilde(result.syncPath.trim()),
|
|
95
|
+
};
|
|
96
|
+
if (index === null)
|
|
97
|
+
config.accounts.push(account);
|
|
98
|
+
else
|
|
99
|
+
config.accounts[index] = account;
|
|
100
|
+
saveConfig(config);
|
|
101
|
+
}
|
|
102
|
+
async function confirmDelete(config, index) {
|
|
103
|
+
const account = config.accounts[index];
|
|
104
|
+
const result = await pickFromMenu(`Delete "${account.label}"? Synced markdown files are not deleted.`, [
|
|
105
|
+
{ label: 'Cancel', value: 'cancel' }, // default row: a reflexive double-Enter is safe
|
|
106
|
+
{ label: 'Delete', value: 'delete' },
|
|
107
|
+
], '[↑/↓] select · [enter] confirm · [esc] cancel');
|
|
108
|
+
if (result.kind === 'pick' && result.value === 'delete') {
|
|
109
|
+
config.accounts.splice(index, 1);
|
|
110
|
+
saveConfig(config);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async function menuLoop(config) {
|
|
114
|
+
while (true) {
|
|
115
|
+
const options = [
|
|
116
|
+
...config.accounts.map((a, i) => ({
|
|
117
|
+
label: `${a.label} (${a.username} @ ${a.host})`,
|
|
118
|
+
value: `account:${i}`,
|
|
119
|
+
})),
|
|
120
|
+
{ label: 'Add account', value: 'add' },
|
|
121
|
+
{ label: 'Quit', value: 'quit' },
|
|
122
|
+
];
|
|
123
|
+
const result = await pickFromMenu('Accounts:', options, MENU_FOOTER, { enableCtrlX: true });
|
|
124
|
+
if (result.kind === 'escape')
|
|
125
|
+
return;
|
|
126
|
+
const accountIndex = result.value.startsWith('account:')
|
|
127
|
+
? parseInt(result.value.slice('account:'.length), 10)
|
|
128
|
+
: null;
|
|
129
|
+
if (result.kind === 'ctrl-x') {
|
|
130
|
+
if (accountIndex !== null)
|
|
131
|
+
await confirmDelete(config, accountIndex);
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (result.value === 'quit')
|
|
135
|
+
return;
|
|
136
|
+
if (result.value === 'add') {
|
|
137
|
+
await editAccount(config, null);
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (accountIndex !== null)
|
|
141
|
+
await editAccount(config, accountIndex);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async function main() {
|
|
145
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
146
|
+
console.error('`npm run auth` needs an interactive terminal.');
|
|
147
|
+
process.exitCode = 1;
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const config = loadConfig(); // before enterTui so a parse error prints normally
|
|
151
|
+
enterTui();
|
|
152
|
+
try {
|
|
153
|
+
await menuLoop(config);
|
|
154
|
+
}
|
|
155
|
+
finally {
|
|
156
|
+
exitTui();
|
|
157
|
+
}
|
|
158
|
+
const n = config.accounts.length;
|
|
159
|
+
console.log(`${n} account${n === 1 ? '' : 's'} configured.`);
|
|
160
|
+
console.log(dim(`Config: ${CONFIG_PATH}`));
|
|
161
|
+
console.log(dim('Run `npm start` to sync.'));
|
|
162
|
+
}
|
|
163
|
+
main().catch((err) => {
|
|
164
|
+
console.error(red(errorMessage(err)));
|
|
165
|
+
process.exitCode = 1;
|
|
166
|
+
});
|
|
167
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// `inbox-to-md <command>` — the npm bin entry. Dispatches to the same entry
|
|
3
|
+
// modules the npm scripts use:
|
|
4
|
+
//
|
|
5
|
+
// inbox-to-md auth manage IMAP accounts (interactive TUI)
|
|
6
|
+
// inbox-to-md sync [flags] sync inboxes to markdown (default command)
|
|
7
|
+
// inbox-to-md compact compact synced emails into one digest
|
|
8
|
+
// inbox-to-md archive <md dir> archive the emails behind the .md files
|
|
9
|
+
//
|
|
10
|
+
// Each entry module runs its own main() on import and reads its arguments
|
|
11
|
+
// from process.argv.slice(2), so the command token is spliced out before the
|
|
12
|
+
// dynamic import.
|
|
13
|
+
import { createRequire } from 'module';
|
|
14
|
+
const COMMANDS = {
|
|
15
|
+
sync: './index.js',
|
|
16
|
+
auth: './auth.js',
|
|
17
|
+
compact: './compact.js',
|
|
18
|
+
archive: './archive.js',
|
|
19
|
+
};
|
|
20
|
+
const USAGE = `Usage: inbox-to-md <command>
|
|
21
|
+
|
|
22
|
+
Commands:
|
|
23
|
+
auth manage IMAP accounts (interactive TUI)
|
|
24
|
+
sync [--since YYYY-MM-DD] [--force-rewrite]
|
|
25
|
+
sync inboxes to markdown (default command)
|
|
26
|
+
compact compact synced emails into one digest per account
|
|
27
|
+
archive <md dir> archive the emails behind the .md files in a directory
|
|
28
|
+
|
|
29
|
+
Options:
|
|
30
|
+
-h, --help show this help
|
|
31
|
+
-v, --version print the version`;
|
|
32
|
+
const arg = process.argv[2];
|
|
33
|
+
if (arg === '-h' || arg === '--help') {
|
|
34
|
+
console.log(USAGE);
|
|
35
|
+
}
|
|
36
|
+
else if (arg === '-v' || arg === '--version') {
|
|
37
|
+
const require = createRequire(import.meta.url);
|
|
38
|
+
console.log(require('../package.json').version);
|
|
39
|
+
}
|
|
40
|
+
else if (arg !== undefined && !arg.startsWith('-') && !(arg in COMMANDS)) {
|
|
41
|
+
console.error(`Unknown command "${arg}"\n\n${USAGE}`);
|
|
42
|
+
process.exitCode = 1;
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
// A known command is consumed; a flag or nothing falls through to `sync`
|
|
46
|
+
// with the arguments untouched.
|
|
47
|
+
const command = arg !== undefined && arg in COMMANDS ? arg : 'sync';
|
|
48
|
+
if (command === process.argv[2])
|
|
49
|
+
process.argv.splice(2, 1);
|
|
50
|
+
await import(COMMANDS[command]);
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,4EAA4E;AAC5E,+BAA+B;AAC/B,EAAE;AACF,gFAAgF;AAChF,oFAAoF;AACpF,+EAA+E;AAC/E,iFAAiF;AACjF,EAAE;AACF,0EAA0E;AAC1E,6EAA6E;AAC7E,kBAAkB;AAElB,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAEvC,MAAM,QAAQ,GAA2B;IACvC,IAAI,EAAE,YAAY;IAClB,IAAI,EAAE,WAAW;IACjB,OAAO,EAAE,cAAc;IACvB,OAAO,EAAE,cAAc;CACxB,CAAC;AAEF,MAAM,KAAK,GAAG;;;;;;;;;;;yCAW2B,CAAC;AAE1C,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAE5B,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,QAAQ,EAAE,CAAC;IACrC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AACrB,CAAC;KAAM,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,WAAW,EAAE,CAAC;IAC/C,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/C,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,OAAO,CAAC,CAAC;AAClD,CAAC;KAAM,IAAI,GAAG,KAAK,SAAS,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,QAAQ,CAAC,EAAE,CAAC;IAC3E,OAAO,CAAC,KAAK,CAAC,oBAAoB,GAAG,QAAQ,KAAK,EAAE,CAAC,CAAC;IACtD,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC;KAAM,CAAC;IACN,yEAAyE;IACzE,gCAAgC;IAChC,MAAM,OAAO,GAAG,GAAG,KAAK,SAAS,IAAI,GAAG,IAAI,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC;IACpE,IAAI,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC3D,MAAM,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;AAClC,CAAC"}
|