youmd 0.7.1 → 0.8.2

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 (125) hide show
  1. package/dist/commands/chat.d.ts.map +1 -1
  2. package/dist/commands/chat.js +3 -2
  3. package/dist/commands/chat.js.map +1 -1
  4. package/dist/commands/env.d.ts +2 -0
  5. package/dist/commands/env.d.ts.map +1 -1
  6. package/dist/commands/env.js +7 -3
  7. package/dist/commands/env.js.map +1 -1
  8. package/dist/commands/login.d.ts +1 -0
  9. package/dist/commands/login.d.ts.map +1 -1
  10. package/dist/commands/login.js +20 -4
  11. package/dist/commands/login.js.map +1 -1
  12. package/dist/commands/machine.d.ts +31 -0
  13. package/dist/commands/machine.d.ts.map +1 -1
  14. package/dist/commands/machine.js +610 -14
  15. package/dist/commands/machine.js.map +1 -1
  16. package/dist/commands/project-log.d.ts +13 -0
  17. package/dist/commands/project-log.d.ts.map +1 -0
  18. package/dist/commands/project-log.js +241 -0
  19. package/dist/commands/project-log.js.map +1 -0
  20. package/dist/commands/project.d.ts.map +1 -1
  21. package/dist/commands/project.js +872 -15
  22. package/dist/commands/project.js.map +1 -1
  23. package/dist/commands/prompts.d.ts +19 -0
  24. package/dist/commands/prompts.d.ts.map +1 -1
  25. package/dist/commands/prompts.js +304 -1
  26. package/dist/commands/prompts.js.map +1 -1
  27. package/dist/commands/pull.d.ts.map +1 -1
  28. package/dist/commands/pull.js +28 -8
  29. package/dist/commands/pull.js.map +1 -1
  30. package/dist/commands/push.d.ts +3 -1
  31. package/dist/commands/push.d.ts.map +1 -1
  32. package/dist/commands/push.js +26 -12
  33. package/dist/commands/push.js.map +1 -1
  34. package/dist/commands/skill.d.ts.map +1 -1
  35. package/dist/commands/skill.js +19 -15
  36. package/dist/commands/skill.js.map +1 -1
  37. package/dist/commands/stack.d.ts.map +1 -1
  38. package/dist/commands/stack.js +37 -17
  39. package/dist/commands/stack.js.map +1 -1
  40. package/dist/commands/status.d.ts.map +1 -1
  41. package/dist/commands/status.js +31 -0
  42. package/dist/commands/status.js.map +1 -1
  43. package/dist/commands/sync.d.ts +1 -0
  44. package/dist/commands/sync.d.ts.map +1 -1
  45. package/dist/commands/sync.js +51 -2
  46. package/dist/commands/sync.js.map +1 -1
  47. package/dist/index.js +125 -36
  48. package/dist/index.js.map +1 -1
  49. package/dist/lib/api.d.ts +324 -0
  50. package/dist/lib/api.d.ts.map +1 -1
  51. package/dist/lib/api.js +68 -0
  52. package/dist/lib/api.js.map +1 -1
  53. package/dist/lib/compiler.d.ts.map +1 -1
  54. package/dist/lib/compiler.js +10 -0
  55. package/dist/lib/compiler.js.map +1 -1
  56. package/dist/lib/daemon.d.ts +23 -0
  57. package/dist/lib/daemon.d.ts.map +1 -0
  58. package/dist/lib/daemon.js +166 -0
  59. package/dist/lib/daemon.js.map +1 -0
  60. package/dist/lib/first-run.d.ts +1 -1
  61. package/dist/lib/first-run.d.ts.map +1 -1
  62. package/dist/lib/first-run.js +8 -0
  63. package/dist/lib/first-run.js.map +1 -1
  64. package/dist/lib/machine-bootstrap-prompt.d.ts +17 -0
  65. package/dist/lib/machine-bootstrap-prompt.d.ts.map +1 -0
  66. package/dist/lib/machine-bootstrap-prompt.js +305 -0
  67. package/dist/lib/machine-bootstrap-prompt.js.map +1 -0
  68. package/dist/lib/machine-projects.d.ts +71 -0
  69. package/dist/lib/machine-projects.d.ts.map +1 -0
  70. package/dist/lib/machine-projects.js +510 -0
  71. package/dist/lib/machine-projects.js.map +1 -0
  72. package/dist/lib/machine-verify.d.ts +175 -0
  73. package/dist/lib/machine-verify.d.ts.map +1 -0
  74. package/dist/lib/machine-verify.js +684 -0
  75. package/dist/lib/machine-verify.js.map +1 -0
  76. package/dist/lib/portfolio-audit.d.ts +34 -0
  77. package/dist/lib/portfolio-audit.d.ts.map +1 -0
  78. package/dist/lib/portfolio-audit.js +254 -0
  79. package/dist/lib/portfolio-audit.js.map +1 -0
  80. package/dist/lib/portfolio-graph.d.ts +104 -0
  81. package/dist/lib/portfolio-graph.d.ts.map +1 -0
  82. package/dist/lib/portfolio-graph.js +281 -0
  83. package/dist/lib/portfolio-graph.js.map +1 -0
  84. package/dist/lib/project-strategy.d.ts +45 -0
  85. package/dist/lib/project-strategy.d.ts.map +1 -0
  86. package/dist/lib/project-strategy.js +234 -0
  87. package/dist/lib/project-strategy.js.map +1 -0
  88. package/dist/lib/reusable-patterns.d.ts +30 -0
  89. package/dist/lib/reusable-patterns.d.ts.map +1 -0
  90. package/dist/lib/reusable-patterns.js +375 -0
  91. package/dist/lib/reusable-patterns.js.map +1 -0
  92. package/dist/lib/skill-catalog.d.ts.map +1 -1
  93. package/dist/lib/skill-catalog.js +20 -0
  94. package/dist/lib/skill-catalog.js.map +1 -1
  95. package/dist/lib/skills.d.ts +1 -0
  96. package/dist/lib/skills.d.ts.map +1 -1
  97. package/dist/lib/skills.js +43 -13
  98. package/dist/lib/skills.js.map +1 -1
  99. package/dist/lib/version.d.ts +2 -0
  100. package/dist/lib/version.d.ts.map +1 -0
  101. package/dist/lib/version.js +63 -0
  102. package/dist/lib/version.js.map +1 -0
  103. package/dist/mcp/registry.d.ts +5 -1
  104. package/dist/mcp/registry.d.ts.map +1 -1
  105. package/dist/mcp/registry.js +426 -2
  106. package/dist/mcp/registry.js.map +1 -1
  107. package/dist/mcp/server.d.ts +2 -0
  108. package/dist/mcp/server.d.ts.map +1 -1
  109. package/dist/mcp/server.js +50 -5
  110. package/dist/mcp/server.js.map +1 -1
  111. package/package.json +1 -1
  112. package/scripts/env-vault/README.md +37 -10
  113. package/scripts/env-vault/backup.sh +70 -4
  114. package/scripts/env-vault/restore.sh +117 -9
  115. package/scripts/skillstack-sync/README.md +13 -6
  116. package/scripts/skillstack-sync/bootstrap-new-mac.sh +19 -5
  117. package/scripts/skillstack-sync/capture-agent-config.sh +110 -0
  118. package/scripts/skillstack-sync/com.youmd.context-sync.plist +38 -0
  119. package/scripts/skillstack-sync/com.youmd.identity-sync.plist +3 -1
  120. package/scripts/skillstack-sync/com.youmd.skillstack-sync.plist +1 -1
  121. package/scripts/skillstack-sync/context-sync.sh +412 -0
  122. package/scripts/skillstack-sync/install-daemons.sh +7 -3
  123. package/scripts/skillstack-sync/restore-agent-config.sh +152 -0
  124. package/scripts/skillstack-sync/sync.sh +20 -6
  125. package/skills/machine-bootstrap.md +243 -0
