Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Kairos

καιρός — the right moment to act

Kairos is a Rust TUI tool that automates the job application workflow for the Australian market.

Search across platforms, analyze JDs with AI, tailor your resume per role, and track every application — all from your terminal.

Features

  • Multi-Platform Search — Seek.com.au, Indeed AU, LinkedIn
  • LLM-Powered Analysis — Claude analyzes JDs, extracts requirements, scores match against your profile
  • Smart Resume Tailoring — Auto-rewrite resume sections per JD with anti-hallucination guards
  • Beautiful TUI — ratatui-based, keyboard-driven, async-friendly
  • Privacy First — All data local in SQLite, API keys never leave your machine
  • Australia Focus — Built for AU market, expandable to US/UK/CA/NZ

Semi-Automatic by Design

Kairos is intentionally semi-automatic. It handles the grunt work but keeps you in control:

  • You review every resume modification before saving
  • You click the final submit button in your browser
  • You decide which jobs to pursue based on match scores

Quick Start

cargo install --path crates/kairos-tui
kairos init
kairos resume import resume.tex
kairos   # launch TUI

Overview

The Problem

Applying for jobs is repetitive and time-consuming:

  1. Search across multiple platforms (Seek, Indeed, LinkedIn)
  2. Read each JD and decide if it’s a good fit
  3. Customize your resume for each application
  4. Submit the application
  5. Track everything in a spreadsheet

Kairos automates steps 1-4 and replaces the spreadsheet with a proper tracking system.

How It Works

flowchart TD
    A[Configure Profile + Import Resume] --> B[Search across platforms]
    B --> C[Analyze JDs with Claude AI]
    C --> D[Tailor resume per JD]
    D --> E[Review diff + Approve]
    E --> F[Browser opens → You submit]
    F --> G[Track in Dashboard]

Target Platforms

PlatformRegionMethod
Seek.com.auAustralia, NZHTTP scraping (SSR)
IndeedAU, US, UK, CAHeadless browser (JS required)
LinkedInGlobalPublic listing scraping

Tech Stack

Every dependency was researched and validated. Here’s what we use and why.

Core

CrateVersionPurposeWhy This One
tokiolatestAsync runtimeIndustry standard, full features
ratatui0.30TUI framework19.1K stars, only mature option, Component Architecture
crosstermlatestTerminal backendratatui default, cross-platform
rusqlite0.39DatabaseBundled SQLite, zero external deps, ideal for CLI
refinerylatestDB migrationsClean integration with rusqlite

TUI Components

CratePurpose
rat-widgetForm widgets (text input, date, number, checkbox, tabs)
tui-textareaMulti-line text editing
tui-scrollviewScrollable content views
throbber-widgets-tuiLoading spinners for async operations
similarText diff for resume comparison

Networking & Scraping

CrateVersionPurposeWhy
reqwest0.13HTTP client412M downloads, rustls-tls
scraper0.26HTML parsingCSS selectors, built on html5ever
chromiumoxide0.9Headless browserCDP protocol, async, needed for Indeed

LLM Integration

CrateVersionPurposeWhy
rig-core0.33LLM framework6.6K stars, native Anthropic support, structured output, streaming

Why rig-core over raw HTTP?

  • Multi-provider support (Claude, GPT, Gemini) — switch models without code changes
  • Structured output with Serde — deserialize LLM responses directly into Rust structs
  • Tool calling support — for potential future agent workflows
  • Streaming responses — show LLM output in real-time in the TUI

Alternatives considered

AlternativeVerdict
Raw reqwest to Anthropic APIWorks but reinvents serialization, retry, streaming
anthropic-ai-sdk (37K downloads)Low adoption, uncertain maintenance
async-anthropic (32K downloads)Stale since May 2025
genai 0.6 betaStill in beta, less mature than rig

Error Handling

CrateLayerPurpose
thiserrorLibrary cratesCustom error types per crate
color-eyreBinary crateBeautiful error reports, terminal-aware

Key Decisions

rusqlite vs sqlx

  • bundled feature = SQLite compiled into binary, zero runtime deps
  • No compile-time query checking needed for this scale
  • Faster builds (no proc macros)
  • Note: rusqlite and sqlx cannot coexist in the same binary (libsqlite3-sys conflict)

ratatui vs cursive

  • Ecosystem: 2,800+ dependent crates vs cursive’s shrinking community
  • Control: immediate-mode rendering gives pixel-perfect layouts
  • Async: first-class tokio integration

chromiumoxide vs fantoccini

  • Direct CDP access (can intercept network, modify headers)
  • Async-native (tokio)
  • Better anti-detection capabilities than WebDriver-based tools

