jekyll-theme-zer0 1.26.0 → 1.28.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.
Files changed (111) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +558 -5
  3. data/README.md +10 -27
  4. data/_data/authors.yml +4 -3
  5. data/_data/backlog.yml +28 -0
  6. data/_data/consumers.yml +147 -0
  7. data/_data/features.yml +142 -23
  8. data/_data/i18n/fr.yml +98 -0
  9. data/_data/i18n/languages.yml +36 -0
  10. data/_data/i18n/manifest.yml +1502 -0
  11. data/_data/navigation/docs.yml +2 -0
  12. data/_data/navigation/main.yml +6 -0
  13. data/_data/series.yml +19 -0
  14. data/_data/theme-manifest.yml +348 -268
  15. data/_data/ui-text.yml +36 -246
  16. data/_includes/README.md +25 -2
  17. data/_includes/analytics/google-tag-manager-body.html +11 -2
  18. data/_includes/analytics/google-tag-manager-head.html +16 -6
  19. data/_includes/analytics/posthog.html +33 -4
  20. data/_includes/components/abc-letter.html +43 -0
  21. data/_includes/components/author-avatar-url.html +4 -2
  22. data/_includes/components/book-card.html +42 -0
  23. data/_includes/components/book-nav.html +80 -0
  24. data/_includes/components/book-plate.html +31 -0
  25. data/_includes/components/book-toc.html +47 -0
  26. data/_includes/components/bookshelf.html +68 -0
  27. data/_includes/components/card-grid.html +60 -0
  28. data/_includes/components/data-card.html +95 -0
  29. data/_includes/components/env-switcher.html +3 -1
  30. data/_includes/components/halfmoon.html +5 -1
  31. data/_includes/components/language-toggle.html +81 -0
  32. data/_includes/components/page-feedback.html +65 -2
  33. data/_includes/components/search-modal.html +2 -2
  34. data/_includes/components/shortcuts-modal.html +1 -1
  35. data/_includes/components/theme-controls-bar.html +10 -2
  36. data/_includes/components/theme-customizer.html +8 -2
  37. data/_includes/components/translation-notice.html +27 -0
  38. data/_includes/content/intro.html +16 -15
  39. data/_includes/content/seo.html +9 -3
  40. data/_includes/core/color-mode-init.html +13 -4
  41. data/_includes/core/favicon.html +46 -0
  42. data/_includes/core/footer.html +9 -4
  43. data/_includes/core/head.html +18 -0
  44. data/_includes/core/header.html +9 -4
  45. data/_includes/core/hreflang.html +33 -0
  46. data/_includes/core/i18n.html +36 -0
  47. data/_includes/custom/body-end.html +18 -0
  48. data/_includes/custom/body-start.html +17 -0
  49. data/_includes/custom/footer.html +18 -0
  50. data/_includes/custom/head.html +18 -0
  51. data/_includes/navigation/breadcrumbs.html +1 -1
  52. data/_includes/navigation/local-graph.html +28 -2
  53. data/_includes/navigation/nav-tree.html +3 -3
  54. data/_includes/navigation/navbar.html +5 -4
  55. data/_includes/navigation/sidebar-config.html +21 -0
  56. data/_includes/navigation/sidebar-nav.html +4 -0
  57. data/_includes/navigation/sidebar-pagetree.html +150 -0
  58. data/_includes/navigation/sidebar-right.html +3 -2
  59. data/_includes/navigation/unified-drawer.html +9 -3
  60. data/_includes/obsidian/full-graph.html +165 -136
  61. data/_layouts/404.html +260 -0
  62. data/_layouts/README.md +2 -0
  63. data/_layouts/article.html +7 -1
  64. data/_layouts/book-abc.html +106 -0
  65. data/_layouts/book-story.html +91 -0
  66. data/_layouts/book.html +113 -0
  67. data/_layouts/collection.html +17 -5
  68. data/_layouts/default.html +5 -3
  69. data/_layouts/home.html +5 -2
  70. data/_layouts/landing.html +9 -0
  71. data/_layouts/news.html +159 -33
  72. data/_layouts/root.html +29 -9
  73. data/_layouts/section.html +61 -17
  74. data/_sass/components/_book.scss +423 -0
  75. data/_sass/core/_navbar.scss +13 -0
  76. data/_sass/core/_obsidian.scss +295 -7
  77. data/_sass/layouts/_navbar-extras.scss +6 -1
  78. data/_sass/theme/_backgrounds.scss +21 -8
  79. data/assets/css/main.scss +1 -0
  80. data/assets/js/auto-hide-nav.js +5 -1
  81. data/assets/js/halfmoon.js +26 -0
  82. data/assets/js/obsidian-graph.js +707 -265
  83. data/assets/js/obsidian-local-graph.js +161 -54
  84. data/assets/js/search-modal.js +4 -1
  85. data/scripts/README.md +20 -26
  86. data/scripts/bin/audit-consumer +1 -1
  87. data/scripts/bin/manifest +31 -5
  88. data/scripts/bin/sync-plugins +0 -1
  89. data/scripts/bin/validate +5 -1
  90. data/scripts/dev/rasterize-svg.js +65 -0
  91. data/scripts/features/generate-preview-images +49 -1390
  92. data/scripts/features/install-preview-generator +55 -33
  93. data/scripts/install/README.md +55 -25
  94. data/scripts/install/ai/client.sh +302 -93
  95. data/scripts/install/ai/prompts/spec.schema.json +1 -1
  96. data/scripts/install/ai/prompts/wizard.system.md +8 -17
  97. data/scripts/install/ai/wizard.sh +10 -5
  98. data/scripts/install/apply.sh +7 -3
  99. data/scripts/install/cli.sh +54 -4
  100. data/scripts/install/config.sh +167 -0
  101. data/scripts/install/doctor.sh +38 -0
  102. data/scripts/install/plan.sh +10 -0
  103. data/scripts/install/spec.sh +15 -7
  104. data/scripts/install/template.sh +4 -0
  105. data/scripts/lib/README.md +1 -5
  106. data/scripts/lib/install/deploy/README.md +3 -9
  107. data/scripts/lib/preview_generator.py +2261 -1341
  108. data/scripts/propagate.rb +277 -0
  109. data/scripts/translate.rb +1204 -0
  110. metadata +34 -3
  111. data/_plugins/preview_image_generator.rb +0 -351
