impel-cli 0.17.6 → 0.17.7
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 +93 -32
- package/src/commands/apps.js +31 -11
package/package.json
CHANGED
package/src/apps.js
CHANGED
|
@@ -41,19 +41,26 @@ const APP_DEFINITIONS = {
|
|
|
41
41
|
// CLI release therefore installs and accepts only the exact app builds tested
|
|
42
42
|
// with its compatibility patches. Updating a pin requires a new CLI release.
|
|
43
43
|
export const PINNED_VENDOR_APPS = Object.freeze({
|
|
44
|
+
// 1.22209.3 (the previous pin) routes every managed 3P gateway profile into
|
|
45
|
+
// the Claude Code marketing page at app://localhost/code/disabled: the
|
|
46
|
+
// desktop Code surface never becomes available, the hardcoded-off
|
|
47
|
+
// claude_code_web gate traps /code/*, and the page's "Back to Claude" link
|
|
48
|
+
// targets "/" so the persisted Code mode loops straight back. 1.20186.9 is
|
|
49
|
+
// the last build verified working end-to-end for gateway profiles. Do not
|
|
50
|
+
// re-upgrade without a verified fix for the Code-surface routing contract.
|
|
44
51
|
claude: Object.freeze({
|
|
45
|
-
version: "1.
|
|
46
|
-
claudeCodeVersion: "2.1.
|
|
47
|
-
claudeCodeCommit: "
|
|
52
|
+
version: "1.20186.9",
|
|
53
|
+
claudeCodeVersion: "2.1.209",
|
|
54
|
+
claudeCodeCommit: "0fe048596fd45e79e99353cbe2c3d1b1ac069568",
|
|
48
55
|
bundleName: "Claude.app",
|
|
49
56
|
downloads: Object.freeze({
|
|
50
57
|
arm64: Object.freeze({
|
|
51
|
-
url: "https://downloads.claude.ai/releases/darwin/universal/1.
|
|
52
|
-
sha256: "
|
|
58
|
+
url: "https://downloads.claude.ai/releases/darwin/universal/1.20186.9/Claude-69f150a4c9316d5c8cd7b9f130ed583d15c0383e.zip",
|
|
59
|
+
sha256: "9364648c74bd525aa293a28103eb37262a5553de21c7ca907766d7fc40623015",
|
|
53
60
|
}),
|
|
54
61
|
x64: Object.freeze({
|
|
55
|
-
url: "https://downloads.claude.ai/releases/darwin/universal/1.
|
|
56
|
-
sha256: "
|
|
62
|
+
url: "https://downloads.claude.ai/releases/darwin/universal/1.20186.9/Claude-69f150a4c9316d5c8cd7b9f130ed583d15c0383e.zip",
|
|
63
|
+
sha256: "9364648c74bd525aa293a28103eb37262a5553de21c7ca907766d7fc40623015",
|
|
57
64
|
}),
|
|
58
65
|
}),
|
|
59
66
|
}),
|
|
@@ -85,10 +92,12 @@ const ULTRA_REASONING_LEVEL = { effort: "ultra", description: "Maximum reasoning
|
|
|
85
92
|
const FAST_SERVICE_TIER = { id: "priority", name: "Fast", description: "1.5x speed, increased usage" };
|
|
86
93
|
const FAST_MODE_AUTH_GATE = /([A-Za-z_$][\w$]*)=([A-Za-z_$][\w$]*)&&!([A-Za-z_$][\w$]*)&&([A-Za-z_$][\w$]*)!=null&&\4\?\.requirements\?\.featureRequirements\?\.fast_mode!==!1/gu;
|
|
87
94
|
const CHATGPT_RESOURCE_ROOT = "linked_vendor_resources_with_patched_asar";
|
|
88
|
-
const CLAUDE_PLAN_USAGE_GUARD = "if(!
|
|
89
|
-
const IMPEL_CLAUDE_PLAN_USAGE_GUARD =
|
|
90
|
-
const
|
|
91
|
-
const
|
|
95
|
+
const CLAUDE_PLAN_USAGE_GUARD = "if(!Ze().hasOrgPolicyBackend())return;if((r=fr())";
|
|
96
|
+
const IMPEL_CLAUDE_PLAN_USAGE_GUARD = `${"if(!wze())return;".padEnd("if(!Ze().hasOrgPolicyBackend())return;".length, " ")}if((r=fr())`;
|
|
97
|
+
const CLAUDE_PLAN_USAGE_REQUEST = 'P.net.fetch(`${ht()}/api/organizations/${o}/usage`,{signal:AbortSignal.timeout(mcr)})';
|
|
98
|
+
const IMPEL_CLAUDE_PLAN_USAGE_REQUEST = 'P.net.fetch("https://gateway.useimpel.com/ui/a/u",{headers:{key:dR(Oe()).apiKey}})'.padEnd(CLAUDE_PLAN_USAGE_REQUEST.length, " ");
|
|
99
|
+
const CLAUDE_DRAFT_AGENT_MENTIONS = 'agentMentionsEnabled:"draft"===Be&&s';
|
|
100
|
+
const IMPEL_CLAUDE_BOUND_AGENT_MENTIONS = 'agentMentionsEnabled:"draft"===Be?s:Le?.agent??!1';
|
|
92
101
|
const CLAUDE_AGENT_MENTION_FILTER = 'return e.filter(e=>"user"===e.source&&(!s||e.name.toLowerCase().includes(s))).slice(0,10)';
|
|
93
102
|
const IMPEL_CLAUDE_BOUND_AGENT_MENTION_FILTER = 'return e.filter(e=>"user"===e.source&&(!s||e.name.toLowerCase().includes(s))&&("string"!=typeof y||e.name===y)).slice(0,10)';
|
|
94
103
|
const CLAUDE_AGENT_MENTION_SELECT = 'onSelect:t=>(e(String(t)),Promise.resolve(null))';
|
|
@@ -174,7 +183,7 @@ export const CURRENT_CONFIG_VERSION = 17;
|
|
|
174
183
|
// — which is what made every `impel update` re-trigger macOS permission
|
|
175
184
|
// prompts. Bump this ONLY when a code change alters the bytes of a built
|
|
176
185
|
// bundle; leave it alone for changes that don't touch bundle contents.
|
|
177
|
-
export const BUNDLE_BUILD_FINGERPRINT = "bundle-2026-07-
|
|
186
|
+
export const BUNDLE_BUILD_FINGERPRINT = "bundle-2026-07-21.1";
|
|
178
187
|
|
|
179
188
|
/** Parse the tenant's install manifest, or null when absent/corrupt. */
|
|
180
189
|
export function readTenantManifest(homeDir = os.homedir(), tenantId = null) {
|
|
@@ -245,6 +254,7 @@ export function bundleIsCurrent(status, {
|
|
|
245
254
|
compatibility.patches?.followupBoundAgentEnable === 1
|
|
246
255
|
&& compatibility.patches?.followupBoundAgentFilter === 1
|
|
247
256
|
&& compatibility.patches?.followupBoundAgentChip === 1
|
|
257
|
+
&& claudePatchSetMatchesCompatibility(compatibility)
|
|
248
258
|
&& managedClaudeRendererMatchesCompatibility(status, compatibility)
|
|
249
259
|
))
|
|
250
260
|
&& (status.target !== "chatgpt" || (
|
|
@@ -278,6 +288,36 @@ function wrapperLaunchesOnTahoe(status) {
|
|
|
278
288
|
}
|
|
279
289
|
}
|
|
280
290
|
|
|
291
|
+
// The exact patch and renderer-asset groups this CLI build writes. A bundle
|
|
292
|
+
// whose compatibility metadata records a DIFFERENT set — even a superset — was
|
|
293
|
+
// built by different patching logic (e.g. a dev working tree that reused the
|
|
294
|
+
// released fingerprint), so it must be reported stale and rebuilt rather than
|
|
295
|
+
// trusted to behave like a reviewed release.
|
|
296
|
+
const CLAUDE_COMPATIBILITY_PATCH_KEYS = Object.freeze([
|
|
297
|
+
"aggregatePlanUsageGuard",
|
|
298
|
+
"aggregatePlanUsageRequest",
|
|
299
|
+
"safeStorageName",
|
|
300
|
+
"followupBoundAgentEnable",
|
|
301
|
+
"followupBoundAgentFilter",
|
|
302
|
+
"followupBoundAgentChip",
|
|
303
|
+
]);
|
|
304
|
+
const CLAUDE_COMPATIBILITY_RENDERER_GROUPS = Object.freeze(["agentMentions"]);
|
|
305
|
+
|
|
306
|
+
function claudePatchSetMatchesCompatibility(compatibility) {
|
|
307
|
+
try {
|
|
308
|
+
const patchKeys = Object.keys(compatibility.patches ?? {}).sort();
|
|
309
|
+
const rendererGroups = Object.keys(compatibility.rendererAssets ?? {}).sort();
|
|
310
|
+
return (
|
|
311
|
+
patchKeys.length === CLAUDE_COMPATIBILITY_PATCH_KEYS.length
|
|
312
|
+
&& [...CLAUDE_COMPATIBILITY_PATCH_KEYS].sort().every((key, index) => patchKeys[index] === key)
|
|
313
|
+
&& rendererGroups.length === CLAUDE_COMPATIBILITY_RENDERER_GROUPS.length
|
|
314
|
+
&& [...CLAUDE_COMPATIBILITY_RENDERER_GROUPS].sort().every((key, index) => rendererGroups[index] === key)
|
|
315
|
+
);
|
|
316
|
+
} catch {
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
281
321
|
function managedClaudeRendererMatchesCompatibility(status, compatibility) {
|
|
282
322
|
try {
|
|
283
323
|
const renderers = compatibility.rendererAssets?.agentMentions;
|
|
@@ -618,27 +658,13 @@ export function installManagedAppFiles({
|
|
|
618
658
|
|
|
619
659
|
const installed = [];
|
|
620
660
|
for (const target of targets) {
|
|
621
|
-
if (target === "claude") {
|
|
622
|
-
migrateLegacyClaudeAppSessions(paths.claude.userData, homeDir);
|
|
623
|
-
writeClaudeNative3PSelection(paths);
|
|
624
|
-
writeClaudeCodeSettings(paths);
|
|
625
|
-
writeClaudeConfig(paths, config, models);
|
|
626
|
-
ensureClaudeSessionHooks(paths.claude.userData, config.tenantId, "claude_desktop");
|
|
627
|
-
}
|
|
628
|
-
else {
|
|
629
|
-
writeChatGPTConfig(
|
|
630
|
-
paths,
|
|
631
|
-
config,
|
|
632
|
-
models,
|
|
633
|
-
vendorCodexModels,
|
|
634
|
-
chatgptInvocations,
|
|
635
|
-
generatedAt,
|
|
636
|
-
);
|
|
637
|
-
ensureCodexSessionHooks(paths.chatgpt.codexHome, config.tenantId, "codex_desktop");
|
|
638
|
-
}
|
|
639
661
|
// Non-bundle targets are the background-refresh / fast-open path: configs,
|
|
640
662
|
// token helper, catalog, and manifest only. Bundle swaps require the app
|
|
641
663
|
// to be closed (see quitBlockingApps), so those paths never attempt one.
|
|
664
|
+
// A stale bundle is rebuilt BEFORE its profile config is rewritten: the
|
|
665
|
+
// generated config is only reviewed against the pinned vendor build, so a
|
|
666
|
+
// failed rebuild must leave the old bundle paired with its old config
|
|
667
|
+
// rather than stranding it with a contract written for a different app.
|
|
642
668
|
if (bundleTargets.has(target)) {
|
|
643
669
|
if (resolvedVendorPaths[target] && isVendableVendorApp(target, resolvedVendorPaths[target])) {
|
|
644
670
|
assertPinnedVendorAppVersion(target, resolvedVendorPaths[target]);
|
|
@@ -656,6 +682,24 @@ export function installManagedAppFiles({
|
|
|
656
682
|
fs.rmSync(appPaths(homeDir)[target].launcher, { recursive: true, force: true });
|
|
657
683
|
}
|
|
658
684
|
}
|
|
685
|
+
if (target === "claude") {
|
|
686
|
+
migrateLegacyClaudeAppSessions(paths.claude.userData, homeDir);
|
|
687
|
+
writeClaudeNative3PSelection(paths);
|
|
688
|
+
writeClaudeCodeSettings(paths);
|
|
689
|
+
writeClaudeConfig(paths, config, models);
|
|
690
|
+
ensureClaudeSessionHooks(paths.claude.userData, config.tenantId, "claude_desktop");
|
|
691
|
+
}
|
|
692
|
+
else {
|
|
693
|
+
writeChatGPTConfig(
|
|
694
|
+
paths,
|
|
695
|
+
config,
|
|
696
|
+
models,
|
|
697
|
+
vendorCodexModels,
|
|
698
|
+
chatgptInvocations,
|
|
699
|
+
generatedAt,
|
|
700
|
+
);
|
|
701
|
+
ensureCodexSessionHooks(paths.chatgpt.codexHome, config.tenantId, "codex_desktop");
|
|
702
|
+
}
|
|
659
703
|
installed.push({
|
|
660
704
|
target,
|
|
661
705
|
launcher: paths[target].launcher,
|
|
@@ -1672,6 +1716,23 @@ function patchFixedWidthAsarString(asarPath, original, replacement, label) {
|
|
|
1672
1716
|
function patchClaudePlanUsageRequest(asarPath) {
|
|
1673
1717
|
const archive = fs.readFileSync(asarPath);
|
|
1674
1718
|
const source = archive.toString("latin1");
|
|
1719
|
+
const legacyPatches = [];
|
|
1720
|
+
for (
|
|
1721
|
+
let offset = source.indexOf(CLAUDE_PLAN_USAGE_REQUEST);
|
|
1722
|
+
offset !== -1;
|
|
1723
|
+
offset = source.indexOf(CLAUDE_PLAN_USAGE_REQUEST, offset + CLAUDE_PLAN_USAGE_REQUEST.length)
|
|
1724
|
+
) {
|
|
1725
|
+
legacyPatches.push({
|
|
1726
|
+
offset,
|
|
1727
|
+
original: CLAUDE_PLAN_USAGE_REQUEST,
|
|
1728
|
+
replacement: IMPEL_CLAUDE_PLAN_USAGE_REQUEST,
|
|
1729
|
+
});
|
|
1730
|
+
}
|
|
1731
|
+
if (legacyPatches.length > 0) {
|
|
1732
|
+
return applyFixedWidthAsarPatches(
|
|
1733
|
+
asarPath, archive, legacyPatches, "Claude plan-usage gateway request",
|
|
1734
|
+
);
|
|
1735
|
+
}
|
|
1675
1736
|
const credentialAccessor = source.match(
|
|
1676
1737
|
/function ([A-Za-z_$][\w$]*)\(e\)\{var t;return [\s\S]{0,500}?\[custom-3p\] Credentials loaded from managed config/u,
|
|
1677
1738
|
)?.[1];
|
|
@@ -1687,10 +1748,10 @@ function patchClaudePlanUsageRequest(asarPath) {
|
|
|
1687
1748
|
return applyFixedWidthAsarPatches(asarPath, archive, [], "Claude plan-usage gateway request");
|
|
1688
1749
|
}
|
|
1689
1750
|
|
|
1690
|
-
const requestPattern = /
|
|
1751
|
+
const requestPattern = /([A-Za-z_$][\w$]*)\.net\.fetch\(`\$\{[A-Za-z_$][\w$]*\(\)\}\/api\/organizations\/\$\{[A-Za-z_$][\w$]*\}\/usage`,\{signal:AbortSignal\.timeout\([A-Za-z_$][\w$]*\)\}\)/gu;
|
|
1691
1752
|
const patches = [...source.matchAll(requestPattern)].map((match) => {
|
|
1692
1753
|
const original = match[0];
|
|
1693
|
-
const replacement =
|
|
1754
|
+
const replacement = `${match[1]}.net.fetch("https://gateway.useimpel.com/ui/a/u",{headers:{key:${credentialAccessor}(${configAccessor}()).apiKey}})`;
|
|
1694
1755
|
if (replacement.length > original.length) {
|
|
1695
1756
|
throw new Error("Claude plan-usage gateway request replacement no longer fits the vendor ASAR contract");
|
|
1696
1757
|
}
|
package/src/commands/apps.js
CHANGED
|
@@ -772,7 +772,7 @@ export async function cmdApps(argv, overrides = {}) {
|
|
|
772
772
|
return refreshApps(targets, {
|
|
773
773
|
staleOnly: Boolean(flags["stale-only"]),
|
|
774
774
|
tenantId: flags.tenant || null,
|
|
775
|
-
});
|
|
775
|
+
}, overrides);
|
|
776
776
|
}
|
|
777
777
|
if (action === "uninstall") {
|
|
778
778
|
const config = loadConfig();
|
|
@@ -1163,8 +1163,20 @@ function claudeConfigIsCurrent(configPath, config, gatewayUrl) {
|
|
|
1163
1163
|
// run (config writes only, never a bundle swap, never quits anything) and
|
|
1164
1164
|
// silent on any failure: it is invoked detached from the apps' token helper,
|
|
1165
1165
|
// so there is no user to talk to and nothing may leak to stdout.
|
|
1166
|
-
async function refreshApps(targets, { staleOnly = false, tenantId = null } = {}) {
|
|
1167
|
-
const
|
|
1166
|
+
async function refreshApps(targets, { staleOnly = false, tenantId = null } = {}, overrides = {}) {
|
|
1167
|
+
const io = {
|
|
1168
|
+
loadStoredConfig: loadConfig,
|
|
1169
|
+
selectedConfig: selectedAppConfig,
|
|
1170
|
+
status: appStatus,
|
|
1171
|
+
bundleCurrent: bundleIsCurrent,
|
|
1172
|
+
fetchModels: fetchGatewayModels,
|
|
1173
|
+
installFiles: installManagedAppFiles,
|
|
1174
|
+
syncSkills: syncSkillsSafe,
|
|
1175
|
+
syncAgents: syncAgentProfilesSafe,
|
|
1176
|
+
secureCodexHome: secureManagedCodexHome,
|
|
1177
|
+
...overrides,
|
|
1178
|
+
};
|
|
1179
|
+
const stored = io.loadStoredConfig();
|
|
1168
1180
|
if (!stored?.pat) return;
|
|
1169
1181
|
const requestedTenantId = tenantId
|
|
1170
1182
|
? normalizeTenantId(tenantId)
|
|
@@ -1181,17 +1193,25 @@ async function refreshApps(targets, { staleOnly = false, tenantId = null } = {})
|
|
|
1181
1193
|
|
|
1182
1194
|
let config;
|
|
1183
1195
|
try {
|
|
1184
|
-
config = await
|
|
1196
|
+
config = await io.selectedConfig(targets, tenantId ? requestedTenantId : null);
|
|
1185
1197
|
} catch {
|
|
1186
1198
|
return;
|
|
1187
1199
|
}
|
|
1188
|
-
|
|
1189
|
-
|
|
1200
|
+
// Refresh only targets whose installed bundle was built by THIS CLI's
|
|
1201
|
+
// bundle logic. The generated config contract is reviewed against the
|
|
1202
|
+
// pinned vendor build, so rewriting configs (and advancing the manifest)
|
|
1203
|
+
// under a stale bundle would strand an old app with a config written for a
|
|
1204
|
+
// different app version. Stale bundles wait for the next foreground
|
|
1205
|
+
// install/open/update, which swaps bundle and config together.
|
|
1206
|
+
const statuses = io.status(targets, os.homedir(), config.tenantId, config.tenantName);
|
|
1207
|
+
const installedTargets = statuses.filter((status) => (
|
|
1208
|
+
status.launcherInstalled && io.bundleCurrent(status)
|
|
1209
|
+
));
|
|
1190
1210
|
if (installedTargets.length === 0) return;
|
|
1191
1211
|
|
|
1192
1212
|
let catalog;
|
|
1193
1213
|
try {
|
|
1194
|
-
catalog = await
|
|
1214
|
+
catalog = await io.fetchModels(config);
|
|
1195
1215
|
} catch {
|
|
1196
1216
|
return;
|
|
1197
1217
|
}
|
|
@@ -1204,7 +1224,7 @@ async function refreshApps(targets, { staleOnly = false, tenantId = null } = {})
|
|
|
1204
1224
|
supportedTargetSet.has(status.target)
|
|
1205
1225
|
));
|
|
1206
1226
|
if (supportedStatuses.length === 0) return;
|
|
1207
|
-
|
|
1227
|
+
io.installFiles({
|
|
1208
1228
|
config,
|
|
1209
1229
|
targets: supportedStatuses.map((status) => status.target),
|
|
1210
1230
|
models: catalog.models,
|
|
@@ -1217,10 +1237,10 @@ async function refreshApps(targets, { staleOnly = false, tenantId = null } = {})
|
|
|
1217
1237
|
const gatewayUrl = resolveSkillsGateway(config.gatewayUrl);
|
|
1218
1238
|
for (const status of supportedStatuses) {
|
|
1219
1239
|
const { client, env, label } = appSkillTarget(status.target, tenantPaths);
|
|
1220
|
-
await
|
|
1221
|
-
if (status.target === "chatgpt")
|
|
1240
|
+
await io.syncSkills({ client, gatewayUrl, env, label });
|
|
1241
|
+
if (status.target === "chatgpt") io.secureCodexHome(tenantPaths.chatgpt.codexHome);
|
|
1222
1242
|
}
|
|
1223
|
-
await
|
|
1243
|
+
await io.syncAgents({
|
|
1224
1244
|
profiles: supportedStatuses.map((status) => appAgentProfile(status.target, tenantPaths)),
|
|
1225
1245
|
gatewayUrl: config.gatewayUrl,
|
|
1226
1246
|
credential: config.pat,
|