@holt-os/holt 0.5.2 → 0.6.0

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/README.md +4 -1
  2. package/dist/cli.js +209 -22
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -63,6 +63,8 @@ The point of `/brain`: Holt owns the transcript, so you can start a thread on on
63
63
 
64
64
  Every exchange is saved to `<folder>/.holt/memory/turns.jsonl`, private and local. On each message, Holt recalls the most relevant moments from your *past* sessions in that folder and hands them to the brain, so it remembers what you told it last week.
65
65
 
66
+ When you end a chat, Holt asks the brain to distill 1 to 5 durable facts from the session (decisions, preferences, key names, numbers) and writes them to a human-readable `<folder>/.holt/memory/facts.md` you can read and edit. Those facts are also embedded and ranked slightly higher than raw turns during recall, so the signal rises to the top over time. See them with `holt memory facts`. Turn it off by setting `memory.extractFacts` to `false` in `config.json`.
67
+
66
68
  Two recall modes, picked automatically:
67
69
 
68
70
  - **Semantic** (best): a local [Ollama](https://ollama.com) with an embedding model, which `holt init` offers to set up for you. Recall matches by meaning: asking "who owns my apartment" finds "my landlord is called Pieter". No API keys, nothing leaves your machine.
@@ -72,6 +74,7 @@ Inspect it any time:
72
74
 
73
75
  ```bash
74
76
  holt memory # stats for this folder
77
+ holt memory facts # show the distilled facts (facts.md)
75
78
  holt memory search <query> # find remembered moments
76
79
  holt memory embed # embed older moments for semantic recall
77
80
  holt memory clear # wipe this folder's memory
@@ -155,7 +158,7 @@ Small strongly-typed **TypeScript core**, zero runtime dependencies: command dis
155
158
  Built in always-shippable phases toward a full-vision v1:
156
159
 
157
160
  0. **Skeleton and chat**: trust, init with install and sign-in, chat, brain switching with kept context *(shipped)*
158
- 1. **Memory**: per-folder store, semantic recall via local embeddings with keyword fallback, streaming replies *(shipped)*
161
+ 1. **Memory**: per-folder store, semantic recall, streaming replies, and distilled facts per session *(shipped)*
159
162
  2. **Any LLM directly**: API brains with your own key, HTML or Markdown output *(shipped)*
160
163
  3. **Skills**: portable SKILL.md skills, create/add/run *(shipped)*
161
164
  4. **Knowledge graph**: see and navigate your own memory with `holt graph` *(shipped)*
package/dist/cli.js CHANGED
@@ -121,7 +121,7 @@ function defaultConfig() {
121
121
  const d = BRAIN_DEFS[id];
122
122
  brains[id] = { id, label: d.label, command: d.command, args: [...d.args], enabled: false };
123
123
  }
124
- return { version: 3, defaultBrain: null, brains, apiBrains: [], outputFormat: "markdown" };
124
+ return { version: 4, defaultBrain: null, brains, apiBrains: [], outputFormat: "markdown", memory: { extractFacts: true } };
125
125
  }
126
126
  function loadConfig() {
127
127
  const path = wsConfigPath();
@@ -132,11 +132,14 @@ function loadConfig() {
132
132
  const brains = cfg.brains ?? {};
133
133
  for (const id of BRAIN_IDS) if (!brains[id]) brains[id] = base.brains[id];
134
134
  return {
135
- version: 3,
135
+ version: 4,
136
136
  defaultBrain: cfg.defaultBrain ?? null,
137
137
  brains,
138
138
  apiBrains: Array.isArray(cfg.apiBrains) ? cfg.apiBrains : [],
139
- outputFormat: cfg.outputFormat === "html" ? "html" : "markdown"
139
+ outputFormat: cfg.outputFormat === "html" ? "html" : "markdown",
140
+ memory: {
141
+ extractFacts: cfg.memory && typeof cfg.memory.extractFacts === "boolean" ? cfg.memory.extractFacts : true
142
+ }
140
143
  };
141
144
  } catch {
142
145
  return null;
@@ -415,6 +418,9 @@ function memDir() {
415
418
  function memPath() {
416
419
  return join4(memDir(), "turns.jsonl");
417
420
  }
421
+ function factsMdPath() {
422
+ return join4(memDir(), "facts.md");
423
+ }
418
424
  function newSessionId() {
419
425
  return randomUUID().slice(0, 8);
420
426
  }
@@ -470,12 +476,58 @@ function appendTurn(t) {
470
476
  function clearMemory() {
471
477
  if (existsSync4(memPath())) rmSync2(memPath());
472
478
  }
479
+ function normalizeFact(s) {
480
+ return s.toLowerCase().replace(/\s+/g, " ").trim().replace(/[.,;:!?]+$/, "");
481
+ }
482
+ var FACTS_HEADER = "# Holt facts\n\nDistilled memories for this folder. Safe to edit by hand.\n";
483
+ async function saveFact(content, session) {
484
+ const clean = content.trim();
485
+ if (!clean) return false;
486
+ const norm2 = normalizeFact(clean);
487
+ for (const t of loadTurns()) {
488
+ if (t.role === "fact" && normalizeFact(t.content) === norm2) return false;
489
+ }
490
+ if (existsSync4(factsMdPath())) {
491
+ for (const line of readFileSync4(factsMdPath(), "utf8").split("\n")) {
492
+ const l = line.trim();
493
+ if (!l.startsWith("- ")) continue;
494
+ if (normalizeFact(l.slice(2)) === norm2) return false;
495
+ }
496
+ }
497
+ mkdirSync4(memDir(), { recursive: true });
498
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
499
+ let existing = existsSync4(factsMdPath()) ? readFileSync4(factsMdPath(), "utf8") : "";
500
+ if (!existing) existing = FACTS_HEADER;
501
+ const headings = existing.match(/^## (\d{4}-\d{2}-\d{2})/gm);
502
+ const lastHeading = headings && headings.length ? headings[headings.length - 1] : null;
503
+ const needsHeading = lastHeading !== `## ${today}`;
504
+ let addition = "";
505
+ if (needsHeading) {
506
+ if (!existing.endsWith("\n")) addition += "\n";
507
+ addition += `
508
+ ## ${today}
509
+ `;
510
+ }
511
+ addition += `- ${clean}
512
+ `;
513
+ writeFileSync4(factsMdPath(), existing + addition, "utf8");
514
+ appendTurn({
515
+ id: randomUUID().slice(0, 8),
516
+ ts: Date.now(),
517
+ session,
518
+ role: "fact",
519
+ content: clean,
520
+ emb: await embed(clean) ?? void 0
521
+ });
522
+ return true;
523
+ }
473
524
  function memStats() {
474
525
  const turns = loadTurns();
475
526
  const sessions = new Set(turns.map((t) => t.session)).size;
476
527
  const withEmbeddings = turns.filter((t) => Array.isArray(t.emb)).length;
528
+ const facts = turns.filter((t) => t.role === "fact").length;
477
529
  const bytes = existsSync4(memPath()) ? statSync(memPath()).size : 0;
478
- return { turns: turns.length, sessions, withEmbeddings, bytes };
530
+ return { turns: turns.length, facts, sessions, withEmbeddings, bytes };
479
531
  }
