impel-cli 0.18.15-beta.3 → 0.18.15-beta.5
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/package.json +1 -1
- package/src/apps.js +138 -26
- package/src/windowsApps.js +61 -17
package/package.json
CHANGED
package/src/apps.js
CHANGED
|
@@ -37,6 +37,17 @@ const APP_DEFINITIONS = {
|
|
|
37
37
|
executableNames: ["ChatGPT", "Codex"],
|
|
38
38
|
},
|
|
39
39
|
};
|
|
40
|
+
const BUNDLE_ARTIFACT_REMOVE_OPTIONS = Object.freeze({
|
|
41
|
+
recursive: true,
|
|
42
|
+
force: true,
|
|
43
|
+
maxRetries: 5,
|
|
44
|
+
retryDelay: 25,
|
|
45
|
+
});
|
|
46
|
+
const BUNDLE_LOCK_WAIT_MS = 120_000;
|
|
47
|
+
const BUNDLE_LOCK_POLL_MS = 100;
|
|
48
|
+
const BUNDLE_LOCK_SLEEP = new Int32Array(new SharedArrayBuffer(4));
|
|
49
|
+
const MACOS_O_EXLOCK = 0x00000020;
|
|
50
|
+
const portableBundleMutationLocks = new Set();
|
|
40
51
|
|
|
41
52
|
// Vendor updates can change minified renderer contracts without notice. Each
|
|
42
53
|
// CLI release therefore installs and accepts only the exact app builds tested
|
|
@@ -81,17 +92,27 @@ export const PINNED_VENDOR_APPS = Object.freeze({
|
|
|
81
92
|
}),
|
|
82
93
|
}),
|
|
83
94
|
chatgpt: Object.freeze({
|
|
84
|
-
version: "26.
|
|
85
|
-
codexVersion: "0.146.0-alpha.
|
|
95
|
+
version: "26.727.40816",
|
|
96
|
+
codexVersion: "0.146.0-alpha.9.2",
|
|
86
97
|
bundleName: "ChatGPT.app",
|
|
98
|
+
windows: Object.freeze({
|
|
99
|
+
storeProductId: "9PLM9XGG6VKS",
|
|
100
|
+
packageName: "OpenAI.Codex",
|
|
101
|
+
packageVersion: "26.721.11231.0",
|
|
102
|
+
advertisedPackageVersion: "26.727.4816.0",
|
|
103
|
+
codexVersion: "0.146.0-alpha.3.1",
|
|
104
|
+
publisherId: "2p2nqsd0c76g0",
|
|
105
|
+
executable: "app\\ChatGPT.exe",
|
|
106
|
+
updateManifestUrl: "https://persistent.oaistatic.com/codex-app-prod/windows-store-update.json",
|
|
107
|
+
}),
|
|
87
108
|
downloads: Object.freeze({
|
|
88
109
|
arm64: Object.freeze({
|
|
89
|
-
url: "https://persistent.oaistatic.com/codex-app-prod/ChatGPT-darwin-arm64-26.
|
|
90
|
-
sha256: "
|
|
110
|
+
url: "https://persistent.oaistatic.com/codex-app-prod/ChatGPT-darwin-arm64-26.727.40816.zip",
|
|
111
|
+
sha256: "fdbede9b8a28b5bf3bbf1213fa04291724faa6ed2d1d61bb04853bbdfebce219",
|
|
91
112
|
}),
|
|
92
113
|
x64: Object.freeze({
|
|
93
|
-
url: "https://persistent.oaistatic.com/codex-app-prod/ChatGPT-darwin-x64-26.
|
|
94
|
-
sha256: "
|
|
114
|
+
url: "https://persistent.oaistatic.com/codex-app-prod/ChatGPT-darwin-x64-26.727.40816.zip",
|
|
115
|
+
sha256: "12622d916a3993d61fb52a318dcff7af7449eea5070cdd7e094da29f380e3374",
|
|
95
116
|
}),
|
|
96
117
|
}),
|
|
97
118
|
}),
|
|
@@ -245,7 +266,7 @@ export const CURRENT_CONFIG_VERSION = 25;
|
|
|
245
266
|
// — which is what made every `impel update` re-trigger macOS permission
|
|
246
267
|
// prompts. Bump this ONLY when a code change alters the bytes of a built
|
|
247
268
|
// bundle; leave it alone for changes that don't touch bundle contents.
|
|
248
|
-
export const BUNDLE_BUILD_FINGERPRINT = "bundle-2026-07-
|
|
269
|
+
export const BUNDLE_BUILD_FINGERPRINT = "bundle-2026-07-31.1";
|
|
249
270
|
|
|
250
271
|
/** Parse the tenant's install manifest, or null when absent/corrupt. */
|
|
251
272
|
export function readTenantManifest(homeDir = os.homedir(), tenantId = null) {
|
|
@@ -962,7 +983,6 @@ function installPinnedVendorApp(target, homeDir) {
|
|
|
962
983
|
homeDir, ".config", RUNTIME_BRAND.cli.configNamespace, "vendor", target, pin.version, pin.bundleName,
|
|
963
984
|
);
|
|
964
985
|
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
|
965
|
-
sweepStaleBundleArtifacts(path.dirname(destination), path.basename(destination));
|
|
966
986
|
replaceDirectory(destination, extractedApp);
|
|
967
987
|
return destination;
|
|
968
988
|
} finally {
|
|
@@ -1519,6 +1539,12 @@ exec "$NODE" "$CLI" token${tenantArgs}
|
|
|
1519
1539
|
}
|
|
1520
1540
|
|
|
1521
1541
|
function writeVendoredClaudeBundle(paths, vendorPath, gatewayUrl, safeStorageName) {
|
|
1542
|
+
return withBundleMutationLock(paths.claude.launcher, () => (
|
|
1543
|
+
writeVendoredClaudeBundleLocked(paths, vendorPath, gatewayUrl, safeStorageName)
|
|
1544
|
+
));
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
function writeVendoredClaudeBundleLocked(paths, vendorPath, gatewayUrl, safeStorageName) {
|
|
1522
1548
|
if (!vendorPath) throw new Error("Claude vendor app is unavailable");
|
|
1523
1549
|
if (!isVendableVendorApp("claude", vendorPath)) {
|
|
1524
1550
|
const bundleId = readBundleIdentifier(vendorPath);
|
|
@@ -1532,9 +1558,8 @@ function writeVendoredClaudeBundle(paths, vendorPath, gatewayUrl, safeStorageNam
|
|
|
1532
1558
|
if (!executableName) throw new Error(`Claude executable is missing from ${vendorPath}`);
|
|
1533
1559
|
|
|
1534
1560
|
const bundle = paths.claude.launcher;
|
|
1535
|
-
const staging = `${bundle}.tmp-${process.pid}`;
|
|
1536
1561
|
sweepStaleBundleArtifacts(path.dirname(bundle), path.basename(bundle));
|
|
1537
|
-
|
|
1562
|
+
const staging = uniqueBundleArtifactPath(bundle, "tmp");
|
|
1538
1563
|
cloneAppBundle(vendorPath, staging);
|
|
1539
1564
|
try {
|
|
1540
1565
|
const plistPath = path.join(staging, "Contents", "Info.plist");
|
|
@@ -1616,9 +1641,9 @@ function writeVendoredClaudeBundle(paths, vendorPath, gatewayUrl, safeStorageNam
|
|
|
1616
1641
|
}, null, 2) + "\n", 0o644);
|
|
1617
1642
|
|
|
1618
1643
|
signVendoredApp(staging, vendorExecutable, "Claude", signingIdentity);
|
|
1619
|
-
|
|
1644
|
+
replaceDirectoryUnlocked(bundle, staging);
|
|
1620
1645
|
} catch (error) {
|
|
1621
|
-
|
|
1646
|
+
removeBundleArtifact(staging);
|
|
1622
1647
|
throw error;
|
|
1623
1648
|
}
|
|
1624
1649
|
}
|
|
@@ -1632,6 +1657,12 @@ function claudeUsageCompatibilityEnabled(gatewayUrl) {
|
|
|
1632
1657
|
}
|
|
1633
1658
|
|
|
1634
1659
|
function writeVendoredChatGPTBundle(paths, vendorPath, gatewayUrl, homeDir) {
|
|
1660
|
+
return withBundleMutationLock(paths.chatgpt.launcher, () => (
|
|
1661
|
+
writeVendoredChatGPTBundleLocked(paths, vendorPath, gatewayUrl, homeDir)
|
|
1662
|
+
));
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
function writeVendoredChatGPTBundleLocked(paths, vendorPath, gatewayUrl, homeDir) {
|
|
1635
1666
|
if (!vendorPath) throw new Error("ChatGPT/Codex vendor app is unavailable");
|
|
1636
1667
|
const definition = APP_DEFINITIONS.chatgpt;
|
|
1637
1668
|
const executableName = definition.executableNames.find((name) => (
|
|
@@ -1640,11 +1671,10 @@ function writeVendoredChatGPTBundle(paths, vendorPath, gatewayUrl, homeDir) {
|
|
|
1640
1671
|
if (!executableName) throw new Error(`ChatGPT/Codex executable is missing from ${vendorPath}`);
|
|
1641
1672
|
|
|
1642
1673
|
const bundle = paths.chatgpt.launcher;
|
|
1643
|
-
|
|
1674
|
+
sweepStaleBundleArtifacts(path.dirname(bundle), path.basename(bundle));
|
|
1675
|
+
const staging = uniqueBundleArtifactPath(bundle, "tmp");
|
|
1644
1676
|
const vendorBundleName = path.basename(vendorPath);
|
|
1645
1677
|
const vendorBundle = path.join(staging, "Contents", "Resources", vendorBundleName);
|
|
1646
|
-
sweepStaleBundleArtifacts(path.dirname(bundle), path.basename(bundle));
|
|
1647
|
-
fs.rmSync(staging, { recursive: true, force: true });
|
|
1648
1678
|
cloneAppBundle(vendorPath, vendorBundle);
|
|
1649
1679
|
try {
|
|
1650
1680
|
const asarPath = path.join(vendorBundle, "Contents", "Resources", "app.asar");
|
|
@@ -1720,9 +1750,9 @@ function writeVendoredChatGPTBundle(paths, vendorPath, gatewayUrl, homeDir) {
|
|
|
1720
1750
|
}, null, 2) + "\n", 0o644);
|
|
1721
1751
|
|
|
1722
1752
|
signVendoredApp(staging, vendorExecutable, "ChatGPT", signingIdentity);
|
|
1723
|
-
|
|
1753
|
+
replaceDirectoryUnlocked(bundle, staging);
|
|
1724
1754
|
} catch (error) {
|
|
1725
|
-
|
|
1755
|
+
removeBundleArtifact(staging);
|
|
1726
1756
|
throw error;
|
|
1727
1757
|
}
|
|
1728
1758
|
}
|
|
@@ -2215,6 +2245,7 @@ HERE=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
|
|
2215
2245
|
VENDOR_APP="$HERE/../Resources/"${shellQuote(vendorBundleName)}
|
|
2216
2246
|
export CODEX_HOME=${shellQuote(paths.chatgpt.codexHome)}
|
|
2217
2247
|
export CODEX_AUTHAPI_BASE_URL=${shellQuote(codexAccountBaseUrl)}
|
|
2248
|
+
export CODEX_SPARKLE_ENABLED=false
|
|
2218
2249
|
unset CODEX_ACCESS_TOKEN CODEX_API_KEY OPENAI_API_KEY OPENAI_BASE_URL
|
|
2219
2250
|
secure_codex_home() {
|
|
2220
2251
|
[ ! -L "$CODEX_HOME" ] || exit 1
|
|
@@ -2525,24 +2556,105 @@ export function sweepStaleBundleArtifacts(dir, baseName) {
|
|
|
2525
2556
|
throw error;
|
|
2526
2557
|
}
|
|
2527
2558
|
const prefixes = [`${baseName}.tmp-`, `${baseName}.previous-`];
|
|
2528
|
-
const removed =
|
|
2529
|
-
for (const entry of
|
|
2530
|
-
|
|
2559
|
+
const removed = [];
|
|
2560
|
+
for (const entry of entries.filter((candidate) => prefixes.some((prefix) => candidate.startsWith(prefix)))) {
|
|
2561
|
+
if (removeBundleArtifact(path.join(dir, entry))) removed.push(entry);
|
|
2531
2562
|
}
|
|
2532
2563
|
return removed;
|
|
2533
2564
|
}
|
|
2534
2565
|
|
|
2535
|
-
function
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2566
|
+
function uniqueBundleArtifactPath(destination, kind) {
|
|
2567
|
+
return `${destination}.${kind}-${process.pid}-${crypto.randomBytes(6).toString("hex")}`;
|
|
2568
|
+
}
|
|
2569
|
+
|
|
2570
|
+
function removeBundleArtifact(candidate) {
|
|
2571
|
+
try {
|
|
2572
|
+
fs.rmSync(candidate, BUNDLE_ARTIFACT_REMOVE_OPTIONS);
|
|
2573
|
+
return !fs.existsSync(candidate);
|
|
2574
|
+
} catch {
|
|
2575
|
+
return false;
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2579
|
+
export function withBundleMutationLock(destination, run) {
|
|
2580
|
+
const lockPath = path.join(
|
|
2581
|
+
path.dirname(destination),
|
|
2582
|
+
`.${path.basename(destination)}.impel-install.lock`,
|
|
2583
|
+
);
|
|
2584
|
+
fs.mkdirSync(path.dirname(lockPath), { recursive: true });
|
|
2585
|
+
|
|
2586
|
+
if (process.platform !== "darwin") {
|
|
2587
|
+
if (portableBundleMutationLocks.has(lockPath)) {
|
|
2588
|
+
throw new Error(`another Impel app update is already replacing ${path.basename(destination)}`);
|
|
2589
|
+
}
|
|
2590
|
+
const descriptor = fs.openSync(lockPath, fs.constants.O_CREAT | fs.constants.O_RDWR, 0o600);
|
|
2591
|
+
portableBundleMutationLocks.add(lockPath);
|
|
2592
|
+
try {
|
|
2593
|
+
return run();
|
|
2594
|
+
} finally {
|
|
2595
|
+
portableBundleMutationLocks.delete(lockPath);
|
|
2596
|
+
fs.closeSync(descriptor);
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2599
|
+
|
|
2600
|
+
const deadline = Date.now() + BUNDLE_LOCK_WAIT_MS;
|
|
2601
|
+
let descriptor;
|
|
2602
|
+
while (descriptor === undefined) {
|
|
2603
|
+
try {
|
|
2604
|
+
descriptor = fs.openSync(
|
|
2605
|
+
lockPath,
|
|
2606
|
+
fs.constants.O_CREAT | fs.constants.O_RDWR | fs.constants.O_NONBLOCK | MACOS_O_EXLOCK,
|
|
2607
|
+
0o600,
|
|
2608
|
+
);
|
|
2609
|
+
} catch (error) {
|
|
2610
|
+
if (!["EAGAIN", "EWOULDBLOCK"].includes(error?.code)) throw error;
|
|
2611
|
+
if (Date.now() >= deadline) {
|
|
2612
|
+
throw new Error(`timed out waiting for another Impel app update to replace ${path.basename(destination)}`);
|
|
2613
|
+
}
|
|
2614
|
+
Atomics.wait(BUNDLE_LOCK_SLEEP, 0, 0, BUNDLE_LOCK_POLL_MS);
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
try {
|
|
2618
|
+
return run();
|
|
2619
|
+
} finally {
|
|
2620
|
+
fs.closeSync(descriptor);
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
|
|
2624
|
+
function replaceDirectoryUnlocked(destination, staging, {
|
|
2625
|
+
removeArtifact = removeBundleArtifact,
|
|
2626
|
+
} = {}) {
|
|
2627
|
+
const backup = fs.existsSync(destination)
|
|
2628
|
+
? uniqueBundleArtifactPath(destination, "previous")
|
|
2629
|
+
: null;
|
|
2630
|
+
if (backup) fs.renameSync(destination, backup);
|
|
2539
2631
|
try {
|
|
2540
2632
|
fs.renameSync(staging, destination);
|
|
2541
|
-
fs.rmSync(backup, { recursive: true, force: true });
|
|
2542
2633
|
} catch (error) {
|
|
2543
|
-
if (!fs.existsSync(destination) && fs.existsSync(backup))
|
|
2634
|
+
if (backup && !fs.existsSync(destination) && fs.existsSync(backup)) {
|
|
2635
|
+
fs.renameSync(backup, destination);
|
|
2636
|
+
}
|
|
2544
2637
|
throw error;
|
|
2545
2638
|
}
|
|
2639
|
+
|
|
2640
|
+
// The new bundle is committed once staging has been renamed into place.
|
|
2641
|
+
// Finder, LaunchServices, or a just-closed helper can briefly keep changing
|
|
2642
|
+
// the old tree and make recursive removal end with ENOTEMPTY. Do not turn
|
|
2643
|
+
// that post-commit housekeeping race into a failed tenant update: the next
|
|
2644
|
+
// sweep will retry any backup that remains.
|
|
2645
|
+
if (backup) {
|
|
2646
|
+
try {
|
|
2647
|
+
removeArtifact(backup);
|
|
2648
|
+
} catch {
|
|
2649
|
+
// Cleanup is best-effort after the committed rename.
|
|
2650
|
+
}
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
|
|
2654
|
+
export function replaceDirectory(destination, staging, options = {}) {
|
|
2655
|
+
return withBundleMutationLock(destination, () => (
|
|
2656
|
+
replaceDirectoryUnlocked(destination, staging, options)
|
|
2657
|
+
));
|
|
2546
2658
|
}
|
|
2547
2659
|
|
|
2548
2660
|
function isGatewayModel(model) {
|
package/src/windowsApps.js
CHANGED
|
@@ -11,9 +11,10 @@ import { environmentValue, nativeCommandInvocation } from "./nativeProcess.js";
|
|
|
11
11
|
import { normalizeTenantId } from "./tenants.js";
|
|
12
12
|
import { brandedEnvironmentName, RUNTIME_BRAND } from "./runtimeBrand.js";
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
export const
|
|
16
|
-
export const
|
|
14
|
+
const WINDOWS_CHATGPT_PIN = PINNED_VENDOR_APPS.chatgpt.windows;
|
|
15
|
+
export const WINDOWS_CHATGPT_PACKAGE = WINDOWS_CHATGPT_PIN.storeProductId;
|
|
16
|
+
export const WINDOWS_CHATGPT_PACKAGE_NAME = WINDOWS_CHATGPT_PIN.packageName;
|
|
17
|
+
export const WINDOWS_CHATGPT_PUBLISHER_ID = WINDOWS_CHATGPT_PIN.publisherId;
|
|
17
18
|
const WINDOWS_CLAUDE_DOWNLOAD_TIMEOUT_MS = 5 * 60 * 1000;
|
|
18
19
|
const WINDOWS_WINGET_UPDATE_NOT_APPLICABLE = 0x8A15002B;
|
|
19
20
|
|
|
@@ -60,20 +61,26 @@ function versionedSquirrelCandidates(root, readDirectory = fs.readdirSync) {
|
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
function installedMsixChatGPT(environment, run = spawnSync) {
|
|
64
|
+
function installedMsixChatGPT(environment, run = spawnSync, pin = null) {
|
|
64
65
|
const script = [
|
|
65
66
|
`$package = Get-AppxPackage -Name '${WINDOWS_CHATGPT_PACKAGE_NAME}' -ErrorAction SilentlyContinue`,
|
|
66
|
-
`$package = $package | Where-Object PublisherId -
|
|
67
|
+
`$package = $package | Where-Object { $_.PublisherId -ceq '${WINDOWS_CHATGPT_PUBLISHER_ID}' -and (-not $env:IMPEL_CHATGPT_PACKAGE_VERSION -or $_.Version.ToString() -ceq $env:IMPEL_CHATGPT_PACKAGE_VERSION) } | Sort-Object Version -Descending | Select-Object -First 1`,
|
|
67
68
|
"if ($package) {",
|
|
68
69
|
" $manifest = Get-AppxPackageManifest -Package $package",
|
|
69
70
|
" $relative = $manifest.Package.Applications.Application | Where-Object Id -eq 'App' | Select-Object -First 1 -ExpandProperty Executable",
|
|
70
|
-
"
|
|
71
|
+
" $normalized = ([string]$relative).Replace('/', '\\')",
|
|
72
|
+
" $expected = ([string]$env:IMPEL_CHATGPT_PACKAGE_EXECUTABLE).Replace('/', '\\')",
|
|
73
|
+
" if ($relative -and (-not $expected -or $normalized -ieq $expected)) { Join-Path $package.InstallLocation $relative }",
|
|
71
74
|
"}",
|
|
72
75
|
].join("; ");
|
|
73
76
|
try {
|
|
74
77
|
const result = run("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], {
|
|
75
78
|
encoding: "utf8",
|
|
76
|
-
env:
|
|
79
|
+
env: {
|
|
80
|
+
...windowsPowerShellEnvironment(environment),
|
|
81
|
+
IMPEL_CHATGPT_PACKAGE_VERSION: pin?.packageVersion || "",
|
|
82
|
+
IMPEL_CHATGPT_PACKAGE_EXECUTABLE: pin?.executable || "",
|
|
83
|
+
},
|
|
77
84
|
stdio: ["ignore", "pipe", "ignore"],
|
|
78
85
|
windowsHide: true,
|
|
79
86
|
});
|
|
@@ -141,9 +148,23 @@ export function findWindowsChatGPTApp(environment = process.env, dependencies =
|
|
|
141
148
|
return msix && exists(msix) ? path.win32.normalize(msix) : null;
|
|
142
149
|
}
|
|
143
150
|
|
|
144
|
-
function
|
|
151
|
+
function pinnedWindowsChatGPTApp(environment = process.env, dependencies = {}) {
|
|
152
|
+
const exists = dependencies.isFile || isFile;
|
|
153
|
+
const pin = dependencies.pin || WINDOWS_CHATGPT_PIN;
|
|
154
|
+
const msix = (dependencies.queryMsix || installedMsixChatGPT)(environment, dependencies.run, pin);
|
|
155
|
+
return msix && exists(msix) ? path.win32.normalize(msix) : null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function wingetInvocation(action, environment, {
|
|
159
|
+
packageId,
|
|
160
|
+
source = "winget",
|
|
161
|
+
scope = "user",
|
|
162
|
+
}) {
|
|
145
163
|
const verb = action === "update" ? "upgrade" : "install";
|
|
146
164
|
const scopeArgs = scope ? ["--scope", scope] : [];
|
|
165
|
+
// Microsoft Store packages commonly report Version "Unknown" to winget.
|
|
166
|
+
// Without this switch winget refuses to consider their available update.
|
|
167
|
+
const updateArgs = action === "update" ? ["--include-unknown"] : [];
|
|
147
168
|
return nativeCommandInvocation(
|
|
148
169
|
"winget",
|
|
149
170
|
[
|
|
@@ -154,6 +175,7 @@ function wingetInvocation(action, environment, { packageId, source = "winget", s
|
|
|
154
175
|
"--source",
|
|
155
176
|
source,
|
|
156
177
|
...scopeArgs,
|
|
178
|
+
...updateArgs,
|
|
157
179
|
"--silent",
|
|
158
180
|
"--disable-interactivity",
|
|
159
181
|
"--accept-package-agreements",
|
|
@@ -372,21 +394,31 @@ export async function ensureWindowsClaudeApp(_options = {}, dependencies = {}) {
|
|
|
372
394
|
|
|
373
395
|
/** Install/update OpenAI's official ChatGPT/Codex Store package. */
|
|
374
396
|
export function ensureWindowsChatGPTApp({ update = false } = {}, dependencies = {}) {
|
|
397
|
+
const find = dependencies.find || findWindowsChatGPTApp;
|
|
398
|
+
const findPinned = dependencies.findPinned || dependencies.find || pinnedWindowsChatGPTApp;
|
|
375
399
|
const io = {
|
|
376
400
|
environment: process.env,
|
|
377
|
-
find
|
|
401
|
+
find,
|
|
402
|
+
findPinned,
|
|
378
403
|
logger: console,
|
|
404
|
+
pin: WINDOWS_CHATGPT_PIN,
|
|
379
405
|
run: spawnSync,
|
|
380
406
|
...dependencies,
|
|
381
407
|
};
|
|
382
408
|
const before = io.find(io.environment);
|
|
383
|
-
|
|
409
|
+
const pinnedBefore = io.findPinned(io.environment, { pin: io.pin, run: io.run });
|
|
410
|
+
if (pinnedBefore && !update) {
|
|
411
|
+
return { binary: pinnedBefore, action: "existing", result: null };
|
|
412
|
+
}
|
|
384
413
|
|
|
385
414
|
// `impel update` also repairs missing surfaces. A missing Store package must
|
|
386
415
|
// be installed, not sent to `winget upgrade` (which returns 0x8A150014).
|
|
387
416
|
const shouldUpdate = Boolean(update && before);
|
|
417
|
+
// The msstore source rejects its AppX package version as a winget --version
|
|
418
|
+
// selector. Install the current Store package, then fail closed below unless
|
|
419
|
+
// Windows registered the exact reviewed package version and executable.
|
|
388
420
|
const invocation = wingetInvocation(shouldUpdate ? "update" : "install", io.environment, {
|
|
389
|
-
packageId:
|
|
421
|
+
packageId: io.pin.storeProductId,
|
|
390
422
|
source: "msstore",
|
|
391
423
|
scope: null,
|
|
392
424
|
});
|
|
@@ -400,26 +432,36 @@ export function ensureWindowsChatGPTApp({ update = false } = {}, dependencies =
|
|
|
400
432
|
} catch (error) {
|
|
401
433
|
result = { status: null, error };
|
|
402
434
|
}
|
|
403
|
-
|
|
435
|
+
let resultBinary = io.findPinned(io.environment, { pin: io.pin, run: io.run });
|
|
404
436
|
// winget uses an error HRESULT when `upgrade` finds an installed package
|
|
405
437
|
// with no newer Store release. That is a healthy idempotent update result,
|
|
406
438
|
// not an installation failure (APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE).
|
|
407
439
|
const alreadyUpToDate = Boolean(
|
|
408
440
|
shouldUpdate
|
|
409
|
-
&&
|
|
441
|
+
&& resultBinary
|
|
410
442
|
&& !result?.error
|
|
411
443
|
&& Number.isInteger(result?.status)
|
|
412
444
|
&& (result.status >>> 0) === WINDOWS_WINGET_UPDATE_NOT_APPLICABLE,
|
|
413
445
|
);
|
|
414
446
|
if (alreadyUpToDate) {
|
|
415
|
-
io.logger.log(
|
|
447
|
+
io.logger.log(`ChatGPT/Codex: Microsoft Store package ${io.pin.packageVersion} already up to date.`);
|
|
448
|
+
}
|
|
449
|
+
const commandSucceeded = (result?.status === 0 && !result?.error) || alreadyUpToDate;
|
|
450
|
+
if (commandSucceeded && !resultBinary) {
|
|
451
|
+
result = {
|
|
452
|
+
...result,
|
|
453
|
+
error: new Error(
|
|
454
|
+
`ChatGPT/Codex Store package ${io.pin.packageVersion} did not appear after winget completed`,
|
|
455
|
+
),
|
|
456
|
+
};
|
|
416
457
|
}
|
|
417
|
-
const succeeded =
|
|
458
|
+
const succeeded = commandSucceeded && Boolean(resultBinary);
|
|
459
|
+
if (!succeeded) resultBinary = null;
|
|
418
460
|
return {
|
|
419
|
-
binary,
|
|
461
|
+
binary: resultBinary,
|
|
420
462
|
action: alreadyUpToDate
|
|
421
463
|
? "existing"
|
|
422
|
-
: succeeded ? (shouldUpdate ? "updated" : "installed") :
|
|
464
|
+
: succeeded ? (shouldUpdate ? "updated" : "installed") : "install-failed",
|
|
423
465
|
result,
|
|
424
466
|
};
|
|
425
467
|
}
|
|
@@ -617,6 +659,7 @@ export function launchWindowsChatGPTApp(binary, {
|
|
|
617
659
|
"CODEX_AUTHAPI_BASE_URL",
|
|
618
660
|
"CODEX_ELECTRON_USER_DATA_PATH",
|
|
619
661
|
"CODEX_HOME",
|
|
662
|
+
"CODEX_SPARKLE_ENABLED",
|
|
620
663
|
"OPENAI_API_KEY",
|
|
621
664
|
"OPENAI_BASE_URL",
|
|
622
665
|
]);
|
|
@@ -625,6 +668,7 @@ export function launchWindowsChatGPTApp(binary, {
|
|
|
625
668
|
}
|
|
626
669
|
environment.CODEX_HOME = codexHome;
|
|
627
670
|
environment.CODEX_AUTHAPI_BASE_URL = `${gatewayUrl}/chatgpt_passthrough/backend-api`;
|
|
671
|
+
environment.CODEX_SPARKLE_ENABLED = "false";
|
|
628
672
|
// Codex Desktop resolves this before acquiring Electron's single-instance
|
|
629
673
|
// lock. Keep --user-data-dir below as a compatibility fallback for builds
|
|
630
674
|
// that predate the dedicated desktop override.
|