@@ -63,6 +63,7 @@ _cli_load_core() {
63
63
  _cli_require "fs.sh"
64
64
  _cli_require "template.sh"
65
65
  _cli_require "prompt.sh"
66
+ _cli_require "config.sh"
66
67
  _cli_require "spec.sh"
67
68
  _cli_require "plan.sh"
68
69
  _cli_require "apply.sh"
@@ -95,6 +96,9 @@ _cli_parse_flags() {
95
96
  _FLAG_AGENTS=""
96
97
  _FLAG_TASKS=""
97
98
  _FLAG_AI=0
99
+ _FLAG_AI_PROVIDER=""
100
+ _FLAG_AI_MODEL=""
101
+ _FLAG_CONFIG=""
98
102
  _FLAG_SPEC=""
99
103
  _FLAG_SCRAPE_URL=""
100
104
  _FLAG_SCRAPE_DEPTH=""
@@ -111,6 +115,7 @@ _cli_parse_flags() {
111
115
  _FLAG_SITE_URL _FLAG_SITE_AUTHOR _FLAG_SITE_EMAIL \
112
116
  _FLAG_GITHUB_USER _FLAG_GITHUB_REPO _FLAG_THEME_SOURCE \
113
117
  _FLAG_DEPLOY _FLAG_AGENTS _FLAG_TASKS _FLAG_AI _FLAG_SPEC \
118
+ _FLAG_AI_PROVIDER _FLAG_AI_MODEL _FLAG_CONFIG \
114
119
  _FLAG_SCRAPE_URL _FLAG_SCRAPE_DEPTH _FLAG_SCRAPE_MAX_PAGES
115
120
 
116
121
  while [[ $# -gt 0 ]]; do
@@ -123,6 +128,14 @@ _cli_parse_flags() {
123
128
  --skip-doctor) _FLAG_SKIP_DOCTOR=1 ;;
124
129
  --verbose|-v) _FLAG_VERBOSE=1 ;;
125
130
  --ai) _FLAG_AI=1 ;;
131
+ --yes|-y) _FLAG_AUTO_ACCEPT=1 ;;
132
+ --ai-provider) shift; _FLAG_AI_PROVIDER="${1:-}" ;;
133
+ --ai-provider=*) _FLAG_AI_PROVIDER="${1#--ai-provider=}" ;;
134
+ --ai-model) shift; _FLAG_AI_MODEL="${1:-}" ;;
135
+ --ai-model=*) _FLAG_AI_MODEL="${1#--ai-model=}" ;;
136
+ --config) shift; _FLAG_CONFIG="${1:-}" ;;
137
+ --config=*) _FLAG_CONFIG="${1#--config=}" ;;
138
+ --no-ai) _FLAG_AI_PROVIDER="none" ;;
126
139
  --profile) shift; _FLAG_PROFILE="${1:-}" ;;
127
140
  --profile=*) _FLAG_PROFILE="${1#--profile=}" ;;
128
141
  --output) shift; _FLAG_OUTPUT="${1:-}" ;;
@@ -241,6 +254,22 @@ _cmd_wizard() {
241
254
  fi
242
255
  }
243
256
 
