@bobfrankston/mailx-settings 0.1.34 → 0.1.35

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 CHANGED
@@ -6,12 +6,12 @@
6
6
  * which prevents conflicts with existing folders of the same name.
7
7
  * All machines using the same OAuth client ID share the same folder.
8
8
  *
9
- * ── Restoring removed providers ──
10
- * OneDrive (removed 2026-04-06): Required microsoft-credentials.json in ~/.mailx/,
11
- * MS_SCOPES = "https://graph.microsoft.com/Files.ReadWrite offline_access",
12
- * authenticateOAuth with tokenDir ~/.mailx/tokens/microsoft/.
13
- * Read/write via Graph API: GET/PUT https://graph.microsoft.com/v1.0/me/drive/root:/{path}:/content
14
- * Azure AD app registration needed: https://portal.azure.com → App registrations → Desktop app
9
+ * OneDrive (restored 2026-07-12): Microsoft Graph, PATH-addressed (no folder
10
+ * IDs `root:/{path}:/content`), so the whole GDrive folder-ID
11
+ * discovery/validation/ownership machinery has no OneDrive counterpart.
12
+ * Needs ~/.rmfmail/microsoft-credentials.json (Azure public-client app;
13
+ * same registration the Outlook Graph mail provider uses — different
14
+ * scopes on the same token infrastructure).
15
15
  *
16
16
  * Dropbox (removed 2026-04-06): Never implemented — placeholder only.
17
17
  * Would need Dropbox OAuth app, token management, and Dropbox API v2 calls.
@@ -46,7 +46,12 @@ export declare function gDriveValidateCachedFolder(folderId: string): Promise<bo
46
46
  * orphaning the user's accounts/contacts/etc. Switched to the dotted name
47
47
  * matching the actual folder convention. */
48
48
  export declare function gDriveFindOrCreateFolder(): Promise<string | null>;
