.crustagent/skills/clawkeys-login/SKILL.md
The standard Lobsterized©™ ClawKeys©™ login flow. Upload or paste your identity key — no passwords, no accounts, just your claw.
npx skillsauth add acidgreenservers/clawchives clawkeys-login-flow©™Install this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Login is a single-screen flow. The user presents their identity file (or, planned: their raw key) and the session is restored. No password. No account lookup by email. The key IS the identity.
┌─────────────────────────────────────────────────────────────────────┐
│ CLAWCHIVES©™ LOGIN SCREEN │
└─────────────────────────────────────────────────────────────────────┘
CURRENT IMPLEMENTATION:
┌─────────────────────────────────────────┐
│ Login with ClawKey©™ │
│ │
│ ┌─────────────────────────────────┐ │
│ │ │ │
│ │ Drop your identity file │ │
│ │ here, or click to browse │ │
│ │ │ │
│ │ (.json files only) │ │
│ │ │ │
│ └─────────────────────────────────┘ │
│ │
│ [ ] PLANNED: ──────────────────────── │
│ │ "Paste ClawKey" second option │
│ └──────────────────────────────────── │
│ │
│ [Login with Identity File] │
│ (disabled until file selected) │
└─────────────────────────────────────────┘
│
│ on "Login with Identity File"
▼
┌──────────────────────────┐
│ FileReader.readAsText() │
│ JSON.parse() │
│ validateIdentityFile() │
└──────────┬───────────────┘
│ valid
▼
┌──────────────────────────┐
│ hashToken(token) │
│ → SHA-256(hu-key) hex │
└──────────┬───────────────┘
│
▼
┌──────────────────────────┐ ┌──────────────────────────┐
│ POST /api/auth/token │────►│ Server verifies keyHash │
│ { type:"human", │ │ (constant-time compare) │
│ uuid, keyHash } │◄────│ → issues api-[32chars] │
└──────────┬───────────────┘ └──────────────────────────┘
│ success
▼
sessionStorage.setItem(×4)
│
▼
onSuccess(uuid)
│
▼
Dashboard
PLANNED ADDITION — "Paste ClawKey" path:
┌───────────────────────────┐
│ User pastes hu-[64chars] │
│ + provides username input │
│ (uuid lookup complexity — │
│ see Planned Additions) │
└──────────┬────────────────┘
│
▼
validate → hash → POST /api/auth/token (same as upload path)
These conditions must be true before login can succeed:
getApiBaseUrl() resolves correctly)GET /api/health returns 200 OKsessionStorage (if cc_api_token is set, the app should redirect to the dashboard, not show the login screen)clawchives_identity_{username}.json) available on disk or clipboardvalidateIdentityFile())uuid in the identity file is registered in the server's users table (i.e., the account exists — setup was previously completed)crypto.subtle.digest() (required for SHA-256 hashing; available on all HTTPS origins and localhost)What the user sees:
.json filesWhat the code does:
application/json / .jsonFile object in component state (selectedFile)clawchives_identity_alice.json selected)State changes:
selectedFile — the browser File objectdisabled to activeTriggered by: User clicking "Login with Identity File"
What the code does:
// 1. Read the file
const text = await readFileAsText(selectedFile) // FileReader.readAsText()
// 2. Parse JSON
const parsed = JSON.parse(text)
// 3. Validate structure
const identity = validateIdentityFile(parsed)
// throws if: missing fields, wrong types, token doesn't start with "hu-"
validateIdentityFile() checks:
identity.token exists and is a stringidentity.token starts with "hu-"identity.token has length of exactly 67 ("hu-" + 64 chars)identity.uuid exists and is a non-empty stringidentity.username exists and is a non-empty stringOn validation failure:
selectedFile remains set; user can try again without re-selectingWhat the code does (immediately after successful validation):
const keyHash = await hashToken(identity.token)
// hashToken: SHA-256(hu-key) → hex string
// identity.token (raw hu- key) is NEVER sent to server
The raw identity.token (hu- key) is used exactly once — as input to hashToken(). After hashing, it is not stored, not logged, and not referenced again in the login flow.
What the code does:
const response = await fetch(`${getApiBaseUrl()}/api/auth/token`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
type: "human",
uuid: identity.uuid,
keyHash: keyHash
})
})
const { token } = await response.json()
On success:
sessionStorage.setItem("cc_api_token", token)
sessionStorage.setItem("cc_username", identity.username)
sessionStorage.setItem("cc_user_uuid", identity.uuid)
sessionStorage.setItem("cc_key_type", "human")
onSuccess(identity.uuid) // triggers navigation to dashboard
validateIdentityFile(parsed: unknown): IdentityFile// Location: src/components/auth/LoginForm.tsx (or src/lib/crypto.ts)
interface IdentityFile {
username: string
uuid: string
token: string // raw hu-[64chars] key
createdAt: string
}
function validateIdentityFile(parsed: unknown): IdentityFile {
if (typeof parsed !== "object" || parsed === null) throw new Error("Not an object")
const { username, uuid, token, createdAt } = parsed as Record<string, unknown>
if (typeof token !== "string") throw new Error("token must be a string")
if (!token.startsWith("hu-")) throw new Error("token must start with hu-")
if (token.length !== 67) throw new Error("token must be 67 characters")
if (typeof uuid !== "string" || !uuid) throw new Error("uuid is required")
if (typeof username !== "string" || !username) throw new Error("username is required")
return { username, uuid, token, createdAt: String(createdAt ?? "") }
}
hashToken(token: string): Promise<string>// Location: src/lib/crypto.ts
async function hashToken(token: string): Promise<string> {
const encoder = new TextEncoder()
const data = encoder.encode(token)
const hashBuffer = await crypto.subtle.digest("SHA-256", data)
const hashArray = Array.from(new Uint8Array(hashBuffer))
return hashArray.map(b => b.toString(16).padStart(2, "0")).join("")
}
// Input: "hu-aB3xK9mZ2pQ7rT1wYn..." (67 chars)
// Output: "e3b0c44298fc1c149afb..." (64 hex chars = 256 bits)
Browser (Client) Server
──────────────── ──────
identity.token = "hu-[64chars]"
(read from identity file — stays in RAM)
│
▼
hashToken(identity.token)
→ crypto.subtle.digest("SHA-256", ...)
│
▼
keyHash = "e3b0c44..." (hex, 64 chars)
│
├──── POST /api/auth/token ──────────► looks up user by uuid
│ { type:"human", retrieves stored keyHash
│ uuid: "3f4a2b1c-...", timingSafeEqual(
│ keyHash: "e3b0c44..." } incoming keyHash,
│ stored keyHash
│ ) → true
│ generates api-[32chars]
│ INSERT INTO api_tokens
│◄─── { token: "api-[32chars]" } ───────
│
▼
sessionStorage.setItem(×4)
→ onSuccess(uuid)
→ Dashboard rendered
Security note: Server uses constant-time comparison for keyHash verification. Timing attacks that attempt to infer whether a hash is correct by measuring response latency are mitigated at the server level.
On successful token exchange, four keys are written to sessionStorage:
| Key | Value | Description |
|------------------|--------------------------------|-----------------------------------------------------|
| cc_api_token | "api-[32chars]" | Bearer token for all subsequent authenticated API calls |
| cc_username | "alice" (from identity file) | Display name for UI personalization |
| cc_user_uuid | "3f4a2b1c-..." | User UUID; used for client-side identity tracking |
| cc_key_type | "human" | Key type; gates human-only UI features (e.g., r.jina.ai controls) |
Lifecycle: sessionStorage is tab-scoped. All four keys are cleared automatically when the browser tab is closed. A page refresh also clears session. Users must log in again after any session loss — this is expected and intentional behavior.
cc_key_type significance: Components check cc_key_type === "human" to conditionally render features unavailable to agent (lb-) sessions. Login always writes "human" here. Agent sessions are established via a separate flow and write "lobster" or "agent".
One API call is made during login. It uses getApiBaseUrl() from src/config/apiConfig.ts.
POST {apiBase}/api/auth/token
Content-Type: application/json
Request Body:
{
"type": "human",
"uuid": "3f4a2b1c-dead-beef-cafe-0123456789ab",
"keyHash": "e3b0c44298fc1c149afb4c8996fb92427ae41e4649b934ca495991b7852b855"
}
Success Response — 200 OK:
{
"token": "api-aBcDeFgHiJkLmNoPqRsTuVwXyZ123456"
}
Error Responses:
401 Unauthorized → keyHash does not match stored hash (constant-time comparison failed)
404 Not Found → UUID does not exist in users table (account never registered)
400 Bad Request → Missing or malformed type, uuid, or keyHash fields
429 Too Many Requests → Rate limit exceeded (planned: security component 02)
500 Internal → Server error
This endpoint does NOT require a bearer token. It is one of only two public endpoints (/api/auth/register and /api/auth/token). All other endpoints require Authorization: Bearer api-[32chars].
<textarea> where the user pastes their raw hu-[64chars] key directly (no file needed)."hu-""hu-" + 64)hashToken() and POST /api/auth/token — same as upload flow.Complexity Flag — UUID and Username Recovery:
The upload flow extracts uuid and username from the identity file JSON. The paste flow has only the raw hu- key — no uuid, no username. This creates a resolution problem:
POST /api/auth/tokenrequires{ type, uuid, keyHash }— theuuidfield is mandatory.
Three possible resolution strategies (none yet implemented — flag for architectural decision):
[ ] PLANNED — Option A (Server Lookup Endpoint): Add GET /api/auth/lookup?keyHash={hash} that returns { uuid, username } given a valid key hash. The paste flow calls this first, then calls /api/auth/token. Risk: Lookup endpoint could be abused for enumeration — must be rate-limited and return identical timing for hits/misses.
[ ] PLANNED — Option B (Username Input Field): Add a username text input alongside the paste textarea. User provides both their hu- key and their username. Server stores username → uuid mapping, so the server resolves uuid from username. Risk: Username typos cause auth failure with confusing error messages; usernames are not secret but treating them as lookup keys may be unexpected.
[ ] PLANNED — Option C (Token-Only Auth Endpoint): Add a new endpoint POST /api/auth/token-by-hash that accepts only { type, keyHash } and performs the lookup server-side — returning uuid and username alongside the api token. Cleanest UX. Requires a server-side index on key_hash column in users table for performance.
Recommendation: Option C is the most user-friendly (single paste, no extra input). Requires a server schema change (CREATE INDEX idx_users_key_hash ON users(key_hash)). Flag for implementation in Phase 3.
validateIdentityFile() throws; error message displayed to userclawchives_identity_{username}.json filehu- key (root credential), not an api- token. The api- token is issued fresh on every login. There is no "expired identity file" — only an expired session (which simply requires re-login using the same identity file).POST /api/auth/token throws a network error or times outVITE_API_URL resolves correctly for the current environmentgetApiBaseUrl() in src/config/apiConfig.ts — production builds use relative paths; dev uses http://localhost:4242users table (e.g., user is pointing at a different ClawChives©™ instance, or the data volume was reset)POST /api/auth/token returns 404 Not Foundtoken in the uploaded file does not hash to the stored keyHash on the server — either the file was manually edited, corrupted, or this is a different user's key filePOST /api/auth/token returns 401 Unauthorizedclawchives_identity_{username}.json file anywherehu- key cannot be reconstructed from the server (only its SHA-256 hash is stored).{ uuid, username, keyHash } — but keyHash is a one-way hash. The original hu- key cannot be derived from it. Even a server administrator cannot recover the account without the original key.crypto.subtle.digest() is undefined (non-HTTPS context in a strict browser, or a very old browser)hashToken() throws immediately; login fails before any network callhttp://localhost is always permitted by browsers."A lobster does not ask the ocean to remember its password. It returns to the same rock, presents its shell, and the sea knows it by its form alone. Your identity file is that shell — carry it as a lobster carries its molt: close, private, and irreplaceable. The ocean has no recovery desk. The claw that unlocks your burrow was grown by you alone."
This SKILL.md is part of the ClawChives©™ CrustAgent©™ skill library. Update this document whenever LoginForm.tsx, validateIdentityFile(), or the /api/auth/token endpoint contract is modified.
development
# Feature Development Assistant ## Mission Statement You are an expert full-stack developer who builds complete features from concept to implementation using Desktop Commander's file management capabilities. Your role is to analyze existing codebases, design feature architecture, implement all necessary code, and integrate seamlessly with existing systems. ## Important: Multi-Chat Workflow **Feature development requires multiple chat sessions to avoid context limits and manage implementation c
development
The canonical ClawChives©™ agent integration skill. Full API reference for autonomous agents (Lobsters©™) to authenticate, manage bookmarks, folders, and integrate with the ClawChive bookmarking system.
development
# Truthpack Updater Skill ## When to Use Activate this skill whenever: - A new route is added to `server/routes/` - A new environment variable is introduced - The project structure or a feature cluster is modified - Security protocols or auth rules are updated ## Instructions 1. **Audit Phase**: Perform a full-codebase scan (`grep` or `list_dir`) to identify changes since the last truthpack sync. 2. **Atomic Updates**: Update the corresponding JSON file in `.crustagent/vibecheck/truthpack/`:
development
# Truthpack Lookup Skill ## When to Use Activate this skill BEFORE generating any code that: - Creates or modifies API routes - References environment variables - Touches authentication/authorization - Modifies API request/response shapes ## Instructions 1. Read `.crustagent/vibecheck/truthpack/routes.json` for verified API routes 2. Read `.crustagent/vibecheck/truthpack/env.json` for verified environment variables 3. Read `.crustagent/vibecheck/truthpack/auth.json` for verified auth rules 4.