@leemour/cli-memo 0.0.0-stage → 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 +21 -0
- package/README.md +121 -2
- package/dist/app.d.ts +9 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +10 -0
- package/dist/app.js.map +1 -0
- package/dist/bin/memo.d.ts +3 -0
- package/dist/bin/memo.d.ts.map +1 -0
- package/dist/bin/memo.js +14 -0
- package/dist/bin/memo.js.map +1 -0
- package/dist/config.d.ts +25 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +20 -0
- package/dist/config.js.map +1 -0
- package/dist/context/command.d.ts +6 -0
- package/dist/context/command.d.ts.map +1 -0
- package/dist/context/command.js +56 -0
- package/dist/context/command.js.map +1 -0
- package/dist/context/context.d.ts +34 -0
- package/dist/context/context.d.ts.map +1 -0
- package/dist/context/context.js +50 -0
- package/dist/context/context.js.map +1 -0
- package/dist/mail/command.d.ts +4 -0
- package/dist/mail/command.d.ts.map +1 -0
- package/dist/mail/command.js +56 -0
- package/dist/mail/command.js.map +1 -0
- package/dist/mail/gmail.d.ts +14 -0
- package/dist/mail/gmail.d.ts.map +1 -0
- package/dist/mail/gmail.js +57 -0
- package/dist/mail/gmail.js.map +1 -0
- package/dist/mail/himalaya.d.ts +4 -0
- package/dist/mail/himalaya.d.ts.map +1 -0
- package/dist/mail/himalaya.js +13 -0
- package/dist/mail/himalaya.js.map +1 -0
- package/dist/mail/import.d.ts +29 -0
- package/dist/mail/import.d.ts.map +1 -0
- package/dist/mail/import.js +78 -0
- package/dist/mail/import.js.map +1 -0
- package/dist/mail/parse.d.ts +13 -0
- package/dist/mail/parse.d.ts.map +1 -0
- package/dist/mail/parse.js +53 -0
- package/dist/mail/parse.js.map +1 -0
- package/dist/notes/command.d.ts +4 -0
- package/dist/notes/command.d.ts.map +1 -0
- package/dist/notes/command.js +103 -0
- package/dist/notes/command.js.map +1 -0
- package/dist/notes/embed.d.ts +33 -0
- package/dist/notes/embed.d.ts.map +1 -0
- package/dist/notes/embed.js +74 -0
- package/dist/notes/embed.js.map +1 -0
- package/dist/notes/files.d.ts +8 -0
- package/dist/notes/files.d.ts.map +1 -0
- package/dist/notes/files.js +42 -0
- package/dist/notes/files.js.map +1 -0
- package/dist/notes/import.d.ts +24 -0
- package/dist/notes/import.d.ts.map +1 -0
- package/dist/notes/import.js +129 -0
- package/dist/notes/import.js.map +1 -0
- package/dist/notes/read.d.ts +31 -0
- package/dist/notes/read.d.ts.map +1 -0
- package/dist/notes/read.js +97 -0
- package/dist/notes/read.js.map +1 -0
- package/dist/notes/search.d.ts +43 -0
- package/dist/notes/search.d.ts.map +1 -0
- package/dist/notes/search.js +83 -0
- package/dist/notes/search.js.map +1 -0
- package/dist/notes/text.d.ts +3 -0
- package/dist/notes/text.d.ts.map +1 -0
- package/dist/notes/text.js +13 -0
- package/dist/notes/text.js.map +1 -0
- package/dist/options.d.ts +2 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +8 -0
- package/dist/options.js.map +1 -0
- package/dist/people/command.d.ts +5 -0
- package/dist/people/command.d.ts.map +1 -0
- package/dist/people/command.js +34 -0
- package/dist/people/command.js.map +1 -0
- package/dist/people/identity.d.ts +4 -0
- package/dist/people/identity.d.ts.map +1 -0
- package/dist/people/identity.js +13 -0
- package/dist/people/identity.js.map +1 -0
- package/dist/people/notes-map.d.ts +8 -0
- package/dist/people/notes-map.d.ts.map +1 -0
- package/dist/people/notes-map.js +8 -0
- package/dist/people/notes-map.js.map +1 -0
- package/dist/program.d.ts +7 -0
- package/dist/program.d.ts.map +1 -0
- package/dist/program.js +22 -0
- package/dist/program.js.map +1 -0
- package/dist/run/auto.d.ts +20 -0
- package/dist/run/auto.d.ts.map +1 -0
- package/dist/run/auto.js +139 -0
- package/dist/run/auto.js.map +1 -0
- package/dist/run/import.d.ts +18 -0
- package/dist/run/import.d.ts.map +1 -0
- package/dist/run/import.js +92 -0
- package/dist/run/import.js.map +1 -0
- package/dist/store/gone.d.ts +14 -0
- package/dist/store/gone.d.ts.map +1 -0
- package/dist/store/gone.js +48 -0
- package/dist/store/gone.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +2 -0
- package/dist/version.js.map +1 -0
- package/package.json +53 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Viacheslav Ptsarev
|
|
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
CHANGED
|
@@ -1,3 +1,122 @@
|
|
|
1
|
-
#
|
|
1
|
+
# cli-memo
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`memo` answers "what do I need to know about this person": who they are in each messenger, the
|
|
4
|
+
newest messages each way, the notes about them, and the newest mail threads — each item with a link
|
|
5
|
+
to its source.
|
|
6
|
+
|
|
7
|
+
It reads the local message store that [tg-cli](https://github.com/leemour/tg-cli) and
|
|
8
|
+
[max-cli](https://github.com/leemour/max-cli) share, Markdown notes such as an Obsidian vault, and mail
|
|
9
|
+
through [Himalaya](https://github.com/pimalaya/himalaya). Everything stays on your machine; nothing is
|
|
10
|
+
sent or changed at the source.
|
|
11
|
+
|
|
12
|
+
**Status:** early. Person context, notes search, Gmail import and timed incremental import work.
|
|
13
|
+
|
|
14
|
+
## Context about a person
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
memo context telegram:"Rin Example" # or max:<name or id>, email:<address>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
One answer from the shared store: every identity linked to the person (`tg|max contacts link`), the
|
|
21
|
+
last message each way, recent messages in direct chats and groups, chats in common — mail included once
|
|
22
|
+
an address is linked — then the note about them (`memo note`) and the stored notes naming them in full,
|
|
23
|
+
each with its locator. What gave nothing is listed with the reason. `--json` for agents, `--limit` for more.
|
|
24
|
+
|
|
25
|
+
## Notes
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
memo notes import # load the notes into the shared store; run again after edits
|
|
29
|
+
memo notes search "lighthouse budget" # notes by their words, and the people they name
|
|
30
|
+
memo notes about "Rin Example" # notes about a person, read straight from the folders
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`import` stores each Markdown or text note as one entry of provider `notes` in the shared store (the
|
|
34
|
+
folder is the account, each subfolder a chat), so `memo notes search`, `tg messages search "in:notes …"`
|
|
35
|
+
and agents find notes beside messages and mail. An edited note keeps its old text as a revision; a note
|
|
36
|
+
deleted from the folder loses its text on the next import.
|
|
37
|
+
|
|
38
|
+
`import` also builds each folder's notes for search and embeds them for search by meaning with the local
|
|
39
|
+
e5-small model (shared with tg and max: `tg models text download e5-small`), only what changed, at most
|
|
40
|
+
600 chunks a run — the next run continues. `--no-embed`, or `notes.embed: false` in the config, skips the
|
|
41
|
+
embedding. `search` ranks notes by meaning and by words together and says which found each; without the
|
|
42
|
+
model it searches words alone and says so. It then lists who they name: `[[Name]]` links, and people the
|
|
43
|
+
store knows from tg, MAX or mail whose full name appears in the text — by name only, so a guess.
|
|
44
|
+
|
|
45
|
+
`about` lists the notes *about* the person (file name or `aliases` in the front matter), the notes that
|
|
46
|
+
*link* them, and lines that only say the name (weak). Hidden folders such as `.obsidian` are skipped,
|
|
47
|
+
and nothing is ever written to a notes folder.
|
|
48
|
+
|
|
49
|
+
Folders and what to skip go in `~/.config/cli-memo/config.json`; `--folder` and `--ignore` add to them
|
|
50
|
+
for one run. An ignore rule is a path inside the folder — a file, or a folder and everything under it —
|
|
51
|
+
or a glob (`**/Private*`, `*.txt`):
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{ "notes": { "folders": ["/path/to/vault"], "ignore": ["Journal/Private.md", "Archive"] } }
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Keeping it current
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
memo import # notes and mail, only what changed since the last run
|
|
61
|
+
memo auto on --every 5m # run memo import on a systemd user timer (default 5m, a minute or more)
|
|
62
|
+
memo auto status # on/off, interval, next and last run
|
|
63
|
+
memo auto off # stop and remove the timer
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The config is the one source: `memo auto on/off` write `auto: { enabled, every }` to
|
|
67
|
+
`~/.config/cli-memo/config.json`, and a hand edit there takes effect at the next timer run, which
|
|
68
|
+
rewrites or removes its own timer to match. The timer runs `memo import` from the place it was set up
|
|
69
|
+
from, with the `PATH` of that shell, so Himalaya and the keyring helper are found.
|
|
70
|
+
|
|
71
|
+
`memo import` is incremental. A note whose size and change time are as last stored is not read; one
|
|
72
|
+
whose content hash is the same is not saved. Mail reads bodies only of messages not stored yet. A lock
|
|
73
|
+
keeps two imports from running at once, and one failing source does not stop the others.
|
|
74
|
+
|
|
75
|
+
## Mail
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
memo mail import --since 2026-09-01
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Reads a Gmail account's All Mail through [Himalaya](https://github.com/pimalaya/himalaya) into the
|
|
82
|
+
shared message store as provider `email`: one chat per Gmail thread, the mailbox address as the
|
|
83
|
+
account, senders and recipients as people. Messenger search and person context then see mail too.
|
|
84
|
+
|
|
85
|
+
- Read only: the mailbox is opened read-only, nothing is sent, and no mail is marked read.
|
|
86
|
+
- Run it again any time: what is stored is skipped; `--max` (default 200) bounds the new mails read
|
|
87
|
+
per run, and the next run continues.
|
|
88
|
+
- A mail deleted at the source loses its text in the store. The first two days of the window are left
|
|
89
|
+
alone, and a run that finds an unusually large share missing deletes nothing and says so. Mail moved
|
|
90
|
+
to Spam leaves All Mail and counts as deleted.
|
|
91
|
+
|
|
92
|
+
Set the account up in Himalaya first (an IMAP account with a Gmail
|
|
93
|
+
[app password](https://myaccount.google.com/apppasswords); `bin/mail-password <name>` stores it in the
|
|
94
|
+
keyring), then name it in `~/.config/cli-memo/config.json`:
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{ "mail": { "accounts": [{ "name": "gmail", "address": "you@example.com" }] } }
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## People
|
|
101
|
+
|
|
102
|
+
Who is who is decided in messaging: `tg contacts link <person> email:<address>` (or `max contacts link`)
|
|
103
|
+
records that a messenger identity and a mail address are one person, in the shared store. A same name
|
|
104
|
+
is never enough. `memo` only adds the note that is about the person:
|
|
105
|
+
|
|
106
|
+
```sh
|
|
107
|
+
memo note telegram:"Rin Example" ~/Notes/people/Rin.md
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
An identity is `<messenger>:<name or id>`; a name two people share is refused, with their ids to
|
|
111
|
+
choose from. The note path lives in `~/.config/cli-memo/people-notes.json`.
|
|
112
|
+
|
|
113
|
+
## Development
|
|
114
|
+
|
|
115
|
+
```sh
|
|
116
|
+
pnpm install
|
|
117
|
+
pnpm lint && pnpm typecheck && pnpm test
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## License
|
|
121
|
+
|
|
122
|
+
MIT
|
package/dist/app.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAEA,oFAAoF;AACpF,eAAO,MAAM,GAAG;;;;;;CAMf,CAAA"}
|
package/dist/app.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { VERSION } from "./version.js";
|
|
2
|
+
/** How memo names itself to the shared services: in their hints and their files. */
|
|
3
|
+
export const APP = {
|
|
4
|
+
command: "memo",
|
|
5
|
+
appName: "cli-memo",
|
|
6
|
+
envPrefix: "MEMO",
|
|
7
|
+
description: "Context about people across messengers, notes and mail",
|
|
8
|
+
version: VERSION,
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=app.js.map
|
package/dist/app.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAEtC,oFAAoF;AACpF,MAAM,CAAC,MAAM,GAAG,GAAG;IACjB,OAAO,EAAE,MAAM;IACf,OAAO,EAAE,UAAU;IACnB,SAAS,EAAE,MAAM;IACjB,WAAW,EAAE,wDAAwD;IACrE,OAAO,EAAE,OAAO;CACjB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memo.d.ts","sourceRoot":"","sources":["../../src/bin/memo.ts"],"names":[],"mappings":""}
|
package/dist/bin/memo.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { exitCodeFor, GENERIC_FAILURE, isCliError } from "@leemour/cli-core";
|
|
3
|
+
import { ensureSqlite } from "@leemour/cli-messaging/sqlite-runtime";
|
|
4
|
+
await ensureSqlite();
|
|
5
|
+
// A static import would load the store's SQLite before ensureSqlite could swap it.
|
|
6
|
+
const { createProgram } = await import("../program.js");
|
|
7
|
+
try {
|
|
8
|
+
await createProgram().parseAsync(process.argv);
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
process.stderr.write(`memo: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
12
|
+
process.exitCode = isCliError(error) ? exitCodeFor(error.code) : GENERIC_FAILURE;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=memo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memo.js","sourceRoot":"","sources":["../../src/bin/memo.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAC5E,OAAO,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAA;AAEpE,MAAM,YAAY,EAAE,CAAA;AACpB,mFAAmF;AACnF,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,CAAA;AACvD,IAAI,CAAC;IACH,MAAM,aAAa,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;AAChD,CAAC;AAAC,OAAO,KAAK,EAAE,CAAC;IACf,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IACzF,OAAO,CAAC,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,eAAe,CAAA;AAClF,CAAC"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
declare const ConfigSchema: v.ObjectSchema<{
|
|
3
|
+
readonly notes: v.OptionalSchema<v.ObjectSchema<{
|
|
4
|
+
readonly folders: v.ArraySchema<v.StringSchema<undefined>, undefined>;
|
|
5
|
+
readonly ignore: v.OptionalSchema<v.ArraySchema<v.StringSchema<undefined>, undefined>, undefined>;
|
|
6
|
+
readonly embed: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
|
|
7
|
+
}, undefined>, undefined>;
|
|
8
|
+
readonly auto: v.OptionalSchema<v.ObjectSchema<{
|
|
9
|
+
readonly enabled: v.OptionalSchema<v.BooleanSchema<undefined>, undefined>;
|
|
10
|
+
readonly every: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
11
|
+
}, undefined>, undefined>;
|
|
12
|
+
readonly mail: v.OptionalSchema<v.ObjectSchema<{
|
|
13
|
+
readonly accounts: v.ArraySchema<v.ObjectSchema<{
|
|
14
|
+
readonly name: v.StringSchema<undefined>;
|
|
15
|
+
readonly address: v.StringSchema<undefined>;
|
|
16
|
+
}, undefined>, undefined>;
|
|
17
|
+
}, undefined>, undefined>;
|
|
18
|
+
}, undefined>;
|
|
19
|
+
export type Config = v.InferOutput<typeof ConfigSchema>;
|
|
20
|
+
export declare const configPath: (env?: NodeJS.ProcessEnv) => string;
|
|
21
|
+
export declare const loadConfig: (env?: NodeJS.ProcessEnv) => Config;
|
|
22
|
+
/** Changes one part of the config and keeps the rest as written. */
|
|
23
|
+
export declare const updateConfig: (change: (config: Config) => Config, env?: NodeJS.ProcessEnv) => Config;
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,CAAC,MAAM,SAAS,CAAA;AAE5B,QAAA,MAAM,YAAY;;;;;;;;;;;;;;;;aAUhB,CAAA;AAEF,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,WAAW,CAAC,OAAO,YAAY,CAAC,CAAA;AAEvD,eAAO,MAAM,UAAU,SAAS,MAAM,CAAC,UAAU,KAAiB,MACiB,CAAA;AAEnF,eAAO,MAAM,UAAU,SAAS,MAAM,CAAC,UAAU,KAAiB,MACP,CAAA;AAE3D,oEAAoE;AACpE,eAAO,MAAM,YAAY,WAAY,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,QAAO,MAAM,CAAC,UAAU,KAAiB,MAIvG,CAAA"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { configFilePath, loadConfigFile, resolvePaths, saveConfigFile } from "@leemour/cli-core";
|
|
2
|
+
import * as v from "valibot";
|
|
3
|
+
const ConfigSchema = v.object({
|
|
4
|
+
notes: v.optional(v.object({
|
|
5
|
+
folders: v.array(v.string()),
|
|
6
|
+
ignore: v.optional(v.array(v.string())),
|
|
7
|
+
embed: v.optional(v.boolean()),
|
|
8
|
+
})),
|
|
9
|
+
auto: v.optional(v.object({ enabled: v.optional(v.boolean()), every: v.optional(v.string()) })),
|
|
10
|
+
mail: v.optional(v.object({ accounts: v.array(v.object({ name: v.string(), address: v.string() })) })),
|
|
11
|
+
});
|
|
12
|
+
export const configPath = (env = process.env) => configFilePath(resolvePaths({ appName: "cli-memo", prefix: "MEMO", env }).config);
|
|
13
|
+
export const loadConfig = (env = process.env) => loadConfigFile(configPath(env), ConfigSchema, () => ({}));
|
|
14
|
+
/** Changes one part of the config and keeps the rest as written. */
|
|
15
|
+
export const updateConfig = (change, env = process.env) => {
|
|
16
|
+
const next = change(loadConfig(env));
|
|
17
|
+
saveConfigFile(configPath(env), next);
|
|
18
|
+
return next;
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAChG,OAAO,KAAK,CAAC,MAAM,SAAS,CAAA;AAE5B,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC;IAC5B,KAAK,EAAE,CAAC,CAAC,QAAQ,CACf,CAAC,CAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;QAC5B,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;QACvC,KAAK,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;KAC/B,CAAC,CACH;IACD,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;IAC/F,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;CACvG,CAAC,CAAA;AAIF,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,GAAG,GAAsB,OAAO,CAAC,GAAG,EAAU,EAAE,CACzE,cAAc,CAAC,YAAY,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,CAAA;AAEnF,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,GAAG,GAAsB,OAAO,CAAC,GAAG,EAAU,EAAE,CACzE,cAAc,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,YAAY,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;AAE3D,oEAAoE;AACpE,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,MAAkC,EAAE,GAAG,GAAsB,OAAO,CAAC,GAAG,EAAU,EAAE;IAC/G,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAA;IACpC,cAAc,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAA;IACrC,OAAO,IAAI,CAAA;AACb,CAAC,CAAA"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type Streams } from "@leemour/cli-core";
|
|
2
|
+
import type { Command } from "commander";
|
|
3
|
+
import { type MemoContext } from "./context.js";
|
|
4
|
+
export declare const contextText: ({ messages, notes, notRead }: MemoContext) => string;
|
|
5
|
+
export declare const contextCommand: (program: Command, streams: Streams, env: NodeJS.ProcessEnv) => void;
|
|
6
|
+
//# sourceMappingURL=command.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../src/context/command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAGhE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAGxC,OAAO,EAAE,KAAK,WAAW,EAAe,MAAM,cAAc,CAAA;AAU5D,eAAO,MAAM,WAAW,iCAAkC,WAAW,KAAG,MAuBvE,CAAA;AAED,eAAO,MAAM,cAAc,YAAa,OAAO,WAAW,OAAO,OAAO,MAAM,CAAC,UAAU,KAAG,IAiB3F,CAAA"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { createRenderer } from "@leemour/cli-core";
|
|
2
|
+
import { openStore } from "@leemour/cli-messaging/store";
|
|
3
|
+
import { positive } from "../options.js";
|
|
4
|
+
import { loadNotesMap } from "../people/notes-map.js";
|
|
5
|
+
import { memoContext } from "./context.js";
|
|
6
|
+
const MAX_TEXT = 160;
|
|
7
|
+
const one = (message) => message === null
|
|
8
|
+
? "—"
|
|
9
|
+
: `${message.timestamp.slice(0, 16).replace("T", " ")} ${message.provider} ${message.chatTitle ?? message.chatId}: ` +
|
|
10
|
+
message.text.replace(/\s+/g, " ").slice(0, MAX_TEXT);
|
|
11
|
+
export const contextText = ({ messages, notes, notRead }) => {
|
|
12
|
+
const { person, last, recent, shared } = messages;
|
|
13
|
+
return [
|
|
14
|
+
`${person.name ?? person.uid}`,
|
|
15
|
+
...person.identities.map(({ provider, id, name }) => ` ${provider}:${id} ${name ?? ""}`),
|
|
16
|
+
"",
|
|
17
|
+
`Last from them ${one(last.fromThemAnywhere ?? last.fromThem)}`,
|
|
18
|
+
`Last from me ${one(last.fromMe)}`,
|
|
19
|
+
`In common ${shared.length} chats`,
|
|
20
|
+
...(recent.direct.length === 0 ? [] : ["", "Recent, direct", ...recent.direct.map((m) => ` ${one(m)}`)]),
|
|
21
|
+
...(recent.groups.length === 0 ? [] : ["", "Recent, in groups", ...recent.groups.map((m) => ` ${one(m)}`)]),
|
|
22
|
+
"",
|
|
23
|
+
`Note about them ${notes.about ?? "none — memo note <identity> <path>"}`,
|
|
24
|
+
...(notes.mentions.length === 0
|
|
25
|
+
? []
|
|
26
|
+
: [
|
|
27
|
+
"Notes naming them (by name — may be someone else)",
|
|
28
|
+
...notes.mentions.map(({ path, line }) => ` ${path} ${line}`),
|
|
29
|
+
...(notes.hasMore ? [" more — raise --limit"] : []),
|
|
30
|
+
]),
|
|
31
|
+
...(messages.complete ? [] : ["", `Not read in full: ${messages.notRead.length} chats — store fetch <chat>`]),
|
|
32
|
+
...notRead.map(({ source, reason }) => `Not read: ${source} — ${reason}`),
|
|
33
|
+
].join("\n");
|
|
34
|
+
};
|
|
35
|
+
export const contextCommand = (program, streams, env) => {
|
|
36
|
+
program
|
|
37
|
+
.command("context")
|
|
38
|
+
.description("What the store holds about a person — messages, mail and notes — with a link to each source")
|
|
39
|
+
.argument("<person>", "<messenger>:<name or id>, e.g. telegram:Ana or email:ana@example.com")
|
|
40
|
+
.option("--limit <n>", "most messages and notes of each kind", positive, 20)
|
|
41
|
+
.option("--json", "print JSON")
|
|
42
|
+
.action(async (person, options) => {
|
|
43
|
+
const store = await openStore({ env });
|
|
44
|
+
try {
|
|
45
|
+
const answer = await memoContext(store, person, { limit: options.limit, notesMap: loadNotesMap(env) });
|
|
46
|
+
if (options.json)
|
|
47
|
+
createRenderer({ format: "json", color: false, streams }).result(answer);
|
|
48
|
+
else
|
|
49
|
+
streams.data(`${contextText(answer)}\n`);
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
await store.close();
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
//# sourceMappingURL=command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command.js","sourceRoot":"","sources":["../../src/context/command.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type PersonContext } from "@leemour/cli-messaging/services";
|
|
2
|
+
import type { MessageStore } from "@leemour/cli-messaging/store";
|
|
3
|
+
import type { NotesMap } from "../people/notes-map.js";
|
|
4
|
+
export interface NoteMention {
|
|
5
|
+
locator: string;
|
|
6
|
+
path: string;
|
|
7
|
+
/** The first line naming them. */
|
|
8
|
+
line: string;
|
|
9
|
+
}
|
|
10
|
+
export interface MemoContext {
|
|
11
|
+
/** Messages and mail, from the store: every identity linked to the person, by id. */
|
|
12
|
+
messages: PersonContext;
|
|
13
|
+
notes: {
|
|
14
|
+
/** The note about them, as `memo note` named it. */
|
|
15
|
+
about: string | null;
|
|
16
|
+
/** Stored notes naming them in full — by name, so possibly someone else. */
|
|
17
|
+
mentions: NoteMention[];
|
|
18
|
+
hasMore: boolean;
|
|
19
|
+
};
|
|
20
|
+
/** Sources that gave nothing, and why. */
|
|
21
|
+
notRead: {
|
|
22
|
+
source: string;
|
|
23
|
+
reason: string;
|
|
24
|
+
}[];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* One answer about a person: what the store holds of them across messengers and mail (cli-messaging's
|
|
28
|
+
* person context), the note about them, and the notes that name them.
|
|
29
|
+
*/
|
|
30
|
+
export declare const memoContext: (store: MessageStore, reference: string, { limit, notesMap }?: {
|
|
31
|
+
limit?: number;
|
|
32
|
+
notesMap?: NotesMap;
|
|
33
|
+
}) => Promise<MemoContext>;
|
|
34
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/context/context.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,aAAa,EAAiB,MAAM,iCAAiC,CAAA;AACnF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAA;AAChE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAA;AAEtD,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,WAAW;IAC1B,qFAAqF;IACrF,QAAQ,EAAE,aAAa,CAAA;IACvB,KAAK,EAAE;QACL,oDAAoD;QACpD,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;QACpB,4EAA4E;QAC5E,QAAQ,EAAE,WAAW,EAAE,CAAA;QACvB,OAAO,EAAE,OAAO,CAAA;KACjB,CAAA;IACD,0CAA0C;IAC1C,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAC9C;AAQD;;;GAGG;AACH,eAAO,MAAM,WAAW,UACf,YAAY,aACR,MAAM,wBACc;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,QAAQ,CAAA;CAAE,KACrE,OAAO,CAAC,WAAW,CAyCrB,CAAA"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { CliError } from "@leemour/cli-core";
|
|
2
|
+
import { personContext } from "@leemour/cli-messaging/services";
|
|
3
|
+
const MAX_LINE = 200;
|
|
4
|
+
/** Shorter names, or a single word, match too much ordinary text to mean anyone. */
|
|
5
|
+
const MIN_NAME = 5;
|
|
6
|
+
const literal = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7
|
+
/**
|
|
8
|
+
* One answer about a person: what the store holds of them across messengers and mail (cli-messaging's
|
|
9
|
+
* person context), the note about them, and the notes that name them.
|
|
10
|
+
*/
|
|
11
|
+
export const memoContext = async (store, reference, { limit = 20, notesMap = {} } = {}) => {
|
|
12
|
+
const [, provider, who] = /^([a-z][a-z0-9-]*):(.+)$/.exec(reference.trim()) ?? [];
|
|
13
|
+
if (provider === undefined || who === undefined)
|
|
14
|
+
throw new CliError("validation_error", `"${reference}" names no messenger — write it as <messenger>:<name or id>`);
|
|
15
|
+
const accounts = await store.accounts();
|
|
16
|
+
const asked = accounts.find((account) => account.provider === provider);
|
|
17
|
+
if (asked === undefined)
|
|
18
|
+
throw new CliError("not_found", `the store holds no ${provider} account`);
|
|
19
|
+
const messages = await personContext(store, asked, who, { messages: limit });
|
|
20
|
+
const about = notesMap[messages.person.uid] ?? null;
|
|
21
|
+
const names = [
|
|
22
|
+
...new Set([messages.person.name, ...messages.person.identities.map(({ name }) => name)].map((name) => name?.trim())),
|
|
23
|
+
].filter((name) => !!name && name.length >= MIN_NAME && /\s/.test(name));
|
|
24
|
+
const notRead = [];
|
|
25
|
+
let mentions = [];
|
|
26
|
+
let hasMore = false;
|
|
27
|
+
if (!accounts.some((account) => account.provider === "notes")) {
|
|
28
|
+
notRead.push({ source: "notes", reason: "no notes imported — memo notes import" });
|
|
29
|
+
}
|
|
30
|
+
else if (names.length > 0) {
|
|
31
|
+
const pattern = new RegExp(`(?<![\\p{L}\\p{N}])(${names.map(literal).join("|")})(?![\\p{L}\\p{N}])`, "iu");
|
|
32
|
+
const found = await store.find({ provider: "notes", pattern, limit: limit + 1 });
|
|
33
|
+
hasMore = found.items.length > limit || found.hasMore;
|
|
34
|
+
mentions = found.items
|
|
35
|
+
.slice(0, limit)
|
|
36
|
+
.filter(({ id }) => about === null || !about.endsWith(`/${id}`))
|
|
37
|
+
.map((hit) => ({
|
|
38
|
+
locator: hit.locator,
|
|
39
|
+
path: hit.id,
|
|
40
|
+
line: (hit.text.split("\n").find((line) => pattern.test(line)) ?? "").trim().slice(0, MAX_LINE),
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
if (!messages.person.identities.some((identity) => identity.provider === "email"))
|
|
44
|
+
notRead.push({
|
|
45
|
+
source: "mail",
|
|
46
|
+
reason: "no mail address linked to them — tg contacts link <person> email:<address>",
|
|
47
|
+
});
|
|
48
|
+
return { messages, notes: { about, mentions, hasMore }, notRead };
|
|
49
|
+
};
|
|
50
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/context/context.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../src/mail/command.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,KAAK,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAE1E,OAAO,EAAE,KAAK,OAAO,EAAwB,MAAM,WAAW,CAAA;AAuB9D,eAAO,MAAM,WAAW,YAAa,OAAO,WAAW,OAAO,OAAO,MAAM,CAAC,UAAU,KAAG,IAoCxF,CAAA"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { CliError, createRenderer } from "@leemour/cli-core";
|
|
2
|
+
import { openStore } from "@leemour/cli-messaging/store";
|
|
3
|
+
import { InvalidArgumentError } from "commander";
|
|
4
|
+
import { loadConfig } from "../config.js";
|
|
5
|
+
import { positive } from "../options.js";
|
|
6
|
+
import { himalaya } from "./himalaya.js";
|
|
7
|
+
import { importMail } from "./import.js";
|
|
8
|
+
const DAY = 86_400_000;
|
|
9
|
+
const day = (value) => {
|
|
10
|
+
const date = new Date(`${value}T00:00:00Z`);
|
|
11
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(value) || Number.isNaN(date.getTime()))
|
|
12
|
+
throw new InvalidArgumentError("a date as YYYY-MM-DD");
|
|
13
|
+
return date;
|
|
14
|
+
};
|
|
15
|
+
const text = (result) => [
|
|
16
|
+
`${result.account}: ${result.listed} in All Mail since ${result.since.slice(0, 10)}, ` +
|
|
17
|
+
`${result.saved} saved, ${result.alreadyStored} already stored, ${result.deleted} marked deleted.`,
|
|
18
|
+
...(result.complete ? [] : ["Not every new message was read — run it again to continue."]),
|
|
19
|
+
...(result.deletionsSkipped === undefined ? [] : [`Deletions skipped: ${result.deletionsSkipped}`]),
|
|
20
|
+
].join("\n");
|
|
21
|
+
export const mailCommand = (program, streams, env) => {
|
|
22
|
+
const mail = program.command("mail").description("Mail, read through Himalaya into the shared message store");
|
|
23
|
+
mail
|
|
24
|
+
.command("import")
|
|
25
|
+
.description("Read a Gmail account's All Mail into the store, and drop the text of mail deleted at the source")
|
|
26
|
+
.option("--account <name>", "the account's name in Himalaya's config (default: the first in mail.accounts)")
|
|
27
|
+
.option("--since <date>", "first day to read, YYYY-MM-DD (default: 30 days ago)", day)
|
|
28
|
+
.option("--max <n>", "most new messages to read in this run", positive, 200)
|
|
29
|
+
.option("--json", "print JSON")
|
|
30
|
+
.action(async (options) => {
|
|
31
|
+
const accounts = loadConfig(env).mail?.accounts ?? [];
|
|
32
|
+
const account = options.account === undefined ? accounts[0] : accounts.find(({ name }) => name === options.account);
|
|
33
|
+
if (account === undefined)
|
|
34
|
+
throw new CliError("configuration_error", options.account === undefined
|
|
35
|
+
? "no mail account: add mail.accounts [{ name, address }] to the config"
|
|
36
|
+
: `no mail account "${options.account}" in mail.accounts of the config`);
|
|
37
|
+
const store = await openStore({ env });
|
|
38
|
+
try {
|
|
39
|
+
const result = await importMail({
|
|
40
|
+
store,
|
|
41
|
+
run: himalaya(env),
|
|
42
|
+
account,
|
|
43
|
+
since: options.since ?? new Date(Date.now() - 30 * DAY),
|
|
44
|
+
max: options.max,
|
|
45
|
+
});
|
|
46
|
+
if (options.json)
|
|
47
|
+
createRenderer({ format: "json", color: false, streams }).result(result);
|
|
48
|
+
else
|
|
49
|
+
streams.data(`${text(result)}\n`);
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
await store.close();
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
//# sourceMappingURL=command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command.js","sourceRoot":"","sources":["../../src/mail/command.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Himalaya } from "./himalaya.js";
|
|
2
|
+
export interface Listed {
|
|
3
|
+
uid: string;
|
|
4
|
+
/** Gmail's own ids: the same in every folder, and they survive a UID reset. */
|
|
5
|
+
messageId: string;
|
|
6
|
+
threadId: string;
|
|
7
|
+
receivedAt: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Every message in All Mail received since `since`, read with EXAMINE so nothing can change a flag.
|
|
11
|
+
* All Mail rather than INBOX: an archived message leaves INBOX but is not deleted.
|
|
12
|
+
*/
|
|
13
|
+
export declare const listAllMail: (run: Himalaya, account: string, since: Date) => Promise<Listed[]>;
|
|
14
|
+
//# sourceMappingURL=gmail.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gmail.d.ts","sourceRoot":"","sources":["../../src/mail/gmail.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAE7C,MAAM,WAAW,MAAM;IACrB,GAAG,EAAE,MAAM,CAAA;IACX,+EAA+E;IAC/E,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;CACnB;AAmCD;;;GAGG;AACH,eAAO,MAAM,WAAW,QAAe,QAAQ,WAAW,MAAM,SAAS,IAAI,KAAG,OAAO,CAAC,MAAM,EAAE,CAkC/F,CAAA"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { CliError } from "@leemour/cli-core";
|
|
2
|
+
const ALL_MAIL = '"[Gmail]/All Mail"';
|
|
3
|
+
const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
4
|
+
const FETCH_BATCH = 500;
|
|
5
|
+
const imapDate = (day) => `${day.getUTCDate()}-${MONTHS[day.getUTCMonth()]}-${day.getUTCFullYear()}`;
|
|
6
|
+
/** IMAP's `INTERNALDATE`: `"04-Oct-2026 19:51:09 +0000"`. */
|
|
7
|
+
const isoOf = (internal) => {
|
|
8
|
+
const match = /^\s*(\d{1,2})-(\w{3})-(\d{4}) (\d{2}):(\d{2}):(\d{2}) ([+-])(\d{2})(\d{2})$/.exec(internal);
|
|
9
|
+
if (match === null)
|
|
10
|
+
throw new CliError("invalid_response", `unexpected IMAP date "${internal}"`);
|
|
11
|
+
const [, day, month, year, hour, minute, second, sign, offsetHours, offsetMinutes] = match;
|
|
12
|
+
const local = Date.UTC(Number(year), MONTHS.indexOf(month ?? ""), Number(day), Number(hour), Number(minute), Number(second));
|
|
13
|
+
const offset = (Number(offsetHours) * 60 + Number(offsetMinutes)) * 60_000 * (sign === "-" ? -1 : 1);
|
|
14
|
+
return new Date(local - offset).toISOString();
|
|
15
|
+
};
|
|
16
|
+
/** A tagged NO or BAD comes back as output, not as an error, so every tag must end in OK. */
|
|
17
|
+
const assertCompleted = (output, tags) => {
|
|
18
|
+
for (const tag of tags) {
|
|
19
|
+
if (!new RegExp(`^${tag} OK`, "m").test(output)) {
|
|
20
|
+
const line = output.split(/\r?\n/).find((text) => text.startsWith(`${tag} `)) ?? "no answer";
|
|
21
|
+
throw new CliError("provider_error", `IMAP command ${tag} did not complete: ${line}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Every message in All Mail received since `since`, read with EXAMINE so nothing can change a flag.
|
|
27
|
+
* All Mail rather than INBOX: an archived message leaves INBOX but is not deleted.
|
|
28
|
+
*/
|
|
29
|
+
export const listAllMail = async (run, account, since) => {
|
|
30
|
+
const search = await run(["imap", "raw", "-a", account], `a EXAMINE ${ALL_MAIL}\r\nb UID SEARCH SINCE ${imapDate(since)}\r\n`);
|
|
31
|
+
assertCompleted(search, ["a", "b"]);
|
|
32
|
+
const uids = (/^\* SEARCH(.*)$/m.exec(search)?.[1] ?? "").trim().split(/\s+/).filter(Boolean);
|
|
33
|
+
if (uids.length === 0)
|
|
34
|
+
return [];
|
|
35
|
+
const batches = [];
|
|
36
|
+
for (let start = 0; start < uids.length; start += FETCH_BATCH)
|
|
37
|
+
batches.push(uids.slice(start, start + FETCH_BATCH).join(","));
|
|
38
|
+
const tags = batches.map((_, index) => `f${index}`);
|
|
39
|
+
const fetched = await run(["imap", "raw", "-a", account], `a EXAMINE ${ALL_MAIL}\r\n${batches
|
|
40
|
+
.map((set, index) => `${tags[index]} UID FETCH ${set} (UID X-GM-MSGID X-GM-THRID INTERNALDATE)\r\n`)
|
|
41
|
+
.join("")}`);
|
|
42
|
+
assertCompleted(fetched, ["a", ...tags]);
|
|
43
|
+
const listed = [...fetched.matchAll(/^\* \d+ FETCH \((.*)\)\s*$/gm)].map(([, items = ""]) => {
|
|
44
|
+
const item = (name) => new RegExp(`${name} (\\d+)`).exec(items)?.[1];
|
|
45
|
+
const internal = /INTERNALDATE "([^"]+)"/.exec(items)?.[1];
|
|
46
|
+
const uid = item("UID");
|
|
47
|
+
const messageId = item("X-GM-MSGID");
|
|
48
|
+
const threadId = item("X-GM-THRID");
|
|
49
|
+
if (!uid || !messageId || !threadId || !internal)
|
|
50
|
+
throw new CliError("invalid_response", `IMAP FETCH answer without the Gmail ids: ${items}`);
|
|
51
|
+
return { uid, messageId, threadId, receivedAt: isoOf(internal) };
|
|
52
|
+
});
|
|
53
|
+
if (listed.length !== uids.length)
|
|
54
|
+
throw new CliError("invalid_response", `IMAP listed ${uids.length} messages but fetched ${listed.length}`);
|
|
55
|
+
return listed;
|
|
56
|
+
};
|
|
57
|
+
//# sourceMappingURL=gmail.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gmail.js","sourceRoot":"","sources":["../../src/mail/gmail.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type Himalaya = (args: string[], input?: string) => Promise<string>;
|
|
2
|
+
/** `MEMO_HIMALAYA` names the program, so the test sandbox can point it at a fake. */
|
|
3
|
+
export declare const himalaya: (env?: NodeJS.ProcessEnv) => Himalaya;
|
|
4
|
+
//# sourceMappingURL=himalaya.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"himalaya.d.ts","sourceRoot":"","sources":["../../src/mail/himalaya.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,QAAQ,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;AAE1E,qFAAqF;AACrF,eAAO,MAAM,QAAQ,SACb,MAAM,CAAC,UAAU,KAAiB,QAgBpC,CAAA"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { CliError } from "@leemour/cli-core";
|
|
3
|
+
/** `MEMO_HIMALAYA` names the program, so the test sandbox can point it at a fake. */
|
|
4
|
+
export const himalaya = (env = process.env) => (args, input) => new Promise((resolve, reject) => {
|
|
5
|
+
const child = execFile(env.MEMO_HIMALAYA ?? "himalaya", args, { env, maxBuffer: 64 * 1024 * 1024 }, (error, stdout, stderr) => {
|
|
6
|
+
if (error === null)
|
|
7
|
+
resolve(stdout);
|
|
8
|
+
else
|
|
9
|
+
reject(new CliError("provider_error", `himalaya ${args[0] ?? ""} failed: ${stderr.trim() || error.message}`));
|
|
10
|
+
});
|
|
11
|
+
child.stdin?.end(input ?? "");
|
|
12
|
+
});
|
|
13
|
+
//# sourceMappingURL=himalaya.js.map
|