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.
@@ -1,8 +1,22 @@
1
- # env-vault — Secure .env.local Backup & Restore
1
+ # env-vault — Secure .env.local + Agent-Auth Backup & Restore
2
2
 
3
- Backs up all `.env.local` files across your CODE_2025 projects into a single
4
- encrypted archive, and restores them on a new Mac. No secret values ever appear
5
- in plaintext outside the encrypted blob.
3
+ Backs up all `.env.local` files across your CODE_2025 projects **and** a fixed
4
+ set of hard agent-auth secret files into a single encrypted archive, and
5
+ restores them on a new Mac. No secret values ever appear in plaintext outside
6
+ the encrypted blob.
7
+
8
+ ### What's in the vault
9
+
10
+ | Source path | Archive path | Restore destination |
11
+ |---|---|---|
12
+ | `<CODE_2025>/<project>/.env.local` | `<project>/.env.local` | `<CODE_2025>/<project>/.env.local` |
13
+ | `~/.codex/auth.json` | `agent-auth/codex-auth.json` | `~/.codex/auth.json` |
14
+ | `~/.cursor/mcp.json` | `agent-auth/cursor-mcp.json` | `~/.cursor/mcp.json` |
15
+ | `~/.claude.json` | `agent-auth/claude.json` | `~/.claude.json` |
16
+
17
+ To add more agent-auth files, extend the `AGENT_SECRET_FILES` array near the
18
+ top of `backup.sh` and add the matching entry to `AGENT_AUTH_MAP` in
19
+ `restore.sh`.
6
20
 
7
21
  ---
8
22
 
@@ -21,7 +35,7 @@ bash cli/scripts/env-vault/backup.sh
21
35
 
22
36
  This writes two files to `.env-vault/`:
23
37
  - `env-vault-<date>.tar.<ext>` — encrypted archive (safe to transport)
24
- - `manifest-<date>.txt` — variable names + counts only (safe to read, no values)
38
+ - `manifest-<date>.txt` — variable names + counts + agent-auth file paths/sizes only (safe to read, no values)
25
39
 
26
40
  You will be prompted for a passphrase. Store it in **1Password** immediately.
27
41
 
@@ -40,7 +54,7 @@ Do NOT transfer via unencrypted email or public Slack.
40
54
  # Install dependencies first (e.g. Node, pnpm, etc.), then:
41
55
  youmd env restore /path/to/env-vault-<date>.tar.<ext>
42
56
 
43
- # If project dirs already exist with .env.local files:
57
+ # If files already exist on the new Mac:
44
58
  youmd env restore --force /path/to/env-vault-<date>.tar.<ext>
45
59
 
46
60
  # or directly via bash:
@@ -48,8 +62,11 @@ bash cli/scripts/env-vault/restore.sh /path/to/env-vault-<date>.tar.<ext>
48
62
  bash cli/scripts/env-vault/restore.sh --force /path/to/env-vault-<date>.tar.<ext>
49
63
  ```
50
64
 
51
- The script creates `<CODE_2025>/<project>/.env.local` for each project.
52
- Existing files are backed up to `.env.local.bak.<timestamp>` (or overwritten with `--force`).
65
+ The script:
66
+ 1. Creates `<CODE_2025>/<project>/.env.local` for each project.
67
+ 2. Restores agent-auth files to their absolute home paths (`~/.codex/auth.json`, `~/.cursor/mcp.json`, `~/.claude.json`), creating parent directories as needed.
68
+
69
+ Existing files are backed up to `<path>.bak.<timestamp>` before overwrite (or silently overwritten with `--force`).
53
70
 
54
71
  ---
55
72
 
@@ -70,8 +87,8 @@ The encrypted file itself is safe to store anywhere. The passphrase is the secre
70
87
  ## Security Model
71
88
 
72
89
  - **Encryption:** `age` (preferred) → `gpg AES-256` → `openssl AES-256-CBC PBKDF2`
73
- - **What's in the encrypted archive:** full `.env.local` file contents
74
- - **What's in plaintext:** only the project name + variable NAMES + count (the manifest)
90
+ - **What's in the encrypted archive:** full `.env.local` file contents + agent-auth files (`~/.codex/auth.json`, `~/.cursor/mcp.json`, `~/.claude.json`)
91
+ - **What's in plaintext:** only the project name + variable NAMES + count + agent-auth file paths/byte-sizes (the manifest)
75
92
  - **Values:** never printed, echoed, logged, or written anywhere outside the encrypted blob
76
93
  - **Passphrase:** stored nowhere by these scripts — you must store it in 1Password
77
94
 
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env bash
2
- # env-vault/backup.sh — backs up all .env.local files under CODE_2025 into an
3
- # encrypted vault. NEVER prints, echoes, logs, or writes any secret VALUE.
4
- # Only variable NAMES and counts appear in plaintext output.
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 from an encrypted vault
3
- # created by backup.sh. NEVER prints, echoes, or logs any secret VALUES.
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 .env.local files without backing them up
9
- # --root: destination root (default: /Users/houstongolden/Desktop/CODE_2025)
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 .env.local files."
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. Reminder: restore secrets"
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 (~/Desktop/CODE_2025/youmd/cli/scripts/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 "6. Next steps"
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 ==="