codexuse-cli 2.3.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js 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,474 @@ 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
+ profilesByName: {},
165
+ migration: {
166
+ status: "pending",
167
+ startedAt: null,
168
+ completedAt: null,
169
+ localStorageImportedAt: null,
170
+ lastError: null
129
171
  }
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
- `);
172
+ };
190
173
  }
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;
174
+ function asString(value) {
175
+ if (typeof value !== "string") {
176
+ return null;
295
177
  }
296
- if (userVersion < 2) {
297
- database.exec(`PRAGMA user_version = 2;`);
298
- userVersion = 2;
178
+ const trimmed = value.trim();
179
+ return trimmed.length > 0 ? trimmed : null;
180
+ }
181
+ function normalizeAppState(raw) {
182
+ const defaults = createDefaultAppState();
183
+ if (!isRecord(raw)) {
184
+ return defaults;
299
185
  }
300
- if (userVersion < 3) {
301
- database.exec(`PRAGMA user_version = 3;`);
302
- userVersion = 3;
186
+ const next = deepMerge(defaults, raw);
187
+ const merged = clone(next);
188
+ merged.schemaVersion = 1;
189
+ if (typeof merged.autoRoll.enabled !== "boolean") {
190
+ merged.autoRoll.enabled = false;
303
191
  }
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;
192
+ if (!Number.isFinite(merged.autoRoll.warningThreshold)) {
193
+ merged.autoRoll.warningThreshold = 85;
371
194
  }
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;
195
+ if (!Number.isFinite(merged.autoRoll.switchThreshold)) {
196
+ merged.autoRoll.switchThreshold = 95;
451
197
  }
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;
198
+ if (merged.autoRoll.warningThreshold < 50 || merged.autoRoll.warningThreshold > 99) {
199
+ merged.autoRoll.warningThreshold = 85;
495
200
  }
496
- if (userVersion < 8) {
497
- ensureChatTables(database);
498
- database.exec(`PRAGMA user_version = 8;`);
499
- userVersion = 8;
201
+ if (merged.autoRoll.switchThreshold <= merged.autoRoll.warningThreshold || merged.autoRoll.switchThreshold > 100) {
202
+ merged.autoRoll.switchThreshold = Math.min(100, Math.max(merged.autoRoll.warningThreshold + 1, 95));
500
203
  }
501
- if (userVersion < 9) {
502
- ensureSessionTables(database);
503
- database.exec(`PRAGMA user_version = 9;`);
504
- userVersion = 9;
204
+ merged.app.cliChannel = merged.app.cliChannel === "alpha" ? "alpha" : "stable";
205
+ merged.app.lastAppVersion = asString(merged.app.lastAppVersion);
206
+ merged.app.pendingUpdateVersion = asString(merged.app.pendingUpdateVersion);
207
+ merged.app.lastProfileName = asString(merged.app.lastProfileName);
208
+ merged.license.licenseKey = asString(merged.license.licenseKey);
209
+ merged.license.purchaseEmail = asString(merged.license.purchaseEmail);
210
+ merged.license.lastVerifiedAt = asString(merged.license.lastVerifiedAt);
211
+ merged.license.nextCheckAt = asString(merged.license.nextCheckAt);
212
+ merged.license.lastVerificationError = asString(merged.license.lastVerificationError);
213
+ merged.license.signature = asString(merged.license.signature);
214
+ if (!["inactive", "active", "grace", "error"].includes(merged.license.status)) {
215
+ merged.license.status = "inactive";
505
216
  }
506
- if (userVersion < 10) {
507
- ensureUsageTables(database);
508
- database.exec(`PRAGMA user_version = 10;`);
509
- userVersion = 10;
217
+ if (!Array.isArray(merged.providers.list)) {
218
+ merged.providers.list = [];
510
219
  }
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;
220
+ merged.providers.selectedProviderId = asString(merged.providers.selectedProviderId) ?? "openai";
221
+ merged.providers.defaultModel = asString(merged.providers.defaultModel);
222
+ if (!["minimal", "low", "medium", "high", "xhigh", "none"].includes(merged.providers.defaultReasoningEffort)) {
223
+ merged.providers.defaultReasoningEffort = "medium";
518
224
  }
519
- try {
520
- database.exec("PRAGMA foreign_keys = ON;");
521
- } catch {
225
+ if (!isRecord(merged.providers.overridesByPath)) {
226
+ merged.providers.overridesByPath = {};
522
227
  }
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);
228
+ if (!isRecord(merged.sandbox.overridesByPath)) {
229
+ merged.sandbox.overridesByPath = {};
539
230
  }
540
- const db = await globalCache.cachedDbPromise;
541
- try {
542
- runMigrations(db);
543
- } catch (error) {
544
- console.warn("Failed to ensure migrations:", error);
231
+ if (!Array.isArray(merged.preferences.excludeFolders)) {
232
+ merged.preferences.excludeFolders = [];
545
233
  }
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
- }
234
+ if (!Array.isArray(merged.preferences.folderHistory)) {
235
+ merged.preferences.folderHistory = [];
555
236
  }
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
- });
237
+ if (!Array.isArray(merged.preferences.pinnedPaths)) {
238
+ merged.preferences.pinnedPaths = [];
704
239
  }
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;
240
+ if (!isRecord(merged.projectSettingsByPath)) {
241
+ merged.projectSettingsByPath = {};
725
242
  }
726
- console.warn(...args);
727
- }
728
- function logError(...args) {
729
- if (isTestEnv && !isMocked(console.error)) {
730
- return;
243
+ if (!isRecord(merged.conversationCategoriesByCwd)) {
244
+ merged.conversationCategoriesByCwd = {};
731
245
  }
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;
246
+ if (!isRecord(merged.conversationCategoryAssignmentsByCwd)) {
247
+ merged.conversationCategoryAssignmentsByCwd = {};
743
248
  }
744
- const normalized = value.trim().toLowerCase();
745
- if (!normalized) {
746
- return null;
249
+ if (!isRecord(merged.skills)) {
250
+ merged.skills = clone(defaults.skills);
747
251
  }
748
- return KNOWN_CHANNELS.has(normalized) ? normalized : null;
252
+ if (!Array.isArray(merged.skills.sources)) {
253
+ merged.skills.sources = [];
254
+ }
255
+ if (!isRecord(merged.skills.installsBySlug)) {
256
+ merged.skills.installsBySlug = {};
257
+ }
258
+ if (!isRecord(merged.sync)) {
259
+ merged.sync = clone(defaults.sync);
260
+ }
261
+ merged.sync.lastPushAt = asString(merged.sync.lastPushAt);
262
+ merged.sync.lastPullAt = asString(merged.sync.lastPullAt);
263
+ merged.sync.lastError = asString(merged.sync.lastError);
264
+ merged.sync.remoteUpdatedAt = asString(merged.sync.remoteUpdatedAt);
265
+ if (!isRecord(merged.profilesByName)) {
266
+ merged.profilesByName = {};
267
+ }
268
+ if (!isRecord(merged.migration)) {
269
+ merged.migration = clone(defaults.migration);
270
+ }
271
+ if (!["pending", "pending_local_storage", "complete"].includes(merged.migration.status)) {
272
+ merged.migration.status = "pending";
273
+ }
274
+ merged.migration.startedAt = asString(merged.migration.startedAt);
275
+ merged.migration.completedAt = asString(merged.migration.completedAt);
276
+ merged.migration.localStorageImportedAt = asString(merged.migration.localStorageImportedAt);
277
+ merged.migration.lastError = asString(merged.migration.lastError);
278
+ return merged;
749
279
  }
750
- function normalizeCodexCliChannel(value, fallback = "stable") {
751
- return parseCodexCliChannel(value) ?? fallback;
280
+ function deepMerge(base, patch) {
281
+ if (!isRecord(base) || !isRecord(patch)) {
282
+ return clone(patch ?? base);
283
+ }
284
+ const next = { ...base };
285
+ for (const [key, patchValue] of Object.entries(patch)) {
286
+ if (patchValue === void 0) {
287
+ continue;
288
+ }
289
+ const currentValue = next[key];
290
+ if (Array.isArray(patchValue)) {
291
+ next[key] = clone(patchValue);
292
+ continue;
293
+ }
294
+ if (isRecord(currentValue) && isRecord(patchValue)) {
295
+ next[key] = deepMerge(currentValue, patchValue);
296
+ continue;
297
+ }
298
+ next[key] = clone(patchValue);
299
+ }
300
+ return next;
752
301
  }
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() {
302
+ async function writeAtomic(filePath, contents) {
303
+ const dir = import_node_path.default.dirname(filePath);
304
+ const base = import_node_path.default.basename(filePath);
305
+ const token = `${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}`;
306
+ const tempPath = import_node_path.default.join(dir, `${base}.${token}.tmp`);
307
+ await import_node_fs.promises.mkdir(dir, { recursive: true });
308
+ await import_node_fs.promises.writeFile(tempPath, contents, "utf8");
774
309
  try {
775
- await import_node_fs2.promises.mkdir(resolveCodexDir(), { recursive: true });
776
- } catch (error) {
777
- logWarn("Failed to ensure Codex directory exists:", error);
310
+ await import_node_fs.promises.rename(tempPath, filePath);
311
+ } finally {
312
+ await import_node_fs.promises.rm(tempPath, { force: true }).catch(() => void 0);
778
313
  }
779
314
  }
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");
315
+ async function writeAppStateToDisk(state) {
316
+ const filePath = resolveAppStatePath();
317
+ await writeAtomic(filePath, `${JSON.stringify(state, null, 2)}
318
+ `);
319
+ }
320
+ async function readAppStateFromDisk() {
321
+ const filePath = resolveAppStatePath();
784
322
  try {
785
- await import_node_fs2.promises.rename(tempPath, filePath);
323
+ const raw = await import_node_fs.promises.readFile(filePath, "utf8");
324
+ return normalizeAppState(JSON.parse(raw));
786
325
  } catch (error) {
787
326
  const code = error.code;
788
327
  if (code === "ENOENT") {
789
- await import_node_fs2.promises.writeFile(filePath, contents, "utf8");
790
- } else {
791
- throw error;
328
+ return null;
792
329
  }
330
+ throw error;
793
331
  }
332
+ }
333
+ async function ensureInitialized() {
334
+ if (appStateCache) {
335
+ return appStateCache;
336
+ }
337
+ const filePath = resolveAppStatePath();
338
+ await import_node_fs.promises.mkdir(import_node_path.default.dirname(filePath), { recursive: true });
339
+ const loaded = await readAppStateFromDisk();
340
+ const normalized = loaded ?? normalizeAppState(null);
341
+ appStateCache = normalized;
342
+ if (loaded === null) {
343
+ await writeAppStateToDisk(normalized);
344
+ }
345
+ return appStateCache;
346
+ }
347
+ async function withWriteLock(task) {
348
+ if (writeLockContext.getStore()) {
349
+ return task();
350
+ }
351
+ const previous = writeLock;
352
+ let release = null;
353
+ writeLock = new Promise((resolve) => {
354
+ release = resolve;
355
+ });
356
+ await previous;
794
357
  try {
795
- await import_node_fs2.promises.rm(tempPath, { force: true });
796
- } catch {
358
+ return await writeLockContext.run(true, task);
359
+ } finally {
360
+ release?.();
361
+ }
362
+ }
363
+ async function getAppState() {
364
+ const state = await ensureInitialized();
365
+ return clone(state);
366
+ }
367
+ async function updateAppState(transform, options = {}) {
368
+ const mode = options.mode ?? "patch";
369
+ const allowBeforeMigrationComplete = options.allowBeforeMigrationComplete === true;
370
+ return withWriteLock(async () => {
371
+ const current = await readAppStateFromDisk() ?? await ensureInitialized();
372
+ appStateCache = current;
373
+ if (!allowBeforeMigrationComplete && current.migration.status !== "complete") {
374
+ throw new Error("Storage migration is not complete yet.");
375
+ }
376
+ const transformed = transform(clone(current));
377
+ const nextState = mode === "replace" ? normalizeAppState(transformed) : normalizeAppState(deepMerge(current, transformed));
378
+ await writeAppStateToDisk(nextState);
379
+ appStateCache = nextState;
380
+ return clone(nextState);
381
+ });
382
+ }
383
+ async function patchAppState(patch) {
384
+ return updateAppState(() => patch, {
385
+ mode: "patch",
386
+ allowBeforeMigrationComplete: false
387
+ });
388
+ }
389
+
390
+ // ../../lib/codex-settings.ts
391
+ function asString2(value) {
392
+ if (typeof value !== "string") {
393
+ return null;
797
394
  }
395
+ const trimmed = value.trim();
396
+ return trimmed.length > 0 ? trimmed : null;
397
+ }
398
+ function isRecord2(value) {
399
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
798
400
  }
799
- function normalizeStoredLicense(raw) {
800
- if (!raw || typeof raw !== "object") {
401
+ function parseStoredLicense(raw) {
402
+ if (!isRecord2(raw)) {
801
403
  return null;
802
404
  }
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;
405
+ const statusCandidate = asString2(raw.status);
406
+ const status = ["inactive", "active", "grace", "error"].includes(statusCandidate ?? "") ? statusCandidate : void 0;
811
407
  const license = {
812
- licenseKey,
813
- purchaseEmail,
814
- lastVerifiedAt,
815
- nextCheckAt,
816
- lastVerificationError,
817
- signature,
818
- status: statusCandidate
408
+ licenseKey: asString2(raw.licenseKey ?? raw.license_key),
409
+ purchaseEmail: asString2(raw.purchaseEmail ?? raw.purchase_email),
410
+ lastVerifiedAt: asString2(raw.lastVerifiedAt ?? raw.last_verified_at),
411
+ nextCheckAt: asString2(raw.nextCheckAt ?? raw.next_check_at),
412
+ lastVerificationError: asString2(raw.lastVerificationError ?? raw.last_verification_error),
413
+ status,
414
+ signature: asString2(raw.signature)
819
415
  };
820
- const hasLicenseData = Boolean(
821
- license.licenseKey ?? license.purchaseEmail ?? license.lastVerifiedAt ?? license.nextCheckAt ?? license.lastVerificationError ?? license.status
416
+ const hasValue = Boolean(
417
+ license.licenseKey || license.purchaseEmail || license.lastVerifiedAt || license.nextCheckAt || license.lastVerificationError || license.status
822
418
  );
823
- return hasLicenseData ? license : null;
419
+ return hasValue ? license : null;
824
420
  }
825
- function normalizeStoredAutoRoll(raw) {
421
+ function parseAutoRoll(raw) {
826
422
  if (!raw) {
827
423
  return null;
828
424
  }
829
- const parsed = typeof raw === "string" ? parseJsonValue(raw) : raw;
830
- if (!parsed || typeof parsed !== "object") {
831
- return null;
832
- }
833
425
  try {
834
- return normalizeAutoRollSettings(parsed);
426
+ return normalizeAutoRollSettings(raw);
835
427
  } catch {
836
428
  return null;
837
429
  }
838
430
  }
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;
853
- }
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
- }
431
+ async function getLastProfileName() {
432
+ const state = await getAppState();
433
+ return asString2(state.app.lastProfileName);
861
434
  }
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;
435
+ async function persistLastProfileName(profileName) {
436
+ await patchAppState({
437
+ app: {
438
+ lastProfileName: asString2(profileName)
868
439
  }
869
- const normalized = normalizeParsedSettings(parsed);
870
- await writeSettingsFile(normalized);
871
- return normalized;
872
- } catch {
873
- return null;
874
- }
440
+ });
875
441
  }
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
- };
442
+ async function getStoredLicense() {
443
+ const state = await getAppState();
444
+ return parseStoredLicense(state.license);
895
445
  }
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
- }
446
+ async function persistLicense(license) {
447
+ const parsed = parseStoredLicense(license);
448
+ await patchAppState({
449
+ license: parsed ? {
450
+ licenseKey: parsed.licenseKey ?? null,
451
+ purchaseEmail: parsed.purchaseEmail ?? null,
452
+ lastVerifiedAt: parsed.lastVerifiedAt ?? null,
453
+ nextCheckAt: parsed.nextCheckAt ?? null,
454
+ lastVerificationError: parsed.lastVerificationError ?? null,
455
+ status: parsed.status ?? "inactive",
456
+ signature: parsed.signature ?? null
457
+ } : {
458
+ licenseKey: null,
459
+ purchaseEmail: null,
460
+ lastVerifiedAt: null,
461
+ nextCheckAt: null,
462
+ lastVerificationError: null,
463
+ status: "inactive",
464
+ signature: null
924
465
  }
925
- logWarn("Failed to parse Codex settings file, using defaults:", error);
926
- return null;
927
- }
466
+ });
928
467
  }
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
- }
468
+ async function getCodexCliChannel() {
469
+ const state = await getAppState();
470
+ return normalizeCodexCliChannel(state.app.cliChannel);
979
471
  }
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 } : {}
472
+ async function readCodexSettingsJsonRaw() {
473
+ const state = await getAppState();
474
+ return {
475
+ lastProfileName: state.app.lastProfileName,
476
+ lastAppVersion: state.app.lastAppVersion,
477
+ pendingUpdateVersion: state.app.pendingUpdateVersion,
478
+ cliChannel: state.app.cliChannel,
479
+ autoRoll: state.autoRoll,
480
+ license: state.license,
481
+ providers: state.providers.list,
482
+ selectedProviderId: state.providers.selectedProviderId,
483
+ defaultModel: state.providers.defaultModel,
484
+ defaultReasoningEffort: state.providers.defaultReasoningEffort,
485
+ selectionsByCwd: state.providers.overridesByPath,
486
+ mode: state.sandbox.defaultMode,
487
+ approvalPolicy: state.sandbox.defaultApprovalPolicy,
488
+ sandboxSelectionsByCwd: state.sandbox.overridesByPath,
489
+ excludeFolders: state.preferences.excludeFolders,
490
+ enableTaskCompleteBeep: state.preferences.enableTaskCompleteBeep,
491
+ preventSleepDuringTasks: state.preferences.preventSleepDuringTasks,
492
+ folderHistory: state.preferences.folderHistory,
493
+ pinnedPaths: state.preferences.pinnedPaths,
494
+ projectSettingsByPath: state.projectSettingsByPath,
495
+ categoriesByCwd: state.conversationCategoriesByCwd,
496
+ conversationCategoryByCwd: state.conversationCategoryAssignmentsByCwd,
497
+ sync: state.sync
1011
498
  };
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
- }
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
499
  }
1058
- async function getStoredLicense() {
1059
- const settings = await getCodexSettings();
1060
- if (!settings.license) {
1061
- return null;
500
+ async function writeCodexSettingsJsonRaw(payload) {
501
+ if (!isRecord2(payload)) {
502
+ return;
1062
503
  }
1063
- return settings.license;
1064
- }
1065
- async function persistLicense(license) {
1066
- const settings = await getCodexSettings();
1067
- if (license) {
1068
- settings.license = {
504
+ const autoRoll = parseAutoRoll(payload.autoRoll ?? payload.auto_roll);
505
+ const license = parseStoredLicense(payload.license);
506
+ const cliChannel = parseCodexCliChannel(payload.cliChannel ?? payload.cli_channel);
507
+ await patchAppState({
508
+ app: {
509
+ lastProfileName: asString2(payload.lastProfileName ?? payload.last_profile_name),
510
+ lastAppVersion: asString2(payload.lastAppVersion ?? payload.last_app_version),
511
+ pendingUpdateVersion: asString2(payload.pendingUpdateVersion ?? payload.pending_update_version),
512
+ cliChannel: cliChannel ?? void 0
513
+ },
514
+ autoRoll: autoRoll ? {
515
+ enabled: autoRoll.enabled,
516
+ warningThreshold: autoRoll.warningThreshold,
517
+ switchThreshold: autoRoll.switchThreshold
518
+ } : void 0,
519
+ license: license ? {
1069
520
  licenseKey: license.licenseKey ?? null,
1070
521
  purchaseEmail: license.purchaseEmail ?? null,
1071
522
  lastVerifiedAt: license.lastVerifiedAt ?? null,
@@ -1073,15 +524,55 @@ async function persistLicense(license) {
1073
524
  lastVerificationError: license.lastVerificationError ?? null,
1074
525
  status: license.status ?? "inactive",
1075
526
  signature: license.signature ?? null
1076
- };
1077
- } else if (settings.license) {
1078
- delete settings.license;
527
+ } : void 0,
528
+ providers: {
529
+ list: Array.isArray(payload.providers) ? payload.providers : void 0,
530
+ selectedProviderId: asString2(payload.selectedProviderId) ?? void 0,
531
+ defaultModel: asString2(payload.defaultModel ?? payload.selectedModel),
532
+ defaultReasoningEffort: typeof payload.defaultReasoningEffort === "string" ? payload.defaultReasoningEffort : typeof payload.reasoningEffort === "string" ? payload.reasoningEffort : void 0,
533
+ overridesByPath: isRecord2(payload.selectionsByCwd) ? payload.selectionsByCwd : void 0
534
+ },
535
+ sandbox: {
536
+ defaultMode: asString2(payload.mode ?? payload.defaultMode) ?? void 0,
537
+ defaultApprovalPolicy: asString2(payload.approvalPolicy ?? payload.defaultApprovalPolicy) ?? void 0,
538
+ overridesByPath: isRecord2(payload.sandboxSelectionsByCwd) ? payload.sandboxSelectionsByCwd : void 0
539
+ },
540
+ preferences: {
541
+ excludeFolders: Array.isArray(payload.excludeFolders) ? payload.excludeFolders : void 0,
542
+ enableTaskCompleteBeep: typeof payload.enableTaskCompleteBeep === "boolean" ? payload.enableTaskCompleteBeep : void 0,
543
+ preventSleepDuringTasks: typeof payload.preventSleepDuringTasks === "boolean" ? payload.preventSleepDuringTasks : void 0,
544
+ folderHistory: Array.isArray(payload.folderHistory) ? payload.folderHistory : void 0,
545
+ pinnedPaths: Array.isArray(payload.pinnedPaths) ? payload.pinnedPaths : void 0
546
+ },
547
+ projectSettingsByPath: isRecord2(payload.projectSettingsByPath) ? payload.projectSettingsByPath : void 0,
548
+ conversationCategoriesByCwd: isRecord2(payload.categoriesByCwd) ? payload.categoriesByCwd : void 0,
549
+ conversationCategoryAssignmentsByCwd: isRecord2(payload.conversationCategoryByCwd) ? payload.conversationCategoryByCwd : void 0,
550
+ sync: isRecord2(payload.sync) ? payload.sync : void 0
551
+ });
552
+ }
553
+
554
+ // ../../lib/logger.ts
555
+ var isTestEnv = process.env.NODE_ENV === "test" || process.env.VITEST === "true" || process.env.VITEST === "1";
556
+ function isMocked(fn) {
557
+ return Boolean(fn && typeof fn === "function" && "mock" in fn);
558
+ }
559
+ function logWarn(...args) {
560
+ if (isTestEnv && !isMocked(console.warn)) {
561
+ return;
1079
562
  }
1080
- await setCodexSettings(settings);
563
+ console.warn(...args);
1081
564
  }
1082
- async function getCodexCliChannel() {
1083
- const settings = await getCodexSettings();
1084
- return normalizeCodexCliChannel(settings.cliChannel);
565
+ function logError(...args) {
566
+ if (isTestEnv && !isMocked(console.error)) {
567
+ return;
568
+ }
569
+ console.error(...args);
570
+ }
571
+ function logInfo(...args) {
572
+ if (isTestEnv && !isMocked(console.warn)) {
573
+ return;
574
+ }
575
+ console.warn(...args);
1085
576
  }
1086
577
 
1087
578
  // ../../lib/profile-manager.ts
@@ -1090,15 +581,11 @@ var REFRESH_TOKEN_REDEEMED_SNIPPET = "refresh token was already used";
1090
581
  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
582
  var AUTH_BACKUP_MISSING_MARKER = "__codexuse_missing_auth__";
1092
583
  var DEFAULT_WORKSPACE_ID = "__default__";
1093
- var PROFILE_RECORD_FILENAME = "profile.json";
1094
584
  var ProfileManager = class {
1095
585
  constructor() {
1096
586
  const homeDir = process.env.HOME || process.env.USERPROFILE || "";
1097
587
  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");
588
+ this.profileHomesRoot = (0, import_path2.join)(getUserDataDir(), "profile-homes");
1102
589
  this.activeAuth = (0, import_path2.join)(this.codexDir, "auth.json");
1103
590
  this.activeAuthBackup = `${this.activeAuth}.swap`;
1104
591
  this.lastActiveAuthErrorSignature = null;
@@ -1162,183 +649,84 @@ var ProfileManager = class {
1162
649
  logWarn("Failed to persist preferred profile name:", error);
1163
650
  }
1164
651
  }
1165
- getProfileRecordPath(profileName) {
1166
- return (0, import_path2.join)(this.getProfileHomePath(profileName), PROFILE_RECORD_FILENAME);
652
+ toStateRecord(record) {
653
+ return {
654
+ name: record.name,
655
+ displayName: record.displayName ?? null,
656
+ data: record.data,
657
+ metadata: record.metadata,
658
+ accountId: record.accountId ?? null,
659
+ workspaceId: record.workspaceId ?? null,
660
+ workspaceName: record.workspaceName ?? null,
661
+ email: record.email ?? null,
662
+ authMethod: record.authMethod ?? null,
663
+ createdAt: record.createdAt ?? null,
664
+ updatedAt: record.updatedAt ?? null
665
+ };
1167
666
  }
1168
- parseProfileRecordPayload(profileName, parsed) {
1169
- if (!parsed || typeof parsed !== "object") {
667
+ fromStateRecord(profileName, raw) {
668
+ if (!raw || typeof raw !== "object") {
1170
669
  return null;
1171
670
  }
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 {
671
+ const dataRaw = raw.data;
672
+ if (!dataRaw || typeof dataRaw !== "object") {
1184
673
  return null;
1185
674
  }
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
675
  return {
1191
676
  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
677
+ displayName: typeof raw.displayName === "string" ? raw.displayName : null,
678
+ data: dataRaw,
679
+ metadata: raw.metadata,
680
+ accountId: typeof raw.accountId === "string" ? raw.accountId : null,
681
+ workspaceId: typeof raw.workspaceId === "string" ? raw.workspaceId : null,
682
+ workspaceName: typeof raw.workspaceName === "string" ? raw.workspaceName : null,
683
+ email: typeof raw.email === "string" ? raw.email : null,
684
+ authMethod: typeof raw.authMethod === "string" ? raw.authMethod : null,
685
+ createdAt: typeof raw.createdAt === "string" ? raw.createdAt : null,
686
+ updatedAt: typeof raw.updatedAt === "string" ? raw.updatedAt : null
1202
687
  };
1203
688
  }
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
- }
689
+ async readProfilesStateMap() {
690
+ const state = await getAppState();
691
+ return { ...state.profilesByName ?? {} };
1212
692
  }
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;
693
+ async writeProfilesStateMap(nextMap) {
694
+ await updateAppState((state) => ({
695
+ ...state,
696
+ profilesByName: nextMap
697
+ }), { mode: "replace" });
1237
698
  }
1238
699
  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
- }
700
+ const map = await this.readProfilesStateMap();
701
+ const raw = map[profileName];
702
+ if (!raw) {
703
+ return null;
1250
704
  }
1251
- return this.recoverProfileRecord(recordPath, profileName);
705
+ return this.fromStateRecord(profileName, raw);
1252
706
  }
1253
707
  async listProfileRecords() {
708
+ const map = await this.readProfilesStateMap();
1254
709
  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);
710
+ for (const [profileName, raw] of Object.entries(map)) {
711
+ const record = this.fromStateRecord(profileName, raw);
712
+ if (record) {
713
+ records.push(record);
1302
714
  }
1303
715
  }
1304
716
  return records;
1305
717
  }
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
718
  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);
719
+ const map = await this.readProfilesStateMap();
720
+ map[record.name] = this.toStateRecord(record);
721
+ await this.writeProfilesStateMap(map);
722
+ }
723
+ async deleteProfileRecord(name) {
724
+ const map = await this.readProfilesStateMap();
725
+ if (!Object.prototype.hasOwnProperty.call(map, name)) {
726
+ return;
1341
727
  }
728
+ delete map[name];
729
+ await this.writeProfilesStateMap(map);
1342
730
  }
1343
731
  resolveAuthMethod(data) {
1344
732
  const raw = typeof data?.auth_method === "string" ? data.auth_method.trim().toLowerCase() : "";
@@ -1629,6 +1017,7 @@ var ProfileManager = class {
1629
1017
  * Initialize the profile manager and create necessary directories
1630
1018
  */
1631
1019
  async initialize() {
1020
+ this.profileHomesRoot = (0, import_path2.join)(getUserDataDir(), "profile-homes");
1632
1021
  try {
1633
1022
  await import_fs.promises.mkdir(this.codexDir, { recursive: true });
1634
1023
  } catch (error) {
@@ -1642,189 +1031,6 @@ var ProfileManager = class {
1642
1031
  throw new Error("Failed to initialize profile manager");
1643
1032
  }
1644
1033
  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
1034
  }
1829
1035
  enqueueAuthSwap(task) {
1830
1036
  const run = this.authSwapLock.then(task, task);
@@ -2552,6 +1758,7 @@ var ProfileManager = class {
2552
1758
  name: targetName
2553
1759
  };
2554
1760
  await this.writeProfileRecord(updatedRecord);
1761
+ await this.deleteProfileRecord(sourceName);
2555
1762
  } catch (error) {
2556
1763
  logWarn(`Failed to rewrite profile record after renaming '${sourceName}' to '${targetName}':`, error);
2557
1764
  }
@@ -2596,6 +1803,7 @@ var ProfileManager = class {
2596
1803
  logWarn(`Failed to remove profile home for '${profileName}':`, error);
2597
1804
  }
2598
1805
  }
1806
+ await this.deleteProfileRecord(profileName);
2599
1807
  return true;
2600
1808
  }
2601
1809
  /**
@@ -2626,6 +1834,7 @@ var ProfileManager = class {
2626
1834
  for (const name of deleteCandidates) {
2627
1835
  try {
2628
1836
  await import_fs.promises.rm(this.getProfileHomePath(name), { recursive: true, force: true });
1837
+ await this.deleteProfileRecord(name);
2629
1838
  removed.push(name);
2630
1839
  } catch (error) {
2631
1840
  if (!this.isNotFoundError(error)) {
@@ -2719,62 +1928,139 @@ var ProfileManager = class {
2719
1928
  };
2720
1929
  }
2721
1930
  /**
2722
- * Export all profiles to a portable format.
1931
+ * Export profiles for cloud sync (includes token-bearing auth data).
2723
1932
  */
2724
- async exportAllProfiles() {
1933
+ async exportProfilesForCloudSync() {
2725
1934
  await this.initialize();
2726
1935
  const records = await this.listProfileRecords();
2727
- const exportedAt = (/* @__PURE__ */ new Date()).toISOString();
2728
1936
  return records.map((record) => ({
2729
1937
  name: record.name,
2730
1938
  displayName: record.displayName,
2731
- email: record.email,
1939
+ data: record.data,
1940
+ metadata: record.metadata,
2732
1941
  accountId: record.accountId,
2733
1942
  workspaceId: record.workspaceId,
2734
1943
  workspaceName: record.workspaceName,
1944
+ email: record.email,
2735
1945
  authMethod: record.authMethod,
2736
1946
  createdAt: record.createdAt,
2737
- metadata: record.metadata,
2738
- exportedAt
1947
+ updatedAt: record.updatedAt
2739
1948
  }));
2740
1949
  }
2741
- };
2742
-
2743
- // ../../lib/license-service.ts
2744
- var import_node_crypto2 = __toESM(require("crypto"));
1950
+ /**
1951
+ * Replace local profiles with a full snapshot from cloud sync.
1952
+ */
1953
+ async replaceProfilesFromCloudSync(records) {
1954
+ await this.initialize();
1955
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1956
+ const normalized = /* @__PURE__ */ new Map();
1957
+ const existingMap = await this.readProfilesStateMap();
1958
+ const existingNames = new Set(Object.keys(existingMap));
1959
+ for (const candidate of records ?? []) {
1960
+ try {
1961
+ const name = this.normalizeProfileName(candidate?.name ?? "");
1962
+ const data = candidate && typeof candidate.data === "object" && candidate.data !== null ? candidate.data : null;
1963
+ if (!data) {
1964
+ continue;
1965
+ }
1966
+ const metadata = candidate.metadata && typeof candidate.metadata === "object" ? candidate.metadata : void 0;
1967
+ const record = {
1968
+ name,
1969
+ displayName: typeof candidate.displayName === "string" ? candidate.displayName : null,
1970
+ data,
1971
+ metadata,
1972
+ accountId: typeof candidate.accountId === "string" ? candidate.accountId : null,
1973
+ workspaceId: typeof candidate.workspaceId === "string" ? candidate.workspaceId : null,
1974
+ workspaceName: typeof candidate.workspaceName === "string" ? candidate.workspaceName : null,
1975
+ email: typeof candidate.email === "string" ? candidate.email : null,
1976
+ authMethod: typeof candidate.authMethod === "string" ? candidate.authMethod : this.resolveAuthMethod(data),
1977
+ createdAt: typeof candidate.createdAt === "string" ? candidate.createdAt : now,
1978
+ updatedAt: typeof candidate.updatedAt === "string" ? candidate.updatedAt : now
1979
+ };
1980
+ normalized.set(name, record);
1981
+ } catch {
1982
+ }
1983
+ }
1984
+ const nextMap = {};
1985
+ for (const record of normalized.values()) {
1986
+ nextMap[record.name] = this.toStateRecord(record);
1987
+ }
1988
+ await this.writeProfilesStateMap(nextMap);
1989
+ let removed = 0;
1990
+ for (const name of existingNames) {
1991
+ if (normalized.has(name)) {
1992
+ continue;
1993
+ }
1994
+ try {
1995
+ await import_fs.promises.rm(this.getProfileHomePath(name), { recursive: true, force: true });
1996
+ removed += 1;
1997
+ } catch (error) {
1998
+ if (!this.isNotFoundError(error)) {
1999
+ logWarn(`Failed to remove profile '${name}' during cloud sync replace:`, error);
2000
+ }
2001
+ }
2002
+ }
2003
+ const preferred = await this.readPreferredProfileName();
2004
+ if (preferred) {
2005
+ try {
2006
+ const normalizedPreferred = this.normalizeProfileName(preferred);
2007
+ if (!normalized.has(normalizedPreferred)) {
2008
+ await this.persistPreferredProfileName(null);
2009
+ }
2010
+ } catch {
2011
+ await this.persistPreferredProfileName(null);
2012
+ }
2013
+ }
2014
+ return {
2015
+ imported: normalized.size,
2016
+ removed
2017
+ };
2018
+ }
2019
+ };
2020
+
2021
+ // ../../lib/license-service.ts
2022
+ var import_node_crypto2 = __toESM(require("crypto"));
2745
2023
 
2746
2024
  // ../../lib/license-secret.ts
2747
- var import_node_fs3 = require("fs");
2748
- var import_node_path3 = __toESM(require("path"));
2025
+ var import_node_fs2 = require("fs");
2026
+ var import_node_path2 = __toESM(require("path"));
2749
2027
  var import_node_crypto = __toESM(require("crypto"));
2750
2028
  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
2029
  function resolveSecretPath() {
2756
- return import_node_path3.default.join(resolveCodexDir2(), SECRET_FILE);
2030
+ return import_node_path2.default.join(getUserDataDir(), SECRET_FILE);
2757
2031
  }
2758
2032
  async function generateSecret() {
2759
2033
  return import_node_crypto.default.randomBytes(32).toString("hex");
2760
2034
  }
2761
- async function getLicenseSecret() {
2762
- const secretPath = resolveSecretPath();
2035
+ async function readSecretIfExists(secretPath) {
2763
2036
  try {
2764
- const existing = await import_node_fs3.promises.readFile(secretPath, "utf8");
2037
+ const existing = await import_node_fs2.promises.readFile(secretPath, "utf8");
2765
2038
  const trimmed = existing.trim();
2766
2039
  if (trimmed.length > 0) {
2767
2040
  return trimmed;
2768
2041
  }
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 {
2042
+ return null;
2043
+ } catch (error) {
2044
+ if (error.code === "ENOENT") {
2045
+ return null;
2046
+ }
2047
+ throw error;
2775
2048
  }
2776
- await import_node_fs3.promises.writeFile(secretPath, `${secret}
2049
+ }
2050
+ async function writeSecret(secretPath, secret) {
2051
+ await import_node_fs2.promises.mkdir(import_node_path2.default.dirname(secretPath), { recursive: true });
2052
+ await import_node_fs2.promises.writeFile(secretPath, `${secret}
2777
2053
  `, { mode: 384 });
2054
+ await import_node_fs2.promises.chmod(secretPath, 384).catch(() => void 0);
2055
+ }
2056
+ async function getLicenseSecret() {
2057
+ const secretPath = resolveSecretPath();
2058
+ const existing = await readSecretIfExists(secretPath);
2059
+ if (existing) {
2060
+ return existing;
2061
+ }
2062
+ const secret = await generateSecret();
2063
+ await writeSecret(secretPath, secret);
2778
2064
  return secret;
2779
2065
  }
