mcpField
L
Letter Writer

Letter Writer

github.com/jmsktmドキュメント10207 回インストール40 週間前に更新
リポジトリ

Letter Writer について

Letter Writerは、ビジネスや個人向けの手紙を作成するスキルで、カバーレターからお礼状まで対応します。相手に合わせた文体や書式を使い、PDF、DOCX、HTMLへの出力や、テンプレートと宛先データによる一括作成ができます。

主な機能

  1. 01ビジネスや個人向けの手紙作成
  2. 02カバーレター、推薦状、お礼状の作成
  3. 03相手に合わせた文体と構成の調整
  4. 04PDF、DOCX、HTMLへの出力
  5. 05個別化した手紙の一括作成
  6. 06宛先データを使ったテンプレート適用

活用シーン

  1. 01求人応募用のカバーレター作成
  2. 02正式なビジネス文書の作成
  3. 03同僚や学生への推薦状作成
  4. 04複数の宛先向け手紙の個別作成

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/jmsktm/claude-settings --skill letter-writer

インストール後、Claude Code で/letter-writerを入力して読み込まれたか確認します。

詳細

レター作成

レター作成スキルは、適切な書式、語調、構成で、ビジネス用および個人用のレターを自動作成します。ビジネス文書、カバーレター、推薦状、お礼状、正式な連絡文書など、さまざまな種類のレターに対応します。対象者や目的に合わせた適切な書式、プロフェッショナルな表現、カスタマイズを実現します。

さまざまな用途やコミュニケーションのニーズに合わせてカスタマイズできるテンプレートを使用し、PDF、DOCX、HTML 形式のレターを生成します。

主なワークフロー

ワークフロー 1:カバーレターの作成

目的: 特定の求人応募に合わせた、説得力のあるカバーレターを作成します。

手順:

  1. 求人の詳細(職種、会社、応募要件)を収集します
  2. 候補者の関連する資格や経験を抽出します
  3. 適切なビジネス書式でレターを構成します
  4. 読み手を引き付ける書き出しの段落を作成します
  5. 関連する経験と実績を詳しく述べます
  6. スキルと求人要件を結び付けます
  7. 次の行動を促す、力強い結びの言葉を作成します
  8. 適切な行間と余白で書式を整えます
  9. PDF と DOCX 形式で出力します

実装:

const { Document, Packer, Paragraph, AlignmentType, TextRun } = require('docx');
const fs = require('fs');

async function generateCoverLetter(letterData, outputPath) {
  const doc = new Document({
    sections: [{
      properties: {
        page: {
          margin: {
            top: 1440,    // 1 inch
            right: 1440,
            bottom: 1440,
            left: 1440
          }
        }
      },
      children: [
        // Your contact information
        new Paragraph({
          text: letterData.sender.name,
          spacing: { after: 0 }
        }),
        new Paragraph({
          text: letterData.sender.address,
          spacing: { after: 0 }
        }),
        new Paragraph({
          text: `${letterData.sender.city}, ${letterData.sender.state} ${letterData.sender.zip}`,
          spacing: { after: 0 }
        }),
        new Paragraph({
          text: letterData.sender.email,
          spacing: { after: 0 }
        }),
        new Paragraph({
          text: letterData.sender.phone,
          spacing: { after: 200 }
        }),

        // Date
        new Paragraph({
          text: new Date().toLocaleDateString('en-US', {
            year: 'numeric',
            month: 'long',
            day: 'numeric'
          }),
          spacing: { after: 200 }
        }),

        // Recipient information
        new Paragraph({
          text: letterData.recipient.name || 'Hiring Manager',
          spacing: { after: 0 }
        }),
        new Paragraph({
          text: letterData.recipient.company,
          spacing: { after: 0 }
        }),
        new Paragraph({
          text: letterData.recipient.address,
          spacing: { after: 0 }
        }),
        new Paragraph({
          text: `${letterData.recipient.city}, ${letterData.recipient.state} ${letterData.recipient.zip}`,
          spacing: { after: 200 }
        }),

        // Salutation
        new Paragraph({
          text: `Dear ${letterData.recipient.name || 'Hiring Manager'}:`,
          spacing: { after: 200 }
        }),

        // Opening paragraph
        new Paragraph({
          text: `I am writing to express my strong interest in the ${letterData.position} position at ${letterData.recipient.company}. ${letterData.opening}`,
          alignment: AlignmentType.JUSTIFIED,
          spacing: { after: 200 }
        }),

        // Body paragraphs
        ...letterData.bodyParagraphs.map(para =>
          new Paragraph({
            text: para,
            alignment: AlignmentType.JUSTIFIED,
            spacing: { after: 200 }
          })
        ),

        // Closing paragraph
        new Paragraph({
          text: letterData.closing || `Thank you for considering my application. I am excited about the opportunity to contribute to ${letterData.recipient.company} and would welcome the chance to discuss how my skills and experience align with your needs. I look forward to hearing from you.`,
          alignment: AlignmentType.JUSTIFIED,
          spacing: { after: 200 }
        }),

        // Signature
        new Paragraph({
          text: 'Sincerely,',
          spacing: { after: 400 }
        }),
        new Paragraph({
          text: letterData.sender.name
        })
      ]
    }]
  });

  const buffer = await Packer.toBuffer(doc);
  fs.writeFileSync(outputPath, buffer);

  return outputPath;
}

