machinaos 0.0.80 → 0.0.81
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +27 -1
- package/cli/__main__.py +93 -21
- package/cli/supervisor.py +12 -2
- package/client/package.json +1 -1
- package/package.json +1 -1
- package/scripts/install.js +19 -1
package/bin/cli.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { spawn, execSync } from 'child_process';
|
|
4
4
|
import { dirname, resolve } from 'path';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
|
-
import { readFileSync } from 'fs';
|
|
6
|
+
import { readFileSync, existsSync } from 'fs';
|
|
7
7
|
|
|
8
8
|
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
9
9
|
const PKG = JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf-8'));
|
|
@@ -126,7 +126,33 @@ function doctor() {
|
|
|
126
126
|
console.log('');
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
// Resolve <ROOT>/.cli-venv Python if the postinstall step provisioned it.
|
|
130
|
+
// Returns null on source checkouts (no venv -> fall back to ``npm run``).
|
|
131
|
+
function venvPython() {
|
|
132
|
+
const py = process.platform === 'win32'
|
|
133
|
+
? resolve(ROOT, '.cli-venv', 'Scripts', 'python.exe')
|
|
134
|
+
: resolve(ROOT, '.cli-venv', 'bin', 'python');
|
|
135
|
+
return existsSync(py) ? py : null;
|
|
136
|
+
}
|
|
137
|
+
|
|
129
138
|
function run(script, extraArgs = []) {
|
|
139
|
+
// Global-install fast path: spawn the venv's Python directly with
|
|
140
|
+
// ``-m cli <cmd>``. Skips the ``npm run`` shim that previously re-
|
|
141
|
+
// resolved the system ``python`` (which on PEP 668 systems lacks
|
|
142
|
+
// the CLI runtime deps -- typer/rich/anyio/psutil). The npm-run
|
|
143
|
+
// path stays as the source-checkout fallback (``pnpm run start``
|
|
144
|
+
// uses package.json scripts directly).
|
|
145
|
+
const venvPy = venvPython();
|
|
146
|
+
if (venvPy) {
|
|
147
|
+
const child = spawn(venvPy, ['-m', 'cli', script, ...extraArgs], {
|
|
148
|
+
cwd: ROOT,
|
|
149
|
+
stdio: 'inherit',
|
|
150
|
+
});
|
|
151
|
+
child.on('error', (e) => { console.error(`Failed: ${e.message}`); process.exit(1); });
|
|
152
|
+
child.on('close', (code) => process.exit(code || 0));
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
130
156
|
const npmArgs = ['run', script];
|
|
131
157
|
if (extraArgs.length) npmArgs.push('--', ...extraArgs);
|
|
132
158
|
const child = spawn(process.platform === 'win32' ? 'npm.cmd' : 'npm', npmArgs, {
|
package/cli/__main__.py
CHANGED
|
@@ -1,38 +1,110 @@
|
|
|
1
1
|
"""Entry point for ``python -m cli``.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
The npm package ships the ``cli/`` source directory without its
|
|
4
|
+
runtime dependencies (``typer`` / ``rich`` / ``anyio`` / ``psutil`` /
|
|
5
|
+
``platformdirs`` / ``pywin32`` on Windows). The previous bootstrap
|
|
6
|
+
``pip install``ed them against ``sys.executable``, which fails on any
|
|
7
|
+
modern PEP 668 distro (Ubuntu 24.04+, Debian 12+, Homebrew Python,
|
|
8
|
+
NixOS) with ``error: externally-managed-environment``.
|
|
9
|
+
|
|
10
|
+
Fix: provision a private venv at ``<package_root>/.cli-venv`` via
|
|
11
|
+
``uv`` (a hard install dependency verified by ``scripts/install.js``)
|
|
12
|
+
and re-exec under that venv's Python. ``uv pip install`` operates
|
|
13
|
+
inside the venv -- PEP 668 only governs the system interpreter, so
|
|
14
|
+
this is the canonical workaround documented by
|
|
15
|
+
https://peps.python.org/pep-0668/#guide-users-towards-virtual-environments.
|
|
16
|
+
|
|
17
|
+
``scripts/install.js`` provisions the same venv at postinstall time,
|
|
18
|
+
so end users never pay the first-run latency. This module is the
|
|
19
|
+
fallback for the source-checkout / broken-install path.
|
|
11
20
|
"""
|
|
12
21
|
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import os
|
|
25
|
+
import shutil
|
|
13
26
|
import subprocess
|
|
14
27
|
import sys
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
|
|
30
|
+
_ROOT = Path(__file__).resolve().parent.parent
|
|
31
|
+
_VENV_DIR = _ROOT / ".cli-venv"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _venv_python() -> Path:
|
|
35
|
+
"""Platform-specific path to the venv's Python interpreter."""
|
|
36
|
+
if sys.platform == "win32":
|
|
37
|
+
return _VENV_DIR / "Scripts" / "python.exe"
|
|
38
|
+
return _VENV_DIR / "bin" / "python"
|
|
15
39
|
|
|
16
|
-
_RUNTIME_DEPS = (
|
|
17
|
-
"typer>=0.12",
|
|
18
|
-
"rich>=13",
|
|
19
|
-
"anyio>=4",
|
|
20
|
-
"psutil>=6",
|
|
21
|
-
)
|
|
22
40
|
|
|
41
|
+
def _running_under_venv() -> bool:
|
|
42
|
+
"""True if ``sys.executable`` is already the CLI venv's Python.
|
|
23
43
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
44
|
+
Guards against infinite re-exec loops when the venv's interpreter
|
|
45
|
+
itself can't import the deps (would mean ``uv pip install`` was
|
|
46
|
+
silently no-op'd -- a real bug worth surfacing, not retrying).
|
|
47
|
+
"""
|
|
48
|
+
try:
|
|
49
|
+
return Path(sys.executable).resolve() == _venv_python().resolve()
|
|
50
|
+
except (OSError, ValueError):
|
|
51
|
+
return False
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _provision_venv() -> Path | None:
|
|
55
|
+
"""Create ``<ROOT>/.cli-venv`` and install CLI deps via ``uv``.
|
|
56
|
+
|
|
57
|
+
Returns the venv's Python path on success, ``None`` if ``uv`` is
|
|
58
|
+
missing or the install fails. Output is inherited (not captured)
|
|
59
|
+
so the user sees ``uv``'s progress + any error context.
|
|
60
|
+
"""
|
|
61
|
+
uv = shutil.which("uv")
|
|
62
|
+
if not uv:
|
|
63
|
+
return None
|
|
64
|
+
try:
|
|
65
|
+
if not _venv_python().exists():
|
|
66
|
+
print(
|
|
67
|
+
"machina: provisioning CLI runtime venv (first run)...",
|
|
68
|
+
file=sys.stderr,
|
|
69
|
+
)
|
|
70
|
+
subprocess.check_call([uv, "venv", "--quiet", str(_VENV_DIR)])
|
|
71
|
+
subprocess.check_call(
|
|
72
|
+
[
|
|
73
|
+
uv,
|
|
74
|
+
"pip",
|
|
75
|
+
"install",
|
|
76
|
+
"--quiet",
|
|
77
|
+
"--python",
|
|
78
|
+
str(_venv_python()),
|
|
79
|
+
"-e",
|
|
80
|
+
str(_ROOT),
|
|
81
|
+
]
|
|
82
|
+
)
|
|
83
|
+
except subprocess.CalledProcessError:
|
|
84
|
+
return None
|
|
85
|
+
return _venv_python() if _venv_python().exists() else None
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _reexec_in_venv() -> None:
|
|
89
|
+
"""Provision the venv if needed, then re-exec ``python -m cli`` under it."""
|
|
90
|
+
venv_py = _venv_python() if _venv_python().exists() else _provision_venv()
|
|
91
|
+
if not venv_py:
|
|
92
|
+
sys.stderr.write(
|
|
93
|
+
"machina: runtime dependencies are missing and the CLI venv\n"
|
|
94
|
+
" could not be provisioned. Install uv\n"
|
|
95
|
+
" (https://docs.astral.sh/uv/getting-started/installation/)\n"
|
|
96
|
+
" and re-run, or run `machina build` to regenerate the venv.\n"
|
|
97
|
+
)
|
|
98
|
+
sys.exit(1)
|
|
99
|
+
os.execv(str(venv_py), [str(venv_py), "-m", "cli", *sys.argv[1:]])
|
|
29
100
|
|
|
30
101
|
|
|
31
102
|
try:
|
|
32
103
|
from cli.cli import app
|
|
33
104
|
except ImportError:
|
|
34
|
-
|
|
35
|
-
|
|
105
|
+
if _running_under_venv():
|
|
106
|
+
raise
|
|
107
|
+
_reexec_in_venv()
|
|
36
108
|
|
|
37
109
|
|
|
38
110
|
if __name__ == "__main__":
|
package/cli/supervisor.py
CHANGED
|
@@ -59,9 +59,19 @@ class ServiceSpec:
|
|
|
59
59
|
|
|
60
60
|
|
|
61
61
|
def _full_env(spec_env: dict[str, str]) -> dict[str, str]:
|
|
62
|
-
"""Inherit parent env + force-color so child output stays readable.
|
|
62
|
+
"""Inherit parent env + force-color so child output stays readable.
|
|
63
|
+
|
|
64
|
+
``VIRTUAL_ENV`` is stripped: ``uv run`` resolves the project venv
|
|
65
|
+
via the workspace ``pyproject.toml`` and warns when an inherited
|
|
66
|
+
``VIRTUAL_ENV`` points elsewhere (which it does whenever the user
|
|
67
|
+
has the root ``.venv`` activated in their shell and the spec runs
|
|
68
|
+
with ``cwd=server/``). The warning has no operational effect --
|
|
69
|
+
uv ignores ``VIRTUAL_ENV`` unless ``--active`` is passed -- so we
|
|
70
|
+
drop it at the source rather than teach every reader to ignore it.
|
|
71
|
+
"""
|
|
72
|
+
inherited = {k: v for k, v in os.environ.items() if k != "VIRTUAL_ENV"}
|
|
63
73
|
return {
|
|
64
|
-
**
|
|
74
|
+
**inherited,
|
|
65
75
|
"FORCE_COLOR": "1",
|
|
66
76
|
"PYTHONUNBUFFERED": "1",
|
|
67
77
|
**spec_env,
|
package/client/package.json
CHANGED
package/package.json
CHANGED
package/scripts/install.js
CHANGED
|
@@ -148,7 +148,7 @@ try {
|
|
|
148
148
|
// Calculate total steps
|
|
149
149
|
let totalSteps = 1; // .env always
|
|
150
150
|
if (!clientDistExists) totalSteps += 2; // client deps + build
|
|
151
|
-
totalSteps +=
|
|
151
|
+
totalSteps += 3; // Python deps + bytecode compile + CLI venv
|
|
152
152
|
let step = 0;
|
|
153
153
|
|
|
154
154
|
// Create .env if needed
|
|
@@ -202,6 +202,24 @@ try {
|
|
|
202
202
|
console.log(` Warning: bytecode compilation failed (non-fatal): ${err.message}`);
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
+
// Provision a private venv for the CLI runtime deps (typer, rich,
|
|
206
|
+
// anyio, psutil, platformdirs, pywin32-on-Windows). ``python -m cli``
|
|
207
|
+
// re-execs itself under this venv (see ``cli/__main__.py``), so the
|
|
208
|
+
// system Python never needs the deps -- avoids the PEP 668
|
|
209
|
+
// ``externally-managed-environment`` failure on Ubuntu 24.04+,
|
|
210
|
+
// Debian 12+, Homebrew Python, NixOS, etc.
|
|
211
|
+
// (https://peps.python.org/pep-0668/)
|
|
212
|
+
step++;
|
|
213
|
+
console.log(`[${step}/${totalSteps}] Provisioning CLI runtime venv...`);
|
|
214
|
+
const cliVenvDir = resolve(ROOT, '.cli-venv');
|
|
215
|
+
const cliVenvPython = process.platform === 'win32'
|
|
216
|
+
? resolve(cliVenvDir, 'Scripts', 'python.exe')
|
|
217
|
+
: resolve(cliVenvDir, 'bin', 'python');
|
|
218
|
+
if (!existsSync(cliVenvPython)) {
|
|
219
|
+
run(`uv venv "${cliVenvDir}"`, ROOT);
|
|
220
|
+
}
|
|
221
|
+
run(`uv pip install --python "${cliVenvPython}" --quiet -e .`, ROOT);
|
|
222
|
+
|
|
205
223
|
// WhatsApp RPC is now an npm dependency - binary downloaded via postinstall
|
|
206
224
|
console.log('');
|
|
207
225
|
console.log('Done!');
|