@wirecat/cli-core 0.514.0 → 0.515.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/CHANGELOG.md +12 -0
- package/dist/mail/index.d.ts +3 -0
- package/dist/mail/index.js +3 -0
- package/dist/mail/labels.d.ts +5 -0
- package/dist/mail/labels.js +27 -0
- package/dist/mail/progress.d.ts +10 -0
- package/dist/mail/progress.js +29 -0
- package/dist/mail/rule-stored.d.ts +9 -0
- package/dist/mail/rule-stored.js +57 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,18 @@ shape of this file.
|
|
|
7
7
|
|
|
8
8
|
## Unreleased
|
|
9
9
|
|
|
10
|
+
## 0.515.0 — 01.11.2026
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`storedMailRules(store, account, query?)`** reads one account's mail rules from the store in the shape
|
|
15
|
+
`syncMail` takes as `account.rules`, and `storedMailRule(stored)` reads one; a rule with a condition this build does
|
|
16
|
+
not know is left out. An account the store has not seen has no rules.
|
|
17
|
+
- **`labelName(raw)`, `systemLabel(name)` and `decodeMailboxName(name)`** name Gmail labels as people read them
|
|
18
|
+
(`\Inbox` and `Inbox` are one label, modified UTF-7 is decoded), moved from memo.
|
|
19
|
+
- **`mailProgress(store, key)`** gives each synced folder's window, done and total from the saved resume points,
|
|
20
|
+
without asking the mailbox. It treats only `not_found` as "no account yet" and passes other store errors on.
|
|
21
|
+
|
|
10
22
|
## 0.514.0 — 01.11.2026
|
|
11
23
|
|
|
12
24
|
### Fixed
|
package/dist/mail/index.d.ts
CHANGED
|
@@ -3,11 +3,14 @@ export { factsOf } from "./facts.js";
|
|
|
3
3
|
export { GMAIL_ALL_MAIL, mailFolders } from "./folders.js";
|
|
4
4
|
export { dropMissing, type Gone, tooManyGone } from "./gone.js";
|
|
5
5
|
export { type AuthVerdicts, HEADER_FIELDS, headersOf, type MailHeaders, mailHeadersOf } from "./headers.js";
|
|
6
|
+
export { decodeMailboxName, labelName, systemLabel } from "./labels.js";
|
|
6
7
|
export { type Address, htmlToText, looksLikeHtml, MAX_BODY_CHARS, type ParsedMail, parseMail, } from "./parse.js";
|
|
7
8
|
export { PRESET_NAMES, PRESETS, type PresetName, presetMatches } from "./presets.js";
|
|
8
9
|
export { domainsToAge, groupKey, type Probe, type ProbeGroup, probeMail, SUGGEST_RULE } from "./probe.js";
|
|
10
|
+
export { type MailFolderProgress, mailProgress } from "./progress.js";
|
|
9
11
|
export { type FileStamp, FULL_PASS_MS, MBOX_RESUME, type MboxPoint, mergeRanges, parsePoint, type Ranges, type ResumePoint, rangesOf, resumeKey, rulesKey, within, } from "./resume.js";
|
|
10
12
|
export { ruleInput, STORE_ACTION } from "./rule-input.js";
|
|
13
|
+
export { storedMailRule, storedMailRules } from "./rule-stored.js";
|
|
11
14
|
export { mapProposedTags, ruleTagger, settleRuleTags, type TagOutcome } from "./rule-tags.js";
|
|
12
15
|
export { conditionKeys, decide, describeMatch, ENVELOPE, envelopeBox, envelopeBoxOf, type MailAction, type MailFacts, type MailRule, type MailRuleMatch, matches, sameMatch, tagsOf, } from "./rules.js";
|
|
13
16
|
export { type DomainAge, FRAUD_SIGNALS, FREE_MAIL, registrableOf, SCORE_RULE, type Scored, type Signal, scoreGroup, WEIGHTS, } from "./score.js";
|
package/dist/mail/index.js
CHANGED
|
@@ -3,11 +3,14 @@ export { factsOf } from "./facts.js";
|
|
|
3
3
|
export { GMAIL_ALL_MAIL, mailFolders } from "./folders.js";
|
|
4
4
|
export { dropMissing, tooManyGone } from "./gone.js";
|
|
5
5
|
export { HEADER_FIELDS, headersOf, mailHeadersOf } from "./headers.js";
|
|
6
|
+
export { decodeMailboxName, labelName, systemLabel } from "./labels.js";
|
|
6
7
|
export { htmlToText, looksLikeHtml, MAX_BODY_CHARS, parseMail, } from "./parse.js";
|
|
7
8
|
export { PRESET_NAMES, PRESETS, presetMatches } from "./presets.js";
|
|
8
9
|
export { domainsToAge, groupKey, probeMail, SUGGEST_RULE } from "./probe.js";
|
|
10
|
+
export { mailProgress } from "./progress.js";
|
|
9
11
|
export { FULL_PASS_MS, MBOX_RESUME, mergeRanges, parsePoint, rangesOf, resumeKey, rulesKey, within, } from "./resume.js";
|
|
10
12
|
export { ruleInput, STORE_ACTION } from "./rule-input.js";
|
|
13
|
+
export { storedMailRule, storedMailRules } from "./rule-stored.js";
|
|
11
14
|
export { mapProposedTags, ruleTagger, settleRuleTags } from "./rule-tags.js";
|
|
12
15
|
export { conditionKeys, decide, describeMatch, ENVELOPE, envelopeBox, envelopeBoxOf, matches, sameMatch, tagsOf, } from "./rules.js";
|
|
13
16
|
export { FRAUD_SIGNALS, FREE_MAIL, registrableOf, SCORE_RULE, scoreGroup, WEIGHTS, } from "./score.js";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** IMAP's modified UTF-7 (RFC 3501 §5.1.3): `&` opens base64 of UTF-16BE, `,` stands for `/`, `-` closes. */
|
|
2
|
+
export declare const decodeMailboxName: (name: string) => string;
|
|
3
|
+
export declare const systemLabel: (name: string) => string;
|
|
4
|
+
/** A label as people read it: `\\Important` and `Important` are one label, and names are decoded. */
|
|
5
|
+
export declare const labelName: (raw: string) => string;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const SYSTEM_LABELS = {
|
|
2
|
+
inbox: "Inbox",
|
|
3
|
+
sent: "Sent",
|
|
4
|
+
important: "Important",
|
|
5
|
+
starred: "Starred",
|
|
6
|
+
draft: "Drafts",
|
|
7
|
+
drafts: "Drafts",
|
|
8
|
+
spam: "Spam",
|
|
9
|
+
trash: "Trash",
|
|
10
|
+
junk: "Spam",
|
|
11
|
+
};
|
|
12
|
+
/** IMAP's modified UTF-7 (RFC 3501 §5.1.3): `&` opens base64 of UTF-16BE, `,` stands for `/`, `-` closes. */
|
|
13
|
+
export const decodeMailboxName = (name) => name.replace(/&([^-]*)-/g, (_, run) => {
|
|
14
|
+
if (run === "")
|
|
15
|
+
return "&";
|
|
16
|
+
const bytes = Buffer.from(run.replace(/,/g, "/"), "base64");
|
|
17
|
+
let text = "";
|
|
18
|
+
for (let at = 0; at + 1 < bytes.length; at += 2)
|
|
19
|
+
text += String.fromCharCode(bytes.readUInt16BE(at));
|
|
20
|
+
return text;
|
|
21
|
+
});
|
|
22
|
+
export const systemLabel = (name) => SYSTEM_LABELS[name.toLowerCase()] ?? name;
|
|
23
|
+
/** A label as people read it: `\\Important` and `Important` are one label, and names are decoded. */
|
|
24
|
+
export const labelName = (raw) => {
|
|
25
|
+
const system = raw.startsWith("\\") ? raw.slice(1) : raw;
|
|
26
|
+
return SYSTEM_LABELS[system.toLowerCase()] ?? decodeMailboxName(system);
|
|
27
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AccountKey, MessageStore } from "../store/index.js";
|
|
2
|
+
export interface MailFolderProgress {
|
|
3
|
+
folder: string;
|
|
4
|
+
since: string;
|
|
5
|
+
done: number;
|
|
6
|
+
total: number;
|
|
7
|
+
updatedAt: string;
|
|
8
|
+
}
|
|
9
|
+
/** How far the sync of each stored folder is, from the points the runs saved; the mailbox is not asked. */
|
|
10
|
+
export declare const mailProgress: (store: MessageStore, key: AccountKey) => Promise<MailFolderProgress[]>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { CliError } from "@wirecat/cli-base";
|
|
2
|
+
import { parsePoint, resumeKey } from "./resume.js";
|
|
3
|
+
/** How far the sync of each stored folder is, from the points the runs saved; the mailbox is not asked. */
|
|
4
|
+
export const mailProgress = async (store, key) => {
|
|
5
|
+
let accountId;
|
|
6
|
+
try {
|
|
7
|
+
accountId = (await store.accounts.storedAccount(key)).id;
|
|
8
|
+
}
|
|
9
|
+
catch (error) {
|
|
10
|
+
if (error instanceof CliError && error.code === "not_found")
|
|
11
|
+
return [];
|
|
12
|
+
throw error;
|
|
13
|
+
}
|
|
14
|
+
const folders = [];
|
|
15
|
+
for (const { kind, externalId: folder } of await store.mail.mailboxes(accountId)) {
|
|
16
|
+
if (kind !== null)
|
|
17
|
+
continue;
|
|
18
|
+
const point = parsePoint((await store.sync.syncState(key, resumeKey(folder)))?.value);
|
|
19
|
+
if (point !== undefined)
|
|
20
|
+
folders.push({
|
|
21
|
+
folder,
|
|
22
|
+
since: point.window.since,
|
|
23
|
+
done: point.window.done,
|
|
24
|
+
total: point.window.total,
|
|
25
|
+
updatedAt: point.updatedAt,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return folders;
|
|
29
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { QueryAst } from "@wirecat/cli-base/query";
|
|
2
|
+
import type { MessageStore, MailRule as StoredRule } from "../store/index.js";
|
|
3
|
+
import type { MailRule } from "./rules.js";
|
|
4
|
+
/** A stored rule as the sync reads it; `undefined` for one this build cannot read, so it is left to the build that can. */
|
|
5
|
+
export declare const storedMailRule: (stored: StoredRule) => MailRule | undefined;
|
|
6
|
+
/** One account's rules from the store, for `account.rules` of the sync; none when the store has not seen the account. */
|
|
7
|
+
export declare const storedMailRules: (store: MessageStore, account: {
|
|
8
|
+
address: string;
|
|
9
|
+
}, query?: QueryAst) => Promise<MailRule[]>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { CliError } from "@wirecat/cli-base";
|
|
2
|
+
import { PRESET_NAMES } from "./presets.js";
|
|
3
|
+
const ACTION_OF = { full: "keep", envelope: "envelope", skip: "skip" };
|
|
4
|
+
const KINDS = {
|
|
5
|
+
from: "string",
|
|
6
|
+
from_contains: "string",
|
|
7
|
+
domain: "string",
|
|
8
|
+
to: "string",
|
|
9
|
+
list_id: "string",
|
|
10
|
+
subject: "string",
|
|
11
|
+
older_than_days: "number",
|
|
12
|
+
larger_than_bytes: "number",
|
|
13
|
+
has_attachments: "boolean",
|
|
14
|
+
label: "string",
|
|
15
|
+
unread: "boolean",
|
|
16
|
+
bulk: "boolean",
|
|
17
|
+
score_at_least: "number",
|
|
18
|
+
preset: "string",
|
|
19
|
+
};
|
|
20
|
+
/** A stored rule as the sync reads it; `undefined` for one this build cannot read, so it is left to the build that can. */
|
|
21
|
+
export const storedMailRule = (stored) => {
|
|
22
|
+
const action = ACTION_OF[String(stored.actions.store)];
|
|
23
|
+
const entries = Object.entries(stored.conditions);
|
|
24
|
+
const known = entries.every(([key, value]) => key in KINDS &&
|
|
25
|
+
typeof value === KINDS[key] &&
|
|
26
|
+
!(key === "preset" && !PRESET_NAMES.includes(value)));
|
|
27
|
+
if (action === undefined || !known || entries.length === 0)
|
|
28
|
+
return undefined;
|
|
29
|
+
const tags = Array.isArray(stored.actions.tags) ? stored.actions.tags.filter((tag) => typeof tag === "string") : [];
|
|
30
|
+
const skipped = stored.actions.tags_skipped;
|
|
31
|
+
const tagsSkipped = skipped !== null && typeof skipped === "object" && !Array.isArray(skipped)
|
|
32
|
+
? Object.fromEntries(Object.entries(skipped).filter(([, why]) => typeof why === "string"))
|
|
33
|
+
: {};
|
|
34
|
+
return {
|
|
35
|
+
id: stored.id,
|
|
36
|
+
match: Object.fromEntries(entries),
|
|
37
|
+
action,
|
|
38
|
+
...(tags.length ? { tags: tags } : {}),
|
|
39
|
+
...(Object.keys(tagsSkipped).length ? { tags_skipped: tagsSkipped } : {}),
|
|
40
|
+
...(stored.reason === null ? {} : { reason: stored.reason }),
|
|
41
|
+
...(stored.protected ? { protected: true } : {}),
|
|
42
|
+
...(stored.source === "owner" ? {} : { source: stored.source }),
|
|
43
|
+
decided_at: new Date(stored.decidedAt).toISOString(),
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
/** One account's rules from the store, for `account.rules` of the sync; none when the store has not seen the account. */
|
|
47
|
+
export const storedMailRules = async (store, account, query) => {
|
|
48
|
+
try {
|
|
49
|
+
const { id } = await store.accounts.storedAccount({ provider: "email", account: account.address.toLowerCase() });
|
|
50
|
+
return (await store.mail.rules.list(id, query)).flatMap((stored) => storedMailRule(stored) ?? []);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
if (error instanceof CliError && error.code === "not_found")
|
|
54
|
+
return [];
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wirecat/cli-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.515.0",
|
|
4
4
|
"description": "The messenger-neutral half of a messaging CLI: domain model, message locators, rendering, name resolution, the SQLite seam, the command skeleton and shared read commands, the send guard, run records and the message store",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Viacheslav Ptsarev",
|