@bobfrankston/mailx-settings 0.1.16 → 0.1.18
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/cloud.d.ts.map +1 -1
- package/cloud.js +22 -4
- package/docs/accounts.md +1 -1
- package/docs/multi-view.md +81 -0
- package/docs/search.md +5 -1
- package/index.d.ts +7 -0
- package/index.d.ts.map +1 -1
- package/index.js +38 -1
- package/package.json +3 -3
package/cloud.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloud.d.ts","sourceRoot":"","sources":["cloud.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AA0JH;;;;;;;;;oDASoD;AACpD,wBAAsB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CA8BnF;AAcD;;;;;;;;;;;;;;;;;6CAiB6C;AAC7C,wBAAsB,wBAAwB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAwBvE;
|
|
1
|
+
{"version":3,"file":"cloud.d.ts","sourceRoot":"","sources":["cloud.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AA0JH;;;;;;;;;oDASoD;AACpD,wBAAsB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CA8BnF;AAcD;;;;;;;;;;;;;;;;;6CAiB6C;AAC7C,wBAAsB,wBAAwB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAwBvE;AA6FD,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;AAE1D,MAAM,WAAW,SAAS;IACtB,kFAAkF;IAClF,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC/C,uGAAuG;IACvG,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,8BAA8B;IAC9B,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC9C;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAuBtF;AAED;;;;2DAI2D;AAC3D,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAoBvG"}
|
package/cloud.js
CHANGED
|
@@ -278,6 +278,22 @@ export async function gDriveFindOrCreateFolder() {
|
|
|
278
278
|
return null;
|
|
279
279
|
}
|
|
280
280
|
}
|
|
281
|
+
/** Google Drive allows two files with the SAME name in one folder (a sync
|
|
282
|
+
* conflict or a double-create produces them; the desktop mount can only
|
|
283
|
+
* show one, so the user never sees the stray). A blind `files[0]` then
|
|
284
|
+
* picks arbitrarily — and reading an empty duplicate over a real 1.5 MB
|
|
285
|
+
* config file silently wipes contacts/accounts. Pick the LARGEST match (an
|
|
286
|
+
* empty stub must never beat the real file) and warn so the dup gets
|
|
287
|
+
* cleaned up. */
|
|
288
|
+
function pickDriveFile(files, fileName) {
|
|
289
|
+
if (!files || files.length === 0)
|
|
290
|
+
return undefined;
|
|
291
|
+
if (files.length === 1)
|
|
292
|
+
return files[0].id;
|
|
293
|
+
const sorted = [...files].sort((a, b) => parseInt(b.size || "0", 10) - parseInt(a.size || "0", 10));
|
|
294
|
+
console.warn(` [cloud] ${files.length} files named '${fileName}' in the folder — using the largest (${sorted[0].size || "?"} bytes). DELETE the duplicate(s) in Google Drive — ids: ${sorted.slice(1).map(f => f.id).join(", ")}`);
|
|
295
|
+
return sorted[0].id;
|
|
296
|
+
}
|
|
281
297
|
/** Read a file by name from a folder (by ID) */
|
|
282
298
|
async function gDriveReadFromFolder(folderId, fileName) {
|
|
283
299
|
const token = await getGoogleDriveToken();
|
|
@@ -288,7 +304,7 @@ async function gDriveReadFromFolder(folderId, fileName) {
|
|
|
288
304
|
try {
|
|
289
305
|
// Find file in folder
|
|
290
306
|
const query = `name='${fileName}' and '${folderId}' in parents and trashed=false`;
|
|
291
|
-
const res = await fetch(`https://www.googleapis.com/drive/v3/files?q=${encodeURIComponent(query)}&fields=files(id)`, {
|
|
307
|
+
const res = await fetch(`https://www.googleapis.com/drive/v3/files?q=${encodeURIComponent(query)}&fields=files(id,name,size)`, {
|
|
292
308
|
headers: { Authorization: `Bearer ${token}` },
|
|
293
309
|
});
|
|
294
310
|
if (!res.ok) {
|
|
@@ -296,7 +312,7 @@ async function gDriveReadFromFolder(folderId, fileName) {
|
|
|
296
312
|
return null;
|
|
297
313
|
}
|
|
298
314
|
const data = await res.json();
|
|
299
|
-
const fileId = data.files
|
|
315
|
+
const fileId = pickDriveFile(data.files, fileName);
|
|
300
316
|
if (!fileId)
|
|
301
317
|
return null;
|
|
302
318
|
// Download content
|
|
@@ -322,7 +338,7 @@ async function gDriveWriteToFolder(folderId, fileName, content) {
|
|
|
322
338
|
throw new Error("Google Drive: no auth token (OAuth not granted or expired)");
|
|
323
339
|
// Check if file exists in folder
|
|
324
340
|
const query = `name='${fileName}' and '${folderId}' in parents and trashed=false`;
|
|
325
|
-
const findRes = await fetch(`https://www.googleapis.com/drive/v3/files?q=${encodeURIComponent(query)}&fields=files(id)`, {
|
|
341
|
+
const findRes = await fetch(`https://www.googleapis.com/drive/v3/files?q=${encodeURIComponent(query)}&fields=files(id,name,size)`, {
|
|
326
342
|
headers: { Authorization: `Bearer ${token}` },
|
|
327
343
|
});
|
|
328
344
|
if (!findRes.ok) {
|
|
@@ -330,7 +346,9 @@ async function gDriveWriteToFolder(folderId, fileName, content) {
|
|
|
330
346
|
throw new Error(`Google Drive: lookup '${fileName}' failed (${findRes.status} ${findRes.statusText}) ${body.slice(0, 200)}`);
|
|
331
347
|
}
|
|
332
348
|
const findData = await findRes.json();
|
|
333
|
-
|
|
349
|
+
// Write to the SAME file a read would pick (largest), so read/write stay
|
|
350
|
+
// consistent when a duplicate exists.
|
|
351
|
+
const existingId = pickDriveFile(findData.files, fileName);
|
|
334
352
|
if (existingId) {
|
|
335
353
|
// Update existing file
|
|
336
354
|
const res = await fetch(`https://www.googleapis.com/upload/drive/v3/files/${existingId}?uploadType=media`, {
|
package/docs/accounts.md
CHANGED
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
- **imap / smtp** — explicit server config. Omit for known providers (Gmail / Outlook / Yahoo / iCloud / Google Workspace are detected from the email domain via MX records).
|
|
48
48
|
- **auth** — `"password"` for traditional IMAP/SMTP, `"oauth2"` for Gmail/Google Workspace/Outlook.
|
|
49
49
|
- **enabled** — set `false` to keep the account record but skip sync at startup.
|
|
50
|
-
- **identityDomains** —
|
|
50
|
+
- **identityDomains** — domains you treat as "yours" for Reply-From detection. When you Reply, rmfmail looks at the original message's To/Cc; if any address is on one of these domains (exact match OR any subdomain), Reply uses that address as the From. Default if omitted is just the domain of `email` above. Matching is case-insensitive. Subdomain folding is automatic — listing both `frankston.com` and `bobf.frankston.com` is the same as listing just `frankston.com` (the longer entry is dropped at load time). Different domains (`bobfrankston.com` vs `frankston.com` — distinct DNS, no subsumption) stay separate. Example: `"identityDomains": ["bob.ma", "bobfrankston.com", "frankston.com"]`.
|
|
51
51
|
- **sig** — per-account signature for *new* messages (skipped on reply / forward / draft-resume). `text` is the plain-text body, HTML-escaped at insertion with `\n` → `<br>`. The optional `html` flag is reserved for "trust as raw HTML"; leave it `false` (or omit) for now. Specify either `sig` or `signature`, not both — `sig` wins if both are present and the message is new. May also be specified at the **file level** (alongside `name`) to apply across every account that doesn't define its own; per-account always wins over file-level.
|
|
52
52
|
- **signature** — legacy alternative: an HTML string applied to *new + reply + forward* (positioned before the quote on replies, at the end for new messages). Useful when you want the signature on every outgoing message regardless of context. Also supported at the **file level** as a global fallback.
|
|
53
53
|
- **keys.anthropic / keys.openai** — API keys for the AI features (translate / proofread / summarize / autocomplete). Lives at the file's top level alongside `accounts:` so you set it once across all your devices. Generated at console.anthropic.com or platform.openai.com. Provider selection is in `preferences.jsonc`; the key here is read based on which provider is active. Empty string means "not configured" — the AI feature silently no-ops. Local Ollama provider needs no key.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Multi-view: tabs, tear-off, separate windows
|
|
2
|
+
|
|
3
|
+
Goal: multiple **views** over one backend — like Thunderbird/Outlook — so the
|
|
4
|
+
user can keep a message open while working the list, compare two folders, etc.
|
|
5
|
+
NOT multiple daemon instances (`--another` already exists for that, and a
|
|
6
|
+
second daemon means a second DB connection — contention, not what's wanted).
|
|
7
|
+
|
|
8
|
+
## Key fact: reminders already spawn separate native windows
|
|
9
|
+
|
|
10
|
+
`MailxService.showReminderPopup` → injected `popupFn` → msger
|
|
11
|
+
`showMessageBoxEx` opens a **real OS WebView window** fed a self-contained HTML
|
|
12
|
+
document (`rawHtml: true`: the caller ships a full HTML doc with its own button
|
|
13
|
+
row + an inline script that posts results back over the wry bridge). It returns
|
|
14
|
+
a handle with `pid` + `close()`.
|
|
15
|
+
|
|
16
|
+
Consequence: msger spawning extra native windows is **not** the blocker. The
|
|
17
|
+
"popups don't inherit the custom protocol" limitation only blocks a window that
|
|
18
|
+
must load the *multi-file app* (index.html + app.bundle.js + CSS + importmap
|
|
19
|
+
deps, all served from `msger.localhost`). A **single rendered view** is
|
|
20
|
+
self-contained content — the reminder shape.
|
|
21
|
+
|
|
22
|
+
So:
|
|
23
|
+
- **Torn-off READ-ONLY message reader** = the reminder mechanism. The daemon
|
|
24
|
+
already has rendered `bodyHtml` from `getMessage`; wrap header + sanitized
|
|
25
|
+
body as a standalone HTML doc, hand to `showMessageBoxEx`. No custom
|
|
26
|
+
protocol, no second IPC channel. Cheap.
|
|
27
|
+
- **Torn-off INTERACTIVE view** (reply / navigate / delete / live sync
|
|
28
|
+
updates) needs a real IPC channel back to the daemon → msger multi-channel
|
|
29
|
+
work. Expensive. Deferred.
|
|
30
|
+
|
|
31
|
+
## Three view modes, one backend
|
|
32
|
+
|
|
33
|
+
1. **Tabs** inside the main window — one WebView, one IPC channel, a tab strip;
|
|
34
|
+
each tab is an independent three-pane (or a single-message view). Pure
|
|
35
|
+
client work.
|
|
36
|
+
2. **Tear-off** — detach a tab into its own OS window.
|
|
37
|
+
- Read-only message tab → `showMessageBoxEx` + `rawHtml` (reminder path).
|
|
38
|
+
- Interactive tab → needs msger multi-channel IPC (later).
|
|
39
|
+
3. **Re-dock** — drag a torn-off window's content back in as a tab. For the
|
|
40
|
+
read-only reader this is just: close the popup, re-open the message as an
|
|
41
|
+
in-window tab. For interactive, follows whatever (2) settles on.
|
|
42
|
+
|
|
43
|
+
## Tabs architecture (the real work)
|
|
44
|
+
|
|
45
|
+
Today the list view is module-level global state in `client/components/
|
|
46
|
+
message-list.ts` — `currentAccountId`, `currentFolderId`, `searchMode`,
|
|
47
|
+
`currentSearchQuery`, `positionMemory`, `listCache` — and `message-state.ts` is
|
|
48
|
+
a single global "source of truth" for the list+viewer. One window = one view.
|
|
49
|
+
|
|
50
|
+
Tabs require that view-state to become **per-tab**:
|
|
51
|
+
|
|
52
|
+
- **Per-tab:** selected account+folder (or search), selected message, list
|
|
53
|
+
scroll position, the `message-state` instance, sort order, flagged-only
|
|
54
|
+
toggle.
|
|
55
|
+
- **Shared (stays global):** the IPC connection + sync event stream, folder
|
|
56
|
+
counts, the contacts cache, settings, the parsed-body LRU on the daemon.
|
|
57
|
+
Sync/new-mail events broadcast to every tab; each tab decides if the event
|
|
58
|
+
touches its folder.
|
|
59
|
+
|
|
60
|
+
Shape: a `TabManager` owning an array of `ViewTab`, each `ViewTab` holding its
|
|
61
|
+
own `MessageListState` + the DOM subtree for its three-pane. Only the active
|
|
62
|
+
tab's subtree is in layout; inactive tabs keep their DOM detached (cheap) or
|
|
63
|
+
their state and rebuild on activate. `message-list.ts` functions take a
|
|
64
|
+
`ViewTab` (or read `TabManager.active`) instead of the module globals.
|
|
65
|
+
|
|
66
|
+
## Staging
|
|
67
|
+
|
|
68
|
+
1. **Tab strip + per-tab three-pane.** Extract the message-list globals into a
|
|
69
|
+
`ViewTab`/`TabManager`; render a tab strip; New Tab opens another inbox
|
|
70
|
+
view. The bulk of the work and the architectural commit.
|
|
71
|
+
2. **Open-in-new-tab** for a message (and a folder). Exercises per-tab state on
|
|
72
|
+
the simplest views.
|
|
73
|
+
3. **Tear-off a read-only message tab** → `showMessageBoxEx` standalone window
|
|
74
|
+
(reminder mechanism). Re-dock = reopen as a tab.
|
|
75
|
+
4. **Interactive tear-off / true second app window.** Needs msger to let a
|
|
76
|
+
spawned window inherit the custom protocol AND carry its own IPC channel,
|
|
77
|
+
plus the daemon multiplexing IPC and broadcasting events to N channels.
|
|
78
|
+
Separate, cross-component project — do last, only if the read-only
|
|
79
|
+
tear-off proves insufficient.
|
|
80
|
+
|
|
81
|
+
Start at stage 1. Each stage is independently shippable.
|
package/docs/search.md
CHANGED
|
@@ -19,7 +19,7 @@ These are parsed by mailx before dispatch, so they apply to all three scopes:
|
|
|
19
19
|
| `from:bob` | Match sender substring |
|
|
20
20
|
| `to:eleanor` | Match recipient substring |
|
|
21
21
|
| `subject:lunch` | Match subject substring (everything until the next `keyword:` or end) |
|
|
22
|
-
| `/regex/` | Client-side regex over the currently-visible rows. Local only — never sent to the server. |
|
|
22
|
+
| `/regex/` | Client-side regex over the currently-visible rows. **Case-insensitive.** Local only — never sent to the server. |
|
|
23
23
|
|
|
24
24
|
The remaining (unqualified) text becomes the **body** search term in the server query, or a free-text FTS5 phrase in local mode.
|
|
25
25
|
|
|
@@ -80,6 +80,10 @@ So if you typed `bob AND eleanor` on bobma and got results, what likely happened
|
|
|
80
80
|
|
|
81
81
|
That explains the asymmetry you may have noticed: `bob AND eleanor` "worked" against bobma (literal-text coincidence) but not against Gmail (no IMAP/API call ever fired).
|
|
82
82
|
|
|
83
|
+
## Case sensitivity
|
|
84
|
+
|
|
85
|
+
All three modes are **case-insensitive** — local FTS5, IMAP server SEARCH, and `/regex/` filtering alike. `Lunch`, `lunch`, and `LUNCH` match the same messages. There's no way to force a case-sensitive search.
|
|
86
|
+
|
|
83
87
|
## Limitations
|
|
84
88
|
|
|
85
89
|
- **No regex on the server side** (any provider). `/pattern/` only filters the visible local rows.
|
package/index.d.ts
CHANGED
|
@@ -153,6 +153,13 @@ export declare function saveAutocomplete(settings: AutocompleteSettings): void;
|
|
|
153
153
|
export declare function loadAllowlist(): typeof DEFAULT_ALLOWLIST;
|
|
154
154
|
/** Save allow-list — merges with existing cloud copy (multi-client safe) */
|
|
155
155
|
export declare function saveAllowlist(list: typeof DEFAULT_ALLOWLIST): Promise<void>;
|
|
156
|
+
/** Load user-added dictionary words. Mirrored to GDrive so "Add to dictionary"
|
|
157
|
+
* on one machine appears on every machine. Same multi-client merge pattern
|
|
158
|
+
* as allowlist. Returns a string array; duplicates removed by saveUserDict. */
|
|
159
|
+
export declare function loadUserDict(): string[];
|
|
160
|
+
/** Save user dictionary — merges with cloud copy so concurrent edits on
|
|
161
|
+
* different machines union rather than overwrite. */
|
|
162
|
+
export declare function saveUserDict(words: string[]): Promise<void>;
|
|
156
163
|
/** Load settings — unified view combining all files (backward compatible) */
|
|
157
164
|
export declare function loadSettings(): MailxSettings;
|
|
158
165
|
export declare function saveSettings(settings: MailxSettings): Promise<void>;
|
package/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"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"}
|
package/index.js
CHANGED
|
@@ -1047,6 +1047,30 @@ export async function saveAllowlist(list) {
|
|
|
1047
1047
|
catch { /* cloud read failed — save local version */ }
|
|
1048
1048
|
saveFile("allowlist.jsonc", merged);
|
|
1049
1049
|
}
|
|
1050
|
+
/** Load user-added dictionary words. Mirrored to GDrive so "Add to dictionary"
|
|
1051
|
+
* on one machine appears on every machine. Same multi-client merge pattern
|
|
1052
|
+
* as allowlist. Returns a string array; duplicates removed by saveUserDict. */
|
|
1053
|
+
export function loadUserDict() {
|
|
1054
|
+
const raw = loadFile("userdict.jsonc", { words: [] });
|
|
1055
|
+
return Array.isArray(raw?.words) ? raw.words : [];
|
|
1056
|
+
}
|
|
1057
|
+
/** Save user dictionary — merges with cloud copy so concurrent edits on
|
|
1058
|
+
* different machines union rather than overwrite. */
|
|
1059
|
+
export async function saveUserDict(words) {
|
|
1060
|
+
let merged = [...new Set(words)];
|
|
1061
|
+
try {
|
|
1062
|
+
const cloudContent = await cloudRead("userdict.jsonc");
|
|
1063
|
+
if (cloudContent) {
|
|
1064
|
+
const cloud = parseJsonc(cloudContent);
|
|
1065
|
+
if (cloud && Array.isArray(cloud.words)) {
|
|
1066
|
+
merged = [...new Set([...merged, ...cloud.words])];
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
catch { /* cloud unreachable — save local version */ }
|
|
1071
|
+
merged.sort();
|
|
1072
|
+
saveFile("userdict.jsonc", { words: merged });
|
|
1073
|
+
}
|
|
1050
1074
|
// ── Legacy compatibility ──
|
|
1051
1075
|
function loadLegacySettings() {
|
|
1052
1076
|
const config = readLocalConfig();
|
|
@@ -1249,11 +1273,24 @@ export async function deployDocs(appVersion) {
|
|
|
1249
1273
|
console.log(" [docs] no docs/ dir found in package — skipping deploy");
|
|
1250
1274
|
return;
|
|
1251
1275
|
}
|
|
1276
|
+
// Whitelist: only deploy docs that document a `.jsonc` config file or a
|
|
1277
|
+
// user-facing feature. Internal release-notes / publishing playbooks
|
|
1278
|
+
// (`prod.md`, `prod-android.md`, `npmglobalize-disttag.md`, `rmf-tiny.md`,
|
|
1279
|
+
// `push-relay.md`, etc.) live in the same docs/ folder for developer
|
|
1280
|
+
// reference but have no business on the user's GDrive.
|
|
1281
|
+
// NOTE: only `.jsonc` config-file help belongs here. Feature help (search,
|
|
1282
|
+
// editor, …) is HTML compiled into the client app — see
|
|
1283
|
+
// client/help/*.ts — and must NOT be deployed as a `.md` doc.
|
|
1284
|
+
const USER_DOC_WHITELIST = new Set([
|
|
1285
|
+
"accounts.md", "allowlist.md", "clients.md", "config.md",
|
|
1286
|
+
"contacts.md", "contact-rules.md", "preferences.md",
|
|
1287
|
+
"editor.md",
|
|
1288
|
+
]);
|
|
1252
1289
|
try {
|
|
1253
1290
|
const deployedVersion = (await provider.read(".docs-version") || "").trim();
|
|
1254
1291
|
if (deployedVersion === appVersion)
|
|
1255
1292
|
return; // already up to date
|
|
1256
|
-
const mdFiles = fs.readdirSync(docsDir).filter(f => f.endsWith(".md"));
|
|
1293
|
+
const mdFiles = fs.readdirSync(docsDir).filter(f => f.endsWith(".md") && USER_DOC_WHITELIST.has(f));
|
|
1257
1294
|
// Parallel cloud writes — each provider.write is independent.
|
|
1258
1295
|
// The previous serial loop took ~5-10 s for the 9 .md files
|
|
1259
1296
|
// because every write was one round-trip to GDrive. The doc-
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/mailx-settings",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.18",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"license": "ISC",
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@bobfrankston/mailx-types": "^0.1.
|
|
20
|
+
"@bobfrankston/mailx-types": "^0.1.15",
|
|
21
21
|
"jsonc-parser": "^3.3.1"
|
|
22
22
|
},
|
|
23
23
|
"repository": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
},
|
|
34
34
|
".transformedSnapshot": {
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@bobfrankston/mailx-types": "^0.1.
|
|
36
|
+
"@bobfrankston/mailx-types": "^0.1.15",
|
|
37
37
|
"jsonc-parser": "^3.3.1"
|
|
38
38
|
}
|
|
39
39
|
}
|