257
+ _cmd_suggest() {
258
+ # install suggest [TARGET_DIR] [GOAL...]
259
+ # Recommends a profile + deploy target from an optional goal description.
260
+ # Uses the resolved AI provider; falls back to rule-based defaults.
261
+ local target="${_CLI_POS_0:-$(pwd)}"
262
+ local goal="${_CLI_POS_1:-}"
263
+ local sug="${_CLI_DIR}/ai/suggest.sh"
264
+ if [[ ! -f "$sug" ]]; then
265
+ log_error "suggest: module not available: $sug"
266
+ return 1
267
+ fi
268
+ # shellcheck source=/dev/null
269
+ source "$sug"
270
+ ai_suggest_run "$target" "$goal"
271
+ }
272
+
244
273
  _cmd_agents() {
245
274
  local target="${_CLI_TARGET:-$(pwd)}"
246
275
  local spec_file
@@ -299,8 +328,12 @@ _cmd_deploy() {
299
328
  fi
300
329
 
301
330
  # Caller explicitly asked for this deploy target — override spec defaults.
331
+ # Deploy is deploy-only: no base tasks and no agent rewrites (use
332
+ # `install agents` for those). SPEC_TASKS="" now serialises to [] rather
333
+ # than falling back to the full default list (which would clobber content).
302
334
  SPEC_DEPLOY="$target_name"
303
- SPEC_TASKS="" # only run deploy plugins
335
+ SPEC_TASKS=""
336
+ SPEC_AGENTS=""
304
337
 
305
338
  spec_write "$spec_file"
306
339
  apply_run "$spec_file"
@@ -460,9 +493,10 @@ Usage:
460
493
  Subcommands:
461
494
  init Install from flags + profile (default)
462
495
  wizard Interactive wizard (--ai for AI-driven spec)
496
+ suggest Recommend a profile + deploy target (AI or rule-based)
463
497
  agents Install AI agent files only
464
498
  deploy Configure deployment plugin(s)
465
- doctor Run pre-install health checks
499
+ doctor Run pre-install health checks (incl. AI provider status)
466
500
  scrape Crawl an existing site into ./.zer0/scrape (no install)
467
501
  diagnose Diagnose an existing install (--ai for suggestions)
468
502
  upgrade Re-apply spec to an existing install
@@ -494,7 +528,12 @@ Global options:
494
528
  --skip-doctor Skip pre-install health checks
495
529
  --verbose, -v Extra debug output
496
530
  --output json|human Log format (default: human)
531
+ --yes, -y Auto-confirm all yes/no prompts (alias of --auto-accept)
532
+ --config FILE Load installer settings from a YAML config file
497
533
  --ai Use AI assistant for wizard/diagnose
534
+ --ai-provider NAME AI provider: auto|claude-cli|anthropic|openai|none
535
+ --ai-model NAME Override the AI model id for the active provider
536
+ --no-ai Disable AI (equivalent to --ai-provider none)
498
537
  --spec FILE Load spec from FILE instead of detecting
499
538
  --scrape URL Import content from URL during init (adds 'scrape' task)
500
539
  --scrape-depth N Max crawl depth (default: 2)
@@ -503,9 +542,12 @@ Global options:
503
542
  Examples:
504
543
  install init . --profile blog --site-title "My Blog"
505
544
  install init ~/mysite --profile github-pages --github-user bamr87
506
- install wizard . --ai
545
+ install wizard . --ai # uses Claude Code OAuth if available
546
+ install wizard . --ai --ai-provider anthropic
547
+ install suggest . "a docs site for a Rust CLI"
548
+ install init . --config zer0.install.yml
507
549
  install agents . --copilot --claude
508
- install doctor .
550
+ install doctor . # includes AI provider status
509
551
  install diff .
510
552
  install upgrade . --force
511
553
  install plan . --profile docs
@@ -537,9 +579,17 @@ cli_main() {
537
579
  [[ "$_FLAG_VERBOSE" == "1" ]] && _LOG_VERBOSE=1 && export _LOG_VERBOSE
538
580
  [[ -n "$_FLAG_OUTPUT" ]] && _LOG_OUTPUT="$_FLAG_OUTPUT" && export _LOG_OUTPUT
539
581
 
582
+ # Propagate AI selection + config path to the environment so provider
583
+ # resolution and config discovery see them regardless of the spec flow
584
+ # (e.g. `doctor`, `suggest`, `wizard --ai`).
585
+ [[ -n "$_FLAG_AI_PROVIDER" ]] && export ZER0_AI_PROVIDER="$_FLAG_AI_PROVIDER"
586
+ [[ -n "$_FLAG_AI_MODEL" ]] && export ZER0_AI_MODEL="$_FLAG_AI_MODEL"
587
+ [[ -n "$_FLAG_CONFIG" ]] && export ZER0_CONFIG="$_FLAG_CONFIG"
588
+
540
589
  case "$subcommand" in
541
590
  init) _cmd_init ;;
542
591
  wizard) _cmd_wizard ;;
592
+ suggest) _cmd_suggest ;;
543
593
  agents) _cmd_agents ;;
544
594
  deploy) _cmd_deploy ;;
545
595
  doctor) _cmd_doctor ;;
