Skip to content
nikcli
Docs
Documentation

Configuration

How nikcli discovers, parses, merges, validates, and applies config across global, project, and runtime sources.

Overview

Config is resolved by packages/nikcli/src/config/config.ts with path discovery from config/paths.ts. Files are JSONC, support trailing commas, and are validated against Config.Info. Arrays for plugin and instructions are concatenated and deduplicated instead of simply replaced.

User

Global config

Loaded from Global.Path.config/nikcli.json and used for user-wide defaults.

Repo

Project config

Discovered upward from the working directory and worktree unless project config is disabled.

Plugins

Config directories

Directories ending in .nikcli and NIKCLI_CONFIG_DIR can provide config, commands, agents, modes, plugins, and instructions.

Override

Inline env config

NIKCLI_CONFIG_CONTENT is merged after global/custom/project config, before directory extras are loaded.

Resolution order

The current source order is precise and matters:

  1. Remote well-known config from saved auth records with type wellknown. This is the lowest-precedence layer.
  2. Global config from Global.Path.config/nikcli.json.
  3. Custom config file from NIKCLI_CONFIG.
  4. Project config files discovered upward, unless NIKCLI_DISABLE_PROJECT_CONFIG is set.
  5. Inline JSON from NIKCLI_CONFIG_CONTENT.
  6. Config directories from ConfigPaths.directories, including global config dir, repo .nikcli dirs, home .nikcli, and NIKCLI_CONFIG_DIR.
  7. Command, agent, mode, and plugin entries loaded from those directories.
  8. Deprecated mode entries are migrated into agent.
  9. NIKCLI_PERMISSION merges into permission.
  10. Legacy top-level tools is converted into permission rules.
  11. Runtime flag overrides such as NIKCLI_DISABLE_AUTOCOMPACT and NIKCLI_DISABLE_PRUNE are applied.
Directory extras load after inline config

Even though NIKCLI_CONFIG_CONTENT is the highest file/content layer, later directory scans still load command, agent, mode, plugin, and .nikcli/nikcli.json entries from config directories.

Where files live

LocationHow foundPurpose
Global config dirGlobal.Path.configUser-wide nikcli.json, command, agent, plugin, and skill files.
Project configFilesystem.findUp("nikcli.json")Repo or subdirectory-specific overrides.
.nikcli directoriesFilesystem.up from directory/worktree and homeLocal config modules and plugin entrypoints.
NIKCLI_CONFIGExact env pathSingle custom config file.
NIKCLI_CONFIG_DIRExact env directoryAdditional config directory, always included in directory scan.
Terminal
bash
NIKCLI_CONFIG=/path/to/nikcli.json nikcli
NIKCLI_CONFIG_DIR=/path/to/.nikcli nikcli
NIKCLI_DISABLE_PROJECT_CONFIG=1 nikcli

Config tokens

Config text supports {env:VAR} and {file:path} substitutions before JSONC parsing. Relative file paths resolve against the config file's directory. Lines commented with // skip file-token expansion.

nikcli.json
json
{
  "provider": {
    "openai": {
      "options": {
        "apiKey": "{env:OPENAI_API_KEY}"
      }
    },
    "openrouter": {
      "options": {
        "apiKey": "{file:./secrets/openrouter.txt}"
      }
    }
  }
}
Well-known config is safer

Remote well-known config only receives the saved token through an explicitly named env key from the auth record. It does not freely read process env or local file references.

Minimal config

nikcli.json
json
{
  "$schema": "https://nikcli-ai.dev/config.json",
  "theme": "tokyonight",
  "model": "openai/gpt-5",
  "small_model": "openai/gpt-5-mini",
  "default_agent": "build",
  "share": "manual",
  "provider": {
    "openai": {
      "options": {
        "apiKey": "{env:OPENAI_API_KEY}"
      }
    }
  },
  "permission": {
    "bash": "ask",
    "edit": "ask",
    "webfetch": "ask"
  }
}

Top-level fields

