jekyll-theme-zer0 1.8.2 → 1.9.1

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +33 -3
  3. data/README.md +98 -7
  4. data/_data/content_statistics.yml +253 -251
  5. data/_includes/components/nav-export.html +61 -0
  6. data/_includes/components/nav-overview.html +54 -0
  7. data/scripts/bin/install +52 -705
  8. data/scripts/github-setup.sh +0 -0
  9. data/scripts/install/README.md +162 -0
  10. data/scripts/install/ai/client.sh +164 -0
  11. data/scripts/install/ai/diagnose.sh +81 -0
  12. data/scripts/install/ai/prompts/diagnose.system.md +42 -0
  13. data/scripts/install/ai/prompts/spec.schema.json +129 -0
  14. data/scripts/install/ai/prompts/suggest.system.md +43 -0
  15. data/scripts/install/ai/prompts/wizard.system.md +142 -0
  16. data/scripts/install/ai/suggest.sh +57 -0
  17. data/scripts/install/ai/wizard.sh +150 -0
  18. data/scripts/install/apply.sh +156 -0
  19. data/scripts/install/cli.sh +561 -0
  20. data/scripts/install/diff.sh +128 -0
  21. data/scripts/install/doctor.sh +168 -0
  22. data/scripts/install/fs.sh +138 -0
  23. data/scripts/install/log.sh +119 -0
  24. data/scripts/install/plan.sh +299 -0
  25. data/scripts/install/platform.sh +122 -0
  26. data/scripts/install/prompt.sh +124 -0
  27. data/scripts/install/repair.sh +45 -0
  28. data/scripts/install/scrape.sh +535 -0
  29. data/scripts/install/scrape_html.py +764 -0
  30. data/scripts/install/spec.sh +486 -0
  31. data/scripts/install/tasks/_registry.sh +65 -0
  32. data/scripts/install/tasks/agents.sh +60 -0
  33. data/scripts/install/tasks/config.sh +37 -0
  34. data/scripts/install/tasks/data.sh +18 -0
  35. data/scripts/install/tasks/deploy_azure-swa.sh +17 -0
  36. data/scripts/install/tasks/deploy_docker-prod.sh +21 -0
  37. data/scripts/install/tasks/deploy_github-pages.sh +18 -0
  38. data/scripts/install/tasks/devcontainer.sh +26 -0
  39. data/scripts/install/tasks/docker.sh +29 -0
  40. data/scripts/install/tasks/gemfile.sh +42 -0
  41. data/scripts/install/tasks/gitignore.sh +26 -0
  42. data/scripts/install/tasks/marker.sh +46 -0
  43. data/scripts/install/tasks/nav.sh +18 -0
  44. data/scripts/install/tasks/pages.sh +61 -0
  45. data/scripts/install/tasks/readme.sh +27 -0
  46. data/scripts/install/tasks/scrape.sh +348 -0
  47. data/scripts/install/template.sh +138 -0
  48. data/scripts/install/tui.sh +110 -0
  49. data/scripts/install/upgrade.sh +49 -0
  50. data/scripts/lib/install/template.sh +1 -0
  51. metadata +49 -6