Getting Started

WIP — Kairos is under active development. This page describes the planned setup flow.

Prerequisites

  • Rust toolchain (1.80+)
  • A Claude API key from Anthropic Console
  • Chrome/Chromium (for Indeed scraping via headless browser)

Installation

git clone https://github.com/Misoto22/kairos.git
cd kairos
cargo install --path crates/kairos-tui

First-Time Setup

kairos init

This interactive wizard will:

  1. Create config at ~/.config/kairos/config.toml
  2. Ask for your Claude API key (stored locally, never transmitted elsewhere)
  3. Set up your profile (name, location, target roles)
  4. Initialize the SQLite database at ~/.local/share/kairos/kairos.db

Import Your Resume

kairos resume import ~/resume.tex

Supports LaTeX (.tex) and Markdown (.md) formats. The parser splits your resume into sections and marks which ones are tailorable by the LLM.

Tailorable sections (LLM can modify):

  • Summary / Professional Profile
  • Technologies / Skills
  • Work experience bullet points

Protected sections (never modified):

  • Education, dates, company names
  • Contact information, references

Launch the TUI

kairos

Use number keys 1-5 to navigate between views.

Configuration

Edit ~/.config/kairos/config.toml directly or use the Config view in the TUI.

[profile]
name = "Your Name"
location = "Perth WA"
country = "AU"
target_roles = ["Software Engineer", "Backend Developer"]

[api]
claude_api_key = ""  # or set KAIROS_CLAUDE_API_KEY env var
claude_model = "claude-sonnet-4-20250514"

[search.defaults]
platforms = ["seek", "indeed"]
location = "Perth"
job_type = "fulltime"
salary_min = 70000
posted_within_days = 14

Tip: API keys can also be set via environment variable KAIROS_CLAUDE_API_KEY, which takes precedence over the config file.

Workflow

The complete Kairos workflow in one diagram:

flowchart TD
    subgraph Setup["Setup (one-time)"]
        Init[kairos init] --> Import[Import resume]
    end

    subgraph Search["Search"]
        Criteria[Configure criteria] --> Scrape[Scrape platforms]
        Scrape --> Store[Store in SQLite]
    end

    subgraph Analyze["Analyze"]
        Store --> LLM1[Claude analyzes JD]
        LLM1 --> Score[Match score 0.0-1.0]
        Score --> Filter[Filter by score]
    end

    subgraph Tailor["Tailor"]
        Filter --> LLM2[Claude rewrites resume]
        LLM2 --> Diff[Show diff]
        Diff --> Approve{Approve?}
        Approve -->|Yes| Save[Save tailored version]
        Approve -->|No| Discard[Discard]
    end

    subgraph Apply["Apply"]
        Save --> Open[Open browser]
        Open --> Submit[You submit manually]
        Submit --> Track[Track status]
    end

    Setup --> Search

Step-by-Step

StepWhat Kairos DoesWhat You Do
SearchScrapes Seek/Indeed/LinkedInSet criteria
AnalyzeCalls Claude to parse JD + score matchReview scores
TailorRewrites resume sections per JDReview diff, approve/reject
ApplyOpens application URL in browserFill form, click submit
TrackRecords status in SQLiteUpdate status as it progresses

Search Jobs

The Search view lets you configure criteria and scrape jobs from multiple platforms simultaneously.

Usage

In the TUI, press 2 to open Search view.

Search Criteria

FieldDescriptionExample
KeywordsRole titles or skillssoftware engineer, rust developer
LocationCity or regionPerth, Melbourne, Sydney
PlatformsTarget job boardsseek, indeed, linkedin
Salary MinMinimum salary (AUD)70000
Job TypeEmployment typefulltime, parttime, contract
Posted WithinDays since posted7, 14, 30
Exclude CompaniesCompanies to skip["Acme Corp"]
Exclude KeywordsKeywords to filter out["senior", "lead"]

How It Works

sequenceDiagram
    participant U as User
    participant TUI as Kairos TUI
    participant S as Seek Adapter
    participant I as Indeed Adapter
    participant DB as SQLite

    U->>TUI: Configure search criteria
    TUI->>S: search(criteria)
    TUI->>I: search(criteria)
    S-->>TUI: Vec of Jobs
    I-->>TUI: Vec of Jobs
    TUI->>DB: save_jobs(jobs)
    TUI->>U: Display job list (deduplicated)

Platform-Specific Notes

