@metaphi-ai/hum 0.1.7 → 0.1.9

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.
Files changed (3) hide show
  1. package/bin/hum.js +3 -2
  2. package/dist/cli.mjs +140 -63
  3. package/package.json +1 -1
package/bin/hum.js CHANGED
@@ -2,7 +2,8 @@
2
2
  // `hum` from npm: the Ink front-end, which starts Hum's engine (Python, installed by postinstall
3
3
  // via uv) and talks to it over localhost. Everything the person sees is here; everything the
4
4
  // session records is in the engine.
5
- import { fileURLToPath } from 'node:url';
5
+ import { fileURLToPath, pathToFileURL } from 'node:url';
6
6
  import path from 'node:path';
7
7
  const here = path.dirname(fileURLToPath(import.meta.url));
8
- await import(path.join(here, '..', 'dist', 'cli.mjs'));
8
+ // a URL, not a path: on Windows `C:\…` is not something the ESM loader will import
9
+ await import(pathToFileURL(path.join(here, '..', 'dist', 'cli.mjs')).href);
package/dist/cli.mjs CHANGED
@@ -83738,7 +83738,7 @@ var COMMANDS = [
83738
83738
  { name: "/compact", desc: "summarise and restart the context now" },
83739
83739
  { name: "/model", desc: "switch the model for this session (/model <name>)" },
83740
83740
  { name: "/cost", desc: "tokens and cost so far" },
83741
- { name: "/approve", desc: "approvals on|off \u2014 whether changes and commands wait for you first" },
83741
+ { name: "/approve", desc: "approvals on|off \u2014 whether every change and command waits for you first (leaving the sandbox always asks)" },
83742
83742
  { name: "/continue", desc: "after a pause: let the agent carry on" },
83743
83743
  { name: "/image", desc: "attach an image file to your next message (/image <path>); ctrl+v pastes a screenshot" },
83744
83744
  { name: "/tools", desc: "what is mounted" },
@@ -83747,7 +83747,7 @@ var COMMANDS = [
83747
83747
  { name: "/quit", desc: "leave; the session keeps running (hum brings you back)" },
83748
83748
  { name: "/stop", desc: "end this session's engine (the record is saved)" }
83749
83749
  ];
83750
- var HELP = "!cmd runs a command as you \xB7 esc interrupts the model (what it had is kept) \xB7 type while it works to steer \xB7 edit files in your editor any time (recorded as your turn) \xB7 ctrl+o shows the full output of the last tool result \xB7 ctrl+v pastes a screenshot from the clipboard \xB7 @path.png or /image <path> attaches an image file \xB7 /approve makes changes and commands wait for you (enter approves, a typed reason rejects) \xB7 /resume picks a past session (a running one attaches) \xB7 /compact restarts the context from a summary \xB7 ctrl+c twice leaves and the session keeps running (`hum` brings you back) \xB7 /stop ends it";
83750
+ var HELP = "!cmd runs a command as you \xB7 esc interrupts the model (what it had is kept) \xB7 type while it works to steer \xB7 edit files in your editor any time (recorded as your turn) \xB7 ctrl+o shows the full output of the last tool result \xB7 ctrl+v pastes a screenshot from the clipboard \xB7 @path.png or /image <path> attaches an image file \xB7 commands run in a sandbox (writes in the working directory, no network); leaving it asks you first (enter approves, a typed reason rejects) \xB7 /approve makes every change and command wait for you \xB7 /resume picks a past session (a running one attaches) \xB7 /compact restarts the context from a summary \xB7 ctrl+c twice leaves and the session keeps running (`hum` brings you back) \xB7 /stop ends it";
83751
83751
  var DIFF_TAIL_LINES = 20;
83752
83752
  var OUTPUT_MAX_LINES = 200;
83753
83753
  var ERR_TAIL_LINES = 5;
@@ -83860,7 +83860,7 @@ function App2({ engine: engine2, task, resume, images = [] }) {
83860
83860
  const state = ev.state && ev.state !== "new" && ev.state !== "idle" ? ` \xB7 ${ev.state}` : "";
83861
83861
  append({
83862
83862
  kind: "banner",
83863
- text: `${ev.model}${ev.user ? ` \xB7 ${ev.user}` : ""} \xB7 ${homeRel(ev.cwd)} \xB7 tools: ${ev.tools.join(", ")}${state}`,
83863
+ text: `${ev.model}${ev.user ? ` \xB7 ${ev.user}` : ""} \xB7 ${homeRel(ev.cwd)} \xB7 tools: ${ev.tools.join(", ")}${ev.sandbox ? ` \xB7 sandbox: ${ev.sandbox}` : ""}${state}`,
83864
83864
  instruction: ev.session ? ev.instruction : null
83865
83865
  });
83866
83866
  preHello.current.forEach((it) => append(it));
@@ -84429,7 +84429,9 @@ function App2({ engine: engine2, task, resume, images = [] }) {
84429
84429
  proposal && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { flexDirection: "column", borderStyle: "round", borderColor: ACCENT, paddingX: 1, marginTop: 1, children: [
84430
84430
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Text, { children: [
84431
84431
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: ACCENT, bold: true, children: "? " }),
84432
- "hum wants to ",
84432
+ "hum wants to",
84433
+ proposal.calls.some((c) => c.escape) ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "yellow", children: " leave the sandbox" }) : null,
84434
+ " ",
84433
84435
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { dimColor: true, children: "\xB7 enter approves \xB7 type a reason to reject \xB7 esc stops" })
84434
84436
  ] }),
