@emend-ai/utim 1.43.5 → 1.43.7
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/utim.js +133 -58
- package/package.json +1 -1
- package/scripts/postinstall.js +49 -68
package/bin/utim.js
CHANGED
|
@@ -1,18 +1,56 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* UTIM CLI Launcher
|
|
5
|
+
*
|
|
6
|
+
* Designed for zero-friction UX: the user runs `utim` and it just works.
|
|
7
|
+
* If the Python engine isn't installed yet, this script installs it silently
|
|
8
|
+
* with a spinner before launching — no extra commands needed.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const { spawnSync, spawn, execFileSync } = require('child_process');
|
|
5
12
|
const os = require('os');
|
|
6
13
|
const fs = require('fs');
|
|
7
14
|
|
|
8
15
|
const args = process.argv.slice(2);
|
|
9
|
-
const
|
|
16
|
+
const isWin = os.platform() === 'win32';
|
|
17
|
+
const isMac = os.platform() === 'darwin';
|
|
18
|
+
const isTermux =
|
|
19
|
+
(process.env.PREFIX && process.env.PREFIX.includes('com.termux')) ||
|
|
20
|
+
fs.existsSync('/data/data/com.termux');
|
|
21
|
+
|
|
22
|
+
// ── Spinner ───────────────────────────────────────────────────────────────────
|
|
23
|
+
function createSpinner(text) {
|
|
24
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
25
|
+
let i = 0;
|
|
26
|
+
// Only show spinner if stdout is a TTY (not piped)
|
|
27
|
+
if (!process.stdout.isTTY) {
|
|
28
|
+
process.stderr.write(text + '\n');
|
|
29
|
+
return { stop: (msg) => { if (msg) process.stderr.write(msg + '\n'); } };
|
|
30
|
+
}
|
|
31
|
+
const iv = setInterval(() => {
|
|
32
|
+
process.stderr.write(`\r${frames[i++ % frames.length]} ${text}`);
|
|
33
|
+
}, 80);
|
|
34
|
+
return {
|
|
35
|
+
stop: (msg) => {
|
|
36
|
+
clearInterval(iv);
|
|
37
|
+
process.stderr.write('\r' + ' '.repeat(text.length + 4) + '\r');
|
|
38
|
+
if (msg) process.stderr.write(msg + '\n');
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
10
42
|
|
|
11
43
|
// ── Find a working Python 3 interpreter ──────────────────────────────────────
|
|
12
44
|
function findPython() {
|
|
13
|
-
|
|
45
|
+
const candidates = isWin ? ['python', 'python3', 'py'] : ['python3', 'python'];
|
|
46
|
+
for (const candidate of candidates) {
|
|
14
47
|
try {
|
|
15
|
-
const r = spawnSync(candidate, ['--version'], {
|
|
48
|
+
const r = spawnSync(candidate, ['--version'], {
|
|
49
|
+
encoding: 'utf8',
|
|
50
|
+
timeout: 4000,
|
|
51
|
+
shell: isWin,
|
|
52
|
+
windowsHide: true,
|
|
53
|
+
});
|
|
16
54
|
const out = (r.stdout || '') + (r.stderr || '');
|
|
17
55
|
if (out.includes('Python 3')) return candidate;
|
|
18
56
|
} catch (_) {}
|
|
@@ -20,73 +58,110 @@ function findPython() {
|
|
|
20
58
|
return null;
|
|
21
59
|
}
|
|
22
60
|
|
|
23
|
-
// ──
|
|
24
|
-
function
|
|
61
|
+
// ── Check if utim_cli Python package is importable ───────────────────────────
|
|
62
|
+
function isUtimInstalled(python) {
|
|
25
63
|
try {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
return false;
|
|
64
|
+
const r = spawnSync(python, ['-c', 'import utim_cli'], {
|
|
65
|
+
timeout: 5000,
|
|
66
|
+
shell: isWin,
|
|
67
|
+
windowsHide: true,
|
|
68
|
+
});
|
|
69
|
+
return r.status === 0;
|
|
70
|
+
} catch (_) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
36
73
|
}
|
|
37
74
|
|
|
38
|
-
// ──
|
|
39
|
-
function
|
|
40
|
-
|
|
41
|
-
}
|
|
75
|
+
// ── Install UTIM engine from PyPI with a spinner ──────────────────────────────
|
|
76
|
+
function installEngine(python) {
|
|
77
|
+
const spinner = createSpinner('Setting up UTIM (first run)...');
|
|
42
78
|
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
79
|
+
// On Termux: prebuilt packages must exist before pydantic-core compiles
|
|
80
|
+
if (isTermux) {
|
|
81
|
+
spinner.stop();
|
|
82
|
+
process.stderr.write(
|
|
83
|
+
'\n💡 Termux detected — installing prebuilt dependencies first...\n' +
|
|
84
|
+
' (This is needed to avoid pydantic-core compilation errors)\n\n'
|
|
85
|
+
);
|
|
86
|
+
spawnSync('pkg', ['install', '-y', 'python-cryptography', 'python-pydantic'], {
|
|
87
|
+
stdio: 'inherit',
|
|
88
|
+
shell: false,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const pipArgs = ['-m', 'pip', 'install', '--upgrade', '--quiet', 'utim-cli'];
|
|
93
|
+
const result = spawnSync(python, pipArgs, {
|
|
94
|
+
stdio: isTermux ? 'inherit' : 'pipe',
|
|
95
|
+
shell: isWin,
|
|
96
|
+
windowsHide: true,
|
|
97
|
+
timeout: 300000, // 5 min max
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
if (!isTermux) spinner.stop();
|
|
101
|
+
|
|
102
|
+
return result.status === 0;
|
|
54
103
|
}
|
|
55
104
|
|
|
56
|
-
// ──
|
|
57
|
-
function
|
|
58
|
-
|
|
59
|
-
const install = spawnSync(python, ['-m', 'pip', 'install', '--upgrade', 'utim-cli'], {
|
|
105
|
+
// ── Launch UTIM Python engine ─────────────────────────────────────────────────
|
|
106
|
+
function launch(python) {
|
|
107
|
+
const proc = spawn(python, ['-m', 'utim_cli.utim', ...args], {
|
|
60
108
|
stdio: 'inherit',
|
|
61
|
-
shell:
|
|
109
|
+
shell: false,
|
|
110
|
+
windowsHide: false,
|
|
62
111
|
});
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
112
|
+
proc.on('exit', (code) => process.exit(code == null ? 0 : code));
|
|
113
|
+
proc.on('error', (err) => {
|
|
114
|
+
process.stderr.write(`\nFailed to start UTIM: ${err.message}\n`);
|
|
115
|
+
process.exit(1);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ── Error: Python not found ───────────────────────────────────────────────────
|
|
120
|
+
function noPythonError() {
|
|
121
|
+
process.stderr.write('\n❌ Python 3 was not found on this system.\n\n');
|
|
122
|
+
if (isTermux) {
|
|
123
|
+
process.stderr.write(
|
|
124
|
+
'💡 Termux: run pkg install python then try utim again.\n'
|
|
125
|
+
);
|
|
126
|
+
} else if (isMac) {
|
|
127
|
+
process.stderr.write(
|
|
128
|
+
'Install Python via Homebrew: brew install python\n' +
|
|
129
|
+
'or download from: https://python.org\n'
|
|
130
|
+
);
|
|
131
|
+
} else if (isWin) {
|
|
132
|
+
process.stderr.write(
|
|
133
|
+
'Download Python from https://python.org\n' +
|
|
134
|
+
'Make sure to check "Add Python to PATH" during installation.\n'
|
|
135
|
+
);
|
|
136
|
+
} else {
|
|
137
|
+
process.stderr.write(
|
|
138
|
+
'Install Python 3.9+ via your package manager, e.g.:\n' +
|
|
139
|
+
' sudo apt install python3 python3-pip\n'
|
|
140
|
+
);
|
|
66
141
|
}
|
|
67
|
-
|
|
142
|
+
process.stderr.write('\nThen run utim again — no extra steps required.\n\n');
|
|
143
|
+
process.exit(1);
|
|
68
144
|
}
|
|
69
145
|
|
|
70
146
|
// ── Main ─────────────────────────────────────────────────────────────────────
|
|
71
147
|
const python = findPython();
|
|
72
148
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
(python && selfInstallAndRun(python));
|
|
149
|
+
if (!python) {
|
|
150
|
+
noPythonError();
|
|
151
|
+
}
|
|
77
152
|
|
|
78
|
-
if (!
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
console.error(`Tried Python at: ${python}`);
|
|
89
|
-
console.error('Run manually: pip install utim-cli or pip3 install --upgrade utim-cli');
|
|
153
|
+
if (!isUtimInstalled(python)) {
|
|
154
|
+
const ok = installEngine(python);
|
|
155
|
+
if (!ok || !isUtimInstalled(python)) {
|
|
156
|
+
process.stderr.write(
|
|
157
|
+
'\n❌ Failed to install UTIM Python engine automatically.\n\n' +
|
|
158
|
+
'Please run manually:\n' +
|
|
159
|
+
` ${python} -m pip install utim-cli\n\n` +
|
|
160
|
+
'Then run utim again.\n\n'
|
|
161
|
+
);
|
|
162
|
+
process.exit(1);
|
|
90
163
|
}
|
|
91
|
-
process.exit(1);
|
|
92
164
|
}
|
|
165
|
+
|
|
166
|
+
// Engine is confirmed installed — launch it
|
|
167
|
+
launch(python);
|
package/package.json
CHANGED
package/scripts/postinstall.js
CHANGED
|
@@ -1,30 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Optional postinstall pre-warm for @emend-ai/utim.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* 3. Set the execute bit on bin/utim.js (Unix / Termux only).
|
|
4
|
+
* This is a best-effort pre-installation of the UTIM Python engine.
|
|
5
|
+
* If it fails for ANY reason, we exit 0 — the main launcher (bin/utim.js)
|
|
6
|
+
* will handle installation automatically on first run with a clean spinner.
|
|
8
7
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* This script exists purely to make the first `utim` launch faster
|
|
9
|
+
* by pre-installing the Python engine during `npm install`.
|
|
11
10
|
*/
|
|
12
11
|
const { spawnSync } = require('child_process');
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const fs = require('fs');
|
|
12
|
+
const os = require('os');
|
|
13
|
+
const fs = require('fs');
|
|
16
14
|
|
|
17
|
-
const
|
|
18
|
-
const
|
|
15
|
+
const isWin = os.platform() === 'win32';
|
|
16
|
+
const isTermux =
|
|
17
|
+
(process.env.PREFIX && process.env.PREFIX.includes('com.termux')) ||
|
|
18
|
+
fs.existsSync('/data/data/com.termux');
|
|
19
19
|
|
|
20
|
-
// ── Find a usable Python 3 interpreter ────────────────────────────────────────
|
|
21
20
|
function findPython() {
|
|
22
|
-
|
|
21
|
+
const candidates = isWin ? ['python', 'python3', 'py'] : ['python3', 'python'];
|
|
22
|
+
for (const candidate of candidates) {
|
|
23
23
|
try {
|
|
24
24
|
const r = spawnSync(candidate, ['--version'], {
|
|
25
25
|
encoding: 'utf8',
|
|
26
26
|
timeout: 4000,
|
|
27
|
-
shell:
|
|
27
|
+
shell: isWin,
|
|
28
|
+
windowsHide: true,
|
|
28
29
|
});
|
|
29
30
|
const out = (r.stdout || '') + (r.stderr || '');
|
|
30
31
|
if (out.includes('Python 3')) return candidate;
|
|
@@ -33,68 +34,48 @@ function findPython() {
|
|
|
33
34
|
return null;
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
//
|
|
37
|
-
function installFromPyPI(python) {
|
|
38
|
-
console.log('\n⚙ Installing UTIM Python engine from PyPI (pip install utim-cli)...\n');
|
|
39
|
-
try {
|
|
40
|
-
const r = spawnSync(
|
|
41
|
-
python,
|
|
42
|
-
['-m', 'pip', 'install', '--upgrade', 'utim-cli'],
|
|
43
|
-
{ stdio: 'inherit', shell: useShell }
|
|
44
|
-
);
|
|
45
|
-
return r.status === 0;
|
|
46
|
-
} catch (_) {
|
|
47
|
-
return false;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// ── Set execute bit on Unix / Termux ─────────────────────────────────────────
|
|
37
|
+
// Set execute bit on Unix / macOS / Termux
|
|
52
38
|
function setExecuteBit() {
|
|
53
|
-
if (
|
|
54
|
-
const binFile = path.join(pkgDir, 'bin', 'utim.js');
|
|
39
|
+
if (!isWin) {
|
|
55
40
|
try {
|
|
41
|
+
const binFile = require('path').join(__dirname, '..', 'bin', 'utim.js');
|
|
56
42
|
fs.chmodSync(binFile, 0o755);
|
|
57
|
-
} catch (_) {
|
|
58
|
-
// non-fatal — npm usually sets the bit itself via package.json "bin"
|
|
59
|
-
}
|
|
43
|
+
} catch (_) {}
|
|
60
44
|
}
|
|
61
45
|
}
|
|
62
46
|
|
|
63
|
-
|
|
64
|
-
const python = findPython();
|
|
47
|
+
setExecuteBit();
|
|
65
48
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
' Please install Python 3.9+ from https://python.org and then run:\n' +
|
|
70
|
-
' pip install utim-cli\n' +
|
|
71
|
-
' to finish setting up UTIM.\n'
|
|
72
|
-
);
|
|
73
|
-
// Exit 0 so npm does not roll back the install — user can pip install manually
|
|
49
|
+
// Skip pre-warm on Termux — needs prebuilt pkg packages first,
|
|
50
|
+
// which the main launcher handles properly with user guidance.
|
|
51
|
+
if (isTermux) {
|
|
74
52
|
process.exit(0);
|
|
75
53
|
}
|
|
76
54
|
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if (ok) {
|
|
81
|
-
console.log('\n✅ UTIM installed successfully. Run utim to get started.\n');
|
|
82
|
-
} else {
|
|
83
|
-
const isTermux = (process.env.PREFIX && process.env.PREFIX.includes('com.termux')) || fs.existsSync('/data/data/com.termux');
|
|
84
|
-
if (isTermux) {
|
|
85
|
-
console.warn(
|
|
86
|
-
'\n💡 Note for Termux Users: Precompiled binaries are required to avoid Rust compiler errors (pydantic-core).\n' +
|
|
87
|
-
' Please run: pkg install python-cryptography python-pydantic\n' +
|
|
88
|
-
' And then: pip install utim-cli\n'
|
|
89
|
-
);
|
|
90
|
-
} else {
|
|
91
|
-
console.warn(
|
|
92
|
-
'\n⚠ pip install failed. Try running manually:\n' +
|
|
93
|
-
' pip install utim-cli\n' +
|
|
94
|
-
' or:\n' +
|
|
95
|
-
' pip3 install utim-cli\n'
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
// Exit 0 — don't break the npm install; user can manually pip install
|
|
55
|
+
const python = findPython();
|
|
56
|
+
if (!python) {
|
|
57
|
+
// No Python — launcher will guide the user on first run
|
|
99
58
|
process.exit(0);
|
|
100
59
|
}
|
|
60
|
+
|
|
61
|
+
// Silently pre-install in the background (pipe output, not inherit)
|
|
62
|
+
// so the npm install output stays clean. Any failure is fine —
|
|
63
|
+
// the launcher self-heals on first `utim` run.
|
|
64
|
+
try {
|
|
65
|
+
const r = spawnSync(
|
|
66
|
+
python,
|
|
67
|
+
['-m', 'pip', 'install', '--upgrade', '--quiet', 'utim-cli'],
|
|
68
|
+
{
|
|
69
|
+
stdio: 'pipe',
|
|
70
|
+
shell: isWin,
|
|
71
|
+
windowsHide: true,
|
|
72
|
+
timeout: 300000,
|
|
73
|
+
}
|
|
74
|
+
);
|
|
75
|
+
if (r.status === 0) {
|
|
76
|
+
console.log('\n✅ UTIM ready. Run utim to get started.\n');
|
|
77
|
+
}
|
|
78
|
+
// If it fails, stay silent — launcher will handle it
|
|
79
|
+
} catch (_) {}
|
|
80
|
+
|
|
81
|
+
process.exit(0);
|