昨日は、執筆ツールとしてCursorが使えるのかどうか、という観点から、文字数のカウントを表示させる方法について書きました。
本日も、Cursorを執筆に使うために必要な設定についてお話しします。
お題は、Cursorからnoteに記事を投稿する方法です。
わたしは今年の年初から1日も欠かさずにnoteの投稿をしています。わたしの執筆活動というと、小説だけではなく、noteの投稿も欠くことのできないものになっています。
そんななかで、執筆ツールをObsidianからCursorに集約させるためには、note執筆の効率性も上げていかなければなりません。Cursorから直接noteに投稿できるかどうかは、その観点からも重要と言えます。
ご存じの通り、noteは直接記事を投稿するためのAPIを公開していません。WordPressですとAPIが公開されていますので、直接投稿することができますが、noteではできないのです。
そこで、Cursorからnoteに間接的に記事を投稿する方法を考えてみました
難しい話ではありません。
まず、Cursorのテキストをクリップボードにコピーします。
その上で、noteのサイトを開きます。
これだけです。但し、これをいちいち手動でやるのは面倒なので、スクリプトでやってみました。
noteのサイトが開けば、手動で新規に記事を作成してテキストを貼り付けるだけです。
スクリプトはJavaScriptで作りました。次の通りです(このスクリプトを実行するためにはNode.jsというツールが必要です。わたしは公式サイトからダウンロードしたものを使用しました。この記事の一番最後に、インストール方法について記載しておきます)。
// note-publisher.js
const fs = require('fs').promises;
const path = require('path');
const { exec } = require('child_process');
const yargs = require('yargs');
// コマンドライン引数の処理
const argv = yargs
.option('file', {
alias: 'f',
describe: '投稿するファイルのパス',
type: 'string',
demandOption: true
})
.option('clipboard', {
alias: 'c',
describe: 'クリップボードにコピーするかどうか',
type: 'boolean',
default: true
})
.option('open', {
alias: 'o',
describe: 'noteを自動で開くかどうか',
type: 'boolean',
default: true
})
.option('debug', {
alias: 'd',
describe: 'デバッグモード(クリップボード内容を表示)',
type: 'boolean',
default: false
})
.option('verify', {
alias: 'v',
describe: 'クリップボードの内容を検証',
type: 'boolean',
default: false
})
.help()
.argv;
// クリップボードにコピーする関数(改善版)
async function copyToClipboard(text) {
return new Promise((resolve, reject) => {
const platform = process.platform;
if (platform === 'darwin') {
// macOS - より確実な方法
const fs = require('fs');
const tempFile = '/tmp/cursor-note-temp.txt';
try {
// 一時ファイルに書き込み
fs.writeFileSync(tempFile, text, 'utf8');
// pbcopyで読み込み
exec(`pbcopy < "${tempFile}"`, (error) => {
// 一時ファイルを削除
try {
fs.unlinkSync(tempFile);
} catch (e) {
// 削除エラーは無視
}
if (error) {
reject(error);
} else {
resolve();
}
});
} catch (error) {
reject(error);
}
} else if (platform === 'win32') {
// Windows
const tempFile = require('os').tmpdir() + '\\cursor-note-temp.txt';
const fs = require('fs');
try {
fs.writeFileSync(tempFile, text, 'utf8');
exec(`type "${tempFile}" | clip`, (error) => {
try {
fs.unlinkSync(tempFile);
} catch (e) {}
if (error) {
reject(error);
} else {
resolve();
}
});
} catch (error) {
reject(error);
}
} else {
// Linux
exec(`echo "${text.replace(/"/g, '\\"')}" | xclip -selection clipboard`, (error) => {
if (error) {
reject(error);
} else {
resolve();
}
});
}
});
}
// ブラウザでnoteを開く(改善版)
function openNote() {
const platform = process.platform;
let command;
if (platform === 'win32') {
command = 'start';
} else if (platform === 'darwin') {
command = 'open';
} else {
command = 'xdg-open';
}
console.log('🌐 note.comを開いています...');
exec(`${command} "https://note.com/?popover=postings"`);
// ページ読み込み待機のメッセージ
console.log('⏰ ページの読み込みを待ってからペーストしてください(約3-5秒後)');
}
// Obsidianリンクやマークアップをクリーンアップ
function cleanContent(content) {
return content
// Obsidianリンク [[link]] や [[link|display]] を変換
.replace(/!?\[\[([^|\]]+)(?:\|[^\]]+)?\]\]/g, "$1")
// コメント削除
.replace(//gs, "")
// 余分な改行を整理
.replace(/\n{3,}/g, '\n\n')
.trim();
}
// Markdownファイルを処理
async function processMarkdownFile(filePath) {
try {
// ファイル存在チェック
await fs.access(filePath);
// ファイル内容を読み込み
const content = await fs.readFile(filePath, 'utf8');
// ファイル名からタイトルを取得(拡張子を除く)
const title = path.basename(filePath, '.md');
// コンテンツをクリーンアップ
const cleanedContent = cleanContent(content);
// タイトルと内容を結合
const fullText = `${title}\n\n${cleanedContent}`;
return {
title,
content: cleanedContent,
fullText,
success: true
};
} catch (error) {
return {
success: false,
error: error.message
};
}
}
// メイン処理
async function main() {
console.log('📝 note.com投稿準備...');
// ファイルパスの取得
let filePath = argv.file;
// 相対パスを絶対パスに変換
if (!path.isAbsolute(filePath)) {
filePath = path.resolve(process.cwd(), filePath);
}
console.log(`📖 ファイル処理中: ${filePath}`);
// ファイルを処理
const result = await processMarkdownFile(filePath);
if (!result.success) {
console.error(`❌ エラー: ${result.error}`);
process.exit(1);
}
console.log(`✅ 処理完了:`);
console.log(` タイトル: ${result.title}`);
console.log(` 文字数: ${result.fullText.length}文字`);
// クリップボードにコピー
if (argv.clipboard) {
try {
await copyToClipboard(result.fullText);
console.log('📋 クリップボードにコピーしました');
// デバッグモード
if (argv.debug) {
console.log('=== デバッグ: コピーされた内容(最初の200文字) ===');
console.log(result.fullText.substring(0, 200) + '...');
console.log('=== デバッグ: コピーされた内容(最後の200文字) ===');
console.log('...' + result.fullText.substring(result.fullText.length - 200));
}
// クリップボード検証
if (argv.verify) {
setTimeout(() => {
exec('pbpaste', (error, stdout) => {
if (!error) {
const clipboardContent = stdout;
if (clipboardContent === result.fullText) {
console.log('✅ クリップボード検証: 正常にコピーされています');
} else {
console.log('❌ クリップボード検証: 内容が一致しません');
console.log(`期待値の長さ: ${result.fullText.length}`);
console.log(`実際の長さ: ${clipboardContent.length}`);
}
}
});
}, 500);
}
} catch (error) {
console.warn('⚠️ クリップボードコピー失敗:', error.message);
console.log('--- コピー用テキスト(手動コピー用) ---');
console.log(result.fullText);
console.log('--- ここまで ---');
}
}
// noteを開く
if (argv.open) {
console.log('🌐 note.comを開いています...');
openNote();
}
console.log('🎉 完了!noteで投稿してください');
}
// エラーハンドリング
process.on('uncaughtException', (error) => {
console.error('予期しないエラー:', error.message);
process.exit(1);
});
// スクリプト実行
if (require.main === module) {
main().catch(console.error);
}
package.jsonはこちら。
{
"scripts": {
"note": "node note-publisher.js",
"note-debug": "node note-publisher.js --debug --verify",
"note-verify": "node note-publisher.js --verify"
}
}
task.jsonはこちら。
{
"label": "note.com投稿(検証付き)",
"type": "shell",
"command": "node",
"args": [
"/Users/ユーザー名/wordpress-publisher/note-publisher.js",
"-f",
"${file}",
"--verify"
],
"options": {
"cwd": "/Users/ユーザー名/wordpress-publisher"
}
}
keybindings.jsonはこちら。
{
"key": "ctrl+shift+n",
"command": "workbench.action.tasks.runTask",
"args": "note.com投稿",
"when": "editorTextFocus && resourceExtname == '.md'"
}
これで、
- Cursorで執筆
- 記事ファイルを選択
- キーボードショートカット実行
- cmd+Shift+N → note.com投稿
- ObsidianはClaudeと連携するための原稿保管庫
- バックアップはObsidian SyncとGitHub
というフローができあがりました。
物書きとしての創作ワークフローが大幅に効率化されたと思います。これで、執筆に集中でき、投稿作業は瞬時に完了です。
Node.jpインストール方法
Windows
公式インストーラー
- 公式サイトからダウンロード
- nodejs.org にアクセス
- 「LTS」版をダウンロード(Long Term Support版、安定版)
- Windows Installer (.msi) をダウンロード
- インストール実行
- ダウンロードした .msi ファイルを実行
- インストールウィザードに従って進行
- 「Add to PATH」にチェックが入っていることを確認
- 「Automatically install the necessary tools」にチェック
macOS
公式インストーラー
- nodejs.org から macOS Installer (.pkg) をダウンロード
- ダウンロードしたファイルを実行してインストール
package.jsonはこちら。
{ "scripts": { "note": "node note-publisher.js", "note-debug": "node note-publisher.js --debug --verify", "note-verify": "node note-publisher.js --verify" } }
task.jsonはこちら。
{ "label": "note.com投稿(検証付き)", "type": "shell", "command": "node", "args": [ "/Users/ユーザー名/wordpress-publisher/note-publisher.js", "-f", "${file}", "--verify" ], "options": { "cwd": "/Users/ユーザー名/wordpress-publisher" } }
keybindings.jsonはこちら。
{ "key": "ctrl+shift+n", "command": "workbench.action.tasks.runTask", "args": "note.com投稿", "when": "editorTextFocus && resourceExtname == '.md'" }
これで、
- Cursorで執筆
- 記事ファイルを選択
- キーボードショートカット実行
- cmd+Shift+N → note.com投稿
- ObsidianはClaudeと連携するための原稿保管庫
- バックアップはObsidian SyncとGitHub
というフローができあがりました。
物書きとしての創作ワークフローが大幅に効率化されたと思います。これで、執筆に集中でき、投稿作業は瞬時に完了です。
Node.jpインストール方法
Windows
公式インストーラー
- 公式サイトからダウンロード
- nodejs.org にアクセス
- 「LTS」版をダウンロード(Long Term Support版、安定版)
- Windows Installer (.msi) をダウンロード
- インストール実行
- ダウンロードした .msi ファイルを実行
- インストールウィザードに従って進行
- 「Add to PATH」にチェックが入っていることを確認
- 「Automatically install the necessary tools」にチェック
macOS
公式インストーラー
- nodejs.org から macOS Installer (.pkg) をダウンロード
- ダウンロードしたファイルを実行してインストール
コメント