init: content-forge project with write-article skill, scripts, and CLAUDE.md

This commit is contained in:
lizikk 2026-03-02 11:20:38 +08:00
commit c3209dfa68
6 changed files with 724 additions and 0 deletions

View File

@ -0,0 +1,126 @@
---
name: write-article
description: >
Generate articles from topics and outlines stored in an Obsidian vault.
This skill should be used when creating long-form content such as tech blogs,
opinion pieces, tutorials, or social posts from structured topic briefs.
Depends on obsidian:obsidian-cli for all vault read/write operations.
Trigger words: 写文章、写作、生成草稿、技术博客、观点文、教程、社交短文、
article、blog post、draft。
---
# Write-ArticleObsidian 文章写作技能
## 目标
用统一流程产出可发布草稿,避免无来源、无结构、无 frontmatter 的半成品。
## 输入要求
必须提供:
- `topic`文章主题1 句话)
- `style``tech_blog` | `opinion` | `tutorial` | `social_short`
- `audience`:目标读者
- `source_notes`:至少 1 个 Obsidian note 路径vault 相对路径)
- `tags`1-5 个标签
可选但建议提供:
- `title`:文章标题
- `slug`:文件 slug英文短横线
- `length_target`:目标字数或段落数
- `cta`:结尾行动建议
- `publish_channel`:发布渠道
## 强制工作流(不得跳步)
### Step 1读取素材
逐个读取 `source_notes` 中的笔记,确认事实边界。
```bash
obsidian read path="<source_note_path_1>"
obsidian read path="<source_note_path_2>"
```
执行要求:
- `source_notes` 中每个路径都要读取。
- 未读完素材前,禁止进入写作。
- 素材不足以支撑结论时,输出 `[?]` 标记并向用户索要补充信息。
### Step 2生成文章草稿
`references/writing-styles.md` 选择对应风格模板生成正文。
执行要求:
- 只使用已读取素材中的事实。
- 观点必须有论证链,不得堆砌口号。
- 内容结构必须匹配所选风格的"结构"要求。
### Step 3创建草稿文件
将生成的 Markdown 写入 `02-drafts/` 目录。
```bash
obsidian create path="02-drafts/<YYYY-MM-DD>-<slug>.md" content="<ARTICLE_MARKDOWN>"
```
执行要求:
- 目标目录必须是 `02-drafts/`
- 文件名必须包含日期前缀与 slug。
- 正文必须是 Markdown可直接发布或二次编辑。
### Step 4设置 frontmatter
对 Step 3 产物逐项设置元数据。
```bash
obsidian property:set path="02-drafts/<YYYY-MM-DD>-<slug>.md" name="title" value="<title>"
obsidian property:set path="02-drafts/<YYYY-MM-DD>-<slug>.md" name="slug" value="<slug>"
obsidian property:set path="02-drafts/<YYYY-MM-DD>-<slug>.md" name="style" value="<style>"
obsidian property:set path="02-drafts/<YYYY-MM-DD>-<slug>.md" name="audience" value="<audience>"
obsidian property:set path="02-drafts/<YYYY-MM-DD>-<slug>.md" name="status" value="draft"
obsidian property:set path="02-drafts/<YYYY-MM-DD>-<slug>.md" name="tags" value="[\"tag1\",\"tag2\"]" type=list
obsidian property:set path="02-drafts/<YYYY-MM-DD>-<slug>.md" name="source_notes" value="[\"note1.md\"]" type=list
obsidian property:set path="02-drafts/<YYYY-MM-DD>-<slug>.md" name="created_at" value="<YYYY-MM-DD>" type=date
obsidian property:set path="02-drafts/<YYYY-MM-DD>-<slug>.md" name="updated_at" value="<YYYY-MM-DD>" type=date
```
最小 frontmatter 字段集合:
- `title`
- `slug`
- `style`
- `audience`
- `status`
- `tags`
- `source_notes`
- `created_at`
- `updated_at`
## 输出约束
- 输出仅包含文章草稿及必要元数据,不输出无关解释。
- 文章语言默认中文,除非用户显式要求其他语言。
- 禁止编造数据、案例、引文、实验结果。
- 未确认的事实必须标记 `[?]`,并明确缺失项。
- 结构必须符合所选风格规范(见 `references/writing-styles.md`)。
- 段落应可读,避免空泛套话与重复表达。
## 质量检查(交付前必须全部通过)
- [ ] 所有 `source_notes` 已执行 `obsidian read`
- [ ] 文章结构与目标风格一致
- [ ] 关键结论可追溯到输入素材
- [ ] 草稿已通过 `obsidian create` 写入 `02-drafts/`
- [ ] frontmatter 必填字段已通过 `obsidian property:set` 完整设置
- [ ] 标题、slug、标签与内容主题一致
- [ ] 不存在事实捏造与无依据断言
## 失败处理
- 缺少必要输入时,返回:`[?] 缺失输入:<字段名>`,并停止写入。
- `obsidian` 命令失败时,返回:`[✗] <命令> 失败:<错误信息>`,并停止后续步骤。
- 如果无法确定风格,默认使用 `tech_blog`,同时显式告知用户。
## 风格参考
- `references/writing-styles.md`

