Configuration Reference
FinFocus is configured via a configuration file (default:
~/.finfocus/config.hujson) and environment variables.
File Format
Section titled “File Format”The configuration file is HuJSON: JSON with comments and trailing commas.
config set and the dashboard Config page write config.hujson. A legacy
config.yaml is still read. The next save of that tier writes the HuJSON
sibling and leaves the YAML file in place. Examples below show the keys; the
on-disk file uses JSON syntax.
output: default_format: table # table, json, ndjson precision: 2
logging: level: info # debug, info, warn, error
plugin_dir: ~/.finfocus/pluginsConfiguration Resolution
Section titled “Configuration Resolution”FinFocus uses a two-tier configuration system where project-local settings override user-global defaults.
Two-Tier Configuration
Section titled “Two-Tier Configuration”| Tier | Location | Purpose |
|---|---|---|
| Project-local | $PULUMI_PROJECT/.finfocus/config.hujson |
Project-specific budgets, output preferences, plugin config |
| User-global | ~/.finfocus/config.hujson |
Shared defaults across all projects |
Project-Local Directory Structure
Section titled “Project-Local Directory Structure”When a Pulumi project is detected, FinFocus creates a .finfocus/ directory
alongside Pulumi.yaml:
my-pulumi-project/├── Pulumi.yaml├── .finfocus/│ ├── .gitignore # Auto-generated, protects user-specific data│ ├── config.hujson # Project budgets, output prefs│ └── dismissed.json # Per-project recommendation dismissalsResolution Precedence
Section titled “Resolution Precedence”Project-specific settings (config, dismissals) are resolved in this order:
--project-dirflag (explicit override)FINFOCUS_PROJECT_DIRenvironment variable- Walk up from CWD to find
Pulumi.yaml, then use$PROJECT/.finfocus/ - Fall back to
~/.finfocus/(backward compatible)
Global resources (plugins, cache, logs) are resolved separately:
FINFOCUS_HOMEenvironment variablePULUMI_HOME/finfocus~/.finfocus/
Config Merge Behavior
Section titled “Config Merge Behavior”Project config.hujson overrides global config.hujson at the top-level key
level (shallow merge). Keys absent in the project config inherit from global
defaults.
For example, if the global config defines output and logging sections, and
the project config only defines output, the project output section replaces
the global one entirely while logging is inherited from the global config.
# ~/.finfocus/config.hujson (global)output: default_format: table precision: 2logging: level: info
# my-project/.finfocus/config.hujson (project)output: default_format: json precision: 4
# Effective config: output from project, logging from globalSections
Section titled “Sections”Output
Section titled “Output”default_format: The default output format for commands.precision: Number of decimal places for cost values.
Logging
Section titled “Logging”level: The verbosity of logs.
Plugin Directory
Section titled “Plugin Directory”plugin_dir: Top-level key that overrides the computed plugin installation directory. Equivalent to setting theFINFOCUS_PLUGIN_DIRenvironment variable (env var takes precedence). Example:plugin_dir: /opt/finfocus/plugins
Pulumi ESC can supply plugin process environment variables. The section is
top-level. It does not live under plugins.<name>, because that object is
injected into the plugin process.
{ "esc": { "environments": ["billing/aws-readonly@stable"], "plugins": { "vantage": { "environments": ["billing/vantage"], "variables": ["VANTAGE_TOKEN"] } } }}| Key | Kind | Meaning |
|---|---|---|
esc.environments |
String list | References used for every plugin. Replaces the stack’s ESC imports. |
esc.plugins.<name>.environments |
String list | References for that plugin. Replaces esc.environments and the stack imports. |
esc.plugins.<name>.variables |
String list | Extra variable names that plugin may receive from ESC. |
A missing environments key means “no preference”: finfocus uses the stack
imports. An empty list ([]) turns ESC off for what that key covers. A
per-plugin empty list disables ESC for that plugin only. An empty global list
disables ESC for every plugin that does not set its own list.
A project esc section replaces the global esc section as a whole, the
same way every other top-level section merges. Keys under esc are not
secrets. config get and the Config page show the references and the
variable names.
config set takes a comma-separated list:
finfocus config set esc.environments 'billing/aws-readonly@stable,shared/tags'finfocus config set esc.plugins.vantage.variables 'VANTAGE_TOKEN'finfocus config set esc.environments ''The empty string writes [], which disables ESC. There is no unset command.
Delete the key from the file, or remove it on the Config page, to return to
“not set”.
Every command except the config commands rejects a bad value with exit
code 2 before a plugin starts. config validate reports it. Validation does
not contact Pulumi Cloud.
| Problem | Message |
|---|---|
| Malformed reference | esc.environments[0]: invalid ESC environment reference "a//b": empty segment |
| Bad variable name | esc.plugins.vantage.variables[1]: invalid environment variable name "1BAD" |
| Reserved variable | esc.plugins.vantage.variables[0]: FINFOCUS_PLUGIN_PORT is reserved for plugin startup |
| Unknown plugin | esc.plugins.foo: no plugin named "foo" is installed (warning) |
| Wrong JSON type | The usual type-mismatch error for esc.environments |
config set still accepts a replacement while the file contains a bad
reference, so the value can be repaired. config validate keeps reporting
that error until the file is valid. See
Pulumi ESC credentials.
Configure the BoltDB-backed cost calculation cache.
enabled: Master switch for caching. Set totrueto enable. Default:false.ttl_seconds: Seconds until a cached entry expires.0disables expiration. Default:3600(1 hour).directory: Explicit path for the cache database file. When empty, auto-resolves to the project.finfocus/directory or~/.finfocus/cache/.lru_enabled: Put an in-memory LRU in front of BoltDB. Default:false.FINFOCUS_CACHE_LRU_ENABLEDoverrides this value.lru_max_items: How many entries the memory tier keeps.0uses 256.FINFOCUS_CACHE_LRU_MAX_ITEMSoverrides this value. A negative value is ignored.
Scoring
Section titled “Scoring”Configure the optional recommendation scoring step. It is off by default and adds no network activity of its own; the scorer is a plugin. See the Recommendation Scoring Guide for behavior and data handling.
scoring.enabled: Turn scoring on. Default:false.scoring.plugin: Name of the scorer plugin. Required when enabled.scoring.identifier_mode: How resource ids and names reach the scorer:pseudonymized(default),omittedorraw.scoring.field_allowlist: Recommendation fields sent to the scorer. Empty sends all. Valid names:category,action_type,resource,impact,priority,confidence_score,description,reasoning,source,created_at,metadata,action_detail,primary_reason,secondary_reasons,pricing_category,spot_interruption_risk_score.scoring.timeout_seconds: Timeout for each scorer call. Default:30.scoring.needs_review.risk,scoring.needs_review.false_positive,scoring.needs_review.insufficient_evidence: Thresholds (0 to 1) for the needs-review marker. Defaults:0.3,0.3,0.5.scoring.needs_review.dead_band: Band around each threshold that is still flagged. Default:0.1.
Resource History
Section titled “Resource History”Configure the BoltDB-backed resource history store that tracks cloud resource identity across replacements and deletions. The history store enables accurate full-month cost reporting by remembering all cloud IDs a resource has ever had.
Important: Unlike the cache, the history database contains persistent data that cannot be regenerated. Deleting
history.dbcauses accuracy degradation for past billing periods.
| Option | Type | Default | Env Var | Description |
|---|---|---|---|---|
cost.history.enabled |
bool | true |
FINFOCUS_HISTORY_ENABLED |
Master switch for resource history tracking. When disabled, all history operations are no-ops and commands fall back to current-state-only behavior. |
cost.history.retention_days |
int | 90 |
FINFOCUS_HISTORY_RETENTION_DAYS |
Days before stale entries are removed. Entries with LastSeen older than this window are cleaned up on startup. |
cost.history.directory |
string | <FinFocus home>/history |
FINFOCUS_HISTORY_DIR |
History database directory. |
cost.history.pulumi_cloud.enabled |
bool or string enum | false |
None | Import Pulumi Cloud resource identities: false disables requests, auto falls back to local history, true requires a complete import. Requires cost.history.enabled. |
The history directory resolves in this order: FINFOCUS_HISTORY_DIR, then
cost.history.directory, then <FinFocus home>/history. FinFocus home is
FINFOCUS_HOME, then $PULUMI_HOME/finfocus, then ~/.finfocus (or
./.finfocus when no home directory can be found). When FINFOCUS_HOME and
PULUMI_HOME are unset, the directory is ~/.finfocus/history.
FinFocus ignores a project-level cost.history.directory and logs a warning
when it is absolute, contains .., or resolves outside the project (for
example through a symlink), or when a history database file in it is a symlink, because a cloned repository
controls that file. The global config value and FINFOCUS_HISTORY_DIR are
trusted.
cost.history.pulumi_cloud.enabled accepts YAML/JSON booleans false and
true, or strings "false", "auto", and "true". Other values are rejected.
Both global and project configuration may set it. config get and the Config
page display string values; config set and the Config page save strings.
finfocus config set cost.history.pulumi_cloud.enabled autofinfocus config get cost.history.pulumi_cloud.enabledThe dashboard Config page provides an editable enum row for
cost.history.pulumi_cloud.enabled with the three choices and default false.
Overview and web cost views use stored history; they never import from Cloud.
See the Resource History Guide
for import behavior and observation limits.
Pulumi Cloud credentials come from the Pulumi CLI login or
PULUMI_ACCESS_TOKEN; no token config key exists. An environment token override
for a project’s backend.url requires PULUMI_BACKEND_URL to explicitly select
that backend. Without it, only the token saved for that URL is used.
Example: History Configuration
Section titled “Example: History Configuration”cost: history: enabled: true retention_days: 90 directory: "" # FINFOCUS_HISTORY_DIR, else <FinFocus home>/history pulumi_cloud: enabled: auto # false (default), auto, or trueCost Allocation
Section titled “Cost Allocation”Configure tag-based cost attribution for resources that lack direct billing identity. This is a Layer 2 feature — the configuration is validated and stored, but the tag-based query logic is deferred to a future milestone.
| Option | Type | Default | Description |
|---|---|---|---|
cost.allocation.enabled |
bool | false |
Will enable tag-based cost attribution fallback. The configuration is validated and stored now; tag-based query logic will be applied in a future milestone. |
cost.allocation.tags |
string list | [] |
Tag keys that will be used for billing API queries (e.g., pulumi:project, aws:createdBy). Each key must be non-empty and at most 128 characters. Validated on load but not yet applied for attribution. |
Example: Allocation Configuration
Section titled “Example: Allocation Configuration”cost: allocation: enabled: true tags: - "pulumi:project" - "aws:createdBy" - "team"Cost & Budgets
Section titled “Cost & Budgets”Configure budget limits, alerts, and cost calculation preferences.
cost.terraform.include_workspace
Section titled “cost.terraform.include_workspace”Boolean, default false. When true, Terraform history identity includes the
workspace name from terraform workspace show (or tofu). When false,
FinFocus does not ask for the workspace and uses the stack name unscoped.
The setting does not change a Pulumi history key.
cost.profile
Section titled “cost.profile”Default workload hint for projected cost and recommendations. Accepted values
are dev, development, prod, production, burst, and default.
An omitted field means no preference. An empty value is a configuration
error. default is the same as no preference. --profile and FINFOCUS_PROFILE override this field for one
run and do not rewrite the file. cost actual and cost history collect
accept the flag so a bad value fails early, and they do not send the hint.
Hierarchical Budget Configuration
Section titled “Hierarchical Budget Configuration”The cost.budgets section supports hierarchical scoping with global, providers, tags, and types sections.
cost.budgets.global
Section titled “cost.budgets.global”Global budget applied to all resources.
| Option | Type | Default | Description |
|---|---|---|---|
amount |
number | - | Required. The budget limit amount. |
currency |
string | USD |
ISO 4217 currency code. |
period |
string | monthly |
Budget period (daily, weekly, monthly, yearly). |
alerts |
list | [] |
List of alert definitions. |
exit_on_threshold |
boolean | false |
Whether to exit CI/CD when the budget threshold is reached (global and per-scope). |
exit_code |
number | 2 | Exit code when budget exceeded (CI/CD integration). |
cost.budgets.providers
Section titled “cost.budgets.providers”Per-provider budgets for multi-cloud cost control.
| Option | Type | Default | Description |
|---|---|---|---|
<provider> |
object | - | Provider name (aws, gcp, azure) with budget settings. |
<provider>.amount |
number | - | Required. Provider budget limit. |
<provider>.currency |
string | Global currency | Must match global budget currency. |
cost.budgets.tags
Section titled “cost.budgets.tags”Tag-based budgets for team/project cost allocation.
| Option | Type | Default | Description |
|---|---|---|---|
selector |
string | - | Required. Tag pattern (key:value or key:*). |
priority |
number | 0 | Priority for overlapping tags (higher wins). |
amount |
number | - | Required. Tag budget limit. |
currency |
string | Global currency | Must match global budget currency. |
cost.budgets.types
Section titled “cost.budgets.types”Per-resource-type budgets for category control.
| Option | Type | Default | Description |
|---|---|---|---|
<type> |
object | - | Resource type (e.g., aws:ec2/instance) with budget settings. |
<type>.amount |
number | - | Required. Type budget limit. |
<type>.currency |
string | Global currency | Must match global budget currency. |
cost.budgets.alerts (within any scope)
Section titled “cost.budgets.alerts (within any scope)”| Option | Type | Default | Description |
|---|---|---|---|
threshold |
number | - | Required. Percentage of budget (1-100) to trigger alert. |
type |
string | actual |
Trigger on actual (historical) or forecasted (projected) cost. |
notifications |
list | [] |
Destinations notified when the threshold is exceeded (see below). |
cost.budgets.<scope>.alerts[].notifications
Section titled “cost.budgets.<scope>.alerts[].notifications”Each destination is sent only in runs with --notify or FINFOCUS_NOTIFY=true.
| Option | Type | Default | Description |
|---|---|---|---|
type |
string | - | Required. slack (incoming webhook) or webhook (generic HTTPS endpoint). |
url |
string | - | Required. https:// URL with a host, or ${FINFOCUS_NOTIFY_*} references. |
channel |
string | - | Slack only. Channel override sent with the message. |
method |
string | POST |
Webhook only. POST or PUT. |
headers |
object | - | Webhook only. Extra request headers; values may use ${FINFOCUS_NOTIFY_*}. |
Only variables named FINFOCUS_NOTIFY_* expand, and only in the global config.
A project config destination must use literal values; validation rejects any
${...} there. config get and config list show url and header values as
[REDACTED] unless the value is a single ${NAME} reference. See
Budget Notifications.
Example: Scoped Budget Configuration
Section titled “Example: Scoped Budget Configuration”cost: budgets: global: amount: 10000.00 currency: USD period: monthly exit_code: 2 alerts: - threshold: 80 type: actual providers: aws: amount: 5000.00 gcp: amount: 3000.00 tags: - selector: 'team:platform' priority: 100 amount: 3000.00 - selector: 'env:prod' priority: 50 amount: 5000.00 types: 'aws:ec2/instance': amount: 2000.00 'aws:rds/instance': amount: 3000.00See Budget Configuration Guide for detailed usage.
JSON Schema Validation
Section titled “JSON Schema Validation”For IDE autocompletion (VS Code, JetBrains), add this comment to the top of
config.hujson:
// yaml-language-server: $schema=https://rshade.github.io/finfocus/schemas/config.jsonA legacy config.yaml can keep the same schema comment with a leading #.