impel-cli 0.18.9 → 0.18.11-beta.0

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "impel-cli",
3
- "version": "0.18.9",
3
+ "version": "0.18.11-beta.0",
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
@@ -42,42 +42,41 @@ const APP_DEFINITIONS = {
42
42
  // CLI release therefore installs and accepts only the exact app builds tested
43
43
  // with its compatibility patches. Updating a pin requires a new CLI release.
44
44
  export const PINNED_VENDOR_APPS = Object.freeze({
45
- // 1.22209.3 (the previous pin) routes every managed 3P gateway profile into
46
- // the Claude Code marketing page at app://localhost/code/disabled: the
47
- // desktop Code surface never becomes available, the hardcoded-off
48
- // claude_code_web gate traps /code/*, and the page's "Back to Claude" link
49
- // targets "/" so the persisted Code mode loops straight back. 1.20186.9 is
50
- // the last build verified working end-to-end for gateway profiles. Do not
51
- // re-upgrade without a verified fix for the Code-surface routing contract.
45
+ // 1.22209.3 routed managed 3P gateway profiles into /code/disabled because
46
+ // it did not translate the managed desktop-Code switch into renderer access.
47
+ // 1.24012.9 restores that contract by synthesizing claude_code and
48
+ // claude_code_desktop access from the configured code surface. The pinned
49
+ // contract test asserts that source path directly so this regression cannot
50
+ // hide behind otherwise-valid configLibrary schema markers again.
52
51
  claude: Object.freeze({
53
- version: "1.20186.9",
54
- claudeCodeVersion: "2.1.209",
55
- claudeCodeCommit: "0fe048596fd45e79e99353cbe2c3d1b1ac069568",
52
+ version: "1.24012.9",
53
+ claudeCodeVersion: "2.1.219",
54
+ claudeCodeCommit: "7006c4c3acac98e554d3997baeda6a7fa4d1ff7c",
56
55
  bundleName: "Claude.app",
57
56
  windows: Object.freeze({
58
57
  packageName: "Claude",
59
- packageVersion: "1.20186.9.0",
58
+ packageVersion: "1.24012.9.0",
60
59
  publisher: 'CN="Anthropic, PBC", O="Anthropic, PBC", L=San Francisco, S=California, C=US, SERIALNUMBER=4860621, OID.2.5.4.15=Private Organization, OID.1.3.6.1.4.1.311.60.2.1.2=Delaware, OID.1.3.6.1.4.1.311.60.2.1.3=US',
61
60
  executable: "app\\Claude.exe",
62
61
  downloads: Object.freeze({
63
62
  arm64: Object.freeze({
64
- url: "https://downloads.claude.ai/releases/win32/arm64/1.20186.9/Claude-69f150a4c9316d5c8cd7b9f130ed583d15c0383e.msix",
65
- sha256: "9a0591a0d9e298dea92d5547ee259f35e818ce0b1982512ad1550f5ed2744c39",
63
+ url: "https://downloads.claude.ai/releases/win32/arm64/1.24012.9/Claude-03c61d06f8e01a4db2273b9514e225f21d2ba62e.msix",
64
+ sha256: "52ed0402ca70fc95e064417ff9bdf3a6336fe5dbf0612f7797937fd0d35ecdba",
66
65
  }),
67
66
  x64: Object.freeze({
68
- url: "https://downloads.claude.ai/releases/win32/x64/1.20186.9/Claude-69f150a4c9316d5c8cd7b9f130ed583d15c0383e.msix",
69
- sha256: "55a0b8f8c3c8d46cd64a03d3f42818da28f312cd4be904265cd61baee7ea68a5",
67
+ url: "https://downloads.claude.ai/releases/win32/x64/1.24012.9/Claude-03c61d06f8e01a4db2273b9514e225f21d2ba62e.msix",
68
+ sha256: "c7d61479f06a3490bd2598405aa95a428abb9fe31ed97f9e0a1fba7cd2745fe2",
70
69
  }),
71
70
  }),
72
71
  }),
73
72
  downloads: Object.freeze({
74
73
  arm64: Object.freeze({
75
- url: "https://downloads.claude.ai/releases/darwin/universal/1.20186.9/Claude-69f150a4c9316d5c8cd7b9f130ed583d15c0383e.zip",
76
- sha256: "9364648c74bd525aa293a28103eb37262a5553de21c7ca907766d7fc40623015",
74
+ url: "https://downloads.claude.ai/releases/darwin/universal/1.24012.9/Claude-03c61d06f8e01a4db2273b9514e225f21d2ba62e.zip",
75
+ sha256: "39ad2d181e2b1b017c5506f66c08399c23575c802d8b63927f40128e77103a9b",
77
76
  }),
78
77
  x64: Object.freeze({
79
- url: "https://downloads.claude.ai/releases/darwin/universal/1.20186.9/Claude-69f150a4c9316d5c8cd7b9f130ed583d15c0383e.zip",
80
- sha256: "9364648c74bd525aa293a28103eb37262a5553de21c7ca907766d7fc40623015",
78
+ url: "https://downloads.claude.ai/releases/darwin/universal/1.24012.9/Claude-03c61d06f8e01a4db2273b9514e225f21d2ba62e.zip",
79
+ sha256: "39ad2d181e2b1b017c5506f66c08399c23575c802d8b63927f40128e77103a9b",
81
80
  }),
82
81
  }),
83
82
  }),
@@ -109,16 +108,14 @@ const ULTRA_REASONING_LEVEL = { effort: "ultra", description: "Maximum reasoning
109
108
  const FAST_SERVICE_TIER = { id: "priority", name: "Fast", description: "1.5x speed, increased usage" };
110
109
  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;
111
110
  const CHATGPT_RESOURCE_ROOT = "linked_vendor_resources_with_patched_asar";
112
- const CLAUDE_PLAN_USAGE_GUARD = "if(!Ze().hasOrgPolicyBackend())return;if((r=fr())";
113
- const IMPEL_CLAUDE_PLAN_USAGE_GUARD = `${"if(!wze())return;".padEnd("if(!Ze().hasOrgPolicyBackend())return;".length, " ")}if((r=fr())`;
114
- const CLAUDE_PLAN_USAGE_REQUEST = 'P.net.fetch(`${ht()}/api/organizations/${o}/usage`,{signal:AbortSignal.timeout(mcr)})';
115
- 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, " ");
116
- const CLAUDE_DRAFT_AGENT_MENTIONS = 'agentMentionsEnabled:"draft"===Be&&s';
117
- const IMPEL_CLAUDE_BOUND_AGENT_MENTIONS = 'agentMentionsEnabled:"draft"===Be?s:Le?.agent??!1';
118
- const CLAUDE_AGENT_MENTION_FILTER = 'return e.filter(e=>"user"===e.source&&(!s||e.name.toLowerCase().includes(s))).slice(0,10)';
119
- 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)';
111
+ const CLAUDE_PLAN_USAGE_GUARD = "if(!PSe()){cz();return}if(!Ge().hasOrgPolicyBackend())return;if((r=ar())";
112
+ const IMPEL_CLAUDE_PLAN_USAGE_GUARD = `if(!PSe()){cz();return}${"".padEnd("if(!Ge().hasOrgPolicyBackend())return;".length, " ")}if((r=ar())`;
113
+ const CLAUDE_DRAFT_AGENT_MENTIONS = 'agentMentionsEnabled:"draft"===Ge&&t';
114
+ const IMPEL_CLAUDE_BOUND_AGENT_MENTIONS = 'agentMentionsEnabled:"draft"===Ge?t:Ue?.agent??!1';
115
+ const CLAUDE_AGENT_MENTION_FILTER = 'return e.filter(e=>"user"===e.source&&(!n||e.name.toLowerCase().includes(n))).slice(0,10)';
116
+ const IMPEL_CLAUDE_BOUND_AGENT_MENTION_FILTER = 'return e.filter(e=>"user"===e.source&&(!n||e.name.toLowerCase().includes(n))&&("string"!=typeof x||e.name===x)).slice(0,10)';
120
117
  const CLAUDE_AGENT_MENTION_SELECT = 'onSelect:t=>(e(String(t)),Promise.resolve(null))';
121
- const IMPEL_CLAUDE_BOUND_AGENT_MENTION_SELECT = 'onSelect:t=>"string"==typeof y?Promise.resolve({chipText:String(t)}):(e(String(t)),Promise.resolve(null))';
118
+ const IMPEL_CLAUDE_BOUND_AGENT_MENTION_SELECT = 'onSelect:t=>"string"==typeof x?Promise.resolve({chipText:String(t)}):(e(String(t)),Promise.resolve(null))';
122
119
  const LEGACY_CLAUDE_SAFE_STORAGE_NAME = "Claude";
123
120
  const CLAUDE_SAFE_STORAGE_METADATA = "safe-storage.json";
124
121
  // The pinned Claude renderer decides desktop-vs-web mode SOLELY by matching its
@@ -157,7 +154,8 @@ const escapedDisplayPrefix = RUNTIME_BRAND.apps.displayPrefix.replace(/[.*+?^${}
157
154
  const LEGACY_TRAPPED_CLAUDE_NAME = new RegExp(`^${escapedDisplayPrefix} Claude( \\[[^\\]]+\\])?$`, "u");
158
155
 
159
156
  export const FALLBACK_MODELS = [
160
- { id: "claude-opus-4-8", provider: "claude", display_name: "Claude Opus 4.8", family: "opus", family_default: true, default: true, context_window: 200000 },
157
+ { id: "claude-opus-5", provider: "claude", display_name: "Claude Opus 5", family: "opus", family_default: true, default: true, supports_1m: true, context_window: 1000000 },
158
+ { id: "claude-opus-4-8", provider: "claude", display_name: "Claude Opus 4.8", family: "opus", supports_1m: true, context_window: 1000000 },
161
159
  { id: "claude-sonnet-5", provider: "claude", display_name: "Claude Sonnet 5", family: "sonnet", family_default: true, context_window: 200000 },
162
160
  { id: "claude-sonnet-4-6", provider: "claude", display_name: "Claude Sonnet 4.6", family: "sonnet", context_window: 200000 },
163
161
  { id: "claude-haiku-4-5", provider: "claude", display_name: "Claude Haiku 4.5", family: "haiku", family_default: true, context_window: 200000 },
@@ -240,7 +238,7 @@ export const CURRENT_CONFIG_VERSION = 21;
240
238
  // — which is what made every `impel update` re-trigger macOS permission
241
239
  // prompts. Bump this ONLY when a code change alters the bytes of a built
242
240
  // bundle; leave it alone for changes that don't touch bundle contents.
243
- export const BUNDLE_BUILD_FINGERPRINT = "bundle-2026-07-21.2";
241
+ export const BUNDLE_BUILD_FINGERPRINT = "bundle-2026-07-25.1";
244
242
 
245
243
  /** Parse the tenant's install manifest, or null when absent/corrupt. */
246
244
  export function readTenantManifest(homeDir = os.homedir(), tenantId = null) {
@@ -835,10 +833,21 @@ export function isVendableVendorApp(target, appPath) {
835
833
  const hasExecutable = definition.executableNames.some((name) =>
836
834
  fs.existsSync(path.join(appPath, "Contents", "MacOS", name)),
837
835
  );
836
+ const hasClaudeSimulator = target !== "claude" || (
837
+ fs.existsSync(path.join(
838
+ appPath,
839
+ "Contents", "Helpers", "Claude iOS Sim.app", "Contents", "Info.plist",
840
+ ))
841
+ && fs.existsSync(path.join(
842
+ appPath,
843
+ "Contents", "Helpers", "Claude iOS Sim.app", "Contents", "MacOS", "claude-ios-sim",
844
+ ))
845
+ );
838
846
  return (
839
847
  hasExecutable
840
848
  && fs.existsSync(path.join(appPath, "Contents", "Info.plist"))
841
849
  && fs.existsSync(path.join(appPath, "Contents", "Resources", "app.asar"))
850
+ && hasClaudeSimulator
842
851
  );
843
852
  }
844
853
 
@@ -1022,11 +1031,7 @@ function writeClaudeConfig(paths, config, models) {
1022
1031
  const experimental = crossAppModelsEnabled(config);
1023
1032
  const claudeModels = experimental
1024
1033
  ? projectClaudeDesktopModels(models)
1025
- : models.filter((model) => model.provider === "claude").map((model) => ({
1026
- name: model.id,
1027
- ...(model.family ? { anthropicFamilyTier: model.family } : {}),
1028
- ...(model.family_default ? { isFamilyDefault: true } : {}),
1029
- }));
1034
+ : models.filter((model) => model.provider === "claude").map(nativeClaudeDesktopModel);
1030
1035
  const body = {
1031
1036
  inferenceProvider: "gateway",
1032
1037
  inferenceGatewayBaseUrl: experimental
@@ -1061,6 +1066,28 @@ function writeClaudeConfig(paths, config, models) {
1061
1066
  }, null, 2) + "\n", 0o600);
1062
1067
  }
1063
1068
 
1069
+ function nativeClaudeDesktopModel(model) {
1070
+ const capability = model.client_capabilities?.claude_desktop;
1071
+ const label = typeof capability?.label === "string" && capability.label.trim()
1072
+ ? capability.label
1073
+ : null;
1074
+ const family = typeof capability?.anthropic_family_tier === "string"
1075
+ && capability.anthropic_family_tier
1076
+ ? capability.anthropic_family_tier
1077
+ : model.family;
1078
+ return {
1079
+ name: model.id,
1080
+ ...(label ? { labelOverride: label } : {}),
1081
+ ...(family ? { anthropicFamilyTier: family } : {}),
1082
+ ...(capability?.family_default === true || model.family_default === true
1083
+ ? { isFamilyDefault: true }
1084
+ : {}),
1085
+ ...(capability?.supports_1m === true || model.supports_1m === true
1086
+ ? { supports1m: true }
1087
+ : {}),
1088
+ };
1089
+ }
1090
+
1064
1091
  function writeClaudeCodeSettings(paths) {
1065
1092
  const settingsPath = path.join(paths.claude.userData, "settings.json");
1066
1093
  let settings = {};
@@ -1484,6 +1511,12 @@ exec "$NODE" "$CLI" token${tenantArgs}
1484
1511
 
1485
1512
  function writeVendoredClaudeBundle(paths, vendorPath, gatewayUrl, safeStorageName) {
1486
1513
  if (!vendorPath) throw new Error("Claude vendor app is unavailable");
1514
+ if (!isVendableVendorApp("claude", vendorPath)) {
1515
+ const bundleId = readBundleIdentifier(vendorPath);
1516
+ throw new Error(
1517
+ `unsupported Claude bundle layout in ${vendorPath}${bundleId ? ` (bundle id ${bundleId})` : ""}`,
1518
+ );
1519
+ }
1487
1520
  const executableName = APP_DEFINITIONS.claude.executableNames.find((name) => (
1488
1521
  fs.existsSync(path.join(vendorPath, "Contents", "MacOS", name))
1489
1522
  ));
@@ -1832,23 +1865,6 @@ function patchFixedWidthAsarString(asarPath, original, replacement, label) {
1832
1865
  function patchClaudePlanUsageRequest(asarPath) {
1833
1866
  const archive = fs.readFileSync(asarPath);
1834
1867
  const source = archive.toString("latin1");
1835
- const legacyPatches = [];
1836
- for (
1837
- let offset = source.indexOf(CLAUDE_PLAN_USAGE_REQUEST);
1838
- offset !== -1;
1839
- offset = source.indexOf(CLAUDE_PLAN_USAGE_REQUEST, offset + CLAUDE_PLAN_USAGE_REQUEST.length)
1840
- ) {
1841
- legacyPatches.push({
1842
- offset,
1843
- original: CLAUDE_PLAN_USAGE_REQUEST,
1844
- replacement: IMPEL_CLAUDE_PLAN_USAGE_REQUEST,
1845
- });
1846
- }
1847
- if (legacyPatches.length > 0) {
1848
- return applyFixedWidthAsarPatches(
1849
- asarPath, archive, legacyPatches, "Claude plan-usage gateway request",
1850
- );
1851
- }
1852
1868
  const credentialAccessor = source.match(
1853
1869
  /function ([A-Za-z_$][\w$]*)\(e\)\{var t;return [\s\S]{0,500}?\[custom-3p\] Credentials loaded from managed config/u,
1854
1870
  )?.[1];
@@ -1864,10 +1880,10 @@ function patchClaudePlanUsageRequest(asarPath) {
1864
1880
  return applyFixedWidthAsarPatches(asarPath, archive, [], "Claude plan-usage gateway request");
1865
1881
  }
1866
1882
 
1867
- 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;
1883
+ const requestPattern = /const ([A-Za-z_$][\w$]*)=`\$\{[A-Za-z_$][\w$]*\(\)\}\/api\/organizations\/\$\{[A-Za-z_$][\w$]*\}\/usage\$\{[A-Za-z_$][\w$]*\?"\?skip_spend=1":""\}`,([A-Za-z_$][\w$]*)=await ([A-Za-z_$][\w$]*)\.net\.fetch\(\1,\{signal:AbortSignal\.timeout\([A-Za-z_$][\w$]*\)\}\)/gu;
1868
1884
  const patches = [...source.matchAll(requestPattern)].map((match) => {
1869
1885
  const original = match[0];
1870
- const replacement = `${match[1]}.net.fetch("https://gateway.useimpel.com/ui/a/u",{headers:{key:${credentialAccessor}(${configAccessor}()).apiKey}})`;
1886
+ const replacement = `const ${match[1]}="https://gateway.useimpel.com/ui/a/u",${match[2]}=await ${match[3]}.net.fetch(${match[1]},{headers:{key:${credentialAccessor}(${configAccessor}()).apiKey}})`;
1871
1887
  if (replacement.length > original.length) {
1872
1888
  throw new Error("Claude plan-usage gateway request replacement no longer fits the vendor ASAR contract");
1873
1889
  }
@@ -161,7 +161,6 @@ export async function cmdDoctor(argv) {
161
161
  flags.gateway !== undefined
162
162
  ? flags.gateway
163
163
  : process.env[brandedEnvironmentName("GATEWAY_URL")]
164
- || process.env.IMPEL_GATEWAY_URL
165
164
  || config.gatewayUrl
166
165
  || resolveDefaultGateway(),
167
166
  );
@@ -21,7 +21,7 @@ import { brandedEnvironmentName, RUNTIME_BRAND } from "../runtimeBrand.js";
21
21
  const MANAGED_SESSION_HOOK_FLAG = `${RUNTIME_BRAND.cli.command}-managed-session-hook-v1`;
22
22
 
23
23
  function environmentValue(suffix) {
24
- return process.env[brandedEnvironmentName(suffix)] ?? process.env[`IMPEL_${suffix}`];
24
+ return process.env[brandedEnvironmentName(suffix)];
25
25
  }
26
26
 
27
27
  const SPEC = {
@@ -12,7 +12,12 @@ import { tenantCredential } from "../tenants.js";
12
12
  export const DEFAULT_RECOVERY_ORG = "impel";
13
13
  const REQUEST_TIMEOUT_MS = 90_000;
14
14
  const MAX_RETRIES = 2;
15
- const FALLBACK_MODELS = ["claude-sonnet-5", "claude-haiku-4-5", "claude-opus-4-8"];
15
+ const FALLBACK_MODELS = [
16
+ "claude-sonnet-5",
17
+ "claude-haiku-4-5",
18
+ "claude-opus-5",
19
+ "claude-opus-4-8",
20
+ ];
16
21
 
17
22
  function recoveryOrgCandidates(config, environment) {
18
23
  const preferred = String(environment.IMPEL_RECOVERY_ORG || DEFAULT_RECOVERY_ORG).trim();
@@ -24,9 +24,7 @@ const SAFE_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/u;
24
24
  const TASK_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u;
25
25
 
26
26
  function brandedEnvironmentValue(suffix) {
27
- const branded = process.env[brandedEnvironmentName(suffix)];
28
- if (branded !== undefined) return branded;
29
- return process.env[`IMPEL_${suffix}`];
27
+ return process.env[brandedEnvironmentName(suffix)];
30
28
  }
31
29
 
32
30
  function stateRoot() {
@@ -509,7 +507,7 @@ function repositoryMetadata(cwd) {
509
507
  };
510
508
  }
511
509
 
512
- function sessionsUrl(config) {
510
+ export function resolveSessionsUrl(config) {
513
511
  return String(
514
512
  brandedEnvironmentValue("SESSIONS_URL")
515
513
  || config?.sessionsUrl
@@ -545,7 +543,7 @@ async function apiRequest(config, tenantId, route, options = {}) {
545
543
  const controller = new AbortController();
546
544
  const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
547
545
  try {
548
- const response = await fetch(`${sessionsUrl(config)}${route}`, {
546
+ const response = await fetch(`${resolveSessionsUrl(config)}${route}`, {
549
547
  ...options,
550
548
  headers: requestHeaders(config, tenantId, options.headers),
551
549
  signal: controller.signal,