opencode-sync-plugin 0.2.6 → 0.2.8

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 CHANGED
@@ -27,7 +27,7 @@ npm run build
27
27
 
28
28
  ### 1. Get your credentials
29
29
 
30
- You need two things from your OpenSync deployment:
30
+ You need two things from your OpenSync deployment
31
31
 
32
32
  - **Convex URL**: Your deployment URL from the Convex dashboard (e.g., `https://your-project-123.convex.cloud`)
33
33
  - **API Key**: Generated in the OpenSync dashboard at **Settings > API Key** (starts with `osk_`)
@@ -117,7 +117,9 @@ Data is stored in your Convex deployment. You can view, search, and share sessio
117
117
  | `opencode-sync login` | Configure with Convex URL and API Key |
118
118
  | `opencode-sync verify` | Verify credentials and OpenCode config |
119
119
  | `opencode-sync sync` | Test connectivity and create a test session |
120
- | `opencode-sync sync --all` | Sync all local OpenCode sessions to the cloud |
120
+ | `opencode-sync sync --new` | Sync only new sessions (uses local tracking) |
121
+ | `opencode-sync sync --all` | Sync all sessions (queries backend, skips existing) |
122
+ | `opencode-sync sync --force` | Clear tracking and resync all sessions |
121
123
  | `opencode-sync logout` | Clear stored credentials |
122
124
  | `opencode-sync status` | Show authentication status |
123
125
  | `opencode-sync config` | Show current configuration |
@@ -0,0 +1,81 @@
1
+ // src/config.ts
2
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
3
+ import { homedir } from "os";
4
+ import { join } from "path";
5
+ var CONFIG_DIR = join(homedir(), ".opensync");
6
+ var CONFIG_FILE = join(CONFIG_DIR, "credentials.json");
7
+ var SYNCED_SESSIONS_FILE = join(CONFIG_DIR, "synced-sessions.json");
8
+ function getConfig() {
9
+ try {
10
+ if (!existsSync(CONFIG_FILE)) return null;
11
+ const content = readFileSync(CONFIG_FILE, "utf8");
12
+ const config = JSON.parse(content);
13
+ if (!config || !config.convexUrl) return null;
14
+ return config;
15
+ } catch (e) {
16
+ console.error("Error reading config:", e);
17
+ return null;
18
+ }
19
+ }
20
+ function setConfig(cfg) {
21
+ try {
22
+ if (!existsSync(CONFIG_DIR)) {
23
+ mkdirSync(CONFIG_DIR, { recursive: true });
24
+ }
25
+ writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2), "utf8");
26
+ } catch (e) {
27
+ console.error("Error saving config:", e);
28
+ }
29
+ }
30
+ function clearConfig() {
31
+ try {
32
+ if (existsSync(CONFIG_FILE)) {
33
+ writeFileSync(CONFIG_FILE, "{}", "utf8");
34
+ }
35
+ } catch (e) {
36
+ console.error("Error clearing config:", e);
37
+ }
38
+ }
39
+ function getSyncedSessions() {
40
+ try {
41
+ if (!existsSync(SYNCED_SESSIONS_FILE)) return /* @__PURE__ */ new Set();
42
+ const content = readFileSync(SYNCED_SESSIONS_FILE, "utf8");
43
+ const data = JSON.parse(content);
44
+ return new Set(Array.isArray(data.sessionIds) ? data.sessionIds : []);
45
+ } catch {
46
+ return /* @__PURE__ */ new Set();
47
+ }
48
+ }
49
+ function addSyncedSessions(sessionIds) {
50
+ try {
51
+ if (!existsSync(CONFIG_DIR)) {
52
+ mkdirSync(CONFIG_DIR, { recursive: true });
53
+ }
54
+ const existing = getSyncedSessions();
55
+ for (const id of sessionIds) {
56
+ existing.add(id);
57
+ }
58
+ const data = { sessionIds: Array.from(existing), lastUpdated: Date.now() };
59
+ writeFileSync(SYNCED_SESSIONS_FILE, JSON.stringify(data, null, 2), "utf8");
60
+ } catch (e) {
61
+ console.error("Error saving synced sessions:", e);
62
+ }
63
+ }
64
+ function clearSyncedSessions() {
65
+ try {
66
+ if (existsSync(SYNCED_SESSIONS_FILE)) {
67
+ writeFileSync(SYNCED_SESSIONS_FILE, JSON.stringify({ sessionIds: [], lastUpdated: Date.now() }), "utf8");
68
+ }
69
+ } catch (e) {
70
+ console.error("Error clearing synced sessions:", e);
71
+ }
72
+ }
73
+
74
+ export {
75
+ getConfig,
76
+ setConfig,
77
+ clearConfig,
78
+ getSyncedSessions,
79
+ addSyncedSessions,
80
+ clearSyncedSessions
81
+ };
package/dist/cli.js CHANGED
@@ -1,9 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ addSyncedSessions,
3
4
  clearConfig,
