youmd 0.7.0 → 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 +6 -0
- package/dist/commands/machine.d.ts.map +1 -0
- package/dist/commands/machine.js +142 -0
- package/dist/commands/machine.js.map +1 -0
- 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 +43 -15
- 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
|
@@ -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 "$@"
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# restore-agent-config.sh — apply ~/.agent-shared/agent-config/ back onto this machine
|
|
3
|
+
#
|
|
4
|
+
# Mirror-image of capture-agent-config.sh. Takes each item in agent-config/
|
|
5
|
+
# and copies it to its canonical home location, backing up any existing
|
|
6
|
+
# file/dir to <dest>.bak.<UTC-ts> first (unless --force skips the backup).
|
|
7
|
+
#
|
|
8
|
+
# Restored mappings:
|
|
9
|
+
# agent-config/claude/* → ~/.claude/
|
|
10
|
+
# agent-config/codex/config.toml → ~/.codex/config.toml
|
|
11
|
+
# agent-config/warp/* → ~/.warp/
|
|
12
|
+
# agent-config/agents-skills/* → ~/.agents/skills/
|
|
13
|
+
# agent-config/automations/launchd/*.plist → ~/Library/LaunchAgents/ (HOME templated back)
|
|
14
|
+
# agent-config/automations/crontab.txt → PRINTED ONLY (user must merge manually)
|
|
15
|
+
#
|
|
16
|
+
# bash 3.2 / BSD safe. Idempotent. --dry-run to preview without writing.
|
|
17
|
+
# --force to skip per-file backups (still idempotent, overwrites in place).
|
|
18
|
+
#
|
|
19
|
+
# SECRETS: there should be none in agent-config (the capture script guards
|
|
20
|
+
# against it), but this script never prints file contents — only paths.
|
|
21
|
+
#
|
|
22
|
+
# Usage: bash restore-agent-config.sh [--dry-run] [--force]
|
|
23
|
+
|
|
24
|
+
set -euo pipefail
|
|
25
|
+
|
|
26
|
+
AGENT_SHARED="${HOME}/.agent-shared"
|
|
27
|
+
SRC="${AGENT_SHARED}/agent-config"
|
|
28
|
+
LOG_DIR="${HOME}/.youmd/logs"
|
|
29
|
+
LOG_FILE="${LOG_DIR}/restore-agent-config.log"
|
|
30
|
+
|
|
31
|
+
DRY_RUN=0
|
|
32
|
+
FORCE=0
|
|
33
|
+
for arg in "$@"; do
|
|
34
|
+
case "$arg" in
|
|
35
|
+
--dry-run) DRY_RUN=1 ;;
|
|
36
|
+
--force) FORCE=1 ;;
|
|
37
|
+
*) echo "Unknown flag: $arg" >&2; exit 1 ;;
|
|
38
|
+
esac
|
|
39
|
+
done
|
|
40
|
+
|
|
41
|
+
mkdir -p "${LOG_DIR}"
|
|
42
|
+
log() { local ts; ts="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"; echo "[${ts}] $*"; echo "[${ts}] $*" >> "${LOG_FILE}"; }
|
|
43
|
+
is_dry() { [ "${DRY_RUN}" -eq 1 ]; }
|
|
44
|
+
is_force() { [ "${FORCE}" -eq 1 ]; }
|
|
45
|
+
|
|
46
|
+
if [ ! -d "${AGENT_SHARED}/.git" ]; then
|
|
47
|
+
log "ERROR: ${AGENT_SHARED} is not a git repo — run 'youmd machine setup' first."
|
|
48
|
+
exit 1
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
if [ ! -d "${SRC}" ]; then
|
|
52
|
+
log "ERROR: ${SRC} does not exist — nothing to restore. Run 'youmd machine capture' on the source machine first."
|
|
53
|
+
exit 1
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
log "=== restore-agent-config START ($([ "${DRY_RUN}" -eq 1 ] && echo DRY || echo LIVE) $([ "${FORCE}" -eq 1 ] && echo FORCE || echo SAFE)) ==="
|
|
57
|
+
|
|
58
|
+
# backup_dest <path>
|
|
59
|
+
# If the dest exists and --force is not set, back it up.
|
|
60
|
+
backup_dest() {
|
|
61
|
+
local dest="$1"
|
|
62
|
+
if [ -e "${dest}" ] && ! is_force; then
|
|
63
|
+
local ts; ts="$(date -u +"%Y%m%dT%H%M%SZ")"
|
|
64
|
+
local bak="${dest}.bak.${ts}"
|
|
65
|
+
if is_dry; then
|
|
66
|
+
log "DRY would backup ${dest} → ${bak}"
|
|
67
|
+
else
|
|
68
|
+
mv "${dest}" "${bak}"
|
|
69
|
+
log "backed up ${dest} → ${bak}"
|
|
70
|
+
fi
|
|
71
|
+
fi
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
# copy_out <src-relative-to-agent-config> <absolute-dest>
|
|
75
|
+
# Copy a file or directory from agent-config onto this machine.
|
|
76
|
+
copy_out() {
|
|
77
|
+
local rel="$1" dest="$2" src
|
|
78
|
+
src="${SRC}/${rel}"
|
|
79
|
+
if [ ! -e "${src}" ]; then return 0; fi
|
|
80
|
+
|
|
81
|
+
if is_dry; then
|
|
82
|
+
log "DRY would restore agent-config/${rel} → ${dest}"
|
|
83
|
+
return 0
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
mkdir -p "$(dirname "${dest}")"
|
|
87
|
+
backup_dest "${dest}"
|
|
88
|
+
if [ -d "${src}" ]; then
|
|
89
|
+
rsync -a "${src}/" "${dest}/"
|
|
90
|
+
else
|
|
91
|
+
cp "${src}" "${dest}"
|
|
92
|
+
fi
|
|
93
|
+
log "restored agent-config/${rel} → ${dest}"
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
# ── Claude config ──────────────────────────────────────────────────────────────
|
|
97
|
+
copy_out "claude/settings.json" "${HOME}/.claude/settings.json"
|
|
98
|
+
copy_out "claude/settings.local.json" "${HOME}/.claude/settings.local.json"
|
|
99
|
+
copy_out "claude/commands" "${HOME}/.claude/commands"
|
|
100
|
+
copy_out "claude/mcp.json" "${HOME}/.claude/mcp.json"
|
|
101
|
+
copy_out "claude/plugins/installed_plugins.json" "${HOME}/.claude/plugins/installed_plugins.json"
|
|
102
|
+
|
|
103
|
+
# ── Codex config ───────────────────────────────────────────────────────────────
|
|
104
|
+
copy_out "codex/config.toml" "${HOME}/.codex/config.toml"
|
|
105
|
+
|
|
106
|
+
# ── Warp ───────────────────────────────────────────────────────────────────────
|
|
107
|
+
copy_out "warp/settings.toml" "${HOME}/.warp/settings.toml"
|
|
108
|
+
copy_out "warp/keybindings.yaml" "${HOME}/.warp/keybindings.yaml"
|
|
109
|
+
|
|
110
|
+
# ── Orphaned ~/.agents skills ──────────────────────────────────────────────────
|
|
111
|
+
copy_out "agents-skills/find-skills" "${HOME}/.agents/skills/find-skills"
|
|
112
|
+
copy_out "agents-skills/ui-ux-pro-max" "${HOME}/.agents/skills/ui-ux-pro-max"
|
|
113
|
+
|
|
114
|
+
# ── Automations: launchd plists (HOME-templated → real HOME) ───────────────────
|
|
115
|
+
LAUNCH_AGENTS_DIR="${HOME}/Library/LaunchAgents"
|
|
116
|
+
LAUNCHD_SRC="${SRC}/automations/launchd"
|
|
117
|
+
if [ -d "${LAUNCHD_SRC}" ]; then
|
|
118
|
+
# BSD-compatible glob: use find to enumerate plists
|
|
119
|
+
while IFS= read -r plist_src; do
|
|
120
|
+
name="$(basename "${plist_src}")"
|
|
121
|
+
dest="${LAUNCH_AGENTS_DIR}/${name}"
|
|
122
|
+
if is_dry; then
|
|
123
|
+
log "DRY would restore plist ${name} → ${dest} (with __HOME__ → \$HOME)"
|
|
124
|
+
else
|
|
125
|
+
mkdir -p "${LAUNCH_AGENTS_DIR}"
|
|
126
|
+
backup_dest "${dest}"
|
|
127
|
+
# Replace __HOME__ placeholder with the real HOME value (BSD sed, no -i extension trick)
|
|
128
|
+
sed "s|__HOME__|${HOME}|g" "${plist_src}" > "${dest}"
|
|
129
|
+
log "restored plist ${name} → ${dest}"
|
|
130
|
+
fi
|
|
131
|
+
log "NOTE: to load this daemon run: launchctl load -w ${dest}"
|
|
132
|
+
log " (or run: youmd stack daemon install — covers the youmd ones)"
|
|
133
|
+
done < <(find "${LAUNCHD_SRC}" -maxdepth 1 -name "*.plist" 2>/dev/null || true)
|
|
134
|
+
fi
|
|
135
|
+
|
|
136
|
+
# ── Crontab — PRINT ONLY, never overwrite automatically ────────────────────────
|
|
137
|
+
CRONTAB_SRC="${SRC}/automations/crontab.txt"
|
|
138
|
+
if [ -f "${CRONTAB_SRC}" ]; then
|
|
139
|
+
log "crontab.txt found — NOT writing automatically (clobbering crontab is destructive)."
|
|
140
|
+
log "Inspect and merge manually with: crontab -e"
|
|
141
|
+
echo ""
|
|
142
|
+
echo " ── captured crontab entries (merge manually with 'crontab -e') ──"
|
|
143
|
+
# Print each line with a leading " " indent so it's visually distinct.
|
|
144
|
+
# Use a while-read loop (no cat) to stay shell-safe.
|
|
145
|
+
while IFS= read -r line; do
|
|
146
|
+
echo " ${line}"
|
|
147
|
+
done < "${CRONTAB_SRC}"
|
|
148
|
+
echo " ── end crontab ──"
|
|
149
|
+
echo ""
|
|
150
|
+
fi
|
|
151
|
+
|
|
152
|
+
log "=== restore-agent-config DONE ==="
|
|
@@ -30,12 +30,15 @@ SCISTACK="${HOME}/.claude/scistack"
|
|
|
30
30
|
LOOSE_SKILLS="agent-runtime-guard agent-stack-sync continue skill-governor"
|
|
31
31
|
|
|
32
32
|
# Repos to sync (space-separated absolute paths; override via env).
|
|
33
|
-
#
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
|
|
33
|
+
# Houston's agent skill/stack layer syncs by DEFAULT:
|
|
34
|
+
# - ~/.agent-shared → shared agent config + loose skills (houstongolden/agent-shared)
|
|
35
|
+
# - ~/.claude/scistack → ALL science skills (Hubify-Projects/scistack): 68 skill
|
|
36
|
+
# symlink targets under hubstack/ + astrostack/ + extensions/, incl. the
|
|
37
|
+
# learning-loop IP. Conflict-safe (commit→merge-pull→push, abort on conflict),
|
|
38
|
+
# so auto-syncing his own repo is exactly the cross-machine behavior intended.
|
|
39
|
+
# gstack is NOT here (upstream garrytan/gstack, reinstalled via gstack-upgrade).
|
|
40
|
+
# Add more repos by setting SKILLSTACK_REPOS to a space-separated path list.
|
|
41
|
+
DEFAULT_REPOS="${AGENT_SHARED} ${SCISTACK}"
|
|
39
42
|
SKILLSTACK_REPOS="${SKILLSTACK_REPOS:-${DEFAULT_REPOS}}"
|
|
40
43
|
|
|
41
44
|
DRY_RUN=0
|
|
@@ -291,6 +294,17 @@ main() {
|
|
|
291
294
|
log "skillstack-sync START [${mode}] on $(hostname_short)"
|
|
292
295
|
log "============================================================"
|
|
293
296
|
|
|
297
|
+
# Step 0: Capture non-secret agent config (claude/codex/warp settings, slash
|
|
298
|
+
# commands, plugin list, orphan skills, automations) into agent-shared so it
|
|
299
|
+
# travels with the sync. Secret-leak-guarded; never blocks the sync on failure.
|
|
300
|
+
if [ -x "${SCRIPT_DIR}/capture-agent-config.sh" ]; then
|
|
301
|
+
if is_dry; then
|
|
302
|
+
bash "${SCRIPT_DIR}/capture-agent-config.sh" --dry-run || log_warn "capture-agent-config dry-run failed (non-fatal)"
|
|
303
|
+
else
|
|
304
|
+
bash "${SCRIPT_DIR}/capture-agent-config.sh" || log_warn "capture-agent-config failed (non-fatal — continuing sync)"
|
|
305
|
+
fi
|
|
306
|
+
fi
|
|
307
|
+
|
|
294
308
|
# Step 1: Mirror loose skills into agent-shared BEFORE syncing it
|
|
295
309
|
mirror_loose_skills_to_repo
|
|
296
310
|
|