@wipcomputer/wip-ai-devops-toolbox 1.9.20

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 (146) hide show
  1. package/.license-guard.json +7 -0
  2. package/.publish-skill.json +4 -0
  3. package/CHANGELOG.md +1120 -0
  4. package/CLA.md +19 -0
  5. package/DEV-GUIDE-GENERAL-PUBLIC.md +882 -0
  6. package/LICENSE +52 -0
  7. package/README.md +238 -0
  8. package/SKILL.md +728 -0
  9. package/TECHNICAL.md +282 -0
  10. package/UNIVERSAL-INTERFACE.md +180 -0
  11. package/_trash/RELEASE-NOTES-v1-8-0.md +29 -0
  12. package/_trash/RELEASE-NOTES-v1-8-1.md +7 -0
  13. package/_trash/RELEASE-NOTES-v1-8-2.md +7 -0
  14. package/_trash/RELEASE-NOTES-v1-9-0.md +37 -0
  15. package/_trash/RELEASE-NOTES-v1-9-1.md +38 -0
  16. package/_trash/RELEASE-NOTES-v1-9-10.md +40 -0
  17. package/_trash/RELEASE-NOTES-v1-9-2.md +40 -0
  18. package/_trash/RELEASE-NOTES-v1-9-6.md +72 -0
  19. package/_trash/RELEASE-NOTES-v1-9-7.md +23 -0
  20. package/_trash/RELEASE-NOTES-v1-9-9.md +75 -0
  21. package/_trash/guide 2/DEV-GUIDE.md +487 -0
  22. package/_trash/guide 2/scripts/deploy-public.sh +152 -0
  23. package/package.json +27 -0
  24. package/scripts/SKILL-deploy-public.md +61 -0
  25. package/scripts/SKILL-post-merge-rename.md +47 -0
  26. package/scripts/deploy-public.sh +264 -0
  27. package/scripts/post-merge-rename.sh +205 -0
  28. package/scripts/publish-skill.sh +134 -0
  29. package/tools/deploy-public/LICENSE +52 -0
  30. package/tools/deploy-public/README.md +31 -0
  31. package/tools/deploy-public/SKILL.md +71 -0
  32. package/tools/deploy-public/deploy-public.sh +264 -0
  33. package/tools/deploy-public/package.json +9 -0
  34. package/tools/ldm-jobs/LICENSE +52 -0
  35. package/tools/ldm-jobs/README.md +46 -0
  36. package/tools/ldm-jobs/backup.sh +16 -0
  37. package/tools/ldm-jobs/branch-protect.sh +39 -0
  38. package/tools/ldm-jobs/crystal-capture.sh +19 -0
  39. package/tools/ldm-jobs/setup-shell.sh +27 -0
  40. package/tools/ldm-jobs/visibility-audit.sh +27 -0
  41. package/tools/post-merge-rename/LICENSE +52 -0
  42. package/tools/post-merge-rename/README.md +29 -0
  43. package/tools/post-merge-rename/SKILL.md +57 -0
  44. package/tools/post-merge-rename/package.json +9 -0
  45. package/tools/post-merge-rename/post-merge-rename.sh +122 -0
  46. package/tools/wip-branch-guard/INSTALL.md +41 -0
  47. package/tools/wip-branch-guard/guard.mjs +259 -0
  48. package/tools/wip-branch-guard/package.json +11 -0
  49. package/tools/wip-file-guard/CHANGELOG.md +6 -0
  50. package/tools/wip-file-guard/LICENSE +52 -0
  51. package/tools/wip-file-guard/README.md +113 -0
  52. package/tools/wip-file-guard/REFERENCE.md +86 -0
  53. package/tools/wip-file-guard/SKILL.md +105 -0
  54. package/tools/wip-file-guard/guard.mjs +128 -0
  55. package/tools/wip-file-guard/openclaw.plugin.json +8 -0
  56. package/tools/wip-file-guard/package.json +27 -0
  57. package/tools/wip-file-guard/test.sh +119 -0
  58. package/tools/wip-license-guard/LICENSE +52 -0
  59. package/tools/wip-license-guard/README.md +32 -0
  60. package/tools/wip-license-guard/SKILL.md +65 -0
  61. package/tools/wip-license-guard/cli.mjs +464 -0
  62. package/tools/wip-license-guard/core.mjs +310 -0
  63. package/tools/wip-license-guard/hook.mjs +146 -0
  64. package/tools/wip-license-guard/package.json +15 -0
  65. package/tools/wip-license-hook/CHANGELOG.md +17 -0
  66. package/tools/wip-license-hook/LICENSE +52 -0
  67. package/tools/wip-license-hook/README.md +200 -0
  68. package/tools/wip-license-hook/SKILL.md +111 -0
  69. package/tools/wip-license-hook/dist/cli/index.d.ts +15 -0
  70. package/tools/wip-license-hook/dist/cli/index.js +170 -0
  71. package/tools/wip-license-hook/dist/cli/index.js.map +1 -0
  72. package/tools/wip-license-hook/dist/core/detector.d.ts +12 -0
  73. package/tools/wip-license-hook/dist/core/detector.js +104 -0
  74. package/tools/wip-license-hook/dist/core/detector.js.map +1 -0
  75. package/tools/wip-license-hook/dist/core/index.d.ts +4 -0
  76. package/tools/wip-license-hook/dist/core/index.js +5 -0
  77. package/tools/wip-license-hook/dist/core/index.js.map +1 -0
  78. package/tools/wip-license-hook/dist/core/ledger.d.ts +49 -0
  79. package/tools/wip-license-hook/dist/core/ledger.js +72 -0
  80. package/tools/wip-license-hook/dist/core/ledger.js.map +1 -0
  81. package/tools/wip-license-hook/dist/core/reporter.d.ts +14 -0
  82. package/tools/wip-license-hook/dist/core/reporter.js +227 -0
  83. package/tools/wip-license-hook/dist/core/reporter.js.map +1 -0
  84. package/tools/wip-license-hook/dist/core/scanner.d.ts +39 -0
  85. package/tools/wip-license-hook/dist/core/scanner.js +325 -0
  86. package/tools/wip-license-hook/dist/core/scanner.js.map +1 -0
  87. package/tools/wip-license-hook/hooks/pre-pull.sh +55 -0
  88. package/tools/wip-license-hook/hooks/pre-push.sh +51 -0
  89. package/tools/wip-license-hook/mcp-server.mjs +119 -0
  90. package/tools/wip-license-hook/package-lock.json +54 -0
  91. package/tools/wip-license-hook/package.json +43 -0
  92. package/tools/wip-license-hook/src/cli/index.ts +189 -0
  93. package/tools/wip-license-hook/src/core/detector.ts +130 -0
  94. package/tools/wip-license-hook/src/core/index.ts +4 -0
  95. package/tools/wip-license-hook/src/core/ledger.ts +116 -0
  96. package/tools/wip-license-hook/src/core/reporter.ts +255 -0
  97. package/tools/wip-license-hook/src/core/scanner.ts +367 -0
  98. package/tools/wip-license-hook/tsconfig.json +16 -0
  99. package/tools/wip-readme-format/README.md +49 -0
  100. package/tools/wip-readme-format/SKILL.md +84 -0
  101. package/tools/wip-readme-format/format.mjs +570 -0
  102. package/tools/wip-readme-format/package.json +15 -0
  103. package/tools/wip-release/CHANGELOG.md +42 -0
  104. package/tools/wip-release/LICENSE +52 -0
  105. package/tools/wip-release/README.md +45 -0
  106. package/tools/wip-release/REFERENCE.md +100 -0
  107. package/tools/wip-release/SKILL.md +139 -0
  108. package/tools/wip-release/cli.js +161 -0
  109. package/tools/wip-release/core.mjs +1174 -0
  110. package/tools/wip-release/mcp-server.mjs +109 -0
  111. package/tools/wip-release/package.json +36 -0
  112. package/tools/wip-repo-init/README.md +38 -0
  113. package/tools/wip-repo-init/SKILL.md +77 -0
  114. package/tools/wip-repo-init/init.mjs +142 -0
  115. package/tools/wip-repo-init/package.json +11 -0
  116. package/tools/wip-repo-permissions-hook/LICENSE +52 -0
  117. package/tools/wip-repo-permissions-hook/README.md +86 -0
  118. package/tools/wip-repo-permissions-hook/SKILL.md +73 -0
  119. package/tools/wip-repo-permissions-hook/cli.js +83 -0
  120. package/tools/wip-repo-permissions-hook/core.mjs +122 -0
  121. package/tools/wip-repo-permissions-hook/guard.mjs +64 -0
  122. package/tools/wip-repo-permissions-hook/mcp-server.mjs +92 -0
  123. package/tools/wip-repo-permissions-hook/openclaw.plugin.json +8 -0
  124. package/tools/wip-repo-permissions-hook/package.json +31 -0
  125. package/tools/wip-repos/LICENSE +52 -0
  126. package/tools/wip-repos/README.md +77 -0
  127. package/tools/wip-repos/SKILL.md +80 -0
  128. package/tools/wip-repos/cli.mjs +176 -0
  129. package/tools/wip-repos/core.mjs +290 -0
  130. package/tools/wip-repos/mcp-server.mjs +157 -0
  131. package/tools/wip-repos/package.json +34 -0
  132. package/tools/wip-universal-installer/CHANGELOG.md +57 -0
  133. package/tools/wip-universal-installer/LICENSE +52 -0
  134. package/tools/wip-universal-installer/README.md +81 -0
  135. package/tools/wip-universal-installer/REFERENCE.md +122 -0
  136. package/tools/wip-universal-installer/SKILL.md +87 -0
  137. package/tools/wip-universal-installer/SPEC.md +180 -0
  138. package/tools/wip-universal-installer/detect.mjs +130 -0
  139. package/tools/wip-universal-installer/examples/minimal/README.md +20 -0
  140. package/tools/wip-universal-installer/examples/minimal/SKILL.md +28 -0
  141. package/tools/wip-universal-installer/examples/minimal/cli.mjs +4 -0
  142. package/tools/wip-universal-installer/examples/minimal/core.mjs +8 -0
  143. package/tools/wip-universal-installer/examples/minimal/mcp-server.mjs +27 -0
  144. package/tools/wip-universal-installer/examples/minimal/package.json +12 -0
  145. package/tools/wip-universal-installer/install.js +930 -0
  146. package/tools/wip-universal-installer/package.json +36 -0