// Helper function to generate body paragraphs based on job requirements
function generateCoverLetterBody(candidateData, jobData) {
  const paragraphs = [];

  // Experience paragraph
  const relevantExperience = candidateData.experience
    .filter(exp => exp.relevanceScore > 0.7)
    .slice(0, 2);

  if (relevantExperience.length > 0) {
    const expText = `With ${calculateYearsExperience(candidateData.experience)} years of experience in ${jobData.industry}, I have developed strong expertise in ${jobData.keySkills.slice(0, 3).join(', ')}. In my current role at ${relevantExperience[0].company}, ${relevantExperience[0].achievements[0].toLowerCase()}`;
    paragraphs.push(expText);
  }

  // Skills and qualifications paragraph
  const matchingSkills = candidateData.skills
    .filter(skill => jobData.requiredSkills.includes(skill.name))
    .slice(0, 4);

  if (matchingSkills.length > 0) {
    const skillsText = `I am particularly well-suited for this role given my proficiency in ${matchingSkills.map(s => s.name).join(', ')}. ${candidateData.achievements.find(a => a.includes(matchingSkills[0].name)) || 'I have successfully applied these skills to deliver measurable results.'}`;
    paragraphs.push(skillsText);
  }

  // Company-specific paragraph
  if (jobData.companyInfo) {
    const companyText = `I am particularly drawn to ${jobData.company} because of ${jobData.companyInfo.appeal || 'your reputation for innovation and excellence'}. I believe my background in ${jobData.relevantBackground} would enable me to make immediate contributions to your team.`;
    paragraphs.push(companyText);
  }

  return paragraphs;
}
ワークフロー 2:ビジネスレターの作成

目的: 適切な書式で正式なビジネス文書を作成します。

手順:

  1. レターの種類(問い合わせ、苦情、提案など)を選択します
  2. ビジネスレターの構成に沿って書式を整えます
  3. プロフェッショナルな語調と表現を使用します
  4. 必要に応じて件名を記載します
  5. 明確で簡潔な内容を作成します
  6. 適切な結びの言葉と署名欄を追加します
  7. 利用可能な場合は、レターヘッドに合わせて書式を整えます

実装:

