From a3a0cd4c08f81bfd1d92d831d72682706c9bfaa7 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 14 Mar 2026 21:59:20 +0000 Subject: [PATCH 1/4] feat: add support for syncing skills directory Add `includeSkills` option to sync configuration, which defaults to true. This allows the `~/.config/opencode/skills/` directory to be synced alongside other configuration files and directories. Updated: - `src/sync/config.ts`: Added `includeSkills` to interfaces and normalization logic. - `src/sync/paths.ts`: Added `skills` directory to sync plan items. - `src/index.ts`: Exposed `includeSkills` in `opencode_sync` tool arguments. - `src/sync/service.ts`: Handled `includeSkills` in init and status commands. - `README.md`: Documented the new option and synced path. - `src/sync/config.test.ts` & `src/sync/paths.test.ts`: Added tests for the new feature. Co-authored-by: iHildy <25069719+iHildy@users.noreply.github.com> --- README.md | 2 ++ src/index.ts | 5 +++++ src/sync/config.test.ts | 5 +++++ src/sync/config.ts | 4 ++++ src/sync/paths.test.ts | 29 +++++++++++++++++++++++++++++ src/sync/paths.ts | 11 +++++++++++ src/sync/service.ts | 4 ++++ 7 files changed, 60 insertions(+) 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..a280544 100644 --- a/src/index.ts +++ b/src/index.ts @@ -152,6 +152,10 @@ 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 +179,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, From 75bdde897e43360165a49e02ae75daa915e9aa72 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 14 Mar 2026 22:00:30 +0000 Subject: [PATCH 2/4] feat: add support for syncing skills directory Add `includeSkills` option to sync configuration, which defaults to true. This allows the `~/.config/opencode/skills/` directory to be synced alongside other configuration files and directories. Updated: - `src/sync/config.ts`: Added `includeSkills` to interfaces and normalization logic. - `src/sync/paths.ts`: Added `skills` directory to sync plan items. - `src/index.ts`: Exposed `includeSkills` in `opencode_sync` tool arguments. - `src/sync/service.ts`: Handled `includeSkills` in init and status commands. - `README.md`: Documented the new option and synced path. - `src/sync/config.test.ts` & `src/sync/paths.test.ts`: Added tests for the new feature. Co-authored-by: iHildy <25069719+iHildy@users.noreply.github.com> From 4564ae23b485bc85a6a0d9992ece097c4c14bdee Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 14 Mar 2026 22:02:19 +0000 Subject: [PATCH 3/4] feat: add support for syncing skills directory Add `includeSkills` option to sync configuration, which defaults to true. This allows the `~/.config/opencode/skills/` directory to be synced alongside other configuration files and directories. Updated: - `src/sync/config.ts`: Added `includeSkills` to interfaces and normalization logic. - `src/sync/paths.ts`: Added `skills` directory to sync plan items. - `src/index.ts`: Exposed `includeSkills` in `opencode_sync` tool arguments. - `src/sync/service.ts`: Handled `includeSkills` in init and status commands. - `README.md`: Documented the new option and synced path. - `src/sync/config.test.ts` & `src/sync/paths.test.ts`: Added tests for the new feature. Co-authored-by: iHildy <25069719+iHildy@users.noreply.github.com> From b5c70907f7defed8b200895c522b914cc06de2ff Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 14 Mar 2026 22:05:25 +0000 Subject: [PATCH 4/4] feat: add support for syncing skills directory Add `includeSkills` option to sync configuration, which defaults to true. This allows the `~/.config/opencode/skills/` directory to be synced alongside other configuration files and directories. Updated: - `src/sync/config.ts`: Added `includeSkills` to interfaces and normalization logic. - `src/sync/paths.ts`: Added `skills` directory to sync plan items. - `src/index.ts`: Exposed `includeSkills` in `opencode_sync` tool arguments. - `src/sync/service.ts`: Handled `includeSkills` in init and status commands. - `README.md`: Documented the new option and synced path. - `src/sync/config.test.ts` & `src/sync/paths.test.ts`: Added tests for the new feature. Co-authored-by: iHildy <25069719+iHildy@users.noreply.github.com> --- src/index.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/index.ts b/src/index.ts index a280544..31bc247 100644 --- a/src/index.ts +++ b/src/index.ts @@ -152,10 +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'), + 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(),