84435
84437
  proposal.content ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Text, { dimColor: true, children: [
@@ -84442,10 +84444,14 @@ function App2({ engine: engine2, task, resume, images = [] }) {
84442
84444
  c.desc,
84443
84445
  c.lines != null ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { dimColor: true, children: ` (${c.lines} lines)` }) : null
84444
84446
  ] }),
84445
- c.name === "bash" || c.name === "powershell" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Text, { color: "cyan", children: [
84447
+ (c.name === "bash" || c.name === "powershell") && c.desc !== `$ ${String(c.args?.command || "")}` ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Text, { color: "cyan", children: [
84446
84448
  " $ ",
84447
84449
  String(c.args?.command || "").split("\n").join(" \u23CE ").slice(0, 300)
84448
84450
  ] }) : null,
84451
+ c.escape ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Text, { color: "yellow", children: [
84452
+ " outside the sandbox \u2014 ",
84453
+ String(c.reason || "").slice(0, Math.max(20, width - 32))
84454
+ ] }) : null,
84449
84455
  c.diff ? c.diff.split("\n").slice(0, DIFF_TAIL_LINES).map((l, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
84450
84456
  Text,
84451
84457
  {
@@ -84518,7 +84524,7 @@ import net from "node:net";
84518
84524
  import os4 from "node:os";
84519
84525
  import path2 from "node:path";
84520
84526
  import crypto from "node:crypto";
84521
- import { spawn } from "node:child_process";
84527
+ import { spawn, spawnSync } from "node:child_process";
84522
84528
  var runDir = () => path2.join(process.env.HUM_HOME || path2.join(os4.homedir(), ".hum"), "run");
84523
84529
  var readRun = (id) => {
84524
84530
  try {
@@ -84529,73 +84535,144 @@ var readRun = (id) => {
84529
84535
  };
84530
84536
  var EngineError = class extends Error {
84531
84537
  };
84532
- var ENGINE_CANDIDATES = [
84533
- process.env.HUM_ENGINE,
84534
- // explicit: a command to run
84535
- "hum-engine",
84536
- // uv tool install hum-cli
84537
- `${process.env.HOME || ""}/.local/bin/hum-engine`
84538
- ];
84539
- function spawnEngine(cwd2, { task = null, images = [], resume = null } = {}) {
84538
+ var win = process.platform === "win32";
84539
+ var exists = (p) => {
84540
+ try {
84541
+ fs3.accessSync(p);
84542
+ return true;
84543
+ } catch {
84544
+ return false;
84545
+ }
84546
+ };
84547
+ function uvToolDirs() {
84548
+ const dirs = [];
84549
+ if (process.env.UV_TOOL_DIR) dirs.push(process.env.UV_TOOL_DIR);
84550
+ if (win) {
84551
+ for (const base of [process.env.APPDATA, process.env.LOCALAPPDATA]) if (base) dirs.push(path2.join(base, "uv", "tools"));
84552
+ } else dirs.push(path2.join(process.env.XDG_DATA_HOME || path2.join(os4.homedir(), ".local", "share"), "uv", "tools"));
84553
+ if (!dirs.some(exists)) {
84554
+ for (const uv of ["uv", path2.join(os4.homedir(), ".local", "bin", win ? "uv.exe" : "uv")]) {
84555
+ try {
84556
+ const r = spawnSync(uv, ["tool", "dir"], { encoding: "utf8", windowsHide: true });
84557
+ if (r.status === 0 && r.stdout.trim()) {
84558
+ dirs.push(r.stdout.trim());
84559
+ break;
84560
+ }
84561
+ } catch {
84562
+ }
84563
+ }
84564
+ }
84565
+ return dirs;
84566
+ }
84567
+ function enginePython(dirs = uvToolDirs()) {
84568
+ for (const dir of dirs) for (const name of ["hum-cli", "hum"]) {
84569
+ const py = win ? path2.join(dir, name, "Scripts", "python.exe") : path2.join(dir, name, "bin", "python");
84570
+ if (exists(py)) return py;
84571
+ }
84572
+ return null;
84573
+ }
84574
+ function engineCandidates(env3 = process.env) {
84575
+ const out = [];
84576
+ if (env3.HUM_ENGINE) {
84577
+ const [bin, ...args2] = env3.HUM_ENGINE.split(" ");
84578
+ out.push({ bin, args: args2, where: "HUM_ENGINE" });
84579
+ }
84580
+ const py = enginePython();
84581
+ if (py) out.push({ bin: py, args: ["-m", "hum.cli", "engine"], where: "the engine's interpreter" });
84582
+ out.push({ bin: "hum-engine", args: [], where: "PATH" });
84583
+ out.push({ bin: path2.join(os4.homedir(), ".local", "bin", win ? "hum-engine.exe" : "hum-engine"), args: [], where: "uv's bin dir" });
84584
+ return out;
84585
+ }
84586
+ function startFailure(failures) {
84587
+ const refused = failures.find((f) => f.error && f.error.code !== "ENOENT");
84588
+ if (!refused) {
84589
+ return "Hum's engine is not installed. Run: uv tool install hum-cli (or set HUM_ENGINE to the command that starts it)";
84590
+ }
84591
+ const what = refused.error.code || refused.error.message;
84592
+ const lines = [
84593
+ `Hum's engine is installed (${refused.bin}) but this machine would not start it: ${what}.`
84594
+ ];
84595
+ if (win) {
84596
+ lines.push("On a managed Windows machine that is usually Device Guard / WDAC refusing an executable it does not know.");
84597
+ lines.push(`To see the policy's own message, run: "${refused.bin}" --help`);
84598
+ const dir = uvToolDirs()[0];
84599
+ lines.push(`If uv's environment is elsewhere, point Hum at its interpreter: set HUM_ENGINE=${path2.join(dir || "<uv tool dir>", "hum-cli", "Scripts", "python.exe")} -m hum.cli engine`);
84600
+ } else {
84601
+ lines.push('Set HUM_ENGINE to a command that starts it, e.g. HUM_ENGINE="python -m hum.cli engine"');
84602
+ }
84603
+ return lines.join("\n");
84604
+ }
84605
+ function attempt({ bin, args: args2 }, cwd2, { task, images, resume }) {
84540
84606
  return new Promise((resolve, reject) => {
84541
- const tryNext = (i) => {
84542
- const cmd = ENGINE_CANDIDATES[i];
84543
- if (cmd === void 0) {
84544
- reject(new EngineError(
84545
- "Hum's engine is not installed. Run: uv tool install hum-cli (or set HUM_ENGINE to the command that starts it)"
84546
- ));
84607
+ const runId = crypto.randomBytes(6).toString("hex");
84608
+ fs3.mkdirSync(runDir(), { recursive: true });
84609
+ const logPath = path2.join(runDir(), `${runId}.log`);
84610
+ const log = fs3.openSync(logPath, "a");
84611
+ const argv = [...args2, "-C", cwd2, "--run-id", runId];
84612
+ if (task) argv.push("--task", task);
84613
+ for (const p of images) argv.push("--image", p);
84614
+ if (resume) argv.push("--resume", resume);
84615
+ let settled = false;
84616
+ const settle = (fn) => {
84617
+ if (!settled) {
84618
+ settled = true;
84619
+ fn();
84620
+ }
84621
+ };
84622
+ let child;
84623
+ try {
84624
+ child = spawn(bin, argv, { stdio: ["ignore", log, log], detached: true, windowsHide: true });
84625
+ } catch (e) {
84626
+ fs3.closeSync(log);
84627
+ reject({ spawn: e });
84628
+ return;
84629
+ }
84630
+ fs3.closeSync(log);
84631
+ let died = false;
84632
+ child.on("error", (e) => settle(() => reject({ spawn: e })));
84633
+ child.on("exit", () => {
84634
+ died = true;
84635
+ });
84636
+ const t0 = Date.now();
84637
+ const poll = () => {
84638
+ if (settled) return;
84639
+ const rec = readRun(runId);
84640
+ if (rec && rec.port) {
84641
+ child.unref();
84642
+ settle(() => resolve(rec));
84547
84643
  return;
84548
84644
  }
84549
- if (!cmd) {
84550
- tryNext(i + 1);
84645
+ if (died && !rec) {
84646
+ settle(() => reject({ died: logPath }));
84551
84647
  return;
84552
84648
  }
84553
- const runId = crypto.randomBytes(6).toString("hex");
84554
- fs3.mkdirSync(runDir(), { recursive: true });
84555
- const log = fs3.openSync(path2.join(runDir(), `${runId}.log`), "a");
84556
- const [bin, ...rest] = cmd.split(" ");
84557
- const args2 = [...rest, "-C", cwd2, "--run-id", runId];
84558
- if (task) args2.push("--task", task);
84559
- for (const p of images) args2.push("--image", p);
84560
- if (resume) args2.push("--resume", resume);
84561
- let child;
84562
- try {
84563
- child = spawn(bin, args2, { stdio: ["ignore", log, log], detached: true, windowsHide: true });
84564
- } catch {
84565
- tryNext(i + 1);
84649
+ if (Date.now() - t0 > 25e3) {
84650
+ settle(() => reject({ timeout: true }));
84566
84651
  return;
84567
84652
  }
84568
- let failed = false;
84569
- child.on("error", () => {
84570
- failed = true;
84571
- tryNext(i + 1);
84572
- });
84573
- child.on("exit", () => {
84574
- failed = true;
84575
- });
84576
- const t0 = Date.now();
84577
- const poll = () => {
84578
- const rec = readRun(runId);
84579
- if (rec && rec.port) {
84580
- child.unref();
84581
- resolve(rec);
84582
- return;
84583
- }
84584
- if (failed && !rec) {
84585
- reject(new EngineError(`the engine did not start (see ${path2.join(runDir(), `${runId}.log`)})`));
84586
- return;
84587
- }
84588
- if (Date.now() - t0 > 25e3) {
84589
- reject(new EngineError("the engine did not start in time"));
84590
- return;
84591
- }
84592
- setTimeout(poll, 50);
84593
- };
84594
- poll();
84653
+ setTimeout(poll, 50);
84595
84654
  };
84596
- tryNext(0);
84655
+ poll();
84597
84656
  });
84598
84657
  }
84658
+ async function spawnEngine(cwd2, { task = null, images = [], resume = null, candidates = engineCandidates() } = {}) {
84659
+ const failures = [];
84660
+ for (const cand of candidates) {
84661
+ if (!cand.bin) continue;
84662
+ try {
84663
+ return await attempt(cand, cwd2, { task, images, resume });
84664
+ } catch (f) {
84665
+ if (f && f.spawn) {
84666
+ failures.push({ ...cand, error: f.spawn });
84667
+ continue;
84668
+ }
84669
+ if (f && f.died) throw new EngineError(`the engine did not start (see ${f.died})`);
84670
+ if (f && f.timeout) throw new EngineError("the engine did not start in time");
84671
+ throw f;
84672
+ }
84673
+ }
84674
+ throw new EngineError(startFailure(failures));
84675
+ }
84599
84676
  var Engine = class {
84600
84677
  constructor(port2) {
84601
84678
  this.port = port2;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metaphi-ai/hum",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Hum (हम): a self-improving coding agent for your terminal.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",