diff --git a/sitemd/0.1.3/.codex-plugin/plugin.json b/sitemd/0.1.3/.codex-plugin/plugin.json new file mode 100644 index 00000000..6eafea45 --- /dev/null +++ b/sitemd/0.1.3/.codex-plugin/plugin.json @@ -0,0 +1,4 @@ +{ + "name": "sitemd", + "version": "0.1.3" +} diff --git a/sitemd/0.1.3/.mcp.json b/sitemd/0.1.3/.mcp.json new file mode 100644 index 00000000..b7e03656 --- /dev/null +++ b/sitemd/0.1.3/.mcp.json @@ -0,0 +1,11 @@ +{ + "mcpServers": { + "sitemd": { + "type": "stdio", + "command": "./sitemd/sitemd", + "args": ["mcp"], + "env": {}, + "startup_timeout_sec": 20 + } + } +} diff --git a/sitemd/0.1.3/sitemd/install b/sitemd/0.1.3/sitemd/install new file mode 100755 index 00000000..57ea8bbf --- /dev/null +++ b/sitemd/0.1.3/sitemd/install @@ -0,0 +1,139 @@ +#!/bin/sh +# sitemd install — downloads the sitemd binary for your platform. +# No Node.js required. Run: ./sitemd/install +# +# This script detects your OS and architecture, downloads the correct +# binary from GitHub Releases, and places it at sitemd/sitemd. + +set -e + +REPO="sitemd-cc/sitemd" +# On-disk name is sitemd-bin to avoid collision with the sitemd/cli.js JS +# wrapper that npm's bin shim points at. Archive still ships the file as +# `sitemd` / `sitemd.exe` — we rename during extraction (see below). +BINARY_NAME="sitemd-bin" +ARCHIVE_BINARY_NAME="sitemd" +FORCE=0 +[ "$1" = "--force" ] && FORCE=1 + +# Detect platform +OS=$(uname -s | tr '[:upper:]' '[:lower:]') +ARCH=$(uname -m) + +case "$OS" in + darwin) PLATFORM="darwin" ;; + linux) PLATFORM="linux" ;; + mingw*|msys*|cygwin*) PLATFORM="win" ;; + *) echo "Unsupported OS: $OS" >&2; exit 1 ;; +esac + +case "$ARCH" in + x86_64|amd64) ARCH="x64" ;; + arm64|aarch64) ARCH="arm64" ;; + *) echo "Unsupported architecture: $ARCH" >&2; exit 1 ;; +esac + +if [ "$PLATFORM" = "win" ]; then + ASSET="sitemd-${PLATFORM}-${ARCH}.zip" + BINARY_NAME="sitemd-bin.exe" + ARCHIVE_BINARY_NAME="sitemd.exe" +else + ASSET="sitemd-*-${PLATFORM}-${ARCH}.tar.gz" +fi + +# Find the sitemd directory (where this script lives) +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + +# Determine wanted version. Prefer the sibling package.json (npm package or +# project created by `sitemd init`); fall back to GitHub Releases API for +# clone-from-source and download-the-tarball flows. +WANTED_VERSION="" +if [ -f "$SCRIPT_DIR/../package.json" ]; then + WANTED_VERSION=$(grep '"version"' "$SCRIPT_DIR/../package.json" | head -1 | sed 's/.*"version" *: *"\([^"]*\)".*/\1/') +fi + +if [ -z "$WANTED_VERSION" ]; then + echo " sitemd install" + echo " Platform: ${PLATFORM}-${ARCH}" + echo " Finding latest release..." + if command -v curl >/dev/null 2>&1; then + LATEST=$(curl -fsSL "https://api.github.com/repos/$REPO/releases/latest" | grep '"tag_name"' | head -1 | sed 's/.*"tag_name": *"//;s/".*//') + elif command -v wget >/dev/null 2>&1; then + LATEST=$(wget -qO- "https://api.github.com/repos/$REPO/releases/latest" | grep '"tag_name"' | head -1 | sed 's/.*"tag_name": *"//;s/".*//') + else + echo " Error: curl or wget required" >&2 + exit 1 + fi + if [ -z "$LATEST" ]; then + echo " Error: could not determine latest release" >&2 + exit 1 + fi + WANTED_VERSION="${LATEST#v}" +fi + +VERSION="$WANTED_VERSION" +LATEST="v$VERSION" + +# Idempotency: if a binary is already installed at the wanted version, exit. +if [ "$FORCE" = "0" ] && [ -f "$SCRIPT_DIR/$BINARY_NAME" ]; then + EXISTING_VERSION=$("$SCRIPT_DIR/$BINARY_NAME" --version 2>/dev/null | head -1 | sed 's/.*[^0-9]\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\).*/\1/') + if [ -n "$EXISTING_VERSION" ] && [ "$EXISTING_VERSION" = "$VERSION" ]; then + exit 0 + fi + if [ -n "$EXISTING_VERSION" ] && [ "$EXISTING_VERSION" != "$VERSION" ]; then + echo " sitemd: upgrading $EXISTING_VERSION -> $VERSION" + fi +fi + +echo " sitemd install" +echo " Platform: ${PLATFORM}-${ARCH}" +echo " Version: $VERSION" + +# Build download URL +ARCHIVE="sitemd-${VERSION}-${PLATFORM}-${ARCH}" +if [ "$PLATFORM" = "win" ]; then + ARCHIVE="${ARCHIVE}.zip" +else + ARCHIVE="${ARCHIVE}.tar.gz" +fi +URL="https://github.com/$REPO/releases/download/$LATEST/$ARCHIVE" + +# Download +TMPDIR=$(mktemp -d) +TMPFILE="$TMPDIR/$ARCHIVE" + +echo " Downloading $ARCHIVE..." +if command -v curl >/dev/null 2>&1; then + curl -fsSL -o "$TMPFILE" "$URL" +else + wget -q -O "$TMPFILE" "$URL" +fi + +# Extract binary +echo " Extracting..." +if [ "$PLATFORM" = "win" ]; then + unzip -qo "$TMPFILE" -d "$TMPDIR/extracted" +else + mkdir -p "$TMPDIR/extracted" + tar -xzf "$TMPFILE" -C "$TMPDIR/extracted" +fi + +# Find the binary inside the archive (ships as sitemd/sitemd, renamed on extraction to sitemd-bin) +EXTRACTED_BIN=$(find "$TMPDIR/extracted" -name "$ARCHIVE_BINARY_NAME" -type f | head -1) +if [ -z "$EXTRACTED_BIN" ]; then + echo " Error: binary not found in archive" >&2 + rm -rf "$TMPDIR" + exit 1 +fi + +# Install +cp "$EXTRACTED_BIN" "$SCRIPT_DIR/$BINARY_NAME" +chmod +x "$SCRIPT_DIR/$BINARY_NAME" + +# Clean up +rm -rf "$TMPDIR" + +echo "" +echo " Installed: $SCRIPT_DIR/$BINARY_NAME" +echo " Run: ./sitemd/sitemd launch" +echo "" diff --git a/sitemd/0.1.3/sitemd/install.js b/sitemd/0.1.3/sitemd/install.js new file mode 100644 index 00000000..bd7c6d02 --- /dev/null +++ b/sitemd/0.1.3/sitemd/install.js @@ -0,0 +1,263 @@ +#!/usr/bin/env node +/** + * sitemd install — downloads the platform binary from GitHub Releases. + * + * Cross-platform Node bootstrap. Used as the npm postinstall hook and as a + * manual recovery command when --ignore-scripts was used. The shell script + * `install` next to this file does the same job for environments without Node. + * + * Idempotent: re-running with a matching binary version is a no-op. + * + * Source of truth for the wanted version is the sibling `../package.json`. + * That works in both contexts where this script ships: + * - npm package: ../package.json is the published @sitemd-cc/sitemd version + * - sitemd init project: ../package.json is the version copied at init time + */ + +const fs = require('fs') +const path = require('path') +const os = require('os') +const https = require('https') +const { execFileSync } = require('child_process') + +const GITHUB_RELEASE_URL = 'https://github.com/sitemd-cc/sitemd/releases/download' + +const FORCE = process.argv.includes('--force') + +function detectPlatform() { + const platform = process.platform === 'darwin' ? 'darwin' + : process.platform === 'linux' ? 'linux' + : process.platform === 'win32' ? 'win' : null + if (!platform) throw new Error(`Unsupported platform: ${process.platform}`) + const arch = process.arch === 'arm64' ? 'arm64' : 'x64' + const ext = platform === 'win' ? '.zip' : '.tar.gz' + // On-disk name is sitemd-bin so it doesn't collide with the sitemd/sitemd + // JS wrapper (which npm's bin shim points at). The release archive still + // contains the native binary as `sitemd` / `sitemd.exe` — we rename on + // extraction to sitemd-bin / sitemd-bin.exe. + const binaryName = platform === 'win' ? 'sitemd-bin.exe' : 'sitemd-bin' + const archiveBinaryName = platform === 'win' ? 'sitemd.exe' : 'sitemd' + return { platform, arch, ext, binaryName, archiveBinaryName } +} + +function readWantedVersion() { + const pkgPath = path.join(__dirname, '..', 'package.json') + if (!fs.existsSync(pkgPath)) return null + try { + const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')) + return pkg.version || null + } catch { + return null + } +} + +function readExistingVersion(binaryPath) { + if (!fs.existsSync(binaryPath)) return null + try { + const out = execFileSync(binaryPath, ['--version'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }) + const match = out.match(/(\d+\.\d+\.\d+)/) + return match ? match[1] : null + } catch { + return null + } +} + +function download(url, dest) { + return new Promise((resolve, reject) => { + const follow = (u) => { + https.get(u, { headers: { 'User-Agent': 'sitemd-install' } }, res => { + if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { + return follow(res.headers.location) + } + if (res.statusCode !== 200) { + return reject(new Error(`Download failed: HTTP ${res.statusCode} ${u}`)) + } + const file = fs.createWriteStream(dest) + res.pipe(file) + file.on('finish', () => { file.close(); resolve() }) + file.on('error', reject) + }).on('error', reject) + } + follow(url) + }) +} + +// --------------------------------------------------------------------------- +// End-of-install next-steps message (dep-install flow) +// --------------------------------------------------------------------------- + +function printNextSteps(projectRoot) { + const hasScaffold = fs.existsSync(path.join(projectRoot, 'sitemd', 'sitemd')) + console.log() + console.log(` sitemd ready${hasScaffold ? ' at ./sitemd/' : ''}`) + console.log() + console.log(` Launch the dev server:`) + console.log(` Restart your agent session so it picks up the sitemd MCP server,`) + console.log(` then ask: "launch sitemd" — your agent runs the /launch skill.`) + console.log() + if (hasScaffold) { + console.log(` Or from shell:`) + console.log(` cd sitemd && npm install && ./sitemd/sitemd launch`) + console.log() + } + console.log(` Docs: https://sitemd.cc/docs`) + console.log() +} + +// --------------------------------------------------------------------------- +// Agent file setup — delegates to the shared non-destructive writer. +// --------------------------------------------------------------------------- + +async function setupAgentFiles(sitemdDir, projectRoot) { + const agentRes = path.join(sitemdDir, 'agent-resources') + if (!fs.existsSync(agentRes)) return + + // Binary command in .mcp.json: + // 1. Prefer `./sitemd/sitemd` — the Phase-4 scaffolded wrapper at + // /sitemd/sitemd. Stable, self-contained. + // 2. Fall back to `./node_modules/.bin/sitemd` — npm's bin shim, which + // always exists after install and survives hoisting. + // 3. Final fallback: relative path from projectRoot into the package. + const scaffoldedWrapper = path.join(projectRoot, 'sitemd', 'sitemd') + const binShim = path.join(projectRoot, 'node_modules', '.bin', 'sitemd') + const binaryRel = fs.existsSync(scaffoldedWrapper) + ? './sitemd/sitemd' + : fs.existsSync(binShim) + ? './node_modules/.bin/sitemd' + : './' + path.relative(projectRoot, path.join(sitemdDir, 'sitemd')).split(path.sep).join('/') + + const { copyAgentFiles } = require('./engine/cli/agent-files') + await copyAgentFiles(sitemdDir, projectRoot, { binaryRel }) +} + +// --------------------------------------------------------------------------- +// Main +// --------------------------------------------------------------------------- + +async function main() { + const { platform, arch, ext, binaryName, archiveBinaryName } = detectPlatform() + const binaryPath = path.join(__dirname, binaryName) + const wanted = readWantedVersion() + + if (!wanted) { + console.error(` sitemd install: could not read version from ../package.json`) + console.error(` Try the shell installer instead: ./sitemd/install`) + return + } + + // Idempotency: skip if existing binary matches wanted version + if (!FORCE) { + const existing = readExistingVersion(binaryPath) + if (existing && existing === wanted) { + // Silent no-op — common case for re-runs + return + } + if (existing && existing !== wanted) { + console.log(` sitemd: upgrading ${existing} → ${wanted}`) + } + } + + const archive = `sitemd-${wanted}-${platform}-${arch}${ext}` + const url = `${GITHUB_RELEASE_URL}/v${wanted}/${archive}` + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'sitemd-install-')) + const archivePath = path.join(tmpDir, archive) + const extractDir = path.join(tmpDir, 'extracted') + + try { + console.log(` sitemd: downloading v${wanted} (${platform}-${arch})...`) + await download(url, archivePath) + + fs.mkdirSync(extractDir, { recursive: true }) + if (ext === '.zip') { + // Windows 10 1803+ ships tar.exe which handles zip files + execFileSync('tar', ['-xf', archivePath, '-C', extractDir], { stdio: 'ignore' }) + } else { + execFileSync('tar', ['-xzf', archivePath, '-C', extractDir], { stdio: 'ignore' }) + } + + // Find the binary inside the extracted archive (under sitemd/) + let sourceRoot = extractDir + if (fs.existsSync(path.join(extractDir, 'sitemd'))) { + sourceRoot = path.join(extractDir, 'sitemd') + } else { + const entries = fs.readdirSync(extractDir) + if (entries.length === 1 && fs.statSync(path.join(extractDir, entries[0])).isDirectory()) { + sourceRoot = path.join(extractDir, entries[0]) + if (fs.existsSync(path.join(sourceRoot, 'sitemd'))) { + sourceRoot = path.join(sourceRoot, 'sitemd') + } + } + } + const extractedBinary = path.join(sourceRoot, archiveBinaryName) + if (!fs.existsSync(extractedBinary)) { + throw new Error(`Binary not found in archive: ${archiveBinaryName}`) + } + + fs.rmSync(binaryPath, { force: true }) + fs.copyFileSync(extractedBinary, binaryPath) + if (platform !== 'win') fs.chmodSync(binaryPath, 0o755) + + console.log(` sitemd v${wanted} installed`) + + // Propagate agent files to the project root (where npm install was invoked). + // Three flows: + // - npx one-shot: __dirname is in the ephemeral npx cache (path contains + // `_npx`). `init` writes agent files itself with the correct scaffold + // path; this postinstall pre-scaffold can't know the target dir, and + // anything it wrote would point into the cache (broken on prune). + // → skip. + // - source-repo dev (maintainers running `npm install` inside + // /Users/.../sitemd): don't pollute the source tree. → skip. + // - dep install (`npm install @sitemd-cc/sitemd` in a user project): + // write agent files at INIT_CWD pointing at the Phase-4 scaffold + // (./sitemd/sitemd) or the bin shim fallback. → run. + const projectRoot = process.env.INIT_CWD + const isNpxCache = __dirname.split(path.sep).includes('_npx') + const isSourceRepo = projectRoot && path.resolve(projectRoot) === path.resolve(__dirname, '..') + if (projectRoot && !isNpxCache && !isSourceRepo) { + // Phase 4: auto-scaffold `./sitemd/` so `npm install @sitemd-cc/sitemd` + // produces the same end state as `npx init sitemd`. Shell out to the + // binary we just downloaded; `--skip-agent-files` keeps init from + // writing a cwd-level pass (postinstall owns that via setupAgentFiles + // below). `--quiet` suppresses init's Next-Steps output; this + // postinstall prints its own unified message. + const scaffoldDir = path.join(projectRoot, 'sitemd') + if (!fs.existsSync(scaffoldDir)) { + try { + execFileSync(binaryPath, ['init', 'sitemd', '--skip-agent-files', '--quiet'], { + stdio: 'inherit', + cwd: projectRoot, + }) + } catch (err) { + console.error(` sitemd: scaffold failed (${err.message})`) + console.error(` To retry manually: ./node_modules/.bin/sitemd init sitemd`) + } + } else { + console.log(` sitemd: ./sitemd/ already exists — skipped scaffolding`) + } + + await setupAgentFiles(__dirname, projectRoot) + + printNextSteps(projectRoot) + } + } catch (err) { + console.error(` sitemd install failed: ${err.message}`) + console.error(` To retry: node sitemd/install.js (or ./sitemd/install on Unix)`) + } finally { + try { fs.rmSync(tmpDir, { recursive: true, force: true }) } catch {} + } +} + +// Run main() only when invoked directly (e.g. `node sitemd/install.js` or +// npm's postinstall hook). When required from cli.js as a recovery path, +// the caller invokes main() itself and handles errors. +if (require.main === module) { + main().catch(err => { + console.error(` sitemd install failed: ${err.message}`) + console.error(` To retry: node sitemd/install.js (or ./sitemd/install on Unix)`) + // Always exit 0 — npm install must not fail because of binary download trouble + process.exit(0) + }) +} + +module.exports = { main, detectPlatform } diff --git a/sitemd/0.1.3/skills/SKILL.md b/sitemd/0.1.3/skills/SKILL.md new file mode 100644 index 00000000..8abbbc5b --- /dev/null +++ b/sitemd/0.1.3/skills/SKILL.md @@ -0,0 +1,74 @@ +--- +name: sitemd +description: Build and manage sitemd static websites from Markdown. Create pages, generate content, configure settings, and deploy. +--- + +# sitemd + +You are working in a sitemd project — a markdown-based static site builder with MCP integration. + +## Project Structure + +- `sitemd` — Compiled binary (run `./sitemd/sitemd launch`) +- `install` — Bootstrap script (run `./sitemd/install` to download binary) +- `install.js` — Cross-platform Node bootstrap (run `node sitemd/install.js`); also runs automatically as the npm `postinstall` hook +- `pages/` — Markdown content files with YAML frontmatter +- `settings/` — Site configuration (YAML frontmatter in `.md` files) +- `theme/` — CSS and HTML templates +- `media/` — Images and assets +- `site/` — Built output + +## Available MCP Tools + +Use these tools to manage the site: + +| Tool | Purpose | +|---|---| +| `sitemd_status` | Project state overview | +| `sitemd_pages_create` | Create new pages (writes file + nav + groups) | +| `sitemd_pages_create_batch` | Create multiple pages in one call | +| `sitemd_pages_delete` | Delete a page (cleans up nav + groups) | +| `sitemd_groups_add_pages` | Add pages to group sidebar | +| `sitemd_site_context` | Site identity, pages, conventions | +| `sitemd_content_validate` | Validate content | +| `sitemd_seo_audit` | SEO health check with scored report | +| `sitemd_init` | Initialize project from template | +| `sitemd_build` | Build site locally | +| `sitemd_deploy` | Build and deploy site | +| `sitemd_activate` | Activate site (permanent) | +| `sitemd_clone` | Clone existing website | +| `sitemd_config_set` | Set backend config | +| `sitemd_update_check` | Check for updates | + +Read pages, settings, and groups files directly — no MCP tool needed for reads. + +## First Steps + +1. **If no binary** (`sitemd/sitemd` does not exist) — run `./sitemd/install` (Unix) or `node sitemd/install.js` (any platform) to download it. The MCP server runs the binary; without it, every sitemd_* tool call will fail. +2. Call `sitemd_status` to understand the project state +3. Read files in `pages/` to see existing content +4. Call `sitemd_site_context` with a content type to get site identity, conventions, and existing pages +5. Create pages with `sitemd_pages_create` — use rich components (buttons, cards, embeds, galleries) +6. Validate with `sitemd_content_validate` + +## Settings + +All configuration lives in `settings/*.md` frontmatter. Key files: `meta.md` (identity), `header.md` (nav), `footer.md` (footer), `groups.md` (page groups), `theme.md` (colors/fonts), `build.md` (dev server), `deploy.md` (deployment). + +## Markdown Extensions + +Beyond standard markdown, sitemd supports rich components. The syntax reference is below. + +- `button: Label: /slug` — styled buttons. Modifiers: `+outline`, `+big`, `+newtab`, `+color:red` +- `card: Title` / `card-text:` / `card-image:` / `card-link:` — responsive card grids +- `embed: URL` — auto-detects YouTube, Vimeo, Spotify, X, CodePen, etc. +- `gallery:` with indented `![alt](url)` — image grid with lightbox +- `image-row:` with indented `![alt](url)` — equal-height image row +- `![alt](url +width:N +circle +bw +expand)` — image modifiers +- `[text]{tooltip content}` — inline tooltips +- `modal: id` with indented content, trigger via `[link](#modal:id)` — modal dialogs +- `{#custom-id}` — inline anchors +- `[text](url+newtab)` — link modifiers +- `form:` with indented YAML — forms +- `gated: type1, type2` ... `/gated` — gated sections +- `data: source` / `data-display: cards|list|table` — dynamic data \ No newline at end of file