@metaphi-ai/hum 0.1.8 → 0.1.10

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/cli.mjs CHANGED
@@ -84524,7 +84524,7 @@ import net from "node:net";
84524
84524
  import os4 from "node:os";
84525
84525
  import path2 from "node:path";
84526
84526
  import crypto from "node:crypto";
84527
- import { spawn } from "node:child_process";
84527
+ import { spawn, spawnSync } from "node:child_process";
84528
84528
  var runDir = () => path2.join(process.env.HUM_HOME || path2.join(os4.homedir(), ".hum"), "run");
84529
84529
  var readRun = (id) => {
84530
84530
  try {
@@ -84535,74 +84535,170 @@ var readRun = (id) => {
84535
84535
  };
84536
84536
  var EngineError = class extends Error {
84537
84537
  };
84538
- var ENGINE_CANDIDATES = [
84539
- process.env.HUM_ENGINE,
84540
- // explicit: a command to run (e.g. `python -m hum.cli engine`)
84541
- "hum-engine",
84542
- // uv tool install hum-cli
84543
- path2.join(os4.homedir(), ".local", "bin", process.platform === "win32" ? "hum-engine.exe" : "hum-engine")
84544
- // uv's bin dir, off PATH
84545
- ];
84546
- function spawnEngine(cwd2, { task = null, images = [], resume = null } = {}) {
84538
+ var win = process.platform === "win32";
84539
+ var exists = (p) => {
84540
+ try {
84541
+ fs3.accessSync(p);
84542
+ return true;
84543
+ } catch {
84544
+ return false;
84545
+ }
84546
+ };
84547
+ function uvToolDirs() {
84548
+ const dirs = [];
84549
+ if (process.env.UV_TOOL_DIR) dirs.push(process.env.UV_TOOL_DIR);
84550
+ if (win) {
84551
+ for (const base of [process.env.APPDATA, process.env.LOCALAPPDATA]) if (base) dirs.push(path2.join(base, "uv", "tools"));
84552
+ } else dirs.push(path2.join(process.env.XDG_DATA_HOME || path2.join(os4.homedir(), ".local", "share"), "uv", "tools"));
84553
+ if (!dirs.some(exists)) {
84554
+ for (const uv of ["uv", path2.join(os4.homedir(), ".local", "bin", win ? "uv.exe" : "uv")]) {
84555
+ try {
84556
+ const r = spawnSync(uv, ["tool", "dir"], { encoding: "utf8", windowsHide: true });
84557
+ if (r.status === 0 && r.stdout.trim()) {
84558
+ dirs.push(r.stdout.trim());
84559
+ break;
84560
+ }
84561
+ } catch {
84562
+ }
84563
+ }
84564
+ }
84565
+ return dirs;
84566
+ }
84567
+ function enginePython(dirs = uvToolDirs()) {
84568
+ for (const dir of dirs) for (const name of ["hum-cli", "hum"]) {
84569
+ const py = win ? path2.join(dir, name, "Scripts", "python.exe") : path2.join(dir, name, "bin", "python");
84570
+ if (exists(py)) return py;
84571
+ }
84572
+ return null;
84573
+ }
84574
+ function siblingEngine(execPath = process.execPath) {
84575
+ let dir;
84576
+ try {
84577
+ dir = path2.dirname(fs3.realpathSync(execPath));
84578
+ } catch {
84579
+ return null;
84580
+ }
84581
+ const p = path2.join(dir, "engine", win ? "hum-engine.exe" : "hum-engine");
84582
+ return exists(p) ? p : null;
84583
+ }
84584
+ function homeEngine(env3 = process.env) {
84585
+ const home = env3.HUM_HOME || path2.join(os4.homedir(), ".hum");
84586
+ const py = win ? path2.join(home, "engine", "Scripts", "python.exe") : path2.join(home, "engine", "bin", "python");
84587
+ return exists(py) ? py : null;
84588
+ }
84589
+ function engineCandidates(env3 = process.env) {
84590
+ const out = [];
84591
+ if (env3.HUM_ENGINE) {
84592
+ const [bin, ...args2] = env3.HUM_ENGINE.split(" ");
84593
+ out.push({ bin, args: args2, where: "HUM_ENGINE" });
84594
+ }
84595
+ const sib = siblingEngine();
84596
+ if (sib) out.push({ bin: sib, args: ["engine"], where: "beside hum" });
84597
+ const home = homeEngine(env3);
84598
+ if (home) out.push({ bin: home, args: ["-m", "hum.cli", "engine"], where: "HUM_HOME/engine" });
84599
+ const py = enginePython();
84600
+ if (py) out.push({ bin: py, args: ["-m", "hum.cli", "engine"], where: "the engine's interpreter" });
84601
+ out.push({ bin: "hum-engine", args: [], where: "PATH" });
84602
+ out.push({ bin: path2.join(os4.homedir(), ".local", "bin", win ? "hum-engine.exe" : "hum-engine"), args: [], where: "uv's bin dir" });
84603
+ return out;
84604
+ }
84605
+ function engineCommand(argv, candidates = engineCandidates()) {
84606
+ for (const c of candidates) {
84607
+ if (c.args[c.args.length - 1] !== "engine") continue;
84608
+ return { bin: c.bin, args: [...c.args.slice(0, -1), ...argv], where: c.where };
84609
+ }
84610
+ return null;
84611
+ }
84612
+ function startFailure(failures) {
84613
+ const refused = failures.find((f) => f.error && f.error.code !== "ENOENT");
84614
+ if (!refused) {
84615
+ return "Hum's engine is not installed. Run: uv tool install hum-cli (or set HUM_ENGINE to the command that starts it)";
84616
+ }
84617
+ const what = refused.error.code || refused.error.message;
84618
+ const lines = [
84619
+ `Hum's engine is installed (${refused.bin}) but this machine would not start it: ${what}.`
84620
+ ];
84621
+ if (win) {
84622
+ lines.push("On a managed Windows machine that is usually Device Guard / WDAC refusing an executable it does not know.");
84623
+ lines.push(`To see the policy's own message, run: "${refused.bin}" --help`);
84624
+ const dir = uvToolDirs()[0];
84625
+ lines.push(`If uv's environment is elsewhere, point Hum at its interpreter: set HUM_ENGINE=${path2.join(dir || "<uv tool dir>", "hum-cli", "Scripts", "python.exe")} -m hum.cli engine`);
84626
+ } else {
84627
+ lines.push('Set HUM_ENGINE to a command that starts it, e.g. HUM_ENGINE="python -m hum.cli engine"');
84628
+ }
84629
+ return lines.join("\n");
84630
+ }
84631
+ function attempt({ bin, args: args2 }, cwd2, { task, images, resume }) {
84547
84632
  return new Promise((resolve, reject) => {
84548
- const tryNext = (i) => {
84549
- const cmd = ENGINE_CANDIDATES[i];
84550
- if (cmd === void 0) {
84551
- reject(new EngineError(
84552
- "Hum's engine is not installed. Run: uv tool install hum-cli (or set HUM_ENGINE to the command that starts it)"
84553
- ));
84633
+ const runId = crypto.randomBytes(6).toString("hex");
84634
+ fs3.mkdirSync(runDir(), { recursive: true });
84635
+ const logPath = path2.join(runDir(), `${runId}.log`);
84636
+ const log = fs3.openSync(logPath, "a");
84637
+ const argv = [...args2, "-C", cwd2, "--run-id", runId];
84638
+ if (task) argv.push("--task", task);
84639
+ for (const p of images) argv.push("--image", p);
84640
+ if (resume) argv.push("--resume", resume);
84641
+ let settled = false;
84642
+ const settle = (fn) => {
84643
+ if (!settled) {
84644
+ settled = true;
84645
+ fn();
84646
+ }
84647
+ };
84648
+ let child;
84649
+ try {
84650
+ child = spawn(bin, argv, { stdio: ["ignore", log, log], detached: true, windowsHide: true });
84651
+ } catch (e) {
84652
+ fs3.closeSync(log);
84653
+ reject({ spawn: e });
84654
+ return;
84655
+ }
84656
+ fs3.closeSync(log);
84657
+ let died = false;
84658
+ child.on("error", (e) => settle(() => reject({ spawn: e })));
84659
+ child.on("exit", () => {
84660
+ died = true;
84661
+ });
84662
+ const t0 = Date.now();
84663
+ const poll = () => {
84664
+ if (settled) return;
84665
+ const rec = readRun(runId);
84666
+ if (rec && rec.port) {
84667
+ child.unref();
84668
+ settle(() => resolve(rec));
84554
84669
  return;
84555
84670
  }
84556
- if (!cmd) {
84557
- tryNext(i + 1);
84671
+ if (died && !rec) {
84672
+ settle(() => reject({ died: logPath }));
84558
84673
  return;
84559
84674
  }
84560
- const runId = crypto.randomBytes(6).toString("hex");
84561
- fs3.mkdirSync(runDir(), { recursive: true });
84562
- const log = fs3.openSync(path2.join(runDir(), `${runId}.log`), "a");
84563
- const [bin, ...rest] = cmd.split(" ");
84564
- const args2 = [...rest, "-C", cwd2, "--run-id", runId];
84565
- if (task) args2.push("--task", task);
84566
- for (const p of images) args2.push("--image", p);
84567
- if (resume) args2.push("--resume", resume);
84568
- let child;
84569
- try {
84570
- child = spawn(bin, args2, { stdio: ["ignore", log, log], detached: true, windowsHide: true });
84571
- } catch {
84572
- tryNext(i + 1);
84675
+ if (Date.now() - t0 > 25e3) {
84676
+ settle(() => reject({ timeout: true }));
84573
84677
  return;
84574
84678
  }
84575
- let failed = false;
84576
- child.on("error", () => {
84577
- failed = true;
84578
- tryNext(i + 1);
84579
- });
84580
- child.on("exit", () => {
84581
- failed = true;
84582
- });
84583
- const t0 = Date.now();
84584
- const poll = () => {
84585
- const rec = readRun(runId);
84586
- if (rec && rec.port) {
84587
- child.unref();
84588
- resolve(rec);
84589
- return;
84590
- }
84591
- if (failed && !rec) {
84592
- reject(new EngineError(`the engine did not start (see ${path2.join(runDir(), `${runId}.log`)})`));
84593
- return;
84594
- }
84595
- if (Date.now() - t0 > 25e3) {
84596
- reject(new EngineError("the engine did not start in time"));
84597
- return;
84598
- }
84599
- setTimeout(poll, 50);
84600
- };
84601
- poll();
84679
+ setTimeout(poll, 50);
84602
84680
  };
84603
- tryNext(0);
84681
+ poll();
84604
84682
  });
84605
84683
  }
84684
+ async function spawnEngine(cwd2, { task = null, images = [], resume = null, candidates = engineCandidates() } = {}) {
84685
+ const failures = [];
84686
+ for (const cand of candidates) {
84687
+ if (!cand.bin) continue;
84688
+ try {
84689
+ return await attempt(cand, cwd2, { task, images, resume });
84690
+ } catch (f) {
84691
+ if (f && f.spawn) {
84692
+ failures.push({ ...cand, error: f.spawn });
84693
+ continue;
84694
+ }
84695
+ if (f && f.died) throw new EngineError(`the engine did not start (see ${f.died})`);
84696
+ if (f && f.timeout) throw new EngineError("the engine did not start in time");
84697
+ throw f;
84698
+ }
84699
+ }
84700
+ throw new EngineError(startFailure(failures));
84701
+ }
84606
84702
  var Engine = class {
84607
84703
  constructor(port2) {
84608
84704
  this.port = port2;
@@ -84676,6 +84772,27 @@ var Engine = class {
84676
84772
  var import_jsx_runtime3 = __toESM(require_jsx_runtime(), 1);
84677
84773
  import fs4 from "node:fs";
84678
84774
  import path3 from "node:path";
84775
+ import { spawnSync as spawnSync2 } from "node:child_process";
84776
+ var ENGINE_COMMANDS = /* @__PURE__ */ new Set(["run", "login", "logout", "whoami", "model", "key", "sessions", "show", "sync", "skill", "ps", "stop", "hooks", "engine"]);
84777
+ var argv0 = process.argv.slice(2);
84778
+ if (argv0[0] === "--version" || argv0[0] === "-V") {
84779
+ process.stdout.write(`hum ${true ? "0.1.10" : "0.0.0"}
84780
+ `);
84781
+ process.exit(0);
84782
+ }
84783
+ if (ENGINE_COMMANDS.has(argv0[0])) {
84784
+ const cmd = engineCommand(argv0);
84785
+ if (!cmd) {
84786
+ process.stderr.write(startFailure([]) + "\n");
84787
+ process.exit(1);
84788
+ }
84789
+ const r = spawnSync2(cmd.bin, cmd.args, { stdio: "inherit", windowsHide: true });
84790
+ if (r.error) {
84791
+ process.stderr.write(startFailure([{ bin: cmd.bin, error: r.error }]) + "\n");
84792
+ process.exit(1);
84793
+ }
84794
+ process.exit(r.status === null ? 1 : r.status);
84795
+ }
84679
84796
  function parseArgs(argv) {
84680
84797
  const a = { task: null, resume: null, cwd: process.cwd(), images: [] };
84681
84798
  for (let i = 0; i < argv.length; i += 1) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metaphi-ai/hum",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "Hum (हम): a self-improving coding agent for your terminal.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -1,23 +1,58 @@
1
- // Make sure the engine is installed: `hum-engine` on PATH (uv tool install hum-cli).
2
- // Quiet when it already is; installs uv first when it is missing; never fails the npm install.
3
- import { execSync, spawnSync } from 'node:child_process';
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';
8
+ import fs from 'node:fs';
4
9
  import os from 'node:os';
5
10
  import path from 'node:path';
11
+ import { fileURLToPath } from 'node:url';
6
12
 
7
- const which = (cmd) => spawnSync(process.platform === 'win32' ? 'where' : 'which', [cmd], { stdio: 'ignore' }).status === 0;
8
- const local = path.join(os.homedir(), '.local', 'bin');
9
- const sh = (cmd) => { try { execSync(cmd, { stdio: 'inherit', env: { ...process.env, PATH: `${local}${path.delimiter}${process.env.PATH || ''}` } }); return true; } catch { return false; } };
13
+ const win = process.platform === 'win32';
14
+ const here = path.dirname(fileURLToPath(import.meta.url));
15
+ 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}`);
10
21
 
11
- if (which('hum-engine') || spawnSync(path.join(local, 'hum-engine'), ['--help'], { stdio: 'ignore' }).status === 0) {
12
- process.exit(0);
13
- }
14
- console.log('hum: installing the engine (uv tool install hum-cli) …');
15
- if (!which('uv') && spawnSync(path.join(local, 'uv'), ['--version'], { stdio: 'ignore' }).status !== 0) {
16
- const ok = process.platform === 'win32'
17
- ? sh('powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"')
18
- : sh('curl -LsSf https://astral.sh/uv/install.sh | sh');
19
- if (!ok) { console.log('hum: could not install uv; install it from https://astral.sh/uv then run: uv tool install hum-cli'); process.exit(0); }
20
- }
21
- if (!sh('uv tool install --quiet hum-cli')) {
22
- console.log('hum: engine install failed; run manually: uv tool install hum-cli');
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`);
23
58
  }