Skip to content

Wrappers

dotagents._wrappers

Write dotagents / dotagents.cmd wrappers: at a built pyz, or at python -m dotagents for a plain install.

POSIX_TEMPLATE = '#!/bin/sh\nexec "{python}" "{pyz}" "$@"\n' module-attribute

POSIX_TEMPLATE_MODULE = '#!/bin/sh\nexec "{python}" -m dotagents "$@"\n' module-attribute

POSIX_TEMPLATE_REL = '#!/bin/sh\nd=$(dirname "$(readlink -f "$0" 2>/dev/null || echo "$0")")\nexec "{python}" "$d/{pyz}" "$@"\n' module-attribute

check_path_warning(bin_dir)

Return a warning string (with the literal export hint) if bin_dir is not on PATH, else None.

Source code in src/dotagents/_wrappers.py
179
180
181
182
183
184
185
186
187
188
189
190
def check_path_warning(bin_dir: Path) -> "str | None":
    """Return a warning string (with the literal export hint) if `bin_dir` is
    not on PATH, else None."""
    bin_dir = str(Path(bin_dir).resolve())
    path_entries = [str(Path(p).resolve()) for p in os.environ.get("PATH", "").split(os.pathsep) if p]
    if bin_dir in path_entries:
        return None
    if os.name == "nt":
        hint = '$env:PATH += ";%s"' % bin_dir
    else:
        hint = 'export PATH="%s:$PATH"' % bin_dir
    return "warning: %s is not on PATH. Add it with:\n  %s" % (bin_dir, hint)

wrapper_points_at_pyz(bin_dir)

True if bin_dir holds a dotagents wrapper that runs a .pyz.

Source code in src/dotagents/_wrappers.py
70
71
72
73
74
75
76
77
78
def wrapper_points_at_pyz(bin_dir: Path) -> bool:
    """True if `bin_dir` holds a `dotagents` wrapper that runs a `.pyz`."""
    for name in ("dotagents", "dotagents.cmd"):
        try:
            if ".pyz" in (Path(bin_dir) / name).read_text(encoding="utf-8"):
                return True
        except OSError:
            pass
    return False

write_module_wrappers(bin_dir, python=None)

Write both wrapper forms into bin_dir running "<python>" -m dotagents (python defaults to sys.executable, absolute -- see :func:write_wrappers). What a plain install gets, so the hooks that call <store>/bin/dotagents[.cmd] find a command there too.