@@ -0,0 +1,167 @@
1
+ #!/bin/bash
2
+ # =============================================================================
3
+ # scripts/install/config.sh — User config-file layer
4
+ # =============================================================================
5
+ # Lets a site persist its installer choices in a small YAML file instead of
6
+ # re-typing flags every run. Discovered files are merged low→high precedence:
7
+ #
8
+ # 1. ~/.config/zer0/install.yml (user-global defaults)
9
+ # 2. <target>/zer0.install.yml (project-local)
10
+ # 3. <target>/.zer0/config.yml (project-local, hidden)
11
+ # 4. $ZER0_CONFIG / --config FILE (explicit — wins over the rest)
12
+ #
13
+ # In the plan pipeline this sits between profile defaults and env/flag
14
+ # overrides: defaults < profile < CONFIG FILE < env vars < CLI flags.
15
+ #
16
+ # Recognised keys (nested or flat/dotted both work):
17
+ # profile
18
+ # site.title|description|author|email|url|timezone|locale
19
+ # github.user|repo|pages_branch|enable_pages
20
+ # theme.source|version
21
+ # deploy (scalar or list → space-joined)
22
+ # agents (scalar or list → space-joined)
23
+ # tasks (scalar or list → space-joined)
24
+ # ai.provider (auto|claude-cli|anthropic|openai|none)
25
+ # ai.model
26
+ #
27
+ # API keys are deliberately NOT read from config files — they stay in the
28
+ # environment only (see ai/client.sh).
29
+ #
30
+ # Bash 3.2 compatible. No `declare -A`, no `set -euo pipefail` here.
31
+ # =============================================================================
32
+ [[ -n "${_HAS_CONFIG_LIB:-}" ]] && return 0
33
+ _HAS_CONFIG_LIB=1
34
+
35
+ # ---------------------------------------------------------------------------
36
+ # config_discover TARGET_DIR → print existing config files, low→high priority.
37
+ # ---------------------------------------------------------------------------
38
+ config_discover() {
39
+ local target="${1:-$(pwd)}"
40
+ local f
41
+ for f in \
42
+ "${HOME:-/nonexistent}/.config/zer0/install.yml" \
43
+ "${target}/zer0.install.yml" \
44
+ "${target}/zer0.install.yaml" \
45
+ "${target}/.zer0/config.yml" \
46
+ "${_FLAG_CONFIG:-}" \
47
+ "${ZER0_CONFIG:-}" ; do
48
+ [[ -n "$f" && -f "$f" ]] && printf '%s\n' "$f"
49
+ done
50
+ }
51
+
52
+ # ---------------------------------------------------------------------------
53
+ # _config_flatten FILE → emit `key=value` for a small YAML subset:
54
+ # - top-level scalars foo: bar → foo=bar
55
+ # - one level of nested maps site:\n title: x → site.title=x
56
+ # - block lists deploy:\n - a → deploy=a b
57
+ # - inline flow lists deploy: [a, b] → deploy=a b
58
+ # Quotes and inline comments are stripped. Not a full YAML parser — just
59
+ # enough for installer config, mirroring plan.sh's awk profile reader.
60
+ # ---------------------------------------------------------------------------
61
+ _config_flatten() {
62
+ local file="$1"
63
+ [[ -f "$file" ]] || return 0
64
+ awk '
65
+ function strip(s) {
66
+ sub(/[[:space:]]*#.*$/, "", s) # trailing comment
67
+ sub(/^[[:space:]]+/, "", s); sub(/[[:space:]]+$/, "", s)
68
+ gsub(/^["'"'"']|["'"'"']$/, "", s) # surrounding quotes
69
+ return s
70
+ }
71
+ # blank / comment lines
72
+ /^[[:space:]]*$/ { next }
73
+ /^[[:space:]]*#/ { next }
74
+ {
75
+ indent = 0
76
+ tmp = $0
77
+ while (substr(tmp,1,1) == " ") { indent++; tmp = substr(tmp,2) }
78
+ }
79
+ # list item under the current parent key
80
+ indent > 0 && tmp ~ /^-[[:space:]]+/ {
81
+ if (listkey != "") {
82
+ item = tmp; sub(/^-[[:space:]]+/, "", item); item = strip(item)
83
+ vals[listkey] = (vals[listkey] == "" ? item : vals[listkey] " " item)
84
+ }
85
+ next
86
+ }
87
+ # key: value (any depth we care about is 0 or 1)
88
+ match(tmp, /^[A-Za-z0-9_.-]+[[:space:]]*:/) {
89
+ key = tmp; sub(/[[:space:]]*:.*$/, "", key)
90
+ val = tmp; sub(/^[A-Za-z0-9_.-]+[[:space:]]*:/, "", val); val = strip(val)
91
+
92
+ if (indent == 0) {
93
+ parent = key
94
+ listkey = ""
95
+ if (val == "") { listkey = key; next } # map/list header
96
+ # inline flow list? key: [a, b, c]
97
+ if (val ~ /^\[.*\]$/) {
98
+ gsub(/^\[|\]$/, "", val); gsub(/[[:space:],]+/, " ", val)
99
+ val = strip(val)
100
+ }
101
+ print key "=" val
102
+ } else {
103
+ # nested child of the last top-level parent
104
+ if (parent != "") print parent "." key "=" val
105
+ }
106
+ next
107
+ }
108
+ END {
109
+ for (k in vals) print k "=" vals[k]
110
+ }
111
+ ' "$file"
112
+ }
113
+
114
+ # ---------------------------------------------------------------------------
115
+ # config_apply_file FILE → map recognised keys onto SPEC_* globals + AI env.
116
+ # ---------------------------------------------------------------------------
117
+ config_apply_file() {
118
+ local file="$1"
119
+ [[ -f "$file" ]] || return 0
120
+ log_debug "config: loading ${file}"
121
+
122
+ local line key val
123
+ while IFS= read -r line; do
124
+ key="${line%%=*}"
125
+ val="${line#*=}"
126
+ [[ -z "$key" || "$key" == "$line" ]] && continue
127
+ case "$key" in
128
+ profile) SPEC_PROFILE="$val" ;;
129
+ site.title|site_title) SPEC_SITE_TITLE="$val" ;;
130
+ site.description|site_description) SPEC_SITE_DESCRIPTION="$val" ;;
131
+ site.author|site_author) SPEC_SITE_AUTHOR="$val" ;;
132
+ site.email|site_email) SPEC_SITE_EMAIL="$val" ;;
133
+ site.url|site_url) SPEC_SITE_URL="$val" ;;
134
+ site.timezone|site_timezone) SPEC_SITE_TIMEZONE="$val" ;;
135
+ site.locale|site_locale) SPEC_SITE_LOCALE="$val" ;;
136
+ github.user|github_user) SPEC_GITHUB_USER="$val" ;;
137
+ github.repo|github_repo) SPEC_GITHUB_REPO="$val" ;;
138
+ github.pages_branch) SPEC_GITHUB_PAGES_BRANCH="$val" ;;
139
+ github.enable_pages) SPEC_GITHUB_ENABLE_PAGES="$val" ;;
140
+ theme.source|theme_source) SPEC_THEME_SOURCE="$val" ;;
141
+ theme.version|theme_version) SPEC_THEME_VERSION="$val" ;;
142
+ deploy|deploy_targets) SPEC_DEPLOY="$val" ;;
143
+ agents|agent_files) SPEC_AGENTS="$val" ;;
144
+ tasks) SPEC_TASKS="$val" ;;
145
+ ai.provider|ai_provider)
146
+ SPEC_AI_PROVIDER="$val"
147
+ export ZER0_AI_PROVIDER="${ZER0_AI_PROVIDER:-$val}" ;;
148
+ ai.model|ai_model)
149
+ SPEC_AI_MODEL="$val"
150
+ export ZER0_AI_MODEL="${ZER0_AI_MODEL:-$val}" ;;
151
+ *) log_debug "config: ignoring unknown key '${key}'" ;;
152
+ esac
153
+ done < <(_config_flatten "$file")
154
+ return 0
155
+ }
156
+
157
+ # ---------------------------------------------------------------------------
158
+ # config_load TARGET_DIR → apply every discovered config file in order.
159
+ # ---------------------------------------------------------------------------
160
+ config_load() {
161
+ local target="${1:-$(pwd)}"
162
+ local f
163
+ while IFS= read -r f; do
164
+ [[ -n "$f" ]] && config_apply_file "$f"
165
+ done < <(config_discover "$target")
166
+ return 0
167
+ }
@@ -118,6 +118,43 @@ doctor_check_gh() {
118
118
  _doctor_pass "GitHub CLI $ghver"
119
119
  }
