youmd 0.7.1 → 0.8.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.
- package/dist/commands/machine.d.ts +4 -0
- package/dist/commands/machine.d.ts.map +1 -1
- package/dist/commands/machine.js +86 -15
- package/dist/commands/machine.js.map +1 -1
- package/dist/commands/project-log.d.ts +13 -0
- package/dist/commands/project-log.d.ts.map +1 -0
- package/dist/commands/project-log.js +241 -0
- package/dist/commands/project-log.js.map +1 -0
- package/dist/commands/project.d.ts.map +1 -1
- package/dist/commands/project.js +12 -0
- package/dist/commands/project.js.map +1 -1
- package/dist/commands/stack.d.ts.map +1 -1
- package/dist/commands/stack.js +23 -0
- package/dist/commands/stack.js.map +1 -1
- package/dist/index.js +9 -10
- package/dist/index.js.map +1 -1
- package/dist/lib/compiler.d.ts.map +1 -1
- package/dist/lib/compiler.js +10 -0
- package/dist/lib/compiler.js.map +1 -1
- package/package.json +1 -1
- package/scripts/env-vault/README.md +27 -10
- package/scripts/env-vault/backup.sh +46 -3
- package/scripts/env-vault/restore.sh +73 -7
- package/scripts/skillstack-sync/bootstrap-new-mac.sh +19 -5
- package/scripts/skillstack-sync/capture-agent-config.sh +109 -0
- package/scripts/skillstack-sync/context-sync.sh +344 -0
- package/scripts/skillstack-sync/restore-agent-config.sh +152 -0
- package/scripts/skillstack-sync/sync.sh +20 -6
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# env-vault/backup.sh — backs up all .env.local files under CODE_2025
|
|
3
|
-
#
|
|
4
|
-
# Only variable NAMES
|
|
2
|
+
# env-vault/backup.sh — backs up all .env.local files under CODE_2025 AND a
|
|
3
|
+
# fixed set of agent-auth secret files into an encrypted vault. NEVER prints,
|
|
4
|
+
# echoes, logs, or writes any secret VALUE. Only variable NAMES, counts, and
|
|
5
|
+
# file paths appear in plaintext output.
|
|
5
6
|
#
|
|
6
7
|
# Usage: bash backup.sh [--root <search-root>] [--out <output-dir>]
|
|
7
8
|
# Defaults:
|
|
@@ -14,6 +15,16 @@ set -euo pipefail
|
|
|
14
15
|
SEARCH_ROOT="${ENV_VAULT_ROOT:-/Users/houstongolden/Desktop/CODE_2025}"
|
|
15
16
|
OUTPUT_DIR="${ENV_VAULT_OUT:-/Users/houstongolden/Desktop/CODE_2025/youmd/.env-vault}"
|
|
16
17
|
|
|
18
|
+
# ── agent-auth secret files ────────────────────────────────────────────────────
|
|
19
|
+
# Absolute paths to hard agent-auth secrets that must travel with the vault.
|
|
20
|
+
# Each entry is archived as agent-auth/<stable-name> inside the tar.
|
|
21
|
+
# To add more: append to this array. Format: "absolute-path:stable-archive-name"
|
|
22
|
+
AGENT_SECRET_FILES=(
|
|
23
|
+
"${HOME}/.codex/auth.json:codex-auth.json"
|
|
24
|
+
"${HOME}/.cursor/mcp.json:cursor-mcp.json"
|
|
25
|
+
"${HOME}/.claude.json:claude.json"
|
|
26
|
+
)
|
|
27
|
+
|
|
17
28
|
# ── parse flags ────────────────────────────────────────────────────────────────
|
|
18
29
|
while [[ $# -gt 0 ]]; do
|
|
19
30
|
case $1 in
|
|
@@ -94,6 +105,38 @@ for ENV_FILE in "${ENV_FILES[@]}"; do
|
|
|
94
105
|
>> "${MANIFEST_PATH}"
|
|
95
106
|
done
|
|
96
107
|
|
|
108
|
+
# ── stage agent-auth secret files ─────────────────────────────────────────────
|
|
109
|
+
# Each entry in AGENT_SECRET_FILES has format "absolute-path:stable-archive-name".
|
|
110
|
+
# Files are stored under agent-auth/<stable-name> inside the archive.
|
|
111
|
+
# Only the path and byte-size appear in the manifest — never the contents.
|
|
112
|
+
AGENT_AUTH_STAGING="${STAGING_DIR}/agent-auth"
|
|
113
|
+
mkdir -p "${AGENT_AUTH_STAGING}"
|
|
114
|
+
AGENT_AUTH_COUNT=0
|
|
115
|
+
|
|
116
|
+
echo "" >> "${MANIFEST_PATH}"
|
|
117
|
+
echo "agent-auth files:" >> "${MANIFEST_PATH}"
|
|
118
|
+
|
|
119
|
+
for ENTRY in "${AGENT_SECRET_FILES[@]}"; do
|
|
120
|
+
SRC_PATH="${ENTRY%%:*}"
|
|
121
|
+
ARCHIVE_NAME="${ENTRY##*:}"
|
|
122
|
+
|
|
123
|
+
if [[ -f "${SRC_PATH}" ]]; then
|
|
124
|
+
cp "${SRC_PATH}" "${AGENT_AUTH_STAGING}/${ARCHIVE_NAME}"
|
|
125
|
+
FILE_BYTES=$(wc -c < "${SRC_PATH}" | tr -d ' ')
|
|
126
|
+
echo " agent-auth/${ARCHIVE_NAME} (${SRC_PATH}): ${FILE_BYTES} bytes" \
|
|
127
|
+
>> "${MANIFEST_PATH}"
|
|
128
|
+
echo " Staged agent-auth: ${SRC_PATH} → agent-auth/${ARCHIVE_NAME}"
|
|
129
|
+
(( AGENT_AUTH_COUNT++ )) || true
|
|
130
|
+
else
|
|
131
|
+
echo " agent-auth/${ARCHIVE_NAME} (${SRC_PATH}): NOT FOUND — skipped" \
|
|
132
|
+
>> "${MANIFEST_PATH}"
|
|
133
|
+
echo " Skipped (not found): ${SRC_PATH}"
|
|
134
|
+
fi
|
|
135
|
+
done
|
|
136
|
+
|
|
137
|
+
echo ""
|
|
138
|
+
echo "Staged ${AGENT_AUTH_COUNT} agent-auth file(s)."
|
|
139
|
+
|
|
97
140
|
echo "" >> "${MANIFEST_PATH}"
|
|
98
141
|
echo "SECURITY: The encrypted archive is safe to transport. Keep the passphrase private." \
|
|
99
142
|
>> "${MANIFEST_PATH}"
|
|
@@ -1,15 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# env-vault/restore.sh — restores .env.local files
|
|
3
|
-
# created by backup.sh. NEVER prints, echoes, or logs any
|
|
4
|
-
# Only prints which file paths were restored or skipped.
|
|
2
|
+
# env-vault/restore.sh — restores .env.local files AND agent-auth secrets from
|
|
3
|
+
# an encrypted vault created by backup.sh. NEVER prints, echoes, or logs any
|
|
4
|
+
# secret VALUES. Only prints which file paths were restored or skipped.
|
|
5
5
|
#
|
|
6
6
|
# Usage: bash restore.sh [--force] [--root <target-root>] <vault-file>
|
|
7
7
|
# vault-file: path to env-vault-*.tar.age / .tar.gpg / .tar.enc
|
|
8
|
-
# --force: overwrite existing
|
|
9
|
-
# --root: destination root
|
|
8
|
+
# --force: overwrite existing files without backing them up
|
|
9
|
+
# --root: destination root for .env.local files
|
|
10
|
+
# (default: /Users/houstongolden/Desktop/CODE_2025)
|
|
11
|
+
#
|
|
12
|
+
# agent-auth restore map (archive name → absolute home path):
|
|
13
|
+
# agent-auth/codex-auth.json → ~/.codex/auth.json
|
|
14
|
+
# agent-auth/cursor-mcp.json → ~/.cursor/mcp.json
|
|
15
|
+
# agent-auth/claude.json → ~/.claude.json
|
|
10
16
|
|
|
11
17
|
set -euo pipefail
|
|
12
18
|
|
|
19
|
+
# ── agent-auth restore map ─────────────────────────────────────────────────────
|
|
20
|
+
# Maps archive name (inside agent-auth/) to its absolute home destination.
|
|
21
|
+
# Format: "archive-name:absolute-destination-path"
|
|
22
|
+
# Must stay in sync with AGENT_SECRET_FILES in backup.sh.
|
|
23
|
+
AGENT_AUTH_MAP=(
|
|
24
|
+
"codex-auth.json:${HOME}/.codex/auth.json"
|
|
25
|
+
"cursor-mcp.json:${HOME}/.cursor/mcp.json"
|
|
26
|
+
"claude.json:${HOME}/.claude.json"
|
|
27
|
+
)
|
|
28
|
+
|
|
13
29
|
# ── defaults ──────────────────────────────────────────────────────────────────
|
|
14
30
|
TARGET_ROOT="${ENV_VAULT_RESTORE_ROOT:-/Users/houstongolden/Desktop/CODE_2025}"
|
|
15
31
|
FORCE=false
|
|
@@ -139,11 +155,61 @@ while IFS= read -r -d '' ENV_FILE; do
|
|
|
139
155
|
fi
|
|
140
156
|
done < <(find "${EXTRACT_DIR}" -name ".env.local" -print0)
|
|
141
157
|
|
|
158
|
+
# ── restore agent-auth files ───────────────────────────────────────────────────
|
|
159
|
+
AGENT_RESTORED=0
|
|
160
|
+
AGENT_SKIPPED=0
|
|
161
|
+
AGENT_BACKED_UP=0
|
|
162
|
+
|
|
163
|
+
AGENT_AUTH_SRC="${EXTRACT_DIR}/agent-auth"
|
|
164
|
+
|
|
165
|
+
if [[ -d "${AGENT_AUTH_SRC}" ]]; then
|
|
166
|
+
echo "Restoring agent-auth files…"
|
|
167
|
+
for ENTRY in "${AGENT_AUTH_MAP[@]}"; do
|
|
168
|
+
ARCHIVE_NAME="${ENTRY%%:*}"
|
|
169
|
+
DEST_PATH="${ENTRY##*:}"
|
|
170
|
+
SRC_FILE="${AGENT_AUTH_SRC}/${ARCHIVE_NAME}"
|
|
171
|
+
|
|
172
|
+
if [[ ! -f "${SRC_FILE}" ]]; then
|
|
173
|
+
echo " SKIPPED (not in vault): agent-auth/${ARCHIVE_NAME}"
|
|
174
|
+
(( AGENT_SKIPPED++ )) || true
|
|
175
|
+
continue
|
|
176
|
+
fi
|
|
177
|
+
|
|
178
|
+
DEST_DIR="$(dirname "${DEST_PATH}")"
|
|
179
|
+
mkdir -p "${DEST_DIR}"
|
|
180
|
+
|
|
181
|
+
if [[ -f "${DEST_PATH}" ]]; then
|
|
182
|
+
if [[ "${FORCE}" == "true" ]]; then
|
|
183
|
+
cp "${SRC_FILE}" "${DEST_PATH}"
|
|
184
|
+
echo " RESTORED (overwrite): ${DEST_PATH}"
|
|
185
|
+
(( AGENT_RESTORED++ )) || true
|
|
186
|
+
else
|
|
187
|
+
TS="$(date -u +%Y%m%dT%H%M%SZ)"
|
|
188
|
+
BAK="${DEST_PATH}.bak.${TS}"
|
|
189
|
+
cp "${DEST_PATH}" "${BAK}"
|
|
190
|
+
cp "${SRC_FILE}" "${DEST_PATH}"
|
|
191
|
+
echo " RESTORED (backed up old → $(basename "${BAK}")): ${DEST_PATH}"
|
|
192
|
+
(( AGENT_RESTORED++ )) || true
|
|
193
|
+
(( AGENT_BACKED_UP++ )) || true
|
|
194
|
+
fi
|
|
195
|
+
else
|
|
196
|
+
cp "${SRC_FILE}" "${DEST_PATH}"
|
|
197
|
+
echo " RESTORED (new): ${DEST_PATH}"
|
|
198
|
+
(( AGENT_RESTORED++ )) || true
|
|
199
|
+
fi
|
|
200
|
+
done
|
|
201
|
+
echo ""
|
|
202
|
+
else
|
|
203
|
+
echo "No agent-auth/ directory in vault — skipping agent-auth restore."
|
|
204
|
+
echo ""
|
|
205
|
+
fi
|
|
206
|
+
|
|
142
207
|
echo ""
|
|
143
208
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
144
209
|
echo " Restore summary"
|
|
145
210
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
146
|
-
echo " Restored: ${RESTORED} | Skipped: ${SKIPPED} | Old files backed up: ${BACKED_UP}"
|
|
211
|
+
echo " .env.local — Restored: ${RESTORED} | Skipped: ${SKIPPED} | Old files backed up: ${BACKED_UP}"
|
|
212
|
+
echo " agent-auth — Restored: ${AGENT_RESTORED} | Skipped: ${AGENT_SKIPPED} | Old files backed up: ${AGENT_BACKED_UP}"
|
|
147
213
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
148
214
|
echo ""
|
|
149
|
-
echo "Secrets are now in place. Do not print or share
|
|
215
|
+
echo "Secrets are now in place. Do not print or share secret files."
|
|
@@ -101,14 +101,29 @@ for skill in ${LOOSE_SKILLS}; do
|
|
|
101
101
|
done
|
|
102
102
|
|
|
103
103
|
# ---------------------------------------------------------------------------
|
|
104
|
-
step "5.
|
|
104
|
+
step "5. Restore agent config from agent-shared"
|
|
105
|
+
# ---------------------------------------------------------------------------
|
|
106
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
107
|
+
RESTORE_SCRIPT="${SCRIPT_DIR}/restore-agent-config.sh"
|
|
108
|
+
|
|
109
|
+
if [ -f "${RESTORE_SCRIPT}" ]; then
|
|
110
|
+
info "Running restore-agent-config.sh (backs up existing files, safe to re-run)..."
|
|
111
|
+
bash "${RESTORE_SCRIPT}"
|
|
112
|
+
info "Agent config restored."
|
|
113
|
+
else
|
|
114
|
+
warn "restore-agent-config.sh not found at ${RESTORE_SCRIPT} — skipping agent config restore."
|
|
115
|
+
warn "You can run it manually later once youmd is fully installed:"
|
|
116
|
+
warn " bash ${RESTORE_SCRIPT}"
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
# ---------------------------------------------------------------------------
|
|
120
|
+
step "6. Reminder: restore secrets"
|
|
105
121
|
# ---------------------------------------------------------------------------
|
|
106
122
|
echo ""
|
|
107
123
|
echo " !! IMPORTANT: Secrets are NOT in git. Restore them separately:"
|
|
108
124
|
echo ""
|
|
109
|
-
echo " If you use the env-vault
|
|
125
|
+
echo " If you use the env-vault:"
|
|
110
126
|
echo " youmd env restore <vault-file> # CLI wrapper (preferred)"
|
|
111
|
-
echo " bash ~/Desktop/CODE_2025/youmd/cli/scripts/env-vault/restore.sh <vault-file>"
|
|
112
127
|
echo ""
|
|
113
128
|
echo " Secrets to restore (set in your shell profile or .env files):"
|
|
114
129
|
echo " OPENROUTER_API_KEY"
|
|
@@ -119,7 +134,7 @@ echo " ANTHROPIC_API_KEY"
|
|
|
119
134
|
echo ""
|
|
120
135
|
|
|
121
136
|
# ---------------------------------------------------------------------------
|
|
122
|
-
step "
|
|
137
|
+
step "7. Next steps"
|
|
123
138
|
# ---------------------------------------------------------------------------
|
|
124
139
|
echo ""
|
|
125
140
|
echo " Bootstrap complete. To activate the auto-sync daemons, run:"
|
|
@@ -127,7 +142,6 @@ echo ""
|
|
|
127
142
|
echo " youmd stack daemon install # preferred (CLI-native)"
|
|
128
143
|
echo ""
|
|
129
144
|
echo " Or run the raw script directly:"
|
|
130
|
-
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
131
145
|
echo " bash ${SCRIPT_DIR}/install-daemons.sh"
|
|
132
146
|
echo ""
|
|
133
147
|
echo " To do a manual sync now:"
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# capture-agent-config.sh — snapshot NON-SECRET agent config into ~/.agent-shared/
|
|
3
|
+
#
|
|
4
|
+
# This captures the agent-behavior config that isn't already in a synced repo —
|
|
5
|
+
# Claude/Codex/Warp settings, slash commands, plugin list, the orphaned
|
|
6
|
+
# ~/.agents skills, and the launchd/cron automations — into the agent-shared
|
|
7
|
+
# repo so they travel across machines. The skillstack-sync daemon then commits +
|
|
8
|
+
# pushes agent-shared, and `youmd machine setup`/restore applies them on a new Mac.
|
|
9
|
+
#
|
|
10
|
+
# SECRETS NEVER GO HERE. agent-shared is a git repo. Live secrets (codex
|
|
11
|
+
# auth.json, cursor mcp.json, ~/.claude.json BAMF token, .env.local) travel via
|
|
12
|
+
# the ENCRYPTED env-vault instead. This script captures only files confirmed to
|
|
13
|
+
# be secret-free, and a final guard greps the staged output for secret patterns
|
|
14
|
+
# and ABORTS if any are found (nothing is left in agent-shared on abort).
|
|
15
|
+
#
|
|
16
|
+
# bash 3.2 / BSD safe. Idempotent. --dry-run to preview.
|
|
17
|
+
|
|
18
|
+
set -euo pipefail
|
|
19
|
+
|
|
20
|
+
AGENT_SHARED="${HOME}/.agent-shared"
|
|
21
|
+
DEST="${AGENT_SHARED}/agent-config"
|
|
22
|
+
LOG_DIR="${HOME}/.youmd/logs"
|
|
23
|
+
LOG_FILE="${LOG_DIR}/capture-agent-config.log"
|
|
24
|
+
|
|
25
|
+
DRY_RUN=0
|
|
26
|
+
for arg in "$@"; do
|
|
27
|
+
case "$arg" in
|
|
28
|
+
--dry-run) DRY_RUN=1 ;;
|
|
29
|
+
*) echo "Unknown flag: $arg" >&2; exit 1 ;;
|
|
30
|
+
esac
|
|
31
|
+
done
|
|
32
|
+
|
|
33
|
+
mkdir -p "${LOG_DIR}"
|
|
34
|
+
log() { local ts; ts="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"; echo "[${ts}] $*"; echo "[${ts}] $*" >> "${LOG_FILE}"; }
|
|
35
|
+
is_dry() { [ "${DRY_RUN}" -eq 1 ]; }
|
|
36
|
+
|
|
37
|
+
if [ ! -d "${AGENT_SHARED}/.git" ]; then
|
|
38
|
+
log "ERROR: ${AGENT_SHARED} is not a git repo — run machine setup first."
|
|
39
|
+
exit 1
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
# copy_in <src> <dest-subpath> — copy a file/dir into the staging dest if it exists
|
|
43
|
+
copy_in() {
|
|
44
|
+
local src="$1" rel="$2" target
|
|
45
|
+
target="${DEST}/${rel}"
|
|
46
|
+
if [ ! -e "${src}" ]; then return 0; fi
|
|
47
|
+
if is_dry; then log "DRY would capture ${src} → agent-config/${rel}"; return 0; fi
|
|
48
|
+
mkdir -p "$(dirname "${target}")"
|
|
49
|
+
if [ -d "${src}" ]; then
|
|
50
|
+
rsync -a --delete "${src}/" "${target}/"
|
|
51
|
+
else
|
|
52
|
+
cp "${src}" "${target}"
|
|
53
|
+
fi
|
|
54
|
+
log "captured ${src} → agent-config/${rel}"
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
log "=== capture-agent-config START ($([ "${DRY_RUN}" -eq 1 ] && echo DRY || echo LIVE)) ==="
|
|
58
|
+
|
|
59
|
+
# ── Claude config (settings have no raw secrets; ~/.claude.json is EXCLUDED — vaulted) ──
|
|
60
|
+
copy_in "${HOME}/.claude/settings.json" "claude/settings.json"
|
|
61
|
+
copy_in "${HOME}/.claude/settings.local.json" "claude/settings.local.json"
|
|
62
|
+
copy_in "${HOME}/.claude/commands" "claude/commands"
|
|
63
|
+
copy_in "${HOME}/.claude/mcp.json" "claude/mcp.json"
|
|
64
|
+
copy_in "${HOME}/.claude/plugins/installed_plugins.json" "claude/plugins/installed_plugins.json"
|
|
65
|
+
|
|
66
|
+
# ── Codex config (config.toml only; auth.json is EXCLUDED — vaulted) ──
|
|
67
|
+
copy_in "${HOME}/.codex/config.toml" "codex/config.toml"
|
|
68
|
+
|
|
69
|
+
# ── Warp (no secrets) ──
|
|
70
|
+
copy_in "${HOME}/.warp/settings.toml" "warp/settings.toml"
|
|
71
|
+
copy_in "${HOME}/.warp/keybindings.yaml" "warp/keybindings.yaml"
|
|
72
|
+
|
|
73
|
+
# ── Orphaned ~/.agents skills (no repo of their own today) ──
|
|
74
|
+
copy_in "${HOME}/.agents/skills/find-skills" "agents-skills/find-skills"
|
|
75
|
+
copy_in "${HOME}/.agents/skills/ui-ux-pro-max" "agents-skills/ui-ux-pro-max"
|
|
76
|
+
|
|
77
|
+
# ── Automations: launchd plists (HOME-templated) + crontab snapshot ──
|
|
78
|
+
capture_plist() {
|
|
79
|
+
local label="$1" src="${HOME}/Library/LaunchAgents/$1.plist" target="${DEST}/automations/launchd/$1.plist"
|
|
80
|
+
if [ ! -f "${src}" ]; then return 0; fi
|
|
81
|
+
if is_dry; then log "DRY would capture plist ${label} (HOME-templated)"; return 0; fi
|
|
82
|
+
mkdir -p "$(dirname "${target}")"
|
|
83
|
+
sed "s|${HOME}|__HOME__|g" "${src}" > "${target}"
|
|
84
|
+
log "captured plist ${label} → agent-config/automations/launchd/${label}.plist"
|
|
85
|
+
}
|
|
86
|
+
capture_plist "com.youmd.skillstack-sync"
|
|
87
|
+
capture_plist "com.youmd.identity-sync"
|
|
88
|
+
capture_plist "com.houstongolden.agent-runtime-guard"
|
|
89
|
+
|
|
90
|
+
if ! is_dry; then
|
|
91
|
+
mkdir -p "${DEST}/automations"
|
|
92
|
+
crontab -l 2>/dev/null > "${DEST}/automations/crontab.txt" || true
|
|
93
|
+
log "captured crontab → agent-config/automations/crontab.txt"
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
# ── SECRET-LEAK GUARD — abort if anything captured looks like a live secret ──
|
|
97
|
+
if ! is_dry && [ -d "${DEST}" ]; then
|
|
98
|
+
# Match real secret token shapes; allow bare key NAMES (KEY=) which are fine.
|
|
99
|
+
HITS="$(grep -rIlE 'sk-ant-[A-Za-z0-9_-]{20}|sk-[A-Za-z0-9]{32}|ghp_[A-Za-z0-9]{30}|gho_[A-Za-z0-9]{30}|Bearer [A-Za-z0-9._-]{20}|AKIA[0-9A-Z]{16}|xox[baprs]-[A-Za-z0-9-]{10}' "${DEST}" 2>/dev/null || true)"
|
|
100
|
+
if [ -n "${HITS}" ]; then
|
|
101
|
+
log "ABORT: possible secret(s) found in captured config — removing capture, nothing committed:"
|
|
102
|
+
log "${HITS}"
|
|
103
|
+
rm -rf "${DEST}"
|
|
104
|
+
exit 1
|
|
105
|
+
fi
|
|
106
|
+
log "secret-leak guard: clean (no live secret patterns in captured config)"
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
log "=== capture-agent-config DONE ==="
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# context-sync.sh — per-project agent context syncer (cross-machine, code-safe)
|
|
3
|
+
# Compatible: bash 3.2+, macOS BSD coreutils (NO mapfile, NO GNU mktemp flags)
|
|
4
|
+
#
|
|
5
|
+
# WHAT IT DOES
|
|
6
|
+
# ============
|
|
7
|
+
# For each of Houston's own git repos, commits ONLY the agent-context paths
|
|
8
|
+
# (AGENTS.md, CLAUDE.md, project-context/, .claude/) and pull+pushes, so
|
|
9
|
+
# agent context travels across machines without ever touching application code.
|
|
10
|
+
#
|
|
11
|
+
# SAFETY GUARANTEES
|
|
12
|
+
# =================
|
|
13
|
+
# 1. Only stages named context paths — NEVER uses `git add -A` or `git add .`
|
|
14
|
+
# 2. Never stashes, resets, checks out, or cleans anything in the working tree
|
|
15
|
+
# 3. Never touches a repo that is mid-merge or mid-rebase
|
|
16
|
+
# 4. Never touches a repo with no `origin` remote
|
|
17
|
+
# 5. If the working tree has uncommitted non-context changes, commits context
|
|
18
|
+
# locally but SKIPS pull/push to avoid merge conflicts touching his code;
|
|
19
|
+
# logs a clear "push manually" warning
|
|
20
|
+
# 6. On pull conflict: aborts merge, skips push, logs loudly — never force
|
|
21
|
+
# 7. Per-repo failures are non-fatal; the loop always continues
|
|
22
|
+
# 8. --dry-run logs intended actions; writes nothing to disk
|
|
23
|
+
#
|
|
24
|
+
# WHAT IS EXCLUDED (by design)
|
|
25
|
+
# =============================
|
|
26
|
+
# - Third-party / fork repos (disler/*, coffeefuelbump/*, etc.) — Houston
|
|
27
|
+
# doesn't own the remote, so auto-pushing context there is wrong
|
|
28
|
+
# - Repos with no origin remote or push-disabled remotes — detected at runtime
|
|
29
|
+
# - gstack, agent-shared, scistack — covered by sync.sh (skillstack layer)
|
|
30
|
+
#
|
|
31
|
+
# Usage:
|
|
32
|
+
# ./context-sync.sh # sync all context repos
|
|
33
|
+
# ./context-sync.sh --dry-run # log intended actions only, write nothing
|
|
34
|
+
#
|
|
35
|
+
# Env overrides:
|
|
36
|
+
# CONTEXT_REPOS space-separated list of repo DIRECTORY NAMES under
|
|
37
|
+
# ~/Desktop/CODE_2025/ (not full paths)
|
|
38
|
+
# GIT_USER_NAME commit author name (default: Houston Golden)
|
|
39
|
+
# GIT_USER_EMAIL commit author email (default: houston@bamf.ai)
|
|
40
|
+
|
|
41
|
+
set -euo pipefail
|
|
42
|
+
|
|
43
|
+
# ---------------------------------------------------------------------------
|
|
44
|
+
# Config
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
47
|
+
LOG_DIR="${HOME}/.youmd/logs"
|
|
48
|
+
LOG_FILE="${LOG_DIR}/context-sync.log"
|
|
49
|
+
|
|
50
|
+
GIT_USER_NAME="${GIT_USER_NAME:-Houston Golden}"
|
|
51
|
+
GIT_USER_EMAIL="${GIT_USER_EMAIL:-houston@bamf.ai}"
|
|
52
|
+
|
|
53
|
+
CODE_ROOT="${HOME}/Desktop/CODE_2025"
|
|
54
|
+
|
|
55
|
+
# Curated list of Houston's OWN repos with rich agent context.
|
|
56
|
+
# These all have houstongolden/* or hubify-projects/* remotes — confirmed safe
|
|
57
|
+
# to push to. Override via env CONTEXT_REPOS (space-separated dir names).
|
|
58
|
+
#
|
|
59
|
+
# Excluded by design:
|
|
60
|
+
# - disler/*, coffeefuelbump/*, etc. → not Houston's remote
|
|
61
|
+
# - repos with no origin → detected and skipped at runtime
|
|
62
|
+
# - gstack, agent-shared, scistack → handled by sync.sh
|
|
63
|
+
DEFAULT_CONTEXT_REPOS="bamfaiapp bigbounce hubify-aios myo hubifycode badapp claws"
|
|
64
|
+
CONTEXT_REPOS="${CONTEXT_REPOS:-${DEFAULT_CONTEXT_REPOS}}"
|
|
65
|
+
|
|
66
|
+
# The ONLY paths we will ever stage. Nothing else is touched.
|
|
67
|
+
CONTEXT_PATHSPECS="AGENTS.md CLAUDE.md project-context .claude"
|
|
68
|
+
|
|
69
|
+
DRY_RUN=0
|
|
70
|
+
|
|
71
|
+
# ---------------------------------------------------------------------------
|
|
72
|
+
# Parse flags
|
|
73
|
+
# ---------------------------------------------------------------------------
|
|
74
|
+
for arg in "$@"; do
|
|
75
|
+
case "$arg" in
|
|
76
|
+
--dry-run) DRY_RUN=1 ;;
|
|
77
|
+
--once) : ;; # accepted for explicit invocation parity with sync.sh
|
|
78
|
+
*) echo "Unknown flag: $arg" >&2; exit 1 ;;
|
|
79
|
+
esac
|
|
80
|
+
done
|
|
81
|
+
|
|
82
|
+
# ---------------------------------------------------------------------------
|
|
83
|
+
# Logging
|
|
84
|
+
# ---------------------------------------------------------------------------
|
|
85
|
+
mkdir -p "${LOG_DIR}"
|
|
86
|
+
|
|
87
|
+
log() {
|
|
88
|
+
local ts
|
|
89
|
+
ts="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
|
90
|
+
local msg="[${ts}] $*"
|
|
91
|
+
echo "${msg}"
|
|
92
|
+
echo "${msg}" >> "${LOG_FILE}"
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
log_warn() {
|
|
96
|
+
log "WARN $*"
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
log_error() {
|
|
100
|
+
log "ERROR $*"
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
log_dry() {
|
|
104
|
+
log "DRY $*"
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
# ---------------------------------------------------------------------------
|
|
108
|
+
# Helpers
|
|
109
|
+
# ---------------------------------------------------------------------------
|
|
110
|
+
|
|
111
|
+
hostname_short() {
|
|
112
|
+
# BSD hostname (no --short flag on macOS)
|
|
113
|
+
hostname | cut -d. -f1
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
is_dry() {
|
|
117
|
+
[ "${DRY_RUN}" -eq 1 ]
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
# Check if repo is in a mid-merge/rebase state
|
|
121
|
+
repo_is_in_progress() {
|
|
122
|
+
local repo="$1"
|
|
123
|
+
if [ -f "${repo}/.git/MERGE_HEAD" ] \
|
|
124
|
+
|| [ -d "${repo}/.git/rebase-merge" ] \
|
|
125
|
+
|| [ -d "${repo}/.git/rebase-apply" ]; then
|
|
126
|
+
return 0
|
|
127
|
+
fi
|
|
128
|
+
return 1
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
# Collect context pathspecs that actually exist in this repo.
|
|
132
|
+
# Returns a space-separated list (possibly empty).
|
|
133
|
+
existing_context_paths() {
|
|
134
|
+
local repo="$1"
|
|
135
|
+
local found=""
|
|
136
|
+
local p
|
|
137
|
+
for p in ${CONTEXT_PATHSPECS}; do
|
|
138
|
+
if [ -e "${repo}/${p}" ]; then
|
|
139
|
+
if [ -z "${found}" ]; then
|
|
140
|
+
found="${p}"
|
|
141
|
+
else
|
|
142
|
+
found="${found} ${p}"
|
|
143
|
+
fi
|
|
144
|
+
fi
|
|
145
|
+
done
|
|
146
|
+
echo "${found}"
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
# ---------------------------------------------------------------------------
|
|
150
|
+
# SYNC ONE REPO
|
|
151
|
+
# Returns 0 on success, 1 on non-fatal failure.
|
|
152
|
+
#
|
|
153
|
+
# Pull/push safety decision (dirty non-context working tree):
|
|
154
|
+
# ============================================================
|
|
155
|
+
# After committing context changes, the working tree may still have untracked
|
|
156
|
+
# or modified files (Houston's WIP code). A `git pull` that brings in remote
|
|
157
|
+
# changes could attempt to merge those dirty files, causing conflicts or
|
|
158
|
+
# accidentally modifying them. The SAFE choice:
|
|
159
|
+
# - If the working tree has ANY remaining dirty files after the context
|
|
160
|
+
# commit, SKIP pull/push and log a clear warning.
|
|
161
|
+
# - The context commit is still made locally, so the changes are preserved
|
|
162
|
+
# and Houston can push manually when he's ready.
|
|
163
|
+
# - We never stash, reset, or touch his code.
|
|
164
|
+
# ---------------------------------------------------------------------------
|
|
165
|
+
sync_context_repo() {
|
|
166
|
+
local repo_name="$1"
|
|
167
|
+
local repo="${CODE_ROOT}/${repo_name}"
|
|
168
|
+
|
|
169
|
+
log "=== Syncing context for: ${repo_name} ==="
|
|
170
|
+
|
|
171
|
+
# Guard: directory must exist
|
|
172
|
+
if [ ! -d "${repo}" ]; then
|
|
173
|
+
log_warn "Skipping ${repo_name}: directory not found at ${repo}."
|
|
174
|
+
return 1
|
|
175
|
+
fi
|
|
176
|
+
|
|
177
|
+
# Guard: must be a git repo
|
|
178
|
+
if [ ! -d "${repo}/.git" ]; then
|
|
179
|
+
log_warn "Skipping ${repo_name}: not a git repository."
|
|
180
|
+
return 1
|
|
181
|
+
fi
|
|
182
|
+
|
|
183
|
+
# Guard: must not be mid-merge/rebase
|
|
184
|
+
if repo_is_in_progress "${repo}"; then
|
|
185
|
+
log_warn "Skipping ${repo_name}: repository is mid-merge or mid-rebase. Resolve manually first."
|
|
186
|
+
return 1
|
|
187
|
+
fi
|
|
188
|
+
|
|
189
|
+
# Guard: must have an origin remote
|
|
190
|
+
if ! git -C "${repo}" remote get-url origin >/dev/null 2>&1; then
|
|
191
|
+
log_warn "Skipping ${repo_name}: no 'origin' remote configured."
|
|
192
|
+
return 1
|
|
193
|
+
fi
|
|
194
|
+
|
|
195
|
+
# Determine which context paths exist in this repo
|
|
196
|
+
local paths
|
|
197
|
+
paths="$(existing_context_paths "${repo}")"
|
|
198
|
+
|
|
199
|
+
if [ -z "${paths}" ]; then
|
|
200
|
+
log "No context paths found in ${repo_name} (none of: ${CONTEXT_PATHSPECS}). Skipping."
|
|
201
|
+
return 0
|
|
202
|
+
fi
|
|
203
|
+
|
|
204
|
+
log "Context paths present in ${repo_name}: ${paths}"
|
|
205
|
+
|
|
206
|
+
# ---- Stage ONLY the context paths (never git add -A or git add .) --------
|
|
207
|
+
if is_dry; then
|
|
208
|
+
log_dry "Would stage in ${repo_name}: ${paths}"
|
|
209
|
+
else
|
|
210
|
+
# Build the git add command with only existing paths.
|
|
211
|
+
# We pass each path as a separate argument — safe because they come from
|
|
212
|
+
# our controlled CONTEXT_PATHSPECS list, not user input.
|
|
213
|
+
local add_args=""
|
|
214
|
+
local p
|
|
215
|
+
for p in ${paths}; do
|
|
216
|
+
add_args="${add_args} ${p}"
|
|
217
|
+
done
|
|
218
|
+
# shellcheck disable=SC2086
|
|
219
|
+
git -C "${repo}" add -- ${add_args}
|
|
220
|
+
log "Staged context paths in ${repo_name}: ${paths}"
|
|
221
|
+
fi
|
|
222
|
+
|
|
223
|
+
# ---- Commit if staged changes exist ---------------------------------------
|
|
224
|
+
local context_committed=0
|
|
225
|
+
if is_dry; then
|
|
226
|
+
log_dry "Would check for staged changes and commit context in ${repo_name} if any"
|
|
227
|
+
context_committed=1
|
|
228
|
+
else
|
|
229
|
+
if ! git -C "${repo}" diff --cached --quiet 2>/dev/null; then
|
|
230
|
+
local ts host commit_msg
|
|
231
|
+
ts="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
|
232
|
+
host="$(hostname_short)"
|
|
233
|
+
commit_msg="context-sync: ${host} ${ts}"
|
|
234
|
+
|
|
235
|
+
git -C "${repo}" \
|
|
236
|
+
-c "user.name=${GIT_USER_NAME}" \
|
|
237
|
+
-c "user.email=${GIT_USER_EMAIL}" \
|
|
238
|
+
commit -m "${commit_msg}"
|
|
239
|
+
log "Committed context changes in ${repo_name}: '${commit_msg}'"
|
|
240
|
+
context_committed=1
|
|
241
|
+
else
|
|
242
|
+
log "No staged context changes in ${repo_name} (already up to date)."
|
|
243
|
+
context_committed=0
|
|
244
|
+
fi
|
|
245
|
+
fi
|
|
246
|
+
|
|
247
|
+
# ---- Check for remaining dirty working tree (WIP code) -------------------
|
|
248
|
+
# After committing context, if there are still dirty files, skip pull/push
|
|
249
|
+
# to avoid merge conflicts or accidental code modification.
|
|
250
|
+
if ! is_dry; then
|
|
251
|
+
local remaining_dirty
|
|
252
|
+
remaining_dirty="$(git -C "${repo}" status --porcelain 2>/dev/null)"
|
|
253
|
+
if [ -n "${remaining_dirty}" ]; then
|
|
254
|
+
log_warn "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
|
|
255
|
+
log_warn "${repo_name} has uncommitted non-context changes (WIP code)."
|
|
256
|
+
log_warn "Context was committed locally. Skipping pull/push to avoid"
|
|
257
|
+
log_warn "touching your code. Push manually when your code is ready:"
|
|
258
|
+
log_warn " cd ${repo} && git pull --no-rebase && git push"
|
|
259
|
+
log_warn "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
|
|
260
|
+
return 0
|
|
261
|
+
fi
|
|
262
|
+
fi
|
|
263
|
+
|
|
264
|
+
# ---- Pull (merge, no rebase) ---------------------------------------------
|
|
265
|
+
if is_dry; then
|
|
266
|
+
log_dry "Would git pull --no-rebase --no-edit in ${repo_name}"
|
|
267
|
+
else
|
|
268
|
+
local pull_log
|
|
269
|
+
# BSD mktemp: no -p, use /tmp explicitly
|
|
270
|
+
pull_log="$(mktemp -t context_sync_pull)"
|
|
271
|
+
local pull_ok=0
|
|
272
|
+
git -C "${repo}" pull --no-rebase --no-edit >"${pull_log}" 2>&1 || pull_ok=$?
|
|
273
|
+
|
|
274
|
+
while IFS= read -r line; do
|
|
275
|
+
log "pull|${repo_name}: ${line}"
|
|
276
|
+
done < "${pull_log}"
|
|
277
|
+
rm -f "${pull_log}"
|
|
278
|
+
|
|
279
|
+
if [ "${pull_ok}" -ne 0 ]; then
|
|
280
|
+
log_error "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
|
|
281
|
+
log_error "CONFLICT in ${repo_name} — pull failed."
|
|
282
|
+
log_error "Aborting merge and skipping push."
|
|
283
|
+
log_error "Manual resolution needed: cd ${repo} && git status"
|
|
284
|
+
log_error "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
|
|
285
|
+
git -C "${repo}" merge --abort 2>/dev/null || true
|
|
286
|
+
git -C "${repo}" rebase --abort 2>/dev/null || true
|
|
287
|
+
return 1
|
|
288
|
+
fi
|
|
289
|
+
log "Pull succeeded for ${repo_name}."
|
|
290
|
+
fi
|
|
291
|
+
|
|
292
|
+
# ---- Push ----------------------------------------------------------------
|
|
293
|
+
if is_dry; then
|
|
294
|
+
log_dry "Would git push in ${repo_name}"
|
|
295
|
+
else
|
|
296
|
+
local push_log
|
|
297
|
+
push_log="$(mktemp -t context_sync_push)"
|
|
298
|
+
local push_ok=0
|
|
299
|
+
git -C "${repo}" push >"${push_log}" 2>&1 || push_ok=$?
|
|
300
|
+
|
|
301
|
+
while IFS= read -r line; do
|
|
302
|
+
log "push|${repo_name}: ${line}"
|
|
303
|
+
done < "${push_log}"
|
|
304
|
+
rm -f "${push_log}"
|
|
305
|
+
|
|
306
|
+
if [ "${push_ok}" -ne 0 ]; then
|
|
307
|
+
log_error "Push FAILED for ${repo_name}. Check connectivity/auth."
|
|
308
|
+
return 1
|
|
309
|
+
fi
|
|
310
|
+
log "Push succeeded for ${repo_name}."
|
|
311
|
+
fi
|
|
312
|
+
|
|
313
|
+
log "=== Done: ${repo_name} ==="
|
|
314
|
+
return 0
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
# ---------------------------------------------------------------------------
|
|
318
|
+
# MAIN
|
|
319
|
+
# ---------------------------------------------------------------------------
|
|
320
|
+
main() {
|
|
321
|
+
local mode
|
|
322
|
+
if is_dry; then
|
|
323
|
+
mode="DRY-RUN"
|
|
324
|
+
else
|
|
325
|
+
mode="LIVE"
|
|
326
|
+
fi
|
|
327
|
+
|
|
328
|
+
log "============================================================"
|
|
329
|
+
log "context-sync START [${mode}] on $(hostname_short)"
|
|
330
|
+
log "Repos: ${CONTEXT_REPOS}"
|
|
331
|
+
log "Context paths: ${CONTEXT_PATHSPECS}"
|
|
332
|
+
log "============================================================"
|
|
333
|
+
|
|
334
|
+
local repo_name
|
|
335
|
+
for repo_name in ${CONTEXT_REPOS}; do
|
|
336
|
+
sync_context_repo "${repo_name}" || true
|
|
337
|
+
done
|
|
338
|
+
|
|
339
|
+
log "============================================================"
|
|
340
|
+
log "context-sync DONE [${mode}]"
|
|
341
|
+
log "============================================================"
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
main "$@"
|