impel-cli 0.17.4 → 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 +58 -66
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "impel-cli",
3
- "version": "0.17.4",
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
@@ -165,7 +165,7 @@ export function managedAppIdentity(target, tenantId = null, tenantName = null) {
165
165
 
166
166
  // Bump when the written config/manifest schema changes; a mismatch forces the
167
167
  // slow open path (and thus a full config rewrite) after a CLI update.
168
- export const CURRENT_CONFIG_VERSION = 16;
168
+ export const CURRENT_CONFIG_VERSION = 17;
169
169
 
170
170
  // Identifies the bundle-BUILDING logic — the asar patches, plist rewrites,
171
171
  // helper rebranding, and signing. A vendored bundle is rebuilt only when this
@@ -174,7 +174,7 @@ export const CURRENT_CONFIG_VERSION = 16;
174
174
  // — which is what made every `impel update` re-trigger macOS permission
175
175
  // prompts. Bump this ONLY when a code change alters the bytes of a built
176
176
  // bundle; leave it alone for changes that don't touch bundle contents.
177
- export const BUNDLE_BUILD_FINGERPRINT = "bundle-2026-07-20.1";
177
+ export const BUNDLE_BUILD_FINGERPRINT = "bundle-2026-07-20.3";
178
178
 
179
179
  /** Parse the tenant's install manifest, or null when absent/corrupt. */
180
180
  export function readTenantManifest(homeDir = os.homedir(), tenantId = null) {
@@ -620,7 +620,7 @@ export function installManagedAppFiles({
620
620
  for (const target of targets) {
621
621
  if (target === "claude") {
622
622
  migrateLegacyClaudeAppSessions(paths.claude.userData, homeDir);
623
- writeClaudeDefaultApp(paths);
623
+ writeClaudeNative3PSelection(paths);
624
624
  writeClaudeCodeSettings(paths);
625
625
  writeClaudeConfig(paths, config, models);
626
626
  ensureClaudeSessionHooks(paths.claude.userData, config.tenantId, "claude_desktop");
@@ -911,16 +911,16 @@ function writeClaudeConfig(paths, config, models) {
911
911
  inferenceGatewayAuthScheme: "bearer",
912
912
  modelDiscoveryEnabled: false,
913
913
  inferenceModels: claudeModels,
914
- // 3P builds leave Chat unavailable unless the administrator opts in. The
915
- // Code install/disabled screen's "Back to Claude" link targets the Chat
916
- // root; without this key the root router selects Code again and strands
917
- // 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.
918
920
  chatTabEnabled: true,
919
- // Managed flat key (maps to authentication.disableClaudeAiSignIn, vendor
920
- // 1.3834.0+): hides the Claude.ai sign-in option and keeps the app in 3P
921
- // mode even when the persisted deploymentMode is missing or "1p". Claude.ai
922
- // account login is a dead end for gateway profiles anyway — the server
923
- // 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.
924
924
  disableDeploymentModeChooser: true,
925
925
  };
926
926
  writeAtomic(path.join(dir, `${CLAUDE_CONFIG_ID}.json`), JSON.stringify(body, null, 2) + "\n", 0o600);
@@ -947,7 +947,7 @@ function writeClaudeCodeSettings(paths) {
947
947
  writeAtomic(settingsPath, `${JSON.stringify(settings, null, 2)}\n`, 0o600);
948
948
  }
949
949
 
950
- function writeClaudeDefaultApp(paths) {
950
+ function writeClaudeNative3PSelection(paths) {
951
951
  const configPath = path.join(paths.claude.userData, "claude_desktop_config.json");
952
952
  let current = {};
953
953
  if (fs.existsSync(configPath)) {
@@ -960,33 +960,13 @@ function writeClaudeDefaultApp(paths) {
960
960
  if (!current || typeof current !== "object" || Array.isArray(current)) {
961
961
  throw new Error(`Claude desktop config must contain a JSON object: ${configPath}`);
962
962
  }
963
- const preferences = current.preferences ?? {};
964
- if (!preferences || typeof preferences !== "object" || Array.isArray(preferences)) {
965
- throw new Error(`Claude desktop preferences must contain a JSON object: ${configPath}`);
966
- }
967
- // The desktop app gates its UI on a persisted top-level `deploymentMode`:
968
- // until it is "3p", every launch lands on the sign-in / provider-chooser
969
- // screen even though the applied configLibrary gateway config loads fine —
970
- // and Claude.ai email login is server-disabled for 3P profiles
971
- // (custom_3p_not_available). A managed gateway profile is only ever 3P, so
972
- // enforce the key like the sandbox policy (heal "1p"/cleared values too,
973
- // e.g. after an in-app sign-out), not seed-once like a user preference.
974
- const needsDeploymentMode = current.deploymentMode !== "3p";
975
- // Claude persists the selected top-level app as `preferences.sidebarMode`.
976
- // Seed Code for a new isolated profile, then leave subsequent user choices
977
- // alone during install, update, and background config refreshes.
978
- const needsSidebarMode = !Object.hasOwn(preferences, "sidebarMode");
979
- if (!needsDeploymentMode && !needsSidebarMode) return;
980
- const next = {
981
- ...current,
982
- ...(needsDeploymentMode ? { deploymentMode: "3p" } : {}),
983
- preferences: needsSidebarMode ? { ...preferences, sidebarMode: "code" } : preferences,
984
- };
985
- // A vendor sign-out writes `awaitingSignIn: true` alongside "1p". Left in
986
- // place it re-arms the app's quit-time revert — which deletes deploymentMode
987
- // again — should the gateway config ever fail to load. Drop it when healing.
988
- if (needsDeploymentMode) delete next.awaitingSignIn;
989
- 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);
990
970
  }
991
971
 
992
972
  function writeChatGPTConfig(
@@ -1407,7 +1387,7 @@ function writeVendoredClaudeBundle(paths, vendorPath, gatewayUrl, safeStorageNam
1407
1387
  const vendorExecutable = path.join(macos, executableName);
1408
1388
  const signingIdentity = resolveVendoredAppSigningIdentity(vendorExecutable);
1409
1389
  writeAtomic(path.join(staging, "Contents", "Resources", "impel-compatibility.json"), JSON.stringify({
1410
- schemaVersion: 5,
1390
+ schemaVersion: 7,
1411
1391
  cliVersion: CLI_VERSION,
1412
1392
  bundleFingerprint: BUNDLE_BUILD_FINGERPRINT,
1413
1393
  signingMode: signingModeForIdentity(signingIdentity),
@@ -1565,31 +1545,48 @@ function linkVendoredChatGPTResources(bundle, vendorBundleName) {
1565
1545
  }
1566
1546
 
1567
1547
  function patchClaudeFollowupAgentMentions(bundle) {
1548
+ const assets = patchClaudeRendererContracts(bundle, {
1549
+ supportLabel: "follow-up agent mentions",
1550
+ patchLabel: "follow-up agent-mention",
1551
+ contracts: [
1552
+ {
1553
+ key: "enable",
1554
+ original: CLAUDE_DRAFT_AGENT_MENTIONS,
1555
+ replacement: IMPEL_CLAUDE_BOUND_AGENT_MENTIONS,
1556
+ },
1557
+ {
1558
+ key: "filter",
1559
+ original: CLAUDE_AGENT_MENTION_FILTER,
1560
+ replacement: IMPEL_CLAUDE_BOUND_AGENT_MENTION_FILTER,
1561
+ },
1562
+ {
1563
+ key: "chip",
1564
+ original: CLAUDE_AGENT_MENTION_SELECT,
1565
+ replacement: IMPEL_CLAUDE_BOUND_AGENT_MENTION_SELECT,
1566
+ },
1567
+ ],
1568
+ });
1569
+ return {
1570
+ enable: 1,
1571
+ filter: 1,
1572
+ chip: 1,
1573
+ assets,
1574
+ };
1575
+ }
1576
+
1577
+ function patchClaudeRendererContracts(bundle, {
1578
+ supportLabel,
1579
+ patchLabel,
1580
+ contracts,
1581
+ }) {
1568
1582
  const version = bundleVersion(bundle);
1569
1583
  if (version !== PINNED_VENDOR_APPS.claude.version) {
1570
1584
  throw new Error(
1571
- `Claude follow-up agent mentions support is pinned to ${PINNED_VENDOR_APPS.claude.version}; found ${version || "unknown"}`,
1585
+ `Claude ${supportLabel} support is pinned to ${PINNED_VENDOR_APPS.claude.version}; found ${version || "unknown"}`,
1572
1586
  );
1573
1587
  }
1574
1588
 
1575
1589
  const ionDist = path.join(bundle, "Contents", "Resources", "ion-dist");
1576
- const contracts = [
1577
- {
1578
- key: "enable",
1579
- original: CLAUDE_DRAFT_AGENT_MENTIONS,
1580
- replacement: IMPEL_CLAUDE_BOUND_AGENT_MENTIONS,
1581
- },
1582
- {
1583
- key: "filter",
1584
- original: CLAUDE_AGENT_MENTION_FILTER,
1585
- replacement: IMPEL_CLAUDE_BOUND_AGENT_MENTION_FILTER,
1586
- },
1587
- {
1588
- key: "chip",
1589
- original: CLAUDE_AGENT_MENTION_SELECT,
1590
- replacement: IMPEL_CLAUDE_BOUND_AGENT_MENTION_SELECT,
1591
- },
1592
- ];
1593
1590
  const sources = new Map(listJavaScriptFiles(ionDist).map((candidate) => [
1594
1591
  candidate,
1595
1592
  fs.readFileSync(candidate, "utf8"),
@@ -1604,7 +1601,7 @@ function patchClaudeFollowupAgentMentions(bundle) {
1604
1601
  for (const contract of contracts) {
1605
1602
  const count = matches.get(contract.key).reduce((total, match) => total + match.count, 0);
1606
1603
  if (count !== 1) {
1607
- throw new Error(`Claude follow-up agent-mention ${contract.key} patch expected 1 match, found ${count}`);
1604
+ throw new Error(`Claude ${patchLabel} ${contract.key} patch expected 1 match, found ${count}`);
1608
1605
  }
1609
1606
  }
1610
1607
 
@@ -1626,12 +1623,7 @@ function patchClaudeFollowupAgentMentions(bundle) {
1626
1623
  });
1627
1624
  }
1628
1625
 
1629
- return {
1630
- enable: 1,
1631
- filter: 1,
1632
- chip: 1,
1633
- assets,
1634
- };
1626
+ return assets;
1635
1627
  }
1636
1628
 
1637
1629
  function listJavaScriptFiles(root) {