Seek.com.au

  • Server-side rendered pages with embedded JSON
  • Rate limit: 2-5 second delay between requests
  • ~20 results per page, max ~550 per query
  • Most reliable scraping target

Indeed AU

  • Requires headless browser (Cloudflare protection)
  • Randomized delays (3-8 seconds) with jitter
  • User-Agent rotation per session
  • May occasionally require CAPTCHA solving (manual fallback)

LinkedIn

  • Public job listings (no login required)
  • Moderate anti-bot measures
  • Falls back to “open in browser” if blocked

Deduplication

Jobs found on multiple platforms are deduplicated by company name + job title similarity. The first instance is kept, with cross-references to other platform URLs.

Analyze JD

After searching, use the Analyze step to have Claude AI parse each job description and compute a match score against your profile.

Usage

In the TUI, select a job from the list and press a to analyze.

What Gets Extracted

FieldDescription
Required SkillsMust-have technical skills
Preferred SkillsNice-to-have skills
Years ExperienceMinimum years required
Key ResponsibilitiesMain duties of the role
ATS KeywordsKeywords to include in your resume
Match Score0.0 - 1.0 match against your profile
Match ReasoningWhy this score was given

Match Scoring

flowchart LR
    JD[Job Description] --> LLM[Claude AI]
    Profile[Your Profile] --> LLM
    LLM --> Analysis[JdAnalysis]
    Analysis --> Score[Match Score]
    Analysis --> Keywords[ATS Keywords]
    Analysis --> Skills[Skill Gaps]

The match score considers:

  • Skill overlap — how many required/preferred skills you have
  • Experience level — your years vs. what they ask for
  • Location compatibility — remote, on-site, visa requirements
  • Role alignment — how well responsibilities match your background

Cost

Each analysis calls Claude API (~500-1500 tokens per JD):

  • ~$0.003-0.01 per job analyzed
  • Batch of 50 jobs ~ $0.15-0.50

The TUI shows estimated cost before batch analysis.

Tailor Resume

The Tailor step uses Claude AI to rewrite your resume sections based on the specific JD, with strict guards against hallucination.

Usage

In the TUI, select an analyzed job and press t to tailor.

How It Works

flowchart TD
    Base[Base Resume] --> Split[Split into Sections]
    JD[JD Analysis] --> Prompt[Tailoring Prompt]
    Split --> |Tailorable sections only| Prompt
    Prompt --> LLM[Claude AI]
    LLM --> Modified[Modified Sections]
    Modified --> Diff[Side-by-Side Diff]
    Diff --> |User approves| Save[Save Tailored Version]
    Diff --> |User rejects| Discard[Discard]
    Split --> |Protected sections| Merge[Merge]
    Save --> Merge
    Merge --> Final[Complete Tailored Resume]

Anti-Hallucination Guards

The LLM prompt enforces strict rules:

  1. Only rephrase, reorder, or emphasize existing content
  2. Never invent skills, experiences, or achievements
  3. Never modify: Education, dates, company names, contact info
  4. Only modify: Summary, Technologies/Skills, experience bullet points

The system works by:

  1. Sending only tailorable: true sections to Claude
  2. Including the JD analysis (keywords, required skills) as context
  3. Asking Claude to optimize for ATS keyword matching while preserving truthfulness
  4. Setting temperature to 0.3 for controlled creativity

The Diff View

After tailoring, the TUI shows a side-by-side diff:

+-- Original ----------------+-- Tailored -------------------+
|                             |                               |
| Experienced software        | Experienced software          |
| developer with expertise    | developer with deep expertise |
| in Rust and Python.         | in Rust, async systems, and   |
|                             | cloud-native Python services. |
|                             |                               |
| - Built CLI tools in Rust   | - Built high-performance CLI  |
|                             |   tools in Rust with async    |
|                             |   I/O and error handling      |
+-----------------------------+-------------------------------+
| [y] Approve  [n] Reject  [e] Edit manually  [r] Retry     |
+------------------------------------------------------------+

Resume Versions

  • Base resume is never modified
  • Tailored versions only store the changed sections
  • Full resume is reconstructed by merging base + tailored sections
  • Export to LaTeX or PDF: select job and press x to export

Apply

The Apply step opens the job application page in your browser. Kairos prepares everything — you make the final call.

Usage

In the TUI, select a tailored job and press Enter to apply.

Workflow

flowchart LR
    Ready[Resume Tailored] --> Open[Open Browser]
    Open --> Page[Application Page]
    Page --> User[You Submit Manually]
    User --> Confirm[Mark as Submitted in TUI]
  1. Kairos checks that a tailored resume exists and is approved
  2. Opens the job URL in your default browser
  3. You fill in the application form and upload the tailored resume
  4. Back in the TUI, confirm submission and status updates to “Submitted”

