Koshi Documentation
Welcome to the Koshi documentation. This guide covers everything from installation to the full API reference. Koshi is a terminal-native, decentralized social network powered by ed25519 cryptography.
๐ฆ Version: 2.0.2 ยท License: MIT ยท Author: game_ryo
Installation
Koshi requires Node.js 18+ and PostgreSQL 15+ (for the server). The CLI can be installed independently.
Global Install (CLI only)
# From npm (requires package to be published)
$ npm install -g @ryopc/koshi
# Or from source
$ git clone https://github.com/ryopc/koshi.git
$ cd koshi
$ npm install
$ npm link
Server Installation
$ git clone https://github.com/ryopc/koshi.git
$ cd koshi
$ npm install
# Create PostgreSQL database
$ createdb koshi
# Run database migrations
$ DATABASE_URL=postgresql://user:pass@localhost:5432/koshi \
JWT_SECRET=your-secret \
node src/db/migrate.js
# Start the server
$ DATABASE_URL=postgresql://user:pass@localhost:5432/koshi \
JWT_SECRET=your-secret \
node bin/server.js
Or use a .env file:
# .env
DATABASE_URL=postgresql://user:pass@localhost:5432/koshi
JWT_SECRET=your-secret-change-in-production
PORT=3000
NODE_ENV=development
Quick Start
Once the server is running and you have the CLI installed:
# Register a new account (generates ed25519 keypair)
$ kb register alice
โ Registration successful!
# Post to the board
$ kb post "Hello, koshi! ๐"
โ Post created!
# View your feed
$ kb feed --limit=10
๐ Koshi Board Feed
# Follow someone
$ kb follow bob
# Live stream
$ kb realtime
Configuration
Credentials are stored in ~/.config/koshi/config.json and ~/.snsrc. The CLI reads these automatically.
| Variable | Default | Description |
|---|---|---|
KOSHI_API_URL | https://koshi-api.ryopc.f5.si | API base URL |
KOSHI_WS_URL | wss://koshi-api.ryopc.f5.si | WebSocket URL |
CLI Reference
The kb command is the gateway to Koshi. All commands are terminal-native with colored output via Chalk and loading spinners via Ora.
$ kb --help
๐ koshi โ Terminal-Native Decentralized SNS
Version 1.2.0
Commands:
register Create a new account with ed25519 keypair
login Authenticate using existing keypair
whoami Show your profile information
post Create a new post on the koshi board
feed Display your post feed
follow Follow a user
unfollow Unfollow a user
dm Send a direct message
dms View your direct messages
profile View a user profile
search Search users by username
realtime Connect to the real-time event stream
chat Real-time interactive DM chat
edit-profile Update your own profile
admin Admin: users, delete-user, grant, revoke
help Show this help message
Auth Commands
kb register <username>
Creates a new account. Generates an ed25519 keypair locally, sends the public key to the server, and stores credentials in ~/.config/koshi/config.json.
$ kb register alice
โ Generating ed25519 keypair...
โ Registration successful!
Username: alice
User ID: e7d00463-38f8-4d85-8231-6762bb988f7d
Token: eyJhbGciOiJIUzI1NiIs...
โ You are now logged in.
kb login <username>
Authenticates using an existing keypair stored in ~/.snsrc. Signs a challenge with your secret key to prove identity.
$ kb login alice
โ Signing authentication challenge...
โ Login successful!
Post Commands
kb post <message>
Creates a new post on the Koshi board. Content is signed with your ed25519 key. Max 2000 characters.
$ kb post "Hello, koshi! ๐"
โ Signing and posting...
โ Post created!
ID: 550e8400-e29b-41d4-a716-446655440000
Posted: 7/14/2026, 2:30:00 PM
kb feed [--limit=N]
Displays the post feed. Shows posts from users you follow plus your own. Falls back to global feed when not authenticated.
$ kb feed --limit=5
๐ Koshi Board Feed (5 posts)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Alice @alice
7/14/2026, 2:30:00 PM
Hello, koshi! ๐
Social Commands
kb follow <username>
$ kb follow bob
โ Following @bob...
โ You are now following @bob!
kb unfollow <username>
$ kb unfollow bob
โ Unfollowing @bob...
โ Unfollowed @bob.
kb profile [username]
View a user's public profile. Defaults to your own profile when no username is given.
$ kb profile alice
๐ค Profile
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Username: alice
Display Name: Alice
Bio: Building the terminal future
Followers: 42
Following: 12
Joined: 7/14/2026
kb search <query>
Search users by username or display name. Query must be at least 2 characters.
$ kb search ali
๐ Search Results for "ali"
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
alice โ Alice
kb whoami
Show your own profile information.
$ kb whoami
Username: alice
Followers: 42
Following: 12
Joined: 7/14/2026
Multi-Account Management ๐
Koshi v2.0 supports managing multiple accounts from a single CLI configuration. Each account has its own ed25519 keypair, Nostr keys (optional), and JWT token.
kb register <username>
Create a new account. The new account is automatically added to the multi-account config and set as active.
kb login [username]
Without arguments, shows an interactive list of saved accounts to choose from. With a username, logs in to that specific account.
$ kb login
๐ ใขใซใฆใณใใ้ธๆ
1. alice โ โ ็พๅจ
2. bob โ
0. ใญใฃใณใปใซ
็ชๅทใ้ธๆ (1-2): 2
โ bob ใ้ธๆใใพใใ
kb switch [username]
Switch between saved accounts without re-authenticating. Interactive selection if no username is given.
kb accounts
List all saved accounts and their login status.
$ kb accounts
๐ ใขใซใฆใณใไธ่ฆง (2ไปถ)
1. alice โ ใญใฐใคใณๆธ โ ็พๅจใฎใขใซใฆใณใ
2. bob ๆชใญใฐใคใณ
kb account remove <username>
Remove a saved account from the local configuration. Shows confirmation prompt before deleting.
$ kb account remove bob
๐๏ธ @bob ใใญใผใซใซ่จญๅฎใใๅ้คใใฆใใใใใใงใใ๏ผ (y/N): y
โ @bob ใใญใผใซใซ่จญๅฎใใๅ้คใใพใใใ
Account credentials are stored in ~/.config/koshi/config.json in the following format:
{
"version": "2.0.2",
"activeUsername": "alice",
"accounts": {
"alice": {
"userId": "uuid",
"username": "alice",
"publicKey": "hex...",
"secretKey": "hex...",
"token": "jwt...",
"nostr": { ... }
},
"bob": { ... }
}
}
kb edit-profile --display-name=... --bio=... --avatar-url=...
Update your own profile fields. Only include the flags you want to change.
$ kb edit-profile --display-name="Alice" --bio="Building the terminal future"
โ Updating profile...
โ Profile updated!
Username: alice
Display Name: Alice
Bio: Building the terminal future
Available flags:
--display-name="Your Name" # Max 64 characters
--bio="About you" # Max 500 characters
--avatar-url="https://..." # Max 512 characters, must be valid URL
Message Commands
kb dm <username> <message>
Send a signed direct message to another user.
$ kb dm bob "Hey, how's it going?"
โ Sending DM to @bob...
โ DM sent to @bob!
ID: 550e8400-e29b-41d4-a716-446655440001
kb dms [--unread] [--limit=N]
View your DM inbox. Use --unread to show only unread messages.
$ kb dms --unread
๐จ Unread Messages (2 messages)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ From: Bob @bob
7/14/2026, 2:35:00 PM
Hey Alice! Welcome to koshi ๐
Realtime Command
kb realtime
Connects to the WebSocket event stream for live updates. Shows new posts, DMs, and user online/offline events in real time.
$ kb realtime
๐ก Connecting to realtime feed...
โ Connected! Waiting for new posts...
๐ New Post
bob 2:40:00 PM
Just joined koshi!
Press Ctrl+C to disconnect.
Interactive Chat Command
kb chat <username>
Starts an interactive real-time DM session with another user. Messages are sent and received via WebSocket in real time, with ed25519 signatures for authenticity.
$ kb chat bob
๐ฌ Live Chat with bob
Connect to real-time DMs. Type your message and press Enter.
Type /exit or press Ctrl+C to quit.
โ Connected!
๐ข bob is now online
> Hey Bob! How are you?
Bob: Hey Alice! I'm great, thanks!
>
Features:
- Real-time message delivery via WebSocket
- Messages signed with your ed25519 key
- See when the other user is online/offline
- Type
/exitor press Ctrl+C to quit
Admin Commands
kb admin users
List all registered users with their stats (posts, followers, join date). Admin privileges required.
$ kb admin users
๐ฅ All Users (3 total)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
alice [ADMIN] โ Alice
ID: e7d00463-... | Posts: 42 | Followers: 12 | Joined: 7/14/2026
bob โ Bob
ID: f8e11574-... | Posts: 7 | Followers: 3 | Joined: 7/14/2026
kb admin user <id|username>
View detailed information about a specific user, including public key, DM count, and admin status. Accepts either a UUID or a username.
kb admin delete-user <username>
Permanently delete a user account and all associated data (posts, follows, DMs โ cascade deleted). Uses interactive confirmation.
$ kb admin delete-user bob
โ ๏ธ WARNING: This will permanently delete the user and all their data.
This action cannot be undone.
Target: @bob
Type the username to confirm: bob
โ User @bob has been permanently deleted.
Use --force flag to skip confirmation for scripting.
kb admin grant <username>
Grant admin privileges to a user.
$ kb admin grant bob
โ @bob is now an admin!
kb admin revoke <username>
Revoke admin privileges from a user.
$ kb admin revoke bob
โ Admin privileges removed from @bob.
CLI Environment Variables
| Variable | Default | Description |
|---|---|---|
KOSHI_API_URL | https://koshi-api.ryopc.f5.si | API base URL for all requests |
KOSHI_WS_URL | wss://koshi-api.ryopc.f5.si | WebSocket URL for realtime |
Nostr Integration ๐
Koshi v2.0 integrates with the Nostr protocol, allowing you to bridge your koshi identity with the broader Nostr network. You can generate Nostr keys, push posts to relays, and pull events from the Nostr ecosystem.
โจ New in v2.0: Nostr integration enables cross-posting between koshi board and Nostr relays, making your content discoverable beyond the koshi ecosystem.
Key Management
Generate or import Nostr keys (nsec/npub) directly from the CLI:
# Generate a new Nostr keypair
$ kb nostr key generate
โ Nostr keypair generated and saved!
npub: npub1...
nsec: nsec1...
# Import an existing nsec key
$ kb nostr key import nsec1...
# Show current keys
$ kb nostr key show
# Display public key (npub)
$ kb nostr npub
Pushing Posts to Nostr
Publish your koshi board posts to Nostr relays as kind 1 (short text note) events:
$ kb nostr push --limit=20
โ 5/20 posts published to Nostr!
๐ก Relay Results:
โข wss://relay.damus.io โ 5 published
โข wss://relay.nostr.band โ 5 published
Pulling Events from Nostr
Fetch Nostr events and display them in koshi format:
$ kb nostr pull --limit=50
โ Found 12 Nostr events!
๐ก Nostr Events (12ไปถ)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
nostr:a1b2c3d4 ...
Hello from Nostr!
Relay Management
Add, remove, list, and test Nostr relay connections:
$ kb nostr relay list
๐ Nostr Relays (4ไปถ)
โข wss://relay.damus.io
โข wss://relay.nostr.band
$ kb nostr relay add wss://relay.example.com
โ ใชใฌใผใ่ฟฝๅ ใใพใใ: wss://relay.example.com
$ kb nostr relay test wss://relay.damus.io
โ wss://relay.damus.io Latency: 120ms
P2P Sync ๐
Koshi v2.0 introduces peer-to-peer synchronization powered by the Hypercore protocol stack. Your posts and DMs are replicated across connected peers using hyperswarm DHT for discovery, enabling offline-first and decentralized data distribution.
โจ New in v2.0: P2P sync uses hypercore (append-only logs with Merkle tree verification), corestore (hypercore factory), and hyperswarm (DHT-based peer discovery with NAT traversal).
Starting a P2P Node
Launch a P2P node to start syncing with peers:
$ kb p2p start
โ P2P node started!
Status: Running
Posts: 42
DMs: 12
Peers: 3
$ kb p2p status
Status: Running
Posts: 42
DMs: 12
Peers: 3
Data Sync
View synced data and manage replication:
# View synced posts
$ kb p2p sync
# View synced DMs
$ kb p2p dms
# Stop the P2P node
$ kb p2p stop
โ P2P node stopped.
Configuration
P2P settings are stored in ~/.config/koshi/config.json under the p2p key:
{
"p2p": {
"corestorePath": "/home/user/.config/koshi/corestore",
"autoSync": false,
"port": 0
}
}
Enable automatic P2P startup by setting "autoSync": true in the config.
API Reference
Koshi exposes a RESTful JSON API. All endpoints are prefixed with /api.
Base URLs:
Development: http://localhost:3000/api
Production: https://koshi-api.ryopc.f5.si/api
Authentication
Authenticated endpoints require a JWT Bearer token in the Authorization header:
Authorization: Bearer <token>
POST /api/auth/register
Register a new user with an ed25519 public key. Rate limited to 10 req/min.
| Method | Path | Auth | Rate Limit |
|---|---|---|---|
| POST | /api/auth/register | No | 10/min |
Request Body:
{
"username": "alice", // 3-32 chars, alphanumeric + underscores
"publicKey": "abcdef..." // 64-char hex string (32 bytes)
}
Response (201):
{
"userId": "e7d00463-38f8-4d85-8231-6762bb988f7d",
"token": "eyJhbGciOiJIUzI1NiIs..."
}
Errors: 400 (validation), 409 (already registered), 500 (server error)
POST /api/auth/login
Login by signing a challenge with your ed25519 key. The challenge format is koshi:login:{username}.
| Method | Path | Auth | Rate Limit |
|---|---|---|---|
| POST | /api/auth/login | No | 10/min |
Request Body:
{
"username": "alice",
"signature": "abc123..." // 128-char hex signature of challenge
}
Response (200):
{
"token": "eyJhbGciOiJIUzI1NiIs...",
"userId": "e7d00463-38f8-4d85-8231-6762bb988f7d"
}
User Endpoints
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/users/:username | No | Get user profile |
| PUT | /api/users/me | Yes | Update own profile |
| GET | /api/users/search/:query | No | Search users (min 2 chars) |
| GET | /api/users/:id/followers | No | Get user's followers |
| GET | /api/users/:id/following | No | Get user's following |
| POST | /api/users/:id/follow | Yes | Follow a user |
| DELETE | /api/users/:id/follow | Yes | Unfollow a user |
GET /api/users/:username
$ curl https://koshi-api.ryopc.f5.si/api/users/alice
{
"id": "e7d00463-38f8-4d85-8231-6762bb988f7d",
"username": "alice",
"displayName": "Alice",
"bio": "Building the terminal future",
"avatarUrl": null,
"followersCount": 42,
"followingCount": 12,
"createdAt": "2026-07-14T05:00:00.000Z"
}
PUT /api/users/me
Update profile fields. Only include fields you want to change.
$ curl -X PUT https://koshi-api.ryopc.f5.si/api/users/me \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"displayName": "Alice",
"bio": "Building the terminal future"
}'
Post Endpoints
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/posts/feed | Optional | Get post feed (user's follows or global) |
| POST | /api/posts | Yes | Create a new post |
| GET | /api/posts/:id | No | Get a single post |
GET /api/posts/feed
Returns posts from followed users + own posts (authenticated) or global feed (anonymous).
Query params: ?limit=20 (max 100), ?offset=0
POST /api/posts
Create a new post. Content must be signed with your ed25519 key. Rate limited to 10 posts/min.
$ curl -X POST https://koshi-api.ryopc.f5.si/api/posts \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"content": "Hello, koshi! ๐",
"signature": "abc123..."
}'
Response (201):
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"author": {
"id": "e7d00463-38f8-4d85-8231-6762bb988f7d",
"username": "alice",
"displayName": "Alice"
},
"content": "Hello, koshi! ๐",
"signature": "abc123...",
"createdAt": "2026-07-14T05:00:00.000Z"
}
Admin Endpoints
Admin endpoints require authentication AND admin privileges. Admin status is determined by the is_admin flag in the database or the ADMIN_USERNAME environment variable.
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/admin/users | Admin | List all users with stats |
| GET | /api/admin/users/:id | Admin | Get detailed user info โ accepts UUID or username |
| DELETE | /api/admin/users/:id | Admin | Permanently delete a user โ accepts UUID or username |
| PUT | /api/admin/users/:id/admin | Admin | Grant/revoke admin privileges โ accepts UUID or username |
DELETE /api/admin/users/:id
Permanently deletes a user and all associated data (posts, follows, DMs) via database CASCADE. Accepts either a UUID or a username as the :id parameter.
$ curl -X DELETE https://koshi-api.ryopc.f5.si/api/admin/users/bob \
-H "Authorization: Bearer <admin-token>"
# Or using a UUID:
$ curl -X DELETE https://koshi-api.ryopc.f5.si/api/admin/users/f8e11574-38f8-4d85-8231-6762bb988f7d \
-H "Authorization: Bearer <admin-token>"
{
"success": true,
"deletedUser": {
"id": "f8e11574-...",
"username": "bob"
}
}
๐ก Tip: All admin user endpoints accept both a UUID or a username in the :id path parameter. The server automatically detects which one you're using.
PUT /api/admin/users/:id/admin
Toggle admin status for a user. Accepts either a UUID or a username as the :id parameter. Body: { "isAdmin": true } or { "isAdmin": false }.
$ curl -X PUT https://koshi-api.ryopc.f5.si/api/admin/users/bob/admin \
-H "Authorization: Bearer <admin-token>" \
-H "Content-Type: application/json" \
-d '{"isAdmin": true}'
{
"success": true,
"user": {
"id": "...",
"username": "bob",
"isAdmin": true
}
}
DM Endpoints
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /api/dms | Yes | Get DM inbox |
| POST | /api/dms/:userId | Yes | Send a DM to a user |
| PUT | /api/dms/:id/read | Yes | Mark DM as read |
| GET | /api/dms/unread/count | Yes | Count unread DMs |
GET /api/dms
Query params: ?limit=50, ?offset=0, ?unread=true
POST /api/dms/:userId
Send a signed direct message. Content max 5000 characters.
$ curl -X POST https://koshi-api.ryopc.f5.si/api/dms/TARGET_USER_ID \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"content": "Hey there!",
"signature": "abc123..."
}'
Health Check
| Method | Path | Description |
|---|---|---|
| GET | /api/health | Server health check (no DB required) |
$ curl https://koshi-api.ryopc.f5.si/api/health
{
"status": "ok",
"service": "koshi-api",
"version": "1.2.0",
"timestamp": "2026-07-14T05:00:00.000Z"
}
Cryptography
Koshi uses ed25519 elliptic curve cryptography for all identity and signing operations. This is the same curve used by the Nostr protocol.
Key Generation
When you register, a new ed25519 keypair is generated locally using tweetnacl. Keys are hex-encoded for storage:
import nacl from 'tweetnacl';
function generateKeypair() {
const keypair = nacl.sign.keyPair();
return {
publicKey: bytesToHex(keypair.publicKey), // 64 hex chars (32 bytes)
secretKey: bytesToHex(keypair.secretKey), // 128 hex chars (64 bytes)
};
}
Key formats:
Public Key: 64-character hex string (32 bytes)
Secret Key: 128-character hex string (64 bytes: seed + public key)
Signature: 128-character hex string (64 bytes)
Signing & Verification
Messages (posts, DMs) are signed with your secret key using @noble/ed25519. Verification uses your public key.
Signing (CLI side)
import * as ed from '@noble/ed25519';
async function signMessage(message, secretKey) {
const skBytes = hexToBytes(secretKey);
const msgBytes = new TextEncoder().encode(message);
const signature = await ed.sign(msgBytes, skBytes);
return bytesToHex(signature);
}
Verification (Server side)
async function verifySignature(message, signature, publicKey) {
const msgBytes = new TextEncoder().encode(message);
const sigBytes = hexToBytes(signature);
const pkBytes = hexToBytes(publicKey);
return await ed.verify(sigBytes, msgBytes, pkBytes);
}
Authentication Flow
The authentication flow works as follows:
- Registration: Client generates keypair, sends public key + username to server. Server stores the public key and issues a JWT.
- Login: Client signs the challenge string
"koshi:login:{username}"with their secret key. Server verifies the signature against the stored public key and issues a JWT. - Subsequent requests: JWT token is sent in the
Authorization: Bearer {token}header for all authenticated endpoints.
JWT Tokens
JWTs are signed using HS256 (HMAC with SHA-256) and expire after 24 hours.
{
// Token payload
userId: "e7d00463-38f8-4d85-8231-6762bb988f7d",
username: "alice",
iat: 1784006345,
exp: 1784092745 // 24 hours
}
โ Important: Protect your secret key and JWT secret. The JWT secret (JWT_SECRET) should be a cryptographically random string (32+ bytes). Generate one with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
Key Derivation
You can derive the public key from a secret key using derivePublicKey(secretKey). This works for both 32-byte seeds and 64-byte full secret keys.
WebSocket Reference
Koshi supports real-time communication via WebSocket. Connect with your JWT token for authentication.
Connection
# Connect with JWT token
ws://host:port/ws?token=<your-jwt-token>
# Production example
wss://koshi-api.ryopc.f5.si/ws?token=eyJhbGciOiJIUzI1NiIs...
Server Events (received from server)
| Event | Payload | Description |
|---|---|---|
connected | {} | Initial connection confirmation |
post_created | { author, content, timestamp } | New post on the board |
dm_received | { from, content, timestamp } | New DM received (targeted to recipient) |
user_online | { userId, username } | User came online |
user_offline | { userId } | User went offline |
follow_notification | { follower, following } | Someone followed you |
pong | { timestamp } | Heartbeat response |
Client Actions (sent to server)
| Type | Payload | Description |
|---|---|---|
ping | {} | Heartbeat ping |
post:create | { content, signature } | Create a new post |
dm:send | { recipientId, content, signature } | Send a DM |
follow | { userId } | Follow a user |
unfollow | { userId } | Unfollow a user |
Example: Create post via WebSocket
// Send
{
"type": "post:create",
"payload": {
"content": "Hello from WebSocket!",
"signature": "abc123..."
}
}
// Receive confirmation
{
"type": "post:created",
"payload": {
"id": "550e8400-...",
"author": { "id": "...", "username": "alice" },
"content": "Hello from WebSocket!",
"timestamp": "2026-07-14T05:00:00.000Z"
}
}
Database Schema
Koshi uses PostgreSQL 15 with the following schema. All tables use UUID primary keys and include timestamps.
Tables
users
Stores user identities with ed25519 public keys.
| Column | Type | Constraints |
|---|---|---|
id | UUID | PK, default uuid_generate_v4() |
username | VARCHAR(32) | UNIQUE, NOT NULL |
public_key | TEXT | UNIQUE, NOT NULL |
display_name | VARCHAR(64) | Nullable |
bio | TEXT | Nullable |
avatar_url | VARCHAR(512) | Nullable |
is_admin | BOOLEAN | NOT NULL, default FALSE |
created_at | TIMESTAMPTZ | NOT NULL, default NOW() |
updated_at | TIMESTAMPTZ | NOT NULL, auto-updated |
follows
Tracks user follow relationships.
| Column | Type | Constraints |
|---|---|---|
id | UUID | PK |
follower_id | UUID | FK โ users(id), ON DELETE CASCADE |
following_id | UUID | FK โ users(id), ON DELETE CASCADE |
created_at | TIMESTAMPTZ | NOT NULL |
UNIQUE (follower_id, following_id) | ||
kb_posts
Stores signed posts on the Koshi board.
| Column | Type | Constraints |
|---|---|---|
id | UUID | PK |
author_id | UUID | FK โ users(id) |
content | TEXT | CHECK 1-2000 characters |
signature | TEXT | NOT NULL |
created_at | TIMESTAMPTZ | NOT NULL, indexed DESC |
dms
Stores signed direct messages between users.
| Column | Type | Constraints |
|---|---|---|
id | UUID | PK |
sender_id | UUID | FK โ users(id) |
recipient_id | UUID | FK โ users(id) |
content | TEXT | CHECK 1-5000 characters |
signature | TEXT | NOT NULL |
is_read | BOOLEAN | DEFAULT FALSE |
created_at | TIMESTAMPTZ | NOT NULL |
Indexes exist on username, public_key, follower/following IDs, post timestamps, and DM recipient inbox queries.
Deployment
Render.com + Neon.tech (Recommended)
1. Set up Neon.tech Database
- Go to Neon.tech and create an account
- Create a new project (PostgreSQL 15)
- Get your connection string from Connection Details
- For production, use the pooled connection string (
?pgbouncer=true)
2. Deploy on Render.com
- Push the repo to GitHub
- Go to Render.com and connect your GitHub repo
- Render will auto-detect
render.yaml(Blueprint) and create the service - In Render dashboard, add environment variables:
DATABASE_URLโ your Neon.tech connection stringJWT_SECRETโ generate with:node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
- Render will auto-deploy on every push to
main
Auto-migration: Koshi runs database migrations automatically on every server startup. Tables are created before the HTTP server starts listening, so no manual migration step is needed.
Server Environment Variables
| Variable | Required | Description |
|---|---|---|
DATABASE_URL | โ | PostgreSQL connection string |
JWT_SECRET | โ | Secret key for JWT signing (32+ bytes) |
PORT | โ | Server port (default: 3000) |
NODE_ENV | โ | development or production |
LOG_LEVEL | โ | debug, info, warn, error |
ADMIN_USERNAME | โ | Bootstrap the first admin user (e.g., alice) |
Docker
# Build the image
$ docker build -t koshi-api .
# Run the container
$ docker run -d \
--name koshi-api \
-p 3000:3000 \
-e DATABASE_URL=postgresql://... \
-e JWT_SECRET=... \
koshi-api
Docker Compose
version: '3.8'
services:
db:
image: postgres:15-alpine
environment:
POSTGRES_DB: koshi
POSTGRES_PASSWORD: postgres
volumes:
- pgdata:/var/lib/postgresql/data
api:
build: .
ports:
- "3000:3000"
environment:
DATABASE_URL: postgresql://postgres:postgres@db:5432/koshi
JWT_SECRET: change-this-in-production
depends_on:
- db
volumes:
pgdata:
Security
- Ed25519 Signatures โ All posts and DMs are signed for authenticity. No passwords.
- JWT Auth โ Tokens expire after 24 hours. Use
JWT_SECRETenvironment variable. - Parameterized Queries โ All SQL uses parameterized queries ($1, $2) to prevent SQL injection.
- Input Validation โ All endpoints validate input format, length, and types.
- Helmet โ Security headers (CSP, HSTS, X-Frame-Options, etc.) are enabled.
- No Hardcoded Secrets โ Everything via environment variables.
- CORS โ Configured for CLI client origins.
Rate Limiting
Koshi uses express-rate-limit to protect endpoints:
| Limiter | Rate | Applied To |
|---|---|---|
authLimiter | 10 requests/min per IP | Register & Login |
apiLimiter | 100 requests/min per IP | All API routes |
postLimiter | 10 posts/min per user | Post creation |
Development
Setup
$ git clone https://github.com/ryopc/koshi.git
$ cd koshi
$ npm install
$ npm run dev # Auto-reload with nodemon
$ npm run migrate # Run database migrations
$ npm test # Run tests
$ npm run lint # Run linter
Project Structure
koshi/
โโโ bin/
โ โโโ cli.js # CLI/TUI entry point (kb command)
โ โโโ server.js # Express + WebSocket server
โโโ src/
โ โโโ api/
โ โ โโโ auth.js # Auth routes (register/login)
โ โ โโโ users.js # User management routes
โ โ โโโ posts.js # Posts routes (koshi board)
โ โ โโโ dms.js # Direct messages routes
โ โ โโโ admin.js # Admin routes (users, delete, grant)
โ โโโ auth/
โ โ โโโ ed25519.js # Ed25519 crypto utilities
โ โ โโโ jwt.js # JWT token utilities
โ โ โโโ utils.js # Hex encoding utilities
โ โโโ db/
โ โ โโโ schema.sql # PostgreSQL schema
โ โ โโโ migrate.js # Migration script
โ โ โโโ pool.js # Database connection pool
โ โโโ middleware/
โ โ โโโ auth.js # Auth middleware
โ โ โโโ rateLimit.js # Rate limiting
โ โโโ ws/
โ โ โโโ index.js # WebSocket server setup
โ โ โโโ handlers.js # WebSocket message handlers
โ โโโ index.js # Express app setup
โโโ Dockerfile
โโโ render.yaml # Render Blueprint config
โโโ package.json