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