Why Semi-Automatic?

  • Respect for employers: No mass bot applications
  • Quality control: You review every application
  • Platform TOS: Automated submission violates most platforms’ terms
  • Your judgment: Some fields need human context (cover letter, salary, visa questions)

Application Tracking

After applying, track status in the Dashboard:

StatusMeaning
PreparedResume tailored, ready to submit
SubmittedYou confirmed submission
AcknowledgedCompany acknowledged receipt
InterviewInterview scheduled
RejectedApplication rejected
OfferReceived an offer

Follow-Up Reminders

  • Default follow-up: 2 weeks after submission
  • Kairos highlights overdue follow-ups in the Dashboard

Architecture Overview

Kairos follows Clean Architecture with a Cargo workspace separating concerns into independent crates.

High-Level Architecture

graph TB
    subgraph Binary Layer
        TUI["kairos-tui\nratatui + crossterm"]
    end

    subgraph Application Layer
        Core["kairos-core\nEntities + Traits + Services"]
    end

    subgraph Infrastructure Layer
        Platform["kairos-platform\nSeek / Indeed / LinkedIn"]
        LLM["kairos-llm\nrig-core + Claude"]
        DB["kairos-db\nrusqlite + SQLite"]
    end

    TUI --> Core
    Platform --> Core
    LLM --> Core
    DB --> Core

Dependency Flow

kairos-tui (binary)
  +-- kairos-core (domain: entities, traits, errors)
  +-- kairos-platform (implements JobSearchService)
  |     +-- kairos-core
  +-- kairos-llm (implements JdAnalysisService, ResumeTailorService)
  |     +-- kairos-core
  +-- kairos-db (implements *Repository traits)
        +-- kairos-core

Rule: Infrastructure crates depend on kairos-core for trait definitions. kairos-core depends on nothing — it’s the innermost layer.

Crate Responsibilities

CrateResponsibilityKey Trait
kairos-coreDomain entities, service traits, error typesAll trait definitions
kairos-tuiTerminal UI, event loop, user interaction— (binary)
kairos-platformJob scraping from Seek/Indeed/LinkedInJobSearchService
kairos-llmJD analysis + resume tailoring via ClaudeJdAnalysisService, ResumeTailorService
kairos-dbSQLite persistenceJobRepository, ResumeRepository, ApplicationRepository

Async Architecture

flowchart LR
    subgraph Main Thread
        EventLoop["Event Loop\ntokio::select!"]
        Render[Render TUI]
    end

    subgraph Background Tasks
        Scrape["Scrape Jobs\ntokio::spawn"]
        Analyze["Analyze JD\ntokio::spawn"]
        Tailor["Tailor Resume\ntokio::spawn"]
    end

    Input[Keyboard Input] --> EventLoop
    Channel[mpsc Channel] --> EventLoop
    EventLoop --> Render
    EventLoop --> Scrape
    EventLoop --> Analyze
    EventLoop --> Tailor
    Scrape --> Channel
    Analyze --> Channel
    Tailor --> Channel

All I/O-heavy operations (scraping, LLM calls) run as tokio::spawn tasks. Results flow back through an mpsc channel so the TUI never blocks.

Project Structure

Workspace Layout

