@duanluan/codex-plus-plus-launcher 1.2.0 → 1.2.1

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.
@@ -1 +1 @@
1
- __version__ = "1.2.0"
1
+ __version__ = "1.2.1"
@@ -9,6 +9,14 @@ function packageRoot() {
9
9
  return path.resolve(__dirname, '..');
10
10
  }
11
11
 
12
+ function packageVersion(root = packageRoot()) {
13
+ return JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')).version;
14
+ }
15
+
16
+ function defaultUpstreamRef(root = packageRoot()) {
17
+ return `v${packageVersion(root)}`;
18
+ }
19
+
12
20
  function run(command, args, options = {}) {
13
21
  const result = spawnSync(command, args, { stdio: 'inherit', shell: false, ...options });
14
22
  if (result.status !== 0) {
@@ -16,6 +24,11 @@ function run(command, args, options = {}) {
16
24
  }
17
25
  }
18
26
 
27
+ function runSucceeds(command, args, options = {}) {
28
+ const result = spawnSync(command, args, { stdio: 'ignore', shell: false, ...options });
29
+ return result.status === 0;
30
+ }
31
+
19
32
  function platformKey(platform = process.platform, arch = process.arch) {
20
33
  return `${platform}-${arch}`;
21
34
  }
@@ -42,10 +55,20 @@ function packageBuildArgs(key) {
42
55
  return key === 'linux-x64' ? ['-p', 'codex-plus-launcher'] : [];
43
56
  }
44
57
 
58
+ function applyPluginUnlockPatch(upstreamDir, root = packageRoot()) {
59
+ const patchPath = path.join(root, 'patches', 'codex-plus-plus-plugin-unlock.patch');
60
+ if (runSucceeds('git', ['apply', '--reverse', '--check', patchPath], { cwd: upstreamDir })) {
61
+ return;
62
+ }
63
+ run('git', ['apply', '--check', patchPath], { cwd: upstreamDir });
64
+ run('git', ['apply', patchPath], { cwd: upstreamDir });
65
+ }
66
+
45
67
  function main() {
46
68
  const root = packageRoot();
47
- const upstreamDir = process.env.CODEXPP_UPSTREAM_DIR || path.join(os.tmpdir(), 'CodexPlusPlus-upstream-build');
48
- const ref = process.env.CODEXPP_UPSTREAM_REF || 'v1.2.0';
69
+ const ref = process.env.CODEXPP_UPSTREAM_REF || defaultUpstreamRef(root);
70
+ const refKey = ref.replace(/[^A-Za-z0-9._-]/g, '_');
71
+ const upstreamDir = process.env.CODEXPP_UPSTREAM_DIR || path.join(os.tmpdir(), `CodexPlusPlus-upstream-build-${refKey}`);
49
72
  const key = process.env.CODEXPP_SIDECAR_PLATFORM || platformKey();
50
73
  const outDir = path.join(root, 'upstream-bin', key);
51
74
 
@@ -56,6 +79,8 @@ function main() {
56
79
  run('git', ['checkout', 'FETCH_HEAD'], { cwd: upstreamDir });
57
80
  }
58
81
 
82
+ applyPluginUnlockPatch(upstreamDir, root);
83
+
59
84
  if (key !== 'linux-x64') {
60
85
  run('npm', ['install', '--package-lock=false'], { cwd: path.join(upstreamDir, 'apps', 'codex-plus-manager'), shell: process.platform === 'win32' });
61
86
  run('npm', ['run', 'vite:build'], { cwd: path.join(upstreamDir, 'apps', 'codex-plus-manager'), shell: process.platform === 'win32' });
@@ -100,4 +125,4 @@ if (require.main === module) {
100
125
  }
101
126
  }
102
127
 
103
- module.exports = { buildTargetArgs, cargoTargetForKey, main, packageBuildArgs, platformKey };
128
+ module.exports = { applyPluginUnlockPatch, buildTargetArgs, cargoTargetForKey, defaultUpstreamRef, main, packageBuildArgs, platformKey };
package/npm/launcher.js CHANGED
@@ -13,6 +13,8 @@ const MANAGER_NAME = 'Codex++ 管理工具';
13
13
  const LINUX_SHIM_DIR_NAME = 'codex-desktop-linux-shim';
14
14
  const LINUX_SHIM_BINARY = 'codex.exe';
15
15
  const LINUX_DESKTOP_ENTRY = 'codex-plus-plus.desktop';
16
+ const PLUGIN_AUTH_UNLOCK_FILE = 'plugin-auth-unlocked.js';
17
+ const PLUGIN_AUTH_UNLOCK_CONTENT = 'function e(e){return false}export{e as t};\n';
16
18
 
17
19
  function optionValue(options, key, fallback) {
18
20
  const value = options[key];
@@ -63,6 +65,11 @@ function upstreamMetadataPath(options = {}) {
63
65
  return path.join(root, 'upstream-bin', 'upstream-release.json');
64
66
  }
65
67
 
68
+ function pluginAuthUnlockPath(options = {}) {
69
+ const root = optionValue(options, 'packageRoot', packageRoot);
70
+ return optionValue(options, 'pluginAuthUnlockPath', () => path.join(root, 'npm', PLUGIN_AUTH_UNLOCK_FILE));
71
+ }
72
+
66
73
  function readJsonFile(candidate, fsImpl = fs) {
67
74
  try {
68
75
  return JSON.parse(fsImpl.readFileSync(candidate, 'utf8'));
@@ -585,11 +592,113 @@ function shellSingleQuote(value) {
585
592
  return `'${String(value).replace(/'/g, "'\\''")}'`;
586
593
  }
587
594
 
588
- function linuxCodexShimScript(startScript) {
595
+ function linuxCodexShimScript(startScript, pluginAuthUnlockFile) {
589
596
  return [
590
597
  '#!/usr/bin/env bash',
591
598
  'set -euo pipefail',
592
- `exec ${shellSingleQuote(startScript)} -- "$@"`,
599
+ '',
600
+ `START_SCRIPT=${shellSingleQuote(startScript)}`,
601
+ `PLUGIN_AUTH_UNLOCK_FILE=${shellSingleQuote(pluginAuthUnlockFile)}`,
602
+ 'TMP_APP_ROOT=""',
603
+ 'CHILD_PID=""',
604
+ '',
605
+ 'resolve_file_dir() {',
606
+ ' local source="$1"',
607
+ ' local dir',
608
+ ' while [ -L "$source" ]; do',
609
+ ' dir="$(cd -P "$(dirname "$source")" && pwd)"',
610
+ ' source="$(readlink "$source")"',
611
+ ' case "$source" in',
612
+ ' /*) ;;',
613
+ ' *) source="$dir/$source" ;;',
614
+ ' esac',
615
+ ' done',
616
+ ' cd -P "$(dirname "$source")" && pwd',
617
+ '}',
618
+ '',
619
+ 'cleanup() {',
620
+ ' if [ -n "$TMP_APP_ROOT" ]; then',
621
+ ' rm -rf "$TMP_APP_ROOT"',
622
+ ' fi',
623
+ '}',
624
+ '',
625
+ 'forward_signal() {',
626
+ ' local sig="$1"',
627
+ ' if [ -n "$CHILD_PID" ] && kill -0 "$CHILD_PID" 2>/dev/null; then',
628
+ ' kill -"$sig" "$CHILD_PID" 2>/dev/null || true',
629
+ ' fi',
630
+ '}',
631
+ '',
632
+ 'trap cleanup EXIT',
633
+ "trap 'forward_signal HUP' HUP",
634
+ "trap 'forward_signal INT' INT",
635
+ "trap 'forward_signal TERM' TERM",
636
+ '',
637
+ 'APP_ROOT="$(resolve_file_dir "$START_SCRIPT")"',
638
+ 'WEBVIEW_DIR="$APP_ROOT/content/webview"',
639
+ '',
640
+ 'if [ ! -f "$PLUGIN_AUTH_UNLOCK_FILE" ]; then',
641
+ ' echo "Codex++ plugin auth unlock file not found: $PLUGIN_AUTH_UNLOCK_FILE" >&2',
642
+ ' exit 1',
643
+ 'fi',
644
+ '',
645
+ 'if [ ! -d "$WEBVIEW_DIR" ]; then',
646
+ ' exec "$START_SCRIPT" -- "$@"',
647
+ 'fi',
648
+ '',
649
+ 'TMP_APP_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/codex-plus-plus-linux-app.XXXXXX")"',
650
+ 'cp "$START_SCRIPT" "$TMP_APP_ROOT/start.sh"',
651
+ 'chmod 755 "$TMP_APP_ROOT/start.sh"',
652
+ '',
653
+ 'shopt -s dotglob nullglob',
654
+ 'for entry in "$APP_ROOT"/*; do',
655
+ ' name="$(basename "$entry")"',
656
+ ' if [ "$name" = "start.sh" ] || [ "$name" = "content" ]; then',
657
+ ' continue',
658
+ ' fi',
659
+ ' ln -s "$entry" "$TMP_APP_ROOT/$name"',
660
+ 'done',
661
+ 'shopt -u dotglob nullglob',
662
+ '',
663
+ 'mkdir -p "$TMP_APP_ROOT/content/webview/assets"',
664
+ 'if [ -d "$APP_ROOT/content" ]; then',
665
+ ' shopt -s dotglob nullglob',
666
+ ' for entry in "$APP_ROOT/content"/*; do',
667
+ ' name="$(basename "$entry")"',
668
+ ' if [ "$name" = "webview" ]; then',
669
+ ' continue',
670
+ ' fi',
671
+ ' ln -s "$entry" "$TMP_APP_ROOT/content/$name"',
672
+ ' done',
673
+ ' shopt -u dotglob nullglob',
674
+ 'fi',
675
+ '',
676
+ 'shopt -s dotglob nullglob',
677
+ 'for entry in "$WEBVIEW_DIR"/*; do',
678
+ ' name="$(basename "$entry")"',
679
+ ' if [ "$name" = "assets" ]; then',
680
+ ' continue',
681
+ ' fi',
682
+ ' ln -s "$entry" "$TMP_APP_ROOT/content/webview/$name"',
683
+ 'done',
684
+ 'for entry in "$WEBVIEW_DIR/assets"/*; do',
685
+ ' name="$(basename "$entry")"',
686
+ ' if [[ "$name" == plugin-auth-*.js ]]; then',
687
+ ' ln -s "$PLUGIN_AUTH_UNLOCK_FILE" "$TMP_APP_ROOT/content/webview/assets/$name"',
688
+ ' else',
689
+ ' ln -s "$entry" "$TMP_APP_ROOT/content/webview/assets/$name"',
690
+ ' fi',
691
+ 'done',
692
+ 'shopt -u dotglob nullglob',
693
+ '',
694
+ '"$TMP_APP_ROOT/start.sh" -- "$@" &',
695
+ 'CHILD_PID=$!',
696
+ 'set +e',
697
+ 'wait "$CHILD_PID"',
698
+ 'STATUS=$?',
699
+ 'set -e',
700
+ 'CHILD_PID=""',
701
+ 'exit "$STATUS"',
593
702
  '',
594
703
  ].join('\n');
595
704
  }
@@ -599,9 +708,16 @@ function writeLinuxCodexShim(installation, options = {}) {
599
708
  const shimRoot = linuxShimRoot(options);
600
709
  fsImpl.mkdirSync(shimRoot, { recursive: true });
601
710
  const shimPath = path.join(shimRoot, LINUX_SHIM_BINARY);
602
- fsImpl.writeFileSync(shimPath, linuxCodexShimScript(installation.startScript), 'utf8');
711
+ const pluginAuthTarget = path.join(shimRoot, PLUGIN_AUTH_UNLOCK_FILE);
712
+ const pluginAuthSource = pluginAuthUnlockPath(options);
713
+ if (fsImpl.existsSync(pluginAuthSource)) {
714
+ fsImpl.copyFileSync(pluginAuthSource, pluginAuthTarget);
715
+ } else {
716
+ fsImpl.writeFileSync(pluginAuthTarget, PLUGIN_AUTH_UNLOCK_CONTENT, 'utf8');
717
+ }
718
+ fsImpl.writeFileSync(shimPath, linuxCodexShimScript(installation.startScript, pluginAuthTarget), 'utf8');
603
719
  fsImpl.chmodSync(shimPath, 0o755);
604
- return { shimRoot, shimPath };
720
+ return { shimRoot, shimPath, pluginAuthUnlock: pluginAuthTarget };
605
721
  }
606
722
 
607
723
  function linuxDesktopEntry({ name, execPath, iconPath, comment }) {
@@ -756,6 +872,7 @@ async function installLinuxApp(options = {}) {
756
872
  const shim = writeLinuxCodexShim(installation, options);
757
873
  installed.linuxShim = shim.shimPath;
758
874
  installed.linuxShimRoot = shim.shimRoot;
875
+ installed.linuxPluginAuthUnlock = shim.pluginAuthUnlock;
759
876
  installed.linuxCodexStart = installation.startScript;
760
877
  const entrypoints = await installEntrypoints(installed, { ...options, installRoot: installed.installRoot });
761
878
  const statePath = writeLinuxInstallState(installed, installation, options);
@@ -0,0 +1 @@
1
+ function e(e){return false}export{e as t};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duanluan/codex-plus-plus-launcher",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "Install and launch Codex++ from npm",
5
5
  "bin": {
6
6
  "cxpp": "npm/cxpp.js",
@@ -15,12 +15,14 @@
15
15
  "files": [
16
16
  "codex_plus_plus_launcher/*.py",
17
17
  "codex_plus_plus_launcher/assets/*",
18
+ "patches/**",
18
19
  "upstream-bin/**",
19
20
  "install-npm.cjs",
20
21
  "npm/cxpp.js",
21
22
  "npm/i18n.js",
22
23
  "npm/launcher.js",
23
24
  "npm/install.js",
25
+ "npm/plugin-auth-unlocked.js",
24
26
  "npm/build-local-binary.cjs",
25
27
  "npm/verify-package.cjs",
26
28
  "npm/smoke-install.cjs",
@@ -0,0 +1,164 @@
1
+ --- a/assets/inject/renderer-inject.js
2
+ +++ b/assets/inject/renderer-inject.js
3
+ @@ -67,0 +68 @@
4
+ + const codexPluginNavUnlockVersion = "2";
5
+ @@ -2701,0 +2703,154 @@
6
+ + function pluginNavButtonCandidates() {
7
+ + return Array.from(document.querySelectorAll(selectors.pluginNavButton))
8
+ + .filter((button) => button instanceof HTMLElement && isPluginNavButton(button));
9
+ + }
10
+ +
11
+ + function isPluginNavButton(button) {
12
+ + const text = (button.textContent || "").trim();
13
+ + if (/^(Plugins|插件)$/.test(text)) return true;
14
+ + return !!button.querySelector?.(selectors.pluginSvgPath);
15
+ + }
16
+ +
17
+ + function reactPropKeys(element) {
18
+ + return Object.keys(element || {}).filter((key) => key.startsWith("__reactProps"));
19
+ + }
20
+ +
21
+ + function reactPropsFor(element) {
22
+ + return reactPropKeys(element)
23
+ + .map((key) => element[key])
24
+ + .filter((props) => props && typeof props === "object");
25
+ + }
26
+ +
27
+ + function reactFiberFor(element) {
28
+ + const key = Object.keys(element || {}).find((key) => key.startsWith("__reactFiber"));
29
+ + return key ? element[key] : null;
30
+ + }
31
+ +
32
+ + function reactPropHandlersFor(element) {
33
+ + const handlers = reactPropsFor(element)
34
+ + .map((props) => props.onClick)
35
+ + .filter((handler) => typeof handler === "function");
36
+ + const fiber = reactFiberFor(element);
37
+ + [
38
+ + fiber?.memoizedProps?.onClick,
39
+ + fiber?.pendingProps?.onClick,
40
+ + fiber?.alternate?.memoizedProps?.onClick,
41
+ + fiber?.alternate?.pendingProps?.onClick,
42
+ + ].forEach((handler) => {
43
+ + if (typeof handler === "function") handlers.push(handler);
44
+ + });
45
+ + return Array.from(new Set(handlers));
46
+ + }
47
+ +
48
+ + function reactClickHandlerFor(element) {
49
+ + for (const handler of reactPropHandlersFor(element)) {
50
+ + if (typeof handler === "function") return handler;
51
+ + }
52
+ + return null;
53
+ + }
54
+ +
55
+ + function isUsefulPluginNavClickHandler(handler) {
56
+ + if (typeof handler !== "function") return false;
57
+ + const source = Function.prototype.toString.call(handler);
58
+ + if (/^\s*(?:\(\)\s*=>\s*\{\s*\}|function\s*[^(]*\([^)]*\)\s*\{\s*\})\s*$/.test(source)) return false;
59
+ + if (source.includes("navigateToCodexPlugins")) return false;
60
+ + return true;
61
+ + }
62
+ +
63
+ + function isSkillsRouteClickHandler(handler) {
64
+ + if (!isUsefulPluginNavClickHandler(handler)) return false;
65
+ + const source = Function.prototype.toString.call(handler);
66
+ + return source.includes("Uy(") || source.includes("/skills");
67
+ + }
68
+ +
69
+ + function isSkillsNavButton(button) {
70
+ + const text = (button.textContent || "").trim();
71
+ + return /^(Skills|技能)$/.test(text);
72
+ + }
73
+ +
74
+ + function pluginNavFallbackClickHandler(button) {
75
+ + const ownSkillsHandler = reactPropHandlersFor(button).find(isSkillsRouteClickHandler);
76
+ + if (ownSkillsHandler) return ownSkillsHandler;
77
+ + const currentHandler = reactClickHandlerFor(button);
78
+ + if (isUsefulPluginNavClickHandler(currentHandler)) return currentHandler;
79
+ + const nav = button.closest?.('nav[role="navigation"]');
80
+ + const candidates = Array.from(nav?.querySelectorAll?.(selectors.pluginNavButton) || []);
81
+ + for (const candidate of candidates) {
82
+ + if (candidate === button) continue;
83
+ + const handlers = reactPropHandlersFor(candidate);
84
+ + const skillsHandler = handlers.find(isSkillsRouteClickHandler);
85
+ + if (skillsHandler) return skillsHandler;
86
+ + }
87
+ + const skillsButton = candidates.find((candidate) => candidate !== button && isSkillsNavButton(candidate));
88
+ + const skillsHandler = reactClickHandlerFor(skillsButton);
89
+ + if (isUsefulPluginNavClickHandler(skillsHandler)) return skillsHandler;
90
+ + return null;
91
+ + }
92
+ +
93
+ + function patchReactPluginNavProps(element) {
94
+ + const pluginHandler = pluginNavFallbackClickHandler(element);
95
+ + reactPropsFor(element)
96
+ + .forEach((props) => {
97
+ + props.disabled = false;
98
+ + props["aria-disabled"] = false;
99
+ + props["data-disabled"] = undefined;
100
+ + if (pluginHandler && !isUsefulPluginNavClickHandler(props.onClick)) {
101
+ + props.onClick = pluginHandler;
102
+ + }
103
+ + });
104
+ + }
105
+ +
106
+ + function clearPluginNavDisabledState(element) {
107
+ + if (!(element instanceof HTMLElement)) return;
108
+ + if ("disabled" in element) element.disabled = false;
109
+ + element.removeAttribute("disabled");
110
+ + element.removeAttribute("aria-disabled");
111
+ + element.removeAttribute("data-disabled");
112
+ + element.removeAttribute("inert");
113
+ + element.classList.remove("disabled", "opacity-50", "cursor-not-allowed", "pointer-events-none");
114
+ + element.style.pointerEvents = "auto";
115
+ + element.style.opacity = "";
116
+ + element.style.cursor = "pointer";
117
+ + element.tabIndex = 0;
118
+ + patchReactPluginNavProps(element);
119
+ + }
120
+ +
121
+ + function pluginNavUnlockNodes(button) {
122
+ + const nodes = [button];
123
+ + button.querySelectorAll?.("[disabled], [aria-disabled], [data-disabled], .cursor-not-allowed, .pointer-events-none")
124
+ + .forEach((node) => nodes.push(node));
125
+ + let parent = button.parentElement;
126
+ + for (let depth = 0; parent && depth < 3; depth += 1, parent = parent.parentElement) {
127
+ + if (parent.matches?.("button, [role='button'], [disabled], [aria-disabled], [data-disabled], .cursor-not-allowed, .pointer-events-none")) {
128
+ + nodes.push(parent);
129
+ + }
130
+ + }
131
+ + return Array.from(new Set(nodes));
132
+ + }
133
+ +
134
+ + function unlockPluginNavButtons() {
135
+ + if (!codexPlusSettings().pluginMarketplaceUnlock) return;
136
+ + pluginNavButtonCandidates().forEach((button) => {
137
+ + button.dataset.codexPluginNavUnlocked = codexPluginNavUnlockVersion;
138
+ + pluginNavUnlockNodes(button).forEach(clearPluginNavDisabledState);
139
+ + });
140
+ + }
141
+ +
142
+ + function refreshPluginNavUnlockLoop() {
143
+ + const shouldRun = codexPlusSettings().pluginMarketplaceUnlock;
144
+ + if (!shouldRun) {
145
+ + clearInterval(window.__codexPluginNavUnlockRefreshTimer);
146
+ + window.__codexPluginNavUnlockRefreshTimer = null;
147
+ + return;
148
+ + }
149
+ + if (window.__codexPluginNavUnlockRefreshTimer) return;
150
+ + window.__codexPluginNavUnlockRefreshTimer = setInterval(() => {
151
+ + if (!codexPlusSettings().pluginMarketplaceUnlock) {
152
+ + clearInterval(window.__codexPluginNavUnlockRefreshTimer);
153
+ + window.__codexPluginNavUnlockRefreshTimer = null;
154
+ + return;
155
+ + }
156
+ + unlockPluginNavButtons();
157
+ + }, codexForcePluginInstallRefreshIntervalMs);
158
+ + }
159
+ +
160
+ @@ -7592,0 +7748,2 @@
161
+ + unlockPluginNavButtons();
162
+ + refreshPluginNavUnlockLoop();
163
+ @@ -7647,0 +7805 @@
164
+ + selectors.pluginNavButton,
@@ -1,16 +1,16 @@
1
1
  {
2
- "version": "v1.2.0",
3
- "html_url": "https://github.com/BigPizzaV3/CodexPlusPlus/releases/tag/v1.2.0",
4
- "asset_name": "CodexPlusPlus-1.2.0-macos-arm64.dmg",
5
- "asset_url": "https://github.com/BigPizzaV3/CodexPlusPlus/releases/download/v1.2.0/CodexPlusPlus-1.2.0-macos-arm64.dmg",
6
- "windows_asset_name": "CodexPlusPlus-1.2.0-windows-x64-setup.exe",
7
- "windows_asset_url": "https://github.com/BigPizzaV3/CodexPlusPlus/releases/download/v1.2.0/CodexPlusPlus-1.2.0-windows-x64-setup.exe",
8
- "macos_x64_asset_name": "CodexPlusPlus-1.2.0-macos-x64.dmg",
9
- "macos_x64_asset_url": "https://github.com/BigPizzaV3/CodexPlusPlus/releases/download/v1.2.0/CodexPlusPlus-1.2.0-macos-x64.dmg",
10
- "macos_arm64_asset_name": "CodexPlusPlus-1.2.0-macos-arm64.dmg",
11
- "macos_arm64_asset_url": "https://github.com/BigPizzaV3/CodexPlusPlus/releases/download/v1.2.0/CodexPlusPlus-1.2.0-macos-arm64.dmg",
12
- "source_zip_url": "https://github.com/BigPizzaV3/CodexPlusPlus/archive/refs/tags/v1.2.0.zip",
13
- "install_spec": "https://github.com/BigPizzaV3/CodexPlusPlus/releases/download/v1.2.0/CodexPlusPlus-1.2.0-macos-arm64.dmg",
14
- "commit": "68f72e36db03c3052145e8fd1601847ac0f54d17",
2
+ "version": "v1.2.1",
3
+ "html_url": "https://github.com/BigPizzaV3/CodexPlusPlus/releases/tag/v1.2.1",
4
+ "asset_name": "CodexPlusPlus-1.2.1-macos-arm64.dmg",
5
+ "asset_url": "https://github.com/BigPizzaV3/CodexPlusPlus/releases/download/v1.2.1/CodexPlusPlus-1.2.1-macos-arm64.dmg",
6
+ "windows_asset_name": "CodexPlusPlus-1.2.1-windows-x64-setup.exe",
7
+ "windows_asset_url": "https://github.com/BigPizzaV3/CodexPlusPlus/releases/download/v1.2.1/CodexPlusPlus-1.2.1-windows-x64-setup.exe",
8
+ "macos_x64_asset_name": "CodexPlusPlus-1.2.1-macos-x64.dmg",
9
+ "macos_x64_asset_url": "https://github.com/BigPizzaV3/CodexPlusPlus/releases/download/v1.2.1/CodexPlusPlus-1.2.1-macos-x64.dmg",
10
+ "macos_arm64_asset_name": "CodexPlusPlus-1.2.1-macos-arm64.dmg",
11
+ "macos_arm64_asset_url": "https://github.com/BigPizzaV3/CodexPlusPlus/releases/download/v1.2.1/CodexPlusPlus-1.2.1-macos-arm64.dmg",
12
+ "source_zip_url": "https://github.com/BigPizzaV3/CodexPlusPlus/archive/refs/tags/v1.2.1.zip",
13
+ "install_spec": "https://github.com/BigPizzaV3/CodexPlusPlus/releases/download/v1.2.1/CodexPlusPlus-1.2.1-macos-arm64.dmg",
14
+ "commit": "b20ca373d2e5a29bb75b532bd93f74bac18626f5",
15
15
  "repository": "BigPizzaV3/CodexPlusPlus"
16
16
  }