feat: implement dynamic Eleventy tiling blog, modular CSS, and agent customizations
- Add project foundation README.md and root AGENTS.md for pair programming - Configure .agents/ rules and skills for Eleventy tiling and Obsidian publishing - Decompose monolithic styles into modular tokens, reset, layout, and component CSS - Implement Nunjucks layout inheritance (base.njk, post.njk, page.njk) and reusable partials - Configure Eleventy 3 with markdown-it-anchor, syntax highlighting, and dynamic collections - Migrate sample prototypes into dynamic dispatches lander, gateway, and Markdown posts - Add multi-stage Dockerfile and clean-URL nginx.conf for production serving
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
---
|
||||
name: obsidian-sync
|
||||
description: >-
|
||||
Use this skill when designing, building, or troubleshooting the Obsidian vault publishing integration, the server upload API, note normalization (wikilinks, callouts, frontmatter), or the custom Obsidian plugin.
|
||||
---
|
||||
|
||||
# Obsidian Vault Publishing & Sync Skill
|
||||
|
||||
This skill guides the agent in implementing the custom Obsidian plugin and server-side ingestion workflow for publishing blog posts directly from an Obsidian vault.
|
||||
|
||||
---
|
||||
|
||||
## 1. System Architecture
|
||||
|
||||
```text
|
||||
+-------------------+ +-----------------------+
|
||||
| Obsidian Vault | | Blog Server |
|
||||
| | | |
|
||||
| [Active Markdown] | POST /api/posts | /api/posts Endpoint |
|
||||
| + | -------------------> | + |
|
||||
| Custom Plugin | (Payload: JSON/MD) | Frontmatter Validator |
|
||||
+-------------------+ | + |
|
||||
| Wikilink Normalizer |
|
||||
| + |
|
||||
+-------------------+ | Write to `src/posts/` |
|
||||
| Self-Hosted | CouchDB Sync | + |
|
||||
| LiveSync Server | ===================> | 11ty Build Trigger |
|
||||
+-------------------+ +-----------------------+
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Ingestion API Specification
|
||||
|
||||
### Endpoint: `POST /api/posts`
|
||||
- **Headers:**
|
||||
- `Content-Type: application/json`
|
||||
- `Authorization: Bearer <API_SECRET>`
|
||||
- **Request Body:**
|
||||
```json
|
||||
{
|
||||
"slug": "reverse-engineering-iot-firmware",
|
||||
"content": "---\ntitle: \"Reverse Engineering IoT Firmware\"\ndate: 2026-09-12\ntags: [\"#firmware\"]\n---\n\n## Content...",
|
||||
"assets": [
|
||||
{
|
||||
"filename": "diagram.png",
|
||||
"data": "<base64-encoded-image>"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
- **Response:**
|
||||
- `200 OK`: `{"status": "published", "path": "/posts/reverse-engineering-iot-firmware/"}`
|
||||
- `400 Bad Request`: `{"error": "Missing required frontmatter: title, date"}`
|
||||
|
||||
---
|
||||
|
||||
## 3. Obsidian Markdown Normalization Rules
|
||||
|
||||
1. **Frontmatter Invariants:**
|
||||
- Must contain `title`, `date`, `tags` (array), and optional `excerpt`, `readingTime`, `draft`.
|
||||
2. **Wikilinks Translation:**
|
||||
- `[[article-slug]]` $\rightarrow$ `[article-slug](/posts/article-slug/)`
|
||||
- `[[article-slug|Custom Label]]` $\rightarrow$ `[Custom Label](/posts/article-slug/)`
|
||||
3. **Callout Translation:**
|
||||
- Obsidian callouts:
|
||||
```markdown
|
||||
> [!NOTE]
|
||||
> Informational notice
|
||||
```
|
||||
Must be rendered with corresponding terminal alert styling.
|
||||
4. **Internal Attachments:**
|
||||
- `![[screenshot.png]]` $\rightarrow$ translated to `` and written to `src/assets/images/posts/`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Custom Obsidian Plugin Development
|
||||
|
||||
The Obsidian plugin runs inside Obsidian desktop/mobile and interacts with the Obsidian API:
|
||||
1. Registers a ribbon icon (e.g. `send` or `terminal`) and a command: `Publish Current Note to River Rooks Blog`.
|
||||
2. Reads the active file:
|
||||
```typescript
|
||||
const activeFile = this.app.workspace.getActiveFile();
|
||||
const content = await this.app.vault.read(activeFile);
|
||||
```
|
||||
3. Parses frontmatter using Obsidian's metadata cache:
|
||||
```typescript
|
||||
const cache = this.app.metadataCache.getFileCache(activeFile);
|
||||
```
|
||||
4. Sends the HTTP payload to the configured blog server URL with the Bearer token.
|
||||
5. Displays an Obsidian `Notice('Published successfully to River Rooks Blog!')`.
|
||||
|
||||
---
|
||||
|
||||
## 5. Self-Hosted LiveSync Compatibility
|
||||
|
||||
If using CouchDB-based self-hosted LiveSync:
|
||||
- A local watcher or CouchDB change listener daemon can be configured to detect updates to notes marked with `published: true` or in a dedicated `Blog/` folder.
|
||||
- When an update event fires, the daemon pulls the latest revision from the CouchDB database, runs the normalization pipeline, and saves it to `src/posts/`.
|
||||
Reference in New Issue
Block a user