async function generateBusinessLetter(letterData, outputPath) {
  const letterTypes = {
    inquiry: {
      subject: 'Inquiry Regarding',
      opening: 'I am writing to inquire about',
      tone: 'polite and professional'
    },
    complaint: {
      subject: 'Concern Regarding',
      opening: 'I am writing to bring to your attention',
      tone: 'firm but professional'
    },
    proposal: {
      subject: 'Proposal for',
      opening: 'I am pleased to present',
      tone: 'professional and enthusiastic'
    },
    thankyou: {
      subject: 'Thank You',
      opening: 'I wanted to express my sincere gratitude for',
      tone: 'warm and appreciative'
    }
  };

  const template = letterTypes[letterData.type] || letterTypes.inquiry;

  const doc = new Document({
    sections: [{
      properties: {
        page: {
          margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 }
        }
      },
      children: [
        // Company letterhead (if provided)
        ...(letterData.letterhead ? [
          new Paragraph({
            text: letterData.letterhead.companyName,
            alignment: AlignmentType.CENTER,
            spacing: { after: 0 }
          }),
          new Paragraph({
            text: letterData.letterhead.address,
            alignment: AlignmentType.CENTER,
            spacing: { after: 0 }
          }),
          new Paragraph({
            text: `${letterData.letterhead.phone} | ${letterData.letterhead.email}`,
            alignment: AlignmentType.CENTER,
            spacing: { after: 400 }
          })
        ] : []),

        // Date
        new Paragraph({
          text: new Date().toLocaleDateString('en-US', {
            year: 'numeric',
            month: 'long',
            day: 'numeric'
          }),
          spacing: { after: 200 }
        }),

        // Recipient
        new Paragraph({
          text: letterData.recipient.name,
          spacing: { after: 0 }
        }),
        new Paragraph({
          text: letterData.recipient.title,
          spacing: { after: 0 }
        }),
        new Paragraph({
          text: letterData.recipient.company,
          spacing: { after: 0 }
        }),
        new Paragraph({
          text: letterData.recipient.address,
          spacing: { after: 200 }
        }),

        // Subject line
        ...(letterData.subject ? [
          new Paragraph({
            children: [
              new TextRun({ text: 'RE: ', bold: true }),
              new TextRun(letterData.subject)
            ],
            spacing: { after: 200 }
          })
        ] : []),

        // Salutation
        new Paragraph({
          text: `Dear ${letterData.recipient.name}:`,
          spacing: { after: 200 }
        }),

        // Body
        ...letterData.paragraphs.map(para =>
          new Paragraph({
            text: para,
            alignment: AlignmentType.JUSTIFIED,
            spacing: { after: 200 }
          })
        ),

        // Closing
        new Paragraph({
          text: letterData.closing || 'Sincerely,',
          spacing: { after: 400 }
        }),

        // Signature
        new Paragraph({
          text: letterData.sender.name,
          spacing: { after: 0 }
        }),
        new Paragraph({
          text: letterData.sender.title
        })
      ]
    }]
  });

  const buffer = await Packer.toBuffer(doc);
  fs.writeFileSync(outputPath, buffer);

  return outputPath;
}
ワークフロー 3:推薦状の作成

目的: 従業員、学生、同僚の推薦状を作成します。

手順:

  1. 推薦する人物についての情報を収集します
  2. レターの目的と宛先を確認します
  3. 推薦者としての信頼性と関係性を明確にします
  4. 具体的な資格や実績を詳しく述べます
  5. 具体例を提示します
  6. 適切な場合は、同僚などと比較します
  7. 熱意を込めて推薦します
  8. 追加情報を提供できることを伝えます

実装:

function generateRecommendationLetter(recommendationData) {
  const letterData = {
    sender: recommendationData.recommender,
    recipient: recommendationData.recipient || {
      name: 'To Whom It May Concern'
    },
    type: 'recommendation',

    paragraphs: [
      // Opening - establish relationship
      `I am writing to enthusiastically recommend ${recommendationData.candidate.name} for ${recommendationData.purpose}. I have had the pleasure of working with ${recommendationData.candidate.firstName} for ${recommendationData.duration} in my capacity as ${recommendationData.recommender.title} at ${recommendationData.recommender.company}.`,

      // Qualifications and strengths
      `During our time working together, ${recommendationData.candidate.firstName} has consistently demonstrated exceptional ${recommendationData.strengths.join(', ')}. ${recommendationData.standoutQualities}`,

      // Specific examples
      ...recommendationData.examples.map(example =>
        `For instance, ${example.description} This ${example.impact}`
      ),

      // Comparison to peers (if applicable)
      ...(recommendationData.comparison ? [
        `${recommendationData.candidate.firstName} stands out among ${recommendationData.comparison.peerGroup} for ${recommendationData.comparison.distinguishingFactors}.`
      ] : []),

      // Strong endorsement
      `I am confident that ${recommendationData.candidate.firstName} will be an outstanding ${recommendationData.targetRole} and will bring the same level of dedication, skill, and enthusiasm that I have witnessed firsthand. ${recommendationData.candidate.pronoun} has my highest recommendation without reservation.`,

      // Offer additional information
      `Please feel free to contact me at ${recommendationData.recommender.phone} or ${recommendationData.recommender.email} if you would like to discuss ${recommendationData.candidate.firstName}'s qualifications further.`
    ]
  };

  return generateBusinessLetter(letterData, recommendationData.outputPath);
}
ワークフロー 4:お礼状の作成

