codexkit 1.0.4 → 1.0.6

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/.codex/.version CHANGED
@@ -1 +1 @@
1
- 1.0.4
1
+ 1.0.6
package/CHANGELOG.md CHANGED
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.0.6] — 2026-03-18
11
+
12
+ ### Fixed
13
+
14
+ - **Windows compatibility**: CLI entry point changed from bash script (`bin/codexkit`) to Node.js wrapper (`bin/codexkit.js`), fixing `The term '/bin/bash.exe' is not recognized` error when installing via `npm install -g`, `pnpm add -g`, or `bun add -g` on Windows
15
+ - Node.js wrapper auto-detects bash from Git for Windows, MSYS2, WSL, or PATH; provides clear error message with install links if bash is not found
16
+ - Works cross-platform with all major package managers (npm, pnpm, bun, yarn)
17
+ - All install scripts now use `_python` / `_sha256` cross-platform helpers (via `install/_compat.sh`) instead of hardcoded `python3` / `shasum`, fixing failures on Windows where only `python` and `sha256sum` are available
18
+
10
19
  ## [1.0.4] — 2026-03-18
11
20
 
12
21
  ### Added
package/bin/codexkit CHANGED
@@ -23,6 +23,9 @@ SCRIPT_PATH="$(resolve_script_path)"
23
23
  SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)"
24
24
  REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
25
25
 
