impel-cli 0.17.2 → 0.17.4
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/README.md +15 -0
- package/package.json +1 -1
- package/src/apps.js +123 -87
- package/src/windowsApps.js +39 -3
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
package/src/apps.js
CHANGED
|
@@ -42,30 +42,33 @@ const APP_DEFINITIONS = {
|
|
|
42
42
|
// with its compatibility patches. Updating a pin requires a new CLI release.
|
|
43
43
|
export const PINNED_VENDOR_APPS = Object.freeze({
|
|
44
44
|
claude: Object.freeze({
|
|
45
|
-
version: "1.
|
|
45
|
+
version: "1.22209.3",
|
|
46
|
+
claudeCodeVersion: "2.1.215",
|
|
47
|
+
claudeCodeCommit: "316ce99628e89900bf0b1328fed3b8fec0c0c92d",
|
|
46
48
|
bundleName: "Claude.app",
|
|
47
49
|
downloads: Object.freeze({
|
|
48
50
|
arm64: Object.freeze({
|
|
49
|
-
url: "https://downloads.claude.ai/releases/darwin/universal/1.
|
|
50
|
-
sha256: "
|
|
51
|
+
url: "https://downloads.claude.ai/releases/darwin/universal/1.22209.3/Claude-babe11577dfefe3e209c06bd674628d862f0dbae.zip",
|
|
52
|
+
sha256: "b110e0fdb7b2601d80dfdd7893f7811de382de84c4586113e7873e28f17680fc",
|
|
51
53
|
}),
|
|
52
54
|
x64: Object.freeze({
|
|
53
|
-
url: "https://downloads.claude.ai/releases/darwin/universal/1.
|
|
54
|
-
sha256: "
|
|
55
|
+
url: "https://downloads.claude.ai/releases/darwin/universal/1.22209.3/Claude-babe11577dfefe3e209c06bd674628d862f0dbae.zip",
|
|
56
|
+
sha256: "b110e0fdb7b2601d80dfdd7893f7811de382de84c4586113e7873e28f17680fc",
|
|
55
57
|
}),
|
|
56
58
|
}),
|
|
57
59
|
}),
|
|
58
60
|
chatgpt: Object.freeze({
|
|
59
|
-
version: "26.
|
|
61
|
+
version: "26.715.31925",
|
|
62
|
+
codexVersion: "0.145.0-alpha.18",
|
|
60
63
|
bundleName: "ChatGPT.app",
|
|
61
64
|
downloads: Object.freeze({
|
|
62
65
|
arm64: Object.freeze({
|
|
63
|
-
url: "https://persistent.oaistatic.com/codex-app-prod/ChatGPT-darwin-arm64-26.
|
|
64
|
-
sha256: "
|
|
66
|
+
url: "https://persistent.oaistatic.com/codex-app-prod/ChatGPT-darwin-arm64-26.715.31925.zip",
|
|
67
|
+
sha256: "a0cded116975c71c723dc8601c07402533b5ff23881d15e52cfb6dc71413e45b",
|
|
65
68
|
}),
|
|
66
69
|
x64: Object.freeze({
|
|
67
|
-
url: "https://persistent.oaistatic.com/codex-app-prod/ChatGPT-darwin-x64-26.
|
|
68
|
-
sha256: "
|
|
70
|
+
url: "https://persistent.oaistatic.com/codex-app-prod/ChatGPT-darwin-x64-26.715.31925.zip",
|
|
71
|
+
sha256: "f18972b893207ff5cbf047599369ce96768c2ace25128a09315058ef0a5cfdcb",
|
|
69
72
|
}),
|
|
70
73
|
}),
|
|
71
74
|
}),
|
|
@@ -82,12 +85,10 @@ const ULTRA_REASONING_LEVEL = { effort: "ultra", description: "Maximum reasoning
|
|
|
82
85
|
const FAST_SERVICE_TIER = { id: "priority", name: "Fast", description: "1.5x speed, increased usage" };
|
|
83
86
|
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;
|
|
84
87
|
const CHATGPT_RESOURCE_ROOT = "linked_vendor_resources_with_patched_asar";
|
|
85
|
-
const CLAUDE_PLAN_USAGE_GUARD = "if(!
|
|
86
|
-
const IMPEL_CLAUDE_PLAN_USAGE_GUARD =
|
|
87
|
-
const
|
|
88
|
-
const
|
|
89
|
-
const CLAUDE_DRAFT_AGENT_MENTIONS = 'agentMentionsEnabled:"draft"===Be&&s';
|
|
90
|
-
const IMPEL_CLAUDE_BOUND_AGENT_MENTIONS = 'agentMentionsEnabled:"draft"===Be?s:Le?.agent??!1';
|
|
88
|
+
const CLAUDE_PLAN_USAGE_GUARD = "if(!Vve()){f8();return}if(!et().hasOrgPolicyBackend())return;if((r=yr())";
|
|
89
|
+
const IMPEL_CLAUDE_PLAN_USAGE_GUARD = `if(!Vve()){f8();return}${"".padEnd("if(!et().hasOrgPolicyBackend())return;".length, " ")}if((r=yr())`;
|
|
90
|
+
const CLAUDE_DRAFT_AGENT_MENTIONS = 'agentMentionsEnabled:"draft"===He&&s';
|
|
91
|
+
const IMPEL_CLAUDE_BOUND_AGENT_MENTIONS = 'agentMentionsEnabled:"draft"===He?s:Le?.agent??!1';
|
|
91
92
|
const CLAUDE_AGENT_MENTION_FILTER = 'return e.filter(e=>"user"===e.source&&(!s||e.name.toLowerCase().includes(s))).slice(0,10)';
|
|
92
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)';
|
|
93
94
|
const CLAUDE_AGENT_MENTION_SELECT = 'onSelect:t=>(e(String(t)),Promise.resolve(null))';
|
|
@@ -173,7 +174,7 @@ export const CURRENT_CONFIG_VERSION = 16;
|
|
|
173
174
|
// — which is what made every `impel update` re-trigger macOS permission
|
|
174
175
|
// prompts. Bump this ONLY when a code change alters the bytes of a built
|
|
175
176
|
// bundle; leave it alone for changes that don't touch bundle contents.
|
|
176
|
-
export const BUNDLE_BUILD_FINGERPRINT = "bundle-2026-07-
|
|
177
|
+
export const BUNDLE_BUILD_FINGERPRINT = "bundle-2026-07-20.1";
|
|
177
178
|
|
|
178
179
|
/** Parse the tenant's install manifest, or null when absent/corrupt. */
|
|
179
180
|
export function readTenantManifest(homeDir = os.homedir(), tenantId = null) {
|
|
@@ -279,12 +280,15 @@ function wrapperLaunchesOnTahoe(status) {
|
|
|
279
280
|
|
|
280
281
|
function managedClaudeRendererMatchesCompatibility(status, compatibility) {
|
|
281
282
|
try {
|
|
282
|
-
const
|
|
283
|
-
if (!
|
|
283
|
+
const renderers = compatibility.rendererAssets?.agentMentions;
|
|
284
|
+
if (!Array.isArray(renderers) || renderers.length === 0) return false;
|
|
284
285
|
const root = path.resolve(status.launcher, "Contents", "Resources", "ion-dist");
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
286
|
+
return renderers.every((renderer) => {
|
|
287
|
+
if (!renderer?.path || !renderer.sha256 || path.isAbsolute(renderer.path)) return false;
|
|
288
|
+
const target = path.resolve(root, renderer.path);
|
|
289
|
+
if (!target.startsWith(`${root}${path.sep}`)) return false;
|
|
290
|
+
return sha256(fs.readFileSync(target)) === renderer.sha256;
|
|
291
|
+
});
|
|
288
292
|
} catch {
|
|
289
293
|
return false;
|
|
290
294
|
}
|
|
@@ -589,6 +593,7 @@ export function installManagedAppFiles({
|
|
|
589
593
|
writeTokenHelperFile = true,
|
|
590
594
|
claudeUserData = null,
|
|
591
595
|
chatgptInvocations = null,
|
|
596
|
+
generatedAt = new Date().toISOString(),
|
|
592
597
|
}) {
|
|
593
598
|
// true = rebuild every target's bundle; false = configs only; an array
|
|
594
599
|
// limits the expensive rebuild to just the stale targets.
|
|
@@ -621,7 +626,14 @@ export function installManagedAppFiles({
|
|
|
621
626
|
ensureClaudeSessionHooks(paths.claude.userData, config.tenantId, "claude_desktop");
|
|
622
627
|
}
|
|
623
628
|
else {
|
|
624
|
-
writeChatGPTConfig(
|
|
629
|
+
writeChatGPTConfig(
|
|
630
|
+
paths,
|
|
631
|
+
config,
|
|
632
|
+
models,
|
|
633
|
+
vendorCodexModels,
|
|
634
|
+
chatgptInvocations,
|
|
635
|
+
generatedAt,
|
|
636
|
+
);
|
|
625
637
|
ensureCodexSessionHooks(paths.chatgpt.codexHome, config.tenantId, "codex_desktop");
|
|
626
638
|
}
|
|
627
639
|
// Non-bundle targets are the background-refresh / fast-open path: configs,
|
|
@@ -666,7 +678,7 @@ export function installManagedAppFiles({
|
|
|
666
678
|
config,
|
|
667
679
|
),
|
|
668
680
|
models: models.map((model) => model.id),
|
|
669
|
-
updatedAt:
|
|
681
|
+
updatedAt: generatedAt,
|
|
670
682
|
}, null, 2) + "\n", 0o600);
|
|
671
683
|
return installed;
|
|
672
684
|
}
|
|
@@ -815,10 +827,13 @@ function installPinnedVendorApp(target, homeDir) {
|
|
|
815
827
|
}
|
|
816
828
|
}
|
|
817
829
|
|
|
818
|
-
export function ensureVendorApp(target, {
|
|
830
|
+
export function ensureVendorApp(target, {
|
|
831
|
+
homeDir = os.homedir(),
|
|
832
|
+
forceDownload = false,
|
|
833
|
+
} = {}) {
|
|
819
834
|
const definition = APP_DEFINITIONS[target];
|
|
820
835
|
const pin = PINNED_VENDOR_APPS[target];
|
|
821
|
-
const current = findVendorApp(target, homeDir);
|
|
836
|
+
const current = forceDownload ? null : findVendorApp(target, homeDir);
|
|
822
837
|
if (current) return { path: current, action: "existing", note: `verified ${pin.version}` };
|
|
823
838
|
|
|
824
839
|
// Name-collision transparency: e.g. OpenAI's native ChatGPT chat app
|
|
@@ -974,7 +989,14 @@ function writeClaudeDefaultApp(paths) {
|
|
|
974
989
|
writeAtomic(configPath, JSON.stringify(next, null, 2) + "\n", 0o600);
|
|
975
990
|
}
|
|
976
991
|
|
|
977
|
-
function writeChatGPTConfig(
|
|
992
|
+
function writeChatGPTConfig(
|
|
993
|
+
paths,
|
|
994
|
+
config,
|
|
995
|
+
models,
|
|
996
|
+
vendorCodexModels,
|
|
997
|
+
invocations = null,
|
|
998
|
+
generatedAt = new Date().toISOString(),
|
|
999
|
+
) {
|
|
978
1000
|
const experimental = crossAppModelsEnabled(config);
|
|
979
1001
|
const orderedModels = experimental
|
|
980
1002
|
? [...models.filter((model) => model.provider === "codex"), ...models.filter((model) => model.provider === "claude")]
|
|
@@ -1008,7 +1030,7 @@ function writeChatGPTConfig(paths, config, models, vendorCodexModels, invocation
|
|
|
1008
1030
|
? configuredTier
|
|
1009
1031
|
: (configuredTier === "fast" && supportedTiers.includes("priority") ? "priority" : null);
|
|
1010
1032
|
const catalog = {
|
|
1011
|
-
fetched_at:
|
|
1033
|
+
fetched_at: generatedAt,
|
|
1012
1034
|
client_version: "impel-managed-2",
|
|
1013
1035
|
models: codexModels,
|
|
1014
1036
|
};
|
|
@@ -1209,18 +1231,27 @@ function codexCatalogEntry(model, index, vendorModel) {
|
|
|
1209
1231
|
function readVendorCodexModels(vendorPath) {
|
|
1210
1232
|
const binary = vendorPath && path.join(vendorPath, "Contents", "Resources", "codex");
|
|
1211
1233
|
if (!binary || !fs.existsSync(binary)) return new Map();
|
|
1212
|
-
const
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1234
|
+
const codexHome = fs.mkdtempSync(path.join(os.tmpdir(), "impel-vendor-codex-models-"));
|
|
1235
|
+
try {
|
|
1236
|
+
// Vendor enrichment must come only from the exact bundled release. Reading
|
|
1237
|
+
// the operator's native CODEX_HOME here would make generated app configs
|
|
1238
|
+
// depend on unrelated local state and could trigger an online catalog read.
|
|
1239
|
+
const env = { ...process.env, CODEX_HOME: codexHome };
|
|
1240
|
+
const result = spawnSync(binary, ["debug", "models", "--bundled"], {
|
|
1241
|
+
encoding: "utf8",
|
|
1242
|
+
env,
|
|
1243
|
+
maxBuffer: 20 * 1024 * 1024,
|
|
1244
|
+
});
|
|
1245
|
+
if (result.status !== 0) return new Map();
|
|
1217
1246
|
try {
|
|
1218
1247
|
const payload = JSON.parse(result.stdout);
|
|
1219
1248
|
const models = Array.isArray(payload) ? payload : payload.models;
|
|
1220
1249
|
if (Array.isArray(models)) return new Map(models.map((model) => [model.slug, model]));
|
|
1221
1250
|
} catch {
|
|
1222
|
-
// Fall through to the
|
|
1251
|
+
// Fall through to the Impel-generated schema.
|
|
1223
1252
|
}
|
|
1253
|
+
} finally {
|
|
1254
|
+
fs.rmSync(codexHome, { recursive: true, force: true });
|
|
1224
1255
|
}
|
|
1225
1256
|
return new Map();
|
|
1226
1257
|
}
|
|
@@ -1376,7 +1407,7 @@ function writeVendoredClaudeBundle(paths, vendorPath, gatewayUrl, safeStorageNam
|
|
|
1376
1407
|
const vendorExecutable = path.join(macos, executableName);
|
|
1377
1408
|
const signingIdentity = resolveVendoredAppSigningIdentity(vendorExecutable);
|
|
1378
1409
|
writeAtomic(path.join(staging, "Contents", "Resources", "impel-compatibility.json"), JSON.stringify({
|
|
1379
|
-
schemaVersion:
|
|
1410
|
+
schemaVersion: 5,
|
|
1380
1411
|
cliVersion: CLI_VERSION,
|
|
1381
1412
|
bundleFingerprint: BUNDLE_BUILD_FINGERPRINT,
|
|
1382
1413
|
signingMode: signingModeForIdentity(signingIdentity),
|
|
@@ -1397,10 +1428,7 @@ function writeVendoredClaudeBundle(paths, vendorPath, gatewayUrl, safeStorageNam
|
|
|
1397
1428
|
followupBoundAgentChip: agentMentionPatches.chip,
|
|
1398
1429
|
},
|
|
1399
1430
|
rendererAssets: {
|
|
1400
|
-
agentMentions:
|
|
1401
|
-
path: agentMentionPatches.path,
|
|
1402
|
-
sha256: agentMentionPatches.sha256,
|
|
1403
|
-
},
|
|
1431
|
+
agentMentions: agentMentionPatches.assets,
|
|
1404
1432
|
},
|
|
1405
1433
|
asarHeaderSha256: newAsarHash,
|
|
1406
1434
|
}, null, 2) + "\n", 0o644);
|
|
@@ -1546,42 +1574,63 @@ function patchClaudeFollowupAgentMentions(bundle) {
|
|
|
1546
1574
|
|
|
1547
1575
|
const ionDist = path.join(bundle, "Contents", "Resources", "ion-dist");
|
|
1548
1576
|
const contracts = [
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
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
|
+
},
|
|
1552
1592
|
];
|
|
1553
|
-
const
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1593
|
+
const sources = new Map(listJavaScriptFiles(ionDist).map((candidate) => [
|
|
1594
|
+
candidate,
|
|
1595
|
+
fs.readFileSync(candidate, "utf8"),
|
|
1596
|
+
]));
|
|
1597
|
+
const matches = new Map(contracts.map(({ key }) => [key, []]));
|
|
1598
|
+
for (const [candidate, source] of sources) {
|
|
1599
|
+
for (const contract of contracts) {
|
|
1600
|
+
const count = exactTextMatchCount(source, contract.original);
|
|
1601
|
+
if (count > 0) matches.get(contract.key).push({ candidate, count });
|
|
1602
|
+
}
|
|
1561
1603
|
}
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
let source = fs.readFileSync(renderer, "utf8");
|
|
1565
|
-
const enable = exactTextMatchCount(source, CLAUDE_DRAFT_AGENT_MENTIONS);
|
|
1566
|
-
const filter = exactTextMatchCount(source, CLAUDE_AGENT_MENTION_FILTER);
|
|
1567
|
-
const chip = exactTextMatchCount(source, CLAUDE_AGENT_MENTION_SELECT);
|
|
1568
|
-
for (const [label, count] of [["enable", enable], ["filter", filter], ["chip", chip]]) {
|
|
1604
|
+
for (const contract of contracts) {
|
|
1605
|
+
const count = matches.get(contract.key).reduce((total, match) => total + match.count, 0);
|
|
1569
1606
|
if (count !== 1) {
|
|
1570
|
-
throw new Error(`Claude follow-up agent-mention ${
|
|
1607
|
+
throw new Error(`Claude follow-up agent-mention ${contract.key} patch expected 1 match, found ${count}`);
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
const assets = [];
|
|
1612
|
+
for (const candidate of [...new Set(
|
|
1613
|
+
[...matches.values()].flatMap((entries) => entries.map(({ candidate: file }) => file)),
|
|
1614
|
+
)].sort()) {
|
|
1615
|
+
const source = fs.readFileSync(candidate, "utf8");
|
|
1616
|
+
const applicable = contracts.filter((contract) => source.includes(contract.original));
|
|
1617
|
+
let patched = source;
|
|
1618
|
+
for (const contract of applicable) {
|
|
1619
|
+
patched = patched.replace(contract.original, contract.replacement);
|
|
1571
1620
|
}
|
|
1621
|
+
writeAtomic(candidate, patched, fs.statSync(candidate).mode & 0o777);
|
|
1622
|
+
assets.push({
|
|
1623
|
+
path: path.relative(ionDist, candidate).split(path.sep).join("/"),
|
|
1624
|
+
sha256: sha256(Buffer.from(patched, "utf8")),
|
|
1625
|
+
patches: applicable.map(({ key }) => key),
|
|
1626
|
+
});
|
|
1572
1627
|
}
|
|
1573
1628
|
|
|
1574
|
-
source = source
|
|
1575
|
-
.replace(CLAUDE_DRAFT_AGENT_MENTIONS, IMPEL_CLAUDE_BOUND_AGENT_MENTIONS)
|
|
1576
|
-
.replace(CLAUDE_AGENT_MENTION_FILTER, IMPEL_CLAUDE_BOUND_AGENT_MENTION_FILTER)
|
|
1577
|
-
.replace(CLAUDE_AGENT_MENTION_SELECT, IMPEL_CLAUDE_BOUND_AGENT_MENTION_SELECT);
|
|
1578
|
-
writeAtomic(renderer, source, fs.statSync(renderer).mode & 0o777);
|
|
1579
1629
|
return {
|
|
1580
|
-
enable,
|
|
1581
|
-
filter,
|
|
1582
|
-
chip,
|
|
1583
|
-
|
|
1584
|
-
sha256: sha256(Buffer.from(source, "utf8")),
|
|
1630
|
+
enable: 1,
|
|
1631
|
+
filter: 1,
|
|
1632
|
+
chip: 1,
|
|
1633
|
+
assets,
|
|
1585
1634
|
};
|
|
1586
1635
|
}
|
|
1587
1636
|
|
|
@@ -1631,23 +1680,6 @@ function patchFixedWidthAsarString(asarPath, original, replacement, label) {
|
|
|
1631
1680
|
function patchClaudePlanUsageRequest(asarPath) {
|
|
1632
1681
|
const archive = fs.readFileSync(asarPath);
|
|
1633
1682
|
const source = archive.toString("latin1");
|
|
1634
|
-
const legacyPatches = [];
|
|
1635
|
-
for (
|
|
1636
|
-
let offset = source.indexOf(CLAUDE_PLAN_USAGE_REQUEST);
|
|
1637
|
-
offset !== -1;
|
|
1638
|
-
offset = source.indexOf(CLAUDE_PLAN_USAGE_REQUEST, offset + CLAUDE_PLAN_USAGE_REQUEST.length)
|
|
1639
|
-
) {
|
|
1640
|
-
legacyPatches.push({
|
|
1641
|
-
offset,
|
|
1642
|
-
original: CLAUDE_PLAN_USAGE_REQUEST,
|
|
1643
|
-
replacement: IMPEL_CLAUDE_PLAN_USAGE_REQUEST,
|
|
1644
|
-
});
|
|
1645
|
-
}
|
|
1646
|
-
if (legacyPatches.length > 0) {
|
|
1647
|
-
return applyFixedWidthAsarPatches(
|
|
1648
|
-
asarPath, archive, legacyPatches, "Claude plan-usage gateway request",
|
|
1649
|
-
);
|
|
1650
|
-
}
|
|
1651
1683
|
const credentialAccessor = source.match(
|
|
1652
1684
|
/function ([A-Za-z_$][\w$]*)\(e\)\{var t;return [\s\S]{0,500}?\[custom-3p\] Credentials loaded from managed config/u,
|
|
1653
1685
|
)?.[1];
|
|
@@ -1663,10 +1695,10 @@ function patchClaudePlanUsageRequest(asarPath) {
|
|
|
1663
1695
|
return applyFixedWidthAsarPatches(asarPath, archive, [], "Claude plan-usage gateway request");
|
|
1664
1696
|
}
|
|
1665
1697
|
|
|
1666
|
-
const requestPattern = /([A-Za-z_$][\w$]*)
|
|
1698
|
+
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;
|
|
1667
1699
|
const patches = [...source.matchAll(requestPattern)].map((match) => {
|
|
1668
1700
|
const original = match[0];
|
|
1669
|
-
const replacement =
|
|
1701
|
+
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}})`;
|
|
1670
1702
|
if (replacement.length > original.length) {
|
|
1671
1703
|
throw new Error("Claude plan-usage gateway request replacement no longer fits the vendor ASAR contract");
|
|
1672
1704
|
}
|
|
@@ -1967,7 +1999,11 @@ function vendoredChatGPTWrapperPlist(vendorPath, asarHash, identity, executableN
|
|
|
1967
1999
|
<key>CFBundleShortVersionString</key><string>${xmlEscape(bundleVersion(vendorPath) || "1")}</string>
|
|
1968
2000
|
<key>ElectronAsarIntegrity</key><dict><key>Resources/app.asar</key><dict><key>algorithm</key><string>SHA256</string><key>hash</key><string>${xmlEscape(asarHash)}</string></dict></dict>
|
|
1969
2001
|
<key>LSMinimumSystemVersion</key><string>12.0</string>
|
|
2002
|
+
<key>NSCalendarsFullAccessUsageDescription</key><string>Allow access so that ChatGPT can read and update your calendar events when you ask.</string>
|
|
2003
|
+
<key>NSCalendarsUsageDescription</key><string>Allow access so that ChatGPT can read and update your calendar events when you ask.</string>
|
|
1970
2004
|
<key>NSHighResolutionCapable</key><true/>
|
|
2005
|
+
<key>NSRemindersFullAccessUsageDescription</key><string>Allow access so that ChatGPT can read and update your reminders when you ask.</string>
|
|
2006
|
+
<key>NSRemindersUsageDescription</key><string>Allow access so that ChatGPT can read and update your reminders when you ask.</string>
|
|
1971
2007
|
</dict></plist>
|
|
1972
2008
|
`;
|
|
1973
2009
|
}
|
package/src/windowsApps.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
+
import crypto from "node:crypto";
|
|
2
3
|
import fs from "node:fs";
|
|
3
4
|
import os from "node:os";
|
|
4
5
|
import path from "node:path";
|
|
@@ -251,14 +252,47 @@ export function findManagedWindowsChatGPTApp(appsRoot, dependencies = {}) {
|
|
|
251
252
|
return null;
|
|
252
253
|
}
|
|
253
254
|
|
|
255
|
+
function sha256File(file) {
|
|
256
|
+
const hash = crypto.createHash("sha256");
|
|
257
|
+
const buffer = Buffer.allocUnsafe(1024 * 1024);
|
|
258
|
+
const descriptor = fs.openSync(file, "r");
|
|
259
|
+
try {
|
|
260
|
+
for (let offset = 0; ; offset += buffer.length) {
|
|
261
|
+
const bytesRead = fs.readSync(descriptor, buffer, 0, buffer.length, offset);
|
|
262
|
+
if (bytesRead === 0) break;
|
|
263
|
+
hash.update(buffer.subarray(0, bytesRead));
|
|
264
|
+
}
|
|
265
|
+
return hash.digest("hex");
|
|
266
|
+
} finally {
|
|
267
|
+
fs.closeSync(descriptor);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function windowsChatGPTSourceIdentity(binary) {
|
|
272
|
+
const sourceRoot = path.dirname(binary);
|
|
273
|
+
const files = {
|
|
274
|
+
executable: binary,
|
|
275
|
+
appAsar: path.join(sourceRoot, "resources", "app.asar"),
|
|
276
|
+
codex: path.join(sourceRoot, "resources", "codex.exe"),
|
|
277
|
+
};
|
|
278
|
+
return {
|
|
279
|
+
executableName: path.basename(binary),
|
|
280
|
+
sha256: Object.fromEntries(
|
|
281
|
+
Object.entries(files).map(([name, file]) => [name, sha256File(file)]),
|
|
282
|
+
),
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
254
286
|
export function windowsChatGPTStageIsCurrent(binary, appsRoot) {
|
|
255
287
|
if (!binary || !findManagedWindowsChatGPTApp(appsRoot)) return false;
|
|
256
288
|
try {
|
|
257
289
|
const record = JSON.parse(
|
|
258
290
|
fs.readFileSync(path.join(windowsChatGPTCacheRoot(appsRoot), "source.json"), "utf8"),
|
|
259
291
|
);
|
|
260
|
-
return
|
|
261
|
-
|
|
292
|
+
return record.schemaVersion === 2
|
|
293
|
+
&& path.win32.normalize(record.sourceBinary || "").toLowerCase()
|
|
294
|
+
=== path.win32.normalize(binary).toLowerCase()
|
|
295
|
+
&& JSON.stringify(record.sourceIdentity) === JSON.stringify(windowsChatGPTSourceIdentity(binary));
|
|
262
296
|
} catch {
|
|
263
297
|
return false;
|
|
264
298
|
}
|
|
@@ -288,6 +322,7 @@ export function stageWindowsChatGPTApp(binary, appsRoot, dependencies = {}) {
|
|
|
288
322
|
if (!io.isFile(binary) || !io.isFile(sourceAsar) || !io.isFile(sourceCodex)) {
|
|
289
323
|
throw new Error(`unsupported ChatGPT/Codex Windows app layout at ${sourceRoot}`);
|
|
290
324
|
}
|
|
325
|
+
const sourceIdentity = windowsChatGPTSourceIdentity(binary);
|
|
291
326
|
|
|
292
327
|
const cacheRoot = windowsChatGPTCacheRoot(appsRoot);
|
|
293
328
|
const destination = windowsManagedChatGPTRoot(appsRoot);
|
|
@@ -309,8 +344,9 @@ export function stageWindowsChatGPTApp(binary, appsRoot, dependencies = {}) {
|
|
|
309
344
|
try {
|
|
310
345
|
io.rename(staging, destination);
|
|
311
346
|
fs.writeFileSync(path.join(cacheRoot, "source.json"), `${JSON.stringify({
|
|
312
|
-
schemaVersion:
|
|
347
|
+
schemaVersion: 2,
|
|
313
348
|
sourceBinary: path.win32.normalize(binary),
|
|
349
|
+
sourceIdentity,
|
|
314
350
|
stagedAt: new Date().toISOString(),
|
|
315
351
|
}, null, 2)}\n`, { mode: 0o600 });
|
|
316
352
|
io.remove(backup);
|