Source code in src/dotagents/_wrappers.py
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
def write_module_wrappers(bin_dir: Path, python: "str | None" = None) -> "list[Path]":
    """Write both wrapper forms into `bin_dir` running `"<python>" -m dotagents`
    (``python`` defaults to ``sys.executable``, absolute -- see
    :func:`write_wrappers`). What a plain install gets, so the hooks that call
    `<store>/bin/dotagents[.cmd]` find a command there too."""
    bin_dir = Path(bin_dir)
    bin_dir.mkdir(parents=True, exist_ok=True)
    python = python or sys.executable
    sh_path, cmd_path = bin_dir / "dotagents", bin_dir / "dotagents.cmd"
    with open(sh_path, "w", encoding="utf-8", newline="") as f:
        f.write(POSIX_TEMPLATE_MODULE.format(python=Path(python).as_posix()))
    if os.name != "nt":
        sh_path.chmod(sh_path.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
    _write_cmd(cmd_path, '"%s" -m dotagents %%*' % _cmd_safe(str(python)))
    return [sh_path, cmd_path]

write_wrappers(bin_dir, pyz_path, python=None, *, relative=False)

Write both wrapper scripts into bin_dir, returning the paths written.

BOTH files are always written, on every platform: dotagents (sh) and dotagents.cmd. A Windows box with Git Bash / WSL runs the sh one and cmd runs the .cmd, and the two never collide -- cmd.exe resolves .cmd via PATHEXT while sh picks the extensionless file. Writing only the platform's "native" form would break the other shell on the same box.

python defaults to the interpreter running this code (sys.executable), embedded as an absolute path. A bare python/python3 is NOT usable: on Windows it resolves to the Microsoft Store alias stub ("Python was not found...") for anyone who has not installed the Store package, so a wrapper calling it exits 0 having done nothing -- which then silently breaks any hook that shells out to dotagents.

relative=True points the wrapper at pyz_path relative to bin_dir (the real file's directory / %~dp0), so a scope directory stays relocatable. Falls back to an absolute path when the two live on different drives.

Source code in src/dotagents/_wrappers.py
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
def write_wrappers(
    bin_dir: Path,
    pyz_path: Path,
    python: "str | None" = None,
    *,
    relative: bool = False,
) -> "list[Path]":
    """Write both wrapper scripts into `bin_dir`, returning the paths written.

    BOTH files are always written, on every platform: `dotagents` (sh) and
    `dotagents.cmd`. A Windows box with Git Bash / WSL runs the sh one and cmd
    runs the `.cmd`, and the two never collide -- cmd.exe resolves `.cmd` via
    PATHEXT while sh picks the extensionless file. Writing only the platform's
    "native" form would break the other shell on the same box.

    `python` defaults to the interpreter running this code (`sys.executable`),
    embedded as an absolute path. A bare `python`/`python3` is NOT usable: on
    Windows it resolves to the Microsoft Store alias stub ("Python was not
    found...") for anyone who has not installed the Store package, so a wrapper
    calling it exits 0 having done nothing -- which then silently breaks any hook
    that shells out to `dotagents`.

    `relative=True` points the wrapper at `pyz_path` relative to `bin_dir`
    (the real file's directory / `%~dp0`), so a scope directory stays relocatable. Falls
    back to an absolute path when the two live on different drives.
    """
    bin_dir = Path(bin_dir)
    bin_dir.mkdir(parents=True, exist_ok=True)
    pyz_path = Path(pyz_path).resolve()
    python = python or sys.executable

    rel_pyz = None
    if relative:
        try:
            candidate = Path(os.path.relpath(pyz_path, bin_dir))
        except ValueError:
            # Different drives on Windows -- no relative path exists.
            candidate = None
        # Relative is for a pyz living in or beside the scope (keeps the store
        # relocatable): `dotagents.pyz` or `../dotagents.pyz`. A pyz elsewhere on
        # disk yields a long `../../../..` chain that is unreadable and MORE
        # fragile than absolute -- it breaks when either side moves, not just the
        # scope. One `..` is the limit.
        if candidate is not None and candidate.parts.count("..") <= 1:
            rel_pyz = candidate

    written = []

    # `Path.write_text(..., newline=...)` needs Python 3.10+; open() directly
    # (with newline="") so the exact \n / \r\n bytes above are preserved
    # unchanged on every Python 3.9+ platform.
    sh_path = bin_dir / "dotagents"
    with open(sh_path, "w", encoding="utf-8", newline="") as f:
        if rel_pyz is not None:
            f.write(
                POSIX_TEMPLATE_REL.format(
                    python=Path(python).as_posix(), pyz=rel_pyz.as_posix()
                )
            )
        else:
            f.write(
                POSIX_TEMPLATE.format(
                    python=Path(python).as_posix(), pyz=pyz_path.as_posix()
                )
            )
    if os.name != "nt":
        mode = sh_path.stat().st_mode
        sh_path.chmod(mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
    written.append(sh_path)

    cmd_path = bin_dir / "dotagents.cmd"
    exe = _cmd_safe(str(python))
    if rel_pyz is not None:
        _write_cmd(cmd_path, '"%s" "%%~dp0%s" %%*' % (exe, rel_pyz))
    else:
        _write_cmd(cmd_path, '"%s" "%s" %%*' % (exe, _cmd_safe(str(pyz_path))))
    written.append(cmd_path)

    return written