machinaos 0.0.80 → 0.0.82
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 +2 -1
- package/pyproject.toml +57 -0
- package/scripts/install.js +28 -2
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "machinaos",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.82",
|
|
4
4
|
"description": "Open source workflow automation platform with AI agents, React Flow, and n8n-inspired architecture",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
"!**/node_modules/",
|
|
54
54
|
"!**/.vite/",
|
|
55
55
|
".env.template",
|
|
56
|
+
"pyproject.toml",
|
|
56
57
|
"README.md",
|
|
57
58
|
"install.sh",
|
|
58
59
|
"install.ps1"
|
package/pyproject.toml
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "machinaos-cli"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "MachinaOS project supervisor CLI (machina)."
|
|
5
|
+
requires-python = ">=3.12"
|
|
6
|
+
dependencies = [
|
|
7
|
+
"typer>=0.12",
|
|
8
|
+
"rich>=13",
|
|
9
|
+
"anyio>=4",
|
|
10
|
+
"psutil>=6",
|
|
11
|
+
# OS-native user data / cache / config / log dirs. Soft dependency
|
|
12
|
+
# in the CLI (``cli/platform_.py`` imports it inside a try/except
|
|
13
|
+
# and falls back to a stdlib implementation if the wheel isn't
|
|
14
|
+
# available) so ``machina clean`` / ``machina build`` still work
|
|
15
|
+
# immediately after a wipe, before ``pip install`` has had a
|
|
16
|
+
# chance to materialise it.
|
|
17
|
+
"platformdirs>=4.0",
|
|
18
|
+
# Required on Windows for the supervisor's Job Object (cli/tree.py).
|
|
19
|
+
# Without it, child processes survive abnormal supervisor exit
|
|
20
|
+
# (SIGKILL, BSOD, console close) and accumulate as orphans across
|
|
21
|
+
# dev restarts. Floor 308 is the first release shipping prebuilt
|
|
22
|
+
# wheels for Python 3.13.
|
|
23
|
+
"pywin32>=308; platform_system == 'Windows'",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.scripts]
|
|
27
|
+
machina = "cli.cli:app"
|
|
28
|
+
|
|
29
|
+
[build-system]
|
|
30
|
+
requires = ["hatchling"]
|
|
31
|
+
build-backend = "hatchling.build"
|
|
32
|
+
|
|
33
|
+
[tool.hatch.build.targets.wheel]
|
|
34
|
+
packages = ["cli"]
|
|
35
|
+
|
|
36
|
+
# The CLI runs under the user's global / pipx-managed Python — it is
|
|
37
|
+
# NOT a uv-managed project. ``managed = false`` tells ``uv`` to leave
|
|
38
|
+
# this project alone: ``uv sync`` at the repo root is a no-op, no
|
|
39
|
+
# ``<repo>/.venv/`` is created, ``python`` on PATH resolves to the
|
|
40
|
+
# user's global interpreter instead of being shadowed by a workspace
|
|
41
|
+
# venv. The server is the only uv-managed project (``server/.venv``).
|
|
42
|
+
[tool.uv]
|
|
43
|
+
managed = false
|
|
44
|
+
|
|
45
|
+
[dependency-groups]
|
|
46
|
+
dev = []
|
|
47
|
+
|
|
48
|
+
# ``machinaos-cli`` is a standalone Python CLI package -- installable
|
|
49
|
+
# via ``pipx install .`` / ``pip install .`` against any Python >=3.12
|
|
50
|
+
# without involving uv. It depends on nothing from ``server/``. The
|
|
51
|
+
# server has its own uv-managed venv at ``server/.venv``; the CLI
|
|
52
|
+
# invokes server-side commands by shelling out via ``cli.run.uv_run``
|
|
53
|
+
# (which runs ``uv run --no-sync ...`` with ``cwd=server/`` so uv
|
|
54
|
+
# discovers ``server/pyproject.toml`` and activates ``server/.venv``).
|
|
55
|
+
# Keeping the CLI independent of the uv workspace means contributors
|
|
56
|
+
# can install / run / debug ``machina`` on whatever interpreter is on
|
|
57
|
+
# PATH -- no per-checkout venv activation required.
|
package/scripts/install.js
CHANGED
|
@@ -23,12 +23,20 @@ const ROOT = resolve(__dirname, '..');
|
|
|
23
23
|
process.env.PYTHONUTF8 = '1';
|
|
24
24
|
|
|
25
25
|
function run(cmd, cwd = ROOT, timeoutMs = 300000) {
|
|
26
|
+
// Strip VIRTUAL_ENV from the spawned env. When the user runs
|
|
27
|
+
// ``npm install -g machinaos`` from a shell that has activated a
|
|
28
|
+
// venv (very common during dev), uv emits a noisy ``VIRTUAL_ENV
|
|
29
|
+
// ... does not match the project environment path`` warning per
|
|
30
|
+
// invocation. uv only honours VIRTUAL_ENV with ``--active``, which
|
|
31
|
+
// we never pass, so dropping it at the source is the documented
|
|
32
|
+
// workaround. Same fix applied to cli/supervisor.py's _full_env.
|
|
33
|
+
const { VIRTUAL_ENV, ...cleanEnv } = process.env;
|
|
26
34
|
execSync(cmd, {
|
|
27
35
|
cwd,
|
|
28
36
|
stdio: 'inherit',
|
|
29
37
|
shell: true,
|
|
30
38
|
timeout: timeoutMs,
|
|
31
|
-
env: { ...
|
|
39
|
+
env: { ...cleanEnv, MACHINAOS_INSTALLING: 'true' }
|
|
32
40
|
});
|
|
33
41
|
}
|
|
34
42
|
|
|
@@ -148,7 +156,7 @@ try {
|
|
|
148
156
|
// Calculate total steps
|
|
149
157
|
let totalSteps = 1; // .env always
|
|
150
158
|
if (!clientDistExists) totalSteps += 2; // client deps + build
|
|
151
|
-
totalSteps +=
|
|
159
|
+
totalSteps += 3; // Python deps + bytecode compile + CLI venv
|
|
152
160
|
let step = 0;
|
|
153
161
|
|
|
154
162
|
// Create .env if needed
|
|
@@ -202,6 +210,24 @@ try {
|
|
|
202
210
|
console.log(` Warning: bytecode compilation failed (non-fatal): ${err.message}`);
|
|
203
211
|
}
|
|
204
212
|
|
|
213
|
+
// Provision a private venv for the CLI runtime deps (typer, rich,
|
|
214
|
+
// anyio, psutil, platformdirs, pywin32-on-Windows). ``python -m cli``
|
|
215
|
+
// re-execs itself under this venv (see ``cli/__main__.py``), so the
|
|
216
|
+
// system Python never needs the deps -- avoids the PEP 668
|
|
217
|
+
// ``externally-managed-environment`` failure on Ubuntu 24.04+,
|
|
218
|
+
// Debian 12+, Homebrew Python, NixOS, etc.
|
|
219
|
+
// (https://peps.python.org/pep-0668/)
|
|
220
|
+
step++;
|
|
221
|
+
console.log(`[${step}/${totalSteps}] Provisioning CLI runtime venv...`);
|
|
222
|
+
const cliVenvDir = resolve(ROOT, '.cli-venv');
|
|
223
|
+
const cliVenvPython = process.platform === 'win32'
|
|
224
|
+
? resolve(cliVenvDir, 'Scripts', 'python.exe')
|
|
225
|
+
: resolve(cliVenvDir, 'bin', 'python');
|
|
226
|
+
if (!existsSync(cliVenvPython)) {
|
|
227
|
+
run(`uv venv "${cliVenvDir}"`, ROOT);
|
|
228
|
+
}
|
|
229
|
+
run(`uv pip install --python "${cliVenvPython}" --quiet -e .`, ROOT);
|
|
230
|
+
|
|
205
231
|
// WhatsApp RPC is now an npm dependency - binary downloaded via postinstall
|
|
206
232
|
console.log('');
|
|
207
233
|
console.log('Done!');
|