
Daemon
- 35 installs
- 17.2k repo stars
- Updated August 1, 2026
- danielmiessler/personal_ai_infrastructure
Manages a public 'daemon' profile aggregating what you are building and reading from PAI sources, with security filtering, and deploys it to Cloudflare Pages.
About
Aggregates PAI data into a daemon-data.json, applies deterministic security filtering to strip sensitive content, and deploys a VitePress static site to Cloudflare Pages. Developers use it to publish and update a living public digital-presence profile.
- Deterministic pattern-based security filter, not LLM judgment
- Two-repo public framework plus private content pattern
Daemon by the numbers
- 35 all-time installs (skills.sh)
- Ranked #1,180 of 2,715 Automation & Workflows skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/danielmiessler/personal_ai_infrastructure --skill daemonAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 35 |
|---|---|
| repo stars | ★ 17.2k |
| Last updated | August 1, 2026 |
| Repository | danielmiessler/personal_ai_infrastructure ↗ |
What it does
Manages a public 'daemon' profile aggregating what you are building and reading from PAI sources, with security filtering, and deploys it to Cloudflare Pages.
Files
Customization
Before executing, check for user customizations at: ~/.claude/PAI/USER/SKILLCUSTOMIZATIONS/Daemon/
If this directory exists, load and apply any SecurityOverrides.md or PREFERENCES.md found there. These override default security classification. If the directory does not exist, proceed with skill defaults.
Voice Notification
curl -s -X POST http://localhost:31337/notify \
-H "Content-Type: application/json" \
-d '{"message": "Running the WORKFLOWNAME workflow in the Daemon skill to ACTION"}' \
> /dev/null 2>&1 &Daemon Skill
Manages your public daemon profile — a living digital representation of what you're working on, thinking about, reading, and building. Automatically aggregates data from your PAI system with deterministic security filtering to ensure only publicly safe content is published.
Workflow Routing
| Workflow | Trigger | File |
|---|---|---|
| UpdateDaemon | "update daemon", "refresh daemon" | Workflows/UpdateDaemon.md |
| ReadDaemon | "read daemon", "check daemon", "daemon status" | Workflows/ReadDaemon.md |
| PreviewDaemon | "preview daemon", "daemon diff" | Workflows/PreviewDaemon.md |
| DeployDaemon | "deploy daemon", "push daemon", "ship daemon" | Workflows/DeployDaemon.md |
Architecture
Two-repo pattern: public framework + private content.
PAI SOURCES (private, read-only)
TELOS/ (missions, goals, books, movies, wisdom)
KNOWLEDGE/Ideas/ (title + thesis only)
PROJECTS.md (public projects only)
MEMORY/WORK/ (abstracted to topic themes)
PRINCIPAL_IDENTITY.md (public bio data)
│
├──[DaemonAggregator.ts]──→ Reads sources, merges with existing data
│
├──[SecurityFilter.ts]──→ Deterministic code-level allowlist filter
│ Strips names, paths, credentials, internal refs
│ NOT an LLM filter — enforced by pattern matching
│
└──→ daemon-data.json → ~/Projects/daemon-dm/ (PRIVATE repo)
│
└──[deploy.sh]──→ Copies JSON into framework → VitePress build → Cloudflare Pages
│
~/Projects/daemon/ (PUBLIC repo — forkable framework)
STRUCTURALLY EXCLUDED (never read):
CONTACTS.md, FINANCES/, HEALTH/, TRAUMAS.md,
KNOWLEDGE/People/, KNOWLEDGE/Companies/,
OUR_STORY.md, OPINIONS.md, BUSINESS/Skill Structure
skills/Daemon/
├── SKILL.md (this file)
├── Tools/
│ ├── DaemonAggregator.ts (reads PAI sources → daemon-data.json)
│ └── SecurityFilter.ts (deterministic content sanitizer)
├── Workflows/
│ ├── UpdateDaemon.md (aggregate → preview → approve → deploy)
│ ├── ReadDaemon.md (read current daemon-data.json)
│ ├── PreviewDaemon.md (show diff without deploying)
│ └── DeployDaemon.md (bash deploy.sh from daemon-dm)
└── Docs/
└── SecurityClassification.md (public/private data categories)Important Paths
| Purpose | Path |
|---|---|
| Private data repo | ~/Projects/daemon-dm/ |
| daemon-data.json | ~/Projects/daemon-dm/daemon-data.json |
| Deploy script | ~/Projects/daemon-dm/deploy.sh |
| Public framework repo | ~/Projects/daemon/ |
| Security classification | ${CLAUDE_SKILL_DIR}/Docs/SecurityClassification.md |
| Security overrides | ${PAI_USER_DIR}/SKILLCUSTOMIZATIONS/Daemon/SecurityOverrides.md |
Live Endpoints
| Endpoint | Purpose |
|---|---|
daemon.example.com | Public website (Cloudflare Pages, fully static) |
Security Philosophy
1. Private by default: All data is private until explicitly classified as public 2. Code-level enforcement: SecurityFilter.ts is deterministic pattern matching, NOT LLM judgment 3. Structural exclusion: Sensitive files (CONTACTS, FINANCES, HEALTH) are never opened by the aggregator 4. Defense in depth: Aggregator filter + SecurityFilter + pre-commit hook + manual approval 5. Fail closed: If uncertain, exclude the content
Data Sources
The DaemonAggregator reads from these PAI sources:
| Source | What's Extracted | Section |
|---|---|---|
| TELOS/MISSION.md | M1, M2 (public missions) | [MISSION] |
| TELOS/GOALS.md | Public project goals | [TELOS] |
| TELOS/BOOKS.md | Book titles | [FAVORITE_BOOKS] |
| TELOS/MOVIES.md | Movie titles | [FAVORITE_MOVIES] |
| TELOS/WISDOM.md | Top 5 quotes | [WISDOM] |
| KNOWLEDGE/Ideas/_index.md | 10 recent Ideas (title + thesis) | [RECENT_IDEAS] |
| PROJECTS.md | Public repos and sites | Projects integration |
| MEMORY/WORK/ | Topic themes (last 14 days) | [CURRENTLY_WORKING_ON] |
| PRINCIPAL_IDENTITY.md | Public bio, role, focus | [ABOUT] |
| Existing daemon.md | Preserved sections (predictions, routine, podcasts, preferences) | Various |
For Community Forks
This skill is designed to be generic:
1. Fork the public Daemon repo (danielmiessler/Daemon) 2. Create your own private data repo with daemon-data.json 3. Configure with your own blocked names/paths 4. The aggregator reads from standard PAI directory structure 5. Use deploy.sh to build and deploy to your own Cloudflare Pages
Examples
Example 1: Full update cycle
User: "update daemon"
→ Aggregates PAI data sources
→ Applies security filter (deterministic)
→ Shows preview diff to user
→ User approves
→ Writes daemon-data.json to daemon-dm → deploys static siteExample 2: Check what's current
User: "check daemon"
→ Reads daemon-data.json from daemon-dm
→ Shows section-by-section statusExample 3: Preview before committing
User: "preview daemon"
→ Runs aggregator in preview mode
→ Shows diff against current daemon-data.json
→ No writes, no deploysGotchas
- Two repos: Public framework (
~/Projects/daemon/) and private content (~/Projects/daemon-dm/). The framework is forkable. The content is yours. - deploy.sh copies data into the framework at build time, then cleans up. Personal data never gets committed to the public repo.
- SecurityFilter is code, not prompts. If you need to add new blocked patterns, edit SecurityFilter.ts, not the workflow markdown.
- Site is fully static. Data is embedded at build time. Changes require running
deploy.sh.
Execution Log
After completing any workflow, append a single JSONL entry:
echo '{"ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","skill":"Daemon","workflow":"WORKFLOW_USED","input":"8_WORD_SUMMARY","status":"ok|error","duration_s":SECONDS}' >> ~/.claude/PAI/MEMORY/SKILLS/execution.jsonlDaemon Security Classification
Defines what data is public vs private for daemon aggregation. The aggregator uses this as its allowlist — only explicitly public content passes through.
Core Principle
Private by default. Promote known-safe. Every field must be explicitly classified as public before the aggregator includes it. Unknown data is excluded.
Source Classification
ALWAYS PUBLIC (safe to publish verbatim)
| Source | Fields | Notes |
|---|---|---|
| TELOS/BOOKS.md | All titles | Book preferences are public |
| TELOS/MOVIES.md | All titles | Movie preferences are public |
| TELOS/WISDOM.md | All quotes | Philosophical quotes, no PII |
| TELOS/MISSION.md | M1, M2 | Philosophical missions |
| daemon data: predictions | All | Public predictions with confidence |
| daemon data: daily_routine | All | Generic routine, no locations |
| daemon data: podcasts | All | Public preferences |
PUBLIC WITH FILTERING (safe after security filter applied)
| Source | Public Fields | Filtered Out |
|---|---|---|
| TELOS/GOALS.md | Public project goals (G0, G1, G9-G14) | Revenue targets, follower counts, private repos |
| TELOS/MISSION.md | M1, M2 | M3 (references partner) |
| TELOS/CHALLENGES.md | C0-C2 (general self-improvement) | Any referencing private people |
| PRINCIPAL_IDENTITY.md | Role, focus, career, interests, worldview | Partner name, private contacts |
| PROJECTS.md | Public repos and sites only | Private repos, internal tools |
| KNOWLEDGE/Ideas/ | Title + thesis only | Evidence, implications, internal refs |
| MEMORY/WORK/ | Abstracted topic themes | ISA details, task slugs, client info |
| daemon data: preferences | Generic preferences | Internal tooling specifics |
| daemon data: about | Bio text | Private names, internal paths |
STRUCTURALLY EXCLUDED (aggregator never reads these)
| Source | Reason |
|---|---|
| PAI/USER/CONTACTS.md | Contains real names, emails, phones |
| PAI/USER/FINANCES/ | Financial data |
| PAI/USER/HEALTH/ | Health data |
| PAI/USER/TELOS/TRAUMAS.md | Deeply personal |
| PAI/USER/BUSINESS/ | Business confidential |
| MEMORY/KNOWLEDGE/People/ | OSINT dossiers, consent not given |
| MEMORY/KNOWLEDGE/Companies/ | May contain proprietary intel |
| PAI/USER/OUR_STORY.md | Private relationship context |
| PAI/USER/OPINIONS.md | Internal operational opinions |
| Any .env, .key, .pem file | Credentials |
PROJECTS PUBLIC/PRIVATE CLASSIFICATION
Public projects (include in daemon):
- Website (example.com)
- Fabric (open source, 30K+ stars)
- SecLists (open source, in Kali)
- PAI (public repo)
- (your public products — list here)
- Daemon (daemon.example.com)
- Substrate (public repo)
- Telos (public repo)
- TheAlgorithm (public repo)
- FoundryServices (public repo)
- Ladder (public repo)
Private projects (exclude from daemon):
- (your internal dashboards — list here)
- (your private infrastructure — list here)
- Feed (private infrastructure)
- the DA (private, is the PAI system itself)
- PAI Observatory (internal)
- iMessage Bot (private)
- (your private workers — list here)
- Backups (private)
- NewarkCrimeData (side project, not core)
Entity Blocklist
These strings must never appear in public output. The SecurityFilter enforces this deterministically.
Names (from CONTACTS.md + known references)
- (partner references — list in SecurityOverrides.md)
- Angela, Kaleigh, Sasa, Saša
- Jason Haddix, Chad Lynch, Greg Reindel
- Olivia Gallucci, Andrew Ringlein, Bryan Solari
- Chuck Keith, Dave Goldsmith, Maria Ringlein
- Brooks Garrett, Mark Cunningham
Aliases and Abbreviations
- "B" when used as a person reference (e.g., "B's minds", "me and B")
- "my partner", "my girlfriend" (when followed by identifying context)
Paths
- /Users/(your-user)/
- ~/.claude/
- ~/Cloud/
- ~/LocalProjects/
Credentials
- Any string matching: sk-, ghp_, CLOUDFLARE_API_TOKEN, ANTHROPIC_API_KEY
- Any string matching: _API_KEY, _TOKEN, *_SECRET
Internal Architecture
- PAI internal system names when used as implementation details
- Hook filenames, tool paths, internal pipeline names
- Pulse port numbers, internal API endpoints
Customization
Users customize this classification by placing overrides in:
Override format:
## Additional Blocked Names
- Name1
- Name2
## Additional Public Projects
- ProjectName
## Additional Excluded Paths
- /path/to/exclude#!/usr/bin/env bun
/**
* DaemonAggregator — Reads PAI system data sources and produces
* a security-filtered daemon.md update.
*
* This tool aggregates from TELOS, Knowledge, Projects, and Work sessions,
* applies the SecurityFilter, and outputs either a daemon.md file or
* a structured JSON diff for preview.
*
* Usage:
* bun DaemonAggregator.ts --output <daemon.md> Write updated daemon.md
* bun DaemonAggregator.ts --preview Show what would change
* bun DaemonAggregator.ts --json Output as JSON (for pipeline)
* bun DaemonAggregator.ts --diff <current-daemon.md> Show diff against current
*/
import { readFileSync, existsSync, writeFileSync, readdirSync, statSync } from "fs";
import { join, resolve } from "path";
import { filterContent, filterDaemonData, loadSecurityOverrides } from "./SecurityFilter.ts";
// ─── Path Resolution ───
const HOME = process.env.HOME || process.env.USERPROFILE || "";
const PAI_DIR = process.env.PAI_DIR || join(HOME, ".claude", "PAI");
const USER_DIR = join(PAI_DIR, "USER");
const MEMORY_DIR = join(PAI_DIR, "MEMORY");
const TELOS_DIR = join(USER_DIR, "TELOS");
const KNOWLEDGE_DIR = join(MEMORY_DIR, "KNOWLEDGE");
const WORK_DIR = join(MEMORY_DIR, "WORK");
const PROJECTS_FILE = join(USER_DIR, "PROJECTS", "PROJECTS.md");
const IDENTITY_FILE = join(USER_DIR, "PRINCIPAL_IDENTITY.md");
const CUSTOMIZATIONS_DIR = join(USER_DIR, "SKILLCUSTOMIZATIONS", "Daemon");
const USER_DAEMON_DIR = join(USER_DIR, "Daemon");
// ─── Structurally Excluded Paths (NEVER read these) ───
const EXCLUDED_PATHS = [
join(USER_DIR, "CONTACTS.md"),
join(USER_DIR, "FINANCES"),
join(USER_DIR, "HEALTH"),
join(USER_DIR, "BUSINESS"),
join(USER_DIR, "OUR_STORY.md"),
join(USER_DIR, "OPINIONS.md"),
join(TELOS_DIR, "TRAUMAS.md"),
join(KNOWLEDGE_DIR, "People"),
join(KNOWLEDGE_DIR, "Companies"),
// ─── Current→Ideal Monitoring Spine (2026-04-15) ───
// the user's explicit decision: IDEAL_STATE is fully private (Decision #3).
// CURRENT_STATE contains aggregated health/finance/location/social — hardest private.
// Preference files with location or consumption data are private.
join(TELOS_DIR, "IDEAL_STATE"),
join(TELOS_DIR, "CURRENT_STATE"),
join(TELOS_DIR, "GAP"),
join(TELOS_DIR, "RESTAURANTS.md"),
join(TELOS_DIR, "FOOD_PREFERENCES.md"),
join(TELOS_DIR, "LEARNING.md"),
join(TELOS_DIR, "MEETUPS.md"),
join(TELOS_DIR, "CIVIC.md"),
];
function isExcluded(filePath: string): boolean {
const resolved = resolve(filePath);
return EXCLUDED_PATHS.some((excluded) => resolved.startsWith(resolve(excluded)));
}
// ─── Public Projects List ───
const PUBLIC_PROJECTS = [
"Website", "Fabric", "SecLists", "PAI", "Surface",
"Human 3.0", "UL Site", "Daemon", "Substrate", "Telos",
"TheAlgorithm", "FoundryServices", "Ladder", "PAI Marketing",
];
// ─── Source Readers ───
function readFileIfExists(path: string): string | null {
if (isExcluded(path)) return null;
if (!existsSync(path)) return null;
return readFileSync(path, "utf-8");
}
function readMissions(): string {
const content = readFileIfExists(join(TELOS_DIR, "MISSION.md"));
if (!content) return "";
const lines = content.split("\n");
const publicMissions: string[] = [];
for (const line of lines) {
const trimmed = line.trim();
// Include M0 and M1 — they're public-safe philosophical missions
if (trimmed.match(/^[-*]\s+\*?\*?M[01]\b/)) {
publicMissions.push(trimmed.replace(/^[-*]\s+/, ""));
}
}
// M2 reworded: mind upload aspiration without partner reference
publicMissions.push(
"M2: Explore the transfer and storage of human minds into digital formats for future continuity."
);
return publicMissions.join("\n");
}
function readGoals(): string {
const content = readFileIfExists(join(TELOS_DIR, "GOALS.md"));
if (!content) return "";
const lines = content.split("\n");
const publicGoals: string[] = [];
for (const line of lines) {
const trimmed = line.trim();
// Include goals for public projects, exclude revenue/follower targets
if (trimmed.match(/^[-*]\s+\*?\*?G\d+\b/)) {
// Filter out goals with revenue, follower count, or monetization targets
if (
!trimmed.match(/\b(revenue|follower|subscriber|monetiz)/i) &&
!trimmed.match(/\b\d+[Kk]\s+(follower|subscriber)/i)
) {
publicGoals.push(trimmed.replace(/^[-*]\s+/, ""));
}
}
}
return publicGoals.join("\n");
}
function readBooks(): string[] {
const content = readFileIfExists(join(TELOS_DIR, "BOOKS.md"));
if (!content) return [];
return content
.split("\n")
.filter((l) => l.match(/^[-*]\s+/))
.map((l) => l.replace(/^[-*]\s+/, "").trim())
.filter((l) => l.length > 0);
}
function readMovies(): string[] {
const content = readFileIfExists(join(TELOS_DIR, "MOVIES.md"));
if (!content) return [];
return content
.split("\n")
.filter((l) => l.match(/^[-*]\s+/))
.map((l) => l.replace(/^[-*]\s+/, "").trim())
.filter((l) => l.length > 0);
}
function readWisdom(): string[] {
const content = readFileIfExists(join(TELOS_DIR, "WISDOM.md"));
if (!content) return [];
// Split by double newlines to get individual quotes
return content
.split(/\n{2,}/)
.map((q) => q.trim())
.filter((q) => q.length > 10 && !q.startsWith("#"));
}
function readRecentIdeas(limit = 10): Array<{ title: string; thesis: string }> {
const indexPath = join(KNOWLEDGE_DIR, "Ideas", "_index.md");
const content = readFileIfExists(indexPath);
if (!content) return [];
// Extract recently updated ideas from the index
const recentSection = content.match(/## Recently Updated\n([\s\S]*?)(?=\n## |$)/);
if (!recentSection) return [];
const ideaSlugs = recentSection[1]
.split("\n")
.filter((l) => l.match(/^\s*-\s+\[\[/))
.slice(0, limit)
.map((l) => {
const slugMatch = l.match(/\[\[([^\]]+)\]\]/);
const titleMatch = l.match(/"([^"]+)"/);
return {
slug: slugMatch?.[1] || "",
title: titleMatch?.[1] || "",
};
})
.filter((i) => i.slug && i.title);
const ideas: Array<{ title: string; thesis: string }> = [];
for (const { slug, title } of ideaSlugs) {
const ideaPath = join(KNOWLEDGE_DIR, "Ideas", `${slug}.md`);
const ideaContent = readFileIfExists(ideaPath);
if (!ideaContent) {
ideas.push({ title, thesis: "" });
continue;
}
// Extract thesis section (first paragraph after ## Thesis)
const thesisMatch = ideaContent.match(/## Thesis\s*\n([\s\S]*?)(?=\n## |$)/);
const thesis = thesisMatch
? thesisMatch[1].trim().split("\n")[0].trim() // First line only
: "";
// Skip ideas that reference internal PAI architecture
if (
thesis.match(/PAI\/|hooks\/|MEMORY\/|Algorithm\/|\.hook\.ts/i) ||
title.match(/^(PAI|Hook|Pulse|Algorithm)\b/i)
) {
continue;
}
ideas.push({ title, thesis });
}
return ideas;
}
function readPublicProjects(): { technical: string[]; creative: string[]; personal: string[] } {
const content = readFileIfExists(PROJECTS_FILE);
if (!content) return { technical: [], creative: [], personal: [] };
const technical: string[] = [];
const creative: string[] = [];
// Parse the projects table
const lines = content.split("\n");
for (const line of lines) {
if (!line.startsWith("|")) continue;
if (line.includes("---")) continue;
if (line.includes("Project")) continue;
// Extract project name
const cells = line.split("|").map((c) => c.trim()).filter(Boolean);
if (cells.length < 2) continue;
const name = cells[0].replace(/\*\*/g, "").trim();
if (PUBLIC_PROJECTS.includes(name)) {
const url = cells[2] || "";
if (url.includes("github.com")) {
technical.push(`${name} — ${url}`);
} else if (url) {
creative.push(`${name} — ${url}`);
} else {
technical.push(name);
}
}
}
return { technical, creative, personal: [] };
}
function readWorkThemes(daysBack = 14, limit = 8): string[] {
if (!existsSync(WORK_DIR)) return [];
const cutoff = new Date();
cutoff.setDate(cutoff.getDate() - daysBack);
const themes = new Map<string, number>();
try {
const dirs = readdirSync(WORK_DIR)
.filter((d) => d.match(/^\d{8}-/))
.sort()
.reverse()
.slice(0, 50); // Check last 50 sessions max
for (const dir of dirs) {
// Extract date from dir name (YYYYMMDD-HHMMSS_description)
const dateStr = dir.slice(0, 8);
const year = parseInt(dateStr.slice(0, 4));
const month = parseInt(dateStr.slice(4, 6)) - 1;
const day = parseInt(dateStr.slice(6, 8));
const dirDate = new Date(year, month, day);
if (dirDate < cutoff) continue;
// Extract theme from directory name (after the timestamp_)
const descPart = dir.replace(/^\d{8}-\d{6}_/, "");
if (!descPart) continue;
// Generalize the theme (remove specific details)
const theme = generalizeTheme(descPart);
if (theme) {
themes.set(theme, (themes.get(theme) || 0) + 1);
}
}
} catch {
return [];
}
// Sort by frequency, return top N
return Array.from(themes.entries())
.sort((a, b) => b[1] - a[1])
.slice(0, limit)
.map(([theme]) => theme);
}
function generalizeTheme(slug: string): string | null {
// Convert kebab-case slug to human-readable theme
const words = slug.replace(/-/g, " ").toLowerCase();
// Map specific patterns to general themes
const themeMap: Array<[RegExp, string]> = [
[/blog|post|writing|draft/, "Writing and content creation"],
[/security|vuln|pentest|recon/, "Security research and assessment"],
[/ai|llm|model|prompt/, "AI systems and development"],
[/deploy|build|ship|release/, "Building and shipping software"],
[/design|ui|ux|frontend/, "Design and user experience"],
[/research|investigate|analysis/, "Research and analysis"],
[/feed|surface|news/, "Content curation and intelligence"],
[/pai|algorithm|skill|hook/, null], // Internal — exclude
[/fix|bug|debug|error/, "Debugging and problem-solving"],
[/newsletter|email|broadcast/, "Newsletter and communications"],
[/telos|goal|mission/, "Purpose and goal development"],
];
for (const [pattern, theme] of themeMap) {
if (words.match(pattern)) return theme;
}
// If no pattern matches, create a generic theme from the first 3 meaningful words
const meaningful = words
.split(" ")
.filter((w) => w.length > 3 && !["this", "that", "with", "from", "into"].includes(w))
.slice(0, 3);
if (meaningful.length >= 2) {
return meaningful.join(" ").replace(/^\w/, (c) => c.toUpperCase());
}
return null;
}
function readAbout(): string {
const content = readFileIfExists(IDENTITY_FILE);
if (!content) return "";
// Extract key public information
const lines = content.split("\n");
const parts: string[] = [];
for (const line of lines) {
if (line.includes("Name:")) {
continue; // Skip, we'll compose our own
}
if (line.includes("Focus:")) {
const focus = line.replace(/.*Focus:\*?\*?\s*/, "").trim();
parts.push(focus);
}
if (line.includes("Online Since:")) {
const since = line.replace(/.*Online Since:\*?\*?\s*/, "").trim();
parts.push(`Online since ${since}`);
}
}
return parts.join(". ");
}
function readPreferences(): string[] {
// Read from existing daemon data if available
const existingDaemon = readExistingDaemon();
if (existingDaemon.preferences) {
return typeof existingDaemon.preferences === "string"
? existingDaemon.preferences.split("\n").filter(Boolean)
: (existingDaemon.preferences as string[]);
}
return [];
}
function readExistingDaemon(): Record<string, unknown> {
const daemonPath = join(USER_DAEMON_DIR, "daemon.md");
if (!existsSync(daemonPath)) {
// Fall back to old location
const oldPath = join(HOME, ".claude", "skills", "_DAEMON", "Mcp", "daemon.md");
if (!existsSync(oldPath)) return {};
return parseDaemonMd(readFileSync(oldPath, "utf-8"));
}
return parseDaemonMd(readFileSync(daemonPath, "utf-8"));
}
function parseDaemonMd(content: string): Record<string, unknown> {
const sections: Record<string, string> = {};
let currentSection: string | null = null;
let sectionContent: string[] = [];
for (const line of content.split("\n")) {
const sectionMatch = line.match(/^\[([A-Z_]+)\]$/);
if (sectionMatch) {
if (currentSection) {
sections[currentSection] = sectionContent.join("\n").trim();
}
currentSection = sectionMatch[1].toLowerCase();
sectionContent = [];
} else if (currentSection && line.trim() && !line.startsWith("#")) {
sectionContent.push(line);
}
}
if (currentSection) {
sections[currentSection] = sectionContent.join("\n").trim();
}
return sections;
}
// ─── Aggregation ───
interface DaemonUpdate {
about: string;
mission: string;
current_location: string;
telos: string;
favorite_books: string[];
favorite_movies: string[];
predictions: string[];
preferences: string[];
daily_routine: string[];
favorite_podcasts: string[];
recent_ideas: Array<{ title: string; thesis: string }>;
projects: { technical: string[]; creative: string[]; personal: string[] };
work_themes: string[];
wisdom: string[];
last_updated: string;
}
export function aggregate(): DaemonUpdate {
const existing = readExistingDaemon();
// About: always prefer existing hand-written bio over auto-generated
const about = (existing.about as string) || readAbout() || "";
const mission = readMissions() || (existing.mission as string) || "";
const books = readBooks();
const movies = readMovies();
const wisdom = readWisdom();
const recentIdeas = readRecentIdeas(10);
const projects = readPublicProjects();
const workThemes = readWorkThemes(14, 8);
const goals = readGoals();
// Combine missions and goals into TELOS section
const telosParts: string[] = [];
if (mission) telosParts.push(mission);
if (goals) telosParts.push(goals);
// Preserve existing sections that we don't have PAI sources for
const predictions = existing.predictions
? (typeof existing.predictions === "string"
? existing.predictions.split("\n").filter(Boolean).map((l: string) => l.replace(/^[-*]\s+/, ""))
: (existing.predictions as string[]))
: [];
const preferences = readPreferences();
const dailyRoutine = existing.daily_routine
? (typeof existing.daily_routine === "string"
? existing.daily_routine.split("\n").filter(Boolean).map((l: string) => l.replace(/^[-*]\s+/, ""))
: (existing.daily_routine as string[]))
: [];
const podcasts = existing.favorite_podcasts
? (typeof existing.favorite_podcasts === "string"
? existing.favorite_podcasts.split("\n").filter(Boolean).map((l: string) => l.replace(/^[-*]\s+/, ""))
: (existing.favorite_podcasts as string[]))
: [];
// Merge: PAI source books + existing daemon books (deduplicated)
const existingBooks = existing.favorite_books
? (typeof existing.favorite_books === "string"
? existing.favorite_books.split("\n").filter(Boolean).map((l: string) => l.replace(/^[-*]\s+/, "").replace(/^"(.+)".*$/, "$1"))
: (existing.favorite_books as string[]))
: [];
const mergedBooks = [...new Set([...books, ...existingBooks])];
// Merge movies similarly
const existingMovies = existing.favorite_movies
? (typeof existing.favorite_movies === "string"
? existing.favorite_movies.split("\n").filter(Boolean).map((l: string) => l.replace(/^[-*]\s+/, ""))
: (existing.favorite_movies as string[]))
: [];
const mergedMovies = [...new Set([...movies, ...existingMovies])];
return {
about,
mission: telosParts.join("\n\n"),
current_location: (existing.current_location as string) || "San Francisco Bay Area",
telos: telosParts.join("\n\n"),
favorite_books: mergedBooks,
favorite_movies: mergedMovies,
predictions,
preferences,
daily_routine: dailyRoutine,
favorite_podcasts: podcasts,
recent_ideas: recentIdeas,
projects,
work_themes: workThemes,
wisdom: wisdom.slice(0, 5), // Top 5 quotes
last_updated: new Date().toISOString(),
};
}
// ─── Output Formatters ───
function toDaemonMd(data: DaemonUpdate): string {
const sections: string[] = [
"# DAEMON DATA FILE",
"",
"# This file contains personal information for the daemon profile",
"# Format: Section headers are marked with [SECTION_NAME]",
"# Auto-generated by DaemonAggregator from PAI sources",
"",
];
sections.push("[ABOUT]", "", data.about, "");
sections.push("[CURRENT_LOCATION]", "", data.current_location, "");
sections.push("[MISSION]", "", data.mission, "");
if (data.telos) {
sections.push("[TELOS]", "", data.telos, "");
}
sections.push("[FAVORITE_BOOKS]", "");
for (const book of data.favorite_books) {
sections.push(`- ${book}`);
}
sections.push("");
sections.push("[FAVORITE_MOVIES]", "");
for (const movie of data.favorite_movies) {
sections.push(`- ${movie}`);
}
sections.push("");
if (data.daily_routine.length > 0) {
sections.push("[DAILY_ROUTINE]", "");
for (const item of data.daily_routine) {
sections.push(`- ${item}`);
}
sections.push("");
}
if (data.preferences.length > 0) {
sections.push("[PREFERENCES]", "");
for (const pref of data.preferences) {
sections.push(`- ${pref}`);
}
sections.push("");
}
if (data.favorite_podcasts.length > 0) {
sections.push("[FAVORITE_PODCASTS]", "");
for (const pod of data.favorite_podcasts) {
sections.push(`- ${pod}`);
}
sections.push("");
}
if (data.predictions.length > 0) {
sections.push("[PREDICTIONS]", "");
for (const pred of data.predictions) {
sections.push(`- ${pred}`);
}
sections.push("");
}
if (data.recent_ideas.length > 0) {
sections.push("[RECENT_IDEAS]", "");
for (const idea of data.recent_ideas) {
const line = idea.thesis ? `- ${idea.title}: ${idea.thesis}` : `- ${idea.title}`;
sections.push(line);
}
sections.push("");
}
if (data.work_themes.length > 0) {
sections.push("[CURRENTLY_WORKING_ON]", "");
for (const theme of data.work_themes) {
sections.push(`- ${theme}`);
}
sections.push("");
}
if (data.wisdom.length > 0) {
sections.push("[WISDOM]", "");
for (const quote of data.wisdom) {
sections.push(`- ${quote}`);
}
sections.push("");
}
sections.push("# Note: PROJECTS are pulled dynamically");
sections.push("");
return sections.join("\n");
}
// ─── CLI ───
if (import.meta.main) {
const args = process.argv.slice(2);
if (args.includes("--help") || args.includes("-h")) {
console.log(`
DaemonAggregator — Aggregate PAI data into daemon.md
Usage:
bun DaemonAggregator.ts --output <path> Write daemon.md to path
bun DaemonAggregator.ts --preview Show aggregated content (no write)
bun DaemonAggregator.ts --json Output as JSON
bun DaemonAggregator.ts --diff <current> Show diff against current daemon.md
bun DaemonAggregator.ts --sources List data sources and their status
Options:
--filter Apply SecurityFilter to output (default: on)
--no-filter Skip SecurityFilter (for debugging only)
--verbose Show aggregation details
`);
process.exit(0);
}
// Load security overrides if available
const overridesPath = join(CUSTOMIZATIONS_DIR, "SecurityOverrides.md");
const overrides = loadSecurityOverrides(overridesPath);
if (args.includes("--sources")) {
console.log("Data Source Status:\n");
const sources = [
{ name: "TELOS/MISSION.md", path: join(TELOS_DIR, "MISSION.md") },
{ name: "TELOS/GOALS.md", path: join(TELOS_DIR, "GOALS.md") },
{ name: "TELOS/BOOKS.md", path: join(TELOS_DIR, "BOOKS.md") },
{ name: "TELOS/MOVIES.md", path: join(TELOS_DIR, "MOVIES.md") },
{ name: "TELOS/WISDOM.md", path: join(TELOS_DIR, "WISDOM.md") },
{ name: "KNOWLEDGE/Ideas/_index.md", path: join(KNOWLEDGE_DIR, "Ideas", "_index.md") },
{ name: "PROJECTS.md", path: PROJECTS_FILE },
{ name: "PRINCIPAL_IDENTITY.md", path: IDENTITY_FILE },
{ name: "WORK/ (sessions)", path: WORK_DIR },
{ name: "User daemon.md", path: join(USER_DAEMON_DIR, "daemon.md") },
];
for (const s of sources) {
const exists = existsSync(s.path);
const excluded = isExcluded(s.path);
const status = excluded ? "EXCLUDED" : exists ? "OK" : "MISSING";
const icon = excluded ? "X" : exists ? "+" : "-";
console.log(` [${icon}] ${s.name}: ${status}`);
}
process.exit(0);
}
console.log("Aggregating PAI data sources...\n");
const data = aggregate();
// Apply security filter unless --no-filter
const skipFilter = args.includes("--no-filter");
let daemonMd = toDaemonMd(data);
if (!skipFilter) {
const result = filterContent(daemonMd, overrides);
daemonMd = result.clean;
if (result.redactions.length > 0) {
console.log(`Security filter applied: ${result.redactions.length} redactions`);
if (args.includes("--verbose")) {
for (const r of result.redactions) {
console.log(` [${r.type}] "${r.original}"`);
}
}
} else {
console.log("Security filter applied: clean (no redactions needed)");
}
}
if (args.includes("--json")) {
console.log(JSON.stringify(data, null, 2));
process.exit(0);
}
if (args.includes("--preview")) {
console.log("\n--- PREVIEW ---\n");
console.log(daemonMd);
console.log("\n--- END PREVIEW ---");
// Summary
console.log("\nSections populated:");
console.log(` Books: ${data.favorite_books.length}`);
console.log(` Movies: ${data.favorite_movies.length}`);
console.log(` Ideas: ${data.recent_ideas.length}`);
console.log(` Work themes: ${data.work_themes.length}`);
console.log(` Wisdom: ${data.wisdom.length}`);
console.log(` Predictions: ${data.predictions.length}`);
process.exit(0);
}
const diffIdx = args.indexOf("--diff");
if (diffIdx !== -1 && args[diffIdx + 1]) {
const currentPath = args[diffIdx + 1];
if (existsSync(currentPath)) {
const current = readFileSync(currentPath, "utf-8");
// Simple line-by-line diff summary
const currentLines = new Set(current.split("\n").map((l) => l.trim()).filter(Boolean));
const newLines = new Set(daemonMd.split("\n").map((l) => l.trim()).filter(Boolean));
const added = [...newLines].filter((l) => !currentLines.has(l));
const removed = [...currentLines].filter((l) => !newLines.has(l));
console.log(`\nDiff Summary:`);
console.log(` Added: ${added.length} lines`);
console.log(` Removed: ${removed.length} lines`);
if (added.length > 0) {
console.log("\n+ Added:");
for (const line of added.slice(0, 20)) {
console.log(` + ${line}`);
}
if (added.length > 20) console.log(` ... and ${added.length - 20} more`);
}
if (removed.length > 0) {
console.log("\n- Removed:");
for (const line of removed.slice(0, 20)) {
console.log(` - ${line}`);
}
if (removed.length > 20) console.log(` ... and ${removed.length - 20} more`);
}
} else {
console.log(`Current file not found: ${currentPath}`);
}
process.exit(0);
}
const outputIdx = args.indexOf("--output");
if (outputIdx !== -1 && args[outputIdx + 1]) {
const outputPath = args[outputIdx + 1];
writeFileSync(outputPath, daemonMd);
console.log(`\nWrote daemon.md to: ${outputPath}`);
console.log(`Size: ${daemonMd.length} bytes`);
process.exit(0);
}
// Default: preview mode
console.log(daemonMd);
}
#!/usr/bin/env bun
/**
* SecurityFilter — Deterministic allowlist-based content sanitizer for Daemon.
*
* This is a CODE-LEVEL filter, not an LLM filter. Every field passes through
* deterministic pattern matching. The LLM can assist in drafting content,
* but this filter is the enforcement boundary.
*
* Usage:
* bun SecurityFilter.ts --input <json-file> [--contacts <contacts-file>] [--overrides <overrides-file>]
* echo '{"text": "..."}' | bun SecurityFilter.ts --stdin
*/
import { readFileSync, existsSync } from "fs";
// ─── Blocked Patterns (baseline — intentionally empty) ───
// Private blocked names are loaded at runtime from
// ~/.claude/PAI/USER/SKILLCUSTOMIZATIONS/Daemon/SecurityOverrides.md
// so no principal-specific identities ship in the public skill.
const BLOCKED_NAMES_BASELINE: string[] = [];
const BLOCKED_PATH_PATTERNS = [
/\/Users\/\w+\//g,
/~\/\.claude\//g,
/~\/Cloud\//g,
/~\/LocalProjects\//g,
/PAI\/USER\//g,
/PAI\/MEMORY\//g,
/MEMORY\/WORK\//g,
/MEMORY\/KNOWLEDGE\//g,
];
const BLOCKED_CREDENTIAL_PATTERNS = [
/sk-[a-zA-Z0-9_-]{20,}/g,
/ghp_[a-zA-Z0-9]{36,}/g,
/\b[A-Z_]+_API_KEY\s*[=:]\s*\S+/g,
/\b[A-Z_]+_TOKEN\s*[=:]\s*\S+/g,
/\b[A-Z_]+_SECRET\s*[=:]\s*\S+/g,
/CLOUDFLARE_API_TOKEN/g,
/ANTHROPIC_API_KEY/g,
];
const BLOCKED_INTERNAL_PATTERNS = [
/localhost:\d{4,5}/g,
/\.hook\.ts/g,
/hooks\/\w+/g,
/PAI\/Algorithm\/v[\d.]+\.md/g,
/Tools\/\w+\.ts/g,
/Pulse\/\w+/g,
];
// Partner alias patterns (contextual — "B" alone is too common)
const PARTNER_ALIAS_PATTERNS = [
/\bB's\s+(mind|brain|thought|life|dream)/gi,
/\bme\s+and\s+B\b/gi,
/\bmy\s+and\s+B's\b/gi,
/\bB\s+and\s+(I|me)\b/gi,
];
// ─── Types ───
interface FilterResult {
clean: string;
redactions: Redaction[];
passed: boolean;
}
interface Redaction {
type: "name" | "path" | "credential" | "internal" | "alias";
original: string;
position: number;
}
interface FilterOptions {
extraBlockedNames?: string[];
extraBlockedPaths?: string[];
}
// ─── Core Filter ───
export function filterContent(
text: string,
options: FilterOptions = {}
): FilterResult {
const redactions: Redaction[] = [];
let clean = text;
// 1. Remove blocked names (case-insensitive word boundary match)
const allNames = [
...BLOCKED_NAMES_BASELINE,
...(options.extraBlockedNames || []),
];
for (const name of allNames) {
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const regex = new RegExp(`\\b${escaped}\\b`, "gi");
let match: RegExpExecArray | null;
while ((match = regex.exec(clean)) !== null) {
redactions.push({
type: "name",
original: match[0],
position: match.index,
});
}
clean = clean.replace(regex, "[REDACTED]");
}
// 2. Remove partner aliases
for (const pattern of PARTNER_ALIAS_PATTERNS) {
let match: RegExpExecArray | null;
const testClean = clean;
while ((match = pattern.exec(testClean)) !== null) {
redactions.push({
type: "alias",
original: match[0],
position: match.index,
});
}
clean = clean.replace(pattern, "[REDACTED]");
}
// 3. Remove private paths
const allPathPatterns = [...BLOCKED_PATH_PATTERNS];
if (options.extraBlockedPaths) {
for (const p of options.extraBlockedPaths) {
const escaped = p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
allPathPatterns.push(new RegExp(escaped, "g"));
}
}
for (const pattern of allPathPatterns) {
let match: RegExpExecArray | null;
const freshPattern = new RegExp(pattern.source, pattern.flags);
while ((match = freshPattern.exec(clean)) !== null) {
redactions.push({
type: "path",
original: match[0],
position: match.index,
});
}
clean = clean.replace(new RegExp(pattern.source, pattern.flags), "[PATH_REDACTED]");
}
// 4. Remove credentials
for (const pattern of BLOCKED_CREDENTIAL_PATTERNS) {
let match: RegExpExecArray | null;
const freshPattern = new RegExp(pattern.source, pattern.flags);
while ((match = freshPattern.exec(clean)) !== null) {
redactions.push({
type: "credential",
original: match[0].slice(0, 10) + "...",
position: match.index,
});
}
clean = clean.replace(new RegExp(pattern.source, pattern.flags), "[CREDENTIAL_REDACTED]");
}
// 5. Remove internal architecture references
for (const pattern of BLOCKED_INTERNAL_PATTERNS) {
let match: RegExpExecArray | null;
const freshPattern = new RegExp(pattern.source, pattern.flags);
while ((match = freshPattern.exec(clean)) !== null) {
redactions.push({
type: "internal",
original: match[0],
position: match.index,
});
}
clean = clean.replace(new RegExp(pattern.source, pattern.flags), "[INTERNAL_REDACTED]");
}
// Clean up multiple consecutive [REDACTED] markers
clean = clean.replace(/(\[(?:REDACTED|PATH_REDACTED|CREDENTIAL_REDACTED|INTERNAL_REDACTED)\]\s*){2,}/g, "[REDACTED] ");
return {
clean: clean.trim(),
redactions,
passed: redactions.length === 0,
};
}
/**
* Filter a structured daemon data object. Applies filterContent to every string field.
*/
export function filterDaemonData(
data: Record<string, unknown>,
options: FilterOptions = {}
): { data: Record<string, unknown>; totalRedactions: number; redactionsBySection: Record<string, number> } {
let totalRedactions = 0;
const redactionsBySection: Record<string, number> = {};
function filterValue(value: unknown, section: string): unknown {
if (typeof value === "string") {
const result = filterContent(value, options);
if (result.redactions.length > 0) {
totalRedactions += result.redactions.length;
redactionsBySection[section] = (redactionsBySection[section] || 0) + result.redactions.length;
}
return result.clean;
}
if (Array.isArray(value)) {
return value.map((item) => filterValue(item, section));
}
if (value && typeof value === "object") {
const filtered: Record<string, unknown> = {};
for (const [k, v] of Object.entries(value)) {
filtered[k] = filterValue(v, section);
}
return filtered;
}
return value;
}
const filtered: Record<string, unknown> = {};
for (const [key, value] of Object.entries(data)) {
filtered[key] = filterValue(value, key);
}
return { data: filtered, totalRedactions, redactionsBySection };
}
/**
* Load extra blocked names from a contacts file (one name per line, or markdown list).
*/
export function loadContactNames(contactsPath: string): string[] {
if (!existsSync(contactsPath)) return [];
const content = readFileSync(contactsPath, "utf-8");
const names: string[] = [];
for (const line of content.split("\n")) {
const match = line.match(/^[-*]\s+(.+)/);
if (match) {
const name = match[1].trim();
if (name && name.length > 1 && !name.startsWith("#")) {
names.push(name);
}
}
}
return names;
}
/**
* Load security overrides from SKILLCUSTOMIZATIONS.
*/
export function loadSecurityOverrides(overridesPath: string): FilterOptions {
if (!existsSync(overridesPath)) return {};
const content = readFileSync(overridesPath, "utf-8");
const extraNames: string[] = [];
const extraPaths: string[] = [];
let currentSection = "";
for (const line of content.split("\n")) {
if (line.startsWith("## Additional Blocked Names")) {
currentSection = "names";
} else if (line.startsWith("## Additional Excluded Paths")) {
currentSection = "paths";
} else if (line.startsWith("##")) {
currentSection = "";
} else if (currentSection === "names") {
const match = line.match(/^[-*]\s+(.+)/);
if (match) extraNames.push(match[1].trim());
} else if (currentSection === "paths") {
const match = line.match(/^[-*]\s+(.+)/);
if (match) extraPaths.push(match[1].trim());
}
}
return {
extraBlockedNames: extraNames.length > 0 ? extraNames : undefined,
extraBlockedPaths: extraPaths.length > 0 ? extraPaths : undefined,
};
}
// ─── CLI ───
if (import.meta.main) {
const args = process.argv.slice(2);
if (args.includes("--help") || args.includes("-h")) {
console.log(`
SecurityFilter — Deterministic content sanitizer for Daemon
Usage:
bun SecurityFilter.ts --input <json-file> Filter a JSON data file
bun SecurityFilter.ts --text "some text" Filter a text string
bun SecurityFilter.ts --test Run self-test with known patterns
Options:
--contacts <file> Load additional blocked names from file
--overrides <file> Load security overrides (extra names/paths)
--verbose Show each redaction detail
`);
process.exit(0);
}
if (args.includes("--test")) {
console.log("Running SecurityFilter self-test...\n");
const testCases = [
{ input: "my and B's minds into digital format", expectRedactions: true, desc: "Partner alias" },
{ input: "File at /Users/example/.claude/PAI/hooks/test.ts", expectRedactions: true, desc: "Private path" },
{ input: "Token: sk-abc123def456ghi789jkl012mno345", expectRedactions: true, desc: "API key" },
{ input: "Building open source tools for everyone", expectRedactions: false, desc: "Clean text" },
{ input: "localhost:31337 pulse server", expectRedactions: true, desc: "Internal endpoint" },
];
let passed = 0;
for (const tc of testCases) {
const result = filterContent(tc.input);
const ok = tc.expectRedactions ? result.redactions.length > 0 : result.redactions.length === 0;
console.log(`${ok ? "PASS" : "FAIL"}: ${tc.desc}`);
if (!ok) {
console.log(` Input: "${tc.input}"`);
console.log(` Expected redactions: ${tc.expectRedactions}, Got: ${result.redactions.length}`);
} else {
passed++;
}
}
console.log(`\n${passed}/${testCases.length} tests passed`);
process.exit(passed === testCases.length ? 0 : 1);
}
const textIdx = args.indexOf("--text");
if (textIdx !== -1 && args[textIdx + 1]) {
const result = filterContent(args[textIdx + 1]);
console.log("Clean:", result.clean);
if (result.redactions.length > 0) {
console.log(`Redactions: ${result.redactions.length}`);
for (const r of result.redactions) {
console.log(` [${r.type}] "${r.original}" at position ${r.position}`);
}
}
process.exit(0);
}
const inputIdx = args.indexOf("--input");
if (inputIdx !== -1 && args[inputIdx + 1]) {
const inputFile = args[inputIdx + 1];
const data = JSON.parse(readFileSync(inputFile, "utf-8"));
let options: FilterOptions = {};
const contactsIdx = args.indexOf("--contacts");
if (contactsIdx !== -1 && args[contactsIdx + 1]) {
options.extraBlockedNames = loadContactNames(args[contactsIdx + 1]);
}
const overridesIdx = args.indexOf("--overrides");
if (overridesIdx !== -1 && args[overridesIdx + 1]) {
options = { ...options, ...loadSecurityOverrides(args[overridesIdx + 1]) };
}
const result = filterDaemonData(data, options);
console.log(JSON.stringify(result.data, null, 2));
if (args.includes("--verbose")) {
console.error(`\nTotal redactions: ${result.totalRedactions}`);
for (const [section, count] of Object.entries(result.redactionsBySection)) {
console.error(` ${section}: ${count} redactions`);
}
}
process.exit(0);
}
console.error("No input specified. Use --help for usage.");
process.exit(1);
}
DeployDaemon Workflow
Purpose: Deploy the daemon website and sync data to MCP KV store. Does NOT aggregate or modify content — use UpdateDaemon for that.
Trigger Phrases
- "deploy daemon"
- "push daemon"
- "ship daemon"
Process
Step 1: Push Website to GitHub
cd ~/Projects/daemon && git add -A && git commit -m "Deploy daemon $(date +%Y-%m-%d)" && git pushPre-commit hook runs automatically and blocks sensitive data. Cloudflare Pages auto-deploys on push.
Step 2: Sync Data to MCP KV Store
cd ${CLAUDE_SKILL_DIR}/Mcp && bun install && bun update-daemonThis runs the existing pipeline: sync integrations, aggregate daemon.md + integrations, validate with Zod, upload to Cloudflare KV.
Step 3: Verify Deployment
curl -s -o /dev/null -w "%{http_code}" https://daemon.example.comcurl -s https://mcp.daemon.example.com \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"get_about","arguments":{}},"id":1}' | head -c 200Notes
- If only website UI changed (no content): Step 1 is sufficient
- If daemon.md content changed: Both steps needed
- Run UpdateDaemon workflow FIRST if you want to aggregate fresh PAI data
PreviewDaemon Workflow
Purpose: Show what an UpdateDaemon would change without writing or deploying anything.
Trigger Phrases
- "preview daemon"
- "preview daemon update"
- "what would daemon update look like"
- "daemon diff"
Process
Step 1: Run Aggregator in Preview Mode
bun ${CLAUDE_SKILL_DIR}/Tools/DaemonAggregator.ts --diff ${PAI_USER_DIR}/Daemon/daemon.md --verboseStep 2: Show Section-by-Section Summary
Present changes grouped by section:
- Which sections have new content
- Which sections are unchanged
- How many security redactions would be applied
- Source data freshness per section
Step 3: Highlight Risks
Flag any sections where:
- Content is older than 30 days
- Security filter made redactions (show what was caught)
- PAI source file is missing
Output Format
Daemon Preview — what would change:
[ABOUT]: unchanged
[MISSION]: 2 goals updated from TELOS
[FAVORITE_BOOKS]: +2 new (from TELOS/BOOKS.md)
[RECENT_IDEAS]: 10 new ideas (title + thesis)
[CURRENTLY_WORKING_ON]: 6 themes from last 14 days
[WISDOM]: 5 quotes added
Security: 0 redactions needed
Sources: all present and fresh
To apply: run "update daemon"ReadDaemon Workflow
Purpose: Fetch and display the current state of the live daemon profile.
Trigger Phrases
- "read daemon"
- "check daemon"
- "what's on my daemon"
- "daemon status"
- "show daemon"
Process
Step 1: Fetch Live MCP Data
curl -s https://mcp.daemon.example.com \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"get_all","arguments":{}},"id":1}'Step 2: Parse and Display
Extract the JSON response and display each section with its content length and freshness.
Step 3: Check Local vs Live
Compare the local ${PAI_USER_DIR}/Daemon/daemon.md against the live API response to identify drift.
bun ${CLAUDE_SKILL_DIR}/Tools/DaemonAggregator.ts --sourcesOutput Format
Daemon Status (daemon.example.com)
Last updated: 2026-04-08T21:45:00Z
Sections:
About: 342 chars
Mission: 256 chars
Location: Bay Area
Books: 12 items
Movies: 8 items
Predictions: 8 items
Preferences: 10 items
Daily Routine: 9 items
Podcasts: 5 items
TELOS: populated
Projects: 8 technical, 3 creative
Live endpoint: 200 OK
MCP API: responding
Local sync: [in sync / X sections drifted]UpdateDaemon Workflow
Purpose: Aggregate PAI system data, apply security filter, preview for approval, then deploy to daemon.example.com and MCP API.
Trigger Phrases
- "update daemon"
- "refresh daemon"
- "update my daemon"
- "sync and update daemon"
Process
Step 1: Run Aggregator
bun ${CLAUDE_SKILL_DIR}/Tools/DaemonAggregator.ts --preview --verboseThis reads from:
- TELOS (missions, goals, books, movies, wisdom)
- Knowledge archive (recent Ideas — title + thesis only)
- PROJECTS.md (public projects only)
- Recent work sessions (abstracted to topic themes)
- Existing daemon.md (preserves manually curated sections)
The SecurityFilter runs automatically and reports any redactions.
Step 2: Show Diff Against Current
bun ${CLAUDE_SKILL_DIR}/Tools/DaemonAggregator.ts --diff ${PAI_USER_DIR}/Daemon/daemon.mdPresent the diff to the user showing:
- New content added
- Content removed
- Sections updated
- Any security redactions applied
Keep the review to one screen max. Summarize changes by section, don't dump raw content.
Step 3: Get Approval
Ask the user to confirm the update. Show:
- Section-by-section summary of what changed
- Number of security redactions applied
- Any warnings (stale sections, missing sources)
Do not proceed without explicit approval.
Step 4: Write Updated daemon.md
bun ${CLAUDE_SKILL_DIR}/Tools/DaemonAggregator.ts --output ${PAI_USER_DIR}/Daemon/daemon.mdStep 5: Sync to Public Repo
Copy the filtered daemon.md to the public website repo:
cp ${PAI_USER_DIR}/Daemon/daemon.md ~/Projects/daemon/public/daemon.mdStep 6: Deploy
Run the existing deploy pipeline:
cd ~/Projects/daemon && git add -A && git commit -m "Update daemon data $(date +%Y-%m-%d)" && git pushThen sync to MCP KV:
cd ${CLAUDE_SKILL_DIR}/Mcp && bun install && bun update-daemonStep 7: Verify
Confirm both endpoints are live:
- Website:
curl -s -o /dev/null -w "%{http_code}" https://daemon.example.com - MCP API:
curl -s https://mcp.daemon.example.com -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"get_about","arguments":{}},"id":1}' | head -c 200
Example Response
Running the **UpdateDaemon** workflow in the **Daemon** skill to aggregate and deploy...
Aggregated PAI data:
Books: 12 | Movies: 8 | Ideas: 10 | Work themes: 6 | Wisdom: 5
Security filter: clean (no redactions needed)
Changes vs current daemon:
+ Added 3 new recent ideas
+ Updated work themes (4 new, 2 removed)
~ Books list merged (2 new from TELOS)
= Mission, location, predictions unchanged
Approve this update? [Waiting for the user]
[After approval]
Wrote daemon.md (4,832 bytes)
Deployed to Cloudflare Pages
Synced to MCP KV
Website: 200 OK
MCP API: responding