opencode-sync-plugin 0.2.8 → 0.2.9

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
 
@@ -36,6 +36,18 @@ function clearConfig() {
36
36
  console.error("Error clearing config:", e);
37
37
  }
38
38
  }
39
+ function isLoggingEnabled() {
40
+ const config = getConfig();
41
+ return config?.logging === true;
42
+ }
43
+ function setLogging(enabled) {
44
+ const config = getConfig();
45
+ if (config) {
46
+ setConfig({ ...config, logging: enabled });
47
+ } else {
48
+ console.error("No config found. Please run 'opencode-sync login' first.");
49
+ }
50
+ }
39
51
  function getSyncedSessions() {
40
52
  try {
41
53
  if (!existsSync(SYNCED_SESSIONS_FILE)) return /* @__PURE__ */ new Set();
@@ -64,7 +76,11 @@ function addSyncedSessions(sessionIds) {
64
76
  function clearSyncedSessions() {
65
77
  try {
66
78
  if (existsSync(SYNCED_SESSIONS_FILE)) {
67
- writeFileSync(SYNCED_SESSIONS_FILE, JSON.stringify({ sessionIds: [], lastUpdated: Date.now() }), "utf8");
79
+ writeFileSync(
80
+ SYNCED_SESSIONS_FILE,
81
+ JSON.stringify({ sessionIds: [], lastUpdated: Date.now() }),
82
+ "utf8"
83
+ );
68
84
  }
69
85
  } catch (e) {
70
86
  console.error("Error clearing synced sessions:", e);
@@ -75,6 +91,8 @@ export {
75
91
  getConfig,
76
92
  setConfig,
77
93
  clearConfig,
94
+ isLoggingEnabled,
95
+ setLogging,
78
96
  getSyncedSessions,
79
97
  addSyncedSessions,
80
98
  clearSyncedSessions
package/dist/cli.js CHANGED
@@ -5,8 +5,9 @@ import {
5
5
  clearSyncedSessions,
6
6
  getConfig,
7
7
  getSyncedSessions,
8
- setConfig
9
- } from "./chunk-6K7TIEXR.js";
8
+ setConfig,
9
+ setLogging
10
+ } from "./chunk-FYT43SUA.js";
10
11
 
11
12
  // src/cli.ts
12
13
  import { readFileSync, existsSync, readdirSync } from "fs";
@@ -42,7 +43,7 @@ async function main() {
42
43
  status();
43
44
  break;
44
45
  case "config":
45
- showConfig();
46
+ handleConfig();
46
47
  break;
47
48
  case "sync":
48
49
  await sync();
@@ -63,16 +64,22 @@ async function main() {
63
64
  }
64
65
  async function login() {
65
66
  console.log("\n OpenSync Login\n");
66
- const convexUrl = await prompt("Convex URL (e.g., https://your-project.convex.cloud): ");
67
+ const convexUrl = await prompt(
68
+ "Convex URL (e.g., https://your-project.convex.cloud): "
69
+ );
67
70
  if (!convexUrl) {
68
71
  console.error("Convex URL is required");
69
72
  process.exit(1);
70
73
  }
71
74
  if (!convexUrl.includes(".convex.cloud") && !convexUrl.includes(".convex.site")) {
72
- console.error("Invalid Convex URL. Should end with .convex.cloud or .convex.site");
75
+ console.error(
76
+ "Invalid Convex URL. Should end with .convex.cloud or .convex.site"
77
+ );
73
78
  process.exit(1);
74
79
  }
75
- const apiKey = await prompt("API Key (from Settings page, starts with osk_): ");
80
+ const apiKey = await prompt(
81
+ "API Key (from Settings page, starts with osk_): "
82
+ );
76
83
  if (!apiKey) {
77
84
  console.error("API Key is required");
78
85
  process.exit(1);
@@ -102,8 +109,12 @@ async function login() {
102
109
  }' > ~/.config/opencode/opencode.json`);
103
110
  console.log("\n Then verify your setup:\n");
104
111
  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');
112
+ console.log(
113
+ " Note: If you have existing opencode.json settings, manually add"
114
+ );
115
+ console.log(
116
+ ' "plugin": ["opencode-sync-plugin"] to preserve your config.\n'
117
+ );
107
118
  } catch (e) {
108
119
  console.error("\nFailed to connect to OpenSync backend.");
109
120
  console.error("Please verify your Convex URL is correct.");
@@ -125,10 +136,18 @@ function verify() {
125
136
  } else {
126
137
  console.log(" Credentials: OK");
127
138
  console.log(" Convex URL:", config.convexUrl);
128
- console.log(" API Key:", config.apiKey.slice(0, 8) + "..." + config.apiKey.slice(-4));
139
+ console.log(
140
+ " API Key:",
141
+ config.apiKey.slice(0, 8) + "..." + config.apiKey.slice(-4)
142
+ );
129
143
  console.log();
130
144
  }
131
- const opencodeConfigPath = join(homedir(), ".config", "opencode", "opencode.json");
145
+ const opencodeConfigPath = join(
146
+ homedir(),
147
+ ".config",
148
+ "opencode",
149
+ "opencode.json"
150
+ );
132
151
  const projectConfigPath = join(process.cwd(), "opencode.json");
133
152
  let configFound = false;
134
153
  let configPath = "";
@@ -169,9 +188,13 @@ function verify() {
169
188
  console.log();
170
189
  }
171
190
  if (hasErrors) {
172
- console.log(" Setup incomplete. Fix the issues above and run verify again.\n");
191
+ console.log(
192
+ " Setup incomplete. Fix the issues above and run verify again.\n"
193
+ );
173
194
  } else {
174
- console.log(" Ready! Start OpenCode and the plugin will load automatically.\n");
195
+ console.log(
196
+ " Ready! Start OpenCode and the plugin will load automatically.\n"
197
+ );
175
198
  }
176
199
  }
177
200
  function status() {
@@ -190,9 +213,30 @@ function status() {
190
213
  }
191
214
  console.log(" Status: Configured\n");
192
215
  console.log(" Convex URL:", config.convexUrl);
193
- console.log(" API Key:", config.apiKey.slice(0, 8) + "..." + config.apiKey.slice(-4));
216
+ console.log(
217
+ " API Key:",
218
+ config.apiKey.slice(0, 8) + "..." + config.apiKey.slice(-4)
219
+ );
194
220
  console.log();
195
221
  }
222
+ function handleConfig() {
223
+ const loggingArg = args.find((a) => a.startsWith("--logging="));
224
+ if (loggingArg) {
225
+ const value = loggingArg.split("=")[1]?.toLowerCase();
226
+ if (value === "true" || value === "1" || value === "on") {
227
+ setLogging(true);
228
+ console.log("\n Logging enabled.\n");
229
+ } else if (value === "false" || value === "0" || value === "off") {
230
+ setLogging(false);
231
+ console.log("\n Logging disabled.\n");
232
+ } else {
233
+ console.log("\n Invalid value for --logging. Use true or false.\n");
234
+ console.log(" Example: opencode-sync config --logging=true\n");
235
+ }
236
+ return;
237
+ }
238
+ showConfig();
239
+ }
196
240
  function showConfig() {
197
241
  const config = getConfig();
198
242
  console.log("\n OpenSync Config\n");
@@ -202,9 +246,39 @@ function showConfig() {
202
246
  return;
203
247
  }
204
248
  console.log(" Convex URL:", config.convexUrl);
205
- console.log(" API Key:", config.apiKey ? config.apiKey.slice(0, 8) + "..." + config.apiKey.slice(-4) : "Not set");
249
+ console.log(
250
+ " API Key:",
251
+ config.apiKey ? config.apiKey.slice(0, 8) + "..." + config.apiKey.slice(-4) : "Not set"
252
+ );
253
+ console.log(" Logging:", config.logging ? "enabled" : "disabled");
206
254
  console.log();
207
255
  }
256
+ function getMessageTextContent(partBasePath, messageId) {
257
+ const messagePartPath = join(partBasePath, messageId);
258
+ if (!existsSync(messagePartPath)) {
259
+ return "";
260
+ }
261
+ try {
262
+ const partFiles = readdirSync(messagePartPath).filter(
263
+ (f) => f.endsWith(".json")
264
+ );
265
+ let textContent = "";
266
+ for (const partFile of partFiles) {
267
+ try {
268
+ const partData = JSON.parse(
269
+ readFileSync(join(messagePartPath, partFile), "utf8")
270
+ );
271
+ if (partData.type === "text" && partData.text) {
272
+ textContent += partData.text;
273
+ }
274
+ } catch {
275
+ }
276
+ }
277
+ return textContent;
278
+ } catch {
279
+ return "";
280
+ }
281
+ }
208
282
  async function sync() {
209
283
  const syncAll = args.includes("--all");
210
284
  const syncNew = args.includes("--new");
@@ -308,9 +382,16 @@ async function syncAllSessions(siteUrl, apiKey, mode) {
308
382
  console.log(`
309
383
  OpenSync: ${modeLabel} Local Sessions
310
384
  `);
311
- const opencodePath = join(homedir(), ".local", "share", "opencode", "storage");
385
+ const opencodePath = join(
386
+ homedir(),
387
+ ".local",
388
+ "share",
389
+ "opencode",
390
+ "storage"
391
+ );
312
392
  const sessionPath = join(opencodePath, "session");
313
393
  const messagePath = join(opencodePath, "message");
394
+ const partPath = join(opencodePath, "part");
314
395
  if (!existsSync(sessionPath)) {
315
396
  console.log(" No OpenCode sessions found.");
316
397
  console.log(" Expected path:", sessionPath);
@@ -322,7 +403,9 @@ async function syncAllSessions(siteUrl, apiKey, mode) {
322
403
  const projectDirs = readdirSync(sessionPath, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
323
404
  for (const projectDir of projectDirs) {
324
405
  const projectSessionPath = join(sessionPath, projectDir);
325
- const sessionFiles = readdirSync(projectSessionPath).filter((f) => f.endsWith(".json"));
406
+ const sessionFiles = readdirSync(projectSessionPath).filter(
407
+ (f) => f.endsWith(".json")
408
+ );
326
409
  for (const file of sessionFiles) {
327
410
  try {
328
411
  const content = readFileSync(join(projectSessionPath, file), "utf8");
@@ -335,7 +418,10 @@ async function syncAllSessions(siteUrl, apiKey, mode) {
335
418
  }
336
419
  }
337
420
  } catch (e) {
338
- console.log(" Error reading sessions:", e instanceof Error ? e.message : String(e));
421
+ console.log(
422
+ " Error reading sessions:",
423
+ e instanceof Error ? e.message : String(e)
424
+ );
339
425
  return;
340
426
  }
341
427
  console.log(` Found ${sessions.length} local sessions`);
@@ -372,7 +458,9 @@ async function syncAllSessions(siteUrl, apiKey, mode) {
372
458
  const newlySyncedIds = [];
373
459
  for (const session of sessionsToSync) {
374
460
  const { data } = session;
375
- process.stdout.write(` Syncing: ${data.title || data.slug || data.id}... `);
461
+ process.stdout.write(
462
+ ` Syncing: ${data.title || data.slug || data.id}... `
463
+ );
376
464
  let totalPromptTokens = 0;
377
465
  let totalCompletionTokens = 0;
378
466
  let totalCost = 0;
@@ -382,10 +470,15 @@ async function syncAllSessions(siteUrl, apiKey, mode) {
382
470
  const messages = [];
383
471
  if (existsSync(sessionMessagePath)) {
384
472
  try {
385
- const messageFiles = readdirSync(sessionMessagePath).filter((f) => f.endsWith(".json"));
473
+ const messageFiles = readdirSync(sessionMessagePath).filter(
474
+ (f) => f.endsWith(".json")
475
+ );
386
476
  for (const msgFile of messageFiles) {
387
477
  try {
388
- const msgContent = readFileSync(join(sessionMessagePath, msgFile), "utf8");
478
+ const msgContent = readFileSync(
479
+ join(sessionMessagePath, msgFile),
480
+ "utf8"
481
+ );
389
482
  const msgData = JSON.parse(msgContent);
390
483
  if (msgData.id && msgData.sessionID === data.id) {
391
484
  messages.push(msgData);
@@ -438,6 +531,7 @@ async function syncAllSessions(siteUrl, apiKey, mode) {
438
531
  let msgCount = 0;
439
532
  for (const msg of messages) {
440
533
  try {
534
+ const textContent = getMessageTextContent(partPath, msg.id);
441
535
  const msgRes = await fetch(`${siteUrl}/sync/message`, {
442
536
  method: "POST",
443
537
  headers: {
@@ -448,8 +542,7 @@ async function syncAllSessions(siteUrl, apiKey, mode) {
448
542
  sessionExternalId: data.id,
449
543
  externalId: msg.id,
450
544
  role: msg.role,
451
- textContent: "",
452
- // We don't have content in the message metadata files
545
+ textContent,
453
546
  model: msg.modelID,
454
547
  promptTokens: msg.tokens?.input,
455
548
  completionTokens: msg.tokens?.output,
@@ -493,17 +586,19 @@ function help() {
493
586
  Usage: opencode-sync <command> [options]
494
587
 
495
588
  Commands:
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
506
- help Show this help message
589
+ login Configure with Convex URL and API Key
590
+ verify Verify credentials and OpenCode config
591
+ sync Test connectivity and create a test session
592
+ sync --new Sync only sessions not in local tracking file
593
+ sync --all Sync all sessions (checks backend, skips existing)
594
+ sync --force Clear tracking and resync all sessions
595
+ logout Clear stored credentials
596
+ status Show current authentication status
597
+ config Show current configuration
598
+ config --logging=true Enable debug logging
599
+ config --logging=false Disable debug logging (default)
600
+ version Show version number
601
+ help Show this help message
507
602
 
508
603
  Setup:
509
604
  1. Go to your OpenSync dashboard Settings page
package/dist/config.d.ts CHANGED
@@ -1,12 +1,15 @@
1
1
  interface Config {
2
2
  convexUrl: string;
3
3
  apiKey: string;
4
+ logging?: boolean;
4
5
  }
5
6
  declare function getConfig(): Config | null;
6
7
  declare function setConfig(cfg: Config): void;
7
8
  declare function clearConfig(): void;
9
+ declare function isLoggingEnabled(): boolean;
10
+ declare function setLogging(enabled: boolean): void;
8
11
  declare function getSyncedSessions(): Set<string>;
9
12
  declare function addSyncedSessions(sessionIds: string[]): void;
10
13
  declare function clearSyncedSessions(): void;
11
14
 
12
- export { addSyncedSessions, clearConfig, clearSyncedSessions, getConfig, getSyncedSessions, setConfig };
15
+ export { addSyncedSessions, clearConfig, clearSyncedSessions, getConfig, getSyncedSessions, isLoggingEnabled, setConfig, setLogging };
package/dist/config.js CHANGED
@@ -4,13 +4,17 @@ import {
4
4
  clearSyncedSessions,
5
5
  getConfig,
6
6
  getSyncedSessions,
7
- setConfig
8
- } from "./chunk-6K7TIEXR.js";
7
+ isLoggingEnabled,
8
+ setConfig,
9
+ setLogging
10
+ } from "./chunk-FYT43SUA.js";
9
11
  export {
10
12
  addSyncedSessions,
11
13
  clearConfig,
12
14
  clearSyncedSessions,
13
15
  getConfig,
14
16
  getSyncedSessions,
15
- setConfig
17
+ isLoggingEnabled,
18
+ setConfig,
19
+ setLogging
16
20
  };
package/dist/index.js CHANGED
@@ -1,8 +1,14 @@
1
1
  import {
2
- getConfig
3
- } from "./chunk-6K7TIEXR.js";
2
+ getConfig,
3
+ isLoggingEnabled
4
+ } from "./chunk-FYT43SUA.js";
4
5
 
5
6
  // src/index.ts
7
+ function log(...args) {
8
+ if (isLoggingEnabled()) {
9
+ console.log(...args);
10
+ }
11
+ }
6
12
  var syncedSessions = /* @__PURE__ */ new Set();
7
13
  var syncedMessages = /* @__PURE__ */ new Set();
8
14
  var messagePartsText = /* @__PURE__ */ new Map();
@@ -48,7 +54,7 @@ function doSyncSession(session) {
48
54
  return;
49
55
  }
50
56
  const url = config.convexUrl.replace(".convex.cloud", ".convex.site");
51
- console.log("[opencode-sync] Syncing session:", session.id);
57
+ log("[opencode-sync] Syncing session:", session.id);
52
58
  const projectPath = session.path?.cwd || session.cwd || session.directory;
53
59
  const modelId = session.modelID || session.model?.modelID || session.model;
54
60
  const providerId = session.providerID || session.model?.providerID || session.provider;
@@ -72,7 +78,9 @@ function doSyncSession(session) {
72
78
  completionTokens,
73
79
  cost
74
80
  })
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));
81
+ }).then((r) => r.json()).then((data) => log("[opencode-sync] Session sync response:", data)).catch(
82
+ (err) => console.error("[opencode-sync] Session sync error:", err)
83
+ );
76
84
  } catch (err) {
77
85
  console.error("[opencode-sync] doSyncSession error:", err);
78
86
  }
@@ -85,12 +93,12 @@ function doSyncMessage(sessionId, messageId, role, textContent, metadata) {
85
93
  return;
86
94
  }
87
95
  if (!textContent || textContent.trim().length === 0) {
88
- console.log("[opencode-sync] Skipping empty message:", messageId);
96
+ log("[opencode-sync] Skipping empty message:", messageId);
89
97
  return;
90
98
  }
91
99
  const finalRole = role === "unknown" || !role ? inferRole(textContent) : role;
92
100
  const url = config.convexUrl.replace(".convex.cloud", ".convex.site");
93
- console.log(
101
+ log(
94
102
  "[opencode-sync] Syncing message:",
95
103
  messageId,
96
104
  "role:",
@@ -118,7 +126,9 @@ function doSyncMessage(sessionId, messageId, role, textContent, metadata) {
118
126
  completionTokens: metadata?.tokens?.output,
119
127
  durationMs
120
128
  })
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));
129
+ }).then((r) => r.json()).then((data) => log("[opencode-sync] Message sync response:", data)).catch(
130
+ (err) => console.error("[opencode-sync] Message sync error:", err)
131
+ );
122
132
  } catch (err) {
123
133
  console.error("[opencode-sync] doSyncMessage error:", err);
124
134
  }
@@ -131,7 +141,13 @@ function trySyncMessage(messageId) {
131
141
  const textContent = textParts.join("");
132
142
  if (!textContent.trim()) return;
133
143
  syncedMessages.add(messageId);
134
- doSyncMessage(metadata.sessionId, messageId, metadata.role, textContent, metadata.info);
144
+ doSyncMessage(
145
+ metadata.sessionId,
146
+ messageId,
147
+ metadata.role,
148
+ textContent,
149
+ metadata.info
150
+ );
135
151
  messagePartsText.delete(messageId);
136
152
  messageMetadata.delete(messageId);
137
153
  }
@@ -145,7 +161,7 @@ function scheduleSyncMessage(messageId) {
145
161
  syncTimeouts.set(messageId, timeout);
146
162
  }
147
163
  var OpenCodeSyncPlugin = async (input) => {
148
- console.log("[opencode-sync] Plugin initialized for project:", input.project?.id);
164
+ log("[opencode-sync] Plugin initialized for project:", input.project?.id);
149
165
  return {
150
166
  event: async ({ event }) => {
151
167
  try {
@@ -163,7 +179,12 @@ var OpenCodeSyncPlugin = async (input) => {
163
179
  if (event.type === "message.updated") {
164
180
  const info = props?.info;
165
181
  if (info?.id && info?.sessionID && info?.role) {
166
- console.log("[opencode-sync] Message metadata received:", info.id, "role:", info.role);
182
+ log(
183
+ "[opencode-sync] Message metadata received:",
184
+ info.id,
185
+ "role:",
186
+ info.role
187
+ );
167
188
  messageMetadata.set(info.id, {
168
189
  role: info.role,
169
190
  sessionId: info.sessionID,
@@ -179,7 +200,7 @@ var OpenCodeSyncPlugin = async (input) => {
179
200
  if (part?.type === "text" && part?.messageID && part?.sessionID) {
180
201
  const messageId = part.messageID;
181
202
  const text = part.text || "";
182
- console.log(
203
+ log(
183
204
  "[opencode-sync] Text part received for message:",
184
205
  messageId,
185
206
  "length:",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-sync-plugin",
3
- "version": "0.2.8",
3
+ "version": "0.2.9",
4
4
  "description": "Sync your OpenCode sessions to the cloud",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",