claudekit-codex-sync 0.1.0

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 (56) hide show
  1. package/AGENTS.md +45 -0
  2. package/README.md +131 -0
  3. package/bin/ck-codex-sync +12 -0
  4. package/bin/ck-codex-sync.js +9 -0
  5. package/docs/code-standards.md +62 -0
  6. package/docs/codebase-summary.md +83 -0
  7. package/docs/codex-vs-claude-agents.md +74 -0
  8. package/docs/installation-guide.md +64 -0
  9. package/docs/project-overview-pdr.md +44 -0
  10. package/docs/project-roadmap.md +51 -0
  11. package/docs/system-architecture.md +106 -0
  12. package/package.json +16 -0
  13. package/plans/260222-2051-claudekit-codex-community-sync/phase-01-productization.md +36 -0
  14. package/plans/260222-2051-claudekit-codex-community-sync/phase-02-core-refactor.md +32 -0
  15. package/plans/260222-2051-claudekit-codex-community-sync/phase-03-agent-transpiler.md +33 -0
  16. package/plans/260222-2051-claudekit-codex-community-sync/phase-04-parity-harness.md +43 -0
  17. package/plans/260222-2051-claudekit-codex-community-sync/phase-05-distribution-npm.md +35 -0
  18. package/plans/260222-2051-claudekit-codex-community-sync/phase-06-git-clone-docs.md +28 -0
  19. package/plans/260222-2051-claudekit-codex-community-sync/phase-07-qa-release.md +35 -0
  20. package/plans/260222-2051-claudekit-codex-community-sync/plan.md +99 -0
  21. package/plans/260223-0951-refactor-and-upgrade/phase-01-project-structure.md +79 -0
  22. package/plans/260223-0951-refactor-and-upgrade/phase-02-extract-templates.md +36 -0
  23. package/plans/260223-0951-refactor-and-upgrade/phase-03-modularize-python.md +107 -0
  24. package/plans/260223-0951-refactor-and-upgrade/phase-04-live-source-detection.md +76 -0
  25. package/plans/260223-0951-refactor-and-upgrade/phase-05-agent-toml-config.md +88 -0
  26. package/plans/260223-0951-refactor-and-upgrade/phase-06-backup-registry.md +58 -0
  27. package/plans/260223-0951-refactor-and-upgrade/phase-07-tests-docs-push.md +54 -0
  28. package/plans/260223-0951-refactor-and-upgrade/plan.md +72 -0
  29. package/reports/brainstorm-260222-2051-claudekit-codex-community-sync.md +113 -0
  30. package/scripts/bootstrap-claudekit-skill-scripts.sh +150 -0
  31. package/scripts/claudekit-sync-all.py +1150 -0
  32. package/scripts/export-claudekit-prompts.sh +221 -0
  33. package/scripts/normalize-claudekit-for-codex.sh +261 -0
  34. package/src/claudekit_codex_sync/__init__.py +0 -0
  35. package/src/claudekit_codex_sync/asset_sync_dir.py +125 -0
  36. package/src/claudekit_codex_sync/asset_sync_zip.py +140 -0
  37. package/src/claudekit_codex_sync/bridge_generator.py +33 -0
  38. package/src/claudekit_codex_sync/cli.py +199 -0
  39. package/src/claudekit_codex_sync/config_enforcer.py +140 -0
  40. package/src/claudekit_codex_sync/constants.py +104 -0
  41. package/src/claudekit_codex_sync/dep_bootstrapper.py +73 -0
  42. package/src/claudekit_codex_sync/path_normalizer.py +248 -0
  43. package/src/claudekit_codex_sync/prompt_exporter.py +89 -0
  44. package/src/claudekit_codex_sync/runtime_verifier.py +32 -0
  45. package/src/claudekit_codex_sync/source_resolver.py +78 -0
  46. package/src/claudekit_codex_sync/sync_registry.py +77 -0
  47. package/src/claudekit_codex_sync/utils.py +130 -0
  48. package/templates/agents-md.md +45 -0
  49. package/templates/bridge-docs-init.sh +25 -0
  50. package/templates/bridge-project-status.sh +49 -0
  51. package/templates/bridge-resolve-command.py +52 -0
  52. package/templates/bridge-skill.md +63 -0
  53. package/templates/command-map.md +44 -0
  54. package/tests/__init__.py +1 -0
  55. package/tests/test_config_enforcer.py +44 -0
  56. package/tests/test_path_normalizer.py +61 -0
