Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 83 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ jobs:
- name: File lock checks
run: ./tests/file_locks.ps1 -Godot $env:GODOT
- name: Package
run: ./tools/package_windows.ps1 -Godot $env:GODOT
run: ./tools/package.ps1 -Godot $env:GODOT
- name: Run the packaged app
run: |
Expand-Archive build/AgentGuildBuilders-windows.zip .smoke
Expand All @@ -102,20 +102,100 @@ jobs:
compression-level: 0
retention-days: 1

build-macos:
runs-on: macos-latest
timeout-minutes: 45
defaults:
run:
shell: pwsh
env:
AGENT_GUILD_CHECKOUT: ${{ github.workspace }}/.agent-guild
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
repository: oddessentials/agent-guild
ref: ${{ env.AGENT_GUILD_COMMIT }}
path: .agent-guild
sparse-checkout: web/yard/assets/gnomeland
- name: Find Godot's folders
run: |
Add-Content $env:GITHUB_ENV "TEMPLATES=$HOME/Library/Application Support/Godot/export_templates"
Add-Content $env:GITHUB_ENV "GODOT=$PWD/.godot-bin/Godot.app/Contents/MacOS/Godot"
- uses: actions/cache@v4
id: godot
with:
path: |
.godot-bin
${{ env.TEMPLATES }}
key: godot-${{ env.GODOT_VERSION }}-macos
- name: Install Godot
if: steps.godot.outputs.cache-hit != 'true'
run: |
$base = "https://github.com/godotengine/godot/releases/download/$env:GODOT_VERSION-stable"
Invoke-WebRequest "$base/Godot_v$env:GODOT_VERSION-stable_macos.universal.zip" -OutFile editor.zip
ditto -x -k editor.zip .godot-bin
Remove-Item editor.zip
Invoke-WebRequest "$base/Godot_v$env:GODOT_VERSION-stable_export_templates.tpz" -OutFile templates.zip
$dest = "$env:TEMPLATES/$env:GODOT_VERSION.stable"
New-Item -ItemType Directory -Force $dest | Out-Null
unzip -j templates.zip templates/macos.zip templates/version.txt -d $dest
if ($LASTEXITCODE -ne 0) { throw 'Extracting the macOS template failed.' }
Remove-Item templates.zip
- uses: actions/cache@v4
with:
path: build/kit-*.tar.gz
key: kit-${{ hashFiles('kits/kit-lock.json') }}
- name: Fetch the kit
run: python3 tools/fetch_kit.py
- name: Import
run: |
& $env:GODOT --headless --path . --editor --import --quit
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
- name: Test
run: |
foreach ($suite in 'run', 'hardening', 'clearance_hardening', 'package_hardening', 'site_commands') {
& $env:GODOT --headless --path . --script "res://tests/$suite.gd"
if ($LASTEXITCODE -ne 0) { throw "$suite failed" }
}
- name: Package
run: ./tools/package.ps1 -Godot $env:GODOT -Platform macOS
- name: Run the packaged app
run: |
ditto -x -k build/AgentGuildBuilders-macos.zip .smoke
codesign --verify --deep --strict .smoke/AgentGuildBuilders.app
if ($LASTEXITCODE -ne 0) { throw 'The packaged app is not validly signed.' }
$exe = '.smoke/AgentGuildBuilders.app/Contents/MacOS/Agent Guild Builders'
$world = Join-Path $PWD '.smoke/world'
& $exe --headless -- --new=meadow --title=Smoke "--save=$world"
if ($LASTEXITCODE -ne 0 -or -not (Test-Path "$world/world.json")) { throw 'The packaged app did not create a world.' }
node tests/mcp_smoke.mjs $exe
if ($LASTEXITCODE -ne 0) { throw 'The packaged app did not serve MCP.' }
- uses: actions/upload-artifact@v4
if: github.event_name == 'push'
with:
name: macos-zip
path: build/AgentGuildBuilders-macos.zip
compression-level: 0
retention-days: 1