目的: さまざまな場面に応じた、プロフェッショナルなお礼状を作成します。

手順:

  1. 場面(面接、贈り物、会議など)を特定します
  2. 具体的に感謝の気持ちを伝えます
  3. やり取りの中での重要な詳細に触れます
  4. 関心や関係性をあらためて伝えます
  5. 簡潔ながらも誠意のある内容にします
  6. 温かみがありながらもプロフェッショナルな語調を用います
  7. 媒体に適した書式に整えます

実装:

function generateThankYouLetter(thankYouData) {
  const templates = {
    interview: {
      opening: `Thank you for taking the time to meet with me ${thankYouData.when} to discuss the ${thankYouData.position} position at ${thankYouData.company}.`,
      body: `I enjoyed learning more about ${thankYouData.discussionTopics} and was particularly excited to hear about ${thankYouData.highlight}. Our conversation reinforced my strong interest in joining your team and contributing to ${thankYouData.contribution}.`,
      closing: `I am very enthusiastic about this opportunity and believe my ${thankYouData.relevantSkills} would be a strong fit for your needs. Thank you again for your time and consideration. I look forward to hearing from you.`
    },

    meeting: {
      opening: `Thank you for meeting with me on ${thankYouData.date} to discuss ${thankYouData.topic}.`,
      body: `I found our conversation about ${thankYouData.keyPoints} very valuable. Your insights regarding ${thankYouData.insight} gave me a new perspective on ${thankYouData.subject}.`,
      closing: `I appreciate your time and expertise. I look forward to our continued collaboration.`
    },

    gift: {
      opening: `Thank you so much for the thoughtful ${thankYouData.gift}.`,
      body: `Your generosity and thoughtfulness are greatly appreciated. ${thankYouData.personalNote}`,
      closing: `Thank you again for thinking of me. Your kindness means a lot.`
    },

    networking: {
      opening: `It was a pleasure meeting you at ${thankYouData.event} on ${thankYouData.date}.`,
      body: `I enjoyed our conversation about ${thankYouData.topic} and found your perspective on ${thankYouData.insight} particularly interesting. I would love to stay connected and explore opportunities for ${thankYouData.futureCollaboration}.`,
      closing: `Thank you for taking the time to speak with me. I've connected with you on LinkedIn and look forward to staying in touch.`
    }
  };

  const template = templates[thankYouData.type] || templates.meeting;

  return {
    sender: thankYouData.sender,
    recipient: thankYouData.recipient,
    subject: `Thank you - ${thankYouData.regarding}`,
    paragraphs: [
      template.opening,
      template.body,
      template.closing
    ],
    closing: 'Best regards,'
  };
}
ワークフロー 5:レターの一括生成

目的: テンプレートとデータソースを使って、複数の宛先に合わせたレターを作成します。

手順:

  1. プレースホルダーを含むレターテンプレートを読み込みます
  2. 宛先データ(CSV、JSON、データベース)を読み込みます
  3. 宛先ごとに、以下を実行します:
    • データをテンプレートに反映します
    • 内容を宛先に合わせて調整します
    • レターを生成します
    • 固有のファイル名で保存します
  4. 生成の進捗を記録します
  5. 概要レポートを作成します

実装:

const Handlebars = require('handlebars');