@@ -9,7 +9,7 @@
9
9
  <array>
10
10
  <string>/bin/zsh</string>
11
11
  <string>-lc</string>
12
- <string>youmd sync</string>
12
+ <string>youmd sync --daemon</string>
13
13
  </array>
14
14
 
15
15
  <key>StartInterval</key>
@@ -31,6 +31,8 @@
31
31
  <dict>
32
32
  <key>HOME</key>
33
33
  <string>__HOME__</string>
34
+ <key>PATH</key>
35
+ <string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
34
36
  </dict>
35
37
  </dict>
36
38
  </plist>
@@ -32,7 +32,7 @@
32
32
  <key>HOME</key>
33
33
  <string>__HOME__</string>
34
34
  <key>PATH</key>
35
- <string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
35
+ <string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
36
36
  </dict>
37
37
  </dict>
38
38
  </plist>
@@ -0,0 +1,412 @@
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
+ path_is_context() {
150
+ local changed="$1"
151
+ local p
152
+ for p in ${CONTEXT_PATHSPECS}; do
153
+ if [ "${changed}" = "${p}" ]; then
154
+ return 0
155
+ fi
156
+ case "${changed}" in
157
+ "${p}/"*) return 0 ;;
158
+ esac
159
+ done
160
+ return 1
161
+ }
162
+
163
+ remote_has_non_context_changes() {
164
+ local repo="$1" repo_name="$2" upstream="$3"
165
+ local changed non_context line
166
+ changed="$(git -C "${repo}" diff --name-only "HEAD..${upstream}" 2>/dev/null || true)"
167
+ if [ -z "${changed}" ]; then
168
+ return 1
169
+ fi
170
+
171
+ non_context=""
172
+ while IFS= read -r line; do
173
+ [ -z "${line}" ] && continue
174
+ if ! path_is_context "${line}"; then
175
+ if [ -z "${non_context}" ]; then
176
+ non_context="${line}"
177
+ else
178
+ non_context="${non_context}, ${line}"
179
+ fi
180
+ fi
181
+ done <<EOF
182
+ ${changed}
183
+ EOF
184
+
185
+ if [ -n "${non_context}" ]; then
186
+ log_warn "${repo_name}: upstream has non-context changes (${non_context}). Skipping pull/push so context-sync never merges application code."
187
+ return 0
188
+ fi
189
+ return 1
190
+ }
191
+
192
+ # ---------------------------------------------------------------------------
193
+ # SYNC ONE REPO
194
+ # Returns 0 on success, 1 on non-fatal failure.
195
+ #
196
+ # Pull/push safety decision (dirty non-context working tree):
197
+ # ============================================================
198
+ # After committing context changes, the working tree may still have untracked
199
+ # or modified files (Houston's WIP code). A `git pull` that brings in remote
200
+ # changes could attempt to merge those dirty files, causing conflicts or
201
+ # accidentally modifying them. The SAFE choice:
202
+ # - If the working tree has ANY remaining dirty files after the context
203
+ # commit, SKIP pull/push and log a clear warning.
204
+ # - The context commit is still made locally, so the changes are preserved
205
+ # and Houston can push manually when he's ready.
206
+ # - We never stash, reset, or touch his code.
207
+ # ---------------------------------------------------------------------------
208
+ sync_context_repo() {
209
+ local repo_name="$1"
210
+ local repo="${CODE_ROOT}/${repo_name}"
211
+
212
+ log "=== Syncing context for: ${repo_name} ==="
213
+
214
+ # Guard: directory must exist
215
+ if [ ! -d "${repo}" ]; then
216
+ log_warn "Skipping ${repo_name}: directory not found at ${repo}."
217
+ return 1
218
+ fi
219
+
220
+ # Guard: must be a git repo
221
+ if [ ! -d "${repo}/.git" ]; then
222
+ log_warn "Skipping ${repo_name}: not a git repository."
223
+ return 1
224
+ fi
225
+
226
+ # Guard: must not be mid-merge/rebase
227
+ if repo_is_in_progress "${repo}"; then
228
+ log_warn "Skipping ${repo_name}: repository is mid-merge or mid-rebase. Resolve manually first."
229
+ return 1
230
+ fi
231
+
232
+ # Guard: must have an origin remote
233
+ if ! git -C "${repo}" remote get-url origin >/dev/null 2>&1; then
234
+ log_warn "Skipping ${repo_name}: no 'origin' remote configured."
235
+ return 1
236
+ fi
237
+
238
+ # Determine which context paths exist in this repo
239
+ local paths
240
+ paths="$(existing_context_paths "${repo}")"
241
+
242
+ if [ -z "${paths}" ]; then
243
+ log "No context paths found in ${repo_name} (none of: ${CONTEXT_PATHSPECS}). Skipping."
244
+ return 0
245
+ fi
246
+
247
+ log "Context paths present in ${repo_name}: ${paths}"
248
+
249
+ # ---- Stage ONLY the context paths (never git add -A or git add .) --------
250
+ if is_dry; then
251
+ log_dry "Would stage in ${repo_name}: ${paths}"
252
+ else
253
+ # Build the git add command with only existing paths.
254
+ # We pass each path as a separate argument — safe because they come from
255
+ # our controlled CONTEXT_PATHSPECS list, not user input.
256
+ local add_args=""
257
+ local p
258
+ for p in ${paths}; do
259
+ add_args="${add_args} ${p}"
260
+ done
261
+ # shellcheck disable=SC2086
262
+ git -C "${repo}" add -- ${add_args}
263
+ log "Staged context paths in ${repo_name}: ${paths}"
264
+ fi
265
+
266
+ # ---- Commit if staged changes exist ---------------------------------------
267
+ local context_committed=0
268
+ if is_dry; then
269
+ log_dry "Would check for staged changes and commit context in ${repo_name} if any"
270
+ context_committed=1
271
+ else
272
+ if ! git -C "${repo}" diff --cached --quiet 2>/dev/null; then
273
+ local ts host commit_msg
274
+ ts="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
275
+ host="$(hostname_short)"
276
+ commit_msg="context-sync: ${host} ${ts}"
277
+
278
+ git -C "${repo}" \
279
+ -c "user.name=${GIT_USER_NAME}" \
280
+ -c "user.email=${GIT_USER_EMAIL}" \
281
+ commit -m "${commit_msg}"
282
+ log "Committed context changes in ${repo_name}: '${commit_msg}'"
283
+ context_committed=1
284
+ else
285
+ log "No staged context changes in ${repo_name} (already up to date)."
286
+ context_committed=0
287
+ fi
288
+ fi
289
+
290
+ # ---- Check for remaining dirty working tree (WIP code) -------------------
291
+ # After committing context, if there are still dirty files, skip pull/push
292
+ # to avoid merge conflicts or accidental code modification.
293
+ if ! is_dry; then
294
+ local remaining_dirty
295
+ remaining_dirty="$(git -C "${repo}" status --porcelain 2>/dev/null)"
296
+ if [ -n "${remaining_dirty}" ]; then
297
+ log_warn "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
298
+ log_warn "${repo_name} has uncommitted non-context changes (WIP code)."
299
+ log_warn "Context was committed locally. Skipping pull/push to avoid"
300
+ log_warn "touching your code. Push manually when your code is ready:"
301
+ log_warn " cd ${repo} && git pull --no-rebase && git push"
302
+ log_warn "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
303
+ return 0
304
+ fi
305
+ fi
306
+
307
+ # ---- Pull (merge, no rebase) ---------------------------------------------
308
+ if is_dry; then
309
+ log_dry "Would fetch upstream and pull only if remote changes are limited to context paths in ${repo_name}"
310
+ else
311
+ local upstream
312
+ upstream="$(git -C "${repo}" rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null || true)"
313
+ if [ -z "${upstream}" ]; then
314
+ log_warn "Skipping pull/push for ${repo_name}: no upstream branch configured."
315
+ return 0
316
+ fi
317
+
318
+ local fetch_log
319
+ fetch_log="$(mktemp -t context_sync_fetch)"
320
+ local fetch_ok=0
321
+ git -C "${repo}" fetch --quiet >"${fetch_log}" 2>&1 || fetch_ok=$?
322
+ if [ "${fetch_ok}" -ne 0 ]; then
323
+ while IFS= read -r line; do
324
+ log "fetch|${repo_name}: ${line}"
325
+ done < "${fetch_log}"
326
+ rm -f "${fetch_log}"
327
+ log_error "Fetch FAILED for ${repo_name}. Skipping pull/push."
328
+ return 1
329
+ fi
330
+ rm -f "${fetch_log}"
331
+
332
+ if remote_has_non_context_changes "${repo}" "${repo_name}" "${upstream}"; then
333
+ return 0
334
+ fi
335
+
336
+ local pull_log
337
+ # BSD mktemp: no -p, use /tmp explicitly
338
+ pull_log="$(mktemp -t context_sync_pull)"
339
+ local pull_ok=0
340
+ git -C "${repo}" pull --no-rebase --no-edit >"${pull_log}" 2>&1 || pull_ok=$?
341
+
342
+ while IFS= read -r line; do
343
+ log "pull|${repo_name}: ${line}"
344
+ done < "${pull_log}"
345
+ rm -f "${pull_log}"
346
+
347
+ if [ "${pull_ok}" -ne 0 ]; then
348
+ log_error "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
349
+ log_error "CONFLICT in ${repo_name} — pull failed."
350
+ log_error "Aborting merge and skipping push."
351
+ log_error "Manual resolution needed: cd ${repo} && git status"
352
+ log_error "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
353
+ git -C "${repo}" merge --abort 2>/dev/null || true
354
+ git -C "${repo}" rebase --abort 2>/dev/null || true
355
+ return 1
356
+ fi
357
+ log "Pull succeeded for ${repo_name}."
358
+ fi
359
+
360
+ # ---- Push ----------------------------------------------------------------
361
+ if is_dry; then
362
+ log_dry "Would git push in ${repo_name}"
363
+ else
364
+ local push_log
365
+ push_log="$(mktemp -t context_sync_push)"
366
+ local push_ok=0
367
+ git -C "${repo}" push >"${push_log}" 2>&1 || push_ok=$?
368
+
369
+ while IFS= read -r line; do
370
+ log "push|${repo_name}: ${line}"
371
+ done < "${push_log}"
372
+ rm -f "${push_log}"
373
+
374
+ if [ "${push_ok}" -ne 0 ]; then
375
+ log_error "Push FAILED for ${repo_name}. Check connectivity/auth."
376
+ return 1
377
+ fi
378
+ log "Push succeeded for ${repo_name}."
379
+ fi
380
+
381
+ log "=== Done: ${repo_name} ==="
382
+ return 0
383
+ }
384
+
385
+ # ---------------------------------------------------------------------------
386
+ # MAIN
387
+ # ---------------------------------------------------------------------------
388
+ main() {
389
+ local mode
390
+ if is_dry; then
391
+ mode="DRY-RUN"
392
+ else
393
+ mode="LIVE"
394
+ fi
395
+
396
+ log "============================================================"
397
+ log "context-sync START [${mode}] on $(hostname_short)"
398
+ log "Repos: ${CONTEXT_REPOS}"
399
+ log "Context paths: ${CONTEXT_PATHSPECS}"
400
+ log "============================================================"
401
+
402
+ local repo_name
403
+ for repo_name in ${CONTEXT_REPOS}; do
404
+ sync_context_repo "${repo_name}" || true
405
+ done
406
+
407
+ log "============================================================"
408
+ log "context-sync DONE [${mode}]"
409
+ log "============================================================"
410
+ }
411
+
412
+ main "$@"
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env bash
2
- # install-daemons.sh — install skillstack-sync and identity-sync LaunchAgents
2
+ # install-daemons.sh — install resident You.md sync LaunchAgents
3
3
  #
