impel-cli 0.11.1 → 0.11.2

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.
Files changed (3) hide show
  1. package/README.md +5 -2
  2. package/package.json +1 -1
  3. package/src/apps.js +139 -5
package/README.md CHANGED
@@ -703,8 +703,11 @@ a stable, unique bundle identifier and gateway configuration:
703
703
  to the authenticated aggregate Claude subscription pool exposed by the Impel
704
704
  gateway.
705
705
  - Impel ChatGPT wraps an APFS-cloned official app while preserving the nested
706
- OpenAI-signed bundle and executable. Its outer resource root contains the
707
- narrowly patched renderer ASAR used at runtime. The wrapper sets `CODEX_HOME`,
706
+ OpenAI-signed bundle and executable. A wrapper-owned startup preload pins
707
+ Electron's application name before the native menu is created, so macOS
708
+ shows `Impel ChatGPT (<org>)` without changing OpenAI's signed code. Its outer
709
+ resource root contains the narrowly patched renderer ASAR used at runtime.
710
+ The wrapper sets `CODEX_HOME`,
708
711
  passes a separate Chromium `--user-data-dir`, and writes its provider config
709
712
  and model catalog below `~/.config/impel/apps/tenants/<org>/chatgpt`. It also supplies a
710
713
  process-scoped, Codex-compatible wrapper around the Impel PAT so native
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "impel-cli",
3
- "version": "0.11.1",
3
+ "version": "0.11.2",
4
4
  "description": "Configure Claude Code and Codex CLI to talk to Impel's gateway, authenticated by an Impel Personal Access Token",
5
5
  "type": "module",
6
6
  "bin": {
package/src/apps.js CHANGED
@@ -193,6 +193,7 @@ export function bundleIsCurrent(status) {
193
193
  compatibility.vendorSignaturesPreserved === true
194
194
  && compatibility.resourceRoot === CHATGPT_RESOURCE_ROOT
195
195
  && compatibility.patches?.fastModeForGatewayAuth > 0
196
+ && compatibility.patches?.tenantDisplayNamePreload === 1
196
197
  )),
197
198
  );
198
199
  }
@@ -1060,6 +1061,11 @@ function writeVendoredChatGPTBundle(paths, vendorPath, gatewayUrl) {
1060
1061
  vendoredChatGPTLauncher(paths, vendorBundleName, executableName, gatewayUrl),
1061
1062
  0o755,
1062
1063
  );
