bisync-cli 0.0.10 → 0.0.12
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/dist/bisync.js +517 -247
- package/package.json +2 -2
package/dist/bisync.js
CHANGED
|
@@ -2,207 +2,52 @@
|
|
|
2
2
|
// @bun
|
|
3
3
|
|
|
4
4
|
// src/bin.ts
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { homedir } from "os";
|
|
9
|
-
import { join, relative, resolve, sep } from "path";
|
|
5
|
+
import { appendFileSync, mkdirSync } from "fs";
|
|
6
|
+
import { homedir as homedir3 } from "os";
|
|
7
|
+
import { join as join3, resolve as resolve2 } from "path";
|
|
10
8
|
import { createInterface } from "readline";
|
|
11
9
|
import { setTimeout } from "timers/promises";
|
|
12
10
|
import { parseArgs as parseArgsUtil } from "util";
|
|
13
11
|
// package.json
|
|
14
|
-
var version = "0.0.
|
|
15
|
-
|
|
16
|
-
// src/bin.ts
|
|
17
|
-
var CONFIG_DIR = join(homedir(), ".agent-bisync");
|
|
18
|
-
var CONFIG_PATH = join(CONFIG_DIR, "config.json");
|
|
19
|
-
var CLAUDE_SETTINGS_PATH = join(homedir(), ".claude", "settings.json");
|
|
20
|
-
var CLAUDE_PROJECTS_DIR = join(homedir(), ".claude", "projects");
|
|
21
|
-
var DEBUG_LOG_PATH = join(CONFIG_DIR, "debug.log");
|
|
22
|
-
var CLIENT_ID = "bisync-cli";
|
|
23
|
-
var MAX_LINES_PER_BATCH = 200;
|
|
24
|
-
var parseArgs = () => {
|
|
25
|
-
const parsed = parseArgsUtil({
|
|
26
|
-
args: process.argv.slice(2),
|
|
27
|
-
options: {
|
|
28
|
-
verbose: { type: "boolean" },
|
|
29
|
-
version: { type: "boolean" },
|
|
30
|
-
help: { type: "boolean" },
|
|
31
|
-
force: { type: "boolean" },
|
|
32
|
-
"site-url": { type: "string" },
|
|
33
|
-
siteUrl: { type: "string" }
|
|
34
|
-
},
|
|
35
|
-
allowPositionals: true
|
|
36
|
-
});
|
|
37
|
-
const values = parsed.values;
|
|
38
|
-
const positionals = parsed.positionals;
|
|
39
|
-
return { values, positionals };
|
|
40
|
-
};
|
|
41
|
-
var printUsage = () => {
|
|
42
|
-
console.log(`bisync <command>
|
|
43
|
-
|
|
44
|
-
Commands:
|
|
45
|
-
auth login --site-url <url> Authenticate via device flow
|
|
46
|
-
auth logout Sign out of the CLI
|
|
47
|
-
session list List session ids
|
|
48
|
-
setup --site-url <url> Configure hooks and authenticate
|
|
49
|
-
hook <HOOK> Handle Claude hook input (stdin JSON)
|
|
12
|
+
var version = "0.0.11";
|
|
50
13
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
14
|
+
// src/claude.ts
|
|
15
|
+
import { readdir, stat } from "fs/promises";
|
|
16
|
+
import { homedir } from "os";
|
|
17
|
+
import { join, relative, sep } from "path";
|
|
54
18
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
var getDebugLogStream = () => {
|
|
61
|
-
if (debugLogStream)
|
|
62
|
-
return debugLogStream;
|
|
63
|
-
try {
|
|
64
|
-
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
65
|
-
debugLogStream = createWriteStream(DEBUG_LOG_PATH, { flags: "a" });
|
|
66
|
-
debugLogStream.on("error", () => {
|
|
67
|
-
debugLogStream = null;
|
|
68
|
-
});
|
|
69
|
-
return debugLogStream;
|
|
70
|
-
} catch {
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
var log = (level, message, fields) => {
|
|
75
|
-
let logMessage = `[${level}] ${message}`;
|
|
76
|
-
if (fields) {
|
|
77
|
-
for (const [key, value] of Object.entries(fields)) {
|
|
78
|
-
logMessage += ` ${key}=${value}`;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
getDebugLogStream()?.write(`[${new Date().toISOString()}]${logMessage}
|
|
82
|
-
`);
|
|
83
|
-
if (level < LOG_LEVEL) {
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
console.log(`[bisync]${logMessage}`);
|
|
87
|
-
};
|
|
88
|
-
var readConfig = async () => {
|
|
89
|
-
return await Bun.file(CONFIG_PATH).json();
|
|
90
|
-
};
|
|
91
|
-
var writeConfig = async (config) => {
|
|
92
|
-
await Bun.write(CONFIG_PATH, JSON.stringify(config, null, 2), {
|
|
93
|
-
createPath: true
|
|
94
|
-
});
|
|
95
|
-
};
|
|
96
|
-
var readConfigIfExists = async () => {
|
|
97
|
-
const configFile = Bun.file(CONFIG_PATH);
|
|
98
|
-
if (!await configFile.exists()) {
|
|
99
|
-
return null;
|
|
100
|
-
}
|
|
101
|
-
try {
|
|
102
|
-
return await configFile.json();
|
|
103
|
-
} catch {
|
|
104
|
-
return null;
|
|
105
|
-
}
|
|
106
|
-
};
|
|
107
|
-
var promptYesNo = async (question, defaultValue) => {
|
|
108
|
-
if (!process.stdin.isTTY) {
|
|
109
|
-
log("warn", `${question} Using default ${defaultValue ? "yes" : "no"} (stdin not interactive).`);
|
|
110
|
-
return defaultValue;
|
|
111
|
-
}
|
|
112
|
-
const suffix = defaultValue ? " [Y/n] " : " [y/N] ";
|
|
113
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
114
|
-
const answer = await new Promise((resolve2) => rl.question(`${question}${suffix}`, resolve2));
|
|
115
|
-
rl.close();
|
|
116
|
-
const normalized = answer.trim().toLowerCase();
|
|
117
|
-
if (!normalized) {
|
|
118
|
-
return defaultValue;
|
|
119
|
-
}
|
|
120
|
-
return normalized === "y" || normalized === "yes";
|
|
121
|
-
};
|
|
122
|
-
var resolveSiteUrl = (args) => {
|
|
123
|
-
const argValue = args["site-url"] ?? args.siteUrl;
|
|
124
|
-
const envValue = process.env.BISYNC_SITE_URL;
|
|
125
|
-
if (argValue) {
|
|
126
|
-
return { siteUrl: argValue, source: "arg" };
|
|
127
|
-
}
|
|
128
|
-
if (envValue) {
|
|
129
|
-
return { siteUrl: envValue, source: "env" };
|
|
130
|
-
}
|
|
131
|
-
return { siteUrl: null, source: "missing" };
|
|
132
|
-
};
|
|
133
|
-
var openBrowser = async (url) => {
|
|
134
|
-
if (process.platform !== "darwin") {
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
try {
|
|
138
|
-
await Bun.$`open ${url}`;
|
|
139
|
-
} catch {}
|
|
140
|
-
};
|
|
141
|
-
var buildVerificationUrl = (data) => {
|
|
142
|
-
if (data.verification_uri_complete) {
|
|
143
|
-
return data.verification_uri_complete;
|
|
144
|
-
}
|
|
145
|
-
try {
|
|
146
|
-
const url = new URL(data.verification_uri);
|
|
147
|
-
url.searchParams.set("user_code", data.user_code);
|
|
148
|
-
return url.toString();
|
|
149
|
-
} catch {
|
|
150
|
-
const separator = data.verification_uri.includes("?") ? "&" : "?";
|
|
151
|
-
return `${data.verification_uri}${separator}user_code=${encodeURIComponent(data.user_code)}`;
|
|
152
|
-
}
|
|
153
|
-
};
|
|
154
|
-
var deviceAuthFlow = async (siteUrl) => {
|
|
155
|
-
log("debug", `device auth start`, { siteUrl });
|
|
156
|
-
const codeResponse = await fetch(`${siteUrl}/api/auth/device/code`, {
|
|
157
|
-
method: "POST",
|
|
158
|
-
headers: { "Content-Type": "application/json" },
|
|
159
|
-
body: JSON.stringify({ client_id: CLIENT_ID })
|
|
160
|
-
});
|
|
161
|
-
if (!codeResponse.ok) {
|
|
162
|
-
const errorText = await codeResponse.text();
|
|
163
|
-
log("debug", `device code request failed`, { status: codeResponse.status });
|
|
164
|
-
throw new Error(`Device code request failed: ${codeResponse.status} ${errorText}`);
|
|
19
|
+
// src/shared.ts
|
|
20
|
+
import { createHash } from "crypto";
|
|
21
|
+
var buildStateFilesFromCollection = async (files, kind, siteUrl, token, helpers) => {
|
|
22
|
+
if (files.length === 0) {
|
|
23
|
+
return [];
|
|
165
24
|
}
|
|
166
|
-
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
log("debug", `device auth polling`, { intervalMs });
|
|
174
|
-
let pollDelay = intervalMs;
|
|
175
|
-
while (true) {
|
|
176
|
-
await setTimeout(pollDelay);
|
|
177
|
-
const tokenResponse = await fetch(`${siteUrl}/api/auth/device/token`, {
|
|
178
|
-
method: "POST",
|
|
179
|
-
headers: { "Content-Type": "application/json" },
|
|
180
|
-
body: JSON.stringify({
|
|
181
|
-
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
182
|
-
device_code: codeData.device_code,
|
|
183
|
-
client_id: CLIENT_ID
|
|
184
|
-
})
|
|
185
|
-
});
|
|
186
|
-
const tokenData = await tokenResponse.json();
|
|
187
|
-
if (tokenResponse.ok && tokenData.access_token) {
|
|
188
|
-
log("debug", `device auth success`, { status: tokenResponse.status });
|
|
189
|
-
return tokenData.access_token;
|
|
190
|
-
}
|
|
191
|
-
if (tokenData.error === "authorization_pending") {
|
|
192
|
-
continue;
|
|
193
|
-
}
|
|
194
|
-
if (tokenData.error === "slow_down") {
|
|
195
|
-
pollDelay += 1000;
|
|
196
|
-
log("debug", `device auth slow_down`, { nextDelayMs: pollDelay });
|
|
197
|
-
continue;
|
|
25
|
+
const uploadUrls = await helpers.getStateUploadUrls(siteUrl, token, files.length);
|
|
26
|
+
const entries = [];
|
|
27
|
+
for (let index = 0;index < files.length; index += 1) {
|
|
28
|
+
const file = files[index];
|
|
29
|
+
const uploadUrl = uploadUrls[index];
|
|
30
|
+
if (!file || !uploadUrl) {
|
|
31
|
+
throw new Error("Upload URL list did not match file list");
|
|
198
32
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
33
|
+
const data = new Uint8Array(await Bun.file(file.fullPath).arrayBuffer());
|
|
34
|
+
const fileHash = createHash("sha256").update(data).digest("hex");
|
|
35
|
+
const storageId = await helpers.uploadStateFileToUrl(uploadUrl, file.fullPath);
|
|
36
|
+
entries.push({
|
|
37
|
+
path: file.relativePath,
|
|
38
|
+
fileHash,
|
|
39
|
+
storageId,
|
|
40
|
+
size: file.size,
|
|
41
|
+
mtimeMs: file.mtimeMs,
|
|
42
|
+
kind
|
|
202
43
|
});
|
|
203
|
-
throw new Error(`Device auth failed: ${tokenData.error ?? "unknown"} ${tokenData.error_description ?? ""}`.trim());
|
|
204
44
|
}
|
|
45
|
+
return entries;
|
|
205
46
|
};
|
|
47
|
+
|
|
48
|
+
// src/claude.ts
|
|
49
|
+
var CLAUDE_SETTINGS_PATH = join(homedir(), ".claude", "settings.json");
|
|
50
|
+
var CLAUDE_PROJECTS_DIR = join(homedir(), ".claude", "projects");
|
|
206
51
|
var ensureHookEntry = (entries, next) => {
|
|
207
52
|
const sameMatcher = (entry) => (entry.matcher ?? null) === (next.matcher ?? null);
|
|
208
53
|
const command = next.hooks[0]?.command;
|
|
@@ -219,7 +64,7 @@ var ensureHookEntry = (entries, next) => {
|
|
|
219
64
|
}
|
|
220
65
|
return entries;
|
|
221
66
|
};
|
|
222
|
-
var
|
|
67
|
+
var mergeClaudeSettings = async (log, options) => {
|
|
223
68
|
log("debug", `mergeSettings`, { path: CLAUDE_SETTINGS_PATH });
|
|
224
69
|
const settingsFile = Bun.file(CLAUDE_SETTINGS_PATH);
|
|
225
70
|
const settingsExists = await settingsFile.exists();
|
|
@@ -256,7 +101,14 @@ var mergeSettings = async () => {
|
|
|
256
101
|
enabledPlugins: current.enabledPlugins ?? {},
|
|
257
102
|
hooks
|
|
258
103
|
};
|
|
259
|
-
|
|
104
|
+
const serialized = JSON.stringify(nextConfig, null, 2);
|
|
105
|
+
if (options?.dry) {
|
|
106
|
+
console.log(`# ${CLAUDE_SETTINGS_PATH}`);
|
|
107
|
+
console.log(serialized);
|
|
108
|
+
log("debug", `mergeSettings dry run complete`);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
await Bun.write(CLAUDE_SETTINGS_PATH, serialized, {
|
|
260
112
|
createPath: true
|
|
261
113
|
});
|
|
262
114
|
log("debug", `mergeSettings wrote settings`);
|
|
@@ -272,7 +124,7 @@ var mergeSettings = async () => {
|
|
|
272
124
|
});
|
|
273
125
|
}
|
|
274
126
|
};
|
|
275
|
-
var
|
|
127
|
+
var findClaudeSessionFile = async (sessionId) => {
|
|
276
128
|
let bestPath = null;
|
|
277
129
|
let bestMtimeMs = 0;
|
|
278
130
|
const targetName = `${sessionId}.jsonl`;
|
|
@@ -309,14 +161,14 @@ var isDirectory = async (path) => {
|
|
|
309
161
|
return false;
|
|
310
162
|
}
|
|
311
163
|
};
|
|
312
|
-
var
|
|
164
|
+
var resolveClaudeSessionDir = async (sessionFile) => {
|
|
313
165
|
if (!sessionFile.endsWith(".jsonl")) {
|
|
314
166
|
return null;
|
|
315
167
|
}
|
|
316
168
|
const candidate = sessionFile.slice(0, -".jsonl".length);
|
|
317
169
|
return await isDirectory(candidate) ? candidate : null;
|
|
318
170
|
};
|
|
319
|
-
var
|
|
171
|
+
var findClaudeSessionDir = async (sessionId) => {
|
|
320
172
|
let bestPath = null;
|
|
321
173
|
let bestMtimeMs = 0;
|
|
322
174
|
const walk = async (dir) => {
|
|
@@ -345,7 +197,7 @@ var findSessionDir = async (sessionId) => {
|
|
|
345
197
|
return bestPath;
|
|
346
198
|
};
|
|
347
199
|
var normalizePath = (value) => value.split(sep).join("/");
|
|
348
|
-
var
|
|
200
|
+
var collectClaudeStateFiles = async (sessionDir) => {
|
|
349
201
|
const files = [];
|
|
350
202
|
const walk = async (dir) => {
|
|
351
203
|
let entries;
|
|
@@ -375,6 +227,329 @@ var collectSessionStateFiles = async (sessionDir) => {
|
|
|
375
227
|
await walk(sessionDir);
|
|
376
228
|
return files;
|
|
377
229
|
};
|
|
230
|
+
var buildClaudeStateFiles = async (sessionDir, siteUrl, token, helpers) => {
|
|
231
|
+
const files = await collectClaudeStateFiles(sessionDir);
|
|
232
|
+
return buildStateFilesFromCollection(files, "claude-state", siteUrl, token, helpers);
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
// src/codex.ts
|
|
236
|
+
import { stat as stat2 } from "fs/promises";
|
|
237
|
+
import { homedir as homedir2, userInfo } from "os";
|
|
238
|
+
import { join as join2, resolve } from "path";
|
|
239
|
+
var resolveHomeDir = () => {
|
|
240
|
+
const envHome = process.env.HOME ?? process.env.USERPROFILE;
|
|
241
|
+
if (envHome) {
|
|
242
|
+
return envHome;
|
|
243
|
+
}
|
|
244
|
+
try {
|
|
245
|
+
return userInfo().homedir;
|
|
246
|
+
} catch {
|
|
247
|
+
return homedir2();
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
var CODEX_HOME = process.env.CODEX_HOME ? resolve(process.env.CODEX_HOME) : join2(resolveHomeDir(), ".codex");
|
|
251
|
+
var CODEX_CONFIG_PATH = join2(CODEX_HOME, "config.toml");
|
|
252
|
+
var CODEX_SESSIONS_DIR = join2(CODEX_HOME, "sessions");
|
|
253
|
+
var CODEX_ARCHIVED_SESSIONS_DIR = join2(CODEX_HOME, "archived_sessions");
|
|
254
|
+
var CODEX_STATE_FILES = ["state.sqlite", "session_index.jsonl", "history.jsonl"];
|
|
255
|
+
var CODEX_NOTIFY_COMMAND = ["bisync", "hook", "Codex"];
|
|
256
|
+
var getCodexPaths = () => ({
|
|
257
|
+
home: CODEX_HOME,
|
|
258
|
+
sessionsDir: CODEX_SESSIONS_DIR,
|
|
259
|
+
archivedSessionsDir: CODEX_ARCHIVED_SESSIONS_DIR
|
|
260
|
+
});
|
|
261
|
+
var mergeCodexConfig = async (force, log, options) => {
|
|
262
|
+
let current = {};
|
|
263
|
+
const configFile = Bun.file(CODEX_CONFIG_PATH);
|
|
264
|
+
if (await configFile.exists()) {
|
|
265
|
+
try {
|
|
266
|
+
const parsed = Bun.TOML.parse(await configFile.text());
|
|
267
|
+
if (parsed && typeof parsed === "object") {
|
|
268
|
+
current = parsed;
|
|
269
|
+
}
|
|
270
|
+
} catch (error) {
|
|
271
|
+
log("debug", "mergeCodexConfig parse failed", {
|
|
272
|
+
error: error instanceof Error ? error.message : String(error)
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
const existingNotify = current.notify;
|
|
277
|
+
const isArray = Array.isArray(existingNotify);
|
|
278
|
+
const matches = isArray && existingNotify.length === CODEX_NOTIFY_COMMAND.length && existingNotify.every((value, index) => value === CODEX_NOTIFY_COMMAND[index]);
|
|
279
|
+
const hasNotify = Object.prototype.hasOwnProperty.call(current, "notify");
|
|
280
|
+
if (!force && hasNotify) {
|
|
281
|
+
if (matches) {
|
|
282
|
+
log("debug", "mergeCodexConfig notify already configured");
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
log("debug", "mergeCodexConfig existing notify preserved");
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
const nextConfig = {
|
|
289
|
+
...current,
|
|
290
|
+
notify: CODEX_NOTIFY_COMMAND
|
|
291
|
+
};
|
|
292
|
+
try {
|
|
293
|
+
const tomlStringify = Bun.TOML.stringify ?? ((obj) => {
|
|
294
|
+
const lines = [];
|
|
295
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
296
|
+
if (Array.isArray(value)) {
|
|
297
|
+
const items = value.map((v) => typeof v === "string" ? `"${v.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"` : String(v));
|
|
298
|
+
lines.push(`${key} = [${items.join(", ")}]`);
|
|
299
|
+
} else if (typeof value === "string") {
|
|
300
|
+
lines.push(`${key} = "${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`);
|
|
301
|
+
} else if (typeof value === "number" || typeof value === "boolean") {
|
|
302
|
+
lines.push(`${key} = ${value}`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return lines.join(`
|
|
306
|
+
`) + `
|
|
307
|
+
`;
|
|
308
|
+
});
|
|
309
|
+
const serialized = tomlStringify(nextConfig);
|
|
310
|
+
if (options?.dry) {
|
|
311
|
+
console.log(`# ${CODEX_CONFIG_PATH}`);
|
|
312
|
+
console.log(serialized);
|
|
313
|
+
log("debug", "mergeCodexConfig dry run complete");
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
await Bun.write(CODEX_CONFIG_PATH, serialized, { createPath: true });
|
|
317
|
+
log("debug", "mergeCodexConfig wrote config");
|
|
318
|
+
} catch (error) {
|
|
319
|
+
log("debug", "mergeCodexConfig write failed", {
|
|
320
|
+
error: error instanceof Error ? error.message : String(error)
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
var findCodexRolloutFile = async (threadId, options) => {
|
|
325
|
+
const log = options?.log;
|
|
326
|
+
const roots = [CODEX_SESSIONS_DIR, CODEX_ARCHIVED_SESSIONS_DIR];
|
|
327
|
+
const glob = new Bun.Glob(`**/rollout-*-${threadId}.jsonl`);
|
|
328
|
+
let bestPath = null;
|
|
329
|
+
let bestMtimeMs = 0;
|
|
330
|
+
for (const root of roots) {
|
|
331
|
+
try {
|
|
332
|
+
for await (const match of glob.scan({ cwd: root, absolute: true })) {
|
|
333
|
+
const info = await stat2(match);
|
|
334
|
+
if (!bestPath || info.mtimeMs > bestMtimeMs) {
|
|
335
|
+
bestPath = match;
|
|
336
|
+
bestMtimeMs = info.mtimeMs;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
} catch (error) {
|
|
340
|
+
log?.("debug", "findCodexRolloutFile scan failed", {
|
|
341
|
+
root,
|
|
342
|
+
error: error instanceof Error ? error.message : String(error)
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return bestPath;
|
|
347
|
+
};
|
|
348
|
+
var collectCodexStateFiles = async () => {
|
|
349
|
+
const files = [];
|
|
350
|
+
for (const name of CODEX_STATE_FILES) {
|
|
351
|
+
const fullPath = join2(CODEX_HOME, name);
|
|
352
|
+
try {
|
|
353
|
+
const info = await stat2(fullPath);
|
|
354
|
+
if (!info.isFile()) {
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
files.push({
|
|
358
|
+
fullPath,
|
|
359
|
+
relativePath: name,
|
|
360
|
+
size: info.size,
|
|
361
|
+
mtimeMs: info.mtimeMs
|
|
362
|
+
});
|
|
363
|
+
} catch {}
|
|
364
|
+
}
|
|
365
|
+
return files;
|
|
366
|
+
};
|
|
367
|
+
var buildCodexStateFiles = async (siteUrl, token, helpers) => {
|
|
368
|
+
const files = await collectCodexStateFiles();
|
|
369
|
+
return buildStateFilesFromCollection(files, "codex-state", siteUrl, token, helpers);
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
// src/bin.ts
|
|
373
|
+
var CONFIG_DIR = join3(homedir3(), ".agent-bisync");
|
|
374
|
+
var CONFIG_PATH = join3(CONFIG_DIR, "config.json");
|
|
375
|
+
var DEBUG_LOG_PATH = join3(CONFIG_DIR, "debug.log");
|
|
376
|
+
var CLIENT_ID = "bisync-cli";
|
|
377
|
+
var MAX_LINES_PER_BATCH = 200;
|
|
378
|
+
var parseArgs = () => {
|
|
379
|
+
const parsed = parseArgsUtil({
|
|
380
|
+
args: process.argv.slice(2),
|
|
381
|
+
options: {
|
|
382
|
+
verbose: { type: "boolean" },
|
|
383
|
+
version: { type: "boolean" },
|
|
384
|
+
help: { type: "boolean" },
|
|
385
|
+
force: { type: "boolean" },
|
|
386
|
+
dry: { type: "boolean" },
|
|
387
|
+
"site-url": { type: "string" },
|
|
388
|
+
siteUrl: { type: "string" }
|
|
389
|
+
},
|
|
390
|
+
allowPositionals: true
|
|
391
|
+
});
|
|
392
|
+
const values = parsed.values;
|
|
393
|
+
const positionals = parsed.positionals;
|
|
394
|
+
return { values, positionals };
|
|
395
|
+
};
|
|
396
|
+
var printUsage = () => {
|
|
397
|
+
console.log(`bisync <command>
|
|
398
|
+
|
|
399
|
+
Commands:
|
|
400
|
+
auth login --site-url <url> Authenticate via device flow
|
|
401
|
+
auth logout Sign out of the CLI
|
|
402
|
+
session list List session ids
|
|
403
|
+
setup [claude|codex] --site-url <url> Configure hooks and authenticate
|
|
404
|
+
hook <HOOK> Handle Claude/Codex hook input (stdin JSON)
|
|
405
|
+
|
|
406
|
+
Global options:
|
|
407
|
+
--verbose Enable verbose logging
|
|
408
|
+
--dry Print updated configs instead of writing files
|
|
409
|
+
--help Show usage
|
|
410
|
+
|
|
411
|
+
Version: ${version} (Bun ${Bun.version})
|
|
412
|
+
`);
|
|
413
|
+
};
|
|
414
|
+
var LOG_LEVEL = "info";
|
|
415
|
+
var appendDebugLog = (line) => {
|
|
416
|
+
try {
|
|
417
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
418
|
+
appendFileSync(DEBUG_LOG_PATH, line);
|
|
419
|
+
} catch {}
|
|
420
|
+
};
|
|
421
|
+
var log = (level, message, fields) => {
|
|
422
|
+
let logMessage = `[${level}] ${message}`;
|
|
423
|
+
if (fields) {
|
|
424
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
425
|
+
logMessage += ` ${key}=${value}`;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
appendDebugLog(`[${new Date().toISOString()}]${logMessage}
|
|
429
|
+
`);
|
|
430
|
+
if (level < LOG_LEVEL) {
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
console.log(`[bisync]${logMessage}`);
|
|
434
|
+
};
|
|
435
|
+
var readConfig = async () => {
|
|
436
|
+
return await Bun.file(CONFIG_PATH).json();
|
|
437
|
+
};
|
|
438
|
+
var writeConfig = async (config) => {
|
|
439
|
+
await Bun.write(CONFIG_PATH, JSON.stringify(config, null, 2), {
|
|
440
|
+
createPath: true
|
|
441
|
+
});
|
|
442
|
+
};
|
|
443
|
+
var readConfigIfExists = async () => {
|
|
444
|
+
const configFile = Bun.file(CONFIG_PATH);
|
|
445
|
+
if (!await configFile.exists()) {
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
448
|
+
try {
|
|
449
|
+
return await configFile.json();
|
|
450
|
+
} catch {
|
|
451
|
+
return null;
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
var promptYesNo = async (question, defaultValue) => {
|
|
455
|
+
if (!process.stdin.isTTY) {
|
|
456
|
+
log("warn", `${question} Using default ${defaultValue ? "yes" : "no"} (stdin not interactive).`);
|
|
457
|
+
return defaultValue;
|
|
458
|
+
}
|
|
459
|
+
const suffix = defaultValue ? " [Y/n] " : " [y/N] ";
|
|
460
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
461
|
+
const answer = await new Promise((resolve3) => rl.question(`${question}${suffix}`, resolve3));
|
|
462
|
+
rl.close();
|
|
463
|
+
const normalized = answer.trim().toLowerCase();
|
|
464
|
+
if (!normalized) {
|
|
465
|
+
return defaultValue;
|
|
466
|
+
}
|
|
467
|
+
return normalized === "y" || normalized === "yes";
|
|
468
|
+
};
|
|
469
|
+
var resolveSiteUrl = (args) => {
|
|
470
|
+
const argValue = args["site-url"] ?? args.siteUrl;
|
|
471
|
+
const envValue = process.env.BISYNC_SITE_URL;
|
|
472
|
+
if (argValue) {
|
|
473
|
+
return { siteUrl: argValue, source: "arg" };
|
|
474
|
+
}
|
|
475
|
+
if (envValue) {
|
|
476
|
+
return { siteUrl: envValue, source: "env" };
|
|
477
|
+
}
|
|
478
|
+
return { siteUrl: null, source: "missing" };
|
|
479
|
+
};
|
|
480
|
+
var openBrowser = async (url) => {
|
|
481
|
+
if (process.platform !== "darwin") {
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
try {
|
|
485
|
+
await Bun.$`open ${url}`;
|
|
486
|
+
} catch {}
|
|
487
|
+
};
|
|
488
|
+
var buildVerificationUrl = (data) => {
|
|
489
|
+
if (data.verification_uri_complete) {
|
|
490
|
+
return data.verification_uri_complete;
|
|
491
|
+
}
|
|
492
|
+
try {
|
|
493
|
+
const url = new URL(data.verification_uri);
|
|
494
|
+
url.searchParams.set("user_code", data.user_code);
|
|
495
|
+
return url.toString();
|
|
496
|
+
} catch {
|
|
497
|
+
const separator = data.verification_uri.includes("?") ? "&" : "?";
|
|
498
|
+
return `${data.verification_uri}${separator}user_code=${encodeURIComponent(data.user_code)}`;
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
var deviceAuthFlow = async (siteUrl) => {
|
|
502
|
+
log("debug", `device auth start`, { siteUrl });
|
|
503
|
+
const codeResponse = await fetch(`${siteUrl}/api/auth/device/code`, {
|
|
504
|
+
method: "POST",
|
|
505
|
+
headers: { "Content-Type": "application/json" },
|
|
506
|
+
body: JSON.stringify({ client_id: CLIENT_ID })
|
|
507
|
+
});
|
|
508
|
+
if (!codeResponse.ok) {
|
|
509
|
+
const errorText = await codeResponse.text();
|
|
510
|
+
log("debug", `device code request failed`, { status: codeResponse.status });
|
|
511
|
+
throw new Error(`Device code request failed: ${codeResponse.status} ${errorText}`);
|
|
512
|
+
}
|
|
513
|
+
log("debug", `device code request ok`, { status: codeResponse.status });
|
|
514
|
+
const codeData = await codeResponse.json();
|
|
515
|
+
const verificationUrl = buildVerificationUrl(codeData);
|
|
516
|
+
console.log(`Authorize this device: ${verificationUrl}`);
|
|
517
|
+
console.log(`Your Verification Code: ${codeData.user_code}`);
|
|
518
|
+
await openBrowser(verificationUrl);
|
|
519
|
+
const intervalMs = Math.max(1, codeData.interval ?? 5) * 1000;
|
|
520
|
+
log("debug", `device auth polling`, { intervalMs });
|
|
521
|
+
let pollDelay = intervalMs;
|
|
522
|
+
while (true) {
|
|
523
|
+
await setTimeout(pollDelay);
|
|
524
|
+
const tokenResponse = await fetch(`${siteUrl}/api/auth/device/token`, {
|
|
525
|
+
method: "POST",
|
|
526
|
+
headers: { "Content-Type": "application/json" },
|
|
527
|
+
body: JSON.stringify({
|
|
528
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
529
|
+
device_code: codeData.device_code,
|
|
530
|
+
client_id: CLIENT_ID
|
|
531
|
+
})
|
|
532
|
+
});
|
|
533
|
+
const tokenData = await tokenResponse.json();
|
|
534
|
+
if (tokenResponse.ok && tokenData.access_token) {
|
|
535
|
+
log("debug", `device auth success`, { status: tokenResponse.status });
|
|
536
|
+
return tokenData.access_token;
|
|
537
|
+
}
|
|
538
|
+
if (tokenData.error === "authorization_pending") {
|
|
539
|
+
continue;
|
|
540
|
+
}
|
|
541
|
+
if (tokenData.error === "slow_down") {
|
|
542
|
+
pollDelay += 1000;
|
|
543
|
+
log("debug", `device auth slow_down`, { nextDelayMs: pollDelay });
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
log("debug", `device auth failed`, {
|
|
547
|
+
status: tokenResponse.status,
|
|
548
|
+
error: tokenData.error ?? "unknown"
|
|
549
|
+
});
|
|
550
|
+
throw new Error(`Device auth failed: ${tokenData.error ?? "unknown"} ${tokenData.error_description ?? ""}`.trim());
|
|
551
|
+
}
|
|
552
|
+
};
|
|
378
553
|
var getStateUploadUrls = async (siteUrl, token, count) => {
|
|
379
554
|
const response = await fetch(`${siteUrl}/api/storage/upload-url`, {
|
|
380
555
|
method: "POST",
|
|
@@ -411,32 +586,6 @@ var uploadStateFileToUrl = async (uploadUrl, filePath) => {
|
|
|
411
586
|
}
|
|
412
587
|
return data.storageId;
|
|
413
588
|
};
|
|
414
|
-
var buildStateFiles = async (sessionDir, siteUrl, token) => {
|
|
415
|
-
const files = await collectSessionStateFiles(sessionDir);
|
|
416
|
-
if (files.length === 0) {
|
|
417
|
-
return [];
|
|
418
|
-
}
|
|
419
|
-
const uploadUrls = await getStateUploadUrls(siteUrl, token, files.length);
|
|
420
|
-
const entries = [];
|
|
421
|
-
for (let index = 0;index < files.length; index += 1) {
|
|
422
|
-
const file = files[index];
|
|
423
|
-
const uploadUrl = uploadUrls[index];
|
|
424
|
-
if (!file || !uploadUrl) {
|
|
425
|
-
throw new Error("Upload URL list did not match file list");
|
|
426
|
-
}
|
|
427
|
-
const data = new Uint8Array(await Bun.file(file.fullPath).arrayBuffer());
|
|
428
|
-
const fileHash = createHash("sha256").update(data).digest("hex");
|
|
429
|
-
const storageId = await uploadStateFileToUrl(uploadUrl, file.fullPath);
|
|
430
|
-
entries.push({
|
|
431
|
-
path: file.relativePath,
|
|
432
|
-
fileHash,
|
|
433
|
-
storageId,
|
|
434
|
-
size: file.size,
|
|
435
|
-
mtimeMs: file.mtimeMs
|
|
436
|
-
});
|
|
437
|
-
}
|
|
438
|
-
return entries;
|
|
439
|
-
};
|
|
440
589
|
var buildLogLines = (raw) => {
|
|
441
590
|
const lines = raw.split(`
|
|
442
591
|
`).filter((line) => line.trim().length > 0);
|
|
@@ -449,6 +598,11 @@ var buildLogLines = (raw) => {
|
|
|
449
598
|
ts = parsed.ts;
|
|
450
599
|
} else if (typeof parsed.timestamp === "number") {
|
|
451
600
|
ts = parsed.timestamp;
|
|
601
|
+
} else if (typeof parsed.timestamp === "string") {
|
|
602
|
+
const parsedTs = Date.parse(parsed.timestamp);
|
|
603
|
+
if (!Number.isNaN(parsedTs)) {
|
|
604
|
+
ts = parsedTs;
|
|
605
|
+
}
|
|
452
606
|
}
|
|
453
607
|
} catch {}
|
|
454
608
|
return { ts, log: line, encoding: "utf8" };
|
|
@@ -523,9 +677,25 @@ var signOutRemote = async (siteUrl, token) => {
|
|
|
523
677
|
};
|
|
524
678
|
}
|
|
525
679
|
};
|
|
526
|
-
var
|
|
527
|
-
|
|
680
|
+
var describeSetupTarget = (target) => {
|
|
681
|
+
if (target === "claude")
|
|
682
|
+
return "Claude";
|
|
683
|
+
if (target === "codex")
|
|
684
|
+
return "Codex";
|
|
685
|
+
return "Claude + Codex";
|
|
686
|
+
};
|
|
687
|
+
var updateSettingsForTarget = async (target, force, dry) => {
|
|
688
|
+
if (target === "claude" || target === "all") {
|
|
689
|
+
await mergeClaudeSettings(log, { dry });
|
|
690
|
+
}
|
|
691
|
+
if (target === "codex" || target === "all") {
|
|
692
|
+
await mergeCodexConfig(force, log, { dry });
|
|
693
|
+
}
|
|
694
|
+
};
|
|
695
|
+
var runSetup = async (args, target) => {
|
|
696
|
+
log("debug", `runSetup start`, { target });
|
|
528
697
|
const force = Boolean(args.force);
|
|
698
|
+
const dry = Boolean(args.dry);
|
|
529
699
|
log("debug", "runSetup", { force });
|
|
530
700
|
const configExists = await Bun.file(CONFIG_PATH).exists();
|
|
531
701
|
log("debug", "config", { path: CONFIG_PATH, exists: configExists });
|
|
@@ -556,16 +726,37 @@ var runSetup = async (args) => {
|
|
|
556
726
|
throw new Error("Missing site URL. Provide --site-url or BISYNC_SITE_URL.");
|
|
557
727
|
}
|
|
558
728
|
if (configExists && existingConfig?.token && !force) {
|
|
559
|
-
log("debug", "using existing token; updating
|
|
560
|
-
await
|
|
561
|
-
|
|
729
|
+
log("debug", "using existing token; updating settings only", { target });
|
|
730
|
+
await updateSettingsForTarget(target, force, dry);
|
|
731
|
+
if (dry) {
|
|
732
|
+
log("info", `Printed ${describeSetupTarget(target)} settings (dry run)`, {
|
|
733
|
+
path: CONFIG_PATH
|
|
734
|
+
});
|
|
735
|
+
} else {
|
|
736
|
+
log("info", `Updated ${describeSetupTarget(target)} settings using existing credentials`, {
|
|
737
|
+
path: CONFIG_PATH
|
|
738
|
+
});
|
|
739
|
+
}
|
|
562
740
|
return;
|
|
563
741
|
}
|
|
564
742
|
const token = await deviceAuthFlow(siteUrl);
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
743
|
+
if (dry) {
|
|
744
|
+
console.log(`# ${CONFIG_PATH}`);
|
|
745
|
+
console.log(JSON.stringify({ siteUrl, token, clientId: CLIENT_ID }, null, 2));
|
|
746
|
+
} else {
|
|
747
|
+
await writeConfig({ siteUrl, token, clientId: CLIENT_ID });
|
|
748
|
+
log("debug", "config written", { path: CONFIG_PATH });
|
|
749
|
+
}
|
|
750
|
+
await updateSettingsForTarget(target, force, dry);
|
|
751
|
+
if (dry) {
|
|
752
|
+
log("info", `Printed ${describeSetupTarget(target)} settings (dry run)`, {
|
|
753
|
+
path: CONFIG_PATH
|
|
754
|
+
});
|
|
755
|
+
} else {
|
|
756
|
+
log("info", `Configured ${describeSetupTarget(target)} settings and saved credentials`, {
|
|
757
|
+
path: CONFIG_PATH
|
|
758
|
+
});
|
|
759
|
+
}
|
|
569
760
|
};
|
|
570
761
|
var runAuthLogin = async (args) => {
|
|
571
762
|
log("debug", "runAuthLogin start");
|
|
@@ -644,22 +835,91 @@ var runSessionList = async () => {
|
|
|
644
835
|
console.log(session.externalId);
|
|
645
836
|
}
|
|
646
837
|
};
|
|
647
|
-
var runHook = async (hookName) => {
|
|
838
|
+
var runHook = async (hookName, payloadArg) => {
|
|
648
839
|
log("debug", `runHook start`, { hook: hookName });
|
|
649
|
-
const stdinRaw = await new Promise((
|
|
840
|
+
const stdinRaw = payloadArg && payloadArg.trim().length > 0 ? "" : await new Promise((resolve3, reject) => {
|
|
650
841
|
let data = "";
|
|
651
842
|
process.stdin.setEncoding("utf8");
|
|
652
843
|
process.stdin.on("data", (chunk) => {
|
|
653
844
|
data += chunk;
|
|
654
845
|
});
|
|
655
|
-
process.stdin.on("end", () =>
|
|
846
|
+
process.stdin.on("end", () => resolve3(data));
|
|
656
847
|
process.stdin.on("error", (error) => reject(error));
|
|
657
848
|
});
|
|
658
|
-
|
|
659
|
-
|
|
849
|
+
const rawPayload = stdinRaw.trim() || payloadArg?.trim() || "";
|
|
850
|
+
log("debug", `runHook`, { hook: hookName, stdin: stdinRaw.trim(), arg: payloadArg ?? "" });
|
|
851
|
+
if (!rawPayload)
|
|
852
|
+
return;
|
|
853
|
+
let payload;
|
|
854
|
+
try {
|
|
855
|
+
payload = JSON.parse(rawPayload);
|
|
856
|
+
} catch (error) {
|
|
857
|
+
log("debug", `runHook`, {
|
|
858
|
+
hook: hookName,
|
|
859
|
+
error: error instanceof Error ? error.message : String(error)
|
|
860
|
+
});
|
|
660
861
|
return;
|
|
661
|
-
|
|
662
|
-
|
|
862
|
+
}
|
|
863
|
+
if (hookName === "Codex") {
|
|
864
|
+
const threadId = (typeof payload["thread-id"] === "string" ? payload["thread-id"] : undefined) ?? (typeof payload.thread_id === "string" ? payload.thread_id : undefined) ?? (typeof payload.threadId === "string" ? payload.threadId : undefined);
|
|
865
|
+
if (!threadId) {
|
|
866
|
+
log("debug", `runHook`, { hook: hookName, error: "missing-thread-id" });
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
const config2 = await readConfig();
|
|
870
|
+
const siteUrl2 = process.env.BISYNC_SITE_URL ?? config2.siteUrl;
|
|
871
|
+
const token2 = config2.token;
|
|
872
|
+
const codexPaths = getCodexPaths();
|
|
873
|
+
const sessionsExists = await Bun.file(codexPaths.sessionsDir).exists();
|
|
874
|
+
const archivedExists = await Bun.file(codexPaths.archivedSessionsDir).exists();
|
|
875
|
+
log("debug", `runHook`, {
|
|
876
|
+
threadId,
|
|
877
|
+
codexHome: codexPaths.home,
|
|
878
|
+
sessionsDir: codexPaths.sessionsDir,
|
|
879
|
+
sessionsExists,
|
|
880
|
+
archivedSessionsDir: codexPaths.archivedSessionsDir,
|
|
881
|
+
archivedExists
|
|
882
|
+
});
|
|
883
|
+
const rolloutFile = await findCodexRolloutFile(threadId, { log });
|
|
884
|
+
if (!rolloutFile) {
|
|
885
|
+
log("debug", `runHook`, { threadId, error: "rollout-not-found" });
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
log("debug", `runHook`, { threadId, rolloutFile });
|
|
889
|
+
const raw = await Bun.file(rolloutFile).text();
|
|
890
|
+
if (!raw.trim()) {
|
|
891
|
+
log("debug", `runHook`, { threadId, error: "empty-log" });
|
|
892
|
+
} else {
|
|
893
|
+
try {
|
|
894
|
+
await uploadLogs(siteUrl2, token2, threadId, raw);
|
|
895
|
+
log("debug", `runHook`, { threadId, error: "upload-ok" });
|
|
896
|
+
} catch (error) {
|
|
897
|
+
log("debug", `runHook`, {
|
|
898
|
+
threadId,
|
|
899
|
+
error: error instanceof Error ? error.message : String(error)
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
try {
|
|
904
|
+
const stateFiles = await buildCodexStateFiles(siteUrl2, token2, {
|
|
905
|
+
getStateUploadUrls,
|
|
906
|
+
uploadStateFileToUrl
|
|
907
|
+
});
|
|
908
|
+
if (stateFiles.length === 0) {
|
|
909
|
+
log("debug", `runHook`, { threadId, error: "no-state-files" });
|
|
910
|
+
} else {
|
|
911
|
+
await uploadStateFiles(siteUrl2, token2, threadId, stateFiles);
|
|
912
|
+
log("debug", `runHook`, { threadId, error: "state-upload-ok" });
|
|
913
|
+
}
|
|
914
|
+
} catch (error) {
|
|
915
|
+
log("debug", `runHook`, {
|
|
916
|
+
threadId,
|
|
917
|
+
error: error instanceof Error ? error.message : String(error)
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
const sessionId = typeof payload.session_id === "string" ? payload.session_id : undefined;
|
|
663
923
|
if (!sessionId) {
|
|
664
924
|
log("debug", `runHook`, { hook: hookName, error: "missing-session-id" });
|
|
665
925
|
return;
|
|
@@ -669,24 +929,25 @@ var runHook = async (hookName) => {
|
|
|
669
929
|
const token = config.token;
|
|
670
930
|
log("debug", `runHook`, { siteUrl });
|
|
671
931
|
let sessionFile = null;
|
|
672
|
-
|
|
673
|
-
|
|
932
|
+
const transcriptPath = typeof payload.transcript_path === "string" ? payload.transcript_path : undefined;
|
|
933
|
+
if (transcriptPath) {
|
|
934
|
+
const resolvedPath = resolve2(transcriptPath);
|
|
674
935
|
if (await Bun.file(resolvedPath).exists()) {
|
|
675
936
|
sessionFile = resolvedPath;
|
|
676
937
|
}
|
|
677
938
|
}
|
|
678
939
|
if (!sessionFile) {
|
|
679
|
-
sessionFile = await
|
|
940
|
+
sessionFile = await findClaudeSessionFile(sessionId);
|
|
680
941
|
}
|
|
681
942
|
if (!sessionFile) {
|
|
682
943
|
log("debug", `runHook`, { sessionId, error: "session-file-not-found" });
|
|
683
944
|
}
|
|
684
945
|
let sessionDir = null;
|
|
685
946
|
if (sessionFile) {
|
|
686
|
-
sessionDir = await
|
|
947
|
+
sessionDir = await resolveClaudeSessionDir(sessionFile);
|
|
687
948
|
}
|
|
688
949
|
if (!sessionDir) {
|
|
689
|
-
sessionDir = await
|
|
950
|
+
sessionDir = await findClaudeSessionDir(sessionId);
|
|
690
951
|
}
|
|
691
952
|
if (!sessionFile && !sessionDir) {
|
|
692
953
|
return;
|
|
@@ -711,7 +972,10 @@ var runHook = async (hookName) => {
|
|
|
711
972
|
if (sessionDir && hookName === "SessionEnd") {
|
|
712
973
|
log("debug", `runHook`, { sessionDir });
|
|
713
974
|
try {
|
|
714
|
-
const stateFiles = await
|
|
975
|
+
const stateFiles = await buildClaudeStateFiles(sessionDir, siteUrl, token, {
|
|
976
|
+
getStateUploadUrls,
|
|
977
|
+
uploadStateFileToUrl
|
|
978
|
+
});
|
|
715
979
|
if (stateFiles.length === 0) {
|
|
716
980
|
log("debug", `runHook`, { sessionId, error: "no-state-files" });
|
|
717
981
|
return;
|
|
@@ -739,7 +1003,7 @@ var main = async () => {
|
|
|
739
1003
|
if (values.verbose) {
|
|
740
1004
|
LOG_LEVEL = "debug";
|
|
741
1005
|
}
|
|
742
|
-
const [command, subcommand] = positionals;
|
|
1006
|
+
const [command, subcommand, ...rest] = positionals;
|
|
743
1007
|
log("debug", "argv", { argv: JSON.stringify(process.argv.slice(2)) });
|
|
744
1008
|
if (!command) {
|
|
745
1009
|
printUsage();
|
|
@@ -747,7 +1011,12 @@ var main = async () => {
|
|
|
747
1011
|
}
|
|
748
1012
|
try {
|
|
749
1013
|
if (command === "setup") {
|
|
750
|
-
|
|
1014
|
+
if (subcommand && subcommand !== "claude" && subcommand !== "codex") {
|
|
1015
|
+
printUsage();
|
|
1016
|
+
process.exit(1);
|
|
1017
|
+
}
|
|
1018
|
+
const target = subcommand ?? "all";
|
|
1019
|
+
await runSetup(values, target);
|
|
751
1020
|
process.exit(0);
|
|
752
1021
|
}
|
|
753
1022
|
if (command === "auth") {
|
|
@@ -771,7 +1040,8 @@ var main = async () => {
|
|
|
771
1040
|
process.exit(1);
|
|
772
1041
|
}
|
|
773
1042
|
if (command === "hook") {
|
|
774
|
-
|
|
1043
|
+
const payloadArg = rest.length > 0 ? rest.join(" ") : null;
|
|
1044
|
+
await runHook(subcommand ?? "unknown", payloadArg);
|
|
775
1045
|
process.exit(0);
|
|
776
1046
|
}
|
|
777
1047
|
printUsage();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bisync-cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.12",
|
|
4
4
|
"bin": {
|
|
5
5
|
"bisync": "dist/bisync.js"
|
|
6
6
|
},
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
],
|
|
10
10
|
"type": "module",
|
|
11
11
|
"scripts": {
|
|
12
|
-
"build": "bun build src/bin.ts --outfile dist/bisync.js"
|
|
12
|
+
"build": "bun build --target bun src/bin.ts --outfile dist/bisync.js"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
15
|
"@types/bun": "^1.3.5"
|