@metaphi-ai/hum 0.2.10 → 0.2.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/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,30 +88502,30 @@ 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);
88503
88511
  const makeVenv = (clear) => (runImpl(u, ["venv", "--quiet", "--python", trusted || "3.12", ...clear ? ["--clear"] : [], dir], { stdio: "inherit" }) || {}).status === 0;
88504
- const pipIn = () => (runImpl(u, ["pip", "install", "--quiet", "--python", python, spec], { stdio: "inherit" }) || {}).status === 0;
88512
+ const pipIn = () => (runImpl(u, ["pip", "install", "--quiet", "--refresh-package", "hum-cli", "--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,127 @@ 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(), retries = 0, every = 90 * 1e3, wait = pause } = {}) {
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
+ const attempt2 = (what, step) => {
88608
+ let r = step();
88609
+ for (let i = 0; !r.ok && i < retries; i += 1) {
88610
+ say(`${what} is not there yet (${r.error}); trying again in ${Math.round(every / 1e3)} s`);
88611
+ wait(every);
88612
+ r = step();
88613
+ }
88614
+ return r;
88615
+ };
88616
+ if (!fs3.existsSync(entryFile(env3, version))) {
88617
+ const p = attempt2("the package", () => installPackage(version, { env: env3, say, runImpl }));
88618
+ if (!p.ok) return p;
88619
+ }
88620
+ if (installedVersion(env3, version, runImpl) !== version) {
88621
+ const c = attempt2("the part of hum that does the work", () => installCore(version, { env: env3, say, runImpl, now }));
88622
+ if (!c.ok) return { ok: false, error: c.error };
88623
+ }
88624
+ fs3.writeFileSync(readyFile(env3, version), `${version}
88625
+ `);
88626
+ say(`hum ${version} is ready; the next hum you start is ${version}`);
88627
+ prune(env3, { say });
88628
+ return { ok: true, error: null };
88629
+ } catch (e) {
88630
+ return { ok: false, error: e && e.message ? e.message : String(e) };
88631
+ }
88632
+ }
88633
+ 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, pause;
88534
88634
  var init_install = __esm({
88535
88635
  "src/install.js"() {
88536
88636
  win = process.platform === "win32";
88637
+ MINE = true ? "0.2.12" : "";
88537
88638
  npmFix = (version = "") => `npm i -g @metaphi-ai/hum@${version || "latest"} --foreground-scripts`;
88538
88639
  NPM_FIX = npmFix("latest");
88539
88640
  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");
88641
+ versionsDir = (env3 = process.env) => path.join(humHome(env3), "versions");
88642
+ versionDir = (env3 = process.env, version = MINE) => path.join(versionsDir(env3), version);
88643
+ pkgDir = (env3 = process.env, version = MINE) => path.join(versionDir(env3, version), "pkg");
88644
+ coreDir = (env3 = process.env, version = MINE) => path.join(versionDir(env3, version), "core");
88645
+ corePython = (env3 = process.env, version = MINE) => win ? path.join(coreDir(env3, version), "Scripts", "python.exe") : path.join(coreDir(env3, version), "bin", "python");
88646
+ readyFile = (env3 = process.env, version = MINE) => path.join(versionDir(env3, version), "ready");
88647
+ entryFile = (env3 = process.env, version = MINE) => path.join(pkgDir(env3, version), "dist", "cli.mjs");
88648
+ stampPath = (env3, version) => path.join(coreDir(env3, version), "version");
88543
88649
  statePath = (env3) => path.join(humHome(env3), "core-install.json");
88650
+ isVersion = (v) => /^\d+\.\d+\.\d+$/.test(v);
88651
+ newer = (a, b) => {
88652
+ const pa = String(a).split(".").map(Number), pb = String(b).split(".").map(Number);
88653
+ for (let i = 0; i < 3; i += 1) {
88654
+ if ((pa[i] || 0) !== (pb[i] || 0)) return (pa[i] || 0) > (pb[i] || 0);
88655
+ }
88656
+ return false;
88657
+ };
88544
88658
  run = (cmd, args2, opts = {}) => spawnSync(cmd, args2, { encoding: "utf8", windowsHide: true, ...opts });
88545
88659
  ASK_VERSION = ["-I", "-c", "import hum; print(hum.__version__)"];
88546
88660
  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)}`;
88661
+ 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
88662
  readInstallState = (env3 = process.env) => {
88549
88663
  try {
88550
88664
  return JSON.parse(fs3.readFileSync(statePath(env3), "utf8"));
@@ -88559,6 +88673,30 @@ var init_install = __esm({
88559
88673
  } catch {
88560
88674
  }
88561
88675
  };
88676
+ pause = (ms) => {
88677
+ try {
88678
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
88679
+ } catch {
88680
+ }
88681
+ };
88682
+ if (process.argv[1] && /install\.mjs$/.test(process.argv[1]) && process.argv[2] === "install") {
88683
+ const log = path.join(humHome(), "update.log");
88684
+ const say = (m) => {
88685
+ try {
88686
+ fs3.appendFileSync(log, `${(/* @__PURE__ */ new Date()).toISOString()} ${m}
88687
+ `);
88688
+ } catch {
88689
+ }
88690
+ };
88691
+ const runLogged = (cmd, args2, opts = {}) => {
88692
+ const r2 = run(cmd, args2, { ...opts, stdio: "pipe" });
88693
+ if (r2 && r2.status !== 0 && (r2.stderr || r2.stdout)) say(String(r2.stderr || r2.stdout).trim().split(/\r?\n/).slice(-5).join("\n"));
88694
+ return r2;
88695
+ };
88696
+ const r = installVersion(process.argv[3] || "", { say, runImpl: runLogged, retries: 3 });
88697
+ if (!r.ok) say(`hum ${process.argv[3]} did not install: ${r.error}`);
88698
+ process.exit(r.ok ? 0 : 1);
88699
+ }
88562
88700
  }
88563
88701
  });
88564
88702
 
@@ -88566,6 +88704,7 @@ var init_install = __esm({
88566
88704
  import fs4 from "node:fs";
88567
88705
  import os4 from "node:os";
88568
88706
  import path2 from "node:path";
88707
+ import { fileURLToPath } from "node:url";
88569
88708
  import { spawn, spawnSync as spawnSync2 } from "node:child_process";
88570
88709
  async function latestVersion(fetchImpl = globalThis.fetch, env3 = process.env) {
88571
88710
  if (env3.HUM_NO_UPDATE_CHECK || typeof fetchImpl !== "function") return "";
@@ -88588,20 +88727,9 @@ function installLayout(execPath = process.execPath, env3 = process.env) {
88588
88727
  }
88589
88728
  return { kind: "native", exe, dir: path2.dirname(real), base: path2.dirname(path2.dirname(real)) };
88590
88729
  }
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 = "") {
88730
+ function installerArgv(version, platform2 = process.platform, layout2 = { kind: "native" }, log = "", node = process.execPath) {
88600
88731
  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
- }
88732
+ if (layout2.kind === "npm") return [node, [installerScript(), "install", version], {}];
88605
88733
  if (platform2 === "win32") {
88606
88734
  const cmd = `irm ${base}/install.ps1 | iex`;
88607
88735
  return ["powershell", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", log ? `& { ${cmd} } *>> '${log}'` : cmd], {}];
@@ -88613,6 +88741,7 @@ function startBackgroundUpdate(version, { env: env3 = process.env, now = Date.no
88613
88741
  const s = readState(env3);
88614
88742
  if (s.version === version && now - (s.started || 0) < 30 * 60 * 1e3) return false;
88615
88743
  const hidden = platform2 === "win32";
88744
+ const ownLog = layout2.kind === "npm";
88616
88745
  const log = path2.join(humHome2(env3), "update.log");
88617
88746
  let out = "ignore";
88618
88747
  let logged = false;
@@ -88620,15 +88749,15 @@ function startBackgroundUpdate(version, { env: env3 = process.env, now = Date.no
88620
88749
  fs4.mkdirSync(humHome2(env3), { recursive: true });
88621
88750
  out = fs4.openSync(log, "w");
88622
88751
  logged = true;
88623
- if (hidden) {
88752
+ if (hidden || ownLog) {
88624
88753
  fs4.closeSync(out);
88625
88754
  out = "ignore";
88626
88755
  }
88627
88756
  } catch {
88628
88757
  out = "ignore";
88629
88758
  }
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 };
88759
+ const [bin, args2, extra] = installerArgv(version, platform2, layout2, hidden && logged && !ownLog ? log : "");
88760
+ const io = hidden ? { stdio: "ignore", windowsHide: true } : { detached: true, stdio: ownLog ? "ignore" : ["ignore", out, out], windowsHide: true };
88632
88761
  try {
88633
88762
  const child = spawnImpl(bin, args2, { ...io, ...extra, env: { ...env3, HUM_VERSION: version, HUM_DIST: dist(env3) } });
88634
88763
  child.unref();
@@ -88651,7 +88780,7 @@ function updateNotice(mine, { running = "", latest = "", env: env3 = process.env
88651
88780
  if (drift) return drift;
88652
88781
  if (env3.HUM_NO_UPDATE_CHECK || !mine || !latest || !newer(latest, mine)) return "";
88653
88782
  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`;
88783
+ if (readyVersion(layout2, latest, env3)) return `hum ${latest} is ready \xB7 restart hum to use it`;
88655
88784
  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
88785
  if (readState(env3).version === latest) return `hum ${latest} is still installing in the background (${path2.join(humHome2(env3), "update.log")} has the details)`;
88657
88786
  return "";
@@ -88673,45 +88802,54 @@ function watchReady(mine, latest, onReady, { env: env3 = process.env, layout: la
88673
88802
  if (t.unref) t.unref();
88674
88803
  return () => clearInterval(t);
88675
88804
  }
88676
- function updateNow(layout2 = installLayout()) {
88805
+ async function updateNow(layout2 = installLayout(), { mine = MINE, latest = latestVersion, install = installVersion, write = (m) => process.stdout.write(m) } = {}) {
88677
88806
  if (layout2.kind === "native") {
88678
88807
  const [bin, args2] = installerArgv();
88679
88808
  return spawnSync2(bin, args2, { stdio: "inherit", env: { ...process.env, HUM_DIST: dist() } }).status ?? 1;
88680
88809
  }
88681
88810
  if (layout2.kind === "python") {
88682
88811
  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)}
88812
+ if (r2.error || r2.status !== 0) write(`update with your package manager: ${updateCommand(layout2)}
88684
88813
  `);
88685
88814
  return r2.error ? 1 : r2.status ?? 1;
88686
88815
  }
88687
- const r = spawnSync2(win2() ? "npm.cmd" : "npm", ["i", "-g", "@metaphi-ai/hum@latest"], { stdio: "inherit", shell: win2() });
88688
- return r.status ?? 1;
88816
+ const v = await latest();
88817
+ if (!v) {
88818
+ write(`could not read the latest release from ${dist()}
88819
+ `);
88820
+ return 1;
88821
+ }
88822
+ if (mine && !newer(v, mine)) {
88823
+ write(`hum ${mine} is the latest
88824
+ `);
88825
+ return 0;
88826
+ }
88827
+ const r = install(v, { say: (m) => write(`hum: ${m}
88828
+ `) });
88829
+ if (!r.ok) write(`hum: ${r.error}
88830
+ `);
88831
+ return r.ok ? 0 : 1;
88689
88832
  }
88690
- var dist, humHome2, win2, newer, updateCommand, readyVersion, stateFile, readState, writeState;
88833
+ var dist, humHome2, win2, updateCommand, readyVersion, installerScript, stateFile, readState, writeState;
88691
88834
  var init_update = __esm({
88692
88835
  "src/update.js"() {
88836
+ init_install();
88693
88837
  init_install();
88694
88838
  dist = (env3 = process.env) => (env3.HUM_DIST || "https://humboldt.metaphi.ai").replace(/\/+$/, "");
88695
88839
  humHome2 = (env3 = process.env) => env3.HUM_HOME || path2.join(os4.homedir(), ".hum");
88696
88840
  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
88841
  updateCommand = (layout2 = installLayout(), platform2 = process.platform) => {
88705
88842
  if (layout2.kind === "native") return platform2 === "win32" ? `irm ${dist()}/install.ps1 | iex` : `curl -fsSL ${dist()}/install.sh | sh`;
88706
88843
  if (layout2.kind === "python") return "uv tool upgrade hum-cli (or: pip install -U hum-cli)";
88707
88844
  return "npm i -g @metaphi-ai/hum@latest";
88708
88845
  };
88709
- readyVersion = (layout2, version, pkg = packageVersion) => {
88846
+ readyVersion = (layout2, version, env3 = process.env) => {
88710
88847
  if (!version) return "";
88711
88848
  if (layout2.kind === "native") return fs4.existsSync(path2.join(layout2.base, version, layout2.exe)) ? version : "";
88712
- if (layout2.kind === "npm") return pkg() === version ? version : "";
88849
+ if (layout2.kind === "npm") return fs4.existsSync(readyFile(env3, version)) ? version : "";
88713
88850
  return "";
88714
88851
  };
88852
+ installerScript = () => fileURLToPath(new URL("./install.mjs", import.meta.url));
88715
88853
  stateFile = (env3 = process.env) => path2.join(humHome2(env3), "update.json");
88716
88854
  readState = (env3 = process.env) => {
88717
88855
  try {
@@ -88774,12 +88912,12 @@ function siblingCore(execPath = process.execPath) {
88774
88912
  const p = path3.join(dir, "core", win3 ? "hum-core.exe" : "hum-core");
88775
88913
  return exists(p) ? p : null;
88776
88914
  }
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");
88915
+ function homeCore(env3 = process.env, version = MINE) {
88916
+ if (!version) return null;
88917
+ const py = corePython(env3, version);
88780
88918
  return exists(py) ? py : null;
88781
88919
  }
88782
- function coreCandidates(env3 = process.env) {
88920
+ function coreCandidates(env3 = process.env, version = MINE) {
88783
88921
  const out = [];
88784
88922
  if (env3.HUM_CORE) {
88785
88923
  const [bin, ...args2] = env3.HUM_CORE.split(" ");
@@ -88787,8 +88925,8 @@ function coreCandidates(env3 = process.env) {
88787
88925
  }
88788
88926
  const sib = siblingCore();
88789
88927
  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" });
88928
+ const home = homeCore(env3, version);
88929
+ if (home) out.push({ bin: home, args: ["-m", "hum.cli", "core"], where: "HUM_HOME/versions" });
88792
88930
  const py = corePython2();
88793
88931
  if (py) out.push({ bin: py, args: ["-m", "hum.cli", "core"], where: "the interpreter beside it" });
88794
88932
  out.push({ bin: "hum-core", args: [], where: "PATH" });
@@ -88805,9 +88943,9 @@ function coreCommand(argv, candidates = coreCandidates()) {
88805
88943
  }
88806
88944
  return null;
88807
88945
  }
88808
- function candidateVersion(c, { env: env3 = process.env, runImpl = spawnSync3 } = {}) {
88946
+ function candidateVersion(c, { env: env3 = process.env, runImpl = spawnSync3, version = MINE } = {}) {
88809
88947
  if (!c || !c.bin) return null;
88810
- if (c.where === "HUM_HOME/core") return installedVersion(env3);
88948
+ if (c.where === "HUM_HOME/versions") return installedVersion(env3, version);
88811
88949
  const interp = c.args.length >= 3 && c.args[c.args.length - 3] === "-m" && c.args[c.args.length - 2] === "hum.cli";
88812
88950
  let r;
88813
88951
  try {
@@ -88832,10 +88970,10 @@ function clientEnv() {
88832
88970
  platform: process.platform,
88833
88971
  arch: process.arch,
88834
88972
  terminal: process.env.TERM_PROGRAM || (process.env.WT_SESSION ? "Windows Terminal" : process.env.TERM || ""),
88835
- tui: true ? "0.2.10" : ""
88973
+ tui: true ? "0.2.12" : ""
88836
88974
  };
88837
88975
  }
88838
- function startFailure(failures, { npm = /^(node|bun)(\.exe)?$/i.test(String(process.execPath).split(/[\\/]/).pop()), platform: platform2 = process.platform, version = true ? "0.2.10" : "" } = {}) {
88976
+ function startFailure(failures, { npm = /^(node|bun)(\.exe)?$/i.test(String(process.execPath).split(/[\\/]/).pop()), platform: platform2 = process.platform, version = true ? "0.2.12" : "" } = {}) {
88839
88977
  const refused = failures.find((f) => f.error && f.error.code !== "ENOENT");
88840
88978
  const fix = npm ? npmFix(version) : updateCommand({ kind: "native" }, platform2);
88841
88979
  if (!refused) {
@@ -89658,10 +89796,12 @@ async function diagnose({
89658
89796
  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
89797
  if (layout2.kind === "npm") {
89660
89798
  const st = readInstallState(env3);
89661
- const stamp = installedVersion(env3, runImpl);
89799
+ const stamp = installedVersion(env3, mine, runImpl);
89662
89800
  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)));
89801
+ else if (!stamp) checks.push(check2("fail", `nothing installed under ${coreDir(env3, mine)}`, npmFix2(mine)));
89802
+ else checks.push(check2(stamp === mine ? "ok" : "warn", `${coreDir(env3, mine)} holds ${stamp}`, stamp === mine ? "" : npmFix2(mine)));
89803
+ const ready = readyVersions(env3);
89804
+ if (ready.length) checks.push(check2("ok", `installed here: ${ready.join(", ")} (the newest starts next)`));
89665
89805
  }
89666
89806
  if (layout2.kind === "native") {
89667
89807
  const sib = path6.join(layout2.dir, "core", isWin(platform2) ? "hum-core.exe" : "hum-core");
@@ -89686,7 +89826,7 @@ async function diagnose({
89686
89826
  else if (latest) checks.push(check2("ok", `${mine} is the latest release`));
89687
89827
  sections.push({ key: "updates", title: "Updates", checks });
89688
89828
  }
89689
- const interp = running && running.args.length >= 3 && running.args[running.args.length - 2] === "hum.cli" ? running.bin : exists2(corePython(env3)) ? corePython(env3) : null;
89829
+ 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
89830
  let toolServers = null;
89691
89831
  {
89692
89832
  const checks = [];
@@ -90291,7 +90431,7 @@ var input_default = MultilineInput;
90291
90431
 
90292
90432
  // src/app.jsx
90293
90433
  var import_jsx_runtime2 = __toESM(require_jsx_runtime(), 1);
90294
- var MINE = true ? "0.2.10" : "";
90434
+ var MINE2 = true ? "0.2.12" : "";
90295
90435
  var fmtDur = (s) => s < 60 ? `${Math.max(1, Math.round(s))}s` : `${Math.floor(s / 60)}m ${Math.round(s % 60)}s`;
90296
90436
  var plural = (n, w) => `${n} ${w}${n === 1 ? "" : "s"}`;
90297
90437
  var firstResp = (e) => e.first_response_s ? ` \xB7 first response ${e.first_response_s.toFixed(1)}s` : "";
@@ -90679,7 +90819,7 @@ function App2({ core: core2, task, resume, images = [], peek = peekSession, upda
90679
90819
  (ev.questions || []).forEach(addAsk);
90680
90820
  if (ev.fresh) append({ kind: "wordmark" });
90681
90821
  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 });
90822
+ append({ kind: "banner", lines: bannerLines(ev, homeRel, MINE2), instruction: ev.session ? ev.instruction : null });
90683
90823
  if (ev.note) append({ kind: "warn", text: ev.note });
90684
90824
  setGoal(ev.goal_state && ev.goal_state.text ? ev.goal_state : ev.goal ? { text: ev.goal, status: "active", exchanges: 0 } : null);
90685
90825
  if (ev.goal_state && ev.goal_state.text) append({ kind: "info", text: goalLine(ev.goal_state) });
@@ -91053,7 +91193,7 @@ function App2({ core: core2, task, resume, images = [], peek = peekSession, upda
91053
91193
  const noticedRef = (0, import_react36.useRef)("");
91054
91194
  (0, import_react36.useEffect)(() => {
91055
91195
  if (!latest) return void 0;
91056
- const mine = MINE || latest.version || "0.0.0";
91196
+ const mine = MINE2 || latest.version || "0.0.0";
91057
91197
  const want = latest.latest_version || "";
91058
91198
  const line = update(mine, { running: latest.version || "", latest: want });
91059
91199
  if (line && noticedRef.current !== line) {
@@ -92116,33 +92256,33 @@ init_update();
92116
92256
  init_install();
92117
92257
  var import_jsx_runtime4 = __toESM(require_jsx_runtime(), 1);
92118
92258
  import { spawnSync as spawnSync5 } from "node:child_process";
92119
- var MINE2 = true ? "0.2.10" : "0.0.0";
92259
+ var MINE3 = true ? "0.2.12" : "0.0.0";
92120
92260
  var layout = installLayout();
92121
92261
  function completeInstall() {
92122
- if (layout.kind !== "npm" || !needsInstall(MINE2)) return;
92262
+ if (layout.kind !== "npm" || !needsInstall(MINE3)) return;
92123
92263
  const say = (m) => process.stderr.write(`hum: ${m}
92124
92264
  `);
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)}`);
92265
+ say(`updating to ${MINE3} \u2026`);
92266
+ const r = installCore(MINE3, { say });
92267
+ if (!r.ok) say(`the update did not finish: ${r.error} \xB7 ${npmFix(MINE3)}`);
92128
92268
  }
92129
92269
  var CORE_COMMANDS = /* @__PURE__ */ new Set(["run", "login", "logout", "whoami", "model", "key", "sessions", "show", "sync", "skill", "persona", "ps", "stop", "hooks", "core"]);
92130
92270
  var argv0 = process.argv.slice(2);
92131
92271
  if (argv0[0] === "--version" || argv0[0] === "-V") {
92132
- process.stdout.write(`hum ${MINE2}
92272
+ process.stdout.write(`hum ${MINE3}
92133
92273
  `);
92134
92274
  if (layout.kind === "npm") {
92135
92275
  const r = runningVersion();
92136
- const drift = mismatchLine(MINE2, r ? r.version : "", { layout });
92276
+ const drift = mismatchLine(MINE3, r ? r.version : "", { layout });
92137
92277
  if (drift) process.stdout.write(`${drift}
92138
92278
  `);
92139
92279
  }
92140
92280
  process.exit(0);
92141
92281
  }
92142
- if (argv0[0] === "update") process.exit(updateNow());
92282
+ if (argv0[0] === "update") process.exit(await updateNow());
92143
92283
  if (argv0[0] === "doctor") {
92144
92284
  const { doctor: doctor2 } = await Promise.resolve().then(() => (init_doctor(), doctor_exports));
92145
- process.exit(await doctor2({ json: argv0.includes("--json"), mine: MINE2, layout }));
92285
+ process.exit(await doctor2({ json: argv0.includes("--json"), mine: MINE3, layout }));
92146
92286
  }
92147
92287
  if (CORE_COMMANDS.has(argv0[0]) || argv0[0] === "agents" && argv0.includes("--json") || wantsPlain(argv0)) {
92148
92288
  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,30 +120,30 @@ 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);
100
129
  const makeVenv = (clear) => (runImpl(u, ["venv", "--quiet", "--python", trusted || "3.12", ...clear ? ["--clear"] : [], dir], { stdio: "inherit" }) || {}).status === 0;
101
- const pipIn = () => (runImpl(u, ["pip", "install", "--quiet", "--python", python, spec], { stdio: "inherit" }) || {}).status === 0;
130
+ const pipIn = () => (runImpl(u, ["pip", "install", "--quiet", "--refresh-package", "hum-cli", "--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,148 @@ 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
+ var pause = (ms) => {
218
+ try {
219
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
220
+ } catch {
221
+ }
222
+ };
223
+ function installVersion(version, { env = process.env, say = (m) => console.log(`hum: ${m}`), runImpl = run, now = Date.now(), retries = 0, every = 90 * 1e3, wait = pause } = {}) {
224
+ if (!isVersion(version)) return { ok: false, error: `not a version: ${version}` };
225
+ try {
226
+ fs.mkdirSync(versionDir(env, version), { recursive: true });
227
+ try {
228
+ fs.unlinkSync(readyFile(env, version));
229
+ } catch {
230
+ }
231
+ const attempt = (what, step) => {
232
+ let r = step();
233
+ for (let i = 0; !r.ok && i < retries; i += 1) {
234
+ say(`${what} is not there yet (${r.error}); trying again in ${Math.round(every / 1e3)} s`);
235
+ wait(every);
236
+ r = step();
237
+ }
238
+ return r;
239
+ };
240
+ if (!fs.existsSync(entryFile(env, version))) {
241
+ const p = attempt("the package", () => installPackage(version, { env, say, runImpl }));
242
+ if (!p.ok) return p;
243
+ }
244
+ if (installedVersion(env, version, runImpl) !== version) {
245
+ const c = attempt("the part of hum that does the work", () => installCore(version, { env, say, runImpl, now }));
246
+ if (!c.ok) return { ok: false, error: c.error };
247
+ }
248
+ fs.writeFileSync(readyFile(env, version), `${version}
249
+ `);
250
+ say(`hum ${version} is ready; the next hum you start is ${version}`);
251
+ prune(env, { say });
252
+ return { ok: true, error: null };
253
+ } catch (e) {
254
+ return { ok: false, error: e && e.message ? e.message : String(e) };
255
+ }
256
+ }
257
+ if (process.argv[1] && /install\.mjs$/.test(process.argv[1]) && process.argv[2] === "install") {
258
+ const log = path.join(humHome(), "update.log");
259
+ const say = (m) => {
260
+ try {
261
+ fs.appendFileSync(log, `${(/* @__PURE__ */ new Date()).toISOString()} ${m}
262
+ `);
263
+ } catch {
264
+ }
265
+ };
266
+ const runLogged = (cmd, args, opts = {}) => {
267
+ const r2 = run(cmd, args, { ...opts, stdio: "pipe" });
268
+ if (r2 && r2.status !== 0 && (r2.stderr || r2.stdout)) say(String(r2.stderr || r2.stdout).trim().split(/\r?\n/).slice(-5).join("\n"));
269
+ return r2;
270
+ };
271
+ const r = installVersion(process.argv[3] || "", { say, runImpl: runLogged, retries: 3 });
272
+ if (!r.ok) say(`hum ${process.argv[3]} did not install: ${r.error}`);
273
+ process.exit(r.ok ? 0 : 1);
274
+ }
130
275
  export {
131
276
  ASK_SSL,
132
277
  ASK_VERSION,
278
+ MINE,
133
279
  NPM_FIX,
280
+ chosenEntry,
134
281
  coreDir,
135
282
  corePython,
283
+ entryFile,
136
284
  humHome,
137
285
  importedVersion,
138
286
  installCore,
287
+ installPackage,
288
+ installVersion,
139
289
  installedVersion,
140
290
  needsInstall,
291
+ newer,
141
292
  npmFix,
293
+ pkgDir,
294
+ prune,
142
295
  readInstallState,
296
+ readyFile,
297
+ readyVersions,
143
298
  sslAdvice,
144
299
  sslBlocked,
145
- trustedPython
300
+ trustedPython,
301
+ versionDir,
302
+ versionsDir
146
303
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metaphi-ai/hum",
3
- "version": "0.2.10",
3
+ "version": "0.2.12",
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
  }