From 5dc6be13ef8e2670acc643684e269f7f59ae69c1 Mon Sep 17 00:00:00 2001 From: well0nez Date: Fri, 6 Feb 2026 14:09:43 +0100 Subject: [PATCH] fix: support XDG paths on Windows for opencode compatibility opencode uses XDG-style paths (~/.config, ~/.local/share, ~/.local/state) on Windows instead of standard AppData paths. This causes the sync plugin to look in the wrong locations for sessions, secrets, state, and config data. The fix adds XDG path detection to resolveXdgPaths() on win32: - Honor XDG_CONFIG_HOME/XDG_DATA_HOME/XDG_STATE_HOME env vars when set - Auto-detect XDG layout by checking for ~/.config/opencode existence - Fall back to standard Windows AppData paths only if neither applies --- src/sync/paths.test.ts | 30 ++++++++++++++++++++++++++++++ src/sync/paths.ts | 19 ++++++++++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/sync/paths.test.ts b/src/sync/paths.test.ts index 06764f4..2b7a01d 100644 --- a/src/sync/paths.test.ts +++ b/src/sync/paths.test.ts @@ -23,6 +23,36 @@ describe('resolveXdgPaths', () => { expect(paths.configDir).toBe('C:\\Users\\Test\\AppData\\Roaming'); expect(paths.dataDir).toBe('C:\\Users\\Test\\AppData\\Local'); }); + + it('respects XDG env vars on windows', () => { + const env = { + USERPROFILE: 'C:\\Users\\Test', + APPDATA: 'C:\\Users\\Test\\AppData\\Roaming', + LOCALAPPDATA: 'C:\\Users\\Test\\AppData\\Local', + XDG_CONFIG_HOME: 'C:\\Users\\Test\\.config', + XDG_DATA_HOME: 'C:\\Users\\Test\\.local\\share', + XDG_STATE_HOME: 'C:\\Users\\Test\\.local\\state', + } as NodeJS.ProcessEnv; + const paths = resolveXdgPaths(env, 'win32'); + + expect(paths.configDir).toBe('C:\\Users\\Test\\.config'); + expect(paths.dataDir).toBe('C:\\Users\\Test\\.local\\share'); + expect(paths.stateDir).toBe('C:\\Users\\Test\\.local\\state'); + }); + + it('prefers XDG on windows when only XDG_CONFIG_HOME is set', () => { + const env = { + USERPROFILE: 'C:\\Users\\Test', + APPDATA: 'C:\\Users\\Test\\AppData\\Roaming', + LOCALAPPDATA: 'C:\\Users\\Test\\AppData\\Local', + XDG_CONFIG_HOME: 'C:\\Users\\Test\\.config', + } as NodeJS.ProcessEnv; + const paths = resolveXdgPaths(env, 'win32'); + + expect(paths.configDir).toBe('C:\\Users\\Test\\.config'); + expect(paths.dataDir).toBe('C:\\Users\\Test\\.local\\share'); + expect(paths.stateDir).toBe('C:\\Users\\Test\\.local\\state'); + }); }); describe('resolveSyncLocations', () => { diff --git a/src/sync/paths.ts b/src/sync/paths.ts index 6440394..071b320 100644 --- a/src/sync/paths.ts +++ b/src/sync/paths.ts @@ -1,4 +1,5 @@ import crypto from 'node:crypto'; +import fs from 'node:fs'; import path from 'node:path'; import type { SyncConfig } from './config.js'; @@ -83,9 +84,25 @@ export function resolveXdgPaths( } if (platform === 'win32') { + // On Windows, prefer XDG environment variables when explicitly set. + // Some tools (including opencode itself) use XDG-style paths on Windows + // (e.g. ~/.config, ~/.local/share, ~/.local/state) instead of AppData. + // If XDG vars are set, honor them. Otherwise, detect whether opencode + // is using XDG layout by checking for ~/.config/opencode, and fall back + // to standard Windows paths only if XDG paths don't exist. + const xdgConfigDir = env.XDG_CONFIG_HOME ?? path.join(homeDir, '.config'); + const xdgDataDir = env.XDG_DATA_HOME ?? path.join(homeDir, '.local', 'share'); + const xdgStateDir = env.XDG_STATE_HOME ?? path.join(homeDir, '.local', 'state'); + + const hasXdgEnv = Boolean(env.XDG_CONFIG_HOME ?? env.XDG_DATA_HOME ?? env.XDG_STATE_HOME); + const xdgConfigExists = fs.existsSync(path.join(xdgConfigDir, 'opencode')); + + if (hasXdgEnv || xdgConfigExists) { + return { homeDir, configDir: xdgConfigDir, dataDir: xdgDataDir, stateDir: xdgStateDir }; + } + const configDir = env.APPDATA ?? path.join(homeDir, 'AppData', 'Roaming'); const dataDir = env.LOCALAPPDATA ?? path.join(homeDir, 'AppData', 'Local'); - // Windows doesn't have XDG_STATE_HOME equivalent, use LOCALAPPDATA const stateDir = env.LOCALAPPDATA ?? path.join(homeDir, 'AppData', 'Local'); return { homeDir, configDir, dataDir, stateDir }; }