Personalizzazione e amministrazioneComandi e azioni
Personalizzazione e amministrazione

Comandi e azioni personalizzati

In cmux.json insegni a cmux le tue abitudini: comandi shell frequenti, workspace con terminali e browser già disposti e azioni personalizzate. Puoi associare queste azioni a scorciatoie, a pulsanti della barra dei tab e alla Command Palette. Mettine uno in .cmux/cmux.json dentro un progetto, in ~/.config/cmux/cmux.json per tutti i progetti, oppure in entrambi.

Dove cmux legge cmux.json

Le voci possono arrivare da un file di progetto, da un file a livello utente o da entrambi. Per i file di progetto, cmux parte dalla directory corrente e controlla anche ogni directory superiore.

  • Progetto: ./.cmux/cmux.json (trovato cercando verso l’alto a partire dalla directory di lavoro; ha la precedenza sul file a livello utente)
  • Fallback locale: ./cmux.json (ancora supportato per i repository esistenti)
  • Livello utente: ~/.config/cmux/cmux.json (condiviso da tutti i progetti; fornisce tutto ciò che un file di progetto lascia indefinito)
Le azioni e i comandi locali sostituiscono le voci globali con lo stesso ID o nome.

Modifica cmux.json, quindi premi Cmd+Shift+, oppure esegui cmux reload-config per applicare le modifiche.

Se una configurazione di progetto o globale contiene un errore di schema, cmux usa la configurazione valida successiva e mostra una riga cmux.json Schema Error in Command Palette. Selezionala per aprire il file di configurazione.

Comandi shell

La voce più semplice è un nome più una stringa command. Selezionandola nella Command Palette, quella stringa viene digitata nel terminale che ha il focus ed eseguita.

cmux.json
{
  "commands": [
    {
      "name": "Format Changed Files",
      "description": "Run the formatter on files that differ from main",
      "keywords": [
        "fmt",
        "prettier",
        "style"
      ],
      "command": "git diff --name-only main -- '*.ts' '*.tsx' | xargs npx prettier --write"
    },
    {
      "name": "Reset Local Database",
      "keywords": [
        "db",
        "wipe"
      ],
      "command": "cd \"$(git rev-parse --show-toplevel)\" && ./scripts/db-reset.sh",
      "confirm": true
    }
  ]
}

Campi di un comando shell

  • name: Etichetta obbligatoria e non vuota che scegli nella Command Palette
  • command: Il testo shell che cmux digita nel terminale attivo ed esegue
  • description: Testo secondario facoltativo mostrato accanto alla voce
  • keywords: Altre parole che devono corrispondere a questa voce quando cerchi nella Command Palette
  • confirm: Impostalo a true per ricevere una richiesta di conferma prima di ogni esecuzione

Un comando shell viene eseguito ovunque si trovi in quel momento il terminale attivo. Se il comando ha bisogno di un punto di partenza prevedibile, inizialo con cd "$(git rev-parse --show-toplevel)" && per spostarti alla radice del repository Git, oppure con cd /some/folder && per vincolarlo a una cartella fissa.

Comandi che aprono un workspace

Se dai a un comando un oggetto workspace al posto di una stringa command, aprirà invece un nuovo workspace, organizzato come un albero di pannelli divisi con terminali e browser. Ogni voce deve avere esattamente uno dei due.

cmux.json
{
  "commands": [
    {
      "name": "API Stack",
      "keywords": [
        "api",
        "server",
        "logs"
      ],
      "restart": "confirm",
      "workspace": {
        "name": "API",
        "cwd": "./services/api",
        "color": "#10b981",
        "env": {
          "PORT": "8080"
        },
        "layout": {
          "direction": "vertical",
          "split": 0.65,
          "children": [
            {
              "direction": "horizontal",
              "children": [
                {
                  "pane": {
                    "surfaces": [
                      {
                        "type": "terminal",
                        "name": "Server",
                        "command": "go run ./cmd/api",
                        "focus": true
                      }
                    ]
                  }
                },
                {
                  "pane": {
                    "surfaces": [
                      {
                        "type": "terminal",
                        "name": "Worker",
                        "command": "go run ./cmd/worker",
                        "env": {
                          "QUEUE": "default"
                        }
                      }
                    ]
                  }
                }
              ]
            },
            {
              "pane": {
                "surfaces": [
                  {
                    "type": "terminal",
                    "name": "Logs",
                    "cwd": "/var/log",
                    "command": "tail -F system.log"
                  },
                  {
                    "type": "browser",
                    "name": "Health",
                    "url": "http://localhost:8080/healthz"
                  }
                ]
              }
            }
          ]
        }
      }
    }
  ]
}

