diff --git a/README.md b/README.md index e296e79..1580f67 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,7 @@ Create `~/.config/opencode/opencode-synced.jsonc`: "includeSessions": false, "includePromptStash": false, "includeModelFavorites": true, + "includeSkills": true, "extraSecretPaths": [], "extraConfigPaths": [], } @@ -89,6 +90,7 @@ Create `~/.config/opencode/opencode-synced.jsonc`: - `~/.config/opencode/opencode.json` and `opencode.jsonc` - `~/.config/opencode/AGENTS.md` - `~/.config/opencode/agent/`, `command/`, `mode/`, `tool/`, `themes/`, `plugin/` +- `~/.config/opencode/skills/` - `~/.local/state/opencode/model.json` (model favorites) - Any extra paths in `extraConfigPaths` (allowlist, files or folders) diff --git a/src/index.ts b/src/index.ts index 1f314ce..31bc247 100644 --- a/src/index.ts +++ b/src/index.ts @@ -152,6 +152,7 @@ export const opencodeConfigSync: Plugin = async (ctx) => { .boolean() .optional() .describe('Sync model favorites (state/model.json)'), + includeSkills: tool.schema.boolean().optional().describe('Sync skills directory'), create: tool.schema.boolean().optional().describe('Create repo if missing'), private: tool.schema.boolean().optional().describe('Create repo as private'), extraSecretPaths: tool.schema.array(tool.schema.string()).optional(), @@ -175,6 +176,7 @@ export const opencodeConfigSync: Plugin = async (ctx) => { includeSessions: args.includeSessions, includePromptStash: args.includePromptStash, includeModelFavorites: args.includeModelFavorites, + includeSkills: args.includeSkills, create: args.create, private: args.private, extraSecretPaths: args.extraSecretPaths, diff --git a/src/sync/config.test.ts b/src/sync/config.test.ts index 66b8894..df5ceee 100644 --- a/src/sync/config.test.ts +++ b/src/sync/config.test.ts @@ -79,6 +79,11 @@ describe('normalizeSyncConfig', () => { expect(normalized.includeModelFavorites).toBe(true); }); + it('enables skills by default', () => { + const normalized = normalizeSyncConfig({}); + expect(normalized.includeSkills).toBe(true); + }); + it('defaults extra path lists when omitted', () => { const normalized = normalizeSyncConfig({ includeSecrets: true }); expect(normalized.extraSecretPaths).toEqual([]); diff --git a/src/sync/config.ts b/src/sync/config.ts index b0d4af5..4067a5a 100644 --- a/src/sync/config.ts +++ b/src/sync/config.ts @@ -32,6 +32,7 @@ export interface SyncConfig { includeSessions?: boolean; includePromptStash?: boolean; includeModelFavorites?: boolean; + includeSkills?: boolean; secretsBackend?: SecretsBackendConfig; extraSecretPaths?: string[]; extraConfigPaths?: string[]; @@ -43,6 +44,7 @@ export interface NormalizedSyncConfig extends SyncConfig { includeSessions: boolean; includePromptStash: boolean; includeModelFavorites: boolean; + includeSkills: boolean; secretsBackend?: SecretsBackendConfig; extraSecretPaths: string[]; extraConfigPaths: string[]; @@ -106,12 +108,14 @@ export function normalizeSecretsBackend( export function normalizeSyncConfig(config: SyncConfig): NormalizedSyncConfig { const includeSecrets = Boolean(config.includeSecrets); const includeModelFavorites = config.includeModelFavorites !== false; + const includeSkills = config.includeSkills !== false; return { includeSecrets, includeMcpSecrets: includeSecrets ? Boolean(config.includeMcpSecrets) : false, includeSessions: Boolean(config.includeSessions), includePromptStash: Boolean(config.includePromptStash), includeModelFavorites, + includeSkills, secretsBackend: normalizeSecretsBackend(config.secretsBackend), extraSecretPaths: Array.isArray(config.extraSecretPaths) ? config.extraSecretPaths : [], extraConfigPaths: Array.isArray(config.extraConfigPaths) ? config.extraConfigPaths : [], diff --git a/src/sync/paths.test.ts b/src/sync/paths.test.ts index 9ede236..a5311be 100644 --- a/src/sync/paths.test.ts +++ b/src/sync/paths.test.ts @@ -161,4 +161,33 @@ describe('buildSyncPlan', () => { expect(disabledItem).toBeUndefined(); }); + + it('includes skills directory by default and allows disabling', () => { + const env = { HOME: '/home/test' } as NodeJS.ProcessEnv; + const locations = resolveSyncLocations(env, 'linux'); + const config: SyncConfig = { + repo: { owner: 'acme', name: 'config' }, + includeSecrets: false, + }; + + const plan = buildSyncPlan(normalizeSyncConfig(config), locations, '/repo', 'linux'); + const skillsItem = plan.items.find((item) => + item.localPath.endsWith('/.config/opencode/skills') + ); + + expect(skillsItem).toBeTruthy(); + expect(skillsItem?.type).toBe('dir'); + + const disabledPlan = buildSyncPlan( + normalizeSyncConfig({ ...config, includeSkills: false }), + locations, + '/repo', + 'linux' + ); + const disabledItem = disabledPlan.items.find((item) => + item.localPath.endsWith('/.config/opencode/skills') + ); + + expect(disabledItem).toBeUndefined(); + }); }); diff --git a/src/sync/paths.ts b/src/sync/paths.ts index 995118d..b9b66c1 100644 --- a/src/sync/paths.ts +++ b/src/sync/paths.ts @@ -52,6 +52,7 @@ const DEFAULT_OVERRIDES_NAME = 'opencode-synced.overrides.jsonc'; const DEFAULT_STATE_NAME = 'sync-state.json'; const CONFIG_DIRS = ['agent', 'command', 'mode', 'tool', 'themes', 'plugin']; +const SKILLS_DIR = 'skills'; const SESSION_DIRS = ['storage/session', 'storage/message', 'storage/part', 'storage/session_diff']; const PROMPT_STASH_FILES = ['prompt-stash.jsonl', 'prompt-history.jsonl']; const MODEL_FAVORITES_FILE = 'model.json'; @@ -224,6 +225,16 @@ export function buildSyncPlan( }); } + if (config.includeSkills !== false) { + items.push({ + localPath: path.join(configRoot, SKILLS_DIR), + repoPath: path.join(repoConfigRoot, SKILLS_DIR), + type: 'dir', + isSecret: false, + isConfigFile: false, + }); + } + if (config.includeSecrets) { if (!usingSecretsBackend) { items.push( diff --git a/src/sync/service.ts b/src/sync/service.ts index b9d4b65..d3d0013 100644 --- a/src/sync/service.ts +++ b/src/sync/service.ts @@ -63,6 +63,7 @@ interface InitOptions { includeSessions?: boolean; includePromptStash?: boolean; includeModelFavorites?: boolean; + includeSkills?: boolean; create?: boolean; private?: boolean; extraSecretPaths?: string[]; @@ -317,6 +318,7 @@ export function createSyncService(ctx: SyncServiceContext): SyncService { const includeSessions = config.includeSessions ? 'enabled' : 'disabled'; const includePromptStash = config.includePromptStash ? 'enabled' : 'disabled'; const includeModelFavorites = config.includeModelFavorites ? 'enabled' : 'disabled'; + const includeSkills = config.includeSkills ? 'enabled' : 'disabled'; const secretsBackend = config.secretsBackend?.type ?? 'none'; const lastPull = state.lastPull ?? 'never'; const lastPush = state.lastPush ?? 'never'; @@ -340,6 +342,7 @@ export function createSyncService(ctx: SyncServiceContext): SyncService { `Sessions: ${includeSessions}`, `Prompt stash: ${includePromptStash}`, `Model favorites: ${includeModelFavorites}`, + `Skills: ${includeSkills}`, `Last pull: ${lastPull}`, `Last push: ${lastPush}`, `Working tree: ${changesLabel}`, @@ -767,6 +770,7 @@ async function buildConfigFromInit($: Shell, options: InitOptions) { includeSessions: options.includeSessions ?? false, includePromptStash: options.includePromptStash ?? false, includeModelFavorites: options.includeModelFavorites ?? true, + includeSkills: options.includeSkills ?? true, extraSecretPaths: options.extraSecretPaths ?? [], extraConfigPaths: options.extraConfigPaths ?? [], localRepoPath: options.localRepoPath,