diff --git a/template/content/index.md b/template/content/index.md
index 0c686d0..a844b7f 100644
--- a/template/content/index.md
+++ b/template/content/index.md
@@ -1,24 +1 @@
-# Welcome to Nuxt Content Starter
-
-This is the main page displaying Markdown located at [content/index.md](https://github.com/nuxt/starter/blob/content/content/index.md).
-
-Move to [about](/about) page.
-
-## Manage your Contents
-
-Create new pages or modify the existing ones in `content/` directory.
-
-## Query & Render Pages
-
-You can find an example of querying contents and rendering them in a [catch-all page](https://github.com/nuxt/starter/blob/content/app/pages/%5B...slug%5D.vue)
-
-## Integrate Vue Component
-
-::alert{color="green"}
-The current [alert](https://github.com/nuxt/starter/blob/content/app/components/Alert.vue) and the [counter](https://github.com/nuxt/starter/blob/content/app/components/Counter.vue) below are `Vue` components integrated into the Markdown.
-::
-
-::counter
-::
-
-Checkout out the [documentation](https://content.nuxt.com/docs/getting-started) to learn more.
+Hola que tal!
\ No newline at end of file
diff --git a/template/convert.js b/template/convert.js
index e69de29..e2c8ebb 100644
--- a/template/convert.js
+++ b/template/convert.js
@@ -0,0 +1,376 @@
+#!/usr/bin/env node
+
+/**
+ * obsidian-to-nuxt.js
+ *
+ * Converts an Obsidian vault's Markdown files to Nuxt Content-compatible pages.
+ *
+ * Transformations applied:
+ * 1. [[Wiki links]] → [Wiki links](/slug)
+ * 2. [[Wiki links|Alias]] → [Alias](/slug)
+ * 3. ![[image.png]] →
/ NuxtImg tag
+ * 4. ![[note]] → MDC ::include component
+ * 5. Callouts > [!TYPE] Title → MDC ::callout{type} blocks
+ * 6. Block IDs ^block-id → anchors
+ * 7. Block refs [[note#^ref]] → [note](/note#block-ref)
+ * 8. Heading links [[note#Head]] → [note](/note#heading-slug)
+ * 9. Inline #tags → stripped (kept in frontmatter.tags)
+ * 10. Frontmatter → passed through; tags array merged
+ *
+ * Usage:
+ * node obsidian-to-nuxt.js --vault ./my-vault --out ./content --base-path /notes
+ *
+ * Options:
+ * --vault Path to Obsidian vault root (default: ./vault)
+ * --out Output directory for converted files (default: ./content)
+ * --base-path URL prefix for all generated links (default: "")
+ * --img-prefix URL prefix for embedded images (default: /images)
+ * --nuxt-img Use instead of
(flag)
+ * --dry-run Print conversions without writing files (flag)
+ */
+
+import fs from "fs";
+import path from "path";
+import { parseArgs } from "util";
+
+// ─── CLI args ────────────────────────────────────────────────────────────────
+
+const { values: args } = parseArgs({
+ options: {
+ vault: { type: "string", default: "./vault" },
+ out: { type: "string", default: "./content" },
+ "base-path":{ type: "string", default: "" },
+ "img-prefix":{ type: "string", default: "/images" },
+ "nuxt-img": { type: "boolean", default: false },
+ "dry-run": { type: "boolean", default: false },
+ },
+});
+
+const VAULT_DIR = path.resolve(args["vault"]);
+const OUT_DIR = path.resolve(args["out"]);
+const BASE_PATH = args["base-path"].replace(/\/$/, "");
+const IMG_PREFIX = args["img-prefix"].replace(/\/$/, "");
+const USE_NUXT_IMG = args["nuxt-img"];
+const DRY_RUN = args["dry-run"];
+
+const IMAGE_EXTS = new Set([".png", ".jpg", ".jpeg", ".gif", ".svg", ".webp", ".avif"]);
+
+// ─── Utilities ───────────────────────────────────────────────────────────────
+
+/** Turn a note name / file stem into a URL-safe slug */
+function slugify(name) {
+ return name
+ .toLowerCase()
+ .replace(/\s+/g, "-")
+ .replace(/[^\w\-]/g, "")
+ .replace(/--+/g, "-");
+}
+
+/** Turn a heading string into a GitHub-style anchor slug */
+function headingSlug(heading) {
+ return heading
+ .toLowerCase()
+ .replace(/\s+/g, "-")
+ .replace(/[^\w\-]/g, "");
+}
+
+/** Recursively collect all .md files in a directory */
+function collectMarkdownFiles(dir) {
+ const results = [];
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
+ const full = path.join(dir, entry.name);
+ if (entry.isDirectory()) {
+ results.push(...collectMarkdownFiles(full));
+ } else if (entry.isFile() && entry.name.endsWith(".md")) {
+ results.push(full);
+ }
+ }
+ return results;
+}
+
+/**
+ * Build a lookup map: note stem (lowercase) → relative URL path
+ * Handles duplicate names by preferring the shortest path (vault-root wins).
+ */
+function buildNoteIndex(files) {
+ const index = new Map();
+ for (const file of files) {
+ const stem = path.basename(file, ".md");
+ const rel = path.relative(VAULT_DIR, file).replace(/\.md$/, "");
+ const url = BASE_PATH + "/" + rel.split(path.sep).map(slugify).join("/");
+ const key = stem.toLowerCase();
+ if (!index.has(key) || rel.split(path.sep).length < index.get(key).depth) {
+ index.set(key, { url, depth: rel.split(path.sep).length });
+ }
+ }
+ return new Map([...index.entries()].map(([k, v]) => [k, v.url]));
+}
+
+// ─── Frontmatter helpers ─────────────────────────────────────────────────────
+
+const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---/;
+
+function parseFrontmatter(content) {
+ const match = content.match(FRONTMATTER_RE);
+ if (!match) return { frontmatter: {}, body: content };
+
+ const raw = match[1];
+ const body = content.slice(match[0].length);
+
+ // Minimal YAML parser (handles string scalars and simple arrays)
+ const fm = {};
+ let currentKey = null;
+ for (const line of raw.split("\n")) {
+ const listItem = line.match(/^\s+-\s+(.+)/);
+ if (listItem && currentKey) {
+ fm[currentKey] = Array.isArray(fm[currentKey]) ? fm[currentKey] : [];
+ fm[currentKey].push(listItem[1].trim());
+ continue;
+ }
+ const kv = line.match(/^(\w[\w-]*):\s*(.*)/);
+ if (kv) {
+ currentKey = kv[1];
+ const val = kv[2].trim();
+ fm[currentKey] = val === "" ? [] : val.replace(/^["']|["']$/g, "");
+ }
+ }
+ return { frontmatter: fm, body };
+}
+
+function serializeFrontmatter(fm) {
+ const lines = ["---"];
+ for (const [key, val] of Object.entries(fm)) {
+ if (Array.isArray(val)) {
+ lines.push(`${key}:`);
+ for (const v of val) lines.push(` - ${v}`);
+ } else {
+ lines.push(`${key}: ${val}`);
+ }
+ }
+ lines.push("---");
+ return lines.join("\n");
+}
+
+// ─── Inline-tag extractor ────────────────────────────────────────────────────
+
+/** Pull #tags from body text (skip # inside code fences or headings) */
+function extractInlineTags(body) {
+ const tags = new Set();
+ // Skip fenced code blocks
+ const stripped = body.replace(/```[\s\S]*?```/g, "");
+ const tagRe = /(? {
+ return ` `;
+ });
+}
+
+// ─── Callouts ────────────────────────────────────────────────────────────────
+
+const CALLOUT_START_RE = /^> \[!([A-Z]+)\]([+-]?)\s*(.*)/i;
+
+/**
+ * Convert Obsidian callout blocks to MDC (Nuxt Content) component syntax:
+ *
+ * > [!NOTE] Title ::callout{type="note" title="Title"}
+ * > content content
+ * ::
+ */
+function convertCallouts(body) {
+ const lines = body.split("\n");
+ const out = [];
+ let inCallout = false;
+
+ for (let i = 0; i < lines.length; i++) {
+ const line = lines[i];
+
+ if (!inCallout) {
+ const m = line.match(CALLOUT_START_RE);
+ if (m) {
+ const type = m[1].toLowerCase();
+ const title = m[3].trim();
+ const titleAttr = title ? ` title="${title}"` : "";
+ out.push(`::callout{type="${type}"${titleAttr}}`);
+ inCallout = true;
+ continue;
+ }
+ out.push(line);
+ } else {
+ // Continuation lines start with "> " or are blank (closing the block)
+ if (line.startsWith("> ")) {
+ out.push(line.slice(2)); // strip "> "
+ } else {
+ out.push("::"); // close MDC block
+ out.push(line);
+ inCallout = false;
+ }
+ }
+ }
+
+ if (inCallout) out.push("::");
+ return out.join("\n");
+}
+
+// ─── Embedded content ![[...]] ──────────────────────────────────────────────
+
+function convertEmbeds(body, noteIndex) {
+ return body.replace(/!\[\[([^\]]+?)\]\]/g, (_, ref) => {
+ const [rawName, size] = ref.split("|");
+ const name = rawName.trim();
+ const ext = path.extname(name).toLowerCase();
+
+ if (IMAGE_EXTS.has(ext)) {
+ // Image embed
+ const src = `${IMG_PREFIX}/${name}`;
+ const attrs = size ? ` width="${size.split("x")[0]}"` : "";
+ if (USE_NUXT_IMG) {
+ return ``;
+ }
+ return `
`;
+ }
+
+ if (ext === ".mp4" || ext === ".webm") {
+ const src = `${IMG_PREFIX}/${name}`;
+ return ``;
+ }
+
+ // Note embed → MDC include component
+ const stem = path.basename(name, ext || ".md");
+ const slug = noteIndex.get(stem.toLowerCase()) ?? `${BASE_PATH}/${slugify(stem)}`;
+ return `::include{path="${slug}"}`;
+ });
+}
+
+// ─── Wiki links [[...]] ─────────────────────────────────────────────────────
+
+function convertWikiLinks(body, noteIndex) {
+ return body.replace(/\[\[([^\]]+?)\]\]/g, (_, ref) => {
+ // Split off alias: [[Note|Alias]]
+ const [target, alias] = ref.split("|").map(s => s.trim());
+
+ // Split off heading or block ref: [[Note#Heading]] [[Note#^block]]
+ const [notePart, anchor] = target.split("#").map(s => s.trim());
+
+ const stem = path.basename(notePart, ".md");
+ const baseUrl = noteIndex.get(stem.toLowerCase()) ?? `${BASE_PATH}/${slugify(stem)}`;
+
+ let url = baseUrl;
+ if (anchor) {
+ if (anchor.startsWith("^")) {
+ // Block reference
+ url += "#" + anchor.slice(1);
+ } else {
+ // Heading reference
+ url += "#" + headingSlug(anchor);
+ }
+ }
+
+ const label = alias || (anchor ? `${stem} › ${anchor}` : stem);
+ return `[${label}](${url})`;
+ });
+}
+
+// ─── Main conversion ──────────────────────────────────────────────────────────
+
+function convertFile(content, noteIndex) {
+ let { frontmatter, body } = parseFrontmatter(content);
+
+ // 1. Extract and merge inline tags into frontmatter
+ const inlineTags = extractInlineTags(body);
+ if (inlineTags.length) {
+ const existing = Array.isArray(frontmatter.tags)
+ ? frontmatter.tags
+ : frontmatter.tags
+ ? [frontmatter.tags]
+ : [];
+ frontmatter.tags = [...new Set([...existing, ...inlineTags])];
+ body = stripInlineTags(body);
+ }
+
+ // 2. Callout blocks (before wiki-link pass so "> [[link]]" still works)
+ body = convertCallouts(body);
+
+ // 3. Block IDs
+ body = convertBlockIds(body);
+
+ // 4. Embedded files ![[...]]
+ body = convertEmbeds(body, noteIndex);
+
+ // 5. Wiki links [[...]]
+ body = convertWikiLinks(body, noteIndex);
+
+ const fmString = Object.keys(frontmatter).length
+ ? serializeFrontmatter(frontmatter) + "\n\n"
+ : "";
+
+ return fmString + body.trim() + "\n";
+}
+
+// ─── Entry point ──────────────────────────────────────────────────────────────
+
+function main() {
+ if (!fs.existsSync(VAULT_DIR)) {
+ console.error(`Vault directory not found: ${VAULT_DIR}`);
+ process.exit(1);
+ }
+
+ const files = collectMarkdownFiles(VAULT_DIR);
+ const noteIndex = buildNoteIndex(files);
+
+ console.log(`Found ${files.length} markdown files in ${VAULT_DIR}`);
+
+ let converted = 0, skipped = 0;
+
+ for (const file of files) {
+ const rel = path.relative(VAULT_DIR, file);
+ const outPath = path.join(
+ OUT_DIR,
+ rel
+ .split(path.sep)
+ .map((seg, i, arr) => i === arr.length - 1 ? seg : slugify(seg))
+ .join(path.sep)
+ );
+
+ const content = fs.readFileSync(file, "utf8");
+ let converted_content;
+
+ try {
+ converted_content = convertFile(content, noteIndex);
+ } catch (err) {
+ console.warn(` ⚠ Skipping ${rel}: ${err.message}`);
+ skipped++;
+ continue;
+ }
+
+ if (DRY_RUN) {
+ console.log(`\n${"─".repeat(60)}\n[DRY-RUN] ${rel} → ${path.relative(process.cwd(), outPath)}\n`);
+ console.log(converted_content.slice(0, 500) + (converted_content.length > 500 ? "\n…" : ""));
+ } else {
+ fs.mkdirSync(path.dirname(outPath), { recursive: true });
+ fs.writeFileSync(outPath, converted_content, "utf8");
+ console.log(` ✓ ${rel} → ${path.relative(process.cwd(), outPath)}`);
+ }
+ converted++;
+ }
+
+ console.log(`\nDone. ${converted} file(s) converted, ${skipped} skipped.`);
+}
+
+main();
\ No newline at end of file