@@ -0,0 +1,221 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
5
+ SRC_DIR="${CODEX_HOME}/claudekit/commands"
6
+ DEST_DIR="${CODEX_HOME}/prompts"
7
+ MANIFEST_NAME=".claudekit-generated-prompts.txt"
8
+ PREFIX=""
9
+ FORCE="false"
10
+ DRY_RUN="false"
11
+ INCLUDE_MCP="false"
12
+
13
+ usage() {
14
+ cat <<'EOF'
15
+ Export ClaudeKit commands into Codex custom prompts.
16
+
17
+ Usage:
18
+ export-claudekit-prompts.sh [options]
19
+
20
+ Options:
21
+ --source <dir> Source commands directory (default: ~/.codex/claudekit/commands).
22
+ --dest <dir> Prompt output directory (default: ~/.codex/prompts).
23
+ --prefix <value> Prefix for generated prompt names (default: empty).
24
+ --force Overwrite existing unmanaged prompt files on name collision.
25
+ --include-mcp Include use-mcp.md (disabled by default).
26
+ --dry-run Show planned changes without writing files.
27
+ -h, --help Show this help.
28
+ EOF
29
+ }
30
+
31
+ while [[ $# -gt 0 ]]; do
32
+ case "$1" in
33
+ --source)
34
+ shift
35
+ SRC_DIR="${1:-}"
36
+ ;;
37
+ --dest)
38
+ shift
39
+ DEST_DIR="${1:-}"
40
+ ;;
41
+ --prefix)
42
+ shift
43
+ PREFIX="${1:-}"
44
+ ;;
45
+ --force)
46
+ FORCE="true"
47
+ ;;
48
+ --include-mcp)
49
+ INCLUDE_MCP="true"
50
+ ;;
51
+ --dry-run)
52
+ DRY_RUN="true"
53
+ ;;
54
+ -h|--help)
55
+ usage
56
+ exit 0
57
+ ;;
58
+ *)
59
+ echo "Unknown option: $1" >&2
60
+ usage >&2
61
+ exit 1
62
+ ;;
63
+ esac
64
+ shift
65
+ done
66
+
67
+ if [[ ! -d "$SRC_DIR" ]]; then
68
+ echo "Source directory not found: $SRC_DIR" >&2
69
+ exit 1
70
+ fi
71
+
72
+ MANIFEST="${DEST_DIR}/${MANIFEST_NAME}"
73
+ declare -A previous_generated=()
74
+ if [[ -f "$MANIFEST" ]]; then
75
+ while IFS= read -r line; do
76
+ [[ -n "$line" ]] && previous_generated["$line"]=1
77
+ done < "$MANIFEST"
78
+ fi
79
+
80
+ should_skip() {
81
+ local rel="$1"
82
+ local base
83
+ base="$(basename "$rel")"
84
+ if [[ "$base" == "codex-command-map.md" ]]; then
85
+ return 0
86
+ fi
87
+ if [[ "$base" == "use-mcp.md" && "$INCLUDE_MCP" != "true" ]]; then
88
+ return 0
89
+ fi
90
+ return 1
91
+ }
92
+
93
+ transform_content() {
94
+ sed \
95
+ -e 's#\$HOME/\.claude/skills/#${CODEX_HOME:-$HOME/.codex}/skills/#g' \
96
+ -e 's#\$HOME/\.claude/scripts/#${CODEX_HOME:-$HOME/.codex}/claudekit/scripts/#g' \
97
+ -e 's#\$HOME/\.claude/rules/#${CODEX_HOME:-$HOME/.codex}/claudekit/rules/#g' \
98
+ -e 's#\$HOME/\.claude/#${CODEX_HOME:-$HOME/.codex}/#g' \
99
+ -e 's#\./\.claude/skills/#~/.codex/skills/#g' \
100
+ -e 's#\.claude/skills/#~/.codex/skills/#g' \
101
+ -e 's#\./\.claude/scripts/#~/.codex/claudekit/scripts/#g' \
102
+ -e 's#\.claude/scripts/#~/.codex/claudekit/scripts/#g' \
103
+ -e 's#\./\.claude/rules/#~/.codex/claudekit/rules/#g' \
104
+ -e 's#\.claude/rules/#~/.codex/claudekit/rules/#g' \
105
+ -e 's#~/.claude/.ck.json#~/.codex/claudekit/.ck.json#g' \
106
+ -e 's#\./\.claude/.ck.json#~/.codex/claudekit/.ck.json#g' \
107
+ -e 's#\.claude/.ck.json#~/.codex/claudekit/.ck.json#g' \
108
+ -e 's#\$HOME/\${CODEX_HOME:-\$HOME/\.codex}/#${CODEX_HOME:-$HOME/.codex}/#g'
109
+ }
110
+
111
+ build_prompt_file() {
112
+ local src="$1"
113
+ local rel="$2"
114
+ local out="$3"
115
+ local cmd_path
116
+ cmd_path="/${rel%.md}"
117
+ local transformed
118
+ transformed="$(mktemp /tmp/ck-prompt-content.XXXXXX)"
119
+ transform_content < "$src" > "$transformed"
120
+
121
+ if [[ "$(head -n 1 "$transformed")" == "---" ]]; then
122
+ cat "$transformed" > "$out"
123
+ else
124
+ {
125
+ echo "---"
126
+ echo "description: ClaudeKit compatibility prompt for ${cmd_path}"
127
+ echo "---"
128
+ echo
129
+ cat "$transformed"
130
+ } > "$out"
131
+ fi
132
+
133
+ rm -f "$transformed"
134
+ }
135
+
136
+ mapfile -t source_files < <(find "$SRC_DIR" -type f -name '*.md' | sort)
137
+ if [[ ${#source_files[@]} -eq 0 ]]; then
138
+ echo "No markdown command files found in: $SRC_DIR" >&2
139
+ exit 1
140
+ fi
141
+
142
+ tmp_dir="$(mktemp -d /tmp/ck-prompts.XXXXXX)"
143
+ cleanup() {
144
+ rm -rf "$tmp_dir"
145
+ }
146
+ trap cleanup EXIT
147
+
148
+ if [[ "$DRY_RUN" != "true" ]]; then
149
+ mkdir -p "$DEST_DIR"
150
+ fi
151
+
152
+ declare -A generated=()
153
+ added=0
154
+ updated=0
155
+ skipped=0
156
+ collisions=0
157
+ removed=0
158
+
159
+ for src in "${source_files[@]}"; do
160
+ rel="${src#$SRC_DIR/}"
161
+ if should_skip "$rel"; then
162
+ skipped=$((skipped + 1))
163
+ echo "skip: $rel"
164
+ continue
165
+ fi
166
+
167
+ prompt_name="${rel%.md}"
168
+ prompt_name="${prompt_name//\//-}"
169
+ prompt_file="${PREFIX}${prompt_name}.md"
170
+ prompt_dest="${DEST_DIR}/${prompt_file}"
171
+ prompt_temp="${tmp_dir}/${prompt_file}"
172
+
173
+ build_prompt_file "$src" "$rel" "$prompt_temp"
174
+
175
+ if [[ -f "$prompt_dest" && -z "${previous_generated[$prompt_file]:-}" && "$FORCE" != "true" ]]; then
176
+ collisions=$((collisions + 1))
177
+ echo "skip(collision): $prompt_file"
178
+ continue
179
+ fi
180
+
181
+ if [[ -f "$prompt_dest" ]]; then
182
+ updated=$((updated + 1))
183
+ echo "update: $prompt_file <= $rel"
184
+ else
185
+ added=$((added + 1))
186
+ echo "add: $prompt_file <= $rel"
187
+ fi
188
+
189
+ generated["$prompt_file"]=1
190
+ if [[ "$DRY_RUN" != "true" ]]; then
191
+ install -m 0644 "$prompt_temp" "$prompt_dest"
192
+ fi
193
+ done
194
+
195
+ if [[ "$DRY_RUN" != "true" ]]; then
196
+ for old in "${!previous_generated[@]}"; do
197
+ if [[ -z "${generated[$old]:-}" ]]; then
198
+ stale_path="${DEST_DIR}/${old}"
199
+ if [[ -f "$stale_path" ]]; then
200
+ rm -f "$stale_path"
201
+ removed=$((removed + 1))
202
+ echo "remove(stale): $old"
203
+ fi
204
+ fi
205
+ done
206
+
207
+ {
208
+ for f in "${!generated[@]}"; do
209
+ echo "$f"
210
+ done
211
+ } | sort > "$MANIFEST"
212
+ fi
213
+
214
+ total_generated="${#generated[@]}"
215
+ echo "source: $SRC_DIR"
216
+ echo "dest: $DEST_DIR"
217
+ echo "added=$added updated=$updated skipped=$skipped collisions=$collisions removed=$removed total_generated=$total_generated"
218
+
219
+ if [[ "$DRY_RUN" == "true" ]]; then
220
+ echo "DRY_RUN=true (no files changed)"
221
+ fi
@@ -0,0 +1,261 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
5
+ SKILLS_DIR="${CODEX_HOME}/skills"
6
+ CLAUDEKIT_DIR="${CODEX_HOME}/claudekit"
7
+ DRY_RUN="false"
8
+ INCLUDE_MCP="false"
9
+
10
+ usage() {
11
+ cat <<'EOF'
12
+ Re-apply Codex compatibility transforms after ClaudeKit sync.
13
+
14
+ Usage:
15
+ normalize-claudekit-for-codex.sh [options]
16
+
17
+ Options:
18
+ --include-mcp Also normalize MCP skills/docs (disabled by default).
19
+ --dry-run Show planned changes without writing files.
20
+ -h, --help Show this help.
21
+ EOF
22
+ }
23
+
24
+ while [[ $# -gt 0 ]]; do
25
+ case "$1" in
26
+ --include-mcp)
27
+ INCLUDE_MCP="true"
28
+ ;;
29
+ --dry-run)
30
+ DRY_RUN="true"
31
+ ;;
32
+ -h|--help)
33
+ usage
34
+ exit 0
35
+ ;;
36
+ *)
37
+ echo "Unknown option: $1" >&2
38
+ usage >&2
39
+ exit 1
40
+ ;;
41
+ esac
42
+ shift
43
+ done
44
+
45
+ if [[ ! -d "$SKILLS_DIR" ]]; then
46
+ echo "Skills directory not found: $SKILLS_DIR" >&2
47
+ exit 1
48
+ fi
49
+
50
+ if [[ ! -d "$CLAUDEKIT_DIR" ]]; then
51
+ echo "ClaudeKit directory not found: $CLAUDEKIT_DIR" >&2
52
+ exit 1
53
+ fi
54
+
55
+ transform_file() {
56
+ local file="$1"
57
+ local tmp
58
+ tmp="$(mktemp /tmp/ck-normalize.XXXXXX)"
59
+
60
+ sed \
61
+ -e 's#\$HOME/\.claude/skills/#${CODEX_HOME:-$HOME/.codex}/skills/#g' \
62
+ -e 's#\$HOME/\.claude/scripts/#${CODEX_HOME:-$HOME/.codex}/claudekit/scripts/#g' \
63
+ -e 's#\$HOME/\.claude/rules/#${CODEX_HOME:-$HOME/.codex}/claudekit/rules/#g' \
64
+ -e 's#\$HOME/\.claude/#${CODEX_HOME:-$HOME/.codex}/#g' \
65
+ -e 's#\./\.claude/skills/#${CODEX_HOME:-$HOME/.codex}/skills/#g' \
66
+ -e 's#\.claude/skills/#${CODEX_HOME:-$HOME/.codex}/skills/#g' \
67
+ -e 's#\./\.claude/scripts/#${CODEX_HOME:-$HOME/.codex}/claudekit/scripts/#g' \
68
+ -e 's#\.claude/scripts/#${CODEX_HOME:-$HOME/.codex}/claudekit/scripts/#g' \
69
+ -e 's#\./\.claude/rules/#${CODEX_HOME:-$HOME/.codex}/claudekit/rules/#g' \
70
+ -e 's#\.claude/rules/#${CODEX_HOME:-$HOME/.codex}/claudekit/rules/#g' \
71
+ -e 's#~/.claude/.ck.json#~/.codex/claudekit/.ck.json#g' \
72
+ -e 's#\./\.claude/.ck.json#~/.codex/claudekit/.ck.json#g' \
73
+ -e 's#\.claude/.ck.json#~/.codex/claudekit/.ck.json#g' \
74
+ -e 's#~/.claude/#~/.codex/#g' \
75
+ -e 's#\./\.claude/#./.codex/#g' \
76
+ -e 's#<project>/.claude/#<project>/.codex/#g' \
77
+ -e 's#\.claude/#.codex/#g' \
78
+ -e 's#`\.claude`#`.codex`#g' \
79
+ -e 's#\$HOME/\${CODEX_HOME:-\$HOME/\.codex}/#${CODEX_HOME:-$HOME/.codex}/#g' \
80
+ "$file" > "$tmp"
81
+
82
+ if cmp -s "$file" "$tmp"; then
83
+ rm -f "$tmp"
84
+ return 1
85
+ fi
86
+
87
+ if [[ "$DRY_RUN" == "true" ]]; then
88
+ rm -f "$tmp"
89
+ return 0
90
+ fi
91
+
92
+ cp "$tmp" "$file"
93
+ rm -f "$tmp"
94
+ return 0
95
+ }
96
+
97
+ changed=0
98
+
99
+ while IFS= read -r -d '' file; do
100
+ if [[ "$INCLUDE_MCP" != "true" && ( "$file" == *"/mcp-builder/"* || "$file" == *"/mcp-management/"* ) ]]; then
101
+ continue
102
+ fi
103
+ if transform_file "$file"; then
104
+ changed=$((changed + 1))
105
+ echo "normalize: ${file#$CODEX_HOME/}"
106
+ fi
107
+ done < <(find "$SKILLS_DIR" -type f -name 'SKILL.md' -not -path '*/.system/*' -print0)
108
+
109
+ while IFS= read -r -d '' file; do
110
+ if transform_file "$file"; then
111
+ changed=$((changed + 1))
112
+ echo "normalize: ${file#$CODEX_HOME/}"
113
+ fi
114
+ done < <(find "$CLAUDEKIT_DIR" -type f -name '*.md' -print0)
115
+
116
+ copywriting_script="${SKILLS_DIR}/copywriting/scripts/extract-writing-styles.py"
117
+ if [[ -f "$copywriting_script" ]]; then
118
+ if [[ "$DRY_RUN" == "true" ]]; then
119
+ if ! grep -q "CODEX_HOME = Path(os.environ.get('CODEX_HOME'" "$copywriting_script"; then
120
+ echo "normalize: skills/copywriting/scripts/extract-writing-styles.py (patch pending)"
121
+ changed=$((changed + 1))
122
+ fi
123
+ else
124
+ patch_result="$(python3 - "$copywriting_script" <<'PY'
125
+ import re
126
+ import sys
127
+ from pathlib import Path
128
+
129
+ path = Path(sys.argv[1])
130
+ text = path.read_text(encoding="utf-8")
131
+ original = text
132
+ needs_patch = "CODEX_HOME = Path(os.environ.get('CODEX_HOME'" not in text
133
+
134
+ if needs_patch:
135
+ new_func = """def find_project_root(start_dir: Path) -> Path:
136
+ \"\"\"Find project root by preferring a directory that contains assets/writing-styles.\"\"\"
137
+ search_chain = [start_dir] + list(start_dir.parents)
138
+ for parent in search_chain:
139
+ if (parent / 'assets' / 'writing-styles').exists():
140
+ return parent
141
+ for parent in search_chain:
142
+ if (parent / 'SKILL.md').exists():
143
+ return parent
144
+ for parent in search_chain:
145
+ if (parent / '.codex').exists() or (parent / '.claude').exists():
146
+ return parent
147
+ return start_dir
148
+ """
149
+ text, func_count = re.subn(
150
+ r"def find_project_root\(start_dir: Path\) -> Path:\n(?: .*\n)+? return start_dir\n",
151
+ new_func,
152
+ text,
153
+ count=1,
154
+ )
155
+
156
+ new_block = """PROJECT_ROOT = find_project_root(Path(__file__).parent)
157
+ STYLES_DIR = PROJECT_ROOT / 'assets' / 'writing-styles'
158
+ CODEX_HOME = Path(os.environ.get('CODEX_HOME', str(Path.home() / '.codex')))
159
+
160
+ _ai_multimodal_candidates = [
161
+ PROJECT_ROOT / '.claude' / 'skills' / 'ai-multimodal' / 'scripts',
162
+ CODEX_HOME / 'skills' / 'ai-multimodal' / 'scripts',
163
+ ]
164
+ AI_MULTIMODAL_SCRIPTS = next((p for p in _ai_multimodal_candidates if p.exists()), _ai_multimodal_candidates[-1])
165
+ """
166
+ text, block_count = re.subn(
167
+ r"PROJECT_ROOT = find_project_root\(Path\(__file__\)\.parent\)\nSTYLES_DIR = PROJECT_ROOT / 'assets' / 'writing-styles'\nAI_MULTIMODAL_SCRIPTS = PROJECT_ROOT / '.claude' / 'skills' / 'ai-multimodal' / 'scripts'\n",
168
+ new_block,
169
+ text,
170
+ count=1,
171
+ )
172
+ if func_count == 0 or block_count == 0:
173
+ print("error:copywriting_patch_pattern_miss")
174
+ sys.exit(2)
175
+
176
+ if text != original:
177
+ path.write_text(text, encoding="utf-8")
178
+ print("patched")
179
+ else:
180
+ print("unchanged")
181
+ PY
182
+ )"
183
+ if [[ "$patch_result" == "patched" ]]; then
184
+ echo "normalize: skills/copywriting/scripts/extract-writing-styles.py"
185
+ changed=$((changed + 1))
186
+ fi
187
+ fi
188
+ fi
189
+
190
+ default_style="${SKILLS_DIR}/copywriting/assets/writing-styles/default.md"
191
+ fallback_style="${SKILLS_DIR}/copywriting/references/writing-styles.md"
192
+ if [[ ! -f "$default_style" && -f "$fallback_style" ]]; then
193
+ if [[ "$DRY_RUN" == "true" ]]; then
194
+ echo "add: skills/copywriting/assets/writing-styles/default.md"
195
+ else
196
+ mkdir -p "$(dirname "$default_style")"
197
+ cp "$fallback_style" "$default_style"
198
+ echo "add: skills/copywriting/assets/writing-styles/default.md"
199
+ fi
200
+ changed=$((changed + 1))
201
+ fi
202
+
203
+ map_file="${CLAUDEKIT_DIR}/commands/codex-command-map.md"
204
+ if [[ ! -f "$map_file" ]]; then
205
+ if [[ "$DRY_RUN" == "true" ]]; then
206
+ echo "add: claudekit/commands/codex-command-map.md"
207
+ else
208
+ cat > "$map_file" <<'EOF'
209
+ # ClaudeKit -> Codex Command Map
210
+
211
+ ## Covered by existing skills
212
+
213
+ - `/preview` -> `markdown-novel-viewer`
214
+ - `/kanban` -> `plans-kanban`
215
+ - `/review/codebase` -> `code-review`
216
+ - `/test`, `/test/ui` -> `web-testing`
217
+ - `/worktree` -> `git`
218
+ - `/plan/*` -> `plan`
219
+
220
+ ## Converted into bridge workflows
221
+
222
+ - `/ck-help` -> `claudekit-command-bridge` (`resolve-command.py`)
223
+ - `/coding-level` -> `claudekit-command-bridge` (depth rubric + output styles)
224
+ - `/ask` -> `claudekit-command-bridge` (architecture mode)
225
+ - `/docs/init`, `/docs/update`, `/docs/summarize` -> `claudekit-command-bridge`
226
+ - `/journal`, `/watzup` -> `claudekit-command-bridge`
227
+
228
+ ## Explicitly excluded in this sync
229
+
230
+ - `/use-mcp` (excluded per user request: no MCP sync)
231
+ - Hooks (excluded per user request)
232
+
233
+ ## Custom Prompt Aliases (`/prompts:<name>`)
234
+
235
+ - `/ask` -> `/prompts:ask`
236
+ - `/ck-help` -> `/prompts:ck-help`
237
+ - `/coding-level` -> `/prompts:coding-level`
238
+ - `/docs/init` -> `/prompts:docs-init`
239
+ - `/docs/summarize` -> `/prompts:docs-summarize`
240
+ - `/docs/update` -> `/prompts:docs-update`
241
+ - `/journal` -> `/prompts:journal`
242
+ - `/kanban` -> `/prompts:kanban`
243
+ - `/plan/archive` -> `/prompts:plan-archive`
244
+ - `/plan/red-team` -> `/prompts:plan-red-team`
245
+ - `/plan/validate` -> `/prompts:plan-validate`
246
+ - `/preview` -> `/prompts:preview`
247
+ - `/review/codebase` -> `/prompts:review-codebase`
248
+ - `/review/codebase/parallel` -> `/prompts:review-codebase-parallel`
249
+ - `/test` -> `/prompts:test`
250
+ - `/test/ui` -> `/prompts:test-ui`
251
+ - `/watzup` -> `/prompts:watzup`
252
+ - `/worktree` -> `/prompts:worktree`
253
+ EOF
254
+ fi
255
+ changed=$((changed + 1))
256
+ fi
257
+
258
+ echo "changed=$changed"
259
+ if [[ "$DRY_RUN" == "true" ]]; then
260
+ echo "DRY_RUN=true (no files changed)"
261
+ fi
File without changes
@@ -0,0 +1,125 @@
1
+ """Asset and skill synchronization from live directory."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import shutil
6
+ from pathlib import Path
7
+ from typing import Dict
8
+
9
+ from .constants import ASSET_DIRS, ASSET_FILES, CONFLICT_SKILLS, EXCLUDED_SKILLS_ALWAYS, MCP_SKILLS
10
+ from .utils import is_excluded_path, write_bytes_if_changed
11
+
12
+
13
+ def sync_assets_from_dir(
14
+ source: Path,
15
+ *,
16
+ codex_home: Path,
17
+ include_hooks: bool,
18
+ dry_run: bool,
19
+ ) -> Dict[str, int]:
20
+ """Sync non-skill assets from live directory."""
21
+ claudekit_dir = codex_home / "claudekit"
22
+ claudekit_dir.mkdir(parents=True, exist_ok=True)
23
+ added = updated = 0
24
+
25
+ for dirname in ASSET_DIRS:
26
+ src_dir = source / dirname
27
+ if not src_dir.exists():
28
+ continue
29
+ dst_dir = claudekit_dir / dirname
30
+ if not dry_run:
31
+ dst_dir.mkdir(parents=True, exist_ok=True)
32
+
33
+ for src_file in sorted(src_dir.rglob("*")):
34
+ if not src_file.is_file() or is_excluded_path(src_file.parts):
35
+ continue
36
+ rel = src_file.relative_to(src_dir)
37
+ dst = dst_dir / rel
38
+ data = src_file.read_bytes()
39
+ mode = src_file.stat().st_mode & 0o777 if src_file.stat().st_mode & 0o111 else None
40
+ changed, is_added = write_bytes_if_changed(dst, data, mode=mode, dry_run=dry_run)
41
+ if changed:
42
+ if is_added:
43
+ added += 1
44
+ print(f"add: claudekit/{dirname}/{rel}")
45
+ else:
46
+ updated += 1
47
+ print(f"update: claudekit/{dirname}/{rel}")
48
+
49
+ for filename in ASSET_FILES:
50
+ src = source / filename
51
+ if not src.exists():
52
+ continue
53
+ dst = claudekit_dir / filename
54
+ data = src.read_bytes()
55
+ mode = src.stat().st_mode & 0o777 if src.stat().st_mode & 0o111 else None
56
+ changed, is_added = write_bytes_if_changed(dst, data, mode=mode, dry_run=dry_run)
57
+ if changed:
58
+ if is_added:
59
+ added += 1
60
+ print(f"add: claudekit/{filename}")
61
+ else:
62
+ updated += 1
63
+ print(f"update: claudekit/{filename}")
64
+
65
+ return {"added": added, "updated": updated, "removed": 0, "managed_files": added + updated}
66
+
67
+
68
+ def sync_skills_from_dir(
69
+ source: Path,
70
+ *,
71
+ codex_home: Path,
72
+ include_mcp: bool,
73
+ include_conflicts: bool,
74
+ dry_run: bool,
75
+ ) -> Dict[str, int]:
76
+ """Sync skills from live directory."""
77
+ skills_src = source / "skills"
78
+ skills_dst = codex_home / "skills"
79
+ added = updated = skipped = 0
80
+
81
+ if not skills_src.exists():
82
+ return {"added": 0, "updated": 0, "skipped": 0, "total_skills": 0}
83
+
84
+ for skill_dir in sorted(skills_src.iterdir()):
85
+ if not skill_dir.is_dir() or skill_dir.name.startswith("."):
86
+ continue
87
+ skill = skill_dir.name
88
+
89
+ if skill in EXCLUDED_SKILLS_ALWAYS:
90
+ skipped += 1
91
+ print(f"skip: {skill}")
92
+ continue
93
+ if not include_mcp and skill in MCP_SKILLS:
94
+ skipped += 1
95
+ print(f"skip: {skill}")
96
+ continue
97
+ if skill in CONFLICT_SKILLS:
98
+ skipped += 1
99
+ print(f"skip: {skill}")
100
+ continue
101
+ if not include_conflicts and (skills_dst / ".system" / skill).exists():
102
+ skipped += 1
103
+ print(f"skip: {skill}")
104
+ continue
105
+
106
+ dst = skills_dst / skill
107
+ exists = dst.exists()
108
+ if exists:
109
+ updated += 1
110
+ print(f"update: {skill}")
111
+ else:
112
+ added += 1
113
+ print(f"add: {skill}")
114
+
115
+ if dry_run:
116
+ continue
117
+
118
+ if exists:
119
+ shutil.rmtree(dst)
120
+ ignore = shutil.ignore_patterns("*.pyc", "__pycache__", ".venv", "node_modules", "dist", "build")
121
+ shutil.copytree(skill_dir, dst, ignore=ignore)
122
+
123
+ skills_dst.mkdir(parents=True, exist_ok=True)
124
+ total_skills = len(list(skills_dst.rglob("SKILL.md")))
125
+ return {"added": added, "updated": updated, "skipped": skipped, "total_skills": total_skills}