impel-cli 0.18.3 → 0.18.4
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 +8 -0
- package/package.json +1 -1
- package/src/commands/converge.js +14 -2
- package/src/commands/setup.js +8 -1
- package/src/installRecovery/tools.js +16 -4
- package/src/prompt.js +24 -4
- package/src/windowsApps.js +32 -5
package/README.md
CHANGED
|
@@ -329,6 +329,14 @@ run a real package manager, or modify native personal profiles.
|
|
|
329
329
|
CI runs on Ubuntu, macOS, and Windows with supported Node versions. See
|
|
330
330
|
[RELEASING.md](RELEASING.md) for the npm release process.
|
|
331
331
|
|
|
332
|
+
The separate Windows vendor contract runs only on a disposable GitHub-hosted
|
|
333
|
+
Windows VM. It installs the exact newer Claude fixture that preceded the
|
|
334
|
+
current compatibility pin, invokes the production downloader and installer to
|
|
335
|
+
downgrade to the exact pinned MSIX, verifies the registered AppX identity,
|
|
336
|
+
version, architecture, executable, and idempotent second run, then removes the
|
|
337
|
+
package. Docker/Wine remains useful for Windows path and process semantics, but
|
|
338
|
+
cannot validate AppX deployment, packaged services, trust, or Store behavior.
|
|
339
|
+
|
|
332
340
|
The macOS config-contract job downloads the exact ChatGPT archive declared in
|
|
333
341
|
`PINNED_VENDOR_APPS`, verifies its checksum, signature, app version, and
|
|
334
342
|
embedded Codex version, then validates a deterministic generated profile with
|
package/package.json
CHANGED
package/src/commands/converge.js
CHANGED
|
@@ -91,13 +91,18 @@ export async function cmdConverge(argv = [], overrides = {}) {
|
|
|
91
91
|
// answer (not unref'd) so the auto-decline reliably fires even when the
|
|
92
92
|
// prompt is the only thing keeping the event loop alive.
|
|
93
93
|
const allowed = confirmed(await new Promise((resolve) => {
|
|
94
|
-
const
|
|
94
|
+
const controller = new AbortController();
|
|
95
|
+
const timer = setTimeout(() => {
|
|
96
|
+
controller.abort();
|
|
97
|
+
resolve("");
|
|
98
|
+
}, io.vendorPromptTimeoutMs ?? 60_000);
|
|
95
99
|
const settle = (answer) => {
|
|
96
100
|
clearTimeout(timer);
|
|
97
101
|
resolve(answer);
|
|
98
102
|
};
|
|
99
103
|
Promise.resolve(io.promptText(
|
|
100
104
|
`Allow the verified ${label} vendor ${context.mode === "update" ? "update" : "installation"}? [y/N] (auto-N in 60s) `,
|
|
105
|
+
{ signal: controller.signal },
|
|
101
106
|
)).then(settle, () => settle(""));
|
|
102
107
|
}));
|
|
103
108
|
vendorAppDecisions.set(target, allowed);
|
|
@@ -272,7 +277,14 @@ export async function cmdConverge(argv = [], overrides = {}) {
|
|
|
272
277
|
|| product === target
|
|
273
278
|
|| (target === "codex" && product === "chatgpt")
|
|
274
279
|
));
|
|
275
|
-
|
|
280
|
+
const installed = vendorAppTargetReady(retryReport, target);
|
|
281
|
+
return {
|
|
282
|
+
installed,
|
|
283
|
+
error: installed
|
|
284
|
+
? null
|
|
285
|
+
: retryReport?.tenants?.[0]?.errors?.join("; ")
|
|
286
|
+
|| `The ${target} vendor app did not reach a verified state.`,
|
|
287
|
+
};
|
|
276
288
|
},
|
|
277
289
|
},
|
|
278
290
|
}, overrides.recoveryOverrides || {});
|
package/src/commands/setup.js
CHANGED
|
@@ -453,7 +453,14 @@ export async function cmdSetup(argv, overrides = {}) {
|
|
|
453
453
|
|| product === target
|
|
454
454
|
|| (target === "codex" && product === "chatgpt")
|
|
455
455
|
));
|
|
456
|
-
|
|
456
|
+
const installed = vendorAppTargetReady(retryReport, target);
|
|
457
|
+
return {
|
|
458
|
+
installed,
|
|
459
|
+
error: installed
|
|
460
|
+
? null
|
|
461
|
+
: retryReport?.tenants?.[0]?.errors?.join("; ")
|
|
462
|
+
|| `The ${target} vendor app did not reach a verified state.`,
|
|
463
|
+
};
|
|
457
464
|
},
|
|
458
465
|
},
|
|
459
466
|
}, overrides.recoveryOverrides || {});
|
|
@@ -500,10 +500,22 @@ async function installVendorApp(input, context) {
|
|
|
500
500
|
if (typeof context.installVendorApp !== "function") {
|
|
501
501
|
return result("failed", "Vendor app installation is unavailable in this recovery context.");
|
|
502
502
|
}
|
|
503
|
-
const
|
|
504
|
-
|
|
505
|
-
?
|
|
506
|
-
:
|
|
503
|
+
const outcome = await context.installVendorApp(input.target);
|
|
504
|
+
const installed = typeof outcome === "object" && outcome !== null
|
|
505
|
+
? outcome.installed !== false
|
|
506
|
+
: outcome !== false;
|
|
507
|
+
if (installed) {
|
|
508
|
+
return result("succeeded", `The ${input.target} vendor app installation completed.`);
|
|
509
|
+
}
|
|
510
|
+
const detail = typeof outcome === "object" && outcome?.error
|
|
511
|
+
? redactInstallRecoveryText(outcome.error).slice(0, 1_000)
|
|
512
|
+
: null;
|
|
513
|
+
return result(
|
|
514
|
+
"failed",
|
|
515
|
+
detail
|
|
516
|
+
? `The ${input.target} vendor app did not install cleanly: ${detail}`
|
|
517
|
+
: `The ${input.target} vendor app did not install cleanly.`,
|
|
518
|
+
);
|
|
507
519
|
}
|
|
508
520
|
|
|
509
521
|
function repairUserPath(input, context, io) {
|
package/src/prompt.js
CHANGED
|
@@ -40,12 +40,32 @@ export function promptSecret(question) {
|
|
|
40
40
|
});
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
export function promptText(question
|
|
43
|
+
export function promptText(question, {
|
|
44
|
+
signal = null,
|
|
45
|
+
input = process.stdin,
|
|
46
|
+
output = process.stdout,
|
|
47
|
+
} = {}) {
|
|
44
48
|
return new Promise((resolve) => {
|
|
45
|
-
const rl = readline.createInterface({ input
|
|
46
|
-
|
|
49
|
+
const rl = readline.createInterface({ input, output });
|
|
50
|
+
let settled = false;
|
|
51
|
+
const finish = (answer, aborted = false) => {
|
|
52
|
+
if (settled) return;
|
|
53
|
+
settled = true;
|
|
54
|
+
signal?.removeEventListener("abort", abort);
|
|
47
55
|
rl.close();
|
|
48
|
-
|
|
56
|
+
// A cancelled readline prompt otherwise leaves the next status line on
|
|
57
|
+
// the same terminal row as its unanswered question.
|
|
58
|
+
if (aborted && output?.isTTY) output.write("\n");
|
|
59
|
+
resolve(String(answer || "").trim());
|
|
60
|
+
};
|
|
61
|
+
const abort = () => finish("", true);
|
|
62
|
+
if (signal?.aborted) {
|
|
63
|
+
abort();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
67
|
+
rl.question(question, (answer) => {
|
|
68
|
+
finish(answer);
|
|
49
69
|
});
|
|
50
70
|
});
|
|
51
71
|
}
|
package/src/windowsApps.js
CHANGED
|
@@ -14,6 +14,7 @@ export const WINDOWS_CHATGPT_PACKAGE = "9PLM9XGG6VKS";
|
|
|
14
14
|
export const WINDOWS_CHATGPT_PACKAGE_NAME = "OpenAI.Codex";
|
|
15
15
|
export const WINDOWS_CHATGPT_PUBLISHER_ID = "2p2nqsd0c76g0";
|
|
16
16
|
const WINDOWS_CLAUDE_DOWNLOAD_TIMEOUT_MS = 5 * 60 * 1000;
|
|
17
|
+
const WINDOWS_WINGET_UPDATE_NOT_APPLICABLE = 0x8A15002B;
|
|
17
18
|
|
|
18
19
|
export function windowsClaudeUserData(environment = process.env, tenantId = null) {
|
|
19
20
|
const localAppData = environmentValue(environment, "LOCALAPPDATA")
|
|
@@ -34,6 +35,16 @@ function isFile(filePath) {
|
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
// A CLI launched from PowerShell 7 inherits its PSModulePath. Passing that
|
|
39
|
+
// value into Windows PowerShell 5.1 makes legacy modules such as
|
|
40
|
+
// Microsoft.PowerShell.Security visible but unloadable. Omit the variable so
|
|
41
|
+
// powershell.exe reconstructs its own version-correct default module path.
|
|
42
|
+
function windowsPowerShellEnvironment(environment = process.env) {
|
|
43
|
+
return Object.fromEntries(
|
|
44
|
+
Object.entries(environment).filter(([key]) => key.toLowerCase() !== "psmodulepath"),
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
37
48
|
function versionedSquirrelCandidates(root, readDirectory = fs.readdirSync) {
|
|
38
49
|
try {
|
|
39
50
|
return readDirectory(root, { withFileTypes: true })
|
|
@@ -61,7 +72,7 @@ function installedMsixChatGPT(environment, run = spawnSync) {
|
|
|
61
72
|
try {
|
|
62
73
|
const result = run("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], {
|
|
63
74
|
encoding: "utf8",
|
|
64
|
-
env: environment,
|
|
75
|
+
env: windowsPowerShellEnvironment(environment),
|
|
65
76
|
stdio: ["ignore", "pipe", "ignore"],
|
|
66
77
|
windowsHide: true,
|
|
67
78
|
});
|
|
@@ -179,7 +190,7 @@ function pinnedWindowsClaudeApp(environment = process.env, dependencies = {}) {
|
|
|
179
190
|
const result = io.run("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], {
|
|
180
191
|
encoding: "utf8",
|
|
181
192
|
env: {
|
|
182
|
-
...environment,
|
|
193
|
+
...windowsPowerShellEnvironment(environment),
|
|
183
194
|
IMPEL_CLAUDE_PACKAGE_VERSION: io.pin.packageVersion,
|
|
184
195
|
IMPEL_CLAUDE_PACKAGE_PUBLISHER: io.pin.publisher,
|
|
185
196
|
IMPEL_CLAUDE_PACKAGE_ARCHITECTURE: expectedArchitecture,
|
|
@@ -269,7 +280,7 @@ function installPinnedWindowsClaudeMsix(packagePath, environment, pin, architect
|
|
|
269
280
|
{
|
|
270
281
|
encoding: "utf8",
|
|
271
282
|
env: {
|
|
272
|
-
...environment,
|
|
283
|
+
...windowsPowerShellEnvironment(environment),
|
|
273
284
|
IMPEL_CLAUDE_MSIX_PATH: packagePath,
|
|
274
285
|
IMPEL_CLAUDE_PACKAGE_NAME: pin.packageName,
|
|
275
286
|
IMPEL_CLAUDE_PACKAGE_VERSION: pin.packageVersion,
|
|
@@ -363,6 +374,7 @@ export function ensureWindowsChatGPTApp({ update = false } = {}, dependencies =
|
|
|
363
374
|
const io = {
|
|
364
375
|
environment: process.env,
|
|
365
376
|
find: findWindowsChatGPTApp,
|
|
377
|
+
logger: console,
|
|
366
378
|
run: spawnSync,
|
|
367
379
|
...dependencies,
|
|
368
380
|
};
|
|
@@ -388,10 +400,25 @@ export function ensureWindowsChatGPTApp({ update = false } = {}, dependencies =
|
|
|
388
400
|
result = { status: null, error };
|
|
389
401
|
}
|
|
390
402
|
const binary = io.find(io.environment) || before;
|
|
391
|
-
|
|
403
|
+
// winget uses an error HRESULT when `upgrade` finds an installed package
|
|
404
|
+
// with no newer Store release. That is a healthy idempotent update result,
|
|
405
|
+
// not an installation failure (APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE).
|
|
406
|
+
const alreadyUpToDate = Boolean(
|
|
407
|
+
shouldUpdate
|
|
408
|
+
&& binary
|
|
409
|
+
&& !result?.error
|
|
410
|
+
&& Number.isInteger(result?.status)
|
|
411
|
+
&& (result.status >>> 0) === WINDOWS_WINGET_UPDATE_NOT_APPLICABLE,
|
|
412
|
+
);
|
|
413
|
+
if (alreadyUpToDate) {
|
|
414
|
+
io.logger.log("ChatGPT/Codex: Microsoft Store package already up to date.");
|
|
415
|
+
}
|
|
416
|
+
const succeeded = (result?.status === 0 && !result?.error) || alreadyUpToDate;
|
|
392
417
|
return {
|
|
393
418
|
binary,
|
|
394
|
-
action:
|
|
419
|
+
action: alreadyUpToDate
|
|
420
|
+
? "existing"
|
|
421
|
+
: succeeded ? (shouldUpdate ? "updated" : "installed") : (before ? "existing" : "install-failed"),
|
|
395
422
|
result,
|
|
396
423
|
};
|
|
397
424
|
}
|