feat(tools): create @tpmjs/createblogpost test tool package

Add test tool package to verify sync system can discover and process tools:

Package Features:
- Creates structured blog posts with frontmatter and metadata
- Supports both Markdown and MDX output formats
- Automatic slug generation from title
- Word count and reading time calculation
- SEO-friendly metadata generation

TPMJS Integration:
- Rich-tier tpmjs field with all optional metadata
- Tagged with 'tpmjs-tool' keyword for NPM discovery
- Complete parameter and return type documentation
- AI agent guidance for optimal LLM usage
- Pricing, authentication, and framework metadata

Implementation:
- Full TypeScript implementation with exported types
- Proper tsconfig and tsup build configuration
- Comprehensive README with usage examples
- Changeset for publishing workflow
- Added packages/tools/* to pnpm workspace

This package serves as an end-to-end test of the sync workers to verify:
1. NPM keyword search discovers the package
2. Changes feed picks up updates
3. tpmjs field validation works correctly
4. Rich-tier metadata is properly extracted
5. Quality score calculation functions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Ajax Davis 2025-11-28 03:10:31 +10:00
parent 3b95502577
commit b58df449da
7 changed files with 567 additions and 21 deletions

View file

@ -0,0 +1,107 @@
# @tpmjs/createblogpost
A tool for creating structured blog posts with frontmatter and metadata. Part of the TPMJS registry.
## Installation
```bash
npm install @tpmjs/createblogpost
# or
pnpm add @tpmjs/createblogpost
# or
yarn add @tpmjs/createblogpost
```
## Usage
```typescript
import { createBlogPost } from '@tpmjs/createblogpost';
const post = await createBlogPost({
title: 'Getting Started with TypeScript',
author: 'John Doe',
content: 'TypeScript is a typed superset of JavaScript that compiles to plain JavaScript...',
tags: ['typescript', 'javascript', 'programming'],
excerpt: 'Learn the basics of TypeScript in this comprehensive guide',
format: 'markdown',
});
console.log(post.formattedOutput);
```
## API
### `createBlogPost(options: BlogPostOptions): Promise<BlogPost>`
Creates a structured blog post with frontmatter and metadata.
#### Options
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `title` | `string` | Yes | - | The title of the blog post |
| `author` | `string` | Yes | - | The author of the blog post |
| `content` | `string` | Yes | - | The main content of the blog post |
| `tags` | `string[]` | No | `[]` | Array of tags for categorization |
| `format` | `'markdown' \| 'mdx'` | No | `'markdown'` | Output format for the blog post |
| `excerpt` | `string` | No | - | Short excerpt or summary of the post |
| `publishDate` | `Date` | No | `new Date()` | Publication date |
#### Returns
Returns a `BlogPost` object with the following structure:
```typescript
{
frontmatter: {
title: string;
author: string;
date: string; // ISO date format (YYYY-MM-DD)
tags: string[];
slug: string; // Auto-generated from title
wordCount: number; // Calculated from content
readingTime: number; // Estimated minutes to read
excerpt?: string;
};
content: string;
formattedOutput: string; // Complete post with frontmatter
}
```
## Example Output
```markdown
---
title: "Getting Started with TypeScript"
author: John Doe
date: 2025-11-28
slug: getting-started-with-typescript
tags: ["typescript", "javascript", "programming"]
excerpt: "Learn the basics of TypeScript in this comprehensive guide"
wordCount: 250
readingTime: 2
---
TypeScript is a typed superset of JavaScript that compiles to plain JavaScript...
```
## Features
- Automatic slug generation from title
- Word count calculation
- Reading time estimation (200 words/min)
- Support for both Markdown and MDX formats
- Customizable frontmatter
- SEO-friendly metadata
## Use Cases
- Static site generators (Next.js, Gatsby, Astro)
- Content management systems
- Blog platforms
- Documentation sites
- Automated content generation
## License
MIT

View file

@ -0,0 +1,108 @@
{
"name": "@tpmjs/createblogpost",
"version": "0.1.0",
"description": "A tool for creating structured blog posts with AI-generated content",
"type": "module",
"keywords": ["tpmjs-tool", "blog", "content", "ai", "writing"],
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"files": ["dist"],
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"type-check": "tsc --noEmit",
"clean": "rm -rf dist .turbo"
},
"devDependencies": {
"@tpmjs/tsconfig": "workspace:*",
"tsup": "^8.3.5",
"typescript": "^5.9.3"
},
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/ajaxdavis/tpmjs.git",
"directory": "packages/tools/createBlogPost"
},
"homepage": "https://tpmjs.com",
"license": "MIT",
"tpmjs": {
"category": "text-analysis",
"description": "Creates structured blog posts with customizable frontmatter, content sections, and SEO metadata. Supports multiple output formats including Markdown and MDX.",
"example": "const post = await createBlogPost({ title: 'My First Post', author: 'John Doe', content: 'Hello World!', tags: ['intro', 'blog'] });",
"parameters": [
{
"name": "title",
"type": "string",
"description": "The title of the blog post",
"required": true
},
{
"name": "author",
"type": "string",
"description": "The author of the blog post",
"required": true
},
{
"name": "content",
"type": "string",
"description": "The main content of the blog post",
"required": true
},
{
"name": "tags",
"type": "string[]",
"description": "Array of tags for categorization",
"required": false,
"default": []
},
{
"name": "format",
"type": "'markdown' | 'mdx'",
"description": "Output format for the blog post",
"required": false,
"default": "markdown"
},
{
"name": "excerpt",
"type": "string",
"description": "Short excerpt or summary of the post",
"required": false
}
],
"returns": {
"type": "BlogPost",
"description": "A structured blog post object with frontmatter, content, and metadata including slug, wordCount, readingTime, and formattedOutput"
},
"authentication": {
"required": false,
"type": "api-key"
},
"pricing": {
"model": "free"
},
"frameworks": ["vercel-ai", "langchain"],
"links": {
"documentation": "https://tpmjs.com/tools/createblogpost",
"repository": "https://github.com/ajaxdavis/tpmjs/tree/main/packages/tools/createBlogPost",
"homepage": "https://tpmjs.com"
},
"tags": ["blog", "content", "markdown", "mdx", "writing", "seo"],
"status": "stable",
"aiAgent": {
"useCase": "Use this tool when users need to generate blog posts, articles, or structured content with proper frontmatter and metadata. Ideal for content management systems, static site generators, and documentation sites.",
"limitations": "Does not include AI content generation - you must provide the content. Only formats and structures existing content.",
"examples": [
"Create a blog post about TypeScript best practices",
"Generate a tutorial post with code examples",
"Format an article with SEO metadata"
]
}
}
}

View file

@ -0,0 +1,147 @@
/**
* Blog Post Creation Tool for TPMJS
* Creates structured blog posts with frontmatter and metadata
*/
export interface BlogPostOptions {
title: string;
author: string;
content: string;
tags?: string[];
format?: 'markdown' | 'mdx';
excerpt?: string;
publishDate?: Date;
}
export interface BlogPost {
frontmatter: {
title: string;
author: string;
date: string;
tags: string[];
excerpt?: string;
slug: string;
wordCount: number;
readingTime: number;
};
content: string;
formattedOutput: string;
}
/**
* Creates a slug from a title
*/
function createSlug(title: string): string {
return title
.toLowerCase()
.trim()
.replace(/[^\w\s-]/g, '')
.replace(/[\s_-]+/g, '-')
.replace(/^-+|-+$/g, '');
}
/**
* Calculates reading time based on word count
* Assumes average reading speed of 200 words per minute
*/
function calculateReadingTime(wordCount: number): number {
return Math.ceil(wordCount / 200);
}
/**
* Counts words in content
*/
function countWords(content: string): number {
return content.trim().split(/\s+/).length;
}
/**
* Formats frontmatter as YAML
*/
function formatFrontmatter(
frontmatter: BlogPost['frontmatter'],
format: 'markdown' | 'mdx'
): string {
const delimiter = format === 'mdx' ? '---' : '---';
const lines = [
delimiter,
`title: "${frontmatter.title}"`,
`author: ${frontmatter.author}`,
`date: ${frontmatter.date}`,
`slug: ${frontmatter.slug}`,
`tags: [${frontmatter.tags.map((tag) => `"${tag}"`).join(', ')}]`,
];
if (frontmatter.excerpt) {
lines.push(`excerpt: "${frontmatter.excerpt}"`);
}
lines.push(`wordCount: ${frontmatter.wordCount}`);
lines.push(`readingTime: ${frontmatter.readingTime}`);
lines.push(delimiter);
return lines.join('\n');
}
/**
* Creates a structured blog post with frontmatter and metadata
*/
export async function createBlogPost(options: BlogPostOptions): Promise<BlogPost> {
const {
title,
author,
content,
tags = [],
format = 'markdown',
excerpt,
publishDate = new Date(),
} = options;
// Validate required fields
if (!title || title.trim().length === 0) {
throw new Error('Title is required');
}
if (!author || author.trim().length === 0) {
throw new Error('Author is required');
}
if (!content || content.trim().length === 0) {
throw new Error('Content is required');
}
// Calculate metadata
const slug = createSlug(title);
const wordCount = countWords(content);
const readingTime = calculateReadingTime(wordCount);
// Build frontmatter
const frontmatter: BlogPost['frontmatter'] = {
title,
author,
date: publishDate.toISOString().split('T')[0] || '',
tags,
slug,
wordCount,
readingTime,
};
if (excerpt) {
frontmatter.excerpt = excerpt;
}
// Format the complete blog post
const formattedFrontmatter = formatFrontmatter(frontmatter, format);
const formattedOutput = `${formattedFrontmatter}\n\n${content}`;
return {
frontmatter,
content,
formattedOutput,
};
}
/**
* Export default for convenience
*/
export default createBlogPost;

View file

@ -0,0 +1,11 @@
{
"extends": "@tpmjs/tsconfig/base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src",
"incremental": false,
"composite": false
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}

View file

@ -0,0 +1,10 @@
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm'],
dts: true,
clean: true,
treeshake: true,
splitting: false,
});