49
- export type CloudProvider = "gdrive" | "google" | "local";
49
+ /** Fetch the authenticated Microsoft user's profile (setup-flow display). */
50
+ export declare function getMicrosoftProfile(): Promise<{
51
+ name?: string;
52
+ email?: string;
53
+ } | null>;
54
+ export type CloudProvider = "gdrive" | "google" | "onedrive" | "local";
50
55
  export interface CloudFile {
51
56
  /** Read a file. For gdrive, path is just the filename (folder ID is implicit). */
52
57
  read(filePath: string): Promise<string | null>;
@@ -58,10 +63,12 @@ export interface CloudFile {
58
63
  exists(filePath: string): Promise<boolean>;
59
64
  }
60
65
  /**
61
- * Get a cloud file provider. For gdrive, pass the folder ID.
62
- * Files are stored flat in the folder (no subdirectory navigation).
66
+ * Get a cloud file provider. The locator is provider-specific: gdrive takes
67
+ * the folder ID; onedrive takes the base PATH under the drive root (e.g.
68
+ * "home/.rmfmail"). Files are stored flat in that folder either way — the
69
+ * CloudFile contract is identical, only the FS API underneath differs.
63
70
  */
64
- export declare function getCloudProvider(provider: string, folderId?: string): CloudFile | null;
71
+ export declare function getCloudProvider(provider: string, locator?: string): CloudFile | null;
65
72
  /** Fetch the authenticated Google user's profile (name + email) via the People API.
66
73
  * Caller must supply an OAuth access token whose scopes include `contacts.readonly`
67
74
  * or `userinfo.profile`. The Drive token (drive.file) does NOT have the right scope
package/cloud.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cloud.d.ts","sourceRoot":"","sources":["cloud.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AA+RH;;;;;;;;;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;AAyHD,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,8DAA8D;IAC9D,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACrD,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,CAyBtF;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"}
1
+ {"version":3,"file":"cloud.d.ts","sourceRoot":"","sources":["cloud.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AA+RH;;;;;;;;;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;AA8OD,6EAA6E;AAC7E,wBAAsB,mBAAmB,IAAI,OAAO,CAAC;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAS7F;AAID,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAC;AAEvE,MAAM,WAAW,SAAS;IACtB,kFAAkF;IAClF,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC/C,8DAA8D;IAC9D,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACrD,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;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAoCrF;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
@@ -6,12 +6,12 @@
6
6
  * which prevents conflicts with existing folders of the same name.
7
7
  * All machines using the same OAuth client ID share the same folder.
8
8
  *
9
- * ── Restoring removed providers ──
10
- * OneDrive (removed 2026-04-06): Required microsoft-credentials.json in ~/.mailx/,
11
- * MS_SCOPES = "https://graph.microsoft.com/Files.ReadWrite offline_access",
12
- * authenticateOAuth with tokenDir ~/.mailx/tokens/microsoft/.
13
- * Read/write via Graph API: GET/PUT https://graph.microsoft.com/v1.0/me/drive/root:/{path}:/content
14
- * Azure AD app registration needed: https://portal.azure.com → App registrations → Desktop app
9
+ * OneDrive (restored 2026-07-12): Microsoft Graph, PATH-addressed (no folder
10
+ * IDs `root:/{path}:/content`), so the whole GDrive folder-ID
11
+ * discovery/validation/ownership machinery has no OneDrive counterpart.
12
+ * Needs ~/.rmfmail/microsoft-credentials.json (Azure public-client app;
13
+ * same registration the Outlook Graph mail provider uses — different
14
+ * scopes on the same token infrastructure).
15
15
  *
16
16
  * Dropbox (removed 2026-04-06): Never implemented — placeholder only.
17
17
  * Would need Dropbox OAuth app, token management, and Dropbox API v2 calls.
@@ -568,14 +568,154 @@ async function gDriveWriteToFolder(folderId, fileName, content) {
568
568
  }
569
569
  }
570
570
  }
571
+ // ── OneDrive (Microsoft Graph, path-addressed) ──
572
+ const MS_TOKEN_DIR = path.join(SETTINGS_DIR, "tokens", "microsoft");
573
+ // offline_access is a SCOPE on the Microsoft identity platform (unlike
574
+ // Google's access_type=offline query param) — required for a refresh token.
575
+ const ONEDRIVE_SCOPES = "https://graph.microsoft.com/Files.ReadWrite offline_access";
576
+ const GRAPH_ROOT = "https://graph.microsoft.com/v1.0/me/drive";
577
+ function findMicrosoftCredentials() {
578
+ const local = path.join(SETTINGS_DIR, "microsoft-credentials.json");
579
+ return fs.existsSync(local) ? local : null;
580
+ }
581
+ async function getOneDriveToken() {
582
+ const creds = findMicrosoftCredentials();
583
+ if (!creds) {
584
+ console.error(` [cloud] No Microsoft credentials — expected ${path.join(SETTINGS_DIR, "microsoft-credentials.json")} (Azure portal → App registrations → public/native client)`);
585
+ return null;
586
+ }
587
+ try {
588
+ const token = await authenticateOAuth(creds, {
589
+ scope: ONEDRIVE_SCOPES,
590
+ tokenDirectory: MS_TOKEN_DIR,
591
+ tokenFileName: "onedrive-token.json",
592
+ credentialsKey: "installed",
593
+ // Google-ism; Microsoft grants refresh tokens via the
594
+ // offline_access scope above instead.
595
+ includeOfflineAccess: false,
596
+ logFn: (type, message) => {
597
+ const p = " [oauth onedrive]";
598
+ if (type === "error")
599
+ console.error(`${p} ${message}`);
600
+ else
601
+ console.log(`${p} ${message}`);
602
+ },
603
+ });
604
+ if (!token?.access_token)
605
+ console.error(" [cloud] OneDrive auth: returned null — OAuth likely timed out");
606
+ return token?.access_token || null;
607
+ }
608
+ catch (e) {
609
+ console.error(` [cloud] OneDrive auth FAILED: ${e.message}`);
610
+ return null;
611
+ }
612
+ }
613
+ /** Graph item URL for `<basePath>/<fileName>` under the drive root. */
614
+ function graphContentUrl(basePath, fileName) {
615
+ const rel = [basePath.replace(/^\/+|\/+$/g, ""), fileName]
616
+ .filter(Boolean).join("/")
617
+ .split("/").map(encodeURIComponent).join("/");
618
+ return `${GRAPH_ROOT}/root:/${rel}:/content`;
619
+ }
620
+ /** Create every segment of basePath that doesn't exist yet. Graph's
621
+ * PUT :/content does NOT auto-create parent folders — a fresh account's
622
+ * first write would 404. conflictBehavior:fail + tolerating the
623
+ * nameAlreadyExists error makes this an idempotent walk. Once per
624
+ * process per path — after that the folders exist. */
625
+ const _ensuredPaths = new Set();
626
+ async function oneDriveEnsurePath(token, basePath) {
627
+ const clean = basePath.replace(/^\/+|\/+$/g, "");
628
+ if (!clean || _ensuredPaths.has(clean))
629
+ return;
630
+ let parent = "";
631
+ for (const seg of clean.split("/")) {
632
+ const url = parent
633
+ ? `${GRAPH_ROOT}/root:/${parent.split("/").map(encodeURIComponent).join("/")}:/children`
634
+ : `${GRAPH_ROOT}/root/children`;
635
+ const r = await fetch(url, {
636
+ method: "POST",
637
+ headers: { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" },
638
+ body: JSON.stringify({ name: seg, folder: {}, "@microsoft.graph.conflictBehavior": "fail" }),
639
+ });
640
+ // 201 created, 409 nameAlreadyExists — both mean the segment exists now.
641
+ if (!r.ok && r.status !== 409) {
642
+ throw new Error(`OneDrive folder create "${seg}" failed: ${r.status} ${await r.text().catch(() => "")}`);
643
+ }
644
+ parent = parent ? `${parent}/${seg}` : seg;
645
+ }
646
+ _ensuredPaths.add(clean);
647
+ }
648
+ async function oneDriveRead(basePath, fileName) {
649
+ const token = await getOneDriveToken();
650
+ if (!token)
651
+ return null;
652
+ const r = await fetch(graphContentUrl(basePath, fileName), { headers: { "Authorization": `Bearer ${token}` } });
653
+ if (r.status === 404)
654
+ return null;
655
+ if (!r.ok) {
656
+ console.error(` [cloud] OneDrive read ${fileName}: ${r.status}`);
657
+ return null;
658
+ }
659
+ return await r.text();
660
+ }
661
+ async function oneDriveReadBinary(basePath, fileName) {
662
+ const token = await getOneDriveToken();
663
+ if (!token)
664
+ return null;
665
+ const r = await fetch(graphContentUrl(basePath, fileName), { headers: { "Authorization": `Bearer ${token}` } });
666
+ if (r.status === 404)
667
+ return null;
668
+ if (!r.ok) {
669
+ console.error(` [cloud] OneDrive readBinary ${fileName}: ${r.status}`);
670
+ return null;
671
+ }
672
+ return Buffer.from(await r.arrayBuffer()).toString("base64");
673
+ }
674
+ async function oneDriveWrite(basePath, fileName, content) {
675
+ const token = await getOneDriveToken();
676
+ if (!token)
677
+ throw new Error("OneDrive auth failed — cannot write");
678
+ const put = () => fetch(graphContentUrl(basePath, fileName), {
679
+ method: "PUT",
680
+ headers: { "Authorization": `Bearer ${token}`, "Content-Type": "text/plain" },
681
+ body: content,
682
+ });
683
+ let r = await put();
684
+ if (r.status === 404 || r.status === 409) {
685
+ // Parent path missing (fresh account) — create it and retry once.
686
+ await oneDriveEnsurePath(token, basePath);
687
+ r = await put();
688
+ }
689
+ if (!r.ok)
690
+ throw new Error(`OneDrive write ${fileName} failed: ${r.status} ${await r.text().catch(() => "")}`);
691
+ }
692
+ /** Fetch the authenticated Microsoft user's profile (setup-flow display). */
693
+ export async function getMicrosoftProfile() {
694
+ const token = await getOneDriveToken();
695
+ if (!token)
696
+ return null;
697
+ try {
698
+ const r = await fetch("https://graph.microsoft.com/v1.0/me", { headers: { "Authorization": `Bearer ${token}` } });
699
+ if (!r.ok)
700
+ return null;
701
+ const me = await r.json();
702
+ return { name: me.displayName, email: me.mail || me.userPrincipalName };
703
+ }
704
+ catch {
705
+ return null;
706
+ }
707
+ }
571
708
  /**
572
- * Get a cloud file provider. For gdrive, pass the folder ID.
573
- * Files are stored flat in the folder (no subdirectory navigation).
709
+ * Get a cloud file provider. The locator is provider-specific: gdrive takes
710
+ * the folder ID; onedrive takes the base PATH under the drive root (e.g.
711
+ * "home/.rmfmail"). Files are stored flat in that folder either way — the
712
+ * CloudFile contract is identical, only the FS API underneath differs.
574
713
  */
575
- export function getCloudProvider(provider, folderId) {
714
+ export function getCloudProvider(provider, locator) {
576
715
  switch (provider) {
577
716
  case "google":
578
- case "gdrive":
717
+ case "gdrive": {
718
+ const folderId = locator;
579
719
  if (!folderId) {
580
720
  console.error(" [cloud] gdrive requires a folder ID — run initCloudConfig first");
581
721
  return null;
@@ -586,6 +726,16 @@ export function getCloudProvider(provider, folderId) {
586
726
  write: (fileName, content) => gDriveWriteToFolder(folderId, fileName, content),
587
727
  exists: async (fileName) => (await gDriveReadFromFolder(folderId, fileName)) !== null,
588
728
  };
729
+ }
730
+ case "onedrive": {
731
+ const basePath = locator || "home/.rmfmail";
732
+ return {
733
+ read: (fileName) => oneDriveRead(basePath, fileName),
734
+ readBinary: (fileName) => oneDriveReadBinary(basePath, fileName),
735
+ write: (fileName, content) => oneDriveWrite(basePath, fileName, content),
736
+ exists: async (fileName) => (await oneDriveRead(basePath, fileName)) !== null,
737
+ };
738
+ }
589
739
  case "local":
590
740
  return {
591
741
  read: async (p) => { try {
@@ -604,7 +754,7 @@ export function getCloudProvider(provider, folderId) {
604
754
  exists: async (p) => fs.existsSync(p),
605
755
  };
606
756
  default:
607
- console.error(` [cloud] Provider "${provider}" not supported — only gdrive is available`);
757
+ console.error(` [cloud] Provider "${provider}" not supported — gdrive and onedrive are available`);
608
758
  return null;
609
759
  }
610
760
  }
package/index.d.ts CHANGED
@@ -231,7 +231,7 @@ export declare function initLocalConfig(sharedDir?: string, storePath?: string):
231
231
  * Finds or creates the app-owned ".rmfmail" folder via Drive API and stores its ID.
232
232
  * No mount scanning — API only. Existing settings at other paths (e.g., home/.rmfmail
233
233
  * from Desktop sync) must be migrated manually or via config.jsonc importPath. */
234
- export declare function initCloudConfig(provider?: "gdrive"): Promise<void>;
234
+ export declare function initCloudConfig(provider?: "gdrive" | "onedrive"): Promise<void>;
235
235
  declare const DEFAULT_SETTINGS: MailxSettings;
236
236
  /** Get historyDays for an account: per-account override > system override > shared default */
237
237
  export declare function getHistoryDays(accountId?: string): number;
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
@@ -146,6 +146,12 @@ const LOCAL_DIR = path.join(process.env.USERPROFILE || process.env.HOME || ".",
146
146
  const LOCAL_CONFIG_PATH = path.join(LOCAL_DIR, "config.jsonc");
147
147
  const LEGACY_CONFIG_PATH = path.join(LOCAL_DIR, "config.json");
148
148
  const DEFAULT_STORE_PATH = path.join(LOCAL_DIR, "mailxstore");
149
+ /** Provider-specific locator for getCloudProvider: gdrive wants the cached
150
+ * folder ID, onedrive wants the base path. One helper so the call sites
151
+ * can't drift apart. */
152
+ function cloudLocator(cfg) {
153
+ return cfg.provider === "onedrive" ? cfg.path : cfg.folderId;
154
+ }
149
155
  /** Resolve a path from config — relative to ~/.mailx/, ~ expands to home */
150
156
  function resolvePath(p) {
151
157
  if (!p)
@@ -174,7 +180,7 @@ function readLocalConfig() {
174
180
  /** Resolve provider config to a filesystem path (checks for local Google Drive mount) */
175
181
  function resolveProvider(cfg) {
176
182
  // Cloud providers use API only — no filesystem mount scanning
177
- if (cfg.provider === "gdrive" || cfg.provider === "google")
183
+ if (cfg.provider === "gdrive" || cfg.provider === "google" || cfg.provider === "onedrive")
178
184
  return undefined;
179
185
  if (cfg.provider === "local")
180
186
  return resolvePath(cfg.path);
@@ -248,8 +254,8 @@ export async function cloudRead(filename) {
248
254
  clearFolderIdInConfig();
249
255
  }
250
256
  }
251
- // Ensure we have a folder ID
252
- if (!pendingCloudConfig.folderId) {
257
+ // Ensure we have a folder ID (gdrive only — onedrive is path-addressed)
258
+ if (pendingCloudConfig.provider !== "onedrive" && !pendingCloudConfig.folderId) {
253
259
  pendingCloudConfig.folderId = await gDriveFindOrCreateFolder() || undefined;
254
260
  if (pendingCloudConfig.folderId)
255
261
  saveFolderIdToConfig(pendingCloudConfig.folderId);
@@ -258,7 +264,7 @@ export async function cloudRead(filename) {
258
264
  return null;
259
265
  }
260
266
  }
261
- const provider = getCloudProvider(pendingCloudConfig.provider, pendingCloudConfig.folderId);
267
+ const provider = getCloudProvider(pendingCloudConfig.provider, cloudLocator(pendingCloudConfig));
262
268
  if (!provider) {
263
269
  setCloudError(`No cloud provider for ${pendingCloudConfig.provider}`, { op: "read", filename });
264
270
  return null;
@@ -296,14 +302,14 @@ export async function cloudRead(filename) {
296
302
  export async function cloudReadBinary(filename) {
297
303
  if (!pendingCloudConfig)
298
304
  return null;
299
- if (!pendingCloudConfig.folderId) {
305
+ if (pendingCloudConfig.provider !== "onedrive" && !pendingCloudConfig.folderId) {
300
306
  pendingCloudConfig.folderId = await gDriveFindOrCreateFolder() || undefined;
301
307
  if (pendingCloudConfig.folderId)
302
308
  saveFolderIdToConfig(pendingCloudConfig.folderId);
303
309
  if (!pendingCloudConfig.folderId)
304
310
  return null;
305
311
  }
306
- const provider = getCloudProvider(pendingCloudConfig.provider, pendingCloudConfig.folderId);
312
+ const provider = getCloudProvider(pendingCloudConfig.provider, cloudLocator(pendingCloudConfig));
307
313
  if (!provider?.readBinary)
308
314
  return null;
309
315
  try {
@@ -327,8 +333,8 @@ export async function cloudWrite(filename, content) {
327
333
  setCloudError(err, { op: "write", filename });
328
334
  throw new Error(err);
329
335
  }
330
- // Ensure we have a folder ID
331
- if (!pendingCloudConfig.folderId) {
336
+ // Ensure we have a folder ID (gdrive only — onedrive is path-addressed)
337
+ if (pendingCloudConfig.provider !== "onedrive" && !pendingCloudConfig.folderId) {
332
338
  pendingCloudConfig.folderId = await gDriveFindOrCreateFolder() || undefined;
333
339
  if (pendingCloudConfig.folderId)
334
340
  saveFolderIdToConfig(pendingCloudConfig.folderId);
@@ -338,7 +344,7 @@ export async function cloudWrite(filename, content) {
338
344
  throw new Error(err);
339
345
  }
340
346
  }
341
- const provider = getCloudProvider(pendingCloudConfig.provider, pendingCloudConfig.folderId);
347
+ const provider = getCloudProvider(pendingCloudConfig.provider, cloudLocator(pendingCloudConfig));
342
348
  if (!provider) {
343
349
  const err = `No cloud provider for ${pendingCloudConfig.provider}`;
344
350
  setCloudError(err, { op: "write", filename });
@@ -403,8 +409,8 @@ export function getStorageInfo() {
403
409
  }
404
410
  // Provider-based entry — check for API mode (folderId) first, then mount
405
411
  const name = (entry.provider === "gdrive" || entry.provider === "google") ? "gdrive" : entry.provider;
406
- if (entry.folderId) {
407
- // Has folder ID → API mode (don't scan filesystem for mounts)
412
+ if (entry.folderId || entry.provider === "onedrive") {
413
+ // Has folder ID (or is path-addressed onedrive) → API mode (don't scan filesystem for mounts)
408
414
  return { provider: name, mode: "api", cloudPath: entry.path, folderName: entry.path, folderId: entry.folderId, configDir, cloudError: lastCloudError || undefined };
409
415
  }
410
416
  const resolved = resolveSharedEntry(entry);
@@ -1385,17 +1391,25 @@ export async function initCloudConfig(provider = "gdrive") {
1385
1391
  const existing = readLocalConfig();
1386
1392
  if (existing.sharedDir)
1387
1393
  return; // Already configured
1388
- // Find or create the settings folder via Drive API. Stored path is the
1389
- // canonical post-rebrand location; the folderId is what actually drives
1390
- // subsequent reads/writes, so even legacy users with the old folder name
1391
- // work fine since gDriveFindOrCreateFolder resolves whichever exists.
1392
- const folderId = await gDriveFindOrCreateFolder();
1393
- const sharedDir = { provider, path: "home/.rmfmail", folderId: folderId || undefined };
1394
+ let sharedDir;
1395
+ if (provider === "onedrive") {
1396
+ // Path-addressed no folder discovery; the first write creates the
1397
+ // folder chain (oneDriveEnsurePath). Same layout convention as Drive.
1398
+ sharedDir = { provider, path: "home/.rmfmail" };
1399
+ }
1400
+ else {
1401
+ // Find or create the settings folder via Drive API. Stored path is the
1402
+ // canonical post-rebrand location; the folderId is what actually drives
1403
+ // subsequent reads/writes, so even legacy users with the old folder name
1404
+ // work fine since gDriveFindOrCreateFolder resolves whichever exists.
1405
+ const folderId = await gDriveFindOrCreateFolder();
1406
+ sharedDir = { provider, path: "home/.rmfmail", folderId: folderId || undefined };
1407
+ }
1394
1408
  const config = { ...existing, sharedDir, storePath: existing.storePath || DEFAULT_STORE_PATH };
1395
1409
  fs.mkdirSync(LOCAL_DIR, { recursive: true });
1396
1410
  atomicWrite(LOCAL_CONFIG_PATH, config);
1397
1411
  pendingCloudConfig = sharedDir;
1398
- console.log(` Initialized cloud config: ${provider} (folder ID: ${folderId || "pending"})`);
1412
+ console.log(` Initialized cloud config: ${provider} (${sharedDir.folderId ? `folder ID: ${sharedDir.folderId}` : sharedDir.path})`);
1399
1413
  }
1400
1414
  const DEFAULT_SETTINGS = {
1401
1415
  accounts: [],
@@ -1437,9 +1451,9 @@ export { DEFAULT_SETTINGS, DEFAULT_ALLOWLIST, DEFAULT_PREFERENCES, DEFAULT_AUTOC
1437
1451
  * bearing — the app works without them.
1438
1452
  */
1439
1453
  export async function deployDocs(appVersion) {
1440
- if (!pendingCloudConfig?.folderId)
1454
+ if (!pendingCloudConfig || (pendingCloudConfig.provider !== "onedrive" && !pendingCloudConfig.folderId))
1441
1455
  return;
1442
- const provider = getCloudProvider(pendingCloudConfig.provider, pendingCloudConfig.folderId);
1456
+ const provider = getCloudProvider(pendingCloudConfig.provider, cloudLocator(pendingCloudConfig));
1443
1457
  if (!provider)
1444
1458
  return;
1445
1459
  // Resolve the source docs dir. In dev the workspace has `app/docs/`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/mailx-settings",
3
- "version": "0.1.34",
3
+ "version": "0.1.35",
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.22",
20
+ "@bobfrankston/mailx-types": "^0.1.24",
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.22",
36
+ "@bobfrankston/mailx-types": "^0.1.24",
37
37
  "jsonc-parser": "^3.3.1"
38
38
  }
39
39
  }