impel-cli 0.18.15-beta.8 → 0.18.15

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.
@@ -20,9 +20,8 @@ import { assertProviderScopes, ensureTenantSelection, tenantCredential } from ".
20
20
  import { maybePrintUpdateNotice } from "../updates.js";
21
21
  import {
22
22
  nativeSpawnInvocation,
23
+ resolveNativeBinary,
23
24
  } from "../nativeProcess.js";
24
- import { resolveReviewedVendorCliBinary } from "../vendorCliBinaries.js";
25
- import { PINNED_VENDOR_CLI_VERSIONS } from "../vendorCliVersions.js";
26
25
  import { RUNTIME_BRAND } from "../runtimeBrand.js";
27
26
  import {
28
27
  cacheProviderReadiness,
@@ -116,15 +115,7 @@ async function assertLiveProviderReadiness({ config, gatewayUrl, credential, ten
116
115
  }
117
116
 
118
117
  function runNativeCli(tool, argv, environment) {
119
- const binary = resolveReviewedVendorCliBinary(tool, environment);
120
- if (!binary) {
121
- const version = PINNED_VENDOR_CLI_VERSIONS[tool];
122
- console.error(
123
- `impel ${tool}: reviewed ${tool === "claude" ? "Claude Code" : "Codex"} CLI `
124
- + `v${version} is not installed. Run \`impel update\` to repair it.`,
125
- );
126
- return Promise.resolve(127);
127
- }
118
+ const binary = resolveNativeBinary(tool, environment);
128
119
  let invocation;
129
120
  try {
130
121
  invocation = nativeSpawnInvocation(binary, argv, environment);
@@ -4,9 +4,8 @@ import path from "node:path";
4
4
 
5
5
  import { appPaths, CLAUDE_CONFIG_ID, readTenantManifest } from "../apps.js";
6
6
  import { crossAppModelsEnabled, loadConfig, maskSecret } from "../config.js";
7
- import { environmentValue } from "../nativeProcess.js";
7
+ import { environmentValue, findNativeBinary } from "../nativeProcess.js";
8
8
  import { tenantCliClientReadiness } from "../provisioning.js";
9
- import { findReviewedVendorCliBinary } from "../vendorCliBinaries.js";
10
9
  import { windowsTenantShortcutName } from "../shellEntries.js";
11
10
  import {
12
11
  ensureTenantSelection,
@@ -87,7 +86,7 @@ export async function cmdStatus(overrides = {}) {
87
86
  cliReadiness: tenantCliClientReadiness,
88
87
  desktopReadiness,
89
88
  installedVersion,
90
- findNativeBinary: findReviewedVendorCliBinary,
89
+ findNativeBinary,
91
90
  maybePrintUpdateNotice,
92
91
  platform: process.platform,
93
92
  environment: process.env,
@@ -18,7 +18,6 @@ import {
18
18
  isNewerVersion,
19
19
  refreshUpdateCache,
20
20
  updateInstallSpec,
21
- updateTagForVersion,
22
21
  writeUpdateCache,
23
22
  } from "../updates.js";
24
23
 
@@ -201,41 +200,30 @@ export async function cmdUpdate(argv, overrides = {}) {
201
200
  }
202
201
 
203
202
  const current = io.installedVersion();
204
- const updateTag = updateTagForVersion(current);
205
- const installSpec = updateInstallSpec(updateTag);
206
- const remote = await io.progress(
207
- "Checking npm for impel-cli updates",
208
- () => io.fetchRemoteVersion({ tag: updateTag }),
209
- );
203
+ const remote = await io.progress("Checking npm for impel-cli updates", () => io.fetchRemoteVersion());
210
204
  if (remote) io.writeCache({ remoteVersion: remote, checkedAt: Date.now() });
211
205
 
212
206
  console.log(`impel-cli v${current ?? "?"}`);
213
- console.log(`npm ${updateTag}: ${remote ? `v${remote}` : "unknown — registry check failed"}`);
207
+ console.log(`npm latest: ${remote ? `v${remote}` : "unknown — registry check failed"}`);
214
208
 
215
209
  const updateAvailable = Boolean(current && remote && isNewerVersion(remote, current));
216
210
  const upToDate = Boolean(current && remote && !updateAvailable);
217
211
  if (flags.check) {
218
- if (!remote) {
219
- console.log("Update status unavailable; the installed CLI was not changed.");
220
- process.exitCode = 1;
221
- } else {
222
- console.log(upToDate ? "Up to date." : "Update available: run `impel update`.");
223
- }
212
+ console.log(upToDate ? "Up to date." : "Update available: run `impel update`.");
213
+ if (!remote) process.exitCode = 1;
224
214
  return;
225
215
  }
226
216
 
227
217
  // ── CLI ────────────────────────────────────────────────────────────────
228
- if (!remote) {
229
- console.warn("CLI: npm update check unavailable; keeping the installed build.");
230
- } else if (upToDate) {
218
+ if (upToDate) {
231
219
  console.log("CLI: already up to date.");
232
220
  } else {
233
- console.log(`CLI: installing the npm ${updateTag} build…`);
234
- if (!await io.progress(`Installing the npm ${updateTag} impel-cli build`, () => io.selfUpdate(installSpec))) {
221
+ console.log("CLI: installing the latest build…");
222
+ if (!await io.progress("Installing the latest impel-cli build", () => io.selfUpdate(updateInstallSpec()))) {
235
223
  console.error("impel update: `npm install -g` failed; the CLI was not updated.");
236
224
  if (io.platform === "win32") {
237
225
  console.error(" Verify `npm --version` in PowerShell, then retry `impel update`.");
238
- console.error(` Manual recovery: \`npm install --global ${installSpec}\`.`);
226
+ console.error(" Manual recovery: `npm install --global impel-cli@latest`.");
239
227
  }
240
228
  const config = io.loadConfig();
241
229
  let recovered = false;
@@ -251,7 +239,7 @@ export async function cmdUpdate(argv, overrides = {}) {
251
239
  platform: io.platform,
252
240
  architecture: process.arch,
253
241
  step: "install.impel_cli",
254
- command: `npm install --global ${installSpec}`,
242
+ command: `npm install --global ${RUNTIME_BRAND.cli.packageName}@latest`,
255
243
  message: `The npm-verified global ${RUNTIME_BRAND.cli.packageName} update failed.`,
256
244
  },
257
245
  config,
@@ -277,7 +265,7 @@ export async function cmdUpdate(argv, overrides = {}) {
277
265
  }
278
266
  },
279
267
  retryStep: async () => {
280
- updateState.ok = io.selfUpdate(installSpec) === true;
268
+ updateState.ok = io.selfUpdate(updateInstallSpec()) === true;
281
269
  return updateState.ok;
282
270
  },
283
271
  },
@@ -306,7 +294,7 @@ export async function cmdUpdate(argv, overrides = {}) {
306
294
  console.error(`impel update: npm reported success but the running CLI still resolves v${postInstall ?? "?"} (expected v${remote}).`);
307
295
  console.error(` Running CLI: ${CLI_BIN}`);
308
296
  console.error(" This usually means the `impel` on PATH belongs to a different npm prefix than `npm prefix -g`.");
309
- console.error(` Fix: run \`npm prefix -g\`, confirm it owns the \`impel\` shim on PATH, then \`npm install --global ${installSpec}\` there.`);
297
+ console.error(" Fix: run `npm prefix -g`, confirm it owns the `impel` shim on PATH, then `npm install --global impel-cli@latest` there.");
310
298
  process.exitCode = 1;
311
299
  return;
312
300
  }
package/src/macSetup.js CHANGED
@@ -7,12 +7,11 @@ import { spawnSync } from "node:child_process";
7
7
  import { ensureImpelClaudeProfile, ensureImpelCodexProfile } from "./cliProfiles.js";
8
8
  import { findNativeBinary } from "./nativeProcess.js";
9
9
  import { syncSkillsSafe } from "./skills.js";
10
- import { verifyReviewedMacVendorCli } from "./vendorCliBinaries.js";
11
- import { PINNED_VENDOR_CLI_VERSIONS } from "./vendorCliVersions.js";
12
10
 
13
11
  const MAX_INSTALLER_BYTES = 512 * 1024;
14
12
  const INSTALLER_DOWNLOAD_TIMEOUT_MS = 30_000;
15
13
  const INSTALLER_RUN_TIMEOUT_MS = 10 * 60 * 1_000;
14
+ const MAC_SIGNING_TEAMS = Object.freeze({ claude: "Q6L2SF6YDW", codex: "2DC432GLL2" });
16
15
 
17
16
  /**
18
17
  * The script bytes are pinned per impel-cli release. Both vendor scripts then
@@ -20,34 +19,59 @@ const INSTALLER_RUN_TIMEOUT_MS = 10 * 60 * 1_000;
20
19
  */
21
20
  export const MAC_CLI_INSTALLERS = Object.freeze({
22
21
  claude: Object.freeze({
23
- version: PINNED_VENDOR_CLI_VERSIONS.claude,
24
22
  url: "https://claude.ai/install.sh",
25
23
  trustedOrigins: Object.freeze([
26
24
  "https://claude.ai",
27
25
  "https://downloads.claude.ai",
28
26
  ]),
29
27
  sha256: "cde4f1702d3b1695f92b73d26888364e17bca476e17f0fd676484c951d36c125",
30
- command: `curl -fsSL https://claude.ai/install.sh | bash -s ${PINNED_VENDOR_CLI_VERSIONS.claude}`,
31
- args: Object.freeze([PINNED_VENDOR_CLI_VERSIONS.claude]),
28
+ command: "curl -fsSL https://claude.ai/install.sh | bash -s stable",
29
+ args: Object.freeze(["stable"]),
32
30
  environment: Object.freeze({}),
33
31
  }),
34
32
  codex: Object.freeze({
35
- version: PINNED_VENDOR_CLI_VERSIONS.codex,
36
33
  url: "https://chatgpt.com/codex/install.sh",
37
34
  trustedOrigins: Object.freeze([
38
35
  "https://chatgpt.com",
39
36
  "https://releases.openai.com",
40
37
  ]),
41
38
  sha256: "ba92dd27e5c06f0d3bbc58bfa4b9cfb6599cd2742fbb1f92a2765e6c07dedb5a",
42
- command: `curl -fsSL https://chatgpt.com/codex/install.sh | CODEX_NON_INTERACTIVE=1 CODEX_RELEASE=${PINNED_VENDOR_CLI_VERSIONS.codex} sh`,
39
+ command: "curl -fsSL https://chatgpt.com/codex/install.sh | CODEX_NON_INTERACTIVE=1 CODEX_RELEASE=0.144.6 sh",
43
40
  args: Object.freeze([]),
44
- environment: Object.freeze({
45
- CODEX_NON_INTERACTIVE: "1",
46
- CODEX_RELEASE: PINNED_VENDOR_CLI_VERSIONS.codex,
47
- }),
41
+ environment: Object.freeze({ CODEX_NON_INTERACTIVE: "1", CODEX_RELEASE: "0.144.6" }),
48
42
  }),
49
43
  });
50
44
 
45
+ function verifyMacCli(tool, binary, environment, run = spawnSync) {
46
+ try {
47
+ const result = run(binary, ["--version"], {
48
+ encoding: "utf8",
49
+ env: environment,
50
+ stdio: ["ignore", "pipe", "pipe"],
51
+ timeout: 15_000,
52
+ });
53
+ if (result?.status !== 0 || result?.error) return false;
54
+ const explicitOverride = tool === "claude" ? environment.IMPEL_CLAUDE_BIN : environment.IMPEL_CODEX_BIN;
55
+ if (explicitOverride && path.resolve(explicitOverride) === path.resolve(binary)) return true;
56
+ const resolved = fs.realpathSync(binary);
57
+ const verified = run("/usr/bin/codesign", ["--verify", "--strict", resolved], {
58
+ encoding: "utf8",
59
+ stdio: ["ignore", "pipe", "pipe"],
60
+ timeout: 15_000,
61
+ });
62
+ if (verified?.status !== 0 || verified?.error) return false;
63
+ const details = run("/usr/bin/codesign", ["-dv", "--verbose=4", resolved], {
64
+ encoding: "utf8",
65
+ stdio: ["ignore", "pipe", "pipe"],
66
+ timeout: 15_000,
67
+ });
68
+ return details?.status === 0
69
+ && String(details.stderr || details.stdout || "").includes(`TeamIdentifier=${MAC_SIGNING_TEAMS[tool]}`);
70
+ } catch {
71
+ return false;
72
+ }
73
+ }
74
+
51
75
  function detectMacClis(find, environment, verify) {
52
76
  const detect = (tool) => find(
53
77
  tool,
@@ -176,7 +200,7 @@ export async function prepareMacClis({
176
200
  const io = {
177
201
  environment: process.env,
178
202
  find: findNativeBinary,
179
- verify: verifyReviewedMacVendorCli,
203
+ verify: verifyMacCli,
180
204
  run: spawnSync,
181
205
  fetchImpl: fetch,
182
206
  fsImpl: fs,
@@ -228,10 +252,7 @@ export async function prepareMacClis({
228
252
  await io.syncSkills({
229
253
  client: "claude",
230
254
  gatewayUrl,
231
- env: {
232
- CLAUDE_CONFIG_DIR: claudeProfile.configDir,
233
- IMPEL_CLAUDE_BIN: binaries.claude,
234
- },
255
+ env: { CLAUDE_CONFIG_DIR: claudeProfile.configDir },
235
256
  label: "Impel isolated Claude (impel claude)",
236
257
  // Lets Windows spawns use a git-safe cwd (see skillSyncSpawnDirectory).
237
258
  homeDir: os.homedir(),
@@ -241,10 +262,7 @@ export async function prepareMacClis({
241
262
  await io.syncSkills({
242
263
  client: "codex",
243
264
  gatewayUrl,
244
- env: {
245
- CODEX_HOME: codexProfile.codexHome,
246
- IMPEL_CODEX_BIN: binaries.codex,
247
- },
265
+ env: { CODEX_HOME: codexProfile.codexHome },
248
266
  label: "Impel isolated Codex (impel codex)",
249
267
  homeDir: os.homedir(),
250
268
  });
@@ -5,6 +5,7 @@ import path from "node:path";
5
5
  import { syncAgentProfilesSafe } from "./agents.js";
6
6
  import { crossAppModelsEnabled, redactSecretText } from "./config.js";
7
7
  import { ensureImpelClaudeProfile, ensureImpelCodexProfile, tenantCliProfilePaths } from "./cliProfiles.js";
8
+ import { findNativeBinary } from "./nativeProcess.js";
8
9
  import { mapWithConcurrency } from "./progress.js";
9
10
  import { registerTenantShellEntries } from "./shellEntries.js";
10
11
  import { resolveSkillsGateway, syncSkillsSafe } from "./skills.js";
@@ -16,7 +17,6 @@ import {
16
17
  } from "./tenants.js";
17
18
  import { reconcileTenantApps } from "./commands/apps.js";
18
19
  import { RUNTIME_BRAND } from "./runtimeBrand.js";
19
- import { findReviewedVendorCliBinary } from "./vendorCliBinaries.js";
20
20
 
21
21
  export function selectDefaultTenant(listing, { requested = null, currentTenantId = null } = {}) {
22
22
  const normalized = requested ? normalizeTenantId(requested) : null;
@@ -93,19 +93,13 @@ async function prepareTenantCli(config, tenant, io, binaries) {
93
93
  crossAppModels: crossAppModelsEnabled(config),
94
94
  }),
95
95
  root: (profile) => profile.configDir,
96
- env: (profile) => ({
97
- CLAUDE_CONFIG_DIR: profile.configDir,
98
- IMPEL_CLAUDE_BIN: binaries.claude,
99
- }),
96
+ env: (profile) => ({ CLAUDE_CONFIG_DIR: profile.configDir }),
100
97
  },
101
98
  codex: {
102
99
  supported: providerSupported(config.scopes, PAT_SCOPE_CODEX),
103
100
  ensure: () => io.ensureCodex(config.gatewayUrl, tenant.id),
104
101
  root: (profile) => profile.codexHome,
105
- env: (profile) => ({
106
- CODEX_HOME: profile.codexHome,
107
- IMPEL_CODEX_BIN: binaries.codex,
108
- }),
102
+ env: (profile) => ({ CODEX_HOME: profile.codexHome }),
109
103
  },
110
104
  };
111
105
  const clients = {};
@@ -187,7 +181,7 @@ export async function reconcileAllTenants({
187
181
  ensureCodex: ensureImpelCodexProfile,
188
182
  syncSkills: syncSkillsSafe,
189
183
  syncAgents: syncAgentProfilesSafe,
190
- findBinary: findReviewedVendorCliBinary,
184
+ findBinary: findNativeBinary,
191
185
  reconcileApps: reconcileTenantApps,
192
186
  registerShellEntries: registerTenantShellEntries,
193
187
  knownTenantIds: locallyKnownTenantIds,
package/src/skills.js CHANGED
@@ -25,8 +25,6 @@ import path from "node:path";
25
25
 
26
26
  import { resolveDefaultGateway, normalizeGatewayUrl } from "./config.js";
27
27
  import { findNativeBinary, nativeCommandInvocation } from "./nativeProcess.js";
28
- import { findReviewedVendorCliBinary } from "./vendorCliBinaries.js";
29
- import { PINNED_VENDOR_CLI_VERSIONS } from "./vendorCliVersions.js";
30
28
 
31
29
  /** The bundled plugin the Bifrost registry publishes; contains every served skill. */
32
30
  export const SKILL_PLUGIN_NAME = "bifrost-all-skills";
@@ -501,7 +499,6 @@ export async function syncSkills({
501
499
  platform = process.platform,
502
500
  findGit = findNativeBinary,
503
501
  homeDir = null,
504
- findVendorBinary = findReviewedVendorCliBinary,
505
502
  } = {}) {
506
503
  const spec = CLIENT_SPECS[client];
507
504
  if (!spec) {
@@ -515,18 +512,6 @@ export async function syncSkills({
515
512
  return { client, label: displayLabel, skipped: true, reason: "disabled" };
516
513
  }
517
514
 
518
- let commandBinary = spec.bin;
519
- if (platform === "darwin" && run === runSkillCommand) {
520
- commandBinary = findVendorBinary(client, { ...process.env, ...env }, platform);
521
- if (!commandBinary) {
522
- logger.warn(
523
- `impel: skipping skill sync for ${displayLabel} — reviewed ${spec.label} `
524
- + `v${PINNED_VENDOR_CLI_VERSIONS[client]} is not installed; run \`impel update\`.`,
525
- );
526
- return { client, label: displayLabel, skipped: true, reason: "binary-version-mismatch" };
527
- }
528
- }
529
-
530
515
  // Callers that manage real profiles pass their home directory so Windows
531
516
  // spawns happen from a git-safe cwd (see skillSyncSpawnDirectory).
532
517
  const spawnDirectory = skillSyncSpawnDirectory({ platform, homeDir });
@@ -536,10 +521,9 @@ export async function syncSkills({
536
521
 
537
522
  try {
538
523
  // Confirm the binary and its `plugin` subcommand exist before doing anything.
539
- const help = await runCommand(commandBinary, ["plugin", "--help"], env);
524
+ const help = await runCommand(spec.bin, ["plugin", "--help"], env);
540
525
  if (help.missing) {
541
- const location = commandBinary === spec.bin ? " on PATH" : "";
542
- logger.warn(`impel: skipping skill sync for ${displayLabel} — \`${commandBinary}\` CLI not found${location}.`);
526
+ logger.warn(`impel: skipping skill sync for ${displayLabel} — \`${spec.bin}\` CLI not found on PATH.`);
543
527
  return { client, label: displayLabel, skipped: true, reason: "binary-missing" };
544
528
  }
545
529
  if (!help.ok) {
@@ -571,12 +555,12 @@ export async function syncSkills({
571
555
  })[0];
572
556
  const registerResult = await runSkillCommandWithRetry(
573
557
  runCommand,
574
- commandBinary,
558
+ spec.bin,
575
559
  registerCommand.args,
576
560
  env,
577
561
  );
578
562
  if (registerResult.missing) {
579
- failures.push({ phase: registerCommand.phase, reason: `\`${commandBinary}\` disappeared mid-sync` });
563
+ failures.push({ phase: registerCommand.phase, reason: `\`${spec.bin}\` disappeared mid-sync` });
580
564
  } else if (!isBenign(registerResult)) {
581
565
  failures.push({
582
566
  phase: registerCommand.phase,
@@ -589,7 +573,7 @@ export async function syncSkills({
589
573
  // gives us the same dynamic name so refreshes can still use
590
574
  // PLUGIN@MARKETPLACE.
591
575
  if (!marketplaceName && !registerResult.missing) {
592
- const listed = await runCommand(commandBinary, ["plugin", "marketplace", "list", "--json"], env);
576
+ const listed = await runCommand(spec.bin, ["plugin", "marketplace", "list", "--json"], env);
593
577
  if (listed.ok) marketplaceName = resolveConfiguredMarketplaceName(listed.stdout, sourceUrl);
594
578
  }
595
579
 
@@ -601,12 +585,12 @@ export async function syncSkills({
601
585
  for (const command of commands) {
602
586
  const result = await runSkillCommandWithRetry(
603
587
  runCommand,
604
- commandBinary,
588
+ spec.bin,
605
589
  command.args,
606
590
  env,
607
591
  );
608
592
  if (result.missing) {
609
- failures.push({ phase: command.phase, reason: `\`${commandBinary}\` disappeared mid-sync` });
593
+ failures.push({ phase: command.phase, reason: `\`${spec.bin}\` disappeared mid-sync` });
610
594
  break;
611
595
  }
612
596
  if (isBenign(result)) continue;
@@ -619,7 +603,7 @@ export async function syncSkills({
619
603
  ) {
620
604
  const repair = await reregisterCodexMarketplace({
621
605
  run: runCommand,
622
- bin: commandBinary,
606
+ bin: spec.bin,
623
607
  env,
624
608
  marketplaceName,
625
609
  sourceUrl,
package/src/updates.js CHANGED
@@ -36,19 +36,8 @@ export function updateRegistry() {
36
36
  ).replace(/\/+$/u, "");
37
37
  }
38
38
 
39
- function normalizeUpdateTag(tag) {
40
- if (tag === "latest" || tag === "next") return tag;
41
- throw new Error(`unsupported npm update tag "${tag}"`);
42
- }
43
-
44
- /** Keep prerelease installs on npm's prerelease channel. */
45
- export function updateTagForVersion(version) {
46
- const parsed = validVersion(version) ? version.match(VERSION_RE) : null;
47
- return parsed?.[4] ? "next" : "latest";
48
- }
49
-
50
- export function updateInstallSpec(tag = "latest") {
51
- return `${updatePackage()}@${normalizeUpdateTag(tag)}`;
39
+ export function updateInstallSpec() {
40
+ return `${updatePackage()}@latest`;
52
41
  }
53
42
 
54
43
  export function installedVersion() {
@@ -99,17 +88,15 @@ export function isNewerVersion(candidate, current) {
99
88
  function fetchRemoteVersionWithNpm({
100
89
  packageName,
101
90
  registry,
102
- tag,
103
91
  timeoutMs,
104
92
  spawnSyncImpl = spawnSync,
105
93
  environment = process.env,
106
94
  platform = process.platform,
107
95
  }) {
108
96
  try {
109
- const target = tag === "latest" ? packageName : `${packageName}@${tag}`;
110
97
  const invocation = nativeCommandInvocation(
111
98
  "npm",
112
- ["view", target, "version", "--json", "--registry", registry],
99
+ ["view", packageName, "version", "--json", "--registry", registry],
113
100
  environment,
114
101
  platform,
115
102
  );
@@ -135,71 +122,39 @@ function fetchRemoteVersionWithNpm({
135
122
  export async function fetchRemoteVersion({
136
123
  packageName = updatePackage(),
137
124
  registry = updateRegistry(),
138
- tag = "latest",
139
125
  timeoutMs = 10_000,
140
126
  fetchImpl = null,
141
- fallbackToNpm = fetchImpl === null,
142
127
  spawnSyncImpl = spawnSync,
143
128
  environment = process.env,
144
129
  platform = process.platform,
145
130
  } = {}) {
146
- const normalizedTag = normalizeUpdateTag(tag);
147
131
  const normalizedRegistry = String(registry).replace(/\/+$/u, "");
148
132
  if (!fetchImpl && normalizedRegistry !== DEFAULT_UPDATE_REGISTRY) {
149
133
  return fetchRemoteVersionWithNpm({
150
134
  packageName,
151
135
  registry: normalizedRegistry,
152
- tag: normalizedTag,
153
- timeoutMs,
154
- spawnSyncImpl,
155
- environment,
156
- platform,
157
- });
158
- }
159
- // Windows npm commonly has proxy, certificate, or registry settings that
160
- // Node's bare fetch does not inherit. Prefer the already-working npm client
161
- // there, then retain the direct registry request as a fallback.
162
- if (!fetchImpl && platform === "win32") {
163
- const npmVersion = fetchRemoteVersionWithNpm({
164
- packageName,
165
- registry: normalizedRegistry,
166
- tag: normalizedTag,
167
136
  timeoutMs,
168
137
  spawnSyncImpl,
169
138
  environment,
170
139
  platform,
171
140
  });
172
- if (npmVersion) return npmVersion;
173
141
  }
174
142
  const controller = new AbortController();
175
143
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
176
- let remoteVersion = null;
177
144
  try {
178
145
  const encodedName = encodeURIComponent(packageName);
179
- const response = await (fetchImpl || globalThis.fetch)(`${normalizedRegistry}/${encodedName}/${normalizedTag}`, {
146
+ const response = await (fetchImpl || globalThis.fetch)(`${normalizedRegistry}/${encodedName}/latest`, {
180
147
  headers: { accept: "application/json" },
181
148
  signal: controller.signal,
182
149
  });
183
- if (response.ok) {
184
- const metadata = await response.json();
185
- remoteVersion = validVersion(metadata?.version) ? metadata.version : null;
186
- }
150
+ if (!response.ok) return null;
151
+ const metadata = await response.json();
152
+ return validVersion(metadata?.version) ? metadata.version : null;
187
153
  } catch {
188
- // npm is the authoritative fallback below. It also honors npm's proxy and
189
- // registry configuration, which is especially important on Windows.
154
+ return null;
190
155
  } finally {
191
156
  clearTimeout(timeout);
192
157
  }
193
- if (remoteVersion || !fallbackToNpm) return remoteVersion;
194
- return fetchRemoteVersionWithNpm({
195
- packageName,
196
- registry: normalizedRegistry,
197
- tag: normalizedTag,
198
- timeoutMs,
199
- spawnSyncImpl,
200
- environment,
201
- platform,
202
- });
203
158
  }
204
159
 
205
160
  export function readUpdateCache() {
@@ -243,9 +198,7 @@ export function cacheIsFresh(cache, now = Date.now()) {
243
198
  export async function refreshUpdateCache(dependencies = {}) {
244
199
  const fetchLatest = dependencies.fetchRemoteVersion || fetchRemoteVersion;
245
200
  const writeCache = dependencies.writeCache || writeUpdateCache;
246
- const currentVersion = (dependencies.installedVersion || installedVersion)();
247
- const tag = updateTagForVersion(currentVersion);
248
- const remoteVersion = await fetchLatest({ tag });
201
+ const remoteVersion = await fetchLatest();
249
202
  if (!remoteVersion) {
250
203
  try {
251
204
  writeCache({ lastFailedAt: Date.now() });
@@ -254,7 +207,7 @@ export async function refreshUpdateCache(dependencies = {}) {
254
207
  }
255
208
  return null;
256
209
  }
257
- return writeCache({ remoteVersion, updateTag: tag, checkedAt: Date.now() });
210
+ return writeCache({ remoteVersion, checkedAt: Date.now() });
258
211
  }
259
212
 
260
213
  /** One-line human notice when npm has a newer stable package. */