FieldPurpose
$schemaSchema URL for editor validation.
theme, locale, usernamePresentation, language, regional formatting, and displayed user label.
model, small_modelDefault primary and helper model ids.
default_agent, agentDefault agent and agent-specific prompts/tools/mode config.
providerProvider credentials, base URLs, models, variants, timeouts, allow/block lists.
mcp, connectorsExternal tool servers and service connectors.
formatter, lspFormatter and language server config or false to disable.
permission, toolsCurrent permission rules and legacy tool boolean compatibility.
plugin, instructionsPlugin specifiers and extra instruction files/patterns. Arrays concatenate across layers.
server, remoteServe/web defaults and remote control behavior.
share, autoshareSharing mode. autoshare migrates to share: auto.
analyticsAnonymous usage reporting. On by default; see below to opt out.
compactionAuto compaction, prune, and reserved token buffer.
experimentalFeature flags such as hooks, brain, memory, MCP timeout, native LLM runtime.
image, attachment, speakMedia generation and attachment normalization config.
notifications, mobileTodo/general notifications and mobile development helpers.

Anonymous usage reporting

nikcli keeps per-day analytics locally, for the /analytics view. It also contributes those per-day totals, anonymously, to the public stats at nikcli-ai.dev/data — which is how that page can show the models people actually run on their own provider keys rather than only what the nikcli gateway served. This is on by default, because a sample of only the people who opted in would measure those people rather than the community.

Turning it off, in order of precedence:

# either environment variable, no config file needed
DO_NOT_TRACK=1
NIKCLI_DISABLE_ANALYTICS=1
{
  "analytics": {
    "share": false
  }
}

The environment variables win over the config, so a machine or a CI image can opt out without a config file existing at all.

What is sent: date, provider, model, distinct session count, assistant message count, token counts and cost. Along with them goes a random UUID generated on your machine, so a resend replaces a day rather than doubling it — it is never published, and the public feed only ever emits a count of distinct installs.

What is never sent: prompts, responses, file paths, repository names, session titles, account details, or anything about your environment. Set analytics.endpoint to send reports to your own collector instead of nikcli's. A failed report is retried later and never surfaces an error. Run nikcli analytics show to see exactly what would be sent.

Providers

Provider entries are partial models.dev providers plus nikcli-specific options. Use enabled_providers to allow only specific provider ids, or disabled_providers to remove automatic providers.

provider config
json
{
  "enabled_providers": ["openai", "anthropic", "openrouter"],
  "provider": {
    "anthropic": {
      "options": {
        "apiKey": "{env:ANTHROPIC_API_KEY}"
      }
    },
    "amazon-bedrock": {
      "options": {
        "region": "us-east-1",
        "profile": "default"
      }
    }
  }
}

Agents and commands

agent config controls primary, subagent, and specialized agents. Deprecated mode entries are migrated into agent with mode: primary. Command templates are loaded from config and from config directories.

FieldPurpose
agent.build, agent.planPrimary code and plan agents.
agent.general, agent.explore, agent.scoutSubagent presets.
agent.title, agent.summary, agent.compactionSpecialized helper agents.
commandNamed command templates available to CLI/TUI and prompt flows.
instructionsAdditional instruction files or globs included in session context.

MCP and connectors

external tools
json
{
  "mcp": {
    "filesystem": {
      "type": "local",
      "command": ["bunx", "@modelcontextprotocol/server-filesystem", "."]
    },
    "remote-docs": {
      "type": "remote",
      "url": "https://example.com/mcp",
      "headers": { "Authorization": "Bearer token" }
    }
  },
  "connectors": {
    "github": { "type": "github", "enabled": true },
    "slack": { "type": "slack", "enabled": true },
    "figma": { "type": "figma", "enabled": true }
  }
}
ConfigValid forms
mcp.*Local stdio command, remote URL with headers/OAuth, or enabled-only toggle patch.
connectors.*Figma, Slack, GitHub, Lovable, Discord, Teams, Google Chat, Linear, or enabled-only toggle patch.

LSP and formatter

lsp can be false, can disable a built-in server by id, or can define a custom server. Custom LSP servers must provide extensions unless they reuse a known built-in id. Formatter config follows the same pattern: disable all with false, override or disable named formatter entries otherwise.

lsp config
json
{
  "lsp": {
    "pyright": { "disabled": true },
    "custom-ts": {
      "command": ["typescript-language-server", "--stdio"],
      "extensions": [".ts", ".tsx"],
      "env": { "NODE_ENV": "development" }
    }
  },
  "formatter": {
    "prettier": {
      "command": ["bunx", "prettier", "--write"],
      "extensions": [".ts", ".tsx", ".json"]
    }
  }
}

Permissions

Permission config accepts either a single action for all permissions, a tool-to-action map, or a tool-to-pattern map. Actions are ask, allow, and deny. The legacy top-level tools boolean map is converted into permission actions.

Permission modes

