opencode-sync-plugin 0.1.0
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 +160 -0
- package/dist/chunk-LXC2JAKD.js +242 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +136 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +12 -0
- package/package.json +54 -0
package/README.md
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# opencode-sync-plugin
|
|
2
|
+
|
|
3
|
+
Sync your OpenCode sessions to the cloud. Search, share, and access your coding history from anywhere.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
### From npm
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g opencode-sync-plugin
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### From source
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
git clone https://github.com/waynesutton/opencode-sync-plugin
|
|
17
|
+
cd opencode-sync-plugin
|
|
18
|
+
npm install
|
|
19
|
+
npm run build
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Setup
|
|
23
|
+
|
|
24
|
+
### 1. Get your credentials
|
|
25
|
+
|
|
26
|
+
You need two things from your OpenSync deployment:
|
|
27
|
+
|
|
28
|
+
- **Convex URL**: Your deployment URL from the Convex dashboard (e.g., `https://your-project-123.convex.cloud`)
|
|
29
|
+
- **API Key**: Generated in the OpenSync dashboard at **Settings > API Key** (starts with `osk_`)
|
|
30
|
+
|
|
31
|
+
The plugin automatically converts the `.cloud` URL to `.site` for API calls.
|
|
32
|
+
|
|
33
|
+
### 2. Configure the plugin
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
opencode-sync login
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Follow the prompts:
|
|
40
|
+
|
|
41
|
+
1. Enter your Convex URL
|
|
42
|
+
2. Enter your API Key
|
|
43
|
+
|
|
44
|
+
No browser authentication required.
|
|
45
|
+
|
|
46
|
+
### 3. Add to OpenCode
|
|
47
|
+
|
|
48
|
+
Add the plugin to your `opencode.json`:
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"$schema": "https://opencode.ai/config.json",
|
|
53
|
+
"plugin": ["opencode-sync-plugin"]
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Or add globally at `~/.config/opencode/opencode.json`.
|
|
58
|
+
|
|
59
|
+
## How it works
|
|
60
|
+
|
|
61
|
+
The plugin hooks into OpenCode events and syncs data automatically:
|
|
62
|
+
|
|
63
|
+
| Event | Action |
|
|
64
|
+
|-------|--------|
|
|
65
|
+
| `session.created` | Creates session record in cloud |
|
|
66
|
+
| `session.updated` | Updates session metadata |
|
|
67
|
+
| `session.idle` | Final sync with token counts and cost |
|
|
68
|
+
| `message.updated` | Syncs user and assistant messages |
|
|
69
|
+
| `message.part.updated` | Syncs completed message parts |
|
|
70
|
+
|
|
71
|
+
Data is stored in your Convex deployment. You can view, search, and share sessions via the web UI.
|
|
72
|
+
|
|
73
|
+
## CLI Commands
|
|
74
|
+
|
|
75
|
+
| Command | Description |
|
|
76
|
+
|---------|-------------|
|
|
77
|
+
| `opencode-sync login` | Configure with Convex URL and API Key |
|
|
78
|
+
| `opencode-sync logout` | Clear stored credentials |
|
|
79
|
+
| `opencode-sync status` | Show authentication status |
|
|
80
|
+
| `opencode-sync config` | Show current configuration |
|
|
81
|
+
|
|
82
|
+
## Configuration storage
|
|
83
|
+
|
|
84
|
+
Credentials are stored at:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
~/.config/opencode-sync/
|
|
88
|
+
config.json # Convex URL, API Key
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Plugin architecture
|
|
92
|
+
|
|
93
|
+
This plugin follows the [OpenCode plugin specification](https://opencode.ai/docs/plugins/):
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
import type { Plugin } from "@opencode-ai/plugin";
|
|
97
|
+
|
|
98
|
+
export const OpenCodeSyncPlugin: Plugin = async ({ project, client, $, directory, worktree }) => {
|
|
99
|
+
// Initialize plugin
|
|
100
|
+
await client.app.log({
|
|
101
|
+
service: "opencode-sync",
|
|
102
|
+
level: "info",
|
|
103
|
+
message: "Plugin initialized",
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
// Subscribe to events
|
|
108
|
+
event: async ({ event }) => {
|
|
109
|
+
if (event.type === "session.created") {
|
|
110
|
+
// Sync session to cloud
|
|
111
|
+
}
|
|
112
|
+
if (event.type === "message.updated") {
|
|
113
|
+
// Sync message to cloud
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Troubleshooting
|
|
121
|
+
|
|
122
|
+
### "Not authenticated" errors
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
opencode-sync login
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Invalid API Key
|
|
129
|
+
|
|
130
|
+
1. Go to your OpenSync dashboard
|
|
131
|
+
2. Navigate to Settings
|
|
132
|
+
3. Generate a new API Key
|
|
133
|
+
4. Run `opencode-sync login` with the new key
|
|
134
|
+
|
|
135
|
+
### Check status
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
opencode-sync status
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### View logs
|
|
142
|
+
|
|
143
|
+
Plugin logs are available in OpenCode's log output. Look for entries with `service: "opencode-sync"`.
|
|
144
|
+
|
|
145
|
+
## Development
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
# Install dependencies
|
|
149
|
+
npm install
|
|
150
|
+
|
|
151
|
+
# Build
|
|
152
|
+
npm run build
|
|
153
|
+
|
|
154
|
+
# Watch mode
|
|
155
|
+
npm run dev
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## License
|
|
159
|
+
|
|
160
|
+
MIT
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import Conf from "conf";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
var config = new Conf({
|
|
6
|
+
projectName: "opencode-sync",
|
|
7
|
+
cwd: join(homedir(), ".config", "opencode-sync"),
|
|
8
|
+
configName: "config"
|
|
9
|
+
});
|
|
10
|
+
function getConfig() {
|
|
11
|
+
const url = config.get("convexUrl");
|
|
12
|
+
const key = config.get("apiKey");
|
|
13
|
+
if (!url) return null;
|
|
14
|
+
return { convexUrl: url, apiKey: key || "" };
|
|
15
|
+
}
|
|
16
|
+
function setConfig(cfg) {
|
|
17
|
+
config.set("convexUrl", cfg.convexUrl);
|
|
18
|
+
config.set("apiKey", cfg.apiKey);
|
|
19
|
+
}
|
|
20
|
+
function clearConfig() {
|
|
21
|
+
config.clear();
|
|
22
|
+
}
|
|
23
|
+
function getApiKey() {
|
|
24
|
+
const cfg = getConfig();
|
|
25
|
+
if (!cfg || !cfg.apiKey) return null;
|
|
26
|
+
return cfg.apiKey;
|
|
27
|
+
}
|
|
28
|
+
function normalizeToSiteUrl(url) {
|
|
29
|
+
if (url.includes(".convex.cloud")) {
|
|
30
|
+
return url.replace(".convex.cloud", ".convex.site");
|
|
31
|
+
}
|
|
32
|
+
return url;
|
|
33
|
+
}
|
|
34
|
+
function getSiteUrl() {
|
|
35
|
+
const cfg = getConfig();
|
|
36
|
+
if (!cfg || !cfg.convexUrl) return null;
|
|
37
|
+
return normalizeToSiteUrl(cfg.convexUrl);
|
|
38
|
+
}
|
|
39
|
+
async function syncSession(session, client) {
|
|
40
|
+
const apiKey = getApiKey();
|
|
41
|
+
const siteUrl = getSiteUrl();
|
|
42
|
+
if (!apiKey || !siteUrl) {
|
|
43
|
+
await client.app.log({
|
|
44
|
+
service: "opencode-sync",
|
|
45
|
+
level: "warn",
|
|
46
|
+
message: "Not authenticated. Run: opencode-sync login"
|
|
47
|
+
});
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
const response = await fetch(`${siteUrl}/sync/session`, {
|
|
52
|
+
method: "POST",
|
|
53
|
+
headers: {
|
|
54
|
+
"Content-Type": "application/json",
|
|
55
|
+
Authorization: `Bearer ${apiKey}`
|
|
56
|
+
},
|
|
57
|
+
body: JSON.stringify({
|
|
58
|
+
externalId: session.id,
|
|
59
|
+
title: session.title || extractTitle(session),
|
|
60
|
+
projectPath: session.cwd,
|
|
61
|
+
projectName: session.cwd?.split("/").pop(),
|
|
62
|
+
model: session.model,
|
|
63
|
+
provider: session.provider,
|
|
64
|
+
promptTokens: session.usage?.promptTokens || 0,
|
|
65
|
+
completionTokens: session.usage?.completionTokens || 0,
|
|
66
|
+
cost: session.usage?.cost || 0
|
|
67
|
+
})
|
|
68
|
+
});
|
|
69
|
+
if (!response.ok) {
|
|
70
|
+
const errorText = await response.text();
|
|
71
|
+
await client.app.log({
|
|
72
|
+
service: "opencode-sync",
|
|
73
|
+
level: "error",
|
|
74
|
+
message: `Session sync failed: ${errorText}`
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
} catch (e) {
|
|
78
|
+
await client.app.log({
|
|
79
|
+
service: "opencode-sync",
|
|
80
|
+
level: "error",
|
|
81
|
+
message: `Session sync error: ${e}`
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async function syncMessage(sessionId, message, client) {
|
|
86
|
+
const apiKey = getApiKey();
|
|
87
|
+
const siteUrl = getSiteUrl();
|
|
88
|
+
if (!apiKey || !siteUrl) return;
|
|
89
|
+
const parts = extractParts(message.content);
|
|
90
|
+
try {
|
|
91
|
+
const response = await fetch(`${siteUrl}/sync/message`, {
|
|
92
|
+
method: "POST",
|
|
93
|
+
headers: {
|
|
94
|
+
"Content-Type": "application/json",
|
|
95
|
+
Authorization: `Bearer ${apiKey}`
|
|
96
|
+
},
|
|
97
|
+
body: JSON.stringify({
|
|
98
|
+
sessionExternalId: sessionId,
|
|
99
|
+
externalId: message.id,
|
|
100
|
+
role: message.role,
|
|
101
|
+
textContent: extractTextContent(message.content),
|
|
102
|
+
model: message.model,
|
|
103
|
+
promptTokens: message.usage?.promptTokens,
|
|
104
|
+
completionTokens: message.usage?.completionTokens,
|
|
105
|
+
durationMs: message.duration,
|
|
106
|
+
parts
|
|
107
|
+
})
|
|
108
|
+
});
|
|
109
|
+
if (!response.ok) {
|
|
110
|
+
const errorText = await response.text();
|
|
111
|
+
await client.app.log({
|
|
112
|
+
service: "opencode-sync",
|
|
113
|
+
level: "error",
|
|
114
|
+
message: `Message sync failed: ${errorText}`
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
} catch (e) {
|
|
118
|
+
await client.app.log({
|
|
119
|
+
service: "opencode-sync",
|
|
120
|
+
level: "error",
|
|
121
|
+
message: `Message sync error: ${e}`
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function extractTitle(session) {
|
|
126
|
+
const firstMessage = session.messages?.find((m) => m.role === "user");
|
|
127
|
+
if (firstMessage) {
|
|
128
|
+
const text = extractTextContent(firstMessage.content);
|
|
129
|
+
if (text) {
|
|
130
|
+
return text.slice(0, 100) + (text.length > 100 ? "..." : "");
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return "Untitled Session";
|
|
134
|
+
}
|
|
135
|
+
function extractTextContent(content) {
|
|
136
|
+
if (typeof content === "string") return content;
|
|
137
|
+
if (Array.isArray(content)) {
|
|
138
|
+
return content.filter((p) => p.type === "text").map((p) => p.text).join("\n");
|
|
139
|
+
}
|
|
140
|
+
return "";
|
|
141
|
+
}
|
|
142
|
+
function extractParts(content) {
|
|
143
|
+
if (typeof content === "string") {
|
|
144
|
+
return [{ type: "text", content }];
|
|
145
|
+
}
|
|
146
|
+
if (Array.isArray(content)) {
|
|
147
|
+
return content.map((part) => {
|
|
148
|
+
if (part.type === "text") {
|
|
149
|
+
return { type: "text", content: part.text };
|
|
150
|
+
}
|
|
151
|
+
if (part.type === "tool_use" || part.type === "tool-call") {
|
|
152
|
+
const toolPart = part;
|
|
153
|
+
return {
|
|
154
|
+
type: "tool-call",
|
|
155
|
+
content: { name: toolPart.name, args: toolPart.input || toolPart.args || {} }
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
if (part.type === "tool_result" || part.type === "tool-result") {
|
|
159
|
+
const resultPart = part;
|
|
160
|
+
return {
|
|
161
|
+
type: "tool-result",
|
|
162
|
+
content: { result: resultPart.content || resultPart.result }
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
return { type: part.type, content: part };
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
return [];
|
|
169
|
+
}
|
|
170
|
+
var syncedMessages = /* @__PURE__ */ new Set();
|
|
171
|
+
var syncedSessions = /* @__PURE__ */ new Set();
|
|
172
|
+
var OpenCodeSyncPlugin = async ({ project, client, $, directory, worktree }) => {
|
|
173
|
+
const cfg = getConfig();
|
|
174
|
+
if (!cfg || !cfg.apiKey) {
|
|
175
|
+
await client.app.log({
|
|
176
|
+
service: "opencode-sync",
|
|
177
|
+
level: "warn",
|
|
178
|
+
message: "Not configured. Run: opencode-sync login"
|
|
179
|
+
});
|
|
180
|
+
} else {
|
|
181
|
+
await client.app.log({
|
|
182
|
+
service: "opencode-sync",
|
|
183
|
+
level: "info",
|
|
184
|
+
message: "Plugin initialized",
|
|
185
|
+
extra: { directory, worktree }
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
// Handle all events via generic event handler
|
|
190
|
+
event: async ({ event }) => {
|
|
191
|
+
const props = event.properties;
|
|
192
|
+
if (event.type === "session.created") {
|
|
193
|
+
const session = props;
|
|
194
|
+
if (session?.id && !syncedSessions.has(session.id)) {
|
|
195
|
+
syncedSessions.add(session.id);
|
|
196
|
+
await syncSession(session, client);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (event.type === "session.updated") {
|
|
200
|
+
const session = props;
|
|
201
|
+
if (session?.id) {
|
|
202
|
+
await syncSession(session, client);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (event.type === "session.idle") {
|
|
206
|
+
const session = props;
|
|
207
|
+
if (session?.id) {
|
|
208
|
+
await syncSession(session, client);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (event.type === "message.updated") {
|
|
212
|
+
const messageProps = props;
|
|
213
|
+
const sessionId = messageProps?.sessionId;
|
|
214
|
+
const message = messageProps?.message;
|
|
215
|
+
if (sessionId && message?.id && !syncedMessages.has(message.id)) {
|
|
216
|
+
syncedMessages.add(message.id);
|
|
217
|
+
await syncMessage(sessionId, message, client);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (event.type === "message.part.updated") {
|
|
221
|
+
const messageProps = props;
|
|
222
|
+
const sessionId = messageProps?.sessionId;
|
|
223
|
+
const message = messageProps?.message;
|
|
224
|
+
if (sessionId && message?.id) {
|
|
225
|
+
if (message.status === "completed" || message.role === "user") {
|
|
226
|
+
if (!syncedMessages.has(message.id)) {
|
|
227
|
+
syncedMessages.add(message.id);
|
|
228
|
+
await syncMessage(sessionId, message, client);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
export {
|
|
238
|
+
getConfig,
|
|
239
|
+
setConfig,
|
|
240
|
+
clearConfig,
|
|
241
|
+
OpenCodeSyncPlugin
|
|
242
|
+
};
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
clearConfig,
|
|
4
|
+
getConfig,
|
|
5
|
+
setConfig
|
|
6
|
+
} from "./chunk-LXC2JAKD.js";
|
|
7
|
+
|
|
8
|
+
// src/cli.ts
|
|
9
|
+
var args = process.argv.slice(2);
|
|
10
|
+
var command = args[0];
|
|
11
|
+
async function main() {
|
|
12
|
+
switch (command) {
|
|
13
|
+
case "login":
|
|
14
|
+
await login();
|
|
15
|
+
break;
|
|
16
|
+
case "logout":
|
|
17
|
+
logout();
|
|
18
|
+
break;
|
|
19
|
+
case "status":
|
|
20
|
+
status();
|
|
21
|
+
break;
|
|
22
|
+
case "config":
|
|
23
|
+
showConfig();
|
|
24
|
+
break;
|
|
25
|
+
default:
|
|
26
|
+
help();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
async function login() {
|
|
30
|
+
console.log("\n OpenSync Login\n");
|
|
31
|
+
const convexUrl = await prompt("Convex URL (e.g., https://your-project.convex.cloud): ");
|
|
32
|
+
if (!convexUrl) {
|
|
33
|
+
console.error("Convex URL is required");
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
if (!convexUrl.includes(".convex.cloud") && !convexUrl.includes(".convex.site")) {
|
|
37
|
+
console.error("Invalid Convex URL. Should end with .convex.cloud or .convex.site");
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
const apiKey = await prompt("API Key (from Settings page, starts with osk_): ");
|
|
41
|
+
if (!apiKey) {
|
|
42
|
+
console.error("API Key is required");
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
if (!apiKey.startsWith("osk_")) {
|
|
46
|
+
console.error("Invalid API Key format. Should start with 'osk_'");
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
const siteUrl = convexUrl.replace(".convex.cloud", ".convex.site");
|
|
50
|
+
console.log("\nVerifying credentials...");
|
|
51
|
+
try {
|
|
52
|
+
const response = await fetch(`${siteUrl}/health`);
|
|
53
|
+
if (!response.ok) {
|
|
54
|
+
console.error("\nFailed to connect to OpenSync backend.");
|
|
55
|
+
console.error("Please verify your Convex URL is correct.");
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
setConfig({ convexUrl, apiKey });
|
|
59
|
+
console.log("\nLogin successful!\n");
|
|
60
|
+
console.log(" Convex URL:", convexUrl);
|
|
61
|
+
console.log(" API Key:", apiKey.slice(0, 8) + "..." + apiKey.slice(-4));
|
|
62
|
+
console.log("\n Add the plugin to your opencode.json:");
|
|
63
|
+
console.log(' { "plugin": ["opencode-sync-plugin"] }\n');
|
|
64
|
+
} catch (e) {
|
|
65
|
+
console.error("\nFailed to connect to OpenSync backend.");
|
|
66
|
+
console.error("Please verify your Convex URL is correct.");
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function logout() {
|
|
71
|
+
clearConfig();
|
|
72
|
+
console.log("\nLogged out successfully\n");
|
|
73
|
+
}
|
|
74
|
+
function status() {
|
|
75
|
+
const config = getConfig();
|
|
76
|
+
console.log("\n OpenSync Status\n");
|
|
77
|
+
if (!config) {
|
|
78
|
+
console.log(" Status: Not configured\n");
|
|
79
|
+
console.log(" Run: opencode-sync login\n");
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (!config.apiKey) {
|
|
83
|
+
console.log(" Status: Not authenticated\n");
|
|
84
|
+
console.log(" Convex URL:", config.convexUrl);
|
|
85
|
+
console.log("\n Run: opencode-sync login\n");
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
console.log(" Status: Configured\n");
|
|
89
|
+
console.log(" Convex URL:", config.convexUrl);
|
|
90
|
+
console.log(" API Key:", config.apiKey.slice(0, 8) + "..." + config.apiKey.slice(-4));
|
|
91
|
+
console.log();
|
|
92
|
+
}
|
|
93
|
+
function showConfig() {
|
|
94
|
+
const config = getConfig();
|
|
95
|
+
console.log("\n OpenSync Config\n");
|
|
96
|
+
if (!config) {
|
|
97
|
+
console.log(" No configuration found.\n");
|
|
98
|
+
console.log(" Run: opencode-sync login\n");
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
console.log(" Convex URL:", config.convexUrl);
|
|
102
|
+
console.log(" API Key:", config.apiKey ? config.apiKey.slice(0, 8) + "..." + config.apiKey.slice(-4) : "Not set");
|
|
103
|
+
console.log();
|
|
104
|
+
}
|
|
105
|
+
function help() {
|
|
106
|
+
console.log(`
|
|
107
|
+
OpenSync CLI
|
|
108
|
+
|
|
109
|
+
Usage: opencode-sync <command>
|
|
110
|
+
|
|
111
|
+
Commands:
|
|
112
|
+
login Configure with Convex URL and API Key
|
|
113
|
+
logout Clear stored credentials
|
|
114
|
+
status Show current authentication status
|
|
115
|
+
config Show current configuration
|
|
116
|
+
|
|
117
|
+
Setup:
|
|
118
|
+
1. Go to your OpenSync dashboard Settings page
|
|
119
|
+
2. Generate an API Key (starts with osk_)
|
|
120
|
+
3. Run: opencode-sync login
|
|
121
|
+
4. Enter your Convex URL and API Key
|
|
122
|
+
5. Add plugin to opencode.json: { "plugin": ["opencode-sync-plugin"] }
|
|
123
|
+
`);
|
|
124
|
+
}
|
|
125
|
+
function prompt(question) {
|
|
126
|
+
return new Promise((resolve) => {
|
|
127
|
+
process.stdout.write(question);
|
|
128
|
+
let input = "";
|
|
129
|
+
process.stdin.setEncoding("utf8");
|
|
130
|
+
process.stdin.once("data", (data) => {
|
|
131
|
+
input = data.toString().trim();
|
|
132
|
+
resolve(input);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
main().catch(console.error);
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
interface PluginClient {
|
|
2
|
+
app: {
|
|
3
|
+
log: (entry: {
|
|
4
|
+
service: string;
|
|
5
|
+
level: "debug" | "info" | "warn" | "error";
|
|
6
|
+
message: string;
|
|
7
|
+
extra?: Record<string, unknown>;
|
|
8
|
+
}) => Promise<void>;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
interface PluginContext {
|
|
12
|
+
project: unknown;
|
|
13
|
+
client: PluginClient;
|
|
14
|
+
$: unknown;
|
|
15
|
+
directory: string;
|
|
16
|
+
worktree: string;
|
|
17
|
+
}
|
|
18
|
+
interface PluginEvent {
|
|
19
|
+
type: string;
|
|
20
|
+
properties?: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
interface PluginHooks {
|
|
23
|
+
event?: (input: {
|
|
24
|
+
event: PluginEvent;
|
|
25
|
+
}) => Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
type Plugin = (ctx: PluginContext) => Promise<PluginHooks>;
|
|
28
|
+
interface Config {
|
|
29
|
+
convexUrl: string;
|
|
30
|
+
apiKey: string;
|
|
31
|
+
}
|
|
32
|
+
declare function getConfig(): Config | null;
|
|
33
|
+
declare function setConfig(cfg: Config): void;
|
|
34
|
+
declare function clearConfig(): void;
|
|
35
|
+
/**
|
|
36
|
+
* OpenCode Sync Plugin
|
|
37
|
+
* Syncs sessions and messages to cloud storage via Convex backend
|
|
38
|
+
* Authentication: API Key (osk_*) from OpenSync Settings page
|
|
39
|
+
*/
|
|
40
|
+
declare const OpenCodeSyncPlugin: Plugin;
|
|
41
|
+
|
|
42
|
+
export { OpenCodeSyncPlugin, clearConfig, getConfig, setConfig };
|
package/dist/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "opencode-sync-plugin",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Sync your OpenCode sessions to the cloud",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"bin": {
|
|
15
|
+
"opencode-sync": "dist/cli.js"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsup src/index.ts src/cli.ts --format esm --dts --clean",
|
|
19
|
+
"dev": "tsup src/index.ts src/cli.ts --format esm --dts --watch"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"keywords": [
|
|
25
|
+
"opencode",
|
|
26
|
+
"opencode-plugin",
|
|
27
|
+
"ai",
|
|
28
|
+
"sync",
|
|
29
|
+
"sessions",
|
|
30
|
+
"convex"
|
|
31
|
+
],
|
|
32
|
+
"author": "waynesutton",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/waynesutton/opencode-sync-plugin"
|
|
36
|
+
},
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"conf": "^12.0.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^20.0.0",
|
|
43
|
+
"tsup": "^8.0.0",
|
|
44
|
+
"typescript": "^5.3.0"
|
|
45
|
+
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"@opencode-ai/plugin": ">=0.1.0"
|
|
48
|
+
},
|
|
49
|
+
"peerDependenciesMeta": {
|
|
50
|
+
"@opencode-ai/plugin": {
|
|
51
|
+
"optional": true
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|