Ktavi
Make Markdown drafts publish-ready.
Ktavi is inspired by the idea of polishing and refining a draft before it goes live. The name is short, memorable, and professional while still feeling less plain than a purely functional CLI name.
Ktavi is a workflow-driven AI assistant for preparing Markdown blog drafts for publishing.
The MVP should not behave as a fully autonomous AI agent yet. Instead, it should provide a deterministic, CLI-first workflow that uses AI-powered tools for specific tasks such as grammar review, SEO suggestions, and blog cover prompt generation.
The architecture should be designed so these workflow components can later be exposed as reusable agent skills, MCP tools, or VS Code extension commands.
The goal of the MVP is to help writers take an existing Markdown blog draft and prepare it for publishing by:
- Parsing Markdown content and YAML frontmatter.
- Reviewing metadata for SEO quality.
- Reviewing the article body for grammar, clarity, and diction improvements.
- Generating a cover image concept and image prompt based on metadata and content.
- Generating a cover image using an image generation provider.
- Saving the generated cover locally or uploading it to Cloudinary.
- Updating the Markdown frontmatter with the generated image path or URL.
- Showing clear before/after diffs before applying changes.
The MVP should prioritize reviewable suggestions and deterministic file changes over autonomous rewriting.
The MVP should not include:
- A full web application.
- Multi-user accounts.
- Authentication.
- Publishing directly to Dev.to, Hashnode, Medium, or custom CMS platforms.
- Background jobs or scheduling.
- Full article generation from scratch.
- Fully autonomous edit/apply behavior without user confirmation.
- Complex analytics.
- Collaboration features.
- GitHub Action integration.
- VS Code extension UI.
- MCP server implementation.
These can be added later after the CLI workflow is stable.
Primary users:
- Technical bloggers.
- Developer advocates.
- Indie hackers.
- Engineers writing Markdown-based blogs.
- Writers using static-site generators or content frameworks.
Supported blogging formats should include, but not be limited to:
- Astro content collections.
- Nuxt Content.
- Jekyll.
- Hugo.
- Eleventy.
- Custom Markdown blogs.
- Dev.to-style Markdown drafts.
The tool should show suggestions and diffs before modifying files.
Default behavior should be dry-run/review mode.
Use --apply to write changes.
Grammar and writing suggestions should improve quality without aggressively rewriting the article.
Default writing mode should be light or medium, not strong.
Use normal code for deterministic tasks:
- Parsing Markdown.
- Reading frontmatter.
- Validating missing fields.
- Updating frontmatter.
- Generating diffs.
- Uploading assets.
- Saving files.
Use AI only where it adds real value:
- Grammar/style review.
- SEO suggestions.
- Cover concept generation.
- Cover image prompt generation.
- Alt text generation.
Use this mental model:
Tools = atomic capabilities
Workflows = predictable product flows
Skills = future agent-facing wrappers around tools/workflows
Agents = future decision-makers that choose skills dynamically
For MVP, implement tools/ and workflows/.
Add skills/ and agents/ later when exposing the same logic to agentic runtimes.
The CLI should accept a Markdown file path and parse:
- YAML frontmatter.
- Markdown body.
- Title.
- Description.
- Tags.
- Slug.
- Existing cover image field.
- Headings.
- Links.
- Images.
Example command:
ktavi analyze ./posts/my-post.mdExpected behavior:
- Read the file.
- Extract frontmatter.
- Extract Markdown body.
- Return a structured internal
BlogDraftobject. - Print a readable analysis summary.
The tool should review SEO-related metadata and suggest improvements.
Fields to inspect:
titledescriptionslugtagscover/image/heroImage/ configurable cover field- headings
- content summary
Checks should include:
- Missing title.
- Title too short or too vague.
- Missing description.
- Description too short or too long.
- Missing slug.
- Weak slug formatting.
- Missing or weak tags.
- Missing cover image.
- Missing H1/H2 structure.
- Missing image alt text.
Some checks should be deterministic. AI can be used for qualitative suggestions.
Example command:
ktavi seo ./posts/my-post.mdThe tool should review the Markdown body and suggest writing improvements.
Modes:
light = grammar, spelling, punctuation
medium = grammar, clarity, sentence flow
strong = larger readability rewrites
Default mode:
medium
The tool should produce structured suggestions with:
- Original text.
- Suggested text.
- Category.
- Reason.
- Confidence.
Example command:
ktavi review ./posts/my-post.mdExample with mode:
ktavi review ./posts/my-post.md --mode lightThe MVP should show before/after diffs for suggested text or frontmatter changes.
Example:
- This approach make data fetching more easier.
+ This approach makes data fetching easier.The tool should support:
ktavi fix ./posts/my-post.mdThis shows suggestions and a diff but does not write changes.
ktavi fix ./posts/my-post.md --applyThis applies accepted or safe changes.
For MVP, it is acceptable to apply only structured metadata/frontmatter updates and avoid automatic body rewriting unless explicitly enabled.
The tool should generate a cover image concept from:
- Title.
- Description.
- Tags.
- Slug.
- Markdown body summary.
- Optional brand/style configuration.
The output should include:
- Visual concept.
- Image generation prompt.
- Suggested filename.
- Suggested alt text.
Example command:
ktavi cover ./posts/my-post.md --prompt-onlyExpected output:
Visual concept:
A clean developer workspace with floating UI cards representing cache, queries, and async states.
Image prompt:
Create a modern editorial blog cover illustration for a technical article about TanStack Query in Vue...
Alt text:
Illustration of a developer workflow showing data fetching, caching, and UI state updates.
Suggested filename:
tanstack-query-vue-data-fetching-cover.png
The tool should generate a cover image using a pluggable image provider.
The MVP provider can reuse the existing CoCover VS Code Extension logic where relevant.
Provider abstraction:
interface ImageGenerationProvider {
generateImage(input: GenerateImageInput): Promise<GeneratedImage>;
}The provider should return either:
- A local file path.
- A buffer.
- A base64-encoded image.
- Provider metadata.
Example command:
ktavi cover ./posts/my-post.md --generateThe MVP should support saving the generated image locally.
Example command:
ktavi cover ./posts/my-post.md --generate --save localDefault local output:
./public/images/blog/{suggestedFilename}
This should be configurable.
The MVP should support optional Cloudinary upload.
Example command:
ktavi cover ./posts/my-post.md --generate --upload cloudinaryRequired environment variables:
CLOUDINARY_CLOUD_NAME=
CLOUDINARY_API_KEY=
CLOUDINARY_API_SECRET=The upload tool should return:
- URL.
- Secure URL.
- Public ID.
- Width.
- Height.
- Format.
- Bytes.
The tool should update the draft frontmatter with the image path or URL.
Example:
title: Using TanStack Query in Vue
description: Learn how to simplify data fetching and caching in Vue apps.
tags:
- vue
- tanstack-query
- frontend
cover: https://res.cloudinary.com/example/image/upload/blog/tanstack-query-vue-cover.pngExample command:
ktavi cover ./posts/my-post.md --generate --upload cloudinary --applyImportant:
- Do not write changes unless
--applyis provided. - Show the frontmatter diff before applying.
- Preserve existing frontmatter fields where possible.
- Support a configurable cover field name.
Supported default cover field candidates:
cover
image
heroImage
ogImage
thumbnail
The main MVP command should run the full publish-preparation workflow.
Example:
ktavi prepare ./posts/my-post.mdDefault behavior:
- Parse draft.
- Review SEO metadata.
- Review grammar/style.
- Generate cover concept and prompt.
- Print suggested changes.
- Show diffs.
- Do not write files.
Apply behavior:
ktavi prepare ./posts/my-post.md --generate-cover --upload cloudinary --applyThis may:
- Generate cover image.
- Upload cover.
- Update frontmatter.
- Optionally apply safe metadata fixes.
Body text rewriting should remain conservative in the MVP.
Recommended CLI name:
ktaviktavi analyze <file>Purpose:
- Parse Markdown.
- Print metadata summary.
- Print detected headings, links, images, and missing fields.
ktavi seo <file>Purpose:
- Review SEO metadata.
- Suggest title, description, slug, tags, and structure improvements.
Options:
--json
--applyktavi review <file>Purpose:
- Review writing quality.
- Suggest grammar, clarity, and diction improvements.
Options:
--mode light|medium|strong
--jsonktavi fix <file>Purpose:
- Generate suggested changes.
- Show diff.
- Optionally apply safe fixes.
Options:
--apply
--mode light|medium|strongktavi cover <file>Purpose:
- Generate cover concept.
- Generate image prompt.
- Optionally generate image.
- Optionally save or upload.
- Optionally update frontmatter.
Options:
--prompt-only
--generate
--save local
--upload cloudinary
--apply
--size 1792x1024ktavi prepare <file>Purpose:
- Run the full publish-preparation workflow.
Options:
--generate-cover
--upload cloudinary|none
--save local|none
--apply
--mode light|medium|strong
--json- Node.js
- TypeScript
Recommended:
commander
Alternative:
oclif
For MVP, prefer commander because it is simpler and faster to set up.
Recommended packages:
gray-matterfor YAML frontmatter.unifiedecosystem for Markdown parsing.remark-parsefor Markdown AST.
gray-matteryamlif more control is needed.
difforjsdiff
cloudinaryNode SDK
Use an internal provider abstraction.
Initial provider:
- OpenAI API-compatible provider for GPT-5.5 text tasks.
- Image generation provider reusing CoCover logic where possible.
Do not hard-code provider logic deeply into workflows.
Recommended:
zod
Use zod for:
- Config validation.
- Tool input validation.
- Tool output validation.
- AI structured output parsing.
Recommended:
vitest
Test fixtures:
- Valid post.
- Missing metadata.
- Existing cover image.
- Invalid frontmatter.
- Post with images.
- Post with no headings.
Recommended:
eslintprettiertsxfor local TypeScript execution during development.
CLI Commands
↓
Workflows
↓
Tools
↓
Providers
↓
External APIs / File System
Responsible for:
- Parsing command arguments.
- Loading config.
- Calling workflows.
- Printing human-readable output.
- Setting process exit code.
CLI commands should not contain business logic.
Responsible for composing tools into product flows.
Examples:
reviewDraftWorkflowgenerateAndAttachCoverWorkflowprepareDraftWorkflow
Atomic capabilities.
Examples:
parseMarkdownToolreviewSeoToolreviewWritingToolgenerateCoverPromptToolgenerateImageTooluploadAssetToolupdateFrontmatterToolgenerateDiffTool
Adapters for external systems.
Examples:
- OpenAI text provider.
- OpenAI/image generation provider.
- Cloudinary storage provider.
- Local filesystem storage provider.
Future agent-facing wrappers around workflows or tools.
Example:
skills/generate-cover.skill.ts
This can call:
workflows/generate-and-attach-cover.ts
flowchart TD
A[User runs ktavi prepare post.md] --> B[Parse Markdown and YAML frontmatter]
B --> C[Extract metadata, headings, links, images]
C --> D[Run deterministic SEO checks]
D --> E[Run AI SEO suggestion task]
E --> F[Run AI grammar and clarity review]
F --> G[Generate cover concept and image prompt]
G --> H[Generate report]
H --> I[Show suggestions and diffs]
I --> J{--apply?}
J -- No --> K[Exit without modifying files]
J -- Yes --> L[Apply safe metadata/frontmatter changes]
L --> M[Write updated Markdown file]
flowchart TD
A[User runs ktavi cover post.md] --> B[Parse Markdown]
B --> C[Generate cover concept]
C --> D[Generate image prompt]
D --> E{--generate?}
E -- No --> F[Print prompt and alt text]
E -- Yes --> G[Generate image]
G --> H{Storage target}
H -- local --> I[Save image locally]
H -- cloudinary --> J[Upload image to Cloudinary]
I --> K[Return asset path]
J --> K[Return asset URL]
K --> L{--apply?}
L -- No --> M[Show frontmatter diff only]
L -- Yes --> N[Update cover field in frontmatter]
N --> O[Write Markdown file]
flowchart TD
A[User: Prepare this post for publishing] --> B[Blogging Agent]
B --> C{Decide needed skills}
C --> D[Review Draft Skill]
C --> E[Optimize SEO Skill]
C --> F[Generate Cover Skill]
C --> G[Attach Cover Skill]
D --> H[Existing tools/workflows]
E --> H
F --> H
G --> H
H --> I[Suggestions, diffs, and final patch]
Create central types in:
src/core/types.ts
export type BlogDraft = {
filePath: string;
rawContent: string;
frontmatter: BlogFrontmatter;
markdownBody: string;
metadata: DraftMetadata;
};export type BlogFrontmatter = {
title?: string;
description?: string;
slug?: string;
tags?: string[];
date?: string;
cover?: string;
image?: string;
heroImage?: string;
ogImage?: string;
thumbnail?: string;
canonical?: string;
draft?: boolean;
[key: string]: unknown;
};export type DraftMetadata = {
title?: string;
description?: string;
slug?: string;
tags: string[];
coverImage?: string;
headings: MarkdownHeading[];
links: MarkdownLink[];
images: MarkdownImage[];
wordCount: number;
estimatedReadingTimeMinutes: number;
};export type MarkdownHeading = {
depth: number;
text: string;
};export type MarkdownLink = {
text: string;
url: string;
};export type MarkdownImage = {
alt?: string;
url: string;
title?: string;
};export type SeoSuggestion = {
field: 'title' | 'description' | 'slug' | 'tags' | 'headings' | 'cover' | 'content' | 'images';
severity: 'info' | 'warning' | 'critical';
current?: string | string[];
suggested?: string | string[];
reason: string;
source: 'deterministic' | 'ai';
};export type WritingSuggestion = {
original: string;
suggestion: string;
reason: string;
category: 'grammar' | 'clarity' | 'tone' | 'structure' | 'diction';
confidence: number;
};export type CoverPromptResult = {
visualConcept: string;
prompt: string;
altText: string;
suggestedFilename: string;
};export type GeneratedImage = {
fileName: string;
mimeType: string;
buffer?: Buffer;
base64?: string;
localPath?: string;
providerMetadata?: Record<string, unknown>;
};export type UploadedAsset = {
provider: 'cloudinary' | 'local';
url: string;
secureUrl?: string;
publicId?: string;
localPath?: string;
width?: number;
height?: number;
format?: string;
bytes?: number;
metadata?: Record<string, unknown>;
};export type DraftPatch = {
filePath: string;
originalContent: string;
updatedContent: string;
diff: string;
changes: DraftChange[];
};export type DraftChange = {
type: 'frontmatter' | 'body';
field?: string;
original?: unknown;
updated?: unknown;
reason?: string;
};export type Tool<TInput, TOutput> = {
name: string;
description: string;
run(input: TInput): Promise<TOutput>;
};Create provider interfaces in:
src/core/providers.ts
export type TextAIProvider = {
generateStructuredOutput<TOutput>(input: {
systemPrompt: string;
userPrompt: string;
schemaName: string;
}): Promise<TOutput>;
};export type GenerateImageInput = {
prompt: string;
fileName: string;
size?: '1024x1024' | '1536x1024' | '1792x1024';
};
export type ImageGenerationProvider = {
generateImage(input: GenerateImageInput): Promise<GeneratedImage>;
};export type AssetStorageProvider = {
upload(image: GeneratedImage): Promise<UploadedAsset>;
};Support a config file:
ktavi.config.ts
Example:
import type { KtaviConfig } from './src/core/config';
const config: KtaviConfig = {
ai: {
provider: 'openai',
textModel: 'gpt-5.5',
imageModel: 'image-generation-default',
},
markdown: {
coverField: 'cover',
preserveFrontmatterOrder: true,
},
writing: {
defaultMode: 'medium',
},
image: {
size: '1792x1024',
style: 'modern editorial illustration',
},
storage: {
provider: 'local',
local: {
outputDir: './public/images/blog',
publicPathPrefix: '/images/blog',
},
cloudinary: {
folder: 'blog-covers',
},
},
};
export default config;Type:
export type KtaviConfig = {
ai: {
provider: 'openai';
textModel: string;
imageModel?: string;
};
markdown: {
coverField: 'cover' | 'image' | 'heroImage' | 'ogImage' | 'thumbnail';
preserveFrontmatterOrder?: boolean;
};
writing: {
defaultMode: 'light' | 'medium' | 'strong';
};
image: {
size: '1024x1024' | '1536x1024' | '1792x1024';
style?: string;
};
storage: {
provider: 'local' | 'cloudinary';
local?: {
outputDir: string;
publicPathPrefix: string;
};
cloudinary?: {
folder: string;
};
};
};Use .env for local development.
OPENAI_API_KEY=
CLOUDINARY_CLOUD_NAME=
CLOUDINARY_API_KEY=
CLOUDINARY_API_SECRET=Do not commit .env.
Create .env.example:
OPENAI_API_KEY=
CLOUDINARY_CLOUD_NAME=
CLOUDINARY_API_KEY=
CLOUDINARY_API_SECRET=ktavi/
README.md
package.json
tsconfig.json
.env.example
.gitignore
ktavi.config.ts
src/
cli/
index.ts
commands/
analyze.ts
seo.ts
review.ts
fix.ts
cover.ts
prepare.ts
core/
types.ts
config.ts
providers.ts
tool.ts
workflow.ts
errors.ts
logger.ts
tools/
parse-markdown/
index.ts
parseMarkdownTool.ts
extractMetadata.ts
review-seo/
index.ts
reviewSeoTool.ts
deterministicSeoChecks.ts
aiSeoReview.ts
review-writing/
index.ts
reviewWritingTool.ts
prompts.ts
generate-cover-prompt/
index.ts
generateCoverPromptTool.ts
prompts.ts
generate-image/
index.ts
generateImageTool.ts
upload-asset/
index.ts
uploadAssetTool.ts
update-frontmatter/
index.ts
updateFrontmatterTool.ts
generate-diff/
index.ts
generateDiffTool.ts
workflows/
analyzeDraftWorkflow.ts
reviewDraftWorkflow.ts
optimizeSeoWorkflow.ts
generateAndAttachCoverWorkflow.ts
prepareDraftWorkflow.ts
providers/
ai/
openaiTextProvider.ts
image/
openaiImageProvider.ts
cocoverImageProvider.ts
storage/
localStorageProvider.ts
cloudinaryStorageProvider.ts
utils/
fileSystem.ts
frontmatter.ts
markdown.ts
slug.ts
readingTime.ts
output.ts
tests/
fixtures/
valid-post.md
missing-meta.md
existing-cover.md
invalid-frontmatter.md
post-with-images.md
post-with-no-headings.md
tools/
parseMarkdownTool.test.ts
deterministicSeoChecks.test.ts
updateFrontmatterTool.test.ts
generateDiffTool.test.ts
workflows/
prepareDraftWorkflow.test.ts
Future structure when adding agentic support:
src/
agents/
bloggingAgent.ts
planner.ts
prompts.ts
skills/
blogging/
reviewDraft.skill.ts
optimizeSeo.skill.ts
generateCover.skill.ts
prepareForPublishing.skill.ts
Do not add agents/ or skills/ in MVP unless needed.
Input:
{
filePath: string;
}Output:
BlogDraft;Responsibilities:
- Read Markdown file.
- Parse frontmatter.
- Parse Markdown body.
- Extract metadata.
- Count words.
- Estimate reading time.
- Extract headings, links, and images.
Input:
{
draft: BlogDraft;
}Output:
{
suggestions: SeoSuggestion[];
}Responsibilities:
- Run deterministic SEO checks.
- Call AI provider for qualitative SEO suggestions.
- Merge deterministic and AI suggestions.
- Return structured suggestions.
Input:
{
draft: BlogDraft;
mode: 'light' | 'medium' | 'strong';
}Output:
{
suggestions: WritingSuggestion[];
}Responsibilities:
- Review body text.
- Suggest grammar, clarity, and diction improvements.
- Return structured suggestions.
- Avoid rewriting the entire article by default.
Input:
{
draft: BlogDraft;
style?: string;
}Output:
CoverPromptResult;Responsibilities:
- Summarize article theme.
- Generate visual concept.
- Generate image prompt.
- Generate suggested alt text.
- Generate filename.
Input:
{
prompt: CoverPromptResult;
size: '1024x1024' | '1536x1024' | '1792x1024';
}Output:
GeneratedImage;Responsibilities:
- Call image generation provider.
- Return generated image object.
- Do not upload image.
- Do not update Markdown.
Input:
{
image: GeneratedImage;
provider: 'local' | 'cloudinary';
}Output:
UploadedAsset;Responsibilities:
- Save image locally or upload to Cloudinary.
- Return asset path or URL.
- Do not update Markdown.
Input:
{
draft: BlogDraft;
updates: Record<string, unknown>;
apply: boolean;
}Output:
DraftPatch;Responsibilities:
- Patch frontmatter fields.
- Generate updated Markdown content.
- Generate diff.
- Write to disk only when
applyis true.
Input:
{
original: string;
updated: string;
}Output:
{
diff: string;
}Responsibilities:
- Produce a readable unified diff.
Steps:
- Parse Markdown.
- Return draft metadata summary.
Steps:
- Parse Markdown.
- Run writing review.
- Return writing suggestions.
Steps:
- Parse Markdown.
- Run SEO review.
- Return SEO suggestions.
- Optionally produce frontmatter patch.
Steps:
- Parse Markdown.
- Generate cover prompt.
- Optionally generate image.
- Optionally save or upload image.
- Generate frontmatter patch.
- Apply patch only when
applyis true.
Steps:
- Parse Markdown.
- Run SEO review.
- Run writing review.
- Generate cover prompt.
- Optionally generate and attach cover.
- Show combined report.
- Apply safe changes only when
applyis true.
AI prompts should ask for structured JSON output.
Use schemas and validation.
Do not accept free-form AI output as final data without parsing and validation.
The writing review prompt should instruct the model to:
- Preserve the writer’s tone.
- Avoid rewriting entire paragraphs unless necessary.
- Focus on grammar, clarity, diction, and readability.
- Return precise suggestions.
- Avoid generic feedback.
- Avoid changing code blocks.
- Avoid changing quoted text unless clearly wrong.
The SEO prompt should instruct the model to:
- Review the draft as a blog post.
- Suggest better metadata.
- Explain why each suggestion improves discoverability or clarity.
- Avoid clickbait.
- Keep title and description aligned with the actual content.
- Return structured suggestions.
The cover prompt task should instruct the model to:
- Create a visual concept based on the article’s actual topic.
- Avoid text-heavy image concepts.
- Avoid logos unless explicitly requested.
- Generate alt text.
- Generate a safe filename.
- Respect configured brand/style preferences.
Create typed errors for common failure cases:
export class BlogAgentError extends Error {
constructor(
message: string,
public code: string,
public cause?: unknown,
) {
super(message);
}
}Recommended error codes:
FILE_NOT_FOUND
INVALID_MARKDOWN
INVALID_FRONTMATTER
AI_PROVIDER_ERROR
IMAGE_GENERATION_FAILED
CLOUDINARY_UPLOAD_FAILED
CONFIG_NOT_FOUND
CONFIG_INVALID
PATCH_FAILED
WRITE_FAILED
CLI should print friendly messages.
Example:
Could not parse frontmatter in ./posts/my-post.md.
Please check that the YAML block starts and ends with ---.
Never write files unless --apply is provided.
Do not print API keys or Cloudinary secrets.
Cloudinary upload should require an explicit flag:
--upload cloudinaryDo not silently apply large body rewrites.
Avoid modifying code blocks during grammar review.
Warn in README that Markdown content may be sent to the configured AI provider for review unless a local provider is used in the future.
Test:
- Markdown parsing.
- Frontmatter extraction.
- Metadata extraction.
- SEO deterministic checks.
- Frontmatter updates.
- Diff generation.
- Config loading.
Test workflows with fixtures:
- Analyze draft.
- SEO review with mocked AI provider.
- Writing review with mocked AI provider.
- Generate cover prompt with mocked AI provider.
- Generate and attach cover with mocked image/storage providers.
Mock external providers by default.
Do not call real AI or Cloudinary APIs in unit tests.
Create:
tests/fixtures/missing-meta.md
Content:
---
title: TanStack Query in Vue
---
# TanStack Query in Vue
This post explain how TanStack Query can make data fetching more easier in Vue apps.
## Why it matters
Managing loading, errors, cache and refetching manually can become hard.Expected SEO suggestions:
- Missing description.
- Missing tags.
- Missing slug.
- Missing cover image.
Expected writing suggestion:
- This post explain how TanStack Query can make data fetching more easier in Vue apps.
+ This post explains how TanStack Query can make data fetching easier in Vue apps.Recommended package.json scripts:
{
"scripts": {
"dev": "tsx src/cli/index.ts",
"build": "tsc",
"test": "vitest run",
"test:watch": "vitest",
"lint": "eslint .",
"format": "prettier --write .",
"typecheck": "tsc --noEmit"
}
}Recommended package fields:
{
"type": "module",
"bin": {
"ktavi": "dist/cli/index.js"
}
}Runtime dependencies:
npm install commander gray-matter unified remark-parse zod diff cloudinary dotenvDevelopment dependencies:
npm install -D typescript tsx vitest eslint prettier @types/nodeAdd OpenAI SDK or compatible AI SDK according to the provider implementation:
npm install openai- Initialize TypeScript project.
- Add CLI entry.
- Add config loader.
- Add test setup.
- Implement
parseMarkdownTool. - Add fixtures.
- Add unit tests.
- Implement deterministic checks first.
- Add tests.
- Implement
generateDiffTool. - Implement
updateFrontmatterTool. - Add tests.
- Define
TextAIProvider. - Add mocked provider for tests.
- Add OpenAI provider implementation.
- Implement structured AI review tools.
- Validate output with
zod.
- Implement prompt generation.
- Return concept, prompt, alt text, filename.
- Implement image provider abstraction.
- Add local storage provider.
- Add Cloudinary provider.
- Implement
prepareDraftWorkflow. - Implement
generateAndAttachCoverWorkflow.
- Add readable terminal summaries.
- Add
--jsonoutput option. - Improve error handling.
The MVP is complete when:
- A user can run:
ktavi analyze ./posts/example.mdand see parsed metadata and content structure.
- A user can run:
ktavi seo ./posts/example.mdand receive useful SEO suggestions.
- A user can run:
ktavi review ./posts/example.mdand receive grammar/clarity suggestions.
- A user can run:
ktavi cover ./posts/example.md --prompt-onlyand receive a cover concept, prompt, alt text, and filename.
- A user can run:
ktavi cover ./posts/example.md --generate --save local --applyand the tool saves a generated cover image locally and updates frontmatter.
- A user can run:
ktavi cover ./posts/example.md --generate --upload cloudinary --applyand the tool uploads the generated cover image to Cloudinary and updates frontmatter.
-
The tool does not write changes unless
--applyis passed. -
The project has unit tests for parsing, deterministic SEO checks, diff generation, and frontmatter patching.
-
AI providers and storage providers are abstracted so they can be replaced later.
-
The architecture can later support
skills/,agents/, VS Code extension commands, or MCP tools without rewriting core logic.
Expose workflows as VS Code commands:
- Review current draft.
- Optimize SEO metadata.
- Generate cover image.
- Attach cover to frontmatter.
- Show suggestions in side panel.
Run on pull requests:
- Check blog drafts.
- Comment SEO suggestions.
- Comment grammar suggestions.
- Warn about missing cover images or metadata.
Add:
src/agents/
src/skills/
Expose workflows as skills:
reviewDraftSkilloptimizeSeoSkillgenerateCoverSkillprepareForPublishingSkill
Agent behavior:
- Inspect draft.
- Decide which skills are needed.
- Ask for confirmation before file writes/uploads.
- Produce a publishing-readiness report.
Expose tools through MCP so other AI agents and editors can call them.
Potential MCP tools:
parse_blog_draftreview_blog_seoreview_blog_writinggenerate_blog_cover_promptgenerate_blog_cover_imageattach_cover_to_blog_draft
When generating the boilerplate:
- Use TypeScript.
- Create a CLI-first project.
- Keep business logic out of CLI command files.
- Implement tools as isolated modules.
- Implement workflows as composition layers.
- Use provider interfaces for AI, image generation, and storage.
- Add tests with mocked providers.
- Do not implement a full autonomous agent in MVP.
- Do not create web UI in MVP.
- Do not hard-code Cloudinary as the only storage option.
- Do not hard-code OpenAI deeply into workflows.
- Do not write files unless
applyis explicitly true. - Return structured data from tools and workflows.
- Print user-friendly CLI output separately from core logic.
- Preserve future ability to expose workflows as reusable agent skills.
# Ktavi
A CLI-first, workflow-driven AI assistant for preparing Markdown blog drafts for publishing.
It reviews your draft for grammar, clarity, and SEO metadata, generates a matching cover image concept, optionally creates and uploads the image, and updates your Markdown frontmatter with a reviewable diff.
## MVP Features
- Parse Markdown and YAML frontmatter
- Review SEO metadata
- Suggest grammar and clarity improvements
- Generate blog cover image prompts
- Generate cover images
- Save images locally or upload to Cloudinary
- Update Markdown frontmatter
- Show diffs before applying changes
## Usage
```bash
ktavi analyze ./posts/my-post.md
ktavi seo ./posts/my-post.md
ktavi review ./posts/my-post.md
ktavi cover ./posts/my-post.md --prompt-only
ktavi prepare ./posts/my-post.md
```Use --apply to write changes.
---
## 30. Final Architecture Summary
The MVP should be implemented as:
```text
CLI-first TypeScript tool
↓
Workflow-driven architecture
↓
Modular tools
↓
Provider abstractions
↓
Future-ready for agent skills and MCP
Recommended framing:
A workflow-driven AI assistant for preparing Markdown blog drafts for publishing, designed so its workflows can later be exposed as reusable agent skills.