2026-01-18 16:24:48 +09:00
2026-01-18 11:43:58 +09:00
2026-01-18 11:43:58 +09:00
2026-01-18 16:24:48 +09:00
2026-01-18 16:24:48 +09:00
2026-01-18 12:38:59 +09:00
2026-01-18 12:38:59 +09:00
2026-01-18 11:43:58 +09:00
2026-01-18 12:38:59 +09:00
2026-01-18 11:43:58 +09:00
2026-01-18 15:17:17 +09:00
2026-01-18 15:17:17 +09:00
2026-01-18 15:17:17 +09:00
2026-01-18 15:17:17 +09:00

ailog

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 /@handle path
  • 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>]

# 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>]

# 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
│   ├── post.rs      # post, get, delete, sync
│   └── gen.rs       # lexicon code generation
└── 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"
}

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

Description
No description provided
https://syui.ai
Readme 159 MiB
Languages
Rust 43.3%
TypeScript 41.4%
CSS 14.4%
HTML 0.9%