impel-cli 0.17.5 → 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 +114 -115
- 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,22 +92,16 @@ 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))';
|
|
95
104
|
const IMPEL_CLAUDE_BOUND_AGENT_MENTION_SELECT = 'onSelect:t=>"string"==typeof y?Promise.resolve({chipText:String(t)}):(e(String(t)),Promise.resolve(null))';
|
|
96
|
-
// Claude's Code-disabled screens link Back to "/", but the root router honors
|
|
97
|
-
// its separately persisted last Code mode and immediately returns to the same
|
|
98
|
-
// disabled screen. Route directly to the pinned build's canonical Chat page;
|
|
99
|
-
// its normal mode effect then reconciles both renderer and desktop preference.
|
|
100
|
-
const CLAUDE_CODE_DISABLED_BACK_LINK = 'function Fi(){return e.jsxs("a",{href:"/",className:"inline-flex items-center text-sm text-text-400 hover:text-text-300 outline-none"';
|
|
101
|
-
const IMPEL_CLAUDE_CODE_DISABLED_BACK_LINK = 'function Fi(){return e.jsxs("a",{href:"/chat/new",className:"inline-flex items-center text-sm text-text-400 hover:text-text-300 outline-none"';
|
|
102
|
-
const CLAUDE_CODE_NO_SEAT_BACK_LINK = 'b.jsx(D,{variant:"ghost",href:"/",icon:"ArrowLeft",children:b.jsx(A,{defaultMessage:"Back to Claude",id:"e4M0bJ/pZc"})})';
|
|
103
|
-
const IMPEL_CLAUDE_CODE_NO_SEAT_BACK_LINK = 'b.jsx(D,{variant:"ghost",href:"/chat/new",icon:"ArrowLeft",children:b.jsx(A,{defaultMessage:"Back to Claude",id:"e4M0bJ/pZc"})})';
|
|
104
105
|
const LEGACY_CLAUDE_SAFE_STORAGE_NAME = "Claude";
|
|
105
106
|
const CLAUDE_SAFE_STORAGE_METADATA = "safe-storage.json";
|
|
106
107
|
|
|
@@ -173,7 +174,7 @@ export function managedAppIdentity(target, tenantId = null, tenantName = null) {
|
|
|
173
174
|
|
|
174
175
|
// Bump when the written config/manifest schema changes; a mismatch forces the
|
|
175
176
|
// slow open path (and thus a full config rewrite) after a CLI update.
|
|
176
|
-
export const CURRENT_CONFIG_VERSION =
|
|
177
|
+
export const CURRENT_CONFIG_VERSION = 17;
|
|
177
178
|
|
|
178
179
|
// Identifies the bundle-BUILDING logic — the asar patches, plist rewrites,
|
|
179
180
|
// helper rebranding, and signing. A vendored bundle is rebuilt only when this
|
|
@@ -182,7 +183,7 @@ export const CURRENT_CONFIG_VERSION = 16;
|
|
|
182
183
|
// — which is what made every `impel update` re-trigger macOS permission
|
|
183
184
|
// prompts. Bump this ONLY when a code change alters the bytes of a built
|
|
184
185
|
// bundle; leave it alone for changes that don't touch bundle contents.
|
|
185
|
-
export const BUNDLE_BUILD_FINGERPRINT = "bundle-2026-07-
|
|
186
|
+
export const BUNDLE_BUILD_FINGERPRINT = "bundle-2026-07-21.1";
|
|
186
187
|
|
|
187
188
|
/** Parse the tenant's install manifest, or null when absent/corrupt. */
|
|
188
189
|
export function readTenantManifest(homeDir = os.homedir(), tenantId = null) {
|
|
@@ -253,8 +254,7 @@ export function bundleIsCurrent(status, {
|
|
|
253
254
|
compatibility.patches?.followupBoundAgentEnable === 1
|
|
254
255
|
&& compatibility.patches?.followupBoundAgentFilter === 1
|
|
255
256
|
&& compatibility.patches?.followupBoundAgentChip === 1
|
|
256
|
-
&& compatibility
|
|
257
|
-
&& compatibility.patches?.codeNoSeatBack === 1
|
|
257
|
+
&& claudePatchSetMatchesCompatibility(compatibility)
|
|
258
258
|
&& managedClaudeRendererMatchesCompatibility(status, compatibility)
|
|
259
259
|
))
|
|
260
260
|
&& (status.target !== "chatgpt" || (
|
|
@@ -288,14 +288,40 @@ function wrapperLaunchesOnTahoe(status) {
|
|
|
288
288
|
}
|
|
289
289
|
}
|
|
290
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
|
+
|
|
291
321
|
function managedClaudeRendererMatchesCompatibility(status, compatibility) {
|
|
292
322
|
try {
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
compatibility.rendererAssets?.codeDisabledBack,
|
|
296
|
-
];
|
|
297
|
-
if (groups.some((renderers) => !Array.isArray(renderers) || renderers.length === 0)) return false;
|
|
298
|
-
const renderers = groups.flat();
|
|
323
|
+
const renderers = compatibility.rendererAssets?.agentMentions;
|
|
324
|
+
if (!Array.isArray(renderers) || renderers.length === 0) return false;
|
|
299
325
|
const root = path.resolve(status.launcher, "Contents", "Resources", "ion-dist");
|
|
300
326
|
return renderers.every((renderer) => {
|
|
301
327
|
if (!renderer?.path || !renderer.sha256 || path.isAbsolute(renderer.path)) return false;
|
|
@@ -632,27 +658,13 @@ export function installManagedAppFiles({
|
|
|
632
658
|
|
|
633
659
|
const installed = [];
|
|
634
660
|
for (const target of targets) {
|
|
635
|
-
if (target === "claude") {
|
|
636
|
-
migrateLegacyClaudeAppSessions(paths.claude.userData, homeDir);
|
|
637
|
-
writeClaudeDefaultApp(paths);
|
|
638
|
-
writeClaudeCodeSettings(paths);
|
|
639
|
-
writeClaudeConfig(paths, config, models);
|
|
640
|
-
ensureClaudeSessionHooks(paths.claude.userData, config.tenantId, "claude_desktop");
|
|
641
|
-
}
|
|
642
|
-
else {
|
|
643
|
-
writeChatGPTConfig(
|
|
644
|
-
paths,
|
|
645
|
-
config,
|
|
646
|
-
models,
|
|
647
|
-
vendorCodexModels,
|
|
648
|
-
chatgptInvocations,
|
|
649
|
-
generatedAt,
|
|
650
|
-
);
|
|
651
|
-
ensureCodexSessionHooks(paths.chatgpt.codexHome, config.tenantId, "codex_desktop");
|
|
652
|
-
}
|
|
653
661
|
// Non-bundle targets are the background-refresh / fast-open path: configs,
|
|
654
662
|
// token helper, catalog, and manifest only. Bundle swaps require the app
|
|
655
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.
|
|
656
668
|
if (bundleTargets.has(target)) {
|
|
657
669
|
if (resolvedVendorPaths[target] && isVendableVendorApp(target, resolvedVendorPaths[target])) {
|
|
658
670
|
assertPinnedVendorAppVersion(target, resolvedVendorPaths[target]);
|
|
@@ -670,6 +682,24 @@ export function installManagedAppFiles({
|
|
|
670
682
|
fs.rmSync(appPaths(homeDir)[target].launcher, { recursive: true, force: true });
|
|
671
683
|
}
|
|
672
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
|
+
}
|
|
673
703
|
installed.push({
|
|
674
704
|
target,
|
|
675
705
|
launcher: paths[target].launcher,
|
|
@@ -925,16 +955,16 @@ function writeClaudeConfig(paths, config, models) {
|
|
|
925
955
|
inferenceGatewayAuthScheme: "bearer",
|
|
926
956
|
modelDiscoveryEnabled: false,
|
|
927
957
|
inferenceModels: claudeModels,
|
|
928
|
-
//
|
|
929
|
-
// Code
|
|
930
|
-
//
|
|
931
|
-
|
|
958
|
+
// These are Anthropic's native managed 3P surface controls. Cowork and
|
|
959
|
+
// Code default to enabled in the pinned build, but state them explicitly
|
|
960
|
+
// so an older generated profile cannot leave either surface disabled.
|
|
961
|
+
coworkTabEnabled: true,
|
|
962
|
+
isClaudeCodeForDesktopEnabled: true,
|
|
963
|
+
// Chat is the only opt-in surface in the pinned build.
|
|
932
964
|
chatTabEnabled: true,
|
|
933
|
-
//
|
|
934
|
-
//
|
|
935
|
-
//
|
|
936
|
-
// account login is a dead end for gateway profiles anyway — the server
|
|
937
|
-
// rejects it with custom_3p_not_available.
|
|
965
|
+
// Native managed flat key (maps to authentication.disableClaudeAiSignIn):
|
|
966
|
+
// with an inference config present the pinned app selects 3P and hides the
|
|
967
|
+
// inapplicable Claude.ai sign-in option.
|
|
938
968
|
disableDeploymentModeChooser: true,
|
|
939
969
|
};
|
|
940
970
|
writeAtomic(path.join(dir, `${CLAUDE_CONFIG_ID}.json`), JSON.stringify(body, null, 2) + "\n", 0o600);
|
|
@@ -961,7 +991,7 @@ function writeClaudeCodeSettings(paths) {
|
|
|
961
991
|
writeAtomic(settingsPath, `${JSON.stringify(settings, null, 2)}\n`, 0o600);
|
|
962
992
|
}
|
|
963
993
|
|
|
964
|
-
function
|
|
994
|
+
function writeClaudeNative3PSelection(paths) {
|
|
965
995
|
const configPath = path.join(paths.claude.userData, "claude_desktop_config.json");
|
|
966
996
|
let current = {};
|
|
967
997
|
if (fs.existsSync(configPath)) {
|
|
@@ -974,33 +1004,13 @@ function writeClaudeDefaultApp(paths) {
|
|
|
974
1004
|
if (!current || typeof current !== "object" || Array.isArray(current)) {
|
|
975
1005
|
throw new Error(`Claude desktop config must contain a JSON object: ${configPath}`);
|
|
976
1006
|
}
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
// and Claude.ai email login is server-disabled for 3P profiles
|
|
985
|
-
// (custom_3p_not_available). A managed gateway profile is only ever 3P, so
|
|
986
|
-
// enforce the key like the sandbox policy (heal "1p"/cleared values too,
|
|
987
|
-
// e.g. after an in-app sign-out), not seed-once like a user preference.
|
|
988
|
-
const needsDeploymentMode = current.deploymentMode !== "3p";
|
|
989
|
-
// Claude persists the selected top-level app as `preferences.sidebarMode`.
|
|
990
|
-
// Seed Code for a new isolated profile, then leave subsequent user choices
|
|
991
|
-
// alone during install, update, and background config refreshes.
|
|
992
|
-
const needsSidebarMode = !Object.hasOwn(preferences, "sidebarMode");
|
|
993
|
-
if (!needsDeploymentMode && !needsSidebarMode) return;
|
|
994
|
-
const next = {
|
|
995
|
-
...current,
|
|
996
|
-
...(needsDeploymentMode ? { deploymentMode: "3p" } : {}),
|
|
997
|
-
preferences: needsSidebarMode ? { ...preferences, sidebarMode: "code" } : preferences,
|
|
998
|
-
};
|
|
999
|
-
// A vendor sign-out writes `awaitingSignIn: true` alongside "1p". Left in
|
|
1000
|
-
// place it re-arms the app's quit-time revert — which deletes deploymentMode
|
|
1001
|
-
// again — should the gateway config ever fail to load. Drop it when healing.
|
|
1002
|
-
if (needsDeploymentMode) delete next.awaitingSignIn;
|
|
1003
|
-
writeAtomic(configPath, JSON.stringify(next, null, 2) + "\n", 0o600);
|
|
1007
|
+
// Mirror the pinned app's native "select 3P" persistence contract. This
|
|
1008
|
+
// marks the first-run choice complete and clears a pending 1P sign-in while
|
|
1009
|
+
// leaving Claude-owned preferences (including sidebarMode) untouched.
|
|
1010
|
+
if (current.deploymentMode === "3p" && !Object.hasOwn(current, "awaitingSignIn")) return;
|
|
1011
|
+
const next = { ...current, deploymentMode: "3p" };
|
|
1012
|
+
delete next.awaitingSignIn;
|
|
1013
|
+
writeAtomic(configPath, `${JSON.stringify(next, null, 2)}\n`, 0o600);
|
|
1004
1014
|
}
|
|
1005
1015
|
|
|
1006
1016
|
function writeChatGPTConfig(
|
|
@@ -1379,7 +1389,6 @@ function writeVendoredClaudeBundle(paths, vendorPath, gatewayUrl, safeStorageNam
|
|
|
1379
1389
|
}
|
|
1380
1390
|
|
|
1381
1391
|
const agentMentionPatches = patchClaudeFollowupAgentMentions(staging);
|
|
1382
|
-
const codeDisabledBackPatches = patchClaudeCodeDisabledBackRouting(staging);
|
|
1383
1392
|
const oldAsarHash = asarHeaderHash(asarPath);
|
|
1384
1393
|
let planUsageGuardPatchCount = 0;
|
|
1385
1394
|
let planUsageRequestPatchCount = 0;
|
|
@@ -1422,7 +1431,7 @@ function writeVendoredClaudeBundle(paths, vendorPath, gatewayUrl, safeStorageNam
|
|
|
1422
1431
|
const vendorExecutable = path.join(macos, executableName);
|
|
1423
1432
|
const signingIdentity = resolveVendoredAppSigningIdentity(vendorExecutable);
|
|
1424
1433
|
writeAtomic(path.join(staging, "Contents", "Resources", "impel-compatibility.json"), JSON.stringify({
|
|
1425
|
-
schemaVersion:
|
|
1434
|
+
schemaVersion: 7,
|
|
1426
1435
|
cliVersion: CLI_VERSION,
|
|
1427
1436
|
bundleFingerprint: BUNDLE_BUILD_FINGERPRINT,
|
|
1428
1437
|
signingMode: signingModeForIdentity(signingIdentity),
|
|
@@ -1441,12 +1450,9 @@ function writeVendoredClaudeBundle(paths, vendorPath, gatewayUrl, safeStorageNam
|
|
|
1441
1450
|
followupBoundAgentEnable: agentMentionPatches.enable,
|
|
1442
1451
|
followupBoundAgentFilter: agentMentionPatches.filter,
|
|
1443
1452
|
followupBoundAgentChip: agentMentionPatches.chip,
|
|
1444
|
-
codeDisabledBack: codeDisabledBackPatches.disabled,
|
|
1445
|
-
codeNoSeatBack: codeDisabledBackPatches.noSeat,
|
|
1446
1453
|
},
|
|
1447
1454
|
rendererAssets: {
|
|
1448
1455
|
agentMentions: agentMentionPatches.assets,
|
|
1449
|
-
codeDisabledBack: codeDisabledBackPatches.assets,
|
|
1450
1456
|
},
|
|
1451
1457
|
asarHeaderSha256: newAsarHash,
|
|
1452
1458
|
}, null, 2) + "\n", 0o644);
|
|
@@ -1612,30 +1618,6 @@ function patchClaudeFollowupAgentMentions(bundle) {
|
|
|
1612
1618
|
};
|
|
1613
1619
|
}
|
|
1614
1620
|
|
|
1615
|
-
function patchClaudeCodeDisabledBackRouting(bundle) {
|
|
1616
|
-
const assets = patchClaudeRendererContracts(bundle, {
|
|
1617
|
-
supportLabel: "Code-disabled Back routing",
|
|
1618
|
-
patchLabel: "Code-disabled Back routing",
|
|
1619
|
-
contracts: [
|
|
1620
|
-
{
|
|
1621
|
-
key: "disabled",
|
|
1622
|
-
original: CLAUDE_CODE_DISABLED_BACK_LINK,
|
|
1623
|
-
replacement: IMPEL_CLAUDE_CODE_DISABLED_BACK_LINK,
|
|
1624
|
-
},
|
|
1625
|
-
{
|
|
1626
|
-
key: "noSeat",
|
|
1627
|
-
original: CLAUDE_CODE_NO_SEAT_BACK_LINK,
|
|
1628
|
-
replacement: IMPEL_CLAUDE_CODE_NO_SEAT_BACK_LINK,
|
|
1629
|
-
},
|
|
1630
|
-
],
|
|
1631
|
-
});
|
|
1632
|
-
return {
|
|
1633
|
-
disabled: 1,
|
|
1634
|
-
noSeat: 1,
|
|
1635
|
-
assets,
|
|
1636
|
-
};
|
|
1637
|
-
}
|
|
1638
|
-
|
|
1639
1621
|
function patchClaudeRendererContracts(bundle, {
|
|
1640
1622
|
supportLabel,
|
|
1641
1623
|
patchLabel,
|
|
@@ -1734,6 +1716,23 @@ function patchFixedWidthAsarString(asarPath, original, replacement, label) {
|
|
|
1734
1716
|
function patchClaudePlanUsageRequest(asarPath) {
|
|
1735
1717
|
const archive = fs.readFileSync(asarPath);
|
|
1736
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
|
+
}
|
|
1737
1736
|
const credentialAccessor = source.match(
|
|
1738
1737
|
/function ([A-Za-z_$][\w$]*)\(e\)\{var t;return [\s\S]{0,500}?\[custom-3p\] Credentials loaded from managed config/u,
|
|
1739
1738
|
)?.[1];
|
|
@@ -1749,10 +1748,10 @@ function patchClaudePlanUsageRequest(asarPath) {
|
|
|
1749
1748
|
return applyFixedWidthAsarPatches(asarPath, archive, [], "Claude plan-usage gateway request");
|
|
1750
1749
|
}
|
|
1751
1750
|
|
|
1752
|
-
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;
|
|
1753
1752
|
const patches = [...source.matchAll(requestPattern)].map((match) => {
|
|
1754
1753
|
const original = match[0];
|
|
1755
|
-
const replacement =
|
|
1754
|
+
const replacement = `${match[1]}.net.fetch("https://gateway.useimpel.com/ui/a/u",{headers:{key:${credentialAccessor}(${configAccessor}()).apiKey}})`;
|
|
1756
1755
|
if (replacement.length > original.length) {
|
|
1757
1756
|
throw new Error("Claude plan-usage gateway request replacement no longer fits the vendor ASAR contract");
|
|
1758
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,
|