View File

@ -0,0 +1,82 @@
# Writing Styles Reference
本文档定义 `write-article` skill 的 4 类写作风格。每种风格都包含固定的语气、结构、禁忌。
## 1) 技术博客(`tech_blog`
### 语气
- 专业、克制、解释导向。
- 对术语给出必要定义,不炫技。
- 结论可验证,避免绝对化表达。
### 结构
1. 问题背景:问题是什么,为什么值得解决。
2. 核心方案:关键设计与取舍。
3. 实现细节:步骤、代码片段、配置或流程。
4. 验证结果:如何验证有效性,边界在哪里。
5. 总结与扩展:适用场景、下一步优化。
### 禁忌
- 只贴代码不解释设计意图。
- 用“黑魔法”式技巧替代可维护方案。
- 夸大性能收益但不给验证条件。
## 2) 观点文(`opinion`
### 语气
- 立场鲜明,但论证必须有证据链。
- 可批判、可反驳,不做人身攻击。
- 允许价值判断,但要明确前提条件。
### 结构
1. 核心论点:开篇直接给结论。
2. 论据展开2-3 个关键论据,逐条论证。
3. 反方视角:承认反例或限制条件。
4. 立场收束:重申结论并给出行动建议。
### 禁忌
- 情绪输出替代推理。
- 稻草人论证(曲解对方观点再反驳)。
- 断言“唯一正确答案”却不说明边界。
## 3) 教程(`tutorial`
### 语气
- 教练式表达,步骤清晰、可执行。
- 默认读者不知道上下文,减少跳步。
- 重点是“让读者做出来”,不是展示作者懂多少。
### 结构
1. 目标与前置条件:完成后能得到什么,需要什么环境。
2. Step-by-step按顺序执行每步只有一个主要动作。
3. 结果验证:每步的预期输出或检查点。
4. 常见错误:高频坑位与修复方法。
5. 收尾:复盘关键点与可选进阶路线。
### 禁忌
- 跳过关键前置条件。
- 把多个动作塞进同一步,导致不可复现。
- 只给“成功路径”,不提供失败排查。
## 4) 社交短文(`social_short`
### 语气
- 短促、有画面感、观点集中。
- 首句抓人,结尾可互动。
- 避免过度术语化,优先可传播表达。
### 结构
1. Hook首句提出冲突、反差或问题。
2. 核心信息1-2 个关键观点或故事片段。
3. 行动结尾提问、CTA 或一句可转发结论。
### 禁忌
- 铺垫过长,前 3 句还没进主题。
- 一条短文塞入过多观点。
- 使用标题党或误导性陈述。
## 风格选择建议
- 需要解释技术方案与实践细节:选 `tech_blog`
- 需要表达立场并说服读者:选 `opinion`
- 需要让读者可复现地完成任务:选 `tutorial`
- 需要快速传播观点或洞察:选 `social_short`

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
# Vault has its own git repo
content-forge/