kairos/
+-- Cargo.toml                      # Workspace root
+-- config.example.toml
+-- docs/                           # mdbook documentation
|
+-- crates/
    +-- kairos-tui/                 # Binary: TUI application
    |   +-- Cargo.toml
    |   +-- src/
    |       +-- main.rs             # Entry point, terminal setup
    |       +-- app.rs              # App state, router, DI wiring
    |       +-- event.rs            # Event loop (input + async results)
    |       +-- action.rs           # Action enum
    |       +-- theme.rs            # Color theme definitions
    |       +-- components/
    |           +-- mod.rs
    |           +-- dashboard.rs    # Home: application status overview
    |           +-- search.rs       # Search criteria form
    |           +-- job_list.rs     # Sortable, filterable job table
    |           +-- job_detail.rs   # JD text + analysis results
    |           +-- resume_diff.rs  # Side-by-side resume diff
    |           +-- resume_import.rs
    |           +-- config_editor.rs
    |           +-- status_bar.rs   # Keyboard shortcuts hint
    |           +-- popup.rs        # Confirmation dialogs
    |
    +-- kairos-core/                # Domain layer (no external deps)
    |   +-- Cargo.toml
    |   +-- src/
    |       +-- lib.rs
    |       +-- error.rs            # DomainError (thiserror)
    |       +-- entity/
    |       |   +-- mod.rs
    |       |   +-- job.rs          # Job, JdAnalysis, Platform
    |       |   +-- resume.rs       # Resume, ResumeSection, TailoredResume
    |       |   +-- application.rs  # Application, ApplicationStatus
    |       |   +-- user_profile.rs # UserProfile, Country
    |       |   +-- search_criteria.rs
    |       +-- repository/
    |       |   +-- mod.rs
    |       |   +-- job_repository.rs
    |       |   +-- resume_repository.rs
    |       |   +-- application_repository.rs
    |       +-- service/
    |           +-- mod.rs
    |           +-- job_search_service.rs
    |           +-- jd_analysis_service.rs
    |           +-- resume_tailor_service.rs
    |           +-- application_service.rs
    |
    +-- kairos-platform/            # Platform adapters
    |   +-- Cargo.toml
    |   +-- src/
    |       +-- lib.rs
    |       +-- error.rs
    |       +-- rate_limiter.rs
    |       +-- http_client.rs
    |       +-- seek/
    |       |   +-- mod.rs
    |       |   +-- client.rs       # impl JobSearchService
    |       |   +-- parser.rs
    |       |   +-- types.rs
    |       +-- indeed/
    |       |   +-- mod.rs, client.rs, parser.rs, types.rs
    |       +-- linkedin/
    |           +-- mod.rs, client.rs, parser.rs, types.rs
    |
    +-- kairos-llm/                 # LLM integration
    |   +-- Cargo.toml
    |   +-- src/
    |       +-- lib.rs
    |       +-- error.rs
    |       +-- analyzer.rs         # impl JdAnalysisService
    |       +-- tailor.rs           # impl ResumeTailorService
    |       +-- prompt/
    |           +-- mod.rs
    |           +-- jd_analysis.rs
    |           +-- resume_tailoring.rs
    |
    +-- kairos-db/                  # Persistence layer
        +-- Cargo.toml
        +-- migrations/
        |   +-- V1__initial.sql
        +-- src/
            +-- lib.rs
            +-- error.rs
            +-- repository/
                +-- mod.rs
                +-- sqlite_job_repo.rs
                +-- sqlite_resume_repo.rs
                +-- sqlite_application_repo.rs

Crate Dependency Graph

graph TD
    TUI[kairos-tui] --> Core[kairos-core]
    TUI --> Platform[kairos-platform]
    TUI --> LLM[kairos-llm]
    TUI --> DB[kairos-db]
    Platform --> Core
    LLM --> Core
    DB --> Core

Design Principles

  1. kairos-core has zero infrastructure dependencies — only thiserror, chrono, uuid, serde, async-trait
  2. One concept per file — each file owns one struct/trait
  3. Traits in core, implementations in infrastructure — ports-and-adapters pattern
  4. All I/O is async — via tokio
  5. Error types per crate — each crate has its own error.rs with thiserror

Data Model

Entity Relationship

