autoblogger 0.1.1 → 0.1.3

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/README.md CHANGED
@@ -1,164 +1,529 @@
1
1
  # Autoblogger
2
2
 
3
- A headless CMS with AI writing tools, WYSIWYG editor, and RSS auto-draft for Next.js.
3
+ [![npm version](https://img.shields.io/npm/v/autoblogger.svg)](https://www.npmjs.com/package/autoblogger)
4
+ [![license](https://img.shields.io/npm/l/autoblogger.svg)](https://github.com/hrosenblume/autoblogger/blob/main/LICENSE)
5
+
6
+ A complete content management system that embeds into your Next.js app. Write blog posts with AI assistance, manage revisions, handle comments, and auto-generate drafts from RSS feeds—all from a dashboard that lives inside your existing application.
7
+
8
+ ```bash
9
+ npm install autoblogger
10
+ ```
11
+
12
+ ---
13
+
14
+ ## What is Autoblogger?
15
+
16
+ Autoblogger is an **embeddable CMS package**. Instead of running a separate blog platform like WordPress or Ghost, you install Autoblogger as an npm package and mount it inside your Next.js app. It becomes part of your application—using your database, your auth system, and your hosting.
17
+
18
+ **This is not a standalone application.** It's a library that provides:
19
+
20
+ 1. **A React dashboard** — A full writer/admin interface you mount at a route like `/writer`
21
+ 2. **API handlers** — RESTful endpoints you mount at a route like `/api/cms`
22
+ 3. **Data utilities** — Functions to query posts, render markdown, generate SEO metadata
23
+
24
+ You keep full control. Autoblogger uses your Prisma client, respects your auth, and stores everything in your database.
25
+
26
+ ---
27
+
28
+ ## Who is this for?
29
+
30
+ - **Developers building blogs** who want a writing dashboard without building one from scratch
31
+ - **Teams** who need collaborative editing with comments and revision history
32
+ - **AI-assisted writers** who want to generate drafts with Claude or GPT
33
+ - **Content aggregators** who want to auto-draft posts from RSS feeds
34
+
35
+ ---
4
36
 
5
37
  ## Features
6
38
 
7
- - **AI Writing** - Generate and edit essays with Claude or GPT
8
- - **WYSIWYG Editor** - Tiptap-based editor with markdown support
9
- - **RSS Auto-Draft** - Subscribe to feeds and auto-generate drafts
10
- - **Custom Fields** - Extend posts with site-specific fields
11
- - **WYSIWYG Parity** - Editor matches your public page styling
39
+ | Feature | Description |
40
+ |---------|-------------|
41
+ | **AI Writing** | Generate essays with Claude or GPT. Stream responses in real-time. Chat to refine your content. |
42
+ | **WYSIWYG Editor** | Tiptap-based editor with formatting toolbar. Syncs to markdown for storage. |
43
+ | **Revision History** | Every save creates a revision. Browse and restore any previous version. |
44
+ | **Inline Comments** | Highlight text and leave comments. Reply in threads. Resolve when done. |
45
+ | **RSS Auto-Draft** | Subscribe to RSS feeds. Filter articles by keywords. Auto-generate draft posts from news. |
46
+ | **Tag Management** | Organize posts with tags. Bulk edit from the settings panel. |
47
+ | **User Roles** | Admin, writer, and drafter roles with different permissions. |
48
+ | **SEO Fields** | Custom title, description, keywords, and OG image per post. |
49
+ | **Preview Links** | Generate expiring preview URLs for unpublished drafts. |
50
+
51
+ ---
52
+
53
+ ## Requirements
54
+
55
+ Before installing, make sure you have:
56
+
57
+ - **Next.js 14 or 15** (App Router)
58
+ - **React 18 or 19**
59
+ - **Prisma 5 or 6** with a configured database
60
+ - **Node.js 20+**
61
+
62
+ You'll also need API keys if you want AI features:
63
+
64
+ - **Anthropic API key** for Claude models
65
+ - **OpenAI API key** for GPT models
66
+
67
+ ---
12
68
 
13
69
  ## Installation
14
70
 
71
+ ### Step 1: Install the package
72
+
15
73
  ```bash
16
74
  npm install autoblogger
17
75
  ```
18
76
 
19
- ## Quick Start
77
+ This installs Autoblogger and its dependencies (Tiptap editor, AI SDKs, markdown utilities).
78
+
79
+ ### Step 2: Add the database models
80
+
81
+ Autoblogger needs several tables in your database. Copy the models from the package's schema file into your own Prisma schema.
82
+
83
+ **Option A: Copy the file and merge manually**
84
+
85
+ ```bash
86
+ # View the schema
87
+ cat node_modules/autoblogger/prisma/schema.prisma
88
+ ```
89
+
90
+ Then copy the models (Post, Revision, Comment, User, Tag, etc.) into your `prisma/schema.prisma`.
91
+
92
+ **Option B: If starting fresh, use it directly**
93
+
94
+ ```bash
95
+ cp node_modules/autoblogger/prisma/schema.prisma ./prisma/schema.prisma
96
+ ```
97
+
98
+ The required models are:
99
+
100
+ | Model | Purpose |
101
+ |-------|---------|
102
+ | `Post` | Blog posts with title, markdown content, status, SEO fields |
103
+ | `Revision` | Version history for posts |
104
+ | `Comment` | Inline editor comments with threading |
105
+ | `User` | CMS users with roles (admin, writer, drafter) |
106
+ | `Tag` | Tags for organizing posts |
107
+ | `PostTag` | Many-to-many relation between posts and tags |
108
+ | `AISettings` | AI model preferences and prompt templates |
109
+ | `IntegrationSettings` | Feature flags like auto-draft enabled |
110
+ | `TopicSubscription` | RSS feed subscriptions for auto-drafting |
111
+ | `NewsItem` | Individual RSS items fetched from subscriptions |
112
+
113
+ ### Step 3: Run the migration
20
114
 
21
- ### 1. Copy and merge schema
115
+ After adding the models to your schema:
22
116
 
23
117
  ```bash
24
- cp node_modules/autoblogger/prisma/schema.prisma ./prisma/
25
118
  npx prisma migrate dev --name add-autoblogger
26
119
  ```
27
120
 
28
- ### 2. Configure CMS
121
+ This creates the tables in your database.
122
+
123
+ ### Step 4: Generate the Prisma client
124
+
125
+ ```bash
126
+ npx prisma generate
127
+ ```
128
+
129
+ ---
130
+
131
+ ## Configuration
132
+
133
+ Create a configuration file that sets up Autoblogger with your app's Prisma client and auth:
29
134
 
30
135
  ```typescript
31
136
  // lib/cms.ts
32
137
  import { createAutoblogger } from 'autoblogger'
33
- import { auth } from '@/lib/auth'
34
- import { prisma } from '@/lib/db'
138
+ import { prisma } from '@/lib/db' // Your Prisma client
139
+ import { auth } from '@/lib/auth' // Your auth function (e.g., NextAuth)
35
140
 
36
141
  export const cms = createAutoblogger({
142
+ // Required: Your Prisma client instance
37
143
  prisma,
38
144
 
145
+ // Required: Authentication configuration
39
146
  auth: {
40
- getSession: auth,
147
+ // Function that returns the current session/user
148
+ getSession: () => auth(),
149
+
150
+ // Check if user is an admin (can access settings, manage users)
41
151
  isAdmin: (session) => session?.user?.role === 'admin',
152
+
153
+ // Check if user can publish posts (admins and writers can, drafters can't)
42
154
  canPublish: (session) => ['admin', 'writer'].includes(session?.user?.role ?? ''),
43
155
  },
44
156
 
157
+ // Optional: AI configuration
45
158
  ai: {
46
159
  anthropicKey: process.env.ANTHROPIC_API_KEY,
47
160
  openaiKey: process.env.OPENAI_API_KEY,
48
161
  },
49
162
 
163
+ // Optional: File upload handler
50
164
  storage: {
51
- upload: async (file) => {
52
- // Your upload implementation
53
- return { url: 'https://...' }
165
+ upload: async (file: File) => {
166
+ // Implement your upload logic here (S3, Cloudflare R2, Vercel Blob, etc.)
167
+ // Return an object with the public URL
168
+ const url = await uploadToYourStorage(file)
169
+ return { url }
54
170
  }
55
171
  },
56
-
57
- styles: {
58
- container: 'max-w-2xl mx-auto px-6',
59
- title: 'text-2xl font-bold',
60
- prose: 'prose dark:prose-invert max-w-none',
61
- },
62
172
  })
173
+ ```
174
+
175
+ ### Environment Variables
176
+
177
+ Add these to your `.env.local`:
178
+
179
+ ```bash
180
+ # Database (you probably already have this)
181
+ DATABASE_URL="postgresql://..."
63
182
 
64
- export const cmsStyles = cms.config.styles
183
+ # AI API keys (optional, only needed for AI features)
184
+ ANTHROPIC_API_KEY="sk-ant-..."
185
+ OPENAI_API_KEY="sk-..."
65
186
  ```
66
187
 
67
- ### 3. Mount API
188
+ ---
189
+
190
+ ## Mounting the API
191
+
192
+ Autoblogger needs API routes to handle requests from the dashboard. Create a catch-all route:
68
193
 
69
194
  ```typescript
70
195
  // app/api/cms/[...path]/route.ts
71
196
  import { cms } from '@/lib/cms'
72
- import { createAPIHandler } from 'autoblogger'
73
-
74
- const handler = createAPIHandler(cms, { basePath: '/api/cms' })
197
+ import { NextRequest } from 'next/server'
198
+
199
+ async function handler(
200
+ req: NextRequest,
201
+ { params }: { params: Promise<{ path: string[] }> }
202
+ ) {
203
+ const { path } = await params
204
+ return cms.handleRequest(req, path.join('/'))
205
+ }
75
206
 
76
- export const GET = handler
77
- export const POST = handler
78
- export const PATCH = handler
79
- export const DELETE = handler
207
+ export { handler as GET, handler as POST, handler as PATCH, handler as DELETE }
80
208
  ```
81
209
 
82
- ### 4. Mount Dashboard
210
+ This single file handles all CMS API routes:
211
+
212
+ | Route | Purpose |
213
+ |-------|---------|
214
+ | `GET /api/cms/posts` | List all posts |
215
+ | `POST /api/cms/posts` | Create a new post |
216
+ | `PATCH /api/cms/posts/:id` | Update a post |
217
+ | `DELETE /api/cms/posts/:id` | Delete a post |
218
+ | `GET /api/cms/revisions` | List revisions |
219
+ | `POST /api/cms/revisions/:id/restore` | Restore a revision |
220
+ | `GET /api/cms/comments` | List comments |
221
+ | `POST /api/cms/ai/generate` | Generate essay with AI |
222
+ | `POST /api/cms/ai/chat` | Chat with AI (streaming) |
223
+ | ... | And more |
224
+
225
+ ---
226
+
227
+ ## Mounting the Dashboard
228
+
229
+ The dashboard is a React component that renders the full CMS interface. Mount it at a route in your app:
83
230
 
84
231
  ```typescript
85
232
  // app/writer/[[...path]]/page.tsx
86
- 'use client'
87
-
88
233
  import { AutobloggerDashboard } from 'autoblogger/ui'
89
- import { cmsStyles } from '@/lib/cms'
90
-
91
- export default function WriterPage() {
234
+ import { auth } from '@/lib/auth'
235
+ import { redirect } from 'next/navigation'
236
+
237
+ export default async function WriterPage({
238
+ params
239
+ }: {
240
+ params: Promise<{ path?: string[] }>
241
+ }) {
242
+ // Protect this route - only authenticated users
243
+ const session = await auth()
244
+ if (!session) {
245
+ redirect('/login')
246
+ }
247
+
248
+ const { path } = await params
249
+
92
250
  return (
93
251
  <AutobloggerDashboard
94
- basePath="/writer"
95
252
  apiBasePath="/api/cms"
96
- styles={cmsStyles}
253
+ session={session}
254
+ path={path?.join('/') || ''}
97
255
  />
98
256
  )
99
257
  }
100
258
  ```
101
259
 
102
- ### 5. Add Tailwind preset
260
+ The `[[...path]]` syntax is a catch-all route that captures the dashboard's internal navigation:
261
+
262
+ | URL | Dashboard Page |
263
+ |-----|----------------|
264
+ | `/writer` | Post list (drafts, published, suggested) |
265
+ | `/writer/editor/my-post-slug` | Edit a specific post |
266
+ | `/writer/settings` | Settings overview |
267
+ | `/writer/settings/ai` | AI model and prompt configuration |
268
+ | `/writer/settings/users` | User management |
269
+ | `/writer/settings/tags` | Tag management |
270
+ | `/writer/settings/topics` | RSS topic subscriptions |
271
+
272
+ ---
273
+
274
+ ## Configuring Tailwind
275
+
276
+ Autoblogger's UI uses Tailwind CSS classes. Add the package's files to your Tailwind content configuration so the classes aren't purged:
103
277
 
104
278
  ```javascript
105
- // tailwind.config.js
279
+ // tailwind.config.js (or tailwind.config.ts)
106
280
  module.exports = {
107
- presets: [require('autoblogger/styles/preset')],
108
281
  content: [
109
282
  './app/**/*.{js,ts,jsx,tsx}',
110
- './node_modules/autoblogger/dist/**/*.{js,jsx}',
283
+ './components/**/*.{js,ts,jsx,tsx}',
284
+ // Add this line to include Autoblogger's components
285
+ './node_modules/autoblogger/dist/**/*.{js,mjs}',
111
286
  ],
287
+ // ... rest of your config
112
288
  }
113
289
  ```
114
290
 
115
- ### 6. Use in public pages
291
+ The dashboard uses semantic color tokens like `bg-card`, `text-muted-foreground`, `border-border`, etc. These come from shadcn/ui conventions. If you're using shadcn, they'll work automatically. Otherwise, add these CSS variables to your globals:
292
+
293
+ ```css
294
+ /* globals.css */
295
+ :root {
296
+ --background: 0 0% 100%;
297
+ --foreground: 0 0% 3.9%;
298
+ --card: 0 0% 100%;
299
+ --card-foreground: 0 0% 3.9%;
300
+ --popover: 0 0% 100%;
301
+ --popover-foreground: 0 0% 3.9%;
302
+ --primary: 0 0% 9%;
303
+ --primary-foreground: 0 0% 98%;
304
+ --secondary: 0 0% 96.1%;
305
+ --secondary-foreground: 0 0% 9%;
306
+ --muted: 0 0% 96.1%;
307
+ --muted-foreground: 0 0% 45.1%;
308
+ --accent: 0 0% 96.1%;
309
+ --accent-foreground: 0 0% 9%;
310
+ --destructive: 0 84.2% 60.2%;
311
+ --destructive-foreground: 0 0% 98%;
312
+ --border: 0 0% 89.8%;
313
+ --input: 0 0% 89.8%;
314
+ --ring: 0 0% 3.9%;
315
+ }
316
+
317
+ .dark {
318
+ --background: 0 0% 3.9%;
319
+ --foreground: 0 0% 98%;
320
+ --card: 0 0% 3.9%;
321
+ --card-foreground: 0 0% 98%;
322
+ --popover: 0 0% 3.9%;
323
+ --popover-foreground: 0 0% 98%;
324
+ --primary: 0 0% 98%;
325
+ --primary-foreground: 0 0% 9%;
326
+ --secondary: 0 0% 14.9%;
327
+ --secondary-foreground: 0 0% 98%;
328
+ --muted: 0 0% 14.9%;
329
+ --muted-foreground: 0 0% 63.9%;
330
+ --accent: 0 0% 14.9%;
331
+ --accent-foreground: 0 0% 98%;
332
+ --destructive: 0 62.8% 30.6%;
333
+ --destructive-foreground: 0 0% 98%;
334
+ --border: 0 0% 14.9%;
335
+ --input: 0 0% 14.9%;
336
+ --ring: 0 0% 83.1%;
337
+ }
338
+ ```
339
+
340
+ ---
341
+
342
+ ## Displaying Posts on Your Site
343
+
344
+ Use the CMS data layer to fetch posts for your public pages:
116
345
 
117
346
  ```typescript
118
- // app/blog/[slug]/page.tsx
347
+ // app/blog/page.tsx
119
348
  import { cms } from '@/lib/cms'
120
- import { renderMarkdown, getSeoValues } from 'autoblogger'
349
+ import Link from 'next/link'
350
+
351
+ export default async function BlogPage() {
352
+ const { posts } = await cms.data.posts.findAll({
353
+ where: { status: 'published' },
354
+ orderBy: { publishedAt: 'desc' },
355
+ take: 20,
356
+ })
357
+
358
+ return (
359
+ <div>
360
+ <h1>Blog</h1>
361
+ <ul>
362
+ {posts.map(post => (
363
+ <li key={post.id}>
364
+ <Link href={`/blog/${post.slug}`}>
365
+ {post.title}
366
+ </Link>
367
+ </li>
368
+ ))}
369
+ </ul>
370
+ </div>
371
+ )
372
+ }
373
+ ```
374
+
375
+ ### Rendering a Single Post
121
376
 
122
- export default async function PostPage({ params }) {
123
- const post = await cms.posts.findBySlug(params.slug)
377
+ ```typescript
378
+ // app/blog/[slug]/page.tsx
379
+ import { cms } from '@/lib/cms'
380
+ import { renderMarkdown } from 'autoblogger/markdown'
381
+ import { generateSeoMetadata } from 'autoblogger/seo'
382
+ import { notFound } from 'next/navigation'
383
+
384
+ export default async function PostPage({
385
+ params
386
+ }: {
387
+ params: Promise<{ slug: string }>
388
+ }) {
389
+ const { slug } = await params
390
+ const post = await cms.data.posts.findBySlug(slug)
391
+
392
+ if (!post || post.status !== 'published') {
393
+ notFound()
394
+ }
395
+
396
+ const html = renderMarkdown(post.markdown)
124
397
 
125
398
  return (
126
399
  <article>
127
400
  <h1>{post.title}</h1>
128
- <div dangerouslySetInnerHTML={{ __html: renderMarkdown(post.markdown) }} />
401
+ {post.subtitle && <p className="text-xl text-gray-600">{post.subtitle}</p>}
402
+ <div
403
+ className="prose dark:prose-invert"
404
+ dangerouslySetInnerHTML={{ __html: html }}
405
+ />
129
406
  </article>
130
407
  )
131
408
  }
132
409
 
133
- export async function generateMetadata({ params }) {
134
- const post = await cms.posts.findBySlug(params.slug)
135
- const seo = getSeoValues(post)
136
- return { title: seo.title, description: seo.description }
410
+ // Generate SEO metadata
411
+ export async function generateMetadata({
412
+ params
413
+ }: {
414
+ params: Promise<{ slug: string }>
415
+ }) {
416
+ const { slug } = await params
417
+ const post = await cms.data.posts.findBySlug(slug)
418
+
419
+ if (!post) return {}
420
+
421
+ return generateSeoMetadata(post)
137
422
  }
138
423
  ```
139
424
 
140
- ## Custom Fields
425
+ ---
141
426
 
142
- Add site-specific fields to posts:
427
+ ## Package Exports
428
+
429
+ Autoblogger provides several entry points:
143
430
 
144
431
  ```typescript
145
- // app/writer/[[...path]]/page.tsx
432
+ // Main entry — server-side data layer and API handlers
433
+ import { createAutoblogger } from 'autoblogger'
434
+
435
+ // UI components — React dashboard (client-side)
146
436
  import { AutobloggerDashboard } from 'autoblogger/ui'
147
- import { MyCustomField } from '@/components/MyCustomField'
148
437
 
149
- export default function WriterPage() {
150
- return (
151
- <AutobloggerDashboard
152
- basePath="/writer"
153
- apiBasePath="/api/cms"
154
- fields={[
155
- { name: 'customData', label: 'Custom', component: MyCustomField, position: 'footer' }
156
- ]}
157
- />
158
- )
438
+ // Markdown utilities render markdown to HTML, parse HTML to markdown
439
+ import { renderMarkdown, parseMarkdown } from 'autoblogger/markdown'
440
+
441
+ // SEO utilities — generate meta tags from post data
442
+ import { generateSeoMetadata } from 'autoblogger/seo'
443
+
444
+ // Article styles CSS class helpers for consistent article layout
445
+ import { ARTICLE_STYLES } from 'autoblogger/styles/article'
446
+ ```
447
+
448
+ ---
449
+
450
+ ## AI Models
451
+
452
+ Autoblogger supports these AI models out of the box:
453
+
454
+ | ID | Name | Provider | Best For |
455
+ |----|------|----------|----------|
456
+ | `claude-sonnet` | Claude Sonnet 4.5 | Anthropic | Fast, balanced writing |
457
+ | `claude-opus` | Claude Opus 4.5 | Anthropic | Complex, nuanced essays |
458
+ | `gpt-5.2` | GPT-5.2 | OpenAI | General purpose |
459
+ | `gpt-5-mini` | GPT-5 Mini | OpenAI | Quick drafts |
460
+
461
+ Configure the default model and custom prompts in the dashboard under **Settings → AI**.
462
+
463
+ ---
464
+
465
+ ## User Roles
466
+
467
+ Autoblogger supports three roles:
468
+
469
+ | Role | Permissions |
470
+ |------|-------------|
471
+ | `admin` | Full access. Manage users, settings, publish/delete any post. |
472
+ | `writer` | Create posts, publish their own posts, edit drafts. |
473
+ | `drafter` | Create drafts only. Cannot publish. |
474
+
475
+ Roles are stored in the `User.role` field. Your auth configuration determines how roles are checked.
476
+
477
+ ---
478
+
479
+ ## Troubleshooting
480
+
481
+ ### "Cannot find module 'autoblogger/ui'"
482
+
483
+ Make sure you're importing from the correct path and that the package is installed:
484
+
485
+ ```bash
486
+ npm install autoblogger
487
+ ```
488
+
489
+ ### Tailwind classes not applying
490
+
491
+ Add the package to your Tailwind content config:
492
+
493
+ ```javascript
494
+ content: [
495
+ // ... your files
496
+ './node_modules/autoblogger/dist/**/*.{js,mjs}',
497
+ ]
498
+ ```
499
+
500
+ ### AI features not working
501
+
502
+ Check that your API keys are set in environment variables:
503
+
504
+ ```bash
505
+ ANTHROPIC_API_KEY="sk-ant-..."
506
+ OPENAI_API_KEY="sk-..."
507
+ ```
508
+
509
+ And that you're passing them in the config:
510
+
511
+ ```typescript
512
+ ai: {
513
+ anthropicKey: process.env.ANTHROPIC_API_KEY,
514
+ openaiKey: process.env.OPENAI_API_KEY,
159
515
  }
160
516
  ```
161
517
 
518
+ ### Database errors
519
+
520
+ Make sure you've:
521
+ 1. Added all required models to your Prisma schema
522
+ 2. Run `npx prisma migrate dev`
523
+ 3. Run `npx prisma generate`
524
+
525
+ ---
526
+
162
527
  ## License
163
528
 
164
- MIT
529
+ MIT © [Hunter Rosenblume](https://github.com/hrosenblume)