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
22 changes: 11 additions & 11 deletions .github/workflows/opencode-smoke.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Opencode Smoke
name: opencode smoke

on:
workflow_dispatch:
Expand Down Expand Up @@ -45,17 +45,17 @@ jobs:

- name: Install opencode
env:
OPENCODE_INSTALL_DIR: ${{ runner.temp }}/opencode/bin
opencode_install_dir: ${{ runner.temp }}/opencode/bin
run: |
curl -fsSL https://opencode.ai/install | bash
echo "${OPENCODE_INSTALL_DIR}" >> "$GITHUB_PATH"
echo "${opencode_install_dir}" >> "$GITHUB_PATH"

- name: Configure clean opencode home
env:
OPENCODE_HOME: ${{ runner.temp }}/opencode-home
opencode_home: ${{ runner.temp }}/opencode-home
PLUGIN_VERSION: ${{ steps.version.outputs.version }}
run: |
export HOME="$OPENCODE_HOME"
export HOME="$opencode_home"
export XDG_CONFIG_HOME="$HOME/.config"
export XDG_CACHE_HOME="$HOME/.cache"
export XDG_DATA_HOME="$HOME/.local/share"
Expand Down Expand Up @@ -89,11 +89,11 @@ jobs:

- name: Launch opencode (smoke)
env:
OPENCODE_HOME: ${{ runner.temp }}/opencode-home
OPENCODE_SMOKE_TIMEOUT: ${{ inputs.timeout }}
OPENCODE_SMOKE_LOG: ${{ runner.temp }}/opencode-smoke.log
opencode_home: ${{ runner.temp }}/opencode-home
opencode_smoke_timeout: ${{ inputs.timeout }}
opencode_smoke_log: ${{ runner.temp }}/opencode-smoke.log
run: |
export HOME="$OPENCODE_HOME"
export HOME="$opencode_home"
export XDG_CONFIG_HOME="$HOME/.config"
export XDG_CACHE_HOME="$HOME/.cache"
export XDG_DATA_HOME="$HOME/.local/share"
Expand All @@ -106,8 +106,8 @@ jobs:
import time
import sys

timeout = int(os.environ.get("OPENCODE_SMOKE_TIMEOUT", "20"))
log_path = os.environ.get("OPENCODE_SMOKE_LOG", "opencode-smoke.log")
timeout = int(os.environ.get("opencode_smoke_timeout", "20"))
log_path = os.environ.get("opencode_smoke_log", "opencode-smoke.log")
cmd = ["opencode", "serve", "--port", "4096", "--hostname", "127.0.0.1"]

