@4yi-dev/cli 0.1.15 → 0.1.16
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 +1 -1
- package/package.json +1 -1
- package/src/connect.mjs +42 -18
package/README.md
CHANGED
|
@@ -39,7 +39,7 @@ On macOS, `4yi connect claude` also configures an installed Claude Desktop throu
|
|
|
39
39
|
|
|
40
40
|
`--scope project` changes only the project's Claude Code settings and never changes the global Claude Desktop profile.
|
|
41
41
|
|
|
42
|
-
Codex CLI and Codex App share the same Codex home on native Windows (`%USERPROFILE%\\.codex`). After `4yi connect codex`, finish active App tasks, fully quit and reopen the App, and start a new chat so its embedded Codex process loads the new provider. Microsoft Store/AppX installations may not expose a conventional executable path, but that detection does not gate the shared configuration update.
|
|
42
|
+
Codex CLI and Codex App share the same Codex home on native Windows (`%USERPROFILE%\\.codex`). After `4yi connect codex`, finish active App tasks, fully quit and reopen the App, and start a new chat so its embedded Codex process loads the new provider. Microsoft Store/AppX installations may not expose a conventional executable path, but that detection does not gate the shared configuration update. While the external 4YI provider supplies authentication, Codex App may hide its `Log out` action because there is no App-managed provider credential to clear. To switch back to OpenAI, run `4yi restore codex`, fully quit and reopen the App, and start a new chat.
|
|
43
43
|
|
|
44
44
|
Existing Codex tasks keep the provider they were created with. Run `4yi migrate codex`, choose a recent task by number, and 4YI creates a new task with the same history and project using your Plan's default model. The original task is not modified. Scripts can use `--thread <id>` and optionally `--model <id>`.
|
|
45
45
|
|
package/package.json
CHANGED
package/src/connect.mjs
CHANGED
|
@@ -216,9 +216,9 @@ function reportDesktopApp(target, {
|
|
|
216
216
|
const detected = candidates.some((candidate) => exists(candidate));
|
|
217
217
|
if (target === "codex" && platform === "win32") {
|
|
218
218
|
if (detected) {
|
|
219
|
-
stdout("Codex App detected. 4YI updated the shared Windows Codex home. Finish active tasks, fully quit and reopen the App, then start a new chat to load the new provider.");
|
|
219
|
+
stdout("Codex App detected. 4YI updated the shared Windows Codex home. Finish active tasks, fully quit and reopen the App, then start a new chat to load the new provider. The App may hide Log out while this external provider supplies authentication; use `4yi restore codex`, then reopen the App, to return to OpenAI.");
|
|
220
220
|
} else {
|
|
221
|
-
stdout("4YI updated the shared Windows Codex home. A Microsoft Store/AppX install may not expose a standard executable path; finish active tasks, fully quit and reopen any running Codex App, then start a new chat.");
|
|
221
|
+
stdout("4YI updated the shared Windows Codex home. A Microsoft Store/AppX install may not expose a standard executable path; finish active tasks, fully quit and reopen any running Codex App, then start a new chat. The App may hide Log out while this external provider supplies authentication; use `4yi restore codex`, then reopen the App, to return to OpenAI.");
|
|
222
222
|
}
|
|
223
223
|
return detected;
|
|
224
224
|
}
|
|
@@ -249,21 +249,37 @@ function backupFile(target, file, home, groupId = timestamp()) {
|
|
|
249
249
|
return backup;
|
|
250
250
|
}
|
|
251
251
|
|
|
252
|
-
function
|
|
252
|
+
function backupGroups(target, home) {
|
|
253
253
|
const dir = path.join(pathsForHome(home).backupsDir, target);
|
|
254
254
|
if (!fs.existsSync(dir)) return [];
|
|
255
255
|
const entries = fs.readdirSync(dir)
|
|
256
256
|
.filter((entry) => entry.endsWith(".json"))
|
|
257
257
|
.sort()
|
|
258
258
|
.reverse();
|
|
259
|
-
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
.
|
|
266
|
-
|
|
259
|
+
const groups = new Map();
|
|
260
|
+
for (const entry of entries) {
|
|
261
|
+
const file = path.join(dir, entry);
|
|
262
|
+
const record = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
263
|
+
const groupId = record.group_id || entry;
|
|
264
|
+
if (!groups.has(groupId)) groups.set(groupId, []);
|
|
265
|
+
groups.get(groupId).push({ file, record });
|
|
266
|
+
}
|
|
267
|
+
return [...groups.values()];
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function latestBackups(target, home) {
|
|
271
|
+
const groups = backupGroups(target, home);
|
|
272
|
+
if (target !== "codex") return groups[0] || [];
|
|
273
|
+
|
|
274
|
+
// Older CLI releases created a fresh backup every time `connect codex` ran,
|
|
275
|
+
// even when Codex was already connected. Such a group only restores the 4YI
|
|
276
|
+
// provider and credential helper. Walk back to the newest group whose config
|
|
277
|
+
// predates 4YI management so existing installations can still return to the
|
|
278
|
+
// official provider.
|
|
279
|
+
return groups.find((group) => !group.some(({ record }) => (
|
|
280
|
+
typeof record.content === "string"
|
|
281
|
+
&& (record.content.includes(CODEX_ROOT_START) || record.content.includes(CODEX_PROVIDER_START))
|
|
282
|
+
))) || [];
|
|
267
283
|
}
|
|
268
284
|
|
|
269
285
|
function mask(value) {
|
|
@@ -566,10 +582,15 @@ async function connectCodex({ session, home, codexHome, codexBaseUrl, stdout, sk
|
|
|
566
582
|
const catalog = buildCodexCatalog(models, template);
|
|
567
583
|
atomicWrite(paths.codexCatalog, `${JSON.stringify(catalog, null, 2)}\n`);
|
|
568
584
|
|
|
569
|
-
const backupGroup = timestamp();
|
|
570
|
-
const backup = backupFile("codex", paths.codexConfig, home, backupGroup);
|
|
571
|
-
const helperBackup = backupFile("codex", paths.codexCredentialHelper, home, backupGroup);
|
|
572
585
|
let existing = fs.existsSync(paths.codexConfig) ? fs.readFileSync(paths.codexConfig, "utf8") : "";
|
|
586
|
+
const alreadyManaged = existing.includes(CODEX_ROOT_START) || existing.includes(CODEX_PROVIDER_START);
|
|
587
|
+
let backup;
|
|
588
|
+
let helperBackup;
|
|
589
|
+
if (!alreadyManaged) {
|
|
590
|
+
const backupGroup = timestamp();
|
|
591
|
+
backup = backupFile("codex", paths.codexConfig, home, backupGroup);
|
|
592
|
+
helperBackup = backupFile("codex", paths.codexCredentialHelper, home, backupGroup);
|
|
593
|
+
}
|
|
573
594
|
existing = removeManagedBlock(existing, CODEX_ROOT_START, CODEX_ROOT_END);
|
|
574
595
|
existing = removeManagedBlock(existing, CODEX_PROVIDER_START, CODEX_PROVIDER_END);
|
|
575
596
|
existing = removeCodexRootAssignments(existing);
|
|
@@ -584,8 +605,12 @@ async function connectCodex({ session, home, codexHome, codexBaseUrl, stdout, sk
|
|
|
584
605
|
stdout(`Connected Codex: ${paths.codexConfig}`);
|
|
585
606
|
stdout(`Codex credential helper: ${paths.codexCredentialHelper}`);
|
|
586
607
|
stdout(`Available Codex models: ${models.map((model) => model.id).join(", ")}`);
|
|
587
|
-
|
|
588
|
-
|
|
608
|
+
if (backup && helperBackup) {
|
|
609
|
+
stdout(`Backup: ${backup}`);
|
|
610
|
+
stdout(`Backup: ${helperBackup}`);
|
|
611
|
+
} else {
|
|
612
|
+
stdout("Preserved the existing Codex restore point.");
|
|
613
|
+
}
|
|
589
614
|
}
|
|
590
615
|
|
|
591
616
|
function resolveUrls(session, options) {
|
|
@@ -684,8 +709,7 @@ function restoreOne(target, home, stdout, { required = true } = {}) {
|
|
|
684
709
|
if (required) throw new Error(`No ${target} backup found.`);
|
|
685
710
|
return false;
|
|
686
711
|
}
|
|
687
|
-
for (const backup of backups) {
|
|
688
|
-
const record = JSON.parse(fs.readFileSync(backup, "utf8"));
|
|
712
|
+
for (const { file: backup, record } of backups) {
|
|
689
713
|
if (record.existed) atomicWrite(record.source, record.content || "");
|
|
690
714
|
else if (fs.existsSync(record.source)) fs.unlinkSync(record.source);
|
|
691
715
|
stdout(`Restored ${target}: ${record.source}`);
|