async function batchGenerateLetters(templatePath, recipientsData, outputDir) {
  const template = fs.readFileSync(templatePath, 'utf8');
  const compiledTemplate = Handlebars.compile(template);

  const results = [];

  for (const recipient of recipientsData) {
    try {
      // Merge recipient data with template
      const letterContent = compiledTemplate(recipient);

      // Generate filename
      const filename = `${outputDir}/letter-${recipient.id}-${recipient.name.replace(/\s+/g, '-')}.docx`;

      // Create letter data
      const letterData = {
        sender: recipient.sender || recipientsData.defaultSender,
        recipient: {
          name: recipient.name,
          title: recipient.title,
          company: recipient.company,
          address: recipient.address
        },
        paragraphs: letterContent.split('\n\n').filter(p => p.trim()),
        subject: recipient.subject
      };

      // Generate letter
      await generateBusinessLetter(letterData, filename);

      results.push({
        success: true,
        recipientId: recipient.id,
        name: recipient.name,
        filename: filename
      });

    } catch (error) {
      results.push({
        success: false,
        recipientId: recipient.id,
        name: recipient.name,
        error: error.message
      });
    }
  }

  // Generate summary report
  const summary = {
    total: recipientsData.length,
    successful: results.filter(r => r.success).length,
    failed: results.filter(r => !r.success).length,
    results: results
  };

  fs.writeFileSync(
    `${outputDir}/batch-summary.json`,
    JSON.stringify(summary, null, 2)
  );

  return summary;
}

クイックリファレンス

アクションコマンド/トリガー
カバーレター「[position] のカバーレターを書いて」
ビジネスレター「[recipient] 宛てのビジネスレターを作成して」
推薦状「推薦状を作成して」
お礼状「お礼状を書いて」
正式なレター「正式な連絡文書の草案を作成して」
レターの一括作成「[recipients] 宛てのレターを作成して」

ベストプラクティス

  • 適切な形式: 正しい余白を設定し、標準的なビジネスレターの形式を使用します
  • プロフェッショナルなトーン: 目的や対象者に合わせたトーンにします
  • 簡潔な文章: 明確かつ直接的に書き、不要な語句を避けます
  • 校正: 誤字脱字や文法上の誤りがないようにします
  • パーソナライズ: レターごとに内容を調整し、ありきたりな表現を避けます
  • 具体的な詳細: 具体例や詳細な情報を盛り込みます
  • 能動態: より力強く明確な文章にするため、能動態を使用します
  • 適切な長さ: カバーレターは3~4段落、ビジネスレターは必要な内容を簡潔にまとめます
  • 行動喚起: 明確な次のステップや望ましい結果を示して締めくくります
  • 署名: 連絡先情報を記載した適切な署名ブロックを含めます
  • 書式: フォント、余白、行間を統一します
  • タイミング: お礼状は24~48時間以内に送ります

よくあるパターン

求人応募一式:

{
  coverLetter: generateCoverLetter(candidateData, jobData),
  resume: generateResume(candidateData),
  references: generateReferencesList(candidateData.references)
}

カスタマーサービスの返信:

{
  type: 'response',
  tone: 'apologetic and solution-oriented',
  structure: ['acknowledge issue', 'explain what happened', 'describe resolution', 'offer compensation', 'prevent future']
}

依存関係

必要なパッケージをインストールします:

npm install docx
npm install handlebars     # For templating
npm install pdf-lib        # For PDF generation
npm install nodemailer     # For email sending (optional)

エラー処理

  • データ不足: 任意のフィールドにはデフォルト値を設定します
  • 無効な住所: 住所の形式を検証します
  • テンプレートのエラー: レンダリングエラーを適切に処理します
  • ファイルの権限: 書き込みエラーに対処します

高度な機能

メール連携:

async function sendLetter(letterPath, recipientEmail, subject) {
  const transporter = nodemailer.createTransporter(config);

  await transporter.sendMail({
    from: senderEmail,
    to: recipientEmail,
    subject: subject,
    html: '<p>Please find attached letter.</p>',
    attachments: [{ path: letterPath }]
  });
}

署名連携:

async function addDigitalSignature(letterPath, signatureImagePath) {
  // Add scanned signature image to letter
  // Position at signature block
}

テンプレートライブラリ:

const letterTemplates = {
  coverLetter: './templates/cover-letter.hbs',
  businessInquiry: './templates/business-inquiry.hbs',
  recommendation: './templates/recommendation.hbs',
  thankYou: './templates/thank-you.hbs'
};

コメント