hd-garden 0.13.3 → 0.14.2
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.
- package/.agents/skills/garden-compose/SKILL.md +24 -0
- package/.agents/skills/garden-compose/references/colors.md +11 -0
- package/.agents/skills/garden-compose/references/composition-patterns.md +162 -0
- package/.agents/skills/garden-compose/references/examples.md +55 -0
- package/.agents/skills/garden-compose/references/frames.md +7 -0
- package/.agents/skills/garden-compose/references/layout-recipes.md +84 -0
- package/.agents/skills/garden-compose/references/rendering-model.md +120 -0
- package/.agents/skills/garden-compose/references/static-play.md +19 -0
- package/.agents/skills/garden-compose/references/visual-selection.md +38 -0
- package/.agents/skills/garden-compose/references/workflow.md +27 -0
- package/.agents/skills/garden-compose/scripts/_cli.mjs +12 -0
- package/.agents/skills/garden-compose/scripts/apply.mjs +2 -0
- package/.agents/skills/garden-compose/scripts/check.mjs +2 -0
- package/.agents/skills/garden-compose/scripts/prompt.mjs +2 -0
- package/.agents/skills/garden-compose/scripts/scan.mjs +2 -0
- package/.agents/skills/garden-post-review/SKILL.md +5 -16
- package/Makefile +12 -3
- package/README.en.md +10 -5
- package/README.md +10 -5
- package/app/admin/preview/[...path]/page.tsx +4 -16
- package/app/api/admin/ui/blocks/route.ts +1 -1
- package/app/components/AdminUiPanels.tsx +6 -3
- package/app/components/GardenContentGuide.tsx +7 -8
- package/app/components/MarkdownArticle.tsx +2 -0
- package/app/components/UiStudio.tsx +29 -32
- package/app/components/garden/AuthorRich.tsx +5 -0
- package/app/components/garden/ComposePreview.tsx +9 -0
- package/app/components/garden/GardenBlock.tsx +12 -10
- package/app/components/garden/GardenFile.tsx +1 -1
- package/app/components/garden/blocks.tsx +9 -197
- package/app/components/garden/file-icons.tsx +1 -1
- package/app/components/garden/patterns.tsx +7 -65
- package/app/lib/rehype-garden-blocks.mjs +30 -0
- package/app/lib/remark-garden.mjs +0 -4
- package/app/lib/ui-model.ts +8 -9
- package/app/styles/a2.css +17 -1
- package/bin/garden.mjs +34 -35
- package/docs/handbook/ai-writing.md +19 -22
- package/docs/handbook/components/compose.md +16 -7
- package/docs/handbook/components/image.md +1 -1
- package/docs/handbook/components/link-card.md +2 -2
- package/docs/handbook/components.md +5 -213
- package/docs/handbook/deploy.md +11 -0
- package/docs/handbook/en/ai-writing.md +17 -20
- package/docs/handbook/en/components/compose.md +15 -6
- package/docs/handbook/en/components/image.md +1 -1
- package/docs/handbook/en/components/link-card.md +2 -2
- package/docs/handbook/en/components.md +7 -215
- package/docs/handbook/en/deploy.md +11 -0
- package/docs/handbook/en/index.md +29 -53
- package/docs/handbook/en/reference/compose.md +77 -148
- package/docs/handbook/en/reference.md +6 -22
- package/docs/handbook/en/start.md +1 -1
- package/docs/handbook/en/workflow.md +21 -17
- package/docs/handbook/en/writing.md +3 -3
- package/docs/handbook/index.md +26 -47
- package/docs/handbook/reference/compose.md +79 -148
- package/docs/handbook/reference.md +6 -22
- package/docs/handbook/start.md +1 -1
- package/docs/handbook/workflow.md +21 -17
- package/docs/handbook/writing.md +3 -3
- package/editors/dist/typora/main.js +324 -330
- package/editors/dist/typora/style.css +4299 -11
- package/editors/dist/vscode/garden-ui-0.14.2.vsix +0 -0
- package/eslint.config.mjs +3 -0
- package/package.json +5 -3
- package/public/images/compose-resources/resource-015b41eb0f32.svg +1 -0
- package/public/images/compose-resources/resource-ecee6b47f015.svg +1 -0
- package/public/images/compose-resources/resource-fd51121b5e8c.svg +1 -0
- package/scripts/build-content.mjs +40 -131
- package/scripts/build-ui-preview.mjs +7 -5
- package/scripts/check-post.mjs +10 -106
- package/scripts/check-site.mjs +80 -0
- package/scripts/compose-style-reference.mjs +22 -0
- package/scripts/editor-preview-css.mjs +14 -0
- package/scripts/file-preview.mjs +1 -1
- package/scripts/file-sizes.mjs +7 -7
- package/scripts/generated-cache.mjs +36 -0
- package/scripts/handbook-facts.mjs +10 -3
- package/scripts/image-sizes.mjs +6 -12
- package/scripts/link-assets.mjs +35 -10
- package/scripts/managed-skills.mjs +56 -0
- package/scripts/pack-editors.mjs +12 -1
- package/scripts/prepare-pages-output.mjs +1 -1
- package/scripts/site-guide.mjs +19 -0
- package/scripts/sync-editor-version.mjs +28 -0
- package/scripts/ui-cli.mjs +26 -33
- package/templates/garden-blocks.md +4 -180
- package/templates/garden-snippets.plist +2 -183
- package/templates/site/AGENTS.en.md +89 -0
- package/templates/site/AGENTS.md +72 -0
- package/templates/site/Makefile +12 -3
- package/templates/site/Makefile.en +12 -3
- package/templates/site/README.en.md +6 -1
- package/templates/site/README.md +6 -1
- package/templates/skills-en/garden-compose/SKILL.md +24 -0
- package/templates/skills-en/garden-post-review/SKILL.md +5 -16
- package/templates/workflows/pages.en.yml +11 -0
- package/templates/workflows/pages.yml +11 -0
- package/ui/compose/compose.css +54 -14
- package/ui/compose/contracts.mjs +16 -0
- package/ui/compose/dil.mjs +109 -48
- package/ui/compose/distribution.mjs +7 -0
- package/ui/compose/features.mjs +90 -0
- package/ui/compose/icons.mjs +8 -0
- package/ui/compose/index.mjs +2 -0
- package/ui/compose/inspect.mjs +11 -0
- package/ui/compose/markup.mjs +80 -23
- package/ui/compose/preview.mjs +13 -0
- package/ui/compose/prompt-catalog.mjs +15 -3
- package/ui/compose/render.mjs +46 -11
- package/ui/compose/scenes.mjs +10 -0
- package/ui/core/apply.mjs +12 -45
- package/ui/core/catalog.mjs +22 -32
- package/ui/core/cli-locale.mjs +8 -18
- package/ui/core/color.mjs +34 -0
- package/ui/core/compile.mjs +1 -8
- package/ui/core/demos.mjs +138 -108
- package/ui/core/fence.mjs +14 -70
- package/ui/core/fix-prompt.mjs +1 -3
- package/ui/core/garden-fence.mjs +3 -63
- package/ui/core/index.mjs +3 -5
- package/ui/core/parse.mjs +22 -8
- package/ui/core/scan.mjs +31 -43
- package/ui/core/starters.mjs +3 -3
- package/ui/core/static.mjs +36 -6
- package/ui/core/transform.mjs +64 -149
- package/ui/core/validate.mjs +20 -9
- package/ui/garden/article-content.mjs +98 -0
- package/ui/garden/catalog.mjs +21 -427
- package/ui/garden/check.mjs +18 -60
- package/ui/garden/file-block.mjs +1 -1
- package/ui/garden/file-paths.mjs +8 -6
- package/ui/garden/parse.mjs +19 -182
- package/ui/garden/relations.mjs +1 -1
- package/ui/garden/snippets.mjs +12 -153
- package/ui/garden/source-icon.mjs +17 -9
- package/ui/garden/word-count.mjs +16 -85
- package/ui/index.mjs +1 -5
- package/ui/preview/dist/garden-ui.css +4300 -11
- package/ui/preview/dist/garden-ui.esm.js +446 -418
- package/ui/preview/dist/garden-ui.iife.js +446 -418
- package/ui/preview/index.tsx +31 -98
- package/ui/preview/site-config.ts +4 -0
- package/ui/prompt/build.mjs +1 -4
- package/ui/prompt/recipes/code-walkthrough.md +17 -12
- package/ui/prompt/recipes/compare.md +14 -10
- package/ui/prompt/recipes/compose.md +3 -3
- package/ui/prompt/recipes/converter.md +17 -14
- package/ui/prompt/recipes/data-chart.md +9 -8
- package/ui/prompt/recipes/inspect-grid.md +9 -8
- package/ui/prompt/recipes/param-bench.md +17 -12
- package/ui/prompt/recipes/plan.md +4 -3
- package/ui/prompt/recipes/stepper.md +18 -16
- package/ui/prompt/recipes/trace.md +20 -18
- package/ui/prompt/rules.md +26 -133
- package/ui/prompt/studio.md +3 -18
- package/ui/react/CatalogPage.tsx +19 -21
- package/ui/react/recipes.ts +3 -3
- package/ui/react/runtime.tsx +23 -2
- package/ui/react/ui.css +17 -11
- package/vite.config.ts +16 -3
- package/.agents/skills/garden-interactive-chart/SKILL.md +0 -84
- package/.agents/skills/garden-interactive-chart/references/shell.html +0 -49
- package/.agents/skills/garden-interactive-chart/references/style.md +0 -61
- package/.agents/skills/garden-ui/SKILL.md +0 -125
- package/app/components/UiBlock.tsx +0 -23
- package/app/components/WidgetChart.tsx +0 -224
- package/app/lib/generated-content.ts +0 -385
- package/docs/handbook/components/card.md +0 -67
- package/docs/handbook/components/chart.md +0 -38
- package/docs/handbook/components/choice.md +0 -42
- package/docs/handbook/components/compare.md +0 -53
- package/docs/handbook/components/file.md +0 -29
- package/docs/handbook/components/html.md +0 -49
- package/docs/handbook/components/interactive.md +0 -48
- package/docs/handbook/components/list.md +0 -65
- package/docs/handbook/components/relations.md +0 -95
- package/docs/handbook/components/stat.md +0 -39
- package/docs/handbook/components/timeline.md +0 -37
- package/docs/handbook/en/components/card.md +0 -67
- package/docs/handbook/en/components/chart.md +0 -38
- package/docs/handbook/en/components/choice.md +0 -42
- package/docs/handbook/en/components/compare.md +0 -53
- package/docs/handbook/en/components/file.md +0 -29
- package/docs/handbook/en/components/html.md +0 -49
- package/docs/handbook/en/components/interactive.md +0 -44
- package/docs/handbook/en/components/list.md +0 -65
- package/docs/handbook/en/components/relations.md +0 -95
- package/docs/handbook/en/components/stat.md +0 -39
- package/docs/handbook/en/components/timeline.md +0 -37
- package/docs/handbook/en/reference/card.md +0 -85
- package/docs/handbook/en/reference/chart.md +0 -68
- package/docs/handbook/en/reference/choice.md +0 -69
- package/docs/handbook/en/reference/compare.md +0 -59
- package/docs/handbook/en/reference/file.md +0 -50
- package/docs/handbook/en/reference/html.md +0 -62
- package/docs/handbook/en/reference/interactive.md +0 -71
- package/docs/handbook/en/reference/list.md +0 -72
- package/docs/handbook/en/reference/relations.md +0 -113
- package/docs/handbook/en/reference/stat.md +0 -60
- package/docs/handbook/en/reference/timeline.md +0 -56
- package/docs/handbook/reference/card.md +0 -85
- package/docs/handbook/reference/chart.md +0 -68
- package/docs/handbook/reference/choice.md +0 -69
- package/docs/handbook/reference/compare.md +0 -59
- package/docs/handbook/reference/file.md +0 -50
- package/docs/handbook/reference/html.md +0 -62
- package/docs/handbook/reference/interactive.md +0 -71
- package/docs/handbook/reference/list.md +0 -72
- package/docs/handbook/reference/relations.md +0 -113
- package/docs/handbook/reference/stat.md +0 -60
- package/docs/handbook/reference/timeline.md +0 -56
- package/editors/dist/vscode/garden-ui-0.1.1.vsix +0 -0
- package/templates/skills-en/garden-interactive-chart/SKILL.md +0 -84
- package/templates/skills-en/garden-interactive-chart/references/shell.html +0 -49
- package/templates/skills-en/garden-interactive-chart/references/style.md +0 -61
- package/templates/skills-en/garden-ui/SKILL.md +0 -125
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
import { AuthorRich as Rich } from "./AuthorRich";
|
|
2
3
|
|
|
3
|
-
import {
|
|
4
|
-
import ReactMarkdown from "react-markdown";
|
|
4
|
+
import { type ReactNode } from "react";
|
|
5
5
|
import { useUi, useUiLang } from "../UiLang";
|
|
6
|
-
import { langOf, ui } from "../../lib/i18n";
|
|
7
|
-
import { interactiveUiSource } from "../../../ui/core/garden-fence.mjs";
|
|
8
|
-
import { markdownPlugins } from "../../lib/markdown-plugins";
|
|
9
6
|
import { compileUiBlock } from "../../../ui/core/compile.mjs";
|
|
10
7
|
import { splitCompareLine } from "../../../ui/garden/compare-line.mjs";
|
|
11
8
|
import { RuntimeLangProvider, UiErrorCard, UiFigure, type Diagnostic, type UiPayload } from "../../../ui/react";
|
|
12
|
-
import { FullscreenFrame
|
|
9
|
+
import { FullscreenFrame } from "./chrome";
|
|
13
10
|
import { SourceMark, VerdictIcon } from "./patterns";
|
|
14
11
|
|
|
15
12
|
const BASE_PATH = (process.env.NEXT_PUBLIC_BASE_PATH ?? "").replace(/\/+$/u, "");
|
|
@@ -20,13 +17,7 @@ function publicSrc(src: string) {
|
|
|
20
17
|
return `${BASE_PATH}${src}`;
|
|
21
18
|
}
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
return (
|
|
25
|
-
<ReactMarkdown remarkPlugins={markdownPlugins} components={{ p: ({ children }) => <>{children}</> }}>
|
|
26
|
-
{String(text ?? "")}
|
|
27
|
-
</ReactMarkdown>
|
|
28
|
-
);
|
|
29
|
-
}
|
|
20
|
+
|
|
30
21
|
|
|
31
22
|
function hostOf(url: string) {
|
|
32
23
|
try {
|
|
@@ -265,188 +256,11 @@ export function GardenChart({ props }: { props: Record<string, unknown> }) {
|
|
|
265
256
|
);
|
|
266
257
|
}
|
|
267
258
|
|
|
268
|
-
function
|
|
269
|
-
return /<script\b(?![^>]*\btype\s*=\s*["']application\/json["'])/iu.test(source);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
function isLightWidget(source: string) {
|
|
273
|
-
return /data-g="(?:quiz|calc|tabs|stepper|kvplay)"|class="[^"]*\b(?:g-steps|g-faq|g-quiz|g-tabs|g-play|g-calc)\b/iu.test(source);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
function bindGardenWidgets(root: HTMLElement) {
|
|
277
|
-
const attr = root.closest("[lang]")?.getAttribute("lang");
|
|
278
|
-
const t = ui(attr ? langOf(attr) : undefined);
|
|
279
|
-
root.querySelectorAll<HTMLElement>("[data-g=tabs]").forEach((widget) => {
|
|
280
|
-
const list = widget.querySelector<HTMLElement>(":scope > [role=tablist]");
|
|
281
|
-
const tabs = [...(list?.querySelectorAll<HTMLElement>(":scope > [role=tab]") ?? [])];
|
|
282
|
-
const select = (index: number, focus = false) => {
|
|
283
|
-
tabs.forEach((tab, tabIndex) => {
|
|
284
|
-
const on = index === tabIndex;
|
|
285
|
-
tab.setAttribute("aria-selected", String(on));
|
|
286
|
-
tab.tabIndex = on ? 0 : -1;
|
|
287
|
-
const panel = document.getElementById(tab.getAttribute("aria-controls") || "");
|
|
288
|
-
if (panel) panel.hidden = !on;
|
|
289
|
-
});
|
|
290
|
-
if (focus) tabs[index]?.focus();
|
|
291
|
-
};
|
|
292
|
-
tabs.forEach((tab, index) => {
|
|
293
|
-
tab.addEventListener("click", () => select(index));
|
|
294
|
-
tab.addEventListener("keydown", (event) => {
|
|
295
|
-
const count = tabs.length;
|
|
296
|
-
const next = event.key === "ArrowRight" ? (index + 1) % count
|
|
297
|
-
: event.key === "ArrowLeft" ? (index - 1 + count) % count
|
|
298
|
-
: event.key === "Home" ? 0
|
|
299
|
-
: event.key === "End" ? count - 1 : -1;
|
|
300
|
-
if (next < 0) return;
|
|
301
|
-
event.preventDefault();
|
|
302
|
-
select(next, true);
|
|
303
|
-
});
|
|
304
|
-
});
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
root.querySelectorAll<HTMLFormElement>("[data-g=quiz]").forEach((form) => {
|
|
308
|
-
const out = form.querySelector<HTMLElement>(".g-quiz-out");
|
|
309
|
-
const explain = form.querySelector<HTMLElement>(".g-quiz-ex");
|
|
310
|
-
form.addEventListener("submit", (event) => {
|
|
311
|
-
event.preventDefault();
|
|
312
|
-
if (!out) return;
|
|
313
|
-
const chosen = form.querySelector<HTMLInputElement>("input:checked");
|
|
314
|
-
if (!chosen) {
|
|
315
|
-
out.className = "g-quiz-out";
|
|
316
|
-
out.textContent = t.pickAnswer;
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
319
|
-
const correct = chosen.value === form.dataset.answer;
|
|
320
|
-
out.className = `g-quiz-out ${correct ? "ok" : "no"}`;
|
|
321
|
-
out.textContent = correct ? t.correct : t.wrong;
|
|
322
|
-
if (explain) explain.hidden = !correct;
|
|
323
|
-
});
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
root.querySelectorAll<HTMLElement>("[data-g=stepper]").forEach((stepper) => {
|
|
327
|
-
const panels = [...stepper.querySelectorAll<HTMLElement>("[data-step]")];
|
|
328
|
-
const dots = [...stepper.querySelectorAll<HTMLElement>(".g-stepper-dots i")];
|
|
329
|
-
const count = stepper.querySelector(".g-stepper-n");
|
|
330
|
-
const title = stepper.querySelector(".g-stepper-t");
|
|
331
|
-
const prev = stepper.querySelector<HTMLButtonElement>("[data-prev]");
|
|
332
|
-
const next = stepper.querySelector<HTMLButtonElement>("[data-next]");
|
|
333
|
-
let index = 0;
|
|
334
|
-
const show = () => {
|
|
335
|
-
panels.forEach((panel, panelIndex) => { panel.hidden = panelIndex !== index; });
|
|
336
|
-
if (count) count.textContent = `${index + 1} / ${panels.length}`;
|
|
337
|
-
if (title) title.textContent = panels[index]?.dataset.title || "";
|
|
338
|
-
dots.forEach((dot, dotIndex) => dot.classList.toggle("on", dotIndex <= index));
|
|
339
|
-
if (prev) prev.disabled = index === 0;
|
|
340
|
-
if (next) next.disabled = index === panels.length - 1;
|
|
341
|
-
};
|
|
342
|
-
prev?.addEventListener("click", () => { if (index > 0) { index -= 1; show(); } });
|
|
343
|
-
next?.addEventListener("click", () => { if (index < panels.length - 1) { index += 1; show(); } });
|
|
344
|
-
show();
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
root.querySelectorAll<HTMLElement>("[data-g=calc]").forEach((calc) => {
|
|
348
|
-
const raw = calc.querySelector("script[type='application/json'], script[type=\"application/json\"]")?.textContent;
|
|
349
|
-
if (!raw) return;
|
|
350
|
-
const spec = JSON.parse(raw) as {
|
|
351
|
-
formula: string;
|
|
352
|
-
vars: Record<string, { fixed?: number; values?: number[]; label?: string }>;
|
|
353
|
-
format?: string;
|
|
354
|
-
unit?: string;
|
|
355
|
-
show?: string;
|
|
356
|
-
};
|
|
357
|
-
const names = Object.keys(spec.vars);
|
|
358
|
-
const fn = new Function(...names, `return (${spec.formula})`) as (...args: number[]) => number;
|
|
359
|
-
const read = (key: string) => {
|
|
360
|
-
const variable = spec.vars[key];
|
|
361
|
-
if (variable?.fixed !== undefined) return Number(variable.fixed);
|
|
362
|
-
const field = calc.querySelector<HTMLInputElement | HTMLSelectElement>(`[data-k="${key}"]:checked`)
|
|
363
|
-
?? calc.querySelector<HTMLInputElement | HTMLSelectElement>(`[data-k="${key}"]`);
|
|
364
|
-
if (!field) return 0;
|
|
365
|
-
if (variable?.values && field instanceof HTMLInputElement && field.type === "range") {
|
|
366
|
-
const value = variable.values[Number(field.value)] ?? variable.values[0];
|
|
367
|
-
const output = field.parentElement?.querySelector("output");
|
|
368
|
-
if (output) output.textContent = String(value);
|
|
369
|
-
field.setAttribute("aria-valuetext", String(value));
|
|
370
|
-
return Number(value);
|
|
371
|
-
}
|
|
372
|
-
const value = Number(field.value);
|
|
373
|
-
const output = field.parentElement?.querySelector("output");
|
|
374
|
-
if (output && field instanceof HTMLInputElement && field.type === "range") output.textContent = field.value;
|
|
375
|
-
return value;
|
|
376
|
-
};
|
|
377
|
-
const run = () => {
|
|
378
|
-
const values = Object.fromEntries(names.map((key) => [key, read(key)]));
|
|
379
|
-
const result = fn(...names.map((key) => values[key]));
|
|
380
|
-
const out = calc.querySelector("[data-out]");
|
|
381
|
-
if (out) {
|
|
382
|
-
out.textContent = spec.format === "GB"
|
|
383
|
-
? `${(result / 2 ** 30).toFixed(2)} GB`
|
|
384
|
-
: `${Math.round(result * 1e4) / 1e4}${spec.unit ? ` ${spec.unit}` : ""}`;
|
|
385
|
-
}
|
|
386
|
-
const equation = calc.querySelector("[data-calc]");
|
|
387
|
-
if (equation && spec.show) {
|
|
388
|
-
const shown = spec.show.replace(/\{(\w+)\}/g, (_, key: string) => String(values[key]));
|
|
389
|
-
equation.textContent = `${shown} = ${spec.format === "GB" ? `${Math.round(result).toLocaleString("en-US")} ${t.bytes}` : String(Math.round(result * 1e4) / 1e4)}`;
|
|
390
|
-
}
|
|
391
|
-
};
|
|
392
|
-
calc.addEventListener("input", run);
|
|
393
|
-
calc.addEventListener("change", run);
|
|
394
|
-
run();
|
|
395
|
-
});
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
function StaticGardenHtml({ code }: { code: string }) {
|
|
399
|
-
const ref = useRef<HTMLDivElement>(null);
|
|
400
|
-
useEffect(() => {
|
|
401
|
-
const host = ref.current;
|
|
402
|
-
if (!host) return;
|
|
403
|
-
const root = host.shadowRoot ?? host.attachShadow({ mode: "open" });
|
|
404
|
-
root.innerHTML = code;
|
|
405
|
-
}, [code]);
|
|
406
|
-
return (
|
|
407
|
-
<div
|
|
408
|
-
className="g-html"
|
|
409
|
-
ref={ref}
|
|
410
|
-
suppressHydrationWarning
|
|
411
|
-
dangerouslySetInnerHTML={{ __html: `<template shadowrootmode="open">${code}</template>` }}
|
|
412
|
-
/>
|
|
413
|
-
);
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
function LightGardenHtml({ code }: { code: string }) {
|
|
417
|
-
const ref = useRef<HTMLDivElement>(null);
|
|
418
|
-
useEffect(() => {
|
|
419
|
-
const host = ref.current;
|
|
420
|
-
if (host) bindGardenWidgets(host);
|
|
421
|
-
}, [code]);
|
|
422
|
-
return <div className="g-widget" ref={ref} dangerouslySetInnerHTML={{ __html: code }} />;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
export function GardenHtml({ props, children }: { props: Record<string, unknown>; children?: string }) {
|
|
426
|
-
const code = typeof props.code === "string" ? props.code : children || "";
|
|
427
|
-
const t = useUi();
|
|
428
|
-
const title = String(props.title ?? "HTML");
|
|
429
|
-
if (!code && typeof props.src === "string") {
|
|
430
|
-
return <p className="g-embed-note">{t.htmlAt(String(props.src))}</p>;
|
|
431
|
-
}
|
|
432
|
-
if (htmlHasScript(code)) {
|
|
433
|
-
return <SandboxFrame html={code} title={title} />;
|
|
434
|
-
}
|
|
435
|
-
if (isLightWidget(code)) {
|
|
436
|
-
const view = <LightGardenHtml code={code} />;
|
|
437
|
-
return /class="[^"]*\bg-play\b|data-g="calc"/iu.test(code) ? <FullscreenFrame label={title}>{view}</FullscreenFrame> : view;
|
|
438
|
-
}
|
|
439
|
-
return <FullscreenFrame label={title}><StaticGardenHtml code={code} /></FullscreenFrame>;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
export function GardenInteractive({ block }: { block: { id?: string | null; props: Record<string, unknown>; children?: string; uiSource?: string } }) {
|
|
259
|
+
export function GardenInteractive({ block, renderScene }: { renderScene?: (source: string) => React.ReactNode; block: { id?: string | null; props: Record<string, unknown>; children?: string; uiSource?: string; payload?: UiPayload | null } }) {
|
|
443
260
|
const t = useUi();
|
|
444
261
|
const lang = useUiLang();
|
|
445
|
-
|
|
446
|
-
const
|
|
447
|
-
const source = typeof block.uiSource === "string" && block.uiSource.trim() ? block.uiSource : built.source;
|
|
448
|
-
const header = built.header;
|
|
449
|
-
const { payload, check } = compileUiBlock(source);
|
|
262
|
+
const { payload, check } = block.payload ? {payload:block.payload,check:{header:{id:block.payload.id,caption:block.payload.caption},diagnostics:[]}} : compileUiBlock(block.uiSource ?? "");
|
|
263
|
+
const header = {id:payload?.id??block.id,caption:payload?.caption??block.props.caption};
|
|
450
264
|
if (!payload) {
|
|
451
265
|
return (
|
|
452
266
|
<RuntimeLangProvider lang={lang}>
|
|
@@ -462,7 +276,7 @@ export function GardenInteractive({ block }: { block: { id?: string | null; prop
|
|
|
462
276
|
<RuntimeLangProvider lang={lang}>
|
|
463
277
|
<FullscreenFrame label={String(header.caption || t.interactive)}>
|
|
464
278
|
<div className="g-play">
|
|
465
|
-
<UiFigure payload={payload as UiPayload} />
|
|
279
|
+
<UiFigure payload={payload as UiPayload} host={renderScene ? {renderScene} : undefined} />
|
|
466
280
|
</div>
|
|
467
281
|
</FullscreenFrame>
|
|
468
282
|
</RuntimeLangProvider>
|
|
@@ -479,9 +293,7 @@ export function GardenIdea({ text }: { text: string }) {
|
|
|
479
293
|
);
|
|
480
294
|
}
|
|
481
295
|
|
|
482
|
-
|
|
483
|
-
return <p className="g-embed-note">{suggestion}</p>;
|
|
484
|
-
}
|
|
296
|
+
|
|
485
297
|
|
|
486
298
|
export function GardenUnknown({ name }: { name: string }) {
|
|
487
299
|
const t = useUi();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { safeFileHref } from "../../../ui/garden/file-block.mjs";
|
|
2
2
|
|
|
3
|
-
// Icons for
|
|
3
|
+
// Icons for Compose file. No hooks: used by the server card and the client drawer alike.
|
|
4
4
|
const PAGE = "M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z";
|
|
5
5
|
const FOLD = "M14 3v5h5";
|
|
6
6
|
const MARKS: Record<string, string> = {
|
|
@@ -1,76 +1,18 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
import { hasIcon, iconSvg } from "../../../ui/compose/icons.mjs";
|
|
3
|
+
import { AuthorRich as Rich } from "./AuthorRich";
|
|
2
4
|
|
|
3
|
-
import { createContext, useContext, useEffect, useId, useRef, useState, type
|
|
5
|
+
import { createContext, useContext, useEffect, useId, useRef, useState, type CSSProperties, type ReactNode } from "react";
|
|
4
6
|
import { sourceIcon } from "../../../ui/garden/source-icon.mjs";
|
|
5
7
|
import { circled, labelFits, legendLine, placeMarks, relationKeyAction, RELATION_COMPACT_WIDTH, RELATION_LABEL_FONT_MIN, relationEdges, relationHeights, relationIndex, relationLayout, relationLines, type RelationEdge as Edge } from "../../../ui/garden/relations.mjs";
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
Bell,
|
|
9
|
-
BookOpen,
|
|
10
|
-
ChartBar,
|
|
11
|
-
ChatCircle,
|
|
12
|
-
CheckCircle,
|
|
13
|
-
Clock,
|
|
14
|
-
Eye,
|
|
15
|
-
FileText,
|
|
16
|
-
Flag,
|
|
17
|
-
Flask,
|
|
18
|
-
Gear,
|
|
19
|
-
Heart,
|
|
20
|
-
Lightbulb,
|
|
21
|
-
LinkSimple,
|
|
22
|
-
MagnifyingGlass,
|
|
23
|
-
MapPin,
|
|
24
|
-
Question,
|
|
25
|
-
ShieldCheck,
|
|
26
|
-
Star,
|
|
27
|
-
Target,
|
|
28
|
-
User,
|
|
29
|
-
Wallet,
|
|
30
|
-
XCircle,
|
|
31
|
-
type IconProps,
|
|
32
|
-
} from "@phosphor-icons/react/ssr";
|
|
8
|
+
import {CheckCircle, XCircle} from "@phosphor-icons/react/ssr";
|
|
33
9
|
import { useUi, useUiLang } from "../UiLang";
|
|
34
|
-
import { markdownPlugins } from "../../lib/markdown-plugins";
|
|
35
10
|
|
|
36
|
-
function Rich({ text }: { text: string }) {
|
|
37
|
-
return (
|
|
38
|
-
<ReactMarkdown remarkPlugins={markdownPlugins} components={{ p: ({ children }) => <>{children}</> }}>
|
|
39
|
-
{String(text ?? "")}
|
|
40
|
-
</ReactMarkdown>
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
11
|
|
|
44
|
-
/** Line icons an author can name in `icon:`. Unknown names draw nothing. */
|
|
45
|
-
export const GARDEN_ICONS: Record<string, ComponentType<IconProps>> = {
|
|
46
|
-
search: MagnifyingGlass,
|
|
47
|
-
eye: Eye,
|
|
48
|
-
heart: Heart,
|
|
49
|
-
chat: ChatCircle,
|
|
50
|
-
shield: ShieldCheck,
|
|
51
|
-
wallet: Wallet,
|
|
52
|
-
bell: Bell,
|
|
53
|
-
check: CheckCircle,
|
|
54
|
-
x: XCircle,
|
|
55
|
-
star: Star,
|
|
56
|
-
flag: Flag,
|
|
57
|
-
clock: Clock,
|
|
58
|
-
idea: Lightbulb,
|
|
59
|
-
book: BookOpen,
|
|
60
|
-
link: LinkSimple,
|
|
61
|
-
user: User,
|
|
62
|
-
chart: ChartBar,
|
|
63
|
-
gear: Gear,
|
|
64
|
-
file: FileText,
|
|
65
|
-
pin: MapPin,
|
|
66
|
-
flask: Flask,
|
|
67
|
-
target: Target,
|
|
68
|
-
question: Question,
|
|
69
|
-
};
|
|
70
12
|
|
|
71
|
-
export function GardenIcon({
|
|
72
|
-
|
|
73
|
-
return
|
|
13
|
+
export function GardenIcon({name,size=18}:{name:unknown;size?:number}) {
|
|
14
|
+
if(!hasIcon(String(name)))return null;
|
|
15
|
+
return <span aria-hidden="true" dangerouslySetInnerHTML={{__html:iconSvg(String(name)).replace('class="g-compose-icon"',`class="g-compose-icon" style="width:${size}px;height:${size}px"`)}}/>;
|
|
74
16
|
}
|
|
75
17
|
|
|
76
18
|
const textsOf = (value: unknown) => (value == null ? [] : Array.isArray(value) ? value.map(String) : [String(value)]);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const FLOW_TAGS = new Set(['garden-block', 'garden-refdemo', 'garden-codediff']);
|
|
2
|
+
|
|
3
|
+
/** Custom elements are phrasing content to the HTML parser, but render as flow.
|
|
4
|
+
* Lift them before React renders them into figures/divs inside a paragraph. */
|
|
5
|
+
export function rehypeGardenBlocks() {
|
|
6
|
+
return tree => {
|
|
7
|
+
function visit(parent) {
|
|
8
|
+
if (!Array.isArray(parent.children)) return;
|
|
9
|
+
parent.children = parent.children.flatMap(node => {
|
|
10
|
+
visit(node);
|
|
11
|
+
if (node.type !== 'element' || node.tagName !== 'p' || !node.children.some(child => child.type === 'element' && FLOW_TAGS.has(child.tagName))) return [node];
|
|
12
|
+
const result = [];
|
|
13
|
+
let prose = [];
|
|
14
|
+
const flush = () => {
|
|
15
|
+
if (prose.some(child => child.type !== 'text' || child.value.trim())) result.push({...node, children:prose});
|
|
16
|
+
prose = [];
|
|
17
|
+
};
|
|
18
|
+
for (const child of node.children) {
|
|
19
|
+
if (child.type === 'element' && FLOW_TAGS.has(child.tagName)) {
|
|
20
|
+
flush();
|
|
21
|
+
result.push(child);
|
|
22
|
+
} else prose.push(child);
|
|
23
|
+
}
|
|
24
|
+
flush();
|
|
25
|
+
return result;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
visit(tree);
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -94,10 +94,6 @@ function rewriteChildren(node, nextScope) {
|
|
|
94
94
|
if (child?.type === "code") {
|
|
95
95
|
const record = fenceNodeToRecord(child);
|
|
96
96
|
const kind = classifyFence({ ...record, body: child.value, line: 1, endLine: 1 });
|
|
97
|
-
if (kind.kind === "legacy") {
|
|
98
|
-
next.push(gardenElement({ legacy: true, from: kind.lang || record.lang, suggestion: kind.suggestion }));
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
97
|
if (kind.kind === "garden" && kind.block?.name === "compose" && !kind.block.error && kind.block.mode !== "play") {
|
|
102
98
|
next.push(htmlElement(renderCompose(kind.block.props, { scope: nextScope() })));
|
|
103
99
|
continue;
|
package/app/lib/ui-model.ts
CHANGED
|
@@ -9,8 +9,8 @@ import converter from "../../ui/prompt/recipes/converter.md?raw";
|
|
|
9
9
|
import plan from "../../ui/prompt/recipes/plan.md?raw";
|
|
10
10
|
import dataChart from "../../ui/prompt/recipes/data-chart.md?raw";
|
|
11
11
|
import codeWalkthrough from "../../ui/prompt/recipes/code-walkthrough.md?raw";
|
|
12
|
-
import { findBlocks
|
|
13
|
-
import {
|
|
12
|
+
import { findBlocks } from "../../ui/core/fence.mjs";
|
|
13
|
+
import { checkComposeFence } from "../../ui/core/scan.mjs";
|
|
14
14
|
import { buildPrompt, RECIPE_FILES } from "../../ui/prompt/build.mjs";
|
|
15
15
|
|
|
16
16
|
const recipeText: Record<string, string> = {
|
|
@@ -91,7 +91,7 @@ async function complete(messages: Message[]) {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
function extract(content: string) {
|
|
94
|
-
const fence = findBlocks(content).find((block) => block.lang === "
|
|
94
|
+
const fence = findBlocks(content).find((block) => block.lang === "garden-compose" && block.closed);
|
|
95
95
|
const before = fence ? content.slice(0, fence.start) : content;
|
|
96
96
|
const note = /^提醒[::]\s*(.+)$/mu.exec(before)?.[1]?.trim() ?? null;
|
|
97
97
|
return { fence, note };
|
|
@@ -122,14 +122,13 @@ export async function draftUiBlock(input: { mode: "create" | "revise"; instructi
|
|
|
122
122
|
const { fence, note } = extract(reply.content);
|
|
123
123
|
if (!fence) {
|
|
124
124
|
if (round === 2) break;
|
|
125
|
-
messages.push({ role: "assistant", content: reply.content }, { role: "user", content: "回复里没有
|
|
125
|
+
messages.push({ role: "assistant", content: reply.content }, { role: "user", content: "回复里没有 garden compose 围栏。请只回复一个完整的 garden compose 围栏。" });
|
|
126
126
|
continue;
|
|
127
127
|
}
|
|
128
128
|
// lineOffset 1: in the workbench editor the fence starts on line 1.
|
|
129
|
-
const check =
|
|
129
|
+
const check = checkComposeFence(fence, {skipData:true});
|
|
130
130
|
const errors = (check.diagnostics as DraftDiagnostic[]).filter((item) => item.severity === "error");
|
|
131
|
-
const
|
|
132
|
-
const stamped = typeof header?.intent === "string" ? setCompiled(fence.raw, intentHash(header.intent)) : fence.raw;
|
|
131
|
+
const stamped = fence.raw;
|
|
133
132
|
if (!errors.length || round === 2) {
|
|
134
133
|
return { fence: stamped, note, diagnostics: check.diagnostics as DraftDiagnostic[], rounds: round, usage };
|
|
135
134
|
}
|
|
@@ -137,9 +136,9 @@ export async function draftUiBlock(input: { mode: "create" | "revise"; instructi
|
|
|
137
136
|
{ role: "assistant", content: reply.content },
|
|
138
137
|
{
|
|
139
138
|
role: "user",
|
|
140
|
-
content: `程序校验发现这些问题:\n${errors.map((item) => `- 第 ${item.line} 行 ${item.code}:${item.message}`).join("\n")}\n请修正后重新给出完整的
|
|
139
|
+
content: `程序校验发现这些问题:\n${errors.map((item) => `- 第 ${item.line} 行 ${item.code}:${item.message}`).join("\n")}\n请修正后重新给出完整的 garden compose 围栏,格式不变。`,
|
|
141
140
|
},
|
|
142
141
|
);
|
|
143
142
|
}
|
|
144
|
-
return { fence: null, note: null, diagnostics: [], rounds: 2, usage, error: "模型没有给出
|
|
143
|
+
return { fence: null, note: null, diagnostics: [], rounds: 2, usage, error: "模型没有给出 garden compose 围栏", raw: lastContent };
|
|
145
144
|
}
|
package/app/styles/a2.css
CHANGED
|
@@ -367,6 +367,18 @@ html[data-theme="dark"] .column-reading-page .e-content {
|
|
|
367
367
|
margin-top: 28px;
|
|
368
368
|
}
|
|
369
369
|
|
|
370
|
+
/* H4 sits below H3: same sans family, smaller size, still bold. */
|
|
371
|
+
.markdown-body h4 {
|
|
372
|
+
font: 600 var(--fs-body) / 1.55 var(--sans);
|
|
373
|
+
color: var(--ink);
|
|
374
|
+
letter-spacing: 0;
|
|
375
|
+
margin: 28px 0 10px;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.markdown-body h3 + h4 {
|
|
379
|
+
margin-top: 18px;
|
|
380
|
+
}
|
|
381
|
+
|
|
370
382
|
.sec-no {
|
|
371
383
|
color: var(--blue);
|
|
372
384
|
font-variant-numeric: tabular-nums;
|
|
@@ -377,6 +389,10 @@ html[data-theme="dark"] .column-reading-page .e-content {
|
|
|
377
389
|
margin: 0 0 22px;
|
|
378
390
|
}
|
|
379
391
|
|
|
392
|
+
.markdown-body > :is(ul, ol) + p {
|
|
393
|
+
margin-top: 22px;
|
|
394
|
+
}
|
|
395
|
+
|
|
380
396
|
.markdown-body strong {
|
|
381
397
|
font-weight: 600;
|
|
382
398
|
color: var(--ink);
|
|
@@ -1631,7 +1647,7 @@ html[data-theme="dark"] .g-quiz-out.no { color: #f29aab; }
|
|
|
1631
1647
|
.g-range { grid-template-columns: 84px minmax(0, 1fr) 54px; gap: 8px; }
|
|
1632
1648
|
}
|
|
1633
1649
|
|
|
1634
|
-
/* ----
|
|
1650
|
+
/* ---- Compose files: a download card, a group of rows, a preview drawer ---- */
|
|
1635
1651
|
.gf { margin: 24px 0; border: 1px solid var(--rule); border-radius: 12px; background: transparent; font-family: var(--sans); }
|
|
1636
1652
|
.gf-title { padding: 14px 16px 0; font-size: 14px; font-weight: 600; line-height: 1.4; color: var(--muted); }
|
|
1637
1653
|
.gf-row { display: flex; align-items: center; gap: 14px; padding: 14px 16px; }
|
package/bin/garden.mjs
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import { randomBytes } from "node:crypto";
|
|
3
3
|
import { spawnSync } from "node:child_process";
|
|
4
4
|
import { startDev } from "../scripts/dev-server.mjs";
|
|
5
|
+
import { installSiteGuide } from "../scripts/site-guide.mjs";
|
|
6
|
+
import { installManagedSkills } from "../scripts/managed-skills.mjs";
|
|
5
7
|
import fs from "node:fs";
|
|
6
8
|
import path from "node:path";
|
|
7
9
|
import { createRequire } from "node:module";
|
|
@@ -202,6 +204,7 @@ async function ensureSiteKit(destinationRoot = siteRoot) {
|
|
|
202
204
|
const lang = siteCliLang(destinationRoot);
|
|
203
205
|
if (!samePath(destinationRoot, gardenPackageRoot)) {
|
|
204
206
|
const added = [];
|
|
207
|
+
if (installSiteGuide({ engineRoot: gardenPackageRoot, siteRoot: destinationRoot, lang })) added.push("AGENTS.md");
|
|
205
208
|
if (
|
|
206
209
|
copyIfMissing(
|
|
207
210
|
path.join(gardenPackageRoot, "templates", "site", lang === "en" ? "Makefile.en" : "Makefile"),
|
|
@@ -283,11 +286,17 @@ async function prepare() {
|
|
|
283
286
|
await ensureSiteKit();
|
|
284
287
|
}
|
|
285
288
|
|
|
286
|
-
async function sync() {
|
|
289
|
+
async function sync({ refreshLinks = false } = {}) {
|
|
287
290
|
if (!fs.existsSync(path.join(siteRoot, "site.config.yaml"))) {
|
|
288
291
|
fail(`当前目录不是 Garden 站点(缺少 site.config.yaml):${siteRoot}`);
|
|
289
292
|
}
|
|
290
293
|
await prepare();
|
|
294
|
+
// Same path as `garden links` / formal build: collect Markdown URLs (incl. drafts),
|
|
295
|
+
// refresh link-assets.json + public assets, then regenerate. Failures keep usable cache.
|
|
296
|
+
if (refreshLinks && process.env.GARDEN_OFFLINE !== "1") {
|
|
297
|
+
const links = spawnSync(process.execPath, [path.join(gardenPackageRoot, "scripts/link-assets.mjs"), "--dev"], { cwd: siteRoot, env: siteEnv(), stdio: "inherit" });
|
|
298
|
+
if (links.status !== 0) console.warn("链接资源:刷新失败,沿用可用缓存");
|
|
299
|
+
}
|
|
291
300
|
runNode("scripts/build-content.mjs");
|
|
292
301
|
}
|
|
293
302
|
|
|
@@ -320,7 +329,7 @@ function ensureDevGitHubLogin() {
|
|
|
320
329
|
async function runVinext(vinextArgs, { copyDist = false } = {}) {
|
|
321
330
|
const cwd = vinextRoot();
|
|
322
331
|
if (vinextArgs[0] === "dev") {
|
|
323
|
-
const { finished } = await startDev({ cwd, executable: resolveVinext(), args: vinextArgs.slice(1), env: siteEnv(), prepare: sync });
|
|
332
|
+
const { finished } = await startDev({ cwd, executable: resolveVinext(), args: vinextArgs.slice(1), env: siteEnv(), prepare: () => sync({ refreshLinks: true }) });
|
|
324
333
|
const result = await finished;
|
|
325
334
|
if (result.status !== 0) process.exit(result.status || 1);
|
|
326
335
|
return;
|
|
@@ -341,7 +350,7 @@ const EN_TEMPLATE_VARIANTS = new Set(["site.config.en.yaml", "Makefile.en", "REA
|
|
|
341
350
|
function copyTemplate(from, to) {
|
|
342
351
|
fs.mkdirSync(to, { recursive: true });
|
|
343
352
|
for (const entry of fs.readdirSync(from, { withFileTypes: true })) {
|
|
344
|
-
if (entry.name === ".git" || EN_TEMPLATE_VARIANTS.has(entry.name)) continue;
|
|
353
|
+
if (entry.name === ".git" || entry.name === "AGENTS.md" || entry.name === "AGENTS.en.md" || EN_TEMPLATE_VARIANTS.has(entry.name)) continue;
|
|
345
354
|
const source = path.join(from, entry.name);
|
|
346
355
|
const destination = path.join(to, entry.name);
|
|
347
356
|
if (entry.isDirectory()) copyTemplate(source, destination);
|
|
@@ -414,6 +423,7 @@ async function initSite() {
|
|
|
414
423
|
fs.copyFileSync(enWorkflow, workflowDest);
|
|
415
424
|
}
|
|
416
425
|
}
|
|
426
|
+
installSiteGuide({ engineRoot: gardenPackageRoot, siteRoot: destination, lang });
|
|
417
427
|
writeEngineDependency(destination);
|
|
418
428
|
process.stdout.write(lang === "en" ? `Created site: ${destination}\n` : `已创建站点:${destination}\n`);
|
|
419
429
|
installSkill(destination, { lang });
|
|
@@ -466,34 +476,10 @@ async function editorsCommand(argv) {
|
|
|
466
476
|
if (status) process.exit(status);
|
|
467
477
|
}
|
|
468
478
|
|
|
469
|
-
function
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
.filter((entry) => entry.isDirectory() && fs.existsSync(path.join(root, entry.name, "SKILL.md")))
|
|
474
|
-
.map((entry) => entry.name);
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
function installSkill(destinationRoot = siteRoot, { onlyMissing = false, lang = siteCliLang(destinationRoot) } = {}) {
|
|
478
|
-
const names = packagedSkills();
|
|
479
|
-
if (!names.length) fail("Garden 包里没有 skill。");
|
|
480
|
-
const installed = [];
|
|
481
|
-
for (const name of names) {
|
|
482
|
-
const to = path.join(destinationRoot, ".agents", "skills", name);
|
|
483
|
-
if (onlyMissing && fs.existsSync(path.join(to, "SKILL.md"))) continue;
|
|
484
|
-
fs.cpSync(path.join(gardenPackageRoot, ".agents", "skills", name), to, { recursive: true });
|
|
485
|
-
// English sites get the English skill text; files without a translation keep the packaged copy.
|
|
486
|
-
const english = path.join(gardenPackageRoot, "templates", "skills-en", name);
|
|
487
|
-
if (lang === "en" && fs.existsSync(english)) fs.cpSync(english, to, { recursive: true, force: true });
|
|
488
|
-
installed.push(name);
|
|
489
|
-
}
|
|
490
|
-
if (installed.length) {
|
|
491
|
-
process.stdout.write(
|
|
492
|
-
lang === "en"
|
|
493
|
-
? `Installed skills: ${installed.join(", ")}\n`
|
|
494
|
-
: `已安装 skill:${installed.join("、")}\n`,
|
|
495
|
-
);
|
|
496
|
-
}
|
|
479
|
+
function installSkill(destinationRoot = siteRoot, { lang = siteCliLang(destinationRoot) } = {}) {
|
|
480
|
+
// Managed kits update recursively even on automatic sync; local edits are backed up.
|
|
481
|
+
const installed = installManagedSkills({ engineRoot: gardenPackageRoot, siteRoot: destinationRoot, lang });
|
|
482
|
+
process.stdout.write(lang === "en" ? `Installed skills: ${installed.join(", ")}\n` : `已安装 skill:${installed.join("、")}\n`);
|
|
497
483
|
}
|
|
498
484
|
|
|
499
485
|
function help() {
|
|
@@ -504,14 +490,16 @@ function help() {
|
|
|
504
490
|
garden sync 只从 Markdown 生成索引
|
|
505
491
|
garden new 创建文章草稿
|
|
506
492
|
garden embed 拉取外链摘要
|
|
507
|
-
garden links
|
|
508
|
-
garden images 读取新的远程图片尺寸,写入 image-sizes.json(build 时会自动做)
|
|
509
|
-
garden files 读取
|
|
493
|
+
garden links 重新拉取外链图标与卡片元数据(dev / build 时也会自动做)
|
|
494
|
+
garden images 读取新的远程图片尺寸,写入 .garden/image-sizes.json(build 时会自动做)
|
|
495
|
+
garden files 读取 Compose <file> 里远程文件的大小,写入 file-sizes.json(build 时会自动做)
|
|
510
496
|
garden init [目录] 创建只含稿和配置的新站点
|
|
511
497
|
garden check <文章.md> 检查一篇博文的硬性结构
|
|
498
|
+
garden verify [文章.md|--diff] 全量/单篇/改动文档严格预检(包含草稿;make check 使用)
|
|
512
499
|
garden ui <子命令> Intelligent UI:scan、check、apply、prompt、schema、catalog
|
|
513
500
|
garden hooks 安装提交前检查(检查暂存稿件里的 ui 块)
|
|
514
501
|
garden editors install 安装 Intelligent UI 插件到 VS Code / Cursor 与 Typora(--vscode、--typora、--yes)
|
|
502
|
+
garden agents 仅在缺失时安装站点 AGENTS.md,保留已有指南
|
|
515
503
|
garden skill 把引擎自带的 skill 装进当前站点
|
|
516
504
|
garden pages:validate 检查 dist/client
|
|
517
505
|
garden pages:prepare 写入公开产物仓库
|
|
@@ -542,7 +530,7 @@ async function main() {
|
|
|
542
530
|
if (process.env.GARDEN_OFFLINE !== "1") runNode("scripts/link-assets.mjs");
|
|
543
531
|
// Remote image sizes: only new images are probed; the rest come from image-sizes.json.
|
|
544
532
|
if (process.env.GARDEN_OFFLINE !== "1") runNode("scripts/image-sizes.mjs");
|
|
545
|
-
// Sizes of remote files in
|
|
533
|
+
// Sizes of remote files in Compose file components; failures fall back to file-sizes.json.
|
|
546
534
|
if (process.env.GARDEN_OFFLINE !== "1") runNode("scripts/file-sizes.mjs");
|
|
547
535
|
await runVinext(["build", ...rest], { copyDist: true });
|
|
548
536
|
// /favicon.ico from the site's own icon (brand.favicon).
|
|
@@ -576,6 +564,9 @@ async function main() {
|
|
|
576
564
|
case "embed":
|
|
577
565
|
runNode("scripts/fetch-embed.mjs", rest);
|
|
578
566
|
break;
|
|
567
|
+
case "verify":
|
|
568
|
+
runNode("scripts/check-site.mjs", rest);
|
|
569
|
+
break;
|
|
579
570
|
case "check":
|
|
580
571
|
runNode("scripts/check-post.mjs", rest);
|
|
581
572
|
break;
|
|
@@ -591,6 +582,14 @@ async function main() {
|
|
|
591
582
|
case "init":
|
|
592
583
|
await initSite();
|
|
593
584
|
break;
|
|
585
|
+
case "agents": {
|
|
586
|
+
if (samePath(siteRoot, gardenPackageRoot)) fail("This command is for a Garden site, not the engine repository.");
|
|
587
|
+
if (!fs.existsSync(path.join(siteRoot, "site.config.yaml"))) fail("Not a Garden site: missing site.config.yaml");
|
|
588
|
+
const lang = siteCliLang(siteRoot);
|
|
589
|
+
const created = installSiteGuide({ engineRoot: gardenPackageRoot, siteRoot, lang });
|
|
590
|
+
process.stdout.write(lang === "en" ? (created ? "Created AGENTS.md\n" : "Kept existing AGENTS.md\n") : (created ? "已创建 AGENTS.md\n" : "保留已有 AGENTS.md\n"));
|
|
591
|
+
break;
|
|
592
|
+
}
|
|
594
593
|
case "skill":
|
|
595
594
|
case "skills":
|
|
596
595
|
installSkill();
|