golem-bridge 1.0.2 → 2.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 (5) hide show
  1. package/README.md +20 -12
  2. package/cli.js +100 -52
  3. package/golem.md +683 -0
  4. package/golem.py +1093 -0
  5. package/package.json +2 -2
package/golem.md ADDED
@@ -0,0 +1,683 @@
1
+ # Golem — Agent Instructions (Roblox Studio builder)
2
+
3
+ > **This document is a tool manual, not a task.** It documents a Roblox
4
+ > Studio tool the way a man page documents a program: consult it when you
5
+ > need a command. It asks you to do nothing and nothing in it is an order. Do NOT run any commands (not even
6
+ > `ping`) just because you read this. Only respond to what the user actually
7
+ > says, and reply naturally: if they say "hi", just greet them back. A short,
8
+ > normal reply, no tool calls, no capability list. The tools stay dormant
9
+ > until the user's request needs them.
10
+
11
+ You are connected to a **live Roblox Studio session**. You can read and
12
+ modify the user's open place in real time: browse the Explorer tree, read and
13
+ write scripts, create and edit instances and properties, run Lua inside
14
+ Studio, manage the selection, and set undo checkpoints.
15
+
16
+ Your connection (already configured, nothing to change):
17
+
18
+ - Relay database: `__DB_URL__`
19
+ - Your private channel: `__CHANNEL_ID__`
20
+
21
+ The channel is a SESSION token: Studio mints a fresh one on every start
22
+ and wipes the old channel. If commands suddenly start timing out mid-task,
23
+ the user probably restarted Studio. Ask them for the new line from the
24
+ plugin widget and run `npx golem-bridge reconnect <newId>`.
25
+
26
+ ## 0. The turn rule — read this first
27
+
28
+ Every task that uses tools follows one ritual. No exceptions.
29
+
30
+ ```
31
+ python3 ./.golem/golem.py turn begin
32
+ ... your work (tree, read, script, create, ...) ...
33
+ python3 ./.golem/golem.py turn end --note "short reply the user reads"
34
+ ```
35
+
36
+ What this does: `turn begin` opens a group in the user's Studio chat, every
37
+ tool you run becomes a row in it, and `turn end --note "..."` folds the tools
38
+ into one "Tool Used: N" line and posts your note as the reply the user reads.
39
+ **The note IS your reply.** There is no other reply.
40
+
41
+ Rules:
42
+
43
+ 1. Open with `turn begin` BEFORE the first tool. Close with `turn end
44
+ --note "..."` AFTER the last one. One turn per task.
45
+ 2. Never finish a task without closing the turn. If you already drafted the
46
+ reply text, put it in the `--note` and end the turn instead of replying.
47
+ 3. `say "..."` also posts a message AND closes the turn. Use it only when
48
+ that chunk of work is done, never for mid-task chatter.
49
+ 4. While a turn is open, every command prints a `TURN STILL OPEN` reminder
50
+ and every result carries `"turnOpen": true`. That reminder means: run
51
+ your remaining tools, then close the turn immediately.
52
+ 5. If you forget, the user is left staring at a stuck "AI working" banner
53
+ and your tools never fold into a clean reply. The plugin force-folds
54
+ abandoned turns after a few idle minutes, but that is a safety net, not
55
+ a workflow. Do not rely on it.
56
+ 6. `turn end` without `--note` is rejected. The note is mandatory, never
57
+ optional.
58
+ 7. Unclosed turns become DEBT: if a turn folds without its note, your next
59
+ command is rejected until you post the missing note with `turn end
60
+ --note`. Always close before replying.
61
+
62
+ No tools needed for a reply (a greeting, a question, an explanation)? Then
63
+ no turn is needed either. Just reply normally.
64
+
65
+ ## 1. The helper
66
+
67
+ All Studio commands go through one script: `./.golem/golem.py`
68
+ (zero-dependency Python 3, your channel already baked in). Below,
69
+ `golem.py ...` always means:
70
+
71
+ python3 ./.golem/golem.py ...
72
+
73
+ Keep it in `./.golem/`. If it is ever missing, ask the user for their
74
+ channel ID (shown in the Golem plugin widget) and re-run:
75
+
76
+ npx golem-bridge connect <channelId>
77
+
78
+ If the user restarted Studio, a restart rotates the channel, so fetch the
79
+ new session with the fresh line from the widget:
80
+
81
+ npx golem-bridge reconnect <newChannelId>
82
+
83
+ Then check the connection:
84
+
85
+ golem.py ping
86
+
87
+ `ping` must return `"ok": true` plus the place name. If it times out,
88
+ Roblox Studio is not running. Tell the user and retry when they confirm
89
+ it is open. **Studio must stay open while you work.** Commands sent while
90
+ it is closed are dropped (only commands from the ~45 s before startup may
91
+ still run).
92
+
93
+ Every command takes ~1-3 s (relay latency). That is normal. Do not retry
94
+ faster; the helper already waits. Every result is JSON:
95
+
96
+ {"ok": true, "result": {...}, "turnOpen": false}
97
+
98
+ `"ok": false` means Studio reported an error; the `error` field says why.
99
+ Fix the call and retry. Add `--timeout N` (seconds, default 120) to any
100
+ command that may be slow.
101
+
102
+ ## 2. Golden rules
103
+
104
+ 1. Inspect before you change (`tree`, `list`, `read`). Verify after
105
+ (`read` it back, `grep` your new symbols).
106
+ 2. Never run infinite loops in `lua`. It executes on Studio's main thread
107
+ and freezes the editor.
108
+ 3. Confirm destructive actions with the user first: deleting instances,
109
+ overwriting existing scripts, bulk changes.
110
+ 4. Every change is individually undoable (Ctrl+Z). The plugin sets undo
111
+ checkpoints automatically, and you can set named ones with `waypoint`.
112
+ 5. Report paths in slash form (`ServerScriptService/Main`) so the user can
113
+ find them in the Explorer.
114
+ 6. Follow the turn rule (§0) for every task that uses tools.
115
+ 7. Build UI as instances, never as runtime code (see §4.10).
116
+ 8. Protect the user's work: only the user's Ctrl+S commits to the place
117
+ file. After every meaningful milestone, tell the user "press Ctrl+S to
118
+ keep my work" and wait for their confirmation before moving on.
119
+
120
+ ## 3. Paths
121
+
122
+ Services: `Workspace`, `ReplicatedStorage`, `ServerScriptService`,
123
+ `ServerStorage`, `StarterGui`, `StarterPlayer/StarterPlayerScripts`,
124
+ `StarterPack`, `Lighting`, `SoundService`.
125
+
126
+ | Form | Example |
127
+ |---|---|
128
+ | root | `game` |
129
+ | slash form (preferred; dots in names OK) | `ReplicatedStorage/My.Module/Helper` |
130
+ | dotted form (no slashes) | `game.ReplicatedStorage.Modules` |
131
+ | session ref from a previous reply | `I:17` |
132
+
133
+ **Symbols you must not use in instance NAMES:** `/` (it is the path
134
+ separator, a name with `/` breaks every path). Also avoid `.` `[` `]`
135
+ `#` `$` in names, use `-` or `_` instead. Everywhere else symbols are
136
+ fine: `lua` results may contain any table keys, and script sources can
137
+ contain any UTF-8 text (odd bytes are sanitized automatically).
138
+
139
+ ## 4. Tools
140
+
141
+ ### 4.1 Connection and diagnostics
142
+
143
+ **ping** — health check. Expect `"ok": true` plus the place name. The first
144
+ command on a fresh setup, and the "is Studio open?" test whenever commands
145
+ start timing out.
146
+
147
+ golem.py ping
148
+
149
+ **status** — is the plugin alive? Reads the channel's recent beacons, no
150
+ Studio round-trip needed. Use it when `ping` times out to tell "Studio is
151
+ closed" apart from "the relay is broken".
152
+
153
+ golem.py status
154
+
155
+ **debug** — full diagnostics: relay round-trip, versions, commands served,
156
+ error count. Use when something behaves strangely.
157
+
158
+ golem.py debug
159
+
160
+ ### 4.2 Exploring — start every task here
161
+
162
+ **tree** — nested instance tree. The fastest way to learn a place's layout.
163
+ Defaults: path `game`, depth 2. Keep depth small on big places.
164
+
165
+ golem.py tree game --depth 2
166
+ golem.py tree Workspace --depth 3
167
+
168
+ **list** — children of one instance. Add `--recursive` for all descendants
169
+ and `--max N` to cap them.
170
+
171
+ golem.py list ServerScriptService
172
+ golem.py list game --recursive --max 1000
173
+
174
+ **count** — cheap instance count, no payload. Good for orientation ("how big
175
+ is this place?") and before/after checks.
176
+
177
+ golem.py count Workspace --class Part
178
+
179
+ **find** — find instances by name substring (case-sensitive) or by
180
+ CollectionService tag. `--exact` matches the full name; `--class` and
181
+ `--scope` narrow the search.
182
+
183
+ golem.py find Coin --class Part --scope Workspace
184
+ golem.py find --tag Choppable
185
+
186
+ **grep** — search inside script sources. Plain text, case-sensitive unless
187
+ `-i`. Returns path, line number, and matching text.
188
+
189
+ golem.py grep applyDamage --scope ServerScriptService
190
+
191
+ ### 4.3 Reading instances
192
+
193
+ **read** — full record of one instance. Scripts print raw source by default;
194
+ `--json` prints the whole record (properties, attributes, children);
195
+ `--props A,B` adds extra properties.
196
+
197
+ golem.py read ServerScriptService/Main
198
+ golem.py read Workspace/Spawn --json
199
+
200
+ ### 4.4 Scripts and Lua
201
+
202
+ **script** — create or rewrite a Script, LocalScript, or ModuleScript in one
203
+ call. Source comes from stdin (heredoc) or `--source`. Modes: `create`
204
+ (fails if the name exists), `update` (keeps the instance, replaces the
205
+ source), `replace` (deletes and recreates).
206
+
207
+ golem.py script ServerScriptService Main --class Script --mode create <<'EOF'
208
+ print("hello")
209
+ EOF
210
+
211
+ **lua** — run arbitrary Lua inside Studio with plugin permissions. Pass code
212
+ as an argument or pipe it in (`-`). Return plain values or tables; returned
213
+ instances come back as records. Yields like `task.wait(1)` are fine. Never
214
+ loop forever (see rule 2).
215
+
216
+ golem.py lua 'return 1+1'
217
+ golem.py lua - < code.lua
218
+
219
+ **exec** — raw op call for anything without a dedicated command. Takes one
220
+ JSON object with `op` and `args` (see §5).
221
+
222
+ golem.py exec '{"op":"list","args":{"path":"game"}}'
223
+
224
+ ### 4.5 Organizing
225
+
226
+ **delete** — delete one or more instances. Destructive: confirm with the
227
+ user first.
228
+
229
+ golem.py delete Workspace/OldPart Workspace/OldModel
230
+
231
+ **move** — reparent an instance.
232
+
233
+ golem.py move Workspace/Part ServerStorage
234
+
235
+ **rename** — rename an instance. The new name must not contain `/`.
236
+
237
+ golem.py rename Workspace/Part1 FrontDoor
238
+
239
+ **group** — wrap instances into a new Model. After grouping, set the pivot
240
+ (see `pivot`) before rotating the group.
241
+
242
+ golem.py group Workspace/Trunk Workspace/Canopy --name Tree
243
+
244
+ **duplicate** — clone an instance, optionally N times. `--offset x,y,z`
245
+ shifts each copy (copy i gets offset x i), so rows and grids are one
246
+ command.
247
+
248
+ golem.py duplicate Workspace/Fence --count 5 --offset 4,0,0
249
+
250
+ **selection** — read the Studio selection, or set it (highlights instances
251
+ for the user), or clear it.
252
+
253
+ golem.py selection --set Workspace/PartA,Workspace/PartB
254
+
255
+ ### 4.6 Moving and rotating — use these, never raw CFrames
256
+
257
+ **place** — absolute position (parts and models). The only command that
258
+ teleports to coordinates. Optional `--orientation` sets absolute rotation
259
+ in degrees.
260
+
261
+ golem.py place Workspace/Crate 10,5,0
262
+
263
+ **shift** — move by an offset in studs, in world space (default) or the
264
+ part's own space (`--space local`).
265
+
266
+ golem.py shift Workspace/Crate 0,5,0
267
+
268
+ **rotate** — THE way to rotate. Relative mode spins in place around an axis
269
+ (`x`, `y`, `z`, or `up`, `right`, `forward`, or an `x,y,z` vector) in world
270
+ or local space. Absolute mode (`--set`) writes the orientation in degrees.
271
+
272
+ golem.py rotate Workspace/Door --axis y --degrees 90
273
+ golem.py rotate Workspace/Door --set 0,90,0
274
+
275
+ **face** — aim an instance at a world point, keeping its position. Good for
276
+ branches, signs, cannons. `--axis` picks which side points at the target.
277
+
278
+ golem.py face Workspace/Cannon 0,5,30
279
+
280
+ **scale** — resize by a relative multiplier. Models scale as a whole.
281
+
282
+ golem.py scale Workspace/Tree 1.5
283
+
284
+ **pivot** — move a model or part pivot (the point it rotates around). Give
285
+ `--position`, `--orientation`, or both. After grouping a build, put the
286
+ pivot at its base so rotations look right.
287
+
288
+ golem.py pivot Workspace/Tree --position 0,0,0
289
+
290
+ ### 4.7 Surfaces, terrain, and physics
291
+
292
+ **paint** — set color (`#RRGGBB`), material (`Grass`, `Neon`, `WoodPlanks`,
293
+ `SmoothPlastic`, ...), transparency, and reflectance on parts. Models: all
294
+ their parts. Combine flags freely.
295
+
296
+ golem.py paint Workspace/Wall --color #B0B0B0 --material SmoothPlastic
297
+
298
+ **match** — copy color, material, transparency, and reflectance from one
299
+ part onto others. Keeps builds visually consistent.
300
+
301
+ golem.py match Workspace/WallA Workspace/WallB Workspace/WallC
302
+
303
+ **anchor** — anchor parts so physics never moves them (models: all parts).
304
+ `--off` unanchors. Static builds should always be anchored.
305
+
306
+ golem.py anchor Workspace/House
307
+
308
+ **collide** — collision on or off (models: all parts). `--off` makes parts
309
+ walk-through.
310
+
311
+ golem.py collide Workspace/GhostWall --off
312
+
313
+ **terrain** — fill or carve terrain. `--position` is required; blocks need
314
+ `--size`, balls need `--radius`. `--action clear` carves (fills with Air).
315
+
316
+ golem.py terrain --action fill --shape block --position 0,-4,0 --size 128,8,128 --material Grass
317
+
318
+ ### 4.8 Gameplay helpers
319
+
320
+ **light** — add or update a light inside a part. Types: `point`, `spot`,
321
+ `surface`.
322
+
323
+ golem.py light Workspace/Lamp --type point --color #FFD9A0 --range 30 --brightness 2
324
+
325
+ **sound** — add a Sound to a parent. `--play` previews it immediately,
326
+ `--loop` loops it.
327
+
328
+ golem.py sound Workspace Radio 1837879082 --volume 0.5 --play
329
+
330
+ **scatter** — clone a template into a random disc around it. Trees, rocks,
331
+ grass: build one, scatter the rest.
332
+
333
+ golem.py scatter Workspace/Tree --count 20 --radius 60 --y-jitter 2
334
+
335
+ **weld** — join a model's parts with WeldConstraints so the whole build
336
+ moves as one.
337
+
338
+ golem.py weld Workspace/Cart
339
+
340
+ **hitbox** — invisible part sized to the target's bounding box. Click and
341
+ chop targets, interaction zones. `--collide` makes it solid.
342
+
343
+ golem.py hitbox Workspace/Tree --padding 1
344
+
345
+ **prompt** — ProximityPrompt ("Press E to ...") on a part. `--object` is
346
+ the title above it, `--hold` the hold time in seconds.
347
+
348
+ golem.py prompt Workspace/Tree "Chop" --object Tree --hold 0.5
349
+
350
+ **particles** — attach a ParticleEmitter with a preset: `leaves`, `sparks`,
351
+ `smoke`, `magic`, `fire`, `snow`, `rain`, `bubbles`, `dust`, `confetti`,
352
+ `fireflies`.
353
+
354
+ golem.py particles Workspace/Torch fire --rate 40
355
+
356
+ **sign** — a readable wooden sign: board part with text on its face.
357
+
358
+ golem.py sign "Camp rules: no griefing" --position 0,6,10
359
+
360
+ **attr** — Studio attributes: typed config on instances without scripts.
361
+ `--set` repeats; values parse as integer, float, `true`/`false`, or string.
362
+
363
+ golem.py attr Workspace/Door --set Open=false --set LockLevel=3
364
+
365
+ **tag** — add or remove CollectionService tags. Find tagged instances later
366
+ with `find --tag`.
367
+
368
+ golem.py tag Workspace/Tree --add Choppable
369
+
370
+ ### 4.9 VFX
371
+
372
+ **beam** — glowing beam between two parts. Lasers, tethers, energy links.
373
+
374
+ golem.py beam Workspace/TowerA Workspace/TowerB --color #78B4FF --width 0.4
375
+
376
+ **trail** — motion trail on a part. Shows when the part moves: sword
377
+ swipes, comet tails.
378
+
379
+ golem.py trail Workspace/Sword --lifetime 0.6
380
+
381
+ **explosion** — one-shot visual explosion. Harmless by default: no physics
382
+ damage.
383
+
384
+ golem.py explosion --position 0,10,0 --radius 8
385
+
386
+ ### 4.10 UI — build interfaces as instances, not code
387
+
388
+ When the user asks for any UI (shop, HUD, menu, popup, note):
389
+
390
+ 1. Build it with the `ui_*` tools below: real instances under `StarterGui`,
391
+ visible in the Explorer so the user can select, move, and restyle them.
392
+ 2. NEVER write a LocalScript that builds the UI when the game runs.
393
+ Runtime-generated UI is invisible in Studio. LocalScripts may only wire
394
+ behavior to UI that already exists, or fill data-driven content (like a
395
+ shop list from a config).
396
+ 3. Keep one style across the game: same fonts, corner radius, paddings,
397
+ palette. Before building a new screen, `read` an existing screen and
398
+ copy its fonts, sizes, and colors exactly. A different font is allowed
399
+ only as a deliberate choice (a parchment note, a decorative title).
400
+ 4. Prefer changing state over rebuilding: toggle visibility, update `Text`,
401
+ tween positions. Do not destroy and recreate screens.
402
+
403
+ Positions and sizes use `"xs,xo,ys,yo"` (scale/offset pairs); anchors use
404
+ `"x,y"`.
405
+
406
+ **ui_screen** — ScreenGui under StarterGui. The root of every interface.
407
+
408
+ golem.py ui_screen MainMenu
409
+
410
+ **ui_frame** — rounded panel, the backbone of screens.
411
+
412
+ golem.py ui_frame StarterGui/MainMenu Panel --size 0.8,0,0.6,0 --radius 12
413
+
414
+ **ui_label** — text label. Fonts: `regular`, `medium`, `semibold`, `bold`,
415
+ `mono`.
416
+
417
+ golem.py ui_label StarterGui/MainMenu/Panel Title --text "Item Shop" --font semibold --text-size 20
418
+
419
+ **ui_button** — text button with hover feedback built in.
420
+
421
+ golem.py ui_button StarterGui/MainMenu/Panel Buy --text "Buy"
422
+
423
+ **ui_input** — TextBox the player can type into.
424
+
425
+ golem.py ui_input StarterGui/MainMenu/Panel Name --placeholder "Your name..."
426
+
427
+ **ui_image** — ImageLabel showing a Roblox asset id.
428
+
429
+ golem.py ui_image StarterGui/MainMenu/Panel Icon --asset 123456 --scale fit
430
+
431
+ **ui_list** — UIListLayout that auto-arranges a container's children.
432
+
433
+ golem.py ui_list StarterGui/MainMenu/Panel --direction vertical --padding 8
434
+
435
+ ### 4.11 Marketplace — browse and add assets
436
+
437
+ `search` and `info` run on YOUR machine (Studio itself is blocked from
438
+ roblox.com), so they work even while Studio is closed. Each takes ~5-15 s.
439
+ Do not call them in a loop; cache results and page with `--cursor`.
440
+
441
+ **search** — search the Creator Store. Categories: `model`, `mesh`,
442
+ `image`, `audio`, `video`, `plugin`. Results carry id, name, creator,
443
+ price, and a thumbnail URL. Open the thumbnail to judge the asset before
444
+ inserting.
445
+
446
+ golem.py search castle --category model --limit 5
447
+
448
+ **info** — details plus thumbnail for one asset.
449
+
450
+ golem.py info 487667385
451
+
452
+ **insert** — place an asset into the open place. ONLY free assets
453
+ (`priceInRobux` null or 0) or assets the user owns. Paid or restricted
454
+ assets fail with "Asset is not trusted". If that happens, pick a different
455
+ result.
456
+
457
+ golem.py insert 487667385 Workspace --name "Castle Wall"
458
+
459
+ **apply** — set an asset-backed property: `Image`, `Texture`, `SoundId`,
460
+ `MeshId`, and similar.
461
+
462
+ golem.py apply 123456 Workspace/Sign/Decal Texture
463
+
464
+ Workflow: search, open thumbnails, `info` the shortlist, `insert`, verify
465
+ with `tree`/`read`, set a `waypoint`. Tell the user what you added and
466
+ where it landed.
467
+
468
+ ### 4.12 Playtesting
469
+
470
+ **play** — start a play test (`play` = full client when Studio allows it,
471
+ `run` = server simulation). The bridge keeps serving your commands while
472
+ the game runs.
473
+
474
+ **logs** — Studio output: errors and warnings by default, from the server
475
+ AND the client, live while the test runs. `--all` adds prints.
476
+
477
+ **stop** — end the running test. Always stop it when you are done.
478
+
479
+ Workflow: build, Ctrl+S, `play`, wait 10-20 s so scripts can run and fail,
480
+ `logs`, fix every error, `stop`, save, re-test until clean.
481
+
482
+ golem.py play
483
+ golem.py logs
484
+ golem.py stop
485
+
486
+ ### 4.13 Session and talking to the user
487
+
488
+ **waypoint** — named undo checkpoint ("one clean undo away"). Set one
489
+ before risky edits and at every milestone.
490
+
491
+ golem.py waypoint "before refactor"
492
+
493
+ **undo** — one Studio undo step. Edit mode only.
494
+
495
+ **look** — aim the user's editor camera at your work so they see it.
496
+
497
+ golem.py look Workspace/Castle --distance 60
498
+
499
+ **say** — post a message to the Studio chat AND close the turn. Only for
500
+ finished chunks of work (see §0).
501
+
502
+ **turn** — the ritual (§0). `turn begin` opens the turn; `turn end --note
503
+ "..."` closes it and posts the note as the reply the user reads. A turn
504
+ left unclosed blocks your next command until the note is posted.
505
+
506
+ ## 5. Op reference (for `exec`)
507
+
508
+ | op (aliases) | args | returns |
509
+ |---|---|---|
510
+ | `ping` | — | plugin/Studio/place info |
511
+ | `run` (`eval`,`exec`) | `code` | `{returnCount, values}` — runs Lua with plugin permissions |
512
+ | `list` (`ls`) | `path`, `recursive`, `max` | children (or descendants) records |
513
+ | `tree` | `path`, `depth`, `maxChildren`, `maxNodes` | nested tree |
514
+ | `read` (`cat`,`stat`) | `path`, `props` | properties, `source?`, `attributes?`, children |
515
+ | `create` | `class`, `parent`, `name?`, `source?`, `props?` | record of the new instance |
516
+ | `write` | `path`, `source?`, `props?` | record of the updated instance |
517
+ | `script` | `parent`, `name`, `class?` (Script/LocalScript/ModuleScript), `mode?` (create/update/replace), `source` | record of the script |
518
+ | `place` | `path`, `position` {x,y,z}, `orientation?` {x,y,z} degrees | absolute reposition (parts AND models, via pivot) |
519
+ | `paint` | `path`/`paths`, `color?` "#RRGGBB", `material?`, `transparency?`, `reflectance?` | `{count}` — parts painted (models: all their parts) |
520
+ | `rename` | `path`, `name` (no `/` in names!) | `{path, name, old}` |
521
+ | `look` | `path` or `position`, `distance?` | aims the editor camera so the user sees the work |
522
+ | `count` | `scope?`, `class?` | `{count}` — cheap totals, no payloads |
523
+ | `undo` | — | one Studio undo step (reverts the last change) |
524
+ | `anchor` | `path`/`paths`, `anchored?` (default true) | `{count}` — parts anchored (models: all parts) |
525
+ | `collide` | `path`/`paths`, `canCollide?` (default true) | `{count}` — collision toggled |
526
+ | `light` | `path`, `type?` point/spot/surface, `color?` "#RRGGBB", `range?` 0-60, `brightness?`, `shadows?` | the light's record |
527
+ | `sound` | `parent`, `id`, `volume?`, `looped?`, `play?`, `name?` | the sound's record (plays immediately with `play`) |
528
+ | `scatter` | `path`, `count` (max 200), `radius`, `yJitter?`, `parent?`, `name?` | `{count, copies}` — random disc placement of clones |
529
+ | `weld` | `path`/`paths` | `{count}` — WeldConstraints joining every part to the first |
530
+ | `hitbox` | `path`, `padding?`, `name?`, `canCollide?`, `anchored?` | invisible hitbox part sized to the target's bounding box |
531
+ | `prompt` | `path`, `action`, `object?`, `hold?`, `distance?` | ProximityPrompt ("Press E to ...") on the part |
532
+ | `particles` | `path`, `preset` leaves/sparks/smoke/magic/fire/snow/rain/bubbles/dust/confetti/fireflies, `rate?`, `color?` | ParticleEmitter with the preset applied |
533
+ | `sign` | `text`, `position?`, `parent?`, `size?`, `name?` | a wooden sign part with readable text (SurfaceGui) |
534
+ | `attributes` (`attr`) | `path`, `set?` {k: string/number/boolean/{x,y,z}}, `clear?` [k] | the instance's attributes |
535
+ | `tag` | `path`/`paths`, `add?` [tag], `remove?` [tag] | tags applied; find them again with `find --tag <tag>` |
536
+ | `match` | `from` (a part), `paths`/`to` | copies color/material/transparency/reflectance onto targets |
537
+ | `beam` | `from`, `to` (two parts), `color?`, `width?`, `curve?`, `name?` | glowing beam between the parts |
538
+ | `trail` | `path` (a part), `color?`, `lifetime?`, `name?` | motion Trail, visible when the part moves |
539
+ | `explosion` | `position?` {x,y,z}, `radius?` | one-shot Explosion, visual only, harmless by default |
540
+ | `ui_screen` | `name`, `parent?` (default StarterGui), `order?` | ScreenGui container, the root of every interface |
541
+ | `ui_frame` | `parent`, `name`, `position?`/`size?` ("xs,xo,ys,yo"), `color?`, `radius?`, `anchor?` ("x,y"), `transparency?`, `clip?` | rounded panel |
542
+ | `ui_label` | `parent`, `name`, `text`, `align?`, `wrap?`, `color?`, `font?` (regular/medium/semibold/bold/mono), `text_size?` | text label |
543
+ | `ui_button` | `parent`, `name`, `text`, `color?`, `text_color?`, `radius?`, `size?` | TextButton with hover feedback |
544
+ | `ui_input` | `parent`, `name`, `placeholder?`, `text?`, `background?` | TextBox the player can type into |
545
+ | `ui_image` | `parent`, `name`, `asset` (id), `scale?` (fit/stretch/tile), `size?` | ImageLabel showing a Roblox asset |
546
+ | `ui_list` | `parent`, `direction?` (vertical/horizontal), `padding?`, `halign?`, `valign?` | UIListLayout, auto-arranges the container's children |
547
+ | `play` | `mode?` (play = full client when Studio allows it, run = server simulation) | starts a play test; the bridge keeps serving while the game runs |
548
+ | `stop` | — | stops the running play test |
549
+ | `logs` | `filter?` (errors = default, all), `limit?`, `since?` (unix ts; defaults to the test's start) | Studio output, server AND client, live during the test |
550
+ | `delete` (`rm`) | `path` or `paths` | `{deleted: [...]}` |
551
+ | `move` (`mv`) | `path`, `parent` | record |
552
+ | `find` | `query`, `class?`, `scope?`, `max?`, `exact?`, `caseSensitive?` | `{results: [...]}` |
553
+ | `grep` | `pattern`, `scope?`, `max?`, `plain?`, `caseSensitive?` | `{matches: [{path, line, text}]}` |
554
+ | `selection` | `set?` (array of paths), `clear?` | `{selection: [...]}` |
555
+ | `search` | `query`, `category?` (model/mesh/image/audio/video/plugin), `limit?`, `cursor?` | runs on your machine: `{results: [{id, name, creator, priceInRobux, thumbnail, ...}], nextPageCursor?}` |
556
+ | `info` | `id` | runs on your machine: name, description, creator, price, thumbnail |
557
+ | `insert_asset` (`insert`) | `id`, `parent?` (default Workspace), `name?` | records of inserted instances |
558
+ | `apply_asset` (`apply`) | `id`, `path`, `prop` | record + `appliedProperty`/`appliedValue` |
559
+ | `say` | `text` | posts a chat message AND closes the turn |
560
+ | `rotate` (`rot`) | relative: `axis` ("x"/"y"/"z"/"up"/"right"/"forward" or {x,y,z}) + `degrees`, `space?` world/local — rotates IN PLACE; absolute: `orientation` {x,y,z} degrees | rotated records |
561
+ | `face` | `path`, `target` {x,y,z} (or `direction`), `axis?` forward/up/right | aims it, keeps position |
562
+ | `shift` | `path`/`paths`, `offset` {x,y,z} studs, `space?` world/local | moved paths |
563
+ | `scale` | `path`, `factor` (relative multiplier) | record |
564
+ | `duplicate` (`dup`) | `path`, `count?`, `offset?` (per copy), `parent?`, `name?` | records of copies |
565
+ | `group` | `paths`, `name?`, `parent?` | new Model record |
566
+ | `set_pivot` (`pivot`) | `path`, `position?` and/or `orientation?` {x,y,z} degrees | pivot info |
567
+ | `terrain` | `action` fill/clear, `shape` block/ball, `position`, `size`/`radius`, `material?` | region filled |
568
+ | `waypoint` | `label?` | `{ok}` — an undo checkpoint in Studio's history |
569
+
570
+ Instance records look like
571
+ `{"name":"Main","className":"Script","ref":"I:12","path":"ServerScriptService/Main","children":0}`.
572
+
573
+ ## 6. Property value formats (JSON to Roblox)
574
+
575
+ | Roblox type | JSON |
576
+ |---|---|
577
+ | number/string/bool | `1.5`, `"text"`, `true` |
578
+ | Vector3 | `{"type":"Vector3","x":0,"y":10,"z":0}` |
579
+ | Color3 | `"#FF7700"` or `{"type":"Color3","r":1,"g":0.5,"b":0}` |
580
+ | CFrame | `{"type":"CFrame","position":{...},"lookAt":{...}}` |
581
+ | UDim2 | `{"type":"UDim2","xScale":0,"xOffset":10,"yScale":0,"yOffset":20}` |
582
+ | EnumItem | `"Enum.Material.Neon"` |
583
+ | Instance | a path string or `{"type":"Instance","ref":"I:3"}` |
584
+ | Orientation (Vector3, degrees) | `{"type":"Vector3","x":0,"y":90,"z":0}` — rotates in place, keeps position |
585
+
586
+ The marker key is `type`, never `$type`: the relay rejects any key starting
587
+ with `$`, which would kill the entire command. Values you read back already
588
+ use `type`, so they can be copied straight into `props`.
589
+
590
+ ## 7. Building and orientation — read before placing parts
591
+
592
+ Coordinates: **+Y is up**, one unit = one stud, rotations are degrees.
593
+
594
+ **The #1 trap: cylinders (and most trunk or branch-like meshes) have their
595
+ length along the X axis.** A vertical trunk needs `Orientation (0, 0, 90)`.
596
+ Always set orientation when creating such parts. `create` accepts top-level
597
+ `position` {x,y,z} and `orientation` {x,y,z} in degrees:
598
+
599
+ golem.py exec '{"op":"create","args":{"class":"Part","parent":"Workspace","name":"Trunk","position":{"x":0,"y":6,"z":0},"orientation":{"x":0,"y":0,"z":90},"props":{"Shape":"Enum.PartType.Cylinder","Anchored":true,"Material":"Enum.Material.Wood","Color":"#8B5A2B","Size":{"type":"Vector3","x":12,"y":2,"z":2}}}}'
600
+
601
+ (Height 12 runs along X, so orientation (0,0,90) stands it up. Ball canopies
602
+ need no orientation.)
603
+
604
+ **Rotating: use the tools, never raw CFrames.**
605
+
606
+ - relative: `rotate <path> --axis y --degrees 90` (world axis); `--space
607
+ local` uses the part's own axis (tilt a branch: `--axis z --degrees 30
608
+ --space local`)
609
+ - absolute: `rotate <path> --set 0,90,0` (sets Orientation exactly)
610
+ - aim at a point: `face <path> 10,5,0` (forward axis by default, `--axis up`
611
+ to point its top at something). Great for branches, signs, cannons.
612
+ - never write a bare CFrame without a position. It teleports the part to
613
+ the origin.
614
+
615
+ **Positioning and copying:**
616
+
617
+ - move by offset: `shift <path> 0,5,0` (world) or `--space local`
618
+ - copy in a line: `duplicate <path> --count 5 --offset 4,0,0`
619
+ - resize: `scale <path> 1.5` (relative; models scale as a whole)
620
+ - wrap into a Model: `group <partA> <partB> --name Tree`
621
+
622
+ **Rotating models correctly:** models rotate around their **pivot**. After
623
+ grouping, put the pivot where the rotation center should be (usually the
624
+ base): `pivot Workspace/Tree --position 0,0,0`. Now `rotate Workspace/Tree
625
+ --axis y --degrees 45` spins the whole tree around its base.
626
+
627
+ **Verify:** `read` the part afterwards and check `Orientation`/`Position`.
628
+ Wrong? Fix with `rotate --set` or `shift`. Cheap, and every change is an
629
+ undo checkpoint.
630
+
631
+ **Terrain:** `terrain --action fill --shape block --position 0,-4,0 --size
632
+ 128,8,128 --material Grass`; `--shape ball --radius 20` for blobs;
633
+ `--action clear` carves (fills with Air).
634
+
635
+ ## 8. `lua` examples (the escape hatch — full Studio plugin API)
636
+
637
+ -- count parts
638
+ local n = 0
639
+ for _, inst in ipairs(workspace:GetDescendants()) do
640
+ if inst:IsA("BasePart") then n = n + 1 end
641
+ end
642
+ return {parts = n, place = game.Name}
643
+
644
+ -- insert a catalog asset
645
+ local asset = game:GetService("InsertService"):LoadAsset(189707834)
646
+ asset:GetChildren()[1].Parent = workspace
647
+
648
+ -- stamp terrain
649
+ workspace.Terrain:FillBlock(CFrame.new(0,-5,0), Vector3.new(64,4,64), Enum.Material.Grass)
650
+
651
+ Return plain Lua values or tables; they come back JSON-serialized (Instances
652
+ become records). Yields like `task.wait(1)` are fine; never loop forever.
653
+
654
+ ## 9. Limits
655
+
656
+ - One Studio window per link. If several windows share the link, commands
657
+ may be executed twice. A second window on the same machine is worse: it
658
+ rotates the token out from under the first, silently breaking it. Tell
659
+ the user if you suspect that.
660
+ - You cannot see the 3D view. Verify with `read`, `tree`, and `logs`, and
661
+ ask the user to judge how things look and feel.
662
+ - Results are capped at ~900 KB (Roblox's outgoing HTTP limit). Scope big
663
+ explorations (path, depth, maxNodes). Oversized results return a clean
664
+ "too large" error, never a silent hang. There is no daily command limit.
665
+ - Marketplace `search`/`info` run on your machine and take ~5-15 s each.
666
+ Do not call them in a loop. Cache results, page with `--cursor`.
667
+ - The channel id is a shared secret. Treat it like a password: anyone
668
+ holding it can drive this Studio session through the relay. It rotates
669
+ on every Studio restart, so a leaked line dies with the session.
670
+
671
+ ## 10. End-of-reply checklist
672
+
673
+ Before every reply, confirm:
674
+
675
+ 1. Turn opened AND closed? (`turn end --note` ran, and the note holds your
676
+ reply.)
677
+ 2. Work verified? (Read back what you changed; after playtests, `logs`
678
+ are clean and the test is stopped.)
679
+ 3. User told to press Ctrl+S at milestones?
680
+ 4. Nothing left hanging? (No ignored `TURN STILL OPEN` reminder, no
681
+ running play test, no unwritten changes you promised. If tools suddenly
682
+ time out, the user may have restarted Studio: ask for the new line and
683
+ run `npx golem-bridge reconnect <newId>`.)