opencode-synced 0.6.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # opencode-synced
2
2
 
3
- Sync global OpenCode configuration across machines via a GitHub repo, with optional secrets support for private repos.
3
+ Sync global opencode configuration across machines via a GitHub repo, with optional secrets support for private repos.
4
4
 
5
5
  ## Features
6
6
 
7
- - Syncs global OpenCode config (`~/.config/opencode`) and related directories
7
+ - Syncs global opencode config (`~/.config/opencode`) and related directories
8
8
  - Optional secrets sync when the repo is private
9
9
  - Optional session sync to share conversation history across machines
10
10
  - Optional prompt stash sync to share stashed prompts and history across machines
@@ -19,7 +19,7 @@ Sync global OpenCode configuration across machines via a GitHub repo, with optio
19
19
 
20
20
  ## Setup
21
21
 
22
- Enable the plugin in your global OpenCode config (OpenCode will install it on next run):
22
+ Enable the plugin in your global opencode config (opencode will install it on next run):
23
23
 
24
24
  ```jsonc
25
25
  {
@@ -28,12 +28,7 @@ Enable the plugin in your global OpenCode config (OpenCode will install it on ne
28
28
  }
29
29
  ```
30
30
 
31
- OpenCode does not auto-update plugins. To update, remove the cached plugin and restart OpenCode:
32
-
33
- ```bash
34
- rm -rf ~/.cache/opencode/node_modules/opencode-synced
35
- opencode
36
- ```
31
+ opencode does not auto-update plugins. To update, modify the version number in your config file.
37
32
 
38
33
  ## Configure
39
34
 
@@ -55,7 +50,7 @@ Run `/sync-link` to connect to your existing sync repo:
55
50
 
56
51
  If auto-detection fails, specify the repo name: `/sync-link my-opencode-config`
57
52
 
58
- After linking, restart OpenCode to apply the synced settings.
53
+ After linking, restart opencode to apply the synced settings.
59
54
 
60
55
  ### Custom repo name or org
61
56
 
@@ -106,7 +101,7 @@ in a private repo, set `"includeMcpSecrets": true` (requires `includeSecrets`).
106
101
 
107
102
  ### Sessions (private repos only)
108
103
 
109
- 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.
104
+ 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.
110
105
 
111
106
  ```jsonc
112
107
  {
@@ -163,8 +158,8 @@ If you want MCP secrets committed (private repos only), set `"includeMcpSecrets"
163
158
  Env var naming rules:
164
159
 
165
160
  - If the header name already looks like an env var (e.g. `CONTEXT7_API_KEY`), it is used directly.
166
- - Otherwise: `OPENCODE_MCP_<SERVER>_<HEADER>` (uppercase, non-alphanumerics become `_`).
167
- - OAuth client secrets use `OPENCODE_MCP_<SERVER>_OAUTH_CLIENT_SECRET`.
161
+ - Otherwise: `opencode_mcp_<SERVER>_<HEADER>` (non-alphanumerics become `_`).
162
+ - OAuth client secrets use `opencode_mcp_<SERVER>_OAUTH_CLIENT_SECRET`.
168
163
 
169
164
  ## Usage
170
165
 
@@ -183,7 +178,7 @@ Env var naming rules:
183
178
 
184
179
  ### Trigger a sync
185
180
 
186
- Restart OpenCode to run the startup sync flow (pull remote, apply if changed, push local changes if needed).
181
+ Restart opencode to run the startup sync flow (pull remote, apply if changed, push local changes if needed).
187
182
 
188
183
  ### Check status
189
184
 
@@ -270,7 +265,7 @@ bun -e '
270
265
  ### Local testing (production-like)
271
266
 
272
267
  To test the same artifact that would be published, install from a packed tarball
273
- into OpenCode's cache:
268
+ into opencode's cache:
274
269
 
275
270
  ```bash
276
271
  mise run local-pack-test
@@ -284,7 +279,7 @@ Then set `~/.config/opencode/opencode.json` to use:
284
279
  }
285
280
  ```
286
281
 
287
- Restart OpenCode to pick up the cached install.
282
+ Restart opencode to pick up the cached install.
288
283
 
289
284
 
290
285
  ## Prefer a CLI version?
