diff --git a/README.md b/README.md
index ff256b8..7f502f9 100644
--- a/README.md
+++ b/README.md
@@ -5,16 +5,16 @@
Welcome to [**StoryCraftr**](https://storycraftr.app), the open-source project designed to revolutionize how books are written. With the power of AI and a streamlined command-line interface (CLI), StoryCraftr helps you craft your story, manage worldbuilding, structure your book, and generate chapters — all while keeping you in full control.
-## Release Notes v0.4.0
+## Release Notes v0.5.0-alpha1
-You can find the release notes for version `v0.4.0` [here](https://github.com/raestrada/storycraftr/releases/tag/v0.4.0).
+You can find the release notes for version `v0.5.0-alpha1` [here](https://github.com/raestrada/storycraftr/releases/tag/v0.5.0-alpha1).
## Installation
You can install the current version of **StoryCraftr** via `pipx` using the following command:
```bash
-pipx install git+https://github.com/raestrada/storycraftr.git@v0.4.0
+pipx install git+https://github.com/raestrada/storycraftr.git@v0.5.0-alpha1
```
### Important: Before using StoryCraftr, make sure to set your OpenAI API key:
diff --git a/docs/getting_started.md b/docs/getting_started.md
index 9b6109b..4693d16 100644
--- a/docs/getting_started.md
+++ b/docs/getting_started.md
@@ -11,7 +11,7 @@ First, install **StoryCraftr** using [pipx](https://pypa.github.io/pipx/), a too
To install **StoryCraftr**, run the following command:
```bash
-pipx install git+https://github.com/raestrada/storycraftr.git@v0.4.0
+pipx install git+https://github.com/raestrada/storycraftr.git@v0.5.0-alpha1
```
### Important: Before running the `storycraftr` command
diff --git a/docs/index.html b/docs/index.html
index eda0ce9..5fc4026 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -181,7 +181,7 @@
@@ -213,9 +213,9 @@ Welcome to StoryCraftr 📚✨
- 🚧 Coming Soon - StoryCraftr is in development. Stay tuned for the Alpha release! 🚀
- Install the current version:
- pipx install git+https://github.com/raestrada/storycraftr.git@v0.4.0
+ 🚧 Coming Soon - Beta Release! 🚀
+ Meanwhile, you can download the Alpha version, which is fully usable but may still have bugs:
+ pipx install git+https://github.com/raestrada/storycraftr.git@v0.5.0-alpha1
diff --git a/docs/iterate.md b/docs/iterate.md
index 208c11d..b8aa98e 100644
--- a/docs/iterate.md
+++ b/docs/iterate.md
@@ -21,7 +21,7 @@ storycraftr iterate check-names "Check character names for consistency."
To update or fix a character name across the entire book:
```bash
-storycraftr iterate fix-name Santi Santiago
+storycraftr iterate fix-name Zevid Rhaedin
```
### 2. Refine Character Motivation
@@ -29,7 +29,7 @@ storycraftr iterate fix-name Santi Santiago
Characters need strong motivations, and sometimes these evolve as you write. This command will **refine character motivations** throughout the book, ensuring consistency and depth.
```bash
-storycraftr iterate refine-motivation "Refine the motivations of 'Elena' in a story about rebellion against gods."
+storycraftr iterate refine-motivation "Rahedin ""Refine its motivations in a story about rebellion against gods."
```
### 3. Strengthen Story Argument
@@ -45,7 +45,7 @@ storycraftr iterate strengthen-argument "Ensure the argument of rebellion agains
Sometimes, you may need to insert a chapter between two existing ones. This command will **insert a new chapter** and automatically **adjust the numbering** of all subsequent chapters.
```bash
-storycraftr iterate insert-chapter 5 "just extend the idea in 3 chapters instead of 2"
+storycraftr iterate insert-chapter 2 "Insert a new chapter that explores a critical event from the protagonist’s past, shedding light on their true intentions and setting the stage for the conflict in chapter 1."
```
### 5. Split a Chapter
@@ -53,7 +53,7 @@ storycraftr iterate insert-chapter 5 "just extend the idea in 3 chapters instead
If a chapter becomes too long or covers too many topics, you can **split it into two separate chapters**. This command will split the specified chapter and adjust the numbering of all subsequent chapters.
```bash
-storycraftr iterate split-chapter "Split chapter 5 into two chapters." 5
+storycraftr 5 iterate split-chapter "Split chapter 5 into two chapters."
```
### 6. Add Flashbacks
@@ -61,7 +61,7 @@ storycraftr iterate split-chapter "Split chapter 5 into two chapters." 5
Flashbacks are a great way to add depth to a character’s backstory. This command lets you **add a flashback scene** between two chapters, ensuring it integrates seamlessly with the surrounding narrative.
```bash
-storycraftr iterate add-flashback "Add a flashback between chapters 6 and 7." 6
+storycraftr iterate add-flashback 3 "Insert a flashback revealing a hidden alliance the protagonist formed years ago, explaining a key turning point in the current events."
```
### 7. Update Plot Points
diff --git a/storycraftr/agent/iterate.py b/storycraftr/agent/iterate.py
index 8252ad2..1a9b02b 100644
--- a/storycraftr/agent/iterate.py
+++ b/storycraftr/agent/iterate.py
@@ -10,6 +10,7 @@
REWRITE_SURROUNDING_CHAPTERS_PROMPT,
INSERT_FLASHBACK_CHAPTER_PROMPT,
REWRITE_SURROUNDING_CHAPTERS_FOR_FLASHBACK_PROMPT,
+ CHECK_CHAPTER_CONSISTENCY_PROMPT,
)
from storycraftr.agent.agents import (
update_agent_files,
@@ -22,7 +23,9 @@
console = Console()
-def check_character_names_consistency(book_path, chapter_path, progress, task_id):
+def check_character_names_consistency(
+ book_path, chapter_path, progress, task_id, assistant
+):
"""
Checks for character name consistency in a chapter file.
Uses an OpenAI assistant to perform the review.
@@ -32,7 +35,6 @@ def check_character_names_consistency(book_path, chapter_path, progress, task_id
prompt = CHECK_NAMES_PROMPT
# Get or create the assistant and the thread
- assistant = create_or_get_assistant(book_path, progress, task_id)
thread = get_thread()
# Create the message with the thread_id and assistant
@@ -84,6 +86,7 @@ def iterate_check_names(book_path):
return corrections
# Create a rich progress bar
+ assistant = create_or_get_assistant(book_path)
with Progress() as progress:
# Task to process chapters
task_chapters = progress.add_task(
@@ -100,7 +103,7 @@ def iterate_check_names(book_path):
# Call the function to check name consistency in the chapter
corrections[chapter_file] = check_character_names_consistency(
- book_path, chapter_path, progress, task_openai
+ book_path, chapter_path, progress, task_openai, assistant=assistant
)
# Save to markdown
@@ -188,9 +191,11 @@ def fix_name_in_chapters(book_path, original_name, new_name):
return
-def refine_character_motivation(book_path, character_name, story_context):
+def process_chapters(
+ book_path, prompt_template, task_description, file_suffix, **prompt_kwargs
+):
"""
- Function to refine character motivations across all chapters in a book.
+ Generic function to process chapters in a book with a given prompt template.
"""
chapters_dir = os.path.join(book_path, "chapters")
@@ -209,25 +214,20 @@ def refine_character_motivation(book_path, character_name, story_context):
# Create progress bar
with Progress() as progress:
task_chapters = progress.add_task(
- "[cyan]Refining character motivations...", total=len(files_to_process)
+ f"[cyan]{task_description}", total=len(files_to_process)
)
- # Task for calling OpenAI for each chapter
task_openai = progress.add_task("[green]Calling OpenAI...", total=1)
- # Iterate over each chapter file
for chapter_file in files_to_process:
chapter_path = os.path.join(chapters_dir, chapter_file)
- # Create the prompt using the defined format
- prompt = REFINE_MOTIVATION_PROMPT.format(
- character_name=character_name, story_context=story_context
- )
+ # Create the prompt using the provided template and kwargs
+ prompt = prompt_template.format(**prompt_kwargs)
# Get the assistant and thread
assistant = create_or_get_assistant(book_path)
thread = get_thread()
- # Send the message to refine the character's motivations
progress.reset(task_openai)
refined_text = create_message(
book_path,
@@ -243,84 +243,56 @@ def refine_character_motivation(book_path, character_name, story_context):
save_to_markdown(
book_path,
os.path.join("chapters", chapter_file),
- "Character Motivation Refinement",
+ file_suffix,
refined_text,
progress=progress,
task=task_chapters,
)
- # Advance the progress bar
progress.update(task_chapters, advance=1)
update_agent_files(book_path, assistant)
return
-def strengthen_core_argument(book_path, argument):
+def refine_character_motivation(book_path, character_name, story_context):
"""
- Function to strengthen the core argument across all chapters in a book.
+ Function to refine character motivations across all chapters in a book.
"""
- chapters_dir = os.path.join(book_path, "chapters")
-
- if not os.path.exists(chapters_dir):
- raise FileNotFoundError(
- f"The chapter directory '{chapters_dir}' does not exist."
- )
-
- files_to_process = [f for f in os.listdir(chapters_dir) if f.endswith(".md")]
-
- if not files_to_process:
- raise FileNotFoundError(
- "No Markdown (.md) files were found in the chapter directory."
- )
-
- # Create progress bar
- with Progress() as progress:
- task_chapters = progress.add_task(
- "[cyan]Strengthening core argument across chapters...",
- total=len(files_to_process),
- )
- # Task for calling OpenAI for each chapter
- task_openai = progress.add_task("[green]Calling OpenAI...", total=1)
-
- # Iterate over each chapter file
- for chapter_file in files_to_process:
- chapter_path = os.path.join(chapters_dir, chapter_file)
-
- # Create the prompt using the defined format
- prompt = STRENGTHEN_ARGUMENT_PROMPT.format(argument=argument)
-
- # Get the assistant and thread
- assistant = create_or_get_assistant(book_path)
- thread = get_thread()
+ process_chapters(
+ book_path,
+ prompt_template=REFINE_MOTIVATION_PROMPT,
+ task_description="Refining character motivations...",
+ file_suffix="Character Motivation Refinement",
+ character_name=character_name,
+ story_context=story_context,
+ )
- progress.reset(task_openai)
- # Send the message to refine the argument in the chapter
- refined_text = create_message(
- book_path,
- thread_id=thread.id,
- content=prompt,
- assistant=assistant,
- progress=progress,
- task_id=task_openai,
- file_path=chapter_path,
- )
- # Save the refined chapter
- save_to_markdown(
- book_path,
- os.path.join("chapters", chapter_file),
- "Core Argument Strengthening",
- refined_text,
- progress=progress,
- task=task_chapters,
- )
+def strengthen_core_argument(book_path, argument):
+ """
+ Function to strengthen the core argument across all chapters in a book.
+ """
+ process_chapters(
+ book_path,
+ prompt_template=STRENGTHEN_ARGUMENT_PROMPT,
+ task_description="Strengthening core argument across chapters...",
+ file_suffix="Core Argument Strengthening",
+ argument=argument,
+ )
- # Advance the progress bar
- progress.update(task_chapters, advance=1)
- update_agent_files(book_path, assistant)
- return
+def check_consistency_across(book_path, consistency_type):
+ """
+ Function to check the consistency of chapters in a book.
+ """
+ process_chapters(
+ book_path,
+ prompt_template=CHECK_CHAPTER_CONSISTENCY_PROMPT,
+ task_description=f"Checking {consistency_type} consistency across chapters...",
+ file_suffix=f"{consistency_type} Consistency Check",
+ consistency_type=consistency_type,
+ )
def insert_new_chapter(book_path, position, prompt, flashback=False):
diff --git a/storycraftr/cli.py b/storycraftr/cli.py
index e14e38a..36e5e98 100644
--- a/storycraftr/cli.py
+++ b/storycraftr/cli.py
@@ -2,16 +2,6 @@
import os
import json
from rich.console import Console
-import storycraftr.templates.folder
-from storycraftr.state import debug_state
-from storycraftr.cmd.worldbuilding import worldbuilding
-from storycraftr.cmd.outline import outline
-from storycraftr.cmd.chapters import chapters
-from storycraftr.cmd.iterate import iterate
-from storycraftr.cmd.publish import publish
-from storycraftr.cmd.chat import chat
-from storycraftr.templates.tex import TEMPLATE_TEX
-
console = Console()
@@ -33,9 +23,18 @@ def load_openai_api_key():
console.print(f"[red]The file {api_key_file} does not exist.[/red]")
-# Run the function
load_openai_api_key()
+import storycraftr.templates.folder
+from storycraftr.state import debug_state
+from storycraftr.cmd.worldbuilding import worldbuilding
+from storycraftr.cmd.outline import outline
+from storycraftr.cmd.chapters import chapters
+from storycraftr.cmd.iterate import iterate
+from storycraftr.cmd.publish import publish
+from storycraftr.cmd.chat import chat
+from storycraftr.templates.tex import TEMPLATE_TEX
+
def verify_book_path(book_path=None):
"""Verify if the book path is valid and contains storycraftr.json."""
@@ -254,4 +253,5 @@ def init(
cli.add_command(chat)
if __name__ == "__main__":
+ # Run the function
cli()
diff --git a/storycraftr/cmd/iterate.py b/storycraftr/cmd/iterate.py
index bf5fce2..07950b3 100644
--- a/storycraftr/cmd/iterate.py
+++ b/storycraftr/cmd/iterate.py
@@ -8,6 +8,7 @@
refine_character_motivation,
strengthen_core_argument,
insert_new_chapter,
+ check_consistency_across,
)
console = Console()
@@ -228,22 +229,32 @@ def update_plot_points(prompt, book_path):
@iterate.command()
-@click.option("--book-path", type=click.Path(), help="Path to the book directory")
+@click.option(
+ "--book-path", type=click.Path(), help="Path to the book directory", required=False
+)
@click.argument("prompt")
def check_consistency(prompt, book_path):
- """Check for consistency across all chapters and elements of the book."""
+ """
+ Command to check the overall consistency of chapters in the book.
+ Takes an optional custom prompt as an argument.
+ """
if not book_path:
book_path = os.getcwd()
if not load_book_config(book_path):
return None
- # Placeholder for future retrieval-based consistency check
console.print(
- f"[yellow]The command 'check-consistency' is not yet implemented.[/yellow]"
+ f"[bold blue]Starting to check overall consistency in the book: {book_path}[/bold blue]"
+ )
+
+ # Call the function to check the consistency across chapters
+ check_consistency_across(book_path, prompt)
+
+ # Success log
+ console.print(
+ f"[green bold]Overall consistency check completed across all chapters![/green bold]"
)
- console.print(f"Prompt: {prompt}")
- # Future implementation would involve checking character arcs, plot points, worldbuilding, etc., using retrieval.
if __name__ == "__main__":
diff --git a/storycraftr/prompts/iterate.py b/storycraftr/prompts/iterate.py
index 377f9c1..20dfe4b 100644
--- a/storycraftr/prompts/iterate.py
+++ b/storycraftr/prompts/iterate.py
@@ -58,3 +58,10 @@
Generate content only for the requested chapter, without altering or including content from other chapters.
Use this prompt for context: {prompt}.
"""
+
+CHECK_CHAPTER_CONSISTENCY_PROMPT = """
+Check the consistency of chapter with the entire book, ensuring that the events, tone, and character developments align with the overall narrative.
+Use the retrieval system to access all relevant information from the entire book to maintain coherence in plot, character arcs, and themes.
+Execute this check chapter by chapter, making sure each chapter fits seamlessly into the full story, and that there are no inconsistencies with the previous and subsequent chapters.
+Ensure that the writing style and tone remain consistent throughout.
+"""
diff --git a/storycraftr/prompts/permute.py b/storycraftr/prompts/permute.py
new file mode 100644
index 0000000..f70234e
--- /dev/null
+++ b/storycraftr/prompts/permute.py
@@ -0,0 +1,51 @@
+longer_date_formats = [
+ "Prompt generated on the date: {date}, during this particular session for refinement purposes.",
+ "As of {date}, this session was conducted for the generation of this prompt, without any additional context.",
+ "Generated on {date}, solely to capture this moment in the context of ongoing refinements and evaluations.",
+ "The exact timestamp for this iteration, noted as {date}, was recorded without further elaboration or context.",
+ "{date} is the formalized date for this session, where refinements were made in accordance with the ongoing process.",
+ "On {date}, a prompt was generated as part of this continuous refinement effort, nothing beyond this was noted.",
+ "This prompt was officially generated on {date}, simply serving as a marker for the session currently in progress.",
+ "During the {date} session, this prompt was created, although no significant details were added at this juncture.",
+ "The ongoing session as of {date} produced this prompt, which aligns with our current iterative process.",
+ "This current iteration was generated on {date}, with no additional content or changes beyond the expected scope.",
+ "For the session conducted on {date}, this prompt serves as a formalized iteration without further context.",
+ "{date} is marked as the day for this refinement session, created for no particular reason other than the iteration.",
+ "Generated on {date}, this prompt serves as a simple marker for the ongoing refinement process.",
+ "In this session, noted on {date}, the following prompt was generated, providing no additional details beyond this.",
+ "The date {date} marks the time of generation for this prompt, created as part of the regular refinement loop.",
+ "This session, taking place on {date}, generated this iteration, with no additional or unique changes noted.",
+ "On {date}, a new iteration was created, with no specific context provided for the refinement process.",
+ "For the purposes of documentation, this prompt was generated on {date}, marking an iteration step without extra detail.",
+ "The refinement process recorded a new iteration on {date}, but without any further clarifying context.",
+ "As of {date}, this prompt was generated, fulfilling the requirements of this iterative session.",
+ "{date} was the day when this session took place, leading to the generation of this specific iteration.",
+ "For the session dated {date}, this iteration was generated as part of a continuous refinement loop.",
+ "A prompt was generated on {date}, marking an iteration within the current refinement cycle without further context.",
+ "The following iteration was created on {date}, with no additional markers or contextual details noted.",
+ "During this session, which took place on {date}, a new prompt was created to continue the refinement process.",
+ "On the date {date}, a new iteration was recorded as part of this continuous process.",
+ "{date} marks the creation of this prompt, solely for the purposes of recording within the refinement process.",
+ "A new iteration was generated on {date}, serving as a formal marker for the ongoing refinement loop.",
+ "For the session held on {date}, this prompt was generated, with no unique context or additional information.",
+ "As of {date}, the following iteration was created as part of the refinement session.",
+ "The session on {date} resulted in the creation of this prompt, without any further context or details.",
+ "This specific session, recorded on {date}, generated a new iteration of this prompt.",
+ "During the {date} session, this prompt was created to fulfill the requirements of the refinement process.",
+ "On {date}, this iteration was recorded, without any additional clarifications or unique content added.",
+ "A new prompt was generated on {date}, within the bounds of the ongoing refinement process.",
+ "{date} marks the generation of this iteration, recorded solely as part of the session taking place.",
+ "The refinement process continued with a new iteration created on {date}, as part of this session.",
+ "In this session dated {date}, a prompt was generated without further modifications or additional context.",
+ "For the session on {date}, this prompt was generated as part of the ongoing process of refinement.",
+ "{date} marks the time of generation for this iteration, created within the normal refinement process.",
+ "This session, dated {date}, generated a new iteration, without any further explanation or content.",
+ "A new iteration was generated as part of the session on {date}, marking this moment in the process.",
+ "{date} marks the creation of this prompt, without any additional content or significant markers added.",
+ "On {date}, this prompt was generated within the standard refinement process, without further details.",
+ "A new prompt was generated during the session on {date}, fulfilling the regular iteration requirement.",
+ "{date} marks the recording of this session, which produced a new iteration as part of the refinement process.",
+ "This prompt was generated during a session on {date}, with no further clarifications or context.",
+ "{date} was the formal timestamp for this prompt’s generation, created within the regular refinement loop.",
+ "For the session on {date}, this prompt was generated, marking an iteration step in the process.",
+]
diff --git a/storycraftr/state.py b/storycraftr/state.py
new file mode 100644
index 0000000..40564f2
--- /dev/null
+++ b/storycraftr/state.py
@@ -0,0 +1,16 @@
+# debug_state.py
+
+
+class DebugState:
+ def __init__(self):
+ self.debug = False
+
+ def set_debug(self, value: bool):
+ self.debug = value
+
+ def is_debug(self) -> bool:
+ return self.debug
+
+
+# Singleton para almacenar el estado global
+debug_state = DebugState()