😉

Claude Desktop を使いやすくするスニペット

2025/02/27に公開

Claude Desktop をちょっと便利にするスニペットを紹介します

つらみ

私は MCP が便利なので Claude Desktop をよく使っています

https://zenn.dev/bm_sms/articles/claude_mcp_aggregate

MCP を利用することで色々できて便利なんですが

  • 日本語入力者にとって Enter での送信は誤送信が多くて辛い
    • 特に Claude では、編集したときに変換後の Enter でも送信されてしまう
    • Web 版では ChatGPT Ctrl+Enter Sender 等の拡張機能で回避しているが、Desktop アプリなのでそうもいかない
  • MCP をたくさん使うと安全なコマンドは Auto Approve したくなる
  • MCP でエージェントな動きをさせると1レスポンスの上限に捕まりやすく、「続けて」入力が必要になりがち

等、かゆいところに手が届かない感があります

スクリプトで解決する

Claude Desktop では Chrome 拡張機能等は利用できませんが、設定をすれば Developer Tool を起動して任意のスクリプトを実行することが可能です!

手順は

  1. developer_settings.json に allowDevTools のフラグを設定する
$ echo '{"allowDevTools": true}' > ~/Library/Application\ Support/Claude/developer_settings.json
  1. Claude Desktop 上で Command + Option + Shift + i を押下する

これで Developer Tool が起動します

あとは、以下のスニペットを「https://claude.ai/new 」のウィンドウに貼り付けて実行します

snippet.js
/**
 * Claude Desktop の補助スクリプト
 * Cmd + Alt + Shift + i で Developer Console を開いて、このスクリプトを貼り付けることで実行できる
 * 2つウィンドウが開くけど、「AI」というログが出てる方(https://claude.ai/new の方)に貼り付ける
 *
 * 機能:
 * - trustedTools については Auto Approve させる
 * - Claude が1レスポンスの上限で止まってしまった場合は自動で「続けて」と送信する
 * - Enter を送信ではなく改行に割り当てる。Shift 以外の修飾キー有りの場合はなにもしないので Ctrl + Enter や Command + Enter で送信できる
 */

const trustedTools = [
  "fetch",
  "search",
  "read_file",
  "read_multiple_files",
  "list_directory",
  "directory_tree",
  "search_files",
  "get_file_info",
  "list_allowed_directories",
]

let lastExecution = 0
const COOLDOWN_MS = 500

const autoContinue = () => {
  const latestMessageBox = Array.from(
    document.querySelectorAll(`div[data-test-render-count`)
  ).at(-1)

  if (latestMessageBox === undefined) {
    console.log("❌ No message box found")
    return false
  }

  const warningMessage = latestMessageBox.querySelector(
    "div[data-testid=message-warning]"
  )
  if (
    warningMessage === null ||
    warningMessage.innerText !==
      "Claude’s response was limited as it hit the maximum length allowed at this time."
  ) {
    console.log("❌ No warning message found")
    return false
  }

  document.querySelector("div[contenteditable=true]").innerText = "続けて"
  document.querySelector("button[aria-label='Send Message']").click()

  return true
}

const autoApprove = () => {
  console.log("🔍 Checking mutations...")

  const dialog = document.querySelector("[role=dialog]")
  if (!dialog) {
    console.log("❌ No dialog found")
    return false
  }

  const toolName = dialog
    .querySelector("button > div")
    .textContent?.match(/Run (\S+) from/)
    ?.at(1)
  const allowButton = dialog.querySelector("[type=button]")

  if (!toolName || !allowButton) {
    console.error(
      "❌ No tool name or Allow button found",
      toolName,
      allowButton
    )
    return false
  }

  console.log("🛠️ Tool name:", toolName)

  if (trustedTools.includes(toolName)) {
    console.log("🚀 Auto-approving tool:", toolName)
    allowButton.click()
  } else {
    console.log("❌ Tool not in trusted list:", toolName)
  }

  return true
}

const handleKeydown = (event) => {
  const contentEditableAttr = event.target.getAttribute("contenteditable")
  if (contentEditableAttr !== "true") {
    console.log(
      "[DEBUG] contenteditable 属性が設定されていないのでスキップします。",
      event.target
    )
    return
  }

  // Enterキーのみの場合(Shift 以外の修飾キーなし)
  const isOnlyEnter = event.key === "Enter" && !(event.ctrlKey || event.metaKey)

  // Enter を無効にして改行判定の Shift+Enter に差し替える
  // 装飾キーがない場合はハンドリングされないので、適当な装飾キーをつければ送信できる
  if (isOnlyEnter) {
    console.log("[DEBUG] Enterキーのため処理を実行")
    // Enter単体でのイベントをキャンセル
    event.preventDefault()
    event.stopPropagation()
    event.stopImmediatePropagation()

    // Shift+Enterイベントを新たに発行する
    console.log("[DEBUG] Shift+Enter イベントを発行します")
    const newEvent = new KeyboardEvent("keydown", {
      bubbles: true,
      cancelable: true,
      key: "Enter",
      code: "Enter",
      keyCode: 13,
      which: 13,
      shiftKey: true,
      ctrlKey: false,
      metaKey: false,
    })

    event.target.dispatchEvent(newEvent)
  } else {
    console.log("[DEBUG] Enter キーでないのでなにもしません")
  }
}

// Execute Script
const observer = new MutationObserver((mutations) => {
  const now = Date.now()

  if (now - lastExecution < COOLDOWN_MS) {
    console.log("🕒 Still in cooldown period, skipping...")
    return
  }

  try {
    autoContinue()
    autoApprove()
  } catch (error) {
    console.error("❌ Error in observer:", error)
  } finally {
    lastExecution = now
  }
})

const entry = () => {
  if (window.customScriptEnabled) {
    console.log("カスタムスクリプトはすでに有効なのでスキップします。")
    return
  }

  // Start observing
  console.log(
    "👀 AutoApprove するツールの observing を開始します。",
    trustedTools
  )
  observer.observe(document.body, {
    childList: true,
    subtree: true,
  })

  console.log("👀 キーバインドの変更を当てる Listener を設定しました。")
  // WARN: Claude 本体の Listener が document に capture で設定されているので window & capture でないと先にハンドリングできない
  window.addEventListener("keydown", handleKeydown, { capture: true })

  window.customScriptEnabled = true

  console.log("✅ カスタムスクリプトが有効になりました。")
}

entry()

これで Claude Desktop アプリを閉じるまでの間は

  • Enter で送信されなくなり、Ctrl + Enter or Command + Enter で送信
  • 指定したツールは自動で Approve される
  • 1リクエストのトークン上限で止まった場合は自動で再開

ができるようになります 👏

参考

  • Auto Approve は reddit で紹介されていたものを拡張しました

Discussion