@junheep/gwt 0.1.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -3
- package/bin/gwt.mjs +206 -47
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,6 +33,38 @@ confirmation. The integration also provides Zsh completion for commands,
|
|
|
33
33
|
options, worktrees, and Git refs. It only changes directories; it does not load
|
|
34
34
|
environment variables or run project hooks.
|
|
35
35
|
|
|
36
|
+
## Coding agents
|
|
37
|
+
|
|
38
|
+
Coding agents do not know about `gwt` and reach for `git worktree add`, which
|
|
39
|
+
skips the copied files, assigned ports, and setup hooks. Install a skill that
|
|
40
|
+
tells them otherwise:
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
gwt skill install claude
|
|
44
|
+
gwt skill install codex
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The installer shows the target path and asks for confirmation before writing.
|
|
48
|
+
Both agents read the same `SKILL.md` format and differ only in location, so
|
|
49
|
+
the installed skill is identical:
|
|
50
|
+
|
|
51
|
+
| Agent | Default | With `--project` |
|
|
52
|
+
| ------ | ------------------------------ | ---------------------------- |
|
|
53
|
+
| Claude | `~/.claude/skills/gwt/SKILL.md` | `.claude/skills/gwt/SKILL.md` |
|
|
54
|
+
| Codex | `~/.agents/skills/gwt/SKILL.md` | `.agents/skills/gwt/SKILL.md` |
|
|
55
|
+
|
|
56
|
+
`--project` writes into the primary worktree so the skill can be committed for
|
|
57
|
+
the team.
|
|
58
|
+
|
|
59
|
+
The skill covers what `gwt --help` does not: that gwt is preferred over native
|
|
60
|
+
`git worktree`, that project hooks need `gwt trust`, that a failed setup is
|
|
61
|
+
retried rather than recreated, that ports come from `gwt info`, and that
|
|
62
|
+
removal is destructive. It points at `gwt <command> --help` for command
|
|
63
|
+
details instead of repeating them, so it does not go stale as gwt changes.
|
|
64
|
+
Reinstall after upgrading to pick up a revised skill; an unchanged file is
|
|
65
|
+
reported as already installed, and a modified one is replaced only after
|
|
66
|
+
confirmation.
|
|
67
|
+
|
|
36
68
|
## Configuration
|
|
37
69
|
|
|
38
70
|
Create user configuration for the current repository:
|
|
@@ -167,6 +199,7 @@ gwt trust [--revoke]
|
|
|
167
199
|
gwt config create [--project]
|
|
168
200
|
gwt config show
|
|
169
201
|
gwt shell install zsh [--dry-run] [--yes]
|
|
202
|
+
gwt skill install <claude|codex> [--project] [--dry-run] [--yes]
|
|
170
203
|
```
|
|
171
204
|
|
|
172
205
|
Run `gwt --help` for the command overview, or `gwt <command> --help` for
|
|
@@ -181,9 +214,9 @@ Setup failures retain the worktree and record the failure. Retry with
|
|
|
181
214
|
`gwt setup <id>` or remove it explicitly.
|
|
182
215
|
|
|
183
216
|
Run `gwt switch` without a target to open the interactive picker. Use the
|
|
184
|
-
arrow keys, `j`/`k`, or Ctrl-n/Ctrl-p to move; press
|
|
185
|
-
|
|
186
|
-
|
|
217
|
+
arrow keys, `j`/`k`, or Ctrl-n/Ctrl-p to move; press `/` to filter by branch,
|
|
218
|
+
ID, or path. Enter switches to the selected worktree. Escape leaves filter
|
|
219
|
+
mode or cancels the picker.
|
|
187
220
|
|
|
188
221
|
`gwt remove` refuses dirty worktrees and first tries to delete the branch with
|
|
189
222
|
`git branch -d`. If Git rejects safe deletion, an interactive terminal asks
|
package/bin/gwt.mjs
CHANGED
|
@@ -23,12 +23,14 @@ import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path"
|
|
|
23
23
|
import { emitKeypressEvents } from "node:readline"
|
|
24
24
|
import { createInterface } from "node:readline/promises"
|
|
25
25
|
|
|
26
|
-
const VERSION = "0.1.0"
|
|
27
26
|
const PROJECT_CONFIG_FILE = ".gwt.json"
|
|
28
27
|
const PORT_MIN = 20_000
|
|
29
28
|
const PORT_MAX = 39_999
|
|
29
|
+
const PICKER_ESCAPE_CODE_TIMEOUT_MS = 50
|
|
30
30
|
const ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/
|
|
31
31
|
const DEFAULT_CONFIG = { worktreeDirectory: ".worktrees", copyFiles: [], ports: [] }
|
|
32
|
+
const SKILL_DIRECTORIES = { claude: ".claude", codex: ".agents" }
|
|
33
|
+
const SKILL_USAGE = `Usage: gwt skill install <${Object.keys(SKILL_DIRECTORIES).join("|")}> [--project] [--dry-run] [--yes]`
|
|
32
34
|
|
|
33
35
|
class CliError extends Error {}
|
|
34
36
|
|
|
@@ -654,17 +656,35 @@ async function commandNew(args) {
|
|
|
654
656
|
}
|
|
655
657
|
}
|
|
656
658
|
|
|
657
|
-
|
|
658
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY) throw new CliError("A worktree selector is required in non-interactive mode")
|
|
659
|
+
function linkedWorktreeRows(repository, metadata = loadMetadata(repository)) {
|
|
659
660
|
const current = currentWorktree(repository)
|
|
660
|
-
|
|
661
|
-
const
|
|
662
|
-
const relativePath = relative(repository.primaryPath, worktree.path)
|
|
661
|
+
return repository.worktrees.map((worktree, index) => {
|
|
662
|
+
const item = metadata.find((entry) => resolve(entry.path) === resolve(worktree.path))
|
|
663
663
|
return {
|
|
664
664
|
worktree,
|
|
665
|
-
current: resolve(current
|
|
665
|
+
current: current && resolve(current.path) === resolve(worktree.path),
|
|
666
|
+
id: item?.id ?? (index === 0 ? "primary" : "-"),
|
|
666
667
|
branch: worktree.branch ?? "(detached)",
|
|
667
|
-
|
|
668
|
+
setup: item?.setup ?? (index === 0 ? "-" : "unmanaged"),
|
|
669
|
+
path: worktree.path,
|
|
670
|
+
}
|
|
671
|
+
})
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function terminalColors() {
|
|
675
|
+
if (!process.stdout.isTTY || process.env.NO_COLOR !== undefined) {
|
|
676
|
+
return { cyan: "", yellow: "", dim: "", reset: "" }
|
|
677
|
+
}
|
|
678
|
+
return { cyan: "\x1b[36m", yellow: "\x1b[33m", dim: "\x1b[2m", reset: "\x1b[0m" }
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
async function chooseWorktree(repository) {
|
|
682
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) throw new CliError("A worktree selector is required in non-interactive mode")
|
|
683
|
+
const choices = linkedWorktreeRows(repository).map((row) => {
|
|
684
|
+
const { worktree } = row
|
|
685
|
+
const relativePath = relative(repository.primaryPath, worktree.path)
|
|
686
|
+
return {
|
|
687
|
+
...row,
|
|
668
688
|
path: relativePath === "" ? "." : relativePath.startsWith(`..${sep}`) ? worktree.path : relativePath,
|
|
669
689
|
}
|
|
670
690
|
})
|
|
@@ -672,12 +692,11 @@ async function chooseWorktree(repository) {
|
|
|
672
692
|
return new Promise((resolveChoice, rejectChoice) => {
|
|
673
693
|
let query = ""
|
|
674
694
|
let filtering = false
|
|
675
|
-
|
|
695
|
+
const initialSelected = Math.max(0, choices.findIndex((choice) => choice.current))
|
|
696
|
+
let selected = initialSelected
|
|
676
697
|
let renderedLines = 0
|
|
677
698
|
const wasRaw = process.stdin.isRaw
|
|
678
|
-
const colors =
|
|
679
|
-
? { cyan: "\x1b[36m", yellow: "\x1b[33m", dim: "\x1b[2m", reset: "\x1b[0m" }
|
|
680
|
-
: { cyan: "", yellow: "", dim: "", reset: "" }
|
|
699
|
+
const colors = terminalColors()
|
|
681
700
|
|
|
682
701
|
const clear = () => {
|
|
683
702
|
if (renderedLines > 0) process.stdout.write(`\x1b[${renderedLines}A\r\x1b[J`)
|
|
@@ -690,22 +709,24 @@ async function chooseWorktree(repository) {
|
|
|
690
709
|
.some((value) => value.toLowerCase().includes(normalizedQuery)))
|
|
691
710
|
if (selected >= filtered.length) selected = Math.max(0, filtered.length - 1)
|
|
692
711
|
|
|
693
|
-
const terminalWidth = Math.max(
|
|
694
|
-
const numberWidth = String(Math.max(1, filtered.length)).length
|
|
712
|
+
const terminalWidth = Math.max(48, process.stdout.columns ?? 100)
|
|
695
713
|
const idWidth = 8
|
|
714
|
+
const setupWidth = Math.max(5, ...filtered.map((choice) => choice.setup.length))
|
|
696
715
|
const longestBranch = Math.max(12, ...filtered.map((choice) => choice.branch.length))
|
|
697
|
-
const
|
|
698
|
-
const
|
|
716
|
+
const flexibleWidth = terminalWidth - idWidth - setupWidth - 10
|
|
717
|
+
const branchWidth = Math.min(32, longestBranch, Math.max(8, flexibleWidth - 8))
|
|
718
|
+
const pathWidth = Math.max(8, flexibleWidth - branchWidth)
|
|
699
719
|
const fit = (value, width) => value.length > width
|
|
700
720
|
? `${value.slice(0, Math.max(0, width - 1))}…`
|
|
701
721
|
: value.padEnd(width)
|
|
702
722
|
const visibleCount = Math.max(3, (process.stdout.rows ?? 24) - 5)
|
|
703
723
|
const start = Math.max(0, Math.min(selected - Math.floor(visibleCount / 2), filtered.length - visibleCount))
|
|
704
724
|
const visible = filtered.slice(start, start + visibleCount)
|
|
725
|
+
const escapeAction = filtering ? "clear" : "cancel"
|
|
705
726
|
const lines = [
|
|
706
|
-
`${colors.dim}${fit(
|
|
707
|
-
|
|
708
|
-
`${colors.dim}
|
|
727
|
+
`${colors.dim}${fit(`Switch worktree Esc ${escapeAction} · Enter select · ↑↓/jk/C-n/C-p · / filter`, terminalWidth)}${colors.reset}`,
|
|
728
|
+
...(filtering ? [fit(`Filter: /${query}`, terminalWidth)] : []),
|
|
729
|
+
`${colors.dim} ${fit("BRANCH", branchWidth)} ${fit("ID", idWidth)} ${fit("SETUP", setupWidth)} ${fit("PATH", pathWidth)}${colors.reset}`,
|
|
709
730
|
]
|
|
710
731
|
|
|
711
732
|
if (visible.length === 0) {
|
|
@@ -714,8 +735,8 @@ async function chooseWorktree(repository) {
|
|
|
714
735
|
visible.forEach((choice, visibleIndex) => {
|
|
715
736
|
const index = start + visibleIndex
|
|
716
737
|
const selection = index === selected ? `${colors.cyan}>${colors.reset}` : " "
|
|
717
|
-
const currentMarker = choice.current ? `${colors.yellow}
|
|
718
|
-
lines.push(`${selection} ${
|
|
738
|
+
const currentMarker = choice.current ? `${colors.yellow}*${colors.reset}` : " "
|
|
739
|
+
lines.push(`${selection} ${currentMarker} ${fit(choice.branch, branchWidth)} ${fit(choice.id, idWidth)} ${fit(choice.setup, setupWidth)} ${fit(choice.path, pathWidth)}`)
|
|
719
740
|
})
|
|
720
741
|
}
|
|
721
742
|
|
|
@@ -746,7 +767,10 @@ async function chooseWorktree(repository) {
|
|
|
746
767
|
}
|
|
747
768
|
if (key.name === "escape") {
|
|
748
769
|
if (filtering) {
|
|
770
|
+
const selectedChoice = filtered[selected]
|
|
749
771
|
filtering = false
|
|
772
|
+
query = ""
|
|
773
|
+
selected = selectedChoice ? choices.indexOf(selectedChoice) : initialSelected
|
|
750
774
|
render()
|
|
751
775
|
} else {
|
|
752
776
|
finish(new CliError("Selection cancelled"))
|
|
@@ -767,11 +791,6 @@ async function chooseWorktree(repository) {
|
|
|
767
791
|
} else if (filtering && key.name === "backspace") {
|
|
768
792
|
query = [...query].slice(0, -1).join("")
|
|
769
793
|
selected = 0
|
|
770
|
-
} else if (!filtering && /^[1-9]$/.test(text)) {
|
|
771
|
-
const choice = filtered[Number(text) - 1]
|
|
772
|
-
if (choice) finish(null, choice)
|
|
773
|
-
else process.stdout.write("\x07")
|
|
774
|
-
return
|
|
775
794
|
} else if (filtering && text && !key.ctrl && !key.meta) {
|
|
776
795
|
query += text.replace(/[\x00-\x1f\x7f]/g, "")
|
|
777
796
|
selected = 0
|
|
@@ -779,7 +798,7 @@ async function chooseWorktree(repository) {
|
|
|
779
798
|
render()
|
|
780
799
|
}
|
|
781
800
|
|
|
782
|
-
emitKeypressEvents(process.stdin)
|
|
801
|
+
emitKeypressEvents(process.stdin, { escapeCodeTimeout: PICKER_ESCAPE_CODE_TIMEOUT_MS })
|
|
783
802
|
process.stdin.on("keypress", onKeypress)
|
|
784
803
|
process.stdout.on("resize", render)
|
|
785
804
|
process.stdin.setRawMode(true)
|
|
@@ -798,18 +817,8 @@ async function commandSwitch(args) {
|
|
|
798
817
|
}
|
|
799
818
|
|
|
800
819
|
function worktreeRows(repository) {
|
|
801
|
-
const current = currentWorktree(repository)
|
|
802
820
|
const metadata = loadMetadata(repository)
|
|
803
|
-
const rows = repository
|
|
804
|
-
const item = metadata.find((entry) => resolve(entry.path) === resolve(worktree.path))
|
|
805
|
-
return {
|
|
806
|
-
current: current && resolve(current.path) === resolve(worktree.path),
|
|
807
|
-
id: item?.id ?? (index === 0 ? "primary" : "-"),
|
|
808
|
-
branch: worktree.branch ?? "(detached)",
|
|
809
|
-
setup: item?.setup ?? (index === 0 ? "-" : "unmanaged"),
|
|
810
|
-
path: worktree.path,
|
|
811
|
-
}
|
|
812
|
-
})
|
|
821
|
+
const rows = linkedWorktreeRows(repository, metadata)
|
|
813
822
|
const registeredPaths = new Set(repository.worktrees.map((worktree) => resolve(worktree.path)))
|
|
814
823
|
for (const item of metadata.filter((entry) => !registeredPaths.has(resolve(entry.path)))) {
|
|
815
824
|
rows.push({ current: false, id: item.id, branch: "-", setup: "stale", path: item.path })
|
|
@@ -849,14 +858,16 @@ function commandList(args) {
|
|
|
849
858
|
if (args.length > 0) throw new CliError("Usage: gwt list")
|
|
850
859
|
const repository = discoverRepository()
|
|
851
860
|
const rows = worktreeRows(repository)
|
|
861
|
+
const colors = terminalColors()
|
|
852
862
|
const widths = {
|
|
853
|
-
id: Math.max(2, ...rows.map((row) => displayWidth(row.id))),
|
|
854
863
|
branch: Math.max(6, ...rows.map((row) => displayWidth(row.branch))),
|
|
864
|
+
id: Math.max(2, ...rows.map((row) => displayWidth(row.id))),
|
|
855
865
|
setup: Math.max(5, ...rows.map((row) => displayWidth(row.setup))),
|
|
856
866
|
}
|
|
857
|
-
console.log(
|
|
867
|
+
console.log(`${colors.dim} ${padDisplay("BRANCH", widths.branch)} ${padDisplay("ID", widths.id)} ${padDisplay("SETUP", widths.setup)} PATH${colors.reset}`)
|
|
858
868
|
for (const row of rows) {
|
|
859
|
-
|
|
869
|
+
const currentMarker = row.current ? `${colors.yellow}*${colors.reset}` : " "
|
|
870
|
+
console.log(`${currentMarker} ${padDisplay(row.branch, widths.branch)} ${padDisplay(row.id, widths.id)} ${padDisplay(row.setup, widths.setup)} ${row.path}`)
|
|
860
871
|
}
|
|
861
872
|
}
|
|
862
873
|
|
|
@@ -1076,6 +1087,7 @@ if command -v gwt >/dev/null 2>&1; then
|
|
|
1076
1087
|
'trust:Approve project hooks'
|
|
1077
1088
|
'config:Manage user and project configuration'
|
|
1078
1089
|
'shell:Install shell integration'
|
|
1090
|
+
'skill:Install the gwt skill for coding agents'
|
|
1079
1091
|
)
|
|
1080
1092
|
|
|
1081
1093
|
if (( CURRENT == 2 )); then
|
|
@@ -1133,6 +1145,15 @@ if command -v gwt >/dev/null 2>&1; then
|
|
|
1133
1145
|
'--yes[skip installation confirmation]' \
|
|
1134
1146
|
'(-h --help)'{-h,--help}'[show help]'
|
|
1135
1147
|
;;
|
|
1148
|
+
skill)
|
|
1149
|
+
_arguments \
|
|
1150
|
+
'2:action:(install)' \
|
|
1151
|
+
'3:agent:(claude codex)' \
|
|
1152
|
+
'--project[install into the repository instead of the home directory]' \
|
|
1153
|
+
'--dry-run[show the change without writing]' \
|
|
1154
|
+
'--yes[skip installation confirmation]' \
|
|
1155
|
+
'(-h --help)'{-h,--help}'[show help]'
|
|
1156
|
+
;;
|
|
1136
1157
|
esac
|
|
1137
1158
|
}
|
|
1138
1159
|
|
|
@@ -1142,6 +1163,92 @@ if command -v gwt >/dev/null 2>&1; then
|
|
|
1142
1163
|
fi`
|
|
1143
1164
|
}
|
|
1144
1165
|
|
|
1166
|
+
function agentSkill() {
|
|
1167
|
+
return `---
|
|
1168
|
+
name: gwt
|
|
1169
|
+
description: Use gwt to create, list, switch, and remove Git worktrees. Use whenever a task needs an isolated worktree, or in place of running 'git worktree' directly.
|
|
1170
|
+
---
|
|
1171
|
+
|
|
1172
|
+
gwt wraps native Git worktrees and prepares each one with the project's local
|
|
1173
|
+
files, assigned ports, and setup hooks.
|
|
1174
|
+
|
|
1175
|
+
## Prefer gwt over 'git worktree'
|
|
1176
|
+
|
|
1177
|
+
Create worktrees with \`gwt new\`, not \`git worktree add\`. A worktree added with
|
|
1178
|
+
plain Git skips the configured file copies, port assignment, and postCreate
|
|
1179
|
+
hook, and records no gwt metadata, so \`gwt list\` and \`gwt remove\` cannot manage
|
|
1180
|
+
it. Adopt an existing one with \`gwt setup <path>\`.
|
|
1181
|
+
|
|
1182
|
+
## Read the help instead of guessing flags
|
|
1183
|
+
|
|
1184
|
+
\`gwt --help\` lists the commands, \`gwt <command> --help\` documents arguments,
|
|
1185
|
+
options, and behavior, and nested commands such as \`gwt config create --help\`
|
|
1186
|
+
have their own help. This skill does not repeat command signatures so that they
|
|
1187
|
+
stay accurate across versions.
|
|
1188
|
+
|
|
1189
|
+
## What the help does not make obvious
|
|
1190
|
+
|
|
1191
|
+
- Hooks declared by a committed \`.gwt.json\` do not run until the repository is
|
|
1192
|
+
approved with \`gwt trust\`. Approval is invalidated whenever the config or a
|
|
1193
|
+
hook changes, so a repository that worked before can start asking again.
|
|
1194
|
+
- A failed setup keeps the worktree and records the failure. Retry it with
|
|
1195
|
+
\`gwt setup <id>\` rather than removing and recreating the worktree.
|
|
1196
|
+
- Ports are assigned per worktree. Read them from \`gwt info\` instead of assuming
|
|
1197
|
+
a project default; two worktrees of the same project never share a port.
|
|
1198
|
+
- \`gwt switch\` changes the shell's directory only when the shell integration is
|
|
1199
|
+
installed. Otherwise it just prints the path.
|
|
1200
|
+
- \`gwt switch\` with no target opens an interactive picker, so always pass an
|
|
1201
|
+
explicit target when running non-interactively.
|
|
1202
|
+
|
|
1203
|
+
## Removal is destructive
|
|
1204
|
+
|
|
1205
|
+
\`gwt remove\` deletes the worktree and, by default, its branch. Confirm with the
|
|
1206
|
+
user before running it, and never pass \`--discard --yes\` on your own: together
|
|
1207
|
+
they discard uncommitted changes and force-delete an unmerged branch.
|
|
1208
|
+
`
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
function skillPath(agent, project) {
|
|
1212
|
+
const base = project ? discoverRepository().primaryPath : homedir()
|
|
1213
|
+
return join(base, SKILL_DIRECTORIES[agent], "skills", "gwt", "SKILL.md")
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
async function installSkill(agent, args) {
|
|
1217
|
+
const { options, positionals } = parseOptions(args, {
|
|
1218
|
+
"--project": "boolean",
|
|
1219
|
+
"--dry-run": "boolean",
|
|
1220
|
+
"--yes": "boolean",
|
|
1221
|
+
})
|
|
1222
|
+
if (positionals.length > 0) throw new CliError(SKILL_USAGE)
|
|
1223
|
+
|
|
1224
|
+
const path = skillPath(agent, options.project)
|
|
1225
|
+
const contents = agentSkill()
|
|
1226
|
+
const existing = existsSync(path) ? readFileSync(path, "utf8") : null
|
|
1227
|
+
if (existing === contents) {
|
|
1228
|
+
console.log(`Skill is already installed in ${path}`)
|
|
1229
|
+
return
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
const verb = existing === null ? "Create" : "Replace"
|
|
1233
|
+
console.log(`${verb} ${path}`)
|
|
1234
|
+
if (options["dry-run"]) {
|
|
1235
|
+
console.log(`\n${contents}`)
|
|
1236
|
+
return
|
|
1237
|
+
}
|
|
1238
|
+
if (!options.yes && !(await ask(`${verb}? [y/N] `))) throw new CliError("Skill installation cancelled")
|
|
1239
|
+
|
|
1240
|
+
mkdirSync(dirname(path), { recursive: true })
|
|
1241
|
+
writeFileSync(path, contents)
|
|
1242
|
+
console.log(`Installed skill in ${path}`)
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
async function commandSkill(args) {
|
|
1246
|
+
if (args[0] === "install" && Object.hasOwn(SKILL_DIRECTORIES, args[1])) {
|
|
1247
|
+
return installSkill(args[1], args.slice(2))
|
|
1248
|
+
}
|
|
1249
|
+
throw new CliError(SKILL_USAGE)
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1145
1252
|
function zshConfigPath() {
|
|
1146
1253
|
return join(process.env.ZDOTDIR ? resolve(process.env.ZDOTDIR) : homedir(), ".zshrc")
|
|
1147
1254
|
}
|
|
@@ -1211,10 +1318,15 @@ function commandComplete(args) {
|
|
|
1211
1318
|
throw new CliError("Invalid completion request")
|
|
1212
1319
|
}
|
|
1213
1320
|
|
|
1321
|
+
function version() {
|
|
1322
|
+
const path = join(import.meta.dirname, "..", "package.json")
|
|
1323
|
+
return JSON.parse(readFileSync(path, "utf8")).version
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1214
1326
|
function help(command, subcommand) {
|
|
1215
1327
|
const topic = [command, subcommand].filter(Boolean).join(" ")
|
|
1216
1328
|
const texts = {
|
|
1217
|
-
"": `gwt ${
|
|
1329
|
+
"": `gwt ${version()} - lightweight native Git worktree workflows
|
|
1218
1330
|
|
|
1219
1331
|
Usage:
|
|
1220
1332
|
gwt <command> [options]
|
|
@@ -1229,6 +1341,7 @@ Commands:
|
|
|
1229
1341
|
trust Approve or revoke repository project hooks
|
|
1230
1342
|
config Create or inspect configuration
|
|
1231
1343
|
shell Install shell integration
|
|
1344
|
+
skill Install the gwt skill for coding agents
|
|
1232
1345
|
|
|
1233
1346
|
Options:
|
|
1234
1347
|
-h, --help Show help.
|
|
@@ -1240,6 +1353,7 @@ Examples:
|
|
|
1240
1353
|
gwt remove
|
|
1241
1354
|
gwt config create
|
|
1242
1355
|
gwt shell install zsh
|
|
1356
|
+
gwt skill install claude
|
|
1243
1357
|
|
|
1244
1358
|
Run 'gwt <command> --help' for command behavior and more examples.`,
|
|
1245
1359
|
new: `Create a worktree, prepare its development environment, and switch to it.
|
|
@@ -1313,9 +1427,9 @@ Options:
|
|
|
1313
1427
|
-h, --help Show help for this command.
|
|
1314
1428
|
|
|
1315
1429
|
Behavior:
|
|
1316
|
-
The picker supports arrow keys, j/k, Ctrl-n/Ctrl-p,
|
|
1317
|
-
|
|
1318
|
-
|
|
1430
|
+
The picker supports arrow keys, j/k, Ctrl-n/Ctrl-p, and '/' filtering. Shell
|
|
1431
|
+
integration must be installed for gwt to change the parent shell's directory;
|
|
1432
|
+
otherwise the selected path is only printed.
|
|
1319
1433
|
|
|
1320
1434
|
Examples:
|
|
1321
1435
|
gwt switch
|
|
@@ -1460,6 +1574,50 @@ when ZDOTDIR is set. Restart Zsh or source the file after installation.
|
|
|
1460
1574
|
Examples:
|
|
1461
1575
|
gwt shell install zsh
|
|
1462
1576
|
gwt shell install zsh --dry-run`,
|
|
1577
|
+
skill: `Install the gwt skill so coding agents use gwt correctly.
|
|
1578
|
+
|
|
1579
|
+
Usage:
|
|
1580
|
+
gwt skill install <claude|codex> [--project] [--dry-run] [--yes]
|
|
1581
|
+
|
|
1582
|
+
Options:
|
|
1583
|
+
-h, --help Show help for this command.
|
|
1584
|
+
|
|
1585
|
+
The skill teaches an agent to prefer gwt over native 'git worktree', to read
|
|
1586
|
+
'gwt <command> --help' for command details, and to treat removal as
|
|
1587
|
+
destructive. It does not duplicate command signatures, so it stays accurate
|
|
1588
|
+
as gwt changes.
|
|
1589
|
+
|
|
1590
|
+
Examples:
|
|
1591
|
+
gwt skill install claude
|
|
1592
|
+
gwt skill install codex`,
|
|
1593
|
+
"skill install": `Install the gwt skill for a coding agent.
|
|
1594
|
+
|
|
1595
|
+
Usage:
|
|
1596
|
+
gwt skill install <claude|codex> [--project] [--dry-run] [--yes]
|
|
1597
|
+
|
|
1598
|
+
Arguments:
|
|
1599
|
+
claude Install for Claude Code, under .claude/skills.
|
|
1600
|
+
codex Install for Codex, under .agents/skills.
|
|
1601
|
+
|
|
1602
|
+
Options:
|
|
1603
|
+
--project Write the skill inside the primary worktree, so it can be
|
|
1604
|
+
committed for the team, instead of the home directory.
|
|
1605
|
+
--dry-run Print the target path and the skill without writing it.
|
|
1606
|
+
--yes Install without asking for confirmation.
|
|
1607
|
+
-h, --help Show help for this command.
|
|
1608
|
+
|
|
1609
|
+
Both agents read the same SKILL.md format and only differ in location, so the
|
|
1610
|
+
installed skill is identical. Install it once per agent.
|
|
1611
|
+
|
|
1612
|
+
Reinstall after upgrading gwt to pick up a revised skill. The command reports
|
|
1613
|
+
an unchanged file as already installed and asks before replacing a modified
|
|
1614
|
+
one.
|
|
1615
|
+
|
|
1616
|
+
Examples:
|
|
1617
|
+
gwt skill install claude
|
|
1618
|
+
gwt skill install codex
|
|
1619
|
+
gwt skill install codex --project
|
|
1620
|
+
gwt skill install claude --dry-run`,
|
|
1463
1621
|
}
|
|
1464
1622
|
|
|
1465
1623
|
if (!Object.hasOwn(texts, topic)) throw new CliError(`Unknown help topic: ${topic}`)
|
|
@@ -1470,9 +1628,9 @@ async function main() {
|
|
|
1470
1628
|
const [command, ...args] = process.argv.slice(2)
|
|
1471
1629
|
if (!command || command === "--help" || command === "-h") return help()
|
|
1472
1630
|
if (command === "help") return help(args[0], args[1])
|
|
1473
|
-
if (command === "--version" || command === "-V") return console.log(
|
|
1631
|
+
if (command === "--version" || command === "-V") return console.log(version())
|
|
1474
1632
|
if (args.includes("--help") || args.includes("-h")) {
|
|
1475
|
-
const subcommand = ["config", "shell"].includes(command)
|
|
1633
|
+
const subcommand = ["config", "shell", "skill"].includes(command)
|
|
1476
1634
|
? args.find((argument) => !argument.startsWith("-"))
|
|
1477
1635
|
: undefined
|
|
1478
1636
|
return help(command, subcommand)
|
|
@@ -1486,6 +1644,7 @@ async function main() {
|
|
|
1486
1644
|
if (command === "trust") return commandTrust(args)
|
|
1487
1645
|
if (command === "config") return commandConfig(args)
|
|
1488
1646
|
if (command === "shell") return commandShell(args)
|
|
1647
|
+
if (command === "skill") return commandSkill(args)
|
|
1489
1648
|
if (command === "__complete") return commandComplete(args)
|
|
1490
1649
|
throw new CliError(`Unknown command: ${command}`)
|
|
1491
1650
|
}
|