From c3209dfa680383b9ab43415b9a370ab15003f985 Mon Sep 17 00:00:00 2001 From: lizikk Date: Mon, 2 Mar 2026 11:20:38 +0800 Subject: [PATCH] init: content-forge project with write-article skill, scripts, and CLAUDE.md --- .claude/skills/write-article/SKILL.md | 126 ++++++++ .../references/writing-styles.md | 82 +++++ .gitignore | 2 + CLAUDE.md | 292 ++++++++++++++++++ scripts/init-vault.sh | 207 +++++++++++++ scripts/vault-sync.sh | 15 + 6 files changed, 724 insertions(+) create mode 100644 .claude/skills/write-article/SKILL.md create mode 100644 .claude/skills/write-article/references/writing-styles.md create mode 100644 .gitignore create mode 100644 CLAUDE.md create mode 100755 scripts/init-vault.sh create mode 100755 scripts/vault-sync.sh diff --git a/.claude/skills/write-article/SKILL.md b/.claude/skills/write-article/SKILL.md new file mode 100644 index 0000000..8cf4d2f --- /dev/null +++ b/.claude/skills/write-article/SKILL.md @@ -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-Article:Obsidian 文章写作技能 + +## 目标 + +用统一流程产出可发布草稿,避免无来源、无结构、无 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="" +obsidian read path="" +``` + +执行要求: +- `source_notes` 中每个路径都要读取。 +- 未读完素材前,禁止进入写作。 +- 素材不足以支撑结论时,输出 `[?]` 标记并向用户索要补充信息。 + +### Step 2:生成文章草稿 + +按 `references/writing-styles.md` 选择对应风格模板生成正文。 + +执行要求: +- 只使用已读取素材中的事实。 +- 观点必须有论证链,不得堆砌口号。 +- 内容结构必须匹配所选风格的"结构"要求。 + +### Step 3:创建草稿文件 + +将生成的 Markdown 写入 `02-drafts/` 目录。 + +```bash +obsidian create path="02-drafts/-.md" content="" +``` + +执行要求: +- 目标目录必须是 `02-drafts/`。 +- 文件名必须包含日期前缀与 slug。 +- 正文必须是 Markdown,可直接发布或二次编辑。 + +### Step 4:设置 frontmatter + +对 Step 3 产物逐项设置元数据。 + +```bash +obsidian property:set path="02-drafts/-.md" name="title" value="" +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` diff --git a/.claude/skills/write-article/references/writing-styles.md b/.claude/skills/write-article/references/writing-styles.md new file mode 100644 index 0000000..96f259e --- /dev/null +++ b/.claude/skills/write-article/references/writing-styles.md @@ -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` diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8aadd37 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# Vault has its own git repo +content-forge/ diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..e00fc66 --- /dev/null +++ b/CLAUDE.md @@ -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 API(API 模式) | 公众号发布 | `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 +- **项目阶段**:初始化完成,待首次端到端验证 diff --git a/scripts/init-vault.sh b/scripts/init-vault.sh new file mode 100755 index 0000000..aaa2219 --- /dev/null +++ b/scripts/init-vault.sh @@ -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 "$@" diff --git a/scripts/vault-sync.sh b/scripts/vault-sync.sh new file mode 100755 index 0000000..e5ed76d --- /dev/null +++ b/scripts/vault-sync.sh @@ -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