mitsupi 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 (77) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +95 -0
  3. package/TODO.md +11 -0
  4. package/commands/handoff.md +100 -0
  5. package/commands/make-release.md +75 -0
  6. package/commands/pickup.md +30 -0
  7. package/commands/update-changelog.md +78 -0
  8. package/package.json +22 -0
  9. package/pi-extensions/answer.ts +527 -0
  10. package/pi-extensions/codex-tuning.ts +632 -0
  11. package/pi-extensions/commit.ts +248 -0
  12. package/pi-extensions/cwd-history.ts +237 -0
  13. package/pi-extensions/issues.ts +548 -0
  14. package/pi-extensions/loop.ts +446 -0
  15. package/pi-extensions/qna.ts +167 -0
  16. package/pi-extensions/reveal.ts +689 -0
  17. package/pi-extensions/review.ts +807 -0
  18. package/pi-themes/armin.json +81 -0
  19. package/pi-themes/nightowl.json +82 -0
  20. package/skills/anachb/SKILL.md +183 -0
  21. package/skills/anachb/departures.sh +79 -0
  22. package/skills/anachb/disruptions.sh +53 -0
  23. package/skills/anachb/route.sh +87 -0
  24. package/skills/anachb/search.sh +43 -0
  25. package/skills/ghidra/SKILL.md +254 -0
  26. package/skills/ghidra/scripts/find-ghidra.sh +54 -0
  27. package/skills/ghidra/scripts/ghidra-analyze.sh +239 -0
  28. package/skills/ghidra/scripts/ghidra_scripts/ExportAll.java +278 -0
  29. package/skills/ghidra/scripts/ghidra_scripts/ExportCalls.java +148 -0
  30. package/skills/ghidra/scripts/ghidra_scripts/ExportDecompiled.java +84 -0
  31. package/skills/ghidra/scripts/ghidra_scripts/ExportFunctions.java +114 -0
  32. package/skills/ghidra/scripts/ghidra_scripts/ExportStrings.java +123 -0
  33. package/skills/ghidra/scripts/ghidra_scripts/ExportSymbols.java +135 -0
  34. package/skills/github/SKILL.md +47 -0
  35. package/skills/improve-skill/SKILL.md +155 -0
  36. package/skills/improve-skill/scripts/extract-session.js +349 -0
  37. package/skills/oebb-scotty/SKILL.md +429 -0
  38. package/skills/oebb-scotty/arrivals.sh +83 -0
  39. package/skills/oebb-scotty/departures.sh +83 -0
  40. package/skills/oebb-scotty/disruptions.sh +33 -0
  41. package/skills/oebb-scotty/search-station.sh +36 -0
  42. package/skills/oebb-scotty/trip.sh +119 -0
  43. package/skills/openscad/SKILL.md +232 -0
  44. package/skills/openscad/examples/parametric_box.scad +92 -0
  45. package/skills/openscad/examples/phone_stand.scad +95 -0
  46. package/skills/openscad/tools/common.sh +50 -0
  47. package/skills/openscad/tools/export-stl.sh +56 -0
  48. package/skills/openscad/tools/extract-params.sh +147 -0
  49. package/skills/openscad/tools/multi-preview.sh +68 -0
  50. package/skills/openscad/tools/preview.sh +74 -0
  51. package/skills/openscad/tools/render-with-params.sh +91 -0
  52. package/skills/openscad/tools/validate.sh +46 -0
  53. package/skills/pi-share/SKILL.md +105 -0
  54. package/skills/pi-share/fetch-session.mjs +322 -0
  55. package/skills/sentry/SKILL.md +239 -0
  56. package/skills/sentry/lib/auth.js +99 -0
  57. package/skills/sentry/scripts/fetch-event.js +329 -0
  58. package/skills/sentry/scripts/fetch-issue.js +356 -0
  59. package/skills/sentry/scripts/list-issues.js +239 -0
  60. package/skills/sentry/scripts/search-events.js +291 -0
  61. package/skills/sentry/scripts/search-logs.js +240 -0
  62. package/skills/tmux/SKILL.md +105 -0
  63. package/skills/tmux/scripts/find-sessions.sh +112 -0
  64. package/skills/tmux/scripts/wait-for-text.sh +83 -0
  65. package/skills/web-browser/SKILL.md +91 -0
  66. package/skills/web-browser/scripts/cdp.js +210 -0
  67. package/skills/web-browser/scripts/dismiss-cookies.js +373 -0
  68. package/skills/web-browser/scripts/eval.js +68 -0
  69. package/skills/web-browser/scripts/logs-tail.js +69 -0
  70. package/skills/web-browser/scripts/nav.js +65 -0
  71. package/skills/web-browser/scripts/net-summary.js +94 -0
  72. package/skills/web-browser/scripts/package-lock.json +33 -0
  73. package/skills/web-browser/scripts/package.json +6 -0
  74. package/skills/web-browser/scripts/pick.js +165 -0
  75. package/skills/web-browser/scripts/screenshot.js +52 -0
  76. package/skills/web-browser/scripts/start.js +80 -0
  77. package/skills/web-browser/scripts/watch.js +266 -0
