2025年4月14日月曜日

git rm で削除したファイルを復元する方法

git rm で削除したファイルを復元する方法

概要

すでにリモート側にも push している状態から復元する方法を紹介します

環境

  • git 2.47.0

ファイルとコミットハッシュの確認

  • git log -- src/utils/logger.js

復元

  • git checkout fe3c7b64cd81ae68b797a7804960accaae2fbfd4 – src/utils/logger.js

最後に

過去の実装を戻したい場合に使えます

2025年4月13日日曜日

emacs の copilot-chat で copilot-chat-add-current-buffer を使うと現在のバッファを質問のコンテクストとして加えることができる

emacs の copilot-chat で copilot-chat-add-current-buffer を使うと現在のバッファを質問のコンテクストとして加えることができる

概要

タイトルの通りです
手元のコードを学習させたりコンテキストとして持たせたりするベストプラクティスは何がいいのでしょうか

環境

  • emacs 30.1
  • copilot-chat 2.0.1

チャット履歴

まずはコンテキストを与えないで質問します

* *[11:06:32]* You                 :you:
Please provide a sample python code that uses the Message class.

** *[11:06:34]* Copilot(gpt-4o)                 :copilot:
*** Sample Python Code Using the Message Class

Here is a sample Python code that demonstrates the use of the `Message` class from the `email.message` module. This example creates an email message, sets its headers, and adds content.

#+BEGIN_SRC python
from email.message import EmailMessage

def create_email():
    # Create an instance of the EmailMessage class
    msg = EmailMessage()
    
    # Set the email headers
    msg['From'] = 'sender@example.com'
    msg['To'] = 'recipient@example.com'
    msg['Subject'] = 'Sample Email Subject'
    
    # Add the email content
    msg.set_content('This is the plain text content of the email.')
    
    # Add an HTML version of the content
    msg.add_alternative("""
    <html>
        <body>
            <h1>This is the HTML content of the email.</h1>
            <p>It includes <b>bold</b> and <i>italic</i> text.</p>
        </body>
    </html>
    """, subtype='html')
    
    return msg

# Example usage
if __name__ == "__main__":
    email_message = create_email()
    print(email_message)
#+END_SRC

*** Key Points
+ The `EmailMessage` class is part of Python's built-in `email` module.
+ The `set_content` method is used to add plain text content.
+ The `add_alternative` method is used to add an HTML version of the email content.
+ Headers like ~From~, ~To~, and ~Subject~ are set using dictionary-like syntax.

次に以下のファイルを開いて copilot-chat-add-current-buffer で開いたファイルをコンテキストに追加します

  • vim my_lib/utils.py
import time


class Message:
    def say(self, **kwargs):
        print("Start say")
        print(kwargs)
        time.sleep(10)
        raise ValueError()
        # print("End say")

そして更に質問します
するとコンテキストとして追加したクラスをちゃんと使って修正してくれます

* *[11:07:47]* You                 :you:
Use the Message class in my_libs/utils.py

** *[11:07:48]* Copilot(gpt-4o)                 :copilot:
*** Sample Code Using the ~Message~ Class in ~my_libs/utils.py~

Below is an example of how to use the ~Message~ class from your provided code in ~my_libs/utils.py~.

#+BEGIN_SRC python
from my_libs.utils import Message

def main():
    # Create an instance of the Message class
    msg = Message()
    
    # Call the say method with keyword arguments
    try:
        msg.say(text="Hello, World!", sender="user@example.com", recipient="recipient@example.com")
    except ValueError:
        print("An error occurred while executing the say method.")

if __name__ == "__main__":
    main()
#+END_SRC

*** Explanation
+ The ~Message~ class is imported from ~my_libs/utils.py~.
+ An instance of the ~Message~ class is created using ~msg = Message()~.
+ The ~say~ method is called with keyword arguments like ~text~, ~sender~, and ~recipient~.
+ A ~try-except~ block is used to handle the ~ValueError~ raised by the ~say~ method.

更に Message クラスに機能を追加する依頼をしてみます
ちゃんと元の Message クラスの構成をキープしつつ追加してくれました

* *[11:11:04]* You                 :you:
Add a field to the Message class to manage the message body.


** *[11:11:06]* Copilot(gpt-4o)                 :copilot:
*** Updated ~Message~ Class with a Field for the Message Body

Below is the updated ~Message~ class with a new field to manage the message body.

#+BEGIN_SRC python
import time

class Message:
    def __init__(self, body=""):
        """
        Initialize the Message class with an optional body field.
        """
        self.body = body

    def say(self, **kwargs):
        """
        Print the message body and additional keyword arguments.
        """
        print("Start say")
        print("Message Body:", self.body)
        print("Additional Arguments:", kwargs)
        time.sleep(10)
        raise ValueError()
        # print("End say")
#+END_SRC

*** Explanation of Changes
+ A new field ~body~ is added to the ~Message~ class, initialized in the constructor (~__init__~).
+ The ~say~ method now prints the value of the ~body~ field along with the additional keyword arguments.

*** Example Usage
Here is an example of how to use the updated ~Message~ class:

#+BEGIN_SRC python
from my_libs.utils import Message

def main():
    # Create an instance of the Message class with a custom body
    msg = Message(body="This is the message body.")
    
    # Call the say method with keyword arguments
    try:
        msg.say(sender="user@example.com", recipient="recipient@example.com")
    except ValueError:
        print("An error occurred while executing the say method.")

if __name__ == "__main__":
    main()
#+END_SRC

*** Key Points
+ The ~body~ field allows you to store and manage the message content directly within the ~Message~ class.
+ The ~say~ method now includes the ~body~ field in its output for better context.


* ╭──── Chat Input ────╮ :copilotChatInput:

最後に

emacs の copilot-chat でコンテキストを追加する方法を紹介しました
こんな感じで公開されていないコードや手元にあるライブラリ情報をコンテキストとして追加すればそれを考慮したコードを書いてくれるようです

