infinicode 2.8.10 → 2.8.12
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/dist/robopark/enroll.js +3 -36
- package/dist/robopark/preview-agent-launcher.js +3 -38
- package/dist/robopark/python-env.d.ts +9 -0
- package/dist/robopark/python-env.js +79 -0
- package/package.json +1 -1
- package/packages/robopark/scheduler/preview_agent.py +3 -2
- package/packages/robopark/scheduler/requirements-robot.txt +9 -0
package/dist/robopark/enroll.js
CHANGED
|
@@ -9,6 +9,7 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
|
9
9
|
import { homedir, hostname } from 'node:os';
|
|
10
10
|
import { join } from 'node:path';
|
|
11
11
|
import chalk from 'chalk';
|
|
12
|
+
import { findPython, findSchedulerPath, ensurePythonDeps } from './python-env.js';
|
|
12
13
|
const ROBOPARK_DIR = join(homedir(), '.robopark');
|
|
13
14
|
const ENROLLMENT_FILE = join(ROBOPARK_DIR, 'enrollment_token');
|
|
14
15
|
function ensureDir() {
|
|
@@ -24,42 +25,6 @@ function saveEnrollmentToken(token) {
|
|
|
24
25
|
ensureDir();
|
|
25
26
|
writeFileSync(ENROLLMENT_FILE, token, { mode: 0o600 });
|
|
26
27
|
}
|
|
27
|
-
async function findSchedulerPath() {
|
|
28
|
-
const candidates = [
|
|
29
|
-
join(process.cwd(), 'packages', 'robopark', 'scheduler', 'preview_agent.py'),
|
|
30
|
-
join(process.cwd(), 'scheduler', 'preview_agent.py'),
|
|
31
|
-
];
|
|
32
|
-
for (const p of candidates) {
|
|
33
|
-
if (existsSync(p))
|
|
34
|
-
return p;
|
|
35
|
-
}
|
|
36
|
-
try {
|
|
37
|
-
const { createRequire } = await import('node:module');
|
|
38
|
-
const require = createRequire(import.meta.url);
|
|
39
|
-
const pkgMain = require.resolve('infinicode/package.json');
|
|
40
|
-
const p = join(pkgMain, '..', 'packages', 'robopark', 'scheduler', 'preview_agent.py');
|
|
41
|
-
if (existsSync(p))
|
|
42
|
-
return p;
|
|
43
|
-
}
|
|
44
|
-
catch { /* ignore */ }
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
function findPython() {
|
|
48
|
-
const names = process.platform === 'win32'
|
|
49
|
-
? ['python', 'python3', 'py']
|
|
50
|
-
: ['python3', 'python'];
|
|
51
|
-
for (const name of names) {
|
|
52
|
-
try {
|
|
53
|
-
const { execSync } = require('node:child_process');
|
|
54
|
-
execSync(`${name} --version`, { stdio: 'ignore' });
|
|
55
|
-
return name;
|
|
56
|
-
}
|
|
57
|
-
catch {
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
return process.platform === 'win32' ? 'python' : 'python3';
|
|
62
|
-
}
|
|
63
28
|
export async function roboparkEnroll(opts) {
|
|
64
29
|
const token = opts.enrollmentToken ?? readEnrollmentToken();
|
|
65
30
|
if (!token) {
|
|
@@ -75,6 +40,8 @@ export async function roboparkEnroll(opts) {
|
|
|
75
40
|
process.exit(1);
|
|
76
41
|
}
|
|
77
42
|
const python = findPython();
|
|
43
|
+
if (!ensurePythonDeps(python, script))
|
|
44
|
+
process.exit(1);
|
|
78
45
|
const args = [
|
|
79
46
|
script,
|
|
80
47
|
'--scheduler-url', schedulerUrl,
|
|
@@ -5,48 +5,11 @@
|
|
|
5
5
|
* the scheduler URL from mesh/hub, and detaches unless --foreground.
|
|
6
6
|
*/
|
|
7
7
|
import { spawn } from 'node:child_process';
|
|
8
|
-
import { existsSync } from 'node:fs';
|
|
9
8
|
import { hostname } from 'node:os';
|
|
10
|
-
import { join } from 'node:path';
|
|
11
9
|
import chalk from 'chalk';
|
|
12
10
|
import { discoverContext } from './discovery.js';
|
|
11
|
+
import { findPython, findSchedulerPath, ensurePythonDeps } from './python-env.js';
|
|
13
12
|
const DEFAULT_SCHEDULER_PORT = 8080;
|
|
14
|
-
async function findSchedulerPath() {
|
|
15
|
-
const candidates = [
|
|
16
|
-
join(process.cwd(), 'packages', 'robopark', 'scheduler', 'preview_agent.py'),
|
|
17
|
-
join(process.cwd(), 'scheduler', 'preview_agent.py'),
|
|
18
|
-
];
|
|
19
|
-
for (const p of candidates) {
|
|
20
|
-
if (existsSync(p))
|
|
21
|
-
return p;
|
|
22
|
-
}
|
|
23
|
-
try {
|
|
24
|
-
const { createRequire } = await import('node:module');
|
|
25
|
-
const require = createRequire(import.meta.url);
|
|
26
|
-
const pkgMain = require.resolve('infinicode/package.json');
|
|
27
|
-
const p = join(pkgMain, '..', 'packages', 'robopark', 'scheduler', 'preview_agent.py');
|
|
28
|
-
if (existsSync(p))
|
|
29
|
-
return p;
|
|
30
|
-
}
|
|
31
|
-
catch { /* ignore */ }
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
function findPython() {
|
|
35
|
-
const names = process.platform === 'win32'
|
|
36
|
-
? ['python', 'python3', 'py']
|
|
37
|
-
: ['python3', 'python'];
|
|
38
|
-
for (const name of names) {
|
|
39
|
-
try {
|
|
40
|
-
const { execSync } = require('node:child_process');
|
|
41
|
-
execSync(`${name} --version`, { stdio: 'ignore' });
|
|
42
|
-
return name;
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
return process.platform === 'win32' ? 'python' : 'python3';
|
|
49
|
-
}
|
|
50
13
|
export async function roboparkPreviewAgent(opts) {
|
|
51
14
|
const ctx = await discoverContext();
|
|
52
15
|
const port = DEFAULT_SCHEDULER_PORT;
|
|
@@ -62,6 +25,8 @@ export async function roboparkPreviewAgent(opts) {
|
|
|
62
25
|
process.exit(1);
|
|
63
26
|
}
|
|
64
27
|
const python = findPython();
|
|
28
|
+
if (!ensurePythonDeps(python, script))
|
|
29
|
+
process.exit(1);
|
|
65
30
|
const args = [
|
|
66
31
|
script,
|
|
67
32
|
'--scheduler-url', schedulerUrl,
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function findSchedulerPath(): Promise<string | null>;
|
|
2
|
+
export declare function findPython(): string;
|
|
3
|
+
/**
|
|
4
|
+
* Ensure preview_agent.py's Python deps are importable, installing them from
|
|
5
|
+
* requirements-robot.txt (next to the script) if not. Returns false — with a
|
|
6
|
+
* message already printed — if deps are still missing after the install
|
|
7
|
+
* attempt, so callers can bail out before hitting a raw traceback.
|
|
8
|
+
*/
|
|
9
|
+
export declare function ensurePythonDeps(python: string, scriptPath: string): boolean;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RoboPark — shared Python environment helpers for `enroll` and
|
|
3
|
+
* `preview-agent`, which both spawn `preview_agent.py`.
|
|
4
|
+
*/
|
|
5
|
+
import { execSync, spawnSync } from 'node:child_process';
|
|
6
|
+
import { existsSync } from 'node:fs';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
/** Modules preview_agent.py imports — kept in sync with requirements-robot.txt. */
|
|
10
|
+
const REQUIRED_MODULES = ['httpx', 'cv2', 'pyaudio', 'livekit'];
|
|
11
|
+
export async function findSchedulerPath() {
|
|
12
|
+
const candidates = [
|
|
13
|
+
join(process.cwd(), 'packages', 'robopark', 'scheduler', 'preview_agent.py'),
|
|
14
|
+
join(process.cwd(), 'scheduler', 'preview_agent.py'),
|
|
15
|
+
];
|
|
16
|
+
for (const p of candidates) {
|
|
17
|
+
if (existsSync(p))
|
|
18
|
+
return p;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const { createRequire } = await import('node:module');
|
|
22
|
+
const require = createRequire(import.meta.url);
|
|
23
|
+
const pkgMain = require.resolve('infinicode/package.json');
|
|
24
|
+
const p = join(pkgMain, '..', 'packages', 'robopark', 'scheduler', 'preview_agent.py');
|
|
25
|
+
if (existsSync(p))
|
|
26
|
+
return p;
|
|
27
|
+
}
|
|
28
|
+
catch { /* ignore */ }
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
export function findPython() {
|
|
32
|
+
const names = process.platform === 'win32'
|
|
33
|
+
? ['python', 'python3', 'py']
|
|
34
|
+
: ['python3', 'python'];
|
|
35
|
+
for (const name of names) {
|
|
36
|
+
try {
|
|
37
|
+
execSync(`${name} --version`, { stdio: 'ignore' });
|
|
38
|
+
return name;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return process.platform === 'win32' ? 'python' : 'python3';
|
|
45
|
+
}
|
|
46
|
+
function missingModules(python) {
|
|
47
|
+
return REQUIRED_MODULES.filter(mod => {
|
|
48
|
+
const res = spawnSync(python, ['-c', `import ${mod}`], { stdio: 'ignore' });
|
|
49
|
+
return res.status !== 0;
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Ensure preview_agent.py's Python deps are importable, installing them from
|
|
54
|
+
* requirements-robot.txt (next to the script) if not. Returns false — with a
|
|
55
|
+
* message already printed — if deps are still missing after the install
|
|
56
|
+
* attempt, so callers can bail out before hitting a raw traceback.
|
|
57
|
+
*/
|
|
58
|
+
export function ensurePythonDeps(python, scriptPath) {
|
|
59
|
+
const missing = missingModules(python);
|
|
60
|
+
if (missing.length === 0)
|
|
61
|
+
return true;
|
|
62
|
+
const reqPath = join(scriptPath, '..', 'requirements-robot.txt');
|
|
63
|
+
if (!existsSync(reqPath)) {
|
|
64
|
+
console.log(chalk.red(` ✗ missing Python modules: ${missing.join(', ')} (and requirements-robot.txt not found next to ${scriptPath})`));
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
console.log(chalk.dim(` installing Python deps (${missing.join(', ')})…`));
|
|
68
|
+
const install = spawnSync(python, ['-m', 'pip', 'install', '-r', reqPath], { stdio: 'inherit' });
|
|
69
|
+
if (install.status !== 0) {
|
|
70
|
+
console.log(chalk.red(` ✗ pip install failed — install manually: ${python} -m pip install -r "${reqPath}"`));
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
const stillMissing = missingModules(python);
|
|
74
|
+
if (stillMissing.length > 0) {
|
|
75
|
+
console.log(chalk.red(` ✗ still missing after install: ${stillMissing.join(', ')} — install manually: ${python} -m pip install -r "${reqPath}"`));
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
return true;
|
|
79
|
+
}
|
package/package.json
CHANGED
|
@@ -142,6 +142,7 @@ class PreviewAgent:
|
|
|
142
142
|
self.robot_id = cfg.get("robot_id", os.getenv("ROBOT_ID", _hostname()))
|
|
143
143
|
self.device_token: Optional[str] = cfg.get("device_token") or os.getenv("DEVICE_TOKEN") or _load_token()
|
|
144
144
|
self.device_id: Optional[str] = cfg.get("device_id")
|
|
145
|
+
self.enrollment_token: Optional[str] = cfg.get("enrollment_token")
|
|
145
146
|
self.video_device = cfg.get("video_device", os.getenv("VIDEO_DEVICE", "auto"))
|
|
146
147
|
self.audio_device = cfg.get("audio_device", os.getenv("AUDIO_DEVICE", "default"))
|
|
147
148
|
self.width = int(cfg.get("video_width", os.getenv("VIDEO_WIDTH", DEFAULT_VIDEO_WIDTH)))
|
|
@@ -157,7 +158,7 @@ class PreviewAgent:
|
|
|
157
158
|
self._publisher: Optional["LiveKitPublisher"] = None
|
|
158
159
|
|
|
159
160
|
async def run(self) -> None:
|
|
160
|
-
enrollment_token = os.getenv("ENROLLMENT_TOKEN") or
|
|
161
|
+
enrollment_token = os.getenv("ENROLLMENT_TOKEN") or self.enrollment_token
|
|
161
162
|
if not self.device_token and enrollment_token:
|
|
162
163
|
self.device_token = await _enroll(self.scheduler_url, enrollment_token, self.robot_id)
|
|
163
164
|
|
|
@@ -230,7 +231,7 @@ class PreviewAgent:
|
|
|
230
231
|
if not self._session or not self.device_token:
|
|
231
232
|
return PreviewState()
|
|
232
233
|
r = await self._session.get(
|
|
233
|
-
f"{self.scheduler_url.rstrip('/')}/api/robots/{self.robot_id}/preview/agent",
|
|
234
|
+
f"{self.scheduler_url.rstrip('/')}/api/robots/{self.device_id or self.robot_id}/preview/agent",
|
|
234
235
|
headers={"Authorization": f"Bearer {self.device_token}"},
|
|
235
236
|
timeout=10.0,
|
|
236
237
|
)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Python deps for the ROBOT/PI side only (preview_agent.py) — a subset of
|
|
2
|
+
# requirements.txt, which also covers the scheduler server (fastapi/uvicorn/
|
|
3
|
+
# aiosqlite/etc). Keeping this split means a Pi/robot install doesn't pull in
|
|
4
|
+
# server-only packages it will never use.
|
|
5
|
+
httpx==0.27.2
|
|
6
|
+
livekit>=0.18
|
|
7
|
+
opencv-python>=4.8
|
|
8
|
+
pyaudio>=0.2
|
|
9
|
+
picamera2>=0.3; platform_machine == 'aarch64' or platform_machine == 'arm64'
|