novel-doomsday-resurgence/skills/fanfic-writer/scripts/state_manager.py
唐天洛 cb9b16e5a8 初始提交:番茄小说创作工作区
包含:
- 核心配置文件(AGENTS.md, SOUL.md, USER.md等)
- 记忆系统(memory/文件夹)
- 技能库(skills/文件夹)
- 小说内容(novel/文件夹)
- .gitignore配置
2026-03-30 15:46:26 +08:00

198 lines
6.3 KiB
Python

"""
Novel Writing State Manager
Tracks progress of each book being written
"""
import json
import os
from datetime import datetime
from pathlib import Path
def get_novels_dir():
"""Get the novels working directory"""
# Default Windows path, can be overridden by environment variable
base_path = os.environ.get("NOVELS_DIR", "C:\\Users\\10179\\clawd\\novels")
return Path(base_path)
def get_registry_path():
"""Get the books registry file path"""
return get_novels_dir() / "books-registry.json"
def ensure_workspace():
"""Ensure novels directory exists"""
novels_dir = get_novels_dir()
novels_dir.mkdir(parents=True, exist_ok=True)
return novels_dir
def create_new_book(genre, target_words, book_title=None):
"""Create a new book workspace"""
ensure_workspace()
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
if not book_title:
book_title = f"novel_{timestamp}"
# Sanitize title for folder name
safe_title = "".join(c for c in book_title if c.isalnum() or c in "-_").strip()
book_dir = get_novels_dir() / f"{timestamp}_{safe_title}"
book_dir.mkdir(exist_ok=True)
# Create subdirectories
(book_dir / "chapters").mkdir(exist_ok=True)
(book_dir / "drafts").mkdir(exist_ok=True)
(book_dir / "final").mkdir(exist_ok=True)
# Initialize book config
config = {
"book_id": f"{timestamp}_{safe_title}",
"title": book_title,
"genre": genre,
"target_words": target_words,
"created_at": datetime.now().isoformat(),
"status": "outlining", # outlining, worldbuilding, writing, completed
"current_chapter": 0,
"total_chapters": 0,
"completed_words": 0
}
with open(book_dir / "0-book-config.json", 'w', encoding='utf-8') as f:
json.dump(config, f, indent=2, ensure_ascii=False)
# Update registry
registry = load_registry()
registry[config["book_id"]] = {
"title": book_title,
"path": str(book_dir),
"status": config["status"],
"created_at": config["created_at"]
}
save_registry(registry)
return book_dir, config
def load_registry():
"""Load books registry"""
registry_path = get_registry_path()
if registry_path.exists():
with open(registry_path, 'r', encoding='utf-8') as f:
return json.load(f)
return {}
def save_registry(registry):
"""Save books registry"""
registry_path = get_registry_path()
ensure_workspace()
with open(registry_path, 'w', encoding='utf-8') as f:
json.dump(registry, f, indent=2, ensure_ascii=False)
def get_book_path(book_id_or_title):
"""Find book path by ID or title"""
registry = load_registry()
# Try exact ID match first
if book_id_or_title in registry:
return registry[book_id_or_title]["path"]
# Try title match
for book_id, info in registry.items():
if info["title"] == book_id_or_title:
return info["path"]
# Try partial match
matches = []
for book_id, info in registry.items():
if book_id_or_title.lower() in book_id.lower() or book_id_or_title.lower() in info["title"].lower():
matches.append((book_id, info["path"]))
if len(matches) == 1:
return matches[0][1]
elif len(matches) > 1:
raise ValueError(f"Multiple books match '{book_id_or_title}': {[m[0] for m in matches]}")
raise ValueError(f"Book not found: {book_id_or_title}")
def load_book_config(book_dir):
"""Load book configuration"""
config_path = Path(book_dir) / "0-book-config.json"
with open(config_path, 'r', encoding='utf-8') as f:
return json.load(f)
def save_book_config(book_dir, config):
"""Save book configuration"""
config_path = Path(book_dir) / "0-book-config.json"
with open(config_path, 'w', encoding='utf-8') as f:
json.dump(config, f, indent=2, ensure_ascii=False)
# Also update registry
registry = load_registry()
if config["book_id"] in registry:
registry[config["book_id"]]["status"] = config["status"]
save_registry(registry)
def update_chapter_progress(book_dir, chapter_num):
"""Update current chapter progress"""
config = load_book_config(book_dir)
config["current_chapter"] = chapter_num
config["status"] = "writing"
save_book_config(book_dir, config)
def list_books():
"""List all books in registry"""
registry = load_registry()
return registry
def get_writing_state(book_dir):
"""Load writing state"""
state_path = Path(book_dir) / "4-writing-state.json"
if state_path.exists():
with open(state_path, 'r', encoding='utf-8') as f:
return json.load(f)
return {
"current_chapter": 1,
"chapters_completed": [],
"total_words_written": 0,
"last_modified": datetime.now().isoformat()
}
def save_writing_state(book_dir, state):
"""Save writing state"""
state_path = Path(book_dir) / "4-writing-state.json"
state["last_modified"] = datetime.now().isoformat()
with open(state_path, 'w', encoding='utf-8') as f:
json.dump(state, f, indent=2, ensure_ascii=False)
if __name__ == "__main__":
import sys
if len(sys.argv) < 2:
print("Usage: state_manager.py <command> [args]")
print("Commands:")
print(" create <genre> <target_words> [title] - Create new book")
print(" list - List all books")
print(" path <book_id_or_title> - Get book path")
sys.exit(1)
cmd = sys.argv[1]
if cmd == "create":
if len(sys.argv) < 4:
print("Usage: state_manager.py create <genre> <target_words> [title]")
sys.exit(1)
genre = sys.argv[2]
target_words = int(sys.argv[3])
title = sys.argv[4] if len(sys.argv) > 4 else None
book_dir, config = create_new_book(genre, target_words, title)
print(f"Created book: {config['book_id']}")
print(f"Path: {book_dir}")
elif cmd == "list":
books = list_books()
for book_id, info in books.items():
print(f"{book_id}: {info['title']} ({info['status']})")
elif cmd == "path":
if len(sys.argv) < 3:
print("Usage: state_manager.py path <book_id_or_title>")
sys.exit(1)
path = get_book_path(sys.argv[2])
print(path)