codex-copilot-dx 0.6.10 → 0.6.12

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.
@@ -9,12 +9,19 @@ import {
9
9
  } from "./auth-profile.mjs";
10
10
  import {
11
11
  PM_STUDIO_2_9_7_RECIPE,
12
+ PM_STUDIO_2_9_10_RECIPE,
12
13
  PM_STUDIO_RECIPES,
14
+ createCompatiblePmStudioRecipe,
13
15
  inspectElectronAsarIntegritySlots,
14
16
  inspectAsarBuffer,
15
17
  patchAsarBuffer,
16
18
  sha256Hex,
17
19
  } from "./pm-studio-asar.mjs";
20
+ import {
21
+ PM_STUDIO_OFFICIAL_REPOSITORY,
22
+ verifyOfficialPmStudioProvenance,
23
+ } from "./pm-studio-provenance.mjs";
24
+ import { withPmStudioSetupLock } from "./pm-studio-setup-lock.mjs";
18
25
 
19
26
  export const DEFAULT_PM_STUDIO_APP_PATH = "/Applications/PM Studio.app";
20
27
  export const PM_STUDIO_CLAUDE_AUTH_COMMAND = "ccdx auth login claude --reauth --github-login <personal-login>";
@@ -315,6 +322,27 @@ function defaultInspectExecutableIntegrity({ appPath, infoPlistPath, recipe, pro
315
322
  };
316
323
  }
317
324
 
