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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +33 -3
- data/README.md +98 -7
- data/_data/content_statistics.yml +253 -251
- data/_includes/components/nav-export.html +61 -0
- data/_includes/components/nav-overview.html +54 -0
- data/scripts/bin/install +52 -705
- data/scripts/github-setup.sh +0 -0
- data/scripts/install/README.md +162 -0
- data/scripts/install/ai/client.sh +164 -0
- data/scripts/install/ai/diagnose.sh +81 -0
- data/scripts/install/ai/prompts/diagnose.system.md +42 -0
- data/scripts/install/ai/prompts/spec.schema.json +129 -0
- data/scripts/install/ai/prompts/suggest.system.md +43 -0
- data/scripts/install/ai/prompts/wizard.system.md +142 -0
- data/scripts/install/ai/suggest.sh +57 -0
- data/scripts/install/ai/wizard.sh +150 -0
- data/scripts/install/apply.sh +156 -0
- data/scripts/install/cli.sh +561 -0
- data/scripts/install/diff.sh +128 -0
- data/scripts/install/doctor.sh +168 -0
- data/scripts/install/fs.sh +138 -0
- data/scripts/install/log.sh +119 -0
- data/scripts/install/plan.sh +299 -0
- data/scripts/install/platform.sh +122 -0
- data/scripts/install/prompt.sh +124 -0
- data/scripts/install/repair.sh +45 -0
- data/scripts/install/scrape.sh +535 -0
- data/scripts/install/scrape_html.py +764 -0
- data/scripts/install/spec.sh +486 -0
- data/scripts/install/tasks/_registry.sh +65 -0
- data/scripts/install/tasks/agents.sh +60 -0
- data/scripts/install/tasks/config.sh +37 -0
- data/scripts/install/tasks/data.sh +18 -0
- data/scripts/install/tasks/deploy_azure-swa.sh +17 -0
- data/scripts/install/tasks/deploy_docker-prod.sh +21 -0
- data/scripts/install/tasks/deploy_github-pages.sh +18 -0
- data/scripts/install/tasks/devcontainer.sh +26 -0
- data/scripts/install/tasks/docker.sh +29 -0
- data/scripts/install/tasks/gemfile.sh +42 -0
- data/scripts/install/tasks/gitignore.sh +26 -0
- data/scripts/install/tasks/marker.sh +46 -0
- data/scripts/install/tasks/nav.sh +18 -0
- data/scripts/install/tasks/pages.sh +61 -0
- data/scripts/install/tasks/readme.sh +27 -0
- data/scripts/install/tasks/scrape.sh +348 -0
- data/scripts/install/template.sh +138 -0
- data/scripts/install/tui.sh +110 -0
- data/scripts/install/upgrade.sh +49 -0
- data/scripts/lib/install/template.sh +1 -0
- 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
|
+
}
|