@@ -5,11 +5,11 @@ description: Link this computer to an existing sync repo
5
5
  Use the opencode_sync tool with command "link".
6
6
  This command is for linking a second (or additional) computer to an existing sync repo that was created on another machine.
7
7
 
8
- 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).
8
+ 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).
9
9
 
10
10
  If the user provides a repo name argument, pass it as name="repo-name".
11
11
  If no repo name is provided, the tool will automatically search for common sync repo names.
12
12
 
13
13
  After linking:
14
- - Remind the user to restart OpenCode to apply the synced config
14
+ - Remind the user to restart opencode to apply the synced config
15
15
  - If they want to enable secrets sync, they should run /sync-enable-secrets
@@ -1,6 +1,6 @@
1
1
  ---
2
- description: Pull and apply synced OpenCode config
2
+ description: Pull and apply synced opencode config
3
3
  ---
4
4
 
5
5
  Use the opencode_sync tool with command "pull".
6
- If updates are applied, remind the user to restart OpenCode.
6
+ If updates are applied, remind the user to restart opencode.
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: Push local OpenCode config to the sync repo
2
+ description: Push local opencode config to the sync repo
3
3
  ---
4
4
 
5
5
  Use the opencode_sync tool with command "push".
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  import type { Plugin } from '@opencode-ai/plugin';
2
- export declare const OpencodeConfigSync: Plugin;
3
- export declare const OpencodeSynced: Plugin;
4
- export default OpencodeConfigSync;
2
+ export declare const opencodeConfigSync: Plugin;
3
+ export declare const opencodeSynced: Plugin;
4
+ export default opencodeConfigSync;
package/dist/index.js CHANGED
@@ -85,11 +85,11 @@ async function loadCommands() {
85
85
  }
86
86
  return commands;
87
87
  }