Campi di un oggetto workspace

  • name: Titolo del workspace; se manca, si usa il nome del comando
  • cwd: Directory di base del workspace; il cwd di ogni tab viene risolto rispetto a essa
  • layout: Il nodo radice dell’albero di layout descritto più avanti
  • color: Colore nella barra laterale, come esadecimale #RRGGBB o nome di un colore del workspace
  • env: Environment variables inherited by every shell in the workspace
  • setup: Bootstrap command sent to the workspace's first terminal before that terminal's own command

Quando il workspace è già aperto

Imposta restart nel comando per decidere cosa succede se esiste già un workspace con lo stesso nome:

  • "new": Crea un nuovo workspace (predefinito)
  • "confirm": Chiedi prima, poi sostituiscilo se confermi
  • "recreate": Sostituiscilo subito, senza chiedere
  • "ignore": Passa al workspace esistente

Descrivere un layout

Un layout è un albero. Ogni nodo interno divide la propria area in due e ogni foglia è un pannello che contiene tab.

Nodi split

Uno split divide la propria area in due parti:

  • direction: "horizontal" o "vertical"
  • children: Un array di esattamente due nodi, ciascuno dei quali è un altro split o un pannello
  • split: Posizione del divisore, come frazione dell’area. Il valore predefinito è 0.5; i valori al di fuori dell’intervallo da 0.1 a 0.9 vengono limitati

Nodi pannello

Un pannello è una foglia dell’albero. Il suo array surfaces elenca i tab con cui si apre e deve contenere almeno una voce.

Superfici

Ogni voce nell’array surfaces di un pannello apre un tab, che è un terminale oppure un browser:

  • type: "terminal" o "browser"
  • name: Titolo del tab da mostrare al posto di quello predefinito
  • focus: Impostalo a true per dare il focus della tastiera a questo tab una volta costruito il layout
  • command: Solo terminali: testo shell da eseguire appena il terminale si apre
  • url: Solo browser: la pagina da caricare
  • cwd: Directory iniziale di questo tab, risolta con le regole qui sotto
  • env: Un oggetto con nomi e valori di variabili d’ambiente aggiuntive

Come vengono risolti i valori di cwd

  • ~/path: risolto all’interno della tua cartella Inizio
  • Un percorso che inizia con /: usato così com’è
  • ./subdir: aggiunto in coda alla directory del workspace
  • . o non impostato: eredita la directory del workspace

Struttura del file

Il registro delle azioni è una funzione nightly. Installa la build nightly più recente prima di usare actions, shortcut o ui.surfaceTabBar.buttons.

commands continua a definire comandi shell riutilizzabili e layout di workspace. Le build nightly aggiungono un registro actions. Le azioni sono gli ID pubblici condivisi dalla barra delle schede di superficie, da Command Palette e dalle scorciatoie a livello di azione.

cmux.json
{
  "actions": {
    "cmux.newTerminal": {
      "type": "agent",
      "agent": "claude",
      "title": "Claude Code",
      "subtitle": "Start Claude Code in a new tab"
    },
    "lint": {
      "type": "command",
      "title": "Lint",
      "subtitle": "Run the linter in a fresh tab",
      "keywords": [
        "eslint",
        "check"
      ],
      "command": "npm run lint",
      "shortcut": [
        "cmd+k",
        "cmd+l"
      ],
      "icon": {
        "type": "symbol",
        "name": "checkmark.seal"
      }
    },
    "scratch-dir": {
      "type": "command",
      "title": "Scratch Directory",
      "command": "cd \"$(mktemp -d)\"",
      "target": "currentTerminal",
      "palette": false,
      "icon": {
        "type": "emoji",
        "value": "📝",
        "scale": 0.85
      }
    },
    "api-stack": {
      "type": "workspaceCommand",
      "title": "API Stack",
      "commandName": "API Stack"
    }
  },
  "ui": {
    "surfaceTabBar": {
      "buttons": [
        "cmux.newTerminal",
        "cmux.splitRight",
        "lint",
        {
          "action": "scratch-dir",
          "tooltip": "cd into a new temp folder"
        }
      ]
    }
  },
  "commands": [
    {
      "name": "API Stack",
      "keywords": [
        "api"
      ],
      "workspace": {
        "cwd": "./services/api",
        "layout": {
          "pane": {
            "surfaces": [
              {
                "type": "terminal",
                "name": "Server",
                "command": "go run ./cmd/api"
              }
            ]
          }
        }
      }
    }
  ]
}