以下懸念点です

  • 複数のバッファをコンテキストとして追加するにはどうすればいいのか
  • 一度コンテキストとして追加したバッファを復元するにはどうすればいいのか
    • chat を開くたびに追加しなければならない

参考サイト

2025年4月12日土曜日

Claude Desktop を使って MCP Server に入門してみる

Claude Desktop を使って MCP Server に入門してみる

概要

前回 Claude を Mac にインストールしてみました
今回は Claude Desktop の MCP クライアントとしての機能を使ってみます
サーバ側は https://github.com/github/github-mcp-server を使います
この MCP サーバは Github にある自身のリポジトリや Issue などを検索するのに役立ちます

環境

  • macOS 15.4
  • Claude desktop 0.9.2
  • docker 28.0.4

MCP サーバの追加

Claude desktop に MCP サーバを追加します
設定ファイルがあるのでそれに MCP サーバの定義を追加します

claude_desktop_config.json 自体は初回にはないので作成すれば OK です

  • vim ~/Library/Application\ Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "github": {
      "command": "docker",
      "args": [
        "run",
        "-i",
        "--rm",
        "-e",
        "GITHUB_PERSONAL_ACCESS_TOKEN",
        "ghcr.io/github/github-mcp-server"
      ],
      "env": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "<YOUR_TOKEN>"
      }
    }
  }
}

YOUR_TOKEN の部分には Github から取得したトークンを入力してください
MCP サーバはほぼすべての Github API を使用するので権限は多めに設定するのがいいかなと思います
MCP サーバに呼び出させたい API だけ権限を付与するのもありです

このように記載すると Claude 起動時に docker を使って自動で MCP サーバを起動してくれます

動作確認

初回起動時は Claude から docker へのアクセス権限がなく失敗するので許可してから再起動しましょう

MCP サーバが docker で起動していれば OK です

docker ps 
CONTAINER ID   IMAGE                              COMMAND                   CREATED        STATUS        PORTS     NAMES
3b725ec0e9a8   ghcr.io/github/github-mcp-server   "./github-mcp-server…"   1 second ago   Up 1 second             amazing_lumiere

これで Github に関する質問をすると MCP サーバを介して検索してくれます
最初に get_me という命令を使って自分の Github の情報を Claude が取得してもいいか聞かれるので許可します

こんな感じで Claude が MCP サーバを介して Github から必要な情報を取得してくれます

最後に

他にも公開されている MCP サーバはたくさんあるので助けてほしい項目のサーバがあれば追加してみるといいかなと思います

基本的には RAG に近い機能ではありますが MCP サーバを構築/追加するだけであるコンテキストに特化した情報が簡単に検索できるようになるので便利です
また MCP サーバというかアプリケーションなので例えば Github であれば Issue を作成したり Pull-Request の操作なども Claude desktop からできるようにもなります

起動した MCP サーバは Claude desktop を閉じれば自動的に停止します

参考サイト

2025年4月11日金曜日

git で管理してるファイルから拡張子の一覧を取得する方法

git で管理してるファイルから拡張子の一覧を取得する方法

概要

git ls-files を使って取得する方法を紹介します

環境

  • macOS 15.4
  • git 2.49.0

コマンド

  • git ls-files | grep -oE '\.[^.\/]+$' | sort | uniq
.el
.gitignore
.lock
.md
.rb
.ruby-version
.sh
.yasnippet
.yml

カウント数付き

  • git ls-files | grep -oE '\.[^.\/]+$' | sort | uniq -c | sort -nr
  15 .yasnippet
  13 .el
   8 .rb
   2 .yml
   2 .md
   1 .sh
   1 .ruby-version
   1 .lock
   1 .gitignore

カウントの多い順に csv にして出力する

  • git ls-files | grep -oE '\.([^.\/]+)$' | sed 's/^\.//' | sort | uniq -c | sort -nr | awk '{print $2}' | paste -sd, -

最後に

拡張子のないファイルは取得されません

Claude クライアントを MacOS にインストールする

Claude クライアントを MacOS にインストールする

概要

Claude はマルチプラットフォームな AI クライアントツールです
今回は Mac にインストールして使ってみました

環境

  • macOS 15.4
  • Claude desktop 0.9.2

インストール

  • brew install claude --cask

サインイン

メールアドレスで登録できます

無料プランを選択しましょう

会話する

あとはチャットすれば OK です

最後に

次回は MCP Server と連携してみます

2025年4月10日木曜日

emacs で copilot に質問する

emacs で copilot に質問する

概要

過去に紹介した copilot.el だと補完はしてくれますがチャットはできないので別途拡張をインストールする必要があります

環境

  • Ubuntu 24.04
  • emacs 29.1

インストール

  • package-list-packages
  • copilot-chat
  • i -> x -> y

チャットする

  • M-x copilot-chat-display

初回はログインが必要

チャット用のバッファが起動したら質者を入力し送信します
送信する場合はエンターではなく C-c C-c で送信できます

するとワンタイムコードが発行されるので copilot を使うユーザでブラウザの https://github.com/login/device にアクセスし表示されているワンタイムコードを入力します
これで copilot-chat が使えるようになります

結果を確認する

C-c C-c でチャットを送信すると以下のように結果が返ってきます

結果を別のバッファに反映させるには copilot-chat-yank を実行します

キーバインドで行いたい場合は以下の設定が良いようです

(use-package copilot-chat
  :bind (:map global-map
            ("C-c C-y" . copilot-chat-yank)
            ("C-c M-y" . copilot-chat-yank-pop)
            ("C-c C-M-y" . (lambda () (interactive) (copilot-chat-yank-pop -1))))
)

最後に

emacs から copilot にチャットを送信してみました
Symbol's function definition is void: json-parse-buffer になる場合はそもそも copilot のチャット機能が無効化されている可能性があります

