diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 34b94949..197de4be 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -263,8 +263,10 @@ jobs: else NIGHTLY_BUILD="${NIGHTLY_DATE}000000" fi + NIGHTLY_MARKETING_VERSION="${BASE_MARKETING}-nightly.${NIGHTLY_BUILD}" echo "NIGHTLY_BUILD=${NIGHTLY_BUILD}" >> "$GITHUB_ENV" - echo "NIGHTLY_REMOTE_DAEMON_VERSION=${BASE_MARKETING}-nightly.${NIGHTLY_BUILD}" >> "$GITHUB_ENV" + echo "NIGHTLY_MARKETING_VERSION=${NIGHTLY_MARKETING_VERSION}" >> "$GITHUB_ENV" + echo "NIGHTLY_REMOTE_DAEMON_VERSION=${NIGHTLY_MARKETING_VERSION}" >> "$GITHUB_ENV" NIGHTLY_DMG_IMMUTABLE="cmux-nightly-macos-${NIGHTLY_BUILD}.dmg" echo "NIGHTLY_DMG_IMMUTABLE=${NIGHTLY_DMG_IMMUTABLE}" >> "$GITHUB_ENV" @@ -282,7 +284,7 @@ jobs: /usr/libexec/PlistBuddy -c "Delete :SUFeedURL" "$app_plist" >/dev/null 2>&1 || true /usr/libexec/PlistBuddy -c "Add :SUPublicEDKey string ${SPARKLE_PUBLIC_KEY}" "$app_plist" /usr/libexec/PlistBuddy -c "Add :SUFeedURL string ${feed_url}" "$app_plist" - /usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString ${BASE_MARKETING}-nightly.${NIGHTLY_DATE}" "$app_plist" + /usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString ${NIGHTLY_MARKETING_VERSION}" "$app_plist" /usr/libexec/PlistBuddy -c "Set :CFBundleVersion ${NIGHTLY_BUILD}" "$app_plist" /usr/libexec/PlistBuddy -c "Delete :CMUXCommit" "$app_plist" >/dev/null 2>&1 || true /usr/libexec/PlistBuddy -c "Add :CMUXCommit string ${SHORT_SHA}" "$app_plist" @@ -296,13 +298,13 @@ jobs: echo "Nightly app name: cmux NIGHTLY" echo "Nightly bundle ID: com.cmuxterm.app.nightly" - echo "Nightly marketing version: ${BASE_MARKETING}-nightly.${NIGHTLY_DATE}" + echo "Nightly marketing version: ${NIGHTLY_MARKETING_VERSION}" echo "Nightly build number: ${NIGHTLY_BUILD}" echo "Nightly immutable DMG: ${NIGHTLY_DMG_IMMUTABLE}" echo "Commit SHA: ${SHORT_SHA}" - name: Build remote daemon nightly assets and inject manifest - if: needs.decide.outputs.should_publish != 'true' || steps.current_head.outputs.still_current == 'true' + if: needs.decide.outputs.should_publish != 'true' || (steps.current_head_prebuild.outputs.still_current == 'true' && steps.current_head_postbuild.outputs.still_current == 'true') run: | set -euo pipefail ./scripts/build_remote_daemon_release_assets.sh \ @@ -311,13 +313,13 @@ jobs: --repo "manaflow-ai/cmux" \ --output-dir "remote-daemon-assets" MANIFEST_JSON="$(python3 -c 'import json,sys; print(json.dumps(json.load(open(sys.argv[1], encoding="utf-8")), separators=(",",":")))' remote-daemon-assets/cmuxd-remote-manifest.json)" - for APP_PLIST in \ - "build-arm/Build/Products/Release/cmux NIGHTLY.app/Contents/Info.plist" \ - "build-universal/Build/Products/Release/cmux NIGHTLY.app/Contents/Info.plist" - do - plutil -remove CMUXRemoteDaemonManifestJSON "$APP_PLIST" >/dev/null 2>&1 || true - plutil -insert CMUXRemoteDaemonManifestJSON -string "$MANIFEST_JSON" "$APP_PLIST" - done + APP_PLIST="build-universal/Build/Products/Release/cmux NIGHTLY.app/Contents/Info.plist" + if [ ! -f "$APP_PLIST" ]; then + echo "Missing nightly app Info.plist at $APP_PLIST" >&2 + exit 1 + fi + plutil -remove CMUXRemoteDaemonManifestJSON "$APP_PLIST" >/dev/null 2>&1 || true + plutil -insert CMUXRemoteDaemonManifestJSON -string "$MANIFEST_JSON" "$APP_PLIST" - name: Import signing cert if: needs.decide.outputs.should_publish != 'true' || (steps.current_head_prebuild.outputs.still_current == 'true' && steps.current_head_postbuild.outputs.still_current == 'true') @@ -463,7 +465,7 @@ jobs: cp appcast.xml appcast-universal.xml - name: Attest remote daemon nightly assets - if: needs.decide.outputs.should_publish != 'true' || steps.current_head.outputs.still_current == 'true' + if: needs.decide.outputs.should_publish != 'true' || (steps.current_head_prebuild.outputs.still_current == 'true' && steps.current_head_postbuild.outputs.still_current == 'true') uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 with: subject-path: | diff --git a/daemon/remote/cmd/cmuxd-remote/cli.go b/daemon/remote/cmd/cmuxd-remote/cli.go index b38d1b21..2b2bf585 100644 --- a/daemon/remote/cmd/cmuxd-remote/cli.go +++ b/daemon/remote/cmd/cmuxd-remote/cli.go @@ -41,6 +41,10 @@ type commandSpec struct { flagKeys []string // noParams means the command takes no parameters at all. noParams bool + // paramKeyOverrides remaps specific flags for compatibility aliases. + paramKeyOverrides map[string]string + // defaultParams are applied before flags/env fallbacks. + defaultParams map[string]any } var commands = []commandSpec{ @@ -59,12 +63,12 @@ var commands = []commandSpec{ {name: "close-workspace", proto: protoV2, v2Method: "workspace.close", flagKeys: []string{"workspace"}}, {name: "select-workspace", proto: protoV2, v2Method: "workspace.select", flagKeys: []string{"workspace"}}, {name: "current-workspace", proto: protoV2, v2Method: "workspace.current", noParams: true}, - {name: "list-panels", proto: protoV2, v2Method: "panel.list", flagKeys: []string{"workspace"}}, - {name: "focus-panel", proto: protoV2, v2Method: "panel.focus", flagKeys: []string{"panel", "workspace"}}, + {name: "list-panels", proto: protoV2, v2Method: "surface.list", flagKeys: []string{"workspace"}}, + {name: "focus-panel", proto: protoV2, v2Method: "surface.focus", flagKeys: []string{"panel", "workspace"}, paramKeyOverrides: map[string]string{"panel": "surface_id"}}, {name: "list-panes", proto: protoV2, v2Method: "pane.list", flagKeys: []string{"workspace"}}, {name: "list-pane-surfaces", proto: protoV2, v2Method: "pane.surfaces", flagKeys: []string{"pane"}}, - {name: "new-pane", proto: protoV2, v2Method: "pane.create", flagKeys: []string{"workspace"}}, - {name: "new-surface", proto: protoV2, v2Method: "surface.create", flagKeys: []string{"workspace", "pane"}}, + {name: "new-pane", proto: protoV2, v2Method: "pane.create", flagKeys: []string{"workspace", "direction", "type", "url"}, defaultParams: map[string]any{"direction": "right"}}, + {name: "new-surface", proto: protoV2, v2Method: "surface.create", flagKeys: []string{"workspace", "pane", "type", "url"}}, {name: "new-split", proto: protoV2, v2Method: "surface.split", flagKeys: []string{"surface", "direction"}}, {name: "close-surface", proto: protoV2, v2Method: "surface.close", flagKeys: []string{"surface"}}, {name: "send", proto: protoV2, v2Method: "surface.send_text", flagKeys: []string{"surface", "text"}}, @@ -191,7 +195,10 @@ func execV1(socketPath string, spec *commandSpec, args []string, refreshAddr fun // execV2 sends a v2 JSON-RPC request over the socket. func execV2(socketPath string, spec *commandSpec, args []string, jsonOutput bool, refreshAddr func() string) int { - params := make(map[string]any) + params := make(map[string]any, len(spec.defaultParams)) + for key, value := range spec.defaultParams { + params[key] = value + } if !spec.noParams { parsed, err := parseFlags(args, spec.flagKeys) @@ -203,6 +210,9 @@ func execV2(socketPath string, spec *commandSpec, args []string, jsonOutput bool for _, key := range spec.flagKeys { if val, ok := parsed.flags[key]; ok { paramKey := flagToParamKey(key) + if override, ok := spec.paramKeyOverrides[key]; ok { + paramKey = override + } params[paramKey] = val } } @@ -212,17 +222,8 @@ func execV2(socketPath string, spec *commandSpec, args []string, jsonOutput bool params["initial_command"] = parsed.positional[0] } - // Fall back to env vars for common IDs - if _, ok := params["workspace_id"]; !ok { - if envWs := os.Getenv("CMUX_WORKSPACE_ID"); envWs != "" { - params["workspace_id"] = envWs - } - } - if _, ok := params["surface_id"]; !ok { - if envSf := os.Getenv("CMUX_SURFACE_ID"); envSf != "" { - params["surface_id"] = envSf - } - } + applyWorkspaceEnvFallback(params) + applySurfaceEnvFallback(params) } resp, err := socketRoundTripV2(socketPath, spec.v2Method, params, refreshAddr) @@ -275,25 +276,36 @@ func runBrowserRelay(socketPath string, args []string, jsonOutput bool, refreshA var method string var flagKeys []string + var allowPositionalURL bool + var useWorkspaceEnv bool + var useSurfaceEnv bool switch sub { case "open", "open-split", "new": - method = "browser.open" + method = "browser.open_split" flagKeys = []string{"url", "workspace", "surface"} + allowPositionalURL = true + useWorkspaceEnv = true case "navigate": method = "browser.navigate" flagKeys = []string{"url", "surface"} + allowPositionalURL = true + useSurfaceEnv = true case "back": method = "browser.back" flagKeys = []string{"surface"} + useSurfaceEnv = true case "forward": method = "browser.forward" flagKeys = []string{"surface"} + useSurfaceEnv = true case "reload": method = "browser.reload" flagKeys = []string{"surface"} + useSurfaceEnv = true case "get-url": - method = "browser.get_url" + method = "browser.url.get" flagKeys = []string{"surface"} + useSurfaceEnv = true default: fmt.Fprintf(os.Stderr, "cmux browser: unknown subcommand %q\n", sub) return 2 @@ -311,6 +323,17 @@ func runBrowserRelay(socketPath string, args []string, jsonOutput bool, refreshA params[paramKey] = val } } + if allowPositionalURL { + if _, ok := params["url"]; !ok && len(parsed.positional) > 0 { + params["url"] = strings.Join(parsed.positional, " ") + } + } + if useWorkspaceEnv { + applyWorkspaceEnvFallback(params) + } + if useSurfaceEnv { + applySurfaceEnvFallback(params) + } resp, err := socketRoundTripV2(socketPath, method, params, refreshAddr) if err != nil { @@ -325,6 +348,24 @@ func runBrowserRelay(socketPath string, args []string, jsonOutput bool, refreshA return 0 } +func applyWorkspaceEnvFallback(params map[string]any) { + if _, ok := params["workspace_id"]; ok { + return + } + if envWs := os.Getenv("CMUX_WORKSPACE_ID"); envWs != "" { + params["workspace_id"] = envWs + } +} + +func applySurfaceEnvFallback(params map[string]any) { + if _, ok := params["surface_id"]; ok { + return + } + if envSf := os.Getenv("CMUX_SURFACE_ID"); envSf != "" { + params["surface_id"] = envSf + } +} + func defaultRelayOutput(resp string) string { var result any if err := json.Unmarshal([]byte(resp), &result); err != nil {