5
+ clearSyncedSessions,
4
6
  getConfig,
7
+ getSyncedSessions,
5
8
  setConfig
6
- } from "./chunk-JPPDGYOB.js";
9
+ } from "./chunk-6K7TIEXR.js";
7
10
 
8
11
  // src/cli.ts
9
12
  import { readFileSync, existsSync, readdirSync } from "fs";
@@ -204,6 +207,8 @@ function showConfig() {
204
207
  }
205
208
  async function sync() {
206
209
  const syncAll = args.includes("--all");
210
+ const syncNew = args.includes("--new");
211
+ const syncForce = args.includes("--force");
207
212
  const config = getConfig();
208
213
  if (!config || !config.apiKey || !config.convexUrl) {
209
214
  console.log("\n Status: Not configured\n");
@@ -211,8 +216,13 @@ async function sync() {
211
216
  return;
212
217
  }
213
218
  const siteUrl = config.convexUrl.replace(".convex.cloud", ".convex.site");
214
- if (syncAll) {
215
- await syncAllSessions(siteUrl, config.apiKey);
219
+ if (syncForce) {
220
+ clearSyncedSessions();
221
+ await syncAllSessions(siteUrl, config.apiKey, "force");
222
+ } else if (syncAll) {
223
+ await syncAllSessions(siteUrl, config.apiKey, "all");
224
+ } else if (syncNew) {
225
+ await syncAllSessions(siteUrl, config.apiKey, "new");
216
226
  } else {
217
227
  await syncConnectivityTest(siteUrl, config.apiKey);
218
228
  }
@@ -277,8 +287,27 @@ async function syncConnectivityTest(siteUrl, apiKey) {
277
287
  console.log();
278
288
  }
279
289
  }
280
- async function syncAllSessions(siteUrl, apiKey) {
281
- console.log("\n OpenSync: Syncing All Local Sessions\n");
290
+ async function fetchBackendSessionIds(siteUrl, apiKey) {
291
+ try {
292
+ const res = await fetch(`${siteUrl}/sync/sessions/list`, {
293
+ method: "GET",
294
+ headers: {
295
+ Authorization: `Bearer ${apiKey}`
296
+ }
297
+ });
298
+ if (res.ok) {
299
+ const data = await res.json();
300
+ return new Set(Array.isArray(data.sessionIds) ? data.sessionIds : []);
301
+ }
302
+ } catch {
303
+ }
304
+ return /* @__PURE__ */ new Set();
305
+ }
306
+ async function syncAllSessions(siteUrl, apiKey, mode) {
307
+ const modeLabel = mode === "force" ? "Force Syncing" : mode === "new" ? "Syncing New" : "Syncing All";
308
+ console.log(`
309
+ OpenSync: ${modeLabel} Local Sessions
310
+ `);
282
311
  const opencodePath = join(homedir(), ".local", "share", "opencode", "storage");
283
312
  const sessionPath = join(opencodePath, "session");
284
313
  const messagePath = join(opencodePath, "message");
@@ -309,15 +338,39 @@ async function syncAllSessions(siteUrl, apiKey) {
309
338
  console.log(" Error reading sessions:", e instanceof Error ? e.message : String(e));
310
339
  return;
311
340
  }
312
- console.log(` Found ${sessions.length} sessions
313
- `);
341
+ console.log(` Found ${sessions.length} local sessions`);
314
342
  if (sessions.length === 0) {
315
343
  return;
316
344
  }
317
- let syncedSessions = 0;
345
+ let alreadySynced = /* @__PURE__ */ new Set();
346
+ if (mode === "all") {
347
+ console.log(" Checking backend for existing sessions...");
348
+ alreadySynced = await fetchBackendSessionIds(siteUrl, apiKey);
349
+ if (alreadySynced.size > 0) {
350
+ console.log(` Found ${alreadySynced.size} already synced on backend`);
351
+ }
352
+ } else if (mode === "new") {
353
+ alreadySynced = getSyncedSessions();
354
+ if (alreadySynced.size > 0) {
355
+ console.log(` Found ${alreadySynced.size} in local tracking file`);
356
+ }
357
+ }
358
+ const sessionsToSync = sessions.filter((s) => !alreadySynced.has(s.data.id));
359
+ const skippedCount = sessions.length - sessionsToSync.length;
360
+ if (skippedCount > 0) {
361
+ console.log(` Skipping ${skippedCount} already synced sessions`);
362
+ }
363
+ console.log(` Will sync ${sessionsToSync.length} sessions
364
+ `);
365
+ if (sessionsToSync.length === 0) {
366
+ console.log(" All sessions already synced. Use --force to resync.\n");
367
+ return;
368
+ }
369
+ let syncedSessionCount = 0;
318
370
  let syncedMessages = 0;
319
371
  let failedSessions = 0;
320
- for (const session of sessions) {
372
+ const newlySyncedIds = [];
373
+ for (const session of sessionsToSync) {
321
374
  const { data } = session;
322
375
  process.stdout.write(` Syncing: ${data.title || data.slug || data.id}... `);
323
376
  let totalPromptTokens = 0;
@@ -380,7 +433,8 @@ async function syncAllSessions(siteUrl, apiKey) {
380
433
  failedSessions++;
381
434
  continue;
382
435
  }
383
- syncedSessions++;
436
+ syncedSessionCount++;
437
+ newlySyncedIds.push(data.id);
384
438
  let msgCount = 0;
385
439
  for (const msg of messages) {
386
440
  try {
@@ -415,10 +469,16 @@ async function syncAllSessions(siteUrl, apiKey) {
415
469
  failedSessions++;
416
470
  }
417
471
  }
472
+ if (newlySyncedIds.length > 0) {
473
+ addSyncedSessions(newlySyncedIds);
474
+ }
418
475
  console.log();
419
476
  console.log(` Summary:`);
420
- console.log(` Sessions synced: ${syncedSessions}`);
477
+ console.log(` Sessions synced: ${syncedSessionCount}`);
421
478
  console.log(` Messages synced: ${syncedMessages}`);
479
+ if (skippedCount > 0) {
480
+ console.log(` Skipped: ${skippedCount}`);
481
+ }
422
482
  if (failedSessions > 0) {
423
483
  console.log(` Failed: ${failedSessions}`);
424
484
  }
@@ -433,14 +493,16 @@ function help() {
433
493
  Usage: opencode-sync <command> [options]
434
494
 
435
495
  Commands:
436
- login Configure with Convex URL and API Key
437
- verify Verify credentials and OpenCode config
438
- sync Test connectivity and create a test session
439
- sync --all Sync all local OpenCode sessions to the cloud
440
- logout Clear stored credentials
441
- status Show current authentication status
442
- config Show current configuration
443
- version Show version number
496
+ login Configure with Convex URL and API Key
497
+ verify Verify credentials and OpenCode config
498
+ sync Test connectivity and create a test session
499
+ sync --new Sync only sessions not in local tracking file
500
+ sync --all Sync all sessions (checks backend, skips existing)
501
+ sync --force Clear tracking and resync all sessions
502
+ logout Clear stored credentials
503
+ status Show current authentication status
504
+ config Show current configuration
505
+ version Show version number
444
506
  help Show this help message
445
507
 
446
508
  Setup:
@@ -451,7 +513,12 @@ function help() {
451
513
  5. Add plugin to opencode.json (see instructions after login)
452
514
  6. Run: opencode-sync verify
453
515
  7. Run: opencode-sync sync (to test connectivity)
454
- 8. Run: opencode-sync sync --all (to sync existing sessions)
516
+ 8. Run: opencode-sync sync --new (to sync new sessions only)
517
+
518
+ Sync Modes:
519
+ --new Fast: uses local tracking, skips previously synced
520
+ --all Accurate: queries backend, skips existing on server
521
+ --force Full: clears tracking and resyncs everything
455
522
  `);
456
523
  }
457
524
  function prompt(question) {
package/dist/config.d.ts CHANGED
@@ -5,5 +5,8 @@ interface Config {
5
5
  declare function getConfig(): Config | null;
6
6
  declare function setConfig(cfg: Config): void;
7
7
  declare function clearConfig(): void;
8
+ declare function getSyncedSessions(): Set<string>;
9
+ declare function addSyncedSessions(sessionIds: string[]): void;
10
+ declare function clearSyncedSessions(): void;
8
11
 
9
- export { clearConfig, getConfig, setConfig };
12
+ export { addSyncedSessions, clearConfig, clearSyncedSessions, getConfig, getSyncedSessions, setConfig };
package/dist/config.js CHANGED
@@ -1,10 +1,16 @@
1
1
  import {
2
+ addSyncedSessions,
2
3
  clearConfig,
4
+ clearSyncedSessions,
3
5
  getConfig,
6
+ getSyncedSessions,
4
7
  setConfig
5
- } from "./chunk-JPPDGYOB.js";
8
+ } from "./chunk-6K7TIEXR.js";
6
9
  export {
10
+ addSyncedSessions,
7
11
  clearConfig,
12
+ clearSyncedSessions,
8
13
  getConfig,
14
+ getSyncedSessions,
9
15
  setConfig
10
16
  };
package/dist/index.d.ts CHANGED
@@ -1,10 +1,5 @@
1
- declare const OpenCodeSyncPlugin: (_ctx: Record<string, unknown>) => Promise<{
2
- event: (input: {
3
- event: {
4
- type: string;
5
- properties?: Record<string, unknown>;
6
- };
7
- }) => Promise<void>;
8
- }>;
1
+ import { Plugin } from '@opencode-ai/plugin';
9
2
 
10
- export { OpenCodeSyncPlugin as default };
3
+ declare const OpenCodeSyncPlugin: Plugin;
4
+
5
+ export { OpenCodeSyncPlugin, OpenCodeSyncPlugin as default };
package/dist/index.js CHANGED
@@ -1,104 +1,210 @@
1
1
  import {
2
2
  getConfig
3
- } from "./chunk-JPPDGYOB.js";
3
+ } from "./chunk-6K7TIEXR.js";
4
4
 
5
5
  // src/index.ts
6
- var syncedMessages = /* @__PURE__ */ new Set();
7
6
  var syncedSessions = /* @__PURE__ */ new Set();
8
- function extractText(content) {
9
- if (typeof content === "string") return content;
10
- if (Array.isArray(content)) {
11
- return content.filter((p) => p.type === "text" && p.text).map((p) => p.text).join("\n");
7
+ var syncedMessages = /* @__PURE__ */ new Set();
8
+ var messagePartsText = /* @__PURE__ */ new Map();
9
+ var messageMetadata = /* @__PURE__ */ new Map();
10
+ var syncTimeouts = /* @__PURE__ */ new Map();
11
+ var DEBOUNCE_MS = 800;
12
+ function inferRole(textContent) {
13
+ const assistantPatterns = [
14
+ /^(I'll|Let me|Here's|I can|I've|I'm going to|I will|Sure|Certainly|Of course)/i,
15
+ /```[\s\S]+```/,
16
+ // Code blocks
17
+ /^(Yes|No),?\s+(I|you|we|this|that)/i,
18
+ // Answering patterns
19
+ /\*\*[^*]+\*\*/,
20
+ // Bold markdown (explanations)
21
+ /^\d+\.\s+\*\*/
22
+ // Numbered lists with bold
23
+ ];
24
+ const userPatterns = [
25
+ /\?$/,
26
+ // Questions
27
+ /^(create|fix|add|update|show|make|build|implement|write|delete|remove|change|modify|help|can you|please|I want|I need)/i,
28
+ /^@/
29
+ // File references
30
+ ];
31
+ for (const pattern of assistantPatterns) {
32
+ if (pattern.test(textContent)) {
33
+ return "assistant";
34
+ }
12
35
  }
13
- return "";
14
- }
15
- function getTitle(session) {
16
- const first = session.messages?.find((m) => m.role === "user");
17
- if (first) {
18
- const text = extractText(first.content);
19
- if (text) return text.slice(0, 100) + (text.length > 100 ? "..." : "");
36
+ for (const pattern of userPatterns) {
37
+ if (pattern.test(textContent)) {
38
+ return "user";
39
+ }
20
40
  }
21
- return "Untitled Session";
41
+ return textContent.length > 500 ? "assistant" : "user";
22
42
  }
23
43
  function doSyncSession(session) {
24
44
  try {
25
45
  const config = getConfig();
26
- if (!config?.apiKey || !config?.convexUrl) return;
46
+ if (!config?.apiKey || !config?.convexUrl) {
47
+ console.error("[opencode-sync] Missing config - cannot sync session");
48
+ return;
49
+ }
27
50
  const url = config.convexUrl.replace(".convex.cloud", ".convex.site");
51
+ console.log("[opencode-sync] Syncing session:", session.id);
52
+ const projectPath = session.path?.cwd || session.cwd || session.directory;
53
+ const modelId = session.modelID || session.model?.modelID || session.model;
54
+ const providerId = session.providerID || session.model?.providerID || session.provider;
55
+ const promptTokens = session.tokens?.input || session.usage?.promptTokens || 0;
56
+ const completionTokens = session.tokens?.output || session.usage?.completionTokens || 0;
57
+ const cost = session.cost || session.usage?.cost || 0;
28
58
  fetch(`${url}/sync/session`, {
29
59
  method: "POST",
30
- headers: { "Content-Type": "application/json", Authorization: `Bearer ${config.apiKey}` },
60
+ headers: {
61
+ "Content-Type": "application/json",
62
+ Authorization: `Bearer ${config.apiKey}`
63
+ },
31
64
  body: JSON.stringify({
32
65
  externalId: session.id,
33
- title: session.title || getTitle(session),
34
- projectPath: session.cwd,
35
- projectName: session.cwd?.split("/").pop(),
36
- model: session.model,
37
- provider: session.provider,
38
- promptTokens: session.usage?.promptTokens || 0,
39
- completionTokens: session.usage?.completionTokens || 0,
40
- cost: session.usage?.cost || 0
66
+ title: session.title || "Untitled Session",
67
+ projectPath,
68
+ projectName: projectPath?.split("/").pop(),
69
+ model: modelId,
70
+ provider: providerId,
71
+ promptTokens,
72
+ completionTokens,
73
+ cost
41
74
  })
42
- }).catch(() => {
43
- });
44
- } catch {
75
+ }).then((r) => r.json()).then((data) => console.log("[opencode-sync] Session sync response:", data)).catch((err) => console.error("[opencode-sync] Session sync error:", err));
76
+ } catch (err) {
77
+ console.error("[opencode-sync] doSyncSession error:", err);
45
78
  }
46
79
  }
47
- function doSyncMessage(sessionId, message) {
80
+ function doSyncMessage(sessionId, messageId, role, textContent, metadata) {
48
81
  try {
49
82
  const config = getConfig();
50
- if (!config?.apiKey || !config?.convexUrl) return;
83
+ if (!config?.apiKey || !config?.convexUrl) {
84
+ console.error("[opencode-sync] Missing config - cannot sync message");
85
+ return;
86
+ }
87
+ if (!textContent || textContent.trim().length === 0) {
88
+ console.log("[opencode-sync] Skipping empty message:", messageId);
89
+ return;
90
+ }
91
+ const finalRole = role === "unknown" || !role ? inferRole(textContent) : role;
51
92
  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
+ let durationMs;
102
+ if (metadata?.time?.completed && metadata?.time?.created) {
103
+ durationMs = metadata.time.completed - metadata.time.created;
104
+ }
52
105
  fetch(`${url}/sync/message`, {
53
106
  method: "POST",
54
- headers: { "Content-Type": "application/json", Authorization: `Bearer ${config.apiKey}` },
107
+ headers: {
108
+ "Content-Type": "application/json",
109
+ Authorization: `Bearer ${config.apiKey}`
110
+ },
55
111
  body: JSON.stringify({
56
112
  sessionExternalId: sessionId,
57
- externalId: message.id,
58
- role: message.role,
59
- textContent: extractText(message.content),
60
- model: message.model,
61
- promptTokens: message.usage?.promptTokens,
62
- completionTokens: message.usage?.completionTokens,
63
- durationMs: message.duration
113
+ externalId: messageId,
114
+ role: finalRole,
115
+ textContent,
116
+ model: metadata?.modelID,
117
+ promptTokens: metadata?.tokens?.input,
118
+ completionTokens: metadata?.tokens?.output,
119
+ durationMs
64
120
  })
65
- }).catch(() => {
66
- });
67
- } catch {
121
+ }).then((r) => r.json()).then((data) => console.log("[opencode-sync] Message sync response:", data)).catch((err) => console.error("[opencode-sync] Message sync error:", err));
122
+ } catch (err) {
123
+ console.error("[opencode-sync] doSyncMessage error:", err);
68
124
  }
69
125
  }
70
- var OpenCodeSyncPlugin = async (_ctx) => {
126
+ function trySyncMessage(messageId) {
127
+ if (syncedMessages.has(messageId)) return;
128
+ const metadata = messageMetadata.get(messageId);
129
+ const textParts = messagePartsText.get(messageId);
130
+ if (!metadata || !textParts || textParts.length === 0) return;
131
+ const textContent = textParts.join("");
132
+ if (!textContent.trim()) return;
133
+ syncedMessages.add(messageId);
134
+ doSyncMessage(metadata.sessionId, messageId, metadata.role, textContent, metadata.info);
135
+ messagePartsText.delete(messageId);
136
+ messageMetadata.delete(messageId);
137
+ }
138
+ function scheduleSyncMessage(messageId) {
139
+ const existing = syncTimeouts.get(messageId);
140
+ if (existing) clearTimeout(existing);
141
+ const timeout = setTimeout(() => {
142
+ syncTimeouts.delete(messageId);
143
+ trySyncMessage(messageId);
144
+ }, DEBOUNCE_MS);
145
+ syncTimeouts.set(messageId, timeout);
146
+ }
147
+ var OpenCodeSyncPlugin = async (input) => {
148
+ console.log("[opencode-sync] Plugin initialized for project:", input.project?.id);
71
149
  return {
72
- event: async (input) => {
150
+ event: async ({ event }) => {
73
151
  try {
74
- const { event } = input;
75
152
  const props = event.properties;
76
153
  if (event.type === "session.created" || event.type === "session.updated" || event.type === "session.idle") {
77
- const session = props;
78
- if (session?.id) {
79
- if (event.type === "session.created" && syncedSessions.has(session.id)) return;
80
- if (event.type === "session.created") syncedSessions.add(session.id);
81
- doSyncSession(session);
154
+ const sessionId = props?.id;
155
+ if (sessionId) {
156
+ if (event.type === "session.created") {
157
+ if (syncedSessions.has(sessionId)) return;
158
+ syncedSessions.add(sessionId);
159
+ }
160
+ doSyncSession(props);
161
+ }
162
+ }
163
+ if (event.type === "message.updated") {
164
+ const info = props?.info;
165
+ if (info?.id && info?.sessionID && info?.role) {
166
+ console.log("[opencode-sync] Message metadata received:", info.id, "role:", info.role);
167
+ messageMetadata.set(info.id, {
168
+ role: info.role,
169
+ sessionId: info.sessionID,
170
+ info
171
+ });
172
+ if (messagePartsText.has(info.id)) {
173
+ scheduleSyncMessage(info.id);
174
+ }
82
175
  }
83
176
  }
84
- if (event.type === "message.updated" || event.type === "message.part.updated") {
85
- const messageProps = props;
86
- const sessionId = messageProps?.sessionId;
87
- const message = messageProps?.message;
88
- if (sessionId && message?.id && !syncedMessages.has(message.id)) {
89
- if (event.type === "message.part.updated" && message.status !== "completed" && message.role !== "user") {
90
- return;
177
+ if (event.type === "message.part.updated") {
178
+ const part = props?.part;
179
+ if (part?.type === "text" && part?.messageID && part?.sessionID) {
180
+ const messageId = part.messageID;
181
+ const text = part.text || "";
182
+ console.log(
183
+ "[opencode-sync] Text part received for message:",
184
+ messageId,
185
+ "length:",
186
+ text.length
187
+ );
188
+ messagePartsText.set(messageId, [text]);
189
+ if (!messageMetadata.has(messageId)) {
190
+ messageMetadata.set(messageId, {
191
+ role: "unknown",
192
+ // Will be inferred or updated from message.updated
193
+ sessionId: part.sessionID,
194
+ info: {}
195
+ });
91
196
  }
92
- syncedMessages.add(message.id);
93
- doSyncMessage(sessionId, message);
197
+ scheduleSyncMessage(messageId);
94
198
  }
95
199
  }
96
- } catch {
200
+ } catch (err) {
201
+ console.error("[opencode-sync] Event handler error:", err);
97
202
  }
98
203
  }
99
204
  };
100
205
  };
101
206
  var index_default = OpenCodeSyncPlugin;
102
207
  export {
208
+ OpenCodeSyncPlugin,
103
209
  index_default as default
104
210
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-sync-plugin",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "description": "Sync your OpenCode sessions to the cloud",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -36,12 +36,13 @@
36
36
  },
37
37
  "license": "MIT",
38
38
  "devDependencies": {
39
+ "@opencode-ai/plugin": "^1.1.25",
39
40
  "@types/node": "^20.0.0",
40
41
  "tsup": "^8.0.0",
41
42
  "typescript": "^5.3.0"
42
43
  },
43
44
  "peerDependencies": {
44
- "@opencode-ai/plugin": ">=0.1.0"
45
+ "@opencode-ai/plugin": ">=1.0.0"
45
46
  },
46
47
  "peerDependenciesMeta": {
47
48
  "@opencode-ai/plugin": {
@@ -1,43 +0,0 @@
1
- // src/config.ts
2
- import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
3
- import { homedir } from "os";
4
- import { join } from "path";
5
- var CONFIG_DIR = join(homedir(), ".opensync");
6
- var CONFIG_FILE = join(CONFIG_DIR, "credentials.json");
7
- function getConfig() {
8
- try {
9
- if (!existsSync(CONFIG_FILE)) return null;
10
- const content = readFileSync(CONFIG_FILE, "utf8");
11
- const config = JSON.parse(content);
12
- if (!config || !config.convexUrl) return null;
13
- return config;
14
- } catch (e) {
15
- console.error("Error reading config:", e);
16
- return null;
17
- }
18
- }
19
- function setConfig(cfg) {
20
- try {
21
- if (!existsSync(CONFIG_DIR)) {
22
- mkdirSync(CONFIG_DIR, { recursive: true });
23
- }
24
- writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2), "utf8");
25
- } catch (e) {
26
- console.error("Error saving config:", e);
27
- }
28
- }
29
- function clearConfig() {
30
- try {
31
- if (existsSync(CONFIG_FILE)) {
32
- writeFileSync(CONFIG_FILE, "{}", "utf8");
33
- }
34
- } catch (e) {
35
- console.error("Error clearing config:", e);
36
- }
37
- }
38
-
39
- export {
40
- getConfig,
41
- setConfig,
42
- clearConfig
43
- };