@metaphi-ai/hum 0.1.26 → 0.1.29

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.
@@ -0,0 +1,119 @@
1
+ // src/install.js
2
+ import { spawnSync } from "node:child_process";
3
+ import fs from "node:fs";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ var win = process.platform === "win32";
7
+ var npmFix = (version = "") => `npm i -g @metaphi-ai/hum@${version || "latest"} --foreground-scripts`;
8
+ var NPM_FIX = npmFix("latest");
9
+ var humHome = (env = process.env) => env.HUM_HOME || path.join(os.homedir(), ".hum");
10
+ var engineDir = (env = process.env) => path.join(humHome(env), "engine");
11
+ var enginePython = (env = process.env) => win ? path.join(engineDir(env), "Scripts", "python.exe") : path.join(engineDir(env), "bin", "python");
12
+ var stampPath = (env) => path.join(engineDir(env), "version");
13
+ var statePath = (env) => path.join(humHome(env), "engine-install.json");
14
+ var run = (cmd, args, opts = {}) => spawnSync(cmd, args, { encoding: "utf8", windowsHide: true, ...opts });
15
+ var ASK_VERSION = ["-I", "-c", "import hum; print(hum.__version__)"];
16
+ function importedVersion(env = process.env, runImpl = run) {
17
+ const r = runImpl(enginePython(env), ASK_VERSION);
18
+ return r && r.status === 0 ? String(r.stdout).trim() : null;
19
+ }
20
+ function installedVersion(env = process.env, runImpl = run) {
21
+ try {
22
+ return fs.readFileSync(stampPath(env), "utf8").trim() || null;
23
+ } catch {
24
+ }
25
+ if (!fs.existsSync(enginePython(env))) return null;
26
+ const v = importedVersion(env, runImpl);
27
+ if (v) {
28
+ try {
29
+ fs.writeFileSync(stampPath(env), `${v}
30
+ `);
31
+ } catch {
32
+ }
33
+ }
34
+ return v;
35
+ }
36
+ var readInstallState = (env = process.env) => {
37
+ try {
38
+ return JSON.parse(fs.readFileSync(statePath(env), "utf8"));
39
+ } catch {
40
+ return null;
41
+ }
42
+ };
43
+ var writeInstallState = (s, env) => {
44
+ try {
45
+ fs.mkdirSync(humHome(env), { recursive: true });
46
+ fs.writeFileSync(statePath(env), JSON.stringify(s));
47
+ } catch {
48
+ }
49
+ };
50
+ function needsInstall(version, { env = process.env, installed = () => installedVersion(env), now = Date.now(), state = readInstallState(env) } = {}) {
51
+ if (!version || env.HUM_ENGINE || env.HUM_ENGINE_PORT) return false;
52
+ if (installed() === version) return false;
53
+ if (state && !state.ok && state.version === version && now - (state.at || 0) < 10 * 60 * 1e3) return false;
54
+ return true;
55
+ }
56
+ function installEngine(version, { env = process.env, say = (m) => console.log(`hum: ${m}`), runImpl = run, now = Date.now() } = {}) {
57
+ const home = humHome(env);
58
+ const dir = engineDir(env);
59
+ const python = enginePython(env);
60
+ const localBin = path.join(os.homedir(), ".local", "bin");
61
+ const uvNames = ["uv", path.join(localBin, win ? "uv.exe" : "uv")];
62
+ const uv = () => uvNames.find((u) => {
63
+ const r = runImpl(u, ["--version"]);
64
+ return r && r.status === 0;
65
+ });
66
+ const spec = env.HUM_ENGINE_SPEC || `hum-cli==${version}`;
67
+ const outcome = (ok, error = null, got = version) => {
68
+ writeInstallState({ version: got, wanted: version, ok, error, at: now }, env);
69
+ return { ok, version: got, error };
70
+ };
71
+ try {
72
+ let u = uv();
73
+ if (!u) {
74
+ say("installing uv (it creates the Python environment for the part of hum that does the work) \u2026");
75
+ const r = win ? runImpl("powershell", ["-ExecutionPolicy", "ByPass", "-c", "irm https://astral.sh/uv/install.ps1 | iex"], { stdio: "inherit" }) : runImpl("sh", ["-c", "curl -LsSf https://astral.sh/uv/install.sh | sh"], { stdio: "inherit" });
76
+ u = r && r.status === 0 ? uv() : null;
77
+ if (!u) return outcome(false, `could not install uv; install it from https://astral.sh/uv and run: ${npmFix(version)}`);
78
+ }
79
+ say(`installing ${env.HUM_ENGINE_SPEC ? spec : `hum ${version}`} into ${dir} \u2026`);
80
+ fs.mkdirSync(home, { recursive: true });
81
+ try {
82
+ fs.unlinkSync(stampPath(env));
83
+ } catch {
84
+ }
85
+ if (!fs.existsSync(python)) {
86
+ const args = ["venv", "--quiet", "--python", "3.12", ...fs.existsSync(dir) ? ["--clear"] : [], dir];
87
+ if ((runImpl(u, args, { stdio: "inherit" }) || {}).status !== 0) return outcome(false, "uv venv failed");
88
+ }
89
+ if ((runImpl(u, ["pip", "install", "--quiet", "--python", python, spec], { stdio: "inherit" }) || {}).status !== 0) return outcome(false, `uv pip install ${spec} failed`);
90
+ const got = importedVersion(env, runImpl);
91
+ if (!got) return outcome(false, "hum did not import after the install");
92
+ if (got !== version && !env.HUM_ENGINE_SPEC) return outcome(false, `installed ${got}, wanted ${version}`, got);
93
+ fs.mkdirSync(dir, { recursive: true });
94
+ fs.writeFileSync(stampPath(env), `${got}
95
+ `);
96
+ const list = runImpl(u, ["tool", "list"]);
97
+ if (list && list.status === 0 && /^hum-cli\b/m.test(list.stdout)) {
98
+ const rm = runImpl(u, ["tool", "uninstall", "hum-cli"]);
99
+ if (rm && rm.status === 0) say(`removed the uv install of hum-cli and its shims in ${localBin}; hum lives in ${dir} now`);
100
+ }
101
+ say(`hum ${got} ready`);
102
+ return outcome(true, null, got);
103
+ } catch (e) {
104
+ return outcome(false, e && e.message ? e.message : String(e));
105
+ }
106
+ }
107
+ export {
108
+ ASK_VERSION,
109
+ NPM_FIX,
110
+ engineDir,
111
+ enginePython,
112
+ humHome,
113
+ importedVersion,
114
+ installEngine,
115
+ installedVersion,
116
+ needsInstall,
117
+ npmFix,
118
+ readInstallState
119
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metaphi-ai/hum",
3
- "version": "0.1.26",
3
+ "version": "0.1.29",
4
4
  "description": "Hum (हम): a self-improving coding agent for your terminal.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -1,58 +1,17 @@
1
- // The engine, installed beside the person's Hum data: ~/.hum/engine (HUM_HOME/engine), a plain
2
- // Python environment holding hum-cli at this package's version. No launcher shims are written
3
- // anywhere `hum` starts the engine through the environment's own interpreter so nothing of
4
- // ours lands in ~/.local/bin, and nothing there can shadow the `hum` npm installed.
5
- // uv creates the environment (and fetches a Python if the machine has none); it is installed
6
- // when missing. Quiet when the engine is already at this version; never fails the npm install.
7
- import { execFileSync, spawnSync } from 'node:child_process';
1
+ // npm's postinstall: complete the install at this package's version. The work is in
2
+ // dist/install.mjs the same code `hum` runs at start when the two halves differ and the
3
+ // outcome lands in ~/.hum/engine-install.json, because npm shows none of this unless it is
4
+ // run with --foreground-scripts. Never fails the npm install.
8
5
  import fs from 'node:fs';
9
- import os from 'node:os';
10
6
  import path from 'node:path';
11
- import { fileURLToPath } from 'node:url';
7
+ import { fileURLToPath, pathToFileURL } from 'node:url';
12
8
 
13
- const win = process.platform === 'win32';
14
9
  const here = path.dirname(fileURLToPath(import.meta.url));
15
10
  const version = JSON.parse(fs.readFileSync(path.join(here, '..', 'package.json'), 'utf8')).version;
16
- const home = process.env.HUM_HOME || path.join(os.homedir(), '.hum');
17
- const env = path.join(home, 'engine');
18
- const python = win ? path.join(env, 'Scripts', 'python.exe') : path.join(env, 'bin', 'python');
19
- const localBin = path.join(os.homedir(), '.local', 'bin');
20
- const say = (m) => console.log(`hum: ${m}`);
11
+ // a URL, not a path: on Windows `C:\…` is not something the ESM loader will import
12
+ const { installedVersion, installEngine, npmFix } = await import(pathToFileURL(path.join(here, '..', 'dist', 'install.mjs')).href);
21
13
 
22
- const run = (cmd, args, opts = {}) => spawnSync(cmd, args, { encoding: 'utf8', windowsHide: true, ...opts });
23
- const uvNames = ['uv', path.join(localBin, win ? 'uv.exe' : 'uv')];
24
- const uv = () => uvNames.find((u) => run(u, ['--version']).status === 0);
25
-
26
- const installed = () => {
27
- const r = run(python, ['-c', 'import hum; print(hum.__version__)']);
28
- return r.status === 0 ? r.stdout.trim() : null;
29
- };
30
-
31
- try {
32
- if (installed() === version) process.exit(0);
33
- let u = uv();
34
- if (!u) {
35
- say('installing uv (it creates the engine\'s Python environment) …');
36
- const r = win
37
- ? run('powershell', ['-ExecutionPolicy', 'ByPass', '-c', 'irm https://astral.sh/uv/install.ps1 | iex'], { stdio: 'inherit' })
38
- : run('sh', ['-c', 'curl -LsSf https://astral.sh/uv/install.sh | sh'], { stdio: 'inherit' });
39
- u = r.status === 0 ? uv() : null;
40
- if (!u) { say('could not install uv; install it from https://astral.sh/uv and run: npm rebuild -g @metaphi-ai/hum'); process.exit(0); }
41
- }
42
- say(`installing the engine ${version} into ${env} …`);
43
- fs.mkdirSync(home, { recursive: true });
44
- if (run(u, ['venv', '--quiet', '--python', '3.12', env], { stdio: 'inherit' }).status !== 0) throw new Error('uv venv failed');
45
- if (run(u, ['pip', 'install', '--quiet', '--python', python, `hum-cli==${version}`], { stdio: 'inherit' }).status !== 0) throw new Error('uv pip install failed');
46
- if (installed() !== version) throw new Error('the engine did not import after install');
47
-
48
- // An earlier version installed the engine with `uv tool install`, which wrote launcher shims
49
- // (hum, hum-engine) into ~/.local/bin — where they shadow the `hum` npm installed, and where a
50
- // managed Windows machine refuses them. That engine is redundant now; take it and its shims out.
51
- const list = run(u, ['tool', 'list']);
52
- if (list.status === 0 && /^hum-cli\b/m.test(list.stdout)) {
53
- if (run(u, ['tool', 'uninstall', 'hum-cli']).status === 0) say(`removed the uv install of hum-cli and its shims in ${localBin}; the engine lives in ${env} now`);
54
- }
55
- say(`engine ${version} ready`);
56
- } catch (e) {
57
- say(`engine install failed (${e.message}); run: npm rebuild -g @metaphi-ai/hum`);
14
+ if (installedVersion() !== version) {
15
+ const r = installEngine(version);
16
+ if (!r.ok) console.log(`hum: the install did not finish (${r.error}); run: ${npmFix(version)}`);
58
17
  }