feat: add psagentai agent with deployment and configuration

Initial project setup including the main agent script, configuration
files, profile JSON state, systemd service unit, startup script,
deployment documentation, and Python dependencies.

Includes .env, venv/, and __pycache__/ which should likely be
excluded via .gitignore to avoid committing secrets and artifacts.
This commit is contained in:
2026-10-25 17:05:13 -03:00
commit 6b3b6541a7
3792 changed files with 805563 additions and 0 deletions
@@ -0,0 +1,21 @@
from pip._internal.distributions.base import AbstractDistribution
from pip._internal.distributions.sdist import SourceDistribution
from pip._internal.distributions.wheel import WheelDistribution
from pip._internal.req.req_install import InstallRequirement
def make_distribution_for_install_requirement(
install_req: InstallRequirement,
) -> AbstractDistribution:
"""Returns a Distribution for the given InstallRequirement"""
# Editable requirements will always be source distributions. They use the
# legacy logic until we create a modern standard for them.
if install_req.editable:
return SourceDistribution(install_req)
# If it's a wheel, it's a WheelDistribution
if install_req.is_wheel:
return WheelDistribution(install_req)
# Otherwise, a SourceDistribution
return SourceDistribution(install_req)
@@ -0,0 +1,57 @@
from __future__ import annotations
import abc
from typing import TYPE_CHECKING
from pip._internal.metadata.base import BaseDistribution
from pip._internal.req import InstallRequirement
if TYPE_CHECKING:
from pip._internal.build_env import BuildEnvironmentInstaller, BuildIsolationMode
class AbstractDistribution(metaclass=abc.ABCMeta):
"""A base class for handling installable artifacts.
The requirements for anything installable are as follows:
- we must be able to determine the requirement name
(or we can't correctly handle the non-upgrade case).
- for packages with setup requirements, we must also be able
to determine their requirements without installing additional
packages (for the same reason as run-time dependencies)
- we must be able to create a Distribution object exposing the
above metadata.
- if we need to do work in the build tracker, we must be able to generate a unique
string to identify the requirement in the build tracker.
"""
def __init__(self, req: InstallRequirement) -> None:
super().__init__()
self.req = req
@property
@abc.abstractmethod
def build_tracker_id(self) -> str | None:
"""A string that uniquely identifies this requirement to the build tracker.
If None, then this dist has no work to do in the build tracker, and
``.prepare_distribution_metadata()`` will not be called."""
raise NotImplementedError()
@abc.abstractmethod
def get_metadata_distribution(self) -> BaseDistribution:
raise NotImplementedError()
@abc.abstractmethod
def prepare_distribution_metadata(
self,
build_env_installer: BuildEnvironmentInstaller,
build_isolation: BuildIsolationMode,
check_build_deps: bool,
allow_editables: bool,
) -> None:
raise NotImplementedError()
@@ -0,0 +1,34 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from pip._internal.distributions.base import AbstractDistribution
from pip._internal.metadata import BaseDistribution
if TYPE_CHECKING:
from pip._internal.build_env import BuildEnvironmentInstaller, BuildIsolationMode
class InstalledDistribution(AbstractDistribution):
"""Represents an installed package.
This does not need any preparation as the required information has already
been computed.
"""
@property
def build_tracker_id(self) -> str | None:
return None
def get_metadata_distribution(self) -> BaseDistribution:
assert self.req.satisfied_by is not None, "not actually installed"
return self.req.satisfied_by
def prepare_distribution_metadata(
self,
build_env_installer: BuildEnvironmentInstaller,
build_isolation: BuildIsolationMode,
check_build_deps: bool,
allow_editables: bool,
) -> None:
pass
@@ -0,0 +1,193 @@
from __future__ import annotations
import logging
from collections.abc import Iterable
from contextlib import nullcontext
from typing import TYPE_CHECKING
from pip._internal.build_env import (
BuildIsolationMode,
NoOpBuildEnvironment,
VenvBuildEnvironment,
VirtualBuildEnvironment,
)
from pip._internal.distributions.base import AbstractDistribution
from pip._internal.exceptions import InstallationError
from pip._internal.metadata import BaseDistribution
from pip._internal.utils.subprocess import runner_with_spinner_message
if TYPE_CHECKING:
from pip._internal.build_env import BuildEnvironmentInstaller
logger = logging.getLogger(__name__)
class SourceDistribution(AbstractDistribution):
"""Represents a source distribution.
The preparation step for these needs metadata for the packages to be
generated.
"""
@property
def build_tracker_id(self) -> str | None:
"""Identify this requirement uniquely by its link."""
assert self.req.link
return self.req.link.url_without_fragment
def get_metadata_distribution(self) -> BaseDistribution:
return self.req.get_dist()
def prepare_distribution_metadata(
self,
build_env_installer: BuildEnvironmentInstaller,
build_isolation: BuildIsolationMode,
check_build_deps: bool,
allow_editables: bool,
) -> None:
# Load pyproject.toml and set up backend environment
self.req.load_pyproject_toml()
self._prepare_build_env(build_isolation, build_env_installer)
# Set up the build isolation, if this requirement should be isolated
if build_isolation != "off":
# Setup an isolated environment and install the build backend static
# requirements in it.
self._prepare_build_backend(build_isolation)
# Check that the build backend supports PEP 660. This cannot be done
# earlier because we need to setup the build backend to verify it
# supports build_editable, nor can it be done later, because we want
# to avoid installing build requirements needlessly.
self.req.editable_sanity_check()
# Install the dynamic build requirements.
self._install_build_reqs(
build_env_installer, build_isolation, allow_editables=allow_editables
)
else:
# When not using build isolation, we still need to check that
# the build backend supports PEP 660.
self.req.editable_sanity_check()
# Check if the current environment provides build dependencies
if check_build_deps:
pyproject_requires = self.req.pyproject_requires
assert pyproject_requires is not None
conflicting, missing = self.req.build_env.check_requirements(
pyproject_requires
)
if conflicting:
self._raise_conflicts("the backend dependencies", conflicting)
if missing:
self._raise_missing_reqs(missing)
self.req.prepare_metadata(allow_editables)
def _prepare_build_env(
self,
build_isolation: BuildIsolationMode,
build_env_installer: BuildEnvironmentInstaller,
) -> None:
if build_isolation == "virtual":
self.req.build_env = VirtualBuildEnvironment(build_env_installer)
elif build_isolation == "venv":
self.req.build_env = VenvBuildEnvironment(build_env_installer)
self.req.configure_backend(self.req.build_env.python_executable)
def _prepare_build_backend(self, build_isolation: BuildIsolationMode) -> None:
# Install the pyproject.toml declared build-time requirements.
pyproject_requires = self.req.pyproject_requires
assert pyproject_requires is not None
assert not isinstance(self.req.build_env, NoOpBuildEnvironment)
context = self.req.build_env if build_isolation == "venv" else nullcontext()
with context:
self.req.build_env.install_requirements(
pyproject_requires,
"overlay",
kind="build dependencies",
for_req=self.req,
)
conflicting, missing = self.req.build_env.check_requirements(
self.req.requirements_to_check
)
if conflicting:
self._raise_conflicts("PEP 517/518 supported requirements", conflicting)
if missing:
logger.warning(
"Missing build requirements in pyproject.toml for %s.",
self.req,
)
logger.warning(
"The project does not specify a build backend, and "
"pip cannot fall back to setuptools without %s.",
" and ".join(map(repr, sorted(missing))),
)
def _get_build_requires_wheel(self) -> Iterable[str]:
with self.req.build_env:
runner = runner_with_spinner_message("Getting requirements to build wheel")
backend = self.req.pep517_backend
assert backend is not None
with backend.subprocess_runner(runner):
return backend.get_requires_for_build_wheel()
def _get_build_requires_editable(self) -> Iterable[str]:
with self.req.build_env:
runner = runner_with_spinner_message(
"Getting requirements to build editable"
)
backend = self.req.pep517_backend
assert backend is not None
with backend.subprocess_runner(runner):
return backend.get_requires_for_build_editable()
def _install_build_reqs(
self,
build_env_installer: BuildEnvironmentInstaller,
build_isolation: BuildIsolationMode,
allow_editables: bool,
) -> None:
# Install any extra build dependencies that the backend requests.
# This must be done in a second pass, as the pyproject.toml
# dependencies must be installed before we can call the backend.
if (
self.req.editable
and allow_editables
and self.req.supports_pyproject_editable
):
build_reqs = self._get_build_requires_editable()
else:
build_reqs = self._get_build_requires_wheel()
conflicting, missing = self.req.build_env.check_requirements(build_reqs)
if conflicting:
self._raise_conflicts("the backend dependencies", conflicting)
context = self.req.build_env if build_isolation == "venv" else nullcontext()
with context:
self.req.build_env.install_requirements(
missing, "normal", kind="backend dependencies", for_req=self.req
)
def _raise_conflicts(
self, conflicting_with: str, conflicting_reqs: set[tuple[str, str]]
) -> None:
format_string = (
"Some build dependencies for {requirement} "
"conflict with {conflicting_with}: {description}."
)
error_message = format_string.format(
requirement=self.req,
conflicting_with=conflicting_with,
description=", ".join(
f"{installed} is incompatible with {wanted}"
for installed, wanted in sorted(conflicting_reqs)
),
)
raise InstallationError(error_message)
def _raise_missing_reqs(self, missing: set[str]) -> None:
format_string = (
"Some build dependencies for {requirement} are missing: {missing}."
)
error_message = format_string.format(
requirement=self.req, missing=", ".join(map(repr, sorted(missing)))
)
raise InstallationError(error_message)
@@ -0,0 +1,45 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from pip._vendor.packaging.utils import canonicalize_name
from pip._internal.distributions.base import AbstractDistribution
from pip._internal.metadata import (
BaseDistribution,
FilesystemWheel,
get_wheel_distribution,
)
if TYPE_CHECKING:
from pip._internal.build_env import BuildEnvironmentInstaller, BuildIsolationMode
class WheelDistribution(AbstractDistribution):
"""Represents a wheel distribution.
This does not need any preparation as wheels can be directly unpacked.
"""
@property
def build_tracker_id(self) -> str | None:
return None
def get_metadata_distribution(self) -> BaseDistribution:
"""Loads the metadata from the wheel file into memory and returns a
Distribution that uses it, not relying on the wheel file or
requirement.
"""
assert self.req.local_file_path, "Set as part of preparation during download"
assert self.req.name, "Wheels are never unnamed"
wheel = FilesystemWheel(self.req.local_file_path)
return get_wheel_distribution(wheel, canonicalize_name(self.req.name))
def prepare_distribution_metadata(
self,
build_env_installer: BuildEnvironmentInstaller,
build_isolation: BuildIsolationMode,
check_build_deps: bool,
allow_editables: bool,
) -> None:
pass