プログラム

終日コードを書く(書いてもらう) – 2025年7月12日

私用で待ち時間が多かったこともあり、ふと今週アイディアとしてディスカッションしていたあるアプリケーションを実装してみたくなってひたすらClaude Codeと向き合って壁打ちしながらコードを書いていた1。自分が作りたいものを箇条書きでメモとして作ったり、UIとして実現したいものは別途ChatGPTと壁打ちして、これも簡単な仕様を作ってClaude Codeに渡して実装を進めてみた。

あっという間に行数は増え続け、自分がある程度イメージしていたものは表現することができて久しぶりにこのイメージを形にしていくプロセスを踏んで、そういえばこれは自分が昔から好きなプロセスだったと思い出す感じがあって懐かしかった。

一方、このスピード感で仕上がっていく事実といま自分が仕事をしている中で出せているスピードとのギャップに多少焦りを感じた。

  1. 正確には書いてもらっていた。だと思う[]

PoCをする場所としてのWordPress – 2025年5月10日

このブログ、Wordpressで動かす様にして時間がしばらく過ぎた。1当時からはモチベーションは変わらず自社のホスティングを利用してWordpressをたくさんの方々に動かして頂いているので自分も経験してみようというモチベーションだったり、ユーザーの方が利用されているプラグインを自分もすぐに試せる様にしてみようと思い運用している。

更に最近はちょっとした事業上考えていることを自分なりに検証する場所としてWordpressを使い始めてたりする。試すのにはWordpressのプラグインを作成していたりする。Wordpressのエコシステムをそのまま使うとストレージやデータベースも使えるの楽というのがモチベーション。

コンセンプトを試すためプラグインそのものも以前だとコーディングをしていた2のだけれども、最近はAIを介して作成してもらって動かしていたりする。作るのもAIエディタ使ったりだとか次々でてくるソリューション試すのもよいし、素朴にChatGPTと壁打ちしながらでもソースコードの出力は普通にできるので出力されたものをZipに圧縮してWordpressのプラグインとして新規追加すれば普通に動く。

何かしらのプラグインだったりChrome extensionの様な既にいま普及しているインフラやソフトウェアを少しプラグインだったりの仕組みで拡張することでこれまでもコンセプト検証できることもあったけれども、その拡張するそのものがAIに支援されてカジュアルに作れる様になったのは本当に嬉しいなと思う。3

  1. 振り返ると2020年4月だった[]
  2. これはこれですごく楽しい[]
  3. そもそもこういう「実験場」じゃないけれどもカジュアルに実験できる環境を持っておくのは個人的にはお勧めである[]

chromiumapp.org というドメインとその役割を知った – 2025年4月29日

Chrome拡張を作ってみようといろいろと試しているとGPTからchromiumapp.orgというドメインをOAuth認証に使うコードが示されて、このドメインは何だ?となったので調べたのでメモとして残しておく。

結論から書くと、chromiumapp.org は Google(Chromium プロジェクト)が Chrome拡張/Chrome アプリ専用に確保しているワイルドカードドメインとのことだった。通常の Web サイトとは違い、実際にコンテンツをホストしているわけではなく、主目的は OAuth などの外部認証フローで「拡張機能自身」をリダイレクト先(redirect_uri)として扱えるようにすることとのことだった。

仕組みとしては、

拡張IDでサブドメイン生成

最初拡張IDとはなんぞやと思ったのだけれども、Chromeで拡張機能一覧 chrome://extensions/ を開いたときに導入されている拡張機能とともにIDが振られているのがわかってそれを指している。
それを利用して chrome.identity.getRedirectURL('path') をコールするとブラウザが https://<extension-id>.chromiumapp.org/<path> というURLを生成してくれる

ブラウザがインターセプトしてくれる

認証プロバイダが上記 URL へリダイレクトすると ネットワークに到達する前に Chrome が捕捉 し、launchWebAuthFlow() のコールバックへ最終URLを渡してくれる。

サーバーが不要

開発者が Web サーバを用意する必要はなく、DNS 解決や TLS 証明書も Google が管理してくれる。