@@ -0,0 +1,561 @@
1
+ #!/bin/bash
2
+ # =============================================================================
3
+ # scripts/install/cli.sh — Subcommand dispatcher
4
+ # =============================================================================
5
+ # Entry point for all installer commands. Sources needed modules and
6
+ # routes to the appropriate handler based on the first positional argument.
7
+ #
8
+ # Usage:
9
+ # cli_main [SUBCOMMAND] [OPTIONS] [TARGET_DIR]
10
+ #
11
+ # Subcommands:
12
+ # init Build spec from flags, run doctor, apply tasks
13
+ # wizard Interactive TUI (--ai flag invokes ai/wizard.sh)
14
+ # agents Install AI agent files only
15
+ # deploy Configure deployment target(s) only
16
+ # doctor Run health checks only
17
+ # diagnose AI-assisted diagnostics
18
+ # upgrade Re-run apply on an existing install
19
+ # diff Show what apply would change
20
+ # plan Print the spec that would be generated (without applying)
21
+ # list-profiles List available profiles
22
+ # list-tasks List available tasks
23
+ # version Print installer version
24
+ # help Print usage
25
+ #
26
+ # Global flags (accepted by all subcommands):
27
+ # --dry-run No filesystem writes
28
+ # --force Overwrite existing files
29
+ # --no-backup Skip file backups
30
+ # --non-interactive No prompts; use defaults
31
+ # --auto-accept Auto-confirm all prompts
32
+ # --skip-doctor Skip pre-install health checks
33
+ # --verbose Extra debug output
34
+ # --output json|human Log format (default: human)
35
+ # --profile NAME Installation profile
36
+ #
37
+ # Bash 3.2 compatible. No set -euo pipefail here.
38
+ # =============================================================================
39
+ [[ -n "${_HAS_CLI_LIB:-}" ]] && return 0
40
+ _HAS_CLI_LIB=1
41
+
42
+ _CLI_VERSION="2.0.0"
43
+
44
+ # ---- module root --------------------------------------------------------
45
+ _CLI_DIR="${_CLI_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" 2>/dev/null && pwd)}"
46
+
47
+ # ---- source helpers -------------------------------------------------------
48
+ _cli_require() {
49
+ local mod="$1"
50
+ local f="${_CLI_DIR}/${mod}"
51
+ if [[ -f "$f" ]]; then
52
+ # shellcheck source=/dev/null
53
+ source "$f"
54
+ else
55
+ echo "[ERROR] cli.sh: required module not found: $f" >&2
56
+ return 1
57
+ fi
58
+ }
59
+
60
+ _cli_load_core() {
61
+ _cli_require "log.sh"
62
+ _cli_require "platform.sh"
63
+ _cli_require "fs.sh"
64
+ _cli_require "template.sh"
65
+ _cli_require "prompt.sh"
66
+ _cli_require "spec.sh"
67
+ _cli_require "plan.sh"
68
+ _cli_require "apply.sh"
69
+ _cli_require "diff.sh"
70
+ _cli_require "doctor.sh"
71
+ }
72
+
73
+ # ---------------------------------------------------------------------------
74
+ # Flag parser — sets _FLAG_* and _CLI_SUBCOMMAND, _CLI_TARGET
75
+ # ---------------------------------------------------------------------------
76
+ _cli_parse_flags() {
77
+ _FLAG_PROFILE=""
78
+ _FLAG_DRY_RUN=0
79
+ _FLAG_FORCE=0
80
+ _FLAG_NO_BACKUP=0
81
+ _FLAG_NON_INTERACTIVE=0
82
+ _FLAG_AUTO_ACCEPT=0
83
+ _FLAG_SKIP_DOCTOR=0
84
+ _FLAG_VERBOSE=0
85
+ _FLAG_OUTPUT=""
86
+ _FLAG_SITE_TITLE=""
87
+ _FLAG_SITE_DESC=""
88
+ _FLAG_SITE_URL=""
89
+ _FLAG_SITE_AUTHOR=""
90
+ _FLAG_SITE_EMAIL=""
91
+ _FLAG_GITHUB_USER=""
92
+ _FLAG_GITHUB_REPO=""
93
+ _FLAG_THEME_SOURCE=""
94
+ _FLAG_DEPLOY=""
95
+ _FLAG_AGENTS=""
96
+ _FLAG_TASKS=""
97
+ _FLAG_AI=0
98
+ _FLAG_SPEC=""
99
+ _FLAG_SCRAPE_URL=""
100
+ _FLAG_SCRAPE_DEPTH=""
101
+ _FLAG_SCRAPE_MAX_PAGES=""
102
+ _CLI_TARGET=""
103
+ _CLI_POS_COUNT=0
104
+ _CLI_POS_0=""
105
+ _CLI_POS_1=""
106
+
107
+ # Export flag globals for plan.sh
108
+ export _FLAG_PROFILE _FLAG_DRY_RUN _FLAG_FORCE _FLAG_NO_BACKUP \
109
+ _FLAG_NON_INTERACTIVE _FLAG_AUTO_ACCEPT _FLAG_SKIP_DOCTOR \
110
+ _FLAG_VERBOSE _FLAG_OUTPUT _FLAG_SITE_TITLE _FLAG_SITE_DESC \
111
+ _FLAG_SITE_URL _FLAG_SITE_AUTHOR _FLAG_SITE_EMAIL \
112
+ _FLAG_GITHUB_USER _FLAG_GITHUB_REPO _FLAG_THEME_SOURCE \
113
+ _FLAG_DEPLOY _FLAG_AGENTS _FLAG_TASKS _FLAG_AI _FLAG_SPEC \
114
+ _FLAG_SCRAPE_URL _FLAG_SCRAPE_DEPTH _FLAG_SCRAPE_MAX_PAGES
115
+
116
+ while [[ $# -gt 0 ]]; do
117
+ case "$1" in
118
+ --dry-run) _FLAG_DRY_RUN=1 ;;
119
+ --force) _FLAG_FORCE=1 ;;
120
+ --no-backup) _FLAG_NO_BACKUP=1 ;;
121
+ --non-interactive) _FLAG_NON_INTERACTIVE=1 ;;
122
+ --auto-accept) _FLAG_AUTO_ACCEPT=1 ;;
123
+ --skip-doctor) _FLAG_SKIP_DOCTOR=1 ;;
124
+ --verbose|-v) _FLAG_VERBOSE=1 ;;
125
+ --ai) _FLAG_AI=1 ;;
126
+ --profile) shift; _FLAG_PROFILE="${1:-}" ;;
127
+ --profile=*) _FLAG_PROFILE="${1#--profile=}" ;;
128
+ --output) shift; _FLAG_OUTPUT="${1:-}" ;;
129
+ --output=*) _FLAG_OUTPUT="${1#--output=}" ;;
130
+ --site-title) shift; _FLAG_SITE_TITLE="${1:-}" ;;
131
+ --site-title=*) _FLAG_SITE_TITLE="${1#--site-title=}" ;;
132
+ --site-desc) shift; _FLAG_SITE_DESC="${1:-}" ;;
133
+ --site-desc=*) _FLAG_SITE_DESC="${1#--site-desc=}" ;;
134
+ --site-url) shift; _FLAG_SITE_URL="${1:-}" ;;
135
+ --site-url=*) _FLAG_SITE_URL="${1#--site-url=}" ;;
136
+ --site-author) shift; _FLAG_SITE_AUTHOR="${1:-}" ;;
137
+ --site-author=*) _FLAG_SITE_AUTHOR="${1#--site-author=}" ;;
138
+ --site-email) shift; _FLAG_SITE_EMAIL="${1:-}" ;;
139
+ --site-email=*) _FLAG_SITE_EMAIL="${1#--site-email=}" ;;
140
+ --github-user) shift; _FLAG_GITHUB_USER="${1:-}" ;;
141
+ --github-user=*) _FLAG_GITHUB_USER="${1#--github-user=}" ;;
142
+ --github-repo) shift; _FLAG_GITHUB_REPO="${1:-}" ;;
143
+ --github-repo=*) _FLAG_GITHUB_REPO="${1#--github-repo=}" ;;
144
+ --theme-source) shift; _FLAG_THEME_SOURCE="${1:-}" ;;
145
+ --theme-source=*) _FLAG_THEME_SOURCE="${1#--theme-source=}" ;;
146
+ --deploy) shift; _FLAG_DEPLOY="${1:-}" ;;
147
+ --deploy=*) _FLAG_DEPLOY="${1#--deploy=}" ;;
148
+ --agents) shift; _FLAG_AGENTS="${1:-}" ;;
149
+ --agents=*) _FLAG_AGENTS="${1#--agents=}" ;;
150
+ --tasks) shift; _FLAG_TASKS="${1:-}" ;;
151
+ --tasks=*) _FLAG_TASKS="${1#--tasks=}" ;;
152
+ --spec) shift; _FLAG_SPEC="${1:-}" ;;
153
+ --spec=*) _FLAG_SPEC="${1#--spec=}" ;;
154
+ --scrape) shift; _FLAG_SCRAPE_URL="${1:-}" ;;
155
+ --scrape=*) _FLAG_SCRAPE_URL="${1#--scrape=}" ;;
156
+ --scrape-depth) shift; _FLAG_SCRAPE_DEPTH="${1:-}" ;;
157
+ --scrape-depth=*) _FLAG_SCRAPE_DEPTH="${1#--scrape-depth=}" ;;
158
+ --scrape-max-pages) shift; _FLAG_SCRAPE_MAX_PAGES="${1:-}" ;;
159
+ --scrape-max-pages=*) _FLAG_SCRAPE_MAX_PAGES="${1#--scrape-max-pages=}" ;;
160
+ # Compat: --claude|--cursor|--aider|--copilot|--all (agents subcommand)
161
+ --claude|--cursor|--aider|--copilot|--all)
162
+ local _agent="${1#--}"
163
+ _FLAG_AGENTS="${_FLAG_AGENTS:+$_FLAG_AGENTS }${_agent}"
164
+ ;;
165
+ --*)
166
+ log_warning "Unknown flag: $1 (ignored)"
167
+ ;;
168
+ *)
169
+ # Capture up to two positionals; first also seeds _CLI_TARGET
170
+ # for backwards compatibility with init/wizard/upgrade/etc.
171
+ case "$_CLI_POS_COUNT" in
172
+ 0) _CLI_POS_0="$1"; _CLI_TARGET="$1" ;;
173
+ 1) _CLI_POS_1="$1" ;;
174
+ esac
175
+ _CLI_POS_COUNT=$((_CLI_POS_COUNT + 1))
176
+ ;;
177
+ esac
178
+ shift
179
+ done
180
+
181
+ # Apply verbose immediately
182
+ [[ "$_FLAG_VERBOSE" == "1" ]] && _LOG_VERBOSE=1
183
+
184
+ export _CLI_TARGET
185
+ }
186
+
187
+ # ---------------------------------------------------------------------------
188
+ # Subcommand handlers
189
+ # ---------------------------------------------------------------------------
190
+ _cmd_init() {
191
+ local target="${_CLI_TARGET:-$(pwd)}"
192
+
193
+ plan_build "$target" "${_FLAG_PROFILE:-default}"
194
+
195
+ # Optionally print the spec before applying
196
+ if [[ "$_FLAG_VERBOSE" == "1" ]]; then
197
+ log_debug "--- generated spec ---"
198
+ plan_print >&2
199
+ log_debug "--- end spec ---"
200
+ fi
201
+
202
+ local spec_file
203
+ spec_file="$(spec_path "$target")"
204
+
205
+ # Write spec first (apply.sh will re-read it)
206
+ spec_write "$spec_file"
207
+
208
+ apply_run "$spec_file"
209
+ }
210
+
211
+ _cmd_wizard() {
212
+ local target="${_CLI_TARGET:-$(pwd)}"
213
+
214
+ if [[ "$_FLAG_AI" == "1" ]]; then
215
+ local wizard="${_CLI_DIR}/ai/wizard.sh"
216
+ if [[ -f "$wizard" ]]; then
217
+ # shellcheck source=/dev/null
218
+ source "$wizard"
219
+ if ai_wizard_run "$target"; then
220
+ # Spec has been written; now apply it (unless dry-run)
221
+ local spec_file="$(spec_path "$target")"
222
+ apply_run "$spec_file"
223
+ return $?
224
+ else
225
+ return 1
226
+ fi
227
+ else
228
+ log_error "AI wizard not available: $wizard"
229
+ return 1
230
+ fi
231
+ else
232
+ local tui="${_CLI_DIR}/tui.sh"
233
+ if [[ -f "$tui" ]]; then
234
+ # shellcheck source=/dev/null
235
+ source "$tui"
236
+ tui_run "$target"
237
+ else
238
+ log_warning "Interactive wizard not available. Falling back to 'init'."
239
+ _cmd_init
240
+ fi
241
+ fi
242
+ }
243
+
244
+ _cmd_agents() {
245
+ local target="${_CLI_TARGET:-$(pwd)}"
246
+ local spec_file
247
+
248
+ # Load existing spec if present; otherwise build one with agents-only task
249
+ spec_file="$(spec_path "$target")"
250
+ if [[ -f "$spec_file" ]]; then
251
+ spec_read "$spec_file"
252
+ else
253
+ plan_build "$target"
254
+ fi
255
+
256
+ # Override agents from flag
257
+ [[ -n "${_FLAG_AGENTS:-}" ]] && SPEC_AGENTS="$_FLAG_AGENTS"
258
+ SPEC_TASKS="agents"
259
+
260
+ spec_write "$spec_file"
261
+ apply_run "$spec_file"
262
+ }
263
+
264
+ _cmd_deploy() {
265
+ # Surface: install deploy <TARGET_NAME> [WORKSPACE]
266
+ # - 2 positionals: TARGET = $1, WORKSPACE = $2
267
+ # - 1 positional + --deploy flag: WORKSPACE = $1, TARGET = $_FLAG_DEPLOY
268
+ # - 1 positional only: TARGET = $1, WORKSPACE = $(pwd)
269
+ # - 0 positionals: TARGET = $_FLAG_DEPLOY, WORKSPACE = $(pwd)
270
+ local target_name=""
271
+ local workspace=""
272
+ if [[ "${_CLI_POS_COUNT:-0}" -ge 2 ]]; then
273
+ target_name="$_CLI_POS_0"
274
+ workspace="$_CLI_POS_1"
275
+ elif [[ "${_CLI_POS_COUNT:-0}" -eq 1 ]]; then
276
+ if [[ -n "${_FLAG_DEPLOY:-}" ]]; then
277
+ workspace="$_CLI_POS_0"
278
+ target_name="$_FLAG_DEPLOY"
279
+ else
280
+ target_name="$_CLI_POS_0"
281
+ workspace="$(pwd)"
282
+ fi
283
+ else
284
+ target_name="${_FLAG_DEPLOY:-}"
285
+ workspace="$(pwd)"
286
+ fi
287
+
288
+ [[ -n "$target_name" ]] || { log_error "deploy: target name required (e.g. 'install deploy github-pages')"; return 2; }
289
+
290
+ _CLI_TARGET="$workspace"
291
+ export _CLI_TARGET
292
+ local spec_file
293
+ spec_file="$(spec_path "$workspace")"
294
+
295
+ if [[ -f "$spec_file" ]]; then
296
+ spec_read "$spec_file"
297
+ else
298
+ plan_build "$workspace"
299
+ fi
300
+
301
+ # Caller explicitly asked for this deploy target — override spec defaults.
302
+ SPEC_DEPLOY="$target_name"
303
+ SPEC_TASKS="" # only run deploy plugins
304
+
305
+ spec_write "$spec_file"
306
+ apply_run "$spec_file"
307
+ }
308
+
309
+ _cmd_doctor() {
310
+ local target="${_CLI_TARGET:-$(pwd)}"
311
+ local spec_file="$(spec_path "$target")"
312
+ [[ -f "$spec_file" ]] && spec_read "$spec_file"
313
+ doctor_run "$target"
314
+ }
315
+
316
+ # ---------------------------------------------------------------------------
317
+ # scrape — standalone crawler. Writes scraped corpus only; does NOT apply a
318
+ # full install. Used to preview content or to feed `init --scrape`.
319
+ #
320
+ # Surface:
321
+ # install scrape <URL> [OUT_DIR] [--scrape-depth N] [--scrape-max-pages N]
322
+ # ---------------------------------------------------------------------------
323
+ _cmd_scrape() {
324
+ local url=""
325
+ local out_dir=""
326
+
327
+ # First positional is the URL (override --scrape if both given).
328
+ if [[ "${_CLI_POS_COUNT:-0}" -ge 1 ]]; then
329
+ url="$_CLI_POS_0"
330
+ fi
331
+ [[ -z "$url" && -n "${_FLAG_SCRAPE_URL:-}" ]] && url="$_FLAG_SCRAPE_URL"
332
+
333
+ if [[ "${_CLI_POS_COUNT:-0}" -ge 2 ]]; then
334
+ out_dir="$_CLI_POS_1"
335
+ fi
336
+ [[ -z "$out_dir" ]] && out_dir="$(pwd)/.zer0/scrape"
337
+
338
+ if [[ -z "$url" ]]; then
339
+ log_error "scrape: URL required (e.g. 'install scrape https://example.com')"
340
+ return 2
341
+ fi
342
+
343
+ local scrape_mod="${_CLI_DIR}/scrape.sh"
344
+ if [[ ! -f "$scrape_mod" ]]; then
345
+ log_error "scrape: module not found: $scrape_mod"
346
+ return 1
347
+ fi
348
+ # shellcheck source=/dev/null
349
+ source "$scrape_mod"
350
+
351
+ local depth="${_FLAG_SCRAPE_DEPTH:-2}"
352
+ local max_pages="${_FLAG_SCRAPE_MAX_PAGES:-25}"
353
+
354
+ scrape_run "$url" "$out_dir" "$depth" "$max_pages"
355
+ }
356
+
357
+ _cmd_diff() {
358
+ local target="${_CLI_TARGET:-$(pwd)}"
359
+ local spec_file
360
+
361
+ if [[ -n "${_FLAG_SPEC:-}" ]]; then
362
+ spec_file="$_FLAG_SPEC"
363
+ else
364
+ spec_file="$(spec_path "$target")"
365
+ if [[ ! -f "$spec_file" ]]; then
366
+ log_info "No spec found at $spec_file — building from current flags..."
367
+ plan_build "$target"
368
+ local tmp
369
+ tmp=$(mktemp /tmp/zer0-spec-XXXXXX.json)
370
+ spec_write "$tmp"
371
+ spec_file="$tmp"
372
+ fi
373
+ fi
374
+
375
+ diff_spec "$spec_file"
376
+ }
377
+
378
+ _cmd_plan() {
379
+ local target="${_CLI_TARGET:-$(pwd)}"
380
+ plan_build "$target" "${_FLAG_PROFILE:-default}"
381
+ plan_print
382
+ }
383
+
384
+ _cmd_upgrade() {
385
+ local target="${_CLI_TARGET:-$(pwd)}"
386
+ local spec_file="$(spec_path "$target")"
387
+
388
+ if [[ ! -f "$spec_file" ]]; then
389
+ log_error "upgrade: no spec found at $target — run 'init' first"
390
+ return 1
391
+ fi
392
+
393
+ spec_read "$spec_file"
394
+
395
+ # Flag overrides
396
+ plan_apply_flags
397
+ plan_apply_platform
398
+
399
+ spec_write "$spec_file"
400
+ apply_run "$spec_file"
401
+ }
402
+
403
+ _cmd_diagnose() {
404
+ local target="${_CLI_TARGET:-$(pwd)}"
405
+
406
+ if [[ "$_FLAG_AI" == "1" ]]; then
407
+ local diag="${_CLI_DIR}/ai/diagnose.sh"
408
+ if [[ -f "$diag" ]]; then
409
+ # shellcheck source=/dev/null
410
+ source "$diag"
411
+ ai_diagnose_run "$target"
412
+ return $?
413
+ fi
414
+ log_warning "AI diagnose module not available — falling back to doctor"
415
+ fi
416
+ _cmd_doctor
417
+ }
418
+
419
+ _cmd_list_profiles() {
420
+ local profiles_dir="${TEMPLATES_DIR:-}/profiles"
421
+ if [[ -d "$profiles_dir" ]]; then
422
+ printf "Available profiles:\n" >&2
423
+ for f in "${profiles_dir}"/*.yml; do
424
+ [[ -f "$f" ]] || continue
425
+ local name
426
+ name=$(basename "$f" .yml)
427
+ local desc
428
+ desc=$(awk '/^description:/ {sub(/^description:[[:space:]]*/,""); print; exit}' "$f")
429
+ printf " %-20s %s\n" "$name" "${desc:-}" >&2
430
+ done
431
+ else
432
+ printf "default minimal blog docs portfolio github-pages fork\n" >&2
433
+ fi
434
+ }
435
+
436
+ _cmd_list_tasks() {
437
+ local reg="${_CLI_DIR}/tasks/_registry.sh"
438
+ if [[ -f "$reg" ]]; then
439
+ # shellcheck source=/dev/null
440
+ source "$reg"
441
+ task_list_all | while read -r t; do
442
+ printf " %-20s %s\n" "$t" "$(task_description "$t")"
443
+ done
444
+ else
445
+ echo "config gemfile docker theme pages nav data devcontainer agents gitignore readme marker"
446
+ fi
447
+ }
448
+
449
+ _cmd_version() {
450
+ echo "zer0-mistakes installer v${_CLI_VERSION}"
451
+ }
452
+
453
+ _cmd_help() {
454
+ cat >&2 <<'USAGE'
455
+ zer0-mistakes installer
456
+
457
+ Usage:
458
+ install <subcommand> [OPTIONS] [TARGET_DIR]
459
+
460
+ Subcommands:
461
+ init Install from flags + profile (default)
462
+ wizard Interactive wizard (--ai for AI-driven spec)
463
+ agents Install AI agent files only
464
+ deploy Configure deployment plugin(s)
465
+ doctor Run pre-install health checks
466
+ scrape Crawl an existing site into ./.zer0/scrape (no install)
467
+ diagnose Diagnose an existing install (--ai for suggestions)
468
+ upgrade Re-apply spec to an existing install
469
+ diff Show what would change without applying
470
+ plan Print the spec that would be generated
471
+ list-profiles List available installation profiles
472
+ list-tasks List available installer tasks
473
+ version Print installer version
474
+ help Show this help
475
+
476
+ Global options:
477
+ --profile NAME Profile: default|minimal|blog|docs|portfolio|github-pages|fork
478
+ --site-title TITLE Site title
479
+ --site-desc TEXT Site description
480
+ --site-url URL Site URL
481
+ --site-author NAME Author name
482
+ --site-email EMAIL Author email
483
+ --github-user USER GitHub username
484
+ --github-repo REPO GitHub repository name
485
+ --theme-source TYPE gem|remote|vendored
486
+ --deploy LIST Comma/space-separated deploy targets
487
+ --agents LIST AI agent files: copilot,claude,cursor,aider,generic,all
488
+ --tasks LIST Override task execution list
489
+ --dry-run Simulate writes; no files modified
490
+ --force Overwrite existing files
491
+ --no-backup Skip file backups
492
+ --non-interactive No prompts; use defaults
493
+ --auto-accept Auto-confirm all yes/no prompts
494
+ --skip-doctor Skip pre-install health checks
495
+ --verbose, -v Extra debug output
496
+ --output json|human Log format (default: human)
497
+ --ai Use AI assistant for wizard/diagnose
498
+ --spec FILE Load spec from FILE instead of detecting
499
+ --scrape URL Import content from URL during init (adds 'scrape' task)
500
+ --scrape-depth N Max crawl depth (default: 2)
501
+ --scrape-max-pages N Max pages to fetch (default: 25)
502
+
503
+ Examples:
504
+ install init . --profile blog --site-title "My Blog"
505
+ install init ~/mysite --profile github-pages --github-user bamr87
506
+ install wizard . --ai
507
+ install agents . --copilot --claude
508
+ install doctor .
509
+ install diff .
510
+ install upgrade . --force
511
+ install plan . --profile docs
512
+ install scrape https://www.cicit.org/
513
+ install init ./newsite --scrape https://www.cicit.org/ --scrape-max-pages 15
514
+ USAGE
515
+ }
516
+
517
+ # ---------------------------------------------------------------------------
518
+ # cli_main — entry point
519
+ # ---------------------------------------------------------------------------
520
+ cli_main() {
521
+ # Set TEMPLATES_DIR if not already set (relative to installer root)
522
+ if [[ -z "${TEMPLATES_DIR:-}" ]]; then
523
+ local _repo_root
524
+ _repo_root="$(cd "${_CLI_DIR}/../.." 2>/dev/null && pwd)"
525
+ TEMPLATES_DIR="${_repo_root}/templates"
526
+ export TEMPLATES_DIR
527
+ fi
528
+
529
+ _cli_load_core
530
+
531
+ local subcommand="${1:-help}"
532
+ shift || true
533
+
534
+ _cli_parse_flags "$@"
535
+
536
+ # Apply verbose from flags immediately
537
+ [[ "$_FLAG_VERBOSE" == "1" ]] && _LOG_VERBOSE=1 && export _LOG_VERBOSE
538
+ [[ -n "$_FLAG_OUTPUT" ]] && _LOG_OUTPUT="$_FLAG_OUTPUT" && export _LOG_OUTPUT
539
+
540
+ case "$subcommand" in
541
+ init) _cmd_init ;;
542
+ wizard) _cmd_wizard ;;
543
+ agents) _cmd_agents ;;
544
+ deploy) _cmd_deploy ;;
545
+ doctor) _cmd_doctor ;;
546
+ scrape) _cmd_scrape ;;
547
+ diagnose) _cmd_diagnose ;;
548
+ upgrade) _cmd_upgrade ;;
549
+ diff) _cmd_diff ;;
550
+ plan) _cmd_plan ;;
551
+ list-profiles) _cmd_list_profiles ;;
552
+ list-tasks) _cmd_list_tasks ;;
553
+ version) _cmd_version ;;
554
+ help|--help|-h) _cmd_help ;;
555
+ *)
556
+ log_error "Unknown subcommand: $subcommand"
557
+ _cmd_help
558
+ return 1
559
+ ;;
560
+ esac
561
+ }
@@ -0,0 +1,128 @@
1
+ #!/bin/bash
2
+ # =============================================================================
3
+ # scripts/install/diff.sh — Spec-vs-disk diff renderer
4
+ # =============================================================================
5
+ # Shows what apply.sh WOULD do before it does it.
6
+ #
7
+ # Provides:
8
+ # diff_spec SPEC_FILE
9
+ # Print a human-readable diff of spec intent vs. current target dir.
10
+ # Each task prints: MISSING | EXISTS | CHANGED | WOULD-WRITE
11
+ # Returns 0 when target matches spec (no-op), 1 when changes needed.
12
+ #
13
+ # Bash 3.2 compatible. No set -euo pipefail here.
14
+ # =============================================================================
15
+ [[ -n "${_HAS_DIFF_LIB:-}" ]] && return 0
16
+ _HAS_DIFF_LIB=1
17
+
18
+ # Internal: compare a single file using diff or hash
19
+ _diff_compare_file() {
20
+ local src="$1"
21
+ local dest="$2"
22
+ local label="$3"
23
+
24
+ if [[ ! -e "$dest" ]]; then
25
+ printf " ${_LOG_GREEN:-}+ MISSING${_LOG_NC:-} %s\n" "$label" >&2
26
+ return 1 # change needed
27
+ fi
28
+
29
+ if command -v diff >/dev/null 2>&1; then
30
+ if ! diff -q "$src" "$dest" >/dev/null 2>&1; then
31
+ printf " ${_LOG_YELLOW:-}~ CHANGED${_LOG_NC:-} %s\n" "$label" >&2
32
+ return 1
33
+ fi
34
+ fi
35
+
36
+ printf " ${_LOG_CYAN:-}= EXISTS${_LOG_NC:-} %s\n" "$label" >&2
37
+ return 0
38
+ }
39
+
40
+ # ---------------------------------------------------------------------------
41
+ # diff_spec SPEC_FILE
42
+ # ---------------------------------------------------------------------------
43
+ diff_spec() {
44
+ local spec_file="$1"
45
+
46
+ if [[ ! -f "$spec_file" ]]; then
47
+ log_error "diff_spec: spec file not found: $spec_file"
48
+ return 1
49
+ fi
50
+
51
+ spec_read "$spec_file"
52
+ local target="${SPEC_TARGET_DIR:-}"
53
+
54
+ if [[ -z "$target" ]]; then
55
+ log_error "diff_spec: spec.target_dir is empty"
56
+ return 1
57
+ fi
58
+
59
+ printf "\n${_LOG_BOLD:-}Diff: spec vs. %s${_LOG_NC:-}\n\n" "$target" >&2
60
+
61
+ local changes=0
62
+
63
+ # Check known output paths per task
64
+ local task
65
+ for task in ${SPEC_TASKS:-}; do
66
+ printf "${_LOG_BLUE:-}[task: %s]${_LOG_NC:-}\n" "$task" >&2
67
+ case "$task" in
68
+ config)
69
+ [[ -f "${target}/_config.yml" ]] || { printf " + MISSING _config.yml\n" >&2; changes=$(( changes + 1 )); }
70
+ ;;
71
+ gemfile)
72
+ [[ -f "${target}/Gemfile" ]] || { printf " + MISSING Gemfile\n" >&2; changes=$(( changes + 1 )); }
73
+ ;;
74
+ docker)
75
+ [[ -f "${target}/docker-compose.yml" ]] || { printf " + MISSING docker-compose.yml\n" >&2; changes=$(( changes + 1 )); }
76
+ [[ -f "${target}/docker/Dockerfile" ]] || { printf " + MISSING docker/Dockerfile\n" >&2; changes=$(( changes + 1 )); }
77
+ ;;
78
+ theme)
79
+ [[ -d "${target}/_layouts" ]] || { printf " + MISSING _layouts/\n" >&2; changes=$(( changes + 1 )); }
80
+ [[ -d "${target}/_includes" ]] || { printf " + MISSING _includes/\n" >&2; changes=$(( changes + 1 )); }
81
+ ;;
82
+ pages)
83
+ [[ -f "${target}/index.md" ]] || [[ -f "${target}/index.html" ]] || \
84
+ { printf " + MISSING index.md\n" >&2; changes=$(( changes + 1 )); }
85
+ ;;
86
+ nav)
87
+ [[ -f "${target}/_data/navigation/main.yml" ]] || \
88
+ { printf " + MISSING _data/navigation/main.yml\n" >&2; changes=$(( changes + 1 )); }
89
+ ;;
90
+ data)
91
+ [[ -f "${target}/_data/authors.yml" ]] || \
92
+ { printf " + MISSING _data/authors.yml\n" >&2; changes=$(( changes + 1 )); }
93
+ ;;
94
+ devcontainer)
95
+ [[ -f "${target}/.devcontainer/devcontainer.json" ]] || \
96
+ { printf " + MISSING .devcontainer/devcontainer.json\n" >&2; changes=$(( changes + 1 )); }
97
+ ;;
98
+ agents)
99
+ [[ -f "${target}/AGENTS.md" ]] || \
100
+ { printf " + MISSING AGENTS.md\n" >&2; changes=$(( changes + 1 )); }
101
+ ;;
102
+ gitignore)
103
+ [[ -f "${target}/.gitignore" ]] || \
104
+ { printf " + MISSING .gitignore\n" >&2; changes=$(( changes + 1 )); }
105
+ ;;
106
+ readme)
107
+ [[ -f "${target}/INSTALLATION.md" ]] || \
108
+ { printf " + MISSING INSTALLATION.md\n" >&2; changes=$(( changes + 1 )); }
109
+ ;;
110
+ marker)
111
+ [[ -f "${target}/.zer0-installed" ]] || \
112
+ { printf " + MISSING .zer0-installed\n" >&2; changes=$(( changes + 1 )); }
113
+ ;;
114
+ *)
115
+ printf " ? UNKNOWN task: %s\n" "$task" >&2
116
+ ;;
117
+ esac
118
+ printf "\n" >&2
119
+ done
120
+
121
+ if [[ $changes -eq 0 ]]; then
122
+ printf "${_LOG_GREEN:-}No changes needed — target matches spec.${_LOG_NC:-}\n\n" >&2
123
+ return 0
124
+ else
125
+ printf "${_LOG_YELLOW:-}%d change(s) would be applied.${_LOG_NC:-}\n\n" "$changes" >&2
126
+ return 1
127
+ fi
128
+ }