opencode-homebrew-agent 1.0.0

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 (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +175 -0
  3. package/agents/brew.md +146 -0
  4. package/package.json +22 -0
  5. package/src/AGENTS.md +172 -0
  6. package/src/scripts/brew-analyze.sh +373 -0
  7. package/src/scripts/brew-deps.sh +140 -0
  8. package/src/scripts/brew-env.sh +288 -0
  9. package/src/scripts/brew-search.sh +150 -0
  10. package/src/scripts/brew-template.sh +263 -0
  11. package/src/scripts/package.json +21 -0
  12. package/src/skills/homebrew-agent/SKILL.md +203 -0
  13. package/src/skills/homebrew-tap/SKILL.md +97 -0
  14. package/src/templates/taps/01-full-ci/AGENTS.md +71 -0
  15. package/src/templates/taps/01-full-ci/tap-structure/.github/workflows/autobump.yml +24 -0
  16. package/src/templates/taps/01-full-ci/tap-structure/.github/workflows/publish.yml +33 -0
  17. package/src/templates/taps/01-full-ci/tap-structure/.github/workflows/tests.yml +33 -0
  18. package/src/templates/taps/01-full-ci/tap-structure/Formula/_formula.rb +31 -0
  19. package/src/templates/taps/01-full-ci/tap-structure/README.md +27 -0
  20. package/src/templates/taps/01-full-ci/tap-structure/cmd/.gitkeep +0 -0
  21. package/src/templates/taps/01-full-ci/tap-structure/formula_renames.json +1 -0
  22. package/src/templates/taps/01-full-ci/tap-structure/lib/.gitkeep +0 -0
  23. package/src/templates/taps/01-full-ci/tap-structure/require/.gitkeep +0 -0
  24. package/src/templates/taps/01-full-ci/tap-structure/tap_migrations.json +1 -0
  25. package/src/templates/taps/01-full-ci/template.md +45 -0
  26. package/src/templates/taps/02-root-level/AGENTS.md +72 -0
  27. package/src/templates/taps/02-root-level/tap-structure/README.md +27 -0
  28. package/src/templates/taps/02-root-level/tap-structure/_formula.rb +29 -0
  29. package/src/templates/taps/02-root-level/template.md +35 -0
  30. package/src/templates/taps/03-simple-script/AGENTS.md +71 -0
  31. package/src/templates/taps/03-simple-script/tap-structure/Formula/_formula.rb +27 -0
  32. package/src/templates/taps/03-simple-script/tap-structure/README.md +27 -0
  33. package/src/templates/taps/03-simple-script/tap-structure/scripts/release.rb +46 -0
  34. package/src/templates/taps/03-simple-script/template.md +41 -0
  35. package/src/templates/taps/04-python-venv/AGENTS.md +83 -0
  36. package/src/templates/taps/04-python-venv/tap-structure/Formula/_formula.rb +57 -0
  37. package/src/templates/taps/04-python-venv/tap-structure/README.md +27 -0
  38. package/src/templates/taps/04-python-venv/tap-structure/scripts/release.rb +44 -0
  39. package/src/templates/taps/04-python-venv/template.md +58 -0
  40. package/src/templates/taps/05-node-npm/AGENTS.md +90 -0
  41. package/src/templates/taps/05-node-npm/tap-structure/Formula/_formula.rb +46 -0
  42. package/src/templates/taps/05-node-npm/tap-structure/README.md +27 -0
  43. package/src/templates/taps/05-node-npm/tap-structure/scripts/release.rb +40 -0
  44. package/src/templates/taps/05-node-npm/template.md +74 -0
  45. package/src/templates/taps/06-keg-only/AGENTS.md +82 -0
  46. package/src/templates/taps/06-keg-only/tap-structure/Formula/_formula.rb +45 -0
  47. package/src/templates/taps/06-keg-only/tap-structure/README.md +27 -0
  48. package/src/templates/taps/06-keg-only/template.md +60 -0
  49. package/src/templates/taps/07-cask-only/AGENTS.md +97 -0
  50. package/src/templates/taps/07-cask-only/tap-structure/Casks/_app.rb +26 -0
  51. package/src/templates/taps/07-cask-only/tap-structure/README.md +27 -0
  52. package/src/templates/taps/07-cask-only/template.md +58 -0
  53. package/src/templates/taps/08-go-binary/AGENTS.md +86 -0
  54. package/src/templates/taps/08-go-binary/tap-structure/Formula/_formula.rb +40 -0
  55. package/src/templates/taps/08-go-binary/tap-structure/README.md +27 -0
  56. package/src/templates/taps/08-go-binary/tap-structure/scripts/release.rb +38 -0
  57. package/src/templates/taps/08-go-binary/template.md +60 -0
  58. package/src/workflows/analyze-source.sh +124 -0
  59. package/src/workflows/convert-npm-to-bun.sh +112 -0
  60. package/src/workflows/create-tap.sh +196 -0
@@ -0,0 +1,288 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ # scripts/brew-env.sh
5
+ # Inspect the Homebrew environment and output a formatted report.
6
+
7
+ PROG="brew-env.sh"
8
+
9
+ usage() {
10
+ cat <<EOF
11
+ Usage: ${PROG} [OPTION]
12
+
13
+ Inspect the Homebrew environment.
14
+
15
+ Options:
16
+ --config Show only the brew config section
17
+ --taps Show only tapped repositories
18
+ --cellar Show only Cellar contents summary
19
+ --ruby Show only Ruby version info
20
+ --json Output full report as JSON
21
+ --help Show this help message and exit
22
+
23
+ With no arguments, prints a full human-readable report.
24
+ EOF
25
+ }
26
+
27
+ # Diagnostics go to stderr
28
+ die() {
29
+ echo "${PROG}: error: $1" >&2
30
+ exit 1
31
+ }
32
+
33
+ # ---------------------------------------------------------------------------
34
+ # Data collection
35
+ # ---------------------------------------------------------------------------
36
+
37
+ get_brew_config() {
38
+ if command -v brew >/dev/null 2>&1; then
39
+ brew config 2>/dev/null | grep -E '^[A-Z_]+:' || true
40
+ else
41
+ echo "brew not found"
42
+ fi
43
+ }
44
+
45
+ get_taps() {
46
+ if command -v brew >/dev/null 2>&1; then
47
+ brew tap 2>/dev/null || true
48
+ else
49
+ echo "brew not found"
50
+ fi
51
+ }
52
+
53
+ get_cellar() {
54
+ local cellar_path
55
+ if command -v brew >/dev/null 2>&1; then
56
+ cellar_path="$(brew --prefix 2>/dev/null)/Cellar"
57
+ else
58
+ cellar_path="/opt/homebrew/Cellar"
59
+ fi
60
+
61
+ if [[ -d "${cellar_path}" ]]; then
62
+ ls -1 "${cellar_path}" 2>/dev/null || true
63
+ else
64
+ echo "Cellar not found at ${cellar_path}"
65
+ fi
66
+ }
67
+
68
+ get_ruby_version() {
69
+ if command -v brew >/dev/null 2>&1; then
70
+ brew ruby --version 2>/dev/null || ruby --version 2>/dev/null || echo "Ruby not found"
71
+ else
72
+ ruby --version 2>/dev/null || echo "Ruby not found"
73
+ fi
74
+ }
75
+
76
+ get_prefix() {
77
+ if command -v brew >/dev/null 2>&1; then
78
+ brew --prefix 2>/dev/null || echo "unknown"
79
+ else
80
+ echo "brew not found"
81
+ fi
82
+ }
83
+
84
+ get_github_api_status() {
85
+ local status
86
+ status=$(curl -sI https://api.github.com 2>/dev/null | head -n 1 | tr -d '\r')
87
+ if [[ -z "${status}" ]]; then
88
+ echo "unreachable"
89
+ else
90
+ echo "${status}"
91
+ fi
92
+ }
93
+
94
+ # ---------------------------------------------------------------------------
95
+ # Output formatters
96
+ # ---------------------------------------------------------------------------
97
+
98
+ print_header() {
99
+ echo ""
100
+ echo "== $1 =="
101
+ }
102
+
103
+ print_kv() {
104
+ local key value
105
+ key="$1"
106
+ value="$2"
107
+ printf " %-30s %s\n" "${key}:" "${value}"
108
+ }
109
+
110
+ output_human() {
111
+ local show_config=false
112
+ local show_taps=false
113
+ local show_cellar=false
114
+ local show_ruby=false
115
+
116
+ if [[ -z "${1:-}" ]]; then
117
+ show_config=true
118
+ show_taps=true
119
+ show_cellar=true
120
+ show_ruby=true
121
+ else
122
+ case "$1" in
123
+ --config) show_config=true ;;
124
+ --taps) show_taps=true ;;
125
+ --cellar) show_cellar=true ;;
126
+ --ruby) show_ruby=true ;;
127
+ esac
128
+ fi
129
+
130
+ if [[ "${show_config}" == true ]]; then
131
+ print_header "Homebrew Config"
132
+ while IFS= read -r line; do
133
+ if [[ -n "${line}" ]]; then
134
+ local key value
135
+ key="${line%%:*}"
136
+ value="${line#*: }"
137
+ print_kv "${key}" "${value}"
138
+ fi
139
+ done <<< "$(get_brew_config)"
140
+ fi
141
+
142
+ if [[ "${show_taps}" == true ]]; then
143
+ print_header "Tapped Repositories"
144
+ local taps
145
+ taps=$(get_taps)
146
+ if [[ -z "${taps}" ]]; then
147
+ echo " (none)"
148
+ else
149
+ while IFS= read -r line; do
150
+ echo " ${line}"
151
+ done <<< "${taps}"
152
+ fi
153
+ fi
154
+
155
+ if [[ "${show_cellar}" == true ]]; then
156
+ print_header "Cellar Contents"
157
+ local cellar
158
+ cellar=$(get_cellar)
159
+ if [[ -z "${cellar}" ]]; then
160
+ echo " (empty)"
161
+ else
162
+ while IFS= read -r line; do
163
+ echo " ${line}"
164
+ done <<< "${cellar}"
165
+ fi
166
+ fi
167
+
168
+ if [[ "${show_ruby}" == true ]]; then
169
+ print_header "Ruby Version"
170
+ echo " $(get_ruby_version)"
171
+ print_header "Homebrew Prefix"
172
+ echo " $(get_prefix)"
173
+ print_header "GitHub API Access"
174
+ echo " $(get_github_api_status)"
175
+ fi
176
+ }
177
+
178
+ # Simple JSON string escaping helper
179
+ json_escape() {
180
+ local str="$1"
181
+ str="${str//\\/\\\\}"
182
+ str="${str//\"/\\\"}"
183
+ str="${str//$'\n'/\\n}"
184
+ str="${str//$'\r'/}"
185
+ printf '%s' "$str"
186
+ }
187
+
188
+ output_json() {
189
+ local config_lines taps_lines cellar_lines ruby_version prefix github_status
190
+ config_lines=$(get_brew_config)
191
+ taps_lines=$(get_taps)
192
+ cellar_lines=$(get_cellar)
193
+ ruby_version=$(get_ruby_version)
194
+ prefix=$(get_prefix)
195
+ github_status=$(get_github_api_status)
196
+
197
+ echo "{"
198
+
199
+ # config object
200
+ echo ' "config": {'
201
+ local first=true
202
+ while IFS= read -r line; do
203
+ if [[ -n "${line}" ]]; then
204
+ local key value
205
+ key="${line%%:*}"
206
+ value="${line#*: }"
207
+ if [[ "${first}" == true ]]; then
208
+ first=false
209
+ else
210
+ echo ","
211
+ fi
212
+ printf ' "%s": "%s"' "$(json_escape "${key}")" "$(json_escape "${value}")"
213
+ fi
214
+ done <<< "${config_lines}"
215
+ echo ""
216
+ echo " },"
217
+
218
+ # taps array
219
+ echo ' "taps": ['
220
+ first=true
221
+ while IFS= read -r line; do
222
+ if [[ -n "${line}" ]]; then
223
+ if [[ "${first}" == true ]]; then
224
+ first=false
225
+ else
226
+ echo ","
227
+ fi
228
+ printf ' "%s"' "$(json_escape "${line}")"
229
+ fi
230
+ done <<< "${taps_lines}"
231
+ echo ""
232
+ echo " ],"
233
+
234
+ # cellar array
235
+ echo ' "cellar": ['
236
+ first=true
237
+ while IFS= read -r line; do
238
+ if [[ -n "${line}" ]]; then
239
+ if [[ "${first}" == true ]]; then
240
+ first=false
241
+ else
242
+ echo ","
243
+ fi
244
+ printf ' "%s"' "$(json_escape "${line}")"
245
+ fi
246
+ done <<< "${cellar_lines}"
247
+ echo ""
248
+ echo " ],"
249
+
250
+ # metadata
251
+ printf ' "ruby_version": "%s",\n' "$(json_escape "${ruby_version}")"
252
+ printf ' "prefix": "%s",\n' "$(json_escape "${prefix}")"
253
+ printf ' "github_api_status": "%s"\n' "$(json_escape "${github_status}")"
254
+
255
+ echo "}"
256
+ }
257
+
258
+ # ---------------------------------------------------------------------------
259
+ # Main
260
+ # ---------------------------------------------------------------------------
261
+
262
+ main() {
263
+ local arg="${1:-}"
264
+
265
+ case "${arg}" in
266
+ --help|-h)
267
+ usage
268
+ exit 0
269
+ ;;
270
+ --json)
271
+ output_json
272
+ exit 0
273
+ ;;
274
+ --config|--taps|--cellar|--ruby)
275
+ output_human "${arg}"
276
+ exit 0
277
+ ;;
278
+ "")
279
+ output_human
280
+ exit 0
281
+ ;;
282
+ *)
283
+ die "unknown option: ${arg}"
284
+ ;;
285
+ esac
286
+ }
287
+
288
+ main "$@"
@@ -0,0 +1,150 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ PROG_NAME="$(basename "$0")"
5
+
6
+ usage() {
7
+ cat <<EOF
8
+ Usage: ${PROG_NAME} [options] <query>
9
+
10
+ Search Homebrew formulae and casks with rich output.
11
+
12
+ Options:
13
+ --formulae Search only formulae
14
+ --casks Search only casks
15
+ --desc Search descriptions only
16
+ --json Output results as JSON
17
+ --help Show this help message
18
+
19
+ Examples:
20
+ ${PROG_NAME} python
21
+ ${PROG_NAME} --casks firefox
22
+ ${PROG_NAME} --json python
23
+ EOF
24
+ }
25
+
26
+ search_formulae=true
27
+ search_casks=true
28
+ search_desc=true
29
+ json_output=false
30
+ query=""
31
+
32
+ while [[ $# -gt 0 ]]; do
33
+ case "$1" in
34
+ --help)
35
+ usage
36
+ exit 0
37
+ ;;
38
+ --formulae)
39
+ search_formulae=true
40
+ search_casks=false
41
+ search_desc=false
42
+ ;;
43
+ --casks)
44
+ search_formulae=false
45
+ search_casks=true
46
+ search_desc=false
47
+ ;;
48
+ --desc)
49
+ search_formulae=false
50
+ search_casks=false
51
+ search_desc=true
52
+ ;;
53
+ --json)
54
+ json_output=true
55
+ ;;
56
+ --)
57
+ shift
58
+ break
59
+ ;;
60
+ -*)
61
+ echo "Error: Unknown option $1" >&2
62
+ usage >&2
63
+ exit 1
64
+ ;;
65
+ *)
66
+ if [[ -z "$query" ]]; then
67
+ query="$1"
68
+ else
69
+ query="$query $1"
70
+ fi
71
+ ;;
72
+ esac
73
+ shift
74
+ done
75
+
76
+ if [[ -z "$query" ]]; then
77
+ usage >&2
78
+ echo >&2
79
+ brew search
80
+ exit 1
81
+ fi
82
+
83
+ search_formulae_fn() {
84
+ brew search --formula --quiet "$query" 2>/dev/null | grep -v "Use \`brew desc\`" | grep -v "^$" || true
85
+ }
86
+
87
+ search_casks_fn() {
88
+ brew search --casks --quiet "$query" 2>/dev/null | grep -v "Use \`brew desc\`" | grep -v "^$" || true
89
+ }
90
+
91
+ search_desc_fn() {
92
+ brew search --desc --quiet "$query" 2>/dev/null | grep -v "Use \`brew desc\`" | grep -v "^$" || true
93
+ }
94
+
95
+ results_formulae=""
96
+ results_casks=""
97
+ results_desc=""
98
+
99
+ if [[ "$search_formulae" == true ]]; then
100
+ results_formulae=$(search_formulae_fn)
101
+ fi
102
+
103
+ if [[ "$search_casks" == true ]]; then
104
+ results_casks=$(search_casks_fn)
105
+ fi
106
+
107
+ if [[ "$search_desc" == true ]]; then
108
+ results_desc=$(search_desc_fn)
109
+ fi
110
+
111
+ if [[ -z "$results_formulae" && -z "$results_casks" && -z "$results_desc" ]]; then
112
+ if [[ "$json_output" == true ]]; then
113
+ echo '{"formulae":[],"casks":[],"descriptions":[]}'
114
+ else
115
+ echo "No matches found"
116
+ fi
117
+ exit 0
118
+ fi
119
+
120
+ if [[ "$json_output" == true ]]; then
121
+ json_formulae=$(printf '%s\n' "$results_formulae" | ruby -rjson -e 'puts JSON.generate(ARGF.read.lines.map(&:chomp).reject(&:empty?))')
122
+ json_casks=$(printf '%s\n' "$results_casks" | ruby -rjson -e 'puts JSON.generate(ARGF.read.lines.map(&:chomp).reject(&:empty?))')
123
+ json_descriptions=$(printf '%s\n' "$results_desc" | ruby -rjson -e 'puts JSON.generate(ARGF.read.lines.map(&:chomp).reject(&:empty?))')
124
+
125
+ echo "{\"formulae\":${json_formulae},\"casks\":${json_casks},\"descriptions\":${json_descriptions}}"
126
+ else
127
+ print_section() {
128
+ local title="$1"
129
+ local content="$2"
130
+ if [[ -n "$content" ]]; then
131
+ local count
132
+ count=$(echo "$content" | wc -l | tr -d ' ')
133
+ echo "==> ${title} (${count})"
134
+ echo "$content"
135
+ echo
136
+ fi
137
+ }
138
+
139
+ if [[ "$search_formulae" == true ]]; then
140
+ print_section "Formulae" "$results_formulae"
141
+ fi
142
+
143
+ if [[ "$search_casks" == true ]]; then
144
+ print_section "Casks" "$results_casks"
145
+ fi
146
+
147
+ if [[ "$search_desc" == true ]]; then
148
+ print_section "Description Matches" "$results_desc"
149
+ fi
150
+ fi
@@ -0,0 +1,263 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
6
+ TEMPLATES_DIR="$PROJECT_ROOT/templates/taps"
7
+
8
+ usage() {
9
+ cat <<'EOF'
10
+ Usage: brew-template.sh <project-type> [options]
11
+
12
+ Recommend and apply the best Homebrew tap template for a given project type.
13
+
14
+ Project types:
15
+ go-binary Go CLI built from source
16
+ python-venv Python CLI with pip virtualenv
17
+ node-npm JS/TS CLI with npm or bun
18
+ simple-script Simple script + release script
19
+ full-ci Full CI suite (test-bot + bottles + autobump)
20
+ root-level Root-level .rb (minimal, 1-2 formulae)
21
+ keg-only System tool replacement (shadows macOS)
22
+ cask macOS GUI app (.app/.dmg)
23
+
24
+ Options:
25
+ --name <formula> Formula class name (CamelCase, auto-converts kebab-case)
26
+ --tap <user/tapname> Tap identifier
27
+ --output <path> Where to create the tap structure (required)
28
+ --with-ci Run 'brew tap-new <user>/<tapname>' first
29
+ --convert-to-bun If node-npm, add a note about using bun
30
+ --list List all templates with descriptions
31
+ --help Show this message
32
+
33
+ Examples:
34
+ brew-template.sh go-binary --name my-tool --output ./homebrew-mytool
35
+ brew-template.sh node-npm --name my-tool --output ./homebrew-mytool --convert-to-bun
36
+ brew-template.sh --list
37
+ EOF
38
+ }
39
+
40
+ list_templates() {
41
+ cat <<'EOF'
42
+ Available templates:
43
+ 01-full-ci Full CI suite (test-bot + bottles + autobump)
44
+ 02-root-level Root-level .rb (minimal, 1-2 formulae)
45
+ 03-simple-script Simple script + release script
46
+ 04-python-venv Python CLI with pip virtualenv
47
+ 05-node-npm JS/TS CLI with npm or bun
48
+ 06-keg-only System tool replacement (shadows macOS)
49
+ 07-cask-only macOS GUI app (.app/.dmg)
50
+ 08-go-binary Go CLI built from source
51
+ EOF
52
+ }
53
+
54
+ map_type_to_dir() {
55
+ local type_name="$1"
56
+ case "$type_name" in
57
+ full-ci) echo "01-full-ci" ;;
58
+ root-level) echo "02-root-level" ;;
59
+ simple-script) echo "03-simple-script" ;;
60
+ python-venv) echo "04-python-venv" ;;
61
+ node-npm) echo "05-node-npm" ;;
62
+ keg-only) echo "06-keg-only" ;;
63
+ cask) echo "07-cask-only" ;;
64
+ go-binary) echo "08-go-binary" ;;
65
+ *) echo "invalid" ;;
66
+ esac
67
+ }
68
+
69
+ # Convert kebab-case or snake_case to CamelCase
70
+ # e.g. "my-test-cli" -> "MyTestCli", "my_test_cli" -> "MyTestCli"
71
+ to_camelcase() {
72
+ local input="$1"
73
+ # Replace underscores with hyphens for uniform splitting
74
+ input="${input//_/-}"
75
+ if [[ "$input" == *"-"* ]]; then
76
+ local result=""
77
+ local IFS='-'
78
+ read -ra parts <<< "$input"
79
+ for part in "${parts[@]}"; do
80
+ # Capitalize first letter, lowercase rest
81
+ local first="${part:0:1}"
82
+ local rest="${part:1}"
83
+ result+=""$(echo "$first" | tr '[:lower:]' '[:upper:]')$(echo "$rest" | tr '[:upper:]' '[:lower:]')""
84
+ done
85
+ echo "$result"
86
+ else
87
+ # No hyphens: just ensure first letter is capitalized
88
+ local first="${input:0:1}"
89
+ local rest="${input:1}"
90
+ echo "$(echo "$first" | tr '[:lower:]' '[:upper:]')$rest"
91
+ fi
92
+ }
93
+
94
+ # Lowercase a name for filename
95
+ # e.g. "MyTestCli" -> "mytestcli", "my-test-cli" -> "my-test-cli"
96
+ to_lowercase_filename() {
97
+ local input="$1"
98
+ echo "$input" | tr '[:upper:]' '[:lower:]'
99
+ }
100
+
101
+ PROJECT_TYPE=""
102
+ FORMULA_NAME=""
103
+ TAP_NAME=""
104
+ OUTPUT_DIR=""
105
+ WITH_CI=false
106
+ CONVERT_TO_BUN=false
107
+
108
+ # Parse arguments
109
+ while [[ $# -gt 0 ]]; do
110
+ case "$1" in
111
+ --help)
112
+ usage
113
+ exit 0
114
+ ;;
115
+ --list)
116
+ list_templates
117
+ exit 0
118
+ ;;
119
+ --name)
120
+ if [[ -z "${2:-}" ]]; then
121
+ echo "Error: --name requires an argument" >&2
122
+ exit 1
123
+ fi
124
+ FORMULA_NAME="$2"
125
+ shift 2
126
+ ;;
127
+ --tap)
128
+ if [[ -z "${2:-}" ]]; then
129
+ echo "Error: --tap requires an argument" >&2
130
+ exit 1
131
+ fi
132
+ TAP_NAME="$2"
133
+ shift 2
134
+ ;;
135
+ --output)
136
+ if [[ -z "${2:-}" ]]; then
137
+ echo "Error: --output requires an argument" >&2
138
+ exit 1
139
+ fi
140
+ OUTPUT_DIR="$2"
141
+ shift 2
142
+ ;;
143
+ --with-ci)
144
+ WITH_CI=true
145
+ shift
146
+ ;;
147
+ --convert-to-bun)
148
+ CONVERT_TO_BUN=true
149
+ shift
150
+ ;;
151
+ --*)
152
+ echo "Error: unknown option $1" >&2
153
+ usage >&2
154
+ exit 1
155
+ ;;
156
+ *)
157
+ if [[ -z "$PROJECT_TYPE" ]]; then
158
+ PROJECT_TYPE="$1"
159
+ shift
160
+ else
161
+ echo "Error: unexpected argument $1" >&2
162
+ usage >&2
163
+ exit 1
164
+ fi
165
+ ;;
166
+ esac
167
+ done
168
+
169
+ if [[ -z "${PROJECT_TYPE:-}" ]]; then
170
+ echo "Error: project type is required" >&2
171
+ usage >&2
172
+ exit 1
173
+ fi
174
+
175
+ if [[ -z "${OUTPUT_DIR:-}" ]]; then
176
+ echo "Error: --output is required" >&2
177
+ usage >&2
178
+ exit 1
179
+ fi
180
+
181
+ TEMPLATE_DIR_NAME="$(map_type_to_dir "$PROJECT_TYPE")"
182
+ if [[ "$TEMPLATE_DIR_NAME" == "invalid" ]]; then
183
+ echo "Error: unknown project type '$PROJECT_TYPE'" >&2
184
+ echo "" >&2
185
+ list_templates >&2
186
+ exit 1
187
+ fi
188
+
189
+ TEMPLATE_SRC="$TEMPLATES_DIR/$TEMPLATE_DIR_NAME/tap-structure"
190
+ if [[ ! -d "$TEMPLATE_SRC" ]]; then
191
+ echo "Error: template source not found: $TEMPLATE_SRC" >&2
192
+ exit 1
193
+ fi
194
+
195
+ # Handle existing output directory
196
+ if [[ -e "$OUTPUT_DIR" ]]; then
197
+ if [[ -t 0 ]]; then
198
+ read -rp "Directory '$OUTPUT_DIR' already exists. Overwrite? [y/N] " answer
199
+ case "$answer" in
200
+ [Yy]*) rm -rf "$OUTPUT_DIR" ;;
201
+ *) echo "Aborted." >&2; exit 1 ;;
202
+ esac
203
+ else
204
+ echo "Error: output directory '$OUTPUT_DIR' already exists" >&2
205
+ exit 1
206
+ fi
207
+ fi
208
+
209
+ # Optionally run brew tap-new
210
+ if [[ "$WITH_CI" == true ]]; then
211
+ if [[ -z "$TAP_NAME" ]]; then
212
+ echo "Error: --tap is required when using --with-ci" >&2
213
+ exit 1
214
+ fi
215
+ brew tap-new "$TAP_NAME"
216
+ fi
217
+
218
+ # Copy template
219
+ mkdir -p "$OUTPUT_DIR"
220
+ cp -R "$TEMPLATE_SRC/"* "$OUTPUT_DIR/"
221
+
222
+ # Rename placeholder formula/cask files
223
+ if [[ -n "$FORMULA_NAME" ]]; then
224
+ CAMEL_NAME="$(to_camelcase "$FORMULA_NAME")"
225
+ LOWER_NAME="$(to_lowercase_filename "$FORMULA_NAME")"
226
+
227
+ # Find placeholder formula files
228
+ while IFS= read -r -d '' placeholder; do
229
+ dir="$(dirname "$placeholder")"
230
+ new_path="$dir/$LOWER_NAME.rb"
231
+ mv "$placeholder" "$new_path"
232
+
233
+ if [[ "$PROJECT_TYPE" == "cask" ]]; then
234
+ # Update cask token
235
+ sed -i.bak "s/cask \"<app>\"/cask \"$LOWER_NAME\"/g" "$new_path"
236
+ rm -f "$new_path.bak"
237
+ else
238
+ # Update formula class name
239
+ sed -i.bak "s/class <Formula>/class $CAMEL_NAME/g" "$new_path"
240
+ rm -f "$new_path.bak"
241
+ fi
242
+ done < <(find "$OUTPUT_DIR" \( -name '_formula.rb' -o -name '_app.rb' \) -print0)
243
+ fi
244
+
245
+ # Handle --convert-to-bun for node-npm
246
+ if [[ "$CONVERT_TO_BUN" == true ]] && [[ "$PROJECT_TYPE" == "node-npm" ]]; then
247
+ FORMULA_FILE=$(find "$OUTPUT_DIR" -name '*.rb' | head -n 1)
248
+ if [[ -n "$FORMULA_FILE" ]]; then
249
+ {
250
+ echo "# NOTE: --convert-to-bun was specified. Uncomment the bun pattern below and remove the npm pattern."
251
+ cat "$FORMULA_FILE"
252
+ } > "$FORMULA_FILE.tmp"
253
+ mv "$FORMULA_FILE.tmp" "$FORMULA_FILE"
254
+ fi
255
+ fi
256
+
257
+ echo "Tap structure created at: $OUTPUT_DIR"
258
+ if [[ -n "$TAP_NAME" ]]; then
259
+ echo "Tap: $TAP_NAME"
260
+ fi
261
+ if [[ -n "$FORMULA_NAME" ]]; then
262
+ echo "Formula: $FORMULA_NAME"
263
+ fi