release:
if: github.event_name == 'push'
needs: build
needs: [build, build-macos]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/download-artifact@v4
with:
name: windows-zip
- uses: actions/download-artifact@v4
with:
name: macos-zip
- name: Publish
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release create "build-${{ github.run_number }}" AgentGuildBuilders-windows.zip \
gh release create "build-${{ github.run_number }}" AgentGuildBuilders-windows.zip AgentGuildBuilders-macos.zip \
--repo "${{ github.repository }}" --target "${{ github.sha }}" \
--title "Build ${{ github.run_number }}" --generate-notes --latest
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,25 @@
</picture>
</a>
</p>
<p align="center">
On a Mac (macOS 13 or later): <a href="https://github.com/oddessentials/agent-guild-builders/releases/latest/download/AgentGuildBuilders-macos.zip">download for Mac</a>
</p>
<p align="center">
<a href="https://oddessentials.github.io/agent-guild-builders/">For agents</a>
</p>

<details>
<summary>Opening Builders on a Mac for the first time</summary>

Builders isn't notarized by Apple, so macOS asks before it first opens.

1. Unzip the download if your browser didn't, and drag `AgentGuildBuilders` into Applications. Open it from there, so agents you connect can find it.
2. Open it. macOS says it can't verify the app. Choose Done.
3. Open System Settings, then Privacy & Security. Under Security, choose Open Anyway next to AgentGuildBuilders, and confirm.

It opens normally from then on. On macOS 13 or 14 you can instead Control-click the app, choose Open, then Open again.

</details>
<p align="center">
<a href="https://github.com/oddessentials/agent-guild-builders/releases/latest">
<picture>
Expand Down
22 changes: 22 additions & 0 deletions export_presets.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,25 @@ script_export_mode=2
binary_format/embed_pck=true
custom_template/debug=""
custom_template/release=""

[preset.1]

name="macOS"
platform="macOS"
runnable=true
advanced_options=false
dedicated_server=false
custom_features=""
export_filter="all_resources"
include_filter="data/*"
exclude_filter="kits/*, tests/*, tools/*"
export_path="build/AgentGuildBuilders.app"
script_export_mode=2

[preset.1.options]

binary_format/architecture="universal"
custom_template/debug=""
custom_template/release=""
application/bundle_identifier="com.oddessentials.agentguildbuilders"
codesign/codesign=1
2 changes: 2 additions & 0 deletions project.godot
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ config_version=5
config/name="Agent Guild Builders"
config/description="Build an environment for your Agent Guild Yard."
run/print_header=false
run/flush_stdout_on_print=true
run/main_scene="res://src/ui/main.tscn"
config/features=PackedStringArray("4.7", "Forward Plus")

Expand All @@ -16,3 +17,4 @@ window/stretch/aspect="expand"
lights_and_shadows/directional_shadow/soft_shadow_filter_quality=3
lights_and_shadows/directional_shadow/size=8192
textures/default_filters/anisotropic_filtering_level=4
textures/vram_compression/import_etc2_astc=true
87 changes: 85 additions & 2 deletions site/index.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Set up Agent Guild: Builders

Agent Guild: Builders makes environments for Agent Guild Yards. It runs on Windows 10 or 11 (64-bit) and opens a window the person can watch while you build.
Agent Guild: Builders makes environments for Agent Guild Yards. It runs on Windows 10 or 11 (64-bit) or macOS 13 or later, and opens a window the person can watch while you build.

