impel-cli 0.17.2 → 0.17.3

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 +15 -0
  2. package/package.json +1 -1
  3. package/src/apps.js +42 -12
package/README.md CHANGED
@@ -311,3 +311,18 @@ run a real package manager, or modify native personal profiles.
311
311
 
312
312
  CI runs on Ubuntu, macOS, and Windows with supported Node versions. See
313
313
  [RELEASING.md](RELEASING.md) for the npm release process.
314
+
315
+ The macOS config-contract job downloads the exact ChatGPT archive declared in
316
+ `PINNED_VENDOR_APPS`, verifies its checksum, signature, app version, and
317
+ embedded Codex version, then validates a deterministic generated profile with
318
+ Codex's strict config loader. A ChatGPT pin update must also update the embedded
319
+ Codex version and the reviewed config fixture when its contract changes.
320
+
321
+ The pinned Claude contract job likewise force-downloads the exact desktop app,
322
+ verifies the embedded Claude Code release manifest, builds the managed bundle
323
+ with ad-hoc signing, and checks deterministic generated desktop and Claude Code
324
+ settings against a reviewed fixture. The manifest-selected Claude Code runtime
325
+ runs `doctor` in bare mode against an isolated profile; CI intercepts the
326
+ `security` command so the check cannot read, reset, unlock, or modify a
327
+ Keychain. A Claude pin update must also update its `claudeCodeVersion` and
328
+ `claudeCodeCommit` pins and reviewed contract fixture.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "impel-cli",
3
- "version": "0.17.2",
3
+ "version": "0.17.3",
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
@@ -43,6 +43,8 @@ const APP_DEFINITIONS = {
43
43
  export const PINNED_VENDOR_APPS = Object.freeze({
44
44
  claude: Object.freeze({
45
45
  version: "1.20186.9",
46
+ claudeCodeVersion: "2.1.209",
47
+ claudeCodeCommit: "0fe048596fd45e79e99353cbe2c3d1b1ac069568",
46
48
  bundleName: "Claude.app",
47
49
  downloads: Object.freeze({
48
50
  arm64: Object.freeze({
@@ -57,6 +59,7 @@ export const PINNED_VENDOR_APPS = Object.freeze({
57
59
  }),
58
60
  chatgpt: Object.freeze({
59
61
  version: "26.707.72221",
62
+ codexVersion: "0.144.2",
60
63
  bundleName: "ChatGPT.app",
61
64
  downloads: Object.freeze({
62
65
  arm64: Object.freeze({
@@ -589,6 +592,7 @@ export function installManagedAppFiles({
589
592
  writeTokenHelperFile = true,
590
593
  claudeUserData = null,
591
594
  chatgptInvocations = null,
595
+ generatedAt = new Date().toISOString(),
592
596
  }) {
593
597
  // true = rebuild every target's bundle; false = configs only; an array
594
598
  // limits the expensive rebuild to just the stale targets.
@@ -621,7 +625,14 @@ export function installManagedAppFiles({
621
625
  ensureClaudeSessionHooks(paths.claude.userData, config.tenantId, "claude_desktop");
622
626
  }
623
627
  else {
624
- writeChatGPTConfig(paths, config, models, vendorCodexModels, chatgptInvocations);
628
+ writeChatGPTConfig(
629
+ paths,
630
+ config,
631
+ models,
632
+ vendorCodexModels,
633
+ chatgptInvocations,
634
+ generatedAt,
635
+ );
625
636
  ensureCodexSessionHooks(paths.chatgpt.codexHome, config.tenantId, "codex_desktop");
626
637
  }
627
638
  // Non-bundle targets are the background-refresh / fast-open path: configs,
@@ -666,7 +677,7 @@ export function installManagedAppFiles({
666
677
  config,
667
678
  ),
668
679
  models: models.map((model) => model.id),
669
- updatedAt: new Date().toISOString(),
680
+ updatedAt: generatedAt,
670
681
  }, null, 2) + "\n", 0o600);
671
682
  return installed;
672
683
  }
@@ -815,10 +826,13 @@ function installPinnedVendorApp(target, homeDir) {
815
826
  }
816
827
  }
817
828
 
818
- export function ensureVendorApp(target, { homeDir = os.homedir() } = {}) {
829
+ export function ensureVendorApp(target, {
830
+ homeDir = os.homedir(),
831
+ forceDownload = false,
832
+ } = {}) {
819
833
  const definition = APP_DEFINITIONS[target];
820
834
  const pin = PINNED_VENDOR_APPS[target];
821
- const current = findVendorApp(target, homeDir);
835
+ const current = forceDownload ? null : findVendorApp(target, homeDir);
822
836
  if (current) return { path: current, action: "existing", note: `verified ${pin.version}` };
823
837
 
824
838
  // Name-collision transparency: e.g. OpenAI's native ChatGPT chat app
@@ -974,7 +988,14 @@ function writeClaudeDefaultApp(paths) {
974
988
  writeAtomic(configPath, JSON.stringify(next, null, 2) + "\n", 0o600);
975
989
  }
976
990
 
977
- function writeChatGPTConfig(paths, config, models, vendorCodexModels, invocations = null) {
991
+ function writeChatGPTConfig(
992
+ paths,
993
+ config,
994
+ models,
995
+ vendorCodexModels,
996
+ invocations = null,
997
+ generatedAt = new Date().toISOString(),
998
+ ) {
978
999
  const experimental = crossAppModelsEnabled(config);
979
1000
  const orderedModels = experimental
980
1001
  ? [...models.filter((model) => model.provider === "codex"), ...models.filter((model) => model.provider === "claude")]
@@ -1008,7 +1029,7 @@ function writeChatGPTConfig(paths, config, models, vendorCodexModels, invocation
1008
1029
  ? configuredTier
1009
1030
  : (configuredTier === "fast" && supportedTiers.includes("priority") ? "priority" : null);
1010
1031
  const catalog = {
1011
- fetched_at: new Date().toISOString(),
1032
+ fetched_at: generatedAt,
1012
1033
  client_version: "impel-managed-2",
1013
1034
  models: codexModels,
1014
1035
  };
@@ -1209,18 +1230,27 @@ function codexCatalogEntry(model, index, vendorModel) {
1209
1230
  function readVendorCodexModels(vendorPath) {
1210
1231
  const binary = vendorPath && path.join(vendorPath, "Contents", "Resources", "codex");
1211
1232
  if (!binary || !fs.existsSync(binary)) return new Map();
1212
- const env = { ...process.env };
1213
- delete env.CODEX_HOME;
1214
- for (const args of [["debug", "models"], ["debug", "models", "--bundled"]]) {
1215
- const result = spawnSync(binary, args, { encoding: "utf8", env, maxBuffer: 20 * 1024 * 1024 });
1216
- if (result.status !== 0) continue;
1233
+ const codexHome = fs.mkdtempSync(path.join(os.tmpdir(), "impel-vendor-codex-models-"));
1234
+ try {
1235
+ // Vendor enrichment must come only from the exact bundled release. Reading
1236
+ // the operator's native CODEX_HOME here would make generated app configs
1237
+ // depend on unrelated local state and could trigger an online catalog read.
1238
+ const env = { ...process.env, CODEX_HOME: codexHome };
1239
+ const result = spawnSync(binary, ["debug", "models", "--bundled"], {
1240
+ encoding: "utf8",
1241
+ env,
1242
+ maxBuffer: 20 * 1024 * 1024,
1243
+ });
1244
+ if (result.status !== 0) return new Map();
1217
1245
  try {
1218
1246
  const payload = JSON.parse(result.stdout);
1219
1247
  const models = Array.isArray(payload) ? payload : payload.models;
1220
1248
  if (Array.isArray(models)) return new Map(models.map((model) => [model.slug, model]));
1221
1249
  } catch {
1222
- // Fall through to the bundled catalog or the Impel-generated schema.
1250
+ // Fall through to the Impel-generated schema.
1223
1251
  }
1252
+ } finally {
1253
+ fs.rmSync(codexHome, { recursive: true, force: true });
1224
1254
  }
1225
1255
  return new Map();
1226
1256
  }