Imports:
  - Types:
      - resolve_version
    Usages:
      - resolve-forms
    From: goga/version
  - Types:
      - ProjectConfig
      - load_project_config
    Usages:
      - project-configuration
    From: goga/config
  - Types:
      - resync_registered_agents
    Usages:
      - resync-agents
    From: goga/connect

Usages:
  convention: .goga/usages/conventions.md
  click: .goga/usages/cooks/click.md

Annotations: |
  The `convention` practice is used for:
  - Working with the codebase
  - Organizing the REPL development cycle
  - Debugging and testing
  - Organizing the test infrastructure
  - Understanding the general principles and rules of development and testing in the project

  Use the `click` practice to build the commands, their positional arguments,
  options, and flags (a long form and a short alias may share a single
  Option), the confirmation prompt with a default answer, and exit-code
  propagation.

  Use the `resolve-forms` practice for version form resolution — `resolve_version`
  is the sole owner of the four-form grammar and the single point where malformed
  forms raise ValueError.

  Use the `project-configuration` practice when the bulk path needs to read
  .goga/config.yml — `load_project_config` is the single entrypoint for config consumption,
  and its tools field is a raw mapping validated structurally only.

  After a successful pip install in single and bulk mode, activate every agent
  recorded in ~/.goga/connect.yml via `resync_registered_agents`; the --no-connect
  flag suppresses this activation, and empty mode never activates.

  Use the `resync-agents` practice for the post-change agent re-sync: after a
  successful pip uninstall, call `resync_registered_agents` with the goga home
  resolved for the target user — the re-sync is the removal's artifact-cleanup
  mechanism and never runs under sudo.

  The standard library subprocess, sys, pwd, and pathlib modules are used for
  pip invocation through the current interpreter, HOME resolution by user
  name, and goga-home path construction.

---

