@the-sanjeevkumar/nova-agent 1.0.0 → 1.0.1
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/npm/nova.js +34 -12
- package/package.json +1 -1
- package/pm/environments.py +18 -6
- package/pyproject.toml +17 -2
- package/uv.lock +4 -4
package/npm/nova.js
CHANGED
|
@@ -19,6 +19,14 @@ const APP_ROOT = resolve(__dirname, '..');
|
|
|
19
19
|
const NOVA_HOME = process.env.NOVA_HOME || join(homedir(), '.nova');
|
|
20
20
|
const VENV_DIR = join(NOVA_HOME, 'venv');
|
|
21
21
|
const IS_WIN = platform() === 'win32';
|
|
22
|
+
// Termux reports process.platform === 'android'; $PREFIX distinguishes it from
|
|
23
|
+
// other Android Pythons. The Python floor below must match pyproject.toml:
|
|
24
|
+
// every runtime dependency is pinned with `python_version >= '3.14'`, so a
|
|
25
|
+
// venv built on older Python installs the package with no dependencies and
|
|
26
|
+
// `nova` dies on the first import (seen on Termux, which ships 3.12/3.13).
|
|
27
|
+
const IS_TERMUX = platform() === 'android' || (process.env.PREFIX || '').includes('com.termux');
|
|
28
|
+
const MIN_PY_MAJOR = 3;
|
|
29
|
+
const MIN_PY_MINOR = 14;
|
|
22
30
|
|
|
23
31
|
const VERSION = readVersion();
|
|
24
32
|
|
|
@@ -30,6 +38,16 @@ function readVersion() {
|
|
|
30
38
|
}
|
|
31
39
|
}
|
|
32
40
|
|
|
41
|
+
function pythonTooOldError() {
|
|
42
|
+
console.error(`Nova Agent requires Python ${MIN_PY_MAJOR}.${MIN_PY_MINOR} or newer.`);
|
|
43
|
+
if (IS_TERMUX) {
|
|
44
|
+
console.error('Termux: run `pkg update && pkg install python`, then re-run `nova`.');
|
|
45
|
+
} else {
|
|
46
|
+
console.error('Install Python from https://www.python.org/downloads/ and re-run `nova`.');
|
|
47
|
+
}
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
|
|
33
51
|
function findPython() {
|
|
34
52
|
const candidates = IS_WIN ? ['python', 'python3', 'py'] : ['python3', 'python'];
|
|
35
53
|
for (const c of candidates) {
|
|
@@ -39,11 +57,11 @@ function findPython() {
|
|
|
39
57
|
const m = (r.stdout + r.stderr).match(/Python (\d+)\.(\d+)\.(\d+)/);
|
|
40
58
|
if (!m) continue;
|
|
41
59
|
const [major, minor] = [parseInt(m[1]), parseInt(m[2])];
|
|
42
|
-
if (major >
|
|
60
|
+
if (major > MIN_PY_MAJOR || (major === MIN_PY_MAJOR && minor >= MIN_PY_MINOR)) {
|
|
43
61
|
return c === 'py' ? { cmd: 'py', prefix: ['-3'] } : { cmd: c, prefix: [] };
|
|
44
62
|
}
|
|
45
63
|
}
|
|
46
|
-
|
|
64
|
+
pythonTooOldError();
|
|
47
65
|
}
|
|
48
66
|
|
|
49
67
|
function venvPython() {
|
|
@@ -67,11 +85,17 @@ function markerPath() {
|
|
|
67
85
|
return join(VENV_DIR, '.nova-version');
|
|
68
86
|
}
|
|
69
87
|
|
|
70
|
-
function
|
|
88
|
+
function provisionMarker(py) {
|
|
89
|
+
// Two lines: launcher VERSION, then the venv's Python major.minor. A marker
|
|
90
|
+
// without the Python line (pre-1.0.2 installs) forces re-provision, so venvs
|
|
91
|
+
// built under older Python are not silently reused.
|
|
92
|
+
return `${VERSION}\n${py ? `${py.major}.${py.minor}` : 'unknown'}\n`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function needsProvision(py) {
|
|
71
96
|
if (!existsSync(venvPython()) || !existsSync(venvNova())) return true;
|
|
72
97
|
try {
|
|
73
|
-
|
|
74
|
-
return installed !== VERSION;
|
|
98
|
+
return readFileSync(markerPath(), 'utf8') !== provisionMarker(py);
|
|
75
99
|
} catch {
|
|
76
100
|
return true;
|
|
77
101
|
}
|
|
@@ -79,11 +103,6 @@ function needsProvision() {
|
|
|
79
103
|
|
|
80
104
|
function provision() {
|
|
81
105
|
const py = findPython();
|
|
82
|
-
if (!py) {
|
|
83
|
-
console.error('Nova Agent requires Python 3.11 or newer.');
|
|
84
|
-
console.error('Install Python from https://www.python.org/downloads/ and re-run `nova`.');
|
|
85
|
-
process.exit(1);
|
|
86
|
-
}
|
|
87
106
|
|
|
88
107
|
mkdirSync(NOVA_HOME, { recursive: true });
|
|
89
108
|
|
|
@@ -105,15 +124,18 @@ function provision() {
|
|
|
105
124
|
const install = runSync(venvPython(), ['-m', 'pip', 'install', '--quiet', APP_ROOT]);
|
|
106
125
|
if (install !== 0) {
|
|
107
126
|
console.error('Nova Agent installation failed.');
|
|
127
|
+
console.error('pip printed the failure above. If you are on Termux, PyPI wheels for');
|
|
128
|
+
console.error('pydantic-core/psutil do not target Android/Bionic libc; use the Nova');
|
|
129
|
+
console.error('Termux deb package (or a PM wheelhouse) instead of the npm launcher.');
|
|
108
130
|
process.exit(install);
|
|
109
131
|
}
|
|
110
132
|
|
|
111
|
-
writeFileSync(markerPath(),
|
|
133
|
+
writeFileSync(markerPath(), provisionMarker(py));
|
|
112
134
|
console.log('✅ Nova Agent ready.\n');
|
|
113
135
|
}
|
|
114
136
|
|
|
115
137
|
function main() {
|
|
116
|
-
if (needsProvision()) {
|
|
138
|
+
if (needsProvision(findPython())) {
|
|
117
139
|
provision();
|
|
118
140
|
}
|
|
119
141
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@the-sanjeevkumar/nova-agent",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Nova Agent - a universal AI agent you can run from any terminal. Installs a self-contained Python environment on first run.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Sanjeev Kumar",
|
package/pm/environments.py
CHANGED
|
@@ -139,12 +139,24 @@ def record_activation_inputs(stamps: Path, mtimes: dict[str, int], project_root:
|
|
|
139
139
|
stamps.mkdir(parents=True, exist_ok=True)
|
|
140
140
|
(stamps / ".project-root").write_text(str(Path(project_root).resolve()), encoding="utf-8")
|
|
141
141
|
if test_environment:
|
|
142
|
-
(
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
142
|
+
# The reader (scripts/_activation.sh::nova_activation_current) compares the
|
|
143
|
+
# file's CONTENT against the flag it composes (default "--test-environment").
|
|
144
|
+
# An empty touch() read as "" and falsified every post-sync activation check,
|
|
145
|
+
# so run-in-nova-env always reported "did not produce a current environment".
|
|
146
|
+
(stamps / ".test-environment").write_text("--test-environment\n", encoding="utf-8")
|
|
147
|
+
manifest = stamps / "inputs"
|
|
148
|
+
with open(manifest, "wb") as f:
|
|
149
|
+
# The same reader's mtime loop ports "<input=stamp>\0" pairs from this
|
|
150
|
+
# manifest and judges staleness pairwise (`-nt`/`-ot`); the per-input
|
|
151
|
+
# stamp files alone give it nothing to read, and the check then fails
|
|
152
|
+
# on the missing file (bash reports line 88: inputs: No such file).
|
|
153
|
+
for name, mtime in mtimes.items():
|
|
154
|
+
stamp = stamps / name
|
|
155
|
+
stamp.parent.mkdir(parents=True, exist_ok=True)
|
|
156
|
+
stamp.touch()
|
|
157
|
+
os.utime(stamp, ns=(mtime, mtime))
|
|
158
|
+
input_path = Path(project_root) / name
|
|
159
|
+
f.write(f"{input_path}={stamp}\0".encode())
|
|
148
160
|
|
|
149
161
|
|
|
150
162
|
def _payload_manifest(root: Path) -> tuple[dict, Path] | None:
|
package/pyproject.toml
CHANGED
|
@@ -67,7 +67,15 @@ dependencies = [
|
|
|
67
67
|
# arrangement dated to the package's uv exclude-newer quarantine, which
|
|
68
68
|
# has long expired). PM's doc-extract extra repairs lean/broken installs
|
|
69
69
|
# from this same dependency declaration.
|
|
70
|
-
|
|
70
|
+
# The `sys_platform != 'android'` clause (same shape as psutil's split
|
|
71
|
+
# and nemo-relay's wheel-target guard) keeps the Android/Bionic python
|
|
72
|
+
# out: PyPI ships no bionic wheels for anydoc, so a faithful marker-less
|
|
73
|
+
# pin sends the phone's pip into an sdist build that bricks the whole
|
|
74
|
+
# npm/curl setup run (#Termux: "failed to build firecrawl-anydoc").
|
|
75
|
+
# `tools/read_extract.py` degrades to the typed NeedsOcrError teaching
|
|
76
|
+
# error without it; the Termux deb wheelhouse builds natives itself and
|
|
77
|
+
# is unaffected by marker evaluation on the build container.
|
|
78
|
+
"firecrawl-anydoc==0.2.4; python_version >= '3.14' and sys_platform != 'android'",
|
|
71
79
|
# Bumped from 2.12.5 to 2.13.4 to pull in pydantic-core 2.46.4.
|
|
72
80
|
# pydantic-core 2.41.5 (pulled by 2.12.5) segfaults when the OpenAI SDK's
|
|
73
81
|
# Responses API resource is exercised from a non-main thread, which is the
|
|
@@ -373,7 +381,7 @@ google-chat = [
|
|
|
373
381
|
# Document extraction (read_file anydoc converter self-heal) — core already
|
|
374
382
|
# bundles firecrawl-anydoc==0.2.4; the extra exists so a lean/broken install
|
|
375
383
|
# can re-sync exactly the pin from core.
|
|
376
|
-
doc-extract = ["firecrawl-anydoc==0.2.4"]
|
|
384
|
+
doc-extract = ["firecrawl-anydoc==0.2.4; python_version >= '3.14' and sys_platform != 'android'"]
|
|
377
385
|
# Office/PDF authoring libraries for skills/productivity/{docx,xlsx,powerpoint,pdf}.
|
|
378
386
|
# Baked into the Docker image (Dockerfile `--extra documents`), whose sealed
|
|
379
387
|
# venv has no pip and whose root fs resets on restart; ~25 MB there. Not in
|
|
@@ -611,6 +619,13 @@ matrix = "sys_platform == 'linux'"
|
|
|
611
619
|
google-chat = "platform_machine != 'ARM64' or sys_platform != 'win32'"
|
|
612
620
|
# Match the engine dependency markers so PM refuses an unsupported install.
|
|
613
621
|
wake-openwakeword = "(platform_machine != 'ARM64' or sys_platform != 'win32') and (platform_machine != 'x86_64' or sys_platform != 'darwin')"
|
|
622
|
+
# wake-openwakeword = "(platform_machine != 'ARM64' or sys_platform != 'win32') and (platform_machine != 'x86_64' or sys_platform != 'darwin')"
|
|
623
|
+
# doc-extract: marker must equal its dependency marker — the gates contract
|
|
624
|
+
# (tests/pm/test_extras.py::test_declared_extra_gates_match_dependency_selection)
|
|
625
|
+
# compares gate vs dep-marker admission per platform, and ensure_import must
|
|
626
|
+
# refuse cleanly on the bionic python instead of resolving the extra to an
|
|
627
|
+
# empty set in a loop.
|
|
628
|
+
doc-extract = "python_version >= '3.14' and sys_platform != 'android'"
|
|
614
629
|
# faster-whisper's closure is prebuilt-only: ctranslate2 has no win_arm64 wheel and onnxruntime
|
|
615
630
|
# none for darwin-x64. Its ANCHOR is faster_whisper, so an unsupported machine could install the
|
|
616
631
|
# extra, rebuild the whole dependency environment and still fail the anchor on every retry.
|
package/uv.lock
CHANGED
|
@@ -3686,7 +3686,7 @@ dependencies = [
|
|
|
3686
3686
|
{ name = "cryptography" },
|
|
3687
3687
|
{ name = "fastapi" },
|
|
3688
3688
|
{ name = "fire" },
|
|
3689
|
-
{ name = "firecrawl-anydoc" },
|
|
3689
|
+
{ name = "firecrawl-anydoc", marker = "sys_platform != 'android'" },
|
|
3690
3690
|
{ name = "httptools" },
|
|
3691
3691
|
{ name = "httpx", extra = ["socks"] },
|
|
3692
3692
|
{ name = "jinja2" },
|
|
@@ -3782,7 +3782,7 @@ discord = [
|
|
|
3782
3782
|
{ name = "discord-py", extra = ["voice"] },
|
|
3783
3783
|
]
|
|
3784
3784
|
doc-extract = [
|
|
3785
|
-
{ name = "firecrawl-anydoc" },
|
|
3785
|
+
{ name = "firecrawl-anydoc", marker = "sys_platform != 'android'" },
|
|
3786
3786
|
]
|
|
3787
3787
|
documents = [
|
|
3788
3788
|
{ name = "openpyxl" },
|
|
@@ -4027,8 +4027,8 @@ requires-dist = [
|
|
|
4027
4027
|
{ name = "faster-whisper", marker = "(platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'stt-whisper') or (platform_machine != 'ARM64' and sys_platform == 'win32' and extra == 'stt-whisper') or (sys_platform != 'android' and sys_platform != 'darwin' and sys_platform != 'win32' and extra == 'stt-whisper')", specifier = "==1.2.1" },
|
|
4028
4028
|
{ name = "faster-whisper", marker = "(platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'voice') or (platform_machine != 'ARM64' and sys_platform == 'win32' and extra == 'voice') or (sys_platform != 'android' and sys_platform != 'darwin' and sys_platform != 'win32' and extra == 'voice')", specifier = "==1.2.1" },
|
|
4029
4029
|
{ name = "fire", marker = "python_full_version >= '3.14'", specifier = "==0.7.1" },
|
|
4030
|
-
{ name = "firecrawl-anydoc", marker = "python_full_version >= '3.14'", specifier = "==0.2.4" },
|
|
4031
|
-
{ name = "firecrawl-anydoc", marker = "extra == 'doc-extract'", specifier = "==0.2.4" },
|
|
4030
|
+
{ name = "firecrawl-anydoc", marker = "python_full_version >= '3.14' and sys_platform != 'android'", specifier = "==0.2.4" },
|
|
4031
|
+
{ name = "firecrawl-anydoc", marker = "python_full_version >= '3.14' and sys_platform != 'android' and extra == 'doc-extract'", specifier = "==0.2.4" },
|
|
4032
4032
|
{ name = "firecrawl-py", marker = "extra == 'firecrawl'", specifier = "==4.53.2" },
|
|
4033
4033
|
{ name = "google-api-python-client", marker = "extra == 'google'", specifier = "==2.201.0" },
|
|
4034
4034
|
{ name = "google-auth", marker = "extra == 'google'", specifier = "==2.62.0" },
|