26
+ # shellcheck source=../install/_compat.sh
27
+ source "$REPO_DIR/install/_compat.sh"
28
+
26
29
  usage() {
27
30
  cat <<'EOF'
28
31
  Codexkit CLI
@@ -99,7 +102,7 @@ print_version() {
99
102
  if [ -f "$REPO_DIR/.codex/.version" ]; then
100
103
  cat "$REPO_DIR/.codex/.version"
101
104
  elif [ -f "$REPO_DIR/package.json" ]; then
102
- python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["version"])' "$REPO_DIR/package.json"
105
+ _python -c 'import json,sys; print(json.load(open(sys.argv[1]))["version"])' "$REPO_DIR/package.json"
103
106
  else
104
107
  echo "unknown"
105
108
  fi
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const { execFileSync, execSync } = require("child_process");
5
+ const path = require("path");
6
+ const fs = require("fs");
7
+ const os = require("os");
8
+
9
+ const BASH_SCRIPT = path.join(__dirname, "codexkit");
10
+
11
+ function findBash() {
12
+ if (os.platform() !== "win32") {
13
+ return "/bin/bash";
14
+ }
15
+
16
+ // On Windows, try common bash locations in order
17
+ const candidates = [];
18
+
19
+ // Git for Windows (most common)
20
+ const programFiles = process.env.ProgramFiles || "C:\\Program Files";
21
+ const programFilesX86 =
22
+ process.env["ProgramFiles(x86)"] || "C:\\Program Files (x86)";
23
+ candidates.push(
24
+ path.join(programFiles, "Git", "bin", "bash.exe"),
25
+ path.join(programFilesX86, "Git", "bin", "bash.exe"),
26
+ path.join(programFiles, "Git", "usr", "bin", "bash.exe")
27
+ );
28
+
29
+ // MSYS2
30
+ candidates.push("C:\\msys64\\usr\\bin\\bash.exe");
31
+
32
+ // WSL
33
+ const system32 = path.join(
34
+ process.env.SystemRoot || "C:\\Windows",
35
+ "System32"
36
+ );
37
+ candidates.push(path.join(system32, "bash.exe"));
38
+
39
+ // Check PATH via where
40
+ try {
41
+ const result = execSync("where bash.exe", {
42
+ encoding: "utf8",
43
+ stdio: ["pipe", "pipe", "pipe"],
44
+ }).trim();
45
+ if (result) {
46
+ const first = result.split(/\r?\n/)[0];
47
+ if (first && fs.existsSync(first)) {
48
+ return first;
49
+ }
50
+ }
51
+ } catch (_) {
52
+ // not found via where
53
+ }
54
+
55
+ for (const candidate of candidates) {
56
+ if (fs.existsSync(candidate)) {
57
+ return candidate;
58
+ }
59
+ }
60
+
61
+ console.error(
62
+ "Error: bash not found. Codexkit requires bash to run.\n\n" +
63
+ "On Windows, install one of the following:\n" +
64
+ " - Git for Windows: https://git-scm.com/download/win\n" +
65
+ " - WSL: wsl --install\n" +
66
+ " - MSYS2: https://www.msys2.org/\n"
67
+ );
68
+ process.exit(1);
69
+ }
70
+
71
+ const bash = findBash();
72
+ const args = process.argv.slice(2);
73
+
74
+ try {
75
+ const result = execFileSync(bash, [BASH_SCRIPT, ...args], {
76
+ stdio: "inherit",
77
+ env: { ...process.env },
78
+ windowsHide: false,
79
+ });
80
+ process.exit(0);
81
+ } catch (err) {
82
+ if (err.status != null) {
83
+ process.exit(err.status);
84
+ }
85
+ console.error(err.message);
86
+ process.exit(1);
87
+ }
@@ -0,0 +1,24 @@
1
+ #!/bin/bash
2
+ # Cross-platform compatibility helpers for Windows (Git Bash), macOS, and Linux.
3
+ # Source this file at the top of any install script that uses python3 or shasum.
4
+
5
+ _python() {
6
+ if command -v python3 &>/dev/null; then
7
+ python3 "$@"
8
+ elif command -v python &>/dev/null; then
9
+ python "$@"
10
+ else
11
+ echo "✗ Python not found. Codexkit requires Python 3." >&2
12
+ exit 1
13
+ fi
14
+ }
15
+
16
+ _sha256() {
17
+ if command -v shasum &>/dev/null; then
18
+ shasum -a 256 "$@"
19
+ elif command -v sha256sum &>/dev/null; then
20
+ sha256sum "$@"
21
+ else
22
+ echo ""
23
+ fi
24
+ }
@@ -3,6 +3,9 @@ set -euo pipefail
3
3
 
4
4
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
5
5
  REPO_DIR="$(dirname "$SCRIPT_DIR")"
6
+
7
+ # shellcheck source=_compat.sh
8
+ source "$SCRIPT_DIR/_compat.sh"
6
9
  OUTPUT_FILE="$REPO_DIR/.template-manifest.json"
7
10
  CODEX_OUTPUT_FILE="$REPO_DIR/.codex/.template-manifest.json"
8
11
 
@@ -38,7 +41,7 @@ while IFS= read -r file; do FILES+=("$file"); done < <(find .codex/plans -type f
38
41
  for file in "${FILES[@]}"; do
39
42
  normalized="${file#./}"
40
43
  [[ -f "$normalized" ]] || continue
41
- hash="$(shasum -a 256 "$normalized" | cut -d' ' -f1)"
44
+ hash="$(_sha256 "$normalized" | cut -d' ' -f1)"
42
45
  if $first; then
43
46
  first=false
44
47
  else
@@ -4,6 +4,9 @@ set -euo pipefail
4
4
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
5
5
  REPO_DIR="$(dirname "$SCRIPT_DIR")"
6
6
 
7
+ # shellcheck source=_compat.sh
8
+ source "$SCRIPT_DIR/_compat.sh"
9
+
7
10
  # Parse flags
8
11
  FORCE=false
9
12
  UPGRADE=false
@@ -38,8 +41,8 @@ check_copy_action() {
38
41
  $FORCE && return 2
39
42
 
40
43
  if $UPGRADE && [ -f "$MANIFEST" ]; then
41
- manifest_hash=$(python3 -c 'import json,sys; d=json.load(open(sys.argv[1])); print(d.get("files",{}).get(sys.argv[2],""))' "$MANIFEST" "$source_path" 2>/dev/null || echo "")
42
- installed_hash=$(shasum -a 256 "$target" 2>/dev/null | cut -d' ' -f1)
44
+ manifest_hash=$(_python -c 'import json,sys; d=json.load(open(sys.argv[1])); print(d.get("files",{}).get(sys.argv[2],""))' "$MANIFEST" "$source_path" 2>/dev/null || echo "")
45
+ installed_hash=$(_sha256 "$target" 2>/dev/null | cut -d' ' -f1)
43
46
  if [ -n "$manifest_hash" ] && [ "$installed_hash" = "$manifest_hash" ]; then
44
47
  return 2
45
48
  fi
@@ -4,6 +4,9 @@ set -euo pipefail
4
4
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
5
5
  REPO_DIR="$(dirname "$SCRIPT_DIR")"
6
6
 
7
+ # shellcheck source=_compat.sh
8
+ source "$SCRIPT_DIR/_compat.sh"
9
+
7
10
  # Parse args
8
11
  PROJECT_DIR=""
9
12
  FORCE=false
@@ -66,8 +69,8 @@ check_copy_action() {
66
69
 
67
70
  # Upgrade mode → check if customized
68
71
  if $UPGRADE && [ -f "$MANIFEST" ]; then
69
- manifest_hash=$(python3 -c 'import json,sys; d=json.load(open(sys.argv[1])); print(d.get("files",{}).get(sys.argv[2],""))' "$MANIFEST" "$source_path" 2>/dev/null || echo "")
70
- installed_hash=$(shasum -a 256 "$target" 2>/dev/null | cut -d' ' -f1)
72
+ manifest_hash=$(_python -c 'import json,sys; d=json.load(open(sys.argv[1])); print(d.get("files",{}).get(sys.argv[2],""))' "$MANIFEST" "$source_path" 2>/dev/null || echo "")
73
+ installed_hash=$(_sha256 "$target" 2>/dev/null | cut -d' ' -f1)
71
74
 
72
75
  if [ -n "$manifest_hash" ] && [ "$installed_hash" = "$manifest_hash" ]; then
73
76
  # File matches original template → safe to upgrade
@@ -174,7 +177,7 @@ detect_orphans() {
174
177
 
175
178
  # Write manifest keys to a temp file for O(1) lookup via grep -Fxf
176
179
  manifest_file=$(mktemp)
177
- python3 -c '
180
+ _python -c '
178
181
  import json, sys
179
182
  d = json.load(open(sys.argv[1]))
180
183
  for k in sorted(d.get("files", {}).keys()):
@@ -734,9 +737,9 @@ if [ -n "$TEMPLATE_NAME" ]; then
734
737
  echo "Applying template: $TEMPLATE_NAME"
735
738
 
736
739
  # Collect variables from template.json and prompt user
737
- if command -v python3 &>/dev/null; then
740
+ if command -v python3 &>/dev/null || command -v python &>/dev/null; then
738
741
  # Extract variable names and defaults
739
- _var_json=$(python3 -c '
742
+ _var_json=$(_python -c '
740
743
  import json, sys
741
744
  t = json.load(open(sys.argv[1]))
742
745
  vars = t.get("variables", {})
package/install/patch.sh CHANGED
@@ -3,6 +3,9 @@ set -euo pipefail
3
3
 
4
4
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
5
5
  REPO_DIR="$(dirname "$SCRIPT_DIR")"
6
+
7
+ # shellcheck source=_compat.sh
8
+ source "$SCRIPT_DIR/_compat.sh"
6
9
  PROJECT_DIR="${CODEXKIT_PROJECT_DIR:-$(pwd)}"
7
10
  PATCHES_DIR="$PROJECT_DIR/Codexkit_patches"
8
11
  MANIFEST_FILE="$PROJECT_DIR/.codex/.template-manifest.json"
@@ -45,7 +48,7 @@ require_project() {
45
48
  }
46
49
 
47
50
  slugify() {
48
- python3 - "$1" <<'PY'
51
+ _python - "$1" <<'PY'
49
52
  import re
50
53
  import sys
51
54
  value = sys.argv[1].strip().lower()
@@ -56,7 +59,7 @@ PY
56
59
  }
57
60
 
58
61
  split_patch_input() {
59
- python3 - "$1" <<'PY'
62
+ _python - "$1" <<'PY'
60
63
  import sys
61
64
  raw = sys.argv[1].strip()
62
65
  if " - " in raw:
@@ -69,7 +72,7 @@ PY
69
72
  }
70
73
 
71
74
  json_get() {
72
- python3 - "$1" "$2" <<'PY'
75
+ _python - "$1" "$2" <<'PY'
73
76
  import json
74
77
  import sys
75
78
  data = json.load(open(sys.argv[1]))
@@ -90,7 +93,7 @@ PY
90
93
  }
91
94
 
92
95
  metadata_update_enabled() {
93
- python3 - "$1" "$2" <<'PY'
96
+ _python - "$1" "$2" <<'PY'
94
97
  import json
95
98
  import sys
96
99
  path = sys.argv[1]
@@ -105,7 +108,7 @@ PY
105
108
  }
106
109
 
107
110
  manifest_hash_for() {
108
- python3 - "$MANIFEST_FILE" "$1" <<'PY'
111
+ _python - "$MANIFEST_FILE" "$1" <<'PY'
109
112
  import json
110
113
  import sys
111
114
  data = json.load(open(sys.argv[1]))
@@ -114,7 +117,7 @@ PY
114
117
  }
115
118
 
116
119
  find_customized_pairs() {
117
- python3 -c '
120
+ _python -c '
118
121
  import hashlib
119
122
  import json
120
123
  import os
@@ -287,7 +290,7 @@ cmd_create() {
287
290
 
288
291
  mv "$tmp_patch" "$patch_file"
289
292
 
290
- python3 - "$metadata_file" "$slug" "$title" "$description" "$(project_version)" "$(date +%F)" "${target_files[@]}" <<'PY'
293
+ _python - "$metadata_file" "$slug" "$title" "$description" "$(project_version)" "$(date +%F)" "${target_files[@]}" <<'PY'
291
294
  import json
292
295
  import sys
293
296
  path, name, title, desc, version, created, *targets = sys.argv[1:]
package/install/plugin.sh CHANGED
@@ -5,6 +5,9 @@
5
5
  # ──────────────────────────────────────────────────────────────
6
6
  set -euo pipefail
7
7
 
8
+ # shellcheck source=_compat.sh
9
+ source "$(cd "$(dirname "$0")" && pwd)/_compat.sh"
10
+
8
11
  CODEXKIT_HOME="${CODEXKIT_HOME:-$HOME/.codexkit}"
9
12
  PLUGINS_DIR="$CODEXKIT_HOME/plugins"
10
13
  REGISTRY_FILE="$CODEXKIT_HOME/registry.json"
@@ -79,7 +82,7 @@ _registry_add_entry() {
79
82
  local tmp
80
83
  tmp=$(mktemp)
81
84
  # Read current registry, build new one
82
- python3 -c "
85
+ _python -c "
83
86
  import json, sys
84
87
  with open('$REGISTRY_FILE') as f:
85
88
  reg = json.load(f)
@@ -94,7 +97,7 @@ _registry_remove_entry() {
94
97
  local section="$1" name="$2"
95
98
  local tmp
96
99
  tmp=$(mktemp)
97
- python3 -c "
100
+ _python -c "
98
101
  import json
99
102
  with open('$REGISTRY_FILE') as f:
100
103
  reg = json.load(f)
@@ -5,6 +5,9 @@
5
5
  # ──────────────────────────────────────────────────────────────
6
6
  set -euo pipefail
7
7
 
8
+ # shellcheck source=_compat.sh
9
+ source "$(cd "$(dirname "$0")" && pwd)/_compat.sh"
10
+
8
11
  CODEXKIT_HOME="${CODEXKIT_HOME:-$HOME/.codexkit}"
9
12
  TEMPLATES_DIR="$CODEXKIT_HOME/templates"
10
13
  REGISTRY_FILE="$CODEXKIT_HOME/registry.json"
@@ -46,7 +49,7 @@ registry_add_template() {
46
49
  '.templates[$n] = {"version": $v, "source": $s, "installed": $d}' \
47
50
  "$REGISTRY_FILE" > "$tmp" && mv "$tmp" "$REGISTRY_FILE"
48
51
  else
49
- python3 -c "
52
+ _python -c "
50
53
  import json
51
54
  with open('$REGISTRY_FILE') as f:
52
55
  reg = json.load(f)
@@ -65,7 +68,7 @@ registry_remove_template() {
65
68
  tmp=$(mktemp)
66
69
  jq --arg n "$name" 'del(.templates[$n])' "$REGISTRY_FILE" > "$tmp" && mv "$tmp" "$REGISTRY_FILE"
67
70
  else
68
- python3 -c "
71
+ _python -c "
69
72
  import json
70
73
  with open('$REGISTRY_FILE') as f:
71
74
  reg = json.load(f)
@@ -93,7 +96,7 @@ get_variables() {
93
96
  if command -v jq &>/dev/null; then
94
97
  jq -r '.variables // {} | keys[]' "$manifest" 2>/dev/null
95
98
  else
96
- python3 -c "
99
+ _python -c "
97
100
  import json
98
101
  with open('$manifest') as f:
99
102
  t = json.load(f)
@@ -109,7 +112,7 @@ get_variable_prop() {
109
112
  if command -v jq &>/dev/null; then
110
113
  jq -r ".variables[\"$var_name\"].$prop // empty" "$manifest" 2>/dev/null
111
114
  else
112
- python3 -c "
115
+ _python -c "
113
116
  import json
114
117
  with open('$manifest') as f:
115
118
  t = json.load(f)
@@ -3,6 +3,9 @@ set -euo pipefail
3
3
 
4
4
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
5
5
  REPO_DIR="$(dirname "$SCRIPT_DIR")"
6
+
7
+ # shellcheck source=_compat.sh
8
+ source "$SCRIPT_DIR/_compat.sh"
6
9
  PASS=0
7
10
  FAIL=0
8
11
  WARN=0
@@ -58,7 +61,7 @@ project_has_local_shared_inventory() {
58
61
  }
59
62
 
60
63
  path_sha256() {
61
- shasum -a 256 "$1" 2>/dev/null | cut -d' ' -f1
64
+ _sha256 "$1" 2>/dev/null | cut -d' ' -f1
62
65
  }
63
66
 
64
67
  classify_local_shared_files() {
@@ -113,7 +116,7 @@ patch_inventory_counts() {
113
116
  }
114
117
 
115
118
  while IFS= read -r metadata; do
116
- if enabled="$(python3 -c 'import json,sys; print(str(json.load(open(sys.argv[1])).get("enabled", True)).lower())' "$metadata" 2>/dev/null)"; then
119
+ if enabled="$(_python -c 'import json,sys; print(str(json.load(open(sys.argv[1])).get("enabled", True)).lower())' "$metadata" 2>/dev/null)"; then
117
120
  if [ "$enabled" = "true" ]; then
118
121
  enabled_count=$((enabled_count + 1))
119
122
  else
@@ -559,7 +562,7 @@ if [ -d "$CODEXKIT_HOME/plugins" ]; then
559
562
  plugin_invalid=$((plugin_invalid + 1))
560
563
  else
561
564
  # Validate required fields
562
- if python3 -c '
565
+ if _python -c '
563
566
  import json, sys
564
567
  p = json.load(open(sys.argv[1]))
565
568
  assert p.get("name"), "missing name"
@@ -595,7 +598,7 @@ if [ -d "$CODEXKIT_HOME/templates" ]; then
595
598
  check "Template '$tname' missing template.json" "fail"
596
599
  template_invalid=$((template_invalid + 1))
597
600
  else
598
- if python3 -c '
601
+ if _python -c '
599
602
  import json, sys
600
603
  t = json.load(open(sys.argv[1]))
601
604
  assert t.get("name"), "missing name"
@@ -621,7 +624,7 @@ fi
621
624
  echo ""
622
625
  echo "Registry:"
623
626
  if [ -f "$CODEXKIT_HOME/registry.json" ]; then
624
- if python3 -c '
627
+ if _python -c '
625
628
  import json, sys
626
629
  r = json.load(open(sys.argv[1]))
627
630
  assert isinstance(r.get("plugins", {}), dict)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codexkit",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "Professional configuration kit for OpenAI Codex CLI",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -11,7 +11,7 @@
11
11
  "installer"
12
12
  ],
13
13
  "bin": {
14
- "codexkit": "bin/codexkit"
14
+ "codexkit": "bin/codexkit.js"
15
15
  },
16
16
  "files": [
17
17
  "bin",