@bobfrankston/mailx-settings 0.1.7 → 0.1.10
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/docs/accounts.md +6 -1
- package/docs/config.md +1 -1
- package/index.d.ts +26 -2
- package/index.d.ts.map +1 -1
- package/index.js +131 -17
- package/package.json +3 -3
package/docs/accounts.md
CHANGED
|
@@ -21,7 +21,11 @@
|
|
|
21
21
|
"enabled": true, // false skips this account at startup
|
|
22
22
|
"identityDomains": ["alias.com"] // extra domains for Reply-From auto-detect
|
|
23
23
|
}
|
|
24
|
-
]
|
|
24
|
+
],
|
|
25
|
+
"keys": { // AI provider API keys (optional)
|
|
26
|
+
"anthropic": "", // sk-ant-api03-...
|
|
27
|
+
"openai": "" // sk-...
|
|
28
|
+
}
|
|
25
29
|
}
|
|
26
30
|
```
|
|
27
31
|
|
|
@@ -33,6 +37,7 @@
|
|
|
33
37
|
- **auth** — `"password"` for traditional IMAP/SMTP, `"oauth2"` for Gmail/Google Workspace/Outlook.
|
|
34
38
|
- **enabled** — set `false` to keep the account record but skip sync at startup.
|
|
35
39
|
- **identityDomains** — addresses you receive at on alternative domains. When you Reply, rmfmail picks the matching identity address as From instead of the account's primary.
|
|
40
|
+
- **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.
|
|
36
41
|
|
|
37
42
|
## Notes
|
|
38
43
|
|
package/docs/config.md
CHANGED
|
@@ -17,7 +17,7 @@ Lives at `~/.rmfmail/config.jsonc` on the local filesystem.
|
|
|
17
17
|
"path": ".rmfmail", // folder name in My Drive (currently My Drive/home/.rmfmail)
|
|
18
18
|
"folderId": "1AbCdEf...XYZ" // resolved Drive folderId, cached after first lookup
|
|
19
19
|
},
|
|
20
|
-
"storePath": "
|
|
20
|
+
"storePath": "~/.rmfmail/mailxstore", // local directory for .eml message bodies
|
|
21
21
|
"historyDays": 30 // how far back to sync; overrides the shared default
|
|
22
22
|
}
|
|
23
23
|
```
|
package/index.d.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*
|
|
16
16
|
* The old settings.jsonc is still supported for backward compatibility.
|
|
17
17
|
*/
|
|
18
|
-
import type { MailxSettings, AccountConfig, AutocompleteSettings } from "@bobfrankston/mailx-types";
|
|
18
|
+
import type { MailxSettings, AccountConfig, AutocompleteSettings, AiKeys } from "@bobfrankston/mailx-types";
|
|
19
19
|
declare const LOCAL_DIR: string;
|
|
20
20
|
/** Subscribers notified whenever lastCloudError changes (push to UI immediately). */
|
|
21
21
|
type CloudErrorListener = (error: string | null, context?: {
|
|
@@ -107,8 +107,32 @@ export declare function denormalizeAccount(acct: AccountConfig, globalName?: str
|
|
|
107
107
|
/** Save account configs */
|
|
108
108
|
/** Save accounts — merges with cloud copy by email (multi-client safe).
|
|
109
109
|
* Writes the lean form via denormalizeAccount so accounts.jsonc stays
|
|
110
|
-
* compact and human-editable.
|
|
110
|
+
* compact and human-editable. Preserves the top-level `keys` field
|
|
111
|
+
* (AI API keys) if present in the existing cloud or local file. */
|
|
111
112
|
export declare function saveAccounts(accounts: AccountConfig[]): Promise<void>;
|
|
113
|
+
/** Load top-level AI keys from accounts.jsonc. Returns empty placeholders
|
|
114
|
+
* ({ anthropic: "", openai: "" }) when the file lacks a `keys` section,
|
|
115
|
+
* so callers can call this unconditionally without missing-section guards.
|
|
116
|
+
* Reads from the same shared/local path resolution as loadAccounts. */
|
|
117
|
+
export declare function loadKeys(): AiKeys;
|
|
118
|
+
/** Save AI keys back to accounts.jsonc, preserving the rest of the file
|
|
119
|
+
* (accounts list, file-level name). Used by Settings → AI to persist a
|
|
120
|
+
* key the user just entered. Cloud-synced via saveFile. */
|
|
121
|
+
export declare function saveKeys(keys: AiKeys): Promise<void>;
|
|
122
|
+
/** First-run helper: ensure `keys: { anthropic: "", openai: "" }` is
|
|
123
|
+
* materialized in accounts.jsonc so the user sees placeholders for every
|
|
124
|
+
* supported provider when they open the config editor. Adds missing
|
|
125
|
+
* fields without disturbing existing values, so `keys: {}` becomes
|
|
126
|
+
* `keys: { anthropic: "", openai: "" }` and `keys: { anthropic: "sk-..." }`
|
|
127
|
+
* becomes `keys: { anthropic: "sk-...", openai: "" }`. Idempotent. */
|
|
128
|
+
export declare function ensureKeysSectionExists(): Promise<void>;
|
|
129
|
+
/** First-run helper: materialize the `autocomplete` block in
|
|
130
|
+
* preferences.jsonc with explicit defaults. loadPreferences() merges
|
|
131
|
+
* defaults at read time, but the file on disk often has no
|
|
132
|
+
* `autocomplete` key, so a user opening Settings → Edit config files
|
|
133
|
+
* doesn't see the AI provider knobs. This writes the defaults out so
|
|
134
|
+
* the user can edit them. Idempotent. */
|
|
135
|
+
export declare function ensureAutocompletePrefsExist(): Promise<void>;
|
|
112
136
|
/** Load preferences (shared + local overrides, with legacy fallback) */
|
|
113
137
|
export declare function loadPreferences(): typeof DEFAULT_PREFERENCES;
|
|
114
138
|
/** Save preferences */
|
package/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"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"}
|
package/index.js
CHANGED
|
@@ -572,7 +572,7 @@ const DEFAULT_PREFERENCES = {
|
|
|
572
572
|
ollamaUrl: "http://localhost:11434",
|
|
573
573
|
ollamaModel: "qwen2.5-coder:1.5b",
|
|
574
574
|
cloudApiKey: "",
|
|
575
|
-
cloudModel: "claude-sonnet-4-
|
|
575
|
+
cloudModel: "claude-sonnet-4-5",
|
|
576
576
|
debounceMs: 600,
|
|
577
577
|
maxTokens: 60,
|
|
578
578
|
},
|
|
@@ -583,7 +583,7 @@ const DEFAULT_AUTOCOMPLETE = {
|
|
|
583
583
|
ollamaUrl: "http://localhost:11434",
|
|
584
584
|
ollamaModel: "qwen2.5-coder:1.5b",
|
|
585
585
|
cloudApiKey: "",
|
|
586
|
-
cloudModel: "claude-sonnet-4-
|
|
586
|
+
cloudModel: "claude-sonnet-4-5",
|
|
587
587
|
debounceMs: 600,
|
|
588
588
|
maxTokens: 60,
|
|
589
589
|
};
|
|
@@ -800,28 +800,44 @@ export function denormalizeAccount(acct, globalName) {
|
|
|
800
800
|
/** Save account configs */
|
|
801
801
|
/** Save accounts — merges with cloud copy by email (multi-client safe).
|
|
802
802
|
* Writes the lean form via denormalizeAccount so accounts.jsonc stays
|
|
803
|
-
* compact and human-editable.
|
|
803
|
+
* compact and human-editable. Preserves the top-level `keys` field
|
|
804
|
+
* (AI API keys) if present in the existing cloud or local file. */
|
|
804
805
|
export async function saveAccounts(accounts) {
|
|
805
|
-
//
|
|
806
|
+
// Read existing top-level fields we need to preserve (keys, etc.) before
|
|
807
|
+
// we rewrite. Prefer cloud over local.
|
|
808
|
+
let preservedKeys;
|
|
809
|
+
let cloudCopy;
|
|
806
810
|
try {
|
|
807
811
|
const cloudContent = await cloudRead("accounts.jsonc");
|
|
808
|
-
if (cloudContent)
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
812
|
+
if (cloudContent)
|
|
813
|
+
cloudCopy = parseJsonc(cloudContent);
|
|
814
|
+
}
|
|
815
|
+
catch { /* ignore */ }
|
|
816
|
+
if (!cloudCopy) {
|
|
817
|
+
try {
|
|
818
|
+
const localPath = path.join(LOCAL_DIR, "accounts.jsonc");
|
|
819
|
+
if (fs.existsSync(localPath))
|
|
820
|
+
cloudCopy = readJsonc(localPath);
|
|
821
|
+
}
|
|
822
|
+
catch { /* ignore */ }
|
|
823
|
+
}
|
|
824
|
+
if (cloudCopy?.keys && typeof cloudCopy.keys === "object")
|
|
825
|
+
preservedKeys = cloudCopy.keys;
|
|
826
|
+
// Merge with cloud: keep all accounts, deduplicate by normalized email
|
|
827
|
+
if (cloudCopy) {
|
|
828
|
+
const cloudAccts = cloudCopy.accounts || (Array.isArray(cloudCopy) ? cloudCopy : []);
|
|
829
|
+
if (cloudAccts.length > 0) {
|
|
830
|
+
const seen = new Set(accounts.map(a => normalizeEmail(a.email)));
|
|
831
|
+
for (const ca of cloudAccts) {
|
|
832
|
+
if (ca.email && !seen.has(normalizeEmail(ca.email))) {
|
|
833
|
+
// Cloud entries are already lean — feed through
|
|
834
|
+
// normalizeAccount to coerce to AccountConfig shape.
|
|
835
|
+
accounts.push(normalizeAccount(ca));
|
|
836
|
+
seen.add(normalizeEmail(ca.email));
|
|
820
837
|
}
|
|
821
838
|
}
|
|
822
839
|
}
|
|
823
840
|
}
|
|
824
|
-
catch { /* cloud read failed — save local version */ }
|
|
825
841
|
// Promote a shared "name" to file level when every account has the same
|
|
826
842
|
// name — keeps the JSONC tidy ({ "name": "Bob Frankston", "accounts": [...] }
|
|
827
843
|
// instead of repeating "name" on each entry).
|
|
@@ -829,8 +845,106 @@ export async function saveAccounts(accounts) {
|
|
|
829
845
|
const globalName = names.size === 1 ? [...names][0] : undefined;
|
|
830
846
|
const lean = accounts.map(a => denormalizeAccount(a, globalName));
|
|
831
847
|
const payload = globalName ? { name: globalName, accounts: lean } : { accounts: lean };
|
|
848
|
+
if (preservedKeys)
|
|
849
|
+
payload.keys = preservedKeys;
|
|
832
850
|
saveFile("accounts.jsonc", payload);
|
|
833
851
|
}
|
|
852
|
+
/** Load top-level AI keys from accounts.jsonc. Returns empty placeholders
|
|
853
|
+
* ({ anthropic: "", openai: "" }) when the file lacks a `keys` section,
|
|
854
|
+
* so callers can call this unconditionally without missing-section guards.
|
|
855
|
+
* Reads from the same shared/local path resolution as loadAccounts. */
|
|
856
|
+
export function loadKeys() {
|
|
857
|
+
const sharedDir = getSharedDir();
|
|
858
|
+
const sharedPath = path.join(sharedDir, "accounts.jsonc");
|
|
859
|
+
const localPath = path.join(LOCAL_DIR, "accounts.jsonc");
|
|
860
|
+
let raw = readJsonc(sharedPath);
|
|
861
|
+
if (!raw)
|
|
862
|
+
raw = readJsonc(localPath);
|
|
863
|
+
const keys = raw?.keys;
|
|
864
|
+
return {
|
|
865
|
+
anthropic: typeof keys?.anthropic === "string" ? keys.anthropic : "",
|
|
866
|
+
openai: typeof keys?.openai === "string" ? keys.openai : "",
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
/** Save AI keys back to accounts.jsonc, preserving the rest of the file
|
|
870
|
+
* (accounts list, file-level name). Used by Settings → AI to persist a
|
|
871
|
+
* key the user just entered. Cloud-synced via saveFile. */
|
|
872
|
+
export async function saveKeys(keys) {
|
|
873
|
+
let existing = {};
|
|
874
|
+
try {
|
|
875
|
+
const cloudContent = await cloudRead("accounts.jsonc");
|
|
876
|
+
if (cloudContent)
|
|
877
|
+
existing = parseJsonc(cloudContent) || {};
|
|
878
|
+
}
|
|
879
|
+
catch { /* ignore */ }
|
|
880
|
+
if (!existing.accounts) {
|
|
881
|
+
try {
|
|
882
|
+
const localPath = path.join(LOCAL_DIR, "accounts.jsonc");
|
|
883
|
+
if (fs.existsSync(localPath))
|
|
884
|
+
existing = readJsonc(localPath) || existing;
|
|
885
|
+
}
|
|
886
|
+
catch { /* ignore */ }
|
|
887
|
+
}
|
|
888
|
+
const merged = { ...existing.keys, ...keys };
|
|
889
|
+
// Drop empty-string fields so the on-disk file stays tidy when the user
|
|
890
|
+
// clears a key (writes `""` from the UI) — keep only meaningful values.
|
|
891
|
+
const cleaned = {};
|
|
892
|
+
if (merged.anthropic)
|
|
893
|
+
cleaned.anthropic = merged.anthropic;
|
|
894
|
+
if (merged.openai)
|
|
895
|
+
cleaned.openai = merged.openai;
|
|
896
|
+
existing.keys = cleaned;
|
|
897
|
+
saveFile("accounts.jsonc", existing);
|
|
898
|
+
}
|
|
899
|
+
/** First-run helper: ensure `keys: { anthropic: "", openai: "" }` is
|
|
900
|
+
* materialized in accounts.jsonc so the user sees placeholders for every
|
|
901
|
+
* supported provider when they open the config editor. Adds missing
|
|
902
|
+
* fields without disturbing existing values, so `keys: {}` becomes
|
|
903
|
+
* `keys: { anthropic: "", openai: "" }` and `keys: { anthropic: "sk-..." }`
|
|
904
|
+
* becomes `keys: { anthropic: "sk-...", openai: "" }`. Idempotent. */
|
|
905
|
+
export async function ensureKeysSectionExists() {
|
|
906
|
+
const sharedDir = getSharedDir();
|
|
907
|
+
const sharedPath = path.join(sharedDir, "accounts.jsonc");
|
|
908
|
+
const raw = readJsonc(sharedPath);
|
|
909
|
+
if (!raw)
|
|
910
|
+
return; // no accounts file yet — first-time setup will handle it
|
|
911
|
+
const existing = (raw.keys && typeof raw.keys === "object") ? raw.keys : {};
|
|
912
|
+
const merged = {
|
|
913
|
+
anthropic: typeof existing.anthropic === "string" ? existing.anthropic : "",
|
|
914
|
+
openai: typeof existing.openai === "string" ? existing.openai : "",
|
|
915
|
+
};
|
|
916
|
+
// Skip the write if the on-disk shape already matches — avoids touching
|
|
917
|
+
// accounts.jsonc on every startup and the spurious "config changed" banner.
|
|
918
|
+
if (raw.keys
|
|
919
|
+
&& typeof raw.keys === "object"
|
|
920
|
+
&& raw.keys.anthropic === merged.anthropic
|
|
921
|
+
&& raw.keys.openai === merged.openai
|
|
922
|
+
&& Object.keys(raw.keys).length === 2)
|
|
923
|
+
return;
|
|
924
|
+
raw.keys = merged;
|
|
925
|
+
saveFile("accounts.jsonc", raw);
|
|
926
|
+
}
|
|
927
|
+
/** First-run helper: materialize the `autocomplete` block in
|
|
928
|
+
* preferences.jsonc with explicit defaults. loadPreferences() merges
|
|
929
|
+
* defaults at read time, but the file on disk often has no
|
|
930
|
+
* `autocomplete` key, so a user opening Settings → Edit config files
|
|
931
|
+
* doesn't see the AI provider knobs. This writes the defaults out so
|
|
932
|
+
* the user can edit them. Idempotent. */
|
|
933
|
+
export async function ensureAutocompletePrefsExist() {
|
|
934
|
+
const sharedDir = getSharedDir();
|
|
935
|
+
const sharedPath = path.join(sharedDir, "preferences.jsonc");
|
|
936
|
+
let raw = readJsonc(sharedPath);
|
|
937
|
+
if (!raw)
|
|
938
|
+
raw = {};
|
|
939
|
+
if (raw.autocomplete && typeof raw.autocomplete === "object")
|
|
940
|
+
return; // already present
|
|
941
|
+
raw.autocomplete = { ...DEFAULT_AUTOCOMPLETE };
|
|
942
|
+
// Strip the cloudApiKey field — the API key now lives in
|
|
943
|
+
// accounts.jsonc.keys, not here. Writing "" would mislead users into
|
|
944
|
+
// editing the wrong file.
|
|
945
|
+
delete raw.autocomplete.cloudApiKey;
|
|
946
|
+
saveFile("preferences.jsonc", raw);
|
|
947
|
+
}
|
|
834
948
|
/** Load preferences (shared + local overrides, with legacy fallback) */
|
|
835
949
|
export function loadPreferences() {
|
|
836
950
|
let shared = loadFile("preferences.jsonc", DEFAULT_PREFERENCES);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/mailx-settings",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
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.8",
|
|
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.8",
|
|
37
37
|
"jsonc-parser": "^3.3.1"
|
|
38
38
|
}
|
|
39
39
|
}
|