Skip to content

Commit f305544

Browse files
committed
feat: update installation guide and improve error handling in repo client
1 parent 1d14e5c commit f305544

3 files changed

Lines changed: 73 additions & 85 deletions

File tree

docs/LLM-INSTALL.md

Lines changed: 50 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -18,117 +18,88 @@ Ask the user:
1818
Run this command:
1919
npm install oc-sync
2020
21-
### Step 2: Create Private GitHub Repository
22-
Guide the user:
23-
1. Go to https://github.com/new
24-
2. Repository name: suggest "opencode-sync-data" or ask user preference
25-
3. Set visibility to "Private" (important for security)
26-
4. Click "Create repository"
27-
5. Ask user: "What is your GitHub username?" - Save this as OWNER
28-
6. Ask user: "What did you name the repository?" - Save this as REPO
29-
30-
### Step 3: Generate GitHub Personal Access Token
21+
### Step 2: Generate GitHub Personal Access Token
3122
Guide the user step by step:
32-
1. Go to https://github.com/settings/tokens?type=beta
33-
2. Click "Generate new token"
23+
1. Go to https://github.com/settings/tokens
24+
2. Click "Generate new token (classic)"
3425
3. Token name: "opencode-sync"
3526
4. Expiration: recommend "90 days" or "No expiration" based on user preference
36-
5. Under "Repository access", select "Only select repositories" and choose the sync repo created in Step 2
37-
6. Under "Permissions" → "Repository permissions":
38-
- Contents: Read and write
39-
- Metadata: Read-only
40-
7. Click "Generate token"
41-
8. IMPORTANT: Tell user to copy the token immediately (starts with "github_pat_")
42-
9. Ask user: "Please paste your token here" - Save this as TOKEN
43-
44-
### Step 4: Encryption Key (Optional)
45-
Ask the user:
46-
"Do you want to encrypt your credentials (auth tokens) before syncing? This adds extra security but requires the same key on all machines."
47-
48-
If YES:
49-
- Generate a random 32-character alphanumeric string, or let user provide their own (minimum 16 characters)
50-
- Save this as ENCRYPTION_KEY
51-
- IMPORTANT: Tell user to save this key securely. If lost, encrypted credentials cannot be recovered.
52-
- WARNING: All machines must use the same key. Adding encryption later will break sync on machines without the key.
53-
54-
If NO:
55-
- Skip this step. Credentials will sync in plain text (still protected by GitHub's private repo authentication).
56-
57-
### Step 5: Create Configuration
58-
Create or update the OpenCode config file with these values:
27+
5. Select scope: "repo" (Full control of private repositories)
28+
6. Click "Generate token"
29+
7. IMPORTANT: Tell user to copy the token immediately (starts with "ghp_")
30+
8. Ask user: "Please paste your token here" - Save this as TOKEN
5931
60-
Without encryption:
61-
{
62-
"plugins": {
63-
"oc-sync": {
64-
"token": "<TOKEN from Step 3>",
65-
"owner": "<OWNER from Step 2>",
66-
"repo": "<REPO from Step 2>",
67-
"branch": "main"
68-
}
69-
}
70-
}
32+
### Step 3: Create Configuration
33+
Create the config file at ~/.config/opencode/opencode-sync.json:
7134
72-
With encryption:
7335
{
74-
"plugins": {
75-
"oc-sync": {
76-
"token": "<TOKEN from Step 3>",
77-
"owner": "<OWNER from Step 2>",
78-
"repo": "<REPO from Step 2>",
79-
"branch": "main",
80-
"encryptionKey": "<ENCRYPTION_KEY from Step 4>"
81-
}
82-
}
36+
"token": "<TOKEN from Step 2>"
8337
}
8438
85-
Show the user the complete config with their actual values filled in.
39+
That's it! The plugin will automatically:
40+
- Detect your GitHub username
41+
- Create a private repository (.opencode-sync)
42+
- Detect the default branch (main/master)
8643
87-
### Step 6: Verify Installation
44+
### Step 4: Verify Installation
8845
Tell user to restart OpenCode. The plugin will:
89-
- Auto-sync on startup
46+
- Auto-create the sync repository if needed
47+
- Sync on startup
9048
- Watch for file changes
9149
- Push/pull automatically
9250
51+
Check logs at: ~/.local/share/opencode/log/opencode-sync.log
52+
53+
Expected output:
54+
[opencode-sync] Plugin starting...
55+
[opencode-sync] Token loaded from: config file
56+
[opencode-sync] Setting up sync storage...
57+
[opencode-sync] Linked to repo: username/.opencode-sync
58+
[opencode-sync] Plugin ready
59+
9360
### Troubleshooting
9461
If user reports errors:
95-
- "404 Not Found": Repository name or owner is wrong. Verify spelling.
9662
- "401 Unauthorized": Token is invalid or expired. Generate a new one.
97-
- "403 Forbidden": Token lacks required permissions. Recreate with Contents read/write.
98-
- "Encryption error": Key is less than 16 characters or contains invalid characters.
99-
- "Decryption failed": Wrong encryption key or data was synced with a different key.
63+
- "403 Forbidden": Token lacks "repo" scope. Recreate with correct permissions.
64+
- "Not Found": Check token has repo scope for creating private repositories.
65+
66+
### Advanced Options (Optional)
67+
These settings are auto-detected but can be overridden if needed:
68+
69+
{
70+
"token": "ghp_...",
71+
"repoOwner": "custom-username", // Override auto-detected username
72+
"repoName": "custom-repo-name", // Override default .opencode-sync
73+
"branch": "custom-branch", // Override auto-detected main/master
74+
"encryptionKey": "your-secret-key" // Encrypt credentials before sync
75+
}
76+
77+
### Encryption (Optional)
78+
If user wants to encrypt credentials:
79+
1. Add encryptionKey to config (minimum 16 characters)
80+
2. WARNING: Same key must be used on ALL machines
81+
3. WARNING: If key is lost, encrypted credentials cannot be recovered
10082
10183
### Key Rotation (Advanced)
10284
If user needs to change their encryption key:
10385
10486
1. On ALL machines, update config to include BOTH keys:
10587
{
106-
"plugins": {
107-
"oc-sync": {
108-
"token": "...",
109-
"owner": "...",
110-
"repo": "...",
111-
"encryptionKey": "<NEW_KEY>",
112-
"oldEncryptionKey": "<OLD_KEY>"
113-
}
114-
}
88+
"token": "...",
89+
"encryptionKey": "<NEW_KEY>",
90+
"oldEncryptionKey": "<OLD_KEY>"
11591
}
11692
11793
2. Restart OpenCode on each machine. The plugin will:
11894
- Decrypt using old key (fallback)
11995
- Re-encrypt using new key on push
12096
121-
3. After ALL machines have synced with the new key, remove oldEncryptionKey from config.
122-
123-
IMPORTANT: Do NOT remove oldEncryptionKey until all machines have synced at least once with the new configuration.
97+
3. After ALL machines have synced, remove oldEncryptionKey.
12498
12599
### Summary
126100
After setup, confirm with user:
127-
- GitHub username: [OWNER]
128-
- Repository: [REPO] (private)
129101
- Token: configured (never display full token)
130-
- Encryption: enabled/disabled
131102
- Status: Ready to sync
132103
```
133104

134-
---
105+
---

src/plugin/plugin.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,6 @@ async function ensureStorageExists(pathConfig: PathConfig): Promise<void> {
179179

180180
log(`Linked to repo: ${owner}/${repoName}`);
181181
await updateConfig(pathConfig, { repoOwner: owner, repoName });
182-
log('Repo saved to config');
183182
initializeEngine();
184183
} catch (error) {
185184
const errMsg = error instanceof Error ? error.message : String(error);

src/storage/repo/repo-client.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,16 @@ export class RepoStorageBackend implements StorageBackend {
7777
}
7878

7979
public async exists(): Promise<boolean> {
80-
const res = await this.fetch(`/contents/${SYNC_DIR}/manifest.json`);
81-
return res.ok;
80+
try {
81+
const res = await this.fetch(`/contents/${SYNC_DIR}/manifest.json`);
82+
return res.ok;
83+
} catch (error) {
84+
// 404 means file doesn't exist, which is expected for new repos
85+
if (error instanceof RepoApiError && error.status === 404) {
86+
return false;
87+
}
88+
throw error;
89+
}
8290
}
8391

8492
public async initialize(manifest: string): Promise<void> {
@@ -170,9 +178,19 @@ export class RepoStorageBackend implements StorageBackend {
170178
}
171179

172180
private async createOrUpdateFile(path: string, content: string, message: string): Promise<void> {
173-
// Get current file SHA if exists
174-
const existing = await this.fetch(`/contents/${path}`);
175-
const sha = existing.ok ? ((await existing.json()) as ContentFile).sha : undefined;
181+
// Get current file SHA if exists (404 means file doesn't exist yet)
182+
let sha: string | undefined;
183+
try {
184+
const existing = await this.fetch(`/contents/${path}`);
185+
if (existing.ok) {
186+
sha = ((await existing.json()) as ContentFile).sha;
187+
}
188+
} catch (error) {
189+
// 404 is expected for new files
190+
if (!(error instanceof RepoApiError && error.status === 404)) {
191+
throw error;
192+
}
193+
}
176194

177195
const body = JSON.stringify({
178196
message,

0 commit comments

Comments
 (0)