diff --git a/README.md b/README.md
index 28a0b26..e626246 100644
--- a/README.md
+++ b/README.md
@@ -37,6 +37,25 @@ opencode
## Configure
+Run `/sync-init` to get started. This will:
+
+1. Detect your GitHub username from the CLI
+2. Create a private repo (`my-opencode-config` by default) if it doesn't exist
+3. Clone the repo and set up sync
+
+That's it! Your config will now sync automatically on startup.
+
+### Custom repo name or org
+
+You can specify a custom repo name or use an organization:
+
+- `/sync-init` - Uses `{your-username}/my-opencode-config`
+- `/sync-init my-config` - Uses `{your-username}/my-config`
+- `/sync-init my-org/team-config` - Uses `my-org/team-config`
+
+
+Manual configuration
+
Create `~/.config/opencode/opencode-sync.jsonc`:
```jsonc
@@ -53,7 +72,7 @@ Create `~/.config/opencode/opencode-sync.jsonc`:
}
```
-You can also run `/sync-init` to scaffold this file.
+
### Synced paths (default)
@@ -117,6 +136,7 @@ Overrides are merged into the runtime config and re-applied to `opencode.json(c)
## Usage
+- `/sync-init` to set up sync (creates repo if needed)
- `/sync-status` for repo status and last sync
- `/sync-pull` to fetch and apply remote config
- `/sync-push` to commit and push local changes
@@ -124,29 +144,7 @@ Overrides are merged into the runtime config and re-applied to `opencode.json(c)
- `/sync-resolve` to automatically resolve uncommitted changes using AI
-Manual (slash command alternative)
-
-### Configure
-
-Create `~/.config/opencode/opencode-sync.jsonc`:
-
-```jsonc
-{
- "repo": {
- "owner": "your-org",
- "name": "opencode-config",
- "branch": "main",
- },
- "includeSecrets": false,
- "includeSessions": false,
- "includePromptStash": false,
- "extraSecretPaths": [],
-}
-```
-
-### Enable secrets (private repo required)
-
-Set `"includeSecrets": true` and optionally add `"extraSecretPaths"`. The plugin will refuse to sync secrets if the repo is not private.
+Manual sync (without slash commands)
### Trigger a sync
diff --git a/src/command/sync-init.md b/src/command/sync-init.md
index c5e305a..d3d9dc6 100644
--- a/src/command/sync-init.md
+++ b/src/command/sync-init.md
@@ -1,7 +1,11 @@
---
description: Initialize opencode-sync configuration
---
+
Use the opencode_sync tool with command "init".
-If the repo is unknown, ask for a GitHub repo (owner/name or URL).
-If the user wants to create it, pass create=true and private=true unless they request public.
+The repo will be created automatically if it doesn't exist (private by default).
+Default repo name is "my-opencode-config" with owner auto-detected from GitHub CLI.
+If the user wants a custom repo name, pass name="custom-name".
+If the user wants an org-owned repo, pass owner="org-name".
+If the user wants a public repo, pass private=false.
Include includeSecrets if the user explicitly opts in.
diff --git a/src/sync/repo.ts b/src/sync/repo.ts
index 1520b08..f5c428b 100644
--- a/src/sync/repo.ts
+++ b/src/sync/repo.ts
@@ -236,3 +236,23 @@ function formatError(error: unknown): string {
if (error instanceof Error) return error.message;
return String(error);
}
+
+export async function repoExists($: Shell, repoIdentifier: string): Promise {
+ try {
+ await $`gh repo view ${repoIdentifier} --json name`;
+ return true;
+ } catch {
+ return false;
+ }
+}
+
+export async function getAuthenticatedUser($: Shell): Promise {
+ try {
+ const output = await $`gh api user --jq .login`.text();
+ return output.trim();
+ } catch (error) {
+ throw new SyncCommandError(
+ `Failed to detect GitHub user. Ensure gh is authenticated: ${formatError(error)}`
+ );
+ }
+}
diff --git a/src/sync/service.ts b/src/sync/service.ts
index 357b3ab..a07fdb9 100644
--- a/src/sync/service.ts
+++ b/src/sync/service.ts
@@ -18,10 +18,12 @@ import {
ensureRepoCloned,
ensureRepoPrivate,
fetchAndFastForward,
+ getAuthenticatedUser,
getRepoStatus,
hasLocalChanges,
isRepoCloned,
pushBranch,
+ repoExists,
resolveRepoBranch,
resolveRepoIdentifier,
} from './repo.ts';
@@ -125,14 +127,16 @@ export function createSyncService(ctx: SyncServiceContext): SyncService {
return statusLines.join('\n');
},
init: async (options: InitOptions) => {
- const config = buildConfigFromInit(options);
- if (!config.repo) {
- throw new SyncCommandError('Provide repo info (owner/name or URL) to initialize sync.');
- }
+ const config = await buildConfigFromInit(ctx.$, options);
const repoIdentifier = resolveRepoIdentifier(config);
- if (options.create) {
- await createRepo(ctx.$, config, options.private ?? true);
+ const isPrivate = options.private ?? true;
+
+ const exists = await repoExists(ctx.$, repoIdentifier);
+ let created = false;
+ if (!exists) {
+ await createRepo(ctx.$, config, isPrivate);
+ created = true;
}
await writeSyncConfig(locations, config);
@@ -140,12 +144,14 @@ export function createSyncService(ctx: SyncServiceContext): SyncService {
await ensureRepoCloned(ctx.$, config, repoRoot);
await ensureSecretsPolicy(ctx, config);
- return [
+ const lines = [
'opencode-sync configured.',
- `Repo: ${repoIdentifier}`,
+ `Repo: ${repoIdentifier}${created ? ' (created)' : ''}`,
`Branch: ${resolveRepoBranch(config)}`,
`Local repo: ${repoRoot}`,
- ].join('\n');
+ ];
+
+ return lines.join('\n');
},
pull: async () => {
const config = await getConfigOrThrow(locations);
@@ -340,8 +346,10 @@ async function resolveBranch(
}
}
-function buildConfigFromInit(options: InitOptions) {
- const repo = resolveRepoFromInit(options);
+const DEFAULT_REPO_NAME = 'my-opencode-config';
+
+async function buildConfigFromInit($: Shell, options: InitOptions) {
+ const repo = await resolveRepoFromInit($, options);
return normalizeSyncConfig({
repo,
includeSecrets: options.includeSecrets ?? false,
@@ -352,7 +360,7 @@ function buildConfigFromInit(options: InitOptions) {
});
}
-function resolveRepoFromInit(options: InitOptions) {
+async function resolveRepoFromInit($: Shell, options: InitOptions) {
if (options.url) {
return { url: options.url, branch: options.branch };
}
@@ -363,12 +371,21 @@ function resolveRepoFromInit(options: InitOptions) {
if (options.repo.includes('://') || options.repo.endsWith('.git')) {
return { url: options.repo, branch: options.branch };
}
- const [owner, name] = options.repo.split('/');
- if (owner && name) {
- return { owner, name, branch: options.branch };
+ if (options.repo.includes('/')) {
+ const [owner, name] = options.repo.split('/');
+ if (owner && name) {
+ return { owner, name, branch: options.branch };
+ }
}
+
+ const owner = await getAuthenticatedUser($);
+ return { owner, name: options.repo, branch: options.branch };
}
- return undefined;
+
+ // Default: auto-detect owner, use default repo name
+ const owner = await getAuthenticatedUser($);
+ const name = DEFAULT_REPO_NAME;
+ return { owner, name, branch: options.branch };
}
async function createRepo(