4
4
  # Idempotent: safe to re-run. Unloads before reloading.
5
5
  # Does NOT need sudo — LaunchAgents run as the current user.
@@ -12,7 +12,7 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
12
12
  LAUNCH_AGENTS_DIR="${HOME}/Library/LaunchAgents"
13
13
  LOG_DIR="${HOME}/.youmd/logs"
14
14
 
15
- PLISTS="com.youmd.skillstack-sync com.youmd.identity-sync"
15
+ PLISTS="com.youmd.skillstack-sync com.youmd.identity-sync com.youmd.context-sync"
16
16
 
17
17
  echo "==> Creating log directory: ${LOG_DIR}"
18
18
  mkdir -p "${LOG_DIR}"
@@ -55,7 +55,7 @@ done
55
55
 
56
56
  echo ""
57
57
  echo "================================================================"
58
- echo "Both daemons installed and running."
58
+ echo "Resident You.md daemons installed and running."
59
59
  echo ""
60
60
  echo "Log files:"
61
61
  echo " ${LOG_DIR}/skillstack-sync.log (combined sync log)"
@@ -63,10 +63,14 @@ echo " ${LOG_DIR}/skillstack-sync.out.log (daemon stdout)"
63
63
  echo " ${LOG_DIR}/skillstack-sync.err.log (daemon stderr)"
