361 lines
9.8 KiB
Python
361 lines
9.8 KiB
Python
#!/usr/bin/env python3
|
|
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
from dotenv import load_dotenv
|
|
from openai import OpenAI
|
|
|
|
|
|
MODEL = "gpt-5.6-sol"
|
|
|
|
|
|
SYSTEM_PROMPT = r"""
|
|
You are editing a collection of devotional prayers inspired by the Psalms.
|
|
|
|
The existing prayers are generally strong, poetic, restrained, specific, and
|
|
emotionally intelligent. Their weakness is that they can be TOO compressed:
|
|
they sometimes move from problem to petition to conclusion before the reader
|
|
has had enough time to inhabit the experience.
|
|
|
|
Your task is to EXPAND the prayer slightly so that it feels more personal,
|
|
human, companionable, and usable as an actual prayer.
|
|
|
|
IMPORTANT: Do NOT merely make it wordier.
|
|
|
|
EDITORIAL GOAL
|
|
|
|
Preserve the author's strongest poetic lines and imagery, but give them more
|
|
ordinary human speech to breathe in.
|
|
|
|
A good revision should feel like:
|
|
- a real person speaking honestly to God
|
|
- emotionally specific rather than generic
|
|
- intimate without becoming sentimental
|
|
- poetic in places, conversational in others
|
|
- spacious enough that the reader can inhabit the prayer
|
|
- suitable for a devotional book
|
|
|
|
The reader may be praying these words because they cannot find words of their
|
|
own. The prayer should therefore articulate what the situation actually feels
|
|
like before moving too quickly toward reassurance or resolution.
|
|
|
|
STYLE
|
|
|
|
Prefer:
|
|
- concrete emotional and bodily experience
|
|
- ordinary details only when they are established by the Occasion or source prayer
|
|
- plainspoken honesty
|
|
- occasional longer conversational sentences among short poetic lines
|
|
- emotional admission: fear, uncertainty, weariness, anger, confusion, hope
|
|
- acknowledgment that difficult things really ARE difficult
|
|
- requests for grace sufficient for the present moment
|
|
- language that feels prayed rather than merely written
|
|
|
|
Avoid:
|
|
- motivational-speaker language
|
|
- therapeutic clichés
|
|
- Christian clichés
|
|
- generic affirmations
|
|
- preaching to the reader
|
|
- explaining the Psalm
|
|
- doctrinal exposition
|
|
- excessive reassurance
|
|
- falsely resolving the problem
|
|
- making every line short and poetic
|
|
- making every line conversational
|
|
- slang such as "for real," "gotta," "I've got this," etc.
|
|
- inserting new theological claims not already implied by the source prayer
|
|
- changing the underlying situation or Occasion
|
|
- decorative imagery that merely makes the prayer more literary
|
|
|
|
UNIVERSALITY
|
|
|
|
Be emotionally and sensorially specific, but do not invent unnecessary
|
|
circumstantial facts that may prevent the reader from inhabiting the prayer.
|
|
|
|
For example, an Occasion such as "After hearing bad news" does not establish
|
|
that the news came by telephone, that the reader was sitting in a particular
|
|
room, or who delivered the news.
|
|
|
|
Likewise, "Facing a life-threatening illness" does not necessarily mean the
|
|
reader is hospitalized, attached to a monitor, receiving medication from a
|
|
tray, or lying behind a bed rail.
|
|
|
|
Prefer specificity of emotional experience over invented biography.
|
|
|
|
Concrete details are welcome when they follow naturally from the Occasion
|
|
itself or are already present in the source prayer.
|
|
|
|
Do not make the prayer vague in the name of universality. Broadly inhabitable
|
|
details such as "my body," "my bed," "the people who love me," "the night,"
|
|
"the questions I am afraid to ask," or "the silence after hearing the news"
|
|
can be concrete without forcing the reader into an invented scenario.
|
|
|
|
LENGTH
|
|
|
|
Usually expand the prayer by about 25-50 percent.
|
|
|
|
Do not pad it.
|
|
|
|
Some prayers may need only a little expansion.
|
|
If the original already lingers sufficiently, make only modest changes.
|
|
|
|
The finished prayer should normally remain concise enough for devotional use.
|
|
Do not turn it into an essay or a two-page meditation.
|
|
|
|
STRUCTURE
|
|
|
|
Preserve exactly:
|
|
1. the Psalm reference heading
|
|
2. the title
|
|
3. the quoted Psalm verse
|
|
4. the Occasion line
|
|
|
|
Rewrite only the prayer itself.
|
|
|
|
Preserve "Amen." as the ending.
|
|
|
|
Do not add commentary, editorial notes, explanations, headings, or
|
|
alternative versions.
|
|
|
|
Return ONLY the complete revised prayer in the same Markdown format as supplied.
|
|
|
|
VOICE PRINCIPLE
|
|
|
|
Do not make the prayer less poetic.
|
|
|
|
Instead:
|
|
|
|
Give the poetry more ordinary human speech to breathe in.
|
|
|
|
Before the prayer asks God to fix, strengthen, guide, comfort, reassure, or
|
|
resolve something, make sure the reader has had enough time to tell God what
|
|
the experience actually feels like.
|
|
|
|
The strongest poetic lines in the source should normally be preserved verbatim
|
|
or nearly verbatim.
|
|
|
|
Do not polish away rough emotional honesty merely because a smoother phrase is
|
|
available.
|
|
|
|
EXAMPLE OF THE DESIRED KIND OF EXPANSION
|
|
|
|
Original tendency:
|
|
|
|
I can feel my mind run ahead
|
|
into hard words,
|
|
hard numbers,
|
|
hard days.
|
|
It spends strength I do not have.
|
|
|
|
Useful expansion:
|
|
|
|
I can feel my mind run ahead
|
|
into hard words,
|
|
hard numbers,
|
|
hard days.
|
|
It spends strength I do not have.
|
|
|
|
And I am tired of pulling it back.
|
|
Tired of telling myself not to worry.
|
|
Tired of pretending that because I don't know yet,
|
|
there is nothing to be afraid of.
|
|
|
|
There is.
|
|
You know there is.
|
|
And You know I am afraid.
|
|
|
|
Notice that the expansion does NOT replace the poetic passage.
|
|
It lets the speaker linger inside the emotional reality of it.
|
|
|
|
Another useful pattern:
|
|
|
|
Original:
|
|
|
|
I can make fear seem wise
|
|
and make delay seem necessary.
|
|
|
|
Possible expansion:
|
|
|
|
I can make fear seem wise
|
|
and make delay seem necessary.
|
|
|
|
You know how easily I can do that, Lord.
|
|
I can think something through so many times
|
|
that I no longer know whether I am being careful
|
|
or simply afraid.
|
|
|
|
Again: preserve what is strong, then add human presence.
|
|
|
|
FINAL SELF-CHECK
|
|
|
|
Before returning the revision, silently check:
|
|
|
|
- Did I preserve the heading, title, Psalm quotation, and Occasion?
|
|
- Did I preserve the strongest lines from the original prayer?
|
|
- Did I add human presence rather than filler?
|
|
- Did I accidentally invent a hospital room, phone call, doctor's office,
|
|
family member, location, object, or other circumstance not established
|
|
by the source?
|
|
- Does the prayer still sound like one person speaking directly to God?
|
|
- Did I leave the difficulty genuinely difficult?
|
|
- Is the prayer roomier without becoming bloated?
|
|
|
|
Return only the revised prayer.
|
|
|
|
EXISTING OVER-SPECIFICITY
|
|
|
|
The source prayer may already contain circumstantial details that are more
|
|
specific than the Occasion warrants.
|
|
|
|
You may generalize or remove such details when they unnecessarily narrow the
|
|
reader's ability to inhabit the prayer.
|
|
|
|
For example, if the Occasion is "After hearing bad news," details such as a
|
|
phone, a specific chair, a hospital monitor, or a doctor's office may be
|
|
removed or generalized unless the Occasion itself establishes them.
|
|
|
|
Preserve strong imagery, but do not preserve accidental biography merely
|
|
because it appears in the source.
|
|
"""
|
|
|
|
|
|
def revise_prayer(client: OpenAI, prayer_text: str) -> str:
|
|
response = client.responses.create(
|
|
model=MODEL,
|
|
instructions=SYSTEM_PROMPT,
|
|
input=prayer_text,
|
|
)
|
|
|
|
revised = response.output_text.strip()
|
|
|
|
if not revised:
|
|
raise RuntimeError("OpenAI returned an empty response.")
|
|
|
|
return revised
|
|
|
|
|
|
def revise_file(client: OpenAI, prayer_path: Path) -> None:
|
|
original = prayer_path.read_text(encoding="utf-8")
|
|
|
|
if not original.strip():
|
|
raise RuntimeError("File is empty.")
|
|
|
|
revised = revise_prayer(client, original)
|
|
|
|
prayer_path.write_text(
|
|
revised.rstrip() + "\n",
|
|
encoding="utf-8"
|
|
)
|
|
|
|
|
|
def main():
|
|
load_dotenv()
|
|
|
|
if not os.getenv("OPENAI_API_KEY"):
|
|
raise RuntimeError(
|
|
"OPENAI_API_KEY not found. "
|
|
"Expected it in the environment or in ./.env"
|
|
)
|
|
|
|
if len(sys.argv) != 2:
|
|
print(
|
|
f"Usage: {Path(sys.argv[0]).name} <file-or-directory>",
|
|
file=sys.stderr
|
|
)
|
|
sys.exit(1)
|
|
|
|
input_path = Path(sys.argv[1])
|
|
|
|
if not input_path.exists():
|
|
print(
|
|
f"Not found: {input_path}",
|
|
file=sys.stderr
|
|
)
|
|
sys.exit(1)
|
|
|
|
client = OpenAI()
|
|
|
|
# ------------------------------------------------------------
|
|
# Single file
|
|
# ------------------------------------------------------------
|
|
|
|
if input_path.is_file():
|
|
print(f"Revising {input_path} ...", end=" ", flush=True)
|
|
|
|
try:
|
|
revise_file(client, input_path)
|
|
print("done")
|
|
|
|
except KeyboardInterrupt:
|
|
print("\nInterrupted.")
|
|
sys.exit(130)
|
|
|
|
except Exception as e:
|
|
print(f"ERROR: {e}")
|
|
sys.exit(1)
|
|
|
|
return
|
|
|
|
# ------------------------------------------------------------
|
|
# Directory
|
|
# ------------------------------------------------------------
|
|
|
|
files = sorted(
|
|
p
|
|
for p in input_path.iterdir()
|
|
if p.is_file()
|
|
and p.suffix.lower() in {".txt", ".md"}
|
|
)
|
|
|
|
if not files:
|
|
print(
|
|
f"No .txt or .md files found in {input_path}",
|
|
file=sys.stderr
|
|
)
|
|
sys.exit(1)
|
|
|
|
print(f"Found {len(files)} prayer files.")
|
|
print("Files will be rewritten IN PLACE.")
|
|
print()
|
|
|
|
successes = 0
|
|
failures = 0
|
|
|
|
for n, prayer_path in enumerate(files, start=1):
|
|
|
|
print(
|
|
f"[{n}/{len(files)}] {prayer_path.name}",
|
|
end=" ... ",
|
|
flush=True
|
|
)
|
|
|
|
try:
|
|
revise_file(client, prayer_path)
|
|
successes += 1
|
|
print("done")
|
|
|
|
except KeyboardInterrupt:
|
|
print("\nInterrupted.")
|
|
print(
|
|
f"{successes} completed, "
|
|
f"{failures} failed before interruption."
|
|
)
|
|
sys.exit(130)
|
|
|
|
except Exception as e:
|
|
failures += 1
|
|
print(f"ERROR: {e}")
|
|
|
|
print()
|
|
print(
|
|
f"Finished: {successes} revised, "
|
|
f"{failures} failed."
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|