120
120
 
121
+ # ---------------------------------------------------------------------------
122
+ # doctor_check_ai — report which AI provider (if any) will serve wizard/
123
+ # suggest/diagnose. Warn-only: AI is always opt-in and never blocks install.
124
+ # ---------------------------------------------------------------------------
125
+ doctor_check_ai() {
126
+ if [[ "$(type -t ai_client_provider)" != "function" ]]; then
127
+ local _d
128
+ _d="${_CLI_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" 2>/dev/null && pwd)}"
129
+ [[ -f "${_d}/ai/client.sh" ]] && source "${_d}/ai/client.sh"
130
+ fi
131
+ if [[ "$(type -t ai_client_provider)" != "function" ]]; then
132
+ _doctor_warn "AI: client module unavailable (wizard/suggest/diagnose disabled)"
133
+ return 0
134
+ fi
135
+
136
+ if [[ "${ZER0_NO_AI:-0}" == "1" ]]; then
137
+ _doctor_pass "AI: disabled via ZER0_NO_AI (rule-based fallbacks only)"
138
+ return 0
139
+ fi
140
+ case "${ZER0_AI_PROVIDER:-}" in
141
+ none|off|disabled)
142
+ _doctor_pass "AI: disabled by choice (rule-based fallbacks only)"
143
+ return 0 ;;
144
+ esac
145
+
146
+ local provider
147
+ provider="$(ai_client_provider)"
148
+ if [[ "$provider" == "none" ]] || ! ai_client_available; then
149
+ _doctor_warn "AI: no provider available — install the 'claude' CLI (Claude Code OAuth), or set CLAUDE_CODE_OAUTH_TOKEN / ANTHROPIC_API_KEY / OPENAI_API_KEY"
150
+ return 0
151
+ fi
152
+
153
+ local model
154
+ model="$(ai_client_model "$provider")"
155
+ _doctor_pass "AI: ${provider} via $(ai_client_auth_source)${model:+ (model ${model})}"
156
+ }
157
+
121
158
  # ---------------------------------------------------------------------------