Registro azioni nightly

actions associa ID stabili a comportamenti eseguibili. Usa gli ID integrati cmux.newTerminal, cmux.newBrowser, cmux.splitRight e cmux.splitDown per sovrascrivere i valori predefiniti. Usa ID personali per gli strumenti specifici del progetto.

palette è true per impostazione predefinita. Impostalo su false per nascondere un’azione da Command Palette mantenendola disponibile nella barra delle schede di superficie o tramite una scorciatoia. shortcut usa la stessa sintassi delle scorciatoie delle impostazioni, ad esempio cmd+shift+c o ["cmd+k", "cmd+c"].

ui.surfaceTabBar.buttons sostituisce l’elenco predefinito dei pulsanti quando è presente. Ometti un ID integrato per nasconderlo. Le icone usano sempre una forma oggetto: { "type": "symbol", "name": "play.circle" }, { "type": "emoji", "value": "🧪", "scale": 0.9 } oppure { "type": "image", "path": "./icons/codex.svg" }. I percorsi delle immagini sono relativi al file di configurazione. Il scale delle emoji è facoltativo e il valore predefinito è 1. Sono supportati SVG, PDF, PNG, JPEG, GIF, TIFF, BMP, HEIC, HEIF, WebP, AVIF, ICO e ICNS.

Ogni voce pulsante può essere una stringa ID di azione o un oggetto pulsante. Usa un oggetto pulsante quando vuoi la stessa azione con un’etichetta di superficie, un’icona o un tooltip diverso. Il titolo risolto del pulsante viene usato anche come titolo della richiesta di attendibilità.

Inserisci eventuali flag di approvazione o permesso direttamente nella stringa del comando che vuoi davvero eseguire. Il target predefinito dell’azione è newTabInCurrentPane, quindi lo schema comune è aprire una nuova scheda terminale nel pannello corrente e avviare lì Codex, Claude Code o OpenCode.

Le azioni locali del progetto compaiono subito nella barra delle schede di superficie e in Command Palette. La prima esecuzione richiede ancora l’attendibilità. L’attendibilità vale per l’impronta esatta dell’azione, non per il repo. Le icone immagine locali del progetto restano bloccate finché quell’azione non viene considerata attendibile.

Azioni personalizzate e Command Palette

Una voce actions è l’elemento riutilizzabile che cmux esegue. Usa le azioni quando lo stesso comportamento deve essere disponibile da Command Palette, dalla barra delle schede di superficie, dalle scorciatoie o dal menu del pulsante più. Mantieni commands per comandi shell riutilizzabili e layout degli spazi di lavoro. Imposta palette su false quando un’azione deve restare fuori da Command Palette.

Tipi di azione

  • "command": Esegue testo shell in un terminale. Usa target per scegliere il terminale corrente o una nuova scheda nel pannello corrente.
  • "agent": Avvia un agente di programmazione supportato. Oggi supporta codex e claude, con argomenti opzionali.
  • "builtin": Crea un alias per un’azione cmux integrata come cmux.newTerminal, cmux.newBrowser, cmux.splitRight o cmux.splitDown. Azioni integrate di copia: cmux.copyWorkingDirectory copia la directory di lavoro del terminale, cmux.copyProjectRoot copia la radice del suo repository git (o la directory di lavoro al di fuori di un repository) e cmux.copyScreen copia lo schermo visibile. In un workspace SSH, cloud o tmux remoto, cmux.copyProjectRoot copia la directory di lavoro remota, perché cmux non può individuare il repository git sull'altro host.
  • "workspaceCommand": Esegue una definizione di spazio di lavoro con nome da commands. Usalo per layout multipannello, directory di lavoro personalizzate e comandi di avvio.
  • "workspace": Create a workspace from an inline workspace definition, with optional restart behavior. Offered in the new-workspace plus-button menu automatically.
  • "setting": Modifica un’impostazione nel tuo cmux.json globale, ad esempio per alternare un’impostazione true/false o scorrere un elenco di valori. Vedi Azioni sulle impostazioni più sotto.
  • "settingPreset": Applica in un solo passaggio un gruppo di impostazioni con nome definito in settingPresets.

