261 lines
5.1 KiB
Bash
Executable File
261 lines
5.1 KiB
Bash
Executable File
#!/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"
|
|
|
|
# Use environment variables to avoid command injection
|
|
export VAULT_PATH CONFIG_PATH="$config_path"
|
|
|
|
python3 -c "
|
|
import json, os, hashlib, time
|
|
|
|
vault_path = os.environ['VAULT_PATH']
|
|
vault_id = hashlib.md5(vault_path.encode()).hexdigest()[:16]
|
|
config_path = os.environ['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"
|
|
"06-archived"
|
|
"07-leads"
|
|
"08-events"
|
|
"09-viral-examples"
|
|
"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
|
|
|
|
# viral-example template — for 09-viral-examples/ collection
|
|
if [[ ! -f "${t}/viral-example.md" ]]; then
|
|
cat >"${t}/viral-example.md" <<'EOF'
|
|
---
|
|
id: ""
|
|
title: ""
|
|
source_url: ""
|
|
platform: "" # x | wechat | xiaohongshu | douyin | bilibili | medium | other
|
|
author: ""
|
|
author_url: ""
|
|
published_at: ""
|
|
collected_at: ""
|
|
metrics:
|
|
likes: null
|
|
comments: null
|
|
shares: null
|
|
views: null
|
|
viral_score: null
|
|
tags: []
|
|
language: "zh-CN"
|
|
related_topics: []
|
|
analysis: null
|
|
takeaways: []
|
|
---
|
|
|
|
# 原文(或摘要)
|
|
|
|
[原文内容或核心观点摘要]
|
|
|
|
# 为什么火
|
|
|
|
[你的分析:时机、情绪、表达、平台特性等]
|
|
|
|
# 可复用点
|
|
|
|
-
|
|
|
|
# 关联选题
|
|
|
|
- [[01-topics/xxx|相关选题]]
|
|
EOF
|
|
log "Created template: viral-example"
|
|
else
|
|
log "Template already exists, skip: viral-example"
|
|
fi
|
|
}
|
|
|
|
main() {
|
|
require_obsidian
|
|
mkdir -p "${VAULT_PATH}"
|
|
register_vault
|
|
create_directories
|
|
create_templates
|
|
log "Vault initialization complete: ${VAULT_PATH}"
|
|
}
|
|
|
|
main "$@"
|