skills/profile/SKILL.md
Complete financial profile interview to create .finyx/profile.json — the shared context file read by all Finyx advisors. Use when the user wants to set up their financial profile, update personal data, or when no .finyx/profile.json exists and another skill needs it.
npx skillsauth add italolelis/finyx finyx-profileInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Complete a structured financial profile interview that creates .finyx/profile.json — the shared user context file read by all Finyx specialist commands.
This is the mandatory first step. No other Finyx command will run without a completed profile.
Creates:
.finyx/profile.json — Financial profile with identity, tax, and goals data.finyx/STATE.md — Workflow state tracker.finyx/research/market/, .finyx/research/locations/, .finyx/analysis/, .finyx/output/ — Directory structureproperties/ — Folder for property documentsFINYX.md — Project summary fileInterview groups:
<execution_context>
${CLAUDE_SKILL_DIR}/references/disclaimer.md ${CLAUDE_SKILL_DIR}/references/germany/tax-rules.md ${CLAUDE_SKILL_DIR}/references/profile.json ${CLAUDE_SKILL_DIR}/references/state.md
</execution_context>
<process>Verify no existing profile (4-tier shared resolver):
# Profile path resolution: shared resolver (4-tier precedence)
# See scripts/README.md for full precedence rules.
if PROFILE_PATH=$("${CLAUDE_SKILL_DIR}/../../scripts/resolve-profile.sh" 2>/dev/null); then
# Determine which tier resolved (for the user-facing "PROFILE_EXISTS_*" banner)
case "$PROFILE_PATH" in
"$(pwd)/.finyx/profile.json") echo "PROFILE_EXISTS_LOCAL" ;;
"$HOME/.finyx/profile.json") echo "PROFILE_EXISTS_GLOBAL" ;;
*) echo "PROFILE_EXISTS_OVERRIDE" ;;
esac
else
echo "OK"
fi
If PROFILE_EXISTS_LOCAL or PROFILE_EXISTS_GLOBAL:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
FINYX > PROFILE ALREADY EXISTS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
A financial profile already exists at [PROFILE_PATH].
Use /finyx:status to see your current profile summary.
To start fresh, delete the profile and run /finyx:profile again.
Exit without making changes.
If PROFILE_EXISTS_OVERRIDE (resolved via $FINYX_PROFILE or ~/.config/finyx/config.json profile_path):
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
FINYX > PROFILE ALREADY EXISTS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
A financial profile already exists at [PROFILE_PATH] (resolved via $FINYX_PROFILE or ~/.config/finyx/config.json).
Use /finyx:status to see your current profile summary.
To start fresh, delete the profile and run /finyx:profile again, or unset the override.
Exit without making changes.
Display opening banner:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
FINYX > FINANCIAL PROFILE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Welcome. This interview takes about 5 minutes.
Your answers are stored locally in .finyx/profile.json.
Group 1 of 3: Residency + Nationality
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Use AskUserQuestion:
Store as residence_country.
Use AskUserQuestion:
Store as nationality.
Ask inline: "Do you earn income in any other country besides your country of residence? If yes, list the countries (e.g., 'Germany, Brazil'). If no, type 'no'."
Store as income_countries_raw. Parse into array: income_countries = [residence_country] + any additional countries named.
Compute cross_border:
cross_border = false
// Check 1: residence vs nationality mismatch
if residence_country != nationality_country:
cross_border = true
// Check 2: income from multiple countries
if income_countries has more than one unique country:
cross_border = true
If cross_border = true:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Cross-border situation detected
Residence: [residence_country] | Nationality: [nationality]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
We will collect tax details for all relevant jurisdictions.
Display section header:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Group 2 of 3: Income + Tax
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Determine which country branches are relevant:
residence_country == "Germany" OR "Germany" in income_countriesresidence_country == "Brazil" OR "Brazil" in income_countriesUse AskUserQuestion:
Store mapped value: I → "I", III/V → "III", IV/IV → "IV"
Use AskUserQuestion:
Store as boolean church_tax.
Ask inline: "What is your annual gross income from Germany (in EUR)? Enter a number, e.g., 85000"
Store as de_gross_income.
Calculate German marginal rate using the loaded tax-rules reference:
Base tax rate calculation:
- Single (class I): top bracket if income > 68,480 EUR → baseRate = 42%
Sub-bracket 57,919 - 68,480 EUR → progressive zone ~42%
Sub-bracket 17,006 - 57,918 EUR → progressive zone ~24%-42%
Below 10,908 EUR → 0% (Grundfreibetrag)
- Married (class III, splitting): thresholds doubled
top bracket if income > 136,960 EUR (half of 277,826 household)
Soli surcharge: if baseRate > 0, add 5.5% of income tax
Church tax: if church_tax, add 8% of income tax (Bavaria/Baden-Wuerttemberg) or 9% elsewhere
Simple marginal rate estimate:
if single and income > 68,480: marginalRate = 42 + (42 * 0.055) = 44.31%
if single and income 17,006-68,480: marginalRate ≈ 32 + (32 * 0.055) = ~33.76%
if married (III) and income > 136,960: marginalRate = 42 + (42 * 0.055) = 44.31%
if married (IV) use single thresholds
if church_tax: add baseRate * 0.09 (use 9% as default)
Display calculated rate:
Estimated marginal tax rate: [RATE]%
(Based on [TAX_CLASS], €[INCOME]/year gross income)
Ask inline: "Does this look correct? Enter 'yes' to accept, or enter your actual marginal tax rate (e.g., '44.31')."
If user enters a number, use that value instead. Store as de_marginal_rate.
Use AskUserQuestion:
Store mapped value: Simplificado → "simplificado", Completo → "completo"
Ask inline: "What is your annual gross income from Brazil in BRL? (If none, type 0)"
Store as br_gross_income.
Ask inline: "What is your CPF number? (Optional — for reference only, not shared externally. Type 'skip' to leave blank)"
Store as br_cpf (empty string if skip).
Display section header:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Group 3 of 3: Goals + Risk Tolerance
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Use AskUserQuestion:
Store as family_status.
Ask inline: "How many children do you have? (Enter 0 if none)"
Store as children (integer).
Use AskUserQuestion:
Store mapped value: Conservative → "conservative", Moderate → "moderate", Aggressive → "aggressive"
Use AskUserQuestion:
Store mapped integer: Short-term → 2, Medium-term → 7, Long-term → 15
Ask inline: "What are your primary financial goals? List them (e.g., 'real estate investment, ETF portfolio, retirement savings, tax optimization')"
Store as primary_goals array (split by comma).
Use AskUserQuestion:
Ask inline: "What is your exact liquid asset amount available for investment (EUR)? Enter a number."
Store as liquid_assets (integer).
Ask inline: "What are your total fixed monthly financial commitments (rent, existing loans, insurance, etc.) in EUR? Enter a number."
Store as monthly_commitments (integer).
Determine write path:
Profile write location:
- profile.json target is determined by `scripts/resolve-profile.sh --write-target` (see scripts/README.md).
Precedence: `$FINYX_PROFILE` env var → `~/.config/finyx/config.json` `profile_path` key → `./.finyx/profile.json` (when in a project dir) → `~/.finyx/profile.json` (global fallback).
- Create the target directory if it does not exist.
- IMPORTANT: only profile.json is relocatable. Working artifacts (`research/`, `analysis/`, `output/`, `STATE.md`, `insights-config.json`) are always written under `./.finyx/` of the current working directory — they are NOT placed alongside a relocated profile.json.
Create directory structure:
# Resolve write target via shared resolver (--write-target mode).
# This honors $FINYX_PROFILE / ~/.config/finyx/config.json if set,
# else falls through to project-local .finyx/ or ~/.finyx/.
PROFILE_TARGET=$("${CLAUDE_SKILL_DIR}/../../scripts/resolve-profile.sh" --write-target) || {
echo "ERROR: cannot determine where to write profile.json. See stderr above." >&2
exit 1
}
mkdir -p "$(dirname "$PROFILE_TARGET")" # ensure profile dir exists
mkdir -p .finyx/research/market # working artifacts always project-local
mkdir -p .finyx/research/locations
mkdir -p .finyx/analysis
mkdir -p .finyx/output
mkdir -p properties
Write $PROFILE_TARGET (the resolver-determined profile.json location) using all interview answers:
{
"$schema": "https://finyx.dev/schemas/profile.schema.json",
"version": "1.0.0",
"created": "[ISO_DATE]",
"updated": "[ISO_DATE]",
"_holdings_schema": {
"description": "Each broker in countries.{country}.brokers[] can contain a holdings[] array",
"holding_fields": {
"ticker": "Exchange ticker symbol (e.g., VWCE, BOVA11)",
"isin": "ISIN if available (e.g., IE00BK5BQT80), null for B3 assets",
"shares": "Number of shares/units held (decimal)",
"cost_basis": "Average cost per share in local currency",
"asset_class": "One of: equity-etf, bond-etf, reit-etf, stock, fii, fixed-income",
"geography": "One of: global, us, europe, emerging-markets, brazil, germany"
}
},
"identity": {
"residence_country": "[residence_country]",
"nationality": "[nationality]",
"income_countries": [income_countries],
"has_income_in_multiple_countries": [boolean],
"cross_border": [cross_border],
"family_status": "[family_status]",
"children": [children]
},
"countries": {
"germany": {
"tax_class": "[de_tax_class or null]",
"church_tax": [de_church_tax or false],
"gross_income": [de_gross_income or 0],
"marginal_rate": [de_marginal_rate or 0],
"brokers": []
},
"brazil": {
"ir_regime": "[br_ir_regime or null]",
"gross_income": [br_gross_income or 0],
"cpf": "[br_cpf or null]",
"brokers": []
}
},
"goals": {
"risk_tolerance": "[risk_tolerance]",
"investment_horizon": [investment_horizon_years],
"primary_goals": [primary_goals_array]
},
"pension": {
"de_rentenpunkte": null,
"expected_real_return_de": 1.5,
"br_inss_status": null,
"expected_real_return_br": 2.0,
"target_retirement_age": null
},
"project": {
"name": "[current directory name]",
"propertiesFolder": "properties",
"outputFolder": ".finyx/output",
"language": "en"
},
"investor": {
"country": "[residence_country]",
"taxClass": "[de_tax_class or empty string]",
"churchTax": [de_church_tax or false],
"children": [children],
"income": {
"primary": [de_gross_income or 0],
"spouse": 0,
"total": [de_gross_income or 0]
},
"marginalRate": [de_marginal_rate or 0],
"liquidAssets": [liquid_assets],
"monthlyCommitments": [monthly_commitments]
},
"strategy": {
"type": "neubau-rent-sell",
"horizon": [investment_horizon_years],
"financing": {
"ltv": 100,
"fixedPeriod": 10
},
"management": "professional",
"exitPlan": "sell-tax-free"
},
"criteria": {
"propertyType": "2-bedroom apartment",
"bedrooms": [2],
"minYield": 2.8,
"maxPrice": 450000,
"minSize": 45,
"maxSize": 80,
"parkingRequired": false,
"parkingPreferred": true,
"excludeErbpacht": true,
"excludeGroundFloor": false,
"topFloorPreferred": false,
"newConstructionOnly": true
},
"assumptions": {
"appreciation": 2.0,
"rentIncrease": 0,
"vacancy": 0,
"verwaltungPerSqm": 1.0,
"rucklagePerSqm": 0.6,
"saleCosts": 7.0,
"constructionPeriod": 18,
"constructionDraw": 50
}
}
Populate only the countries that are relevant — if Germany is not in scope, set all germany fields to null/0/false. Same for Brazil.
Write .finyx/STATE.md (always project-local — STATE.md is a working artifact, not relocatable) using the state template, updated for Finyx:
# FINYX Project State
**Project:** [directory name]
**Created:** [DATE]
**Status:** PROFILE COMPLETE
## Workflow
| Step | Status | Date |
|------|--------|------|
| Profile | ✅ COMPLETE | [DATE] |
| Scout | ⏳ PENDING | - |
| Analyze | ⏳ PENDING | - |
| Filter | ⏳ PENDING | - |
| Compare | ⏳ PENDING | - |
| Report | ⏳ PENDING | - |
## Locations
| Location | Status | Units | Shortlisted | Notes |
|----------|--------|-------|-------------|-------|
| (none yet) | - | - | - | Add via /finyx:scout |
## Notes
Profile created [DATE]. Run `/finyx:scout [location]` to begin real estate research.
Write FINYX.md project summary (always project-local):
# FINYX Project
**Created:** [DATE]
**Investor Country:** [residence_country]
**Tax Rate:** [marginal_rate]%
## Quick Reference
Run `/finyx:help` for all commands.
Run `/finyx:status` to see current state.
## Workflow
1. `/finyx:scout [location]` — Research a location
2. `/finyx:analyze [location]` — Calculate metrics
3. `/finyx:filter [location]` — Apply criteria
4. `/finyx:compare` — Side-by-side comparison
5. `/finyx:report` — Generate advisor briefing
Display summary banner:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
FINYX > PROFILE COMPLETE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Profile Summary:
Residence: [residence_country]
Nationality: [nationality]
Cross-border: [YES / NO]
Family Status: [family_status]
Children: [children]
[if Germany relevant:]
DE Tax Class: [tax_class]
DE Gross Income: €[amount]/year
DE Marginal Rate: [rate]%
Church Tax: [YES / NO]
[if Brazil relevant:]
BR IR Regime: [regime]
BR Gross Income: R$[amount]/year
Risk Tolerance: [risk_tolerance]
Horizon: [horizon] years
Liquid Assets: €[liquid_assets]
Commitments: €[monthly_commitments]/month
Files created:
$PROFILE_TARGET
.finyx/STATE.md
FINYX.md
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
NEXT STEPS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Real estate analysis:
1. Add property documents to properties/[location]/
2. Run /finyx:scout [location] to research the location
3. Run /finyx:analyze [location] to calculate metrics
4. Run /finyx:filter [location] to apply your criteria
5. Run /finyx:compare to compare shortlisted options
6. Run /finyx:report to generate advisor briefing
Financial advisors (ready to use):
/finyx:tax — Tax optimization advisor
/finyx:invest — Investment portfolio advisor
/finyx:broker — Broker comparison advisor
/finyx:pension — Pension planning advisor
Run /finyx:help for all available commands.
Append the legal disclaimer from the loaded disclaimer.md reference at the end of this output.
</process><error_handling>
If .finyx/profile.json or ~/.finyx/profile.json exists, display the error message in Phase 1 and exit cleanly. Do not overwrite.
If user enters non-numeric income value, prompt again: "Please enter a number (e.g., 85000 for €85,000/year)."
If user selects "Other" for residence country: "Note: Only Germany and Brazil have full tax integration in Finyx v1. Your profile will be created with limited tax data. You can still use the real estate commands — you will need to enter your marginal tax rate manually."
Then ask inline for their marginal tax rate and store in investor.marginalRate.
If user is unsure of their marginal tax rate, explain briefly: "Your marginal tax rate is the tax rate you pay on each additional euro of income. For Germany, this is income tax + Soli surcharge + optional church tax. A rough estimate for high earners (>68,480 EUR) is ~44%. Check your last tax return (Einkommenssteuerbescheid) for the exact figure."
</error_handling>
<notes>cross_border is derived automatically — the user does not select it. Detection rules:
Example: German national living in Germany with Brazilian rental income → cross_border = true
The skill delegates profile-path resolution to the shared resolver at scripts/resolve-profile.sh. See scripts/README.md for the full contract.
Brief recap — the resolver uses a 4-tier precedence chain (first match wins):
$FINYX_PROFILE env var — per-shell override~/.config/finyx/config.json profile_path key — user-level default./.finyx/profile.json — project-local (when CWD looks like a project: has .finyx/, .git, package.json, or Makefile)~/.finyx/profile.json — global fallbackAt startup (Phase 1): the resolver runs in read mode. If it succeeds, a profile already exists and the skill aborts to prevent overwriting. The PROFILE_EXISTS_LOCAL / PROFILE_EXISTS_GLOBAL / PROFILE_EXISTS_OVERRIDE banner reflects which tier resolved.
At write time (Phase 5): the resolver runs in --write-target mode (which never errors when no override is configured — it always falls through to project-local or global). The returned path is stored in $PROFILE_TARGET, and BASE_DIR=$(dirname "$PROFILE_TARGET") is used for the profile file only. Working artifacts (research/, analysis/, output/, STATE.md, insights-config.json) are always written under ./.finyx/ of the current working directory regardless of where profile.json lands.
This separation lets users keep a single financial profile across multiple investment projects (each with its own .finyx/ working directory) without copying or symlinking.
The investor.* section mirrors IMMO's original config.json structure. All existing RE commands that read investor.marginalRate will work without modification.
The profile command only creates — it does not update. To update individual fields in a future release, a /finyx:update-profile command will be added. For now, delete .finyx/ and re-run /finyx:profile.
The marginal rate calculation is an estimate. Users should always confirm with their actual Einkommenssteuerbescheid. The profile stores the user-confirmed rate, not the estimate.
</notes>tools
Home Assistant sensor integration bridge — air quality, humidity, temperature, sleep, weight, and blood pressure data correlated with health diary entries. Uses ha-mcp (user-side uvx install) as the sole MCP boundary via finyx-home-sensor-agent. Sub-skills: setup (one-time HA config), snapshot (current sensor state), audit (30/90-day environmental history), correlate (sensor vs. symptom cross-correlation as HYPOTHESES). Use when the user asks about: Home Assistant, HA sensor data, air quality correlation, sleep environment analysis, indoor humidity, CO2, PM2.5, VOC, allergen exposure tracking, sensor staleness, environmental audit, correlate sensors with symptoms. Does NOT cover: medical diagnosis, clinical advice, automated home control, non-health sensor analytics. Sibling skill finyx-health covers symptoms, records, and clinical preparation. All sensor-symptom correlations are HYPOTHESES — never causal claims. Prerequisites: ha-mcp installed (see README.md). HOMEASSISTANT_TOKEN env var set to a Long-Lived Access Token. finyx-home setup run once.
tools
Pre-visit preparation and health education for adults in Germany. Helps users arrive at medical appointments prepared — symptom intake (OPQRST/SOCRATES), record decoding (Arztbriefe, Laborbefunde), differential context with AWMF/ IQWiG/Cochrane citations, doctor sourcing (KV Berlin first, Germany-wide expansion when warranted), G-BA Vorsorgekatalog gap analysis, medication checks (PRISCUS 2.0), §27b SGB V second-opinion case structuring. Use when the user asks about: symptoms, Arztbrief, Laborbefund, Befund decoding, pre-visit preparation, finding a Facharzt / GP / Hausarzt in Berlin, Vorsorge, Check-up 35, Krebsfrüherkennung, IGeL, PRISCUS, medication interactions, second opinion, Maximalversorger, certified Zentrum, health diary, symptom tracking, biomarker logging, blood pressure log, weight log, mood log, sleep log, appointment tracking, Doctolib search, samedi search, Arzttermin, Terminsuche. Does NOT cover: clinical diagnosis, treatment recommendations, dose changes, automated booking, ePA integration, pediatric care, Brazilian health system, wearables ingest. Sibling skill finyx-home covers Home Assistant sensor data.
data-ai
Investment tax advisor for German and Brazilian investors — Abgeltungssteuer, Sparerpauschbetrag, Vorabpauschale, DARF, come-cotas, cross-border DBA guidance. Use when the user asks about taxes, tax optimization, Steuererklärung, tax class, deductions, or investment taxation.
testing
Real estate investment analysis for Germany — location scouting (Erbpacht, transport), property analysis (yield, Kaufnebenkosten, AfA), filtering, shortlist comparison, stress testing, advisor briefing, and mortgage rate research. Use when the user asks about real estate, property investment, Immobilien, mortgage, Kaufnebenkosten, rental yield, or location scouting.