impel-cli 0.20.40 → 0.20.41
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/RELEASE_NOTES.md +15 -0
- package/package.json +1 -1
- package/src/commands/apps.js +4 -2
- package/src/commands/status.js +9 -4
- package/src/windowsApps.js +122 -10
package/RELEASE_NOTES.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Release notes
|
|
2
2
|
|
|
3
|
+
## 0.20.41 — Windows onboarding: converge over an already-installed Store ChatGPT
|
|
4
|
+
|
|
5
|
+
- Accepts winget's UPDATE_NOT_APPLICABLE result during setup as well as
|
|
6
|
+
update, gated by the pin-exact package probe, ending the unconvergeable
|
|
7
|
+
first-run failure loop on machines where the ChatGPT Store app already
|
|
8
|
+
exists (and, downstream, the managed sign-in loop it caused).
|
|
9
|
+
- Registers a machine-wide staged Store package for the current user
|
|
10
|
+
(download-free, no elevation) when winget sees it but the per-user probe
|
|
11
|
+
does not, still failing closed through the pin-exact probe.
|
|
12
|
+
- Resolves the app-open path strictly through the pinned finder so an
|
|
13
|
+
off-pin Store build can never be staged as the managed app.
|
|
14
|
+
- Names installed-vs-pinned versions in vendor-install diagnostics with a
|
|
15
|
+
measured direction, adds the 0x8A15002B winget hint, and makes Windows
|
|
16
|
+
status require the staged managed app rather than config presence alone.
|
|
17
|
+
|
|
3
18
|
## 0.20.40 — Field-report fixes: launcher drift, sync ownership, Codex capability, one-run update
|
|
4
19
|
|
|
5
20
|
- Survives Claude auto-update drift: `impel claude` resolves the exact reviewed
|
package/package.json
CHANGED
package/src/commands/apps.js
CHANGED
|
@@ -55,6 +55,7 @@ import {
|
|
|
55
55
|
findWindowsChatGPTApp,
|
|
56
56
|
findManagedWindowsChatGPTApp,
|
|
57
57
|
launchWindowsClaudeApp,
|
|
58
|
+
pinnedWindowsChatGPTApp,
|
|
58
59
|
launchWindowsChatGPTApp,
|
|
59
60
|
removeManagedWindowsChatGPTApp,
|
|
60
61
|
stageWindowsChatGPTApp,
|
|
@@ -280,6 +281,7 @@ const WINGET_EXIT_HINTS = new Map([
|
|
|
280
281
|
[0x8A150014, "no package matched the winget query"],
|
|
281
282
|
[0x8A15001B, "the Microsoft Store client is blocked by policy"],
|
|
282
283
|
[0x8A15001C, "the Microsoft Store app is blocked by policy"],
|
|
284
|
+
[0x8A15002B, "a Store package is already installed but does not match the reviewed pin — its version likely moved past the pinned release"],
|
|
283
285
|
]);
|
|
284
286
|
|
|
285
287
|
function windowsProcessFailure(result) {
|
|
@@ -380,7 +382,7 @@ export async function reconcileWindowsTenantApps({
|
|
|
380
382
|
findClaudeApp: findWindowsClaudeApp,
|
|
381
383
|
ensureClaudeApp: ensureWindowsClaudeApp,
|
|
382
384
|
claudeUserData: windowsClaudeUserData,
|
|
383
|
-
findChatGPTApp:
|
|
385
|
+
findChatGPTApp: (environment) => pinnedWindowsChatGPTApp(environment),
|
|
384
386
|
ensureChatGPTApp: ensureWindowsChatGPTApp,
|
|
385
387
|
findManagedChatGPTApp: findManagedWindowsChatGPTApp,
|
|
386
388
|
stageChatGPTApp: stageWindowsChatGPTApp,
|
|
@@ -664,7 +666,7 @@ export async function cmdWindowsApps(argv, overrides = {}) {
|
|
|
664
666
|
ensureClaudeApp: ensureWindowsClaudeApp,
|
|
665
667
|
launchClaudeApp: launchWindowsClaudeApp,
|
|
666
668
|
claudeUserData: windowsClaudeUserData,
|
|
667
|
-
findChatGPTApp:
|
|
669
|
+
findChatGPTApp: (environment) => pinnedWindowsChatGPTApp(environment),
|
|
668
670
|
ensureChatGPTApp: ensureWindowsChatGPTApp,
|
|
669
671
|
findManagedChatGPTApp: findManagedWindowsChatGPTApp,
|
|
670
672
|
launchChatGPTApp: launchWindowsChatGPTApp,
|
package/src/commands/status.js
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
productAccessLabel,
|
|
16
16
|
} from "../tenants.js";
|
|
17
17
|
import { installedVersion, maybePrintUpdateNotice } from "../updates.js";
|
|
18
|
-
import { windowsClaudeUserData } from "../windowsApps.js";
|
|
18
|
+
import { findManagedWindowsChatGPTApp, windowsClaudeUserData } from "../windowsApps.js";
|
|
19
19
|
import { RUNTIME_BRAND } from "../runtimeBrand.js";
|
|
20
20
|
import { cachedProviderReadiness, providerAvailable } from "../providerReadiness.js";
|
|
21
21
|
|
|
@@ -52,11 +52,16 @@ export function desktopReadiness(tenant, {
|
|
|
52
52
|
states[client] = { app: "unsupported", shell: "unsupported" };
|
|
53
53
|
continue;
|
|
54
54
|
}
|
|
55
|
+
// A generated config alone proves nothing launchable on Windows: when the
|
|
56
|
+
// vendor ensure failed, config.toml can exist while no managed app was
|
|
57
|
+
// ever staged — reporting "ready" then hides the exact failure the user
|
|
58
|
+
// is debugging (IMP-474). Require the staged binary too.
|
|
55
59
|
const appReady = platform === "darwin"
|
|
56
60
|
? existsSync(paths[product].launcher)
|
|
57
|
-
:
|
|
58
|
-
? path.join(paths.claude.userData, "configLibrary", `${CLAUDE_CONFIG_ID}.json`)
|
|
59
|
-
: path.join(paths.chatgpt.codexHome, "config.toml"))
|
|
61
|
+
: product === "claude"
|
|
62
|
+
? existsSync(path.join(paths.claude.userData, "configLibrary", `${CLAUDE_CONFIG_ID}.json`))
|
|
63
|
+
: existsSync(path.join(paths.chatgpt.codexHome, "config.toml"))
|
|
64
|
+
&& Boolean(findManagedWindowsChatGPTApp(paths.root, { isFile: existsSync }));
|
|
60
65
|
let shellReady = appReady && registered.has(product);
|
|
61
66
|
if (platform === "win32" && appData) {
|
|
62
67
|
const shortcutPath = path.win32.join(
|
package/src/windowsApps.js
CHANGED
|
@@ -90,6 +90,71 @@ function installedMsixChatGPT(environment, run = spawnSync, pin = null) {
|
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Register the already-staged ChatGPT Store package for the current user.
|
|
95
|
+
* winget resolves packages machine-wide, so on a machine where another user
|
|
96
|
+
* (or provisioning) installed the app, `winget install` reports
|
|
97
|
+
* UPDATE_NOT_APPLICABLE while the current user's Get-AppxPackage sees
|
|
98
|
+
* nothing. Add-AppxPackage -RegisterByFamilyName registers the existing
|
|
99
|
+
* staged bits for this user without downloading or elevating.
|
|
100
|
+
*/
|
|
101
|
+
export function registerProvisionedMsixChatGPT(environment, run = spawnSync) {
|
|
102
|
+
const familyName = `${WINDOWS_CHATGPT_PACKAGE_NAME}_${WINDOWS_CHATGPT_PUBLISHER_ID}`;
|
|
103
|
+
try {
|
|
104
|
+
const result = run("powershell.exe", [
|
|
105
|
+
"-NoProfile",
|
|
106
|
+
"-NonInteractive",
|
|
107
|
+
"-Command",
|
|
108
|
+
`Add-AppxPackage -RegisterByFamilyName -MainPackage '${familyName}' -ErrorAction Stop`,
|
|
109
|
+
], {
|
|
110
|
+
encoding: "utf8",
|
|
111
|
+
env: windowsPowerShellEnvironment(environment),
|
|
112
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
113
|
+
timeout: 120_000,
|
|
114
|
+
windowsHide: true,
|
|
115
|
+
});
|
|
116
|
+
return result?.status === 0 && !result?.error;
|
|
117
|
+
} catch {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function compareDottedVersions(left, right) {
|
|
123
|
+
const parse = (value) => String(value).split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
124
|
+
const a = parse(left);
|
|
125
|
+
const b = parse(right);
|
|
126
|
+
for (let index = 0; index < Math.max(a.length, b.length); index += 1) {
|
|
127
|
+
const delta = (a[index] || 0) - (b[index] || 0);
|
|
128
|
+
if (delta !== 0) return delta;
|
|
129
|
+
}
|
|
130
|
+
return 0;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* The version string of whatever ChatGPT Store package is installed,
|
|
135
|
+
* regardless of the reviewed pin. Diagnostic-only: it lets a pin mismatch
|
|
136
|
+
* name the actual installed version instead of a bare winget HRESULT.
|
|
137
|
+
*/
|
|
138
|
+
export function installedMsixChatGPTVersion(environment, run = spawnSync) {
|
|
139
|
+
const script = [
|
|
140
|
+
`$package = Get-AppxPackage -Name '${WINDOWS_CHATGPT_PACKAGE_NAME}' -ErrorAction SilentlyContinue`,
|
|
141
|
+
`$package = $package | Where-Object { $_.PublisherId -ceq '${WINDOWS_CHATGPT_PUBLISHER_ID}' } | Sort-Object Version -Descending | Select-Object -First 1`,
|
|
142
|
+
"if ($package) { $package.Version.ToString() }",
|
|
143
|
+
].join("; ");
|
|
144
|
+
try {
|
|
145
|
+
const result = run("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], {
|
|
146
|
+
encoding: "utf8",
|
|
147
|
+
env: windowsPowerShellEnvironment(environment),
|
|
148
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
149
|
+
timeout: 15_000,
|
|
150
|
+
windowsHide: true,
|
|
151
|
+
});
|
|
152
|
+
return result?.status === 0 ? String(result.stdout || "").trim() || null : null;
|
|
153
|
+
} catch {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
93
158
|
/** Candidate paths for Anthropic's signed per-user Windows desktop install. */
|
|
94
159
|
export function windowsClaudeAppCandidates(environment = process.env, dependencies = {}) {
|
|
95
160
|
const io = { readDirectory: fs.readdirSync, ...dependencies };
|
|
@@ -148,7 +213,7 @@ export function findWindowsChatGPTApp(environment = process.env, dependencies =
|
|
|
148
213
|
return msix && exists(msix) ? path.win32.normalize(msix) : null;
|
|
149
214
|
}
|
|
150
215
|
|
|
151
|
-
function pinnedWindowsChatGPTApp(environment = process.env, dependencies = {}) {
|
|
216
|
+
export function pinnedWindowsChatGPTApp(environment = process.env, dependencies = {}) {
|
|
152
217
|
const exists = dependencies.isFile || isFile;
|
|
153
218
|
const pin = dependencies.pin || WINDOWS_CHATGPT_PIN;
|
|
154
219
|
const msix = (dependencies.queryMsix || installedMsixChatGPT)(environment, dependencies.run, pin);
|
|
@@ -434,16 +499,36 @@ export function ensureWindowsChatGPTApp({ update = false } = {}, dependencies =
|
|
|
434
499
|
result = { status: null, error };
|
|
435
500
|
}
|
|
436
501
|
let resultBinary = io.findPinned(io.environment, { pin: io.pin, run: io.run });
|
|
437
|
-
|
|
438
|
-
// with no newer Store release. That is a healthy idempotent update result,
|
|
439
|
-
// not an installation failure (APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE).
|
|
440
|
-
const alreadyUpToDate = Boolean(
|
|
441
|
-
shouldUpdate
|
|
442
|
-
&& resultBinary
|
|
443
|
-
&& !result?.error
|
|
502
|
+
const noApplicableUpgrade = !result?.error
|
|
444
503
|
&& Number.isInteger(result?.status)
|
|
445
|
-
&& (result.status >>> 0) === WINDOWS_WINGET_UPDATE_NOT_APPLICABLE
|
|
446
|
-
|
|
504
|
+
&& (result.status >>> 0) === WINDOWS_WINGET_UPDATE_NOT_APPLICABLE;
|
|
505
|
+
// winget resolves machine-wide: when it reports the package installed but
|
|
506
|
+
// the current user's probe sees nothing, the staged bits are registered to
|
|
507
|
+
// another user or provisioning. Registering them for this user is a local,
|
|
508
|
+
// download-free repair; the pin-exact probe afterwards keeps it fail-closed.
|
|
509
|
+
let registeredOffPin = false;
|
|
510
|
+
if (noApplicableUpgrade && !resultBinary) {
|
|
511
|
+
io.logger.log("ChatGPT/Codex: attempting to register the machine-wide Microsoft Store package for the current user…");
|
|
512
|
+
if ((io.registerProvisioned || registerProvisionedMsixChatGPT)(io.environment, io.run)) {
|
|
513
|
+
resultBinary = io.findPinned(io.environment, { pin: io.pin, run: io.run });
|
|
514
|
+
if (resultBinary) {
|
|
515
|
+
io.logger.log("ChatGPT/Codex: registered the already-staged Microsoft Store package for the current user.");
|
|
516
|
+
} else {
|
|
517
|
+
registeredOffPin = true;
|
|
518
|
+
}
|
|
519
|
+
} else {
|
|
520
|
+
io.logger.log("ChatGPT/Codex: the Store package could not be registered for the current user.");
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
// winget uses an error HRESULT when it finds an installed package with no
|
|
524
|
+
// newer Store release (APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE). That
|
|
525
|
+
// is a healthy idempotent result whenever the pin-exact re-query proves the
|
|
526
|
+
// reviewed package is present — during setup/install as much as update:
|
|
527
|
+
// `winget install` reports the same HRESULT for an already-installed
|
|
528
|
+
// package, and treating it as failure strands first-run machines in an
|
|
529
|
+
// unconvergeable recovery loop. Acceptance stays fail-closed through the
|
|
530
|
+
// pin-exact probe below.
|
|
531
|
+
const alreadyUpToDate = Boolean(resultBinary && noApplicableUpgrade);
|
|
447
532
|
if (alreadyUpToDate) {
|
|
448
533
|
io.logger.log(`ChatGPT/Codex: Microsoft Store package ${io.pin.packageVersion} already up to date.`);
|
|
449
534
|
}
|
|
@@ -456,6 +541,33 @@ export function ensureWindowsChatGPTApp({ update = false } = {}, dependencies =
|
|
|
456
541
|
),
|
|
457
542
|
};
|
|
458
543
|
}
|
|
544
|
+
// A no-applicable-upgrade result over a package that fails the pin-exact
|
|
545
|
+
// probe means the Store carries a different (usually newer) build the
|
|
546
|
+
// Store cannot downgrade. Name both versions so the operator learns this
|
|
547
|
+
// needs a CLI release with an updated ChatGPT pin, not another retry.
|
|
548
|
+
if (!commandSucceeded && !resultBinary && noApplicableUpgrade) {
|
|
549
|
+
const installedVersion = (io.installedVersion || installedMsixChatGPTVersion)(io.environment, io.run);
|
|
550
|
+
if (installedVersion) {
|
|
551
|
+
const newer = compareDottedVersions(installedVersion, io.pin.packageVersion) > 0;
|
|
552
|
+
result = {
|
|
553
|
+
...result,
|
|
554
|
+
error: new Error(
|
|
555
|
+
`installed ChatGPT Store package ${installedVersion} does not match the reviewed pin ${io.pin.packageVersion}; `
|
|
556
|
+
+ (newer
|
|
557
|
+
? "the Microsoft Store cannot downgrade, so this needs a CLI release with an updated ChatGPT pin"
|
|
558
|
+
: "the Store has not fulfilled the pinned build on this machine yet — retry once the Store catches up"),
|
|
559
|
+
),
|
|
560
|
+
};
|
|
561
|
+
} else if (registeredOffPin) {
|
|
562
|
+
result = {
|
|
563
|
+
...result,
|
|
564
|
+
error: new Error(
|
|
565
|
+
"a machine-wide ChatGPT Store package was registered for the current user but does not match "
|
|
566
|
+
+ `the reviewed pin ${io.pin.packageVersion}; the managed app stays uninstalled until a matching build is available`,
|
|
567
|
+
),
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
}
|
|
459
571
|
const succeeded = commandSucceeded && Boolean(resultBinary);
|
|
460
572
|
if (!succeeded) resultBinary = null;
|
|
461
573
|
return {
|