codex-auth-sync 0.1.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/dist/index.js +805 -0
- package/dist/index.js.map +1 -0
- package/package.json +37 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,805 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/commands.ts
|
|
7
|
+
import { existsSync as existsSync3 } from "fs";
|
|
8
|
+
|
|
9
|
+
// ../shared/dist/crypto.js
|
|
10
|
+
var AES_KEY_BITS = 256;
|
|
11
|
+
var AES_KEY_BYTES = AES_KEY_BITS / 8;
|
|
12
|
+
var DEFAULT_KDF_ITERATIONS = 31e4;
|
|
13
|
+
function bytesToBase64(bytes) {
|
|
14
|
+
let binary = "";
|
|
15
|
+
for (let index = 0; index < bytes.length; index += 32768) {
|
|
16
|
+
binary += String.fromCharCode(...bytes.slice(index, index + 32768));
|
|
17
|
+
}
|
|
18
|
+
if (typeof btoa === "function") {
|
|
19
|
+
return btoa(binary);
|
|
20
|
+
}
|
|
21
|
+
return Buffer.from(bytes).toString("base64");
|
|
22
|
+
}
|
|
23
|
+
function base64ToBytes(value) {
|
|
24
|
+
if (typeof atob === "function") {
|
|
25
|
+
const binary = atob(value);
|
|
26
|
+
return Uint8Array.from(binary, (char) => char.charCodeAt(0));
|
|
27
|
+
}
|
|
28
|
+
return new Uint8Array(Buffer.from(value, "base64"));
|
|
29
|
+
}
|
|
30
|
+
function randomBytes(length) {
|
|
31
|
+
const bytes = new Uint8Array(length);
|
|
32
|
+
crypto.getRandomValues(bytes);
|
|
33
|
+
return bytes;
|
|
34
|
+
}
|
|
35
|
+
function toArrayBuffer(bytes) {
|
|
36
|
+
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
37
|
+
}
|
|
38
|
+
function bytesToHex(bytes) {
|
|
39
|
+
return [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
40
|
+
}
|
|
41
|
+
async function sha256Hex(bytes) {
|
|
42
|
+
const digest = await crypto.subtle.digest("SHA-256", toArrayBuffer(bytes));
|
|
43
|
+
return bytesToHex(new Uint8Array(digest));
|
|
44
|
+
}
|
|
45
|
+
function generateProfileKey() {
|
|
46
|
+
return randomBytes(AES_KEY_BYTES);
|
|
47
|
+
}
|
|
48
|
+
function encodeProfileKey(key) {
|
|
49
|
+
if (key.byteLength !== AES_KEY_BYTES) {
|
|
50
|
+
throw new Error("Profile key must be 32 bytes.");
|
|
51
|
+
}
|
|
52
|
+
return bytesToBase64(key);
|
|
53
|
+
}
|
|
54
|
+
function decodeProfileKey(value) {
|
|
55
|
+
const key = base64ToBytes(value);
|
|
56
|
+
if (key.byteLength !== AES_KEY_BYTES) {
|
|
57
|
+
throw new Error("Profile key must be 32 bytes.");
|
|
58
|
+
}
|
|
59
|
+
return key;
|
|
60
|
+
}
|
|
61
|
+
async function wrapProfileKey(profile, passphrase, rawProfileKey, options = {}) {
|
|
62
|
+
const keyVersion = options.keyVersion ?? 1;
|
|
63
|
+
const iterations = options.iterations ?? DEFAULT_KDF_ITERATIONS;
|
|
64
|
+
const salt = randomBytes(16);
|
|
65
|
+
const iv = randomBytes(12);
|
|
66
|
+
const wrappingKey = await deriveWrappingKey(passphrase, salt, iterations);
|
|
67
|
+
const wrappedKey = await crypto.subtle.encrypt({ name: "AES-GCM", iv: toArrayBuffer(iv) }, wrappingKey, toArrayBuffer(rawProfileKey));
|
|
68
|
+
return {
|
|
69
|
+
schemaVersion: 1,
|
|
70
|
+
profile,
|
|
71
|
+
keyVersion,
|
|
72
|
+
kdf: {
|
|
73
|
+
name: "PBKDF2-SHA256",
|
|
74
|
+
iterations,
|
|
75
|
+
salt: bytesToBase64(salt)
|
|
76
|
+
},
|
|
77
|
+
wrap: {
|
|
78
|
+
algorithm: "AES-256-GCM",
|
|
79
|
+
iv: bytesToBase64(iv),
|
|
80
|
+
wrappedKey: bytesToBase64(new Uint8Array(wrappedKey))
|
|
81
|
+
},
|
|
82
|
+
createdAt: (options.now ?? /* @__PURE__ */ new Date()).toISOString()
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
async function unwrapProfileKey(passphrase, envelope) {
|
|
86
|
+
if (envelope.schemaVersion !== 1 || envelope.wrap.algorithm !== "AES-256-GCM") {
|
|
87
|
+
throw new Error("Unsupported profile key envelope.");
|
|
88
|
+
}
|
|
89
|
+
const wrappingKey = await deriveWrappingKey(passphrase, base64ToBytes(envelope.kdf.salt), envelope.kdf.iterations);
|
|
90
|
+
const key = await crypto.subtle.decrypt({ name: "AES-GCM", iv: toArrayBuffer(base64ToBytes(envelope.wrap.iv)) }, wrappingKey, toArrayBuffer(base64ToBytes(envelope.wrap.wrappedKey)));
|
|
91
|
+
const rawKey = new Uint8Array(key);
|
|
92
|
+
if (rawKey.byteLength !== AES_KEY_BYTES) {
|
|
93
|
+
throw new Error("Invalid profile key length.");
|
|
94
|
+
}
|
|
95
|
+
return rawKey;
|
|
96
|
+
}
|
|
97
|
+
async function encryptAuthBytes(input2) {
|
|
98
|
+
const iv = randomBytes(12);
|
|
99
|
+
const key = await importAesKey(input2.profileKey, ["encrypt"]);
|
|
100
|
+
const ciphertext = await crypto.subtle.encrypt({ name: "AES-GCM", iv: toArrayBuffer(iv) }, key, toArrayBuffer(input2.plaintext));
|
|
101
|
+
return {
|
|
102
|
+
schemaVersion: 1,
|
|
103
|
+
profile: input2.profile,
|
|
104
|
+
version: input2.version,
|
|
105
|
+
keyVersion: input2.keyVersion,
|
|
106
|
+
encryption: {
|
|
107
|
+
algorithm: "AES-256-GCM",
|
|
108
|
+
iv: bytesToBase64(iv)
|
|
109
|
+
},
|
|
110
|
+
ciphertext: bytesToBase64(new Uint8Array(ciphertext)),
|
|
111
|
+
plaintextSize: input2.plaintext.byteLength,
|
|
112
|
+
createdAt: (input2.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
113
|
+
updatedByDevice: input2.updatedByDevice
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
async function decryptAuthBytes(profileKey, blob) {
|
|
117
|
+
if (blob.schemaVersion !== 1 || blob.encryption.algorithm !== "AES-256-GCM") {
|
|
118
|
+
throw new Error("Unsupported encrypted auth blob.");
|
|
119
|
+
}
|
|
120
|
+
const key = await importAesKey(profileKey, ["decrypt"]);
|
|
121
|
+
const plaintext = await crypto.subtle.decrypt({ name: "AES-GCM", iv: toArrayBuffer(base64ToBytes(blob.encryption.iv)) }, key, toArrayBuffer(base64ToBytes(blob.ciphertext)));
|
|
122
|
+
return new Uint8Array(plaintext);
|
|
123
|
+
}
|
|
124
|
+
async function deriveWrappingKey(passphrase, salt, iterations) {
|
|
125
|
+
if (!passphrase) {
|
|
126
|
+
throw new Error("Passphrase is required.");
|
|
127
|
+
}
|
|
128
|
+
const baseKey = await crypto.subtle.importKey("raw", toArrayBuffer(new TextEncoder().encode(passphrase)), "PBKDF2", false, ["deriveKey"]);
|
|
129
|
+
return crypto.subtle.deriveKey({
|
|
130
|
+
name: "PBKDF2",
|
|
131
|
+
hash: "SHA-256",
|
|
132
|
+
salt: toArrayBuffer(salt),
|
|
133
|
+
iterations
|
|
134
|
+
}, baseKey, { name: "AES-GCM", length: AES_KEY_BITS }, false, ["encrypt", "decrypt"]);
|
|
135
|
+
}
|
|
136
|
+
async function importAesKey(rawKey, usages) {
|
|
137
|
+
if (rawKey.byteLength !== AES_KEY_BYTES) {
|
|
138
|
+
throw new Error("Profile key must be 32 bytes.");
|
|
139
|
+
}
|
|
140
|
+
return crypto.subtle.importKey("raw", toArrayBuffer(rawKey), { name: "AES-GCM", length: AES_KEY_BITS }, false, usages);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ../shared/dist/validation.js
|
|
144
|
+
function isApiErrorBody(value) {
|
|
145
|
+
if (!value || typeof value !== "object") {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
const error = value.error;
|
|
149
|
+
return Boolean(error && typeof error === "object" && typeof error.code === "string" && typeof error.message === "string");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// src/api.ts
|
|
153
|
+
var ApiError = class extends Error {
|
|
154
|
+
constructor(status, code, message) {
|
|
155
|
+
super(message);
|
|
156
|
+
this.status = status;
|
|
157
|
+
this.code = code;
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
var ApiClient = class {
|
|
161
|
+
constructor(baseUrl, token) {
|
|
162
|
+
this.baseUrl = baseUrl;
|
|
163
|
+
this.token = token;
|
|
164
|
+
}
|
|
165
|
+
async request(path4, init = {}) {
|
|
166
|
+
const headers = new Headers(init.headers);
|
|
167
|
+
headers.set("Authorization", `Bearer ${this.token}`);
|
|
168
|
+
if (init.body && !headers.has("Content-Type")) {
|
|
169
|
+
headers.set("Content-Type", "application/json");
|
|
170
|
+
}
|
|
171
|
+
const response = await fetch(`${this.baseUrl}${path4}`, {
|
|
172
|
+
...init,
|
|
173
|
+
headers
|
|
174
|
+
});
|
|
175
|
+
if (!response.ok) {
|
|
176
|
+
let body = null;
|
|
177
|
+
try {
|
|
178
|
+
body = await response.json();
|
|
179
|
+
} catch {
|
|
180
|
+
body = null;
|
|
181
|
+
}
|
|
182
|
+
if (isApiErrorBody(body)) {
|
|
183
|
+
throw new ApiError(response.status, body.error.code, body.error.message);
|
|
184
|
+
}
|
|
185
|
+
throw new ApiError(response.status, "http_error", `${response.status} ${response.statusText}`);
|
|
186
|
+
}
|
|
187
|
+
if (response.status === 204) {
|
|
188
|
+
return void 0;
|
|
189
|
+
}
|
|
190
|
+
return response.json();
|
|
191
|
+
}
|
|
192
|
+
websocketUrl(deviceId) {
|
|
193
|
+
const url = new URL(`/api/v1/devices/${encodeURIComponent(deviceId)}/ws`, this.baseUrl);
|
|
194
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
195
|
+
return url.toString();
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
function isNotFound(error) {
|
|
199
|
+
return error instanceof ApiError && error.status === 404;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// src/config.ts
|
|
203
|
+
import { mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
204
|
+
import { existsSync } from "fs";
|
|
205
|
+
import os from "os";
|
|
206
|
+
import path from "path";
|
|
207
|
+
import { randomUUID } from "crypto";
|
|
208
|
+
import { parse } from "smol-toml";
|
|
209
|
+
function expandHome(value) {
|
|
210
|
+
if (value === "~") {
|
|
211
|
+
return os.homedir();
|
|
212
|
+
}
|
|
213
|
+
if (value.startsWith("~/")) {
|
|
214
|
+
return path.join(os.homedir(), value.slice(2));
|
|
215
|
+
}
|
|
216
|
+
return value;
|
|
217
|
+
}
|
|
218
|
+
function defaultConfigDir() {
|
|
219
|
+
return process.env.CODEX_AUTH_SYNC_CONFIG_DIR ?? path.join(os.homedir(), ".config", "codex-auth-sync");
|
|
220
|
+
}
|
|
221
|
+
function configPath() {
|
|
222
|
+
return path.join(defaultConfigDir(), "config.json");
|
|
223
|
+
}
|
|
224
|
+
function defaultCodexHome() {
|
|
225
|
+
return path.join(os.homedir(), ".codex");
|
|
226
|
+
}
|
|
227
|
+
async function loadConfig() {
|
|
228
|
+
try {
|
|
229
|
+
return JSON.parse(await readFile(configPath(), "utf8"));
|
|
230
|
+
} catch (error) {
|
|
231
|
+
if (error.code === "ENOENT") {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
throw error;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
async function saveConfig(config) {
|
|
238
|
+
await mkdir(defaultConfigDir(), { recursive: true, mode: 448 });
|
|
239
|
+
await writeFile(configPath(), `${JSON.stringify(config, null, 2)}
|
|
240
|
+
`, { mode: 384 });
|
|
241
|
+
}
|
|
242
|
+
async function removeConfig() {
|
|
243
|
+
await rm(configPath(), { force: true });
|
|
244
|
+
}
|
|
245
|
+
function ensureProfileState(config, profile = config.activeProfile) {
|
|
246
|
+
config.profiles[profile] ??= { lastSyncedVersion: 0 };
|
|
247
|
+
return config.profiles[profile];
|
|
248
|
+
}
|
|
249
|
+
function createConfig(input2) {
|
|
250
|
+
const codexHome = expandHome(input2.codexHome ?? defaultCodexHome());
|
|
251
|
+
const authFile = expandHome(input2.authFile ?? path.join(codexHome, "auth.json"));
|
|
252
|
+
return {
|
|
253
|
+
apiUrl: input2.apiUrl.replace(/\/+$/, ""),
|
|
254
|
+
deviceId: input2.deviceId ?? `${os.hostname()}-${randomUUID().slice(0, 8)}`,
|
|
255
|
+
activeProfile: input2.profile,
|
|
256
|
+
codexHome,
|
|
257
|
+
authFile,
|
|
258
|
+
profiles: {
|
|
259
|
+
[input2.profile]: { lastSyncedVersion: 0 }
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
async function readCodexCredentialStore(codexHome) {
|
|
264
|
+
const configToml = path.join(codexHome, "config.toml");
|
|
265
|
+
if (!existsSync(configToml)) {
|
|
266
|
+
return "unknown";
|
|
267
|
+
}
|
|
268
|
+
try {
|
|
269
|
+
const parsed = parse(await readFile(configToml, "utf8"));
|
|
270
|
+
const value = parsed.cli_auth_credentials_store;
|
|
271
|
+
if (value === "file" || value === "keyring" || value === "auto") {
|
|
272
|
+
return value;
|
|
273
|
+
}
|
|
274
|
+
return "unknown";
|
|
275
|
+
} catch {
|
|
276
|
+
return "unknown";
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// src/prompts.ts
|
|
281
|
+
import { input, password } from "@inquirer/prompts";
|
|
282
|
+
async function getApiToken(options) {
|
|
283
|
+
if (options.token) {
|
|
284
|
+
return options.token;
|
|
285
|
+
}
|
|
286
|
+
if (process.env.CODEX_AUTH_SYNC_API_TOKEN) {
|
|
287
|
+
return process.env.CODEX_AUTH_SYNC_API_TOKEN;
|
|
288
|
+
}
|
|
289
|
+
if (options.existingToken) {
|
|
290
|
+
return options.existingToken;
|
|
291
|
+
}
|
|
292
|
+
return password({ message: "API token" });
|
|
293
|
+
}
|
|
294
|
+
async function getPassphrase(message = "Encryption passphrase") {
|
|
295
|
+
if (process.env.CODEX_AUTH_SYNC_PASSPHRASE) {
|
|
296
|
+
return process.env.CODEX_AUTH_SYNC_PASSPHRASE;
|
|
297
|
+
}
|
|
298
|
+
return password({ message, mask: "*" });
|
|
299
|
+
}
|
|
300
|
+
async function getWorkerUrl(value) {
|
|
301
|
+
if (value) {
|
|
302
|
+
return value;
|
|
303
|
+
}
|
|
304
|
+
return input({ message: "Worker URL" });
|
|
305
|
+
}
|
|
306
|
+
async function saveApiToken(secretStore, token) {
|
|
307
|
+
await secretStore.set("api-token", token);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// src/secrets.ts
|
|
311
|
+
import { mkdir as mkdir2, readFile as readFile2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
312
|
+
import path2 from "path";
|
|
313
|
+
var SERVICE = "codex-auth-sync";
|
|
314
|
+
var SecretStore = class {
|
|
315
|
+
async get(name) {
|
|
316
|
+
const keytar = await loadKeytar();
|
|
317
|
+
if (keytar) {
|
|
318
|
+
return keytar.getPassword(SERVICE, name);
|
|
319
|
+
}
|
|
320
|
+
const data = await readFallbackSecrets();
|
|
321
|
+
return data[name] ?? null;
|
|
322
|
+
}
|
|
323
|
+
async set(name, value) {
|
|
324
|
+
const keytar = await loadKeytar();
|
|
325
|
+
if (keytar) {
|
|
326
|
+
await keytar.setPassword(SERVICE, name, value);
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
assertFallbackAllowed();
|
|
330
|
+
const data = await readFallbackSecrets();
|
|
331
|
+
data[name] = value;
|
|
332
|
+
await writeFallbackSecrets(data);
|
|
333
|
+
}
|
|
334
|
+
async delete(name) {
|
|
335
|
+
const keytar = await loadKeytar();
|
|
336
|
+
if (keytar) {
|
|
337
|
+
await keytar.deletePassword(SERVICE, name);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
const data = await readFallbackSecrets();
|
|
341
|
+
delete data[name];
|
|
342
|
+
await writeFallbackSecrets(data);
|
|
343
|
+
}
|
|
344
|
+
async deleteAllKnown(profileNames) {
|
|
345
|
+
await this.delete("api-token");
|
|
346
|
+
for (const profile of profileNames) {
|
|
347
|
+
await this.delete(profileKeySecretName(profile));
|
|
348
|
+
}
|
|
349
|
+
if (!await loadKeytar()) {
|
|
350
|
+
await rm2(fallbackSecretsPath(), { force: true });
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
function profileKeySecretName(profile) {
|
|
355
|
+
return `profile-key:${profile}`;
|
|
356
|
+
}
|
|
357
|
+
async function loadKeytar() {
|
|
358
|
+
if (process.env.CODEX_AUTH_SYNC_DISABLE_KEYCHAIN === "1") {
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
try {
|
|
362
|
+
return await import("keytar");
|
|
363
|
+
} catch {
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
function fallbackSecretsPath() {
|
|
368
|
+
return path2.join(defaultConfigDir(), "secrets.local.json");
|
|
369
|
+
}
|
|
370
|
+
function assertFallbackAllowed() {
|
|
371
|
+
if (process.env.CODEX_AUTH_SYNC_ALLOW_INSECURE_SECRETS === "1" || process.env.NODE_ENV === "test") {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
throw new Error(
|
|
375
|
+
"No OS keychain module is available. Install keytar support or set CODEX_AUTH_SYNC_ALLOW_INSECURE_SECRETS=1 for a local plaintext fallback."
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
async function readFallbackSecrets() {
|
|
379
|
+
try {
|
|
380
|
+
return JSON.parse(await readFile2(fallbackSecretsPath(), "utf8"));
|
|
381
|
+
} catch (error) {
|
|
382
|
+
if (error.code === "ENOENT") {
|
|
383
|
+
return {};
|
|
384
|
+
}
|
|
385
|
+
throw error;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
async function writeFallbackSecrets(data) {
|
|
389
|
+
assertFallbackAllowed();
|
|
390
|
+
await mkdir2(defaultConfigDir(), { recursive: true, mode: 448 });
|
|
391
|
+
await writeFile2(fallbackSecretsPath(), `${JSON.stringify(data, null, 2)}
|
|
392
|
+
`, { mode: 384 });
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// src/profile-key.ts
|
|
396
|
+
async function loadCachedProfileKey(secretStore, profile) {
|
|
397
|
+
const cached = await secretStore.get(profileKeySecretName(profile));
|
|
398
|
+
return cached ? decodeProfileKey(cached) : null;
|
|
399
|
+
}
|
|
400
|
+
async function getOrUnlockProfileKey(input2) {
|
|
401
|
+
const cached = await loadCachedProfileKey(input2.secretStore, input2.profile);
|
|
402
|
+
const meta = input2.meta ?? await input2.api.request(`/api/v1/profiles/${input2.profile}/meta`);
|
|
403
|
+
if (cached) {
|
|
404
|
+
return { key: cached, meta };
|
|
405
|
+
}
|
|
406
|
+
const passphrase = await getPassphrase(`Passphrase for ${input2.profile}`);
|
|
407
|
+
const key = await unwrapProfileKey(passphrase, meta.keyEnvelope);
|
|
408
|
+
await input2.secretStore.set(profileKeySecretName(input2.profile), encodeProfileKey(key));
|
|
409
|
+
return { key, meta };
|
|
410
|
+
}
|
|
411
|
+
async function ensureProfile(input2) {
|
|
412
|
+
try {
|
|
413
|
+
const meta2 = await input2.api.request(`/api/v1/profiles/${input2.profile}/meta`);
|
|
414
|
+
const { key: key2 } = await getOrUnlockProfileKey({ ...input2, meta: meta2 });
|
|
415
|
+
return { key: key2, meta: meta2, created: false };
|
|
416
|
+
} catch (error) {
|
|
417
|
+
if (!isNotFound(error) || !input2.createIfMissing) {
|
|
418
|
+
throw error;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
const passphrase = await getPassphrase(`Create passphrase for ${input2.profile}`);
|
|
422
|
+
const key = generateProfileKey();
|
|
423
|
+
const keyEnvelope = await wrapProfileKey(input2.profile, passphrase, key);
|
|
424
|
+
const meta = await input2.api.request("/api/v1/profiles", {
|
|
425
|
+
method: "POST",
|
|
426
|
+
body: JSON.stringify({ profile: input2.profile, keyEnvelope })
|
|
427
|
+
});
|
|
428
|
+
await input2.secretStore.set(profileKeySecretName(input2.profile), encodeProfileKey(key));
|
|
429
|
+
return { key, meta, created: true };
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// src/local-auth.ts
|
|
433
|
+
import { mkdir as mkdir3, readFile as readFile3, rename, writeFile as writeFile3 } from "fs/promises";
|
|
434
|
+
import { existsSync as existsSync2 } from "fs";
|
|
435
|
+
import path3 from "path";
|
|
436
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
437
|
+
async function readAuthBytes(authFile) {
|
|
438
|
+
try {
|
|
439
|
+
return new Uint8Array(await readFile3(authFile));
|
|
440
|
+
} catch (error) {
|
|
441
|
+
if (error.code === "ENOENT") {
|
|
442
|
+
return null;
|
|
443
|
+
}
|
|
444
|
+
throw error;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
async function hashAuthFile(authFile) {
|
|
448
|
+
const bytes = await readAuthBytes(authFile);
|
|
449
|
+
return bytes ? sha256Hex(bytes) : void 0;
|
|
450
|
+
}
|
|
451
|
+
async function writeAuthBytesAtomic(authFile, bytes) {
|
|
452
|
+
await mkdir3(path3.dirname(authFile), { recursive: true, mode: 448 });
|
|
453
|
+
const tmpFile = path3.join(path3.dirname(authFile), `.authsync-${randomUUID2()}.tmp`);
|
|
454
|
+
await writeFile3(tmpFile, bytes, { mode: 384 });
|
|
455
|
+
await rename(tmpFile, authFile);
|
|
456
|
+
}
|
|
457
|
+
async function writeConflictCopy(authFile, bytes) {
|
|
458
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
459
|
+
const conflictPath = `${authFile}.conflict.${stamp}.json`;
|
|
460
|
+
await writeFile3(conflictPath, bytes, { mode: 384 });
|
|
461
|
+
return conflictPath;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// src/sync.ts
|
|
465
|
+
function decideSync(input2) {
|
|
466
|
+
if (input2.mode === "upload") {
|
|
467
|
+
return input2.localExists ? "upload" : "missing";
|
|
468
|
+
}
|
|
469
|
+
if (input2.mode === "download") {
|
|
470
|
+
return input2.remoteVersion > 0 ? "download" : "missing";
|
|
471
|
+
}
|
|
472
|
+
const remoteChanged = input2.remoteVersion !== input2.lastSyncedVersion;
|
|
473
|
+
if (input2.localChanged && remoteChanged) {
|
|
474
|
+
return "conflict";
|
|
475
|
+
}
|
|
476
|
+
if (input2.localChanged) {
|
|
477
|
+
return "upload";
|
|
478
|
+
}
|
|
479
|
+
if (remoteChanged && input2.remoteVersion > 0) {
|
|
480
|
+
return "download";
|
|
481
|
+
}
|
|
482
|
+
return "clean";
|
|
483
|
+
}
|
|
484
|
+
async function syncOnce(input2) {
|
|
485
|
+
const mode = input2.mode ?? "auto";
|
|
486
|
+
const api = new ApiClient(input2.config.apiUrl, input2.token);
|
|
487
|
+
const profile = input2.config.activeProfile;
|
|
488
|
+
const state = ensureProfileState(input2.config, profile);
|
|
489
|
+
const { meta, key } = await getOrUnlockProfileKey({ api, secretStore: input2.secretStore, profile });
|
|
490
|
+
const localBytes = await readAuthBytes(input2.config.authFile);
|
|
491
|
+
const localHash = localBytes ? await sha256Hex(localBytes) : void 0;
|
|
492
|
+
const localChanged = Boolean(localHash && localHash !== state.lastPlainHash);
|
|
493
|
+
const decision = decideSync({
|
|
494
|
+
localExists: Boolean(localBytes),
|
|
495
|
+
localChanged,
|
|
496
|
+
remoteVersion: meta.currentVersion,
|
|
497
|
+
lastSyncedVersion: state.lastSyncedVersion,
|
|
498
|
+
mode
|
|
499
|
+
});
|
|
500
|
+
if (decision === "upload" && localBytes) {
|
|
501
|
+
await uploadLocal({ api, config: input2.config, meta, key, localBytes });
|
|
502
|
+
await saveConfig(input2.config);
|
|
503
|
+
if (!input2.quiet) {
|
|
504
|
+
console.log(`uploaded ${profile} v${input2.config.profiles[profile]?.lastSyncedVersion}`);
|
|
505
|
+
}
|
|
506
|
+
return decision;
|
|
507
|
+
}
|
|
508
|
+
if (decision === "download") {
|
|
509
|
+
if (localBytes && localChanged) {
|
|
510
|
+
const conflictPath = await writeConflictCopy(input2.config.authFile, localBytes);
|
|
511
|
+
if (!input2.quiet) {
|
|
512
|
+
console.log(`saved local conflict ${conflictPath}`);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
await downloadRemote({ api, config: input2.config, meta, key });
|
|
516
|
+
await saveConfig(input2.config);
|
|
517
|
+
if (!input2.quiet) {
|
|
518
|
+
console.log(`downloaded ${profile} v${meta.currentVersion}`);
|
|
519
|
+
}
|
|
520
|
+
return decision;
|
|
521
|
+
}
|
|
522
|
+
if (decision === "conflict") {
|
|
523
|
+
state.conflict = localHash ? {
|
|
524
|
+
detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
525
|
+
localHash,
|
|
526
|
+
remoteVersion: meta.currentVersion
|
|
527
|
+
} : {
|
|
528
|
+
detectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
529
|
+
remoteVersion: meta.currentVersion
|
|
530
|
+
};
|
|
531
|
+
await saveConfig(input2.config);
|
|
532
|
+
if (!input2.quiet) {
|
|
533
|
+
console.log(`conflict ${profile}: run sync --upload or sync --download`);
|
|
534
|
+
}
|
|
535
|
+
return decision;
|
|
536
|
+
}
|
|
537
|
+
if (decision === "missing" && !input2.quiet) {
|
|
538
|
+
console.log("nothing to sync");
|
|
539
|
+
return decision;
|
|
540
|
+
}
|
|
541
|
+
if (!input2.quiet) {
|
|
542
|
+
console.log(`${profile} already synced`);
|
|
543
|
+
}
|
|
544
|
+
return decision;
|
|
545
|
+
}
|
|
546
|
+
async function uploadLocal(input2) {
|
|
547
|
+
const nextVersion = input2.meta.currentVersion + 1;
|
|
548
|
+
const blob = await encryptAuthBytes({
|
|
549
|
+
profile: input2.config.activeProfile,
|
|
550
|
+
version: nextVersion,
|
|
551
|
+
keyVersion: input2.meta.keyEnvelope.keyVersion,
|
|
552
|
+
updatedByDevice: input2.config.deviceId,
|
|
553
|
+
profileKey: input2.key,
|
|
554
|
+
plaintext: input2.localBytes
|
|
555
|
+
});
|
|
556
|
+
const response = await input2.api.request(
|
|
557
|
+
`/api/v1/profiles/${input2.config.activeProfile}/versions`,
|
|
558
|
+
{
|
|
559
|
+
method: "PUT",
|
|
560
|
+
body: JSON.stringify({ baseVersion: input2.meta.currentVersion, blob })
|
|
561
|
+
}
|
|
562
|
+
);
|
|
563
|
+
const state = ensureProfileState(input2.config);
|
|
564
|
+
state.lastSyncedVersion = response.meta.currentVersion;
|
|
565
|
+
state.lastPlainHash = await sha256Hex(input2.localBytes);
|
|
566
|
+
if (response.meta.currentCiphertextHash) {
|
|
567
|
+
state.lastCiphertextHash = response.meta.currentCiphertextHash;
|
|
568
|
+
} else {
|
|
569
|
+
delete state.lastCiphertextHash;
|
|
570
|
+
}
|
|
571
|
+
state.keyVersion = response.meta.keyEnvelope.keyVersion;
|
|
572
|
+
delete state.conflict;
|
|
573
|
+
}
|
|
574
|
+
async function downloadRemote(input2) {
|
|
575
|
+
if (input2.meta.currentVersion < 1) {
|
|
576
|
+
throw new Error("Remote profile has no versions.");
|
|
577
|
+
}
|
|
578
|
+
const blob = await input2.api.request(
|
|
579
|
+
`/api/v1/profiles/${input2.config.activeProfile}/versions/${input2.meta.currentVersion}`
|
|
580
|
+
);
|
|
581
|
+
const plaintext = await decryptAuthBytes(input2.key, blob);
|
|
582
|
+
await writeAuthBytesAtomic(input2.config.authFile, plaintext);
|
|
583
|
+
const state = ensureProfileState(input2.config);
|
|
584
|
+
state.lastSyncedVersion = input2.meta.currentVersion;
|
|
585
|
+
const plainHash = await hashAuthFile(input2.config.authFile);
|
|
586
|
+
if (plainHash) {
|
|
587
|
+
state.lastPlainHash = plainHash;
|
|
588
|
+
} else {
|
|
589
|
+
delete state.lastPlainHash;
|
|
590
|
+
}
|
|
591
|
+
if (input2.meta.currentCiphertextHash) {
|
|
592
|
+
state.lastCiphertextHash = input2.meta.currentCiphertextHash;
|
|
593
|
+
} else {
|
|
594
|
+
delete state.lastCiphertextHash;
|
|
595
|
+
}
|
|
596
|
+
state.keyVersion = input2.meta.keyEnvelope.keyVersion;
|
|
597
|
+
delete state.conflict;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// src/commands.ts
|
|
601
|
+
async function initCommand(options) {
|
|
602
|
+
const secretStore = new SecretStore();
|
|
603
|
+
const existingToken = await secretStore.get("api-token");
|
|
604
|
+
const apiUrl = await getWorkerUrl(options.url);
|
|
605
|
+
const token = await getApiToken({ token: options.token, existingToken });
|
|
606
|
+
const config = createConfig({
|
|
607
|
+
apiUrl,
|
|
608
|
+
profile: options.profile,
|
|
609
|
+
codexHome: options.codexHome,
|
|
610
|
+
authFile: options.authFile
|
|
611
|
+
});
|
|
612
|
+
const api = new ApiClient(config.apiUrl, token);
|
|
613
|
+
await saveApiToken(secretStore, token);
|
|
614
|
+
await api.request("/api/v1/devices/register", {
|
|
615
|
+
method: "POST",
|
|
616
|
+
body: JSON.stringify({ deviceId: config.deviceId, activeProfile: config.activeProfile })
|
|
617
|
+
});
|
|
618
|
+
const ensured = await ensureProfile({ api, secretStore, profile: config.activeProfile, createIfMissing: true });
|
|
619
|
+
ensureProfileState(config).keyVersion = ensured.meta.keyEnvelope.keyVersion;
|
|
620
|
+
const store = await readCodexCredentialStore(config.codexHome);
|
|
621
|
+
if ((store === "keyring" || store === "auto") && !existsSync3(config.authFile)) {
|
|
622
|
+
console.log(`warning: no auth.json found at ${config.authFile}; Codex may be using ${store} credential storage`);
|
|
623
|
+
}
|
|
624
|
+
await saveConfig(config);
|
|
625
|
+
console.log(`initialized ${config.activeProfile}`);
|
|
626
|
+
console.log("next: codex-auth-sync sync");
|
|
627
|
+
}
|
|
628
|
+
async function statusCommand() {
|
|
629
|
+
const { config, token } = await requireConfigAndToken();
|
|
630
|
+
const api = new ApiClient(config.apiUrl, token);
|
|
631
|
+
const meta = await api.request(`/api/v1/profiles/${config.activeProfile}/meta`);
|
|
632
|
+
const state = ensureProfileState(config);
|
|
633
|
+
const localHash = await hashAuthFile(config.authFile);
|
|
634
|
+
const localState = localHash ? localHash === state.lastPlainHash ? "clean" : "changed" : "missing";
|
|
635
|
+
const remoteState = meta.currentVersion === state.lastSyncedVersion ? "clean" : "changed";
|
|
636
|
+
console.log(`profile: ${config.activeProfile}`);
|
|
637
|
+
console.log(`device: ${config.deviceId}`);
|
|
638
|
+
console.log(`auth: ${config.authFile}`);
|
|
639
|
+
console.log(`local: ${localState}`);
|
|
640
|
+
console.log(`remote: v${meta.currentVersion} ${remoteState}`);
|
|
641
|
+
if (state.conflict) {
|
|
642
|
+
console.log(`conflict: remote v${state.conflict.remoteVersion}`);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
async function profilesCommand() {
|
|
646
|
+
const { config, token } = await requireConfigAndToken();
|
|
647
|
+
const api = new ApiClient(config.apiUrl, token);
|
|
648
|
+
const response = await api.request(
|
|
649
|
+
"/api/v1/profiles"
|
|
650
|
+
);
|
|
651
|
+
if (response.profiles.length === 0) {
|
|
652
|
+
console.log("no profiles");
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
for (const profile of response.profiles) {
|
|
656
|
+
const active = profile.profile === config.activeProfile ? "*" : " ";
|
|
657
|
+
console.log(`${active} ${profile.profile} v${profile.currentVersion}`);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
async function syncCommand(options) {
|
|
661
|
+
const { config, token, secretStore } = await requireConfigAndToken();
|
|
662
|
+
const mode = options.upload ? "upload" : options.download ? "download" : "auto";
|
|
663
|
+
await syncOnce({ config, token, secretStore, mode });
|
|
664
|
+
}
|
|
665
|
+
async function switchCommand(profile, options) {
|
|
666
|
+
const { config, token, secretStore } = await requireConfigAndToken();
|
|
667
|
+
const currentState = ensureProfileState(config);
|
|
668
|
+
const localHash = await hashAuthFile(config.authFile);
|
|
669
|
+
if (localHash && localHash !== currentState.lastPlainHash) {
|
|
670
|
+
console.log(`local changes in ${config.activeProfile}; run codex-auth-sync sync first`);
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
const api = new ApiClient(config.apiUrl, token);
|
|
674
|
+
await ensureProfile({ api, secretStore, profile, createIfMissing: Boolean(options.create) });
|
|
675
|
+
config.activeProfile = profile;
|
|
676
|
+
ensureProfileState(config, profile);
|
|
677
|
+
await api.request("/api/v1/devices/register", {
|
|
678
|
+
method: "POST",
|
|
679
|
+
body: JSON.stringify({ deviceId: config.deviceId, activeProfile: profile })
|
|
680
|
+
});
|
|
681
|
+
await saveConfig(config);
|
|
682
|
+
console.log(`switched ${profile}`);
|
|
683
|
+
await syncOnce({ config, token, secretStore, mode: "auto" });
|
|
684
|
+
}
|
|
685
|
+
async function logoutCommand(options) {
|
|
686
|
+
const config = await loadConfig();
|
|
687
|
+
const secretStore = new SecretStore();
|
|
688
|
+
await secretStore.deleteAllKnown(config ? Object.keys(config.profiles) : []);
|
|
689
|
+
if (options.removeConfig) {
|
|
690
|
+
await removeConfig();
|
|
691
|
+
}
|
|
692
|
+
console.log(options.removeConfig ? "logged out and removed config" : "logged out");
|
|
693
|
+
}
|
|
694
|
+
async function requireConfigAndToken() {
|
|
695
|
+
const config = await loadConfig();
|
|
696
|
+
if (!config) {
|
|
697
|
+
throw new Error("Not initialized. Run codex-auth-sync init first.");
|
|
698
|
+
}
|
|
699
|
+
const secretStore = new SecretStore();
|
|
700
|
+
const token = await secretStore.get("api-token");
|
|
701
|
+
if (!token) {
|
|
702
|
+
throw new Error("Missing API token. Run codex-auth-sync init again.");
|
|
703
|
+
}
|
|
704
|
+
return { config, token, secretStore };
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// src/watch.ts
|
|
708
|
+
import chokidar from "chokidar";
|
|
709
|
+
import WebSocket from "ws";
|
|
710
|
+
async function watchCommand() {
|
|
711
|
+
const config = await loadConfig();
|
|
712
|
+
if (!config) {
|
|
713
|
+
throw new Error("Not initialized. Run codex-auth-sync init first.");
|
|
714
|
+
}
|
|
715
|
+
const secretStore = new SecretStore();
|
|
716
|
+
const token = await secretStore.get("api-token");
|
|
717
|
+
if (!token) {
|
|
718
|
+
throw new Error("Missing API token. Run codex-auth-sync init again.");
|
|
719
|
+
}
|
|
720
|
+
const api = new ApiClient(config.apiUrl, token);
|
|
721
|
+
await syncOnce({ config, token, secretStore });
|
|
722
|
+
let timer;
|
|
723
|
+
const scheduleSync = (mode = "auto") => {
|
|
724
|
+
if (timer) {
|
|
725
|
+
clearTimeout(timer);
|
|
726
|
+
}
|
|
727
|
+
timer = setTimeout(() => {
|
|
728
|
+
syncOnce({ config, token, secretStore, mode }).catch((error) => {
|
|
729
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
730
|
+
});
|
|
731
|
+
}, 1500);
|
|
732
|
+
};
|
|
733
|
+
const watcher = chokidar.watch(config.authFile, {
|
|
734
|
+
ignoreInitial: true,
|
|
735
|
+
awaitWriteFinish: {
|
|
736
|
+
stabilityThreshold: 1e3,
|
|
737
|
+
pollInterval: 100
|
|
738
|
+
}
|
|
739
|
+
});
|
|
740
|
+
watcher.on("add", () => scheduleSync());
|
|
741
|
+
watcher.on("change", () => scheduleSync());
|
|
742
|
+
let socket = null;
|
|
743
|
+
let reconnectTimer;
|
|
744
|
+
const connect = () => {
|
|
745
|
+
socket = new WebSocket(api.websocketUrl(config.deviceId), {
|
|
746
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
747
|
+
});
|
|
748
|
+
socket.on("message", (data) => {
|
|
749
|
+
const message = JSON.parse(data.toString());
|
|
750
|
+
if (message.type === "profile_updated" && message.profile === config.activeProfile) {
|
|
751
|
+
scheduleSync("download");
|
|
752
|
+
}
|
|
753
|
+
if (message.type === "catch_up") {
|
|
754
|
+
const relevant = message.pending.some((event) => event.profile === config.activeProfile);
|
|
755
|
+
if (relevant) {
|
|
756
|
+
scheduleSync();
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
});
|
|
760
|
+
socket.on("close", () => {
|
|
761
|
+
reconnectTimer = setTimeout(connect, 5e3);
|
|
762
|
+
});
|
|
763
|
+
socket.on("error", () => {
|
|
764
|
+
socket?.close();
|
|
765
|
+
});
|
|
766
|
+
};
|
|
767
|
+
connect();
|
|
768
|
+
console.log(`watching ${config.activeProfile}`);
|
|
769
|
+
const stop = async () => {
|
|
770
|
+
if (reconnectTimer) {
|
|
771
|
+
clearTimeout(reconnectTimer);
|
|
772
|
+
}
|
|
773
|
+
if (timer) {
|
|
774
|
+
clearTimeout(timer);
|
|
775
|
+
}
|
|
776
|
+
socket?.close();
|
|
777
|
+
await watcher.close();
|
|
778
|
+
process.exit(0);
|
|
779
|
+
};
|
|
780
|
+
process.once("SIGINT", () => void stop());
|
|
781
|
+
process.once("SIGTERM", () => void stop());
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// src/index.ts
|
|
785
|
+
var program = new Command();
|
|
786
|
+
program.name("codex-auth-sync").description("Encrypted sync for Codex auth.json profiles").version("0.1.0");
|
|
787
|
+
program.command("init").requiredOption("--profile <name>").option("--url <url>").option("--codex-home <path>").option("--auth-file <path>").option("--token <token>").action(wrap(initCommand));
|
|
788
|
+
program.command("watch").action(wrap(watchCommand));
|
|
789
|
+
program.command("switch <profile>").option("--create").action(wrap(switchCommand));
|
|
790
|
+
program.command("sync").option("--upload").option("--download").action(wrap(syncCommand));
|
|
791
|
+
program.command("status").action(wrap(statusCommand));
|
|
792
|
+
program.command("profiles").action(wrap(profilesCommand));
|
|
793
|
+
program.command("logout").option("--remove-config").action(wrap(logoutCommand));
|
|
794
|
+
await program.parseAsync(process.argv);
|
|
795
|
+
function wrap(handler) {
|
|
796
|
+
return async (...args) => {
|
|
797
|
+
try {
|
|
798
|
+
await handler(...args);
|
|
799
|
+
} catch (error) {
|
|
800
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
801
|
+
process.exitCode = 1;
|
|
802
|
+
}
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/commands.ts","../../shared/src/crypto.ts","../../shared/src/validation.ts","../src/api.ts","../src/config.ts","../src/prompts.ts","../src/secrets.ts","../src/profile-key.ts","../src/local-auth.ts","../src/sync.ts","../src/watch.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { Command } from \"commander\";\nimport {\n initCommand,\n logoutCommand,\n profilesCommand,\n statusCommand,\n switchCommand,\n syncCommand,\n} from \"./commands.js\";\nimport { watchCommand } from \"./watch.js\";\n\nconst program = new Command();\n\nprogram.name(\"codex-auth-sync\").description(\"Encrypted sync for Codex auth.json profiles\").version(\"0.1.0\");\n\nprogram\n .command(\"init\")\n .requiredOption(\"--profile <name>\")\n .option(\"--url <url>\")\n .option(\"--codex-home <path>\")\n .option(\"--auth-file <path>\")\n .option(\"--token <token>\")\n .action(wrap(initCommand));\n\nprogram.command(\"watch\").action(wrap(watchCommand));\n\nprogram.command(\"switch <profile>\").option(\"--create\").action(wrap(switchCommand));\n\nprogram.command(\"sync\").option(\"--upload\").option(\"--download\").action(wrap(syncCommand));\n\nprogram.command(\"status\").action(wrap(statusCommand));\n\nprogram.command(\"profiles\").action(wrap(profilesCommand));\n\nprogram.command(\"logout\").option(\"--remove-config\").action(wrap(logoutCommand));\n\nawait program.parseAsync(process.argv);\n\nfunction wrap<T extends unknown[]>(handler: (...args: T) => Promise<void>) {\n return async (...args: T) => {\n try {\n await handler(...args);\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error));\n process.exitCode = 1;\n }\n };\n}\n","import { existsSync } from \"node:fs\";\nimport { ApiClient } from \"./api.js\";\nimport {\n createConfig,\n ensureProfileState,\n loadConfig,\n readCodexCredentialStore,\n removeConfig,\n saveConfig,\n type LocalConfig,\n} from \"./config.js\";\nimport { getApiToken, getWorkerUrl, saveApiToken } from \"./prompts.js\";\nimport { ensureProfile } from \"./profile-key.js\";\nimport { SecretStore } from \"./secrets.js\";\nimport { hashAuthFile } from \"./local-auth.js\";\nimport { syncOnce, type SyncMode } from \"./sync.js\";\n\nexport async function initCommand(options: {\n url?: string;\n profile: string;\n codexHome?: string;\n authFile?: string;\n token?: string;\n}): Promise<void> {\n const secretStore = new SecretStore();\n const existingToken = await secretStore.get(\"api-token\");\n const apiUrl = await getWorkerUrl(options.url);\n const token = await getApiToken({ token: options.token, existingToken });\n const config = createConfig({\n apiUrl,\n profile: options.profile,\n codexHome: options.codexHome,\n authFile: options.authFile,\n });\n const api = new ApiClient(config.apiUrl, token);\n\n await saveApiToken(secretStore, token);\n await api.request(\"/api/v1/devices/register\", {\n method: \"POST\",\n body: JSON.stringify({ deviceId: config.deviceId, activeProfile: config.activeProfile }),\n });\n const ensured = await ensureProfile({ api, secretStore, profile: config.activeProfile, createIfMissing: true });\n ensureProfileState(config).keyVersion = ensured.meta.keyEnvelope.keyVersion;\n\n const store = await readCodexCredentialStore(config.codexHome);\n if ((store === \"keyring\" || store === \"auto\") && !existsSync(config.authFile)) {\n console.log(`warning: no auth.json found at ${config.authFile}; Codex may be using ${store} credential storage`);\n }\n\n await saveConfig(config);\n console.log(`initialized ${config.activeProfile}`);\n console.log(\"next: codex-auth-sync sync\");\n}\n\nexport async function statusCommand(): Promise<void> {\n const { config, token } = await requireConfigAndToken();\n const api = new ApiClient(config.apiUrl, token);\n const meta = await api.request<{ currentVersion: number }>(`/api/v1/profiles/${config.activeProfile}/meta`);\n const state = ensureProfileState(config);\n const localHash = await hashAuthFile(config.authFile);\n const localState = localHash ? (localHash === state.lastPlainHash ? \"clean\" : \"changed\") : \"missing\";\n const remoteState = meta.currentVersion === state.lastSyncedVersion ? \"clean\" : \"changed\";\n\n console.log(`profile: ${config.activeProfile}`);\n console.log(`device: ${config.deviceId}`);\n console.log(`auth: ${config.authFile}`);\n console.log(`local: ${localState}`);\n console.log(`remote: v${meta.currentVersion} ${remoteState}`);\n if (state.conflict) {\n console.log(`conflict: remote v${state.conflict.remoteVersion}`);\n }\n}\n\nexport async function profilesCommand(): Promise<void> {\n const { config, token } = await requireConfigAndToken();\n const api = new ApiClient(config.apiUrl, token);\n const response = await api.request<{ profiles: Array<{ profile: string; currentVersion: number; updatedAt?: string }> }>(\n \"/api/v1/profiles\",\n );\n\n if (response.profiles.length === 0) {\n console.log(\"no profiles\");\n return;\n }\n\n for (const profile of response.profiles) {\n const active = profile.profile === config.activeProfile ? \"*\" : \" \";\n console.log(`${active} ${profile.profile} v${profile.currentVersion}`);\n }\n}\n\nexport async function syncCommand(options: { upload?: boolean; download?: boolean }): Promise<void> {\n const { config, token, secretStore } = await requireConfigAndToken();\n const mode: SyncMode = options.upload ? \"upload\" : options.download ? \"download\" : \"auto\";\n await syncOnce({ config, token, secretStore, mode });\n}\n\nexport async function switchCommand(profile: string, options: { create?: boolean }): Promise<void> {\n const { config, token, secretStore } = await requireConfigAndToken();\n const currentState = ensureProfileState(config);\n const localHash = await hashAuthFile(config.authFile);\n if (localHash && localHash !== currentState.lastPlainHash) {\n console.log(`local changes in ${config.activeProfile}; run codex-auth-sync sync first`);\n return;\n }\n\n const api = new ApiClient(config.apiUrl, token);\n await ensureProfile({ api, secretStore, profile, createIfMissing: Boolean(options.create) });\n config.activeProfile = profile;\n ensureProfileState(config, profile);\n await api.request(\"/api/v1/devices/register\", {\n method: \"POST\",\n body: JSON.stringify({ deviceId: config.deviceId, activeProfile: profile }),\n });\n await saveConfig(config);\n console.log(`switched ${profile}`);\n await syncOnce({ config, token, secretStore, mode: \"auto\" });\n}\n\nexport async function logoutCommand(options: { removeConfig?: boolean }): Promise<void> {\n const config = await loadConfig();\n const secretStore = new SecretStore();\n await secretStore.deleteAllKnown(config ? Object.keys(config.profiles) : []);\n if (options.removeConfig) {\n await removeConfig();\n }\n console.log(options.removeConfig ? \"logged out and removed config\" : \"logged out\");\n}\n\nasync function requireConfigAndToken(): Promise<{\n config: LocalConfig;\n token: string;\n secretStore: SecretStore;\n}> {\n const config = await loadConfig();\n if (!config) {\n throw new Error(\"Not initialized. Run codex-auth-sync init first.\");\n }\n\n const secretStore = new SecretStore();\n const token = await secretStore.get(\"api-token\");\n if (!token) {\n throw new Error(\"Missing API token. Run codex-auth-sync init again.\");\n }\n\n return { config, token, secretStore };\n}\n","import type { EncryptedAuthBlob, ProfileKeyEnvelope } from \"./types.js\";\n\nconst AES_KEY_BITS = 256;\nconst AES_KEY_BYTES = AES_KEY_BITS / 8;\nconst DEFAULT_KDF_ITERATIONS = 310_000;\n\nexport function bytesToBase64(bytes: Uint8Array): string {\n let binary = \"\";\n for (let index = 0; index < bytes.length; index += 0x8000) {\n binary += String.fromCharCode(...bytes.slice(index, index + 0x8000));\n }\n\n if (typeof btoa === \"function\") {\n return btoa(binary);\n }\n\n return Buffer.from(bytes).toString(\"base64\");\n}\n\nexport function base64ToBytes(value: string): Uint8Array {\n if (typeof atob === \"function\") {\n const binary = atob(value);\n return Uint8Array.from(binary, (char) => char.charCodeAt(0));\n }\n\n return new Uint8Array(Buffer.from(value, \"base64\"));\n}\n\nexport function randomBytes(length: number): Uint8Array {\n const bytes = new Uint8Array(length);\n crypto.getRandomValues(bytes);\n return bytes;\n}\n\nfunction toArrayBuffer(bytes: Uint8Array): ArrayBuffer {\n return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer;\n}\n\nexport function bytesToHex(bytes: Uint8Array): string {\n return [...bytes].map((byte) => byte.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nexport async function sha256Hex(bytes: Uint8Array): Promise<string> {\n const digest = await crypto.subtle.digest(\"SHA-256\", toArrayBuffer(bytes));\n return bytesToHex(new Uint8Array(digest));\n}\n\nexport function generateProfileKey(): Uint8Array {\n return randomBytes(AES_KEY_BYTES);\n}\n\nexport function encodeProfileKey(key: Uint8Array): string {\n if (key.byteLength !== AES_KEY_BYTES) {\n throw new Error(\"Profile key must be 32 bytes.\");\n }\n return bytesToBase64(key);\n}\n\nexport function decodeProfileKey(value: string): Uint8Array {\n const key = base64ToBytes(value);\n if (key.byteLength !== AES_KEY_BYTES) {\n throw new Error(\"Profile key must be 32 bytes.\");\n }\n return key;\n}\n\nexport async function wrapProfileKey(\n profile: string,\n passphrase: string,\n rawProfileKey: Uint8Array,\n options: { keyVersion?: number; iterations?: number; now?: Date } = {},\n): Promise<ProfileKeyEnvelope> {\n const keyVersion = options.keyVersion ?? 1;\n const iterations = options.iterations ?? DEFAULT_KDF_ITERATIONS;\n const salt = randomBytes(16);\n const iv = randomBytes(12);\n const wrappingKey = await deriveWrappingKey(passphrase, salt, iterations);\n const wrappedKey = await crypto.subtle.encrypt(\n { name: \"AES-GCM\", iv: toArrayBuffer(iv) },\n wrappingKey,\n toArrayBuffer(rawProfileKey),\n );\n\n return {\n schemaVersion: 1,\n profile,\n keyVersion,\n kdf: {\n name: \"PBKDF2-SHA256\",\n iterations,\n salt: bytesToBase64(salt),\n },\n wrap: {\n algorithm: \"AES-256-GCM\",\n iv: bytesToBase64(iv),\n wrappedKey: bytesToBase64(new Uint8Array(wrappedKey)),\n },\n createdAt: (options.now ?? new Date()).toISOString(),\n };\n}\n\nexport async function unwrapProfileKey(passphrase: string, envelope: ProfileKeyEnvelope): Promise<Uint8Array> {\n if (envelope.schemaVersion !== 1 || envelope.wrap.algorithm !== \"AES-256-GCM\") {\n throw new Error(\"Unsupported profile key envelope.\");\n }\n\n const wrappingKey = await deriveWrappingKey(\n passphrase,\n base64ToBytes(envelope.kdf.salt),\n envelope.kdf.iterations,\n );\n\n const key = await crypto.subtle.decrypt(\n { name: \"AES-GCM\", iv: toArrayBuffer(base64ToBytes(envelope.wrap.iv)) },\n wrappingKey,\n toArrayBuffer(base64ToBytes(envelope.wrap.wrappedKey)),\n );\n\n const rawKey = new Uint8Array(key);\n if (rawKey.byteLength !== AES_KEY_BYTES) {\n throw new Error(\"Invalid profile key length.\");\n }\n return rawKey;\n}\n\nexport async function encryptAuthBytes(input: {\n profile: string;\n version: number;\n keyVersion: number;\n updatedByDevice: string;\n profileKey: Uint8Array;\n plaintext: Uint8Array;\n now?: Date;\n}): Promise<EncryptedAuthBlob> {\n const iv = randomBytes(12);\n const key = await importAesKey(input.profileKey, [\"encrypt\"]);\n const ciphertext = await crypto.subtle.encrypt(\n { name: \"AES-GCM\", iv: toArrayBuffer(iv) },\n key,\n toArrayBuffer(input.plaintext),\n );\n\n return {\n schemaVersion: 1,\n profile: input.profile,\n version: input.version,\n keyVersion: input.keyVersion,\n encryption: {\n algorithm: \"AES-256-GCM\",\n iv: bytesToBase64(iv),\n },\n ciphertext: bytesToBase64(new Uint8Array(ciphertext)),\n plaintextSize: input.plaintext.byteLength,\n createdAt: (input.now ?? new Date()).toISOString(),\n updatedByDevice: input.updatedByDevice,\n };\n}\n\nexport async function decryptAuthBytes(profileKey: Uint8Array, blob: EncryptedAuthBlob): Promise<Uint8Array> {\n if (blob.schemaVersion !== 1 || blob.encryption.algorithm !== \"AES-256-GCM\") {\n throw new Error(\"Unsupported encrypted auth blob.\");\n }\n\n const key = await importAesKey(profileKey, [\"decrypt\"]);\n const plaintext = await crypto.subtle.decrypt(\n { name: \"AES-GCM\", iv: toArrayBuffer(base64ToBytes(blob.encryption.iv)) },\n key,\n toArrayBuffer(base64ToBytes(blob.ciphertext)),\n );\n\n return new Uint8Array(plaintext);\n}\n\nasync function deriveWrappingKey(passphrase: string, salt: Uint8Array, iterations: number): Promise<CryptoKey> {\n if (!passphrase) {\n throw new Error(\"Passphrase is required.\");\n }\n\n const baseKey = await crypto.subtle.importKey(\n \"raw\",\n toArrayBuffer(new TextEncoder().encode(passphrase)),\n \"PBKDF2\",\n false,\n [\"deriveKey\"],\n );\n\n return crypto.subtle.deriveKey(\n {\n name: \"PBKDF2\",\n hash: \"SHA-256\",\n salt: toArrayBuffer(salt),\n iterations,\n },\n baseKey,\n { name: \"AES-GCM\", length: AES_KEY_BITS },\n false,\n [\"encrypt\", \"decrypt\"],\n );\n}\n\nasync function importAesKey(rawKey: Uint8Array, usages: KeyUsage[]): Promise<CryptoKey> {\n if (rawKey.byteLength !== AES_KEY_BYTES) {\n throw new Error(\"Profile key must be 32 bytes.\");\n }\n\n return crypto.subtle.importKey(\"raw\", toArrayBuffer(rawKey), { name: \"AES-GCM\", length: AES_KEY_BITS }, false, usages);\n}\n","export function assertProfileName(profile: string): void {\n if (!/^[A-Za-z0-9._-]{1,64}$/.test(profile)) {\n throw new Error(\"Profile names may only contain letters, numbers, dots, underscores, and dashes.\");\n }\n}\n\nexport function assertDeviceId(deviceId: string): void {\n if (!/^[A-Za-z0-9._:-]{1,128}$/.test(deviceId)) {\n throw new Error(\"Device IDs may only contain letters, numbers, dots, underscores, dashes, and colons.\");\n }\n}\n\nexport function isApiErrorBody(value: unknown): value is { error: { code: string; message: string } } {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const error = (value as { error?: unknown }).error;\n return Boolean(\n error &&\n typeof error === \"object\" &&\n typeof (error as { code?: unknown }).code === \"string\" &&\n typeof (error as { message?: unknown }).message === \"string\",\n );\n}\n","import { isApiErrorBody } from \"@codex-auth-sync/shared\";\n\nexport class ApiError extends Error {\n constructor(\n readonly status: number,\n readonly code: string,\n message: string,\n ) {\n super(message);\n }\n}\n\nexport class ApiClient {\n constructor(\n readonly baseUrl: string,\n private readonly token: string,\n ) {}\n\n async request<T>(path: string, init: RequestInit = {}): Promise<T> {\n const headers = new Headers(init.headers);\n headers.set(\"Authorization\", `Bearer ${this.token}`);\n\n if (init.body && !headers.has(\"Content-Type\")) {\n headers.set(\"Content-Type\", \"application/json\");\n }\n\n const response = await fetch(`${this.baseUrl}${path}`, {\n ...init,\n headers,\n });\n\n if (!response.ok) {\n let body: unknown = null;\n try {\n body = await response.json();\n } catch {\n body = null;\n }\n\n if (isApiErrorBody(body)) {\n throw new ApiError(response.status, body.error.code, body.error.message);\n }\n throw new ApiError(response.status, \"http_error\", `${response.status} ${response.statusText}`);\n }\n\n if (response.status === 204) {\n return undefined as T;\n }\n\n return response.json() as Promise<T>;\n }\n\n websocketUrl(deviceId: string): string {\n const url = new URL(`/api/v1/devices/${encodeURIComponent(deviceId)}/ws`, this.baseUrl);\n url.protocol = url.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n return url.toString();\n }\n}\n\nexport function isNotFound(error: unknown): boolean {\n return error instanceof ApiError && error.status === 404;\n}\n","import { mkdir, readFile, rm, writeFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { randomUUID } from \"node:crypto\";\nimport { parse } from \"smol-toml\";\n\nexport interface LocalProfileState {\n lastSyncedVersion: number;\n lastPlainHash?: string;\n lastCiphertextHash?: string;\n keyVersion?: number;\n conflict?: {\n detectedAt: string;\n localHash?: string;\n remoteVersion: number;\n };\n}\n\nexport interface LocalConfig {\n apiUrl: string;\n deviceId: string;\n activeProfile: string;\n codexHome: string;\n authFile: string;\n profiles: Record<string, LocalProfileState>;\n}\n\nexport function expandHome(value: string): string {\n if (value === \"~\") {\n return os.homedir();\n }\n if (value.startsWith(\"~/\")) {\n return path.join(os.homedir(), value.slice(2));\n }\n return value;\n}\n\nexport function defaultConfigDir(): string {\n return process.env.CODEX_AUTH_SYNC_CONFIG_DIR ?? path.join(os.homedir(), \".config\", \"codex-auth-sync\");\n}\n\nexport function configPath(): string {\n return path.join(defaultConfigDir(), \"config.json\");\n}\n\nexport function defaultCodexHome(): string {\n return path.join(os.homedir(), \".codex\");\n}\n\nexport async function loadConfig(): Promise<LocalConfig | null> {\n try {\n return JSON.parse(await readFile(configPath(), \"utf8\")) as LocalConfig;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return null;\n }\n throw error;\n }\n}\n\nexport async function saveConfig(config: LocalConfig): Promise<void> {\n await mkdir(defaultConfigDir(), { recursive: true, mode: 0o700 });\n await writeFile(configPath(), `${JSON.stringify(config, null, 2)}\\n`, { mode: 0o600 });\n}\n\nexport async function removeConfig(): Promise<void> {\n await rm(configPath(), { force: true });\n}\n\nexport function ensureProfileState(config: LocalConfig, profile = config.activeProfile): LocalProfileState {\n config.profiles[profile] ??= { lastSyncedVersion: 0 };\n return config.profiles[profile];\n}\n\nexport function createConfig(input: {\n apiUrl: string;\n profile: string;\n codexHome?: string | undefined;\n authFile?: string | undefined;\n deviceId?: string | undefined;\n}): LocalConfig {\n const codexHome = expandHome(input.codexHome ?? defaultCodexHome());\n const authFile = expandHome(input.authFile ?? path.join(codexHome, \"auth.json\"));\n\n return {\n apiUrl: input.apiUrl.replace(/\\/+$/, \"\"),\n deviceId: input.deviceId ?? `${os.hostname()}-${randomUUID().slice(0, 8)}`,\n activeProfile: input.profile,\n codexHome,\n authFile,\n profiles: {\n [input.profile]: { lastSyncedVersion: 0 },\n },\n };\n}\n\nexport async function readCodexCredentialStore(codexHome: string): Promise<\"file\" | \"keyring\" | \"auto\" | \"unknown\"> {\n const configToml = path.join(codexHome, \"config.toml\");\n if (!existsSync(configToml)) {\n return \"unknown\";\n }\n\n try {\n const parsed = parse(await readFile(configToml, \"utf8\")) as { cli_auth_credentials_store?: unknown };\n const value = parsed.cli_auth_credentials_store;\n if (value === \"file\" || value === \"keyring\" || value === \"auto\") {\n return value;\n }\n return \"unknown\";\n } catch {\n return \"unknown\";\n }\n}\n","import { input, password } from \"@inquirer/prompts\";\nimport { SecretStore } from \"./secrets.js\";\n\nexport async function getApiToken(options: {\n token?: string | undefined;\n existingToken?: string | null | undefined;\n}): Promise<string> {\n if (options.token) {\n return options.token;\n }\n if (process.env.CODEX_AUTH_SYNC_API_TOKEN) {\n return process.env.CODEX_AUTH_SYNC_API_TOKEN;\n }\n if (options.existingToken) {\n return options.existingToken;\n }\n return password({ message: \"API token\" });\n}\n\nexport async function getPassphrase(message = \"Encryption passphrase\"): Promise<string> {\n if (process.env.CODEX_AUTH_SYNC_PASSPHRASE) {\n return process.env.CODEX_AUTH_SYNC_PASSPHRASE;\n }\n return password({ message, mask: \"*\" });\n}\n\nexport async function getWorkerUrl(value?: string): Promise<string> {\n if (value) {\n return value;\n }\n return input({ message: \"Worker URL\" });\n}\n\nexport async function saveApiToken(secretStore: SecretStore, token: string): Promise<void> {\n await secretStore.set(\"api-token\", token);\n}\n","import { mkdir, readFile, rm, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { defaultConfigDir } from \"./config.js\";\n\nconst SERVICE = \"codex-auth-sync\";\n\ntype Keytar = {\n getPassword(service: string, account: string): Promise<string | null>;\n setPassword(service: string, account: string, password: string): Promise<void>;\n deletePassword(service: string, account: string): Promise<boolean>;\n};\n\nexport class SecretStore {\n async get(name: string): Promise<string | null> {\n const keytar = await loadKeytar();\n if (keytar) {\n return keytar.getPassword(SERVICE, name);\n }\n\n const data = await readFallbackSecrets();\n return data[name] ?? null;\n }\n\n async set(name: string, value: string): Promise<void> {\n const keytar = await loadKeytar();\n if (keytar) {\n await keytar.setPassword(SERVICE, name, value);\n return;\n }\n\n assertFallbackAllowed();\n const data = await readFallbackSecrets();\n data[name] = value;\n await writeFallbackSecrets(data);\n }\n\n async delete(name: string): Promise<void> {\n const keytar = await loadKeytar();\n if (keytar) {\n await keytar.deletePassword(SERVICE, name);\n return;\n }\n\n const data = await readFallbackSecrets();\n delete data[name];\n await writeFallbackSecrets(data);\n }\n\n async deleteAllKnown(profileNames: string[]): Promise<void> {\n await this.delete(\"api-token\");\n for (const profile of profileNames) {\n await this.delete(profileKeySecretName(profile));\n }\n\n if (!(await loadKeytar())) {\n await rm(fallbackSecretsPath(), { force: true });\n }\n }\n}\n\nexport function profileKeySecretName(profile: string): string {\n return `profile-key:${profile}`;\n}\n\nasync function loadKeytar(): Promise<Keytar | null> {\n if (process.env.CODEX_AUTH_SYNC_DISABLE_KEYCHAIN === \"1\") {\n return null;\n }\n\n try {\n return (await import(\"keytar\")) as Keytar;\n } catch {\n return null;\n }\n}\n\nfunction fallbackSecretsPath(): string {\n return path.join(defaultConfigDir(), \"secrets.local.json\");\n}\n\nfunction assertFallbackAllowed(): void {\n if (process.env.CODEX_AUTH_SYNC_ALLOW_INSECURE_SECRETS === \"1\" || process.env.NODE_ENV === \"test\") {\n return;\n }\n\n throw new Error(\n \"No OS keychain module is available. Install keytar support or set CODEX_AUTH_SYNC_ALLOW_INSECURE_SECRETS=1 for a local plaintext fallback.\",\n );\n}\n\nasync function readFallbackSecrets(): Promise<Record<string, string>> {\n try {\n return JSON.parse(await readFile(fallbackSecretsPath(), \"utf8\")) as Record<string, string>;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return {};\n }\n throw error;\n }\n}\n\nasync function writeFallbackSecrets(data: Record<string, string>): Promise<void> {\n assertFallbackAllowed();\n await mkdir(defaultConfigDir(), { recursive: true, mode: 0o700 });\n await writeFile(fallbackSecretsPath(), `${JSON.stringify(data, null, 2)}\\n`, { mode: 0o600 });\n}\n","import {\n decodeProfileKey,\n encodeProfileKey,\n generateProfileKey,\n type ProfileMeta,\n unwrapProfileKey,\n wrapProfileKey,\n} from \"@codex-auth-sync/shared\";\nimport { ApiClient, isNotFound } from \"./api.js\";\nimport { getPassphrase } from \"./prompts.js\";\nimport { profileKeySecretName, SecretStore } from \"./secrets.js\";\n\nexport async function loadCachedProfileKey(secretStore: SecretStore, profile: string): Promise<Uint8Array | null> {\n const cached = await secretStore.get(profileKeySecretName(profile));\n return cached ? decodeProfileKey(cached) : null;\n}\n\nexport async function getOrUnlockProfileKey(input: {\n api: ApiClient;\n secretStore: SecretStore;\n profile: string;\n meta?: ProfileMeta;\n}): Promise<{ key: Uint8Array; meta: ProfileMeta }> {\n const cached = await loadCachedProfileKey(input.secretStore, input.profile);\n const meta = input.meta ?? (await input.api.request<ProfileMeta>(`/api/v1/profiles/${input.profile}/meta`));\n if (cached) {\n return { key: cached, meta };\n }\n\n const passphrase = await getPassphrase(`Passphrase for ${input.profile}`);\n const key = await unwrapProfileKey(passphrase, meta.keyEnvelope);\n await input.secretStore.set(profileKeySecretName(input.profile), encodeProfileKey(key));\n return { key, meta };\n}\n\nexport async function ensureProfile(input: {\n api: ApiClient;\n secretStore: SecretStore;\n profile: string;\n createIfMissing: boolean;\n}): Promise<{ key: Uint8Array; meta: ProfileMeta; created: boolean }> {\n try {\n const meta = await input.api.request<ProfileMeta>(`/api/v1/profiles/${input.profile}/meta`);\n const { key } = await getOrUnlockProfileKey({ ...input, meta });\n return { key, meta, created: false };\n } catch (error) {\n if (!isNotFound(error) || !input.createIfMissing) {\n throw error;\n }\n }\n\n const passphrase = await getPassphrase(`Create passphrase for ${input.profile}`);\n const key = generateProfileKey();\n const keyEnvelope = await wrapProfileKey(input.profile, passphrase, key);\n const meta = await input.api.request<ProfileMeta>(\"/api/v1/profiles\", {\n method: \"POST\",\n body: JSON.stringify({ profile: input.profile, keyEnvelope }),\n });\n await input.secretStore.set(profileKeySecretName(input.profile), encodeProfileKey(key));\n return { key, meta, created: true };\n}\n","import { mkdir, readFile, rename, writeFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { randomUUID } from \"node:crypto\";\nimport { sha256Hex } from \"@codex-auth-sync/shared\";\n\nexport function authFileExists(authFile: string): boolean {\n return existsSync(authFile);\n}\n\nexport async function readAuthBytes(authFile: string): Promise<Uint8Array | null> {\n try {\n return new Uint8Array(await readFile(authFile));\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return null;\n }\n throw error;\n }\n}\n\nexport async function hashAuthFile(authFile: string): Promise<string | undefined> {\n const bytes = await readAuthBytes(authFile);\n return bytes ? sha256Hex(bytes) : undefined;\n}\n\nexport async function writeAuthBytesAtomic(authFile: string, bytes: Uint8Array): Promise<void> {\n await mkdir(path.dirname(authFile), { recursive: true, mode: 0o700 });\n const tmpFile = path.join(path.dirname(authFile), `.authsync-${randomUUID()}.tmp`);\n await writeFile(tmpFile, bytes, { mode: 0o600 });\n await rename(tmpFile, authFile);\n}\n\nexport async function writeConflictCopy(authFile: string, bytes: Uint8Array): Promise<string> {\n const stamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n const conflictPath = `${authFile}.conflict.${stamp}.json`;\n await writeFile(conflictPath, bytes, { mode: 0o600 });\n return conflictPath;\n}\n","import {\n decryptAuthBytes,\n encryptAuthBytes,\n sha256Hex,\n type EncryptedAuthBlob,\n type ProfileMeta,\n type UploadVersionResponse,\n} from \"@codex-auth-sync/shared\";\nimport { ApiClient } from \"./api.js\";\nimport { ensureProfileState, type LocalConfig, saveConfig } from \"./config.js\";\nimport { hashAuthFile, readAuthBytes, writeAuthBytesAtomic, writeConflictCopy } from \"./local-auth.js\";\nimport { getOrUnlockProfileKey } from \"./profile-key.js\";\nimport { SecretStore } from \"./secrets.js\";\n\nexport type SyncMode = \"auto\" | \"upload\" | \"download\";\n\nexport type SyncDecision = \"clean\" | \"upload\" | \"download\" | \"conflict\" | \"missing\";\n\nexport function decideSync(input: {\n localExists: boolean;\n localChanged: boolean;\n remoteVersion: number;\n lastSyncedVersion: number;\n mode: SyncMode;\n}): SyncDecision {\n if (input.mode === \"upload\") {\n return input.localExists ? \"upload\" : \"missing\";\n }\n if (input.mode === \"download\") {\n return input.remoteVersion > 0 ? \"download\" : \"missing\";\n }\n\n const remoteChanged = input.remoteVersion !== input.lastSyncedVersion;\n if (input.localChanged && remoteChanged) {\n return \"conflict\";\n }\n if (input.localChanged) {\n return \"upload\";\n }\n if (remoteChanged && input.remoteVersion > 0) {\n return \"download\";\n }\n return \"clean\";\n}\n\nexport async function syncOnce(input: {\n config: LocalConfig;\n token: string;\n secretStore: SecretStore;\n mode?: SyncMode;\n quiet?: boolean;\n}): Promise<SyncDecision> {\n const mode = input.mode ?? \"auto\";\n const api = new ApiClient(input.config.apiUrl, input.token);\n const profile = input.config.activeProfile;\n const state = ensureProfileState(input.config, profile);\n const { meta, key } = await getOrUnlockProfileKey({ api, secretStore: input.secretStore, profile });\n const localBytes = await readAuthBytes(input.config.authFile);\n const localHash = localBytes ? await sha256Hex(localBytes) : undefined;\n const localChanged = Boolean(localHash && localHash !== state.lastPlainHash);\n\n const decision = decideSync({\n localExists: Boolean(localBytes),\n localChanged,\n remoteVersion: meta.currentVersion,\n lastSyncedVersion: state.lastSyncedVersion,\n mode,\n });\n\n if (decision === \"upload\" && localBytes) {\n await uploadLocal({ api, config: input.config, meta, key, localBytes });\n await saveConfig(input.config);\n if (!input.quiet) {\n console.log(`uploaded ${profile} v${input.config.profiles[profile]?.lastSyncedVersion}`);\n }\n return decision;\n }\n\n if (decision === \"download\") {\n if (localBytes && localChanged) {\n const conflictPath = await writeConflictCopy(input.config.authFile, localBytes);\n if (!input.quiet) {\n console.log(`saved local conflict ${conflictPath}`);\n }\n }\n await downloadRemote({ api, config: input.config, meta, key });\n await saveConfig(input.config);\n if (!input.quiet) {\n console.log(`downloaded ${profile} v${meta.currentVersion}`);\n }\n return decision;\n }\n\n if (decision === \"conflict\") {\n state.conflict = localHash\n ? {\n detectedAt: new Date().toISOString(),\n localHash,\n remoteVersion: meta.currentVersion,\n }\n : {\n detectedAt: new Date().toISOString(),\n remoteVersion: meta.currentVersion,\n };\n await saveConfig(input.config);\n if (!input.quiet) {\n console.log(`conflict ${profile}: run sync --upload or sync --download`);\n }\n return decision;\n }\n\n if (decision === \"missing\" && !input.quiet) {\n console.log(\"nothing to sync\");\n return decision;\n }\n\n if (!input.quiet) {\n console.log(`${profile} already synced`);\n }\n return decision;\n}\n\nasync function uploadLocal(input: {\n api: ApiClient;\n config: LocalConfig;\n meta: ProfileMeta;\n key: Uint8Array;\n localBytes: Uint8Array;\n}): Promise<void> {\n const nextVersion = input.meta.currentVersion + 1;\n const blob = await encryptAuthBytes({\n profile: input.config.activeProfile,\n version: nextVersion,\n keyVersion: input.meta.keyEnvelope.keyVersion,\n updatedByDevice: input.config.deviceId,\n profileKey: input.key,\n plaintext: input.localBytes,\n });\n\n const response = await input.api.request<UploadVersionResponse>(\n `/api/v1/profiles/${input.config.activeProfile}/versions`,\n {\n method: \"PUT\",\n body: JSON.stringify({ baseVersion: input.meta.currentVersion, blob }),\n },\n );\n\n const state = ensureProfileState(input.config);\n state.lastSyncedVersion = response.meta.currentVersion;\n state.lastPlainHash = await sha256Hex(input.localBytes);\n if (response.meta.currentCiphertextHash) {\n state.lastCiphertextHash = response.meta.currentCiphertextHash;\n } else {\n delete state.lastCiphertextHash;\n }\n state.keyVersion = response.meta.keyEnvelope.keyVersion;\n delete state.conflict;\n}\n\nasync function downloadRemote(input: {\n api: ApiClient;\n config: LocalConfig;\n meta: ProfileMeta;\n key: Uint8Array;\n}): Promise<void> {\n if (input.meta.currentVersion < 1) {\n throw new Error(\"Remote profile has no versions.\");\n }\n\n const blob = await input.api.request<EncryptedAuthBlob>(\n `/api/v1/profiles/${input.config.activeProfile}/versions/${input.meta.currentVersion}`,\n );\n const plaintext = await decryptAuthBytes(input.key, blob);\n await writeAuthBytesAtomic(input.config.authFile, plaintext);\n\n const state = ensureProfileState(input.config);\n state.lastSyncedVersion = input.meta.currentVersion;\n const plainHash = await hashAuthFile(input.config.authFile);\n if (plainHash) {\n state.lastPlainHash = plainHash;\n } else {\n delete state.lastPlainHash;\n }\n if (input.meta.currentCiphertextHash) {\n state.lastCiphertextHash = input.meta.currentCiphertextHash;\n } else {\n delete state.lastCiphertextHash;\n }\n state.keyVersion = input.meta.keyEnvelope.keyVersion;\n delete state.conflict;\n}\n","import chokidar from \"chokidar\";\nimport WebSocket from \"ws\";\nimport { type DeviceSocketMessage } from \"@codex-auth-sync/shared\";\nimport { loadConfig } from \"./config.js\";\nimport { SecretStore } from \"./secrets.js\";\nimport { syncOnce } from \"./sync.js\";\nimport { ApiClient } from \"./api.js\";\n\nexport async function watchCommand(): Promise<void> {\n const config = await loadConfig();\n if (!config) {\n throw new Error(\"Not initialized. Run codex-auth-sync init first.\");\n }\n\n const secretStore = new SecretStore();\n const token = await secretStore.get(\"api-token\");\n if (!token) {\n throw new Error(\"Missing API token. Run codex-auth-sync init again.\");\n }\n\n const api = new ApiClient(config.apiUrl, token);\n await syncOnce({ config, token, secretStore });\n\n let timer: NodeJS.Timeout | undefined;\n const scheduleSync = (mode: \"auto\" | \"download\" = \"auto\") => {\n if (timer) {\n clearTimeout(timer);\n }\n timer = setTimeout(() => {\n syncOnce({ config, token, secretStore, mode }).catch((error: unknown) => {\n console.error(error instanceof Error ? error.message : String(error));\n });\n }, 1_500);\n };\n\n const watcher = chokidar.watch(config.authFile, {\n ignoreInitial: true,\n awaitWriteFinish: {\n stabilityThreshold: 1_000,\n pollInterval: 100,\n },\n });\n watcher.on(\"add\", () => scheduleSync());\n watcher.on(\"change\", () => scheduleSync());\n\n let socket: WebSocket | null = null;\n let reconnectTimer: NodeJS.Timeout | undefined;\n const connect = () => {\n socket = new WebSocket(api.websocketUrl(config.deviceId), {\n headers: { Authorization: `Bearer ${token}` },\n });\n socket.on(\"message\", (data) => {\n const message = JSON.parse(data.toString()) as DeviceSocketMessage;\n if (message.type === \"profile_updated\" && message.profile === config.activeProfile) {\n scheduleSync(\"download\");\n }\n if (message.type === \"catch_up\") {\n const relevant = message.pending.some((event) => event.profile === config.activeProfile);\n if (relevant) {\n scheduleSync();\n }\n }\n });\n socket.on(\"close\", () => {\n reconnectTimer = setTimeout(connect, 5_000);\n });\n socket.on(\"error\", () => {\n socket?.close();\n });\n };\n\n connect();\n console.log(`watching ${config.activeProfile}`);\n\n const stop = async () => {\n if (reconnectTimer) {\n clearTimeout(reconnectTimer);\n }\n if (timer) {\n clearTimeout(timer);\n }\n socket?.close();\n await watcher.close();\n process.exit(0);\n };\n\n process.once(\"SIGINT\", () => void stop());\n process.once(\"SIGTERM\", () => void stop());\n}\n"],"mappings":"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","names":["existsSync","input","path","input","mkdir","readFile","rm","writeFile","path","rm","path","readFile","mkdir","writeFile","input","meta","key","mkdir","readFile","writeFile","existsSync","path","randomUUID","readFile","mkdir","path","randomUUID","writeFile","input","existsSync"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "codex-auth-sync",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Encrypted sync for Codex auth.json profiles",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"codex-auth-sync": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "rm -rf dist && tsup --config tsup.config.ts && node scripts/fix-bin-mode.mjs",
|
|
15
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
16
|
+
"test": "vitest run"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@inquirer/prompts": "^7.8.0",
|
|
20
|
+
"chokidar": "^4.0.3",
|
|
21
|
+
"commander": "^14.0.0",
|
|
22
|
+
"smol-toml": "^1.4.2",
|
|
23
|
+
"ws": "^8.18.3"
|
|
24
|
+
},
|
|
25
|
+
"optionalDependencies": {
|
|
26
|
+
"keytar": "^7.9.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@codex-auth-sync/shared": "workspace:*",
|
|
30
|
+
"@types/ws": "^8.18.1",
|
|
31
|
+
"tsup": "^8.5.1",
|
|
32
|
+
"vitest": "^3.2.4"
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=20"
|
|
36
|
+
}
|
|
37
|
+
}
|