2780
2066
 
@@ -3062,63 +2348,1427 @@ var LicenseService = class {
3062
2348
  return fallback;
3063
2349
  }
3064
2350
  };
3065
- this.verificationPromise = verify();
2351
+ this.verificationPromise = verify();
2352
+ try {
2353
+ return await this.verificationPromise;
2354
+ } finally {
2355
+ this.verificationPromise = null;
2356
+ }
2357
+ }
2358
+ async activate(licenseKey) {
2359
+ const trimmed = licenseKey.trim();
2360
+ if (!trimmed) {
2361
+ throw new Error("License key is required.");
2362
+ }
2363
+ const secret = await getLicenseSecret();
2364
+ const digest = import_node_crypto2.default.createHash("sha256").update(trimmed).digest("hex");
2365
+ const result = await requestGumroadVerify(trimmed, "activation");
2366
+ ensureWithinUseLimit(result);
2367
+ const normalized = normalizeVerificationResult(result);
2368
+ const stored = {
2369
+ licenseKey: trimmed,
2370
+ purchaseEmail: normalized.email,
2371
+ lastVerifiedAt: nowIso(),
2372
+ nextCheckAt: new Date(Date.now() + LICENSE_REFRESH_INTERVAL_MS).toISOString(),
2373
+ status: "active",
2374
+ lastVerificationError: null
2375
+ };
2376
+ const signed = withSignature(stored, secret);
2377
+ await persistLicense(signed);
2378
+ const status = toLicenseStatus(signed, {
2379
+ message: `License verified (${digest.slice(0, 8)}).`
2380
+ });
2381
+ this.cache = status;
2382
+ return status;
2383
+ }
2384
+ applyProfileCount(status, profileCount) {
2385
+ const profilesRemaining = resolveProfilesRemaining(status.profileLimit, profileCount);
2386
+ return {
2387
+ ...status,
2388
+ profilesRemaining
2389
+ };
2390
+ }
2391
+ refreshStatusInBackground(options = {}) {
2392
+ if (this.refreshPromise) {
2393
+ return;
2394
+ }
2395
+ const forceRefresh = Boolean(options.force);
2396
+ this.refreshPromise = (async () => {
2397
+ try {
2398
+ await this.getStatus({ forceRefresh });
2399
+ } catch (error) {
2400
+ console.warn("Background license refresh failed:", error);
2401
+ } finally {
2402
+ this.refreshPromise = null;
2403
+ }
2404
+ })();
2405
+ }
2406
+ };
2407
+ var licenseService = new LicenseService();
2408
+
2409
+ // ../../lib/cloud-sync-service.ts
2410
+ var import_node_fs4 = require("fs");
2411
+ var import_node_path6 = __toESM(require("path"));
2412
+
2413
+ // ../../lib/cloud-sync-types.ts
2414
+ var CLOUD_SYNC_SCHEMA_VERSION = 1;
2415
+
2416
+ // ../../lib/type-guards.ts
2417
+ function isRecord3(value) {
2418
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2419
+ }
2420
+ function toIsoOrNull(value) {
2421
+ if (typeof value !== "string") {
2422
+ return null;
2423
+ }
2424
+ const parsed = Date.parse(value);
2425
+ return Number.isNaN(parsed) ? null : new Date(parsed).toISOString();
2426
+ }
2427
+
2428
+ // ../../lib/cloud-sync-client.ts
2429
+ var DEFAULT_CLOUD_SYNC_API_BASE_URL = "https://api.codexuse.com";
2430
+ var CLOUD_SYNC_TIMEOUT_MS = 15e3;
2431
+ var CloudSyncClientError = class extends Error {
2432
+ constructor(message, status = null) {
2433
+ super(message);
2434
+ this.name = "CloudSyncClientError";
2435
+ this.status = status;
2436
+ }
2437
+ };
2438
+ function normalizeBaseUrl(value) {
2439
+ const trimmed = value.trim();
2440
+ return trimmed.endsWith("/") ? trimmed.slice(0, -1) : trimmed;
2441
+ }
2442
+ function getCloudSyncApiBaseUrl() {
2443
+ const fromEnv = typeof process.env.CODEXUSE_API_BASE_URL === "string" ? process.env.CODEXUSE_API_BASE_URL : "";
2444
+ if (fromEnv.trim().length === 0) {
2445
+ return DEFAULT_CLOUD_SYNC_API_BASE_URL;
2446
+ }
2447
+ return normalizeBaseUrl(fromEnv);
2448
+ }
2449
+ function buildSyncUrl(pathname) {
2450
+ const baseUrl = getCloudSyncApiBaseUrl();
2451
+ const normalizedPath = pathname.startsWith("/") ? pathname : `/${pathname}`;
2452
+ return `${baseUrl}${normalizedPath}`;
2453
+ }
2454
+ function normalizeSnapshot(value) {
2455
+ if (!isRecord3(value)) {
2456
+ return null;
2457
+ }
2458
+ if (isRecord3(value.snapshot)) {
2459
+ const nested = normalizeSnapshot(value.snapshot);
2460
+ if (nested) {
2461
+ return nested;
2462
+ }
2463
+ }
2464
+ const updatedAt = toIsoOrNull(value.updatedAt);
2465
+ if (!updatedAt) {
2466
+ return null;
2467
+ }
2468
+ const schemaVersion = Number(value.schemaVersion);
2469
+ if (!Number.isFinite(schemaVersion) || schemaVersion !== CLOUD_SYNC_SCHEMA_VERSION) {
2470
+ return null;
2471
+ }
2472
+ const rawProfiles = Array.isArray(value.profiles) ? value.profiles : [];
2473
+ const profiles = rawProfiles.map((entry) => isRecord3(entry) ? entry : null).filter((entry) => Boolean(entry)).map((entry) => {
2474
+ const data = isRecord3(entry.data) ? entry.data : {};
2475
+ const metadata = isRecord3(entry.metadata) ? entry.metadata : void 0;
2476
+ return {
2477
+ name: typeof entry.name === "string" ? entry.name : "",
2478
+ displayName: typeof entry.displayName === "string" ? entry.displayName : null,
2479
+ data,
2480
+ metadata,
2481
+ accountId: typeof entry.accountId === "string" ? entry.accountId : null,
2482
+ workspaceId: typeof entry.workspaceId === "string" ? entry.workspaceId : null,
2483
+ workspaceName: typeof entry.workspaceName === "string" ? entry.workspaceName : null,
2484
+ email: typeof entry.email === "string" ? entry.email : null,
2485
+ authMethod: typeof entry.authMethod === "string" ? entry.authMethod : null,
2486
+ createdAt: typeof entry.createdAt === "string" ? entry.createdAt : null,
2487
+ updatedAt: typeof entry.updatedAt === "string" ? entry.updatedAt : null
2488
+ };
2489
+ }).filter((entry) => entry.name.trim().length > 0);
2490
+ const rawSettings = value.settingsJson;
2491
+ const settingsJson = isRecord3(rawSettings) ? rawSettings : null;
2492
+ return {
2493
+ schemaVersion: CLOUD_SYNC_SCHEMA_VERSION,
2494
+ updatedAt,
2495
+ profiles,
2496
+ configTomlContent: typeof value.configTomlContent === "string" ? value.configTomlContent : null,
2497
+ settingsJson
2498
+ };
2499
+ }
2500
+ async function requestJson(method, pathname, licenseKey, body, rawBody) {
2501
+ const controller = new AbortController();
2502
+ const timeout = setTimeout(() => controller.abort(), CLOUD_SYNC_TIMEOUT_MS);
2503
+ try {
2504
+ const response = await fetch(buildSyncUrl(pathname), {
2505
+ method,
2506
+ headers: {
2507
+ "Content-Type": "application/json",
2508
+ "x-codexuse-license-key": licenseKey
2509
+ },
2510
+ body: typeof rawBody === "string" ? rawBody : body === void 0 ? void 0 : JSON.stringify(body),
2511
+ signal: controller.signal
2512
+ });
2513
+ if (!response.ok) {
2514
+ const message = await response.text().catch(() => "");
2515
+ throw new CloudSyncClientError(
2516
+ message || `Cloud sync request failed (${response.status}).`,
2517
+ response.status
2518
+ );
2519
+ }
2520
+ return await response.json();
2521
+ } catch (error) {
2522
+ if (error instanceof CloudSyncClientError) {
2523
+ throw error;
2524
+ }
2525
+ if (error instanceof Error && error.name === "AbortError") {
2526
+ throw new CloudSyncClientError("Cloud sync request timed out.");
2527
+ }
2528
+ throw new CloudSyncClientError(
2529
+ error instanceof Error ? error.message : "Cloud sync request failed."
2530
+ );
2531
+ } finally {
2532
+ clearTimeout(timeout);
2533
+ }
2534
+ }
2535
+ async function fetchRemoteSnapshot(licenseKey) {
2536
+ const response = await requestJson("GET", "/v1/sync/snapshot", licenseKey);
2537
+ return normalizeSnapshot(response.snapshot);
2538
+ }
2539
+ async function fetchRemoteSnapshotMeta(licenseKey) {
2540
+ const response = await requestJson(
2541
+ "GET",
2542
+ "/v1/sync/snapshot/meta",
2543
+ licenseKey
2544
+ );
2545
+ return toIsoOrNull(response.updatedAt);
2546
+ }
2547
+ async function pushRemoteSnapshot(licenseKey, snapshot, options) {
2548
+ const serializedSnapshot = typeof options?.serializedSnapshot === "string" ? options.serializedSnapshot : JSON.stringify(snapshot);
2549
+ const response = await requestJson(
2550
+ "PUT",
2551
+ "/v1/sync/snapshot",
2552
+ licenseKey,
2553
+ void 0,
2554
+ `{"snapshot":${serializedSnapshot}}`
2555
+ );
2556
+ const normalized = normalizeSnapshot(response.snapshot);
2557
+ if (!normalized) {
2558
+ throw new CloudSyncClientError("Cloud sync server returned an invalid snapshot payload.");
2559
+ }
2560
+ const status = response.status === "stale" ? "stale" : "applied";
2561
+ return {
2562
+ status,
2563
+ snapshot: normalized
2564
+ };
2565
+ }
2566
+
2567
+ // ../../lib/codex-config.ts
2568
+ var import_promises2 = require("fs/promises");
2569
+ var import_node_path5 = __toESM(require("path"));
2570
+ var import_toml2 = require("@iarna/toml");
2571
+
2572
+ // ../../lib/codex-config-metadata.ts
2573
+ var import_node_child_process = require("child_process");
2574
+ var import_promises = require("fs/promises");
2575
+ var import_node_path4 = __toESM(require("path"));
2576
+
2577
+ // ../../lib/codex-cli.ts
2578
+ var import_node_fs3 = require("fs");
2579
+ var import_node_path3 = __toESM(require("path"));
2580
+ var cachedStatus = null;
2581
+ var cachedChannel = null;
2582
+ function fileExists(candidate) {
2583
+ if (!candidate) {
2584
+ return null;
2585
+ }
2586
+ const normalized = import_node_path3.default.resolve(candidate);
2587
+ try {
2588
+ const stats = (0, import_node_fs3.statSync)(normalized);
2589
+ if (stats.isFile()) {
2590
+ return normalized;
2591
+ }
2592
+ } catch {
2593
+ }
2594
+ return null;
2595
+ }
2596
+ function resolveBundledCodexBinary(channel) {
2597
+ const candidates = [];
2598
+ const processWithResources = process;
2599
+ const resourcesPath = processWithResources.resourcesPath;
2600
+ const packageSegments = channel === "alpha" ? ["@openai", "codex-alpha"] : ["@openai", "codex"];
2601
+ if (resourcesPath) {
2602
+ candidates.push(
2603
+ import_node_path3.default.join(resourcesPath, "app.asar.unpacked", "node_modules", ...packageSegments, "bin", "codex.js")
2604
+ );
2605
+ candidates.push(
2606
+ import_node_path3.default.join(resourcesPath, "app.asar.unpacked", "node_modules", ...packageSegments, "bin", "codex")
2607
+ );
2608
+ }
2609
+ const projectRoot = process.cwd();
2610
+ candidates.push(import_node_path3.default.join(projectRoot, "node_modules", ...packageSegments, "bin", "codex.js"));
2611
+ candidates.push(import_node_path3.default.join(projectRoot, "node_modules", ...packageSegments, "bin", "codex"));
2612
+ for (const candidate of candidates) {
2613
+ const resolved = fileExists(candidate);
2614
+ if (resolved) {
2615
+ return resolved;
2616
+ }
2617
+ }
2618
+ return null;
2619
+ }
2620
+ function resolveBundledCodexBinaryWithFallback(requestedChannel) {
2621
+ const direct = resolveBundledCodexBinary(requestedChannel);
2622
+ if (direct) {
2623
+ return { path: direct, resolvedChannel: requestedChannel, fallbackUsed: false };
2624
+ }
2625
+ if (requestedChannel === "alpha") {
2626
+ const stable = resolveBundledCodexBinary("stable");
2627
+ if (stable) {
2628
+ return { path: stable, resolvedChannel: "stable", fallbackUsed: true };
2629
+ }
2630
+ }
2631
+ return { path: null, resolvedChannel: null, fallbackUsed: false };
2632
+ }
2633
+ function buildUnavailableStatus(channel) {
2634
+ const channelLabel = channel === "alpha" ? "alpha " : "";
2635
+ const channelSuffix = channel === "alpha" ? " Switch to Stable in Settings or reinstall CodexUse to restore the CLI." : " Reinstall CodexUse to restore the CLI.";
2636
+ return {
2637
+ available: false,
2638
+ path: null,
2639
+ reason: `Bundled ${channelLabel}Codex CLI is missing.${channelSuffix}`,
2640
+ source: null,
2641
+ channel,
2642
+ requestedChannel: channel,
2643
+ fallbackUsed: false
2644
+ };
2645
+ }
2646
+ function evaluateCodexCliStatus(requestedChannel) {
2647
+ const resolved = resolveBundledCodexBinaryWithFallback(requestedChannel);
2648
+ if (resolved.path && resolved.resolvedChannel) {
2649
+ return {
2650
+ available: true,
2651
+ path: resolved.path,
2652
+ reason: null,
2653
+ source: "bundled",
2654
+ channel: resolved.resolvedChannel,
2655
+ requestedChannel,
2656
+ fallbackUsed: resolved.fallbackUsed
2657
+ };
2658
+ }
2659
+ return buildUnavailableStatus(requestedChannel);
2660
+ }
2661
+ async function refreshCodexStatus() {
2662
+ const channel = await getCodexCliChannel();
2663
+ cachedChannel = channel;
2664
+ cachedStatus = evaluateCodexCliStatus(channel);
2665
+ return { ...cachedStatus };
2666
+ }
2667
+ var CodexCliMissingError = class extends Error {
2668
+ constructor(status, message) {
2669
+ super(message ?? status.reason ?? "Codex CLI is not available");
2670
+ Object.setPrototypeOf(this, new.target.prototype);
2671
+ this.name = "CodexCliMissingError";
2672
+ this.status = { ...status };
2673
+ }
2674
+ };
2675
+ async function requireCodexCli() {
2676
+ const status = await refreshCodexStatus();
2677
+ if (!status.available || !status.path) {
2678
+ throw new CodexCliMissingError(status);
2679
+ }
2680
+ return status.path;
2681
+ }
2682
+
2683
+ // ../../lib/codex-config-metadata.ts
2684
+ var CONFIG_SCHEMA_URL = "https://raw.githubusercontent.com/openai/codex/main/codex-rs/core/config.schema.json";
2685
+ var METADATA_CACHE_TTL_MS = 6e4;
2686
+ var FALLBACK_SCHEMA_TOP_LEVEL_KEYS = [
2687
+ "agents",
2688
+ "analytics",
2689
+ "approval_policy",
2690
+ "apps",
2691
+ "chatgpt_base_url",
2692
+ "check_for_update_on_startup",
2693
+ "cli_auth_credentials_store",
2694
+ "compact_prompt",
2695
+ "developer_instructions",
2696
+ "disable_paste_burst",
2697
+ "experimental_compact_prompt_file",
2698
+ "experimental_use_freeform_apply_patch",
2699
+ "experimental_use_unified_exec_tool",
2700
+ "features",
2701
+ "feedback",
2702
+ "file_opener",
2703
+ "forced_chatgpt_workspace_id",
2704
+ "forced_login_method",
2705
+ "ghost_snapshot",
2706
+ "hide_agent_reasoning",
2707
+ "history",
2708
+ "instructions",
2709
+ "log_dir",
2710
+ "mcp_oauth_callback_port",
2711
+ "mcp_oauth_credentials_store",
2712
+ "mcp_servers",
2713
+ "model",
2714
+ "model_auto_compact_token_limit",
2715
+ "model_context_window",
2716
+ "model_instructions_file",
2717
+ "model_provider",
2718
+ "model_providers",
2719
+ "model_reasoning_effort",
2720
+ "model_reasoning_summary",
2721
+ "model_supports_reasoning_summaries",
2722
+ "model_verbosity",
2723
+ "notice",
2724
+ "notify",
2725
+ "oss_provider",
2726
+ "otel",
2727
+ "personality",
2728
+ "profile",
2729
+ "profiles",
2730
+ "project_doc_fallback_filenames",
2731
+ "project_doc_max_bytes",
2732
+ "project_root_markers",
2733
+ "projects",
2734
+ "review_model",
2735
+ "sandbox_mode",
2736
+ "sandbox_workspace_write",
2737
+ "shell_environment_policy",
2738
+ "show_raw_agent_reasoning",
2739
+ "skills",
2740
+ "suppress_unstable_features_warning",
2741
+ "tool_output_token_limit",
2742
+ "tools",
2743
+ "tui",
2744
+ "web_search",
2745
+ "windows_wsl_setup_acknowledged"
2746
+ ];
2747
+ var FALLBACK_FEATURE_KEYS = [
2748
+ "apply_patch_freeform",
2749
+ "apps",
2750
+ "child_agents_md",
2751
+ "collab",
2752
+ "collaboration_modes",
2753
+ "connectors",
2754
+ "elevated_windows_sandbox",
2755
+ "enable_experimental_windows_sandbox",
2756
+ "enable_request_compression",
2757
+ "experimental_use_freeform_apply_patch",
2758
+ "experimental_use_unified_exec_tool",
2759
+ "experimental_windows_sandbox",
2760
+ "include_apply_patch_tool",
2761
+ "memory_tool",
2762
+ "personality",
2763
+ "powershell_utf8",
2764
+ "remote_models",
2765
+ "request_rule",
2766
+ "responses_websockets",
2767
+ "responses_websockets_v2",
2768
+ "runtime_metrics",
2769
+ "search_tool",
2770
+ "shell_snapshot",
2771
+ "shell_tool",
2772
+ "skill_env_var_dependency_prompt",
2773
+ "skill_mcp_dependency_install",
2774
+ "sqlite",
2775
+ "steer",
2776
+ "undo",
2777
+ "unified_exec",
2778
+ "use_linux_sandbox_bwrap",
2779
+ "web_search",
2780
+ "web_search_cached",
2781
+ "web_search_request"
2782
+ ];
2783
+ var metadataCache = null;
2784
+ var metadataCacheAt = 0;
2785
+ function isRecord4(value) {
2786
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2787
+ }
2788
+ function uniqueSorted(values) {
2789
+ return Array.from(new Set(values)).sort((a, b) => a.localeCompare(b));
2790
+ }
2791
+ async function runCommand(command, args, timeoutMs = 3e3) {
2792
+ return new Promise((resolve, reject) => {
2793
+ const child = (0, import_node_child_process.spawn)(command, args, {
2794
+ stdio: ["ignore", "pipe", "pipe"],
2795
+ env: process.env
2796
+ });
2797
+ let stdout = "";
2798
+ let stderr = "";
2799
+ child.stdout?.on("data", (chunk) => {
2800
+ stdout += chunk.toString();
2801
+ });
2802
+ child.stderr?.on("data", (chunk) => {
2803
+ stderr += chunk.toString();
2804
+ });
2805
+ const timeout = setTimeout(() => {
2806
+ child.kill();
2807
+ reject(new Error(`Command timed out: ${command} ${args.join(" ")}`));
2808
+ }, timeoutMs);
2809
+ child.on("error", (error) => {
2810
+ clearTimeout(timeout);
2811
+ reject(error);
2812
+ });
2813
+ child.on("close", (code) => {
2814
+ clearTimeout(timeout);
2815
+ if (code !== 0 && !stdout.trim() && stderr.trim()) {
2816
+ resolve({ stdout: stderr, exitCode: code });
2817
+ return;
2818
+ }
2819
+ resolve({ stdout, exitCode: code });
2820
+ });
2821
+ });
2822
+ }
2823
+ async function runCodexCommand(args) {
2824
+ const codexPath = await requireCodexCli();
2825
+ const isJsLauncher = codexPath.endsWith(".js");
2826
+ const command = isJsLauncher ? process.execPath : codexPath;
2827
+ const commandArgs = isJsLauncher ? [codexPath, ...args] : args;
2828
+ return runCommand(command, commandArgs);
2829
+ }
2830
+ function parseFeatureCatalog(output) {
2831
+ const entries = [];
2832
+ const lines = output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
2833
+ for (const line of lines) {
2834
+ const match = line.match(/^(\S+)\s+(stable|experimental|deprecated|under development)\s+(true|false)$/i);
2835
+ if (!match) {
2836
+ continue;
2837
+ }
2838
+ const [, key, stageRaw, enabledRaw] = match;
2839
+ const stage = stageRaw.toLowerCase();
2840
+ entries.push({
2841
+ key,
2842
+ stage,
2843
+ enabled: enabledRaw === "true"
2844
+ });
2845
+ }
2846
+ return entries.sort((a, b) => a.key.localeCompare(b.key));
2847
+ }
2848
+ function parseSchemaKeys(schemaRaw) {
2849
+ const parsed = JSON.parse(schemaRaw);
2850
+ if (!isRecord4(parsed)) {
2851
+ return {
2852
+ topLevelKeys: FALLBACK_SCHEMA_TOP_LEVEL_KEYS,
2853
+ featureKeys: FALLBACK_FEATURE_KEYS
2854
+ };
2855
+ }
2856
+ const properties = isRecord4(parsed.properties) ? parsed.properties : {};
2857
+ const topLevelKeys = uniqueSorted(Object.keys(properties));
2858
+ const features = isRecord4(properties.features) ? properties.features : {};
2859
+ const featureProperties = isRecord4(features.properties) ? features.properties : {};
2860
+ const featureKeys = uniqueSorted(Object.keys(featureProperties));
2861
+ return {
2862
+ topLevelKeys: topLevelKeys.length > 0 ? topLevelKeys : FALLBACK_SCHEMA_TOP_LEVEL_KEYS,
2863
+ featureKeys: featureKeys.length > 0 ? featureKeys : FALLBACK_FEATURE_KEYS
2864
+ };
2865
+ }
2866
+ async function readSchemaFromLocal() {
2867
+ const candidates = [
2868
+ process.env.CODEX_CONFIG_SCHEMA_PATH,
2869
+ import_node_path4.default.join(process.cwd(), "codex-rs", "core", "config.schema.json"),
2870
+ import_node_path4.default.join(process.cwd(), "node_modules", "@openai", "codex", "codex-rs", "core", "config.schema.json"),
2871
+ import_node_path4.default.join(process.cwd(), "node_modules", "@openai", "codex-alpha", "codex-rs", "core", "config.schema.json")
2872
+ ].filter((candidate) => Boolean(candidate));
2873
+ for (const candidate of candidates) {
2874
+ try {
2875
+ return await (0, import_promises.readFile)(candidate, "utf8");
2876
+ } catch {
2877
+ }
2878
+ }
2879
+ return null;
2880
+ }
2881
+ async function readSchemaFromRemote() {
2882
+ const controller = new AbortController();
2883
+ const timeout = setTimeout(() => controller.abort(), 2500);
2884
+ try {
2885
+ const response = await fetch(CONFIG_SCHEMA_URL, {
2886
+ signal: controller.signal,
2887
+ headers: {
2888
+ "User-Agent": "codexuse-desktop"
2889
+ }
2890
+ });
2891
+ if (!response.ok) {
2892
+ return null;
2893
+ }
2894
+ return await response.text();
2895
+ } catch {
2896
+ return null;
2897
+ } finally {
2898
+ clearTimeout(timeout);
2899
+ }
2900
+ }
2901
+ async function readSchemaKeys() {
2902
+ const local = await readSchemaFromLocal();
2903
+ if (local) {
2904
+ try {
2905
+ return parseSchemaKeys(local);
2906
+ } catch {
2907
+ }
2908
+ }
2909
+ const remote = await readSchemaFromRemote();
2910
+ if (remote) {
2911
+ try {
2912
+ return parseSchemaKeys(remote);
2913
+ } catch {
2914
+ }
2915
+ }
2916
+ return {
2917
+ topLevelKeys: FALLBACK_SCHEMA_TOP_LEVEL_KEYS,
2918
+ featureKeys: FALLBACK_FEATURE_KEYS
2919
+ };
2920
+ }
2921
+ async function readCodexVersion() {
2922
+ try {
2923
+ const { stdout } = await runCodexCommand(["--version"]);
2924
+ const value = stdout.trim();
2925
+ return value.length > 0 ? value : null;
2926
+ } catch {
2927
+ return null;
2928
+ }
2929
+ }
2930
+ async function readFeatureCatalog() {
2931
+ try {
2932
+ const { stdout } = await runCodexCommand(["features", "list"]);
2933
+ return parseFeatureCatalog(stdout);
2934
+ } catch {
2935
+ return [];
2936
+ }
2937
+ }
2938
+ async function getCodexConfigMetadata(forceRefresh = false) {
2939
+ const now = Date.now();
2940
+ if (!forceRefresh && metadataCache && now - metadataCacheAt < METADATA_CACHE_TTL_MS) {
2941
+ return metadataCache;
2942
+ }
2943
+ const [schemaKeys, featureCatalog, codexVersion] = await Promise.all([
2944
+ readSchemaKeys(),
2945
+ readFeatureCatalog(),
2946
+ readCodexVersion()
2947
+ ]);
2948
+ const catalogMap = /* @__PURE__ */ new Map();
2949
+ for (const entry of featureCatalog) {
2950
+ catalogMap.set(entry.key, entry);
2951
+ }
2952
+ for (const featureKey of schemaKeys.featureKeys) {
2953
+ if (!catalogMap.has(featureKey)) {
2954
+ catalogMap.set(featureKey, {
2955
+ key: featureKey,
2956
+ stage: "unknown",
2957
+ enabled: null
2958
+ });
2959
+ }
2960
+ }
2961
+ const mergedFeatureCatalog = Array.from(catalogMap.values()).sort((a, b) => a.key.localeCompare(b.key));
2962
+ const metadata = {
2963
+ schemaTopLevelKeys: uniqueSorted(schemaKeys.topLevelKeys),
2964
+ schemaFeatureKeys: uniqueSorted(schemaKeys.featureKeys),
2965
+ featureCatalog: mergedFeatureCatalog,
2966
+ codexVersion,
2967
+ fetchedAt: now
2968
+ };
2969
+ metadataCache = metadata;
2970
+ metadataCacheAt = now;
2971
+ return metadata;
2972
+ }
2973
+
2974
+ // ../../lib/errors.ts
2975
+ var ANSI_PATTERN = /\u001B\[[0-9;]*m/g;
2976
+ function stripAnsi(value) {
2977
+ return value.replace(ANSI_PATTERN, "");
2978
+ }
2979
+ function normalizeWhitespace(value) {
2980
+ return value.replace(/\s+/g, " ").trim();
2981
+ }
2982
+ function formatUserFacingError(error, options = {}) {
2983
+ const fallback = options.fallback ?? "Something went wrong. Please try again.";
2984
+ if (error === null || typeof error === "undefined") {
2985
+ return fallback;
2986
+ }
2987
+ const rawMessage = typeof error === "string" ? error : error instanceof Error ? error.message : String(error);
2988
+ let cleaned = normalizeWhitespace(stripAnsi(rawMessage));
2989
+ if (!cleaned) {
2990
+ return fallback;
2991
+ }
2992
+ if (cleaned.startsWith("Error invoking remote method")) {
2993
+ cleaned = cleaned.replace(/^Error invoking remote method '[^']+': Error: /, "");
2994
+ }
2995
+ const lower = cleaned.toLowerCase();
2996
+ if (options.notFoundFallback && (lower.includes("enoent") || lower.includes("not found"))) {
2997
+ return options.notFoundFallback;
2998
+ }
2999
+ if (lower.includes("rate limit") || lower.includes("quota exceeded") || lower.includes("too many requests") || lower.includes("429")) {
3000
+ cleaned = "Rate limit exceeded. Please wait a moment or upgrade your plan.";
3001
+ } else if (lower.includes("timeout") || lower.includes("timed out")) {
3002
+ cleaned = "Request timed out. Check your connection and try again.";
3003
+ } else if (lower.includes("network") || lower.includes("econn") || lower.includes("networkerror") || lower.includes("connection")) {
3004
+ cleaned = "Network issue. Check your connection and retry.";
3005
+ } else if (lower.includes("permission") || lower.includes("eacces")) {
3006
+ cleaned = "Permission denied. Restart CodexUse and retry.";
3007
+ } else if (lower.includes("openai codex") && lower.includes("workdir:")) {
3008
+ if (lower.includes("error:")) {
3009
+ const parts = cleaned.split(/error:/i);
3010
+ if (parts.length > 1) {
3011
+ cleaned = parts[parts.length - 1].trim();
3012
+ }
3013
+ } else {
3014
+ cleaned = "Codex CLI failed to respond. Please try again.";
3015
+ }
3016
+ }
3017
+ const maxLength = typeof options.maxLength === "number" ? options.maxLength : 220;
3018
+ if (cleaned.length > maxLength) {
3019
+ cleaned = `${cleaned.slice(0, maxLength).trimEnd()}\u2026`;
3020
+ }
3021
+ return cleaned;
3022
+ }
3023
+
3024
+ // ../../lib/codex-config.ts
3025
+ var DEFAULT_CONFIG_TEMPLATE = [
3026
+ "# ~/.codex/config.toml (managed by CodexUse)",
3027
+ "# Safe defaults with prompts and sandboxed access.",
3028
+ "# To enable full access, opt into the YOLO preset from settings.",
3029
+ "",
3030
+ 'model = "gpt-5.3-codex"',
3031
+ 'review_model = "gpt-5.3-codex"',
3032
+ 'model_reasoning_effort = "medium"',
3033
+ 'model_reasoning_summary = "auto"',
3034
+ 'model_verbosity = "medium"',
3035
+ 'approval_policy = "on-request"',
3036
+ 'sandbox_mode = "read-only"',
3037
+ 'web_search = "cached"',
3038
+ ""
3039
+ ].join("\n");
3040
+ var YOLO_PRESET = {
3041
+ model: "gpt-5.3-codex",
3042
+ reviewModel: "gpt-5.3-codex",
3043
+ modelReasoningEffort: "xhigh",
3044
+ modelReasoningSummary: "detailed",
3045
+ modelVerbosity: "high",
3046
+ approvalPolicy: "never",
3047
+ sandboxMode: "danger-full-access",
3048
+ webSearch: "live",
3049
+ personality: "pragmatic",
3050
+ toolOutputTokenLimit: 25e3,
3051
+ modelAutoCompactTokenLimit: 233e3,
3052
+ features: {
3053
+ unifiedExec: true,
3054
+ shellSnapshot: true
3055
+ },
3056
+ notice: {
3057
+ hideFullAccessWarning: true,
3058
+ hideGpt51MigrationPrompt: true,
3059
+ hideRateLimitModelNudge: true
3060
+ }
3061
+ };
3062
+ function resolveHomeDir() {
3063
+ const homeDir = process.env.HOME || process.env.USERPROFILE;
3064
+ if (!homeDir) {
3065
+ throw new Error("Unable to determine home directory for Codex config.");
3066
+ }
3067
+ return homeDir;
3068
+ }
3069
+ function getConfigPath() {
3070
+ const homeDir = resolveHomeDir();
3071
+ return import_node_path5.default.join(homeDir, ".codex", "config.toml");
3072
+ }
3073
+ async function ensureConfigDirExists(filePath) {
3074
+ const dir = import_node_path5.default.dirname(filePath);
3075
+ await (0, import_promises2.mkdir)(dir, { recursive: true });
3076
+ }
3077
+ function normalizeLineEndings(content) {
3078
+ return content.replace(/\r\n/g, "\n");
3079
+ }
3080
+ function ensureTrailingNewline(content) {
3081
+ return content.endsWith("\n") ? content : `${content}
3082
+ `;
3083
+ }
3084
+ function tryParseToml(content) {
3085
+ try {
3086
+ const parsed = (0, import_toml2.parse)(content);
3087
+ return { data: parsed ?? {}, error: null };
3088
+ } catch (error) {
3089
+ const maybe = error;
3090
+ const line = typeof maybe?.line === "number" ? maybe.line : null;
3091
+ const column = typeof maybe?.column === "number" ? maybe.column : typeof maybe?.col === "number" ? maybe.col : null;
3092
+ const location = line !== null ? `line ${line}${column !== null ? `, column ${column}` : ""}` : null;
3093
+ const friendly = formatUserFacingError(error, {
3094
+ fallback: "Invalid config.toml syntax. Fix it and try again.",
3095
+ maxLength: 180
3096
+ });
3097
+ const message = location ? `${friendly} (near ${location}).` : friendly;
3098
+ return { data: null, error: message };
3099
+ }
3100
+ }
3101
+ function toStringOrNull(value) {
3102
+ if (typeof value !== "string") {
3103
+ return null;
3104
+ }
3105
+ const trimmed = value.trim();
3106
+ return trimmed.length > 0 ? trimmed : null;
3107
+ }
3108
+ function toBooleanOrNull(value) {
3109
+ if (typeof value === "boolean") {
3110
+ return value;
3111
+ }
3112
+ return null;
3113
+ }
3114
+ function toNumberOrNull(value) {
3115
+ if (typeof value !== "number") {
3116
+ return null;
3117
+ }
3118
+ return Number.isFinite(value) ? value : null;
3119
+ }
3120
+ function toStringArray(value) {
3121
+ if (!Array.isArray(value)) {
3122
+ return [];
3123
+ }
3124
+ return value.map((entry) => typeof entry === "string" ? entry.trim() : "").filter((entry) => entry.length > 0);
3125
+ }
3126
+ function toStringRecord(value) {
3127
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
3128
+ return {};
3129
+ }
3130
+ const result = {};
3131
+ for (const [key, raw] of Object.entries(value)) {
3132
+ if (typeof raw !== "string") {
3133
+ continue;
3134
+ }
3135
+ const normalizedKey = key.trim();
3136
+ const normalizedValue = raw.trim();
3137
+ if (!normalizedKey || !normalizedValue) {
3138
+ continue;
3139
+ }
3140
+ result[normalizedKey] = normalizedValue;
3141
+ }
3142
+ return result;
3143
+ }
3144
+ function buildSettingsSummary(data) {
3145
+ const noticeCandidate = data && typeof data["notice"] === "object" && data["notice"] !== null ? data["notice"] : null;
3146
+ const featuresCandidate = data && typeof data["features"] === "object" && data["features"] !== null && !Array.isArray(data["features"]) ? data["features"] : null;
3147
+ const toolsCandidate = data && typeof data["tools"] === "object" && data["tools"] !== null && !Array.isArray(data["tools"]) ? data["tools"] : null;
3148
+ const featureValues = {};
3149
+ if (featuresCandidate) {
3150
+ for (const [key, value] of Object.entries(featuresCandidate)) {
3151
+ featureValues[key] = toBooleanOrNull(value);
3152
+ }
3153
+ }
3154
+ const modelMigrationsRaw = noticeCandidate ? toStringRecord(noticeCandidate["model_migrations"]) : {};
3155
+ return {
3156
+ model: data ? toStringOrNull(data["model"]) : null,
3157
+ reviewModel: data ? toStringOrNull(data["review_model"]) : null,
3158
+ modelReasoningEffort: data ? toStringOrNull(data["model_reasoning_effort"]) : null,
3159
+ modelReasoningSummary: data ? toStringOrNull(data["model_reasoning_summary"]) : null,
3160
+ modelVerbosity: data ? toStringOrNull(data["model_verbosity"]) : null,
3161
+ approvalPolicy: data ? toStringOrNull(data["approval_policy"]) : null,
3162
+ sandboxMode: data ? toStringOrNull(data["sandbox_mode"]) : null,
3163
+ webSearch: data ? toStringOrNull(data["web_search"]) : null,
3164
+ personality: data ? toStringOrNull(data["personality"]) : null,
3165
+ toolOutputTokenLimit: data ? toNumberOrNull(data["tool_output_token_limit"]) : null,
3166
+ modelAutoCompactTokenLimit: data ? toNumberOrNull(data["model_auto_compact_token_limit"]) : null,
3167
+ modelContextWindow: data ? toNumberOrNull(data["model_context_window"]) : null,
3168
+ features: {
3169
+ unifiedExec: featuresCandidate ? toBooleanOrNull(featuresCandidate["unified_exec"]) : null,
3170
+ shellSnapshot: featuresCandidate ? toBooleanOrNull(featuresCandidate["shell_snapshot"]) : null,
3171
+ values: featureValues
3172
+ },
3173
+ tools: {
3174
+ webSearch: toolsCandidate ? toBooleanOrNull(toolsCandidate["web_search"]) : null,
3175
+ viewImage: toolsCandidate ? toBooleanOrNull(toolsCandidate["view_image"]) : null
3176
+ },
3177
+ notice: {
3178
+ hideFullAccessWarning: noticeCandidate ? toBooleanOrNull(noticeCandidate["hide_full_access_warning"]) : null,
3179
+ hideGpt51MigrationPrompt: noticeCandidate ? toBooleanOrNull(noticeCandidate["hide_gpt5_1_migration_prompt"]) : null,
3180
+ hideLegacyGpt51CodexMaxMigrationPrompt: noticeCandidate ? toBooleanOrNull(noticeCandidate["hide_gpt-5.1-codex-max_migration_prompt"]) : null,
3181
+ hideRateLimitModelNudge: noticeCandidate ? toBooleanOrNull(noticeCandidate["hide_rate_limit_model_nudge"]) : null,
3182
+ modelMigrations: Object.keys(modelMigrationsRaw).length > 0 ? modelMigrationsRaw : null
3183
+ }
3184
+ };
3185
+ }
3186
+ function parseRmcpClientEnabled(data) {
3187
+ const rawFeatures = data && typeof data["features"] === "object" && data["features"] !== null && !Array.isArray(data["features"]) ? data["features"] : null;
3188
+ if (rawFeatures && typeof rawFeatures["rmcp_client"] === "boolean") {
3189
+ return rawFeatures["rmcp_client"];
3190
+ }
3191
+ if (data && typeof data["experimental_use_rmcp_client"] === "boolean") {
3192
+ return data["experimental_use_rmcp_client"];
3193
+ }
3194
+ return false;
3195
+ }
3196
+ function parseMcpServer(name, rawValue) {
3197
+ if (typeof rawValue !== "object" || rawValue === null || Array.isArray(rawValue)) {
3198
+ return null;
3199
+ }
3200
+ const raw = rawValue;
3201
+ const typeValue = toStringOrNull(raw["type"]);
3202
+ const command = toStringOrNull(raw["command"]);
3203
+ const args = toStringArray(raw["args"]);
3204
+ const env = toStringRecord(raw["env"]);
3205
+ const envVars = toStringArray(raw["env_vars"]);
3206
+ const cwd = toStringOrNull(raw["cwd"]);
3207
+ const url = toStringOrNull(raw["url"]);
3208
+ const bearerTokenEnvVar = toStringOrNull(raw["bearer_token_env_var"]);
3209
+ const httpHeaders = toStringRecord(raw["http_headers"]);
3210
+ const envHttpHeaders = toStringRecord(raw["env_http_headers"]);
3211
+ const startupTimeoutSec = toNumberOrNull(raw["startup_timeout_sec"]);
3212
+ const toolTimeoutSec = toNumberOrNull(raw["tool_timeout_sec"]);
3213
+ const enabledTools = toStringArray(raw["enabled_tools"]);
3214
+ const disabledTools = toStringArray(raw["disabled_tools"]);
3215
+ const enabled = typeof raw["enabled"] === "boolean" ? raw["enabled"] : true;
3216
+ const transport = typeValue === "stdio" || typeValue === "http" || typeValue === "sse" ? typeValue : url ? "http" : "stdio";
3217
+ return {
3218
+ name,
3219
+ transport,
3220
+ enabled,
3221
+ command,
3222
+ args: args.length > 0 ? args : null,
3223
+ cwd,
3224
+ env: Object.keys(env).length > 0 ? env : null,
3225
+ envVars: envVars.length > 0 ? envVars : null,
3226
+ url,
3227
+ bearerTokenEnvVar,
3228
+ httpHeaders: Object.keys(httpHeaders).length > 0 ? httpHeaders : null,
3229
+ envHttpHeaders: Object.keys(envHttpHeaders).length > 0 ? envHttpHeaders : null,
3230
+ startupTimeoutSec,
3231
+ toolTimeoutSec,
3232
+ enabledTools: enabledTools.length > 0 ? enabledTools : null,
3233
+ disabledTools: disabledTools.length > 0 ? disabledTools : null
3234
+ };
3235
+ }
3236
+ function buildMcpServersList(data) {
3237
+ const rawServers = data?.["mcp_servers"];
3238
+ if (typeof rawServers !== "object" || rawServers === null || Array.isArray(rawServers)) {
3239
+ return [];
3240
+ }
3241
+ const servers = [];
3242
+ for (const [name, rawValue] of Object.entries(rawServers)) {
3243
+ const parsed = parseMcpServer(name, rawValue);
3244
+ if (parsed) {
3245
+ servers.push(parsed);
3246
+ }
3247
+ }
3248
+ return servers;
3249
+ }
3250
+ function getFeatureStage(metadata, featureKey) {
3251
+ const found = metadata.featureCatalog.find((entry) => entry.key === featureKey);
3252
+ return found?.stage ?? "unknown";
3253
+ }
3254
+ function looksLikeSecretValue(value) {
3255
+ const normalized = value.trim();
3256
+ if (normalized.length < 12) {
3257
+ return false;
3258
+ }
3259
+ if (/\s/.test(normalized)) {
3260
+ return false;
3261
+ }
3262
+ if (!/^[A-Za-z0-9._:-]+$/.test(normalized)) {
3263
+ return false;
3264
+ }
3265
+ const hasLetters = /[A-Za-z]/.test(normalized);
3266
+ const hasNumbers = /\d/.test(normalized);
3267
+ return hasLetters && hasNumbers;
3268
+ }
3269
+ function buildConfigDiagnostics(data, summary, mcpServers, metadata) {
3270
+ const diagnostics = [];
3271
+ const featureTable = data && typeof data["features"] === "object" && data["features"] !== null && !Array.isArray(data["features"]) ? data["features"] : null;
3272
+ const knownTopLevelKeys = new Set(metadata.schemaTopLevelKeys);
3273
+ if (data) {
3274
+ for (const key of Object.keys(data)) {
3275
+ if (knownTopLevelKeys.has(key)) {
3276
+ continue;
3277
+ }
3278
+ diagnostics.push({
3279
+ id: `unknown-top-level:${key}`,
3280
+ category: "config",
3281
+ severity: "warning",
3282
+ title: `Unknown config key: ${key}`,
3283
+ message: `This top-level key is not in the known Codex schema and may be ignored by Codex CLI.`,
3284
+ fixId: null,
3285
+ fixLabel: null
3286
+ });
3287
+ }
3288
+ }
3289
+ if (featureTable) {
3290
+ const knownFeatureKeys = new Set(metadata.schemaFeatureKeys);
3291
+ for (const [featureKey] of Object.entries(featureTable)) {
3292
+ if (!knownFeatureKeys.has(featureKey)) {
3293
+ diagnostics.push({
3294
+ id: `unknown-feature:${featureKey}`,
3295
+ category: "feature",
3296
+ severity: "warning",
3297
+ title: `Unknown feature flag: features.${featureKey}`,
3298
+ message: "This feature is not recognized in the current schema. Keeping it is non-destructive, but it may do nothing.",
3299
+ fixId: `remove-feature:${featureKey}`,
3300
+ fixLabel: "Remove flag"
3301
+ });
3302
+ continue;
3303
+ }
3304
+ const stage = getFeatureStage(metadata, featureKey);
3305
+ if (stage === "deprecated") {
3306
+ diagnostics.push({
3307
+ id: `deprecated-feature:${featureKey}`,
3308
+ category: "feature",
3309
+ severity: "warning",
3310
+ title: `Deprecated feature flag: features.${featureKey}`,
3311
+ message: "Codex marks this feature as deprecated. Prefer removing it.",
3312
+ fixId: `remove-feature:${featureKey}`,
3313
+ fixLabel: "Remove deprecated flag"
3314
+ });
3315
+ }
3316
+ }
3317
+ }
3318
+ for (const server of mcpServers) {
3319
+ if (server.transport === "stdio" && server.args) {
3320
+ for (let index = 0; index < server.args.length; index += 1) {
3321
+ const value = server.args[index] ?? "";
3322
+ if (/(api[-_]?key|token|secret)\s*=\s*/i.test(value)) {
3323
+ diagnostics.push({
3324
+ id: `mcp-secret-arg:${server.name}:${index}`,
3325
+ category: "security",
3326
+ severity: "warning",
3327
+ title: `Potential secret in MCP args (${server.name})`,
3328
+ message: "Argument text appears to contain a literal token/API key. Move secrets to environment variables.",
3329
+ fixId: null,
3330
+ fixLabel: null
3331
+ });
3332
+ }
3333
+ }
3334
+ }
3335
+ if (server.httpHeaders) {
3336
+ for (const [header, headerValue] of Object.entries(server.httpHeaders)) {
3337
+ if (!looksLikeSecretValue(headerValue)) {
3338
+ continue;
3339
+ }
3340
+ diagnostics.push({
3341
+ id: `mcp-secret-header:${server.name}:${header}`,
3342
+ category: "security",
3343
+ severity: "warning",
3344
+ title: `Potential secret in MCP header (${server.name})`,
3345
+ message: `HTTP header '${header}' appears to contain a literal secret. Prefer env_http_headers or bearer_token_env_var.`,
3346
+ fixId: null,
3347
+ fixLabel: null
3348
+ });
3349
+ }
3350
+ }
3351
+ }
3352
+ if (summary.approvalPolicy === "never" && summary.sandboxMode === "danger-full-access") {
3353
+ diagnostics.push({
3354
+ id: "risk:yolo-combo",
3355
+ category: "risk",
3356
+ severity: "risk",
3357
+ title: "High-risk execution mode enabled",
3358
+ message: "approval_policy=never + sandbox_mode=danger-full-access removes approval and sandbox guardrails.",
3359
+ fixId: null,
3360
+ fixLabel: null
3361
+ });
3362
+ }
3363
+ return diagnostics;
3364
+ }
3365
+ function isYoloApplied(summary) {
3366
+ 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;
3367
+ }
3368
+ async function readConfigContent() {
3369
+ const configPath = getConfigPath();
3370
+ try {
3371
+ const content = await (0, import_promises2.readFile)(configPath, "utf8");
3372
+ return {
3373
+ exists: true,
3374
+ content: normalizeLineEndings(content)
3375
+ };
3376
+ } catch (error) {
3377
+ const nodeError = error;
3378
+ if (nodeError.code === "ENOENT") {
3379
+ return {
3380
+ exists: false,
3381
+ content: DEFAULT_CONFIG_TEMPLATE
3382
+ };
3383
+ }
3384
+ throw error;
3385
+ }
3386
+ }
3387
+ async function writeConfigContent(content) {
3388
+ const configPath = getConfigPath();
3389
+ await ensureConfigDirExists(configPath);
3390
+ const normalized = ensureTrailingNewline(normalizeLineEndings(content));
3391
+ await (0, import_promises2.writeFile)(configPath, normalized, "utf8");
3392
+ }
3393
+ async function getUpdatedAt(configPath, exists) {
3394
+ if (!exists) {
3395
+ return null;
3396
+ }
3397
+ try {
3398
+ const stats = await (0, import_promises2.stat)(configPath);
3399
+ return stats.mtimeMs;
3400
+ } catch (error) {
3401
+ const nodeError = error;
3402
+ if (nodeError.code === "ENOENT") {
3403
+ return null;
3404
+ }
3405
+ throw error;
3406
+ }
3407
+ }
3408
+ async function buildSnapshot(content, exists) {
3409
+ const configPath = getConfigPath();
3410
+ const parsed = tryParseToml(content);
3411
+ const summary = buildSettingsSummary(parsed.data);
3412
+ const updatedAt = await getUpdatedAt(configPath, exists);
3413
+ const mcpServers = buildMcpServersList(parsed.data);
3414
+ const rmcpClientEnabled = parseRmcpClientEnabled(parsed.data);
3415
+ const metadata = await getCodexConfigMetadata();
3416
+ const diagnostics = parsed.error ? [] : buildConfigDiagnostics(parsed.data, summary, mcpServers, metadata);
3417
+ return {
3418
+ path: configPath,
3419
+ exists,
3420
+ content,
3421
+ settings: summary,
3422
+ mcpServers,
3423
+ rmcpClientEnabled,
3424
+ parseError: parsed.error,
3425
+ isYoloPreset: parsed.error ? false : isYoloApplied(summary),
3426
+ updatedAt,
3427
+ metadata,
3428
+ diagnostics
3429
+ };
3430
+ }
3431
+ async function getCodexConfigSnapshot() {
3432
+ const { content, exists } = await readConfigContent();
3433
+ return buildSnapshot(content, exists);
3434
+ }
3435
+ async function saveCodexConfigContent(content) {
3436
+ const normalized = ensureTrailingNewline(normalizeLineEndings(content));
3437
+ const parsed = tryParseToml(normalized);
3438
+ if (!parsed.data) {
3439
+ throw new Error(parsed.error ?? "config.toml contains invalid TOML syntax.");
3440
+ }
3441
+ await writeConfigContent(normalized);
3442
+ return buildSnapshot(normalized, true);
3443
+ }
3444
+
3445
+ // ../../lib/cloud-sync-service.ts
3446
+ var SYNC_BACKUP_DIR = "sync-backups";
3447
+ var PRE_PULL_BACKUP_PREFIX = "pre-pull-";
3448
+ var PRE_PULL_BACKUP_SUFFIX = ".json";
3449
+ var PRE_PULL_BACKUP_KEEP_COUNT = 3;
3450
+ var SYNC_SIZE_WARN_BYTES = 1 * 1024 * 1024;
3451
+ var SYNC_SIZE_MAX_BYTES = 5 * 1024 * 1024;
3452
+ var MB_DIVISOR = 1024 * 1024;
3453
+ function mapProfilesFromAppState(raw) {
3454
+ if (!isRecord3(raw)) {
3455
+ return [];
3456
+ }
3457
+ const profiles = [];
3458
+ for (const [name, value] of Object.entries(raw)) {
3459
+ if (!isRecord3(value)) {
3460
+ continue;
3461
+ }
3462
+ const data = isRecord3(value.data) ? value.data : null;
3463
+ if (!data) {
3464
+ continue;
3465
+ }
3466
+ const normalizedName = typeof name === "string" ? name.trim() : "";
3467
+ if (!normalizedName) {
3468
+ continue;
3469
+ }
3470
+ profiles.push({
3471
+ name: normalizedName,
3472
+ displayName: typeof value.displayName === "string" ? value.displayName : null,
3473
+ data,
3474
+ metadata: isRecord3(value.metadata) ? value.metadata : void 0,
3475
+ accountId: typeof value.accountId === "string" ? value.accountId : null,
3476
+ workspaceId: typeof value.workspaceId === "string" ? value.workspaceId : null,
3477
+ workspaceName: typeof value.workspaceName === "string" ? value.workspaceName : null,
3478
+ email: typeof value.email === "string" ? value.email : null,
3479
+ authMethod: typeof value.authMethod === "string" ? value.authMethod : null,
3480
+ createdAt: typeof value.createdAt === "string" ? value.createdAt : null,
3481
+ updatedAt: typeof value.updatedAt === "string" ? value.updatedAt : null
3482
+ });
3483
+ }
3484
+ return profiles;
3485
+ }
3486
+ function summarizeSnapshot(snapshot) {
3487
+ const configBytes = typeof snapshot.configTomlContent === "string" ? snapshot.configTomlContent.length : 0;
3488
+ const settingsKeys = isRecord3(snapshot.settingsJson) ? Object.keys(snapshot.settingsJson).length : 0;
3489
+ return {
3490
+ profiles: snapshot.profiles.length,
3491
+ configBytes,
3492
+ settingsKeys
3493
+ };
3494
+ }
3495
+ async function readState() {
3496
+ const state = await getAppState();
3497
+ return {
3498
+ lastPushAt: state.sync.lastPushAt ?? void 0,
3499
+ lastPullAt: state.sync.lastPullAt ?? void 0,
3500
+ lastError: state.sync.lastError ?? void 0,
3501
+ remoteUpdatedAt: state.sync.remoteUpdatedAt ?? void 0
3502
+ };
3503
+ }
3504
+ async function writeState(patch) {
3505
+ await patchAppState({
3506
+ sync: {
3507
+ ...typeof patch.lastPushAt === "string" ? { lastPushAt: patch.lastPushAt } : {},
3508
+ ...typeof patch.lastPullAt === "string" ? { lastPullAt: patch.lastPullAt } : {},
3509
+ ...typeof patch.lastError === "string" ? { lastError: patch.lastError } : patch.lastError === void 0 ? { lastError: null } : {},
3510
+ ...typeof patch.remoteUpdatedAt === "string" ? { remoteUpdatedAt: patch.remoteUpdatedAt } : {}
3511
+ }
3512
+ });
3513
+ }
3514
+ async function resolveEligibility() {
3515
+ const stored = await getStoredLicense();
3516
+ const licenseKey = typeof stored?.licenseKey === "string" ? stored.licenseKey.trim() : "";
3517
+ if (!licenseKey) {
3518
+ return {
3519
+ canSync: false,
3520
+ reason: "License key is required for cloud sync.",
3521
+ licenseKey: null,
3522
+ licenseState: null
3523
+ };
3524
+ }
3525
+ const status = await licenseService.getStatus();
3526
+ const active = status.state === "active" || status.state === "grace";
3527
+ if (!active) {
3528
+ return {
3529
+ canSync: false,
3530
+ reason: "Cloud sync requires an active Pro license.",
3531
+ licenseKey,
3532
+ licenseState: status.state
3533
+ };
3534
+ }
3535
+ return {
3536
+ canSync: true,
3537
+ reason: null,
3538
+ licenseKey,
3539
+ licenseState: status.state
3540
+ };
3541
+ }
3542
+ function formatMegabytes(bytes) {
3543
+ return (bytes / MB_DIVISOR).toFixed(2);
3544
+ }
3545
+ function resolveSyncBackupsDir() {
3546
+ return import_node_path6.default.join(getUserDataDir(), SYNC_BACKUP_DIR);
3547
+ }
3548
+ function toSafeIsoForFileName(iso) {
3549
+ return iso.replace(/:/g, "-");
3550
+ }
3551
+ async function pruneOldPrePullBackups(backupsDir) {
3552
+ const entries = await import_node_fs4.promises.readdir(backupsDir, { withFileTypes: true });
3553
+ const files = entries.filter(
3554
+ (entry) => entry.isFile() && entry.name.startsWith(PRE_PULL_BACKUP_PREFIX) && entry.name.endsWith(PRE_PULL_BACKUP_SUFFIX)
3555
+ ).map((entry) => entry.name).sort((a, b) => b.localeCompare(a));
3556
+ if (files.length <= PRE_PULL_BACKUP_KEEP_COUNT) {
3557
+ return;
3558
+ }
3559
+ for (const stale of files.slice(PRE_PULL_BACKUP_KEEP_COUNT)) {
3560
+ await import_node_fs4.promises.rm(import_node_path6.default.join(backupsDir, stale), { force: true });
3561
+ }
3562
+ }
3563
+ async function createPrePullBackup(profileManager) {
3564
+ const snapshot = await buildLocalSnapshot(profileManager, {
3565
+ enforcePushGuards: false
3566
+ });
3567
+ const backupsDir = resolveSyncBackupsDir();
3568
+ await import_node_fs4.promises.mkdir(backupsDir, { recursive: true });
3569
+ const timestamp = toSafeIsoForFileName((/* @__PURE__ */ new Date()).toISOString());
3570
+ const backupPath = import_node_path6.default.join(
3571
+ backupsDir,
3572
+ `${PRE_PULL_BACKUP_PREFIX}${timestamp}${PRE_PULL_BACKUP_SUFFIX}`
3573
+ );
3574
+ await import_node_fs4.promises.writeFile(backupPath, `${JSON.stringify(snapshot, null, 2)}
3575
+ `, "utf8");
3576
+ await pruneOldPrePullBackups(backupsDir);
3577
+ logInfo("[cloud-sync] created pre-pull backup", { backupPath });
3578
+ return backupPath;
3579
+ }
3580
+ async function buildLocalSnapshot(profileManager, options = {}) {
3581
+ const enforcePushGuards = options.enforcePushGuards !== false;
3582
+ const profilesFromManager = await profileManager.exportProfilesForCloudSync();
3583
+ const state = await getAppState();
3584
+ const profilesFromState = mapProfilesFromAppState(state.profilesByName);
3585
+ const profiles = profilesFromManager.length > 0 ? profilesFromManager : profilesFromState;
3586
+ const config = await getCodexConfigSnapshot();
3587
+ const settingsJson = await readCodexSettingsJsonRaw();
3588
+ const hasProfiles = profiles.length > 0;
3589
+ const hasConfig = typeof config.content === "string" && config.content.trim().length > 0;
3590
+ const hasSettings = isRecord3(settingsJson) && Object.keys(settingsJson).length > 0;
3591
+ if (enforcePushGuards && !hasProfiles && !hasConfig && !hasSettings) {
3592
+ throw new Error("Refusing to push an empty cloud sync snapshot.");
3593
+ }
3594
+ const snapshot = {
3595
+ schemaVersion: CLOUD_SYNC_SCHEMA_VERSION,
3596
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
3597
+ profiles,
3598
+ configTomlContent: config.exists ? config.content : null,
3599
+ settingsJson
3600
+ };
3601
+ if (!enforcePushGuards) {
3602
+ return snapshot;
3603
+ }
3604
+ return snapshot;
3605
+ }
3606
+ async function applyRemoteSnapshot(profileManager, snapshot) {
3607
+ await profileManager.replaceProfilesFromCloudSync(snapshot.profiles ?? []);
3608
+ if (typeof snapshot.configTomlContent === "string") {
3609
+ await saveCodexConfigContent(snapshot.configTomlContent);
3610
+ }
3611
+ if (snapshot.settingsJson && isRecord3(snapshot.settingsJson)) {
3612
+ await writeCodexSettingsJsonRaw(snapshot.settingsJson);
3613
+ } else {
3614
+ await writeCodexSettingsJsonRaw({});
3615
+ }
3616
+ }
3617
+ function toRunError(mode, error) {
3618
+ const message = formatUserFacingError(error, {
3619
+ fallback: `Cloud sync ${mode} failed.`
3620
+ });
3621
+ return {
3622
+ mode,
3623
+ status: "error",
3624
+ message,
3625
+ localUpdatedAt: null,
3626
+ remoteUpdatedAt: null
3627
+ };
3628
+ }
3629
+ async function getCloudSyncStatus() {
3630
+ const eligibility = await resolveEligibility();
3631
+ const state = await readState();
3632
+ let remoteUpdatedAt = state.remoteUpdatedAt ?? null;
3633
+ if (eligibility.canSync && eligibility.licenseKey) {
3066
3634
  try {
3067
- return await this.verificationPromise;
3068
- } finally {
3069
- this.verificationPromise = null;
3635
+ remoteUpdatedAt = await fetchRemoteSnapshotMeta(eligibility.licenseKey);
3636
+ } catch (error) {
3637
+ if (error instanceof CloudSyncClientError && error.status === 404) {
3638
+ try {
3639
+ const remote = await fetchRemoteSnapshot(eligibility.licenseKey);
3640
+ remoteUpdatedAt = remote?.updatedAt ?? null;
3641
+ } catch {
3642
+ }
3643
+ }
3070
3644
  }
3071
3645
  }
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
3646
+ return {
3647
+ endpoint: getCloudSyncApiBaseUrl(),
3648
+ canSync: eligibility.canSync,
3649
+ reason: eligibility.reason,
3650
+ licenseState: eligibility.licenseState,
3651
+ hasLicenseKey: Boolean(eligibility.licenseKey),
3652
+ lastPushAt: state.lastPushAt ?? null,
3653
+ lastPullAt: state.lastPullAt ?? null,
3654
+ lastError: state.lastError ?? null,
3655
+ remoteUpdatedAt
3656
+ };
3657
+ }
3658
+ async function pushCloudSync() {
3659
+ const eligibility = await resolveEligibility();
3660
+ if (!eligibility.canSync || !eligibility.licenseKey) {
3661
+ return {
3662
+ mode: "push",
3663
+ status: "skipped",
3664
+ message: eligibility.reason ?? "Cloud sync unavailable.",
3665
+ localUpdatedAt: null,
3666
+ remoteUpdatedAt: null
3089
3667
  };
3090
- const signed = withSignature(stored, secret);
3091
- await persistLicense(signed);
3092
- const status = toLicenseStatus(signed, {
3093
- message: `License verified (${digest.slice(0, 8)}).`
3668
+ }
3669
+ const profileManager = new ProfileManager();
3670
+ try {
3671
+ await profileManager.initialize();
3672
+ const localSnapshot = await buildLocalSnapshot(profileManager, { enforcePushGuards: true });
3673
+ const serializedSnapshot = JSON.stringify(localSnapshot);
3674
+ const snapshotBytes = Buffer.byteLength(serializedSnapshot, "utf8");
3675
+ if (snapshotBytes > SYNC_SIZE_WARN_BYTES) {
3676
+ logWarn("[cloud-sync] push snapshot is large", {
3677
+ bytes: snapshotBytes,
3678
+ megabytes: formatMegabytes(snapshotBytes)
3679
+ });
3680
+ }
3681
+ if (snapshotBytes > SYNC_SIZE_MAX_BYTES) {
3682
+ throw new Error(
3683
+ `Snapshot too large to sync (${formatMegabytes(snapshotBytes)} MB). Reduce profiles or config size.`
3684
+ );
3685
+ }
3686
+ if (process.env.NODE_ENV !== "production") {
3687
+ logInfo("[cloud-sync] push snapshot summary", summarizeSnapshot(localSnapshot));
3688
+ }
3689
+ const remote = await pushRemoteSnapshot(eligibility.licenseKey, localSnapshot, {
3690
+ serializedSnapshot
3094
3691
  });
3095
- this.cache = status;
3096
- return status;
3692
+ await writeState({
3693
+ lastPushAt: (/* @__PURE__ */ new Date()).toISOString(),
3694
+ remoteUpdatedAt: remote.snapshot.updatedAt,
3695
+ lastError: void 0
3696
+ });
3697
+ if (remote.status === "stale") {
3698
+ return {
3699
+ mode: "push",
3700
+ status: "stale",
3701
+ message: "Remote cloud snapshot is newer. Pull first.",
3702
+ localUpdatedAt: localSnapshot.updatedAt,
3703
+ remoteUpdatedAt: remote.snapshot.updatedAt
3704
+ };
3705
+ }
3706
+ return {
3707
+ mode: "push",
3708
+ status: "applied",
3709
+ message: "Cloud sync push completed.",
3710
+ localUpdatedAt: localSnapshot.updatedAt,
3711
+ remoteUpdatedAt: remote.snapshot.updatedAt
3712
+ };
3713
+ } catch (error) {
3714
+ const result = toRunError("push", error);
3715
+ const lastError = error instanceof CloudSyncClientError ? error.message : result.message;
3716
+ await writeState({ lastError });
3717
+ return result;
3097
3718
  }
3098
- applyProfileCount(status, profileCount) {
3099
- const profilesRemaining = resolveProfilesRemaining(status.profileLimit, profileCount);
3719
+ }
3720
+ async function pullCloudSync() {
3721
+ const eligibility = await resolveEligibility();
3722
+ if (!eligibility.canSync || !eligibility.licenseKey) {
3100
3723
  return {
3101
- ...status,
3102
- profilesRemaining
3724
+ mode: "pull",
3725
+ status: "skipped",
3726
+ message: eligibility.reason ?? "Cloud sync unavailable.",
3727
+ localUpdatedAt: null,
3728
+ remoteUpdatedAt: null
3103
3729
  };
3104
3730
  }
3105
- refreshStatusInBackground(options = {}) {
3106
- if (this.refreshPromise) {
3107
- return;
3731
+ const profileManager = new ProfileManager();
3732
+ try {
3733
+ await profileManager.initialize();
3734
+ const remote = await fetchRemoteSnapshot(eligibility.licenseKey);
3735
+ if (!remote) {
3736
+ return {
3737
+ mode: "pull",
3738
+ status: "skipped",
3739
+ message: "No cloud snapshot found.",
3740
+ localUpdatedAt: null,
3741
+ remoteUpdatedAt: null
3742
+ };
3108
3743
  }
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
- })();
3744
+ let backupWarningSuffix = "";
3745
+ try {
3746
+ await createPrePullBackup(profileManager);
3747
+ } catch (backupError) {
3748
+ logWarn("[cloud-sync] pre-pull backup failed; continuing pull", backupError);
3749
+ const reason = formatUserFacingError(backupError, { fallback: "unknown error" });
3750
+ backupWarningSuffix = ` Warning: pre-pull backup failed (${reason}).`;
3751
+ }
3752
+ await applyRemoteSnapshot(profileManager, remote);
3753
+ await writeState({
3754
+ lastPullAt: (/* @__PURE__ */ new Date()).toISOString(),
3755
+ remoteUpdatedAt: remote.updatedAt,
3756
+ lastError: void 0
3757
+ });
3758
+ return {
3759
+ mode: "pull",
3760
+ status: "applied",
3761
+ message: `Cloud sync pull completed.${backupWarningSuffix}`,
3762
+ localUpdatedAt: remote.updatedAt,
3763
+ remoteUpdatedAt: remote.updatedAt
3764
+ };
3765
+ } catch (error) {
3766
+ const result = toRunError("pull", error);
3767
+ const lastError = error instanceof CloudSyncClientError ? error.message : result.message;
3768
+ await writeState({ lastError });
3769
+ return result;
3119
3770
  }
3120
- };
3121
- var licenseService = new LicenseService();
3771
+ }
3122
3772
 
3123
3773
  // ../../lib/license-guard.ts
3124
3774
  async function assertProfileCreationAllowed(profileManager) {
@@ -3133,20 +3783,20 @@ async function assertProfileCreationAllowed(profileManager) {
3133
3783
  }
3134
3784
 
3135
3785
  // 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"));
3786
+ var import_node_child_process2 = require("child_process");
3787
+ var import_node_fs5 = require("fs");
3788
+ var import_node_path7 = __toESM(require("path"));
3139
3789
  var ENV_HINTS = ["CODEX_BINARY", "CODEX_CLI_PATH", "CODEX_PATH"];
3140
3790
  var NODE_MODULE_CANDIDATES = [
3141
3791
  ["@openai", "codex"],
3142
3792
  ["@openai", "codex-alpha"]
3143
3793
  ];
3144
- function fileExists(candidate) {
3794
+ function fileExists2(candidate) {
3145
3795
  if (!candidate) return null;
3146
- const resolved = import_node_path4.default.resolve(candidate);
3796
+ const resolved = import_node_path7.default.resolve(candidate);
3147
3797
  try {
3148
- const stat = (0, import_node_fs4.statSync)(resolved);
3149
- if (stat.isFile()) return resolved;
3798
+ const stat2 = (0, import_node_fs5.statSync)(resolved);
3799
+ if (stat2.isFile()) return resolved;
3150
3800
  } catch {
3151
3801
  return null;
3152
3802
  }
@@ -3156,49 +3806,49 @@ function resolveFromEnv() {
3156
3806
  for (const key of ENV_HINTS) {
3157
3807
  const value = process.env[key];
3158
3808
  if (!value) continue;
3159
- const resolved = fileExists(value);
3809
+ const resolved = fileExists2(value);
3160
3810
  if (resolved) return resolved;
3161
3811
  }
3162
3812
  return null;
3163
3813
  }
3164
3814
  function resolveFromAppResources() {
3165
3815
  const roots = [
3166
- import_node_path4.default.resolve(__dirname, ".."),
3167
- import_node_path4.default.resolve(__dirname, "..", "..")
3816
+ import_node_path7.default.resolve(__dirname, ".."),
3817
+ import_node_path7.default.resolve(__dirname, "..", "..")
3168
3818
  ];
3169
3819
  for (const root of roots) {
3170
- const base = import_node_path4.default.join(root, "app.asar.unpacked", "node_modules");
3820
+ const base = import_node_path7.default.join(root, "app.asar.unpacked", "node_modules");
3171
3821
  for (const segments of NODE_MODULE_CANDIDATES) {
3172
- const candidate = fileExists(import_node_path4.default.join(base, ...segments, "bin", "codex"));
3822
+ const candidate = fileExists2(import_node_path7.default.join(base, ...segments, "bin", "codex"));
3173
3823
  if (candidate) return candidate;
3174
- const jsCandidate = fileExists(import_node_path4.default.join(base, ...segments, "bin", "codex.js"));
3824
+ const jsCandidate = fileExists2(import_node_path7.default.join(base, ...segments, "bin", "codex.js"));
3175
3825
  if (jsCandidate) return jsCandidate;
3176
3826
  }
3177
3827
  }
3178
3828
  return null;
3179
3829
  }
3180
3830
  function resolveFromNodeModules() {
3181
- let current = import_node_path4.default.resolve(__dirname, "..");
3831
+ let current = import_node_path7.default.resolve(__dirname, "..");
3182
3832
  let last = "";
3183
3833
  while (current !== last) {
3184
3834
  for (const segments of NODE_MODULE_CANDIDATES) {
3185
- const candidate = fileExists(import_node_path4.default.join(current, "node_modules", ...segments, "bin", "codex"));
3835
+ const candidate = fileExists2(import_node_path7.default.join(current, "node_modules", ...segments, "bin", "codex"));
3186
3836
  if (candidate) return candidate;
3187
- const jsCandidate = fileExists(import_node_path4.default.join(current, "node_modules", ...segments, "bin", "codex.js"));
3837
+ const jsCandidate = fileExists2(import_node_path7.default.join(current, "node_modules", ...segments, "bin", "codex.js"));
3188
3838
  if (jsCandidate) return jsCandidate;
3189
3839
  }
3190
3840
  last = current;
3191
- current = import_node_path4.default.dirname(current);
3841
+ current = import_node_path7.default.dirname(current);
3192
3842
  }
3193
3843
  return null;
3194
3844
  }
3195
3845
  function resolveFromPath() {
3196
3846
  const pathValue = process.env.PATH ?? "";
3197
- const entries = pathValue.split(import_node_path4.default.delimiter).filter(Boolean);
3847
+ const entries = pathValue.split(import_node_path7.default.delimiter).filter(Boolean);
3198
3848
  const names = process.platform === "win32" ? ["codex.exe", "codex.cmd", "codex.bat", "codex"] : ["codex"];
3199
3849
  for (const entry of entries) {
3200
3850
  for (const name of names) {
3201
- const candidate = fileExists(import_node_path4.default.join(entry, name));
3851
+ const candidate = fileExists2(import_node_path7.default.join(entry, name));
3202
3852
  if (candidate) return candidate;
3203
3853
  }
3204
3854
  }
@@ -3247,7 +3897,7 @@ async function runCodexLogin(mode) {
3247
3897
  const resolvedMode = resolveLoginMode(mode ?? null);
3248
3898
  const loginArgs = resolvedMode === "device" ? ["login", "--device-auth"] : ["login"];
3249
3899
  const { command, args, shell } = buildCodexCommand(codexPath, loginArgs);
3250
- const child = (0, import_node_child_process.spawn)(command, args, {
3900
+ const child = (0, import_node_child_process2.spawn)(command, args, {
3251
3901
  stdio: "inherit",
3252
3902
  env: process.env,
3253
3903
  shell
@@ -3261,120 +3911,12 @@ async function runCodexLogin(mode) {
3261
3911
  }
3262
3912
 
3263
3913
  // ../../lib/codex-rpc.ts
3264
- var import_node_child_process2 = require("child_process");
3914
+ var import_node_child_process3 = require("child_process");
3265
3915
  var import_node_readline = __toESM(require("readline"));
3266
3916
  var import_node_events = require("events");
3267
3917
  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
3918
+ var import_node_os2 = __toESM(require("os"));
3919
+ var import_node_path8 = __toESM(require("path"));
3378
3920
  var RPC_TIMEOUT_MS = 1e4;
3379
3921
  async function sendPayload(child, payload) {
3380
3922
  child.stdin?.write(JSON.stringify(payload));
@@ -3426,9 +3968,9 @@ function toWindow(rpc) {
3426
3968
  }
3427
3969
  async function fetchRateLimitsViaRpc(envOverride, options = {}) {
3428
3970
  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(),
3971
+ const tempHome = await import_node_fs6.promises.mkdtemp(import_node_path8.default.join(import_node_os2.default.tmpdir(), "codex-rpc-"));
3972
+ const sourceAuthPath = options.authPath ?? (envOverride?.CODEX_HOME ? import_node_path8.default.join(envOverride.CODEX_HOME, "auth.json") : import_node_path8.default.join(
3973
+ envOverride?.HOME ?? process.env.HOME ?? process.env.USERPROFILE ?? import_node_os2.default.homedir(),
3432
3974
  ".codex",
3433
3975
  "auth.json"
3434
3976
  ));
@@ -3437,13 +3979,13 @@ async function fetchRateLimitsViaRpc(envOverride, options = {}) {
3437
3979
  if (!authContent) {
3438
3980
  return null;
3439
3981
  }
3440
- await import_node_fs6.promises.writeFile(import_node_path6.default.join(tempHome, "auth.json"), authContent, "utf8");
3982
+ await import_node_fs6.promises.writeFile(import_node_path8.default.join(tempHome, "auth.json"), authContent, "utf8");
3441
3983
  } catch {
3442
3984
  await import_node_fs6.promises.rm(tempHome, { recursive: true, force: true }).catch(() => {
3443
3985
  });
3444
3986
  return null;
3445
3987
  }
3446
- const child = (0, import_node_child_process2.spawn)(process.execPath, [binaryPath, "-s", "read-only", "-a", "untrusted", "app-server"], {
3988
+ const child = (0, import_node_child_process3.spawn)(process.execPath, [binaryPath, "-s", "read-only", "-a", "untrusted", "app-server"], {
3447
3989
  stdio: ["pipe", "pipe", "pipe"],
3448
3990
  env: {
3449
3991
  ...process.env,
@@ -3515,7 +4057,7 @@ function maxUsedPercent(snapshot) {
3515
4057
  }
3516
4058
 
3517
4059
  // src/index.ts
3518
- var VERSION = true ? "2.3.0" : "0.0.0";
4060
+ var VERSION = true ? "2.4.0" : "0.0.0";
3519
4061
  function printHelp() {
3520
4062
  console.log(`CodexUse CLI v${VERSION}
3521
4063
 
@@ -3531,6 +4073,10 @@ Usage:
3531
4073
  codexuse license status [--refresh]
3532
4074
  codexuse license activate <license-key>
3533
4075
 
4076
+ codexuse sync status
4077
+ codexuse sync pull
4078
+ codexuse sync push
4079
+
3534
4080
  Flags:
3535
4081
  -h, --help Show help
3536
4082
  -v, --version Show version
@@ -3952,6 +4498,70 @@ async function handleLicense(args) {
3952
4498
  return;
3953
4499
  }
3954
4500
  }
4501
+ function printSyncResult(result) {
4502
+ console.log(result.message);
4503
+ if (result.localUpdatedAt) {
4504
+ console.log(`Local snapshot: ${result.localUpdatedAt}`);
4505
+ }
4506
+ if (result.remoteUpdatedAt) {
4507
+ console.log(`Remote snapshot: ${result.remoteUpdatedAt}`);
4508
+ }
4509
+ }
4510
+ async function handleSync(args) {
4511
+ const flags = args.filter((arg) => arg.startsWith("-"));
4512
+ const params = stripFlags(args);
4513
+ const sub = params[0];
4514
+ if (!sub || hasFlag(flags, "--help") || hasFlag(flags, "-h")) {
4515
+ printHelp();
4516
+ return;
4517
+ }
4518
+ switch (sub) {
4519
+ case "status": {
4520
+ const status = await getCloudSyncStatus();
4521
+ console.log(`Endpoint: ${status.endpoint}`);
4522
+ console.log(`Can sync: ${status.canSync ? "yes" : "no"}`);
4523
+ console.log(`Has key: ${status.hasLicenseKey ? "yes" : "no"}`);
4524
+ if (status.licenseState) {
4525
+ console.log(`License state: ${status.licenseState}`);
4526
+ }
4527
+ if (status.reason) {
4528
+ console.log(`Reason: ${status.reason}`);
4529
+ }
4530
+ if (status.remoteUpdatedAt) {
4531
+ console.log(`Remote snapshot: ${status.remoteUpdatedAt}`);
4532
+ }
4533
+ if (status.lastPushAt) {
4534
+ console.log(`Last push: ${status.lastPushAt}`);
4535
+ }
4536
+ if (status.lastPullAt) {
4537
+ console.log(`Last pull: ${status.lastPullAt}`);
4538
+ }
4539
+ if (status.lastError) {
4540
+ console.log(`Last error: ${status.lastError}`);
4541
+ }
4542
+ return;
4543
+ }
4544
+ case "pull": {
4545
+ const result = await pullCloudSync();
4546
+ printSyncResult(result);
4547
+ if (result.status === "error") {
4548
+ process.exitCode = 1;
4549
+ }
4550
+ return;
4551
+ }
4552
+ case "push": {
4553
+ const result = await pushCloudSync();
4554
+ printSyncResult(result);
4555
+ if (result.status === "error") {
4556
+ process.exitCode = 1;
4557
+ }
4558
+ return;
4559
+ }
4560
+ default:
4561
+ printHelp();
4562
+ return;
4563
+ }
4564
+ }
3955
4565
  async function main() {
3956
4566
  const args = process.argv.slice(2);
3957
4567
  if (args.length === 0 || hasFlag(args, "--help") || hasFlag(args, "-h")) {
@@ -3971,6 +4581,9 @@ async function main() {
3971
4581
  case "license":
3972
4582
  await handleLicense(rest);
3973
4583
  return;
4584
+ case "sync":
4585
+ await handleSync(rest);
4586
+ return;
3974
4587
  default:
3975
4588
  printHelp();
3976
4589
  return;