というものだった。
(参照先: https://developer.chrome.com/docs/extensions/reference/api/identity?hl=ja )

スマートフォンのWebViewでOAuthの認可をするときに urn:ietf:wg:oauth:2.0:oob をコールバックURLと指定することもあるけれどもそことの違いとして

項目 chromiumapp.orgを使う場合 urn:ietf:wg:oauth:2.0:oob を使う場合
方式 ブラウザ内で自動リダイレクトを捕捉 ブラウザにコードを「表示」して手動コピー or ディープリンク
ユーザ操作 追加操作なし(即自動処理) 手動コピペなどが必要な場合が多い
推奨用途 拡張・ネイティブアプリの シームレス なログイン体験 CLI や画面を持たないデスクトップアプリなど

といった具合でまとめられる。

なるほど、Chrome拡張とかでこういった認証かけているものを見かけていたがこういったやり方で解決していたのかと学びになった。

nvimでgitの差分箇所を行番号の横に表示させる – 2025年1月4日

nvimでgitの差分を行番号の横に表示させたいなと思って調べたのでメモエントリ。
gitsigns.nvimというのがあるので、これを使うとよさそうだった。


Lazy.nvim を利用していて且つMacOSなので ~/.config/nvim/lua/pluginsgitsigns.lua を置く。
差分の表記を自分の好みに変更する設定だけ入れた。

return {
  "lewis6991/gitsigns.nvim",
  config = function()
    require("gitsigns").setup({
      signs = {
        add = { text = "+" },
        change = { text = "~" },
        delete = { text = "-" },
        topdelete = { text = "‾" },
        changedelete = { text = "~" },
      },
    })
  end,
}

ImageMagickのcompareコマンドで比較度合いを調整する – 2024年10月25日

ImageMagickのcompareコマンドで2枚のスクリーンショット画像の比較などを行う際にスクリーンショットで生じたわずかなレンダリング差(アンチエイリアスの差、ピクセル単位での色の差)が差分として検出されてしまうのを調整するのに、その差分を無視する度合いを与えるオプションがあったのでメモエントリー。

$ compare -fuzz 5% -metric AE new.png old.png diff.png

の様に与えることで、上だと色の差が5%未満だったら無視する様な動きになる。

参照

difyで作成したワークフローをAPIとして呼び出す – 2024年7月15日

社内でdifyを利用できる環境があって少しずつ触ってみていたのだけれども(本質ではないけれども)ワークフローを作成するワークフローエンジンとしても面白いなと思いつつ外部のAPIを呼び出したり、その結果とLLMを絡めたピタゴラスイッチ1を作りやすく、更に作成したワークフロー自体もAPIとして外部から呼べることもわかったので簡単にメモ。

手順としては以下の通り、

  1. ワークフローを作成する
  2. ワークフロー編集画面の左手の APIアクセス を開き、右上にある APIキー のUIからAPIキーを生成して取得する
  3. ワークフロー編集画面右上の 公開する ボタンを押してワークフローを公開(または更新)する
    1. で取得したAPIキーをBearerとして認証ヘッダに設定してPOSTする
      • POSTする際にinputsの中にワークフローの「開始」で設定した入力フィールドの変数名にそろえて入力する

curlコマンドでワークフローをAPI経由で呼び出すには以下の様な形になる。
(今回はプロダクトに組み込むとかの目的ではないのでblockingモードで呼び出しているけれども推奨はstreamingモードになっている。)

ちなみに自分が躓いたところは、

  • ワークフローの「終了」のブロックで出力変数を指定していないので結果が帰ってこない
  • ワークフローを画面上で編集した後、「公開する」のメニューから「更新」を行わないため修正前のワークフローの結果が帰ってきてしまう

の2点でこれにいつまでも気づかずいくら呼び出しても結果が返ってこない、あるいは修正が反省されないと悩み、もしかすると世の中に2名くらいは同じ失敗をした人の役に立つかも知れないと思ったのでここに残しておく。

実行例

ワークローの「開始」に入力フィールドとして "form1", "form2" を設けていた場合、

curl -X POST 'https://difyホスト名/v1/workflows/run' \
--header 'Authorization: Bearer app-xxxxx' \
--header 'Content-Type: application/json' \
--data-raw '{
    "inputs": {
        "form1": "123",
        "form2": "abc"
    },
    "response_mode": "blocking",
    "user": "hideack"
}'

