bisync-cli 0.0.3 → 0.0.4

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.
Files changed (3) hide show
  1. package/dist/bisync.js +88 -17
  2. package/package.json +1 -1
  3. package/src/bin.ts +116 -20
package/dist/bisync.js CHANGED
@@ -13,8 +13,7 @@ var CONFIG_DIR = join(homedir(), ".agent-bisync");
13
13
  var CONFIG_PATH = join(CONFIG_DIR, "config.json");
14
14
  var CLAUDE_SETTINGS_PATH = join(homedir(), ".claude", "settings.json");
15
15
  var CLAUDE_PROJECTS_DIR = join(homedir(), ".claude", "projects");
16
- var DEBUG_DIR = join(homedir(), ".bysync");
17
- var DEBUG_LOG_PATH = join(DEBUG_DIR, "debug.log");
16
+ var DEBUG_LOG_PATH = join(CONFIG_DIR, "debug.log");
18
17
  var CLIENT_ID = "bisync-cli";
19
18
  var MAX_LINES_PER_BATCH = 200;
20
19
  var sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
@@ -25,8 +24,19 @@ Commands:
25
24
  setup --site-url <url> Configure hooks and authenticate
26
25
  verify Check auth and list session ids
27
26
  hook <HOOK> Handle Claude hook input (stdin JSON)
