init
This commit is contained in:
39
.github/workflows/cf-pages.yml
vendored
Normal file
39
.github/workflows/cf-pages.yml
vendored
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
name: Deploy to Cloudflare Pages
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
deployments: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm install
|
||||||
|
|
||||||
|
- name: Fetch content from ATProto
|
||||||
|
run: npm run fetch
|
||||||
|
|
||||||
|
- name: Generate static site
|
||||||
|
run: npm run generate
|
||||||
|
|
||||||
|
- name: Deploy to Cloudflare Pages
|
||||||
|
uses: cloudflare/pages-action@v1
|
||||||
|
with:
|
||||||
|
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
|
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
|
projectName: ${{ secrets.CLOUDFLARE_PROJECT_NAME }}
|
||||||
|
directory: dist
|
||||||
|
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
|
||||||
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
dist
|
||||||
|
repos
|
||||||
|
node_modules
|
||||||
|
package-lock.json
|
||||||
|
CLAUDE.md
|
||||||
|
.claude
|
||||||
|
.env
|
||||||
|
target
|
||||||
5
Cargo.toml
Normal file
5
Cargo.toml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
[package]
|
||||||
|
name = "ailog"
|
||||||
|
version = "0.2.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
8
config.json
Normal file
8
config.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"title": "syui.ai",
|
||||||
|
"handle": "syui.syui.ai",
|
||||||
|
"collection": "ai.syui.log.post",
|
||||||
|
"network": "syu.is",
|
||||||
|
"color": "#EF454A",
|
||||||
|
"siteUrl": "https://syui.ai"
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"uri": "at://did:plc:vzsvtbtbnwn22xjqhcu3vd6y/ai.syui.log.post/3mchqlshygs2s",
|
||||||
|
"cid": "bafyreielgn743kg5xotfj5x53edl25vkbbd2d6v7s3tydyyjsvczcluyme",
|
||||||
|
"value": {
|
||||||
|
"cid": "bafyreidymanu2xk4ftmvfdna3j7ixyijc37s6h3aytstuqgzatgjl4tp7e",
|
||||||
|
"uri": "at://did:plc:vzsvtbtbnwn22xjqhcu3vd6y/ai.syui.log.post/3mchqlshygs2s",
|
||||||
|
"$type": "ai.syui.log.post",
|
||||||
|
"title": "ailogを作り直した",
|
||||||
|
"content": "## ailogとは\n\natprotoと連携するサイトジェネレータ。\n\n## ailogの使い方\n\n```sh\n$ git clone https://git.syui.ai/ai/log\n$ cd log\n$ cat public/config.json\n{\n \"title\": \"syui.ai\",\n \"handle\": \"syui.syui.ai\",\n \"collection\": \"ai.syui.log.post\",\n \"network\": \"syu.is\",\n \"color\": \"#0066cc\",\n \"siteUrl\": \"https://syui.ai\"\n}\n---\n$ npm run dev\n```\n\n## ailogのコンセプト\n\n1. at-browserを基本にする\n2. atproto oauthでログインする\n3. ログインしたアカウントで記事をポストする\n\n## ailogの追加機能\n\n1. atproto recordからjsonをdownloadすると表示速度が上がる(ただし更新はlocalから)\n2. コメントはurlの言及を検索して表示\n\n```sh\n$ npm run fetch\n$ npm run generate\n```",
|
||||||
|
"createdAt": "2026-01-15T13:59:52.367Z",
|
||||||
|
"translations": {
|
||||||
|
"en": {
|
||||||
|
"title": "recreated ailog",
|
||||||
|
"content": "## What is ailog?\n\nA site generator that integrates with the atproto framework.\n\n## How to Use ailog\n\n```sh\n$ git clone https://git.syui.ai/ai/log\n$ cd log\n$ cat public/config.json\n{\n \"title\": \"syui.ai\",\n \"handle\": \"syui.syui.ai\",\n \"collection\": \"ai.syui.log.post\",\n \"network\": \"syu.is\",\n \"color\": \"#0066cc\",\n \"siteUrl\": \"https://syui.ai\"\n}\n---\n$ npm run dev\n```\n\n## ailog's Concept\n\n1. Based on at-browser as its foundation\n2. Authentication via atproto oAuth\n3. Post articles using the logged-in account\n\n## Additional Features of ailog\n\n1. Downloading JSON from atproto record improves display speed (though updates still come from local storage)\n2. Comments are displayed by searching for URL mentions\n\n```sh\n$ npm run fetch\n$ npm run generate\n```"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"uri": "at://did:plc:vzsvtbtbnwn22xjqhcu3vd6y/app.bsky.actor.profile/self",
|
||||||
|
"cid": "bafyreihlch2vdee6wpydo2bwap7nyzszjz6focbtxikz7zljcejxz27npy",
|
||||||
|
"value": {
|
||||||
|
"$type": "app.bsky.actor.profile",
|
||||||
|
"avatar": {
|
||||||
|
"$type": "blob",
|
||||||
|
"ref": {
|
||||||
|
"$link": "bafkreigta4pf5h7uvx6jpfcm3d6aeq4g3qpsiqjdoeytnutwp6vwc2yo7u"
|
||||||
|
},
|
||||||
|
"mimeType": "image/jpeg",
|
||||||
|
"size": 166370
|
||||||
|
},
|
||||||
|
"createdAt": "2025-09-19T06:17:42Z",
|
||||||
|
"description": "",
|
||||||
|
"displayName": "syui"
|
||||||
|
}
|
||||||
|
}
|
||||||
39
content/did:plc:vzsvtbtbnwn22xjqhcu3vd6y/describe.json
Normal file
39
content/did:plc:vzsvtbtbnwn22xjqhcu3vd6y/describe.json
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"handle": "syui.syui.ai",
|
||||||
|
"did": "did:plc:vzsvtbtbnwn22xjqhcu3vd6y",
|
||||||
|
"didDoc": {
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/did/v1",
|
||||||
|
"https://w3id.org/security/multikey/v1",
|
||||||
|
"https://w3id.org/security/suites/secp256k1-2019/v1"
|
||||||
|
],
|
||||||
|
"id": "did:plc:vzsvtbtbnwn22xjqhcu3vd6y",
|
||||||
|
"alsoKnownAs": [
|
||||||
|
"at://syui.syui.ai"
|
||||||
|
],
|
||||||
|
"verificationMethod": [
|
||||||
|
{
|
||||||
|
"id": "did:plc:vzsvtbtbnwn22xjqhcu3vd6y#atproto",
|
||||||
|
"type": "Multikey",
|
||||||
|
"controller": "did:plc:vzsvtbtbnwn22xjqhcu3vd6y",
|
||||||
|
"publicKeyMultibase": "zQ3shZj81oA4A9CmUQgYUv97nFdd7m5qNaRMyG16XZixytTmQ"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"service": [
|
||||||
|
{
|
||||||
|
"id": "#atproto_pds",
|
||||||
|
"type": "AtprotoPersonalDataServer",
|
||||||
|
"serviceEndpoint": "https://syu.is"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"collections": [
|
||||||
|
"ai.syui.log.post",
|
||||||
|
"app.bsky.actor.profile",
|
||||||
|
"app.bsky.feed.post",
|
||||||
|
"app.bsky.feed.repost",
|
||||||
|
"app.bsky.graph.follow",
|
||||||
|
"chat.bsky.actor.declaration"
|
||||||
|
],
|
||||||
|
"handleIsCorrect": true
|
||||||
|
}
|
||||||
68
lexicon/ai.syui.log.post.json
Normal file
68
lexicon/ai.syui.log.post.json
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
{
|
||||||
|
"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 (markdown)."
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "datetime",
|
||||||
|
"description": "Client-declared timestamp when this post was originally created."
|
||||||
|
},
|
||||||
|
"lang": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 10,
|
||||||
|
"description": "Language code of the original content (e.g., 'ja', 'en')."
|
||||||
|
},
|
||||||
|
"translations": {
|
||||||
|
"type": "ref",
|
||||||
|
"ref": "#translationMap",
|
||||||
|
"description": "Translations of the post in other languages."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"translationMap": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Map of language codes to translations.",
|
||||||
|
"properties": {
|
||||||
|
"en": { "type": "ref", "ref": "#translation" },
|
||||||
|
"ja": { "type": "ref", "ref": "#translation" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"translation": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "A translation of a post.",
|
||||||
|
"properties": {
|
||||||
|
"title": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 3000,
|
||||||
|
"maxGraphemes": 300
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 1000000,
|
||||||
|
"maxGraphemes": 100000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
network.json
Normal file
12
network.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"bsky.social": {
|
||||||
|
"plc": "https://plc.directory",
|
||||||
|
"bsky": "https://public.api.bsky.app",
|
||||||
|
"web": "https://bsky.app"
|
||||||
|
},
|
||||||
|
"syu.is": {
|
||||||
|
"plc": "https://plc.syu.is",
|
||||||
|
"bsky": "https://bsky.syu.is",
|
||||||
|
"web": "https://syu.is"
|
||||||
|
}
|
||||||
|
}
|
||||||
4
package.json
Normal file
4
package.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"name": "ailog",
|
||||||
|
"version": "0.2.0"
|
||||||
|
}
|
||||||
224
readme.md
Normal file
224
readme.md
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
|
```json
|
||||||
|
// 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
|
||||||
|
|
||||||
|
```json
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "https://syu.is/xrpc/com.atproto.repo.listRecords?repo=did:plc:xxx&collection=ai.syui.log.post"
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"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)
|
||||||
|
|
||||||
|
```
|
||||||
|
public/records/ai.syui.log.post/3xxx.json
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"uri": "at://did:plc:xxx/ai.syui.log.post/3xxx",
|
||||||
|
"cid": "local",
|
||||||
|
"value": {
|
||||||
|
"title": "Hello World",
|
||||||
|
"content": "# Hello\n\nThis is my post.",
|
||||||
|
"createdAt": "2025-01-01T00:00:00Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Resolution Strategy
|
||||||
|
|
||||||
|
```
|
||||||
|
at-browser
|
||||||
|
│
|
||||||
|
├── admin (config.json user)
|
||||||
|
│ ├── 1. Check local: /records/{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
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"did": "did:plc:xxxxx",
|
||||||
|
"handle": "syui.syui.ai",
|
||||||
|
"pds": "syu.is",
|
||||||
|
"collection": "ai.syui.log.post"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tech Stack
|
||||||
|
|
||||||
|
- **Frontend**: Vite + TypeScript
|
||||||
|
- **ATProto**: @atproto/api
|
||||||
|
- **OAuth**: @atproto/oauth-client-browser
|
||||||
|
- **Markdown**: marked + highlight.js
|
||||||
|
|
||||||
|
## Collection Schema
|
||||||
|
|
||||||
|
### ai.syui.log.post
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"title": "Post Title",
|
||||||
|
"content": "Markdown content...",
|
||||||
|
"createdAt": "2025-01-01T00:00:00Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
Reference in New Issue
Block a user