@@ -0,0 +1,152 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # deploy-public.sh ... sync a private repo to its public counterpart
4
+ #
5
+ # Usage:
6
+ # bash deploy-public.sh <private-repo-path> <public-github-repo>
7
+ #
8
+ # Example:
9
+ # bash deploy-public.sh /path/to/memory-crystal wipcomputer/memory-crystal
10
+ #
11
+ # Convention:
12
+ # - Private repo: {name}-private (where all work happens)
13
+ # - Public repo: {name} (deployment target, never work here directly)
14
+ # - ai/ folder is excluded from public deploys
15
+ # - Old ai/ in public git history is fine, just not going forward
16
+ #
17
+ # Location: wip-dev-guide-private/scripts/deploy-public.sh (one script for all repos)
18
+
19
+ set -euo pipefail
20
+
21
+ PRIVATE_REPO="$1"
22
+ PUBLIC_REPO="$2"
23
+
24
+ if [[ -z "$PRIVATE_REPO" || -z "$PUBLIC_REPO" ]]; then
25
+ echo "Usage: bash deploy-public.sh <private-repo-path> <public-github-repo>"
26
+ echo "Example: bash deploy-public.sh /path/to/memory-crystal wipcomputer/memory-crystal"
27
+ exit 1
28
+ fi
29
+
30
+ if [[ ! -d "$PRIVATE_REPO/.git" ]]; then
31
+ echo "Error: $PRIVATE_REPO is not a git repository"
32
+ exit 1
33
+ fi
34
+
35
+ # Get the latest commit message from private repo
36
+ COMMIT_MSG=$(cd "$PRIVATE_REPO" && git log -1 --pretty=format:"%s")
37
+ COMMIT_HASH=$(cd "$PRIVATE_REPO" && git log -1 --pretty=format:"%h")
38
+
39
+ TMPDIR=$(mktemp -d)
40
+ trap 'rm -rf "$TMPDIR"' EXIT
41
+
42
+ echo "Cloning public repo $PUBLIC_REPO..."
43
+ gh repo clone "$PUBLIC_REPO" "$TMPDIR/public" -- --depth 1 2>/dev/null || {
44
+ echo "Public repo is empty or doesn't exist. Initializing..."
45
+ mkdir -p "$TMPDIR/public"
46
+ cd "$TMPDIR/public"
47
+ git init
48
+ git remote add origin "git@github.com:${PUBLIC_REPO}.git"
49
+ cd - > /dev/null
50
+ }
51
+
52
+ echo "Syncing files from private repo (excluding ai/, .git/)..."
53
+
54
+ # Remove all tracked files in public (except .git) so deleted files get removed
55
+ find "$TMPDIR/public" -mindepth 1 -maxdepth 1 ! -name .git -exec rm -rf {} +
56
+
57
+ # rsync from private, excluding ai/ and .git/
58
+ rsync -a \
59
+ --exclude='ai/' \
60
+ --exclude='.git/' \
61
+ --exclude='.DS_Store' \
62
+ --exclude='.wrangler/' \
63
+ --exclude='.claude/' \
64
+ --exclude='CLAUDE.md' \
65
+ "$PRIVATE_REPO/" "$TMPDIR/public/"
66
+
67
+ cd "$TMPDIR/public"
68
+
69
+ # Check if there are changes
70
+ if git diff --quiet HEAD -- 2>/dev/null && git diff --cached --quiet HEAD -- 2>/dev/null && [[ -z "$(git ls-files --others --exclude-standard)" ]]; then
71
+ echo "No changes to deploy."
72
+ exit 0
73
+ fi
74
+
75
+ # Harness ID for branch prefix. Set HARNESS_ID env var, or auto-detect from private repo path.
76
+ if [[ -z "${HARNESS_ID:-}" ]]; then
77
+ case "$PRIVATE_REPO" in
78
+ *"Claude Code - Mini"*) HARNESS_ID="cc-mini" ;;
79
+ *"Claude Code - MBA"*) HARNESS_ID="cc-air" ;;
80
+ *"Lēsa"*) HARNESS_ID="oc-lesa-mini" ;;
81
+ *) HARNESS_ID="deploy" ;;
82
+ esac
83
+ fi
84
+ BRANCH="$HARNESS_ID/deploy-$(date +%Y%m%d-%H%M%S)"
85
+
86
+ git checkout -b "$BRANCH"
87
+ git add -A
88
+ git commit -m "$COMMIT_MSG (from $COMMIT_HASH)"
89
+
90
+ echo "Pushing branch $BRANCH to $PUBLIC_REPO..."
91
+ git push -u origin "$BRANCH"
92
+
93
+ echo "Creating PR..."
94
+ PR_URL=$(gh pr create -R "$PUBLIC_REPO" \
95
+ --head "$BRANCH" \
96
+ --title "$COMMIT_MSG" \
97
+ --body "Synced from private repo (commit $COMMIT_HASH).")
98
+
99
+ echo "Merging PR..."
100
+ PR_NUMBER=$(echo "$PR_URL" | grep -o '[0-9]*$')
101
+ gh pr merge "$PR_NUMBER" -R "$PUBLIC_REPO" --merge --delete-branch
102
+
103
+ echo "Code synced via PR: $PR_URL"
104
+
105
+ # ── Sync release to public repo ──
106
+ # If the private repo has a version tag, create a matching release on the public repo.
107
+ # Pulls full release notes from the private repo and rewrites any private repo references.
108
+
109
+ # Try package.json first, fall back to latest git tag
110
+ VERSION=$(cd "$PRIVATE_REPO" && node -p "require('./package.json').version" 2>/dev/null || echo "")
111
+ if [[ -z "$VERSION" ]]; then
112
+ # No package.json. Use the latest version tag (v*) in the repo
113
+ TAG=$(cd "$PRIVATE_REPO" && git tag -l 'v*' --sort=-version:refname 2>/dev/null | head -1 || echo "")
114
+ if [[ -n "$TAG" ]]; then
115
+ VERSION="${TAG#v}"
116
+ fi
117
+ fi
118
+ if [[ -n "$VERSION" ]]; then
119
+ TAG="v$VERSION"
120
+ EXISTING=$(gh release view "$TAG" -R "$PUBLIC_REPO" --json tagName 2>/dev/null || echo "")
121
+ if [[ -z "$EXISTING" ]]; then
122
+ # Get the private repo's GitHub path (e.g., wipcomputer/memory-crystal-private)
123
+ PRIVATE_GH=$(cd "$PRIVATE_REPO" && git remote get-url origin | sed 's/.*github.com[:/]\(.*\)\.git/\1/')
124
+
125
+ # Pull full release notes from private repo
126
+ NOTES=$(gh release view "$TAG" -R "$PRIVATE_GH" --json body -q '.body' 2>/dev/null || echo "")
127
+
128
+ if [[ -z "$NOTES" || "$NOTES" == "null" ]]; then
129
+ NOTES="Release $TAG"
130
+ else
131
+ # Rewrite private repo references to public repo
132
+ NOTES=$(echo "$NOTES" | sed "s|$PRIVATE_GH|$PUBLIC_REPO|g")
133
+ fi
134
+
135
+ echo "Creating release $TAG on $PUBLIC_REPO..."
136
+ gh release create "$TAG" -R "$PUBLIC_REPO" --title "$TAG" --notes "$NOTES" 2>/dev/null && echo " ✓ Release $TAG created on $PUBLIC_REPO" || echo " ✗ Release creation failed (non-fatal)"
137
+ else
138
+ # Update existing release notes (in case they were incomplete)
139
+ PRIVATE_GH=$(cd "$PRIVATE_REPO" && git remote get-url origin | sed 's/.*github.com[:/]\(.*\)\.git/\1/')
140
+ NOTES=$(gh release view "$TAG" -R "$PRIVATE_GH" --json body -q '.body' 2>/dev/null || echo "")
141
+ if [[ -n "$NOTES" && "$NOTES" != "null" ]]; then
142
+ NOTES=$(echo "$NOTES" | sed "s|$PRIVATE_GH|$PUBLIC_REPO|g")
143
+ gh release edit "$TAG" -R "$PUBLIC_REPO" --notes "$NOTES" 2>/dev/null && echo " ✓ Release $TAG notes updated on $PUBLIC_REPO" || true
144
+ fi
145
+ echo " Release $TAG exists on $PUBLIC_REPO (notes synced)"
146
+ fi
147
+ fi
148
+
149
+ echo "Done. Public repo updated."
150
+ echo " PR: $PR_URL"
151
+ echo " Commit: $COMMIT_MSG (from $COMMIT_HASH)"
152
+ [[ -n "${VERSION:-}" ]] && echo " Release: v$VERSION"
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@wipcomputer/wip-ai-devops-toolbox",
3
+ "version": "1.9.20",
4
+ "type": "module",
5
+ "description": "The complete AI DevOps toolkit for AI-assisted development teams.",
6
+ "license": "MIT",
7
+ "author": "Parker Todd Brooks",
8
+ "bin": {
9
+ "wip-release": "tools/wip-release/cli.js",
10
+ "wip-repos": "tools/wip-repos/cli.mjs",
11
+ "wip-install": "tools/wip-universal-installer/install.js",
12
+ "wip-file-guard": "tools/wip-file-guard/guard.mjs",
13
+ "wip-license-guard": "tools/wip-license-guard/cli.mjs",
14
+ "wip-license-hook": "tools/wip-license-hook/dist/cli/index.js",
15
+ "wip-repo-permissions": "tools/wip-repo-permissions-hook/cli.js",
16
+ "wip-repo-init": "tools/wip-repo-init/init.mjs",
17
+ "wip-readme-format": "tools/wip-readme-format/format.mjs",
18
+ "wip-branch-guard": "tools/wip-branch-guard/guard.mjs",
19
+ "deploy-public": "tools/deploy-public/deploy-public.sh",
20
+ "post-merge-rename": "tools/post-merge-rename/post-merge-rename.sh"
21
+ },
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+https://github.com/wipcomputer/wip-ai-devops-toolbox.git"
25
+ },
26
+ "homepage": "https://github.com/wipcomputer/wip-ai-devops-toolbox"
27
+ }
@@ -0,0 +1,61 @@
1
+ ---
2
+ name: deploy-public
3
+ version: 1.3.0
4
+ description: Private-to-public repo sync. Copies everything except ai/ to the public mirror. Creates PR, merges, syncs releases.
5
+ author: Parker Todd Brooks
6
+ interface: [cli]
7
+ metadata:
8
+ category: dev-tools
9
+ capabilities:
10
+ - repo-sync
11
+ - release-sync
12
+ ---
13
+
14
+ # deploy-public
15
+
16
+ Private-to-public repo sync. One script for all repos. Copies code, creates a PR on the public repo, merges it, and syncs GitHub releases.
17
+
18
+ ## When to Use This Skill
19
+
20
+ **Use deploy-public for:**
21
+ - Publishing a private repo's code to its public counterpart
22
+ - After running `wip-release` on the private repo (release must exist first)
23
+ - Syncing release notes from private to public
24
+
25
+ **CRITICAL: Release order matters.**
26
+ 1. Merge PR to private repo's main
27
+ 2. Run `wip-release` (creates GitHub release with notes on private repo)
28
+ 3. THEN run `deploy-public.sh` (pulls notes from private release)
29
+
30
+ If you skip step 2, the public release gets empty notes.
31
+
32
+ ### Do NOT Use For
33
+
34
+ - Repos without a `-private` counterpart
35
+ - First-time repo setup (create the public repo on GitHub first)
36
+
37
+ ## API Reference
38
+
39
+ ### CLI
40
+
41
+ ```bash
42
+ bash scripts/deploy-public.sh /path/to/private-repo org/public-repo
43
+ ```
44
+
45
+ ### Examples
46
+
47
+ ```bash
48
+ # Deploy memory-crystal
49
+ bash scripts/deploy-public.sh /path/to/memory-crystal-private wipcomputer/memory-crystal
50
+
51
+ # Deploy wip-dev-tools
52
+ bash scripts/deploy-public.sh /path/to/wip-ai-devops-toolbox-private wipcomputer/wip-ai-devops-toolbox
53
+ ```
54
+
55
+ ## What It Does
56
+
57
+ 1. Clones the public repo to a temp directory
58
+ 2. Copies all files from private repo (excluding `ai/`, `.git/`)
59
+ 3. Creates a branch, commits, pushes, creates PR
60
+ 4. Merges the PR (regular merge, never squash)
61
+ 5. Syncs GitHub releases (pulls notes from private repo's releases)
@@ -0,0 +1,47 @@
1
+ ---
2
+ name: post-merge-rename
3
+ version: 1.3.0
4
+ description: Post-merge branch renaming. Appends --merged-YYYY-MM-DD to preserve history.
5
+ author: Parker Todd Brooks
6
+ interface: [cli]
7
+ metadata:
8
+ category: dev-tools
9
+ capabilities:
10
+ - branch-rename
11
+ - history-preservation
12
+ ---
13
+
14
+ # post-merge-rename
15
+
16
+ Scans for merged branches that haven't been renamed and appends `--merged-YYYY-MM-DD` to preserve history. We never delete branches. We rename them.
17
+
18
+ ## When to Use This Skill
19
+
20
+ **Use post-merge-rename for:**
21
+ - After merging PRs, to rename the source branch
22
+ - Cleaning up branches that were merged but not renamed
23
+ - Runs automatically as step 10 of `wip-release`
24
+
25
+ ### Do NOT Use For
26
+
27
+ - Unmerged branches
28
+ - Branches you're currently working on
29
+
30
+ ## API Reference
31
+
32
+ ### CLI
33
+
34
+ ```bash
35
+ bash scripts/post-merge-rename.sh # scan + rename all merged branches
36
+ bash scripts/post-merge-rename.sh --dry-run # preview only
37
+ bash scripts/post-merge-rename.sh <branch> # rename specific branch
38
+ ```
39
+
40
+ ## What It Does
41
+
42
+ 1. Lists all local branches merged into main
43
+ 2. Skips branches already renamed (containing `--merged-`)
44
+ 3. Finds the merge date from git history
45
+ 4. Renames: `feature-branch` -> `feature-branch--merged-2026-03-09`
46
+ 5. Pushes the renamed branch to origin
47
+ 6. Deletes the old branch name from origin
@@ -0,0 +1,264 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # deploy-public.sh ... sync a private repo to its public counterpart
4
+ #
5
+ # Usage:
6
+ # bash deploy-public.sh <private-repo-path> <public-github-repo>
7
+ #
8
+ # Example:
9
+ # bash deploy-public.sh /path/to/memory-crystal wipcomputer/memory-crystal
10
+ #
11
+ # Convention:
12
+ # - Private repo: {name}-private (where all work happens)
13
+ # - Public repo: {name} (deployment target, never work here directly)
14
+ # - ai/ folder is excluded from public deploys
15
+ # - Old ai/ in public git history is fine, just not going forward
16
+ #
17
+ # Location: wip-dev-guide-private/scripts/deploy-public.sh (one script for all repos)
18
+
19
+ set -euo pipefail
20
+
21
+ PRIVATE_REPO="$1"
22
+ PUBLIC_REPO="$2"
23
+
24
+ if [[ -z "$PRIVATE_REPO" || -z "$PUBLIC_REPO" ]]; then
25
+ echo "Usage: bash deploy-public.sh <private-repo-path> <public-github-repo>"
26
+ echo "Example: bash deploy-public.sh /path/to/memory-crystal wipcomputer/memory-crystal"
27
+ exit 1
28
+ fi
29
+
30
+ if [[ ! -d "$PRIVATE_REPO/.git" ]]; then
31
+ echo "Error: $PRIVATE_REPO is not a git repository"
32
+ exit 1
33
+ fi
34
+
35
+ # ── Safety: never deploy back to the source repo ──
36
+ # Extract the private repo's GitHub org/name from its remote URL
37
+ PRIVATE_REMOTE=$(cd "$PRIVATE_REPO" && git remote get-url origin 2>/dev/null | sed 's/.*github.com[:/]\(.*\)\.git/\1/' || echo "")
38
+
39
+ if [[ -n "$PRIVATE_REMOTE" && "$PRIVATE_REMOTE" == "$PUBLIC_REPO" ]]; then
40
+ echo "ERROR: PUBLIC_REPO ($PUBLIC_REPO) is the same as the private repo's origin ($PRIVATE_REMOTE)."
41
+ echo "This would deploy sanitized code (no ai/) back to the source repo and destroy files."
42
+ echo "Did you mean to target the public counterpart instead?"
43
+ exit 1
44
+ fi
45
+
46
+ if [[ "$PUBLIC_REPO" == *"-private"* ]]; then
47
+ echo "ERROR: PUBLIC_REPO ($PUBLIC_REPO) contains '-private'."
48
+ echo "deploy-public.sh should only target public repos. Private repos have ai/ folders that would be destroyed."
49
+ exit 1
50
+ fi
51
+
52
+ # Get the latest commit message from private repo
53
+ COMMIT_MSG=$(cd "$PRIVATE_REPO" && git log -1 --pretty=format:"%s")
54
+ COMMIT_HASH=$(cd "$PRIVATE_REPO" && git log -1 --pretty=format:"%h")
55
+
56
+ TMPDIR=$(mktemp -d)
57
+ trap 'rm -rf "$TMPDIR"' EXIT
58
+
59
+ # ── Auto-create public repo if it doesn't exist ──
60
+ REPO_EXISTS=$(gh repo view "$PUBLIC_REPO" --json name -q '.name' 2>/dev/null || echo "")
61
+ if [[ -z "$REPO_EXISTS" ]]; then
62
+ echo "Public repo $PUBLIC_REPO does not exist. Creating..."
63
+ DESCRIPTION=$(cd "$PRIVATE_REPO" && node -p "require('./package.json').description" 2>/dev/null || echo "")
64
+ gh repo create "$PUBLIC_REPO" --public --description "${DESCRIPTION:-Synced from private repo}" 2>/dev/null
65
+ echo " + Created $PUBLIC_REPO"
66
+ EMPTY_REPO=true
67
+ else
68
+ EMPTY_REPO=false
69
+ # Verify the resolved repo is actually the one we asked for (catch GitHub redirects)
70
+ RESOLVED_NAME=$(gh repo view "$PUBLIC_REPO" --json nameWithOwner -q '.nameWithOwner' 2>/dev/null || echo "")
71
+ if [[ -n "$RESOLVED_NAME" && "$RESOLVED_NAME" != "$PUBLIC_REPO" ]]; then
72
+ echo "ERROR: GitHub redirected $PUBLIC_REPO to $RESOLVED_NAME."
73
+ echo "The public repo doesn't actually exist. A repo with a similar name is redirecting."
74
+ echo "Create the public repo first: gh repo create $PUBLIC_REPO --public"
75
+ exit 1
76
+ fi
77
+ fi
78
+
79
+ echo "Cloning public repo $PUBLIC_REPO..."
80
+ gh repo clone "$PUBLIC_REPO" "$TMPDIR/public" -- --depth 1 2>/dev/null || {
81
+ echo "Public repo is empty. Initializing..."
82
+ mkdir -p "$TMPDIR/public"
83
+ cd "$TMPDIR/public"
84
+ git init
85
+ git remote add origin "git@github.com:${PUBLIC_REPO}.git"
86
+ cd - > /dev/null
87
+ EMPTY_REPO=true
88
+ }
89
+
90
+ echo "Syncing files from private repo (excluding ai/, .git/)..."
91
+
92
+ # Remove all tracked files in public (except .git) so deleted files get removed
93
+ find "$TMPDIR/public" -mindepth 1 -maxdepth 1 ! -name .git -exec rm -rf {} +
94
+
95
+ # rsync from private, excluding ai/ and .git/
96
+ rsync -a \
97
+ --exclude='ai/' \
98
+ --exclude='_trash/' \
99
+ --exclude='.git/' \
100
+ --exclude='.DS_Store' \
101
+ --exclude='.wrangler/' \
102
+ --exclude='.claude/' \
103
+ --exclude='CLAUDE.md' \
104
+ "$PRIVATE_REPO/" "$TMPDIR/public/"
105
+
106
+ cd "$TMPDIR/public"
107
+
108
+ # Check if there are changes
109
+ if git diff --quiet HEAD -- 2>/dev/null && git diff --cached --quiet HEAD -- 2>/dev/null && [[ -z "$(git ls-files --others --exclude-standard)" ]]; then
110
+ echo "No changes to deploy."
111
+ exit 0
112
+ fi
113
+
114
+ # Harness ID for branch prefix. Set HARNESS_ID env var, or auto-detect from private repo path.
115
+ if [[ -z "${HARNESS_ID:-}" ]]; then
116
+ case "$PRIVATE_REPO" in
117
+ *"Claude Code - Mini"*) HARNESS_ID="cc-mini" ;;
118
+ *"Claude Code - MBA"*) HARNESS_ID="cc-air" ;;
119
+ *"Lēsa"*) HARNESS_ID="oc-lesa-mini" ;;
120
+ *) HARNESS_ID="deploy" ;;
121
+ esac
122
+ fi
123
+ BRANCH="$HARNESS_ID/deploy-$(date +%Y%m%d-%H%M%S)"
124
+
125
+ git add -A
126
+ git commit -m "$COMMIT_MSG (from $COMMIT_HASH)"
127
+
128
+ if [[ "$EMPTY_REPO" == "true" ]]; then
129
+ # Empty repo: push directly to main (no base branch to PR against)
130
+ echo "Pushing initial commit to main on $PUBLIC_REPO..."
131
+ git branch -M main
132
+ git push -u origin main
133
+ gh repo edit "$PUBLIC_REPO" --default-branch main 2>/dev/null || true
134
+ PR_URL="(initial push, no PR)"
135
+ echo " + Initial commit pushed to main"
136
+ else
137
+ git checkout -b "$BRANCH"
138
+
139
+ echo "Pushing branch $BRANCH to $PUBLIC_REPO..."
140
+ git push -u origin "$BRANCH"
141
+
142
+ echo "Creating PR..."
143
+ PR_URL=$(gh pr create -R "$PUBLIC_REPO" \
144
+ --head "$BRANCH" \
145
+ --title "$COMMIT_MSG" \
146
+ --body "Synced from private repo (commit $COMMIT_HASH).")
147
+
148
+ echo "Merging PR..."
149
+ PR_NUMBER=$(echo "$PR_URL" | grep -o '[0-9]*$')
150
+ gh pr merge "$PR_NUMBER" -R "$PUBLIC_REPO" --merge --delete-branch
151
+
152
+ # Clean up any other non-main branches on public repo
153
+ echo "Checking for stale branches on public repo..."
154
+ STALE_BRANCHES=$(gh api "repos/$PUBLIC_REPO/branches" --paginate --jq '.[].name' 2>/dev/null | grep -v '^main$' || true)
155
+ if [[ -n "$STALE_BRANCHES" ]]; then
156
+ STALE_COUNT=$(echo "$STALE_BRANCHES" | wc -l | tr -d ' ')
157
+ echo " Found $STALE_COUNT stale branch(es). Deleting..."
158
+ echo "$STALE_BRANCHES" | while read -r stale; do
159
+ gh api -X DELETE "repos/$PUBLIC_REPO/git/refs/heads/$stale" 2>/dev/null && echo " ✓ Deleted $stale" || echo " ! Could not delete $stale"
160
+ done
161
+ else
162
+ echo " ✓ No stale branches"
163
+ fi
164
+
165
+ echo "Code synced via PR: $PR_URL"
166
+ fi
167
+
168
+ # ── Sync release to public repo ──
169
+ # If the private repo has a version tag, create a matching release on the public repo.
170
+ # Pulls full release notes from the private repo and rewrites any private repo references.
171
+
172
+ # Try package.json first, fall back to latest git tag
173
+ VERSION=$(cd "$PRIVATE_REPO" && node -p "require('./package.json').version" 2>/dev/null || echo "")
174
+ if [[ -z "$VERSION" ]]; then
175
+ # No package.json. Use the latest version tag (v*) in the repo
176
+ TAG=$(cd "$PRIVATE_REPO" && git tag -l 'v*' --sort=-version:refname 2>/dev/null | head -1 || echo "")
177
+ if [[ -n "$TAG" ]]; then
178
+ VERSION="${TAG#v}"
179
+ fi
180
+ fi
181
+ if [[ -n "$VERSION" ]]; then
182
+ TAG="v$VERSION"
183
+ EXISTING=$(gh release view "$TAG" -R "$PUBLIC_REPO" --json tagName 2>/dev/null || echo "")
184
+ if [[ -z "$EXISTING" ]]; then
185
+ # Get the private repo's GitHub path (e.g., wipcomputer/memory-crystal-private)
186
+ PRIVATE_GH=$(cd "$PRIVATE_REPO" && git remote get-url origin | sed 's/.*github.com[:/]\(.*\)\.git/\1/')
187
+
188
+ # Pull full release notes from private repo
189
+ NOTES=$(gh release view "$TAG" -R "$PRIVATE_GH" --json body -q '.body' 2>/dev/null || echo "")
190
+
191
+ if [[ -z "$NOTES" || "$NOTES" == "null" ]]; then
192
+ NOTES="Release $TAG"
193
+ else
194
+ # Rewrite private repo references to public repo
195
+ NOTES=$(echo "$NOTES" | sed "s|$PRIVATE_GH|$PUBLIC_REPO|g")
196
+ fi
197
+
198
+ echo "Creating release $TAG on $PUBLIC_REPO..."
199
+ gh release create "$TAG" -R "$PUBLIC_REPO" --title "$TAG" --notes "$NOTES" 2>/dev/null && echo " ✓ Release $TAG created on $PUBLIC_REPO" || echo " ✗ Release creation failed (non-fatal)"
200
+ else
201
+ # Update existing release notes (in case they were incomplete)
202
+ PRIVATE_GH=$(cd "$PRIVATE_REPO" && git remote get-url origin | sed 's/.*github.com[:/]\(.*\)\.git/\1/')
203
+ NOTES=$(gh release view "$TAG" -R "$PRIVATE_GH" --json body -q '.body' 2>/dev/null || echo "")
204
+ if [[ -n "$NOTES" && "$NOTES" != "null" ]]; then
205
+ NOTES=$(echo "$NOTES" | sed "s|$PRIVATE_GH|$PUBLIC_REPO|g")
206
+ gh release edit "$TAG" -R "$PUBLIC_REPO" --notes "$NOTES" 2>/dev/null && echo " ✓ Release $TAG notes updated on $PUBLIC_REPO" || true
207
+ fi
208
+ echo " Release $TAG exists on $PUBLIC_REPO (notes synced)"
209
+ fi
210
+ fi
211
+
212
+ # ── npm publish from public repo (#100) ──
213
+ # After syncing code and release, publish to npm from the public clone.
214
+ # Only if package.json exists and private !== true.
215
+
216
+ if [[ -n "${VERSION:-}" ]]; then
217
+ # Re-clone public for npm publish (the previous tmpdir might be gone)
218
+ NPM_TMPDIR=$(mktemp -d)
219
+ gh repo clone "$PUBLIC_REPO" "$NPM_TMPDIR/public" -- --depth 1 2>/dev/null
220
+
221
+ if [[ -f "$NPM_TMPDIR/public/package.json" ]]; then
222
+ IS_PRIVATE=$(cd "$NPM_TMPDIR/public" && node -p "require('./package.json').private || false" 2>/dev/null)
223
+ echo "Publishing to npm from public repo..."
224
+ NPM_TOKEN=$(OP_SERVICE_ACCOUNT_TOKEN=$(cat ~/.openclaw/secrets/op-sa-token) \
225
+ op item get "npm Token" --vault "Agent Secrets" --fields label=password --reveal 2>/dev/null || echo "")
226
+ if [[ -n "$NPM_TOKEN" ]]; then
227
+ cd "$NPM_TMPDIR/public"
228
+
229
+ # Publish root package (if not private)
230
+ if [[ "$IS_PRIVATE" != "true" ]]; then
231
+ echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc
232
+ npm publish --access public 2>/dev/null && echo " ✓ Published root package to npm" || echo " ✗ Root npm publish failed (non-fatal)"
233
+ rm -f .npmrc
234
+ else
235
+ echo " - Root package is private. Skipping root npm publish."
236
+ fi
237
+
238
+ # For toolbox repos: publish each sub-tool regardless of root private status
239
+ if [[ -d "tools" ]]; then
240
+ for TOOL_DIR in tools/*/; do
241
+ if [[ -f "${TOOL_DIR}package.json" ]]; then
242
+ TOOL_PRIVATE=$(node -p "require('./${TOOL_DIR}package.json').private || false" 2>/dev/null)
243
+ if [[ "$TOOL_PRIVATE" != "true" ]]; then
244
+ TOOL_NAME=$(node -p "require('./${TOOL_DIR}package.json').name" 2>/dev/null)
245
+ echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > "${TOOL_DIR}.npmrc"
246
+ (cd "$TOOL_DIR" && npm publish --access public 2>/dev/null) && echo " ✓ Published $TOOL_NAME to npm" || echo " ✗ npm publish failed for $TOOL_NAME (non-fatal)"
247
+ rm -f "${TOOL_DIR}.npmrc"
248
+ fi
249
+ fi
250
+ done
251
+ fi
252
+
253
+ cd - > /dev/null
254
+ else
255
+ echo " ! npm Token not found in 1Password. Skipping npm publish."
256
+ fi
257
+ fi
258
+ rm -rf "$NPM_TMPDIR"
259
+ fi
260
+
261
+ echo "Done. Public repo updated."
262
+ echo " PR: $PR_URL"
263
+ echo " Commit: $COMMIT_MSG (from $COMMIT_HASH)"
264
+ [[ -n "${VERSION:-}" ]] && echo " Release: v$VERSION"