もしくは emacs のバージョンが古いと出るので 30 台にバージョンアップしてください

参考サイト

2025年4月9日水曜日

Github Copilot を CLI から使う

Github Copilot を CLI から使う

概要

コマンドの説明とコマンドの推論だけできるようです

環境

  • gh 2.69.0
  • Ubuntu 24.04

インストール

(type -p wget >/dev/null || (sudo apt update && sudo apt-get install wget -y)) \
	&& sudo mkdir -p -m 755 /etc/apt/keyrings \
        && out=$(mktemp) && wget -nv -O$out https://cli.github.com/packages/githubcli-archive-keyring.gpg \
        && cat $out | sudo tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null \
	&& sudo chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \
	&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null \
	&& sudo apt update \
	&& sudo apt install gh -y

ログイン

  • gh auth login

https://github.com/login/device にアクセスしてコードを入力する方法でログインします

? What account do you want to log into? GitHub.com
? What is your preferred protocol for Git operations on this host? HTTPS
? Authenticate Git with your GitHub credentials? Yes
? How would you like to authenticate GitHub CLI? Login with a web browser

! First copy your one-time code: xxxx-xxxx
Press Enter to open github.com in your browser...
! Failed opening a web browser at https://github.com/login/device
  exec: "xdg-open,x-www-browser,www-browser,wslview": executable file not found in $PATH
  Please try entering the URL in your browser manually
? Authentication complete.
- gh config set -h github.com git_protocol https
? Configured git protocol
! Authentication credentials saved in plain text
? Logged in as username

使う

  • gh copilot explain "ls"

最後に

あとは suggest コマンドなどが使えます

参考サイト

2025年4月8日火曜日

Google フォトで画像を回転させるショートカット

Google フォトで画像を回転させるショートカット

概要

忘れるのでメモ

環境

  • Google Photo ブラウザ版 (2025/04/08 時点)

方法

  • Shift + r

2025年4月7日月曜日

no such file or directory _brew_services の対処方法

no such file or directory _brew_services の対処方法

概要

compinit:527: no such file or directory: /opt/homebrew/share/zsh/site-functions/_brew_services

の対応

環境

  • macOS 15.3.2
  • zsh 5.9

対応方法

  • brew cleanup && rm -f $ZSH_COMPDUMP

最後に

いつも忘れるのでメモ

参考サイト

2025年4月4日金曜日

nvm で管理している nodejs をアップグレードする方法

nvm で管理している nodejs をアップグレードする方法

概要

忘れるのでメモ

環境

  • macOS 15.3.2
  • nvm 0.40.2

nvm 自体のバージョンアップ

https://hawksnowlog.blogspot.com/2024/11/self-update-nvm.html

更新可能なバージョンの確認

  • nvm ls-remote

バージョンアップ

  • nvm install v20.19.0

バージョンアップしたバージョンをデフォルトに設定

基本的には同系列のバージョンアップであれば自動的にデフォルトで新しいバージョンを使うようになります
明示的に指定したいのであれば use を使います

  • nvm use 20.19.0

プロジェクトごとに指定する場合は

  • nvm use --save 20.19.0

2025年4月3日木曜日

Gitlab で renovate を試す

Gitlab で renovate を試す

概要

gitlab のリポジトリに対して renovate を実行してみました
CLI で直接実行する方法と CI を使う方法を紹介します

環境

  • Gitlab 17.8

CLI

  • LOG_LEVEL=debug renovate --platform=gitlab --endpoint=https://your-gitlab-endpoint/api/v4 --username=oauth2 --token=glpat-xxx user/repo

CI

renovate/renovate-runnerを自身の Gitlab にミラーして使います
fork なりミラーしたリポジトリ名を yaml に記載します
Gitlab.com であれば以下のままで OK です

スキャン対象のリポジトリを RENOVATE_EXTRA_FLAGS に追加することで管理するようです

stages:
  - test

include:
  - project: 'renovate/renovate-runner'
    file: '/templates/renovate.gitlab-ci.yml'

renovate:
  stage: test
  variables:
    RENOVATE_EXTRA_FLAGS: "--platform=gitlab --endpoint=https://your-gitlab-endpoint/api/v4 --username=oauth2 user/repo"
  rules:
    - if: '$CI_PIPELINE_SOURCE == "schedule"'
    - if: '$CI_PIPELINE_SOURCE == "push"'

CI 変数

RENOVATE_TOKEN は必須なので CI の変数に設定しましょう
設定する値は Gitlab の Personal Access Token で OK です

GITHUB_COM_TOKEN はあったほうがいいです
Github アカウントがある場合は Github の Personal Access Token を取得して設定しましょう

最後に

基本は CI を使って定期的に MR を作る感じになるかなと思います
ただ更新対象が自動で決まるのとデフォルトだと2MRしか作成してくれないのでそこは変数による調整が必要かもです

また当然ですがバージョンを固定しているケースは renovate でもバージョンを上げられないのでそこは手動によるバージョン管理になります

参考サイト

2025年4月1日火曜日

renovate を bitbucket で使ってみる

renovate を bitbucket で使ってみる

概要

前回ローカルで renovate を動かしてみました
今回は bitbucket と連携して使ってみます

bitbucket からアプリパスワードの作成

https://bitbucket.org/account/settings/app-passwords/ から作成できます
作成するパスワードに必要な権限は以下のとおりです

  • account -> Account: Read
  • team -> Workspace membership: Read
  • issue:write -> Issues: Write
  • pullrequest:write -> Pull requests: Write

renovate.json の修正

手動で実行するので schedule の部分はいつでも OK にします
特定の時間帯で renovate が PR を作成可能にする場合は時間を指定しましょう

