codexuse-cli 2.3.1 → 2.4.1

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 CHANGED
@@ -6,13 +6,6 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __getProtoOf = Object.getPrototypeOf;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
- var __esm = (fn, res) => function __init() {
10
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
- };
12
- var __export = (target, all) => {
13
- for (var name in all)
14
- __defProp(target, name, { get: all[name], enumerable: true });
15
- };
16
9
  var __copyProps = (to, from, except, desc) => {
17
10
  if (from && typeof from === "object" || typeof from === "function") {
18
11
  for (let key of __getOwnPropNames(from))
@@ -30,7 +23,17 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
30
23
  mod
31
24
  ));
32
25
 
26
+ // ../../lib/profile-manager.ts
27
+ var import_fs = require("fs");
28
+ var import_path = __toESM(require("path"));
29
+ var import_path2 = require("path");
30
+ var import_toml = require("@iarna/toml");
31
+
33
32
  // ../../lib/auto-roll-settings.ts
33
+ var DEFAULT_AUTO_ROLL_ENABLED = false;
34
+ var DEFAULT_AUTO_ROLL_WARNING_THRESHOLD = 85;
35
+ var DEFAULT_AUTO_ROLL_SWITCH_THRESHOLD = 95;
36
+ var AUTO_ROLL_WARNING_MIN = 50;
34
37
  function clampNumber(value, min, max) {
35
38
  return Math.min(max, Math.max(min, value));
36
39
  }
@@ -46,1026 +49,489 @@ function normalizeAutoRollSettings(raw) {
46
49
  switchThreshold: normalizedSwitch
47
50
  };
48
51
  }
49
- var DEFAULT_AUTO_ROLL_ENABLED, DEFAULT_AUTO_ROLL_WARNING_THRESHOLD, DEFAULT_AUTO_ROLL_SWITCH_THRESHOLD, AUTO_ROLL_WARNING_MIN;
50
- var init_auto_roll_settings = __esm({
51
- "../../lib/auto-roll-settings.ts"() {
52
- "use strict";
53
- DEFAULT_AUTO_ROLL_ENABLED = false;
54
- DEFAULT_AUTO_ROLL_WARNING_THRESHOLD = 85;
55
- DEFAULT_AUTO_ROLL_SWITCH_THRESHOLD = 95;
56
- AUTO_ROLL_WARNING_MIN = 50;
57
- }
58
- });
59
52
 
