impel-cli 0.17.5 → 0.17.6

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/apps.js +23 -85
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "impel-cli",
3
- "version": "0.17.5",
3
+ "version": "0.17.6",
4
4
  "description": "Prepare isolated Claude and Codex workspaces for every accessible Impel tenant",
5
5
  "type": "module",
6
6
  "bin": {
package/src/apps.js CHANGED
@@ -93,14 +93,6 @@ const CLAUDE_AGENT_MENTION_FILTER = 'return e.filter(e=>"user"===e.source&&(!s||
93
93
  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
94
  const CLAUDE_AGENT_MENTION_SELECT = 'onSelect:t=>(e(String(t)),Promise.resolve(null))';
95
95
  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
96
  const LEGACY_CLAUDE_SAFE_STORAGE_NAME = "Claude";
105
97
  const CLAUDE_SAFE_STORAGE_METADATA = "safe-storage.json";
106
98
 
@@ -173,7 +165,7 @@ export function managedAppIdentity(target, tenantId = null, tenantName = null) {
173
165
 
174
166
  // Bump when the written config/manifest schema changes; a mismatch forces the
175
167
  // slow open path (and thus a full config rewrite) after a CLI update.
176
- export const CURRENT_CONFIG_VERSION = 16;
168
+ export const CURRENT_CONFIG_VERSION = 17;
177
169
 
178
170
  // Identifies the bundle-BUILDING logic — the asar patches, plist rewrites,
179
171
  // helper rebranding, and signing. A vendored bundle is rebuilt only when this
@@ -182,7 +174,7 @@ export const CURRENT_CONFIG_VERSION = 16;
182
174
  // — which is what made every `impel update` re-trigger macOS permission
183
175
  // prompts. Bump this ONLY when a code change alters the bytes of a built
184
176
  // bundle; leave it alone for changes that don't touch bundle contents.
185
- export const BUNDLE_BUILD_FINGERPRINT = "bundle-2026-07-20.2";
177
+ export const BUNDLE_BUILD_FINGERPRINT = "bundle-2026-07-20.3";
186
178
 
187
179
  /** Parse the tenant's install manifest, or null when absent/corrupt. */
188
180
  export function readTenantManifest(homeDir = os.homedir(), tenantId = null) {
@@ -253,8 +245,6 @@ export function bundleIsCurrent(status, {
253
245
  compatibility.patches?.followupBoundAgentEnable === 1
254
246
  && compatibility.patches?.followupBoundAgentFilter === 1
255
247
  && compatibility.patches?.followupBoundAgentChip === 1
256
- && compatibility.patches?.codeDisabledBack === 1
257
- && compatibility.patches?.codeNoSeatBack === 1
258
248
  && managedClaudeRendererMatchesCompatibility(status, compatibility)
259
249
  ))
260
250
  && (status.target !== "chatgpt" || (
@@ -290,12 +280,8 @@ function wrapperLaunchesOnTahoe(status) {
290
280
 
291
281
  function managedClaudeRendererMatchesCompatibility(status, compatibility) {
292
282
  try {
293
- const groups = [
294
- compatibility.rendererAssets?.agentMentions,
295
- compatibility.rendererAssets?.codeDisabledBack,
296
- ];
297
- if (groups.some((renderers) => !Array.isArray(renderers) || renderers.length === 0)) return false;
298
- const renderers = groups.flat();
283
+ const renderers = compatibility.rendererAssets?.agentMentions;
284
+ if (!Array.isArray(renderers) || renderers.length === 0) return false;
299
285
  const root = path.resolve(status.launcher, "Contents", "Resources", "ion-dist");
300
286
  return renderers.every((renderer) => {
301
287
  if (!renderer?.path || !renderer.sha256 || path.isAbsolute(renderer.path)) return false;
@@ -634,7 +620,7 @@ export function installManagedAppFiles({
634
620
  for (const target of targets) {
635
621
  if (target === "claude") {
636
622
  migrateLegacyClaudeAppSessions(paths.claude.userData, homeDir);
637
- writeClaudeDefaultApp(paths);
623
+ writeClaudeNative3PSelection(paths);
638
624
  writeClaudeCodeSettings(paths);
639
625
  writeClaudeConfig(paths, config, models);
640
626
  ensureClaudeSessionHooks(paths.claude.userData, config.tenantId, "claude_desktop");
@@ -925,16 +911,16 @@ function writeClaudeConfig(paths, config, models) {
925
911
  inferenceGatewayAuthScheme: "bearer",
926
912
  modelDiscoveryEnabled: false,
927
913
  inferenceModels: claudeModels,
928
- // 3P builds leave Chat unavailable unless the administrator opts in. The
929
- // Code install/disabled screen's "Back to Claude" link targets the Chat
930
- // root; without this key the root router selects Code again and strands
931
- // gateway users in a loop even though inference is healthy.
914
+ // These are Anthropic's native managed 3P surface controls. Cowork and
915
+ // Code default to enabled in the pinned build, but state them explicitly
916
+ // so an older generated profile cannot leave either surface disabled.
917
+ coworkTabEnabled: true,
918
+ isClaudeCodeForDesktopEnabled: true,
919
+ // Chat is the only opt-in surface in the pinned build.
932
920
  chatTabEnabled: true,
933
- // Managed flat key (maps to authentication.disableClaudeAiSignIn, vendor
934
- // 1.3834.0+): hides the Claude.ai sign-in option and keeps the app in 3P
935
- // mode even when the persisted deploymentMode is missing or "1p". Claude.ai
936
- // account login is a dead end for gateway profiles anyway — the server
937
- // rejects it with custom_3p_not_available.
921
+ // Native managed flat key (maps to authentication.disableClaudeAiSignIn):
922
+ // with an inference config present the pinned app selects 3P and hides the
923
+ // inapplicable Claude.ai sign-in option.
938
924
  disableDeploymentModeChooser: true,
939
925
  };
940
926
  writeAtomic(path.join(dir, `${CLAUDE_CONFIG_ID}.json`), JSON.stringify(body, null, 2) + "\n", 0o600);
@@ -961,7 +947,7 @@ function writeClaudeCodeSettings(paths) {
961
947
  writeAtomic(settingsPath, `${JSON.stringify(settings, null, 2)}\n`, 0o600);
962
948
  }
963
949
 
964
- function writeClaudeDefaultApp(paths) {
950
+ function writeClaudeNative3PSelection(paths) {
965
951
  const configPath = path.join(paths.claude.userData, "claude_desktop_config.json");
966
952
  let current = {};
967
953
  if (fs.existsSync(configPath)) {
@@ -974,33 +960,13 @@ function writeClaudeDefaultApp(paths) {
974
960
  if (!current || typeof current !== "object" || Array.isArray(current)) {
975
961
  throw new Error(`Claude desktop config must contain a JSON object: ${configPath}`);
976
962
  }
977
- const preferences = current.preferences ?? {};
978
- if (!preferences || typeof preferences !== "object" || Array.isArray(preferences)) {
979
- throw new Error(`Claude desktop preferences must contain a JSON object: ${configPath}`);
980
- }
981
- // The desktop app gates its UI on a persisted top-level `deploymentMode`:
982
- // until it is "3p", every launch lands on the sign-in / provider-chooser
983
- // screen even though the applied configLibrary gateway config loads fine —
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);
963
+ // Mirror the pinned app's native "select 3P" persistence contract. This
964
+ // marks the first-run choice complete and clears a pending 1P sign-in while
965
+ // leaving Claude-owned preferences (including sidebarMode) untouched.
966
+ if (current.deploymentMode === "3p" && !Object.hasOwn(current, "awaitingSignIn")) return;
967
+ const next = { ...current, deploymentMode: "3p" };
968
+ delete next.awaitingSignIn;
969
+ writeAtomic(configPath, `${JSON.stringify(next, null, 2)}\n`, 0o600);
1004
970
  }
1005
971
 
1006
972
  function writeChatGPTConfig(
@@ -1379,7 +1345,6 @@ function writeVendoredClaudeBundle(paths, vendorPath, gatewayUrl, safeStorageNam
1379
1345
  }
1380
1346
 
1381
1347
  const agentMentionPatches = patchClaudeFollowupAgentMentions(staging);
1382
- const codeDisabledBackPatches = patchClaudeCodeDisabledBackRouting(staging);
1383
1348
  const oldAsarHash = asarHeaderHash(asarPath);
1384
1349
  let planUsageGuardPatchCount = 0;
1385
1350
  let planUsageRequestPatchCount = 0;
@@ -1422,7 +1387,7 @@ function writeVendoredClaudeBundle(paths, vendorPath, gatewayUrl, safeStorageNam
1422
1387
  const vendorExecutable = path.join(macos, executableName);
1423
1388
  const signingIdentity = resolveVendoredAppSigningIdentity(vendorExecutable);
1424
1389
  writeAtomic(path.join(staging, "Contents", "Resources", "impel-compatibility.json"), JSON.stringify({
1425
- schemaVersion: 6,
1390
+ schemaVersion: 7,
1426
1391
  cliVersion: CLI_VERSION,
1427
1392
  bundleFingerprint: BUNDLE_BUILD_FINGERPRINT,
1428
1393
  signingMode: signingModeForIdentity(signingIdentity),
@@ -1441,12 +1406,9 @@ function writeVendoredClaudeBundle(paths, vendorPath, gatewayUrl, safeStorageNam
1441
1406
  followupBoundAgentEnable: agentMentionPatches.enable,
1442
1407
  followupBoundAgentFilter: agentMentionPatches.filter,
1443
1408
  followupBoundAgentChip: agentMentionPatches.chip,
1444
- codeDisabledBack: codeDisabledBackPatches.disabled,
1445
- codeNoSeatBack: codeDisabledBackPatches.noSeat,
1446
1409
  },
1447
1410
  rendererAssets: {
1448
1411
  agentMentions: agentMentionPatches.assets,
1449
- codeDisabledBack: codeDisabledBackPatches.assets,
1450
1412
  },
1451
1413
  asarHeaderSha256: newAsarHash,
1452
1414
  }, null, 2) + "\n", 0o644);
@@ -1612,30 +1574,6 @@ function patchClaudeFollowupAgentMentions(bundle) {
1612
1574
  };
1613
1575
  }
1614
1576
 
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
1577
  function patchClaudeRendererContracts(bundle, {
1640
1578
  supportLabel,
1641
1579
  patchLabel,