Add output options: save to S3 and GitHub

New CLI options:
--save-s3 BUCKET/PATH      Save manuscript to S3
--save-s3-endpoint URL    S3 endpoint for MinIO/DO Spaces
--save-repo OWNER/REPO    Commit to GitHub repo
--save-branch NAME        GitHub branch (default: main)
--save-commit-msg MSG    Commit message

Full pipeline example:
opus generate --repo owner/notes --save-repo owner/manuscripts --save-branch develop
This commit is contained in:
2026-03-13 03:37:57 +00:00
parent 92a4fac9c7
commit a6b43cd623
2 changed files with 155 additions and 20 deletions
+24 -2
View File
@@ -90,6 +90,14 @@ opus --api-url http://localhost:8000 generate --concept "Your idea"
| **API Client** | Client mode for remote servers | ✅ |
| **Python Module** | Import as library | ✅ |
### Output Options
| Destination | CLI Flag | Description |
|------------|----------|-------------|
| **Local File** | `--output FILE` | Save to local filesystem |
| **S3/MinIO** | `--save-s3 BUCKET/PATH` | Upload to S3-compatible storage |
| **GitHub** | `--save-repo OWNER/REPO` | Commit to GitHub repository |
---
## 🚀 Usage
@@ -97,13 +105,27 @@ opus --api-url http://localhost:8000 generate --concept "Your idea"
### CLI Commands
```bash
# Generate manuscript (local)
# Generate manuscript (local, save to file)
opus generate --concept "Your story idea" --framework snowflake --words 5000
# Generate from GitHub
opus generate --repo owner/repo --framework hero-journey --words 80000
# Generate from S3/MinIO
# Generate and save to S3/MinIO
opus generate --concept "..." --save-s3 my-bucket/manuscripts/
opus generate --concept "..." --save-s3 my-bucket/path/ --save-s3-endpoint https://nyc3.digitaloceanspaces.com
# Generate and save to GitHub repo
opus generate --concept "..." --save-repo owner/my-manuscripts
opus generate --concept "..." --save-repo owner/my-manuscripts --save-branch develop --save-commit-msg "New story draft"
# Generate from S3, save to GitHub
opus generate --repo owner/repo --save-repo owner/output-repo
# Generate from S3, save to different S3 bucket
opus ingest-s3 --bucket input-bucket --prefix notes/ | opus generate --save-s3 output-bucket/
# Ingest from S3/MinIO
opus ingest-s3 --bucket my-bucket --prefix notes/ --output content.txt
# Start API server
+131 -18
View File
@@ -246,7 +246,28 @@ Examples:
)
gen_parser.add_argument(
"--output", "-o",
help="Output file path",
help="Output file path (local)",
)
gen_parser.add_argument(
"--save-s3",
help="Save to S3 bucket (bucket/path format)",
)
gen_parser.add_argument(
"--save-s3-endpoint",
help="S3 endpoint URL (for MinIO, DO Spaces, etc.)",
)
gen_parser.add_argument(
"--save-repo",
help="Save to GitHub repo (owner/repo format)",
)
gen_parser.add_argument(
"--save-branch",
default="main",
help="GitHub branch to commit to (default: main)",
)
gen_parser.add_argument(
"--save-commit-msg",
help="Commit message for GitHub save",
)
gen_parser.add_argument(
"--use-crewai",
@@ -549,29 +570,121 @@ async def run_generate(args: argparse.Namespace) -> int:
manuscript = result.get("manuscript", str(result))
# Save output
output_path = args.output
if not output_path:
from datetime import datetime
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
output_path = f"opus_manuscript_{timestamp}.md"
with open(output_path, "w") as f:
f.write(f"# Opus Generated Manuscript\n\n")
f.write(f"Framework: {args.framework}\n")
f.write(f"Genre: {args.genre}\n")
f.write(f"Type: {args.book_type}\n")
f.write(f"Chapters: {args.chapters}\n")
f.write(f"Target Words: {args.words:,}\n\n")
f.write(f"---\n\n")
f.write(manuscript)
# Build full manuscript content with metadata
manuscript_content = f"""# Opus Generated Manuscript
Framework: {args.framework}
Genre: {args.genre}
Type: {args.book_type}
Chapters: {args.chapters}
Target Words: {args.words:,}
---
{manuscript}
"""
word_count = len(manuscript.split())
# Save to local file
output_path = args.output
if output_path or args.save_s3 or args.save_repo:
# Determine filename
if not output_path:
from datetime import datetime
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
output_path = f"opus_manuscript_{timestamp}.md"
with open(output_path, "w") as f:
f.write(manuscript_content)
print(f" 💾 Saved locally: {output_path}")
# Save to S3
if args.save_s3:
from opus_orchestrator import S3Ingestor
bucket, key = args.save_s3.split("/", 1) if "/" in args.save_s3 else (args.save_s3, "")
if not key:
from datetime import datetime
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
key = f"manuscripts/opus_{timestamp}.md"
print(f" 🪣 Saving to S3: {bucket}/{key}")
s3 = S3Ingestor(endpoint_url=args.save_s3_endpoint, bucket=bucket)
# Upload the manuscript content
import io
from botocore.config import Config
s3.s3_client.put_object(
Bucket=bucket,
Key=key,
Body=manuscript_content.encode("utf-8"),
ContentType="text/markdown",
)
print(f" ✅ Uploaded to S3: s3://{bucket}/{key}")
# Save to GitHub repo
if args.save_repo:
import requests
print(f" 📤 Saving to GitHub: {args.save_repo}")
github_token = os.environ.get("GITHUB_TOKEN")
if not github_token:
print(" ⚠️ GITHUB_TOKEN not set, cannot save to repo")
else:
# Parse owner/repo
owner, repo = args.save_repo.split("/")
# Create filename
from datetime import datetime
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"manuscript_{timestamp}.md"
# Get current branch
branch = args.save_branch
# Create/update file via GitHub API
import base64
api_url = f"https://api.github.com/repos/{owner}/{repo}/contents/manuscripts/{filename}"
headers = {
"Authorization": f"token {github_token}",
"Accept": "application/vnd.github.v3+json",
}
# Check if file exists to get SHA
existing = requests.get(api_url, headers=headers)
sha = existing.json().get("sha") if existing.status_code == 200 else None
# Create content
content_b64 = base64.b64encode(manuscript_content.encode("utf-8")).decode("utf-8")
data = {
"message": args.save_commit_msg or f"Add generated manuscript: {filename}",
"content": content_b64,
"branch": branch,
}
if sha:
data["sha"] = sha
resp = requests.put(api_url, headers=headers, json=data)
if resp.status_code in [200, 201]:
print(f" ✅ Committed to GitHub: {args.save_repo}/manuscripts/{filename}")
else:
print(f" ⚠️ GitHub save failed: {resp.status_code} - {resp.text}")
print(f"\n{'='*60}")
print(f"✅ COMPLETE!")
print(f" Words: {word_count:,}")
print(f" Output: {output_path}")
if not args.output and not args.save_s3 and not args.save_repo:
print(f" Output: {output_path}")
print(f"{'='*60}\n")
return 0