1064
+ writeAtomic(
1065
+ path.join(staging, "Contents", "Resources", "impel-chatgpt-preload.cjs"),
1066
+ vendoredChatGPTDisplayNamePreload(),
1067
+ 0o644,
1068
+ );
1063
1069
  writeAtomic(path.join(staging, "Contents", "Resources", "impel-compatibility.json"), JSON.stringify({
1064
1070
  schemaVersion: 2,
1065
1071
  cliVersion: CLI_VERSION,
@@ -1070,7 +1076,11 @@ function writeVendoredChatGPTBundle(paths, vendorPath, gatewayUrl) {
1070
1076
  profileRoot: paths.chatgpt.root,
1071
1077
  codexHome: paths.chatgpt.codexHome,
1072
1078
  browserData: paths.chatgpt.browserData,
1073
- patches: { fastModeForGatewayAuth: fastModePatchCount, desktopAPIForGatewayAuth: 0 },
1079
+ patches: {
1080
+ fastModeForGatewayAuth: fastModePatchCount,
1081
+ desktopAPIForGatewayAuth: 0,
1082
+ tenantDisplayNamePreload: 1,
1083
+ },
1074
1084
  vendorSignaturesPreserved: true,
1075
1085
  resourceRoot: CHATGPT_RESOURCE_ROOT,
1076
1086
  asarHeaderSha256: asarHash,
@@ -1384,14 +1394,138 @@ unset TOKEN
1384
1394
  secure_codex_home
1385
1395
  BROWSER_DATA=${shellQuote(paths.chatgpt.browserData)}
1386
1396
  mkdir -p "$BROWSER_DATA"
1387
- # Electron resolves resources from the Impel wrapper when a nested app is
1388
- # executed directly. The wrapper exposes the vendor resource tree there, so
1389
- # app.asar and companion binaries resolve without LaunchServices handing the
1390
- # process to the separately running, identically identified vendor app.
1397
+ export IMPEL_APP_DISPLAY_NAME=${shellQuote(paths.chatgpt.displayName)}
1398
+ PRELOAD="$HERE/../Resources/impel-chatgpt-preload.cjs"
1399
+ export NODE_OPTIONS="--require=\\\"$PRELOAD\\\""
1400
+ # Electron resolves resources from the Impel wrapper when the nested app is
1401
+ # executed directly. The wrapper exposes the vendor resource tree there while
1402
+ # preserving the nested OpenAI-signed executable and bundle. The startup
1403
+ # preload pins Electron's application name before its native menu is created.
1391
1404
  exec "$VENDOR_APP/Contents/MacOS/${executableName}" --user-data-dir="$BROWSER_DATA" "$@"
1392
1405
  `;
1393
1406
  }
1394
1407
 
1408
+ function vendoredChatGPTDisplayNamePreload() {
1409
+ return `"use strict";
1410
+
1411
+ const displayName = process.env.IMPEL_APP_DISPLAY_NAME;
1412
+ if (process.type === "browser" && displayName) {
1413
+ const Module = require("node:module");
1414
+ const path = require("node:path");
1415
+ const load = Module._load;
1416
+ let objc;
1417
+ let appKit;
1418
+ let launchServicesNamed = false;
1419
+ let activationPolicyRefreshed = false;
1420
+
1421
+ const loadNativeBridge = () => {
1422
+ objc ||= require(path.join(
1423
+ process.resourcesPath,
1424
+ "app.asar.unpacked/node_modules/objc-js/dist/index.js",
1425
+ ));
1426
+ appKit ||= new objc.NobjcLibrary("/System/Library/Frameworks/AppKit.framework/AppKit");
1427
+ return { objc, appKit };
1428
+ };
1429
+
1430
+ const setLaunchServicesApplicationName = () => {
1431
+ if (launchServicesNamed) return;
1432
+ try {
1433
+ const native = loadNativeBridge();
1434
+ new native.objc.NobjcLibrary(
1435
+ "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/ApplicationServices",
1436
+ );
1437
+ const title = native.appKit.NSString.stringWithUTF8String$(displayName);
1438
+ const info = native.appKit.NSMutableDictionary.dictionaryWithDictionary$(
1439
+ native.appKit.NSBundle.mainBundle().infoDictionary(),
1440
+ );
1441
+ for (const key of ["CFBundleName", "CFBundleDisplayName"]) {
1442
+ info.setObject$forKey$(title, native.appKit.NSString.stringWithUTF8String$(key));
1443
+ }
1444
+ native.objc.callFunction(
1445
+ "_LSSetApplicationLaunchServicesServerConnectionStatus",
1446
+ { returns: "v", args: ["Q", "@"] },
1447
+ 0,
1448
+ null,
1449
+ );
1450
+ const checked = native.objc.callFunction(
1451
+ "_LSApplicationCheckIn",
1452
+ { returns: "@", args: ["i", "@"] },
1453
+ -2,
1454
+ info,
1455
+ );
1456
+ const asn = native.objc.callFunction("_LSGetCurrentApplicationASN", { returns: "@" });
1457
+ if (!checked || !asn) return;
1458
+ const status = native.objc.callFunction(
1459
+ "_LSSetApplicationInformationItem",
1460
+ { returns: "i", args: ["i", "@", "@", "@", "@"] },
1461
+ -2,
1462
+ asn,
1463
+ native.appKit.NSString.stringWithUTF8String$("LSDisplayName"),
1464
+ title,
1465
+ null,
1466
+ );
1467
+ launchServicesNamed = status === 0;
1468
+ } catch {
1469
+ // Keep the signed vendor app usable if LaunchServices changes this
1470
+ // private-but-established process-title API in a future macOS release.
1471
+ }
1472
+ };
1473
+
1474
+ const setNativeApplicationMenuTitle = () => {
1475
+ try {
1476
+ const native = loadNativeBridge();
1477
+ const title = native.appKit.NSString.stringWithUTF8String$(displayName);
1478
+ native.appKit.NSProcessInfo.processInfo().setProcessName$(title);
1479
+ const application = native.appKit.NSApplication.sharedApplication();
1480
+ application.setAccessibilityTitle$(title);
1481
+ const menu = application.mainMenu();
1482
+ if (menu && menu.numberOfItems() > 0) {
1483
+ const item = menu.itemAtIndex$(0);
1484
+ item.setTitle$(title);
1485
+ item.setAccessibilityTitle$(title);
1486
+ item.setAccessibilityLabel$(title);
1487
+ }
1488
+ } catch {
1489
+ // The OpenAI-signed native bridge is version-specific. Leave the app
1490
+ // usable if a future vendor build removes it; compatibility metadata
1491
+ // will force the next CLI version to rebuild the wrapper.
1492
+ }
1493
+ };
1494
+
1495
+ Module._load = function (request) {
1496
+ const loaded = load.apply(this, arguments);
1497
+ if (request === "electron" && loaded?.app) {
1498
+ Module._load = load;
1499
+ const setName = loaded.app.setName.bind(loaded.app);
1500
+ setName(displayName);
1501
+ loaded.app.setName = () => setName(displayName);
1502
+ setLaunchServicesApplicationName();
1503
+ setNativeApplicationMenuTitle();
1504
+ const setApplicationMenu = loaded.Menu.setApplicationMenu.bind(loaded.Menu);
1505
+ loaded.Menu.setApplicationMenu = (menu) => {
1506
+ const result = setApplicationMenu(menu);
1507
+ setNativeApplicationMenuTitle();
1508
+ if (!activationPolicyRefreshed) {
1509
+ activationPolicyRefreshed = true;
1510
+ loaded.app.setActivationPolicy("accessory");
1511
+ setTimeout(() => {
1512
+ loaded.app.setActivationPolicy("regular");
1513
+ launchServicesNamed = false;
1514
+ setLaunchServicesApplicationName();
1515
+ setApplicationMenu(menu);
1516
+ setNativeApplicationMenuTitle();
1517
+ }, 0);
1518
+ }
1519
+ setTimeout(setNativeApplicationMenuTitle, 0);
1520
+ return result;
1521
+ };
1522
+ }
1523
+ return loaded;
1524
+ };
1525
+ }
1526
+ `;
1527
+ }
1528
+
1395
1529
  function signVendoredApp(bundle, vendorExecutable, label) {
1396
1530
  if (process.platform !== "darwin" || !isMachO(vendorExecutable)) return;
1397
1531
  spawnSync("/usr/bin/xattr", ["-dr", "com.apple.quarantine", bundle], { stdio: "ignore" });