480
532
  function cosine(a, b) {
481
533
  let dot = 0;
@@ -503,6 +555,7 @@ function keywordScore(q, text) {
503
555
  for (const w of q) if (t.has(w)) hit++;
504
556
  return hit / q.size;
505
557
  }
558
+ var FACT_BOOST = 1.15;
506
559
  async function recall(query, currentSession, k = 4) {
507
560
  const past = loadTurns().filter((t) => t.session !== currentSession);
508
561
  if (past.length === 0) return [];
@@ -513,7 +566,10 @@ async function recall(query, currentSession, k = 4) {
513
566
  let score = 0;
514
567
  if (qEmb && Array.isArray(turn.emb)) score = cosine(qEmb, turn.emb);
515
568
  else score = keywordScore(qTok, turn.content);
516
- if (score > (qEmb && Array.isArray(turn.emb) ? 0.35 : 0.15)) scored.push({ turn, score });
569
+ if (score > (qEmb && Array.isArray(turn.emb) ? 0.35 : 0.15)) {
570
+ if (turn.role === "fact") score *= FACT_BOOST;
571
+ scored.push({ turn, score });
572
+ }
517
573
  }
518
574
  scored.sort((a, b) => b.score - a.score);
519
575
  return scored.slice(0, k);
@@ -975,6 +1031,115 @@ async function runApiBrain(brain, prompt, onChunk) {
975
1031
  return { ok: false, text: `${brain.provider} returned an empty reply.` };
976
1032
  }
977
1033
 
1034
+ // src/facts.ts
1035
+ var MIN_EXCHANGES_FOR_EXTRACTION = 3;
1036
+ var MAX_TRANSCRIPT_TURNS = 20;
1037
+ var MAX_TURN_CHARS = 1e3;
1038
+ function buildExtractionPrompt(history) {
1039
+ const recent = history.slice(-MAX_TRANSCRIPT_TURNS);
1040
+ const transcript = recent.map((t) => `${t.role === "user" ? "USER" : "ASSISTANT"}: ${t.content.slice(0, MAX_TURN_CHARS)}`).join("\n\n");
1041
+ const lines = [
1042
+ "Given this conversation, extract 1 to 5 durable facts worth remembering long-term.",
1043
+ "Output ONLY a JSON array of strings, no prose, no code fences.",
1044
+ "",
1045
+ "Rules:",
1046
+ "- Keep decisions, outcomes, stable preferences, key names, dates, and numbers.",
1047
+ "- Skip pleasantries, one-off questions, and generic advice.",
1048
+ "- Make each fact self-contained so it reads on its own in a future session.",
1049
+ "- If nothing is worth saving, output exactly []",
1050
+ "",
1051
+ "Good examples:",
1052
+ '- "User decided to target the Netherlands as the primary job market, HSM visa route."',
1053
+ '- "HDFC EMI is 17,394 rupees per month with 29 payments remaining as of March 2026."',
1054
+ "",
1055
+ "Bad examples (do NOT output these):",
1056
+ '- "User asked a question about their finances."',
1057
+ '- "The assistant gave some helpful advice."',
1058
+ "",
1059
+ "Conversation:",
1060
+ transcript,
1061
+ "",
1062
+ "JSON array:"
1063
+ ];
1064
+ return lines.join("\n");
1065
+ }
1066
+ function firstBalancedArray(s) {
1067
+ const start = s.indexOf("[");
1068
+ if (start < 0) return null;
1069
+ let depth = 0;
1070
+ let inStr = false;
1071
+ let esc = false;
1072
+ for (let i = start; i < s.length; i++) {
1073
+ const ch = s[i];
1074
+ if (inStr) {
1075
+ if (esc) esc = false;
1076
+ else if (ch === "\\") esc = true;
1077
+ else if (ch === '"') inStr = false;
1078
+ continue;
1079
+ }
1080
+ if (ch === '"') inStr = true;
1081
+ else if (ch === "[") depth++;
1082
+ else if (ch === "]") {
1083
+ depth--;
1084
+ if (depth === 0) {
1085
+ const slice = s.slice(start, i + 1);
1086
+ try {
1087
+ const parsed = JSON.parse(slice);
1088
+ return Array.isArray(parsed) ? parsed : null;
1089
+ } catch {
1090
+ return null;
1091
+ }
1092
+ }
1093
+ }
1094
+ }
1095
+ return null;
1096
+ }
1097
+ function norm(s) {
1098
+ return s.toLowerCase().replace(/\s+/g, " ").trim().replace(/[.,;:!?]+$/, "");
1099
+ }
1100
+ function parseFacts(raw) {
1101
+ let s = (raw || "").trim();
1102
+ s = s.replace(/^```(?:json)?\s*/i, "").replace(/```\s*$/i, "").trim();
1103
+ const m = s.match(/\[[\s\S]*\]/);
1104
+ if (!m) return [];
1105
+ let arr = null;
1106
+ try {
1107
+ arr = JSON.parse(m[0]);
1108
+ } catch {
1109
+ arr = firstBalancedArray(s);
1110
+ }
1111
+ if (!Array.isArray(arr)) return [];
1112
+ const seen = /* @__PURE__ */ new Set();
1113
+ const out = [];
1114
+ for (const item of arr) {
1115
+ if (typeof item !== "string") continue;
1116
+ const v = String(item).trim();
1117
+ if (v.length <= 10) continue;
1118
+ const key = norm(v);
1119
+ if (seen.has(key)) continue;
1120
+ seen.add(key);
1121
+ out.push(v);
1122
+ if (out.length >= 5) break;
1123
+ }
1124
+ return out;
1125
+ }
1126
+ async function extractAndSaveFacts(active, cfg, history, session) {
1127
+ try {
1128
+ if (Math.floor(history.length / 2) < MIN_EXCHANGES_FOR_EXTRACTION) return 0;
1129
+ const prompt = buildExtractionPrompt(history);
1130
+ const res = active.kind === "cli" ? await runBrain(cfg.brains[active.id], prompt) : await runApiBrain(active.brain, prompt);
1131
+ if (!res.ok) return 0;
1132
+ const facts = parseFacts(res.text);
1133
+ let saved = 0;
1134
+ for (const f of facts) {
1135
+ if (await saveFact(f, session)) saved++;
1136
+ }
1137
+ return saved;
1138
+ } catch {
1139
+ return 0;
1140
+ }
1141
+ }
1142
+
978
1143
  // src/output.ts
979
1144
  import { writeFileSync as writeFileSync5 } from "fs";
980
1145
  import { join as join5 } from "path";
@@ -1301,7 +1466,7 @@ async function chat() {
1301
1466
  const stats = memStats();
1302
1467
  console.log("\n" + c.accent("Holt") + c.dim(` brain: ${active.label}`));
1303
1468
  console.log(c.dim(
1304
- `Memory: ${stats.turns} moments from ${stats.sessions} session${stats.sessions === 1 ? "" : "s"} in this folder (recall: ${embedOk ? "embeddings via local Ollama" : "keyword match"}).`
1469
+ `Memory: ${stats.turns} moments (${stats.facts} facts) from ${stats.sessions} session${stats.sessions === 1 ? "" : "s"} in this folder (recall: ${embedOk ? "embeddings via local Ollama" : "keyword match"}).`
1305
1470
  ));
1306
1471
  if (embedOk && stats.withEmbeddings < stats.turns) {
1307
1472
  console.log(c.dim(` ${stats.turns - stats.withEmbeddings} older moments lack embeddings. Run "holt memory embed" to upgrade them.`));
@@ -1343,7 +1508,7 @@ async function chat() {
1343
1508
  else for (const h of hits) console.log(c.dim(` ${h.score.toFixed(2)} (${h.turn.role}) ${h.turn.content.slice(0, 110).replace(/\s+/g, " ")}`));
1344
1509
  } else {
1345
1510
  const s = memStats();
1346
- console.log(c.dim(` ${s.turns} moments, ${s.sessions} sessions, ${s.withEmbeddings} embedded, ${(s.bytes / 1024).toFixed(1)} KB in ./.holt/memory/`));
1511
+ console.log(c.dim(` ${s.turns} moments, ${s.facts} facts, ${s.sessions} sessions, ${s.withEmbeddings} embedded, ${(s.bytes / 1024).toFixed(1)} KB in ./.holt/memory/`));
1347
1512
  console.log(c.dim(' usage: /memory <query> to preview recall, or "holt memory clear" to wipe.'));
1348
1513
  }
1349
1514
  continue;
@@ -1451,6 +1616,14 @@ async function chat() {
1451
1616
  console.log(c.red("\n " + res.text + "\n"));
1452
1617
  }
1453
1618
  }
1619
+ if (cfg.memory?.extractFacts !== false) {
1620
+ try {
1621
+ const arg = active.kind === "cli" ? { kind: "cli", id: active.id } : { kind: "api", brain: active.brain };
1622
+ const n = await extractAndSaveFacts(arg, cfg, history, session);
1623
+ if (n > 0) console.log(c.dim(` distilled ${n} fact${n === 1 ? "" : "s"} from this session.`));
1624
+ } catch {
1625
+ }
1626
+ }
1454
1627
  close();
1455
1628
  console.log(c.dim("\nBye.\n"));
1456
1629
  }
@@ -1472,6 +1645,7 @@ async function login(which) {
1472
1645
  }
1473
1646
 
1474
1647
  // src/commands/memory.ts
1648
+ import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
1475
1649
  async function memoryCmd(sub, rest = []) {
1476
1650
  const { ask, close } = createReader();
1477
1651
  if (!await ensureTrusted(ask)) {
@@ -1505,7 +1679,7 @@ async function memoryCmd(sub, rest = []) {
1505
1679
  }
1506
1680
  const missing = loadTurns().filter((t) => !Array.isArray(t.emb)).length;
1507
1681
  if (missing === 0) {
1508
- console.log(c.dim("\n All moments already have embeddings.\n"));
1682
+ console.log(c.dim("\n All memories already have embeddings.\n"));
1509
1683
  close();
1510
1684
  return;
1511
1685
  }
@@ -1513,7 +1687,18 @@ async function memoryCmd(sub, rest = []) {
1513
1687
  const r = await backfillEmbeddings((done, total) => {
1514
1688
  process.stdout.write(`\r embedding ${done}/${total}...`);
1515
1689
  });
1516
- console.log("\n" + c.green(` Done. ${r.embedded} of ${r.total} moments embedded.`) + "\n");
1690
+ console.log("\n" + c.green(` Done. ${r.embedded} of ${r.total} memories embedded.`) + "\n");
1691
+ close();
1692
+ return;
1693
+ }
1694
+ if (action === "facts") {
1695
+ if (!existsSync6(factsMdPath())) {
1696
+ console.log(c.dim("\n No facts distilled yet. They form when you end a chat session.\n"));
1697
+ close();
1698
+ return;
1699
+ }
1700
+ const body = readFileSync6(factsMdPath(), "utf8").trim();
1701
+ console.log("\n" + body + "\n");
1517
1702
  close();
1518
1703
  return;
1519
1704
  }
@@ -1540,6 +1725,7 @@ async function memoryCmd(sub, rest = []) {
1540
1725
  const sessions = new Set(loadTurns().map((t) => t.session)).size;
1541
1726
  console.log("\n" + c.accent("Holt memory") + c.dim(" (this folder)"));
1542
1727
  console.log(` moments ${s.turns}`);
1728
+ console.log(` facts ${s.facts} (./.holt/memory/facts.md)`);
1543
1729
  console.log(` sessions ${sessions}`);
1544
1730
  console.log(` embedded ${s.withEmbeddings} of ${s.turns}`);
1545
1731
  console.log(` size ${(s.bytes / 1024).toFixed(1)} KB (./.holt/memory/turns.jsonl)`);
@@ -1549,7 +1735,8 @@ async function memoryCmd(sub, rest = []) {
1549
1735
  ${s.turns - s.withEmbeddings} moments lack embeddings. Run "holt memory embed" to upgrade them to semantic recall.`));
1550
1736
  }
1551
1737
  console.log(c.dim("\n holt memory search <query> find remembered moments"));
1552
- console.log(c.dim(" holt memory embed embed older moments for semantic recall"));
1738
+ console.log(c.dim(" holt memory facts show distilled facts (facts.md)"));
1739
+ console.log(c.dim(" holt memory embed embed older memories for semantic recall"));
1553
1740
  console.log(c.dim(" holt memory clear wipe this folder's memory\n"));
1554
1741
  close();
1555
1742
  }
@@ -1558,8 +1745,8 @@ async function memoryCmd(sub, rest = []) {
1558
1745
  import {
1559
1746
  mkdirSync as mkdirSync5,
1560
1747
  writeFileSync as writeFileSync6,
1561
- readFileSync as readFileSync6,
1562
- existsSync as existsSync6,
1748
+ readFileSync as readFileSync7,
1749
+ existsSync as existsSync7,
1563
1750
  readdirSync as readdirSync2,
1564
1751
  statSync as statSync3,
1565
1752
  rmSync as rmSync3,
@@ -1612,7 +1799,7 @@ function isGitUrl(s) {
1612
1799
  return s.includes("://") || s.endsWith(".git") || s.startsWith("git@");
1613
1800
  }
1614
1801
  function findSkillDir(root) {
1615
- if (existsSync6(join7(root, "SKILL.md"))) return root;
1802
+ if (existsSync7(join7(root, "SKILL.md"))) return root;
1616
1803
  let subs = [];
1617
1804
  try {
1618
1805
  subs = readdirSync2(root).filter((f) => {
@@ -1625,7 +1812,7 @@ function findSkillDir(root) {
1625
1812
  } catch {
1626
1813
  return null;
1627
1814
  }
1628
- const withSkill = subs.filter((f) => existsSync6(join7(root, f, "SKILL.md")));
1815
+ const withSkill = subs.filter((f) => existsSync7(join7(root, f, "SKILL.md")));
1629
1816
  if (withSkill.length === 1) return join7(root, withSkill[0]);
1630
1817
  return null;
1631
1818
  }
@@ -1678,7 +1865,7 @@ function cmdCreate(name, global) {
1678
1865
  }
1679
1866
  const scope = global ? "global" : "workspace";
1680
1867
  const dir = join7(skillsRoot(scope), clean);
1681
- if (existsSync6(dir)) {
1868
+ if (existsSync7(dir)) {
1682
1869
  console.log(c.red(`
1683
1870
  A ${scope} skill named "${clean}" already exists.`));
1684
1871
  console.log(c.dim(" " + dir + "\n"));
@@ -1712,7 +1899,7 @@ function cmdAdd(source, global) {
1712
1899
  fetched = tempDir;
1713
1900
  } else {
1714
1901
  fetched = resolve(source);
1715
- if (!existsSync6(fetched)) {
1902
+ if (!existsSync7(fetched)) {
1716
1903
  console.log(c.red(`
1717
1904
  No such path: ${fetched}
1718
1905
  `));
@@ -1726,7 +1913,7 @@ function cmdAdd(source, global) {
1726
1913
  }
1727
1914
  let skillName = "";
1728
1915
  try {
1729
- const { data } = parseFrontmatter(readFileSync6(join7(skillDir, "SKILL.md"), "utf8"));
1916
+ const { data } = parseFrontmatter(readFileSync7(join7(skillDir, "SKILL.md"), "utf8"));
1730
1917
  skillName = sanitizeName(data.name || "");
1731
1918
  } catch {
1732
1919
  skillName = "";
@@ -1737,7 +1924,7 @@ function cmdAdd(source, global) {
1737
1924
  return;
1738
1925
  }
1739
1926
  const dest = join7(skillsRoot(scope), skillName);
1740
- if (existsSync6(dest)) {
1927
+ if (existsSync7(dest)) {
1741
1928
  console.log(c.red(`
1742
1929
  A ${scope} skill named "${skillName}" already exists.`));
1743
1930
  console.log(c.dim(" " + dest + "\n"));
@@ -1746,12 +1933,12 @@ function cmdAdd(source, global) {
1746
1933
  mkdirSync5(skillsRoot(scope), { recursive: true });
1747
1934
  cpSync(skillDir, dest, { recursive: true });
1748
1935
  const nestedGit = join7(dest, ".git");
1749
- if (existsSync6(nestedGit)) rmSync3(nestedGit, { recursive: true, force: true });
1936
+ if (existsSync7(nestedGit)) rmSync3(nestedGit, { recursive: true, force: true });
1750
1937
  console.log(c.green(`
1751
1938
  Installed ${scope} skill "${skillName}".`));
1752
1939
  console.log(c.dim(" " + join7(dest, "SKILL.md") + "\n"));
1753
1940
  } finally {
1754
- if (tempDir && existsSync6(tempDir)) {
1941
+ if (tempDir && existsSync7(tempDir)) {
1755
1942
  try {
1756
1943
  rmSync3(tempDir, { recursive: true, force: true });
1757
1944
  } catch {
@@ -1767,7 +1954,7 @@ async function cmdRemove(name, ask) {
1767
1954
  }
1768
1955
  const wsDir = join7(skillsRoot("workspace"), clean);
1769
1956
  const globalDir = join7(skillsRoot("global"), clean);
1770
- const target = existsSync6(wsDir) ? wsDir : existsSync6(globalDir) ? globalDir : "";
1957
+ const target = existsSync7(wsDir) ? wsDir : existsSync7(globalDir) ? globalDir : "";
1771
1958
  if (!target) {
1772
1959
  console.log(c.dim(`
1773
1960
  No skill named "${clean}" found.
@@ -2506,7 +2693,7 @@ async function graph(args = []) {
2506
2693
  }
2507
2694
 
2508
2695
  // src/cli.ts
2509
- var VERSION = "0.5.2";
2696
+ var VERSION = "0.6.0";
2510
2697
  var BANNER = `
2511
2698
  \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
2512
2699
  \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@holt-os/holt",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "description": "An open-source personal agent OS: any LLM, private memory you can see and walk.",
5
5
  "type": "module",
6
6
  "license": "MIT",