@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.
Files changed (3) hide show
  1. package/README.md +36 -3
  2. package/bin/gwt.mjs +206 -47
  3. 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 1–9 to select a numbered
185
- row immediately; or press `/` to filter by branch, ID, or path. Enter switches
186
- to the selected worktree. Escape leaves filter mode or cancels the picker.
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
- async function chooseWorktree(repository) {
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
- const choices = repository.worktrees.map((worktree, index) => {
661
- const metadata = metadataForWorktree(repository, worktree)
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?.path ?? "") === resolve(worktree.path),
665
+ current: current && resolve(current.path) === resolve(worktree.path),
666
+ id: item?.id ?? (index === 0 ? "primary" : "-"),
666
667
  branch: worktree.branch ?? "(detached)",
667
- id: metadata?.id ?? (index === 0 ? "primary" : "native"),
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
- let selected = Math.max(0, choices.findIndex((choice) => choice.current))
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 = process.env.NO_COLOR === undefined
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(40, process.stdout.columns ?? 100)
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 branchWidth = Math.min(32, longestBranch, terminalWidth - numberWidth - idWidth - 22)
698
- const pathWidth = Math.max(8, terminalWidth - numberWidth - branchWidth - idWidth - 10)
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("Switch worktree ↑↓/jk/C-n/C-p move · 1-9 select · / filter · Enter", terminalWidth)}${colors.reset}`,
707
- fit(`Filter: ${filtering ? "/" : ""}${query}`, terminalWidth),
708
- `${colors.dim} ${fit("#", numberWidth)} ${fit("BRANCH", branchWidth)} ${fit("ID", idWidth)} ${fit("PATH", pathWidth)}${colors.reset}`,
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}@${colors.reset}` : " "
718
- lines.push(`${selection} ${fit(String(index + 1), numberWidth)} ${currentMarker} ${fit(choice.branch, branchWidth)} ${fit(choice.id, idWidth)} ${fit(choice.path, pathWidth)}`)
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.worktrees.map((worktree, index) => {
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(` ${padDisplay("ID", widths.id)} ${padDisplay("BRANCH", widths.branch)} ${padDisplay("SETUP", widths.setup)} PATH`)
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
- console.log(`${row.current ? "*" : " "} ${padDisplay(row.id, widths.id)} ${padDisplay(row.branch, widths.branch)} ${padDisplay(row.setup, widths.setup)} ${row.path}`)
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 ${VERSION} - lightweight native Git worktree workflows
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, number shortcuts, and
1317
- '/' filtering. Shell integration must be installed for gwt to change the
1318
- parent shell's directory; otherwise the selected path is only printed.
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(VERSION)
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@junheep/gwt",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "description": "Lightweight native Git worktree workflows",
5
5
  "license": "MIT",
6
6
  "author": "Junhee Park",