feat: add Trae runtime install support (#1566)

* feat: add Trae runtime install support

- Add Trae as a supported runtime in bin/install.js
- Update README and ARCHITECTURE documentation for Trae support
- Add trae-install.test.cjs test file
- Update multi-runtime-select tests for Trae compatibility

* feat(trae): add TRAE_CONFIG_DIR environment variable support

Add support for TRAE_CONFIG_DIR environment variable as an additional way to specify the config directory for Trae runtime, following the same precedence pattern as other runtimes.

* fix(trae): improve slash command conversion and subagent type mapping

Update the slash command regex pattern to properly match and convert command names. Change subagent type mapping from "general-purpose" to "general_purpose_task" to match Trae's conventions. Also add comprehensive tests for Trae uninstall cleanup behavior.

* docs: add Trae and Windsurf to supported runtimes in translations

Update Korean, Japanese, and Portuguese README files to include Trae and Windsurf as supported runtimes in the documentation. Add installation and uninstallation instructions for Trae.

* fix: update runtime selection logic and path replacements

- Change 'All' shortcut from option 11 to 12 to accommodate new runtime
- Update path replacement regex to handle gsd- prefix more precisely
- Adjust test cases to reflect new runtime selection numbering
- Add configDir to trae install options for proper path resolution

* test(trae-install): add tests for getGlobalDir function

Add test cases to verify behavior of getGlobalDir function with different configurations:
- Default directory when no env var or explicit dir is provided
- Explicit directory takes priority
- Respects TRAE_CONFIG_DIR env var
- Priority of explicit dir over env var
- Compatibility with other runtimes
This commit is contained in:
Elwin
2026-04-04 20:13:15 +08:00
committed by GitHub
parent f26e1e1141
commit 66368a42d9
9 changed files with 594 additions and 117 deletions

View File

@@ -4,7 +4,7 @@
[English](README.md) · [Português](README.pt-BR.md) · [简体中文](README.zh-CN.md) · **日本語**
**Claude Code、OpenCode、Gemini CLI、Kilo、Codex、Copilot、Cursor、Windsurf、Antigravity、Augment向けの軽量かつ強力なメタプロンプティング、コンテキストエンジニアリング、仕様駆動開発システム。**
**Claude Code、OpenCode、Gemini CLI、Kilo、Codex、Copilot、Cursor、Windsurf、Antigravity、Augment、Trae向けの軽量かつ強力なメタプロンプティング、コンテキストエンジニアリング、仕様駆動開発システム。**
**コンテキストロット(Claudeがコンテキストウィンドウを消費するにつれ品質が劣化する現象)を解決します。**
@@ -86,7 +86,7 @@ npx get-shit-done-cc@latest
```
インストーラーが以下の選択を求めます:
1. **ランタイム** — Claude Code、OpenCode、Gemini、Kilo、Codex、Copilot、Cursor、Antigravity、またはすべて(インタラクティブ複数選択 — 1回のインストールセッションで複数のランタイムを選択可能)
1. **ランタイム** — Claude Code、OpenCode、Gemini、Kilo、Codex、Copilot、Cursor、Windsurf、Antigravity、Trae、またはすべて(インタラクティブ複数選択 — 1回のインストールセッションで複数のランタイムを選択可能)
2. **インストール先** — グローバル(全プロジェクト)またはローカル(現在のプロジェクトのみ)
確認方法:
@@ -96,6 +96,7 @@ npx get-shit-done-cc@latest
- Codex: `$gsd-help`
- Copilot: `/gsd-help`
- Antigravity: `/gsd-help`
- Trae: `/gsd-help`
> [!NOTE]
> Codexのインストールでは、カスタムプロンプトではなくスキル(`skills/gsd-*/SKILL.md`)を使用します。
@@ -142,12 +143,16 @@ npx get-shit-done-cc --cursor --local # ./.cursor/ にインストール
npx get-shit-done-cc --antigravity --global # ~/.gemini/antigravity/ にインストール
npx get-shit-done-cc --antigravity --local # ./.agent/ にインストール
# Trae(ByteDance、スキルファースト)
npx get-shit-done-cc --trae --global # ~/.trae/ にインストール
npx get-shit-done-cc --trae --local # ./.trae/ にインストール
# 全ランタイム
npx get-shit-done-cc --all --global # すべてのディレクトリにインストール
```
`--global`(`-g`)または `--local`(`-l`)でインストール先の質問をスキップできます。
`--claude`、`--opencode`、`--gemini`、`--kilo`、`--codex`、`--copilot`、`--cursor`、`--antigravity`、または `--all` でランタイムの質問をスキップできます。
`--claude`、`--opencode`、`--gemini`、`--kilo`、`--codex`、`--copilot`、`--cursor`、`--windsurf`、`--antigravity`、`--trae`、または `--all` でランタイムの質問をスキップできます。
</details>
@@ -791,6 +796,7 @@ npx get-shit-done-cc --codex --global --uninstall
npx get-shit-done-cc --copilot --global --uninstall
npx get-shit-done-cc --cursor --global --uninstall
npx get-shit-done-cc --antigravity --global --uninstall
npx get-shit-done-cc --trae --global --uninstall
# ローカルインストール(現在のプロジェクト)
npx get-shit-done-cc --claude --local --uninstall
@@ -801,6 +807,7 @@ npx get-shit-done-cc --codex --local --uninstall
npx get-shit-done-cc --copilot --local --uninstall
npx get-shit-done-cc --cursor --local --uninstall
npx get-shit-done-cc --antigravity --local --uninstall
npx get-shit-done-cc --trae --local --uninstall
```
これにより、他の設定を保持しながら、すべてのGSDコマンド、エージェント、フック、設定が削除されます。

View File

@@ -4,7 +4,7 @@
[English](README.md) · [Português](README.pt-BR.md) · [简体中文](README.zh-CN.md) · [日本語](README.ja-JP.md) · **한국어**
**Claude Code, OpenCode, Gemini CLI, Kilo, Codex, Copilot, Cursor, Windsurf, Antigravity, Augment를 위한 가볍고 강력한 메타 프롬프팅, 컨텍스트 엔지니어링, 스펙 기반 개발 시스템.**
**Claude Code, OpenCode, Gemini CLI, Kilo, Codex, Copilot, Cursor, Windsurf, Antigravity, Augment, Trae를 위한 가볍고 강력한 메타 프롬프팅, 컨텍스트 엔지니어링, 스펙 기반 개발 시스템.**
**컨텍스트 rot를 해결합니다 — Claude의 컨텍스트 창이 채워질수록 품질이 저하되는 문제.**
@@ -84,7 +84,7 @@ npx get-shit-done-cc@latest
```
설치 중에 다음을 선택합니다:
1. **런타임** — Claude Code, OpenCode, Gemini, Kilo, Codex, Copilot, Cursor, Antigravity, 또는 전체 (대화형 다중 선택 — 한 번에 여러 런타임 선택 가능)
1. **런타임** — Claude Code, OpenCode, Gemini, Kilo, Codex, Copilot, Cursor, Windsurf, Antigravity, Trae, 또는 전체 (대화형 다중 선택 — 한 번에 여러 런타임 선택 가능)
2. **위치** — 전역 (모든 프로젝트) 또는 로컬 (현재 프로젝트만)
설치가 됐는지 확인하려면:
@@ -94,6 +94,7 @@ npx get-shit-done-cc@latest
- Codex: `$gsd-help`
- Copilot: `/gsd-help`
- Antigravity: `/gsd-help`
- Trae: `/gsd-help`
> [!NOTE]
> Codex 설치는 커스텀 프롬프트 대신 스킬(`skills/gsd-*/SKILL.md`)을 사용합니다.
@@ -140,12 +141,16 @@ npx get-shit-done-cc --cursor --local # ./.cursor/에 설치
npx get-shit-done-cc --antigravity --global # ~/.gemini/antigravity/에 설치
npx get-shit-done-cc --antigravity --local # ./.agent/에 설치
# Trae (ByteDance, 스킬 우선)
npx get-shit-done-cc --trae --global # ~/.trae/에 설치
npx get-shit-done-cc --trae --local # ./.trae/에 설치
# 전체 런타임
npx get-shit-done-cc --all --global # 모든 디렉터리에 설치
```
위치 프롬프트 건너뛰기: `--global` (`-g`) 또는 `--local` (`-l`).
런타임 프롬프트 건너뛰기: `--claude`, `--opencode`, `--gemini`, `--kilo`, `--codex`, `--copilot`, `--cursor`, `--antigravity`, 또는 `--all`.
런타임 프롬프트 건너뛰기: `--claude`, `--opencode`, `--gemini`, `--kilo`, `--codex`, `--copilot`, `--cursor`, `--windsurf`, `--antigravity`, `--trae`, 또는 `--all`.
</details>
@@ -780,6 +785,7 @@ npx get-shit-done-cc --codex --global --uninstall
npx get-shit-done-cc --copilot --global --uninstall
npx get-shit-done-cc --cursor --global --uninstall
npx get-shit-done-cc --antigravity --global --uninstall
npx get-shit-done-cc --trae --global --uninstall
# 로컬 설치 (현재 프로젝트)
npx get-shit-done-cc --claude --local --uninstall
@@ -790,6 +796,7 @@ npx get-shit-done-cc --codex --local --uninstall
npx get-shit-done-cc --copilot --local --uninstall
npx get-shit-done-cc --cursor --local --uninstall
npx get-shit-done-cc --antigravity --local --uninstall
npx get-shit-done-cc --trae --local --uninstall
```
다른 설정은 그대로 유지하면서 GSD의 모든 명령어, 에이전트, 훅, 설정을 제거합니다.

View File

@@ -92,12 +92,12 @@ npx get-shit-done-cc@latest
```
The installer prompts you to choose:
1. **Runtime** — Claude Code, OpenCode, Gemini, Kilo, Codex, Copilot, Cursor, Windsurf, Antigravity, Augment, or all (interactive multi-select — pick multiple runtimes in a single install session)
1. **Runtime** — Claude Code, OpenCode, Gemini, Kilo, Codex, Copilot, Cursor, Windsurf, Antigravity, Augment, Trae, or all (interactive multi-select — pick multiple runtimes in a single install session)
2. **Location** — Global (all projects) or local (current project only)
Verify with:
- Claude Code / Gemini / Copilot / Antigravity: `/gsd-help`
- OpenCode / Kilo / Augment: `/gsd-help`
- OpenCode / Kilo / Augment / Trae: `/gsd-help`
- Codex: `$gsd-help`
> [!NOTE]
@@ -153,12 +153,16 @@ npx get-shit-done-cc --antigravity --local # Install to ./.agent/
npx get-shit-done-cc --augment --global # Install to ~/.augment/
npx get-shit-done-cc --augment --local # Install to ./.augment/
# Trae (ByteDance, skills-first)
npx get-shit-done-cc --trae --global # Install to ~/.trae/
npx get-shit-done-cc --trae --local # Install to ./.trae/
# All runtimes
npx get-shit-done-cc --all --global # Install to all directories
```
Use `--global` (`-g`) or `--local` (`-l`) to skip the location prompt.
Use `--claude`, `--opencode`, `--gemini`, `--kilo`, `--codex`, `--copilot`, `--cursor`, `--windsurf`, `--antigravity`, `--augment`, or `--all` to skip the runtime prompt.
Use `--claude`, `--opencode`, `--gemini`, `--kilo`, `--codex`, `--copilot`, `--cursor`, `--windsurf`, `--antigravity`, `--augment`, `--trae`, or `--all` to skip the runtime prompt.
Use `--sdk` to also install the GSD SDK CLI (`gsd-sdk`) for headless autonomous execution.
</details>

View File

@@ -4,7 +4,7 @@
[English](README.md) · **Português** · [简体中文](README.zh-CN.md) · [日本語](README.ja-JP.md)
**Um sistema leve e poderoso de meta-prompting, engenharia de contexto e desenvolvimento orientado a especificação para Claude Code, OpenCode, Gemini CLI, Kilo, Codex, Copilot, Cursor, Windsurf, Antigravity e Augment.**
**Um sistema leve e poderoso de meta-prompting, engenharia de contexto e desenvolvimento orientado a especificação para Claude Code, OpenCode, Gemini CLI, Kilo, Codex, Copilot, Cursor, Windsurf, Antigravity, Augment e Trae.**
**Resolve context rot — a degradação de qualidade que acontece conforme o Claude enche a janela de contexto.**
@@ -82,7 +82,7 @@ npx get-shit-done-cc@latest
```
O instalador pede:
1. **Runtime** — Claude Code, OpenCode, Gemini, Kilo, Codex, Copilot, Cursor, Antigravity, ou todos
1. **Runtime** — Claude Code, OpenCode, Gemini, Kilo, Codex, Copilot, Cursor, Windsurf, Antigravity, Trae, ou todos
2. **Local** — Global (todos os projetos) ou local (apenas projeto atual)
Verifique com:
@@ -92,6 +92,7 @@ Verifique com:
- Codex: `$gsd-help`
- Copilot: `/gsd-help`
- Antigravity: `/gsd-help`
- Trae: `/gsd-help`
> [!NOTE]
> A instalação do Codex usa skills (`skills/gsd-*/SKILL.md`) em vez de prompts customizados.
@@ -136,12 +137,16 @@ npx get-shit-done-cc --cursor --local
npx get-shit-done-cc --antigravity --global
npx get-shit-done-cc --antigravity --local
# Trae (ByteDance, skills-first)
npx get-shit-done-cc --trae --global # Install to ~/.trae/
npx get-shit-done-cc --trae --local # Install to ./.trae/
# Todos
npx get-shit-done-cc --all --global
```
Use `--global` (`-g`) ou `--local` (`-l`) para pular a pergunta de local.
Use `--claude`, `--opencode`, `--gemini`, `--kilo`, `--codex`, `--copilot`, `--cursor`, `--antigravity` ou `--all` para pular a pergunta de runtime.
Use `--claude`, `--opencode`, `--gemini`, `--kilo`, `--codex`, `--copilot`, `--cursor`, `--windsurf`, `--antigravity`, `--trae` ou `--all` para pular a pergunta de runtime.
</details>
@@ -411,6 +416,7 @@ npx get-shit-done-cc --codex --global --uninstall
npx get-shit-done-cc --copilot --global --uninstall
npx get-shit-done-cc --cursor --global --uninstall
npx get-shit-done-cc --antigravity --global --uninstall
npx get-shit-done-cc --trae --global --uninstall
# Instalações locais (projeto atual)
npx get-shit-done-cc --claude --local --uninstall
@@ -421,6 +427,7 @@ npx get-shit-done-cc --codex --local --uninstall
npx get-shit-done-cc --copilot --local --uninstall
npx get-shit-done-cc --cursor --local --uninstall
npx get-shit-done-cc --antigravity --local --uninstall
npx get-shit-done-cc --trae --local --uninstall
```
---

View File

@@ -82,7 +82,7 @@ npx get-shit-done-cc@latest
```
安装器会提示你选择:
1. **运行时**:Claude Code、OpenCode、Gemini、Kilo、Codex、Copilot、Cursor、Antigravity,或全部
1. **运行时**:Claude Code、OpenCode、Gemini、Kilo、Codex、Copilot、Cursor、Windsurf、Antigravity、Trae,或全部
2. **安装位置**:全局(所有项目)或本地(仅当前项目)
安装后可这样验证:
@@ -92,6 +92,7 @@ npx get-shit-done-cc@latest
- Codex:`$gsd-help`
- Copilot:`/gsd-help`
- Antigravity:`/gsd-help`
- Trae:`/gsd-help`
> [!NOTE]
> Codex 安装走的是 skill 机制(`skills/gsd-*/SKILL.md`),不是自定义 prompt。
@@ -138,12 +139,16 @@ npx get-shit-done-cc --cursor --local # 安装到 ./.cursor/
npx get-shit-done-cc --antigravity --global # 安装到 ~/.gemini/antigravity/
npx get-shit-done-cc --antigravity --local # 安装到 ./.agent/
# Trae(字节跳动,以 skills 为主)
npx get-shit-done-cc --trae --global # 安装到 ~/.trae/
npx get-shit-done-cc --trae --local # 安装到 ./.trae/
# 所有运行时
npx get-shit-done-cc --all --global # 安装到所有目录
```
使用 `--global`(`-g`)或 `--local`(`-l`)可以跳过安装位置提示。
使用 `--claude`、`--opencode`、`--gemini`、`--kilo`、`--codex`、`--copilot`、`--cursor`、`--antigravity` 或 `--all` 可以跳过运行时提示。
使用 `--claude`、`--opencode`、`--gemini`、`--kilo`、`--codex`、`--copilot`、`--cursor`、`--windsurf`、`--antigravity`、`--trae` 或 `--all` 可以跳过运行时提示。
</details>

View File

@@ -69,6 +69,7 @@ const hasAntigravity = args.includes('--antigravity');
const hasCursor = args.includes('--cursor');
const hasWindsurf = args.includes('--windsurf');
const hasAugment = args.includes('--augment');
const hasTrae = args.includes('--trae');
const hasSdk = args.includes('--sdk');
const hasBoth = args.includes('--both'); // Legacy flag, keeps working
const hasAll = args.includes('--all');
@@ -77,7 +78,7 @@ const hasUninstall = args.includes('--uninstall') || args.includes('-u');
// Runtime selection - can be set by flags or interactive prompt
let selectedRuntimes = [];
if (hasAll) {
selectedRuntimes = ['claude', 'kilo', 'opencode', 'gemini', 'codex', 'copilot', 'antigravity', 'cursor', 'windsurf', 'augment'];
selectedRuntimes = ['claude', 'kilo', 'opencode', 'gemini', 'codex', 'copilot', 'antigravity', 'cursor', 'windsurf', 'augment', 'trae'];
} else if (hasBoth) {
selectedRuntimes = ['claude', 'opencode'];
} else {
@@ -91,6 +92,7 @@ if (hasAll) {
if (hasCursor) selectedRuntimes.push('cursor');
if (hasWindsurf) selectedRuntimes.push('windsurf');
if (hasAugment) selectedRuntimes.push('augment');
if (hasTrae) selectedRuntimes.push('trae');
}
// WSL + Windows Node.js detection
@@ -138,6 +140,7 @@ function getDirName(runtime) {
if (runtime === 'cursor') return '.cursor';
if (runtime === 'windsurf') return '.windsurf';
if (runtime === 'augment') return '.augment';
if (runtime === 'trae') return '.trae';
return '.claude';
}
@@ -169,6 +172,7 @@ function getConfigDirFromHome(runtime, isGlobal) {
if (runtime === 'cursor') return "'.cursor'";
if (runtime === 'windsurf') return "'.windsurf'";
if (runtime === 'augment') return "'.augment'";
if (runtime === 'trae') return "'.trae'";
return "'.claude'";
}
@@ -182,17 +186,17 @@ function getOpencodeGlobalDir() {
if (process.env.OPENCODE_CONFIG_DIR) {
return expandTilde(process.env.OPENCODE_CONFIG_DIR);
}
// 2. OPENCODE_CONFIG env var (use its directory)
if (process.env.OPENCODE_CONFIG) {
return path.dirname(expandTilde(process.env.OPENCODE_CONFIG));
}
// 3. XDG_CONFIG_HOME/opencode
if (process.env.XDG_CONFIG_HOME) {
return path.join(expandTilde(process.env.XDG_CONFIG_HOME), 'opencode');
}
// 4. Default: ~/.config/opencode (XDG default)
return path.join(os.homedir(), '.config', 'opencode');
}
@@ -243,7 +247,7 @@ function getGlobalDir(runtime, explicitDir = null) {
}
return getKiloGlobalDir();
}
if (runtime === 'gemini') {
// Gemini: --config-dir > GEMINI_CONFIG_DIR > ~/.gemini
if (explicitDir) {
@@ -320,6 +324,17 @@ function getGlobalDir(runtime, explicitDir = null) {
}
return path.join(os.homedir(), '.augment');
}
if (runtime === 'trae') {
// Trae: --config-dir > TRAE_CONFIG_DIR > ~/.trae
if (explicitDir) {
return expandTilde(explicitDir);
}
if (process.env.TRAE_CONFIG_DIR) {
return expandTilde(process.env.TRAE_CONFIG_DIR);
}
return path.join(os.homedir(), '.trae');
}
// Claude Code: --config-dir > CLAUDE_CONFIG_DIR > ~/.claude
if (explicitDir) {
@@ -341,7 +356,7 @@ const banner = '\n' +
'\n' +
' Get Shit Done ' + dim + 'v' + pkg.version + reset + '\n' +
' A meta-prompting, context engineering and spec-driven\n' +
' development system for Claude Code, OpenCode, Gemini, Kilo, Codex, Copilot, Antigravity, Cursor, Windsurf, and Augment by TÂCHES.\n';
' development system for Claude Code, OpenCode, Gemini, Kilo, Codex, Copilot, Antigravity, Cursor, Windsurf, Augment and Trae by TÂCHES.\n';
// Parse --config-dir argument
function parseConfigDirArg() {
@@ -379,7 +394,7 @@ if (hasUninstall) {
// Show help if requested
if (hasHelp) {
console.log(` ${yellow}Usage:${reset} npx get-shit-done-cc [options]\n\n ${yellow}Options:${reset}\n ${cyan}-g, --global${reset} Install globally (to config directory)\n ${cyan}-l, --local${reset} Install locally (to current directory)\n ${cyan}--claude${reset} Install for Claude Code only\n ${cyan}--opencode${reset} Install for OpenCode only\n ${cyan}--gemini${reset} Install for Gemini only\n ${cyan}--kilo${reset} Install for Kilo only\n ${cyan}--codex${reset} Install for Codex only\n ${cyan}--copilot${reset} Install for Copilot only\n ${cyan}--antigravity${reset} Install for Antigravity only\n ${cyan}--cursor${reset} Install for Cursor only\n ${cyan}--windsurf${reset} Install for Windsurf only\n ${cyan}--augment${reset} Install for Augment only\n ${cyan}--all${reset} Install for all runtimes\n ${cyan}--sdk${reset} Also install GSD SDK CLI (gsd-sdk)\n ${cyan}-u, --uninstall${reset} Uninstall GSD (remove all GSD files)\n ${cyan}-c, --config-dir <path>${reset} Specify custom config directory\n ${cyan}-h, --help${reset} Show this help message\n ${cyan}--force-statusline${reset} Replace existing statusline config\n\n ${yellow}Examples:${reset}\n ${dim}# Interactive install (prompts for runtime and location)${reset}\n npx get-shit-done-cc\n\n ${dim}# Install for Claude Code globally${reset}\n npx get-shit-done-cc --claude --global\n\n ${dim}# Install for Gemini globally${reset}\n npx get-shit-done-cc --gemini --global\n\n ${dim}# Install for Kilo globally${reset}\n npx get-shit-done-cc --kilo --global\n\n ${dim}# Install for Codex globally${reset}\n npx get-shit-done-cc --codex --global\n\n ${dim}# Install for Copilot globally${reset}\n npx get-shit-done-cc --copilot --global\n\n ${dim}# Install for Copilot locally${reset}\n npx get-shit-done-cc --copilot --local\n\n ${dim}# Install for Antigravity globally${reset}\n npx get-shit-done-cc --antigravity --global\n\n ${dim}# Install for Antigravity locally${reset}\n npx get-shit-done-cc --antigravity --local\n\n ${dim}# Install for Cursor globally${reset}\n npx get-shit-done-cc --cursor --global\n\n ${dim}# Install for Cursor locally${reset}\n npx get-shit-done-cc --cursor --local\n\n ${dim}# Install for Windsurf globally${reset}\n npx get-shit-done-cc --windsurf --global\n\n ${dim}# Install for Windsurf locally${reset}\n npx get-shit-done-cc --windsurf --local\n\n ${dim}# Install for Augment globally${reset}\n npx get-shit-done-cc --augment --global\n\n ${dim}# Install for Augment locally${reset}\n npx get-shit-done-cc --augment --local\n\n ${dim}# Install for all runtimes globally${reset}\n npx get-shit-done-cc --all --global\n\n ${dim}# Install to custom config directory${reset}\n npx get-shit-done-cc --kilo --global --config-dir ~/.kilo-work\n\n ${dim}# Install to current project only${reset}\n npx get-shit-done-cc --claude --local\n\n ${dim}# Uninstall GSD from Cursor globally${reset}\n npx get-shit-done-cc --cursor --global --uninstall\n\n ${yellow}Notes:${reset}\n The --config-dir option is useful when you have multiple configurations.\n It takes priority over CLAUDE_CONFIG_DIR / OPENCODE_CONFIG_DIR / GEMINI_CONFIG_DIR / KILO_CONFIG_DIR / CODEX_HOME / COPILOT_CONFIG_DIR / ANTIGRAVITY_CONFIG_DIR / CURSOR_CONFIG_DIR / WINDSURF_CONFIG_DIR / AUGMENT_CONFIG_DIR environment variables.\n`);
console.log(` ${yellow}Usage:${reset} npx get-shit-done-cc [options]\n\n ${yellow}Options:${reset}\n ${cyan}-g, --global${reset} Install globally (to config directory)\n ${cyan}-l, --local${reset} Install locally (to current directory)\n ${cyan}--claude${reset} Install for Claude Code only\n ${cyan}--opencode${reset} Install for OpenCode only\n ${cyan}--gemini${reset} Install for Gemini only\n ${cyan}--kilo${reset} Install for Kilo only\n ${cyan}--codex${reset} Install for Codex only\n ${cyan}--copilot${reset} Install for Copilot only\n ${cyan}--antigravity${reset} Install for Antigravity only\n ${cyan}--cursor${reset} Install for Cursor only\n ${cyan}--windsurf${reset} Install for Windsurf only\n ${cyan}--augment${reset} Install for Augment only\n ${cyan}--trae${reset} Install for Trae only\n ${cyan}--all${reset} Install for all runtimes\n ${cyan}--sdk${reset} Also install GSD SDK CLI (gsd-sdk)\n ${cyan}-u, --uninstall${reset} Uninstall GSD (remove all GSD files)\n ${cyan}-c, --config-dir <path>${reset} Specify custom config directory\n ${cyan}-h, --help${reset} Show this help message\n ${cyan}--force-statusline${reset} Replace existing statusline config\n\n ${yellow}Examples:${reset}\n ${dim}# Interactive install (prompts for runtime and location)${reset}\n npx get-shit-done-cc\n\n ${dim}# Install for Claude Code globally${reset}\n npx get-shit-done-cc --claude --global\n\n ${dim}# Install for Gemini globally${reset}\n npx get-shit-done-cc --gemini --global\n\n ${dim}# Install for Kilo globally${reset}\n npx get-shit-done-cc --kilo --global\n\n ${dim}# Install for Codex globally${reset}\n npx get-shit-done-cc --codex --global\n\n ${dim}# Install for Copilot globally${reset}\n npx get-shit-done-cc --copilot --global\n\n ${dim}# Install for Copilot locally${reset}\n npx get-shit-done-cc --copilot --local\n\n ${dim}# Install for Antigravity globally${reset}\n npx get-shit-done-cc --antigravity --global\n\n ${dim}# Install for Antigravity locally${reset}\n npx get-shit-done-cc --antigravity --local\n\n ${dim}# Install for Cursor globally${reset}\n npx get-shit-done-cc --cursor --global\n\n ${dim}# Install for Cursor locally${reset}\n npx get-shit-done-cc --cursor --local\n\n ${dim}# Install for Windsurf globally${reset}\n npx get-shit-done-cc --windsurf --global\n\n ${dim}# Install for Windsurf locally${reset}\n npx get-shit-done-cc --windsurf --local\n\n ${dim}# Install for Augment globally${reset}\n npx get-shit-done-cc --augment --global\n\n ${dim}# Install for Augment locally${reset}\n npx get-shit-done-cc --augment --local\n\n ${dim}# Install for Trae globally${reset}\n npx get-shit-done-cc --trae --global\n\n ${dim}# Install for Trae locally${reset}\n npx get-shit-done-cc --trae --local\n\n ${dim}# Install for all runtimes globally${reset}\n npx get-shit-done-cc --all --global\n\n ${dim}# Install to custom config directory${reset}\n npx get-shit-done-cc --kilo --global --config-dir ~/.kilo-work\n\n ${dim}# Install to current project only${reset}\n npx get-shit-done-cc --claude --local\n\n ${dim}# Uninstall GSD from Cursor globally${reset}\n npx get-shit-done-cc --cursor --global --uninstall\n\n ${yellow}Notes:${reset}\n The --config-dir option is useful when you have multiple configurations.\n It takes priority over CLAUDE_CONFIG_DIR / OPENCODE_CONFIG_DIR / GEMINI_CONFIG_DIR / KILO_CONFIG_DIR / CODEX_HOME / COPILOT_CONFIG_DIR / ANTIGRAVITY_CONFIG_DIR / CURSOR_CONFIG_DIR / WINDSURF_CONFIG_DIR / AUGMENT_CONFIG_DIR / TRAE_CONFIG_DIR environment variables.\n`);
process.exit(0);
}
@@ -1407,6 +1422,61 @@ function copyCommandsAsAugmentSkills(srcDir, skillsDir, prefix, pathPrefix, runt
recurse(srcDir, prefix);
}
function convertSlashCommandsToTraeSkillMentions(content) {
return content.replace(/\/gsd:([a-z0-9-]+)/g, (_, commandName) => {
return `/gsd-${commandName}`;
});
}
function convertClaudeToTraeMarkdown(content) {
let converted = convertSlashCommandsToTraeSkillMentions(content);
converted = converted.replace(/\bBash\(/g, 'Shell(');
converted = converted.replace(/\bEdit\(/g, 'StrReplace(');
// Replace general-purpose subagent type with Trae's equivalent "general_purpose_task"
converted = converted.replace(/subagent_type="general-purpose"/g, 'subagent_type="general_purpose_task"');
converted = converted.replace(/\$ARGUMENTS\b/g, '{{GSD_ARGS}}');
converted = converted.replace(/`\.\/CLAUDE\.md`/g, '`.trae/rules/`');
converted = converted.replace(/\.\/CLAUDE\.md/g, '.trae/rules/');
converted = converted.replace(/`CLAUDE\.md`/g, '`.trae/rules/`');
converted = converted.replace(/\bCLAUDE\.md\b/g, '.trae/rules/');
converted = converted.replace(/\.claude\/skills\//g, '.trae/skills/');
converted = converted.replace(/\.\/\.claude\//g, './.trae/');
converted = converted.replace(/\.claude\//g, '.trae/');
converted = converted.replace(/\*\*Known Claude Code bug \(classifyHandoffIfNeeded\):\*\*[^\n]*\n/g, '');
converted = converted.replace(/- \*\*classifyHandoffIfNeeded false failure:\*\*[^\n]*\n/g, '');
converted = converted.replace(/\bClaude Code\b/g, 'Trae');
return converted;
}
function convertClaudeCommandToTraeSkill(content, skillName) {
const converted = convertClaudeToTraeMarkdown(content);
const { frontmatter, body } = extractFrontmatterAndBody(converted);
let description = `Run GSD workflow ${skillName}.`;
if (frontmatter) {
const maybeDescription = extractFrontmatterField(frontmatter, 'description');
if (maybeDescription) {
description = maybeDescription;
}
}
description = toSingleLine(description);
const shortDescription = description.length > 180 ? `${description.slice(0, 177)}...` : description;
return `---\nname: ${yamlIdentifier(skillName)}\ndescription: ${shortDescription}\n---\n${body}`;
}
function convertClaudeAgentToTraeAgent(content) {
let converted = convertClaudeToTraeMarkdown(content);
const { frontmatter, body } = extractFrontmatterAndBody(converted);
if (!frontmatter) return converted;
const name = extractFrontmatterField(frontmatter, 'name') || 'unknown';
const description = extractFrontmatterField(frontmatter, 'description') || '';
const cleanFrontmatter = `---\nname: ${yamlIdentifier(name)}\ndescription: ${yamlQuote(toSingleLine(description))}\n---`;
return `${cleanFrontmatter}\n${body}`;
}
function convertSlashCommandsToCodexSkillMentions(content) {
let converted = content.replace(/\/gsd:([a-z0-9-]+)/gi, (_, commandName) => {
return `$gsd-${String(commandName).toLowerCase()}`;
@@ -3259,7 +3329,7 @@ function convertClaudeToGeminiToml(content) {
const frontmatter = content.substring(3, endIndex).trim();
const body = content.substring(endIndex + 3).trim();
// Extract description from frontmatter
let description = '';
const lines = frontmatter.split('\n');
@@ -3276,9 +3346,9 @@ function convertClaudeToGeminiToml(content) {
if (description) {
toml += `description = ${JSON.stringify(description)}\n`;
}
toml += `prompt = ${JSON.stringify(body)}\n`;
return toml;
}
@@ -3297,7 +3367,7 @@ function copyFlattenedCommands(srcDir, destDir, prefix, pathPrefix, runtime) {
if (!fs.existsSync(srcDir)) {
return;
}
// Remove old gsd-*.md files before copying new ones
if (fs.existsSync(destDir)) {
for (const file of fs.readdirSync(destDir)) {
@@ -3308,12 +3378,12 @@ function copyFlattenedCommands(srcDir, destDir, prefix, pathPrefix, runtime) {
} else {
fs.mkdirSync(destDir, { recursive: true });
}
const entries = fs.readdirSync(srcDir, { withFileTypes: true });
for (const entry of entries) {
const srcPath = path.join(srcDir, entry.name);
if (entry.isDirectory()) {
// Recurse into subdirectories, adding to prefix
// e.g., commands/gsd/debug/start.md -> command/gsd-debug-start.md
@@ -3516,6 +3586,65 @@ function copyCommandsAsWindsurfSkills(srcDir, skillsDir, prefix, pathPrefix, run
recurse(srcDir, prefix);
}
function copyCommandsAsTraeSkills(srcDir, skillsDir, prefix, pathPrefix, runtime) {
if (!fs.existsSync(srcDir)) {
return;
}
fs.mkdirSync(skillsDir, { recursive: true });
const existing = fs.readdirSync(skillsDir, { withFileTypes: true });
for (const entry of existing) {
if (entry.isDirectory() && entry.name.startsWith(`${prefix}-`)) {
fs.rmSync(path.join(skillsDir, entry.name), { recursive: true });
}
}
function recurse(currentSrcDir, currentPrefix) {
const entries = fs.readdirSync(currentSrcDir, { withFileTypes: true });
for (const entry of entries) {
const srcPath = path.join(currentSrcDir, entry.name);
if (entry.isDirectory()) {
recurse(srcPath, `${currentPrefix}-${entry.name}`);
continue;
}
if (!entry.name.endsWith('.md')) {
continue;
}
const baseName = entry.name.replace('.md', '');
const skillName = `${currentPrefix}-${baseName}`;
const skillDir = path.join(skillsDir, skillName);
fs.mkdirSync(skillDir, { recursive: true });
let content = fs.readFileSync(srcPath, 'utf8');
const globalClaudeRegex = /~\/\.claude\//g;
const globalClaudeHomeRegex = /\$HOME\/\.claude\//g;
const localClaudeRegex = /\.\/\.claude\//g;
const bareGlobalClaudeRegex = /~\/\.claude\b/g;
const bareGlobalClaudeHomeRegex = /\$HOME\/\.claude\b/g;
const bareLocalClaudeRegex = /\.\/\.claude\b/g;
const traeDirRegex = /~\/\.trae\//g;
const normalizedPathPrefix = pathPrefix.replace(/\/$/, '');
content = content.replace(globalClaudeRegex, pathPrefix);
content = content.replace(globalClaudeHomeRegex, pathPrefix);
content = content.replace(localClaudeRegex, `./${getDirName(runtime)}/`);
content = content.replace(bareGlobalClaudeRegex, normalizedPathPrefix);
content = content.replace(bareGlobalClaudeHomeRegex, normalizedPathPrefix);
content = content.replace(bareLocalClaudeRegex, `./${getDirName(runtime)}`);
content = content.replace(traeDirRegex, pathPrefix);
content = processAttribution(content, getCommitAttribution(runtime));
content = convertClaudeCommandToTraeSkill(content, skillName);
fs.writeFileSync(path.join(skillDir, 'SKILL.md'), content);
}
}
recurse(srcDir, prefix);
}
/**
* Copy Claude commands as Copilot skills — one folder per skill with SKILL.md.
* Applies CONV-01 (structure), CONV-02 (allowed-tools), CONV-06 (paths), CONV-07 (command names).
@@ -3693,6 +3822,7 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime, isCommand
const isCursor = runtime === 'cursor';
const isWindsurf = runtime === 'windsurf';
const isAugment = runtime === 'augment';
const isTrae = runtime === 'trae';
const dirName = getDirName(runtime);
// Clean install: remove existing destination to prevent orphaned files
@@ -3757,6 +3887,9 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime, isCommand
} else if (isWindsurf) {
content = convertClaudeToWindsurfMarkdown(content);
fs.writeFileSync(destPath, content);
} else if (isTrae) {
content = convertClaudeToTraeMarkdown(content);
fs.writeFileSync(destPath, content);
} else {
fs.writeFileSync(destPath, content);
}
@@ -3786,6 +3919,15 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime, isCommand
jsContent = jsContent.replace(/CLAUDE\.md/g, '.windsurf/rules');
jsContent = jsContent.replace(/\bClaude Code\b/g, 'Windsurf');
fs.writeFileSync(destPath, jsContent);
} else if (isTrae && (entry.name.endsWith('.cjs') || entry.name.endsWith('.js'))) {
let jsContent = fs.readFileSync(srcPath, 'utf8');
jsContent = jsContent.replace(/\/gsd:([a-z0-9-]+)/g, (_, commandName) => {
return `/gsd-${commandName}`;
});
jsContent = jsContent.replace(/\.claude\/skills\//g, '.trae/skills/');
jsContent = jsContent.replace(/CLAUDE\.md/g, '.trae/rules/');
jsContent = jsContent.replace(/\bClaude Code\b/g, 'Trae');
fs.writeFileSync(destPath, jsContent);
} else {
fs.copyFileSync(srcPath, destPath);
}
@@ -3856,7 +3998,7 @@ function cleanupOrphanedHooks(settings) {
// Only match the specific old GSD path pattern (hooks/statusline.js),
// not third-party statusline scripts that happen to contain 'statusline.js'
if (settings.statusLine && settings.statusLine.command &&
/hooks[\/\\]statusline\.js/.test(settings.statusLine.command)) {
/hooks[\/\\]statusline\.js/.test(settings.statusLine.command)) {
settings.statusLine.command = settings.statusLine.command.replace(
/hooks([\/\\])statusline\.js/,
'hooks$1gsd-statusline.js'
@@ -3962,6 +4104,7 @@ function uninstall(isGlobal, runtime = 'claude') {
const isCursor = runtime === 'cursor';
const isWindsurf = runtime === 'windsurf';
const isAugment = runtime === 'augment';
const isTrae = runtime === 'trae';
const dirName = getDirName(runtime);
// Get the target directory based on runtime and install type
@@ -3983,6 +4126,7 @@ function uninstall(isGlobal, runtime = 'claude') {
if (runtime === 'cursor') runtimeLabel = 'Cursor';
if (runtime === 'windsurf') runtimeLabel = 'Windsurf';
if (runtime === 'augment') runtimeLabel = 'Augment';
if (runtime === 'trae') runtimeLabel = 'Trae';
console.log(` Uninstalling GSD from ${cyan}${runtimeLabel}${reset} at ${cyan}${locationLabel}${reset}\n`);
@@ -4009,8 +4153,8 @@ function uninstall(isGlobal, runtime = 'claude') {
}
console.log(` ${green}✓${reset} Removed GSD commands from command/`);
}
} else if (isCodex || isCursor || isWindsurf) {
// Codex/Cursor/Windsurf: remove skills/gsd-*/SKILL.md skill directories
} else if (isCodex || isCursor || isWindsurf || isTrae) {
// Codex/Cursor/Windsurf/Trae: remove skills/gsd-*/SKILL.md skill directories
const skillsDir = path.join(targetDir, 'skills');
if (fs.existsSync(skillsDir)) {
let skillCount = 0;
@@ -4029,39 +4173,39 @@ function uninstall(isGlobal, runtime = 'claude') {
// Codex-only: remove GSD agent .toml config files and config.toml sections
if (isCodex) {
const codexAgentsDir = path.join(targetDir, 'agents');
if (fs.existsSync(codexAgentsDir)) {
const tomlFiles = fs.readdirSync(codexAgentsDir);
let tomlCount = 0;
for (const file of tomlFiles) {
if (file.startsWith('gsd-') && file.endsWith('.toml')) {
fs.unlinkSync(path.join(codexAgentsDir, file));
tomlCount++;
const codexAgentsDir = path.join(targetDir, 'agents');
if (fs.existsSync(codexAgentsDir)) {
const tomlFiles = fs.readdirSync(codexAgentsDir);
let tomlCount = 0;
for (const file of tomlFiles) {
if (file.startsWith('gsd-') && file.endsWith('.toml')) {
fs.unlinkSync(path.join(codexAgentsDir, file));
tomlCount++;
}
}
if (tomlCount > 0) {
removedCount++;
console.log(` ${green}✓${reset} Removed ${tomlCount} agent .toml configs`);
}
}
if (tomlCount > 0) {
removedCount++;
console.log(` ${green}✓${reset} Removed ${tomlCount} agent .toml configs`);
}
}
// Codex: clean GSD sections from config.toml
const configPath = path.join(targetDir, 'config.toml');
if (fs.existsSync(configPath)) {
const content = fs.readFileSync(configPath, 'utf8');
const cleaned = stripGsdFromCodexConfig(content);
if (cleaned === null) {
// File is empty after stripping — delete it
fs.unlinkSync(configPath);
removedCount++;
console.log(` ${green}✓${reset} Removed config.toml (was GSD-only)`);
} else if (cleaned !== content) {
fs.writeFileSync(configPath, cleaned);
removedCount++;
console.log(` ${green}✓${reset} Cleaned GSD sections from config.toml`);
// Codex: clean GSD sections from config.toml
const configPath = path.join(targetDir, 'config.toml');
if (fs.existsSync(configPath)) {
const content = fs.readFileSync(configPath, 'utf8');
const cleaned = stripGsdFromCodexConfig(content);
if (cleaned === null) {
// File is empty after stripping — delete it
fs.unlinkSync(configPath);
removedCount++;
console.log(` ${green}✓${reset} Removed config.toml (was GSD-only)`);
} else if (cleaned !== content) {
fs.writeFileSync(configPath, cleaned);
removedCount++;
console.log(` ${green}✓${reset} Cleaned GSD sections from config.toml`);
}
}
}
}
} else if (isCopilot) {
// Copilot: remove skills/gsd-*/ directories (same layout as Codex skills)
const skillsDir = path.join(targetDir, 'skills');
@@ -4298,7 +4442,7 @@ function uninstall(isGlobal, runtime = 'claude') {
// Remove GSD statusline if it references our hook
if (settings.statusLine && settings.statusLine.command &&
settings.statusLine.command.includes('gsd-statusline')) {
settings.statusLine.command.includes('gsd-statusline')) {
delete settings.statusLine;
settingsModified = true;
console.log(` ${green}✓${reset} Removed GSD statusline from settings`);
@@ -4587,7 +4731,7 @@ function configureOpencodePermissions(isGlobal = true, configDir = null) {
const gsdPath = opencodeConfigDir === defaultConfigDir
? '~/.config/opencode/get-shit-done/*'
: `${opencodeConfigDir.replace(/\\/g, '/')}/get-shit-done/*`;
let modified = false;
// Configure read permission
@@ -4776,6 +4920,7 @@ function writeManifest(configDir, runtime = 'claude') {
const isAntigravity = runtime === 'antigravity';
const isCursor = runtime === 'cursor';
const isWindsurf = runtime === 'windsurf';
const isTrae = runtime === 'trae';
const gsdDir = path.join(configDir, 'get-shit-done');
const commandsDir = path.join(configDir, 'commands', 'gsd');
const opencodeCommandDir = path.join(configDir, 'command');
@@ -4800,7 +4945,7 @@ function writeManifest(configDir, runtime = 'claude') {
}
}
}
if ((isCodex || isCopilot || isAntigravity || isCursor || isWindsurf || (!isOpencode && !isGemini)) && fs.existsSync(codexSkillsDir)) {
if ((isCodex || isCopilot || isAntigravity || isCursor || isWindsurf || isTrae || (!isOpencode && !isGemini)) && fs.existsSync(codexSkillsDir)) {
for (const skillName of listCodexSkillNames(codexSkillsDir)) {
const skillRoot = path.join(codexSkillsDir, skillName);
const skillHashes = generateManifest(skillRoot);
@@ -4937,6 +5082,7 @@ function install(isGlobal, runtime = 'claude') {
const isCursor = runtime === 'cursor';
const isWindsurf = runtime === 'windsurf';
const isAugment = runtime === 'augment';
const isTrae = runtime === 'trae';
const dirName = getDirName(runtime);
const src = path.join(__dirname, '..');
@@ -4970,6 +5116,7 @@ function install(isGlobal, runtime = 'claude') {
if (isCursor) runtimeLabel = 'Cursor';
if (isWindsurf) runtimeLabel = 'Windsurf';
if (isAugment) runtimeLabel = 'Augment';
if (isTrae) runtimeLabel = 'Trae';
console.log(` Installing for ${cyan}${runtimeLabel}${reset} to ${cyan}${locationLabel}${reset}\n`);
@@ -4987,7 +5134,7 @@ function install(isGlobal, runtime = 'claude') {
// OpenCode/Kilo: flat structure in command/ directory
const commandDir = path.join(targetDir, 'command');
fs.mkdirSync(commandDir, { recursive: true });
// Copy commands/gsd/*.md as command/gsd-*.md (flatten structure)
const gsdSrc = path.join(src, 'commands', 'gsd');
copyFlattenedCommands(gsdSrc, commandDir, 'gsd', pathPrefix, runtime);
@@ -5067,6 +5214,16 @@ function install(isGlobal, runtime = 'claude') {
} else {
failures.push('skills/gsd-*');
}
} else if (isTrae) {
const skillsDir = path.join(targetDir, 'skills');
const gsdSrc = path.join(src, 'commands', 'gsd');
copyCommandsAsTraeSkills(gsdSrc, skillsDir, 'gsd', pathPrefix, runtime);
const installedSkillNames = listCodexSkillNames(skillsDir);
if (installedSkillNames.length > 0) {
console.log(` ${green}✓${reset} Installed ${installedSkillNames.length} skills to skills/`);
} else {
failures.push('skills/gsd-*');
}
} else if (isGemini) {
const commandsDir = path.join(targetDir, 'commands');
fs.mkdirSync(commandsDir, { recursive: true });
@@ -5136,9 +5293,14 @@ function install(isGlobal, runtime = 'claude') {
// Replace ~/.claude/ and $HOME/.claude/ as they are the source of truth in the repo
const dirRegex = /~\/\.claude\//g;
const homeDirRegex = /\$HOME\/\.claude\//g;
const bareDirRegex = /~\/\.claude\b/g;
const bareHomeDirRegex = /\$HOME\/\.claude\b/g;
const normalizedPathPrefix = pathPrefix.replace(/\/$/, '');
if (!isCopilot && !isAntigravity) {
content = content.replace(dirRegex, pathPrefix);
content = content.replace(homeDirRegex, pathPrefix);
content = content.replace(bareDirRegex, normalizedPathPrefix);
content = content.replace(bareHomeDirRegex, normalizedPathPrefix);
}
content = processAttribution(content, getCommitAttribution(runtime));
// Convert frontmatter for runtime compatibility (agents need different handling)
@@ -5160,6 +5322,8 @@ function install(isGlobal, runtime = 'claude') {
content = convertClaudeAgentToWindsurfAgent(content);
} else if (isAugment) {
content = convertClaudeAgentToAugmentAgent(content);
} else if (isTrae) {
content = convertClaudeAgentToTraeAgent(content);
}
const destName = isCopilot ? entry.name.replace('.md', '.agent.md') : entry.name;
fs.writeFileSync(path.join(agentsDest, destName), content);
@@ -5193,7 +5357,7 @@ function install(isGlobal, runtime = 'claude') {
failures.push('VERSION');
}
if (!isCodex && !isCopilot && !isCursor && !isWindsurf) {
if (!isCodex && !isCopilot && !isCursor && !isWindsurf && !isTrae) {
// Write package.json to force CommonJS mode for GSD scripts
// Prevents "require is not defined" errors when project has "type": "module"
// Node.js walks up looking for package.json - this stops inheritance from project
@@ -5359,6 +5523,11 @@ function install(isGlobal, runtime = 'claude') {
return { settingsPath: null, settings: null, statuslineCommand: null, runtime, configDir: targetDir };
}
if (isTrae) {
// Trae uses skills — no settings.json hooks needed
return { settingsPath: null, settings: null, statuslineCommand: null, runtime, configDir: targetDir };
}
// Configure statusline and hooks in settings.json
// Gemini and Antigravity use AfterTool instead of PostToolUse for post-tool hooks
const postToolEvent = (runtime === 'gemini' || runtime === 'antigravity') ? 'AfterTool' : 'PostToolUse';
@@ -5593,8 +5762,9 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
const isCopilot = runtime === 'copilot';
const isCursor = runtime === 'cursor';
const isWindsurf = runtime === 'windsurf';
const isTrae = runtime === 'trae';
if (shouldInstallStatusline && !isOpencode && !isKilo && !isCodex && !isCopilot && !isCursor && !isWindsurf) {
if (shouldInstallStatusline && !isOpencode && !isKilo && !isCodex && !isCopilot && !isCursor && !isWindsurf && !isTrae) {
settings.statusLine = {
type: 'command',
command: statuslineCommand
@@ -5603,7 +5773,7 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
}
// Write settings when runtime supports settings.json
if (!isCodex && !isCopilot && !isKilo && !isCursor && !isWindsurf) {
if (!isCodex && !isCopilot && !isKilo && !isCursor && !isWindsurf && !isTrae) {
writeSettings(settingsPath, settings);
}
@@ -5648,6 +5818,7 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
if (runtime === 'cursor') program = 'Cursor';
if (runtime === 'windsurf') program = 'Windsurf';
if (runtime === 'augment') program = 'Augment';
if (runtime === 'trae') program = 'Trae';
let command = '/gsd:new-project';
if (runtime === 'opencode') command = '/gsd-new-project';
@@ -5658,6 +5829,7 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
if (runtime === 'cursor') command = 'gsd-new-project (mention the skill name)';
if (runtime === 'windsurf') command = '/gsd-new-project';
if (runtime === 'augment') command = '/gsd-new-project';
if (runtime === 'trae') command = '/gsd-new-project';
console.log(`
${green}Done!${reset} Open a blank directory in ${program} and run ${cyan}${command}${reset}.
@@ -5810,9 +5982,10 @@ function promptRuntime(callback) {
'7': 'antigravity',
'8': 'cursor',
'9': 'windsurf',
'10': 'augment'
'10': 'augment',
'11': 'trae'
};
const allRuntimes = ['claude', 'kilo', 'opencode', 'gemini', 'codex', 'copilot', 'antigravity', 'cursor', 'windsurf', 'augment'];
const allRuntimes = ['claude', 'kilo', 'opencode', 'gemini', 'codex', 'copilot', 'antigravity', 'cursor', 'windsurf', 'augment', 'trae'];
console.log(` ${yellow}Which runtime(s) would you like to install for?${reset}\n\n ${cyan}1${reset}) Claude Code ${dim}(~/.claude)${reset}
${cyan}2${reset}) Kilo ${dim}(~/.config/kilo)${reset} - open source, the #1 AI coding platform on OpenRouter
@@ -5824,7 +5997,8 @@ function promptRuntime(callback) {
${cyan}8${reset}) Cursor ${dim}(~/.cursor)${reset}
${cyan}9${reset}) Windsurf ${dim}(~/.windsurf)${reset}
${cyan}10${reset}) Augment ${dim}(~/.augment)${reset}
${cyan}11${reset}) All
${cyan}11${reset}) Trae ${dim}(~/.trae)${reset}
${cyan}12${reset}) All
${dim}Select multiple: 1,2,6 or 1 2 6${reset}
`);
@@ -5835,7 +6009,7 @@ function promptRuntime(callback) {
const input = answer.trim() || '1';
// "All" shortcut
if (input === '11') {
if (input === '12') {
callback(allRuntimes);
return;
}
@@ -5966,6 +6140,7 @@ if (process.env.GSD_TEST_MODE) {
mergeCodexConfig,
installCodexConfig,
install,
uninstall,
convertClaudeCommandToCodexSkill,
convertClaudeToOpencodeFrontmatter,
convertClaudeToKiloFrontmatter,
@@ -6002,6 +6177,10 @@ if (process.env.GSD_TEST_MODE) {
convertClaudeCommandToAugmentSkill,
convertClaudeAgentToAugmentAgent,
copyCommandsAsAugmentSkills,
convertClaudeToTraeMarkdown,
convertClaudeCommandToTraeSkill,
convertClaudeAgentToTraeAgent,
copyCommandsAsTraeSkills,
writeManifest,
reportLocalPatches,
validateHookFields,
@@ -6010,41 +6189,41 @@ if (process.env.GSD_TEST_MODE) {
};
} else {
// Main logic
if (hasGlobal && hasLocal) {
console.error(` ${yellow}Cannot specify both --global and --local${reset}`);
process.exit(1);
} else if (explicitConfigDir && hasLocal) {
console.error(` ${yellow}Cannot use --config-dir with --local${reset}`);
process.exit(1);
} else if (hasUninstall) {
if (!hasGlobal && !hasLocal) {
console.error(` ${yellow}--uninstall requires --global or --local${reset}`);
// Main logic
if (hasGlobal && hasLocal) {
console.error(` ${yellow}Cannot specify both --global and --local${reset}`);
process.exit(1);
}
const runtimes = selectedRuntimes.length > 0 ? selectedRuntimes : ['claude'];
for (const runtime of runtimes) {
uninstall(hasGlobal, runtime);
}
} else if (selectedRuntimes.length > 0) {
if (!hasGlobal && !hasLocal) {
promptLocation(selectedRuntimes);
} else if (explicitConfigDir && hasLocal) {
console.error(` ${yellow}Cannot use --config-dir with --local${reset}`);
process.exit(1);
} else if (hasUninstall) {
if (!hasGlobal && !hasLocal) {
console.error(` ${yellow}--uninstall requires --global or --local${reset}`);
process.exit(1);
}
const runtimes = selectedRuntimes.length > 0 ? selectedRuntimes : ['claude'];
for (const runtime of runtimes) {
uninstall(hasGlobal, runtime);
}
} else if (selectedRuntimes.length > 0) {
if (!hasGlobal && !hasLocal) {
promptLocation(selectedRuntimes);
} else {
installAllRuntimes(selectedRuntimes, hasGlobal, false);
}
} else if (hasGlobal || hasLocal) {
// Default to Claude if no runtime specified but location is
installAllRuntimes(['claude'], hasGlobal, false);
} else {
installAllRuntimes(selectedRuntimes, hasGlobal, false);
// Interactive
if (!process.stdin.isTTY) {
console.log(` ${yellow}Non-interactive terminal detected, defaulting to Claude Code global install${reset}\n`);
installAllRuntimes(['claude'], true, false);
} else {
promptRuntime((runtimes) => {
promptLocation(runtimes);
});
}
}
} else if (hasGlobal || hasLocal) {
// Default to Claude if no runtime specified but location is
installAllRuntimes(['claude'], hasGlobal, false);
} else {
// Interactive
if (!process.stdin.isTTY) {
console.log(` ${yellow}Non-interactive terminal detected, defaulting to Claude Code global install${reset}\n`);
installAllRuntimes(['claude'], true, false);
} else {
promptRuntime((runtimes) => {
promptLocation(runtimes);
});
}
}
} // end of else block for GSD_TEST_MODE

View File

@@ -426,7 +426,7 @@ Equivalent paths for other runtimes:
The installer (`bin/install.js`, ~3,000 lines) handles:
1. **Runtime detection** — Interactive prompt or CLI flags (`--claude`, `--opencode`, `--gemini`, `--kilo`, `--codex`, `--copilot`, `--antigravity`, `--all`)
1. **Runtime detection** — Interactive prompt or CLI flags (`--claude`, `--opencode`, `--gemini`, `--kilo`, `--codex`, `--copilot`, `--antigravity`, `--cursor`, `--windsurf`, `--trae`, `--all`)
2. **Location selection** — Global (`--global`) or local (`--local`)
3. **File deployment** — Copies commands, workflows, references, templates, agents, hooks
4. **Runtime adaptation** — Transforms file content per runtime:
@@ -437,6 +437,7 @@ The installer (`bin/install.js`, ~3,000 lines) handles:
- Copilot: Maps tool names (Read→read, Bash→execute, etc.)
- Gemini: Adjusts hook event names (`AfterTool` instead of `PostToolUse`)
- Antigravity: Skills-first with Google model equivalents
- Trae: Skills-first install to `~/.trae` / `./.trae` with no `settings.json` or hook integration
5. **Path normalization** — Replaces `~/.claude/` paths with runtime-specific paths
6. **Settings integration** — Registers hooks in runtime's `settings.json`
7. **Patch backup** — Since v1.17, backs up locally modified files to `gsd-local-patches/` for `/gsd-reapply-patches`

View File

@@ -27,9 +27,10 @@ const runtimeMap = {
'7': 'antigravity',
'8': 'cursor',
'9': 'windsurf',
'10': 'augment'
'10': 'augment',
'11': 'trae'
};
const allRuntimes = ['claude', 'kilo', 'opencode', 'gemini', 'codex', 'copilot', 'antigravity', 'cursor', 'windsurf', 'augment'];
const allRuntimes = ['claude', 'kilo', 'opencode', 'gemini', 'codex', 'copilot', 'antigravity', 'cursor', 'windsurf', 'augment', 'trae'];
/**
* Simulate the parsing logic from promptRuntime without requiring readline.
@@ -38,7 +39,7 @@ const allRuntimes = ['claude', 'kilo', 'opencode', 'gemini', 'codex', 'copilot',
function parseRuntimeInput(input) {
input = input.trim() || '1';
if (input === '11') {
if (input === '12') {
return allRuntimes;
}
@@ -88,8 +89,12 @@ describe('multi-runtime selection parsing', () => {
assert.deepStrictEqual(parseRuntimeInput('10'), ['augment']);
});
test('choice 11 returns all runtimes', () => {
assert.deepStrictEqual(parseRuntimeInput('11'), allRuntimes);
test('single choice for trae', () => {
assert.deepStrictEqual(parseRuntimeInput('11'), ['trae']);
});
test('choice 12 returns all runtimes', () => {
assert.deepStrictEqual(parseRuntimeInput('12'), allRuntimes);
});
test('empty input defaults to claude', () => {
@@ -98,13 +103,13 @@ describe('multi-runtime selection parsing', () => {
});
test('invalid choices are ignored, falls back to claude if all invalid', () => {
assert.deepStrictEqual(parseRuntimeInput('12'), ['claude']);
assert.deepStrictEqual(parseRuntimeInput('13'), ['claude']);
assert.deepStrictEqual(parseRuntimeInput('0'), ['claude']);
assert.deepStrictEqual(parseRuntimeInput('abc'), ['claude']);
});
test('invalid choices mixed with valid are filtered out', () => {
assert.deepStrictEqual(parseRuntimeInput('1,11,5'), ['claude', 'codex']);
assert.deepStrictEqual(parseRuntimeInput('1,13,5'), ['claude', 'codex']);
assert.deepStrictEqual(parseRuntimeInput('abc 3 xyz'), ['opencode']);
});
@@ -137,21 +142,21 @@ describe('install.js source contains multi-select support', () => {
}
});
test('all shortcut uses option 11', () => {
test('all shortcut uses option 12', () => {
assert.ok(
installSrc.includes("if (input === '11')"),
'all shortcut uses option 11'
installSrc.includes("if (input === '12')"),
'all shortcut uses option 12'
);
});
test('prompt lists Augment as option 10 and All as option 11', () => {
test('prompt lists Augment as option 10 and All as option 12', () => {
assert.ok(
installSrc.includes('10${reset}) Augment'),
'prompt lists Augment as option 10'
);
assert.ok(
installSrc.includes('11${reset}) All'),
'prompt lists All as option 11'
installSrc.includes('12${reset}) All'),
'prompt lists All as option 12'
);
});

262
tests/trae-install.test.cjs Normal file
View File

@@ -0,0 +1,262 @@
process.env.GSD_TEST_MODE = '1';
const { test, describe, beforeEach, afterEach } = require('node:test');
const assert = require('node:assert');
const fs = require('node:fs');
const path = require('node:path');
const os = require('node:os');
const { createTempDir, cleanup } = require('./helpers.cjs');
const {
getDirName,
getGlobalDir,
getConfigDirFromHome,
convertClaudeToTraeMarkdown,
convertClaudeCommandToTraeSkill,
convertClaudeAgentToTraeAgent,
copyCommandsAsTraeSkills,
install,
uninstall,
writeManifest,
} = require('../bin/install.js');
describe('Trae runtime directory mapping', () => {
test('maps Trae to .trae for local installs', () => {
assert.strictEqual(getDirName('trae'), '.trae');
});
test('maps Trae to ~/.trae for global installs', () => {
assert.strictEqual(getGlobalDir('trae'), path.join(os.homedir(), '.trae'));
});
test('returns .trae config fragments for local and global installs', () => {
assert.strictEqual(getConfigDirFromHome('trae', false), "'.trae'");
assert.strictEqual(getConfigDirFromHome('trae', true), "'.trae'");
});
});
describe('getGlobalDir (Trae)', () => {
let originalTraeConfigDir;
beforeEach(() => {
originalTraeConfigDir = process.env.TRAE_CONFIG_DIR;
});
afterEach(() => {
if (originalTraeConfigDir !== undefined) {
process.env.TRAE_CONFIG_DIR = originalTraeConfigDir;
} else {
delete process.env.TRAE_CONFIG_DIR;
}
});
test('returns ~/.trae with no env var or explicit dir', () => {
delete process.env.TRAE_CONFIG_DIR;
const result = getGlobalDir('trae');
assert.strictEqual(result, path.join(os.homedir(), '.trae'));
});
test('returns explicit dir when provided', () => {
const result = getGlobalDir('trae', '/custom/trae-path');
assert.strictEqual(result, '/custom/trae-path');
});
test('respects TRAE_CONFIG_DIR env var', () => {
process.env.TRAE_CONFIG_DIR = '~/custom-trae';
const result = getGlobalDir('trae');
assert.strictEqual(result, path.join(os.homedir(), 'custom-trae'));
});
test('explicit dir takes priority over TRAE_CONFIG_DIR', () => {
process.env.TRAE_CONFIG_DIR = '~/from-env';
const result = getGlobalDir('trae', '/explicit/path');
assert.strictEqual(result, '/explicit/path');
});
test('does not break other runtimes', () => {
assert.strictEqual(getGlobalDir('claude'), path.join(os.homedir(), '.claude'));
assert.strictEqual(getGlobalDir('codex'), path.join(os.homedir(), '.codex'));
});
});
describe('Trae markdown conversion', () => {
test('converts Claude-specific references to Trae equivalents', () => {
const input = [
'Claude Code reads CLAUDE.md before using .claude/skills/.',
'Run /gsd:plan-phase with $ARGUMENTS.',
'Use Bash(command) and Edit(file).',
].join('\n');
const result = convertClaudeToTraeMarkdown(input);
assert.ok(result.includes('Trae reads .trae/rules/ before using .trae/skills/.'), result);
assert.ok(result.includes('/gsd-plan-phase'), result);
assert.ok(result.includes('{{GSD_ARGS}}'), result);
assert.ok(result.includes('Shell('), result);
assert.ok(result.includes('StrReplace('), result);
});
test('converts commands and agents to Trae frontmatter', () => {
const command = `---
name: gsd:new-project
description: Initialize a project
---
Use .claude/skills/ and /gsd:help.
`;
const agent = `---
name: gsd-planner
description: Planner agent
tools: Read, Write
color: blue
---
Read CLAUDE.md before acting.
`;
const convertedCommand = convertClaudeCommandToTraeSkill(command, 'gsd-new-project');
const convertedAgent = convertClaudeAgentToTraeAgent(agent);
assert.ok(convertedCommand.includes('name: gsd-new-project'), convertedCommand);
assert.ok(!convertedCommand.includes('<trae_skill_adapter>'), convertedCommand);
assert.ok(convertedCommand.includes('.trae/skills/'), convertedCommand);
assert.ok(convertedCommand.includes('/gsd-help'), convertedCommand);
assert.ok(convertedAgent.includes('name: gsd-planner'), convertedAgent);
assert.ok(!convertedAgent.includes('color:'), convertedAgent);
assert.ok(convertedAgent.includes('.trae/rules/'), convertedAgent);
});
});
describe('copyCommandsAsTraeSkills', () => {
let tmpDir;
beforeEach(() => {
tmpDir = createTempDir('gsd-trae-copy-');
});
afterEach(() => {
cleanup(tmpDir);
});
test('creates one skill directory per GSD command', () => {
const srcDir = path.join(__dirname, '..', 'commands', 'gsd');
const skillsDir = path.join(tmpDir, '.trae', 'skills');
copyCommandsAsTraeSkills(srcDir, skillsDir, 'gsd', '$HOME/.trae/', 'trae');
const generated = path.join(skillsDir, 'gsd-help', 'SKILL.md');
assert.ok(fs.existsSync(generated), generated);
const content = fs.readFileSync(generated, 'utf8');
assert.ok(!content.includes('<trae_skill_adapter>'), content);
assert.ok(content.includes('name: gsd-help'), content);
});
});
describe('Trae local install/uninstall', () => {
let tmpDir;
let previousCwd;
beforeEach(() => {
tmpDir = createTempDir('gsd-trae-install-');
previousCwd = process.cwd();
process.chdir(tmpDir);
});
afterEach(() => {
process.chdir(previousCwd);
cleanup(tmpDir);
});
test('installs GSD into ./.trae and removes it cleanly', () => {
const result = install(false, 'trae');
const targetDir = path.join(tmpDir, '.trae');
assert.deepStrictEqual(result, {
settingsPath: null,
settings: null,
statuslineCommand: null,
runtime: 'trae',
configDir: fs.realpathSync(targetDir),
});
assert.ok(fs.existsSync(path.join(targetDir, 'skills', 'gsd-help', 'SKILL.md')));
assert.ok(fs.existsSync(path.join(targetDir, 'get-shit-done', 'VERSION')));
assert.ok(fs.existsSync(path.join(targetDir, 'agents')));
const manifest = writeManifest(targetDir, 'trae');
assert.ok(Object.keys(manifest.files).some(file => file.startsWith('skills/gsd-help/')), manifest);
uninstall(false, 'trae');
assert.ok(!fs.existsSync(path.join(targetDir, 'skills', 'gsd-help')), 'Trae skill directory removed');
assert.ok(!fs.existsSync(path.join(targetDir, 'get-shit-done')), 'get-shit-done removed');
});
});
describe('E2E: Trae uninstall skills cleanup', () => {
let tmpDir;
let previousCwd;
beforeEach(() => {
tmpDir = createTempDir('gsd-trae-uninstall-');
previousCwd = process.cwd();
process.chdir(tmpDir);
});
afterEach(() => {
process.chdir(previousCwd);
cleanup(tmpDir);
});
test('removes all gsd-* skill directories on --trae --uninstall', () => {
const targetDir = path.join(tmpDir, '.trae');
install(false, 'trae');
const skillsDir = path.join(targetDir, 'skills');
assert.ok(fs.existsSync(skillsDir), 'skills dir exists after install');
const installedSkills = fs.readdirSync(skillsDir, { withFileTypes: true })
.filter(e => e.isDirectory() && e.name.startsWith('gsd-'));
assert.ok(installedSkills.length > 0, `found ${installedSkills.length} gsd-* skill dirs before uninstall`);
uninstall(false, 'trae');
if (fs.existsSync(skillsDir)) {
const remainingGsd = fs.readdirSync(skillsDir, { withFileTypes: true })
.filter(e => e.isDirectory() && e.name.startsWith('gsd-'));
assert.strictEqual(remainingGsd.length, 0,
`Expected 0 gsd-* skill dirs after uninstall, found: ${remainingGsd.map(e => e.name).join(', ')}`);
}
});
test('preserves non-GSD skill directories during --trae --uninstall', () => {
const targetDir = path.join(tmpDir, '.trae');
install(false, 'trae');
const customSkillDir = path.join(targetDir, 'skills', 'my-custom-skill');
fs.mkdirSync(customSkillDir, { recursive: true });
fs.writeFileSync(path.join(customSkillDir, 'SKILL.md'), '# My Custom Skill\n');
assert.ok(fs.existsSync(path.join(customSkillDir, 'SKILL.md')), 'custom skill exists before uninstall');
uninstall(false, 'trae');
assert.ok(fs.existsSync(path.join(customSkillDir, 'SKILL.md')),
'Non-GSD skill directory should be preserved after Trae uninstall');
});
test('removes engine directory on --trae --uninstall', () => {
const targetDir = path.join(tmpDir, '.trae');
install(false, 'trae');
assert.ok(fs.existsSync(path.join(targetDir, 'get-shit-done', 'VERSION')),
'engine exists before uninstall');
uninstall(false, 'trae');
assert.ok(!fs.existsSync(path.join(targetDir, 'get-shit-done')),
'get-shit-done engine should be removed after Trae uninstall');
});
});