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.
Files changed (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +194 -0
  3. package/bin/seshat.js +15 -0
  4. package/dist/commands/generate.d.ts +8 -0
  5. package/dist/commands/generate.d.ts.map +1 -0
  6. package/dist/commands/generate.js +126 -0
  7. package/dist/commands/generate.js.map +1 -0
  8. package/dist/commands/init.d.ts +5 -0
  9. package/dist/commands/init.d.ts.map +1 -0
  10. package/dist/commands/init.js +28 -0
  11. package/dist/commands/init.js.map +1 -0
  12. package/dist/config.d.ts +36 -0
  13. package/dist/config.d.ts.map +1 -0
  14. package/dist/config.js +33 -0
  15. package/dist/config.js.map +1 -0
  16. package/dist/core/analyzer.d.ts +28 -0
  17. package/dist/core/analyzer.d.ts.map +1 -0
  18. package/dist/core/analyzer.js +91 -0
  19. package/dist/core/analyzer.js.map +1 -0
  20. package/dist/core/artist.d.ts +6 -0
  21. package/dist/core/artist.d.ts.map +1 -0
  22. package/dist/core/artist.js +81 -0
  23. package/dist/core/artist.js.map +1 -0
  24. package/dist/core/planner.d.ts +31 -0
  25. package/dist/core/planner.d.ts.map +1 -0
  26. package/dist/core/planner.js +62 -0
  27. package/dist/core/planner.js.map +1 -0
  28. package/dist/core/writer.d.ts +7 -0
  29. package/dist/core/writer.d.ts.map +1 -0
  30. package/dist/core/writer.js +99 -0
  31. package/dist/core/writer.js.map +1 -0
  32. package/dist/index.d.ts +3 -0
  33. package/dist/index.d.ts.map +1 -0
  34. package/dist/index.js +39 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/lib/gemini.d.ts +10 -0
  37. package/dist/lib/gemini.d.ts.map +1 -0
  38. package/dist/lib/gemini.js +25 -0
  39. package/dist/lib/gemini.js.map +1 -0
  40. package/dist/lib/output.d.ts +29 -0
  41. package/dist/lib/output.d.ts.map +1 -0
  42. package/dist/lib/output.js +48 -0
  43. package/dist/lib/output.js.map +1 -0
  44. package/package.json +55 -0
  45. 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,8 @@
1
+ export interface GenerateOptions {
2
+ dryRun?: boolean;
3
+ }
4
+ /**
5
+ * Main command: Generate a new blog post with image
6
+ */
7
+ export declare function generate(options?: GenerateOptions): Promise<void>;
8
+ //# sourceMappingURL=generate.d.ts.map
@@ -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,5 @@
1
+ /**
2
+ * Initialize Seshat configuration in the current directory
3
+ */
4
+ export declare function init(): Promise<void>;
5
+ //# sourceMappingURL=init.d.ts.map
@@ -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"}
@@ -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,6 @@
1
+ import type { SeshatConfig } from '../config.js';
2
+ /**
3
+ * Generate an image using Gemini's Imagen 4.0 model
4
+ */
5
+ export declare function generateImage(config: SeshatConfig, imagePrompt: string, slug: string): Promise<string>;
6
+ //# sourceMappingURL=artist.d.ts.map
@@ -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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -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