27
+
28
+ Global options:
29
+ --verbose Enable verbose logging
28
30
  `);
29
31
  };
32
+ var LOG_LEVEL = "info";
33
+ var log = (level, message) => {
34
+ if (level < LOG_LEVEL) {
35
+ return;
36
+ }
37
+ const logMessage = `[bisync][${level}] ${message}`;
38
+ console.log(logMessage);
39
+ };
30
40
  var readConfig = async () => {
31
41
  return await Bun.file(CONFIG_PATH).json();
32
42
  };
@@ -40,16 +50,22 @@ var resolveSiteUrl = (args) => {
40
50
  const argValue = argIndex >= 0 ? args[argIndex + 1] : undefined;
41
51
  const envValue = process.env.BISYNC_SITE_URL;
42
52
  if (argValue) {
43
- return argValue;
53
+ return { siteUrl: argValue, source: "arg" };
44
54
  }
45
55
  if (envValue) {
46
- return envValue;
56
+ return { siteUrl: envValue, source: "env" };
47
57
  }
48
58
  const viteConvexUrl = process.env.VITE_CONVEX_URL;
49
59
  if (viteConvexUrl?.endsWith(".convex.cloud")) {
50
- return viteConvexUrl.replace(".convex.cloud", ".convex.site");
60
+ return {
61
+ siteUrl: viteConvexUrl.replace(".convex.cloud", ".convex.site"),
62
+ source: "vite"
63
+ };
51
64
  }
52
- return viteConvexUrl ?? null;
65
+ if (viteConvexUrl) {
66
+ return { siteUrl: viteConvexUrl, source: "vite" };
67
+ }
68
+ return { siteUrl: null, source: "missing" };
53
69
  };
54
70
  var openBrowser = async (url) => {
55
71
  if (process.platform !== "darwin") {
@@ -60,6 +76,7 @@ var openBrowser = async (url) => {
60
76
  } catch {}
61
77
  };
62
78
  var deviceAuthFlow = async (siteUrl) => {
79
+ log("debug", `device auth start siteUrl=${siteUrl}`);
63
80
  const codeResponse = await fetch(`${siteUrl}/api/auth/device/code`, {
64
81
  method: "POST",
65
82
  headers: { "Content-Type": "application/json" },
@@ -67,14 +84,17 @@ var deviceAuthFlow = async (siteUrl) => {
67
84
  });
68
85
  if (!codeResponse.ok) {
69
86
  const errorText = await codeResponse.text();
87
+ log("debug", `device code request failed status=${codeResponse.status}`);
70
88
  throw new Error(`Device code request failed: ${codeResponse.status} ${errorText}`);
71
89
  }
90
+ log("debug", `device code request ok status=${codeResponse.status}`);
72
91
  const codeData = await codeResponse.json();
73
92
  const verificationUrl = codeData.verification_uri_complete ?? codeData.verification_uri;
74
- console.log(`Authorize this device: ${verificationUrl}`);
75
- console.log(`User code: ${codeData.user_code}`);
93
+ log("info", `Authorize this device: ${verificationUrl}`);
94
+ log("info", `User code: ${codeData.user_code}`);
76
95
  await openBrowser(verificationUrl);
77
96
  const intervalMs = Math.max(1, codeData.interval ?? 5) * 1000;
97
+ log("debug", `device auth polling intervalMs=${intervalMs}`);
78
98
  let pollDelay = intervalMs;
79
99
  while (true) {
80
100
  await sleep(pollDelay);
@@ -89,6 +109,7 @@ var deviceAuthFlow = async (siteUrl) => {
89
109
  });
90
110
  const tokenData = await tokenResponse.json();
91
111
  if (tokenResponse.ok && tokenData.access_token) {
112
+ log("debug", `device auth success status=${tokenResponse.status}`);
92
113
  return tokenData.access_token;
93
114
  }
94
115
  if (tokenData.error === "authorization_pending") {
@@ -96,8 +117,10 @@ var deviceAuthFlow = async (siteUrl) => {
96
117
  }
97
118
  if (tokenData.error === "slow_down") {
98
119
  pollDelay += 1000;
120
+ log("debug", `device auth slow_down nextDelayMs=${pollDelay}`);
99
121
  continue;
100
122
  }
123
+ log("debug", `device auth failed status=${tokenResponse.status} error=${tokenData.error ?? "unknown"}`);
101
124
  throw new Error(`Device auth failed: ${tokenData.error ?? "unknown"} ${tokenData.error_description ?? ""}`.trim());
102
125
  }
103
126
  };
@@ -118,7 +141,20 @@ var ensureHookEntry = (entries, next) => {
118
141
  return entries;
119
142
  };
120
143
  var mergeSettings = async () => {
121
- const current = await Bun.file(CLAUDE_SETTINGS_PATH).json().catch(() => ({}));
144
+ log("debug", `mergeSettings path=${CLAUDE_SETTINGS_PATH}`);
145
+ const settingsFile = Bun.file(CLAUDE_SETTINGS_PATH);
146
+ const settingsExists = await settingsFile.exists();
147
+ log("debug", `mergeSettings existing=${settingsExists}`);
148
+ let current = {};
149
+ if (settingsExists) {
150
+ try {
151
+ current = await settingsFile.json();
152
+ log("debug", `mergeSettings read ok`);
153
+ } catch (error) {
154
+ log("debug", `mergeSettings read failed: ${error instanceof Error ? error.message : String(error)}`);
155
+ current = {};
156
+ }
157
+ }
122
158
  const hooks = current.hooks ?? {};
123
159
  const definitions = [
124
160
  { name: "SessionStart" },
@@ -144,6 +180,13 @@ var mergeSettings = async () => {
144
180
  await Bun.write(CLAUDE_SETTINGS_PATH, JSON.stringify(nextConfig, null, 2), {
145
181
  createPath: true
146
182
  });
183
+ log("debug", `mergeSettings wrote settings`);
184
+ try {
185
+ const info = await stat(CLAUDE_SETTINGS_PATH);
186
+ log("debug", `mergeSettings file size=${info.size} mtime=${new Date(info.mtimeMs).toISOString()}`);
187
+ } catch (error) {
188
+ log("debug", `mergeSettings stat failed: ${error instanceof Error ? error.message : String(error)}`);
189
+ }
147
190
  };
148
191
  var findSessionFile = async (sessionId) => {
149
192
  let bestPath = null;
@@ -178,7 +221,7 @@ var findSessionFile = async (sessionId) => {
178
221
  var appendDebugLog = async (message) => {
179
222
  const logFile = Bun.file(DEBUG_LOG_PATH);
180
223
  const content = await logFile.text();
181
- await logFile.write(content + message);
224
+ await Bun.write(DEBUG_LOG_PATH, content + message, { createPath: true });
182
225
  };
183
226
  var buildLogLines = (raw) => {
184
227
  const lines = raw.split(`