erDiagram
    Job ||--o| JdAnalysis : analyzed
    Job ||--o{ Application : applied
    Resume ||--o{ TailoredResume : tailored
    Resume ||--|{ ResumeSection : contains
    TailoredResume ||--|{ TailoredSection : contains
    TailoredResume }o--|| Job : for
    Application }o--o| TailoredResume : uses

    Job {
        uuid id PK
        string platform
        string platform_job_id
        string title
        string company
        string location
        int salary_min
        int salary_max
        string url
        text description_raw
        string status
        datetime scraped_at
    }

    JdAnalysis {
        uuid job_id FK
        json required_skills
        json preferred_skills
        int years_experience
        json keywords
        float match_score
        text match_reasoning
    }

    Resume {
        uuid id PK
        bool is_base
        string format
    }

    ResumeSection {
        uuid id PK
        uuid resume_id FK
        string section_type
        text content
        bool tailorable
    }

    TailoredResume {
        uuid id PK
        uuid base_resume_id FK
        uuid job_id FK
        text diff_summary
        bool approved
    }

    Application {
        uuid id PK
        uuid job_id FK
        uuid tailored_resume_id FK
        string status
        datetime applied_at
    }

Key Entities

Job

The central entity. Tracks a job posting from discovery through application.

#![allow(unused)]
fn main() {
pub enum Platform { Seek, LinkedIn, Indeed }

pub enum JobStatus {
    New,        // Just scraped
    Analyzed,   // JD analyzed by LLM
    Matched,    // User marked as interesting
    Skipped,    // User passed
    Applied,    // Submitted
    Rejected,   // Got rejection
    Interview,  // Interview scheduled
    Offer,      // Received offer
}
}

JdAnalysis

LLM-generated analysis attached 1:1 to a Job. Key fields:

  • required_skills / preferred_skills — extracted from JD
  • keywords — ATS keywords to include in resume
  • match_score — 0.0 to 1.0, computed against UserProfile
  • match_reasoning — human-readable explanation

Resume & TailoredResume

Base resume imported once and split into sections. Each section marked tailorable: true/false.

Tailored resumes only store modified sections — unchanged sections inherit from base at render time.

#![allow(unused)]
fn main() {
pub enum SectionType {
    Summary,          // tailorable
    Education,        // protected
    WorkExperience,   // tailorable (bullets only)
    Internship,       // tailorable (bullets only)
    Projects,         // tailorable
    Technologies,     // tailorable
    Extracurricular,  // protected
    References,       // protected
}
}

Application

Tracks the lifecycle from preparation to outcome.

#![allow(unused)]
fn main() {
pub enum ApplicationStatus {
    Prepared,      // Resume tailored, ready
    Submitted,     // User confirmed
    Acknowledged,  // Company acknowledged
    Interview,     // Scheduled
    Rejected,
    Offer,
}
}

SQLite Schema

All JSON arrays (skills, keywords) stored as TEXT with serde_json. UUIDs as TEXT. Dates in ISO 8601.

See crates/kairos-db/migrations/V1__initial.sql for the full schema.

Platform Adapters

Each job platform is implemented as an adapter behind the JobSearchService trait.

Trait Definition

#![allow(unused)]
fn main() {
#[async_trait]
pub trait JobSearchService: Send + Sync {
    fn platform(&self) -> Platform;
    async fn search(&self, criteria: &SearchCriteria) -> Result<Vec<Job>, DomainError>;
    async fn fetch_details(&self, job: &Job) -> Result<Job, DomainError>;
}
}

Adapter Architecture

graph LR
    subgraph kairos-core
        Trait[JobSearchService trait]
    end

    subgraph kairos-platform
        Seek[SeekClient]
        Indeed[IndeedClient]
        LinkedIn[LinkedInClient]
        RL[RateLimiter]
        HC[PlatformHttpClient]
    end

    Seek --> Trait
    Indeed --> Trait
    LinkedIn --> Trait
    Seek --> HC
    Indeed --> HC
    LinkedIn --> HC
    HC --> RL

Shared Infrastructure

RateLimiter

Enforces randomized delays between requests:

  • Seek: 2-5s
  • Indeed: 3-8s (more aggressive anti-bot)
  • LinkedIn: 2-6s

PlatformHttpClient

Wraps reqwest::Client with:

  • Rate limiting
  • User-Agent rotation (pool of realistic browser UAs)
  • Retry with exponential backoff (3 attempts)
  • Configurable proxy support

Seek.com.au

Method: reqwest + scraper (HTTP + HTML parsing)

flowchart LR
    Search[Search URL] --> HTTP[reqwest GET]
    HTTP --> HTML[HTML Response]
    HTML --> Parse["scraper: extract JSON"]
    Parse --> Struct[SeekJobListing]
    Struct --> Map[Map to Job entity]
  • Server-side rendered with job data embedded as JSON in HTML
  • Pagination: 20 results per page
  • Key selector: <script type="application/json"> tag

Indeed AU

Method: chromiumoxide (headless Chrome via CDP)

flowchart LR
    Search[Search URL] --> Chrome[chromiumoxide]
    Chrome --> Wait[Wait for render]
    Wait --> DOM[Extract from DOM]
    DOM --> Parse[Parse listings]
    Parse --> Map[Map to Job entity]
  • Requires JavaScript rendering (Cloudflare protection)
  • Anti-detection: disable automation flags, randomized viewport, human-like scrolling
  • Slower (~5-10s per page)

LinkedIn

Method: reqwest + scraper (public job listings)

  • Public job search pages, no login required
  • Fallback: if blocked (403/429), suggests user browse manually
  • Jobs can be added by pasting URL

Adding a New Platform

  1. Create module under kairos-platform/src/
  2. Implement JobSearchService trait
  3. Add variant to Platform enum in kairos-core
  4. Register adapter in kairos-tui/src/app.rs

No existing code needs to change.

LLM Integration

Kairos uses rig-core to communicate with Claude AI for two core functions: JD analysis and resume tailoring.

Architecture

graph TB
    subgraph "kairos-core (traits)"
        T1[JdAnalysisService]
        T2[ResumeTailorService]
    end

    subgraph "kairos-llm (implementations)"
        A[ClaudeAnalyzer]
        T[ClaudeTailor]
        P1[JD Analysis Prompts]
        P2[Resume Tailoring Prompts]
        RC[rig-core Client]
    end

    A --> T1
    T --> T2
    A --> P1
    T --> P2
    A --> RC
    T --> RC
    RC --> API[Claude API]

JD Analysis

Prompt Strategy

Temperature: 0.0 (deterministic extraction)

The prompt instructs Claude to:

  1. Extract structured data from the JD text
  2. Identify required vs preferred skills
  3. Extract ATS keywords
  4. Compare against the user’s profile
  5. Compute a match score (0.0 - 1.0) with reasoning

Output is parsed as JSON matching JdAnalysis struct via rig-core’s structured output.

Resume Tailoring

Anti-Hallucination Rules (enforced in system prompt)

  1. Only rephrase, reorder, or emphasize existing content
  2. Never invent skills, experiences, achievements, or metrics
  3. Never change company names, dates, job titles, education details
  4. Only modify sections marked as tailorable
  5. Preserve the original format (LaTeX or Markdown)

Temperature: 0.3 (controlled creativity)

Validation

After LLM response, a validation step checks:

  • No new company names or job titles appeared
  • No new degree or institution names
  • Dates haven’t changed
  • Section types match what was sent

If validation fails, retry with stricter prompt or flag to user.

Output Flow

flowchart TD
    Input["Base Resume Sections\n+ JD Analysis"] --> LLM[Claude via rig-core]
    LLM --> Raw[Raw LLM Response]
    Raw --> Parse[Parse modified sections]
    Parse --> Validate[Validate: no fabricated content]
    Validate --> |Pass| Diff[Generate diff]
    Validate --> |Fail| Retry[Retry with stricter prompt]
    Diff --> User[Show to user for approval]

Cost

OperationTokens (approx)Cost at Sonnet
JD Analysis500-1,500~$0.003-0.01
Resume Tailoring1,000-3,000~$0.01-0.03

Model Configuration

Default: claude-sonnet-4-20250514

[api]
claude_model = "claude-sonnet-4-20250514"

rig-core’s multi-provider support allows switching to OpenAI or Gemini in the future.

TUI Design

Kairos uses ratatui 0.30 with the Component Architecture pattern.

Layout

+-------------------------------------------------------------+
|  Kairos                                    Perth, AU         |
+--------+----------------------------------------------------+
|        |                                                    |
| 1 Home |                                                    |
| 2 Search              Active View                          |
| 3 Jobs               (content area)                        |
| 4 Resume                                                   |
| 5 Status                                                   |
|        |                                                    |
+--------+----------------------------------------------------+
| q Quit | 1-5 Navigate | / Search | ? Help | Loading...     |
+-------------------------------------------------------------+

Views

1. Dashboard (Home)

Pipeline overview + recent activity + overdue follow-ups.

Form-based search criteria configuration with platform selection.

3. Job List

Sortable, filterable table with columns: Score, Title, Company, Salary, Status.

4. Resume

Resume management: import, view base, view tailored versions, side-by-side diff.

5. Status

Application tracking: all applications with status, applied date, notes.

Component Architecture

graph TD
    App[App State + Router]
    App --> Dashboard
    App --> Search
    App --> JobList[Job List]
    App --> JobDetail[Job Detail]
    App --> ResumeDiff[Resume Diff]
    App --> StatusView[Status View]
    App --> StatusBar[Status Bar]
    App --> Popup

Each component implements:

#![allow(unused)]
fn main() {
pub trait Component {
    fn handle_event(&mut self, event: &Event) -> Option<Action>;
    fn update(&mut self, action: &Action);
    fn render(&self, frame: &mut Frame, area: Rect);
}
}

Event Loop

flowchart TD
    Start[Start] --> Init[Initialize Terminal]
    Init --> Loop{Main Loop}
    Loop --> Select["tokio::select!"]
    Select --> |Keyboard| HandleInput[Handle Input]
    Select --> |Channel msg| HandleAsync[Handle Async Result]
    Select --> |Tick| Render[Render Frame]
    HandleInput --> Dispatch[Dispatch Action]
    HandleAsync --> Dispatch
    Dispatch --> Update[Update Component State]
    Update --> Render
    Render --> Loop
    Loop --> |quit| Cleanup[Restore Terminal]

Keyboard Navigation

KeyAction
1-5Switch views
j/k or arrowsNavigate lists
EnterSelect / confirm
/Search / filter
aAnalyze selected job
tTailor resume for job
SpaceApply (open browser)
sSort
fFilter
qQuit / back
?Help overlay

Theme

Catppuccin Mocha inspired palette:

ElementColor
Primary (headers, selected)Blue #4a9eff
Success (applied, offer)Green #51cf66
Warning (follow-up)Yellow #ffd43b
Error (rejected)Red #ff6b6b
Accent (match score)Purple #cc5de8
BackgroundDark #1e1e2e
TextLight gray #cdd6f4

Implementation Roadmap

Phase Overview

gantt
    title Kairos Implementation Phases
    dateFormat YYYY-MM-DD
    axisFormat %b %d

    section Phase 1 Skeleton
    Workspace + Entities + Traits       :p1a, 2026-03-27, 2d
    Config + SQLite + Migrations        :p1b, after p1a, 1d
    TUI shell with navigation           :p1c, after p1a, 2d

    section Phase 2 Seek Search
    SeekClient + Parser                 :p2a, after p1c, 3d
    SQLite Job Repository               :p2b, after p1b, 1d
    Search View + Job List View         :p2c, after p2a, 2d

    section Phase 3 LLM Analysis
    rig-core Claude Client              :p3a, after p2c, 2d
    JD Analysis + Match Scoring         :p3b, after p3a, 2d
    Job Detail View                     :p3c, after p3b, 1d

    section Phase 4 Resume Tailoring
    LaTeX Parser                        :p4a, after p3c, 2d
    Tailoring Prompts                   :p4b, after p4a, 3d
    Resume Diff View                    :p4c, after p4b, 2d

    section Phase 5 Apply and Track
    Apply Workflow + Browser Open       :p5a, after p4c, 1d
    Dashboard + Status View             :p5b, after p5a, 2d
    Indeed + LinkedIn Adapters          :p5c, after p5a, 3d

    section Phase 6 Polish
    Error Handling + Edge Cases         :p6a, after p5c, 2d
    Theme + UX Polish                   :p6b, after p6a, 2d

Phase Details

Phase 1: Skeleton

Goal: Compilable workspace with navigable TUI shell.

  • Init cargo workspace with all 5 crates
  • Define all entities in kairos-core
  • Define all traits (Repository + Service)
  • Define error types per crate
  • Config loading (TOML + XDG paths)
  • SQLite setup + migration V1
  • TUI event loop + view navigation
  • Empty component shells + status bar

Verify: cargo build succeeds, TUI launches, can switch views.

Goal: Search Seek.com.au and display results in TUI.

  • SeekClient with HTTP + HTML parsing
  • Rate limiter with randomized delays
  • SqliteJobRepository
  • Search view (form with criteria)
  • Job list view (sortable table)
  • Async task dispatch (search -> spawn -> channel -> update)

Verify: Enter criteria -> see Seek jobs in table.

Phase 3: LLM Analysis

Goal: Analyze JDs with Claude and show match scores.

  • rig-core client setup
  • JD analysis prompt templates
  • Analyzer implementation
  • Job detail view with analysis
  • Match score column in job list
  • Batch analyze action

Verify: Select job -> press a -> see analysis with score.

Phase 4: Resume Tailoring

Goal: Import resume, tailor per JD, show diff.

  • LaTeX section parser
  • Resume import flow
  • SqliteResumeRepository
  • Tailoring prompts with anti-hallucination
  • Tailor implementation with validation
  • Side-by-side diff view
  • Approve/reject flow

Verify: Import resume -> select job -> t -> see diff -> approve.

Phase 5: Apply + Track

Goal: Complete semi-automatic workflow.

  • Apply action (open browser)
  • SqliteApplicationRepository
  • Dashboard view (pipeline overview)
  • Status view (application tracking)
  • Indeed adapter (chromiumoxide)
  • LinkedIn adapter

Verify: Full flow: search -> analyze -> tailor -> apply -> track.

Phase 6: Polish

Goal: Production-quality UX.

  • Graceful error handling in all views
  • Loading spinners for async operations
  • Catppuccin Mocha theme finalization
  • Help overlay (? key)
  • Empty states, network failures, API rate limits
  • Resume export (PDF/LaTeX)

Definition of Done

  • All crates build with cargo build
  • cargo clippy passes with no warnings
  • Core workflow works end-to-end
  • At least Seek adapter is fully functional
  • Resume tailoring produces valid LaTeX output
  • TUI is responsive and doesn’t block on async operations
  • API keys handled securely (env var or config, never logged)