122
159
  # doctor_check_writable TARGET_DIR
123
160
  # ---------------------------------------------------------------------------
@@ -155,6 +192,7 @@ doctor_run() {
155
192
  doctor_check_docker
156
193
  doctor_check_git || failures=$(( failures + 1 ))
157
194
  doctor_check_gh
195
+ doctor_check_ai
158
196
  [[ -n "$target" ]] && { doctor_check_writable "$target" || failures=$(( failures + 1 )); }
159
197
 
160
198
  if [[ $failures -eq 0 ]]; then
@@ -144,6 +144,8 @@ plan_apply_flags() {
144
144
  [[ -n "${_FLAG_DEPLOY:-}" ]] && SPEC_DEPLOY="$_FLAG_DEPLOY"
145
145
  [[ -n "${_FLAG_AGENTS:-}" ]] && SPEC_AGENTS="$_FLAG_AGENTS"
146
146
  [[ -n "${_FLAG_TASKS:-}" ]] && SPEC_TASKS="$_FLAG_TASKS"
147
+ [[ -n "${_FLAG_AI_PROVIDER:-}" ]] && SPEC_AI_PROVIDER="$_FLAG_AI_PROVIDER"
148
+ [[ -n "${_FLAG_AI_MODEL:-}" ]] && SPEC_AI_MODEL="$_FLAG_AI_MODEL"
147
149
 
148
150
  # Booleans — flags are set to "1" when present
149
151
  [[ "${_FLAG_DRY_RUN:-0}" == "1" ]] && SPEC_OPT_DRY_RUN=true
@@ -231,12 +233,19 @@ plan_build() {
231
233
  # Layer 1: Profile defaults
232
234
  [[ -n "$profile_file" ]] && plan_load_profile "$profile_file"
233
235
 
236
+ # Layer 1.5: User config file(s) — override profile, sit below env/flags.
237
+ if [[ "$(type -t config_load)" == "function" ]]; then
238
+ config_load "$target_dir"
239
+ fi
240
+
234
241
  # Layer 2: Environment variable overrides (ZER0_SITE_* etc.)
235
242
  [[ -n "${ZER0_SITE_TITLE:-}" ]] && SPEC_SITE_TITLE="$ZER0_SITE_TITLE"
236
243
  [[ -n "${ZER0_SITE_AUTHOR:-}" ]] && SPEC_SITE_AUTHOR="$ZER0_SITE_AUTHOR"
237
244
  [[ -n "${ZER0_SITE_EMAIL:-}" ]] && SPEC_SITE_EMAIL="$ZER0_SITE_EMAIL"
238
245
  [[ -n "${ZER0_GITHUB_USER:-}" ]] && SPEC_GITHUB_USER="$ZER0_GITHUB_USER"
239
246
  [[ -n "${ZER0_GITHUB_REPO:-}" ]] && SPEC_GITHUB_REPO="$ZER0_GITHUB_REPO"
247
+ [[ -n "${ZER0_AI_PROVIDER:-}" ]] && SPEC_AI_PROVIDER="$ZER0_AI_PROVIDER"
248
+ [[ -n "${ZER0_AI_MODEL:-}" ]] && SPEC_AI_MODEL="$ZER0_AI_MODEL"
240
249
 
241
250
  # Layer 3: CLI flags (highest priority)
242
251
  plan_apply_flags
@@ -271,6 +280,7 @@ plan_build() {
271
280
  SPEC_OPT_DRY_RUN SPEC_OPT_FORCE SPEC_OPT_BACKUP \
272
281
  SPEC_OPT_NON_INTERACTIVE SPEC_OPT_OUTPUT SPEC_OPT_AUTO_ACCEPT \
273
282
  SPEC_OPT_SKIP_DOCTOR SPEC_OPT_VERBOSE \
283
+ SPEC_AI_PROVIDER SPEC_AI_MODEL \
274
284
  SITE_TITLE SITE_DESCRIPTION SITE_AUTHOR SITE_EMAIL SITE_URL \
275
285
  SITE_TIMEZONE SITE_LOCALE GITHUB_USER GITHUB_REPO \
276
286
  GITHUB_PAGES_BRANCH THEME_SOURCE REPOSITORY_NAME INSTALL_PROFILE
@@ -85,7 +85,7 @@ spec_default() {
85
85
  },
86
86
  "ai": {
87
87
  "used": false,
88
- "provider": "openai",
88
+ "provider": "auto",
89
89
  "model": "",
90
90
  "tokens_estimated": 0,
91
91
  "spec_hash": ""
@@ -108,10 +108,18 @@ spec_write() {
108
108
  local out_file="$1"
109
109
  local dry_run="${_FS_DRY_RUN:-0}"
110
110
 
111
- # Build tasks array from space-separated SPEC_TASKS
111
+ # Build tasks array from space-separated SPEC_TASKS.
112
+ # Distinguish UNSET (→ default task list) from EMPTY (→ [], e.g. a
113
+ # deploy-only run). Using `${SPEC_TASKS:-default}` here would turn an
114
+ # intentional empty list back into the full default and clobber content.
112
115
  local tasks_json=""
113
- local t
114
- for t in ${SPEC_TASKS:-config gemfile docker pages nav data gitignore readme marker}; do
116
+ local t _tasks_src
117
+ if [[ -z "${SPEC_TASKS+set}" ]]; then
118
+ _tasks_src="config gemfile docker pages nav data gitignore readme marker"
119
+ else
120
+ _tasks_src="${SPEC_TASKS}"
121
+ fi
122
+ for t in ${_tasks_src}; do
115
123
  tasks_json="${tasks_json}\"${t}\","
116
124
  done
117
125
  tasks_json="[${tasks_json%,}]"
@@ -168,7 +176,7 @@ spec_write() {
168
176
  },
169
177
  "ai": {
170
178
  "used": ${SPEC_AI_USED:-false},
171
- "provider": "${SPEC_AI_PROVIDER:-openai}",
179
+ "provider": "${SPEC_AI_PROVIDER:-auto}",
172
180
  "model": "${SPEC_AI_MODEL:-}",
173
181
  "tokens_estimated": ${SPEC_AI_TOKENS:-0},
174
182
  "spec_hash": ""
@@ -248,7 +256,7 @@ _spec_read_jq() {
248
256
  SPEC_OPT_SKIP_DOCTOR=$(jq -r '.options.skip_doctor // false' "$f")
249
257
  SPEC_OPT_VERBOSE=$(jq -r '.options.verbose // false' "$f")
250
258
  SPEC_AI_USED=$(jq -r '.ai.used // false' "$f")
251
- SPEC_AI_PROVIDER=$(jq -r '.ai.provider // "openai"' "$f")
259
+ SPEC_AI_PROVIDER=$(jq -r '.ai.provider // "auto"' "$f")
252
260
  SPEC_AI_MODEL=$(jq -r '.ai.model // ""' "$f")
253
261
  SPEC_AI_TOKENS=$(jq -r '.ai.tokens_estimated // 0' "$f")
254
262
  SPEC_SCRAPE_SOURCE_URL=$(jq -r '.scrape.source_url // ""' "$f")
@@ -369,7 +377,7 @@ _spec_read_awk() {
369
377
  SPEC_OPT_SKIP_DOCTOR="${SPEC_OPT_SKIP_DOCTOR:-false}"
370
378
  SPEC_OPT_VERBOSE="${SPEC_OPT_VERBOSE:-false}"
371
379
  SPEC_AI_USED="${SPEC_AI_USED:-false}"
372
- SPEC_AI_PROVIDER="${SPEC_AI_PROVIDER:-openai}"
380
+ SPEC_AI_PROVIDER="${SPEC_AI_PROVIDER:-auto}"
373
381
  SPEC_AI_MODEL="${SPEC_AI_MODEL:-}"
374
382
  SPEC_AI_TOKENS="${SPEC_AI_TOKENS:-0}"
375
383
  SPEC_SCRAPE_SOURCE_URL="${SPEC_SCRAPE_SOURCE_URL:-$(awk '/"source_url"/ { gsub(/.*"source_url"[[:space:]]*:[[:space:]]*"/, ""); gsub(/".*/, ""); print; exit }' "$f" 2>/dev/null)}"
@@ -61,6 +61,7 @@ tmpl_render() {
61
61
  -e "s|{{THEME_DISPLAY_NAME}}|${THEME_DISPLAY_NAME:-Zer0-Mistakes}|g" \
62
62
  -e "s|{{THEME_VERSION}}|${THEME_VERSION:-}|g" \
63
63
  -e "s|{{THEME_SOURCE}}|${THEME_SOURCE:-gem}|g" \
64
+ -e "s|{{THEME_REMOTE}}|${THEME_REMOTE:-bamr87/zer0-mistakes}|g" \
64
65
  -e "s|{{GITHUB_USER}}|${GITHUB_USER:-}|g" \
65
66
  -e "s|{{FORK_GITHUB_USER}}|${GITHUB_USER:-}|g" \
66
67
  -e "s|{{GITHUB_REPO}}|${GITHUB_REPO:-}|g" \
@@ -72,6 +73,7 @@ tmpl_render() {
72
73
  -e "s|{{REMOTE_BRANCH}}|${REMOTE_BRANCH:-${GITHUB_PAGES_BRANCH:-gh-pages}}|g" \
73
74
  -e "s|{{REPOSITORY_NAME}}|${REPOSITORY_NAME:-${GITHUB_REPO:-my-site}}|g" \
74
75
  -e "s|{{SITE_TITLE}}|${SITE_TITLE:-My Jekyll Site}|g" \
76
+ -e "s|{{SITE_NAME}}|${SITE_TITLE:-My Jekyll Site}|g" \
75
77
  -e "s|{{SITE_DESCRIPTION}}|${SITE_DESCRIPTION:-A Jekyll site built with zer0-mistakes}|g" \
76
78
  -e "s|{{SITE_AUTHOR}}|${SITE_AUTHOR:-Site Author}|g" \
77
79
  -e "s|{{SITE_EMAIL}}|${SITE_EMAIL:-}|g" \
@@ -87,6 +89,8 @@ tmpl_render() {
87
89
  -e "s|{{COMMONMARKER_MACOS_VERSION}}|${COMMONMARKER_MACOS_VERSION:-~> 0.23}|g" \
88
90
  -e "s|{{GITHUB_PAGES_MAX_VERSION}}|${GITHUB_PAGES_MAX_VERSION:-232}|g" \
89
91
  -e "s|{{RUBY_MIN_VERSION_MACOS}}|${RUBY_MIN_VERSION_MACOS:-2.6.0}|g" \
92
+ -e "s|{{DEFAULT_BRANCH}}|${DEFAULT_BRANCH:-main}|g" \
93
+ -e "s|{{RUBY_VERSION}}|${RUBY_VERSION:-3.3}|g" \
90
94
  -e "s|{{INSTALL_PROFILE}}|${INSTALL_PROFILE:-default}|g" \
91
95
  -e "s|{{INSTALL_MODE}}|${INSTALL_MODE:-full}|g" \
92
96
  -e "s|{{CURRENT_DATE}}|$(date +%Y-%m-%d)|g" \
@@ -49,11 +49,7 @@ validate_environment false false # skip_publish=false, require_gh=false
49
49
 
50
50
  ### ✅ `scripts/bin/validate` - Preflight Validation
51
51
 
52
- Canonical command for local and CI preflight checks. It composes the release
53
- validation helpers with project-specific checks for version consistency, YAML
54
- configuration/data files, active configuration contracts, config-file
55
- classification, navigation data shape, Jekyll build/doctor, compiled assets,
56
- and optional test suites.
52
+ Canonical command for local and CI preflight checks. It composes the release validation helpers with project-specific checks for version consistency, YAML configuration/data files, active configuration contracts, config-file classification, navigation data shape, Jekyll build/doctor, compiled assets, and optional test suites.
57
53
 
58
54
  **Usage:**
59
55
 
@@ -1,8 +1,6 @@
1
1
  # scripts/lib/install/deploy/
2
2
 
3
- Pluggable deploy-target modules consumed by `scripts/bin/install deploy`
4
- (Phase 4 of the installer refactor). Each module configures one target;
5
- the registry coordinates discovery, dispatch, and verification.
3
+ Pluggable deploy-target modules consumed by `scripts/bin/install deploy` (Phase 4 of the installer refactor). Each module configures one target; the registry coordinates discovery, dispatch, and verification.
6
4
 
7
5
  ## Files
8
6
 
@@ -26,16 +24,12 @@ Every module must define:
26
24
  | `deploy_<slug>_verify <dir>` | Confirm expected files exist + look correct. |
27
25
  | `deploy_<slug>_doc_url` | Print the canonical upstream documentation URL. |
28
26
 
29
- Modules use the lightweight `deploy_render` placeholder set
30
- (`{{RUBY_VERSION}}`, `{{DEFAULT_BRANCH}}`, `{{GITHUB_USER}}`,
31
- `{{SITE_NAME}}`) so they can run without the full install.sh global
32
- environment.
27
+ Modules use the lightweight `deploy_render` placeholder set (`{{RUBY_VERSION}}`, `{{DEFAULT_BRANCH}}`, `{{GITHUB_USER}}`, `{{SITE_NAME}}`) so they can run without the full install.sh global environment.
33
28
 
34
29
  ## Adding a target
35
30
 
36
31
  1. Add `templates/deploy/<slug>/` with the assets (workflow YAML,
37
- Dockerfile, README, etc.). Use `*.template` for files that need
38
- variable substitution.
32
+ Dockerfile, README, etc.). Use `*.template` for files that need variable substitution.
39
33
  2. Create `scripts/lib/install/deploy/<slug>.sh` exporting the four
40
34
  hooks above.
41
35
  3. Add `<slug>` to `DEPLOY_TARGETS_LIST` in `registry.sh` (alphabetical).