Sub-task of #96
Summary
Add a ktavi config set <key> <value> command to update individual config values in the project or global config file. Use dot-notation keys (e.g. ktavi config set ai.textModel gpt-4o-mini). Validate values against the zod schema before writing.
Command signature
ktavi config set <key> <value> [--global]
--global — Update global config at ~/.config/ktavi/config.js instead of project config
Examples
ktavi config set ai.textModel gpt-4o-mini
ktavi config set writing.defaultMode strong
ktavi config set storage.provider cloudinary
ktavi config set markdown.preserveFrontmatterOrder false
ktavi config set image.style "watercolor illustration"
ktavi config set storage.local.outputDir ./out/images --global
Valid keys and value constraints
| Key |
Type |
Valid values |
ai.provider |
enum |
openai |
ai.textModel |
string |
any |
ai.imageModel |
string |
any |
markdown.coverField |
enum |
cover, image, heroImage, ogImage, thumbnail |
markdown.preserveFrontmatterOrder |
boolean |
true, false |
writing.defaultMode |
enum |
light, medium, strong |
image.size |
enum |
1024x1024, 1536x1024, 1792x1024 |
image.style |
string |
any |
storage.provider |
enum |
local, cloudinary |
storage.local.outputDir |
string |
any |
storage.local.publicPathPrefix |
string |
any |
storage.cloudinary.folder |
string |
any |
Implementation steps
1. Export validation from config.ts
Export a validateConfig() function wrapping ktaviConfigSchema.parse() so config set can validate before writing. Keeps the schema encapsulated.
2. Extract serializeConfig to shared utility
Currently lives in configInit.ts. Move to src/utils/configSerializer.ts so both config init and config set can use it.
3. Core logic (configSet.ts)
1. Parse dot-notation key into path segments
2. Coerce string value to correct type (boolean for preserveFrontmatterOrder)
3. Determine target file: --global → ~/.config/ktavi/config.js, else → ktavi.config.ts/.js
4. Load existing config via loadSingleConfigFile (or start with {} if none exists)
5. Set the nested value on the config object
6. Validate the full merged config with validateConfig()
7. Serialize and write back
8. Print confirmation: "Set ai.textModel = gpt-4o-mini in ktavi.config.ts"
Key design decisions
- Value coercion: CLI args are always strings. Coerce
"true"/"false" → boolean for preserveFrontmatterOrder. Everything else stays as string (enums are string-based).
- Unknown key handling: Reject keys not in the schema with a clear error listing valid keys.
- Missing config file: If no config file exists, create one with just the set value. Detect
.ts vs .js by checking for tsconfig.json.
- Validation timing: Validate the complete merged config (defaults + existing + new value) through the zod schema before writing.
- Empty string clears optional fields:
config set ai.imageModel "" removes the field from config.
Error messages
$ ktavi config set writing.defaultMode turbo
Error: Invalid value "turbo" for writing.defaultMode
Valid values: light, medium, strong
$ ktavi config set ai.foo bar
Error: Unknown config key "ai.foo"
Valid keys: ai.provider, ai.textModel, ai.imageModel, writing.defaultMode, ...
$ ktavi config set ai.textModel gpt-4o-mini
✔ Set ai.textModel = "gpt-4o-mini" in ktavi.config.ts
Files to create/modify
| File |
Change |
src/core/config.ts |
Export validateConfig() function |
src/utils/configSerializer.ts |
New — extract serializeConfig from configInit.ts |
src/cli/commands/configInit.ts |
Import serializeConfig from shared utility |
src/cli/commands/configSet.ts |
New — set command logic |
src/cli/commands/config.ts |
Register config set subcommand |
tests/cli/configSet.test.ts |
New — tests |
Test plan
| Case |
Input |
Expected |
| Set string value |
config set ai.textModel gpt-4o-mini |
Config file updated, confirmed |
| Set enum value |
config set writing.defaultMode strong |
Writes strong, validates |
| Set boolean value |
config set markdown.preserveFrontmatterOrder false |
Coerces to boolean, writes |
| Set nested 3-level key |
config set storage.local.outputDir ./out |
Deep-sets correctly |
| Invalid enum value |
config set writing.defaultMode turbo |
Error with valid options listed |
| Invalid key |
config set ai.foo bar |
Error with valid keys listed |
| No config file exists |
config set ai.textModel gpt-4o-mini |
Creates new config file |
| Existing config preserved |
Set one field |
Other fields unchanged |
--global flag |
config set ai.textModel gpt-4o-mini --global |
Writes to global config |
| Overwrite existing value |
Set field with existing value |
Old value replaced |
| Optional field set |
config set image.style "pixel art" |
Writes optional field |
| Optional field clear |
config set ai.imageModel "" |
Removes field from config |
Sub-task of #96
Summary
Add a
ktavi config set <key> <value>command to update individual config values in the project or global config file. Use dot-notation keys (e.g.ktavi config set ai.textModel gpt-4o-mini). Validate values against the zod schema before writing.Command signature
--global— Update global config at~/.config/ktavi/config.jsinstead of project configExamples
Valid keys and value constraints
ai.provideropenaiai.textModelai.imageModelmarkdown.coverFieldcover,image,heroImage,ogImage,thumbnailmarkdown.preserveFrontmatterOrdertrue,falsewriting.defaultModelight,medium,strongimage.size1024x1024,1536x1024,1792x1024image.stylestorage.providerlocal,cloudinarystorage.local.outputDirstorage.local.publicPathPrefixstorage.cloudinary.folderImplementation steps
1. Export validation from
config.tsExport a
validateConfig()function wrappingktaviConfigSchema.parse()soconfig setcan validate before writing. Keeps the schema encapsulated.2. Extract
serializeConfigto shared utilityCurrently lives in
configInit.ts. Move tosrc/utils/configSerializer.tsso bothconfig initandconfig setcan use it.3. Core logic (
configSet.ts)Key design decisions
"true"/"false"→ boolean forpreserveFrontmatterOrder. Everything else stays as string (enums are string-based)..tsvs.jsby checking fortsconfig.json.config set ai.imageModel ""removes the field from config.Error messages
Files to create/modify
src/core/config.tsvalidateConfig()functionsrc/utils/configSerializer.tsserializeConfigfromconfigInit.tssrc/cli/commands/configInit.tsserializeConfigfrom shared utilitysrc/cli/commands/configSet.tssrc/cli/commands/config.tsconfig setsubcommandtests/cli/configSet.test.tsTest plan
config set ai.textModel gpt-4o-miniconfig set writing.defaultMode strongstrong, validatesconfig set markdown.preserveFrontmatterOrder falseconfig set storage.local.outputDir ./outconfig set writing.defaultMode turboconfig set ai.foo barconfig set ai.textModel gpt-4o-mini--globalflagconfig set ai.textModel gpt-4o-mini --globalconfig set image.style "pixel art"config set ai.imageModel ""