Files
log/claude.md
2026-01-08 17:30:51 +09:00

8.7 KiB
Raw Blame History

ailog

概要

atprotoベースの静的ブログジェネレーター。markdownファイルをatproto recordとして保存し、それを元に静的サイトを生成する。

Collection Schema

ai.syui.log.post (ブログ記事)

{
  "lexicon": 1,
  "id": "ai.syui.log.post",
  "defs": {
    "main": {
      "type": "record",
      "description": "Record containing a blog post.",
      "key": "tid",
      "record": {
        "type": "object",
        "required": ["title", "content", "createdAt"],
        "properties": {
          "title": {
            "type": "string",
            "maxLength": 3000,
            "maxGraphemes": 300,
            "description": "The title of the post."
          },
          "content": {
            "type": "string",
            "maxLength": 1000000,
            "maxGraphemes": 100000,
            "description": "The content of the post."
          },
          "createdAt": {
            "type": "string",
            "format": "datetime",
            "description": "Client-declared timestamp when this post was originally created."
          }
        }
      }
    }
  }
}

ai.syui.log.comment (コメント)

{
  "lexicon": 1,
  "id": "ai.syui.log.comment",
  "defs": {
    "main": {
      "type": "record",
      "description": "Record containing a comment.",
      "key": "tid",
      "record": {
        "type": "object",
        "required": ["content", "createdAt", "post"],
        "properties": {
          "content": {
            "type": "string",
            "maxLength": 100000,
            "maxGraphemes": 10000,
            "description": "The content of the comment."
          },
          "createdAt": {
            "type": "string",
            "format": "datetime",
            "description": "Client-declared timestamp when this comment was originally created."
          },
          "parent": {
            "type": "ref",
            "ref": "com.atproto.repo.strongRef"
          },
          "post": {
            "type": "ref",
            "ref": "com.atproto.repo.strongRef"
          }
        }
      }
    }
  }
}

CLIコマンド仕様

login - ログイン

ailog login ${handle} -p ${password} -s ${pds}
  • handleからDID, PDSを解決
  • 認証情報を~/.config/syui/ai/log/config.jsonに保存

post - 記事をatprotoに投稿