proc = subprocess.Popen(
Expand Down
2 changes: 1 addition & 1 deletion .mise/tasks/local-pack-test
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
#MISE description="Install packed plugin into OpenCode cache (production-like test)"
#MISE description="Install packed plugin into opencode cache (production-like test)"
set -euo pipefail

PLUGIN_LOCAL_FILE="${HOME}/.config/opencode/plugin/opencode-synced-local.ts"
Expand Down
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,6 @@

## Project Context

- **Type**: ES Module package for OpenCode plugin system
- **Type**: ES Module package for opencode plugin system
- **Target**: Bun runtime, ES2021+
- **Purpose**: Sync global OpenCode config across machines via GitHub
- **Purpose**: Sync global opencode config across machines via GitHub
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,11 @@ All notable changes to this project will be documented here by Release Please.

### Features

- **sync**: Initial implementation of OpenCode configuration sync via GitHub
- **sync**: Initial implementation of opencode configuration sync via GitHub
- **sync**: Support for secrets sync in private repositories
- **sync**: Support for session history sync
- **sync**: Support for prompt stash sync
- **sync**: Added `/sync-*` slash commands for easy management
- **sync**: Added automatic sync on OpenCode startup
- **sync**: Added automatic sync on opencode startup
- **sync**: Added AI-powered conflict resolution with `/sync-resolve`
- **sync**: Automatic GitHub repository detection and creation during initialization
22 changes: 11 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# opencode-synced

Sync global OpenCode configuration across machines via a GitHub repo, with optional secrets support for private repos.
Sync global opencode configuration across machines via a GitHub repo, with optional secrets support for private repos.

## Features

- Syncs global OpenCode config (`~/.config/opencode`) and related directories
- Syncs global opencode config (`~/.config/opencode`) and related directories
- Optional secrets sync when the repo is private
- Optional session sync to share conversation history across machines
- Optional prompt stash sync to share stashed prompts and history across machines
Expand All @@ -19,7 +19,7 @@ Sync global OpenCode configuration across machines via a GitHub repo, with optio

## Setup

Enable the plugin in your global OpenCode config (OpenCode will install it on next run):
Enable the plugin in your global opencode config (opencode will install it on next run):

```jsonc
{
Expand All @@ -28,7 +28,7 @@ Enable the plugin in your global OpenCode config (OpenCode will install it on ne
}
```

OpenCode does not auto-update plugins. To update, modify the version number in your config file.
opencode does not auto-update plugins. To update, modify the version number in your config file.

## Configure

Expand All @@ -50,7 +50,7 @@ Run `/sync-link` to connect to your existing sync repo:

If auto-detection fails, specify the repo name: `/sync-link my-opencode-config`

After linking, restart OpenCode to apply the synced settings.
After linking, restart opencode to apply the synced settings.

### Custom repo name or org

Expand Down Expand Up @@ -101,7 +101,7 @@ in a private repo, set `"includeMcpSecrets": true` (requires `includeSecrets`).

### Sessions (private repos only)

Sync your OpenCode sessions (conversation history from `/sessions`) across machines by setting `"includeSessions": true`. This requires `includeSecrets` to also be enabled since sessions may contain sensitive data.
Sync your opencode sessions (conversation history from `/sessions`) across machines by setting `"includeSessions": true`. This requires `includeSecrets` to also be enabled since sessions may contain sensitive data.

```jsonc
{
Expand Down Expand Up @@ -158,8 +158,8 @@ If you want MCP secrets committed (private repos only), set `"includeMcpSecrets"
Env var naming rules:

- If the header name already looks like an env var (e.g. `CONTEXT7_API_KEY`), it is used directly.
- Otherwise: `OPENCODE_MCP_<SERVER>_<HEADER>` (uppercase, non-alphanumerics become `_`).
- OAuth client secrets use `OPENCODE_MCP_<SERVER>_OAUTH_CLIENT_SECRET`.
- Otherwise: `opencode_mcp_<SERVER>_<HEADER>` (non-alphanumerics become `_`).
- OAuth client secrets use `opencode_mcp_<SERVER>_OAUTH_CLIENT_SECRET`.

## Usage

Expand All @@ -178,7 +178,7 @@ Env var naming rules:

### Trigger a sync

Restart OpenCode to run the startup sync flow (pull remote, apply if changed, push local changes if needed).
Restart opencode to run the startup sync flow (pull remote, apply if changed, push local changes if needed).

### Check status

Expand Down Expand Up @@ -265,7 +265,7 @@ bun -e '
### Local testing (production-like)

To test the same artifact that would be published, install from a packed tarball
into OpenCode's cache:
into opencode's cache:

```bash
mise run local-pack-test
Expand All @@ -279,7 +279,7 @@ Then set `~/.config/opencode/opencode.json` to use:
}
```

Restart OpenCode to pick up the cached install.
Restart opencode to pick up the cached install.


## Prefer a CLI version?
Expand Down
10 changes: 3 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "opencode-synced",
"version": "0.7.0",
"description": "Sync global OpenCode config across machines via GitHub.",
"description": "Sync global opencode config across machines via GitHub.",
"author": {
"name": "Ian Hildebrand"
},
Expand All @@ -22,9 +22,7 @@
"publishConfig": {
"access": "public"
},
"files": [
"dist"
],
"files": ["dist"],
"dependencies": {
"@opencode-ai/plugin": "1.0.85"
},
Expand All @@ -51,8 +49,6 @@
"prepare": "husky"
},
"lint-staged": {
"*.{js,ts,json}": [
"biome check --write --no-errors-on-unmatched"
]
"*.{js,ts,json}": ["biome check --write --no-errors-on-unmatched"]
}
}
4 changes: 2 additions & 2 deletions src/command/sync-link.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ description: Link this computer to an existing sync repo
Use the opencode_sync tool with command "link".
This command is for linking a second (or additional) computer to an existing sync repo that was created on another machine.