{
  "extends": [
    "config:recommended"
  ],
  "enabledManagers": [
    "pipenv"
  ],
  "packageRules": [
    {
      "description": "Schedule all packages update on Sunday nights (9 PM - 12 AM)",
      "matchManagers": [
        "pipenv"
      ],
      "groupName": "pip dependencies",
      "schedule": ["* * * * *"],
      "automerge": true,
      "automergeType": "branch"
    }
  ],
  "pipenv": {
    "lockFileMaintenance": {
      "enabled": true,
      "schedule": ["* * * * *"]
    },
    "fileMatch": [
      "(^|/)Pipfile$"
    ]
  }
}

package.json の修正

bitbucket のユーザ名と先ほど発行したアプリパスワードを使って認証するように修正します
ユーザ名/パスワードは環境変数で渡せるようにしましょう

{
  "dependencies": {
    "renovate": "^39.222.1"
  },
  "scripts": {
    "renovate": "renovate --platform=bitbucket --username=$BITBUCKET_USERNAME --password=$BITBUCKET_TOKEN team-name/repo-name",
    "renovate_help": "renovate --help"
  }
}

team-name/repo-name の部分は自身の bitbucket 上で管理しているチーム名とリポジトリ名に変更してください
リポジトリ名を指定せずに --autodiscover=true にしても OK ですがリポジトリが大量にある場合は精査に時間がかかるのでリポジトリ名を指定したほうが速いです

トラブルシュート: PR が作成されない場合は

Renovate 用のアカウントを作成する必要があるので作成しましょう
そしてそのアカウントからアプリパスワードを取得し設定します

動作確認

とりあえずローカルで動くか試しましょう
認証情報の部分は適宜変更してください

  • BITBUCKET_USERNAME=name BITBUCKET_TOKEN=password LOG_LEVEL=debug npm run renovate

もしこれで master ブランチ上で更新するべきパッケージがあった場合には MR を作成してくれます

最後に

あとは renovate コマンドを定期的に実行する CI を設定するだけです
bitbucket の場合 Mend という専用のアプリがあるのでこれを使うのが一番簡単です

参考サイト

2025年3月31日月曜日

ローカルで renovate を実行し python のパッケージバージョンの管理をする

ローカルで renovate を実行し python のパッケージバージョンの管理をする

概要

renovate は nodejs で書かれた自動パッケージ更新ツールです
python の pipenv でも使えるのでローカルで試してみました
なおローカルで試す場合には実際にブランチやPRの作成はされません
本来は CI と組み合わせて使います

環境

  • macOS 15.3.2
  • node 20.18.2
    • renovate 39.222.1

インストール

  • nvm use 20.18.2 --save
  • npm i renovate

renovate.json の作成

{
  "extends": [
    "config:recommended"
  ],
  "enabledManagers": [
    "pipenv"
  ],
  "packageRules": [
    {
      "description": "Schedule all packages update on Sunday nights (9 PM - 12 AM)",
      "matchManagers": [
        "pipenv"
      ],
      "groupName": "pip dependencies",
      "schedule": [
        "* 21-23 * * 0"
      ],
      "automerge": true,
      "automergeType": "branch"
    }
  ],
  "pipenv": {
    "lockFileMaintenance": {
      "enabled": true,
      "schedule": [
        "monthly"
      ]
    },
    "fileMatch": [
      "(^|/)Pipfile$"
    ]
  }
}

package.json への追記

{
  "dependencies": {
    "renovate": "^39.222.1"
  },
  "scripts": {
    "renovate": "renovate --platform=local --dry-run=full",
    "renovate_help": "renovate --help"
  }
}

renovate 実行

  • RENOVATE_CONFIG_FILE=renovate.json npm run renovate

ローカルで実行する場合にはいろいろとオプションが必要です
コマンドの実体は以下です

  • RENOVATE_CONFIG_FILE=renovate.json renovate --platform=local --dry-run=full

これで Pipfile に書かれたパッケージが更新されるはずです

npx で直接実行もできます

  • RENOVATE_CONFIG_FILE=renovate.json npx renovate --platform=local --dry-run=full

local 動作では --dry-run=lookup になっており Pipfile.lock が作成されないので上書きします
LOG_LEVEL=debug を指定すると詳細なログが確認できます
またまだ renovate.json がない場合には --require-config=ignore を設定してください

エラーが出なければ OK です

動作確認

通常 renovate は更新があった場合にブランチからのPRが作成されます
ただ platform=local 実行の場合にはブランチの作成が許可されていないので platform=bitbucket などを指定して実際の挙動を確認しましょう

== の場合

当然ですがバージョンが固定されているので更新されません

isort = "==6.0.0"

で renovate を実行しても 6.0.1 には更新されない

~=の場合

この場合は 6.0.1 に更新されます

isort = "~=6.0.0"

で Pipfile.lock が更新され 6.0.1 がインストールされます

最後に

ローカルで renovate を試してみました
ローカルでは設定ファイルの確認くらいしかできないので実際はリモートリポジトリと組み合わせて使います
次回は bitbucket と組み合わせてみたいと思います

参考サイト

2025年3月28日金曜日

emacs で gptel を使ってローカルで起動した ollama と接続してみる

emacs で gptel を使ってローカルで起動した ollama と接続してみる

概要

前回 Openhermes をローカルに構築しました
今回は emacs の gptel を使って Openhermes と通信してみます
なお今回の記事はすでにローカルで Openhermes で起動している前提で話を進めます

環境

  • macOS 15.3.2
  • ollama 0.6.2
  • Openhermes v2.5
  • emacs 30.1
  • gptel 0.9.8

gptel インストール

  • M-x package-list-packages
  • gptel -> i -> x

gptel 設定ファイル作成

  • vim .emacs.d/site-lisp/llm/init.el
