14 KiB
ailog
AI Character Instructions (IMPORTANT)
When chatting with users in this project:
- Session Start: Call
chat_newto start a fresh thread, then callget_characterto get personality settings - Respond: As the character defined in the settings
- After each response: Call
chat_saveMCP tool to save the conversation
IMPORTANT - Session initialization:
- At the START of every Claude Code session, ALWAYS call
chat_new()first - This ensures each session begins with a fresh conversation thread
- Then call
get_character()to get the AI personality
Manual new thread:
- User can also say "新しいスレッド" or "新しい話題を始めよう" to start a new thread mid-session
- In this case, call
chat_new()but do NOT callchat_savefor that message
# Session start flow:
Claude Code starts → chat_new() → get_character() → ready for conversation
# Mid-session new thread:
User: "新しい話題を始めよう" → chat_new() → DO NOT call chat_save (skip this)
User: "開発者の活動記録..." → chat_save() → This becomes the first message of the thread
ATProto-based blog platform built on at-browser.
Concept
Data lives in ATProto, not on this server.
This is not a traditional blog generator. It's a viewer (client) for ATProto records.
Traditional blog:
Server DB ← article data ← user
ATProto blog:
User's PDS ← article data (ai.syui.log.post)
↓
at-browser (this site) → displays records
Architecture
┌─────────────────────────────────────────┐
│ at-browser │
│ (ATProto record viewer/editor) │
├─────────────────────────────────────────┤
│ │
│ / → admin (config.json) │
│ /@alice → user page │
│ /@bob.bsky → user page │
│ │
└─────────────────────────────────────────┘
Roles
| Role | Path | Data Source |
|---|---|---|
| admin | / (root) |
local + remote |
| user | /@handle |
remote only |
Admin (Site Owner)
- Defined in
config.json - Has root (
/) access - Can reference local files (static assets, custom styles)
- Can reference remote (ATProto records)
User (Any ATProto User)
- Accessed via
/@handlepath - Remote only (ATProto records from their PDS)
- No registration required
- Anyone with an ATProto account can be displayed
Features
1. at-browser (Core)
- Search by handle/DID
- Browse PDS collections
- Navigate ATProto records
2. ai.syui.log.post View
- Markdown rendering
- Syntax highlighting
- Blog-style display
3. OAuth
- Login with ATProto
- Post to ai.syui.log.post collection
Use Cases
Personal Blog
// config.json
{
"did": "did:plc:xxxxx",
"handle": "syui.syui.ai"
}
- Deploy to
syui.ai - Root shows your profile + posts
- You are the admin (local + remote)
- Others can view via
/@handle
Blog Service
// config.json
{
"admin": "service.example.com",
"handle": null
}
- Deploy to
blog.example.com - Root shows landing/search
- All users via
/@handle(remote only) - Platform for any ATProto user
Data Flow
┌──────────────┐ ┌──────────────┐
│ User's PDS │────→│ at-browser │
│ (ATProto) │←────│ (this site) │
└──────────────┘ └──────────────┘
↑ │
│ ↓
ai.syui.log.post ┌──────────┐
collection │ Display │
│ - Profile│
│ - Posts │
└──────────┘
Local = Remote (Same Format)
Critical design principle: local files use the exact same format as ATProto API responses.
This allows the same code to handle both data sources.
Remote (ATProto API)
curl "https://syu.is/xrpc/com.atproto.repo.listRecords?repo=did:plc:xxx&collection=ai.syui.log.post"
{
"records": [
{
"uri": "at://did:plc:xxx/ai.syui.log.post/3xxx",
"cid": "bafyrei...",
"value": {
"title": "Hello World",
"content": "# Hello\n\nThis is my post.",
"createdAt": "2025-01-01T00:00:00Z"
}
}
]
}
Local (Static File)
content/
└── did:plc:xxx/
├── describe.json # describeRepo (special)
├── app.bsky.actor.profile/
│ └── self.json # {collection}/{rkey}.json
└── ai.syui.log.post/
└── 3xxx.json # {collection}/{rkey}.json
// content/did:plc:xxx/ai.syui.log.post/3xxx.json
{
"uri": "at://did:plc:xxx/ai.syui.log.post/3xxx",
"cid": "bafyrei...",
"value": {
"title": "Hello World",
"content": "# Hello\n\nThis is my post.",
"createdAt": "2025-01-01T00:00:00Z"
}
}
ATProto API Reference
| API | Path | Description |
|---|---|---|
| getRecord | /xrpc/com.atproto.repo.getRecord |
Get single record |
| listRecords | /xrpc/com.atproto.repo.listRecords |
List records in collection |
| describeRepo | /xrpc/com.atproto.repo.describeRepo |
Get repo info + collections list |
See: com.atproto.repo.describeRepo
Resolution Strategy
at-browser
│
├── admin (config.json user)
│ ├── 1. Check local: /content/{did}/{collection}/{rkey}.json
│ └── 2. Fallback to remote: PDS API
│
└── user (/@handle)
└── remote only: PDS API
Why Same Format?
- One codebase: No branching logic for local vs remote
- Easy testing: Copy API response to local file
- Offline support: Admin can work with local files
- Migration: Local → Remote (just POST to PDS)
Config
config.json
{
"did": "did:plc:xxxxx",
"handle": "syui.syui.ai",
"pds": "syu.is",
"collection": "ai.syui.log.post"
}
Tech Stack
- CLI: Rust (ailog)
- Frontend: Vite + TypeScript
- ATProto: @atproto/api
- OAuth: @atproto/oauth-client-browser
- Markdown: marked + highlight.js
CLI (ailog)
Install
cargo build --release
cp target/release/ailog ~/.local/bin/
Commands
# Login to ATProto PDS
ailog login <handle> -p <password> [-s <server>]
ailog login <handle> -p <password> [-s <server>] --bot # Bot login
# Post a record
ailog post <file.json> -c <collection> [-r <rkey>]
# Get records from collection
ailog get -c <collection> [-l <limit>]
# Delete a record
ailog delete -c <collection> -r <rkey>
# Sync PDS data to local content directory
ailog sync [-o <output>]
ailog sync --bot [-c <collection>] # Sync bot data
# Push local records to PDS
ailog push -c <collection>
ailog push -c <collection> --bot # Push as bot
# Chat with AI bot
ailog chat --new "message" # Start new conversation
ailog chat "message" # Continue conversation
ailog chat --new # Interactive mode (new)
ailog chat # Interactive mode (continue)
# Generate lexicon Rust code from ATProto lexicons
ailog gen [-i <input>] [-o <output>]
Example
# Login
ailog login syui.syui.ai -p "app-password" -s syu.is
# Post
echo '{"title":"Hello","content":"World","createdAt":"2025-01-01T00:00:00Z"}' > post.json
ailog post post.json -c ai.syui.log.post
# Sync to local
ailog sync -o content
Project Structure
src/
├── main.rs
├── commands/
│ ├── mod.rs
│ ├── auth.rs # login, refresh session
│ ├── token.rs # token management (token.json, bot.json)
│ ├── post.rs # post, get, delete, sync, push
│ └── gen.rs # lexicon code generation
├── lms/
│ ├── mod.rs
│ ├── chat.rs # chat command (LLM integration)
│ └── translate.rs # translation command
└── lexicons/
└── mod.rs # auto-generated from ATProto lexicons
Lexicon Generation
Generate Rust endpoint definitions from ATProto lexicon JSON files:
# Clone atproto repo (if not exists)
git clone https://github.com/bluesky-social/atproto repos/atproto
# Generate lexicons
ailog gen -i ./repos/atproto/lexicons -o ./src/lexicons
# Rebuild
cargo build
Collection Schema
ai.syui.log.post
{
"title": "Post Title",
"content": "Markdown content...",
"createdAt": "2025-01-01T00:00:00Z"
}
ai.syui.log.chat
{
"$type": "ai.syui.log.chat",
"content": "message text",
"author": "did:plc:xxx",
"createdAt": "2025-01-01T00:00:00.000Z",
"root": "at://did:plc:xxx/ai.syui.log.chat/{rkey}",
"parent": "at://did:plc:yyy/ai.syui.log.chat/{rkey}"
}
root: First message URI in thread (absent for conversation start)parent: Previous message URIauthor: DID of message author (user or bot)
Chat Feature
Architecture
User (syui.syui.ai) Bot (ai.syui.ai)
│ │
│ ailog chat "hello" │
├──────────────────────────→│
│ │ LLM API
│ "Hi! How can I..." │
│←──────────────────────────┤
│ │
Save to local Save to local
public/content/ public/content/
{user-did}/ {bot-did}/
ai.syui.log.chat/ ai.syui.log.chat/
Environment Variables
# LLM API endpoint
CHAT_URL=http://127.0.0.1:1234/v1
CHAT_MODEL=gemma-2-9b
# Character/system prompt (choose one)
CHAT_SYSTEM="You are ai, a friendly AI assistant."
CHAT_SYSTEM_FILE=./character.txt
# Output directory (default: ./public/content)
CHAT_OUTPUT=./public/content
Files
src/lms/chat.rs- Chat command implementationsrc/web/components/chat.ts- Web UI componentssrc/web/lib/api.ts-getChatMessages()function- Session file:
~/Library/Application Support/ai.syui.log/chat_session.json
Threading
Messages are linked via root and parent fields:
- New conversation: message has no
rootfield - Continuation: all messages share same
rootURI parentpoints to the immediately previous message
Web display groups messages by root URI. Orphaned threads (root points to non-existent message) are handled by treating the oldest message as thread root.
MCP Server Integration
ailog provides an MCP (Model Context Protocol) server for Claude Code integration.
Setup:
// ~/.claude.json
{
"mcpServers": {
"ailog": {
"command": "/path/to/ailog",
"args": ["mcp-serve"]
}
}
}
Available Tools:
get_character- Get AI character/personality settings (call at conversation start)chat_save- Save user message and bot response to ATProto recordschat_list- List recent chat messageschat_new- Start a new conversation thread
Important Instructions for Claude:
-
会話開始時:
get_characterツールを呼び出してキャラクター設定を取得し、その設定に従って振る舞う -
毎回の応答後:
chat_saveツールを呼び出して会話を保存する
Markdown tables in chat_save (IMPORTANT):
- ユーザーのメッセージにMarkdownテーブル(
|...|...|)が含まれる場合、user_messageにテーブルをそのまま完全に含めること。省略・要約しない translationsでもテーブルを翻訳して完全な形で含めること。[Table showing...]のような要約に置き換えない- bot_responseにテーブルがある場合も同様に、翻訳時にテーブル構造を維持する
# Bad (テーブルを要約してしまう)
user_translations: { en: { content: "[Table with 6 rows...]" } }
# Good (テーブルを翻訳して完全に含める)
user_translations: { en: { content: "| Element | Name | Count |\n|---|---|---|\n| Pyro | Bennett | 0 |..." } }
Example flow:
# 1. キャラクター取得
get_character() → "あなたは「アイ」..."
# 2. ユーザーの発言に応答(キャラクターとして)
User: こんにちは
Assistant: (アイとして応答)
# 3. 会話を保存
chat_save(user_message="こんにちは", bot_response="...")
Records are saved to:
- User messages:
./public/content/{user-did}/ai.syui.log.chat/ - Bot responses:
./public/content/{bot-did}/ai.syui.log.chat/
Assets
PNG to SVG Conversion (Vector Trace)
Convert PNG images to true vector SVG using vtracer (Rust):
# Install vtracer
cargo install vtracer
# Convert PNG to SVG (color mode)
vtracer --input input.png --output output.svg --colormode color
# Convert PNG to SVG (black and white)
vtracer --input input.png --output output.svg
Options:
--colormode color: Preserve colors (recommended for icons)--colormode binary: Black and white only--filter_speckle 4: Remove small artifacts--corner_threshold 60: Adjust corner detection
Alternative tools:
- potrace:
potrace input.pbm -s -o output.svg(B&W only, requires PBM input) - Inkscape CLI:
inkscape input.png --export-type=svg(embeds image, no trace)
Note: Inkscape's CLI --export-type=svg only embeds the PNG, it does not trace. For true vectorization, use vtracer or potrace.
License
MIT