Document defensive behavior: openai_compat with no base_url returns True
(cloud) because unknown destination is assumed cloud. Add explanatory
comment to LOCAL_URL_MARKERS for the 0.0.0.0 bind-address case.
Replaces NotImplementedError stub with full LLM-backed implementation.
Builds a prompt from the last 3 resume positions plus already-selected
skills/domains/keywords, calls LLMRouter, and returns de-duped suggestions
in all three categories.
Replaces NotImplementedError stub with a real LLMRouter-backed implementation
that builds a structured prompt covering blocklist alias expansion, values
misalignment, and role-type filtering, then parses the JSON response into
suggested_titles and suggested_excludes lists.
Moves LLMRouter import to module level so tests can patch it at
scripts.suggest_helpers.LLMRouter.
BYOK policy: if a user supplies any LLM backend (local ollama/vllm or
their own API key), they get full access to AI generation features.
Charging for the UI around a service they already pay for is bad UX.
app/wizard/tiers.py:
- BYOK_UNLOCKABLE frozenset: pure LLM-call features that unlock with
any configured backend (llm_career_summary, company_research,
interview_prep, survey_assistant, voice guidelines, etc.)
- has_configured_llm(): checks llm.yaml for any enabled non-vision
backend; local + external API keys both count
- can_use(tier, feature, has_byok=False): BYOK_UNLOCKABLE features
return True when has_byok=True regardless of tier
- tier_label(feature, has_byok=False): suppresses lock icon for
BYOK_UNLOCKABLE features when BYOK is active
Still gated (require CF infrastructure, not just an LLM call):
llm_keywords_blocklist, email_classifier, model_fine_tuning,
shared_cover_writer_model, multi_user, all integrations
app/pages/2_Settings.py:
- Compute _byok = has_configured_llm() once at page load
- Pass has_byok=_byok to can_use() for _gen_panel_active
- Update caption to mention BYOK as an alternative to paid tier
app/pages/0_Setup.py:
- Wizard generation widget passes has_byok=has_configured_llm()
to can_use() and tier_label()
tests/test_wizard_tiers.py:
- 6 new BYOK-specific tests covering unlock, non-unlock, and
label suppression cases
Primary parse path is now fully deterministic — no LLM, no token limits,
no JSON generation. Handles two-column experience headers, institution-before-
or-after-degree education layouts, and header bleed prevention via
looks_like_header detection.
LLM path retained as optional career_summary enhancement only (1500 chars,
falls back silently). structure_resume() now returns tuple[dict, str].
Tests updated to match the new API.
- generate() accepts previous_result + feedback; appends both to LLM prompt
- task_runner cover_letter handler parses params JSON, passes fields through
- Apply Workspace: "Refine with Feedback" expander with text area + Regenerate
button; only shown when a draft exists; clears feedback after submitting
- 8 new tests (TestGenerateRefinement + TestTaskRunnerCoverLetterParams)
Replaces the old 5-step wizard with a 7-step orchestrator that uses the
step modules built in Tasks 2-8. Steps 1-6 are mandatory (hardware, tier,
identity, resume, inference, search); step 7 (integrations) is optional.
Each Next click validates, writes wizard_step to user.yaml for crash recovery,
and resumes at the correct step on page reload. LLM generation buttons
submit wizard_generate tasks and poll via @st.fragment(run_every=3). Finish
sets wizard_complete=True, removes wizard_step, and calls apply_service_urls.
Adds tests/test_wizard_flow.py (7 tests) covering validate() chain, yaml
persistence helpers, and wizard state inference.
Add all 13 integration modules (Notion, Google Drive, Google Sheets,
Airtable, Dropbox, OneDrive, MEGA, Nextcloud, Google Calendar, Apple
Calendar/CalDAV, Slack, Discord, Home Assistant) with fields(), connect(),
and test() implementations. Add config/integrations/*.yaml.example files
and gitignore rules for live config files. Add 5 new registry/schema
tests bringing total to 193 passing.
- Add tier, dev_tier_override, wizard_complete, wizard_step, dismissed_banners
fields to UserProfile with defaults and effective_tier property
- Add params TEXT column to background_tasks table (CREATE + migration)
- Update insert_task() to accept params with params-aware dedup logic
- Update submit_task() and _run_task() to thread params through
- Add test_wizard_defaults, test_effective_tier_override,
test_effective_tier_no_override, and test_insert_task_with_params
- UserProfile class drives all personal data
- First-run wizard gates app until user.yaml exists
- Docker Compose stack: remote/cpu/single-gpu/dual-gpu profiles
- Vision service containerized (single-gpu/dual-gpu)
- All Alex/Library references removed from app and scripts
- Circuit Forge LLC / Peregrine branding throughout
Replace hard-coded paths (/Library/Documents/JobSearch), names (Alex Rivera),
NDA sets (_NDA_COMPANIES), and the scraper path with UserProfile-driven lookups.
Update tests to be profile-agnostic (no user.yaml in peregrine config dir).