上でAPIへリクエストするとblockingモードの場合は以下の様なJSONが戻ってくる。ワークフローの処理結果が完了するまでブロックされるのでoutputsにワークフローの出力(= 終了ブロックの出力変数)が含まれる。

{
    "data": {
        "created_at": 1721012680,
        "elapsed_time": 20.174075001035817,
        "error": null,
        "finished_at": 1721012700,
        "id": "f4166b4b-1234-xxxx-yyyy-zzzz",
        "outputs": {
            "result": "結果がここに入ります"
        },
        "status": "succeeded",
        "total_steps": 6,
        "total_tokens": 893,
        "workflow_id": "cba8a879-1234-xxxx-yyyy-zzzz"
    },
    "task_id": "f8c804e5-1234-xxxx-yyyy-zzzz",
    "workflow_run_id": "f4166b4b-1234-xxxx-yyyy-zzzz"
}
  1. この比喩も通じる人が少なくなってきた。詳しくはこちらを参照[]

ブクログのエクスポート機能で出力されたCSVファイルから特定期間に読了した本のMarkdownテーブルを出力する

タイトル通り。

今日書いたエントリーで使った様なテーブルをブクログのエクスポート機能で得られるCSVファイルからGoogle App Script で作成する方法。
実行するとシートの末尾にMarkdownが出力される。

function generateMarkdownTable() {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  var data = sheet.getDataRange().getValues();
  var markdown = "| タイトル | 作者名 | 出版社名 |\n| --- | --- | --- |\n";

  for (var i = 1; i < data.length; i++) {
    var row = data[i];
    var completionDate = new Date(row[10]); // 読了日の列インデックスを仮定(例:11列目)
    var startDate = new Date("2023/01/01");
    var endDate = new Date("2023/12/31");
    var itemID = row[1]; // アイテムIDの列インデックスを仮定(例:2列目)

    if (completionDate >= startDate && completionDate <= endDate) {
      var title = row[11]; // タイトルの列インデックスを仮定(例:12列目)
      var author = row[12]; // 作者名の列インデックスを仮定(例:13列目)
      var publisher = row[13]; // 出版社名の列インデックスを仮定(例:14列目)
      var titleLink = "https://www.amazon.co.jp/dp/" + itemID;

      markdown += "| [" + title + "](" + titleLink + ") | " + author + " | " + publisher + " |\n";
    }
  }

  // 最後の行にMarkdownテーブルを追加
  var lastRow = sheet.getLastRow();
  sheet.getRange(lastRow + 1, 1).setValue(markdown);
}

ChatGPTと会話してGoogle Apps Scriptのスクリプトを組み立てる

仕事で使っているあるスプレッドシートをソースとしてある分析をしたいと思ったときにChatGPTのcode interpreterを利用して分析してもらおうと一瞬思ったのだけれども、そのソースのスプレッドシートのサイズがある程度の大きさだったり、そもそも扱っているデータの兼ね合いもあるので、ChatGPT側で処理をするのではなくて以下の様な手順でGoogle Apps ScriptをChatGPTに組んでもらった。やり方は...

  1. ChatGPTに目的とする分析結果を得るための手法を3つ提案してもらう
  2. 提案してもらった中から現状のデータセットを鑑みて試してみたいと思う手法を1つ選ぶ
    1. で選んだ解析をGoogle Apps Scriptで実装してほしいと伝える。その際にスプレッドシートの仕様を添える (1行目はラベルで2行目以降にデータがセットされており1列目は識別子で2列目は... といった具合でデータの諸元となるシートの仕様を箇条書きにして伝える)
  3. ChatGPTから出力されるGoogle Apps Scriptをスプレッドシートにセットする
  4. スクリプトを実行する
  5. 実行した結果を確認する

といった流れ。4.から6.を繰り返してブラッシュアップしていくのだけれども30分くらいのやりとりで120-130行くらいでかなりスマートな(と自分には見え、少なくとも自分には書けない)スクリプトを出力してくれて、これを現時点での自力で書いてたらもっと時間かかっていただろうなと思うのと、こういったやり方が引き続き実現できるのであれば30代までにエンジニアとしてコードを書くことを生業にしていたことが違う形で活かすことができて面白いなと思った。
出力されるコード自体が所望の動きをするものかは確認しないといけないのだけれども、それも

  • ログを入れてほしい
  • 出力結果の検証をしたいのだけれどもどういったログを確認するとよいか

