feat(cli/launcher): build integration + cli.wasm discovery (14a.5c)#30
Merged
hyperpolymath merged 1 commit intoMay 20, 2026
Conversation
Phase 14a.5c. Closes the prep arc: `zig build install` now produces both the launcher binary AND a compiled cli.wasm, placed under the install prefix's share/gossamer/. The launcher discovers cli.wasm at runtime via a standard search order. New file cli/src/Main.eph (~25 lines): Placeholder entry point. Declares env::print_i32 and env::argv_count as extern "env" fns and defines `fn main(): Unit = print_i32(argv_count())`. Compiles cleanly via ephapax compile to a 715-byte .wasm with the expected `main` export. Real CLI subcommand dispatch lands in #15. cli/launcher/build.zig (+30 LOC): • Reads $EPHAPAX env var (defaults to `ephapax` on PATH) so dev workflows can point at a sibling repo's debug build. • addSystemCommand wraps `ephapax compile cli/src/Main.eph -o cli.wasm`. addOutputFileArg makes the .wasm a tracked build artefact. • addInstallFileWithDir places it at `share/gossamer/cli.wasm` under the install prefix. • Run step sets GOSSAMER_WASM to the install location so `zig build run -- args...` uses the freshly-built wasm. cli/launcher/src/main.zig (+50 LOC): • New findCliWasm(allocator) checks (in order): $GOSSAMER_WASM env var, <exe_dir>/../share/gossamer/cli.wasm, /usr/local/share/gossamer/ cli.wasm, /usr/share/gossamer/cli.wasm. Returns null if none exist. • main() falls through to discovery when no .wasm argument is given (argv[1] must end in .wasm to be treated as an explicit override). The guest_argv slice shifts by 1 or 2 depending on whether a wasm path was passed, so the guest sees exactly the user's intended arguments regardless of invocation shape. • Usage message updated to document the search order. cli/launcher/README.adoc: • Documents the new build workflow (EPHAPAX env var, zig build install --prefix, post-install discovery). • Documents the 4-step search order. • Notes that the launcher is still installed as `gossamer-launcher` alongside the existing `gossamer` native binary — the rename + IPC handler migration is #15's job. What's NOT in this PR (intentionally): • Renaming gossamer-launcher → gossamer. Requires migrating the 27 gossamer_channel_bind IPC handlers out of cli/src/main.zig first. • Filling in Main.eph with real CLI subcommand dispatch (dev / build / bundle / run / init / info). That's #15. Verified: • zig ast-check on main.zig + build.zig — clean. • ephapax compile cli/src/Main.eph → 715-byte .wasm with correct imports (env::print_i32, env::argv_count, baseline ephapax) and `main` export. • Wasm module inspection confirms imports + exports match the launcher's expectations. Stacked on 14a.5b (gossamer #29). When 14a.5b merges, this PR's base auto-rebases to main. This concludes Phase 14a (the prep arc). #15 — the actual port of cli/src/main.zig logic into Main.eph — can now proceed against a known- working launcher + bridge surface. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 tasks
7bbbbbd
into
feat/cli-launcher-bridges-14a5b
13 of 14 checks passed
hyperpolymath
added a commit
that referenced
this pull request
May 20, 2026
) * feat(cli/launcher): libgossamer bridges + eager-grant caps (14a.5b) Phase 14a.5b. Adds 29 env::gossamer_* host imports and 1 env::cap_token to the wasmtime launcher MVP from 14a.5a. The guest can now talk to libgossamer (open windows, navigate, run watcher, spawn shells, read conf, etc.) through these bridges. New file cli/launcher/src/bridges.zig (~470 LOC): • extern fn declarations for all 29 libgossamer symbols (matching src/interface/ffi/src/*.zig exports). • marshalling helpers: dupeZGuestString (wasm ptr/len → host null-terminated dupe) and writeCStringToGuest (host C string → guest buffer, WASI-style write-into-caller-buffer pattern). • One bridge callback per symbol. String inputs unmarshal via dupeZGuestString; string outputs use writeCStringToGuest. Opaque handles (watcher, shell child, conf) cross the wasm boundary as i64 (bit-cast u64 = host pointer). • A static Imports table registered with the wasmtime linker. • grantCaps(host_env) eager-grants tokens for ResourceKind 0 (FileSystem), 1 (Network), 2 (Shell). The guest reads them via env::cap_token(kind). Updates cli/launcher/src/main.zig: • HostEnv gains cap_tokens: [6]u64 keyed by ResourceKind. • HostEnv, ImportSpec, guestSlice, and the @cImport bound `c` are now `pub` so bridges.zig can use them. • main() calls bridges.grantCaps() and registers bridges.Imports alongside the 5 baseline imports. Updates cli/launcher/build.zig to link libgossamer (added addLibraryPath + addRPath pointing at ../../src/interface/ffi/zig-out and linkSystemLibrary("gossamer")). Added cli/launcher/src/hello-bridges.wat — smoke test calling gossamer_version_to into a guest buffer and printing via print_string. Validated with wasm-tools parse (208 bytes). Coverage: String-out gossamer_version_to / build_info_to / last_error_to Webview create_ex / navigate / load_html / channel_open / set_csp / set_title / registry_add / run Groove groove_discover / groove_status Watcher watcher_start / watcher_stop Shell shell_spawn / shell_kill Filesystem fs_read_text / fs_write_text / fs_exists / fs_mkdir_p / fs_copy_file Conf conf_load / get_string / get_int / get_bool / has / free Caps cap_token Explicitly out of scope: • The 27 gossamer_channel_bind IPC handlers (window_minimize, group_*, transmute, etc. — currently in cli/src/main.zig) DO NOT bridge through wasm. They live native-side. A follow-up moves them into libgossamer's channel_open default-handler set. • Building cli.wasm from .eph source — 14a.5c. • cli.wasm discovery at /usr/share/gossamer — 14a.5c. Verified: • zig ast-check on main.zig, bridges.zig, build.zig — clean. • wasm-tools parse on hello-bridges.wat — 208-byte valid module. • Full link requires libwasmtime (14a.5a dep) AND libgossamer built in ../../src/interface/ffi/zig-out; both deferred to local / CI build environment. Builds on top of 14a.5a (gossamer #28); PR base set accordingly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(cli/launcher): build integration + cli.wasm discovery (14a.5c) (#30) Phase 14a.5c. Closes the prep arc: `zig build install` now produces both the launcher binary AND a compiled cli.wasm, placed under the install prefix's share/gossamer/. The launcher discovers cli.wasm at runtime via a standard search order. New file cli/src/Main.eph (~25 lines): Placeholder entry point. Declares env::print_i32 and env::argv_count as extern "env" fns and defines `fn main(): Unit = print_i32(argv_count())`. Compiles cleanly via ephapax compile to a 715-byte .wasm with the expected `main` export. Real CLI subcommand dispatch lands in #15. cli/launcher/build.zig (+30 LOC): • Reads $EPHAPAX env var (defaults to `ephapax` on PATH) so dev workflows can point at a sibling repo's debug build. • addSystemCommand wraps `ephapax compile cli/src/Main.eph -o cli.wasm`. addOutputFileArg makes the .wasm a tracked build artefact. • addInstallFileWithDir places it at `share/gossamer/cli.wasm` under the install prefix. • Run step sets GOSSAMER_WASM to the install location so `zig build run -- args...` uses the freshly-built wasm. cli/launcher/src/main.zig (+50 LOC): • New findCliWasm(allocator) checks (in order): $GOSSAMER_WASM env var, <exe_dir>/../share/gossamer/cli.wasm, /usr/local/share/gossamer/ cli.wasm, /usr/share/gossamer/cli.wasm. Returns null if none exist. • main() falls through to discovery when no .wasm argument is given (argv[1] must end in .wasm to be treated as an explicit override). The guest_argv slice shifts by 1 or 2 depending on whether a wasm path was passed, so the guest sees exactly the user's intended arguments regardless of invocation shape. • Usage message updated to document the search order. cli/launcher/README.adoc: • Documents the new build workflow (EPHAPAX env var, zig build install --prefix, post-install discovery). • Documents the 4-step search order. • Notes that the launcher is still installed as `gossamer-launcher` alongside the existing `gossamer` native binary — the rename + IPC handler migration is #15's job. What's NOT in this PR (intentionally): • Renaming gossamer-launcher → gossamer. Requires migrating the 27 gossamer_channel_bind IPC handlers out of cli/src/main.zig first. • Filling in Main.eph with real CLI subcommand dispatch (dev / build / bundle / run / init / info). That's #15. Verified: • zig ast-check on main.zig + build.zig — clean. • ephapax compile cli/src/Main.eph → 715-byte .wasm with correct imports (env::print_i32, env::argv_count, baseline ephapax) and `main` export. • Wasm module inspection confirms imports + exports match the launcher's expectations. Stacked on 14a.5b (gossamer #29). When 14a.5b merges, this PR's base auto-rebases to main. This concludes Phase 14a (the prep arc). #15 — the actual port of cli/src/main.zig logic into Main.eph — can now proceed against a known- working launcher + bridge surface. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
hyperpolymath
added a commit
that referenced
this pull request
May 20, 2026
…#31) Moves the 28 channel-bind handlers (27 window/group/transmute/debug/ groove + 1 shell-exec) from cli/src/main.zig into the new src/interface/ffi/src/ipc_handlers.zig. libgossamer now registers them automatically via gossamer_channel_register_defaults; the native CLI calls that single function instead of binding each handler manually. Why now: The upcoming Ephapax-wasm CLI (#15 proper) opens a channel and would otherwise have to bridge 28 wasmtime_func_callback_t shims back into libgossamer just to do work that's already living inside libgossamer. Moving the defaults out of the application code makes wasm and native dispatch byte-identical and shrinks the cli/ surface area considerably. Numbers: cli/src/main.zig 1335 → 690 lines (-645) libgossamer (new file) 0 → 644 lines (+644 ipc_handlers.zig) libgossamer main.zig +10 (comptime import block) Net deletion in cli/, net move into libgossamer. The handler bodies themselves are unchanged byte-for-byte aside from the now-extern declarations of the gossamer_* symbols they call (since they sit one module away from main.zig in the libgossamer build now). What the migration touches: • src/interface/ffi/src/ipc_handlers.zig (new) — extracts the whole IPC block from cli/src/main.zig: shellExecHandler, extractSimpleJsonField, the 27 window-control handlers, the bindWindowControlHandlers helper, and one new public export: pub export fn gossamer_channel_register_defaults(channel, handle_ptr) void • src/interface/ffi/src/main.zig — adds the comptime import line so the new module's exports land in libgossamer's symbol table. • cli/src/main.zig: - Removes the 27 window/group/transmute/debug/groove handlers - Removes shellExecHandler + extractSimpleJsonField - Removes bindWindowControlHandlers - Removes ~30 now-unused extern fn declarations (gossamer_show, hide, minimize, maximize, restore, resize, request_close, set_title, eval, emit, destroy, guard_set/get, group_*, raise/lower, broadcast, send_to, arrange, transmute, transmute_get, activity_set/get, debug_*, groove_dock/undock/ connect_typed/disconnect_typed/query_type, channel_bind, channel_bind_async) - Adds the new extern fn gossamer_channel_register_defaults - Replaces two call sites in cmdDev / cmdRun: bindWindowControlHandlers(channel, handle) + gossamer_channel_bind_async(channel, "opsm_runtime", ...) becomes simply: gossamer_channel_register_defaults(channel, handle) Verified: • zig ast-check on all three touched files — clean. • Full link requires GTK3/WebKitGTK dev headers (pre-existing baseline limitation in this WSL env); CI runs the full build. First step of #15 (the gossamer CLI port to typed-wasm Ephapax). Logically independent of the launcher prep stack (PRs #28/#29/#30) — base is main. After this lands, the Ephapax-side Main.eph can drive the webview through the launcher's existing bridges without re-implementing any IPC handlers. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 20, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase 14a.5c — closes the prep arc. `zig build install` now produces both the launcher binary AND a compiled `cli.wasm`, placed under the install prefix's `share/gossamer/`. The launcher discovers `cli.wasm` at runtime via a standard search order.
What lands
Discovery search order
Explicit `gossamer-launcher /path/to/foo.wasm` still works (MVP smoke-test workflow); `argv[1]` is treated as an override when it ends in `.wasm`.
Build workflow
```sh
Use a dev build of ephapax instead of one on PATH:
export EPHAPAX=$HOME/dev/repos/ephapax/target/debug/ephapax
cd cli/launcher
zig build install --prefix /usr/local
→ /usr/local/bin/gossamer-launcher
→ /usr/local/share/gossamer/cli.wasm
gossamer-launcher # discovers + runs cli.wasm
gossamer-launcher dev # passes 'dev' to the guest
zig build run -- dev # same via build step
```
Test plan
NOT in scope
What this concludes
This is the final Phase 14a PR. Six prep PRs across two repos:
With these merged, #15 (the actual port) has a known-working launcher + bridge surface to write `Main.eph` against.
🤖 Generated with Claude Code