325
+ function defaultReadAppArchitecture({ executablePath, processRunner }) {
326
+ const output = runChecked(processRunner, "/usr/bin/lipo", ["-archs", executablePath],
327
+ "Reading PM Studio executable architecture").stdout.trim();
328
+ const architectures = output.split(/\s+/).filter(Boolean);
329
+ if (architectures.length !== 1 || !["arm64", "x86_64"].includes(architectures[0])) {
330
+ throw setupError("PM_STUDIO_ARCHITECTURE_UNSUPPORTED",
331
+ "PM Studio must contain exactly one supported macOS architecture");
332
+ }
333
+ return architectures[0] === "x86_64" ? "x64" : architectures[0];
334
+ }
335
+
336
+ async function defaultVerifyOfficialProvenance(options) {
337
+ return verifyOfficialPmStudioProvenance({
338
+ ...options,
339
+ extractor: async ({ archivePath, destination }) => {
340
+ runChecked(options.processRunner, "/usr/bin/ditto", ["-x", "-k", archivePath, destination],
341
+ "Extracting official PM Studio release");
342
+ },
343
+ });
344
+ }
345
+
318
346
  function defaultSignApp({ appPath, processRunner }) {
319
347
  runChecked(processRunner, "/usr/bin/codesign", [
320
348
  "--force", "--deep", "--sign", "-", "--timestamp=none",
@@ -412,6 +440,9 @@ export function createPmStudioSetupOperations(overrides = {}) {
412
440
  inspectCodeSign: defaultInspectCodeSign,
413
441
  inspectBundleContent: defaultInspectBundleContent,
414
442
  inspectExecutableIntegrity: defaultInspectExecutableIntegrity,
443
+ readAppArchitecture: defaultReadAppArchitecture,
444
+ verifyOfficialProvenance: defaultVerifyOfficialProvenance,
445
+ withSetupLock: ({ appPath }, fn) => withPmStudioSetupLock({ appPath }, fn),
415
446
  signApp: defaultSignApp,
416
447
  listBlockingProcesses: defaultListBlockingProcesses,
417
448
  copyBundle: defaultCopyBundle,
@@ -507,6 +538,7 @@ export function inspectPmStudioApp({
507
538
  || bundleContentMatches(bundleContent, recipe.sourceBundleContent);
508
539
  const strictVendorSignature = codeSign?.valid === true
509
540
  && codeSign?.adHoc === false
541
+ && codeSign?.identifier === recipe.bundleIdentifier
510
542
  && codeSign?.teamIdentifier === recipe.sourceTeamIdentifier;
511
543
  const exactInvalidVendorSignature = recipe.sourceBundleContent
512
544
  && exactSourceBundle
@@ -648,6 +680,385 @@ function selectRecipe(metadata, recipes) {
648
680
  && recipe.bundleIdentifier === String(metadata.bundleIdentifier));
649
681
  }
650
682
 
683
+ function compatibleStaticOptions(metadata) {
684
+ return {
685
+ version: String(metadata.version),
686
+ build: String(metadata.build),
687
+ bundleIdentifier: String(metadata.bundleIdentifier),
688
+ sourceTeamIdentifier: PM_STUDIO_2_9_10_RECIPE.sourceTeamIdentifier,
689
+ executable: PM_STUDIO_2_9_10_RECIPE.executable,
690
+ electronFrameworkPath: PM_STUDIO_2_9_10_RECIPE.electronFrameworkPath,
691
+ embeddedAsarIntegrity: PM_STUDIO_2_9_10_RECIPE.embeddedAsarIntegrity,
692
+ asarPath: PM_STUDIO_2_9_10_RECIPE.asarPath,
693
+ infoPlistPath: PM_STUDIO_2_9_10_RECIPE.infoPlistPath,
694
+ integrityKey: PM_STUDIO_2_9_10_RECIPE.integrityKey,
695
+ };
696
+ }
697
+
698
+ function compatibleSourceBundleContent({ appPath, operations }) {
699
+ const ignoredXattrs = PM_STUDIO_2_9_10_RECIPE.sourceBundleContent.ignoredXattrs;
700
+ const inspected = operations.inspectBundleContent({
701
+ ...appPaths(appPath, PM_STUDIO_2_9_10_RECIPE),
702
+ recipe: PM_STUDIO_2_9_10_RECIPE,
703
+ processRunner: operations.processRunner,
704
+ });
705
+ return {
706
+ ...inspected,
707
+ ignoredXattrs: [...ignoredXattrs],
708
+ };
709
+ }
710
+
711
+ function compatibleSourceStructure({ appPath, metadata, operations }) {
712
+ const paths = appPaths(appPath, PM_STUDIO_2_9_10_RECIPE);
713
+ if (String(metadata.bundleIdentifier) !== PM_STUDIO_2_9_10_RECIPE.bundleIdentifier) {
714
+ throw setupError("PM_STUDIO_COMPATIBLE_SOURCE_UNTRUSTED",
715
+ "PM Studio compatible-version discovery found an unexpected bundle identifier");
716
+ }
717
+ const executableIntegrity = operations.inspectExecutableIntegrity({
718
+ ...paths,
719
+ recipe: PM_STUDIO_2_9_10_RECIPE,
720
+ processRunner: operations.processRunner,
721
+ });
722
+ if (!executableIntegrity?.matchesRecipeName
723
+ || !executableIntegrity?.embeddedIntegrity?.supported
724
+ || executableIntegrity.embeddedIntegrity.state !== PM_STUDIO_2_9_10_RECIPE.embeddedAsarIntegrity) {
725
+ throw setupError("PM_STUDIO_COMPATIBLE_SOURCE_UNTRUSTED",
726
+ "PM Studio compatible-version discovery found an unsupported executable or embedded ASAR integrity policy");
727
+ }
728
+ const compatibleRecipe = createCompatiblePmStudioRecipe(fs.readFileSync(paths.asarPath), {
729
+ ...compatibleStaticOptions(metadata),
730
+ sourceExecutableSha256: executableIntegrity.executableSha256,
731
+ sourceElectronFrameworkSha256: executableIntegrity.frameworkSha256,
732
+ });
733
+ const plistIntegrity = operations.readAsarIntegrity({
734
+ ...paths,
735
+ recipe: compatibleRecipe,
736
+ processRunner: operations.processRunner,
737
+ });
738
+ if (!integrityMatches(plistIntegrity, "SHA256", compatibleRecipe.sourceHeaderSha256)) {
739
+ throw setupError("PM_STUDIO_COMPATIBLE_SOURCE_UNTRUSTED",
740
+ "PM Studio compatible-version discovery found mismatched ElectronAsarIntegrity metadata");
741
+ }
742
+ return { paths, executableIntegrity, compatibleRecipe };
743
+ }
744
+
745
+ function frozenBundleContent(value) {
746
+ return Object.freeze({
747
+ ...value,
748
+ ignoredXattrs: Object.freeze([...(value.ignoredXattrs || [])]),
749
+ });
750
+ }
751
+
752
+ function sourceCodeSignature(codeSign) {
753
+ return Object.freeze({
754
+ identifier: codeSign.identifier,
755
+ teamIdentifier: codeSign.teamIdentifier,
756
+ flags: codeSign.flags,
757
+ runtimeVersion: codeSign.runtimeVersion,
758
+ cdHashFull: codeSign.cdHashFull,
759
+ notarizationTicket: codeSign.notarizationTicket,
760
+ });
761
+ }
762
+
763
+ function createCompatibleSourceRecipe({ appPath, metadata, operations }) {
764
+ const { paths, compatibleRecipe } = compatibleSourceStructure({ appPath, metadata, operations });
765
+ const codeSign = operations.inspectCodeSign({
766
+ ...paths,
767
+ recipe: PM_STUDIO_2_9_10_RECIPE,
768
+ processRunner: operations.processRunner,
769
+ });
770
+ if (codeSign?.valid !== true
771
+ || codeSign?.adHoc !== false
772
+ || codeSign?.identifier !== PM_STUDIO_2_9_10_RECIPE.bundleIdentifier
773
+ || codeSign?.teamIdentifier !== PM_STUDIO_2_9_10_RECIPE.sourceTeamIdentifier) {
774
+ throw setupError("PM_STUDIO_COMPATIBLE_SOURCE_UNTRUSTED",
775
+ "PM Studio compatible-version discovery requires an intact vendor signature");
776
+ }
777
+ const sourceBundleContent = compatibleSourceBundleContent({ appPath, operations });
778
+ return Object.freeze({
779
+ ...compatibleRecipe,
780
+ sourceBundleContent: frozenBundleContent(sourceBundleContent),
781
+ });
782
+ }
783
+
784
+ function exactInvalidVendorSignature(codeSign) {
785
+ return codeSign?.valid === false
786
+ && codeSign?.verifyValid === false
787
+ && codeSign?.displayValid === true
788
+ && codeSign?.entitlementsState === "invalid"
789
+ && codeSign?.adHoc === false
790
+ && codeSign?.identifier === PM_STUDIO_2_9_10_RECIPE.bundleIdentifier
791
+ && codeSign?.teamIdentifier === PM_STUDIO_2_9_10_RECIPE.sourceTeamIdentifier
792
+ && typeof codeSign?.flags === "string" && codeSign.flags.length > 0
793
+ && typeof codeSign?.runtimeVersion === "string" && codeSign.runtimeVersion.length > 0
794
+ && /^[0-9a-f]{64}$/.test(codeSign?.cdHashFull || "")
795
+ && codeSign?.notarizationTicket === "stapled";
796
+ }
797
+
798
+ function officialArtifact(provenance, metadata, architecture) {
799
+ if (provenance?.verified !== true
800
+ || provenance.repository !== PM_STUDIO_OFFICIAL_REPOSITORY
801
+ || provenance.version !== String(metadata.version)
802
+ || provenance.tag !== `v${metadata.version}`
803
+ || provenance.arch !== architecture
804
+ || provenance.comparison?.matched !== true
805
+ || typeof provenance.releaseUrl !== "string"
806
+ || typeof provenance.asset?.name !== "string"
807
+ || !Number.isSafeInteger(provenance.asset?.size)
808
+ || provenance.asset.size <= 0
809
+ || !/^[0-9a-f]{64}$/.test(provenance.asset?.sha256 || "")) {
810
+ throw setupError("PM_STUDIO_COMPATIBLE_SOURCE_UNTRUSTED",
811
+ "Official PM Studio release verification returned incomplete evidence");
812
+ }
813
+ return Object.freeze({
814
+ repository: provenance.repository,
815
+ tag: provenance.tag,
816
+ arch: provenance.arch,
817
+ releaseUrl: provenance.releaseUrl,
818
+ asset: Object.freeze({
819
+ name: provenance.asset.name,
820
+ size: provenance.asset.size,
821
+ sha256: provenance.asset.sha256,
822
+ }),
823
+ });
824
+ }
825
+
826
+ async function createOfficialCompatibleSourceRecipe({ appPath, metadata, operations }) {
827
+ const { paths, executableIntegrity, compatibleRecipe } = compatibleSourceStructure({
828
+ appPath, metadata, operations,
829
+ });
830
+ const codeSign = operations.inspectCodeSign({
831
+ ...paths,
832
+ recipe: PM_STUDIO_2_9_10_RECIPE,
833
+ processRunner: operations.processRunner,
834
+ });
835
+ if (!exactInvalidVendorSignature(codeSign)) {
836
+ throw setupError("PM_STUDIO_COMPATIBLE_SOURCE_UNTRUSTED",
837
+ "PM Studio has neither an intact vendor signature nor the exact invalid-signature shape accepted for official artifact verification");
838
+ }
839
+ const architecture = operations.readAppArchitecture({
840
+ ...paths,
841
+ executablePath: executableIntegrity.executablePath,
842
+ processRunner: operations.processRunner,
843
+ });
844
+ const sourceBundleContent = compatibleSourceBundleContent({ appPath, operations });
845
+ const provenance = await operations.verifyOfficialProvenance({
846
+ installedAppPath: appPath,
847
+ version: String(metadata.version),
848
+ arch: architecture,
849
+ processRunner: operations.processRunner,
850
+ });
851
+ return Object.freeze({
852
+ ...compatibleRecipe,
853
+ sourceBundleContent: frozenBundleContent(sourceBundleContent),
854
+ sourceArtifact: officialArtifact(provenance, metadata, architecture),
855
+ sourceCodeSignature: sourceCodeSignature(codeSign),
856
+ });
857
+ }
858
+
859
+ function sameIgnoredXattrs(value) {
860
+ return JSON.stringify(value)
861
+ === JSON.stringify(PM_STUDIO_2_9_10_RECIPE.sourceBundleContent.ignoredXattrs);
862
+ }
863
+
864
+ function storedOfficialArtifact(value, metadata) {
865
+ const version = String(metadata.version);
866
+ const arch = value?.arch;
867
+ const assetName = `PM-Studio-${version}-mac-${arch}.zip`;
868
+ if (!value || Array.isArray(value)
869
+ || JSON.stringify(Object.keys(value).sort())
870
+ !== JSON.stringify(["arch", "asset", "releaseUrl", "repository", "tag"])
871
+ || value.repository !== PM_STUDIO_OFFICIAL_REPOSITORY
872
+ || value.tag !== `v${version}`
873
+ || !["arm64", "x64"].includes(arch)
874
+ || value.releaseUrl !== `https://github.com/${PM_STUDIO_OFFICIAL_REPOSITORY}/releases/tag/v${version}`
875
+ || !value.asset || Array.isArray(value.asset)
876
+ || JSON.stringify(Object.keys(value.asset).sort())
877
+ !== JSON.stringify(["name", "sha256", "size"])
878
+ || value.asset.name !== assetName
879
+ || !Number.isSafeInteger(value.asset.size)
880
+ || value.asset.size <= 0
881
+ || !/^[0-9a-f]{64}$/.test(value.asset.sha256 || "")) {
882
+ throw setupError("PM_STUDIO_BACKUP_INVALID",
883
+ "Compatible PM Studio backup has invalid official release evidence");
884
+ }
885
+ return Object.freeze({
886
+ ...value,
887
+ asset: Object.freeze({ ...value.asset }),
888
+ });
889
+ }
890
+
891
+ function storedSourceCodeSignature(value) {
892
+ if (!value || Array.isArray(value)
893
+ || JSON.stringify(Object.keys(value).sort()) !== JSON.stringify([
894
+ "cdHashFull",
895
+ "flags",
896
+ "identifier",
897
+ "notarizationTicket",
898
+ "runtimeVersion",
899
+ "teamIdentifier",
900
+ ])
901
+ || value.identifier !== PM_STUDIO_2_9_10_RECIPE.bundleIdentifier
902
+ || value.teamIdentifier !== PM_STUDIO_2_9_10_RECIPE.sourceTeamIdentifier
903
+ || typeof value.flags !== "string" || value.flags.length === 0
904
+ || typeof value.runtimeVersion !== "string" || value.runtimeVersion.length === 0
905
+ || !/^[0-9a-f]{64}$/.test(value.cdHashFull || "")
906
+ || value.notarizationTicket !== "stapled") {
907
+ throw setupError("PM_STUDIO_BACKUP_INVALID",
908
+ "Compatible PM Studio backup has invalid source signing evidence");
909
+ }
910
+ return Object.freeze({ ...value });
911
+ }
912
+
913
+ function compatibleRecipeFromManifest({ backupDir, manifest, metadata, operations }) {
914
+ if (manifest?.schema_version !== 2
915
+ || manifest?.kind !== "ccdx-pm-studio-backup"
916
+ || !String(manifest?.recipe_id || "").startsWith("pm-studio-compatible-")
917
+ || manifest?.app?.bundle_identifier !== String(metadata.bundleIdentifier)
918
+ || manifest?.app?.version !== String(metadata.version)
919
+ || manifest?.app?.build !== String(metadata.build)
920
+ || !sameIgnoredXattrs(manifest?.source?.bundle_content?.ignoredXattrs)) {
921
+ return null;
922
+ }
923
+ const backupAppPath = path.join(backupDir, "PM Studio.app");
924
+ assertInstalledAppPath(backupAppPath);
925
+ const sourceAsarPath = path.join(backupAppPath, PM_STUDIO_2_9_10_RECIPE.asarPath);
926
+ const compatibleRecipe = createCompatiblePmStudioRecipe(fs.readFileSync(sourceAsarPath), {
927
+ ...compatibleStaticOptions(metadata),
928
+ sourceExecutableSha256: manifest?.source?.binaries?.main_executable_sha256,
929
+ sourceElectronFrameworkSha256: manifest?.source?.binaries?.electron_framework_sha256,
930
+ });
931
+ const hasOfficialArtifact = manifest?.source?.artifact !== undefined;
932
+ const hasSourceCodeSignature = manifest?.source?.code_signature !== undefined;
933
+ if (hasOfficialArtifact !== hasSourceCodeSignature) {
934
+ throw setupError("PM_STUDIO_BACKUP_INVALID",
935
+ "Compatible PM Studio backup has incomplete official source evidence");
936
+ }
937
+ const recipe = Object.freeze({
938
+ ...compatibleRecipe,
939
+ sourceBundleContent: frozenBundleContent(manifest.source.bundle_content),
940
+ ...(hasOfficialArtifact ? {
941
+ sourceArtifact: storedOfficialArtifact(manifest.source.artifact, metadata),
942
+ sourceCodeSignature: storedSourceCodeSignature(manifest.source.code_signature),
943
+ } : {}),
944
+ });
945
+ if (manifest.compatibility !== recipe.compatibility
946
+ || manifest.recipe_id !== recipe.id
947
+ || manifest?.source?.asar_sha256 !== recipe.sourceAsarSha256
948
+ || manifest?.source?.asar_header_sha256 !== recipe.sourceHeaderSha256
949
+ || manifest?.patched?.asar_sha256 !== recipe.patchedAsarSha256
950
+ || manifest?.patched?.asar_header_sha256 !== recipe.patchedHeaderSha256) {
951
+ throw setupError("PM_STUDIO_BACKUP_INVALID",
952
+ `Compatible PM Studio backup does not match its reconstructed recipe: ${backupDir}`);
953
+ }
954
+ const inspection = inspectPmStudioApp({ appPath: backupAppPath, recipe, operations });
955
+ const expectedVerification = hasOfficialArtifact ? "exact-bundle-content" : "codesign";
956
+ if (inspection.state !== "clean" || inspection.sourceVerification !== expectedVerification) {
957
+ throw setupError("PM_STUDIO_BACKUP_INVALID",
958
+ `Compatible PM Studio backup source verification failed: ${backupDir}`,
959
+ inspection.issues);
960
+ }
961
+ return recipe;
962
+ }
963
+
964
+ function compatibleRecipeFromBackup({ appPath, backupRoot, metadata, operations }) {
965
+ if (!fs.existsSync(backupRoot)) return null;
966
+ const installedAsar = fs.readFileSync(path.join(appPath, PM_STUDIO_2_9_10_RECIPE.asarPath));
967
+ let candidates = [];
968
+ const invalidCandidates = [];
969
+ for (const entry of fs.readdirSync(backupRoot, { withFileTypes: true })) {
970
+ if (!entry.isDirectory() || entry.isSymbolicLink()) continue;
971
+ const backupDir = path.join(backupRoot, entry.name);
972
+ const manifestPath = path.join(backupDir, "manifest.json");
973
+ if (!fs.existsSync(manifestPath)) continue;
974
+ let manifest;
975
+ try {
976
+ manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
977
+ } catch {
978
+ continue;
979
+ }
980
+ let recipe;
981
+ try {
982
+ recipe = compatibleRecipeFromManifest({ backupDir, manifest, metadata, operations });
983
+ } catch (error) {
984
+ invalidCandidates.push(error);
985
+ continue;
986
+ }
987
+ if (!recipe) continue;
988
+ const asarState = inspectAsarBuffer(installedAsar, recipe).state;
989
+ if (asarState === "drift") continue;
990
+ candidates.push({ recipe, manifest, asarState });
991
+ }
992
+ if (candidates.length > 1) {
993
+ let installedBundleContent;
994
+ try {
995
+ const recipe = candidates[0].recipe;
996
+ installedBundleContent = operations.inspectBundleContent({
997
+ ...appPaths(appPath, recipe),
998
+ recipe,
999
+ processRunner: operations.processRunner,
1000
+ });
1001
+ } catch (error) {
1002
+ throw setupError("PM_STUDIO_BACKUP_INVALID",
1003
+ "PM Studio compatible backup candidates could not be distinguished safely",
1004
+ [error.message]);
1005
+ }
1006
+ candidates = candidates.filter(({ recipe, manifest, asarState }) => bundleContentMatches(
1007
+ installedBundleContent,
1008
+ asarState === "clean" ? recipe.sourceBundleContent : manifest?.patched?.bundle_content,
1009
+ ));
1010
+ }
1011
+ if (candidates.length > 1) {
1012
+ throw setupError("PM_STUDIO_UNSUPPORTED_VERSION",
1013
+ `PM Studio ${metadata.version} build ${metadata.build} matches multiple compatible backups; no files were changed`);
1014
+ }
1015
+ if (candidates.length === 1) return candidates[0].recipe;
1016
+ if (invalidCandidates.length > 0) {
1017
+ throw setupError("PM_STUDIO_BACKUP_INVALID",
1018
+ `PM Studio ${metadata.version} build ${metadata.build} has no matching verified compatible backup`,
1019
+ invalidCandidates.map((error) => error?.message || String(error)));
1020
+ }
1021
+ return null;
1022
+ }
1023
+
1024
+ function selectCompatibleRecipe({ appPath, backupRoot, metadata, operations }) {
1025
+ let sourceError = null;
1026
+ try {
1027
+ return createCompatibleSourceRecipe({ appPath, metadata, operations });
1028
+ } catch (error) {
1029
+ sourceError = error;
1030
+ }
1031
+ try {
1032
+ const backupRecipe = compatibleRecipeFromBackup({ appPath, backupRoot, metadata, operations });
1033
+ if (backupRecipe) return backupRecipe;
1034
+ } catch (error) {
1035
+ if (error?.code === "PM_STUDIO_BACKUP_INVALID") throw error;
1036
+ sourceError = error;
1037
+ }
1038
+ throw setupError("PM_STUDIO_UNSUPPORTED_VERSION",
1039
+ `PM Studio ${metadata.version} build ${metadata.build} is not structurally compatible with the safe patch; no files were changed`,
1040
+ sourceError?.message ? [sourceError.message] : undefined);
1041
+ }
1042
+
1043
+ export function resolvePmStudioCompatibleRecipe({
1044
+ appPath = DEFAULT_PM_STUDIO_APP_PATH,
1045
+ home = os.homedir(),
1046
+ backupRoot = pmStudioBackupRoot(home),
1047
+ metadata,
1048
+ recipes = PM_STUDIO_RECIPES,
1049
+ operations = createPmStudioSetupOperations(),
1050
+ } = {}) {
1051
+ if (!metadata || typeof metadata !== "object") {
1052
+ throw new TypeError("PM Studio bundle metadata is required");
1053
+ }
1054
+ return selectRecipe(metadata, recipes) || selectCompatibleRecipe({
1055
+ appPath,
1056
+ backupRoot,
1057
+ metadata,
1058
+ operations,
1059
+ });
1060
+ }
1061
+
651
1062
  function backupName(recipe) {
652
1063
  const sourceHash = recipe.sourceBundleContent?.sha256 || recipe.sourceAsarSha256;
653
1064
  return `${recipe.id}-${sourceHash.slice(0, 12)}`;
@@ -671,6 +1082,7 @@ function backupManifest(recipe, createdAt) {
671
1082
  schema_version: recipe.sourceBundleContent ? 2 : 1,
672
1083
  kind: "ccdx-pm-studio-backup",
673
1084
  recipe_id: recipe.id,
1085
+ ...(recipe.compatibility ? { compatibility: recipe.compatibility } : {}),
674
1086
  created_at: createdAt.toISOString(),
675
1087
  app: {
676
1088
  name: "PM Studio",
@@ -689,6 +1101,7 @@ function backupManifest(recipe, createdAt) {
689
1101
  },
690
1102
  ...(recipe.sourceBundleContent ? { bundle_content: recipe.sourceBundleContent } : {}),
691
1103
  ...(recipe.sourceArtifact ? { artifact: recipe.sourceArtifact } : {}),
1104
+ ...(recipe.sourceCodeSignature ? { code_signature: recipe.sourceCodeSignature } : {}),
692
1105
  },
693
1106
  patched: {
694
1107
  asar_sha256: recipe.patchedAsarSha256,
@@ -737,6 +1150,7 @@ function validateBackup({ backupDir, recipe, operations }) {
737
1150
  if (manifest?.schema_version !== expectedSchema
738
1151
  || manifest?.kind !== "ccdx-pm-studio-backup"
739
1152
  || manifest?.recipe_id !== recipe.id
1153
+ || (recipe.compatibility && manifest?.compatibility !== recipe.compatibility)
740
1154
  || manifest?.app?.bundle_identifier !== recipe.bundleIdentifier
741
1155
  || manifest?.app?.version !== recipe.version
742
1156
  || manifest?.app?.build !== recipe.build
@@ -750,6 +1164,8 @@ function validateBackup({ backupDir, recipe, operations }) {
750
1164
  && JSON.stringify(manifest?.source?.bundle_content) !== JSON.stringify(recipe.sourceBundleContent))
751
1165
  || (recipe.sourceArtifact
752
1166
  && JSON.stringify(manifest?.source?.artifact) !== JSON.stringify(recipe.sourceArtifact))
1167
+ || (recipe.compatibility && recipe.sourceCodeSignature
1168
+ && JSON.stringify(manifest?.source?.code_signature) !== JSON.stringify(recipe.sourceCodeSignature))
753
1169
  || (manifest?.predecessor_patched !== undefined
754
1170
  && !patchRecordMatches(manifest.predecessor_patched, recipe, "predecessor"))
755
1171
  || (!patchRecordForState(manifest, recipe, "patched")
@@ -851,6 +1267,7 @@ export function assertPatchedBinaryRecord({
851
1267
  if (recipe && (manifest?.schema_version !== expectedSchema
852
1268
  || manifest?.kind !== "ccdx-pm-studio-backup"
853
1269
  || manifest?.recipe_id !== recipe.id
1270
+ || (recipe.compatibility && manifest?.compatibility !== recipe.compatibility)
854
1271
  || manifest?.app?.bundle_identifier !== recipe.bundleIdentifier
855
1272
  || manifest?.app?.version !== recipe.version
856
1273
  || manifest?.app?.build !== recipe.build
@@ -864,7 +1281,9 @@ export function assertPatchedBinaryRecord({
864
1281
  || JSON.stringify(manifest?.source?.bundle_content) !== JSON.stringify(recipe.sourceBundleContent)
865
1282
  ))
866
1283
  || (recipe.sourceArtifact
867
- && JSON.stringify(manifest?.source?.artifact) !== JSON.stringify(recipe.sourceArtifact)))) {
1284
+ && JSON.stringify(manifest?.source?.artifact) !== JSON.stringify(recipe.sourceArtifact))
1285
+ || (recipe.compatibility && recipe.sourceCodeSignature
1286
+ && JSON.stringify(manifest?.source?.code_signature) !== JSON.stringify(recipe.sourceCodeSignature)))) {
868
1287
  throw setupError("PM_STUDIO_PATCH_RECORD_INVALID", "PM Studio patched-binary record does not match the installed patch recipe");
869
1288
  }
870
1289
  const record = patchRecordForState(manifest, recipe, recordState);
@@ -915,6 +1334,17 @@ function assertSigningMetadataPreserved(source, staged, recipe) {
915
1334
  const after = signingMetadata(staged.codeSign);
916
1335
  before.flags = flagsWithoutAdHoc(before.flags);
917
1336
  after.flags = flagsWithoutAdHoc(after.flags);
1337
+ if (recipe.compatibility
1338
+ && recipe.sourceArtifact
1339
+ && source.sourceVerification === "exact-bundle-content"
1340
+ && source.codeSign?.entitlementsState === "invalid") {
1341
+ if (staged.codeSign?.entitlementsState !== "xml"
1342
+ || !/^[0-9a-f]{64}$/.test(after.entitlements_sha256)) {
1343
+ throw setupError("PM_STUDIO_SIGNING_METADATA_CHANGED",
1344
+ "Ad-hoc signing did not produce verifiable PM Studio entitlements from the official source bundle");
1345
+ }
1346
+ before.entitlements_sha256 = after.entitlements_sha256;
1347
+ }
918
1348
  if (JSON.stringify(before) !== JSON.stringify(after)) {
919
1349
  throw setupError("PM_STUDIO_SIGNING_METADATA_CHANGED",
920
1350
  "Ad-hoc signing did not preserve PM Studio identifier, entitlements, flags, and runtime metadata");
@@ -963,6 +1393,7 @@ export async function runPmStudioSetup({
963
1393
  logger = (line) => console.log(line),
964
1394
  } = {}) {
965
1395
  const operations = createPmStudioSetupOperations(operationOverrides);
1396
+ return operations.withSetupLock({ appPath }, async () => {
966
1397
  const messages = [];
967
1398
  const emit = (message) => {
968
1399
  messages.push(message);
@@ -980,10 +1411,27 @@ export async function runPmStudioSetup({
980
1411
  processRunner: operations.processRunner,
981
1412
  };
982
1413
  const metadata = operations.readBundleMetadata(genericPaths);
983
- const recipe = selectRecipe(metadata, recipes);
1414
+ let recipe = selectRecipe(metadata, recipes);
984
1415
  if (!recipe) {
985
- throw setupError("PM_STUDIO_UNSUPPORTED_VERSION",
986
- `PM Studio ${metadata.version} build ${metadata.build} is not supported; no files were changed`);
1416
+ try {
1417
+ recipe = resolvePmStudioCompatibleRecipe({
1418
+ appPath,
1419
+ backupRoot,
1420
+ metadata,
1421
+ recipes,
1422
+ operations,
1423
+ });
1424
+ } catch (error) {
1425
+ if (error?.code !== "PM_STUDIO_UNSUPPORTED_VERSION") throw error;
1426
+ emit(`[INFO] Verifying PM Studio ${metadata.version} build ${metadata.build} against its official GitHub release before patching.`);
1427
+ try {
1428
+ recipe = await createOfficialCompatibleSourceRecipe({ appPath, metadata, operations });
1429
+ } catch (sourceError) {
1430
+ throw setupError("PM_STUDIO_UNSUPPORTED_VERSION",
1431
+ `PM Studio ${metadata.version} build ${metadata.build} could not be safely verified as a structurally compatible official release; no files were changed`,
1432
+ sourceError?.message ? [sourceError.message] : undefined);
1433
+ }
1434
+ }
987
1435
  }
988
1436
 
989
1437
  const source = inspectPmStudioApp({ appPath, recipe, operations });
@@ -1009,7 +1457,9 @@ export async function runPmStudioSetup({
1009
1457
  }
1010
1458
 
1011
1459
  if (source.sourceVerification === "exact-bundle-content") {
1012
- emit(`[WARN] macOS rejected the vendor signature; setup accepted only the exact PM Studio ${recipe.version} official bundle content fingerprint.`);
1460
+ emit(recipe.sourceArtifact
1461
+ ? `[WARN] macOS rejected the vendor signature; setup accepted PM Studio ${recipe.version} only after its files matched the SHA-256-verified official GitHub release.`
1462
+ : `[WARN] macOS rejected the vendor signature; setup accepted only the exact PM Studio ${recipe.version} official bundle content fingerprint.`);
1013
1463
  }
1014
1464
 
1015
1465
  const backupExists = fs.existsSync(finalBackupDir);
@@ -1140,4 +1590,5 @@ export async function runPmStudioSetup({
1140
1590
  } finally {
1141
1591
  if (!preserveStage && fs.existsSync(stagePath)) operations.removePath({ target: stagePath });
1142
1592
  }
1593
+ });
1143
1594
  }
@@ -14,6 +14,7 @@ import {
14
14
  PM_STUDIO_CLAUDE_AUTH_COMMAND,
15
15
  pmStudioBackupRoot,
16
16
  pmStudioPatchManifestPath,
17
+ resolvePmStudioCompatibleRecipe,
17
18
  } from "./pm-studio-setup.mjs";
18
19
  import { readAdapterStatus } from "./cli-status.mjs";
19
20
  import {
@@ -146,6 +147,7 @@ export async function inspectPmStudioStatus({
146
147
  existsSync = fs.existsSync,
147
148
  inspectApp = inspectPmStudioApp,
148
149
  verifyPatchRecord = assertPatchedBinaryRecord,
150
+ resolveCompatibleRecipe = resolvePmStudioCompatibleRecipe,
149
151
  readClaudeCredentials = readAuthProfileCredentials,
150
152
  checkRunningAdapterFn = checkRunningAdapter,
151
153
  readAdapterStatusFn = readAdapterStatus,
@@ -167,7 +169,22 @@ export async function inspectPmStudioStatus({
167
169
  build: String(metadata.build),
168
170
  bundleIdentifier: String(metadata.bundleIdentifier),
169
171
  };
170
- const recipe = selectRecipe(normalized, recipes);
172
+ let recipe = selectRecipe(normalized, recipes);
173
+ let compatibilityError = null;
174
+ if (!recipe) {
175
+ try {
176
+ recipe = resolveCompatibleRecipe({
177
+ appPath,
178
+ home,
179
+ backupRoot,
180
+ metadata: normalized,
181
+ recipes,
182
+ operations,
183
+ });
184
+ } catch (error) {
185
+ compatibilityError = error;
186
+ }
187
+ }
171
188
  if (recipe) {
172
189
  app = { ...inspectApp({ appPath, recipe, operations }), recipe: recipe.id };
173
190
  if (["patched", "predecessor"].includes(app.state)) {
@@ -189,7 +206,12 @@ export async function inspectPmStudioStatus({
189
206
  }
190
207
  }
191
208
  } else {
192
- app = { state: "unsupported", metadata: normalized, issues: [], recipe: "" };
209
+ app = {
210
+ state: "unsupported",
211
+ metadata: normalized,
212
+ issues: compatibilityError ? [safeMessage(compatibilityError)] : [],
213
+ recipe: "",
214
+ };
193
215
  }
194
216
  } catch (error) {
195
217
  app = { state: "error", metadata: null, issues: [safeMessage(error)], recipe: "" };
@@ -251,7 +273,7 @@ function appStatusItem(result, commandName) {
251
273
  return { kind: "err", component: "App patch", detail: `${version}; legacy global-origin patch`, message: `PM Studio ${version} has a legacy global-origin patch; run ${commandName} pms setup to migrate` };
252
274
  }
253
275
  if (app.state === "clean") return { kind: "warn", component: "App patch", detail: `${version}; supported, not patched`, message: `PM Studio ${version} is supported but not patched; run ${commandName} pms setup` };
254
- if (app.state === "unsupported") return { kind: "err", component: "App patch", detail: `${version}; unsupported version`, message: `PM Studio ${version} has no exact patch recipe; no files will be changed` };
276
+ if (app.state === "unsupported") return { kind: "err", component: "App patch", detail: `${version}; compatibility unverified`, message: `PM Studio ${version} compatibility cannot be safely verified; no files will be changed` };
255
277
  if (app.state === "drift" && app.patchRecipeMatched) {
256
278
  return { kind: "err", component: "App patch", detail: `${version}; patch record drift`, message: `PM Studio ${version} matches the patch recipe, but its installed patch record is not verified: ${app.issues.join("; ")}` };
257
279
  }