@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.
- package/bin/hum.js +3 -2
- package/dist/cli.mjs +140 -63
- 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
|
-
|
|
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
|
|
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
|
|
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
|
|
84533
|
-
|
|
84534
|
-
|
|
84535
|
-
|
|
84536
|
-
|
|
84537
|
-
|
|
84538
|
-
|
|
84539
|
-
|
|
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
|
|
84542
|
-
|
|
84543
|
-
|
|
84544
|
-
|
|
84545
|
-
|
|
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 (!
|
|
84550
|
-
|
|
84645
|
+
if (died && !rec) {
|
|
84646
|
+
settle(() => reject({ died: logPath }));
|
|
84551
84647
|
return;
|
|
84552
84648
|
}
|
|
84553
|
-
|
|
84554
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|