IMPORTANT: This will OVERWRITE the local OpenCode configuration with the contents from the synced repo. The only thing preserved is the local overrides file (opencode-synced.overrides.jsonc).
IMPORTANT: This will OVERWRITE the local opencode configuration with the contents from the synced repo. The only thing preserved is the local overrides file (opencode-synced.overrides.jsonc).

If the user provides a repo name argument, pass it as name="repo-name".
If no repo name is provided, the tool will automatically search for common sync repo names.

After linking:
- Remind the user to restart OpenCode to apply the synced config
- Remind the user to restart opencode to apply the synced config
- If they want to enable secrets sync, they should run /sync-enable-secrets
4 changes: 2 additions & 2 deletions src/command/sync-pull.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
description: Pull and apply synced OpenCode config
description: Pull and apply synced opencode config
---

Use the opencode_sync tool with command "pull".
If updates are applied, remind the user to restart OpenCode.
If updates are applied, remind the user to restart opencode.
2 changes: 1 addition & 1 deletion src/command/sync-push.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
description: Push local OpenCode config to the sync repo
description: Push local opencode config to the sync repo
---

Use the opencode_sync tool with command "push".
8 changes: 4 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,12 @@ async function loadCommands(): Promise<ParsedCommand[]> {
return commands;
}

export const OpencodeConfigSync: Plugin = async (ctx) => {
export const opencodeConfigSync: Plugin = async (ctx) => {
const commands = await loadCommands();
const service = createSyncService(ctx);

const syncTool = tool({
description: 'Manage OpenCode config sync with a GitHub repo',
description: 'Manage opencode config sync with a GitHub repo',
args: {
command: tool.schema
.enum(['status', 'init', 'link', 'pull', 'push', 'enable-secrets', 'resolve'])
Expand Down Expand Up @@ -229,8 +229,8 @@ export const OpencodeConfigSync: Plugin = async (ctx) => {
};
};

export const OpencodeSynced = OpencodeConfigSync;
export default OpencodeConfigSync;
export const opencodeSynced = opencodeConfigSync;
export default opencodeConfigSync;

function formatError(error: unknown): string {
if (error instanceof Error) return error.message;
Expand Down
4 changes: 2 additions & 2 deletions src/sync/commit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export async function generateCommitMessage(
repoDir: string,
fallbackDate = new Date()
): Promise<string> {
const fallback = `Sync OpenCode config (${formatDate(fallbackDate)})`;
const fallback = `Sync opencode config (${formatDate(fallbackDate)})`;

const diffSummary = await getDiffSummary(ctx.$, repoDir);
if (!diffSummary) return fallback;
Expand All @@ -24,7 +24,7 @@ export async function generateCommitMessage(

const prompt = [
'Generate a concise single-line git commit message (max 72 chars).',
'Focus on OpenCode config sync changes.',
'Focus on opencode config sync changes.',
'Return only the message, no quotes.',
'',
'Diff summary:',
Expand Down
6 changes: 3 additions & 3 deletions src/sync/mcp-secrets.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,11 @@ describe('extractMcpSecrets', () => {
mcp: {
github: {
headers: {
Authorization: 'Bearer {env:OPENCODE_MCP_GITHUB_AUTHORIZATION}',
Authorization: 'Bearer {env:opencode_mcp_GITHUB_AUTHORIZATION}',
},
oauth: {
clientId: 'public',
clientSecret: '{env:OPENCODE_MCP_GITHUB_OAUTH_CLIENT_SECRET}',
clientSecret: '{env:opencode_mcp_GITHUB_OAUTH_CLIENT_SECRET}',
},
},
},
Expand All @@ -120,7 +120,7 @@ describe('extractMcpSecrets', () => {
mcp: {
gitlab: {
headers: {
Authorization: 'Token {env:OPENCODE_MCP_GITLAB_AUTHORIZATION}',
Authorization: 'Token {env:opencode_mcp_GITLAB_AUTHORIZATION}',
},
},
},
Expand Down
2 changes: 1 addition & 1 deletion src/sync/mcp-secrets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ function buildHeaderEnvVar(serverName: string, headerName: string): string {
function buildEnvVar(serverName: string, key: string): string {
const serverToken = toEnvToken(serverName, 'SERVER');
const keyToken = toEnvToken(key, 'VALUE');
return `OPENCODE_MCP_${serverToken}_${keyToken}`;
return `opencode_mcp_${serverToken}_${keyToken}`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This generates a mixed-case environment variable (e.g., opencode_mcp_GITHUB_AUTHORIZATION), which is inconsistent with other variables in this PR that are fully lowercase (e.g., opencode_config_dir). For better consistency across all environment variables, consider converting the entire generated variable to lowercase. Note that this would also require updating the corresponding test expectations.

Suggested change
return `opencode_mcp_${serverToken}_${keyToken}`;
return `opencode_mcp_${serverToken}_${keyToken}`.toLowerCase();

}

function toEnvToken(input: string, fallback: string): string {
Expand Down
4 changes: 2 additions & 2 deletions src/sync/paths.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ describe('resolveXdgPaths', () => {
});

describe('resolveSyncLocations', () => {
it('respects OPENCODE_CONFIG_DIR', () => {
it('respects opencode_config_dir', () => {
const env = {
HOME: '/home/test',
OPENCODE_CONFIG_DIR: '/custom/opencode',
opencode_config_dir: '/custom/opencode',
} as NodeJS.ProcessEnv;
const locations = resolveSyncLocations(env, 'linux');

Expand Down
2 changes: 1 addition & 1 deletion src/sync/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export function resolveSyncLocations(
platform: NodeJS.Platform = process.platform
): SyncLocations {
const xdg = resolveXdgPaths(env, platform);
const customConfigDir = env.OPENCODE_CONFIG_DIR;
const customConfigDir = env.opencode_config_dir;
const configRoot = customConfigDir
? path.resolve(expandHome(customConfigDir, xdg.homeDir))
: path.join(xdg.configDir, 'opencode');
Expand Down
12 changes: 6 additions & 6 deletions src/sync/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,17 +296,17 @@ export function createSyncService(ctx: SyncServiceContext): SyncService {
const lines = [
`Linked to existing sync repo: ${found.owner}/${found.name}`,
'',
'Your local OpenCode config has been OVERWRITTEN with the synced config.',
'Your local opencode config has been OVERWRITTEN with the synced config.',
'Your local overrides file was preserved and applied on top.',
'',
'Restart OpenCode to apply the new settings.',
'Restart opencode to apply the new settings.',
'',
found.isPrivate
? 'To enable secrets sync, run: /sync-enable-secrets'
: 'Note: Repo is public. Secrets sync is disabled.',
];

await showToast(ctx.client, 'Config synced. Restart OpenCode to apply.', 'info');
await showToast(ctx.client, 'Config synced. Restart opencode to apply.', 'info');
return lines.join('\n');
}),
pull: () =>
Expand Down Expand Up @@ -339,8 +339,8 @@ export function createSyncService(ctx: SyncServiceContext): SyncService {
lastRemoteUpdate: new Date().toISOString(),
});

await showToast(ctx.client, 'Config updated. Restart OpenCode to apply.', 'info');
return 'Remote config applied. Restart OpenCode to use new settings.';
await showToast(ctx.client, 'Config updated. Restart opencode to apply.', 'info');
return 'Remote config applied. Restart opencode to use new settings.';
}),
push: () =>
runExclusive(async () => {
Expand Down Expand Up @@ -469,7 +469,7 @@ async function runStartup(
lastPull: new Date().toISOString(),
lastRemoteUpdate: new Date().toISOString(),
});
await showToast(ctx.client, 'Config updated. Restart OpenCode to apply.', 'info');
await showToast(ctx.client, 'Config updated. Restart opencode to apply.', 'info');
return;
}

Expand Down
Loading