@@ -0,0 +1,119 @@
1
+ #!/bin/bash
2
+ # Search for train connections between two stations
3
+ # Usage: ./trip.sh <from-station> <to-station> [date] [time] [num-results]
4
+ #
5
+ # Date format: YYYYMMDD (default: today)
6
+ # Time format: HHMM (default: now)
7
+ # Example: ./trip.sh "Wien Hbf" "Salzburg Hbf" 20260109 0800 5
8
+
9
+ FROM="${1:-}"
10
+ TO="${2:-}"
11
+ DATE="${3:-$(date +%Y%m%d)}"
12
+ TIME="${4:-$(date +%H%M)}00"
13
+ NUM="${5:-5}"
14
+
15
+ if [ -z "$FROM" ] || [ -z "$TO" ]; then
16
+ echo "Usage: $0 <from-station> <to-station> [date] [time] [num-results]"
17
+ echo "Example: $0 'Wien Hbf' 'Salzburg Hbf' 20260109 0800 5"
18
+ exit 1
19
+ fi
20
+
21
+ # Ensure time has seconds
22
+ if [ ${#TIME} -eq 4 ]; then
23
+ TIME="${TIME}00"
24
+ fi
25
+
26
+ # First, resolve station names to LIDs
27
+ FROM_LID=$(curl -s -X POST "https://fahrplan.oebb.at/bin/mgate.exe" \
28
+ -H "Content-Type: application/json" \
29
+ -d '{
30
+ "id":"1","ver":"1.67","lang":"deu",
31
+ "auth":{"type":"AID","aid":"OWDL4fE4ixNiPBBm"},
32
+ "client":{"id":"OEBB","type":"WEB","name":"webapp","l":"vs_webapp"},
33
+ "formatted":false,
34
+ "svcReqL":[{
35
+ "req":{"input":{"field":"S","loc":{"name":"'"$FROM"'","type":"S"},"maxLoc":1}},
36
+ "meth":"LocMatch"
37
+ }]
38
+ }' | jq -r '.svcResL[0].res.match.locL[0].lid // empty')
39
+
40
+ TO_LID=$(curl -s -X POST "https://fahrplan.oebb.at/bin/mgate.exe" \
41
+ -H "Content-Type: application/json" \
42
+ -d '{
43
+ "id":"1","ver":"1.67","lang":"deu",
44
+ "auth":{"type":"AID","aid":"OWDL4fE4ixNiPBBm"},
45
+ "client":{"id":"OEBB","type":"WEB","name":"webapp","l":"vs_webapp"},
46
+ "formatted":false,
47
+ "svcReqL":[{
48
+ "req":{"input":{"field":"S","loc":{"name":"'"$TO"'","type":"S"},"maxLoc":1}},
49
+ "meth":"LocMatch"
50
+ }]
51
+ }' | jq -r '.svcResL[0].res.match.locL[0].lid // empty')
52
+
53
+ if [ -z "$FROM_LID" ]; then
54
+ echo "Error: Could not find station: $FROM"
55
+ exit 1
56
+ fi
57
+
58
+ if [ -z "$TO_LID" ]; then
59
+ echo "Error: Could not find station: $TO"
60
+ exit 1
61
+ fi
62
+
63
+ # Now search for trips
64
+ curl -s -X POST "https://fahrplan.oebb.at/bin/mgate.exe" \
65
+ -H "Content-Type: application/json" \
66
+ -d '{
67
+ "id":"1","ver":"1.67","lang":"deu",
68
+ "auth":{"type":"AID","aid":"OWDL4fE4ixNiPBBm"},
69
+ "client":{"id":"OEBB","type":"WEB","name":"webapp","l":"vs_webapp"},
70
+ "formatted":false,
71
+ "svcReqL":[{
72
+ "req":{
73
+ "depLocL":[{"lid":"'"$FROM_LID"'","type":"S"}],
74
+ "arrLocL":[{"lid":"'"$TO_LID"'","type":"S"}],
75
+ "jnyFltrL":[{"type":"PROD","mode":"INC","value":"1023"}],
76
+ "getPolyline":false,
77
+ "getPasslist":true,
78
+ "outDate":"'"$DATE"'",
79
+ "outTime":"'"$TIME"'",
80
+ "outFrwd":true,
81
+ "numF":'"$NUM"'
82
+ },
83
+ "meth":"TripSearch"
84
+ }]
85
+ }' | jq '
86
+ .svcResL[0].res as $res |
87
+ $res.outConL[] | {
88
+ date: .date,
89
+ departure: {
90
+ time: (.dep.dTimeS | "\(.[0:2]):\(.[2:4])"),
91
+ timeReal: (if .dep.dTimeR then (.dep.dTimeR | "\(.[0:2]):\(.[2:4])") else null end),
92
+ platform: .dep.dPltfS.txt,
93
+ station: ($res.common.locL[.dep.locX].name)
94
+ },
95
+ arrival: {
96
+ time: (.arr.aTimeS | "\(.[0:2]):\(.[2:4])"),
97
+ timeReal: (if .arr.aTimeR then (.arr.aTimeR | "\(.[0:2]):\(.[2:4])") else null end),
98
+ platform: .arr.aPltfS.txt,
99
+ station: ($res.common.locL[.arr.locX].name)
100
+ },
101
+ duration: (.dur | "\(.[0:2])h \(.[2:4])m"),
102
+ changes: .chg,
103
+ legs: [.secL[] | select(.type == "JNY") | {
104
+ train: ($res.common.prodL[.jny.prodX].name // "Train"),
105
+ category: ($res.common.prodL[.jny.prodX].prodCtx.catOutL // ""),
106
+ direction: .jny.dirTxt,
107
+ departure: {
108
+ time: (.dep.dTimeS | "\(.[0:2]):\(.[2:4])"),
109
+ station: ($res.common.locL[.dep.locX].name),
110
+ platform: .dep.dPltfS.txt
111
+ },
112
+ arrival: {
113
+ time: (.arr.aTimeS | "\(.[0:2]):\(.[2:4])"),
114
+ station: ($res.common.locL[.arr.locX].name),
115
+ platform: .arr.aPltfS.txt
116
+ }
117
+ }]
118
+ }
119
+ '
@@ -0,0 +1,232 @@
1
+ ---
2
+ name: openscad
3
+ description: "Create and render OpenSCAD 3D models. Generate preview images from multiple angles, extract customizable parameters, validate syntax, and export STL files for 3D printing platforms like MakerWorld."
4
+ ---
5
+
6
+ # OpenSCAD Skill
7
+
8
+ Create, validate, and export OpenSCAD 3D models. Supports parameter customization, visual preview from multiple angles, and STL export for 3D printing platforms like MakerWorld.
9
+
10
+ ## Prerequisites
11
+
12
+ OpenSCAD must be installed. Install via Homebrew:
13
+ ```bash
14
+ brew install openscad
15
+ ```
16
+
17
+ ## Tools
18
+
19
+ This skill provides several tools in the `tools/` directory:
20
+
21
+ ### Preview Generation
22
+ ```bash
23
+ # Generate a single preview image
24
+ ./tools/preview.sh model.scad output.png [--camera=x,y,z,tx,ty,tz,dist] [--size=800x600]
25
+
26
+ # Generate multi-angle preview (front, back, left, right, top, iso)
27
+ ./tools/multi-preview.sh model.scad output_dir/
28
+ ```
29
+
30
+ ### STL Export
31
+ ```bash
32
+ # Export to STL for 3D printing
33
+ ./tools/export-stl.sh model.scad output.stl [-D 'param=value']
34
+ ```
35
+
36
+ ### Parameter Extraction
37
+ ```bash
38
+ # Extract customizable parameters from an OpenSCAD file
39
+ ./tools/extract-params.sh model.scad
40
+ ```
41
+
42
+ ### Validation
43
+ ```bash
44
+ # Check for syntax errors and warnings
45
+ ./tools/validate.sh model.scad
46
+ ```
47
+
48
+ ## Visual Validation (Required)
49
+
50
+ **Always validate your OpenSCAD models visually after creating or modifying them.**
51
+
52
+ After writing or editing any OpenSCAD file:
53
+
54
+ 1. **Generate multi-angle previews** using `multi-preview.sh`
55
+ 2. **View each generated image** using the `read` tool
56
+ 3. **Check for issues** from multiple perspectives:
57
+ - Front/back: Verify symmetry, features, and proportions
58
+ - Left/right: Check depth and side profiles
59
+ - Top: Ensure top features are correct
60
+ - Isometric: Overall shape validation
61
+ 4. **Iterate if needed**: If something looks wrong, fix the code and re-validate
62
+
63
+ This catches issues that syntax validation alone cannot detect:
64
+ - Inverted normals or inside-out geometry
65
+ - Misaligned features or incorrect boolean operations
66
+ - Proportions that don't match the intended design
67
+ - Missing or floating geometry
68
+ - Z-fighting or overlapping surfaces
69
+
70
+ **Never deliver an OpenSCAD model without visually confirming it looks correct from multiple angles.**
71
+
72
+ ## Workflow
73
+
74
+ ### 1. Creating an OpenSCAD Model
75
+
76
+ Write OpenSCAD code with customizable parameters at the top:
77
+
78
+ ```openscad
79
+ // Customizable parameters
80
+ wall_thickness = 2; // [1:0.5:5] Wall thickness in mm
81
+ width = 50; // [20:100] Width in mm
82
+ height = 30; // [10:80] Height in mm
83
+ rounded = true; // Add rounded corners
84
+
85
+ // Model code below
86
+ module main_shape() {
87
+ if (rounded) {
88
+ minkowski() {
89
+ cube([width - 4, width - 4, height - 2]);
90
+ sphere(r = 2);
91
+ }
92
+ } else {
93
+ cube([width, width, height]);
94
+ }
95
+ }
96
+
97
+ difference() {
98
+ main_shape();
99
+ translate([wall_thickness, wall_thickness, wall_thickness])
100
+ scale([1 - 2*wall_thickness/width, 1 - 2*wall_thickness/width, 1])
101
+ main_shape();
102
+ }
103
+ ```
104
+
105
+ Parameter comment format:
106
+ - `// [min:max]` - numeric range
107
+ - `// [min:step:max]` - numeric range with step
108
+ - `// [opt1, opt2, opt3]` - dropdown options
109
+ - `// Description text` - plain description
110
+
111
+ ### 2. Validate the Model
112
+ ```bash
113
+ ./tools/validate.sh model.scad
114
+ ```
115
+
116
+ ### 3. Generate Previews
117
+
118
+ Generate preview images to visually validate the model:
119
+ ```bash
120
+ ./tools/multi-preview.sh model.scad ./previews/
121
+ ```
122
+
123
+ This creates PNG images from multiple angles. Use the `read` tool to view them.
124
+
125
+ ### 4. Export to STL
126
+ ```bash
127
+ ./tools/export-stl.sh model.scad output.stl
128
+ # With custom parameters:
129
+ ./tools/export-stl.sh model.scad output.stl -D 'width=60' -D 'height=40'
130
+ ```
131
+
132
+ ## Camera Positions
133
+
134
+ Common camera angles for previews:
135
+ - **Isometric**: `--camera=0,0,0,45,0,45,200`
136
+ - **Front**: `--camera=0,0,0,90,0,0,200`
137
+ - **Top**: `--camera=0,0,0,0,0,0,200`
138
+ - **Right**: `--camera=0,0,0,90,0,90,200`
139
+
140
+ Format: `x,y,z,rotx,roty,rotz,distance`
141
+
142
+ ## MakerWorld Publishing
143
+
144
+ For MakerWorld, you typically need:
145
+ 1. STL file(s) exported via `export-stl.sh`
146
+ 2. Preview images (at least one good isometric view)
147
+ 3. A description of customizable parameters
148
+
149
+ Consider creating a `model.json` with metadata:
150
+ ```json
151
+ {
152
+ "name": "Model Name",
153
+ "description": "Description for MakerWorld",
154
+ "parameters": [...],
155
+ "tags": ["functional", "container", "organizer"]
156
+ }
157
+ ```
158
+
159
+ ## Example: Full Workflow
160
+
161
+ ```bash
162
+ # 1. Create the model (write .scad file)
163
+
164
+ # 2. Validate syntax
165
+ ./tools/validate.sh box.scad
166
+
167
+ # 3. Generate multi-angle previews
168
+ ./tools/multi-preview.sh box.scad ./previews/
169
+
170
+ # 4. IMPORTANT: View and validate ALL preview images
171
+ # Use the read tool on each PNG file to visually inspect:
172
+ # - previews/box_front.png
173
+ # - previews/box_back.png
174
+ # - previews/box_left.png
175
+ # - previews/box_right.png
176
+ # - previews/box_top.png
177
+ # - previews/box_iso.png
178
+ # Look for geometry issues, misalignments, or unexpected results.
179
+ # If anything looks wrong, go back to step 1 and fix it!
180
+
181
+ # 5. Extract and review parameters
182
+ ./tools/extract-params.sh box.scad
183
+
184
+ # 6. Export STL with default parameters
185
+ ./tools/export-stl.sh box.scad box.stl
186
+
187
+ # 7. Export STL with custom parameters
188
+ ./tools/export-stl.sh box.scad box_large.stl -D 'width=80' -D 'height=60'
189
+ ```
190
+
191
+ **Remember**: Never skip the visual validation step. Many issues (wrong dimensions, boolean operation errors, inverted geometry) are only visible when you actually look at the rendered model.
192
+
193
+ ## OpenSCAD Quick Reference
194
+
195
+ ### Basic Shapes
196
+ ```openscad
197
+ cube([x, y, z]);
198
+ sphere(r = radius);
199
+ cylinder(h = height, r = radius);
200
+ cylinder(h = height, r1 = bottom_r, r2 = top_r); // cone
201
+ ```
202
+
203
+ ### Transformations
204
+ ```openscad
205
+ translate([x, y, z]) object();
206
+ rotate([rx, ry, rz]) object();
207
+ scale([sx, sy, sz]) object();
208
+ mirror([x, y, z]) object();
209
+ ```
210
+
211
+ ### Boolean Operations
212
+ ```openscad
213
+ union() { a(); b(); } // combine
214
+ difference() { a(); b(); } // subtract b from a
215
+ intersection() { a(); b(); } // overlap only
216
+ ```
217
+
218
+ ### Advanced
219
+ ```openscad
220
+ linear_extrude(height) 2d_shape();
221
+ rotate_extrude() 2d_shape();
222
+ hull() { objects(); } // convex hull
223
+ minkowski() { a(); b(); } // minkowski sum (rounding)
224
+ ```
225
+
226
+ ### 2D Shapes
227
+ ```openscad
228
+ circle(r = radius);
229
+ square([x, y]);
230
+ polygon(points = [[x1,y1], [x2,y2], ...]);
231
+ text("string", size = 10);
232
+ ```
@@ -0,0 +1,92 @@
1
+ // Parametric Box with Lid
2
+ // A customizable storage box for 3D printing
3
+
4
+ // === Box Parameters ===
5
+ width = 60; // [20:200] Width in mm
6
+ depth = 40; // [20:200] Depth in mm
7
+ height = 30; // [10:150] Height in mm
8
+ wall_thickness = 2; // [1:0.5:5] Wall thickness in mm
9
+
10
+ // === Lid Parameters ===
11
+ include_lid = true; // Include a separate lid
12
+ lid_height = 8; // [5:30] Lid height in mm
13
+ lid_tolerance = 0.3; // [0.1:0.1:0.8] Gap for lid fit
14
+
15
+ // === Style Options ===
16
+ corner_radius = 3; // [0:10] Corner rounding radius
17
+ add_grip = true; // Add grip indents to lid
18
+
19
+ // === Internal ===
20
+ $fn = 32; // Smoothness
21
+
22
+ // Rounded box module
23
+ module rounded_box(w, d, h, r) {
24
+ if (r > 0) {
25
+ hull() {
26
+ for (x = [r, w-r]) {
27
+ for (y = [r, d-r]) {
28
+ translate([x, y, 0])
29
+ cylinder(h = h, r = r);
30
+ }
31
+ }
32
+ }
33
+ } else {
34
+ cube([w, d, h]);
35
+ }
36
+ }
37
+
38
+ // Main box body
39
+ module box_body() {
40
+ difference() {
41
+ rounded_box(width, depth, height, corner_radius);
42
+
43
+ // Hollow inside
44
+ translate([wall_thickness, wall_thickness, wall_thickness])
45
+ rounded_box(
46
+ width - 2*wall_thickness,
47
+ depth - 2*wall_thickness,
48
+ height, // Open top
49
+ max(0, corner_radius - wall_thickness)
50
+ );
51
+ }
52
+ }
53
+
54
+ // Lid
55
+ module lid() {
56
+ inner_w = width - 2*wall_thickness - 2*lid_tolerance;
57
+ inner_d = depth - 2*wall_thickness - 2*lid_tolerance;
58
+ lip_height = lid_height * 0.6;
59
+
60
+ difference() {
61
+ union() {
62
+ // Top cap
63
+ rounded_box(width, depth, wall_thickness, corner_radius);
64
+
65
+ // Inner lip
66
+ translate([wall_thickness + lid_tolerance, wall_thickness + lid_tolerance, -lip_height + wall_thickness])
67
+ rounded_box(inner_w, inner_d, lip_height, max(0, corner_radius - wall_thickness));
68
+ }
69
+
70
+ // Grip indents
71
+ if (add_grip) {
72
+ for (x = [width * 0.3, width * 0.7]) {
73
+ translate([x, -1, wall_thickness/2])
74
+ rotate([-90, 0, 0])
75
+ cylinder(h = 5, r = 3, $fn = 16);
76
+ translate([x, depth - 4, wall_thickness/2])
77
+ rotate([-90, 0, 0])
78
+ cylinder(h = 5, r = 3, $fn = 16);
79
+ }
80
+ }
81
+ }
82
+ }
83
+
84
+ // Render
85
+ box_body();
86
+
87
+ if (include_lid) {
88
+ // Position lid next to box for printing
89
+ translate([width + 10, 0, lid_height - wall_thickness])
90
+ rotate([180, 0, 0])
91
+ lid();
92
+ }
@@ -0,0 +1,95 @@
1
+ // Adjustable Phone/Tablet Stand
2
+ // Parametric stand with customizable angle and size
3
+
4
+ // === Device Parameters ===
5
+ device_width = 80; // [50:200] Device width in mm
6
+ device_thickness = 12; // [6:20] Device thickness (with case)
7
+
8
+ // === Stand Parameters ===
9
+ stand_angle = 65; // [45:85] Viewing angle in degrees
10
+ stand_depth = 80; // [50:150] Base depth in mm
11
+ stand_height = 100; // [60:200] Back support height in mm
12
+
13
+ // === Construction ===
14
+ material_thickness = 4; // [2:0.5:8] Material thickness
15
+ slot_depth = 15; // [10:30] How deep device sits in slot
16
+
17
+ // === Features ===
18
+ cable_hole = true; // Add cable pass-through hole
19
+ cable_diameter = 15; // [8:25] Cable hole diameter
20
+ add_feet = true; // Add anti-slip feet
21
+
22
+ // === Quality ===
23
+ $fn = 48;
24
+
25
+ module stand_profile() {
26
+ // 2D profile of the stand side
27
+ polygon([
28
+ [0, 0], // Front bottom
29
+ [stand_depth, 0], // Back bottom
30
+ [stand_depth, material_thickness], // Back bottom inner
31
+ [stand_depth - material_thickness, material_thickness], // Base top back
32
+ [slot_depth + material_thickness, material_thickness], // Base top front (behind slot)
33
+ [slot_depth + material_thickness, slot_depth * tan(90 - stand_angle) + material_thickness], // Slot back
34
+ [material_thickness, slot_depth * tan(90 - stand_angle) + material_thickness + device_thickness / sin(stand_angle)], // Slot front top
35
+ [0, slot_depth * tan(90 - stand_angle) + material_thickness], // Front face bottom of slot
36
+ [0, 0] // Close
37
+ ]);
38
+ }
39
+
40
+ module back_support() {
41
+ // Back angled support
42
+ translate([stand_depth - material_thickness, 0, material_thickness]) {
43
+ rotate([0, -90 + stand_angle, 0]) {
44
+ cube([stand_height, device_width, material_thickness]);
45
+ }
46
+ }
47
+ }
48
+
49
+ module cable_cutout() {
50
+ if (cable_hole) {
51
+ translate([stand_depth/2, device_width/2, -1])
52
+ cylinder(h = material_thickness + 2, d = cable_diameter);
53
+ }
54
+ }
55
+
56
+ module foot() {
57
+ cylinder(h = 2, d1 = 10, d2 = 8);
58
+ }
59
+
60
+ module stand() {
61
+ difference() {
62
+ union() {
63
+ // Left side
64
+ linear_extrude(material_thickness)
65
+ stand_profile();
66
+
67
+ // Right side
68
+ translate([0, device_width - material_thickness, 0])
69
+ linear_extrude(material_thickness)
70
+ stand_profile();
71
+
72
+ // Base plate
73
+ cube([stand_depth, device_width, material_thickness]);
74
+
75
+ // Front lip
76
+ cube([material_thickness, device_width, slot_depth * tan(90 - stand_angle) + material_thickness]);
77
+
78
+ // Back support
79
+ back_support();
80
+ }
81
+
82
+ // Cable hole
83
+ cable_cutout();
84
+ }
85
+
86
+ // Feet
87
+ if (add_feet) {
88
+ translate([10, 10, 0]) foot();
89
+ translate([10, device_width - 10, 0]) foot();
90
+ translate([stand_depth - 10, 10, 0]) foot();
91
+ translate([stand_depth - 10, device_width - 10, 0]) foot();
92
+ }
93
+ }
94
+
95
+ stand();
@@ -0,0 +1,50 @@
1
+ #!/bin/bash
2
+ # Common utilities for OpenSCAD tools
3
+
4
+ # Find OpenSCAD executable
5
+ find_openscad() {
6
+ # Check common locations
7
+ if command -v openscad &> /dev/null; then
8
+ echo "openscad"
9
+ return 0
10
+ fi
11
+
12
+ # macOS Application bundle
13
+ if [ -d "/Applications/OpenSCAD.app" ]; then
14
+ echo "/Applications/OpenSCAD.app/Contents/MacOS/OpenSCAD"
15
+ return 0
16
+ fi
17
+
18
+ # Homebrew on Apple Silicon
19
+ if [ -x "/opt/homebrew/bin/openscad" ]; then
20
+ echo "/opt/homebrew/bin/openscad"
21
+ return 0
22
+ fi
23
+
24
+ # Homebrew on Intel
25
+ if [ -x "/usr/local/bin/openscad" ]; then
26
+ echo "/usr/local/bin/openscad"
27
+ return 0
28
+ fi
29
+
30
+ return 1
31
+ }
32
+
33
+ # Check if OpenSCAD is available
34
+ check_openscad() {
35
+ OPENSCAD=$(find_openscad) || {
36
+ echo "Error: OpenSCAD not found!"
37
+ echo ""
38
+ echo "Install OpenSCAD using one of:"
39
+ echo " brew install openscad"
40
+ echo " Download from https://openscad.org/downloads.html"
41
+ exit 1
42
+ }
43
+ export OPENSCAD
44
+ }
45
+
46
+ # Get version info
47
+ openscad_version() {
48
+ check_openscad
49
+ $OPENSCAD --version 2>&1
50
+ }
@@ -0,0 +1,56 @@
1
+ #!/bin/bash
2
+ # Export OpenSCAD file to STL
3
+ # Usage: export-stl.sh input.scad output.stl [-D 'var=value' ...]
4
+
5
+ set -e
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
8
+ source "$SCRIPT_DIR/common.sh"
9
+
10
+ check_openscad
11
+
12
+ if [ $# -lt 2 ]; then
13
+ echo "Usage: $0 input.scad output.stl [-D 'var=value' ...]"
14
+ echo ""
15
+ echo "Examples:"
16
+ echo " $0 box.scad box.stl"
17
+ echo " $0 box.scad box_large.stl -D 'width=80' -D 'height=60'"
18
+ exit 1
19
+ fi
20
+
21
+ INPUT="$1"
22
+ OUTPUT="$2"
23
+ shift 2
24
+
25
+ # Collect -D parameters
26
+ DEFINES=()
27
+ while [ $# -gt 0 ]; do
28
+ case "$1" in
29
+ -D)
30
+ shift
31
+ DEFINES+=("-D" "$1")
32
+ ;;
33
+ *)
34
+ echo "Unknown option: $1"
35
+ exit 1
36
+ ;;
37
+ esac
38
+ shift
39
+ done
40
+
41
+ # Ensure output directory exists
42
+ mkdir -p "$(dirname "$OUTPUT")"
43
+
44
+ echo "Exporting STL: $INPUT -> $OUTPUT"
45
+ if [ ${#DEFINES[@]} -gt 0 ]; then
46
+ echo "Parameters: ${DEFINES[*]}"
47
+ fi
48
+
49
+ $OPENSCAD \
50
+ "${DEFINES[@]}" \
51
+ -o "$OUTPUT" \
52
+ "$INPUT"
53
+
54
+ # Show file info
55
+ SIZE=$(ls -lh "$OUTPUT" | awk '{print $5}')
56
+ echo "STL exported: $OUTPUT ($SIZE)"