diff --git a/MANAGED_CLI_GLOBAL_INSTALL_LOOP_PLAN.md b/MANAGED_CLI_GLOBAL_INSTALL_LOOP_PLAN.md index d82e75db1..32d15c373 100644 --- a/MANAGED_CLI_GLOBAL_INSTALL_LOOP_PLAN.md +++ b/MANAGED_CLI_GLOBAL_INSTALL_LOOP_PLAN.md @@ -1,233 +1,84 @@ -# Managed CLI Global Install Loop Plan +# Managed CLI Global Install -## Task +## Status -Implement a proper desktop-managed CLI install flow for the desktop app. +Implemented on this branch. -The current implementation installs a user-local shim and routes through the desktop app binary with `--paseo-cli-shim `. The target design is: +The original plan was to replace the desktop app's state-file trampoline CLI install flow with a stable outer shim, a bundle-managed inner launcher, structured install results, UI fallback instructions, and Docker-like CLI default host resolution. That work now exists in code and this file records what shipped. -- The installed PATH shim must be trivial and stable, like Tailscale. -- The installed PATH shim should forward argv to an inner executable/shim inside the app bundle. -- The inner executable/shim should resolve the bundled Node runtime and bundled `@getpaseo/cli` entrypoint and invoke the CLI directly. -- The CLI should gain Docker-like default connection resolution so the shim does not need to hardcode daemon transport details. -- The desktop install button should attempt a best-effort global install and clearly communicate that a permissions prompt may appear. -- If global install does not succeed, the app must present a fallback dialog with exact platform-specific terminal instructions. +## What Exists -This work must be planned and implemented cleanly, not patched onto the current state-file trampoline design. +### Desktop runtime and launcher -## Relevant Code And Current Behavior +- The desktop runtime bundle is assembled by [build-managed-runtime.mjs](/Users/moboudra/.paseo/worktrees/1luy0po7/managed-daemon-bundle/packages/desktop/scripts/build-managed-runtime.mjs). +- The Tauri runtime manager now maintains: + - a stable inner launcher under `bin/` + - a trivial outer PATH shim + - structured CLI install/uninstall results + - manual install instructions +- The old `--paseo-cli-shim ` trampoline is no longer the installed shim path. -- `packages/desktop/src-tauri/src/runtime_manager.rs` - - Contains managed runtime install logic, CLI shim install/uninstall logic, and the current `try_run_cli_shim_from_args()` trampoline path. - - Today the installed shim calls the desktop app executable, passes `--paseo-cli-shim `, and the app then resolves bundled Node + bundled CLI and execs them. - - Today macOS/Linux install to `~/.local/bin/paseo`; Windows installs a `.cmd` shim under the user data dir. -- `packages/desktop/src-tauri/src/lib.rs` - - Exposes `install_cli_shim` / `uninstall_cli_shim` Tauri commands and includes some desktop-side `command -v paseo` checks. -- `packages/app/src/desktop/managed-runtime/managed-runtime.ts` - - TypeScript bridge for desktop managed-runtime commands. - - Today `CliShimResult` is only `{ installed, path, message }`. -- `packages/app/src/desktop/components/desktop-updates-section.tsx` - - Contains the Install CLI / Uninstall CLI button and current status-message handling. - - Today there is no preflight permission notice and no fallback modal with manual commands. -- `packages/cli` - - The CLI currently expects explicit host wiring in some flows. It needs to be inspected and updated so that when no host is passed it behaves more like Docker: - 1. use explicit `--host` if provided - 2. otherwise try local socket / local pipe first - 3. then fall back to `localhost:6767` +Relevant files: -## Implementation Requirements +- [runtime_manager.rs](/Users/moboudra/.paseo/worktrees/1luy0po7/managed-daemon-bundle/packages/desktop/src-tauri/src/runtime_manager.rs) +- [lib.rs](/Users/moboudra/.paseo/worktrees/1luy0po7/managed-daemon-bundle/packages/desktop/src-tauri/src/lib.rs) +- [build-managed-runtime.mjs](/Users/moboudra/.paseo/worktrees/1luy0po7/managed-daemon-bundle/packages/desktop/scripts/build-managed-runtime.mjs) +- [managed-daemon-smoke.mjs](/Users/moboudra/.paseo/worktrees/1luy0po7/managed-daemon-bundle/packages/desktop/scripts/managed-daemon-smoke.mjs) -### 1. Replace the current installed shim architecture with a two-shim design +### App UI and desktop bridge -Implement two distinct layers: +- The desktop bridge exposes a structured `CliShimResult` with explicit statuses and optional manual instructions. +- The desktop settings UI now: + - warns that a permissions prompt may appear before install starts + - opens a dedicated modal when manual install is required + - shows copyable platform-specific commands -- Outer PATH shim: - - Must be extremely simple and stable. - - It should do nothing except invoke an inner executable or shim within the installed app/bundle and forward argv. - - It must not embed managed runtime state, a state-file path, runtime id, socket path, or transport details. - - On macOS, the desired style is comparable to: - `#!/bin/sh` - `exec /Applications/Paseo.app/Contents/MacOS/paseo "$@"` -- Inner bundle shim / entrypoint: - - Lives inside the app bundle / managed runtime and is responsible for finding bundled Node and the bundled CLI entrypoint. - - It should invoke the bundled CLI directly, as if launching the packaged CLI with bundled Node. - - It must not route back through the desktop app command trampoline with `--paseo-cli-shim`. +Relevant files: -Remove or obsolete the current state-file-based trampoline path if the new direct inner-shim path fully replaces it. +- [managed-runtime.ts](/Users/moboudra/.paseo/worktrees/1luy0po7/managed-daemon-bundle/packages/app/src/desktop/managed-runtime/managed-runtime.ts) +- [desktop-updates-section.tsx](/Users/moboudra/.paseo/worktrees/1luy0po7/managed-daemon-bundle/packages/app/src/desktop/components/desktop-updates-section.tsx) -### 2. Make CLI host resolution work by default without shim transport knowledge +### CLI default host resolution -Update the CLI so that the launcher does not have to inject managed socket details in the normal case. +- The CLI now resolves default daemon targets in this order when `--host` is not provided: + 1. local IPC target if discoverable + 2. configured non-default TCP target + 3. `localhost:6767` -Desired behavior when the user runs `paseo` and no host is explicitly passed: +Relevant files: -1. If `--host` is passed, use it. -2. Otherwise, try the platform-appropriate local managed/default transport first: - - Unix socket on macOS/Linux - - Named pipe on Windows, if supported by the CLI host format -3. If no local transport is available, fall back to `localhost:6767`. +- [client.ts](/Users/moboudra/.paseo/worktrees/1luy0po7/managed-daemon-bundle/packages/cli/src/utils/client.ts) +- [28-client-ipc-targets.test.ts](/Users/moboudra/.paseo/worktrees/1luy0po7/managed-daemon-bundle/packages/cli/tests/28-client-ipc-targets.test.ts) -The judge must verify the actual host-resolution code path and ensure it is not just documentation text. +## Files Created For This Work -### 3. Add best-effort global install behavior for the desktop-managed CLI +- [MANAGED_CLI_GLOBAL_INSTALL_LOOP_PLAN.md](/Users/moboudra/.paseo/worktrees/1luy0po7/managed-daemon-bundle/MANAGED_CLI_GLOBAL_INSTALL_LOOP_PLAN.md) +- [managed-runtime.test.ts](/Users/moboudra/.paseo/worktrees/1luy0po7/managed-daemon-bundle/packages/app/src/desktop/managed-runtime/managed-runtime.test.ts) +- [managed-runtime.ts](/Users/moboudra/.paseo/worktrees/1luy0po7/managed-daemon-bundle/packages/app/src/desktop/managed-runtime/managed-runtime.ts) +- [managed-tauri-daemon-transport.test.ts](/Users/moboudra/.paseo/worktrees/1luy0po7/managed-daemon-bundle/packages/app/src/utils/managed-tauri-daemon-transport.test.ts) +- [managed-tauri-daemon-transport.ts](/Users/moboudra/.paseo/worktrees/1luy0po7/managed-daemon-bundle/packages/app/src/utils/managed-tauri-daemon-transport.ts) +- [28-client-ipc-targets.test.ts](/Users/moboudra/.paseo/worktrees/1luy0po7/managed-daemon-bundle/packages/cli/tests/28-client-ipc-targets.test.ts) +- [build-managed-runtime.mjs](/Users/moboudra/.paseo/worktrees/1luy0po7/managed-daemon-bundle/packages/desktop/scripts/build-managed-runtime.mjs) +- [managed-daemon-smoke.mjs](/Users/moboudra/.paseo/worktrees/1luy0po7/managed-daemon-bundle/packages/desktop/scripts/managed-daemon-smoke.mjs) +- [runtime_manager.rs](/Users/moboudra/.paseo/worktrees/1luy0po7/managed-daemon-bundle/packages/desktop/src-tauri/src/runtime_manager.rs) +- [resources/.gitkeep](/Users/moboudra/.paseo/worktrees/1luy0po7/managed-daemon-bundle/packages/desktop/src-tauri/resources/.gitkeep) -The desktop Install CLI action should attempt to install the outer shim globally in a platform-appropriate location. +## Acceptance Check -Platform expectations: +- Stable outer shim: covered by runtime-manager tests that assert the outer shim forwards to a stable inner launcher and does not encode runtime-specific roots. +- Inner launcher: implemented inside the managed runtime tree and invoked directly by the outer shim. +- Structured install results: implemented in Rust and parsed in TypeScript without string matching. +- Permission notice and fallback modal: implemented in the desktop settings UI. +- macOS global target: `/usr/local/bin/paseo`. +- CLI host ordering: covered by CLI tests, including the non-default TCP fallback case. -- macOS: - - Prefer `/usr/local/bin/paseo`. - - Attempt a privileged install so the user sees the normal macOS admin-password prompt when needed. - - The flow should be initiated from the click action. -- Linux: - - Best effort only. - - If a reliable privileged install path is not available, fall back to manual instructions. - - Do not fake success. -- Windows: - - Best effort only. - - If protected install cannot be completed automatically, return a manual-instructions path. +## Verification -Do not silently keep the old user-local install path as the primary desktop behavior on macOS. +- Run `npm run typecheck` +- Run targeted tests for the CLI and desktop-managed runtime paths +- Perform a manual macOS smoke check for the privileged `/usr/local/bin/paseo` install flow -### 4. Add explicit user-facing permission guidance before and after the attempt +## Remaining Manual Validation -When the user clicks Install CLI in the desktop UI: - -- Immediately show a clear status line or notice saying that a permissions popup may appear to install the CLI globally. -- Do this before awaiting the install attempt, so the user understands what is happening. - -If installation fails, or if the backend reports that manual action is required: - -- Open a dedicated dialog/modal in the desktop UI. -- The modal should clearly explain: - - A permissions popup should appear prompting for permission to install the CLI globally. - - If it does not work, open a terminal and run the provided commands. -- Include a selectable and copyable code block with exact platform-specific commands. - -### 5. Return structured install results from the desktop backend - -Expand the desktop backend/frontend result shape for CLI install so the UI can make decisions without parsing freeform error strings. - -The result must be rich enough to distinguish at least: - -- install succeeded automatically -- install failed because elevation was denied -- install could not be attempted automatically on this platform -- manual installation is required - -It must also include: - -- installed path, when available -- a user-facing summary message -- manual instructions payload, when required - -### 6. Manual fallback instructions must be real and platform-specific - -The fallback dialog must show platform-specific commands that match the actual chosen launcher architecture. - -That means the commands should install or write the simple outer shim that points at the inner app/bundle shim. - -The commands must use real paths derived from the installed app/runtime layout, not placeholder pseudo-paths. - -They must be quoted correctly for spaces in file paths. - -### 7. Preserve or update uninstall behavior coherently - -Uninstall CLI must continue to remove the installed outer shim from whatever location the new install flow uses. - -If install location or result state changes, update the stored metadata and uninstall behavior accordingly. - -### 8. Update or add verification coverage where practical - -Add or update tests for the new behavior where practical and already patterned in the codebase. - -At minimum, verify: - -- any changed TypeScript types and desktop command parsing -- any changed Rust result serialization assumptions -- any changed CLI host-resolution logic - -If automated coverage for the privileged install is impractical, document the manual verification path in code comments or test notes near the relevant smoke/integration checks rather than hand-waving it away. - -## Constraints - -- Do not reintroduce or preserve the current state-file-based app trampoline design unless strictly required for a narrow compatibility bridge. -- Do not parse user-facing error strings in the frontend to determine behavior. -- Do not implement the UI fallback as a generic alert with truncated instructions; use a proper modal/dialog with copyable command text. -- Do not leave macOS as a `~/.local/bin` install target for the primary desktop-managed global install flow. -- Do not skip typecheck. The repo instructions require typecheck after every change. -- Be careful with existing unrelated worktree changes; do not revert unrelated user changes. - -## Acceptance Criteria - -The judge should mark the loop successful only if all of the following are true: - -1. The installed outer shim architecture has been simplified so it is a trivial forwarder to an inner app/bundle shim or executable, with no embedded managed state-file path. -2. The inner app/bundle shim directly invokes bundled Node + bundled CLI, rather than re-entering the desktop app via the current `--paseo-cli-shim` trampoline. -3. CLI default host resolution has been updated in code so that no-host invocation first prefers local transport and then falls back to `localhost:6767`. -4. The desktop Install CLI UI shows an up-front notice that a permissions popup may appear. -5. The desktop install flow returns structured results that distinguish automatic success from manual fallback cases. -6. On failure or manual-required paths, the desktop UI opens a dedicated dialog/modal with copyable platform-specific terminal instructions. -7. macOS best-effort install targets `/usr/local/bin/paseo` and is implemented as a privileged install attempt rather than the old user-local path. -8. Uninstall behavior remains coherent with the new install location and metadata. -9. `npm run typecheck` passes. -10. Any relevant tests added or changed by the implementation pass, or the worker explicitly documents why a test path is not runnable and what was verified instead. - -## Hard Acceptance Criteria - -The following are non-negotiable. If any one of them is not true in the code, the judge must return `criteria_met: false`. - -### H1. Stable outer shim across runtime updates - -The installed outer PATH shim must not hardcode a versioned managed-runtime path. - -This specifically means: - -- The installed outer shim at `/usr/local/bin/paseo` on macOS/Linux, or the Windows outer shim path, must not point directly into `paths.runtime_root` or any runtime-id/version-specific directory. -- Updating the managed runtime to a new runtime id/version must not require reinstalling the outer PATH shim just to keep `paseo` working. -- The outer shim may point at a stable app-bundle executable path or another stable non-versioned path under app-owned state, but not a versioned runtime root. - -The judge must inspect the actual generated launcher code paths and fail if the installed shim still embeds a runtime-root-specific target. - -### H2. CLI default host resolution must preserve configured non-default TCP daemons - -The new CLI default-resolution logic must not discard a configured non-IPC TCP daemon endpoint. - -Required order when `--host` is not passed: - -1. If an explicit host override is present, use it. -2. Otherwise, if a local IPC target is discoverable, prefer it first. -3. Otherwise, if config or environment resolves to a TCP listen target, use that configured TCP target. -4. Only if none of the above is available, fall back to `localhost:6767`. - -This means a user who has intentionally configured the daemon on a non-default TCP endpoint must still have the CLI connect there by default. - -The judge must fail if the implementation: - -- always falls back to `localhost:6767` after checking only IPC candidates, or -- ignores configured TCP listen values when no IPC target exists. - -### H3. Tests must prove both hard cases - -Automated verification must cover both failure modes found in review: - -- a test or equivalent deterministic proof that the outer shim target remains stable across runtime updates / runtime-id changes, or at minimum that it resolves through a stable path that does not encode the runtime root -- a CLI host-resolution test proving that when no IPC target exists but config/environment specifies a non-default TCP daemon endpoint, that endpoint is chosen before `localhost:6767` - -The judge must not accept hand-wavy reasoning in place of these checks. - -## Execution Notes For Worker - -- Start by tracing the current shim architecture and the CLI host-resolution path before editing. -- Prefer cohesive refactoring over additive patch layers. -- Keep the outer shim intentionally dumb. -- If you need a temporary compatibility bridge, keep it narrow and justify it in code comments or commit notes. -- Run `npm run typecheck` after completing the changes. - -## Execution Notes For Judge - -- Verify the actual code paths, not just strings or comments. -- Inspect whether the old `--paseo-cli-shim ` trampoline still remains as the active path. If so, fail unless it is clearly obsolete and no longer used by the installed outer shim flow. -- Verify that the fallback dialog is a real modal/dialog in the UI code and that it contains actionable copyable instructions. -- Verify the install target logic on macOS is `/usr/local/bin/paseo`. -- Verify `npm run typecheck` was run successfully from logs or explicit output artifacts. +- Privileged install and uninstall prompts still need the normal on-device macOS smoke pass. +- Merge readiness depends on the branch test pass after rebasing onto `origin/main`.