Skip to content

Commit 971f8ca

Browse files
authored
feat(cli): add openspec config command for global configuration management (#382)
* feat(cli): add openspec config command for global configuration management Implements the `openspec config` command with subcommands: - `path`: Show config file location - `list [--json]`: Show all current settings - `get <key>`: Get a specific value (raw output for scripting) - `set <key> <value> [--string]`: Set a value with auto type coercion - `unset <key>`: Remove a key (revert to default) - `reset --all [-y]`: Reset configuration to defaults - `edit`: Open config in $EDITOR/$VISUAL Key features: - Dot notation for nested key access (e.g., featureFlags.someFlag) - Auto type coercion (true/false → boolean, numbers → number) - --string flag to force string storage - Zod schema validation with unknown field passthrough - Reserved --scope flag for future project-local config - Windows-compatible editor spawning with proper path quoting - Shell completion registry integration * test(config): add additional unit tests for validation and coercion - Add tests for unknown fields with various types - Add test to verify error message path for featureFlags - Add test for number values rejection in featureFlags - Add config set simulation tests to verify full coerce → set → validate flow * fix(config): avoid shell parsing in config edit to handle paths with spaces Use spawn with shell: false and pass configPath as an argument instead of building a shell command string. This correctly handles spaces in both the EDITOR path and config file path on all platforms. * chore(openspec): archive add-config-command and create cli-config spec Move completed change to archive and apply spec deltas to create the cli-config specification documenting the config command interface. * Validate config keys on set
1 parent 68e0a7e commit 971f8ca

File tree

12 files changed

+1660
-39
lines changed

12 files changed

+1660
-39
lines changed

openspec/changes/add-config-command/proposal.md

Lines changed: 0 additions & 39 deletions
This file was deleted.
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
## Context
2+
3+
The `global-config` spec defines how OpenSpec reads/writes `config.json`, but users currently must edit it by hand. This command provides a CLI interface to that config.
4+
5+
## Goals / Non-Goals
6+
7+
**Goals:**
8+
- Provide a discoverable CLI for config management
9+
- Support scripting with machine-readable output
10+
- Validate config changes with zod schema
11+
- Handle nested keys gracefully
12+
13+
**Non-Goals:**
14+
- Project-local config (reserved for future via `--scope` flag)
15+
- Complex queries (JSONPath, filtering)
16+
- Config file format migration
17+
18+
## Decisions
19+
20+
### Key Naming: camelCase with Dot Notation
21+
22+
**Decision:** Keys use camelCase matching the JSON structure, with dot notation for nesting.
23+
24+
**Rationale:**
25+
- Matches the actual JSON keys (no translation layer)
26+
- Dot notation is intuitive and widely used (lodash, jq, kubectl)
27+
- Avoids complexity of supporting multiple casing styles
28+
29+
**Examples:**
30+
```bash
31+
openspec config get featureFlags # Returns object
32+
openspec config get featureFlags.experimental # Returns nested value
33+
openspec config set featureFlags.newFlag true
34+
```
35+
36+
### Type Coercion: Auto-detect with `--string` Override
37+
38+
**Decision:** Parse values automatically; provide `--string` flag to force string storage.
39+
40+
**Rationale:**
41+
- Most intuitive for common cases (`true`, `false`, `123`)
42+
- Explicit override for edge cases (storing literal string "true")
43+
- Follows npm/yarn config patterns
44+
45+
**Coercion rules:**
46+
| Input | Stored As |
47+
|-------|-----------|
48+
| `true`, `false` | boolean |
49+
| Numeric string (`123`, `3.14`) | number |
50+
| Everything else | string |
51+
| Any value with `--string` | string |
52+
53+
### Output Format: Raw by Default
54+
55+
**Decision:** `get` prints raw value only. `list` prints YAML-like format by default, JSON with `--json`.
56+
57+
**Rationale:**
58+
- Raw output enables piping: `VAR=$(openspec config get key)`
59+
- YAML-like is human-readable for inspection
60+
- JSON for automation/scripting
61+
62+
### Schema Validation: Zod with Unknown Field Passthrough
63+
64+
**Decision:** Use zod for validation but preserve unknown fields per `global-config` spec.
65+
66+
**Rationale:**
67+
- Type safety for known fields
68+
- Forward compatibility (old CLI doesn't break new config)
69+
- Follows existing `global-config` spec requirement
70+
71+
### Reserved Flag: `--scope`
72+
73+
**Decision:** Reserve `--scope global|project` but only implement `global` initially.
74+
75+
**Rationale:**
76+
- Avoids breaking change if project-local config is added later
77+
- Clear error message if someone tries `--scope project`
78+
79+
## Risks / Trade-offs
80+
81+
| Risk | Mitigation |
82+
|------|------------|
83+
| Dot notation conflicts with keys containing dots | Rare in practice; document limitation |
84+
| Type coercion surprises | `--string` escape hatch; document rules |
85+
| $EDITOR not set | Check and provide helpful error message |
86+
87+
## Open Questions
88+
89+
None - design is straightforward.
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
## Why
2+
3+
Users need a way to view and modify their global OpenSpec settings without manually editing JSON files. The `global-config` spec provides the foundation, but there's no user-facing interface to interact with the config. A dedicated `openspec config` command provides discoverability and ease of use.
4+
5+
## What Changes
6+
7+
Add `openspec config` subcommand with the following operations:
8+
9+
```bash
10+
openspec config path # Show config file location
11+
openspec config list [--json] # Show all current settings
12+
openspec config get <key> # Get a specific value (raw, scriptable)
13+
openspec config set <key> <value> [--string] # Set a value (auto-coerce types)
14+
openspec config unset <key> # Remove a key (revert to default)
15+
openspec config reset --all [-y] # Reset everything to defaults
16+
openspec config edit # Open config in $EDITOR
17+
```
18+
19+
**Key design decisions:**
20+
- **Key naming**: Use camelCase to match JSON structure (e.g., `featureFlags.someFlag`)
21+
- **Nested keys**: Support dot notation for nested access
22+
- **Type coercion**: Auto-detect types by default; `--string` flag forces string storage
23+
- **Scriptable output**: `get` prints raw value only (no labels) for easy piping
24+
- **Zod validation**: Use zod for config schema validation and type safety
25+
- **Future-proofing**: Reserve `--scope global|project` flag for potential project-local config
26+
27+
**Example usage:**
28+
```bash
29+
$ openspec config path
30+
/Users/me/.config/openspec/config.json
31+
32+
$ openspec config list
33+
featureFlags: {}
34+
35+
$ openspec config set featureFlags.enableTelemetry false
36+
Set featureFlags.enableTelemetry = false
37+
38+
$ openspec config get featureFlags.enableTelemetry
39+
false
40+
41+
$ openspec config list --json
42+
{
43+
"featureFlags": {}
44+
}
45+
46+
$ openspec config unset featureFlags.enableTelemetry
47+
Unset featureFlags.enableTelemetry (reverted to default)
48+
49+
$ openspec config edit
50+
# Opens $EDITOR with config.json
51+
```
52+
53+
## Impact
54+
55+
- Affected specs: New `cli-config` capability
56+
- Affected code:
57+
- New `src/commands/config.ts`
58+
- New `src/core/config-schema.ts` (zod schema)
59+
- Update CLI entry point to register config command
60+
- Dependencies: Requires `global-config` spec (already implemented)

0 commit comments

Comments
 (0)