Agent Guild itself: [agentguild.oddessentials.ai/agents](https://agentguild.oddessentials.ai/agents/). Characters and companions for Agent Guild: World: [oddessentials.github.io/agent-guild-steam](https://oddessentials.github.io/agent-guild-steam/).

Follow these steps in order. Run each block in PowerShell. If your shell isn't PowerShell, save the block as a `.ps1` file and run `powershell -NoProfile -ExecutionPolicy Bypass -File <file>`.
On a Mac, follow the Mac steps further down instead. On Windows, follow these steps in order. Run each block in PowerShell. If your shell isn't PowerShell, save the block as a `.ps1` file and run `powershell -NoProfile -ExecutionPolicy Bypass -File <file>`.

## 1. Choose the folder

Expand Down Expand Up @@ -98,3 +98,86 @@ Builders is now set up. Its tools explain how to build.
## Update

Run step 2 again with the same folder. The connection and the person's worlds are kept. Then start a new session, so the Builders tools load again.

## On a Mac

Follow these steps in order. Run each block in Terminal's shell, zsh, or in bash.

## Mac 1. Choose the folder

Every block below starts with this line, which puts Builders in `~/Applications`:

```zsh
dest="$HOME/Applications"
```

Change that line in every block, to `dest='<folder>'` with any `'` in the folder written as `'\''`, if:

- the person named a folder: use it.
- Builders is already connected to you or another agent: use the folder that contains its `AgentGuildBuilders.app`. Find the connected path as in step 1 above.

Don't move Builders after connecting it.

## Mac 2. Download and install

If Builders is already running, ask the person to close its window first. The download is large.

```zsh
dest="$HOME/Applications"
tmp="$(mktemp -d)" &&
curl -fsSL -o "$tmp/AgentGuildBuilders-macos.zip" 'https://github.com/oddessentials/agent-guild-builders/releases/latest/download/AgentGuildBuilders-macos.zip' &&
ditto -x -k "$tmp/AgentGuildBuilders-macos.zip" "$tmp" &&
mkdir -p "$dest" &&
rm -rf "$dest/AgentGuildBuilders.app" &&
mv "$tmp/AgentGuildBuilders.app" "$dest/" &&
rm -rf "$tmp" &&
echo "$dest/AgentGuildBuilders.app/Contents/MacOS/Agent Guild Builders"
```

It prints the full path of the app's executable. Installed this way, macOS doesn't ask the person before Builders first opens.

## Mac 3. Connect

Skip this step if Builders is already connected to you. Otherwise run the block for the agent you are.

Claude Code:

```zsh
dest="$HOME/Applications"
exe="$dest/AgentGuildBuilders.app/Contents/MacOS/Agent Guild Builders"
claude mcp add -s user agent-guild-builders "--" "$exe" --no-header "--" --mcp
```

Codex CLI:

```zsh
dest="$HOME/Applications"
exe="$dest/AgentGuildBuilders.app/Contents/MacOS/Agent Guild Builders"
codex mcp add agent-guild-builders "--" "$exe" --no-header "--" --mcp
```

Antigravity CLI:

```zsh
dest="$HOME/Applications"
exe="$dest/AgentGuildBuilders.app/Contents/MacOS/Agent Guild Builders"
agy mcp add agent-guild-builders "--" "$exe" --no-header "--" --mcp
```

Grok Build:

```zsh
dest="$HOME/Applications"
exe="$dest/AgentGuildBuilders.app/Contents/MacOS/Agent Guild Builders"
grok mcp add agent-guild-builders "--" "$exe" --no-header "--" --mcp
```

Docker Agent: add the toolset shown in step 3 above, with `<exe>` the path Mac step 2 printed.

## Mac 4. Check that it works

Follow step 4 above.

## Mac update

Run Mac step 2 again with the same folder. The connection and the person's worlds are kept. Then start a new session, so the Builders tools load again.
2 changes: 1 addition & 1 deletion src/core/library.gd
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,6 @@ static func duplicate_world(path: String) -> String:
DirAccess.copy_absolute(path.path_join(WorldDoc.THUMB), folder.path_join(WorldDoc.THUMB))
return folder

## Goes to the Recycle Bin, so a mistake can be undone.
## Goes to the Recycle Bin, or the Trash on a Mac, so a mistake can be undone.
static func delete_world(path: String) -> Error:
return OS.move_to_trash(path)
17 changes: 15 additions & 2 deletions src/export/package.gd
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,9 @@ static func _publish_staged(tmp: String, out_path: String, had_previous: bool, r

## Publishes without ever replacing, which a rename can't promise: Windows'
## plain File.Move and a hard link both fail atomically if the name is taken.
## Paths go through the environment, never through a shell or script text.
## Paths never pass through a shell or script text: on Windows they go through
## the environment, and elsewhere ln is started directly, because OS.execute
## runs its command through a shell on macOS.
static func _publish_new(tmp: String, out_path: String, report: Dictionary) -> Error:
var bytes := FileAccess.get_file_as_bytes(tmp)
var output := []
Expand All @@ -252,7 +254,7 @@ static func _publish_new(tmp: String, out_path: String, report: Dictionary) -> E
OS.unset_environment("AGB_PUBLISH_FROM")
OS.unset_environment("AGB_PUBLISH_TO")
else:
moved = OS.execute("ln", [tmp, out_path], output, true)
moved = _hard_link(ProjectSettings.globalize_path(tmp), ProjectSettings.globalize_path(out_path))
var published := moved == 0 and FileAccess.file_exists(out_path) and FileAccess.get_file_as_bytes(out_path) == bytes
if FileAccess.file_exists(tmp):
DirAccess.remove_absolute(tmp)
Expand All @@ -262,6 +264,17 @@ static func _publish_new(tmp: String, out_path: String, report: Dictionary) -> E
return _occupied(report, out_path)
return _write_error(report, FAILED, "publish the export without risking a replacement", out_path)

## /bin/ln with its own pipes, so the paths reach it as they are and it never
## shares stdin or stdout, which carry MCP. Returns ln's exit code, or -1.
static func _hard_link(from: String, to: String) -> int:
var started := OS.execute_with_pipe("/bin/ln", [from, to], false)
var pid: int = started.get("pid", -1)
if pid <= 0:
return -1
while OS.is_process_running(pid):
OS.delay_msec(1)
return OS.get_process_exit_code(pid)

## Windows' extended-length form: no 260-character limit, and taken literally.
static func _extended_path(path: String) -> String:
var p := ProjectSettings.globalize_path(path).simplify_path().replace("/", "\\")
Expand Down
2 changes: 1 addition & 1 deletion src/mcp/tools.gd
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ static func _flat(points: Array) -> Array:
static func export_problem(to: String, replace: bool) -> String:
var path := to.replace("\\", "/")
if not path.is_absolute_path() or path.begins_with("res://") or path.begins_with("user://"):
return "`to` must be a full path, such as C:/Users/you/Documents/My Yard.agworld."
return "`to` must be a full path, such as %s." % ("/Users/you/Documents/My Yard.agworld" if OS.get_name() == "macOS" else "C:/Users/you/Documents/My Yard.agworld")
if path.split("/").has(".."):
return "`to` can't contain `..`."
if not path.to_lower().ends_with(WorldPackage.EXT):
Expand Down
10 changes: 6 additions & 4 deletions src/ui/editor_view.gd
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ func _build_ui() -> void:
_status.size_flags_horizontal = Control.SIZE_EXPAND_FILL
foot_row.add_child(_status)
_result_button = Button.new()
_result_button.text = "Show in folder"
_result_button.text = "Show in Finder" if OS.get_name() == "macOS" else "Show in folder"
_result_button.visible = false
_result_button.pressed.connect(func(): OS.shell_show_in_file_manager(_result_path))
foot_row.add_child(_result_button)
Expand Down Expand Up @@ -2006,8 +2006,9 @@ func _update_undo_buttons() -> void:
var locked := _exporting
_undo_button.disabled = locked or not history.can_undo()
_redo_button.disabled = locked or not history.can_redo()
_undo_button.tooltip_text = "Undo: %s (Ctrl+Z)" % history.undo_label() if history.can_undo() else "Nothing to undo"
_redo_button.tooltip_text = "Redo: %s (Ctrl+Y)" % history.redo_label() if history.can_redo() else "Nothing to redo"
var mac := OS.get_name() == "macOS"
_undo_button.tooltip_text = "Undo: %s (%s)" % [history.undo_label(), "⌘Z" if mac else "Ctrl+Z"] if history.can_undo() else "Nothing to undo"
_redo_button.tooltip_text = "Redo: %s (%s)" % [history.redo_label(), "⇧⌘Z" if mac else "Ctrl+Y"] if history.can_redo() else "Nothing to redo"

func _mark_dirty() -> void:
_dirty_at = Time.get_ticks_msec() / 1000.0
Expand All @@ -2034,7 +2035,8 @@ func _commit_title() -> void:
func _unhandled_key_input(event: InputEvent) -> void:
if not event.pressed or event.echo:
return
if event.ctrl_pressed:
# Cmd on a Mac, Ctrl elsewhere.
if event.is_command_or_control_pressed():
match event.keycode:
KEY_Z:
if event.shift_pressed:
Expand Down
Loading
Loading