@maccesar/titools 2.5.1 → 2.6.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 (35) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +83 -5
  3. package/bin/titools.js +8 -0
  4. package/lib/commands/list.js +105 -0
  5. package/lib/config.js +1 -0
  6. package/package.json +1 -1
  7. package/skills/alloy-guides/SKILL.md +1 -1
  8. package/skills/alloy-howtos/SKILL.md +1 -1
  9. package/skills/purgetss/SKILL.md +1 -1
  10. package/skills/ti-api/SKILL.md +1 -1
  11. package/skills/ti-branding/SKILL.md +225 -0
  12. package/skills/ti-branding/assets/ic_launcher.xml +6 -0
  13. package/skills/ti-branding/references/android-adaptive-icons.md +85 -0
  14. package/skills/ti-branding/references/cleanup-legacy.md +112 -0
  15. package/skills/ti-branding/references/ios-appiconset.md +62 -0
  16. package/skills/ti-branding/references/master-input-guidelines.md +84 -0
  17. package/skills/ti-branding/references/notification-icons.md +63 -0
  18. package/skills/ti-branding/references/splash-screen-api.md +81 -0
  19. package/skills/ti-branding/references/ti-icon-paths.md +84 -0
  20. package/skills/ti-branding/references/tiapp-xml-snippets.md +92 -0
  21. package/skills/ti-branding/scripts/lib/cleanup-legacy.sh +230 -0
  22. package/skills/ti-branding/scripts/lib/deps.sh +58 -0
  23. package/skills/ti-branding/scripts/lib/gen-android-adaptive.sh +52 -0
  24. package/skills/ti-branding/scripts/lib/gen-android-legacy.sh +36 -0
  25. package/skills/ti-branding/scripts/lib/gen-ios.sh +30 -0
  26. package/skills/ti-branding/scripts/lib/gen-marketplace.sh +40 -0
  27. package/skills/ti-branding/scripts/lib/gen-notification.sh +34 -0
  28. package/skills/ti-branding/scripts/lib/gen-splash-icon.sh +31 -0
  29. package/skills/ti-branding/scripts/lib/prepare-master.sh +48 -0
  30. package/skills/ti-branding/scripts/lib/validate.sh +67 -0
  31. package/skills/ti-branding/scripts/ti-branding +462 -0
  32. package/skills/ti-expert/SKILL.md +1 -1
  33. package/skills/ti-guides/SKILL.md +1 -1
  34. package/skills/ti-howtos/SKILL.md +1 -1
  35. package/skills/ti-ui/SKILL.md +1 -1