といったコミュニケーションをChatGPTと取っていたら、各処理の開始と終了、データをパースする際の先頭と最後をログ取る様にするのでそれが意図した挙動の出力になっているか確認するとよいと思うと言われ、ではそれで確認するのでスクリプトをアップデートしてほしい。といった一連のやりとりでスクリプトは更新されていくのであった。
そして、それで確認すると変なところでセル内の数値の加算の処理がスクリプトでは文字連結されているのに気づいたり、集計時のハッシュのキーが誤っていたりと不具合に自分が気づき、再びそれをフィードバックすると... という具合で自分が望むスクリプトが組み上がっていくのは二人組になってペアプログラミングしている様な感覚。

もう少しこのあたり自分なりに体系化してみたい気もする。

ブクログで表示中のページからアイテムページのURLを抽出する

タイトル通りのメモエントリー。

月に一度、読んだ本のまとめをブクログで抽出してそれをこのブログのエントリとしてまとめていて、以前は貼り付け用のコードが公式から提供されていたのだけれども廃止になったので温かみのある手作業でアイテムページのURLをコピペしていた。同じことを3回やったら自動化できないか考えたほうがみたいなことを普段は棚上げ力を発揮して発言していたのだけれども、いい加減一発で取得できる様にしようということでChatGPTに教えてもらった。

以前だったらこういうのなんとなくこういう感じでコード書くんだろうな。あれ、この正規表現のときどうだっけ...と調べながら書いてたのがスッと出てくるのは生産性劇的に違うなと実感する。

いや、このくらいだったらChatGPTに聞かづとも出てくるでしょうという指摘は甘んじて受けます...。

let htmlContent = document.documentElement.innerHTML;
let regexPattern = /https:\/\/booklog\.jp\/item\/1\/[A-Za-z0-9]{10,13}/g;
let extractedStrings = htmlContent.match(regexPattern);

if (extractedStrings !== null) {
  extractedStrings = [...new Set(extractedStrings)];
  extractedStrings.forEach(url => console.log(url));
} else {
  console.log("No matching URLs found.");
}

通院, OpenAI APIと戯れる – 2023年3月19日

午前中は通院などあって付添のため外出。移動中には先日から読み始めた「シャープ 再生への道」を読む。

自宅に戻ってから、先週来からのChatGPTの盛り上がりを受けてOpenAI APIを介した実装に興味が湧いてきたのでシンプルにhttps://github.com/hideack/slack-cli-streamのコマンドとして実装をしてみた。
OpenAI API向けのAPIクライアントのNode.js実装は公開されているものがあるのでサンプルに従って実装したらすぐ呼び出しまで行えた。コマンドとして試したものはSlackの時系列を眺めるなかでふと気になったことがあったら同プログラムのコマンドラインから

> gpt おいしいカレーの作り方を教えてください

といった形で対話の文字列を与えることでAPIに渡せることができてその応答がSlackなどの発言に混ざって帰ってくるもの。

https://github.com/hideack/slack-cli-stream から OpenAI API呼び出しの様子

gpt + 質問文字列 でOpenAI APIを介してリクエスト

どちらかというと久しくコードを書いておらず、VSCodeを開いてvimプラグインを入れてみるなどのテスト勉強になると突然机の周りを整理しだす学生かの如く、開発環境に手を入れ始めてしまい、その中で先のプラグインを入れたときにキーを押しっぱなしにしてもカーソルが動かず、その原因を調べる中で更に深みにはまり... といったことにも時間を使ってしまった。でも、こういうところも含めてコードを書くのは楽しいと思ったのだけどこういった行為もAIとの対話の中でどういった位置づけになるのだろうとふと考えたりした。
なお、このvimプラグインを入れたときの問題は以下のサイトで示されている解決策で解決した。先に書いておくとVSCodeのコンソールから以下の設定を入れた。

defaults write com.microsoft.VSCode ApplePressAndHoldEnabled -bool false # For VS Code
defaults write com.microsoft.VSCodeInsiders ApplePressAndHoldEnabled -bool false # For VS Code Insider