292
CLAUDE.md Normal file
View File

@ -0,0 +1,292 @@
# content-forge / CLAUDE.md
## 1) 项目定位
`content-forge` 是一个「以 Obsidian Vault 为单一内容源」的自媒体内容生产编排项目,目标是把选题、写作、配图、格式化、发布变成可重复执行的流水线。
核心边界:
- 只新增一个本地 skill`write-article`(文章生成)。
- 其余能力优先复用现有 `baoyu-*`、`image-gen-orchestrator`、`basic-image-gen`、`social-media-pipeline`。
- 所有中间产物必须落到 Vault禁止在多个系统维护平行真相。
成功标准:
- 可以从 `01-topics` 的选题笔记自动产出 `02-drafts` 草稿。
- 可以把草稿格式化并生成配图资产,再发布到微信/X。
- 全链路状态通过 frontmatter 字段可追踪。
### 1.1 Quick Start
```bash
# 1. 初始化 Vault创建目录 + 注册到 Obsidian
bash scripts/init-vault.sh
# 2. 验证环境
command -v obsidian # CLI 可用
obsidian vault # Vault 已注册
obsidian search query="templates" # 模板可搜索
# 3. 创建第一个选题
obsidian create path="01-topics/2026-03-02-my-first-topic.md" content="---
title: 我的第一个选题
slug: my-first-topic
status: topic
content_type: article
---
# 选题内容"
# 4. 用 write-article skill 生成草稿
# 在 Claude Code 中执行:/write-article
```
### 1.2 项目文件结构
```text
content-forge/
├── CLAUDE.md # 项目规范(本文件)
├── scripts/
│ └── init-vault.sh # Vault 初始化脚本
├── .claude/
│ └── skills/
│ └── write-article/ # 本项目唯一新增 skill
│ ├── SKILL.md # skill 定义与工作流
│ └── references/
│ └── writing-styles.md # 4 种写作风格规范
└── content-forge/ # Obsidian Vault内容存储
├── 00-inbox/ # 临时灵感、原始素材
├── 01-topics/ # 结构化选题
├── 02-drafts/ # 初稿/改稿
├── 03-review/ # 待审核稿
├── 04-published/ # 已发布归档
├── 05-assets/ # 配图、封面等资产
└── templates/ # article / tech-blog / social-post
```
---
## 2) 完整依赖关系图
### 2.1 技能依赖图(必读)
```mermaid
graph TD
WA[write-article<br/>新建] --> OBS[obsidian:obsidian-cli]
BAI[baoyu-article-illustrator] --> AUTO[image gen skill auto-discover]
BCI[baoyu-cover-image] --> AUTO
BXI[baoyu-xhs-images] --> AUTO
BINF[baoyu-infographic] --> AUTO
AUTO --> IGO[image-gen-orchestrator]
IGO --> BIG[basic-image-gen<br/>硬依赖]
AUTO --> BIMG[baoyu-image-gen<br/>备选]
PTW[post-to-wechat] --> M2H[baoyu-markdown-to-html<br/>硬依赖]
PTW --> CDP[Chrome CDP 发布链路]
PTW --> WXA[WeChat API 发布链路]
PTX[post-to-x] --> XCDP[Chrome CDP + X 登录态]
UTM[url-to-markdown] --> UCDP[Chrome CDP]
FMD[format-markdown] --> REMARK[remark + remark-cjk-friendly]
SMP[social-media-pipeline] --> NONE[无 skill 运行时依赖]
```
### 2.2 生产链路图(执行顺序)
```text
social-media-pipeline
-> url-to-markdown可选采集外部素材
-> write-article
-> format-markdown
-> baoyu-cover-image / baoyu-article-illustrator / baoyu-xhs-images / baoyu-infographic
-> image-gen-orchestrator -> basic-image-gen
-> post-to-wechat
-> post-to-x
```
---
## 3) 必需插件 / API 清单
### 3.1 插件与本地技能
| 类型 | 名称 | 作用 | 必要性 | 最小验证 |
|---|---|---|---|---|
| 插件 | `obsidian` | 提供 `obsidian-cli`Vault 读写) | 必需 | `obs vault` |
| 插件包 | `ai-generation-skills``content-skills` | 提供 `baoyu-*` 系列技能 | 必需 | 在技能列表可见对应 skill |
| 本地 skill | `basic-image-gen` | 图片生成底层能力 | 必需 | 能被 `image-gen-orchestrator` 调用 |
| 本地 skill | `image-gen-orchestrator` | 批量图片编排 | 必需 | 触发图像任务成功返回 |
| 本地 skill | `social-media-pipeline` | 选题/模板/日历方法论 | 建议启用 | 能输出结构化选题与日历 |
| 本地 skill | `write-article` | 从 Vault 选题生成文章 | 必需(本项目唯一新增) | 生成 `02-drafts/*.md` |
### 3.2 外部依赖与环境变量
| 类别 | 项 | 用途 | 关键配置 |
|---|---|---|---|
| 浏览器 | Google Chrome | CDP 抓取与发布url-to-markdown / post-to-wechat / post-to-x | 本机可启动 Chrome |
| 图像 API | OpenAI / Google / DashScope 任一 | `basic-image-gen` 推理 | `OPENAI_API_KEY``GOOGLE_API_KEY``DASHSCOPE_API_KEY` |
| 微信发布 | WeChat APIAPI 模式) | 公众号发布 | `WECHAT_APP_ID`, `WECHAT_APP_SECRET` |
| 微信/X 发布 | 浏览器登录态CDP 模式) | 无 API 时发布兜底 | 本地已登录对应账号 |
| 配置文档 | `EXTEND.md`(各 skill | 首次配置入口 | 按 skill 文档逐项完成 |
---
## 4) Vault 目录约定与 frontmatter 规范
### 4.1 目录约定(固定)
默认 Vault 路径:`<PROJECT_ROOT>/content-forge/`(通过 `scripts/init-vault.sh` 初始化,可用 `VAULT_PATH` 环境变量覆盖)。
Obsidian CLI 用**目录名**作为 vault 名称,因此目录名必须是 `content-forge`(非 `vault`),命令中使用 `vault="content-forge"` 指定。
```text
<VAULT_ROOT>/
├── 00-inbox/ # 临时灵感、原始素材、待分类
├── 01-topics/ # 结构化选题write-article 输入)
├── 02-drafts/ # 初稿/改稿format-markdown 输入)
├── 03-review/ # 待审核稿(人工或规则审校)
├── 04-published/ # 已发布内容归档
├── 05-assets/ # 配图、封面、信息图等资产
└── templates/ # 模板article / tech-blog / social-post
```
命名规则:
- 文件名:`YYYY-MM-DD-<slug>.md`
- 资产目录:`05-assets/<slug>/...`
- 一个内容单元topic/draft/review/published使用同一个 `slug`,避免多份身份。
### 4.2 frontmatter 统一规范
所有 `01-topics`、`02-drafts`、`03-review`、`04-published` 笔记必须包含以下字段:
```yaml
---
id: "2026-03-01-example-slug"
title: "示例标题"
slug: "example-slug"
status: "topic" # topic | draft | review | published | archived
content_type: "article" # article | thread | x-post | wechat-post | infographic
channels: ["wechat", "x"]
language: "zh-CN"
source_urls: []
assets: []
cover_image: ""
template: "article"
owner: "content-forge"
created_at: "2026-03-01T00:00:00+08:00"
updated_at: "2026-03-01T00:00:00+08:00"
published_at: null
---
```
字段约束:
- `id``slug` 一一对应,禁止同 slug 多 id。
- `status` 只能按 `topic -> draft -> review -> published` 前进。
- `assets` 只存 Vault 相对路径(禁止外部临时路径)。
---
## 5) 内容生产流水线(阶段 -> skill -> obs 命令)
> 说明:以下命令是编排约定,默认在已注册 Vault 环境执行。
| 阶段 | 输入/输出 | Skill | obs 命令(最小可执行) |
|---|---|---|---|
| S0 选题入库 | 输入:灵感/素材;输出:`01-topics/*.md` | `social-media-pipeline`(方法论) | `obsidian create path="01-topics/2026-03-01-<slug>.md"` |
| S1 外链采集(可选) | 输入URL输出Markdown 摘要并入 topic | `url-to-markdown` | `obsidian read path="01-topics/2026-03-01-<slug>.md"` |
| S2 草稿生成 | 输入topic输出`02-drafts/*.md` | `write-article` | `obsidian create path="02-drafts/2026-03-01-<slug>.md"` |
| S3 状态标记 | 输入draft输出frontmatter 完整化 | `write-article` | `obsidian property:set path="02-drafts/2026-03-01-<slug>.md" name="status" value="draft"` |
| S4 格式清洗 | 输入draft输出发布友好 Markdown | `format-markdown` | `obsidian read path="02-drafts/2026-03-01-<slug>.md"` |
| S5 视觉资产生成 | 输入final markdown输出`05-assets/<slug>/...` | `baoyu-cover-image` / `baoyu-article-illustrator` / `baoyu-xhs-images` / `baoyu-infographic` -> `image-gen-orchestrator` -> `basic-image-gen` | `obsidian property:set path="02-drafts/2026-03-01-<slug>.md" name="assets" value='["05-assets/<slug>/cover.png"]'` |
| S6 审核入列 | 输入draft+assets输出`03-review/*.md` | 人工审核 + 必要 skill 回修 | `obsidian move path="02-drafts/2026-03-01-<slug>.md" to="03-review"` |
| S7 发布微信 | 输入review 稿;输出:公众号文章 | `post-to-wechat`(依赖 `baoyu-markdown-to-html` | `obsidian property:set path="03-review/2026-03-01-<slug>.md" name="channels" value='["wechat","x"]'` |
| S8 发布 X | 输入review 稿输出X 帖子 | `post-to-x` | `obsidian property:set path="03-review/2026-03-01-<slug>.md" name="status" value="published"` |
| S9 发布归档 | 输入:发布结果;输出:`04-published/*.md` | 发布后归档流程 | `obsidian move path="03-review/2026-03-01-<slug>.md" to="04-published"` |
### 5.1 常用命令速查
```bash
# 选题
obsidian create path="01-topics/YYYY-MM-DD-<slug>.md" content="<MARKDOWN>"
obsidian read path="01-topics/YYYY-MM-DD-<slug>.md"
# 草稿
obsidian create path="02-drafts/YYYY-MM-DD-<slug>.md" content="<MARKDOWN>"
obsidian property:set path="02-drafts/YYYY-MM-DD-<slug>.md" name="status" value="draft"
# 阶段流转
obsidian move path="02-drafts/YYYY-MM-DD-<slug>.md" to="03-review"
obsidian move path="03-review/YYYY-MM-DD-<slug>.md" to="04-published"
# 查找
obsidian search query="<slug>"
# 资产关联
obsidian property:set path="<path>" name="assets" value='["05-assets/<slug>/cover.png"]'
```
### 5.2 端到端最小验证链路
```bash
obsidian vault
obsidian create path="01-topics/2026-03-01-demo.md" content="# Demo Topic"
obsidian read path="01-topics/2026-03-01-demo.md"
obsidian create path="02-drafts/2026-03-01-demo.md" content="# Demo Draft"
obsidian property:set path="02-drafts/2026-03-01-demo.md" name="status" value="draft"
obsidian search query="2026-03-01-demo"
```
---
## 6) Obsidian CLI 使用规范与故障排查
### 6.1 使用规范(强制)
1. 所有写入必须使用 Vault 相对路径,禁止绝对路径。
2. 写入前先创建文件,再写 frontmatter 属性,避免属性写入到不存在文件。
3. 每次阶段切换都要更新 `status``updated_at`
4. 资产路径统一写入 `assets` 数组,不在正文硬编码临时 URL。
5. 发布前必须能通过 `obsidian search query="<slug>"` 找到 topic/draft/review/published 至少一个节点。
### 6.2 故障排查(按优先级)
| 症状 | 根因 | 诊断命令 | 处理动作 |
|---|---|---|---|
| `obsidian: command not found` | CLI 未安装或 PATH 未注入 | `command -v obsidian` | 按 obsidian-setup skill 安装 |
| `obsidian vault` 无输出或报错 | Vault 未注册 | `obsidian vault` | 运行 `scripts/init-vault.sh` 或手动注册 |
| `obsidian read` 失败 | 路径拼写或文件不存在 | `obsidian search query="<slug>"` | 先 `obsidian create``obsidian read` |
| `obsidian property:set` 不生效 | frontmatter 块损坏或 name 拼写错误 | `obsidian read path="<path>"` | 修复 frontmatter 结构后重试 |
| `obsidian vault` 段错误segfault | CLI 版本与系统不兼容或内存问题 | `obsidian --version` | 重新安装 CLI`npm i -g obsidian-cli` 或 `pip install obsidian-cli`;如持续崩溃,用文件系统直接读写 Vault 作为降级方案 |
| 配图 skill 调用失败 | `basic-image-gen` API key 缺失 | 检查环境变量 | 补齐 `OPENAI_API_KEY/GOOGLE_API_KEY/DASHSCOPE_API_KEY` |
| 微信/X 发布失败 | 登录态失效或接口凭证过期 | 检查浏览器登录态/API 凭证 | 重新登录或刷新凭证 |
### 6.3 运行前检查清单
```bash
command -v obsidian
obsidian vault
obsidian search query="templates"
```
通过后再执行流水线,避免中途失败回滚。
---
## 7) 已知问题与限制
| 问题 | 影响 | 状态 | 降级方案 |
|---|---|---|---|
| `obsidian vault` 执行时 segfault核心已转储 | 无法通过 CLI 操作 Vault | 待修复 | 用文件系统直接读写 `content-forge/` 目录 |
| 所有内容目录为空 | 流水线尚未有端到端产出 | 正常(项目刚初始化) | 按 Quick Start 创建首个选题 |
| `obsidian` 插件 CLI 与 Obsidian 桌面客户端为独立进程 | 桌面端修改不会自动触发 CLI 事件 | 设计如此 | 以文件系统为准CLI 和桌面端都会读取同一目录 |
---
## 版本
- **CLAUDE.md 版本**1.1.0
- **最后更新**2026-03-02
- **项目阶段**:初始化完成,待首次端到端验证

207
scripts/init-vault.sh Executable file
View File

@ -0,0 +1,207 @@
#!/usr/bin/env bash
set -Eeuo pipefail
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)"
VAULT_NAME="${VAULT_NAME:-content-forge}"
VAULT_PATH="${VAULT_PATH:-${PROJECT_ROOT}/content-forge}"
readonly SCRIPT_DIR
readonly PROJECT_ROOT
readonly VAULT_NAME
readonly VAULT_PATH
log() {
printf '[init-vault] %s\n' "$*"
}
die() {
printf '[init-vault] ERROR: %s\n' "$*" >&2
exit 1
}
trap 'die "Command failed at line ${LINENO}: ${BASH_COMMAND}"' ERR
require_obsidian() {
if ! command -v obsidian >/dev/null 2>&1; then
die "Missing required command: obsidian. Run the obsidian-setup skill first."
fi
}
register_vault() {
# Vault registration writes directly to ~/.config/obsidian/obsidian.json.
# The obsidian CLI has no vault-add subcommand; registration is done by
# editing the config file (same approach as obsidian-setup/scripts/register_vault.py).
local config_path="$HOME/.config/obsidian/obsidian.json"
python3 -c "
import json, os, hashlib, time
vault_path = '${VAULT_PATH}'
vault_id = hashlib.md5(vault_path.encode()).hexdigest()[:16]
config_path = os.path.expanduser('${config_path}')
os.makedirs(os.path.join(vault_path, '.obsidian'), exist_ok=True)
if os.path.exists(config_path):
with open(config_path) as f:
config = json.load(f)
else:
os.makedirs(os.path.dirname(config_path), exist_ok=True)
config = {}
config['cli'] = True
config.setdefault('vaults', {})
# Check if already registered
for vid, vdata in config['vaults'].items():
if vdata.get('path') == vault_path:
print(f'Vault already registered: {vault_path} (id: {vid})')
exit(0)
config['vaults'][vault_id] = {'path': vault_path, 'ts': int(time.time() * 1000)}
with open(config_path, 'w') as f:
json.dump(config, f, indent=2)
print(f'Registered vault: {vault_path} (id: {vault_id})')
"
}
create_directories() {
local dirs=(
"00-inbox"
"01-topics"
"02-drafts"
"03-review"
"04-published"
"05-assets"
"templates"
)
local dir
for dir in "${dirs[@]}"; do
mkdir -p "${VAULT_PATH}/${dir}"
done
log "Ensured vault directories exist."
}
create_templates() {
local t="${VAULT_PATH}/templates"
mkdir -p "${t}"
# article template — matches writing-styles.md:tech_blog structure
if [[ ! -f "${t}/article.md" ]]; then
cat >"${t}/article.md" <<'EOF'
---
id: ""
title: ""
slug: ""
status: "topic"
content_type: "article"
channels: []
language: "zh-CN"
source_urls: []
assets: []
cover_image: ""
template: "article"
owner: "content-forge"
created_at: ""
updated_at: ""
published_at: null
---
# 问题背景
# 核心方案
# 实现细节
# 验证结果
# 总结与扩展
EOF
log "Created template: article"
else
log "Template already exists, skip: article"
fi
# tech-blog template — same frontmatter, tech_blog structure
if [[ ! -f "${t}/tech-blog.md" ]]; then
cat >"${t}/tech-blog.md" <<'EOF'
---
id: ""
title: ""
slug: ""
status: "topic"
content_type: "article"
channels: []
language: "zh-CN"
source_urls: []
assets: []
cover_image: ""
template: "tech-blog"
owner: "content-forge"
created_at: ""
updated_at: ""
published_at: null
---
# 问题背景
# 核心方案
# 实现细节
# 验证结果
# 总结与扩展
EOF
log "Created template: tech-blog"
else
log "Template already exists, skip: tech-blog"
fi
# social-post template — matches writing-styles.md:social_short structure
if [[ ! -f "${t}/social-post.md" ]]; then
cat >"${t}/social-post.md" <<'EOF'
---
id: ""
title: ""
slug: ""
status: "topic"
content_type: "x-post"
channels: ["x"]
language: "zh-CN"
source_urls: []
assets: []
cover_image: ""
template: "social-post"
owner: "content-forge"
created_at: ""
updated_at: ""
published_at: null
---
<!-- Hook: 首句提出冲突、反差或问题 -->
<!-- 核心信息: 1-2 个关键观点 -->
<!-- 行动结尾: 提问、CTA 或可转发结论 -->
EOF
log "Created template: social-post"
else
log "Template already exists, skip: social-post"
fi
}
main() {
require_obsidian
mkdir -p "${VAULT_PATH}"
register_vault
create_directories
create_templates
log "Vault initialization complete: ${VAULT_PATH}"
}
main "$@"

15
scripts/vault-sync.sh Executable file
View File

@ -0,0 +1,15 @@
#!/usr/bin/env bash
set -euo pipefail
VAULT_PATH="${VAULT_PATH:-/home/kang/apps/content-forge/content-forge}"
cd "$VAULT_PATH"
# Skip if no changes
if git diff --quiet && git diff --cached --quiet && [ -z "$(git ls-files --others --exclude-standard)" ]; then
exit 0
fi
git add -A
git commit -m "vault: auto-sync $(date '+%Y-%m-%d %H:%M')"
git push origin main