Skip to content

Commit d722553

Browse files
committed
Add verbose file change reporting
1 parent fb6e588 commit d722553

6 files changed

Lines changed: 126 additions & 20 deletions

File tree

‎README.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ installs Vale when needed and configures the Elastic rules. Use
5555
are explicit because the upstream installers can install system tools and may
5656
ask before replacing existing local configuration.
5757

58+
See the [command and managed-locations reference](docs/reference.md) for
59+
`--verbose`, configuration paths, and optional-tool locations.
60+
5861
## What it manages
5962

6063
- Canonical skills at `~/.agents/skills`; Claude Code and Cursor receive links

‎cmd/elastic-docs-utils/main.go‎

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ import (
3737
var Version = "dev"
3838

3939
type globalFlags struct {
40-
color string
40+
color string
41+
verbose bool
4142
}
4243

4344
func main() {
@@ -54,6 +55,7 @@ func run(args []string) error {
5455
return err
5556
}
5657
renderer := ui.New(ui.ColorMode(global.color), os.Stdout, os.Stderr)
58+
renderer.SetVerbose(global.verbose)
5759
if len(args) == 0 {
5860
return commandInstall(renderer, nil)
5961
}
@@ -89,6 +91,11 @@ func run(args []string) error {
8991
func parseGlobal(args []string, flags *globalFlags) ([]string, error) {
9092
remaining := make([]string, 0, len(args))
9193
for len(args) > 0 {
94+
if args[0] == "--verbose" {
95+
flags.verbose = true
96+
args = args[1:]
97+
continue
98+
}
9299
if !strings.HasPrefix(args[0], "--color") {
93100
remaining, args = append(remaining, args[0]), args[1:]
94101
continue
@@ -153,6 +160,9 @@ func commandInstall(r *ui.Renderer, args []string) error {
153160
if err := state.SavePreferences(state.Preferences{Hosts: hosts.Strings(ids), Internal: *internal}); err != nil {
154161
return err
155162
}
163+
if path, err := paths.PreferencesPath(); err == nil {
164+
r.Verbose("Updated preferences: %s", path)
165+
}
156166
if err := refreshUpdates(r); err != nil {
157167
return err
158168
}
@@ -183,12 +193,14 @@ func installOptionalTools(r *ui.Renderer, vale, docsBuilder, dryRun bool) error
183193
}
184194
if vale {
185195
r.Info("Installing Vale and Elastic Vale rules.")
196+
r.Verbose("Runs the upstream Elastic Vale Rules installer; it reports the Vale binary, configuration, and rule paths it edits.")
186197
if err := bootstrap.InstallVale(); err != nil {
187198
return fmt.Errorf("install Vale and Elastic Vale rules: %w", err)
188199
}
189200
}
190201
if docsBuilder {
191202
r.Info("Installing docs-builder.")
203+
r.Verbose("Runs the upstream docs-builder installer; it reports the binary path it edits.")
192204
if err := bootstrap.InstallDocsBuilder(); err != nil {
193205
return fmt.Errorf("install docs-builder: %w", err)
194206
}
@@ -469,27 +481,44 @@ func synchronize(ids []hosts.ID, internal, dryRun, force bool, r *ui.Renderer) e
469481
}
470482
if dryRun {
471483
r.Info("Would refresh public%s skills in ~/.agents/skills.", map[bool]string{true: " and internal", false: ""}[internal])
484+
if root, err := paths.CanonicalSkillsDir(); err == nil {
485+
r.Verbose("Would refresh managed skills beneath: %s", root)
486+
}
472487
} else {
473488
r.Success("Installed %d managed skills.", len(skillResult.Installed))
489+
for _, path := range skillResult.Files {
490+
r.Verbose("Updated managed skill or discovery link: %s", path)
491+
}
474492
}
475493
r.Section("Synchronizing host adapters")
476494
adapterResult, err := adapters.Sync(ids, internal, dryRun, force)
477495
if err != nil {
478496
return err
479497
}
480498
if dryRun {
499+
for name, host := range adapterResult.Hosts {
500+
for _, path := range host.Files {
501+
r.Verbose("Would update %s configuration: %s", name, path)
502+
}
503+
}
481504
r.Info("Would configure %d selected host adapters.", len(ids))
482505
return nil
483506
}
484507
for name, host := range adapterResult.Hosts {
485508
s.Hosts[name] = host
509+
for _, path := range host.Files {
510+
r.Verbose("Updated %s configuration: %s", name, path)
511+
}
486512
}
487513
for name, record := range skillResult.Records {
488514
s.Skills[name] = record
489515
}
490516
if err := state.Save(s); err != nil {
491517
return err
492518
}
519+
if path, err := paths.StatePath(); err == nil {
520+
r.Verbose("Updated managed state: %s", path)
521+
}
493522
for _, warning := range adapterResult.Warnings {
494523
r.Warn("%s", warning)
495524
}
@@ -529,5 +558,6 @@ Commands:
529558
530559
Global options:
531560
--color auto|always|never
561+
--verbose Show managed file locations that are changed
532562
`)
533563
}

‎docs/reference.md‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Elastic Docs Utils reference
2+
3+
## Global options
4+
5+
| Option | Meaning |
6+
|---|---|
7+
| `--color auto\|always\|never` | Control ANSI color output. |
8+
| `--verbose` | Print every Elastic Docs Utils-managed file or directory changed by a mutating command. It prints paths only, never configuration values or secrets. |
9+
10+
Global options can appear before or after the command:
11+
12+
```bash
13+
elastic-docs-utils --verbose install --with-vale
14+
elastic-docs-utils install --verbose --with-docs-builder
15+
```
16+
17+
## Managed locations
18+
19+
| Purpose | Location |
20+
|---|---|
21+
| Preferences | macOS/Linux: OS config directory under `elastic/docs-utils/config.json`; Windows: `%AppData%\elastic\docs-utils\config.json` |
22+
| Managed state | macOS/Linux: OS config directory under `elastic/docs-utils/state.json`; Windows: `%AppData%\elastic\docs-utils\state.json` |
23+
| Update cache | OS cache directory under `elastic/docs-utils/update-status.json` |
24+
| Shared skills | `~/.agents/skills/<skill>` |
25+
| Claude Code MCP | `~/.claude.json` |
26+
| Claude Code session hook | `~/.claude/settings.json` |
27+
| Codex MCP | `~/.codex/config.toml` |
28+
| Cursor CLI MCP | `~/.cursor/mcp.json` |
29+
| OpenCode MCP | macOS/Linux: `~/.config/opencode/opencode.json`; Windows: the OS config directory under `opencode/opencode.json` |
30+
31+
Claude Code and Cursor CLI receive skill discovery links below their respective
32+
`~/.claude/skills` and `~/.cursor/skills` directories. Existing non-Elastic
33+
configuration is retained unless `--force` is explicitly supplied for a
34+
conflicting Elastic Docs MCP entry.
35+
36+
## Optional documentation tools
37+
38+
`install --with-vale` runs the maintained Elastic Vale Rules platform
39+
installer. It may install the Vale binary and manages the following locations:
40+
41+
| Platform | Vale configuration | Elastic styles |
42+
|---|---|---|
43+
| macOS | `~/Library/Application Support/vale/.vale.ini` | `~/Library/Application Support/vale/styles/Elastic` |
44+
| Linux | `~/.config/vale/.vale.ini` | `~/.local/share/vale/styles/Elastic` (or XDG overrides) |
45+
| Windows | `%LOCALAPPDATA%\vale\.vale.ini` | `%LOCALAPPDATA%\vale\styles\Elastic` |
46+
47+
`install --with-docs-builder` runs the maintained Docs Builder installer. Its
48+
installation location is chosen by that upstream script and is printed by the
49+
script while it runs.

‎internal/adapters/adapters.go‎

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ func Sync(targets []hosts.ID, internal, dryRun, force bool) (Result, error) {
114114
case hosts.Claude:
115115
files, err = addClaude(internal, dryRun)
116116
case hosts.Codex:
117-
err = addCodex(internal, dryRun)
117+
files, err = addCodex(internal, dryRun)
118118
case hosts.Cursor:
119119
files, err = writeCursor(internal, dryRun, force)
120120
case hosts.OpenCode:
@@ -185,6 +185,10 @@ func servers(internal bool) []struct{ name, url string } {
185185
}
186186

187187
func addClaude(internal, dryRun bool) ([]string, error) {
188+
home, err := os.UserHomeDir()
189+
if err != nil {
190+
return nil, err
191+
}
188192
for _, server := range servers(internal) {
189193
if dryRun {
190194
continue
@@ -193,29 +197,30 @@ func addClaude(internal, dryRun bool) ([]string, error) {
193197
return nil, err
194198
}
195199
}
196-
path, err := claudeHookPath()
197-
if err != nil {
198-
return nil, err
199-
}
200+
path := filepath.Join(home, ".claude", "settings.json")
200201
if dryRun {
201-
return []string{path}, nil
202+
return []string{filepath.Join(home, ".claude.json"), path}, nil
202203
}
203204
if err := writeClaudeHook(path); err != nil {
204205
return nil, err
205206
}
206-
return []string{path}, nil
207+
return []string{filepath.Join(home, ".claude.json"), path}, nil
207208
}
208209

209-
func addCodex(internal, dryRun bool) error {
210+
func addCodex(internal, dryRun bool) ([]string, error) {
210211
for _, server := range servers(internal) {
211212
if dryRun {
212213
continue
213214
}
214215
if err := runAdd("codex", "mcp", "add", server.name, "--url", server.url); err != nil {
215-
return err
216+
return nil, err
216217
}
217218
}
218-
return nil
219+
home, err := os.UserHomeDir()
220+
if err != nil {
221+
return nil, err
222+
}
223+
return []string{filepath.Join(home, ".codex", "config.toml")}, nil
219224
}
220225

221226
func runAdd(command string, args ...string) error {

‎internal/skills/skills.go‎

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ type Result struct {
4141
Installed []string
4242
Linked []string
4343
Records map[string]state.SkillState
44+
Files []string
4445
}
4546

4647
// Sync fetches enabled catalogs, installs all skills into ~/.agents/skills,
@@ -79,6 +80,7 @@ func Sync(targets []hosts.ID, internal, dryRun bool) (Result, error) {
7980
}
8081
result.Installed = append(result.Installed, name)
8182
result.Records[name] = state.SkillState{Source: repo, Commit: commit}
83+
result.Files = append(result.Files, destination)
8284
}
8385
if err := os.RemoveAll(staging); err != nil {
8486
return result, err
@@ -93,10 +95,12 @@ func Sync(targets []hosts.ID, internal, dryRun bool) (Result, error) {
9395
}
9496
for _, id := range targets {
9597
for _, target := range linkRoots(id) {
96-
if err := linkAll(root, target); err != nil {
98+
links, err := linkAll(root, target)
99+
if err != nil {
97100
return result, err
98101
}
99102
result.Linked = append(result.Linked, string(id))
103+
result.Files = append(result.Files, links...)
100104
}
101105
}
102106
return result, nil
@@ -255,14 +259,15 @@ func linkRoots(id hosts.ID) []string {
255259
}
256260
}
257261

258-
func linkAll(source, target string) error {
262+
func linkAll(source, target string) ([]string, error) {
259263
if err := os.MkdirAll(target, 0o755); err != nil {
260-
return err
264+
return nil, err
261265
}
262266
entries, err := os.ReadDir(source)
263267
if err != nil {
264-
return err
268+
return nil, err
265269
}
270+
links := []string{}
266271
for _, entry := range entries {
267272
if !entry.IsDir() {
268273
continue
@@ -273,11 +278,12 @@ func linkAll(source, target string) error {
273278
}
274279
if err := os.Symlink(filepath.Join(source, entry.Name()), link); err != nil {
275280
if err := copyDir(filepath.Join(source, entry.Name()), link); err != nil {
276-
return err
281+
return nil, err
277282
}
278283
}
284+
links = append(links, link)
279285
}
280-
return nil
286+
return links, nil
281287
}
282288

283289
// RemoveOwned deletes only skill directories carrying this product's marker.

‎internal/ui/ui.go‎

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,15 @@ const (
3535
// Renderer renders human-readable command output. JSON-producing callers do
3636
// not use this type.
3737
type Renderer struct {
38-
out io.Writer
39-
err io.Writer
40-
color bool
38+
out io.Writer
39+
err io.Writer
40+
color bool
41+
verbose bool
4142
}
4243

44+
// SetVerbose enables diagnostic file-change output for mutating commands.
45+
func (r *Renderer) SetVerbose(enabled bool) { r.verbose = enabled }
46+
4347
// New creates a renderer. ColorAuto honors NO_COLOR, TERM=dumb, and whether
4448
// stdout is an interactive terminal.
4549
func New(mode ColorMode, out, errOut io.Writer) *Renderer {
@@ -120,6 +124,15 @@ func (r *Renderer) Info(format string, args ...any) {
120124
fmt.Fprintf(r.out, "%s %s\n", r.paint("36;1", "[INFO]"), fmt.Sprintf(format, args...))
121125
}
122126

127+
// Verbose describes an owned file or configuration location changed by a
128+
// command. It never prints secrets or configuration contents.
129+
func (r *Renderer) Verbose(format string, args ...any) {
130+
if !r.verbose {
131+
return
132+
}
133+
fmt.Fprintf(r.out, "%s %s\n", r.paint("90", "[VERBOSE]"), fmt.Sprintf(format, args...))
134+
}
135+
123136
func (r *Renderer) Success(format string, args ...any) {
124137
fmt.Fprintf(r.out, "%s %s\n", r.paint("32;1", "[OK]"), fmt.Sprintf(format, args...))
125138
}

0 commit comments

Comments
 (0)