@metaphi-ai/hum 0.2.9 → 0.2.11

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/hum.js CHANGED
@@ -1,7 +1,11 @@
1
1
  #!/usr/bin/env node
2
- // `hum` from npm: the Ink front-end, which starts Hum's core (Python, installed by postinstall
3
- // via uv) and talks to it over localhost. Everything the person sees is here; everything the
4
- // session records is in the core.
2
+ // `hum` from npm: the launcher. It starts the newest release installed under ~/.hum/versions
3
+ // (a session installs a newer one there in the background while it runs) and, when none is newer
4
+ // than this package, this package's own front-end, which starts Hum's core (Python) and talks to
5
+ // it over localhost. Everything the person sees is there; everything the session records is in
6
+ // the core. This file is what npm put on PATH and it changes only when npm reinstalls it: keep
7
+ // it small, and keep it running whatever the versions directory holds.
8
+ import fs from 'node:fs';
5
9
  import { fileURLToPath, pathToFileURL } from 'node:url';
6
10
  import path from 'node:path';
7
11
 
@@ -15,4 +19,11 @@ if (Number(process.versions.node.split('.')[0]) < NODE_FLOOR) {
15
19
 
16
20
  const here = path.dirname(fileURLToPath(import.meta.url));
17
21
  // a URL, not a path: on Windows `C:\…` is not something the ESM loader will import
18
- await import(pathToFileURL(path.join(here, '..', 'dist', 'cli.mjs')).href);
22
+ const asUrl = (p) => pathToFileURL(p).href;
23
+ let entry = path.join(here, '..', 'dist', 'cli.mjs');
24
+ try {
25
+ const own = JSON.parse(fs.readFileSync(path.join(here, '..', 'package.json'), 'utf8')).version;
26
+ const { chosenEntry } = await import(asUrl(path.join(here, '..', 'dist', 'install.mjs')));
27
+ entry = chosenEntry(own) || entry;
28
+ } catch { /* this package's own */ }
29
+ await import(asUrl(entry));
package/dist/cli.mjs CHANGED
@@ -88432,20 +88432,29 @@ import { spawnSync } from "node:child_process";
88432
88432
  import fs3 from "node:fs";
88433
88433
  import os3 from "node:os";
88434
88434
  import path from "node:path";
88435
- function importedVersion(env3 = process.env, runImpl = run) {
88436
- const r = runImpl(corePython(env3), ASK_VERSION);
88435
+ function readyVersions(env3 = process.env) {
88436
+ let names = [];
88437
+ try {
88438
+ names = fs3.readdirSync(versionsDir(env3));
88439
+ } catch {
88440
+ return [];
88441
+ }
88442
+ return names.filter((v) => isVersion(v) && fs3.existsSync(readyFile(env3, v))).sort((a, b) => newer(a, b) ? -1 : newer(b, a) ? 1 : 0);
88443
+ }
88444
+ function importedVersion(env3 = process.env, version = MINE, runImpl = run) {
88445
+ const r = runImpl(corePython(env3, version), ASK_VERSION);
88437
88446
  return r && r.status === 0 ? String(r.stdout).trim() : null;
88438
88447
  }
88439
- function installedVersion(env3 = process.env, runImpl = run) {
88448
+ function installedVersion(env3 = process.env, version = MINE, runImpl = run) {
88440
88449
  try {
88441
- return fs3.readFileSync(stampPath(env3), "utf8").trim() || null;
88450
+ return fs3.readFileSync(stampPath(env3, version), "utf8").trim() || null;
88442
88451
  } catch {
88443
88452
  }
88444
- if (!fs3.existsSync(corePython(env3))) return null;
88445
- const v = importedVersion(env3, runImpl);
88453
+ if (!fs3.existsSync(corePython(env3, version))) return null;
88454
+ const v = importedVersion(env3, version, runImpl);
88446
88455
  if (v) {
88447
88456
  try {
88448
- fs3.writeFileSync(stampPath(env3), `${v}
88457
+ fs3.writeFileSync(stampPath(env3, version), `${v}
88449
88458
  `);
88450
88459
  } catch {
88451
88460
  }
@@ -88458,22 +88467,21 @@ function trustedPython(env3 = process.env, runImpl = run) {
88458
88467
  const p = r && r.status === 0 ? String(r.stdout).trim().split(/\r?\n/).pop() : "";
88459
88468
  return /^[a-z]:\\.+\\python(w)?\.exe$/i.test(p) ? p : null;
88460
88469
  }
88461
- function sslBlocked(env3 = process.env, runImpl = run) {
88462
- const r = runImpl(corePython(env3), ASK_SSL);
88470
+ function sslBlocked(env3 = process.env, version = MINE, runImpl = run) {
88471
+ const r = runImpl(corePython(env3, version), ASK_SSL);
88463
88472
  if (r && r.status === 0) return null;
88464
88473
  const text = String(r && (r.stderr || r.stdout) || "ssl did not import").trim().split(/\r?\n/).filter(Boolean).pop() || "ssl did not import";
88465
88474
  return text;
88466
88475
  }
88467
- function needsInstall(version, { env: env3 = process.env, installed = () => installedVersion(env3), now = Date.now(), state = readInstallState(env3) } = {}) {
88476
+ function needsInstall(version, { env: env3 = process.env, installed = () => installedVersion(env3, version), now = Date.now(), state = readInstallState(env3) } = {}) {
88468
88477
  if (!version || env3.HUM_CORE || env3.HUM_CORE_PORT) return false;
88469
88478
  if (installed() === version) return false;
88470
88479
  if (state && !state.ok && state.version === version && now - (state.at || 0) < 10 * 60 * 1e3) return false;
88471
88480
  return true;
88472
88481
  }
88473
88482
  function installCore(version, { env: env3 = process.env, say = (m) => console.log(`hum: ${m}`), runImpl = run, now = Date.now() } = {}) {
88474
- const home = humHome(env3);
88475
- const dir = coreDir(env3);
88476
- const python = corePython(env3);
88483
+ const dir = coreDir(env3, version);
88484
+ const python = corePython(env3, version);
88477
88485
  const localBin = path.join(os3.homedir(), ".local", "bin");
88478
88486
  const uvNames = ["uv", path.join(localBin, win ? "uv.exe" : "uv")];
88479
88487
  const uv = () => uvNames.find((u) => {
@@ -88494,9 +88502,9 @@ function installCore(version, { env: env3 = process.env, say = (m) => console.lo
88494
88502
  if (!u) return outcome(false, `could not install uv; install it from https://astral.sh/uv and run: ${npmFix(version)}`);
88495
88503
  }
88496
88504
  say(`installing ${env3.HUM_CORE_SPEC ? spec : `hum ${version}`} into ${dir} \u2026`);
88497
- fs3.mkdirSync(home, { recursive: true });
88505
+ fs3.mkdirSync(path.dirname(dir), { recursive: true });
88498
88506
  try {
88499
- fs3.unlinkSync(stampPath(env3));
88507
+ fs3.unlinkSync(stampPath(env3, version));
88500
88508
  } catch {
88501
88509
  }
88502
88510
  const trusted = trustedPython(env3, runImpl);
@@ -88504,20 +88512,20 @@ function installCore(version, { env: env3 = process.env, say = (m) => console.lo
88504
88512
  const pipIn = () => (runImpl(u, ["pip", "install", "--quiet", "--python", python, spec], { stdio: "inherit" }) || {}).status === 0;
88505
88513
  if (!fs3.existsSync(python) && !makeVenv(fs3.existsSync(dir))) return outcome(false, "uv venv failed");
88506
88514
  if (!pipIn()) return outcome(false, `uv pip install ${spec} failed`);
88507
- let got = importedVersion(env3, runImpl);
88515
+ let got = importedVersion(env3, version, runImpl);
88508
88516
  if (!got) return outcome(false, "hum did not import after the install");
88509
88517
  if (got !== version && !env3.HUM_CORE_SPEC) return outcome(false, `installed ${got}, wanted ${version}`, got);
88510
- let blocked = sslBlocked(env3, runImpl);
88518
+ let blocked = sslBlocked(env3, version, runImpl);
88511
88519
  if (blocked && trusted) {
88512
88520
  say(`the policy here refuses the environment's OpenSSL (${blocked}); rebuilding it on ${trusted} \u2026`);
88513
88521
  if (makeVenv(true) && pipIn()) {
88514
- got = importedVersion(env3, runImpl) || got;
88515
- blocked = sslBlocked(env3, runImpl);
88522
+ got = importedVersion(env3, version, runImpl) || got;
88523
+ blocked = sslBlocked(env3, version, runImpl);
88516
88524
  }
88517
88525
  }
88518
88526
  if (blocked) return outcome(false, sslAdvice(env3, version));
88519
88527
  fs3.mkdirSync(dir, { recursive: true });
88520
- fs3.writeFileSync(stampPath(env3), `${got}
88528
+ fs3.writeFileSync(stampPath(env3, version), `${got}
88521
88529
  `);
88522
88530
  const list2 = runImpl(u, ["tool", "list"]);
88523
88531
  if (list2 && list2.status === 0 && /^hum-cli\b/m.test(list2.stdout)) {
@@ -88530,21 +88538,118 @@ function installCore(version, { env: env3 = process.env, say = (m) => console.lo
88530
88538
  return outcome(false, e && e.message ? e.message : String(e));
88531
88539
  }
88532
88540
  }
88533
- var win, npmFix, NPM_FIX, humHome, coreDir, corePython, stampPath, statePath, run, ASK_VERSION, ASK_SSL, sslAdvice, readInstallState, writeInstallState;
88541
+ function installPackage(version, { env: env3 = process.env, say = (m) => console.log(`hum: ${m}`), runImpl = run } = {}) {
88542
+ const dest = pkgDir(env3, version);
88543
+ const stage = path.join(versionDir(env3, version), "npm");
88544
+ say(`fetching hum ${version} \u2026`);
88545
+ fs3.rmSync(stage, { recursive: true, force: true });
88546
+ fs3.mkdirSync(stage, { recursive: true });
88547
+ const r = runImpl(win ? "npm.cmd" : "npm", [
88548
+ "install",
88549
+ "--prefix",
88550
+ stage,
88551
+ "--ignore-scripts",
88552
+ "--no-audit",
88553
+ "--no-fund",
88554
+ "--no-package-lock",
88555
+ "--loglevel=error",
88556
+ `@metaphi-ai/hum@${version}`
88557
+ ], { shell: win, env: { ...env3, npm_config_update_notifier: "false" } });
88558
+ const got = path.join(stage, "node_modules", "@metaphi-ai", "hum");
88559
+ if (!r || r.status !== 0 || !fs3.existsSync(path.join(got, "dist", "cli.mjs"))) {
88560
+ const why = String(r && (r.stderr || r.stdout) || "").trim().split(/\r?\n/).filter(Boolean).pop() || `npm exited ${r ? r.status : "without running"}`;
88561
+ fs3.rmSync(stage, { recursive: true, force: true });
88562
+ return { ok: false, error: `npm could not fetch @metaphi-ai/hum@${version}: ${why}` };
88563
+ }
88564
+ fs3.rmSync(dest, { recursive: true, force: true });
88565
+ fs3.renameSync(got, dest);
88566
+ fs3.rmSync(stage, { recursive: true, force: true });
88567
+ return { ok: true, error: null };
88568
+ }
88569
+ function prune(env3 = process.env, { keep = MINE, say = () => {
88570
+ }, now = Date.now() } = {}) {
88571
+ let names = [];
88572
+ try {
88573
+ names = fs3.readdirSync(versionsDir(env3)).filter(isVersion);
88574
+ } catch {
88575
+ return [];
88576
+ }
88577
+ const ready = readyVersions(env3);
88578
+ const stay = /* @__PURE__ */ new Set([keep, ...ready.slice(0, 3)]);
88579
+ const gone = [];
88580
+ for (const v of names) {
88581
+ if (stay.has(v)) continue;
88582
+ if (!ready.includes(v)) {
88583
+ let at = 0;
88584
+ try {
88585
+ at = fs3.statSync(versionDir(env3, v)).mtimeMs;
88586
+ } catch {
88587
+ }
88588
+ if (now - at < 30 * 60 * 1e3) continue;
88589
+ }
88590
+ try {
88591
+ fs3.rmSync(versionDir(env3, v), { recursive: true, force: true });
88592
+ gone.push(v);
88593
+ say(`removed hum ${v}`);
88594
+ } catch {
88595
+ }
88596
+ }
88597
+ return gone.sort((a, b) => newer(a, b) ? -1 : newer(b, a) ? 1 : 0);
88598
+ }
88599
+ function installVersion(version, { env: env3 = process.env, say = (m) => console.log(`hum: ${m}`), runImpl = run, now = Date.now() } = {}) {
88600
+ if (!isVersion(version)) return { ok: false, error: `not a version: ${version}` };
88601
+ try {
88602
+ fs3.mkdirSync(versionDir(env3, version), { recursive: true });
88603
+ try {
88604
+ fs3.unlinkSync(readyFile(env3, version));
88605
+ } catch {
88606
+ }
88607
+ if (!fs3.existsSync(entryFile(env3, version))) {
88608
+ const p = installPackage(version, { env: env3, say, runImpl });
88609
+ if (!p.ok) return p;
88610
+ }
88611
+ if (installedVersion(env3, version, runImpl) !== version) {
88612
+ const c = installCore(version, { env: env3, say, runImpl, now });
88613
+ if (!c.ok) return { ok: false, error: c.error };
88614
+ }
88615
+ fs3.writeFileSync(readyFile(env3, version), `${version}
88616
+ `);
88617
+ say(`hum ${version} is ready; the next hum you start is ${version}`);
88618
+ prune(env3, { say });
88619
+ return { ok: true, error: null };
88620
+ } catch (e) {
88621
+ return { ok: false, error: e && e.message ? e.message : String(e) };
88622
+ }
88623
+ }
88624
+ var win, MINE, npmFix, NPM_FIX, humHome, versionsDir, versionDir, pkgDir, coreDir, corePython, readyFile, entryFile, stampPath, statePath, isVersion, newer, run, ASK_VERSION, ASK_SSL, sslAdvice, readInstallState, writeInstallState;
88534
88625
  var init_install = __esm({
88535
88626
  "src/install.js"() {
88536
88627
  win = process.platform === "win32";
88628
+ MINE = true ? "0.2.11" : "";
88537
88629
  npmFix = (version = "") => `npm i -g @metaphi-ai/hum@${version || "latest"} --foreground-scripts`;
88538
88630
  NPM_FIX = npmFix("latest");
88539
88631
  humHome = (env3 = process.env) => env3.HUM_HOME || path.join(os3.homedir(), ".hum");
88540
- coreDir = (env3 = process.env) => path.join(humHome(env3), "core");
88541
- corePython = (env3 = process.env) => win ? path.join(coreDir(env3), "Scripts", "python.exe") : path.join(coreDir(env3), "bin", "python");
88542
- stampPath = (env3) => path.join(coreDir(env3), "version");
88632
+ versionsDir = (env3 = process.env) => path.join(humHome(env3), "versions");
88633
+ versionDir = (env3 = process.env, version = MINE) => path.join(versionsDir(env3), version);
88634
+ pkgDir = (env3 = process.env, version = MINE) => path.join(versionDir(env3, version), "pkg");
88635
+ coreDir = (env3 = process.env, version = MINE) => path.join(versionDir(env3, version), "core");
88636
+ corePython = (env3 = process.env, version = MINE) => win ? path.join(coreDir(env3, version), "Scripts", "python.exe") : path.join(coreDir(env3, version), "bin", "python");
88637
+ readyFile = (env3 = process.env, version = MINE) => path.join(versionDir(env3, version), "ready");
88638
+ entryFile = (env3 = process.env, version = MINE) => path.join(pkgDir(env3, version), "dist", "cli.mjs");
88639
+ stampPath = (env3, version) => path.join(coreDir(env3, version), "version");
88543
88640
  statePath = (env3) => path.join(humHome(env3), "core-install.json");
88641
+ isVersion = (v) => /^\d+\.\d+\.\d+$/.test(v);
88642
+ newer = (a, b) => {
88643
+ const pa = String(a).split(".").map(Number), pb = String(b).split(".").map(Number);
88644
+ for (let i = 0; i < 3; i += 1) {
88645
+ if ((pa[i] || 0) !== (pb[i] || 0)) return (pa[i] || 0) > (pb[i] || 0);
88646
+ }
88647
+ return false;
88648
+ };
88544
88649
  run = (cmd, args2, opts = {}) => spawnSync(cmd, args2, { encoding: "utf8", windowsHide: true, ...opts });
88545
88650
  ASK_VERSION = ["-I", "-c", "import hum; print(hum.__version__)"];
88546
88651
  ASK_SSL = ["-I", "-c", "import ssl"];
88547
- sslAdvice = (env3, version) => `the machine's application control blocked Python's OpenSSL DLL in ${coreDir(env3)}; install Python 3.12 from python.org (Microsoft signs it), then run ${npmFix(version)}`;
88652
+ sslAdvice = (env3, version) => `the machine's application control blocked Python's OpenSSL DLL in ${coreDir(env3, version)}; install Python 3.12 from python.org (Microsoft signs it), then run ${npmFix(version)}`;
88548
88653
  readInstallState = (env3 = process.env) => {
88549
88654
  try {
88550
88655
  return JSON.parse(fs3.readFileSync(statePath(env3), "utf8"));
@@ -88559,6 +88664,24 @@ var init_install = __esm({
88559
88664
  } catch {
88560
88665
  }
88561
88666
  };
88667
+ if (process.argv[1] && /install\.mjs$/.test(process.argv[1]) && process.argv[2] === "install") {
88668
+ const log = path.join(humHome(), "update.log");
88669
+ const say = (m) => {
88670
+ try {
88671
+ fs3.appendFileSync(log, `${(/* @__PURE__ */ new Date()).toISOString()} ${m}
88672
+ `);
88673
+ } catch {
88674
+ }
88675
+ };
88676
+ const runLogged = (cmd, args2, opts = {}) => {
88677
+ const r2 = run(cmd, args2, { ...opts, stdio: "pipe" });
88678
+ if (r2 && r2.status !== 0 && (r2.stderr || r2.stdout)) say(String(r2.stderr || r2.stdout).trim().split(/\r?\n/).slice(-5).join("\n"));
88679
+ return r2;
88680
+ };
88681
+ const r = installVersion(process.argv[3] || "", { say, runImpl: runLogged });
88682
+ if (!r.ok) say(`hum ${process.argv[3]} did not install: ${r.error}`);
88683
+ process.exit(r.ok ? 0 : 1);
88684
+ }
88562
88685
  }
88563
88686
  });
88564
88687
 
@@ -88566,6 +88689,7 @@ var init_install = __esm({
88566
88689
  import fs4 from "node:fs";
88567
88690
  import os4 from "node:os";
88568
88691
  import path2 from "node:path";
88692
+ import { fileURLToPath } from "node:url";
88569
88693
  import { spawn, spawnSync as spawnSync2 } from "node:child_process";
88570
88694
  async function latestVersion(fetchImpl = globalThis.fetch, env3 = process.env) {
88571
88695
  if (env3.HUM_NO_UPDATE_CHECK || typeof fetchImpl !== "function") return "";
@@ -88588,20 +88712,9 @@ function installLayout(execPath = process.execPath, env3 = process.env) {
88588
88712
  }
88589
88713
  return { kind: "native", exe, dir: path2.dirname(real), base: path2.dirname(path2.dirname(real)) };
88590
88714
  }
88591
- function packageVersion(entry = process.argv[1]) {
88592
- try {
88593
- const dir = path2.dirname(fs4.realpathSync(entry));
88594
- return JSON.parse(fs4.readFileSync(path2.join(dir, "..", "package.json"), "utf8")).version || "";
88595
- } catch {
88596
- return "";
88597
- }
88598
- }
88599
- function installerArgv(version, platform2 = process.platform, layout2 = { kind: "native" }, log = "") {
88715
+ function installerArgv(version, platform2 = process.platform, layout2 = { kind: "native" }, log = "", node = process.execPath) {
88600
88716
  const base = dist();
88601
- if (layout2.kind === "npm") {
88602
- const cmd = `npm i -g --ignore-scripts @metaphi-ai/hum@${version || "latest"}`;
88603
- return platform2 === "win32" ? ["cmd.exe", ["/d", "/s", "/c", `"${cmd}${log ? ` >> "${log}" 2>&1` : ""}"`], { windowsVerbatimArguments: true }] : ["/bin/sh", ["-c", cmd], {}];
88604
- }
88717
+ if (layout2.kind === "npm") return [node, [installerScript(), "install", version], {}];
88605
88718
  if (platform2 === "win32") {
88606
88719
  const cmd = `irm ${base}/install.ps1 | iex`;
88607
88720
  return ["powershell", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", log ? `& { ${cmd} } *>> '${log}'` : cmd], {}];
@@ -88613,6 +88726,7 @@ function startBackgroundUpdate(version, { env: env3 = process.env, now = Date.no
88613
88726
  const s = readState(env3);
88614
88727
  if (s.version === version && now - (s.started || 0) < 30 * 60 * 1e3) return false;
88615
88728
  const hidden = platform2 === "win32";
88729
+ const ownLog = layout2.kind === "npm";
88616
88730
  const log = path2.join(humHome2(env3), "update.log");
88617
88731
  let out = "ignore";
88618
88732
  let logged = false;
@@ -88620,15 +88734,15 @@ function startBackgroundUpdate(version, { env: env3 = process.env, now = Date.no
88620
88734
  fs4.mkdirSync(humHome2(env3), { recursive: true });
88621
88735
  out = fs4.openSync(log, "w");
88622
88736
  logged = true;
88623
- if (hidden) {
88737
+ if (hidden || ownLog) {
88624
88738
  fs4.closeSync(out);
88625
88739
  out = "ignore";
88626
88740
  }
88627
88741
  } catch {
88628
88742
  out = "ignore";
88629
88743
  }
88630
- const [bin, args2, extra] = installerArgv(version, platform2, layout2, hidden && logged ? log : "");
88631
- const io = hidden ? { stdio: "ignore", windowsHide: true } : { detached: true, stdio: ["ignore", out, out], windowsHide: true };
88744
+ const [bin, args2, extra] = installerArgv(version, platform2, layout2, hidden && logged && !ownLog ? log : "");
88745
+ const io = hidden ? { stdio: "ignore", windowsHide: true } : { detached: true, stdio: ownLog ? "ignore" : ["ignore", out, out], windowsHide: true };
88632
88746
  try {
88633
88747
  const child = spawnImpl(bin, args2, { ...io, ...extra, env: { ...env3, HUM_VERSION: version, HUM_DIST: dist(env3) } });
88634
88748
  child.unref();
@@ -88651,7 +88765,7 @@ function updateNotice(mine, { running = "", latest = "", env: env3 = process.env
88651
88765
  if (drift) return drift;
88652
88766
  if (env3.HUM_NO_UPDATE_CHECK || !mine || !latest || !newer(latest, mine)) return "";
88653
88767
  if (env3.HUM_NO_AUTO_UPDATE || layout2.kind === "python") return `hum ${latest} is out \xB7 ${updateCommand(layout2)}`;
88654
- if (readyVersion(layout2, latest)) return `hum ${latest} is ready \xB7 restart hum to use it`;
88768
+ if (readyVersion(layout2, latest, env3)) return `hum ${latest} is ready \xB7 restart hum to use it`;
88655
88769
  if (startBackgroundUpdate(latest, { env: env3, spawnImpl, layout: layout2, now, platform: platform2 })) return `hum ${latest} is installing in the background \xB7 it takes effect the next time you start hum`;
88656
88770
  if (readState(env3).version === latest) return `hum ${latest} is still installing in the background (${path2.join(humHome2(env3), "update.log")} has the details)`;
88657
88771
  return "";
@@ -88673,45 +88787,54 @@ function watchReady(mine, latest, onReady, { env: env3 = process.env, layout: la
88673
88787
  if (t.unref) t.unref();
88674
88788
  return () => clearInterval(t);
88675
88789
  }
88676
- function updateNow(layout2 = installLayout()) {
88790
+ async function updateNow(layout2 = installLayout(), { mine = MINE, latest = latestVersion, install = installVersion, write = (m) => process.stdout.write(m) } = {}) {
88677
88791
  if (layout2.kind === "native") {
88678
88792
  const [bin, args2] = installerArgv();
88679
88793
  return spawnSync2(bin, args2, { stdio: "inherit", env: { ...process.env, HUM_DIST: dist() } }).status ?? 1;
88680
88794
  }
88681
88795
  if (layout2.kind === "python") {
88682
88796
  const r2 = spawnSync2(win2() ? "uv.exe" : "uv", ["tool", "upgrade", "hum-cli"], { stdio: "inherit", windowsHide: true });
88683
- if (r2.error || r2.status !== 0) process.stdout.write(`update with your package manager: ${updateCommand(layout2)}
88797
+ if (r2.error || r2.status !== 0) write(`update with your package manager: ${updateCommand(layout2)}
88684
88798
  `);
88685
88799
  return r2.error ? 1 : r2.status ?? 1;
88686
88800
  }
88687
- const r = spawnSync2(win2() ? "npm.cmd" : "npm", ["i", "-g", "@metaphi-ai/hum@latest"], { stdio: "inherit", shell: win2() });
88688
- return r.status ?? 1;
88801
+ const v = await latest();
88802
+ if (!v) {
88803
+ write(`could not read the latest release from ${dist()}
88804
+ `);
88805
+ return 1;
88806
+ }
88807
+ if (mine && !newer(v, mine)) {
88808
+ write(`hum ${mine} is the latest
88809
+ `);
88810
+ return 0;
88811
+ }
88812
+ const r = install(v, { say: (m) => write(`hum: ${m}
88813
+ `) });
88814
+ if (!r.ok) write(`hum: ${r.error}
88815
+ `);
88816
+ return r.ok ? 0 : 1;
88689
88817
  }
88690
- var dist, humHome2, win2, newer, updateCommand, readyVersion, stateFile, readState, writeState;
88818
+ var dist, humHome2, win2, updateCommand, readyVersion, installerScript, stateFile, readState, writeState;
88691
88819
  var init_update = __esm({
88692
88820
  "src/update.js"() {
88821
+ init_install();
88693
88822
  init_install();
88694
88823
  dist = (env3 = process.env) => (env3.HUM_DIST || "https://humboldt.metaphi.ai").replace(/\/+$/, "");
88695
88824
  humHome2 = (env3 = process.env) => env3.HUM_HOME || path2.join(os4.homedir(), ".hum");
88696
88825
  win2 = () => process.platform === "win32";
88697
- newer = (a, b) => {
88698
- const pa = String(a).split(".").map(Number), pb = String(b).split(".").map(Number);
88699
- for (let i = 0; i < 3; i += 1) {
88700
- if ((pa[i] || 0) !== (pb[i] || 0)) return (pa[i] || 0) > (pb[i] || 0);
88701
- }
88702
- return false;
88703
- };
88704
88826
  updateCommand = (layout2 = installLayout(), platform2 = process.platform) => {
88705
88827
  if (layout2.kind === "native") return platform2 === "win32" ? `irm ${dist()}/install.ps1 | iex` : `curl -fsSL ${dist()}/install.sh | sh`;
88706
88828
  if (layout2.kind === "python") return "uv tool upgrade hum-cli (or: pip install -U hum-cli)";
88707
88829
  return "npm i -g @metaphi-ai/hum@latest";
88708
88830
  };
88709
- readyVersion = (layout2, version, pkg = packageVersion) => {
88831
+ readyVersion = (layout2, version, env3 = process.env) => {
88710
88832
  if (!version) return "";
88711
88833
  if (layout2.kind === "native") return fs4.existsSync(path2.join(layout2.base, version, layout2.exe)) ? version : "";
88712
- if (layout2.kind === "npm") return pkg() === version ? version : "";
88834
+ if (layout2.kind === "npm") return fs4.existsSync(readyFile(env3, version)) ? version : "";
88713
88835
  return "";
88714
88836
  };
88837
+ installerScript = () => fileURLToPath(new URL("./install.mjs", import.meta.url));
88715
88838
  stateFile = (env3 = process.env) => path2.join(humHome2(env3), "update.json");
88716
88839
  readState = (env3 = process.env) => {
88717
88840
  try {
@@ -88774,12 +88897,12 @@ function siblingCore(execPath = process.execPath) {
88774
88897
  const p = path3.join(dir, "core", win3 ? "hum-core.exe" : "hum-core");
88775
88898
  return exists(p) ? p : null;
88776
88899
  }
88777
- function homeCore(env3 = process.env) {
88778
- const home = env3.HUM_HOME || path3.join(os5.homedir(), ".hum");
88779
- const py = win3 ? path3.join(home, "core", "Scripts", "python.exe") : path3.join(home, "core", "bin", "python");
88900
+ function homeCore(env3 = process.env, version = MINE) {
88901
+ if (!version) return null;
88902
+ const py = corePython(env3, version);
88780
88903
  return exists(py) ? py : null;
88781
88904
  }
88782
- function coreCandidates(env3 = process.env) {
88905
+ function coreCandidates(env3 = process.env, version = MINE) {
88783
88906
  const out = [];
88784
88907
  if (env3.HUM_CORE) {
88785
88908
  const [bin, ...args2] = env3.HUM_CORE.split(" ");
@@ -88787,8 +88910,8 @@ function coreCandidates(env3 = process.env) {
88787
88910
  }
88788
88911
  const sib = siblingCore();
88789
88912
  if (sib) out.push({ bin: sib, args: ["core"], where: "beside hum" });
88790
- const home = homeCore(env3);
88791
- if (home) out.push({ bin: home, args: ["-m", "hum.cli", "core"], where: "HUM_HOME/core" });
88913
+ const home = homeCore(env3, version);
88914
+ if (home) out.push({ bin: home, args: ["-m", "hum.cli", "core"], where: "HUM_HOME/versions" });
88792
88915
  const py = corePython2();
88793
88916
  if (py) out.push({ bin: py, args: ["-m", "hum.cli", "core"], where: "the interpreter beside it" });
88794
88917
  out.push({ bin: "hum-core", args: [], where: "PATH" });
@@ -88805,9 +88928,9 @@ function coreCommand(argv, candidates = coreCandidates()) {
88805
88928
  }
88806
88929
  return null;
88807
88930
  }
88808
- function candidateVersion(c, { env: env3 = process.env, runImpl = spawnSync3 } = {}) {
88931
+ function candidateVersion(c, { env: env3 = process.env, runImpl = spawnSync3, version = MINE } = {}) {
88809
88932
  if (!c || !c.bin) return null;
88810
- if (c.where === "HUM_HOME/core") return installedVersion(env3);
88933
+ if (c.where === "HUM_HOME/versions") return installedVersion(env3, version);
88811
88934
  const interp = c.args.length >= 3 && c.args[c.args.length - 3] === "-m" && c.args[c.args.length - 2] === "hum.cli";
88812
88935
  let r;
88813
88936
  try {
@@ -88832,10 +88955,10 @@ function clientEnv() {
88832
88955
  platform: process.platform,
88833
88956
  arch: process.arch,
88834
88957
  terminal: process.env.TERM_PROGRAM || (process.env.WT_SESSION ? "Windows Terminal" : process.env.TERM || ""),
88835
- tui: true ? "0.2.9" : ""
88958
+ tui: true ? "0.2.11" : ""
88836
88959
  };
88837
88960
  }
88838
- function startFailure(failures, { npm = /^(node|bun)(\.exe)?$/i.test(String(process.execPath).split(/[\\/]/).pop()), platform: platform2 = process.platform, version = true ? "0.2.9" : "" } = {}) {
88961
+ function startFailure(failures, { npm = /^(node|bun)(\.exe)?$/i.test(String(process.execPath).split(/[\\/]/).pop()), platform: platform2 = process.platform, version = true ? "0.2.11" : "" } = {}) {
88839
88962
  const refused = failures.find((f) => f.error && f.error.code !== "ENOENT");
88840
88963
  const fix = npm ? npmFix(version) : updateCommand({ kind: "native" }, platform2);
88841
88964
  if (!refused) {
@@ -89658,10 +89781,12 @@ async function diagnose({
89658
89781
  if (env3.HUM_CORE && running && running.version === mine) checks.push(check2("warn", `HUM_CORE is set (${env3.HUM_CORE}): it decides what runs, ahead of any install`, "unset HUM_CORE"));
89659
89782
  if (layout2.kind === "npm") {
89660
89783
  const st = readInstallState(env3);
89661
- const stamp = installedVersion(env3, runImpl);
89784
+ const stamp = installedVersion(env3, mine, runImpl);
89662
89785
  if (st && !st.ok) checks.push(check2(stamp === mine ? "warn" : "fail", `the last install attempt (wanted ${st.wanted || st.version}) did not finish: ${st.error}`, npmFix2(mine)));
89663
- else if (!stamp) checks.push(check2("fail", `nothing installed under ${coreDir(env3)}`, npmFix2(mine)));
89664
- else checks.push(check2(stamp === mine ? "ok" : "warn", `${coreDir(env3)} holds ${stamp}`, stamp === mine ? "" : npmFix2(mine)));
89786
+ else if (!stamp) checks.push(check2("fail", `nothing installed under ${coreDir(env3, mine)}`, npmFix2(mine)));
89787
+ else checks.push(check2(stamp === mine ? "ok" : "warn", `${coreDir(env3, mine)} holds ${stamp}`, stamp === mine ? "" : npmFix2(mine)));
89788
+ const ready = readyVersions(env3);
89789
+ if (ready.length) checks.push(check2("ok", `installed here: ${ready.join(", ")} (the newest starts next)`));
89665
89790
  }
89666
89791
  if (layout2.kind === "native") {
89667
89792
  const sib = path6.join(layout2.dir, "core", isWin(platform2) ? "hum-core.exe" : "hum-core");
@@ -89686,7 +89811,7 @@ async function diagnose({
89686
89811
  else if (latest) checks.push(check2("ok", `${mine} is the latest release`));
89687
89812
  sections.push({ key: "updates", title: "Updates", checks });
89688
89813
  }
89689
- const interp = running && running.args.length >= 3 && running.args[running.args.length - 2] === "hum.cli" ? running.bin : exists2(corePython(env3)) ? corePython(env3) : null;
89814
+ const interp = running && running.args.length >= 3 && running.args[running.args.length - 2] === "hum.cli" ? running.bin : exists2(corePython(env3, mine)) ? corePython(env3, mine) : null;
89690
89815
  let toolServers = null;
89691
89816
  {
89692
89817
  const checks = [];
@@ -90291,7 +90416,7 @@ var input_default = MultilineInput;
90291
90416
 
90292
90417
  // src/app.jsx
90293
90418
  var import_jsx_runtime2 = __toESM(require_jsx_runtime(), 1);
90294
- var MINE = true ? "0.2.9" : "";
90419
+ var MINE2 = true ? "0.2.11" : "";
90295
90420
  var fmtDur = (s) => s < 60 ? `${Math.max(1, Math.round(s))}s` : `${Math.floor(s / 60)}m ${Math.round(s % 60)}s`;
90296
90421
  var plural = (n, w) => `${n} ${w}${n === 1 ? "" : "s"}`;
90297
90422
  var firstResp = (e) => e.first_response_s ? ` \xB7 first response ${e.first_response_s.toFixed(1)}s` : "";
@@ -90679,7 +90804,7 @@ function App2({ core: core2, task, resume, images = [], peek = peekSession, upda
90679
90804
  (ev.questions || []).forEach(addAsk);
90680
90805
  if (ev.fresh) append({ kind: "wordmark" });
90681
90806
  setLatest({ version: ev.version || "", latest_version: ev.latest_version || "", release_notes_url: ev.release_notes_url || "" });
90682
- append({ kind: "banner", lines: bannerLines(ev, homeRel, MINE), instruction: ev.session ? ev.instruction : null });
90807
+ append({ kind: "banner", lines: bannerLines(ev, homeRel, MINE2), instruction: ev.session ? ev.instruction : null });
90683
90808
  if (ev.note) append({ kind: "warn", text: ev.note });
90684
90809
  setGoal(ev.goal_state && ev.goal_state.text ? ev.goal_state : ev.goal ? { text: ev.goal, status: "active", exchanges: 0 } : null);
90685
90810
  if (ev.goal_state && ev.goal_state.text) append({ kind: "info", text: goalLine(ev.goal_state) });
@@ -91053,7 +91178,7 @@ function App2({ core: core2, task, resume, images = [], peek = peekSession, upda
91053
91178
  const noticedRef = (0, import_react36.useRef)("");
91054
91179
  (0, import_react36.useEffect)(() => {
91055
91180
  if (!latest) return void 0;
91056
- const mine = MINE || latest.version || "0.0.0";
91181
+ const mine = MINE2 || latest.version || "0.0.0";
91057
91182
  const want = latest.latest_version || "";
91058
91183
  const line = update(mine, { running: latest.version || "", latest: want });
91059
91184
  if (line && noticedRef.current !== line) {
@@ -92116,33 +92241,33 @@ init_update();
92116
92241
  init_install();
92117
92242
  var import_jsx_runtime4 = __toESM(require_jsx_runtime(), 1);
92118
92243
  import { spawnSync as spawnSync5 } from "node:child_process";
92119
- var MINE2 = true ? "0.2.9" : "0.0.0";
92244
+ var MINE3 = true ? "0.2.11" : "0.0.0";
92120
92245
  var layout = installLayout();
92121
92246
  function completeInstall() {
92122
- if (layout.kind !== "npm" || !needsInstall(MINE2)) return;
92247
+ if (layout.kind !== "npm" || !needsInstall(MINE3)) return;
92123
92248
  const say = (m) => process.stderr.write(`hum: ${m}
92124
92249
  `);
92125
- say(`updating to ${MINE2} \u2026`);
92126
- const r = installCore(MINE2, { say });
92127
- if (!r.ok) say(`the update did not finish: ${r.error} \xB7 ${npmFix(MINE2)}`);
92250
+ say(`updating to ${MINE3} \u2026`);
92251
+ const r = installCore(MINE3, { say });
92252
+ if (!r.ok) say(`the update did not finish: ${r.error} \xB7 ${npmFix(MINE3)}`);
92128
92253
  }
92129
92254
  var CORE_COMMANDS = /* @__PURE__ */ new Set(["run", "login", "logout", "whoami", "model", "key", "sessions", "show", "sync", "skill", "persona", "ps", "stop", "hooks", "core"]);
92130
92255
  var argv0 = process.argv.slice(2);
92131
92256
  if (argv0[0] === "--version" || argv0[0] === "-V") {
92132
- process.stdout.write(`hum ${MINE2}
92257
+ process.stdout.write(`hum ${MINE3}
92133
92258
  `);
92134
92259
  if (layout.kind === "npm") {
92135
92260
  const r = runningVersion();
92136
- const drift = mismatchLine(MINE2, r ? r.version : "", { layout });
92261
+ const drift = mismatchLine(MINE3, r ? r.version : "", { layout });
92137
92262
  if (drift) process.stdout.write(`${drift}
92138
92263
  `);
92139
92264
  }
92140
92265
  process.exit(0);
92141
92266
  }
92142
- if (argv0[0] === "update") process.exit(updateNow());
92267
+ if (argv0[0] === "update") process.exit(await updateNow());
92143
92268
  if (argv0[0] === "doctor") {
92144
92269
  const { doctor: doctor2 } = await Promise.resolve().then(() => (init_doctor(), doctor_exports));
92145
- process.exit(await doctor2({ json: argv0.includes("--json"), mine: MINE2, layout }));
92270
+ process.exit(await doctor2({ json: argv0.includes("--json"), mine: MINE3, layout }));
92146
92271
  }
92147
92272
  if (CORE_COMMANDS.has(argv0[0]) || argv0[0] === "agents" && argv0.includes("--json") || wantsPlain(argv0)) {
92148
92273
  completeInstall();
package/dist/install.mjs CHANGED
@@ -4,29 +4,59 @@ import fs from "node:fs";
4
4
  import os from "node:os";
5
5
  import path from "node:path";
6
6
  var win = process.platform === "win32";
7
+ var MINE = typeof HUM_VERSION === "string" ? HUM_VERSION : "";
7
8
  var npmFix = (version = "") => `npm i -g @metaphi-ai/hum@${version || "latest"} --foreground-scripts`;
8
9
  var NPM_FIX = npmFix("latest");
9
10
  var humHome = (env = process.env) => env.HUM_HOME || path.join(os.homedir(), ".hum");
10
- var coreDir = (env = process.env) => path.join(humHome(env), "core");
11
- var corePython = (env = process.env) => win ? path.join(coreDir(env), "Scripts", "python.exe") : path.join(coreDir(env), "bin", "python");
12
- var stampPath = (env) => path.join(coreDir(env), "version");
11
+ var versionsDir = (env = process.env) => path.join(humHome(env), "versions");
12
+ var versionDir = (env = process.env, version = MINE) => path.join(versionsDir(env), version);
13
+ var pkgDir = (env = process.env, version = MINE) => path.join(versionDir(env, version), "pkg");
14
+ var coreDir = (env = process.env, version = MINE) => path.join(versionDir(env, version), "core");
15
+ var corePython = (env = process.env, version = MINE) => win ? path.join(coreDir(env, version), "Scripts", "python.exe") : path.join(coreDir(env, version), "bin", "python");
16
+ var readyFile = (env = process.env, version = MINE) => path.join(versionDir(env, version), "ready");
17
+ var entryFile = (env = process.env, version = MINE) => path.join(pkgDir(env, version), "dist", "cli.mjs");
18
+ var stampPath = (env, version) => path.join(coreDir(env, version), "version");
13
19
  var statePath = (env) => path.join(humHome(env), "core-install.json");
20
+ var isVersion = (v) => /^\d+\.\d+\.\d+$/.test(v);
21
+ var newer = (a, b) => {
22
+ const pa = String(a).split(".").map(Number), pb = String(b).split(".").map(Number);
23
+ for (let i = 0; i < 3; i += 1) {
24
+ if ((pa[i] || 0) !== (pb[i] || 0)) return (pa[i] || 0) > (pb[i] || 0);
25
+ }
26
+ return false;
27
+ };
28
+ function readyVersions(env = process.env) {
29
+ let names = [];
30
+ try {
31
+ names = fs.readdirSync(versionsDir(env));
32
+ } catch {
33
+ return [];
34
+ }
35
+ return names.filter((v) => isVersion(v) && fs.existsSync(readyFile(env, v))).sort((a, b) => newer(a, b) ? -1 : newer(b, a) ? 1 : 0);
36
+ }
37
+ function chosenEntry(own, env = process.env) {
38
+ for (const v of readyVersions(env)) {
39
+ if (own && !newer(v, own)) break;
40
+ if (fs.existsSync(entryFile(env, v)) && fs.existsSync(corePython(env, v))) return entryFile(env, v);
41
+ }
42
+ return null;
43
+ }
14
44
  var run = (cmd, args, opts = {}) => spawnSync(cmd, args, { encoding: "utf8", windowsHide: true, ...opts });
15
45
  var ASK_VERSION = ["-I", "-c", "import hum; print(hum.__version__)"];
16
- function importedVersion(env = process.env, runImpl = run) {
17
- const r = runImpl(corePython(env), ASK_VERSION);
46
+ function importedVersion(env = process.env, version = MINE, runImpl = run) {
47
+ const r = runImpl(corePython(env, version), ASK_VERSION);
18
48
  return r && r.status === 0 ? String(r.stdout).trim() : null;
19
49
  }
20
- function installedVersion(env = process.env, runImpl = run) {
50
+ function installedVersion(env = process.env, version = MINE, runImpl = run) {
21
51
  try {
22
- return fs.readFileSync(stampPath(env), "utf8").trim() || null;
52
+ return fs.readFileSync(stampPath(env, version), "utf8").trim() || null;
23
53
  } catch {
24
54
  }
25
- if (!fs.existsSync(corePython(env))) return null;
26
- const v = importedVersion(env, runImpl);
55
+ if (!fs.existsSync(corePython(env, version))) return null;
56
+ const v = importedVersion(env, version, runImpl);
27
57
  if (v) {
28
58
  try {
29
- fs.writeFileSync(stampPath(env), `${v}
59
+ fs.writeFileSync(stampPath(env, version), `${v}
30
60
  `);
31
61
  } catch {
32
62
  }
@@ -40,13 +70,13 @@ function trustedPython(env = process.env, runImpl = run) {
40
70
  return /^[a-z]:\\.+\\python(w)?\.exe$/i.test(p) ? p : null;
41
71
  }
42
72
  var ASK_SSL = ["-I", "-c", "import ssl"];
43
- function sslBlocked(env = process.env, runImpl = run) {
44
- const r = runImpl(corePython(env), ASK_SSL);
73
+ function sslBlocked(env = process.env, version = MINE, runImpl = run) {
74
+ const r = runImpl(corePython(env, version), ASK_SSL);
45
75
  if (r && r.status === 0) return null;
46
76
  const text = String(r && (r.stderr || r.stdout) || "ssl did not import").trim().split(/\r?\n/).filter(Boolean).pop() || "ssl did not import";
47
77
  return text;
48
78
  }
49
- var sslAdvice = (env, version) => `the machine's application control blocked Python's OpenSSL DLL in ${coreDir(env)}; install Python 3.12 from python.org (Microsoft signs it), then run ${npmFix(version)}`;
79
+ var sslAdvice = (env, version) => `the machine's application control blocked Python's OpenSSL DLL in ${coreDir(env, version)}; install Python 3.12 from python.org (Microsoft signs it), then run ${npmFix(version)}`;
50
80
  var readInstallState = (env = process.env) => {
51
81
  try {
52
82
  return JSON.parse(fs.readFileSync(statePath(env), "utf8"));
@@ -61,16 +91,15 @@ var writeInstallState = (s, env) => {
61
91
  } catch {
62
92
  }
63
93
  };
64
- function needsInstall(version, { env = process.env, installed = () => installedVersion(env), now = Date.now(), state = readInstallState(env) } = {}) {
94
+ function needsInstall(version, { env = process.env, installed = () => installedVersion(env, version), now = Date.now(), state = readInstallState(env) } = {}) {
65
95
  if (!version || env.HUM_CORE || env.HUM_CORE_PORT) return false;
66
96
  if (installed() === version) return false;
67
97
  if (state && !state.ok && state.version === version && now - (state.at || 0) < 10 * 60 * 1e3) return false;
68
98
  return true;
69
99
  }
70
100
  function installCore(version, { env = process.env, say = (m) => console.log(`hum: ${m}`), runImpl = run, now = Date.now() } = {}) {
71
- const home = humHome(env);
72
- const dir = coreDir(env);
73
- const python = corePython(env);
101
+ const dir = coreDir(env, version);
102
+ const python = corePython(env, version);
74
103
  const localBin = path.join(os.homedir(), ".local", "bin");
75
104
  const uvNames = ["uv", path.join(localBin, win ? "uv.exe" : "uv")];
76
105
  const uv = () => uvNames.find((u) => {
@@ -91,9 +120,9 @@ function installCore(version, { env = process.env, say = (m) => console.log(`hum
91
120
  if (!u) return outcome(false, `could not install uv; install it from https://astral.sh/uv and run: ${npmFix(version)}`);
92
121
  }
93
122
  say(`installing ${env.HUM_CORE_SPEC ? spec : `hum ${version}`} into ${dir} \u2026`);
94
- fs.mkdirSync(home, { recursive: true });
123
+ fs.mkdirSync(path.dirname(dir), { recursive: true });
95
124
  try {
96
- fs.unlinkSync(stampPath(env));
125
+ fs.unlinkSync(stampPath(env, version));
97
126
  } catch {
98
127
  }
99
128
  const trusted = trustedPython(env, runImpl);
@@ -101,20 +130,20 @@ function installCore(version, { env = process.env, say = (m) => console.log(`hum
101
130
  const pipIn = () => (runImpl(u, ["pip", "install", "--quiet", "--python", python, spec], { stdio: "inherit" }) || {}).status === 0;
102
131
  if (!fs.existsSync(python) && !makeVenv(fs.existsSync(dir))) return outcome(false, "uv venv failed");
103
132
  if (!pipIn()) return outcome(false, `uv pip install ${spec} failed`);
104
- let got = importedVersion(env, runImpl);
133
+ let got = importedVersion(env, version, runImpl);
105
134
  if (!got) return outcome(false, "hum did not import after the install");
106
135
  if (got !== version && !env.HUM_CORE_SPEC) return outcome(false, `installed ${got}, wanted ${version}`, got);
107
- let blocked = sslBlocked(env, runImpl);
136
+ let blocked = sslBlocked(env, version, runImpl);
108
137
  if (blocked && trusted) {
109
138
  say(`the policy here refuses the environment's OpenSSL (${blocked}); rebuilding it on ${trusted} \u2026`);
110
139
  if (makeVenv(true) && pipIn()) {
111
- got = importedVersion(env, runImpl) || got;
112
- blocked = sslBlocked(env, runImpl);
140
+ got = importedVersion(env, version, runImpl) || got;
141
+ blocked = sslBlocked(env, version, runImpl);
113
142
  }
114
143
  }
115
144
  if (blocked) return outcome(false, sslAdvice(env, version));
116
145
  fs.mkdirSync(dir, { recursive: true });
117
- fs.writeFileSync(stampPath(env), `${got}
146
+ fs.writeFileSync(stampPath(env, version), `${got}
118
147
  `);
119
148
  const list = runImpl(u, ["tool", "list"]);
120
149
  if (list && list.status === 0 && /^hum-cli\b/m.test(list.stdout)) {
@@ -127,20 +156,133 @@ function installCore(version, { env = process.env, say = (m) => console.log(`hum
127
156
  return outcome(false, e && e.message ? e.message : String(e));
128
157
  }
129
158
  }
159
+ function installPackage(version, { env = process.env, say = (m) => console.log(`hum: ${m}`), runImpl = run } = {}) {
160
+ const dest = pkgDir(env, version);
161
+ const stage = path.join(versionDir(env, version), "npm");
162
+ say(`fetching hum ${version} \u2026`);
163
+ fs.rmSync(stage, { recursive: true, force: true });
164
+ fs.mkdirSync(stage, { recursive: true });
165
+ const r = runImpl(win ? "npm.cmd" : "npm", [
166
+ "install",
167
+ "--prefix",
168
+ stage,
169
+ "--ignore-scripts",
170
+ "--no-audit",
171
+ "--no-fund",
172
+ "--no-package-lock",
173
+ "--loglevel=error",
174
+ `@metaphi-ai/hum@${version}`
175
+ ], { shell: win, env: { ...env, npm_config_update_notifier: "false" } });
176
+ const got = path.join(stage, "node_modules", "@metaphi-ai", "hum");
177
+ if (!r || r.status !== 0 || !fs.existsSync(path.join(got, "dist", "cli.mjs"))) {
178
+ const why = String(r && (r.stderr || r.stdout) || "").trim().split(/\r?\n/).filter(Boolean).pop() || `npm exited ${r ? r.status : "without running"}`;
179
+ fs.rmSync(stage, { recursive: true, force: true });
180
+ return { ok: false, error: `npm could not fetch @metaphi-ai/hum@${version}: ${why}` };
181
+ }
182
+ fs.rmSync(dest, { recursive: true, force: true });
183
+ fs.renameSync(got, dest);
184
+ fs.rmSync(stage, { recursive: true, force: true });
185
+ return { ok: true, error: null };
186
+ }
187
+ function prune(env = process.env, { keep = MINE, say = () => {
188
+ }, now = Date.now() } = {}) {
189
+ let names = [];
190
+ try {
191
+ names = fs.readdirSync(versionsDir(env)).filter(isVersion);
192
+ } catch {
193
+ return [];
194
+ }
195
+ const ready = readyVersions(env);
196
+ const stay = /* @__PURE__ */ new Set([keep, ...ready.slice(0, 3)]);
197
+ const gone = [];
198
+ for (const v of names) {
199
+ if (stay.has(v)) continue;
200
+ if (!ready.includes(v)) {
201
+ let at = 0;
202
+ try {
203
+ at = fs.statSync(versionDir(env, v)).mtimeMs;
204
+ } catch {
205
+ }
206
+ if (now - at < 30 * 60 * 1e3) continue;
207
+ }
208
+ try {
209
+ fs.rmSync(versionDir(env, v), { recursive: true, force: true });
210
+ gone.push(v);
211
+ say(`removed hum ${v}`);
212
+ } catch {
213
+ }
214
+ }
215
+ return gone.sort((a, b) => newer(a, b) ? -1 : newer(b, a) ? 1 : 0);
216
+ }
217
+ function installVersion(version, { env = process.env, say = (m) => console.log(`hum: ${m}`), runImpl = run, now = Date.now() } = {}) {
218
+ if (!isVersion(version)) return { ok: false, error: `not a version: ${version}` };
219
+ try {
220
+ fs.mkdirSync(versionDir(env, version), { recursive: true });
221
+ try {
222
+ fs.unlinkSync(readyFile(env, version));
223
+ } catch {
224
+ }
225
+ if (!fs.existsSync(entryFile(env, version))) {
226
+ const p = installPackage(version, { env, say, runImpl });
227
+ if (!p.ok) return p;
228
+ }
229
+ if (installedVersion(env, version, runImpl) !== version) {
230
+ const c = installCore(version, { env, say, runImpl, now });
231
+ if (!c.ok) return { ok: false, error: c.error };
232
+ }
233
+ fs.writeFileSync(readyFile(env, version), `${version}
234
+ `);
235
+ say(`hum ${version} is ready; the next hum you start is ${version}`);
236
+ prune(env, { say });
237
+ return { ok: true, error: null };
238
+ } catch (e) {
239
+ return { ok: false, error: e && e.message ? e.message : String(e) };
240
+ }
241
+ }
242
+ if (process.argv[1] && /install\.mjs$/.test(process.argv[1]) && process.argv[2] === "install") {
243
+ const log = path.join(humHome(), "update.log");
244
+ const say = (m) => {
245
+ try {
246
+ fs.appendFileSync(log, `${(/* @__PURE__ */ new Date()).toISOString()} ${m}
247
+ `);
248
+ } catch {
249
+ }
250
+ };
251
+ const runLogged = (cmd, args, opts = {}) => {
252
+ const r2 = run(cmd, args, { ...opts, stdio: "pipe" });
253
+ if (r2 && r2.status !== 0 && (r2.stderr || r2.stdout)) say(String(r2.stderr || r2.stdout).trim().split(/\r?\n/).slice(-5).join("\n"));
254
+ return r2;
255
+ };
256
+ const r = installVersion(process.argv[3] || "", { say, runImpl: runLogged });
257
+ if (!r.ok) say(`hum ${process.argv[3]} did not install: ${r.error}`);
258
+ process.exit(r.ok ? 0 : 1);
259
+ }
130
260
  export {
131
261
  ASK_SSL,
132
262
  ASK_VERSION,
263
+ MINE,
133
264
  NPM_FIX,
265
+ chosenEntry,
134
266
  coreDir,
135
267
  corePython,
268
+ entryFile,
136
269
  humHome,
137
270
  importedVersion,
138
271
  installCore,
272
+ installPackage,
273
+ installVersion,
139
274
  installedVersion,
140
275
  needsInstall,
276
+ newer,
141
277
  npmFix,
278
+ pkgDir,
279
+ prune,
142
280
  readInstallState,
281
+ readyFile,
282
+ readyVersions,
143
283
  sslAdvice,
144
284
  sslBlocked,
145
- trustedPython
285
+ trustedPython,
286
+ versionDir,
287
+ versionsDir
146
288
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metaphi-ai/hum",
3
- "version": "0.2.9",
3
+ "version": "0.2.11",
4
4
  "description": "Hum: a self-improving coding agent for your terminal.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -11,7 +11,7 @@ const version = JSON.parse(fs.readFileSync(path.join(here, '..', 'package.json')
11
11
  // a URL, not a path: on Windows `C:\…` is not something the ESM loader will import
12
12
  const { installedVersion, installCore, npmFix } = await import(pathToFileURL(path.join(here, '..', 'dist', 'install.mjs')).href);
13
13
 
14
- if (installedVersion() !== version) {
14
+ if (installedVersion(process.env, version) !== version) {
15
15
  const r = installCore(version);
16
16
  if (!r.ok) console.log(`hum: the install did not finish (${r.error}); run: ${npmFix(version)}`);
17
17
  }