impel-cli 0.20.55 → 0.20.57
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 +89 -0
- package/package.json +1 -1
- package/src/apps.js +23 -117
- package/src/cli.js +6 -0
- package/src/cliProfiles.js +33 -6
- package/src/codexSecurity.js +11 -4
- package/src/commands/apps.js +43 -0
- package/src/commands/converge.js +30 -1
- package/src/commands/doctor.js +41 -3
- package/src/commands/launch.js +36 -5
- package/src/commands/models.js +160 -0
- package/src/commands/sessions.js +56 -3
- package/src/commands/setup.js +2 -0
- package/src/commands/token.js +11 -1
- package/src/commands/update.js +10 -6
- package/src/convergenceSummary.js +84 -0
- package/src/extension/index.js +2 -0
- package/src/gatewayModels.js +58 -0
- package/src/macSetup.js +3 -2
- package/src/managedProfileVersion.js +9 -1
- package/src/modelCatalog.js +109 -1
- package/src/modelSync.js +197 -0
- package/src/modelsManifest.js +132 -0
- package/src/platformSetup.js +9 -5
- package/src/provisioning.js +3 -1
- package/src/runtimeBrand.js +1 -1
- package/src/stableEntrypoint.js +2 -1
- package/src/updates.js +10 -0
- package/src/vendorCliBinaries.js +109 -9
- package/src/windowsFs.js +23 -0
- package/src/windowsSetup.js +46 -25
package/src/vendorCliBinaries.js
CHANGED
|
@@ -5,6 +5,7 @@ import { spawnSync } from "node:child_process";
|
|
|
5
5
|
import {
|
|
6
6
|
environmentValue,
|
|
7
7
|
findNativeBinary,
|
|
8
|
+
nativeSpawnInvocation,
|
|
8
9
|
resolveNativeBinary,
|
|
9
10
|
} from "./nativeProcess.js";
|
|
10
11
|
import { PINNED_VENDOR_CLI_VERSIONS } from "./vendorCliVersions.js";
|
|
@@ -99,9 +100,100 @@ export function verifyReviewedMacVendorCli(
|
|
|
99
100
|
}
|
|
100
101
|
}
|
|
101
102
|
|
|
103
|
+
/** Run `<binary> --version` through the platform-safe spawn and parse it. */
|
|
104
|
+
export function windowsVendorCliVersion(binary, environment = process.env, { run = spawnSync } = {}) {
|
|
105
|
+
try {
|
|
106
|
+
const invocation = nativeSpawnInvocation(binary, ["--version"], environment, "win32");
|
|
107
|
+
const result = run(invocation.command, invocation.args, {
|
|
108
|
+
encoding: "utf8",
|
|
109
|
+
env: environment,
|
|
110
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
111
|
+
timeout: 15_000,
|
|
112
|
+
windowsHide: true,
|
|
113
|
+
windowsVerbatimArguments: invocation.windowsVerbatimArguments,
|
|
114
|
+
});
|
|
115
|
+
if (result?.status !== 0 || result?.error) return null;
|
|
116
|
+
return parseVendorCliVersion(`${result.stdout || ""}\n${result.stderr || ""}`);
|
|
117
|
+
} catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
102
122
|
/**
|
|
103
|
-
*
|
|
104
|
-
*
|
|
123
|
+
* Accept only the exact reviewed release on Windows. Windows ships no
|
|
124
|
+
* in-box codesign equivalent for these unsigned-shim installs, so the gate
|
|
125
|
+
* is the exact version contract; explicit binary overrides remain
|
|
126
|
+
* authoritative for development and recovery, as on macOS.
|
|
127
|
+
*/
|
|
128
|
+
export function verifyReviewedWindowsVendorCli(
|
|
129
|
+
tool,
|
|
130
|
+
binary,
|
|
131
|
+
environment = process.env,
|
|
132
|
+
{
|
|
133
|
+
run = spawnSync,
|
|
134
|
+
versions = PINNED_VENDOR_CLI_VERSIONS,
|
|
135
|
+
environmentPrefix = "IMPEL",
|
|
136
|
+
} = {},
|
|
137
|
+
) {
|
|
138
|
+
if (!versions[tool]) return false;
|
|
139
|
+
try {
|
|
140
|
+
const override = vendorOverrideName(tool, environmentPrefix);
|
|
141
|
+
const overriddenBinary = override ? environmentValue(environment, override) : null;
|
|
142
|
+
if (overriddenBinary && path.resolve(overriddenBinary) === path.resolve(binary)) return true;
|
|
143
|
+
return windowsVendorCliVersion(binary, environment, { run }) === versions[tool];
|
|
144
|
+
} catch {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function reviewedWindowsClaudeFallback(environment, environmentPrefix, accept, { find, realpath, stat }) {
|
|
150
|
+
// The native Windows installer keeps releases side by side under
|
|
151
|
+
// .local/share/claude/versions/<release> and its auto-updater replaces the
|
|
152
|
+
// .local/bin launcher in place. When the launcher drifts past the reviewed
|
|
153
|
+
// release, resolve the exact reviewed release directly instead of failing
|
|
154
|
+
// the launch; every candidate still passes the version verification.
|
|
155
|
+
const candidates = [];
|
|
156
|
+
const drifted = find("claude", environment, "win32", environmentPrefix);
|
|
157
|
+
if (drifted) {
|
|
158
|
+
try {
|
|
159
|
+
candidates.push(path.win32.join(
|
|
160
|
+
path.win32.dirname(realpath(drifted)),
|
|
161
|
+
"..", "share", "claude", "versions", PINNED_VENDOR_CLI_VERSIONS.claude,
|
|
162
|
+
));
|
|
163
|
+
} catch {
|
|
164
|
+
// An unresolvable launcher only forfeits the derived candidate.
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// USERPROFILE gates the fixed-root candidates: it is only set on real
|
|
168
|
+
// Windows sessions, so cross-platform tests injecting fake finders never
|
|
169
|
+
// fall through to a genuine macOS side-by-side install. HOME is probed too
|
|
170
|
+
// because the vendor updater follows HOME when it diverges from USERPROFILE.
|
|
171
|
+
const userProfile = environmentValue(environment, "USERPROFILE");
|
|
172
|
+
if (userProfile) {
|
|
173
|
+
const homes = [userProfile];
|
|
174
|
+
const home = environmentValue(environment, "HOME");
|
|
175
|
+
if (home && home !== userProfile) homes.push(home);
|
|
176
|
+
for (const root of homes) {
|
|
177
|
+
candidates.push(path.win32.join(root, ".local", "share", "claude", "versions", PINNED_VENDOR_CLI_VERSIONS.claude));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
for (const base of candidates) {
|
|
181
|
+
// The release file is stored bare on every platform, but probe the .exe
|
|
182
|
+
// sibling too in case the installer ever normalizes Windows names.
|
|
183
|
+
for (const candidate of [base, `${base}.exe`]) {
|
|
184
|
+
try {
|
|
185
|
+
if (stat(candidate).isFile() && accept(candidate)) return candidate;
|
|
186
|
+
} catch {
|
|
187
|
+
// A missing side-by-side release keeps resolution fail-closed.
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Locate the reviewed vendor CLI on macOS and Windows. Other platforms
|
|
196
|
+
* intentionally preserve the existing native resolution behavior.
|
|
105
197
|
*/
|
|
106
198
|
export function findReviewedVendorCliBinary(
|
|
107
199
|
tool,
|
|
@@ -110,13 +202,17 @@ export function findReviewedVendorCliBinary(
|
|
|
110
202
|
environmentPrefix = "IMPEL",
|
|
111
203
|
{
|
|
112
204
|
find = findNativeBinary,
|
|
113
|
-
verify =
|
|
205
|
+
verify = null,
|
|
114
206
|
realpath = fs.realpathSync,
|
|
115
207
|
stat = fs.statSync,
|
|
116
208
|
} = {},
|
|
117
209
|
) {
|
|
118
|
-
if (platform !== "darwin"
|
|
119
|
-
|
|
210
|
+
if (platform !== "darwin" && platform !== "win32") {
|
|
211
|
+
return find(tool, environment, platform, environmentPrefix);
|
|
212
|
+
}
|
|
213
|
+
const verifier = verify
|
|
214
|
+
|| (platform === "win32" ? verifyReviewedWindowsVendorCli : verifyReviewedMacVendorCli);
|
|
215
|
+
const accept = (binary) => verifier(tool, binary, environment, { environmentPrefix });
|
|
120
216
|
const reviewed = find(tool, environment, platform, environmentPrefix, accept);
|
|
121
217
|
if (reviewed || tool !== "claude") return reviewed;
|
|
122
218
|
// An explicit override remains authoritative even while unusable: callers
|
|
@@ -124,6 +220,10 @@ export function findReviewedVendorCliBinary(
|
|
|
124
220
|
const override = vendorOverrideName(tool, environmentPrefix);
|
|
125
221
|
if (override && environmentValue(environment, override)) return null;
|
|
126
222
|
|
|
223
|
+
if (platform === "win32") {
|
|
224
|
+
return reviewedWindowsClaudeFallback(environment, environmentPrefix, accept, { find, realpath, stat });
|
|
225
|
+
}
|
|
226
|
+
|
|
127
227
|
// The standalone Claude installer keeps releases side by side under
|
|
128
228
|
// versions/<release> and its auto-updater only moves the launcher symlink.
|
|
129
229
|
// When the symlink drifts past the reviewed release, resolve the exact
|
|
@@ -153,9 +253,9 @@ export function findReviewedVendorCliBinary(
|
|
|
153
253
|
}
|
|
154
254
|
|
|
155
255
|
/**
|
|
156
|
-
* Resolve a launch target. macOS deliberately
|
|
157
|
-
* unreviewed or version-drifted binary exists so callers can request
|
|
158
|
-
* instead of silently launching it.
|
|
256
|
+
* Resolve a launch target. macOS and Windows deliberately return null when
|
|
257
|
+
* only an unreviewed or version-drifted binary exists so callers can request
|
|
258
|
+
* repair instead of silently launching it.
|
|
159
259
|
*/
|
|
160
260
|
export function resolveReviewedVendorCliBinary(
|
|
161
261
|
tool,
|
|
@@ -163,7 +263,7 @@ export function resolveReviewedVendorCliBinary(
|
|
|
163
263
|
platform = process.platform,
|
|
164
264
|
environmentPrefix = "IMPEL",
|
|
165
265
|
) {
|
|
166
|
-
if (platform !== "darwin") {
|
|
266
|
+
if (platform !== "darwin" && platform !== "win32") {
|
|
167
267
|
return resolveNativeBinary(tool, environment, platform, environmentPrefix);
|
|
168
268
|
}
|
|
169
269
|
return findReviewedVendorCliBinary(tool, environment, platform, environmentPrefix);
|
package/src/windowsFs.js
CHANGED
|
@@ -42,3 +42,26 @@ export function renameWithWindowsRetry(from, to, {
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
+
|
|
46
|
+
// chmod on Windows only toggles the FAT-era read-only attribute — it cannot
|
|
47
|
+
// deliver the owner-only guarantee the POSIX callers rely on, and it fails
|
|
48
|
+
// EPERM whenever the file is momentarily held by antivirus or carries an
|
|
49
|
+
// inherited deny ACL (observed in the field: `setup.shared_vendor_clis`
|
|
50
|
+
// aborting a whole tenant convergence over chmod of a managed models.json).
|
|
51
|
+
// On POSIX the mode bits are a real security property, so failures there
|
|
52
|
+
// still throw unless the caller explicitly opts into best-effort.
|
|
53
|
+
|
|
54
|
+
/** chmod that swallows Windows failures; POSIX failures throw unless bestEffort. */
|
|
55
|
+
export function chmodWithPlatformPolicy(target, mode, {
|
|
56
|
+
platform = process.platform,
|
|
57
|
+
chmod = fs.chmodSync,
|
|
58
|
+
bestEffort = false,
|
|
59
|
+
} = {}) {
|
|
60
|
+
try {
|
|
61
|
+
chmod(target, mode);
|
|
62
|
+
return true;
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if (platform === "win32" || bestEffort) return false;
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
}
|
package/src/windowsSetup.js
CHANGED
|
@@ -5,9 +5,13 @@ import { ensureImpelClaudeProfile, ensureImpelCodexProfile } from "./cliProfiles
|
|
|
5
5
|
import {
|
|
6
6
|
findNativeBinary,
|
|
7
7
|
nativeCommandInvocation,
|
|
8
|
-
nativeSpawnInvocation,
|
|
9
8
|
} from "./nativeProcess.js";
|
|
10
9
|
import { syncSkillsSafe } from "./skills.js";
|
|
10
|
+
import {
|
|
11
|
+
findReviewedVendorCliBinary,
|
|
12
|
+
verifyReviewedWindowsVendorCli,
|
|
13
|
+
windowsVendorCliVersion,
|
|
14
|
+
} from "./vendorCliBinaries.js";
|
|
11
15
|
import { PINNED_VENDOR_CLI_VERSIONS } from "./vendorCliVersions.js";
|
|
12
16
|
import { provisionWindowsGit } from "./windowsGit.js";
|
|
13
17
|
|
|
@@ -47,37 +51,49 @@ export function windowsCliInstallCommands(tools = Object.keys(WINDOWS_CLI_INSTAL
|
|
|
47
51
|
}));
|
|
48
52
|
}
|
|
49
53
|
|
|
50
|
-
function verifyWindowsCli(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
timeout: 15_000,
|
|
58
|
-
windowsHide: true,
|
|
59
|
-
windowsVerbatimArguments: invocation.windowsVerbatimArguments,
|
|
60
|
-
});
|
|
61
|
-
return result?.status === 0 && !result?.error;
|
|
62
|
-
} catch {
|
|
63
|
-
return false;
|
|
64
|
-
}
|
|
54
|
+
function verifyWindowsCli(tool, binary, environment) {
|
|
55
|
+
// The exact reviewed release, matching the macOS contract: a runnable but
|
|
56
|
+
// version-drifted vendor CLI must count as missing so setup/update repair
|
|
57
|
+
// it, instead of launching an unreviewed build against the gateway
|
|
58
|
+
// (observed in the field: Windows Claude auto-updated past the pin and the
|
|
59
|
+
// gateway rejected the session while macOS stayed on the reviewed release).
|
|
60
|
+
return verifyReviewedWindowsVendorCli(tool, binary, environment);
|
|
65
61
|
}
|
|
66
62
|
|
|
67
63
|
function detectWindowsClis(find, environment, verify) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
64
|
+
// Detection must share the launcher's reviewed resolution (including the
|
|
65
|
+
// side-by-side fallback after vendor auto-update drift); a raw PATH probe
|
|
66
|
+
// here would classify a drifted-but-launchable install as missing and
|
|
67
|
+
// reinstall it on every setup/update pass.
|
|
68
|
+
const detect = (tool) => findReviewedVendorCliBinary(tool, environment, "win32", "IMPEL", {
|
|
69
|
+
find,
|
|
70
|
+
verify: (candidateTool, binary, candidateEnvironment) =>
|
|
71
|
+
verify(candidateTool, binary, candidateEnvironment),
|
|
72
|
+
});
|
|
75
73
|
return {
|
|
76
74
|
claude: detect("claude"),
|
|
77
75
|
codex: detect("codex"),
|
|
78
76
|
};
|
|
79
77
|
}
|
|
80
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Pin-agnostic probe for the drift diagnostic: which build is actually on
|
|
81
|
+
* this machine when the reviewed detection came back empty.
|
|
82
|
+
*/
|
|
83
|
+
function detectDriftedWindowsClis(find, environment, binaries, { versionOf = windowsVendorCliVersion } = {}) {
|
|
84
|
+
const drifted = {};
|
|
85
|
+
for (const tool of ["claude", "codex"]) {
|
|
86
|
+
if (binaries[tool]) continue;
|
|
87
|
+
const binary = find(tool, environment, "win32", "IMPEL");
|
|
88
|
+
if (!binary) continue;
|
|
89
|
+
const version = versionOf(binary, environment);
|
|
90
|
+
if (version && version !== PINNED_VENDOR_CLI_VERSIONS[tool]) {
|
|
91
|
+
drifted[tool] = { binary, version, pinned: PINNED_VENDOR_CLI_VERSIONS[tool] };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return drifted;
|
|
95
|
+
}
|
|
96
|
+
|
|
81
97
|
function installCli(tool, { environment, run }) {
|
|
82
98
|
const installer = WINDOWS_CLI_INSTALLERS[tool];
|
|
83
99
|
if (!installer) throw new Error(`unsupported Windows CLI installer: ${tool}`);
|
|
@@ -127,11 +143,13 @@ export async function prepareWindowsClis({
|
|
|
127
143
|
skipInstall = false,
|
|
128
144
|
inspectOnly = false,
|
|
129
145
|
installTools = ["claude", "codex"],
|
|
146
|
+
crossAppModels = false,
|
|
130
147
|
} = {}, dependencies = {}) {
|
|
131
148
|
const io = {
|
|
132
149
|
environment: process.env,
|
|
133
150
|
find: findNativeBinary,
|
|
134
151
|
verify: verifyWindowsCli,
|
|
152
|
+
versionOf: windowsVendorCliVersion,
|
|
135
153
|
run: spawnSync,
|
|
136
154
|
ensureClaudeProfile: ensureImpelClaudeProfile,
|
|
137
155
|
ensureCodexProfile: ensureImpelCodexProfile,
|
|
@@ -164,6 +182,7 @@ export async function prepareWindowsClis({
|
|
|
164
182
|
binaries: before,
|
|
165
183
|
missingBefore,
|
|
166
184
|
missingAfter: [...missingBefore],
|
|
185
|
+
drifted: detectDriftedWindowsClis(io.find, io.environment, before, { versionOf: io.versionOf }),
|
|
167
186
|
installAttempted: false,
|
|
168
187
|
installSucceeded: null,
|
|
169
188
|
installFailure: null,
|
|
@@ -219,8 +238,9 @@ export async function prepareWindowsClis({
|
|
|
219
238
|
const binaries = installAttempted
|
|
220
239
|
? detectWindowsClis(io.find, io.environment, io.verify)
|
|
221
240
|
: before;
|
|
222
|
-
const
|
|
223
|
-
const
|
|
241
|
+
const drifted = detectDriftedWindowsClis(io.find, io.environment, binaries, { versionOf: io.versionOf });
|
|
242
|
+
const claudeProfile = io.ensureClaudeProfile(gatewayUrl, tenantId, { crossAppModels });
|
|
243
|
+
const codexProfile = io.ensureCodexProfile(gatewayUrl, tenantId, { crossAppModels });
|
|
224
244
|
|
|
225
245
|
if (binaries.claude) {
|
|
226
246
|
await io.syncSkills({
|
|
@@ -248,6 +268,7 @@ export async function prepareWindowsClis({
|
|
|
248
268
|
binaries,
|
|
249
269
|
missingBefore,
|
|
250
270
|
missingAfter: Object.entries(binaries).filter(([, binary]) => !binary).map(([tool]) => tool),
|
|
271
|
+
drifted,
|
|
251
272
|
installAttempted,
|
|
252
273
|
installSucceeded,
|
|
253
274
|
installFailure,
|