;; OPTIONAL configuration
(setq
 gptel-model 'openhermes:latest
 gptel-backend
 (gptel-make-ollama
  "Ollama"
  :host "localhost:11434"
  :stream t
  :models '(openhermes:latest)))

動作確認

gptel

M-x gptel を実行すると Openhermes とやりとりするバッファを選択するので *Ollama* バッファを選択します

すると ### というプロンプトになるのでここに質問を入力します
質問を入力したら C-c RET で質問を送信できます

gptel-send

gptel-send は現在のカーソル位置より上部のテキストを送信します
また返答も現在のバッファにそのまま書き込まれます

gptel-rewrite

gptel-rewrite はリファクタなどに使えます
現在選択している部分を上書きしてくれる機能です
リファクタする際に一行ずつやリファクタする方法などを追加で送信することもできます

最後に

他にもいろいろ機能はありそうです
Tools モードを使うと特定のパスに設定ファイルを作成してくれるタスクをインタラクティブに生成することもできるようです

copilot-mode と混同しているとキーバインドが同じケースがあるので別の割当てをする必要がありそうです

copilot-mode はデフォルトで auto-complete のように自動で補完してくれます
gptel はデフォルトではそうでなく修正したい箇所や新規でコードを追加してい場合で質問して LLM 側に書いてもらう感じになります

おそらくどちらも同じ機能があるかなと思うので使い分けというよりかはどちらか一方を使い倒すほうがいいかなと思います

copilot は無料だと制限があるので無制限にかつ無料で使いたい場合には必然的に gptel になるかなと思います
ただ gptel は当然ですがローカルに LLM を動かす環境が必要になるのでそれなりのマシンスペックが要求される点に注意です

参考サイト

2025年3月27日木曜日

M2 Pro Mac mini で Openhermes を動かす

M2 Pro Mac mini で Openhermes を動かす

概要

Ollama を使ってローカルで動かします

環境

  • macOS 15.3.2
  • ollama 0.6.2
  • Openhermes v2.5

ollama インストール

  • brew install ollama

ollama 起動

  • ollama services run ollama

Openhermes 起動

  • ollama run openhermes

この時点でインタラクティブモードになり質問することもできます

Open-WebUI 起動

  • docker run -d -p 3000:8080 --add-host=host.docker.internal:host-gateway -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:main

動作確認

localhost:3000 にアクセスすると UI を経由して Openhermes を使うことができます

最後に

精度はそこそこと言った感じです
軽量で日本語にも対応してるのでローカルで動かす LLM としては十分かもしれません
ただそこまで回答に期待はしないほうがいいかもです

参考サイト

2025年3月25日火曜日

eslint を 8 系から 9 系にするメモ

eslint を 8 系から 9 系にするメモ

概要

設定ファイルのマイグレーションが必要です

環境

  • node 20.18.2
  • eslint 8.41.0 -> 9.22.0

マイグレーション

.eslintrc.js を eslint.config.mjs にマイグレーションします

  • npx @eslint/migrate-config .eslintrc.js

バージョンアップ

devDependencies の部分を手動で書き換えます

  • vim package.json
{
  "devDependencies": {
    "eslint": "^9.22.0"
  }
}

ちなみに最新バージョンの確認は npm outdated で確認します
あとはインストールするだけです

  • npm i

ちなみに "eslint": "^8.41.0" という感じで書いている場合に npm update すると 8 系の最新までは自動でアップデートしてくれます

最後に

npm だけを使っている場合には手動でバージョンを書き換えて update する必要があります

2025年3月24日月曜日

treemacs の workspace の設定ファイルがあるパス

treemacs の workspace の設定ファイルがあるパス

概要

複数のワークスペースを手動で追加したい場合には直接設定ファイルを編集しちゃうのが簡単です

環境

  • macOS 15.3.2
  • emacs 30.1

パス

~/.emacs.d/.cache/treemacs-persist

直接編集する

先に emacs を停止しましょう

treemacs-persist とバックアップファイルを直接編集します
例えば以下のように特定のディレクトリ配下のプロジェクトを一括で追加できます

echo "* Default" > ~/.emacs.d/.cache/treemacs-persist && for i in `ls | grep -v update_libs.sh`; do echo "** ${i}\n - path :: ~/data/repo/${i}"; done >> ~/.emacs.d/.cache/treemacs-persist
echo "* Default" > ~/.emacs.d/.cache/treemacs-persist\~ && for i in `ls | grep -v update_libs.sh`; do echo "** ${i}\n - path :: ~/data/repo/${i}"; done >> ~/.emacs.d/.cache/treemacs-persist\~

動作確認

これで再度 emacs を開くと treemacs にプロジェクトが追加されています

トラブルシューティング

なぜか .emacs.d/.cache/treemacs-persist が改行されるというなぞの現象になるので修正します
この現象は before-save-hook などで自動フォーマットが設定されている場合に自動的に treemacs-persist もフォーマットしてしまい発生してる可能性が高いです

  • cat ~/.emacs.d/.cache/treemacs-persist-at-last-error
# State when last error occurred on 2025-03-20 10:36:48
# Error was 'First item must be a workspace name' in line '*'

*
Default
**
python-try
-
path
::
~/data/repo/python-try
  • vim ~/.emacs.d/.cache/treemacs-persist
* Default
** python-try
- path :: ~/data/repo/python-try

最後に

面倒な場合は直接キャッシュファイルを編集しちゃいましょう
フォーマットがあるのでそこだけ気をつけてください

2025年3月22日土曜日

Github Actions で elisp の lint をする方法

Github Actions で elisp の lint をする方法

概要

elisp-autofmt を使ってフォーマットした結果 git diff で差分があった場合はエラーにすることで lint チェックします

環境

  • emacs 30.1

format_elisp_files.sh

フォーマットするためのシェルスクリプトです
内部で emacs を使って elisp-autofmt を実行しフォーマットします
ls-files で取得するファイル名は適宜変更してください

for file_name in `git ls-files files | egrep -e 'el|emacs'`
do
emacs --batch $file_name -l ~/.emacs.d/elpa/elisp-autofmt-*/elisp-autofmt.el --eval "
(progn
  (let ((use-stdout nil))
    (dolist (buf (buffer-list))
      (with-current-buffer buf
        (when (buffer-file-name)
          (setq buffer-undo-list t) ;; Disable undo.
          (if use-stdout
              (progn
                (elisp-autofmt-buffer)
                (princ (buffer-substring-no-properties (point-min) (point-max))))
            (princ buffer-file-name)
            (princ \"\n\")
            (elisp-autofmt-buffer-to-file)))))))"
done

.github/workflows/elisp_lint.yml

ame: Emacs Lisp Format Check

on:
  push:
  pull_request:

jobs:
  format-check:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Install Emacs
        run: sudo apt-get install -y emacs-nox

      - name: Install pyenv
        uses: actions/setup-python@v5
        with:
          python-version: "3.12.9"

      - name: Install elisp-autofmt into emacs
        run: |
          mkdir -p ~/.emacs.d/elpa/elisp-autofmt-latest/
          wget -O ~/.emacs.d/elpa/elisp-autofmt-latest/elisp-autofmt.el https://codeberg.org/ideasman42/emacs-elisp-autofmt/raw/branch/main/elisp-autofmt.el

      - name: Run format_elisp_files.sh
        run: sh ./format_elisp_files.sh

      - name: Check diff
        run: git diff --exit-code

最後に

これで CI 上で elisp の lint ができます

参考サイト

2025年3月21日金曜日

emacs に elisp-autofmt を入れて elisp のフォーマットをしてみる

emacs に elisp-autofmt を入れて elisp のフォーマットをしてみる

概要

emacs の設定ファイルである el ファイルをフォーマットする方法を紹介します

環境

  • macOS 15.3.2
  • emacs 30.1
  • python 3.12.9

インストール

  • package-list-packages
  • elisp-autofmt -> x -> y

フォーマット実行

  • M-x elisp-autofmt-buffer

emacs を実行しているパスで python コマンドが使えないとダメなので注意しましょう

トラブルシューティング

実行時に

elisp-autofmt: error output
Note, built-in variable ‘image-scaling-factor’ not bound

となる場合は再度 elisp-autofmt-buffer を実行すればエラーが解消されます

自動起動

(add-hook 'emacs-lisp-mode-hook 'elisp-autofmt-mode)
(add-hook 'lisp-mode-hook 'elisp-autofmt-mode)

これで el ファイル保存時に自動でフォーマットしてくれます

うまく保存時にフォーマットしてくれない場合は以下でも OK です

(add-hook 'before-save-hook 'elisp-autofmt-buffer)

CLI で実行

emacs が必要ですが CLI からも実行できます
https://codeberg.org/ideasman42/emacs-elisp-autofmt/src/branch/main/elisp-autofmt-cmd.py を使っても OK です

for file_name in `git ls-files files | egrep -e 'el|emacs'`
do
emacs --batch $file_name -l ~/.emacs.d/elpa/elisp-autofmt-*/elisp-autofmt.el --eval "
(progn
  (let ((use-stdout nil))
    (dolist (buf (buffer-list))
      (with-current-buffer buf
        (when (buffer-file-name)
          (setq buffer-undo-list t) ;; Disable undo.
          (if use-stdout
              (progn
                (elisp-autofmt-buffer)
                (princ (buffer-substring-no-properties (point-min) (point-max))))
            (princ buffer-file-name)
            (princ \"\n\")
            (elisp-autofmt-buffer-to-file)))))))"
done

こちらも実行するパスに python が必要になるので注意しましょう

最後に

elisp-autofmt をインストールして elisp ファイルの自動フォーマットをしてみました

次回は CI を組み込んで lint してみたいと思います

参考サイト

2025年3月20日木曜日

emacs で特定のモード起動時にシェルコマンド実行する方法

emacs で特定のモード起動時にシェルコマンド実行する方法

概要

いくつかあるので紹介します

環境

  • macOS 15.3.2
  • emacs 30.1

フォアグラウンドで実行

(add-hook 'python-mode-hook
  (lambda ()
    (shell-command "echo 'Hello, Python!' >> ~/start_python_mode_msg.txt")))

バックグラウンドで実行

(add-hook 'c-mode-hook
  (lambda ()
    (start-process "make-process" nil "make")))

非同期実行

(add-hook 'go-mode-hook
  (lambda ()
    (async-shell-command "go build")))

シェルスクリプトを実行

(add-hook 'js-mode-hook
  (lambda ()
    (shell-command "bash ~/scripts/setup.sh")))

最後に

必要に応じて使い分けましょう

2025年3月19日水曜日

flask-sqlalchemy で pyright 用のスタブファイルを作成してみた

flask-sqlalchemy で pyright 用のスタブファイルを作成してみた

概要

flask-sqlalchemy + pyright を使っているとデータベースのモデルからインスタンスを生成する際に pyright のエラーになります
回避方法はいろいろありますが今回は pyright 用のスタブ情報を手動で作成することで対応してみました

環境

  • Python 3.12.9
    • flask-sqlalchemy 3.1.1
    • pyright 1.1.396

エラーの出るファイル

  • vim my_libs/schema.py
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()


class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(80), nullable=False)


# インスタンス作成
user = User(id=1, name="Alice")

これで pyright を実行すると以下のえらーになります
これをスタブを作成することで回避します

  • pipenv run pyright my_libs/schema.py
/path/to/my_libs/schema.py
  /path/to/my_libs/schema.py:12:13 - error: No parameter named "id" (reportCallIssue)
  /path/to/my_libs/schema.py:12:19 - error: No parameter named "name" (reportCallIssue)
2 errors, 0 warnings, 0 informations

スタブファイルの自動生成

  • pipenv run pyright --createstub flask_sqlalchemy

これで typings 配下に以下のファイルが自動で生成されます

ls -ltR typings                                   
total 0
drwxr-xr-x  12 user01  staff  384  3 18 10:22 flask_sqlalchemy

typings/flask_sqlalchemy:
total 120
-rw-r--r--  1 user01  staff    171  3 18 10:22 track_modifications.pyi
-rw-r--r--  1 user01  staff    710  3 18 10:22 table.pyi
-rw-r--r--  1 user01  staff   1428  3 18 10:22 session.pyi
-rw-r--r--  1 user01  staff   1870  3 18 10:22 record_queries.pyi
-rw-r--r--  1 user01  staff   3024  3 18 10:22 query.pyi
-rw-r--r--  1 user01  staff   5279  3 18 10:22 pagination.pyi
-rw-r--r--  1 user01  staff   5297  3 18 10:22 model.pyi
-rw-r--r--  1 user01  staff  14197  3 18 10:22 extension.pyi
-rw-r--r--  1 user01  staff    321  3 18 10:22 cli.pyi
-rw-r--r--  1 user01  staff    180  3 18 10:22 __init__.pyi

動作確認

スタブファイルを作成した状態で再度 pyright を実行しましょう
先のほどまで出ていた No parameter named "name" などのエラーが出ないことが確認できると思います

おまけ: その他の解決方法

__init__ メソッドを User モデルクラスに実装する

この方法でも pyright のエラー自体は出なくなりますがコンストラクタを生成することで初期化時の処理が変わる可能性があるのであまりおすすめはしないです

dict を使い変数展開する

User 生成時にキーワード引数ではなく辞書の展開を使ってもエラーを回避できます

params = {
  "id": 1,
  "name": "hawk"
}
user = User(**params)

最後に

flask-sqlalchemy の pyright エラーに対応してみました
typings/ ディレクトリは今回は自動生成しているので git には含めないほうがいいです
手動で作成している場合は含めたほうがいいです

CI などで typings が必要な場合は毎回生成してあげるようにしましょう

おまけ: スタブファイルの手動作成(失敗編)

以下試したけどダメだったパターンです

スタブファイルはプロジェクトルートの typings というディレクトリ配下に作成します
またパッケージの階層も同じにする必要がありかつファイル名は pyi ファイルにします

  • mkdir typings
  • mkdir typings/my_libs
  • vim typings/my_libs/schema.pyi
from flask_sqlalchemy import SQLAlchemy

db: SQLAlchemy

class User():
    id: int
    name: str

    def __init__(self, id: int, name: str) -> None: ...

2025年3月18日火曜日

python:3.12.9-slim-bookworm に pyyaml をインストールする方法

python:3.12.9-slim-bookworm に pyyaml をインストールする方法

概要

6.0 だとエラーになるので 6.0.2 をインストールしましょう

環境

  • docker 27.3.1
  • Python 3.12.9
    • pyyaml 6.0.2

Dockerfile

FROM python:3.12.9-slim-bookworm

RUN apt -y update && apt -y install libyaml-dev gcc
RUN pip install pyyaml=="6.0.2"

最後の行が pyyaml=="6.0" だとエラーになります

最後に

理由は不明です
おそらく何かしらのパッケージが足りていないんだと思います

同じように uwsgi も 2.0.22 だと slim-bookworm ではエラーになりました

2025年3月17日月曜日

Github で自分が作成した Issue と Pull-Request をすべて表示する方法

Github で自分が作成した Issue と Pull-Request をすべて表示する方法

概要

Issue および Pull request を一覧ですべて表示します

環境

  • Github 2025/03/14 時点

URL

your-github-account-name に自分のアカウント名を入力すればすべて表示されます

最後に

これまでの活動内容をすべて表示したいときに便利です

2025年3月14日金曜日

iOS アプリを xcodebuild CLI コマンドでビルドする方法

iOS アプリを xcodebuild CLI コマンドでビルドする方法

概要

Xcode を起動しないでとりあえずビルドできるかチェックしたい場合に便利です
あとは CI などに使えます

環境

  • macOS 15.3.1
  • xcodebuild 16.2 (16C5032a)

コマンド

  • cd /path/to/project
  • xcodebuild -workspace project_name.xcworkspace -scheme project_name -destination 'platform=iOS Simulator,name=iPhone 16' clean build

成果物

以下のパスに app ファイルが作成されます

~/Library/Developer/Xcode/DerivedData/project_name-bpovlgogrctpyxcxsxxxxxxxcxon/Build/Products/Debug-iphonesimulator/project_name.app

Any iOS Device でビルドする方法

destination オプションで generic/platform=iOS だけを指定します

  • xcodebuild -workspace project_name.xcworkspace -scheme project_name -destination 'generic/platform=iOS' clean build

成果物のパスは以下に変わります

~/Library/Developer/Xcode/DerivedData/project_name-bpovlgogrctpyxcxsxxxxxxxcxon/Build/Products/Debug-iphoneos/project_name.app

トラブルシュート

Cocoapods を使ってサードパーティのライブラリを管理している場合はこちらの方法でサードパーティ側のバージョンも固定しなければいけない場合があります
固定する設定がなく Pods 側のビルドバージョンの変更を毎回 Xcode でやっていた場合には固定化する設定が必要になります

最後に

わざわざ Xcode を開いてビルドする必要がなくなるので Pods をアップデートして問題なくビルドできるかなどの確認作業を CLI のみで行うことができるようになります

参考サイト

2025年3月13日木曜日

OpenTelemetry で celery の非同期タスクでもリクエストと同じトレースをする方法

OpenTelemetry で celery の非同期タスクでもリクエストと同じトレースをする方法

概要

Flask アプリから celery の非同期タスクを呼び出した際にもトレースできるようにします
Flask、Celery で一貫して OLTP にトレース情報を送信するサンプルコードを紹介します

環境

  • macOS 15.3.1
  • Python 3.12.9
    • flask 3.1.0
    • open-telemetry 0.51b0
    • celery 5.4.0
  • OpenTelemetry Collector 0.121.0
  • Jaeger (all in one) 1.67.0

追加インストール

前回のに追加で以下をインストールします

  • pipenv install opentelemetry-instrumentation-celery celery

app.py

from celery import Celery
from flask import Flask
from opentelemetry import trace
from opentelemetry.propagate import inject

from lib.tasks import roll_dice_async

# Flask アプリの作成
app = Flask(__name__)

# Celery 設定
app.config["CELERY_BROKER_URL"] = "redis://localhost:6379/0"
app.config["CELERY_RESULT_BACKEND"] = "redis://localhost:6379/0"
celery = Celery(app.name, broker=app.config["CELERY_BROKER_URL"])
celery.conf.update(app.config)

# OpenTelemetry の設定
tracer = trace.get_tracer("diceroller.tracer")


@app.route("/rolldice")
def roll_dice():
    username = get_username()

    # トレースコンテキストを取得して Celery に渡す
    headers = {}
    inject(headers)
    roll_dice_async.apply_async(kwargs={"username": username, "headers": headers})

    return "Rolling dice asynchronously!"


def get_username():
    with tracer.start_as_current_span("get_username") as span:
        username = "hawksnowlog"
        span.set_attribute("username", username)
        return username


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8080)

lib/tasks.py

from random import randint

from opentelemetry import trace
from opentelemetry.propagate import extract

from lib.worker import celery

tracer = trace.get_tracer("diceroller.tracer")


@celery.task
def roll_dice_async(username, headers):
    return roll(username, headers)


def roll(username: str, headers):
    # OpenTelemetry のコンテキストを受け取り、トレースを継続
    with tracer.start_as_current_span("roll", context=extract(headers)) as span:
        res = randint(1, 6)
        span.set_attribute("roll.value", res)
        return res

lib/worker.py

from celery import Celery
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.celery import CeleryInstrumentor
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor

# Celery インスタンスを作成
celery = Celery("tasks", broker="redis://localhost:6379/0")
celery.conf.update(broker_connection_retry_on_startup=True)

# OpenTelemetry の設定
tracer_provider = TracerProvider()
tracer_provider.add_span_processor(
    BatchSpanProcessor(OTLPSpanExporter(endpoint="http://localhost:4317"))
)

# Celery の OpenTelemetry を有効化
CeleryInstrumentor().instrument()

起動

Flask

  • export OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true && pipenv run opentelemetry-instrument --logs_exporter otlp --service_name dice-server python app.py

Celery

  • export OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true && pipenv run opentelemetry-instrument --logs_exporter otlp --service_name dice-server celery -A lib.tasks worker --loglevel=INFO

Jaeger

  • docker run --rm --name jaeger -e COLLECTOR_ZIPKIN_HOST_PORT=:9411 -p 16686:16686 -p 4317:4317 -p 4318:4318 -p 14250:14250 -p 14268:14268 -p 14269:14269 -p 9411:9411 jaegertracing/all-in-one:latest

動作確認

localhost:8080/rolldice にアクセスして Jaeger にアクセスすると一つのトレース内に Flask と Celery のタスクが処理が含まれていることが確認できると思います

最後に

Flask + Celery に OLTP のトレースを設定する方法を紹介しました

トレースに間があるのは非同期特有なのかもしれません
もしくは BatchSpanProcessor を使っているのでリアルタイムでなくバッチ処理として送信している影響かもしれません

参考サイト

2025年3月12日水曜日

OpenTelemetry のトレース情報を Jaeger に直接送信する方法

OpenTelemetry のトレース情報を Jaeger に直接送信する方法

概要

これまではアプリ -> コレクタ -> exporter -> Jaeger という経路でトレース情報を送信していました
推奨ではないですがコレクタと exporter をすっ飛ばして直接トレース情報を Jaeger に送信することもできるので紹介します

環境

  • macOS 15.3.1
  • Python 3.12.9
    • flask 3.1.0
    • open-telemetry 0.51b0
  • OpenTelemetry Collector 0.121.0
  • Jaeger (all in one) 1.67.0

Jaeger 起動

  • docker run --rm --name jaeger -e COLLECTOR_ZIPKIN_HOST_PORT=:9411 -p 16686:16686 -p 4317:4317 -p 4318:4318 -p 14250:14250 -p 14268:14268 -p 14269:14269 -p 9411:9411 jaegertracing/all-in-one:latest

4317 で OTLP のトレース情報を直接受けます

アプリ起動

  • export OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true && pipenv run opentelemetry-instrument --logs_exporter otlp --service_name dice-server python app.py

上記の設定では otlp は localhost:4317 に送信されます

app.py

前回と変わりませんが記載しておきます

from random import randint
from flask import Flask

from opentelemetry import trace

# Acquire a tracer
tracer = trace.get_tracer("diceroller.tracer")

app = Flask(__name__)

@app.route("/rolldice")
def roll_dice():
    username = get_username()
    return str(roll(username))

def get_username():
    with tracer.start_as_current_span("get_username") as rollspan:
        username = "hawksnowlog"
        rollspan.set_attribute("username", username)
        return username

def roll(username: str):
    # This creates a new span that's the child of the current one
    with tracer.start_as_current_span("roll") as rollspan:
        res = randint(1, 6)
        rollspan.set_attribute("roll.value", res)
        return res

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8080)

動作確認

Jaeger を確認するとトレース情報が確認できます

最後に

おそらく OpenTelemetry が推奨する方法ではないですがこの方法でもトレース情報を送信できます
アプリのメトリックスを取得しない(トレースだけ)かつ OTLP の情報を Jaeger 以外に送らないのであればこの構成でもいいのかもしれません

参考サイト