60
- // ../../lib/sqlite-db.ts
61
- var sqlite_db_exports = {};
62
- __export(sqlite_db_exports, {
63
- __debugResolveDbPath: () => __debugResolveDbPath,
64
- flushDatabase: () => flushDatabase,
65
- getDatabase: () => getDatabase,
66
- resetDatabaseConnection: () => resetDatabaseConnection
67
- });
68
- async function flushDatabase() {
69
- if (globalCache.cachedDbPromise) {
70
- const db = await globalCache.cachedDbPromise;
71
- if (db instanceof SqliteWasmDatabase) {
72
- await db.flushPersistence();
73
- }
53
+ // ../../lib/codex-cli-channel.ts
54
+ var KNOWN_CHANNELS = /* @__PURE__ */ new Set(["stable", "alpha"]);
55
+ function parseCodexCliChannel(value) {
56
+ if (typeof value !== "string") {
57
+ return null;
58
+ }
59
+ const normalized = value.trim().toLowerCase();
60
+ if (!normalized) {
61
+ return null;
74
62
  }
63
+ return KNOWN_CHANNELS.has(normalized) ? normalized : null;
64
+ }
65
+ function normalizeCodexCliChannel(value, fallback = "stable") {
66
+ return parseCodexCliChannel(value) ?? fallback;
75
67
  }
76
- function isPlainObject(value) {
77
- return typeof value === "object" && value !== null && !Array.isArray(value);
68
+
69
+ // ../../lib/app-state.ts
70
+ var import_node_fs = require("fs");
71
+ var import_node_async_hooks = require("async_hooks");
72
+ var import_node_path = __toESM(require("path"));
73
+ var import_node_os = __toESM(require("os"));
74
+ var APP_STATE_FILE = "app-state.json";
75
+ var APP_NAME = "codexuse-desktop";
76
+ var configuredUserDataDir = null;
77
+ var appStateCache = null;
78
+ var writeLock = Promise.resolve();
79
+ var writeLockContext = new import_node_async_hooks.AsyncLocalStorage();
80
+ function isRecord(value) {
81
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
78
82
  }
79
- function normalizeNamedParameters(params) {
80
- const normalized = {};
81
- for (const [key, rawValue] of Object.entries(params)) {
82
- if (key.startsWith("@") || key.startsWith(":") || key.startsWith("$")) {
83
- normalized[key] = rawValue;
84
- } else {
85
- normalized[`@${key}`] = rawValue;
86
- }
87
- }
88
- return normalized;
83
+ function clone(value) {
84
+ return JSON.parse(JSON.stringify(value));
89
85
  }
90
- function bindParameters(statement, args) {
91
- if (args.length === 0) {
92
- return;
86
+ function resolveDefaultUserDataDir() {
87
+ const home = process.env.HOME || process.env.USERPROFILE || import_node_os.default.homedir();
88
+ if (!home) {
89
+ throw new Error("Unable to resolve home directory for app state.");
93
90
  }
94
- if (args.length === 1 && isPlainObject(args[0])) {
95
- statement.bind(normalizeNamedParameters(args[0]));
96
- return;
91
+ if (process.platform === "darwin") {
92
+ return import_node_path.default.join(home, "Library", "Application Support", APP_NAME);
97
93
  }
98
- statement.bind(args);
99
- }
100
- function resolveHomeDir() {
101
- const homeDir = process.env.HOME || process.env.USERPROFILE;
102
- if (!homeDir) {
103
- throw new Error("HOME directory is not set. Unable to initialize CodexUse storage.");
94
+ if (process.platform === "win32") {
95
+ const appData = process.env.APPDATA;
96
+ if (appData) {
97
+ return import_node_path.default.join(appData, APP_NAME);
98
+ }
99
+ return import_node_path.default.join(home, "AppData", "Roaming", APP_NAME);
104
100
  }
105
- return homeDir;
101
+ return import_node_path.default.join(home, ".config", APP_NAME);
106
102
  }
107
- function resolveStorageDirectory() {
108
- return import_node_path.default.join(resolveHomeDir(), STORAGE_DIRECTORY_NAME);
103
+ function getUserDataDir() {
104
+ return configuredUserDataDir ?? resolveDefaultUserDataDir();
109
105
  }
110
- function resolveDbPath() {
111
- return import_node_path.default.join(resolveStorageDirectory(), STORAGE_FILENAME);
106
+ function resolveAppStatePath() {
107
+ return import_node_path.default.join(getUserDataDir(), APP_STATE_FILE);
112
108
  }
113
- function ensureStorageDirExists(targetPath) {
114
- (0, import_node_fs.mkdirSync)(import_node_path.default.dirname(targetPath), { recursive: true });
115
- }
116
- async function instantiateDatabase(dbPath) {
117
- try {
118
- const bakPath = `${dbPath}.bak`;
119
- if ((0, import_node_fs.existsSync)(dbPath)) {
120
- const size = (0, import_node_fs.statSync)(dbPath).size;
121
- if (size < 1024 && (0, import_node_fs.existsSync)(bakPath)) {
122
- const bakSize = (0, import_node_fs.statSync)(bakPath).size;
123
- if (bakSize > size) {
124
- const backup = (0, import_node_fs.readFileSync)(bakPath);
125
- (0, import_node_fs.writeFileSync)(dbPath, backup);
126
- console.warn("Detected near-empty SQLite file; restored from backup.");
127
- }
128
- }
109
+ function createDefaultAppState() {
110
+ return {
111
+ schemaVersion: 1,
112
+ autoRoll: {
113
+ enabled: false,
114
+ warningThreshold: 85,
115
+ switchThreshold: 95
116
+ },
117
+ app: {
118
+ lastAppVersion: null,
119
+ pendingUpdateVersion: null,
120
+ cliChannel: "stable",
121
+ lastProfileName: null
122
+ },
123
+ license: {
124
+ licenseKey: null,
125
+ purchaseEmail: null,
126
+ lastVerifiedAt: null,
127
+ nextCheckAt: null,
128
+ lastVerificationError: null,
129
+ status: "inactive",
130
+ signature: null
131
+ },
132
+ providers: {
133
+ selectedProviderId: "openai",
134
+ defaultModel: null,
135
+ defaultReasoningEffort: "medium",
136
+ list: [],
137
+ overridesByPath: {}
138
+ },
139
+ sandbox: {
140
+ defaultMode: "chat",
141
+ defaultApprovalPolicy: "on-failure",
142
+ overridesByPath: {}
143
+ },
144
+ preferences: {
145
+ excludeFolders: [],
146
+ enableTaskCompleteBeep: true,
147
+ preventSleepDuringTasks: true,
148
+ folderHistory: [],
149
+ pinnedPaths: []
150
+ },
151
+ projectSettingsByPath: {},
152
+ conversationCategoriesByCwd: {},
153
+ conversationCategoryAssignmentsByCwd: {},
154
+ skills: {
155
+ sources: [],
156
+ installsBySlug: {}
157
+ },
158
+ sync: {
159
+ lastPushAt: null,
160
+ lastPullAt: null,
161
+ lastError: null,
162
+ remoteUpdatedAt: null
163
+ },
164
+ telemetry: {
165
+ installId: null,
166
+ enabled: true,
167
+ lastFlushAt: null,
168
+ lastError: null
169
+ },
170
+ profilesByName: {},
171
+ migration: {
172
+ status: "pending",
173
+ startedAt: null,
174
+ completedAt: null,
175
+ localStorageImportedAt: null,
176
+ lastError: null
129
177
  }
130
- } catch (error) {
131
- console.warn("Failed to check/restore SQLite backup:", error);
132
- }
133
- const SQL = await sqlModulePromise;
134
- let fileBuffer;
135
- if ((0, import_node_fs.existsSync)(dbPath)) {
136
- fileBuffer = new Uint8Array((0, import_node_fs.readFileSync)(dbPath));
137
- }
138
- const driver = fileBuffer ? new SQL.Database(fileBuffer) : new SQL.Database();
139
- const database = new SqliteWasmDatabase(driver, dbPath);
140
- database.exec("PRAGMA journal_mode = WAL;");
141
- database.exec("PRAGMA foreign_keys = ON;");
142
- runMigrations(database);
143
- database.persist();
144
- return database;
145
- }
146
- function readUserVersion(database) {
147
- const result = database.prepare("PRAGMA user_version").get();
148
- const version = result?.user_version;
149
- return typeof version === "number" ? version : 0;
150
- }
151
- function ensureChatTables(database) {
152
- database.exec(`
153
- PRAGMA foreign_keys = ON;
154
-
155
- CREATE TABLE IF NOT EXISTS chat_threads (
156
- thread_key TEXT PRIMARY KEY,
157
- title TEXT,
158
- account_id TEXT,
159
- profile_name TEXT,
160
- project_label TEXT,
161
- project_path TEXT,
162
- source_path TEXT,
163
- started_at TEXT,
164
- ended_at TEXT,
165
- last_message_at TEXT,
166
- message_count INTEGER DEFAULT 0
167
- );
168
-
169
- CREATE TABLE IF NOT EXISTS chat_messages (
170
- id INTEGER PRIMARY KEY AUTOINCREMENT,
171
- thread_key TEXT NOT NULL,
172
- role TEXT,
173
- content TEXT NOT NULL,
174
- account_id TEXT,
175
- profile_name TEXT,
176
- created_at TEXT,
177
- FOREIGN KEY(thread_key) REFERENCES chat_threads(thread_key) ON DELETE CASCADE
178
- );
179
-
180
- CREATE INDEX IF NOT EXISTS idx_chat_messages_thread ON chat_messages(thread_key);
181
- CREATE INDEX IF NOT EXISTS idx_chat_messages_created ON chat_messages(created_at);
182
-
183
- CREATE TABLE IF NOT EXISTS chat_ingest_offsets (
184
- file_path TEXT PRIMARY KEY,
185
- offset INTEGER NOT NULL,
186
- mtime_ms INTEGER NOT NULL,
187
- size INTEGER NOT NULL
188
- );
189
- `);
178
+ };
190
179
  }
191
- function ensureSessionTables(database) {
192
- database.exec(`
193
- PRAGMA foreign_keys = ON;
194
-
195
- CREATE TABLE IF NOT EXISTS sessions (
196
- id TEXT PRIMARY KEY,
197
- title TEXT,
198
- status TEXT,
199
- model TEXT,
200
- project_label TEXT,
201
- project_path TEXT,
202
- created_at TEXT NOT NULL,
203
- updated_at TEXT NOT NULL,
204
- last_message_at TEXT
205
- );
206
-
207
- CREATE TABLE IF NOT EXISTS tool_panels (
208
- id TEXT PRIMARY KEY,
209
- session_id TEXT NOT NULL,
210
- type TEXT NOT NULL,
211
- title TEXT,
212
- state TEXT,
213
- metadata TEXT,
214
- created_at TEXT NOT NULL,
215
- updated_at TEXT NOT NULL,
216
- FOREIGN KEY(session_id) REFERENCES sessions(id) ON DELETE CASCADE
217
- );
218
-
219
- CREATE INDEX IF NOT EXISTS idx_tool_panels_session ON tool_panels(session_id);
220
- CREATE INDEX IF NOT EXISTS idx_tool_panels_type ON tool_panels(type);
221
-
222
- CREATE TABLE IF NOT EXISTS panel_outputs (
223
- id INTEGER PRIMARY KEY AUTOINCREMENT,
224
- panel_id TEXT NOT NULL,
225
- session_id TEXT NOT NULL,
226
- type TEXT NOT NULL,
227
- data TEXT NOT NULL,
228
- created_at TEXT NOT NULL,
229
- FOREIGN KEY(panel_id) REFERENCES tool_panels(id) ON DELETE CASCADE,
230
- FOREIGN KEY(session_id) REFERENCES sessions(id) ON DELETE CASCADE
231
- );
232
-
233
- CREATE INDEX IF NOT EXISTS idx_panel_outputs_panel ON panel_outputs(panel_id);
234
- CREATE INDEX IF NOT EXISTS idx_panel_outputs_session ON panel_outputs(session_id);
235
- CREATE INDEX IF NOT EXISTS idx_panel_outputs_created ON panel_outputs(created_at);
236
-
237
- CREATE TABLE IF NOT EXISTS notes (
238
- id TEXT PRIMARY KEY,
239
- user_id TEXT,
240
- title TEXT NOT NULL,
241
- content TEXT NOT NULL,
242
- tags TEXT,
243
- is_favorited INTEGER NOT NULL DEFAULT 0,
244
- created_at TEXT NOT NULL,
245
- updated_at TEXT NOT NULL,
246
- synced_at TEXT
247
- );
248
-
249
- CREATE INDEX IF NOT EXISTS idx_notes_user ON notes(user_id);
250
- CREATE INDEX IF NOT EXISTS idx_notes_updated ON notes(updated_at);
251
- `);
252
- }
253
- function ensureUsageTables(database) {
254
- database.exec(`
255
- CREATE TABLE IF NOT EXISTS usage (
256
- session_id TEXT PRIMARY KEY,
257
- rollout_path TEXT NOT NULL,
258
- project_path TEXT,
259
- model TEXT,
260
- input_tokens INTEGER NOT NULL,
261
- cached_input_tokens INTEGER NOT NULL,
262
- output_tokens INTEGER NOT NULL,
263
- reasoning_output_tokens INTEGER NOT NULL,
264
- total_tokens INTEGER NOT NULL,
265
- timestamp TEXT NOT NULL
266
- );
267
-
268
- CREATE INDEX IF NOT EXISTS idx_usage_timestamp ON usage(timestamp);
269
- `);
270
- }
271
- function runMigrations(database) {
272
- let userVersion = readUserVersion(database);
273
- if (userVersion < 1) {
274
- database.exec(`
275
- CREATE TABLE IF NOT EXISTS profiles (
276
- id INTEGER PRIMARY KEY AUTOINCREMENT,
277
- name TEXT NOT NULL UNIQUE,
278
- data TEXT NOT NULL,
279
- account_id TEXT,
280
- workspace_id TEXT,
281
- workspace_name TEXT,
282
- email TEXT,
283
- auth_method TEXT,
284
- created_at TEXT,
285
- updated_at TEXT
286
- );
287
-
288
- CREATE UNIQUE INDEX IF NOT EXISTS idx_profiles_account_workspace
289
- ON profiles(account_id, workspace_id)
290
- WHERE account_id IS NOT NULL;
291
-
292
- PRAGMA user_version = 1;
293
- `);
294
- userVersion = 1;
180
+ function asString(value) {
181
+ if (typeof value !== "string") {
182
+ return null;
295
183
  }
296
- if (userVersion < 2) {
297
- database.exec(`PRAGMA user_version = 2;`);
298
- userVersion = 2;
184
+ const trimmed = value.trim();
185
+ return trimmed.length > 0 ? trimmed : null;
186
+ }
187
+ function normalizeAppState(raw) {
188
+ const defaults = createDefaultAppState();
189
+ if (!isRecord(raw)) {
190
+ return defaults;
299
191
  }
300
- if (userVersion < 3) {
301
- database.exec(`PRAGMA user_version = 3;`);
302
- userVersion = 3;
192
+ const next = deepMerge(defaults, raw);
193
+ const merged = clone(next);
194
+ merged.schemaVersion = 1;
195
+ if (typeof merged.autoRoll.enabled !== "boolean") {
196
+ merged.autoRoll.enabled = false;
303
197
  }
304
- if (userVersion < 4) {
305
- let legacySettings = null;
306
- try {
307
- const hasKvStore = Boolean(
308
- database.prepare(
309
- "SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'kv_store'"
310
- ).get()
311
- );
312
- if (hasKvStore) {
313
- const row = database.prepare("SELECT value FROM kv_store WHERE key = 'settings'").get();
314
- if (row?.value) {
315
- const parsed = JSON.parse(row.value);
316
- if (parsed && typeof parsed === "object") {
317
- legacySettings = parsed;
318
- }
319
- }
320
- }
321
- } catch (error) {
322
- console.warn("Failed to read legacy Codex settings:", error);
323
- }
324
- database.exec(`
325
- PRAGMA foreign_keys = OFF;
326
-
327
- DROP TABLE IF EXISTS rate_limit_snapshots;
328
- DROP TABLE IF EXISTS chat_messages;
329
- DROP TABLE IF EXISTS chat_threads;
330
- DROP TABLE IF EXISTS kv_store;
331
-
332
- CREATE TABLE IF NOT EXISTS app_settings (
333
- id INTEGER PRIMARY KEY CHECK (id = 1),
334
- last_profile_name TEXT,
335
- license_key TEXT,
336
- purchase_email TEXT,
337
- last_verified_at TEXT,
338
- next_check_at TEXT,
339
- last_verification_error TEXT,
340
- status TEXT
341
- );
342
-
343
- INSERT OR IGNORE INTO app_settings (id) VALUES (1);
344
-
345
- PRAGMA foreign_keys = ON;
346
- `);
347
- if (legacySettings) {
348
- const license = legacySettings.license;
349
- database.prepare(
350
- `UPDATE app_settings
351
- SET last_profile_name = @lastProfileName,
352
- license_key = @licenseKey,
353
- purchase_email = @purchaseEmail,
354
- last_verified_at = @lastVerifiedAt,
355
- next_check_at = @nextCheckAt,
356
- last_verification_error = @lastVerificationError,
357
- status = @status
358
- WHERE id = 1`
359
- ).run({
360
- lastProfileName: typeof legacySettings.lastProfileName === "string" ? legacySettings.lastProfileName : null,
361
- licenseKey: license && typeof license.licenseKey === "string" ? license.licenseKey : null,
362
- purchaseEmail: license && typeof license.purchaseEmail === "string" ? license.purchaseEmail : null,
363
- lastVerifiedAt: license && typeof license.lastVerifiedAt === "string" ? license.lastVerifiedAt : null,
364
- nextCheckAt: license && typeof license.nextCheckAt === "string" ? license.nextCheckAt : null,
365
- lastVerificationError: license && typeof license.lastVerificationError === "string" ? license.lastVerificationError : null,
366
- status: license && typeof license.status === "string" ? license.status : null
367
- });
368
- }
369
- database.exec(`PRAGMA user_version = 4;`);
370
- userVersion = 4;
198
+ if (!Number.isFinite(merged.autoRoll.warningThreshold)) {
199
+ merged.autoRoll.warningThreshold = 85;
371
200
  }
372
- if (userVersion < 5) {
373
- database.exec(`
374
- ALTER TABLE app_settings ADD COLUMN auto_roll_enabled INTEGER;
375
- ALTER TABLE app_settings ADD COLUMN auto_roll_warning_threshold INTEGER;
376
- ALTER TABLE app_settings ADD COLUMN auto_roll_switch_threshold INTEGER;
377
-
378
- PRAGMA user_version = 5;
379
- `);
380
- userVersion = 5;
381
- }
382
- if (userVersion < 6) {
383
- database.exec(`
384
- CREATE TABLE IF NOT EXISTS app_kv (
385
- key TEXT PRIMARY KEY,
386
- value TEXT NOT NULL
387
- );
388
- `);
389
- let appSettingsRow = null;
390
- try {
391
- const hasAppSettings = Boolean(
392
- database.prepare(
393
- "SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'app_settings'"
394
- ).get()
395
- );
396
- if (hasAppSettings) {
397
- appSettingsRow = database.prepare(
398
- `SELECT last_profile_name, license_key, purchase_email,
399
- last_verified_at, next_check_at, last_verification_error, status,
400
- auto_roll_enabled, auto_roll_warning_threshold, auto_roll_switch_threshold
401
- FROM app_settings
402
- WHERE id = 1`
403
- ).get();
404
- }
405
- } catch (error) {
406
- console.warn("Failed to read legacy app_settings row:", error);
407
- }
408
- try {
409
- const upsertKv = database.prepare("INSERT OR REPLACE INTO app_kv (key, value) VALUES (@key, @value)");
410
- if (appSettingsRow) {
411
- const lastProfile = typeof appSettingsRow.last_profile_name === "string" ? appSettingsRow.last_profile_name.trim() : "";
412
- if (lastProfile) {
413
- upsertKv.run({ key: "last_profile_name", value: JSON.stringify(lastProfile) });
414
- }
415
- const license = {
416
- licenseKey: typeof appSettingsRow.license_key === "string" ? appSettingsRow.license_key : null,
417
- purchaseEmail: typeof appSettingsRow.purchase_email === "string" ? appSettingsRow.purchase_email : null,
418
- lastVerifiedAt: typeof appSettingsRow.last_verified_at === "string" ? appSettingsRow.last_verified_at : null,
419
- nextCheckAt: typeof appSettingsRow.next_check_at === "string" ? appSettingsRow.next_check_at : null,
420
- lastVerificationError: typeof appSettingsRow.last_verification_error === "string" ? appSettingsRow.last_verification_error : null,
421
- status: typeof appSettingsRow.status === "string" ? appSettingsRow.status : null
422
- };
423
- const hasLicenseData = Boolean(
424
- license.licenseKey ?? license.purchaseEmail ?? license.lastVerifiedAt ?? license.nextCheckAt ?? license.lastVerificationError ?? license.status
425
- );
426
- if (hasLicenseData) {
427
- upsertKv.run({ key: "license", value: JSON.stringify(license) });
428
- }
429
- const hasAutoRollData = typeof appSettingsRow.auto_roll_enabled === "number" || typeof appSettingsRow.auto_roll_warning_threshold === "number" || typeof appSettingsRow.auto_roll_switch_threshold === "number";
430
- if (hasAutoRollData) {
431
- const warning = typeof appSettingsRow.auto_roll_warning_threshold === "number" ? appSettingsRow.auto_roll_warning_threshold : DEFAULT_AUTO_ROLL_WARNING_THRESHOLD;
432
- const switchThreshold = typeof appSettingsRow.auto_roll_switch_threshold === "number" ? appSettingsRow.auto_roll_switch_threshold : DEFAULT_AUTO_ROLL_SWITCH_THRESHOLD;
433
- const autoRoll = {
434
- enabled: Boolean(appSettingsRow.auto_roll_enabled ?? DEFAULT_AUTO_ROLL_ENABLED),
435
- warningThreshold: warning,
436
- switchThreshold
437
- };
438
- upsertKv.run({ key: "auto_roll", value: JSON.stringify(autoRoll) });
439
- }
440
- }
441
- try {
442
- database.exec("DROP TABLE IF EXISTS app_settings;");
443
- } catch (error) {
444
- console.warn("Failed to drop legacy app_settings table:", error);
445
- }
446
- } catch (error) {
447
- console.warn("Failed to migrate app_settings to app_kv:", error);
448
- }
449
- database.exec(`PRAGMA user_version = 6;`);
450
- userVersion = 6;
201
+ if (!Number.isFinite(merged.autoRoll.switchThreshold)) {
202
+ merged.autoRoll.switchThreshold = 95;
451
203
  }
452
- if (userVersion < 7) {
453
- try {
454
- const columns = database.prepare("PRAGMA table_info(profiles)").all();
455
- const hasWorkspaceId = Boolean(columns?.some((column) => column.name === "workspace_id"));
456
- const hasWorkspaceName = Boolean(columns?.some((column) => column.name === "workspace_name"));
457
- if (!hasWorkspaceId) {
458
- database.exec(`ALTER TABLE profiles ADD COLUMN workspace_id TEXT;`);
459
- }
460
- if (!hasWorkspaceName) {
461
- database.exec(`ALTER TABLE profiles ADD COLUMN workspace_name TEXT;`);
462
- }
463
- } catch (error) {
464
- console.warn("Failed to add workspace columns during migration:", error);
465
- }
466
- database.exec(`
467
- DROP INDEX IF EXISTS idx_profiles_account;
468
- DROP INDEX IF EXISTS idx_profiles_account_workspace;
469
-
470
- CREATE UNIQUE INDEX IF NOT EXISTS idx_profiles_account_workspace
471
- ON profiles(account_id, workspace_id)
472
- WHERE account_id IS NOT NULL;
473
- `);
474
- try {
475
- const rows = database.prepare("SELECT name, workspace_id FROM profiles").all();
476
- const update = database.prepare(
477
- "UPDATE profiles SET workspace_id = @workspaceId WHERE name = @name AND (workspace_id IS NULL OR workspace_id = '')"
478
- );
479
- for (const row of rows) {
480
- const name = typeof row.name === "string" ? row.name : null;
481
- if (!name) {
482
- continue;
483
- }
484
- const existingWorkspace = typeof row.workspace_id === "string" ? row.workspace_id.trim() : "";
485
- if (existingWorkspace) {
486
- continue;
487
- }
488
- update.run({ name, workspaceId: "__default__" });
489
- }
490
- } catch (error) {
491
- console.warn("Failed to backfill workspace_id during migration:", error);
492
- }
493
- database.exec(`PRAGMA user_version = 7;`);
494
- userVersion = 7;
204
+ if (merged.autoRoll.warningThreshold < 50 || merged.autoRoll.warningThreshold > 99) {
205
+ merged.autoRoll.warningThreshold = 85;
495
206
  }
496
- if (userVersion < 8) {
497
- ensureChatTables(database);
498
- database.exec(`PRAGMA user_version = 8;`);
499
- userVersion = 8;
207
+ if (merged.autoRoll.switchThreshold <= merged.autoRoll.warningThreshold || merged.autoRoll.switchThreshold > 100) {
208
+ merged.autoRoll.switchThreshold = Math.min(100, Math.max(merged.autoRoll.warningThreshold + 1, 95));
500
209
  }
501
- if (userVersion < 9) {
502
- ensureSessionTables(database);
503
- database.exec(`PRAGMA user_version = 9;`);
504
- userVersion = 9;
210
+ merged.app.cliChannel = merged.app.cliChannel === "alpha" ? "alpha" : "stable";
211
+ merged.app.lastAppVersion = asString(merged.app.lastAppVersion);
212
+ merged.app.pendingUpdateVersion = asString(merged.app.pendingUpdateVersion);
213
+ merged.app.lastProfileName = asString(merged.app.lastProfileName);
214
+ merged.license.licenseKey = asString(merged.license.licenseKey);
215
+ merged.license.purchaseEmail = asString(merged.license.purchaseEmail);
216
+ merged.license.lastVerifiedAt = asString(merged.license.lastVerifiedAt);
217
+ merged.license.nextCheckAt = asString(merged.license.nextCheckAt);
218
+ merged.license.lastVerificationError = asString(merged.license.lastVerificationError);
219
+ merged.license.signature = asString(merged.license.signature);
220
+ if (!["inactive", "active", "grace", "error"].includes(merged.license.status)) {
221
+ merged.license.status = "inactive";
505
222
  }
506
- if (userVersion < 10) {
507
- ensureUsageTables(database);
508
- database.exec(`PRAGMA user_version = 10;`);
509
- userVersion = 10;
223
+ if (!Array.isArray(merged.providers.list)) {
224
+ merged.providers.list = [];
510
225
  }
511
- if (userVersion < 11) {
512
- try {
513
- database.exec(`ALTER TABLE usage ADD COLUMN model TEXT;`);
514
- } catch {
515
- }
516
- database.exec(`PRAGMA user_version = 11;`);
517
- userVersion = 11;
226
+ merged.providers.selectedProviderId = asString(merged.providers.selectedProviderId) ?? "openai";
227
+ merged.providers.defaultModel = asString(merged.providers.defaultModel);
228
+ if (!["minimal", "low", "medium", "high", "xhigh", "none"].includes(merged.providers.defaultReasoningEffort)) {
229
+ merged.providers.defaultReasoningEffort = "medium";
518
230
  }
519
- try {
520
- database.exec("PRAGMA foreign_keys = ON;");
521
- } catch {
231
+ if (!isRecord(merged.providers.overridesByPath)) {
232
+ merged.providers.overridesByPath = {};
522
233
  }
523
- ensureChatTables(database);
524
- ensureSessionTables(database);
525
- ensureUsageTables(database);
526
- }
527
- async function getDatabase() {
528
- const dbPath = resolveDbPath();
529
- if (!globalCache.cachedDbPromise || globalCache.cachedDbPath !== dbPath) {
530
- if (globalCache.cachedDbPromise) {
531
- try {
532
- const previous = await globalCache.cachedDbPromise;
533
- previous.close();
534
- } catch {
535
- }
536
- }
537
- globalCache.cachedDbPath = dbPath;
538
- globalCache.cachedDbPromise = instantiateDatabase(dbPath);
234
+ if (!isRecord(merged.sandbox.overridesByPath)) {
235
+ merged.sandbox.overridesByPath = {};
539
236
  }
540
- const db = await globalCache.cachedDbPromise;
541
- try {
542
- runMigrations(db);
543
- } catch (error) {
544
- console.warn("Failed to ensure migrations:", error);
237
+ if (!Array.isArray(merged.preferences.excludeFolders)) {
238
+ merged.preferences.excludeFolders = [];
545
239
  }
546
- return db;
547
- }
548
- async function resetDatabaseConnection() {
549
- if (globalCache.cachedDbPromise) {
550
- try {
551
- const db = await globalCache.cachedDbPromise;
552
- db.close();
553
- } catch {
554
- }
240
+ if (!Array.isArray(merged.preferences.folderHistory)) {
241
+ merged.preferences.folderHistory = [];
555
242
  }
556
- globalCache.cachedDbPromise = null;
557
- globalCache.cachedDbPath = null;
558
- }
559
- function __debugResolveDbPath() {
560
- return resolveDbPath();
561
- }
562
- var import_node_fs, import_node_path, import_node_module, import_sql, require2, wasmDir, sqlModulePromise, SqliteWasmDatabase, SqliteWasmStatement, STORAGE_DIRECTORY_NAME, STORAGE_FILENAME, GLOBAL_CACHE_KEY, globalCache;
563
- var init_sqlite_db = __esm({
564
- "../../lib/sqlite-db.ts"() {
565
- "use strict";
566
- import_node_fs = require("fs");
567
- import_node_path = __toESM(require("path"));
568
- import_node_module = require("module");
569
- import_sql = __toESM(require("sql.js"));
570
- init_auto_roll_settings();
571
- require2 = (0, import_node_module.createRequire)(__filename);
572
- wasmDir = import_node_path.default.dirname(require2.resolve("sql.js/dist/sql-wasm.wasm"));
573
- sqlModulePromise = (0, import_sql.default)({
574
- locateFile: (file) => import_node_path.default.join(wasmDir, file)
575
- });
576
- SqliteWasmDatabase = class {
577
- constructor(driver, dbPath) {
578
- this.driver = driver;
579
- this.dbPath = dbPath;
580
- this.closed = false;
581
- this.persistTimeout = null;
582
- }
583
- get open() {
584
- return !this.closed;
585
- }
586
- prepare(sql) {
587
- this.assertOpen();
588
- return new SqliteWasmStatement(this, sql);
589
- }
590
- createStatement(sql) {
591
- this.assertOpen();
592
- return this.driver.prepare(sql);
593
- }
594
- exec(sql) {
595
- this.assertOpen();
596
- this.driver.exec(sql);
597
- }
598
- getRowsModified() {
599
- this.assertOpen();
600
- return this.driver.getRowsModified();
601
- }
602
- async persist() {
603
- this.assertOpen();
604
- ensureStorageDirExists(this.dbPath);
605
- if (this.persistTimeout) {
606
- clearTimeout(this.persistTimeout);
607
- }
608
- this.persistTimeout = setTimeout(async () => {
609
- try {
610
- const contents = Buffer.from(this.driver.export());
611
- await import_node_fs.promises.writeFile(this.dbPath, contents);
612
- this.persistTimeout = null;
613
- } catch (error) {
614
- console.error("Failed to persist database:", error);
615
- }
616
- }, 500);
617
- }
618
- close() {
619
- if (!this.closed) {
620
- if (this.persistTimeout) {
621
- clearTimeout(this.persistTimeout);
622
- this.persistTimeout = null;
623
- try {
624
- const contents = Buffer.from(this.driver.export());
625
- (0, import_node_fs.writeFileSync)(this.dbPath, contents);
626
- } catch (error) {
627
- if (error.code !== "ENOENT") {
628
- console.error("Failed to flush database on close:", error);
629
- }
630
- }
631
- }
632
- this.driver.close();
633
- this.closed = true;
634
- }
635
- }
636
- async flushPersistence() {
637
- if (this.persistTimeout) {
638
- clearTimeout(this.persistTimeout);
639
- this.persistTimeout = null;
640
- try {
641
- const contents = Buffer.from(this.driver.export());
642
- await import_node_fs.promises.writeFile(this.dbPath, contents);
643
- } catch (error) {
644
- console.error("Failed to flush database persistence:", error);
645
- }
646
- }
647
- }
648
- assertOpen() {
649
- if (!this.open) {
650
- throw new Error("SQLite database connection is closed.");
651
- }
652
- }
653
- };
654
- SqliteWasmStatement = class {
655
- constructor(database, sql) {
656
- this.database = database;
657
- this.sql = sql;
658
- }
659
- get(...params) {
660
- const statement = this.database.createStatement(this.sql);
661
- try {
662
- bindParameters(statement, params);
663
- if (!statement.step()) {
664
- return void 0;
665
- }
666
- return statement.getAsObject();
667
- } finally {
668
- statement.free();
669
- }
670
- }
671
- all(...params) {
672
- const rows = [];
673
- const statement = this.database.createStatement(this.sql);
674
- try {
675
- bindParameters(statement, params);
676
- while (statement.step()) {
677
- rows.push(statement.getAsObject());
678
- }
679
- } finally {
680
- statement.free();
681
- }
682
- return rows;
683
- }
684
- run(...params) {
685
- const statement = this.database.createStatement(this.sql);
686
- try {
687
- bindParameters(statement, params);
688
- statement.step();
689
- } finally {
690
- statement.free();
691
- }
692
- const changes = this.database.getRowsModified();
693
- void this.database.persist();
694
- return { changes };
695
- }
696
- };
697
- STORAGE_DIRECTORY_NAME = ".f86eb5e712267207";
698
- STORAGE_FILENAME = "state-d64ce728d7a20214.sqlite";
699
- GLOBAL_CACHE_KEY = /* @__PURE__ */ Symbol.for("codex.sqliteCache");
700
- globalCache = globalThis[GLOBAL_CACHE_KEY] ?? (globalThis[GLOBAL_CACHE_KEY] = {
701
- cachedDbPath: null,
702
- cachedDbPromise: null
703
- });
243
+ if (!Array.isArray(merged.preferences.pinnedPaths)) {
244
+ merged.preferences.pinnedPaths = [];
704
245
  }
705
- });
706
-
707
- // ../../lib/profile-manager.ts
708
- var import_fs = require("fs");
709
- var import_path = __toESM(require("path"));
710
- var import_path2 = require("path");
711
- var import_toml = require("@iarna/toml");
712
-
713
- // ../../lib/codex-settings.ts
714
- var import_node_fs2 = require("fs");
715
- var import_node_path2 = __toESM(require("path"));
716
-
717
- // ../../lib/logger.ts
718
- var isTestEnv = process.env.NODE_ENV === "test" || process.env.VITEST === "true" || process.env.VITEST === "1";
719
- function isMocked(fn) {
720
- return Boolean(fn && typeof fn === "function" && "mock" in fn);
721
- }
722
- function logWarn(...args) {
723
- if (isTestEnv && !isMocked(console.warn)) {
724
- return;
246
+ if (!isRecord(merged.projectSettingsByPath)) {
247
+ merged.projectSettingsByPath = {};
725
248
  }
726
- console.warn(...args);
727
- }
728
- function logError(...args) {
729
- if (isTestEnv && !isMocked(console.error)) {
730
- return;
249
+ if (!isRecord(merged.conversationCategoriesByCwd)) {
250
+ merged.conversationCategoriesByCwd = {};
731
251
  }
732
- console.error(...args);
733
- }
734
-
735
- // ../../lib/codex-settings.ts
736
- init_auto_roll_settings();
737
-
738
- // ../../lib/codex-cli-channel.ts
739
- var KNOWN_CHANNELS = /* @__PURE__ */ new Set(["stable", "alpha"]);
740
- function parseCodexCliChannel(value) {
741
- if (typeof value !== "string") {
742
- return null;
252
+ if (!isRecord(merged.conversationCategoryAssignmentsByCwd)) {
253
+ merged.conversationCategoryAssignmentsByCwd = {};
743
254
  }
744
- const normalized = value.trim().toLowerCase();
745
- if (!normalized) {
746
- return null;
255
+ if (!isRecord(merged.skills)) {
256
+ merged.skills = clone(defaults.skills);
747
257
  }
748
- return KNOWN_CHANNELS.has(normalized) ? normalized : null;
258
+ if (!Array.isArray(merged.skills.sources)) {
259
+ merged.skills.sources = [];
260
+ }
261
+ if (!isRecord(merged.skills.installsBySlug)) {
262
+ merged.skills.installsBySlug = {};
263
+ }
264
+ if (!isRecord(merged.sync)) {
265
+ merged.sync = clone(defaults.sync);
266
+ }
267
+ merged.sync.lastPushAt = asString(merged.sync.lastPushAt);
268
+ merged.sync.lastPullAt = asString(merged.sync.lastPullAt);
269
+ merged.sync.lastError = asString(merged.sync.lastError);
270
+ merged.sync.remoteUpdatedAt = asString(merged.sync.remoteUpdatedAt);
271
+ if (!isRecord(merged.telemetry)) {
272
+ merged.telemetry = clone(defaults.telemetry);
273
+ }
274
+ merged.telemetry.installId = asString(merged.telemetry.installId);
275
+ if (typeof merged.telemetry.enabled !== "boolean") {
276
+ merged.telemetry.enabled = true;
277
+ }
278
+ merged.telemetry.lastFlushAt = asString(merged.telemetry.lastFlushAt);
279
+ merged.telemetry.lastError = asString(merged.telemetry.lastError);
280
+ if (!isRecord(merged.profilesByName)) {
281
+ merged.profilesByName = {};
282
+ }
283
+ if (!isRecord(merged.migration)) {
284
+ merged.migration = clone(defaults.migration);
285
+ }
286
+ if (!["pending", "pending_local_storage", "complete"].includes(merged.migration.status)) {
287
+ merged.migration.status = "pending";
288
+ }
289
+ merged.migration.startedAt = asString(merged.migration.startedAt);
290
+ merged.migration.completedAt = asString(merged.migration.completedAt);
291
+ merged.migration.localStorageImportedAt = asString(merged.migration.localStorageImportedAt);
292
+ merged.migration.lastError = asString(merged.migration.lastError);
293
+ return merged;
749
294
  }
750
- function normalizeCodexCliChannel(value, fallback = "stable") {
751
- return parseCodexCliChannel(value) ?? fallback;
295
+ function deepMerge(base, patch) {
296
+ if (!isRecord(base) || !isRecord(patch)) {
297
+ return clone(patch ?? base);
298
+ }
299
+ const next = { ...base };
300
+ for (const [key, patchValue] of Object.entries(patch)) {
301
+ if (patchValue === void 0) {
302
+ continue;
303
+ }
304
+ const currentValue = next[key];
305
+ if (Array.isArray(patchValue)) {
306
+ next[key] = clone(patchValue);
307
+ continue;
308
+ }
309
+ if (isRecord(currentValue) && isRecord(patchValue)) {
310
+ next[key] = deepMerge(currentValue, patchValue);
311
+ continue;
312
+ }
313
+ next[key] = clone(patchValue);
314
+ }
315
+ return next;
752
316
  }
753
-
754
- // ../../lib/codex-settings.ts
755
- var KEY_LAST_PROFILE_NAME = "last_profile_name";
756
- var KEY_LICENSE = "license";
757
- var KEY_AUTO_ROLL = "auto_roll";
758
- var KEY_LAST_APP_VERSION = "last_app_version";
759
- var KEY_PENDING_UPDATE_VERSION = "pending_update_version";
760
- var KEY_CLI_CHANNEL = "cli_channel";
761
- var SETTINGS_FILE = "settings.json";
762
- var SETTINGS_BACKUP_FILE = "settings.json.bak";
763
- function resolveCodexDir() {
764
- const homeDir = process.env.HOME || process.env.USERPROFILE || "";
765
- return homeDir ? import_node_path2.default.join(homeDir, ".codex") : ".codex";
766
- }
767
- function resolveSettingsPath() {
768
- return import_node_path2.default.join(resolveCodexDir(), SETTINGS_FILE);
769
- }
770
- function resolveBackupSettingsPath() {
771
- return import_node_path2.default.join(resolveCodexDir(), SETTINGS_BACKUP_FILE);
772
- }
773
- async function ensureCodexDir() {
317
+ async function writeAtomic(filePath, contents) {
318
+ const dir = import_node_path.default.dirname(filePath);
319
+ const base = import_node_path.default.basename(filePath);
320
+ const token = `${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}`;
321
+ const tempPath = import_node_path.default.join(dir, `${base}.${token}.tmp`);
322
+ await import_node_fs.promises.mkdir(dir, { recursive: true });
323
+ await import_node_fs.promises.writeFile(tempPath, contents, "utf8");
774
324
  try {
775
- await import_node_fs2.promises.mkdir(resolveCodexDir(), { recursive: true });
776
- } catch (error) {
777
- logWarn("Failed to ensure Codex directory exists:", error);
325
+ await import_node_fs.promises.rename(tempPath, filePath);
326
+ } finally {
327
+ await import_node_fs.promises.rm(tempPath, { force: true }).catch(() => void 0);
778
328
  }
779
329
  }
780
- async function writeAtomic(filePath, contents) {
781
- const tempPath = `${filePath}.tmp`;
782
- await import_node_fs2.promises.mkdir(import_node_path2.default.dirname(filePath), { recursive: true });
783
- await import_node_fs2.promises.writeFile(tempPath, contents, "utf8");
330
+ async function writeAppStateToDisk(state) {
331
+ const filePath = resolveAppStatePath();
332
+ await writeAtomic(filePath, `${JSON.stringify(state, null, 2)}
333
+ `);
334
+ }
335
+ async function readAppStateFromDisk() {
336
+ const filePath = resolveAppStatePath();
784
337
  try {
785
- await import_node_fs2.promises.rename(tempPath, filePath);
338
+ const raw = await import_node_fs.promises.readFile(filePath, "utf8");
339
+ return normalizeAppState(JSON.parse(raw));
786
340
  } catch (error) {
787
341
  const code = error.code;
788
342
  if (code === "ENOENT") {
789
- await import_node_fs2.promises.writeFile(filePath, contents, "utf8");
790
- } else {
791
- throw error;
343
+ return null;
792
344
  }
345
+ throw error;
793
346
  }
347
+ }
348
+ async function ensureInitialized() {
349
+ if (appStateCache) {
350
+ return appStateCache;
351
+ }
352
+ const filePath = resolveAppStatePath();
353
+ await import_node_fs.promises.mkdir(import_node_path.default.dirname(filePath), { recursive: true });
354
+ const loaded = await readAppStateFromDisk();
355
+ const normalized = loaded ?? normalizeAppState(null);
356
+ appStateCache = normalized;
357
+ if (loaded === null) {
358
+ await writeAppStateToDisk(normalized);
359
+ }
360
+ return appStateCache;
361
+ }
362
+ async function withWriteLock(task) {
363
+ if (writeLockContext.getStore()) {
364
+ return task();
365
+ }
366
+ const previous = writeLock;
367
+ let release = null;
368
+ writeLock = new Promise((resolve) => {
369
+ release = resolve;
370
+ });
371
+ await previous;
794
372
  try {
795
- await import_node_fs2.promises.rm(tempPath, { force: true });
796
- } catch {
373
+ return await writeLockContext.run(true, task);
374
+ } finally {
375
+ release?.();
376
+ }
377
+ }
378
+ async function getAppState() {
379
+ const state = await ensureInitialized();
380
+ return clone(state);
381
+ }
382
+ async function updateAppState(transform, options = {}) {
383
+ const mode = options.mode ?? "patch";
384
+ const allowBeforeMigrationComplete = options.allowBeforeMigrationComplete === true;
385
+ return withWriteLock(async () => {
386
+ const current = await readAppStateFromDisk() ?? await ensureInitialized();
387
+ appStateCache = current;
388
+ if (!allowBeforeMigrationComplete && current.migration.status !== "complete") {
389
+ throw new Error("Storage migration is not complete yet.");
390
+ }
391
+ const transformed = transform(clone(current));
392
+ const nextState = mode === "replace" ? normalizeAppState(transformed) : normalizeAppState(deepMerge(current, transformed));
393
+ await writeAppStateToDisk(nextState);
394
+ appStateCache = nextState;
395
+ return clone(nextState);
396
+ });
397
+ }
398
+ async function patchAppState(patch) {
399
+ return updateAppState(() => patch, {
400
+ mode: "patch",
401
+ allowBeforeMigrationComplete: false
402
+ });
403
+ }
404
+
405
+ // ../../lib/codex-settings.ts
406
+ function asString2(value) {
407
+ if (typeof value !== "string") {
408
+ return null;
797
409
  }
410
+ const trimmed = value.trim();
411
+ return trimmed.length > 0 ? trimmed : null;
412
+ }
413
+ function isRecord2(value) {
414
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
798
415
  }
799
- function normalizeStoredLicense(raw) {
800
- if (!raw || typeof raw !== "object") {
416
+ function parseStoredLicense(raw) {
417
+ if (!isRecord2(raw)) {
801
418
  return null;
802
419
  }
803
- const source = raw;
804
- const licenseKey = typeof source.licenseKey === "string" ? source.licenseKey : typeof source.license_key === "string" ? source.license_key : null;
805
- const purchaseEmail = typeof source.purchaseEmail === "string" ? source.purchaseEmail : typeof source.purchase_email === "string" ? source.purchase_email : null;
806
- const lastVerifiedAt = typeof source.lastVerifiedAt === "string" ? source.lastVerifiedAt : typeof source.last_verified_at === "string" ? source.last_verified_at : null;
807
- const nextCheckAt = typeof source.nextCheckAt === "string" ? source.nextCheckAt : typeof source.next_check_at === "string" ? source.next_check_at : null;
808
- const lastVerificationError = typeof source.lastVerificationError === "string" ? source.lastVerificationError : typeof source.last_verification_error === "string" ? source.last_verification_error : null;
809
- const signature = typeof source.signature === "string" ? source.signature : null;
810
- const statusCandidate = typeof source.status === "string" ? source.status : void 0;
420
+ const statusCandidate = asString2(raw.status);
421
+ const status = ["inactive", "active", "grace", "error"].includes(statusCandidate ?? "") ? statusCandidate : void 0;
811
422
  const license = {
812
- licenseKey,
813
- purchaseEmail,
814
- lastVerifiedAt,
815
- nextCheckAt,
816
- lastVerificationError,
817
- signature,
818
- status: statusCandidate
423
+ licenseKey: asString2(raw.licenseKey ?? raw.license_key),
424
+ purchaseEmail: asString2(raw.purchaseEmail ?? raw.purchase_email),
425
+ lastVerifiedAt: asString2(raw.lastVerifiedAt ?? raw.last_verified_at),
426
+ nextCheckAt: asString2(raw.nextCheckAt ?? raw.next_check_at),
427
+ lastVerificationError: asString2(raw.lastVerificationError ?? raw.last_verification_error),
428
+ status,
429
+ signature: asString2(raw.signature)
819
430
  };
820
- const hasLicenseData = Boolean(
821
- license.licenseKey ?? license.purchaseEmail ?? license.lastVerifiedAt ?? license.nextCheckAt ?? license.lastVerificationError ?? license.status
431
+ const hasValue = Boolean(
432
+ license.licenseKey || license.purchaseEmail || license.lastVerifiedAt || license.nextCheckAt || license.lastVerificationError || license.status
822
433
  );
823
- return hasLicenseData ? license : null;
434
+ return hasValue ? license : null;
824
435
  }
825
- function normalizeStoredAutoRoll(raw) {
436
+ function parseAutoRoll(raw) {
826
437
  if (!raw) {
827
438
  return null;
828
439
  }
829
- const parsed = typeof raw === "string" ? parseJsonValue(raw) : raw;
830
- if (!parsed || typeof parsed !== "object") {
831
- return null;
832
- }
833
440
  try {
834
- return normalizeAutoRollSettings(parsed);
441
+ return normalizeAutoRollSettings(raw);
835
442
  } catch {
836
443
  return null;
837
444
  }
838
445
  }
839
- function parseStoredProfileName(raw) {
840
- if (raw === null || raw === void 0) {
841
- return null;
842
- }
843
- if (typeof raw === "string" || typeof raw === "number") {
844
- const normalized = String(raw).trim();
845
- return normalized.length > 0 ? normalized : null;
846
- }
847
- const candidate = typeof raw === "string" ? parseJsonValue(raw) : parseJsonValue(raw);
848
- if (typeof candidate === "string" || typeof candidate === "number") {
849
- const normalized = String(candidate).trim();
850
- return normalized.length > 0 ? normalized : null;
851
- }
852
- return null;
446
+ async function getLastProfileName() {
447
+ const state = await getAppState();
448
+ return asString2(state.app.lastProfileName);
853
449
  }
854
- async function markSettingsCorrupt(settingsPath, raw) {
855
- try {
856
- const suffix = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
857
- const backupPath = `${settingsPath}.corrupt-${suffix}`;
858
- await import_node_fs2.promises.writeFile(backupPath, raw, "utf8");
859
- } catch {
860
- }
861
- }
862
- async function tryRestoreSettings(fromPath, _targetPath) {
863
- try {
864
- const raw = await import_node_fs2.promises.readFile(fromPath, "utf8");
865
- const parsed = JSON.parse(raw);
866
- if (!parsed || typeof parsed !== "object") {
867
- return null;
450
+ async function persistLastProfileName(profileName) {
451
+ await patchAppState({
452
+ app: {
453
+ lastProfileName: asString2(profileName)
868
454
  }
869
- const normalized = normalizeParsedSettings(parsed);
870
- await writeSettingsFile(normalized);
871
- return normalized;
872
- } catch {
873
- return null;
874
- }
455
+ });
875
456
  }
876
- function normalizeParsedSettings(parsed) {
877
- const license = normalizeStoredLicense(parsed.license ?? parsed[KEY_LICENSE]);
878
- const autoRoll = normalizeStoredAutoRoll(parsed.autoRoll ?? parsed[KEY_AUTO_ROLL]);
879
- const lastProfileName = parseStoredProfileName(
880
- parsed.lastProfileName ?? parsed[KEY_LAST_PROFILE_NAME]
881
- );
882
- const lastAppVersion = typeof parsed.lastAppVersion === "string" ? parsed.lastAppVersion : typeof parsed[KEY_LAST_APP_VERSION] === "string" ? parsed[KEY_LAST_APP_VERSION] : null;
883
- const pendingUpdateVersion = typeof parsed.pendingUpdateVersion === "string" ? parsed.pendingUpdateVersion : typeof parsed[KEY_PENDING_UPDATE_VERSION] === "string" ? parsed[KEY_PENDING_UPDATE_VERSION] : null;
884
- const cliChannel = parseCodexCliChannel(
885
- parsed.cliChannel ?? parsed[KEY_CLI_CHANNEL]
886
- );
887
- return {
888
- ...license ? { license } : {},
889
- ...autoRoll ? { autoRoll } : {},
890
- ...lastProfileName ? { lastProfileName } : {},
891
- ...lastAppVersion ? { lastAppVersion } : {},
892
- ...pendingUpdateVersion ? { pendingUpdateVersion } : {},
893
- ...cliChannel ? { cliChannel } : {}
894
- };
457
+ async function getStoredLicense() {
458
+ const state = await getAppState();
459
+ return parseStoredLicense(state.license);
895
460
  }
896
- async function readSettingsFromDisk() {
897
- const settingsPath = resolveSettingsPath();
898
- try {
899
- const raw = await import_node_fs2.promises.readFile(settingsPath, "utf8");
900
- const parsed = JSON.parse(raw);
901
- if (!parsed || typeof parsed !== "object") {
902
- return null;
903
- }
904
- return normalizeParsedSettings(parsed);
905
- } catch (error) {
906
- if (error.code === "ENOENT") {
907
- return null;
908
- }
909
- if (error instanceof SyntaxError) {
910
- try {
911
- const raw = await import_node_fs2.promises.readFile(settingsPath, "utf8");
912
- await markSettingsCorrupt(settingsPath, raw);
913
- const restoredFromTemp = await tryRestoreSettings(`${settingsPath}.tmp`, settingsPath);
914
- if (restoredFromTemp) {
915
- return restoredFromTemp;
916
- }
917
- const restoredFromBackup = await tryRestoreSettings(resolveBackupSettingsPath(), settingsPath);
918
- if (restoredFromBackup) {
919
- return restoredFromBackup;
920
- }
921
- await import_node_fs2.promises.writeFile(settingsPath, "{}\n", "utf8");
922
- } catch {
923
- }
461
+ async function persistLicense(license) {
462
+ const parsed = parseStoredLicense(license);
463
+ await patchAppState({
464
+ license: parsed ? {
465
+ licenseKey: parsed.licenseKey ?? null,
466
+ purchaseEmail: parsed.purchaseEmail ?? null,
467
+ lastVerifiedAt: parsed.lastVerifiedAt ?? null,
468
+ nextCheckAt: parsed.nextCheckAt ?? null,
469
+ lastVerificationError: parsed.lastVerificationError ?? null,
470
+ status: parsed.status ?? "inactive",
471
+ signature: parsed.signature ?? null
472
+ } : {
473
+ licenseKey: null,
474
+ purchaseEmail: null,
475
+ lastVerifiedAt: null,
476
+ nextCheckAt: null,
477
+ lastVerificationError: null,
478
+ status: "inactive",
479
+ signature: null
924
480
  }
925
- logWarn("Failed to parse Codex settings file, using defaults:", error);
926
- return null;
927
- }
481
+ });
928
482
  }
929
- async function migrateSettingsFromDatabase() {
930
- try {
931
- const { __debugResolveDbPath: __debugResolveDbPath2, getDatabase: getDatabase2 } = await Promise.resolve().then(() => (init_sqlite_db(), sqlite_db_exports));
932
- const dbPath = __debugResolveDbPath2();
933
- try {
934
- await import_node_fs2.promises.access(dbPath);
935
- } catch {
936
- return null;
937
- }
938
- const db = await getDatabase2();
939
- const rows = db.prepare(
940
- `SELECT key, value
941
- FROM app_kv
942
- WHERE key IN ('${KEY_LAST_PROFILE_NAME}', '${KEY_LICENSE}', '${KEY_AUTO_ROLL}', '${KEY_LAST_APP_VERSION}', '${KEY_PENDING_UPDATE_VERSION}', '${KEY_CLI_CHANNEL}')`
943
- ).all();
944
- if (!rows || rows.length === 0) {
945
- return null;
946
- }
947
- const kv = /* @__PURE__ */ new Map();
948
- for (const row of rows) {
949
- if (!row.key) continue;
950
- if (typeof row.value === "string") {
951
- kv.set(row.key, parseJsonValue(row.value));
952
- } else {
953
- kv.set(row.key, row.value);
954
- }
955
- }
956
- const license = normalizeStoredLicense(kv.get(KEY_LICENSE));
957
- const autoRoll = normalizeStoredAutoRoll(kv.get(KEY_AUTO_ROLL));
958
- const lastProfileName = parseStoredProfileName(kv.get(KEY_LAST_PROFILE_NAME));
959
- const lastAppVersion = typeof kv.get(KEY_LAST_APP_VERSION) === "string" ? kv.get(KEY_LAST_APP_VERSION) : null;
960
- const pendingUpdateVersion = typeof kv.get(KEY_PENDING_UPDATE_VERSION) === "string" ? kv.get(KEY_PENDING_UPDATE_VERSION) : null;
961
- const cliChannel = parseCodexCliChannel(kv.get(KEY_CLI_CHANNEL));
962
- const settings = {
963
- ...license ? { license } : {},
964
- ...autoRoll ? { autoRoll } : {},
965
- ...lastProfileName ? { lastProfileName } : {},
966
- ...lastAppVersion ? { lastAppVersion } : {},
967
- ...pendingUpdateVersion ? { pendingUpdateVersion } : {},
968
- ...cliChannel ? { cliChannel } : {}
969
- };
970
- if (Object.keys(settings).length === 0) {
971
- return null;
972
- }
973
- await writeSettingsFile(settings);
974
- return settings;
975
- } catch (error) {
976
- logWarn("Failed to migrate Codex settings from database:", error);
977
- return null;
978
- }
483
+ async function getCodexCliChannel() {
484
+ const state = await getAppState();
485
+ return normalizeCodexCliChannel(state.app.cliChannel);
979
486
  }
980
- async function readSettingsFile() {
981
- await ensureCodexDir();
982
- const diskSettings = await readSettingsFromDisk();
983
- if (diskSettings) {
984
- return diskSettings;
985
- }
986
- const migrated = await migrateSettingsFromDatabase();
987
- if (migrated) {
988
- return migrated;
989
- }
990
- return {};
991
- }
992
- async function writeSettingsFile(settings) {
993
- await ensureCodexDir();
994
- const normalizedAutoRoll = settings.autoRoll ? normalizeAutoRollSettings(settings.autoRoll) : null;
995
- const normalizedLicense = settings.license ? {
996
- licenseKey: settings.license.licenseKey ?? null,
997
- purchaseEmail: settings.license.purchaseEmail ?? null,
998
- lastVerifiedAt: settings.license.lastVerifiedAt ?? null,
999
- nextCheckAt: settings.license.nextCheckAt ?? null,
1000
- lastVerificationError: settings.license.lastVerificationError ?? null,
1001
- status: settings.license.status ?? void 0,
1002
- signature: settings.license.signature ?? null
1003
- } : null;
1004
- const payload = {
1005
- ...settings.lastProfileName ? { lastProfileName: settings.lastProfileName } : {},
1006
- ...normalizedLicense ? { license: normalizedLicense } : {},
1007
- ...normalizedAutoRoll ? { autoRoll: normalizedAutoRoll } : {},
1008
- ...settings.lastAppVersion ? { lastAppVersion: settings.lastAppVersion } : {},
1009
- ...settings.pendingUpdateVersion ? { pendingUpdateVersion: settings.pendingUpdateVersion } : {},
1010
- ...settings.cliChannel ? { cliChannel: settings.cliChannel } : {}
487
+ async function readCodexSettingsJsonRaw() {
488
+ const state = await getAppState();
489
+ return {
490
+ lastProfileName: state.app.lastProfileName,
491
+ lastAppVersion: state.app.lastAppVersion,
492
+ pendingUpdateVersion: state.app.pendingUpdateVersion,
493
+ cliChannel: state.app.cliChannel,
494
+ autoRoll: state.autoRoll,
495
+ license: state.license,
496
+ providers: state.providers.list,
497
+ selectedProviderId: state.providers.selectedProviderId,
498
+ defaultModel: state.providers.defaultModel,
499
+ defaultReasoningEffort: state.providers.defaultReasoningEffort,
500
+ selectionsByCwd: state.providers.overridesByPath,
501
+ mode: state.sandbox.defaultMode,
502
+ approvalPolicy: state.sandbox.defaultApprovalPolicy,
503
+ sandboxSelectionsByCwd: state.sandbox.overridesByPath,
504
+ excludeFolders: state.preferences.excludeFolders,
505
+ enableTaskCompleteBeep: state.preferences.enableTaskCompleteBeep,
506
+ preventSleepDuringTasks: state.preferences.preventSleepDuringTasks,
507
+ folderHistory: state.preferences.folderHistory,
508
+ pinnedPaths: state.preferences.pinnedPaths,
509
+ projectSettingsByPath: state.projectSettingsByPath,
510
+ categoriesByCwd: state.conversationCategoriesByCwd,
511
+ conversationCategoryByCwd: state.conversationCategoryAssignmentsByCwd,
512
+ sync: state.sync
1011
513
  };
1012
- const serialized = JSON.stringify(payload, null, 2);
1013
- const settingsPath = resolveSettingsPath();
1014
- const backupPath = resolveBackupSettingsPath();
1015
- const contents = `${serialized}
1016
- `;
1017
- await writeAtomic(settingsPath, contents);
1018
- try {
1019
- await writeAtomic(backupPath, contents);
1020
- } catch (error) {
1021
- logWarn("Failed to write Codex settings backup:", error);
1022
- }
1023
- }
1024
- function parseJsonValue(value) {
1025
- if (typeof value !== "string") {
1026
- return null;
1027
- }
1028
- try {
1029
- return JSON.parse(value);
1030
- } catch {
1031
- return value;
1032
- }
1033
- }
1034
- async function getCodexSettings() {
1035
- return readSettingsFile();
1036
- }
1037
- async function setCodexSettings(settings) {
1038
- await writeSettingsFile(settings);
1039
- }
1040
- async function getLastProfileName() {
1041
- const settings = await getCodexSettings();
1042
- const candidate = settings.lastProfileName;
1043
- if (typeof candidate !== "string") {
1044
- return null;
1045
- }
1046
- const trimmed = candidate.trim();
1047
- return trimmed.length === 0 ? null : trimmed;
1048
514
  }
1049
- async function persistLastProfileName(profileName) {
1050
- const settings = await getCodexSettings();
1051
- if (profileName && profileName.trim().length > 0) {
1052
- settings.lastProfileName = profileName.trim();
1053
- } else if (settings.lastProfileName) {
1054
- delete settings.lastProfileName;
1055
- }
1056
- await setCodexSettings(settings);
1057
- }
1058
- async function getStoredLicense() {
1059
- const settings = await getCodexSettings();
1060
- if (!settings.license) {
1061
- return null;
515
+ async function writeCodexSettingsJsonRaw(payload) {
516
+ if (!isRecord2(payload)) {
517
+ return;
1062
518
  }
1063
- return settings.license;
1064
- }
1065
- async function persistLicense(license) {
1066
- const settings = await getCodexSettings();
1067
- if (license) {
1068
- settings.license = {
519
+ const autoRoll = parseAutoRoll(payload.autoRoll ?? payload.auto_roll);
520
+ const license = parseStoredLicense(payload.license);
521
+ const cliChannel = parseCodexCliChannel(payload.cliChannel ?? payload.cli_channel);
522
+ await patchAppState({
523
+ app: {
524
+ lastProfileName: asString2(payload.lastProfileName ?? payload.last_profile_name),
525
+ lastAppVersion: asString2(payload.lastAppVersion ?? payload.last_app_version),
526
+ pendingUpdateVersion: asString2(payload.pendingUpdateVersion ?? payload.pending_update_version),
527
+ cliChannel: cliChannel ?? void 0
528
+ },
529
+ autoRoll: autoRoll ? {
530
+ enabled: autoRoll.enabled,
531
+ warningThreshold: autoRoll.warningThreshold,
532
+ switchThreshold: autoRoll.switchThreshold
533
+ } : void 0,
534
+ license: license ? {
1069
535
  licenseKey: license.licenseKey ?? null,
1070
536
  purchaseEmail: license.purchaseEmail ?? null,
1071
537
  lastVerifiedAt: license.lastVerifiedAt ?? null,
@@ -1073,15 +539,55 @@ async function persistLicense(license) {
1073
539
  lastVerificationError: license.lastVerificationError ?? null,
1074
540
  status: license.status ?? "inactive",
1075
541
  signature: license.signature ?? null
1076
- };
1077
- } else if (settings.license) {
1078
- delete settings.license;
542
+ } : void 0,
543
+ providers: {
544
+ list: Array.isArray(payload.providers) ? payload.providers : void 0,
545
+ selectedProviderId: asString2(payload.selectedProviderId) ?? void 0,
546
+ defaultModel: asString2(payload.defaultModel ?? payload.selectedModel),
547
+ defaultReasoningEffort: typeof payload.defaultReasoningEffort === "string" ? payload.defaultReasoningEffort : typeof payload.reasoningEffort === "string" ? payload.reasoningEffort : void 0,
548
+ overridesByPath: isRecord2(payload.selectionsByCwd) ? payload.selectionsByCwd : void 0
549
+ },
550
+ sandbox: {
551
+ defaultMode: asString2(payload.mode ?? payload.defaultMode) ?? void 0,
552
+ defaultApprovalPolicy: asString2(payload.approvalPolicy ?? payload.defaultApprovalPolicy) ?? void 0,
553
+ overridesByPath: isRecord2(payload.sandboxSelectionsByCwd) ? payload.sandboxSelectionsByCwd : void 0
554
+ },
555
+ preferences: {
556
+ excludeFolders: Array.isArray(payload.excludeFolders) ? payload.excludeFolders : void 0,
557
+ enableTaskCompleteBeep: typeof payload.enableTaskCompleteBeep === "boolean" ? payload.enableTaskCompleteBeep : void 0,
558
+ preventSleepDuringTasks: typeof payload.preventSleepDuringTasks === "boolean" ? payload.preventSleepDuringTasks : void 0,
559
+ folderHistory: Array.isArray(payload.folderHistory) ? payload.folderHistory : void 0,
560
+ pinnedPaths: Array.isArray(payload.pinnedPaths) ? payload.pinnedPaths : void 0
561
+ },
562
+ projectSettingsByPath: isRecord2(payload.projectSettingsByPath) ? payload.projectSettingsByPath : void 0,
563
+ conversationCategoriesByCwd: isRecord2(payload.categoriesByCwd) ? payload.categoriesByCwd : void 0,
564
+ conversationCategoryAssignmentsByCwd: isRecord2(payload.conversationCategoryByCwd) ? payload.conversationCategoryByCwd : void 0,
565
+ sync: isRecord2(payload.sync) ? payload.sync : void 0
566
+ });
567
+ }
568
+
569
+ // ../../lib/logger.ts
570
+ var isTestEnv = process.env.NODE_ENV === "test" || process.env.VITEST === "true" || process.env.VITEST === "1";
571
+ function isMocked(fn) {
572
+ return Boolean(fn && typeof fn === "function" && "mock" in fn);
573
+ }
574
+ function logWarn(...args) {
575
+ if (isTestEnv && !isMocked(console.warn)) {
576
+ return;
1079
577
  }
1080
- await setCodexSettings(settings);
578
+ console.warn(...args);
1081
579
  }
1082
- async function getCodexCliChannel() {
1083
- const settings = await getCodexSettings();
1084
- return normalizeCodexCliChannel(settings.cliChannel);
580
+ function logError(...args) {
581
+ if (isTestEnv && !isMocked(console.error)) {
582
+ return;
583
+ }
584
+ console.error(...args);
585
+ }
586
+ function logInfo(...args) {
587
+ if (isTestEnv && !isMocked(console.warn)) {
588
+ return;
589
+ }
590
+ console.warn(...args);
1085
591
  }
1086
592
 
1087
593
  // ../../lib/profile-manager.ts
@@ -1090,15 +596,11 @@ var REFRESH_TOKEN_REDEEMED_SNIPPET = "refresh token was already used";
1090
596
  var REFRESH_TOKEN_REDEEMED_REASON = "Your access token could not be refreshed because your refresh token was already used. Please log out and sign in again.";
1091
597
  var AUTH_BACKUP_MISSING_MARKER = "__codexuse_missing_auth__";
1092
598
  var DEFAULT_WORKSPACE_ID = "__default__";
1093
- var PROFILE_RECORD_FILENAME = "profile.json";
1094
599
  var ProfileManager = class {
1095
600
  constructor() {
1096
601
  const homeDir = process.env.HOME || process.env.USERPROFILE || "";
1097
602
  this.codexDir = (0, import_path2.join)(homeDir, ".codex");
1098
- this.profilesDir = (0, import_path2.join)(this.codexDir, "profiles");
1099
- this.profileHomesRoot = (0, import_path2.join)(this.codexDir, "profile-homes");
1100
- this.migrationsDir = (0, import_path2.join)(this.codexDir, "migrations");
1101
- this.profileMigrationMarker = (0, import_path2.join)(this.migrationsDir, "profiles-v1");
603
+ this.profileHomesRoot = (0, import_path2.join)(getUserDataDir(), "profile-homes");
1102
604
  this.activeAuth = (0, import_path2.join)(this.codexDir, "auth.json");
1103
605
  this.activeAuthBackup = `${this.activeAuth}.swap`;
1104
606
  this.lastActiveAuthErrorSignature = null;
@@ -1162,183 +664,84 @@ var ProfileManager = class {
1162
664
  logWarn("Failed to persist preferred profile name:", error);
1163
665
  }
1164
666
  }
1165
- getProfileRecordPath(profileName) {
1166
- return (0, import_path2.join)(this.getProfileHomePath(profileName), PROFILE_RECORD_FILENAME);
667
+ toStateRecord(record) {
668
+ return {
669
+ name: record.name,
670
+ displayName: record.displayName ?? null,
671
+ data: record.data,
672
+ metadata: record.metadata,
673
+ accountId: record.accountId ?? null,
674
+ workspaceId: record.workspaceId ?? null,
675
+ workspaceName: record.workspaceName ?? null,
676
+ email: record.email ?? null,
677
+ authMethod: record.authMethod ?? null,
678
+ createdAt: record.createdAt ?? null,
679
+ updatedAt: record.updatedAt ?? null
680
+ };
1167
681
  }
1168
- parseProfileRecordPayload(profileName, parsed) {
1169
- if (!parsed || typeof parsed !== "object") {
682
+ fromStateRecord(profileName, raw) {
683
+ if (!raw || typeof raw !== "object") {
1170
684
  return null;
1171
685
  }
1172
- const dataRaw = parsed["data"];
1173
- let data = null;
1174
- if (dataRaw && typeof dataRaw === "object") {
1175
- data = dataRaw;
1176
- } else if (typeof dataRaw === "string") {
1177
- try {
1178
- data = JSON.parse(dataRaw);
1179
- } catch (error) {
1180
- logWarn(`Failed to parse data payload for profile '${profileName}':`, error);
1181
- return null;
1182
- }
1183
- } else {
686
+ const dataRaw = raw.data;
687
+ if (!dataRaw || typeof dataRaw !== "object") {
1184
688
  return null;
1185
689
  }
1186
- const createdAt = typeof parsed["createdAt"] === "string" ? parsed["createdAt"] : typeof parsed["created_at"] === "string" ? parsed["created_at"] : null;
1187
- const updatedAt = typeof parsed["updatedAt"] === "string" ? parsed["updatedAt"] : typeof parsed["updated_at"] === "string" ? parsed["updated_at"] : null;
1188
- const metadata = parsed["metadata"] && typeof parsed["metadata"] === "object" ? parsed["metadata"] : void 0;
1189
- const displayName = typeof parsed["displayName"] === "string" ? parsed["displayName"] : typeof parsed["display_name"] === "string" ? parsed["display_name"] : null;
1190
690
  return {
1191
691
  name: profileName,
1192
- displayName,
1193
- data,
1194
- metadata,
1195
- accountId: typeof parsed["accountId"] === "string" ? parsed["accountId"] : typeof parsed["account_id"] === "string" ? parsed["account_id"] : null,
1196
- workspaceId: typeof parsed["workspaceId"] === "string" ? parsed["workspaceId"] : typeof parsed["workspace_id"] === "string" ? parsed["workspace_id"] : null,
1197
- workspaceName: typeof parsed["workspaceName"] === "string" ? parsed["workspaceName"] : typeof parsed["workspace_name"] === "string" ? parsed["workspace_name"] : null,
1198
- email: typeof parsed["email"] === "string" ? parsed["email"] : typeof parsed["email"] === "boolean" ? null : null,
1199
- authMethod: typeof parsed["authMethod"] === "string" ? parsed["authMethod"] : typeof parsed["auth_method"] === "string" ? parsed["auth_method"] : null,
1200
- createdAt,
1201
- updatedAt
692
+ displayName: typeof raw.displayName === "string" ? raw.displayName : null,
693
+ data: dataRaw,
694
+ metadata: raw.metadata,
695
+ accountId: typeof raw.accountId === "string" ? raw.accountId : null,
696
+ workspaceId: typeof raw.workspaceId === "string" ? raw.workspaceId : null,
697
+ workspaceName: typeof raw.workspaceName === "string" ? raw.workspaceName : null,
698
+ email: typeof raw.email === "string" ? raw.email : null,
699
+ authMethod: typeof raw.authMethod === "string" ? raw.authMethod : null,
700
+ createdAt: typeof raw.createdAt === "string" ? raw.createdAt : null,
701
+ updatedAt: typeof raw.updatedAt === "string" ? raw.updatedAt : null
1202
702
  };
1203
703
  }
1204
- decodeProfileRecord(profileName, raw, sourceLabel) {
1205
- try {
1206
- const parsed = JSON.parse(raw);
1207
- return this.parseProfileRecordPayload(profileName, parsed);
1208
- } catch (error) {
1209
- logWarn(`Failed to parse profile record '${profileName}' from '${sourceLabel}':`, error);
1210
- return null;
1211
- }
704
+ async readProfilesStateMap() {
705
+ const state = await getAppState();
706
+ return { ...state.profilesByName ?? {} };
1212
707
  }
1213
- async recoverProfileRecord(recordPath, profileName) {
1214
- const candidates = [`${recordPath}.bak`, `${recordPath}.tmp`];
1215
- for (const candidate of candidates) {
1216
- let raw;
1217
- try {
1218
- raw = await import_fs.promises.readFile(candidate, "utf8");
1219
- } catch (error) {
1220
- if (!this.isNotFoundError(error)) {
1221
- logWarn(`Failed to read backup for '${profileName}' from '${candidate}':`, error);
1222
- }
1223
- continue;
1224
- }
1225
- const decoded = this.decodeProfileRecord(profileName, raw, candidate);
1226
- if (!decoded) {
1227
- continue;
1228
- }
1229
- try {
1230
- await this.writeProfileRecord(decoded);
1231
- } catch (error) {
1232
- logWarn(`Failed to restore profile '${profileName}' from '${candidate}':`, error);
1233
- }
1234
- return decoded;
1235
- }
1236
- return null;
708
+ async writeProfilesStateMap(nextMap) {
709
+ await updateAppState((state) => ({
710
+ ...state,
711
+ profilesByName: nextMap
712
+ }), { mode: "replace" });
1237
713
  }
1238
714
  async readProfileRecord(profileName) {
1239
- const recordPath = this.getProfileRecordPath(profileName);
1240
- try {
1241
- const raw = await import_fs.promises.readFile(recordPath, "utf8");
1242
- const decoded = this.decodeProfileRecord(profileName, raw, recordPath);
1243
- if (decoded) {
1244
- return decoded;
1245
- }
1246
- } catch (error) {
1247
- if (!this.isNotFoundError(error)) {
1248
- logWarn(`Failed to read profile record '${profileName}':`, error);
1249
- }
715
+ const map = await this.readProfilesStateMap();
716
+ const raw = map[profileName];
717
+ if (!raw) {
718
+ return null;
1250
719
  }
1251
- return this.recoverProfileRecord(recordPath, profileName);
720
+ return this.fromStateRecord(profileName, raw);
1252
721
  }
1253
722
  async listProfileRecords() {
723
+ const map = await this.readProfilesStateMap();
1254
724
  const records = [];
1255
- let entries = [];
1256
- try {
1257
- entries = await import_fs.promises.readdir(this.profileHomesRoot);
1258
- } catch (error) {
1259
- if (!this.isNotFoundError(error)) {
1260
- logWarn("Failed to list profile homes:", error);
1261
- }
1262
- return records;
1263
- }
1264
- for (const entry of entries) {
1265
- const homePath = (0, import_path2.join)(this.profileHomesRoot, entry);
1266
- try {
1267
- const stat = await import_fs.promises.stat(homePath);
1268
- if (!stat.isDirectory()) {
1269
- continue;
1270
- }
1271
- } catch (error) {
1272
- if (!this.isNotFoundError(error)) {
1273
- logWarn(`Failed to inspect profile home '${entry}':`, error);
1274
- }
1275
- continue;
1276
- }
1277
- try {
1278
- const normalizedName = this.normalizeProfileName(entry);
1279
- const record = await this.readProfileRecord(normalizedName);
1280
- if (record) {
1281
- records.push(record);
1282
- continue;
1283
- }
1284
- const authPath = (0, import_path2.join)(homePath, "auth.json");
1285
- try {
1286
- const authRaw = await import_fs.promises.readFile(authPath, "utf8");
1287
- const authData = JSON.parse(authRaw);
1288
- const normalized = this.normalizeProfileData(authData);
1289
- const metadata = this.extractProfileMetadata(normalized);
1290
- await this.persistProfileRecord(normalizedName, normalized, metadata);
1291
- const restored = await this.readProfileRecord(normalizedName);
1292
- if (restored) {
1293
- records.push(restored);
1294
- }
1295
- } catch (error) {
1296
- if (!this.isNotFoundError(error)) {
1297
- logWarn(`Failed to restore profile record for '${normalizedName}' from auth.json:`, error);
1298
- }
1299
- }
1300
- } catch (error) {
1301
- logWarn(`Skipping invalid profile home '${entry}':`, error);
725
+ for (const [profileName, raw] of Object.entries(map)) {
726
+ const record = this.fromStateRecord(profileName, raw);
727
+ if (record) {
728
+ records.push(record);
1302
729
  }
1303
730
  }
1304
731
  return records;
1305
732
  }
1306
- async snapshotExistingProfileRecord(recordPath) {
1307
- try {
1308
- const existing = await import_fs.promises.readFile(recordPath, "utf8");
1309
- if (existing && existing.trim()) {
1310
- await this.writeAtomic(`${recordPath}.bak`, existing);
1311
- }
1312
- } catch (error) {
1313
- if (!this.isNotFoundError(error)) {
1314
- logWarn(`Failed to snapshot existing profile record '${recordPath}':`, error);
1315
- }
1316
- }
1317
- }
1318
733
  async writeProfileRecord(record) {
1319
- const recordPath = this.getProfileRecordPath(record.name);
1320
- const payload = {
1321
- name: record.name,
1322
- display_name: record.displayName,
1323
- data: record.data,
1324
- metadata: record.metadata,
1325
- account_id: record.accountId,
1326
- workspace_id: record.workspaceId,
1327
- workspace_name: record.workspaceName,
1328
- email: record.email,
1329
- auth_method: record.authMethod,
1330
- created_at: record.createdAt,
1331
- updated_at: record.updatedAt
1332
- };
1333
- const serialized = `${JSON.stringify(payload, null, 2)}
1334
- `;
1335
- await this.snapshotExistingProfileRecord(recordPath);
1336
- await this.writeAtomic(recordPath, serialized);
1337
- try {
1338
- await this.writeAtomic(`${recordPath}.bak`, serialized);
1339
- } catch (error) {
1340
- logWarn(`Failed to persist profile backup for '${record.name}':`, error);
734
+ const map = await this.readProfilesStateMap();
735
+ map[record.name] = this.toStateRecord(record);
736
+ await this.writeProfilesStateMap(map);
737
+ }
738
+ async deleteProfileRecord(name) {
739
+ const map = await this.readProfilesStateMap();
740
+ if (!Object.prototype.hasOwnProperty.call(map, name)) {
741
+ return;
1341
742
  }
743
+ delete map[name];
744
+ await this.writeProfilesStateMap(map);
1342
745
  }
1343
746
  resolveAuthMethod(data) {
1344
747
  const raw = typeof data?.auth_method === "string" ? data.auth_method.trim().toLowerCase() : "";
@@ -1629,6 +1032,7 @@ var ProfileManager = class {
1629
1032
  * Initialize the profile manager and create necessary directories
1630
1033
  */
1631
1034
  async initialize() {
1035
+ this.profileHomesRoot = (0, import_path2.join)(getUserDataDir(), "profile-homes");
1632
1036
  try {
1633
1037
  await import_fs.promises.mkdir(this.codexDir, { recursive: true });
1634
1038
  } catch (error) {
@@ -1642,189 +1046,6 @@ var ProfileManager = class {
1642
1046
  throw new Error("Failed to initialize profile manager");
1643
1047
  }
1644
1048
  await this.recoverActiveAuthBackup();
1645
- const migrationsComplete = await this.hasCompletedMigrations();
1646
- if (!migrationsComplete) {
1647
- await this.migrateLegacyProfiles();
1648
- await this.migrateProfilesFromDatabase();
1649
- await this.removeLegacyArtifacts();
1650
- await this.markMigrationsComplete();
1651
- }
1652
- }
1653
- /**
1654
- * Remove deprecated files that previously tracked the current profile.
1655
- * These are now redundant since we infer the active profile from auth.json.
1656
- */
1657
- async removeLegacyArtifacts() {
1658
- const legacyTargets = [
1659
- (0, import_path2.join)(this.codexDir, "current-profile.json"),
1660
- (0, import_path2.join)(this.profilesDir, ".current"),
1661
- `${this.activeAuth}.backup`,
1662
- (0, import_path2.join)(this.codexDir, "cache", "rate-limits")
1663
- ];
1664
- for (const target of legacyTargets) {
1665
- try {
1666
- await import_fs.promises.rm(target, { recursive: true, force: true });
1667
- } catch (error) {
1668
- if (!this.isNotFoundError(error)) {
1669
- logWarn(`Failed to remove legacy artifact '${target}':`, error);
1670
- }
1671
- }
1672
- }
1673
- try {
1674
- await import_fs.promises.rm(this.profilesDir, { recursive: true, force: true });
1675
- } catch (error) {
1676
- if (!this.isNotFoundError(error)) {
1677
- logWarn(`Failed to remove legacy profiles directory '${this.profilesDir}':`, error);
1678
- }
1679
- }
1680
- }
1681
- async hasCompletedMigrations() {
1682
- try {
1683
- await import_fs.promises.access(this.profileMigrationMarker);
1684
- return true;
1685
- } catch (error) {
1686
- if (!this.isNotFoundError(error)) {
1687
- logWarn("Failed to read profile migration marker:", error);
1688
- }
1689
- return false;
1690
- }
1691
- }
1692
- async markMigrationsComplete() {
1693
- try {
1694
- await import_fs.promises.mkdir(this.migrationsDir, { recursive: true });
1695
- await import_fs.promises.writeFile(this.profileMigrationMarker, "ok");
1696
- } catch (error) {
1697
- logWarn("Failed to persist profile migration marker:", error);
1698
- }
1699
- }
1700
- async migrateLegacyProfiles() {
1701
- let files;
1702
- try {
1703
- files = await import_fs.promises.readdir(this.profilesDir);
1704
- } catch (error) {
1705
- if (!this.isNotFoundError(error)) {
1706
- logWarn("Failed to inspect legacy profiles directory:", error);
1707
- }
1708
- return;
1709
- }
1710
- if (files.length === 0) {
1711
- return;
1712
- }
1713
- const accountCandidates = /* @__PURE__ */ new Map();
1714
- const orphanCandidates = [];
1715
- const insertedNames = /* @__PURE__ */ new Set();
1716
- for (const file of files) {
1717
- if (!file.endsWith(".json")) {
1718
- continue;
1719
- }
1720
- const name = file.replace(/\.json$/, "");
1721
- const filePath = (0, import_path2.join)(this.profilesDir, file);
1722
- try {
1723
- const raw = await import_fs.promises.readFile(filePath, "utf8");
1724
- const data = JSON.parse(raw);
1725
- const normalizedName = this.normalizeProfileName(name);
1726
- const metadata = this.extractProfileMetadata(data);
1727
- const resolvedEmail = this.resolveProfileEmail(data, metadata);
1728
- if (resolvedEmail) {
1729
- data.email = resolvedEmail;
1730
- }
1731
- const accountId = this.getAccountIdFromData(data);
1732
- const candidate = {
1733
- name: normalizedName,
1734
- data,
1735
- profile: this.buildProfileFromData(normalizedName, data),
1736
- accountId
1737
- };
1738
- if (accountId) {
1739
- const existing = accountCandidates.get(accountId);
1740
- if (existing) {
1741
- const preferred = this.selectPreferredProfile([existing.profile, candidate.profile]);
1742
- if (preferred === candidate.profile) {
1743
- logWarn(
1744
- `Replacing legacy profile '${existing.name}' with '${normalizedName}' for account '${accountId}'.`
1745
- );
1746
- accountCandidates.set(accountId, candidate);
1747
- } else {
1748
- logWarn(
1749
- `Skipping legacy profile '${name}' because account '${accountId}' already exists as '${existing.name}'.`
1750
- );
1751
- }
1752
- } else {
1753
- accountCandidates.set(accountId, candidate);
1754
- }
1755
- } else {
1756
- orphanCandidates.push(candidate);
1757
- }
1758
- await import_fs.promises.rm(filePath, { force: true });
1759
- } catch (error) {
1760
- logWarn(`Failed to migrate legacy profile '${name}':`, error);
1761
- }
1762
- }
1763
- const persistQueue = [...accountCandidates.values(), ...orphanCandidates];
1764
- const existingRecords = await this.listProfileRecords();
1765
- for (const record of existingRecords) {
1766
- insertedNames.add(record.name);
1767
- }
1768
- for (const candidate of persistQueue) {
1769
- if (insertedNames.has(candidate.name)) {
1770
- logWarn(`Skipping legacy profile '${candidate.name}' because it was already imported.`);
1771
- continue;
1772
- }
1773
- try {
1774
- await this.persistProfileRecord(candidate.name, candidate.data, candidate.profile.metadata);
1775
- insertedNames.add(candidate.name);
1776
- } catch (error) {
1777
- logWarn(`Failed to persist migrated profile '${candidate.name}':`, error);
1778
- }
1779
- }
1780
- }
1781
- async migrateProfilesFromDatabase() {
1782
- let rows = [];
1783
- try {
1784
- const { getDatabase: getDatabase2 } = await Promise.resolve().then(() => (init_sqlite_db(), sqlite_db_exports));
1785
- const db = await getDatabase2();
1786
- rows = db.prepare(
1787
- "SELECT name, data, account_id, workspace_id, workspace_name, email, auth_method, created_at, updated_at FROM profiles"
1788
- ).all();
1789
- } catch (error) {
1790
- logWarn("Failed to read existing database profiles for migration:", error);
1791
- return;
1792
- }
1793
- if (!rows || rows.length === 0) {
1794
- return;
1795
- }
1796
- const existingNames = new Set((await this.listProfileRecords()).map((record) => record.name));
1797
- for (const row of rows) {
1798
- const name = typeof row?.name === "string" ? row.name : null;
1799
- const serialized = typeof row?.data === "string" ? row.data : null;
1800
- if (!name || !serialized) {
1801
- continue;
1802
- }
1803
- let data;
1804
- try {
1805
- data = JSON.parse(serialized);
1806
- } catch (error) {
1807
- logWarn(`Failed to parse database profile '${name}' during migration:`, error);
1808
- continue;
1809
- }
1810
- try {
1811
- const normalizedName = this.normalizeProfileName(name);
1812
- if (existingNames.has(normalizedName)) {
1813
- continue;
1814
- }
1815
- data.created_at = data.created_at ?? (typeof row?.created_at === "string" ? row.created_at : void 0);
1816
- data.email = data.email ?? (typeof row?.email === "string" ? row.email : void 0);
1817
- data.auth_method = data.auth_method ?? (typeof row?.auth_method === "string" ? row.auth_method : void 0);
1818
- data.workspace_id = data.workspace_id ?? (typeof row?.workspace_id === "string" ? row.workspace_id : void 0);
1819
- data.workspace_name = data.workspace_name ?? (typeof row?.workspace_name === "string" ? row.workspace_name : void 0);
1820
- data.updated_at = data.updated_at ?? (typeof row?.updated_at === "string" ? row.updated_at : void 0);
1821
- const metadata = this.extractProfileMetadata(data);
1822
- await this.persistProfileRecord(normalizedName, data, metadata);
1823
- existingNames.add(normalizedName);
1824
- } catch (error) {
1825
- logWarn(`Failed to migrate database profile '${name}':`, error);
1826
- }
1827
- }
1828
1049
  }
1829
1050
  enqueueAuthSwap(task) {
1830
1051
  const run = this.authSwapLock.then(task, task);
@@ -2552,6 +1773,7 @@ var ProfileManager = class {
2552
1773
  name: targetName
2553
1774
  };
2554
1775
  await this.writeProfileRecord(updatedRecord);
1776
+ await this.deleteProfileRecord(sourceName);
2555
1777
  } catch (error) {
2556
1778
  logWarn(`Failed to rewrite profile record after renaming '${sourceName}' to '${targetName}':`, error);
2557
1779
  }
@@ -2596,6 +1818,7 @@ var ProfileManager = class {
2596
1818
  logWarn(`Failed to remove profile home for '${profileName}':`, error);
2597
1819
  }
2598
1820
  }
1821
+ await this.deleteProfileRecord(profileName);
2599
1822
  return true;
2600
1823
  }
2601
1824
  /**
@@ -2626,6 +1849,7 @@ var ProfileManager = class {
2626
1849
  for (const name of deleteCandidates) {
2627
1850
  try {
2628
1851
  await import_fs.promises.rm(this.getProfileHomePath(name), { recursive: true, force: true });
1852
+ await this.deleteProfileRecord(name);
2629
1853
  removed.push(name);
2630
1854
  } catch (error) {
2631
1855
  if (!this.isNotFoundError(error)) {
@@ -2719,62 +1943,139 @@ var ProfileManager = class {
2719
1943
  };
2720
1944
  }
2721
1945
  /**
2722
- * Export all profiles to a portable format.
1946
+ * Export profiles for cloud sync (includes token-bearing auth data).
2723
1947
  */
2724
- async exportAllProfiles() {
1948
+ async exportProfilesForCloudSync() {
2725
1949
  await this.initialize();
2726
1950
  const records = await this.listProfileRecords();
2727
- const exportedAt = (/* @__PURE__ */ new Date()).toISOString();
2728
1951
  return records.map((record) => ({
2729
1952
  name: record.name,
2730
1953
  displayName: record.displayName,
2731
- email: record.email,
1954
+ data: record.data,
1955
+ metadata: record.metadata,
2732
1956
  accountId: record.accountId,
2733
1957
  workspaceId: record.workspaceId,
2734
1958
  workspaceName: record.workspaceName,
1959
+ email: record.email,
2735
1960
  authMethod: record.authMethod,
2736
1961
  createdAt: record.createdAt,
2737
- metadata: record.metadata,
2738
- exportedAt
1962
+ updatedAt: record.updatedAt
2739
1963
  }));
2740
1964
  }
2741
- };
2742
-
1965
+ /**
1966
+ * Replace local profiles with a full snapshot from cloud sync.
1967
+ */
1968
+ async replaceProfilesFromCloudSync(records) {
1969
+ await this.initialize();
1970
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1971
+ const normalized = /* @__PURE__ */ new Map();
1972
+ const existingMap = await this.readProfilesStateMap();
1973
+ const existingNames = new Set(Object.keys(existingMap));
1974
+ for (const candidate of records ?? []) {
1975
+ try {
1976
+ const name = this.normalizeProfileName(candidate?.name ?? "");
1977
+ const data = candidate && typeof candidate.data === "object" && candidate.data !== null ? candidate.data : null;
1978
+ if (!data) {
1979
+ continue;
1980
+ }
1981
+ const metadata = candidate.metadata && typeof candidate.metadata === "object" ? candidate.metadata : void 0;
1982
+ const record = {
1983
+ name,
1984
+ displayName: typeof candidate.displayName === "string" ? candidate.displayName : null,
1985
+ data,
1986
+ metadata,
1987
+ accountId: typeof candidate.accountId === "string" ? candidate.accountId : null,
1988
+ workspaceId: typeof candidate.workspaceId === "string" ? candidate.workspaceId : null,
1989
+ workspaceName: typeof candidate.workspaceName === "string" ? candidate.workspaceName : null,
1990
+ email: typeof candidate.email === "string" ? candidate.email : null,
1991
+ authMethod: typeof candidate.authMethod === "string" ? candidate.authMethod : this.resolveAuthMethod(data),
1992
+ createdAt: typeof candidate.createdAt === "string" ? candidate.createdAt : now,
1993
+ updatedAt: typeof candidate.updatedAt === "string" ? candidate.updatedAt : now
1994
+ };
1995
+ normalized.set(name, record);
1996
+ } catch {
1997
+ }
1998
+ }
1999
+ const nextMap = {};
2000
+ for (const record of normalized.values()) {
2001
+ nextMap[record.name] = this.toStateRecord(record);
2002
+ }
2003
+ await this.writeProfilesStateMap(nextMap);
2004
+ let removed = 0;
2005
+ for (const name of existingNames) {
2006
+ if (normalized.has(name)) {
2007
+ continue;
2008
+ }
2009
+ try {
2010
+ await import_fs.promises.rm(this.getProfileHomePath(name), { recursive: true, force: true });
2011
+ removed += 1;
2012
+ } catch (error) {
2013
+ if (!this.isNotFoundError(error)) {
2014
+ logWarn(`Failed to remove profile '${name}' during cloud sync replace:`, error);
2015
+ }
2016
+ }
2017
+ }
2018
+ const preferred = await this.readPreferredProfileName();
2019
+ if (preferred) {
2020
+ try {
2021
+ const normalizedPreferred = this.normalizeProfileName(preferred);
2022
+ if (!normalized.has(normalizedPreferred)) {
2023
+ await this.persistPreferredProfileName(null);
2024
+ }
2025
+ } catch {
2026
+ await this.persistPreferredProfileName(null);
2027
+ }
2028
+ }
2029
+ return {
2030
+ imported: normalized.size,
2031
+ removed
2032
+ };
2033
+ }
2034
+ };
2035
+
2743
2036
  // ../../lib/license-service.ts
2744
2037
  var import_node_crypto2 = __toESM(require("crypto"));
2745
2038
 
2746
2039
  // ../../lib/license-secret.ts
2747
- var import_node_fs3 = require("fs");
2748
- var import_node_path3 = __toESM(require("path"));
2040
+ var import_node_fs2 = require("fs");
2041
+ var import_node_path2 = __toESM(require("path"));
2749
2042
  var import_node_crypto = __toESM(require("crypto"));
2750
2043
  var SECRET_FILE = "license.secret";
2751
- function resolveCodexDir2() {
2752
- const homeDir = process.env.HOME || process.env.USERPROFILE || "";
2753
- return homeDir ? import_node_path3.default.join(homeDir, ".codex") : ".codex";
2754
- }
2755
2044
  function resolveSecretPath() {
2756
- return import_node_path3.default.join(resolveCodexDir2(), SECRET_FILE);
2045
+ return import_node_path2.default.join(getUserDataDir(), SECRET_FILE);
2757
2046
  }
2758
2047
  async function generateSecret() {
2759
2048
  return import_node_crypto.default.randomBytes(32).toString("hex");
2760
2049
  }
2761
- async function getLicenseSecret() {
2762
- const secretPath = resolveSecretPath();
2050
+ async function readSecretIfExists(secretPath) {
2763
2051
  try {
2764
- const existing = await import_node_fs3.promises.readFile(secretPath, "utf8");
2052
+ const existing = await import_node_fs2.promises.readFile(secretPath, "utf8");
2765
2053
  const trimmed = existing.trim();
2766
2054
  if (trimmed.length > 0) {
2767
2055
  return trimmed;
2768
2056
  }
2769
- } catch {
2770
- }
2771
- const secret = await generateSecret();
2772
- try {
2773
- await import_node_fs3.promises.mkdir(import_node_path3.default.dirname(secretPath), { recursive: true });
2774
- } catch {
2057
+ return null;
2058
+ } catch (error) {
2059
+ if (error.code === "ENOENT") {
2060
+ return null;
2061
+ }
2062
+ throw error;
2775
2063
  }
2776
- await import_node_fs3.promises.writeFile(secretPath, `${secret}
2064
+ }
2065
+ async function writeSecret(secretPath, secret) {
2066
+ await import_node_fs2.promises.mkdir(import_node_path2.default.dirname(secretPath), { recursive: true });
2067
+ await import_node_fs2.promises.writeFile(secretPath, `${secret}
2777
2068
  `, { mode: 384 });
2069
+ await import_node_fs2.promises.chmod(secretPath, 384).catch(() => void 0);
2070
+ }
2071
+ async function getLicenseSecret() {
2072
+ const secretPath = resolveSecretPath();
2073
+ const existing = await readSecretIfExists(secretPath);
2074
+ if (existing) {
2075
+ return existing;
2076
+ }
2077
+ const secret = await generateSecret();
2078
+ await writeSecret(secretPath, secret);
2778
2079
  return secret;
2779
2080
  }
2780
2081
 
@@ -3062,63 +2363,1427 @@ var LicenseService = class {
3062
2363
  return fallback;
3063
2364
  }
3064
2365
  };
3065
- this.verificationPromise = verify();
2366
+ this.verificationPromise = verify();
2367
+ try {
2368
+ return await this.verificationPromise;
2369
+ } finally {
2370
+ this.verificationPromise = null;
2371
+ }
2372
+ }
2373
+ async activate(licenseKey) {
2374
+ const trimmed = licenseKey.trim();
2375
+ if (!trimmed) {
2376
+ throw new Error("License key is required.");
2377
+ }
2378
+ const secret = await getLicenseSecret();
2379
+ const digest = import_node_crypto2.default.createHash("sha256").update(trimmed).digest("hex");
2380
+ const result = await requestGumroadVerify(trimmed, "activation");
2381
+ ensureWithinUseLimit(result);
2382
+ const normalized = normalizeVerificationResult(result);
2383
+ const stored = {
2384
+ licenseKey: trimmed,
2385
+ purchaseEmail: normalized.email,
2386
+ lastVerifiedAt: nowIso(),
2387
+ nextCheckAt: new Date(Date.now() + LICENSE_REFRESH_INTERVAL_MS).toISOString(),
2388
+ status: "active",
2389
+ lastVerificationError: null
2390
+ };
2391
+ const signed = withSignature(stored, secret);
2392
+ await persistLicense(signed);
2393
+ const status = toLicenseStatus(signed, {
2394
+ message: `License verified (${digest.slice(0, 8)}).`
2395
+ });
2396
+ this.cache = status;
2397
+ return status;
2398
+ }
2399
+ applyProfileCount(status, profileCount) {
2400
+ const profilesRemaining = resolveProfilesRemaining(status.profileLimit, profileCount);
2401
+ return {
2402
+ ...status,
2403
+ profilesRemaining
2404
+ };
2405
+ }
2406
+ refreshStatusInBackground(options = {}) {
2407
+ if (this.refreshPromise) {
2408
+ return;
2409
+ }
2410
+ const forceRefresh = Boolean(options.force);
2411
+ this.refreshPromise = (async () => {
2412
+ try {
2413
+ await this.getStatus({ forceRefresh });
2414
+ } catch (error) {
2415
+ console.warn("Background license refresh failed:", error);
2416
+ } finally {
2417
+ this.refreshPromise = null;
2418
+ }
2419
+ })();
2420
+ }
2421
+ };
2422
+ var licenseService = new LicenseService();
2423
+
2424
+ // ../../lib/cloud-sync-service.ts
2425
+ var import_node_fs4 = require("fs");
2426
+ var import_node_path6 = __toESM(require("path"));
2427
+
2428
+ // ../../lib/cloud-sync-types.ts
2429
+ var CLOUD_SYNC_SCHEMA_VERSION = 1;
2430
+
2431
+ // ../../lib/type-guards.ts
2432
+ function isRecord3(value) {
2433
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2434
+ }
2435
+ function toIsoOrNull(value) {
2436
+ if (typeof value !== "string") {
2437
+ return null;
2438
+ }
2439
+ const parsed = Date.parse(value);
2440
+ return Number.isNaN(parsed) ? null : new Date(parsed).toISOString();
2441
+ }
2442
+
2443
+ // ../../lib/cloud-sync-client.ts
2444
+ var DEFAULT_CLOUD_SYNC_API_BASE_URL = "https://api.codexuse.com";
2445
+ var CLOUD_SYNC_TIMEOUT_MS = 15e3;
2446
+ var CloudSyncClientError = class extends Error {
2447
+ constructor(message, status = null) {
2448
+ super(message);
2449
+ this.name = "CloudSyncClientError";
2450
+ this.status = status;
2451
+ }
2452
+ };
2453
+ function normalizeBaseUrl(value) {
2454
+ const trimmed = value.trim();
2455
+ return trimmed.endsWith("/") ? trimmed.slice(0, -1) : trimmed;
2456
+ }
2457
+ function getCloudSyncApiBaseUrl() {
2458
+ const fromEnv = typeof process.env.CODEXUSE_API_BASE_URL === "string" ? process.env.CODEXUSE_API_BASE_URL : "";
2459
+ if (fromEnv.trim().length === 0) {
2460
+ return DEFAULT_CLOUD_SYNC_API_BASE_URL;
2461
+ }
2462
+ return normalizeBaseUrl(fromEnv);
2463
+ }
2464
+ function buildSyncUrl(pathname) {
2465
+ const baseUrl = getCloudSyncApiBaseUrl();
2466
+ const normalizedPath = pathname.startsWith("/") ? pathname : `/${pathname}`;
2467
+ return `${baseUrl}${normalizedPath}`;
2468
+ }
2469
+ function normalizeSnapshot(value) {
2470
+ if (!isRecord3(value)) {
2471
+ return null;
2472
+ }
2473
+ if (isRecord3(value.snapshot)) {
2474
+ const nested = normalizeSnapshot(value.snapshot);
2475
+ if (nested) {
2476
+ return nested;
2477
+ }
2478
+ }
2479
+ const updatedAt = toIsoOrNull(value.updatedAt);
2480
+ if (!updatedAt) {
2481
+ return null;
2482
+ }
2483
+ const schemaVersion = Number(value.schemaVersion);
2484
+ if (!Number.isFinite(schemaVersion) || schemaVersion !== CLOUD_SYNC_SCHEMA_VERSION) {
2485
+ return null;
2486
+ }
2487
+ const rawProfiles = Array.isArray(value.profiles) ? value.profiles : [];
2488
+ const profiles = rawProfiles.map((entry) => isRecord3(entry) ? entry : null).filter((entry) => Boolean(entry)).map((entry) => {
2489
+ const data = isRecord3(entry.data) ? entry.data : {};
2490
+ const metadata = isRecord3(entry.metadata) ? entry.metadata : void 0;
2491
+ return {
2492
+ name: typeof entry.name === "string" ? entry.name : "",
2493
+ displayName: typeof entry.displayName === "string" ? entry.displayName : null,
2494
+ data,
2495
+ metadata,
2496
+ accountId: typeof entry.accountId === "string" ? entry.accountId : null,
2497
+ workspaceId: typeof entry.workspaceId === "string" ? entry.workspaceId : null,
2498
+ workspaceName: typeof entry.workspaceName === "string" ? entry.workspaceName : null,
2499
+ email: typeof entry.email === "string" ? entry.email : null,
2500
+ authMethod: typeof entry.authMethod === "string" ? entry.authMethod : null,
2501
+ createdAt: typeof entry.createdAt === "string" ? entry.createdAt : null,
2502
+ updatedAt: typeof entry.updatedAt === "string" ? entry.updatedAt : null
2503
+ };
2504
+ }).filter((entry) => entry.name.trim().length > 0);
2505
+ const rawSettings = value.settingsJson;
2506
+ const settingsJson = isRecord3(rawSettings) ? rawSettings : null;
2507
+ return {
2508
+ schemaVersion: CLOUD_SYNC_SCHEMA_VERSION,
2509
+ updatedAt,
2510
+ profiles,
2511
+ configTomlContent: typeof value.configTomlContent === "string" ? value.configTomlContent : null,
2512
+ settingsJson
2513
+ };
2514
+ }
2515
+ async function requestJson(method, pathname, licenseKey, body, rawBody) {
2516
+ const controller = new AbortController();
2517
+ const timeout = setTimeout(() => controller.abort(), CLOUD_SYNC_TIMEOUT_MS);
2518
+ try {
2519
+ const response = await fetch(buildSyncUrl(pathname), {
2520
+ method,
2521
+ headers: {
2522
+ "Content-Type": "application/json",
2523
+ "x-codexuse-license-key": licenseKey
2524
+ },
2525
+ body: typeof rawBody === "string" ? rawBody : body === void 0 ? void 0 : JSON.stringify(body),
2526
+ signal: controller.signal
2527
+ });
2528
+ if (!response.ok) {
2529
+ const message = await response.text().catch(() => "");
2530
+ throw new CloudSyncClientError(
2531
+ message || `Cloud sync request failed (${response.status}).`,
2532
+ response.status
2533
+ );
2534
+ }
2535
+ return await response.json();
2536
+ } catch (error) {
2537
+ if (error instanceof CloudSyncClientError) {
2538
+ throw error;
2539
+ }
2540
+ if (error instanceof Error && error.name === "AbortError") {
2541
+ throw new CloudSyncClientError("Cloud sync request timed out.");
2542
+ }
2543
+ throw new CloudSyncClientError(
2544
+ error instanceof Error ? error.message : "Cloud sync request failed."
2545
+ );
2546
+ } finally {
2547
+ clearTimeout(timeout);
2548
+ }
2549
+ }
2550
+ async function fetchRemoteSnapshot(licenseKey) {
2551
+ const response = await requestJson("GET", "/v1/sync/snapshot", licenseKey);
2552
+ return normalizeSnapshot(response.snapshot);
2553
+ }
2554
+ async function fetchRemoteSnapshotMeta(licenseKey) {
2555
+ const response = await requestJson(
2556
+ "GET",
2557
+ "/v1/sync/snapshot/meta",
2558
+ licenseKey
2559
+ );
2560
+ return toIsoOrNull(response.updatedAt);
2561
+ }
2562
+ async function pushRemoteSnapshot(licenseKey, snapshot, options) {
2563
+ const serializedSnapshot = typeof options?.serializedSnapshot === "string" ? options.serializedSnapshot : JSON.stringify(snapshot);
2564
+ const response = await requestJson(
2565
+ "PUT",
2566
+ "/v1/sync/snapshot",
2567
+ licenseKey,
2568
+ void 0,
2569
+ `{"snapshot":${serializedSnapshot}}`
2570
+ );
2571
+ const normalized = normalizeSnapshot(response.snapshot);
2572
+ if (!normalized) {
2573
+ throw new CloudSyncClientError("Cloud sync server returned an invalid snapshot payload.");
2574
+ }
2575
+ const status = response.status === "stale" ? "stale" : "applied";
2576
+ return {
2577
+ status,
2578
+ snapshot: normalized
2579
+ };
2580
+ }
2581
+
2582
+ // ../../lib/codex-config.ts
2583
+ var import_promises2 = require("fs/promises");
2584
+ var import_node_path5 = __toESM(require("path"));
2585
+ var import_toml2 = require("@iarna/toml");
2586
+
2587
+ // ../../lib/codex-config-metadata.ts
2588
+ var import_node_child_process = require("child_process");
2589
+ var import_promises = require("fs/promises");
2590
+ var import_node_path4 = __toESM(require("path"));
2591
+
2592
+ // ../../lib/codex-cli.ts
2593
+ var import_node_fs3 = require("fs");
2594
+ var import_node_path3 = __toESM(require("path"));
2595
+ var cachedStatus = null;
2596
+ var cachedChannel = null;
2597
+ function fileExists(candidate) {
2598
+ if (!candidate) {
2599
+ return null;
2600
+ }
2601
+ const normalized = import_node_path3.default.resolve(candidate);
2602
+ try {
2603
+ const stats = (0, import_node_fs3.statSync)(normalized);
2604
+ if (stats.isFile()) {
2605
+ return normalized;
2606
+ }
2607
+ } catch {
2608
+ }
2609
+ return null;
2610
+ }
2611
+ function resolveBundledCodexBinary(channel) {
2612
+ const candidates = [];
2613
+ const processWithResources = process;
2614
+ const resourcesPath = processWithResources.resourcesPath;
2615
+ const packageSegments = channel === "alpha" ? ["@openai", "codex-alpha"] : ["@openai", "codex"];
2616
+ if (resourcesPath) {
2617
+ candidates.push(
2618
+ import_node_path3.default.join(resourcesPath, "app.asar.unpacked", "node_modules", ...packageSegments, "bin", "codex.js")
2619
+ );
2620
+ candidates.push(
2621
+ import_node_path3.default.join(resourcesPath, "app.asar.unpacked", "node_modules", ...packageSegments, "bin", "codex")
2622
+ );
2623
+ }
2624
+ const projectRoot = process.cwd();
2625
+ candidates.push(import_node_path3.default.join(projectRoot, "node_modules", ...packageSegments, "bin", "codex.js"));
2626
+ candidates.push(import_node_path3.default.join(projectRoot, "node_modules", ...packageSegments, "bin", "codex"));
2627
+ for (const candidate of candidates) {
2628
+ const resolved = fileExists(candidate);
2629
+ if (resolved) {
2630
+ return resolved;
2631
+ }
2632
+ }
2633
+ return null;
2634
+ }
2635
+ function resolveBundledCodexBinaryWithFallback(requestedChannel) {
2636
+ const direct = resolveBundledCodexBinary(requestedChannel);
2637
+ if (direct) {
2638
+ return { path: direct, resolvedChannel: requestedChannel, fallbackUsed: false };
2639
+ }
2640
+ if (requestedChannel === "alpha") {
2641
+ const stable = resolveBundledCodexBinary("stable");
2642
+ if (stable) {
2643
+ return { path: stable, resolvedChannel: "stable", fallbackUsed: true };
2644
+ }
2645
+ }
2646
+ return { path: null, resolvedChannel: null, fallbackUsed: false };
2647
+ }
2648
+ function buildUnavailableStatus(channel) {
2649
+ const channelLabel = channel === "alpha" ? "alpha " : "";
2650
+ const channelSuffix = channel === "alpha" ? " Switch to Stable in Settings or reinstall CodexUse to restore the CLI." : " Reinstall CodexUse to restore the CLI.";
2651
+ return {
2652
+ available: false,
2653
+ path: null,
2654
+ reason: `Bundled ${channelLabel}Codex CLI is missing.${channelSuffix}`,
2655
+ source: null,
2656
+ channel,
2657
+ requestedChannel: channel,
2658
+ fallbackUsed: false
2659
+ };
2660
+ }
2661
+ function evaluateCodexCliStatus(requestedChannel) {
2662
+ const resolved = resolveBundledCodexBinaryWithFallback(requestedChannel);
2663
+ if (resolved.path && resolved.resolvedChannel) {
2664
+ return {
2665
+ available: true,
2666
+ path: resolved.path,
2667
+ reason: null,
2668
+ source: "bundled",
2669
+ channel: resolved.resolvedChannel,
2670
+ requestedChannel,
2671
+ fallbackUsed: resolved.fallbackUsed
2672
+ };
2673
+ }
2674
+ return buildUnavailableStatus(requestedChannel);
2675
+ }
2676
+ async function refreshCodexStatus() {
2677
+ const channel = await getCodexCliChannel();
2678
+ cachedChannel = channel;
2679
+ cachedStatus = evaluateCodexCliStatus(channel);
2680
+ return { ...cachedStatus };
2681
+ }
2682
+ var CodexCliMissingError = class extends Error {
2683
+ constructor(status, message) {
2684
+ super(message ?? status.reason ?? "Codex CLI is not available");
2685
+ Object.setPrototypeOf(this, new.target.prototype);
2686
+ this.name = "CodexCliMissingError";
2687
+ this.status = { ...status };
2688
+ }
2689
+ };
2690
+ async function requireCodexCli() {
2691
+ const status = await refreshCodexStatus();
2692
+ if (!status.available || !status.path) {
2693
+ throw new CodexCliMissingError(status);
2694
+ }
2695
+ return status.path;
2696
+ }
2697
+
2698
+ // ../../lib/codex-config-metadata.ts
2699
+ var CONFIG_SCHEMA_URL = "https://raw.githubusercontent.com/openai/codex/main/codex-rs/core/config.schema.json";
2700
+ var METADATA_CACHE_TTL_MS = 6e4;
2701
+ var FALLBACK_SCHEMA_TOP_LEVEL_KEYS = [
2702
+ "agents",
2703
+ "analytics",
2704
+ "approval_policy",
2705
+ "apps",
2706
+ "chatgpt_base_url",
2707
+ "check_for_update_on_startup",
2708
+ "cli_auth_credentials_store",
2709
+ "compact_prompt",
2710
+ "developer_instructions",
2711
+ "disable_paste_burst",
2712
+ "experimental_compact_prompt_file",
2713
+ "experimental_use_freeform_apply_patch",
2714
+ "experimental_use_unified_exec_tool",
2715
+ "features",
2716
+ "feedback",
2717
+ "file_opener",
2718
+ "forced_chatgpt_workspace_id",
2719
+ "forced_login_method",
2720
+ "ghost_snapshot",
2721
+ "hide_agent_reasoning",
2722
+ "history",
2723
+ "instructions",
2724
+ "log_dir",
2725
+ "mcp_oauth_callback_port",
2726
+ "mcp_oauth_credentials_store",
2727
+ "mcp_servers",
2728
+ "model",
2729
+ "model_auto_compact_token_limit",
2730
+ "model_context_window",
2731
+ "model_instructions_file",
2732
+ "model_provider",
2733
+ "model_providers",
2734
+ "model_reasoning_effort",
2735
+ "model_reasoning_summary",
2736
+ "model_supports_reasoning_summaries",
2737
+ "model_verbosity",
2738
+ "notice",
2739
+ "notify",
2740
+ "oss_provider",
2741
+ "otel",
2742
+ "personality",
2743
+ "profile",
2744
+ "profiles",
2745
+ "project_doc_fallback_filenames",
2746
+ "project_doc_max_bytes",
2747
+ "project_root_markers",
2748
+ "projects",
2749
+ "review_model",
2750
+ "sandbox_mode",
2751
+ "sandbox_workspace_write",
2752
+ "shell_environment_policy",
2753
+ "show_raw_agent_reasoning",
2754
+ "skills",
2755
+ "suppress_unstable_features_warning",
2756
+ "tool_output_token_limit",
2757
+ "tools",
2758
+ "tui",
2759
+ "web_search",
2760
+ "windows_wsl_setup_acknowledged"
2761
+ ];
2762
+ var FALLBACK_FEATURE_KEYS = [
2763
+ "apply_patch_freeform",
2764
+ "apps",
2765
+ "child_agents_md",
2766
+ "collab",
2767
+ "collaboration_modes",
2768
+ "connectors",
2769
+ "elevated_windows_sandbox",
2770
+ "enable_experimental_windows_sandbox",
2771
+ "enable_request_compression",
2772
+ "experimental_use_freeform_apply_patch",
2773
+ "experimental_use_unified_exec_tool",
2774
+ "experimental_windows_sandbox",
2775
+ "include_apply_patch_tool",
2776
+ "memory_tool",
2777
+ "personality",
2778
+ "powershell_utf8",
2779
+ "remote_models",
2780
+ "request_rule",
2781
+ "responses_websockets",
2782
+ "responses_websockets_v2",
2783
+ "runtime_metrics",
2784
+ "search_tool",
2785
+ "shell_snapshot",
2786
+ "shell_tool",
2787
+ "skill_env_var_dependency_prompt",
2788
+ "skill_mcp_dependency_install",
2789
+ "sqlite",
2790
+ "steer",
2791
+ "undo",
2792
+ "unified_exec",
2793
+ "use_linux_sandbox_bwrap",
2794
+ "web_search",
2795
+ "web_search_cached",
2796
+ "web_search_request"
2797
+ ];
2798
+ var metadataCache = null;
2799
+ var metadataCacheAt = 0;
2800
+ function isRecord4(value) {
2801
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2802
+ }
2803
+ function uniqueSorted(values) {
2804
+ return Array.from(new Set(values)).sort((a, b) => a.localeCompare(b));
2805
+ }
2806
+ async function runCommand(command, args, timeoutMs = 3e3) {
2807
+ return new Promise((resolve, reject) => {
2808
+ const child = (0, import_node_child_process.spawn)(command, args, {
2809
+ stdio: ["ignore", "pipe", "pipe"],
2810
+ env: process.env
2811
+ });
2812
+ let stdout = "";
2813
+ let stderr = "";
2814
+ child.stdout?.on("data", (chunk) => {
2815
+ stdout += chunk.toString();
2816
+ });
2817
+ child.stderr?.on("data", (chunk) => {
2818
+ stderr += chunk.toString();
2819
+ });
2820
+ const timeout = setTimeout(() => {
2821
+ child.kill();
2822
+ reject(new Error(`Command timed out: ${command} ${args.join(" ")}`));
2823
+ }, timeoutMs);
2824
+ child.on("error", (error) => {
2825
+ clearTimeout(timeout);
2826
+ reject(error);
2827
+ });
2828
+ child.on("close", (code) => {
2829
+ clearTimeout(timeout);
2830
+ if (code !== 0 && !stdout.trim() && stderr.trim()) {
2831
+ resolve({ stdout: stderr, exitCode: code });
2832
+ return;
2833
+ }
2834
+ resolve({ stdout, exitCode: code });
2835
+ });
2836
+ });
2837
+ }
2838
+ async function runCodexCommand(args) {
2839
+ const codexPath = await requireCodexCli();
2840
+ const isJsLauncher = codexPath.endsWith(".js");
2841
+ const command = isJsLauncher ? process.execPath : codexPath;
2842
+ const commandArgs = isJsLauncher ? [codexPath, ...args] : args;
2843
+ return runCommand(command, commandArgs);
2844
+ }
2845
+ function parseFeatureCatalog(output) {
2846
+ const entries = [];
2847
+ const lines = output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
2848
+ for (const line of lines) {
2849
+ const match = line.match(/^(\S+)\s+(stable|experimental|deprecated|under development)\s+(true|false)$/i);
2850
+ if (!match) {
2851
+ continue;
2852
+ }
2853
+ const [, key, stageRaw, enabledRaw] = match;
2854
+ const stage = stageRaw.toLowerCase();
2855
+ entries.push({
2856
+ key,
2857
+ stage,
2858
+ enabled: enabledRaw === "true"
2859
+ });
2860
+ }
2861
+ return entries.sort((a, b) => a.key.localeCompare(b.key));
2862
+ }
2863
+ function parseSchemaKeys(schemaRaw) {
2864
+ const parsed = JSON.parse(schemaRaw);
2865
+ if (!isRecord4(parsed)) {
2866
+ return {
2867
+ topLevelKeys: FALLBACK_SCHEMA_TOP_LEVEL_KEYS,
2868
+ featureKeys: FALLBACK_FEATURE_KEYS
2869
+ };
2870
+ }
2871
+ const properties = isRecord4(parsed.properties) ? parsed.properties : {};
2872
+ const topLevelKeys = uniqueSorted(Object.keys(properties));
2873
+ const features = isRecord4(properties.features) ? properties.features : {};
2874
+ const featureProperties = isRecord4(features.properties) ? features.properties : {};
2875
+ const featureKeys = uniqueSorted(Object.keys(featureProperties));
2876
+ return {
2877
+ topLevelKeys: topLevelKeys.length > 0 ? topLevelKeys : FALLBACK_SCHEMA_TOP_LEVEL_KEYS,
2878
+ featureKeys: featureKeys.length > 0 ? featureKeys : FALLBACK_FEATURE_KEYS
2879
+ };
2880
+ }
2881
+ async function readSchemaFromLocal() {
2882
+ const candidates = [
2883
+ process.env.CODEX_CONFIG_SCHEMA_PATH,
2884
+ import_node_path4.default.join(process.cwd(), "codex-rs", "core", "config.schema.json"),
2885
+ import_node_path4.default.join(process.cwd(), "node_modules", "@openai", "codex", "codex-rs", "core", "config.schema.json"),
2886
+ import_node_path4.default.join(process.cwd(), "node_modules", "@openai", "codex-alpha", "codex-rs", "core", "config.schema.json")
2887
+ ].filter((candidate) => Boolean(candidate));
2888
+ for (const candidate of candidates) {
2889
+ try {
2890
+ return await (0, import_promises.readFile)(candidate, "utf8");
2891
+ } catch {
2892
+ }
2893
+ }
2894
+ return null;
2895
+ }
2896
+ async function readSchemaFromRemote() {
2897
+ const controller = new AbortController();
2898
+ const timeout = setTimeout(() => controller.abort(), 2500);
2899
+ try {
2900
+ const response = await fetch(CONFIG_SCHEMA_URL, {
2901
+ signal: controller.signal,
2902
+ headers: {
2903
+ "User-Agent": "codexuse-desktop"
2904
+ }
2905
+ });
2906
+ if (!response.ok) {
2907
+ return null;
2908
+ }
2909
+ return await response.text();
2910
+ } catch {
2911
+ return null;
2912
+ } finally {
2913
+ clearTimeout(timeout);
2914
+ }
2915
+ }
2916
+ async function readSchemaKeys() {
2917
+ const local = await readSchemaFromLocal();
2918
+ if (local) {
2919
+ try {
2920
+ return parseSchemaKeys(local);
2921
+ } catch {
2922
+ }
2923
+ }
2924
+ const remote = await readSchemaFromRemote();
2925
+ if (remote) {
2926
+ try {
2927
+ return parseSchemaKeys(remote);
2928
+ } catch {
2929
+ }
2930
+ }
2931
+ return {
2932
+ topLevelKeys: FALLBACK_SCHEMA_TOP_LEVEL_KEYS,
2933
+ featureKeys: FALLBACK_FEATURE_KEYS
2934
+ };
2935
+ }
2936
+ async function readCodexVersion() {
2937
+ try {
2938
+ const { stdout } = await runCodexCommand(["--version"]);
2939
+ const value = stdout.trim();
2940
+ return value.length > 0 ? value : null;
2941
+ } catch {
2942
+ return null;
2943
+ }
2944
+ }
2945
+ async function readFeatureCatalog() {
2946
+ try {
2947
+ const { stdout } = await runCodexCommand(["features", "list"]);
2948
+ return parseFeatureCatalog(stdout);
2949
+ } catch {
2950
+ return [];
2951
+ }
2952
+ }
2953
+ async function getCodexConfigMetadata(forceRefresh = false) {
2954
+ const now = Date.now();
2955
+ if (!forceRefresh && metadataCache && now - metadataCacheAt < METADATA_CACHE_TTL_MS) {
2956
+ return metadataCache;
2957
+ }
2958
+ const [schemaKeys, featureCatalog, codexVersion] = await Promise.all([
2959
+ readSchemaKeys(),
2960
+ readFeatureCatalog(),
2961
+ readCodexVersion()
2962
+ ]);
2963
+ const catalogMap = /* @__PURE__ */ new Map();
2964
+ for (const entry of featureCatalog) {
2965
+ catalogMap.set(entry.key, entry);
2966
+ }
2967
+ for (const featureKey of schemaKeys.featureKeys) {
2968
+ if (!catalogMap.has(featureKey)) {
2969
+ catalogMap.set(featureKey, {
2970
+ key: featureKey,
2971
+ stage: "unknown",
2972
+ enabled: null
2973
+ });
2974
+ }
2975
+ }
2976
+ const mergedFeatureCatalog = Array.from(catalogMap.values()).sort((a, b) => a.key.localeCompare(b.key));
2977
+ const metadata = {
2978
+ schemaTopLevelKeys: uniqueSorted(schemaKeys.topLevelKeys),
2979
+ schemaFeatureKeys: uniqueSorted(schemaKeys.featureKeys),
2980
+ featureCatalog: mergedFeatureCatalog,
2981
+ codexVersion,
2982
+ fetchedAt: now
2983
+ };
2984
+ metadataCache = metadata;
2985
+ metadataCacheAt = now;
2986
+ return metadata;
2987
+ }
2988
+
2989
+ // ../../lib/errors.ts
2990
+ var ANSI_PATTERN = /\u001B\[[0-9;]*m/g;
2991
+ function stripAnsi(value) {
2992
+ return value.replace(ANSI_PATTERN, "");
2993
+ }
2994
+ function normalizeWhitespace(value) {
2995
+ return value.replace(/\s+/g, " ").trim();
2996
+ }
2997
+ function formatUserFacingError(error, options = {}) {
2998
+ const fallback = options.fallback ?? "Something went wrong. Please try again.";
2999
+ if (error === null || typeof error === "undefined") {
3000
+ return fallback;
3001
+ }
3002
+ const rawMessage = typeof error === "string" ? error : error instanceof Error ? error.message : String(error);
3003
+ let cleaned = normalizeWhitespace(stripAnsi(rawMessage));
3004
+ if (!cleaned) {
3005
+ return fallback;
3006
+ }
3007
+ if (cleaned.startsWith("Error invoking remote method")) {
3008
+ cleaned = cleaned.replace(/^Error invoking remote method '[^']+': Error: /, "");
3009
+ }
3010
+ const lower = cleaned.toLowerCase();
3011
+ if (options.notFoundFallback && (lower.includes("enoent") || lower.includes("not found"))) {
3012
+ return options.notFoundFallback;
3013
+ }
3014
+ if (lower.includes("rate limit") || lower.includes("quota exceeded") || lower.includes("too many requests") || lower.includes("429")) {
3015
+ cleaned = "Rate limit exceeded. Please wait a moment or upgrade your plan.";
3016
+ } else if (lower.includes("timeout") || lower.includes("timed out")) {
3017
+ cleaned = "Request timed out. Check your connection and try again.";
3018
+ } else if (lower.includes("network") || lower.includes("econn") || lower.includes("networkerror") || lower.includes("connection")) {
3019
+ cleaned = "Network issue. Check your connection and retry.";
3020
+ } else if (lower.includes("permission") || lower.includes("eacces")) {
3021
+ cleaned = "Permission denied. Restart CodexUse and retry.";
3022
+ } else if (lower.includes("openai codex") && lower.includes("workdir:")) {
3023
+ if (lower.includes("error:")) {
3024
+ const parts = cleaned.split(/error:/i);
3025
+ if (parts.length > 1) {
3026
+ cleaned = parts[parts.length - 1].trim();
3027
+ }
3028
+ } else {
3029
+ cleaned = "Codex CLI failed to respond. Please try again.";
3030
+ }
3031
+ }
3032
+ const maxLength = typeof options.maxLength === "number" ? options.maxLength : 220;
3033
+ if (cleaned.length > maxLength) {
3034
+ cleaned = `${cleaned.slice(0, maxLength).trimEnd()}\u2026`;
3035
+ }
3036
+ return cleaned;
3037
+ }
3038
+
3039
+ // ../../lib/codex-config.ts
3040
+ var DEFAULT_CONFIG_TEMPLATE = [
3041
+ "# ~/.codex/config.toml (managed by CodexUse)",
3042
+ "# Safe defaults with prompts and sandboxed access.",
3043
+ "# To enable full access, opt into the YOLO preset from settings.",
3044
+ "",
3045
+ 'model = "gpt-5.3-codex"',
3046
+ 'review_model = "gpt-5.3-codex"',
3047
+ 'model_reasoning_effort = "medium"',
3048
+ 'model_reasoning_summary = "auto"',
3049
+ 'model_verbosity = "medium"',
3050
+ 'approval_policy = "on-request"',
3051
+ 'sandbox_mode = "read-only"',
3052
+ 'web_search = "cached"',
3053
+ ""
3054
+ ].join("\n");
3055
+ var YOLO_PRESET = {
3056
+ model: "gpt-5.3-codex",
3057
+ reviewModel: "gpt-5.3-codex",
3058
+ modelReasoningEffort: "xhigh",
3059
+ modelReasoningSummary: "detailed",
3060
+ modelVerbosity: "high",
3061
+ approvalPolicy: "never",
3062
+ sandboxMode: "danger-full-access",
3063
+ webSearch: "live",
3064
+ personality: "pragmatic",
3065
+ toolOutputTokenLimit: 25e3,
3066
+ modelAutoCompactTokenLimit: 233e3,
3067
+ features: {
3068
+ unifiedExec: true,
3069
+ shellSnapshot: true
3070
+ },
3071
+ notice: {
3072
+ hideFullAccessWarning: true,
3073
+ hideGpt51MigrationPrompt: true,
3074
+ hideRateLimitModelNudge: true
3075
+ }
3076
+ };
3077
+ function resolveHomeDir() {
3078
+ const homeDir = process.env.HOME || process.env.USERPROFILE;
3079
+ if (!homeDir) {
3080
+ throw new Error("Unable to determine home directory for Codex config.");
3081
+ }
3082
+ return homeDir;
3083
+ }
3084
+ function getConfigPath() {
3085
+ const homeDir = resolveHomeDir();
3086
+ return import_node_path5.default.join(homeDir, ".codex", "config.toml");
3087
+ }
3088
+ async function ensureConfigDirExists(filePath) {
3089
+ const dir = import_node_path5.default.dirname(filePath);
3090
+ await (0, import_promises2.mkdir)(dir, { recursive: true });
3091
+ }
3092
+ function normalizeLineEndings(content) {
3093
+ return content.replace(/\r\n/g, "\n");
3094
+ }
3095
+ function ensureTrailingNewline(content) {
3096
+ return content.endsWith("\n") ? content : `${content}
3097
+ `;
3098
+ }
3099
+ function tryParseToml(content) {
3100
+ try {
3101
+ const parsed = (0, import_toml2.parse)(content);
3102
+ return { data: parsed ?? {}, error: null };
3103
+ } catch (error) {
3104
+ const maybe = error;
3105
+ const line = typeof maybe?.line === "number" ? maybe.line : null;
3106
+ const column = typeof maybe?.column === "number" ? maybe.column : typeof maybe?.col === "number" ? maybe.col : null;
3107
+ const location = line !== null ? `line ${line}${column !== null ? `, column ${column}` : ""}` : null;
3108
+ const friendly = formatUserFacingError(error, {
3109
+ fallback: "Invalid config.toml syntax. Fix it and try again.",
3110
+ maxLength: 180
3111
+ });
3112
+ const message = location ? `${friendly} (near ${location}).` : friendly;
3113
+ return { data: null, error: message };
3114
+ }
3115
+ }
3116
+ function toStringOrNull(value) {
3117
+ if (typeof value !== "string") {
3118
+ return null;
3119
+ }
3120
+ const trimmed = value.trim();
3121
+ return trimmed.length > 0 ? trimmed : null;
3122
+ }
3123
+ function toBooleanOrNull(value) {
3124
+ if (typeof value === "boolean") {
3125
+ return value;
3126
+ }
3127
+ return null;
3128
+ }
3129
+ function toNumberOrNull(value) {
3130
+ if (typeof value !== "number") {
3131
+ return null;
3132
+ }
3133
+ return Number.isFinite(value) ? value : null;
3134
+ }
3135
+ function toStringArray(value) {
3136
+ if (!Array.isArray(value)) {
3137
+ return [];
3138
+ }
3139
+ return value.map((entry) => typeof entry === "string" ? entry.trim() : "").filter((entry) => entry.length > 0);
3140
+ }
3141
+ function toStringRecord(value) {
3142
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
3143
+ return {};
3144
+ }
3145
+ const result = {};
3146
+ for (const [key, raw] of Object.entries(value)) {
3147
+ if (typeof raw !== "string") {
3148
+ continue;
3149
+ }
3150
+ const normalizedKey = key.trim();
3151
+ const normalizedValue = raw.trim();
3152
+ if (!normalizedKey || !normalizedValue) {
3153
+ continue;
3154
+ }
3155
+ result[normalizedKey] = normalizedValue;
3156
+ }
3157
+ return result;
3158
+ }
3159
+ function buildSettingsSummary(data) {
3160
+ const noticeCandidate = data && typeof data["notice"] === "object" && data["notice"] !== null ? data["notice"] : null;
3161
+ const featuresCandidate = data && typeof data["features"] === "object" && data["features"] !== null && !Array.isArray(data["features"]) ? data["features"] : null;
3162
+ const toolsCandidate = data && typeof data["tools"] === "object" && data["tools"] !== null && !Array.isArray(data["tools"]) ? data["tools"] : null;
3163
+ const featureValues = {};
3164
+ if (featuresCandidate) {
3165
+ for (const [key, value] of Object.entries(featuresCandidate)) {
3166
+ featureValues[key] = toBooleanOrNull(value);
3167
+ }
3168
+ }
3169
+ const modelMigrationsRaw = noticeCandidate ? toStringRecord(noticeCandidate["model_migrations"]) : {};
3170
+ return {
3171
+ model: data ? toStringOrNull(data["model"]) : null,
3172
+ reviewModel: data ? toStringOrNull(data["review_model"]) : null,
3173
+ modelReasoningEffort: data ? toStringOrNull(data["model_reasoning_effort"]) : null,
3174
+ modelReasoningSummary: data ? toStringOrNull(data["model_reasoning_summary"]) : null,
3175
+ modelVerbosity: data ? toStringOrNull(data["model_verbosity"]) : null,
3176
+ approvalPolicy: data ? toStringOrNull(data["approval_policy"]) : null,
3177
+ sandboxMode: data ? toStringOrNull(data["sandbox_mode"]) : null,
3178
+ webSearch: data ? toStringOrNull(data["web_search"]) : null,
3179
+ personality: data ? toStringOrNull(data["personality"]) : null,
3180
+ toolOutputTokenLimit: data ? toNumberOrNull(data["tool_output_token_limit"]) : null,
3181
+ modelAutoCompactTokenLimit: data ? toNumberOrNull(data["model_auto_compact_token_limit"]) : null,
3182
+ modelContextWindow: data ? toNumberOrNull(data["model_context_window"]) : null,
3183
+ features: {
3184
+ unifiedExec: featuresCandidate ? toBooleanOrNull(featuresCandidate["unified_exec"]) : null,
3185
+ shellSnapshot: featuresCandidate ? toBooleanOrNull(featuresCandidate["shell_snapshot"]) : null,
3186
+ values: featureValues
3187
+ },
3188
+ tools: {
3189
+ webSearch: toolsCandidate ? toBooleanOrNull(toolsCandidate["web_search"]) : null,
3190
+ viewImage: toolsCandidate ? toBooleanOrNull(toolsCandidate["view_image"]) : null
3191
+ },
3192
+ notice: {
3193
+ hideFullAccessWarning: noticeCandidate ? toBooleanOrNull(noticeCandidate["hide_full_access_warning"]) : null,
3194
+ hideGpt51MigrationPrompt: noticeCandidate ? toBooleanOrNull(noticeCandidate["hide_gpt5_1_migration_prompt"]) : null,
3195
+ hideLegacyGpt51CodexMaxMigrationPrompt: noticeCandidate ? toBooleanOrNull(noticeCandidate["hide_gpt-5.1-codex-max_migration_prompt"]) : null,
3196
+ hideRateLimitModelNudge: noticeCandidate ? toBooleanOrNull(noticeCandidate["hide_rate_limit_model_nudge"]) : null,
3197
+ modelMigrations: Object.keys(modelMigrationsRaw).length > 0 ? modelMigrationsRaw : null
3198
+ }
3199
+ };
3200
+ }
3201
+ function parseRmcpClientEnabled(data) {
3202
+ const rawFeatures = data && typeof data["features"] === "object" && data["features"] !== null && !Array.isArray(data["features"]) ? data["features"] : null;
3203
+ if (rawFeatures && typeof rawFeatures["rmcp_client"] === "boolean") {
3204
+ return rawFeatures["rmcp_client"];
3205
+ }
3206
+ if (data && typeof data["experimental_use_rmcp_client"] === "boolean") {
3207
+ return data["experimental_use_rmcp_client"];
3208
+ }
3209
+ return false;
3210
+ }
3211
+ function parseMcpServer(name, rawValue) {
3212
+ if (typeof rawValue !== "object" || rawValue === null || Array.isArray(rawValue)) {
3213
+ return null;
3214
+ }
3215
+ const raw = rawValue;
3216
+ const typeValue = toStringOrNull(raw["type"]);
3217
+ const command = toStringOrNull(raw["command"]);
3218
+ const args = toStringArray(raw["args"]);
3219
+ const env = toStringRecord(raw["env"]);
3220
+ const envVars = toStringArray(raw["env_vars"]);
3221
+ const cwd = toStringOrNull(raw["cwd"]);
3222
+ const url = toStringOrNull(raw["url"]);
3223
+ const bearerTokenEnvVar = toStringOrNull(raw["bearer_token_env_var"]);
3224
+ const httpHeaders = toStringRecord(raw["http_headers"]);
3225
+ const envHttpHeaders = toStringRecord(raw["env_http_headers"]);
3226
+ const startupTimeoutSec = toNumberOrNull(raw["startup_timeout_sec"]);
3227
+ const toolTimeoutSec = toNumberOrNull(raw["tool_timeout_sec"]);
3228
+ const enabledTools = toStringArray(raw["enabled_tools"]);
3229
+ const disabledTools = toStringArray(raw["disabled_tools"]);
3230
+ const enabled = typeof raw["enabled"] === "boolean" ? raw["enabled"] : true;
3231
+ const transport = typeValue === "stdio" || typeValue === "http" || typeValue === "sse" ? typeValue : url ? "http" : "stdio";
3232
+ return {
3233
+ name,
3234
+ transport,
3235
+ enabled,
3236
+ command,
3237
+ args: args.length > 0 ? args : null,
3238
+ cwd,
3239
+ env: Object.keys(env).length > 0 ? env : null,
3240
+ envVars: envVars.length > 0 ? envVars : null,
3241
+ url,
3242
+ bearerTokenEnvVar,
3243
+ httpHeaders: Object.keys(httpHeaders).length > 0 ? httpHeaders : null,
3244
+ envHttpHeaders: Object.keys(envHttpHeaders).length > 0 ? envHttpHeaders : null,
3245
+ startupTimeoutSec,
3246
+ toolTimeoutSec,
3247
+ enabledTools: enabledTools.length > 0 ? enabledTools : null,
3248
+ disabledTools: disabledTools.length > 0 ? disabledTools : null
3249
+ };
3250
+ }
3251
+ function buildMcpServersList(data) {
3252
+ const rawServers = data?.["mcp_servers"];
3253
+ if (typeof rawServers !== "object" || rawServers === null || Array.isArray(rawServers)) {
3254
+ return [];
3255
+ }
3256
+ const servers = [];
3257
+ for (const [name, rawValue] of Object.entries(rawServers)) {
3258
+ const parsed = parseMcpServer(name, rawValue);
3259
+ if (parsed) {
3260
+ servers.push(parsed);
3261
+ }
3262
+ }
3263
+ return servers;
3264
+ }
3265
+ function getFeatureStage(metadata, featureKey) {
3266
+ const found = metadata.featureCatalog.find((entry) => entry.key === featureKey);
3267
+ return found?.stage ?? "unknown";
3268
+ }
3269
+ function looksLikeSecretValue(value) {
3270
+ const normalized = value.trim();
3271
+ if (normalized.length < 12) {
3272
+ return false;
3273
+ }
3274
+ if (/\s/.test(normalized)) {
3275
+ return false;
3276
+ }
3277
+ if (!/^[A-Za-z0-9._:-]+$/.test(normalized)) {
3278
+ return false;
3279
+ }
3280
+ const hasLetters = /[A-Za-z]/.test(normalized);
3281
+ const hasNumbers = /\d/.test(normalized);
3282
+ return hasLetters && hasNumbers;
3283
+ }
3284
+ function buildConfigDiagnostics(data, summary, mcpServers, metadata) {
3285
+ const diagnostics = [];
3286
+ const featureTable = data && typeof data["features"] === "object" && data["features"] !== null && !Array.isArray(data["features"]) ? data["features"] : null;
3287
+ const knownTopLevelKeys = new Set(metadata.schemaTopLevelKeys);
3288
+ if (data) {
3289
+ for (const key of Object.keys(data)) {
3290
+ if (knownTopLevelKeys.has(key)) {
3291
+ continue;
3292
+ }
3293
+ diagnostics.push({
3294
+ id: `unknown-top-level:${key}`,
3295
+ category: "config",
3296
+ severity: "warning",
3297
+ title: `Unknown config key: ${key}`,
3298
+ message: `This top-level key is not in the known Codex schema and may be ignored by Codex CLI.`,
3299
+ fixId: null,
3300
+ fixLabel: null
3301
+ });
3302
+ }
3303
+ }
3304
+ if (featureTable) {
3305
+ const knownFeatureKeys = new Set(metadata.schemaFeatureKeys);
3306
+ for (const [featureKey] of Object.entries(featureTable)) {
3307
+ if (!knownFeatureKeys.has(featureKey)) {
3308
+ diagnostics.push({
3309
+ id: `unknown-feature:${featureKey}`,
3310
+ category: "feature",
3311
+ severity: "warning",
3312
+ title: `Unknown feature flag: features.${featureKey}`,
3313
+ message: "This feature is not recognized in the current schema. Keeping it is non-destructive, but it may do nothing.",
3314
+ fixId: `remove-feature:${featureKey}`,
3315
+ fixLabel: "Remove flag"
3316
+ });
3317
+ continue;
3318
+ }
3319
+ const stage = getFeatureStage(metadata, featureKey);
3320
+ if (stage === "deprecated") {
3321
+ diagnostics.push({
3322
+ id: `deprecated-feature:${featureKey}`,
3323
+ category: "feature",
3324
+ severity: "warning",
3325
+ title: `Deprecated feature flag: features.${featureKey}`,
3326
+ message: "Codex marks this feature as deprecated. Prefer removing it.",
3327
+ fixId: `remove-feature:${featureKey}`,
3328
+ fixLabel: "Remove deprecated flag"
3329
+ });
3330
+ }
3331
+ }
3332
+ }
3333
+ for (const server of mcpServers) {
3334
+ if (server.transport === "stdio" && server.args) {
3335
+ for (let index = 0; index < server.args.length; index += 1) {
3336
+ const value = server.args[index] ?? "";
3337
+ if (/(api[-_]?key|token|secret)\s*=\s*/i.test(value)) {
3338
+ diagnostics.push({
3339
+ id: `mcp-secret-arg:${server.name}:${index}`,
3340
+ category: "security",
3341
+ severity: "warning",
3342
+ title: `Potential secret in MCP args (${server.name})`,
3343
+ message: "Argument text appears to contain a literal token/API key. Move secrets to environment variables.",
3344
+ fixId: null,
3345
+ fixLabel: null
3346
+ });
3347
+ }
3348
+ }
3349
+ }
3350
+ if (server.httpHeaders) {
3351
+ for (const [header, headerValue] of Object.entries(server.httpHeaders)) {
3352
+ if (!looksLikeSecretValue(headerValue)) {
3353
+ continue;
3354
+ }
3355
+ diagnostics.push({
3356
+ id: `mcp-secret-header:${server.name}:${header}`,
3357
+ category: "security",
3358
+ severity: "warning",
3359
+ title: `Potential secret in MCP header (${server.name})`,
3360
+ message: `HTTP header '${header}' appears to contain a literal secret. Prefer env_http_headers or bearer_token_env_var.`,
3361
+ fixId: null,
3362
+ fixLabel: null
3363
+ });
3364
+ }
3365
+ }
3366
+ }
3367
+ if (summary.approvalPolicy === "never" && summary.sandboxMode === "danger-full-access") {
3368
+ diagnostics.push({
3369
+ id: "risk:yolo-combo",
3370
+ category: "risk",
3371
+ severity: "risk",
3372
+ title: "High-risk execution mode enabled",
3373
+ message: "approval_policy=never + sandbox_mode=danger-full-access removes approval and sandbox guardrails.",
3374
+ fixId: null,
3375
+ fixLabel: null
3376
+ });
3377
+ }
3378
+ return diagnostics;
3379
+ }
3380
+ function isYoloApplied(summary) {
3381
+ return summary.model === YOLO_PRESET.model && summary.reviewModel === YOLO_PRESET.reviewModel && summary.modelReasoningEffort === YOLO_PRESET.modelReasoningEffort && summary.modelReasoningSummary === YOLO_PRESET.modelReasoningSummary && summary.modelVerbosity === YOLO_PRESET.modelVerbosity && summary.approvalPolicy === YOLO_PRESET.approvalPolicy && summary.sandboxMode === YOLO_PRESET.sandboxMode && summary.webSearch === YOLO_PRESET.webSearch && summary.personality === YOLO_PRESET.personality && summary.toolOutputTokenLimit === YOLO_PRESET.toolOutputTokenLimit && summary.modelAutoCompactTokenLimit === YOLO_PRESET.modelAutoCompactTokenLimit && summary.features.unifiedExec === true && summary.features.shellSnapshot === true && summary.notice.hideFullAccessWarning === true && summary.notice.hideGpt51MigrationPrompt === true && summary.notice.hideRateLimitModelNudge === true;
3382
+ }
3383
+ async function readConfigContent() {
3384
+ const configPath = getConfigPath();
3385
+ try {
3386
+ const content = await (0, import_promises2.readFile)(configPath, "utf8");
3387
+ return {
3388
+ exists: true,
3389
+ content: normalizeLineEndings(content)
3390
+ };
3391
+ } catch (error) {
3392
+ const nodeError = error;
3393
+ if (nodeError.code === "ENOENT") {
3394
+ return {
3395
+ exists: false,
3396
+ content: DEFAULT_CONFIG_TEMPLATE
3397
+ };
3398
+ }
3399
+ throw error;
3400
+ }
3401
+ }
3402
+ async function writeConfigContent(content) {
3403
+ const configPath = getConfigPath();
3404
+ await ensureConfigDirExists(configPath);
3405
+ const normalized = ensureTrailingNewline(normalizeLineEndings(content));
3406
+ await (0, import_promises2.writeFile)(configPath, normalized, "utf8");
3407
+ }
3408
+ async function getUpdatedAt(configPath, exists) {
3409
+ if (!exists) {
3410
+ return null;
3411
+ }
3412
+ try {
3413
+ const stats = await (0, import_promises2.stat)(configPath);
3414
+ return stats.mtimeMs;
3415
+ } catch (error) {
3416
+ const nodeError = error;
3417
+ if (nodeError.code === "ENOENT") {
3418
+ return null;
3419
+ }
3420
+ throw error;
3421
+ }
3422
+ }
3423
+ async function buildSnapshot(content, exists) {
3424
+ const configPath = getConfigPath();
3425
+ const parsed = tryParseToml(content);
3426
+ const summary = buildSettingsSummary(parsed.data);
3427
+ const updatedAt = await getUpdatedAt(configPath, exists);
3428
+ const mcpServers = buildMcpServersList(parsed.data);
3429
+ const rmcpClientEnabled = parseRmcpClientEnabled(parsed.data);
3430
+ const metadata = await getCodexConfigMetadata();
3431
+ const diagnostics = parsed.error ? [] : buildConfigDiagnostics(parsed.data, summary, mcpServers, metadata);
3432
+ return {
3433
+ path: configPath,
3434
+ exists,
3435
+ content,
3436
+ settings: summary,
3437
+ mcpServers,
3438
+ rmcpClientEnabled,
3439
+ parseError: parsed.error,
3440
+ isYoloPreset: parsed.error ? false : isYoloApplied(summary),
3441
+ updatedAt,
3442
+ metadata,
3443
+ diagnostics
3444
+ };
3445
+ }
3446
+ async function getCodexConfigSnapshot() {
3447
+ const { content, exists } = await readConfigContent();
3448
+ return buildSnapshot(content, exists);
3449
+ }
3450
+ async function saveCodexConfigContent(content) {
3451
+ const normalized = ensureTrailingNewline(normalizeLineEndings(content));
3452
+ const parsed = tryParseToml(normalized);
3453
+ if (!parsed.data) {
3454
+ throw new Error(parsed.error ?? "config.toml contains invalid TOML syntax.");
3455
+ }
3456
+ await writeConfigContent(normalized);
3457
+ return buildSnapshot(normalized, true);
3458
+ }
3459
+
3460
+ // ../../lib/cloud-sync-service.ts
3461
+ var SYNC_BACKUP_DIR = "sync-backups";
3462
+ var PRE_PULL_BACKUP_PREFIX = "pre-pull-";
3463
+ var PRE_PULL_BACKUP_SUFFIX = ".json";
3464
+ var PRE_PULL_BACKUP_KEEP_COUNT = 3;
3465
+ var SYNC_SIZE_WARN_BYTES = 1 * 1024 * 1024;
3466
+ var SYNC_SIZE_MAX_BYTES = 5 * 1024 * 1024;
3467
+ var MB_DIVISOR = 1024 * 1024;
3468
+ function mapProfilesFromAppState(raw) {
3469
+ if (!isRecord3(raw)) {
3470
+ return [];
3471
+ }
3472
+ const profiles = [];
3473
+ for (const [name, value] of Object.entries(raw)) {
3474
+ if (!isRecord3(value)) {
3475
+ continue;
3476
+ }
3477
+ const data = isRecord3(value.data) ? value.data : null;
3478
+ if (!data) {
3479
+ continue;
3480
+ }
3481
+ const normalizedName = typeof name === "string" ? name.trim() : "";
3482
+ if (!normalizedName) {
3483
+ continue;
3484
+ }
3485
+ profiles.push({
3486
+ name: normalizedName,
3487
+ displayName: typeof value.displayName === "string" ? value.displayName : null,
3488
+ data,
3489
+ metadata: isRecord3(value.metadata) ? value.metadata : void 0,
3490
+ accountId: typeof value.accountId === "string" ? value.accountId : null,
3491
+ workspaceId: typeof value.workspaceId === "string" ? value.workspaceId : null,
3492
+ workspaceName: typeof value.workspaceName === "string" ? value.workspaceName : null,
3493
+ email: typeof value.email === "string" ? value.email : null,
3494
+ authMethod: typeof value.authMethod === "string" ? value.authMethod : null,
3495
+ createdAt: typeof value.createdAt === "string" ? value.createdAt : null,
3496
+ updatedAt: typeof value.updatedAt === "string" ? value.updatedAt : null
3497
+ });
3498
+ }
3499
+ return profiles;
3500
+ }
3501
+ function summarizeSnapshot(snapshot) {
3502
+ const configBytes = typeof snapshot.configTomlContent === "string" ? snapshot.configTomlContent.length : 0;
3503
+ const settingsKeys = isRecord3(snapshot.settingsJson) ? Object.keys(snapshot.settingsJson).length : 0;
3504
+ return {
3505
+ profiles: snapshot.profiles.length,
3506
+ configBytes,
3507
+ settingsKeys
3508
+ };
3509
+ }
3510
+ async function readState() {
3511
+ const state = await getAppState();
3512
+ return {
3513
+ lastPushAt: state.sync.lastPushAt ?? void 0,
3514
+ lastPullAt: state.sync.lastPullAt ?? void 0,
3515
+ lastError: state.sync.lastError ?? void 0,
3516
+ remoteUpdatedAt: state.sync.remoteUpdatedAt ?? void 0
3517
+ };
3518
+ }
3519
+ async function writeState(patch) {
3520
+ await patchAppState({
3521
+ sync: {
3522
+ ...typeof patch.lastPushAt === "string" ? { lastPushAt: patch.lastPushAt } : {},
3523
+ ...typeof patch.lastPullAt === "string" ? { lastPullAt: patch.lastPullAt } : {},
3524
+ ...typeof patch.lastError === "string" ? { lastError: patch.lastError } : patch.lastError === void 0 ? { lastError: null } : {},
3525
+ ...typeof patch.remoteUpdatedAt === "string" ? { remoteUpdatedAt: patch.remoteUpdatedAt } : {}
3526
+ }
3527
+ });
3528
+ }
3529
+ async function resolveEligibility() {
3530
+ const stored = await getStoredLicense();
3531
+ const licenseKey = typeof stored?.licenseKey === "string" ? stored.licenseKey.trim() : "";
3532
+ if (!licenseKey) {
3533
+ return {
3534
+ canSync: false,
3535
+ reason: "License key is required for cloud sync.",
3536
+ licenseKey: null,
3537
+ licenseState: null
3538
+ };
3539
+ }
3540
+ const status = await licenseService.getStatus();
3541
+ const active = status.state === "active" || status.state === "grace";
3542
+ if (!active) {
3543
+ return {
3544
+ canSync: false,
3545
+ reason: "Cloud sync requires an active Pro license.",
3546
+ licenseKey,
3547
+ licenseState: status.state
3548
+ };
3549
+ }
3550
+ return {
3551
+ canSync: true,
3552
+ reason: null,
3553
+ licenseKey,
3554
+ licenseState: status.state
3555
+ };
3556
+ }
3557
+ function formatMegabytes(bytes) {
3558
+ return (bytes / MB_DIVISOR).toFixed(2);
3559
+ }
3560
+ function resolveSyncBackupsDir() {
3561
+ return import_node_path6.default.join(getUserDataDir(), SYNC_BACKUP_DIR);
3562
+ }
3563
+ function toSafeIsoForFileName(iso) {
3564
+ return iso.replace(/:/g, "-");
3565
+ }
3566
+ async function pruneOldPrePullBackups(backupsDir) {
3567
+ const entries = await import_node_fs4.promises.readdir(backupsDir, { withFileTypes: true });
3568
+ const files = entries.filter(
3569
+ (entry) => entry.isFile() && entry.name.startsWith(PRE_PULL_BACKUP_PREFIX) && entry.name.endsWith(PRE_PULL_BACKUP_SUFFIX)
3570
+ ).map((entry) => entry.name).sort((a, b) => b.localeCompare(a));
3571
+ if (files.length <= PRE_PULL_BACKUP_KEEP_COUNT) {
3572
+ return;
3573
+ }
3574
+ for (const stale of files.slice(PRE_PULL_BACKUP_KEEP_COUNT)) {
3575
+ await import_node_fs4.promises.rm(import_node_path6.default.join(backupsDir, stale), { force: true });
3576
+ }
3577
+ }
3578
+ async function createPrePullBackup(profileManager) {
3579
+ const snapshot = await buildLocalSnapshot(profileManager, {
3580
+ enforcePushGuards: false
3581
+ });
3582
+ const backupsDir = resolveSyncBackupsDir();
3583
+ await import_node_fs4.promises.mkdir(backupsDir, { recursive: true });
3584
+ const timestamp = toSafeIsoForFileName((/* @__PURE__ */ new Date()).toISOString());
3585
+ const backupPath = import_node_path6.default.join(
3586
+ backupsDir,
3587
+ `${PRE_PULL_BACKUP_PREFIX}${timestamp}${PRE_PULL_BACKUP_SUFFIX}`
3588
+ );
3589
+ await import_node_fs4.promises.writeFile(backupPath, `${JSON.stringify(snapshot, null, 2)}
3590
+ `, "utf8");
3591
+ await pruneOldPrePullBackups(backupsDir);
3592
+ logInfo("[cloud-sync] created pre-pull backup", { backupPath });
3593
+ return backupPath;
3594
+ }
3595
+ async function buildLocalSnapshot(profileManager, options = {}) {
3596
+ const enforcePushGuards = options.enforcePushGuards !== false;
3597
+ const profilesFromManager = await profileManager.exportProfilesForCloudSync();
3598
+ const state = await getAppState();
3599
+ const profilesFromState = mapProfilesFromAppState(state.profilesByName);
3600
+ const profiles = profilesFromManager.length > 0 ? profilesFromManager : profilesFromState;
3601
+ const config = await getCodexConfigSnapshot();
3602
+ const settingsJson = await readCodexSettingsJsonRaw();
3603
+ const hasProfiles = profiles.length > 0;
3604
+ const hasConfig = typeof config.content === "string" && config.content.trim().length > 0;
3605
+ const hasSettings = isRecord3(settingsJson) && Object.keys(settingsJson).length > 0;
3606
+ if (enforcePushGuards && !hasProfiles && !hasConfig && !hasSettings) {
3607
+ throw new Error("Refusing to push an empty cloud sync snapshot.");
3608
+ }
3609
+ const snapshot = {
3610
+ schemaVersion: CLOUD_SYNC_SCHEMA_VERSION,
3611
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
3612
+ profiles,
3613
+ configTomlContent: config.exists ? config.content : null,
3614
+ settingsJson
3615
+ };
3616
+ if (!enforcePushGuards) {
3617
+ return snapshot;
3618
+ }
3619
+ return snapshot;
3620
+ }
3621
+ async function applyRemoteSnapshot(profileManager, snapshot) {
3622
+ await profileManager.replaceProfilesFromCloudSync(snapshot.profiles ?? []);
3623
+ if (typeof snapshot.configTomlContent === "string") {
3624
+ await saveCodexConfigContent(snapshot.configTomlContent);
3625
+ }
3626
+ if (snapshot.settingsJson && isRecord3(snapshot.settingsJson)) {
3627
+ await writeCodexSettingsJsonRaw(snapshot.settingsJson);
3628
+ } else {
3629
+ await writeCodexSettingsJsonRaw({});
3630
+ }
3631
+ }
3632
+ function toRunError(mode, error) {
3633
+ const message = formatUserFacingError(error, {
3634
+ fallback: `Cloud sync ${mode} failed.`
3635
+ });
3636
+ return {
3637
+ mode,
3638
+ status: "error",
3639
+ message,
3640
+ localUpdatedAt: null,
3641
+ remoteUpdatedAt: null
3642
+ };
3643
+ }
3644
+ async function getCloudSyncStatus() {
3645
+ const eligibility = await resolveEligibility();
3646
+ const state = await readState();
3647
+ let remoteUpdatedAt = state.remoteUpdatedAt ?? null;
3648
+ if (eligibility.canSync && eligibility.licenseKey) {
3066
3649
  try {
3067
- return await this.verificationPromise;
3068
- } finally {
3069
- this.verificationPromise = null;
3650
+ remoteUpdatedAt = await fetchRemoteSnapshotMeta(eligibility.licenseKey);
3651
+ } catch (error) {
3652
+ if (error instanceof CloudSyncClientError && error.status === 404) {
3653
+ try {
3654
+ const remote = await fetchRemoteSnapshot(eligibility.licenseKey);
3655
+ remoteUpdatedAt = remote?.updatedAt ?? null;
3656
+ } catch {
3657
+ }
3658
+ }
3070
3659
  }
3071
3660
  }
3072
- async activate(licenseKey) {
3073
- const trimmed = licenseKey.trim();
3074
- if (!trimmed) {
3075
- throw new Error("License key is required.");
3076
- }
3077
- const secret = await getLicenseSecret();
3078
- const digest = import_node_crypto2.default.createHash("sha256").update(trimmed).digest("hex");
3079
- const result = await requestGumroadVerify(trimmed, "activation");
3080
- ensureWithinUseLimit(result);
3081
- const normalized = normalizeVerificationResult(result);
3082
- const stored = {
3083
- licenseKey: trimmed,
3084
- purchaseEmail: normalized.email,
3085
- lastVerifiedAt: nowIso(),
3086
- nextCheckAt: new Date(Date.now() + LICENSE_REFRESH_INTERVAL_MS).toISOString(),
3087
- status: "active",
3088
- lastVerificationError: null
3661
+ return {
3662
+ endpoint: getCloudSyncApiBaseUrl(),
3663
+ canSync: eligibility.canSync,
3664
+ reason: eligibility.reason,
3665
+ licenseState: eligibility.licenseState,
3666
+ hasLicenseKey: Boolean(eligibility.licenseKey),
3667
+ lastPushAt: state.lastPushAt ?? null,
3668
+ lastPullAt: state.lastPullAt ?? null,
3669
+ lastError: state.lastError ?? null,
3670
+ remoteUpdatedAt
3671
+ };
3672
+ }
3673
+ async function pushCloudSync() {
3674
+ const eligibility = await resolveEligibility();
3675
+ if (!eligibility.canSync || !eligibility.licenseKey) {
3676
+ return {
3677
+ mode: "push",
3678
+ status: "skipped",
3679
+ message: eligibility.reason ?? "Cloud sync unavailable.",
3680
+ localUpdatedAt: null,
3681
+ remoteUpdatedAt: null
3089
3682
  };
3090
- const signed = withSignature(stored, secret);
3091
- await persistLicense(signed);
3092
- const status = toLicenseStatus(signed, {
3093
- message: `License verified (${digest.slice(0, 8)}).`
3683
+ }
3684
+ const profileManager = new ProfileManager();
3685
+ try {
3686
+ await profileManager.initialize();
3687
+ const localSnapshot = await buildLocalSnapshot(profileManager, { enforcePushGuards: true });
3688
+ const serializedSnapshot = JSON.stringify(localSnapshot);
3689
+ const snapshotBytes = Buffer.byteLength(serializedSnapshot, "utf8");
3690
+ if (snapshotBytes > SYNC_SIZE_WARN_BYTES) {
3691
+ logWarn("[cloud-sync] push snapshot is large", {
3692
+ bytes: snapshotBytes,
3693
+ megabytes: formatMegabytes(snapshotBytes)
3694
+ });
3695
+ }
3696
+ if (snapshotBytes > SYNC_SIZE_MAX_BYTES) {
3697
+ throw new Error(
3698
+ `Snapshot too large to sync (${formatMegabytes(snapshotBytes)} MB). Reduce profiles or config size.`
3699
+ );
3700
+ }
3701
+ if (process.env.NODE_ENV !== "production") {
3702
+ logInfo("[cloud-sync] push snapshot summary", summarizeSnapshot(localSnapshot));
3703
+ }
3704
+ const remote = await pushRemoteSnapshot(eligibility.licenseKey, localSnapshot, {
3705
+ serializedSnapshot
3094
3706
  });
3095
- this.cache = status;
3096
- return status;
3707
+ await writeState({
3708
+ lastPushAt: (/* @__PURE__ */ new Date()).toISOString(),
3709
+ remoteUpdatedAt: remote.snapshot.updatedAt,
3710
+ lastError: void 0
3711
+ });
3712
+ if (remote.status === "stale") {
3713
+ return {
3714
+ mode: "push",
3715
+ status: "stale",
3716
+ message: "Remote cloud snapshot is newer. Pull first.",
3717
+ localUpdatedAt: localSnapshot.updatedAt,
3718
+ remoteUpdatedAt: remote.snapshot.updatedAt
3719
+ };
3720
+ }
3721
+ return {
3722
+ mode: "push",
3723
+ status: "applied",
3724
+ message: "Cloud sync push completed.",
3725
+ localUpdatedAt: localSnapshot.updatedAt,
3726
+ remoteUpdatedAt: remote.snapshot.updatedAt
3727
+ };
3728
+ } catch (error) {
3729
+ const result = toRunError("push", error);
3730
+ const lastError = error instanceof CloudSyncClientError ? error.message : result.message;
3731
+ await writeState({ lastError });
3732
+ return result;
3097
3733
  }
3098
- applyProfileCount(status, profileCount) {
3099
- const profilesRemaining = resolveProfilesRemaining(status.profileLimit, profileCount);
3734
+ }
3735
+ async function pullCloudSync() {
3736
+ const eligibility = await resolveEligibility();
3737
+ if (!eligibility.canSync || !eligibility.licenseKey) {
3100
3738
  return {
3101
- ...status,
3102
- profilesRemaining
3739
+ mode: "pull",
3740
+ status: "skipped",
3741
+ message: eligibility.reason ?? "Cloud sync unavailable.",
3742
+ localUpdatedAt: null,
3743
+ remoteUpdatedAt: null
3103
3744
  };
3104
3745
  }
3105
- refreshStatusInBackground(options = {}) {
3106
- if (this.refreshPromise) {
3107
- return;
3746
+ const profileManager = new ProfileManager();
3747
+ try {
3748
+ await profileManager.initialize();
3749
+ const remote = await fetchRemoteSnapshot(eligibility.licenseKey);
3750
+ if (!remote) {
3751
+ return {
3752
+ mode: "pull",
3753
+ status: "skipped",
3754
+ message: "No cloud snapshot found.",
3755
+ localUpdatedAt: null,
3756
+ remoteUpdatedAt: null
3757
+ };
3108
3758
  }
3109
- const forceRefresh = Boolean(options.force);
3110
- this.refreshPromise = (async () => {
3111
- try {
3112
- await this.getStatus({ forceRefresh });
3113
- } catch (error) {
3114
- console.warn("Background license refresh failed:", error);
3115
- } finally {
3116
- this.refreshPromise = null;
3117
- }
3118
- })();
3759
+ let backupWarningSuffix = "";
3760
+ try {
3761
+ await createPrePullBackup(profileManager);
3762
+ } catch (backupError) {
3763
+ logWarn("[cloud-sync] pre-pull backup failed; continuing pull", backupError);
3764
+ const reason = formatUserFacingError(backupError, { fallback: "unknown error" });
3765
+ backupWarningSuffix = ` Warning: pre-pull backup failed (${reason}).`;
3766
+ }
3767
+ await applyRemoteSnapshot(profileManager, remote);
3768
+ await writeState({
3769
+ lastPullAt: (/* @__PURE__ */ new Date()).toISOString(),
3770
+ remoteUpdatedAt: remote.updatedAt,
3771
+ lastError: void 0
3772
+ });
3773
+ return {
3774
+ mode: "pull",
3775
+ status: "applied",
3776
+ message: `Cloud sync pull completed.${backupWarningSuffix}`,
3777
+ localUpdatedAt: remote.updatedAt,
3778
+ remoteUpdatedAt: remote.updatedAt
3779
+ };
3780
+ } catch (error) {
3781
+ const result = toRunError("pull", error);
3782
+ const lastError = error instanceof CloudSyncClientError ? error.message : result.message;
3783
+ await writeState({ lastError });
3784
+ return result;
3119
3785
  }
3120
- };
3121
- var licenseService = new LicenseService();
3786
+ }
3122
3787
 
3123
3788
  // ../../lib/license-guard.ts
3124
3789
  async function assertProfileCreationAllowed(profileManager) {
@@ -3133,20 +3798,20 @@ async function assertProfileCreationAllowed(profileManager) {
3133
3798
  }
3134
3799
 
3135
3800
  // src/codex-cli.ts
3136
- var import_node_child_process = require("child_process");
3137
- var import_node_fs4 = require("fs");
3138
- var import_node_path4 = __toESM(require("path"));
3801
+ var import_node_child_process2 = require("child_process");
3802
+ var import_node_fs5 = require("fs");
3803
+ var import_node_path7 = __toESM(require("path"));
3139
3804
  var ENV_HINTS = ["CODEX_BINARY", "CODEX_CLI_PATH", "CODEX_PATH"];
3140
3805
  var NODE_MODULE_CANDIDATES = [
3141
3806
  ["@openai", "codex"],
3142
3807
  ["@openai", "codex-alpha"]
3143
3808
  ];
3144
- function fileExists(candidate) {
3809
+ function fileExists2(candidate) {
3145
3810
  if (!candidate) return null;
3146
- const resolved = import_node_path4.default.resolve(candidate);
3811
+ const resolved = import_node_path7.default.resolve(candidate);
3147
3812
  try {
3148
- const stat = (0, import_node_fs4.statSync)(resolved);
3149
- if (stat.isFile()) return resolved;
3813
+ const stat2 = (0, import_node_fs5.statSync)(resolved);
3814
+ if (stat2.isFile()) return resolved;
3150
3815
  } catch {
3151
3816
  return null;
3152
3817
  }
@@ -3156,49 +3821,49 @@ function resolveFromEnv() {
3156
3821
  for (const key of ENV_HINTS) {
3157
3822
  const value = process.env[key];
3158
3823
  if (!value) continue;
3159
- const resolved = fileExists(value);
3824
+ const resolved = fileExists2(value);
3160
3825
  if (resolved) return resolved;
3161
3826
  }
3162
3827
  return null;
3163
3828
  }
3164
3829
  function resolveFromAppResources() {
3165
3830
  const roots = [
3166
- import_node_path4.default.resolve(__dirname, ".."),
3167
- import_node_path4.default.resolve(__dirname, "..", "..")
3831
+ import_node_path7.default.resolve(__dirname, ".."),
3832
+ import_node_path7.default.resolve(__dirname, "..", "..")
3168
3833
  ];
3169
3834
  for (const root of roots) {
3170
- const base = import_node_path4.default.join(root, "app.asar.unpacked", "node_modules");
3835
+ const base = import_node_path7.default.join(root, "app.asar.unpacked", "node_modules");
3171
3836
  for (const segments of NODE_MODULE_CANDIDATES) {
3172
- const candidate = fileExists(import_node_path4.default.join(base, ...segments, "bin", "codex"));
3837
+ const candidate = fileExists2(import_node_path7.default.join(base, ...segments, "bin", "codex"));
3173
3838
  if (candidate) return candidate;
3174
- const jsCandidate = fileExists(import_node_path4.default.join(base, ...segments, "bin", "codex.js"));
3839
+ const jsCandidate = fileExists2(import_node_path7.default.join(base, ...segments, "bin", "codex.js"));
3175
3840
  if (jsCandidate) return jsCandidate;
3176
3841
  }
3177
3842
  }
3178
3843
  return null;
3179
3844
  }
3180
3845
  function resolveFromNodeModules() {
3181
- let current = import_node_path4.default.resolve(__dirname, "..");
3846
+ let current = import_node_path7.default.resolve(__dirname, "..");
3182
3847
  let last = "";
3183
3848
  while (current !== last) {
3184
3849
  for (const segments of NODE_MODULE_CANDIDATES) {
3185
- const candidate = fileExists(import_node_path4.default.join(current, "node_modules", ...segments, "bin", "codex"));
3850
+ const candidate = fileExists2(import_node_path7.default.join(current, "node_modules", ...segments, "bin", "codex"));
3186
3851
  if (candidate) return candidate;
3187
- const jsCandidate = fileExists(import_node_path4.default.join(current, "node_modules", ...segments, "bin", "codex.js"));
3852
+ const jsCandidate = fileExists2(import_node_path7.default.join(current, "node_modules", ...segments, "bin", "codex.js"));
3188
3853
  if (jsCandidate) return jsCandidate;
3189
3854
  }
3190
3855
  last = current;
3191
- current = import_node_path4.default.dirname(current);
3856
+ current = import_node_path7.default.dirname(current);
3192
3857
  }
3193
3858
  return null;
3194
3859
  }
3195
3860
  function resolveFromPath() {
3196
3861
  const pathValue = process.env.PATH ?? "";
3197
- const entries = pathValue.split(import_node_path4.default.delimiter).filter(Boolean);
3862
+ const entries = pathValue.split(import_node_path7.default.delimiter).filter(Boolean);
3198
3863
  const names = process.platform === "win32" ? ["codex.exe", "codex.cmd", "codex.bat", "codex"] : ["codex"];
3199
3864
  for (const entry of entries) {
3200
3865
  for (const name of names) {
3201
- const candidate = fileExists(import_node_path4.default.join(entry, name));
3866
+ const candidate = fileExists2(import_node_path7.default.join(entry, name));
3202
3867
  if (candidate) return candidate;
3203
3868
  }
3204
3869
  }
@@ -3247,7 +3912,7 @@ async function runCodexLogin(mode) {
3247
3912
  const resolvedMode = resolveLoginMode(mode ?? null);
3248
3913
  const loginArgs = resolvedMode === "device" ? ["login", "--device-auth"] : ["login"];
3249
3914
  const { command, args, shell } = buildCodexCommand(codexPath, loginArgs);
3250
- const child = (0, import_node_child_process.spawn)(command, args, {
3915
+ const child = (0, import_node_child_process2.spawn)(command, args, {
3251
3916
  stdio: "inherit",
3252
3917
  env: process.env,
3253
3918
  shell
@@ -3261,120 +3926,12 @@ async function runCodexLogin(mode) {
3261
3926
  }
3262
3927
 
3263
3928
  // ../../lib/codex-rpc.ts
3264
- var import_node_child_process2 = require("child_process");
3929
+ var import_node_child_process3 = require("child_process");
3265
3930
  var import_node_readline = __toESM(require("readline"));
3266
3931
  var import_node_events = require("events");
3267
3932
  var import_node_fs6 = require("fs");
3268
- var import_node_os = __toESM(require("os"));
3269
- var import_node_path6 = __toESM(require("path"));
3270
-
3271
- // ../../lib/codex-cli.ts
3272
- var import_node_fs5 = require("fs");
3273
- var import_node_path5 = __toESM(require("path"));
3274
- var cachedStatus = null;
3275
- var cachedChannel = null;
3276
- function fileExists2(candidate) {
3277
- if (!candidate) {
3278
- return null;
3279
- }
3280
- const normalized = import_node_path5.default.resolve(candidate);
3281
- try {
3282
- const stats = (0, import_node_fs5.statSync)(normalized);
3283
- if (stats.isFile()) {
3284
- return normalized;
3285
- }
3286
- } catch {
3287
- }
3288
- return null;
3289
- }
3290
- function resolveBundledCodexBinary(channel) {
3291
- const candidates = [];
3292
- const processWithResources = process;
3293
- const resourcesPath = processWithResources.resourcesPath;
3294
- const packageSegments = channel === "alpha" ? ["@openai", "codex-alpha"] : ["@openai", "codex"];
3295
- if (resourcesPath) {
3296
- candidates.push(
3297
- import_node_path5.default.join(resourcesPath, "app.asar.unpacked", "node_modules", ...packageSegments, "bin", "codex.js")
3298
- );
3299
- candidates.push(
3300
- import_node_path5.default.join(resourcesPath, "app.asar.unpacked", "node_modules", ...packageSegments, "bin", "codex")
3301
- );
3302
- }
3303
- const projectRoot = process.cwd();
3304
- candidates.push(import_node_path5.default.join(projectRoot, "node_modules", ...packageSegments, "bin", "codex.js"));
3305
- candidates.push(import_node_path5.default.join(projectRoot, "node_modules", ...packageSegments, "bin", "codex"));
3306
- for (const candidate of candidates) {
3307
- const resolved = fileExists2(candidate);
3308
- if (resolved) {
3309
- return resolved;
3310
- }
3311
- }
3312
- return null;
3313
- }
3314
- function resolveBundledCodexBinaryWithFallback(requestedChannel) {
3315
- const direct = resolveBundledCodexBinary(requestedChannel);
3316
- if (direct) {
3317
- return { path: direct, resolvedChannel: requestedChannel, fallbackUsed: false };
3318
- }
3319
- if (requestedChannel === "alpha") {
3320
- const stable = resolveBundledCodexBinary("stable");
3321
- if (stable) {
3322
- return { path: stable, resolvedChannel: "stable", fallbackUsed: true };
3323
- }
3324
- }
3325
- return { path: null, resolvedChannel: null, fallbackUsed: false };
3326
- }
3327
- function buildUnavailableStatus(channel) {
3328
- const channelLabel = channel === "alpha" ? "alpha " : "";
3329
- const channelSuffix = channel === "alpha" ? " Switch to Stable in Settings or reinstall CodexUse to restore the CLI." : " Reinstall CodexUse to restore the CLI.";
3330
- return {
3331
- available: false,
3332
- path: null,
3333
- reason: `Bundled ${channelLabel}Codex CLI is missing.${channelSuffix}`,
3334
- source: null,
3335
- channel,
3336
- requestedChannel: channel,
3337
- fallbackUsed: false
3338
- };
3339
- }
3340
- function evaluateCodexCliStatus(requestedChannel) {
3341
- const resolved = resolveBundledCodexBinaryWithFallback(requestedChannel);
3342
- if (resolved.path && resolved.resolvedChannel) {
3343
- return {
3344
- available: true,
3345
- path: resolved.path,
3346
- reason: null,
3347
- source: "bundled",
3348
- channel: resolved.resolvedChannel,
3349
- requestedChannel,
3350
- fallbackUsed: resolved.fallbackUsed
3351
- };
3352
- }
3353
- return buildUnavailableStatus(requestedChannel);
3354
- }
3355
- async function refreshCodexStatus() {
3356
- const channel = await getCodexCliChannel();
3357
- cachedChannel = channel;
3358
- cachedStatus = evaluateCodexCliStatus(channel);
3359
- return { ...cachedStatus };
3360
- }
3361
- var CodexCliMissingError = class extends Error {
3362
- constructor(status, message) {
3363
- super(message ?? status.reason ?? "Codex CLI is not available");
3364
- Object.setPrototypeOf(this, new.target.prototype);
3365
- this.name = "CodexCliMissingError";
3366
- this.status = { ...status };
3367
- }
3368
- };
3369
- async function requireCodexCli() {
3370
- const status = await refreshCodexStatus();
3371
- if (!status.available || !status.path) {
3372
- throw new CodexCliMissingError(status);
3373
- }
3374
- return status.path;
3375
- }
3376
-
3377
- // ../../lib/codex-rpc.ts
3933
+ var import_node_os2 = __toESM(require("os"));
3934
+ var import_node_path8 = __toESM(require("path"));
3378
3935
  var RPC_TIMEOUT_MS = 1e4;
3379
3936
  async function sendPayload(child, payload) {
3380
3937
  child.stdin?.write(JSON.stringify(payload));
@@ -3426,9 +3983,9 @@ function toWindow(rpc) {
3426
3983
  }
3427
3984
  async function fetchRateLimitsViaRpc(envOverride, options = {}) {
3428
3985
  const binaryPath = options.codexPath ?? await requireCodexCli();
3429
- const tempHome = await import_node_fs6.promises.mkdtemp(import_node_path6.default.join(import_node_os.default.tmpdir(), "codex-rpc-"));
3430
- const sourceAuthPath = options.authPath ?? (envOverride?.CODEX_HOME ? import_node_path6.default.join(envOverride.CODEX_HOME, "auth.json") : import_node_path6.default.join(
3431
- envOverride?.HOME ?? process.env.HOME ?? process.env.USERPROFILE ?? import_node_os.default.homedir(),
3986
+ const tempHome = await import_node_fs6.promises.mkdtemp(import_node_path8.default.join(import_node_os2.default.tmpdir(), "codex-rpc-"));
3987
+ const sourceAuthPath = options.authPath ?? (envOverride?.CODEX_HOME ? import_node_path8.default.join(envOverride.CODEX_HOME, "auth.json") : import_node_path8.default.join(
3988
+ envOverride?.HOME ?? process.env.HOME ?? process.env.USERPROFILE ?? import_node_os2.default.homedir(),
3432
3989
  ".codex",
3433
3990
  "auth.json"
3434
3991
  ));
@@ -3437,13 +3994,13 @@ async function fetchRateLimitsViaRpc(envOverride, options = {}) {
3437
3994
  if (!authContent) {
3438
3995
  return null;
3439
3996
  }
3440
- await import_node_fs6.promises.writeFile(import_node_path6.default.join(tempHome, "auth.json"), authContent, "utf8");
3997
+ await import_node_fs6.promises.writeFile(import_node_path8.default.join(tempHome, "auth.json"), authContent, "utf8");
3441
3998
  } catch {
3442
3999
  await import_node_fs6.promises.rm(tempHome, { recursive: true, force: true }).catch(() => {
3443
4000
  });
3444
4001
  return null;
3445
4002
  }
3446
- const child = (0, import_node_child_process2.spawn)(process.execPath, [binaryPath, "-s", "read-only", "-a", "untrusted", "app-server"], {
4003
+ const child = (0, import_node_child_process3.spawn)(process.execPath, [binaryPath, "-s", "read-only", "-a", "untrusted", "app-server"], {
3447
4004
  stdio: ["pipe", "pipe", "pipe"],
3448
4005
  env: {
3449
4006
  ...process.env,
@@ -3515,7 +4072,7 @@ function maxUsedPercent(snapshot) {
3515
4072
  }
3516
4073
 
3517
4074
  // src/index.ts
3518
- var VERSION = true ? "2.3.1" : "0.0.0";
4075
+ var VERSION = true ? "2.4.1" : "0.0.0";
3519
4076
  function printHelp() {
3520
4077
  console.log(`CodexUse CLI v${VERSION}
3521
4078
 
@@ -3531,6 +4088,10 @@ Usage:
3531
4088
  codexuse license status [--refresh]
3532
4089
  codexuse license activate <license-key>
3533
4090
 
4091
+ codexuse sync status
4092
+ codexuse sync pull
4093
+ codexuse sync push
4094
+
3534
4095
  Flags:
3535
4096
  -h, --help Show help
3536
4097
  -v, --version Show version
@@ -3952,6 +4513,70 @@ async function handleLicense(args) {
3952
4513
  return;
3953
4514
  }
3954
4515
  }
4516
+ function printSyncResult(result) {
4517
+ console.log(result.message);
4518
+ if (result.localUpdatedAt) {
4519
+ console.log(`Local snapshot: ${result.localUpdatedAt}`);
4520
+ }
4521
+ if (result.remoteUpdatedAt) {
4522
+ console.log(`Remote snapshot: ${result.remoteUpdatedAt}`);
4523
+ }
4524
+ }
4525
+ async function handleSync(args) {
4526
+ const flags = args.filter((arg) => arg.startsWith("-"));
4527
+ const params = stripFlags(args);
4528
+ const sub = params[0];
4529
+ if (!sub || hasFlag(flags, "--help") || hasFlag(flags, "-h")) {
4530
+ printHelp();
4531
+ return;
4532
+ }
4533
+ switch (sub) {
4534
+ case "status": {
4535
+ const status = await getCloudSyncStatus();
4536
+ console.log(`Endpoint: ${status.endpoint}`);
4537
+ console.log(`Can sync: ${status.canSync ? "yes" : "no"}`);
4538
+ console.log(`Has key: ${status.hasLicenseKey ? "yes" : "no"}`);
4539
+ if (status.licenseState) {
4540
+ console.log(`License state: ${status.licenseState}`);
4541
+ }
4542
+ if (status.reason) {
4543
+ console.log(`Reason: ${status.reason}`);
4544
+ }
4545
+ if (status.remoteUpdatedAt) {
4546
+ console.log(`Remote snapshot: ${status.remoteUpdatedAt}`);
4547
+ }
4548
+ if (status.lastPushAt) {
4549
+ console.log(`Last push: ${status.lastPushAt}`);
4550
+ }
4551
+ if (status.lastPullAt) {
4552
+ console.log(`Last pull: ${status.lastPullAt}`);
4553
+ }
4554
+ if (status.lastError) {
4555
+ console.log(`Last error: ${status.lastError}`);
4556
+ }
4557
+ return;
4558
+ }
4559
+ case "pull": {
4560
+ const result = await pullCloudSync();
4561
+ printSyncResult(result);
4562
+ if (result.status === "error") {
4563
+ process.exitCode = 1;
4564
+ }
4565
+ return;
4566
+ }
4567
+ case "push": {
4568
+ const result = await pushCloudSync();
4569
+ printSyncResult(result);
4570
+ if (result.status === "error") {
4571
+ process.exitCode = 1;
4572
+ }
4573
+ return;
4574
+ }
4575
+ default:
4576
+ printHelp();
4577
+ return;
4578
+ }
4579
+ }
3955
4580
  async function main() {
3956
4581
  const args = process.argv.slice(2);
3957
4582
  if (args.length === 0 || hasFlag(args, "--help") || hasFlag(args, "-h")) {
@@ -3971,6 +4596,9 @@ async function main() {
3971
4596
  case "license":
3972
4597
  await handleLicense(rest);
3973
4598
  return;
4599
+ case "sync":
4600
+ await handleSync(rest);
4601
+ return;
3974
4602
  default:
3975
4603
  printHelp();
3976
4604
  return;