opencode-sync-plugin 0.2.8 → 0.3.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 +2 -0
- package/dist/{chunk-6K7TIEXR.js → chunk-J64QRI6W.js} +5 -1
- package/dist/cli.js +98 -22
- package/dist/config.js +1 -1
- package/dist/index.js +16 -38
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -123,6 +123,8 @@ Data is stored in your Convex deployment. You can view, search, and share sessio
|
|
|
123
123
|
| `opencode-sync logout` | Clear stored credentials |
|
|
124
124
|
| `opencode-sync status` | Show authentication status |
|
|
125
125
|
| `opencode-sync config` | Show current configuration |
|
|
126
|
+
| `opencode-sync version` | Show installed version |
|
|
127
|
+
| `opencode-sync help` | Show help message |
|
|
126
128
|
|
|
127
129
|
## Configuration storage
|
|
128
130
|
|
|
@@ -64,7 +64,11 @@ function addSyncedSessions(sessionIds) {
|
|
|
64
64
|
function clearSyncedSessions() {
|
|
65
65
|
try {
|
|
66
66
|
if (existsSync(SYNCED_SESSIONS_FILE)) {
|
|
67
|
-
writeFileSync(
|
|
67
|
+
writeFileSync(
|
|
68
|
+
SYNCED_SESSIONS_FILE,
|
|
69
|
+
JSON.stringify({ sessionIds: [], lastUpdated: Date.now() }),
|
|
70
|
+
"utf8"
|
|
71
|
+
);
|
|
68
72
|
}
|
|
69
73
|
} catch (e) {
|
|
70
74
|
console.error("Error clearing synced sessions:", e);
|
package/dist/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
getConfig,
|
|
7
7
|
getSyncedSessions,
|
|
8
8
|
setConfig
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-J64QRI6W.js";
|
|
10
10
|
|
|
11
11
|
// src/cli.ts
|
|
12
12
|
import { readFileSync, existsSync, readdirSync } from "fs";
|
|
@@ -42,7 +42,7 @@ async function main() {
|
|
|
42
42
|
status();
|
|
43
43
|
break;
|
|
44
44
|
case "config":
|
|
45
|
-
|
|
45
|
+
handleConfig();
|
|
46
46
|
break;
|
|
47
47
|
case "sync":
|
|
48
48
|
await sync();
|
|
@@ -63,16 +63,22 @@ async function main() {
|
|
|
63
63
|
}
|
|
64
64
|
async function login() {
|
|
65
65
|
console.log("\n OpenSync Login\n");
|
|
66
|
-
const convexUrl = await prompt(
|
|
66
|
+
const convexUrl = await prompt(
|
|
67
|
+
"Convex URL (e.g., https://your-project.convex.cloud): "
|
|
68
|
+
);
|
|
67
69
|
if (!convexUrl) {
|
|
68
70
|
console.error("Convex URL is required");
|
|
69
71
|
process.exit(1);
|
|
70
72
|
}
|
|
71
73
|
if (!convexUrl.includes(".convex.cloud") && !convexUrl.includes(".convex.site")) {
|
|
72
|
-
console.error(
|
|
74
|
+
console.error(
|
|
75
|
+
"Invalid Convex URL. Should end with .convex.cloud or .convex.site"
|
|
76
|
+
);
|
|
73
77
|
process.exit(1);
|
|
74
78
|
}
|
|
75
|
-
const apiKey = await prompt(
|
|
79
|
+
const apiKey = await prompt(
|
|
80
|
+
"API Key (from Settings page, starts with osk_): "
|
|
81
|
+
);
|
|
76
82
|
if (!apiKey) {
|
|
77
83
|
console.error("API Key is required");
|
|
78
84
|
process.exit(1);
|
|
@@ -102,8 +108,12 @@ async function login() {
|
|
|
102
108
|
}' > ~/.config/opencode/opencode.json`);
|
|
103
109
|
console.log("\n Then verify your setup:\n");
|
|
104
110
|
console.log(" opencode-sync verify\n");
|
|
105
|
-
console.log(
|
|
106
|
-
|
|
111
|
+
console.log(
|
|
112
|
+
" Note: If you have existing opencode.json settings, manually add"
|
|
113
|
+
);
|
|
114
|
+
console.log(
|
|
115
|
+
' "plugin": ["opencode-sync-plugin"] to preserve your config.\n'
|
|
116
|
+
);
|
|
107
117
|
} catch (e) {
|
|
108
118
|
console.error("\nFailed to connect to OpenSync backend.");
|
|
109
119
|
console.error("Please verify your Convex URL is correct.");
|
|
@@ -125,10 +135,18 @@ function verify() {
|
|
|
125
135
|
} else {
|
|
126
136
|
console.log(" Credentials: OK");
|
|
127
137
|
console.log(" Convex URL:", config.convexUrl);
|
|
128
|
-
console.log(
|
|
138
|
+
console.log(
|
|
139
|
+
" API Key:",
|
|
140
|
+
config.apiKey.slice(0, 8) + "..." + config.apiKey.slice(-4)
|
|
141
|
+
);
|
|
129
142
|
console.log();
|
|
130
143
|
}
|
|
131
|
-
const opencodeConfigPath = join(
|
|
144
|
+
const opencodeConfigPath = join(
|
|
145
|
+
homedir(),
|
|
146
|
+
".config",
|
|
147
|
+
"opencode",
|
|
148
|
+
"opencode.json"
|
|
149
|
+
);
|
|
132
150
|
const projectConfigPath = join(process.cwd(), "opencode.json");
|
|
133
151
|
let configFound = false;
|
|
134
152
|
let configPath = "";
|
|
@@ -169,9 +187,13 @@ function verify() {
|
|
|
169
187
|
console.log();
|
|
170
188
|
}
|
|
171
189
|
if (hasErrors) {
|
|
172
|
-
console.log(
|
|
190
|
+
console.log(
|
|
191
|
+
" Setup incomplete. Fix the issues above and run verify again.\n"
|
|
192
|
+
);
|
|
173
193
|
} else {
|
|
174
|
-
console.log(
|
|
194
|
+
console.log(
|
|
195
|
+
" Ready! Start OpenCode and the plugin will load automatically.\n"
|
|
196
|
+
);
|
|
175
197
|
}
|
|
176
198
|
}
|
|
177
199
|
function status() {
|
|
@@ -190,9 +212,15 @@ function status() {
|
|
|
190
212
|
}
|
|
191
213
|
console.log(" Status: Configured\n");
|
|
192
214
|
console.log(" Convex URL:", config.convexUrl);
|
|
193
|
-
console.log(
|
|
215
|
+
console.log(
|
|
216
|
+
" API Key:",
|
|
217
|
+
config.apiKey.slice(0, 8) + "..." + config.apiKey.slice(-4)
|
|
218
|
+
);
|
|
194
219
|
console.log();
|
|
195
220
|
}
|
|
221
|
+
function handleConfig() {
|
|
222
|
+
showConfig();
|
|
223
|
+
}
|
|
196
224
|
function showConfig() {
|
|
197
225
|
const config = getConfig();
|
|
198
226
|
console.log("\n OpenSync Config\n");
|
|
@@ -202,9 +230,38 @@ function showConfig() {
|
|
|
202
230
|
return;
|
|
203
231
|
}
|
|
204
232
|
console.log(" Convex URL:", config.convexUrl);
|
|
205
|
-
console.log(
|
|
233
|
+
console.log(
|
|
234
|
+
" API Key:",
|
|
235
|
+
config.apiKey ? config.apiKey.slice(0, 8) + "..." + config.apiKey.slice(-4) : "Not set"
|
|
236
|
+
);
|
|
206
237
|
console.log();
|
|
207
238
|
}
|
|
239
|
+
function getMessageTextContent(partBasePath, messageId) {
|
|
240
|
+
const messagePartPath = join(partBasePath, messageId);
|
|
241
|
+
if (!existsSync(messagePartPath)) {
|
|
242
|
+
return "";
|
|
243
|
+
}
|
|
244
|
+
try {
|
|
245
|
+
const partFiles = readdirSync(messagePartPath).filter(
|
|
246
|
+
(f) => f.endsWith(".json")
|
|
247
|
+
);
|
|
248
|
+
let textContent = "";
|
|
249
|
+
for (const partFile of partFiles) {
|
|
250
|
+
try {
|
|
251
|
+
const partData = JSON.parse(
|
|
252
|
+
readFileSync(join(messagePartPath, partFile), "utf8")
|
|
253
|
+
);
|
|
254
|
+
if (partData.type === "text" && partData.text) {
|
|
255
|
+
textContent += partData.text;
|
|
256
|
+
}
|
|
257
|
+
} catch {
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return textContent;
|
|
261
|
+
} catch {
|
|
262
|
+
return "";
|
|
263
|
+
}
|
|
264
|
+
}
|
|
208
265
|
async function sync() {
|
|
209
266
|
const syncAll = args.includes("--all");
|
|
210
267
|
const syncNew = args.includes("--new");
|
|
@@ -308,9 +365,16 @@ async function syncAllSessions(siteUrl, apiKey, mode) {
|
|
|
308
365
|
console.log(`
|
|
309
366
|
OpenSync: ${modeLabel} Local Sessions
|
|
310
367
|
`);
|
|
311
|
-
const opencodePath = join(
|
|
368
|
+
const opencodePath = join(
|
|
369
|
+
homedir(),
|
|
370
|
+
".local",
|
|
371
|
+
"share",
|
|
372
|
+
"opencode",
|
|
373
|
+
"storage"
|
|
374
|
+
);
|
|
312
375
|
const sessionPath = join(opencodePath, "session");
|
|
313
376
|
const messagePath = join(opencodePath, "message");
|
|
377
|
+
const partPath = join(opencodePath, "part");
|
|
314
378
|
if (!existsSync(sessionPath)) {
|
|
315
379
|
console.log(" No OpenCode sessions found.");
|
|
316
380
|
console.log(" Expected path:", sessionPath);
|
|
@@ -322,7 +386,9 @@ async function syncAllSessions(siteUrl, apiKey, mode) {
|
|
|
322
386
|
const projectDirs = readdirSync(sessionPath, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
323
387
|
for (const projectDir of projectDirs) {
|
|
324
388
|
const projectSessionPath = join(sessionPath, projectDir);
|
|
325
|
-
const sessionFiles = readdirSync(projectSessionPath).filter(
|
|
389
|
+
const sessionFiles = readdirSync(projectSessionPath).filter(
|
|
390
|
+
(f) => f.endsWith(".json")
|
|
391
|
+
);
|
|
326
392
|
for (const file of sessionFiles) {
|
|
327
393
|
try {
|
|
328
394
|
const content = readFileSync(join(projectSessionPath, file), "utf8");
|
|
@@ -335,7 +401,10 @@ async function syncAllSessions(siteUrl, apiKey, mode) {
|
|
|
335
401
|
}
|
|
336
402
|
}
|
|
337
403
|
} catch (e) {
|
|
338
|
-
console.log(
|
|
404
|
+
console.log(
|
|
405
|
+
" Error reading sessions:",
|
|
406
|
+
e instanceof Error ? e.message : String(e)
|
|
407
|
+
);
|
|
339
408
|
return;
|
|
340
409
|
}
|
|
341
410
|
console.log(` Found ${sessions.length} local sessions`);
|
|
@@ -372,7 +441,9 @@ async function syncAllSessions(siteUrl, apiKey, mode) {
|
|
|
372
441
|
const newlySyncedIds = [];
|
|
373
442
|
for (const session of sessionsToSync) {
|
|
374
443
|
const { data } = session;
|
|
375
|
-
process.stdout.write(
|
|
444
|
+
process.stdout.write(
|
|
445
|
+
` Syncing: ${data.title || data.slug || data.id}... `
|
|
446
|
+
);
|
|
376
447
|
let totalPromptTokens = 0;
|
|
377
448
|
let totalCompletionTokens = 0;
|
|
378
449
|
let totalCost = 0;
|
|
@@ -382,10 +453,15 @@ async function syncAllSessions(siteUrl, apiKey, mode) {
|
|
|
382
453
|
const messages = [];
|
|
383
454
|
if (existsSync(sessionMessagePath)) {
|
|
384
455
|
try {
|
|
385
|
-
const messageFiles = readdirSync(sessionMessagePath).filter(
|
|
456
|
+
const messageFiles = readdirSync(sessionMessagePath).filter(
|
|
457
|
+
(f) => f.endsWith(".json")
|
|
458
|
+
);
|
|
386
459
|
for (const msgFile of messageFiles) {
|
|
387
460
|
try {
|
|
388
|
-
const msgContent = readFileSync(
|
|
461
|
+
const msgContent = readFileSync(
|
|
462
|
+
join(sessionMessagePath, msgFile),
|
|
463
|
+
"utf8"
|
|
464
|
+
);
|
|
389
465
|
const msgData = JSON.parse(msgContent);
|
|
390
466
|
if (msgData.id && msgData.sessionID === data.id) {
|
|
391
467
|
messages.push(msgData);
|
|
@@ -438,6 +514,7 @@ async function syncAllSessions(siteUrl, apiKey, mode) {
|
|
|
438
514
|
let msgCount = 0;
|
|
439
515
|
for (const msg of messages) {
|
|
440
516
|
try {
|
|
517
|
+
const textContent = getMessageTextContent(partPath, msg.id);
|
|
441
518
|
const msgRes = await fetch(`${siteUrl}/sync/message`, {
|
|
442
519
|
method: "POST",
|
|
443
520
|
headers: {
|
|
@@ -448,8 +525,7 @@ async function syncAllSessions(siteUrl, apiKey, mode) {
|
|
|
448
525
|
sessionExternalId: data.id,
|
|
449
526
|
externalId: msg.id,
|
|
450
527
|
role: msg.role,
|
|
451
|
-
textContent
|
|
452
|
-
// We don't have content in the message metadata files
|
|
528
|
+
textContent,
|
|
453
529
|
model: msg.modelID,
|
|
454
530
|
promptTokens: msg.tokens?.input,
|
|
455
531
|
completionTokens: msg.tokens?.output,
|
|
@@ -503,7 +579,7 @@ function help() {
|
|
|
503
579
|
status Show current authentication status
|
|
504
580
|
config Show current configuration
|
|
505
581
|
version Show version number
|
|
506
|
-
help
|
|
582
|
+
help Show this help message
|
|
507
583
|
|
|
508
584
|
Setup:
|
|
509
585
|
1. Go to your OpenSync dashboard Settings page
|
package/dist/config.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getConfig
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-J64QRI6W.js";
|
|
4
4
|
|
|
5
5
|
// src/index.ts
|
|
6
6
|
var syncedSessions = /* @__PURE__ */ new Set();
|
|
@@ -13,20 +13,14 @@ function inferRole(textContent) {
|
|
|
13
13
|
const assistantPatterns = [
|
|
14
14
|
/^(I'll|Let me|Here's|I can|I've|I'm going to|I will|Sure|Certainly|Of course)/i,
|
|
15
15
|
/```[\s\S]+```/,
|
|
16
|
-
// Code blocks
|
|
17
16
|
/^(Yes|No),?\s+(I|you|we|this|that)/i,
|
|
18
|
-
// Answering patterns
|
|
19
17
|
/\*\*[^*]+\*\*/,
|
|
20
|
-
// Bold markdown (explanations)
|
|
21
18
|
/^\d+\.\s+\*\*/
|
|
22
|
-
// Numbered lists with bold
|
|
23
19
|
];
|
|
24
20
|
const userPatterns = [
|
|
25
21
|
/\?$/,
|
|
26
|
-
// Questions
|
|
27
22
|
/^(create|fix|add|update|show|make|build|implement|write|delete|remove|change|modify|help|can you|please|I want|I need)/i,
|
|
28
23
|
/^@/
|
|
29
|
-
// File references
|
|
30
24
|
];
|
|
31
25
|
for (const pattern of assistantPatterns) {
|
|
32
26
|
if (pattern.test(textContent)) {
|
|
@@ -44,11 +38,9 @@ function doSyncSession(session) {
|
|
|
44
38
|
try {
|
|
45
39
|
const config = getConfig();
|
|
46
40
|
if (!config?.apiKey || !config?.convexUrl) {
|
|
47
|
-
console.error("[opencode-sync] Missing config - cannot sync session");
|
|
48
41
|
return;
|
|
49
42
|
}
|
|
50
43
|
const url = config.convexUrl.replace(".convex.cloud", ".convex.site");
|
|
51
|
-
console.log("[opencode-sync] Syncing session:", session.id);
|
|
52
44
|
const projectPath = session.path?.cwd || session.cwd || session.directory;
|
|
53
45
|
const modelId = session.modelID || session.model?.modelID || session.model;
|
|
54
46
|
const providerId = session.providerID || session.model?.providerID || session.provider;
|
|
@@ -72,32 +64,22 @@ function doSyncSession(session) {
|
|
|
72
64
|
completionTokens,
|
|
73
65
|
cost
|
|
74
66
|
})
|
|
75
|
-
}).
|
|
76
|
-
|
|
77
|
-
|
|
67
|
+
}).catch(() => {
|
|
68
|
+
});
|
|
69
|
+
} catch {
|
|
78
70
|
}
|
|
79
71
|
}
|
|
80
72
|
function doSyncMessage(sessionId, messageId, role, textContent, metadata) {
|
|
81
73
|
try {
|
|
82
74
|
const config = getConfig();
|
|
83
75
|
if (!config?.apiKey || !config?.convexUrl) {
|
|
84
|
-
console.error("[opencode-sync] Missing config - cannot sync message");
|
|
85
76
|
return;
|
|
86
77
|
}
|
|
87
78
|
if (!textContent || textContent.trim().length === 0) {
|
|
88
|
-
console.log("[opencode-sync] Skipping empty message:", messageId);
|
|
89
79
|
return;
|
|
90
80
|
}
|
|
91
81
|
const finalRole = role === "unknown" || !role ? inferRole(textContent) : role;
|
|
92
82
|
const url = config.convexUrl.replace(".convex.cloud", ".convex.site");
|
|
93
|
-
console.log(
|
|
94
|
-
"[opencode-sync] Syncing message:",
|
|
95
|
-
messageId,
|
|
96
|
-
"role:",
|
|
97
|
-
finalRole,
|
|
98
|
-
"text length:",
|
|
99
|
-
textContent.length
|
|
100
|
-
);
|
|
101
83
|
let durationMs;
|
|
102
84
|
if (metadata?.time?.completed && metadata?.time?.created) {
|
|
103
85
|
durationMs = metadata.time.completed - metadata.time.created;
|
|
@@ -118,9 +100,9 @@ function doSyncMessage(sessionId, messageId, role, textContent, metadata) {
|
|
|
118
100
|
completionTokens: metadata?.tokens?.output,
|
|
119
101
|
durationMs
|
|
120
102
|
})
|
|
121
|
-
}).
|
|
122
|
-
|
|
123
|
-
|
|
103
|
+
}).catch(() => {
|
|
104
|
+
});
|
|
105
|
+
} catch {
|
|
124
106
|
}
|
|
125
107
|
}
|
|
126
108
|
function trySyncMessage(messageId) {
|
|
@@ -131,7 +113,13 @@ function trySyncMessage(messageId) {
|
|
|
131
113
|
const textContent = textParts.join("");
|
|
132
114
|
if (!textContent.trim()) return;
|
|
133
115
|
syncedMessages.add(messageId);
|
|
134
|
-
doSyncMessage(
|
|
116
|
+
doSyncMessage(
|
|
117
|
+
metadata.sessionId,
|
|
118
|
+
messageId,
|
|
119
|
+
metadata.role,
|
|
120
|
+
textContent,
|
|
121
|
+
metadata.info
|
|
122
|
+
);
|
|
135
123
|
messagePartsText.delete(messageId);
|
|
136
124
|
messageMetadata.delete(messageId);
|
|
137
125
|
}
|
|
@@ -144,8 +132,7 @@ function scheduleSyncMessage(messageId) {
|
|
|
144
132
|
}, DEBOUNCE_MS);
|
|
145
133
|
syncTimeouts.set(messageId, timeout);
|
|
146
134
|
}
|
|
147
|
-
var OpenCodeSyncPlugin = async (
|
|
148
|
-
console.log("[opencode-sync] Plugin initialized for project:", input.project?.id);
|
|
135
|
+
var OpenCodeSyncPlugin = async () => {
|
|
149
136
|
return {
|
|
150
137
|
event: async ({ event }) => {
|
|
151
138
|
try {
|
|
@@ -163,7 +150,6 @@ var OpenCodeSyncPlugin = async (input) => {
|
|
|
163
150
|
if (event.type === "message.updated") {
|
|
164
151
|
const info = props?.info;
|
|
165
152
|
if (info?.id && info?.sessionID && info?.role) {
|
|
166
|
-
console.log("[opencode-sync] Message metadata received:", info.id, "role:", info.role);
|
|
167
153
|
messageMetadata.set(info.id, {
|
|
168
154
|
role: info.role,
|
|
169
155
|
sessionId: info.sessionID,
|
|
@@ -179,17 +165,10 @@ var OpenCodeSyncPlugin = async (input) => {
|
|
|
179
165
|
if (part?.type === "text" && part?.messageID && part?.sessionID) {
|
|
180
166
|
const messageId = part.messageID;
|
|
181
167
|
const text = part.text || "";
|
|
182
|
-
console.log(
|
|
183
|
-
"[opencode-sync] Text part received for message:",
|
|
184
|
-
messageId,
|
|
185
|
-
"length:",
|
|
186
|
-
text.length
|
|
187
|
-
);
|
|
188
168
|
messagePartsText.set(messageId, [text]);
|
|
189
169
|
if (!messageMetadata.has(messageId)) {
|
|
190
170
|
messageMetadata.set(messageId, {
|
|
191
171
|
role: "unknown",
|
|
192
|
-
// Will be inferred or updated from message.updated
|
|
193
172
|
sessionId: part.sessionID,
|
|
194
173
|
info: {}
|
|
195
174
|
});
|
|
@@ -197,8 +176,7 @@ var OpenCodeSyncPlugin = async (input) => {
|
|
|
197
176
|
scheduleSyncMessage(messageId);
|
|
198
177
|
}
|
|
199
178
|
}
|
|
200
|
-
} catch
|
|
201
|
-
console.error("[opencode-sync] Event handler error:", err);
|
|
179
|
+
} catch {
|
|
202
180
|
}
|
|
203
181
|
}
|
|
204
182
|
};
|