@metaphi-ai/hum 0.1.4 → 0.1.6

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/LICENSE +16 -0
  2. package/dist/cli.mjs +245 -32
  3. package/package.json +2 -2
package/LICENSE ADDED
@@ -0,0 +1,16 @@
1
+ Copyright (c) 2026 Metaphi, Inc. All rights reserved.
2
+
3
+ Hum is proprietary software of Metaphi, Inc.
4
+
5
+ Metaphi, Inc. grants users it has authorized a personal, non-exclusive,
6
+ non-transferable, revocable right to install and run the distributed
7
+ packages (hum-cli on PyPI, @metaphi-ai/hum on npm) for use with Metaphi
8
+ services.
9
+
10
+ No other right is granted. Do not copy, modify, distribute, sublicense,
11
+ reverse engineer, or create derivative works from this software, in whole
12
+ or in part, without prior written permission from Metaphi, Inc.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED. IN NO EVENT SHALL METAPHI, INC. BE LIABLE FOR ANY CLAIM, DAMAGES
16
+ OR OTHER LIABILITY ARISING FROM THE USE OF THE SOFTWARE.
package/dist/cli.mjs CHANGED
@@ -1257,7 +1257,7 @@ var require_react_development = __commonJS({
1257
1257
  }
1258
1258
  return lazyType;
1259
1259
  }