88
- export const OpencodeConfigSync = async (ctx) => {
88
+ export const opencodeConfigSync = async (ctx) => {
89
89
  const commands = await loadCommands();
90
90
  const service = createSyncService(ctx);
91
91
  const syncTool = tool({
92
- description: 'Manage OpenCode config sync with a GitHub repo',
92
+ description: 'Manage opencode config sync with a GitHub repo',
93
93
  args: {
94
94
  command: tool.schema
95
95
  .enum(['status', 'init', 'link', 'pull', 'push', 'enable-secrets', 'resolve'])
@@ -200,8 +200,8 @@ export const OpencodeConfigSync = async (ctx) => {
200
200
  },
201
201
  };
202
202
  };
203
- export const OpencodeSynced = OpencodeConfigSync;
204
- export default OpencodeConfigSync;
203
+ export const opencodeSynced = opencodeConfigSync;
204
+ export default opencodeConfigSync;
205
205
  function formatError(error) {
206
206
  if (error instanceof Error)
207
207
  return error.message;
@@ -1,6 +1,6 @@
1
1
  import { promises as fs } from 'node:fs';
2
2
  import path from 'node:path';
3
- import { deepMerge, hasOwn, parseJsonc, pathExists, stripOverrides, writeJsonFile, } from './config.js';
3
+ import { chmodIfExists, deepMerge, hasOwn, parseJsonc, pathExists, stripOverrides, writeJsonFile, } from './config.js';
4
4
  import { extractMcpSecrets, hasOverrides, mergeOverrides, stripOverrideKeys, } from './mcp-secrets.js';
5
5
  import { normalizePath } from './paths.js';
6
6
  export async function syncRepoToLocal(plan, overrides) {
@@ -117,7 +117,7 @@ async function copyFileWithMode(sourcePath, destinationPath) {
117
117
  const stat = await fs.stat(sourcePath);
118
118
  await fs.mkdir(path.dirname(destinationPath), { recursive: true });
119
119
  await fs.copyFile(sourcePath, destinationPath);
120
- await fs.chmod(destinationPath, stat.mode & 0o777);
120
+ await chmodIfExists(destinationPath, stat.mode & 0o777);
121
121
  }
122
122
  async function copyDirRecursive(sourcePath, destinationPath) {
123
123
  const stat = await fs.stat(sourcePath);
@@ -134,7 +134,7 @@ async function copyDirRecursive(sourcePath, destinationPath) {
134
134
  await copyFileWithMode(entrySource, entryDest);
135
135
  }
136
136
  }
137
- await fs.chmod(destinationPath, stat.mode & 0o777);
137
+ await chmodIfExists(destinationPath, stat.mode & 0o777);
138
138
  }
139
139
  async function removePath(targetPath) {
140
140
  await fs.rm(targetPath, { recursive: true, force: true });
@@ -161,7 +161,7 @@ async function applyExtraSecrets(plan, fromRepo) {
161
161
  if (fromRepo) {
162
162
  await copyFileWithMode(repoPath, localPath);
163
163
  if (entry.mode !== undefined) {
164
- await fs.chmod(localPath, entry.mode);
164
+ await chmodIfExists(localPath, entry.mode);
165
165
  }
166
166
  }
167
167
  }
@@ -1,6 +1,6 @@
1
1
  import { extractTextFromResponse, resolveSmallModel, unwrapData } from './utils.js';
2
2
  export async function generateCommitMessage(ctx, repoDir, fallbackDate = new Date()) {
3
- const fallback = `Sync OpenCode config (${formatDate(fallbackDate)})`;
3
+ const fallback = `Sync opencode config (${formatDate(fallbackDate)})`;
4
4
  const diffSummary = await getDiffSummary(ctx.$, repoDir);
5
5
  if (!diffSummary)
6
6
  return fallback;
@@ -9,7 +9,7 @@ export async function generateCommitMessage(ctx, repoDir, fallbackDate = new Dat
9
9
  return fallback;
10
10
  const prompt = [
11
11
  'Generate a concise single-line git commit message (max 72 chars).',
12
- 'Focus on OpenCode config sync changes.',
12
+ 'Focus on opencode config sync changes.',
13
13
  'Return only the message, no quotes.',
14
14
  '',
15
15
  'Diff summary:',
@@ -20,6 +20,7 @@ export interface SyncState {
20
20
  lastRemoteUpdate?: string;
21
21
  }
22
22
  export declare function pathExists(filePath: string): Promise<boolean>;
23
+ export declare function chmodIfExists(filePath: string, mode: number): Promise<void>;
23
24
  export declare function normalizeSyncConfig(config: SyncConfig): SyncConfig;
24
25
  export declare function canCommitMcpSecrets(config: SyncConfig): boolean;
25
26
  export declare function loadSyncConfig(locations: SyncLocations): Promise<SyncConfig | null>;
@@ -9,6 +9,17 @@ export async function pathExists(filePath) {
9
9
  return false;
10
10
  }
11
11
  }
12
+ export async function chmodIfExists(filePath, mode) {
13
+ try {
14
+ await fs.chmod(filePath, mode);
15
+ }
16
+ catch (error) {
17
+ const maybeErrno = error;
18
+ if (maybeErrno.code === 'ENOENT')
19
+ return;
20
+ throw error;
21
+ }
22
+ }
12
23
  export function normalizeSyncConfig(config) {
13
24
  const includeSecrets = Boolean(config.includeSecrets);
14
25
  return {
@@ -177,7 +188,7 @@ export async function writeJsonFile(filePath, data, options = { jsonc: false })
177
188
  const content = options.jsonc ? `// Generated by opencode-synced\n${json}\n` : `${json}\n`;
178
189
  await fs.writeFile(filePath, content, 'utf8');
179
190
  if (options.mode !== undefined) {
180
- await fs.chmod(filePath, options.mode);
191
+ await chmodIfExists(filePath, options.mode);
181
192
  }
182
193
  }
183
194
  export function isPlainObject(value) {
@@ -0,0 +1,17 @@
1
+ export interface SyncLockInfo {
2
+ pid: number;
3
+ startedAt: string;
4
+ hostname: string;
5
+ }
6
+ export type SyncLockResult = {
7
+ acquired: true;
8
+ info: SyncLockInfo;
9
+ release: () => Promise<void>;
10
+ } | {
11
+ acquired: false;
12
+ info: SyncLockInfo | null;
13
+ };
14
+ export declare function tryAcquireSyncLock(lockPath: string): Promise<SyncLockResult>;
15
+ export declare function withSyncLock<T>(lockPath: string, options: {
16
+ onBusy: (info: SyncLockInfo | null) => T | Promise<T>;
17
+ }, fn: () => Promise<T>): Promise<T>;
@@ -0,0 +1,91 @@
1
+ import { mkdir, open, readFile, rm } from 'node:fs/promises';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ function isProcessAlive(pid) {
5
+ if (!Number.isFinite(pid) || pid <= 0)
6
+ return false;
7
+ try {
8
+ process.kill(pid, 0);
9
+ return true;
10
+ }
11
+ catch (error) {
12
+ const maybeErrno = error;
13
+ if (maybeErrno.code === 'ESRCH')
14
+ return false;
15
+ return true;
16
+ }
17
+ }
18
+ async function readLockInfo(lockPath) {
19
+ try {
20
+ const content = await readFile(lockPath, 'utf8');
21
+ const parsed = JSON.parse(content);
22
+ if (typeof parsed.pid !== 'number')
23
+ return null;
24
+ if (typeof parsed.startedAt !== 'string')
25
+ return null;
26
+ if (typeof parsed.hostname !== 'string')
27
+ return null;
28
+ return {
29
+ pid: parsed.pid,
30
+ startedAt: parsed.startedAt,
31
+ hostname: parsed.hostname,
32
+ };
33
+ }
34
+ catch (error) {
35
+ const maybeErrno = error;
36
+ if (maybeErrno.code === 'ENOENT')
37
+ return null;
38
+ return null;
39
+ }
40
+ }
41
+ export async function tryAcquireSyncLock(lockPath) {
42
+ const parentDir = path.dirname(lockPath);
43
+ await mkdir(parentDir, { recursive: true });
44
+ const ourInfo = {
45
+ pid: process.pid,
46
+ startedAt: new Date().toISOString(),
47
+ hostname: os.hostname(),
48
+ };
49
+ let attempt = 0;
50
+ while (true) {
51
+ try {
52
+ const handle = await open(lockPath, 'wx');
53
+ await handle.writeFile(`${JSON.stringify(ourInfo, null, 2)}\n`, 'utf8');
54
+ return {
55
+ acquired: true,
56
+ info: ourInfo,
57
+ release: async () => {
58
+ await handle.close().catch(() => {
59
+ // ignore close failures
60
+ });
61
+ await rm(lockPath, { force: true });
62
+ },
63
+ };
64
+ }
65
+ catch (error) {
66
+ const maybeErrno = error;
67
+ if (maybeErrno.code !== 'EEXIST')
68
+ throw error;
69
+ const existing = await readLockInfo(lockPath);
70
+ const shouldBreakLock = existing === null || !isProcessAlive(existing.pid);
71
+ if (attempt === 0 && shouldBreakLock) {
72
+ await rm(lockPath, { force: true });
73
+ attempt += 1;
74
+ continue;
75
+ }
76
+ return { acquired: false, info: existing };
77
+ }
78
+ }
79
+ }
80
+ export async function withSyncLock(lockPath, options, fn) {
81
+ const result = await tryAcquireSyncLock(lockPath);
82
+ if (!result.acquired) {
83
+ return await options.onBusy(result.info);
84
+ }
85
+ try {
86
+ return await fn();
87
+ }
88
+ finally {
89
+ await result.release();
90
+ }
91
+ }
@@ -46,7 +46,7 @@ function buildHeaderEnvVar(serverName, headerName) {
46
46
  function buildEnvVar(serverName, key) {
47
47
  const serverToken = toEnvToken(serverName, 'SERVER');
48
48
  const keyToken = toEnvToken(key, 'VALUE');
49
- return `OPENCODE_MCP_${serverToken}_${keyToken}`;
49
+ return `opencode_mcp_${serverToken}_${keyToken}`;
50
50
  }
51
51
  function toEnvToken(input, fallback) {
52
52
  const cleaned = String(input)
@@ -39,7 +39,7 @@ export function resolveXdgPaths(env = process.env, platform = process.platform)
39
39
  }
40
40
  export function resolveSyncLocations(env = process.env, platform = process.platform) {
41
41
  const xdg = resolveXdgPaths(env, platform);
42
- const customConfigDir = env.OPENCODE_CONFIG_DIR;
42
+ const customConfigDir = env.opencode_config_dir;
43
43
  const configRoot = customConfigDir
44
44
  ? path.resolve(expandHome(customConfigDir, xdg.homeDir))
45
45
  : path.join(xdg.configDir, 'opencode');
@@ -1,15 +1,38 @@
1
+ import path from 'node:path';
1
2
  import { syncLocalToRepo, syncRepoToLocal } from './apply.js';
2
3
  import { generateCommitMessage } from './commit.js';
3
4
  import { canCommitMcpSecrets, loadOverrides, loadState, loadSyncConfig, normalizeSyncConfig, writeState, writeSyncConfig, } from './config.js';
4
5
  import { SyncCommandError, SyncConfigMissingError } from './errors.js';
6
+ import { withSyncLock } from './lock.js';
5
7
  import { buildSyncPlan, resolveRepoRoot, resolveSyncLocations } from './paths.js';
6
8
  import { commitAll, ensureRepoCloned, ensureRepoPrivate, fetchAndFastForward, findSyncRepo, getAuthenticatedUser, getRepoStatus, hasLocalChanges, isRepoCloned, pushBranch, repoExists, resolveRepoBranch, resolveRepoIdentifier, } from './repo.js';
7
9
  import { createLogger, extractTextFromResponse, resolveSmallModel, showToast, unwrapData, } from './utils.js';
8
10
  export function createSyncService(ctx) {
9
11
  const locations = resolveSyncLocations();
10
12
  const log = createLogger(ctx.client);
13
+ const lockPath = path.join(path.dirname(locations.statePath), 'sync.lock');
14
+ const formatLockInfo = (info) => {
15
+ if (!info)
16
+ return 'Another sync is already in progress.';
17
+ return `Another sync is already in progress (pid ${info.pid} on ${info.hostname}, started ${info.startedAt}).`;
18
+ };
19
+ const runExclusive = (fn) => withSyncLock(lockPath, {
20
+ onBusy: (info) => {
21
+ throw new SyncCommandError(formatLockInfo(info));
22
+ },
23
+ }, fn);
24
+ const skipIfBusy = (fn) => withSyncLock(lockPath, {
25
+ onBusy: (info) => {
26
+ log.debug('Sync already running, skipping', {
27
+ pid: info?.pid,
28
+ hostname: info?.hostname,
29
+ startedAt: info?.startedAt,
30
+ });
31
+ return;
32
+ },
33
+ }, fn);
11
34
  return {
12
- startupSync: async () => {
35
+ startupSync: () => skipIfBusy(async () => {
13
36
  let config = null;
14
37
  try {
15
38
  config = await loadSyncConfig(locations);
@@ -31,7 +54,7 @@ export function createSyncService(ctx) {
31
54
  log.error('Startup sync failed', { error: formatError(error) });
32
55
  await showToast(ctx.client, formatError(error), 'error');
33
56
  }
34
- },
57
+ }),
35
58
  status: async () => {
36
59
  const config = await loadSyncConfig(locations);
37
60
  if (!config) {
@@ -87,7 +110,7 @@ export function createSyncService(ctx) {
87
110
  ];
88
111
  return statusLines.join('\n');
89
112
  },
90
- init: async (options) => {
113
+ init: (options) => runExclusive(async () => {
91
114
  const config = await buildConfigFromInit(ctx.$, options);
92
115
  const repoIdentifier = resolveRepoIdentifier(config);
93
116
  const isPrivate = options.private ?? true;
@@ -123,8 +146,8 @@ export function createSyncService(ctx) {
123
146
  `Local repo: ${repoRoot}`,
124
147
  ];
125
148
  return lines.join('\n');
126
- },
127
- link: async (options) => {
149
+ }),
150
+ link: (options) => runExclusive(async () => {
128
151
  const found = await findSyncRepo(ctx.$, options.repo);
129
152
  if (!found) {
130
153
  const searchedFor = options.repo
@@ -164,19 +187,19 @@ export function createSyncService(ctx) {
164
187
  const lines = [
165
188
  `Linked to existing sync repo: ${found.owner}/${found.name}`,
166
189
  '',
167
- 'Your local OpenCode config has been OVERWRITTEN with the synced config.',
190
+ 'Your local opencode config has been OVERWRITTEN with the synced config.',
168
191
  'Your local overrides file was preserved and applied on top.',
169
192
  '',
170
- 'Restart OpenCode to apply the new settings.',
193
+ 'Restart opencode to apply the new settings.',
171
194
  '',
172
195
  found.isPrivate
173
196
  ? 'To enable secrets sync, run: /sync-enable-secrets'
174
197
  : 'Note: Repo is public. Secrets sync is disabled.',
175
198
  ];
176
- await showToast(ctx.client, 'Config synced. Restart OpenCode to apply.', 'info');
199
+ await showToast(ctx.client, 'Config synced. Restart opencode to apply.', 'info');
177
200
  return lines.join('\n');
178
- },
179
- pull: async () => {
201
+ }),
202
+ pull: () => runExclusive(async () => {
180
203
  const config = await getConfigOrThrow(locations);
181
204
  const repoRoot = resolveRepoRoot(config, locations);
182
205
  await ensureRepoCloned(ctx.$, config, repoRoot);
@@ -197,10 +220,10 @@ export function createSyncService(ctx) {
197
220
  lastPull: new Date().toISOString(),
198
221
  lastRemoteUpdate: new Date().toISOString(),
199
222
  });
200
- await showToast(ctx.client, 'Config updated. Restart OpenCode to apply.', 'info');
201
- return 'Remote config applied. Restart OpenCode to use new settings.';
202
- },
203
- push: async () => {
223
+ await showToast(ctx.client, 'Config updated. Restart opencode to apply.', 'info');
224
+ return 'Remote config applied. Restart opencode to use new settings.';
225
+ }),
226
+ push: () => runExclusive(async () => {
204
227
  const config = await getConfigOrThrow(locations);
205
228
  const repoRoot = resolveRepoRoot(config, locations);
206
229
  await ensureRepoCloned(ctx.$, config, repoRoot);
@@ -227,8 +250,8 @@ export function createSyncService(ctx) {
227
250
  lastPush: new Date().toISOString(),
228
251
  });
229
252
  return `Pushed changes: ${message}`;
230
- },
231
- enableSecrets: async (options) => {
253
+ }),
254
+ enableSecrets: (options) => runExclusive(async () => {
232
255
  const config = await getConfigOrThrow(locations);
233
256
  config.includeSecrets = true;
234
257
  if (options?.extraSecretPaths) {
@@ -240,8 +263,8 @@ export function createSyncService(ctx) {
240
263
  await ensureRepoPrivate(ctx.$, config);
241
264
  await writeSyncConfig(locations, config);
242
265
  return 'Secrets sync enabled for this repo.';
243
- },
244
- resolve: async () => {
266
+ }),
267
+ resolve: () => runExclusive(async () => {
245
268
  const config = await getConfigOrThrow(locations);
246
269
  const repoRoot = resolveRepoRoot(config, locations);
247
270
  await ensureRepoCloned(ctx.$, config, repoRoot);
@@ -267,7 +290,7 @@ export function createSyncService(ctx) {
267
290
  }
268
291
  }
269
292
  return `Unable to automatically resolve. Please manually resolve in: ${repoRoot}`;
270
- },
293
+ }),
271
294
  };
272
295
  }
273
296
  async function runStartup(ctx, locations, config, log) {
@@ -293,7 +316,7 @@ async function runStartup(ctx, locations, config, log) {
293
316
  lastPull: new Date().toISOString(),
294
317
  lastRemoteUpdate: new Date().toISOString(),
295
318
  });
296
- await showToast(ctx.client, 'Config updated. Restart OpenCode to apply.', 'info');
319
+ await showToast(ctx.client, 'Config updated. Restart opencode to apply.', 'info');
297
320
  return;
298
321
  }
299
322
  const overrides = await loadOverrides(locations);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "opencode-synced",
3
- "version": "0.6.0",
4
- "description": "Sync global OpenCode config across machines via GitHub.",
3
+ "version": "0.7.1",
4
+ "description": "Sync global opencode config across machines via GitHub.",
5
5
  "author": {
6
6
  "name": "Ian Hildebrand"
7
7
  },