opencode-sync-plugin 0.3.2 → 0.3.5
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 +42 -2
- package/dist/index.js +94 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Sync your OpenCode sessions to the cloud. Search, share, and access your coding
|
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/opencode-sync-plugin)
|
|
6
6
|
|
|
7
|
-
## OpenSync Ecosystem
|
|
7
|
+
## OpenSync Ecosystem link
|
|
8
8
|
|
|
9
9
|
| Package | Description | Links |
|
|
10
10
|
|---------|-------------|-------|
|
|
@@ -31,6 +31,44 @@ npm install
|
|
|
31
31
|
npm run build
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
+
## Upgrading
|
|
35
|
+
|
|
36
|
+
To upgrade to the latest version of the plugin:
|
|
37
|
+
|
|
38
|
+
**Using npm:**
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm update -g opencode-sync-plugin
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**Or reinstall to get the latest:**
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npm install -g opencode-sync-plugin@latest
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Clear the OpenCode plugin cache** (required for OpenCode to pick up the new version):
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
rm -rf ~/.cache/opencode/node_modules/opencode-sync-plugin
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Restart OpenCode** to load the updated plugin.
|
|
57
|
+
|
|
58
|
+
**Check your installed version:**
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
opencode-sync version
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Backfill existing sessions with proper titles** (if upgrading from v0.3.2 or earlier):
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
opencode-sync sync --force
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
This re-syncs all local sessions with accurate titles from OpenCode's local storage.
|
|
71
|
+
|
|
34
72
|
## Setup
|
|
35
73
|
|
|
36
74
|
### 1. Get your credentials
|
|
@@ -112,10 +150,12 @@ The plugin hooks into OpenCode events and syncs data automatically:
|
|
|
112
150
|
|-------|--------|
|
|
113
151
|
| `session.created` | Creates session record in cloud |
|
|
114
152
|
| `session.updated` | Updates session metadata |
|
|
115
|
-
| `session.idle` | Final sync with token counts and cost |
|
|
153
|
+
| `session.idle` | Final sync with accurate title, token counts, and cost |
|
|
116
154
|
| `message.updated` | Syncs user and assistant messages |
|
|
117
155
|
| `message.part.updated` | Syncs completed message parts |
|
|
118
156
|
|
|
157
|
+
On `session.idle`, the plugin queries OpenCode's SDK to get the accurate session title (generated after the first message exchange). This ensures sessions are stored with meaningful titles instead of "Untitled".
|
|
158
|
+
|
|
119
159
|
Data is stored in your Convex deployment. You can view, search, and share sessions via the web UI.
|
|
120
160
|
|
|
121
161
|
## CLI Commands
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,64 @@ import {
|
|
|
3
3
|
} from "./chunk-J64QRI6W.js";
|
|
4
4
|
|
|
5
5
|
// src/index.ts
|
|
6
|
+
import { existsSync, readFileSync, readdirSync } from "fs";
|
|
7
|
+
import { homedir } from "os";
|
|
8
|
+
import { join } from "path";
|
|
6
9
|
var syncedSessions = /* @__PURE__ */ new Set();
|
|
10
|
+
function getLocalSessionData(sessionId) {
|
|
11
|
+
try {
|
|
12
|
+
const basePath = join(homedir(), ".local", "share", "opencode", "storage");
|
|
13
|
+
const sessionPath = join(basePath, "session");
|
|
14
|
+
const messagePath = join(basePath, "message", sessionId);
|
|
15
|
+
if (!existsSync(sessionPath)) return null;
|
|
16
|
+
let result = {};
|
|
17
|
+
const projectDirs = readdirSync(sessionPath, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
18
|
+
for (const projectDir of projectDirs) {
|
|
19
|
+
const sessionFile = join(sessionPath, projectDir, `${sessionId}.json`);
|
|
20
|
+
if (existsSync(sessionFile)) {
|
|
21
|
+
const content = readFileSync(sessionFile, "utf8");
|
|
22
|
+
const data = JSON.parse(content);
|
|
23
|
+
result.title = data.title;
|
|
24
|
+
result.slug = data.slug;
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (existsSync(messagePath)) {
|
|
29
|
+
let totalPromptTokens = 0;
|
|
30
|
+
let totalCompletionTokens = 0;
|
|
31
|
+
let totalCost = 0;
|
|
32
|
+
const messageFiles = readdirSync(messagePath).filter(
|
|
33
|
+
(f) => f.endsWith(".json")
|
|
34
|
+
);
|
|
35
|
+
for (const msgFile of messageFiles) {
|
|
36
|
+
try {
|
|
37
|
+
const msgContent = readFileSync(join(messagePath, msgFile), "utf8");
|
|
38
|
+
const msgData = JSON.parse(msgContent);
|
|
39
|
+
if (msgData.tokens) {
|
|
40
|
+
totalPromptTokens += msgData.tokens.input || 0;
|
|
41
|
+
totalCompletionTokens += msgData.tokens.output || 0;
|
|
42
|
+
}
|
|
43
|
+
if (msgData.cost) {
|
|
44
|
+
totalCost += msgData.cost;
|
|
45
|
+
}
|
|
46
|
+
if (!result.model && msgData.modelID) {
|
|
47
|
+
result.model = msgData.modelID;
|
|
48
|
+
}
|
|
49
|
+
if (!result.provider && msgData.providerID) {
|
|
50
|
+
result.provider = msgData.providerID;
|
|
51
|
+
}
|
|
52
|
+
} catch {
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
result.promptTokens = totalPromptTokens;
|
|
56
|
+
result.completionTokens = totalCompletionTokens;
|
|
57
|
+
result.cost = totalCost;
|
|
58
|
+
}
|
|
59
|
+
return result;
|
|
60
|
+
} catch {
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
7
64
|
var syncedMessages = /* @__PURE__ */ new Set();
|
|
8
65
|
var messagePartsText = /* @__PURE__ */ new Map();
|
|
9
66
|
var messageMetadata = /* @__PURE__ */ new Map();
|
|
@@ -55,7 +112,7 @@ function doSyncSession(session) {
|
|
|
55
112
|
},
|
|
56
113
|
body: JSON.stringify({
|
|
57
114
|
externalId: session.id,
|
|
58
|
-
title: session.title || "Untitled Session",
|
|
115
|
+
title: session.title || session.slug || "Untitled Session",
|
|
59
116
|
projectPath,
|
|
60
117
|
projectName: projectPath?.split("/").pop(),
|
|
61
118
|
model: modelId,
|
|
@@ -132,7 +189,7 @@ function scheduleSyncMessage(messageId) {
|
|
|
132
189
|
}, DEBOUNCE_MS);
|
|
133
190
|
syncTimeouts.set(messageId, timeout);
|
|
134
191
|
}
|
|
135
|
-
var OpenCodeSyncPlugin = async () => {
|
|
192
|
+
var OpenCodeSyncPlugin = async ({ client }) => {
|
|
136
193
|
return {
|
|
137
194
|
event: async ({ event }) => {
|
|
138
195
|
try {
|
|
@@ -144,6 +201,41 @@ var OpenCodeSyncPlugin = async () => {
|
|
|
144
201
|
if (syncedSessions.has(sessionId)) return;
|
|
145
202
|
syncedSessions.add(sessionId);
|
|
146
203
|
}
|
|
204
|
+
if (event.type === "session.idle") {
|
|
205
|
+
const localData = getLocalSessionData(sessionId);
|
|
206
|
+
if (localData) {
|
|
207
|
+
doSyncSession({
|
|
208
|
+
...props,
|
|
209
|
+
title: localData.title || props?.title,
|
|
210
|
+
slug: localData.slug || props?.slug,
|
|
211
|
+
modelID: localData.model || props?.modelID,
|
|
212
|
+
providerID: localData.provider || props?.providerID,
|
|
213
|
+
tokens: {
|
|
214
|
+
input: localData.promptTokens || 0,
|
|
215
|
+
output: localData.completionTokens || 0
|
|
216
|
+
},
|
|
217
|
+
cost: localData.cost || 0
|
|
218
|
+
});
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
if (client) {
|
|
222
|
+
try {
|
|
223
|
+
const response = await client.session.get({
|
|
224
|
+
path: { id: sessionId }
|
|
225
|
+
});
|
|
226
|
+
const sessionData = response?.data || response;
|
|
227
|
+
if (sessionData?.title || sessionData?.slug) {
|
|
228
|
+
doSyncSession({
|
|
229
|
+
...props,
|
|
230
|
+
title: sessionData.title,
|
|
231
|
+
slug: sessionData.slug
|
|
232
|
+
});
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
} catch {
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
147
239
|
doSyncSession(props);
|
|
148
240
|
}
|
|
149
241
|
}
|