impel-cli 0.17.4 → 0.17.5

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 +83 -29
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "impel-cli",
3
- "version": "0.17.4",
3
+ "version": "0.17.5",
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,6 +93,14 @@ 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"})})';
96
104
  const LEGACY_CLAUDE_SAFE_STORAGE_NAME = "Claude";
97
105
  const CLAUDE_SAFE_STORAGE_METADATA = "safe-storage.json";
98
106
 
@@ -174,7 +182,7 @@ export const CURRENT_CONFIG_VERSION = 16;
174
182
  // — which is what made every `impel update` re-trigger macOS permission
175
183
  // prompts. Bump this ONLY when a code change alters the bytes of a built
176
184
  // bundle; leave it alone for changes that don't touch bundle contents.
177
- export const BUNDLE_BUILD_FINGERPRINT = "bundle-2026-07-20.1";
185
+ export const BUNDLE_BUILD_FINGERPRINT = "bundle-2026-07-20.2";
178
186
 
179
187
  /** Parse the tenant's install manifest, or null when absent/corrupt. */
180
188
  export function readTenantManifest(homeDir = os.homedir(), tenantId = null) {
@@ -245,6 +253,8 @@ export function bundleIsCurrent(status, {
245
253
  compatibility.patches?.followupBoundAgentEnable === 1
246
254
  && compatibility.patches?.followupBoundAgentFilter === 1
247
255
  && compatibility.patches?.followupBoundAgentChip === 1
256
+ && compatibility.patches?.codeDisabledBack === 1
257
+ && compatibility.patches?.codeNoSeatBack === 1
248
258
  && managedClaudeRendererMatchesCompatibility(status, compatibility)
249
259
  ))
250
260
  && (status.target !== "chatgpt" || (
@@ -280,8 +290,12 @@ function wrapperLaunchesOnTahoe(status) {
280
290
 
281
291
  function managedClaudeRendererMatchesCompatibility(status, compatibility) {
282
292
  try {
283
- const renderers = compatibility.rendererAssets?.agentMentions;
284
- if (!Array.isArray(renderers) || renderers.length === 0) return false;
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();
285
299
  const root = path.resolve(status.launcher, "Contents", "Resources", "ion-dist");
286
300
  return renderers.every((renderer) => {
287
301
  if (!renderer?.path || !renderer.sha256 || path.isAbsolute(renderer.path)) return false;
@@ -1365,6 +1379,7 @@ function writeVendoredClaudeBundle(paths, vendorPath, gatewayUrl, safeStorageNam
1365
1379
  }
1366
1380
 
1367
1381
  const agentMentionPatches = patchClaudeFollowupAgentMentions(staging);
1382
+ const codeDisabledBackPatches = patchClaudeCodeDisabledBackRouting(staging);
1368
1383
  const oldAsarHash = asarHeaderHash(asarPath);
1369
1384
  let planUsageGuardPatchCount = 0;
1370
1385
  let planUsageRequestPatchCount = 0;
@@ -1407,7 +1422,7 @@ function writeVendoredClaudeBundle(paths, vendorPath, gatewayUrl, safeStorageNam
1407
1422
  const vendorExecutable = path.join(macos, executableName);
1408
1423
  const signingIdentity = resolveVendoredAppSigningIdentity(vendorExecutable);
1409
1424
  writeAtomic(path.join(staging, "Contents", "Resources", "impel-compatibility.json"), JSON.stringify({
1410
- schemaVersion: 5,
1425
+ schemaVersion: 6,
1411
1426
  cliVersion: CLI_VERSION,
1412
1427
  bundleFingerprint: BUNDLE_BUILD_FINGERPRINT,
1413
1428
  signingMode: signingModeForIdentity(signingIdentity),
@@ -1426,9 +1441,12 @@ function writeVendoredClaudeBundle(paths, vendorPath, gatewayUrl, safeStorageNam
1426
1441
  followupBoundAgentEnable: agentMentionPatches.enable,
1427
1442
  followupBoundAgentFilter: agentMentionPatches.filter,
1428
1443
  followupBoundAgentChip: agentMentionPatches.chip,
1444
+ codeDisabledBack: codeDisabledBackPatches.disabled,
1445
+ codeNoSeatBack: codeDisabledBackPatches.noSeat,
1429
1446
  },
1430
1447
  rendererAssets: {
1431
1448
  agentMentions: agentMentionPatches.assets,
1449
+ codeDisabledBack: codeDisabledBackPatches.assets,
1432
1450
  },
1433
1451
  asarHeaderSha256: newAsarHash,
1434
1452
  }, null, 2) + "\n", 0o644);
@@ -1565,31 +1583,72 @@ function linkVendoredChatGPTResources(bundle, vendorBundleName) {
1565
1583
  }
1566
1584
 
1567
1585
  function patchClaudeFollowupAgentMentions(bundle) {
1586
+ const assets = patchClaudeRendererContracts(bundle, {
1587
+ supportLabel: "follow-up agent mentions",
1588
+ patchLabel: "follow-up agent-mention",
1589
+ contracts: [
1590
+ {
1591
+ key: "enable",
1592
+ original: CLAUDE_DRAFT_AGENT_MENTIONS,
1593
+ replacement: IMPEL_CLAUDE_BOUND_AGENT_MENTIONS,
1594
+ },
1595
+ {
1596
+ key: "filter",
1597
+ original: CLAUDE_AGENT_MENTION_FILTER,
1598
+ replacement: IMPEL_CLAUDE_BOUND_AGENT_MENTION_FILTER,
1599
+ },
1600
+ {
1601
+ key: "chip",
1602
+ original: CLAUDE_AGENT_MENTION_SELECT,
1603
+ replacement: IMPEL_CLAUDE_BOUND_AGENT_MENTION_SELECT,
1604
+ },
1605
+ ],
1606
+ });
1607
+ return {
1608
+ enable: 1,
1609
+ filter: 1,
1610
+ chip: 1,
1611
+ assets,
1612
+ };
1613
+ }
1614
+
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
+ function patchClaudeRendererContracts(bundle, {
1640
+ supportLabel,
1641
+ patchLabel,
1642
+ contracts,
1643
+ }) {
1568
1644
  const version = bundleVersion(bundle);
1569
1645
  if (version !== PINNED_VENDOR_APPS.claude.version) {
1570
1646
  throw new Error(
1571
- `Claude follow-up agent mentions support is pinned to ${PINNED_VENDOR_APPS.claude.version}; found ${version || "unknown"}`,
1647
+ `Claude ${supportLabel} support is pinned to ${PINNED_VENDOR_APPS.claude.version}; found ${version || "unknown"}`,
1572
1648
  );
1573
1649
  }
1574
1650
 
1575
1651
  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
1652
  const sources = new Map(listJavaScriptFiles(ionDist).map((candidate) => [
1594
1653
  candidate,
1595
1654
  fs.readFileSync(candidate, "utf8"),
@@ -1604,7 +1663,7 @@ function patchClaudeFollowupAgentMentions(bundle) {
1604
1663
  for (const contract of contracts) {
1605
1664
  const count = matches.get(contract.key).reduce((total, match) => total + match.count, 0);
1606
1665
  if (count !== 1) {
1607
- throw new Error(`Claude follow-up agent-mention ${contract.key} patch expected 1 match, found ${count}`);
1666
+ throw new Error(`Claude ${patchLabel} ${contract.key} patch expected 1 match, found ${count}`);
1608
1667
  }
1609
1668
  }
1610
1669
 
@@ -1626,12 +1685,7 @@ function patchClaudeFollowupAgentMentions(bundle) {
1626
1685
  });
1627
1686
  }
1628
1687
 
1629
- return {
1630
- enable: 1,
1631
- filter: 1,
1632
- chip: 1,
1633
- assets,
1634
- };
1688
+ return assets;
1635
1689
  }
1636
1690
 
1637
1691
  function listJavaScriptFiles(root) {