64
64
  echo " ${LOG_DIR}/identity-sync.out.log (youmd sync stdout)"
65
65
  echo " ${LOG_DIR}/identity-sync.err.log (youmd sync stderr)"
66
+ echo " ${LOG_DIR}/context-sync.log (combined project-context sync log)"
67
+ echo " ${LOG_DIR}/context-sync.out.log (daemon stdout)"
68
+ echo " ${LOG_DIR}/context-sync.err.log (daemon stderr)"
66
69
  echo ""
67
70
  echo "Check daemon status:"
68
71
  echo " launchctl list com.youmd.skillstack-sync"
69
72
  echo " launchctl list com.youmd.identity-sync"
73
+ echo " launchctl list com.youmd.context-sync"
70
74
  echo ""
71
75
  echo "To uninstall:"
72
76
  for label in ${PLISTS}; do
@@ -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
- # v1 default is agent-shared ONLY — it's purpose-built for sync and safe to
34
- # auto-commit. scistack is intentionally NOT auto-synced by default: it holds
35
- # protected learning-loop IP where auto-committing WIP on a timer is undesirable.
36
- # To opt scistack (or other repos) in, set:
37
- # SKILLSTACK_REPOS="$HOME/.agent-shared $HOME/.claude/scistack"
38
- DEFAULT_REPOS="${AGENT_SHARED}"
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