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/README.md +5 -1
- package/dist/index.js +2130 -1517
- package/dist/index.js.map +1 -1
- package/package.json +2 -3
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/
|
|
61
|
-
var
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
77
|
-
|
|
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
|
|
80
|
-
|
|
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
|
|
91
|
-
|
|
92
|
-
|
|
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 (
|
|
95
|
-
|
|
96
|
-
return;
|
|
91
|
+
if (process.platform === "darwin") {
|
|
92
|
+
return import_node_path.default.join(home, "Library", "Application Support", APP_NAME);
|
|
97
93
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
|
101
|
+
return import_node_path.default.join(home, ".config", APP_NAME);
|
|
106
102
|
}
|
|
107
|
-
function
|
|
108
|
-
return
|
|
103
|
+
function getUserDataDir() {
|
|
104
|
+
return configuredUserDataDir ?? resolveDefaultUserDataDir();
|
|
109
105
|
}
|
|
110
|
-
function
|
|
111
|
-
return import_node_path.default.join(
|
|
106
|
+
function resolveAppStatePath() {
|
|
107
|
+
return import_node_path.default.join(getUserDataDir(), APP_STATE_FILE);
|
|
112
108
|
}
|
|
113
|
-
function
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
}
|
|
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
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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 (
|
|
305
|
-
|
|
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 (
|
|
373
|
-
|
|
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 (
|
|
453
|
-
|
|
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 (
|
|
497
|
-
|
|
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
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
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 (
|
|
507
|
-
|
|
508
|
-
database.exec(`PRAGMA user_version = 10;`);
|
|
509
|
-
userVersion = 10;
|
|
217
|
+
if (!Array.isArray(merged.providers.list)) {
|
|
218
|
+
merged.providers.list = [];
|
|
510
219
|
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
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
|
-
|
|
520
|
-
|
|
521
|
-
} catch {
|
|
225
|
+
if (!isRecord(merged.providers.overridesByPath)) {
|
|
226
|
+
merged.providers.overridesByPath = {};
|
|
522
227
|
}
|
|
523
|
-
|
|
524
|
-
|
|
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
|
-
|
|
541
|
-
|
|
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
|
-
|
|
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
|
-
|
|
557
|
-
|
|
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
|
-
|
|
727
|
-
}
|
|
728
|
-
function logError(...args) {
|
|
729
|
-
if (isTestEnv && !isMocked(console.error)) {
|
|
730
|
-
return;
|
|
243
|
+
if (!isRecord(merged.conversationCategoriesByCwd)) {
|
|
244
|
+
merged.conversationCategoriesByCwd = {};
|
|
731
245
|
}
|
|
732
|
-
|
|
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
|
-
|
|
745
|
-
|
|
746
|
-
return null;
|
|
249
|
+
if (!isRecord(merged.skills)) {
|
|
250
|
+
merged.skills = clone(defaults.skills);
|
|
747
251
|
}
|
|
748
|
-
|
|
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
|
|
751
|
-
|
|
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
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
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
|
|
776
|
-
}
|
|
777
|
-
|
|
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
|
|
781
|
-
const
|
|
782
|
-
await
|
|
783
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
796
|
-
}
|
|
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
|
|
800
|
-
if (!raw
|
|
401
|
+
function parseStoredLicense(raw) {
|
|
402
|
+
if (!isRecord2(raw)) {
|
|
801
403
|
return null;
|
|
802
404
|
}
|
|
803
|
-
const
|
|
804
|
-
const
|
|
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
|
-
|
|
818
|
-
|
|
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
|
|
821
|
-
license.licenseKey
|
|
416
|
+
const hasValue = Boolean(
|
|
417
|
+
license.licenseKey || license.purchaseEmail || license.lastVerifiedAt || license.nextCheckAt || license.lastVerificationError || license.status
|
|
822
418
|
);
|
|
823
|
-
return
|
|
419
|
+
return hasValue ? license : null;
|
|
824
420
|
}
|
|
825
|
-
function
|
|
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(
|
|
426
|
+
return normalizeAutoRollSettings(raw);
|
|
835
427
|
} catch {
|
|
836
428
|
return null;
|
|
837
429
|
}
|
|
838
430
|
}
|
|
839
|
-
function
|
|
840
|
-
|
|
841
|
-
|
|
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
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
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
|
-
|
|
870
|
-
await writeSettingsFile(normalized);
|
|
871
|
-
return normalized;
|
|
872
|
-
} catch {
|
|
873
|
-
return null;
|
|
874
|
-
}
|
|
440
|
+
});
|
|
875
441
|
}
|
|
876
|
-
function
|
|
877
|
-
const
|
|
878
|
-
|
|
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
|
|
897
|
-
const
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
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
|
-
|
|
926
|
-
return null;
|
|
927
|
-
}
|
|
466
|
+
});
|
|
928
467
|
}
|
|
929
|
-
async function
|
|
930
|
-
|
|
931
|
-
|
|
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
|
|
981
|
-
await
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
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
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
return null;
|
|
500
|
+
async function writeCodexSettingsJsonRaw(payload) {
|
|
501
|
+
if (!isRecord2(payload)) {
|
|
502
|
+
return;
|
|
1062
503
|
}
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
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
|
-
|
|
1078
|
-
|
|
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
|
-
|
|
563
|
+
console.warn(...args);
|
|
1081
564
|
}
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
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.
|
|
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
|
-
|
|
1166
|
-
return
|
|
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
|
-
|
|
1169
|
-
if (!
|
|
667
|
+
fromStateRecord(profileName, raw) {
|
|
668
|
+
if (!raw || typeof raw !== "object") {
|
|
1170
669
|
return null;
|
|
1171
670
|
}
|
|
1172
|
-
const dataRaw =
|
|
1173
|
-
|
|
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
|
|
1196
|
-
workspaceId: typeof
|
|
1197
|
-
workspaceName: typeof
|
|
1198
|
-
email: typeof
|
|
1199
|
-
authMethod: typeof
|
|
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
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
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
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
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
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
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.
|
|
705
|
+
return this.fromStateRecord(profileName, raw);
|
|
1252
706
|
}
|
|
1253
707
|
async listProfileRecords() {
|
|
708
|
+
const map = await this.readProfilesStateMap();
|
|
1254
709
|
const records = [];
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
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
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
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
|
|
1931
|
+
* Export profiles for cloud sync (includes token-bearing auth data).
|
|
2723
1932
|
*/
|
|
2724
|
-
async
|
|
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
|
-
|
|
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
|
-
|
|
2738
|
-
exportedAt
|
|
1947
|
+
updatedAt: record.updatedAt
|
|
2739
1948
|
}));
|
|
2740
1949
|
}
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
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
|
|
2748
|
-
var
|
|
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
|
|
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
|
|
2762
|
-
const secretPath = resolveSecretPath();
|
|
2035
|
+
async function readSecretIfExists(secretPath) {
|
|
2763
2036
|
try {
|
|
2764
|
-
const existing = await
|
|
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
|
-
|
|
2770
|
-
}
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2042
|
+
return null;
|
|
2043
|
+
} catch (error) {
|
|
2044
|
+
if (error.code === "ENOENT") {
|
|
2045
|
+
return null;
|
|
2046
|
+
}
|
|
2047
|
+
throw error;
|
|
2775
2048
|
}
|
|
2776
|
-
|
|
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
|
-
|
|
3068
|
-
}
|
|
3069
|
-
|
|
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
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
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
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
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
|
-
|
|
3096
|
-
|
|
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
|
-
|
|
3099
|
-
|
|
3719
|
+
}
|
|
3720
|
+
async function pullCloudSync() {
|
|
3721
|
+
const eligibility = await resolveEligibility();
|
|
3722
|
+
if (!eligibility.canSync || !eligibility.licenseKey) {
|
|
3100
3723
|
return {
|
|
3101
|
-
|
|
3102
|
-
|
|
3724
|
+
mode: "pull",
|
|
3725
|
+
status: "skipped",
|
|
3726
|
+
message: eligibility.reason ?? "Cloud sync unavailable.",
|
|
3727
|
+
localUpdatedAt: null,
|
|
3728
|
+
remoteUpdatedAt: null
|
|
3103
3729
|
};
|
|
3104
3730
|
}
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
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
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
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
|
|
3137
|
-
var
|
|
3138
|
-
var
|
|
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
|
|
3794
|
+
function fileExists2(candidate) {
|
|
3145
3795
|
if (!candidate) return null;
|
|
3146
|
-
const resolved =
|
|
3796
|
+
const resolved = import_node_path7.default.resolve(candidate);
|
|
3147
3797
|
try {
|
|
3148
|
-
const
|
|
3149
|
-
if (
|
|
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 =
|
|
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
|
-
|
|
3167
|
-
|
|
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 =
|
|
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 =
|
|
3822
|
+
const candidate = fileExists2(import_node_path7.default.join(base, ...segments, "bin", "codex"));
|
|
3173
3823
|
if (candidate) return candidate;
|
|
3174
|
-
const jsCandidate =
|
|
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 =
|
|
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 =
|
|
3835
|
+
const candidate = fileExists2(import_node_path7.default.join(current, "node_modules", ...segments, "bin", "codex"));
|
|
3186
3836
|
if (candidate) return candidate;
|
|
3187
|
-
const jsCandidate =
|
|
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 =
|
|
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(
|
|
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 =
|
|
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,
|
|
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
|
|
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
|
|
3269
|
-
var
|
|
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(
|
|
3430
|
-
const sourceAuthPath = options.authPath ?? (envOverride?.CODEX_HOME ?
|
|
3431
|
-
envOverride?.HOME ?? process.env.HOME ?? process.env.USERPROFILE ??
|
|
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(
|
|
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,
|
|
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.
|
|
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;
|