@@ -221,33 +264,48 @@ var uploadLogs = async (siteUrl, token, sessionId, raw) => {
221
264
  }
222
265
  };
223
266
  var runSetup = async (args) => {
267
+ log("debug", `runSetup start`);
224
268
  const force = args.includes("--force");
269
+ log("debug", `runSetup force=${force}`);
225
270
  const configExists = await Bun.file(CONFIG_PATH).exists();
271
+ log("debug", `config path=${CONFIG_PATH} exists=${configExists}`);
226
272
  let existingConfig = null;
227
273
  if (configExists) {
228
274
  try {
229
275
  existingConfig = await readConfig();
276
+ log("debug", `config loaded siteUrl=${existingConfig.siteUrl} token=${existingConfig.token ? "present" : "missing"}`);
230
277
  } catch {
278
+ log("debug", `config load failed; continuing without existing config`);
231
279
  existingConfig = null;
232
280
  }
233
281
  }
234
- const siteUrl = resolveSiteUrl(args) ?? existingConfig?.siteUrl ?? null;
282
+ const resolution = resolveSiteUrl(args);
283
+ log("debug", `siteUrl resolved=${resolution.siteUrl ?? "null"} source=${resolution.source}`);
284
+ const siteUrl = resolution.siteUrl ?? existingConfig?.siteUrl ?? null;
285
+ if (!resolution.siteUrl && existingConfig?.siteUrl) {
286
+ log("debug", `siteUrl fallback to existing config ${existingConfig.siteUrl}`);
287
+ }
235
288
  if (!siteUrl) {
289
+ log("debug", `runSetup aborted: missing siteUrl`);
236
290
  throw new Error("Missing site URL. Provide --site-url or BISYNC_SITE_URL.");
237
291
  }
238
292
  if (configExists && existingConfig?.token && !force) {
293
+ log("debug", `using existing token; updating Claude settings only`);
239
294
  await mergeSettings();
240
- console.log(`Updated Claude settings using existing credentials at ${CONFIG_PATH}`);
295
+ log("info", `Updated Claude settings using existing credentials at ${CONFIG_PATH}`);
241
296
  return;
242
297
  }
243
298
  const token = await deviceAuthFlow(siteUrl);
244
299
  await writeConfig({ siteUrl, token, clientId: CLIENT_ID });
300
+ log("debug", `config written to ${CONFIG_PATH}`);
245
301
  await mergeSettings();
246
- console.log(`Configured Claude settings and saved credentials to ${CONFIG_PATH}`);
302
+ log("info", `Configured Claude settings and saved credentials to ${CONFIG_PATH}`);
247
303
  };