Campi dell’azione

  • title: Titolo della riga in Command Palette, etichetta del pulsante, titolo della voce di menu e titolo della richiesta di attendibilità, salvo override di una voce.
  • subtitle / description: Testo secondario in Command Palette. description è accettato come alias di subtitle.
  • keywords: Termini di ricerca aggiuntivi per Command Palette.
  • palette: Il valore predefinito è true per le azioni personalizzate. Imposta false per nascondere l’azione da Command Palette mantenendola richiamabile altrove.
  • shortcut: Scorciatoia opzionale dell’azione, con la stessa sintassi a tasto singolo o accordo in due passaggi delle scorciatoie delle impostazioni.
  • target: Solo per azioni command e agent. Usa currentTerminal o newTabInCurrentPane.
  • confirm: Chiede conferma prima di eseguire l’azione.
  • newWorkspaceMenu: Set true to offer any action in the new-workspace plus-button menu, or false to hide a workspace action from it.

Comportamento di Command Palette

Command Palette legge il registro delle azioni risolto. Gli ID delle azioni personalizzate vengono aggiunti come righe quando palette non è false. I vecchi commands vengono aggiunti automaticamente come righe personalizzate, a meno che esista già un’azione con lo stesso ID generato. I comandi integrati mantengono le etichette normali, ma sostituire un ID integrato come cmux.newTerminal cambia il comportamento dietro quel punto di ingresso condiviso.

Azioni sulle impostazioni

Un’azione sulle impostazioni indica il path di un’impostazione ed esattamente uno tra set (scrive un valore), toggle: true (inverte un’impostazione true/false), cycle (passa al valore successivo di un elenco, tornando al primo dopo l’ultimo) o unset: true (rimuove la chiave da cmux.json, così si applica il valore della finestra Impostazioni o quello predefinito). toggle e cycle partono dal valore che cmux sta usando, anche se lo hai impostato nella finestra Impostazioni. Un’azione settingPreset applica una voce di settingPresets, un cmux.json parziale i cui oggetti annidati vengono uniti chiave per chiave. cmux verifica ogni modifica rispetto allo schema prima di salvarla, mantiene i tuoi commenti e le altre chiavi e la applica subito. Le azioni sulle impostazioni e i preset vengono eseguiti solo dal tuo cmux.json globale e dai pacchetti a cui fa riferimento; le configurazioni di progetto non possono dichiararli.

~/.config/cmux/cmux.json
{
  "actions": {
    "scroll.cycle": {
      "type": "setting",
      "title": "Cycle Scroll Speed",
      "path": "terminal.scrollSpeed",
      "cycle": [1.0, 1.4, 1.8]
    },
    "editor.wrap": {
      "type": "setting",
      "title": "Toggle Editor Word Wrap",
      "path": "fileEditor.wordWrap",
      "toggle": true
    },
    "sidebar.quiet": {
      "type": "settingPreset",
      "title": "Quiet Sidebar",
      "preset": "sidebar.quiet"
    }
  },
  "settingPresets": {
    "sidebar.quiet": {
      "sidebar": { "showPorts": false, "showPullRequests": false, "showLog": false }
    }
  }
}

Le stesse modifiche funzionano anche da terminale: cmux config set terminal.scrollSpeed 1.4, cmux config toggle fileEditor.wordWrap e cmux config preset sidebar.quiet.

Con confirm: true, cmux chiede conferma prima di salvare e mostra il comando cmux config equivalente. I percorsi vengono divisi a ogni ".", quindi una chiave che contiene a sua volta "." (come una voce workspaceGroups.byCwd per ~/src/app.web) non si può modificare in questo modo. Modificala direttamente in cmux.json.

Azioni personalizzate del pulsante più