1260
- function forwardRef2(render2) {
1260
+ function forwardRef3(render2) {
1261
1261
  {
1262
1262
  if (render2 != null && render2.$$typeof === REACT_MEMO_TYPE) {
1263
1263
  error("forwardRef requires a render function but received a `memo` component. Instead of forwardRef(memo(...)), use memo(forwardRef(...)).");
@@ -1402,7 +1402,7 @@ var require_react_development = __commonJS({
1402
1402
  var dispatcher = resolveDispatcher();
1403
1403
  return dispatcher.useMemo(create2, deps);
1404
1404
  }
1405
- function useImperativeHandle(ref, create2, deps) {
1405
+ function useImperativeHandle2(ref, create2, deps) {
1406
1406
  var dispatcher = resolveDispatcher();
1407
1407
  return dispatcher.useImperativeHandle(ref, create2, deps);
1408
1408
  }
@@ -2155,7 +2155,7 @@ var require_react_development = __commonJS({
2155
2155
  exports.createElement = createElement$1;
2156
2156
  exports.createFactory = createFactory;
2157
2157
  exports.createRef = createRef;
2158
- exports.forwardRef = forwardRef2;
2158
+ exports.forwardRef = forwardRef3;
2159
2159
  exports.isValidElement = isValidElement;
2160
2160
  exports.lazy = lazy;
2161
2161
  exports.memo = memo;
@@ -2167,7 +2167,7 @@ var require_react_development = __commonJS({
2167
2167
  exports.useDeferredValue = useDeferredValue;
2168
2168
  exports.useEffect = useEffect5;
2169
2169
  exports.useId = useId;
2170
- exports.useImperativeHandle = useImperativeHandle;
2170
+ exports.useImperativeHandle = useImperativeHandle2;
2171
2171
  exports.useInsertionEffect = useInsertionEffect;
2172
2172
  exports.useLayoutEffect = useLayoutEffect2;
2173
2173
  exports.useMemo = useMemo3;
@@ -83569,22 +83569,34 @@ function renderMarkdown(text, width) {
83569
83569
  // src/input.jsx
83570
83570
  var import_react23 = __toESM(require_react(), 1);
83571
83571
  var import_jsx_runtime = __toESM(require_jsx_runtime(), 1);
83572
- function MultilineInput({
83572
+ var MultilineInput = (0, import_react23.forwardRef)(function MultilineInput2({
83573
83573
  value,
83574
83574
  onChange,
83575
83575
  onSubmit,
83576
+ onPasteImage,
83576
83577
  focus = true,
83577
83578
  placeholder = "",
83578
83579
  history = [],
83579
83580
  navActive = true
83580
- }) {
83581
+ }, ref) {
83581
83582
  const [cursor, setCursor] = (0, import_react23.useState)(value.length);
83582
83583
  const hist = (0, import_react23.useRef)({ index: null, stash: "" });
83584
+ const latest = (0, import_react23.useRef)({ value, cursor });
83585
+ latest.current = { value, cursor };
83583
83586
  const clamp = (n, len) => Math.max(0, Math.min(n, len));
83584
83587
  const set = (v, c2) => {
83585
83588
  onChange(v);
83586
83589
  setCursor(clamp(c2, v.length));
83587
83590
  };
83591
+ (0, import_react23.useImperativeHandle)(ref, () => ({
83592
+ // text placed at the cursor as it is NOW (an image chip arrives after a round trip)
83593
+ insert(text) {
83594
+ const { value: v, cursor: cu } = latest.current;
83595
+ const c2 = clamp(cu, v.length);
83596
+ const sep = c2 > 0 && !/\s$/.test(v.slice(0, c2)) ? " " : "";
83597
+ set(v.slice(0, c2) + sep + text + v.slice(c2), c2 + sep.length + text.length);
83598
+ }
83599
+ }));
83588
83600
  const lineStart = (v, c2) => v.lastIndexOf("\n", c2 - 1) + 1;
83589
83601
  const lineEnd = (v, c2) => {
83590
83602
  const i = v.indexOf("\n", c2);
@@ -83660,6 +83672,8 @@ function MultilineInput({
83660
83672
  else if (input === "w") {
83661
83673
  const head = value.slice(0, c2).replace(/\S+\s*$/, "");
83662
83674
  set(head + value.slice(c2), head.length);
83675
+ } else if (input === "v") {
83676
+ if (onPasteImage) onPasteImage();
83663
83677
  }
83664
83678
  return;
83665
83679
  }
@@ -83684,7 +83698,8 @@ function MultilineInput({
83684
83698
  shown = value.slice(0, c) + source_default.inverse(value[c]) + value.slice(c + 1);
83685
83699
  }
83686
83700
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: shown });
83687
- }
83701
+ });
83702
+ var input_default = MultilineInput;
83688
83703
 
83689
83704
  // src/app.jsx
83690
83705
  var import_jsx_runtime2 = __toESM(require_jsx_runtime(), 1);
@@ -83717,12 +83732,18 @@ var COMMANDS = [
83717
83732
  { name: "/compact", desc: "summarise and restart the context now" },
83718
83733
  { name: "/model", desc: "switch the model for this session (/model <name>)" },
83719
83734
  { name: "/cost", desc: "tokens and cost so far" },
83735
+ { name: "/approve", desc: "approvals on|off \u2014 whether changes and commands wait for you first" },
83736
+ { name: "/continue", desc: "after a pause: let the agent carry on" },
83737
+ { name: "/image", desc: "attach an image file to your next message (/image <path>); ctrl+v pastes a screenshot" },
83720
83738
  { name: "/tools", desc: "what is mounted" },
83721
83739
  { name: "/help", desc: "keys and commands" },
83722
83740
  { name: "/quit", desc: "save and leave" }
83723
83741
  ];
83724
- 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 /resume picks a past session \xB7 /compact restarts the context from a summary \xB7 ctrl+c twice quits";
83725
- function App2({ engine: engine2, task, resume }) {
83742
+ 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 \xB7 /compact restarts the context from a summary \xB7 ctrl+c twice quits";
83743
+ var DIFF_TAIL_LINES = 20;
83744
+ var OUTPUT_MAX_LINES = 200;
83745
+ var ERR_TAIL_LINES = 5;
83746
+ function App2({ engine: engine2, task, resume, images = [] }) {
83726
83747
  const { exit } = use_app_default();
83727
83748
  const { stdout } = use_stdout_default();
83728
83749
  const width = (stdout?.columns || 80) - 2;
@@ -83739,6 +83760,11 @@ function App2({ engine: engine2, task, resume }) {
83739
83760
  const [elapsed, setElapsed] = (0, import_react24.useState)(0);
83740
83761
  const [ctrlc, setCtrlc] = (0, import_react24.useState)(0);
83741
83762
  const [picker, setPicker] = (0, import_react24.useState)(null);
83763
+ const [proposal, setProposal] = (0, import_react24.useState)(null);
83764
+ const [gate, setGate] = (0, import_react24.useState)("off");
83765
+ const [paused, setPaused] = (0, import_react24.useState)(false);
83766
+ const attachRef = (0, import_react24.useRef)([]);
83767
+ const inputRef = (0, import_react24.useRef)(null);
83742
83768
  const nextId = (0, import_react24.useRef)(0);
83743
83769
  const phaseRef = (0, import_react24.useRef)("connecting");
83744
83770
  const turnT0 = (0, import_react24.useRef)(null);
@@ -83747,12 +83773,15 @@ function App2({ engine: engine2, task, resume }) {
83747
83773
  const toolsRef = (0, import_react24.useRef)({});
83748
83774
  const historyRef = (0, import_react24.useRef)(loadHistory());
83749
83775
  const modelRef = (0, import_react24.useRef)("");
83776
+ const lastOutRef = (0, import_react24.useRef)(null);
83777
+ const [meter, setMeter] = (0, import_react24.useState)({ tokens: 0, cap: 0, input: 0, output: 0 });
83750
83778
  const setPhaseBoth = (p) => {
83751
83779
  phaseRef.current = p;
83752
83780
  setPhase(p);
83753
83781
  };
83754
83782
  const append = (item) => setItems((prev) => [...prev, { id: nextId.current++, ...item }]);
83755
83783
  const menu = value.trimStart().startsWith("/") && !value.includes(" ") ? COMMANDS.filter((c) => c.name.startsWith(value.trim())) : [];
83784
+ const ctxPct = meter.cap > 0 && meter.tokens > 0 ? Math.round(meter.tokens / meter.cap * 100) : 0;
83756
83785
  (0, import_react24.useEffect)(() => {
83757
83786
  if (phase !== "busy" && phase !== "connecting") return void 0;
83758
83787
  const started = Date.now();
@@ -83814,13 +83843,21 @@ function App2({ engine: engine2, task, resume }) {
83814
83843
  case "hello":
83815
83844
  setHello(ev);
83816
83845
  modelRef.current = ev.model;
83846
+ setMeter((m) => ({ ...m, tokens: 0, cap: ev.compact_at || 0 }));
83847
+ setGate(ev.gate === "approve" ? "approve" : "off");
83817
83848
  append({ kind: "banner", text: `${ev.model}${ev.user ? ` \xB7 ${ev.user}` : ""} \xB7 ${ev.cwd} \xB7 tools: ${ev.tools.join(", ")}` });
83818
- if (task) engine2.send({ cmd: "start", task });
83819
- else if (resume === "pick") {
83849
+ if (task) {
83850
+ if (images.length && !ev.images) append({ kind: "warn", text: "this engine does not take images; update hum" });
83851
+ engine2.send({ cmd: "start", task, images: ev.images ? images : [] });
83852
+ } else if (resume === "pick") {
83820
83853
  setPhaseBoth("idle");
83821
83854
  engine2.send({ cmd: "sessions" });
83822
83855
  } else if (resume) engine2.send({ cmd: "resume", name: resume });
83823
- else setPhaseBoth("idle");
83856
+ else {
83857
+ setPhaseBoth("idle");
83858
+ if (ev.images) images.forEach((p) => engine2.send({ cmd: "attach", path: p }));
83859
+ else if (images.length) append({ kind: "warn", text: "this engine does not take images; update hum" });
83860
+ }
83824
83861
  break;
83825
83862
  case "sessions":
83826
83863
  if (!ev.items || ev.items.length === 0) {
@@ -83832,6 +83869,8 @@ function App2({ engine: engine2, task, resume }) {
83832
83869
  case "session":
83833
83870
  setSession(ev.name);
83834
83871
  setSpend(0);
83872
+ setPaused(false);
83873
+ setProposal(null);
83835
83874
  if (ev.model) modelRef.current = ev.model;
83836
83875
  append({ kind: "info", text: `session ${ev.name}` });
83837
83876
  break;
@@ -83858,15 +83897,26 @@ function App2({ engine: engine2, task, resume }) {
83858
83897
  case "tool_pending":
83859
83898
  break;
83860
83899
  // the status line already shows it; the call itself lands as a transcript line
83861
- case "tool_call":
83900
+ case "tool_call": {
83862
83901
  foldThinking();
83863
83902
  flushDraft();
83864
83903
  toolsRef.current[ev.id] = ev;
83865
- append({ kind: "tool", text: ev.desc || ev.name, name: ev.name, args: ev.args });
83904
+ const label = ev.name === "write_file" && ev.lines != null ? `${ev.desc || ev.name} (${ev.lines} lines)` : ev.desc || ev.name;
83905
+ append({ kind: "tool", text: label, name: ev.name, args: ev.args });
83906
+ if (ev.diff) {
83907
+ const ls = ev.diff.split("\n");
83908
+ append({ kind: "diff", lines: ls.slice(0, DIFF_TAIL_LINES), more: Math.max(0, ls.length - DIFF_TAIL_LINES) });
83909
+ }
83866
83910
  break;
83911
+ }
83867
83912
  case "tool_result": {
83868
- const more = (ev.lines || 0) > 1 ? ` (+${ev.lines - 1} lines)` : "";
83913
+ lastOutRef.current = { name: ev.name, content: ev.content || "" };
83914
+ const more = (ev.lines || 0) > 1 ? ` (+${ev.lines - 1} lines \xB7 ctrl+o expands)` : "";
83869
83915
  append({ kind: "tool_result", text: `${ev.summary || "(no output)"}${more}`, isError: !ev.ok });
83916
+ if (!ev.ok && ev.content) {
83917
+ const t = ev.content.split("\n").filter((l) => l.trim()).slice(-ERR_TAIL_LINES);
83918
+ if (t.length > 1) append({ kind: "errtail", lines: t });
83919
+ }
83870
83920
  break;
83871
83921
  }
83872
83922
  case "plan": {
@@ -83876,11 +83926,37 @@ function App2({ engine: engine2, task, resume }) {
83876
83926
  append({ kind: "plan", steps: (ev.steps || []).map((st) => ({ ...st, glyph: glyph[st.status] || "\u25FB" })), explanation: ev.explanation });
83877
83927
  break;
83878
83928
  }
83929
+ case "proposal":
83930
+ foldThinking();
83931
+ flushDraft();
83932
+ setProposal({ content: ev.content || "", calls: ev.calls || [] });
83933
+ setStatus("waiting for your decision");
83934
+ break;
83935
+ case "paused":
83936
+ foldThinking();
83937
+ flushDraft();
83938
+ setPaused(true);
83939
+ append({ kind: "paused", text: ev.detail, trip: ev.kind });
83940
+ break;
83941
+ case "resumed":
83942
+ setPaused(false);
83943
+ turnT0.current = Date.now();
83944
+ setPhaseBoth("busy");
83945
+ break;
83946
+ case "proposal_released":
83947
+ setProposal(null);
83948
+ append({ kind: "info", text: "approvals off: the waiting proposal ran" });
83949
+ break;
83950
+ case "gate":
83951
+ setGate(ev.mode);
83952
+ append({ kind: "info", text: ev.mode === "approve" ? "approvals on: changes and commands wait for you first" : "approvals off: the agent acts, you steer" });
83953
+ break;
83879
83954
  case "human_edit":
83880
83955
  append({ kind: "info", text: `you edited files: ${ev.stat}` });
83881
83956
  break;
83882
83957
  case "compaction":
83883
- append({ kind: "info", text: "context summarised and restarted (the session keeps everything)" });
83958
+ append({ kind: "info", text: `context summarised and restarted${ev.prompt_tokens_before ? ` at ${ev.prompt_tokens_before.toLocaleString()} tokens` : ""} (the session keeps everything)` });
83959
+ setMeter((m) => ({ ...m, tokens: 0 }));
83884
83960
  break;
83885
83961
  case "retry":
83886
83962
  append({ kind: "info", text: `model connection: ${ev.text} \u2014 retrying (${ev.attempt})` });
@@ -83888,6 +83964,8 @@ function App2({ engine: engine2, task, resume }) {
83888
83964
  case "interrupted":
83889
83965
  foldThinking();
83890
83966
  flushDraft();
83967
+ setProposal(null);
83968
+ setPaused(false);
83891
83969
  append({ kind: "info", text: ev.kept ? "stopped \xB7 what it had so far is kept in the session" : "stopped" });
83892
83970
  setPhaseBoth("idle");
83893
83971
  setStatus("");
@@ -83895,14 +83973,22 @@ function App2({ engine: engine2, task, resume }) {
83895
83973
  case "turn_end": {
83896
83974
  foldThinking();
83897
83975
  flushDraft();
83976
+ setMeter((m) => ({ ...m, tokens: ev.context_tokens ?? m.tokens, input: ev.input_tokens || 0, output: ev.output_tokens || 0 }));
83898
83977
  setSpend(ev.cost_usd);
83899
83978
  const dur = turnT0.current ? fmtDur((Date.now() - turnT0.current) / 1e3) : fmtDur(ev.elapsed_s);
83900
83979
  turnT0.current = null;
83901
- append({ kind: "turn", text: `${dur} \xB7 ${ev.turns} steps \xB7 ${fmtCost(ev.cost_usd)} session` + (ev.stop_reason !== "done" ? ` \xB7 stopped: ${ev.stop_reason}` : "") });
83980
+ append({ kind: "turn", text: `${dur} \xB7 ${ev.turns} steps \xB7 ${fmtCost(ev.cost_usd)} session` + (ev.stop_reason !== "done" && !String(ev.stop_reason).startsWith("paused") ? ` \xB7 stopped: ${ev.stop_reason}` : "") });
83902
83981
  setPhaseBoth("idle");
83903
83982
  setStatus("");
83904
83983
  break;
83905
83984
  }
83985
+ case "image": {
83986
+ const n = attachRef.current.length + 1;
83987
+ const { type, label, ...ref } = ev;
83988
+ attachRef.current = [...attachRef.current, { ref, n, label: label || ref.name || "image", chip: `[Image #${n}]` }];
83989
+ if (inputRef.current) inputRef.current.insert(`[Image #${n}] `);
83990
+ break;
83991
+ }
83906
83992
  case "info":
83907
83993
  append({ kind: "info", text: ev.text });
83908
83994
  break;
@@ -83952,6 +84038,25 @@ function App2({ engine: engine2, task, resume }) {
83952
84038
  quit();
83953
84039
  return;
83954
84040
  }
84041
+ if (key.ctrl && input === "o") {
84042
+ const last = lastOutRef.current;
84043
+ if (last && last.content) {
84044
+ const ls = last.content.split("\n");
84045
+ append({ kind: "output", name: last.name, lines: ls.slice(0, OUTPUT_MAX_LINES), more: Math.max(0, ls.length - OUTPUT_MAX_LINES) });
84046
+ }
84047
+ return;
84048
+ }
84049
+ if (proposal && !picker) {
84050
+ if (key.return && !value.trim()) {
84051
+ decide("accept");
84052
+ return;
84053
+ }
84054
+ if (key.escape) {
84055
+ setProposal(null);
84056
+ engine2.send({ cmd: "interrupt" });
84057
+ return;
84058
+ }
84059
+ }
83955
84060
  if (picker) {
83956
84061
  if (key.upArrow) setPicker((p) => ({ ...p, index: (p.index + p.items.length - 1) % p.items.length }));
83957
84062
  else if (key.downArrow || key.tab) setPicker((p) => ({ ...p, index: (p.index + 1) % p.items.length }));
@@ -83973,6 +84078,13 @@ function App2({ engine: engine2, task, resume }) {
83973
84078
  (0, import_react24.useEffect)(() => {
83974
84079
  setMenuIndex(0);
83975
84080
  }, [value]);
84081
+ const decide = (decision, note) => {
84082
+ const p = proposal;
84083
+ setProposal(null);
84084
+ const what = p && p.calls.length ? p.calls.map((c) => c.desc).join(" \xB7 ") : "the proposal";
84085
+ append({ kind: "you", text: decision === "accept" ? `approved: ${what}` : `rejected: ${what}${note ? ` \u2014 ${note}` : ""}` });
84086
+ engine2.send({ cmd: "decide", decision, note: note || "" });
84087
+ };
83976
84088
  const runCommand = (line) => {
83977
84089
  const [name, ...rest] = line.split(/\s+/);
83978
84090
  const arg = rest.join(" ").trim();
@@ -84000,12 +84112,27 @@ function App2({ engine: engine2, task, resume }) {
84000
84112
  engine2.send({ cmd: "model", name: arg });
84001
84113
  } else append({ kind: "info", text: `model: ${modelRef.current}` });
84002
84114
  break;
84003
- case "/cost":
84004
- append({ kind: "info", text: `${fmtCost(spend)} this session` });
84115
+ case "/cost": {
84116
+ const ctx = ctxPct > 0 ? ` \xB7 context ${ctxPct}% of the compaction threshold` : "";
84117
+ append({ kind: "info", text: `${fmtCost(spend)} this session \xB7 ${meter.input.toLocaleString()}+${meter.output.toLocaleString()} tokens${ctx}` });
84118
+ break;
84119
+ }
84120
+ case "/image":
84121
+ if (!hello?.images) append({ kind: "warn", text: "this engine does not take images; update hum" });
84122
+ else if (!arg) append({ kind: "info", text: "/image <path> attaches the file to your next message \xB7 ctrl+v pastes a screenshot \xB7 @path.png in a message works too" });
84123
+ else engine2.send({ cmd: "attach", path: arg });
84005
84124
  break;
84006
84125
  case "/tools":
84007
84126
  append({ kind: "info", text: (hello?.tools || []).join(", ") });
84008
84127
  break;
84128
+ case "/continue":
84129
+ engine2.send({ cmd: "continue" });
84130
+ break;
84131
+ case "/approve": {
84132
+ const mode = arg === "on" ? "approve" : arg === "off" ? "off" : gate === "approve" ? "off" : "approve";
84133
+ engine2.send({ cmd: "gate", mode });
84134
+ break;
84135
+ }
84009
84136
  default:
84010
84137
  break;
84011
84138
  }
@@ -84013,12 +84140,20 @@ function App2({ engine: engine2, task, resume }) {
84013
84140
  const onSubmit = (line) => {
84014
84141
  const text = line.trim();
84015
84142
  setValue("");
84016
- if (!text) return;
84143
+ const queued = attachRef.current;
84144
+ const attached = text ? queued.filter((a) => text.includes(a.chip)) : queued;
84145
+ const dropped = queued.filter((a) => !attached.includes(a));
84146
+ if (!text && attached.length === 0) return;
84147
+ if (!text && proposal) return;
84017
84148
  const hist = historyRef.current;
84018
- if (hist[hist.length - 1] !== text) {
84149
+ if (text && hist[hist.length - 1] !== text) {
84019
84150
  hist.push(text);
84020
84151
  saveHistory(hist);
84021
84152
  }
84153
+ if (proposal && !/^\/(approve|quit|exit)\b/.test(text)) {
84154
+ decide("reject", text);
84155
+ return;
84156
+ }
84022
84157
  if (text.startsWith("/")) {
84023
84158
  runCommand(text);
84024
84159
  return;
@@ -84029,14 +84164,21 @@ function App2({ engine: engine2, task, resume }) {
84029
84164
  if (phaseRef.current === "idle") setPhaseBoth("busy");
84030
84165
  return;
84031
84166
  }
84032
- append({ kind: "you", text, steering: phaseRef.current === "busy" });
84033
- engine2.send({ cmd: "message", text });
84167
+ if (paused && /^(continue|go on|carry on|keep going)\.?$/i.test(text)) {
84168
+ runCommand("/continue");
84169
+ return;
84170
+ }
84171
+ if (paused) setPaused(false);
84172
+ if (dropped.length) append({ kind: "info", text: `not sent (chip removed): ${dropped.map((a) => a.label).join(", ")}` });
84173
+ append({ kind: "you", text, images: attached.map((a) => `#${a.n} ${a.label}`), steering: phaseRef.current === "busy" });
84174
+ engine2.send({ cmd: "message", text, images: attached.map((a) => a.ref) });
84175
+ attachRef.current = [];
84034
84176
  if (phaseRef.current === "idle") {
84035
84177
  turnT0.current = Date.now();
84036
84178
  setPhaseBoth("busy");
84037
84179
  }
84038
84180
  };
84039
- const statusLine = phase === "connecting" ? "starting the engine \u2026" : `${modelRef.current || ""}${session ? ` \xB7 ${session}` : ""} \xB7 ${fmtCost(spend)}` + (ctrlc ? " \xB7 ctrl+c again to quit" : " \xB7 / for commands");
84181
+ const statusLine = phase === "connecting" ? "starting the engine \u2026" : `${modelRef.current || ""}${session ? ` \xB7 ${session}` : ""} \xB7 ${fmtCost(spend)}` + (ctxPct >= 10 ? ` \xB7 ctx ${ctxPct}%` : "") + (gate === "approve" ? " \xB7 approvals on" : "") + (ctrlc ? " \xB7 ctrl+c again to quit" : " \xB7 / for commands");
84040
84182
  const spinnerLabel = phase === "connecting" ? " starting \u2026" : ` ${status || "working"}${elapsed >= 2 ? ` \xB7 ${fmtDur(elapsed)}` : ""}` + (thinkChars.current > 4e3 ? ` \xB7 ${(thinkChars.current / 1e3).toFixed(0)}k thinking` : "");
84041
84183
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { flexDirection: "column", children: [
84042
84184
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Static, { items, children: (item) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { flexDirection: "column", marginBottom: ["hum", "you", "banner", "turn"].includes(item.kind) ? 1 : 0, children: [
@@ -84057,9 +84199,15 @@ function App2({ engine: engine2, task, resume }) {
84057
84199
  ] }),
84058
84200
  item.kind === "you" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { children: [
84059
84201
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Box_default, { width: 2, flexShrink: 0, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "cyan", bold: true, children: "\u276F" }) }),
84060
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Text, { color: "white", children: [
84061
- item.text,
84062
- item.steering ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { dimColor: true, children: " (steering)" }) : null
84202
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { flexDirection: "column", children: [
84203
+ item.text ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Text, { color: "white", children: [
84204
+ item.text,
84205
+ item.steering ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { dimColor: true, children: " (steering)" }) : null
84206
+ ] }) : null,
84207
+ (item.images || []).map((im, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Text, { dimColor: true, children: [
84208
+ "\u{1F4CE} ",
84209
+ im
84210
+ ] }, i))
84063
84211
  ] })
84064
84212
  ] }),
84065
84213
  item.kind === "agent" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Text, { color: "magenta", children: [
@@ -84098,10 +84246,36 @@ function App2({ engine: engine2, task, resume }) {
84098
84246
  item.agent ? " \u23BF " : " \u23BF ",
84099
84247
  item.text
84100
84248
  ] })),
84249
+ item.kind === "diff" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { flexDirection: "column", paddingLeft: 4, children: [
84250
+ item.lines.map((l, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
84251
+ Text,
84252
+ {
84253
+ color: l.startsWith("+") ? "green" : l.startsWith("-") ? "red" : void 0,
84254
+ dimColor: !l.startsWith("+") && !l.startsWith("-"),
84255
+ children: l || " "
84256
+ },
84257
+ i
84258
+ )),
84259
+ item.more > 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { dimColor: true, children: `\u2026 +${item.more} more diff lines` }) : null
84260
+ ] }),
84261
+ item.kind === "errtail" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Box_default, { flexDirection: "column", paddingLeft: 6, children: item.lines.map((l, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "red", dimColor: true, children: l }, i)) }),
84262
+ item.kind === "output" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { flexDirection: "column", paddingLeft: 4, children: [
84263
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { dimColor: true, children: `full output \xB7 ${item.name}` }),
84264
+ item.lines.map((l, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { dimColor: true, children: l || " " }, i)),
84265
+ item.more > 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { dimColor: true, children: `\u2026 +${item.more} more lines (the session file keeps everything)` }) : null
84266
+ ] }),
84101
84267
  item.kind === "turn" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Text, { children: [
84102
84268
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: ACCENT, children: "\u273B " }),
84103
84269
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { dimColor: true, children: item.text })
84104
84270
  ] }),
84271
+ item.kind === "paused" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { borderStyle: "round", borderColor: "yellow", paddingX: 1, flexDirection: "column", children: [
84272
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Text, { color: "yellow", bold: true, children: [
84273
+ "\u23F8 paused \u2014 ",
84274
+ item.trip === "spend" ? "spend cap" : item.trip === "repeat" ? "repeating itself" : "no progress"
84275
+ ] }),
84276
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { children: item.text }),
84277
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { dimColor: true, children: "say what to do next \xB7 /continue lets it carry on" })
84278
+ ] }),
84105
84279
  item.kind === "info" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { dimColor: true, children: item.text }),
84106
84280
  item.kind === "warn" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "yellow", children: item.text }),
84107
84281
  item.kind === "error" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "red", children: item.text })
@@ -84118,6 +84292,41 @@ function App2({ engine: engine2, task, resume }) {
84118
84292
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: ACCENT, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(build_default, { type: "dots" }) }),
84119
84293
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { dimColor: true, children: spinnerLabel })
84120
84294
  ] }),
84295
+ proposal && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { flexDirection: "column", borderStyle: "round", borderColor: ACCENT, paddingX: 1, marginTop: 1, children: [
84296
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Text, { children: [
84297
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: ACCENT, bold: true, children: "? " }),
84298
+ "hum wants to ",
84299
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { dimColor: true, children: "\xB7 enter approves \xB7 type a reason to reject \xB7 esc stops" })
84300
+ ] }),
84301
+ proposal.content ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Text, { dimColor: true, children: [
84302
+ " ",
84303
+ proposal.content.split("\n")[0].slice(0, width - 4)
84304
+ ] }) : null,
84305
+ proposal.calls.map((c) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { flexDirection: "column", children: [
84306
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Text, { children: [
84307
+ " \xB7 ",
84308
+ c.desc,
84309
+ c.lines != null ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { dimColor: true, children: ` (${c.lines} lines)` }) : null
84310
+ ] }),
84311
+ c.name === "bash" || c.name === "powershell" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Text, { color: "cyan", children: [
84312
+ " $ ",
84313
+ String(c.args?.command || "").split("\n").join(" \u23CE ").slice(0, 300)
84314
+ ] }) : null,
84315
+ c.diff ? c.diff.split("\n").slice(0, DIFF_TAIL_LINES).map((l, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
84316
+ Text,
84317
+ {
84318
+ color: l.startsWith("+") ? "green" : l.startsWith("-") ? "red" : void 0,
84319
+ dimColor: !l.startsWith("+") && !l.startsWith("-"),
84320
+ children: [
84321
+ " ",
84322
+ l || " "
84323
+ ]
84324
+ },
84325
+ i
84326
+ )) : null,
84327
+ c.diff && c.diff.split("\n").length > DIFF_TAIL_LINES ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { dimColor: true, children: ` \u2026 +${c.diff.split("\n").length - DIFF_TAIL_LINES} more diff lines` }) : null
84328
+ ] }, c.id))
84329
+ ] }),
84121
84330
  picker && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { flexDirection: "column", paddingX: 1, marginTop: 1, children: [
84122
84331
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Text, { children: [
84123
84332
  "pick a session to continue ",
@@ -84137,15 +84346,17 @@ function App2({ engine: engine2, task, resume }) {
84137
84346
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { borderStyle: "round", borderColor: "gray", paddingX: 1, marginTop: 1, children: [
84138
84347
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "cyan", children: "\u276F " }),
84139
84348
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
84140
- MultilineInput,
84349
+ input_default,
84141
84350
  {
84351
+ ref: inputRef,
84352
+ onPasteImage: () => hello?.images ? engine2.send({ cmd: "paste_image" }) : append({ kind: "warn", text: "this engine does not take images; update hum" }),
84142
84353
  value,
84143
84354
  onChange: setValue,
84144
84355
  onSubmit,
84145
84356
  focus: phase !== "dead" && !picker,
84146
84357
  navActive: menu.length === 0,
84147
84358
  history: historyRef.current,
84148
- placeholder: phase === "busy" ? "type to steer \xB7 esc to stop \xB7 !cmd runs a command" : session ? "what next?" : "what are we doing?"
84359
+ placeholder: proposal ? "enter to approve \xB7 or type why not" : paused ? "paused \xB7 say what to do, or /continue" : phase === "busy" ? "type to steer \xB7 esc to stop \xB7 !cmd runs a command" : session ? "what next?" : "what are we doing?"
84149
84360
  }
84150
84361
  )
84151
84362
  ] }),
@@ -84264,15 +84475,17 @@ var Engine = class {
84264
84475
  // src/cli.jsx
84265
84476
  var import_jsx_runtime3 = __toESM(require_jsx_runtime(), 1);
84266
84477
  function parseArgs(argv) {
84267
- const a = { task: null, resume: null, cwd: process.cwd() };
84478
+ const a = { task: null, resume: null, cwd: process.cwd(), images: [] };
84268
84479
  for (let i = 0; i < argv.length; i += 1) {
84269
84480
  const x = argv[i];
84270
84481
  if (x === "resume") {
84271
84482
  a.resume = argv[i + 1] && !argv[i + 1].startsWith("-") ? argv[++i] : "pick";
84272
84483
  } else if ((x === "-C" || x === "--cwd") && argv[i + 1]) {
84273
84484
  a.cwd = argv[++i];
84485
+ } else if (x === "--image" && argv[i + 1]) {
84486
+ a.images.push(argv[++i]);
84274
84487
  } else if (x === "-h" || x === "--help") {
84275
- process.stdout.write("usage: hum [task] | hum resume [name]\nIn a session: type to talk, !cmd runs a command, esc stops the model, / for commands.\n");
84488
+ process.stdout.write("usage: hum [task] [--image path] | hum resume [name]\nIn a session: type to talk, !cmd runs a command, esc stops the model, ctrl+v pastes a screenshot, / for commands.\n");
84276
84489
  process.exit(0);
84277
84490
  } else if (!x.startsWith("-")) {
84278
84491
  a.task = a.task ? `${a.task} ${x}` : x;
@@ -84303,7 +84516,7 @@ try {
84303
84516
  process.stderr.write(exc.message + "\n");
84304
84517
  process.exit(1);
84305
84518
  }
84306
- var { waitUntilExit } = render_default(/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(App2, { engine, task: args.task, resume: args.resume }), { exitOnCtrlC: false });
84519
+ var { waitUntilExit } = render_default(/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(App2, { engine, task: args.task, resume: args.resume, images: args.images }), { exitOnCtrlC: false });
84307
84520
  waitUntilExit().then(() => {
84308
84521
  engine.close();
84309
84522
  if (child) setTimeout(() => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@metaphi-ai/hum",
3
- "version": "0.1.4",
4
- "description": "Hum हम. Human and model, one harness. A coding agent for your terminal.",
3
+ "version": "0.1.6",
4
+ "description": "Hum (हम): a self-improving coding agent for your terminal.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
7
7
  "bin": { "hum": "bin/hum.js" },