248
304
  var runVerify = async () => {
305
+ log("debug", `runVerify start config=${CONFIG_PATH}`);
249
306
  const config = await readConfig();
250
307
  const siteUrl = process.env.BISYNC_SITE_URL ?? config.siteUrl;
308
+ log("debug", `runVerify siteUrl=${siteUrl}`);
251
309
  const response = await fetch(`${siteUrl}/api/list-sessions`, {
252
310
  method: "GET",
253
311
  headers: {
@@ -256,18 +314,20 @@ var runVerify = async () => {
256
314
  });
257
315
  if (!response.ok) {
258
316
  const errorText = await response.text();
317
+ log("debug", `runVerify failed status=${response.status}`);
259
318
  throw new Error(`Verify failed: ${response.status} ${errorText}`);
260
319
  }
261
320
  const data = await response.json();
262
321
  if (data.sessions.length === 0) {
263
- console.log("No sessions found.");
322
+ log("info", "No sessions found.");
264
323
  return;
265
324
  }
266
325
  for (const session of data.sessions) {
267
- console.log(session.externalId);
326
+ log("info", session.externalId);
268
327
  }
269
328
  };
270
329
  var runHook = async (hookName) => {
330
+ log("debug", `runHook start hook=${hookName}`);
271
331
  const stdinRaw = await new Promise((resolve2, reject) => {
272
332
  let data = "";
273
333
  process.stdin.setEncoding("utf8");
@@ -294,6 +354,7 @@ var runHook = async (hookName) => {
294
354
  const config = await readConfig();
295
355
  const siteUrl = process.env.BISYNC_SITE_URL ?? config.siteUrl;
296
356
  const token = config.token;
357
+ log("debug", `runHook siteUrl=${siteUrl}`);
297
358
  let sessionFile = null;
298
359
  if (typeof stdinPayload.transcript_path === "string") {
299
360
  const resolvedPath = resolve(stdinPayload.transcript_path);
@@ -305,19 +366,24 @@ var runHook = async (hookName) => {
305
366
  sessionFile = await findSessionFile(sessionId);
306
367
  }
307
368
  if (!sessionFile) {
369
+ log("debug", `runHook session file missing sessionId=${sessionId}`);
308
370
  await appendDebugLog(`[${new Date().toISOString()}] hook=${hookName} error=session-file-not-found session_id=${sessionId}
309
371
  `);
310
372
  return;
311
373
  }
374
+ log("debug", `runHook sessionFile=${sessionFile}`);
312
375
  const raw = await Bun.file(sessionFile).text();
313
376
  if (!raw.trim()) {
377
+ log("debug", `runHook session file empty sessionId=${sessionId}`);
314
378
  await appendDebugLog(`[${new Date().toISOString()}] hook=${hookName} error=empty-log session_id=${sessionId}
315
379
  `);
316
380
  return;
317
381
  }
318
382
  try {
319
383
  await uploadLogs(siteUrl, token, sessionId, raw);
384
+ log("debug", `runHook upload ok sessionId=${sessionId}`);
320
385
  } catch (error) {
386
+ log("debug", `runHook upload failed sessionId=${sessionId} message=${error instanceof Error ? error.message : String(error)}`);
321
387
  await appendDebugLog(`[${new Date().toISOString()}] hook=${hookName} error=upload-failed session_id=${sessionId} message=${error instanceof Error ? error.message : String(error)}
322
388
  `);
323
389
  }
@@ -327,7 +393,12 @@ var main = async () => {
327
393
  console.log(`\u25B6\uFE0E ${version} (Bun ${Bun.version})`);
328
394
  process.exit(0);
329
395
  }
330
- const [command, ...args] = process.argv.slice(2);
396
+ if (process.argv.includes("--verbose")) {
397
+ LOG_LEVEL = "debug";
398
+ }
399
+ const rawArgs = process.argv.slice(2);
400
+ const [command, ...args] = rawArgs;
401
+ log("debug", `argv=${JSON.stringify(rawArgs)}`);
331
402
  if (!command) {
332
403
  usage();
333
404
  process.exit(1);
@@ -348,7 +419,7 @@ var main = async () => {
348
419
  usage();
349
420
  process.exit(1);
350
421
  } catch (error) {
351
- console.error(error instanceof Error ? error.message : error);
422
+ log("error", error instanceof Error ? error.message : String(error));
352
423
  process.exit(1);
353
424
  }
354
425
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bisync-cli",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "bin": {
5
5
  "bisync": "dist/bisync.js"
6
6
  },
package/src/bin.ts CHANGED
@@ -13,8 +13,7 @@ const CONFIG_DIR = join(homedir(), ".agent-bisync");
13
13
  const CONFIG_PATH = join(CONFIG_DIR, "config.json");
14
14
  const CLAUDE_SETTINGS_PATH = join(homedir(), ".claude", "settings.json");
15
15
  const CLAUDE_PROJECTS_DIR = join(homedir(), ".claude", "projects");
16
- const DEBUG_DIR = join(homedir(), ".bysync");
17
- const DEBUG_LOG_PATH = join(DEBUG_DIR, "debug.log");
16
+ const DEBUG_LOG_PATH = join(CONFIG_DIR, "debug.log");
18
17
  const CLIENT_ID = "bisync-cli";
19
18
  const MAX_LINES_PER_BATCH = 200;
20
19
 
@@ -24,6 +23,11 @@ type Config = {
24
23
  clientId: string;
25
24
  };
26
25
 
26
+ type SiteUrlResolution = {
27
+ siteUrl: string | null;
28
+ source: "arg" | "env" | "vite" | "missing";
29
+ };
30
+
27
31
  const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
28
32
 
29
33
  const usage = () => {
@@ -33,9 +37,21 @@ Commands:
33
37
  setup --site-url <url> Configure hooks and authenticate
34
38
  verify Check auth and list session ids
35
39
  hook <HOOK> Handle Claude hook input (stdin JSON)
40
+
41
+ Global options:
42
+ --verbose Enable verbose logging
36
43
  `);
37
44
  };
38
45
 
46
+ let LOG_LEVEL = "info";
47
+ const log = (level: "debug" | "info" | "warn" | "error", message: string) => {
48
+ if (level < LOG_LEVEL) {
49
+ return;
50
+ }
51
+ const logMessage = `[bisync][${level}] ${message}`;
52
+ console.log(logMessage);
53
+ };
54
+
39
55
  const readConfig = async (): Promise<Config> => {
40
56
  return await Bun.file(CONFIG_PATH).json();
41
57
  };
@@ -46,21 +62,27 @@ const writeConfig = async (config: Config) => {
46
62
  });
47
63
  };
48
64
 
49
- const resolveSiteUrl = (args: string[]) => {
65
+ const resolveSiteUrl = (args: string[]): SiteUrlResolution => {
50
66
  const argIndex = args.findIndex((value) => value === "--site-url" || value === "--siteUrl");
51
67
  const argValue = argIndex >= 0 ? args[argIndex + 1] : undefined;
52
68
  const envValue = process.env.BISYNC_SITE_URL;
53
69
  if (argValue) {
54
- return argValue;
70
+ return { siteUrl: argValue, source: "arg" };
55
71
  }
56
72
  if (envValue) {
57
- return envValue;
73
+ return { siteUrl: envValue, source: "env" };
58
74
  }
59
75
  const viteConvexUrl = process.env.VITE_CONVEX_URL;
60
76
  if (viteConvexUrl?.endsWith(".convex.cloud")) {
61
- return viteConvexUrl.replace(".convex.cloud", ".convex.site");
77
+ return {
78
+ siteUrl: viteConvexUrl.replace(".convex.cloud", ".convex.site"),
79
+ source: "vite",
80
+ };
81
+ }
82
+ if (viteConvexUrl) {
83
+ return { siteUrl: viteConvexUrl, source: "vite" };
62
84
  }
63
- return viteConvexUrl ?? null;
85
+ return { siteUrl: null, source: "missing" };
64
86
  };
65
87
 
66
88
  const openBrowser = async (url: string) => {
@@ -75,6 +97,7 @@ const openBrowser = async (url: string) => {
75
97
  };
76
98
 
77
99
  const deviceAuthFlow = async (siteUrl: string) => {
100
+ log("debug", `device auth start siteUrl=${siteUrl}`);
78
101
  const codeResponse = await fetch(`${siteUrl}/api/auth/device/code`, {
79
102
  method: "POST",
80
103
  headers: { "Content-Type": "application/json" },
@@ -83,9 +106,12 @@ const deviceAuthFlow = async (siteUrl: string) => {
83
106
 
84
107
  if (!codeResponse.ok) {
85
108
  const errorText = await codeResponse.text();
109
+ log("debug", `device code request failed status=${codeResponse.status}`);
86
110
  throw new Error(`Device code request failed: ${codeResponse.status} ${errorText}`);
87
111
  }
88
112
 
113
+ log("debug", `device code request ok status=${codeResponse.status}`);
114
+
89
115
  const codeData: {
90
116
  device_code: string;
91
117
  user_code: string;
@@ -95,11 +121,12 @@ const deviceAuthFlow = async (siteUrl: string) => {
95
121
  } = await codeResponse.json();
96
122
 
97
123
  const verificationUrl = codeData.verification_uri_complete ?? codeData.verification_uri;
98
- console.log(`Authorize this device: ${verificationUrl}`);
99
- console.log(`User code: ${codeData.user_code}`);
124
+ log("info", `Authorize this device: ${verificationUrl}`);
125
+ log("info", `User code: ${codeData.user_code}`);
100
126
  await openBrowser(verificationUrl);
101
127
 
102
128
  const intervalMs = Math.max(1, codeData.interval ?? 5) * 1000;
129
+ log("debug", `device auth polling intervalMs=${intervalMs}`);
103
130
  let pollDelay = intervalMs;
104
131
 
105
132
  while (true) {
@@ -121,6 +148,7 @@ const deviceAuthFlow = async (siteUrl: string) => {
121
148
  } = await tokenResponse.json();
122
149
 
123
150
  if (tokenResponse.ok && tokenData.access_token) {
151
+ log("debug", `device auth success status=${tokenResponse.status}`);
124
152
  return tokenData.access_token;
125
153
  }
126
154
 
@@ -129,9 +157,14 @@ const deviceAuthFlow = async (siteUrl: string) => {
129
157
  }
130
158
  if (tokenData.error === "slow_down") {
131
159
  pollDelay += 1000;
160
+ log("debug", `device auth slow_down nextDelayMs=${pollDelay}`);
132
161
  continue;
133
162
  }
134
163
 
164
+ log(
165
+ "debug",
166
+ `device auth failed status=${tokenResponse.status} error=${tokenData.error ?? "unknown"}`,
167
+ );
135
168
  throw new Error(
136
169
  `Device auth failed: ${tokenData.error ?? "unknown"} ${
137
170
  tokenData.error_description ?? ""
@@ -172,9 +205,23 @@ const ensureHookEntry = (entries: ClaudeHookEntry[], next: ClaudeHookEntry) => {
172
205
  };
173
206
 
174
207
  const mergeSettings = async () => {
175
- const current: ClaudeSettings = await Bun.file(CLAUDE_SETTINGS_PATH)
176
- .json()
177
- .catch(() => ({}));
208
+ log("debug", `mergeSettings path=${CLAUDE_SETTINGS_PATH}`);
209
+ const settingsFile = Bun.file(CLAUDE_SETTINGS_PATH);
210
+ const settingsExists = await settingsFile.exists();
211
+ log("debug", `mergeSettings existing=${settingsExists}`);
212
+ let current: ClaudeSettings = {};
213
+ if (settingsExists) {
214
+ try {
215
+ current = await settingsFile.json();
216
+ log("debug", `mergeSettings read ok`);
217
+ } catch (error) {
218
+ log(
219
+ "debug",
220
+ `mergeSettings read failed: ${error instanceof Error ? error.message : String(error)}`,
221
+ );
222
+ current = {};
223
+ }
224
+ }
178
225
 
179
226
  const hooks = current.hooks ?? {};
180
227
  const definitions: Array<{ name: string; matcher?: string }> = [
@@ -203,6 +250,19 @@ const mergeSettings = async () => {
203
250
  await Bun.write(CLAUDE_SETTINGS_PATH, JSON.stringify(nextConfig, null, 2), {
204
251
  createPath: true,
205
252
  });
253
+ log("debug", `mergeSettings wrote settings`);
254
+ try {
255
+ const info = await stat(CLAUDE_SETTINGS_PATH);
256
+ log(
257
+ "debug",
258
+ `mergeSettings file size=${info.size} mtime=${new Date(info.mtimeMs).toISOString()}`,
259
+ );
260
+ } catch (error) {
261
+ log(
262
+ "debug",
263
+ `mergeSettings stat failed: ${error instanceof Error ? error.message : String(error)}`,
264
+ );
265
+ }
206
266
  };
207
267
 
208
268
  const findSessionFile = async (sessionId: string) => {
@@ -242,7 +302,7 @@ const findSessionFile = async (sessionId: string) => {
242
302
  const appendDebugLog = async (message: string) => {
243
303
  const logFile = Bun.file(DEBUG_LOG_PATH);
244
304
  const content = await logFile.text();
245
- await logFile.write(content + message);
305
+ await Bun.write(DEBUG_LOG_PATH, content + message, { createPath: true });
246
306
  };
247
307
 
248
308
  const buildLogLines = (raw: string) => {
@@ -290,37 +350,55 @@ const uploadLogs = async (siteUrl: string, token: string, sessionId: string, raw
290
350
  };
291
351
 
292
352
  const runSetup = async (args: string[]) => {
353
+ log("debug", `runSetup start`);
293
354
  const force = args.includes("--force");
355
+ log("debug", `runSetup force=${force}`);
294
356
  const configExists = await Bun.file(CONFIG_PATH).exists();
357
+ log("debug", `config path=${CONFIG_PATH} exists=${configExists}`);
295
358
  let existingConfig: Config | null = null;
296
359
  if (configExists) {
297
360
  try {
298
361
  existingConfig = await readConfig();
362
+ log(
363
+ "debug",
364
+ `config loaded siteUrl=${existingConfig.siteUrl} token=${existingConfig.token ? "present" : "missing"}`,
365
+ );
299
366
  } catch {
367
+ log("debug", `config load failed; continuing without existing config`);
300
368
  existingConfig = null;
301
369
  }
302
370
  }
303
371
 
304
- const siteUrl = resolveSiteUrl(args) ?? existingConfig?.siteUrl ?? null;
372
+ const resolution = resolveSiteUrl(args);
373
+ log("debug", `siteUrl resolved=${resolution.siteUrl ?? "null"} source=${resolution.source}`);
374
+ const siteUrl = resolution.siteUrl ?? existingConfig?.siteUrl ?? null;
375
+ if (!resolution.siteUrl && existingConfig?.siteUrl) {
376
+ log("debug", `siteUrl fallback to existing config ${existingConfig.siteUrl}`);
377
+ }
305
378
  if (!siteUrl) {
379
+ log("debug", `runSetup aborted: missing siteUrl`);
306
380
  throw new Error("Missing site URL. Provide --site-url or BISYNC_SITE_URL.");
307
381
  }
308
382
 
309
383
  if (configExists && existingConfig?.token && !force) {
384
+ log("debug", `using existing token; updating Claude settings only`);
310
385
  await mergeSettings();
311
- console.log(`Updated Claude settings using existing credentials at ${CONFIG_PATH}`);
386
+ log("info", `Updated Claude settings using existing credentials at ${CONFIG_PATH}`);
312
387
  return;
313
388
  }
314
389
 
315
390
  const token = await deviceAuthFlow(siteUrl);
316
391
  await writeConfig({ siteUrl, token, clientId: CLIENT_ID });
392
+ log("debug", `config written to ${CONFIG_PATH}`);
317
393
  await mergeSettings();
318
- console.log(`Configured Claude settings and saved credentials to ${CONFIG_PATH}`);
394
+ log("info", `Configured Claude settings and saved credentials to ${CONFIG_PATH}`);
319
395
  };
320
396
 
321
397
  const runVerify = async () => {
398
+ log("debug", `runVerify start config=${CONFIG_PATH}`);
322
399
  const config = await readConfig();
323
400
  const siteUrl = process.env.BISYNC_SITE_URL ?? config.siteUrl;
401
+ log("debug", `runVerify siteUrl=${siteUrl}`);
324
402
  const response = await fetch(`${siteUrl}/api/list-sessions`, {
325
403
  method: "GET",
326
404
  headers: {
@@ -330,6 +408,7 @@ const runVerify = async () => {
330
408
 
331
409
  if (!response.ok) {
332
410
  const errorText = await response.text();
411
+ log("debug", `runVerify failed status=${response.status}`);
333
412
  throw new Error(`Verify failed: ${response.status} ${errorText}`);
334
413
  }
335
414
 
@@ -338,16 +417,17 @@ const runVerify = async () => {
338
417
  };
339
418
 
340
419
  if (data.sessions.length === 0) {
341
- console.log("No sessions found.");
420
+ log("info", "No sessions found.");
342
421
  return;
343
422
  }
344
423
 
345
424
  for (const session of data.sessions) {
346
- console.log(session.externalId);
425
+ log("info", session.externalId);
347
426
  }
348
427
  };
349
428
 
350
429
  const runHook = async (hookName: string) => {
430
+ log("debug", `runHook start hook=${hookName}`);
351
431
  const stdinRaw = await new Promise<string>((resolve, reject) => {
352
432
  let data = "";
353
433
  process.stdin.setEncoding("utf8");
@@ -380,6 +460,7 @@ const runHook = async (hookName: string) => {
380
460
  const config = await readConfig();
381
461
  const siteUrl = process.env.BISYNC_SITE_URL ?? config.siteUrl;
382
462
  const token = config.token;
463
+ log("debug", `runHook siteUrl=${siteUrl}`);
383
464
 
384
465
  let sessionFile: string | null = null;
385
466
  if (typeof stdinPayload.transcript_path === "string") {
@@ -392,14 +473,18 @@ const runHook = async (hookName: string) => {
392
473
  sessionFile = await findSessionFile(sessionId);
393
474
  }
394
475
  if (!sessionFile) {
476
+ log("debug", `runHook session file missing sessionId=${sessionId}`);
395
477
  await appendDebugLog(
396
478
  `[${new Date().toISOString()}] hook=${hookName} error=session-file-not-found session_id=${sessionId}\n`,
397
479
  );
398
480
  return;
399
481
  }
400
482
 
483
+ log("debug", `runHook sessionFile=${sessionFile}`);
484
+
401
485
  const raw = await Bun.file(sessionFile).text();
402
486
  if (!raw.trim()) {
487
+ log("debug", `runHook session file empty sessionId=${sessionId}`);
403
488
  await appendDebugLog(
404
489
  `[${new Date().toISOString()}] hook=${hookName} error=empty-log session_id=${sessionId}\n`,
405
490
  );
@@ -408,7 +493,12 @@ const runHook = async (hookName: string) => {
408
493
 
409
494
  try {
410
495
  await uploadLogs(siteUrl, token, sessionId, raw);
496
+ log("debug", `runHook upload ok sessionId=${sessionId}`);
411
497
  } catch (error) {
498
+ log(
499
+ "debug",
500
+ `runHook upload failed sessionId=${sessionId} message=${error instanceof Error ? error.message : String(error)}`,
501
+ );
412
502
  await appendDebugLog(
413
503
  `[${new Date().toISOString()}] hook=${hookName} error=upload-failed session_id=${sessionId} message=${error instanceof Error ? error.message : String(error)}\n`,
414
504
  );
@@ -421,7 +511,13 @@ const main = async () => {
421
511
  process.exit(0);
422
512
  }
423
513
 
424
- const [command, ...args] = process.argv.slice(2);
514
+ if (process.argv.includes("--verbose")) {
515
+ LOG_LEVEL = "debug";
516
+ }
517
+
518
+ const rawArgs = process.argv.slice(2);
519
+ const [command, ...args] = rawArgs;
520
+ log("debug", `argv=${JSON.stringify(rawArgs)}`);
425
521
  if (!command) {
426
522
  usage();
427
523
  process.exit(1);
@@ -444,7 +540,7 @@ const main = async () => {
444
540
  usage();
445
541
  process.exit(1);
446
542
  } catch (error) {
447
- console.error(error instanceof Error ? error.message : error);
543
+ log("error", error instanceof Error ? error.message : String(error));
448
544
  process.exit(1);
449
545
  }
450
546
  };