@suzumiyaaoba/mdxr 0.1.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.
- package/LICENSE +21 -0
- package/README.md +56 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +2902 -0
- package/dist/components.d.mts +791 -0
- package/dist/components.mjs +2 -0
- package/dist/config-SI9IyFiC.mjs +184 -0
- package/dist/doc-context-CEqzMYKv.d.mts +568 -0
- package/dist/index.d.mts +20 -0
- package/dist/index.mjs +4 -0
- package/dist/ui-GKFD0mx5.mjs +15986 -0
- package/package.json +131 -0
- package/skill/SKILL.md +49 -0
- package/skill/references/components/charts.md +172 -0
- package/skill/references/components/document.md +98 -0
- package/skill/references/components/forms.md +35 -0
- package/skill/references/components/investigation.md +198 -0
- package/skill/references/components/layout.md +68 -0
- package/skill/references/components/output.md +158 -0
- package/skill/references/components/planning.md +155 -0
- package/skill/references/components/reports.md +252 -0
- package/skill/references/components/shadcn.md +19 -0
- package/skill/references/components.md +186 -0
- package/skill/references/extending.md +48 -0
- package/src/ask-sheet.ts +40 -0
- package/src/assets/css.ts +392 -0
- package/src/assets/scripts.ts +78 -0
- package/src/catalog.ts +287 -0
- package/src/cli.ts +178 -0
- package/src/client/doc-events.ts +1108 -0
- package/src/client/entry.ts +27 -0
- package/src/client-js.ts +47 -0
- package/src/component-map.ts +47 -0
- package/src/components/ui/accordion.tsx +77 -0
- package/src/components/ui/alert-dialog.tsx +185 -0
- package/src/components/ui/alert.tsx +76 -0
- package/src/components/ui/aspect-ratio.tsx +22 -0
- package/src/components/ui/attachment.tsx +208 -0
- package/src/components/ui/avatar.tsx +106 -0
- package/src/components/ui/badge.tsx +52 -0
- package/src/components/ui/breadcrumb.tsx +121 -0
- package/src/components/ui/bubble.tsx +128 -0
- package/src/components/ui/button-group.tsx +88 -0
- package/src/components/ui/button.tsx +58 -0
- package/src/components/ui/calendar.tsx +226 -0
- package/src/components/ui/card.tsx +102 -0
- package/src/components/ui/carousel.tsx +246 -0
- package/src/components/ui/chart.tsx +379 -0
- package/src/components/ui/checkbox.tsx +27 -0
- package/src/components/ui/collapsible.tsx +19 -0
- package/src/components/ui/combobox.tsx +298 -0
- package/src/components/ui/command.tsx +193 -0
- package/src/components/ui/context-menu.tsx +271 -0
- package/src/components/ui/dialog.tsx +159 -0
- package/src/components/ui/direction.tsx +4 -0
- package/src/components/ui/drawer.tsx +227 -0
- package/src/components/ui/dropdown-menu.tsx +269 -0
- package/src/components/ui/empty.tsx +104 -0
- package/src/components/ui/field.tsx +237 -0
- package/src/components/ui/fieldset.tsx +32 -0
- package/src/components/ui/frame.tsx +87 -0
- package/src/components/ui/hover-card.tsx +50 -0
- package/src/components/ui/input-group.tsx +159 -0
- package/src/components/ui/input-otp.tsx +83 -0
- package/src/components/ui/input.tsx +19 -0
- package/src/components/ui/item.tsx +202 -0
- package/src/components/ui/kbd.tsx +26 -0
- package/src/components/ui/label.tsx +19 -0
- package/src/components/ui/marker.tsx +71 -0
- package/src/components/ui/menubar.tsx +284 -0
- package/src/components/ui/message-scroller.tsx +128 -0
- package/src/components/ui/message.tsx +91 -0
- package/src/components/ui/meter.tsx +80 -0
- package/src/components/ui/native-select.tsx +64 -0
- package/src/components/ui/navigation-menu.tsx +170 -0
- package/src/components/ui/pagination.tsx +133 -0
- package/src/components/ui/popover.tsx +87 -0
- package/src/components/ui/progress.tsx +82 -0
- package/src/components/ui/questionnaire.tsx +328 -0
- package/src/components/ui/radio-group.tsx +35 -0
- package/src/components/ui/resizable.tsx +49 -0
- package/src/components/ui/scroll-area.tsx +50 -0
- package/src/components/ui/select.tsx +201 -0
- package/src/components/ui/separator.tsx +22 -0
- package/src/components/ui/sheet.tsx +135 -0
- package/src/components/ui/sidebar.tsx +730 -0
- package/src/components/ui/skeleton.tsx +13 -0
- package/src/components/ui/slider.tsx +51 -0
- package/src/components/ui/spinner.tsx +16 -0
- package/src/components/ui/switch.tsx +31 -0
- package/src/components/ui/table.tsx +113 -0
- package/src/components/ui/tabs.tsx +82 -0
- package/src/components/ui/textarea.tsx +17 -0
- package/src/components/ui/toast.tsx +229 -0
- package/src/components/ui/toggle-group.tsx +87 -0
- package/src/components/ui/toggle.tsx +43 -0
- package/src/components/ui/tooltip.tsx +65 -0
- package/src/components.ts +78 -0
- package/src/config.ts +56 -0
- package/src/define.ts +122 -0
- package/src/doc-context.ts +20 -0
- package/src/editor.ts +94 -0
- package/src/format-error.ts +78 -0
- package/src/guards.ts +84 -0
- package/src/hooks/use-mobile.ts +21 -0
- package/src/html.ts +91 -0
- package/src/hydrate/export-index.ts +232 -0
- package/src/hydrate/import-scan.ts +169 -0
- package/src/hydrate/plugins.ts +118 -0
- package/src/hydrate/runtime-module.ts +145 -0
- package/src/hydrate-runtime.ts +67 -0
- package/src/hydrate.ts +147 -0
- package/src/index.ts +16 -0
- package/src/init.ts +57 -0
- package/src/langs.ts +148 -0
- package/src/lines.ts +53 -0
- package/src/load-user-module.ts +191 -0
- package/src/mdx.ts +248 -0
- package/src/paths.ts +36 -0
- package/src/rehype/shiki.ts +533 -0
- package/src/remark/alerts.ts +53 -0
- package/src/remark/ast.ts +96 -0
- package/src/remark/callouts.ts +25 -0
- package/src/remark/code-file.ts +85 -0
- package/src/remark/code-meta.ts +21 -0
- package/src/remark/directives.ts +170 -0
- package/src/remark/file-paths.ts +64 -0
- package/src/remark/headings.ts +131 -0
- package/src/remark/no-js.ts +40 -0
- package/src/render.ts +337 -0
- package/src/serve.ts +322 -0
- package/src/styles/globals.css +134 -0
- package/src/styles/shadcn.css +641 -0
- package/src/tailwind.ts +119 -0
- package/src/ui/approvals.tsx +76 -0
- package/src/ui/ask-question.tsx +386 -0
- package/src/ui/ask.tsx +206 -0
- package/src/ui/attrs.ts +51 -0
- package/src/ui/audit.tsx +139 -0
- package/src/ui/bar-chart.tsx +334 -0
- package/src/ui/benchmarks.tsx +143 -0
- package/src/ui/bits.tsx +537 -0
- package/src/ui/board.tsx +173 -0
- package/src/ui/bridge.tsx +207 -0
- package/src/ui/bumps.tsx +178 -0
- package/src/ui/callout.tsx +106 -0
- package/src/ui/changes.tsx +89 -0
- package/src/ui/chart-bits.tsx +52 -0
- package/src/ui/chart.ts +577 -0
- package/src/ui/checks.tsx +203 -0
- package/src/ui/child-index.tsx +44 -0
- package/src/ui/children.ts +43 -0
- package/src/ui/chips.tsx +49 -0
- package/src/ui/cmd.tsx +27 -0
- package/src/ui/columns.tsx +31 -0
- package/src/ui/comments.tsx +544 -0
- package/src/ui/compare.tsx +67 -0
- package/src/ui/decision.tsx +79 -0
- package/src/ui/deps.tsx +78 -0
- package/src/ui/details.tsx +43 -0
- package/src/ui/diff-parse.ts +307 -0
- package/src/ui/diff.tsx +458 -0
- package/src/ui/diffstat.tsx +58 -0
- package/src/ui/due.tsx +65 -0
- package/src/ui/effort.tsx +29 -0
- package/src/ui/endpoints.tsx +118 -0
- package/src/ui/envvars.tsx +96 -0
- package/src/ui/figure.tsx +37 -0
- package/src/ui/file-icon.ts +1020 -0
- package/src/ui/file-link.ts +32 -0
- package/src/ui/file-ref.tsx +39 -0
- package/src/ui/files.tsx +112 -0
- package/src/ui/findings.tsx +85 -0
- package/src/ui/flow.tsx +73 -0
- package/src/ui/funnel.tsx +121 -0
- package/src/ui/gantt.tsx +443 -0
- package/src/ui/gauges.tsx +135 -0
- package/src/ui/glossary.tsx +29 -0
- package/src/ui/graph-layout.ts +149 -0
- package/src/ui/graph-specs.tsx +102 -0
- package/src/ui/graph.tsx +278 -0
- package/src/ui/grid.tsx +119 -0
- package/src/ui/hypothesis.tsx +94 -0
- package/src/ui/icon.tsx +80 -0
- package/src/ui/incident.tsx +130 -0
- package/src/ui/index.ts +342 -0
- package/src/ui/ins-del.tsx +41 -0
- package/src/ui/json.tsx +190 -0
- package/src/ui/layout.ts +9 -0
- package/src/ui/line-chart.tsx +251 -0
- package/src/ui/matrix.tsx +208 -0
- package/src/ui/meta.tsx +73 -0
- package/src/ui/option.tsx +64 -0
- package/src/ui/owner.tsx +40 -0
- package/src/ui/packages.tsx +104 -0
- package/src/ui/pathway.tsx +96 -0
- package/src/ui/phase.tsx +41 -0
- package/src/ui/pie-chart.tsx +170 -0
- package/src/ui/plan.tsx +77 -0
- package/src/ui/pre.tsx +175 -0
- package/src/ui/priority.tsx +51 -0
- package/src/ui/props.tsx +77 -0
- package/src/ui/quadrant.tsx +172 -0
- package/src/ui/radar.tsx +200 -0
- package/src/ui/ref.tsx +160 -0
- package/src/ui/release.tsx +178 -0
- package/src/ui/req.tsx +42 -0
- package/src/ui/review.tsx +133 -0
- package/src/ui/risk.tsx +67 -0
- package/src/ui/sankey.tsx +287 -0
- package/src/ui/scatter.tsx +237 -0
- package/src/ui/schema.tsx +113 -0
- package/src/ui/score.tsx +105 -0
- package/src/ui/search.tsx +120 -0
- package/src/ui/series.tsx +38 -0
- package/src/ui/severity.tsx +69 -0
- package/src/ui/shadcn.tsx +216 -0
- package/src/ui/spark.tsx +86 -0
- package/src/ui/stack.tsx +57 -0
- package/src/ui/stats.tsx +63 -0
- package/src/ui/status-badge.tsx +66 -0
- package/src/ui/statuspage.tsx +238 -0
- package/src/ui/steps.tsx +74 -0
- package/src/ui/summary.tsx +41 -0
- package/src/ui/symbol-ref.tsx +73 -0
- package/src/ui/terminal.tsx +117 -0
- package/src/ui/tests.tsx +218 -0
- package/src/ui/timeline.tsx +63 -0
- package/src/ui/toc.tsx +56 -0
- package/src/ui/tones.ts +187 -0
- package/src/ui/trace.tsx +69 -0
- package/src/ui/tree.tsx +281 -0
- package/src/ui/treemap.tsx +128 -0
- package/src/ui/venn.tsx +258 -0
- package/src/ui/verdict.tsx +78 -0
- package/src/ui/waterfall.tsx +142 -0
package/src/catalog.ts
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import type { ComponentMap } from "./define.js";
|
|
2
|
+
import { isRecord } from "./guards.js";
|
|
3
|
+
import { builtinComponents } from "./ui/index.js";
|
|
4
|
+
|
|
5
|
+
export interface PropInfo {
|
|
6
|
+
type: string;
|
|
7
|
+
required: boolean;
|
|
8
|
+
default?: unknown;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface CatalogEntry {
|
|
12
|
+
name: string;
|
|
13
|
+
source: "builtin" | "project";
|
|
14
|
+
description?: string;
|
|
15
|
+
props: Record<string, PropInfo>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Structural view of a valibot schema — enough for catalog introspection. */
|
|
19
|
+
const describeSchema = (schema: unknown): string => {
|
|
20
|
+
if (!isRecord(schema)) {
|
|
21
|
+
return "unknown";
|
|
22
|
+
}
|
|
23
|
+
if (schema.type === "array") {
|
|
24
|
+
return `${describeSchema(schema.item)}[]`;
|
|
25
|
+
}
|
|
26
|
+
if (schema.type === "literal") {
|
|
27
|
+
return JSON.stringify(schema.literal);
|
|
28
|
+
}
|
|
29
|
+
if (schema.type === "picklist" && Array.isArray(schema.options)) {
|
|
30
|
+
return schema.options.map((o: unknown) => JSON.stringify(o)).join(" | ");
|
|
31
|
+
}
|
|
32
|
+
if (schema.type === "union" && Array.isArray(schema.options)) {
|
|
33
|
+
return schema.options.map(describeSchema).join(" | ");
|
|
34
|
+
}
|
|
35
|
+
// `v.pipe(v.string(), v.toUpperCase(), v.picklist(M))` keeps `type:
|
|
36
|
+
// "string"` — the base type — and stores the actions in `.pipe`. The
|
|
37
|
+
// constrained member (picklist/union/literal) reads better than "string";
|
|
38
|
+
// transforms like trim/case don't describe accepted values.
|
|
39
|
+
if (Array.isArray(schema.pipe)) {
|
|
40
|
+
const pipe: unknown[] = schema.pipe;
|
|
41
|
+
const constrained = pipe.find(
|
|
42
|
+
(s) =>
|
|
43
|
+
isRecord(s) &&
|
|
44
|
+
(s.type === "picklist" || s.type === "union" || s.type === "literal")
|
|
45
|
+
);
|
|
46
|
+
if (constrained !== undefined) {
|
|
47
|
+
return describeSchema(constrained);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return typeof schema.type === "string" ? schema.type : "unknown";
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Wrapper schema types that mark a prop as non-required and may carry a
|
|
54
|
+
// `default` (valibot's optional/nullish/nullable).
|
|
55
|
+
const WRAPPER_TYPES = new Set(["nullable", "nullish", "optional"]);
|
|
56
|
+
|
|
57
|
+
const unwrapProp = (
|
|
58
|
+
raw: unknown
|
|
59
|
+
): { def: unknown; required: boolean; s: Record<string, unknown> } => {
|
|
60
|
+
let s: Record<string, unknown> = isRecord(raw) ? raw : {};
|
|
61
|
+
let required = true;
|
|
62
|
+
let def: unknown;
|
|
63
|
+
while (typeof s.type === "string" && WRAPPER_TYPES.has(s.type)) {
|
|
64
|
+
required = false;
|
|
65
|
+
// First (outermost) default wins: `v.optional(v.nullable(x, "a"), "b")`
|
|
66
|
+
// yields "b" for an absent prop — the inner default never applies.
|
|
67
|
+
def ??= s.default;
|
|
68
|
+
s = isRecord(s.wrapped) ? s.wrapped : {};
|
|
69
|
+
}
|
|
70
|
+
return { def, required, s };
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const propsOf = (schema: unknown): Record<string, PropInfo> => {
|
|
74
|
+
if (!isRecord(schema) || !isRecord(schema.entries)) {
|
|
75
|
+
return {};
|
|
76
|
+
}
|
|
77
|
+
const out: Record<string, PropInfo> = {};
|
|
78
|
+
for (const [key, raw] of Object.entries(schema.entries)) {
|
|
79
|
+
const { def, required, s } = unwrapProp(raw);
|
|
80
|
+
// defineProperty, not assignment: a prop literally named "__proto__"
|
|
81
|
+
// would otherwise replace `out`'s prototype instead of being recorded.
|
|
82
|
+
Object.defineProperty(out, key, {
|
|
83
|
+
configurable: true,
|
|
84
|
+
enumerable: true,
|
|
85
|
+
value: {
|
|
86
|
+
required,
|
|
87
|
+
type: describeSchema(s),
|
|
88
|
+
...(def === undefined ? {} : { default: def }),
|
|
89
|
+
},
|
|
90
|
+
writable: true,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
return out;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const entryOf = (
|
|
97
|
+
name: string,
|
|
98
|
+
comp: ComponentMap[string],
|
|
99
|
+
source: CatalogEntry["source"]
|
|
100
|
+
): CatalogEntry => ({
|
|
101
|
+
description: comp.__mdxr?.description,
|
|
102
|
+
name,
|
|
103
|
+
props: propsOf(comp.__mdxr?.schema),
|
|
104
|
+
source,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
export const catalogEntries = (project: ComponentMap = {}): CatalogEntry[] => {
|
|
108
|
+
const entries: CatalogEntry[] = [];
|
|
109
|
+
for (const [name, comp] of Object.entries(builtinComponents)) {
|
|
110
|
+
// The `pre` override is a convention, not a JSX component.
|
|
111
|
+
if (!/^[A-Z]/u.test(name)) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
// A project override wins the slot wholesale: describe its schema, not
|
|
115
|
+
// the builtin's — the catalog documents what the document actually gets.
|
|
116
|
+
// hasOwn, not `in`: prototype names ("toString", "constructor") must not
|
|
117
|
+
// count as overrides via the prototype chain.
|
|
118
|
+
const overridden = Object.hasOwn(project, name);
|
|
119
|
+
const shown = overridden ? (project[name] ?? comp) : comp;
|
|
120
|
+
entries.push(entryOf(name, shown, overridden ? "project" : "builtin"));
|
|
121
|
+
}
|
|
122
|
+
for (const [name, comp] of Object.entries(project)) {
|
|
123
|
+
if (!Object.hasOwn(builtinComponents, name)) {
|
|
124
|
+
entries.push(entryOf(name, comp, "project"));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return entries;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/** Conventions the agent can use without JSX (kept in sync with remark plugins). */
|
|
131
|
+
export const CONVENTIONS = [
|
|
132
|
+
{
|
|
133
|
+
result: "Callout (with kind)",
|
|
134
|
+
syntax:
|
|
135
|
+
":::note | :::tip | :::warning | :::danger | :::decision | :::goal | :::nongoal | :::question | :::answer",
|
|
136
|
+
},
|
|
137
|
+
{ result: "Phase", syntax: ':::phase{title="..." status="doing"}' },
|
|
138
|
+
{ result: "Steps container", syntax: ":::steps" },
|
|
139
|
+
{
|
|
140
|
+
result: "Flow (numbered call/execution chain)",
|
|
141
|
+
syntax: ':::flow{title="..."}',
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
result: "Findings container / Finding block",
|
|
145
|
+
syntax: ':::findings · :::finding{confidence="inferred" title="..."}',
|
|
146
|
+
},
|
|
147
|
+
{ result: "Files container (related files)", syntax: ":::files" },
|
|
148
|
+
{ result: "Deps container (dependency edges)", syntax: ":::deps" },
|
|
149
|
+
{ result: "Tests container (test report)", syntax: ":::tests" },
|
|
150
|
+
{ result: "Endpoints container (API routes)", syntax: ":::endpoints" },
|
|
151
|
+
{ result: "Board container (kanban)", syntax: ":::board" },
|
|
152
|
+
{
|
|
153
|
+
result: "Graph container (static node/edge SVG, dagre layout)",
|
|
154
|
+
syntax: ':::graph{title="..." direction="right"}',
|
|
155
|
+
},
|
|
156
|
+
{ result: "Waterfall container (timing bars)", syntax: ":::waterfall" },
|
|
157
|
+
{
|
|
158
|
+
result: "Gantt container (date-based schedule)",
|
|
159
|
+
syntax: ':::gantt{title="..."}',
|
|
160
|
+
},
|
|
161
|
+
{ result: "Matrix container (comparison grid)", syntax: ":::matrix" },
|
|
162
|
+
{ result: "Timeline", syntax: ':::timeline{title="..."}' },
|
|
163
|
+
{
|
|
164
|
+
result: "Review container (code review report)",
|
|
165
|
+
syntax: ':::review{title="..." verdict="changes"}',
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
result: "Checks container (CI/verification status)",
|
|
169
|
+
syntax: ":::checks",
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
result: "Audit container (vulnerability inventory)",
|
|
173
|
+
syntax: ':::audit{tool="npm audit"}',
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
result: "Bumps container (dependency upgrades)",
|
|
177
|
+
syntax: ":::bumps",
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
result: "Packages container (dependency inventory)",
|
|
181
|
+
syntax: ":::packages",
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
result: "Gauges container (percent-bar rows: coverage, scores)",
|
|
185
|
+
syntax: ":::gauges",
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
result: "Benchmarks container (before/after comparisons)",
|
|
189
|
+
syntax: ':::benchmarks{better="lower"}',
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
result: "Schema container (DB table definitions)",
|
|
193
|
+
syntax: ":::schema",
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
result: "EnvVars container (environment variable docs)",
|
|
197
|
+
syntax: ":::envvars",
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
result: "StatusPage container (service health rollup)",
|
|
201
|
+
syntax: ":::statuspage",
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
result: "Uptime container (status-page day bars)",
|
|
205
|
+
syntax: ':::uptime{pct="99.9"}',
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
result: "Release container (changelog block)",
|
|
209
|
+
syntax: ':::release{version="v1.0.0"}',
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
result: "Pathway container (version/env stepper)",
|
|
213
|
+
syntax: ':::pathway{title="..."}',
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
result: "Incident header (postmortem card)",
|
|
217
|
+
syntax: ':::incident{title="..." severity="high"}',
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
result: "Verdict banner (approve/pass/warn/fail conclusion)",
|
|
221
|
+
syntax: ':::verdict{status="approve"}',
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
result: "chart containers (quantitative visualizations)",
|
|
225
|
+
syntax:
|
|
226
|
+
":::barchart · :::linechart · :::piechart · :::scatter · :::radar · :::funnel · :::quadrant · :::bridge · :::treemap · :::sankey · :::venn",
|
|
227
|
+
},
|
|
228
|
+
{ result: "table of contents (auto from headings)", syntax: ":::toc" },
|
|
229
|
+
{ result: "Callout (GitHub alert)", syntax: "> [!NOTE] / [!WARNING] / ..." },
|
|
230
|
+
{ result: "diagram (mermaid via CDN)", syntax: "```mermaid fenced block" },
|
|
231
|
+
{
|
|
232
|
+
result: "unified-diff cards (per-file headers, hunk line numbers)",
|
|
233
|
+
syntax: "```diff or ```patch fenced block",
|
|
234
|
+
},
|
|
235
|
+
{ result: "math (KaTeX via CDN)", syntax: "$…$ inline / $$…$$ block" },
|
|
236
|
+
{
|
|
237
|
+
result: "code block (syntax-highlighted) with filename header",
|
|
238
|
+
syntax: '```lang title="file.ts"',
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
result: "FileRef chip (editor link when the file exists)",
|
|
242
|
+
syntax: "`src/x.ts` inline code naming a real file",
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
result: "code line highlights / line numbers",
|
|
246
|
+
syntax: "```ts {1,3-5} · ```ts ln · /word/ in meta",
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
result: "in-code markers (stripped from output)",
|
|
250
|
+
syntax:
|
|
251
|
+
"// [!code hl] · [!code ++] · [!code --] · [!code warning] · [!code error] · [!code focus] · [!code word:x]",
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
result: "embed a real file as a code block",
|
|
255
|
+
syntax: '<CodeFile path="src/x.ts" lines="40-52" />',
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
result: "styled markdown",
|
|
259
|
+
syntax: "GFM tables, task lists, strikethrough, footnotes",
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
result: "Iconify icon (inline SVG, lucide + vscode-icons bundled)",
|
|
263
|
+
syntax: '<Icon name="lucide:rocket"> or class="icon-[lucide--rocket]"',
|
|
264
|
+
},
|
|
265
|
+
];
|
|
266
|
+
|
|
267
|
+
export const formatCatalog = (entries: CatalogEntry[]): string => {
|
|
268
|
+
const lines: string[] = ["Components:", ""];
|
|
269
|
+
for (const e of entries) {
|
|
270
|
+
const props = Object.entries(e.props)
|
|
271
|
+
.map(([k, p]) => `${k}${p.required ? "" : "?"}: ${p.type}`)
|
|
272
|
+
.join(", ");
|
|
273
|
+
lines.push(` <${e.name}${props === "" ? "" : ` ${props}`}>`);
|
|
274
|
+
if (e.description !== undefined) {
|
|
275
|
+
lines.push(` ${e.description}`);
|
|
276
|
+
}
|
|
277
|
+
if (e.source === "project") {
|
|
278
|
+
lines.push(" (project-defined)");
|
|
279
|
+
}
|
|
280
|
+
lines.push("");
|
|
281
|
+
}
|
|
282
|
+
lines.push("Conventions:");
|
|
283
|
+
for (const c of CONVENTIONS) {
|
|
284
|
+
lines.push(` ${c.syntax} → ${c.result}`);
|
|
285
|
+
}
|
|
286
|
+
return lines.join("\n");
|
|
287
|
+
};
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
import { cac } from "cac";
|
|
6
|
+
|
|
7
|
+
import { catalogEntries, formatCatalog, CONVENTIONS } from "./catalog.js";
|
|
8
|
+
import { loadConfig } from "./config.js";
|
|
9
|
+
import { formatError, parseErrorFormat } from "./format-error.js";
|
|
10
|
+
import { installSkill } from "./init.js";
|
|
11
|
+
import { loadUserComponents, render, renderFile } from "./render.js";
|
|
12
|
+
import { serve, serveSource } from "./serve.js";
|
|
13
|
+
import { builtinComponents } from "./ui/index.js";
|
|
14
|
+
|
|
15
|
+
const readStdin = async (): Promise<string> => {
|
|
16
|
+
const chunks: Buffer[] = [];
|
|
17
|
+
for await (const chunk of process.stdin as AsyncIterable<Buffer>) {
|
|
18
|
+
chunks.push(chunk);
|
|
19
|
+
}
|
|
20
|
+
return Buffer.concat(chunks).toString("utf-8");
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const requireStdinSource = (): void => {
|
|
24
|
+
if (process.stdin.isTTY) {
|
|
25
|
+
throw new Error("no input: pass an .mdx file or pipe MDX source via stdin");
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const fail = (err: unknown, json: boolean): never => {
|
|
30
|
+
if (json) {
|
|
31
|
+
console.log(JSON.stringify({ error: formatError(err), ok: false }));
|
|
32
|
+
} else {
|
|
33
|
+
console.error(`mdxr: error: ${formatError(err)}`);
|
|
34
|
+
}
|
|
35
|
+
process.exit(1);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const cli = cac("mdxr");
|
|
39
|
+
|
|
40
|
+
cli
|
|
41
|
+
.command("render [file]", "Render an .mdx document to a standalone HTML file")
|
|
42
|
+
.option(
|
|
43
|
+
"-o, --out <path>",
|
|
44
|
+
"Output path (default: <file>.html; stdout for stdin input or '-')"
|
|
45
|
+
)
|
|
46
|
+
.option("--format <format>", "Error output: text | json")
|
|
47
|
+
.option(
|
|
48
|
+
"--no-hydrate",
|
|
49
|
+
"Emit static HTML without the client hydration bundle"
|
|
50
|
+
)
|
|
51
|
+
.action(
|
|
52
|
+
async (
|
|
53
|
+
file: string | undefined,
|
|
54
|
+
opts: { out?: string; format?: string; hydrate?: boolean }
|
|
55
|
+
) => {
|
|
56
|
+
const json = opts.format === "json";
|
|
57
|
+
try {
|
|
58
|
+
parseErrorFormat(opts.format);
|
|
59
|
+
const fromStdin = file === undefined || file === "-";
|
|
60
|
+
if (fromStdin) {
|
|
61
|
+
requireStdinSource();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const html = fromStdin
|
|
65
|
+
? await render(await readStdin(), {
|
|
66
|
+
dir: process.cwd(),
|
|
67
|
+
filePath: "<stdin>",
|
|
68
|
+
hydrate: opts.hydrate,
|
|
69
|
+
})
|
|
70
|
+
: await renderFile(file, { hydrate: opts.hydrate });
|
|
71
|
+
|
|
72
|
+
const out =
|
|
73
|
+
opts.out === "-"
|
|
74
|
+
? undefined
|
|
75
|
+
: (opts.out ??
|
|
76
|
+
(fromStdin
|
|
77
|
+
? undefined
|
|
78
|
+
: `${file.replace(/\.(?:mdx|md)$/u, "")}.html`));
|
|
79
|
+
|
|
80
|
+
if (out === undefined) {
|
|
81
|
+
// No status line: stdout carries the HTML itself. Downstream
|
|
82
|
+
// pipes (e.g. `| head`) may close early — EPIPE is not an error.
|
|
83
|
+
process.stdout.on("error", (err: NodeJS.ErrnoException) => {
|
|
84
|
+
if (err.code === "EPIPE") {
|
|
85
|
+
process.exit(0);
|
|
86
|
+
}
|
|
87
|
+
throw err;
|
|
88
|
+
});
|
|
89
|
+
process.stdout.write(html);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
await writeFile(path.resolve(out), html);
|
|
93
|
+
if (json) {
|
|
94
|
+
console.log(JSON.stringify({ ok: true, out }));
|
|
95
|
+
} else {
|
|
96
|
+
console.log(`mdxr: wrote ${out}`);
|
|
97
|
+
}
|
|
98
|
+
} catch (error) {
|
|
99
|
+
fail(error, json);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
cli
|
|
105
|
+
.command("serve [file]", "Preview a document in the browser with live reload")
|
|
106
|
+
.option("-p, --port <port>", "Port", { default: 3737 })
|
|
107
|
+
.action(async (file: string | undefined, opts: { port: number | string }) => {
|
|
108
|
+
try {
|
|
109
|
+
// mri leaves a non-numeric flag as a string; http.listen would treat
|
|
110
|
+
// that as a pipe path instead of a port.
|
|
111
|
+
const port = Number(opts.port);
|
|
112
|
+
if (!Number.isInteger(port) || port < 0 || port > 65_535) {
|
|
113
|
+
throw new Error(`invalid --port: ${opts.port}`);
|
|
114
|
+
}
|
|
115
|
+
if (file === undefined || file === "-") {
|
|
116
|
+
requireStdinSource();
|
|
117
|
+
await serveSource(await readStdin(), port, {
|
|
118
|
+
dir: process.cwd(),
|
|
119
|
+
filePath: "<stdin>",
|
|
120
|
+
});
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
await serve(file, port);
|
|
124
|
+
} catch (error) {
|
|
125
|
+
fail(error, false);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
cli
|
|
130
|
+
.command("catalog", "List available components (built-in + project-defined)")
|
|
131
|
+
.option("--json", "Print machine-readable JSON")
|
|
132
|
+
.option("--dir <dir>", "Project directory to read mdxr.config.ts from")
|
|
133
|
+
.action(async (opts: { json?: boolean; dir?: string }) => {
|
|
134
|
+
try {
|
|
135
|
+
const dir = path.resolve(opts.dir ?? process.cwd());
|
|
136
|
+
const config = await loadConfig(dir);
|
|
137
|
+
const user = await loadUserComponents(config);
|
|
138
|
+
const entries = catalogEntries(user.components);
|
|
139
|
+
if (opts.json === true) {
|
|
140
|
+
console.log(
|
|
141
|
+
JSON.stringify(
|
|
142
|
+
{
|
|
143
|
+
builtins: Object.keys(builtinComponents),
|
|
144
|
+
components: entries,
|
|
145
|
+
conventions: CONVENTIONS,
|
|
146
|
+
},
|
|
147
|
+
null,
|
|
148
|
+
2
|
|
149
|
+
)
|
|
150
|
+
);
|
|
151
|
+
} else {
|
|
152
|
+
console.log(formatCatalog(entries));
|
|
153
|
+
}
|
|
154
|
+
} catch (error) {
|
|
155
|
+
fail(error, false);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
cli
|
|
160
|
+
.command("init", "Install the mdxr agent skill into this project")
|
|
161
|
+
.option("--tool <tool>", "agents | claude | devin | all", {
|
|
162
|
+
default: "agents",
|
|
163
|
+
})
|
|
164
|
+
.option("--global", "Install into your home directory instead of the project")
|
|
165
|
+
.option("--force", "Overwrite an existing skill")
|
|
166
|
+
.action(async (opts: { tool: string; global?: boolean; force?: boolean }) => {
|
|
167
|
+
try {
|
|
168
|
+
const paths = await installSkill(opts);
|
|
169
|
+
for (const p of paths) {
|
|
170
|
+
console.log(`mdxr: installed skill → ${p}`);
|
|
171
|
+
}
|
|
172
|
+
} catch (error) {
|
|
173
|
+
fail(error, false);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
cli.help();
|
|
178
|
+
cli.parse();
|