seshat-scribe 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +194 -0
- package/bin/seshat.js +15 -0
- package/dist/commands/generate.d.ts +8 -0
- package/dist/commands/generate.d.ts.map +1 -0
- package/dist/commands/generate.js +126 -0
- package/dist/commands/generate.js.map +1 -0
- package/dist/commands/init.d.ts +5 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +28 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/config.d.ts +36 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +33 -0
- package/dist/config.js.map +1 -0
- package/dist/core/analyzer.d.ts +28 -0
- package/dist/core/analyzer.d.ts.map +1 -0
- package/dist/core/analyzer.js +91 -0
- package/dist/core/analyzer.js.map +1 -0
- package/dist/core/artist.d.ts +6 -0
- package/dist/core/artist.d.ts.map +1 -0
- package/dist/core/artist.js +81 -0
- package/dist/core/artist.js.map +1 -0
- package/dist/core/planner.d.ts +31 -0
- package/dist/core/planner.d.ts.map +1 -0
- package/dist/core/planner.js +62 -0
- package/dist/core/planner.js.map +1 -0
- package/dist/core/writer.d.ts +7 -0
- package/dist/core/writer.d.ts.map +1 -0
- package/dist/core/writer.js +99 -0
- package/dist/core/writer.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/gemini.d.ts +10 -0
- package/dist/lib/gemini.d.ts.map +1 -0
- package/dist/lib/gemini.js +25 -0
- package/dist/lib/gemini.js.map +1 -0
- package/dist/lib/output.d.ts +29 -0
- package/dist/lib/output.d.ts.map +1 -0
- package/dist/lib/output.js +48 -0
- package/dist/lib/output.js.map +1 -0
- package/package.json +55 -0
- package/templates/workflow.yml +39 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Seshat Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# Seshat 𓇋𓏏
|
|
2
|
+
|
|
3
|
+
**The Autonomous Scribe for Static Blogs**
|
|
4
|
+
|
|
5
|
+
Seshat is a strictly typed Node.js CLI tool that autonomously reads your local markdown blog, identifies content gaps using Google Gemini, generates non-duplicate MDX articles with images, and can be automated via GitHub Actions.
|
|
6
|
+
|
|
7
|
+
## ✨ Features
|
|
8
|
+
|
|
9
|
+
- 🤖 **AI-Powered Content Planning**: Uses Google Gemini to analyze your existing blog and suggest new, non-duplicate topics
|
|
10
|
+
- ✍️ **Automated Writing**: Generates complete, high-quality MDX blog posts with proper frontmatter
|
|
11
|
+
- 🎨 **Image Generation**: Creates hero images for your posts (with placeholder support)
|
|
12
|
+
- 🔒 **Type-Safe**: Built with TypeScript and Zod for strict validation
|
|
13
|
+
- 🎯 **Framework Agnostic**: Supports Remix, Next.js, and Astro
|
|
14
|
+
- 🤝 **GitHub Actions Ready**: Automate content creation with scheduled workflows
|
|
15
|
+
- 🎨 **Beautiful CLI**: Gold and cyan themed output with progress indicators
|
|
16
|
+
|
|
17
|
+
## 📦 Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install -g seshat-scribe
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Or use directly with npx:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npx seshat-scribe init
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## 🚀 Quick Start
|
|
30
|
+
|
|
31
|
+
### 1. Initialize Configuration
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
cd your-blog-project
|
|
35
|
+
seshat init
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
This creates a `seshat.config.json` file in your project root.
|
|
39
|
+
|
|
40
|
+
### 2. Configure Your Blog
|
|
41
|
+
|
|
42
|
+
Edit `seshat.config.json`:
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"contentDir": "app/routes/blog",
|
|
47
|
+
"assetsDir": "public/images/generated",
|
|
48
|
+
"publicAssetPath": "/images/generated",
|
|
49
|
+
"topic": "Software Engineering and Technology",
|
|
50
|
+
"tone": "Professional yet witty",
|
|
51
|
+
"framework": "remix"
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 3. Set Up Your API Key
|
|
56
|
+
|
|
57
|
+
Get a Google Gemini API key from [Google AI Studio](https://aistudio.google.com/app/apikey) and add it to your environment:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
export GEMINI_API_KEY="your_api_key_here"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Or create a `.env` file:
|
|
64
|
+
|
|
65
|
+
```env
|
|
66
|
+
GEMINI_API_KEY=your_api_key_here
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 4. Generate Your First Post
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
seshat write
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Use `--dry-run` to preview without saving:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
seshat write --dry-run
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## 🔧 Configuration Options
|
|
82
|
+
|
|
83
|
+
| Option | Type | Description |
|
|
84
|
+
|--------|------|-------------|
|
|
85
|
+
| `contentDir` | string | Directory containing your blog MDX files |
|
|
86
|
+
| `assetsDir` | string | Directory for generated images |
|
|
87
|
+
| `publicAssetPath` | string | Public URL path for images in frontmatter |
|
|
88
|
+
| `topic` | string | Global topic constraint for content generation |
|
|
89
|
+
| `tone` | string | Writing tone (e.g., "Professional yet witty") |
|
|
90
|
+
| `framework` | "remix" \| "next" \| "astro" | Blog framework for frontmatter format |
|
|
91
|
+
|
|
92
|
+
## 🤖 GitHub Actions Automation
|
|
93
|
+
|
|
94
|
+
Automate Seshat to write blog posts on a schedule:
|
|
95
|
+
|
|
96
|
+
### 1. Copy the Workflow Template
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
mkdir -p .github/workflows
|
|
100
|
+
cp node_modules/seshat-scribe/templates/workflow.yml .github/workflows/seshat.yml
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 2. Add Your API Key to GitHub Secrets
|
|
104
|
+
|
|
105
|
+
1. Go to your repository settings
|
|
106
|
+
2. Navigate to Secrets and Variables → Actions
|
|
107
|
+
3. Add a new secret: `GEMINI_API_KEY`
|
|
108
|
+
|
|
109
|
+
### 3. Customize the Schedule
|
|
110
|
+
|
|
111
|
+
Edit `.github/workflows/seshat.yml`:
|
|
112
|
+
|
|
113
|
+
```yaml
|
|
114
|
+
on:
|
|
115
|
+
schedule:
|
|
116
|
+
- cron: '0 9 * * 1' # Every Monday at 9 AM
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## 🎨 CLI Commands
|
|
120
|
+
|
|
121
|
+
### `seshat init`
|
|
122
|
+
|
|
123
|
+
Initialize Seshat configuration in the current directory.
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
seshat init
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### `seshat write`
|
|
130
|
+
|
|
131
|
+
Generate a new blog post with AI.
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
seshat write [options]
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**Options:**
|
|
138
|
+
- `--dry-run`: Preview the content without saving files
|
|
139
|
+
|
|
140
|
+
## 🏗️ Tech Stack
|
|
141
|
+
|
|
142
|
+
- **Runtime**: Node.js v20+
|
|
143
|
+
- **Language**: TypeScript
|
|
144
|
+
- **AI Engine**: @google/generative-ai (Gemini 1.5 Pro)
|
|
145
|
+
- **CLI Framework**: commander + chalk
|
|
146
|
+
- **File Parsing**: gray-matter + zod
|
|
147
|
+
- **Progress UI**: ora
|
|
148
|
+
|
|
149
|
+
## 📝 How It Works
|
|
150
|
+
|
|
151
|
+
1. **Analysis**: Seshat scans your existing blog posts to understand what you've already written
|
|
152
|
+
2. **Planning**: Uses Gemini to propose a new, non-duplicate topic
|
|
153
|
+
3. **Writing**: Generates a complete MDX article with proper structure and frontmatter
|
|
154
|
+
4. **Illustration**: Creates a hero image for the post
|
|
155
|
+
5. **Assembly**: Saves the MDX file and image to your project
|
|
156
|
+
|
|
157
|
+
## 🎭 The Seshat Vibe
|
|
158
|
+
|
|
159
|
+
Seshat is named after the ancient Egyptian goddess of writing, wisdom, and knowledge. The CLI embraces this theme with:
|
|
160
|
+
|
|
161
|
+
- ✦ Gold (#FFD700) and Cyan (#00FFFF) color scheme
|
|
162
|
+
- 𓇋𓏏 Egyptian hieroglyphic symbols
|
|
163
|
+
- Sacred completion message: "The scroll is written. The archives are updated."
|
|
164
|
+
|
|
165
|
+
## 🛠️ Development
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
# Clone the repository
|
|
169
|
+
git clone https://github.com/yourusername/seshat.git
|
|
170
|
+
cd seshat
|
|
171
|
+
|
|
172
|
+
# Install dependencies
|
|
173
|
+
npm install
|
|
174
|
+
|
|
175
|
+
# Build
|
|
176
|
+
npm run build
|
|
177
|
+
|
|
178
|
+
# Link for local development
|
|
179
|
+
npm link
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## 📄 License
|
|
183
|
+
|
|
184
|
+
MIT
|
|
185
|
+
|
|
186
|
+
## 🙏 Acknowledgments
|
|
187
|
+
|
|
188
|
+
- Inspired by ancient Egyptian wisdom
|
|
189
|
+
- Powered by Google Gemini
|
|
190
|
+
- Built for modern static site generators
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
**✦ Seshat 𓇋𓏏** - *The scroll is written. The archives are updated.*
|
package/bin/seshat.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Seshat 𓇋𓏏
|
|
5
|
+
* The Autonomous Scribe for Static Blogs
|
|
6
|
+
*
|
|
7
|
+
* This is the executable entry point for the CLI.
|
|
8
|
+
* It dynamically imports the compiled TypeScript from dist/
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import('../dist/index.js').catch((err) => {
|
|
12
|
+
console.error('Failed to load Seshat:', err);
|
|
13
|
+
console.error('\nMake sure you have run: npm run build');
|
|
14
|
+
process.exit(1);
|
|
15
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/commands/generate.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,wBAAsB,QAAQ,CAAC,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CA4H3E"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { scanRepository } from '../core/analyzer.js';
|
|
5
|
+
import { planBlogPost } from '../core/planner.js';
|
|
6
|
+
import { writeBlogPost } from '../core/writer.js';
|
|
7
|
+
import { generateImage } from '../core/artist.js';
|
|
8
|
+
import { gold, cyan, error, info, scrollWritten } from '../lib/output.js';
|
|
9
|
+
/**
|
|
10
|
+
* Main command: Generate a new blog post with image
|
|
11
|
+
*/
|
|
12
|
+
export async function generate(options = {}) {
|
|
13
|
+
const configPath = path.join(process.cwd(), 'seshat.config.json');
|
|
14
|
+
// Load configuration
|
|
15
|
+
let config;
|
|
16
|
+
try {
|
|
17
|
+
const { loadConfig } = await import('../config.js');
|
|
18
|
+
config = await loadConfig(configPath);
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
error('Could not load seshat.config.json');
|
|
22
|
+
info('Run: seshat init');
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
console.log('');
|
|
26
|
+
console.log(gold.bold('⟡ Seshat awakens...'));
|
|
27
|
+
console.log('');
|
|
28
|
+
// Step 1: Analyze existing content
|
|
29
|
+
const analyzeSpinner = ora({
|
|
30
|
+
text: cyan('Reading the archives...'),
|
|
31
|
+
color: 'cyan',
|
|
32
|
+
}).start();
|
|
33
|
+
let context;
|
|
34
|
+
try {
|
|
35
|
+
context = await scanRepository(config);
|
|
36
|
+
analyzeSpinner.succeed(`Found ${context.totalPosts} existing post${context.totalPosts !== 1 ? 's' : ''}`);
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
analyzeSpinner.fail('Failed to analyze content');
|
|
40
|
+
error(err instanceof Error ? err.message : 'Unknown error');
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
// Step 2: Plan new content
|
|
44
|
+
const planSpinner = ora({
|
|
45
|
+
text: cyan('Seshat is contemplating...'),
|
|
46
|
+
color: 'cyan',
|
|
47
|
+
}).start();
|
|
48
|
+
let plan;
|
|
49
|
+
try {
|
|
50
|
+
plan = await planBlogPost(config, context);
|
|
51
|
+
planSpinner.succeed(`Planned: "${plan.title}"`);
|
|
52
|
+
info(` Slug: ${plan.slug}`);
|
|
53
|
+
info(` Tags: ${plan.tags.join(', ')}`);
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
planSpinner.fail('Failed to plan content');
|
|
57
|
+
error(err instanceof Error ? err.message : 'Unknown error');
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
// Step 3: Write the content
|
|
61
|
+
const writeSpinner = ora({
|
|
62
|
+
text: cyan('Seshat is writing...'),
|
|
63
|
+
color: 'cyan',
|
|
64
|
+
}).start();
|
|
65
|
+
let mdxContent;
|
|
66
|
+
try {
|
|
67
|
+
mdxContent = await writeBlogPost(config, plan);
|
|
68
|
+
writeSpinner.succeed('Content written');
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
writeSpinner.fail('Failed to write content');
|
|
72
|
+
error(err instanceof Error ? err.message : 'Unknown error');
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
// Step 4: Generate the image
|
|
76
|
+
const imageSpinner = ora({
|
|
77
|
+
text: cyan('Seshat is painting...'),
|
|
78
|
+
color: 'cyan',
|
|
79
|
+
}).start();
|
|
80
|
+
let imagePath;
|
|
81
|
+
try {
|
|
82
|
+
imagePath = await generateImage(config, plan.imagePrompt, plan.slug);
|
|
83
|
+
const relativeImagePath = path.relative(process.cwd(), imagePath);
|
|
84
|
+
imageSpinner.succeed(`Image created: ${relativeImagePath}`);
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
imageSpinner.fail('Failed to generate image');
|
|
88
|
+
error(err instanceof Error ? err.message : 'Unknown error');
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
// Step 5: Inject image path and save the file
|
|
92
|
+
const saveSpinner = ora({
|
|
93
|
+
text: cyan('Inscribing the scroll...'),
|
|
94
|
+
color: 'cyan',
|
|
95
|
+
}).start();
|
|
96
|
+
try {
|
|
97
|
+
// Replace the placeholder image path with the actual path
|
|
98
|
+
const publicImagePath = `${config.publicAssetPath}/${plan.slug}.png`;
|
|
99
|
+
mdxContent = mdxContent.replace(/PLACEHOLDER_IMAGE/g, publicImagePath);
|
|
100
|
+
if (options.dryRun) {
|
|
101
|
+
saveSpinner.info('Dry run: skipping file write');
|
|
102
|
+
console.log('');
|
|
103
|
+
console.log(gold('--- Generated Content Preview ---'));
|
|
104
|
+
console.log(mdxContent.substring(0, 500) + '...');
|
|
105
|
+
console.log('');
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
// Ensure content directory exists
|
|
109
|
+
const contentPath = path.resolve(process.cwd(), config.contentDir);
|
|
110
|
+
await fs.mkdir(contentPath, { recursive: true });
|
|
111
|
+
// Write the MDX file
|
|
112
|
+
const mdxFilePath = path.join(contentPath, `${plan.slug}.mdx`);
|
|
113
|
+
await fs.writeFile(mdxFilePath, mdxContent, 'utf-8');
|
|
114
|
+
const relativeMdxPath = path.relative(process.cwd(), mdxFilePath);
|
|
115
|
+
saveSpinner.succeed(`Saved: ${relativeMdxPath}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
saveSpinner.fail('Failed to save file');
|
|
120
|
+
error(err instanceof Error ? err.message : 'Unknown error');
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
// Victory!
|
|
124
|
+
scrollWritten();
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=generate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate.js","sourceRoot":"","sources":["../../src/commands/generate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAKA;;GAEG;AACH,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAqB1C"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { DEFAULT_CONFIG } from '../config.js';
|
|
4
|
+
import { success, info } from '../lib/output.js';
|
|
5
|
+
/**
|
|
6
|
+
* Initialize Seshat configuration in the current directory
|
|
7
|
+
*/
|
|
8
|
+
export async function init() {
|
|
9
|
+
const configPath = path.join(process.cwd(), 'seshat.config.json');
|
|
10
|
+
try {
|
|
11
|
+
await fs.access(configPath);
|
|
12
|
+
info('Configuration file already exists at seshat.config.json');
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
// File doesn't exist, create it
|
|
17
|
+
}
|
|
18
|
+
const configContent = JSON.stringify(DEFAULT_CONFIG, null, 2);
|
|
19
|
+
await fs.writeFile(configPath, configContent, 'utf-8');
|
|
20
|
+
success('Created seshat.config.json');
|
|
21
|
+
info('Edit this file to configure Seshat for your blog');
|
|
22
|
+
console.log('');
|
|
23
|
+
console.log(' Next steps:');
|
|
24
|
+
console.log(' 1. Set your GEMINI_API_KEY environment variable');
|
|
25
|
+
console.log(' 2. Run: seshat write');
|
|
26
|
+
console.log('');
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,aAAa,CAAC;AAC7B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAEjD;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,IAAI;IACxB,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,oBAAoB,CAAC,CAAC;IAElE,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAC5B,IAAI,CAAC,yDAAyD,CAAC,CAAC;QAChE,OAAO;IACT,CAAC;IAAC,MAAM,CAAC;QACP,gCAAgC;IAClC,CAAC;IAED,MAAM,aAAa,GAAG,IAAI,CAAC,SAAS,CAAC,cAAc,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAC9D,MAAM,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,aAAa,EAAE,OAAO,CAAC,CAAC;IAEvD,OAAO,CAAC,4BAA4B,CAAC,CAAC;IACtC,IAAI,CAAC,kDAAkD,CAAC,CAAC;IACzD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IAC7B,OAAO,CAAC,GAAG,CAAC,mDAAmD,CAAC,CAAC;IACjE,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;IACtC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AAClB,CAAC"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Zod schema for Seshat configuration
|
|
4
|
+
*/
|
|
5
|
+
export declare const SeshatConfigSchema: z.ZodObject<{
|
|
6
|
+
contentDir: z.ZodString;
|
|
7
|
+
assetsDir: z.ZodString;
|
|
8
|
+
publicAssetPath: z.ZodString;
|
|
9
|
+
topic: z.ZodString;
|
|
10
|
+
tone: z.ZodString;
|
|
11
|
+
framework: z.ZodEnum<["remix", "next", "astro"]>;
|
|
12
|
+
}, "strip", z.ZodTypeAny, {
|
|
13
|
+
contentDir: string;
|
|
14
|
+
assetsDir: string;
|
|
15
|
+
publicAssetPath: string;
|
|
16
|
+
topic: string;
|
|
17
|
+
tone: string;
|
|
18
|
+
framework: "remix" | "next" | "astro";
|
|
19
|
+
}, {
|
|
20
|
+
contentDir: string;
|
|
21
|
+
assetsDir: string;
|
|
22
|
+
publicAssetPath: string;
|
|
23
|
+
topic: string;
|
|
24
|
+
tone: string;
|
|
25
|
+
framework: "remix" | "next" | "astro";
|
|
26
|
+
}>;
|
|
27
|
+
export type SeshatConfig = z.infer<typeof SeshatConfigSchema>;
|
|
28
|
+
/**
|
|
29
|
+
* Default configuration values
|
|
30
|
+
*/
|
|
31
|
+
export declare const DEFAULT_CONFIG: SeshatConfig;
|
|
32
|
+
/**
|
|
33
|
+
* Load and validate configuration from file
|
|
34
|
+
*/
|
|
35
|
+
export declare function loadConfig(configPath: string): Promise<SeshatConfig>;
|
|
36
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;GAEG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;EAO7B,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,YAO5B,CAAC;AAEF;;GAEG;AACH,wBAAsB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAK1E"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Zod schema for Seshat configuration
|
|
4
|
+
*/
|
|
5
|
+
export const SeshatConfigSchema = z.object({
|
|
6
|
+
contentDir: z.string().describe('Directory containing blog MDX files'),
|
|
7
|
+
assetsDir: z.string().describe('Directory for generated images'),
|
|
8
|
+
publicAssetPath: z.string().describe('Public URL path for images'),
|
|
9
|
+
topic: z.string().describe('Global topic constraint for content generation'),
|
|
10
|
+
tone: z.string().describe('Writing tone (e.g., "Professional yet witty")'),
|
|
11
|
+
framework: z.enum(['remix', 'next', 'astro']).describe('Blog framework type'),
|
|
12
|
+
});
|
|
13
|
+
/**
|
|
14
|
+
* Default configuration values
|
|
15
|
+
*/
|
|
16
|
+
export const DEFAULT_CONFIG = {
|
|
17
|
+
contentDir: 'app/routes/blog',
|
|
18
|
+
assetsDir: 'public/images/generated',
|
|
19
|
+
publicAssetPath: '/images/generated',
|
|
20
|
+
topic: 'Software Engineering and Technology',
|
|
21
|
+
tone: 'Professional yet witty',
|
|
22
|
+
framework: 'remix',
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Load and validate configuration from file
|
|
26
|
+
*/
|
|
27
|
+
export async function loadConfig(configPath) {
|
|
28
|
+
const fs = await import('fs/promises');
|
|
29
|
+
const raw = await fs.readFile(configPath, 'utf-8');
|
|
30
|
+
const json = JSON.parse(raw);
|
|
31
|
+
return SeshatConfigSchema.parse(json);
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;GAEG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,qCAAqC,CAAC;IACtE,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,gCAAgC,CAAC;IAChE,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,4BAA4B,CAAC;IAClE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,gDAAgD,CAAC;IAC5E,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,+CAA+C,CAAC;IAC1E,SAAS,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,qBAAqB,CAAC;CAC9E,CAAC,CAAC;AAIH;;GAEG;AACH,MAAM,CAAC,MAAM,cAAc,GAAiB;IAC1C,UAAU,EAAE,iBAAiB;IAC7B,SAAS,EAAE,yBAAyB;IACpC,eAAe,EAAE,mBAAmB;IACpC,KAAK,EAAE,qCAAqC;IAC5C,IAAI,EAAE,wBAAwB;IAC9B,SAAS,EAAE,OAAO;CACnB,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,UAAkB;IACjD,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;IACvC,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IACnD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC7B,OAAO,kBAAkB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AACxC,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { SeshatConfig } from '../config.js';
|
|
2
|
+
/**
|
|
3
|
+
* Summary of a single blog post
|
|
4
|
+
*/
|
|
5
|
+
export interface PostSummary {
|
|
6
|
+
title: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
tags?: string[];
|
|
9
|
+
date?: string;
|
|
10
|
+
slug: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Repository context for Gemini
|
|
14
|
+
*/
|
|
15
|
+
export interface RepositoryContext {
|
|
16
|
+
totalPosts: number;
|
|
17
|
+
posts: PostSummary[];
|
|
18
|
+
recentTopics: string[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Scan the content directory and extract metadata from all MDX files
|
|
22
|
+
*/
|
|
23
|
+
export declare function scanRepository(config: SeshatConfig): Promise<RepositoryContext>;
|
|
24
|
+
/**
|
|
25
|
+
* Create a compact JSON summary for Gemini (minimize token usage)
|
|
26
|
+
*/
|
|
27
|
+
export declare function summarizeContext(context: RepositoryContext): string;
|
|
28
|
+
//# sourceMappingURL=analyzer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../../src/core/analyzer.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,iBAAiB,CAAC,CA+ErF;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAQnE"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import matter from 'gray-matter';
|
|
4
|
+
/**
|
|
5
|
+
* Scan the content directory and extract metadata from all MDX files
|
|
6
|
+
*/
|
|
7
|
+
export async function scanRepository(config) {
|
|
8
|
+
const contentPath = path.resolve(process.cwd(), config.contentDir);
|
|
9
|
+
try {
|
|
10
|
+
await fs.access(contentPath);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
// Directory doesn't exist yet, return empty context
|
|
14
|
+
return {
|
|
15
|
+
totalPosts: 0,
|
|
16
|
+
posts: [],
|
|
17
|
+
recentTopics: [],
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const entries = await fs.readdir(contentPath, { withFileTypes: true });
|
|
21
|
+
const posts = [];
|
|
22
|
+
for (const entry of entries) {
|
|
23
|
+
if (!entry.isFile())
|
|
24
|
+
continue;
|
|
25
|
+
if (!/\.(md|mdx)$/i.test(entry.name))
|
|
26
|
+
continue;
|
|
27
|
+
const filePath = path.join(contentPath, entry.name);
|
|
28
|
+
const slug = entry.name.replace(/\.(md|mdx)$/i, '');
|
|
29
|
+
try {
|
|
30
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
31
|
+
// Try gray-matter first
|
|
32
|
+
let data;
|
|
33
|
+
try {
|
|
34
|
+
const parsed = matter(content);
|
|
35
|
+
data = parsed.data;
|
|
36
|
+
}
|
|
37
|
+
catch (yamlError) {
|
|
38
|
+
// Fallback: manual YAML parsing for problematic frontmatter
|
|
39
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]+?)\n---/);
|
|
40
|
+
if (!frontmatterMatch)
|
|
41
|
+
throw yamlError;
|
|
42
|
+
const fm = frontmatterMatch[1];
|
|
43
|
+
// Extract fields manually
|
|
44
|
+
const titleMatch = fm.match(/^title:\s*['"](.+?)['"]?\s*$/m);
|
|
45
|
+
const descMatch = fm.match(/^description:\s*>-?\s*\n((?:\s+.+\n?)+)/m);
|
|
46
|
+
const tagMatch = fm.match(/^tags?:\s*(.+?)\s*$/m);
|
|
47
|
+
const dateMatch = fm.match(/^(?:date|pubDate):\s*(.+?)\s*$/m);
|
|
48
|
+
data = {
|
|
49
|
+
title: titleMatch ? titleMatch[1] : slug,
|
|
50
|
+
description: descMatch ? descMatch[1].replace(/\n\s+/g, ' ').trim() : undefined,
|
|
51
|
+
tag: tagMatch ? tagMatch[1].trim() : undefined,
|
|
52
|
+
tags: undefined,
|
|
53
|
+
date: dateMatch ? dateMatch[1].trim() : undefined,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
// Handle 'tag' or 'tags' field (some posts use singular)
|
|
57
|
+
const tags = data.tags || (data.tag ? [data.tag] : undefined);
|
|
58
|
+
posts.push({
|
|
59
|
+
title: data.title || slug,
|
|
60
|
+
description: data.description,
|
|
61
|
+
tags: Array.isArray(tags) ? tags : (tags ? [tags] : undefined),
|
|
62
|
+
date: data.date || data.pubDate,
|
|
63
|
+
slug,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
// Skip files that can't be read or parsed
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Extract unique topics from tags
|
|
72
|
+
const allTags = posts.flatMap((p) => p.tags || []);
|
|
73
|
+
const uniqueTags = [...new Set(allTags)];
|
|
74
|
+
return {
|
|
75
|
+
totalPosts: posts.length,
|
|
76
|
+
posts,
|
|
77
|
+
recentTopics: uniqueTags.slice(0, 20), // Limit to most recent topics
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Create a compact JSON summary for Gemini (minimize token usage)
|
|
82
|
+
*/
|
|
83
|
+
export function summarizeContext(context) {
|
|
84
|
+
const summary = {
|
|
85
|
+
total: context.totalPosts,
|
|
86
|
+
titles: context.posts.map((p) => p.title),
|
|
87
|
+
topics: context.recentTopics,
|
|
88
|
+
};
|
|
89
|
+
return JSON.stringify(summary, null, 2);
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=analyzer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyzer.js","sourceRoot":"","sources":["../../src/core/analyzer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"artist.d.ts","sourceRoot":"","sources":["../../src/core/artist.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD;;GAEG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,YAAY,EACpB,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,CAAC,CA+DjB"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { createGenAIClient, SafetyFilterLevel, PersonGeneration } from '../lib/gemini.js';
|
|
4
|
+
/**
|
|
5
|
+
* Generate an image using Gemini's Imagen 4.0 model
|
|
6
|
+
*/
|
|
7
|
+
export async function generateImage(config, imagePrompt, slug) {
|
|
8
|
+
const assetsPath = path.resolve(process.cwd(), config.assetsDir);
|
|
9
|
+
const imagePath = path.join(assetsPath, `${slug}.png`);
|
|
10
|
+
// Ensure the assets directory exists
|
|
11
|
+
await fs.mkdir(assetsPath, { recursive: true });
|
|
12
|
+
try {
|
|
13
|
+
const ai = createGenAIClient();
|
|
14
|
+
// Enhance the prompt with consistent style instructions
|
|
15
|
+
const enhancedPrompt = `${imagePrompt}
|
|
16
|
+
|
|
17
|
+
Style: Digital drawing style, hand-drawn aesthetic, slightly imperfect and organic.
|
|
18
|
+
Should look human-made, not overly polished or synthetic.
|
|
19
|
+
Warm, approachable colors with a personal touch.
|
|
20
|
+
Think indie illustration or personal blog header art.
|
|
21
|
+
Avoid: Corporate stock photo look, 3D renders, photorealistic AI art, overly perfect compositions.`;
|
|
22
|
+
// Generate the image using the correct API with timeout
|
|
23
|
+
const imagePromise = ai.models.generateImages({
|
|
24
|
+
model: 'imagen-4.0-generate-001',
|
|
25
|
+
prompt: enhancedPrompt,
|
|
26
|
+
config: {
|
|
27
|
+
numberOfImages: 1,
|
|
28
|
+
aspectRatio: '16:9', // Good for blog headers
|
|
29
|
+
safetyFilterLevel: SafetyFilterLevel.BLOCK_LOW_AND_ABOVE,
|
|
30
|
+
personGeneration: PersonGeneration.ALLOW_ADULT,
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
// Add timeout (30 seconds)
|
|
34
|
+
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Image generation timed out after 30s')), 30000));
|
|
35
|
+
const response = await Promise.race([imagePromise, timeoutPromise]);
|
|
36
|
+
if (!response.generatedImages || response.generatedImages.length === 0) {
|
|
37
|
+
throw new Error('No image generated');
|
|
38
|
+
}
|
|
39
|
+
const generatedImage = response.generatedImages[0];
|
|
40
|
+
if (!generatedImage.image?.imageBytes) {
|
|
41
|
+
throw new Error('No image data returned');
|
|
42
|
+
}
|
|
43
|
+
// Convert base64 to buffer and save
|
|
44
|
+
const imageBuffer = Buffer.from(generatedImage.image.imageBytes, 'base64');
|
|
45
|
+
await fs.writeFile(imagePath, imageBuffer);
|
|
46
|
+
return imagePath;
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
console.error(`Image generation failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
50
|
+
console.warn('Falling back to placeholder image...');
|
|
51
|
+
// Fallback to placeholder if Imagen fails
|
|
52
|
+
const placeholderSVG = createPlaceholderImage(slug);
|
|
53
|
+
await fs.writeFile(imagePath, placeholderSVG, 'utf-8');
|
|
54
|
+
return imagePath;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Create a placeholder SVG image
|
|
59
|
+
* Fallback if Imagen generation fails
|
|
60
|
+
*/
|
|
61
|
+
function createPlaceholderImage(slug) {
|
|
62
|
+
// Generate a consistent color based on the slug
|
|
63
|
+
const hash = slug.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
|
|
64
|
+
const hue = hash % 360;
|
|
65
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 630">
|
|
66
|
+
<defs>
|
|
67
|
+
<linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
68
|
+
<stop offset="0%" style="stop-color:hsl(${hue}, 70%, 50%);stop-opacity:1" />
|
|
69
|
+
<stop offset="100%" style="stop-color:hsl(${(hue + 60) % 360}, 70%, 30%);stop-opacity:1" />
|
|
70
|
+
</linearGradient>
|
|
71
|
+
</defs>
|
|
72
|
+
<rect width="1200" height="630" fill="url(#grad)"/>
|
|
73
|
+
<text x="50%" y="50%" font-family="Arial, sans-serif" font-size="48" fill="white" text-anchor="middle" dominant-baseline="middle" opacity="0.9">
|
|
74
|
+
${slug.replace(/-/g, ' ').toUpperCase()}
|
|
75
|
+
</text>
|
|
76
|
+
<text x="50%" y="60%" font-family="Arial, sans-serif" font-size="24" fill="white" text-anchor="middle" dominant-baseline="middle" opacity="0.7">
|
|
77
|
+
Image generation placeholder
|
|
78
|
+
</text>
|
|
79
|
+
</svg>`;
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=artist.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"artist.js","sourceRoot":"","sources":["../../src/core/artist.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { RepositoryContext } from './analyzer.js';
|
|
3
|
+
import type { SeshatConfig } from '../config.js';
|
|
4
|
+
/**
|
|
5
|
+
* Schema for the blog post plan returned by Gemini
|
|
6
|
+
*/
|
|
7
|
+
export declare const BlogPlanSchema: z.ZodObject<{
|
|
8
|
+
title: z.ZodString;
|
|
9
|
+
slug: z.ZodString;
|
|
10
|
+
description: z.ZodString;
|
|
11
|
+
tags: z.ZodArray<z.ZodString, "many">;
|
|
12
|
+
imagePrompt: z.ZodString;
|
|
13
|
+
}, "strip", z.ZodTypeAny, {
|
|
14
|
+
title: string;
|
|
15
|
+
description: string;
|
|
16
|
+
tags: string[];
|
|
17
|
+
slug: string;
|
|
18
|
+
imagePrompt: string;
|
|
19
|
+
}, {
|
|
20
|
+
title: string;
|
|
21
|
+
description: string;
|
|
22
|
+
tags: string[];
|
|
23
|
+
slug: string;
|
|
24
|
+
imagePrompt: string;
|
|
25
|
+
}>;
|
|
26
|
+
export type BlogPlan = z.infer<typeof BlogPlanSchema>;
|
|
27
|
+
/**
|
|
28
|
+
* Use Gemini to plan a new blog post that doesn't duplicate existing content
|
|
29
|
+
*/
|
|
30
|
+
export declare function planBlogPost(config: SeshatConfig, context: RepositoryContext): Promise<BlogPlan>;
|
|
31
|
+
//# sourceMappingURL=planner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"planner.d.ts","sourceRoot":"","sources":["../../src/core/planner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD;;GAEG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;EAMzB,CAAC;AAEH,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAEtD;;GAEG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,YAAY,EACpB,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,QAAQ,CAAC,CAmDnB"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { createGenAIClient } from '../lib/gemini.js';
|
|
3
|
+
/**
|
|
4
|
+
* Schema for the blog post plan returned by Gemini
|
|
5
|
+
*/
|
|
6
|
+
export const BlogPlanSchema = z.object({
|
|
7
|
+
title: z.string(),
|
|
8
|
+
slug: z.string(),
|
|
9
|
+
description: z.string(),
|
|
10
|
+
tags: z.array(z.string()),
|
|
11
|
+
imagePrompt: z.string(),
|
|
12
|
+
});
|
|
13
|
+
/**
|
|
14
|
+
* Use Gemini to plan a new blog post that doesn't duplicate existing content
|
|
15
|
+
*/
|
|
16
|
+
export async function planBlogPost(config, context) {
|
|
17
|
+
const ai = createGenAIClient();
|
|
18
|
+
const existingTitles = context.posts.map((p) => p.title).join('\n - ');
|
|
19
|
+
const existingTopics = context.recentTopics.join(', ');
|
|
20
|
+
const prompt = `Plan a new blog post for a developer blog.
|
|
21
|
+
|
|
22
|
+
**Existing Content:**
|
|
23
|
+
- Total posts: ${context.totalPosts}
|
|
24
|
+
- Titles: ${existingTitles || 'None yet'}
|
|
25
|
+
- Topics: ${existingTopics || 'None yet'}
|
|
26
|
+
|
|
27
|
+
**Topic Scope:** ${config.topic}
|
|
28
|
+
|
|
29
|
+
**Requirements:**
|
|
30
|
+
1. Pick a topic within "${config.topic}" that hasn't been covered
|
|
31
|
+
2. Make it specific and valuable to developers
|
|
32
|
+
3. Avoid duplicating existing posts
|
|
33
|
+
4. Vary from recent posts (if last posts were about X, pick something different)
|
|
34
|
+
5. Create a clear, specific title (not generic)
|
|
35
|
+
|
|
36
|
+
Vary the title from previous posts - don't just repeat the same pattern.
|
|
37
|
+
|
|
38
|
+
**Output JSON:**
|
|
39
|
+
{
|
|
40
|
+
"title": "Specific, clear title",
|
|
41
|
+
"slug": "url-friendly-slug",
|
|
42
|
+
"description": "One sentence explaining what the post covers",
|
|
43
|
+
"tags": ["primary-tag", "related-tag", "context-tag"],
|
|
44
|
+
"imagePrompt": "Describe a clean, modern blog header image. Style: minimalist illustration or abstract design. Colors, composition, mood."
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
Return only valid JSON.`;
|
|
48
|
+
const response = await ai.models.generateContent({
|
|
49
|
+
model: 'gemini-3-flash-preview',
|
|
50
|
+
contents: prompt,
|
|
51
|
+
});
|
|
52
|
+
const text = response.text?.trim() || '';
|
|
53
|
+
// Clean up the response (remove markdown code blocks if present)
|
|
54
|
+
let jsonText = text;
|
|
55
|
+
if (jsonText.startsWith('```')) {
|
|
56
|
+
jsonText = jsonText.replace(/^```(?:json)?\n/, '').replace(/\n```$/, '');
|
|
57
|
+
}
|
|
58
|
+
// Parse and validate the JSON
|
|
59
|
+
const json = JSON.parse(jsonText);
|
|
60
|
+
return BlogPlanSchema.parse(json);
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=planner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"planner.js","sourceRoot":"","sources":["../../src/core/planner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAIrD;;GAEG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC;IACrC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;IACvB,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IACzB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;CACxB,CAAC,CAAC;AAIH;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,MAAoB,EACpB,OAA0B;IAE1B,MAAM,EAAE,GAAG,iBAAiB,EAAE,CAAC;IAE/B,MAAM,cAAc,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACxE,MAAM,cAAc,GAAG,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEvD,MAAM,MAAM,GAAG;;;iBAGA,OAAO,CAAC,UAAU;YACvB,cAAc,IAAI,UAAU;YAC5B,cAAc,IAAI,UAAU;;mBAErB,MAAM,CAAC,KAAK;;;0BAGL,MAAM,CAAC,KAAK;;;;;;;;;;;;;;;;;wBAiBd,CAAC;IAEvB,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC,MAAM,CAAC,eAAe,CAAC;QAC/C,KAAK,EAAE,wBAAwB;QAC/B,QAAQ,EAAE,MAAM;KACjB,CAAC,CAAC;IAEH,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAEzC,iEAAiE;IACjE,IAAI,QAAQ,GAAG,IAAI,CAAC;IACpB,IAAI,QAAQ,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/B,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAC3E,CAAC;IAED,8BAA8B;IAC9B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAClC,OAAO,cAAc,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AACpC,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { BlogPlan } from './planner.js';
|
|
2
|
+
import type { SeshatConfig } from '../config.js';
|
|
3
|
+
/**
|
|
4
|
+
* Use Gemini to write a complete MDX blog post
|
|
5
|
+
*/
|
|
6
|
+
export declare function writeBlogPost(config: SeshatConfig, plan: BlogPlan): Promise<string>;
|
|
7
|
+
//# sourceMappingURL=writer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"writer.d.ts","sourceRoot":"","sources":["../../src/core/writer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD;;GAEG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,YAAY,EACpB,IAAI,EAAE,QAAQ,GACb,OAAO,CAAC,MAAM,CAAC,CAgEjB"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { createGenAIClient } from '../lib/gemini.js';
|
|
2
|
+
/**
|
|
3
|
+
* Use Gemini to write a complete MDX blog post
|
|
4
|
+
*/
|
|
5
|
+
export async function writeBlogPost(config, plan) {
|
|
6
|
+
const ai = createGenAIClient();
|
|
7
|
+
// Get current date in ISO format
|
|
8
|
+
const currentDate = new Date().toISOString();
|
|
9
|
+
// Pick the first tag from the plan
|
|
10
|
+
const primaryTag = plan.tags[0] || 'general';
|
|
11
|
+
const frontmatterFormat = getFrontmatterFormat(config.framework, plan.title, plan.description, currentDate, primaryTag);
|
|
12
|
+
const prompt = `Write a technical blog post in MDX format. Write like Josh W Comeau or Kent C Dodds - clear, valuable, and focused on helping readers understand.
|
|
13
|
+
|
|
14
|
+
**Post Details:**
|
|
15
|
+
- Title: ${plan.title}
|
|
16
|
+
- Description: ${plan.description}
|
|
17
|
+
- Tag: ${primaryTag}
|
|
18
|
+
|
|
19
|
+
**Writing Style:**
|
|
20
|
+
- Clean, straightforward prose focused on delivering value
|
|
21
|
+
- Explain concepts clearly without over-explaining
|
|
22
|
+
- Use first-person when sharing experiences ("I found", "I learned")
|
|
23
|
+
- No fluff, filler, or unnecessary introductions
|
|
24
|
+
- Get to the point quickly
|
|
25
|
+
- Use examples and code to clarify concepts
|
|
26
|
+
- Write like you're helping a colleague understand something
|
|
27
|
+
|
|
28
|
+
**Structure:**
|
|
29
|
+
- Start with why this matters (1-2 sentences)
|
|
30
|
+
- Then dive into the actual content
|
|
31
|
+
- Use clear headings to organize ideas
|
|
32
|
+
- Include practical code examples where helpful
|
|
33
|
+
- Vary your structure - don't follow a template
|
|
34
|
+
|
|
35
|
+
**Technical Quality:**
|
|
36
|
+
- Be accurate and specific
|
|
37
|
+
- Show real code, not pseudo-code
|
|
38
|
+
- Explain the "why" behind decisions
|
|
39
|
+
- Include edge cases or gotchas when relevant
|
|
40
|
+
|
|
41
|
+
**Tone: ${config.tone}**
|
|
42
|
+
|
|
43
|
+
**Format Requirements:**
|
|
44
|
+
Use EXACTLY this frontmatter:
|
|
45
|
+
|
|
46
|
+
${frontmatterFormat}
|
|
47
|
+
|
|
48
|
+
After the frontmatter, start your content immediately. No import statements.
|
|
49
|
+
|
|
50
|
+
Write a focused, valuable post that helps developers understand ${plan.title}.`;
|
|
51
|
+
const response = await ai.models.generateContent({
|
|
52
|
+
model: 'gemini-3-flash-preview',
|
|
53
|
+
contents: prompt,
|
|
54
|
+
});
|
|
55
|
+
let content = response.text?.trim() || '';
|
|
56
|
+
// Clean up: remove wrapping code blocks if Gemini added them
|
|
57
|
+
if (content.startsWith('```')) {
|
|
58
|
+
content = content.replace(/^```(?:mdx|markdown|md)?\n/, '').replace(/\n```$/, '');
|
|
59
|
+
}
|
|
60
|
+
return content;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Get the appropriate frontmatter format for the framework
|
|
64
|
+
*/
|
|
65
|
+
function getFrontmatterFormat(framework, title, description, date, tag) {
|
|
66
|
+
// Format description for multiline YAML (>-)
|
|
67
|
+
const descLines = description.split('\n').map(line => line.trim()).filter(Boolean);
|
|
68
|
+
const formattedDesc = descLines.length > 0
|
|
69
|
+
? `>-\n ${descLines.join('\n ')}`
|
|
70
|
+
: `>-\n ${description}`;
|
|
71
|
+
const formats = {
|
|
72
|
+
remix: `---
|
|
73
|
+
title: "${title}"
|
|
74
|
+
description: ${formattedDesc}
|
|
75
|
+
date: ${date}
|
|
76
|
+
published: true
|
|
77
|
+
tag: ${tag}
|
|
78
|
+
image: "PLACEHOLDER_IMAGE"
|
|
79
|
+
---`,
|
|
80
|
+
next: `---
|
|
81
|
+
title: "${title}"
|
|
82
|
+
description: ${formattedDesc}
|
|
83
|
+
date: ${date}
|
|
84
|
+
published: true
|
|
85
|
+
tag: ${tag}
|
|
86
|
+
coverImage: "PLACEHOLDER_IMAGE"
|
|
87
|
+
---`,
|
|
88
|
+
astro: `---
|
|
89
|
+
title: "${title}"
|
|
90
|
+
description: ${formattedDesc}
|
|
91
|
+
pubDate: ${date}
|
|
92
|
+
published: true
|
|
93
|
+
tag: ${tag}
|
|
94
|
+
heroImage: "PLACEHOLDER_IMAGE"
|
|
95
|
+
---`,
|
|
96
|
+
};
|
|
97
|
+
return formats[framework];
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=writer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"writer.js","sourceRoot":"","sources":["../../src/core/writer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAIrD;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,MAAoB,EACpB,IAAc;IAEd,MAAM,EAAE,GAAG,iBAAiB,EAAE,CAAC;IAE/B,iCAAiC;IACjC,MAAM,WAAW,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAE7C,mCAAmC;IACnC,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC;IAE7C,MAAM,iBAAiB,GAAG,oBAAoB,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,WAAW,EAAE,UAAU,CAAC,CAAC;IAExH,MAAM,MAAM,GAAG;;;WAGN,IAAI,CAAC,KAAK;iBACJ,IAAI,CAAC,WAAW;SACxB,UAAU;;;;;;;;;;;;;;;;;;;;;;;;UAwBT,MAAM,CAAC,IAAI;;;;;EAKnB,iBAAiB;;;;kEAI+C,IAAI,CAAC,KAAK,GAAG,CAAC;IAE9E,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC,MAAM,CAAC,eAAe,CAAC;QAC/C,KAAK,EAAE,wBAAwB;QAC/B,QAAQ,EAAE,MAAM;KACjB,CAAC,CAAC;IAEH,IAAI,OAAO,GAAG,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAE1C,6DAA6D;IAC7D,IAAI,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9B,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,4BAA4B,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IACpF,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;GAEG;AACH,SAAS,oBAAoB,CAC3B,SAAoC,EACpC,KAAa,EACb,WAAmB,EACnB,IAAY,EACZ,GAAW;IAEX,6CAA6C;IAC7C,MAAM,SAAS,GAAG,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACnF,MAAM,aAAa,GAAG,SAAS,CAAC,MAAM,GAAG,CAAC;QACxC,CAAC,CAAC,SAAS,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE;QACnC,CAAC,CAAC,SAAS,WAAW,EAAE,CAAC;IAE3B,MAAM,OAAO,GAA8C;QACzD,KAAK,EAAE;UACD,KAAK;eACA,aAAa;QACpB,IAAI;;OAEL,GAAG;;IAEN;QACA,IAAI,EAAE;UACA,KAAK;eACA,aAAa;QACpB,IAAI;;OAEL,GAAG;;IAEN;QACA,KAAK,EAAE;UACD,KAAK;eACA,aAAa;WACjB,IAAI;;OAER,GAAG;;IAEN;KACD,CAAC;IAEF,OAAO,OAAO,CAAC,SAAS,CAAC,CAAC;AAC5B,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { printHeader } from './lib/output.js';
|
|
4
|
+
import { init } from './commands/init.js';
|
|
5
|
+
import { generate } from './commands/generate.js';
|
|
6
|
+
const program = new Command();
|
|
7
|
+
// Print the Seshat header
|
|
8
|
+
printHeader();
|
|
9
|
+
program
|
|
10
|
+
.name('seshat')
|
|
11
|
+
.description('The Autonomous Scribe for Static Blogs')
|
|
12
|
+
.version('1.0.0');
|
|
13
|
+
program
|
|
14
|
+
.command('init')
|
|
15
|
+
.description('Initialize Seshat configuration in the current directory')
|
|
16
|
+
.action(async () => {
|
|
17
|
+
try {
|
|
18
|
+
await init();
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
console.error('Error:', err instanceof Error ? err.message : 'Unknown error');
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
program
|
|
26
|
+
.command('write')
|
|
27
|
+
.description('Generate a new blog post with AI')
|
|
28
|
+
.option('--dry-run', 'Preview the content without saving files')
|
|
29
|
+
.action(async (options) => {
|
|
30
|
+
try {
|
|
31
|
+
await generate(options);
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
console.error('Error:', err instanceof Error ? err.message : 'Unknown error');
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
program.parse();
|
|
39
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAElD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,0BAA0B;AAC1B,WAAW,EAAE,CAAC;AAEd,OAAO;KACJ,IAAI,CAAC,QAAQ,CAAC;KACd,WAAW,CAAC,wCAAwC,CAAC;KACrD,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO;KACJ,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,0DAA0D,CAAC;KACvE,MAAM,CAAC,KAAK,IAAI,EAAE;IACjB,IAAI,CAAC;QACH,MAAM,IAAI,EAAE,CAAC;IACf,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC;QAC9E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,kCAAkC,CAAC;KAC/C,MAAM,CAAC,WAAW,EAAE,0CAA0C,CAAC;KAC/D,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACxB,IAAI,CAAC;QACH,MAAM,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC1B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC;QAC9E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,OAAO,CAAC,KAAK,EAAE,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { GoogleGenAI, SafetyFilterLevel, PersonGeneration } from '@google/genai';
|
|
2
|
+
/**
|
|
3
|
+
* Get or create a GenAI instance
|
|
4
|
+
*/
|
|
5
|
+
export declare function createGenAIClient(): GoogleGenAI;
|
|
6
|
+
/**
|
|
7
|
+
* Export safety and generation enums for image generation
|
|
8
|
+
*/
|
|
9
|
+
export { SafetyFilterLevel, PersonGeneration };
|
|
10
|
+
//# sourceMappingURL=gemini.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gemini.d.ts","sourceRoot":"","sources":["../../src/lib/gemini.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AASjF;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,WAAW,CAe/C;AAED;;GAEG;AACH,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { GoogleGenAI, SafetyFilterLevel, PersonGeneration } from '@google/genai';
|
|
2
|
+
import dotenv from 'dotenv';
|
|
3
|
+
// Load environment variables
|
|
4
|
+
dotenv.config();
|
|
5
|
+
// Singleton instance cache
|
|
6
|
+
let geminiInstance = null;
|
|
7
|
+
/**
|
|
8
|
+
* Get or create a GenAI instance
|
|
9
|
+
*/
|
|
10
|
+
export function createGenAIClient() {
|
|
11
|
+
const apiKey = process.env.GEMINI_API_KEY;
|
|
12
|
+
if (!apiKey) {
|
|
13
|
+
throw new Error('GEMINI_API_KEY environment variable is not set. ' +
|
|
14
|
+
'Get your API key at: https://aistudio.google.com/app/apikey');
|
|
15
|
+
}
|
|
16
|
+
if (!geminiInstance) {
|
|
17
|
+
geminiInstance = new GoogleGenAI({ apiKey });
|
|
18
|
+
}
|
|
19
|
+
return geminiInstance;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Export safety and generation enums for image generation
|
|
23
|
+
*/
|
|
24
|
+
export { SafetyFilterLevel, PersonGeneration };
|
|
25
|
+
//# sourceMappingURL=gemini.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gemini.js","sourceRoot":"","sources":["../../src/lib/gemini.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjF,OAAO,MAAM,MAAM,QAAQ,CAAC;AAE5B,6BAA6B;AAC7B,MAAM,CAAC,MAAM,EAAE,CAAC;AAEhB,2BAA2B;AAC3B,IAAI,cAAc,GAAuB,IAAI,CAAC;AAE9C;;GAEG;AACH,MAAM,UAAU,iBAAiB;IAC/B,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;IAE1C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CACb,kDAAkD;YAClD,6DAA6D,CAC9D,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,cAAc,GAAG,IAAI,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;IAC/C,CAAC;IAED,OAAO,cAAc,CAAC;AACxB,CAAC;AAED;;GAEG;AACH,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seshat-themed console output utilities
|
|
3
|
+
* Colors: Gold (#FFD700) and Cyan (#00FFFF)
|
|
4
|
+
*/
|
|
5
|
+
export declare const gold: import("chalk").ChalkInstance;
|
|
6
|
+
export declare const cyan: import("chalk").ChalkInstance;
|
|
7
|
+
export declare const dim: import("chalk").ChalkInstance;
|
|
8
|
+
export declare const bold: import("chalk").ChalkInstance;
|
|
9
|
+
/**
|
|
10
|
+
* Print the Seshat header
|
|
11
|
+
*/
|
|
12
|
+
export declare function printHeader(): void;
|
|
13
|
+
/**
|
|
14
|
+
* Success message
|
|
15
|
+
*/
|
|
16
|
+
export declare function success(message: string): void;
|
|
17
|
+
/**
|
|
18
|
+
* Info message
|
|
19
|
+
*/
|
|
20
|
+
export declare function info(message: string): void;
|
|
21
|
+
/**
|
|
22
|
+
* Error message
|
|
23
|
+
*/
|
|
24
|
+
export declare function error(message: string): void;
|
|
25
|
+
/**
|
|
26
|
+
* The sacred completion message
|
|
27
|
+
*/
|
|
28
|
+
export declare function scrollWritten(): void;
|
|
29
|
+
//# sourceMappingURL=output.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../../src/lib/output.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,eAAO,MAAM,IAAI,+BAAuB,CAAC;AACzC,eAAO,MAAM,IAAI,+BAAuB,CAAC;AACzC,eAAO,MAAM,GAAG,+BAAY,CAAC;AAC7B,eAAO,MAAM,IAAI,+BAAa,CAAC;AAE/B;;GAEG;AACH,wBAAgB,WAAW,IAAI,IAAI,CAKlC;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE7C;AAED;;GAEG;AACH,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE1C;AAED;;GAEG;AACH,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE3C;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,IAAI,CAMpC"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seshat-themed console output utilities
|
|
3
|
+
* Colors: Gold (#FFD700) and Cyan (#00FFFF)
|
|
4
|
+
*/
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
// Seshat color palette
|
|
7
|
+
export const gold = chalk.hex('#FFD700');
|
|
8
|
+
export const cyan = chalk.hex('#00FFFF');
|
|
9
|
+
export const dim = chalk.dim;
|
|
10
|
+
export const bold = chalk.bold;
|
|
11
|
+
/**
|
|
12
|
+
* Print the Seshat header
|
|
13
|
+
*/
|
|
14
|
+
export function printHeader() {
|
|
15
|
+
console.log('');
|
|
16
|
+
console.log(gold.bold(' ✦ Seshat 𓇋𓏏'));
|
|
17
|
+
console.log(cyan(' The Autonomous Scribe for Static Blogs'));
|
|
18
|
+
console.log('');
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Success message
|
|
22
|
+
*/
|
|
23
|
+
export function success(message) {
|
|
24
|
+
console.log(gold('✓ ') + message);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Info message
|
|
28
|
+
*/
|
|
29
|
+
export function info(message) {
|
|
30
|
+
console.log(cyan('ℹ ') + dim(message));
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Error message
|
|
34
|
+
*/
|
|
35
|
+
export function error(message) {
|
|
36
|
+
console.log(chalk.red('✗ ') + message);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The sacred completion message
|
|
40
|
+
*/
|
|
41
|
+
export function scrollWritten() {
|
|
42
|
+
console.log('');
|
|
43
|
+
console.log(gold.bold(' ⟡ The scroll is written.'));
|
|
44
|
+
console.log(cyan(' ⟡ The archives are updated.'));
|
|
45
|
+
console.log('');
|
|
46
|
+
console.log(''); // Extra newline before terminal prompt
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=output.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output.js","sourceRoot":"","sources":["../../src/lib/output.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,uBAAuB;AACvB,MAAM,CAAC,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;AACzC,MAAM,CAAC,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;AACzC,MAAM,CAAC,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC;AAC7B,MAAM,CAAC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;AAE/B;;GAEG;AACH,MAAM,UAAU,WAAW;IACzB,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC;IAC1C,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,0CAA0C,CAAC,CAAC,CAAC;IAC9D,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AAClB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,OAAO,CAAC,OAAe;IACrC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC;AACpC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,IAAI,CAAC,OAAe;IAClC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;AACzC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,KAAK,CAAC,OAAe;IACnC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC;AACzC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa;IAC3B,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC,CAAC;IACrD,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,+BAA+B,CAAC,CAAC,CAAC;IACnD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,uCAAuC;AAC1D,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "seshat-scribe",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "The Autonomous Scribe for Static Blogs - AI-powered content generation using Google Gemini",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"seshat": "./bin/seshat.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"dev": "tsx src/index.ts",
|
|
13
|
+
"prepare": "npm run build",
|
|
14
|
+
"test": "echo \"No tests yet\" && exit 0"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"blog",
|
|
18
|
+
"ai",
|
|
19
|
+
"gemini",
|
|
20
|
+
"automation",
|
|
21
|
+
"mdx",
|
|
22
|
+
"content-generation",
|
|
23
|
+
"cli",
|
|
24
|
+
"static-site",
|
|
25
|
+
"remix",
|
|
26
|
+
"nextjs",
|
|
27
|
+
"astro"
|
|
28
|
+
],
|
|
29
|
+
"author": "Loke",
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=20.0.0"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist",
|
|
35
|
+
"bin",
|
|
36
|
+
"templates",
|
|
37
|
+
"README.md",
|
|
38
|
+
"LICENSE"
|
|
39
|
+
],
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@google/genai": "^1.37.0",
|
|
42
|
+
"chalk": "^5.3.0",
|
|
43
|
+
"commander": "^12.1.0",
|
|
44
|
+
"dotenv": "^16.4.7",
|
|
45
|
+
"gray-matter": "^4.0.3",
|
|
46
|
+
"ora": "^8.1.1",
|
|
47
|
+
"zod": "^3.24.1"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/node": "^22.10.5",
|
|
51
|
+
"ts-node": "^10.9.2",
|
|
52
|
+
"tsx": "^4.19.2",
|
|
53
|
+
"typescript": "^5.7.3"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
name: Seshat Scribe
|
|
2
|
+
on:
|
|
3
|
+
schedule:
|
|
4
|
+
# Every Monday at 9:00 AM UTC
|
|
5
|
+
- cron: '0 9 * * 1'
|
|
6
|
+
workflow_dispatch: # Allow manual triggering
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
write:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- name: Checkout repository
|
|
17
|
+
uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Setup Node.js
|
|
20
|
+
uses: actions/setup-node@v4
|
|
21
|
+
with:
|
|
22
|
+
node-version: '20'
|
|
23
|
+
cache: 'npm'
|
|
24
|
+
|
|
25
|
+
- name: Install dependencies
|
|
26
|
+
run: npm ci
|
|
27
|
+
|
|
28
|
+
- name: Summon Seshat
|
|
29
|
+
env:
|
|
30
|
+
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
|
31
|
+
run: npx seshat write
|
|
32
|
+
|
|
33
|
+
- name: Commit and push changes
|
|
34
|
+
run: |
|
|
35
|
+
git config user.name "Seshat Bot"
|
|
36
|
+
git config user.email "seshat@bot.com"
|
|
37
|
+
git add .
|
|
38
|
+
git commit -m "✦ chronicler: new entry added by Seshat" || echo "No changes to commit"
|
|
39
|
+
git push
|