TUI and mobile expose three named presets that map to the same permission map shape shown below: require_approval (every tool asks), approve_for_me (the empty-map default — trusted local work is allowed, shell/network/external/sensitive actions ask), and full_access (every tool is allowed). Editing individual tools after a preset switches the value to custom. See Permissions for the full preset map and behavior table.

permission config
json
{
  "permission": {
    "bash": {
      "git status*": "allow",
      "rm *": "deny",
      "*": "ask"
    },
    "edit": "ask",
    "webfetch": "ask",
    "lsp": "ask"
  }
}

TUI and keybinds

FieldBehavior
tui.scroll_speedFixed scroll speed when scroll acceleration is disabled.
tui.scroll_acceleration.enabledEnable macOS-style scroll acceleration.
tui.diff_styleauto or stacked diff rendering.
tui.mouseEnable or disable mouse capture. Default is true.
tui.soundEnable ambient sound feedback.
tui.bg_pulseEnable animated home-logo background pulse.
keybinds.command_listDefault ctrl+p.
keybinds.agent_cycleDefault tab.
keybinds.input_submitDefault return.
keybinds.input_newlineDefault shift+return,ctrl+return,alt+return,ctrl+j.
keybinds.session_parentDefault ctrl+b, matching background/subagent parent behavior.

Notifications and mobile

Notification config is read by built-in notification plugins. Mobile config currently focuses on Tophat mobile project integration and defaults for platform/destination detection.

notifications and mobile
json
{
  "notifications": {
    "todo": {
      "enabled": true,
      "slack": { "enabled": true, "connector": "slack", "channel": "#builds" }
    },
    "notify": {
      "enabled": true,
      "events": { "sessionIdle": true, "permissionAsked": true },
      "rateLimit": { "windowMs": 60000, "maxPerWindow": 20 }
    }
  },
  "mobile": {
    "tophat": {
      "enabled": true,
      "defaultPlatform": "ios",
      "defaultDestination": "simulator"
    }
  }
}

Environment flags

FlagEffect
NIKCLI_CONFIGLoad an explicit config file after global config.
NIKCLI_CONFIG_CONTENTMerge inline JSON after project config.
NIKCLI_CONFIG_DIRAdd a config directory to directory scanning.
NIKCLI_DISABLE_PROJECT_CONFIGDisable project-level file discovery and .nikcli project directories.
NIKCLI_PERMISSIONInline permission overrides.
NIKCLI_DISABLE_DEFAULT_PLUGINSSkip built-in external plugins.
NIKCLI_DISABLE_EXTERNAL_SKILLSDisable external skill discovery.
NIKCLI_DISABLE_LSP_DOWNLOADPrevent automatic LSP server downloads.
NIKCLI_ENABLE_EXPERIMENTAL_MODELSKeep alpha models in provider catalog.
NIKCLI_DISABLE_AUTOCOMPACTForce compaction.auto false.
NIKCLI_DISABLE_PRUNEForce compaction.prune false.
NIKCLI_SERVER_PASSWORDEnable Basic Auth for server mode.
NIKCLI_SERVER_TAILSCALE_AUTHTrust loopback Tailscale identity headers.
NIKCLI_EXPERIMENTAL_HTTPAPIEnable typed HttpApi bridge.
NIKCLI_EXPERIMENTAL_LSP_TOOLEnable the LSP tool in the registry.
NIKCLI_EXPERIMENTAL_LSP_TYUse ty and disable pyright in the built-in LSP set.

Failure modes

SymptomLikely causeFix
Config parse error includes JSONC inputSyntax error after token substitution.Check the reported line/column and any {file:*} expansion.
Project config ignoredNIKCLI_DISABLE_PROJECT_CONFIG is set.Unset the flag or use NIKCLI_CONFIG.
Custom LSP rejectedUnknown LSP id has no extensions.Add the extension list or use a known built-in id.
Plugin duplicatedMultiple layers append plugin arrays.Config loader deduplicates plugin specs, but check versioned specs and force install behavior.
Permission does not matchLater rules or stored always approvals override earlier ones.Check merge order and project permission storage.

Source anchors

NeedFile
Config loading and schemapackages/nikcli/src/config/config.ts
Config path discoverypackages/nikcli/src/config/paths.ts
TUI configpackages/nikcli/src/config/tui.ts, packages/nikcli/src/config/tui-schema.ts
Environment flags@nikcli-ai/util/flag
Global paths@nikcli-ai/util/global
Plugin install config patchingpackages/nikcli/src/plugin/installer.ts
Connector CLI config patchingpackages/nikcli/src/cli/cmd/connectors.ts