"install(ctx: click.Context, name: str | None, sudo: bool, version: str | None, local: str | None, no_connect: bool = False) -> exit_code: int":
  location: install.py
  annotations: |
    Install one or more goga-tool packages into the current runtime interpreter
    via pip and, on success, activate every already-connected agent. Branches
    across four paths: single (one named tool from PyPI with an optional
    four-form version), bulk (every tool declared in config.tools in a single
    pip invocation), empty (Nothing to install), and LOCAL (one pip-installable
    local directory). After a successful pip in single, local, and bulk mode,
    runs the activation re-sync unless `no_connect` is set. Propagates pip's
    returncode as the exit code whenever pip is invoked or re-sync is skipped;
    otherwise propagates the re-sync outcome (first non-zero per-agent failure).

    `exit_code`: pip's outcome when pip is invoked or re-sync is skipped; the
      first non-zero per-agent re-sync failure otherwise; 0 in the empty path.
    `ctx`: Click execution context used to control process exit codes.
    `name`: optional tool identifier without the goga-tool- / goga_tool_ prefix
      (CLI positional argument). When present, the single path runs and the
      config is ignored. When absent, the bulk/empty path runs from cfg.tools.
    `sudo`: when True, run pip under sudo with HOME preserved (Unix-only).
    `version`: optional version-form string in the four-form grammar. Used by
      the single path only (ignored in the bulk path). Resolved by
      `resolve_version`; operator-prefixed or malformed forms raise ValueError
      at resolution time. The CLI flag binding the callback's `version`
      parameter MUST expose both forms: the primary long form --version and
      the secondary short alias -v — Click receives them on a single Option
      so both deserialise into the same parameter.
    `local`: optional path to a pip-installable local directory. When set (and
      `name` is None), the LOCAL path runs: pip installs from the local
      directory instead of resolving goga-tool-<name> from PyPI. Mutually
      exclusive with `name`; `version` is rejected in this mode.
    `no_connect`: when True, skip the post-install activation re-sync — the
      command performs the pip install only. Defaults to False (re-sync enabled).

    Algorithm:
    0. VALIDATIONS (first, before any path):
       0.1. If `name` is not None AND `local` is not None -> raise a user-facing
            ClickException (mutual exclusion: a PyPI tool name and a local source
            path cannot be combined); exit non-zero.
       0.2. If `local` is not None AND `version` is not None -> raise a user-facing
            ClickException (--version applies to the SINGLE path only and is
            meaningless for a local source); exit non-zero.
    1. If `name` is not None → SINGLE PATH:
       1.1. Resolve `version` via `resolve_version`; on rejection, surface a
            user-facing CLI exception with a non-zero exit
       1.2. Compose the package identifier from `name` and the resolved specifier
            (empty when `resolve_version` returned None)
       1.3. Issue one pip install invocation against the current interpreter with
            the composed identifier and an upgrade request; apply sudo with HOME
            preservation when `sudo` is set
       1.4. Propagate pip's outcome as the pip exit code
    2. Else if `local` is not None -> LOCAL PATH:
       2.1. Issue one pip install invocation against the current interpreter with
            the local directory path as the install target and an upgrade request;
            apply sudo with HOME preservation when `sudo` is set. Do NOT validate
            path existence at the CLI layer — pip owns that error and its return
            code is translated unchanged.
       2.2. Propagate pip's outcome as the pip exit code.
    3. If `name` is None → BULK / EMPTY PATH:
       3.1. Load configuration via `load_project_config`; the result is a `ProjectConfig` instance.
            Loader exceptions (OSError, KeyError, ValueError, yaml.YAMLError)
            MUST be wrapped into a user-facing ClickException (non-zero exit) —
            never let a raw loader exception surface as a traceback.
       3.2. Read the tools mapping from cfg.tools (treat None as empty)
       3.3. If the mapping is empty → EMPTY PATH: print "Nothing to install" to
            stdout and exit 0 without invoking pip and without activation
       3.4. Else → BULK PATH: for each (tool_name, form) preserving insertion
            order, resolve the form via `resolve_version` (surface rejection as a
            user-facing CLI exception), compose the identifier, collect it; issue
            exactly one pip install invocation with every collected identifier
            and an upgrade request; apply sudo with HOME preservation when `sudo`
            is set; propagate pip's outcome as the pip exit code
    4. ACTIVATION (single, local, and bulk paths only, after pip):
       (LOCAL participates by the same rules as single/bulk)
       4.1. If `no_connect` is True → keep the pip exit code and stop
       4.2. If the pip exit code is non-zero → keep the pip exit code and stop
       4.3. Otherwise call `resync_registered_agents` with the current user's goga
            home (~/.goga); the routine reads ~/.goga/connect.yml and re-activates
            every recorded agent
       4.4. The final exit code becomes the re-sync outcome (0 on full success or
            a missing/empty registry; otherwise the first non-zero per-agent failure)

    Apply `click` for the command shape, the optional positional argument, the
    flags, the options (including the --local/-l secondary short alias on a
    single Option, like --version/-v), the mutex/version ClickException,
    ctx.pass_context, ctx.exit, click.echo for the empty-path message, and
    exit-code propagation. Apply `convention` for the CLI command docstring rule
    (--help rendered verbatim by Click; omit Args/Returns/Raises), import
    discipline, and structured logging. Apply `project-configuration` for
    `load_project_config` semantics and the no-validation contract on cfg.tools;
    `project-configuration` is NOT used by the LOCAL path (config is ignored,
    same as SINGLE).

    Requirements:
    - The single path MUST ignore cfg.tools entirely — name + flags fully
      determine the call
    - The bulk path MUST issue exactly one pip invocation whose argv contains
      every resolved goga-tool-<name><spec> in YAML order
    - The empty path MUST print "Nothing to install" to stdout and exit 0
      without invoking pip and without activation
    - The --version option is used by the single path only; the bulk path
      MUST NOT consult it
    - The version flag MUST be registered with both the long form --version
      and the short alias -v on the same Click Option (Click secondary
      flag); both forms bind the callback's `version` parameter identically
    - --sudo MUST apply sudo with HOME preservation to the (single) pip argv in
      both single and bulk modes; activation never runs under sudo
    - Activation MUST run only when pip succeeded (exit 0) in single or bulk
      mode and `no_connect` is False; it MUST NOT run in the empty path or after
      a non-zero pip
    - The final exit code MUST equal the pip outcome when pip failed, when
      `no_connect` is set, or in the empty path; otherwise it MUST equal the
      activation re-sync outcome
    - pip MUST be invoked through the current interpreter with an upgrade
      request present in every invocation
    - The LOCAL path MUST install exactly one local directory via a single pip
      invocation with an upgrade request; the local path replaces the PyPI source
    - `name` and --local MUST be mutually exclusive — combining them is a
      user-facing error (non-zero exit)
    - --version MUST be rejected in the LOCAL path (non-zero exit); SINGLE only
    - The LOCAL path MUST participate in post-install activation by the same
      rules as SINGLE/BULK: activation runs when pip succeeded (exit 0) and
      `no_connect` is False; --no-connect suppresses it
    - The LOCAL path MUST translate pip's return code unchanged (including pip's
      own errors for a missing/non-installable path)
    - --sudo MUST apply sudo with HOME preservation to the single pip argv in
      the LOCAL path; activation never runs under sudo
    - The --local/-l flag MUST be registered with both the long form --local
      and the short alias -l on the same Click Option

    Constraints:
    - Do NOT validate, parse, or modify `version` outside `resolve_version` —
      `resolve_version` is the sole owner of the grammar and the single point
      where malformed forms raise ValueError
    - Do NOT accept operator-prefixed forms in either --version or cfg.tools —
      they raise ValueError at resolution time
    - Do NOT install packages sequentially in the bulk path — all resolved
      packages MUST land in one pip argv
    - Do NOT auto-select sudo — the caller opts in via --sudo
    - Do NOT run activation under sudo — activation operates on the local user
      home; only pip honors --sudo
    - Do NOT write ~/.goga/connect.yml directly — activation goes through
      `resync_registered_agents`; this command never writes the registry
    - Do NOT probe whether the package is already installed — the upgrade
      request handles it
    - Do NOT chunk the bulk argv — even a long argv is issued as a single pip
      invocation
    - On Windows, --sudo is unavailable (sudo is Unix-only)
    - Do NOT install in editable mode (-e) in the LOCAL path — install the
      local directory the same way SINGLE/BULK install from PyPI (regular
      install with -U)
    - Do NOT validate the local path's existence at the CLI layer — let pip
      surface the error and translate its exit code
    - Do NOT resolve a goga-tool-<name> identifier in the LOCAL path — the local
      directory is the install target as-is
    - Do NOT consult `version` in the LOCAL path — it is rejected at validation
      time