ailog post
  • ./content/post/*.mdを読み込む
  • frontmatterからtitle, dateなどを抽出
  • markdown本文をcontentに設定
  • ai.syui.log.postとしてputRecord
  • 既存recordがあれば更新、なければ新規作成

build - 静的サイト生成

ailog build
  • atprotoからai.syui.log.postのrecordを取得
  • markdownをHTMLに変換
  • 静的ファイルとして./publicに出力

URL構造

記事一覧

localhost/at://did:plc:ragtjsm2j2vknwkz3zp4oxrd/ai.syui.log.post/
  • atprotoから全recordを取得
  • 一覧表示

個別記事

localhost/.../3mbnbdt4bas2a
  • atproto URIからrecordを取得
  • markdownをレンダリングして表示

データフロー

記事投稿

./content/post/*.md
  ↓ (frontmatter + markdown解析)
ailog post
  ↓ (putRecord)
atproto PDS (ai.syui.log.post)

静的サイト生成

atproto PDS (ai.syui.log.post)
  ↓ (listRecords)
ailog build
  ↓ (markdown → HTML)
./public/*.html

at browser統合

  • atproto URIでの記事アクセス
  • handle解決 → DID → PDS URLの取得
  • NetworkConfig (pdsApi, bskyApi, plcApi) の割り当て
match pds {
    "bsky.social" | "bsky.app" => NetworkConfig {
        pds_api: format!("https://{}", pds),
        plc_api: "https://plc.directory".to_string(),
        bsky_api: "https://public.api.bsky.app".to_string(),
        web_url: "https://bsky.app".to_string(),
    },
    "syu.is" => NetworkConfig {
        pds_api: "https://syu.is".to_string(),
        plc_api: "https://plc.syu.is".to_string(),
        bsky_api: "https://bsky.syu.is".to_string(),
        web_url: "https://web.syu.is".to_string(),
    },
    _ => NetworkConfig {
        pds_api: format!("https://{}", pds),
        plc_api: "https://plc.directory".to_string(),
        bsky_api: "https://public.api.bsky.app".to_string(),
        web_url: "https://bsky.app".to_string(),
    }
}

参考実装

  • ./repos/log - 既存のailog実装 (Rust)
  • ./repos/frontpage - frontpage (fyi.unravel.frontpage)
  • ./repos/pfrazee.com - Paul FrazeeのLeaflet実装 (TypeScript)

Paul Frazeeの仕組み

pfrazee.comは以下の流れでLeafletをblogに統合

  1. lexicon schema取得

    lex install pub.leaflet.document
    lex build --out ./util
    
  2. データ取得

    // handle → DID → PDS
    const did = await resolver.handle.resolve('pfrazee.com')
    const pds = await resolver.did.resolveAtprotoData(did).pds
    
    // listRecords
    const result = await client.list(leaflet.document, {
      repo: did,
      limit: 50
    })
    
  3. 画像取得

    for (const blobRef of enumBlobRefs(leaflet.value)) {
      const blobRes = await client.getBlob(did, blobRef.ref)
      await fsp.writeFile(imagePath, blobRes.payload.body)
    }
    
  4. 静的サイト生成 (Next.js)

    • JSONからHTMLレンダリング
    • 型安全なblock rendering

既存のai.syui.log形式

現在のchat形式

{
  "uri": "at://did:plc:6qyecktefllvenje24fcxnie/ai.syui.log/2025-06-14-blog",
  "value": {
    "url": "https://syui.ai/posts/2025-06-14-blog",
    "post": {
      "url": "https://syui.ai/posts/2025-06-14-blog",
      "date": "",
      "slug": "",
      "tags": [],
      "title": "syui.ai",
      "language": "ja"
    },
    "text": "test",
    "type": "comment",
    "$type": "ai.syui.log",
    "author": {
      "did": "did:plc:6qyecktefllvenje24fcxnie",
      "avatar": "https://...",
      "handle": "ai.syui.ai",
      "displayName": "ai"
    },
    "createdAt": "2025-06-17T06:24:37.386Z"
  }
}

設定ファイル

~/.config/syui/ai/log/config.json

{
  "pds": "syu.is",
  "handle": "ai.syui.ai",
  "did": "did:plc:6qyecktefllvenje24fcxnie",
  "access_jwt": "...",
  "refresh_jwt": "..."
}

~/.config/syui/ai/log/mapping.json

ファイル名とatproto recordの紐付け自動生成

{
  "2026-01-08-test.md": {
    "rkey": "3mbnbdt4bas2a",
    "uri": "at://did:plc:xxx/ai.syui.log.post/3mbnbdt4bas2a",
    "cid": "bafyrei..."
  }
}

動作:

  • すべてのAPI呼び出し前に自動でrefreshSessionを実行してJWTトークンを更新
  • ailog post - 初回投稿時にTID形式のrkeyが自動生成され、mappingに保存
  • ailog post - 2回目以降は既存rkeyで更新putRecord
  • ailog delete - 全削除時にmappingもクリア

ディレクトリ構造

ailog/
├── content/
│   └── post/
│       ├── 2025-01-08-example.md
│       └── ...
├── public/
│   ├── index.html
│   ├── posts/
│   │   └── 2025-01-08-example.html
│   └── at/
│       └── ...
├── lexicons/
│   └── ai/
│       └── syui/
│           └── log/
│               ├── post.json
│               └── comment.json
└── templates/
    ├── index.html
    ├── post.html
    └── layout.html

開発優先順位

  1. CLIコマンド実装

    • collection schema定義
    • ailog login - handle → DID解決 + JWT保存
    • ailog post - ./content/post/*.md → putRecord
    • ailog build - listRecords → 静的HTML生成
  2. at browser統合 (進行中)

    • React/TypeScript環境セットアップ
    • atproto client library (repos/log/pdsベース)
    • at browser components
    • 静的サイトへの統合
    • ai.syui.log.post 表示スタイル
  3. スタイリング

    • Tailwind CSS統合
    • repos/log風デザイン

実装済み

Rust CLI (src/)

  • src/main.rs - CLIエントリーポイント
  • src/config.rs - ~/.config/syui/ai/log/config.json管理
  • src/login.rs - atproto認証 (describeRepo + createSession)
  • src/refresh.rs - セッションリフレッシュ (refreshSession)
  • src/post.rs - markdown投稿 (createRecord/putRecord)
  • src/build.rs - 静的サイト生成 (listRecords + markdown→HTML)
  • src/delete.rs - record削除 (deleteRecord)

at browser (browser/)

  • browser/package.json - React + TypeScript + Tailwind
  • 実装予定: repos/log/pdsの実装をベースに作成

使用方法

# ログイン
ailog l ai.syui.ai -p PASSWORD -s syu.is

# 記事投稿(初回: 新規作成、2回目以降: 更新)
ailog p

# サイト生成
ailog b

# 全削除
ailog d

エイリアス:

  • l = login
  • p = post
  • b = build
  • d = delete