Usa ui.newWorkspace.action per sovrascrivere ciò che fa il pulsante più. Usa ui.newWorkspace.contextMenu (o l’alias rightClick) per definire il menu ordinato del clic destro. Le voci di menu possono essere ID di azione, oggetti azione o { "type": "separator" }. Se non impostato, il menu elenca cmux.newWorkspace (⌘N), cmux.newCloudWorkspace (⌘Y), cmux.newCloudMachine (⇧⌘Y), cmux.newTerminal (⌘T), cmux.newBrowser (⇧⌘L), ciascuno con la scorciatoia corrente. Le voci cloud appaiono solo quando Cloud Machines è abilitato. New Cloud Workspace usa la macchina dell’ultimo spazio di lavoro Cloud selezionato, poi la prima nella barra laterale destra; se non c’è una macchina utilizzabile, apre New Cloud Machine.

cmux.json
{
  "actions": {
    "worktree-agents": {
      "type": "workspaceCommand",
      "title": "Agents in New Worktree",
      "commandName": "Agents in New Worktree",
      "icon": {
        "type": "symbol",
        "name": "arrow.triangle.branch"
      }
    }
  },
  "ui": {
    "newWorkspace": {
      "action": "worktree-agents",
      "contextMenu": [
        "worktree-agents",
        {
          "type": "separator"
        },
        {
          "action": "cmux.newWorkspace",
          "title": "Blank Workspace"
        },
        {
          "action": "cmux.newBrowser",
          "title": "Browser"
        }
      ]
    }
  },
  "commands": [
    {
      "name": "Agents in New Worktree",
      "description": "Branch off a new Git worktree, then run Codex and Claude Code in it side by side",
      "workspace": {
        "name": "Worktree",
        "layout": {
          "direction": "vertical",
          "split": 0.7,
          "children": [
            {
              "direction": "horizontal",
              "children": [
                {
                  "pane": {
                    "surfaces": [
                      {
                        "type": "terminal",
                        "name": "Codex",
                        "command": "marker=\"${TMPDIR:-/tmp}/cmux-wt-${CMUX_WORKSPACE_ID:-local}\"; echo \"Waiting for the worktree...\"; until [ -s \"$marker\" ]; do sleep 0.25; done; cd \"$(cat \"$marker\")\" && exec codex --yolo"
                      }
                    ]
                  }
                },
                {
                  "pane": {
                    "surfaces": [
                      {
                        "type": "terminal",
                        "name": "Claude",
                        "command": "marker=\"${TMPDIR:-/tmp}/cmux-wt-${CMUX_WORKSPACE_ID:-local}\"; echo \"Waiting for the worktree...\"; until [ -s \"$marker\" ]; do sleep 0.25; done; cd \"$(cat \"$marker\")\" && exec claude --dangerously-skip-permissions"
                      }
                    ]
                  }
                }
              ]
            },
            {
              "pane": {
                "surfaces": [
                  {
                    "type": "terminal",
                    "name": "Setup",
                    "command": "marker=\"${TMPDIR:-/tmp}/cmux-wt-${CMUX_WORKSPACE_ID:-local}\"; rm -f \"$marker\"; root=$(git rev-parse --show-toplevel) || exit 1; branch=\"wt-$(date +%m%d-%H%M%S)\"; wt=\"$root/../$(basename \"$root\")-$branch\"; git -C \"$root\" worktree add -b \"$branch\" \"$wt\" && printf \"%s\\n\" \"$wt\" > \"$marker\" && cd \"$wt\" && exec \"${SHELL:-/bin/zsh}\" -l",
                    "focus": true
                  }
                ]
              }
            }
          ]
        }
      }
    }
  ]
}

Questo esempio fa sì che il normale clic sul più esegua l’azione worktree-agents. Il comando dello spazio di lavoro da commands usa un terminale di configurazione visibile per creare prima il worktree Git. Codex e Claude partono contemporaneamente, attendono il file di stato specifico dello spazio di lavoro, quindi entrano nella directory creata prima di exec.

Workspace layouts

A workspace layout is stored as a workspace action, embedding a full workspace definition directly in the action so it needs no separate commands entry. It supports the same workspace fields plus setup, a bootstrap command sent to the first terminal before that terminal's own command:

cmux.json
{
  "actions": {
    "pair-review": {
      "type": "workspace",
      "title": "Pair Review",
      "icon": {
        "type": "symbol",
        "name": "person.2.wave.2"
      },
      "restart": "ignore",
      "newWorkspaceMenu": true,
      "workspace": {
        "name": "Review",
        "cwd": "~/src/app",
        "setup": "git pull --ff-only",
        "layout": {
          "direction": "horizontal",
          "split": 0.55,
          "children": [
            {
              "pane": {
                "surfaces": [
                  {
                    "type": "terminal",
                    "name": "Diff",
                    "command": "git log -p -n 5",
                    "focus": true
                  }
                ]
              }
            },
            {
              "pane": {
                "surfaces": [
                  {
                    "type": "terminal",
                    "name": "Codex",
                    "command": "codex"
                  },
                  {
                    "type": "terminal",
                    "name": "Shell"
                  }
                ]
              }
            }
          ]
        }
      }
    }
  }
}

Workspace layouts appear in the plus-button right-click menu automatically. Set newWorkspaceMenu to false to hide one, or to true on any other action to add it to the menu.

You can also create these without writing JSON: right-click the plus button and choose Save Workspace as Layout to capture the current workspace's splits, directories, running agents, and browser tabs into ~/.config/cmux/cmux.json. They are stored as entries in the actions block. Customize Workspace Layouts opens that file in your editor.

Default for new workspaces

Use the Default for New Workspace submenu to pick which saved workspace layout plain New Workspace should run, or choose None to return to a blank terminal. The save dialog also has a Use as default for new workspaces checkbox. The setting is ui.newWorkspace.action; a project-local .cmux/cmux.json value wins over ~/.config/cmux/cmux.json.

Esempio completo

cmux.json
{
  "actions": {
    "cmux.newTerminal": {
      "type": "command",
      "title": "Codex",
      "command": "codex --yolo",
      "shortcut": "cmd+t",
      "icon": {
        "type": "image",
        "path": "./icons/codex.svg"
      }
    },
    "review": {
      "type": "agent",
      "agent": "claude",
      "args": "--permission-mode plan",
      "title": "Plan with Claude",
      "shortcut": "cmd+shift+p",
      "icon": {
        "type": "symbol",
        "name": "list.bullet.clipboard"
      }
    },
    "storybook": {
      "type": "workspaceCommand",
      "commandName": "Storybook",
      "newWorkspaceMenu": true
    },
    "typecheck": {
      "type": "command",
      "title": "Typecheck",
      "command": "npx tsc --noEmit",
      "target": "newTabInCurrentPane",
      "confirm": false,
      "icon": {
        "type": "emoji",
        "value": "🔎"
      }
    }
  },
  "ui": {
    "surfaceTabBar": {
      "buttons": [
        "cmux.newTerminal",
        "review",
        {
          "action": "typecheck",
          "title": "tsc"
        },
        "cmux.newBrowser",
        "cmux.splitDown"
      ]
    },
    "newWorkspace": {
      "contextMenu": [
        "storybook",
        {
          "type": "separator"
        },
        "cmux.newTerminal",
        "cmux.newBrowser"
      ]
    }
  },
  "commands": [
    {
      "name": "Storybook",
      "description": "Component explorer with a live preview beside it",
      "keywords": [
        "ui",
        "components",
        "stories"
      ],
      "workspace": {
        "name": "Storybook",
        "cwd": "./packages/ui",
        "color": "#a855f7",
        "layout": {
          "direction": "horizontal",
          "split": 0.4,
          "children": [
            {
              "direction": "vertical",
              "split": 0.5,
              "children": [
                {
                  "pane": {
                    "surfaces": [
                      {
                        "type": "terminal",
                        "name": "Storybook",
                        "command": "npx storybook dev -p 6006 --no-open",
                        "focus": true
                      }
                    ]
                  }
                },
                {
                  "pane": {
                    "surfaces": [
                      {
                        "type": "terminal",
                        "name": "Tests",
                        "command": "npx vitest --watch",
                        "env": {
                          "CI": "false"
                        }
                      }
                    ]
                  }
                }
              ]
            },
            {
              "pane": {
                "surfaces": [
                  {
                    "type": "browser",
                    "name": "Preview",
                    "url": "http://localhost:6006"
                  }
                ]
              }
            }
          ]
        }
      }
    },
    {
      "name": "Install Dependencies",
      "keywords": [
        "install",
        "deps"
      ],
      "command": "npm ci",
      "confirm": true
    },
    {
      "name": "Open Changelog",
      "keywords": [
        "changes",
        "release"
      ],
      "command": "${EDITOR:-vi} CHANGELOG.md"
    }
  ]
}