"uninstall(ctx: click.Context, name: str, sudo: bool = False, yes: bool = False, target_user: str | None = None) -> exit_code: int":
  location: uninstall.py
  annotations: |
    Remove one goga-tool package from the current runtime interpreter via pip
    and, on success, re-sync every connected agent so the removed tool's skills
    and pipelines disappear from the goga home and the agents' symlink trees.

    `ctx`: Click execution context used to control process exit codes.
    `name`: tool identifier without the goga-tool- / goga_tool_ prefix
      (required CLI positional argument).
    `sudo`: when True, run pip under sudo with HOME preserved (Unix-only);
      the re-sync never runs under sudo.
    `yes`: when True, skip the confirmation prompt (the CLI flag exposes a
      long form and a short alias on a single Option; both forms bind this
      parameter identically).
    `target_user`: when set, resolve the goga home of this username via
      pwd.getpwnam for the re-sync instead of the current user's home.
    `exit_code`: pip's outcome when pip was invoked and failed, or 0 when the
      confirmation was declined; 1 via a user-facing ClickException when the
      executable cannot be started (OSError: a missing sudo binary or a
      non-executable interpreter — no returncode exists to propagate);
      otherwise the re-sync outcome.

    Algorithm:
    0. VALIDATIONS (first, before the confirmation prompt and any side
       effect): when `target_user` is set and pwd.getpwnam cannot resolve it
       (unknown user or lookup failure), raise a user-facing ClickException;
       exit non-zero — the confirmation is not shown, pip is not invoked,
       and no re-sync runs
    1. Unless `yes` is set, ask the user to confirm removal with the prompt
       Remove goga tool "<name>"? [Y/n] — the default answer is Y, an empty
       input continues the removal
    2. On a declined confirmation: print a cancellation message to stdout
       and finish with exit code 0 — pip is not invoked and no re-sync runs
    3. Compose the package identifier goga-tool-<name> and issue exactly one
       pip uninstall invocation against the current interpreter with the
       force flag -y; prepend the sudo command with HOME preservation when
       `sudo` is set
    4. When pip exits non-zero: propagate pip's exit code as the final
       result and do not run the re-sync
    5. Resolve the goga home for the re-sync (validated at step 0): the
       home directory of `target_user` via pwd.getpwnam when set, otherwise
       the current user's home
    6. Call `resync_registered_agents` with the resolved goga home; the
       routine reads the registry, re-applies activation per recorded agent,
       and thereby removes the uninstalled tool's orphaned skills and
       pipelines
    7. Return the re-sync outcome as the final exit code

    Apply `click` for the command shape, the required positional argument,
    the confirmation prompt with a default answer, the flag options, the
    pair-form option registration, ctx.pass_context, ctx.exit, click.echo
    for the cancellation message, and exit-code propagation. Apply `convention` for the CLI command docstring rule
    (--help rendered verbatim by Click; omit Args/Returns/Raises), intra-package imports, and
    structured logging. Apply `resync-agents` for the post-removal re-sync:
    when to call it, which goga home to pass, and how its outcome becomes
    the final exit code.

    Requirements:
    - The confirmation prompt MUST be shown before pip runs unless `yes` is
      set; an empty input (Enter) counts as confirmation, an explicit
      lowercase n declines
    - The confirmation flag MUST be registered with both the long form --yes
      and the short alias -y on the same Click Option — both forms bind the
      callback's `yes` parameter identically
    - pip MUST be invoked through the current interpreter as exactly one
      forced uninstall (python -m pip uninstall -y) of the composed
      identifier goga-tool-<name>
    - A declined confirmation MUST finish with exit code 0 and a message on
      stdout; neither pip nor the re-sync may run
    - When standard input ends before the prompt can be answered, the
      confirmation aborts the command with a non-zero exit; the scripted
      form is `yes`
    - A non-zero pip exit MUST propagate unchanged and MUST suppress the
      re-sync
    - pip's "not installed" skip (Skipping ... as it is not installed) is a
      WARNING with exit code 0 — it counts as a pip success: the re-sync
      runs, removes the orphaned artifacts, and the final exit code is the
      re-sync outcome
    - After a successful pip, the re-sync MUST run with the goga home
      resolved from `target_user` when given, otherwise from the current
      user's home; the final exit code MUST equal the re-sync outcome (a
      missing or empty registry is a normal 0)
    - `sudo` MUST apply only to the pip invocation and MUST preserve HOME;
      the re-sync always runs without sudo
    - When both `sudo` and `target_user` are set, `target_user` wins for the
      re-sync's home resolution while pip still runs under sudo
    - An unresolvable `target_user` (pwd.getpwnam KeyError or OSError) MUST
      abort with a user-facing error and a non-zero exit BEFORE the
      confirmation prompt and pip run — nothing is removed

    Constraints:
    - Do NOT validate `name` at the CLI layer — pip owns the
      unknown-package error and its exit code is translated unchanged
    - Do NOT read .goga/config.yml — the command takes exactly one tool
      name and stays config-blind
    - Do NOT offer bulk, empty, or local paths — exactly one package per
      invocation
    - Do NOT add a version form or an activation-suppression flag — pip
      uninstall takes no specifier and the re-sync is part of the removal
      semantics
    - Do NOT read or write the registry directly — the re-sync goes through
      `resync_registered_agents`
    - Do NOT run pip without the force flag -y — the interaction stays at
      the command-level prompt
    - On Windows, `sudo` and pwd-based user resolution are unavailable
      (Unix-only)

---

Author: Goga
CreatedAt: 13/07/26

Description: |
  Lifecycle of goga-tool packages in the current runtime interpreter:
  installation of one, many, or a local source via pip, and removal of one
  tool via a confirmed forced pip uninstall. Every successful pip outcome is
  followed by the shared agent re-sync, and pip's exit code always propagates
  unchanged.
