Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ Create `~/.config/opencode/opencode-synced.jsonc`:
"includeMcpSecrets": false,
"includeSessions": false,
"includePromptStash": false,
"includeModelFavorites": true,
"extraSecretPaths": [],
"extraConfigPaths": [],
}
Expand All @@ -88,6 +89,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/`
- `~/.local/state/opencode/model.json` (model favorites)
- Any extra paths in `extraConfigPaths` (allowlist, files or folders)

### Secrets (private repos only)
Expand Down
1 change: 1 addition & 0 deletions src/command/sync-init.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ If the user wants a public repo, pass private=false.
Include includeSecrets if the user explicitly opts in.
Include includeMcpSecrets only if they want MCP secrets committed to a private repo.
If the user supplies extra config paths, pass extraConfigPaths.
Model favorites sync is enabled by default; set includeModelFavorites=false to disable.
5 changes: 5 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,10 @@ export const OpencodeConfigSync: Plugin = async (ctx) => {
.boolean()
.optional()
.describe('Enable prompt stash/history sync (requires includeSecrets)'),
includeModelFavorites: tool.schema
.boolean()
.optional()
.describe('Sync model favorites (state/model.json)'),
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(),
Expand All @@ -159,6 +163,7 @@ export const OpencodeConfigSync: Plugin = async (ctx) => {
includeMcpSecrets: args.includeMcpSecrets,
includeSessions: args.includeSessions,
includePromptStash: args.includePromptStash,
includeModelFavorites: args.includeModelFavorites,
create: args.create,
private: args.private,
extraSecretPaths: args.extraSecretPaths,
Expand Down
5 changes: 5 additions & 0 deletions src/sync/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ describe('normalizeSyncConfig', () => {
});
expect(normalized.includeMcpSecrets).toBe(true);
});

it('enables model favorites by default', () => {
const normalized = normalizeSyncConfig({});
expect(normalized.includeModelFavorites).toBe(true);
});
});

describe('canCommitMcpSecrets', () => {
Expand Down
3 changes: 3 additions & 0 deletions src/sync/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface SyncConfig {
includeMcpSecrets?: boolean;
includeSessions?: boolean;
includePromptStash?: boolean;
includeModelFavorites?: boolean;
extraSecretPaths?: string[];
extraConfigPaths?: string[];
}
Expand Down Expand Up @@ -48,11 +49,13 @@ export async function chmodIfExists(filePath: string, mode: number): Promise<voi

export function normalizeSyncConfig(config: SyncConfig): SyncConfig {
const includeSecrets = Boolean(config.includeSecrets);
const includeModelFavorites = config.includeModelFavorites !== false;
return {
includeSecrets,
includeMcpSecrets: includeSecrets ? Boolean(config.includeMcpSecrets) : false,
includeSessions: Boolean(config.includeSessions),
includePromptStash: Boolean(config.includePromptStash),
includeModelFavorites,
extraSecretPaths: Array.isArray(config.extraSecretPaths) ? config.extraSecretPaths : [],
extraConfigPaths: Array.isArray(config.extraConfigPaths) ? config.extraConfigPaths : [],
localRepoPath: config.localRepoPath,
Expand Down
28 changes: 28 additions & 0 deletions src/sync/paths.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,32 @@ describe('buildSyncPlan', () => {
expect(plan.extraSecrets.allowlist.length).toBe(1);
expect(plan.extraConfigs.allowlist.length).toBe(1);
});

it('includes model favorites 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(config, locations, '/repo', 'linux');
const favoritesItem = plan.items.find((item) =>
item.localPath.endsWith('/.local/state/opencode/model.json')
);

expect(favoritesItem).toBeTruthy();

const disabledPlan = buildSyncPlan(
{ ...config, includeModelFavorites: false },
locations,
'/repo',
'linux'
);
const disabledItem = disabledPlan.items.find((item) =>
item.localPath.endsWith('/.local/state/opencode/model.json')
);

expect(disabledItem).toBeUndefined();
});
});
15 changes: 13 additions & 2 deletions src/sync/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ const DEFAULT_STATE_NAME = 'sync-state.json';
const CONFIG_DIRS = ['agent', 'command', 'mode', 'tool', 'themes', 'plugin'];
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';

export function resolveHomeDir(
env: NodeJS.ProcessEnv = process.env,
Expand Down Expand Up @@ -173,9 +174,11 @@ export function buildSyncPlan(
): SyncPlan {
const configRoot = locations.configRoot;
const dataRoot = path.join(locations.xdg.dataDir, 'opencode');
const stateRoot = path.join(locations.xdg.stateDir, 'opencode');
const repoConfigRoot = path.join(repoRoot, 'config');
const repoDataRoot = path.join(repoRoot, 'data');
const repoSecretsRoot = path.join(repoRoot, 'secrets');
const repoStateRoot = path.join(repoRoot, 'state');
const repoExtraDir = path.join(repoSecretsRoot, 'extra');
const manifestPath = path.join(repoSecretsRoot, 'extra-manifest.json');
const repoConfigExtraDir = path.join(repoConfigRoot, 'extra');
Expand Down Expand Up @@ -207,6 +210,16 @@ export function buildSyncPlan(
});
}

if (config.includeModelFavorites !== false) {
items.push({
localPath: path.join(stateRoot, MODEL_FAVORITES_FILE),
repoPath: path.join(repoStateRoot, MODEL_FAVORITES_FILE),
type: 'file',
isSecret: false,
isConfigFile: false,
});
}

if (config.includeSecrets) {
items.push(
{
Expand Down Expand Up @@ -238,8 +251,6 @@ export function buildSyncPlan(
}

if (config.includePromptStash) {
const stateRoot = path.join(locations.xdg.stateDir, 'opencode');
const repoStateRoot = path.join(repoRoot, 'state');
for (const fileName of PROMPT_STASH_FILES) {
items.push({
localPath: path.join(stateRoot, fileName),
Expand Down
4 changes: 4 additions & 0 deletions src/sync/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ interface InitOptions {
includeMcpSecrets?: boolean;
includeSessions?: boolean;
includePromptStash?: boolean;
includeModelFavorites?: boolean;
create?: boolean;
private?: boolean;
extraSecretPaths?: string[];
Expand Down Expand Up @@ -175,6 +176,7 @@ export function createSyncService(ctx: SyncServiceContext): SyncService {
const includeMcpSecrets = config.includeMcpSecrets ? 'enabled' : 'disabled';
const includeSessions = config.includeSessions ? 'enabled' : 'disabled';
const includePromptStash = config.includePromptStash ? 'enabled' : 'disabled';
const includeModelFavorites = config.includeModelFavorites ? 'enabled' : 'disabled';
const lastPull = state.lastPull ?? 'never';
const lastPush = state.lastPush ?? 'never';

Expand All @@ -195,6 +197,7 @@ export function createSyncService(ctx: SyncServiceContext): SyncService {
`MCP secrets: ${includeMcpSecrets}`,
`Sessions: ${includeSessions}`,
`Prompt stash: ${includePromptStash}`,
`Model favorites: ${includeModelFavorites}`,
`Last pull: ${lastPull}`,
`Last push: ${lastPush}`,
`Working tree: ${changesLabel}`,
Expand Down Expand Up @@ -539,6 +542,7 @@ async function buildConfigFromInit($: Shell, options: InitOptions) {
includeMcpSecrets: options.includeMcpSecrets ?? false,
includeSessions: options.includeSessions ?? false,
includePromptStash: options.includePromptStash ?? false,
includeModelFavorites: options.includeModelFavorites ?? true,
extraSecretPaths: options.extraSecretPaths ?? [],
extraConfigPaths: options.extraConfigPaths ?? [],
localRepoPath: options.localRepoPath,
Expand Down
Loading