@@ -0,0 +1,230 @@
1
+ #!/usr/bin/env bash
2
+ # cleanup-legacy.sh — context-aware removal of legacy branding artifacts.
3
+ #
4
+ # Reads tiapp.xml to decide what's safe to delete. Categorizes targets into:
5
+ #
6
+ # SAFE always deleted — universally obsolete (dead qualifiers etc.)
7
+ # CONDITIONAL deleted only when project config guarantees they're unused
8
+ # (e.g. iOS legacy launch images when storyboard is enabled)
9
+ # AGGRESSIVE behind --aggressive flag — strongly defensible but some
10
+ # edge cases remain (e.g. ldpi drawables on <1% of devices)
11
+ #
12
+ # Always prints the plan before acting. Respects --dry-run.
13
+
14
+ # Append "path|reason" pair to a named array in a bash-3.2-compatible way.
15
+ _add_target() {
16
+ local array_name="$1"
17
+ local path="$2"
18
+ local reason="$3"
19
+ eval "$array_name+=(\"\$path|\$reason\")"
20
+ }
21
+
22
+ cleanup_legacy() {
23
+ local project_root="$1"
24
+ local project_type="$2"
25
+ local aggressive="$3"
26
+ local dry_run="$4"
27
+
28
+ local tiapp="$project_root/tiapp.xml"
29
+ local storyboard_enabled=0
30
+ local portrait_only=0
31
+
32
+ if [[ -f "$tiapp" ]]; then
33
+ grep -q "<enable-launch-screen-storyboard>true" "$tiapp" && storyboard_enabled=1
34
+ # Portrait-only heuristic: <orientations> block contains UIInterfaceOrientationPortrait
35
+ # for iphone AND no Landscape entries.
36
+ if grep -q '<orientations' "$tiapp" && \
37
+ ! grep -iqE 'UIInterfaceOrientationLandscape' "$tiapp"; then
38
+ portrait_only=1
39
+ fi
40
+ fi
41
+
42
+ local android_images=""
43
+ local iphone_dir=""
44
+ local android_assets=""
45
+ case "$project_type" in
46
+ alloy)
47
+ android_images="$project_root/app/assets/android/images"
48
+ iphone_dir="$project_root/app/assets/iphone"
49
+ android_assets="$project_root/app/assets/android"
50
+ ;;
51
+ classic)
52
+ android_images="$project_root/Resources/android/images"
53
+ iphone_dir="$project_root/Resources/iphone"
54
+ android_assets="$project_root/Resources/android"
55
+ ;;
56
+ esac
57
+
58
+ local has_adaptive=0
59
+ [[ -d "$project_root/app/platform/android/res/mipmap-anydpi-v26" ]] && has_adaptive=1
60
+ [[ -d "$project_root/platform/android/res/mipmap-anydpi-v26" ]] && has_adaptive=1
61
+
62
+ local safe_targets=()
63
+ local cond_targets=()
64
+ local aggressive_targets=()
65
+
66
+ # -------------------------------------------------------------------------
67
+ # SAFE
68
+ # -------------------------------------------------------------------------
69
+
70
+ if [[ -n "$android_images" && -d "$android_images" ]]; then
71
+ local d
72
+ for d in "$android_images"/res-long-* "$android_images"/res-notlong-*; do
73
+ [[ -d "$d" ]] && _add_target safe_targets "$d" "Android long/notlong qualifier (dead since Android 3.0, 2011)"
74
+ done
75
+ fi
76
+
77
+ # -------------------------------------------------------------------------
78
+ # CONDITIONAL
79
+ # -------------------------------------------------------------------------
80
+
81
+ # iOS legacy launch images — only safe with storyboard enabled
82
+ if [[ $storyboard_enabled -eq 1 && -n "$iphone_dir" && -d "$iphone_dir" ]]; then
83
+ local f
84
+ for f in "$iphone_dir"/Default-*.png "$iphone_dir"/Default@2x.png; do
85
+ [[ -f "$f" ]] && _add_target cond_targets "$f" "iOS legacy launch image (storyboard enabled → not consulted)"
86
+ done
87
+ fi
88
+
89
+ # Android landscape-variant folders — safe when app is portrait-only
90
+ if [[ $portrait_only -eq 1 && -n "$android_images" && -d "$android_images" ]]; then
91
+ local d
92
+ for d in "$android_images"/res-*-land-* "$android_images"/res-land-*; do
93
+ [[ -d "$d" ]] && _add_target cond_targets "$d" "Landscape variant (app is portrait-only)"
94
+ done
95
+ fi
96
+
97
+ # Android legacy splash PNG — safe when adaptive icons are in place
98
+ if [[ $has_adaptive -eq 1 && -n "$android_assets" && -f "$android_assets/default.png" ]]; then
99
+ _add_target cond_targets "$android_assets/default.png" "Legacy Android splash PNG (adaptive launcher handles splash now)"
100
+ fi
101
+
102
+ # Android legacy appicon.png — redundant when adaptive icons are in place
103
+ if [[ $has_adaptive -eq 1 && -n "$android_assets" && -f "$android_assets/appicon.png" ]]; then
104
+ _add_target cond_targets "$android_assets/appicon.png" "Legacy appicon.png (adaptive launcher takes precedence)"
105
+ fi
106
+
107
+ # -------------------------------------------------------------------------
108
+ # AGGRESSIVE
109
+ # -------------------------------------------------------------------------
110
+
111
+ if [[ $aggressive -eq 1 ]]; then
112
+ # ldpi everywhere — <1% of active devices globally in 2026
113
+ local d
114
+ if [[ -n "$android_images" && -d "$android_images" ]]; then
115
+ for d in "$android_images"/res-ldpi "$android_images"/res-*-ldpi; do
116
+ [[ -d "$d" ]] && _add_target aggressive_targets "$d" "ldpi density (<1% global market)"
117
+ done
118
+ fi
119
+ for d in "$project_root/app/platform/android/res/drawable-ldpi" \
120
+ "$project_root/app/platform/android/res/values-ldpi" \
121
+ "$project_root/app/platform/android/res/mipmap-ldpi" \
122
+ "$project_root/platform/android/res/drawable-ldpi" \
123
+ "$project_root/platform/android/res/values-ldpi" \
124
+ "$project_root/platform/android/res/mipmap-ldpi"; do
125
+ [[ -d "$d" ]] && _add_target aggressive_targets "$d" "ldpi resource folder (<1% global market)"
126
+ done
127
+ fi
128
+
129
+ # -------------------------------------------------------------------------
130
+ # Print plan
131
+ # -------------------------------------------------------------------------
132
+
133
+ echo
134
+ echo "${C_YELLOW}⚠ WARNING${C_RESET} — --cleanup-legacy deletes files permanently."
135
+ echo " Recommended: commit your project with git before running without --dry-run."
136
+ echo " Provided AS IS under the MIT License. No warranty — use at your own risk."
137
+ echo
138
+ echo "${C_BOLD}Cleanup plan${C_RESET}"
139
+ echo " ${C_DIM}Project: $project_root ($project_type)${C_RESET}"
140
+ echo " ${C_DIM}Storyboard: $([[ $storyboard_enabled -eq 1 ]] && echo "enabled" || echo "disabled")${C_RESET}"
141
+ echo " ${C_DIM}Orientation: $([[ $portrait_only -eq 1 ]] && echo "portrait-only" || echo "landscape allowed")${C_RESET}"
142
+ echo " ${C_DIM}Adaptive icons: $([[ $has_adaptive -eq 1 ]] && echo "present" || echo "not detected")${C_RESET}"
143
+ echo " ${C_DIM}Aggressive mode: $([[ $aggressive -eq 1 ]] && echo "on (includes ldpi)" || echo "off")${C_RESET}"
144
+
145
+ local total=$(( ${#safe_targets[@]} + ${#cond_targets[@]} + ${#aggressive_targets[@]} ))
146
+
147
+ if [[ $total -eq 0 ]]; then
148
+ echo
149
+ log_ok "No legacy artifacts detected. Project is already clean."
150
+ return 0
151
+ fi
152
+
153
+ # bash 3.2 under `set -u` errors on "${empty_array[@]}" — iterate per-bucket
154
+ # with explicit length guards instead of expanding empty arrays into args.
155
+ _print_bucket_safe() {
156
+ local t path reason size
157
+ echo
158
+ echo "${C_GREEN}SAFE — universally obsolete${C_RESET}"
159
+ for t in "${safe_targets[@]}"; do
160
+ path="${t%%|*}"; reason="${t#*|}"
161
+ size=$(du -sk "$path" 2>/dev/null | awk '{print $1}')
162
+ [[ -z "$size" ]] && size=0
163
+ printf " %-6sK %s\n" "$size" "${path#$project_root/}"
164
+ printf " ${C_DIM}%s${C_RESET}\n" "$reason"
165
+ done
166
+ }
167
+ _print_bucket_cond() {
168
+ local t path reason size
169
+ echo
170
+ echo "${C_YELLOW}CONDITIONAL — safe given your project config${C_RESET}"
171
+ for t in "${cond_targets[@]}"; do
172
+ path="${t%%|*}"; reason="${t#*|}"
173
+ size=$(du -sk "$path" 2>/dev/null | awk '{print $1}')
174
+ [[ -z "$size" ]] && size=0
175
+ printf " %-6sK %s\n" "$size" "${path#$project_root/}"
176
+ printf " ${C_DIM}%s${C_RESET}\n" "$reason"
177
+ done
178
+ }
179
+ _print_bucket_aggr() {
180
+ local t path reason size
181
+ echo
182
+ echo "${C_RED}AGGRESSIVE — --aggressive enabled${C_RESET}"
183
+ for t in "${aggressive_targets[@]}"; do
184
+ path="${t%%|*}"; reason="${t#*|}"
185
+ size=$(du -sk "$path" 2>/dev/null | awk '{print $1}')
186
+ [[ -z "$size" ]] && size=0
187
+ printf " %-6sK %s\n" "$size" "${path#$project_root/}"
188
+ printf " ${C_DIM}%s${C_RESET}\n" "$reason"
189
+ done
190
+ }
191
+
192
+ [[ ${#safe_targets[@]} -gt 0 ]] && _print_bucket_safe
193
+ [[ ${#cond_targets[@]} -gt 0 ]] && _print_bucket_cond
194
+ [[ ${#aggressive_targets[@]} -gt 0 ]] && _print_bucket_aggr
195
+
196
+ echo
197
+ echo " ${C_BOLD}Total: $total item(s) to remove${C_RESET}"
198
+
199
+ if [[ $dry_run -eq 1 ]]; then
200
+ echo
201
+ log_info "Dry-run mode — nothing deleted. Re-run without --dry-run to apply."
202
+ return 0
203
+ fi
204
+
205
+ # -------------------------------------------------------------------------
206
+ # Apply
207
+ # -------------------------------------------------------------------------
208
+
209
+ echo
210
+ log_step "Applying cleanup"
211
+ local t path
212
+ if [[ ${#safe_targets[@]} -gt 0 ]]; then
213
+ for t in "${safe_targets[@]}"; do
214
+ path="${t%%|*}"; rm -rf "$path"
215
+ log_ok "Removed ${path#$project_root/}"
216
+ done
217
+ fi
218
+ if [[ ${#cond_targets[@]} -gt 0 ]]; then
219
+ for t in "${cond_targets[@]}"; do
220
+ path="${t%%|*}"; rm -rf "$path"
221
+ log_ok "Removed ${path#$project_root/}"
222
+ done
223
+ fi
224
+ if [[ ${#aggressive_targets[@]} -gt 0 ]]; then
225
+ for t in "${aggressive_targets[@]}"; do
226
+ path="${t%%|*}"; rm -rf "$path"
227
+ log_ok "Removed ${path#$project_root/}"
228
+ done
229
+ fi
230
+ }
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env bash
2
+ # deps.sh — detect required system tools and guide installation.
3
+
4
+ check_deps() {
5
+ local master="$1"
6
+ local need_rsvg=0
7
+ local missing=()
8
+
9
+ local master_lower
10
+ master_lower=$(printf '%s' "$master" | tr '[:upper:]' '[:lower:]')
11
+ case "$master_lower" in
12
+ *.svg) need_rsvg=1 ;;
13
+ esac
14
+
15
+ if ! command -v magick >/dev/null 2>&1; then
16
+ missing+=("imagemagick")
17
+ fi
18
+
19
+ if [[ $need_rsvg -eq 1 ]] && ! command -v rsvg-convert >/dev/null 2>&1; then
20
+ missing+=("librsvg")
21
+ fi
22
+
23
+ if [[ ${#missing[@]} -eq 0 ]]; then
24
+ log_ok "All dependencies present: $(magick -version | head -1 | awk '{print $2,$3}')"
25
+ [[ $need_rsvg -eq 1 ]] && log_ok "librsvg: $(rsvg-convert --version | head -1)"
26
+ return 0
27
+ fi
28
+
29
+ log_err "Missing required tools: ${missing[*]}"
30
+ echo
31
+ echo "Install with one of:"
32
+ local os="$(uname -s)"
33
+ case "$os" in
34
+ Darwin)
35
+ echo " macOS: brew install ${missing[*]}"
36
+ ;;
37
+ Linux)
38
+ if command -v apt >/dev/null 2>&1; then
39
+ local apt_pkgs=()
40
+ for m in "${missing[@]}"; do
41
+ case "$m" in
42
+ imagemagick) apt_pkgs+=("imagemagick") ;;
43
+ librsvg) apt_pkgs+=("librsvg2-bin") ;;
44
+ esac
45
+ done
46
+ echo " Ubuntu: sudo apt install ${apt_pkgs[*]}"
47
+ elif command -v pacman >/dev/null 2>&1; then
48
+ echo " Arch: sudo pacman -S ${missing[*]}"
49
+ elif command -v dnf >/dev/null 2>&1; then
50
+ echo " Fedora: sudo dnf install ${missing[*]}"
51
+ fi
52
+ ;;
53
+ esac
54
+ echo " Windows: choco install ${missing[*]}"
55
+ echo
56
+ echo "After installing, re-run this command."
57
+ exit 1
58
+ }
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env bash
2
+ # gen-android-adaptive.sh — produce the adaptive icon triplet (foreground +
3
+ # background + monochrome) at 5 densities.
4
+ #
5
+ # Android adaptive icon: 108×108dp canvas, 66×66dp safe-zone.
6
+ # Densities: mdpi=108px, hdpi=162, xhdpi=216, xxhdpi=324, xxxhdpi=432.
7
+ #
8
+ # Foreground: logo centered inside safe-zone, transparent outside.
9
+ # Background: solid color (or pattern) filling the full canvas.
10
+ # Monochrome: foreground silhouette in pure white with preserved alpha — Android
11
+ # applies themed tint at runtime on API 31+.
12
+
13
+ gen_android_adaptive() {
14
+ local master="$1"
15
+ local bg="$2"
16
+ local padding="$3" # percent per side
17
+ local res_root="$4" # e.g. /path/app/platform/android/res
18
+
19
+ local densities=(mdpi hdpi xhdpi xxhdpi xxxhdpi)
20
+ local sizes=(108 162 216 324 432)
21
+
22
+ for i in "${!densities[@]}"; do
23
+ local d="${densities[$i]}"
24
+ local size="${sizes[$i]}"
25
+ local inner=$(( size * (100 - 2 * padding) / 100 ))
26
+ local dir="$res_root/mipmap-$d"
27
+ mkdir -p "$dir"
28
+
29
+ # Foreground: logo centered, padded, transparent outside
30
+ magick "$master" \
31
+ -resize "${inner}x${inner}" \
32
+ -background none -gravity center -extent "${size}x${size}" \
33
+ -strip \
34
+ "$dir/ic_launcher_foreground.png"
35
+
36
+ # Background: solid color
37
+ magick -size "${size}x${size}" "xc:$bg" \
38
+ -strip \
39
+ "$dir/ic_launcher_background.png"
40
+
41
+ # Monochrome: foreground tinted to pure white, alpha preserved
42
+ magick "$dir/ic_launcher_foreground.png" \
43
+ -channel RGB -fill white -colorize 100 +channel \
44
+ -strip \
45
+ "$dir/ic_launcher_monochrome.png"
46
+ done
47
+
48
+ # anydpi-v26 binder directory (XML copied by caller from assets/ic_launcher.xml)
49
+ mkdir -p "$res_root/mipmap-anydpi-v26"
50
+
51
+ log_ok "Adaptive icons: foreground + background + monochrome × 5 densities"
52
+ }
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env bash
2
+ # gen-android-legacy.sh — produce the flat ic_launcher.png for pre-adaptive Android
3
+ # (API 21–25, ~3% of users in 2026). Composites foreground over background and
4
+ # scales down to the legacy density sizes.
5
+ #
6
+ # Legacy densities: mdpi=48px, hdpi=72, xhdpi=96, xxhdpi=144, xxxhdpi=192.
7
+
8
+ gen_android_legacy() {
9
+ local master="$1"
10
+ local bg="$2"
11
+ local padding="$3"
12
+ local res_root="$4"
13
+
14
+ local densities=(mdpi hdpi xhdpi xxhdpi xxxhdpi)
15
+ local sizes=(48 72 96 144 192)
16
+
17
+ for i in "${!densities[@]}"; do
18
+ local d="${densities[$i]}"
19
+ local size="${sizes[$i]}"
20
+ # Legacy icons are displayed as-drawn (no adaptive mask), so we can fill more of
21
+ # the canvas. Reduce the padding by ~40% for the legacy composite.
22
+ local legacy_padding=$(( padding * 60 / 100 ))
23
+ local inner=$(( size * (100 - 2 * legacy_padding) / 100 ))
24
+ local dir="$res_root/mipmap-$d"
25
+ mkdir -p "$dir"
26
+
27
+ # Compose: solid bg + logo centered
28
+ magick -size "${size}x${size}" "xc:$bg" \
29
+ \( "$master" -resize "${inner}x${inner}" \) \
30
+ -gravity center -composite \
31
+ -strip \
32
+ "$dir/ic_launcher.png"
33
+ done
34
+
35
+ log_ok "Legacy ic_launcher.png × 5 densities (fallback for Android <8)"
36
+ }
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env bash
2
+ # gen-ios.sh — produce DefaultIcon-ios.png (1024×1024, alpha flattened over bg-color).
3
+ #
4
+ # Unlike Android adaptive icons, iOS app icons have no "safe zone" — the launcher
5
+ # doesn't mask them. What you see is what ships. So padding is purely visual
6
+ # breathing room around the logo. Default 8% per side (logo fills 84% of the
7
+ # canvas), which matches Apple's HIG aesthetic for branded wordmarks.
8
+ #
9
+ # iOS rejects icons with alpha, so the final output is flattened over bg-color.
10
+
11
+ gen_ios() {
12
+ local tight="$1" # tight master (aspect-preserved, transparent bg)
13
+ local bg="$2"
14
+ local padding="$3" # iOS padding percent per side (default 8)
15
+ local out_root="$4"
16
+
17
+ mkdir -p "$out_root"
18
+
19
+ local size=1024
20
+ local inner=$(( size * (100 - 2 * padding) / 100 ))
21
+
22
+ magick "$tight" \
23
+ -resize "${inner}x${inner}" \
24
+ -background none -gravity center -extent "${size}x${size}" \
25
+ -background "$bg" -alpha remove -alpha off \
26
+ -strip \
27
+ "$out_root/DefaultIcon-ios.png"
28
+
29
+ log_ok "DefaultIcon-ios.png (1024×1024, ${padding}% padding, alpha flattened over $bg)"
30
+ }
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env bash
2
+ # gen-marketplace.sh — store-submission artwork.
3
+ #
4
+ # iTunesConnect.png: 1024×1024, no alpha (App Store)
5
+ # MarketplaceArtwork.png: 512×512, no alpha (Google Play)
6
+ #
7
+ # Uses the same --ios-padding as the iOS master (default 8%). Store assets are
8
+ # scrutinized at large sizes and need visual breathing room — no safe-zone to
9
+ # comply with, but aesthetically they should match the DefaultIcon-ios look.
10
+
11
+ gen_marketplace() {
12
+ local tight="$1"
13
+ local bg="$2"
14
+ local padding="$3" # ios-padding percent per side
15
+ local out_root="$4"
16
+
17
+ mkdir -p "$out_root"
18
+
19
+ # App Store (1024²)
20
+ local size_ios=1024
21
+ local inner_ios=$(( size_ios * (100 - 2 * padding) / 100 ))
22
+ magick "$tight" \
23
+ -resize "${inner_ios}x${inner_ios}" \
24
+ -background none -gravity center -extent "${size_ios}x${size_ios}" \
25
+ -background "$bg" -alpha remove -alpha off \
26
+ -strip \
27
+ "$out_root/iTunesConnect.png"
28
+
29
+ # Google Play (512²)
30
+ local size_play=512
31
+ local inner_play=$(( size_play * (100 - 2 * padding) / 100 ))
32
+ magick "$tight" \
33
+ -resize "${inner_play}x${inner_play}" \
34
+ -background none -gravity center -extent "${size_play}x${size_play}" \
35
+ -background "$bg" -alpha remove -alpha off \
36
+ -strip \
37
+ "$out_root/MarketplaceArtwork.png"
38
+
39
+ log_ok "Marketplace artwork: iTunesConnect.png (1024², ${padding}% padding), MarketplaceArtwork.png (512², ${padding}% padding)"
40
+ }
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env bash
2
+ # gen-notification.sh — white-on-transparent notification icons at 5 densities.
3
+ # Android applies a runtime tint based on the notification's color property, so
4
+ # all non-transparent pixels become white and color information is discarded.
5
+ #
6
+ # Sizes per Android spec: mdpi=24px, hdpi=36, xhdpi=48, xxhdpi=72, xxxhdpi=96.
7
+
8
+ gen_notification() {
9
+ local master="$1"
10
+ local padding="$2"
11
+ local res_root="$3"
12
+
13
+ local densities=(mdpi hdpi xhdpi xxhdpi xxxhdpi)
14
+ local sizes=(24 36 48 72 96)
15
+
16
+ for i in "${!densities[@]}"; do
17
+ local d="${densities[$i]}"
18
+ local size="${sizes[$i]}"
19
+ # Notification icons get pretty heavily masked in the status bar, so use
20
+ # slightly tighter padding than the full safe-zone.
21
+ local inner=$(( size * (100 - 2 * padding) / 100 ))
22
+ local dir="$res_root/drawable-$d"
23
+ mkdir -p "$dir"
24
+
25
+ magick "$master" \
26
+ -resize "${inner}x${inner}" \
27
+ -channel RGB -fill white -colorize 100 +channel \
28
+ -background none -gravity center -extent "${size}x${size}" \
29
+ -strip \
30
+ "$dir/ic_stat_notify.png"
31
+ done
32
+
33
+ log_ok "Notification icons (white+alpha) × 5 densities"
34
+ }
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env bash
2
+ # gen-splash-icon.sh — Android 12+ SplashScreen API icon.
3
+ # The spec: 288dp canvas, icon occupies the central 192dp (~67% of canvas).
4
+ # The OS renders a circular mask automatically, so keep the icon transparent
5
+ # outside the 192dp safe-zone.
6
+ #
7
+ # Densities: mdpi=288px, hdpi=432, xhdpi=576, xxhdpi=864, xxxhdpi=1152.
8
+
9
+ gen_splash_icon() {
10
+ local master="$1"
11
+ local res_root="$2"
12
+
13
+ local densities=(mdpi hdpi xhdpi xxhdpi xxxhdpi)
14
+ local sizes=(288 432 576 864 1152)
15
+
16
+ for i in "${!densities[@]}"; do
17
+ local d="${densities[$i]}"
18
+ local size="${sizes[$i]}"
19
+ local inner=$(( size * 192 / 288 ))
20
+ local dir="$res_root/drawable-$d"
21
+ mkdir -p "$dir"
22
+
23
+ magick "$master" \
24
+ -resize "${inner}x${inner}" \
25
+ -background none -gravity center -extent "${size}x${size}" \
26
+ -strip \
27
+ "$dir/splash_icon.png"
28
+ done
29
+
30
+ log_ok "Android 12+ splash_icon.png × 5 densities"
31
+ }
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env bash
2
+ # prepare-master.sh — produces two normalized masters from the input:
3
+ #
4
+ # 1. <out>_square.png — 1024×1024 PNG with the logo centered in a square
5
+ # canvas and transparent padding added to sides (or top/bottom) to keep
6
+ # the logo's aspect intact. Used for iOS DefaultIcon and marketplace
7
+ # artwork — platforms that require square icons.
8
+ #
9
+ # 2. <out>_tight.png — logo rasterized at 1024-px max dimension with its
10
+ # native aspect preserved (no padding added). Used for Android adaptive
11
+ # icon generation so a horizontal wordmark can fill the safe-zone by
12
+ # width instead of being double-padded inside the square master.
13
+ #
14
+ # The skill's main script passes <out>_square.png to iOS generators and
15
+ # <out>_tight.png to Android adaptive/legacy/notification/splash generators.
16
+
17
+ prepare_master() {
18
+ local input="$1"
19
+ local base="$2" # e.g. /path/.ti-branding/_master (no extension)
20
+ local square="${base}_square.png"
21
+ local tight="${base}_tight.png"
22
+ local ext="${input##*.}"
23
+ ext=$(printf '%s' "$ext" | tr '[:upper:]' '[:lower:]')
24
+
25
+ mkdir -p "$(dirname "$base")"
26
+
27
+ case "$ext" in
28
+ svg)
29
+ # Rasterize SVG at 2048-px max dimension preserving native aspect — this
30
+ # is the tight master. rsvg-convert's -a flag preserves aspect when both
31
+ # -w and -h are supplied, producing the larger of the two dimensions.
32
+ rsvg-convert -w 2048 -h 2048 -a "$input" -o "$tight.2x.png"
33
+ magick "$tight.2x.png" -resize "1024x1024>" -strip "$tight"
34
+ rm -f "$tight.2x.png"
35
+ ;;
36
+ png)
37
+ magick "$input" -resize "1024x1024>" -strip "$tight"
38
+ ;;
39
+ esac
40
+
41
+ # Build the square master by padding the tight master to 1024×1024 with
42
+ # transparency. magick's -gravity center + -extent centers the logo and
43
+ # distributes padding to whichever axis is shorter.
44
+ magick "$tight" \
45
+ -background none -gravity center -extent 1024x1024 \
46
+ -strip \
47
+ "$square"
48
+ }
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env bash
2
+ # validate.sh — sanity-check the master input.
3
+
4
+ validate_master() {
5
+ local master="$1"
6
+ local ext="${master##*.}"
7
+ ext=$(printf '%s' "$ext" | tr '[:upper:]' '[:lower:]')
8
+
9
+ case "$ext" in
10
+ svg)
11
+ validate_svg "$master"
12
+ ;;
13
+ png)
14
+ validate_png "$master"
15
+ ;;
16
+ *)
17
+ log_err "Unsupported format: .$ext (expected .svg or .png)"
18
+ exit 2
19
+ ;;
20
+ esac
21
+ }
22
+
23
+ validate_svg() {
24
+ local master="$1"
25
+ if ! grep -q "<svg" "$master" 2>/dev/null; then
26
+ log_err "File does not appear to be valid SVG: $master"
27
+ exit 2
28
+ fi
29
+
30
+ if grep -qE '<(script|foreignObject)' "$master" 2>/dev/null; then
31
+ log_warn "SVG contains <script> or <foreignObject> — may not render correctly."
32
+ fi
33
+
34
+ log_ok "SVG input accepted (will rasterize to 1024×1024)"
35
+ }
36
+
37
+ validate_png() {
38
+ local master="$1"
39
+ local w h
40
+ w=$(magick identify -format "%w" "$master" 2>/dev/null || echo 0)
41
+ h=$(magick identify -format "%h" "$master" 2>/dev/null || echo 0)
42
+
43
+ if [[ -z "$w" || "$w" -eq 0 ]]; then
44
+ log_err "Could not read PNG dimensions: $master"
45
+ exit 2
46
+ fi
47
+
48
+ if [[ "$w" -lt 1024 || "$h" -lt 1024 ]]; then
49
+ log_err "PNG too small: ${w}×${h}. Need at least 1024×1024 for high-density output."
50
+ exit 2
51
+ fi
52
+
53
+ if [[ "$w" -ne "$h" ]]; then
54
+ log_warn "PNG is not square (${w}×${h}). Will be center-cropped to square."
55
+ fi
56
+
57
+ local has_alpha
58
+ has_alpha=$(magick identify -format "%A" "$master" 2>/dev/null)
59
+ case "$has_alpha" in
60
+ True|Blend)
61
+ log_ok "PNG ${w}×${h} with alpha — good for monochrome layer"
62
+ ;;
63
+ *)
64
+ log_warn "PNG ${w}×${h} has no alpha — monochrome layer will be a square silhouette"
65
+ ;;
66
+ esac
67
+ }