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 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(SYNCED_SESSIONS_FILE, JSON.stringify({ sessionIds: [], lastUpdated: Date.now() }), "utf8");
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-6K7TIEXR.js";
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
- showConfig();
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("Convex URL (e.g., https://your-project.convex.cloud): ");
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("Invalid Convex URL. Should end with .convex.cloud or .convex.site");
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("API Key (from Settings page, starts with osk_): ");
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(" Note: If you have existing opencode.json settings, manually add");
106
- console.log(' "plugin": ["opencode-sync-plugin"] to preserve your config.\n');
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(" API Key:", config.apiKey.slice(0, 8) + "..." + config.apiKey.slice(-4));
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(homedir(), ".config", "opencode", "opencode.json");
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(" Setup incomplete. Fix the issues above and run verify again.\n");
190
+ console.log(
191
+ " Setup incomplete. Fix the issues above and run verify again.\n"
192
+ );
173
193
  } else {
174
- console.log(" Ready! Start OpenCode and the plugin will load automatically.\n");
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(" API Key:", config.apiKey.slice(0, 8) + "..." + config.apiKey.slice(-4));
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(" API Key:", config.apiKey ? config.apiKey.slice(0, 8) + "..." + config.apiKey.slice(-4) : "Not set");
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(homedir(), ".local", "share", "opencode", "storage");
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((f) => f.endsWith(".json"));
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(" Error reading sessions:", e instanceof Error ? e.message : String(e));
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(` Syncing: ${data.title || data.slug || data.id}... `);
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((f) => f.endsWith(".json"));
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(join(sessionMessagePath, msgFile), "utf8");
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 Show this help message
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
@@ -5,7 +5,7 @@ import {
5
5
  getConfig,
6
6
  getSyncedSessions,
7
7
  setConfig
8
- } from "./chunk-6K7TIEXR.js";
8
+ } from "./chunk-J64QRI6W.js";
9
9
  export {
10
10
  addSyncedSessions,
11
11
  clearConfig,
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getConfig
3
- } from "./chunk-6K7TIEXR.js";
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
- }).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);
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
- }).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);
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(metadata.sessionId, messageId, metadata.role, textContent, metadata.info);
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 (input) => {
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 (err) {
201
- console.error("[opencode-sync] Event handler error:", err);
179
+ } catch {
202
180
  }
203
181
  }
204
182
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-sync-plugin",
3
- "version": "0.2.8",
3
+ "version": "0.3.0",
4
4
  "description": "Sync your OpenCode sessions to the cloud",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",