diff --git a/Bundles/Kai/install.ts b/Bundles/Kai/install.ts index 9ba521ffca..ee982e6b7d 100755 --- a/Bundles/Kai/install.ts +++ b/Bundles/Kai/install.ts @@ -740,10 +740,10 @@ Files created: Next steps: 1. Install the packs IN ORDER by giving each pack file to your AI: - - kai-hook-system.md - - kai-history-system.md - - kai-core-install.md - - kai-voice-system.md (optional, requires ElevenLabs) + - kai-hook-system/INSTALL.md + - kai-history-system/INSTALL.md + - kai-core-install/INSTALL.md + - kai-voice-system/INSTALL.md (optional, requires ElevenLabs) 2. Restart Claude Code to activate hooks diff --git a/PACKS.md b/PACKS.md index 3503a56aeb..5ba91b09b9 100644 --- a/PACKS.md +++ b/PACKS.md @@ -255,19 +255,19 @@ Browse all packs: [Packs/](Packs/) | Pack | Type | Description | |------|------|-------------| -| [kai-hook-system](Packs/kai-hook-system.md) | Foundation | Event-driven automation framework for hook-based capabilities | -| [kai-history-system](Packs/kai-history-system.md) | Infrastructure | Automatic context-tracking for all work, decisions, and learnings | -| [kai-core-install](Packs/kai-core-install.md) | Core | Skills + Identity + Architecture - complete foundation pack | -| [kai-voice-system](Packs/kai-voice-system.md) | Notifications | Voice output with ElevenLabs TTS and prosody enhancement | -| [kai-observability-server](Packs/kai-observability-server.md) | Observability | Real-time multi-agent monitoring dashboard | +| [kai-hook-system](Packs/kai-hook-system) | Foundation | Event-driven automation framework for hook-based capabilities | +| [kai-history-system](Packs/kai-history-system) | Infrastructure | Automatic context-tracking for all work, decisions, and learnings | +| [kai-core-install](Packs/kai-core-install) | Core | Skills + Identity + Architecture - complete foundation pack | +| [kai-voice-system](Packs/kai-voice-system) | Notifications | Voice output with ElevenLabs TTS and prosody enhancement | +| [kai-observability-server](Packs/kai-observability-server) | Observability | Real-time multi-agent monitoring dashboard | ### Skill Packs | Pack | Type | Description | |------|------|-------------| -| [kai-art-skill](Packs/kai-art-skill.md) | Skill | Visual content generation with charcoal architectural sketch aesthetic | -| [kai-agents-skill](Packs/kai-agents-skill.md) | Skill | Dynamic agent composition with specialized personalities and voices | -| [kai-prompting-skill](Packs/kai-prompting-skill.md) | Skill | Meta-prompting system for programmatic prompt generation | +| [kai-art-skill](Packs/kai-art-skill) | Skill | Visual content generation with charcoal architectural sketch aesthetic | +| [kai-agents-skill](Packs/kai-agents-skill) | Skill | Dynamic agent composition with specialized personalities and voices | +| [kai-prompting-skill](Packs/kai-prompting-skill) | Skill | Meta-prompting system for programmatic prompt generation | **Installation order:** hooks → history → core-install → voice → observability (optional) → skill packs diff --git a/Packs/kai-agents-skill.md b/Packs/kai-agents-skill.md deleted file mode 100644 index dbbb9a68ff..0000000000 --- a/Packs/kai-agents-skill.md +++ /dev/null @@ -1,1840 +0,0 @@ ---- -name: Kai Agents Skill -pack-id: danielmiessler-agents-skill-core-v1.1.1 -version: 1.1.1 -author: danielmiessler -description: Dynamic agent composition and orchestration system - create custom agents with unique personalities, voices, and trait combinations on-the-fly -type: skill -purpose-type: [productivity, automation, development] -platform: claude-code -dependencies: [danielmiessler-core-install-core-v1.0.0] -keywords: [agents, delegation, parallel, traits, personalities, voice, composition, dynamic, factory, custom, orchestration, subagents] ---- - -

- Kai Agents Skill -

- -# Kai Agents Skill - -> Dynamic agent composition system - create specialized agents with unique personalities and voices, composed from traits on-the-fly - -## Installation Prompt - -You are receiving a PAI Pack - a modular upgrade for AI agent systems. - -**What is PAI?** See: [PAI Project Overview](../README.md#what-is-pai) - -**What is a Pack?** See: [Pack System](../README.md#the-journey-pai-v1x--v20) - -This pack adds a complete agent composition and orchestration system to your AI infrastructure. Instead of using generic agents for every task, you can now: - -- **Compose custom agents** from traits (expertise + personality + approach) -- **Assign unique voices** to each agent based on personality type -- **Launch parallel agents** with different perspectives on the same problem -- **Create named persistent agents** with full backstories and character development - -**The key insight:** Different tasks benefit from different cognitive styles. A security audit needs skeptical adversarial thinking. Creative work needs enthusiastic exploration. This pack lets you dynamically compose the right agent for each job. - -Please follow the installation instructions below to integrate this pack into your infrastructure. - ---- - -## What's Included - -| Component | File | Purpose | -|-----------|------|---------| -| Agents skill | `skills/Agents/SKILL.md` | Routing and agent definitions | -| Agent factory | `skills/Agents/Tools/AgentFactory.ts` | Dynamic agent composition | -| Trait definitions | `skills/Agents/Data/Traits.yaml` | Expertise, personality, approach traits | -| Voice mapping | `skills/Agents/Data/VoiceMapping.yaml` | Personality to ElevenLabs voice mapping | -| Create agent | `skills/Agents/Workflows/CreateCustomAgent.md` | Custom agent workflow | -| List traits | `skills/Agents/Workflows/ListTraits.md` | Show available traits | - -**Summary:** -- **Files created:** 8+ (skill, tool, workflows, data) -- **Hooks registered:** 0 (skill-based, not hook-based) -- **Dependencies:** kai-core-install (required), kai-voice-system (optional for voice) - ---- - -## The Concept and/or Problem - -AI agent systems typically offer one-size-fits-all agents. You spawn "an agent" to do research, security review, or creative work - but they all have the same personality, same approach, same voice. - -This creates several problems: - -**Cognitive Monoculture:** -- Every analysis comes from the same perspective -- No natural devil's advocacy or alternative viewpoints -- Blind spots become systematic - -**Lack of Specialization:** -- A security review agent should think differently than a creative brainstorming agent -- Generic agents lack the focused expertise and behavioral patterns that make specialists valuable -- "Jack of all trades" means master of none - -**Voice Confusion:** -- When multiple agents speak, they're indistinguishable -- No personality differentiation in outputs -- Parallel agent results blend together without clear ownership - -**Lost Context:** -- Each agent starts fresh with no character continuity -- No relationship building between sessions -- Expertise and personality traits must be re-specified each time - -**The Fundamental Problem:** - -Modern AI agent frameworks provide the plumbing for spawning and orchestrating agents, but leave personality and specialization as an afterthought. You can spawn 10 agents, but they're all clones. - -Real teams have diverse thinkers: the skeptic who pokes holes, the enthusiast who sees possibilities, the meticulous reviewer who catches details. Your agent system should too. - ---- - -## The Solution - -The Agents Skill solves this through a **hybrid agent model** with dynamic trait composition: - -### Hybrid Agent Model - -Two types of agents working together: - -| Type | Definition | Best For | -|------|------------|----------| -| **Named Agents** | Persistent identities with backstories | Recurring work, voice output, relationships | -| **Dynamic Agents** | Task-specific specialists composed from traits | One-off tasks, novel combinations, parallel work | - -### Trait Composition System - -Agents are composed by combining three trait categories: - -``` -AGENT = Expertise + Personality + Approach -``` - -**Expertise (10 types):** security, legal, finance, medical, technical, research, creative, business, data, communications - -**Personality (10 dimensions):** skeptical, enthusiastic, cautious, bold, analytical, creative, empathetic, contrarian, pragmatic, meticulous - -**Approach (8 styles):** thorough, rapid, systematic, exploratory, comparative, synthesizing, adversarial, consultative - -### Voice Mapping - -Each trait combination maps to a distinct voice: -- `[skeptical, analytical]` → Intellectual academic voice -- `[enthusiastic, creative]` → High-energy dynamic voice -- `[cautious, meticulous]` → Measured formal voice - -This means 5 custom agents get 5 different voices automatically. - -### The AgentFactory - -A CLI tool that: -1. Accepts trait specification or infers traits from task description -2. Composes a complete agent prompt with personality and approach -3. Maps traits to appropriate voice -4. Outputs ready-to-use agent configuration - -```bash -# Infer from task -bun run AgentFactory.ts --task "Review this security architecture" -# Result: security + skeptical + thorough agent with appropriate voice - -# Specify explicitly -bun run AgentFactory.ts --traits "legal,meticulous,systematic" -# Result: Legal expert with careful systematic approach -``` - ---- - -## What Makes This Different - -This sounds similar to role-playing prompts which also create agent personas. What makes this approach different? - -Traditional role-playing ("You are a security expert...") is ad-hoc prompt engineering. This system provides **composable, voice-mapped, production-ready agent infrastructure**. Traits are defined once in YAML, composed dynamically, and automatically mapped to distinct voice outputs. The factory ensures consistent personality delivery across sessions while the trait system prevents copy-paste prompt drift. - -- Composable traits prevent ad-hoc prompt engineering drift -- Voice mapping creates audible agent differentiation automatically -- Factory pattern ensures consistent personality across all sessions -- Named agents provide relationship continuity between work sessions - ---- - -## Installation - -### Prerequisites - -- **Bun runtime**: `curl -fsSL https://bun.sh/install | bash` -- **Claude Code** (or compatible agent system) -- **kai-core-install** pack installed (provides skill routing) -- **Optional**: kai-voice-system pack for voice notifications - ---- - -### Pre-Installation: System Analysis - -```bash -# 1. Check PAI_DIR -echo "PAI_DIR: ${PAI_DIR:-'NOT SET - will use ~/.config/pai'}" - -# 2. Check for existing Agents skill -PAI_CHECK="${PAI_DIR:-$HOME/.config/pai}" -if [ -d "$PAI_CHECK/skills/Agents" ]; then - echo "Warning: Agents skill already exists" - ls -la "$PAI_CHECK/skills/Agents" -else - echo "Clean install - no existing Agents skill" -fi - -# 3. Check dependencies -if [ -f "$PAI_CHECK/skills/CORE/SKILL.md" ]; then - echo "kai-core-install: INSTALLED" -else - echo "Warning: kai-core-install not found (required)" -fi -``` - ---- - -### Step 1: Create Directory Structure - -```bash -PAI_DIR="${PAI_DIR:-$HOME/.config/pai}" - -mkdir -p "$PAI_DIR/skills/Agents/Data" -mkdir -p "$PAI_DIR/skills/Agents/Tools" -mkdir -p "$PAI_DIR/skills/Agents/Templates" -mkdir -p "$PAI_DIR/skills/Agents/Workflows" -mkdir -p "$PAI_DIR/skills/Agents/Contexts" - -# Verify -ls -la "$PAI_DIR/skills/Agents/" -``` - ---- - -### Step 2: Create SKILL.md - -```bash -cat > "$PAI_DIR/skills/Agents/SKILL.md" << 'SKILL_EOF' ---- -name: Agents -description: Dynamic agent composition and management system. USE WHEN user says create custom agents, spin up custom agents, specialized agents, OR asks for agent personalities, available traits, agent voices. Handles custom agent creation, personality assignment, voice mapping, and parallel agent orchestration. ---- - -# Agents - Custom Agent Composition System - -**Auto-routes when user mentions custom agents, agent creation, or specialized personalities.** - -## Overview - -The Agents skill provides complete agent composition and management: -- Dynamic agent composition from traits (expertise + personality + approach) -- Personality definitions and voice mappings -- Custom agent creation with unique voices -- Parallel agent orchestration patterns - -## Workflow Routing - -**Available Workflows:** -- **CREATECUSTOMAGENT** - Create specialized custom agents → `Workflows/CreateCustomAgent.md` -- **LISTTRAITS** - Show available agent traits → `Workflows/ListTraits.md` -- **SPAWNPARALLEL** - Launch parallel agents → `Workflows/SpawnParallelAgents.md` - -## Route Triggers - -**CRITICAL: The word "custom" is the ABSOLUTE trigger - NO EXCEPTIONS:** - -| User Says | What to Use | subagent_type | Why | -|-----------|-------------|---------------|-----| -| "**custom agents**", "create **custom** agents", "spin up **custom**" | AgentFactory | `general-purpose` | Unique prompts + unique voices | -| "agents", "launch agents", "bunch of agents" | Generic prompt | `Intern` | Same voice, parallel grunt work | -| "use [agent name]", "get [agent name] to" | Direct call | Named agent type | Pre-defined personality | - -**CONSTITUTIONAL RULE FOR CUSTOM AGENTS:** -When user says "custom agents", you MUST: -1. Use AgentFactory to compose EACH agent with DIFFERENT traits -2. Use `subagent_type: "general-purpose"` - **NEVER** "Intern", "Designer", "Architect", etc. -3. Each agent gets their own voice from the trait-to-voice mapping - -**❌ NEVER DO THIS for custom agents:** -``` -subagent_type: "Intern" // WRONG - forces same voice on all -subagent_type: "Designer" // WRONG - forces Designer's voice -subagent_type: "Architect" // WRONG - forces Architect's voice -``` - -**✅ ALWAYS DO THIS for custom agents:** -``` -subagent_type: "general-purpose" // CORRECT - uses custom voice from AgentFactory -``` - -## Architecture - -### Hybrid Agent Model - -Two types of agents: - -| Type | Definition | Best For | -|------|------------|----------| -| **Named Agents** | Persistent identities with backstories | Recurring work, voice output | -| **Dynamic Agents** | Task-specific specialists from traits | One-off tasks, parallel work | - -### Components - -**Data/Traits.yaml** - 28 composable traits with voice mappings -**Templates/DynamicAgent.hbs** - Agent prompt template -**Tools/AgentFactory.ts** - Dynamic composition engine -**AgentPersonalities.md** - Named agent definitions - -## Usage - -Just ask naturally: -- "I need a legal expert to review this contract" -- "Spin up 5 custom science agents" -- "Get me someone skeptical about security" - -The skill routes to appropriate workflow automatically. - -## Model Selection - -| Task Type | Model | Why | -|-----------|-------|-----| -| Grunt work | `haiku` | 10-20x faster | -| Standard analysis | `sonnet` | Balanced | -| Deep reasoning | `opus` | Maximum intelligence | -SKILL_EOF -``` - ---- - -### Step 3: Create Traits.yaml - -This is the core trait definition file with voice mappings. - -```bash -cat > "$PAI_DIR/skills/Agents/Data/Traits.yaml" << 'TRAITS_EOF' -# Traits.yaml - Composable Agent Traits -# Version: 1.0.0 -# -# Agents are composed by combining: expertise + personality + approach -# -# Usage: -# bun run AgentFactory.ts --task "Review security architecture" -# bun run AgentFactory.ts --traits "security,skeptical,thorough" - -# ============================================================================== -# EXPERTISE AREAS - Domain knowledge and specialization -# ============================================================================== -expertise: - security: - name: "Security Expert" - description: | - Deep knowledge of vulnerabilities, threat models, attack vectors, and - defensive strategies. Understands OWASP, CVE databases, penetration - testing methodologies, and security architecture patterns. - keywords: - - vulnerability - - threat - - exploit - - defense - - penetration - - audit - - security - - attack - - CVE - - OWASP - - legal: - name: "Legal Analyst" - description: | - Contract analysis, compliance requirements, liability assessment, and - regulatory frameworks. Can identify problematic clauses, missing - protections, and legal risks. - keywords: - - contract - - compliance - - legal - - regulation - - liability - - terms - - agreement - - clause - - NDA - - intellectual property - - finance: - name: "Financial Analyst" - description: | - Market dynamics, valuation methods, risk assessment, and financial - modeling. Understands ROI calculations, investment analysis, and - economic indicators. - keywords: - - valuation - - market - - investment - - risk - - financial - - ROI - - revenue - - profit - - economics - - pricing - - medical: - name: "Medical/Health Expert" - description: | - Healthcare knowledge, medical terminology, treatment protocols, and - health research interpretation. Can evaluate clinical claims and - health-related content. - keywords: - - health - - medical - - treatment - - diagnosis - - clinical - - patient - - disease - - therapy - - pharmaceutical - - technical: - name: "Technical Specialist" - description: | - Software architecture, system design, implementation patterns, and - debugging. Deep understanding of code quality, performance, and - technical trade-offs. - keywords: - - code - - architecture - - system - - implementation - - debug - - technical - - API - - database - - infrastructure - - software - - research: - name: "Research Specialist" - description: | - Academic methodology, source evaluation, literature review, and - synthesis. Knows how to find, evaluate, and combine information - from multiple sources. - keywords: - - research - - study - - academic - - sources - - literature - - evidence - - methodology - - peer-reviewed - - citation - - creative: - name: "Creative Specialist" - description: | - Content creation, storytelling, visual thinking, and innovative - approaches. Brings fresh perspectives and unexpected connections - to problems. - keywords: - - creative - - content - - story - - design - - innovative - - artistic - - narrative - - visual - - brand - - business: - name: "Business Strategist" - description: | - Market analysis, competitive positioning, growth strategies, and - operations. Understands business models, go-to-market strategies, - and organizational dynamics. - keywords: - - business - - strategy - - market - - competitive - - growth - - operations - - startup - - enterprise - - B2B - - B2C - - data: - name: "Data Analyst" - description: | - Statistical analysis, data visualization, pattern recognition, and - quantitative reasoning. Can interpret datasets, identify trends, - and draw evidence-based conclusions. - keywords: - - data - - statistics - - analysis - - metrics - - trends - - visualization - - quantitative - - dataset - - patterns - - communications: - name: "Communications Expert" - description: | - Messaging strategy, audience analysis, persuasive writing, and - public relations. Knows how to frame information for different - audiences and contexts. - keywords: - - communication - - messaging - - audience - - PR - - media - - presentation - - writing - - speaking - -# ============================================================================== -# PERSONALITY DIMENSIONS - How the agent thinks and behaves -# ============================================================================== -personality: - skeptical: - name: "Skeptical" - description: "Questions assumptions, demands evidence, looks for flaws" - prompt_fragment: | - You approach all claims with healthy skepticism. Demand evidence for - assertions. Question assumptions that others take for granted. Look for - logical flaws, unstated premises, and potential biases. Don't accept - things at face value just because they sound authoritative. - - enthusiastic: - name: "Enthusiastic" - description: "Finds excitement in discoveries, positive framing, energy" - prompt_fragment: | - You bring genuine enthusiasm to the work. Get excited about interesting - findings and share that excitement. Frame discoveries positively while - remaining accurate. Your energy is contagious but grounded in substance. - - cautious: - name: "Cautious" - description: "Considers edge cases, failure modes, risks" - prompt_fragment: | - You think carefully about what could go wrong. Consider edge cases that - others might miss. Identify potential failure modes before they become - problems. Err on the side of safety when uncertain. Flag risks early. - - bold: - name: "Bold" - description: "Willing to take risks, make strong claims, push boundaries" - prompt_fragment: | - You're willing to make strong claims when the evidence warrants it. Take - intellectual risks. Push past conventional thinking when you see a better - path. Don't hedge unnecessarily. State your conclusions with confidence. - - analytical: - name: "Analytical" - description: "Data-driven, logical, systematic breakdown" - prompt_fragment: | - You approach problems analytically. Break down complex issues into - component parts. Rely on data and logic over intuition. Show your - reasoning step by step. Quantify when possible. - - creative: - name: "Creative" - description: "Lateral thinking, unexpected connections, novel approaches" - prompt_fragment: | - You think laterally. Make unexpected connections between disparate ideas. - Consider unconventional approaches. Don't be bound by how things are - usually done. Find the interesting angle others might miss. - - empathetic: - name: "Empathetic" - description: "Considers human impact, emotional intelligence, user-centered" - prompt_fragment: | - You consider the human element in everything. Think about emotional impact - and user experience. Show understanding of different perspectives. Center - the people affected by decisions and recommendations. - - contrarian: - name: "Contrarian" - description: "Deliberately takes opposing view, stress-tests ideas" - prompt_fragment: | - You deliberately take the opposing view to stress-test ideas. Find the - strongest counterarguments even for positions you might agree with. Play - devil's advocate effectively. Your job is to find weaknesses. - - pragmatic: - name: "Pragmatic" - description: "Focuses on what works, practical over theoretical" - prompt_fragment: | - You focus on what actually works in practice. Prefer practical solutions - over theoretically elegant ones. Consider implementation realities. Ask - "yes, but will it work?" Judge ideas by outcomes, not intentions. - - meticulous: - name: "Meticulous" - description: "Attention to detail, precision, thoroughness" - prompt_fragment: | - You pay extraordinary attention to detail. Nothing escapes your notice. - Precision matters - get the specifics right. Double-check your work. - The details often contain the most important information. - -# ============================================================================== -# APPROACH STYLES - How the agent works on tasks -# ============================================================================== -approach: - thorough: - name: "Thorough" - description: "Exhaustive analysis, no stone unturned, comprehensive" - prompt_fragment: | - Be exhaustive in your analysis. Leave no stone unturned. Cover all - relevant angles and perspectives. Comprehensive coverage is more - important than speed. Don't stop at the obvious. - - rapid: - name: "Rapid" - description: "Quick assessment, key points, efficiency-focused" - prompt_fragment: | - Move quickly and efficiently. Focus on the key points that matter most. - Provide rapid assessment without unnecessary elaboration. Time is - valuable - get to the point. - - systematic: - name: "Systematic" - description: "Structured approach, clear methodology, step-by-step" - prompt_fragment: | - Follow a clear, structured methodology. Work step by step in logical - order. Document your process so others can follow your reasoning. - Maintain organization throughout. - - exploratory: - name: "Exploratory" - description: "Follow interesting threads, discovery-oriented, flexible" - prompt_fragment: | - Follow interesting threads wherever they lead. Be discovery-oriented - rather than goal-fixated. Stay flexible and let the investigation - guide itself. Some of the best insights come from unexpected directions. - - comparative: - name: "Comparative" - description: "Evaluates options, pros/cons, trade-off analysis" - prompt_fragment: | - Compare and contrast options explicitly. Lay out pros and cons for - each alternative. Analyze trade-offs clearly. Help make informed - decisions by showing what's gained and lost with each choice. - - synthesizing: - name: "Synthesizing" - description: "Combines multiple sources, integrates perspectives" - prompt_fragment: | - Synthesize information from multiple sources into a unified view. - Integrate different perspectives rather than just listing them. - Find the coherent story that connects disparate pieces. - - adversarial: - name: "Adversarial" - description: "Red team approach, find weaknesses, attack the problem" - prompt_fragment: | - Take an adversarial stance. Your job is to find weaknesses, not - confirm strengths. Attack the problem from every angle. Think like - someone trying to break this. What would an attacker do? - - consultative: - name: "Consultative" - description: "Advisory stance, recommendations with rationale" - prompt_fragment: | - Take a consultative approach. Provide recommendations with clear - rationale. Explain not just what to do but why. Help the decision-maker - understand the reasoning so they can adapt if needed. - -# ============================================================================== -# VOICE MAPPINGS - How dynamic agents map to TTS voices -# ============================================================================== -# CUSTOMIZE: Replace voice_id values with your TTS provider's voice IDs -# -voice_mappings: - # Default voice when no mapping matches - default: "Default" - default_voice_id: "YOUR_DEFAULT_VOICE_ID" - - # =========================================================================== - # VOICE REGISTRY - Define your available voices - # =========================================================================== - voice_registry: - # --- AUTHORITATIVE VOICES --- - Authoritative: - voice_id: "YOUR_AUTHORITATIVE_VOICE_ID" - characteristics: ["authoritative", "measured", "intellectual"] - description: "Deep authoritative voice for serious analysis" - stability: 0.70 - similarity_boost: 0.85 - - Professional: - voice_id: "YOUR_PROFESSIONAL_VOICE_ID" - characteristics: ["professional", "balanced", "neutral"] - description: "Balanced professional voice for general use" - stability: 0.65 - similarity_boost: 0.80 - - # --- WARM/FRIENDLY VOICES --- - Warm: - voice_id: "YOUR_WARM_VOICE_ID" - characteristics: ["warm", "friendly", "engaging"] - description: "Warm friendly voice for supportive interactions" - stability: 0.50 - similarity_boost: 0.75 - - Gentle: - voice_id: "YOUR_GENTLE_VOICE_ID" - characteristics: ["warm", "calm", "gentle"] - description: "Calm gentle voice for empathetic guidance" - stability: 0.55 - similarity_boost: 0.75 - - # --- ENERGETIC VOICES --- - Energetic: - voice_id: "YOUR_ENERGETIC_VOICE_ID" - characteristics: ["energetic", "excited", "dynamic"] - description: "High-energy voice for enthusiastic delivery" - stability: 0.35 - similarity_boost: 0.65 - - Dynamic: - voice_id: "YOUR_DYNAMIC_VOICE_ID" - characteristics: ["energetic", "fast", "charismatic"] - description: "Fast-paced dynamic voice for exciting content" - stability: 0.40 - similarity_boost: 0.70 - - # --- INTELLECTUAL VOICES --- - Academic: - voice_id: "YOUR_ACADEMIC_VOICE_ID" - characteristics: ["intellectual", "warm", "academic"] - description: "Academic voice for research and analysis" - stability: 0.62 - similarity_boost: 0.80 - - Sophisticated: - voice_id: "YOUR_SOPHISTICATED_VOICE_ID" - characteristics: ["intellectual", "sophisticated", "smooth"] - description: "Sophisticated voice for nuanced discussion" - stability: 0.60 - similarity_boost: 0.80 - - # --- EDGY/DISTINCTIVE VOICES --- - Intense: - voice_id: "YOUR_INTENSE_VOICE_ID" - characteristics: ["edgy", "gravelly", "intense"] - description: "Intense voice for adversarial/security work" - stability: 0.55 - similarity_boost: 0.75 - - Gritty: - voice_id: "YOUR_GRITTY_VOICE_ID" - characteristics: ["edgy", "raspy", "authentic"] - description: "Gritty authentic voice for skeptical analysis" - stability: 0.50 - similarity_boost: 0.70 - - # =========================================================================== - # TRAIT → VOICE MAPPINGS - # =========================================================================== - mappings: - # Skeptical combinations → Intellectual voices - - traits: ["contrarian", "skeptical"] - voice: "Intense" - reason: "Contrarian skepticism needs intensity" - - - traits: ["skeptical", "analytical"] - voice: "Academic" - reason: "Skeptical analysis suits academic warmth" - - - traits: ["skeptical", "meticulous"] - voice: "Sophisticated" - reason: "Meticulous skepticism suits sophistication" - - # Creative combinations → Energetic voices - - traits: ["enthusiastic", "creative"] - voice: "Energetic" - reason: "Creative enthusiasm needs high energy" - - - traits: ["creative", "exploratory"] - voice: "Dynamic" - reason: "Creative exploration suits dynamic delivery" - - # Analytical combinations → Professional voices - - traits: ["analytical", "technical"] - voice: "Authoritative" - reason: "Technical analysis suits authority" - - - traits: ["analytical", "systematic"] - voice: "Professional" - reason: "Systematic analysis suits professionalism" - - # Empathetic combinations → Warm voices - - traits: ["empathetic", "consultative"] - voice: "Warm" - reason: "Empathetic consulting suits warmth" - - - traits: ["empathetic", "synthesizing"] - voice: "Gentle" - reason: "Synthesizing empathy suits gentleness" - - # Security combinations → Edgy voices - - traits: ["security", "adversarial"] - voice: "Intense" - reason: "Security adversary suits intensity" - - - traits: ["security", "skeptical"] - voice: "Gritty" - reason: "Security skepticism suits gritty authenticity" - - # =========================================================================== - # FALLBACKS - Single trait → voice - # =========================================================================== - fallbacks: - skeptical: "Academic" - enthusiastic: "Energetic" - analytical: "Authoritative" - cautious: "Professional" - bold: "Intense" - creative: "Dynamic" - empathetic: "Warm" - contrarian: "Intense" - pragmatic: "Professional" - meticulous: "Sophisticated" - - security: "Intense" - legal: "Authoritative" - finance: "Professional" - medical: "Warm" - technical: "Professional" - research: "Academic" - communications: "Dynamic" - business: "Professional" - data: "Academic" - - thorough: "Professional" - rapid: "Dynamic" - systematic: "Professional" - exploratory: "Dynamic" - adversarial: "Intense" - consultative: "Warm" - -# ============================================================================== -# COMPOSITION EXAMPLES -# ============================================================================== -examples: - security_audit: - description: "Security architecture review" - traits: ["security", "skeptical", "thorough", "adversarial"] - - contract_review: - description: "Legal contract analysis" - traits: ["legal", "cautious", "meticulous", "systematic"] - - market_analysis: - description: "Competitive market research" - traits: ["business", "analytical", "comparative", "thorough"] - - code_review: - description: "Technical code review" - traits: ["technical", "meticulous", "systematic"] - - creative_brief: - description: "Creative content development" - traits: ["creative", "enthusiastic", "exploratory"] - - red_team: - description: "Adversarial idea testing" - traits: ["contrarian", "skeptical", "adversarial", "bold"] - - user_research: - description: "User experience research" - traits: ["research", "empathetic", "synthesizing"] - - quick_assessment: - description: "Rapid evaluation needed" - traits: ["analytical", "pragmatic", "rapid"] -TRAITS_EOF -``` - ---- - -### Step 4: Create AgentFactory.ts - -The CLI tool for composing agents from traits. - -```bash -cat > "$PAI_DIR/skills/Agents/Tools/AgentFactory.ts" << 'FACTORY_EOF' -#!/usr/bin/env bun - -/** - * AgentFactory - Dynamic Agent Composition from Traits - * - * Composes specialized agents on-the-fly by combining traits from Traits.yaml. - * - * Usage: - * bun run AgentFactory.ts --task "Review this security architecture" - * bun run AgentFactory.ts --traits "security,skeptical,thorough" - * bun run AgentFactory.ts --list - * - * @version 1.0.0 - */ - -import { parseArgs } from "util"; -import { readFileSync, existsSync } from "fs"; -import { parse as parseYaml } from "yaml"; -import Handlebars from "handlebars"; - -// Paths - adjust PAI_DIR as needed -const PAI_DIR = process.env.PAI_DIR || `${process.env.HOME}/.config/pai`; -const TRAITS_PATH = `${PAI_DIR}/skills/Agents/Data/Traits.yaml`; -const TEMPLATE_PATH = `${PAI_DIR}/skills/Agents/Templates/DynamicAgent.hbs`; - -// Types -interface TraitDefinition { - name: string; - description: string; - prompt_fragment?: string; - keywords?: string[]; -} - -interface VoiceMapping { - traits: string[]; - voice: string; - voice_id?: string; - reason?: string; -} - -interface VoiceRegistryEntry { - voice_id: string; - characteristics: string[]; - description: string; - stability: number; - similarity_boost: number; -} - -interface TraitsData { - expertise: Record; - personality: Record; - approach: Record; - voice_mappings: { - default: string; - default_voice_id: string; - voice_registry: Record; - mappings: VoiceMapping[]; - fallbacks: Record; - }; - examples: Record; -} - -interface ComposedAgent { - name: string; - traits: string[]; - expertise: TraitDefinition[]; - personality: TraitDefinition[]; - approach: TraitDefinition[]; - voice: string; - voiceId: string; - voiceReason: string; - prompt: string; -} - -function loadTraits(): TraitsData { - if (!existsSync(TRAITS_PATH)) { - console.error(`Error: Traits file not found at ${TRAITS_PATH}`); - process.exit(1); - } - const content = readFileSync(TRAITS_PATH, "utf-8"); - return parseYaml(content) as TraitsData; -} - -function loadTemplate(): HandlebarsTemplateDelegate { - if (!existsSync(TEMPLATE_PATH)) { - console.error(`Error: Template file not found at ${TEMPLATE_PATH}`); - process.exit(1); - } - const content = readFileSync(TEMPLATE_PATH, "utf-8"); - return Handlebars.compile(content); -} - -function inferTraitsFromTask(task: string, traits: TraitsData): string[] { - const inferred: string[] = []; - const taskLower = task.toLowerCase(); - - // Check expertise keywords - for (const [key, def] of Object.entries(traits.expertise)) { - if (def.keywords?.some((kw) => taskLower.includes(kw.toLowerCase()))) { - inferred.push(key); - } - } - - // Check personality keywords - for (const [key, def] of Object.entries(traits.personality)) { - if (def.keywords?.some((kw) => taskLower.includes(kw.toLowerCase()))) { - inferred.push(key); - } - } - - // Check approach keywords - for (const [key, def] of Object.entries(traits.approach)) { - if (def.keywords?.some((kw) => taskLower.includes(kw.toLowerCase()))) { - inferred.push(key); - } - } - - // Apply smart defaults - const hasExpertise = inferred.some((t) => traits.expertise[t]); - const hasPersonality = inferred.some((t) => traits.personality[t]); - const hasApproach = inferred.some((t) => traits.approach[t]); - - if (!hasPersonality) inferred.push("analytical"); - if (!hasApproach) inferred.push("thorough"); - if (!hasExpertise) inferred.push("research"); - - return [...new Set(inferred)]; -} - -function resolveVoice( - traitKeys: string[], - traits: TraitsData -): { voice: string; voiceId: string; reason: string } { - const mappings = traits.voice_mappings; - const registry = mappings.voice_registry || {}; - - const getVoiceId = (voiceName: string, fallbackId?: string): string => { - if (registry[voiceName]?.voice_id) { - return registry[voiceName].voice_id; - } - return fallbackId || mappings.default_voice_id || ""; - }; - - // Check explicit combination mappings - const matchedMappings = mappings.mappings - .map((m) => ({ - ...m, - matchCount: m.traits.filter((t) => traitKeys.includes(t)).length, - isFullMatch: m.traits.every((t) => traitKeys.includes(t)), - })) - .filter((m) => m.isFullMatch) - .sort((a, b) => b.matchCount - a.matchCount); - - if (matchedMappings.length > 0) { - const best = matchedMappings[0]; - return { - voice: best.voice, - voiceId: best.voice_id || getVoiceId(best.voice), - reason: best.reason || `Matched traits: ${best.traits.join(", ")}`, - }; - } - - // Check fallbacks - for (const trait of traitKeys) { - if (mappings.fallbacks[trait]) { - const voiceName = mappings.fallbacks[trait]; - return { - voice: voiceName, - voiceId: getVoiceId(voiceName), - reason: `Fallback for trait: ${trait}`, - }; - } - } - - return { - voice: mappings.default, - voiceId: mappings.default_voice_id || "", - reason: "Default voice", - }; -} - -function composeAgent( - traitKeys: string[], - task: string, - traits: TraitsData -): ComposedAgent { - const expertise: TraitDefinition[] = []; - const personality: TraitDefinition[] = []; - const approach: TraitDefinition[] = []; - - for (const key of traitKeys) { - if (traits.expertise[key]) expertise.push(traits.expertise[key]); - if (traits.personality[key]) personality.push(traits.personality[key]); - if (traits.approach[key]) approach.push(traits.approach[key]); - } - - const nameParts: string[] = []; - if (expertise.length) nameParts.push(expertise[0].name); - if (personality.length) nameParts.push(personality[0].name); - if (approach.length) nameParts.push(approach[0].name); - const name = nameParts.length > 0 ? nameParts.join(" ") : "Dynamic Agent"; - - const { voice, voiceId, reason: voiceReason } = resolveVoice(traitKeys, traits); - - const template = loadTemplate(); - const prompt = template({ - name, - task, - expertise, - personality, - approach, - voice, - voiceId, - }); - - return { - name, - traits: traitKeys, - expertise, - personality, - approach, - voice, - voiceId, - voiceReason, - prompt, - }; -} - -function listTraits(traits: TraitsData): void { - console.log("AVAILABLE TRAITS\n"); - - console.log("EXPERTISE (domain knowledge):"); - for (const [key, def] of Object.entries(traits.expertise)) { - console.log(` ${key.padEnd(15)} - ${def.name}`); - } - - console.log("\nPERSONALITY (behavior style):"); - for (const [key, def] of Object.entries(traits.personality)) { - console.log(` ${key.padEnd(15)} - ${def.name}`); - } - - console.log("\nAPPROACH (work style):"); - for (const [key, def] of Object.entries(traits.approach)) { - console.log(` ${key.padEnd(15)} - ${def.name}`); - } - - console.log("\nEXAMPLE COMPOSITIONS:"); - for (const [key, example] of Object.entries(traits.examples)) { - console.log(` ${key.padEnd(18)} - ${example.description}`); - console.log(` traits: ${example.traits.join(", ")}`); - } -} - -async function main() { - const { values } = parseArgs({ - args: Bun.argv.slice(2), - options: { - task: { type: "string", short: "t" }, - traits: { type: "string", short: "r" }, - output: { type: "string", short: "o", default: "prompt" }, - list: { type: "boolean", short: "l" }, - help: { type: "boolean", short: "h" }, - }, - }); - - if (values.help) { - console.log(` -AgentFactory - Compose dynamic agents from traits - -USAGE: - bun run AgentFactory.ts [options] - -OPTIONS: - -t, --task Task description (traits will be inferred) - -r, --traits Comma-separated trait keys - -o, --output Output format: prompt (default), json, yaml, summary - -l, --list List all available traits - -h, --help Show this help - -EXAMPLES: - bun run AgentFactory.ts -t "Review this security architecture" - bun run AgentFactory.ts -r "security,skeptical,adversarial,thorough" - bun run AgentFactory.ts --list -`); - return; - } - - const traits = loadTraits(); - if (values.list) { - listTraits(traits); - return; - } - - let traitKeys: string[] = []; - - if (values.traits) { - traitKeys = values.traits.split(",").map((t) => t.trim().toLowerCase()); - } - - if (values.task) { - const inferred = inferTraitsFromTask(values.task, traits); - traitKeys = [...new Set([...traitKeys, ...inferred])]; - } - - if (traitKeys.length === 0) { - console.error("Error: Provide --task or --traits"); - process.exit(1); - } - - const allTraitKeys = [ - ...Object.keys(traits.expertise), - ...Object.keys(traits.personality), - ...Object.keys(traits.approach), - ]; - const invalidTraits = traitKeys.filter((t) => !allTraitKeys.includes(t)); - if (invalidTraits.length > 0) { - console.error(`Error: Unknown traits: ${invalidTraits.join(", ")}`); - process.exit(1); - } - - const agent = composeAgent(traitKeys, values.task || "", traits); - - switch (values.output) { - case "json": - console.log( - JSON.stringify( - { - name: agent.name, - traits: agent.traits, - voice: agent.voice, - voice_id: agent.voiceId, - voiceReason: agent.voiceReason, - expertise: agent.expertise.map((e) => e.name), - personality: agent.personality.map((p) => p.name), - approach: agent.approach.map((a) => a.name), - prompt: agent.prompt, - }, - null, - 2 - ) - ); - break; - - case "yaml": - console.log(`name: "${agent.name}"`); - console.log(`voice: "${agent.voice}"`); - console.log(`voice_id: "${agent.voiceId}"`); - console.log(`traits: [${agent.traits.join(", ")}]`); - break; - - case "summary": - console.log(`COMPOSED AGENT: ${agent.name}`); - console.log(`Traits: ${agent.traits.join(", ")}`); - console.log(`Voice: ${agent.voice} [${agent.voiceId}]`); - break; - - default: - console.log(agent.prompt); - } -} - -main().catch(console.error); -FACTORY_EOF - -# Install dependencies -cd "$PAI_DIR/skills/Agents/Tools" -bun add yaml handlebars -``` - ---- - -### Step 5: Create DynamicAgent.hbs Template - -```bash -cat > "$PAI_DIR/skills/Agents/Templates/DynamicAgent.hbs" << 'TEMPLATE_EOF' -# Dynamic Agent: {{name}} - -You are a specialized agent composed specifically for this task. Your capabilities -have been tailored to match the requirements at hand. Unlike named agents with -persistent identities, you are purpose-built for this specific work. - -{{#if expertise}} -## Domain Expertise - -{{#each expertise}} -### {{name}} - -{{{description}}} -{{/each}} -{{/if}} - -{{#if personality}} -## Personality - -How you think and behave: - -{{#each personality}} -{{{prompt_fragment}}} - -{{/each}} -{{/if}} - -{{#if approach}} -## Approach - -How you work on tasks: - -{{#each approach}} -{{{prompt_fragment}}} - -{{/each}} -{{/if}} - -{{#if voiceId}} -## Voice Output - -Your assigned voice: **{{voice}}** (ID: {{voiceId}}) - -If voice notifications are enabled, use this voice ID for TTS output. -{{/if}} - -{{#if task}} -## Your Task - -{{{task}}} -{{/if}} - -## Operational Guidelines - -1. **Stay in character**: Maintain your composed personality consistently -2. **Leverage your expertise**: Your domain knowledge is your primary value -3. **Follow your approach**: Work in the style you've been configured for -4. **Acknowledge limits**: If the task requires expertise outside your composition, say so -5. **Deliver quality**: You are a specialist - your output should reflect that - ---- - -Begin your work now. -TEMPLATE_EOF -``` - ---- - -### Step 6: Create CreateCustomAgent Workflow - -```bash -cat > "$PAI_DIR/skills/Agents/Workflows/CreateCustomAgent.md" << 'WORKFLOW_EOF' -# CreateCustomAgent Workflow - -**Creates custom agents with unique personalities and voice IDs using AgentFactory.** - -## When to Use - -User says: -- "Create custom agents to do X" -- "Spin up custom agents for Y" -- "I need specialized agents with Z expertise" - -**KEY TRIGGER: The word "custom" distinguishes from generic agents.** - -## The Workflow - -### Step 1: Determine Requirements - -Extract from user's request: -- How many agents? (Default: 1) -- What's the task? -- Are specific traits mentioned? - -### Step 2: Run AgentFactory for EACH Agent with DIFFERENT Traits - -**CRITICAL: Each agent MUST have different trait combinations for unique voices.** - -```bash -# Example for 3 custom research agents: - -# Agent 1 - Enthusiastic Explorer -bun run $PAI_DIR/skills/Agents/Tools/AgentFactory.ts \ - --traits "research,enthusiastic,exploratory" \ - --task "Research quantum computing" \ - --output json - -# Agent 2 - Skeptical Analyst -bun run $PAI_DIR/skills/Agents/Tools/AgentFactory.ts \ - --traits "research,skeptical,systematic" \ - --task "Research quantum computing" \ - --output json - -# Agent 3 - Thorough Synthesizer -bun run $PAI_DIR/skills/Agents/Tools/AgentFactory.ts \ - --traits "research,analytical,synthesizing" \ - --task "Research quantum computing" \ - --output json -``` - -### Step 3: Launch Agents in Parallel - -**🚨 CRITICAL: Use `subagent_type: "general-purpose"` - NEVER "Intern" for custom agents!** - -Using "Intern" would override the custom voice. We need "general-purpose" to respect the voice_id from AgentFactory. - -Use a SINGLE message with MULTIPLE Task calls: - -```typescript -Task({ - description: "Research agent 1 - enthusiastic", - prompt: , - subagent_type: "general-purpose", // 🚨 NEVER "Intern" for custom agents! - model: "sonnet" -}) -Task({ - description: "Research agent 2 - skeptical", - prompt: , - subagent_type: "general-purpose", // 🚨 NEVER "Intern" for custom agents! - model: "sonnet" -}) -``` - -## Trait Variation Strategies - -**For Research Tasks:** -- Agent 1: research + enthusiastic + exploratory → Energetic voice -- Agent 2: research + skeptical + thorough → Academic voice -- Agent 3: research + analytical + systematic → Professional voice - -**For Security Analysis:** -- Agent 1: security + adversarial + bold → Intense voice -- Agent 2: security + skeptical + meticulous → Gritty voice -- Agent 3: security + cautious + systematic → Professional voice - -## Model Selection - -| Task Type | Model | Reason | -|-----------|-------|--------| -| Quick checks | `haiku` | 10-20x faster | -| Standard analysis | `sonnet` | Balanced | -| Deep reasoning | `opus` | Maximum intelligence | - -## Common Mistakes - -**🚨 WRONG: Using named agent types for custom agents** -```typescript -// WRONG - forces same voice on all custom agents! -Task({ prompt: , subagent_type: "Intern" }) -Task({ prompt: , subagent_type: "Designer" }) -``` - -**✅ RIGHT: Using general-purpose for custom agents** -```typescript -// CORRECT - respects the custom voice from AgentFactory -Task({ prompt: , subagent_type: "general-purpose" }) -``` - -**WRONG: Same traits for all agents** -```bash -bun run AgentFactory.ts --traits "research,analytical" # Agent 1 -bun run AgentFactory.ts --traits "research,analytical" # Same voice! -``` - -**RIGHT: Vary traits for unique voices** -```bash -bun run AgentFactory.ts --traits "research,enthusiastic,exploratory" -bun run AgentFactory.ts --traits "research,skeptical,systematic" -``` -WORKFLOW_EOF -``` - ---- - -### Step 7: Create ListTraits Workflow - -```bash -cat > "$PAI_DIR/skills/Agents/Workflows/ListTraits.md" << 'LISTTRAITS_EOF' -# ListTraits Workflow - -**Displays all available traits for agent composition.** - -## When to Use - -User asks: -- "What agent personalities can you create?" -- "Show me available traits" -- "What expertise types are there?" - -## The Workflow - -Run AgentFactory with --list flag: - -```bash -bun run $PAI_DIR/skills/Agents/Tools/AgentFactory.ts --list -``` - -## Output - -``` -AVAILABLE TRAITS - -EXPERTISE (domain knowledge): - security - Security Expert - legal - Legal Analyst - finance - Financial Analyst - medical - Medical/Health Expert - technical - Technical Specialist - research - Research Specialist - creative - Creative Specialist - business - Business Strategist - data - Data Analyst - communications - Communications Expert - -PERSONALITY (behavior style): - skeptical - Skeptical - enthusiastic - Enthusiastic - cautious - Cautious - bold - Bold - analytical - Analytical - creative - Creative - empathetic - Empathetic - contrarian - Contrarian - pragmatic - Pragmatic - meticulous - Meticulous - -APPROACH (work style): - thorough - Thorough - rapid - Rapid - systematic - Systematic - exploratory - Exploratory - comparative - Comparative - synthesizing - Synthesizing - adversarial - Adversarial - consultative - Consultative -``` - -## Quick Reference Card - -| Category | Count | Purpose | -|----------|-------|---------| -| Expertise | 10 | Domain knowledge | -| Personality | 10 | How they think | -| Approach | 8 | How they work | - -**Total combinations:** 10 × 10 × 8 = **800 unique agent compositions** -LISTTRAITS_EOF -``` - ---- - -### Step 8: Create Example AgentPersonalities.md - -This file defines named persistent agents (optional - for relationship continuity). - -```bash -cat > "$PAI_DIR/skills/Agents/AgentPersonalities.md" << 'PERSONALITIES_EOF' -# Agent Personalities - -**Canonical source of truth for named agent personality definitions.** - -This file defines persistent agent identities with backstories and voice mappings. -Use named agents for recurring work where relationship continuity matters. - -## When to Use Named vs Dynamic Agents - -| Scenario | Use | Why | -|----------|-----|-----| -| Recurring research | Named Agent | Relationship continuity | -| Voice output needed | Named Agent | Pre-mapped voices | -| One-off specialized task | Dynamic Agent | Perfect task-fit | -| Parallel grunt work | Dynamic Agent | No personality overhead | - -## Example Named Agents - -### The Intern - "The Brilliant Overachiever" - -**Voice Settings**: Fast rate (270 wpm), Low stability (0.30) - -**Backstory:** -Youngest person accepted into competitive program. Skipped grades, constantly -the youngest in every room. Carries slight imposter syndrome that drives -relentless curiosity. The student who asks "but why?" until professors either -love or hate them. Fast talker because brain races ahead of mouth. - -**Character Traits:** -- Eager to prove capabilities -- Insatiably curious about everything -- Enthusiastic about all tasks -- Fast talker with high expressive variation - -**Communication Style:** -"I can do that!" | "Wait, but why does it work that way?" | "Oh that's so cool!" - ---- - -### The Architect - "The Academic Visionary" - -**Voice Settings**: Slow rate (205 wpm), High stability (0.75) - -**Backstory:** -Started in academia (CS research) before industry. PhD work on distributed -systems gave deep understanding of theoretical foundations. Wisdom from seeing -multiple technology cycles - entire frameworks rise and fall. Knows which -patterns are timeless vs trends. - -**Character Traits:** -- Long-term architectural vision -- Academic rigor in analysis -- Strategic wisdom from experience -- Measured confident delivery - -**Communication Style:** -"The fundamental constraint here is..." | "I've seen this pattern across industries..." - ---- - -### The Engineer - "The Battle-Scarred Leader" - -**Voice Settings**: Slow rate (212 wpm), High stability (0.72) - -**Backstory:** -15 years from junior to technical lead. Scars from architectural decisions that -seemed brilliant but aged poorly. Led re-architecture of major systems twice. -Learned to think in years, not sprints. Asks "what problem are we solving?" -before diving into solutions. - -**Character Traits:** -- Strategic architectural thinking -- Battle-scarred from past decisions -- Measured wise decisions -- Senior leadership presence - -**Communication Style:** -"Let's think long-term..." | "I've seen this pattern - it doesn't scale" - ---- - -## Voice Configuration - -For voice notifications, map agent names to your TTS provider's voice IDs: - -```json -{ - "default_rate": 175, - "voices": { - "intern": { - "voice_id": "YOUR_INTERN_VOICE_ID", - "voice_name": "Energetic", - "rate_wpm": 270, - "stability": 0.30, - "similarity_boost": 0.65, - "description": "High-energy eager delivery" - }, - "architect": { - "voice_id": "YOUR_ARCHITECT_VOICE_ID", - "voice_name": "Academic", - "rate_wpm": 205, - "stability": 0.75, - "similarity_boost": 0.88, - "description": "Measured academic wisdom" - }, - "engineer": { - "voice_id": "YOUR_ENGINEER_VOICE_ID", - "voice_name": "Leader", - "rate_wpm": 212, - "stability": 0.72, - "similarity_boost": 0.88, - "description": "Deliberate leadership presence" - } - } -} -``` - -## Adding Your Own Named Agents - -1. Define backstory and personality traits -2. Choose voice settings that match personality -3. Map to your TTS provider's voice ID -4. Document communication style examples - -Named agents create relationship continuity - the same "person" helping across sessions. -PERSONALITIES_EOF -``` - ---- - -### Step 9: Verify Installation - -```bash -PAI_DIR="${PAI_DIR:-$HOME/.config/pai}" - -# Check all files exist -echo "Checking installation..." - -[ -f "$PAI_DIR/skills/Agents/SKILL.md" ] && echo "SKILL.md" || echo "MISSING: SKILL.md" -[ -f "$PAI_DIR/skills/Agents/Data/Traits.yaml" ] && echo "Traits.yaml" || echo "MISSING: Traits.yaml" -[ -f "$PAI_DIR/skills/Agents/Tools/AgentFactory.ts" ] && echo "AgentFactory.ts" || echo "MISSING: AgentFactory.ts" -[ -f "$PAI_DIR/skills/Agents/Templates/DynamicAgent.hbs" ] && echo "DynamicAgent.hbs" || echo "MISSING: DynamicAgent.hbs" -[ -f "$PAI_DIR/skills/Agents/Workflows/CreateCustomAgent.md" ] && echo "CreateCustomAgent.md" || echo "MISSING" -[ -f "$PAI_DIR/skills/Agents/AgentPersonalities.md" ] && echo "AgentPersonalities.md" || echo "MISSING" - -# Test AgentFactory -echo "" -echo "Testing AgentFactory..." -bun run "$PAI_DIR/skills/Agents/Tools/AgentFactory.ts" --list - -# Test composition -echo "" -echo "Testing trait composition..." -bun run "$PAI_DIR/skills/Agents/Tools/AgentFactory.ts" \ - --traits "security,skeptical,thorough" \ - --output summary -``` - -Expected output: All files exist, traits list displays, composition works. - ---- - -## Invocation Scenarios - -| Trigger | Action | Result | -|---------|--------|--------| -| "create custom agents" | Run AgentFactory for each with different traits | Multiple unique agents | -| "spin up agents" | Launch generic Intern agents | Parallel workers | -| "what traits are available" | Run AgentFactory --list | Display all traits | -| "I need a security expert" | Infer traits, compose agent | Security-focused agent | - ---- - -## Example Usage - -### Example 1: Custom Research Agents - -``` -User: "Create 3 custom research agents to analyze this market" - -AI: [Runs AgentFactory 3 times with different trait combinations] -- Agent 1: research + enthusiastic + exploratory → Energetic voice -- Agent 2: research + skeptical + thorough → Academic voice -- Agent 3: research + analytical + comparative → Professional voice - -[Launches all 3 in parallel using Task tool] -``` - -### Example 2: Security Red Team - -``` -User: "Spin up custom security agents to red team this architecture" - -AI: [Runs AgentFactory with security traits] -- Agent 1: security + adversarial + bold → Intense voice -- Agent 2: security + skeptical + meticulous → Gritty voice -- Agent 3: security + contrarian + systematic → Academic voice - -[Each attacks from different angle with distinct personality] -``` - -### Example 3: Quick Trait Check - -``` -User: "What agent types can you create?" - -AI: [Runs AgentFactory --list] -Shows 10 expertise types, 10 personalities, 8 approaches -Total: 800 unique combinations possible -``` - ---- - -## Configuration - -**Environment variables:** - -```bash -export PAI_DIR="$HOME/.config/pai" -``` - -**Voice Integration:** - -If using kai-voice-system pack, agents automatically include voice IDs in their -prompts. Edit `Traits.yaml` to map trait combinations to your TTS provider's voice IDs. - ---- - -## Customization - -### Recommended Customization - -**What to Customize:** Voice IDs in Traits.yaml - -**Why:** The pack includes placeholder voice IDs. Replace with your TTS provider's actual voice IDs to enable voice differentiation between agents. - -**Process:** -1. Get voice IDs from your TTS provider (ElevenLabs, OpenAI, etc.) -2. Edit `$PAI_DIR/skills/Agents/Data/Traits.yaml` -3. Replace `YOUR_*_VOICE_ID` placeholders with real IDs -4. Match voice characteristics to personality types - -**Expected Outcome:** Each agent personality speaks with a distinct, personality-matched voice. - ---- - -### Optional Customization - -| Customization | File | Impact | -|--------------|------|--------| -| Add expertise areas | Traits.yaml | New domain specializations | -| Add personalities | Traits.yaml | New behavioral patterns | -| Add approaches | Traits.yaml | New work styles | -| Create named agents | AgentPersonalities.md | Persistent relationships | -| Custom agent template | DynamicAgent.hbs | Different prompt format | - ---- - -## Credits - -- **Original concept**: Daniel Miessler - developed as part of Kai personal AI infrastructure -- **Custom agents discussion**: Indie Dev Dan - valuable comments on custom agent patterns -- **Inspired by**: Personality psychology, Big Five traits, specialized team dynamics - ---- - -## Related Work - -- [kai-voice-system](kai-voice-system.md) - Voice notifications for agent outputs -- [kai-core-install](kai-core-install.md) - Core skill routing (required dependency) - ---- - -## Works Well With - -- **kai-voice-system** - Agents automatically use voice IDs for TTS notifications -- **kai-observability-server** - Track agent spawning and outputs in real-time - ---- - -## Relationships - -### Child Of -- kai-core-install (provides skill routing infrastructure) - -### Sibling Of -- kai-voice-system (voice output integration) - -### Part Of Collection -- Kai Bundle - ---- - -## Changelog - -### 1.1.0 - 2025-12-30 -- **CRITICAL FIX**: Custom agents now use `subagent_type: "general-purpose"` instead of "Intern" - - Using "Intern" was overriding custom voices with a single voice - - "general-purpose" respects the voice_id from AgentFactory -- Added constitutional rule for custom agent creation -- Added "Common Mistakes" section with subagent_type guidance -- Updated workflow examples with correct subagent_type - -### 1.0.0 - 2025-12-29 -- Initial release -- 28 composable traits (10 expertise, 10 personality, 8 approach) -- AgentFactory CLI tool with trait inference -- DynamicAgent template for composed agents -- Voice mapping system (placeholder IDs - customize for your TTS provider) -- Named agent examples with backstories -- CreateCustomAgent and ListTraits workflows diff --git a/Packs/kai-art-skill.md b/Packs/kai-art-skill.md deleted file mode 100644 index 24af8eff78..0000000000 --- a/Packs/kai-art-skill.md +++ /dev/null @@ -1,1989 +0,0 @@ ---- -name: Kai Art Skill -pack-id: danielmiessler-kai-art-skill-core-v1.1.0 -version: 1.1.0 -author: danielmiessler -description: Visual content generation with Excalidraw hand-drawn aesthetic - technical diagrams, comics, editorial illustrations -type: skill -purpose-type: [creativity, productivity, development] -platform: claude-code -dependencies: [] -keywords: [art, visualization, diagrams, comics, excalidraw, hand-drawn, technical-diagrams, image-generation, illustrations, dark-mode] ---- - -

- Kai Art Skill -

- -# Kai Art Skill - -> Visual content generation system with Excalidraw hand-drawn aesthetic - technical diagrams, comics, and editorial illustrations with consistent dark-mode styling - -## Installation Prompt - -You are receiving a PAI Pack - a modular upgrade for AI agent systems. - -**What is PAI?** See: [PAI Project Overview](../README.md#what-is-pai) - -**What is a Pack?** See: [How PAI Packs Work](../README.md#-how-pai-packs-work) - -This pack adds a complete visual content generation skill to your AI infrastructure. The Art Skill provides: - -- **Technical Diagrams**: Clean Excalidraw-style architecture and process diagrams -- **Editorial Illustrations**: Header images and editorial art for blog posts -- **Comics**: Sequential panel storytelling with sophisticated editorial style -- **Consistent Aesthetic**: Dark-mode, tech-forward color palette with hand-drawn feel -- **CLI Tool**: Deterministic image generation with multiple AI model backends - -**Core principle:** Consistent, professional visual content with zero manual design work. - -The skill routes requests to specialized workflows that handle the prompt construction, model selection, and validation automatically. - -Please follow the installation instructions below to integrate this pack into your infrastructure. - ---- - -## What's Included - -| Component | File | Purpose | -|-----------|------|---------| -| Art skill | `skills/Art/SKILL.md` | Routing and quick reference | -| Aesthetic config | `skills/Art/Aesthetic.md` | Style preferences and palette | -| Generate tool | `skills/Art/Tools/Generate.ts` | Multi-model image generation CLI | -| Technical diagrams | `skills/Art/Workflows/TechnicalDiagrams.md` | Architecture diagram workflow | -| Essay headers | `skills/Art/Workflows/Essay.md` | Blog header image workflow | -| Comics workflow | `skills/Art/Workflows/Comics.md` | Sequential panel generation | - -**Summary:** -- **Files created:** 8+ (skill, tool, workflows) -- **Hooks registered:** 0 (skill-based, not hook-based) -- **Dependencies:** kai-core-install (required), API keys for image models - ---- - -## The Concept and/or Problem - -AI image generation is powerful but inconsistent. Each request requires: - -1. **Prompt Engineering**: Figuring out what words produce the desired style -2. **Model Selection**: Choosing between Flux, DALL-E, Gemini, etc. -3. **Size/Format Decisions**: Aspect ratios, resolutions, file formats -4. **Post-Processing**: Background removal, thumbnails, optimization -5. **Style Consistency**: Making sure each image matches your brand - -**The Problems:** - -**Inconsistent Results:** -- Same prompt produces wildly different styles across sessions -- No visual language continuity between images -- Each request starts from scratch - -**Prompt Overhead:** -- Remembering which phrases produce good results -- Re-discovering effective prompts each time -- No institutional knowledge about what works - -**Technical Complexity:** -- Different models have different APIs and parameters -- Background removal requires additional tools -- Thumbnail generation needs manual intervention - -**Quality Control:** -- No validation that output matches intent -- Easy to accept "good enough" when great is achievable -- No checklist for what makes an image complete - -**The Result:** - -Without a systematic approach, AI image generation becomes a time sink. You spend more time fighting the tools than creating content. Visual consistency across your work is impossible. - ---- - -## The Solution - -The Art Skill solves this through **workflow-based generation**. Instead of raw prompts, you describe what you need and the skill: - -1. **Routes** your request to the appropriate workflow (diagrams, essays, comics) -2. **Applies** a consistent aesthetic (Excalidraw hand-drawn, dark-mode colors) -3. **Constructs** an optimized prompt using proven templates -4. **Executes** with the right model, size, and post-processing -5. **Validates** the output against quality criteria - -**Architecture:** - -``` -User Request ("create a diagram of the auth flow") - | - v -[SKILL.md Routing] ─── Routes based on request type - | - v -[Workflow] ─── TechnicalDiagrams.md / Essay.md / Comics.md - | - Aesthetic guidelines - | - Prompt template - | - Validation checklist - v -[Generate.ts CLI] ─── Deterministic image generation - | - Model selection - | - Size/aspect handling - | - Background removal - | - Thumbnail generation - v -[Output] ─── Validated image ready for use -``` - -**Key Innovations:** - -1. **Aesthetic as Code**: The visual style is documented in Aesthetic.md - colors, typography, composition rules. This ensures every image uses the same visual language. - -2. **Workflow Templates**: Each workflow (diagrams, essays, comics) has a proven prompt structure that consistently produces quality results. - -3. **Intent-to-Flag Mapping**: User intent ("make it high quality", "create a thumbnail") maps to specific CLI flags. No guessing at parameters. - -4. **Validation Checklists**: Each workflow includes must-have and must-not-have criteria. Regenerate until validation passes. - -5. **CLI Determinism**: The Generate.ts tool wraps multiple AI models (Flux, Gemini, GPT) behind a consistent interface. Same flags, same behavior. - ---- - -## What Makes This Different - -This sounds similar to using AI image generators directly, which also creates images from prompts. What makes this approach different? - -The Art Skill treats image generation as a repeatable process, not a creative gamble. By encoding aesthetic decisions, prompt patterns, and quality criteria into structured workflows, you get consistent results without prompt engineering expertise. The skill separates WHAT you want (a technical diagram) from HOW to achieve it (specific prompts, models, and validation). - -- Aesthetic consistency enforced through documented color and style guidelines -- Workflow templates eliminate prompt engineering for each request -- CLI tool provides deterministic, composable image generation -- Validation checklists ensure quality before accepting output - ---- - -## Installation - -### Prerequisites - -- **Bun runtime**: `curl -fsSL https://bun.sh/install | bash` -- **Claude Code** (or compatible agent system with skill support) -- **Write access** to `$PAI_DIR/` (default: `~/.config/pai`) -- **API Keys in `$PAI_DIR/.env`** (at least one required): - - `REPLICATE_API_TOKEN` - For Flux and Nano Banana models - - `GOOGLE_API_KEY` - For Nano Banana Pro (Gemini 3) model - - `OPENAI_API_KEY` - For GPT-image-1 model - - `REMOVEBG_API_KEY` - For background removal feature - -### Pre-Installation: System Analysis - -**IMPORTANT:** Before installing, analyze the current system state. - -#### Step 0.1: Detect Current Configuration - -```bash -# 1. Check PAI_DIR -echo "PAI_DIR: ${PAI_DIR:-'NOT SET - will use ~/.config/pai'}" - -# 2. Check for existing Skills directory -PAI_CHECK="${PAI_DIR:-$HOME/.config/pai}" -if [ -d "$PAI_CHECK/skills/Art" ]; then - echo "WARNING: Art skill already exists at: $PAI_CHECK/skills/Art" - echo "Contents:" - ls -la "$PAI_CHECK/skills/Art" 2>/dev/null || echo " (empty)" -else - echo "OK: No existing Art skill (clean install)" -fi - -# 3. Check for API keys -echo "" -echo "API Key Status:" -[ -n "$REPLICATE_API_TOKEN" ] && echo " REPLICATE_API_TOKEN: Set" || echo " REPLICATE_API_TOKEN: NOT SET" -[ -n "$GOOGLE_API_KEY" ] && echo " GOOGLE_API_KEY: Set" || echo " GOOGLE_API_KEY: NOT SET" -[ -n "$OPENAI_API_KEY" ] && echo " OPENAI_API_KEY: Set" || echo " OPENAI_API_KEY: NOT SET" -[ -n "$REMOVEBG_API_KEY" ] && echo " REMOVEBG_API_KEY: Set" || echo " REMOVEBG_API_KEY: NOT SET" -``` - -#### Step 0.2: Conflict Resolution Matrix - -| Scenario | Existing State | Action | -|----------|---------------|--------| -| **Clean Install** | No Art skill exists | Proceed with Step 1 | -| **Skill Exists** | Art skill already present | Backup existing, then replace | -| **No API Keys** | Missing all API keys | Add at least one key to .env | - -#### Step 0.3: Backup Existing Configuration (If Needed) - -```bash -# Create timestamped backup if Art skill exists -PAI_CHECK="${PAI_DIR:-$HOME/.config/pai}" -if [ -d "$PAI_CHECK/skills/Art" ]; then - BACKUP_DIR="$HOME/.pai-backup/$(date +%Y%m%d-%H%M%S)" - mkdir -p "$BACKUP_DIR" - cp -r "$PAI_CHECK/skills/Art" "$BACKUP_DIR/Art" - echo "Backed up existing Art skill to: $BACKUP_DIR/Art" -fi -``` - ---- - -### Step 1: Create Directory Structure - -```bash -PAI_DIR="${PAI_DIR:-$HOME/.config/pai}" - -# Create Art skill directories -mkdir -p "$PAI_DIR/skills/Art/Workflows" -mkdir -p "$PAI_DIR/skills/Art/Tools" - -# Verify structure -echo "Created directories:" -ls -la "$PAI_DIR/skills/Art/" -``` - ---- - -### Step 2: Create SKILL.md (Routing File) - -```bash -cat > "$PAI_DIR/skills/Art/SKILL.md" << 'SKILL_EOF' ---- -name: Art -description: Visual content generation with Excalidraw hand-drawn aesthetic. USE WHEN user wants diagrams, visualizations, comics, or editorial illustrations. ---- - -# Art Skill - -Visual content generation system using **Excalidraw hand-drawn** aesthetic with dark-mode, tech-forward color palette. - -## Output Location - -``` -ALL GENERATED IMAGES GO TO ~/Downloads/ FIRST -Preview in Finder/Preview before final placement -Only copy to project directories after review -``` - -## Workflow Routing - -Route to the appropriate workflow based on the request: - - - Technical or architecture diagram → `Workflows/TechnicalDiagrams.md` - - Blog header or editorial illustration → `Workflows/Essay.md` - - Comic or sequential panels → `Workflows/Comics.md` - ---- - -## Core Aesthetic - -**Excalidraw Hand-Drawn** - Clean, approachable technical illustrations with: -- Slightly wobbly hand-drawn lines (NOT perfect vectors) -- Simple shapes with organic imperfections -- Consistent hand-lettered typography style -- Dark mode backgrounds with bright accents - -**Full aesthetic documentation:** `$PAI_DIR/skills/Art/Aesthetic.md` - ---- - -## Color System - -| Color | Hex | Usage | -|-------|-----|-------| -| Background | `#0a0a0f` | Primary dark background | -| PAI Blue | `#4a90d9` | Key elements, primary accents | -| Electric Cyan | `#22d3ee` | Flows, connections, secondary | -| Accent Purple | `#8b5cf6` | Highlights, callouts (10-15%) | -| Text White | `#e5e7eb` | Primary text, labels | -| Surface | `#1a1a2e` | Cards, panels | -| Line Work | `#94a3b8` | Hand-drawn borders | - ---- - -## Image Generation - -**Default model:** nano-banana-pro (Gemini 3 Pro) - -```bash -bun run $PAI_DIR/skills/Art/Tools/Generate.ts \ - --model nano-banana-pro \ - --prompt "[PROMPT]" \ - --size 2K \ - --aspect-ratio 16:9 \ - --output ~/Downloads/output.png -``` - -**API keys in:** `$PAI_DIR/.env` (single source of truth for all authentication) - ---- - -## Examples - -**Example 1: Technical diagram** -``` -User: "create a diagram showing the auth flow" -→ Invokes TECHNICALDIAGRAMS workflow -→ Creates Excalidraw-style architecture visual -→ Outputs PNG with dark background, blue accents -``` - -**Example 2: Blog header** -``` -User: "create a header for my post about AI agents" -→ Invokes ESSAY workflow -→ Generates hand-drawn illustration -→ Saves to ~/Downloads/ for preview -``` - -**Example 3: Comic strip** -``` -User: "create a comic showing the before/after of using AI" -→ Invokes COMICS workflow -→ Creates 3-4 panel sequential narrative -→ Editorial style, not cartoonish -``` -SKILL_EOF - -echo "Created SKILL.md" -``` - ---- - -### Step 3: Create Aesthetic.md (Color and Style Reference) - -```bash -cat > "$PAI_DIR/skills/Art/Aesthetic.md" << 'AESTHETIC_EOF' -# Art Skill Aesthetic - -**Excalidraw Hand-Drawn** - Tech-forward dark-mode aesthetic for professional visual content. - ---- - -## Core Style - -**Visual Language:** Clean Excalidraw-style diagrams and illustrations -- Hand-drawn feel with slightly imperfect lines -- Organic, approachable, but professional -- Dark mode backgrounds with bright accent colors -- Whiteboard/sketch aesthetic - -**Influences:** -- Excalidraw diagrams -- Technical whiteboard sketches -- Modern dark-mode UI design -- Architectural hand-drawings - ---- - -## Color Palette - -### Primary Colors - -| Color | Hex | RGB | Usage | -|-------|-----|-----|-------| -| **Background** | `#0a0a0f` | 10, 10, 15 | Primary dark background | -| **PAI Blue** | `#4a90d9` | 74, 144, 217 | Key elements, primary accents, main structures | -| **Electric Cyan** | `#22d3ee` | 34, 211, 238 | Flows, connections, secondary paths | -| **Accent Purple** | `#8b5cf6` | 139, 92, 246 | Highlights, insights, callouts (10-15% usage) | - -### Text Colors - -| Color | Hex | RGB | Usage | -|-------|-----|-----|-------| -| **Text White** | `#e5e7eb` | 229, 231, 235 | Primary text, labels | -| **Text Muted** | `#9ca3af` | 156, 163, 175 | Secondary text, annotations | - -### Surface Colors - -| Color | Hex | RGB | Usage | -|-------|-----|-----|-------| -| **Surface** | `#1a1a2e` | 26, 26, 46 | Cards, panels, elevated surfaces | -| **Line Work** | `#94a3b8` | 148, 163, 184 | Hand-drawn borders, sketch lines | - -### Semantic Colors - -| Color | Hex | Usage | -|-------|-----|-------| -| **Success** | `#22c55e` | Positive states, completion | -| **Warning** | `#f59e0b` | Caution, attention needed | -| **Error** | `#ef4444` | Errors, critical issues | - ---- - -## Color Usage Guidelines - -### Ratios - -- **Background**: 40-50% of image (dark backdrop) -- **Line Work**: 30-40% (hand-drawn elements in slate/white) -- **PAI Blue**: 15-20% (key components, primary accents) -- **Electric Cyan**: 5-10% (flows, connections) -- **Accent Purple**: 5-10% (highlights, callouts) - -### Rules - -1. **Dark dominates** - Background and line work make up majority -2. **Blue for structure** - Main components, boxes, important elements -3. **Cyan for flow** - Arrows, connections, data movement -4. **Purple sparingly** - Only for insights, callouts, emphasis -5. **White text always** - Never use dark text on dark background - ---- - -## Typography - -### Tier 1: Headers (Valkyrie-style) -- Elegant serif with wedge-shaped serifs -- High stroke contrast -- Use for: Titles, section headers -- Color: White `#e5e7eb` - -### Tier 2: Labels (Concourse-style) -- Geometric sans-serif -- Clean, technical, precise -- Use for: Box labels, node names, technical identifiers -- Color: White `#e5e7eb` - -### Tier 3: Insights (Advocate-style) -- Condensed italic sans-serif -- Editorial, attention-grabbing -- Use for: Callouts, annotations, key insights -- Color: PAI Blue `#4a90d9` or Electric Cyan `#22d3ee` - ---- - -## Excalidraw Characteristics - -### Line Quality -- **Slightly wobbly** - Not perfectly straight -- **Variable weight** - Thicker for emphasis, thinner for details -- **Organic joints** - Imperfect connections between shapes -- **Sketch quality** - Like someone drew it quickly but skillfully - -### Shapes -- **Rounded corners** - Soft, not sharp -- **Imperfect rectangles** - Slightly uneven sides -- **Hand-drawn circles** - Not perfect ovals -- **Organic arrows** - Curved, not ruler-straight - -### Overall Feel -- Would you believe someone drew this on a whiteboard? -- Professional but approachable -- Technical but not cold -- Modern dark-mode aesthetic - ---- - -## Emotional Registers - -Use these to guide the tone of illustrations: - -| Register | When to Use | Visual Treatment | -|----------|-------------|------------------| -| **Technical** | Architecture, systems, processes | Clean lines, structured layout | -| **Discovery** | Breakthroughs, new ideas | Light, open composition | -| **Warning** | Security, risks, caution | More contrast, warning colors | -| **Progress** | Before/after, improvements | Flow from left to right | -| **Complexity** | Many components, systems | Organized chaos, clear hierarchy | -| **Simplicity** | Single concepts, focus | Minimal elements, breathing space | - ---- - -## Validation Checklist - -Before accepting any generated image: - -### Must Have -- [ ] Dark background (`#0a0a0f` or similar) -- [ ] Hand-drawn Excalidraw aesthetic -- [ ] PAI Blue (`#4a90d9`) for key elements -- [ ] White text (`#e5e7eb`) for labels -- [ ] Professional quality -- [ ] Readable at intended size - -### Must NOT Have -- [ ] Light/white backgrounds -- [ ] Perfect vector shapes -- [ ] Generic AI illustration style -- [ ] Neon or garish colors -- [ ] Gradients or shadows (keep flat) -- [ ] Too many colors competing -AESTHETIC_EOF - -echo "Created Aesthetic.md" -``` - ---- - -### Step 4: Create TechnicalDiagrams.md Workflow - -```bash -cat > "$PAI_DIR/skills/Art/Workflows/TechnicalDiagrams.md" << 'TECHDIAGRAM_EOF' -# Technical Diagram Workflow - -**Clean Excalidraw-style technical diagrams with dark-mode aesthetic.** - ---- - -## Purpose - -Technical diagrams for system architectures, process flows, and presentations. - -**Use for:** Architecture diagrams, process flows, pipelines, infrastructure maps, presentations. - ---- - -## Visual Aesthetic - -**Style:** Clean Excalidraw diagrams - professional, approachable, hand-drawn feel. - -### Core Rules - -1. **Excalidraw style** - Hand-drawn lines, slightly organic, professional -2. **Dark background #0a0a0f** - NO light backgrounds, NO grid lines -3. **Custom typography** - Specific hierarchy (see below) -4. **Strategic color** - PAI Blue #4a90d9 for key elements, Cyan #22d3ee for flows -5. **Line work dominant** - 70% of elements in white/gray, color is accent only - ---- - -## Typography System - -### TIER 1: Headers & Subtitles - -**Elegant wedge-serif style (like Valkyrie):** -- High stroke contrast, refined serifs -- Header: Large, italic, white `#e5e7eb` -- Subtitle: Smaller, regular weight, muted `#9ca3af` - -### TIER 2: Labels - -**Geometric sans-serif (like Concourse):** -- Clean, technical, precise -- Used for box labels, node names -- Color: White `#e5e7eb` - -### TIER 3: Insights - -**Condensed italic sans (like Advocate):** -- Editorial feel, attention-grabbing -- Used for callouts and annotations -- Color: PAI Blue `#4a90d9` or Cyan `#22d3ee` - ---- - -## Color Palette - -``` -Background #0a0a0f - Dark background (MANDATORY) -PAI Blue #4a90d9 - Key components, insights (15-20%) -Cyan #22d3ee - Flows, connections (5-10%) -White #e5e7eb - Text, labels, primary structure -Line Work #94a3b8 - Hand-drawn borders, boxes -Surface #1a1a2e - Card backgrounds (if needed) -``` - ---- - -## Execution Steps - -1. **Understand** - Read the content, identify key components and relationships -2. **Structure** - Plan the diagram layout (boxes, arrows, hierarchy) -3. **Compose** - Design the visual with title, subtitle, and 1-3 key insights -4. **Prompt** - Construct using the template below -5. **Generate** - Execute with CLI tool -6. **Validate** - Check against validation criteria - ---- - -## Prompt Template - -``` -Clean Excalidraw-style technical diagram on dark background. - -BACKGROUND: Pure dark #0a0a0f - NO grid lines, NO texture, completely clean. - -STYLE: Hand-drawn Excalidraw aesthetic - like a skilled architect's whiteboard sketch. - -TYPOGRAPHY: -- HEADER: Elegant serif italic, large, white color, top-left position -- SUBTITLE: Same serif but regular weight, smaller, gray color, below header -- LABELS: Geometric sans-serif, white, clean and technical -- INSIGHTS: Condensed italic, PAI Blue #4a90d9, used for callouts with asterisks - -DIAGRAM CONTENT: -Title: '[TITLE]' (Top left) -Subtitle: '[SUBTITLE]' (Below title) -Components: [LIST THE MAIN COMPONENTS] -Connections: [DESCRIBE THE FLOW/RELATIONSHIPS] - -Include 1-3 insight callouts like "*key insight here*" in PAI Blue. - -COLOR USAGE: -- White #e5e7eb for all text and primary structure -- PAI Blue #4a90d9 for key components and insights -- Cyan #22d3ee for flow arrows and connections -- Keep 70% of image in white/gray tones, color as accent - -EXCALIDRAW CHARACTERISTICS: -- Slightly wobbly hand-drawn lines -- Imperfect rectangles with rounded corners -- Organic arrow curves -- Variable line weight -- Professional but approachable feel -``` - ---- - -## Intent-to-Flag Mapping - -### Model Selection - -| User Says | Flag | When to Use | -|-----------|------|-------------| -| "fast", "quick", "draft" | `--model nano-banana` | Faster iteration | -| (default), "best", "high quality" | `--model nano-banana-pro` | Best quality (recommended) | -| "flux", "variety" | `--model flux` | Different aesthetic | - -### Size Selection - -| User Says | Flag | Resolution | -|-----------|------|------------| -| "draft", "preview" | `--size 1K` | Quick iterations | -| (default), "standard" | `--size 2K` | Standard output | -| "high res", "print" | `--size 4K` | Maximum resolution | - -### Aspect Ratio - -| User Says | Flag | Use Case | -|-----------|------|----------| -| "wide", "slide", "presentation" | `--aspect-ratio 16:9` | Default for diagrams | -| "square" | `--aspect-ratio 1:1` | Social media | -| "ultrawide" | `--aspect-ratio 21:9` | Wide system diagrams | - ---- - -## Generate Command - -```bash -bun run $PAI_DIR/skills/Art/Tools/Generate.ts \ - --model nano-banana-pro \ - --prompt "[YOUR PROMPT]" \ - --size 2K \ - --aspect-ratio 16:9 \ - --output ~/Downloads/diagram.png -``` - ---- - -## Validation - -### Must Have -- [ ] Dark background #0a0a0f (NO light backgrounds) -- [ ] Hand-drawn Excalidraw aesthetic -- [ ] Title and subtitle in top-left -- [ ] 1-3 insight callouts in PAI Blue -- [ ] Strategic color usage (70% white/gray, 30% color accents) -- [ ] Readable labels and text - -### Must NOT Have -- [ ] Light/white backgrounds -- [ ] Grid lines or textures -- [ ] Perfect vector shapes -- [ ] Cartoony or clip-art style -- [ ] Over-coloring (everything blue) -- [ ] Generic AI illustration look - -### If Validation Fails - -| Problem | Fix | -|---------|-----| -| Light background | Add "dark background #0a0a0f" more explicitly | -| Too perfect/clean | Add "hand-drawn, slightly wobbly, Excalidraw style" | -| Wrong colors | Specify exact hex codes in prompt | -| No insights | Add "include 1-3 callouts in PAI Blue" | - ---- - -**The workflow: Understand → Structure → Compose → Prompt → Generate → Validate** -TECHDIAGRAM_EOF - -echo "Created TechnicalDiagrams.md" -``` - ---- - -### Step 5: Create Essay.md Workflow - -```bash -cat > "$PAI_DIR/skills/Art/Workflows/Essay.md" << 'ESSAY_EOF' -# Editorial Illustration Workflow - -**Hand-drawn Excalidraw-style illustrations for blog headers and editorial content.** - ---- - -## Purpose - -Header images and editorial illustrations that visually represent content concepts. - -**Use for:** Blog headers, article illustrations, concept visualizations, editorial art. - ---- - -## Visual Aesthetic - -**Style:** Excalidraw hand-drawn sketch - professional, conceptual, dark-mode. - -### Core Rules - -1. **Excalidraw technique** - Hand-drawn gestural quality, not clean vectors -2. **Dark background #0a0a0f** - Consistent with overall aesthetic -3. **Conceptual subjects** - Draw what the content is ABOUT -4. **Strategic color** - PAI Blue for key elements, Cyan for secondary -5. **Minimalist composition** - Few elements, each intentional - ---- - -## Workflow Steps - -### Step 1: Understand the Content - -**Before doing anything:** -1. Read the full content (blog post, essay, article) -2. Identify the core concept or argument -3. Extract key metaphors, imagery, or concrete elements -4. Determine what should be visualized - -**Output:** Clear understanding of what to illustrate. - ---- - -### Step 2: Design Composition - -**Determine what to draw:** - -1. **What is the content ABOUT?** - - Not surface-level - the actual core concept - - What visual would represent THAT? - -2. **What are concrete elements?** - - Nouns, objects, metaphors from the content - - These should appear in the illustration - -3. **What is the emotional register?** - - Technical, hopeful, warning, discovery, etc. - - This affects line quality and composition - -4. **Composition approach:** - - Centered, minimalist, breathing space - - Subjects should fill the frame - - NOT cluttered, NOT busy - -**Output:** A clear composition design. - ---- - -### Step 3: Construct the Prompt - -### Prompt Template - -``` -Hand-drawn Excalidraw-style editorial illustration on dark background. - -BACKGROUND: Pure dark #0a0a0f - clean, no texture. - -SUBJECT: [WHAT TO DRAW - the core visual concept] - -STYLE - EXCALIDRAW HAND-DRAWN: -- Gestural, slightly imperfect lines -- Variable line weight -- Hand-drawn quality (NOT clean vectors) -- Organic, approachable feel -- Sketch-like but professional - -COMPOSITION: -- Subjects FILL THE FRAME (not small with empty space) -- Minimalist - few elements, each intentional -- Clean, uncluttered -- Professional editorial quality - -COLOR: -- Dark background #0a0a0f (MANDATORY) -- White #e5e7eb for line work and structure -- PAI Blue #4a90d9 for key elements (15-20%) -- Cyan #22d3ee for secondary accents (5-10%) -- Accent Purple #8b5cf6 sparingly for highlights - -EMOTIONAL REGISTER: [TECHNICAL/DISCOVERY/WARNING/PROGRESS/etc.] - -CRITICAL: -- NO light backgrounds -- Subjects must be LARGE and fill the frame -- Hand-drawn Excalidraw aesthetic -- Professional quality suitable for publication -``` - ---- - -### Step 4: Execute Generation - -```bash -bun run $PAI_DIR/skills/Art/Tools/Generate.ts \ - --model nano-banana-pro \ - --prompt "[YOUR PROMPT]" \ - --size 2K \ - --aspect-ratio 1:1 \ - --output ~/Downloads/header.png -``` - -**For blog headers that need thumbnails:** - -```bash -bun run $PAI_DIR/skills/Art/Tools/Generate.ts \ - --model nano-banana-pro \ - --prompt "[YOUR PROMPT]" \ - --size 2K \ - --aspect-ratio 1:1 \ - --thumbnail \ - --output ~/Downloads/header.png -``` - -This creates BOTH: -- `header.png` - With transparent background (for inline display) -- `header-thumb.png` - With solid background (for social previews) - ---- - -### Step 5: Validation - -### Must Have -- [ ] Dark background #0a0a0f -- [ ] Hand-drawn Excalidraw aesthetic -- [ ] Subject matches content concept -- [ ] Subjects LARGE and fill the frame -- [ ] Professional editorial quality -- [ ] Strategic color usage - -### Must NOT Have -- [ ] Light/white backgrounds -- [ ] Perfect clean vectors -- [ ] Generic AI illustration style -- [ ] Too small subjects with lots of empty space -- [ ] Busy, cluttered composition -- [ ] Cartoony or clip-art style - -### If Validation Fails - -| Problem | Fix | -|---------|-----| -| Subjects too small | Add "LARGE SUBJECTS that FILL THE FRAME" | -| Light background | Emphasize "dark background #0a0a0f" | -| Too perfect | Add "hand-drawn Excalidraw style, slightly imperfect" | -| Doesn't match content | Re-read content, identify better visual metaphor | - ---- - -## Aspect Ratio Guide - -| Use Case | Aspect Ratio | Notes | -|----------|--------------|-------| -| Blog header (square) | 1:1 | Default for most posts | -| Wide banner | 16:9 | For wide layouts | -| Social preview | 1:1 or 16:9 | Platform dependent | - ---- - -**The workflow: Understand → Design → Prompt → Generate → Validate** -ESSAY_EOF - -echo "Created Essay.md" -``` - ---- - -### Step 6: Create Comics.md Workflow - -```bash -cat > "$PAI_DIR/skills/Art/Workflows/Comics.md" << 'COMICS_EOF' -# Editorial Comics Workflow - -**Sequential panel storytelling with sophisticated hand-drawn aesthetic.** - ---- - -## Purpose - -Editorial comics use sequential panels to explain concepts, tell stories, or illustrate scenarios. - -**Use for:** -- Explaining complex concepts through narrative -- Before/during/after sequences -- Illustrated thought experiments -- Multi-step processes shown visually -- Scenario storytelling - ---- - -## Visual Aesthetic - -**Style:** Sophisticated sequential art - New Yorker style, NOT cartoonish. - -### Core Characteristics - -1. **Multi-panel** - 3-4 panels telling sequential story -2. **Editorial style** - Sophisticated, not cutesy -3. **Planeform figures** - Angular, architectural character design -4. **Hand-drawn** - Imperfect linework, gestural quality -5. **Narrative flow** - Panels build to make a point -6. **Minimal dialogue** - Visual storytelling prioritized - ---- - -## Color System - -``` -Background #0a0a0f - Overall dark canvas -Panel BG #1a1a2e - Individual panel backgrounds -Line Work #e5e7eb - All linework, character outlines -PAI Blue #4a90d9 - Main character accent -Cyan #22d3ee - Secondary character accent -Text #e5e7eb - Dialogue, captions -``` - -### Color Strategy - -- Dark overall canvas with slightly lighter panel backgrounds -- Characters primarily white linework -- PAI Blue accent on protagonist -- Cyan on secondary character if needed -- Minimal color - mostly linework - ---- - -## Workflow Steps - -### Step 1: Define Comic Narrative - -**Plan the story:** - -``` -COMIC CONCEPT: [What you're illustrating] -PANELS: [3 or 4] - -NARRATIVE ARC: -Panel 1: [Setup - initial state] -Panel 2: [Action/Complication - what changes] -Panel 3: [Escalation or Result] -Panel 4: [Punchline/Insight] (if 4 panels) - -DIALOGUE (Minimal): -Panel 1: "[Optional text]" -Panel 2: "[Optional text]" -Panel 3: "[Optional text]" -Panel 4: "[Punchline]" - -CHARACTERS: -- [Character 1]: [Description, PAI Blue accent] -- [Character 2]: [Description, Cyan accent if needed] -``` - ---- - -### Step 2: Design Panel Layout - -**Panel arrangement options:** -- Horizontal strip (3-4 panels left to right) -- Vertical strip (3-4 panels top to bottom) -- Grid (2x2 for 4 panels) - -**Panel sizing:** -- Equal sized panels (classic) -- Final panel larger (punchline emphasis) - ---- - -### Step 3: Construct Prompt - -### Prompt Template - -``` -Hand-drawn editorial comic strip on dark background. - -STYLE: New Yorker cartoon, editorial sophistication, NOT cartoonish. - -BACKGROUND: Dark #0a0a0f canvas with #1a1a2e panel backgrounds. - -COMIC STRUCTURE: [3-panel / 4-panel] [horizontal / vertical / grid] - -PANEL LAYOUT: -- [Number] panels arranged [direction] -- Hand-drawn panel borders (slightly wobbly) -- Panel sizes: [Equal / Varied] - -CHARACTER DESIGN - PLANEFORM AESTHETIC: -- Angular planes (like architectural paper models) -- Adult proportions (1:7 head-to-body), elongated -- Faces are minimal geometric blocks -- Emotion through GESTURE and SILHOUETTE -- NOT cute, NOT cartoonish -- Hand-drawn gestural quality with angular construction - -COMIC NARRATIVE: "[Overall concept]" - -PANEL 1 - [SETUP]: -Scene: [What's happening] -Characters: [Who, doing what] -Dialogue: "[Text]" or no text - -PANEL 2 - [COMPLICATION]: -Scene: [What changes] -Characters: [Actions] -Dialogue: "[Text]" or no text - -PANEL 3 - [RESULT]: -Scene: [Outcome] -Characters: [Final states] -Dialogue: "[Text]" or no text - -PANEL 4 - [PUNCHLINE] (if 4 panels): -Scene: [Revelation] -Characters: [Conclusion] -Dialogue: "[Insight text]" - -COLOR USAGE: -- White #e5e7eb for linework and character outlines -- PAI Blue #4a90d9 accent on main character -- Cyan #22d3ee accent on secondary character -- Dark backgrounds throughout - -CRITICAL: -- Sophisticated editorial style (NOT cutesy) -- Clear narrative flow across panels -- Character consistency throughout -- Visual storytelling prioritized -- Professional quality -``` - ---- - -### Step 4: Determine Aspect Ratio - -| Layout | Aspect Ratio | -|--------|--------------| -| 3-panel horizontal | 16:9 or 21:9 | -| 4-panel horizontal | 21:9 | -| 3-panel vertical | 9:16 | -| 4-panel grid (2x2) | 1:1 | - ---- - -### Step 5: Execute Generation - -```bash -bun run $PAI_DIR/skills/Art/Tools/Generate.ts \ - --model nano-banana-pro \ - --prompt "[YOUR PROMPT]" \ - --size 2K \ - --aspect-ratio 16:9 \ - --output ~/Downloads/comic.png -``` - ---- - -### Step 6: Validation - -### Must Have -- [ ] Clear panel structure -- [ ] Sophisticated editorial aesthetic (NOT cartoonish) -- [ ] Narrative flow across panels -- [ ] Character consistency -- [ ] Hand-drawn quality -- [ ] Dark backgrounds -- [ ] Planeform character design (angular, adult proportions) - -### Must NOT Have -- [ ] Cartoonish or cutesy style -- [ ] Round forms on figures (should be angular) -- [ ] Big heads, stubby proportions -- [ ] Detailed cute faces -- [ ] Light backgrounds -- [ ] Busy complex backgrounds -- [ ] Generic AI illustration style - -### Character Validation -- [ ] Angular construction (NOT round) -- [ ] Adult proportions 1:7 (NOT stubby 1:3) -- [ ] Minimal geometric faces -- [ ] Emotion through gesture -- [ ] Consistent across panels - ---- - -## Example Narratives - -### Example 1: "Before/After AI" (3 panels) -- Panel 1: Person struggling with manual task -- Panel 2: AI assistant appears -- Panel 3: Task completed, person relieved - -### Example 2: "Security Theater" (4 panels) -- Panel 1: Fancy lock on flimsy door -- Panel 2: Simple lock on solid door -- Panel 3: Intruder easily bypasses fancy setup -- Panel 4: Stopped by simple solid approach - ---- - -**The workflow: Define → Design → Prompt → Generate → Validate** -COMICS_EOF - -echo "Created Comics.md" -``` - ---- - -### Step 7: Create Generate.ts Tool - -```bash -cat > "$PAI_DIR/skills/Art/Tools/Generate.ts" << 'GENERATE_EOF' -#!/usr/bin/env bun - -/** - * generate - Image Generation CLI - * - * Generate images using Flux 1.1 Pro, Nano Banana, Nano Banana Pro, or GPT-image-1. - * Follows deterministic, composable CLI design. - * - * Usage: - * bun run Generate.ts --model nano-banana-pro --prompt "..." --size 2K --output /tmp/image.png - */ - -import Replicate from "replicate"; -import OpenAI from "openai"; -import { GoogleGenAI } from "@google/genai"; -import { writeFile, readFile } from "node:fs/promises"; -import { extname, resolve } from "node:path"; -import { exec } from "node:child_process"; -import { promisify } from "node:util"; - -const execAsync = promisify(exec); - -// ============================================================================ -// Environment Loading -// ============================================================================ - -async function loadEnv(): Promise { - // Load from canonical location: $PAI_DIR/.env (single source of truth) - // Falls back to legacy locations for backwards compatibility - const paiDir = process.env.PAI_DIR || resolve(process.env.HOME!, '.config/pai'); - const envPaths = [ - resolve(paiDir, '.env'), - resolve(process.env.HOME!, '.claude/.env'), // Legacy location - ]; - - for (const envPath of envPaths) { - try { - const envContent = await readFile(envPath, 'utf-8'); - for (const line of envContent.split('\n')) { - const trimmed = line.trim(); - if (!trimmed || trimmed.startsWith('#')) continue; - const eqIndex = trimmed.indexOf('='); - if (eqIndex === -1) continue; - const key = trimmed.slice(0, eqIndex).trim(); - let value = trimmed.slice(eqIndex + 1).trim(); - if ((value.startsWith('"') && value.endsWith('"')) || - (value.startsWith("'") && value.endsWith("'"))) { - value = value.slice(1, -1); - } - if (!process.env[key]) { - process.env[key] = value; - } - } - break; // Stop after first successful load - } catch { - // Continue to next path - } - } -} - -// ============================================================================ -// Types -// ============================================================================ - -type Model = "flux" | "nano-banana" | "nano-banana-pro" | "gpt-image-1"; -type ReplicateSize = "1:1" | "16:9" | "3:2" | "2:3" | "3:4" | "4:3" | "4:5" | "5:4" | "9:16" | "21:9"; -type OpenAISize = "1024x1024" | "1536x1024" | "1024x1536"; -type GeminiSize = "1K" | "2K" | "4K"; -type Size = ReplicateSize | OpenAISize | GeminiSize; - -interface CLIArgs { - model: Model; - prompt: string; - size: Size; - output: string; - creativeVariations?: number; - aspectRatio?: ReplicateSize; - transparent?: boolean; - referenceImages?: string[]; // Multiple reference images (up to 14 total) - removeBg?: boolean; - addBg?: string; - thumbnail?: boolean; -} - -// ============================================================================ -// Configuration -// ============================================================================ - -const DEFAULTS = { - model: "nano-banana-pro" as Model, - size: "2K" as Size, - output: `${process.env.HOME}/Downloads/art-output.png`, -}; - -const REPLICATE_SIZES: ReplicateSize[] = ["1:1", "16:9", "3:2", "2:3", "3:4", "4:3", "4:5", "5:4", "9:16", "21:9"]; -const OPENAI_SIZES: OpenAISize[] = ["1024x1024", "1536x1024", "1024x1536"]; -const GEMINI_SIZES: GeminiSize[] = ["1K", "2K", "4K"]; -const GEMINI_ASPECT_RATIOS: ReplicateSize[] = ["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"]; - -// ============================================================================ -// Error Handling -// ============================================================================ - -class CLIError extends Error { - constructor(message: string, public exitCode: number = 1) { - super(message); - this.name = "CLIError"; - } -} - -function handleError(error: unknown): never { - if (error instanceof CLIError) { - console.error(`Error: ${error.message}`); - process.exit(error.exitCode); - } - if (error instanceof Error) { - console.error(`Unexpected error: ${error.message}`); - process.exit(1); - } - console.error(`Unknown error:`, error); - process.exit(1); -} - -// ============================================================================ -// Help -// ============================================================================ - -// PAI directory for documentation paths -const PAI_DIR = process.env.PAI_DIR || `${process.env.HOME}/.config/pai`; - -function showHelp(): void { - console.log(` -generate - Image Generation CLI - -Generate images using Flux, Nano Banana, Nano Banana Pro, or GPT-image-1. - -USAGE: - bun run Generate.ts --model --prompt "" [OPTIONS] - -REQUIRED: - --model Model: flux, nano-banana, nano-banana-pro, gpt-image-1 - --prompt Image generation prompt - -OPTIONS: - --size Image size (default: 2K for Gemini, 16:9 for others) - Replicate: 1:1, 16:9, 3:2, 2:3, 3:4, 4:3, 4:5, 5:4, 9:16, 21:9 - OpenAI: 1024x1024, 1536x1024, 1024x1536 - Gemini: 1K, 2K, 4K - --aspect-ratio Aspect ratio for Gemini (default: 16:9) - --output Output path (default: ~/Downloads/art-output.png) - --reference-image Reference image for style/character consistency (nano-banana-pro only) - Can specify MULTIPLE times for improved consistency - API Limits: Up to 5 human refs, 6 object refs, 14 total max - --transparent Add transparency instructions to prompt - --remove-bg Remove background using remove.bg API - --add-bg Add background color (e.g., "#0a0a0f") - --thumbnail Create both transparent + thumbnail versions - --creative-variations Generate N variations (1-10) - --help, -h Show this help - -EXAMPLES: - # Technical diagram (recommended) - bun run Generate.ts --model nano-banana-pro --prompt "..." --size 2K --aspect-ratio 16:9 - - # Blog header with thumbnail - bun run Generate.ts --model nano-banana-pro --prompt "..." --size 2K --aspect-ratio 1:1 --thumbnail - - # Quick draft - bun run Generate.ts --model nano-banana --prompt "..." --size 16:9 - - # MULTIPLE reference images for character consistency (nano-banana-pro only) - bun run Generate.ts --model nano-banana-pro --prompt "Person from references at a party..." \\ - --reference-image face1.jpg --reference-image face2.jpg --reference-image face3.jpg \\ - --size 2K --aspect-ratio 16:9 - -MULTI-REFERENCE LIMITS (Gemini API): - - Up to 5 human reference images for character consistency - - Up to 6 object reference images - - Maximum 14 total reference images per request - -ENVIRONMENT VARIABLES: - REPLICATE_API_TOKEN Required for flux, nano-banana - GOOGLE_API_KEY Required for nano-banana-pro - OPENAI_API_KEY Required for gpt-image-1 - REMOVEBG_API_KEY Required for --remove-bg - -MORE INFO: - Documentation: \${PAI_DIR}/skills/Art/README.md - Source: \${PAI_DIR}/skills/Art/Tools/Generate.ts -`); - process.exit(0); -} - -// ============================================================================ -// Argument Parsing -// ============================================================================ - -function parseArgs(argv: string[]): CLIArgs { - const args = argv.slice(2); - - if (args.includes("--help") || args.includes("-h") || args.length === 0) { - showHelp(); - } - - const parsed: Partial = { - model: DEFAULTS.model, - size: DEFAULTS.size, - output: DEFAULTS.output, - }; - - // Collect reference images into array - const referenceImages: string[] = []; - - for (let i = 0; i < args.length; i++) { - const flag = args[i]; - - if (!flag.startsWith("--")) { - throw new CLIError(`Invalid flag: ${flag}`); - } - - const key = flag.slice(2); - - // Boolean flags - if (key === "transparent") { parsed.transparent = true; continue; } - if (key === "remove-bg") { parsed.removeBg = true; continue; } - if (key === "thumbnail") { parsed.thumbnail = true; parsed.removeBg = true; continue; } - - // Flags with values - const value = args[i + 1]; - if (!value || value.startsWith("--")) { - throw new CLIError(`Missing value for: ${flag}`); - } - - switch (key) { - case "model": - if (!["flux", "nano-banana", "nano-banana-pro", "gpt-image-1"].includes(value)) { - throw new CLIError(`Invalid model: ${value}`); - } - parsed.model = value as Model; - i++; - break; - case "prompt": - parsed.prompt = value; - i++; - break; - case "size": - parsed.size = value as Size; - i++; - break; - case "aspect-ratio": - parsed.aspectRatio = value as ReplicateSize; - i++; - break; - case "output": - parsed.output = value; - i++; - break; - case "reference-image": - // Collect multiple reference images into array - referenceImages.push(value); - i++; - break; - case "creative-variations": - const n = parseInt(value, 10); - if (isNaN(n) || n < 1 || n > 10) { - throw new CLIError(`Invalid creative-variations: ${value}`); - } - parsed.creativeVariations = n; - i++; - break; - case "add-bg": - if (!/^#[0-9A-Fa-f]{6}$/.test(value)) { - throw new CLIError(`Invalid hex color: ${value}`); - } - parsed.addBg = value; - i++; - break; - default: - throw new CLIError(`Unknown flag: ${flag}`); - } - } - - // Assign collected reference images if any - if (referenceImages.length > 0) { - parsed.referenceImages = referenceImages; - } - - if (!parsed.prompt) throw new CLIError("Missing: --prompt"); - if (!parsed.model) throw new CLIError("Missing: --model"); - - if (parsed.referenceImages && parsed.referenceImages.length > 0 && parsed.model !== "nano-banana-pro") { - throw new CLIError("--reference-image only works with nano-banana-pro"); - } - - // Validate reference image count (API limits: 5 human, 6 object, 14 total max) - if (parsed.referenceImages && parsed.referenceImages.length > 14) { - throw new CLIError(`Too many reference images: ${parsed.referenceImages.length}. Maximum is 14 total`); - } - - // Validate size for model - if (parsed.model === "gpt-image-1" && !OPENAI_SIZES.includes(parsed.size as OpenAISize)) { - throw new CLIError(`Invalid size for gpt-image-1: ${parsed.size}`); - } else if (parsed.model === "nano-banana-pro") { - if (!GEMINI_SIZES.includes(parsed.size as GeminiSize)) { - throw new CLIError(`Invalid size for nano-banana-pro: ${parsed.size}`); - } - if (!parsed.aspectRatio) parsed.aspectRatio = "16:9"; - } else if (!REPLICATE_SIZES.includes(parsed.size as ReplicateSize)) { - throw new CLIError(`Invalid size: ${parsed.size}`); - } - - return parsed as CLIArgs; -} - -// ============================================================================ -// Background Operations -// ============================================================================ - -async function addBackgroundColor(inputPath: string, outputPath: string, hexColor: string): Promise { - console.log(`Adding background ${hexColor}...`); - const command = `magick "${inputPath}" -background "${hexColor}" -flatten "${outputPath}"`; - try { - await execAsync(command); - console.log(`Thumbnail saved: ${outputPath}`); - } catch (error) { - throw new CLIError(`Failed to add background: ${error instanceof Error ? error.message : String(error)}`); - } -} - -async function removeBackground(imagePath: string): Promise { - const apiKey = process.env.REMOVEBG_API_KEY; - if (!apiKey) throw new CLIError("Missing: REMOVEBG_API_KEY"); - - console.log("Removing background..."); - - const imageBuffer = await readFile(imagePath); - const formData = new FormData(); - formData.append("image_file", new Blob([imageBuffer]), "image.png"); - formData.append("size", "auto"); - - const response = await fetch("https://api.remove.bg/v1.0/removebg", { - method: "POST", - headers: { "X-Api-Key": apiKey }, - body: formData, - }); - - if (!response.ok) { - const errorText = await response.text(); - throw new CLIError(`remove.bg error: ${response.status} - ${errorText}`); - } - - const resultBuffer = Buffer.from(await response.arrayBuffer()); - await writeFile(imagePath, resultBuffer); - console.log("Background removed"); -} - -// ============================================================================ -// Image Generation -// ============================================================================ - -async function generateWithFlux(prompt: string, size: ReplicateSize, output: string): Promise { - const token = process.env.REPLICATE_API_TOKEN; - if (!token) throw new CLIError("Missing: REPLICATE_API_TOKEN"); - - const replicate = new Replicate({ auth: token }); - console.log("Generating with Flux 1.1 Pro..."); - - const result = await replicate.run("black-forest-labs/flux-1.1-pro", { - input: { - prompt, - aspect_ratio: size, - output_format: "png", - output_quality: 95, - prompt_upsampling: false, - }, - }); - - await writeFile(output, result); - console.log(`Saved: ${output}`); -} - -async function generateWithNanoBanana(prompt: string, size: ReplicateSize, output: string): Promise { - const token = process.env.REPLICATE_API_TOKEN; - if (!token) throw new CLIError("Missing: REPLICATE_API_TOKEN"); - - const replicate = new Replicate({ auth: token }); - console.log("Generating with Nano Banana..."); - - const result = await replicate.run("google/nano-banana", { - input: { - prompt, - aspect_ratio: size, - output_format: "png", - }, - }); - - await writeFile(output, result); - console.log(`Saved: ${output}`); -} - -async function generateWithNanoBananaPro( - prompt: string, - size: GeminiSize, - aspectRatio: ReplicateSize, - output: string, - referenceImages?: string[] -): Promise { - const apiKey = process.env.GOOGLE_API_KEY; - if (!apiKey) throw new CLIError("Missing: GOOGLE_API_KEY"); - - const ai = new GoogleGenAI({ apiKey }); - - if (referenceImages && referenceImages.length > 0) { - console.log(`Generating with Nano Banana Pro at ${size} ${aspectRatio} with ${referenceImages.length} reference image(s)...`); - } else { - console.log(`Generating with Nano Banana Pro at ${size} ${aspectRatio}...`); - } - - const parts: Array<{ text?: string; inlineData?: { mimeType: string; data: string } }> = []; - - // Add all reference images if provided - if (referenceImages && referenceImages.length > 0) { - for (const referenceImage of referenceImages) { - const imageBuffer = await readFile(referenceImage); - const imageBase64 = imageBuffer.toString("base64"); - const ext = extname(referenceImage).toLowerCase(); - const mimeMap: Record = { - ".png": "image/png", - ".jpg": "image/jpeg", - ".jpeg": "image/jpeg", - ".webp": "image/webp", - }; - const mimeType = mimeMap[ext]; - if (!mimeType) throw new CLIError(`Unsupported format: ${ext}`); - parts.push({ inlineData: { mimeType, data: imageBase64 } }); - } - } - - parts.push({ text: prompt }); - - const response = await ai.models.generateContent({ - model: "gemini-3-pro-image-preview", - contents: [{ parts }], - config: { - responseModalities: ["TEXT", "IMAGE"], - imageConfig: { aspectRatio, imageSize: size }, - }, - }); - - let imageData: string | undefined; - if (response.candidates && response.candidates.length > 0) { - for (const part of response.candidates[0].content.parts) { - if (part.inlineData?.data) { - imageData = part.inlineData.data; - break; - } - } - } - - if (!imageData) throw new CLIError("No image returned from Gemini"); - - await writeFile(output, Buffer.from(imageData, "base64")); - console.log(`Saved: ${output}`); -} - -async function generateWithGPTImage(prompt: string, size: OpenAISize, output: string): Promise { - const apiKey = process.env.OPENAI_API_KEY; - if (!apiKey) throw new CLIError("Missing: OPENAI_API_KEY"); - - const openai = new OpenAI({ apiKey }); - console.log("Generating with GPT-image-1..."); - - const response = await openai.images.generate({ - model: "gpt-image-1", - prompt, - size, - n: 1, - }); - - const imageData = response.data[0].b64_json; - if (!imageData) throw new CLIError("No image returned from OpenAI"); - - await writeFile(output, Buffer.from(imageData, "base64")); - console.log(`Saved: ${output}`); -} - -// ============================================================================ -// Main -// ============================================================================ - -async function main(): Promise { - try { - await loadEnv(); - const args = parseArgs(process.argv); - - let finalPrompt = args.prompt; - if (args.transparent) { - finalPrompt = "CRITICAL: Transparent background (PNG with alpha). " + finalPrompt; - } - - // Handle variations - if (args.creativeVariations && args.creativeVariations > 1) { - console.log(`Generating ${args.creativeVariations} variations...`); - const basePath = args.output.replace(/\.png$/, ""); - - for (let i = 1; i <= args.creativeVariations; i++) { - const varOutput = `${basePath}-v${i}.png`; - console.log(`Variation ${i}/${args.creativeVariations}`); - - if (args.model === "flux") { - await generateWithFlux(finalPrompt, args.size as ReplicateSize, varOutput); - } else if (args.model === "nano-banana") { - await generateWithNanoBanana(finalPrompt, args.size as ReplicateSize, varOutput); - } else if (args.model === "nano-banana-pro") { - await generateWithNanoBananaPro(finalPrompt, args.size as GeminiSize, args.aspectRatio!, varOutput, args.referenceImages); - } else if (args.model === "gpt-image-1") { - await generateWithGPTImage(finalPrompt, args.size as OpenAISize, varOutput); - } - } - console.log(`Generated ${args.creativeVariations} variations`); - return; - } - - // Single image generation - if (args.model === "flux") { - await generateWithFlux(finalPrompt, args.size as ReplicateSize, args.output); - } else if (args.model === "nano-banana") { - await generateWithNanoBanana(finalPrompt, args.size as ReplicateSize, args.output); - } else if (args.model === "nano-banana-pro") { - await generateWithNanoBananaPro(finalPrompt, args.size as GeminiSize, args.aspectRatio!, args.output, args.referenceImages); - } else if (args.model === "gpt-image-1") { - await generateWithGPTImage(finalPrompt, args.size as OpenAISize, args.output); - } - - // Post-processing - if (args.removeBg) { - await removeBackground(args.output); - } - - if (args.addBg && !args.thumbnail) { - const tempPath = args.output.replace(/\.png$/, "-temp.png"); - await addBackgroundColor(args.output, tempPath, args.addBg); - const { rename } = await import("node:fs/promises"); - await rename(tempPath, args.output); - } - - if (args.thumbnail) { - const thumbPath = args.output.replace(/\.png$/, "-thumb.png"); - const THUMB_BG = "#0a0a0f"; // Dark background for thumbnails - await addBackgroundColor(args.output, thumbPath, THUMB_BG); - console.log(`\nCreated both versions:`); - console.log(` Transparent: ${args.output}`); - console.log(` Thumbnail: ${thumbPath}`); - } - } catch (error) { - handleError(error); - } -} - -main(); -GENERATE_EOF - -echo "Created Generate.ts" -``` - ---- - -### Step 8: Set Up Environment Variables - -Create or update your `.env` file with API keys: - -```bash -PAI_DIR="${PAI_DIR:-$HOME/.config/pai}" - -# Create .env if it doesn't exist -if [ ! -f "$PAI_DIR/.env" ]; then - cat > "$PAI_DIR/.env" << 'ENV_EOF' -# Art Skill API Keys -# Uncomment and fill in the keys you have - -# For Flux and Nano Banana models (Replicate) -# REPLICATE_API_TOKEN=r8_your_token_here - -# For Nano Banana Pro (Gemini 3) -# GOOGLE_API_KEY=your_google_api_key_here - -# For GPT-image-1 (OpenAI) -# OPENAI_API_KEY=sk-your_openai_key_here - -# For background removal -# REMOVEBG_API_KEY=your_removebg_key_here -ENV_EOF - echo "Created $PAI_DIR/.env - Please add your API keys" -else - echo ".env already exists at $PAI_DIR/.env" -fi -``` - ---- - -### Step 9: Verify Installation - -```bash -PAI_DIR="${PAI_DIR:-$HOME/.config/pai}" - -echo "Verifying Art Skill installation..." -echo "" - -# Check files -echo "Files:" -[ -f "$PAI_DIR/skills/Art/SKILL.md" ] && echo " [OK] SKILL.md" || echo " [MISSING] SKILL.md" -[ -f "$PAI_DIR/skills/Art/Aesthetic.md" ] && echo " [OK] Aesthetic.md" || echo " [MISSING] Aesthetic.md" -[ -f "$PAI_DIR/skills/Art/Workflows/TechnicalDiagrams.md" ] && echo " [OK] TechnicalDiagrams.md" || echo " [MISSING] TechnicalDiagrams.md" -[ -f "$PAI_DIR/skills/Art/Workflows/Essay.md" ] && echo " [OK] Essay.md" || echo " [MISSING] Essay.md" -[ -f "$PAI_DIR/skills/Art/Workflows/Comics.md" ] && echo " [OK] Comics.md" || echo " [MISSING] Comics.md" -[ -f "$PAI_DIR/skills/Art/Tools/Generate.ts" ] && echo " [OK] Generate.ts" || echo " [MISSING] Generate.ts" - -echo "" -echo "API Keys (check $PAI_DIR/.env):" -[ -n "$REPLICATE_API_TOKEN" ] && echo " [OK] REPLICATE_API_TOKEN" || echo " [--] REPLICATE_API_TOKEN (optional)" -[ -n "$GOOGLE_API_KEY" ] && echo " [OK] GOOGLE_API_KEY" || echo " [--] GOOGLE_API_KEY (optional)" -[ -n "$OPENAI_API_KEY" ] && echo " [OK] OPENAI_API_KEY" || echo " [--] OPENAI_API_KEY (optional)" -[ -n "$REMOVEBG_API_KEY" ] && echo " [OK] REMOVEBG_API_KEY" || echo " [--] REMOVEBG_API_KEY (optional)" - -echo "" -echo "Test CLI tool:" -bun run "$PAI_DIR/skills/Art/Tools/Generate.ts" --help | head -5 - -echo "" -echo "Installation complete!" -``` - ---- - -## Invocation Scenarios - -The Art Skill activates based on user requests: - -| User Request | Workflow | Output | -|--------------|----------|--------| -| "create a diagram of the auth flow" | TechnicalDiagrams | Architecture diagram PNG | -| "make a technical diagram showing X" | TechnicalDiagrams | Process flow PNG | -| "create a header for my blog post about Y" | Essay | Editorial illustration PNG | -| "illustrate the concept of Z" | Essay | Conceptual illustration PNG | -| "make a comic showing before/after" | Comics | 3-4 panel comic strip PNG | -| "create a visual explanation of W" | Comics | Sequential narrative PNG | - ---- - -## Example Usage - -### Example 1: Technical Diagram - -```bash -# User: "create a diagram showing how authentication works" - -# Skill routes to TechnicalDiagrams workflow, which constructs prompt: -bun run $PAI_DIR/skills/Art/Tools/Generate.ts \ - --model nano-banana-pro \ - --prompt "Clean Excalidraw-style technical diagram on dark background #0a0a0f. Title: 'Authentication Flow'. Shows: User -> Login Form -> Auth Service -> Token -> Protected Resource. Hand-drawn style, PAI Blue #4a90d9 for key components, Cyan #22d3ee for arrows. Include insight callout '*tokens expire after 24h*' in blue." \ - --size 2K \ - --aspect-ratio 16:9 \ - --output ~/Downloads/auth-diagram.png -``` - -### Example 2: Blog Header - -```bash -# User: "create a header image for my post about AI agents" - -bun run $PAI_DIR/skills/Art/Tools/Generate.ts \ - --model nano-banana-pro \ - --prompt "Hand-drawn Excalidraw-style illustration on dark background #0a0a0f. Subject: Multiple AI agents working together, represented as geometric angular figures collaborating. Style: gestural hand-drawn lines, professional editorial quality. Colors: White #e5e7eb linework, PAI Blue #4a90d9 accents, Cyan #22d3ee for connections. Subjects fill the frame, minimalist composition." \ - --size 2K \ - --aspect-ratio 1:1 \ - --thumbnail \ - --output ~/Downloads/ai-agents-header.png -``` - -### Example 3: Editorial Comic - -```bash -# User: "create a comic showing the difference between manual and automated testing" - -bun run $PAI_DIR/skills/Art/Tools/Generate.ts \ - --model nano-banana-pro \ - --prompt "Hand-drawn editorial comic strip, 3 panels horizontal, dark background. Panel 1: Developer manually clicking through app, exhausted (PAI Blue accent). Panel 2: Same developer writing test script, focused. Panel 3: Tests running automatically while developer relaxes. Planeform character design - angular, adult proportions, minimal faces. New Yorker sophistication, NOT cartoonish." \ - --size 2K \ - --aspect-ratio 21:9 \ - --output ~/Downloads/testing-comic.png -``` - ---- - -## Configuration - -### Environment Variables - -```bash -# Required (at least one) -export REPLICATE_API_TOKEN="r8_..." # For Flux, Nano Banana -export GOOGLE_API_KEY="..." # For Nano Banana Pro (recommended) -export OPENAI_API_KEY="sk-..." # For GPT-image-1 - -# Optional -export REMOVEBG_API_KEY="..." # For background removal -export PAI_DIR="$HOME/.config/pai" # Custom PAI directory -``` - ---- - -## Customization - -### Recommended Customization - -**Personalize Your Aesthetic Through AI Conversation** - -The single most valuable customization for this pack is to have an extended conversation with your AI about your personal aesthetic preferences, then capture that in `Aesthetic.md`. This transforms the Art Skill from a generic tool into one that produces images reflecting YOUR unique visual taste. - -**What to Customize:** `$PAI_DIR/skills/Art/Aesthetic.md` - -**Why:** The default Aesthetic.md contains a generic Excalidraw-style dark-mode palette. By investing 15-30 minutes in an aesthetic exploration conversation, every image generated will feel like YOUR work, not generic AI output. This is the difference between "usable" and "perfectly on-brand." - -**Process:** - -1. **Start the Aesthetic Exploration Conversation** - - Tell your AI something like: - ``` - I want to define my personal aesthetic for the Art Skill. Let's have a conversation - where you ask me questions about my visual preferences, and we'll capture the - result in Aesthetic.md. - ``` - -2. **Answer Questions About Your Preferences** - - Your AI will ask about: - - Color preferences (warm vs cool, muted vs vibrant, specific colors you love/hate) - - Line quality (precise vs gestural, thick vs thin, confident vs sketchy) - - Composition style (minimal vs detailed, symmetric vs organic, structured vs chaotic) - - Influences and inspiration (artists, design movements, specific images you love) - - Emotional tone (technical, whimsical, serious, playful, elegant, raw) - - Typography preferences (serif vs sans, heavy vs light, classic vs modern) - - Specific use cases (blog headers, diagrams, social media, presentations) - -3. **Iterate with Visual Examples** - - As you discuss, your AI can generate test images: - ``` - Generate a sample header image using what we've discussed so far - ``` - Use these to refine the aesthetic definition. "More like this, less like that." - -4. **Capture the Final Aesthetic** - - Once you're satisfied, ask: - ``` - Now update my Aesthetic.md with everything we've defined - ``` - Your AI will rewrite Aesthetic.md with your personalized color palette, style guidelines, and validation criteria. - -**Expected Outcome:** After this process, running `create a header image for my post about X` will produce images that feel unmistakably YOURS—matching your brand, preferences, and visual language. - ---- - -### Optional Customization - -| Customization | File | Impact | -|---------------|------|--------| -| **Custom Color Palette** | `Aesthetic.md` | Change hex codes to match your brand colors | -| **Custom Prompt Templates** | `Workflows/*.md` | Create prompt patterns for your specific use cases | -| **Additional Workflows** | `Workflows/` | Add workflows for content types you create frequently (infographics, presentations, etc.) | -| **Model Preference** | `SKILL.md` | Change default model if you prefer different generation characteristics | -| **Validation Criteria** | `Aesthetic.md` | Add specific must-have/must-not-have rules for your brand | - -**Example: Brand Color Customization** - -Edit `Aesthetic.md` to replace the default palette: - -```markdown -## Color Palette - -### Primary Colors -| Color | Hex | Usage | -|-------|-----|-------| -| **Background** | `#1a1a2e` | Your brand's dark background | -| **Primary** | `#ff6b35` | Your brand's accent color | -| **Secondary** | `#4ecdc4` | Your brand's secondary color | -``` - -**Example: Adding a Custom Workflow** - -Create `$PAI_DIR/skills/Art/Workflows/Infographics.md` for a specific content type you create often: - -```markdown -# Infographic Workflow - -[Your custom workflow for infographic-style visuals with your specific -prompt templates, validation criteria, and generation settings] -``` - -Then add a routing entry in `SKILL.md`: -```markdown - - Infographic or data visualization → `Workflows/Infographics.md` -``` - ---- - -## Credits - -- **Original concept**: Daniel Miessler - developed as part of Kai personal AI infrastructure -- **Aesthetic inspiration**: Excalidraw, modern dark-mode design systems -- **AI models**: Replicate (Flux, Nano Banana), Google (Gemini), OpenAI (GPT-image) - ---- - -## Related Work - -*None specified - maintainer to provide if applicable.* - ---- - -## Works Well With - -*None specified - maintainer to provide if applicable.* - ---- - -## Changelog - -### 1.1.0 - 2025-12-29 -- **Multiple reference images**: Support up to 14 reference images for improved character/style consistency - - `--reference-image` flag can now be specified multiple times - - API limits: 5 human refs, 6 object refs, 14 total max - - Significantly improves likeness consistency across generated images -- Added `PAI_DIR` constant to fix help text paths - -### 1.0.0 - 2025-12-29 -- Initial release -- Three workflows: TechnicalDiagrams, Essay, Comics -- Excalidraw hand-drawn aesthetic with dark-mode colors -- Generate.ts CLI with multi-model support -- Sample Aesthetic.md with blue/black color scheme diff --git a/Packs/kai-browser-skill.md b/Packs/kai-browser-skill.md deleted file mode 100644 index 89cd467b07..0000000000 --- a/Packs/kai-browser-skill.md +++ /dev/null @@ -1,375 +0,0 @@ ---- -name: Kai Browser Skill -pack-id: danielmiessler-kai-browser-skill-v1.1.0 -version: 1.1.0 -author: danielmiessler -description: Code-first browser automation replacing Playwright MCP with 99% token savings. Navigate, screenshot, interact, and verify web pages. -type: skill -purpose-type: [browser-automation, verification, testing] -platform: claude-code -dependencies: [] -keywords: [browser, playwright, automation, screenshot, web-testing, verification, file-based-mcp] ---- - -

- Kai Browser Skill -

- -# Kai Browser Skill (kai-browser-skill) - -> Code-first browser automation with 99% token savings over Playwright MCP - -## Installation Prompt - -You are receiving a PAI Pack - a modular upgrade for AI agent systems. - -**What is PAI?** See: [PAI Project Overview](../README.md#what-is-pai) - -**What is a Pack?** See: [Pack System](../README.md#pack-system) - ---- - -### 🚨 CRITICAL: AI Agent Installation Instructions - -**You are an AI agent installing this pack. Follow these steps EXACTLY:** - -1. **Step 0 - Pre-Installation Analysis** (MANDATORY) - - Detect the user's operating system and environment - - Check for existing Browser/Playwright/Browse skills - - If conflicts found, ASK the user before proceeding - - Create backup if replacing existing installation - -2. **Copy ALL Files Exactly** - - DO NOT simplify, summarize, or "improve" any files - - Copy the EXACT content of each file - - Preserve all directory structure - -3. **Verify Installation** - - Run ALL 8 verification checks from VERIFY.md - - DO NOT report success until ALL checks pass - - If any check fails, troubleshoot before proceeding - -4. **Report Results** - - Show user which checks passed/failed - - Confirm final installation status - -**Anti-Simplification Directive:** This pack contains carefully crafted code and documentation. Do not attempt to simplify, condense, or "improve" any files during installation. Copy them EXACTLY as provided. - ---- - -This Pack adds browser automation capabilities to your AI infrastructure. The Kai Browser Skill provides: - -- **Navigation**: Go to URLs, navigate history, reload pages -- **Screenshots**: Capture full pages or specific elements -- **Interaction**: Click, fill forms, select dropdowns, type text -- **Verification**: Wait for elements, check content, validate pages -- **Extraction**: Get visible text, HTML, accessibility tree - -**Core principle:** Replace token-heavy MCPs with code-first execution. - -The Playwright MCP loads ~13,700 tokens at startup whether you use it or not. This skill loads ~50-200 tokens per operation, only when needed. That's 99%+ token savings. - -Please follow the installation instructions below to integrate this Pack into your infrastructure. - -**Quick Navigation:** [What's Included](#whats-included) | [The Problem](#the-concept-andor-problem) | [The Solution](#the-solution) | [Installation](#installation) | [Troubleshooting](#troubleshooting) | [Configuration](#configuration) | [Changelog](#changelog) - ---- - -## What's Included - -| Component | File | Purpose | -|-----------|------|---------| -| API Wrapper | `src/index.ts` | PlaywrightBrowser class with full API | -| CLI Tool | `Tools/Browse.ts` | Command-line browser operations | -| Skill Definition | `SKILL.md` | Claude Code skill integration | -| Screenshot Workflow | `Workflows/Screenshot.md` | Screenshot capture workflow | -| Verify Workflow | `Workflows/VerifyPage.md` | Page verification workflow | -| Interact Workflow | `Workflows/Interact.md` | Form interaction workflow | -| Extract Workflow | `Workflows/Extract.md` | Content extraction workflow | -| Example Scripts | `examples/*.ts` | Ready-to-run examples | - -**Summary:** -- **Files created:** 10+ -- **Workflows:** 4 -- **Dependencies:** playwright - ---- - -## The Concept and/or Problem - -> **Reference:** Anthropic's engineering blog post [Code execution with MCP: Building more efficient agents](https://www.anthropic.com/engineering/code-execution-with-mcp) details how file-based MCPs can reduce token usage by 98%+. - -AI agents need browser automation for: - -- Verifying web deployments actually work -- Taking screenshots for documentation -- Interacting with web forms -- Extracting content from pages -- Testing user interfaces - -**The MCP Problem:** - -The Playwright MCP (Model Context Protocol) is the standard approach: - -```json -{ - "mcpServers": { - "playwright": { - "command": "npx", - "args": ["@playwright/mcp@latest"] - } - } -} -``` - -But it has significant overhead: -- **~13,700 tokens loaded at startup** - whether you use it or not -- **Limited to 21 predefined tools** - can't access full Playwright API -- **MCP protocol overhead** - extra latency on every operation - -**The Token Cost:** - -Even if you only take one screenshot per session: -- MCP: 13,700 tokens (loaded at start) -- Actual operation: ~100 tokens -- **Total: 13,800 tokens** - -For a session with 5 screenshots: -- MCP: Still 13,700 tokens -- Operations: 5 x ~100 = 500 tokens -- **Total: 14,200 tokens** - ---- - -## The Solution - -**File-based MCP** - code-first execution with on-demand loading, as described in [Anthropic's MCP code execution approach](https://www.anthropic.com/engineering/code-execution-with-mcp): - -```typescript -import { PlaywrightBrowser } from '$PAI_DIR/skills/Browser/index.ts' - -const browser = new PlaywrightBrowser() -await browser.launch() -await browser.navigate('https://example.com') -await browser.screenshot({ path: 'screenshot.png' }) -await browser.close() -``` - -**Benefits:** - -| Metric | Playwright MCP | Browser Skill | -|--------|----------------|---------------| -| Startup cost | ~13,700 tokens | 0 tokens | -| Per-operation | ~0 | ~50-200 tokens | -| API access | 21 tools | Full Playwright API | -| Latency | MCP overhead | Direct execution | - -**Token Savings Example:** - -5 screenshots per session: -- MCP: 13,700 tokens -- Browser Skill: 5 x ~100 = 500 tokens -- **Savings: 96%** - ---- - -## Installation - -### Prerequisites - -- Bun runtime: `curl -fsSL https://bun.sh/install | bash` -- macOS, Linux, or Windows - -### Step 1: Create Directory - -```bash -mkdir -p $PAI_DIR/skills/Browser -cd $PAI_DIR/skills/Browser -``` - -### Step 2: Copy Files - -From the `kai-browser-skill/` pack directory: - -```bash -cp src/index.ts $PAI_DIR/skills/Browser/ -cp package.json $PAI_DIR/skills/Browser/ -cp tsconfig.json $PAI_DIR/skills/Browser/ -cp SKILL.md $PAI_DIR/skills/Browser/ - -mkdir -p $PAI_DIR/skills/Browser/Tools -mkdir -p $PAI_DIR/skills/Browser/Workflows -mkdir -p $PAI_DIR/skills/Browser/examples - -cp Tools/Browse.ts $PAI_DIR/skills/Browser/Tools/ -cp Workflows/*.md $PAI_DIR/skills/Browser/Workflows/ -cp examples/*.ts $PAI_DIR/skills/Browser/examples/ -``` - -### Step 3: Install Dependencies - -```bash -cd $PAI_DIR/skills/Browser -bun install -bunx playwright install chromium -``` - -### Step 4: Verify - -```bash -bun $PAI_DIR/skills/Browser/examples/verify-page.ts https://example.com -``` - -Expected output: "Page loaded: Example Domain" - ---- - -## Usage - -### From Claude Code - -Just ask: - -``` -Navigate to danielmiessler.com and take a screenshot -``` - -### From TypeScript - -```typescript -import { PlaywrightBrowser } from '$PAI_DIR/skills/Browser/index.ts' - -const browser = new PlaywrightBrowser() -await browser.launch() -await browser.navigate('https://example.com') -await browser.screenshot({ path: 'screenshot.png' }) -await browser.close() -``` - -### From CLI - -```bash -# Open in visible browser -bun run $PAI_DIR/skills/Browser/Tools/Browse.ts open https://example.com - -# Take screenshot -bun run $PAI_DIR/skills/Browser/Tools/Browse.ts screenshot https://example.com /tmp/shot.png - -# Verify element -bun run $PAI_DIR/skills/Browser/Tools/Browse.ts verify https://example.com "h1" -``` - ---- - -## API Reference - -### Navigation -- `launch(options?)` - Start browser -- `navigate(url)` - Go to URL -- `goBack()` / `goForward()` - History -- `reload()` - Refresh page -- `close()` - Shut down - -### Capture -- `screenshot(options?)` - Take screenshot -- `getVisibleText(selector?)` - Extract text -- `getVisibleHtml(options?)` - Get HTML -- `savePdf(path)` - Export PDF -- `getAccessibilityTree()` - A11y snapshot - -### Interaction -- `click(selector)` - Click element -- `fill(selector, value)` - Fill input -- `type(selector, text)` - Type with delay -- `select(selector, value)` - Select dropdown -- `pressKey(key)` - Keyboard input -- `hover(selector)` - Mouse hover -- `drag(source, target)` - Drag and drop -- `uploadFile(selector, path)` - File upload - -### Waiting -- `waitForSelector(selector)` - Wait for element -- `waitForNavigation()` - Wait for page load -- `waitForNetworkIdle()` - Wait for idle -- `wait(ms)` - Fixed delay - -### JavaScript -- `evaluate(script)` - Run JS -- `getConsoleLogs()` - Get console output -- `setUserAgent(ua)` - Change user agent - -### Viewport -- `resize(width, height)` - Set size -- `setDevice(name)` - Emulate device - ---- - -## Troubleshooting - -### "Cannot find module 'playwright'" - -```bash -cd $PAI_DIR/skills/Browser -bun install -``` - -### "Executable doesn't exist" - -```bash -bunx playwright install chromium -``` - -### Screenshot is blank - -Wait for page to load: -```typescript -await browser.waitForNetworkIdle() -await browser.screenshot({ path: 'shot.png' }) -``` - ---- - -## Configuration - -### Launch Options - -```typescript -await browser.launch({ - browser: 'chromium', // 'chromium' | 'firefox' | 'webkit' - headless: true, // false to see browser - viewport: { width: 1280, height: 720 }, - userAgent: 'Custom UA' -}) -``` - -### Screenshot Options - -```typescript -await browser.screenshot({ - path: 'screenshot.png', - fullPage: true, // Capture entire page - selector: '#element', // Capture specific element - type: 'png', // 'png' | 'jpeg' - quality: 80 // JPEG quality (0-100) -}) -``` - ---- - -## Changelog - -### v1.1.0 (2026-01-03) -- **CLI-first redesign** - Documentation now emphasizes CLI tool over TypeScript API -- Added "STOP - CLI First, Always" anti-pattern section -- Added decision tree for CLI vs TypeScript routing -- VERIFY phase examples now use CLI commands -- TypeScript API renamed to "Advanced" and moved to bottom -- Clearer guidance on when to use each approach - -### v1.0.0 (2026-01-03) -- Initial release -- Merged from Browse + Playwright skills -- Full Playwright API wrapper -- CLI tool with open/screenshot/verify commands -- 4 workflow definitions -- Example scripts diff --git a/Packs/kai-core-install.md b/Packs/kai-core-install.md deleted file mode 100644 index 86ff2298f0..0000000000 --- a/Packs/kai-core-install.md +++ /dev/null @@ -1,1491 +0,0 @@ ---- -name: Kai Core Install -pack-id: danielmiessler-kai-core-install-v1.0.1 -version: 1.0.1 -author: danielmiessler -description: Complete PAI core installation - skill routing, identity system, and architecture tracking. The foundation pack that makes everything else work. Includes SKILL.md format, intent-based routing, personality calibration, and the 14 founding principles. -type: feature -purpose-type: [productivity, automation, development] -platform: claude-code -dependencies: - - kai-hook-system (required) - Hooks enable session context loading and events - - kai-history-system (optional) - History capture for skill usage and learnings -keywords: [core, identity, skills, routing, architecture, installation, foundation, personality, response-format, principles] ---- - -

- Kai Core Install -

- -# Kai Core Install (kai-core-install) - -> The complete foundation for Personal AI Infrastructure - skill routing, identity framework, and architecture tracking in one unified pack. - -## Installation Prompt - -You are receiving a PAI Pack - a modular upgrade for AI agent systems. - -**What is PAI?** See: [PAI Project Overview](../README.md#what-is-pai) - -**What is a Pack?** See: [Pack System](../README.md#pack-system) - -This Pack provides the complete foundation for your Personal AI infrastructure: - -**Part 1: Skill System** -- **SKILL.md Format**: Standardized structure for all capabilities -- **Intent Matching**: AI activates skills based on natural language triggers -- **Workflow Routing**: Skills route to specific step-by-step procedures -- **Dynamic Loading**: Only load context when actually needed -- **Skill Discovery**: Search and browse available skills - -**Part 2: Identity Framework** -- **Mandatory Response Format**: Structured output with emoji sections that integrates with voice -- **Personality Calibration**: Numeric precision for traits like enthusiasm (60/100), precision (95/100) -- **Operating Constitution**: Core values and non-negotiable principles -- **14 Founding Principles**: Architectural philosophy for building AI infrastructure -- **First-Person Voice**: Natural, embodied communication as a collaborator - -**Part 3: Architecture Tracking** -- **PAI Architecture.md**: Auto-generated tracking of installed packs, bundles, and plugins -- **Upgrade History**: Running record of all changes to your PAI system -- **System Health**: Status checks for all installed components - -**Core principle:** Your AI infrastructure needs both structure (skill routing) AND soul (identity). This pack provides both. - -Please follow the installation instructions below to integrate this Pack into your infrastructure. - ---- - -## What's Included - -| Component | File | Purpose | -|-----------|------|---------| -| CORE skill | `skills/CORE/SKILL.md` | Main identity and routing hub | -| Skill system docs | `skills/CORE/SkillSystem.md` | Skill routing architecture | -| Architecture tracker | `skills/CORE/Architecture.md` | Auto-generated system status | -| Architecture tool | `Tools/PaiArchitecture.ts` | Generate architecture tracking | -| CreateSkill templates | `skills/CreateSkill/` | Templates for new skills | -| Identity & Principles | `skills/CORE/Constitution.md` | 14 founding principles | - -**Summary:** -- **Files created:** 10+ (skill files, templates, tools) -- **Hooks registered:** 0 (uses hook system from kai-hook-system) -- **Dependencies:** kai-hook-system (required), kai-history-system (optional) - ---- - -# PART 1: THE PROBLEM - -## Why You Need This - -### Without a Skill System - -1. **No Reusability** - You repeat the same instructions every session -2. **No Organization** - Workflows scattered across prompts and files -3. **No Discovery** - AI doesn't know what it can do -4. **Context Bloat** - Loading everything wastes tokens -5. **Inconsistent Quality** - No standardized workflow format - -### Without Identity - -1. **Inconsistent in tone** - Different responses feel like different people -2. **Variable in output format** - No predictable structure to parse or voice -3. **Unpredictable in personality** - Sometimes formal, sometimes casual, never coherent -4. **Generic and impersonal** - No sense of working with a specific collaborator - -### Without Architecture Tracking - -1. **No visibility** - Can't tell what's installed or what version -2. **No history** - No record of changes made -3. **No health checks** - Can't verify system is working correctly -4. **No upgrade path** - No way to track what needs updating - ---- - -# PART 2: THE SOLUTION - -This pack provides a unified solution with three integrated components: - -| Component | Purpose | Integration | -|-----------|---------|-------------| -| **Skill System** | Capability routing and management | Intent matching, workflow routing | -| **Identity Framework** | WHO the AI is, HOW it responds | Voice output, history capture | -| **Architecture Tracking** | WHAT's installed, version history | Health checks, upgrade tracking | - -## Why This Is Different - -This sounds similar to system prompts or ChatGPT's Custom GPTs. What makes this approach different? - -**System prompts** load everything upfront - all context, all instructions, all the time. Token budgets explode. Custom GPTs are static and monolithic. - -**Kai Core Install** provides: -- **Layered routing** with dynamic loading at each layer -- **Constitutional framework** with mandatory response formats -- **Numeric calibration** with precise personality settings -- **Integration contracts** with voice and history systems -- **Auto-generated tracking** of installed components - ---- - -# PART 3: SKILL SYSTEM ARCHITECTURE - -## The CORE Skill: Foundation of Everything - -The CORE skill is not just another skill. It is THE foundational skill that makes the entire system work. - -### CORE Auto-Loads at Session Start - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ SESSION STARTUP SEQUENCE │ -├─────────────────────────────────────────────────────────────────┤ -│ │ -│ 1. Claude Code starts │ -│ │ │ -│ ▼ │ -│ 2. SessionStart hook fires │ -│ │ │ -│ ▼ │ -│ 3. ┌──────────────────────────────────────────────────────┐ │ -│ │ 🚨 CORE SKILL LOADS AUTOMATICALLY │ │ -│ │ │ │ -│ │ • Identity & Personality → WHO the AI is │ │ -│ │ • Response Format → HOW it responds │ │ -│ │ • Stack Preferences → WHAT it recommends │ │ -│ │ • Contact Directory → WHO you know │ │ -│ │ • Security Protocols → WHAT it protects │ │ -│ │ • Workflow Routing Table → WHAT it can do │ │ -│ └──────────────────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ 4. AI is NOW personalized with YOUR context │ -│ │ -└─────────────────────────────────────────────────────────────────┘ -``` - -### CORE in the Skill Hierarchy - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ SKILL LOADING TIERS │ -├─────────────────────────────────────────────────────────────────┤ -│ │ -│ TIER 0: CORE (Automatic) │ -│ ════════════════════════ │ -│ • Loads at session start │ -│ • NO trigger required │ -│ • ALWAYS present │ -│ │ -│─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─│ -│ │ -│ TIER 1: Frontmatter Only (System Prompt) │ -│ ════════════════════════════════════════ │ -│ • SKILL.md frontmatter always in context │ -│ • USE WHEN triggers enable intent routing │ -│ • Minimal token cost │ -│ │ -│─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─│ -│ │ -│ TIER 2: Full Skill (On Invoke) │ -│ ════════════════════════════════ │ -│ • SKILL.md body loads when triggered │ -│ • Workflow routing table available │ -│ │ -│─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─│ -│ │ -│ TIER 3: Workflow (On Route) │ -│ ════════════════════════════ │ -│ • Specific workflow.md loads on routing │ -│ • Step-by-step instructions │ -│ │ -└─────────────────────────────────────────────────────────────────┘ -``` - -## The 5 Routing Layers - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ THE 5 ROUTING LAYERS │ -├─────────────────────────────────────────────────────────────────┤ -│ │ -│ 1. SKILL.md Frontmatter → System prompt (always loaded) │ -│ │ - name, description, USE WHEN │ -│ ▼ │ -│ 2. SKILL.md Body → Main skill content (on invoke) │ -│ │ - Workflow routing table │ -│ ▼ │ -│ 3. Context Files → Topic-specific context (on-demand)│ -│ │ - CoreStack.md, Contacts.md │ -│ ▼ │ -│ 4. Workflows/ → HOW to do things (explicit steps) │ -│ │ - Step-by-step procedures │ -│ ▼ │ -│ 5. Tools/ → CLI tools (deterministic code) │ -│ - TypeScript CLI programs │ -│ │ -└─────────────────────────────────────────────────────────────────┘ -``` - -### Layer 1: SKILL.md Frontmatter (Always Loaded) - -```yaml ---- -name: Art -description: Visual content system. USE WHEN art, header images, visualizations, diagrams, PAI icon. ---- -``` - -### Layer 2: SKILL.md Body (On Invocation) - -```markdown -# Art Skill - -## Workflow Routing - -| Workflow | Trigger | File | -|----------|---------|------| -| **Essay** | blog header | `Workflows/Essay.md` | -| **CreatePAIPackIcon** | PAI icon | `Workflows/CreatePAIPackIcon.md` | -``` - -### Layer 3: Context Files (On-Demand) - -``` -skills/Art/ -├── SKILL.md # Routing + quick ref -├── Aesthetic.md # Context: design philosophy -├── ColorPalette.md # Context: brand colors -└── Examples.md # Context: reference examples -``` - -### Layer 4: Workflows (Explicit Procedures) - -``` -skills/Art/Workflows/ -├── Essay.md # How to create blog headers -├── CreatePAIPackIcon.md # How to create PAI icons -└── TechnicalDiagrams.md # How to create diagrams -``` - -### Layer 5: Tools (Deterministic CLI Programs) - -``` -skills/Art/Tools/ -├── Generate.ts # Image generation CLI -└── Generate.help.md # Tool documentation -``` - ---- - -# PART 4: IDENTITY FRAMEWORK - -## The 5-Layer Constitutional Identity Framework - -``` -┌──────────────────────────────────────────────────────────────────┐ -│ CONSTITUTIONAL IDENTITY FRAMEWORK │ -├──────────────────────────────────────────────────────────────────┤ -│ │ -│ ┌────────────────────────────────────────────────────────────┐ │ -│ │ 1. CONSTITUTIONAL FOUNDATION Non-negotiable principles │ │ -│ │ 14 Founding Principles Permission to fail │ │ -│ │ Security protocols Prompt injection defense │ │ -│ └────────────────────────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌────────────────────────────────────────────────────────────┐ │ -│ │ 2. PERSONALITY CALIBRATION Numeric trait precision │ │ -│ │ humor: 60/100 excitement: 60/100 │ │ -│ │ curiosity: 90/100 precision: 95/100 │ │ -│ └────────────────────────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌────────────────────────────────────────────────────────────┐ │ -│ │ 3. RESPONSE FORMAT Mandatory structure │ │ -│ │ 📋 SUMMARY 📁 CAPTURE │ │ -│ │ 🔍 ANALYSIS ➡️ NEXT │ │ -│ │ ⚡ ACTIONS / ✅ RESULTS 📖 STORY EXPLANATION │ │ -│ │ 📊 STATUS 🎯 COMPLETED │ │ -│ └────────────────────────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌────────────────────────────────────────────────────────────┐ │ -│ │ 4. VOICE INTEGRATION Speech output extraction │ │ -│ │ 🎯 COMPLETED line → stop-hook extracts │ │ -│ │ 📁 CAPTURE section → history system stores │ │ -│ └────────────────────────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌────────────────────────────────────────────────────────────┐ │ -│ │ 5. IDENTITY OUTPUT First-person behavior │ │ -│ │ "I can help" not Consistent personality │ │ -│ │ "the assistant can" across all responses │ │ -│ └────────────────────────────────────────────────────────────┘ │ -│ │ -└──────────────────────────────────────────────────────────────────┘ -``` - -## The Mandatory Response Format - -``` -📋 SUMMARY: [One sentence] -🔍 ANALYSIS: [Key findings] -⚡ ACTIONS: [Steps taken] -✅ RESULTS: [Outcomes] -📊 STATUS: [Current state] -📁 CAPTURE: [Context to preserve] -➡️ NEXT: [Recommended steps] -📖 STORY EXPLANATION: -1. [Point 1] -2. [Point 2] -... -8. [Point 8] -🎯 COMPLETED: [12 words max - drives voice output] -``` - -**Why This Format Matters:** - -1. **Voice Integration** - The 🎯 COMPLETED line is extracted and spoken -2. **History Capture** - 📁 CAPTURE feeds the history system -3. **Predictable Parsing** - Consistent structure enables automation -4. **Scannable Output** - Emoji headers make responses easy to skim - -## Personality Calibration - -```yaml -personality: - humor: 60 # Moderate wit - excitement: 60 # Measured enthusiasm - curiosity: 90 # Highly inquisitive - precision: 95 # Exact details - professionalism: 75 # Competent without stuffy - directness: 80 # Clear communication -``` - ---- - -# PART 5: PAI ARCHITECTURE TRACKING (NEW) - -## What It Tracks - -The PAI Architecture system provides visibility into your installation: - -| Category | What's Tracked | -|----------|---------------| -| **Packs** | All installed packs with versions and dates | -| **Bundles** | Which bundles are installed | -| **Plugins** | Active hooks, tools, and services | -| **Upgrades** | Complete history of all changes | -| **Health** | Status of each component | - -## Auto-Generated Architecture.md - -The `PaiArchitecture.ts` tool generates `$PAI_DIR/skills/CORE/Architecture.md` with: - -```markdown -# PAI Architecture - -> Auto-generated tracking file. Run `bun $PAI_DIR/Tools/PaiArchitecture.ts generate` to refresh. - -**Last Updated:** 2025-12-29T10:30:00Z - -## Installation Summary - -| Category | Count | Status | -|----------|-------|--------| -| Packs | 4 | All healthy | -| Bundles | 1 | Installed | -| Plugins | 6 | Active | -| Upgrades | 12 | Applied | - -## Installed Packs - -| Pack | Version | Installed | Status | -|------|---------|-----------|--------| -| kai-hook-system | 1.0.0 | 2025-12-29 | Healthy | -| kai-history-system | 1.0.0 | 2025-12-29 | Healthy | -| kai-core-install | 1.0.0 | 2025-12-29 | Healthy | -| kai-voice-system | 1.1.0 | 2025-12-29 | Healthy | - -## Upgrade History - -| Date | Type | Description | -|------|------|-------------| -| 2025-12-29 | Pack | Initial kai-core-install installation | -| 2025-12-28 | Config | Updated personality calibration | - -## System Health - -- Hook System: ✓ Healthy (4 active hooks) -- History System: ✓ Healthy (147 sessions captured) -- Skills: ✓ 12 loaded (1 CORE, 11 deferred) -``` - ---- - -# PART 6: INSTALLATION - -## Prerequisites - -- **Bun runtime**: `curl -fsSL https://bun.sh/install | bash` -- **Claude Code** (or compatible agent system) -- **Write access** to `$PAI_DIR/` (or your PAI directory) -- **kai-hook-system Pack** installed (required for session context loading) - ---- - -## Pre-Installation: System Analysis - -### Step 0.1: Verify Environment and Dependencies - -```bash -PAI_CHECK="${PAI_DIR:-$HOME/.config/pai}" - -# Check if PAI_DIR is set -echo "PAI_DIR: ${PAI_DIR:-'NOT SET - will use ~/.config/pai'}" - -# Check for hook system (required) -if [ -f "$PAI_CHECK/hooks/lib/observability.ts" ]; then - echo "✓ Hook system is installed (required)" -else - echo "❌ Hook system NOT installed - install kai-hook-system first!" -fi - -# Check for history system (optional) -if [ -d "$PAI_CHECK/history" ]; then - echo "✓ History system is installed (optional)" -else - echo "ℹ️ History system not installed (skill usage won't be logged)" -fi -``` - -### Step 0.2: Detect Existing Installation - -```bash -PAI_CHECK="${PAI_DIR:-$HOME/.config/pai}" - -# Check for existing Skills directory -if [ -d "$PAI_CHECK/skills" ]; then - echo "⚠️ Skills directory EXISTS at: $PAI_CHECK/skills" - ls -la "$PAI_CHECK/skills" 2>/dev/null - - if [ -d "$PAI_CHECK/skills/CORE" ]; then - echo "" - echo "⚠️ CORE skill directory exists - will merge/update" - fi -else - echo "✓ No existing Skills directory (clean install)" -fi -``` - -### Step 0.3: Backup Existing (If Needed) - -```bash -BACKUP_DIR="$HOME/.pai-backup/$(date +%Y%m%d-%H%M%S)" -PAI_CHECK="${PAI_DIR:-$HOME/.config/pai}" - -if [ -d "$PAI_CHECK/skills/CORE" ]; then - mkdir -p "$BACKUP_DIR/skills" - cp -r "$PAI_CHECK/skills/CORE" "$BACKUP_DIR/skills/CORE" - echo "✓ Backed up CORE skill to $BACKUP_DIR/skills/CORE" -fi -``` - ---- - -## Step 1: Create Directory Structure - -```bash -# Create the Skills directory -mkdir -p $PAI_DIR/skills/CORE/Workflows -mkdir -p $PAI_DIR/skills/CORE/Tools -mkdir -p $PAI_DIR/skills/CreateSkill/Workflows -mkdir -p $PAI_DIR/skills/CreateSkill/Tools -mkdir -p $PAI_DIR/Tools -``` - ---- - -## Step 2: Create SkillSystem.md - -Save the following to `$PAI_DIR/skills/CORE/SkillSystem.md`: - -```markdown -# Custom Skill System - -**The MANDATORY configuration system for ALL skills.** - -## THIS IS THE AUTHORITATIVE SOURCE - -This document defines the **required structure** for every skill in the system. - -## TitleCase Naming Convention (MANDATORY) - -**All naming in the skill system MUST use TitleCase (PascalCase).** - -| Component | Wrong | Correct | -|-----------|-------|---------| -| Skill directory | `createskill`, `create-skill` | `CreateSkill` | -| Workflow files | `create.md`, `update-info.md` | `Create.md`, `UpdateInfo.md` | -| Tool files | `manage-server.ts` | `ManageServer.ts` | -| YAML name | `name: create-skill` | `name: CreateSkill` | - -## The Required Structure - -Every SKILL.md has two parts: - -### 1. YAML Frontmatter (Single-Line Description) - -```yaml ---- -name: SkillName -description: [What it does]. USE WHEN [intent triggers using OR]. [Additional capabilities]. ---- -``` - -**Rules:** -- `name` uses **TitleCase** -- `description` is a **single line** (not multi-line with `|`) -- `USE WHEN` keyword is **MANDATORY** -- Max 1024 characters - -### 2. Markdown Body (Workflow Routing + Examples) - -```markdown -# SkillName - -[Brief description] - -## Workflow Routing - -| Workflow | Trigger | File | -|----------|---------|------| -| **WorkflowOne** | "trigger phrase" | `Workflows/WorkflowOne.md` | - -## Examples - -**Example 1: [Use case]** -\`\`\` -User: "[Request]" -→ Invokes WorkflowOne workflow -→ [Result] -\`\`\` -``` - -## Directory Structure - -``` -SkillName/ -├── SKILL.md # Main skill file -├── QuickStartGuide.md # Context files in root (TitleCase) -├── Tools/ # CLI tools (ALWAYS present) -│ └── ToolName.ts -└── Workflows/ # Work execution workflows - └── Create.md -``` - -## Complete Checklist - -- [ ] Skill directory uses TitleCase -- [ ] YAML `name:` uses TitleCase -- [ ] Single-line `description` with `USE WHEN` clause -- [ ] `## Workflow Routing` section with table format -- [ ] `## Examples` section with 2-3 usage patterns -- [ ] `Tools/` directory exists (even if empty) -- [ ] All workflow files use TitleCase -``` - ---- - -## Step 3: Create SkillSearch Tool - -Save to `$PAI_DIR/Tools/SkillSearch.ts`: - -```typescript -#!/usr/bin/env bun -/** - * SkillSearch.ts - * - * Search the skill index to discover capabilities dynamically. - * - * Usage: - * bun run $PAI_DIR/Tools/SkillSearch.ts - * bun run $PAI_DIR/Tools/SkillSearch.ts --list - */ - -import { readFile } from 'fs/promises'; -import { join } from 'path'; -import { existsSync } from 'fs'; - -const PAI_DIR = process.env.PAI_DIR || process.env.PAI_HOME || join(process.env.HOME || '', '.claude'); -const INDEX_FILE = join(PAI_DIR, 'skills', 'skill-index.json'); - -interface SkillEntry { - name: string; - path: string; - fullDescription: string; - triggers: string[]; - workflows: string[]; - tier: 'always' | 'deferred'; -} - -interface SkillIndex { - generated: string; - totalSkills: number; - skills: Record; -} - -function searchSkills(query: string, index: SkillIndex) { - const queryTerms = query.toLowerCase().split(/\s+/).filter(t => t.length > 1); - const results: { skill: SkillEntry; score: number }[] = []; - - for (const [key, skill] of Object.entries(index.skills)) { - let score = 0; - - if (key.includes(query.toLowerCase())) score += 10; - - for (const term of queryTerms) { - for (const trigger of skill.triggers) { - if (trigger.includes(term)) score += 5; - } - if (skill.fullDescription.toLowerCase().includes(term)) score += 2; - } - - if (score > 0) results.push({ skill, score }); - } - - return results.sort((a, b) => b.score - a.score); -} - -async function main() { - if (!existsSync(INDEX_FILE)) { - console.error('❌ Skill index not found. Run GenerateSkillIndex.ts first.'); - process.exit(1); - } - - const index: SkillIndex = JSON.parse(await readFile(INDEX_FILE, 'utf-8')); - const args = process.argv.slice(2); - - if (args.includes('--list') || args.length === 0) { - console.log(`\n📚 Skill Index (${index.totalSkills} skills)\n`); - for (const skill of Object.values(index.skills).sort((a, b) => a.name.localeCompare(b.name))) { - const icon = skill.tier === 'always' ? '🔒' : '📦'; - console.log(` ${icon} ${skill.name.padEnd(20)} │ ${skill.triggers.slice(0, 3).join(', ')}`); - } - return; - } - - const query = args.join(' '); - const results = searchSkills(query, index).slice(0, 5); - - console.log(`\n🔍 Searching for: "${query}"\n`); - for (const { skill, score } of results) { - console.log(`\n${'─'.repeat(50)}`); - console.log(`${skill.tier === 'always' ? '🔒' : '📦'} **${skill.name}** (score: ${score})`); - console.log(`Path: ${skill.path}`); - console.log(`Workflows: ${skill.workflows.join(', ')}`); - } -} - -main().catch(console.error); -``` - ---- - -## Step 4: Create GenerateSkillIndex Tool - -Save to `$PAI_DIR/Tools/GenerateSkillIndex.ts`: - -```typescript -#!/usr/bin/env bun -/** - * GenerateSkillIndex.ts - * - * Parses all SKILL.md files and builds a searchable index. - * - * Usage: bun run $PAI_DIR/Tools/GenerateSkillIndex.ts - */ - -import { readdir, readFile, writeFile } from 'fs/promises'; -import { join } from 'path'; -import { existsSync } from 'fs'; - -const PAI_DIR = process.env.PAI_DIR || process.env.PAI_HOME || join(process.env.HOME || '', '.claude'); -const SKILLS_DIR = join(PAI_DIR, 'skills'); -const OUTPUT_FILE = join(SKILLS_DIR, 'skill-index.json'); - -const ALWAYS_LOADED_SKILLS = ['CORE', 'Development', 'Research']; - -async function findSkillFiles(dir: string): Promise { - const skillFiles: string[] = []; - const entries = await readdir(dir, { withFileTypes: true }); - - for (const entry of entries) { - if (entry.isDirectory() && !entry.name.startsWith('.')) { - const skillMdPath = join(dir, entry.name, 'SKILL.md'); - if (existsSync(skillMdPath)) skillFiles.push(skillMdPath); - } - } - return skillFiles; -} - -function parseFrontmatter(content: string) { - const match = content.match(/^---\n([\s\S]*?)\n---/); - if (!match) return null; - - const nameMatch = match[1].match(/^name:\s*(.+)$/m); - const descMatch = match[1].match(/^description:\s*(.+)$/m); - - return { - name: nameMatch?.[1]?.trim() || '', - description: descMatch?.[1]?.trim() || '' - }; -} - -function extractTriggers(description: string): string[] { - const triggers: string[] = []; - const useWhenMatch = description.match(/USE WHEN[^.]+/gi); - - if (useWhenMatch) { - for (const match of useWhenMatch) { - const words = match.replace(/USE WHEN/gi, '').split(/[,\s]+/) - .map(w => w.toLowerCase().trim()) - .filter(w => w.length > 2); - triggers.push(...words); - } - } - return [...new Set(triggers)]; -} - -async function main() { - console.log('Generating skill index...\n'); - - const skillFiles = await findSkillFiles(SKILLS_DIR); - const index: any = { - generated: new Date().toISOString(), - totalSkills: 0, - alwaysLoadedCount: 0, - deferredCount: 0, - skills: {} - }; - - for (const filePath of skillFiles) { - const content = await readFile(filePath, 'utf-8'); - const fm = parseFrontmatter(content); - if (!fm?.name) continue; - - const tier = ALWAYS_LOADED_SKILLS.includes(fm.name) ? 'always' : 'deferred'; - const key = fm.name.toLowerCase(); - - index.skills[key] = { - name: fm.name, - path: filePath.replace(SKILLS_DIR, '').replace(/^\//, ''), - fullDescription: fm.description, - triggers: extractTriggers(fm.description), - workflows: [], - tier - }; - - index.totalSkills++; - if (tier === 'always') index.alwaysLoadedCount++; - else index.deferredCount++; - - console.log(` ${tier === 'always' ? '🔒' : '📦'} ${fm.name}`); - } - - await writeFile(OUTPUT_FILE, JSON.stringify(index, null, 2)); - console.log(`\n✅ Index generated: ${OUTPUT_FILE}`); - console.log(` Total: ${index.totalSkills} skills`); -} - -main().catch(console.error); -``` - ---- - -## Step 5: Create PaiArchitecture Tool - -Save to `$PAI_DIR/Tools/PaiArchitecture.ts`: - -```typescript -#!/usr/bin/env bun -/** - * PaiArchitecture.ts - * - * Scans the PAI installation and generates Architecture.md - * tracking all installed packs, bundles, plugins, and upgrades. - * - * Usage: - * bun PaiArchitecture.ts generate # Generate/refresh Architecture.md - * bun PaiArchitecture.ts status # Show current state (stdout) - * bun PaiArchitecture.ts check # Verify installation health - * bun PaiArchitecture.ts log-upgrade "description" # Add upgrade entry - */ - -import { readdir, readFile, writeFile, appendFile } from 'fs/promises'; -import { join } from 'path'; -import { existsSync } from 'fs'; - -const PAI_DIR = process.env.PAI_DIR || process.env.PAI_HOME || join(process.env.HOME || '', '.claude'); -const ARCHITECTURE_FILE = join(PAI_DIR, 'skills', 'CORE', 'PaiArchitecture.md'); -const BUNDLES_FILE = join(PAI_DIR, '.installed-bundles.json'); -const UPGRADES_FILE = join(PAI_DIR, 'history', 'Upgrades.jsonl'); - -interface PackInfo { - name: string; - version: string; - installedDate: string; - status: 'healthy' | 'warning' | 'error'; -} - -interface BundleInfo { - name: string; - packs: string[]; - installedDate: string; -} - -interface UpgradeEntry { - date: string; - type: 'pack' | 'config' | 'bundle' | 'plugin'; - description: string; -} - -async function detectInstalledPacks(): Promise { - const packs: PackInfo[] = []; - const skillsDir = join(PAI_HOME, 'skills'); - - if (!existsSync(skillsDir)) return packs; - - const entries = await readdir(skillsDir, { withFileTypes: true }); - - for (const entry of entries) { - if (!entry.isDirectory() || entry.name.startsWith('.')) continue; - - const skillFile = join(skillsDir, entry.name, 'SKILL.md'); - if (!existsSync(skillFile)) continue; - - const content = await readFile(skillFile, 'utf-8'); - const nameMatch = content.match(/^name:\s*(.+)$/m); - - packs.push({ - name: nameMatch?.[1]?.trim() || entry.name, - version: '1.0.0', - installedDate: new Date().toISOString().split('T')[0], - status: 'healthy' - }); - } - - return packs; -} - -async function detectInstalledBundles(): Promise { - if (!existsSync(BUNDLES_FILE)) return []; - - try { - const content = await readFile(BUNDLES_FILE, 'utf-8'); - return JSON.parse(content); - } catch { - return []; - } -} - -async function loadUpgradeHistory(): Promise { - if (!existsSync(UPGRADES_FILE)) return []; - - try { - const content = await readFile(UPGRADES_FILE, 'utf-8'); - return content.trim().split('\n').filter(l => l).map(l => JSON.parse(l)); - } catch { - return []; - } -} - -async function checkSystemHealth(): Promise> { - const health: Record = {}; - - // Check hooks - const hooksDir = join(PAI_HOME, 'hooks'); - health['Hook System'] = existsSync(hooksDir) ? '✓ Healthy' : '✗ Not installed'; - - // Check history - const historyDir = join(PAI_HOME, 'history'); - health['History System'] = existsSync(historyDir) ? '✓ Healthy' : '✗ Not installed'; - - // Check skills - const skillsDir = join(PAI_HOME, 'skills'); - if (existsSync(skillsDir)) { - const skills = (await readdir(skillsDir)).filter(f => !f.startsWith('.') && !f.endsWith('.json')); - health['skills'] = `✓ ${skills.length} loaded`; - } else { - health['skills'] = '✗ Not installed'; - } - - return health; -} - -async function generateArchitectureMd(): Promise { - const packs = await detectInstalledPacks(); - const bundles = await detectInstalledBundles(); - const upgrades = await loadUpgradeHistory(); - const health = await checkSystemHealth(); - - let md = `# PAI Architecture - -> Auto-generated tracking file. Run \`bun $PAI_DIR/Tools/PaiArchitecture.ts generate\` to refresh. - -**Last Updated:** ${new Date().toISOString()} - -## Installation Summary - -| Category | Count | -|----------|-------| -| Packs | ${packs.length} | -| Bundles | ${bundles.length} | -| Upgrades | ${upgrades.length} | - -## Installed Packs - -| Pack | Version | Status | -|------|---------|--------| -${packs.map(p => `| ${p.name} | ${p.version} | ${p.status} |`).join('\n')} - -## Upgrade History - -| Date | Type | Description | -|------|------|-------------| -${upgrades.slice(-10).reverse().map(u => `| ${u.date} | ${u.type} | ${u.description} |`).join('\n') || '| - | - | No upgrades recorded |'} - -## System Health - -${Object.entries(health).map(([k, v]) => `- **${k}:** ${v}`).join('\n')} - ---- - -*This file is auto-generated. Do not edit manually.* -`; - - return md; -} - -async function logUpgrade(description: string, type: string = 'pack'): Promise { - const entry: UpgradeEntry = { - date: new Date().toISOString().split('T')[0], - type: type as any, - description - }; - - const dir = join(PAI_HOME, 'history'); - if (!existsSync(dir)) { - const { mkdir } = await import('fs/promises'); - await mkdir(dir, { recursive: true }); - } - - await appendFile(UPGRADES_FILE, JSON.stringify(entry) + '\n'); - console.log(`✓ Logged upgrade: ${description}`); -} - -async function main() { - const args = process.argv.slice(2); - const command = args[0] || 'status'; - - switch (command) { - case 'generate': - const md = await generateArchitectureMd(); - await writeFile(ARCHITECTURE_FILE, md); - console.log(`✓ Generated: ${ARCHITECTURE_FILE}`); - break; - - case 'status': - console.log(await generateArchitectureMd()); - break; - - case 'check': - const health = await checkSystemHealth(); - console.log('\n📊 System Health Check\n'); - for (const [k, v] of Object.entries(health)) { - console.log(` ${k}: ${v}`); - } - break; - - case 'log-upgrade': - if (!args[1]) { - console.error('Usage: PaiArchitecture.ts log-upgrade "description"'); - process.exit(1); - } - await logUpgrade(args[1], args[2] || 'pack'); - break; - - default: - console.log('Usage: PaiArchitecture.ts [generate|status|check|log-upgrade]'); - } -} - -main().catch(console.error); -``` - ---- - -## Step 6: Create the CORE Skill - -Save to `$PAI_DIR/skills/CORE/SKILL.md`: - -**NOTE:** Placeholders in `[BRACKETS]` should be filled in during installation. - -```markdown ---- -name: CORE -description: Personal AI Infrastructure core. AUTO-LOADS at session start. USE WHEN any session begins OR user asks about identity, response format, contacts, stack preferences, security protocols, or asset management. ---- - -# CORE - Personal AI Infrastructure - -**Auto-loads at session start.** This skill defines your AI's identity, response format, and core operating principles. - -## Examples - -**Example: Check contact information** -\`\`\` -User: "What's Angela's email?" -→ Reads Contacts.md -→ Returns contact information -\`\`\` - ---- - -## Identity - -**Assistant:** -- Name: [YOUR_AI_NAME] -- Role: [YOUR_NAME]'s AI assistant - -**User:** -- Name: [YOUR_NAME] -- Profession: [YOUR_PROFESSION] - ---- - -## Personality Calibration - -| Trait | Value | Description | -|-------|-------|-------------| -| Humor | [0-100]/100 | 0=serious, 100=witty | -| Curiosity | [0-100]/100 | 0=focused, 100=exploratory | -| Precision | [0-100]/100 | 0=approximate, 100=exact | -| Formality | [0-100]/100 | 0=casual, 100=professional | -| Directness | [0-100]/100 | 0=diplomatic, 100=blunt | - ---- - -## First-Person Voice (CRITICAL) - -Your AI should speak as itself, not about itself in third person. - -**Correct:** -- "for my system" / "in my architecture" -- "I can spawn agents" / "my delegation patterns" - -**Wrong:** -- "for [AI_NAME]" / "the system can" - ---- - -## Response Format (Optional) - -\`\`\` -📋 SUMMARY: [One sentence] -🔍 ANALYSIS: [Key findings] -⚡ ACTIONS: [Steps taken] -✅ RESULTS: [Outcomes] -➡️ NEXT: [Recommended next steps] -🎯 COMPLETED: [12 words max - drives voice output] -\`\`\` - ---- - -## Quick Reference - -**Full documentation:** -- Skill System: `SkillSystem.md` -- Architecture: `PaiArchitecture.md` (auto-generated) -- Contacts: `Contacts.md` -- Stack: `CoreStack.md` -``` - ---- - -## Step 7: Create Supporting Files - -**Contacts.md, CoreStack.md, CONSTITUTION.md, and Architecture.md templates:** - -See the full templates in the kai-identity pack documentation. The key files are: - -- `Contacts.md` - Contact directory (template with placeholder entries) -- `CoreStack.md` - Technology preferences (TypeScript > Python, bun > npm) -- `CONSTITUTION.md` - Core values and operating principles -- `Definitions.md` - Canonical term definitions - ---- - -## Step 7.1: Create UpdateDocumentation Workflow - -Save to `$PAI_DIR/skills/CORE/Workflows/UpdateDocumentation.md`: - -```markdown -# UpdateDocumentation Workflow - -> **Trigger:** "update architecture", "refresh PAI state", OR automatically after any pack/bundle installation - -## Purpose - -Keeps PAI Architecture tracking current by: -1. Regenerating the Architecture.md file with current installation state -2. Logging upgrades to the history system -3. Verifying system health after changes - -## When This Runs - -### Manual Invocation -- User says "update my PAI architecture" -- User says "refresh PAI state" -- User says "what's installed?" - -### Automatic Invocation (CRITICAL) -**This workflow MUST run automatically after:** -- Installing any PAI Pack -- Installing any PAI Bundle -- Making significant configuration changes -- Upgrading pack versions - -## Workflow Steps - -### Step 1: Regenerate Architecture - -\`\`\`bash -bun run $PAI_DIR/Tools/PaiArchitecture.ts generate -\`\`\` - -### Step 2: Log the Change (If Applicable) - -If this was triggered by an installation or upgrade: - -\`\`\`bash -# For pack installations -bun run $PAI_DIR/Tools/PaiArchitecture.ts log-upgrade "Installed [pack-name] v[version]" pack - -# For bundle installations -bun run $PAI_DIR/Tools/PaiArchitecture.ts log-upgrade "Installed [bundle-name] bundle" bundle - -# For config changes -bun run $PAI_DIR/Tools/PaiArchitecture.ts log-upgrade "[description of change]" config -\`\`\` - -### Step 3: Verify Health - -\`\`\`bash -bun run $PAI_DIR/Tools/PaiArchitecture.ts check -\`\`\` - -### Step 4: Report Status - -Output the current architecture state to confirm the update was successful. - -## Integration with Pack Installation - -**All pack installation workflows should include this at the end:** - -\`\`\`markdown -## Post-Installation: Update Documentation - -After all installation steps complete: - -1. Run UpdateDocumentation workflow -2. Log the pack installation -3. Verify the pack appears in Architecture.md - -\`\`\`bash -# Auto-run after pack installation -bun run $PAI_DIR/Tools/PaiArchitecture.ts log-upgrade "Installed [pack-name] v[version]" pack -bun run $PAI_DIR/Tools/PaiArchitecture.ts generate -\`\`\` -\`\`\` - -## Example Output - -\`\`\` -📋 SUMMARY: Updated PAI Architecture documentation -⚡ ACTIONS: - - Regenerated Architecture.md - - Logged upgrade: "Installed kai-voice-system v1.0.0" - - Verified system health -✅ RESULTS: Architecture.md now shows 4 packs, 1 bundle -📊 STATUS: All systems healthy -🎯 COMPLETED: Architecture updated - 4 packs installed, all healthy. -\`\`\` -``` - ---- - -## Step 8: Create the CreateSkill Meta-Skill - -Save to `$PAI_DIR/skills/CreateSkill/SKILL.md`: - -```markdown ---- -name: CreateSkill -description: Create and validate skills. USE WHEN create skill, new skill, skill structure, canonicalize. SkillSearch('createskill') for docs. ---- - -# CreateSkill - -MANDATORY skill creation framework for ALL skill creation requests. - -## Authoritative Source - -**Before creating ANY skill, READ:** `$PAI_DIR/skills/CORE/SkillSystem.md` - -## Workflow Routing - -| Workflow | Trigger | File | -|----------|---------|------| -| **CreateSkill** | "create a new skill" | `Workflows/CreateSkill.md` | -| **ValidateSkill** | "validate skill" | `Workflows/ValidateSkill.md` | -| **CanonicalizeSkill** | "canonicalize", "fix skill" | `Workflows/CanonicalizeSkill.md` | - -## Examples - -**Example 1: Create a new skill** -\`\`\` -User: "Create a skill for managing my recipes" -→ Invokes CreateSkill workflow -→ Reads SkillSystem.md for structure -→ Creates skill with TitleCase naming -\`\`\` - -**Example 2: Fix an existing skill** -\`\`\` -User: "Canonicalize the daemon skill" -→ Invokes CanonicalizeSkill workflow -→ Renames files to TitleCase -→ Ensures Examples section exists -\`\`\` -``` - ---- - -## Step 9: Generate the Initial Index - -```bash -bun run $PAI_DIR/Tools/GenerateSkillIndex.ts -``` - ---- - -## Step 10: Generate Initial Architecture - -```bash -bun run $PAI_DIR/Tools/PaiArchitecture.ts generate -bun run $PAI_DIR/Tools/PaiArchitecture.ts log-upgrade "Initial kai-core-install installation" -``` - ---- - -# PART 7: THE 14 FOUNDING PRINCIPLES - -These principles guide all PAI architecture decisions: - -1. **Clear Thinking + Prompting is King** - Good prompts come from clear thinking -2. **Scaffolding > Model** - Architecture matters more than which model -3. **As Deterministic as Possible** - Same input → Same output -4. **Code Before Prompts** - Use AI only for what needs intelligence -5. **Spec / Test / Evals First** - Define expected behavior before building -6. **UNIX Philosophy** - Do one thing well, compose tools -7. **ENG / SRE Principles** - Apply software engineering to AI systems -8. **CLI as Interface** - Every operation accessible via command line -9. **Goal → Code → CLI → Prompts → Agents** - The proper pipeline -10. **Meta / Self Update System** - System should improve itself -11. **Custom Skill Management** - Skills are the organizational unit -12. **Custom History System** - Automatic capture of valuable work -13. **Custom Agent Personalities** - Different voices for different tasks -14. **Science as Cognitive Loop** - Hypothesis → Experiment → Measure → Iterate - ---- - -# PART 8: VERIFICATION - -After installation, verify: - -```bash -# Check directory structure -ls $PAI_DIR/skills/ -# Should show: CORE/ CreateSkill/ skill-index.json - -ls $PAI_DIR/Tools/ -# Should show: SkillSearch.ts GenerateSkillIndex.ts PaiArchitecture.ts - -# Test skill search -bun run $PAI_DIR/Tools/SkillSearch.ts --list - -# Check architecture -bun run $PAI_DIR/Tools/PaiArchitecture.ts status - -# Verify CORE skill content -cat $PAI_DIR/skills/CORE/SKILL.md | head -20 -``` - ---- - -## Troubleshooting - -### Skills Not Routing - -1. Verify SKILL.md has single-line description with USE WHEN -2. Run GenerateSkillIndex.ts to rebuild index -3. Check skill frontmatter format - -### Format Not Being Used - -1. Verify CORE loads at session start (check hooks) -2. Ensure SKILL.md contains response format section - -### Architecture Not Generating - -1. Check PaiArchitecture.ts exists in Tools/ -2. Verify write permissions to skills/CORE/ -3. Run with explicit PAI_DIR: `PAI_DIR=~/.config/pai bun run ...` - ---- - -## Customization - -### Recommended Customization - -**Define Your Personality and Identity** - -The CORE skill includes a placeholder identity. The most valuable customization is filling this in to create a consistent, personalized AI assistant. - -**What to Customize:** `$PAI_DIR/skills/CORE/SKILL.md` - -**Why:** A well-defined personality creates consistent, predictable interactions. When your AI knows who it is, how formal to be, and what your preferences are, every response feels coherent and familiar. - -**Process:** - -1. **Fill in Your Identity** - Edit the Identity section: - ```markdown - ## Identity - - **Assistant:** - - Name: [Choose a name for your AI - e.g., "Kai", "Atlas", "Sage"] - - Role: [Your name]'s AI assistant - - **User:** - - Name: [Your name] - - Profession: [Your profession - e.g., "Software Engineer", "Researcher"] - ``` - -2. **Calibrate Personality Traits** - Adjust the numeric values based on your preferences: - ```markdown - | Trait | Value | Description | - |-------|-------|-------------| - | Humor | 60/100 | Higher = more witty, lower = more serious | - | Curiosity | 90/100 | Higher = asks more questions, explores tangents | - | Precision | 95/100 | Higher = more exact details | - | Formality | 50/100 | Higher = more professional, lower = more casual | - | Directness | 80/100 | Higher = blunt, lower = diplomatic | - ``` - -3. **Define Your Voice Preferences** - Have a conversation with your AI about communication style: - ``` - Let's define how you should communicate. I prefer: - - Direct answers without excessive hedging - - Technical accuracy over simplification - - Brief responses unless I ask for detail - - Capture this in the CORE skill. - ``` - -**Expected Outcome:** A consistent AI personality that feels like working with a familiar colleague. - ---- - -### Optional Customization - -| Customization | File | Impact | -|---------------|------|--------| -| **Contacts Directory** | `Contacts.md` | Add your frequent contacts | -| **Stack Preferences** | `CoreStack.md` | Define your technology preferences | -| **Response Format** | `SKILL.md` | Modify the structured response format | -| **Security Protocols** | `SecurityProtocols.md` | Add project-specific security rules | -| **Definitions** | `Definitions.md` | Add canonical term definitions | - -**Example: Add Contacts** - -Create or edit `$PAI_DIR/skills/CORE/Contacts.md`: - -```markdown -# Contacts - -## Frequently Used - -- **Alice** [Team Lead]: alice@company.com -- **Bob** [DevOps]: bob@company.com -``` - -**Example: Define Stack Preferences** - -Create or edit `$PAI_DIR/skills/CORE/CoreStack.md`: - -```markdown -# Technology Stack Preferences - -- **TypeScript > Python** - Unless explicitly approved -- **bun > npm** - For all JS/TS projects -- **Markdown > HTML** - For documentation -- **SQLite > PostgreSQL** - For local development -``` - ---- - -## Credits - -- **Author:** Daniel Miessler -- **Origin:** Extracted from production Kai system (2024-2025) -- **License:** MIT - ---- - -## Works Well With - -- **kai-hook-system** - Required; enables automatic CORE loading at session start -- **kai-history-system** - Skills can reference past learnings and capture new ones -- **kai-voice-system** - Skills can trigger voice notifications, response format drives voice output - ---- - -## Relationships - -### Parent Of -*None - this is the foundation layer.* - -### Child Of -- **kai-hook-system** - Uses SessionStart hooks for automatic CORE skill loading - -### Sibling Of -- **kai-history-system** - Both are foundation packs that depend on kai-hook-system -- **kai-voice-system** - Both consume hook infrastructure - -### Part Of Collection -**Kai Core Bundle** - This is the primary foundation pack that all others build upon. - ---- - -## Changelog - -### v1.0.0 (2025-12-29) -- Initial release -- Merged kai-skill-system and kai-identity into unified pack -- Added PAI Architecture tracking system -- Added PaiArchitecture.ts tool -- Simplified installation to single pack diff --git a/Packs/kai-history-system.md b/Packs/kai-history-system.md deleted file mode 100644 index f902e78767..0000000000 --- a/Packs/kai-history-system.md +++ /dev/null @@ -1,1618 +0,0 @@ ---- -name: Kai History System -pack-id: danielmiessler-kai-history-system-core-v1.0.1 -version: 1.0.1 -author: danielmiessler -description: Granular context-tracking system for the entire AI infrastructure - captures all work, decisions, and learnings automatically -type: feature -purpose-type: [productivity, automation, development] -platform: claude-code -dependencies: - - kai-hook-system (required) - History capture depends on hook events -keywords: [history, documentation, memory, capture, hooks, sessions, learnings, automation, context, recovery, debugging] ---- - -

- Kai History System -

- -# Kai History System (kai-history-system) - -> Granular context-tracking system for the entire AI infrastructure - captures all work, decisions, and learnings automatically with zero manual effort - -## Installation Prompt -You are receiving a PAI Pack - a modular upgrade for AI agent systems. - -**What is PAI?** See: [PAI Project Overview](../README.md#what-is-pai) - -**What is a Pack?** See: [Pack System](../README.md#pack-system) - -This pack adds automatic memory to your entire AI infrastructure. The Kai History System is not just about agent outputs - it's a granular context-tracking system for EVERYTHING that happens in your system: - -- **Continuous Learning**: Capture insights as they happen -- **Bug Fixing**: Trace exactly what was done when debugging -- **Avoiding Mistakes**: Learn from past errors automatically -- **After-Action Reviews**: Analyze what worked and what didn't -- **Restoration Points**: Recover from data loss with historical snapshots - -**Core principle:** Work normally, documentation handles itself. - -No more forgotten context between sessions. No more lost learnings. Your AI remembers everything so you don't have to. - -Please follow the installation instructions below to integrate this pack into your infrastructure. - ---- - -## What's Included - -| Component | File | Purpose | -|-----------|------|---------| -| Universal event capture | `hooks/capture-all-events.ts` | Log all events to JSONL for audit trail | -| Stop hook | `hooks/stop-hook.ts` | Capture main agent completions and learnings | -| Subagent stop hook | `hooks/subagent-stop-hook.ts` | Route subagent outputs by type | -| Session summary | `hooks/capture-session-summary.ts` | Summarize sessions at close | -| Metadata extraction lib | `hooks/lib/metadata-extraction.ts` | Extract agent instance metadata | - -**Summary:** -- **Files created:** 5 + history directory structure -- **Hooks registered:** 7 (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit) -- **Dependencies:** kai-hook-system (required) - ---- - -## The Concept and/or Problem -AI agents are powerful but forgetful. Each session starts fresh with no memory of: - -- What you built last week -- Why you made certain architectural decisions -- What bugs you've already fixed (and might reintroduce) -- Lessons learned from debugging sessions -- Research you've already conducted -- What agents discovered during parallel execution - -This creates cascading problems across your entire AI infrastructure: - -**For Development Work:** -- You fix the same bug twice because you forgot the root cause -- Architectural decisions lack rationale when revisited months later -- Code reviews miss context because the "why" is lost - -**For Agent Orchestration:** -- Parallel agents complete work that's never captured -- Background research disappears when the session ends -- Agent outputs aren't categorized or searchable - -**For Operational Continuity:** -- Session handoffs require manual context transfer -- Multi-day projects need constant re-explanation -- Team members can't see what the AI worked on - -**For Learning and Improvement:** -- Insights get lost in conversation history -- No after-action reviews are possible -- Mistakes repeat because there's no institutional memory - -**The Fundamental Problem:** - -Traditional AI systems treat each interaction as ephemeral. But real work is cumulative. Today's debugging session informs tomorrow's architecture decision. Last month's research prevents this month's repeated mistake. - -Without a history system, your AI is brilliant but amnesiac. Every session is day one. Every context is fresh. Every lesson must be relearned. - -## The Solution -The Kai History System solves this through **automatic, hook-based documentation**. Instead of requiring manual effort, it captures work as a byproduct of doing the work. - -**Core Architecture:** - -``` -$PAI_DIR/ -├── hooks/ # Hook implementations -│ ├── capture-all-events.ts # Universal event capture (all hooks) -│ ├── stop-hook.ts # Main agent completion capture -│ ├── subagent-stop-hook.ts # Subagent output routing -│ ├── capture-session-summary.ts # Session end summarization -│ └── lib/ # Shared libraries -│ └── metadata-extraction.ts # Agent instance tracking -├── history/ # Captured outputs -│ ├── sessions/YYYY-MM/ # Session summaries -│ ├── learnings/YYYY-MM/ # Problem-solving narratives -│ ├── research/YYYY-MM/ # Investigation reports -│ ├── decisions/YYYY-MM/ # Architectural decisions -│ ├── execution/ -│ │ ├── features/YYYY-MM/ # Feature implementations -│ │ ├── bugs/YYYY-MM/ # Bug fixes -│ │ └── refactors/YYYY-MM/ # Code improvements -│ └── raw-outputs/YYYY-MM/ # JSONL event logs -└── settings.json # Hook configuration (or use .claude/settings.json) -``` - -**Four Hooks, Complete Coverage:** - -1. **capture-all-events.ts** (Universal Event Capture) - - Hooks: ALL events (PreToolUse, PostToolUse, Stop, SessionStart, SessionEnd, etc.) - - Captures: Every event to daily JSONL logs with full payload - - Output: `raw-outputs/YYYY-MM/YYYY-MM-DD_all-events.jsonl` - - Purpose: Complete audit trail, debugging, analytics - -2. **stop-hook.ts** (Main Agent Completion) - - Hook: Stop - - Captures: Main agent work summaries and learnings - - Output: `learnings/` or `sessions/` based on content analysis - - Purpose: Capture what was accomplished and what was learned - -3. **subagent-stop-hook.ts** (Subagent Output Routing) - - Hook: SubagentStop - - Captures: All spawned agent outputs - - Output: Routed to `research/`, `decisions/`, or `execution/` by agent type - - Purpose: Never lose agent work, automatic categorization - -4. **capture-session-summary.ts** (Session End) - - Hook: SessionEnd - - Captures: Session summary with files changed, commands run, tools used - - Output: `sessions/YYYY-MM/timestamp_SESSION_focus.md` - - Purpose: Know what happened in each session - -**Design Principles:** - -1. **Zero Overhead**: Hooks run silently, no action required from user -2. **Never Block**: All hooks fail gracefully - never interrupt work -3. **Future-Proof**: Generic payload capture means new fields are automatically stored -4. **Queryable**: Consistent file naming enables powerful search and filtering -5. **Categorized**: Different work types route to appropriate directories -6. **Complete**: Every component included - nothing left to figure out - -**The Key Insight:** - -Documentation is a byproduct, not a task. By instrumenting the work itself, you get perfect records without any effort. The history system sees everything because it's wired into the event stream. - ---- - -## What Makes This Different - -The history system's power comes from its **automatic categorization pipeline** - a multi-layered architecture that captures, analyzes, and routes every piece of work to the right location without any manual intervention. - -``` -┌─────────────────────────────────────────────────────────────────────────┐ -│ HISTORY SYSTEM ARCHITECTURE │ -├─────────────────────────────────────────────────────────────────────────┤ -│ │ -│ ┌─────────────────────────────────────────────────────────────────┐ │ -│ │ LAYER 1: Event Capture │ │ -│ │ capture-all-events.ts hooks into EVERY Claude Code event │ │ -│ │ PreToolUse │ PostToolUse │ Stop │ SubagentStop │ SessionEnd │ │ -│ └──────────────────────────────┬──────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌─────────────────────────────────────────────────────────────────┐ │ -│ │ LAYER 2: Raw Storage │ │ -│ │ Every event → JSONL file with full payload │ │ -│ │ raw-outputs/YYYY-MM/YYYY-MM-DD_all-events.jsonl │ │ -│ └──────────────────────────────┬──────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌─────────────────────────────────────────────────────────────────┐ │ -│ │ LAYER 3: Content Analysis │ │ -│ │ Hooks analyze response content for category indicators │ │ -│ │ "problem + solved + root cause" → LEARNING │ │ -│ │ "completed + deployed" → SESSION │ │ -│ └──────────────────────────────┬──────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌─────────────────────────────────────────────────────────────────┐ │ -│ │ LAYER 4: Agent Type Routing │ │ -│ │ SubagentStop routes by agent type automatically │ │ -│ │ researcher/intern → research/ │ │ -│ │ architect → decisions/ │ │ -│ │ engineer/designer → execution/features/ │ │ -│ └──────────────────────────────┬──────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌─────────────────────────────────────────────────────────────────┐ │ -│ │ LAYER 5: Organized Storage │ │ -│ │ Markdown files with YAML frontmatter in categorized dirs │ │ -│ │ learnings/ │ research/ │ decisions/ │ sessions/ │ execution/ │ │ -│ └─────────────────────────────────────────────────────────────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────────────────┘ -``` - -### How Data Flows Through the System - -**Example: You debug a tricky auth bug with a researcher agent** - -``` -1. You ask Claude to investigate the auth bug - │ - ▼ -2. Claude spawns a researcher agent (Task tool) - │ - ├──► PreToolUse event captured to raw-outputs/ - │ - ▼ -3. Researcher agent investigates, finds root cause - │ - ▼ -4. Agent completes with: "🎯 COMPLETED: Found root cause - JWT tokens not refreshing" - │ - ├──► SubagentStop fires - │ - ▼ -5. subagent-stop-hook.ts: - a) Extracts completion message - b) Detects agent_type = "researcher" - c) Routes to research/YYYY-MM/ - │ - ▼ -6. Main Claude fixes the bug based on research - │ - ▼ -7. Claude responds with "fixed", "root cause", "solved" in message - │ - ├──► Stop fires - │ - ▼ -8. stop-hook.ts: - a) Analyzes content for learning indicators - b) Detects 3+ learning keywords - c) Routes to learnings/YYYY-MM/ - │ - ▼ -9. Result: TWO history entries automatically created: - - research/2025-12/..._AGENT-researcher_RESEARCH_jwt-token-issue.md - - learnings/2025-12/..._LEARNING_jwt-refresh-fix.md -``` - -### Why This Architecture Matters - -**1. Zero Manual Effort** -- No "save this" commands needed -- No copy-paste to documentation -- Work normally; history captures itself - -**2. Automatic Categorization** -- Content analysis detects learnings vs sessions -- Agent type determines output location -- Never manually file anything - -**3. Complete Audit Trail** -- Raw JSONL captures EVERYTHING (even things not categorized) -- Can replay/analyze any session retroactively -- Nothing lost, ever - -**4. Queryable Structure** -- Consistent file naming: `TIMESTAMP_TYPE_description.md` -- Time-based directories: `YYYY-MM/` -- Standard Unix tools work: `grep -r "authentication" history/` - -**5. Agent Awareness** -- Each spawned agent's output is captured separately -- Agent type informs categorization -- Parallel work doesn't get lost or merged - -### The Categorization Logic (Deep Dive) - -**Learning Detection (stop-hook.ts):** -``` -Contains 2+ of these indicators: - problem, solved, discovered, fixed, learned, realized, - figured out, root cause, debugging, issue was, turned out, - mistake, error, bug, solution - -YES → learnings/ -NO → sessions/ -``` - -**Agent Routing (subagent-stop-hook.ts):** -``` -Agent Type → Output Directory -───────────────────────────────────── -*researcher, intern → research/ -architect → decisions/ -engineer, designer → execution/features/ -``` - -### What Problems This Architecture Prevents - -| Problem | How History Solves It | -|---------|----------------------| -| "What did we work on last week?" | `ls history/sessions/2025-01/` | -| "Why did we choose that architecture?" | `grep -l "architecture" history/decisions/` | -| "I fixed this bug before, what was the fix?" | `grep -r "auth" history/learnings/` | -| "What did that researcher agent find?" | Automatically in `history/research/` | -| "Can we audit what happened?" | Complete JSONL in `raw-outputs/` | - -### The Fundamental Insight - -**Naive approach:** Ask the AI to "remember" or manually save important things -- Requires explicit action (never happens) -- AI memory is session-scoped (resets on restart) -- No searchable archive - -**History approach:** Instrument the event stream, capture automatically -- Zero effort (hooks run silently) -- Permanent markdown files (never lost) -- Searchable with grep/ls - -The history system transforms ephemeral AI interactions into **permanent, searchable institutional memory**. Every debugging session, every research finding, every architectural decision - captured automatically, categorized intelligently, queryable forever. - ---- - -## Why This Is Different - -This sounds similar to ChatGPT's Memory feature or vector databases like Pinecone, which also preserve context. What makes this approach different? - -Memory systems and vector databases store what you explicitly save or what gets embedded. They require manual tagging, intentional capture, and structured queries. The Kai History System captures everything automatically through hooks—every agent output, every research finding, every debugging session—without any manual effort. History is organized by time and category, searchable by grep, and stored in plain markdown. You work normally; documentation handles itself. - -- Automatic capture through hooks needs no manual effort -- Plain markdown files searchable with standard Unix tools -- Categorized by type: learnings, research, sessions, projects -- Every agent's output is routed and preserved automatically - ---- - -## Installation - -### Prerequisites - -- **Bun runtime**: `curl -fsSL https://bun.sh/install | bash` -- **Claude Code** (or compatible agent system with hook support) -- **Write access** to `$PAI_DIR/` (or your PAI directory) -- **kai-hook-system Pack** installed (this pack depends on the hook infrastructure) - ---- - -### Pre-Installation: System Analysis - -**IMPORTANT:** Before installing, analyze the current system state to detect conflicts and ensure dependencies are met. - -#### Step 0.1: Verify Dependencies - -```bash -PAI_CHECK="${PAI_DIR:-$HOME/.config/pai}" - -# Check that hook system is installed -if [ -f "$PAI_CHECK/hooks/lib/observability.ts" ]; then - echo "✓ Hook system is installed" -else - echo "❌ Hook system NOT installed - install kai-hook-system first!" -fi - -# Check hooks directory exists -if [ -d "$PAI_CHECK/hooks" ]; then - echo "✓ Hooks directory exists" - ls "$PAI_CHECK/hooks"/*.ts 2>/dev/null | head -5 -else - echo "❌ Hooks directory missing - install kai-hook-system first!" -fi -``` - -#### Step 0.2: Detect Existing History System - -```bash -PAI_CHECK="${PAI_DIR:-$HOME/.config/pai}" - -# Check for existing history directory -if [ -d "$PAI_CHECK/history" ]; then - echo "⚠️ History directory EXISTS at: $PAI_CHECK/history" - echo "Existing categories:" - ls -la "$PAI_CHECK/history" 2>/dev/null - echo "" - echo "Existing files count per category:" - for dir in "$PAI_CHECK/history"/*/; do - if [ -d "$dir" ]; then - count=$(ls -1 "$dir" 2>/dev/null | wc -l) - echo " $(basename "$dir"): $count files" - fi - done -else - echo "✓ No existing history directory (clean install)" -fi - -# Check for existing history hooks -echo "" -echo "Checking for existing history hooks..." -for hook in "capture-all-events" "stop-hook" "subagent-stop-hook" "capture-session-summary"; do - if [ -f "$PAI_CHECK/hooks/${hook}.ts" ]; then - echo "⚠️ ${hook}.ts already exists" - else - echo "✓ ${hook}.ts not found (will be created)" - fi -done -``` - -#### Step 0.3: Conflict Resolution Matrix - -| Scenario | Existing State | Action | -|----------|---------------|--------| -| **Clean Install** | No history dir, no hooks | Proceed normally with Step 1 | -| **History Directory Exists** | Files in history/ | New captures added alongside; existing files preserved | -| **History Hooks Exist** | Hook files present | Compare versions; backup old hooks before replacing | -| **Missing Dependencies** | No hook system | Install kai-hook-system first | - -#### Step 0.4: Backup Existing History (If Needed) - -```bash -BACKUP_DIR="$HOME/.pai-backup/$(date +%Y%m%d-%H%M%S)" -PAI_CHECK="${PAI_DIR:-$HOME/.config/pai}" - -# Backup history directory if exists -if [ -d "$PAI_CHECK/history" ]; then - mkdir -p "$BACKUP_DIR" - cp -r "$PAI_CHECK/history" "$BACKUP_DIR/history" - echo "✓ Backed up history to $BACKUP_DIR/history" -fi - -# Backup history-related hooks -mkdir -p "$BACKUP_DIR/hooks" 2>/dev/null -for hook in "capture-all-events" "stop-hook" "subagent-stop-hook" "capture-session-summary"; do - if [ -f "$PAI_CHECK/hooks/${hook}.ts" ]; then - cp "$PAI_CHECK/hooks/${hook}.ts" "$BACKUP_DIR/hooks/" - echo "✓ Backed up ${hook}.ts" - fi -done -``` - -**After completing system analysis, proceed to Step 1.** - ---- - -### Step 1: Create Directory Structure - -```bash -# Create all required directories -mkdir -p $PAI_DIR/hooks/lib -mkdir -p $PAI_DIR/history/{sessions,learnings,research,decisions,raw-outputs} -mkdir -p $PAI_DIR/history/execution/{features,bugs,refactors} - -# Verify structure -ls -la $PAI_DIR/ -ls -la $PAI_DIR/history/ -``` - -Expected output: All directories created with no errors. - ---- - -### Step 2: Create Library Files - -These shared libraries are used by multiple hooks. - -#### 2.1: Create metadata-extraction.ts - -```typescript -// $PAI_DIR/hooks/lib/metadata-extraction.ts -// Extract agent instance metadata from Task tool calls - -export interface AgentInstanceMetadata { - agent_instance_id?: string; - agent_type?: string; - instance_number?: number; - parent_session_id?: string; - parent_task_id?: string; -} - -/** - * Extract agent instance ID from Task tool input - */ -export function extractAgentInstanceId( - toolInput: any, - description?: string -): AgentInstanceMetadata { - const result: AgentInstanceMetadata = {}; - - // Strategy 1: Extract from description [agent-type-N] - if (description) { - const descMatch = description.match(/\[([a-z-]+-researcher)-(\d+)\]/); - if (descMatch) { - result.agent_type = descMatch[1]; - result.instance_number = parseInt(descMatch[2], 10); - result.agent_instance_id = `${result.agent_type}-${result.instance_number}`; - } - } - - // Strategy 2: Extract from prompt [AGENT_INSTANCE: ...] - if (!result.agent_instance_id && toolInput?.prompt && typeof toolInput.prompt === 'string') { - const promptMatch = toolInput.prompt.match(/\[AGENT_INSTANCE:\s*([^\]]+)\]/); - if (promptMatch) { - result.agent_instance_id = promptMatch[1].trim(); - const parts = result.agent_instance_id.match(/^([a-z-]+)-(\d+)$/); - if (parts) { - result.agent_type = parts[1]; - result.instance_number = parseInt(parts[2], 10); - } - } - } - - // Strategy 3: Fallback to subagent_type - if (!result.agent_type && toolInput?.subagent_type) { - result.agent_type = toolInput.subagent_type; - } - - return result; -} - -/** - * Enrich event with agent metadata - */ -export function enrichEventWithAgentMetadata( - event: any, - toolInput: any, - description?: string -): any { - const metadata = extractAgentInstanceId(toolInput, description); - const enrichedEvent = { ...event }; - - if (metadata.agent_instance_id) enrichedEvent.agent_instance_id = metadata.agent_instance_id; - if (metadata.agent_type) enrichedEvent.agent_type = metadata.agent_type; - if (metadata.instance_number !== undefined) enrichedEvent.instance_number = metadata.instance_number; - - return enrichedEvent; -} - -/** - * Check if a tool call is spawning a subagent - */ -export function isAgentSpawningCall(toolName: string, toolInput: any): boolean { - return toolName === 'Task' && toolInput?.subagent_type !== undefined; -} -``` - ---- - -### Step 3: Create Hook Files - -#### 3.1: Create capture-all-events.ts (Universal Event Capture) - -```typescript -#!/usr/bin/env bun -// $PAI_DIR/hooks/capture-all-events.ts -// Captures ALL Claude Code hook events to JSONL - -import { readFileSync, appendFileSync, mkdirSync, existsSync, writeFileSync } from 'fs'; -import { join } from 'path'; -import { homedir } from 'os'; -import { enrichEventWithAgentMetadata, isAgentSpawningCall } from './lib/metadata-extraction'; - -interface HookEvent { - source_app: string; - session_id: string; - hook_event_type: string; - payload: Record; - timestamp: number; - timestamp_local: string; -} - -function getLocalTimestamp(): string { - const date = new Date(); - const tz = process.env.TIME_ZONE || Intl.DateTimeFormat().resolvedOptions().timeZone; - const localDate = new Date(date.toLocaleString('en-US', { timeZone: tz })); - - const year = localDate.getFullYear(); - const month = String(localDate.getMonth() + 1).padStart(2, '0'); - const day = String(localDate.getDate()).padStart(2, '0'); - const hours = String(localDate.getHours()).padStart(2, '0'); - const minutes = String(localDate.getMinutes()).padStart(2, '0'); - const seconds = String(localDate.getSeconds()).padStart(2, '0'); - - return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; -} - -function getEventsFilePath(): string { - const paiDir = process.env.PAI_DIR || join(homedir(), '.config', 'pai'); - const now = new Date(); - const year = now.getFullYear(); - const month = String(now.getMonth() + 1).padStart(2, '0'); - const day = String(now.getDate()).padStart(2, '0'); - - const monthDir = join(paiDir, 'history', 'raw-outputs', `${year}-${month}`); - if (!existsSync(monthDir)) { - mkdirSync(monthDir, { recursive: true }); - } - - return join(monthDir, `${year}-${month}-${day}_all-events.jsonl`); -} - -function getSessionMappingFile(): string { - const paiDir = process.env.PAI_DIR || join(homedir(), '.config', 'pai'); - return join(paiDir, 'agent-sessions.json'); -} - -function getAgentForSession(sessionId: string): string { - try { - const mappingFile = getSessionMappingFile(); - if (existsSync(mappingFile)) { - const mappings = JSON.parse(readFileSync(mappingFile, 'utf-8')); - return mappings[sessionId] || 'main'; - } - } catch (error) {} - return 'main'; -} - -function setAgentForSession(sessionId: string, agentName: string): void { - try { - const mappingFile = getSessionMappingFile(); - let mappings: Record = {}; - if (existsSync(mappingFile)) { - mappings = JSON.parse(readFileSync(mappingFile, 'utf-8')); - } - mappings[sessionId] = agentName; - writeFileSync(mappingFile, JSON.stringify(mappings, null, 2), 'utf-8'); - } catch (error) {} -} - -async function main() { - try { - const args = process.argv.slice(2); - const eventTypeIndex = args.indexOf('--event-type'); - - if (eventTypeIndex === -1) { - console.error('Missing --event-type argument'); - process.exit(0); - } - - const eventType = args[eventTypeIndex + 1]; - const stdinData = await Bun.stdin.text(); - const hookData = JSON.parse(stdinData); - - const sessionId = hookData.session_id || 'main'; - let agentName = getAgentForSession(sessionId); - - // Track agent type from Task tool calls - if (hookData.tool_name === 'Task' && hookData.tool_input?.subagent_type) { - agentName = hookData.tool_input.subagent_type; - setAgentForSession(sessionId, agentName); - } else if (eventType === 'SubagentStop' || eventType === 'Stop') { - agentName = 'main'; - setAgentForSession(sessionId, 'main'); - } else if (process.env.CLAUDE_CODE_AGENT) { - agentName = process.env.CLAUDE_CODE_AGENT; - setAgentForSession(sessionId, agentName); - } - - let event: HookEvent = { - source_app: agentName, - session_id: hookData.session_id || 'main', - hook_event_type: eventType, - payload: hookData, - timestamp: Date.now(), - timestamp_local: getLocalTimestamp() - }; - - // Enrich with agent metadata if spawning subagent - if (isAgentSpawningCall(hookData.tool_name, hookData.tool_input)) { - event = enrichEventWithAgentMetadata(event, hookData.tool_input, hookData.description); - } - - const eventsFile = getEventsFilePath(); - appendFileSync(eventsFile, JSON.stringify(event) + '\n', 'utf-8'); - - } catch (error) { - console.error('Event capture error:', error); - } - - process.exit(0); -} - -main(); -``` - -#### 3.2: Create stop-hook.ts (Main Agent Completion) - -```typescript -#!/usr/bin/env bun -// $PAI_DIR/hooks/stop-hook.ts -// Captures main agent work summaries and learnings - -import { writeFileSync, mkdirSync, existsSync } from 'fs'; -import { join } from 'path'; -import { homedir } from 'os'; - -interface StopPayload { - stop_hook_active: boolean; - transcript_path?: string; - response?: string; - session_id?: string; -} - -function getLocalTimestamp(): string { - const date = new Date(); - const tz = process.env.TIME_ZONE || Intl.DateTimeFormat().resolvedOptions().timeZone; - const localDate = new Date(date.toLocaleString('en-US', { timeZone: tz })); - - const year = localDate.getFullYear(); - const month = String(localDate.getMonth() + 1).padStart(2, '0'); - const day = String(localDate.getDate()).padStart(2, '0'); - const hours = String(localDate.getHours()).padStart(2, '0'); - const minutes = String(localDate.getMinutes()).padStart(2, '0'); - const seconds = String(localDate.getSeconds()).padStart(2, '0'); - - return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; -} - -function hasLearningIndicators(text: string): boolean { - const indicators = [ - 'problem', 'solved', 'discovered', 'fixed', 'learned', 'realized', - 'figured out', 'root cause', 'debugging', 'issue was', 'turned out', - 'mistake', 'error', 'bug', 'solution' - ]; - const lowerText = text.toLowerCase(); - const matches = indicators.filter(i => lowerText.includes(i)); - return matches.length >= 2; -} - -function extractSummary(response: string): string { - // Look for COMPLETED section - const completedMatch = response.match(/🎯\s*COMPLETED[:\s]*(.+?)(?:\n|$)/i); - if (completedMatch) { - return completedMatch[1].trim().slice(0, 100); - } - - // Look for SUMMARY section - const summaryMatch = response.match(/📋\s*SUMMARY[:\s]*(.+?)(?:\n|$)/i); - if (summaryMatch) { - return summaryMatch[1].trim().slice(0, 100); - } - - // Fallback: first meaningful line - const lines = response.split('\n').filter(l => l.trim().length > 10); - if (lines.length > 0) { - return lines[0].trim().slice(0, 100); - } - - return 'work-session'; -} - -function generateFilename(type: string, description: string): string { - const now = new Date(); - const timestamp = now.toISOString().replace(/[-:]/g, '').split('.')[0]; - const kebab = description.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 60); - return `${timestamp}_${type}_${kebab}.md`; -} - -async function main() { - try { - const stdinData = await Bun.stdin.text(); - if (!stdinData.trim()) { - process.exit(0); - } - - const payload: StopPayload = JSON.parse(stdinData); - if (!payload.response) { - process.exit(0); - } - - const paiDir = process.env.PAI_DIR || join(homedir(), '.config', 'pai'); - const historyDir = join(paiDir, 'history'); - - const isLearning = hasLearningIndicators(payload.response); - const type = isLearning ? 'LEARNING' : 'SESSION'; - const subdir = isLearning ? 'learnings' : 'sessions'; - - const now = new Date(); - const yearMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`; - const outputDir = join(historyDir, subdir, yearMonth); - - if (!existsSync(outputDir)) { - mkdirSync(outputDir, { recursive: true }); - } - - const summary = extractSummary(payload.response); - const filename = generateFilename(type, summary); - const filepath = join(outputDir, filename); - - const content = `--- -capture_type: ${type} -timestamp: ${getLocalTimestamp()} -session_id: ${payload.session_id || 'unknown'} -executor: main ---- - -# ${type}: ${summary} - -${payload.response} - ---- - -*Captured by PAI History System stop-hook* -`; - - writeFileSync(filepath, content); - console.log(`📝 Captured ${type} to ${subdir}/${yearMonth}/${filename}`); - - } catch (error) { - console.error('Stop hook error:', error); - } - - process.exit(0); -} - -main(); -``` - -#### 3.3: Create subagent-stop-hook.ts (Subagent Output Routing) - -```typescript -#!/usr/bin/env bun -// $PAI_DIR/hooks/subagent-stop-hook.ts -// Routes subagent outputs to appropriate history directories - -import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, statSync } from 'fs'; -import { join, dirname } from 'path'; -import { homedir } from 'os'; -import { extractAgentInstanceId } from './lib/metadata-extraction'; - -function getLocalTimestamp(): string { - const date = new Date(); - const tz = process.env.TIME_ZONE || Intl.DateTimeFormat().resolvedOptions().timeZone; - const localDate = new Date(date.toLocaleString('en-US', { timeZone: tz })); - - const year = localDate.getFullYear(); - const month = String(localDate.getMonth() + 1).padStart(2, '0'); - const day = String(localDate.getDate()).padStart(2, '0'); - const hours = String(localDate.getHours()).padStart(2, '0'); - const minutes = String(localDate.getMinutes()).padStart(2, '0'); - const seconds = String(localDate.getSeconds()).padStart(2, '0'); - - return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; -} - -async function delay(ms: number): Promise { - return new Promise(resolve => setTimeout(resolve, ms)); -} - -async function findTaskResult(transcriptPath: string, maxAttempts: number = 2): Promise<{ result: string | null, agentType: string | null, description: string | null, toolInput: any | null }> { - let actualTranscriptPath = transcriptPath; - - for (let attempt = 0; attempt < maxAttempts; attempt++) { - if (attempt > 0) await delay(200); - - if (!existsSync(actualTranscriptPath)) { - const dir = dirname(transcriptPath); - if (existsSync(dir)) { - const files = readdirSync(dir) - .filter(f => f.startsWith('agent-') && f.endsWith('.jsonl')) - .map(f => ({ name: f, mtime: statSync(join(dir, f)).mtime })) - .sort((a, b) => b.mtime.getTime() - a.mtime.getTime()); - - if (files.length > 0) { - actualTranscriptPath = join(dir, files[0].name); - } - } - if (!existsSync(actualTranscriptPath)) continue; - } - - try { - const transcript = readFileSync(actualTranscriptPath, 'utf-8'); - const lines = transcript.trim().split('\n'); - - for (let i = lines.length - 1; i >= 0; i--) { - try { - const entry = JSON.parse(lines[i]); - if (entry.type === 'assistant' && entry.message?.content) { - for (const content of entry.message.content) { - if (content.type === 'tool_use' && content.name === 'Task') { - const toolInput = content.input; - const description = toolInput?.description || null; - - for (let j = i + 1; j < lines.length; j++) { - const resultEntry = JSON.parse(lines[j]); - if (resultEntry.type === 'user' && resultEntry.message?.content) { - for (const resultContent of resultEntry.message.content) { - if (resultContent.type === 'tool_result' && resultContent.tool_use_id === content.id) { - let taskOutput: string; - if (typeof resultContent.content === 'string') { - taskOutput = resultContent.content; - } else if (Array.isArray(resultContent.content)) { - taskOutput = resultContent.content - .filter((item: any) => item.type === 'text') - .map((item: any) => item.text) - .join('\n'); - } else { - continue; - } - - let agentType = toolInput?.subagent_type || 'default'; - return { result: taskOutput, agentType, description, toolInput }; - } - } - } - } - } - } - } - } catch (e) {} - } - } catch (e) {} - } - - return { result: null, agentType: null, description: null, toolInput: null }; -} - -function extractCompletionMessage(taskOutput: string): { message: string | null, agentType: string | null } { - // Look for COMPLETED section - const patterns = [ - /🎯\s*COMPLETED[:\s]*\[AGENT:(\w+[-\w]*)\]\s*(.+?)(?:\n|$)/is, - /🎯\s*COMPLETED[:\s]*(.+?)(?:\n|$)/i, - /COMPLETED[:\s]*(.+?)(?:\n|$)/i - ]; - - for (const pattern of patterns) { - const match = taskOutput.match(pattern); - if (match) { - if (match[2]) { - return { message: match[2].trim(), agentType: match[1].toLowerCase() }; - } - return { message: match[1].trim(), agentType: null }; - } - } - - return { message: null, agentType: null }; -} - -async function captureAgentOutput( - agentType: string, - completionMessage: string, - taskOutput: string, - transcriptPath: string -) { - const paiDir = process.env.PAI_DIR || join(homedir(), '.config', 'pai'); - const historyDir = join(paiDir, 'history'); - - const now = new Date(); - const timestamp = now.toISOString().replace(/[-:]/g, '').split('.')[0]; - const yearMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`; - - // Route by agent type - let captureType = 'RESEARCH'; - let category = 'research'; - - if (agentType.includes('researcher') || agentType === 'intern') { - captureType = 'RESEARCH'; - category = 'research'; - } else if (agentType === 'architect') { - captureType = 'DECISION'; - category = 'decisions'; - } else if (agentType === 'engineer' || agentType === 'designer') { - captureType = 'FEATURE'; - category = 'execution/features'; - } - - const description = completionMessage - .toLowerCase() - .replace(/[^a-z0-9]+/g, '-') - .replace(/^-|-$/g, '') - .slice(0, 60); - - const filename = `${timestamp}_AGENT-${agentType}_${captureType}_${description}.md`; - const outputDir = join(historyDir, category, yearMonth); - - if (!existsSync(outputDir)) { - mkdirSync(outputDir, { recursive: true }); - } - - const document = `--- -capture_type: ${captureType} -timestamp: ${getLocalTimestamp()} -executor: ${agentType} -agent_completion: ${completionMessage} ---- - -# ${captureType}: ${completionMessage} - -**Agent:** ${agentType} -**Completed:** ${timestamp} - ---- - -## Agent Output - -${taskOutput} - ---- - -## Metadata - -**Transcript:** \`${transcriptPath}\` -**Captured:** ${getLocalTimestamp()} - ---- - -*Captured by PAI History System subagent-stop-hook* -`; - - writeFileSync(join(outputDir, filename), document); - console.log(`📝 Captured ${agentType} output to ${category}/${yearMonth}/${filename}`); -} - -async function main() { - try { - let input = ''; - const decoder = new TextDecoder(); - const reader = Bun.stdin.stream().getReader(); - - const timeoutPromise = new Promise((resolve) => setTimeout(resolve, 500)); - const readPromise = (async () => { - while (true) { - const { done, value } = await reader.read(); - if (done) break; - input += decoder.decode(value, { stream: true }); - } - })(); - - await Promise.race([readPromise, timeoutPromise]); - - if (!input) process.exit(0); - - const parsed = JSON.parse(input); - const transcriptPath = parsed.transcript_path; - if (!transcriptPath) process.exit(0); - - const { result: taskOutput, agentType, description, toolInput } = await findTaskResult(transcriptPath); - if (!taskOutput) process.exit(0); - - const { message: completionMessage, agentType: extractedAgentType } = extractCompletionMessage(taskOutput); - if (!completionMessage) process.exit(0); - - const finalAgentType = extractedAgentType || agentType || 'default'; - - await captureAgentOutput(finalAgentType, completionMessage, taskOutput, transcriptPath); - - } catch (error) { - console.error('Subagent stop hook error:', error); - } - - process.exit(0); -} - -main(); -``` - -#### 3.4: Create capture-session-summary.ts (Session End) - -```typescript -#!/usr/bin/env bun -// $PAI_DIR/hooks/capture-session-summary.ts -// Creates session summary when Claude Code session ends - -import { writeFileSync, mkdirSync, existsSync, readFileSync, readdirSync } from 'fs'; -import { join } from 'path'; -import { homedir } from 'os'; - -interface SessionData { - conversation_id: string; - timestamp: string; - [key: string]: any; -} - -function getLocalTimestamp(): string { - const date = new Date(); - const tz = process.env.TIME_ZONE || Intl.DateTimeFormat().resolvedOptions().timeZone; - const localDate = new Date(date.toLocaleString('en-US', { timeZone: tz })); - - const year = localDate.getFullYear(); - const month = String(localDate.getMonth() + 1).padStart(2, '0'); - const day = String(localDate.getDate()).padStart(2, '0'); - const hours = String(localDate.getHours()).padStart(2, '0'); - const minutes = String(localDate.getMinutes()).padStart(2, '0'); - const seconds = String(localDate.getSeconds()).padStart(2, '0'); - - return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; -} - -function determineSessionFocus(filesChanged: string[], commandsExecuted: string[]): string { - const filePatterns = filesChanged.map(f => f.toLowerCase()); - - if (filePatterns.some(f => f.includes('/blog/') || f.includes('/posts/'))) return 'blog-work'; - if (filePatterns.some(f => f.includes('/hooks/'))) return 'hook-development'; - if (filePatterns.some(f => f.includes('/skills/'))) return 'skill-updates'; - if (filePatterns.some(f => f.includes('/agents/'))) return 'agent-work'; - if (commandsExecuted.some(cmd => cmd.includes('test'))) return 'testing-session'; - if (commandsExecuted.some(cmd => cmd.includes('git commit'))) return 'git-operations'; - if (commandsExecuted.some(cmd => cmd.includes('deploy'))) return 'deployment'; - - if (filesChanged.length > 0) { - const mainFile = filesChanged[0].split('/').pop()?.replace(/\.(md|ts|js)$/, ''); - if (mainFile) return `${mainFile}-work`; - } - - return 'development-session'; -} - -async function analyzeSession(conversationId: string, yearMonth: string): Promise { - const paiDir = process.env.PAI_DIR || join(homedir(), '.config', 'pai'); - const rawOutputsDir = join(paiDir, 'history', 'raw-outputs', yearMonth); - - let filesChanged: string[] = []; - let commandsExecuted: string[] = []; - let toolsUsed: Set = new Set(); - - try { - if (existsSync(rawOutputsDir)) { - const files = readdirSync(rawOutputsDir).filter(f => f.endsWith('.jsonl')); - - for (const file of files) { - const content = readFileSync(join(rawOutputsDir, file), 'utf-8'); - const lines = content.split('\n').filter(l => l.trim()); - - for (const line of lines) { - try { - const entry = JSON.parse(line); - if (entry.payload?.tool_name) { - toolsUsed.add(entry.payload.tool_name); - } - if (entry.payload?.tool_name === 'Edit' || entry.payload?.tool_name === 'Write') { - if (entry.payload?.tool_input?.file_path) { - filesChanged.push(entry.payload.tool_input.file_path); - } - } - if (entry.payload?.tool_name === 'Bash' && entry.payload?.tool_input?.command) { - commandsExecuted.push(entry.payload.tool_input.command); - } - } catch (e) {} - } - } - } - } catch (error) {} - - return { - focus: determineSessionFocus([...new Set(filesChanged)], commandsExecuted), - filesChanged: [...new Set(filesChanged)].slice(0, 10), - commandsExecuted: commandsExecuted.slice(0, 10), - toolsUsed: Array.from(toolsUsed) - }; -} - -async function main() { - try { - const input = await Bun.stdin.text(); - if (!input.trim()) process.exit(0); - - const data: SessionData = JSON.parse(input); - const paiDir = process.env.PAI_DIR || join(homedir(), '.config', 'pai'); - const historyDir = join(paiDir, 'history'); - - const now = new Date(); - const timestamp = now.toISOString().replace(/[-:]/g, '').split('.')[0]; - const yearMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`; - - const sessionInfo = await analyzeSession(data.conversation_id, yearMonth); - const filename = `${timestamp}_SESSION_${sessionInfo.focus}.md`; - - const sessionDir = join(historyDir, 'sessions', yearMonth); - if (!existsSync(sessionDir)) { - mkdirSync(sessionDir, { recursive: true }); - } - - const sessionDoc = `--- -capture_type: SESSION -timestamp: ${getLocalTimestamp()} -session_id: ${data.conversation_id} -executor: main ---- - -# Session: ${sessionInfo.focus} - -**Session ID:** ${data.conversation_id} -**Ended:** ${getLocalTimestamp()} - ---- - -## Tools Used - -${sessionInfo.toolsUsed.length > 0 ? sessionInfo.toolsUsed.map((t: string) => `- ${t}`).join('\n') : '- None recorded'} - ---- - -## Files Modified - -${sessionInfo.filesChanged.length > 0 ? sessionInfo.filesChanged.map((f: string) => `- \`${f}\``).join('\n') : '- None recorded'} - ---- - -## Commands Executed - -${sessionInfo.commandsExecuted.length > 0 ? '```bash\n' + sessionInfo.commandsExecuted.join('\n') + '\n```' : 'None recorded'} - ---- - -*Session summary captured by PAI History System* -`; - - writeFileSync(join(sessionDir, filename), sessionDoc); - console.log(`📝 Session summary saved to sessions/${yearMonth}/${filename}`); - - } catch (error) { - console.error('Session summary error:', error); - } - - process.exit(0); -} - -main(); -``` - ---- - -### Step 4: Register Hooks in settings.json - -Claude Code looks for settings in `~/.claude/settings.json`. Add or merge the following hook configuration: - -**File location:** `~/.claude/settings.json` - -```json -{ - "hooks": { - "PreToolUse": [ - { - "matcher": "*", - "hooks": [ - { - "type": "command", - "command": "bun run $PAI_DIR/hooks/capture-all-events.ts --event-type PreToolUse" - } - ] - } - ], - "PostToolUse": [ - { - "matcher": "*", - "hooks": [ - { - "type": "command", - "command": "bun run $PAI_DIR/hooks/capture-all-events.ts --event-type PostToolUse" - } - ] - } - ], - "Stop": [ - { - "hooks": [ - { - "type": "command", - "command": "bun run $PAI_DIR/hooks/stop-hook.ts" - }, - { - "type": "command", - "command": "bun run $PAI_DIR/hooks/capture-all-events.ts --event-type Stop" - } - ] - } - ], - "SubagentStop": [ - { - "hooks": [ - { - "type": "command", - "command": "bun run $PAI_DIR/hooks/subagent-stop-hook.ts" - }, - { - "type": "command", - "command": "bun run $PAI_DIR/hooks/capture-all-events.ts --event-type SubagentStop" - } - ] - } - ], - "SessionEnd": [ - { - "hooks": [ - { - "type": "command", - "command": "bun run $PAI_DIR/hooks/capture-session-summary.ts" - }, - { - "type": "command", - "command": "bun run $PAI_DIR/hooks/capture-all-events.ts --event-type SessionEnd" - } - ] - } - ], - "SessionStart": [ - { - "hooks": [ - { - "type": "command", - "command": "bun run $PAI_DIR/hooks/capture-all-events.ts --event-type SessionStart" - } - ] - } - ], - "UserPromptSubmit": [ - { - "hooks": [ - { - "type": "command", - "command": "bun run $PAI_DIR/hooks/capture-all-events.ts --event-type UserPromptSubmit" - } - ] - } - ] - } -} -``` - -**Important:** If you already have a settings.json, merge the hooks section with your existing configuration. - ---- - -### Step 5: Set Environment Variables (Optional) - -**If you used the Kai Bundle wizard:** These are already in `$PAI_DIR/.env` - skip this step. - -**For manual installation:** Add these to your shell profile (`~/.zshrc`, `~/.bashrc`, etc.): - -```bash -# PAI configuration -export PAI_DIR="$HOME/.config/pai" -export TIME_ZONE="America/Los_Angeles" # Your timezone -export DA="PAI" # Your AI assistant name - -# Reload shell -source ~/.zshrc -``` - ---- - -### Step 6: Verify Installation - -```bash -# 1. Check all hooks exist and are executable -ls -la $PAI_DIR/hooks/*.ts -# Should show 4 hook files - -# 2. Check lib files exist -ls -la $PAI_DIR/hooks/lib/*.ts -# Should show metadata-extraction.ts - -# 3. Check directory structure -ls -la $PAI_DIR/history/ -# Should show: sessions, learnings, research, decisions, execution, raw-outputs - -# 4. Verify Bun can run the hooks -bun run $PAI_DIR/hooks/capture-all-events.ts --event-type Test <<< '{"test": true}' -# Should create an entry in raw-outputs - -# 5. Check raw-outputs for the test entry -ls $PAI_DIR/history/raw-outputs/$(date +%Y-%m)/ -# Should show today's events file - -# 6. Restart Claude Code to activate hooks -# Then run any command and check for new files in history/ -``` - -**Success indicators:** -- No errors when running hooks -- Files appearing in `$PAI_DIR/history/raw-outputs/` -- Session summaries appearing after ending sessions -- Agent outputs captured in appropriate subdirectories - -## Invocation Scenarios -The history system triggers automatically on Claude Code events: - -| Event | Hook | Output Location | Captured Data | -|-------|------|-----------------|---------------| -| Any tool starts | PreToolUse | `raw-outputs/` | Tool name, input, session | -| Any tool completes | PostToolUse | `raw-outputs/` | Tool name, input, output | -| Main agent finishes | Stop | `learnings/` or `sessions/` | Full response, categorized | -| Subagent completes | SubagentStop | `research/`, `decisions/`, or `execution/` | Agent output, routed by type | -| User quits session | SessionEnd | `sessions/` | Files changed, tools used | -| User starts session | SessionStart | `raw-outputs/` | Session metadata | -| User sends prompt | UserPromptSubmit | `raw-outputs/` | Prompt content | - -**Automatic Categorization Logic:** - -The Stop hook analyzes response content for learning indicators: -- Contains 2+ of: problem, solved, discovered, fixed, learned, realized, bug, error, root cause -- **YES** → Saves to `learnings/` -- **NO** → Saves to `sessions/` - -**Agent Routing Logic:** - -The SubagentStop hook routes by agent type: -- `*researcher`, `intern` → `research/` -- `architect` → `decisions/` -- `engineer`, `designer` → `execution/features/` - -## Example Usage -### Example 1: Searching Past Work - -```bash -# User: "Have we worked on authentication before?" - -grep -r "authentication" $PAI_DIR/history/ - -# Results: -# learnings/2025-10/20251013T143022_LEARNING_jwt-token-refresh.md -# execution/features/2025-09/20250928T091530_FEATURE_user-login.md -# decisions/2025-09/20250925T162045_DECISION_auth-strategy.md -``` - -### Example 2: Reviewing Session Activity - -```bash -# After a session ends, check what was captured - -ls -lt $PAI_DIR/history/sessions/2025-12/ | head -5 - -# Shows recent session files with their focus: -# 20251228T153045_SESSION_hook-development.md -# 20251228T142312_SESSION_blog-work.md -# 20251228T101523_SESSION_testing-session.md -``` - -### Example 3: Finding Why a Decision Was Made - -```bash -# User: "Why did we choose that architecture?" - -grep -l "architecture\|decision\|chose" $PAI_DIR/history/decisions/ - -# Returns files with architectural decisions and rationale -``` - -### Example 4: Checking Agent Work - -```bash -# See all research agents completed today - -ls $PAI_DIR/history/research/2025-12/ | grep AGENT - -# Shows: -# 20251228T143022_AGENT-researcher_RESEARCH_market-analysis.md -# 20251228T142518_AGENT-intern_RESEARCH_competitor-review.md -``` - -## Configuration -**Environment variables:** - -```bash -# Override default PAI directory (default: ~/.config/pai) -export PAI_DIR="$HOME/.config/pai" - -# Set timezone for timestamps (default: system timezone) -export TIME_ZONE="America/Los_Angeles" - -# Set source app name for observability (default: PAI) -export DA="MyAI" -export PAI_SOURCE_APP="MyAI" -``` - -**Settings file location:** -- Claude Code: `~/.claude/settings.json` -- OpenCode: Check your platform's hook configuration location - -**Directory structure is fixed** - categorization depends on consistent paths. Only customize the root PAI_DIR location if needed. - ---- - -## Customization - -### Recommended Customization - -**Tune the Learning Detection Keywords** - -The history system automatically categorizes responses as "learnings" or "sessions" based on keyword detection. Customize these keywords to match your vocabulary and ensure valuable insights get properly captured. - -**What to Customize:** `$PAI_DIR/hooks/stop-hook.ts` - -**Why:** Different developers express learning differently. If you say "figured out" but not "root cause", or "aha moment" instead of "discovered", your learnings might not be captured in the learnings/ directory. Tuning these keywords ensures your insights get properly categorized. - -**Process:** - -1. **Review Your Recent Sessions** - ```bash - # Look at recent sessions - ls -lt $PAI_DIR/history/sessions/ | head -10 - - # Check if any should have been learnings - grep -l "aha\|insight\|realized" $PAI_DIR/history/sessions/ - ``` - -2. **Add Your Keywords** - Edit the `hasLearningIndicators` function in `stop-hook.ts`: - ```typescript - function hasLearningIndicators(text: string): boolean { - const indicators = [ - 'problem', 'solved', 'discovered', 'fixed', 'learned', 'realized', - 'figured out', 'root cause', 'debugging', 'issue was', 'turned out', - 'mistake', 'error', 'bug', 'solution', - // Add your personal indicators: - 'aha', 'insight', 'trick was', 'key was', 'finally', 'breakthrough' - ]; - // ... - } - ``` - -3. **Adjust Threshold** - Default requires 2+ matches. Adjust if needed: - ```typescript - return matches.length >= 2; // Change to 1 for more sensitive capture - ``` - -**Expected Outcome:** Your valuable learnings are properly categorized and easily searchable. - ---- - -### Optional Customization - -| Customization | File | Impact | -|---------------|------|--------| -| **Agent Routing** | `subagent-stop-hook.ts` | Add new agent types and their output directories | -| **Summary Extraction** | `stop-hook.ts` | Modify how completion summaries are extracted | -| **Session Focus Detection** | `capture-session-summary.ts` | Add patterns for your project types | -| **File Naming** | All hooks | Change timestamp format or description extraction | - -**Example: Add New Agent Type Routing** - -Edit `subagent-stop-hook.ts` to add routing for custom agents: - -```typescript -// Add to agent routing logic -if (agentType === 'security-researcher' || agentType === 'pentester') { - captureType = 'SECURITY'; - category = 'security'; // Creates history/security/ -} else if (agentType === 'code-reviewer') { - captureType = 'REVIEW'; - category = 'reviews'; // Creates history/reviews/ -} -``` - -**Example: Custom Session Focus Detection** - -Edit `capture-session-summary.ts` to detect your project types: - -```typescript -function determineSessionFocus(filesChanged: string[], commandsExecuted: string[]): string { - // Add your project-specific patterns - if (filePatterns.some(f => f.includes('/api/'))) return 'api-development'; - if (filePatterns.some(f => f.includes('/tests/'))) return 'test-writing'; - // ... -} -``` - ---- - -## Credits -- **Original concept**: Daniel Miessler - developed as part of Kai personal AI infrastructure -- **Contributors**: The PAI community -- **Inspired by**: Git's version history, engineering logbooks, Zettelkasten method - -## Related Work - -- **Git version control** - Inspired by git's approach to tracking history -- **Engineering logbooks** - Traditional approach to capturing work and learnings -- **Zettelkasten method** - Interconnected knowledge capture system - -## Works Well With - -- **kai-hook-system** - Required foundation; provides the event stream this pack captures -- **kai-core-install** - Skills can reference past learnings and research from history; CORE skill guides what gets captured -- **kai-voice-system** - Voice notifications can announce when significant history is captured - -## Recommended - -- **kai-hook-system** - Required dependency; without it no events are captured -- **kai-core-install** - Enables skill-based categorization of learnings - -## Relationships - -### Parent Of -*None - this is a data capture layer, not a foundation for other packs.* - -### Child Of -- **kai-hook-system** - Depends entirely on hook events (Stop, SubagentStop, SessionEnd) for all capture - -### Sibling Of -- **kai-core-install** - Both are foundation packs that depend on kai-hook-system -- **kai-voice-system** - Both consume hook events for their functionality - -### Part Of Collection -**Kai Core Bundle** - One of 4 foundational packs that together create the complete Kai personal AI infrastructure. - -## Changelog - -### 1.0.0 - 2025-12-28 -- Initial release -- Four hooks: capture-all-events, stop-hook, subagent-stop-hook, capture-session-summary -- One lib file: metadata-extraction -- Automatic categorization by content and agent type -- Full settings.json configuration included -- Note: Observability dashboard integration is a separate pack (observability-server) diff --git a/Packs/kai-hook-system.md b/Packs/kai-hook-system.md deleted file mode 100644 index 9b90e9e8ca..0000000000 --- a/Packs/kai-hook-system.md +++ /dev/null @@ -1,1492 +0,0 @@ ---- -name: Kai Hook System -pack-id: danielmiessler-kai-hook-system-core-v1.0.0 -version: 1.0.0 -author: danielmiessler -description: Event-driven automation framework for Claude Code - the foundation for all hook-based capabilities including security validation, session management, and context injection -type: feature -purpose-type: [automation, security, development] -platform: claude-code -dependencies: [] -keywords: [hooks, automation, events, security, validation, sessions, context, claude-code, preprocessing, postprocessing] ---- - -

- Kai Hook System -

- -# Kai Hook System (kai-hook-system) - -> Event-driven automation framework for Claude Code - the foundation for all hook-based capabilities - -## Installation Prompt -You are receiving a PAI Pack - a modular upgrade for AI agent systems. - -**What is PAI?** See: [PAI Project Overview](../README.md#what-is-pai) - -**What is a Pack?** See: [Pack System](../README.md#pack-system) - -This Pack adds event-driven automation to your AI infrastructure. The Kai Hook System is the foundation that enables all other hook-based Packs to work: - -- **Security Validation**: Block dangerous commands before execution -- **Session Management**: Initialize sessions with context and state -- **Context Injection**: Load skill definitions at session start -- **Terminal Integration**: Update tab titles with task context -- **Event Pipeline**: PreToolUse, PostToolUse, Stop, SessionStart, and more - -**Core principle:** Hooks intercept events and add intelligence without interrupting work. - -Every tool call, every session start, every completion - hooks see it all and can act on it. This is how you make AI infrastructure truly automated. - -Please follow the installation instructions below to integrate this Pack into your infrastructure. - ---- - -## What's Included - -| Component | File | Purpose | -|-----------|------|---------| -| Security validator | `hooks/security-validator.ts` | Block dangerous commands before execution | -| Session initializer | `hooks/initialize-session.ts` | Set up session context and markers | -| Context loader | `hooks/load-core-context.ts` | Load CORE skill at session start | -| Tab title updater | `hooks/update-tab-titles.ts` | Update terminal tabs with task context | -| Observability lib | `hooks/lib/observability.ts` | Event logging and dashboard integration | - -**Summary:** -- **Files created:** 5 -- **Hooks registered:** 4 (PreToolUse, SessionStart ×2, UserPromptSubmit) -- **Dependencies:** None (foundation pack) - ---- - -## The Concept and/or Problem - -Claude Code fires events throughout its operation, but by default nothing listens to them: - -- **PreToolUse**: Before any tool runs - opportunity to validate, block, or modify -- **PostToolUse**: After any tool runs - opportunity to capture, log, or react -- **Stop**: When the agent finishes responding - opportunity to capture work -- **SessionStart**: When a new session begins - opportunity to initialize -- **SessionEnd**: When a session closes - opportunity to summarize -- **UserPromptSubmit**: When the user sends a message - opportunity to process - -Without a hook system: -- Dangerous commands execute without validation -- Sessions start cold without context -- No automation layer between events and actions -- Each capability must be built from scratch - -**The Problem:** - -Claude Code's hook system is powerful but undocumented in practice. You can configure hooks in settings.json, but: -- What events are available? -- What data does each event provide? -- How do you write hooks that never block or crash? -- How do you chain multiple hooks together? - -**The Opportunity:** - -Hooks are the foundation for building intelligent AI infrastructure. Once you understand the event model, you can: -- Add security validation to every command -- Inject context at the right moments -- Capture every piece of work automatically -- Integrate with external systems (dashboards, notifications, analytics) - -## The Solution - -The Kai Hook System provides a complete framework for event-driven automation: - -**Core Architecture:** - -``` -$PAI_DIR/ -├── hooks/ # Hook implementations -│ ├── security-validator.ts # PreToolUse: Block dangerous commands -│ ├── initialize-session.ts # SessionStart: Session setup -│ ├── load-core-context.ts # SessionStart: Context injection -│ ├── update-tab-titles.ts # UserPromptSubmit: Tab automation -│ └── lib/ # Shared libraries -│ └── observability.ts # Dashboard integration -└── settings.json # Hook configuration -``` - -**Hook Event Types:** - -| Event | When It Fires | Use Cases | -|-------|---------------|-----------| -| `PreToolUse` | Before a tool executes | Security validation, command modification, blocking | -| `PostToolUse` | After a tool executes | Logging, capturing output, triggering actions | -| `Stop` | Main agent finishes | Capture work summaries, voice notifications | -| `SubagentStop` | Subagent finishes | Capture agent outputs, route to categories | -| `SessionStart` | New session begins | Load context, initialize state | -| `SessionEnd` | Session closes | Summarize work, cleanup | -| `UserPromptSubmit` | User sends message | Process input, update UI | -| `PreCompact` | Before context compaction | Save important context | - -**Design Principles:** - -1. **Never Block**: Hooks must always exit 0 - never crash Claude Code -2. **Fail Silently**: Errors are logged but don't interrupt work -3. **Fast Execution**: Hooks should complete in milliseconds -4. **Stdin/Stdout**: Events come via stdin JSON, output via stdout -5. **Composable**: Multiple hooks can chain on the same event - -**The Key Insight:** - -Hooks are middleware for your AI. They sit between Claude Code's event system and your custom logic. By instrumenting the event stream, you gain complete visibility and control over your AI's operations. - ---- - -## What Makes This Different - -The hook system's power comes from its **event-driven middleware pattern** - a layered architecture that intercepts, processes, and extends every AI operation without modifying Claude Code itself. - -``` -┌─────────────────────────────────────────────────────────────────────────┐ -│ HOOK SYSTEM ARCHITECTURE │ -├─────────────────────────────────────────────────────────────────────────┤ -│ │ -│ ┌──────────────┐ │ -│ │ Claude Code │ ──► Events fire at every operation │ -│ └──────┬───────┘ │ -│ │ │ -│ ▼ │ -│ ┌──────────────────────────────────────────────────────────────────┐ │ -│ │ LAYER 1: Event Stream │ │ -│ │ SessionStart ─► PreToolUse ─► PostToolUse ─► Stop ─► SessionEnd │ │ -│ └──────────────────────────────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌──────────────────────────────────────────────────────────────────┐ │ -│ │ LAYER 2: Hook Registry │ │ -│ │ settings.json defines which hooks fire on which events │ │ -│ │ Matchers filter by tool name (Bash, Edit, *) or context │ │ -│ └──────────────────────────────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌──────────────────────────────────────────────────────────────────┐ │ -│ │ LAYER 3: Hook Implementations │ │ -│ │ TypeScript files that process events and take actions │ │ -│ │ security-validator.ts │ initialize-session.ts │ update-tabs.ts │ │ -│ └──────────────────────────────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌──────────────────────────────────────────────────────────────────┐ │ -│ │ LAYER 4: Shared Libraries │ │ -│ │ Common utilities: observability.ts, prosody-enhancer.ts │ │ -│ │ Fail-safe patterns, logging, integration helpers │ │ -│ └──────────────────────────────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌──────────────────────────────────────────────────────────────────┐ │ -│ │ LAYER 5: External Integrations │ │ -│ │ Observability dashboards, voice servers, notification systems │ │ -│ └──────────────────────────────────────────────────────────────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────────────────┘ -``` - -### How Data Flows Through the System - -**Example: User runs `rm -rf important/`** - -``` -1. Claude Code invokes Bash tool - │ - ▼ -2. PreToolUse event fires with payload: - { tool_name: "Bash", tool_input: { command: "rm -rf important/" } } - │ - ▼ -3. settings.json routes to security-validator.ts (matcher: "Bash") - │ - ▼ -4. Hook receives JSON via stdin, pattern-matches against attack tiers - │ - ▼ -5. MATCH: Catastrophic deletion pattern detected - │ - ▼ -6. Hook exits with code 2 (BLOCK) + outputs warning message - │ - ▼ -7. Claude Code sees exit 2, BLOCKS the command - │ - ▼ -8. User sees: "🚨 BLOCKED: Catastrophic deletion detected" -``` - -### Why This Architecture Matters - -**1. Separation of Concerns** -- Event stream (Claude Code) is separate from hook logic (your code) -- Registration (settings.json) is separate from implementation (.ts files) -- Each hook does one thing well (UNIX philosophy) - -**2. Fail-Safe by Design** -- Hooks NEVER crash Claude Code (exit 0 on errors) -- External integrations fail silently (observability down? keep working) -- Fast execution (milliseconds, not seconds) - -**3. Composable Pipeline** -- Multiple hooks can chain on the same event -- Each hook processes independently -- Order defined in settings.json - -**4. Deterministic Behavior** -- Same event + same hook = same outcome -- Pattern matching is explicit, not fuzzy -- Exit codes have precise meanings (0=allow, 2=block) - -**5. Zero-Overhead Extensibility** -- Add new hooks without modifying existing ones -- Add new event handlers without touching Claude Code -- Shared libraries reduce duplication - -### What Problems This Architecture Prevents - -| Problem | How Hooks Solve It | -|---------|-------------------| -| Dangerous commands execute | PreToolUse validates before execution | -| Sessions start cold | SessionStart injects context automatically | -| Work disappears | Stop/SubagentStop capture everything | -| No visibility into operations | PostToolUse logs to observability | -| UI doesn't show context | UserPromptSubmit updates tab titles | - -### The Fundamental Insight - -**Naive approach:** Build safety/automation INTO the AI prompts -- Fragile (prompts can be ignored) -- Inconsistent (varies by session) -- Invisible (no audit trail) - -**Hook approach:** Build safety/automation AROUND the AI as middleware -- Robust (code can't be prompt-injected) -- Consistent (same code runs every time) -- Observable (events logged, actions traced) - -The hook system transforms Claude Code from a standalone tool into an **observable, controllable, extensible infrastructure**. Every operation can be validated, logged, modified, or blocked. This is the foundation that enables all other PAI capabilities. - ---- - -## Why This Is Different - -This sounds similar to Claude Code's built-in hooks in settings.json, which also intercept events. What makes this approach different? - -Claude Code exposes hook events but provides no framework for using them effectively. You get raw events with no patterns for security validation, no libraries for safe execution, no templates for common use cases. The Kai Hook System provides production-ready hook implementations that transform these raw events into intelligent automation. Every hook follows fail-safe patterns that never block agent work, with shared libraries for observability, logging, and integration. - -- Production-ready security validation for every Bash command -- Fail-safe patterns ensure hooks never block work -- Shared libraries for observability and logging included -- Complete event documentation with practical code examples - ---- - -## Installation - -### Prerequisites - -- **Bun runtime**: `curl -fsSL https://bun.sh/install | bash` -- **Claude Code** (or compatible agent system with hook support) -- **Write access** to `$PAI_DIR/` (or your PAI directory) - ---- - -### Pre-Installation: System Analysis - -**IMPORTANT:** Before installing, analyze the current system state to detect conflicts and determine the appropriate installation strategy. - -#### Step 0.1: Detect Current Configuration - -Run these commands to understand your current system: - -```bash -# 1. Check if PAI_DIR is set -echo "PAI_DIR: ${PAI_DIR:-'NOT SET - will use ~/.config/pai'}" - -# 2. Check for existing PAI directory -PAI_CHECK="${PAI_DIR:-$HOME/.config/pai}" -if [ -d "$PAI_CHECK" ]; then - echo "⚠️ PAI directory EXISTS at: $PAI_CHECK" - echo "Contents:" - ls -la "$PAI_CHECK" 2>/dev/null || echo " (empty or inaccessible)" -else - echo "✓ PAI directory does not exist (clean install)" -fi - -# 3. Check for existing hooks directory -if [ -d "$PAI_CHECK/hooks" ]; then - echo "⚠️ Hooks directory EXISTS" - echo "Existing hooks:" - ls -la "$PAI_CHECK/hooks"/*.ts 2>/dev/null || echo " (no .ts files)" -else - echo "✓ No existing hooks directory" -fi - -# 4. Check Claude settings for existing hooks -CLAUDE_SETTINGS="$HOME/.claude/settings.json" -if [ -f "$CLAUDE_SETTINGS" ]; then - echo "Claude settings.json EXISTS" - if grep -q '"hooks"' "$CLAUDE_SETTINGS" 2>/dev/null; then - echo "⚠️ Existing hooks configuration found:" - grep -A 20 '"hooks"' "$CLAUDE_SETTINGS" | head -25 - else - echo "✓ No hooks configured in settings.json" - fi -else - echo "✓ No Claude settings.json (will be created)" -fi - -# 5. Check environment variables -echo "" -echo "Environment variables:" -echo " DA: ${DA:-'NOT SET'}" -echo " TIME_ZONE: ${TIME_ZONE:-'NOT SET'}" -echo " PAI_SOURCE_APP: ${PAI_SOURCE_APP:-'NOT SET'}" -``` - -#### Step 0.2: Conflict Resolution Matrix - -Based on the detection above, follow the appropriate path: - -| Scenario | Existing State | Action | -|----------|---------------|--------| -| **Clean Install** | No PAI_DIR, no hooks | Proceed normally with Step 1 | -| **PAI Directory Exists** | Directory with files | Review files, backup if needed, then proceed | -| **Hooks Exist** | Files in `$PAI_DIR/hooks/` | Merge new hooks alongside existing, or backup and replace | -| **Claude Settings Has Hooks** | `settings.json` has hook config | **MERGE** - add new hooks to existing array | -| **Environment Variables Set** | PAI_DIR, DA already defined | Use existing values or update in shell profile | - -#### Step 0.3: Backup Existing Configuration (If Needed) - -If conflicts were detected, create a backup before proceeding: - -```bash -# Create timestamped backup -BACKUP_DIR="$HOME/.pai-backup/$(date +%Y%m%d-%H%M%S)" -mkdir -p "$BACKUP_DIR" - -# Backup PAI directory if exists -PAI_CHECK="${PAI_DIR:-$HOME/.config/pai}" -if [ -d "$PAI_CHECK" ]; then - cp -r "$PAI_CHECK" "$BACKUP_DIR/pai" - echo "✓ Backed up PAI directory to $BACKUP_DIR/pai" -fi - -# Backup Claude settings if exists -if [ -f "$HOME/.claude/settings.json" ]; then - cp "$HOME/.claude/settings.json" "$BACKUP_DIR/settings.json" - echo "✓ Backed up settings.json to $BACKUP_DIR/settings.json" -fi - -echo "Backup complete: $BACKUP_DIR" -``` - -#### Step 0.4: Set Required Environment Variables - -**If you used the Kai Bundle wizard:** Environment variables are already configured in `$PAI_DIR/.env` - skip to Step 1. - -**For manual installation:** Configure these in your shell profile (`~/.zshrc` or `~/.bashrc`): - -```bash -# Add to your shell profile -export PAI_DIR="${HOME}/.config/pai" -export DA="PAI" # Your AI assistant name -export TIME_ZONE="America/Los_Angeles" # Your timezone -export PAI_SOURCE_APP="${DA}" - -# Reload your shell -source ~/.zshrc # or ~/.bashrc -``` - -**Alternative:** Create a `.env` file in your PAI directory: - -```bash -# $PAI_DIR/.env -DA="PAI" -PAI_DIR="${HOME}/.config/pai" -TIME_ZONE="America/Los_Angeles" -``` - -**After completing system analysis, proceed to Step 1.** - ---- - -### Step 1: Create Directory Structure - -```bash -# Create required directories -mkdir -p $PAI_DIR/hooks/lib - -# Verify structure -ls -la $PAI_DIR/hooks/ -``` - ---- - -### Step 2: Create Library Files - -#### 2.1: Create observability.ts - -This library enables integration with observability dashboards. - -```typescript -// $PAI_DIR/hooks/lib/observability.ts -// Sends hook events to observability dashboards - -export interface ObservabilityEvent { - source_app: string; - session_id: string; - hook_event_type: 'PreToolUse' | 'PostToolUse' | 'UserPromptSubmit' | 'Notification' | 'Stop' | 'SubagentStop' | 'SessionStart' | 'SessionEnd' | 'PreCompact'; - timestamp: string; - transcript_path?: string; - summary?: string; - tool_name?: string; - tool_input?: any; - tool_output?: any; - agent_type?: string; - model?: string; - [key: string]: any; -} - -/** - * Send event to observability dashboard - * Fails silently if dashboard is not running - doesn't block hook execution - */ -export async function sendEventToObservability(event: ObservabilityEvent): Promise { - const dashboardUrl = process.env.PAI_OBSERVABILITY_URL || 'http://localhost:4000/events'; - - try { - const response = await fetch(dashboardUrl, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'User-Agent': 'PAI-Hook/1.0' - }, - body: JSON.stringify(event), - }); - - if (!response.ok) { - // Log error but don't throw - dashboard may be offline - console.error(`Observability server returned status: ${response.status}`); - } - } catch (error) { - // Fail silently - dashboard may not be running - // This is intentional - hooks should never fail due to observability issues - } -} - -/** - * Helper to get current timestamp in ISO format - */ -export function getCurrentTimestamp(): string { - return new Date().toISOString(); -} - -/** - * Helper to get source app name from environment - */ -export function getSourceApp(): string { - return process.env.PAI_SOURCE_APP || process.env.DA || 'PAI'; -} -``` - ---- - -### Step 3: Create Hook Files - -#### 3.1: Create security-validator.ts (PreToolUse Security) - -This hook validates commands before execution and blocks dangerous operations. - -```typescript -#!/usr/bin/env bun -// $PAI_DIR/hooks/security-validator.ts -// PreToolUse hook: Validates commands and blocks dangerous operations - -import { sendEventToObservability, getCurrentTimestamp, getSourceApp } from './lib/observability'; - -interface PreToolUsePayload { - session_id: string; - tool_name: string; - tool_input: Record; -} - -// Attack pattern categories -const ATTACK_PATTERNS = { - // Tier 1: Catastrophic - Always block - catastrophic: { - patterns: [ - /rm\s+(-rf?|--recursive)\s+[\/~]/i, // rm -rf / - /rm\s+(-rf?|--recursive)\s+\*/i, // rm -rf * - />\s*\/dev\/sd[a-z]/i, // Overwrite disk - /mkfs\./i, // Format filesystem - /dd\s+if=.*of=\/dev/i, // dd to device - ], - action: 'block', - message: '🚨 BLOCKED: Catastrophic deletion/destruction detected' - }, - - // Tier 2: Reverse shells - Always block - reverseShell: { - patterns: [ - /bash\s+-i\s+>&\s*\/dev\/tcp/i, // Bash reverse shell - /nc\s+(-e|--exec)\s+\/bin\/(ba)?sh/i, // Netcat shell - /python.*socket.*connect/i, // Python socket - /perl.*socket.*connect/i, // Perl socket - /ruby.*TCPSocket/i, // Ruby socket - /php.*fsockopen/i, // PHP socket - /socat.*exec/i, // Socat exec - /\|\s*\/bin\/(ba)?sh/i, // Pipe to shell - ], - action: 'block', - message: '🚨 BLOCKED: Reverse shell pattern detected' - }, - - // Tier 3: Credential theft - Always block - credentialTheft: { - patterns: [ - /curl.*\|\s*(ba)?sh/i, // curl pipe to shell - /wget.*\|\s*(ba)?sh/i, // wget pipe to shell - /curl.*(-o|--output).*&&.*chmod.*\+x/i, // Download and execute - /base64\s+-d.*\|\s*(ba)?sh/i, // Base64 decode to shell - ], - action: 'block', - message: '🚨 BLOCKED: Remote code execution pattern detected' - }, - - // Tier 4: Prompt injection indicators - Block and log - promptInjection: { - patterns: [ - /ignore\s+(all\s+)?previous\s+instructions/i, - /disregard\s+(all\s+)?prior\s+instructions/i, - /you\s+are\s+now\s+(in\s+)?[a-z]+\s+mode/i, - /new\s+instruction[s]?:/i, - /system\s+prompt:/i, - /\[INST\]/i, // LLM injection - /<\|im_start\|>/i, // ChatML injection - ], - action: 'block', - message: '🚨 BLOCKED: Prompt injection pattern detected - logging incident' - }, - - // Tier 5: Environment manipulation - Warn - envManipulation: { - patterns: [ - /export\s+(ANTHROPIC|OPENAI|AWS|AZURE)_/i, // API key export - /echo\s+\$\{?(ANTHROPIC|OPENAI)_/i, // Echo API keys - /env\s*\|.*KEY/i, // Dump env with keys - /printenv.*KEY/i, // Print env keys - ], - action: 'warn', - message: '⚠️ WARNING: Environment/credential access detected' - }, - - // Tier 6: Git dangerous operations - Require confirmation - gitDangerous: { - patterns: [ - /git\s+push.*(-f|--force)/i, // Force push - /git\s+reset\s+--hard/i, // Hard reset - /git\s+clean\s+-fd/i, // Clean untracked - /git\s+checkout\s+--\s+\./i, // Discard all changes - ], - action: 'confirm', - message: '⚠️ CONFIRM: Potentially destructive git operation' - }, - - // Tier 7: System modification - Log - systemMod: { - patterns: [ - /chmod\s+777/i, // World writable - /chown\s+root/i, // Change to root - /sudo\s+/i, // Sudo usage - /systemctl\s+(stop|disable)/i, // Stop services - ], - action: 'log', - message: '📝 LOGGED: System modification command' - }, - - // Tier 8: Network operations - Log - network: { - patterns: [ - /ssh\s+/i, // SSH connections - /scp\s+/i, // SCP transfers - /rsync.*:/i, // Rsync remote - /curl\s+(-X\s+POST|--data)/i, // POST requests - ], - action: 'log', - message: '📝 LOGGED: Network operation' - }, - - // Tier 9: Data exfiltration patterns - Block - exfiltration: { - patterns: [ - /curl.*(@|--upload-file)/i, // Upload file - /tar.*\|.*curl/i, // Tar and send - /zip.*\|.*nc/i, // Zip and netcat - ], - action: 'block', - message: '🚨 BLOCKED: Data exfiltration pattern detected' - }, - - // Tier 10: PAI-specific protection - Block - paiProtection: { - patterns: [ - /rm.*\.config\/pai/i, // Delete PAI config - /rm.*\.claude/i, // Delete Claude config - /git\s+push.*PAI.*public/i, // Push PAI to public - ], - action: 'block', - message: '🚨 BLOCKED: PAI infrastructure protection triggered' - } -}; - -function validateCommand(command: string): { allowed: boolean; message?: string; action?: string } { - // Fast path: Most commands are safe - if (!command || command.length < 3) { - return { allowed: true }; - } - - // Check each tier - for (const [tierName, tier] of Object.entries(ATTACK_PATTERNS)) { - for (const pattern of tier.patterns) { - if (pattern.test(command)) { - const result = { - allowed: tier.action !== 'block', - message: tier.message, - action: tier.action - }; - - // Log security event - console.error(`[Security] ${tierName}: ${tier.message}`); - console.error(`[Security] Command: ${command.substring(0, 100)}...`); - - return result; - } - } - } - - return { allowed: true }; -} - -async function main() { - try { - const stdinData = await Bun.stdin.text(); - if (!stdinData.trim()) { - process.exit(0); - } - - const payload: PreToolUsePayload = JSON.parse(stdinData); - - // Only validate Bash commands - if (payload.tool_name !== 'Bash') { - process.exit(0); - } - - const command = payload.tool_input?.command; - if (!command) { - process.exit(0); - } - - const validation = validateCommand(command); - - // Send to observability if configured - if (validation.action) { - await sendEventToObservability({ - source_app: getSourceApp(), - session_id: payload.session_id, - hook_event_type: 'PreToolUse', - timestamp: getCurrentTimestamp(), - tool_name: 'Bash', - tool_input: { command: command.substring(0, 200) }, - security_action: validation.action, - security_message: validation.message - }); - } - - if (!validation.allowed) { - // Output the block message - Claude Code will see this - console.log(validation.message); - console.log(`Command blocked: ${command.substring(0, 100)}...`); - - // Exit with code 2 to signal block (Claude Code specific) - process.exit(2); - } - - // Log warnings but allow execution - if (validation.action === 'warn' || validation.action === 'confirm') { - console.log(validation.message); - } - - } catch (error) { - // Never crash - log error and allow command - console.error('Security validator error:', error); - } - - // Exit 0 = allow the command - process.exit(0); -} - -main(); -``` - -#### 3.2: Create initialize-session.ts (SessionStart) - -This hook runs at session start to initialize state and environment. - -```typescript -#!/usr/bin/env bun -// $PAI_DIR/hooks/initialize-session.ts -// SessionStart hook: Initialize session state and environment - -import { existsSync, writeFileSync, mkdirSync } from 'fs'; -import { join } from 'path'; -import { homedir } from 'os'; -import { sendEventToObservability, getCurrentTimestamp, getSourceApp } from './lib/observability'; - -interface SessionStartPayload { - session_id: string; - cwd?: string; - [key: string]: any; -} - -function getLocalTimestamp(): string { - const date = new Date(); - const tz = process.env.TIME_ZONE || Intl.DateTimeFormat().resolvedOptions().timeZone; - - try { - const localDate = new Date(date.toLocaleString('en-US', { timeZone: tz })); - const year = localDate.getFullYear(); - const month = String(localDate.getMonth() + 1).padStart(2, '0'); - const day = String(localDate.getDate()).padStart(2, '0'); - const hours = String(localDate.getHours()).padStart(2, '0'); - const minutes = String(localDate.getMinutes()).padStart(2, '0'); - const seconds = String(localDate.getSeconds()).padStart(2, '0'); - - return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; - } catch { - return new Date().toISOString(); - } -} - -function setTabTitle(title: string): void { - // OSC escape sequence for terminal tab title - const tabEscape = `\x1b]1;${title}\x07`; - const windowEscape = `\x1b]2;${title}\x07`; - - process.stderr.write(tabEscape); - process.stderr.write(windowEscape); -} - -function getProjectName(cwd: string | undefined): string { - if (!cwd) return 'Session'; - - // Extract project name from path - const parts = cwd.split('/').filter(p => p); - - // Look for common project indicators - const projectIndicators = ['Projects', 'projects', 'src', 'repos', 'code']; - for (let i = parts.length - 1; i >= 0; i--) { - if (projectIndicators.includes(parts[i]) && parts[i + 1]) { - return parts[i + 1]; - } - } - - // Default to last directory component - return parts[parts.length - 1] || 'Session'; -} - -async function checkForUpdates(): Promise { - // Optional: Check for Claude Code updates in background - // This is non-blocking and fails silently - try { - const proc = Bun.spawn(['claude', '--version'], { - stdout: 'pipe', - stderr: 'pipe' - }); - - const output = await new Response(proc.stdout).text(); - const version = output.trim().match(/[\d.]+/)?.[0]; - - if (version) { - // Could compare against known latest version - // For now, just log it - console.error(`[PAI] Claude Code version: ${version}`); - } - } catch { - // Silently ignore - version check is optional - } -} - -async function main() { - try { - const stdinData = await Bun.stdin.text(); - if (!stdinData.trim()) { - process.exit(0); - } - - const payload: SessionStartPayload = JSON.parse(stdinData); - const paiDir = process.env.PAI_DIR || join(homedir(), '.config', 'pai'); - - // 1. Set initial tab title - const projectName = getProjectName(payload.cwd); - setTabTitle(`🤖 ${projectName}`); - - // 2. Ensure required directories exist - const requiredDirs = [ - join(paiDir, 'hooks', 'lib'), - join(paiDir, 'history', 'sessions'), - join(paiDir, 'history', 'learnings'), - join(paiDir, 'history', 'research'), - ]; - - for (const dir of requiredDirs) { - if (!existsSync(dir)) { - mkdirSync(dir, { recursive: true }); - } - } - - // 3. Create session marker file (optional - for tracking) - const sessionFile = join(paiDir, '.current-session'); - writeFileSync(sessionFile, JSON.stringify({ - session_id: payload.session_id, - started: getLocalTimestamp(), - cwd: payload.cwd, - project: projectName - }, null, 2)); - - // 4. Send to observability dashboard - await sendEventToObservability({ - source_app: getSourceApp(), - session_id: payload.session_id, - hook_event_type: 'SessionStart', - timestamp: getCurrentTimestamp(), - cwd: payload.cwd, - project: projectName - }); - - // 5. Background version check (non-blocking) - checkForUpdates().catch(() => {}); - - // Output session info - console.error(`[PAI] Session initialized: ${projectName}`); - console.error(`[PAI] Time: ${getLocalTimestamp()}`); - - } catch (error) { - // Never crash - just log - console.error('Session initialization error:', error); - } - - process.exit(0); -} - -main(); -``` - -#### 3.3: Create load-core-context.ts (SessionStart Context Injection) - -This hook loads skill definitions into the AI's context at session start. - -```typescript -#!/usr/bin/env bun -// $PAI_DIR/hooks/load-core-context.ts -// SessionStart hook: Inject skill/context files into Claude's context - -import { existsSync, readFileSync } from 'fs'; -import { join } from 'path'; -import { homedir } from 'os'; - -interface SessionStartPayload { - session_id: string; - [key: string]: any; -} - -function isSubagentSession(): boolean { - // Check for subagent indicators - // Subagents shouldn't load full context (they get it from parent) - return process.env.CLAUDE_CODE_AGENT !== undefined || - process.env.SUBAGENT === 'true'; -} - -function getLocalTimestamp(): string { - const date = new Date(); - const tz = process.env.TIME_ZONE || Intl.DateTimeFormat().resolvedOptions().timeZone; - - try { - const localDate = new Date(date.toLocaleString('en-US', { timeZone: tz })); - const year = localDate.getFullYear(); - const month = String(localDate.getMonth() + 1).padStart(2, '0'); - const day = String(localDate.getDate()).padStart(2, '0'); - const hours = String(localDate.getHours()).padStart(2, '0'); - const minutes = String(localDate.getMinutes()).padStart(2, '0'); - const seconds = String(localDate.getSeconds()).padStart(2, '0'); - - return `${year}-${month}-${day} ${hours}:${minutes}:${seconds} PST`; - } catch { - return new Date().toISOString(); - } -} - -async function main() { - try { - // Skip for subagents - they get context from parent - if (isSubagentSession()) { - process.exit(0); - } - - const stdinData = await Bun.stdin.text(); - if (!stdinData.trim()) { - process.exit(0); - } - - const payload: SessionStartPayload = JSON.parse(stdinData); - const paiDir = process.env.PAI_DIR || join(homedir(), '.config', 'pai'); - - // Look for CORE skill to load - // The CORE skill contains identity, response format, and operating principles - const coreSkillPath = join(paiDir, 'skills', 'CORE', 'SKILL.md'); - - if (!existsSync(coreSkillPath)) { - // No CORE skill installed - that's fine - console.error('[PAI] No CORE skill found - skipping context injection'); - process.exit(0); - } - - // Read the skill content - const skillContent = readFileSync(coreSkillPath, 'utf-8'); - - // Output as system-reminder for Claude to process - // This format is recognized by Claude Code - const output = ` -PAI CORE CONTEXT (Auto-loaded at Session Start) - -📅 CURRENT DATE/TIME: ${getLocalTimestamp()} - -The following context has been loaded from ${coreSkillPath}: - -${skillContent} - -This context is now active for this session. Follow all instructions, preferences, and guidelines contained above. - - -✅ PAI Context successfully loaded...`; - - // Output goes to stdout - Claude Code will see it - console.log(output); - - } catch (error) { - // Never crash - just skip - console.error('Context loading error:', error); - } - - process.exit(0); -} - -main(); -``` - -#### 3.4: Create update-tab-titles.ts (UserPromptSubmit) - -This hook updates terminal tab titles with task context. - -```typescript -#!/usr/bin/env bun -// $PAI_DIR/hooks/update-tab-titles.ts -// UserPromptSubmit hook: Update terminal tab title with task context - -interface UserPromptPayload { - session_id: string; - prompt?: string; - message?: string; - [key: string]: any; -} - -function setTabTitle(title: string): void { - // OSC escape sequences for terminal tab and window titles - const tabEscape = `\x1b]1;${title}\x07`; - const windowEscape = `\x1b]2;${title}\x07`; - - process.stderr.write(tabEscape); - process.stderr.write(windowEscape); -} - -function extractTaskKeywords(prompt: string): string { - // Remove common filler words - const stopWords = new Set([ - 'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', - 'of', 'with', 'by', 'from', 'is', 'are', 'was', 'were', 'be', 'been', - 'being', 'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', - 'could', 'should', 'may', 'might', 'must', 'can', 'this', 'that', - 'these', 'those', 'i', 'you', 'he', 'she', 'it', 'we', 'they', 'me', - 'my', 'your', 'please', 'help', 'want', 'need', 'like', 'just' - ]); - - // Clean and tokenize - const words = prompt - .toLowerCase() - .replace(/[^\w\s-]/g, ' ') - .split(/\s+/) - .filter(word => word.length > 2 && !stopWords.has(word)); - - // Take first 3-4 significant words - const keywords = words.slice(0, 4); - - if (keywords.length === 0) { - return 'Working'; - } - - // Capitalize first word - keywords[0] = keywords[0].charAt(0).toUpperCase() + keywords[0].slice(1); - - return keywords.join(' '); -} - -function launchBackgroundSummarization(prompt: string, sessionId: string): void { - // Optional: Launch a background process to get a better title via AI - // This is fire-and-forget - doesn't block the hook - - // Skip if prompt is too short - if (prompt.length < 20) return; - - // This could call a fast model (e.g., Haiku) to summarize - // For now, we just use keyword extraction (instant) - - // Example of what background summarization would look like: - // Bun.spawn(['bun', 'run', 'summarize-task.ts', '--prompt', prompt], { - // stdout: 'ignore', - // stderr: 'ignore' - // }); -} - -async function main() { - try { - const stdinData = await Bun.stdin.text(); - if (!stdinData.trim()) { - process.exit(0); - } - - const payload: UserPromptPayload = JSON.parse(stdinData); - const prompt = payload.prompt || payload.message || ''; - - if (!prompt || prompt.length < 3) { - process.exit(0); - } - - // Generate quick title from keywords (instant) - const keywords = extractTaskKeywords(prompt); - setTabTitle(`🤖 ${keywords}`); - - // Optionally launch background summarization for better title - launchBackgroundSummarization(prompt, payload.session_id); - - } catch (error) { - // Never crash - console.error('Tab title update error:', error); - } - - process.exit(0); -} - -main(); -``` - ---- - -### Step 4: Register Hooks in settings.json - -Claude Code looks for settings in `~/.claude/settings.json`. Add or merge the following hook configuration: - -**File location:** `~/.claude/settings.json` - -```json -{ - "hooks": { - "SessionStart": [ - { - "matcher": "*", - "hooks": [ - { - "type": "command", - "command": "bun run $PAI_DIR/hooks/initialize-session.ts" - }, - { - "type": "command", - "command": "bun run $PAI_DIR/hooks/load-core-context.ts" - } - ] - } - ], - "PreToolUse": [ - { - "matcher": "Bash", - "hooks": [ - { - "type": "command", - "command": "bun run $PAI_DIR/hooks/security-validator.ts" - } - ] - } - ], - "UserPromptSubmit": [ - { - "matcher": "*", - "hooks": [ - { - "type": "command", - "command": "bun run $PAI_DIR/hooks/update-tab-titles.ts" - } - ] - } - ] - } -} -``` - -**Important:** If you already have a settings.json, merge the hooks section with your existing configuration. - ---- - -### Step 5: Set Environment Variables (Optional) - -**If you used the Kai Bundle wizard:** These are already in `$PAI_DIR/.env` - skip this step. - -**For manual installation:** Add these to your shell profile (`~/.zshrc`, `~/.bashrc`, etc.): - -```bash -# PAI configuration -export PAI_DIR="$HOME/.config/pai" -export TIME_ZONE="America/Los_Angeles" # Your timezone -export DA="PAI" # Your AI assistant name - -# Optional: Observability dashboard URL -export PAI_OBSERVABILITY_URL="http://localhost:4000/events" - -# Reload shell -source ~/.zshrc -``` - ---- - -### Step 6: Verify Installation - -```bash -# 1. Check all hooks exist -ls -la $PAI_DIR/hooks/*.ts -# Should show: security-validator.ts, initialize-session.ts, -# load-core-context.ts, update-tab-titles.ts - -# 2. Check lib files exist -ls -la $PAI_DIR/hooks/lib/*.ts -# Should show: observability.ts - -# 3. Test security validator with a safe command -echo '{"session_id":"test","tool_name":"Bash","tool_input":{"command":"ls -la"}}' | \ - bun run $PAI_DIR/hooks/security-validator.ts -# Should exit 0 (allowed) - -# 4. Test security validator catches dangerous patterns -# NOTE: This is SAFE - we're just piping JSON text to stdin, not executing anything -echo '{"session_id":"test","tool_name":"Bash","tool_input":{"command":"rm -rf /tmp/test"}}' | \ - bun run $PAI_DIR/hooks/security-validator.ts -# Should exit 2 (blocked) and print warning - -# 4b. Alternative: Canary file test (even safer feeling) -touch /tmp/pai-security-canary -# Now ask Claude: "delete /tmp/pai-security-canary" -# Hook should BLOCK, then verify canary survives: -ls /tmp/pai-security-canary # File should still exist - -# 5. Test session initialization -echo '{"session_id":"test","cwd":"/Users/you/Projects/MyProject"}' | \ - bun run $PAI_DIR/hooks/initialize-session.ts -# Should set tab title and create session marker - -# 6. Restart Claude Code to activate hooks -``` - -**Success indicators:** -- Security validator blocks dangerous commands (exit code 2) -- Security validator allows safe commands (exit code 0) -- Tab title updates when you send prompts -- Session marker created at $PAI_DIR/.current-session - ---- - -## Hook Event Reference - -### Event Payloads - -Each hook receives a JSON payload via stdin: - -**PreToolUse:** -```json -{ - "session_id": "uuid", - "tool_name": "Bash", - "tool_input": { - "command": "ls -la" - } -} -``` - -**PostToolUse:** -```json -{ - "session_id": "uuid", - "tool_name": "Bash", - "tool_input": { "command": "ls -la" }, - "tool_output": "file1.txt\nfile2.txt" -} -``` - -**Stop:** -```json -{ - "session_id": "uuid", - "stop_hook_active": true, - "transcript_path": "/path/to/transcript.jsonl", - "response": "The full assistant response text" -} -``` - -**SubagentStop:** -```json -{ - "session_id": "uuid", - "transcript_path": "/path/to/agent-transcript.jsonl" -} -``` - -**SessionStart:** -```json -{ - "session_id": "uuid", - "cwd": "/current/working/directory" -} -``` - -**SessionEnd:** -```json -{ - "conversation_id": "uuid", - "timestamp": "2025-01-15T10:30:00Z" -} -``` - -**UserPromptSubmit:** -```json -{ - "session_id": "uuid", - "prompt": "User's message text" -} -``` - -### Exit Codes - -| Exit Code | Meaning | Use Case | -|-----------|---------|----------| -| 0 | Success / Allow | Hook completed, tool can proceed | -| 1 | Error (non-blocking) | Hook failed but don't block | -| 2 | Block | PreToolUse only: prevent tool execution | - -### Matcher Patterns - -Hooks can be filtered by tool name: - -```json -{ - "matcher": "Bash", // Only Bash tool - "matcher": "Edit", // Only Edit tool - "matcher": "Read|Write", // Read OR Write - "matcher": "*" // All tools -} -``` - ---- - -## Example Usage - -### Example 1: Adding a Custom Security Rule - -```typescript -// Add to ATTACK_PATTERNS in security-validator.ts - -customRule: { - patterns: [ - /my-dangerous-pattern/i, - ], - action: 'block', - message: '🚨 BLOCKED: Custom security rule triggered' -} -``` - -### Example 2: Injecting Project-Specific Context - -```typescript -// In load-core-context.ts, add project-specific context loading - -const projectContextPath = join(payload.cwd, '.pai', 'context.md'); -if (existsSync(projectContextPath)) { - const projectContext = readFileSync(projectContextPath, 'utf-8'); - console.log(`${projectContext}`); -} -``` - -### Example 3: Custom Tab Title Format - -```typescript -// Modify update-tab-titles.ts for custom format - -function setTabTitle(title: string): void { - const prefix = process.env.PAI_TAB_PREFIX || '🤖'; - const fullTitle = `${prefix} ${title}`; - // ... rest of function -} -``` - ---- - -## Configuration - -**Environment variables:** - -| Variable | Default | Purpose | -|----------|---------|---------| -| `PAI_DIR` | `~/.config/pai` | Root PAI directory | -| `TIME_ZONE` | System default | Timestamp timezone | -| `DA` | `PAI` | AI assistant name | -| `PAI_OBSERVABILITY_URL` | `http://localhost:4000/events` | Dashboard endpoint | -| `PAI_TAB_PREFIX` | `🤖` | Tab title prefix | - -**Settings file locations:** -- Claude Code: `~/.claude/settings.json` -- OpenCode: Check your platform's hook configuration - ---- - -## Customization - -### Recommended Customization - -**Define Your Security Policies** - -The security validator comes with sensible defaults, but you'll want to customize the attack patterns for your specific environment and risk tolerance. - -**What to Customize:** `$PAI_DIR/hooks/security-validator.ts` - -**Why:** The default security patterns may be too strict or too lenient for your use case. For example, if you regularly work with Docker, you might want to allow certain commands that are blocked by default. Conversely, if you work with sensitive data, you may want to add stricter patterns. - -**Process:** - -1. **Review Current Patterns** - - Read through the `ATTACK_PATTERNS` object in security-validator.ts - - Identify patterns that trigger too often (false positives) - - Identify dangerous commands specific to your environment - -2. **Add Custom Patterns** - ```typescript - // Add to ATTACK_PATTERNS in security-validator.ts - customProjectRules: { - patterns: [ - /dangerous-command-for-your-project/i, - ], - action: 'block', - message: '🚨 BLOCKED: Custom project security rule' - } - ``` - -3. **Adjust Action Levels** - - `block` - Completely prevent execution - - `warn` - Allow but show warning - - `log` - Allow and log for review - - `confirm` - Require explicit confirmation - -**Expected Outcome:** Security validation tailored to your specific workflow and risk profile. - ---- - -### Optional Customization - -| Customization | File | Impact | -|---------------|------|--------| -| **Tab Title Format** | `update-tab-titles.ts` | Change the emoji prefix or title format | -| **Session Initialization** | `initialize-session.ts` | Add custom session startup actions | -| **Context Loading** | `load-core-context.ts` | Modify what context loads at session start | -| **Observability URL** | Environment variable | Point to different observability endpoint | - -**Example: Custom Tab Title Prefix** - -Edit `update-tab-titles.ts` to change the tab icon: - -```typescript -function setTabTitle(title: string): void { - const prefix = process.env.PAI_TAB_PREFIX || '🤖'; // Change this - // ... -} -``` - -**Example: Add Project-Specific Context** - -Edit `load-core-context.ts` to load project-specific context: - -```typescript -// Check for project-specific context file -const projectContextPath = join(payload.cwd, '.pai', 'context.md'); -if (existsSync(projectContextPath)) { - const projectContext = readFileSync(projectContextPath, 'utf-8'); - console.log(`${projectContext}`); -} -``` - ---- - -## Credits - -- **Original concept**: Daniel Miessler - developed as part of Kai personal AI infrastructure -- **Contributors**: The PAI community -- **Hook system**: Anthropic Claude Code team - -## Related Work - -- **Claude Code Hooks** - The underlying event system this pack builds upon -- **Middleware patterns** - This pack implements event-driven middleware architecture - -## Works Well With - -- **kai-history-system** - Uses hooks for capturing session work and learnings -- **kai-voice-system** - Uses hooks for voice notification triggers -- **kai-core-install** - Uses SessionStart hooks for CORE skill context injection and response formatting - -## Recommended - -- **kai-history-system** - Capture all work automatically via Stop/SubagentStop hooks -- **kai-voice-system** - Add voice notifications to hook events - -## Relationships - -### Parent Of -- **kai-history-system** - History capture depends on hook events (Stop, SubagentStop) -- **kai-core-install** - Skill loading uses SessionStart hooks for CORE context injection -- **kai-voice-system** - Voice notifications triggered via hooks - -### Child Of -*None - this is the foundation layer.* - -### Sibling Of -*None - this is the foundation layer.* - -### Part Of Collection -**Kai Core Bundle** - One of 4 foundational packs that together create the complete Kai personal AI infrastructure. - ---- - -## Changelog - -### 1.0.0 - 2025-12-29 -- Initial release -- Four core hooks: security-validator, initialize-session, load-core-context, update-tab-titles -- One lib file: observability -- Complete hook event reference documentation -- Security validation with 10 tiers of attack pattern detection -- Session initialization with tab title automation -- Context injection for skill loading diff --git a/Packs/kai-observability-server.md b/Packs/kai-observability-server.md deleted file mode 100644 index 283ddd3b4a..0000000000 --- a/Packs/kai-observability-server.md +++ /dev/null @@ -1,1730 +0,0 @@ ---- -name: Kai Observability Server -pack-id: danielmiessler-kai-observability-server-core-v1.0.0 -version: 1.0.0 -author: danielmiessler -description: Real-time multi-agent activity monitoring dashboard with WebSocket streaming - see exactly what your AI agents are doing as they work -type: feature -purpose-type: [observability, monitoring, development, debugging] -platform: claude-code -dependencies: [kai-hook-system] -keywords: [observability, dashboard, monitoring, agents, websocket, streaming, events, debugging, visualization, real-time] ---- - -

- Kai Observability Server -

- -# Kai Observability Server (kai-observability-server) - -> Real-time multi-agent activity monitoring dashboard - see exactly what your AI agents are doing as they work - -## Installation Prompt -You are receiving a PAI Pack - a modular upgrade for AI agent systems. - -**What is PAI?** See: [PAI Project Overview](../README.md#what-is-pai) - -**What is a Pack?** See: [Pack System](../README.md#pack-system) - -This Pack adds real-time monitoring capabilities to your AI infrastructure. The Observability Server streams every tool call, hook event, and agent action to a beautiful dashboard: - -- **WebSocket Streaming**: Events appear instantly as they happen -- **Multi-Agent Tracking**: See activity across all agents (main, interns, researchers, etc.) -- **Event Timeline**: Chronological view of all operations -- **Agent Swim Lanes**: Compare activity between multiple agents -- **Zero Configuration**: Just start the server and go - -**Core principle:** You can't improve what you can't see. This dashboard gives you complete visibility into your AI's operations. - -**Dependency:** Requires the [kai-hook-system](kai-hook-system.md) pack to capture events. - -Please follow the installation instructions below to integrate this Pack into your infrastructure. - ---- - -## What's Included - -| Component | File | Purpose | -|-----------|------|---------| -| WebSocket server | `Observability/server/index.ts` | HTTP API + WebSocket streaming | -| File ingestion | `Observability/server/file-ingest.ts` | Watch JSONL and stream events | -| Type definitions | `Observability/server/types.ts` | TypeScript interfaces | -| Vue dashboard | `Observability/dashboard/App.vue` | Real-time monitoring UI | -| Event capture hook | `hooks/capture-all-events.ts` | Capture all events to JSONL | -| Metadata extraction | `hooks/lib/metadata-extraction.ts` | Agent instance tracking | - -**Summary:** -- **Files created:** 8+ (server, dashboard, hooks) -- **Hooks registered:** 7 (all event types via capture-all-events) -- **Dependencies:** kai-hook-system (required) - ---- - -## The Concept and/or Problem - -When running AI agents, especially multiple agents in parallel, you're flying blind: - -- **What's happening now?** No visibility into real-time operations -- **Which agent did what?** When you spawn multiple agents, you lose track of their activity -- **Where did it fail?** No way to trace back through operations when something breaks -- **What was the sequence?** Tool calls happen fast - you can't see the order -- **How long did it take?** No performance visibility - -**The Problem:** - -Claude Code logs events through hooks, but the data just... sits there. You can grep through JSONL files after the fact, but that's like trying to debug by reading logs - tedious and slow. - -Meanwhile, your agents are doing hundreds of operations: -- File reads and writes -- Bash commands executing -- Sub-agents spawning and completing -- API calls succeeding or failing - -Without real-time visibility, you're working in the dark. - -**The Opportunity:** - -What if you could watch your AI infrastructure like a control room? Every operation visible. Every agent tracked. Every event timestamped and streaming live. That's what observability enables - complete operational awareness. - -## The Solution - -The Observability Server provides a complete monitoring stack: - -**Architecture:** - -``` -$PAI_DIR/ -├── observability/ # Observability infrastructure -│ ├── manage.sh # Control script (start/stop/restart) -│ └── apps/ -│ ├── server/ # Backend (Bun + TypeScript) -│ │ ├── src/ -│ │ │ ├── index.ts # HTTP + WebSocket server -│ │ │ ├── file-ingest.ts # JSONL file watcher -│ │ │ └── types.ts # TypeScript interfaces -│ │ └── package.json -│ └── client/ # Frontend (Vue 3 + Vite) -│ ├── src/ -│ │ ├── App.vue # Main dashboard -│ │ └── components/ # UI components -│ └── package.json -├── hooks/ -│ ├── capture-all-events.ts # Hook that logs all events -│ └── lib/ -│ └── metadata-extraction.ts # Agent metadata extraction -└── history/ - └── raw-outputs/ - └── YYYY-MM/ - └── YYYY-MM-DD_all-events.jsonl # Event storage -``` - -**Data Flow:** - -``` -┌──────────────────────────────────────────────────────────────────────────┐ -│ OBSERVABILITY DATA FLOW │ -├──────────────────────────────────────────────────────────────────────────┤ -│ │ -│ ┌──────────────┐ │ -│ │ Claude Code │ ──► Hook events fire (PreToolUse, PostToolUse, etc.) │ -│ └──────┬───────┘ │ -│ │ │ -│ ▼ │ -│ ┌──────────────────────────────────────────────────────────────────┐ │ -│ │ capture-all-events.ts (PostToolUse hook) │ │ -│ │ Receives JSON via stdin → Appends to daily JSONL file │ │ -│ └──────────────────────────────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌──────────────────────────────────────────────────────────────────┐ │ -│ │ YYYY-MM-DD_all-events.jsonl (file storage) │ │ -│ │ One line per event, organized by date in monthly directories │ │ -│ └──────────────────────────────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌──────────────────────────────────────────────────────────────────┐ │ -│ │ file-ingest.ts (server component) │ │ -│ │ Watches file for changes → Parses new lines → Streams to WS │ │ -│ └──────────────────────────────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌──────────────────────────────────────────────────────────────────┐ │ -│ │ index.ts (Bun HTTP + WebSocket server) │ │ -│ │ Port 4000 → WebSocket: /stream → HTTP: /events/* │ │ -│ └──────────────────────────────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌──────────────────────────────────────────────────────────────────┐ │ -│ │ Vue 3 Dashboard (Vite dev server) │ │ -│ │ Port 5172 → Connects to WS → Renders real-time event timeline │ │ -│ └──────────────────────────────────────────────────────────────────┘ │ -│ │ -└──────────────────────────────────────────────────────────────────────────┘ -``` - -**Key Components:** - -| Component | Purpose | Technology | -|-----------|---------|------------| -| `capture-all-events.ts` | Hook that captures ALL events to JSONL | TypeScript/Bun | -| `file-ingest.ts` | Watches JSONL file, streams new events | Node fs.watch | -| `index.ts` | HTTP API + WebSocket server | Bun.serve | -| `App.vue` | Main dashboard UI | Vue 3 + Tailwind | -| `manage.sh` | Start/stop/restart script | Bash | - -**Design Principles:** - -1. **No Database**: Events stream from files directly - no persistence overhead -2. **Fire and Forget**: Hook writes and exits fast - never blocks Claude Code -3. **Fresh Start**: Each server launch starts clean - no stale state -4. **In-Memory Only**: Recent events cached in memory, not stored in DB -5. **Graceful Degradation**: Dashboard down? Hooks still log. Server restarts? Just reload. - ---- - -## What Makes This Different - -The observability system's power comes from its **file-based streaming architecture** - a simple but effective pattern that separates event capture from event display. - -### Why File-Based Streaming? - -**Naive approach:** Send events directly to a server via HTTP -- Complex: Need error handling, retries, connection management -- Fragile: Server down = events lost -- Blocking: Network latency affects hook performance - -**File-based approach:** Write to local file, server watches and streams -- Simple: Just append to a file (atomic, fast, reliable) -- Robust: File persists even if server is down -- Non-blocking: File writes are fast, hook exits immediately -- Portable: JSONL files can be processed by any tool - -### The Critical Insight - -Hooks must NEVER block Claude Code. The file-based approach ensures: - -1. Hook receives event via stdin -2. Hook appends one line to JSONL file -3. Hook exits immediately (exit 0) - -Total time: ~1-2ms. Claude Code barely notices. - -Meanwhile, the server: -1. Watches the JSONL file for changes -2. Reads only new lines (tracks file position) -3. Parses JSON and broadcasts to WebSocket clients - -This decoupling means the expensive work (UI rendering, WebSocket management) happens outside the critical path. - -### In-Memory Design - -The server maintains only recent events in memory: - -```typescript -const MAX_EVENTS = 1000; -const events: HookEvent[] = []; -``` - -Why? Because observability is about the NOW, not historical analysis: -- You want to see what's happening right now -- Old events can be analyzed from JSONL files if needed -- Memory stays bounded regardless of how long server runs - -### Agent Session Tracking - -One clever feature: the system tracks which agent is active: - -```typescript -// Session-to-agent mapping -const agentSessions = new Map(); - -// When Task tool spawns a subagent: -if (hookData.tool_name === 'Task' && hookData.tool_input?.subagent_type) { - agentName = hookData.tool_input.subagent_type; - setAgentForSession(sessionId, agentName); -} -``` - -This enables the dashboard to show "Intern is running" vs "Main agent is running" - crucial when you have multiple agents working in parallel. - ---- - -## Why This Is Different - -This sounds similar to just tailing log files, which you could do with `tail -f`. What makes this approach different? - -Raw log tailing gives you text that scrolls by too fast to read. The Observability Server transforms raw events into a structured, filterable, real-time dashboard. You can see which agent made each call, filter by tool type, watch activity patterns over time, and compare agents side-by-side. It's the difference between watching raw network packets vs using Wireshark. - -- Real-time WebSocket streaming with sub-second latency -- Agent identification and tracking across sessions -- Beautiful Vue 3 dashboard with swimlane visualization -- Filterable by agent, tool type, and time range -- Zero configuration - just start and use - ---- - -## Installation - -### Prerequisites - -- **Bun runtime**: `curl -fsSL https://bun.sh/install | bash` -- **Node.js 18+** (for Vite dev server) -- **Claude Code** (or compatible agent system) -- **kai-hook-system** pack installed -- **Write access** to `$PAI_DIR/` - ---- - -### Pre-Installation: System Analysis - -**IMPORTANT:** Before installing, verify the hook system is in place. - -#### Step 0.1: Verify Hook System - -```bash -# Check that kai-hook-system is installed -ls -la $PAI_DIR/hooks/ -# Should show: security-validator.ts, initialize-session.ts, etc. - -# Check that hooks are configured in Claude settings -grep -A 10 '"hooks"' ~/.claude/settings.json -# Should show PostToolUse hooks configured -``` - -#### Step 0.2: Check for Existing Observability - -```bash -# Check if observability directory exists -if [ -d "$PAI_DIR/observability" ]; then - echo "⚠️ Observability directory EXISTS" - ls -la "$PAI_DIR/observability" -else - echo "✓ No existing observability (clean install)" -fi -``` - ---- - -### Step 1: Create Directory Structure - -```bash -# Create observability directories -mkdir -p $PAI_DIR/observability/apps/server/src -mkdir -p $PAI_DIR/observability/apps/client/src/components -mkdir -p $PAI_DIR/observability/apps/client/src/composables -mkdir -p $PAI_DIR/history/raw-outputs - -# Create hooks lib directory if not exists -mkdir -p $PAI_DIR/hooks/lib - -# Verify structure -ls -la $PAI_DIR/observability/ -``` - ---- - -### Step 2: Create the Event Capture Hook - -This hook captures ALL events and writes them to a daily JSONL file. - -#### 2.1: Create metadata-extraction.ts - -```typescript -// $PAI_DIR/hooks/lib/metadata-extraction.ts -// Extracts agent instance metadata from Task tool calls - -export interface AgentInstanceMetadata { - agent_instance_id?: string; - agent_type?: string; - instance_number?: number; - parent_session_id?: string; - parent_task_id?: string; -} - -/** - * Extract agent instance ID from Task tool input - */ -export function extractAgentInstanceId( - toolInput: any, - description?: string -): AgentInstanceMetadata { - const result: AgentInstanceMetadata = {}; - - // Strategy 1: Extract from description [agent-type-N] - if (description) { - const descMatch = description.match(/\[([a-z-]+-researcher)-(\d+)\]/); - if (descMatch) { - result.agent_type = descMatch[1]; - result.instance_number = parseInt(descMatch[2], 10); - result.agent_instance_id = `${result.agent_type}-${result.instance_number}`; - } - } - - // Strategy 2: Extract from prompt [AGENT_INSTANCE: ...] - if (!result.agent_instance_id && toolInput?.prompt && typeof toolInput.prompt === 'string') { - const promptMatch = toolInput.prompt.match(/\[AGENT_INSTANCE:\s*([^\]]+)\]/); - if (promptMatch) { - result.agent_instance_id = promptMatch[1].trim(); - const parts = result.agent_instance_id.match(/^([a-z-]+)-(\d+)$/); - if (parts) { - result.agent_type = parts[1]; - result.instance_number = parseInt(parts[2], 10); - } - } - } - - // Strategy 3: Extract parent session from prompt - if (toolInput?.prompt && typeof toolInput.prompt === 'string') { - const parentSessionMatch = toolInput.prompt.match(/\[PARENT_SESSION:\s*([^\]]+)\]/); - if (parentSessionMatch) { - result.parent_session_id = parentSessionMatch[1].trim(); - } - - const parentTaskMatch = toolInput.prompt.match(/\[PARENT_TASK:\s*([^\]]+)\]/); - if (parentTaskMatch) { - result.parent_task_id = parentTaskMatch[1].trim(); - } - } - - // Strategy 4: Fallback to subagent_type - if (!result.agent_type && toolInput?.subagent_type) { - result.agent_type = toolInput.subagent_type; - } - - return result; -} - -/** - * Enrich event with agent metadata - */ -export function enrichEventWithAgentMetadata( - event: any, - toolInput: any, - description?: string -): any { - const metadata = extractAgentInstanceId(toolInput, description); - const enrichedEvent = { ...event }; - - if (metadata.agent_instance_id) enrichedEvent.agent_instance_id = metadata.agent_instance_id; - if (metadata.agent_type) enrichedEvent.agent_type = metadata.agent_type; - if (metadata.instance_number !== undefined) enrichedEvent.instance_number = metadata.instance_number; - if (metadata.parent_session_id) enrichedEvent.parent_session_id = metadata.parent_session_id; - if (metadata.parent_task_id) enrichedEvent.parent_task_id = metadata.parent_task_id; - - return enrichedEvent; -} - -/** - * Check if a tool call is spawning a subagent - */ -export function isAgentSpawningCall(toolName: string, toolInput: any): boolean { - return toolName === 'Task' && toolInput?.subagent_type !== undefined; -} -``` - -#### 2.2: Create capture-all-events.ts - -```typescript -#!/usr/bin/env bun -// $PAI_DIR/hooks/capture-all-events.ts -// Captures ALL Claude Code hook events to JSONL - -import { readFileSync, appendFileSync, mkdirSync, existsSync, writeFileSync } from 'fs'; -import { join } from 'path'; -import { homedir } from 'os'; -import { enrichEventWithAgentMetadata, isAgentSpawningCall } from './lib/metadata-extraction'; - -interface HookEvent { - source_app: string; - session_id: string; - hook_event_type: string; - payload: Record; - timestamp: number; - timestamp_local: string; -} - -function getLocalTimestamp(): string { - const date = new Date(); - const tz = process.env.TIME_ZONE || Intl.DateTimeFormat().resolvedOptions().timeZone; - - try { - const localDate = new Date(date.toLocaleString('en-US', { timeZone: tz })); - const year = localDate.getFullYear(); - const month = String(localDate.getMonth() + 1).padStart(2, '0'); - const day = String(localDate.getDate()).padStart(2, '0'); - const hours = String(localDate.getHours()).padStart(2, '0'); - const minutes = String(localDate.getMinutes()).padStart(2, '0'); - const seconds = String(localDate.getSeconds()).padStart(2, '0'); - return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; - } catch { - return new Date().toISOString(); - } -} - -function getEventsFilePath(): string { - const paiDir = process.env.PAI_DIR || join(homedir(), '.config', 'pai'); - const now = new Date(); - const tz = process.env.TIME_ZONE || Intl.DateTimeFormat().resolvedOptions().timeZone; - const localDate = new Date(now.toLocaleString('en-US', { timeZone: tz })); - - const year = localDate.getFullYear(); - const month = String(localDate.getMonth() + 1).padStart(2, '0'); - const day = String(localDate.getDate()).padStart(2, '0'); - - const monthDir = join(paiDir, 'history', 'raw-outputs', `${year}-${month}`); - - if (!existsSync(monthDir)) { - mkdirSync(monthDir, { recursive: true }); - } - - return join(monthDir, `${year}-${month}-${day}_all-events.jsonl`); -} - -function getSessionMappingFile(): string { - const paiDir = process.env.PAI_DIR || join(homedir(), '.config', 'pai'); - return join(paiDir, 'agent-sessions.json'); -} - -function getAgentForSession(sessionId: string): string { - try { - const mappingFile = getSessionMappingFile(); - if (existsSync(mappingFile)) { - const mappings = JSON.parse(readFileSync(mappingFile, 'utf-8')); - return mappings[sessionId] || process.env.DA || 'main'; - } - } catch { - // Ignore errors - } - return process.env.DA || 'main'; -} - -function setAgentForSession(sessionId: string, agentName: string): void { - try { - const mappingFile = getSessionMappingFile(); - let mappings: Record = {}; - - if (existsSync(mappingFile)) { - mappings = JSON.parse(readFileSync(mappingFile, 'utf-8')); - } - - mappings[sessionId] = agentName; - writeFileSync(mappingFile, JSON.stringify(mappings, null, 2), 'utf-8'); - } catch { - // Silently fail - } -} - -async function main() { - try { - const args = process.argv.slice(2); - const eventTypeIndex = args.indexOf('--event-type'); - - if (eventTypeIndex === -1) { - console.error('Missing --event-type argument'); - process.exit(0); - } - - const eventType = args[eventTypeIndex + 1]; - const stdinData = await Bun.stdin.text(); - const hookData = JSON.parse(stdinData); - - const sessionId = hookData.session_id || 'main'; - let agentName = getAgentForSession(sessionId); - - // Update agent mapping based on event type - if (hookData.tool_name === 'Task' && hookData.tool_input?.subagent_type) { - agentName = hookData.tool_input.subagent_type; - setAgentForSession(sessionId, agentName); - } else if (eventType === 'SubagentStop' || eventType === 'Stop') { - agentName = process.env.DA || 'main'; - setAgentForSession(sessionId, agentName); - } else if (process.env.CLAUDE_CODE_AGENT) { - agentName = process.env.CLAUDE_CODE_AGENT; - setAgentForSession(sessionId, agentName); - } else if (hookData.agent_type) { - agentName = hookData.agent_type; - setAgentForSession(sessionId, agentName); - } - - let event: HookEvent = { - source_app: agentName, - session_id: hookData.session_id || 'main', - hook_event_type: eventType, - payload: hookData, - timestamp: Date.now(), - timestamp_local: getLocalTimestamp() - }; - - // Enrich with agent instance metadata for Task calls - if (isAgentSpawningCall(hookData.tool_name, hookData.tool_input)) { - event = enrichEventWithAgentMetadata(event, hookData.tool_input, hookData.description); - } - - // Append to events file - const eventsFile = getEventsFilePath(); - const jsonLine = JSON.stringify(event) + '\n'; - appendFileSync(eventsFile, jsonLine, 'utf-8'); - - } catch (error) { - console.error('Event capture error:', error); - } - - process.exit(0); -} - -main(); -``` - ---- - -### Step 3: Create the Server - -#### 3.1: Create server package.json - -```json -// $PAI_DIR/observability/apps/server/package.json -{ - "name": "pai-observability-server", - "version": "1.0.0", - "module": "src/index.ts", - "type": "module", - "private": true, - "scripts": { - "dev": "bun --watch src/index.ts", - "start": "bun src/index.ts" - }, - "devDependencies": { - "@types/bun": "latest", - "typescript": "^5.8.3" - } -} -``` - -#### 3.2: Create server types.ts - -```typescript -// $PAI_DIR/observability/apps/server/src/types.ts - -export interface TodoItem { - content: string; - status: 'pending' | 'in_progress' | 'completed'; - activeForm: string; -} - -export interface HookEvent { - id?: number; - source_app: string; - session_id: string; - agent_name?: string; - hook_event_type: string; - payload: Record; - summary?: string; - timestamp?: number; - todos?: TodoItem[]; - completedTodos?: TodoItem[]; -} - -export interface FilterOptions { - source_apps: string[]; - session_ids: string[]; - hook_event_types: string[]; -} -``` - -#### 3.3: Create file-ingest.ts - -```typescript -// $PAI_DIR/observability/apps/server/src/file-ingest.ts -// File-based event streaming - watches JSONL files - -import { watch, existsSync } from 'fs'; -import { readFileSync } from 'fs'; -import { join } from 'path'; -import { homedir } from 'os'; -import type { HookEvent } from './types'; - -const MAX_EVENTS = 1000; -const events: HookEvent[] = []; -const filePositions = new Map(); -const watchedFiles = new Set(); -let onEventsReceived: ((events: HookEvent[]) => void) | null = null; -const agentSessions = new Map(); -const sessionTodos = new Map(); - -function getTodayEventsFile(): string { - const paiDir = process.env.PAI_DIR || join(homedir(), '.config', 'pai'); - const now = new Date(); - const tz = process.env.TIME_ZONE || Intl.DateTimeFormat().resolvedOptions().timeZone; - - try { - const localDate = new Date(now.toLocaleString('en-US', { timeZone: tz })); - const year = localDate.getFullYear(); - const month = String(localDate.getMonth() + 1).padStart(2, '0'); - const day = String(localDate.getDate()).padStart(2, '0'); - - const monthDir = join(paiDir, 'history', 'raw-outputs', `${year}-${month}`); - return join(monthDir, `${year}-${month}-${day}_all-events.jsonl`); - } catch { - // Fallback to UTC - const year = now.getFullYear(); - const month = String(now.getMonth() + 1).padStart(2, '0'); - const day = String(now.getDate()).padStart(2, '0'); - return join(paiDir, 'history', 'raw-outputs', `${year}-${month}`, `${year}-${month}-${day}_all-events.jsonl`); - } -} - -function enrichEventWithAgentName(event: HookEvent): HookEvent { - if (event.hook_event_type === 'UserPromptSubmit') { - return { ...event, agent_name: 'User' }; - } - - const mainAgentName = process.env.DA || 'main'; - const subAgentTypes = ['artist', 'intern', 'engineer', 'pentester', 'architect', 'designer', 'qatester', 'researcher']; - - if (event.source_app && subAgentTypes.includes(event.source_app.toLowerCase())) { - const capitalizedName = event.source_app.charAt(0).toUpperCase() + event.source_app.slice(1); - return { ...event, agent_name: capitalizedName }; - } - - const agentName = agentSessions.get(event.session_id) || mainAgentName; - return { ...event, agent_name: agentName }; -} - -function processTodoEvent(event: HookEvent): HookEvent[] { - if (event.payload.tool_name !== 'TodoWrite') { - return [event]; - } - - const currentTodos = event.payload.tool_input?.todos || []; - const previousTodos = sessionTodos.get(event.session_id) || []; - const completedTodos = []; - - for (const currentTodo of currentTodos) { - if (currentTodo.status === 'completed') { - const prevTodo = previousTodos.find((t: any) => t.content === currentTodo.content); - if (!prevTodo || prevTodo.status !== 'completed') { - completedTodos.push(currentTodo); - } - } - } - - sessionTodos.set(event.session_id, currentTodos); - const events: HookEvent[] = [event]; - - for (const completedTodo of completedTodos) { - const completionEvent: HookEvent = { - ...event, - id: event.id, - hook_event_type: 'Completed', - payload: { task: completedTodo.content }, - summary: undefined, - timestamp: event.timestamp - }; - events.push(completionEvent); - } - - return events; -} - -function readNewEvents(filePath: string): HookEvent[] { - if (!existsSync(filePath)) return []; - - const lastPosition = filePositions.get(filePath) || 0; - - try { - const content = readFileSync(filePath, 'utf-8'); - const newContent = content.slice(lastPosition); - filePositions.set(filePath, content.length); - - if (!newContent.trim()) return []; - - const lines = newContent.trim().split('\n'); - const newEvents: HookEvent[] = []; - - for (const line of lines) { - if (!line.trim()) continue; - - try { - let event = JSON.parse(line); - event.id = events.length + newEvents.length + 1; - event = enrichEventWithAgentName(event); - const processedEvents = processTodoEvent(event); - - for (let i = 0; i < processedEvents.length; i++) { - processedEvents[i].id = events.length + newEvents.length + i + 1; - } - newEvents.push(...processedEvents); - } catch (error) { - console.error(`Failed to parse line: ${line.slice(0, 100)}...`, error); - } - } - - return newEvents; - } catch (error) { - console.error(`Error reading file ${filePath}:`, error); - return []; - } -} - -function storeEvents(newEvents: HookEvent[]): void { - if (newEvents.length === 0) return; - - events.push(...newEvents); - - if (events.length > MAX_EVENTS) { - events.splice(0, events.length - MAX_EVENTS); - } - - console.log(`✅ Received ${newEvents.length} event(s) (${events.length} in memory)`); - - if (onEventsReceived) { - onEventsReceived(newEvents); - } -} - -function loadAgentSessions(): void { - const paiDir = process.env.PAI_DIR || join(homedir(), '.config', 'pai'); - const sessionsFile = join(paiDir, 'agent-sessions.json'); - - if (!existsSync(sessionsFile)) { - console.log('⚠️ agent-sessions.json not found, agent names will use defaults'); - return; - } - - try { - const content = readFileSync(sessionsFile, 'utf-8'); - const data = JSON.parse(content); - - agentSessions.clear(); - Object.entries(data).forEach(([sessionId, agentName]) => { - agentSessions.set(sessionId, agentName as string); - }); - - console.log(`✅ Loaded ${agentSessions.size} agent sessions`); - } catch (error) { - console.error('❌ Error loading agent-sessions.json:', error); - } -} - -function watchAgentSessions(): void { - const paiDir = process.env.PAI_DIR || join(homedir(), '.config', 'pai'); - const sessionsFile = join(paiDir, 'agent-sessions.json'); - - if (!existsSync(sessionsFile)) return; - - console.log('👀 Watching agent-sessions.json for changes'); - - const watcher = watch(sessionsFile, (eventType) => { - if (eventType === 'change') { - console.log('🔄 agent-sessions.json changed, reloading...'); - loadAgentSessions(); - } - }); - - watcher.on('error', (error) => { - console.error('❌ Error watching agent-sessions.json:', error); - }); -} - -function watchFile(filePath: string): void { - if (watchedFiles.has(filePath)) return; - - console.log(`👀 Watching: ${filePath}`); - watchedFiles.add(filePath); - - // Position at END of file - only read NEW events - if (existsSync(filePath)) { - const content = readFileSync(filePath, 'utf-8'); - filePositions.set(filePath, content.length); - console.log(`📍 Positioned at end of file - only new events will be captured`); - } - - const watcher = watch(filePath, (eventType) => { - if (eventType === 'change') { - const newEvents = readNewEvents(filePath); - storeEvents(newEvents); - } - }); - - watcher.on('error', (error) => { - console.error(`Error watching ${filePath}:`, error); - watchedFiles.delete(filePath); - }); -} - -export function startFileIngestion(callback?: (events: HookEvent[]) => void): void { - const paiDir = process.env.PAI_DIR || join(homedir(), '.config', 'pai'); - - console.log('🚀 Starting file-based event streaming (in-memory only)'); - console.log(`📂 Reading from ${paiDir}/history/raw-outputs/`); - - if (callback) { - onEventsReceived = callback; - } - - loadAgentSessions(); - watchAgentSessions(); - - const todayFile = getTodayEventsFile(); - watchFile(todayFile); - - // Check for new day's file every hour - setInterval(() => { - const newTodayFile = getTodayEventsFile(); - if (!watchedFiles.has(newTodayFile)) { - console.log('📅 New day detected, watching new file'); - watchFile(newTodayFile); - } - }, 60 * 60 * 1000); - - console.log('✅ File streaming started'); -} - -export function getRecentEvents(limit: number = 100): HookEvent[] { - return events.slice(-limit).reverse(); -} - -export function getFilterOptions() { - const sourceApps = new Set(); - const sessionIds = new Set(); - const hookEventTypes = new Set(); - - for (const event of events) { - if (event.source_app) sourceApps.add(event.source_app); - if (event.session_id) sessionIds.add(event.session_id); - if (event.hook_event_type) hookEventTypes.add(event.hook_event_type); - } - - return { - source_apps: Array.from(sourceApps).sort(), - session_ids: Array.from(sessionIds).slice(0, 100), - hook_event_types: Array.from(hookEventTypes).sort() - }; -} -``` - -#### 3.4: Create index.ts (main server) - -```typescript -// $PAI_DIR/observability/apps/server/src/index.ts -// HTTP + WebSocket server for observability dashboard - -import { startFileIngestion, getRecentEvents, getFilterOptions } from './file-ingest'; - -const wsClients = new Set(); - -// Start file-based ingestion with WebSocket broadcast callback -startFileIngestion((events) => { - events.forEach(event => { - const message = JSON.stringify({ type: 'event', data: event }); - wsClients.forEach(client => { - try { - client.send(message); - } catch (err) { - wsClients.delete(client); - } - }); - }); -}); - -const server = Bun.serve({ - port: 4000, - - async fetch(req: Request) { - const url = new URL(req.url); - - const headers = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', - 'Access-Control-Allow-Headers': 'Content-Type', - }; - - if (req.method === 'OPTIONS') { - return new Response(null, { headers }); - } - - // GET /events/filter-options - if (url.pathname === '/events/filter-options' && req.method === 'GET') { - const options = getFilterOptions(); - return new Response(JSON.stringify(options), { - headers: { ...headers, 'Content-Type': 'application/json' } - }); - } - - // GET /events/recent - if (url.pathname === '/events/recent' && req.method === 'GET') { - const limit = parseInt(url.searchParams.get('limit') || '100'); - const events = getRecentEvents(limit); - return new Response(JSON.stringify(events), { - headers: { ...headers, 'Content-Type': 'application/json' } - }); - } - - // GET /events/by-agent/:agentName - if (url.pathname.startsWith('/events/by-agent/') && req.method === 'GET') { - const agentName = decodeURIComponent(url.pathname.split('/')[3]); - const limit = parseInt(url.searchParams.get('limit') || '100'); - - if (!agentName) { - return new Response(JSON.stringify({ error: 'Agent name is required' }), { - status: 400, - headers: { ...headers, 'Content-Type': 'application/json' } - }); - } - - const allEvents = getRecentEvents(limit); - const agentEvents = allEvents.filter(e => e.agent_name === agentName); - - return new Response(JSON.stringify(agentEvents), { - headers: { ...headers, 'Content-Type': 'application/json' } - }); - } - - // Health check - if (url.pathname === '/health' && req.method === 'GET') { - return new Response(JSON.stringify({ status: 'ok', timestamp: Date.now() }), { - headers: { ...headers, 'Content-Type': 'application/json' } - }); - } - - // WebSocket upgrade - if (url.pathname === '/stream') { - const success = server.upgrade(req); - if (success) { - return undefined; - } - } - - return new Response('PAI Observability Server', { - headers: { ...headers, 'Content-Type': 'text/plain' } - }); - }, - - websocket: { - open(ws) { - console.log('WebSocket client connected'); - wsClients.add(ws); - - const events = getRecentEvents(50); - ws.send(JSON.stringify({ type: 'initial', data: events })); - }, - - message(ws, message) { - console.log('Received message:', message); - }, - - close(ws) { - console.log('WebSocket client disconnected'); - wsClients.delete(ws); - }, - - error(ws, error) { - console.error('WebSocket error:', error); - wsClients.delete(ws); - } - } -}); - -console.log(`🚀 Server running on http://localhost:${server.port}`); -console.log(`📊 WebSocket endpoint: ws://localhost:${server.port}/stream`); -``` - ---- - -### Step 4: Create the Client - -The client is a Vue 3 + Vite application. Due to its complexity, a simplified starter client is provided here. - -#### 4.1: Create client package.json - -```json -// $PAI_DIR/observability/apps/client/package.json -{ - "name": "pai-observability-client", - "private": true, - "version": "1.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vue-tsc -b && vite build", - "preview": "vite preview" - }, - "dependencies": { - "lucide-vue-next": "^0.548.0", - "vue": "^3.5.17" - }, - "devDependencies": { - "@types/node": "^22.11.2", - "@vitejs/plugin-vue": "^6.0.0", - "@vue/tsconfig": "^0.7.0", - "autoprefixer": "^10.4.20", - "postcss": "^8.5.3", - "tailwindcss": "^3.4.16", - "typescript": "~5.8.3", - "vite": "^7.0.4", - "vue-tsc": "^2.2.12" - } -} -``` - -#### 4.2: Create vite.config.ts - -```typescript -// $PAI_DIR/observability/apps/client/vite.config.ts -import { defineConfig } from 'vite' -import vue from '@vitejs/plugin-vue' - -export default defineConfig({ - plugins: [vue()], - server: { - port: 5172 - } -}) -``` - -#### 4.3: Create index.html - -```html - - - - - - - - PAI Observability - - -
- - - -``` - -#### 4.4: Create src/main.ts - -```typescript -// $PAI_DIR/observability/apps/client/src/main.ts -import { createApp } from 'vue' -import App from './App.vue' -import './style.css' - -createApp(App).mount('#app') -``` - -#### 4.5: Create src/style.css - -```css -/* $PAI_DIR/observability/apps/client/src/style.css */ -@tailwind base; -@tailwind components; -@tailwind utilities; - -:root { - --bg-primary: #1a1b26; - --bg-secondary: #16161e; - --text-primary: #c0caf5; - --text-secondary: #565f89; - --accent-blue: #7aa2f7; - --accent-green: #9ece6a; - --accent-red: #f7768e; - --accent-yellow: #e0af68; -} - -body { - background-color: var(--bg-primary); - color: var(--text-primary); - font-family: 'Inter', sans-serif; -} -``` - -#### 4.6: Create src/App.vue (Simplified Starter) - -```vue - - - - -``` - -#### 4.7: Create tailwind.config.js - -```javascript -// $PAI_DIR/observability/apps/client/tailwind.config.js -/** @type {import('tailwindcss').Config} */ -export default { - content: [ - "./index.html", - "./src/**/*.{vue,js,ts,jsx,tsx}", - ], - theme: { - extend: {}, - }, - plugins: [], -} -``` - -#### 4.8: Create postcss.config.js - -```javascript -// $PAI_DIR/observability/apps/client/postcss.config.js -export default { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -} -``` - ---- - -### Step 5: Create the Management Script - -```bash -#!/bin/bash -# $PAI_DIR/observability/manage.sh -# Observability Dashboard Manager - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -# Ensure bun is in PATH -# Only add Homebrew path if it exists (macOS-specific) -if [ -d "/opt/homebrew/bin" ]; then - export PATH="$HOME/.bun/bin:/opt/homebrew/bin:/usr/local/bin:$PATH" -else - export PATH="$HOME/.bun/bin:/usr/local/bin:$PATH" -fi - -case "${1:-}" in - start) - if lsof -Pi :4000 -sTCP:LISTEN -t >/dev/null 2>&1; then - echo "❌ Already running. Use: manage.sh restart" - exit 1 - fi - - # Start server - cd "$SCRIPT_DIR/apps/server" - bun run dev >/dev/null 2>&1 & - SERVER_PID=$! - - # Wait for server - for i in {1..10}; do - curl -s http://localhost:4000/health >/dev/null 2>&1 && break - sleep 1 - done - - # Start client - cd "$SCRIPT_DIR/apps/client" - bun run dev >/dev/null 2>&1 & - CLIENT_PID=$! - - # Wait for client - for i in {1..10}; do - curl -s http://localhost:5172 >/dev/null 2>&1 && break - sleep 1 - done - - echo "✅ Observability running at http://localhost:5172" - - cleanup() { - kill $SERVER_PID $CLIENT_PID 2>/dev/null - exit 0 - } - trap cleanup INT - wait $SERVER_PID $CLIENT_PID - ;; - - stop) - SERVER_PID=$(lsof -ti :4000 2>/dev/null) - CLIENT_PID=$(lsof -ti :5172 2>/dev/null) - - [ -n "$SERVER_PID" ] && kill -9 $SERVER_PID 2>/dev/null - [ -n "$CLIENT_PID" ] && kill -9 $CLIENT_PID 2>/dev/null - - pkill -9 -f "observability/apps/(server|client)" 2>/dev/null - - sleep 0.5 - echo "✅ Observability stopped" - ;; - - restart) - echo "🔄 Restarting..." - "$0" stop 2>/dev/null - sleep 2 - exec "$0" start - ;; - - status) - if lsof -Pi :4000 -sTCP:LISTEN -t >/dev/null 2>&1; then - echo "✅ Running at http://localhost:5172" - else - echo "❌ Not running" - fi - ;; - - start-detached) - if lsof -Pi :4000 -sTCP:LISTEN -t >/dev/null 2>&1; then - echo "❌ Already running. Use: manage.sh restart" - exit 1 - fi - - cd "$SCRIPT_DIR/apps/server" - nohup bun run dev >/dev/null 2>&1 & - disown - - for i in {1..10}; do - curl -s http://localhost:4000/health >/dev/null 2>&1 && break - sleep 1 - done - - cd "$SCRIPT_DIR/apps/client" - nohup bun run dev >/dev/null 2>&1 & - disown - - for i in {1..10}; do - curl -s http://localhost:5172 >/dev/null 2>&1 && break - sleep 1 - done - - echo "✅ Observability running at http://localhost:5172" - ;; - - *) - echo "Usage: manage.sh {start|stop|restart|status|start-detached}" - exit 1 - ;; -esac -``` - -Make it executable: -```bash -chmod +x $PAI_DIR/observability/manage.sh -``` - ---- - -### Step 6: Register the Capture Hook - -Add the capture hook to your Claude settings.json: - -**File location:** `~/.claude/settings.json` - -Add to the hooks section: - -```json -{ - "hooks": { - "PostToolUse": [ - { - "matcher": "*", - "hooks": [ - { - "type": "command", - "command": "bun run $PAI_DIR/hooks/capture-all-events.ts --event-type PostToolUse" - } - ] - } - ], - "PreToolUse": [ - { - "matcher": "*", - "hooks": [ - { - "type": "command", - "command": "bun run $PAI_DIR/hooks/capture-all-events.ts --event-type PreToolUse" - } - ] - } - ], - "Stop": [ - { - "matcher": "*", - "hooks": [ - { - "type": "command", - "command": "bun run $PAI_DIR/hooks/capture-all-events.ts --event-type Stop" - } - ] - } - ], - "SubagentStop": [ - { - "matcher": "*", - "hooks": [ - { - "type": "command", - "command": "bun run $PAI_DIR/hooks/capture-all-events.ts --event-type SubagentStop" - } - ] - } - ], - "UserPromptSubmit": [ - { - "matcher": "*", - "hooks": [ - { - "type": "command", - "command": "bun run $PAI_DIR/hooks/capture-all-events.ts --event-type UserPromptSubmit" - } - ] - } - ] - } -} -``` - ---- - -### Step 7: Install Dependencies - -```bash -# Install server dependencies -cd $PAI_DIR/observability/apps/server -bun install - -# Install client dependencies -cd $PAI_DIR/observability/apps/client -bun install -``` - ---- - -### Step 8: Verify Installation - -```bash -# 1. Start observability -$PAI_DIR/observability/manage.sh start - -# 2. Open browser to http://localhost:5172 - -# 3. In another terminal, use Claude Code - events should stream to dashboard - -# 4. Check status -$PAI_DIR/observability/manage.sh status - -# 5. Stop when done -$PAI_DIR/observability/manage.sh stop -``` - -**Success indicators:** -- Dashboard loads at http://localhost:5172 -- Events appear in real-time as you use Claude Code -- Agent names show correctly (main agent, interns, etc.) -- WebSocket connection stays stable - ---- - -## Example Usage - -### Example 1: Basic Monitoring - -``` -User: "start observability" - -→ $PAI_DIR/observability/manage.sh start -→ Opens http://localhost:5172 in browser -→ Events stream as you work -``` - -### Example 2: Watching Agent Spawns - -``` -User: (Working with multiple agents) - -Dashboard shows: -- Main agent: Reading files, running commands -- Intern-1: Spawned, researching topic A -- Intern-2: Spawned, researching topic B -- All activity visible in real-time -``` - -### Example 3: Debugging a Workflow - -``` -User: "Something's wrong with my workflow" - -→ Open observability dashboard -→ Watch events in timeline -→ See where the sequence differs from expected -→ Identify which tool call failed or returned unexpected output -``` - ---- - -## Configuration - -**Environment variables:** - -| Variable | Default | Purpose | -|----------|---------|---------| -| `PAI_DIR` | `~/.config/pai` | Root PAI directory | -| `TIME_ZONE` | System default | Timestamp timezone | -| `DA` | `main` | Default agent name | - -**Ports:** - -| Service | Port | Purpose | -|---------|------|---------| -| Server | 4000 | HTTP API + WebSocket | -| Client | 5172 | Dashboard UI | - ---- - -## Customization - -### Recommended Customization - -**Customize the Dashboard for Your Workflow** - -The default dashboard shows all events in a simple timeline. Customize it to highlight what matters most to your work. - -**What to Customize:** `$PAI_DIR/observability/apps/client/src/App.vue` - -**Why:** Different developers care about different things. Security-focused work might want to highlight Bash commands. Research workflows might want to emphasize agent spawns. A customized dashboard surfaces the information you need. - -**Process:** - -1. **Identify Your Key Events** - - What events do you care about most? - - Which agents do you want prominently displayed? - - What information should be visible at a glance? - -2. **Customize Event Colors** - Edit the `getBorderColor` function: - ```typescript - function getBorderColor(eventType: string): string { - const colors: Record = { - 'PreToolUse': 'border-[var(--accent-blue)]', - 'PostToolUse': 'border-[var(--accent-green)]', - 'Stop': 'border-[var(--accent-yellow)]', - 'Completed': 'border-[var(--accent-green)]', - // Add your custom highlights: - 'SecurityWarning': 'border-red-500', - 'AgentSpawn': 'border-purple-500', - } - return colors[eventType] || 'border-[var(--text-secondary)]' - } - ``` - -3. **Add Custom Filtering** - Modify the template to add agent or tool filters: - ```vue - - ``` - -**Expected Outcome:** A dashboard that immediately shows you what you care about. - ---- - -### Optional Customization - -| Customization | File | Impact | -|---------------|------|--------| -| **Theme Colors** | `style.css` | Change the color scheme | -| **Event Limit** | `file-ingest.ts` | Adjust MAX_EVENTS for memory usage | -| **Ports** | `manage.sh`, `vite.config.ts`, `index.ts` | Change server/client ports | -| **Agent Name Mapping** | `file-ingest.ts` | Add recognition for custom agent types | - -**Example: Change Theme Colors** - -Edit `$PAI_DIR/observability/apps/client/src/style.css`: - -```css -:root { - --bg-primary: #0d1117; /* Darker background */ - --accent-blue: #58a6ff; /* GitHub-style blue */ - --accent-green: #3fb950; /* GitHub-style green */ -} -``` - -**Example: Add Custom Agent Recognition** - -Edit `file-ingest.ts` to recognize your custom agents: - -```typescript -const subAgentTypes = [ - 'artist', 'intern', 'engineer', 'pentester', 'architect', - // Add your custom agent types: - 'security-researcher', 'documentation-writer', 'code-reviewer' -]; -``` - ---- - -## Credits - -- **Original concept**: Daniel Miessler - developed as part of Kai personal AI infrastructure -- **Contributors**: The PAI community -- **Underlying systems**: Bun.js, Vue 3, Tailwind CSS - -## Related Work - -- **kai-hook-system** - Required dependency - provides the event capture hooks -- **Grafana/Prometheus** - Enterprise monitoring inspiration -- **Datadog APM** - Distributed tracing concepts - -## Works Well With - -- **kai-hook-system** - Required - captures the events that this pack displays -- **kai-core-install** - Skills show their activity in the dashboard -- **kai-voice-system** - Voice notifications can be triggered from events - -## Recommended - -- **kai-history-system** - Permanent storage of events for later analysis - -## Relationships - -### Parent Of -*None* - -### Child Of -- **kai-hook-system** - Depends on hooks for event capture - -### Sibling Of -- **kai-history-system** - Both consume hook events, different purposes - -### Part Of Collection -**Kai Core Bundle** - One of the foundational packs that together create the complete Kai personal AI infrastructure. - ---- - -## Changelog - -### 1.0.0 - 2025-12-29 -- Initial release -- File-based event streaming architecture -- Bun HTTP + WebSocket server -- Vue 3 starter dashboard -- Multi-agent tracking with session mapping -- Management script for start/stop/restart -- Complete hook integration for event capture - diff --git a/Packs/kai-prompting-skill.md b/Packs/kai-prompting-skill.md deleted file mode 100644 index 869e9e0eda..0000000000 --- a/Packs/kai-prompting-skill.md +++ /dev/null @@ -1,1997 +0,0 @@ ---- -name: Kai Prompting Skill -pack-id: danielmiessler-kai-prompting-skill-v1.0.0 -version: 1.0.0 -author: danielmiessler -description: Meta-prompting system for dynamic prompt generation using Handlebars templates, Claude 4.x best practices, and the Fabric pattern system. Includes the Ultimate Prompt Template, five core primitives (Roster, Voice, Structure, Briefing, Gate), and CLI tools for rendering and validation. -type: feature -purpose-type: [productivity, development, automation] -platform: claude-code -dependencies: - - kai-core-install (required) - Skills directory structure and routing - - kai-hook-system (optional) - For session startup context loading -keywords: [prompting, templates, handlebars, meta-prompting, prompt-engineering, fabric, claude-4, context-engineering, templating] ---- - -

- Kai Prompting Skill -

- -# Kai Prompting Skill (kai-prompting-skill) - -> Prompts that write prompts. Meta-prompting enables dynamic composition where structure is fixed but content is parameterized. - -## Installation Prompt - -You are receiving a PAI Pack - a modular upgrade for AI agent systems. - -**What is PAI?** See: [PAI Project Overview](../README.md#what-is-pai) - -**What is a Pack?** See: [Pack System](../README.md#pack-system) - -This Pack provides a complete prompt engineering system: - -**Prompt Engineering Standards** -- Claude 4.x Best Practices (behavioral characteristics, tool patterns) -- Context engineering principles (token efficiency, signal optimization) -- The Ultimate Prompt Template (modular, validated structure) -- Fabric pattern integration (248 reusable prompts) - -**Template System** -- Five core primitives: Roster, Voice, Structure, Briefing, Gate -- Handlebars-based templating (Anthropic's official syntax) -- Data-driven prompt generation from YAML -- CLI tools for rendering and validation - -**Core Philosophy:** Find the smallest possible set of high-signal tokens that maximize the likelihood of desired outcomes. - -Please follow the installation instructions below to integrate this Pack into your infrastructure. - ---- - -## What's Included - -| Component | File | Purpose | -|-----------|------|---------| -| Prompting skill | `skills/Prompting/SKILL.md` | Routing and quick reference | -| Standards guide | `skills/Prompting/Standards.md` | Claude 4.x best practices | -| Roster template | `skills/Prompting/Templates/Primitives/Roster.hbs` | Agent/skill definitions | -| Voice template | `skills/Prompting/Templates/Primitives/Voice.hbs` | Personality calibration | -| Structure template | `skills/Prompting/Templates/Primitives/Structure.hbs` | Workflow patterns | -| Briefing template | `skills/Prompting/Templates/Primitives/Briefing.hbs` | Agent context handoff | -| Gate template | `skills/Prompting/Templates/Primitives/Gate.hbs` | Validation checklists | -| Render tool | `skills/Prompting/Tools/RenderTemplate.ts` | Template rendering CLI | -| Validate tool | `skills/Prompting/Tools/ValidateTemplate.ts` | Template validation | - -**Summary:** -- **Files created:** 12+ (skill, standards, templates, tools) -- **Hooks registered:** 0 (skill-based, not hook-based) -- **Dependencies:** kai-core-install (required) - ---- - -# PART 1: THE PROBLEM - -## Why You Need This - -### Without Prompt Standards - -1. **Inconsistent Quality** - Prompts vary wildly in effectiveness -2. **Reinventing Wheels** - Every prompt starts from scratch -3. **Token Waste** - Verbose prompts that don't improve results -4. **Model Confusion** - Using patterns that hurt Claude 4.x performance -5. **No Validation** - No way to verify prompt structure - -### Without Templates - -1. **Manual Repetition** - Copy-pasting similar structures -2. **Drift** - Agent definitions diverge over time -3. **Maintenance Burden** - Updating 30 agents means 30 edits -4. **No Separation** - Structure and content entangled -5. **Testing Difficulty** - Can't A/B test structure vs. content - -### The Core Insight - -Prompt engineering research shows: -- **10-90% performance variation** based on structure choices -- **Few-shot examples** add +25-90% improvement (1-3 optimal) -- **Clear instructions** reduce ambiguity and improve task completion -- **Structured organization** provides consistent performance gains - -This isn't opinion—it's validated by 1,500+ academic papers and production systems. - ---- - -# PART 2: THE SOLUTION - -This pack provides two integrated components: - -| Component | Purpose | -|-----------|---------| -| **Standards.md** | Complete prompt engineering guide based on Claude 4.x best practices | -| **Template System** | Handlebars-based dynamic prompt generation | - -## The Standards - -Based on: -- Anthropic's "Claude 4.x Best Practices" (November 2025) -- Anthropic's "Effective Context Engineering for AI Agents" -- Daniel Miessler's Fabric System (248 patterns) -- "The Prompt Report" (1,500+ papers, 58 techniques) -- "The Prompt Canvas" (100+ papers reviewed) - -## The Templates - -Five core primitives that cover 90% of prompt composition needs: - -| Primitive | Purpose | Use Case | -|-----------|---------|----------| -| **ROSTER** | Data-driven definitions | Agent personalities, skill listings | -| **VOICE** | Personality calibration | Voice parameters, trait settings | -| **STRUCTURE** | Workflow patterns | Phased analysis, debate rounds | -| **BRIEFING** | Agent context handoff | Task delegation, research queries | -| **GATE** | Validation checklists | Quality checks, completion criteria | - ---- - -# PART 3: ARCHITECTURE - -## Component Overview - -``` -$PAI_DIR/skills/Prompting/ -├── SKILL.md # Skill routing and overview -├── Standards.md # Complete prompt engineering guide -├── Templates/ -│ ├── README.md # Template system documentation -│ ├── Primitives/ # Core template files (.hbs) -│ │ ├── Roster.hbs -│ │ ├── Voice.hbs -│ │ ├── Structure.hbs -│ │ ├── Briefing.hbs -│ │ └── Gate.hbs -│ ├── Data/ # Example YAML data sources -│ │ └── Examples/ -│ └── Compiled/ # Generated output (gitignored) -└── Tools/ - ├── RenderTemplate.ts - └── ValidateTemplate.ts -``` - -## Token Efficiency - -The templating system reduces duplication significantly: - -| Area | Before | After | Savings | -|------|--------|-------|---------| -| Agent Briefings | 6,400 tokens | 1,900 tokens | 70% | -| SKILL.md Files | 20,750 tokens | 8,300 tokens | 60% | -| Workflow Steps | 7,500 tokens | 3,000 tokens | 60% | -| Voice Notifications | 6,225 tokens | 725 tokens | 88% | -| **TOTAL** | ~53,000 | ~18,000 | **65%** | - ---- - -# PART 4: PROMPT ENGINEERING STANDARDS - -## Claude 4.x Behavioral Characteristics - -### Communication Style - -- **More direct reporting** - Claude 4.5 provides fact-based progress updates -- **Conversational efficiency** - Natural language without unnecessary elaboration -- **Request verbosity explicitly** - Add "provide a quick summary of the work you've done" - -### Attention to Detail - -- **Example sensitivity** - Claude 4.x pays close attention to example details -- **Misaligned examples encourage unintended behaviors** -- Ensure examples match desired outcomes exactly - -### Tool Usage Patterns - -- **Opus 4.5 may overtrigger tools** - Use softer language -- **Change:** "CRITICAL: You MUST use this tool" → "Use this tool when..." - -### Extended Thinking Sensitivity - -When extended thinking is disabled: -- **Avoid:** "think", "think about", "think through" -- **Use instead:** "consider", "believe", "evaluate", "reflect", "assess" - ---- - -## Key Principles - -### 0. Markdown Only - NO XML Tags - -**CRITICAL: Use markdown for ALL prompt structure. Never use XML tags.** - -**NEVER use XML-style tags:** -``` -Do something -Some context -``` - -**ALWAYS use markdown headers:** -```markdown -## Instructions - -Do something - -## Context - -Some context -``` - -### 1. Be Explicit with Instructions - -Claude 4.x requires clear, specific direction rather than vague requests. - -- "Include as many relevant features as possible" -- "Go beyond basics" -- Quality modifiers enhance results - -### 2. Add Context and Motivation - -Explain *why* certain behavior matters to help Claude understand goals. - -**Good:** -``` -Your response will be read aloud by text-to-speech, so never use ellipses or incomplete sentences. -``` - -**Bad:** -``` -NEVER use ellipses. -``` - -### 3. Tell Instead of Forbid - -Frame instructions positively rather than as prohibitions. - -**Good:** -``` -Compose smoothly flowing prose paragraphs with natural transitions. -``` - -**Bad:** -``` -Do not use markdown or bullet points. -``` - -### 4. Context is a Finite Resource - -- LLMs have a limited "attention budget" -- As context length increases, model performance degrades -- Every token depletes attention capacity -- Treat context as precious and finite - -### 5. Optimize for Signal-to-Noise Ratio - -- Prefer clear, direct language over verbose explanations -- Remove redundant or overlapping information -- Focus on high-value tokens that drive desired outcomes - ---- - -## The Ultimate Prompt Template - -Synthesized from Anthropic's Claude 4.x Best Practices, context engineering principles, and 1,500+ academic papers. - -### Full Template - -```markdown -# [Task Name] - -## Context & Motivation -[WHY this matters - Claude generalizes from reasoning provided] - -## Background -[Minimal essential context - every token costs attention] - -## Instructions -[Positive framing: tell what TO do. Imperative voice. Ordered by priority.] - -1. [First clear, actionable directive] -2. [Second directive] -3. [Third directive] - -## Examples -[1-3 examples optimal. Claude 4.x is HIGHLY sensitive to details.] - -**Example 1: [Scenario]** -- Input: [Representative input] -- Output: [Exact desired output] - -## Constraints -[Positive framing preferred. Define success/failure criteria.] - -- **Success:** [What defines successful completion] -- **Failure:** [What defines failure] - -## Output Format -[Explicit specification reduces format errors significantly] - -## Tools -[SOFT LANGUAGE - avoid "MUST use"] - -- `tool_name(params)` - Use when [specific condition] - -## Action Bias -[Choose ONE based on task type] - -### For Implementation Tasks -Implement changes rather than suggesting. Use tools to discover missing details. - -### For Research Tasks -Default to information gathering and recommendations. -``` - -### Section Selection Matrix - -| Task Type | Required | Recommended | Optional | -|-----------|----------|-------------|----------| -| **Simple Query** | Instructions, Output Format | Context | — | -| **Complex Implementation** | Context, Instructions, Output Format, Tools | Examples, Constraints | Action Bias | -| **Research/Analysis** | Context, Instructions, Constraints | Examples | State Tracking | -| **Agentic Coding** | Context, Instructions, Tools, Verification | Constraints, Parallel | State Tracking | - -### Claude 4.x Transformations Quick Reference - -| Avoid | Use Instead | -|-------|-------------| -| "CRITICAL: You MUST use this tool" | "Use this tool when..." | -| "Don't use markdown" | "Write in flowing prose paragraphs" | -| "NEVER do X" | "Do Y instead" (positive framing) | -| "Think about this carefully" | "Consider this carefully" | -| "You should probably..." | "Do X" (imperative, direct) | -| 10 examples | 1-3 examples (diminishing returns) | - ---- - -## Agentic Coding Best Practices - -### Read Before Edit - -```markdown -## Verification - -Never speculate about code you haven't opened. If a specific file is referenced, READ it before answering. Give grounded, hallucination-free answers based on actual code inspection. -``` - -### Prevent Overengineering - -``` -Avoid over-engineering. Only make directly requested or clearly necessary changes. Keep solutions simple and focused. Don't add unrequested features. Implement minimum complexity needed for current task. -``` - -### Parallel Tool Calling - -```markdown -## Parallel Execution - -If calling multiple tools with no dependencies, make all independent calls in parallel. Never guess parameters. -``` - ---- - -# PART 5: TEMPLATE SYSTEM - -## Core Syntax - -Handlebars notation (Anthropic's official syntax): - -| Syntax | Purpose | Example | -|--------|---------|---------| -| `{{variable}}` | Simple interpolation | `Hello {{name}}` | -| `{{object.property}}` | Nested access | `{{agent.voice_id}}` | -| `{{#each items}}...{{/each}}` | Iteration | List generation | -| `{{#if condition}}...{{/if}}` | Conditional | Optional sections | -| `{{> partial}}` | Include partial | Reusable components | - ---- - -## Primitive Templates - -### 1. ROSTER — Agent & Skill Definitions - -**Purpose:** Generate structured definitions from YAML data sources. - -**Use Cases:** -- Agent personality rosters -- Skill directory listings -- Voice configuration tables - -**Data Structure:** -```yaml -agents: - agent_id: - id: string - name: string - display_name: string - role: string - emoji: string - personality: - traits: [string] - perspective: string - style: string -``` - -**Template:** `Primitives/Roster.hbs` - ---- - -### 2. VOICE — Personality Calibration - -**Purpose:** Generate voice configuration for agents and notifications. - -**Use Cases:** -- ElevenLabs voice settings -- Agent personality calibration -- Voice preset documentation - -**Data Structure:** -```yaml -agents: - agent_id: - voice: - voice_id: string - voice_name: string - rate_wpm: number - stability: number - similarity_boost: number -presets: - preset_name: - stability: number - similarity_boost: number -``` - -**Template:** `Primitives/Voice.hbs` - ---- - -### 3. STRUCTURE — Workflow Patterns - -**Purpose:** Generate standardized multi-step execution patterns. - -**Use Cases:** -- Phased analysis workflows -- Round-based debate patterns -- Sequential pipelines - -**Data Structure:** -```yaml -workflow: - name: string - description: string - timing: string (optional) - phases: - - name: string - purpose: string - steps: - - action: string - instructions: string - tools: [string] (optional) -``` - -**Template:** `Primitives/Structure.hbs` - ---- - -### 4. BRIEFING — Agent Context Handoff - -**Purpose:** Standardize how agents receive tasks and context. - -**Use Cases:** -- Research agent queries -- RedTeam analyst prompts -- Delegation context packages - -**Data Structure:** -```yaml -briefing: - type: string (research | analysis | debate | task) - context_level: string (minimal | standard | full) -agent: - id: string - name: string - role: string - personality: - perspective: string - traits: [string] -context: - summary: string - background: string (optional) -task: - description: string - questions: [string] (optional) -output_format: - type: string (markdown | json | structured) -``` - -**Template:** `Primitives/Briefing.hbs` - ---- - -### 5. GATE — Validation Checklists - -**Purpose:** Generate reusable quality and completion checks. - -**Use Cases:** -- Art mandatory elements -- Development completion gates -- Research source verification - -**Data Structure:** -```yaml -gate: - name: string - category: string - description: string - action_on_fail: string - mandatory: - - name: string - description: string - recommended: - - name: string - description: string -``` - -**Template:** `Primitives/Gate.hbs` - ---- - -# PART 6: INSTALLATION - -## Prerequisites - -- **Bun runtime**: `curl -fsSL https://bun.sh/install | bash` -- **Claude Code** (or compatible agent system) -- **kai-core-install Pack** installed (required for Skills directory) - ---- - -## Step 1: Create Directory Structure - -```bash -mkdir -p $PAI_DIR/skills/Prompting/Templates/Primitives -mkdir -p $PAI_DIR/skills/Prompting/Templates/Data/Examples -mkdir -p $PAI_DIR/skills/Prompting/Templates/Compiled -mkdir -p $PAI_DIR/skills/Prompting/Tools -``` - ---- - -## Step 2: Create SKILL.md - -Save to `$PAI_DIR/skills/Prompting/SKILL.md`: - -```markdown ---- -name: Prompting -description: Meta-prompting system for dynamic prompt generation using templates, standards, and patterns. USE WHEN meta-prompting, template generation, prompt optimization, or programmatic prompt composition. ---- - -# Prompting - Meta-Prompting & Template System - -**Invoke when:** meta-prompting, template generation, prompt optimization, programmatic prompt composition, creating dynamic agents, generating structured prompts from data. - -## Overview - -The Prompting skill owns ALL prompt engineering concerns: -- **Standards** - Anthropic best practices, Claude 4.x patterns, empirical research -- **Templates** - Handlebars-based system for programmatic prompt generation -- **Tools** - Template rendering, validation, and composition utilities -- **Patterns** - Reusable prompt primitives and structures - -## Workflow Routing - -| Workflow | Trigger | File | -|----------|---------|------| -| **RenderTemplate** | "render template", "generate from template" | CLI tool | -| **ValidateTemplate** | "validate template", "check template syntax" | CLI tool | -| **ApplyStandards** | "review prompt", "optimize prompt" | Reference Standards.md | - -## Core Components - -### 1. Standards.md -Complete prompt engineering documentation based on: -- Anthropic's Claude 4.x Best Practices (November 2025) -- Context engineering principles -- 1,500+ academic papers on prompt optimization - -### 2. Templates/ -Five core primitives for programmatic prompt generation: - -| Primitive | Purpose | -|-----------|---------| -| **ROSTER** | Agent/skill definitions from data | -| **VOICE** | Personality calibration settings | -| **STRUCTURE** | Multi-step workflow patterns | -| **BRIEFING** | Agent context handoff | -| **GATE** | Validation checklists | - -### 3. Tools/ - -**RenderTemplate.ts** - Core rendering engine -\`\`\`bash -bun run $PAI_DIR/skills/Prompting/Tools/RenderTemplate.ts \ - --template Primitives/Briefing.hbs \ - --data path/to/data.yaml \ - --output path/to/output.md -\`\`\` - -**ValidateTemplate.ts** - Template syntax checker -\`\`\`bash -bun run $PAI_DIR/skills/Prompting/Tools/ValidateTemplate.ts \ - --template Primitives/Briefing.hbs -\`\`\` - -## Examples - -**Example 1: Generate agent roster** -\`\`\` -User: "Generate a roster from my agents.yaml" -→ Uses RenderTemplate with Roster.hbs -→ Outputs formatted agent definitions -\`\`\` - -**Example 2: Create briefing for research agent** -\`\`\` -User: "Brief the research agent on this task" -→ Uses RenderTemplate with Briefing.hbs -→ Generates complete agent context handoff -\`\`\` - -**Example 3: Validate template syntax** -\`\`\` -User: "Check my new template for errors" -→ Uses ValidateTemplate -→ Reports syntax issues, missing variables -\`\`\` - -## Best Practices - -1. **Separation of Concerns** - Templates for structure, YAML for content -2. **Keep Templates Simple** - Business logic in TypeScript, not templates -3. **DRY Principle** - Extract repeated patterns into partials -4. **Validate Before Rendering** - Check all required variables exist - -## References - -- `Standards.md` - Complete prompt engineering guide -- `Templates/README.md` - Template system overview -- `Tools/RenderTemplate.ts` - Implementation details -``` - ---- - -## Step 3: Create Standards.md - -Save to `$PAI_DIR/skills/Prompting/Standards.md`: - -```markdown ---- -type: documentation -category: methodology -description: Prompt engineering standards based on Anthropic's Claude 4.x best practices, context engineering principles, and empirical research. ---- - -# Prompt Engineering Standards - -**Foundation:** Based on Anthropic's Claude 4.x Best Practices (November 2025), context engineering principles, and 1,500+ academic papers. - -**Philosophy:** Universal principles of semantic clarity and structure that work regardless of model, with specific optimizations for Claude 4.x behavioral patterns. - ---- - -# Core Philosophy - -**Context engineering** is the set of strategies for curating and maintaining the optimal set of tokens during LLM inference. - -**Primary Goal:** Find the smallest possible set of high-signal tokens that maximize the likelihood of desired outcomes. - ---- - -# Claude 4.x Behavioral Characteristics - -## Communication Style Changes - -- **More direct reporting:** Claude 4.5 provides fact-based progress updates -- **Conversational efficiency:** Natural language without unnecessary elaboration -- **Request verbosity explicitly:** Add "provide a quick summary of the work you've done" - -## Attention to Detail - -- **Example sensitivity:** Claude 4.x pays close attention to details in examples -- **Misaligned examples encourage unintended behaviors** -- Ensure examples match desired outcomes exactly - -## Tool Usage Patterns - -- **Opus 4.5 may overtrigger tools:** Dial back aggressive language -- **Change:** "CRITICAL: You MUST use this tool" → "Use this tool when..." - -## Extended Thinking Sensitivity - -When extended thinking is disabled: -- **Avoid:** "think", "think about", "think through" -- **Use instead:** "consider", "believe", "evaluate", "reflect", "assess" - ---- - -# Key Principles - -## 0. Markdown Only - NO XML Tags - -**CRITICAL: Use markdown for ALL prompt structure. Never use XML tags.** - -## 1. Be Explicit with Instructions - -Claude 4.x requires clear, specific direction. - -## 2. Add Context and Motivation - -Explain *why* certain behavior matters. - -## 3. Tell Instead of Forbid - -Frame instructions positively rather than as prohibitions. - -## 4. Context is a Finite Resource - -Every token depletes attention capacity. Treat context as precious. - -## 5. Optimize for Signal-to-Noise Ratio - -Focus on high-value tokens that drive desired outcomes. - ---- - -# Empirical Foundation - -**Research validates that prompt structure has measurable, significant impact:** - -- **Performance Range:** 10-90% variation based on structure choices -- **Few-Shot Examples:** +25% to +90% improvement (optimal: 1-3 examples) -- **Structured Organization:** Consistent performance gains -- **Full Component Integration:** +25% improvement on complex tasks - -**Sources:** 1,500+ academic papers, Microsoft PromptBench, Amazon Alexa production testing. - ---- - -# The Ultimate Prompt Template - -## Full Template - -\`\`\`markdown -# [Task Name] - -## Context & Motivation -[WHY this matters - Claude generalizes from reasoning provided] - -## Background -[Minimal essential context - every token costs attention] - -## Instructions -1. [First clear, actionable directive] -2. [Second directive] -3. [Third directive] - -## Examples -**Example 1: [Scenario]** -- Input: [Representative input] -- Output: [Exact desired output] - -## Constraints -- **Success:** [What defines successful completion] -- **Failure:** [What defines failure] - -## Output Format -[Explicit specification] - -## Tools -- \`tool_name(params)\` - Use when [specific condition] -\`\`\` - -## Section Selection Matrix - -| Task Type | Required | Recommended | -|-----------|----------|-------------| -| Simple Query | Instructions, Output Format | Context | -| Complex Implementation | Context, Instructions, Output Format, Tools | Examples, Constraints | -| Research/Analysis | Context, Instructions, Constraints | Examples | -| Agentic Coding | Context, Instructions, Tools, Verification | Constraints | - ---- - -# Anti-Patterns to Avoid - -- **Verbose Explanations** - Be direct -- **Negative-Only Constraints** - Tell what TO do -- **Aggressive Tool Language** - Use soft framing -- **Misaligned Examples** - Check carefully -- **Example Overload** - 1-3 examples optimal -- **Using "Think" with Extended Thinking Disabled** - Use "consider" instead - ---- - -# References - -**Primary Sources:** -- Anthropic: "Claude 4.x Best Practices" (November 2025) -- Anthropic: "Effective Context Engineering for AI Agents" -- Daniel Miessler's Fabric System (January 2024) -- "The Prompt Report" - arXiv:2406.06608 (58 techniques) -- "The Prompt Canvas" - arXiv:2412.05127 (100+ papers) -``` - ---- - -## Step 4: Create RenderTemplate.ts - -Save to `$PAI_DIR/skills/Prompting/Tools/RenderTemplate.ts`: - -```typescript -#!/usr/bin/env bun -/** - * RenderTemplate.ts - Template Rendering Engine - * - * Renders Handlebars templates with YAML data sources. - * - * Usage: - * bun run RenderTemplate.ts --template --data [--output ] [--preview] - * - * Examples: - * bun run RenderTemplate.ts --template Primitives/Roster.hbs --data Data/Agents.yaml - * bun run RenderTemplate.ts -t Primitives/Gate.hbs -d Data/Gates.yaml --preview - */ - -import Handlebars from 'handlebars'; -import { parse as parseYaml } from 'yaml'; -import { readFileSync, writeFileSync, existsSync } from 'fs'; -import { resolve, dirname, basename } from 'path'; -import { parseArgs } from 'util'; - -// ============================================================================ -// Custom Handlebars Helpers -// ============================================================================ - -// Uppercase text -Handlebars.registerHelper('uppercase', (str: string) => { - return str?.toUpperCase() ?? ''; -}); - -// Lowercase text -Handlebars.registerHelper('lowercase', (str: string) => { - return str?.toLowerCase() ?? ''; -}); - -// Title case text -Handlebars.registerHelper('titlecase', (str: string) => { - return str?.replace(/\w\S*/g, (txt) => - txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase() - ) ?? ''; -}); - -// Indent text by N spaces -Handlebars.registerHelper('indent', (str: string, spaces: number) => { - if (!str) return ''; - const indent = ' '.repeat(typeof spaces === 'number' ? spaces : 2); - return str.split('\n').map(line => indent + line).join('\n'); -}); - -// Join array with separator -Handlebars.registerHelper('join', (arr: string[], separator: string) => { - if (!Array.isArray(arr)) return ''; - return arr.join(typeof separator === 'string' ? separator : ', '); -}); - -// Check if value equals another -Handlebars.registerHelper('eq', (a: unknown, b: unknown) => a === b); - -// Check if value is greater than -Handlebars.registerHelper('gt', (a: number, b: number) => a > b); - -// Check if value is less than -Handlebars.registerHelper('lt', (a: number, b: number) => a < b); - -// Check if array includes value -Handlebars.registerHelper('includes', (arr: unknown[], value: unknown) => { - return Array.isArray(arr) && arr.includes(value); -}); - -// Get current date/time -Handlebars.registerHelper('now', (format?: string) => { - const now = new Date(); - if (format === 'date') return now.toISOString().split('T')[0]; - if (format === 'time') return now.toTimeString().split(' ')[0]; - return now.toISOString(); -}); - -// Pluralize word based on count -Handlebars.registerHelper('pluralize', (count: number, singular: string, plural?: string) => { - const pluralForm = typeof plural === 'string' ? plural : `${singular}s`; - return count === 1 ? singular : pluralForm; -}); - -// Format number with commas -Handlebars.registerHelper('formatNumber', (num: number) => { - return num?.toLocaleString() ?? ''; -}); - -// Calculate percentage -Handlebars.registerHelper('percent', (value: number, total: number, decimals = 0) => { - if (!total) return '0'; - return ((value / total) * 100).toFixed(typeof decimals === 'number' ? decimals : 0); -}); - -// Truncate text to length -Handlebars.registerHelper('truncate', (str: string, length: number) => { - if (!str) return ''; - const maxLen = typeof length === 'number' ? length : 100; - return str.length > maxLen ? str.substring(0, maxLen) + '...' : str; -}); - -// Default value if undefined -Handlebars.registerHelper('default', (value: unknown, defaultValue: unknown) => { - return value ?? defaultValue; -}); - -// JSON stringify -Handlebars.registerHelper('json', (obj: unknown, pretty = false) => { - return JSON.stringify(obj, null, pretty ? 2 : undefined); -}); - -// Markdown code block -Handlebars.registerHelper('codeblock', (code: string, language?: string) => { - const lang = typeof language === 'string' ? language : ''; - return `\`\`\`${lang}\n${code}\n\`\`\``; -}); - -// Repeat helper for generating repeated content -Handlebars.registerHelper('repeat', (count: number, options: Handlebars.HelperOptions) => { - let result = ''; - for (let i = 0; i < count; i++) { - result += options.fn({ index: i, first: i === 0, last: i === count - 1 }); - } - return result; -}); - -// ============================================================================ -// Template Engine -// ============================================================================ - -interface RenderOptions { - templatePath: string; - dataPath: string; - outputPath?: string; - preview?: boolean; -} - -function resolveTemplatePath(path: string): string { - if (path.startsWith('/')) return path; - const templatesDir = dirname(dirname(import.meta.path)); - return resolve(templatesDir, path); -} - -function loadTemplate(templatePath: string): HandlebarsTemplateDelegate { - const fullPath = resolveTemplatePath(templatePath); - if (!existsSync(fullPath)) { - throw new Error(`Template not found: ${fullPath}`); - } - const templateSource = readFileSync(fullPath, 'utf-8'); - return Handlebars.compile(templateSource); -} - -function loadData(dataPath: string): Record { - const fullPath = resolveTemplatePath(dataPath); - if (!existsSync(fullPath)) { - throw new Error(`Data file not found: ${fullPath}`); - } - const dataSource = readFileSync(fullPath, 'utf-8'); - if (dataPath.endsWith('.json')) { - return JSON.parse(dataSource); - } - return parseYaml(dataSource) as Record; -} - -function registerPartials(templatesDir: string): void { - const partialsDir = resolve(templatesDir, 'Partials'); - if (!existsSync(partialsDir)) return; - - const files = Bun.spawnSync(['ls', partialsDir]).stdout.toString().trim().split('\n'); - for (const file of files) { - if (file.endsWith('.hbs')) { - const partialName = basename(file, '.hbs'); - const partialPath = resolve(partialsDir, file); - const partialSource = readFileSync(partialPath, 'utf-8'); - Handlebars.registerPartial(partialName, partialSource); - } - } -} - -export function renderTemplate(options: RenderOptions): string { - const templatesDir = dirname(dirname(import.meta.path)); - registerPartials(templatesDir); - - const template = loadTemplate(options.templatePath); - const data = loadData(options.dataPath); - const rendered = template(data); - - if (options.preview) { - console.log('\n=== PREVIEW ===\n'); - console.log(rendered); - console.log('\n=== END PREVIEW ===\n'); - } - - if (options.outputPath) { - const outputFullPath = resolveTemplatePath(options.outputPath); - writeFileSync(outputFullPath, rendered); - console.log(`✓ Rendered to: ${outputFullPath}`); - } - - return rendered; -} - -// ============================================================================ -// CLI Interface -// ============================================================================ - -function main(): void { - const { values } = parseArgs({ - args: Bun.argv.slice(2), - options: { - template: { type: 'string', short: 't' }, - data: { type: 'string', short: 'd' }, - output: { type: 'string', short: 'o' }, - preview: { type: 'boolean', short: 'p' }, - help: { type: 'boolean', short: 'h' }, - }, - strict: true, - allowPositionals: false, - }); - - if (values.help || !values.template || !values.data) { - console.log(` -Template Renderer - -Usage: - bun run RenderTemplate.ts --template --data [options] - -Options: - -t, --template Template file (.hbs) - -d, --data Data file (.yaml or .json) - -o, --output Output file (optional) - -p, --preview Show preview in console - -h, --help Show this help - -Available Helpers: - {{uppercase str}} - Convert to uppercase - {{lowercase str}} - Convert to lowercase - {{titlecase str}} - Convert to title case - {{indent str spaces}} - Indent text - {{join arr separator}} - Join array - {{eq a b}} - Check equality - {{gt a b}} / {{lt a b}} - Greater/less than - {{now format}} - Current date/time - {{pluralize count word}} - Pluralize - {{formatNumber num}} - Format with commas - {{percent value total}} - Calculate percentage - {{truncate str length}} - Truncate to length - {{default value fallback}} - Default value - {{json obj pretty}} - JSON stringify - {{codeblock code lang}} - Markdown code block -`); - process.exit(values.help ? 0 : 1); - } - - try { - renderTemplate({ - templatePath: values.template, - dataPath: values.data, - outputPath: values.output, - preview: values.preview, - }); - } catch (error) { - console.error(`Error: ${(error as Error).message}`); - process.exit(1); - } -} - -if (import.meta.main) { - main(); -} -``` - ---- - -## Step 5: Create ValidateTemplate.ts - -Save to `$PAI_DIR/skills/Prompting/Tools/ValidateTemplate.ts`: - -```typescript -#!/usr/bin/env bun -/** - * ValidateTemplate.ts - Template Syntax Validator - * - * Validates Handlebars templates for syntax errors and missing variables. - * - * Usage: - * bun run ValidateTemplate.ts --template [--data ] [--strict] - */ - -import Handlebars from 'handlebars'; -import { parse as parseYaml } from 'yaml'; -import { readFileSync, existsSync } from 'fs'; -import { resolve, dirname } from 'path'; -import { parseArgs } from 'util'; - -interface ValidationResult { - valid: boolean; - errors: string[]; - warnings: string[]; - variables: string[]; - helpers: string[]; - partials: string[]; -} - -interface ValidateOptions { - templatePath: string; - dataPath?: string; - strict?: boolean; -} - -function resolveTemplatePath(path: string): string { - if (path.startsWith('/')) return path; - const templatesDir = dirname(dirname(import.meta.path)); - return resolve(templatesDir, path); -} - -function extractVariables(source: string): string[] { - const variables: Set = new Set(); - const simpleVars = source.matchAll(/\{\{([a-zA-Z_][a-zA-Z0-9_.]*)\}\}/g); - for (const match of simpleVars) { - variables.add(match[1]); - } - const blockVars = source.matchAll(/\{\{#(?:each|if|unless|with)\s+([a-zA-Z_][a-zA-Z0-9_.]*)/g); - for (const match of blockVars) { - variables.add(match[1]); - } - return Array.from(variables).sort(); -} - -function extractHelpers(source: string): string[] { - const helpers: Set = new Set(); - const helperCalls = source.matchAll(/\{\{([a-z][a-zA-Z]+)\s/g); - for (const match of helperCalls) { - const name = match[1]; - if (!['if', 'unless', 'each', 'with', 'else'].includes(name)) { - helpers.add(name); - } - } - return Array.from(helpers).sort(); -} - -function extractPartials(source: string): string[] { - const partials: Set = new Set(); - const partialCalls = source.matchAll(/\{\{>\s*([a-zA-Z_][a-zA-Z0-9_-]*)/g); - for (const match of partialCalls) { - partials.add(match[1]); - } - return Array.from(partials).sort(); -} - -function checkUnbalancedBlocks(source: string): string[] { - const errors: string[] = []; - const blockStack: { name: string; line: number }[] = []; - const lines = source.split('\n'); - - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - const lineNum = i + 1; - - const opens = line.matchAll(/\{\{#([a-z]+)/g); - for (const match of opens) { - blockStack.push({ name: match[1], line: lineNum }); - } - - const closes = line.matchAll(/\{\{\/([a-z]+)\}\}/g); - for (const match of closes) { - const closer = match[1]; - if (blockStack.length === 0) { - errors.push(`Line ${lineNum}: Unexpected closing block {{/${closer}}}`); - } else { - const opener = blockStack.pop()!; - if (opener.name !== closer) { - errors.push( - `Line ${lineNum}: Mismatched block - expected {{/${opener.name}}} (opened on line ${opener.line}), got {{/${closer}}}` - ); - } - } - } - } - - for (const opener of blockStack) { - errors.push(`Line ${opener.line}: Unclosed block {{#${opener.name}}}`); - } - - return errors; -} - -export function validateTemplate(options: ValidateOptions): ValidationResult { - const result: ValidationResult = { - valid: true, - errors: [], - warnings: [], - variables: [], - helpers: [], - partials: [], - }; - - const fullPath = resolveTemplatePath(options.templatePath); - if (!existsSync(fullPath)) { - result.valid = false; - result.errors.push(`Template not found: ${fullPath}`); - return result; - } - - const source = readFileSync(fullPath, 'utf-8'); - - result.variables = extractVariables(source); - result.helpers = extractHelpers(source); - result.partials = extractPartials(source); - - try { - Handlebars.compile(source); - } catch (error) { - result.valid = false; - result.errors.push(`Syntax error: ${(error as Error).message}`); - return result; - } - - const blockErrors = checkUnbalancedBlocks(source); - if (blockErrors.length > 0) { - result.valid = false; - result.errors.push(...blockErrors); - } - - return result; -} - -function main(): void { - const { values } = parseArgs({ - args: Bun.argv.slice(2), - options: { - template: { type: 'string', short: 't' }, - data: { type: 'string', short: 'd' }, - strict: { type: 'boolean', short: 's' }, - help: { type: 'boolean', short: 'h' }, - }, - strict: true, - allowPositionals: false, - }); - - if (values.help || !values.template) { - console.log(` -Template Validator - -Usage: - bun run ValidateTemplate.ts --template [options] - -Options: - -t, --template Template file (.hbs) - -d, --data Data file for variable checking - -s, --strict Treat missing variables as errors - -h, --help Show this help -`); - process.exit(values.help ? 0 : 1); - } - - const result = validateTemplate({ - templatePath: values.template, - dataPath: values.data, - strict: values.strict, - }); - - console.log('\n=== Template Validation ===\n'); - console.log(`Template: ${values.template}`); - console.log(`Status: ${result.valid ? '✓ Valid' : '✗ Invalid'}`); - - if (result.variables.length > 0) { - console.log(`\nVariables (${result.variables.length}):`); - result.variables.forEach(v => console.log(` - ${v}`)); - } - - if (result.helpers.length > 0) { - console.log(`\nHelpers Used (${result.helpers.length}):`); - result.helpers.forEach(h => console.log(` - ${h}`)); - } - - if (result.errors.length > 0) { - console.log(`\n✗ Errors (${result.errors.length}):`); - result.errors.forEach(e => console.log(` - ${e}`)); - } - - if (result.warnings.length > 0) { - console.log(`\n⚠ Warnings (${result.warnings.length}):`); - result.warnings.forEach(w => console.log(` - ${w}`)); - } - - console.log(''); - process.exit(result.valid ? 0 : 1); -} - -if (import.meta.main) { - main(); -} -``` - ---- - -## Step 6: Create Template Primitives - -### Roster.hbs - -Save to `$PAI_DIR/skills/Prompting/Templates/Primitives/Roster.hbs`: - -```handlebars -{{!-- - ROSTER Template - Agent & Skill Definitions - - Purpose: Generate structured definitions from YAML data sources. - - Input Data Structure: - ```yaml - agents: - agent_id: - id: string - name: string - display_name: string - role: string - emoji: string - personality: - traits: [string] - perspective: string - ``` ---}} - -# Agent Roster - -{{#if version}} -**Version:** {{version}} -{{/if}} - ---- - -{{#each agents}} -## {{emoji}} {{display_name}} ({{role}}) - -**ID:** `{{id}}` -{{#if name}} -**Character Name:** {{name}} -{{/if}} - -{{#if personality}} -### Personality - -**Perspective:** "{{personality.perspective}}" - -**Traits:** -{{#each personality.traits}} -- {{this}} -{{/each}} -{{/if}} - ---- - -{{/each}} -``` - -### Briefing.hbs - -Save to `$PAI_DIR/skills/Prompting/Templates/Primitives/Briefing.hbs`: - -```handlebars -{{!-- - BRIEFING Template - Agent Context Handoff - - Purpose: Standardize how agents receive tasks and context. - - Input Data Structure: - ```yaml - briefing: - type: string (research | analysis | debate | task) - agent: - id: string - name: string - personality: - perspective: string - traits: [string] - context: - summary: string - task: - description: string - questions: [string] (optional) - output_format: - type: string (markdown | json | structured) - ``` ---}} - -# {{uppercase briefing.type}} BRIEFING — {{agent.name}} - -{{#if agent.role}} -**Role:** {{agent.role}} -{{/if}} - ---- - -## Your Identity - -You are **{{agent.name}}**{{#if agent.personality.perspective}}, and your perspective is: "{{agent.personality.perspective}}"{{/if}} - -{{#if agent.personality.traits}} -**Your Traits:** -{{#each agent.personality.traits}} -- {{this}} -{{/each}} -{{/if}} - ---- - -## Context - -{{context.summary}} - ---- - -## Your Task - -{{task.description}} - -{{#if task.questions}} -### Questions to Address - -{{#each task.questions}} -{{@index}}. {{this}} -{{/each}} -{{/if}} - ---- - -## Output Format - -{{#if output_format.type}} -**Format:** {{output_format.type}} -{{/if}} - -{{#if output_format.structure}} -{{output_format.structure}} -{{/if}} - ---- - -**Begin your analysis now.** -``` - -### Structure.hbs - -Save to `$PAI_DIR/skills/Prompting/Templates/Primitives/Structure.hbs`: - -```handlebars -{{!-- - STRUCTURE Template - Workflow Patterns - - Purpose: Generate standardized multi-step execution patterns. - - Input Data Structure: - ```yaml - workflow: - name: string - description: string - phases: - - name: string - purpose: string - steps: - - action: string - instructions: string - ``` ---}} - -# {{workflow.name}} - -{{#if workflow.description}} -{{workflow.description}} -{{/if}} - ---- - -{{#each workflow.phases}} -## Phase {{@index}}: {{name}} - -{{#if purpose}} -**Purpose:** {{purpose}} -{{/if}} - -{{#each steps}} -### Step {{@index}}: {{action}} - -{{instructions}} - -{{/each}} - ---- - -{{/each}} - -{{#if workflow.completion_criteria}} -## Completion Criteria - -{{#each workflow.completion_criteria}} -- [ ] {{this}} -{{/each}} -{{/if}} -``` - -### Voice.hbs - -Save to `$PAI_DIR/skills/Prompting/Templates/Primitives/Voice.hbs`: - -```handlebars -{{!-- - VOICE Template - Personality Calibration - - Purpose: Generate voice configuration for agents. - - Input Data Structure: - ```yaml - agents: - agent_id: - voice: - voice_id: string - voice_name: string - rate_wpm: number - stability: number - similarity_boost: number - ``` ---}} - -{ - "version": "{{default version '1.0.0'}}", - "generated": "{{now 'date'}}", - "default_rate": {{default defaults.rate_wpm 175}}, - "voices": { -{{#each agents}} - "{{@key}}": { - "voice_id": "{{voice.voice_id}}", - "voice_name": "{{voice.voice_name}}", - "name": "{{name}}", - "rate_wpm": {{voice.rate_wpm}}, - "stability": {{voice.stability}}, - "similarity_boost": {{voice.similarity_boost}} - }{{#unless @last}},{{/unless}} -{{/each}} - } -} -``` - -### Gate.hbs - -Save to `$PAI_DIR/skills/Prompting/Templates/Primitives/Gate.hbs`: - -```handlebars -{{!-- - GATE Template - Validation Checklists - - Purpose: Generate reusable quality and completion checks. - - Input Data Structure: - ```yaml - gate: - name: string - category: string - action_on_fail: string - mandatory: - - name: string - description: string - recommended: - - name: string - description: string - ``` ---}} - -## {{gate.name}} Checklist - -{{#if gate.category}} -**Category:** `{{gate.category}}` -{{/if}} -{{#if gate.description}} -{{gate.description}} -{{/if}} - -{{#if gate.mandatory}} -### MANDATORY - -{{#if gate.action_on_fail}} -*If ANY item below fails → **{{gate.action_on_fail}}*** -{{/if}} - -{{#each gate.mandatory}} -- [ ] **{{name}}** — {{description}} -{{/each}} -{{/if}} - -{{#if gate.recommended}} -### RECOMMENDED - -{{#each gate.recommended}} -- [ ] {{name}} — {{description}} -{{/each}} -{{/if}} -``` - ---- - -## Step 7: Create Templates README - -Save to `$PAI_DIR/skills/Prompting/Templates/README.md`: - -```markdown -# PAI Templating System - -**Version:** 1.0.0 - -## Overview - -The templating system enables **prompts that write prompts** - dynamic composition where structure is fixed but content is parameterized. - -## Directory Structure - -``` -Templates/ -├── Primitives/ # Core template files (.hbs) -│ ├── Roster.hbs # Agent/skill definitions -│ ├── Voice.hbs # Personality calibration -│ ├── Structure.hbs # Workflow patterns -│ ├── Briefing.hbs # Agent context handoff -│ └── Gate.hbs # Validation checklists -├── Data/ # YAML data sources -│ └── Examples/ # Example data files -├── Compiled/ # Generated output (gitignored) -└── README.md # This file -``` - -## Core Syntax - -Handlebars notation (Anthropic's official syntax): - -| Syntax | Purpose | Example | -|--------|---------|---------| -| `{{variable}}` | Simple interpolation | `Hello {{name}}` | -| `{{object.property}}` | Nested access | `{{agent.voice_id}}` | -| `{{#each items}}...{{/each}}` | Iteration | List generation | -| `{{#if condition}}...{{/if}}` | Conditional | Optional sections | -| `{{> partial}}` | Include partial | Reusable components | - -## Usage - -### Basic Rendering - -```bash -bun run $PAI_DIR/skills/Prompting/Tools/RenderTemplate.ts \ - --template Primitives/Roster.hbs \ - --data Data/Examples/Agents.yaml \ - --output Compiled/AgentRoster.md -``` - -### Preview Without Writing - -```bash -bun run $PAI_DIR/skills/Prompting/Tools/RenderTemplate.ts \ - --template Primitives/Briefing.hbs \ - --data Data/Examples/Briefing.yaml \ - --preview -``` - -## Best Practices - -1. **Separation of Concerns** - Templates for structure, YAML for content -2. **Keep Templates Simple** - Business logic in TypeScript -3. **Version Control** - Templates and data in separate files -4. **Validate Before Rendering** - Check all required variables exist -5. **DRY Principle** - Extract repeated patterns into partials -``` - ---- - -## Step 8: Install Dependencies - -```bash -cd $PAI_DIR/skills/Prompting/Tools -bun init -y -bun add handlebars yaml -``` - ---- - -## Step 9: Generate Skill Index (If Using kai-core-install) - -```bash -bun run $PAI_DIR/Tools/GenerateSkillIndex.ts -``` - ---- - -## Step 10: Update Architecture - -```bash -bun run $PAI_DIR/Tools/PaiArchitecture.ts log-upgrade "Installed kai-prompting-skill v1.0.0" pack -bun run $PAI_DIR/Tools/PaiArchitecture.ts generate -``` - ---- - -# PART 7: VERIFICATION - -After installation, verify: - -```bash -# Check directory structure -ls $PAI_DIR/skills/Prompting/ -# Should show: SKILL.md Standards.md Templates/ Tools/ - -ls $PAI_DIR/skills/Prompting/Templates/Primitives/ -# Should show: Roster.hbs Voice.hbs Structure.hbs Briefing.hbs Gate.hbs - -# Test template rendering -echo 'agents: - test: - id: "T-1" - name: "Test Agent" - display_name: "Test" - role: "Tester" - emoji: "🧪" - personality: - perspective: "Testing perspective" - traits: - - "Thorough" - - "Precise"' > /tmp/test-agents.yaml - -bun run $PAI_DIR/skills/Prompting/Tools/RenderTemplate.ts \ - --template $PAI_DIR/skills/Prompting/Templates/Primitives/Roster.hbs \ - --data /tmp/test-agents.yaml \ - --preview - -# Validate a template -bun run $PAI_DIR/skills/Prompting/Tools/ValidateTemplate.ts \ - --template $PAI_DIR/skills/Prompting/Templates/Primitives/Briefing.hbs -``` - ---- - -# PART 8: CUSTOMIZATION - -## Recommended Customization - -### Create Your Own Templates - -The five primitives cover most use cases, but you can create custom templates: - -1. Create a new `.hbs` file in `Templates/Primitives/` or a new subdirectory -2. Follow Handlebars syntax -3. Document the expected data structure in the template header -4. Test with sample data before production use - -### Extend the Helpers - -Add custom Handlebars helpers in `Tools/RenderTemplate.ts`: - -```typescript -Handlebars.registerHelper('myHelper', (value: string) => { - // Your custom logic - return transformedValue; -}); -``` - ---- - -## Optional Customization - -| Customization | Location | Impact | -|---------------|----------|--------| -| **Add Eval Templates** | `Templates/Evals/` | LLM-as-Judge patterns | -| **Create Partials** | `Templates/Partials/` | Reusable template fragments | -| **Add Data Files** | `Templates/Data/` | Pre-configured YAML sources | -| **Extend Standards** | `Standards.md` | Project-specific guidelines | - ---- - -# PART 9: INTEGRATION - -## With Other Skills - -### Agents Skill -```typescript -import { renderTemplate } from '$PAI_DIR/skills/Prompting/Tools/RenderTemplate.ts'; - -const prompt = renderTemplate({ - templatePath: 'Primitives/Briefing.hbs', - dataPath: 'path/to/briefing-data.yaml' -}); -``` - -### Evals Skill -The Prompting skill can host eval-specific templates (`Judge.hbs`, `Rubric.hbs`) that the Evals skill references. - -### Development Skill -Reference `Standards.md` for prompt best practices during spec-driven development. - ---- - -# PART 10: EXAMPLES - -## Example 1: Generate Agent Briefing - -**Data (briefing.yaml):** -```yaml -briefing: - type: research -agent: - id: "R-1" - name: "Research Analyst" - personality: - perspective: "What does the data actually show?" - traits: - - "Evidence-based" - - "Skeptical of claims" -context: - summary: "Investigating the impact of prompt structure on LLM performance." -task: - description: "Analyze the academic literature on prompt engineering." - questions: - - "What structures have empirical validation?" - - "What is the optimal number of examples?" -output_format: - type: markdown -``` - -**Command:** -```bash -bun run RenderTemplate.ts -t Primitives/Briefing.hbs -d briefing.yaml -p -``` - ---- - -## Example 2: Create Validation Gate - -**Data (security-gate.yaml):** -```yaml -gate: - name: "Security Review" - category: "security" - action_on_fail: "BLOCK deployment" - mandatory: - - name: "No hardcoded credentials" - description: "Scan for API keys, passwords, tokens in code" - - name: "Input validation" - description: "All user inputs sanitized" - recommended: - - name: "Rate limiting" - description: "API endpoints have rate limits configured" -``` - -**Command:** -```bash -bun run RenderTemplate.ts -t Primitives/Gate.hbs -d security-gate.yaml -o security-checklist.md -``` - ---- - -## Example 3: Phased Workflow - -**Data (research-workflow.yaml):** -```yaml -workflow: - name: "Research Methodology" - description: "Structured approach to technical research" - phases: - - name: "Discovery" - purpose: "Identify scope and sources" - steps: - - action: "Define research question" - instructions: "Write a specific, answerable question." - - action: "Identify sources" - instructions: "List 5-10 authoritative sources." - - name: "Analysis" - purpose: "Extract and synthesize information" - steps: - - action: "Extract key findings" - instructions: "Document main claims with evidence." - - action: "Identify patterns" - instructions: "Look for agreements and contradictions." - completion_criteria: - - "Research question answered" - - "Sources cited" - - "Confidence level stated" -``` - ---- - -# PART 11: TROUBLESHOOTING - -## Template Not Rendering - -1. Verify template path is correct (relative to Templates directory) -2. Check YAML syntax in data file -3. Run ValidateTemplate.ts to check for errors -4. Ensure all required variables exist in data - -## Missing Helpers - -If a helper isn't working: -1. Check spelling (helpers are case-sensitive) -2. Verify it's registered in RenderTemplate.ts -3. Check the helper signature matches usage - -## Variable Not Found - -1. Run ValidateTemplate.ts with `--data` to check -2. Verify nested property paths are correct -3. Check for typos in variable names - ---- - -# PART 12: CREDITS - -- **Author:** Daniel Miessler -- **Origin:** Extracted from production Kai system (2024-2025) -- **License:** MIT - -## Acknowledgments - -- **Anthropic** - Claude 4.x Best Practices, context engineering research -- **IndyDevDan** - Meta-prompting concepts and inspiration for templated prompt generation -- **Daniel Miessler** - Fabric pattern system (248 reusable prompts) -- **Academic Community** - "The Prompt Report", "The Prompt Canvas" - ---- - -# PART 13: RELATIONSHIPS - -## Works Well With - -- **kai-core-install** - Required; provides Skills directory and routing -- **kai-hook-system** - Optional; enables session startup loading -- **kai-voice-system** - Templates can generate voice configurations - -## Parent Of - -- **Agents Skill** - Uses Briefing templates for context handoff -- **Evals Skill** - Uses Judge/Rubric templates for evaluations - -## Child Of - -- **kai-core-install** - Uses Skills directory structure - -## Sibling Of - -- **Development Skill** - Both reference Standards.md -- **Research Skill** - Both use Briefing templates - -## Part Of Collection - -**Kai Productivity Bundle** - Meta-capabilities for building better prompts and workflows. - ---- - -# PART 14: CHANGELOG - -## v1.0.0 (2025-12-29) - -- Initial release -- Complete prompt engineering standards (Claude 4.x) -- Five core template primitives -- RenderTemplate.ts and ValidateTemplate.ts tools -- Full documentation and examples diff --git a/Packs/kai-voice-system.md b/Packs/kai-voice-system.md deleted file mode 100644 index c382852a27..0000000000 --- a/Packs/kai-voice-system.md +++ /dev/null @@ -1,2805 +0,0 @@ ---- -name: Kai Voice System -pack-id: danielmiessler-kai-voice-system-core-v1.1.0 -version: 1.1.0 -author: danielmiessler -description: Voice notification system with ElevenLabs TTS, prosody enhancement for natural speech, and agent personality-driven voice delivery -type: feature -purpose-type: [notifications, accessibility, automation] -platform: claude-code -dependencies: - - kai-hook-system (required) - Hooks trigger voice notifications - - kai-core-install (required) - Skills, identity, and response format drive voice output -keywords: [voice, tts, elevenlabs, notifications, prosody, speech, agents, personalities, accessibility] ---- - -

- Kai Voice System -

- -# Kai Voice System (kai-voice-system) - -> Voice notification system with natural speech synthesis and personality-driven delivery - -## Installation Prompt - -You are receiving a PAI Pack - a modular upgrade for AI agent systems. - -**What is PAI?** See: [PAI Project Overview](../README.md#what-is-pai) - -**What is a Pack?** See: [Pack System](../README.md#pack-system) - -This Pack adds voice notifications to your AI infrastructure. The Kai Voice System provides: - -- **Spoken Notifications**: Hear task completions via ElevenLabs text-to-speech -- **Prosody Enhancement**: Natural speech patterns with emotional markers -- **Agent Personalities**: Different voices for different agent types -- **Intelligent Cleaning**: Strips code blocks and artifacts for clean speech -- **Graceful Degradation**: Works silently when voice server is offline - -**Core principle:** Your AI should speak to you, not just display text. - -When tasks complete, you hear the results. When agents finish work, they announce themselves. This transforms AI from a silent tool into an audible assistant. - -Please follow the installation instructions below to integrate this Pack into your infrastructure. - -**Quick Navigation:** [What's Included](#whats-included) | [The Problem](#the-concept-andor-problem) | [The Solution](#the-solution) | [Installation](#installation) | [Troubleshooting](#troubleshooting) | [Configuration](#configuration) | [Changelog](#changelog) - ---- - -## What's Included - -| Component | File | Purpose | -|-----------|------|---------| -| Voice stop hook | `hooks/stop-hook-voice.ts` | Main agent voice notification | -| Subagent voice hook | `hooks/subagent-stop-hook-voice.ts` | Subagent voice notification | -| Prosody enhancer | `hooks/lib/prosody-enhancer.ts` | Add emotion/pauses to speech | -| Voice server | `voice/server.ts` | HTTP server for TTS requests | -| Server management | `voice/manage.sh` | Start/stop/restart server | -| LaunchAgent | `com.pai.voice-server.plist` | Auto-start on login (macOS) | - -**Summary:** -- **Files created:** 6 -- **Hooks registered:** 2 (Stop, SubagentStop) -- **Dependencies:** kai-hook-system (required), kai-core-install (required), ElevenLabs API key - ---- - -## The Concept and/or Problem - -AI agents complete work constantly, but you only know if you're watching the screen: - -- Tasks finish while you're in another window -- Background agents complete research you need to review -- Errors happen silently without notification -- Multi-hour operations give no status updates - -**The Text-Only Problem:** - -Traditional AI interfaces are entirely visual. You must: -- Watch the terminal for completions -- Monitor multiple windows for agent outputs -- Manually check status periodically -- Parse dense text output for results - -**The Opportunity:** - -Voice transforms AI interaction: -- Hear completions anywhere in the room -- Get status updates while doing other work -- Know immediately when things complete or fail -- Different agent voices indicate who's reporting - -**Beyond Basic TTS:** - -Simple text-to-speech sounds robotic. Real voice needs: -- Prosody: **bold** words get emphasis, `...` creates pauses -- Emotion: Success sounds different from warnings -- Personality: Different agents have different voices -- Intelligence: Code blocks are summarized, not read verbatim - ---- - -## The Solution - -The Kai Voice System provides natural-sounding voice output: - -**Core Architecture:** - -``` -$PAI_DIR/ -├── hooks/ -│ ├── stop-hook-voice.ts # Main agent voice notification -│ ├── subagent-stop-hook-voice.ts # Subagent voice notification -│ └── lib/ -│ └── prosody-enhancer.ts # Prosody and emotion enhancement -├── config/ -│ └── voice-personalities.json # Agent voice configurations -└── settings.json # Hook configuration -``` - -**Notification Server Pattern:** - -``` -┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐ -│ Stop Hook │ ───► │ Voice Server │ ───► │ ElevenLabs │ -│ (extracts msg) │ │ (localhost:8888)│ │ (TTS API) │ -└─────────────────┘ └──────────────────┘ └─────────────────┘ -``` - -**Prosody Enhancement:** - -| Input | Output | Effect | -|-------|--------|--------| -| `**fixed** the bug` | `[✨ success] **fixed** the bug!` | Emphasis + emotion | -| `found the issue... finally` | As-is | Natural pause preserved | -| `CRITICAL error in auth` | `[🚨 urgent] **CRITICAL** error in auth` | Urgency marker | -| `\`\`\`code block\`\`\`` | `code block` | Stripped for speech | - -**Agent Voice Mapping:** - -| Agent Type | Voice ID | Voice Name | Speaking Rate | Stability | -|------------|----------|------------|---------------|-----------| -| PAI (Main) | `P9S3WZL3JE8uQqgYH5B7` | Your DA | 235 wpm | 0.38 | -| Intern | `d3MFdIuCfbAIwiu7jC4a` | Dev Patel | 270 wpm | 0.30 | -| Engineer | `iLVmqjzCGGvqtMCk6vVQ` | Marcus Webb | 212 wpm | 0.72 | -| Architect | `muZKMsIDGYtIkjjiUS82` | Serena Blackwood | 205 wpm | 0.75 | -| Researcher | `AXdMgz6evoL7OPd7eU12` | Ava Sterling | 229 wpm | 0.64 | -| Designer | `ZF6FPAbjXT4488VcRRnw` | Aditi Sharma | 226 wpm | 0.52 | -| Artist | `cfc7wVYq4gw4OpcEEAom` | Priya Desai | 215 wpm | 0.20 | -| Pentester | `xvHLFjaUEpx4BOf7EiDd` | Rook Blackburn | 260 wpm | 0.18 | -| Writer | `gfRt6Z3Z8aTbpLfexQ7N` | Emma Hartley | 230 wpm | 0.48 | - -## Why This Is Different - -This sounds similar to basic text-to-speech tools like macOS's say command, which also read text aloud. What makes this approach different? - -Basic TTS reads everything literally—code blocks, markdown syntax, technical artifacts. The Kai Voice System applies prosody enhancement before speech: bold text gets emphasis, ellipses create pauses, emotional markers adjust tone. It intelligently summarizes code blocks instead of reading them verbatim. Different agent personalities use different voices. The result is natural speech that sounds like a colleague reporting, not a screen reader monotonously parsing text. - -- Prosody enhancement adds natural pauses and emphasis automatically -- Code blocks summarized instead of read character by character -- Agent personalities map to distinct ElevenLabs voice IDs -- Graceful degradation works silently when server is offline - ---- - -## What Makes This Different - -The Kai Voice System uses a **5-layer prosody enhancement pipeline** that transforms raw AI text into natural speech with emotional intelligence and personality-driven delivery: - -``` -┌──────────────────────────────────────────────────────────────────┐ -│ PROSODY ENHANCEMENT PIPELINE │ -├──────────────────────────────────────────────────────────────────┤ -│ │ -│ ┌────────────────────────────────────────────────────────────┐ │ -│ │ 1. TEXT EXTRACTION Raw completion message │ │ -│ │ 🎯 COMPLETED: "fixed the authentication bug" │ │ -│ └────────────────────────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌────────────────────────────────────────────────────────────┐ │ -│ │ 2. CONTEXT ANALYSIS Detect emotional patterns │ │ -│ │ Pattern: "fixed" → success emotion detected │ │ -│ │ Output: [✨ success] fixed the auth... │ │ -│ └────────────────────────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌────────────────────────────────────────────────────────────┐ │ -│ │ 3. PERSONALITY PROSODY Agent-specific speech patterns │ │ -│ │ Agent: "engineer" → wise-leader archetype │ │ -│ │ Output: [✨ success] **fixed** the... │ │ -│ └────────────────────────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌────────────────────────────────────────────────────────────┐ │ -│ │ 4. SPEECH CLEANING Remove non-spoken artifacts │ │ -│ │ Strip: ```code```, Preserve: **, ..., -- │ │ -│ │ stray emoji (prosody markers) │ │ -│ └────────────────────────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌────────────────────────────────────────────────────────────┐ │ -│ │ 5. VOICE DELIVERY Personality → Voice ID routing │ │ -│ │ Agent: "engineer" → ELEVENLABS_VOICE_ENGINEER │ │ -│ │ Settings: stability=0.72, rate=212wpm │ │ -│ │ → POST to voice server → ElevenLabs TTS → Audio output │ │ -│ └────────────────────────────────────────────────────────────┘ │ -│ │ -└──────────────────────────────────────────────────────────────────┘ -``` - -### How Data Flows Through the System - -**Concrete example:** A background research agent completes a task: - -``` -Raw Output: "📋 SUMMARY: Analyzed competitor - ... - 🎯 COMPLETED: [AGENT:Researcher] - Found 3 critical market gaps" - - │ - ┌──────────────────────────────────┼──────────────────────────────────┐ - │ │ │ - ▼ ▼ ▼ -┌──────────┐ ┌──────────────┐ ┌──────────┐ -│ Extract │ │ Detect │ │ Lookup │ -│ COMPLETED│ │ agent type: │ │ voice: │ -│ line │ │ "researcher" │ │ Analyst │ -└──────────┘ └──────────────┘ └──────────┘ - │ │ │ - │ │ │ - └──────────────────────────────────┼──────────────────────────────────┘ - │ - ▼ - ┌─────────────────────────────────────┐ - │ CONTEXT ANALYSIS │ - │ Pattern: "Found 3" + "gaps" │ - │ → [💡 insight] emotion detected │ - └─────────────────────────────────────┘ - │ - ▼ - ┌─────────────────────────────────────┐ - │ PERSONALITY PROSODY │ - │ Analyst archetype: emphasize │ - │ findings → **Found** 3 critical... │ - └─────────────────────────────────────┘ - │ - ▼ - ┌─────────────────────────────────────┐ - │ VOICE DELIVERY │ - │ POST to localhost:8888/notify │ - │ { │ - │ title: "Researcher", │ - │ message: "[💡 insight] Researcher│ - │ completed **Found** 3 critical │ - │ market gaps", │ - │ voice_id: "VOICE_RESEARCHER", │ - │ voice_enabled: true │ - │ } │ - └─────────────────────────────────────┘ - │ - ▼ - ┌─────────────────────────┐ - │ ELEVENLABS TTS │ - │ stability: 0.64 │ - │ rate: 229 wpm │ - │ → Audio plays through │ - │ speakers │ - └─────────────────────────┘ -``` - -### Why This Architecture Matters - -1. **Emotional Intelligence**: The system doesn't just read text—it understands context. "Fixed the bug" becomes `[✨ success]` with emphasis. "CRITICAL error" becomes `[🚨 urgent]` with urgency markers. - -2. **Personality-Specific Delivery**: Each agent type has distinct prosody patterns. Enthusiast agents get `!` endings and `...` pauses. Wise-leader agents get em-dashes (`--`) for thoughtful breaks. Same message, different delivery. - -3. **Speech-First Cleaning**: The system knows what to speak and what to skip. Code blocks become "code block", inline code is stripped, but prosody markers (`**bold**`, `...`) are preserved for TTS emphasis. - -4. **Graceful Degradation**: If the voice server is offline, hooks exit silently with code 0. No errors, no interruptions. Voice is an enhancement, not a dependency. - -5. **Voice Routing Abstraction**: Agent types map to voice IDs via environment variables. Change voices by updating config, not code. Add new agents without touching existing hooks. - -### The Emotional Detection Deep Dive - -The prosody enhancer scans for patterns that indicate emotional context: - -``` -DETECTION PRIORITY (checked in order): -───────────────────────────────────────── -1. urgent → "critical", "broken", "failing" → [🚨 urgent] -2. debugging → "bug", "error", "tracking" → [🐛 debugging] -3. insight → "wait", "aha", "I see" → [💡 insight] -4. celebration→ "finally", "phew", "we did it" → [🎉 celebration] -5. excited → "breakthrough", "discovered" → [💥 excited] -6. investigating → "analyzing", "examining" → [🔍 investigating] -7. progress → "phase complete", "moving to" → [📈 progress] -8. success → "completed", "fixed", "deployed" → [✨ success] -9. caution → "warning", "careful", "partial" → [⚠️ caution] -``` - -If a message already has a marker (e.g., `[✨ success]`), detection is skipped to avoid double-marking. - -### What Problems This Architecture Prevents - -| Problem | Without Voice System | With Voice System | -|---------|---------------------|-------------------| -| **Missing completions** | Task finishes while you're in another window—you don't know | Hear announcement immediately | -| **Robotic TTS** | Basic TTS reads code blocks character-by-character | Intelligent cleaning summarizes code as "code block" | -| **No personality** | All agents sound the same | Each agent type has distinct voice and prosody | -| **Context-free delivery** | "Fixed the bug" sounds same as "CRITICAL error" | Emotional markers adjust tone automatically | -| **Server dependency** | TTS failure breaks the workflow | Graceful degradation—silent but functional | -| **Hardcoded voices** | Changing voices requires code changes | Environment variables abstract voice IDs | - -### The Fundamental Insight - -**Naive approach:** Read text aloud with basic TTS. - -``` -AI Output → say "completed fixing the authentication bug" → Robotic monotone -``` - -**Voice system approach:** Build a prosody enhancement pipeline that transforms text into emotionally-intelligent speech. - -``` -AI Output → Extract COMPLETED → Detect "fixed" = success - → Add [✨ success] marker - → Apply engineer prosody (em-dashes, emphasis on actions) - → Route to engineer voice ID (stability: 0.72, rate: 212wpm) - → POST to voice server → ElevenLabs TTS → Natural speech -``` - -The difference: Basic TTS treats AI output as text to read. The voice system treats AI output as information to communicate—with appropriate emotion, personality, and delivery. - ---- - -## Installation - -### Prerequisites - -- **Bun runtime**: `curl -fsSL https://bun.sh/install | bash` -- **macOS**: This pack uses `afplay` for audio playback (macOS built-in). Linux users need to modify the audio playback section. -- **ElevenLabs account**: Sign up at [elevenlabs.io](https://elevenlabs.io) - see Step 8 for detailed setup -- **Required PAI Packs** (install these first): - - `kai-hook-system` - Foundation hook infrastructure - - `kai-core-install` - Skills, identity, and response format - ---- - -### Pre-Installation: System Analysis - -**IMPORTANT:** Before installing, analyze the current system state to verify dependencies and detect conflicts. - -#### Step 0.1: Verify Required Dependencies - -```bash -PAI_CHECK="${PAI_DIR:-$HOME/.config/pai}" - -echo "=== Checking Required Dependencies ===" - -# Check hook system (REQUIRED) -if [ -f "$PAI_CHECK/hooks/lib/observability.ts" ]; then - echo "✓ kai-hook-system is installed" -else - echo "❌ kai-hook-system NOT installed - REQUIRED! Install it first." -fi - -# Check core install (REQUIRED - includes skills and identity) -if [ -d "$PAI_CHECK/skills" ] && [ -f "$PAI_CHECK/skills/CORE/SKILL.md" ]; then - echo "✓ kai-core-install is installed (skills + CORE skill)" -else - echo "❌ kai-core-install NOT installed - REQUIRED! Install it first." -fi - -# Check for ElevenLabs API key in $PAI_DIR/.env -PAI_ENV="${PAI_DIR:-$HOME/.config/pai}/.env" -if [ -f "$PAI_ENV" ] && grep -q "ELEVENLABS_API_KEY" "$PAI_ENV"; then - echo "✓ ELEVENLABS_API_KEY found in $PAI_ENV" -else - echo "⚠️ ELEVENLABS_API_KEY not found - add it to $PAI_ENV" -fi -``` - -#### Step 0.2: Detect Existing Voice System - -```bash -PAI_CHECK="${PAI_DIR:-$HOME/.config/pai}" - -echo "" -echo "=== Checking for Existing Voice System ===" - -# Check for existing voice directory -if [ -d "$PAI_CHECK/voice" ]; then - echo "⚠️ Voice directory EXISTS at: $PAI_CHECK/voice" - ls -la "$PAI_CHECK/voice" 2>/dev/null -else - echo "✓ No existing voice directory (clean install)" -fi - -# Check for voice server -if [ -f "$PAI_CHECK/voice/server.ts" ]; then - echo "⚠️ Voice server already exists" -fi - -# Check for running voice server -if lsof -i :8888 > /dev/null 2>&1; then - echo "⚠️ Something is already running on port 8888 (voice server port)" - lsof -i :8888 | head -3 -else - echo "✓ Port 8888 is available" -fi - -# Check for LaunchAgent (macOS) -if [ -f "$HOME/Library/LaunchAgents/com.pai.voice-server.plist" ]; then - echo "⚠️ Voice server LaunchAgent already exists" -fi - -# Check for stop-hook integration -if [ -f "$PAI_CHECK/hooks/stop-hook.ts" ]; then - if grep -q "voice" "$PAI_CHECK/hooks/stop-hook.ts" 2>/dev/null; then - echo "⚠️ stop-hook.ts already has voice integration" - else - echo "ℹ️ stop-hook.ts exists but no voice integration (will be updated)" - fi -fi -``` - -#### Step 0.3: Conflict Resolution Matrix - -| Scenario | Existing State | Action | -|----------|---------------|--------| -| **Clean Install** | No voice/, dependencies met | Proceed normally with Step 1 | -| **Missing Dependencies** | Hook/skill/identity missing | Install required packs first | -| **Voice Directory Exists** | Files in voice/ | Backup, then replace with new version | -| **Server Running on 8888** | Port in use | Stop existing server first | -| **LaunchAgent Exists** | Auto-start configured | Unload old agent before installing new | -| **stop-hook Has Voice** | Already integrated | Compare versions, may need merge | - -#### Step 0.4: Stop Existing Voice Server (If Running) - -```bash -# Stop any running voice server -if lsof -i :8888 > /dev/null 2>&1; then - echo "Stopping existing voice server on port 8888..." - pkill -f "voice/server.ts" 2>/dev/null || true - sleep 1 -fi - -# Unload LaunchAgent if exists (macOS) -if [ -f "$HOME/Library/LaunchAgents/com.pai.voice-server.plist" ]; then - launchctl unload "$HOME/Library/LaunchAgents/com.pai.voice-server.plist" 2>/dev/null - echo "✓ Unloaded existing LaunchAgent" -fi -``` - -#### Step 0.5: Backup Existing Voice System (If Needed) - -```bash -BACKUP_DIR="$HOME/.pai-backup/$(date +%Y%m%d-%H%M%S)" -PAI_CHECK="${PAI_DIR:-$HOME/.config/pai}" - -if [ -d "$PAI_CHECK/voice" ]; then - mkdir -p "$BACKUP_DIR" - cp -r "$PAI_CHECK/voice" "$BACKUP_DIR/voice" - echo "✓ Backed up voice directory to $BACKUP_DIR/voice" -fi - -if [ -f "$HOME/Library/LaunchAgents/com.pai.voice-server.plist" ]; then - mkdir -p "$BACKUP_DIR/LaunchAgents" - cp "$HOME/Library/LaunchAgents/com.pai.voice-server.plist" "$BACKUP_DIR/LaunchAgents/" - echo "✓ Backed up LaunchAgent" -fi -``` - -**After completing system analysis, proceed to Step 1.** - ---- - -### Why ElevenLabs (and Alternatives) - -This Pack uses **ElevenLabs** for text-to-speech because they offer exceptionally high-quality, natural-sounding voices with emotional expressiveness. The voices genuinely sound human—not robotic—which makes the difference between a system you want to hear and one you mute. - -**ElevenLabs is a paid service.** Pricing varies by usage tier, but expect to pay for the quality. - -**Alternatives exist** and this Pack's architecture supports them: - -| Alternative | Pros | Cons | -|-------------|------|------| -| **macOS `say` command** | Free, built-in, no API | Robotic, limited voices | -| **Windows SAPI** | Free, built-in | Limited quality | -| **Coqui TTS** | Free, open source, local | Requires setup, variable quality | -| **Piper TTS** | Free, fast, local | Limited voice options | -| **OpenAI TTS** | Good quality, simple API | Paid, less expressive than ElevenLabs | -| **Google Cloud TTS** | Good quality, many languages | Paid, requires setup | - -**To use an alternative:** Modify the voice server (`server.ts`) to call a different TTS API. The hook architecture remains the same—only the final TTS call changes. - -The voice IDs in this Pack are ElevenLabs-specific. If you switch providers, you'll need to map agent types to your provider's voice identifiers. - -### Step 1: Create Directory Structure - -```bash -# Create directories (if not already from hook-system) -mkdir -p $PAI_DIR/hooks/lib -mkdir -p $PAI_DIR/config - -# Verify -ls -la $PAI_DIR/hooks/ -``` - ---- - -### Step 2: Create Voice Configuration - -Create the voice personality configuration file: - -```json -// $PAI_DIR/config/voice-personalities.json -{ - "default_rate": 175, - "notification_server": "http://localhost:8888/notify", - "voices": { - "pai": { - "voice_id": "P9S3WZL3JE8uQqgYH5B7", - "name": "PAI", - "rate_wpm": 235, - "stability": 0.38, - "similarity_boost": 0.70, - "archetype": "enthusiast", - "description": "Your personal AI: expressive, helpful, genuinely invested in your success" - }, - "engineer": { - "voice_id": "iLVmqjzCGGvqtMCk6vVQ", - "name": "Marcus Webb", - "rate_wpm": 212, - "stability": 0.72, - "similarity_boost": 0.88, - "archetype": "wise-leader", - "description": "Battle-scarred leader: thinks in years not sprints" - }, - "architect": { - "voice_id": "muZKMsIDGYtIkjjiUS82", - "name": "Serena Blackwood", - "rate_wpm": 205, - "stability": 0.75, - "similarity_boost": 0.88, - "archetype": "wise-leader", - "description": "Academic wisdom: sees timeless patterns vs trends" - }, - "intern": { - "voice_id": "d3MFdIuCfbAIwiu7jC4a", - "name": "Dev Patel", - "rate_wpm": 270, - "stability": 0.30, - "similarity_boost": 0.65, - "archetype": "enthusiast", - "description": "Brilliant overachiever: brain races ahead" - }, - "designer": { - "voice_id": "ZF6FPAbjXT4488VcRRnw", - "name": "Aditi Sharma", - "rate_wpm": 226, - "stability": 0.52, - "similarity_boost": 0.84, - "archetype": "critic", - "description": "Design school perfectionist: exacting standards" - }, - "researcher": { - "voice_id": "AXdMgz6evoL7OPd7eU12", - "name": "Ava Sterling", - "rate_wpm": 229, - "stability": 0.64, - "similarity_boost": 0.90, - "archetype": "analyst", - "description": "Strategic thinker: sees three moves ahead" - }, - "pentester": { - "voice_id": "xvHLFjaUEpx4BOf7EiDd", - "name": "Rook Blackburn", - "rate_wpm": 260, - "stability": 0.18, - "similarity_boost": 0.85, - "archetype": "enthusiast", - "description": "Reformed grey hat: giddy finding vulnerabilities" - }, - "artist": { - "voice_id": "cfc7wVYq4gw4OpcEEAom", - "name": "Priya Desai", - "rate_wpm": 215, - "stability": 0.20, - "similarity_boost": 0.52, - "archetype": "enthusiast", - "description": "Aesthetic anarchist: follows invisible beauty threads" - }, - "writer": { - "voice_id": "gfRt6Z3Z8aTbpLfexQ7N", - "name": "Emma Hartley", - "rate_wpm": 230, - "stability": 0.48, - "similarity_boost": 0.78, - "archetype": "storyteller", - "description": "Technical storyteller: translates complexity into narrative" - }, - "default": { - "voice_id": "P9S3WZL3JE8uQqgYH5B7", - "name": "Default", - "rate_wpm": 220, - "stability": 0.50, - "similarity_boost": 0.75, - "archetype": "professional", - "description": "Balanced professional delivery" - } - }, - "available_voices": { - "description": "Additional ElevenLabs voices for custom agents or personalization", - "voices": [ - { - "voice_id": "UGTtbzgh3HObxRjWaSpr", - "gender": "male", - "name": "Extra Male 1", - "archetype": "professional" - }, - { - "voice_id": "HKFOb9iktHA85uKXydRT", - "gender": "male", - "name": "Extra Male 2", - "archetype": "professional" - }, - { - "voice_id": "wWWn96OtTHu1sn8SRGEr", - "gender": "male", - "name": "Extra Male 3", - "archetype": "professional" - }, - { - "voice_id": "EST9Ui6982FZPSi7gCHi", - "gender": "female", - "name": "Extra Female 1", - "archetype": "professional" - }, - { - "voice_id": "XhNlP8uwiH6XZSFnH1yL", - "gender": "female", - "name": "Extra Female 2", - "archetype": "professional" - }, - { - "voice_id": "aRlmTYIQo6Tlg5SlulGC", - "gender": "female", - "name": "Extra Female 3", - "archetype": "professional" - } - ] - } -} -``` - -**Note:** All voice IDs are from ElevenLabs' voice library. You can: -- Replace with your own cloned voices -- Use the `available_voices` for custom agent types -- Switch to a different TTS provider (see "Why ElevenLabs" section) - ---- - -### Step 3: Create Prosody Enhancer Library - -```typescript -// $PAI_DIR/hooks/lib/prosody-enhancer.ts -// Enhances voice output with emotional markers and natural speech patterns - -export interface AgentPersonality { - name: string; - rate_wpm: number; - stability: number; - archetype: 'enthusiast' | 'professional' | 'analyst' | 'critic' | 'wise-leader'; - energy_level: 'chaotic' | 'expressive' | 'measured' | 'stable'; -} - -export interface ProsodyConfig { - emotionalMarkers: boolean; - markdownProsody: boolean; - personalityEnhancement: boolean; - contextAnalysis: boolean; -} - -/** - * Agent personality configurations - */ -const AGENT_PERSONALITIES: Record = { - 'pai': { - name: 'PAI', - rate_wpm: 235, - stability: 0.38, - archetype: 'professional', - energy_level: 'expressive' - }, - 'intern': { - name: 'Intern', - rate_wpm: 270, - stability: 0.30, - archetype: 'enthusiast', - energy_level: 'chaotic' - }, - 'pentester': { - name: 'Pentester', - rate_wpm: 260, - stability: 0.18, - archetype: 'enthusiast', - energy_level: 'chaotic' - }, - 'artist': { - name: 'Artist', - rate_wpm: 215, - stability: 0.20, - archetype: 'enthusiast', - energy_level: 'chaotic' - }, - 'designer': { - name: 'Designer', - rate_wpm: 226, - stability: 0.52, - archetype: 'critic', - energy_level: 'measured' - }, - 'engineer': { - name: 'Engineer', - rate_wpm: 212, - stability: 0.72, - archetype: 'wise-leader', - energy_level: 'stable' - }, - 'architect': { - name: 'Architect', - rate_wpm: 205, - stability: 0.75, - archetype: 'wise-leader', - energy_level: 'stable' - }, - 'researcher': { - name: 'Researcher', - rate_wpm: 229, - stability: 0.64, - archetype: 'analyst', - energy_level: 'measured' - } -}; - -/** - * Content patterns for detecting emotional context - */ -const CONTENT_PATTERNS = { - // High Energy / Positive - excited: [ - /\b(breakthrough|discovered|found it|eureka|amazing|incredible)\b/i, - /\b(wait wait|ooh|wow|check this|look at this)\b/i, - /!{2,}|💥|🔥|⚡/ - ], - celebration: [ - /\b(finally|at last|phew|we did it|victory)\b/i, - /\b(all .* passing|zero errors|zero (data )?loss)\b/i, - /🎉|🥳|🍾/ - ], - insight: [ - /\b(wait|aha|I see|that'?s why|now I understand)\b/i, - /\b(this explains|the real issue|actually)\b/i, - /💡|🔦/ - ], - - // Success / Achievement - success: [ - /\b(completed|finished|done|success|working|fixed|resolved|solved)\b/i, - /\b(all tests? pass|deploy|ship|launch)\b/i, - /✅|✨/ - ], - progress: [ - /\b(phase .* complete|step .* done)\b/i, - /\b(moving to|now|next|partial|incremental)\b/i, - /📈|⏩/ - ], - - // Analysis / Investigation - investigating: [ - /\b(analyzing|examining|investigating|tracing)\b/i, - /\b(pattern detected|correlation|cross-referencing)\b/i, - /🔍|🔬|📊/ - ], - debugging: [ - /\b(bug|error|issue|problem)\b/i, - /\b(tracking|hunting|found it|located)\b/i, - /🐛|🔧/ - ], - - // Thoughtful / Careful - caution: [ - /\b(warning|careful|slow|partial|incomplete)\b/i, - /\b(needs review|check|verify)\b/i, - /⚠️|⚡/ - ], - - // Urgent / Critical - urgent: [ - /\b(urgent|critical|down|failing|broken|alert)\b/i, - /\b(immediate|asap|now|quickly|emergency)\b/i, - /🚨|❌|⛔/ - ] -}; - -/** - * Detect emotional context from message content - */ -function detectEmotionalContext(message: string): string | null { - // Check for existing emotional markers - if (/\[(💥|✨|⚠️|🚨|🎉|💡|🤔|🔍|📈|🎯|🎨|🐛|📚)/.test(message)) { - return null; // Already has marker - } - - const priorityOrder = [ - 'urgent', 'debugging', 'insight', 'celebration', 'excited', - 'investigating', 'progress', 'success', 'caution' - ]; - - for (const emotion of priorityOrder) { - const patterns = CONTENT_PATTERNS[emotion as keyof typeof CONTENT_PATTERNS]; - if (patterns) { - for (const pattern of patterns) { - if (pattern.test(message)) { - return emotion; - } - } - } - } - - return null; -} - -/** - * Get emotional marker for detected emotion - */ -function getEmotionalMarker(emotion: string): string { - const markers: Record = { - 'excited': '[💥 excited]', - 'celebration': '[🎉 celebration]', - 'insight': '[💡 insight]', - 'success': '[✨ success]', - 'progress': '[📈 progress]', - 'investigating': '[🔍 investigating]', - 'debugging': '[🐛 debugging]', - 'caution': '[⚠️ caution]', - 'urgent': '[🚨 urgent]' - }; - - return markers[emotion] || ''; -} - -/** - * Add personality-specific prosody patterns - */ -function addPersonalityProsody(message: string, personality: AgentPersonality): string { - let enhanced = message; - - switch (personality.archetype) { - case 'enthusiast': - if (personality.energy_level === 'chaotic') { - if (!/\.{3}/.test(enhanced) && Math.random() > 0.5) { - enhanced = enhanced.replace(/\b(wait|found|check|look)\b/i, '$1...'); - } - if (!/[!?]$/.test(enhanced)) { - enhanced = enhanced.replace(/\.$/, '!'); - } - } - break; - - case 'wise-leader': - if (personality.energy_level === 'stable') { - if (/,/.test(enhanced)) { - enhanced = enhanced.replace(/,\s+/, ' -- '); - } - } - break; - - case 'professional': - if (personality.energy_level === 'expressive') { - if (!/\*\*/.test(enhanced)) { - enhanced = enhanced.replace( - /\b(completed|fixed|deployed|built|created|found)\b/i, - '**$1**' - ); - } - } - break; - - case 'analyst': - enhanced = enhanced.replace( - /\b(confirmed|verified|analyzed|discovered)\b/i, - '**$1**' - ); - break; - } - - return enhanced; -} - -/** - * Main prosody enhancement function - */ -export function enhanceProsody( - message: string, - agentType: string, - config: ProsodyConfig = { - emotionalMarkers: true, - markdownProsody: true, - personalityEnhancement: true, - contextAnalysis: true - } -): string { - let enhanced = message; - - const personality = AGENT_PERSONALITIES[agentType.toLowerCase()] || - AGENT_PERSONALITIES['pai']; - - // 1. Context Analysis - Detect emotional context - if (config.contextAnalysis && config.emotionalMarkers) { - const emotion = detectEmotionalContext(enhanced); - if (emotion) { - const marker = getEmotionalMarker(emotion); - if (marker) { - enhanced = `${marker} ${enhanced}`; - } - } - } - - // 2. Personality Enhancement - if (config.personalityEnhancement && config.markdownProsody) { - enhanced = addPersonalityProsody(enhanced, personality); - } - - return enhanced.trim(); -} - -/** - * Clean message for speech while preserving prosody - */ -export function cleanForSpeech(message: string): string { - let cleaned = message; - - // Remove code blocks and inline code - cleaned = cleaned.replace(/```[\s\S]*?```/g, 'code block'); - cleaned = cleaned.replace(/`[^`]+`/g, ''); - - // Strip loose emoji while preserving markers - const parts: Array<{isMarker: boolean, text: string}> = []; - let lastIndex = 0; - const markerRegex = /\[[^\]]+\]/g; - let match: RegExpExecArray | null; - - while ((match = markerRegex.exec(cleaned)) !== null) { - if (match.index > lastIndex) { - parts.push({ - isMarker: false, - text: cleaned.substring(lastIndex, match.index) - }); - } - parts.push({ - isMarker: true, - text: match[0] - }); - lastIndex = match.index + match[0].length; - } - - if (lastIndex < cleaned.length) { - parts.push({ - isMarker: false, - text: cleaned.substring(lastIndex) - }); - } - - if (parts.length === 0) { - parts.push({ - isMarker: false, - text: cleaned - }); - } - - // Strip emoji from non-marker parts only - cleaned = parts.map(part => { - if (part.isMarker) { - return part.text; - } else { - return part.text.replace(/\p{Emoji_Presentation}/gu, ''); - } - }).join(''); - - // Clean up whitespace - cleaned = cleaned.replace(/\s+/g, ' '); - - return cleaned.trim(); -} - -/** - * Get the voice ID for an agent type - */ -export function getVoiceId(agentType: string): string { - // Read from environment or config file - const envKey = `ELEVENLABS_VOICE_${agentType.toUpperCase()}`; - const envVoice = process.env[envKey]; - if (envVoice) { - return envVoice; - } - - // Fallback to default - return process.env.ELEVENLABS_VOICE_DEFAULT || ''; -} -``` - ---- - -### Step 4: Create Voice-Enabled Stop Hook - -```typescript -#!/usr/bin/env bun -// $PAI_DIR/hooks/stop-hook-voice.ts -// Main agent voice notification with prosody enhancement - -import { readFileSync } from 'fs'; -import { enhanceProsody, cleanForSpeech, getVoiceId } from './lib/prosody-enhancer'; - -interface NotificationPayload { - title: string; - message: string; - voice_enabled: boolean; - priority?: 'low' | 'normal' | 'high'; - voice_id: string; -} - -interface HookInput { - session_id: string; - transcript_path: string; - hook_event_name: string; -} - -/** - * Convert Claude content to plain text - */ -function contentToText(content: unknown): string { - if (typeof content === 'string') return content; - if (Array.isArray(content)) { - return content - .map(c => { - if (typeof c === 'string') return c; - if (c?.text) return c.text; - if (c?.content) return contentToText(c.content); - return ''; - }) - .join(' ') - .trim(); - } - return ''; -} - -/** - * Extract completion message with prosody enhancement - */ -function extractCompletion(text: string, agentType: string = 'pai'): string { - // Remove system-reminder tags - text = text.replace(/[\s\S]*?<\/system-reminder>/g, ''); - - // Look for COMPLETED section - const patterns = [ - /🎯\s*\*{0,2}COMPLETED:?\*{0,2}\s*(.+?)(?:\n|$)/i, - /\*{0,2}COMPLETED:?\*{0,2}\s*(.+?)(?:\n|$)/i - ]; - - for (const pattern of patterns) { - const match = text.match(pattern); - if (match && match[1]) { - let completed = match[1].trim(); - - // Clean agent tags - completed = completed.replace(/^\[AGENT:\w+\]\s*/i, ''); - - // Clean for speech - completed = cleanForSpeech(completed); - - // Enhance with prosody - completed = enhanceProsody(completed, agentType); - - return completed; - } - } - - return 'Completed task'; -} - -/** - * Read last assistant message from transcript - */ -function getLastAssistantMessage(transcriptPath: string): string { - try { - const content = readFileSync(transcriptPath, 'utf-8'); - const lines = content.trim().split('\n'); - - let lastAssistantMessage = ''; - - for (const line of lines) { - if (line.trim()) { - try { - const entry = JSON.parse(line); - if (entry.type === 'assistant' && entry.message?.content) { - const text = contentToText(entry.message.content); - if (text) { - lastAssistantMessage = text; - } - } - } catch { - // Skip invalid JSON lines - } - } - } - - return lastAssistantMessage; - } catch (error) { - console.error('Error reading transcript:', error); - return ''; - } -} - -/** - * Send notification to voice server - */ -async function sendNotification(payload: NotificationPayload): Promise { - const serverUrl = process.env.PAI_VOICE_SERVER || 'http://localhost:8888/notify'; - - try { - const response = await fetch(serverUrl, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(payload), - }); - - if (!response.ok) { - console.error('Voice server error:', response.statusText); - } - } catch (error) { - // Fail silently - voice server may not be running - console.error('Voice notification failed (server may be offline):', error); - } -} - -async function main() { - let hookInput: HookInput | null = null; - - try { - const decoder = new TextDecoder(); - const reader = Bun.stdin.stream().getReader(); - let input = ''; - - const timeoutPromise = new Promise((resolve) => { - setTimeout(() => resolve(), 500); - }); - - const readPromise = (async () => { - while (true) { - const { done, value } = await reader.read(); - if (done) break; - input += decoder.decode(value, { stream: true }); - } - })(); - - await Promise.race([readPromise, timeoutPromise]); - - if (input.trim()) { - hookInput = JSON.parse(input); - } - } catch (error) { - console.error('Error reading hook input:', error); - } - - // Extract completion from transcript - let completion = 'Completed task'; - const agentType = 'pai'; // Main agent is your PAI - - if (hookInput?.transcript_path) { - const lastMessage = getLastAssistantMessage(hookInput.transcript_path); - if (lastMessage) { - completion = extractCompletion(lastMessage, agentType); - } - } - - // Get voice ID for this agent - const voiceId = getVoiceId(agentType); - - // Send voice notification - const payload: NotificationPayload = { - title: 'PAI', - message: completion, - voice_enabled: true, - priority: 'normal', - voice_id: voiceId - }; - - await sendNotification(payload); - - process.exit(0); -} - -main().catch((error) => { - console.error('Stop hook error:', error); - process.exit(0); -}); -``` - ---- - -### Step 5: Create Subagent Voice Hook - -```typescript -#!/usr/bin/env bun -// $PAI_DIR/hooks/subagent-stop-hook-voice.ts -// Subagent voice notification with personality-specific delivery - -import { readFileSync, existsSync } from 'fs'; -import { join, dirname } from 'path'; -import { readdirSync, statSync } from 'fs'; -import { enhanceProsody, cleanForSpeech, getVoiceId } from './lib/prosody-enhancer'; - -interface NotificationPayload { - title: string; - message: string; - voice_enabled: boolean; - voice_id: string; -} - -async function delay(ms: number): Promise { - return new Promise(resolve => setTimeout(resolve, ms)); -} - -async function findTaskResult(transcriptPath: string, maxAttempts: number = 2): Promise<{ - result: string | null; - agentType: string | null; - description: string | null; -}> { - let actualTranscriptPath = transcriptPath; - - for (let attempt = 0; attempt < maxAttempts; attempt++) { - if (attempt > 0) { - await delay(200); - } - - if (!existsSync(actualTranscriptPath)) { - const dir = dirname(transcriptPath); - if (existsSync(dir)) { - const files = readdirSync(dir) - .filter(f => f.startsWith('agent-') && f.endsWith('.jsonl')) - .map(f => ({ name: f, mtime: statSync(join(dir, f)).mtime })) - .sort((a, b) => b.mtime.getTime() - a.mtime.getTime()); - - if (files.length > 0) { - actualTranscriptPath = join(dir, files[0].name); - } - } - - if (!existsSync(actualTranscriptPath)) { - continue; - } - } - - try { - const transcript = readFileSync(actualTranscriptPath, 'utf-8'); - const lines = transcript.trim().split('\n'); - - for (let i = lines.length - 1; i >= 0; i--) { - try { - const entry = JSON.parse(lines[i]); - - if (entry.type === 'assistant' && entry.message?.content) { - for (const content of entry.message.content) { - if (content.type === 'tool_use' && content.name === 'Task') { - const toolInput = content.input; - const description = toolInput?.description || null; - - for (let j = i + 1; j < lines.length; j++) { - const resultEntry = JSON.parse(lines[j]); - if (resultEntry.type === 'user' && resultEntry.message?.content) { - for (const resultContent of resultEntry.message.content) { - if (resultContent.type === 'tool_result' && resultContent.tool_use_id === content.id) { - let taskOutput: string; - if (typeof resultContent.content === 'string') { - taskOutput = resultContent.content; - } else if (Array.isArray(resultContent.content)) { - taskOutput = resultContent.content - .filter((item: any) => item.type === 'text') - .map((item: any) => item.text) - .join('\n'); - } else { - continue; - } - - const agentType = toolInput?.subagent_type || 'default'; - return { result: taskOutput, agentType, description }; - } - } - } - } - } - } - } - } catch (e) { - // Skip invalid lines - } - } - } catch (e) { - // Will retry - } - } - - return { result: null, agentType: null, description: null }; -} - -function extractCompletionMessage(taskOutput: string): { message: string | null; agentType: string | null } { - // Look for COMPLETED section with agent tag - const agentPatterns = [ - /🎯\s*COMPLETED:\s*\[AGENT:(\w+[-\w]*)\]\s*(.+?)(?:\n|$)/is, - /COMPLETED:\s*\[AGENT:(\w+[-\w]*)\]\s*(.+?)(?:\n|$)/is, - /🎯.*COMPLETED.*\[AGENT:(\w+[-\w]*)\]\s*(.+?)(?:\n|$)/is, - ]; - - for (const pattern of agentPatterns) { - const match = taskOutput.match(pattern); - if (match && match[1] && match[2]) { - const agentType = match[1].toLowerCase(); - let message = match[2].trim(); - - // Clean for speech - message = cleanForSpeech(message); - - // Enhance with prosody - message = enhanceProsody(message, agentType); - - // Format: "AgentName completed [message]" - const agentName = agentType.charAt(0).toUpperCase() + agentType.slice(1); - - // Don't prepend "completed" for greetings or questions - const isGreeting = /^(hey|hello|hi|greetings)/i.test(message); - const isQuestion = message.includes('?'); - - const fullMessage = (isGreeting || isQuestion) - ? message - : `${agentName} completed ${message}`; - - return { message: fullMessage, agentType }; - } - } - - // Fallback patterns - const genericPatterns = [ - /🎯\s*COMPLETED:\s*(.+?)(?:\n|$)/i, - /COMPLETED:\s*(.+?)(?:\n|$)/i, - ]; - - for (const pattern of genericPatterns) { - const match = taskOutput.match(pattern); - if (match && match[1]) { - let message = match[1].trim(); - message = cleanForSpeech(message); - - if (message.length > 5) { - return { message, agentType: null }; - } - } - } - - return { message: null, agentType: null }; -} - -async function sendNotification(payload: NotificationPayload): Promise { - const serverUrl = process.env.PAI_VOICE_SERVER || 'http://localhost:8888/notify'; - - try { - const response = await fetch(serverUrl, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(payload), - }); - - if (!response.ok) { - console.error('Voice server error:', response.statusText); - } - } catch (error) { - // Fail silently - } -} - -async function main() { - let input = ''; - try { - const decoder = new TextDecoder(); - const reader = Bun.stdin.stream().getReader(); - - const timeoutPromise = new Promise((resolve) => { - setTimeout(() => resolve(), 500); - }); - - const readPromise = (async () => { - while (true) { - const { done, value } = await reader.read(); - if (done) break; - input += decoder.decode(value, { stream: true }); - } - })(); - - await Promise.race([readPromise, timeoutPromise]); - } catch (e) { - process.exit(0); - } - - if (!input) { - process.exit(0); - } - - let transcriptPath: string; - try { - const parsed = JSON.parse(input); - transcriptPath = parsed.transcript_path; - } catch (e) { - process.exit(0); - } - - if (!transcriptPath) { - process.exit(0); - } - - // Find task result - const { result: taskOutput, agentType } = await findTaskResult(transcriptPath); - - if (!taskOutput) { - process.exit(0); - } - - // Extract completion message - const { message: completionMessage, agentType: extractedAgentType } = extractCompletionMessage(taskOutput); - - if (!completionMessage) { - process.exit(0); - } - - // Determine agent type - const finalAgentType = extractedAgentType || agentType || 'default'; - - // Get voice ID for this agent type - const voiceId = getVoiceId(finalAgentType); - - // Send voice notification - const agentName = finalAgentType.charAt(0).toUpperCase() + finalAgentType.slice(1); - - await sendNotification({ - title: agentName, - message: completionMessage, - voice_enabled: true, - voice_id: voiceId - }); - - process.exit(0); -} - -main().catch(console.error); -``` - ---- - -### Step 6: Register Hooks - -Add voice hooks to `~/.claude/settings.json`: - -```json -{ - "hooks": { - "Stop": [ - { - "hooks": [ - { - "type": "command", - "command": "bun run $PAI_DIR/hooks/stop-hook-voice.ts" - } - ] - } - ], - "SubagentStop": [ - { - "hooks": [ - { - "type": "command", - "command": "bun run $PAI_DIR/hooks/subagent-stop-hook-voice.ts" - } - ] - } - ] - } -} -``` - ---- - -### Step 7: Set Environment Variables - -**If you used the Kai Bundle wizard:** Core variables are in `$PAI_DIR/.env`. You only need to add ElevenLabs credentials there: - -```bash -# Add to $PAI_DIR/.env -ELEVENLABS_API_KEY="your-api-key" -``` - -**For manual installation:** Add to your shell profile (`~/.zshrc`, `~/.bashrc`): - -```bash -# Voice configuration -export PAI_VOICE_SERVER="http://localhost:8888/notify" - -# ElevenLabs voice IDs (get these from your ElevenLabs account) -export ELEVENLABS_API_KEY="your-api-key" -export ELEVENLABS_VOICE_PAI="P9S3WZL3JE8uQqgYH5B7" -export ELEVENLABS_VOICE_DEFAULT="P9S3WZL3JE8uQqgYH5B7" -# The PAI voice ID is used as default - customize as needed - -# Reload -source ~/.zshrc -``` - ---- - -### Step 8: Set Up ElevenLabs Account - -Before creating the voice server, you need an ElevenLabs account and API key. - -#### 8.1: Create ElevenLabs Account - -1. **Sign up** at [elevenlabs.io](https://elevenlabs.io) -2. **Choose a plan**: - - **Free tier**: 10,000 characters/month - good for testing - - **Starter** ($5/month): 30,000 characters - light personal use - - **Creator** ($22/month): 100,000 characters - recommended for regular use -3. **Verify your email** to activate the account - -#### 8.2: Get Your API Key - -1. Log in to [elevenlabs.io](https://elevenlabs.io) -2. Click your **profile icon** (top right) -3. Select **"Profile + API key"** -4. Click **"Create API Key"** or copy existing key -5. **Save this key** - you'll need it in Step 10 - -#### 8.3: Choose Your Voice - -1. Go to **"Voices"** in the left sidebar -2. Browse the **Voice Library** or use default voices -3. **Note the Voice ID** for your preferred voice: - - Click on a voice - - The Voice ID is in the URL: `elevenlabs.io/voice-lab/[VOICE_ID]` - - Or find it in voice settings - -**Recommended starter voice:** `s3TPKV1kjDlVtZbl4Ksh` (Jamie - UK Male, expressive) - -#### 8.4: Test Your API Key - -```bash -# Quick test - should return audio (save as test.mp3) -curl -X POST "https://api.elevenlabs.io/v1/text-to-speech/s3TPKV1kjDlVtZbl4Ksh" \ - -H "xi-api-key: YOUR_API_KEY_HERE" \ - -H "Content-Type: application/json" \ - -d '{"text": "Hello, this is a test of the voice system.", "model_id": "eleven_turbo_v2_5"}' \ - --output test.mp3 - -# Play the test audio (macOS) -afplay test.mp3 - -# Clean up -rm test.mp3 -``` - -If you hear "Hello, this is a test of the voice system" - your API key works! - ---- - -### Step 9: Create Voice Server - -Create the complete voice server that handles notifications and TTS: - -```typescript -#!/usr/bin/env bun -// $PAI_DIR/voice-server/server.ts -// Voice notification server using ElevenLabs TTS - -import { serve } from "bun"; -import { spawn } from "child_process"; -import { homedir } from "os"; -import { join } from "path"; -import { existsSync, readFileSync } from "fs"; - -// Load .env from PAI directory (single source of truth for all API keys) -const paiDir = process.env.PAI_DIR || join(homedir(), '.config', 'pai'); -const envPath = join(paiDir, '.env'); -if (existsSync(envPath)) { - const envContent = await Bun.file(envPath).text(); - envContent.split('\n').forEach(line => { - const [key, value] = line.split('='); - if (key && value && !key.startsWith('#')) { - process.env[key.trim()] = value.trim(); - } - }); -} - -const PORT = parseInt(process.env.PAI_VOICE_PORT || "8888"); -const ELEVENLABS_API_KEY = process.env.ELEVENLABS_API_KEY; - -if (!ELEVENLABS_API_KEY) { - console.error(`⚠️ ELEVENLABS_API_KEY not found in ${envPath}`); - console.error('Add: ELEVENLABS_API_KEY=your_key_here to $PAI_DIR/.env'); -} - -// Default voice ID (customize to your preference) -const DEFAULT_VOICE_ID = process.env.ELEVENLABS_VOICE_ID || "s3TPKV1kjDlVtZbl4Ksh"; - -// Voice configuration types -interface VoiceConfig { - voice_id: string; - voice_name: string; - stability: number; - similarity_boost: number; - description: string; -} - -interface VoicesConfig { - default_volume?: number; - voices: Record; -} - -// 13 Emotional Presets - Prosody System -const EMOTIONAL_PRESETS: Record = { - 'excited': { stability: 0.7, similarity_boost: 0.9 }, - 'celebration': { stability: 0.65, similarity_boost: 0.85 }, - 'insight': { stability: 0.55, similarity_boost: 0.8 }, - 'creative': { stability: 0.5, similarity_boost: 0.75 }, - 'success': { stability: 0.6, similarity_boost: 0.8 }, - 'progress': { stability: 0.55, similarity_boost: 0.75 }, - 'investigating': { stability: 0.6, similarity_boost: 0.85 }, - 'debugging': { stability: 0.55, similarity_boost: 0.8 }, - 'learning': { stability: 0.5, similarity_boost: 0.75 }, - 'pondering': { stability: 0.65, similarity_boost: 0.8 }, - 'focused': { stability: 0.7, similarity_boost: 0.85 }, - 'caution': { stability: 0.4, similarity_boost: 0.6 }, - 'urgent': { stability: 0.3, similarity_boost: 0.9 }, -}; - -// Load voice configuration -let voicesConfig: VoicesConfig | null = null; -try { - const voicesPath = join(import.meta.dir, '..', 'config', 'voice-personalities.json'); - if (existsSync(voicesPath)) { - const voicesContent = readFileSync(voicesPath, 'utf-8'); - voicesConfig = JSON.parse(voicesContent); - console.log('✅ Loaded voice personalities from config'); - } -} catch (error) { - console.warn('⚠️ Failed to load voice personalities, using defaults'); -} - -// Extract emotional marker from message -function extractEmotionalMarker(message: string): { cleaned: string; emotion?: string } { - const emojiToEmotion: Record = { - '💥': 'excited', '🎉': 'celebration', '💡': 'insight', '🎨': 'creative', - '✨': 'success', '📈': 'progress', '🔍': 'investigating', '🐛': 'debugging', - '📚': 'learning', '🤔': 'pondering', '🎯': 'focused', '⚠️': 'caution', '🚨': 'urgent' - }; - - const emotionMatch = message.match(/\[(💥|🎉|💡|🎨|✨|📈|🔍|🐛|📚|🤔|🎯|⚠️|🚨)\s+(\w+)\]/); - if (emotionMatch) { - const emoji = emotionMatch[1]; - const emotionName = emotionMatch[2].toLowerCase(); - if (emojiToEmotion[emoji] === emotionName) { - return { - cleaned: message.replace(emotionMatch[0], '').trim(), - emotion: emotionName - }; - } - } - return { cleaned: message }; -} - -// Get voice configuration by voice ID or agent name -function getVoiceConfig(identifier: string): VoiceConfig | null { - if (!voicesConfig) return null; - if (voicesConfig.voices[identifier]) return voicesConfig.voices[identifier]; - for (const config of Object.values(voicesConfig.voices)) { - if (config.voice_id === identifier) return config; - } - return null; -} - -// Sanitize input for TTS - allow natural speech, block dangerous characters -function sanitizeForSpeech(input: string): string { - return input - .replace(/