jekyll-theme-zer0 1.27.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +386 -4
- data/_data/consumers.yml +147 -0
- data/_data/features.yml +77 -5
- data/_data/i18n/fr.yml +98 -0
- data/_data/i18n/manifest.yml +1502 -0
- data/_data/navigation/docs.yml +2 -0
- data/_data/navigation/main.yml +6 -0
- data/_data/series.yml +19 -0
- data/_data/theme-manifest.yml +348 -266
- data/_includes/README.md +21 -2
- data/_includes/analytics/google-tag-manager-body.html +11 -2
- data/_includes/analytics/google-tag-manager-head.html +16 -6
- data/_includes/analytics/posthog.html +33 -4
- data/_includes/components/abc-letter.html +43 -0
- data/_includes/components/book-card.html +42 -0
- data/_includes/components/book-nav.html +80 -0
- data/_includes/components/book-plate.html +31 -0
- data/_includes/components/book-toc.html +47 -0
- data/_includes/components/bookshelf.html +68 -0
- data/_includes/components/card-grid.html +60 -0
- data/_includes/components/data-card.html +95 -0
- data/_includes/components/halfmoon.html +5 -1
- data/_includes/components/page-feedback.html +65 -2
- data/_includes/components/theme-controls-bar.html +10 -2
- data/_includes/components/theme-customizer.html +8 -2
- data/_includes/content/seo.html +9 -3
- data/_includes/core/color-mode-init.html +13 -4
- data/_includes/core/favicon.html +46 -0
- data/_includes/core/head.html +9 -0
- data/_includes/custom/body-end.html +18 -0
- data/_includes/custom/body-start.html +17 -0
- data/_includes/custom/footer.html +18 -0
- data/_includes/custom/head.html +18 -0
- data/_includes/navigation/local-graph.html +28 -2
- data/_includes/navigation/nav-tree.html +3 -3
- data/_includes/navigation/sidebar-config.html +21 -0
- data/_includes/navigation/sidebar-nav.html +4 -0
- data/_includes/navigation/sidebar-pagetree.html +150 -0
- data/_includes/navigation/unified-drawer.html +8 -2
- data/_includes/obsidian/full-graph.html +165 -136
- data/_layouts/404.html +260 -0
- data/_layouts/README.md +2 -0
- data/_layouts/book-abc.html +106 -0
- data/_layouts/book-story.html +91 -0
- data/_layouts/book.html +113 -0
- data/_layouts/collection.html +17 -5
- data/_layouts/home.html +5 -2
- data/_layouts/landing.html +9 -0
- data/_layouts/news.html +155 -31
- data/_layouts/root.html +16 -2
- data/_layouts/section.html +57 -15
- data/_sass/components/_book.scss +423 -0
- data/_sass/core/_navbar.scss +13 -0
- data/_sass/core/_obsidian.scss +286 -6
- data/_sass/theme/_backgrounds.scss +21 -8
- data/assets/css/main.scss +1 -0
- data/assets/js/auto-hide-nav.js +5 -1
- data/assets/js/halfmoon.js +26 -0
- data/assets/js/obsidian-graph.js +702 -264
- data/assets/js/obsidian-local-graph.js +161 -54
- data/assets/js/search-modal.js +4 -1
- data/scripts/bin/manifest +31 -4
- data/scripts/bin/validate +5 -1
- data/scripts/install/README.md +47 -6
- data/scripts/install/ai/client.sh +302 -93
- data/scripts/install/ai/prompts/spec.schema.json +1 -1
- data/scripts/install/ai/wizard.sh +10 -5
- data/scripts/install/apply.sh +7 -3
- data/scripts/install/cli.sh +54 -4
- data/scripts/install/config.sh +167 -0
- data/scripts/install/doctor.sh +38 -0
- data/scripts/install/plan.sh +10 -0
- data/scripts/install/spec.sh +15 -7
- data/scripts/install/template.sh +4 -0
- data/scripts/propagate.rb +277 -0
- data/scripts/translate.rb +90 -0
- metadata +27 -2
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// Feature: ZER0-045
|
|
1
2
|
/*
|
|
2
3
|
* obsidian-local-graph.js
|
|
3
4
|
*
|
|
@@ -12,8 +13,13 @@
|
|
|
12
13
|
* Subgraph:
|
|
13
14
|
* - center = current page (matched against entry.url, falling back to
|
|
14
15
|
* normalized title/basename/aliases for permalink quirks)
|
|
15
|
-
* - depth =
|
|
16
|
-
*
|
|
16
|
+
* - depth = 1–3, user-adjustable in the panel (default from the
|
|
17
|
+
* data-depth attribute / `local_graph_depth` front matter)
|
|
18
|
+
* - direction = outgoing and/or incoming wiki-links, user-toggleable
|
|
19
|
+
*
|
|
20
|
+
* Depth + direction preferences persist to localStorage
|
|
21
|
+
* (zer0.obsidianLocalGraph.v1) and the graph re-renders in place when they
|
|
22
|
+
* change — same for Bootstrap color-mode (data-bs-theme) switches.
|
|
17
23
|
*
|
|
18
24
|
* If the current page is in the wiki-index but has no local links, the panel
|
|
19
25
|
* stays available and renders a single-node graph for the current page.
|
|
@@ -26,6 +32,7 @@
|
|
|
26
32
|
var PANEL_SELECTOR = '[data-obsidian-local-graph-panel]';
|
|
27
33
|
var TOGGLE_SELECTOR = '[data-obsidian-local-graph-toggle]';
|
|
28
34
|
var STATUS_SELECTOR = '[data-obsidian-local-graph-status]';
|
|
35
|
+
var STORAGE_KEY = 'zer0.obsidianLocalGraph.v1';
|
|
29
36
|
|
|
30
37
|
// Cytoscape is vendored under assets/vendor/ (no runtime CDN — matches the
|
|
31
38
|
// Bootstrap / Icons / Mermaid policy). The path is supplied by Liquid via
|
|
@@ -57,6 +64,28 @@
|
|
|
57
64
|
.replace(/"/g, '"').replace(/'/g, ''');
|
|
58
65
|
}
|
|
59
66
|
|
|
67
|
+
// Depth + direction preferences. Depth falls back to the page's data-depth
|
|
68
|
+
// (front-matter `local_graph_depth`), directions default to both on.
|
|
69
|
+
function loadPrefs(defaultDepth) {
|
|
70
|
+
var prefs = { depth: defaultDepth, outgoing: true, incoming: true };
|
|
71
|
+
try {
|
|
72
|
+
var saved = JSON.parse(window.localStorage.getItem(STORAGE_KEY) || 'null');
|
|
73
|
+
if (saved && typeof saved === 'object') {
|
|
74
|
+
var d = parseInt(saved.depth, 10);
|
|
75
|
+
if (d >= 1 && d <= 3) prefs.depth = d;
|
|
76
|
+
if (typeof saved.outgoing === 'boolean') prefs.outgoing = saved.outgoing;
|
|
77
|
+
if (typeof saved.incoming === 'boolean') prefs.incoming = saved.incoming;
|
|
78
|
+
}
|
|
79
|
+
} catch (e) { /* private mode / corrupt payload — use defaults */ }
|
|
80
|
+
return prefs;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function savePrefs(prefs) {
|
|
84
|
+
try {
|
|
85
|
+
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(prefs));
|
|
86
|
+
} catch (e) { /* storage unavailable — prefs are session-only */ }
|
|
87
|
+
}
|
|
88
|
+
|
|
60
89
|
function companionElements(container) {
|
|
61
90
|
return {
|
|
62
91
|
panel: container.closest(PANEL_SELECTOR),
|
|
@@ -143,10 +172,11 @@
|
|
|
143
172
|
return palette[name] || '#6c757d';
|
|
144
173
|
}
|
|
145
174
|
|
|
146
|
-
// BFS from the current entry up to `depth` hops
|
|
147
|
-
// outgoing
|
|
148
|
-
// includes one of our keys
|
|
149
|
-
function buildSubgraph(entries, lookup, current, depth) {
|
|
175
|
+
// BFS from the current entry up to `depth` hops. Directions are opt-in:
|
|
176
|
+
// opts.outgoing follows this page's [[links]], opts.incoming follows any
|
|
177
|
+
// other entry whose `outgoing` includes one of our keys.
|
|
178
|
+
function buildSubgraph(entries, lookup, current, depth, opts) {
|
|
179
|
+
opts = opts || { outgoing: true, incoming: true };
|
|
150
180
|
var visited = Object.create(null);
|
|
151
181
|
var queue = [{ entry: current, dist: 0 }];
|
|
152
182
|
var nodes = [];
|
|
@@ -206,45 +236,49 @@
|
|
|
206
236
|
if (item.dist >= depth) continue;
|
|
207
237
|
|
|
208
238
|
// Outgoing edges
|
|
209
|
-
(
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
if (resolved
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
239
|
+
if (opts.outgoing) {
|
|
240
|
+
(entry.outgoing || []).forEach(function (target) {
|
|
241
|
+
var nk = normalize(target);
|
|
242
|
+
var resolved = lookup.byKey[nk];
|
|
243
|
+
if (resolved) {
|
|
244
|
+
if (resolved.url === entry.url) return;
|
|
245
|
+
addEdge(entry.url, resolved.url, false);
|
|
246
|
+
if (!visited[resolved.url]) {
|
|
247
|
+
queue.push({ entry: resolved, dist: item.dist + 1 });
|
|
248
|
+
}
|
|
249
|
+
} else {
|
|
250
|
+
var brokenId = '__broken__:' + nk;
|
|
251
|
+
if (!visited[brokenId]) {
|
|
252
|
+
visited[brokenId] = true;
|
|
253
|
+
nodes.push({
|
|
254
|
+
group: 'nodes',
|
|
255
|
+
data: {
|
|
256
|
+
id: brokenId,
|
|
257
|
+
label: target,
|
|
258
|
+
url: null,
|
|
259
|
+
collection: 'broken',
|
|
260
|
+
color: '#dc3545',
|
|
261
|
+
broken: true
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
addEdge(entry.url, brokenId, true);
|
|
217
266
|
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if (!visited[brokenId]) {
|
|
221
|
-
visited[brokenId] = true;
|
|
222
|
-
nodes.push({
|
|
223
|
-
group: 'nodes',
|
|
224
|
-
data: {
|
|
225
|
-
id: brokenId,
|
|
226
|
-
label: target,
|
|
227
|
-
url: null,
|
|
228
|
-
collection: 'broken',
|
|
229
|
-
color: '#dc3545',
|
|
230
|
-
broken: true
|
|
231
|
-
}
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
addEdge(entry.url, brokenId, true);
|
|
235
|
-
}
|
|
236
|
-
});
|
|
267
|
+
});
|
|
268
|
+
}
|
|
237
269
|
|
|
238
270
|
// Incoming edges (anyone whose outgoing matches one of our keys)
|
|
239
|
-
|
|
240
|
-
(
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
271
|
+
if (opts.incoming) {
|
|
272
|
+
keysFor(entry).forEach(function (k) {
|
|
273
|
+
(reverse[k] || []).forEach(function (src) {
|
|
274
|
+
if (src.url === entry.url) return;
|
|
275
|
+
addEdge(src.url, entry.url, false);
|
|
276
|
+
if (!visited[src.url]) {
|
|
277
|
+
queue.push({ entry: src, dist: item.dist + 1 });
|
|
278
|
+
}
|
|
279
|
+
});
|
|
246
280
|
});
|
|
247
|
-
}
|
|
281
|
+
}
|
|
248
282
|
}
|
|
249
283
|
|
|
250
284
|
return nodes.concat(edges);
|
|
@@ -299,6 +333,11 @@
|
|
|
299
333
|
}
|
|
300
334
|
|
|
301
335
|
function render(container, elements, currentUrl) {
|
|
336
|
+
// Re-renders (depth/direction/theme changes) replace the prior instance.
|
|
337
|
+
if (container.__obsidianLocalGraph) {
|
|
338
|
+
try { container.__obsidianLocalGraph.destroy(); } catch (e) { /* ignore */ }
|
|
339
|
+
container.__obsidianLocalGraph = null;
|
|
340
|
+
}
|
|
302
341
|
var theme = readTheme();
|
|
303
342
|
container.style.backgroundColor = theme.canvasBg;
|
|
304
343
|
var motion = prefersReducedMotion() ? '0ms' : '160ms';
|
|
@@ -460,6 +499,59 @@
|
|
|
460
499
|
return nav;
|
|
461
500
|
}
|
|
462
501
|
|
|
502
|
+
// Reflect prefs into the depth radio group + direction switches.
|
|
503
|
+
function syncControls(prefs) {
|
|
504
|
+
var radio = document.getElementById('obsidian-lg-depth-' + prefs.depth);
|
|
505
|
+
if (radio) radio.checked = true;
|
|
506
|
+
var outgoing = document.getElementById('obsidian-lg-outgoing');
|
|
507
|
+
if (outgoing) outgoing.checked = prefs.outgoing;
|
|
508
|
+
var incoming = document.getElementById('obsidian-lg-incoming');
|
|
509
|
+
if (incoming) incoming.checked = prefs.incoming;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function wireControls(prefs, rebuild) {
|
|
513
|
+
[1, 2, 3].forEach(function (d) {
|
|
514
|
+
var radio = document.getElementById('obsidian-lg-depth-' + d);
|
|
515
|
+
if (!radio) return;
|
|
516
|
+
radio.addEventListener('change', function () {
|
|
517
|
+
if (!radio.checked) return;
|
|
518
|
+
prefs.depth = d;
|
|
519
|
+
savePrefs(prefs);
|
|
520
|
+
rebuild();
|
|
521
|
+
});
|
|
522
|
+
});
|
|
523
|
+
var outgoing = document.getElementById('obsidian-lg-outgoing');
|
|
524
|
+
if (outgoing) {
|
|
525
|
+
outgoing.addEventListener('change', function () {
|
|
526
|
+
prefs.outgoing = outgoing.checked;
|
|
527
|
+
savePrefs(prefs);
|
|
528
|
+
rebuild();
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
var incoming = document.getElementById('obsidian-lg-incoming');
|
|
532
|
+
if (incoming) {
|
|
533
|
+
incoming.addEventListener('change', function () {
|
|
534
|
+
prefs.incoming = incoming.checked;
|
|
535
|
+
savePrefs(prefs);
|
|
536
|
+
rebuild();
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Re-render on Bootstrap color-mode changes so canvas colors track the
|
|
542
|
+
// theme without a reload. Local graphs are small — a full rebuild is cheap.
|
|
543
|
+
function watchTheme(rebuild) {
|
|
544
|
+
var restyle = debounce(rebuild, 60);
|
|
545
|
+
var observer = new MutationObserver(restyle);
|
|
546
|
+
[document.documentElement, document.body].forEach(function (el) {
|
|
547
|
+
observer.observe(el, { attributes: true, attributeFilter: ['data-bs-theme'] });
|
|
548
|
+
});
|
|
549
|
+
if (window.matchMedia) {
|
|
550
|
+
var mq = window.matchMedia('(prefers-color-scheme: dark)');
|
|
551
|
+
if (mq.addEventListener) mq.addEventListener('change', restyle);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
463
555
|
function init() {
|
|
464
556
|
var container = document.getElementById(CONTAINER_ID);
|
|
465
557
|
if (!container) return;
|
|
@@ -478,8 +570,9 @@
|
|
|
478
570
|
resizeGraph(container);
|
|
479
571
|
}, 150));
|
|
480
572
|
|
|
481
|
-
var
|
|
482
|
-
if (!isFinite(
|
|
573
|
+
var defaultDepth = parseInt(container.getAttribute('data-depth') || '1', 10);
|
|
574
|
+
if (!isFinite(defaultDepth) || defaultDepth < 1) defaultDepth = 1;
|
|
575
|
+
var prefs = loadPrefs(Math.min(defaultDepth, 3));
|
|
483
576
|
var indexUrl = container.getAttribute('data-index-url') ||
|
|
484
577
|
((document.querySelector('base') || {}).href || '/') +
|
|
485
578
|
'assets/data/wiki-index.json';
|
|
@@ -496,17 +589,21 @@
|
|
|
496
589
|
// Confirmed in-index: reveal the panel + FAB now.
|
|
497
590
|
setPanelAvailable(container, true);
|
|
498
591
|
setStatus(container, 'Loading graph…', false);
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
var
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
592
|
+
syncControls(prefs);
|
|
593
|
+
|
|
594
|
+
var graphAvailable = null; // unknown until loadCytoscape resolves
|
|
595
|
+
|
|
596
|
+
function rebuild() {
|
|
597
|
+
var elements = buildSubgraph(entries, lookup, current, prefs.depth, {
|
|
598
|
+
outgoing: prefs.outgoing,
|
|
599
|
+
incoming: prefs.incoming
|
|
600
|
+
});
|
|
601
|
+
var nodeCount = elements.filter(function (el) { return el.group === 'nodes'; }).length;
|
|
602
|
+
var edgeCount = elements.filter(function (el) { return el.group === 'edges'; }).length;
|
|
603
|
+
// Accessible text fallback (also the graceful degradation if
|
|
604
|
+
// cytoscape can't load): a list of linked neighbours below the canvas.
|
|
605
|
+
var fallback = renderTextFallback(container, elements, current);
|
|
606
|
+
if (graphAvailable === false) {
|
|
510
607
|
container.hidden = true;
|
|
511
608
|
setStatus(container, 'Showing linked pages (interactive graph unavailable).', false);
|
|
512
609
|
return;
|
|
@@ -515,6 +612,16 @@
|
|
|
515
612
|
setStatus(container, nodeCount + ' pages · ' + edgeCount + ' links', false);
|
|
516
613
|
// Graph is the visual representation; keep the list for AT only.
|
|
517
614
|
if (fallback) fallback.classList.add('visually-hidden');
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
wireControls(prefs, rebuild);
|
|
618
|
+
watchTheme(function () {
|
|
619
|
+
if (container.__obsidianLocalGraph) rebuild();
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
loadCytoscape(function (ok) {
|
|
623
|
+
graphAvailable = ok !== false;
|
|
624
|
+
rebuild();
|
|
518
625
|
});
|
|
519
626
|
})
|
|
520
627
|
.catch(function (err) {
|
data/assets/js/search-modal.js
CHANGED
|
@@ -74,8 +74,11 @@
|
|
|
74
74
|
if (typeof bootstrap === 'undefined') return;
|
|
75
75
|
const cookieEl = document.getElementById('cookieSettingsModal');
|
|
76
76
|
const infoEl = document.getElementById('info-section');
|
|
77
|
+
const drawerEl = document.getElementById('zer0UnifiedDrawer');
|
|
77
78
|
afterModalClosed(cookieEl, () => {
|
|
78
|
-
afterOffcanvasClosed(infoEl,
|
|
79
|
+
afterOffcanvasClosed(infoEl, () => {
|
|
80
|
+
afterOffcanvasClosed(drawerEl, showSearchModal);
|
|
81
|
+
});
|
|
79
82
|
});
|
|
80
83
|
};
|
|
81
84
|
|
data/scripts/bin/manifest
CHANGED
|
@@ -35,12 +35,15 @@ DESCRIPTION:
|
|
|
35
35
|
Run automatically by scripts/bin/release before tagging.
|
|
36
36
|
|
|
37
37
|
OPTIONS:
|
|
38
|
+
--check Exit non-zero if the committed manifest is stale; write
|
|
39
|
+
nothing. `generated_at` is ignored in the comparison.
|
|
38
40
|
--dry-run Print manifest to stdout; don't write the file
|
|
39
41
|
--verbose Show each file being processed
|
|
40
42
|
--help, -h Show this help message
|
|
41
43
|
|
|
42
44
|
EXAMPLES:
|
|
43
45
|
./scripts/bin/manifest # Regenerate _data/theme-manifest.yml
|
|
46
|
+
./scripts/bin/manifest --check # CI gate: is the manifest current?
|
|
44
47
|
./scripts/bin/manifest --dry-run # Preview output
|
|
45
48
|
./scripts/bin/manifest --verbose # Show every file being hashed
|
|
46
49
|
EOF
|
|
@@ -81,9 +84,11 @@ OPTIONAL_PLUGIN_PATHS=(
|
|
|
81
84
|
# Argument parsing
|
|
82
85
|
DRY_RUN=false
|
|
83
86
|
VERBOSE=false
|
|
87
|
+
CHECK=false
|
|
84
88
|
|
|
85
89
|
while [[ $# -gt 0 ]]; do
|
|
86
90
|
case $1 in
|
|
91
|
+
--check) CHECK=true; DRY_RUN=true ;;
|
|
87
92
|
--dry-run) DRY_RUN=true ;;
|
|
88
93
|
--verbose) VERBOSE=true ;;
|
|
89
94
|
--help|-h) show_usage; exit 0 ;;
|
|
@@ -93,7 +98,14 @@ while [[ $# -gt 0 ]]; do
|
|
|
93
98
|
done
|
|
94
99
|
|
|
95
100
|
# ---------------------------------------------------------------------------
|
|
96
|
-
|
|
101
|
+
# Under --dry-run stdout carries only the manifest YAML — the CI freshness gate
|
|
102
|
+
# (ci.yml "Theme manifest freshness") diffs that stdout straight against the
|
|
103
|
+
# committed file, so progress logs must not pollute it.
|
|
104
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
105
|
+
step "Generating theme manifest..." >&2
|
|
106
|
+
else
|
|
107
|
+
step "Generating theme manifest..."
|
|
108
|
+
fi
|
|
97
109
|
cd "$REPO_ROOT"
|
|
98
110
|
|
|
99
111
|
CURRENT_VERSION=$(get_current_version)
|
|
@@ -173,10 +185,25 @@ YAML
|
|
|
173
185
|
)
|
|
174
186
|
|
|
175
187
|
# ---------------------------------------------------------------------------
|
|
176
|
-
|
|
177
|
-
|
|
188
|
+
MANIFEST_FILE="$REPO_ROOT/_data/theme-manifest.yml"
|
|
189
|
+
|
|
190
|
+
if [[ "$CHECK" == "true" ]]; then
|
|
191
|
+
# Staleness = any difference other than the generation timestamp, which moves
|
|
192
|
+
# on every run and would otherwise report a false positive every time.
|
|
193
|
+
if [[ ! -f "$MANIFEST_FILE" ]]; then
|
|
194
|
+
error "Manifest missing: _data/theme-manifest.yml — run ./scripts/bin/manifest"
|
|
195
|
+
fi
|
|
196
|
+
if diff -u \
|
|
197
|
+
<(grep -v '^generated_at:' "$MANIFEST_FILE") \
|
|
198
|
+
<(echo "$MANIFEST_CONTENT" | grep -v '^generated_at:') >&2; then
|
|
199
|
+
success "Manifest is current (version $CURRENT_VERSION)" >&2
|
|
200
|
+
exit 0
|
|
201
|
+
fi
|
|
202
|
+
error "Manifest is stale — run ./scripts/bin/manifest and commit the result"
|
|
203
|
+
elif [[ "$DRY_RUN" == "true" ]]; then
|
|
204
|
+
info "[DRY RUN] Would write _data/theme-manifest.yml:" >&2
|
|
178
205
|
echo "$MANIFEST_CONTENT"
|
|
179
206
|
else
|
|
180
|
-
echo "$MANIFEST_CONTENT" > "$
|
|
207
|
+
echo "$MANIFEST_CONTENT" > "$MANIFEST_FILE"
|
|
181
208
|
success "Written: _data/theme-manifest.yml (version $CURRENT_VERSION)"
|
|
182
209
|
fi
|
data/scripts/bin/validate
CHANGED
|
@@ -391,7 +391,11 @@ if File.file?('_config_secrets_local.yml')
|
|
|
391
391
|
assert(!tracked, '_config_secrets_local.yml must remain untracked and ignored')
|
|
392
392
|
end
|
|
393
393
|
|
|
394
|
-
|
|
394
|
+
# examples/ holds self-contained demo sites, each with its own _config.yml and
|
|
395
|
+
# _config_dev.yml. Those belong to the example, not to this site's config
|
|
396
|
+
# surface, so prune the tree rather than registering every example's configs in
|
|
397
|
+
# classified_files below (which would need editing for each new example).
|
|
398
|
+
ignored_dirs = %w[.git _site node_modules vendor .jekyll-cache .sass-cache test/fixtures examples]
|
|
395
399
|
config_like_files = []
|
|
396
400
|
|
|
397
401
|
Find.find('.') do |path|
|
data/scripts/install/README.md
CHANGED
|
@@ -86,8 +86,11 @@ This installer runs on macOS's `/bin/bash` (3.2). Restrictions:
|
|
|
86
86
|
# Local development
|
|
87
87
|
./scripts/bin/install help
|
|
88
88
|
./scripts/bin/install init . --profile blog --site-title "My Blog"
|
|
89
|
-
./scripts/bin/install
|
|
90
|
-
./scripts/bin/install
|
|
89
|
+
./scripts/bin/install init . --config zer0.install.yml # config-file driven
|
|
90
|
+
./scripts/bin/install wizard . --ai # Claude Code OAuth if available
|
|
91
|
+
./scripts/bin/install wizard . --ai --ai-provider anthropic
|
|
92
|
+
./scripts/bin/install suggest . "a docs site for a Rust CLI"
|
|
93
|
+
./scripts/bin/install doctor . # shows the active AI provider
|
|
91
94
|
./scripts/bin/install diff .
|
|
92
95
|
./scripts/bin/install plan . --profile docs
|
|
93
96
|
|
|
@@ -110,17 +113,55 @@ curl -fsSL https://raw.githubusercontent.com/bamr87/zer0-mistakes/main/scripts/b
|
|
|
110
113
|
3. Add template to `templates/config/` or `templates/pages/`
|
|
111
114
|
4. List in the appropriate profile YAML under `templates/profiles/`
|
|
112
115
|
|
|
116
|
+
## Config file layer
|
|
117
|
+
|
|
118
|
+
Sites can persist installer choices in a small YAML file instead of re-typing flags. `config.sh` discovers and merges these (low→high precedence):
|
|
119
|
+
|
|
120
|
+
1. `~/.config/zer0/install.yml` (user-global)
|
|
121
|
+
2. `<target>/zer0.install.yml` (project-local)
|
|
122
|
+
3. `<target>/.zer0/config.yml` (project-local, hidden)
|
|
123
|
+
4. `$ZER0_CONFIG` / `--config FILE` (explicit — wins)
|
|
124
|
+
|
|
125
|
+
The config layer sits between profile defaults and env/flag overrides:
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
defaults < profile < CONFIG FILE < env vars < CLI flags
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Recognised keys (nested **or** flat/dotted both work): `profile`, `site.{title,description,author,email,url,timezone,locale}`, `github.{user,repo,pages_branch,enable_pages}`, `theme.{source,version}`, `deploy`, `agents`, `tasks`, `ai.provider`, `ai.model`. Example:
|
|
132
|
+
|
|
133
|
+
```yaml
|
|
134
|
+
profile: github-pages
|
|
135
|
+
site:
|
|
136
|
+
title: "My Docs"
|
|
137
|
+
deploy: [github-pages]
|
|
138
|
+
ai:
|
|
139
|
+
provider: claude-cli # auto | claude-cli | anthropic | openai | none
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
API keys are **never** read from config files — they stay in the environment.
|
|
143
|
+
|
|
113
144
|
## AI integration
|
|
114
145
|
|
|
115
146
|
The AI path is a first-class citizen but never mandatory:
|
|
116
147
|
|
|
117
148
|
- `ai_wizard_run` → interactive LLM spec generation
|
|
118
|
-
- `ai_diagnose_run` → post-build error analysis
|
|
119
|
-
- `ai_suggest_run` → profile + deploy recommendation
|
|
149
|
+
- `ai_diagnose_run` → post-build error analysis
|
|
150
|
+
- `ai_suggest_run` → profile + deploy recommendation (also `install suggest`)
|
|
151
|
+
|
|
152
|
+
All are guarded by the `ZER0_NO_AI=1` kill-switch and degrade gracefully to defaults or rule-based logic when AI is unavailable.
|
|
153
|
+
|
|
154
|
+
### Providers (`ai/client.sh`)
|
|
155
|
+
|
|
156
|
+
The client is multi-provider. `ZER0_AI_PROVIDER` (or config `ai.provider`, or `--ai-provider`) selects one; the default `auto` resolves in this order:
|
|
120
157
|
|
|
121
|
-
|
|
158
|
+
| # | Provider | Auth | Notes |
|
|
159
|
+
|---|--------------|-------------------------------------------------|-------|
|
|
160
|
+
| 1 | `claude-cli` | the logged-in `claude` CLI (**Claude Code OAuth**) | Zero key handling — reuses `claude setup-token` / `claude login`. Preferred when the `claude` binary is on `PATH`. |
|
|
161
|
+
| 2 | `anthropic` | `CLAUDE_CODE_OAUTH_TOKEN` (OAuth) or `ANTHROPIC_API_KEY` | Anthropic Messages API over HTTPS. |
|
|
162
|
+
| 3 | `openai` | `OPENAI_API_KEY` (or `OPENAI_BASE_URL` for Azure/Ollama) | OpenAI-compatible `/chat/completions`. |
|
|
122
163
|
|
|
123
|
-
|
|
164
|
+
Model is auto-defaulted per provider and overridable with `ZER0_AI_MODEL` / `--ai-model` (`ANTHROPIC_MODEL` / `OPENAI_MODEL` also honoured). Run `install doctor` to see which provider is active. All calls are single-attempt with a 30s timeout; user context is sanitized before it leaves the host.
|
|
124
165
|
|
|
125
166
|
## Deploy plugins
|
|
126
167
|
|