Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
70a4169
fix(lint): remove useless assignments and unnecessary type assertions…
mBukator Jun 6, 2026
0c48045
chore(knip): add unused-code detection to tooling and CI (#94)
mBukator Jun 21, 2026
6fd9fe1
style(docs): realign development scripts table in contributing guide …
mBukator Jun 21, 2026
0bc2b95
build(prettier): broaden format scope to docs and tests (#96)
mBukator Jun 21, 2026
f905007
chore(license): correct copyright holder to Maksym Bukator (#97)
mBukator Jun 21, 2026
d651480
chore(deps): bump dev dependencies to resolve security advisories (#101)
mBukator Jul 4, 2026
365ab85
ci(dependabot): target develop instead of default branch (#102)
mBukator Jul 5, 2026
75dbd1c
chore: sync release v1.0.1 to develop (#106)
mBukator Jul 5, 2026
2dbc1c0
chore(deps): bump actions/setup-node from 6 to 7 (#108)
dependabot[bot] Aug 22, 2026
c74d66e
ci(dependabot): ignore typescript major bumps (#111)
mBukator Aug 22, 2026
c68a706
chore(deps): bump production dependencies and correct node floor (#112)
mBukator Aug 22, 2026
54d61d8
docs(readme): replace static logo with animated banner (#117)
mBukator Oct 14, 2026
f7d70f4
chore(deps): bump the production-dependencies group across 1 director…
dependabot[bot] Oct 14, 2026
3ac053f
fix(deps): resolve dev dependency security advisories (#118)
mBukator Oct 14, 2026
e0f4c93
chore: sync release v1.0.2 to develop (#120)
mBukator Oct 14, 2026
a03bf6b
feat(themes): add custom theme overrides via customTheme config (#122)
mBukator Oct 18, 2026
4f066f2
chore(release): bump version to 1.1.0
mBukator Oct 18, 2026
137d87a
chore(release): merge main into release/v1.1.0
mBukator Oct 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,43 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.1.0] - 2026-10-18

### Added

- Custom themes via `"theme": "custom"` and a `customTheme` block in the user
or project `.merlinrc.json`

`customTheme` takes `extends` (`"wizard"` or `"standard"`) and a
`messages` object. Only the listed messages are overridden; everything
else falls back to the base theme, which also decides decoration such as
emoji columns and menu icons. The three template messages accept
`{field}`, `{maxCharacters}` and `{fileName}` placeholders, filled by
plain string substitution and never evaluated.

- Validation and sanitization for custom theme messages

Overrides are checked against the built-in theme shape, so unknown keys
are dropped. C0 and C1 control characters and bidirectional override
characters are stripped, and each message is capped at 200 code points.

- `custom` option in the `merlin config` theme picker, and the active
`customTheme` in `merlin config --show` output

- Custom theme documentation: a Custom Themes section and a full message key
reference in `docs/usage/themes.md`, plus `customTheme` entries in
`docs/reference/config-schema.md` and `docs/reference/configuration.md`

### Changed

- `DEFAULT_CONFIG` now includes an empty `customTheme` block

The next time `merlin config` saves `~/.merlinrc.json`, it writes a
starter `"customTheme": { "extends": "wizard", "messages": {} }` entry.
The block has no effect unless `theme` is set to `"custom"`. A
hand-written `customTheme` is kept exactly as written when saving from
`merlin config`.

## [1.0.2] - 2026-10-14

### Changed
Expand Down
66 changes: 55 additions & 11 deletions docs/reference/config-schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Both config files are JSON objects at the top level. Every field is optional - o
```json
{
"theme": "wizard",
"customTheme": { "extends": "wizard", "messages": {} },
"maxSubjectLength": 72,
"maxScopeLength": 20,
"editor": "vim",
Expand All @@ -32,7 +33,7 @@ Invalid values are silently ignored and the built-in default is used instead. Me

### `theme`

**Type:** `"wizard"` | `"standard"`
**Type:** `"wizard"` | `"standard"` | `"custom"`
**Default:** `"wizard"`

Controls the UI theme used throughout the prompt sequence.
Expand All @@ -41,6 +42,7 @@ Controls the UI theme used throughout the prompt sequence.
| ------------ | --------------------------------------------------------------------------------------- |
| `"wizard"` | Magical themed prompts with emoji, color, and thematic vocabulary. |
| `"standard"` | Minimal prompts with plain text. Better for low-color terminals or accessibility needs. |
| `"custom"` | Your messages from [`customTheme`](#customtheme), layered over its base theme. |

**Example:**

Expand All @@ -54,6 +56,41 @@ See [Themes](../usage/themes.md) for a full comparison including exact prompt te

---

### `customTheme`

**Type:** `{ extends: "wizard" | "standard"; messages: object }`
**Default:** `{ "extends": "wizard", "messages": {} }`

Messages used when `theme` is `"custom"`. Only the overridden messages need to be listed - every other message comes from the `extends` theme, which also decides decoration (emoji columns and menu icons).

| Field | Type | Required | Description |
| ---------- | -------------------------- | -------- | ----------------------------------------------------------------- |
| `extends` | `"wizard"` \| `"standard"` | Yes | Base theme for fallback messages and decoration |
| `messages` | `object` | No | Nested message overrides. Keys mirror the built-in theme messages |

Template messages (`config.projectFieldAction`, `errors.commit.tooLong`, `init.overwrite`) are written with `{field}`, `{maxCharacters}` and `{fileName}` placeholders.

**Example:**

```json
{
"theme": "custom",
"customTheme": {
"extends": "standard",
"messages": {
"commit": { "intro": "Ready to ship" },
"errors": { "commit": { "tooLong": "Max {maxCharacters} characters" } }
}
}
}
```

Like every field, `customTheme` is replaced as a whole by a higher-priority tier: a project `customTheme` replaces the user one rather than merging with it.

See [Custom Themes](../usage/themes.md#custom-themes) for placeholders, limits and the full key reference.

---

### `maxSubjectLength`

**Type:** `number`
Expand Down Expand Up @@ -268,15 +305,16 @@ To extend the built-ins, include all 11 default types in your array plus your ad

## Validation Rules

| Field | Validation | On invalid value |
| ---------------------- | ---------------------------------------- | --------------------------------------------------------------- |
| `theme` | Must be `"wizard"` or `"standard"` | Ignored, falls back to default |
| `maxSubjectLength` | Must be a number greater than 0 | Ignored, falls back to default |
| `maxScopeLength` | Must be a number greater than 0 | Ignored, falls back to default |
| `editor` | Must be a non-empty string | Ignored, falls back to default |
| `autoAdd` | Must be a boolean | Ignored, falls back to default |
| `showCharacterCounter` | Must be a boolean | Ignored, falls back to default |
| `types` | Array, each entry filtered by type shape | Invalid entries filtered out; empty array falls back to default |
| Field | Validation | On invalid value |
| ---------------------- | ------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `theme` | Must be `"wizard"`, `"standard"` or `"custom"` | Ignored, falls back to default |
| `customTheme` | Object with `extends` of `"wizard"` or `"standard"`; messages must be strings at known keys | Whole object ignored if `extends` is invalid; unknown keys and non-strings dropped; control and bidi-override characters stripped; messages cut to 200 characters |
| `maxSubjectLength` | Must be a number greater than 0 | Ignored, falls back to default |
| `maxScopeLength` | Must be a number greater than 0 | Ignored, falls back to default |
| `editor` | Must be a non-empty string | Ignored, falls back to default |
| `autoAdd` | Must be a boolean | Ignored, falls back to default |
| `showCharacterCounter` | Must be a boolean | Ignored, falls back to default |
| `types` | Array, each entry filtered by type shape | Invalid entries filtered out; empty array falls back to default |

Unknown fields are silently ignored.

Expand All @@ -286,9 +324,15 @@ Unknown fields are silently ignored.

```json
{
// UI theme: "wizard" (default) or "standard"
// UI theme: "wizard" (default), "standard" or "custom"
"theme": "wizard",

// Messages for theme "custom", layered over the "extends" theme
"customTheme": {
"extends": "wizard",
"messages": { "commit": { "intro": "πŸš€ Ready to launch your commit!" } }
},

// Subject line character limit (10-200, default 72)
"maxSubjectLength": 72,

Expand Down
19 changes: 19 additions & 0 deletions docs/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,25 @@ Or edit `~/.merlinrc.json`:

See [Themes](../usage/themes.md) for a comparison of both themes.

### Create a custom theme

Set `theme` to `"custom"` and list only the messages you want to change. Everything else comes from the theme named in `extends`:

```json
{
"theme": "custom",
"customTheme": {
"extends": "wizard",
"messages": {
"commit": { "intro": "πŸš€ Ready to launch your commit!" },
"prompts": { "type": "🎯 Select mission type:" }
}
}
}
```

Put it in the project `.merlinrc.json` to share it with your team. See [Custom Themes](../usage/themes.md#custom-themes) for placeholders, limits and every message key.

### Enable auto-add

When `autoAdd` is true, running `merlin` when no files are staged opens an interactive file picker instead of exiting with an error.
Expand Down
Loading
Loading